From 5016f605b89400b0dc1e12a19c6c2e223b098497 Mon Sep 17 00:00:00 2001 From: Brian Harris Date: Mon, 26 Nov 2012 12:58:24 -0600 Subject: [PATCH] Initial commit --- COPYING.txt | 682 + README.txt | 257 + base/default.cfg | 1 + base/renderprogs/bink.pixel | 60 + base/renderprogs/bink.vertex | 51 + base/renderprogs/bink_gui.pixel | 66 + base/renderprogs/bink_gui.vertex | 56 + base/renderprogs/blendLight.pixel | 46 + base/renderprogs/blendLight.vertex | 59 + base/renderprogs/bloodorb1_capture.pixel | 52 + base/renderprogs/bloodorb1_capture.vertex | 59 + base/renderprogs/bloodorb2_capture.pixel | 58 + base/renderprogs/bloodorb2_capture.vertex | 69 + base/renderprogs/bloodorb3_capture.pixel | 70 + base/renderprogs/bloodorb3_capture.vertex | 76 + base/renderprogs/bloodorb_draw.pixel | 51 + base/renderprogs/bloodorb_draw.vertex | 52 + base/renderprogs/bumpyenvironment.pixel | 67 + base/renderprogs/bumpyenvironment.vertex | 80 + .../bumpyenvironment_skinned.pixel | 67 + .../bumpyenvironment_skinned.vertex | 137 + base/renderprogs/color.pixel | 39 + base/renderprogs/color.vertex | 48 + base/renderprogs/colorProcess.pixel | 47 + base/renderprogs/colorProcess.vertex | 61 + base/renderprogs/depth.pixel | 38 + base/renderprogs/depth.vertex | 44 + base/renderprogs/depth_skinned.pixel | 38 + base/renderprogs/depth_skinned.vertex | 85 + base/renderprogs/enviroSuit.pixel | 57 + base/renderprogs/enviroSuit.vertex | 62 + base/renderprogs/environment.pixel | 56 + base/renderprogs/environment.vertex | 59 + base/renderprogs/environment_skinned.pixel | 56 + base/renderprogs/environment_skinned.vertex | 105 + base/renderprogs/fog.pixel | 47 + base/renderprogs/fog.vertex | 56 + base/renderprogs/fog_skinned.pixel | 47 + base/renderprogs/fog_skinned.vertex | 96 + base/renderprogs/fxaa.pixel | 110 + base/renderprogs/fxaa.vertex | 47 + base/renderprogs/global.inc | 176 + base/renderprogs/gui.pixel | 48 + base/renderprogs/gui.vertex | 56 + base/renderprogs/heatHazeWithMask.pixel | 66 + base/renderprogs/heatHazeWithMask.vertex | 84 + .../heatHazeWithMaskAndVertex.pixel | 68 + .../heatHazeWithMaskAndVertex.vertex | 86 + base/renderprogs/heathaze.pixel | 57 + base/renderprogs/heathaze.vertex | 82 + base/renderprogs/interaction.pixel | 78 + base/renderprogs/interaction.vertex | 120 + base/renderprogs/interactionAmbient.pixel | 78 + base/renderprogs/interactionAmbient.vertex | 113 + .../interactionAmbient_skinned.pixel | 78 + .../interactionAmbient_skinned.vertex | 171 + base/renderprogs/interaction_skinned.pixel | 78 + base/renderprogs/interaction_skinned.vertex | 185 + base/renderprogs/motionBlur.pixel | 92 + base/renderprogs/motionBlur.vertex | 44 + base/renderprogs/postprocess.pixel | 45 + base/renderprogs/postprocess.vertex | 52 + base/renderprogs/shadow.pixel | 38 + base/renderprogs/shadow.vertex | 47 + base/renderprogs/shadowDebug.pixel | 38 + base/renderprogs/shadowDebug.vertex | 47 + base/renderprogs/shadowDebug_skinned.pixel | 38 + base/renderprogs/shadowDebug_skinned.vertex | 91 + base/renderprogs/shadow_skinned.pixel | 38 + base/renderprogs/shadow_skinned.vertex | 91 + base/renderprogs/simpleshade.pixel | 62 + base/renderprogs/simpleshade.vertex | 53 + base/renderprogs/skinning.inc | 71 + base/renderprogs/skybox.pixel | 45 + base/renderprogs/skybox.vertex | 54 + base/renderprogs/stereoDeGhost.pixel | 39 + base/renderprogs/stereoDeGhost.vertex | 48 + base/renderprogs/stereoInterlace.pixel | 49 + base/renderprogs/stereoInterlace.vertex | 48 + base/renderprogs/stereoWarp.pixel | 74 + base/renderprogs/stereoWarp.vertex | 47 + base/renderprogs/texture.pixel | 44 + base/renderprogs/texture.vertex | 58 + base/renderprogs/texture_color.pixel | 47 + base/renderprogs/texture_color.vertex | 62 + base/renderprogs/texture_color_skinned.pixel | 47 + base/renderprogs/texture_color_skinned.vertex | 100 + base/renderprogs/texture_color_texgen.pixel | 52 + base/renderprogs/texture_color_texgen.vertex | 66 + base/renderprogs/wobblesky.pixel | 45 + base/renderprogs/wobblesky.vertex | 57 + base/renderprogs/zcullReconstruct.pixel | 39 + base/renderprogs/zcullReconstruct.vertex | 42 + doomclassic/DoomClassicCommon.props | 19 + doomclassic/doom/DoomLeaderboards.cpp | 147 + doomclassic/doom/DoomLeaderboards.h | 45 + doomclassic/doom/Main.h | 102 + doomclassic/doom/Networking.cpp | 166 + doomclassic/doom/PlayerProfile.cpp | 649 + doomclassic/doom/PlayerProfile.h | 267 + doomclassic/doom/Precompiled.cpp | 29 + doomclassic/doom/Precompiled.h | 69 + doomclassic/doom/am_map.cpp | 1224 ++ doomclassic/doom/am_map.h | 55 + doomclassic/doom/constructs.h | 540 + doomclassic/doom/d_englsh.h | 725 + doomclassic/doom/d_event.h | 125 + doomclassic/doom/d_french.h | 29 + doomclassic/doom/d_items.cpp | 145 + doomclassic/doom/d_items.h | 54 + doomclassic/doom/d_main.cpp | 866 ++ doomclassic/doom/d_main.h | 75 + doomclassic/doom/d_net.cpp | 831 ++ doomclassic/doom/d_net.h | 154 + doomclassic/doom/d_player.h | 228 + doomclassic/doom/d_textur.h | 52 + doomclassic/doom/d_think.h | 78 + doomclassic/doom/d_ticcmd.h | 55 + doomclassic/doom/defs.h | 442 + doomclassic/doom/doomdata.h | 223 + doomclassic/doom/doomdef.cpp | 43 + doomclassic/doom/doomdef.h | 343 + doomclassic/doom/doominterface.cpp | 305 + doomclassic/doom/doominterface.h | 74 + doomclassic/doom/doomlib.cpp | 554 + doomclassic/doom/doomlib.h | 169 + doomclassic/doom/doomstat.cpp | 48 + doomclassic/doom/doomstat.h | 285 + doomclassic/doom/doomtype.h | 45 + doomclassic/doom/dstrings.cpp | 78 + doomclassic/doom/dstrings.h | 59 + doomclassic/doom/f_finale.cpp | 816 ++ doomclassic/doom/f_finale.h | 55 + doomclassic/doom/f_wipe.cpp | 235 + doomclassic/doom/f_wipe.h | 61 + doomclassic/doom/g_game.cpp | 2015 +++ doomclassic/doom/g_game.h | 84 + doomclassic/doom/globaldata.cpp | 106 + doomclassic/doom/globaldata.h | 71 + doomclassic/doom/hu_lib.cpp | 358 + doomclassic/doom/hu_lib.h | 194 + doomclassic/doom/hu_stuff.cpp | 625 + doomclassic/doom/hu_stuff.h | 70 + doomclassic/doom/i_input.cpp | 474 + doomclassic/doom/i_main.cpp | 31 + doomclassic/doom/i_net.cpp | 59 + doomclassic/doom/i_net.h | 49 + doomclassic/doom/i_net_ps3.cpp | 423 + doomclassic/doom/i_net_win32.cpp | 278 + doomclassic/doom/i_net_xbox.cpp | 365 + doomclassic/doom/i_sound.h | 113 + doomclassic/doom/i_sound_win32.cpp | 1084 ++ doomclassic/doom/i_system.cpp | 189 + doomclassic/doom/i_system.h | 88 + doomclassic/doom/i_video.h | 76 + doomclassic/doom/i_video_ps3.cpp | 186 + doomclassic/doom/i_video_xbox.cpp | 174 + doomclassic/doom/info.cpp | 4675 ++++++ doomclassic/doom/info.h | 1347 ++ doomclassic/doom/m_argv.cpp | 61 + doomclassic/doom/m_argv.h | 44 + doomclassic/doom/m_bbox.cpp | 68 + doomclassic/doom/m_bbox.h | 55 + doomclassic/doom/m_cheat.cpp | 127 + doomclassic/doom/m_cheat.h | 62 + doomclassic/doom/m_fixed.cpp | 92 + doomclassic/doom/m_fixed.h | 53 + doomclassic/doom/m_menu.cpp | 1707 +++ doomclassic/doom/m_menu.h | 69 + doomclassic/doom/m_misc.cpp | 372 + doomclassic/doom/m_misc.h | 66 + doomclassic/doom/m_random.cpp | 81 + doomclassic/doom/m_random.h | 49 + doomclassic/doom/m_swap.cpp | 45 + doomclassic/doom/m_swap.h | 73 + doomclassic/doom/mus2midi.cpp | 358 + doomclassic/doom/p_ceilng.cpp | 341 + doomclassic/doom/p_doors.cpp | 560 + doomclassic/doom/p_enemy.cpp | 2028 +++ doomclassic/doom/p_floor.cpp | 561 + doomclassic/doom/p_inter.cpp | 1073 ++ doomclassic/doom/p_inter.h | 43 + doomclassic/doom/p_lights.cpp | 362 + doomclassic/doom/p_local.h | 282 + doomclassic/doom/p_map.cpp | 1353 ++ doomclassic/doom/p_maputl.cpp | 877 ++ doomclassic/doom/p_mobj.cpp | 1012 ++ doomclassic/doom/p_mobj.h | 297 + doomclassic/doom/p_plats.cpp | 319 + doomclassic/doom/p_pspr.cpp | 976 ++ doomclassic/doom/p_pspr.h | 84 + doomclassic/doom/p_saveg.cpp | 1038 ++ doomclassic/doom/p_saveg.h | 53 + doomclassic/doom/p_setup.cpp | 738 + doomclassic/doom/p_setup.h | 50 + doomclassic/doom/p_sight.cpp | 348 + doomclassic/doom/p_spec.cpp | 1424 ++ doomclassic/doom/p_spec.h | 647 + doomclassic/doom/p_switch.cpp | 660 + doomclassic/doom/p_telept.cpp | 138 + doomclassic/doom/p_tick.cpp | 169 + doomclassic/doom/p_tick.h | 46 + doomclassic/doom/p_user.cpp | 450 + doomclassic/doom/r_bsp.cpp | 559 + doomclassic/doom/r_bsp.h | 74 + doomclassic/doom/r_data.cpp | 776 + doomclassic/doom/r_data.h | 63 + doomclassic/doom/r_defs.h | 494 + doomclassic/doom/r_draw.cpp | 839 ++ doomclassic/doom/r_draw.h | 141 + doomclassic/doom/r_local.h | 55 + doomclassic/doom/r_main.cpp | 897 ++ doomclassic/doom/r_main.h | 186 + doomclassic/doom/r_plane.cpp | 437 + doomclassic/doom/r_plane.h | 89 + doomclassic/doom/r_segs.cpp | 718 + doomclassic/doom/r_segs.h | 46 + doomclassic/doom/r_sky.cpp | 61 + doomclassic/doom/r_sky.h | 50 + doomclassic/doom/r_state.h | 137 + doomclassic/doom/r_things.cpp | 969 ++ doomclassic/doom/r_things.h | 78 + doomclassic/doom/s_sound.cpp | 681 + doomclassic/doom/s_sound.h | 111 + doomclassic/doom/sounds.cpp | 161 + doomclassic/doom/sounds.h | 299 + doomclassic/doom/st_lib.cpp | 296 + doomclassic/doom/st_lib.h | 229 + doomclassic/doom/st_stuff.cpp | 1474 ++ doomclassic/doom/st_stuff.h | 87 + doomclassic/doom/structs.h | 487 + doomclassic/doom/tables.cpp | 2121 +++ doomclassic/doom/tables.h | 89 + doomclassic/doom/typedefs.h | 2513 ++++ doomclassic/doom/v_video.cpp | 519 + doomclassic/doom/v_video.h | 119 + doomclassic/doom/vars.h | 909 ++ doomclassic/doom/w_wad.cpp | 497 + doomclassic/doom/w_wad.h | 93 + doomclassic/doom/wi_stuff.cpp | 1756 +++ doomclassic/doom/wi_stuff.h | 47 + doomclassic/doom/z_zone.cpp | 477 + doomclassic/doom/z_zone.h | 113 + doomclassic/doomclassic.vcxproj | 228 + doomclassic/doomclassic.vcxproj.filters | 411 + doomclassic/doomclassic.vcxproj.vspscc | 10 + doomclassic/timidity/FAQ | 112 + doomclassic/timidity/README | 60 + doomclassic/timidity/common.cpp | 178 + doomclassic/timidity/common.h | 42 + doomclassic/timidity/config.h | 206 + doomclassic/timidity/controls.cpp | 41 + doomclassic/timidity/controls.h | 89 + doomclassic/timidity/filter.cpp | 205 + doomclassic/timidity/filter.h | 35 + doomclassic/timidity/instrum.cpp | 683 + doomclassic/timidity/instrum.h | 84 + doomclassic/timidity/mix.cpp | 569 + doomclassic/timidity/mix.h | 27 + doomclassic/timidity/output.cpp | 138 + doomclassic/timidity/output.h | 70 + doomclassic/timidity/playmidi.cpp | 1018 ++ doomclassic/timidity/playmidi.h | 113 + doomclassic/timidity/readmidi.cpp | 758 + doomclassic/timidity/readmidi.h | 34 + doomclassic/timidity/resample.cpp | 738 + doomclassic/timidity/resample.h | 24 + doomclassic/timidity/sdl_a.cpp | 59 + doomclassic/timidity/sdl_c.cpp | 138 + doomclassic/timidity/structs.h | 40 + doomclassic/timidity/tables.cpp | 901 ++ doomclassic/timidity/tables.h | 45 + doomclassic/timidity/timidity.cpp | 400 + doomclassic/timidity/timidity.h | 70 + doomclassic/timidity/timidity.vcxproj | 116 + doomclassic/timidity/timidity.vcxproj.filters | 33 + doomclassic/timidity/timidity.vcxproj.vspscc | 10 + neo/_Common.props | 21 + neo/_Debug.props | 23 + neo/_Dedicated.props | 12 + neo/_DoomExe.props | 18 + neo/_Game-d3xp.props | 21 + neo/_Game.props | 22 + neo/_PCLibs.props | 25 + neo/_Release.props | 27 + neo/_WithInlines.props | 13 + neo/_WithMemoryLog.props | 12 + neo/_external.props | 14 + neo/_idlib.props | 13 + neo/aas/AASFile.cpp | 1317 ++ neo/aas/AASFile.h | 351 + neo/aas/AASFileManager.cpp | 77 + neo/aas/AASFileManager.h | 50 + neo/aas/AASFile_local.h | 99 + neo/aas/AASFile_optimize.cpp | 155 + neo/aas/AASFile_sample.cpp | 609 + neo/amplitude/amplitude.cpp | 343 + neo/amplitude/amplitude.vcxproj | 126 + neo/amplitude/amplitude.vcxproj.filters | 6 + neo/amplitude/amplitude.vcxproj.vspscc | 10 + neo/cm/CollisionModel.h | 150 + neo/cm/CollisionModel_contacts.cpp | 76 + neo/cm/CollisionModel_contents.cpp | 636 + neo/cm/CollisionModel_debug.cpp | 489 + neo/cm/CollisionModel_files.cpp | 671 + neo/cm/CollisionModel_load.cpp | 4068 ++++++ neo/cm/CollisionModel_local.h | 539 + neo/cm/CollisionModel_rotate.cpp | 1695 +++ neo/cm/CollisionModel_trace.cpp | 257 + neo/cm/CollisionModel_translate.cpp | 1086 ++ neo/d3xp/AF.cpp | 1272 ++ neo/d3xp/AF.h | 120 + neo/d3xp/AFEntity.cpp | 3676 +++++ neo/d3xp/AFEntity.h | 597 + neo/d3xp/Achievements.cpp | 537 + neo/d3xp/Achievements.h | 184 + neo/d3xp/Actor.cpp | 3474 +++++ neo/d3xp/Actor.h | 333 + neo/d3xp/AimAssist.cpp | 436 + neo/d3xp/AimAssist.h | 75 + neo/d3xp/BrittleFracture.cpp | 1386 ++ neo/d3xp/BrittleFracture.h | 141 + neo/d3xp/Camera.cpp | 636 + neo/d3xp/Camera.h | 132 + neo/d3xp/EndLevel.cpp | 185 + neo/d3xp/EndLevel.h | 66 + neo/d3xp/Entity.cpp | 5858 ++++++++ neo/d3xp/Entity.h | 711 + neo/d3xp/Fx.cpp | 833 ++ neo/d3xp/Fx.h | 107 + neo/d3xp/Game.def | 2 + neo/d3xp/Game.h | 343 + neo/d3xp/GameEdit.cpp | 1142 ++ neo/d3xp/GameEdit.h | 119 + neo/d3xp/Game_local.cpp | 5085 +++++++ neo/d3xp/Game_local.h | 819 ++ neo/d3xp/Game_network.cpp | 1328 ++ neo/d3xp/Grabber.cpp | 739 + neo/d3xp/Grabber.h | 84 + neo/d3xp/IK.cpp | 1128 ++ neo/d3xp/IK.h | 182 + neo/d3xp/Item.cpp | 2138 +++ neo/d3xp/Item.h | 324 + neo/d3xp/Leaderboards.cpp | 332 + neo/d3xp/Leaderboards.h | 74 + neo/d3xp/Light.cpp | 1182 ++ neo/d3xp/Light.h | 142 + neo/d3xp/Misc.cpp | 3934 +++++ neo/d3xp/Misc.h | 928 ++ neo/d3xp/Moveable.cpp | 1355 ++ neo/d3xp/Moveable.h | 210 + neo/d3xp/Mover.cpp | 4852 +++++++ neo/d3xp/Mover.h | 556 + neo/d3xp/MultiplayerGame.cpp | 3188 +++++ neo/d3xp/MultiplayerGame.h | 357 + neo/d3xp/Player.cpp | 10742 ++++++++++++++ neo/d3xp/Player.h | 924 ++ neo/d3xp/PlayerIcon.cpp | 190 + neo/d3xp/PlayerIcon.h | 65 + neo/d3xp/PlayerView.cpp | 1749 +++ neo/d3xp/PlayerView.h | 417 + neo/d3xp/PredictedValue.h | 78 + neo/d3xp/PredictedValue_impl.h | 220 + neo/d3xp/Projectile.cpp | 2989 ++++ neo/d3xp/Projectile.h | 339 + neo/d3xp/Pvs.cpp | 1421 ++ neo/d3xp/Pvs.h | 126 + neo/d3xp/SecurityCamera.cpp | 587 + neo/d3xp/SecurityCamera.h | 97 + neo/d3xp/SmokeParticles.cpp | 438 + neo/d3xp/SmokeParticles.h | 103 + neo/d3xp/Sound.cpp | 304 + neo/d3xp/Sound.h | 76 + neo/d3xp/Target.cpp | 1921 +++ neo/d3xp/Target.h | 615 + neo/d3xp/Trigger.cpp | 1357 ++ neo/d3xp/Trigger.h | 312 + neo/d3xp/Weapon.cpp | 4195 ++++++ neo/d3xp/Weapon.h | 447 + neo/d3xp/WorldSpawn.cpp | 143 + neo/d3xp/WorldSpawn.h | 55 + neo/d3xp/ai/AAS.cpp | 276 + neo/d3xp/ai/AAS.h | 141 + neo/d3xp/ai/AAS_debug.cpp | 508 + neo/d3xp/ai/AAS_local.h | 189 + neo/d3xp/ai/AAS_pathing.cpp | 717 + neo/d3xp/ai/AAS_routing.cpp | 1350 ++ neo/d3xp/ai/AI.cpp | 5337 +++++++ neo/d3xp/ai/AI.h | 731 + neo/d3xp/ai/AI_Vagary.cpp | 149 + neo/d3xp/ai/AI_events.cpp | 2986 ++++ neo/d3xp/ai/AI_pathing.cpp | 1535 ++ neo/d3xp/anim/Anim.cpp | 1177 ++ neo/d3xp/anim/Anim.h | 598 + neo/d3xp/anim/Anim_Blend.cpp | 5114 +++++++ neo/d3xp/anim/Anim_Testmodel.cpp | 901 ++ neo/d3xp/anim/Anim_Testmodel.h | 95 + neo/d3xp/gamesys/Callbacks.cpp | 2626 ++++ neo/d3xp/gamesys/Class.cpp | 1025 ++ neo/d3xp/gamesys/Class.h | 349 + neo/d3xp/gamesys/Event.cpp | 1062 ++ neo/d3xp/gamesys/Event.h | 210 + neo/d3xp/gamesys/SaveGame.cpp | 1601 +++ neo/d3xp/gamesys/SaveGame.h | 182 + neo/d3xp/gamesys/SysCmds.cpp | 2397 ++++ neo/d3xp/gamesys/SysCmds.h | 34 + neo/d3xp/gamesys/SysCvar.cpp | 323 + neo/d3xp/gamesys/SysCvar.h | 274 + neo/d3xp/menus/MenuHandler.cpp | 449 + neo/d3xp/menus/MenuHandler.h | 516 + neo/d3xp/menus/MenuHandler_HUD.cpp | 182 + neo/d3xp/menus/MenuHandler_PDA.cpp | 599 + neo/d3xp/menus/MenuHandler_Scoreboard.cpp | 510 + neo/d3xp/menus/MenuHandler_Shell.cpp | 1568 ++ neo/d3xp/menus/MenuScreen.cpp | 330 + neo/d3xp/menus/MenuScreen.h | 1642 +++ neo/d3xp/menus/MenuScreen_HUD.cpp | 1967 +++ neo/d3xp/menus/MenuScreen_PDA_Inventory.cpp | 384 + neo/d3xp/menus/MenuScreen_PDA_UserData.cpp | 247 + neo/d3xp/menus/MenuScreen_PDA_UserEmails.cpp | 478 + neo/d3xp/menus/MenuScreen_PDA_VideoDisks.cpp | 322 + neo/d3xp/menus/MenuScreen_Scoreboard.cpp | 570 + neo/d3xp/menus/MenuScreen_Shell_Bindings.cpp | 527 + neo/d3xp/menus/MenuScreen_Shell_Browser.cpp | 403 + .../MenuScreen_Shell_ControllerLayout.cpp | 389 + neo/d3xp/menus/MenuScreen_Shell_Controls.cpp | 387 + neo/d3xp/menus/MenuScreen_Shell_Credits.cpp | 934 ++ neo/d3xp/menus/MenuScreen_Shell_Dev.cpp | 268 + .../menus/MenuScreen_Shell_Difficulty.cpp | 293 + neo/d3xp/menus/MenuScreen_Shell_GameLobby.cpp | 706 + .../menus/MenuScreen_Shell_GameOptions.cpp | 371 + neo/d3xp/menus/MenuScreen_Shell_Gamepad.cpp | 464 + .../menus/MenuScreen_Shell_Leaderboards.cpp | 1037 ++ neo/d3xp/menus/MenuScreen_Shell_Load.cpp | 465 + .../menus/MenuScreen_Shell_MatchSettings.cpp | 465 + .../menus/MenuScreen_Shell_ModeSelect.cpp | 228 + neo/d3xp/menus/MenuScreen_Shell_NewGame.cpp | 206 + .../menus/MenuScreen_Shell_PartyLobby.cpp | 697 + neo/d3xp/menus/MenuScreen_Shell_Pause.cpp | 481 + .../menus/MenuScreen_Shell_Playstation.cpp | 207 + .../menus/MenuScreen_Shell_PressStart.cpp | 302 + .../menus/MenuScreen_Shell_Resolution.cpp | 295 + neo/d3xp/menus/MenuScreen_Shell_Root.cpp | 514 + neo/d3xp/menus/MenuScreen_Shell_Save.cpp | 494 + neo/d3xp/menus/MenuScreen_Shell_Settings.cpp | 248 + .../menus/MenuScreen_Shell_Singleplayer.cpp | 314 + .../menus/MenuScreen_Shell_Stereoscopics.cpp | 422 + .../menus/MenuScreen_Shell_SystemOptions.cpp | 549 + neo/d3xp/menus/MenuWidget.cpp | 545 + neo/d3xp/menus/MenuWidget.h | 1341 ++ neo/d3xp/menus/MenuWidget_Button.cpp | 600 + neo/d3xp/menus/MenuWidget_Carousel.cpp | 265 + neo/d3xp/menus/MenuWidget_CommandBar.cpp | 198 + neo/d3xp/menus/MenuWidget_DevList.cpp | 219 + neo/d3xp/menus/MenuWidget_DynamicList.cpp | 236 + neo/d3xp/menus/MenuWidget_Help.cpp | 139 + neo/d3xp/menus/MenuWidget_InfoBox.cpp | 208 + neo/d3xp/menus/MenuWidget_ItemAssignment.cpp | 104 + neo/d3xp/menus/MenuWidget_List.cpp | 408 + neo/d3xp/menus/MenuWidget_LobbyList.cpp | 126 + neo/d3xp/menus/MenuWidget_MenuBar.cpp | 147 + neo/d3xp/menus/MenuWidget_NavBar.cpp | 175 + neo/d3xp/menus/MenuWidget_NavButton.cpp | 183 + neo/d3xp/menus/MenuWidget_PDA_AudioFiles.cpp | 188 + neo/d3xp/menus/MenuWidget_PDA_EmailInbox.cpp | 178 + neo/d3xp/menus/MenuWidget_PDA_Objective.cpp | 156 + neo/d3xp/menus/MenuWidget_PDA_UserData.cpp | 168 + neo/d3xp/menus/MenuWidget_PDA_VideoInfo.cpp | 116 + neo/d3xp/menus/MenuWidget_Scrollbar.cpp | 261 + neo/d3xp/menus/MenuWidget_Shell_SaveInfo.cpp | 161 + neo/d3xp/physics/Clip.cpp | 1756 +++ neo/d3xp/physics/Clip.h | 353 + neo/d3xp/physics/Force.cpp | 94 + neo/d3xp/physics/Force.h | 66 + neo/d3xp/physics/Force_Constant.cpp | 135 + neo/d3xp/physics/Force_Constant.h | 71 + neo/d3xp/physics/Force_Drag.cpp | 155 + neo/d3xp/physics/Force_Drag.h | 74 + neo/d3xp/physics/Force_Field.cpp | 258 + neo/d3xp/physics/Force_Field.h | 94 + neo/d3xp/physics/Force_Grab.cpp | 186 + neo/d3xp/physics/Force_Grab.h | 79 + neo/d3xp/physics/Force_Spring.cpp | 165 + neo/d3xp/physics/Force_Spring.h | 75 + neo/d3xp/physics/Physics.cpp | 78 + neo/d3xp/physics/Physics.h | 188 + neo/d3xp/physics/Physics_AF.cpp | 8013 +++++++++++ neo/d3xp/physics/Physics_AF.h | 1047 ++ neo/d3xp/physics/Physics_Actor.cpp | 383 + neo/d3xp/physics/Physics_Actor.h | 113 + neo/d3xp/physics/Physics_Base.cpp | 855 ++ neo/d3xp/physics/Physics_Base.h | 204 + neo/d3xp/physics/Physics_Monster.cpp | 807 ++ neo/d3xp/physics/Physics_Monster.h | 156 + neo/d3xp/physics/Physics_Parametric.cpp | 1086 ++ neo/d3xp/physics/Physics_Parametric.h | 180 + neo/d3xp/physics/Physics_Player.cpp | 2178 +++ neo/d3xp/physics/Physics_Player.h | 217 + neo/d3xp/physics/Physics_RigidBody.cpp | 1539 ++ neo/d3xp/physics/Physics_RigidBody.h | 219 + neo/d3xp/physics/Physics_Static.cpp | 924 ++ neo/d3xp/physics/Physics_Static.h | 189 + neo/d3xp/physics/Physics_StaticMulti.cpp | 1082 ++ neo/d3xp/physics/Physics_StaticMulti.h | 160 + neo/d3xp/physics/Push.cpp | 1448 ++ neo/d3xp/physics/Push.h | 113 + neo/d3xp/precompiled.cpp | 29 + neo/d3xp/script/Script_Compiler.cpp | 2648 ++++ neo/d3xp/script/Script_Compiler.h | 278 + neo/d3xp/script/Script_Interpreter.cpp | 1842 +++ neo/d3xp/script/Script_Interpreter.h | 271 + neo/d3xp/script/Script_Program.cpp | 2145 +++ neo/d3xp/script/Script_Program.h | 626 + neo/d3xp/script/Script_Thread.cpp | 1921 +++ neo/d3xp/script/Script_Thread.h | 334 + neo/doom3.sln | 73 + neo/doomexe.vcxproj | 550 + neo/doomexe.vcxproj.filters | 1153 ++ neo/external.vcxproj | 192 + neo/external.vcxproj.filters | 216 + neo/framework/BuildVersion.h | 32 + neo/framework/CVarSystem.cpp | 1206 ++ neo/framework/CVarSystem.h | 311 + neo/framework/CmdSystem.cpp | 801 ++ neo/framework/CmdSystem.h | 259 + neo/framework/Common.cpp | 1709 +++ neo/framework/Common.h | 316 + neo/framework/Common_demos.cpp | 518 + neo/framework/Common_dialog.cpp | 1400 ++ neo/framework/Common_dialog.h | 316 + neo/framework/Common_load.cpp | 1208 ++ neo/framework/Common_local.h | 504 + neo/framework/Common_localize.cpp | 674 + neo/framework/Common_menu.cpp | 188 + neo/framework/Common_network.cpp | 639 + neo/framework/Common_printf.cpp | 543 + neo/framework/Compressor.cpp | 2575 ++++ neo/framework/Compressor.h | 73 + neo/framework/Console.cpp | 1249 ++ neo/framework/Console.h | 90 + neo/framework/ConsoleHistory.cpp | 189 + neo/framework/ConsoleHistory.h | 79 + neo/framework/DebugGraph.cpp | 191 + neo/framework/DebugGraph.h | 103 + neo/framework/DeclAF.cpp | 1747 +++ neo/framework/DeclAF.h | 216 + neo/framework/DeclEntityDef.cpp | 149 + neo/framework/DeclEntityDef.h | 51 + neo/framework/DeclFX.cpp | 473 + neo/framework/DeclFX.h | 113 + neo/framework/DeclManager.cpp | 2433 ++++ neo/framework/DeclManager.h | 339 + neo/framework/DeclPDA.cpp | 609 + neo/framework/DeclPDA.h | 162 + neo/framework/DeclParticle.cpp | 1642 +++ neo/framework/DeclParticle.h | 224 + neo/framework/DeclSkin.cpp | 185 + neo/framework/DeclSkin.h | 64 + neo/framework/DeclTable.cpp | 177 + neo/framework/DeclTable.h | 56 + neo/framework/DemoChecksum.h | 39 + neo/framework/DemoFile.cpp | 328 + neo/framework/DemoFile.h | 88 + neo/framework/EditField.cpp | 604 + neo/framework/EditField.h | 79 + neo/framework/EventLoop.cpp | 274 + neo/framework/EventLoop.h | 88 + neo/framework/File.cpp | 1808 +++ neo/framework/File.h | 374 + neo/framework/FileSystem.cpp | 3221 +++++ neo/framework/FileSystem.h | 206 + neo/framework/File_Manifest.cpp | 200 + neo/framework/File_Manifest.h | 279 + neo/framework/File_Resource.cpp | 501 + neo/framework/File_Resource.h | 110 + neo/framework/File_SaveGame.cpp | 1150 ++ neo/framework/File_SaveGame.h | 252 + neo/framework/KeyInput.cpp | 831 ++ neo/framework/KeyInput.h | 72 + neo/framework/Licensee.h | 58 + neo/framework/PlayerProfile.cpp | 408 + neo/framework/PlayerProfile.h | 135 + neo/framework/Serializer.h | 549 + neo/framework/Session.cpp | 1256 ++ neo/framework/TokenParser.cpp | 254 + neo/framework/TokenParser.h | 152 + neo/framework/Unzip.cpp | 1222 ++ neo/framework/Unzip.h | 326 + neo/framework/UsercmdGen.cpp | 1342 ++ neo/framework/UsercmdGen.h | 400 + neo/framework/Zip.cpp | 2054 +++ neo/framework/Zip.h | 305 + neo/framework/common_frame.cpp | 748 + neo/framework/precompiled.cpp | 29 + neo/framework/zlib/ChangeLog | 855 ++ neo/framework/zlib/FAQ | 339 + neo/framework/zlib/INDEX | 51 + neo/framework/zlib/Makefile | 154 + neo/framework/zlib/Makefile.in | 154 + neo/framework/zlib/README | 125 + neo/framework/zlib/adler32.c | 149 + neo/framework/zlib/algorithm.txt | 209 + neo/framework/zlib/compress.c | 79 + neo/framework/zlib/configure | 459 + neo/framework/zlib/crc32.c | 423 + neo/framework/zlib/crc32.h | 441 + neo/framework/zlib/deflate.c | 1736 +++ neo/framework/zlib/deflate.h | 331 + neo/framework/zlib/example.c | 565 + neo/framework/zlib/gzio.c | 1026 ++ neo/framework/zlib/infback.c | 623 + neo/framework/zlib/inffast.c | 318 + neo/framework/zlib/inffast.h | 11 + neo/framework/zlib/inffixed.h | 94 + neo/framework/zlib/inflate.c | 1368 ++ neo/framework/zlib/inflate.h | 115 + neo/framework/zlib/inftrees.c | 329 + neo/framework/zlib/inftrees.h | 55 + neo/framework/zlib/minigzip.c | 322 + neo/framework/zlib/trees.c | 1219 ++ neo/framework/zlib/trees.h | 128 + neo/framework/zlib/uncompr.c | 61 + neo/framework/zlib/zconf.h | 332 + neo/framework/zlib/zconf.in.h | 332 + neo/framework/zlib/zlib.3 | 159 + neo/framework/zlib/zlib.h | 1357 ++ neo/framework/zlib/zutil.c | 318 + neo/framework/zlib/zutil.h | 269 + neo/game-d3xp.vcxproj | 328 + neo/game-d3xp.vcxproj.filters | 492 + neo/game.vcxproj | 284 + neo/game.vcxproj.filters | 297 + neo/idlib.vcxproj | 253 + neo/idlib.vcxproj.filters | 397 + neo/idlib/Base64.cpp | 233 + neo/idlib/Base64.h | 111 + neo/idlib/BitMsg.cpp | 511 + neo/idlib/BitMsg.h | 958 ++ neo/idlib/Callback.h | 173 + neo/idlib/CmdArgs.cpp | 202 + neo/idlib/CmdArgs.h | 73 + neo/idlib/CommandLink.cpp | 59 + neo/idlib/DataQueue.h | 83 + neo/idlib/Dict.cpp | 943 ++ neo/idlib/Dict.h | 348 + neo/idlib/Heap.cpp | 86 + neo/idlib/Heap.h | 1079 ++ neo/idlib/LangDict.cpp | 600 + neo/idlib/LangDict.h | 150 + neo/idlib/Lexer.cpp | 1918 +++ neo/idlib/Lexer.h | 311 + neo/idlib/Lib.cpp | 608 + neo/idlib/Lib.h | 322 + neo/idlib/MapFile.cpp | 972 ++ neo/idlib/MapFile.h | 237 + neo/idlib/ParallelJobList.cpp | 1297 ++ neo/idlib/ParallelJobList.h | 177 + neo/idlib/ParallelJobList_JobHeaders.h | 67 + neo/idlib/Parser.cpp | 3288 +++++ neo/idlib/Parser.h | 284 + neo/idlib/RectAllocator.cpp | 171 + neo/idlib/SoftwareCache.cpp | 47 + neo/idlib/SoftwareCache.h | 513 + neo/idlib/Str.cpp | 2090 +++ neo/idlib/Str.h | 1215 ++ neo/idlib/StrStatic.h | 125 + neo/idlib/Swap.h | 223 + neo/idlib/Thread.cpp | 283 + neo/idlib/Thread.h | 465 + neo/idlib/Timer.cpp | 160 + neo/idlib/Timer.h | 223 + neo/idlib/Token.cpp | 179 + neo/idlib/Token.h | 165 + neo/idlib/bv/Bounds.cpp | 425 + neo/idlib/bv/Bounds.h | 428 + neo/idlib/bv/Box.cpp | 849 ++ neo/idlib/bv/Box.h | 297 + neo/idlib/bv/Sphere.cpp | 155 + neo/idlib/bv/Sphere.h | 275 + neo/idlib/containers/Array.h | 115 + neo/idlib/containers/BTree.h | 573 + neo/idlib/containers/BinSearch.h | 138 + neo/idlib/containers/HashIndex.cpp | 155 + neo/idlib/containers/HashIndex.h | 422 + neo/idlib/containers/HashTable.h | 897 ++ neo/idlib/containers/Hierarchy.h | 362 + neo/idlib/containers/LinkList.h | 343 + neo/idlib/containers/List.h | 1037 ++ neo/idlib/containers/PlaneSet.h | 81 + neo/idlib/containers/Queue.h | 211 + neo/idlib/containers/Sort.h | 338 + neo/idlib/containers/Stack.h | 86 + neo/idlib/containers/StaticList.h | 656 + neo/idlib/containers/StrList.h | 204 + neo/idlib/containers/StrPool.h | 228 + neo/idlib/containers/VectorSet.h | 270 + neo/idlib/geometry/DrawVert.cpp | 76 + neo/idlib/geometry/DrawVert.h | 739 + neo/idlib/geometry/DrawVert_intrinsics.h | 200 + neo/idlib/geometry/JointTransform.cpp | 87 + neo/idlib/geometry/JointTransform.h | 544 + neo/idlib/geometry/RenderMatrix.cpp | 3238 +++++ neo/idlib/geometry/RenderMatrix.h | 489 + neo/idlib/geometry/Surface.cpp | 929 ++ neo/idlib/geometry/Surface.h | 219 + neo/idlib/geometry/Surface_Patch.cpp | 698 + neo/idlib/geometry/Surface_Patch.h | 146 + neo/idlib/geometry/Surface_Polytope.cpp | 334 + neo/idlib/geometry/Surface_Polytope.h | 71 + neo/idlib/geometry/Surface_SweptSpline.cpp | 223 + neo/idlib/geometry/Surface_SweptSpline.h | 93 + neo/idlib/geometry/TraceModel.cpp | 1492 ++ neo/idlib/geometry/TraceModel.h | 189 + neo/idlib/geometry/Winding.cpp | 1600 +++ neo/idlib/geometry/Winding.h | 401 + neo/idlib/geometry/Winding2D.cpp | 753 + neo/idlib/geometry/Winding2D.h | 169 + neo/idlib/hashing/CRC32.cpp | 167 + neo/idlib/hashing/CRC32.h | 18 + neo/idlib/hashing/MD4.cpp | 259 + neo/idlib/hashing/MD4.h | 15 + neo/idlib/hashing/MD5.cpp | 304 + neo/idlib/hashing/MD5.h | 24 + neo/idlib/math/Angles.cpp | 240 + neo/idlib/math/Angles.h | 262 + neo/idlib/math/Complex.cpp | 41 + neo/idlib/math/Complex.h | 348 + neo/idlib/math/Curve.h | 2542 ++++ neo/idlib/math/Extrapolate.h | 219 + neo/idlib/math/Interpolate.h | 400 + neo/idlib/math/Lcp.cpp | 2949 ++++ neo/idlib/math/Lcp.h | 79 + neo/idlib/math/MatX.cpp | 5350 +++++++ neo/idlib/math/MatX.h | 1571 ++ neo/idlib/math/Math.cpp | 147 + neo/idlib/math/Math.h | 1326 ++ neo/idlib/math/Matrix.cpp | 2916 ++++ neo/idlib/math/Matrix.h | 1759 +++ neo/idlib/math/Ode.cpp | 355 + neo/idlib/math/Ode.h | 146 + neo/idlib/math/Plane.cpp | 154 + neo/idlib/math/Plane.h | 401 + neo/idlib/math/Pluecker.cpp | 86 + neo/idlib/math/Pluecker.h | 368 + neo/idlib/math/Polynomial.cpp | 242 + neo/idlib/math/Polynomial.h | 629 + neo/idlib/math/Quat.cpp | 307 + neo/idlib/math/Quat.h | 433 + neo/idlib/math/Random.h | 158 + neo/idlib/math/Rotation.cpp | 156 + neo/idlib/math/Rotation.h | 211 + neo/idlib/math/Simd.cpp | 1272 ++ neo/idlib/math/Simd.h | 109 + neo/idlib/math/Simd_Generic.cpp | 211 + neo/idlib/math/Simd_Generic.h | 61 + neo/idlib/math/Simd_SSE.cpp | 934 ++ neo/idlib/math/Simd_SSE.h | 52 + neo/idlib/math/VecX.cpp | 49 + neo/idlib/math/VecX.h | 802 ++ neo/idlib/math/Vector.cpp | 377 + neo/idlib/math/Vector.h | 1499 ++ neo/idlib/math/VectorI.h | 97 + neo/idlib/precompiled.cpp | 28 + neo/idlib/precompiled.h | 161 + neo/idlib/sys/sys_alloc_tags.h | 146 + neo/idlib/sys/sys_assert.cpp | 90 + neo/idlib/sys/sys_assert.h | 138 + neo/idlib/sys/sys_builddefines.h | 35 + neo/idlib/sys/sys_defines.h | 154 + neo/idlib/sys/sys_filesystem.h | 53 + neo/idlib/sys/sys_includes.h | 106 + neo/idlib/sys/sys_intrinsics.h | 200 + neo/idlib/sys/sys_threading.h | 168 + neo/idlib/sys/sys_types.h | 287 + neo/idlib/sys/win32/win_thread.cpp | 358 + neo/renderer/AutoRender.cpp | 279 + neo/renderer/AutoRender.h | 54 + neo/renderer/AutoRenderBink.cpp | 32 + neo/renderer/AutoRenderBink.h | 32 + neo/renderer/BinaryImage.cpp | 449 + neo/renderer/BinaryImage.h | 100 + neo/renderer/BinaryImageData.h | 68 + neo/renderer/BoundsTrack.cpp | 291 + neo/renderer/BoundsTrack.h | 72 + neo/renderer/BufferObject.cpp | 833 ++ neo/renderer/BufferObject.h | 202 + neo/renderer/Cinematic.cpp | 177 + neo/renderer/Cinematic.h | 126 + neo/renderer/Color/ColorSpace.cpp | 597 + neo/renderer/Color/ColorSpace.h | 64 + neo/renderer/DXT/DXTCodec.h | 604 + neo/renderer/DXT/DXTCodec_local.h | 38 + neo/renderer/DXT/DXTDecoder.cpp | 745 + neo/renderer/DXT/DXTEncoder.cpp | 4691 ++++++ neo/renderer/DXT/DXTEncoder_SSE2.cpp | 1580 +++ neo/renderer/Font.cpp | 414 + neo/renderer/Font.h | 103 + neo/renderer/GLMatrix.cpp | 423 + neo/renderer/GLMatrix.h | 64 + neo/renderer/GLState.h | 148 + neo/renderer/GraphicsAPIWrapper.h | 165 + neo/renderer/GuiModel.cpp | 371 + neo/renderer/GuiModel.h | 87 + neo/renderer/Image.h | 364 + neo/renderer/ImageManager.cpp | 878 ++ neo/renderer/ImageOpts.h | 157 + neo/renderer/Image_files.cpp | 765 + neo/renderer/Image_intrinsic.cpp | 430 + neo/renderer/Image_load.cpp | 715 + neo/renderer/Image_process.cpp | 492 + neo/renderer/Image_program.cpp | 659 + neo/renderer/Interaction.cpp | 846 ++ neo/renderer/Interaction.h | 147 + neo/renderer/Material.cpp | 2888 ++++ neo/renderer/Material.h | 710 + neo/renderer/Model.cpp | 2482 ++++ neo/renderer/Model.h | 306 + neo/renderer/ModelDecal.cpp | 756 + neo/renderer/ModelDecal.h | 132 + neo/renderer/ModelManager.cpp | 739 + neo/renderer/ModelManager.h | 102 + neo/renderer/ModelOverlay.cpp | 648 + neo/renderer/ModelOverlay.h | 110 + neo/renderer/Model_ase.cpp | 923 ++ neo/renderer/Model_ase.h | 95 + neo/renderer/Model_beam.cpp | 210 + neo/renderer/Model_liquid.cpp | 521 + neo/renderer/Model_local.h | 440 + neo/renderer/Model_lwo.cpp | 4169 ++++++ neo/renderer/Model_lwo.h | 676 + neo/renderer/Model_ma.cpp | 1100 ++ neo/renderer/Model_ma.h | 145 + neo/renderer/Model_md3.cpp | 369 + neo/renderer/Model_md3.h | 146 + neo/renderer/Model_md5.cpp | 1410 ++ neo/renderer/Model_prt.cpp | 287 + neo/renderer/Model_sprite.cpp | 194 + neo/renderer/OpenGL/gl_GraphicsAPIWrapper.cpp | 494 + neo/renderer/OpenGL/gl_Image.cpp | 482 + neo/renderer/OpenGL/gl_backend.cpp | 579 + neo/renderer/OpenGL/glext.h | 11807 ++++++++++++++++ neo/renderer/OpenGL/qgl.h | 549 + neo/renderer/OpenGL/qgl_linked.h | 373 + neo/renderer/OpenGL/wglext.h | 943 ++ neo/renderer/RenderContext.h | 91 + neo/renderer/RenderEntity.cpp | 114 + neo/renderer/RenderLog.cpp | 452 + neo/renderer/RenderLog.h | 187 + neo/renderer/RenderProgs.cpp | 411 + neo/renderer/RenderProgs.h | 302 + neo/renderer/RenderProgs_GLSL.cpp | 1293 ++ neo/renderer/RenderSystem.cpp | 1024 ++ neo/renderer/RenderSystem.h | 331 + neo/renderer/RenderSystem_init.cpp | 2504 ++++ neo/renderer/RenderTexture.h | 71 + neo/renderer/RenderWorld.cpp | 2111 +++ neo/renderer/RenderWorld.h | 433 + neo/renderer/RenderWorld_defs.cpp | 706 + neo/renderer/RenderWorld_demo.cpp | 717 + neo/renderer/RenderWorld_load.cpp | 1031 ++ neo/renderer/RenderWorld_local.h | 296 + neo/renderer/RenderWorld_portals.cpp | 1055 ++ neo/renderer/ResolutionScale.cpp | 193 + neo/renderer/ResolutionScale.h | 64 + neo/renderer/ScreenRect.cpp | 157 + neo/renderer/ScreenRect.h | 74 + neo/renderer/VertexCache.cpp | 338 + neo/renderer/VertexCache.h | 174 + neo/renderer/jobs/ShadowShared.cpp | 413 + neo/renderer/jobs/ShadowShared.h | 49 + .../DynamicShadowVolume.cpp | 1013 ++ .../dynamicshadowvolume/DynamicShadowVolume.h | 116 + .../DynamicShadowVolume_local.h | 47 + .../PreLightShadowVolume.cpp | 94 + .../PreLightShadowVolume.h | 84 + .../PreLightShadowVolume_local.h | 47 + .../staticshadowvolume/StaticShadowVolume.cpp | 93 + .../staticshadowvolume/StaticShadowVolume.h | 87 + .../StaticShadowVolume_local.h | 47 + neo/renderer/jpeg-6/jcapimin.cpp | 230 + neo/renderer/jpeg-6/jcapistd.cpp | 167 + neo/renderer/jpeg-6/jccoefct.cpp | 449 + neo/renderer/jpeg-6/jccolor.cpp | 467 + neo/renderer/jpeg-6/jcdctmgr.cpp | 385 + neo/renderer/jpeg-6/jchuff.cpp | 868 ++ neo/renderer/jpeg-6/jchuff.h | 34 + neo/renderer/jpeg-6/jcinit.cpp | 72 + neo/renderer/jpeg-6/jcmainct.cpp | 296 + neo/renderer/jpeg-6/jcmarker.cpp | 644 + neo/renderer/jpeg-6/jcmaster.cpp | 607 + neo/renderer/jpeg-6/jcomapi.cpp | 91 + neo/renderer/jpeg-6/jconfig.h | 41 + neo/renderer/jpeg-6/jcparam.cpp | 584 + neo/renderer/jpeg-6/jcphuff.cpp | 847 ++ neo/renderer/jpeg-6/jcprepct.cpp | 370 + neo/renderer/jpeg-6/jcsample.cpp | 523 + neo/renderer/jpeg-6/jctrans.cpp | 374 + neo/renderer/jpeg-6/jdapimin.cpp | 398 + neo/renderer/jpeg-6/jdapistd.cpp | 282 + neo/renderer/jpeg-6/jdatadst.cpp | 150 + neo/renderer/jpeg-6/jdatasrc.cpp | 199 + neo/renderer/jpeg-6/jdcoefct.cpp | 750 + neo/renderer/jpeg-6/jdcolor.cpp | 371 + neo/renderer/jpeg-6/jdct.h | 176 + neo/renderer/jpeg-6/jddctmgr.cpp | 270 + neo/renderer/jpeg-6/jdhuff.cpp | 587 + neo/renderer/jpeg-6/jdhuff.h | 202 + neo/renderer/jpeg-6/jdinput.cpp | 392 + neo/renderer/jpeg-6/jdmainct.cpp | 514 + neo/renderer/jpeg-6/jdmarker.cpp | 1092 ++ neo/renderer/jpeg-6/jdmaster.cpp | 568 + neo/renderer/jpeg-6/jdmerge.cpp | 396 + neo/renderer/jpeg-6/jdphuff.cpp | 670 + neo/renderer/jpeg-6/jdpostct.cpp | 290 + neo/renderer/jpeg-6/jdsample.cpp | 478 + neo/renderer/jpeg-6/jdtrans.cpp | 125 + neo/renderer/jpeg-6/jerror.cpp | 233 + neo/renderer/jpeg-6/jerror.h | 273 + neo/renderer/jpeg-6/jfdctflt.cpp | 167 + neo/renderer/jpeg-6/jfdctfst.cpp | 223 + neo/renderer/jpeg-6/jfdctint.cpp | 282 + neo/renderer/jpeg-6/jidctflt.cpp | 240 + neo/renderer/jpeg-6/jidctfst.cpp | 366 + neo/renderer/jpeg-6/jidctint.cpp | 387 + neo/renderer/jpeg-6/jidctred.cpp | 396 + neo/renderer/jpeg-6/jinclude.h | 91 + neo/renderer/jpeg-6/jload.cpp | 145 + neo/renderer/jpeg-6/jmemansi.cpp | 161 + neo/renderer/jpeg-6/jmemdos.cpp | 639 + neo/renderer/jpeg-6/jmemmgr.cpp | 1144 ++ neo/renderer/jpeg-6/jmemname.cpp | 264 + neo/renderer/jpeg-6/jmemnobs.cpp | 95 + neo/renderer/jpeg-6/jmemsys.h | 182 + neo/renderer/jpeg-6/jmorecfg.h | 352 + neo/renderer/jpeg-6/jpegint.h | 388 + neo/renderer/jpeg-6/jpeglib.h | 1051 ++ neo/renderer/jpeg-6/jpegtran.cpp | 381 + neo/renderer/jpeg-6/jquant1.cpp | 857 ++ neo/renderer/jpeg-6/jquant2.cpp | 1357 ++ neo/renderer/jpeg-6/jutils.cpp | 171 + neo/renderer/jpeg-6/jversion.h | 14 + neo/renderer/simplex.h | 600 + neo/renderer/tr_backend_draw.cpp | 2871 ++++ neo/renderer/tr_backend_rendertools.cpp | 2639 ++++ neo/renderer/tr_frontend_addlights.cpp | 608 + neo/renderer/tr_frontend_addmodels.cpp | 1081 ++ neo/renderer/tr_frontend_deform.cpp | 1029 ++ neo/renderer/tr_frontend_guisurf.cpp | 250 + neo/renderer/tr_frontend_main.cpp | 440 + neo/renderer/tr_frontend_subview.cpp | 536 + neo/renderer/tr_local.h | 1366 ++ neo/renderer/tr_trace.cpp | 533 + neo/renderer/tr_trisurf.cpp | 1976 +++ neo/sound/SoundVoice.cpp | 257 + neo/sound/SoundVoice.h | 101 + neo/sound/WaveFile.cpp | 516 + neo/sound/WaveFile.h | 236 + neo/sound/XAudio2/XA2_SoundHardware.cpp | 542 + neo/sound/XAudio2/XA2_SoundHardware.h | 112 + neo/sound/XAudio2/XA2_SoundSample.cpp | 487 + neo/sound/XAudio2/XA2_SoundSample.h | 129 + neo/sound/XAudio2/XA2_SoundVoice.cpp | 492 + neo/sound/XAudio2/XA2_SoundVoice.h | 114 + neo/sound/snd_emitter.cpp | 977 ++ neo/sound/snd_local.h | 483 + neo/sound/snd_shader.cpp | 421 + neo/sound/snd_system.cpp | 596 + neo/sound/snd_world.cpp | 1121 ++ neo/sound/sound.h | 335 + neo/swf/SWF.h | 405 + neo/swf/SWF_Bitstream.cpp | 402 + neo/swf/SWF_Bitstream.h | 145 + neo/swf/SWF_Dictionary.cpp | 155 + neo/swf/SWF_Enums.h | 222 + neo/swf/SWF_Events.cpp | 457 + neo/swf/SWF_Image.cpp | 575 + neo/swf/SWF_Load.cpp | 458 + neo/swf/SWF_Main.cpp | 709 + neo/swf/SWF_Names.cpp | 215 + neo/swf/SWF_ParmList.cpp | 85 + neo/swf/SWF_ParmList.h | 60 + neo/swf/SWF_PlaceObject.cpp | 231 + neo/swf/SWF_Render.cpp | 1536 ++ neo/swf/SWF_ScriptFunction.cpp | 1255 ++ neo/swf/SWF_ScriptFunction.h | 184 + neo/swf/SWF_ScriptObject.cpp | 580 + neo/swf/SWF_ScriptObject.h | 189 + neo/swf/SWF_ScriptVar.cpp | 422 + neo/swf/SWF_ScriptVar.h | 148 + neo/swf/SWF_ShapeParser.cpp | 903 ++ neo/swf/SWF_ShapeParser.h | 89 + neo/swf/SWF_Shapes.cpp | 110 + neo/swf/SWF_Sounds.cpp | 45 + neo/swf/SWF_SpriteInstance.cpp | 1363 ++ neo/swf/SWF_SpriteInstance.h | 258 + neo/swf/SWF_Sprites.cpp | 257 + neo/swf/SWF_Sprites.h | 81 + neo/swf/SWF_Text.cpp | 306 + neo/swf/SWF_TextInstance.cpp | 1242 ++ neo/swf/SWF_TextInstance.h | 251 + neo/swf/SWF_Types.h | 410 + neo/swf/SWF_Zlib.cpp | 59 + neo/sys/LightweightCompression.cpp | 488 + neo/sys/LightweightCompression.h | 209 + neo/sys/PacketProcessor.cpp | 590 + neo/sys/PacketProcessor.h | 262 + neo/sys/Snapshot.cpp | 1281 ++ neo/sys/Snapshot.h | 201 + neo/sys/SnapshotProcessor.cpp | 524 + neo/sys/SnapshotProcessor.h | 148 + neo/sys/Snapshot_Jobs.cpp | 424 + neo/sys/Snapshot_Jobs.h | 127 + neo/sys/sys_achievements.cpp | 48 + neo/sys/sys_achievements.h | 123 + neo/sys/sys_dedicated_server_search.cpp | 183 + neo/sys/sys_dedicated_server_search.h | 65 + neo/sys/sys_leaderboards.h | 209 + neo/sys/sys_lobby.cpp | 4141 ++++++ neo/sys/sys_lobby.h | 914 ++ neo/sys/sys_lobby_backend.h | 280 + neo/sys/sys_lobby_backend_direct.cpp | 226 + neo/sys/sys_lobby_backend_direct.h | 70 + neo/sys/sys_lobby_migrate.cpp | 546 + neo/sys/sys_lobby_snapshot.cpp | 833 ++ neo/sys/sys_lobby_users.cpp | 1411 ++ neo/sys/sys_local.cpp | 270 + neo/sys/sys_local.h | 76 + neo/sys/sys_localuser.cpp | 192 + neo/sys/sys_localuser.h | 149 + neo/sys/sys_profile.cpp | 430 + neo/sys/sys_profile.h | 117 + neo/sys/sys_public.h | 726 + neo/sys/sys_savegame.cpp | 903 ++ neo/sys/sys_savegame.h | 483 + neo/sys/sys_session.h | 643 + neo/sys/sys_session_callbacks.cpp | 409 + neo/sys/sys_session_local.cpp | 4093 ++++++ neo/sys/sys_session_local.h | 700 + neo/sys/sys_session_savegames.cpp | 864 ++ neo/sys/sys_session_savegames.h | 88 + neo/sys/sys_signin.cpp | 249 + neo/sys/sys_signin.h | 99 + neo/sys/sys_stats.h | 91 + neo/sys/sys_stats_misc.h | 147 + neo/sys/sys_voicechat.cpp | 593 + neo/sys/sys_voicechat.h | 143 + neo/sys/win32/rc/doom.rc | 109 + neo/sys/win32/rc/doom_resource.h | 18 + neo/sys/win32/rc/res/doom.ico | Bin 0 -> 22486 bytes neo/sys/win32/win_achievements.cpp | 103 + neo/sys/win32/win_achievements.h | 49 + neo/sys/win32/win_cpu.cpp | 1105 ++ neo/sys/win32/win_gamma.cpp | 93 + neo/sys/win32/win_glimp.cpp | 1562 ++ neo/sys/win32/win_input.cpp | 967 ++ neo/sys/win32/win_input.h | 96 + neo/sys/win32/win_local.h | 166 + neo/sys/win32/win_localuser.cpp | 121 + neo/sys/win32/win_localuser.h | 75 + neo/sys/win32/win_main.cpp | 1661 +++ neo/sys/win32/win_net.cpp | 987 ++ neo/sys/win32/win_qgl.cpp | 2011 +++ neo/sys/win32/win_savegame.cpp | 698 + neo/sys/win32/win_session_local.cpp | 668 + neo/sys/win32/win_shared.cpp | 800 ++ neo/sys/win32/win_signin.cpp | 146 + neo/sys/win32/win_signin.h | 62 + neo/sys/win32/win_snd.cpp | 37 + neo/sys/win32/win_stats.cpp | 29 + neo/sys/win32/win_stats.h | 32 + neo/sys/win32/win_syscon.cpp | 553 + neo/sys/win32/win_taskkeyhook.cpp | 142 + neo/sys/win32/win_wndproc.cpp | 429 + neo/ui/BindWindow.cpp | 122 + neo/ui/BindWindow.h | 53 + neo/ui/ChoiceWindow.cpp | 396 + neo/ui/ChoiceWindow.h | 81 + neo/ui/DeviceContext.cpp | 1094 ++ neo/ui/DeviceContext.h | 176 + neo/ui/EditWindow.cpp | 632 + neo/ui/EditWindow.h | 95 + neo/ui/FieldWindow.cpp | 99 + neo/ui/FieldWindow.h | 53 + neo/ui/GameBearShootWindow.cpp | 893 ++ neo/ui/GameBearShootWindow.h | 132 + neo/ui/GameBustOutWindow.cpp | 1335 ++ neo/ui/GameBustOutWindow.h | 185 + neo/ui/GameSSDWindow.cpp | 2289 +++ neo/ui/GameSSDWindow.h | 615 + neo/ui/GameWindow.cpp | 52 + neo/ui/GameWindow.h | 38 + neo/ui/GuiScript.cpp | 631 + neo/ui/GuiScript.h | 105 + neo/ui/ListGUI.cpp | 184 + neo/ui/ListGUI.h | 61 + neo/ui/ListGUILocal.h | 70 + neo/ui/ListWindow.cpp | 621 + neo/ui/ListWindow.h | 97 + neo/ui/Rectangle.h | 224 + neo/ui/RegExp.cpp | 402 + neo/ui/RegExp.h | 112 + neo/ui/RegExp_old.h | 87 + neo/ui/RenderWindow.cpp | 209 + neo/ui/RenderWindow.h | 74 + neo/ui/SimpleWindow.cpp | 443 + neo/ui/SimpleWindow.h | 100 + neo/ui/SliderWindow.cpp | 410 + neo/ui/SliderWindow.h | 92 + neo/ui/UserInterface.cpp | 698 + neo/ui/UserInterface.h | 167 + neo/ui/UserInterfaceLocal.h | 156 + neo/ui/Window.cpp | 4109 ++++++ neo/ui/Window.h | 450 + neo/ui/Winvar.cpp | 83 + neo/ui/Winvar.h | 861 ++ 1115 files changed, 587266 insertions(+) create mode 100644 COPYING.txt create mode 100644 README.txt create mode 100644 base/default.cfg create mode 100644 base/renderprogs/bink.pixel create mode 100644 base/renderprogs/bink.vertex create mode 100644 base/renderprogs/bink_gui.pixel create mode 100644 base/renderprogs/bink_gui.vertex create mode 100644 base/renderprogs/blendLight.pixel create mode 100644 base/renderprogs/blendLight.vertex create mode 100644 base/renderprogs/bloodorb1_capture.pixel create mode 100644 base/renderprogs/bloodorb1_capture.vertex create mode 100644 base/renderprogs/bloodorb2_capture.pixel create mode 100644 base/renderprogs/bloodorb2_capture.vertex create mode 100644 base/renderprogs/bloodorb3_capture.pixel create mode 100644 base/renderprogs/bloodorb3_capture.vertex create mode 100644 base/renderprogs/bloodorb_draw.pixel create mode 100644 base/renderprogs/bloodorb_draw.vertex create mode 100644 base/renderprogs/bumpyenvironment.pixel create mode 100644 base/renderprogs/bumpyenvironment.vertex create mode 100644 base/renderprogs/bumpyenvironment_skinned.pixel create mode 100644 base/renderprogs/bumpyenvironment_skinned.vertex create mode 100644 base/renderprogs/color.pixel create mode 100644 base/renderprogs/color.vertex create mode 100644 base/renderprogs/colorProcess.pixel create mode 100644 base/renderprogs/colorProcess.vertex create mode 100644 base/renderprogs/depth.pixel create mode 100644 base/renderprogs/depth.vertex create mode 100644 base/renderprogs/depth_skinned.pixel create mode 100644 base/renderprogs/depth_skinned.vertex create mode 100644 base/renderprogs/enviroSuit.pixel create mode 100644 base/renderprogs/enviroSuit.vertex create mode 100644 base/renderprogs/environment.pixel create mode 100644 base/renderprogs/environment.vertex create mode 100644 base/renderprogs/environment_skinned.pixel create mode 100644 base/renderprogs/environment_skinned.vertex create mode 100644 base/renderprogs/fog.pixel create mode 100644 base/renderprogs/fog.vertex create mode 100644 base/renderprogs/fog_skinned.pixel create mode 100644 base/renderprogs/fog_skinned.vertex create mode 100644 base/renderprogs/fxaa.pixel create mode 100644 base/renderprogs/fxaa.vertex create mode 100644 base/renderprogs/global.inc create mode 100644 base/renderprogs/gui.pixel create mode 100644 base/renderprogs/gui.vertex create mode 100644 base/renderprogs/heatHazeWithMask.pixel create mode 100644 base/renderprogs/heatHazeWithMask.vertex create mode 100644 base/renderprogs/heatHazeWithMaskAndVertex.pixel create mode 100644 base/renderprogs/heatHazeWithMaskAndVertex.vertex create mode 100644 base/renderprogs/heathaze.pixel create mode 100644 base/renderprogs/heathaze.vertex create mode 100644 base/renderprogs/interaction.pixel create mode 100644 base/renderprogs/interaction.vertex create mode 100644 base/renderprogs/interactionAmbient.pixel create mode 100644 base/renderprogs/interactionAmbient.vertex create mode 100644 base/renderprogs/interactionAmbient_skinned.pixel create mode 100644 base/renderprogs/interactionAmbient_skinned.vertex create mode 100644 base/renderprogs/interaction_skinned.pixel create mode 100644 base/renderprogs/interaction_skinned.vertex create mode 100644 base/renderprogs/motionBlur.pixel create mode 100644 base/renderprogs/motionBlur.vertex create mode 100644 base/renderprogs/postprocess.pixel create mode 100644 base/renderprogs/postprocess.vertex create mode 100644 base/renderprogs/shadow.pixel create mode 100644 base/renderprogs/shadow.vertex create mode 100644 base/renderprogs/shadowDebug.pixel create mode 100644 base/renderprogs/shadowDebug.vertex create mode 100644 base/renderprogs/shadowDebug_skinned.pixel create mode 100644 base/renderprogs/shadowDebug_skinned.vertex create mode 100644 base/renderprogs/shadow_skinned.pixel create mode 100644 base/renderprogs/shadow_skinned.vertex create mode 100644 base/renderprogs/simpleshade.pixel create mode 100644 base/renderprogs/simpleshade.vertex create mode 100644 base/renderprogs/skinning.inc create mode 100644 base/renderprogs/skybox.pixel create mode 100644 base/renderprogs/skybox.vertex create mode 100644 base/renderprogs/stereoDeGhost.pixel create mode 100644 base/renderprogs/stereoDeGhost.vertex create mode 100644 base/renderprogs/stereoInterlace.pixel create mode 100644 base/renderprogs/stereoInterlace.vertex create mode 100644 base/renderprogs/stereoWarp.pixel create mode 100644 base/renderprogs/stereoWarp.vertex create mode 100644 base/renderprogs/texture.pixel create mode 100644 base/renderprogs/texture.vertex create mode 100644 base/renderprogs/texture_color.pixel create mode 100644 base/renderprogs/texture_color.vertex create mode 100644 base/renderprogs/texture_color_skinned.pixel create mode 100644 base/renderprogs/texture_color_skinned.vertex create mode 100644 base/renderprogs/texture_color_texgen.pixel create mode 100644 base/renderprogs/texture_color_texgen.vertex create mode 100644 base/renderprogs/wobblesky.pixel create mode 100644 base/renderprogs/wobblesky.vertex create mode 100644 base/renderprogs/zcullReconstruct.pixel create mode 100644 base/renderprogs/zcullReconstruct.vertex create mode 100644 doomclassic/DoomClassicCommon.props create mode 100644 doomclassic/doom/DoomLeaderboards.cpp create mode 100644 doomclassic/doom/DoomLeaderboards.h create mode 100644 doomclassic/doom/Main.h create mode 100644 doomclassic/doom/Networking.cpp create mode 100644 doomclassic/doom/PlayerProfile.cpp create mode 100644 doomclassic/doom/PlayerProfile.h create mode 100644 doomclassic/doom/Precompiled.cpp create mode 100644 doomclassic/doom/Precompiled.h create mode 100644 doomclassic/doom/am_map.cpp create mode 100644 doomclassic/doom/am_map.h create mode 100644 doomclassic/doom/constructs.h create mode 100644 doomclassic/doom/d_englsh.h create mode 100644 doomclassic/doom/d_event.h create mode 100644 doomclassic/doom/d_french.h create mode 100644 doomclassic/doom/d_items.cpp create mode 100644 doomclassic/doom/d_items.h create mode 100644 doomclassic/doom/d_main.cpp create mode 100644 doomclassic/doom/d_main.h create mode 100644 doomclassic/doom/d_net.cpp create mode 100644 doomclassic/doom/d_net.h create mode 100644 doomclassic/doom/d_player.h create mode 100644 doomclassic/doom/d_textur.h create mode 100644 doomclassic/doom/d_think.h create mode 100644 doomclassic/doom/d_ticcmd.h create mode 100644 doomclassic/doom/defs.h create mode 100644 doomclassic/doom/doomdata.h create mode 100644 doomclassic/doom/doomdef.cpp create mode 100644 doomclassic/doom/doomdef.h create mode 100644 doomclassic/doom/doominterface.cpp create mode 100644 doomclassic/doom/doominterface.h create mode 100644 doomclassic/doom/doomlib.cpp create mode 100644 doomclassic/doom/doomlib.h create mode 100644 doomclassic/doom/doomstat.cpp create mode 100644 doomclassic/doom/doomstat.h create mode 100644 doomclassic/doom/doomtype.h create mode 100644 doomclassic/doom/dstrings.cpp create mode 100644 doomclassic/doom/dstrings.h create mode 100644 doomclassic/doom/f_finale.cpp create mode 100644 doomclassic/doom/f_finale.h create mode 100644 doomclassic/doom/f_wipe.cpp create mode 100644 doomclassic/doom/f_wipe.h create mode 100644 doomclassic/doom/g_game.cpp create mode 100644 doomclassic/doom/g_game.h create mode 100644 doomclassic/doom/globaldata.cpp create mode 100644 doomclassic/doom/globaldata.h create mode 100644 doomclassic/doom/hu_lib.cpp create mode 100644 doomclassic/doom/hu_lib.h create mode 100644 doomclassic/doom/hu_stuff.cpp create mode 100644 doomclassic/doom/hu_stuff.h create mode 100644 doomclassic/doom/i_input.cpp create mode 100644 doomclassic/doom/i_main.cpp create mode 100644 doomclassic/doom/i_net.cpp create mode 100644 doomclassic/doom/i_net.h create mode 100644 doomclassic/doom/i_net_ps3.cpp create mode 100644 doomclassic/doom/i_net_win32.cpp create mode 100644 doomclassic/doom/i_net_xbox.cpp create mode 100644 doomclassic/doom/i_sound.h create mode 100644 doomclassic/doom/i_sound_win32.cpp create mode 100644 doomclassic/doom/i_system.cpp create mode 100644 doomclassic/doom/i_system.h create mode 100644 doomclassic/doom/i_video.h create mode 100644 doomclassic/doom/i_video_ps3.cpp create mode 100644 doomclassic/doom/i_video_xbox.cpp create mode 100644 doomclassic/doom/info.cpp create mode 100644 doomclassic/doom/info.h create mode 100644 doomclassic/doom/m_argv.cpp create mode 100644 doomclassic/doom/m_argv.h create mode 100644 doomclassic/doom/m_bbox.cpp create mode 100644 doomclassic/doom/m_bbox.h create mode 100644 doomclassic/doom/m_cheat.cpp create mode 100644 doomclassic/doom/m_cheat.h create mode 100644 doomclassic/doom/m_fixed.cpp create mode 100644 doomclassic/doom/m_fixed.h create mode 100644 doomclassic/doom/m_menu.cpp create mode 100644 doomclassic/doom/m_menu.h create mode 100644 doomclassic/doom/m_misc.cpp create mode 100644 doomclassic/doom/m_misc.h create mode 100644 doomclassic/doom/m_random.cpp create mode 100644 doomclassic/doom/m_random.h create mode 100644 doomclassic/doom/m_swap.cpp create mode 100644 doomclassic/doom/m_swap.h create mode 100644 doomclassic/doom/mus2midi.cpp create mode 100644 doomclassic/doom/p_ceilng.cpp create mode 100644 doomclassic/doom/p_doors.cpp create mode 100644 doomclassic/doom/p_enemy.cpp create mode 100644 doomclassic/doom/p_floor.cpp create mode 100644 doomclassic/doom/p_inter.cpp create mode 100644 doomclassic/doom/p_inter.h create mode 100644 doomclassic/doom/p_lights.cpp create mode 100644 doomclassic/doom/p_local.h create mode 100644 doomclassic/doom/p_map.cpp create mode 100644 doomclassic/doom/p_maputl.cpp create mode 100644 doomclassic/doom/p_mobj.cpp create mode 100644 doomclassic/doom/p_mobj.h create mode 100644 doomclassic/doom/p_plats.cpp create mode 100644 doomclassic/doom/p_pspr.cpp create mode 100644 doomclassic/doom/p_pspr.h create mode 100644 doomclassic/doom/p_saveg.cpp create mode 100644 doomclassic/doom/p_saveg.h create mode 100644 doomclassic/doom/p_setup.cpp create mode 100644 doomclassic/doom/p_setup.h create mode 100644 doomclassic/doom/p_sight.cpp create mode 100644 doomclassic/doom/p_spec.cpp create mode 100644 doomclassic/doom/p_spec.h create mode 100644 doomclassic/doom/p_switch.cpp create mode 100644 doomclassic/doom/p_telept.cpp create mode 100644 doomclassic/doom/p_tick.cpp create mode 100644 doomclassic/doom/p_tick.h create mode 100644 doomclassic/doom/p_user.cpp create mode 100644 doomclassic/doom/r_bsp.cpp create mode 100644 doomclassic/doom/r_bsp.h create mode 100644 doomclassic/doom/r_data.cpp create mode 100644 doomclassic/doom/r_data.h create mode 100644 doomclassic/doom/r_defs.h create mode 100644 doomclassic/doom/r_draw.cpp create mode 100644 doomclassic/doom/r_draw.h create mode 100644 doomclassic/doom/r_local.h create mode 100644 doomclassic/doom/r_main.cpp create mode 100644 doomclassic/doom/r_main.h create mode 100644 doomclassic/doom/r_plane.cpp create mode 100644 doomclassic/doom/r_plane.h create mode 100644 doomclassic/doom/r_segs.cpp create mode 100644 doomclassic/doom/r_segs.h create mode 100644 doomclassic/doom/r_sky.cpp create mode 100644 doomclassic/doom/r_sky.h create mode 100644 doomclassic/doom/r_state.h create mode 100644 doomclassic/doom/r_things.cpp create mode 100644 doomclassic/doom/r_things.h create mode 100644 doomclassic/doom/s_sound.cpp create mode 100644 doomclassic/doom/s_sound.h create mode 100644 doomclassic/doom/sounds.cpp create mode 100644 doomclassic/doom/sounds.h create mode 100644 doomclassic/doom/st_lib.cpp create mode 100644 doomclassic/doom/st_lib.h create mode 100644 doomclassic/doom/st_stuff.cpp create mode 100644 doomclassic/doom/st_stuff.h create mode 100644 doomclassic/doom/structs.h create mode 100644 doomclassic/doom/tables.cpp create mode 100644 doomclassic/doom/tables.h create mode 100644 doomclassic/doom/typedefs.h create mode 100644 doomclassic/doom/v_video.cpp create mode 100644 doomclassic/doom/v_video.h create mode 100644 doomclassic/doom/vars.h create mode 100644 doomclassic/doom/w_wad.cpp create mode 100644 doomclassic/doom/w_wad.h create mode 100644 doomclassic/doom/wi_stuff.cpp create mode 100644 doomclassic/doom/wi_stuff.h create mode 100644 doomclassic/doom/z_zone.cpp create mode 100644 doomclassic/doom/z_zone.h create mode 100644 doomclassic/doomclassic.vcxproj create mode 100644 doomclassic/doomclassic.vcxproj.filters create mode 100644 doomclassic/doomclassic.vcxproj.vspscc create mode 100644 doomclassic/timidity/FAQ create mode 100644 doomclassic/timidity/README create mode 100644 doomclassic/timidity/common.cpp create mode 100644 doomclassic/timidity/common.h create mode 100644 doomclassic/timidity/config.h create mode 100644 doomclassic/timidity/controls.cpp create mode 100644 doomclassic/timidity/controls.h create mode 100644 doomclassic/timidity/filter.cpp create mode 100644 doomclassic/timidity/filter.h create mode 100644 doomclassic/timidity/instrum.cpp create mode 100644 doomclassic/timidity/instrum.h create mode 100644 doomclassic/timidity/mix.cpp create mode 100644 doomclassic/timidity/mix.h create mode 100644 doomclassic/timidity/output.cpp create mode 100644 doomclassic/timidity/output.h create mode 100644 doomclassic/timidity/playmidi.cpp create mode 100644 doomclassic/timidity/playmidi.h create mode 100644 doomclassic/timidity/readmidi.cpp create mode 100644 doomclassic/timidity/readmidi.h create mode 100644 doomclassic/timidity/resample.cpp create mode 100644 doomclassic/timidity/resample.h create mode 100644 doomclassic/timidity/sdl_a.cpp create mode 100644 doomclassic/timidity/sdl_c.cpp create mode 100644 doomclassic/timidity/structs.h create mode 100644 doomclassic/timidity/tables.cpp create mode 100644 doomclassic/timidity/tables.h create mode 100644 doomclassic/timidity/timidity.cpp create mode 100644 doomclassic/timidity/timidity.h create mode 100644 doomclassic/timidity/timidity.vcxproj create mode 100644 doomclassic/timidity/timidity.vcxproj.filters create mode 100644 doomclassic/timidity/timidity.vcxproj.vspscc create mode 100644 neo/_Common.props create mode 100644 neo/_Debug.props create mode 100644 neo/_Dedicated.props create mode 100644 neo/_DoomExe.props create mode 100644 neo/_Game-d3xp.props create mode 100644 neo/_Game.props create mode 100644 neo/_PCLibs.props create mode 100644 neo/_Release.props create mode 100644 neo/_WithInlines.props create mode 100644 neo/_WithMemoryLog.props create mode 100644 neo/_external.props create mode 100644 neo/_idlib.props create mode 100644 neo/aas/AASFile.cpp create mode 100644 neo/aas/AASFile.h create mode 100644 neo/aas/AASFileManager.cpp create mode 100644 neo/aas/AASFileManager.h create mode 100644 neo/aas/AASFile_local.h create mode 100644 neo/aas/AASFile_optimize.cpp create mode 100644 neo/aas/AASFile_sample.cpp create mode 100644 neo/amplitude/amplitude.cpp create mode 100644 neo/amplitude/amplitude.vcxproj create mode 100644 neo/amplitude/amplitude.vcxproj.filters create mode 100644 neo/amplitude/amplitude.vcxproj.vspscc create mode 100644 neo/cm/CollisionModel.h create mode 100644 neo/cm/CollisionModel_contacts.cpp create mode 100644 neo/cm/CollisionModel_contents.cpp create mode 100644 neo/cm/CollisionModel_debug.cpp create mode 100644 neo/cm/CollisionModel_files.cpp create mode 100644 neo/cm/CollisionModel_load.cpp create mode 100644 neo/cm/CollisionModel_local.h create mode 100644 neo/cm/CollisionModel_rotate.cpp create mode 100644 neo/cm/CollisionModel_trace.cpp create mode 100644 neo/cm/CollisionModel_translate.cpp create mode 100644 neo/d3xp/AF.cpp create mode 100644 neo/d3xp/AF.h create mode 100644 neo/d3xp/AFEntity.cpp create mode 100644 neo/d3xp/AFEntity.h create mode 100644 neo/d3xp/Achievements.cpp create mode 100644 neo/d3xp/Achievements.h create mode 100644 neo/d3xp/Actor.cpp create mode 100644 neo/d3xp/Actor.h create mode 100644 neo/d3xp/AimAssist.cpp create mode 100644 neo/d3xp/AimAssist.h create mode 100644 neo/d3xp/BrittleFracture.cpp create mode 100644 neo/d3xp/BrittleFracture.h create mode 100644 neo/d3xp/Camera.cpp create mode 100644 neo/d3xp/Camera.h create mode 100644 neo/d3xp/EndLevel.cpp create mode 100644 neo/d3xp/EndLevel.h create mode 100644 neo/d3xp/Entity.cpp create mode 100644 neo/d3xp/Entity.h create mode 100644 neo/d3xp/Fx.cpp create mode 100644 neo/d3xp/Fx.h create mode 100644 neo/d3xp/Game.def create mode 100644 neo/d3xp/Game.h create mode 100644 neo/d3xp/GameEdit.cpp create mode 100644 neo/d3xp/GameEdit.h create mode 100644 neo/d3xp/Game_local.cpp create mode 100644 neo/d3xp/Game_local.h create mode 100644 neo/d3xp/Game_network.cpp create mode 100644 neo/d3xp/Grabber.cpp create mode 100644 neo/d3xp/Grabber.h create mode 100644 neo/d3xp/IK.cpp create mode 100644 neo/d3xp/IK.h create mode 100644 neo/d3xp/Item.cpp create mode 100644 neo/d3xp/Item.h create mode 100644 neo/d3xp/Leaderboards.cpp create mode 100644 neo/d3xp/Leaderboards.h create mode 100644 neo/d3xp/Light.cpp create mode 100644 neo/d3xp/Light.h create mode 100644 neo/d3xp/Misc.cpp create mode 100644 neo/d3xp/Misc.h create mode 100644 neo/d3xp/Moveable.cpp create mode 100644 neo/d3xp/Moveable.h create mode 100644 neo/d3xp/Mover.cpp create mode 100644 neo/d3xp/Mover.h create mode 100644 neo/d3xp/MultiplayerGame.cpp create mode 100644 neo/d3xp/MultiplayerGame.h create mode 100644 neo/d3xp/Player.cpp create mode 100644 neo/d3xp/Player.h create mode 100644 neo/d3xp/PlayerIcon.cpp create mode 100644 neo/d3xp/PlayerIcon.h create mode 100644 neo/d3xp/PlayerView.cpp create mode 100644 neo/d3xp/PlayerView.h create mode 100644 neo/d3xp/PredictedValue.h create mode 100644 neo/d3xp/PredictedValue_impl.h create mode 100644 neo/d3xp/Projectile.cpp create mode 100644 neo/d3xp/Projectile.h create mode 100644 neo/d3xp/Pvs.cpp create mode 100644 neo/d3xp/Pvs.h create mode 100644 neo/d3xp/SecurityCamera.cpp create mode 100644 neo/d3xp/SecurityCamera.h create mode 100644 neo/d3xp/SmokeParticles.cpp create mode 100644 neo/d3xp/SmokeParticles.h create mode 100644 neo/d3xp/Sound.cpp create mode 100644 neo/d3xp/Sound.h create mode 100644 neo/d3xp/Target.cpp create mode 100644 neo/d3xp/Target.h create mode 100644 neo/d3xp/Trigger.cpp create mode 100644 neo/d3xp/Trigger.h create mode 100644 neo/d3xp/Weapon.cpp create mode 100644 neo/d3xp/Weapon.h create mode 100644 neo/d3xp/WorldSpawn.cpp create mode 100644 neo/d3xp/WorldSpawn.h create mode 100644 neo/d3xp/ai/AAS.cpp create mode 100644 neo/d3xp/ai/AAS.h create mode 100644 neo/d3xp/ai/AAS_debug.cpp create mode 100644 neo/d3xp/ai/AAS_local.h create mode 100644 neo/d3xp/ai/AAS_pathing.cpp create mode 100644 neo/d3xp/ai/AAS_routing.cpp create mode 100644 neo/d3xp/ai/AI.cpp create mode 100644 neo/d3xp/ai/AI.h create mode 100644 neo/d3xp/ai/AI_Vagary.cpp create mode 100644 neo/d3xp/ai/AI_events.cpp create mode 100644 neo/d3xp/ai/AI_pathing.cpp create mode 100644 neo/d3xp/anim/Anim.cpp create mode 100644 neo/d3xp/anim/Anim.h create mode 100644 neo/d3xp/anim/Anim_Blend.cpp create mode 100644 neo/d3xp/anim/Anim_Testmodel.cpp create mode 100644 neo/d3xp/anim/Anim_Testmodel.h create mode 100644 neo/d3xp/gamesys/Callbacks.cpp create mode 100644 neo/d3xp/gamesys/Class.cpp create mode 100644 neo/d3xp/gamesys/Class.h create mode 100644 neo/d3xp/gamesys/Event.cpp create mode 100644 neo/d3xp/gamesys/Event.h create mode 100644 neo/d3xp/gamesys/SaveGame.cpp create mode 100644 neo/d3xp/gamesys/SaveGame.h create mode 100644 neo/d3xp/gamesys/SysCmds.cpp create mode 100644 neo/d3xp/gamesys/SysCmds.h create mode 100644 neo/d3xp/gamesys/SysCvar.cpp create mode 100644 neo/d3xp/gamesys/SysCvar.h create mode 100644 neo/d3xp/menus/MenuHandler.cpp create mode 100644 neo/d3xp/menus/MenuHandler.h create mode 100644 neo/d3xp/menus/MenuHandler_HUD.cpp create mode 100644 neo/d3xp/menus/MenuHandler_PDA.cpp create mode 100644 neo/d3xp/menus/MenuHandler_Scoreboard.cpp create mode 100644 neo/d3xp/menus/MenuHandler_Shell.cpp create mode 100644 neo/d3xp/menus/MenuScreen.cpp create mode 100644 neo/d3xp/menus/MenuScreen.h create mode 100644 neo/d3xp/menus/MenuScreen_HUD.cpp create mode 100644 neo/d3xp/menus/MenuScreen_PDA_Inventory.cpp create mode 100644 neo/d3xp/menus/MenuScreen_PDA_UserData.cpp create mode 100644 neo/d3xp/menus/MenuScreen_PDA_UserEmails.cpp create mode 100644 neo/d3xp/menus/MenuScreen_PDA_VideoDisks.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Scoreboard.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Bindings.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Browser.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_ControllerLayout.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Controls.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Credits.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Dev.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Difficulty.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_GameLobby.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_GameOptions.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Gamepad.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Leaderboards.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Load.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_MatchSettings.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_ModeSelect.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_NewGame.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_PartyLobby.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Pause.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Playstation.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_PressStart.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Resolution.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Root.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Save.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Settings.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Singleplayer.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_Stereoscopics.cpp create mode 100644 neo/d3xp/menus/MenuScreen_Shell_SystemOptions.cpp create mode 100644 neo/d3xp/menus/MenuWidget.cpp create mode 100644 neo/d3xp/menus/MenuWidget.h create mode 100644 neo/d3xp/menus/MenuWidget_Button.cpp create mode 100644 neo/d3xp/menus/MenuWidget_Carousel.cpp create mode 100644 neo/d3xp/menus/MenuWidget_CommandBar.cpp create mode 100644 neo/d3xp/menus/MenuWidget_DevList.cpp create mode 100644 neo/d3xp/menus/MenuWidget_DynamicList.cpp create mode 100644 neo/d3xp/menus/MenuWidget_Help.cpp create mode 100644 neo/d3xp/menus/MenuWidget_InfoBox.cpp create mode 100644 neo/d3xp/menus/MenuWidget_ItemAssignment.cpp create mode 100644 neo/d3xp/menus/MenuWidget_List.cpp create mode 100644 neo/d3xp/menus/MenuWidget_LobbyList.cpp create mode 100644 neo/d3xp/menus/MenuWidget_MenuBar.cpp create mode 100644 neo/d3xp/menus/MenuWidget_NavBar.cpp create mode 100644 neo/d3xp/menus/MenuWidget_NavButton.cpp create mode 100644 neo/d3xp/menus/MenuWidget_PDA_AudioFiles.cpp create mode 100644 neo/d3xp/menus/MenuWidget_PDA_EmailInbox.cpp create mode 100644 neo/d3xp/menus/MenuWidget_PDA_Objective.cpp create mode 100644 neo/d3xp/menus/MenuWidget_PDA_UserData.cpp create mode 100644 neo/d3xp/menus/MenuWidget_PDA_VideoInfo.cpp create mode 100644 neo/d3xp/menus/MenuWidget_Scrollbar.cpp create mode 100644 neo/d3xp/menus/MenuWidget_Shell_SaveInfo.cpp create mode 100644 neo/d3xp/physics/Clip.cpp create mode 100644 neo/d3xp/physics/Clip.h create mode 100644 neo/d3xp/physics/Force.cpp create mode 100644 neo/d3xp/physics/Force.h create mode 100644 neo/d3xp/physics/Force_Constant.cpp create mode 100644 neo/d3xp/physics/Force_Constant.h create mode 100644 neo/d3xp/physics/Force_Drag.cpp create mode 100644 neo/d3xp/physics/Force_Drag.h create mode 100644 neo/d3xp/physics/Force_Field.cpp create mode 100644 neo/d3xp/physics/Force_Field.h create mode 100644 neo/d3xp/physics/Force_Grab.cpp create mode 100644 neo/d3xp/physics/Force_Grab.h create mode 100644 neo/d3xp/physics/Force_Spring.cpp create mode 100644 neo/d3xp/physics/Force_Spring.h create mode 100644 neo/d3xp/physics/Physics.cpp create mode 100644 neo/d3xp/physics/Physics.h create mode 100644 neo/d3xp/physics/Physics_AF.cpp create mode 100644 neo/d3xp/physics/Physics_AF.h create mode 100644 neo/d3xp/physics/Physics_Actor.cpp create mode 100644 neo/d3xp/physics/Physics_Actor.h create mode 100644 neo/d3xp/physics/Physics_Base.cpp create mode 100644 neo/d3xp/physics/Physics_Base.h create mode 100644 neo/d3xp/physics/Physics_Monster.cpp create mode 100644 neo/d3xp/physics/Physics_Monster.h create mode 100644 neo/d3xp/physics/Physics_Parametric.cpp create mode 100644 neo/d3xp/physics/Physics_Parametric.h create mode 100644 neo/d3xp/physics/Physics_Player.cpp create mode 100644 neo/d3xp/physics/Physics_Player.h create mode 100644 neo/d3xp/physics/Physics_RigidBody.cpp create mode 100644 neo/d3xp/physics/Physics_RigidBody.h create mode 100644 neo/d3xp/physics/Physics_Static.cpp create mode 100644 neo/d3xp/physics/Physics_Static.h create mode 100644 neo/d3xp/physics/Physics_StaticMulti.cpp create mode 100644 neo/d3xp/physics/Physics_StaticMulti.h create mode 100644 neo/d3xp/physics/Push.cpp create mode 100644 neo/d3xp/physics/Push.h create mode 100644 neo/d3xp/precompiled.cpp create mode 100644 neo/d3xp/script/Script_Compiler.cpp create mode 100644 neo/d3xp/script/Script_Compiler.h create mode 100644 neo/d3xp/script/Script_Interpreter.cpp create mode 100644 neo/d3xp/script/Script_Interpreter.h create mode 100644 neo/d3xp/script/Script_Program.cpp create mode 100644 neo/d3xp/script/Script_Program.h create mode 100644 neo/d3xp/script/Script_Thread.cpp create mode 100644 neo/d3xp/script/Script_Thread.h create mode 100644 neo/doom3.sln create mode 100644 neo/doomexe.vcxproj create mode 100644 neo/doomexe.vcxproj.filters create mode 100644 neo/external.vcxproj create mode 100644 neo/external.vcxproj.filters create mode 100644 neo/framework/BuildVersion.h create mode 100644 neo/framework/CVarSystem.cpp create mode 100644 neo/framework/CVarSystem.h create mode 100644 neo/framework/CmdSystem.cpp create mode 100644 neo/framework/CmdSystem.h create mode 100644 neo/framework/Common.cpp create mode 100644 neo/framework/Common.h create mode 100644 neo/framework/Common_demos.cpp create mode 100644 neo/framework/Common_dialog.cpp create mode 100644 neo/framework/Common_dialog.h create mode 100644 neo/framework/Common_load.cpp create mode 100644 neo/framework/Common_local.h create mode 100644 neo/framework/Common_localize.cpp create mode 100644 neo/framework/Common_menu.cpp create mode 100644 neo/framework/Common_network.cpp create mode 100644 neo/framework/Common_printf.cpp create mode 100644 neo/framework/Compressor.cpp create mode 100644 neo/framework/Compressor.h create mode 100644 neo/framework/Console.cpp create mode 100644 neo/framework/Console.h create mode 100644 neo/framework/ConsoleHistory.cpp create mode 100644 neo/framework/ConsoleHistory.h create mode 100644 neo/framework/DebugGraph.cpp create mode 100644 neo/framework/DebugGraph.h create mode 100644 neo/framework/DeclAF.cpp create mode 100644 neo/framework/DeclAF.h create mode 100644 neo/framework/DeclEntityDef.cpp create mode 100644 neo/framework/DeclEntityDef.h create mode 100644 neo/framework/DeclFX.cpp create mode 100644 neo/framework/DeclFX.h create mode 100644 neo/framework/DeclManager.cpp create mode 100644 neo/framework/DeclManager.h create mode 100644 neo/framework/DeclPDA.cpp create mode 100644 neo/framework/DeclPDA.h create mode 100644 neo/framework/DeclParticle.cpp create mode 100644 neo/framework/DeclParticle.h create mode 100644 neo/framework/DeclSkin.cpp create mode 100644 neo/framework/DeclSkin.h create mode 100644 neo/framework/DeclTable.cpp create mode 100644 neo/framework/DeclTable.h create mode 100644 neo/framework/DemoChecksum.h create mode 100644 neo/framework/DemoFile.cpp create mode 100644 neo/framework/DemoFile.h create mode 100644 neo/framework/EditField.cpp create mode 100644 neo/framework/EditField.h create mode 100644 neo/framework/EventLoop.cpp create mode 100644 neo/framework/EventLoop.h create mode 100644 neo/framework/File.cpp create mode 100644 neo/framework/File.h create mode 100644 neo/framework/FileSystem.cpp create mode 100644 neo/framework/FileSystem.h create mode 100644 neo/framework/File_Manifest.cpp create mode 100644 neo/framework/File_Manifest.h create mode 100644 neo/framework/File_Resource.cpp create mode 100644 neo/framework/File_Resource.h create mode 100644 neo/framework/File_SaveGame.cpp create mode 100644 neo/framework/File_SaveGame.h create mode 100644 neo/framework/KeyInput.cpp create mode 100644 neo/framework/KeyInput.h create mode 100644 neo/framework/Licensee.h create mode 100644 neo/framework/PlayerProfile.cpp create mode 100644 neo/framework/PlayerProfile.h create mode 100644 neo/framework/Serializer.h create mode 100644 neo/framework/Session.cpp create mode 100644 neo/framework/TokenParser.cpp create mode 100644 neo/framework/TokenParser.h create mode 100644 neo/framework/Unzip.cpp create mode 100644 neo/framework/Unzip.h create mode 100644 neo/framework/UsercmdGen.cpp create mode 100644 neo/framework/UsercmdGen.h create mode 100644 neo/framework/Zip.cpp create mode 100644 neo/framework/Zip.h create mode 100644 neo/framework/common_frame.cpp create mode 100644 neo/framework/precompiled.cpp create mode 100644 neo/framework/zlib/ChangeLog create mode 100644 neo/framework/zlib/FAQ create mode 100644 neo/framework/zlib/INDEX create mode 100644 neo/framework/zlib/Makefile create mode 100644 neo/framework/zlib/Makefile.in create mode 100644 neo/framework/zlib/README create mode 100644 neo/framework/zlib/adler32.c create mode 100644 neo/framework/zlib/algorithm.txt create mode 100644 neo/framework/zlib/compress.c create mode 100644 neo/framework/zlib/configure create mode 100644 neo/framework/zlib/crc32.c create mode 100644 neo/framework/zlib/crc32.h create mode 100644 neo/framework/zlib/deflate.c create mode 100644 neo/framework/zlib/deflate.h create mode 100644 neo/framework/zlib/example.c create mode 100644 neo/framework/zlib/gzio.c create mode 100644 neo/framework/zlib/infback.c create mode 100644 neo/framework/zlib/inffast.c create mode 100644 neo/framework/zlib/inffast.h create mode 100644 neo/framework/zlib/inffixed.h create mode 100644 neo/framework/zlib/inflate.c create mode 100644 neo/framework/zlib/inflate.h create mode 100644 neo/framework/zlib/inftrees.c create mode 100644 neo/framework/zlib/inftrees.h create mode 100644 neo/framework/zlib/minigzip.c create mode 100644 neo/framework/zlib/trees.c create mode 100644 neo/framework/zlib/trees.h create mode 100644 neo/framework/zlib/uncompr.c create mode 100644 neo/framework/zlib/zconf.h create mode 100644 neo/framework/zlib/zconf.in.h create mode 100644 neo/framework/zlib/zlib.3 create mode 100644 neo/framework/zlib/zlib.h create mode 100644 neo/framework/zlib/zutil.c create mode 100644 neo/framework/zlib/zutil.h create mode 100644 neo/game-d3xp.vcxproj create mode 100644 neo/game-d3xp.vcxproj.filters create mode 100644 neo/game.vcxproj create mode 100644 neo/game.vcxproj.filters create mode 100644 neo/idlib.vcxproj create mode 100644 neo/idlib.vcxproj.filters create mode 100644 neo/idlib/Base64.cpp create mode 100644 neo/idlib/Base64.h create mode 100644 neo/idlib/BitMsg.cpp create mode 100644 neo/idlib/BitMsg.h create mode 100644 neo/idlib/Callback.h create mode 100644 neo/idlib/CmdArgs.cpp create mode 100644 neo/idlib/CmdArgs.h create mode 100644 neo/idlib/CommandLink.cpp create mode 100644 neo/idlib/DataQueue.h create mode 100644 neo/idlib/Dict.cpp create mode 100644 neo/idlib/Dict.h create mode 100644 neo/idlib/Heap.cpp create mode 100644 neo/idlib/Heap.h create mode 100644 neo/idlib/LangDict.cpp create mode 100644 neo/idlib/LangDict.h create mode 100644 neo/idlib/Lexer.cpp create mode 100644 neo/idlib/Lexer.h create mode 100644 neo/idlib/Lib.cpp create mode 100644 neo/idlib/Lib.h create mode 100644 neo/idlib/MapFile.cpp create mode 100644 neo/idlib/MapFile.h create mode 100644 neo/idlib/ParallelJobList.cpp create mode 100644 neo/idlib/ParallelJobList.h create mode 100644 neo/idlib/ParallelJobList_JobHeaders.h create mode 100644 neo/idlib/Parser.cpp create mode 100644 neo/idlib/Parser.h create mode 100644 neo/idlib/RectAllocator.cpp create mode 100644 neo/idlib/SoftwareCache.cpp create mode 100644 neo/idlib/SoftwareCache.h create mode 100644 neo/idlib/Str.cpp create mode 100644 neo/idlib/Str.h create mode 100644 neo/idlib/StrStatic.h create mode 100644 neo/idlib/Swap.h create mode 100644 neo/idlib/Thread.cpp create mode 100644 neo/idlib/Thread.h create mode 100644 neo/idlib/Timer.cpp create mode 100644 neo/idlib/Timer.h create mode 100644 neo/idlib/Token.cpp create mode 100644 neo/idlib/Token.h create mode 100644 neo/idlib/bv/Bounds.cpp create mode 100644 neo/idlib/bv/Bounds.h create mode 100644 neo/idlib/bv/Box.cpp create mode 100644 neo/idlib/bv/Box.h create mode 100644 neo/idlib/bv/Sphere.cpp create mode 100644 neo/idlib/bv/Sphere.h create mode 100644 neo/idlib/containers/Array.h create mode 100644 neo/idlib/containers/BTree.h create mode 100644 neo/idlib/containers/BinSearch.h create mode 100644 neo/idlib/containers/HashIndex.cpp create mode 100644 neo/idlib/containers/HashIndex.h create mode 100644 neo/idlib/containers/HashTable.h create mode 100644 neo/idlib/containers/Hierarchy.h create mode 100644 neo/idlib/containers/LinkList.h create mode 100644 neo/idlib/containers/List.h create mode 100644 neo/idlib/containers/PlaneSet.h create mode 100644 neo/idlib/containers/Queue.h create mode 100644 neo/idlib/containers/Sort.h create mode 100644 neo/idlib/containers/Stack.h create mode 100644 neo/idlib/containers/StaticList.h create mode 100644 neo/idlib/containers/StrList.h create mode 100644 neo/idlib/containers/StrPool.h create mode 100644 neo/idlib/containers/VectorSet.h create mode 100644 neo/idlib/geometry/DrawVert.cpp create mode 100644 neo/idlib/geometry/DrawVert.h create mode 100644 neo/idlib/geometry/DrawVert_intrinsics.h create mode 100644 neo/idlib/geometry/JointTransform.cpp create mode 100644 neo/idlib/geometry/JointTransform.h create mode 100644 neo/idlib/geometry/RenderMatrix.cpp create mode 100644 neo/idlib/geometry/RenderMatrix.h create mode 100644 neo/idlib/geometry/Surface.cpp create mode 100644 neo/idlib/geometry/Surface.h create mode 100644 neo/idlib/geometry/Surface_Patch.cpp create mode 100644 neo/idlib/geometry/Surface_Patch.h create mode 100644 neo/idlib/geometry/Surface_Polytope.cpp create mode 100644 neo/idlib/geometry/Surface_Polytope.h create mode 100644 neo/idlib/geometry/Surface_SweptSpline.cpp create mode 100644 neo/idlib/geometry/Surface_SweptSpline.h create mode 100644 neo/idlib/geometry/TraceModel.cpp create mode 100644 neo/idlib/geometry/TraceModel.h create mode 100644 neo/idlib/geometry/Winding.cpp create mode 100644 neo/idlib/geometry/Winding.h create mode 100644 neo/idlib/geometry/Winding2D.cpp create mode 100644 neo/idlib/geometry/Winding2D.h create mode 100644 neo/idlib/hashing/CRC32.cpp create mode 100644 neo/idlib/hashing/CRC32.h create mode 100644 neo/idlib/hashing/MD4.cpp create mode 100644 neo/idlib/hashing/MD4.h create mode 100644 neo/idlib/hashing/MD5.cpp create mode 100644 neo/idlib/hashing/MD5.h create mode 100644 neo/idlib/math/Angles.cpp create mode 100644 neo/idlib/math/Angles.h create mode 100644 neo/idlib/math/Complex.cpp create mode 100644 neo/idlib/math/Complex.h create mode 100644 neo/idlib/math/Curve.h create mode 100644 neo/idlib/math/Extrapolate.h create mode 100644 neo/idlib/math/Interpolate.h create mode 100644 neo/idlib/math/Lcp.cpp create mode 100644 neo/idlib/math/Lcp.h create mode 100644 neo/idlib/math/MatX.cpp create mode 100644 neo/idlib/math/MatX.h create mode 100644 neo/idlib/math/Math.cpp create mode 100644 neo/idlib/math/Math.h create mode 100644 neo/idlib/math/Matrix.cpp create mode 100644 neo/idlib/math/Matrix.h create mode 100644 neo/idlib/math/Ode.cpp create mode 100644 neo/idlib/math/Ode.h create mode 100644 neo/idlib/math/Plane.cpp create mode 100644 neo/idlib/math/Plane.h create mode 100644 neo/idlib/math/Pluecker.cpp create mode 100644 neo/idlib/math/Pluecker.h create mode 100644 neo/idlib/math/Polynomial.cpp create mode 100644 neo/idlib/math/Polynomial.h create mode 100644 neo/idlib/math/Quat.cpp create mode 100644 neo/idlib/math/Quat.h create mode 100644 neo/idlib/math/Random.h create mode 100644 neo/idlib/math/Rotation.cpp create mode 100644 neo/idlib/math/Rotation.h create mode 100644 neo/idlib/math/Simd.cpp create mode 100644 neo/idlib/math/Simd.h create mode 100644 neo/idlib/math/Simd_Generic.cpp create mode 100644 neo/idlib/math/Simd_Generic.h create mode 100644 neo/idlib/math/Simd_SSE.cpp create mode 100644 neo/idlib/math/Simd_SSE.h create mode 100644 neo/idlib/math/VecX.cpp create mode 100644 neo/idlib/math/VecX.h create mode 100644 neo/idlib/math/Vector.cpp create mode 100644 neo/idlib/math/Vector.h create mode 100644 neo/idlib/math/VectorI.h create mode 100644 neo/idlib/precompiled.cpp create mode 100644 neo/idlib/precompiled.h create mode 100644 neo/idlib/sys/sys_alloc_tags.h create mode 100644 neo/idlib/sys/sys_assert.cpp create mode 100644 neo/idlib/sys/sys_assert.h create mode 100644 neo/idlib/sys/sys_builddefines.h create mode 100644 neo/idlib/sys/sys_defines.h create mode 100644 neo/idlib/sys/sys_filesystem.h create mode 100644 neo/idlib/sys/sys_includes.h create mode 100644 neo/idlib/sys/sys_intrinsics.h create mode 100644 neo/idlib/sys/sys_threading.h create mode 100644 neo/idlib/sys/sys_types.h create mode 100644 neo/idlib/sys/win32/win_thread.cpp create mode 100644 neo/renderer/AutoRender.cpp create mode 100644 neo/renderer/AutoRender.h create mode 100644 neo/renderer/AutoRenderBink.cpp create mode 100644 neo/renderer/AutoRenderBink.h create mode 100644 neo/renderer/BinaryImage.cpp create mode 100644 neo/renderer/BinaryImage.h create mode 100644 neo/renderer/BinaryImageData.h create mode 100644 neo/renderer/BoundsTrack.cpp create mode 100644 neo/renderer/BoundsTrack.h create mode 100644 neo/renderer/BufferObject.cpp create mode 100644 neo/renderer/BufferObject.h create mode 100644 neo/renderer/Cinematic.cpp create mode 100644 neo/renderer/Cinematic.h create mode 100644 neo/renderer/Color/ColorSpace.cpp create mode 100644 neo/renderer/Color/ColorSpace.h create mode 100644 neo/renderer/DXT/DXTCodec.h create mode 100644 neo/renderer/DXT/DXTCodec_local.h create mode 100644 neo/renderer/DXT/DXTDecoder.cpp create mode 100644 neo/renderer/DXT/DXTEncoder.cpp create mode 100644 neo/renderer/DXT/DXTEncoder_SSE2.cpp create mode 100644 neo/renderer/Font.cpp create mode 100644 neo/renderer/Font.h create mode 100644 neo/renderer/GLMatrix.cpp create mode 100644 neo/renderer/GLMatrix.h create mode 100644 neo/renderer/GLState.h create mode 100644 neo/renderer/GraphicsAPIWrapper.h create mode 100644 neo/renderer/GuiModel.cpp create mode 100644 neo/renderer/GuiModel.h create mode 100644 neo/renderer/Image.h create mode 100644 neo/renderer/ImageManager.cpp create mode 100644 neo/renderer/ImageOpts.h create mode 100644 neo/renderer/Image_files.cpp create mode 100644 neo/renderer/Image_intrinsic.cpp create mode 100644 neo/renderer/Image_load.cpp create mode 100644 neo/renderer/Image_process.cpp create mode 100644 neo/renderer/Image_program.cpp create mode 100644 neo/renderer/Interaction.cpp create mode 100644 neo/renderer/Interaction.h create mode 100644 neo/renderer/Material.cpp create mode 100644 neo/renderer/Material.h create mode 100644 neo/renderer/Model.cpp create mode 100644 neo/renderer/Model.h create mode 100644 neo/renderer/ModelDecal.cpp create mode 100644 neo/renderer/ModelDecal.h create mode 100644 neo/renderer/ModelManager.cpp create mode 100644 neo/renderer/ModelManager.h create mode 100644 neo/renderer/ModelOverlay.cpp create mode 100644 neo/renderer/ModelOverlay.h create mode 100644 neo/renderer/Model_ase.cpp create mode 100644 neo/renderer/Model_ase.h create mode 100644 neo/renderer/Model_beam.cpp create mode 100644 neo/renderer/Model_liquid.cpp create mode 100644 neo/renderer/Model_local.h create mode 100644 neo/renderer/Model_lwo.cpp create mode 100644 neo/renderer/Model_lwo.h create mode 100644 neo/renderer/Model_ma.cpp create mode 100644 neo/renderer/Model_ma.h create mode 100644 neo/renderer/Model_md3.cpp create mode 100644 neo/renderer/Model_md3.h create mode 100644 neo/renderer/Model_md5.cpp create mode 100644 neo/renderer/Model_prt.cpp create mode 100644 neo/renderer/Model_sprite.cpp create mode 100644 neo/renderer/OpenGL/gl_GraphicsAPIWrapper.cpp create mode 100644 neo/renderer/OpenGL/gl_Image.cpp create mode 100644 neo/renderer/OpenGL/gl_backend.cpp create mode 100644 neo/renderer/OpenGL/glext.h create mode 100644 neo/renderer/OpenGL/qgl.h create mode 100644 neo/renderer/OpenGL/qgl_linked.h create mode 100644 neo/renderer/OpenGL/wglext.h create mode 100644 neo/renderer/RenderContext.h create mode 100644 neo/renderer/RenderEntity.cpp create mode 100644 neo/renderer/RenderLog.cpp create mode 100644 neo/renderer/RenderLog.h create mode 100644 neo/renderer/RenderProgs.cpp create mode 100644 neo/renderer/RenderProgs.h create mode 100644 neo/renderer/RenderProgs_GLSL.cpp create mode 100644 neo/renderer/RenderSystem.cpp create mode 100644 neo/renderer/RenderSystem.h create mode 100644 neo/renderer/RenderSystem_init.cpp create mode 100644 neo/renderer/RenderTexture.h create mode 100644 neo/renderer/RenderWorld.cpp create mode 100644 neo/renderer/RenderWorld.h create mode 100644 neo/renderer/RenderWorld_defs.cpp create mode 100644 neo/renderer/RenderWorld_demo.cpp create mode 100644 neo/renderer/RenderWorld_load.cpp create mode 100644 neo/renderer/RenderWorld_local.h create mode 100644 neo/renderer/RenderWorld_portals.cpp create mode 100644 neo/renderer/ResolutionScale.cpp create mode 100644 neo/renderer/ResolutionScale.h create mode 100644 neo/renderer/ScreenRect.cpp create mode 100644 neo/renderer/ScreenRect.h create mode 100644 neo/renderer/VertexCache.cpp create mode 100644 neo/renderer/VertexCache.h create mode 100644 neo/renderer/jobs/ShadowShared.cpp create mode 100644 neo/renderer/jobs/ShadowShared.h create mode 100644 neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.cpp create mode 100644 neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.h create mode 100644 neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume_local.h create mode 100644 neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.cpp create mode 100644 neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.h create mode 100644 neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume_local.h create mode 100644 neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.cpp create mode 100644 neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.h create mode 100644 neo/renderer/jobs/staticshadowvolume/StaticShadowVolume_local.h create mode 100644 neo/renderer/jpeg-6/jcapimin.cpp create mode 100644 neo/renderer/jpeg-6/jcapistd.cpp create mode 100644 neo/renderer/jpeg-6/jccoefct.cpp create mode 100644 neo/renderer/jpeg-6/jccolor.cpp create mode 100644 neo/renderer/jpeg-6/jcdctmgr.cpp create mode 100644 neo/renderer/jpeg-6/jchuff.cpp create mode 100644 neo/renderer/jpeg-6/jchuff.h create mode 100644 neo/renderer/jpeg-6/jcinit.cpp create mode 100644 neo/renderer/jpeg-6/jcmainct.cpp create mode 100644 neo/renderer/jpeg-6/jcmarker.cpp create mode 100644 neo/renderer/jpeg-6/jcmaster.cpp create mode 100644 neo/renderer/jpeg-6/jcomapi.cpp create mode 100644 neo/renderer/jpeg-6/jconfig.h create mode 100644 neo/renderer/jpeg-6/jcparam.cpp create mode 100644 neo/renderer/jpeg-6/jcphuff.cpp create mode 100644 neo/renderer/jpeg-6/jcprepct.cpp create mode 100644 neo/renderer/jpeg-6/jcsample.cpp create mode 100644 neo/renderer/jpeg-6/jctrans.cpp create mode 100644 neo/renderer/jpeg-6/jdapimin.cpp create mode 100644 neo/renderer/jpeg-6/jdapistd.cpp create mode 100644 neo/renderer/jpeg-6/jdatadst.cpp create mode 100644 neo/renderer/jpeg-6/jdatasrc.cpp create mode 100644 neo/renderer/jpeg-6/jdcoefct.cpp create mode 100644 neo/renderer/jpeg-6/jdcolor.cpp create mode 100644 neo/renderer/jpeg-6/jdct.h create mode 100644 neo/renderer/jpeg-6/jddctmgr.cpp create mode 100644 neo/renderer/jpeg-6/jdhuff.cpp create mode 100644 neo/renderer/jpeg-6/jdhuff.h create mode 100644 neo/renderer/jpeg-6/jdinput.cpp create mode 100644 neo/renderer/jpeg-6/jdmainct.cpp create mode 100644 neo/renderer/jpeg-6/jdmarker.cpp create mode 100644 neo/renderer/jpeg-6/jdmaster.cpp create mode 100644 neo/renderer/jpeg-6/jdmerge.cpp create mode 100644 neo/renderer/jpeg-6/jdphuff.cpp create mode 100644 neo/renderer/jpeg-6/jdpostct.cpp create mode 100644 neo/renderer/jpeg-6/jdsample.cpp create mode 100644 neo/renderer/jpeg-6/jdtrans.cpp create mode 100644 neo/renderer/jpeg-6/jerror.cpp create mode 100644 neo/renderer/jpeg-6/jerror.h create mode 100644 neo/renderer/jpeg-6/jfdctflt.cpp create mode 100644 neo/renderer/jpeg-6/jfdctfst.cpp create mode 100644 neo/renderer/jpeg-6/jfdctint.cpp create mode 100644 neo/renderer/jpeg-6/jidctflt.cpp create mode 100644 neo/renderer/jpeg-6/jidctfst.cpp create mode 100644 neo/renderer/jpeg-6/jidctint.cpp create mode 100644 neo/renderer/jpeg-6/jidctred.cpp create mode 100644 neo/renderer/jpeg-6/jinclude.h create mode 100644 neo/renderer/jpeg-6/jload.cpp create mode 100644 neo/renderer/jpeg-6/jmemansi.cpp create mode 100644 neo/renderer/jpeg-6/jmemdos.cpp create mode 100644 neo/renderer/jpeg-6/jmemmgr.cpp create mode 100644 neo/renderer/jpeg-6/jmemname.cpp create mode 100644 neo/renderer/jpeg-6/jmemnobs.cpp create mode 100644 neo/renderer/jpeg-6/jmemsys.h create mode 100644 neo/renderer/jpeg-6/jmorecfg.h create mode 100644 neo/renderer/jpeg-6/jpegint.h create mode 100644 neo/renderer/jpeg-6/jpeglib.h create mode 100644 neo/renderer/jpeg-6/jpegtran.cpp create mode 100644 neo/renderer/jpeg-6/jquant1.cpp create mode 100644 neo/renderer/jpeg-6/jquant2.cpp create mode 100644 neo/renderer/jpeg-6/jutils.cpp create mode 100644 neo/renderer/jpeg-6/jversion.h create mode 100644 neo/renderer/simplex.h create mode 100644 neo/renderer/tr_backend_draw.cpp create mode 100644 neo/renderer/tr_backend_rendertools.cpp create mode 100644 neo/renderer/tr_frontend_addlights.cpp create mode 100644 neo/renderer/tr_frontend_addmodels.cpp create mode 100644 neo/renderer/tr_frontend_deform.cpp create mode 100644 neo/renderer/tr_frontend_guisurf.cpp create mode 100644 neo/renderer/tr_frontend_main.cpp create mode 100644 neo/renderer/tr_frontend_subview.cpp create mode 100644 neo/renderer/tr_local.h create mode 100644 neo/renderer/tr_trace.cpp create mode 100644 neo/renderer/tr_trisurf.cpp create mode 100644 neo/sound/SoundVoice.cpp create mode 100644 neo/sound/SoundVoice.h create mode 100644 neo/sound/WaveFile.cpp create mode 100644 neo/sound/WaveFile.h create mode 100644 neo/sound/XAudio2/XA2_SoundHardware.cpp create mode 100644 neo/sound/XAudio2/XA2_SoundHardware.h create mode 100644 neo/sound/XAudio2/XA2_SoundSample.cpp create mode 100644 neo/sound/XAudio2/XA2_SoundSample.h create mode 100644 neo/sound/XAudio2/XA2_SoundVoice.cpp create mode 100644 neo/sound/XAudio2/XA2_SoundVoice.h create mode 100644 neo/sound/snd_emitter.cpp create mode 100644 neo/sound/snd_local.h create mode 100644 neo/sound/snd_shader.cpp create mode 100644 neo/sound/snd_system.cpp create mode 100644 neo/sound/snd_world.cpp create mode 100644 neo/sound/sound.h create mode 100644 neo/swf/SWF.h create mode 100644 neo/swf/SWF_Bitstream.cpp create mode 100644 neo/swf/SWF_Bitstream.h create mode 100644 neo/swf/SWF_Dictionary.cpp create mode 100644 neo/swf/SWF_Enums.h create mode 100644 neo/swf/SWF_Events.cpp create mode 100644 neo/swf/SWF_Image.cpp create mode 100644 neo/swf/SWF_Load.cpp create mode 100644 neo/swf/SWF_Main.cpp create mode 100644 neo/swf/SWF_Names.cpp create mode 100644 neo/swf/SWF_ParmList.cpp create mode 100644 neo/swf/SWF_ParmList.h create mode 100644 neo/swf/SWF_PlaceObject.cpp create mode 100644 neo/swf/SWF_Render.cpp create mode 100644 neo/swf/SWF_ScriptFunction.cpp create mode 100644 neo/swf/SWF_ScriptFunction.h create mode 100644 neo/swf/SWF_ScriptObject.cpp create mode 100644 neo/swf/SWF_ScriptObject.h create mode 100644 neo/swf/SWF_ScriptVar.cpp create mode 100644 neo/swf/SWF_ScriptVar.h create mode 100644 neo/swf/SWF_ShapeParser.cpp create mode 100644 neo/swf/SWF_ShapeParser.h create mode 100644 neo/swf/SWF_Shapes.cpp create mode 100644 neo/swf/SWF_Sounds.cpp create mode 100644 neo/swf/SWF_SpriteInstance.cpp create mode 100644 neo/swf/SWF_SpriteInstance.h create mode 100644 neo/swf/SWF_Sprites.cpp create mode 100644 neo/swf/SWF_Sprites.h create mode 100644 neo/swf/SWF_Text.cpp create mode 100644 neo/swf/SWF_TextInstance.cpp create mode 100644 neo/swf/SWF_TextInstance.h create mode 100644 neo/swf/SWF_Types.h create mode 100644 neo/swf/SWF_Zlib.cpp create mode 100644 neo/sys/LightweightCompression.cpp create mode 100644 neo/sys/LightweightCompression.h create mode 100644 neo/sys/PacketProcessor.cpp create mode 100644 neo/sys/PacketProcessor.h create mode 100644 neo/sys/Snapshot.cpp create mode 100644 neo/sys/Snapshot.h create mode 100644 neo/sys/SnapshotProcessor.cpp create mode 100644 neo/sys/SnapshotProcessor.h create mode 100644 neo/sys/Snapshot_Jobs.cpp create mode 100644 neo/sys/Snapshot_Jobs.h create mode 100644 neo/sys/sys_achievements.cpp create mode 100644 neo/sys/sys_achievements.h create mode 100644 neo/sys/sys_dedicated_server_search.cpp create mode 100644 neo/sys/sys_dedicated_server_search.h create mode 100644 neo/sys/sys_leaderboards.h create mode 100644 neo/sys/sys_lobby.cpp create mode 100644 neo/sys/sys_lobby.h create mode 100644 neo/sys/sys_lobby_backend.h create mode 100644 neo/sys/sys_lobby_backend_direct.cpp create mode 100644 neo/sys/sys_lobby_backend_direct.h create mode 100644 neo/sys/sys_lobby_migrate.cpp create mode 100644 neo/sys/sys_lobby_snapshot.cpp create mode 100644 neo/sys/sys_lobby_users.cpp create mode 100644 neo/sys/sys_local.cpp create mode 100644 neo/sys/sys_local.h create mode 100644 neo/sys/sys_localuser.cpp create mode 100644 neo/sys/sys_localuser.h create mode 100644 neo/sys/sys_profile.cpp create mode 100644 neo/sys/sys_profile.h create mode 100644 neo/sys/sys_public.h create mode 100644 neo/sys/sys_savegame.cpp create mode 100644 neo/sys/sys_savegame.h create mode 100644 neo/sys/sys_session.h create mode 100644 neo/sys/sys_session_callbacks.cpp create mode 100644 neo/sys/sys_session_local.cpp create mode 100644 neo/sys/sys_session_local.h create mode 100644 neo/sys/sys_session_savegames.cpp create mode 100644 neo/sys/sys_session_savegames.h create mode 100644 neo/sys/sys_signin.cpp create mode 100644 neo/sys/sys_signin.h create mode 100644 neo/sys/sys_stats.h create mode 100644 neo/sys/sys_stats_misc.h create mode 100644 neo/sys/sys_voicechat.cpp create mode 100644 neo/sys/sys_voicechat.h create mode 100644 neo/sys/win32/rc/doom.rc create mode 100644 neo/sys/win32/rc/doom_resource.h create mode 100644 neo/sys/win32/rc/res/doom.ico create mode 100644 neo/sys/win32/win_achievements.cpp create mode 100644 neo/sys/win32/win_achievements.h create mode 100644 neo/sys/win32/win_cpu.cpp create mode 100644 neo/sys/win32/win_gamma.cpp create mode 100644 neo/sys/win32/win_glimp.cpp create mode 100644 neo/sys/win32/win_input.cpp create mode 100644 neo/sys/win32/win_input.h create mode 100644 neo/sys/win32/win_local.h create mode 100644 neo/sys/win32/win_localuser.cpp create mode 100644 neo/sys/win32/win_localuser.h create mode 100644 neo/sys/win32/win_main.cpp create mode 100644 neo/sys/win32/win_net.cpp create mode 100644 neo/sys/win32/win_qgl.cpp create mode 100644 neo/sys/win32/win_savegame.cpp create mode 100644 neo/sys/win32/win_session_local.cpp create mode 100644 neo/sys/win32/win_shared.cpp create mode 100644 neo/sys/win32/win_signin.cpp create mode 100644 neo/sys/win32/win_signin.h create mode 100644 neo/sys/win32/win_snd.cpp create mode 100644 neo/sys/win32/win_stats.cpp create mode 100644 neo/sys/win32/win_stats.h create mode 100644 neo/sys/win32/win_syscon.cpp create mode 100644 neo/sys/win32/win_taskkeyhook.cpp create mode 100644 neo/sys/win32/win_wndproc.cpp create mode 100644 neo/ui/BindWindow.cpp create mode 100644 neo/ui/BindWindow.h create mode 100644 neo/ui/ChoiceWindow.cpp create mode 100644 neo/ui/ChoiceWindow.h create mode 100644 neo/ui/DeviceContext.cpp create mode 100644 neo/ui/DeviceContext.h create mode 100644 neo/ui/EditWindow.cpp create mode 100644 neo/ui/EditWindow.h create mode 100644 neo/ui/FieldWindow.cpp create mode 100644 neo/ui/FieldWindow.h create mode 100644 neo/ui/GameBearShootWindow.cpp create mode 100644 neo/ui/GameBearShootWindow.h create mode 100644 neo/ui/GameBustOutWindow.cpp create mode 100644 neo/ui/GameBustOutWindow.h create mode 100644 neo/ui/GameSSDWindow.cpp create mode 100644 neo/ui/GameSSDWindow.h create mode 100644 neo/ui/GameWindow.cpp create mode 100644 neo/ui/GameWindow.h create mode 100644 neo/ui/GuiScript.cpp create mode 100644 neo/ui/GuiScript.h create mode 100644 neo/ui/ListGUI.cpp create mode 100644 neo/ui/ListGUI.h create mode 100644 neo/ui/ListGUILocal.h create mode 100644 neo/ui/ListWindow.cpp create mode 100644 neo/ui/ListWindow.h create mode 100644 neo/ui/Rectangle.h create mode 100644 neo/ui/RegExp.cpp create mode 100644 neo/ui/RegExp.h create mode 100644 neo/ui/RegExp_old.h create mode 100644 neo/ui/RenderWindow.cpp create mode 100644 neo/ui/RenderWindow.h create mode 100644 neo/ui/SimpleWindow.cpp create mode 100644 neo/ui/SimpleWindow.h create mode 100644 neo/ui/SliderWindow.cpp create mode 100644 neo/ui/SliderWindow.h create mode 100644 neo/ui/UserInterface.cpp create mode 100644 neo/ui/UserInterface.h create mode 100644 neo/ui/UserInterfaceLocal.h create mode 100644 neo/ui/Window.cpp create mode 100644 neo/ui/Window.h create mode 100644 neo/ui/Winvar.cpp create mode 100644 neo/ui/Winvar.h diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 00000000..6108960e --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,682 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + + +ADDITIONAL TERMS APPLICABLE TO THE Doom 3 BFG Edition GPL Source Code. + + The following additional terms ("Additional Terms") supplement and modify + the GNU General Public License, Version 3 ("GPL") applicable to the Doom 3 + BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). In addition + to the terms and conditions of the GPL, the Doom 3 BFG Edition Source Code is + subject to the further restrictions below. + +1. Replacement of Section 15. Section 15 of the GPL shall be deleted in its +entirety and replaced with the following: + +"15. Disclaimer of Warranty. + +THE PROGRAM IS PROVIDED WITHOUT ANY WARRANTIES, WHETHER EXPRESSED OR IMPLIED, +INCLUDING, WITHOUT LIMITATION, IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +PURPOSE, NON-INFRINGEMENT, TITLE AND MERCHANTABILITY. THE PROGRAM IS BEING +DELIVERED OR MADE AVAILABLE "AS IS", "WITH ALL FAULTS" AND WITHOUT WARRANTY OR +REPRESENTATION. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST +OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION." + +2. Replacement of Section 16. Section 16 of the GPL shall be deleted in its +entirety and replaced with the following: + +"16. LIMITATION OF LIABILITY. + +UNDER NO CIRCUMSTANCES SHALL ANY COPYRIGHT HOLDER OR ITS AFFILIATES, OR ANY +OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE +LIABLE TO YOU, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, FOR ANY +DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, DIRECT, INDIRECT, SPECIAL, +INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES ARISING FROM, OUT OF OR IN +CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM OR OTHER DEALINGS WITH +THE PROGRAM(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), WHETHER OR NOT ANY COPYRIGHT +HOLDER OR SUCH OTHER PARTY RECEIVES NOTICE OF ANY SUCH DAMAGES AND WHETHER +OR NOT SUCH DAMAGES COULD HAVE BEEN FORESEEN." + +3. LEGAL NOTICES; NO TRADEMARK LICENSE; ORIGIN. You must reproduce faithfully +all trademark, copyright and other proprietary and legal notices on any copies +of the Program or any other required author attributions. This license does +not grant you rights to use any copyright holder or any other party’s name, +logo, or trademarks. Neither the name of the copyright holder or its +affiliates, or any other party who modifies and/or conveys the Program may be +used to endorse or promote products derived from this software without +specific prior written permission. The origin of the Program must not be +misrepresented; you must not claim that you wrote the original Program. + +Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original Program. + +4. INDEMNIFICATION. IF YOU CONVEY A COVERED WORK AND AGREE WITH ANY RECIPIENT +OF THAT COVERED WORK THAT YOU WILL ASSUME ANY LIABILITY FOR THAT COVERED WORK, +YOU HEREBY AGREE TO INDEMNIFY, DEFEND AND HOLD HARMLESS THE OTHER LICENSORS +AND AUTHORS OF THAT COVERED WORK FOR ANY DAMAEGS, DEMANDS, CLAIMS, LOSSES, +CAUSES OF ACTION, LAWSUITS, JUDGMENTS EXPENSES (INCLUDING WITHOUT LIMITATION +REASONABLE ATTORNEYS' FEES AND EXPENSES) OR ANY OTHER LIABLITY ARISING FROM, +RELATED TO OR IN CONNECTION WITH YOUR ASSUMPTIONS OF LIABILITY. diff --git a/README.txt b/README.txt new file mode 100644 index 00000000..ab595e7a --- /dev/null +++ b/README.txt @@ -0,0 +1,257 @@ +Doom 3 BFG Edition GPL Source Code +================================== + +This file contains the following sections: + +GENERAL NOTES +LICENSE + +GENERAL NOTES +============= + +Game data and patching: +----------------------- + +This source release does not contain any game data, the game data is still +covered by the original EULA and must be obeyed as usual. + +You must patch the game to the latest version. + +Note that Doom 3 BFG Edition is available from the Steam store at +http://store.steampowered.com/app/208200/ + + +Compiling on win32: +------------------- + +A project file for Microsoft Visual Studio 2010 is provided in neo\doom3.sln +We expect the solution file is compatible with the Express releases + +You will need the Microsoft DirectX SDK installed as well. +If it does not reside in "C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)" +you will need to update the project files accordingly. + + +Steam: +------ +The Doom 3 BFG Edition GPL Source Code release does not include functionality for integrating with +Steam. This includes roaming profiles, achievements, leaderboards, matchmaking, the overlay, or +any other Steam features. + + +Bink: +----- + +The Doom 3 BFG Edition GPL Source Code release does not include functionality for rendering Bink Videos. + + +Back End Rendering of Stencil Shadows: +-------------------------------------- + +The Doom 3 BFG Edition GPL Source Code release does not include functionality enabling rendering +of stencil shadows via the "depth fail" method, a functionality commonly known as "Carmack's Reverse". + + +Other platforms, updated source code, security issues: +------------------------------------------------------ + +If you have obtained this source code several weeks after the time of release, +it is likely that you can find modified and improved versions of the engine in +various open source projects across the internet. + +Depending what is your interest with the source code, those may be a better starting point. + + +LICENSE +======= + +See COPYING.txt for the GNU GENERAL PUBLIC LICENSE + +ADDITIONAL TERMS: The Doom 3 BFG Edition GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU GPL which accompanied the Doom 3 BFG Edition GPL Source Code. If not, please request a copy in writing from id Software at id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +EXCLUDED CODE: The code described below and contained in the Doom 3 BFG Edition GPL Source Code release is not part of the Program covered by the GPL and is expressly excluded from its terms. You are solely responsible for obtaining from the copyright holder a license for such code and complying with the applicable license terms. + +JPEG library +----------------------------------------------------------------------------- +neo/renderer/jpeg-6/* + +Copyright (C) 1991-1995, Thomas G. Lane + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +NOTE: unfortunately the README that came with our copy of the library has +been lost, so the one from release 6b is included instead. There are a few +'glue type' modifications to the library to make it easier to use from +the engine, but otherwise the dependency can be easily cleaned up to a +better release of the library. + +zlib library +--------------------------------------------------------------------------- +neo/framework/zlib/* + +Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +Base64 implementation +--------------------------------------------------------------------------- +neo/idlib/Base64.cpp + +Copyright (c) 1996 Lars Wirzenius. All rights reserved. + +June 14 2003: TTimo + modified + endian bug fixes + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=197039 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +IO for uncompress .zip files using zlib +--------------------------------------------------------------------------- +neo/framework/Unzip.cpp +neo/framework/Unzip.h + +Copyright (C) 1998 Gilles Vollant +zlib is Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +MD4 Message-Digest Algorithm +----------------------------------------------------------------------------- +neo/idlib/hashing/MD4.cpp +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +MD5 Message-Digest Algorithm +----------------------------------------------------------------------------- +neo/idlib/hashing/MD5.cpp +This code implements the MD5 message-digest algorithm. +The algorithm is due to Ron Rivest. This code was +written by Colin Plumb in 1993, no copyright is claimed. +This code is in the public domain; do with it what you wish. + +CRC32 Checksum +----------------------------------------------------------------------------- +neo/idlib/hashing/CRC32.cpp +Copyright (C) 1995-1998 Mark Adler + +OpenGL headers +--------------------------------------------------------------------------- +neo/renderer/OpenGL/glext.h +neo/renderer/OpenGL/wglext.h + +Copyright (c) 2007-2012 The Khronos Group Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and/or associated documentation files (the +"Materials"), to deal in the Materials without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Materials, and to +permit persons to whom the Materials are 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 Materials. + +THE MATERIALS ARE 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 THE AUTHORS OR COPYRIGHT HOLDERS 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 +MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + +Timidity +--------------------------------------------------------------------------- +doomclassic/timidity/* + +Copyright (c) 1995 Tuukka Toivonen + +From http://www.cgs.fi/~tt/discontinued.html : + +If you'd like to continue hacking on TiMidity, feel free. I'm +hereby extending the TiMidity license agreement: you can now +select the most convenient license for your needs from (1) the +GNU GPL, (2) the GNU LGPL, or (3) the Perl Artistic License. diff --git a/base/default.cfg b/base/default.cfg new file mode 100644 index 00000000..fa81adaf --- /dev/null +++ b/base/default.cfg @@ -0,0 +1 @@ +# empty file diff --git a/base/renderprogs/bink.pixel b/base/renderprogs/bink.pixel new file mode 100644 index 00000000..f270fcaa --- /dev/null +++ b/base/renderprogs/bink.pixel @@ -0,0 +1,60 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // Y +uniform sampler2D samp1 : register(s1); // Cr +uniform sampler2D samp2 : register(s2); // Cb + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + const float3 crc = float3( 1.595794678, -0.813476563, 0 ); + const float3 crb = float3( 0, -0.391448975, 2.017822266 ); + const float3 adj = float3( -0.87065506, 0.529705048f, -1.081668854f ); + const float3 YScalar = float3( 1.164123535f, 1.164123535f, 1.164123535f ); + + float Y = tex2D( samp0, fragment.texcoord0.xy ).x; + float Cr = tex2D( samp1, fragment.texcoord0.xy ).x; + float Cb = tex2D( samp2, fragment.texcoord0.xy ).x; + + float3 p = ( YScalar * Y ); + p += ( crc * Cr ) + ( crb * Cb ) + adj; + + result.color.xyz = p; + result.color.w = 1.0; + result.color *= rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/bink.vertex b/base/renderprogs/bink.vertex new file mode 100644 index 00000000..292dde2d --- /dev/null +++ b/base/renderprogs/bink.vertex @@ -0,0 +1,51 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/bink_gui.pixel b/base/renderprogs/bink_gui.pixel new file mode 100644 index 00000000..c98c0ccb --- /dev/null +++ b/base/renderprogs/bink_gui.pixel @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // Y +uniform sampler2D samp1 : register(s1); // Cr +uniform sampler2D samp2 : register(s2); // Cb + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float4 texcoord1 : TEXCOORD1_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + const float3 crc = float3( 1.595794678, -0.813476563, 0 ); + const float3 crb = float3( 0, -0.391448975, 2.017822266 ); + const float3 adj = float3( -0.87065506, 0.529705048f, -1.081668854f ); + const float3 YScalar = float3( 1.164123535f, 1.164123535f, 1.164123535f ); + + float Y = tex2D( samp0, fragment.texcoord0.xy ).x; + float Cr = tex2D( samp1, fragment.texcoord0.xy ).x; + float Cb = tex2D( samp2, fragment.texcoord0.xy ).x; + + float3 p = ( YScalar * Y ); + p += ( crc * Cr ) + ( crb * Cb ) + adj; + + float4 binkImage; + binkImage.xyz = p; + binkImage.w = 1.0; + + float4 color = ( binkImage * fragment.color ) + fragment.texcoord1; + result.color.xyz = color.xyz * color.w; + result.color.w = color.w; +} \ No newline at end of file diff --git a/base/renderprogs/bink_gui.vertex b/base/renderprogs/bink_gui.vertex new file mode 100644 index 00000000..fbe8a535 --- /dev/null +++ b/base/renderprogs/bink_gui.vertex @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0.xy = vertex.texcoord.xy; + result.texcoord1 = ( swizzleColor( vertex.color2 ) * 2 ) - 1; + result.color = swizzleColor( vertex.color ); +} diff --git a/base/renderprogs/blendLight.pixel b/base/renderprogs/blendLight.pixel new file mode 100644 index 00000000..3a066615 --- /dev/null +++ b/base/renderprogs/blendLight.pixel @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +uniform sampler2D samp1 : register(s1); + +struct PS_IN { + float4 position : VPOS; + float4 texcoord0 : TEXCOORD0_centroid; + float2 texcoord1 : TEXCOORD1_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + result.color = idtex2Dproj( samp0, fragment.texcoord0 ) * tex2D( samp1, fragment.texcoord1 ) * rpColor; +} diff --git a/base/renderprogs/blendLight.vertex b/base/renderprogs/blendLight.vertex new file mode 100644 index 00000000..b024f18e --- /dev/null +++ b/base/renderprogs/blendLight.vertex @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 binormal : BINORMAL; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0.x = dot4( vertex.position, rpTexGen0S ); + result.texcoord0.y = dot4( vertex.position, rpTexGen0T ); + result.texcoord0.z = 0.0; + result.texcoord0.w = dot4( vertex.position, rpTexGen0Q ); + + result.texcoord1.x = dot4( vertex.position, rpTexGen1S ); + result.texcoord1.y = 0.5; +} diff --git a/base/renderprogs/bloodorb1_capture.pixel b/base/renderprogs/bloodorb1_capture.pixel new file mode 100644 index 00000000..7c44f784 --- /dev/null +++ b/base/renderprogs/bloodorb1_capture.pixel @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); //_accum +uniform sampler2D samp1 : register(s1); //_currentRender +uniform sampler2D samp2 : register(s2); //mask + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float2 texcoord1 : TEXCOORD1_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float4 accumSample = tex2D( samp0, fragment.texcoord0 ); + float4 maskSample = tex2D( samp2, fragment.texcoord1 ); + float4 currentRenderSample = tex2D( samp1, fragment.texcoord1 ); + + result.color = lerp( accumSample, currentRenderSample, maskSample.a ); +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb1_capture.vertex b/base/renderprogs/bloodorb1_capture.vertex new file mode 100644 index 00000000..b3f7c926 --- /dev/null +++ b/base/renderprogs/bloodorb1_capture.vertex @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform float4 rpUser0 : register( c128 ); //rpCenterScale + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + //center scale + const float4 centerScale = rpUser0; + result.texcoord0 = CenterScale( vertex.texcoord, centerScale.xy ); + + // pass through texcoords + result.texcoord1 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb2_capture.pixel b/base/renderprogs/bloodorb2_capture.pixel new file mode 100644 index 00000000..e897785f --- /dev/null +++ b/base/renderprogs/bloodorb2_capture.pixel @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); //_accum +uniform sampler2D samp1 : register(s1); //_currentRender +uniform sampler2D samp2 : register(s2); //mask + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float2 texcoord1 : TEXCOORD1_centroid; + float2 texcoord2 : TEXCOORD2_centroid; + //float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float4 redTint = float4( 1, 0.98, 0.98, 1 ); + float4 accumSample = tex2D( samp0, fragment.texcoord0 ) * redTint; + + float4 maskSample = tex2D( samp2, fragment.texcoord1 ); + + float4 tint = float4( 1.0, 0.8, 0.8, 1 ); + float4 currentRenderSample = tex2D( samp1, fragment.texcoord2 ) * tint; + + result.color = lerp( accumSample, currentRenderSample, maskSample.a ); +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb2_capture.vertex b/base/renderprogs/bloodorb2_capture.vertex new file mode 100644 index 00000000..0e7deada --- /dev/null +++ b/base/renderprogs/bloodorb2_capture.vertex @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform float4 rpUser0 : register( c128 ); //rpCenterScaleTex0 +uniform float4 rpUser1 : register( c129 ); //rpRotateTex0 +uniform float4 rpUser2 : register( c130 ); //rpCenterScaleTex1 + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; + float2 texcoord2 : TEXCOORD2; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + const float4 centerScaleTex0 = rpUser0; + const float4 rotateTex0 = rpUser1; + const float4 centerScaleTex1 = rpUser2; + + //center scale and rotate for _accum + float2 tc0 = CenterScale( vertex.texcoord, centerScaleTex0.xy ); + result.texcoord0 = Rotate2D( tc0, rotateTex0.xy ); + + //center scale for mask + result.texcoord1 = CenterScale( vertex.texcoord, centerScaleTex1.xy ); + + // pass through for currentrender + result.texcoord2 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb3_capture.pixel b/base/renderprogs/bloodorb3_capture.pixel new file mode 100644 index 00000000..588486e8 --- /dev/null +++ b/base/renderprogs/bloodorb3_capture.pixel @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); //_accum +uniform sampler2D samp1 : register(s1); //_currentRender +uniform sampler2D samp2 : register(s2); //mask + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float2 texcoord1 : TEXCOORD1_centroid; + float2 texcoord2 : TEXCOORD2_centroid; + float2 texcoord3 : TEXCOORD3_centroid; + float2 texcoord4 : TEXCOORD4; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float colorFactor = fragment.texcoord4.x; + + float4 color0 = float4( 1.0f - colorFactor, 1.0f - colorFactor, 1.0f, 1.0f ); + float4 color1 = float4( 1.0f, 0.95f - colorFactor, 0.95f, 0.5f ); + float4 color2 = float4( 0.015f, 0.015f, 0.015f, 0.01f ); + + float4 accumSample0 = tex2D( samp0, fragment.texcoord0 ) * color0; + float4 accumSample1 = tex2D( samp0, fragment.texcoord1 ) * color1; + float4 accumSample2 = tex2D( samp0, fragment.texcoord2 ) * color2; + float4 maskSample = tex2D( samp2, fragment.texcoord3 ); + + float4 tint = float4( 0.8, 0.5, 0.5, 1 ); + float4 currentRenderSample = tex2D( samp1, fragment.texcoord3 ) * tint; + + // blend of the first 2 accumulation samples + float4 accumColor = lerp( accumSample0, accumSample1, 0.5f ); + // add thrid sample + accumColor += accumSample2; + + accumColor = lerp( accumColor, currentRenderSample, maskSample.a ); + result.color = accumColor; +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb3_capture.vertex b/base/renderprogs/bloodorb3_capture.vertex new file mode 100644 index 00000000..29d31dff --- /dev/null +++ b/base/renderprogs/bloodorb3_capture.vertex @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform float4 rpUser0 : register( c128 ); //rpCenterScaleTex +uniform float4 rpUser1 : register( c129 ); //rpRotateTex +uniform float4 rpUser2 : register( c130 ); //rpColorFactor + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; + float2 texcoord2 : TEXCOORD2; + float2 texcoord3 : TEXCOORD3; + float2 texcoord4 : TEXCOORD4; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + //_accum 1 + const float4 centerScaleTex = rpUser0; + const float4 rotateTex = rpUser1; + float2 tc0 = CenterScale( vertex.texcoord, centerScaleTex.xy ); + result.texcoord0 = Rotate2D( tc0, rotateTex.xy ); + + // accum 2 + result.texcoord1 = Rotate2D( tc0, float2( rotateTex.z, -rotateTex.w ) ); + + // accum 3 + result.texcoord2 = Rotate2D( tc0, rotateTex.zw ); + + // pass through for currentrender + result.texcoord3 = vertex.texcoord; + + // pass through the color fator + const float4 colorFactor = rpUser2; + result.texcoord4 = colorFactor.xx; +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb_draw.pixel b/base/renderprogs/bloodorb_draw.pixel new file mode 100644 index 00000000..3714e6da --- /dev/null +++ b/base/renderprogs/bloodorb_draw.pixel @@ -0,0 +1,51 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); //_accum +uniform sampler2D samp1 : register(s1); //_currentRender +uniform sampler2D samp2 : register(s2); //mask + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float4 accumSample = tex2D( samp0, fragment.texcoord0 ); + float4 currentRenderSample = tex2D( samp1, fragment.texcoord0 ); + float4 maskSample = tex2D( samp2, fragment.texcoord0 ); + + result.color = lerp( accumSample, currentRenderSample, maskSample.a ); +} \ No newline at end of file diff --git a/base/renderprogs/bloodorb_draw.vertex b/base/renderprogs/bloodorb_draw.vertex new file mode 100644 index 00000000..a0a179d6 --- /dev/null +++ b/base/renderprogs/bloodorb_draw.vertex @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + // pass through texcoords + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/bumpyenvironment.pixel b/base/renderprogs/bumpyenvironment.pixel new file mode 100644 index 00000000..6afc0261 --- /dev/null +++ b/base/renderprogs/bumpyenvironment.pixel @@ -0,0 +1,67 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform samplerCUBE samp0 : register(s0); // texture 0 is the cube map +uniform sampler2D samp1 : register(s1); // normal map + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float3 texcoord1 : TEXCOORD1_centroid; + float3 texcoord2 : TEXCOORD2_centroid; + float3 texcoord3 : TEXCOORD3_centroid; + float3 texcoord4 : TEXCOORD4_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float4 bump = tex2D( samp1, fragment.texcoord0 ) * 2.0f - 1.0f; + float3 localNormal = float3( bump.wy, 0.0f ); + localNormal.z = sqrt( 1.0f - dot3( localNormal, localNormal ) ); + + float3 globalNormal; + globalNormal.x = dot3( localNormal, fragment.texcoord2 ); + globalNormal.y = dot3( localNormal, fragment.texcoord3 ); + globalNormal.z = dot3( localNormal, fragment.texcoord4 ); + + float3 globalEye = normalize( fragment.texcoord1 ); + + float3 reflectionVector = globalNormal * dot3( globalEye, globalNormal ); + reflectionVector = ( reflectionVector * 2.0f ) - globalEye; + + float4 envMap = texCUBE( samp0, reflectionVector ); + + result.color = float4( envMap.xyz, 1.0f ) * fragment.color; +} diff --git a/base/renderprogs/bumpyenvironment.vertex b/base/renderprogs/bumpyenvironment.vertex new file mode 100644 index 00000000..59ab069f --- /dev/null +++ b/base/renderprogs/bumpyenvironment.vertex @@ -0,0 +1,80 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float3 texcoord1 : TEXCOORD1; + float3 texcoord2 : TEXCOORD2; + float3 texcoord3 : TEXCOORD3; + float3 texcoord4 : TEXCOORD4; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + float4 normal = vertex.normal * 2.0 - 1.0; + float4 tangent = vertex.tangent * 2.0 - 1.0; + float3 binormal = cross( normal.xyz, tangent.xyz ) * tangent.w; + + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0 = vertex.texcoord.xy; + + float4 toEye = rpLocalViewOrigin - vertex.position; + result.texcoord1.x = dot3( toEye, rpModelMatrixX ); + result.texcoord1.y = dot3( toEye, rpModelMatrixY ); + result.texcoord1.z = dot3( toEye, rpModelMatrixZ ); + + result.texcoord2.x = dot3( tangent, rpModelMatrixX ); + result.texcoord3.x = dot3( tangent, rpModelMatrixY ); + result.texcoord4.x = dot3( tangent, rpModelMatrixZ ); + + result.texcoord2.y = dot3( binormal, rpModelMatrixX ); + result.texcoord3.y = dot3( binormal, rpModelMatrixY ); + result.texcoord4.y = dot3( binormal, rpModelMatrixZ ); + + result.texcoord2.z = dot3( normal, rpModelMatrixX ); + result.texcoord3.z = dot3( normal, rpModelMatrixY ); + result.texcoord4.z = dot3( normal, rpModelMatrixZ ); + + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/bumpyenvironment_skinned.pixel b/base/renderprogs/bumpyenvironment_skinned.pixel new file mode 100644 index 00000000..6afc0261 --- /dev/null +++ b/base/renderprogs/bumpyenvironment_skinned.pixel @@ -0,0 +1,67 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform samplerCUBE samp0 : register(s0); // texture 0 is the cube map +uniform sampler2D samp1 : register(s1); // normal map + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float3 texcoord1 : TEXCOORD1_centroid; + float3 texcoord2 : TEXCOORD2_centroid; + float3 texcoord3 : TEXCOORD3_centroid; + float3 texcoord4 : TEXCOORD4_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float4 bump = tex2D( samp1, fragment.texcoord0 ) * 2.0f - 1.0f; + float3 localNormal = float3( bump.wy, 0.0f ); + localNormal.z = sqrt( 1.0f - dot3( localNormal, localNormal ) ); + + float3 globalNormal; + globalNormal.x = dot3( localNormal, fragment.texcoord2 ); + globalNormal.y = dot3( localNormal, fragment.texcoord3 ); + globalNormal.z = dot3( localNormal, fragment.texcoord4 ); + + float3 globalEye = normalize( fragment.texcoord1 ); + + float3 reflectionVector = globalNormal * dot3( globalEye, globalNormal ); + reflectionVector = ( reflectionVector * 2.0f ) - globalEye; + + float4 envMap = texCUBE( samp0, reflectionVector ); + + result.color = float4( envMap.xyz, 1.0f ) * fragment.color; +} diff --git a/base/renderprogs/bumpyenvironment_skinned.vertex b/base/renderprogs/bumpyenvironment_skinned.vertex new file mode 100644 index 00000000..50b62804 --- /dev/null +++ b/base/renderprogs/bumpyenvironment_skinned.vertex @@ -0,0 +1,137 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float3 texcoord1 : TEXCOORD1; + float3 texcoord2 : TEXCOORD2; + float3 texcoord3 : TEXCOORD3; + float3 texcoord4 : TEXCOORD4; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + float4 vNormal = vertex.normal * 2.0 - 1.0; + float4 vTangent = vertex.tangent * 2.0 - 1.0; + float3 vBinormal = cross( vNormal.xyz, vTangent.xyz ) * vTangent.w; + + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float3 normal; + normal.x = dot3( matX, vNormal ); + normal.y = dot3( matY, vNormal ); + normal.z = dot3( matZ, vNormal ); + normal = normalize( normal ); + + float3 tangent; + tangent.x = dot3( matX, vTangent ); + tangent.y = dot3( matY, vTangent ); + tangent.z = dot3( matZ, vTangent ); + tangent = normalize( tangent ); + + float3 binormal; + binormal.x = dot3( matX, vBinormal ); + binormal.y = dot3( matY, vBinormal ); + binormal.z = dot3( matZ, vBinormal ); + binormal = normalize( binormal ); + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + result.texcoord0 = vertex.texcoord.xy; + + float4 toEye = rpLocalViewOrigin - modelPosition; + result.texcoord1.x = dot3( toEye, rpModelMatrixX ); + result.texcoord1.y = dot3( toEye, rpModelMatrixY ); + result.texcoord1.z = dot3( toEye, rpModelMatrixZ ); + + result.texcoord2.x = dot3( tangent, rpModelMatrixX ); + result.texcoord3.x = dot3( tangent, rpModelMatrixY ); + result.texcoord4.x = dot3( tangent, rpModelMatrixZ ); + + result.texcoord2.y = dot3( binormal, rpModelMatrixX ); + result.texcoord3.y = dot3( binormal, rpModelMatrixY ); + result.texcoord4.y = dot3( binormal, rpModelMatrixZ ); + + result.texcoord2.z = dot3( normal, rpModelMatrixX ); + result.texcoord3.z = dot3( normal, rpModelMatrixY ); + result.texcoord4.z = dot3( normal, rpModelMatrixZ ); + + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/color.pixel b/base/renderprogs/color.pixel new file mode 100644 index 00000000..2726f008 --- /dev/null +++ b/base/renderprogs/color.pixel @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/color.vertex b/base/renderprogs/color.vertex new file mode 100644 index 00000000..e330f16a --- /dev/null +++ b/base/renderprogs/color.vertex @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/colorProcess.pixel b/base/renderprogs/colorProcess.pixel new file mode 100644 index 00000000..5c602b68 --- /dev/null +++ b/base/renderprogs/colorProcess.pixel @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float4 color : COLOR; + float3 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float4 src = tex2D( samp0, fragment.texcoord0.xy ); + float4 target = fragment.color * dot3( float3( 0.333, 0.333, 0.333 ), src ); + result.color = lerp( src, target, fragment.texcoord0.z ); +} diff --git a/base/renderprogs/colorProcess.vertex b/base/renderprogs/colorProcess.vertex new file mode 100644 index 00000000..a90be574 --- /dev/null +++ b/base/renderprogs/colorProcess.vertex @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform float4 rpUser0 : register(c128); //rpFraction +uniform float4 rpUser1 : register(c129); //rpTargetHue + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 color : COLOR0; + float3 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.color = rpUser1; // targetHue + + result.texcoord0.x = vertex.texcoord.x; + result.texcoord0.y = 1.0f - vertex.texcoord.y; + + result.texcoord0.z = rpUser0.x; // fraction +} + diff --git a/base/renderprogs/depth.pixel b/base/renderprogs/depth.pixel new file mode 100644 index 00000000..8b486789 --- /dev/null +++ b/base/renderprogs/depth.pixel @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = float4( 0.0, 0.0, 0.0, 1.0 ); +} \ No newline at end of file diff --git a/base/renderprogs/depth.vertex b/base/renderprogs/depth.vertex new file mode 100644 index 00000000..ed5588d0 --- /dev/null +++ b/base/renderprogs/depth.vertex @@ -0,0 +1,44 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/depth_skinned.pixel b/base/renderprogs/depth_skinned.pixel new file mode 100644 index 00000000..8b486789 --- /dev/null +++ b/base/renderprogs/depth_skinned.pixel @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = float4( 0.0, 0.0, 0.0, 1.0 ); +} \ No newline at end of file diff --git a/base/renderprogs/depth_skinned.vertex b/base/renderprogs/depth_skinned.vertex new file mode 100644 index 00000000..36253bff --- /dev/null +++ b/base/renderprogs/depth_skinned.vertex @@ -0,0 +1,85 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/enviroSuit.pixel b/base/renderprogs/enviroSuit.pixel new file mode 100644 index 00000000..83ab7d06 --- /dev/null +++ b/base/renderprogs/enviroSuit.pixel @@ -0,0 +1,57 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 0 is _current Render +uniform sampler2D samp1 : register(s1); // texture 1 is the per-surface bump map + +struct PS_IN { + float4 position : VPOS; + float2 texcoord : TEXCOORD0_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float2 screenTexCoord = fragment.texcoord; + + // compute warp factor + float4 warpFactor = 1.0 - ( tex2D( samp1, screenTexCoord.xy ) * fragment.color ); + screenTexCoord -= float2( 0.5, 0.5 ); + screenTexCoord *= warpFactor.xy; + screenTexCoord += float2( 0.5, 0.5 ); + + // load the screen render + result.color = tex2D( samp0, screenTexCoord ); + +} \ No newline at end of file diff --git a/base/renderprogs/enviroSuit.vertex b/base/renderprogs/enviroSuit.vertex new file mode 100644 index 00000000..56de6805 --- /dev/null +++ b/base/renderprogs/enviroSuit.vertex @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +// User Renderparms start at 128 as per renderprogs.h + +uniform float4 rpUser0 : register(c128); // rpScroll +uniform float4 rpUser1 : register(c129); // rpDeformMagnitude + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 color : COLOR; +}; + + +void main( VS_IN vertex, out VS_OUT result ) { + + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord = vertex.texcoord.xy; + + const float4 deformMagnitude = rpUser1; + result.color = deformMagnitude; +} \ No newline at end of file diff --git a/base/renderprogs/environment.pixel b/base/renderprogs/environment.pixel new file mode 100644 index 00000000..589ffed9 --- /dev/null +++ b/base/renderprogs/environment.pixel @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform samplerCUBE samp0 : register(s0); // texture 0 is the cube map + +struct PS_IN { + float4 position : VPOS; + float3 texcoord0 : TEXCOORD0_centroid; + float3 texcoord1 : TEXCOORD1_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float3 globalNormal = normalize( fragment.texcoord1 ); + float3 globalEye = normalize( fragment.texcoord0 ); + + float3 reflectionVector = _float3( dot3( globalEye, globalNormal ) ); + reflectionVector *= globalNormal; + reflectionVector = ( reflectionVector * 2.0f ) - globalEye; + + float4 envMap = texCUBE( samp0, reflectionVector ); + + result.color = float4( envMap.xyz, 1.0f ) * fragment.color; +} diff --git a/base/renderprogs/environment.vertex b/base/renderprogs/environment.vertex new file mode 100644 index 00000000..bcd3667d --- /dev/null +++ b/base/renderprogs/environment.vertex @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float4 normal : NORMAL; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float3 texcoord1 : TEXCOORD1; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + float4 vNormal = vertex.normal * 2.0 - 1.0; + + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + float4 toEye = rpLocalViewOrigin - vertex.position; + + result.texcoord0 = toEye.xyz; + result.texcoord1 = vNormal.xyz; + + result.color = rpColor; +} diff --git a/base/renderprogs/environment_skinned.pixel b/base/renderprogs/environment_skinned.pixel new file mode 100644 index 00000000..589ffed9 --- /dev/null +++ b/base/renderprogs/environment_skinned.pixel @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform samplerCUBE samp0 : register(s0); // texture 0 is the cube map + +struct PS_IN { + float4 position : VPOS; + float3 texcoord0 : TEXCOORD0_centroid; + float3 texcoord1 : TEXCOORD1_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + float3 globalNormal = normalize( fragment.texcoord1 ); + float3 globalEye = normalize( fragment.texcoord0 ); + + float3 reflectionVector = _float3( dot3( globalEye, globalNormal ) ); + reflectionVector *= globalNormal; + reflectionVector = ( reflectionVector * 2.0f ) - globalEye; + + float4 envMap = texCUBE( samp0, reflectionVector ); + + result.color = float4( envMap.xyz, 1.0f ) * fragment.color; +} diff --git a/base/renderprogs/environment_skinned.vertex b/base/renderprogs/environment_skinned.vertex new file mode 100644 index 00000000..754b7ce3 --- /dev/null +++ b/base/renderprogs/environment_skinned.vertex @@ -0,0 +1,105 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float3 texcoord1 : TEXCOORD1; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + float4 vNormal = vertex.normal * 2.0 - 1.0; + + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float3 vNormalSkinned; + vNormalSkinned.x = dot3( matX, vNormal ); + vNormalSkinned.y = dot3( matY, vNormal ); + vNormalSkinned.z = dot3( matZ, vNormal ); + vNormalSkinned = normalize( vNormalSkinned ); + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + float4 toEye = rpLocalViewOrigin - modelPosition; + + result.texcoord0 = toEye.xyz; + result.texcoord1 = vNormalSkinned.xyz; + + result.color = rpColor; +} diff --git a/base/renderprogs/fog.pixel b/base/renderprogs/fog.pixel new file mode 100644 index 00000000..9782b322 --- /dev/null +++ b/base/renderprogs/fog.pixel @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +uniform sampler2D samp1 : register(s1); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float2 texcoord1 : TEXCOORD1_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + result.color = tex2D( samp0, fragment.texcoord0 ) * tex2D( samp1, fragment.texcoord1 ) * rpColor; +} + diff --git a/base/renderprogs/fog.vertex b/base/renderprogs/fog.vertex new file mode 100644 index 00000000..b0f341d7 --- /dev/null +++ b/base/renderprogs/fog.vertex @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0.x = dot4( vertex.position, rpTexGen0S ); + result.texcoord0.y = dot4( vertex.position, rpTexGen0T ); + + result.texcoord1.x = dot4( vertex.position, rpTexGen1S ); + result.texcoord1.y = dot4( vertex.position, rpTexGen1T ); +} diff --git a/base/renderprogs/fog_skinned.pixel b/base/renderprogs/fog_skinned.pixel new file mode 100644 index 00000000..9782b322 --- /dev/null +++ b/base/renderprogs/fog_skinned.pixel @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +uniform sampler2D samp1 : register(s1); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float2 texcoord1 : TEXCOORD1_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + result.color = tex2D( samp0, fragment.texcoord0 ) * tex2D( samp1, fragment.texcoord1 ) * rpColor; +} + diff --git a/base/renderprogs/fog_skinned.vertex b/base/renderprogs/fog_skinned.vertex new file mode 100644 index 00000000..56dbb7aa --- /dev/null +++ b/base/renderprogs/fog_skinned.vertex @@ -0,0 +1,96 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + // end of skinning + + // start of fog portion + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + result.texcoord0.x = dot4( modelPosition, rpTexGen0S ); + result.texcoord0.y = dot4( modelPosition, rpTexGen0T ); + + result.texcoord1.x = dot4( modelPosition, rpTexGen1S ); + result.texcoord1.y = dot4( modelPosition, rpTexGen1T ); +} diff --git a/base/renderprogs/fxaa.pixel b/base/renderprogs/fxaa.pixel new file mode 100644 index 00000000..fd4d866d --- /dev/null +++ b/base/renderprogs/fxaa.pixel @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" +#define FXAA_GREEN_AS_LUMA 1 +#define FXAA_EARLY_EXIT 0 +#include "Fxaa3_11.h" + +uniform sampler2D samp0 : register(s0); +uniform sampler2D samp1 : register(s1); // exponent bias -1 +uniform sampler2D samp2 : register(s2); // exponent bias -2 + +uniform float4 rpUser0 : register( c128 ); +uniform float4 rpUser1 : register( c129 ); +uniform float4 rpUser2 : register( c130 ); +uniform float4 rpUser3 : register( c131 ); +uniform float4 rpUser4 : register( c132 ); +uniform float4 rpUser5 : register( c133 ); +uniform float4 rpUser6 : register( c134 ); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + const float4 FXAAQualityRCPFrame = rpUser0; + const float4 FXAAConsoleRcpFrameOpt = rpUser1; + const float4 FXAAConsoleRcpFrameOpt2 = rpUser2; + const float4 FXAAConsole360RcpFrameOpt2 = rpUser3; + const float4 FXAAQualityParms = rpUser4; + const float4 FXAAConsoleEdgeParms = rpUser5; + const float4 FXAAConsole360ConstDir = rpUser6; + + // Inputs - see more info in fxaa3_11.hfile + FxaaFloat2 fxaaPos = fragment.texcoord0; + FxaaFloat4 fxaaConsolePos; + + float2 halfPixelOffset = float2( 0.5, 0.5 ) * FXAAQualityRCPFrame.xy; + fxaaConsolePos.xy = fxaaPos.xy - ( halfPixelOffset ); + fxaaConsolePos.zw = fxaaPos.xy + ( halfPixelOffset ); + FxaaFloat2 fxaaQualityRcpFrame = FXAAQualityRCPFrame.xy; + FxaaFloat4 fxaaConsoleRcpFrameOpt = FXAAConsoleRcpFrameOpt; + FxaaFloat4 fxaaConsoleRcpFrameOpt2 = FXAAConsoleRcpFrameOpt2; + FxaaFloat4 fxaaConsole360RcpFrameOpt2 = FXAAConsole360RcpFrameOpt2; + + // Quality parms + FxaaFloat fxaaQualitySubpix = FXAAQualityParms.x; + FxaaFloat fxaaQualityEdgeThreshold = FXAAQualityParms.y; + FxaaFloat fxaaQualityEdgeThresholdMin = FXAAQualityParms.z; + + // Console specific Parms + FxaaFloat fxaaConsoleEdgeSharpness = FXAAConsoleEdgeParms.x; + FxaaFloat fxaaConsoleEdgeThreshold = FXAAConsoleEdgeParms.y; + FxaaFloat fxaaConsoleEdgeThresholdMin = FXAAConsoleEdgeParms.z; + + // 360 specific parms these have to come from a constant register so that the compiler + // does not unoptimize the shader + FxaaFloat4 fxaaConsole360ConstDir = FXAAConsole360ConstDir; + + + float4 colorSample = FxaaPixelShader( fxaaPos, + fxaaConsolePos, + samp0, + samp1, + samp2, + fxaaQualityRcpFrame, + fxaaConsoleRcpFrameOpt, + fxaaConsoleRcpFrameOpt2, + fxaaConsole360RcpFrameOpt2, + fxaaQualitySubpix, + fxaaQualityEdgeThreshold, + fxaaQualityEdgeThresholdMin, + fxaaConsoleEdgeSharpness, + fxaaConsoleEdgeThreshold, + fxaaConsoleEdgeThresholdMin, + fxaaConsole360ConstDir ); + + result.color = colorSample; +} \ No newline at end of file diff --git a/base/renderprogs/fxaa.vertex b/base/renderprogs/fxaa.vertex new file mode 100644 index 00000000..9a2b7db4 --- /dev/null +++ b/base/renderprogs/fxaa.vertex @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position = vertex.position; + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/global.inc b/base/renderprogs/global.inc new file mode 100644 index 00000000..74f33ffb --- /dev/null +++ b/base/renderprogs/global.inc @@ -0,0 +1,176 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +uniform float4 rpScreenCorrectionFactor : register(c0); +uniform float4 rpWindowCoord : register(c1); +uniform float4 rpDiffuseModifier : register(c2); +uniform float4 rpSpecularModifier : register(c3); + +uniform float4 rpLocalLightOrigin : register(c4); +uniform float4 rpLocalViewOrigin : register(c5); + +uniform float4 rpLightProjectionS : register(c6); +uniform float4 rpLightProjectionT : register(c7); +uniform float4 rpLightProjectionQ : register(c8); +uniform float4 rpLightFalloffS : register(c9); + +uniform float4 rpBumpMatrixS : register(c10); +uniform float4 rpBumpMatrixT : register(c11); + +uniform float4 rpDiffuseMatrixS : register(c12); +uniform float4 rpDiffuseMatrixT : register(c13); + +uniform float4 rpSpecularMatrixS : register(c14); +uniform float4 rpSpecularMatrixT : register(c15); + +uniform float4 rpVertexColorModulate : register(c16); +uniform float4 rpVertexColorAdd : register(c17); + +uniform float4 rpColor : register(c18); +uniform float4 rpViewOrigin : register(c19); +uniform float4 rpGlobalEyePos : register(c20); + +uniform float4 rpMVPmatrixX : register(c21); +uniform float4 rpMVPmatrixY : register(c22); +uniform float4 rpMVPmatrixZ : register(c23); +uniform float4 rpMVPmatrixW : register(c24); + +uniform float4 rpModelMatrixX : register(c25); +uniform float4 rpModelMatrixY : register(c26); +uniform float4 rpModelMatrixZ : register(c27); +uniform float4 rpModelMatrixW : register(c28); + +uniform float4 rpProjectionMatrixX : register(c29); +uniform float4 rpProjectionMatrixY : register(c30); +uniform float4 rpProjectionMatrixZ : register(c31); +uniform float4 rpProjectionMatrixW : register(c32); + +uniform float4 rpModelViewMatrixX : register(c33); +uniform float4 rpModelViewMatrixY : register(c34); +uniform float4 rpModelViewMatrixZ : register(c35); +uniform float4 rpModelViewMatrixW : register(c36); + +uniform float4 rpTextureMatrixS : register(c37); +uniform float4 rpTextureMatrixT : register(c38); + +uniform float4 rpTexGen0S : register(c39); +uniform float4 rpTexGen0T : register(c40); +uniform float4 rpTexGen0Q : register(c41); +uniform float4 rpTexGen0Enabled : register(c42); + +uniform float4 rpTexGen1S : register(c43); +uniform float4 rpTexGen1T : register(c44); +uniform float4 rpTexGen1Q : register(c45); +uniform float4 rpTexGen1Enabled : register(c46); + +uniform float4 rpWobbleSkyX : register(c47); +uniform float4 rpWobbleSkyY : register(c48); +uniform float4 rpWobbleSkyZ : register(c49); + +uniform float4 rpOverbright : register(c50); +uniform float4 rpEnableSkinning : register(c51); +uniform float4 rpAlphaTest : register(c52); + +static float dot2( float2 a, float2 b ) { return dot( a, b ); } +static float dot3( float3 a, float3 b ) { return dot( a, b ); } +static float dot3( float3 a, float4 b ) { return dot( a, b.xyz ); } +static float dot3( float4 a, float3 b ) { return dot( a.xyz, b ); } +static float dot3( float4 a, float4 b ) { return dot( a.xyz, b.xyz ); } +static float dot4( float4 a, float4 b ) { return dot( a, b ); } +static float dot4( float2 a, float4 b ) { return dot( float4( a, 0, 1 ), b ); } + +// ---------------------- +// YCoCg Color Conversion +// ---------------------- +static const half4 matrixRGB1toCoCg1YX = half4( 0.50, 0.0, -0.50, 0.50196078 ); // Co +static const half4 matrixRGB1toCoCg1YY = half4( -0.25, 0.5, -0.25, 0.50196078 ); // Cg +static const half4 matrixRGB1toCoCg1YZ = half4( 0.0, 0.0, 0.0, 1.0 ); // 1.0 +static const half4 matrixRGB1toCoCg1YW = half4( 0.25, 0.5, 0.25, 0.0 ); // Y + +static const half4 matrixCoCg1YtoRGB1X = half4( 1.0, -1.0, 0.0, 1.0 ); +static const half4 matrixCoCg1YtoRGB1Y = half4( 0.0, 1.0, -0.50196078, 1.0 ); // -0.5 * 256.0 / 255.0 +static const half4 matrixCoCg1YtoRGB1Z = half4( -1.0, -1.0, 1.00392156, 1.0 ); // +1.0 * 256.0 / 255.0 + +static half3 ConvertYCoCgToRGB( half4 YCoCg ) { + half3 rgbColor; + + YCoCg.z = ( YCoCg.z * 31.875 ) + 1.0; //z = z * 255.0/8.0 + 1.0 + YCoCg.z = 1.0 / YCoCg.z; + YCoCg.xy *= YCoCg.z; + rgbColor.x = dot4( YCoCg, matrixCoCg1YtoRGB1X ); + rgbColor.y = dot4( YCoCg, matrixCoCg1YtoRGB1Y ); + rgbColor.z = dot4( YCoCg, matrixCoCg1YtoRGB1Z ); + return rgbColor; +} + +static float2 CenterScale( float2 inTC, float2 centerScale ) { + float scaleX = centerScale.x; + float scaleY = centerScale.y; + float4 tc0 = float4( scaleX, 0, 0, 0.5 - ( 0.5f * scaleX ) ); + float4 tc1 = float4( 0, scaleY, 0, 0.5 - ( 0.5f * scaleY ) ); + + float2 finalTC; + finalTC.x = dot4( inTC, tc0 ); + finalTC.y = dot4( inTC, tc1 ); + return finalTC; +} + +static float2 Rotate2D( float2 inTC, float2 cs ) { + float sinValue = cs.y; + float cosValue = cs.x; + + float4 tc0 = float4( cosValue, -sinValue, 0, ( -0.5f * cosValue ) + ( 0.5f * sinValue ) + 0.5f ); + float4 tc1 = float4( sinValue, cosValue, 0, ( -0.5f * sinValue ) + ( -0.5f * cosValue ) + 0.5f ); + + float2 finalTC; + finalTC.x = dot4( inTC, tc0 ); + finalTC.y = dot4( inTC, tc1 ); + return finalTC; +} + +// better noise function available at https://github.com/ashima/webgl-noise +float rand( float2 co ) { + return frac( sin( dot( co.xy, float2( 12.9898, 78.233 ) ) ) * 43758.5453 ); +} + + +#define _half2( x ) half2( x ) +#define _half3( x ) half3( x ) +#define _half4( x ) half4( x ) +#define _float2( x ) float2( x ) +#define _float3( x ) float3( x ) +#define _float4( x ) float4( x ) + +#define VPOS WPOS +static float4 idtex2Dproj( sampler2D samp, float4 texCoords ) { return tex2Dproj( samp, texCoords.xyw ); } +static float4 swizzleColor( float4 c ) { return c; } +static float2 vposToScreenPosTexCoord( float2 vpos ) { return vpos.xy * rpWindowCoord.xy; } + + +#define BRANCH +#define IFANY diff --git a/base/renderprogs/gui.pixel b/base/renderprogs/gui.pixel new file mode 100644 index 00000000..22fa81a8 --- /dev/null +++ b/base/renderprogs/gui.pixel @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float4 texcoord1 : TEXCOORD1_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float4 color = ( tex2D( samp0, fragment.texcoord0 ) * fragment.color ) + fragment.texcoord1; + result.color.xyz = color.xyz * color.w; + result.color.w = color.w; +} \ No newline at end of file diff --git a/base/renderprogs/gui.vertex b/base/renderprogs/gui.vertex new file mode 100644 index 00000000..fbe8a535 --- /dev/null +++ b/base/renderprogs/gui.vertex @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0.xy = vertex.texcoord.xy; + result.texcoord1 = ( swizzleColor( vertex.color2 ) * 2 ) - 1; + result.color = swizzleColor( vertex.color ); +} diff --git a/base/renderprogs/heatHazeWithMask.pixel b/base/renderprogs/heatHazeWithMask.pixel new file mode 100644 index 00000000..87b1e9aa --- /dev/null +++ b/base/renderprogs/heatHazeWithMask.pixel @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 0 is _current Render +uniform sampler2D samp1 : register(s1); // texture 1 is the per-surface bump map +uniform sampler2D samp2 : register(s2); // texture 2 is the mask texture + +struct PS_IN { + float4 position : VPOS; + float4 texcoord0 : TEXCOORD0_centroid; + float4 texcoord1 : TEXCOORD1_centroid; + float4 texcoord2 : TEXCOORD2_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + // load the distortion map + float4 mask = tex2D( samp2, fragment.texcoord0.xy ); + + // kill the pixel if the distortion wound up being very small + mask.xy -= 0.01f; + clip( mask ); + + // load the filtered normal map and convert to -1 to 1 range + float4 bumpMap = ( tex2D( samp1, fragment.texcoord1.xy ) * 2.0f ) - 1.0f; + float2 localNormal = bumpMap.wy; + localNormal *= mask.xy; + + // calculate the screen texcoord in the 0.0 to 1.0 range + float2 screenTexCoord = vposToScreenPosTexCoord( fragment.position.xy ); + screenTexCoord += ( localNormal * fragment.texcoord2.xy ); + screenTexCoord = saturate( screenTexCoord ); + + result.color = tex2D( samp0, screenTexCoord ); +} \ No newline at end of file diff --git a/base/renderprogs/heatHazeWithMask.vertex b/base/renderprogs/heatHazeWithMask.vertex new file mode 100644 index 00000000..31e7fd9f --- /dev/null +++ b/base/renderprogs/heatHazeWithMask.vertex @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +uniform float4 rpUser0 : register(c128); // rpScroll +uniform float4 rpUser1 : register(c129); // rpDeformMagnitude + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + #include "skinning.inc" + + // texture 0 takes the texture coordinates unmodified + result.texcoord0 = float4( vertex.texcoord.xy, 0, 0 ); + + // texture 1 takes the texture coordinates and adds a scroll + const float4 textureScroll = rpUser0; + result.texcoord1 = float4( vertex.texcoord.xy, 0, 0 ) + textureScroll; + + // texture 2 takes the deform magnitude and scales it by the projection distance + float4 vec = float4( 0, 1, 0, 1 ); + vec.z = dot4( modelPosition, rpModelViewMatrixZ ); + + // magicProjectionAdjust is a magic scalar that scales the projection since we changed from + // using the X axis to the Y axis to calculate R1. It is an approximation to closely match + // what the original game did + const float magicProjectionAdjust = 0.43f; + float x = dot4 ( vec, rpProjectionMatrixY ) * magicProjectionAdjust; + float w = dot4 ( vec, rpProjectionMatrixW ); + + // don't let the recip get near zero for polygons that cross the view plane + w = max( w, 1.0 ); + x /= w; + //x = x * ( 1.0f / ( w + 0.00001f ) ); + + // clamp the distance so the the deformations don't get too wacky near the view + x = min( x, 0.02 ); + + const float4 deformMagnitude = rpUser1; + result.texcoord2 = x * deformMagnitude; +} \ No newline at end of file diff --git a/base/renderprogs/heatHazeWithMaskAndVertex.pixel b/base/renderprogs/heatHazeWithMaskAndVertex.pixel new file mode 100644 index 00000000..cbe9a0f2 --- /dev/null +++ b/base/renderprogs/heatHazeWithMaskAndVertex.pixel @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 0 is _current Render +uniform sampler2D samp1 : register(s1); // texture 1 is the per-surface bump map +uniform sampler2D samp2 : register(s2); // texture 2 is the mask texture + +struct PS_IN { + float4 position : VPOS; + float4 texcoord0 : TEXCOORD0_centroid; + float4 texcoord1 : TEXCOORD1_centroid; + float4 texcoord2 : TEXCOORD2_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + // load the distortion map + float4 mask = tex2D( samp2, fragment.texcoord0.xy ); + + // kill the pixel if the distortion wound up being very small + mask.xy *= fragment.color.xy; + mask.xy -= 0.01f; + clip( mask ); + + // load the filtered normal map and convert to -1 to 1 range + float4 bumpMap = ( tex2D( samp1, fragment.texcoord1.xy ) * 2.0f ) - 1.0f; + float2 localNormal = bumpMap.wy; + localNormal *= mask.xy; + + // calculate the screen texcoord in the 0.0 to 1.0 range + float2 screenTexCoord = vposToScreenPosTexCoord( fragment.position.xy ); + screenTexCoord += ( localNormal * fragment.texcoord2.xy ); + screenTexCoord = saturate( screenTexCoord ); + + result.color = tex2D( samp0, screenTexCoord ); +} \ No newline at end of file diff --git a/base/renderprogs/heatHazeWithMaskAndVertex.vertex b/base/renderprogs/heatHazeWithMaskAndVertex.vertex new file mode 100644 index 00000000..e5a6c2f6 --- /dev/null +++ b/base/renderprogs/heatHazeWithMaskAndVertex.vertex @@ -0,0 +1,86 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +uniform float4 rpUser0 : register(c128); // rpScroll +uniform float4 rpUser1 : register(c129); // rpDeformMagnitude + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + #include "skinning.inc" + + // texture 0 takes the texture coordinates unmodified + result.texcoord0 = float4( vertex.texcoord, 0 , 0 ); + + // texture 1 takes the texture coordinates and adds a scroll + const float4 textureScroll = rpUser0; + result.texcoord1 = float4( vertex.texcoord, 0, 0 ) + textureScroll; + + // texture 2 takes the deform magnitude and scales it by the projection distance + float4 vec = float4( 0, 1, 0, 1 ); + vec.z = dot4( modelPosition, rpModelViewMatrixZ ); + + // magicProjectionAdjust is a magic scalar that scales the projection since we changed from + // using the X axis to the Y axis to calculate x. It is an approximation to closely match + // what the original game did + const float magicProjectionAdjust = 0.43f; + float x = dot4 ( vec, rpProjectionMatrixY ) * magicProjectionAdjust; + float w = dot4( vec, rpProjectionMatrixW ); + + // don't let the recip get near zero for polygons that cross the view plane + w = max( w, 1.0f ); + x /= w; + //x = x * ( 1.0f / w ); + + // clamp the distance so the the deformations don't get too wacky near the view + x = min( x, 0.02f ); + + const float4 deformMagnitude = rpUser1; + result.texcoord2 = x * deformMagnitude; + result.color = swizzleColor( vertex.color ); +} \ No newline at end of file diff --git a/base/renderprogs/heathaze.pixel b/base/renderprogs/heathaze.pixel new file mode 100644 index 00000000..c349be37 --- /dev/null +++ b/base/renderprogs/heathaze.pixel @@ -0,0 +1,57 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 0 is _current Render +uniform sampler2D samp1 : register(s1); // texture 1 is the per-surface bump map + +struct PS_IN { + float4 position : VPOS; + float4 texcoord0 : TEXCOORD0_centroid; + float4 texcoord1 : TEXCOORD1_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + // load the filtered normal map and convert to -1 to 1 range + float4 bumpMap = ( tex2D( samp1, fragment.texcoord0.xy ) * 2.0f ) - 1.0f; + float2 localNormal = bumpMap.wy; + + // calculate the screen texcoord in the 0.0 to 1.0 range + float2 screenTexCoord = vposToScreenPosTexCoord( fragment.position.xy ); + screenTexCoord += ( localNormal * fragment.texcoord1.xy ); + screenTexCoord = saturate( screenTexCoord ); + + // load the screen render + result.color = tex2D( samp0, screenTexCoord.xy ); +} \ No newline at end of file diff --git a/base/renderprogs/heathaze.vertex b/base/renderprogs/heathaze.vertex new file mode 100644 index 00000000..b194add2 --- /dev/null +++ b/base/renderprogs/heathaze.vertex @@ -0,0 +1,82 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +// User Renderparms start at 128 as per renderprogs.h + +uniform float4 rpUser0 : register(c128); // rpScroll +uniform float4 rpUser1 : register(c129); // rpDeformMagnitude + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + #include "skinning.inc" + + //texture 0 takes the texture coordinates and adds a scroll + const float4 textureScroll = rpUser0; + result.texcoord0 = float4( vertex.texcoord.xy, 0, 0 ) + textureScroll; + + //texture 1 takes the deform magnitude and scales it by the projection distance + float4 vec = float4( 0, 1, 0, 1 ); + vec.z = dot4( modelPosition, rpModelViewMatrixZ ); // this is the modelview matrix + + // magicProjectionAdjust is a magic scalar that scales the projection since we changed from + // using the X axis to the Y axis to calculate x. It is an approximation to closely match + // what the original game did + const float magicProjectionAdjust = 0.43f; + float x = dot4( vec, rpProjectionMatrixY ) * magicProjectionAdjust; + float w = dot4( vec, rpProjectionMatrixW ); + + //don't let the recip get near zero for polygons that cross the view plane + w = max( w, 1.0 ); + x /= w; + //x = x * ( 1.0f / w ); + + // clamp the distance so the the deformations don't get too wacky near the view + x = min( x, 0.02 ); + + const float4 deformMagnitude = rpUser1; + result.texcoord1 = x * deformMagnitude; +} \ No newline at end of file diff --git a/base/renderprogs/interaction.pixel b/base/renderprogs/interaction.pixel new file mode 100644 index 00000000..263c4bce --- /dev/null +++ b/base/renderprogs/interaction.pixel @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 1 is the per-surface bump map +uniform sampler2D samp1 : register(s1); // texture 2 is the light falloff texture +uniform sampler2D samp2 : register(s2); // texture 3 is the light projection texture +uniform sampler2D samp3 : register(s3); // texture 4 is the per-surface diffuse map +uniform sampler2D samp4 : register(s4); // texture 5 is the per-surface specular map + +struct PS_IN { + half4 position : VPOS; + half4 texcoord0 : TEXCOORD0_centroid; + half4 texcoord1 : TEXCOORD1_centroid; + half4 texcoord2 : TEXCOORD2_centroid; + half4 texcoord3 : TEXCOORD3_centroid; + half4 texcoord4 : TEXCOORD4_centroid; + half4 texcoord5 : TEXCOORD5_centroid; + half4 texcoord6 : TEXCOORD6_centroid; + half4 color : COLOR0; +}; + +struct PS_OUT { + half4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + half4 bumpMap = tex2D( samp0, fragment.texcoord1.xy ); + half4 lightFalloff = idtex2Dproj( samp1, fragment.texcoord2 ); + half4 lightProj = idtex2Dproj( samp2, fragment.texcoord3 ); + half4 YCoCG = tex2D( samp3, fragment.texcoord4.xy ); + half4 specMap = tex2D( samp4, fragment.texcoord5.xy ); + + half3 lightVector = normalize( fragment.texcoord0.xyz ); + half3 diffuseMap = ConvertYCoCgToRGB( YCoCG ); + + half3 localNormal; + localNormal.xy = bumpMap.wy - 0.5; + localNormal.z = sqrt( abs( dot( localNormal.xy, localNormal.xy ) - 0.25 ) ); + localNormal = normalize( localNormal ); + + const half specularPower = 10.0f; + half hDotN = dot3( normalize( fragment.texcoord6.xyz ), localNormal ); + half3 specularContribution = _half3( pow( hDotN, specularPower ) ); + + half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz; + half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz; + half3 lightColor = dot3( lightVector, localNormal ) * lightProj.xyz * lightFalloff.xyz; + + result.color.xyz = ( diffuseColor + specularColor ) * lightColor * fragment.color.xyz; + result.color.w = 1.0; +} diff --git a/base/renderprogs/interaction.vertex b/base/renderprogs/interaction.vertex new file mode 100644 index 00000000..0b6e4066 --- /dev/null +++ b/base/renderprogs/interaction.vertex @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; + float4 texcoord3 : TEXCOORD3; + float4 texcoord4 : TEXCOORD4; + float4 texcoord5 : TEXCOORD5; + float4 texcoord6 : TEXCOORD6; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + float3 vNormal = vertex.normal.xyz * 2.0 - 1.0; + float4 vTangent = vertex.tangent * 2.0 - 1.0; + float3 vBinormal = cross( vNormal.xyz, vTangent.xyz ) * vTangent.w; + + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + float4 defaultTexCoord = float4( 0.0f, 0.5f, 0.0f, 1.0f ); + + //calculate vector to light in R0 + float4 toLight = rpLocalLightOrigin - vertex.position; + + //result.texcoord0 + result.texcoord0.x = dot3( vTangent.xyz, toLight ); + result.texcoord0.y = dot3( vBinormal, toLight ); + result.texcoord0.z = dot3( vNormal, toLight ); + result.texcoord0.w = 1.0f; + + //textures 1 takes the base coordinates by the texture matrix + result.texcoord1 = defaultTexCoord; + result.texcoord1.x = dot4( vertex.texcoord.xy, rpBumpMatrixS ); + result.texcoord1.y = dot4( vertex.texcoord.xy, rpBumpMatrixT ); + + //# texture 2 has one texgen + result.texcoord2 = defaultTexCoord; + result.texcoord2.x = dot4( vertex.position, rpLightFalloffS ); + + //# texture 3 has three texgens + result.texcoord3.x = dot4( vertex.position, rpLightProjectionS ); + result.texcoord3.y = dot4( vertex.position, rpLightProjectionT ); + result.texcoord3.z = 0.0f; + result.texcoord3.w = dot4( vertex.position, rpLightProjectionQ ); + + //# textures 4 takes the base coordinates by the texture matrix + result.texcoord4 = defaultTexCoord; + result.texcoord4.x = dot4( vertex.texcoord.xy, rpDiffuseMatrixS ); + result.texcoord4.y = dot4( vertex.texcoord.xy, rpDiffuseMatrixT ); + + //# textures 5 takes the base coordinates by the texture matrix + result.texcoord5 = defaultTexCoord; + result.texcoord5.x = dot4( vertex.texcoord.xy, rpSpecularMatrixS ); + result.texcoord5.y = dot4( vertex.texcoord.xy, rpSpecularMatrixT ); + + //# texture 6's texcoords will be the halfangle in texture space + + //# calculate normalized vector to light in R0 + toLight = normalize( toLight ); + + //# calculate normalized vector to viewer in R1 + float4 toView = normalize( rpLocalViewOrigin - vertex.position ); + + //# add together to become the half angle vector in object space (non-normalized) + float4 halfAngleVector = toLight + toView; + + //# put into texture space + result.texcoord6.x = dot3( vTangent.xyz, halfAngleVector ); + result.texcoord6.y = dot3( vBinormal, halfAngleVector ); + result.texcoord6.z = dot3( vNormal, halfAngleVector ); + result.texcoord6.w = 1.0f; + + //# generate the vertex color, which can be 1.0, color, or 1.0 - color + //# for 1.0 : env[16] = 0, env[17] = 1 + //# for color : env[16] = 1, env[17] = 0 + //# for 1.0-color : env[16] = -1, env[17] = 1 + result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +} \ No newline at end of file diff --git a/base/renderprogs/interactionAmbient.pixel b/base/renderprogs/interactionAmbient.pixel new file mode 100644 index 00000000..e7e05972 --- /dev/null +++ b/base/renderprogs/interactionAmbient.pixel @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 1 is the per-surface bump map +uniform sampler2D samp1 : register(s1); // texture 2 is the light falloff texture +uniform sampler2D samp2 : register(s2); // texture 3 is the light projection texture +uniform sampler2D samp3 : register(s3); // texture 4 is the per-surface diffuse map +uniform sampler2D samp4 : register(s4); // texture 5 is the per-surface specular map + +struct PS_IN { + half4 position : VPOS; + half4 texcoord1 : TEXCOORD1_centroid; + half4 texcoord2 : TEXCOORD2_centroid; + half4 texcoord3 : TEXCOORD3_centroid; + half4 texcoord4 : TEXCOORD4_centroid; + half4 texcoord5 : TEXCOORD5_centroid; + half4 texcoord6 : TEXCOORD6_centroid; + half4 color : COLOR0; +}; + +struct PS_OUT { + half4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + half4 bumpMap = tex2D( samp0, fragment.texcoord1.xy ); + half4 lightFalloff = idtex2Dproj( samp1, fragment.texcoord2 ); + half4 lightProj = idtex2Dproj( samp2, fragment.texcoord3 ); + half4 YCoCG = tex2D( samp3, fragment.texcoord4.xy ); + half4 specMap = tex2D( samp4, fragment.texcoord5.xy ); + + const half3 ambientLightVector = half3( 0.5f, 9.5f - 0.385f, 0.8925f ); + half3 lightVector = normalize( ambientLightVector ); + half3 diffuseMap = ConvertYCoCgToRGB( YCoCG ); + + half3 localNormal; + localNormal.xy = bumpMap.wy - 0.5; + localNormal.z = sqrt( abs( dot( localNormal.xy, localNormal.xy ) - 0.25 ) ); + localNormal = normalize( localNormal ); + + const half specularPower = 10.0f; + half hDotN = dot3( normalize( fragment.texcoord6.xyz ), localNormal ); + half3 specularContribution = _half3( pow( hDotN, specularPower ) ); + + half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz; + half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz; + half3 lightColor = dot3( lightVector, localNormal ) * lightProj.xyz * lightFalloff.xyz; + + result.color.xyz = ( diffuseColor + specularColor ) * lightColor * fragment.color.xyz; + result.color.w = 1.0; +} diff --git a/base/renderprogs/interactionAmbient.vertex b/base/renderprogs/interactionAmbient.vertex new file mode 100644 index 00000000..a4e5f416 --- /dev/null +++ b/base/renderprogs/interactionAmbient.vertex @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; + float4 texcoord3 : TEXCOORD3; + float4 texcoord4 : TEXCOORD4; + float4 texcoord5 : TEXCOORD5; + float4 texcoord6 : TEXCOORD6; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + float4 normal = vertex.normal * 2.0 - 1.0; + float4 tangent = vertex.tangent * 2.0 - 1.0; + float3 binormal = cross( normal.xyz, tangent.xyz ) * tangent.w; + + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + float4 defaultTexCoord = float4( 0.0f, 0.5f, 0.0f, 1.0f ); + + //calculate vector to light in R0 + float4 toLight = rpLocalLightOrigin - vertex.position; + + //textures 1 takes the base coordinates by the texture matrix + result.texcoord1 = defaultTexCoord; + result.texcoord1.x = dot4( vertex.texcoord.xy, rpBumpMatrixS ); + result.texcoord1.y = dot4( vertex.texcoord.xy, rpBumpMatrixT ); + + //# texture 2 has one texgen + result.texcoord2 = defaultTexCoord; + result.texcoord2.x = dot4( vertex.position, rpLightFalloffS ); + + //# texture 3 has three texgens + result.texcoord3.x = dot4( vertex.position, rpLightProjectionS ); + result.texcoord3.y = dot4( vertex.position, rpLightProjectionT ); + result.texcoord3.z = 0.0f; + result.texcoord3.w = dot4( vertex.position, rpLightProjectionQ ); + + //# textures 4 takes the base coordinates by the texture matrix + result.texcoord4 = defaultTexCoord; + result.texcoord4.x = dot4( vertex.texcoord.xy, rpDiffuseMatrixS ); + result.texcoord4.y = dot4( vertex.texcoord.xy, rpDiffuseMatrixT ); + + //# textures 5 takes the base coordinates by the texture matrix + result.texcoord5 = defaultTexCoord; + result.texcoord5.x = dot4( vertex.texcoord.xy, rpSpecularMatrixS ); + result.texcoord5.y = dot4( vertex.texcoord.xy, rpSpecularMatrixT ); + + //# texture 6's texcoords will be the halfangle in texture space + + //# calculate normalized vector to light in R0 + toLight = normalize( toLight ); + + //# calculate normalized vector to viewer in R1 + float4 toView = normalize( rpLocalViewOrigin - vertex.position ); + + //# add together to become the half angle vector in object space (non-normalized) + float4 halfAngleVector = toLight + toView; + + //# put into texture space + result.texcoord6.x = dot3( tangent, halfAngleVector ); + result.texcoord6.y = dot3( binormal, halfAngleVector ); + result.texcoord6.z = dot3( normal, halfAngleVector ); + result.texcoord6.w = 1.0f; + + //# generate the vertex color, which can be 1.0, color, or 1.0 - color + //# for 1.0 : env[16] = 0, env[17] = 1 + //# for color : env[16] = 1, env[17] = 0 + //# for 1.0-color : env[16] = -1, env[17] = 1 + result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +} \ No newline at end of file diff --git a/base/renderprogs/interactionAmbient_skinned.pixel b/base/renderprogs/interactionAmbient_skinned.pixel new file mode 100644 index 00000000..e7e05972 --- /dev/null +++ b/base/renderprogs/interactionAmbient_skinned.pixel @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 1 is the per-surface bump map +uniform sampler2D samp1 : register(s1); // texture 2 is the light falloff texture +uniform sampler2D samp2 : register(s2); // texture 3 is the light projection texture +uniform sampler2D samp3 : register(s3); // texture 4 is the per-surface diffuse map +uniform sampler2D samp4 : register(s4); // texture 5 is the per-surface specular map + +struct PS_IN { + half4 position : VPOS; + half4 texcoord1 : TEXCOORD1_centroid; + half4 texcoord2 : TEXCOORD2_centroid; + half4 texcoord3 : TEXCOORD3_centroid; + half4 texcoord4 : TEXCOORD4_centroid; + half4 texcoord5 : TEXCOORD5_centroid; + half4 texcoord6 : TEXCOORD6_centroid; + half4 color : COLOR0; +}; + +struct PS_OUT { + half4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + half4 bumpMap = tex2D( samp0, fragment.texcoord1.xy ); + half4 lightFalloff = idtex2Dproj( samp1, fragment.texcoord2 ); + half4 lightProj = idtex2Dproj( samp2, fragment.texcoord3 ); + half4 YCoCG = tex2D( samp3, fragment.texcoord4.xy ); + half4 specMap = tex2D( samp4, fragment.texcoord5.xy ); + + const half3 ambientLightVector = half3( 0.5f, 9.5f - 0.385f, 0.8925f ); + half3 lightVector = normalize( ambientLightVector ); + half3 diffuseMap = ConvertYCoCgToRGB( YCoCG ); + + half3 localNormal; + localNormal.xy = bumpMap.wy - 0.5; + localNormal.z = sqrt( abs( dot( localNormal.xy, localNormal.xy ) - 0.25 ) ); + localNormal = normalize( localNormal ); + + const half specularPower = 10.0f; + half hDotN = dot3( normalize( fragment.texcoord6.xyz ), localNormal ); + half3 specularContribution = _half3( pow( hDotN, specularPower ) ); + + half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz; + half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz; + half3 lightColor = dot3( lightVector, localNormal ) * lightProj.xyz * lightFalloff.xyz; + + result.color.xyz = ( diffuseColor + specularColor ) * lightColor * fragment.color.xyz; + result.color.w = 1.0; +} diff --git a/base/renderprogs/interactionAmbient_skinned.vertex b/base/renderprogs/interactionAmbient_skinned.vertex new file mode 100644 index 00000000..c062b40b --- /dev/null +++ b/base/renderprogs/interactionAmbient_skinned.vertex @@ -0,0 +1,171 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; + float4 texcoord3 : TEXCOORD3; + float4 texcoord4 : TEXCOORD4; + float4 texcoord5 : TEXCOORD5; + float4 texcoord6 : TEXCOORD6; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + float4 vNormal = vertex.normal * 2.0 - 1.0; + float4 vTangent = vertex.tangent * 2.0 - 1.0; + float3 vBinormal = cross( vNormal.xyz, vTangent.xyz ) * vTangent.w; + + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float3 normal; + normal.x = dot3( matX, vNormal ); + normal.y = dot3( matY, vNormal ); + normal.z = dot3( matZ, vNormal ); + normal = normalize( normal ); + + float3 tangent; + tangent.x = dot3( matX, vTangent ); + tangent.y = dot3( matY, vTangent ); + tangent.z = dot3( matZ, vTangent ); + tangent = normalize( tangent ); + + float3 binormal; + binormal.x = dot3( matX, vBinormal ); + binormal.y = dot3( matY, vBinormal ); + binormal.z = dot3( matZ, vBinormal ); + binormal = normalize( binormal ); + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + float4 defaultTexCoord = float4( 0.0f, 0.5f, 0.0f, 1.0f ); + + //calculate vector to light in R0 + float4 toLight = rpLocalLightOrigin - modelPosition; + + //textures 1 takes the base coordinates by the texture matrix + result.texcoord1 = defaultTexCoord; + result.texcoord1.x = dot4( vertex.texcoord.xy, rpBumpMatrixS ); + result.texcoord1.y = dot4( vertex.texcoord.xy, rpBumpMatrixT ); + + //# texture 2 has one texgen + result.texcoord2 = defaultTexCoord; + result.texcoord2.x = dot4( modelPosition, rpLightFalloffS ); + + //# texture 3 has three texgens + result.texcoord3.x = dot4( modelPosition, rpLightProjectionS ); + result.texcoord3.y = dot4( modelPosition, rpLightProjectionT ); + result.texcoord3.z = 0.0f; + result.texcoord3.w = dot4( modelPosition, rpLightProjectionQ ); + + //# textures 4 takes the base coordinates by the texture matrix + result.texcoord4 = defaultTexCoord; + result.texcoord4.x = dot4( vertex.texcoord.xy, rpDiffuseMatrixS ); + result.texcoord4.y = dot4( vertex.texcoord.xy, rpDiffuseMatrixT ); + + //# textures 5 takes the base coordinates by the texture matrix + result.texcoord5 = defaultTexCoord; + result.texcoord5.x = dot4( vertex.texcoord.xy, rpSpecularMatrixS ); + result.texcoord5.y = dot4( vertex.texcoord.xy, rpSpecularMatrixT ); + + //# texture 6's texcoords will be the halfangle in texture space + + //# calculate normalized vector to light in R0 + toLight = normalize( toLight ); + + //# calculate normalized vector to viewer in R1 + float4 toView = normalize( rpLocalViewOrigin - modelPosition ); + + //# add together to become the half angle vector in object space (non-normalized) + float4 halfAngleVector = toLight + toView; + + //# put into texture space + result.texcoord6.x = dot3( tangent, halfAngleVector ); + result.texcoord6.y = dot3( binormal, halfAngleVector ); + result.texcoord6.z = dot3( normal, halfAngleVector ); + result.texcoord6.w = 1.0f; + + //# generate the vertex color, which can be 1.0, color, or 1.0 - color + //# for 1.0 : env[16] = 0, env[17] = 1 + //# for color : env[16] = 1, env[17] = 0 + //# for 1.0-color : env[16] = -1, env[17] = 1 + result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +} \ No newline at end of file diff --git a/base/renderprogs/interaction_skinned.pixel b/base/renderprogs/interaction_skinned.pixel new file mode 100644 index 00000000..263c4bce --- /dev/null +++ b/base/renderprogs/interaction_skinned.pixel @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // texture 1 is the per-surface bump map +uniform sampler2D samp1 : register(s1); // texture 2 is the light falloff texture +uniform sampler2D samp2 : register(s2); // texture 3 is the light projection texture +uniform sampler2D samp3 : register(s3); // texture 4 is the per-surface diffuse map +uniform sampler2D samp4 : register(s4); // texture 5 is the per-surface specular map + +struct PS_IN { + half4 position : VPOS; + half4 texcoord0 : TEXCOORD0_centroid; + half4 texcoord1 : TEXCOORD1_centroid; + half4 texcoord2 : TEXCOORD2_centroid; + half4 texcoord3 : TEXCOORD3_centroid; + half4 texcoord4 : TEXCOORD4_centroid; + half4 texcoord5 : TEXCOORD5_centroid; + half4 texcoord6 : TEXCOORD6_centroid; + half4 color : COLOR0; +}; + +struct PS_OUT { + half4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + half4 bumpMap = tex2D( samp0, fragment.texcoord1.xy ); + half4 lightFalloff = idtex2Dproj( samp1, fragment.texcoord2 ); + half4 lightProj = idtex2Dproj( samp2, fragment.texcoord3 ); + half4 YCoCG = tex2D( samp3, fragment.texcoord4.xy ); + half4 specMap = tex2D( samp4, fragment.texcoord5.xy ); + + half3 lightVector = normalize( fragment.texcoord0.xyz ); + half3 diffuseMap = ConvertYCoCgToRGB( YCoCG ); + + half3 localNormal; + localNormal.xy = bumpMap.wy - 0.5; + localNormal.z = sqrt( abs( dot( localNormal.xy, localNormal.xy ) - 0.25 ) ); + localNormal = normalize( localNormal ); + + const half specularPower = 10.0f; + half hDotN = dot3( normalize( fragment.texcoord6.xyz ), localNormal ); + half3 specularContribution = _half3( pow( hDotN, specularPower ) ); + + half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz; + half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz; + half3 lightColor = dot3( lightVector, localNormal ) * lightProj.xyz * lightFalloff.xyz; + + result.color.xyz = ( diffuseColor + specularColor ) * lightColor * fragment.color.xyz; + result.color.w = 1.0; +} diff --git a/base/renderprogs/interaction_skinned.vertex b/base/renderprogs/interaction_skinned.vertex new file mode 100644 index 00000000..53ce22aa --- /dev/null +++ b/base/renderprogs/interaction_skinned.vertex @@ -0,0 +1,185 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 texcoord1 : TEXCOORD1; + float4 texcoord2 : TEXCOORD2; + float4 texcoord3 : TEXCOORD3; + float4 texcoord4 : TEXCOORD4; + float4 texcoord5 : TEXCOORD5; + float4 texcoord6 : TEXCOORD6; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + + float4 vNormal = vertex.normal * 2.0 - 1.0; + float4 vTangent = vertex.tangent * 2.0 - 1.0; + float3 vBinormal = cross( vNormal.xyz, vTangent.xyz ) * vTangent.w; + + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float3 normal; + normal.x = dot3( matX, vNormal ); + normal.y = dot3( matY, vNormal ); + normal.z = dot3( matZ, vNormal ); + normal = normalize( normal ); + + float3 tangent; + tangent.x = dot3( matX, vTangent ); + tangent.y = dot3( matY, vTangent ); + tangent.z = dot3( matZ, vTangent ); + tangent = normalize( tangent ); + + float3 binormal; + binormal.x = dot3( matX, vBinormal ); + binormal.y = dot3( matY, vBinormal ); + binormal.z = dot3( matZ, vBinormal ); + binormal = normalize( binormal ); + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + float4 defaultTexCoord = float4( 0.0f, 0.5f, 0.0f, 1.0f ); + + //calculate vector to light in R0 + float4 toLight = rpLocalLightOrigin - modelPosition; + + //-------------------------------------------------------------- + + //result.texcoord0 is the direction to the light in tangent space + result.texcoord0.x = dot3( tangent, toLight ); + result.texcoord0.y = dot3( binormal, toLight ); + result.texcoord0.z = dot3( normal, toLight ); + result.texcoord0.w = 1.0f; + + //textures 1 takes the base coordinates by the texture matrix + result.texcoord1 = defaultTexCoord; + result.texcoord1.x = dot4( vertex.texcoord.xy, rpBumpMatrixS ); + result.texcoord1.y = dot4( vertex.texcoord.xy, rpBumpMatrixT ); + + //# texture 2 has one texgen + result.texcoord2 = defaultTexCoord; + result.texcoord2.x = dot4( modelPosition, rpLightFalloffS ); + + //# texture 3 has three texgens + result.texcoord3.x = dot4( modelPosition, rpLightProjectionS ); + result.texcoord3.y = dot4( modelPosition, rpLightProjectionT ); + result.texcoord3.z = 0.0f; + result.texcoord3.w = dot4( modelPosition, rpLightProjectionQ ); + + //# textures 4 takes the base coordinates by the texture matrix + result.texcoord4 = defaultTexCoord; + result.texcoord4.x = dot4( vertex.texcoord.xy, rpDiffuseMatrixS ); + result.texcoord4.y = dot4( vertex.texcoord.xy, rpDiffuseMatrixT ); + + //# textures 5 takes the base coordinates by the texture matrix + result.texcoord5 = defaultTexCoord; + result.texcoord5.x = dot4( vertex.texcoord.xy, rpSpecularMatrixS ); + result.texcoord5.y = dot4( vertex.texcoord.xy, rpSpecularMatrixT ); + + //# texture 6's texcoords will be the halfangle in texture space + + //# calculate normalized vector to light in R0 + toLight = normalize( toLight ); + + //# calculate normalized vector to viewer in R1 + float4 toView = normalize( rpLocalViewOrigin - modelPosition ); + + //# add together to become the half angle vector in object space (non-normalized) + float4 halfAngleVector = toLight + toView; + + //# put into texture space + result.texcoord6.x = dot3( tangent, halfAngleVector ); + result.texcoord6.y = dot3( binormal, halfAngleVector ); + result.texcoord6.z = dot3( normal, halfAngleVector ); + result.texcoord6.w = 1.0f; + + // for joint transformation of the tangent space, we use color and + // color2 for weighting information, so hopefully there aren't any + // effects that need vertex color... + result.color = float4( 1.0f, 1.0f, 1.0f, 1.0f ); + + //# generate the vertex color, which can be 1.0, color, or 1.0 - color + //# for 1.0 : env[16] = 0, env[17] = 1 + //# for color : env[16] = 1, env[17] = 0 + //# for 1.0-color : env[16] = -1, env[17] = 1 + // result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +} \ No newline at end of file diff --git a/base/renderprogs/motionBlur.pixel b/base/renderprogs/motionBlur.pixel new file mode 100644 index 00000000..d486e103 --- /dev/null +++ b/base/renderprogs/motionBlur.pixel @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); // view color +uniform sampler2D samp1 : register(s1); // view depth + +struct PS_IN { + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { +#if 0 + if ( fragment.texcoord0.x < 0.5 ) { + // only draw on half the screen for comparison + discard; + } +#endif + // don't motion blur the hands, which were drawn with alpha = 0 + if ( tex2D( samp0, fragment.texcoord0 ).w == 0.0 ) { + discard; + } + + // derive clip space from the depth buffer and screen position + float windowZ = tex2D( samp1, fragment.texcoord0 ).x; + float3 ndc = float3( fragment.texcoord0 * 2.0 - 1.0, windowZ * 2.0 - 1.0 ); + float clipW = -rpProjectionMatrixZ.w / ( -rpProjectionMatrixZ.z - ndc.z ); + + float4 clip = float4( ndc * clipW, clipW ); + + // convert from clip space this frame to clip space previous frame + float4 reClip; + reClip.x = dot( rpMVPmatrixX, clip ); + reClip.y = dot( rpMVPmatrixY, clip ); + reClip.z = dot( rpMVPmatrixZ, clip ); + reClip.w = dot( rpMVPmatrixW, clip ); + + // convert to NDC values + float2 prevTexCoord; + prevTexCoord.x = ( reClip.x / reClip.w ) * 0.5 + 0.5; + prevTexCoord.y = ( reClip.y / reClip.w ) * 0.5 + 0.5; + + // sample along the line from prevTexCoord to fragment.texcoord0 + + float2 texCoord = prevTexCoord; //fragment.texcoord0; + float2 delta = ( fragment.texcoord0 - prevTexCoord ); + + float3 sum = float3( 0.0 ); + float goodSamples = 0; + float samples = rpOverbright.x; + + for ( float i = 0 ; i < samples ; i = i + 1 ) { + float2 pos = fragment.texcoord0 + delta * ( ( i / ( samples - 1 ) ) - 0.5 ); + float4 color = tex2D( samp0, pos ); + // only take the values that are not part of the weapon + sum += color.xyz * color.w; + goodSamples += color.w; + } + float invScale = 1.0 / goodSamples; + + result.color = float4( sum * invScale, 1.0 ); +} diff --git a/base/renderprogs/motionBlur.vertex b/base/renderprogs/motionBlur.vertex new file mode 100644 index 00000000..752a938e --- /dev/null +++ b/base/renderprogs/motionBlur.vertex @@ -0,0 +1,44 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position = vertex.position; + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/postprocess.pixel b/base/renderprogs/postprocess.pixel new file mode 100644 index 00000000..5a0b3ebe --- /dev/null +++ b/base/renderprogs/postprocess.pixel @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float2 tCoords = fragment.texcoord0; + result.color = tex2D( samp0, tCoords ); +} diff --git a/base/renderprogs/postprocess.vertex b/base/renderprogs/postprocess.vertex new file mode 100644 index 00000000..87df0f08 --- /dev/null +++ b/base/renderprogs/postprocess.vertex @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position = vertex.position; + + //result.position.x = vertex.position; //dot4( vertex.position, rpMVPmatrixX ); + //result.position.y = dot4( vertex.position, rpMVPmatrixY ); + //result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + //result.position.w = dot4( vertex.position, rpMVPmatrixW ); + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/shadow.pixel b/base/renderprogs/shadow.pixel new file mode 100644 index 00000000..d967be78 --- /dev/null +++ b/base/renderprogs/shadow.pixel @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/shadow.vertex b/base/renderprogs/shadow.vertex new file mode 100644 index 00000000..91a7f63d --- /dev/null +++ b/base/renderprogs/shadow.vertex @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + float4 vPos = vertex.position - rpLocalLightOrigin; + vPos = ( vPos.wwww * rpLocalLightOrigin ) + vPos; + + result.position.x = dot4( vPos, rpMVPmatrixX ); + result.position.y = dot4( vPos, rpMVPmatrixY ); + result.position.z = dot4( vPos, rpMVPmatrixZ ); + result.position.w = dot4( vPos, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/shadowDebug.pixel b/base/renderprogs/shadowDebug.pixel new file mode 100644 index 00000000..d967be78 --- /dev/null +++ b/base/renderprogs/shadowDebug.pixel @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/shadowDebug.vertex b/base/renderprogs/shadowDebug.vertex new file mode 100644 index 00000000..91a7f63d --- /dev/null +++ b/base/renderprogs/shadowDebug.vertex @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + float4 vPos = vertex.position - rpLocalLightOrigin; + vPos = ( vPos.wwww * rpLocalLightOrigin ) + vPos; + + result.position.x = dot4( vPos, rpMVPmatrixX ); + result.position.y = dot4( vPos, rpMVPmatrixY ); + result.position.z = dot4( vPos, rpMVPmatrixZ ); + result.position.w = dot4( vPos, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/shadowDebug_skinned.pixel b/base/renderprogs/shadowDebug_skinned.pixel new file mode 100644 index 00000000..d967be78 --- /dev/null +++ b/base/renderprogs/shadowDebug_skinned.pixel @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/shadowDebug_skinned.vertex b/base/renderprogs/shadowDebug_skinned.vertex new file mode 100644 index 00000000..2d6c0a15 --- /dev/null +++ b/base/renderprogs/shadowDebug_skinned.vertex @@ -0,0 +1,91 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float4 vertexPosition = vertex.position; + vertexPosition.w = 1.0; + + float4 modelPosition; + modelPosition.x = dot4( matX, vertexPosition ); + modelPosition.y = dot4( matY, vertexPosition ); + modelPosition.z = dot4( matZ, vertexPosition ); + modelPosition.w = vertex.position.w; + + float4 vPos = modelPosition - rpLocalLightOrigin; + vPos = ( vPos.wwww * rpLocalLightOrigin ) + vPos; + + result.position.x = dot4( vPos, rpMVPmatrixX ); + result.position.y = dot4( vPos, rpMVPmatrixY ); + result.position.z = dot4( vPos, rpMVPmatrixZ ); + result.position.w = dot4( vPos, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/shadow_skinned.pixel b/base/renderprogs/shadow_skinned.pixel new file mode 100644 index 00000000..d967be78 --- /dev/null +++ b/base/renderprogs/shadow_skinned.pixel @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/shadow_skinned.vertex b/base/renderprogs/shadow_skinned.vertex new file mode 100644 index 00000000..2d6c0a15 --- /dev/null +++ b/base/renderprogs/shadow_skinned.vertex @@ -0,0 +1,91 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float4 vertexPosition = vertex.position; + vertexPosition.w = 1.0; + + float4 modelPosition; + modelPosition.x = dot4( matX, vertexPosition ); + modelPosition.y = dot4( matY, vertexPosition ); + modelPosition.z = dot4( matZ, vertexPosition ); + modelPosition.w = vertex.position.w; + + float4 vPos = modelPosition - rpLocalLightOrigin; + vPos = ( vPos.wwww * rpLocalLightOrigin ) + vPos; + + result.position.x = dot4( vPos, rpMVPmatrixX ); + result.position.y = dot4( vPos, rpMVPmatrixY ); + result.position.z = dot4( vPos, rpMVPmatrixZ ); + result.position.w = dot4( vPos, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/simpleshade.pixel b/base/renderprogs/simpleshade.pixel new file mode 100644 index 00000000..c7c0aea5 --- /dev/null +++ b/base/renderprogs/simpleshade.pixel @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct PS_IN { + float4 position : VPOS; + float4 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +uniform sampler2D samp0 : register(s0); + +static float2 screenPosToTexcoord( float2 pos, float4 bias_scale ) { return ( pos * bias_scale.zw + bias_scale.xy ); } + +void main( PS_IN fragment, out PS_OUT result ) { + const float renderWidth = 1280.0f; + const float renderHeight = 720.0f; + const float4 positionToViewTexture = float4( 0.5f / renderWidth, 0.5f / renderHeight, 1.0f / renderWidth, 1.0f / renderHeight ); + + float interpolatedZOverW = ( 1.0 - ( fragment.texcoord0.z / fragment.texcoord0.w ) ); + + float3 pos; + pos.z = 1.0 / interpolatedZOverW; + pos.xy = pos.z * ( 2.0 * screenPosToTexcoord( fragment.position.xy, positionToViewTexture ) - 1.0 ); + + float3 normal = normalize( cross( ddy( pos ), ddx( pos ) ) ); + + // light is above and to the right in the eye plane + float3 L = normalize( float3( 1.0, 1.0, 0.0 ) - pos ); + + result.color.xyz = _float3( dot3( normal, L ) * 0.75 ); + result.color.w = 1.0; +} \ No newline at end of file diff --git a/base/renderprogs/simpleshade.vertex b/base/renderprogs/simpleshade.vertex new file mode 100644 index 00000000..22c89124 --- /dev/null +++ b/base/renderprogs/simpleshade.vertex @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + float4 position; + position.x = dot4( vertex.position, rpMVPmatrixX ); + position.y = dot4( vertex.position, rpMVPmatrixY ); + position.z = dot4( vertex.position, rpMVPmatrixZ ); + position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.position = position; + result.texcoord0 = position; +} \ No newline at end of file diff --git a/base/renderprogs/skinning.inc b/base/renderprogs/skinning.inc new file mode 100644 index 00000000..05dfb3f0 --- /dev/null +++ b/base/renderprogs/skinning.inc @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +float4 modelPosition = vertex.position; +BRANCH if ( rpEnableSkinning.x > 0.0 ) { + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; +} + +result.position.x = dot4( modelPosition, rpMVPmatrixX ); +result.position.y = dot4( modelPosition, rpMVPmatrixY ); +result.position.z = dot4( modelPosition, rpMVPmatrixZ ); +result.position.w = dot4( modelPosition, rpMVPmatrixW ); \ No newline at end of file diff --git a/base/renderprogs/skybox.pixel b/base/renderprogs/skybox.pixel new file mode 100644 index 00000000..1cc25073 --- /dev/null +++ b/base/renderprogs/skybox.pixel @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform samplerCUBE samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float3 texcoord0 : TEXCOORD0_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + result.color = texCUBE( samp0, fragment.texcoord0 ) * fragment.color; +} diff --git a/base/renderprogs/skybox.vertex b/base/renderprogs/skybox.vertex new file mode 100644 index 00000000..e6b711a9 --- /dev/null +++ b/base/renderprogs/skybox.vertex @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0 = vertex.position.xyz - rpLocalViewOrigin.xyz; + + result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +} diff --git a/base/renderprogs/stereoDeGhost.pixel b/base/renderprogs/stereoDeGhost.pixel new file mode 100644 index 00000000..2726f008 --- /dev/null +++ b/base/renderprogs/stereoDeGhost.pixel @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.color = rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/stereoDeGhost.vertex b/base/renderprogs/stereoDeGhost.vertex new file mode 100644 index 00000000..e330f16a --- /dev/null +++ b/base/renderprogs/stereoDeGhost.vertex @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); +} \ No newline at end of file diff --git a/base/renderprogs/stereoInterlace.pixel b/base/renderprogs/stereoInterlace.pixel new file mode 100644 index 00000000..6049d375 --- /dev/null +++ b/base/renderprogs/stereoInterlace.pixel @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +uniform sampler2D samp1 : register(s1); + +struct PS_IN { + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + // texcoords will run from 0 to 1 across the entire screen + if ( fract( fragment.position.y * 0.5 ) < 0.5 ) { + result.color = tex2D( samp0, vec2( fragment.texcoord0 ) ); + } else { + result.color = tex2D( samp1, vec2( fragment.texcoord0 ) ); + } +} \ No newline at end of file diff --git a/base/renderprogs/stereoInterlace.vertex b/base/renderprogs/stereoInterlace.vertex new file mode 100644 index 00000000..85831d10 --- /dev/null +++ b/base/renderprogs/stereoInterlace.vertex @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/stereoWarp.pixel b/base/renderprogs/stereoWarp.pixel new file mode 100644 index 00000000..17319e30 --- /dev/null +++ b/base/renderprogs/stereoWarp.pixel @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +/* + +This shader will cover a square block of pixel coordinates, but some of them might +be scissored off if the edges of the screen or the midpoint divider are visible through +the optics. + +*/ + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + vec4 texcoord0 : TEXCOORD0_centroid; +}; + + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + const float screenWarp_range = 1.45; + + const vec2 warpCenter = vec2( 0.5, 0.5 ); + vec2 centeredTexcoord = fragment.texcoord0.xy - warpCenter; + + float radialLength = length( centeredTexcoord ); + vec2 radialDir = normalize( centeredTexcoord ); + + // get it down into the 0 - PI/2 range + float range = screenWarp_range; + float scaledRadialLength = radialLength * range; + float tanScaled = tan( scaledRadialLength ); + + float rescaleValue = tan( 0.5 * range ); + + // If radialLength was 0.5, we want rescaled to also come out + // as 0.5, so the edges of the rendered image are at the edges + // of the warped image. + float rescaled = tanScaled / rescaleValue; + + vec2 warped = warpCenter + vec2( 0.5, 0.5 ) * radialDir * rescaled; + + result.color = tex2D( samp0, warped ); +} diff --git a/base/renderprogs/stereoWarp.vertex b/base/renderprogs/stereoWarp.vertex new file mode 100644 index 00000000..dcf8fcc0 --- /dev/null +++ b/base/renderprogs/stereoWarp.vertex @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float4 texcoord : TEXCOORD0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0: TEXCOORD0; // 0 to 1 box +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + result.texcoord0 = vertex.texcoord; +} \ No newline at end of file diff --git a/base/renderprogs/texture.pixel b/base/renderprogs/texture.pixel new file mode 100644 index 00000000..88fa8202 --- /dev/null +++ b/base/renderprogs/texture.pixel @@ -0,0 +1,44 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + result.color = tex2D( samp0, fragment.texcoord0 ) * rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/texture.vertex b/base/renderprogs/texture.vertex new file mode 100644 index 00000000..4c77d408 --- /dev/null +++ b/base/renderprogs/texture.vertex @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + // compute oldschool texgen or multiply by texture matrix + BRANCH if ( rpTexGen0Enabled.x > 0.0 ) { + result.texcoord0.x = dot4( vertex.position, rpTexGen0S ); + result.texcoord0.y = dot4( vertex.position, rpTexGen0T ); + } else { + result.texcoord0.x = dot4( vertex.texcoord.xy, rpTextureMatrixS ); + result.texcoord0.y = dot4( vertex.texcoord.xy, rpTextureMatrixT ); + } +} \ No newline at end of file diff --git a/base/renderprogs/texture_color.pixel b/base/renderprogs/texture_color.pixel new file mode 100644 index 00000000..590f8516 --- /dev/null +++ b/base/renderprogs/texture_color.pixel @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float4 color = tex2D( samp0, fragment.texcoord0 ) * fragment.color; + clip( color.a - rpAlphaTest.x ); + result.color = color; +} \ No newline at end of file diff --git a/base/renderprogs/texture_color.vertex b/base/renderprogs/texture_color.vertex new file mode 100644 index 00000000..7879d5fd --- /dev/null +++ b/base/renderprogs/texture_color.vertex @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + // compute oldschool texgen or multiply by texture matrix + BRANCH if ( rpTexGen0Enabled.x > 0.0 ) { + result.texcoord0.x = dot4( vertex.position, rpTexGen0S ); + result.texcoord0.y = dot4( vertex.position, rpTexGen0T ); + } else { + result.texcoord0.x = dot4( vertex.texcoord.xy, rpTextureMatrixS ); + result.texcoord0.y = dot4( vertex.texcoord.xy, rpTextureMatrixT ); + } + + float4 vertexColor = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; + result.color = vertexColor * rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/texture_color_skinned.pixel b/base/renderprogs/texture_color_skinned.pixel new file mode 100644 index 00000000..590f8516 --- /dev/null +++ b/base/renderprogs/texture_color_skinned.pixel @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float4 color = tex2D( samp0, fragment.texcoord0 ) * fragment.color; + clip( color.a - rpAlphaTest.x ); + result.color = color; +} \ No newline at end of file diff --git a/base/renderprogs/texture_color_skinned.vertex b/base/renderprogs/texture_color_skinned.vertex new file mode 100644 index 00000000..bedef194 --- /dev/null +++ b/base/renderprogs/texture_color_skinned.vertex @@ -0,0 +1,100 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform matrices_ubo { float4 matrices[408]; }; + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 color : COLOR0; + float4 color2 : COLOR1; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + //-------------------------------------------------------------- + // GPU transformation of the normal / binormal / bitangent + // + // multiplying with 255.1 give us the same result and is faster than floor( w * 255 + 0.5 ) + //-------------------------------------------------------------- + const float w0 = vertex.color2.x; + const float w1 = vertex.color2.y; + const float w2 = vertex.color2.z; + const float w3 = vertex.color2.w; + + float4 matX, matY, matZ; // must be float4 for vec4 + float joint = vertex.color.x * 255.1 * 3; + matX = matrices[int(joint+0)] * w0; + matY = matrices[int(joint+1)] * w0; + matZ = matrices[int(joint+2)] * w0; + + joint = vertex.color.y * 255.1 * 3; + matX += matrices[int(joint+0)] * w1; + matY += matrices[int(joint+1)] * w1; + matZ += matrices[int(joint+2)] * w1; + + joint = vertex.color.z * 255.1 * 3; + matX += matrices[int(joint+0)] * w2; + matY += matrices[int(joint+1)] * w2; + matZ += matrices[int(joint+2)] * w2; + + joint = vertex.color.w * 255.1 * 3; + matX += matrices[int(joint+0)] * w3; + matY += matrices[int(joint+1)] * w3; + matZ += matrices[int(joint+2)] * w3; + + float4 modelPosition; + modelPosition.x = dot4( matX, vertex.position ); + modelPosition.y = dot4( matY, vertex.position ); + modelPosition.z = dot4( matZ, vertex.position ); + modelPosition.w = 1.0; + + result.position.x = dot4( modelPosition, rpMVPmatrixX ); + result.position.y = dot4( modelPosition, rpMVPmatrixY ); + result.position.z = dot4( modelPosition, rpMVPmatrixZ ); + result.position.w = dot4( modelPosition, rpMVPmatrixW ); + + // compute oldschool texgen or multiply by texture matrix + BRANCH if ( rpTexGen0Enabled.x > 0.0 ) { + result.texcoord0.x = dot4( modelPosition, rpTexGen0S ); + result.texcoord0.y = dot4( modelPosition, rpTexGen0T ); + } else { + result.texcoord0.x = dot4( vertex.texcoord.xy, rpTextureMatrixS ); + result.texcoord0.y = dot4( vertex.texcoord.xy, rpTextureMatrixT ); + } + + float4 vertexColor = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; + result.color = vertexColor * rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/texture_color_texgen.pixel b/base/renderprogs/texture_color_texgen.pixel new file mode 100644 index 00000000..1c222a21 --- /dev/null +++ b/base/renderprogs/texture_color_texgen.pixel @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float4 texcoord0 : TEXCOORD0_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + + // we always do a projective texture lookup so that we can support texgen + // materials without a separate shader. Basic materials will have texture + // coordinates with w = 1 which will result in a NOP projection when tex2Dproj + // gets called. + float4 texSample = idtex2Dproj( samp0, fragment.texcoord0 ); + + result.color = texSample * fragment.color; +} \ No newline at end of file diff --git a/base/renderprogs/texture_color_texgen.vertex b/base/renderprogs/texture_color_texgen.vertex new file mode 100644 index 00000000..0e9f8816 --- /dev/null +++ b/base/renderprogs/texture_color_texgen.vertex @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + float4 tc0; + tc0.x = dot4( vertex.position, rpTexGen0S ); + tc0.y = dot4( vertex.position, rpTexGen0T ); + + tc0.z = 0.0f; + tc0.w = dot4( vertex.position, rpTexGen0Q ); + + // multiply the texture matrix in + result.texcoord0.x = dot4( tc0, rpTextureMatrixS ); + result.texcoord0.y = dot4( tc0, rpTextureMatrixT ); + result.texcoord0.zw = tc0.zw; + + // compute vertex modulation + float4 vertexColor = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; + result.color = vertexColor * rpColor; +} \ No newline at end of file diff --git a/base/renderprogs/wobblesky.pixel b/base/renderprogs/wobblesky.pixel new file mode 100644 index 00000000..1cc25073 --- /dev/null +++ b/base/renderprogs/wobblesky.pixel @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform samplerCUBE samp0 : register(s0); + +struct PS_IN { + float4 position : VPOS; + float3 texcoord0 : TEXCOORD0_centroid; + float4 color : COLOR0; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + result.color = texCUBE( samp0, fragment.texcoord0 ) * fragment.color; +} diff --git a/base/renderprogs/wobblesky.vertex b/base/renderprogs/wobblesky.vertex new file mode 100644 index 00000000..b85c7328 --- /dev/null +++ b/base/renderprogs/wobblesky.vertex @@ -0,0 +1,57 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + float3 t0 = vertex.position.xyz - rpLocalViewOrigin.xyz; + result.texcoord0.x = dot3( t0, rpWobbleSkyX ); + result.texcoord0.y = dot3( t0, rpWobbleSkyY ); + result.texcoord0.z = dot3( t0, rpWobbleSkyZ ); + + result.color = ( swizzleColor( vertex.color ) * rpVertexColorModulate ) + rpVertexColorAdd; +} diff --git a/base/renderprogs/zcullReconstruct.pixel b/base/renderprogs/zcullReconstruct.pixel new file mode 100644 index 00000000..df1a2b00 --- /dev/null +++ b/base/renderprogs/zcullReconstruct.pixel @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct PS_OUT { + float depth : DEPTH; + float4 color : COLOR; +}; + +void main( out PS_OUT result ) { + result.depth = 1; + result.color = float4( 0.0, 0.0, 0.0, 0.0 ); +} \ No newline at end of file diff --git a/base/renderprogs/zcullReconstruct.vertex b/base/renderprogs/zcullReconstruct.vertex new file mode 100644 index 00000000..12e7cd51 --- /dev/null +++ b/base/renderprogs/zcullReconstruct.vertex @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; +}; + +struct VS_OUT { + float4 position : POSITION; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position = vertex.position; + result.position.z = -1.0; +} \ No newline at end of file diff --git a/doomclassic/DoomClassicCommon.props b/doomclassic/DoomClassicCommon.props new file mode 100644 index 00000000..7ab24c74 --- /dev/null +++ b/doomclassic/DoomClassicCommon.props @@ -0,0 +1,19 @@ + + + + + + $(SolutionDir)..\build\$(PlatformName)\$(Configuration)\ + $(SolutionDir)..\build\$(PlatformName)\$(Configuration)\intermediate\$(ProjectName)\ + + + + $(ProjectDir)Main;$(SolutionDir);$(DXSDK_DIR)\Include\;%(AdditionalIncludeDirectories) + true + false + _D3XP;CTF;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + + + + \ No newline at end of file diff --git a/doomclassic/doom/DoomLeaderboards.cpp b/doomclassic/doom/DoomLeaderboards.cpp new file mode 100644 index 00000000..9134d4bb --- /dev/null +++ b/doomclassic/doom/DoomLeaderboards.cpp @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "DoomLeaderboards.h" +#include "tech5/engine/framework/precompiled.h" +#include "tech5\engine\sys\sys_stats.h" +#include "../doomengine/source/doomdef.h" + +#include + +static columnDef_t columnDefTime[] = { + { "Time", 64, AGGREGATE_MIN, STATS_COLUMN_DISPLAY_TIME_MILLISECONDS } +}; + +const static int NUMLEVELS_ULTIMATE_DOOM = 36; +const static int NUMLEVELS_DOOM2_HELL_ON_EARTH = 32; +const static int NUMLEVELS_FINALDOOM_TNT = 32; +const static int NUMLEVELS_FINALDOOM_PLUTONIA = 32; +const static int NUMLEVELS_DOOM2_MASTER_LEVELS = 21; +const static int NUMLEVELS_DOOM2_NERVE = 9; + + +const static int NUM_LEVEL_LIST[] = { + NUMLEVELS_ULTIMATE_DOOM, + NUMLEVELS_DOOM2_HELL_ON_EARTH, + NUMLEVELS_FINALDOOM_TNT, + NUMLEVELS_FINALDOOM_PLUTONIA, + NUMLEVELS_DOOM2_MASTER_LEVELS, + NUMLEVELS_DOOM2_NERVE +}; + +/* +======================== +GenerateLeaderboard ID +Generates a Leaderboard ID based on current Expansion + episode + map + skill + Type + +Expansion is the base value that the leaderboard ID comes from + +======================== +*/ +const int GenerateLeaderboardID( int expansion, int episode, int map, int skill ) { + + int realMapNumber = ( episode * map - 1 ); + + if( common->GetGameSKU() == GAME_SKU_DOOM1_BFG ) { + + // Doom levels start at 620 .. yeah.. hack. + int block = 615; + int mapAndSkill = ( realMapNumber * ( (int)sk_nightmare + 1 ) ) + skill ; + + return block + mapAndSkill; + } else if( common->GetGameSKU() == GAME_SKU_DOOM2_BFG ) { + + if( expansion == 1 ) { + // Doom 2 Levels start at 800.. Yep.. another hack. + int block = 795; + int mapAndSkill = ( realMapNumber * ( (int)sk_nightmare + 1 ) ) + skill ; + + return block + mapAndSkill; + } else { + // Nerve Levels start at 960... another hack! + int block = 955; + int mapAndSkill = ( realMapNumber * ( (int)sk_nightmare + 1 ) ) + skill ; + + return block + mapAndSkill; + } + + } else { + + // DCC Content + int block = 0; + if( expansion > 0 ){ + for( int expi = 0; expi < expansion; expi++ ) { + block += NUM_LEVEL_LIST[ expi ] * ( (int)sk_nightmare + 1); + } + } + int mapAndSkill = ( realMapNumber * ( (int)sk_nightmare + 1 ) ) + skill ; + return block + mapAndSkill; + } +} + +/* +======================== +InitLeaderboards + +Generates all the possible leaderboard definitions +and stores into an STL Map, with leaderboard ID as the Hash/key value. + +======================== +*/ +void InitLeaderboards() { + + for( int expi = 0; expi < ARRAY_COUNT( NUM_LEVEL_LIST ); expi++ ) { + + for( int udi = 1; udi <= NUM_LEVEL_LIST[expi] ; udi++ ) { + + for( int skilli = 0; skilli <= sk_nightmare; skilli++ ) { + + // Create the Time Trial leaderboard for each level. + int timeTrial_leaderboardID = GenerateLeaderboardID( expi, 1, udi, skilli ); + leaderboardDefinition_t * timeTrial_Leaderboard = new leaderboardDefinition_t( timeTrial_leaderboardID, ARRAY_COUNT( columnDefTime ), columnDefTime, RANK_LEAST_FIRST, false, true ); + + } + } + } +} + +/* +======================== +GenerateLeaderboard +Generates a Leaderboard based on current Expansion + episode + map + skill + Type + +Expansion is the base value that the leaderboard ID comes from + +======================== +*/ +const leaderboardDefinition_t * GetLeaderboard( int expansion, int episode, int map, int skill ) { + + int leaderboardID = GenerateLeaderboardID( expansion, episode, map, skill ); + + return Sys_FindLeaderboardDef( leaderboardID );; +} \ No newline at end of file diff --git a/doomclassic/doom/DoomLeaderboards.h b/doomclassic/doom/DoomLeaderboards.h new file mode 100644 index 00000000..cdb4b7c6 --- /dev/null +++ b/doomclassic/doom/DoomLeaderboards.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __DOOM_LEADERBOARDS_H__ +#define __DOOM_LEADERBOARDS_H__ + + +#include "sys\sys_stats_misc.h" + +enum Leaderboard_type_t { + + LEADERBOARD_TYPE_TIME_TRIAL, + LEADERBOARD_TYPE_FRAGS, + + LEADERBOARD_MAX_TYPES +}; + +void InitLeaderboards(); +const leaderboardDefinition_t * GetLeaderboard( int expansion, int episode, int map, int skill ); + +#endif // __DOOM_LEADERBOARDS_H__ \ No newline at end of file diff --git a/doomclassic/doom/Main.h b/doomclassic/doom/Main.h new file mode 100644 index 00000000..c59ba645 --- /dev/null +++ b/doomclassic/doom/Main.h @@ -0,0 +1,102 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#include "idlib/precompiled.h" + +#include "../doom/doomlib.h" +#include "../doom/doominterface.h" +#include "../doom/globaldata.h" + + +// DHM - Nerve :: Enable demo recording for game clips +#define _DEMO_RECORDING + +#ifdef _DEBUG + #define safeOutputDebug(x) printf( "%s", x ); +#else + #define safeOutputDebug(x) +#endif + +struct SplitscreenData { + int PLAYERCOUNT; + int globalSkill; + int globalEpisode; + int globalLevel; + int globalTimeLimit; + int globalFragLimit; +}; + +void DL_InitNetworking( DoomInterface *pdi ); + +extern int PLAYERCOUNT; +extern bool globalNetworking; +extern bool debugOutput; +extern BOOL globalLicenseFullGame; +extern int globalRichPresenceState; // values from spa.h X_CONTEXT_PRESENCE +extern int globalNeedUpsell; +// PS3 +//extern HXUISTRINGTABLE globalStrings; // gStrings for short +extern bool globalPauseTime; + + +enum MenuStates{ + MENU_NONE, + MENU_XBOX_SYSTEM, + MENU_PAUSE, + MENU_UPSELL, + MENU_UPSELL_INVITE, + MENU_ENDLEVEL_UPSELL, + MENU_ERROR_MESSAGE, + MENU_ERROR_MESSAGE_FATAL, + MENU_END_LEVEL, + MENU_END_EPISODE, + MENU_END_CAST, + MENU_END_LEVEL_COOP, + MENU_END_LEVEL_DM, + MENU_END_GAME_LOBBY, + MENU_END_GAME_LOBBY_PLAYER, + MENU_LOBBY, + MENU_LOBBY_PLAYER, + MENU_INVITE, + MENU_COUNT +}; + +typedef struct { + int maxPing; + + const wchar_t * image; +} PingImage_t; + +extern PingImage_t pingsImages[]; + + +#endif \ No newline at end of file diff --git a/doomclassic/doom/Networking.cpp b/doomclassic/doom/Networking.cpp new file mode 100644 index 00000000..55e3eba9 --- /dev/null +++ b/doomclassic/doom/Networking.cpp @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "../Main/Main.h" + +#include +#include + +#include "tech5/engine/sys/sys_lobby.h" + +bool useTech5Packets = true; + +#if 1 + +struct networkitem +{ + int source; + int size; + char buffer[256*64]; +}; + +std::queue< networkitem > networkstacks[4]; +//sockaddr_in sockaddrs[4]; + +int DoomLibRecv( char* buff, DWORD *numRecv ) +{ + //int player = DoomInterface::CurrentPlayer(); + int player = ::g->consoleplayer; + + if (networkstacks[player].empty()) + return -1; + + networkitem item = networkstacks[player].front(); + + memcpy( buff, item.buffer, item.size ); + *numRecv = item.size; + //*source = sockaddrs[item.source]; + + networkstacks[player].pop(); + + return 1; +} + +void I_Printf(char *error, ...); + +int DoomLibSend( const char* buff, DWORD size, sockaddr_in *target, int toNode ) +{ + int i; + + i = DoomLib::RemoteNodeToPlayerIndex( toNode ); + + //I_Printf( "DoomLibSend %d --> %d: %d\n", ::g->consoleplayer, i, size ); + + networkitem item; + item.source = DoomInterface::CurrentPlayer(); + item.size = size; + memcpy( item.buffer, buff, size ); + networkstacks[i].push( item ); + + return 1; +} + + +int DoomLibSendRemote() +{ + if ( gameLocal == NULL ) { + return 0; + } + + int curPlayer = DoomLib::GetPlayer(); + + for (int player = 0; player < gameLocal->Interface.GetNumPlayers(); ++player) + { + DoomLib::SetPlayer( player ); + + for( int i = 0; i < 4; i++ ) { + + //Check if it is remote + int node = DoomLib::PlayerIndexToRemoteNode( i ); + if ( ::g->sendaddress[node].sin_addr.s_addr == ::g->sendaddress[0].sin_addr.s_addr ) { + continue; + } + + while(!networkstacks[i].empty()) { + networkitem item = networkstacks[i].front(); + + int c; + //WSABUF buffer; + //DWORD num_sent; + + //buffer.buf = (char*)&item.buffer; + //buffer.len = item.size; + + if ( useTech5Packets ) { + idLobby & lobby = static_cast< idLobby & >( session->GetGameLobbyBase() ); + + lobbyUser_t * user = lobby.GetLobbyUser( i ); + + if ( user != NULL ) { + lobby.SendConnectionLess( user->address, idLobby::OOB_GENERIC_GAME_DATA, (const byte *)(&item.buffer[0] ), item.size ); + } + } else { + c = sendto( ::g->sendsocket, &item.buffer, item.size, MSG_DONTWAIT, (sockaddr*)&::g->sendaddress[node], sizeof(::g->sendaddress[node]) ); + + //c = WSASendTo(::g->sendsocket, &buffer, 1, &num_sent, 0, (sockaddr*)&::g->sendaddress[node], sizeof(::g->sendaddress[node]), 0, 0); + } + + networkstacks[i].pop(); + } + } + } + + DoomLib::SetPlayer(curPlayer); + + return 1; +} + +void DL_InitNetworking( DoomInterface *pdi ) +{ + // DHM - Nerve :: Clear out any old splitscreen packets that may be lingering. + for ( int i = 0; i<4; i++ ) { + while ( !networkstacks[i].empty() ) { + networkstacks[i].pop(); + } + } + + /*sockaddrs[0].sin_addr.s_addr = inet_addr("0.0.0.1" ); + sockaddrs[1].sin_addr.s_addr = inet_addr("0.0.0.2" ); + sockaddrs[2].sin_addr.s_addr = inet_addr("0.0.0.3" ); + sockaddrs[3].sin_addr.s_addr = inet_addr("0.0.0.4" );*/ + pdi->SetNetworking( DoomLibRecv, DoomLibSend, DoomLibSendRemote ); +} + +#else + +void DL_InitNetworking( DoomInterface *pdi ) { + +} + +#endif diff --git a/doomclassic/doom/PlayerProfile.cpp b/doomclassic/doom/PlayerProfile.cpp new file mode 100644 index 00000000..571ab582 --- /dev/null +++ b/doomclassic/doom/PlayerProfile.cpp @@ -0,0 +1,649 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "PlayerProfile.h" +#include "PS3_Includes.h" +#include "PSN/PS3_Session.h" + +const int32 FRAMEWORK_PROFILE_VER = 1; + + +// Store master volume settings in archived cvars, becausue we want them to apply +// even if a user isn't signed in. +// The range is from 0 to 15, which matches the setting in vanilla DOOM. +idCVar s_volume_sound( "s_volume_sound", "8", CVAR_ARCHIVE | CVAR_INTEGER, "sound volume", 0, 15 ); +idCVar s_volume_midi( "s_volume_midi", "8", CVAR_ARCHIVE | CVAR_INTEGER, "music volume", 0, 15 ); + + + +/* +================================================ +idProfileMgr +================================================ +*/ + +/* +======================== +idProfileMgr +======================== +*/ +idProfileMgr::idProfileMgr() : + profileSaveProcessor( new (TAG_SAVEGAMES) idSaveGameProcessorSaveProfile ), + profileLoadProcessor( new (TAG_SAVEGAMES) idSaveGameProcessorLoadProfile ), + profile( NULL ), + handle( 0 ) { +} + + +/* +================================================ +~idProfileMgr +================================================ +*/ +idProfileMgr::~idProfileMgr() { + delete profileSaveProcessor; + profileSaveProcessor = NULL; + + delete profileLoadProcessor; + profileLoadProcessor = NULL; +} + +/* +======================== +idProfileMgr::Init +======================== +*/ +void idProfileMgr::Init( idPlayerProfile * profile_ ) { + profile = profile_; + handle = 0; +} + +/* +======================== +idProfileMgr::Pump +======================== +*/ +void idProfileMgr::Pump() { + // profile can be NULL if we forced the user to register as in the case of map-ing into a level from the press start screen + if ( profile == NULL ) { + return; + } + + // See if we are done with saving/loading the profile + bool saving = profile->GetState() == idPlayerProfile::SAVING; + bool loading = profile->GetState() == idPlayerProfile::LOADING; + if ( ( saving || loading ) && psn_session->GetSaveGameManager()->IsSaveGameCompletedFromHandle( handle ) ) { + profile->SetState( idPlayerProfile::IDLE ); + + if ( saving ) { + // Done saving + } else if ( loading ) { + // Done loading + const idSaveLoadParms & parms = profileLoadProcessor->GetParms(); + if ( parms.GetError() == SAVEGAME_E_FOLDER_NOT_FOUND || parms.GetError() == SAVEGAME_E_FILE_NOT_FOUND ) { + profile->SaveSettings(); + } else if ( parms.GetError() != SAVEGAME_E_NONE ) { + profile->SetState( idPlayerProfile::ERR ); + } + } + } + + // See if we need to save/load the profile + if ( profile->GetRequestedState() == idPlayerProfile::SAVE_REQUESTED ) { + SaveSettings(); + profile->SetRequestedState( idPlayerProfile::IDLE ); + } else if ( profile->GetRequestedState() == idPlayerProfile::LOAD_REQUESTED ) { + LoadSettings(); + profile->SetRequestedState( idPlayerProfile::IDLE ); + } +} + +/* +======================== +idProfileMgr::GetProfile +======================== +*/ +idPlayerProfile * idProfileMgr::GetProfile() { + if ( profile == NULL ) { + return NULL; + } + + bool loading = ( profile->GetState() == idPlayerProfile::LOADING ) || ( profile->GetRequestedState() == idPlayerProfile::LOAD_REQUESTED ); + if ( loading ) { + return NULL; + } + + return profile; +} + +/* +======================== +idProfileMgr::SaveSettings +======================== +*/ +void idProfileMgr::SaveSettings() { + if ( profile != NULL && saveGame_enable.GetBool() ) { + // Issue the async save... + if ( profileSaveProcessor->InitSaveProfile( profile, "" ) ) { + handle = psn_session->GetSaveGameManager()->ExecuteProcessor( profileSaveProcessor ); + profile->SetState( idPlayerProfile::SAVING ); + } + } else { + // If not able to save the profile, just change the state and leave + if ( profile == NULL ) { + idLib::Warning( "Not saving profile, profile is NULL." ); + } + if ( !saveGame_enable.GetBool() ) { + idLib::Warning( "Skipping profile save because saveGame_enable = 0" ); + } + } +} + +/* +======================== +idProfileMgr::LoadSettings +======================== +*/ +void idProfileMgr::LoadSettings() { + if ( profile != NULL && saveGame_enable.GetBool() ) { + if ( profileLoadProcessor->InitLoadProfile( profile, "" ) ) { + // Skip the not found error because this might be the first time to play the game! + profileLoadProcessor->SetSkipSystemErrorDialogMask( SAVEGAME_E_FOLDER_NOT_FOUND | SAVEGAME_E_FILE_NOT_FOUND ); + + handle = psn_session->GetSaveGameManager()->ExecuteProcessor( profileLoadProcessor ); + profile->SetState( idPlayerProfile::LOADING ); + } + } else { + // If not able to save the profile, just change the state and leave + if ( profile == NULL ) { + idLib::Warning( "Not loading profile, profile is NULL." ); + } + if ( !saveGame_enable.GetBool() ) { + idLib::Warning( "Skipping profile load because saveGame_enable = 0" ); + } + } +} + +/* +================================================ +idSaveGameProcessorSaveProfile +================================================ +*/ + +/* +======================== +idSaveGameProcessorSaveProfile::idSaveGameProcessorSaveProfile +======================== +*/ +idSaveGameProcessorSaveProfile::idSaveGameProcessorSaveProfile() { + profileFile = NULL; + profile = NULL; +} + +/* +======================== +idSaveGameProcessorSaveProfile::InitSaveProfile +======================== +*/ +bool idSaveGameProcessorSaveProfile::InitSaveProfile( idPlayerProfile * profile_, const char * folder ) { + + // Serialize the profile and pass a file to the processor + profileFile = new (TAG_SAVEGAMES) idFile_Memory( SAVEGAME_PROFILE_FILENAME ); + profileFile->MakeWritable(); + profileFile->SetMaxLength( MAX_PROFILE_SIZE ); + + idTempArray< byte > buffer( MAX_PROFILE_SIZE ); + idBitMsg msg; + msg.InitWrite( buffer.Ptr(), MAX_PROFILE_SIZE ); + idSerializer ser( msg, true ); + profile_->SerializeSettings( ser ); + + profileFile->Write( msg.GetReadData(), msg.GetSize() ); + profileFile->MakeReadOnly(); + + idList< idSaveFileEntry > files; + files.Append( idSaveFileEntry( profileFile, SAVEGAMEFILE_BINARY | SAVEGAMEFILE_AUTO_DELETE, SAVEGAME_PROFILE_FILENAME ) ); + + idSaveGameDetails description; + if ( !idSaveGameProcessor::Init() ) { + return false; + } + + if ( files.Num() == 0 ) { + idLib::Warning( "No files to save." ); + return false; + } + + // Setup save system + parms.directory = AddSaveFolderPrefix( folder, idSaveGameManager::PACKAGE_PROFILE ); + parms.mode = SAVEGAME_MBF_SAVE | SAVEGAME_MBF_HIDDEN; // do NOT delete the existing files + parms.saveFileType = SAVEFILE_TYPE_AUTO; + for ( int i = 0; i < files.Num(); ++i ) { + parms.files.Append( files[i] ); + } + + + description.title = idLocalization::GetString( "#str_savegame_title" ); + description.subTitle = idLocalization::GetString( "#str_savegame_profile_heading" ); + description.summary = idLocalization::GetString( "#str_savegame_profile_desc" ); + + + // Add static image as the thumbnail + staticScreenshotFile = new (TAG_SAVEGAMES) idFile_Memory( "image" ); + + // Open up the Image file and Make it a memory file. + void* thumbImage = NULL; + int imagesize = fileSystem->ReadFile( "base/textures/PROFILE.PNG", &thumbImage ); // This file lives at USRData.. i think. + staticScreenshotFile->MakeWritable(); + staticScreenshotFile->Write( thumbImage, imagesize ); + staticScreenshotFile->MakeReadOnly(); + + parms.files.Append( idSaveFileEntry( staticScreenshotFile, SAVEGAMEFILE_THUMB, "image" ) ); + fileSystem->FreeFile( thumbImage ); + + + this->parms.description = description; + parms.description.slotName = folder; + + + + // TODO:KC - what was the purpose of this? + // JAF idKeyInput::SetUserDeviceNumForBind( profile_->GetDeviceNumForProfile() ); + + profile = profile_; + + return true; +} + +/* +======================== +idSaveGameProcessorSaveProfile::Process +======================== +*/ +bool idSaveGameProcessorSaveProfile::Process() { + // Files already setup for save, just execute as normal files + + // Platform-specific implementation + // This will start a worker thread for async operation. + // It will always signal when it's completed. + Sys_ExecuteSavegameCommandAsync( &parms ); + + return false; +} + +/* +================================================ +idSaveGameProcessorLoadProfile +================================================ +*/ + +/* +======================== +idSaveGameProcessorLoadProfile::idSaveGameProcessorLoadProfile +======================== +*/ +idSaveGameProcessorLoadProfile::idSaveGameProcessorLoadProfile() { + profileFile = NULL; + profile = NULL; +} + +/* +======================== +idSaveGameProcessorLoadProfile::~idSaveGameProcessorLoadProfile +======================== +*/ +idSaveGameProcessorLoadProfile::~idSaveGameProcessorLoadProfile() { +} + +/* +======================== +idSaveGameProcessorLoadProfile::InitLoadFiles +======================== +*/ +bool idSaveGameProcessorLoadProfile::InitLoadProfile( idPlayerProfile * profile_, const char * folder_ ) { + if ( !idSaveGameProcessor::Init() ) { + return false; + } + + parms.directory = AddSaveFolderPrefix( folder_, idSaveGameManager::PACKAGE_PROFILE ); + parms.description.slotName = folder_; + parms.mode = SAVEGAME_MBF_LOAD | SAVEGAME_MBF_HIDDEN; + parms.saveFileType = SAVEFILE_TYPE_AUTO; + + profileFile = new (TAG_SAVEGAMES) idFile_Memory( SAVEGAME_PROFILE_FILENAME ); + parms.files.Append( idSaveFileEntry( profileFile, SAVEGAMEFILE_BINARY, SAVEGAME_PROFILE_FILENAME ) ); + + profile = profile_; + + return true; +} + +/* +======================== +idSaveGameProcessorLoadProfile::Process +======================== +*/ +bool idSaveGameProcessorLoadProfile::Process() { + Sys_ExecuteSavegameCommandAsync( &parms ); + + return false; +} + +/* +======================== +idSaveGameProcessorLoadProfile::PostProcess +======================== +*/ +void idSaveGameProcessorLoadProfile::PostProcess() { + // Serialize the loaded profile + bool foundProfile = profileFile->Length() > 0; + + if ( foundProfile ) { + idTempArray< byte> buffer( MAX_PROFILE_SIZE ); + + // Serialize settings from this buffer + profileFile->MakeReadOnly(); + profileFile->ReadBigArray( buffer.Ptr(), profileFile->Length() ); + + idBitMsg msg; + msg.InitRead( buffer.Ptr(), (int)buffer.Size() ); + idSerializer ser( msg, false ); + profile->SerializeSettings( ser ); + + // JAF idKeyInput::SetUserDeviceNumForBind( profile->GetDeviceNumForProfile() ); + + } else { + parms.errorCode = SAVEGAME_E_FILE_NOT_FOUND; + } + + delete profileFile; +} + +/* +======================== +Contains data that needs to be saved out on a per player profile basis, global for the lifetime of the player so +the data can be shared across computers. +- HUD tint colors +- key bindings +- etc... +======================== +*/ + +/* +======================== +idPlayerProfile::idPlayerProfile +======================== +*/ +idPlayerProfile::idPlayerProfile() { + SetDefaults(); + + // Don't have these in SetDefaults because they're used for state management and SetDefaults is called when + // loading the profile + state = IDLE; + requestedState = IDLE; +} + +/* +======================== +idPlayerProfile::SetDefaults +======================== +*/ +void idPlayerProfile::SetDefaults() { + + achievementBits = 0; + seenInstallMessage = false; + stats.SetNum( MAX_PLAYER_PROFILE_STATS ); + for ( int i = 0; i < MAX_PLAYER_PROFILE_STATS; ++i ) { + stats[i].i = 0; + } + + deviceNum = 0; + state = IDLE; + requestedState = IDLE; + frameScaleX = 0.85f; + frameScaleY = 0.85f; +} + +/* +======================== +idPlayerProfile::Init +======================== +*/ +void idPlayerProfile::Init() { + SetDefaults(); +} + +/* +======================== +idPlayerProfile::~idPlayerProfile +======================== +*/ +idPlayerProfile::~idPlayerProfile() { +} + +/* +======================== +idPlayerProfile::SerializeSettings +======================== +*/ +bool idPlayerProfile::SerializeSettings( idSerializer & ser ) { + int flags = cvarSystem->GetModifiedFlags(); + + // Default to current tag/version + int32 tag = GetProfileTag(); + int32 version = FRAMEWORK_PROFILE_VER; + + // Serialize tag/version + ser.SerializePacked( tag ); + if ( tag != GetProfileTag() ) { + idLib::Warning( "Profile tag did not match, profile will be re-initialized" ); + SetDefaults(); + SaveSettings(); // Flag the profile to save so we have the latest version stored + + return false; + } + ser.SerializePacked( version ); + if ( version != FRAMEWORK_PROFILE_VER ) { + // For now, don't allow profiles with invalid versions load + // We could easily support old version by doing a few version checks below to pick and choose what we load as well. + idLib::Warning( "Profile version did not match. Profile will be replaced" ); + SetDefaults(); + SaveSettings(); // Flag the profile to save so we have the latest version stored + + return false; + } + + // Serialize audio settings + SERIALIZE_BOOL( ser, seenInstallMessage ); + + // New setting to save to make sure that we have or haven't seen this achievement before used to pass TRC R149d + ser.Serialize( achievementBits ); + + ser.Serialize( frameScaleX ); + ser.Serialize( frameScaleY ); + SERIALIZE_BOOL( ser, alwaysRun ); + + + // we save all the cvar-based settings in the profile even though some cvars are archived + // so that we are consistent and don't miss any or get affected when the archive flag is changed + SERIALIZE_CVAR_INT( ser, s_volume_sound ); + SERIALIZE_CVAR_INT( ser, s_volume_midi ); + + // Don't trigger profile save due to modified archived cvars during profile load + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); // must clear because set() is an OR operation, not assignment... + cvarSystem->SetModifiedFlags( flags ); + + return true; +} +/* +======================== +idPlayerProfile::GetLevel +======================== +*/ +int idPlayerProfile::GetLevel() const { + return 1; +} + +/* +======================== +idPlayerProfile::StatSetInt +======================== +*/ +void idPlayerProfile::StatSetInt( int s, int v ) { + stats[s].i = v; +} + +/* +======================== +idPlayerProfile::StatSetFloat +======================== +*/ +void idPlayerProfile::StatSetFloat( int s, float v ) { + stats[s].f = v; +} + +/* +======================== +idPlayerProfile::StatGetInt +======================== +*/ +int idPlayerProfile::StatGetInt( int s ) const { + return stats[s].i; +} + +/* +======================== +idPlayerProfile::StatGetFloat +======================== +*/ +float idPlayerProfile::StatGetFloat( int s ) const { + return stats[s].f; +} + +/* +======================== +idPlayerProfile::SaveSettings +======================== +*/ +void idPlayerProfile::SaveSettings() { + if ( state != SAVING ) { + if ( GetRequestedState() == IDLE ) { + SetRequestedState( SAVE_REQUESTED ); + } + } +} + +/* +======================== +idPlayerProfile::SaveSettings +======================== +*/ +void idPlayerProfile::LoadSettings() { + if ( state != LOADING ) { + if ( verify( GetRequestedState() == IDLE ) ) { + SetRequestedState( LOAD_REQUESTED ); + } + } +} + +/* +======================== +idPlayerProfile::SetAchievementBit +======================== +*/ +void idPlayerProfile::SetAchievementBit( const int id ) { + if ( id > 63 ) { + assert( false ); // FIXME: add another set of achievement bit flags + return; + } + + achievementBits |= (int64)1 << id; +} + +/* +======================== +idPlayerProfile::ClearAchievementBit +======================== +*/ +void idPlayerProfile::ClearAchievementBit( const int id ) { + if ( id > 63 ) { + assert( false ); // FIXME: add another set of achievement bit flags + return; + } + + achievementBits &= ~( (int64)1 << id ); +} + +/* +======================== +idPlayerProfile::GetAchievementBit +======================== +*/ +bool idPlayerProfile::GetAchievementBit( const int id ) const { + if ( id > 63 ) { + assert( false ); // FIXME: add another set of achievement bit flags + return false; + } + + return ( achievementBits & (int64)1 << id ) != 0; +} + +/* +======================== +Returns the value stored in the music volume cvar. +======================== +*/ +int idPlayerProfile::GetMusicVolume() const { + return s_volume_midi.GetInteger(); +} + +/* +======================== +Returns the value stored in the sound volume cvar. +======================== +*/ +int idPlayerProfile::GetSoundVolume() const { + return s_volume_sound.GetInteger(); +} + +/* +======================== +Sets the music volume cvar. +======================== +*/ +void idPlayerProfile::SetMusicVolume( int volume ) { + s_volume_midi.SetInteger( volume ); +} + +/* +======================== +Sets the sound volume cvar. +======================== +*/ +void idPlayerProfile::SetSoundVolume( int volume ) { + s_volume_sound.SetInteger( volume ); +} diff --git a/doomclassic/doom/PlayerProfile.h b/doomclassic/doom/PlayerProfile.h new file mode 100644 index 00000000..0946d98a --- /dev/null +++ b/doomclassic/doom/PlayerProfile.h @@ -0,0 +1,267 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PLAYERPROFILE_H__ +#define __PLAYERPROFILE_H__ + +#include "Precompiled.h" +#include "Serializer.h" +//#include "SaveGameManager.h" + +#define MAX_PROFILE_SIZE ( 1024 * 1000 ) // High number for the key bindings + +#define SAVEGAME_PROFILE_FILENAME "_PROF" + + +extern idCVar s_volume_sound; +extern idCVar s_volume_midi; + + +class idSaveGameProcessorSaveProfile; +class idSaveGameProcessorLoadProfile; +class idPlayerProfile; + +/* +================================================ +idProfileMgr +================================================ +*/ +class idProfileMgr { +public: + idProfileMgr(); + ~idProfileMgr(); + + // Called the first time it's asked to load + void Init( idPlayerProfile * profile ); + + void Pump(); + idPlayerProfile * GetProfile(); + +private: + void LoadSettings(); + void SaveSettings(); + +private: + idSaveGameProcessorSaveProfile * profileSaveProcessor; + idSaveGameProcessorLoadProfile * profileLoadProcessor; + idPlayerProfile * profile; + saveGameHandle_t handle; +}; + +/* +================================================ +idSaveGameProcessorSaveProfile +================================================ +*/ +class idSaveGameProcessorSaveProfile : public idSaveGameProcessor { +public: + DEFINE_CLASS( idSaveGameProcessorSaveProfile ); + + idSaveGameProcessorSaveProfile(); + + bool InitSaveProfile( idPlayerProfile * profile, const char * folder ); + virtual bool Process(); + +private: + idFile_Memory * profileFile; + idFile_Memory * staticScreenshotFile; + idPlayerProfile * profile; +}; + +/* +================================================ +idSaveGameProcessorLoadProfile +================================================ +*/ +class idSaveGameProcessorLoadProfile: public idSaveGameProcessor { +public: + DEFINE_CLASS( idSaveGameProcessorLoadProfile ); + + idSaveGameProcessorLoadProfile(); + ~idSaveGameProcessorLoadProfile(); + + bool InitLoadProfile( idPlayerProfile * profile, const char * folder ); + virtual bool Process(); + virtual void PostProcess(); + +private: + idFile_Memory * profileFile; + idPlayerProfile * profile; +}; + +/* +================================================ +profileStatValue_t +================================================ +*/ +union profileStatValue_t { + int i; + float f; +}; + +/* +================================================ +idPlayerProfile + +The general rule for using cvars for settings is that if you want the player's profile settings to affect the startup +of the game before there is a player associated with the game, use cvars. Example: video & volume settings. +================================================ +*/ +class idPlayerProfile { +public: + static const int MAX_PLAYER_PROFILE_STATS = 500; + + enum state_t { + IDLE = 0, + SAVING, + LOADING, + SAVE_REQUESTED, + LOAD_REQUESTED, + ERR + }; + + enum displayMode_t { + DISPLAY_INVALID = -1, + DISPLAY_WINDOWED, + DISPLAY_FULLSCREEN, + MAX_DISPLAY_MODES + }; + + enum syncTypes_t { + SYNC_INVALID = -1, + SYNC_TEAR, + SYNC_ON, + SYNC_SMART, + MAX_SYNC_COUNT, + }; + +public: + idPlayerProfile(); // don't instantiate. we static_cast the child all over the place + virtual ~idPlayerProfile(); + + //------------------------ + // each game can override but call the parent serialize first + //------------------------ + virtual void SetDefaults(); + virtual void Init(); + virtual bool SerializeSettings( idSerializer & ser ); + + //------------------------ + // each game must override, not an abstract method because we have a static object as a hack... ugh. + //------------------------ + virtual int32 GetProfileTag() { return -1; } + + int GetDeviceNumForProfile() { return deviceNum; } + void SetDeviceNumForProfile( int num ) { deviceNum = num; } + + //------------------------ + void SaveSettings(); + void LoadSettings(); + + state_t GetState() const { return state; } + state_t GetRequestedState() const { return requestedState; } + + //------------------------ + // settings + //------------------------ + float GetFrameScaleX() const { return frameScaleX; } + float GetFrameScaleY() const { return frameScaleY; } + void SetFrameScaleX( float scale ) { frameScaleX = scale; } + void SetFrameScaleY( float scale ) { frameScaleY = scale; } + + int GetMusicVolume() const; + int GetSoundVolume() const; + void SetMusicVolume( int volume ); + void SetSoundVolume( int volume ); + + bool GetAlwaysRun() const { return alwaysRun; } + void SetAlwaysRun( bool set ) { alwaysRun = set; } + + //------------------------ + // misc + //------------------------ + virtual int GetLevel() const; + + void ClearAchievementBit( const int id ); // Should only be called by idLocalUser + bool GetAchievementBit( const int id ) const; + void SetAchievementBit( const int id ); // Should only be called by idLocalUser + + bool GetSeenInstallMessage() const { return seenInstallMessage; } + void SetSeenInstallMessage( bool seen ) { seenInstallMessage = seen; } + + bool HasSavedGame() const { return hasSavedGame; } + void SetHasSavedGame() { hasSavedGame = true; } + +protected: + friend class idLocalUser; + friend class idProfileMgr; + + // used by idLocalUser and internally + void StatSetInt( int s, int v ); + void StatSetFloat( int s, float v ); + int StatGetInt( int s ) const; + float StatGetFloat( int s ) const; + +private: + void SetState( state_t value ) { state = value; } + void SetRequestedState( state_t value ) { requestedState = value; } + +protected: + //------------------------ + // settings + //------------------------ + bool alwaysRun; + int musicVolume; + int soundVolume; + + //------------------------ + // video settings + //------------------------ + float frameScaleX; + float frameScaleY; + + //------------------------ + // state management + //------------------------ + state_t state; + state_t requestedState; + + //------------------------ + // stats are stored in the profile + //------------------------ + idStaticList< profileStatValue_t, MAX_PLAYER_PROFILE_STATS > stats; + + //------------------------ + // misc + //------------------------ + int deviceNum; + bool seenInstallMessage; + uint64 achievementBits; + bool hasSavedGame; +}; + +#endif diff --git a/doomclassic/doom/Precompiled.cpp b/doomclassic/doom/Precompiled.cpp new file mode 100644 index 00000000..0193f003 --- /dev/null +++ b/doomclassic/doom/Precompiled.cpp @@ -0,0 +1,29 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" diff --git a/doomclassic/doom/Precompiled.h b/doomclassic/doom/Precompiled.h new file mode 100644 index 00000000..a06120e1 --- /dev/null +++ b/doomclassic/doom/Precompiled.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma once + +#include "idlib/precompiled.h" + + +#include +#include +#include +#include +#include +#include + +#define ID_INLINE inline + +typedef unsigned char byte; +typedef unsigned int dword; + + +#include +#include + +#define ACTUALTEXTUREWIDTH 1024 // should always be equal to or larger +#define ACTUALTEXTUREHEIGHT 1024 + +#define GLOBAL_IMAGE_SCALER 3 + +#define ORIGINAL_WIDTH 320 +#define ORIGINAL_HEIGHT 200 + +#define WIDTH ( ORIGINAL_WIDTH * GLOBAL_IMAGE_SCALER ) +#define HEIGHT ( ORIGINAL_HEIGHT * GLOBAL_IMAGE_SCALER ) + +#define TEXTUREWIDTH WIDTH +#define TEXTUREHEIGHT HEIGHT + +#define BASE_WIDTH WIDTH +#define SCREENWIDTH WIDTH +#define SCREENHEIGHT HEIGHT + +#define MAXWIDTH 1120 +#define MAXHEIGHT 832 diff --git a/doomclassic/doom/am_map.cpp b/doomclassic/doom/am_map.cpp new file mode 100644 index 00000000..bd2d29c1 --- /dev/null +++ b/doomclassic/doom/am_map.cpp @@ -0,0 +1,1224 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + + +#include "z_zone.h" +#include "doomdef.h" +#include "st_stuff.h" +#include "p_local.h" +#include "w_wad.h" + +#include "m_cheat.h" +#include "i_system.h" + +// Needs access to LFB. +#include "v_video.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "dstrings.h" + +#include "am_map.h" + + +// For use if I do walls with outsides/insides + +// Automap colors + +// drawing stuff + + + +// scale on entry +// how much the automap moves window per tic in frame-::g->buffer coordinates +// moves 140 pixels in 1 second +// how much zoom-in per tic +// goes to 2x in 1 second +// how much zoom-out per tic +// pulls out to 0.5x in 1 second + +// translates between frame-::g->buffer and map distances +// translates between frame-::g->buffer and map coordinates + +// the following is crap + + + + + + + + +// +// The vector graphics for the automap. +// A line drawing of the player pointing right, +// starting from the middle. +// +#define R ((8*PLAYERRADIUS)/7) +mline_t player_arrow[] = +{ + { { -R+R/8, 0 }, { R, 0 } }, // ----- + { { R, 0 }, { R-R/2, R/4 } }, // -----> + { { R, 0 }, { R-R/2, -R/4 } }, + { { -R+R/8, 0 }, { -R-R/8, R/4 } }, // >----> + { { -R+R/8, 0 }, { -R-R/8, -R/4 } }, + { { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, // >>---> + { { -R+3*R/8, 0 }, { -R+R/8, -R/4 } } +}; +#undef R + +#define R ((8*PLAYERRADIUS)/7) +mline_t cheat_player_arrow[] = +{ + { { -R+R/8, 0 }, { R, 0 } }, // ----- + { { R, 0 }, { R-R/2, R/6 } }, // -----> + { { R, 0 }, { R-R/2, -R/6 } }, + { { -R+R/8, 0 }, { -R-R/8, R/6 } }, // >-----> + { { -R+R/8, 0 }, { -R-R/8, -R/6 } }, + { { -R+3*R/8, 0 }, { -R+R/8, R/6 } }, // >>-----> + { { -R+3*R/8, 0 }, { -R+R/8, -R/6 } }, + { { -R/2, 0 }, { -R/2, -R/6 } }, // >>-d---> + { { -R/2, -R/6 }, { -R/2+R/6, -R/6 } }, + { { -R/2+R/6, -R/6 }, { -R/2+R/6, R/4 } }, + { { -R/6, 0 }, { -R/6, -R/6 } }, // >>-dd--> + { { -R/6, -R/6 }, { 0, -R/6 } }, + { { 0, -R/6 }, { 0, R/4 } }, + { { R/6, R/4 }, { R/6, -R/7 } }, // >>-ddt-> + { { R/6, -R/7 }, { R/6+R/32, -R/7-R/32 } }, + { { R/6+R/32, -R/7-R/32 }, { R/6+R/10, -R/7 } } +}; +#undef R + +#define R (FRACUNIT) +mline_t triangle_guy[] = +{ + { { fixed_t(-.867*R), fixed_t(-.5*R) }, { fixed_t(.867*R), fixed_t(-.5*R) } }, + { { fixed_t(.867*R), fixed_t(-.5*R) } , { 0, R } }, + { { 0, R }, { fixed_t(-.867*R), fixed_t(-.5*R) } } +}; +#undef R + +#define R (FRACUNIT) +mline_t thintriangle_guy[] = +{ + { { fixed_t(-.5*R), fixed_t(-.7*R) }, { R, 0 } }, + { { R, 0 }, { fixed_t(-.5*R), fixed_t(.7*R) } }, + { { fixed_t(-.5*R), fixed_t(.7*R) }, { fixed_t(-.5*R), fixed_t(-.7*R) } } +}; +#undef R + + + + + + + +// location of window on screen + +// size of window on screen + + + + +// +// width/height of window on map (map coords) +// + +// based on level size + + +// based on player size + + + +// old stuff for recovery later + +// old location used by the Follower routine + +// used by MTOF to scale from map-to-frame-::g->buffer coords +// used by FTOM to scale from frame-::g->buffer-to-map coords (=1/::g->scale_mtof) + + + + +const unsigned char cheat_amap_seq[] = +{ + 0xb2, 0x26, 0x26, 0x2e, 0xff +}; +cheatseq_t cheat_amap = cheatseq_t( cheat_amap_seq, 0 ); + + +//extern byte ::g->screens[][SCREENWIDTH*SCREENHEIGHT]; + + + +void +V_MarkRect +( int x, + int y, + int width, + int height ); + +// Calculates the slope and slope according to the x-axis of a line +// segment in map coordinates (with the upright y-axis n' all) so +// that it can be used with the brain-dead drawing stuff. + +void +AM_getIslope +( mline_t* ml, + islope_t* is ) +{ + int dx, dy; + + dy = ml->a.y - ml->b.y; + dx = ml->b.x - ml->a.x; + if (!dy) is->islp = (dx<0?-MAXINT:MAXINT); + else is->islp = FixedDiv(dx, dy); + if (!dx) is->slp = (dy<0?-MAXINT:MAXINT); + else is->slp = FixedDiv(dy, dx); + +} + +// +// +// +void AM_activateNewScale(void) +{ + ::g->m_x += ::g->m_w/2; + ::g->m_y += ::g->m_h/2; + ::g->m_w = FTOM(::g->f_w); + ::g->m_h = FTOM(::g->f_h); + ::g->m_x -= ::g->m_w/2; + ::g->m_y -= ::g->m_h/2; + ::g->m_x2 = ::g->m_x + ::g->m_w; + ::g->m_y2 = ::g->m_y + ::g->m_h; +} + +// +// +// +void AM_saveScaleAndLoc(void) +{ + ::g->old_m_x = ::g->m_x; + ::g->old_m_y = ::g->m_y; + ::g->old_m_w = ::g->m_w; + ::g->old_m_h = ::g->m_h; +} + +// +// +// +void AM_restoreScaleAndLoc(void) +{ + + ::g->m_w = ::g->old_m_w; + ::g->m_h = ::g->old_m_h; + if (!::g->followplayer) + { + ::g->m_x = ::g->old_m_x; + ::g->m_y = ::g->old_m_y; + } else { + ::g->m_x = ::g->amap_plr->mo->x - ::g->m_w/2; + ::g->m_y = ::g->amap_plr->mo->y - ::g->m_h/2; + } + ::g->m_x2 = ::g->m_x + ::g->m_w; + ::g->m_y2 = ::g->m_y + ::g->m_h; + + // Change the scaling multipliers + ::g->scale_mtof = FixedDiv(::g->f_w<m_w); + ::g->scale_ftom = FixedDiv(FRACUNIT, ::g->scale_mtof); +} + +// +// adds a marker at the current location +// +void AM_addMark(void) +{ + ::g->markpoints[::g->markpointnum].x = ::g->m_x + ::g->m_w/2; + ::g->markpoints[::g->markpointnum].y = ::g->m_y + ::g->m_h/2; + ::g->markpointnum = (::g->markpointnum + 1) % AM_NUMMARKPOINTS; + +} + +// +// Determines bounding box of all vertices, +// sets global variables controlling zoom range. +// +void AM_findMinMaxBoundaries(void) +{ + int i; + fixed_t a; + fixed_t b; + + ::g->min_x = ::g->min_y = MAXINT; + ::g->max_x = ::g->max_y = -MAXINT; + + for (i=0; i < ::g->numvertexes; i++) + { + if (::g->vertexes[i].x < ::g->min_x) + ::g->min_x = ::g->vertexes[i].x; + else if (::g->vertexes[i].x > ::g->max_x) + ::g->max_x = ::g->vertexes[i].x; + + if (::g->vertexes[i].y < ::g->min_y) + ::g->min_y = ::g->vertexes[i].y; + else if (::g->vertexes[i].y > ::g->max_y) + ::g->max_y = ::g->vertexes[i].y; + } + + ::g->max_w = ::g->max_x - ::g->min_x; + ::g->max_h = ::g->max_y - ::g->min_y; + + ::g->min_w = 2*PLAYERRADIUS; // const? never changed? + ::g->min_h = 2*PLAYERRADIUS; + + a = FixedDiv(::g->f_w<max_w); + b = FixedDiv(::g->f_h<max_h); + + ::g->min_scale_mtof = a < b ? a : b; + ::g->max_scale_mtof = FixedDiv(::g->f_h<m_paninc.x || ::g->m_paninc.y) + { + ::g->followplayer = 0; + ::g->f_oldloc.x = MAXINT; + } + + ::g->m_x += ::g->m_paninc.x; + ::g->m_y += ::g->m_paninc.y; + + if (::g->m_x + ::g->m_w/2 > ::g->max_x) + ::g->m_x = ::g->max_x - ::g->m_w/2; + else if (::g->m_x + ::g->m_w/2 < ::g->min_x) + ::g->m_x = ::g->min_x - ::g->m_w/2; + + if (::g->m_y + ::g->m_h/2 > ::g->max_y) + ::g->m_y = ::g->max_y - ::g->m_h/2; + else if (::g->m_y + ::g->m_h/2 < ::g->min_y) + ::g->m_y = ::g->min_y - ::g->m_h/2; + + ::g->m_x2 = ::g->m_x + ::g->m_w; + ::g->m_y2 = ::g->m_y + ::g->m_h; +} + + +// +// +// +void AM_initVariables(void) +{ + static event_t st_notify = { ev_keyup, AM_MSGENTERED }; + int pnum; + + ::g->automapactive = true; + ::g->fb = ::g->screens[0]; + + ::g->f_oldloc.x = MAXINT; + ::g->amclock = 0; + ::g->lightlev = 0; + + ::g->m_paninc.x = ::g->m_paninc.y = 0; + ::g->ftom_zoommul = FRACUNIT; + ::g->mtof_zoommul = FRACUNIT; + + ::g->m_w = FTOM(::g->f_w); + ::g->m_h = FTOM(::g->f_h); + + // find player to center on initially + if (!::g->playeringame[pnum = ::g->consoleplayer]) + for (pnum=0;pnumplayeringame[pnum]) + break; + + ::g->amap_plr = &::g->players[pnum]; + ::g->m_x = ::g->amap_plr->mo->x - ::g->m_w/2; + ::g->m_y = ::g->amap_plr->mo->y - ::g->m_h/2; + AM_changeWindowLoc(); + + // for saving & restoring + ::g->old_m_x = ::g->m_x; + ::g->old_m_y = ::g->m_y; + ::g->old_m_w = ::g->m_w; + ::g->old_m_h = ::g->m_h; + + // inform the status bar of the change + ST_Responder(&st_notify); + +} + +// +// +// +void AM_loadPics(void) +{ + int i; + char namebuf[9]; + + for (i=0;i<10;i++) + { + sprintf(namebuf, "AMMNUM%d", i); + ::g->marknums[i] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + } + +} + +void AM_unloadPics(void) +{ +// int i; + +} + +void AM_clearMarks(void) +{ + int i; + + for (i=0;imarkpoints[i].x = -1; // means empty + ::g->markpointnum = 0; +} + +// +// should be called at the start of every level +// right now, i figure it out myself +// +void AM_LevelInit(void) +{ + ::g->leveljuststarted = 0; + + ::g->f_x = ::g->f_y = 0; + ::g->f_w = ::g->finit_width; + ::g->f_h = ::g->finit_height; + + AM_clearMarks(); + + AM_findMinMaxBoundaries(); + ::g->scale_mtof = FixedDiv(::g->min_scale_mtof, (int) (0.7*FRACUNIT)); + if (::g->scale_mtof > ::g->max_scale_mtof) + ::g->scale_mtof = ::g->min_scale_mtof; + ::g->scale_ftom = FixedDiv(FRACUNIT, ::g->scale_mtof); +} + + + + +// +// +// +void AM_Stop (void) +{ + static event_t st_notify = { (evtype_t)0, ev_keyup, AM_MSGEXITED }; + + AM_unloadPics(); + ::g->automapactive = false; + ST_Responder(&st_notify); + ::g->stopped = true; +} + +// +// +// +void AM_Start (void) +{ + + if (!::g->stopped) AM_Stop(); + ::g->stopped = false; + if (::g->lastlevel != ::g->gamemap || ::g->lastepisode != ::g->gameepisode) + { + AM_LevelInit(); + ::g->lastlevel = ::g->gamemap; + ::g->lastepisode = ::g->gameepisode; + } + AM_initVariables(); + AM_loadPics(); +} + +// +// set the window scale to the maximum size +// +void AM_minOutWindowScale(void) +{ + ::g->scale_mtof = ::g->min_scale_mtof; + ::g->scale_ftom = FixedDiv(FRACUNIT, ::g->scale_mtof); + AM_activateNewScale(); +} + +// +// set the window scale to the minimum size +// +void AM_maxOutWindowScale(void) +{ + ::g->scale_mtof = ::g->max_scale_mtof; + ::g->scale_ftom = FixedDiv(FRACUNIT, ::g->scale_mtof); + AM_activateNewScale(); +} + + +// +// Handle ::g->events (user inputs) in automap mode +// +qboolean +AM_Responder +( event_t* ev ) +{ + + int rc; + rc = false; + + if (!::g->automapactive) + { + if (ev->type == ev_keydown && ev->data1 == AM_STARTKEY) + { + AM_Start (); + ::g->viewactive = false; + rc = true; + } + } + + else if (ev->type == ev_keydown) + { + + rc = true; + switch(ev->data1) + { + case AM_PANRIGHTKEY: // pan right + if (!::g->followplayer) ::g->m_paninc.x = FTOM(F_PANINC); + else rc = false; + break; + case AM_PANLEFTKEY: // pan left + if (!::g->followplayer) ::g->m_paninc.x = -FTOM(F_PANINC); + else rc = false; + break; + case AM_PANUPKEY: // pan up + if (!::g->followplayer) ::g->m_paninc.y = FTOM(F_PANINC); + else rc = false; + break; + case AM_PANDOWNKEY: // pan down + if (!::g->followplayer) ::g->m_paninc.y = -FTOM(F_PANINC); + else rc = false; + break; + case AM_ZOOMOUTKEY: // zoom out + ::g->mtof_zoommul = M_ZOOMOUT; + ::g->ftom_zoommul = M_ZOOMIN; + break; + case AM_ZOOMINKEY: // zoom in + ::g->mtof_zoommul = M_ZOOMIN; + ::g->ftom_zoommul = M_ZOOMOUT; + break; + case AM_ENDKEY: + ::g->bigstate = 0; + ::g->viewactive = true; + AM_Stop (); + break; + case AM_GOBIGKEY: + ::g->bigstate = !::g->bigstate; + if (::g->bigstate) + { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } + else AM_restoreScaleAndLoc(); + break; + case AM_FOLLOWKEY: + ::g->followplayer = !::g->followplayer; + ::g->f_oldloc.x = MAXINT; + ::g->amap_plr->message = ::g->followplayer ? AMSTR_FOLLOWON : AMSTR_FOLLOWOFF; + break; + case AM_GRIDKEY: + ::g->grid = !::g->grid; + ::g->amap_plr->message = ::g->grid ? AMSTR_GRIDON : AMSTR_GRIDOFF; + break; + case AM_MARKKEY: + sprintf(::g->buffer, "%s %d", AMSTR_MARKEDSPOT, ::g->markpointnum); + ::g->amap_plr->message = ::g->buffer; + AM_addMark(); + break; + case AM_CLEARMARKKEY: + AM_clearMarks(); + ::g->amap_plr->message = AMSTR_MARKSCLEARED; + break; + default: + ::g->cheatstate=0; + rc = false; + } + if (!::g->deathmatch && cht_CheckCheat(&cheat_amap, ev->data1)) + { + rc = false; + ::g->cheating = (::g->cheating+1) % 3; + } + } + + else if (ev->type == ev_keyup) + { + rc = false; + switch (ev->data1) + { + case AM_PANRIGHTKEY: + if (!::g->followplayer) ::g->m_paninc.x = 0; + break; + case AM_PANLEFTKEY: + if (!::g->followplayer) ::g->m_paninc.x = 0; + break; + case AM_PANUPKEY: + if (!::g->followplayer) ::g->m_paninc.y = 0; + break; + case AM_PANDOWNKEY: + if (!::g->followplayer) ::g->m_paninc.y = 0; + break; + case AM_ZOOMOUTKEY: + case AM_ZOOMINKEY: + ::g->mtof_zoommul = FRACUNIT; + ::g->ftom_zoommul = FRACUNIT; + break; + } + } + + return rc; + +} + + +// +// Zooming +// +void AM_changeWindowScale(void) +{ + + // Change the scaling multipliers + ::g->scale_mtof = FixedMul(::g->scale_mtof, ::g->mtof_zoommul); + ::g->scale_ftom = FixedDiv(FRACUNIT, ::g->scale_mtof); + + if (::g->scale_mtof < ::g->min_scale_mtof) + AM_minOutWindowScale(); + else if (::g->scale_mtof > ::g->max_scale_mtof) + AM_maxOutWindowScale(); + else + AM_activateNewScale(); +} + + +// +// +// +void AM_doFollowPlayer(void) +{ + + if (::g->f_oldloc.x != ::g->amap_plr->mo->x || ::g->f_oldloc.y != ::g->amap_plr->mo->y) + { + ::g->m_x = FTOM(MTOF(::g->amap_plr->mo->x)) - ::g->m_w/2; + ::g->m_y = FTOM(MTOF(::g->amap_plr->mo->y)) - ::g->m_h/2; + ::g->m_x2 = ::g->m_x + ::g->m_w; + ::g->m_y2 = ::g->m_y + ::g->m_h; + ::g->f_oldloc.x = ::g->amap_plr->mo->x; + ::g->f_oldloc.y = ::g->amap_plr->mo->y; + + // ::g->m_x = FTOM(MTOF(::g->amap_plr->mo->x - ::g->m_w/2)); + // ::g->m_y = FTOM(MTOF(::g->amap_plr->mo->y - ::g->m_h/2)); + // ::g->m_x = ::g->amap_plr->mo->x - ::g->m_w/2; + // ::g->m_y = ::g->amap_plr->mo->y - ::g->m_h/2; + + } + +} + +// +// +// +void AM_updateLightLev(void) +{ + //static int litelevels[] = { 0, 3, 5, 6, 6, 7, 7, 7 }; + const static int litelevels[] = { 0, 4, 7, 10, 12, 14, 15, 15 }; + + // Change light level + if (::g->amclock>::g->nexttic) + { + ::g->lightlev = litelevels[::g->litelevelscnt++]; + if (::g->litelevelscnt == sizeof(litelevels)/sizeof(int)) ::g->litelevelscnt = 0; + ::g->nexttic = ::g->amclock + 6 - (::g->amclock % 6); + } + +} + + +// +// Updates on Game Tick +// +void AM_Ticker (void) +{ + + if (!::g->automapactive) + return; + + ::g->amclock++; + + if (::g->followplayer) + AM_doFollowPlayer(); + + // Change the zoom if necessary + if (::g->ftom_zoommul != FRACUNIT) + AM_changeWindowScale(); + + // Change x,y location + if (::g->m_paninc.x || ::g->m_paninc.y) + AM_changeWindowLoc(); + + // Update light level + // AM_updateLightLev(); + +} + + +// +// Clear automap frame ::g->buffer. +// +void AM_clearFB(int color) +{ + memset(::g->fb, color, ::g->f_w*::g->f_h); +} + + +// +// Automap clipping of ::g->lines. +// +// Based on Cohen-Sutherland clipping algorithm but with a slightly +// faster reject and precalculated slopes. If the speed is needed, +// use a hash algorithm to handle the common cases. +// +qboolean +AM_clipMline +( mline_t* ml, + fline_t* fl ) +{ + enum + { + LEFT =1, + RIGHT =2, + BOTTOM =4, + TOP =8 + }; + + register int outcode1 = 0; + register int outcode2 = 0; + register int outside; + + fpoint_t tmp = { 0, 0 }; + int dx; + int dy; + + + + + // do trivial rejects and outcodes + if (ml->a.y > ::g->m_y2) + outcode1 = TOP; + else if (ml->a.y < ::g->m_y) + outcode1 = BOTTOM; + + if (ml->b.y > ::g->m_y2) + outcode2 = TOP; + else if (ml->b.y < ::g->m_y) + outcode2 = BOTTOM; + + if (outcode1 & outcode2) + return false; // trivially outside + + if (ml->a.x < ::g->m_x) + outcode1 |= LEFT; + else if (ml->a.x > ::g->m_x2) + outcode1 |= RIGHT; + + if (ml->b.x < ::g->m_x) + outcode2 |= LEFT; + else if (ml->b.x > ::g->m_x2) + outcode2 |= RIGHT; + + if (outcode1 & outcode2) + return false; // trivially outside + + // transform to frame-::g->buffer coordinates. + fl->a.x = CXMTOF(ml->a.x); + fl->a.y = CYMTOF(ml->a.y); + fl->b.x = CXMTOF(ml->b.x); + fl->b.y = CYMTOF(ml->b.y); + + DOOUTCODE(outcode1, fl->a.x, fl->a.y); + DOOUTCODE(outcode2, fl->b.x, fl->b.y); + + if (outcode1 & outcode2) + return false; + + while (outcode1 | outcode2) + { + // may be partially inside box + // find an outside point + if (outcode1) + outside = outcode1; + else + outside = outcode2; + + // clip to each side + if (outside & TOP) + { + dy = fl->a.y - fl->b.y; + dx = fl->b.x - fl->a.x; + tmp.x = fl->a.x + (dx*(fl->a.y))/dy; + tmp.y = 0; + } + else if (outside & BOTTOM) + { + dy = fl->a.y - fl->b.y; + dx = fl->b.x - fl->a.x; + tmp.x = fl->a.x + (dx*(fl->a.y-::g->f_h))/dy; + tmp.y = ::g->f_h-1; + } + else if (outside & RIGHT) + { + dy = fl->b.y - fl->a.y; + dx = fl->b.x - fl->a.x; + tmp.y = fl->a.y + (dy*(::g->f_w-1 - fl->a.x))/dx; + tmp.x = ::g->f_w-1; + } + else if (outside & LEFT) + { + dy = fl->b.y - fl->a.y; + dx = fl->b.x - fl->a.x; + tmp.y = fl->a.y + (dy*(-fl->a.x))/dx; + tmp.x = 0; + } + + if (outside == outcode1) + { + fl->a = tmp; + DOOUTCODE(outcode1, fl->a.x, fl->a.y); + } + else + { + fl->b = tmp; + DOOUTCODE(outcode2, fl->b.x, fl->b.y); + } + + if (outcode1 & outcode2) + return false; // trivially outside + } + + return true; +} +#undef DOOUTCODE + + +// +// Classic Bresenham w/ whatever optimizations needed for speed +// +void +AM_drawFline +( fline_t* fl, + int color ) +{ + register int x; + register int y; + register int dx; + register int dy; + register int sx; + register int sy; + register int ax; + register int ay; + register int d; + + static int fuck = 0; + + // For debugging only + if ( fl->a.x < 0 || fl->a.x >= ::g->f_w + || fl->a.y < 0 || fl->a.y >= ::g->f_h + || fl->b.x < 0 || fl->b.x >= ::g->f_w + || fl->b.y < 0 || fl->b.y >= ::g->f_h) + { + I_PrintfE("fuck %d \r", fuck++); + return; + } + + + dx = fl->b.x - fl->a.x; + ax = 2 * (dx<0 ? -dx : dx); + sx = dx<0 ? -1 : 1; + + dy = fl->b.y - fl->a.y; + ay = 2 * (dy<0 ? -dy : dy); + sy = dy<0 ? -1 : 1; + + x = fl->a.x; + y = fl->a.y; + + if (ax > ay) + { + d = ay - ax/2; + while (1) + { + PUTDOT(x,y,color); + if (x == fl->b.x) return; + if (d>=0) + { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } + else + { + d = ax - ay/2; + while (1) + { + PUTDOT(x, y, color); + if (y == fl->b.y) return; + if (d >= 0) + { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + + +// +// Clip ::g->lines, draw visible part sof ::g->lines. +// +void +AM_drawMline +( mline_t* ml, + int color ) +{ + static fline_t fl; + + if (AM_clipMline(ml, &fl)) + AM_drawFline(&fl, color); // draws it on frame ::g->buffer using ::g->fb coords +} + + + +// +// Draws flat (floor/ceiling tile) aligned ::g->grid ::g->lines. +// +void AM_drawGrid(int color) +{ + fixed_t x, y; + fixed_t start, end; + mline_t ml; + + // Figure out start of vertical gridlines + start = ::g->m_x; + if ((start-::g->bmaporgx)%(MAPBLOCKUNITS<bmaporgx)%(MAPBLOCKUNITS<m_x + ::g->m_w; + + // draw vertical gridlines + ml.a.y = ::g->m_y; + ml.b.y = ::g->m_y+::g->m_h; + for (x=start; xm_y; + if ((start-::g->bmaporgy)%(MAPBLOCKUNITS<bmaporgy)%(MAPBLOCKUNITS<m_y + ::g->m_h; + + // draw horizontal gridlines + ml.a.x = ::g->m_x; + ml.b.x = ::g->m_x + ::g->m_w; + for (y=start; ylines, draws them. +// This is LineDef based, not LineSeg based. +// +void AM_drawWalls(void) +{ + int i; + static mline_t l; + + for (i = 0; i < ::g->numlines; i++) + { + l.a.x = ::g->lines[i].v1->x; + l.a.y = ::g->lines[i].v1->y; + l.b.x = ::g->lines[i].v2->x; + l.b.y = ::g->lines[i].v2->y; + if (::g->cheating || (::g->lines[i].flags & ML_MAPPED)) + { + if ((::g->lines[i].flags & LINE_NEVERSEE) && !::g->cheating) + continue; + if (!::g->lines[i].backsector) + { + AM_drawMline(&l, WALLCOLORS+::g->lightlev); + } + else + { + if (::g->lines[i].special == 39) + { // teleporters + AM_drawMline(&l, WALLCOLORS+WALLRANGE/2); + } + else if (::g->lines[i].flags & ML_SECRET) // secret door + { + if (::g->cheating) AM_drawMline(&l, SECRETWALLCOLORS + ::g->lightlev); + else AM_drawMline(&l, WALLCOLORS+::g->lightlev); + } + else if (::g->lines[i].backsector->floorheight + != ::g->lines[i].frontsector->floorheight) { + AM_drawMline(&l, FDWALLCOLORS + ::g->lightlev); // floor level change + } + else if (::g->lines[i].backsector->ceilingheight + != ::g->lines[i].frontsector->ceilingheight) { + AM_drawMline(&l, CDWALLCOLORS+::g->lightlev); // ceiling level change + } + else if (::g->cheating) { + AM_drawMline(&l, TSWALLCOLORS+::g->lightlev); + } + } + } + else if (::g->amap_plr->powers[pw_allmap]) + { + if (!(::g->lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3); + } + } +} + + +// +// Rotation in 2D. +// Used to rotate player arrow line character. +// +void +AM_rotate +( fixed_t* x, + fixed_t* y, + angle_t a ) +{ + fixed_t tmpx; + + tmpx = + FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT]) + - FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]); + + *y = + FixedMul(*x,finesine[a>>ANGLETOFINESHIFT]) + + FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]); + + *x = tmpx; +} + +void +AM_drawLineCharacter +( mline_t* lineguy, + int lineguylines, + fixed_t scale, + angle_t angle, + int color, + fixed_t x, + fixed_t y ) +{ + int i; + mline_t l; + + for (i=0;inetgame) + { + if (::g->cheating) + AM_drawLineCharacter + (cheat_player_arrow, NUMCHEATPLYRLINES, 0, + ::g->amap_plr->mo->angle, WHITE, ::g->amap_plr->mo->x, ::g->amap_plr->mo->y); + else + AM_drawLineCharacter + (player_arrow, NUMPLYRLINES, 0, ::g->amap_plr->mo->angle, + WHITE, ::g->amap_plr->mo->x, ::g->amap_plr->mo->y); + return; + } + + for (i=0;iplayers[i]; + + if ( (::g->deathmatch && !::g->singledemo) && p != ::g->amap_plr) + continue; + + if (!::g->playeringame[i]) + continue; + + if (p->powers[pw_invisibility]) + color = 246; // *close* to black + else + color = their_colors[their_color]; + + AM_drawLineCharacter + (player_arrow, NUMPLYRLINES, 0, p->mo->angle, + color, p->mo->x, p->mo->y); + } + +} + +void +AM_drawThings +( int colors, + int colorrange) +{ + int i; + mobj_t* t; + + for (i = 0; i < ::g->numsectors; i++) + { + t = ::g->sectors[i].thinglist; + while (t) + { + AM_drawLineCharacter + (thintriangle_guy, NUMTHINTRIANGLEGUYLINES, + 16<angle, colors+::g->lightlev, t->x, t->y); + t = t->snext; + } + } +} + +void AM_drawMarks(void) +{ + int i, fx, fy, w, h; + + for (i=0;imarkpoints[i].x != -1) + { + // w = SHORT(::g->marknums[i]->width); + // h = SHORT(::g->marknums[i]->height); + w = 5; // because something's wrong with the wad, i guess + h = 6; // because something's wrong with the wad, i guess + fx = CXMTOF(::g->markpoints[i].x); + fy = CYMTOF(::g->markpoints[i].y); + if (fx >= ::g->f_x && fx <= ::g->f_w - w && fy >= ::g->f_y && fy <= ::g->f_h - h) + V_DrawPatch(fx/GLOBAL_IMAGE_SCALER, fy/GLOBAL_IMAGE_SCALER, FB, ::g->marknums[i]); + } + } + +} + +void AM_drawCrosshair(int color) +{ + ::g->fb[(::g->f_w*(::g->f_h+1))/2] = color; // single point for now + +} + +void AM_Drawer (void) +{ + if (!::g->automapactive) return; + + AM_clearFB(BACKGROUND); + if (::g->grid) + AM_drawGrid(GRIDCOLORS); + AM_drawWalls(); + AM_drawPlayers(); + if (::g->cheating==2) + AM_drawThings(THINGCOLORS, THINGRANGE); + AM_drawCrosshair(XHAIRCOLORS); + + AM_drawMarks(); + + V_MarkRect(::g->f_x, ::g->f_y, ::g->f_w, ::g->f_h); + +} + diff --git a/doomclassic/doom/am_map.h b/doomclassic/doom/am_map.h new file mode 100644 index 00000000..eae4e08d --- /dev/null +++ b/doomclassic/doom/am_map.h @@ -0,0 +1,55 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AMMAP_H__ +#define __AMMAP_H__ + +// Used by ST StatusBar stuff. +#define AM_MSGHEADER (('a'<<24)+('m'<<16)) +#define AM_MSGENTERED (AM_MSGHEADER | ('e'<<8)) +#define AM_MSGEXITED (AM_MSGHEADER | ('x'<<8)) + + +// Called by main loop. +qboolean AM_Responder (event_t* ev); + +// Called by main loop. +void AM_Ticker (void); + +// Called by main loop, +// called instead of view drawer if automap active. +void AM_Drawer (void); + +// Called to force the automap to quit +// if the level is completed while it is up. +void AM_Stop (void); + + + +#endif + diff --git a/doomclassic/doom/constructs.h b/doomclassic/doom/constructs.h new file mode 100644 index 00000000..109f8fe4 --- /dev/null +++ b/doomclassic/doom/constructs.h @@ -0,0 +1,540 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + memset(::g, 0, sizeof(*::g)); +// am_map.constructs begin // + ::g->cheating = 0; + ::g->grid = 0; + ::g->leveljuststarted = 1; // kluge until AM_LevelInit() is called + ::g->automapactive = false; + ::g->finit_width = SCREENWIDTH; + ::g->finit_height = SCREENHEIGHT - (32 * GLOBAL_IMAGE_SCALER); + ::g->scale_mtof = (fixed_t)INITSCALEMTOF; + ::g->markpointnum = 0; // next point to be assigned + ::g->followplayer = 1; // specifies whether to follow the player around + ::g->stopped = true; + ::g->lastlevel = -1; + ::g->lastepisode = -1; + ::g->cheatstate=0; + ::g->bigstate=0; + ::g->nexttic = 0; + ::g->litelevelscnt = 0; +// am_map.constructs end // +// doomstat.constructs begin // + ::g->gamemode = indetermined; + ::g->gamemission = doom; + ::g->language = english; +// doomstat.constructs end // +// d_main.constructs begin // + ::g->singletics = false; // debug flag to cancel adaptiveness + ::g->oldgamestate = (gamestate_t)-1; + ::g->wipegamestate = GS_DEMOSCREEN; + ::g->viewactivestate = false; + ::g->menuactivestate = false; + ::g->inhelpscreensstate = false; + ::g->fullscreen = false; + ::g->wipe = false; + ::g->wipedone = true; +// d_main.constructs end // +// d_net.constructs begin // +doomcom_t temp_doomcom = { + 0 +}; +memcpy( &::g->doomcom, &temp_doomcom, sizeof(temp_doomcom) ); +// d_net.constructs end // +// f_wipe.constructs begin // + ::g->go = 0; +// f_wipe.constructs end // +// g_game.constructs begin // + ::g->precache = true; // if true, load all graphics at start +fixed_t temp_forwardmove[2] = { +0x19, 0x32 +}; +memcpy( ::g->forwardmove, temp_forwardmove, sizeof(temp_forwardmove) ); +fixed_t temp_sidemove[2] = { +0x18, 0x28 +}; +memcpy( ::g->sidemove, temp_sidemove, sizeof(temp_sidemove) ); +fixed_t temp_angleturn[3] = { +640, 1280, 320 // + slow turn +}; +memcpy( ::g->angleturn, temp_angleturn, sizeof(temp_angleturn) ); + ::g->mousebuttons = &::g->mousearray[1]; // allow [-1] + ::g->joybuttons = &::g->joyarray[1]; // allow [-1] +// g_game.constructs end // +// hu_lib.constructs begin // + ::g->lastautomapactive = true; +// hu_lib.constructs end // +// hu_stuff.constructs begin // + ::g->always_off = false; + ::g->headsupactive = false; + ::g->head = 0; + ::g->tail = 0; + ::g->shiftdown = false; + ::g->altdown = false; + ::g->num_nobrainers = 0; +// hu_stuff.constructs end // +// i_input.constructs begin // +// i_input.constructs end // +// i_system.constructs begin // + ::g->mb_used = 2; + ::g->current_time = 0; +// i_system.constructs end // +// m_cheat.constructs begin // + ::g->firsttime = 1; + ::g->usedcheatbuffer = 0; +// m_cheat.constructs end // +// m_menu.constructs begin // + +menuitem_t temp_QuitMenu[3] = { + {1,"M_ACPT", M_ExitGame,'a'}, + {1,"M_CAN", M_CancelExit,'c'}, + {1,"M_CHG", M_GameSelection,'g'} +}; +memcpy( ::g->QuitMenu, temp_QuitMenu, sizeof(temp_QuitMenu) ); +menu_t temp_QuitDef = { + qut_end, // # of menu items + &::g->MainDef, // previous menu + ::g->QuitMenu, // menuitem_t -> + M_DrawQuit, // drawing routine -> + 48,63, // x,y + g_accept // lastOn +}; +memcpy( &::g->QuitDef, &temp_QuitDef, sizeof(temp_QuitDef) ); + +menuitem_t temp_MainMenu[5]= +{ + {1,"M_NGAME",M_NewGame,'n'}, + {1,"M_OPTION",M_Options,'o'}, + {1,"M_LOADG",M_LoadGame,'l'}, + {1,"M_SAVEG",M_SaveGame,'m'}, + // Another hickup with Special edition. + //{1,"M_RDTHIS",M_ReadThis,'r'}, + {1,"M_QUITG",M_QuitDOOM,'q'} +}; +memcpy( &::g->MainMenu, temp_MainMenu, sizeof(temp_MainMenu) ); +menu_t temp_MainDef = { + main_end, + NULL, + ::g->MainMenu, + M_DrawMainMenu, + 97,64, + 0 +}; + + +memcpy( &::g->MainDef, &temp_MainDef, sizeof(temp_MainDef) ); +menuitem_t temp_EpisodeMenu[4] = { + {1,"M_EPI1", M_Episode,'k'}, + {1,"M_EPI2", M_Episode,'t'}, + {1,"M_EPI3", M_Episode,'i'}, + {1,"M_EPI4", M_Episode,'t'} +}; +memcpy( ::g->EpisodeMenu, temp_EpisodeMenu, sizeof(temp_EpisodeMenu) ); +menu_t temp_EpiDef = { + ep_end, // # of menu items + &::g->MainDef, // previous menu + ::g->EpisodeMenu, // menuitem_t -> + M_DrawEpisode, // drawing routine -> + 48,63, // x,y + ep1 // lastOn +}; +memcpy( &::g->EpiDef, &temp_EpiDef, sizeof(temp_EpiDef) ); + +menuitem_t temp_ExpansionMenu[2] = { + {1,"M_EPI1", M_Expansion,'h'}, + {1,"M_EPI2", M_Expansion,'n'}, +}; +memcpy( ::g->ExpansionMenu, temp_ExpansionMenu, sizeof(temp_ExpansionMenu) ); +menu_t temp_ExpDef = { + ex_end, // # of menu items + &::g->MainDef, // previous menu + ::g->ExpansionMenu, // menuitem_t -> + M_DrawEpisode, // drawing routine -> + 48,63, // x,y + ex1 // lastOn +}; +memcpy( &::g->ExpDef, &temp_ExpDef, sizeof(temp_ExpDef) ); + +menuitem_t temp_LoadExpMenu[2] = { + {1,"M_EPI1", M_LoadExpansion,'h'}, + {1,"M_EPI2", M_LoadExpansion,'n'}, +}; +memcpy( ::g->LoadExpMenu, temp_LoadExpMenu, sizeof(temp_LoadExpMenu) ); +menu_t temp_LoadExpDef = { + ex_end, // # of menu items + &::g->MainDef, // previous menu + ::g->LoadExpMenu, // menuitem_t -> + M_DrawEpisode, // drawing routine -> + 48,63, // x,y + ex1 // lastOn +}; +memcpy( &::g->LoadExpDef, &temp_LoadExpDef, sizeof(temp_LoadExpDef) ); + +menuitem_t temp_NewGameMenu[5] = { + {1,"M_JKILL", M_ChooseSkill, 'i'}, + {1,"M_ROUGH", M_ChooseSkill, 'h'}, + {1,"M_HURT", M_ChooseSkill, 'h'}, + {1,"M_ULTRA", M_ChooseSkill, 'u'}, + {1,"M_NMARE", M_ChooseSkill, 'n'} +}; +memcpy( ::g->NewGameMenu, temp_NewGameMenu, sizeof(temp_NewGameMenu) ); +menu_t temp_NewDef = { + newg_end, // # of menu items + &::g->EpiDef, // previous menu + ::g->NewGameMenu, // menuitem_t -> + M_DrawNewGame, // drawing routine -> + 48,63, // x,y + hurtme // lastOn +}; +memcpy( &::g->NewDef, &temp_NewDef, sizeof(temp_NewDef) ); +menuitem_t temp_OptionsMenu[8] = { + {1,"M_GDHIGH", M_FullScreen,'f'}, + {1,"M_SCRNSZ", M_ChangeGPad,'m'}, + {1,"M_MESSG", M_ChangeMessages,'m'}, + //{1,"M_DETAIL", M_ChangeDetail,'g'}, + //{2,"M_SCRNSZ", M_SizeDisplay,'s'}, + {-1,"",0}, + {2,"M_MSENS", M_ChangeSensitivity,'m'}, + {-1,"",0}, + {1,"M_SVOL", M_Sound,'s'} +}; +memcpy( ::g->OptionsMenu, temp_OptionsMenu, sizeof(temp_OptionsMenu) ); +menu_t temp_OptionsDef = { + opt_end, + &::g->MainDef, + ::g->OptionsMenu, + M_DrawOptions, + 60,37, + 0 +}; +memcpy( &::g->OptionsDef, &temp_OptionsDef, sizeof(temp_OptionsDef) ); +menuitem_t temp_SoundMenu[4] = { + {2,"M_SFXVOL",M_SfxVol,'s'}, + {-1,"",0}, + {2,"M_MUSVOL",M_MusicVol,'m'}, + {-1,"",0} +}; +memcpy( ::g->SoundMenu, temp_SoundMenu, sizeof(temp_SoundMenu) ); +menu_t temp_SoundDef = { + sound_end, + &::g->OptionsDef, + ::g->SoundMenu, + M_DrawSound, + 80,64, + 0 +}; +memcpy( &::g->SoundDef, &temp_SoundDef, sizeof(temp_SoundDef) ); +menuitem_t temp_LoadMenu[6] = { + {1,"", M_LoadSelect,'1'}, + {1,"", M_LoadSelect,'2'}, + {1,"", M_LoadSelect,'3'}, + {1,"", M_LoadSelect,'4'}, + {1,"", M_LoadSelect,'5'}, + {1,"", M_LoadSelect,'6'} +}; +memcpy( ::g->LoadMenu, temp_LoadMenu, sizeof(temp_LoadMenu) ); +menu_t temp_LoadDef = { + load_end, + &::g->MainDef, + ::g->LoadMenu, + M_DrawLoad, + 80,54, + 0 +}; +memcpy( &::g->LoadDef, &temp_LoadDef, sizeof(temp_LoadDef) ); +menuitem_t temp_SaveMenu[6] = { + {1,"", M_SaveSelect,'1'}, + {1,"", M_SaveSelect,'2'}, + {1,"", M_SaveSelect,'3'}, + {1,"", M_SaveSelect,'4'}, + {1,"", M_SaveSelect,'5'}, + {1,"", M_SaveSelect,'6'} +}; +memcpy( ::g->SaveMenu, temp_SaveMenu, sizeof(temp_SaveMenu) ); +menu_t temp_SaveDef = { + load_end, + &::g->MainDef, + ::g->SaveMenu, + M_DrawSave, + 80,54, + 0 +}; +memcpy( &::g->SaveDef, &temp_SaveDef, sizeof(temp_SaveDef) ); +int temp_quitsounds[8] = { + sfx_pldeth, + sfx_dmpain, + sfx_popain, + sfx_slop, + sfx_telept, + sfx_posit1, + sfx_posit3, + sfx_sgtatk +}; +memcpy( ::g->quitsounds, temp_quitsounds, sizeof(temp_quitsounds) ); +int temp_quitsounds2[8] = { + sfx_vilact, + sfx_getpow, + sfx_boscub, + sfx_slop, + sfx_skeswg, + sfx_kntdth, + sfx_bspact, + sfx_sgtatk +}; +memcpy( ::g->quitsounds2, temp_quitsounds2, sizeof(temp_quitsounds2) ); + ::g->joywait = 0; + ::g->mousewait = 0; + ::g->mmenu_mousey = 0; + ::g->lasty = 0; + ::g->mmenu_mousex = 0; + ::g->lastx = 0; +// m_menu.constructs end // +// m_misc.constructs begin // + ::g->g_pszSaveFile = "\\save.dat"; + ::g->g_pszImagePath = "d:\\saveimage.xbx"; + ::g->g_pszImageMeta = "saveimage.xbx"; + extern const char* const temp_chat_macros[]; + for (int i = 0; i < 10; i++) + { + chat_macros[i] = temp_chat_macros[i]; + } +default_t temp_defaults[35] = { + default_t( "mouse_sensitivity",&::g->mouseSensitivity, 7 ), + + default_t( "show_messages",&::g->showMessages, 1 ), + + default_t( "key_right",&::g->key_right, KEY_RIGHTARROW ), + default_t( "key_left",&::g->key_left, KEY_LEFTARROW ), + default_t( "key_up",&::g->key_up, KEY_UPARROW ), + default_t( "key_down",&::g->key_down, KEY_DOWNARROW ), + default_t( "key_strafeleft",&::g->key_strafeleft, ',' ), + default_t( "key_straferight",&::g->key_straferight, '.' ), + + default_t( "key_fire",&::g->key_fire, KEY_RCTRL ), + default_t( "key_use",&::g->key_use, ' ' ), + default_t( "key_strafe",&::g->key_strafe, KEY_RALT ), + default_t( "key_speed",&::g->key_speed, KEY_RSHIFT ), + + default_t( "use_mouse",&::g->usemouse, 1 ), + default_t( "mouseb_fire",&::g->mousebfire,0 ), + default_t( "mouseb_strafe",&::g->mousebstrafe,1 ), + default_t( "mouseb_forward",&::g->mousebforward,2 ), + + default_t( "use_joystick",&::g->usejoystick, 0 ), + default_t( "joyb_fire",&::g->joybfire,0 ), + default_t( "joyb_strafe",&::g->joybstrafe,1 ), + default_t( "joyb_use",&::g->joybuse,3 ), + default_t( "joyb_speed",&::g->joybspeed,2 ), + + default_t( "screenblocks",&::g->screenblocks, 10 ), + default_t( "detaillevel",&::g->detailLevel, 0 ), + + default_t( "snd_channels",&::g->numChannels, S_NUMCHANNELS ), + + + + default_t( "usegamma",&::g->usegamma, 0 ), + + default_t( "chatmacro0", &::g->chat_macros[0], HUSTR_CHATMACRO0 ), + default_t( "chatmacro1", &::g->chat_macros[1], HUSTR_CHATMACRO1 ), + default_t( "chatmacro2", &::g->chat_macros[2], HUSTR_CHATMACRO2 ), + default_t( "chatmacro3", &::g->chat_macros[3], HUSTR_CHATMACRO3 ), + default_t( "chatmacro4", &::g->chat_macros[4], HUSTR_CHATMACRO4 ), + default_t( "chatmacro5", &::g->chat_macros[5], HUSTR_CHATMACRO5 ), + default_t( "chatmacro6", &::g->chat_macros[6], HUSTR_CHATMACRO6 ), + default_t( "chatmacro7", &::g->chat_macros[7], HUSTR_CHATMACRO7 ), + default_t( "chatmacro8", &::g->chat_macros[8], HUSTR_CHATMACRO8 ), + default_t( "chatmacro9", &::g->chat_macros[9], HUSTR_CHATMACRO9 ) + +}; +memcpy( ::g->defaults, temp_defaults, sizeof(temp_defaults) ); +// m_misc.constructs end // +// m_random.constructs begin // + ::g->rndindex = 0; + ::g->prndindex = 0; +// m_random.constructs end // +// p_enemy.constructs begin // + ::g->TRACEANGLE = 0xc000000; + ::g->easy = 0; +// p_enemy.constructs end // +// r_bsp.constructs begin // +int temp_checkcoord[12][4] = { + {3,0,2,1}, + {3,0,2,0}, + {3,1,2,0}, + {0}, + {2,0,2,1}, + {0,0,0,0}, + {3,1,3,0}, + {0}, + {2,0,3,1}, + {2,1,3,1}, + {2,1,3,0} +}; +memcpy( ::g->checkcoord, temp_checkcoord, sizeof(temp_checkcoord) ); +// r_bsp.constructs end // +// r_draw.constructs begin // +int temp_fuzzoffset[FUZZTABLE] = { + FUZZOFF,-FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF, + FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF, + FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF, + FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF, + FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF,FUZZOFF,-FUZZOFF,FUZZOFF +}; +memcpy( ::g->fuzzoffset, temp_fuzzoffset, sizeof(temp_fuzzoffset) ); + ::g->fuzzpos = 0; +// r_draw.constructs end // +// r_main.constructs begin // + ::g->validcount = 1; +// r_main.constructs end // +// sounds.constructs begin // +musicinfo_t temp_S_music[80] = { + { 0 }, + { "e1m1", 0 }, + { "e1m2", 0 }, + { "e1m3", 0 }, + { "e1m4", 0 }, + { "e1m5", 0 }, + { "e1m6", 0 }, + { "e1m7", 0 }, + { "e1m8", 0 }, + { "e1m9", 0 }, + { "e2m1", 0 }, + { "e2m2", 0 }, + { "e2m3", 0 }, + { "e2m4", 0 }, + { "e2m5", 0 }, + { "e2m6", 0 }, + { "e2m7", 0 }, + { "e2m8", 0 }, + { "e2m9", 0 }, + { "e3m1", 0 }, + { "e3m2", 0 }, + { "e3m3", 0 }, + { "e3m4", 0 }, + { "e3m5", 0 }, + { "e3m6", 0 }, + { "e3m7", 0 }, + { "e3m8", 0 }, + { "e3m9", 0 }, + { "inter", 0 }, + { "intro", 0 }, + { "bunny", 0 }, + { "victor", 0 }, + { "introa", 0 }, + { "runnin", 0 }, + { "stalks", 0 }, + { "countd", 0 }, + { "betwee", 0 }, + { "doom", 0 }, + { "the_da", 0 }, + { "shawn", 0 }, + { "ddtblu", 0 }, + { "in_cit", 0 }, + { "dead", 0 }, + { "stlks2", 0 }, + { "theda2", 0 }, + { "doom2", 0 }, + { "ddtbl2", 0 }, + { "runni2", 0 }, + { "dead2", 0 }, + { "stlks3", 0 }, + { "romero", 0 }, + { "shawn2", 0 }, + { "messag", 0 }, + { "count2", 0 }, + { "ddtbl3", 0 }, + { "ampie", 0 }, + { "theda3", 0 }, + { "adrian", 0 }, + { "messg2", 0 }, + { "romer2", 0 }, + { "tense", 0 }, + { "shawn3", 0 }, + { "openin", 0 }, + { "evil", 0 }, + { "ultima", 0 }, + { "read_m", 0 }, + { "dm2ttl", 0 }, + { "dm2int", 0 } +}; +memcpy( ::g->S_music, temp_S_music, sizeof(temp_S_music) ); +// sounds.constructs end // +// st_stuff.constructs begin // + ::g->veryfirsttime = 1; + ::g->st_msgcounter=0; + ::g->st_oldhealth = -1; + ::g->st_facecount = 0; + ::g->st_faceindex = 0; + ::g->oldhealth = -1; + ::g->lastattackdown = -1; + ::g->priority = 0; + ::g->largeammo = 1994; // means "n/a" + ::g->st_palette = 0; + ::g->st_stopped = true; +// st_stuff.constructs end // +// s_sound.constructs begin // + ::g->mus_playing=0; +// s_sound.constructs end // +// wi_stuff.constructs begin // +int temp_NUMANIMS[NUMEPISODES] = { + sizeof(epsd0animinfo)/sizeof(anim_t), + sizeof(epsd1animinfo)/sizeof(anim_t), + sizeof(epsd2animinfo)/sizeof(anim_t) +}; +memcpy( ::g->NUMANIMS, temp_NUMANIMS, sizeof(temp_NUMANIMS) ); + ::g->snl_pointeron = false; + extern const anim_t temp_epsd0animinfo[10]; + extern const anim_t temp_epsd1animinfo[9]; + extern const anim_t temp_epsd2animinfo[6]; + memcpy(::g->epsd0animinfo, temp_epsd0animinfo, sizeof(temp_epsd0animinfo)); + memcpy(::g->epsd1animinfo, temp_epsd1animinfo, sizeof(temp_epsd1animinfo)); + memcpy(::g->epsd2animinfo, temp_epsd2animinfo, sizeof(temp_epsd2animinfo)); + wi_stuff_anims[0] = ::g->epsd0animinfo; + wi_stuff_anims[1] = ::g->epsd1animinfo; + wi_stuff_anims[2] = ::g->epsd2animinfo; +// wi_stuff.constructs end // +// z_zone.constructs begin // + ::g->zones[NUM_ZONES] = NULL; + ::g->NumAlloc = 0; +// z_zone.constructs end // +// info constructs begin // + extern const state_t tempStates[NUMSTATES]; + memcpy(::g->states, tempStates, sizeof(tempStates)); +// info constructs end // +// p_local begin // + ::g->rejectmatrix = NULL; +// p_local end // +// r_data begin //] + ::g->s_numtextures = 0; +// r_data end // + + diff --git a/doomclassic/doom/d_englsh.h b/doomclassic/doom/d_englsh.h new file mode 100644 index 00000000..656c80c5 --- /dev/null +++ b/doomclassic/doom/d_englsh.h @@ -0,0 +1,725 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_ENGLSH__ +#define __D_ENGLSH__ + +// +// Printed strings for translation +// + +// +// D_Main.C +// +#define D_DEVSTR "Development mode ON.\n" +#define D_CDROM "CD-ROM Version: default.cfg from c:\\doomdata\n" + +// +// M_Menu.C +// +#define PRESSKEY "press any button." +#define PRESSYN "press y or n." +#define QUITMSG "are you sure you want to\nquit this great game?" +#define LOADNET "you can't do load while in a net game!\n\n"PRESSKEY +#define QLOADNET "you can't quickload during a netgame!\n\n"PRESSKEY +#define QSAVESPOT "you haven't picked a quicksave slot yet!\n\n"PRESSKEY +#define SAVEDEAD "you can't save if you aren't playing!\n\n"PRESSKEY +#define QSPROMPT "quicksave over your game named\n\n'%s'?\n\n"PRESSYN +#define QLPROMPT "do you want to quickload the game named\n\n'%s'?\n\n"PRESSYN + +#define NEWGAME \ +"you can't start a new game\n"\ +"while in a network game.\n\n"PRESSKEY + +#define NIGHTMARE \ +"are you sure? this skill level\n"\ +"isn't even remotely fair.\n\n"PRESSYN + +#define SWSTRING \ +"this is the shareware version of doom.\n\n"\ +"you need to order the entire trilogy.\n\n"PRESSKEY + +#define MSGOFF "Messages OFF" +#define MSGON "Messages ON" +#define NETEND "you can't end a netgame!\n\n"PRESSKEY +#define ENDGAME "are you sure you want to end the game?\n\n"PRESSYN + +#define DOSY "(press A to quit)" + +#define DETAILHI "High detail" +#define DETAILLO "Low detail" +#define GAMMALVL0 "Gamma correction OFF" +#define GAMMALVL1 "Gamma correction level 1" +#define GAMMALVL2 "Gamma correction level 2" +#define GAMMALVL3 "Gamma correction level 3" +#define GAMMALVL4 "Gamma correction level 4" +//#define EMPTYSTRING "empty slot" +#define EMPTYSTRING "---" + +// +// P_inter.C +// +#define GOTARMOR "Picked up the armor." +#define GOTMEGA "Picked up the MegaArmor!" +#define GOTHTHBONUS "Picked up a health bonus." +#define GOTARMBONUS "Picked up an armor bonus." +#define GOTSTIM "Picked up a stimpack." +#define GOTMEDINEED "Picked up a medikit that you REALLY need!" +#define GOTMEDIKIT "Picked up a medikit." +#define GOTSUPER "Supercharge!" + +#define GOTBLUECARD "Picked up a blue keycard." +#define GOTYELWCARD "Picked up a yellow keycard." +#define GOTREDCARD "Picked up a red keycard." +#define GOTBLUESKUL "Picked up a blue skull key." +#define GOTYELWSKUL "Picked up a yellow skull key." +#define GOTREDSKULL "Picked up a red skull key." + +#define GOTINVUL "Invulnerability!" +#define GOTBERSERK "Berserk!" +#define GOTINVIS "Partial Invisibility" +#define GOTSUIT "Radiation Shielding Suit" +#define GOTMAP "Computer Area Map" +#define GOTVISOR "Light Amplification Visor" +#define GOTMSPHERE "MegaSphere!" + +#define GOTCLIP "Picked up a clip." +#define GOTCLIPBOX "Picked up a box of bullets." +#define GOTROCKET "Picked up a rocket." +#define GOTROCKBOX "Picked up a box of rockets." +#define GOTCELL "Picked up an energy cell." +#define GOTCELLBOX "Picked up an energy cell pack." +#define GOTSHELLS "Picked up 4 shotgun shells." +#define GOTSHELLBOX "Picked up a box of shotgun shells." +#define GOTBACKPACK "Picked up a backpack full of ammo!" + +#define GOTBFG9000 "You got the BFG9000! Oh, yes." +#define GOTCHAINGUN "You got the chaingun!" +#define GOTCHAINSAW "A chainsaw! Find some meat!" +#define GOTLAUNCHER "You got the rocket launcher!" +#define GOTPLASMA "You got the plasma gun!" +#define GOTSHOTGUN "You got the shotgun!" +#define GOTSHOTGUN2 "You got the super shotgun!" + +// +// P_Doors.C +// +#define PD_BLUEO "You need a blue key to activate this object" +#define PD_REDO "You need a red key to activate this object" +#define PD_YELLOWO "You need a yellow key to activate this object" +#define PD_BLUEK "You need a blue key to open this door" +#define PD_REDK "You need a red key to open this door" +#define PD_YELLOWK "You need a yellow key to open this door" + +// +// G_game.C +// +#define GGSAVED "game saved." + +// +// HU_stuff.C +// +#define HUSTR_MSGU "[Message unsent]" + +#define HUSTR_E1M1 "E1M1: Hangar" +#define HUSTR_E1M2 "E1M2: Nuclear Plant" +#define HUSTR_E1M3 "E1M3: Toxin Refinery" +#define HUSTR_E1M4 "E1M4: Command Control" +#define HUSTR_E1M5 "E1M5: Phobos Lab" +#define HUSTR_E1M6 "E1M6: Central Processing" +#define HUSTR_E1M7 "E1M7: Computer Station" +#define HUSTR_E1M8 "E1M8: Phobos Anomaly" +#define HUSTR_E1M9 "E1M9: Military Base" + +#define HUSTR_E2M1 "E2M1: Deimos Anomaly" +#define HUSTR_E2M2 "E2M2: Containment Area" +#define HUSTR_E2M3 "E2M3: Refinery" +#define HUSTR_E2M4 "E2M4: Deimos Lab" +#define HUSTR_E2M5 "E2M5: Command Center" +#define HUSTR_E2M6 "E2M6: Halls of the Damned" +#define HUSTR_E2M7 "E2M7: Spawning Vats" +#define HUSTR_E2M8 "E2M8: Tower of Babel" +#define HUSTR_E2M9 "E2M9: Fortress of Mystery" + +#define HUSTR_E3M1 "E3M1: Hell Keep" +#define HUSTR_E3M2 "E3M2: Slough of Despair" +#define HUSTR_E3M3 "E3M3: Pandemonium" +#define HUSTR_E3M4 "E3M4: House of Pain" +#define HUSTR_E3M5 "E3M5: Unholy Cathedral" +#define HUSTR_E3M6 "E3M6: Mt. Erebus" +#define HUSTR_E3M7 "E3M7: Limbo" +#define HUSTR_E3M8 "E3M8: Dis" +#define HUSTR_E3M9 "E3M9: Warrens" + +#define HUSTR_E4M1 "E4M1: Hell Beneath" +#define HUSTR_E4M2 "E4M2: Perfect Hatred" +#define HUSTR_E4M3 "E4M3: Sever The Wicked" +#define HUSTR_E4M4 "E4M4: Unruly Evil" +#define HUSTR_E4M5 "E4M5: They Will Repent" +#define HUSTR_E4M6 "E4M6: Against Thee Wickedly" +#define HUSTR_E4M7 "E4M7: And Hell Followed" +#define HUSTR_E4M8 "E4M8: Unto The Cruel" +#define HUSTR_E4M9 "E4M9: Fear" + +#define HUSTR_1 "level 1: entryway" +#define HUSTR_2 "level 2: underhalls" +#define HUSTR_3 "level 3: the gantlet" +#define HUSTR_4 "level 4: the focus" +#define HUSTR_5 "level 5: the waste tunnels" +#define HUSTR_6 "level 6: the crusher" +#define HUSTR_7 "level 7: dead simple" +#define HUSTR_8 "level 8: tricks and traps" +#define HUSTR_9 "level 9: the pit" +#define HUSTR_10 "level 10: refueling base" +#define HUSTR_11 "level 11: circle of death" + +#define HUSTR_12 "level 12: the factory" +#define HUSTR_13 "level 13: downtown" +#define HUSTR_14 "level 14: the inmost dens" +#define HUSTR_15 "level 15: industrial zone" +#define HUSTR_16 "level 16: suburbs" +#define HUSTR_17 "level 17: tenements" +#define HUSTR_18 "level 18: the courtyard" +#define HUSTR_19 "level 19: the citadel" +#define HUSTR_20 "level 20: gotcha!" + +#define HUSTR_21 "level 21: nirvana" +#define HUSTR_22 "level 22: the catacombs" +#define HUSTR_23 "level 23: barrels o' fun" +#define HUSTR_24 "level 24: the chasm" +#define HUSTR_25 "level 25: bloodfalls" +#define HUSTR_26 "level 26: the abandoned mines" +#define HUSTR_27 "level 27: monster condo" +#define HUSTR_28 "level 28: the spirit world" +#define HUSTR_29 "level 29: the living end" +#define HUSTR_30 "level 30: icon of sin" + +#define HUSTR_31 "level 31: idkfa" +#define HUSTR_32 "level 32: keen" +#define HUSTR_33 "level 33: betray" + +#define PHUSTR_1 "level 1: congo" +#define PHUSTR_2 "level 2: well of souls" +#define PHUSTR_3 "level 3: aztec" +#define PHUSTR_4 "level 4: caged" +#define PHUSTR_5 "level 5: ghost town" +#define PHUSTR_6 "level 6: baron's lair" +#define PHUSTR_7 "level 7: caughtyard" +#define PHUSTR_8 "level 8: realm" +#define PHUSTR_9 "level 9: abattoire" +#define PHUSTR_10 "level 10: onslaught" +#define PHUSTR_11 "level 11: hunted" + +#define PHUSTR_12 "level 12: speed" +#define PHUSTR_13 "level 13: the crypt" +#define PHUSTR_14 "level 14: genesis" +#define PHUSTR_15 "level 15: the twilight" +#define PHUSTR_16 "level 16: the omen" +#define PHUSTR_17 "level 17: compound" +#define PHUSTR_18 "level 18: neurosphere" +#define PHUSTR_19 "level 19: nme" +#define PHUSTR_20 "level 20: the death domain" + +#define PHUSTR_21 "level 21: slayer" +#define PHUSTR_22 "level 22: impossible mission" +#define PHUSTR_23 "level 23: tombstone" +#define PHUSTR_24 "level 24: the final frontier" +#define PHUSTR_25 "level 25: the temple of darkness" +#define PHUSTR_26 "level 26: bunker" +#define PHUSTR_27 "level 27: anti-christ" +#define PHUSTR_28 "level 28: the sewers" +#define PHUSTR_29 "level 29: odyssey of noises" +#define PHUSTR_30 "level 30: the gateway of hell" + +#define PHUSTR_31 "level 31: cyberden" +#define PHUSTR_32 "level 32: go 2 it" + +#define THUSTR_1 "level 1: system control" +#define THUSTR_2 "level 2: human bbq" +#define THUSTR_3 "level 3: power control" +#define THUSTR_4 "level 4: wormhole" +#define THUSTR_5 "level 5: hanger" +#define THUSTR_6 "level 6: open season" +#define THUSTR_7 "level 7: prison" +#define THUSTR_8 "level 8: metal" +#define THUSTR_9 "level 9: stronghold" +#define THUSTR_10 "level 10: redemption" +#define THUSTR_11 "level 11: storage facility" + +#define THUSTR_12 "level 12: crater" +#define THUSTR_13 "level 13: nukage processing" +#define THUSTR_14 "level 14: steel works" +#define THUSTR_15 "level 15: dead zone" +#define THUSTR_16 "level 16: deepest reaches" +#define THUSTR_17 "level 17: processing area" +#define THUSTR_18 "level 18: mill" +#define THUSTR_19 "level 19: shipping/respawning" +#define THUSTR_20 "level 20: central processing" + +#define THUSTR_21 "level 21: administration center" +#define THUSTR_22 "level 22: habitat" +#define THUSTR_23 "level 23: lunar mining project" +#define THUSTR_24 "level 24: quarry" +#define THUSTR_25 "level 25: baron's den" +#define THUSTR_26 "level 26: ballistyx" +#define THUSTR_27 "level 27: mount pain" +#define THUSTR_28 "level 28: heck" +#define THUSTR_29 "level 29: river styx" +#define THUSTR_30 "level 30: last call" + +#define THUSTR_31 "level 31: pharaoh" +#define THUSTR_32 "level 32: caribbean" + +#define HUSTR_CHATMACRO1 "I'm ready to kick butt!" +#define HUSTR_CHATMACRO2 "I'm OK." +#define HUSTR_CHATMACRO3 "I'm not looking too good!" +#define HUSTR_CHATMACRO4 "Help!" +#define HUSTR_CHATMACRO5 "You suck!" +#define HUSTR_CHATMACRO6 "Next time, scumbag..." +#define HUSTR_CHATMACRO7 "Come here!" +#define HUSTR_CHATMACRO8 "I'll take care of it." +#define HUSTR_CHATMACRO9 "Yes" +#define HUSTR_CHATMACRO0 "No" + +#define HUSTR_TALKTOSELF1 "You mumble to yourself" +#define HUSTR_TALKTOSELF2 "Who's there?" +#define HUSTR_TALKTOSELF3 "You scare yourself" +#define HUSTR_TALKTOSELF4 "You start to rave" +#define HUSTR_TALKTOSELF5 "You've lost it..." + +#define HUSTR_MESSAGESENT "[Message Sent]" + +// The following should NOT be changed unless it seems +// just AWFULLY necessary + +#define HUSTR_PLRGREEN "Green: " +#define HUSTR_PLRINDIGO "Indigo: " +#define HUSTR_PLRBROWN "Brown: " +#define HUSTR_PLRRED "Red: " + +#define HUSTR_KEYGREEN 'g' +#define HUSTR_KEYINDIGO 'i' +#define HUSTR_KEYBROWN 'b' +#define HUSTR_KEYRED 'r' + +// +// AM_map.C +// + +#define AMSTR_FOLLOWON "Follow Mode ON" +#define AMSTR_FOLLOWOFF "Follow Mode OFF" + +#define AMSTR_GRIDON "Grid ON" +#define AMSTR_GRIDOFF "Grid OFF" + +#define AMSTR_MARKEDSPOT "Marked Spot" +#define AMSTR_MARKSCLEARED "All Marks Cleared" + +// +// ST_stuff.C +// + +#define STSTR_MUS "Music Change" +#define STSTR_NOMUS "IMPOSSIBLE SELECTION" +#define STSTR_DQDON "Degreelessness Mode On" +#define STSTR_DQDOFF "Degreelessness Mode Off" + +#define STSTR_KFAADDED "Very Happy Ammo Added" +#define STSTR_FAADDED "Ammo (no keys) Added" + +#define STSTR_NCON "No Clipping Mode ON" +#define STSTR_NCOFF "No Clipping Mode OFF" + +#define STSTR_BEHOLD "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp" +#define STSTR_BEHOLDX "Power-up Toggled" + +#define STSTR_CHOPPERS "... doesn't suck - GM" +#define STSTR_CLEV "Changing Level..." + +// +// F_Finale.C +// +#define E1TEXT \ +"Once you beat the big badasses and\n"\ +"clean out the moon base you're supposed\n"\ +"to win, aren't you? Aren't you? Where's\n"\ +"your fat reward and ticket home? What\n"\ +"the hell is this? It's not supposed to\n"\ +"end this way!\n"\ +"\n" \ +"It stinks like rotten meat, but looks\n"\ +"like the lost Deimos base. Looks like\n"\ +"you're stuck on The Shores of Hell.\n"\ +"The only way out is through.\n"\ +"\n"\ +"To continue the DOOM experience, play\n"\ +"The Shores of Hell and its amazing\n"\ +"sequel, Inferno!\n" + + +#define E2TEXT \ +"You've done it! The hideous cyber-\n"\ +"demon lord that ruled the lost Deimos\n"\ +"moon base has been slain and you\n"\ +"are triumphant! But ... where are\n"\ +"you? You clamber to the edge of the\n"\ +"moon and look down to see the awful\n"\ +"truth.\n" \ +"\n"\ +"Deimos floats above Hell itself!\n"\ +"You've never heard of anyone escaping\n"\ +"from Hell, but you'll make the bastards\n"\ +"sorry they ever heard of you! Quickly,\n"\ +"you rappel down to the surface of\n"\ +"Hell.\n"\ +"\n" \ +"Now, it's on to the final chapter of\n"\ +"DOOM! -- Inferno." + + +#define E3TEXT \ +"The loathsome spiderdemon that\n"\ +"masterminded the invasion of the moon\n"\ +"bases and caused so much death has had\n"\ +"its ass kicked for all time.\n"\ +"\n"\ +"A hidden doorway opens and you enter.\n"\ +"You've proven too tough for Hell to\n"\ +"contain, and now Hell at last plays\n"\ +"fair -- for you emerge from the door\n"\ +"to see the green fields of Earth!\n"\ +"Home at last.\n" \ +"\n"\ +"You wonder what's been happening on\n"\ +"Earth while you were battling evil\n"\ +"unleashed. It's good that no Hell-\n"\ +"spawn could have come through that\n"\ +"door with you ..." + + +#define E4TEXT \ +"the spider mastermind must have sent forth\n"\ +"its legions of hellspawn before your\n"\ +"final confrontation with that terrible\n"\ +"beast from hell. but you stepped forward\n"\ +"and brought forth eternal damnation and\n"\ +"suffering upon the horde as a true hero\n"\ +"would in the face of something so evil.\n"\ +"\n"\ +"besides, someone was gonna pay for what\n"\ +"happened to daisy, your pet rabbit.\n"\ +"\n"\ +"but now, you see spread before you more\n"\ +"potential pain and gibbitude as a nation\n"\ +"of demons run amok among our cities.\n"\ +"\n"\ +"next stop, hell on earth!" + + +// after level 6, put this: + +#define C1TEXT \ +"YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" \ +"STARPORT. BUT SOMETHING IS WRONG. THE\n" \ +"MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" \ +"WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" \ +"IS BEING SUBVERTED BY THEIR PRESENCE.\n" \ +"\n"\ +"AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" \ +"FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" \ +"YOU CAN PENETRATE INTO THE HAUNTED HEART\n" \ +"OF THE STARBASE AND FIND THE CONTROLLING\n" \ +"SWITCH WHICH HOLDS EARTH'S POPULATION\n" \ +"HOSTAGE." + +// After level 11, put this: + +#define C2TEXT \ +"YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" \ +"HUMANKIND TO EVACUATE EARTH AND ESCAPE\n"\ +"THE NIGHTMARE. NOW YOU ARE THE ONLY\n"\ +"HUMAN LEFT ON THE FACE OF THE PLANET.\n"\ +"CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n"\ +"AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n"\ +"YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n"\ +"THAT YOU HAVE SAVED YOUR SPECIES.\n"\ +"\n"\ +"BUT THEN, EARTH CONTROL BEAMS DOWN A\n"\ +"MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n"\ +"THE SOURCE OF THE ALIEN INVASION. IF YOU\n"\ +"GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n"\ +"ENTRY. THE ALIEN BASE IS IN THE HEART OF\n"\ +"YOUR OWN HOME CITY, NOT FAR FROM THE\n"\ +"STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n"\ +"UP AND RETURN TO THE FRAY." + + +// After level 20, put this: + +#define C3TEXT \ +"YOU ARE AT THE CORRUPT HEART OF THE CITY,\n"\ +"SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n"\ +"YOU SEE NO WAY TO DESTROY THE CREATURES'\n"\ +"ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n"\ +"TEETH AND PLUNGE THROUGH IT.\n"\ +"\n"\ +"THERE MUST BE A WAY TO CLOSE IT ON THE\n"\ +"OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n"\ +"GOT TO GO THROUGH HELL TO GET TO IT?" + + +// After level 29, put this: + +#define C4TEXT \ +"THE HORRENDOUS VISAGE OF THE BIGGEST\n"\ +"DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n"\ +"YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n"\ +"HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n"\ +"UP AND DIES, ITS THRASHING LIMBS\n"\ +"DEVASTATING UNTOLD MILES OF HELL'S\n"\ +"SURFACE.\n"\ +"\n"\ +"YOU'VE DONE IT. THE INVASION IS OVER.\n"\ +"EARTH IS SAVED. HELL IS A WRECK. YOU\n"\ +"WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n"\ +"DIE, NOW. WIPING THE SWEAT FROM YOUR\n"\ +"FOREHEAD YOU BEGIN THE LONG TREK BACK\n"\ +"HOME. REBUILDING EARTH OUGHT TO BE A\n"\ +"LOT MORE FUN THAN RUINING IT WAS.\n" + + + +// Before level 31, put this: + +#define C5TEXT \ +"CONGRATULATIONS, YOU'VE FOUND THE SECRET\n"\ +"LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n"\ +"HUMANS, RATHER THAN DEMONS. YOU WONDER\n"\ +"WHO THE INMATES OF THIS CORNER OF HELL\n"\ +"WILL BE." + + +// Before level 32, put this: + +#define C6TEXT \ +"CONGRATULATIONS, YOU'VE FOUND THE\n"\ +"SUPER SECRET LEVEL! YOU'D BETTER\n"\ +"BLAZE THROUGH THIS ONE!\n" + + +#define C7TEXT \ +"TROUBLE WAS BREWING AGAIN IN YOUR FAVORITE\n"\ +"VACATION SPOT... HELL. SOME CYBERDEMON\n"\ +"PUNK THOUGHT HE COULD TURN HELL INTO A\n"\ +"PERSONAL AMUSEMENT PARK, AND MAKE EARTH\nTHE TICKET BOOTH.\n\n"\ +"WELL THAT HALF-ROBOT FREAK SHOW DIDN'T\n"\ +"KNOW WHO WAS COMING TO THE FAIR. THERE'S\n"\ +"NOTHING LIKE A SHOOTING GALLERY FULL OF\n"\ +"HELLSPAWN TO GET THE BLOOD PUMPING...\n\n"\ +"NOW THE WALLS OF THE DEMON'S LABYRINTH\n"\ +"ECHO WITH THE SOUND OF HIS METALLIC LIMBS\n"\ +"HITTING THE FLOOR. HIS DEATH MOAN GURGLES\n" \ +"OUT THROUGH THE MESS YOU LEFT OF HIS FACE.\n\n" \ +"THIS RIDE IS CLOSED." + + +#define C8TEXT \ +"CONGRATULATIONS YOU HAVE FINISHED... \n\n"\ +"THE MASTER LEVELS\n" + +// after map 06 + +#define P1TEXT \ +"You gloat over the steaming carcass of the\n"\ +"Guardian. With its death, you've wrested\n"\ +"the Accelerator from the stinking claws\n"\ +"of Hell. You relax and glance around the\n"\ +"room. Damn! There was supposed to be at\n"\ +"least one working prototype, but you can't\n"\ +"see it. The demons must have taken it.\n"\ +"\n"\ +"You must find the prototype, or all your\n"\ +"struggles will have been wasted. Keep\n"\ +"moving, keep fighting, keep killing.\n"\ +"Oh yes, keep living, too." + + +// after map 11 + +#define P2TEXT \ +"Even the deadly Arch-Vile labyrinth could\n"\ +"not stop you, and you've gotten to the\n"\ +"prototype Accelerator which is soon\n"\ +"efficiently and permanently deactivated.\n"\ +"\n"\ +"You're good at that kind of thing." + + +// after map 20 + +#define P3TEXT \ +"You've bashed and battered your way into\n"\ +"the heart of the devil-hive. Time for a\n"\ +"Search-and-Destroy mission, aimed at the\n"\ +"Gatekeeper, whose foul offspring is\n"\ +"cascading to Earth. Yeah, he's bad. But\n"\ +"you know who's worse!\n"\ +"\n"\ +"Grinning evilly, you check your gear, and\n"\ +"get ready to give the bastard a little Hell\n"\ +"of your own making!" + +// after map 30 + +#define P4TEXT \ +"The Gatekeeper's evil face is splattered\n"\ +"all over the place. As its tattered corpse\n"\ +"collapses, an inverted Gate forms and\n"\ +"sucks down the shards of the last\n"\ +"prototype Accelerator, not to mention the\n"\ +"few remaining demons. You're done. Hell\n"\ +"has gone back to pounding bad dead folks \n"\ +"instead of good live ones. Remember to\n"\ +"tell your grandkids to put a rocket\n"\ +"launcher in your coffin. If you go to Hell\n"\ +"when you die, you'll need it for some\n"\ +"final cleaning-up ..." + +// before map 31 + +#define P5TEXT \ +"You've found the second-hardest level we\n"\ +"got. Hope you have a saved game a level or\n"\ +"two previous. If not, be prepared to die\n"\ +"aplenty. For master marines only." + +// before map 32 + +#define P6TEXT \ +"Betcha wondered just what WAS the hardest\n"\ +"level we had ready for ya? Now you know.\n"\ +"No one gets out alive." + + +#define T1TEXT \ +"You've fought your way out of the infested\n"\ +"experimental labs. It seems that UAC has\n"\ +"once again gulped it down. With their\n"\ +"high turnover, it must be hard for poor\n"\ +"old UAC to buy corporate health insurance\n"\ +"nowadays..\n"\ +"\n"\ +"Ahead lies the military complex, now\n"\ +"swarming with diseased horrors hot to get\n"\ +"their teeth into you. With luck, the\n"\ +"complex still has some warlike ordnance\n"\ +"laying around." + + +#define T2TEXT \ +"You hear the grinding of heavy machinery\n"\ +"ahead. You sure hope they're not stamping\n"\ +"out new hellspawn, but you're ready to\n"\ +"ream out a whole herd if you have to.\n"\ +"They might be planning a blood feast, but\n"\ +"you feel about as mean as two thousand\n"\ +"maniacs packed into one mad killer.\n"\ +"\n"\ +"You don't plan to go down easy." + + +#define T3TEXT \ +"The vista opening ahead looks real damn\n"\ +"familiar. Smells familiar, too -- like\n"\ +"fried excrement. You didn't like this\n"\ +"place before, and you sure as hell ain't\n"\ +"planning to like it now. The more you\n"\ +"brood on it, the madder you get.\n"\ +"Hefting your gun, an evil grin trickles\n"\ +"onto your face. Time to take some names." + +#define T4TEXT \ +"Suddenly, all is silent, from one horizon\n"\ +"to the other. The agonizing echo of Hell\n"\ +"fades away, the nightmare sky turns to\n"\ +"blue, the heaps of monster corpses start \n"\ +"to evaporate along with the evil stench \n"\ +"that filled the air. Jeeze, maybe you've\n"\ +"done it. Have you really won?\n"\ +"\n"\ +"Something rumbles in the distance.\n"\ +"A blue light begins to glow inside the\n"\ +"ruined skull of the demon-spitter." + + +#define T5TEXT \ +"What now? Looks totally different. Kind\n"\ +"of like King Tut's condo. Well,\n"\ +"whatever's here can't be any worse\n"\ +"than usual. Can it? Or maybe it's best\n"\ +"to let sleeping gods lie.." + + +#define T6TEXT \ +"Time for a vacation. You've burst the\n"\ +"bowels of hell and by golly you're ready\n"\ +"for a break. You mutter to yourself,\n"\ +"Maybe someone else can kick Hell's ass\n"\ +"next time around. Ahead lies a quiet town,\n"\ +"with peaceful flowing water, quaint\n"\ +"buildings, and presumably no Hellspawn.\n"\ +"\n"\ +"As you step off the transport, you hear\n"\ +"the stomp of a cyberdemon's iron shoe." + + + +// +// Character cast strings F_FINALE.C +// +#define CC_ZOMBIE "ZOMBIEMAN" +#define CC_SHOTGUN "SHOTGUN GUY" +#define CC_HEAVY "HEAVY WEAPON DUDE" +#define CC_IMP "IMP" +#define CC_DEMON "DEMON" +#define CC_LOST "LOST SOUL" +#define CC_CACO "CACODEMON" +#define CC_HELL "HELL KNIGHT" +#define CC_BARON "BARON OF HELL" +#define CC_ARACH "ARACHNOTRON" +#define CC_PAIN "PAIN ELEMENTAL" +#define CC_REVEN "REVENANT" +#define CC_MANCU "MANCUBUS" +#define CC_ARCH "ARCH-VILE" +#define CC_SPIDER "THE SPIDER MASTERMIND" +#define CC_CYBER "THE CYBERDEMON" +#define CC_HERO "OUR HERO" + + +#endif + diff --git a/doomclassic/doom/d_event.h b/doomclassic/doom/d_event.h new file mode 100644 index 00000000..38357e62 --- /dev/null +++ b/doomclassic/doom/d_event.h @@ -0,0 +1,125 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_EVENT__ +#define __D_EVENT__ + + +#include "doomtype.h" + + +// +// Event handling. +// + +// Input event types. +typedef enum +{ + ev_keydown, + ev_keyup, + ev_mouse, + ev_joystick, + ev_none, +} evtype_t; + +// Event structure. +typedef struct +{ + evtype_t type; + int data1; // keys / mouse/joystick buttons + int data2; // mouse/joystick x move + int data3; // mouse/joystick y move +} event_t; + + +typedef enum +{ + ga_nothing, + ga_loadlevel, + ga_newgame, + ga_loadgame, + ga_savegame, + ga_playdemo, + ga_completed, + ga_victory, + ga_worlddone, + ga_screenshot +} gameaction_t; + + + +// +// Button/action code definitions. +// +typedef enum +{ + // Press "Fire". + BT_ATTACK = 1, + // Use button, to open doors, activate switches. + BT_USE = 2, + + // Flag: game events, not really buttons. + BT_SPECIAL = 128, + BT_SPECIALMASK = 3, + + // Flag, weapon change pending. + // If true, the next 3 bits hold weapon num. + BT_CHANGE = 4, + // The 3bit weapon mask and shift, convenience. + BT_WEAPONMASK = (8+16+32), + BT_WEAPONSHIFT = 3, + + // Pause the game. + BTS_PAUSE = 1, + // Save the game at each console. + BTS_SAVEGAME = 2, + + // Savegame slot numbers + // occupy the second byte of buttons. + BTS_SAVEMASK = (4+8+16), + BTS_SAVESHIFT = 2, + +} buttoncode_t; + + + + +// +// GLOBAL VARIABLES +// +#define MAXEVENTS 64 + +extern event_t events[MAXEVENTS]; +extern int eventhead; +extern int eventtail; + +extern gameaction_t gameaction; + + +#endif + diff --git a/doomclassic/doom/d_french.h b/doomclassic/doom/d_french.h new file mode 100644 index 00000000..805d8afc --- /dev/null +++ b/doomclassic/doom/d_french.h @@ -0,0 +1,29 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + diff --git a/doomclassic/doom/d_items.cpp b/doomclassic/doom/d_items.cpp new file mode 100644 index 00000000..bafcbe30 --- /dev/null +++ b/doomclassic/doom/d_items.cpp @@ -0,0 +1,145 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +// We are referring to sprite numbers. +#include "info.h" + +#ifdef __GNUG__ +#pragma implementation "d_items.h" +#endif +#include "d_items.h" + + +// +// PSPRITE ACTIONS for waepons. +// This struct controls the weapon animations. +// +// Each entry is: +// ammo/amunition type +// upstate +// downstate +// readystate +// atkstate, i.e. attack/fire/hit frame +// flashstate, muzzle flash +// +const weaponinfo_t weaponinfo[NUMWEAPONS] = +{ + { + // fist + am_noammo, + S_PUNCHUP, + S_PUNCHDOWN, + S_PUNCH, + S_PUNCH1, + S_NULL + }, + { + // pistol + am_clip, + S_PISTOLUP, + S_PISTOLDOWN, + S_PISTOL, + S_PISTOL1, + S_PISTOLFLASH + }, + { + // shotgun + am_shell, + S_SGUNUP, + S_SGUNDOWN, + S_SGUN, + S_SGUN1, + S_SGUNFLASH1 + }, + { + // chaingun + am_clip, + S_CHAINUP, + S_CHAINDOWN, + S_CHAIN, + S_CHAIN1, + S_CHAINFLASH1 + }, + { + // missile launcher + am_misl, + S_MISSILEUP, + S_MISSILEDOWN, + S_MISSILE, + S_MISSILE1, + S_MISSILEFLASH1 + }, + { + // plasma rifle + am_cell, + S_PLASMAUP, + S_PLASMADOWN, + S_PLASMA, + S_PLASMA1, + S_PLASMAFLASH1 + }, + { + // bfg 9000 + am_cell, + S_BFGUP, + S_BFGDOWN, + S_BFG, + S_BFG1, + S_BFGFLASH1 + }, + { + // chainsaw + am_noammo, + S_SAWUP, + S_SAWDOWN, + S_SAW, + S_SAW1, + S_NULL + }, + { + // super shotgun + am_shell, + S_DSGUNUP, + S_DSGUNDOWN, + S_DSGUN, + S_DSGUN1, + S_DSGUNFLASH1 + }, +}; + + + + + + + + + diff --git a/doomclassic/doom/d_items.h b/doomclassic/doom/d_items.h new file mode 100644 index 00000000..6e502389 --- /dev/null +++ b/doomclassic/doom/d_items.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_ITEMS__ +#define __D_ITEMS__ + +#include "doomdef.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + +// Weapon info: sprite frames, ammunition use. +typedef struct +{ + ammotype_t ammo; + int upstate; + int downstate; + int readystate; + int atkstate; + int flashstate; + +} weaponinfo_t; + +extern const weaponinfo_t weaponinfo[NUMWEAPONS]; + +#endif + diff --git a/doomclassic/doom/d_main.cpp b/doomclassic/doom/d_main.cpp new file mode 100644 index 00000000..dce8f1a4 --- /dev/null +++ b/doomclassic/doom/d_main.cpp @@ -0,0 +1,866 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + + +#include +#include +#include +#include +#include + +#include "doomdef.h" +#include "doomstat.h" + +#include "dstrings.h" +#include "sounds.h" + + +#include "z_zone.h" +#include "w_wad.h" +#include "s_sound.h" +#include "v_video.h" + +#include "f_finale.h" +#include "f_wipe.h" + +#include "m_argv.h" +#include "m_misc.h" +#include "m_menu.h" + +#include "i_system.h" +#include "i_sound.h" +#include "i_video.h" + +#include "g_game.h" + +#include "hu_stuff.h" +#include "wi_stuff.h" +#include "st_stuff.h" +#include "am_map.h" + +#include "p_setup.h" +#include "r_local.h" + + +#include "d_main.h" + +//#include "../idLib/precompiled.h" +//#include "../Main/PlayerProfile.h" +//#include "../Main/PSN/PS3_Session.h" +#include "d3xp/Game_local.h" + +// +// D-DoomLoop() +// Not a globally visible function, +// just included for source reference, +// called by D_DoomMain, never exits. +// Manages timing and IO, +// calls all ?_Responder, ?_Ticker, and ?_Drawer, +// calls I_GetTime, I_StartFrame, and I_StartTic +// +void D_DoomLoop (void); + +void R_ExecuteSetViewSize (void); +void D_CheckNetGame (void); +bool D_PollNetworkStart(); +void D_ProcessEvents (void); +void D_DoAdvanceDemo (void); + +const char* wadfiles[MAXWADFILES] = +{ + 0 +}; + +const char* extraWad = 0; + +// +// EVENT HANDLING +// +// Events are asynchronous inputs generally generated by the game user. +// Events can be discarded if no responder claims them +// + + +// +// D_PostEvent +// Called by the I/O functions when input is detected +// +void D_PostEvent (event_t* ev) +{ + ::g->events[::g->eventhead] = *ev; + ::g->eventhead = (++::g->eventhead)&(MAXEVENTS-1); +} + + +// +// D_ProcessEvents +// Send all the ::g->events of the given timestamp down the responder chain +// +void D_ProcessEvents (void) +{ + event_t* ev; + + // IF STORE DEMO, DO NOT ACCEPT INPUT + if ( ( ::g->gamemode == commercial ) + && (W_CheckNumForName("map01")<0) ) + return; + + for ( ; ::g->eventtail != ::g->eventhead ; ::g->eventtail = (++::g->eventtail)&(MAXEVENTS-1) ) + { + ev = &::g->events[::g->eventtail]; + if (M_Responder (ev)) + continue; // menu ate the event + G_Responder (ev); + } +} + + + + +// +// D_Display +// draw current display, possibly wiping it from the previous +// +// ::g->wipegamestate can be set to -1 to force a ::g->wipe on the next draw +extern bool waitingForWipe; + +void D_Wipe() +{ + int nowtime, tics; + + nowtime = I_GetTime(); + tics = nowtime - ::g->wipestart; + + if (tics != 0) + { + ::g->wipestart = nowtime; + ::g->wipedone = wipe_ScreenWipe( 0, 0, SCREENWIDTH, SCREENHEIGHT, tics ); + + // DHM - Nerve :: Demo recording :: Stop large hitch on first frame after the wipe + if ( ::g->wipedone ) { + ::g->oldtrt_entertics = nowtime / ::g->ticdup; + ::g->gametime = nowtime; + ::g->wipe = false; + waitingForWipe = false; + } + } +} + + +void D_Display (void) +{ + qboolean redrawsbar; + + if (::g->nodrawers) + return; // for comparative timing / profiling + + redrawsbar = false; + + // change the view size if needed + if (::g->setsizeneeded) + { + R_ExecuteSetViewSize(); + ::g->oldgamestate = (gamestate_t)-1; // force background redraw + ::g->borderdrawcount = 3; + } + + // save the current screen if about to ::g->wipe + if (::g->gamestate != ::g->wipegamestate) + { + ::g->wipe = true; + wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + } + else + ::g->wipe = false; + + if (::g->gamestate == GS_LEVEL && ::g->gametic) + HU_Erase(); + + // do buffered drawing + switch (::g->gamestate) + { + case GS_LEVEL: + if (!::g->gametic) + break; + if (::g->automapactive) + AM_Drawer (); + if (::g->wipe || (::g->viewheight != 200 * GLOBAL_IMAGE_SCALER && ::g->fullscreen) ) + redrawsbar = true; + if (::g->inhelpscreensstate && !::g->inhelpscreens) + redrawsbar = true; // just put away the help screen + ST_Drawer ( ::g->viewheight == 200 * GLOBAL_IMAGE_SCALER, redrawsbar ); + ::g->fullscreen = ::g->viewheight == 200 * GLOBAL_IMAGE_SCALER; + break; + + case GS_INTERMISSION: + WI_Drawer (); + break; + + case GS_FINALE: + F_Drawer (); + break; + + case GS_DEMOSCREEN: + D_PageDrawer (); + break; + } + + // draw buffered stuff to screen + I_UpdateNoBlit (); + + // draw the view directly + if (::g->gamestate == GS_LEVEL && !::g->automapactive && ::g->gametic) + R_RenderPlayerView (&::g->players[::g->displayplayer]); + + if (::g->gamestate == GS_LEVEL && ::g->gametic) + HU_Drawer (); + + // clean up border stuff + if (::g->gamestate != ::g->oldgamestate && ::g->gamestate != GS_LEVEL) + I_SetPalette ((byte*)W_CacheLumpName ("PLAYPAL",PU_CACHE_SHARED)); + + // see if the border needs to be initially drawn + if (::g->gamestate == GS_LEVEL && ::g->oldgamestate != GS_LEVEL) + { + ::g->viewactivestate = false; // view was not active + R_FillBackScreen (); // draw the pattern into the back screen + } + + // see if the border needs to be updated to the screen + if (::g->gamestate == GS_LEVEL && !::g->automapactive && ::g->scaledviewwidth != (320 * GLOBAL_IMAGE_SCALER) ) + { + if (::g->menuactive || ::g->menuactivestate || !::g->viewactivestate) + ::g->borderdrawcount = 3; + if (::g->borderdrawcount) + { + R_DrawViewBorder (); // erase old menu stuff + ::g->borderdrawcount--; + } + + } + + ::g->menuactivestate = ::g->menuactive; + ::g->viewactivestate = ::g->viewactive; + ::g->inhelpscreensstate = ::g->inhelpscreens; + ::g->oldgamestate = ::g->wipegamestate = ::g->gamestate; + + // draw pause pic +/* + if (::g->paused) + { + if (::g->automapactive) + y = 4; + else + y = ::g->viewwindowy+4; + V_DrawPatchDirect(::g->viewwindowx+(ORIGINAL_WIDTH-68)/2, + y,0,(patch_t*)W_CacheLumpName ("M_PAUSE", PU_CACHE_SHARED)); + } +*/ + + // menus go directly to the screen + M_Drawer (); // menu is drawn even on top of everything + NetUpdate ( NULL ); // send out any new accumulation + + // normal update + if (!::g->wipe) + { + I_FinishUpdate (); // page flip or blit buffer + return; + } + + // \ update + wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + + ::g->wipestart = I_GetTime () - 1; + + D_Wipe(); // initialize g->wipedone +} + + + +void D_RunFrame( bool Sounds ) +{ + if (Sounds) { + // move positional sounds + S_UpdateSounds (::g->players[::g->consoleplayer].mo); + } + + // Update display, next frame, with current state. + D_Display (); + + if (Sounds) { + // Update sound output. + I_SubmitSound(); + } +} + + + +// +// D_DoomLoop +// +void D_DoomLoop (void) +{ + // DHM - Not used +/* + if (M_CheckParm ("-debugfile")) + { + char filename[20]; + sprintf (filename,"debug%i.txt",::g->consoleplayer); + I_Printf ("debug output to: %s\n",filename); + ::g->debugfile = f o p e n(filename,"w"); + } + + I_InitGraphics (); + + while (1) + { + TryRunTics(); + D_RunFrame( true ); + } +*/ +} + + + +// +// DEMO LOOP +// + + +// +// D_PageTicker +// Handles timing for warped ::g->projection +// +void D_PageTicker (void) +{ + if (--::g->pagetic < 0) + D_AdvanceDemo (); +} + + + +// +// D_PageDrawer +// +void D_PageDrawer (void) +{ + V_DrawPatch (0,0, 0, (patch_t*)W_CacheLumpName(::g->pagename, PU_CACHE_SHARED)); +} + + +// +// D_AdvanceDemo +// Called after each demo or intro ::g->demosequence finishes +// +void D_AdvanceDemo (void) +{ + ::g->advancedemo = true; +} + + +// +// This cycles through the demo sequences. +// FIXME - version dependend demo numbers? +// +void D_DoAdvanceDemo (void) +{ + ::g->players[::g->consoleplayer].playerstate = PST_LIVE; // not reborn + ::g->advancedemo = false; + ::g->usergame = false; // no save / end game here + ::g->paused = false; + ::g->gameaction = ga_nothing; + + if ( ::g->gamemode == retail ) + ::g->demosequence = (::g->demosequence+1)%8; + else + ::g->demosequence = (::g->demosequence+1)%6; + + switch (::g->demosequence) + { + case 0: + if ( ::g->gamemode == commercial ) + ::g->pagetic = 35 * 11; + else + ::g->pagetic = 8 * TICRATE; + + ::g->gamestate = GS_DEMOSCREEN; + ::g->pagename = "INTERPIC"; + + if ( ::g->gamemode == commercial ) + S_StartMusic(mus_dm2ttl); + else + S_StartMusic (mus_intro); + + break; + case 1: + G_DeferedPlayDemo ("demo1"); + break; + case 2: + ::g->pagetic = 3 * TICRATE; + ::g->gamestate = GS_DEMOSCREEN; + ::g->pagename = "INTERPIC"; + break; + case 3: + G_DeferedPlayDemo ("demo2"); + break; + case 4: + ::g->pagetic = 3 * TICRATE; + ::g->gamestate = GS_DEMOSCREEN; + ::g->pagename = "INTERPIC"; + break; + case 5: + G_DeferedPlayDemo ("demo3"); + break; + // THE DEFINITIVE DOOM Special Edition demo + case 6: + ::g->pagetic = 3 * TICRATE; + ::g->gamestate = GS_DEMOSCREEN; + ::g->pagename = "INTERPIC"; + break; + case 7: + G_DeferedPlayDemo ("demo4"); + break; + } +} + + + +// +// D_StartTitle +// +void D_StartTitle (void) +{ + ::g->gameaction = ga_nothing; + ::g->demosequence = -1; + D_AdvanceDemo (); +} + + + + +// print ::g->title for every printed line + +// +// D_AddExtraWadFile +// +void D_SetExtraWadFile( const char *file ) { + extraWad = file; +} + +// +// D_AddFile +// +void D_AddFile (const char *file) +{ + int numwadfiles; + + for (numwadfiles = 0 ; wadfiles[numwadfiles] ; numwadfiles++) + if (file == wadfiles[numwadfiles]) + return; + ; + wadfiles[numwadfiles] = file; +} + +// +// IdentifyVersion +// Checks availability of IWAD files by name, +// to determine whether registered/commercial features +// should be executed (notably loading PWAD's). +// + +void IdentifyVersion (void) +{ + W_FreeWadFiles(); + + const ExpansionData * expansion = DoomLib::GetCurrentExpansion(); + ::g->gamemode = expansion->gameMode; + ::g->gamemission = expansion->pack_type; + + + if( expansion->type == ExpansionData::PWAD ) { + D_AddFile( expansion->iWadFilename ); + D_AddFile( expansion->pWadFilename ); + + } else { + D_AddFile( expansion->iWadFilename ); + } + +} + + +// +// Find a Response File +// +void FindResponseFile (void) +{ +} + + +// +// D_DoomMain +// + +void D_DoomMain (void) +{ + int p; + char file[256]; + + + FindResponseFile (); + + IdentifyVersion (); + + setbuf (stdout, NULL); + ::g->modifiedgame = false; + + // TODO: Networking + //const bool isDeathmatch = gameLocal->GetMatchParms().GetGameType() == GAME_TYPE_PVP; + const bool isDeathmatch = false; + + ::g->nomonsters = M_CheckParm ("-nomonsters") || isDeathmatch; + ::g->respawnparm = M_CheckParm ("-respawn"); + ::g->fastparm = M_CheckParm ("-fast"); + ::g->devparm = M_CheckParm ("-devparm"); + if (M_CheckParm ("-altdeath") || isDeathmatch) + ::g->deathmatch = 2; + else if (M_CheckParm ("-deathmatch")) + ::g->deathmatch = 1; + + switch ( ::g->gamemode ) + { + case retail: + sprintf (::g->title, + " " + "The Ultimate DOOM Startup v%i.%i" + " ", + VERSION/100,VERSION%100); + break; + case shareware: + sprintf (::g->title, + " " + "DOOM Shareware Startup v%i.%i" + " ", + VERSION/100,VERSION%100); + break; + case registered: + sprintf (::g->title, + " " + "DOOM Registered Startup v%i.%i" + " ", + VERSION/100,VERSION%100); + break; + case commercial: + sprintf (::g->title, + " " + "DOOM 2: Hell on Earth v%i.%i" + " ", + VERSION/100,VERSION%100); + break; + default: + sprintf (::g->title, + " " + "Public DOOM - v%i.%i" + " ", + VERSION/100,VERSION%100); + break; + } + + I_Printf ("%s\n",::g->title); + + if (::g->devparm) + I_Printf(D_DEVSTR); + + if (M_CheckParm("-cdrom")) + { + I_Printf(D_CDROM); +//c++ mkdir("c:\\doomdata",0); + strcpy (::g->basedefault,"c:/doomdata/default.cfg"); + } + + // add any files specified on the command line with -file ::g->wadfile + // to the wad list + // + p = M_CheckParm ("-file"); + if (p) + { + // the parms after p are ::g->wadfile/lump names, + // until end of parms or another - preceded parm + ::g->modifiedgame = true; // homebrew levels + while (++p != ::g->myargc && ::g->myargv[p][0] != '-') + D_AddFile (::g->myargv[p]); + } + + p = M_CheckParm ("-playdemo"); + + if (!p) + p = M_CheckParm ("-timedemo"); + + if (p && p < ::g->myargc-1) + { + sprintf (file,"d:\\%s.lmp", ::g->myargv[p+1]); + D_AddFile (file); + I_Printf("Playing demo %s.lmp.\n",::g->myargv[p+1]); + } + + // get skill / episode / map from defaults + ::g->startskill = sk_medium; + ::g->startepisode = 1; + ::g->startmap = 1; + ::g->autostart = false; + + if ( DoomLib::matchParms.gameEpisode != GAME_EPISODE_UNKNOWN ) { + ::g->startepisode = DoomLib::matchParms.gameEpisode; + ::g->autostart = 1; + } + + if ( DoomLib::matchParms.gameMap != -1 ) { + ::g->startmap = DoomLib::matchParms.gameMap; + ::g->autostart = 1; + } + + if ( DoomLib::matchParms.gameSkill != -1) { + ::g->startskill = (skill_t)DoomLib::matchParms.gameSkill; + } + + // get skill / episode / map from cmdline + p = M_CheckParm ("-skill"); + if (p && p < ::g->myargc-1) + { + ::g->startskill = (skill_t)(::g->myargv[p+1][0]-'1'); + ::g->autostart = true; + } + + p = M_CheckParm ("-episode"); + if (p && p < ::g->myargc-1) + { + ::g->startepisode = ::g->myargv[p+1][0]-'0'; + ::g->startmap = 1; + ::g->autostart = true; + } + + /*p = M_CheckParm ("-timer"); + if (p && p < ::g->myargc-1 && ::g->deathmatch) + {*/ + // TODO: Networking + //const int timeLimit = gameLocal->GetMatchParms().GetTimeLimit(); + const int timeLimit = 0; + if (timeLimit != 0 && ::g->deathmatch) + { + int time; + //time = atoi(::g->myargv[p+1]); + time = timeLimit; + I_Printf("Levels will end after %d minute",time); + if (time>1) + I_Printf("s"); + I_Printf(".\n"); + } + + p = M_CheckParm ("-avg"); + if (p && p < ::g->myargc-1 && ::g->deathmatch) + I_Printf("Austin Virtual Gaming: Levels will end after 20 minutes\n"); + + p = M_CheckParm ("-warp"); + if (p && p < ::g->myargc-1) + { + if (::g->gamemode == commercial) + ::g->startmap = atoi (::g->myargv[p+1]); + else + { + ::g->startepisode = ::g->myargv[p+1][0]-'0'; + ::g->startmap = ::g->myargv[p+2][0]-'0'; + } + ::g->autostart = true; + } + + I_Printf ("Z_Init: Init zone memory allocation daemon. \n"); + Z_Init (); + + // init subsystems + I_Printf ("V_Init: allocate ::g->screens.\n"); + V_Init (); + + I_Printf ("M_LoadDefaults: Load system defaults.\n"); + M_LoadDefaults (); // load before initing other systems + + I_Printf ("W_Init: Init WADfiles.\n"); + W_InitMultipleFiles (wadfiles); + + + // Check for -file in shareware + if (::g->modifiedgame) + { + // These are the lumps that will be checked in IWAD, + // if any one is not present, execution will be aborted. + char name[23][16]= + { + "e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9", + "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9", + "dphoof","bfgga0","heada1","cybra1","spida1d1" + }; + int i; + + if ( ::g->gamemode == shareware) + I_Error("\nYou cannot -file with the shareware " + "version. Register!"); + + // Check for fake IWAD with right name, + // but w/o all the lumps of the registered version. + if (::g->gamemode == registered) + for (i = 0;i < 23; i++) + if (W_CheckNumForName(name[i])<0) + I_Error("\nThis is not the registered version."); + } + + // Iff additonal PWAD files are used, print modified banner + if (::g->modifiedgame) + { + /*m*/I_Printf ( + "===========================================================================\n" + "ATTENTION: This version of DOOM has been modified. If you would like to\n" + "get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n" + " You will not receive technical support for modified games.\n" + " press enter to continue\n" + "===========================================================================\n" + ); + getchar (); + } + + + // Check and print which version is executed. + switch ( ::g->gamemode ) + { + case shareware: + case indetermined: + I_Printf ( + "===========================================================================\n" + " Shareware!\n" + "===========================================================================\n" + ); + break; + case registered: + case retail: + case commercial: + I_Printf ( + "===========================================================================\n" + " Commercial product - do not distribute!\n" + " Please report software piracy to the SPA: 1-800-388-PIR8\n" + "===========================================================================\n" + ); + break; + + default: + // Ouch. + break; + } + + I_Printf ("M_Init: Init miscellaneous info.\n"); + M_Init (); + + I_Printf ("R_Init: Init DOOM refresh daemon - "); + R_Init (); + + I_Printf ("\nP_Init: Init Playloop state.\n"); + P_Init (); + + I_Printf ("I_Init: Setting up machine state.\n"); + I_Init (); + + I_Printf ("D_CheckNetGame: Checking network game status.\n"); + D_CheckNetGame (); +} + +bool D_DoomMainPoll(void) +{ + int p; + char file[256]; + + if (D_PollNetworkStart() == false) + return false; + + + I_Printf( "S_Init: Setting up sound.\n" ); + S_Init( s_volume_sound.GetInteger(), s_volume_midi.GetInteger() ); + + I_Printf ("HU_Init: Setting up heads up display.\n"); + HU_Init (); + + I_Printf ("ST_Init: Init status bar.\n"); + ST_Init (); + + // start the apropriate game based on parms + p = M_CheckParm ("-record"); + + if (p && p < ::g->myargc-1) + { + G_RecordDemo (::g->myargv[p+1]); + ::g->autostart = true; + } + + p = M_CheckParm ("-playdemo"); + if (p && p < ::g->myargc-1) + { + //::g->singledemo = true; // quit after one demo + G_DeferedPlayDemo (::g->myargv[p+1]); + //D_DoomLoop (); // never returns + } + + p = M_CheckParm ("-timedemo"); + if (p && p < ::g->myargc-1) + { + G_TimeDemo ("nukage1");//::g->myargv[p+1]); + D_DoomLoop (); // never returns + } + + p = M_CheckParm ("-loadgame"); + if (p && p < ::g->myargc-1) + { + if (M_CheckParm("-cdrom")) + sprintf(file, "c:\\doomdata\\"SAVEGAMENAME"%c.dsg",::g->myargv[p+1][0]); + else + sprintf(file, SAVEGAMENAME"%c.dsg",::g->myargv[p+1][0]); + G_LoadGame (file); + } + + + if ( ::g->gameaction != ga_loadgame && ::g->gameaction != ga_playdemo ) + { + if (::g->autostart || ::g->netgame ) { + G_InitNew (::g->startskill, ::g->startepisode, ::g->startmap ); + } else if( ::g->gameaction != ga_newgame) { + D_StartTitle (); // start up intro loop + } + } + return true; +} + + diff --git a/doomclassic/doom/d_main.h b/doomclassic/doom/d_main.h new file mode 100644 index 00000000..455c48c9 --- /dev/null +++ b/doomclassic/doom/d_main.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_MAIN__ +#define __D_MAIN__ + +#include "d_event.h" + +#ifdef __GNUG__ +#pragma interface +#endif + +extern const char* extraWad; + +#define MAXWADFILES 20 +extern const char* wadfiles[MAXWADFILES]; + +void D_AddExtraWadFile( const char *file ); +void D_AddFile ( const char *file); + + + +// +// D_DoomMain() +// Not a globally visible function, just included for source reference, +// calls all startup code, parses command line options. +// If not overrided by user input, calls N_AdvanceDemo. +// +void D_DoomMain (void); + +// Called by IO functions when input is detected. +void D_PostEvent (event_t* ev); + + + +// +// BASE LEVEL +// +void D_PageTicker (void); +void D_PageDrawer (void); +void D_AdvanceDemo (void); +void D_StartTitle (void); + +#define R_OK 0x01 +#define X_OK 0x02 +#define W_OK 0x04 +int access(char* name, int val); + + +#endif diff --git a/doomclassic/doom/d_net.cpp b/doomclassic/doom/d_net.cpp new file mode 100644 index 00000000..7c69cca5 --- /dev/null +++ b/doomclassic/doom/d_net.cpp @@ -0,0 +1,831 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "m_menu.h" +#include "i_system.h" +#include "i_video.h" +#include "i_net.h" +#include "g_game.h" +#include "doomdef.h" +#include "doomstat.h" + +#include "doomlib.h" +#include "Main.h" +#include "d3xp/Game_local.h" + + +void I_GetEvents( controller_t * ); +void D_ProcessEvents (void); +void G_BuildTiccmd (ticcmd_t *cmd, idUserCmdMgr *, int newTics ); +void D_DoAdvanceDemo (void); + +extern bool globalNetworking; + +// +// NETWORKING +// +// ::g->gametic is the tic about to (or currently being) run +// ::g->maketic is the tick that hasn't had control made for it yet +// ::g->nettics[] has the maketics for all ::g->players +// +// a ::g->gametic cannot be run until ::g->nettics[] > ::g->gametic for all ::g->players +// + + + + +#define NET_TIMEOUT 1 * TICRATE + + + + + +// +// +// +int NetbufferSize (void) +{ + int size = (int)&(((doomdata_t *)0)->cmds[::g->netbuffer->numtics]); + + return size; +} + +// +// Checksum +// +unsigned NetbufferChecksum (void) +{ + unsigned c; + int i,l; + + c = 0x1234567; + + if ( globalNetworking ) { + l = (NetbufferSize () - (int)&(((doomdata_t *)0)->retransmitfrom))/4; + for (i=0 ; inetbuffer->retransmitfrom)[i] * (i+1); + } + + return c & NCMD_CHECKSUM; +} + +// +// +// +int ExpandTics (int low) +{ + int delta; + + delta = low - (::g->maketic&0xff); + + if (delta >= -64 && delta <= 64) + return (::g->maketic&~0xff) + low; + if (delta > 64) + return (::g->maketic&~0xff) - 256 + low; + if (delta < -64) + return (::g->maketic&~0xff) + 256 + low; + + I_Error ("ExpandTics: strange value %i at ::g->maketic %i",low,::g->maketic); + return 0; +} + + + +// +// HSendPacket +// +void +HSendPacket +(int node, + int flags ) +{ + ::g->netbuffer->checksum = NetbufferChecksum () | flags; + + if (!node) + { + ::g->reboundstore = *::g->netbuffer; + ::g->reboundpacket = true; + return; + } + + if (::g->demoplayback) + return; + + if (!::g->netgame) + I_Error ("Tried to transmit to another node"); + + ::g->doomcom.command = CMD_SEND; + ::g->doomcom.remotenode = node; + ::g->doomcom.datalength = NetbufferSize (); + + if (::g->debugfile) + { + int i; + int realretrans; + if (::g->netbuffer->checksum & NCMD_RETRANSMIT) + realretrans = ExpandTics (::g->netbuffer->retransmitfrom); + else + realretrans = -1; + + fprintf (::g->debugfile,"send (%i + %i, R %i) [%i] ", + ExpandTics(::g->netbuffer->starttic), + ::g->netbuffer->numtics, realretrans, ::g->doomcom.datalength); + + for (i=0 ; i < ::g->doomcom.datalength ; i++) + fprintf (::g->debugfile,"%i ",((byte *)::g->netbuffer)[i]); + + fprintf (::g->debugfile,"\n"); + } + + I_NetCmd (); +} + +// +// HGetPacket +// Returns false if no packet is waiting +// +qboolean HGetPacket (void) +{ + if (::g->reboundpacket) + { + *::g->netbuffer = ::g->reboundstore; + ::g->doomcom.remotenode = 0; + ::g->reboundpacket = false; + return true; + } + + if (!::g->netgame) + return false; + + if (::g->demoplayback) + return false; + + ::g->doomcom.command = CMD_GET; + I_NetCmd (); + + if (::g->doomcom.remotenode == -1) + return false; + + if (::g->doomcom.datalength != NetbufferSize ()) + { + if (::g->debugfile) + fprintf (::g->debugfile,"bad packet length %i\n",::g->doomcom.datalength); + return false; + } + + // ALAN NETWORKING -- this fails a lot on 4 player split debug!! + // TODO: Networking +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + if ( !gameLocal->IsSplitscreen() && NetbufferChecksum() != (::g->netbuffer->checksum&NCMD_CHECKSUM) ) + { + if (::g->debugfile) { + fprintf (::g->debugfile,"bad packet checksum\n"); + } + + return false; + } +#endif + + if (::g->debugfile) + { + int realretrans; + int i; + + if (::g->netbuffer->checksum & NCMD_SETUP) + fprintf (::g->debugfile,"setup packet\n"); + else + { + if (::g->netbuffer->checksum & NCMD_RETRANSMIT) + realretrans = ExpandTics (::g->netbuffer->retransmitfrom); + else + realretrans = -1; + + fprintf (::g->debugfile,"get %i = (%i + %i, R %i)[%i] ", + ::g->doomcom.remotenode, + ExpandTics(::g->netbuffer->starttic), + ::g->netbuffer->numtics, realretrans, ::g->doomcom.datalength); + + for (i=0 ; i < ::g->doomcom.datalength ; i++) + fprintf (::g->debugfile,"%i ",((byte *)::g->netbuffer)[i]); + fprintf (::g->debugfile,"\n"); + } + } + return true; +} + + +// +// GetPackets +// + +void GetPackets (void) +{ + int netconsole; + int netnode; + ticcmd_t *src, *dest; + int realend; + int realstart; + + while ( HGetPacket() ) + { + if (::g->netbuffer->checksum & NCMD_SETUP) + continue; // extra setup packet + + netconsole = ::g->netbuffer->player & ~PL_DRONE; + netnode = ::g->doomcom.remotenode; + + // to save bytes, only the low byte of tic numbers are sent + // Figure out what the rest of the bytes are + realstart = ExpandTics (::g->netbuffer->starttic); + realend = (realstart+::g->netbuffer->numtics); + + // check for exiting the game + if (::g->netbuffer->checksum & NCMD_EXIT) + { + if (!::g->nodeingame[netnode]) + continue; + ::g->nodeingame[netnode] = false; + ::g->playeringame[netconsole] = false; + strcpy (::g->exitmsg, "Player 1 left the game"); + ::g->exitmsg[7] += netconsole; + ::g->players[::g->consoleplayer].message = ::g->exitmsg; + + if( ::g->demorecording ) { + G_CheckDemoStatus(); + } + continue; + } + + // check for a remote game kill +/* + if (::g->netbuffer->checksum & NCMD_KILL) + I_Error ("Killed by network driver"); +*/ + + ::g->nodeforplayer[netconsole] = netnode; + + // check for retransmit request + if ( ::g->resendcount[netnode] <= 0 + && (::g->netbuffer->checksum & NCMD_RETRANSMIT) ) + { + ::g->resendto[netnode] = ExpandTics(::g->netbuffer->retransmitfrom); + if (::g->debugfile) + fprintf (::g->debugfile,"retransmit from %i\n", ::g->resendto[netnode]); + ::g->resendcount[netnode] = RESENDCOUNT; + } + else + ::g->resendcount[netnode]--; + + // check for out of order / duplicated packet + if (realend == ::g->nettics[netnode]) + continue; + + if (realend < ::g->nettics[netnode]) + { + if (::g->debugfile) + fprintf (::g->debugfile, + "out of order packet (%i + %i)\n" , + realstart,::g->netbuffer->numtics); + continue; + } + + // check for a missed packet + if (realstart > ::g->nettics[netnode]) + { + // stop processing until the other system resends the missed tics + if (::g->debugfile) + fprintf (::g->debugfile, + "missed tics from %i (%i - %i)\n", + netnode, realstart, ::g->nettics[netnode]); + ::g->remoteresend[netnode] = true; + continue; + } + + // update command store from the packet + { + int start; + + ::g->remoteresend[netnode] = false; + + start = ::g->nettics[netnode] - realstart; + src = &::g->netbuffer->cmds[start]; + + while (::g->nettics[netnode] < realend) + { + dest = &::g->netcmds[netconsole][::g->nettics[netnode]%BACKUPTICS]; + ::g->nettics[netnode]++; + *dest = *src; + src++; + } + } + } +} + + +// +// NetUpdate +// Builds ticcmds for console player, +// sends out a packet +// + +void NetUpdate ( idUserCmdMgr * userCmdMgr ) +{ + int nowtime; + int newtics; + int i,j; + int realstart; + int gameticdiv; + + // check time + nowtime = I_GetTime ()/::g->ticdup; + newtics = nowtime - ::g->gametime; + ::g->gametime = nowtime; + + if (newtics <= 0) // nothing new to update + goto listen; + + if (::g->skiptics <= newtics) + { + newtics -= ::g->skiptics; + ::g->skiptics = 0; + } + else + { + ::g->skiptics -= newtics; + newtics = 0; + } + + + ::g->netbuffer->player = ::g->consoleplayer; + + // build new ticcmds for console player + gameticdiv = ::g->gametic/::g->ticdup; + for (i=0 ; iI_StartTicCallback () ); + D_ProcessEvents (); + if (::g->maketic - gameticdiv >= BACKUPTICS/2-1) { + printf( "Out of room for ticcmds: maketic = %d, gameticdiv = %d\n", ::g->maketic, gameticdiv ); + break; // can't hold any more + } + + //I_Printf ("mk:%i ",::g->maketic); + + // Grab the latest tech5 command + + G_BuildTiccmd (&::g->localcmds[::g->maketic%BACKUPTICS], userCmdMgr, newtics ); + ::g->maketic++; + } + + + if (::g->singletics) + return; // singletic update is syncronous + + // send the packet to the other ::g->nodes + for (i=0 ; i < ::g->doomcom.numnodes ; i++) { + + if (::g->nodeingame[i]) { + ::g->netbuffer->starttic = realstart = ::g->resendto[i]; + ::g->netbuffer->numtics = ::g->maketic - realstart; + if (::g->netbuffer->numtics > BACKUPTICS) + I_Error ("NetUpdate: ::g->netbuffer->numtics > BACKUPTICS"); + + ::g->resendto[i] = ::g->maketic - ::g->doomcom.extratics; + + for (j=0 ; j< ::g->netbuffer->numtics ; j++) + ::g->netbuffer->cmds[j] = + ::g->localcmds[(realstart+j)%BACKUPTICS]; + + if (::g->remoteresend[i]) + { + ::g->netbuffer->retransmitfrom = ::g->nettics[i]; + HSendPacket (i, NCMD_RETRANSMIT); + } + else + { + ::g->netbuffer->retransmitfrom = 0; + HSendPacket (i, 0); + } + } + } + + // listen for other packets +listen: + GetPackets (); +} + + + +// +// CheckAbort +// +void CheckAbort (void) +{ + // DHM - Time starts at 0 tics when starting a multiplayer game, so we can + // check for timeouts easily. If we're still waiting after N seconds, abort. + if ( I_GetTime() > NET_TIMEOUT ) { + // TOOD: Show error & leave net game. + printf( "NET GAME TIMED OUT!\n" ); + //gameLocal->showFatalErrorMessage( XuiLookupStringTable(globalStrings,L"Timed out waiting for match start.") ); + + + D_QuitNetGame(); + + session->QuitMatch(); + common->Dialog().AddDialog( GDM_OPPONENT_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false ); + } +} + + +// +// D_ArbitrateNetStart +// +bool D_ArbitrateNetStart (void) +{ + int i; + + ::g->autostart = true; + if (::g->doomcom.consoleplayer) + { + // listen for setup info from key player + CheckAbort (); + if (!HGetPacket ()) + return false; + if (::g->netbuffer->checksum & NCMD_SETUP) + { + printf( "Received setup info\n" ); + + if (::g->netbuffer->player != VERSION) + I_Error ("Different DOOM versions cannot play a net game!"); + ::g->startskill = (skill_t)(::g->netbuffer->retransmitfrom & 15); + ::g->deathmatch = (::g->netbuffer->retransmitfrom & 0xc0) >> 6; + ::g->nomonsters = (::g->netbuffer->retransmitfrom & 0x20) > 0; + ::g->respawnparm = (::g->netbuffer->retransmitfrom & 0x10) > 0; + // VV original xbox doom :: don't do this.. it will be setup from the launcher + //::g->startmap = ::g->netbuffer->starttic & 0x3f; + //::g->startepisode = ::g->netbuffer->starttic >> 6; + return true; + } + return false; + } + else + { + // key player, send the setup info + CheckAbort (); + for (i=0 ; i < ::g->doomcom.numnodes ; i++) + { + printf( "Sending setup info to node %d\n", i ); + + ::g->netbuffer->retransmitfrom = ::g->startskill; + if (::g->deathmatch) + ::g->netbuffer->retransmitfrom |= (::g->deathmatch<<6); + if (::g->nomonsters) + ::g->netbuffer->retransmitfrom |= 0x20; + if (::g->respawnparm) + ::g->netbuffer->retransmitfrom |= 0x10; + ::g->netbuffer->starttic = ::g->startepisode * 64 + ::g->startmap; + ::g->netbuffer->player = VERSION; + ::g->netbuffer->numtics = 0; + HSendPacket (i, NCMD_SETUP); + } + + while (HGetPacket ()) + { + ::g->gotinfo[::g->netbuffer->player&0x7f] = true; + } + + for (i=1 ; i < ::g->doomcom.numnodes ; i++) { + if (!::g->gotinfo[i]) + break; + } + + if (i >= ::g->doomcom.numnodes) + return true; + + return false; + } +} + +// +// D_CheckNetGame +// Works out player numbers among the net participants +// + +void D_CheckNetGame (void) +{ + int i; + + for (i=0 ; inodeingame[i] = false; + ::g->nettics[i] = 0; + ::g->remoteresend[i] = false; // set when local needs tics + ::g->resendto[i] = 0; // which tic to start sending + } + + // I_InitNetwork sets ::g->doomcom and ::g->netgame + I_InitNetwork (); +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + if (::g->doomcom.id != DOOMCOM_ID) + I_Error ("Doomcom buffer invalid!"); +#endif + + ::g->netbuffer = &::g->doomcom.data; + ::g->consoleplayer = ::g->displayplayer = ::g->doomcom.consoleplayer; +} + +bool D_PollNetworkStart() +{ + int i; + if (::g->netgame) + { + if (D_ArbitrateNetStart () == false) + return false; + } + + I_Printf ("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", + ::g->startskill, ::g->deathmatch, ::g->startmap, ::g->startepisode); + + // read values out of ::g->doomcom + ::g->ticdup = ::g->doomcom.ticdup; + ::g->maxsend = BACKUPTICS/(2*::g->ticdup)-1; + if (::g->maxsend<1) + ::g->maxsend = 1; + + for (i=0 ; i < ::g->doomcom.numplayers ; i++) + ::g->playeringame[i] = true; + for (i=0 ; i < ::g->doomcom.numnodes ; i++) + ::g->nodeingame[i] = true; + + I_Printf ("player %i of %i (%i ::g->nodes)\n", + ::g->consoleplayer+1, ::g->doomcom.numplayers, ::g->doomcom.numnodes); + + return true; +} + + +// +// D_QuitNetGame +// Called before quitting to leave a net game +// without hanging the other ::g->players +// +void D_QuitNetGame (void) +{ + int i; + + if ( (!::g->netgame && !::g->usergame) || ::g->consoleplayer == -1 || ::g->demoplayback || ::g->netbuffer == NULL ) + return; + + // send a quit packet to the other nodes + ::g->netbuffer->player = ::g->consoleplayer; + ::g->netbuffer->numtics = 0; + + for ( i=1; i < ::g->doomcom.numnodes; i++ ) { + if ( ::g->nodeingame[i] ) { + HSendPacket( i, NCMD_EXIT ); + } + } + DoomLib::SendNetwork(); + + for (i=1 ; inodeingame[i] = false; + ::g->nettics[i] = 0; + ::g->remoteresend[i] = false; // set when local needs tics + ::g->resendto[i] = 0; // which tic to start sending + } + + //memset (&::g->doomcom, 0, sizeof(::g->doomcom) ); + + // Reset singleplayer state + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.ticdup = 1; + ::g->doomcom.extratics = 0; + ::g->doomcom.numplayers = ::g->doomcom.numnodes = 1; + ::g->doomcom.deathmatch = false; + ::g->doomcom.consoleplayer = 0; + ::g->netgame = false; + + ::g->netbuffer = &::g->doomcom.data; + ::g->consoleplayer = ::g->displayplayer = ::g->doomcom.consoleplayer; + + ::g->ticdup = ::g->doomcom.ticdup; + ::g->maxsend = BACKUPTICS/(2*::g->ticdup)-1; + if (::g->maxsend<1) + ::g->maxsend = 1; + + for (i=0 ; i < ::g->doomcom.numplayers ; i++) + ::g->playeringame[i] = true; + for (i=0 ; i < ::g->doomcom.numnodes ; i++) + ::g->nodeingame[i] = true; +} + + + +// +// TryRunTics +// +bool TryRunTics ( idUserCmdMgr * userCmdMgr ) +{ + int i; + int lowtic_node = -1; + + // get real tics + ::g->trt_entertic = I_GetTime ()/::g->ticdup; + ::g->trt_realtics = ::g->trt_entertic - ::g->oldtrt_entertics; + ::g->oldtrt_entertics = ::g->trt_entertic; + + // get available tics + NetUpdate ( userCmdMgr ); + + ::g->trt_lowtic = MAXINT; + ::g->trt_numplaying = 0; + + for (i=0 ; i < ::g->doomcom.numnodes ; i++) { + + if (::g->nodeingame[i]) { + ::g->trt_numplaying++; + + if (::g->nettics[i] < ::g->trt_lowtic) { + ::g->trt_lowtic = ::g->nettics[i]; + lowtic_node = i; + } + } + } + + ::g->trt_availabletics = ::g->trt_lowtic - ::g->gametic/::g->ticdup; + + // decide how many tics to run + if (::g->trt_realtics < ::g->trt_availabletics-1) { + ::g->trt_counts = ::g->trt_realtics+1; + } else if (::g->trt_realtics < ::g->trt_availabletics) { + ::g->trt_counts = ::g->trt_realtics; + } else { + ::g->trt_counts = ::g->trt_availabletics; + } + + if (::g->trt_counts < 1) { + ::g->trt_counts = 1; + } + + ::g->frameon++; + + if (::g->debugfile) { + fprintf (::g->debugfile, "=======real: %i avail: %i game: %i\n", ::g->trt_realtics, ::g->trt_availabletics,::g->trt_counts); + } + + if ( !::g->demoplayback ) + { + // ideally ::g->nettics[0] should be 1 - 3 tics above ::g->trt_lowtic + // if we are consistantly slower, speed up time + for (i=0 ; iplayeringame[i]) { + break; + } + } + + if (::g->consoleplayer == i) { + // the key player does not adapt + } + else { + if (::g->nettics[0] <= ::g->nettics[::g->nodeforplayer[i]]) { + ::g->gametime--; + //OutputDebugString("-"); + } + + ::g->frameskip[::g->frameon&3] = (::g->oldnettics > ::g->nettics[::g->nodeforplayer[i]]); + ::g->oldnettics = ::g->nettics[0]; + + if (::g->frameskip[0] && ::g->frameskip[1] && ::g->frameskip[2] && ::g->frameskip[3]) { + ::g->skiptics = 1; + //OutputDebugString("+"); + } + } + } + + // wait for new tics if needed + if (::g->trt_lowtic < ::g->gametic/::g->ticdup + ::g->trt_counts) + { + int lagtime = 0; + + if (::g->trt_lowtic < ::g->gametic/::g->ticdup) { + I_Error ("TryRunTics: ::g->trt_lowtic < gametic"); + } + + if ( ::g->lastnettic == 0 ) { + ::g->lastnettic = ::g->trt_entertic; + } + lagtime = ::g->trt_entertic - ::g->lastnettic; + + // Detect if a client has stopped sending updates, remove them from the game after 5 secs. + if ( common->IsMultiplayer() && (!::g->demoplayback && ::g->netgame) && lagtime >= TICRATE ) { + + if ( lagtime > NET_TIMEOUT ) { + + if ( lowtic_node == ::g->nodeforplayer[::g->consoleplayer] ) { + +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING +#ifndef __PS3__ + gameLocal->showFatalErrorMessage( XuiLookupStringTable(globalStrings,L"You have been disconnected from the match.") ); + gameLocal->Interface.QuitCurrentGame(); +#endif +#endif + } else { + if (::g->nodeingame[lowtic_node]) { + int i, consoleNum = lowtic_node; + + for ( i=0; i < ::g->doomcom.numnodes; i++ ) { + if ( ::g->nodeforplayer[i] == lowtic_node ) { + consoleNum = i; + break; + } + } + + ::g->nodeingame[lowtic_node] = false; + ::g->playeringame[consoleNum] = false; + strcpy (::g->exitmsg, "Player 1 left the game"); + ::g->exitmsg[7] += consoleNum; + ::g->players[::g->consoleplayer].message = ::g->exitmsg; + + // Stop a demo record now, as playback doesn't support losing players + G_CheckDemoStatus(); + } + } + } + } + + return false; + } + + ::g->lastnettic = 0; + + // run the count * ::g->ticdup dics + while (::g->trt_counts--) + { + for (i=0 ; i < ::g->ticdup ; i++) + { + if (::g->gametic/::g->ticdup > ::g->trt_lowtic) { + I_Error ("gametic(%d) greater than trt_lowtic(%d), trt_counts(%d)", ::g->gametic, ::g->trt_lowtic, ::g->trt_counts ); + return false; + } + + if (::g->advancedemo) { + D_DoAdvanceDemo (); + } + + M_Ticker (); + G_Ticker (); + ::g->gametic++; + + // modify command for duplicated tics + if (i != ::g->ticdup-1) + { + ticcmd_t *cmd; + int buf; + int j; + + buf = (::g->gametic/::g->ticdup)%BACKUPTICS; + for (j=0 ; jnetcmds[j][buf]; + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + } + } + } + + NetUpdate ( userCmdMgr ); // check for new console commands + } + + return true; +} + diff --git a/doomclassic/doom/d_net.h b/doomclassic/doom/d_net.h new file mode 100644 index 00000000..83874bd4 --- /dev/null +++ b/doomclassic/doom/d_net.h @@ -0,0 +1,154 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_NET__ +#define __D_NET__ + +#include "d_player.h" + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// Network play related stuff. +// There is a data struct that stores network +// communication related stuff, and another +// one that defines the actual packets to +// be transmitted. +// + +#define DOOMCOM_ID 0x12345678l + +// Max computers/players in a game. +#define MAXNETNODES 8 + + +// Networking and tick handling related. +#define BACKUPTICS 64 + +typedef enum +{ + CMD_SEND = 1, + CMD_GET = 2 + +} command_t; + + +// +// Network packet data. +// +typedef struct +{ + // High bit is retransmit request. + unsigned checksum; + // Only valid if NCMD_RETRANSMIT. + byte retransmitfrom; + + byte sourceDest; + + byte starttic; + byte player; + byte numtics; + ticcmd_t cmds[BACKUPTICS]; + +} doomdata_t; + + + + +struct doomcom_t +{ + // Supposed to be DOOMCOM_ID? + long id; + + // DOOM executes an int to execute commands. + short intnum; + // Communication between DOOM and the driver. + // Is CMD_SEND or CMD_GET. + short command; + // Is dest for send, set by get (-1 = no packet). + short remotenode; + + // Number of bytes in doomdata to be sent + short datalength; + + // Info common to all nodes. + // Console is allways node 0. + short numnodes; + // Flag: 1 = no duplication, 2-5 = dup for slow nets. + short ticdup; + // Flag: 1 = send a backup tic in every packet. + short extratics; + // Flag: 1 = deathmatch. + short deathmatch; + // Flag: -1 = new game, 0-5 = load savegame + short savegame; + short episode; // 1-3 + short map; // 1-9 + short skill; // 1-5 + + // Info specific to this node. + short consoleplayer; + short numplayers; + + // These are related to the 3-display mode, + // in which two drones looking left and right + // were used to render two additional views + // on two additional computers. + // Probably not operational anymore. + // 1 = left, 0 = center, -1 = right + short angleoffset; + // 1 = drone + short drone; + + // The packet data to be sent. + doomdata_t data; + +} ; + + +class idUserCmdMgr; + +// Create any new ticcmds and broadcast to other players. +void NetUpdate ( idUserCmdMgr * userCmdMgr ); + +// Broadcasts special packets to other players +// to notify of game exit +void D_QuitNetGame (void); + +//? how many ticks to run? +bool TryRunTics (void); + + +#endif + + + diff --git a/doomclassic/doom/d_player.h b/doomclassic/doom/d_player.h new file mode 100644 index 00000000..ded41ea4 --- /dev/null +++ b/doomclassic/doom/d_player.h @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_PLAYER__ +#define __D_PLAYER__ + + +// The player data structure depends on a number +// of other structs: items (internal inventory), +// animation states (closely tied to the sprites +// used to represent them, unfortunately). +#include "d_items.h" +#include "p_pspr.h" + +// In addition, the player is just a special +// case of the generic moving object/actor. +#include "p_mobj.h" + +// Finally, for odd reasons, the player input +// is buffered within the player data struct, +// as commands per game tick. +#include "d_ticcmd.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + + + +// +// Player states. +// +typedef enum +{ + // Playing or camping. + PST_LIVE, + // Dead on the ground, view follows killer. + PST_DEAD, + // Ready to restart/respawn??? + PST_REBORN + +} playerstate_t; + + +// +// Player internal flags, for cheats and debug. +// +typedef enum +{ + // No clipping, walk through barriers. + CF_NOCLIP = 1, + // No damage, no health loss. + CF_GODMODE = 2, + // Not really a cheat, just a debug aid. + CF_NOMOMENTUM = 4, + + // Gives kfa at the beginning of the level. + CF_GIVEALL = 8, + + CF_INFAMMO = 16, +} cheat_t; + + +// +// Extended player object info: player_t +// +typedef struct player_s +{ + mobj_t* mo; + playerstate_t playerstate; + ticcmd_t cmd; + + // Determine POV, + // including viewpoint bobbing during movement. + // Focal origin above r.z + fixed_t viewz; + // Base height above floor for viewz. + fixed_t viewheight; + // Bob/squat speed. + fixed_t deltaviewheight; + // bounded/scaled total momentum. + fixed_t bob; + + // This is only used between levels, + // mo->health is used during levels. + int health; + int armorpoints; + // Armor type is 0-2. + int armortype; + + // Power ups. invinc and invis are tic counters. + int powers[NUMPOWERS]; + qboolean cards[NUMCARDS]; + qboolean backpack; + + // Frags, kills of other players. + int frags[MAXPLAYERS]; + weapontype_t readyweapon; + + // Is wp_nochange if not changing. + weapontype_t pendingweapon; + + int weaponowned[NUMWEAPONS]; + int ammo[NUMAMMO]; + int maxammo[NUMAMMO]; + + // True if button down last tic. + int attackdown; + int usedown; + + // Bit flags, for cheats and debug. + // See cheat_t, above. + int cheats; + + // Refired shots are less accurate. + int refire; + + // For intermission stats. + int killcount; + int itemcount; + int secretcount; + + int chainsawKills; + int berserkKills; + + // Hint messages. + const char* message; + + // For screen flashing (red or bright). + int damagecount; + int bonuscount; + + // Who did damage (NULL for floors/ceilings). + mobj_t* attacker; + + // So gun flashes light up areas. + int extralight; + + // Current PLAYPAL, ??? + // can be set to REDCOLORMAP for pain, etc. + int fixedcolormap; + + // Player skin colorshift, + // 0-3 for which color to draw player. + int colormap; + + // Overlay view sprites (gun, etc). + pspdef_t psprites[NUMPSPRITES]; + + // True if secret level has been done. + qboolean didsecret; + +} player_t; + + +// +// INTERMISSION +// Structure passed e.g. to WI_Start(wb) +// +typedef struct +{ + qboolean in; // whether the player is in game + + // Player stats, kills, collected items etc. + int skills; + int sitems; + int ssecret; + int stime; + int frags[4]; + int score; // current score on entry, modified on return + +} wbplayerstruct_t; + +typedef struct +{ + int epsd; // episode # (0-2) + + // if true, splash the secret level + qboolean didsecret; + + // previous and next levels, origin 0 + int last; + int next; + + int maxkills; + int maxitems; + int maxsecret; + int maxfrags; + + // the par time + int partime; + + // index of this player in game + int pnum; + + wbplayerstruct_t plyr[MAXPLAYERS]; + +} wbstartstruct_t; + + +#endif + diff --git a/doomclassic/doom/d_textur.h b/doomclassic/doom/d_textur.h new file mode 100644 index 00000000..dc9c78d5 --- /dev/null +++ b/doomclassic/doom/d_textur.h @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_TEXTUR__ +#define __D_TEXTUR__ + +#include "doomtype.h" + + + + +// +// Flats? +// +// a pic is an unmasked block of pixels +typedef struct +{ + byte width; + byte height; + byte data; +} pic_t; + + + + +#endif + diff --git a/doomclassic/doom/d_think.h b/doomclassic/doom/d_think.h new file mode 100644 index 00000000..4c8a6655 --- /dev/null +++ b/doomclassic/doom/d_think.h @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_THINK__ +#define __D_THINK__ + + +#ifdef __GNUG__ +#pragma interface +#endif + +// +// Experimental stuff. +// To compile this as "ANSI C with classes" +// we will need to handle the various +// action functions cleanly. +// +struct mobj_t; +typedef void (*actionf_v)(); +typedef void (*actionf_p1)( mobj_t* ); +typedef void (*actionf_p2)( void*, void* ); + +typedef union +{ + actionf_p1 acp1; + actionf_v acv; + actionf_p2 acp2; + +} actionf_t; + + + + + +// Historically, "think_t" is yet another +// function pointer to a routine to handle +// an actor. +typedef actionf_t think_t; + + +// Doubly linked list of actors. +typedef struct thinker_s +{ + struct thinker_s* prev; + struct thinker_s* next; + think_t function; + +} thinker_t; + + + +#endif + diff --git a/doomclassic/doom/d_ticcmd.h b/doomclassic/doom/d_ticcmd.h new file mode 100644 index 00000000..db28d944 --- /dev/null +++ b/doomclassic/doom/d_ticcmd.h @@ -0,0 +1,55 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_TICCMD__ +#define __D_TICCMD__ + +#include "doomtype.h" + +#ifdef __GNUG__ +#pragma interface +#endif + +// The data sampled per tick (single player) +// and transmitted to other peers (multiplayer). +// Mainly movements/button commands per game tick, +// plus a checksum for internal state consistency. +typedef struct +{ + char forwardmove; // *2048 for move + char sidemove; // *2048 for move + short angleturn; // <<16 for angle delta + short consistancy; // checks for net game + byte buttons; + byte nextPrevWeapon; +} ticcmd_t; + + + +#endif + diff --git a/doomclassic/doom/defs.h b/doomclassic/doom/defs.h new file mode 100644 index 00000000..95df665d --- /dev/null +++ b/doomclassic/doom/defs.h @@ -0,0 +1,442 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// am_map.defs begin // +#define REDS (256-5*16) +#define REDRANGE 16 +#define BLUES (256-4*16+8) +#define BLUERANGE 8 +#define GREENS (7*16) +#define GREENRANGE 16 +#define GRAYS (6*16) +#define GRAYSRANGE 16 +#define BROWNS (4*16) +#define BROWNRANGE 16 +#define YELLOWS (256-32+7) +#define YELLOWRANGE 1 +#define BLACK 0 +#define WHITE (256-47) +#define BACKGROUND BLACK +#define YOURCOLORS WHITE +#define YOURRANGE 0 +#define WALLCOLORS REDS +#define WALLRANGE REDRANGE +#define TSWALLCOLORS GRAYS +#define TSWALLRANGE GRAYSRANGE +#define FDWALLCOLORS BROWNS +#define FDWALLRANGE BROWNRANGE +#define CDWALLCOLORS YELLOWS +#define CDWALLRANGE YELLOWRANGE +#define THINGCOLORS GREENS +#define THINGRANGE GREENRANGE +#define SECRETWALLCOLORS WALLCOLORS +#define SECRETWALLRANGE WALLRANGE +#define GRIDCOLORS (GRAYS + GRAYSRANGE/2) +#define GRIDRANGE 0 +#define XHAIRCOLORS GRAYS +#define FB 0 +#define AM_PANDOWNKEY KEY_DOWNARROW +#define AM_PANUPKEY KEY_UPARROW +#define AM_PANRIGHTKEY KEY_RIGHTARROW +#define AM_PANLEFTKEY KEY_LEFTARROW +#define AM_ZOOMINKEY K_EQUALS +#define AM_ZOOMOUTKEY K_MINUS +#define AM_STARTKEY KEY_TAB +#define AM_ENDKEY KEY_TAB +#define AM_GOBIGKEY K_0 +#define AM_FOLLOWKEY K_F +#define AM_GRIDKEY K_G +#define AM_MARKKEY K_M +#define AM_CLEARMARKKEY K_C +#define AM_NUMMARKPOINTS 10 +#define INITSCALEMTOF (.2*FRACUNIT) +#define F_PANINC 4 +#define M_ZOOMIN ((int) (1.02*FRACUNIT)) +#define M_ZOOMOUT ((int) (FRACUNIT/1.02)) +#define FTOM(x) FixedMul(((x)<<16),::g->scale_ftom) +#define MTOF(x) (FixedMul((x),::g->scale_mtof)>>16) +#define CXMTOF(x) (::g->f_x + MTOF((x)-::g->m_x)) +#define CYMTOF(y) (::g->f_y + (::g->f_h - MTOF((y)-::g->m_y))) +#define LINE_NEVERSEE ML_DONTDRAW +#define NUMPLYRLINES (sizeof(player_arrow)/sizeof(mline_t)) +#define NUMCHEATPLYRLINES (sizeof(cheat_player_arrow)/sizeof(mline_t)) +#define NUMTRIANGLEGUYLINES (sizeof(triangle_guy)/sizeof(mline_t)) +#define NUMTHINTRIANGLEGUYLINES (sizeof(thintriangle_guy)/sizeof(mline_t)) +#define DOOUTCODE(oc, mx, my) \ + (oc) = 0; \ + if ((my) < 0) (oc) |= TOP; \ + else if ((my) >= ::g->f_h) (oc) |= BOTTOM; \ + if ((mx) < 0) (oc) |= LEFT; \ + else if ((mx) >= ::g->f_w) (oc) |= RIGHT; +#define PUTDOT(xx,yy,cc) ::g->fb[(yy)*::g->f_w+(xx)]=(cc) +// am_map.defs end // +// d_main.defs begin // +#define BGCOLOR 7 +#define FGCOLOR 8 +#define DOOMWADDIR "wads/" +// d_main.defs end // +// d_net.defs begin // +#define NCMD_EXIT 0x80000000 +#define NCMD_RETRANSMIT 0x40000000 +#define NCMD_SETUP 0x20000000 +#define NCMD_KILL 0x10000000 // kill game +#define NCMD_CHECKSUM 0x0fffffff +#define RESENDCOUNT 10 +#define PL_DRONE 0x80 // bit flag in doomdata->player +// d_net.defs end // +// f_finale.defs begin // +#define TEXTSPEED 3 +#define TEXTWAIT 250 +// f_finale.defs end // +// g_game.defs begin // +#define SAVESTRINGSIZE 64 +#define MAXPLMOVE (::g->forwardmove[1]) +#define TURBOTHRESHOLD 0x32 +#define SLOWTURNTICS 6 +#define NUMKEYS 256 +#define BODYQUESIZE 32 +#define VERSIONSIZE 16 +#define DEMOMARKER 0x80 +// g_game.defs end // +// hu_lib.defs begin // +#define noterased ::g->viewwindowx +// hu_lib.defs end // +// hu_stuff.defs begin // +#define HU_TITLE (mapnames[(::g->gameepisode-1)*9+::g->gamemap-1]) +#define HU_TITLE2 (mapnames2[::g->gamemap-1]) +#define HU_TITLEP (mapnamesp[::g->gamemap-1]) +#define HU_TITLET (mapnamest[::g->gamemap-1]) +#define HU_TITLEHEIGHT 1 +#define HU_TITLEX 0 +#define HU_TITLEY (167 - SHORT(::g->hu_font[0]->height)) +#define HU_INPUTTOGGLE K_T +#define HU_INPUTX HU_MSGX +#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(SHORT(::g->hu_font[0]->height) +1)) +#define HU_INPUTWIDTH 64 +#define HU_INPUTHEIGHT 1 +#define QUEUESIZE 128 +// hu_stuff.defs end // +// i_net.defs begin // +// SMF +/* +#define ntohl(x) \ + ((unsigned long int)((((unsigned long int)(x) & 0x000000ffU) << 24) | \ + (((unsigned long int)(x) & 0x0000ff00U) << 8) | \ + (((unsigned long int)(x) & 0x00ff0000U) >> 8) | \ + (((unsigned long int)(x) & 0xff000000U) >> 24))) +#define ntohs(x) \ + ((unsigned short int)((((unsigned short int)(x) & 0x00ff) << 8) | \ + (((unsigned short int)(x) & 0xff00) >> 8))) \ +#define htonl(x) ntohl(x) +#define htons(x) ntohs(x) +// i_net.defs end // +// i_net_xbox.defs begin // +#define ntohl(x) \ + ((unsigned long int)((((unsigned long int)(x) & 0x000000ffU) << 24) | \ + (((unsigned long int)(x) & 0x0000ff00U) << 8) | \ + (((unsigned long int)(x) & 0x00ff0000U) >> 8) | \ + (((unsigned long int)(x) & 0xff000000U) >> 24))) +#define ntohs(x) \ + ((unsigned short int)((((unsigned short int)(x) & 0x00ff) << 8) | \ + (((unsigned short int)(x) & 0xff00) >> 8))) \ + +#define htonl(x) ntohl(x) +#define htons(x) ntohs(x) +*/ + +#define IPPORT_USERRESERVED 5000 +// i_net_xbox.defs end // +// i_sound_xbox.defs begin // +#define SAMPLECOUNT 512 +#define NUM_SOUNDBUFFERS 64 +#define BUFMUL 4 +#define MIXBUFFERSIZE (SAMPLECOUNT*BUFMUL) +// i_sound_xbox.defs end // +// i_video_xbox.defs begin // +//#define TEXTUREWIDTH 512 +//#define TEXTUREHEIGHT 256 +// i_video_xbox.defs end // +// mus2midi.defs begin // +#define MUSEVENT_KEYOFF 0 +#define MUSEVENT_KEYON 1 +#define MUSEVENT_PITCHWHEEL 2 +#define MUSEVENT_CHANNELMODE 3 +#define MUSEVENT_CONTROLLERCHANGE 4 +#define MUSEVENT_END 6 +#define MIDI_MAXCHANNELS 16 +#define MIDIHEADERSIZE 14 +// mus2midi.defs end // +// m_menu.defs begin // +#define SAVESTRINGSIZE 64 +#define SKULLXOFF -32 +#define LINEHEIGHT 16 +// m_menu.defs end // +// p_enemy.defs begin // +#define MAXSPECIALCROSS 8 +#define FATSPREAD (ANG90/8) +#define SKULLSPEED (20*FRACUNIT) +// p_enemy.defs end // +// p_inter.defs begin // +#define BONUSADD 6 +// p_inter.defs end // +// p_map.defs begin // +#define MAXSPECIALCROSS 8 +// p_map.defs end // +// p_mobj.defs begin // +#define STOPSPEED 0x1000 +#define FRICTION 0xe800 +// p_mobj.defs end // +// p_pspr.defs begin // +#define LOWERSPEED FRACUNIT*6 +#define RAISESPEED FRACUNIT*6 +#define WEAPONBOTTOM 128*FRACUNIT +#define WEAPONTOP 32*FRACUNIT +#define BFGCELLS 40 +// p_pspr.defs end // +// p_saveg.defs begin // +#define PADSAVEP() ::g->save_p += (4 - ((int) ::g->save_p & 3)) & 3 +// p_saveg.defs end // +// p_setup.defs begin // +#define MAX_DEATHMATCH_STARTS 10 +// p_setup.defs end // +// p_spec.defs begin // +#define MAXANIMS 32 +#define MAXLINEANIMS 64 +#define MAX_ADJOINING_SECTORS 20 +// p_spec.defs end // +// p_user.defs begin // +#define INVERSECOLORMAP 32 + +// DHM - NERVE :: MAXBOB reduced 25% +//#define MAXBOB 0x100000 +#define MAXBOB 0xC0000 + +#define ANG5 (ANG90/18) +// p_user.defs end // +// r_bsp.defs begin // +#define MAXSEGS 32 +// r_bsp.defs end // +// r_draw.defs begin // +//#define MAXWIDTH 1120 +//#define MAXHEIGHT 832 +#define SBARHEIGHT 32 * GLOBAL_IMAGE_SCALER +#define FUZZTABLE 50 +#define FUZZOFF (SCREENWIDTH) +// r_draw.defs end // +// r_main.defs begin // +#define FIELDOFVIEW 2048 +#define DISTMAP 2 +// r_main.defs end // +// r_plane.defs begin // +//#define MAXVISPLANES 128 +#define MAXVISPLANES 384 +#define MAXOPENINGS SCREENWIDTH*64 +// r_plane.defs end // +// r_segs.defs begin // +#define HEIGHTBITS 12 +#define HEIGHTUNIT (1<tallnum[0]->width) +#define ST_NUMPAINFACES 5 +#define ST_NUMSTRAIGHTFACES 3 +#define ST_NUMTURNFACES 2 +#define ST_NUMSPECIALFACES 3 +#define ST_FACESTRIDE \ + (ST_NUMSTRAIGHTFACES+ST_NUMTURNFACES+ST_NUMSPECIALFACES) +#define ST_NUMEXTRAFACES 2 +#define ST_NUMFACES \ + (ST_FACESTRIDE*ST_NUMPAINFACES+ST_NUMEXTRAFACES) +#define ST_TURNOFFSET (ST_NUMSTRAIGHTFACES) +#define ST_OUCHOFFSET (ST_TURNOFFSET + ST_NUMTURNFACES) +#define ST_EVILGRINOFFSET (ST_OUCHOFFSET + 1) +#define ST_RAMPAGEOFFSET (ST_EVILGRINOFFSET + 1) +#define ST_GODFACE (ST_NUMPAINFACES*ST_FACESTRIDE) +#define ST_DEADFACE (ST_GODFACE+1) +#define ST_FACESX 143 +#define ST_FACESY 168 +#define ST_EVILGRINCOUNT (2*TICRATE) +#define ST_STRAIGHTFACECOUNT (TICRATE/2) +#define ST_TURNCOUNT (1*TICRATE) +#define ST_OUCHCOUNT (1*TICRATE) +#define ST_RAMPAGEDELAY (2*TICRATE) +#define ST_MUCHPAIN 20 +#define ST_AMMOWIDTH 3 +#define ST_AMMOX 44 +#define ST_AMMOY 171 +#define ST_HEALTHWIDTH 3 +#define ST_HEALTHX 90 +#define ST_HEALTHY 171 +#define ST_ARMSX 111 +#define ST_ARMSY 172 +#define ST_ARMSBGX 104 +#define ST_ARMSBGY 168 +#define ST_ARMSXSPACE 12 +#define ST_ARMSYSPACE 10 +#define ST_FRAGSX 138 +#define ST_FRAGSY 171 +#define ST_FRAGSWIDTH 2 +#define ST_ARMORWIDTH 3 +#define ST_ARMORX 221 +#define ST_ARMORY 171 +#define ST_KEY0WIDTH 8 +#define ST_KEY0HEIGHT 5 +#define ST_KEY0X 239 +#define ST_KEY0Y 171 +#define ST_KEY1WIDTH ST_KEY0WIDTH +#define ST_KEY1X 239 +#define ST_KEY1Y 181 +#define ST_KEY2WIDTH ST_KEY0WIDTH +#define ST_KEY2X 239 +#define ST_KEY2Y 191 +#define ST_AMMO0WIDTH 3 +#define ST_AMMO0HEIGHT 6 +#define ST_AMMO0X 288 +#define ST_AMMO0Y 173 +#define ST_AMMO1WIDTH ST_AMMO0WIDTH +#define ST_AMMO1X 288 +#define ST_AMMO1Y 179 +#define ST_AMMO2WIDTH ST_AMMO0WIDTH +#define ST_AMMO2X 288 +#define ST_AMMO2Y 191 +#define ST_AMMO3WIDTH ST_AMMO0WIDTH +#define ST_AMMO3X 288 +#define ST_AMMO3Y 185 +#define ST_MAXAMMO0WIDTH 3 +#define ST_MAXAMMO0HEIGHT 5 +#define ST_MAXAMMO0X 314 +#define ST_MAXAMMO0Y 173 +#define ST_MAXAMMO1WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO1X 314 +#define ST_MAXAMMO1Y 179 +#define ST_MAXAMMO2WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO2X 314 +#define ST_MAXAMMO2Y 191 +#define ST_MAXAMMO3WIDTH ST_MAXAMMO0WIDTH +#define ST_MAXAMMO3X 314 +#define ST_MAXAMMO3Y 185 +#define ST_WEAPON0X 110 +#define ST_WEAPON0Y 172 +#define ST_WEAPON1X 122 +#define ST_WEAPON1Y 172 +#define ST_WEAPON2X 134 +#define ST_WEAPON2Y 172 +#define ST_WEAPON3X 110 +#define ST_WEAPON3Y 181 +#define ST_WEAPON4X 122 +#define ST_WEAPON4Y 181 +#define ST_WEAPON5X 134 +#define ST_WEAPON5Y 181 +#define ST_WPNSX 109 +#define ST_WPNSY 191 +#define ST_DETHX 109 +#define ST_DETHY 191 +#define ST_MSGTEXTX 0 +#define ST_MSGTEXTY 0 +#define ST_MSGWIDTH 52 +#define ST_MSGHEIGHT 1 +#define ST_OUTTEXTX 0 +#define ST_OUTTEXTY 6 +#define ST_OUTWIDTH 52 +#define ST_OUTHEIGHT 1 +#define ST_MAPWIDTH \ + (strlen(mapnames[(::g->gameepisode-1)*9+(::g->gamemap-1)])) +#define ST_MAPTITLEX \ + (SCREENWIDTH - ST_MAPWIDTH * ST_CHATFONTWIDTH) +#define ST_MAPTITLEY 0 +#define ST_MAPHEIGHT 1 +// st_stuff.defs end // +// s_sound.defs begin // +#define S_MAX_VOLUME 127 +#define S_CLIPPING_DIST (1200*0x10000) +#define S_CLOSE_DIST (160*0x10000) +#define S_ATTENUATOR ((S_CLIPPING_DIST-S_CLOSE_DIST)>>FRACBITS) +#define NORM_VOLUME snd_MaxVolume +#define NORM_PITCH 128 +#define NORM_PRIORITY 64 +#define NORM_SEP 128 +#define S_PITCH_PERTURB 1 +#define S_STEREO_SWING (96*0x10000) +#define S_IFRACVOL 30 +#define NA 0 +#define S_NUMCHANNELS 256 +// s_sound.defs end // +// wi_stuff.defs begin // +#define NUMEPISODES 4 +#define NUMMAPS 9 +#define WI_TITLEY 2 +#define WI_SPACINGY 33 +#define SP_STATSX 50 +#define SP_STATSY 50 +#define SP_TIMEX 16 +#define SP_TIMEY (ORIGINAL_HEIGHT-32) +#define NG_STATSY 50 +#define NG_STATSX (32 + SHORT(::g->star->width)/2 + 32*!::g->dofrags) +#define NG_SPACINGX 64 +#define DM_MATRIXX 42 +#define DM_MATRIXY 68 +#define DM_SPACINGX 40 +#define DM_TOTALSX 269 +#define DM_KILLERSX 10 +#define DM_KILLERSY 100 +#define DM_VICTIMSX 5 +#define DM_VICTIMSY 50 +#define FB 0 +#define SP_KILLS 0 +#define SP_ITEMS 2 +#define SP_SECRET 4 +#define SP_FRAGS 6 +#define SP_TIME 8 +#define SP_PAR ST_TIME +#define SP_PAUSE 1 +#define SHOWNEXTLOCDELAY 4 +// wi_stuff.defs end // +// w_wad.defs begin // + +// w_wad.defs end // +// z_zone.defs begin // +#define ZONEID 0x1d4a11 +#define NUM_ZONES 11 +#define MINFRAGMENT 64 +#define NO_SHARE_LUMPS +// z_zone.defs end // diff --git a/doomclassic/doom/doomdata.h b/doomclassic/doom/doomdata.h new file mode 100644 index 00000000..aca5733e --- /dev/null +++ b/doomclassic/doom/doomdata.h @@ -0,0 +1,223 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DOOMDATA__ +#define __DOOMDATA__ + +// The most basic types we use, portability. +#include "doomtype.h" + +// Some global defines, that configure the game. +#include "doomdef.h" + + + +// +// Map level types. +// The following data structures define the persistent format +// used in the lumps of the WAD files. +// + +// Lump order in a map WAD: each map needs a couple of lumps +// to provide a complete scene geometry description. +enum +{ + ML_LABEL, // A separator, name, ExMx or MAPxx + ML_THINGS, // Monsters, items.. + ML_LINEDEFS, // LineDefs, from editing + ML_SIDEDEFS, // SideDefs, from editing + ML_VERTEXES, // Vertices, edited and BSP splits generated + ML_SEGS, // LineSegs, from LineDefs split by BSP + ML_SSECTORS, // SubSectors, list of LineSegs + ML_NODES, // BSP nodes + ML_SECTORS, // Sectors, from editing + ML_REJECT, // LUT, sector-sector visibility + ML_BLOCKMAP // LUT, motion clipping, walls/grid element +}; + + +// A single Vertex. +typedef struct +{ + short x; + short y; +} mapvertex_t; + + +// A SideDef, defining the visual appearance of a wall, +// by setting textures and offsets. +typedef struct +{ + short textureoffset; + short rowoffset; + char toptexture[8]; + char bottomtexture[8]; + char midtexture[8]; + // Front sector, towards viewer. + short sector; +} mapsidedef_t; + + + +// A LineDef, as used for editing, and as input +// to the BSP builder. +typedef struct +{ + short v1; + short v2; + short flags; + short special; + short tag; + // sidenum[1] will be -1 if one sided + short sidenum[2]; +} maplinedef_t; + + +// +// LineDef attributes. +// + +// Solid, is an obstacle. +#define ML_BLOCKING 1 + +// Blocks monsters only. +#define ML_BLOCKMONSTERS 2 + +// Backside will not be present at all +// if not two sided. +#define ML_TWOSIDED 4 + +// If a texture is pegged, the texture will have +// the end exposed to air held constant at the +// top or bottom of the texture (stairs or pulled +// down things) and will move with a height change +// of one of the neighbor sectors. +// Unpegged textures allways have the first row of +// the texture at the top pixel of the line for both +// top and bottom textures (use next to windows). + +// upper texture unpegged +#define ML_DONTPEGTOP 8 + +// lower texture unpegged +#define ML_DONTPEGBOTTOM 16 + +// In AutoMap: don't map as two sided: IT'S A SECRET! +#define ML_SECRET 32 + +// Sound rendering: don't let sound cross two of these. +#define ML_SOUNDBLOCK 64 + +// Don't draw on the automap at all. +#define ML_DONTDRAW 128 + +// Set if already seen, thus drawn in automap. +#define ML_MAPPED 256 + + + + +// Sector definition, from editing. +typedef struct +{ + short floorheight; + short ceilingheight; + char floorpic[8]; + char ceilingpic[8]; + short lightlevel; + short special; + short tag; +} mapsector_t; + +// SubSector, as generated by BSP. +typedef struct +{ + short numsegs; + // Index of first one, segs are stored sequentially. + short firstseg; +} mapsubsector_t; + + +// LineSeg, generated by splitting LineDefs +// using partition lines selected by BSP builder. +typedef struct +{ + short v1; + short v2; + short angle; + short linedef; + short side; + short offset; +} mapseg_t; + + + +// BSP node structure. + +// Indicate a leaf. +#define NF_SUBSECTOR 0x8000 + +typedef struct +{ + // Partition line from (x,y) to x+dx,y+dy) + short x; + short y; + short dx; + short dy; + + // Bounding box for each child, + // clip against view frustum. + short bbox[2][4]; + + // If NF_SUBSECTOR its a subsector, + // else it's a node of another subtree. + unsigned short children[2]; + +} mapnode_t; + + + + +// Thing definition, position, orientation and type, +// plus skill/visibility flags and attributes. +typedef struct +{ + short x; + short y; + short angle; + short type; + short options; +} mapthing_t; + + + + + +#endif // __DOOMDATA__ + + diff --git a/doomclassic/doom/doomdef.cpp b/doomclassic/doom/doomdef.cpp new file mode 100644 index 00000000..63d063e2 --- /dev/null +++ b/doomclassic/doom/doomdef.cpp @@ -0,0 +1,43 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#ifdef __GNUG__ +#pragma implementation "doomdef.h" +#endif +#include "doomdef.h" + +// Location for any defines turned variables. + +// None. + + + diff --git a/doomclassic/doom/doomdef.h b/doomclassic/doom/doomdef.h new file mode 100644 index 00000000..907ef50a --- /dev/null +++ b/doomclassic/doom/doomdef.h @@ -0,0 +1,343 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DOOMDEF__ +#define __DOOMDEF__ + +#include +#include + +#include "../../neo/sys/sys_public.h" + +// +// Global parameters/defines. +// +// DOOM version +enum { VERSION = 111 }; + + +// Game mode handling - identify IWAD version +// to handle IWAD dependend animations etc. +typedef enum +{ + shareware, // DOOM 1 shareware, E1, M9 + registered, // DOOM 1 registered, E3, M27 + commercial, // DOOM 2 retail, E1 M34 + // DOOM 2 german edition not handled + retail, // DOOM 1 retail, E4, M36 + indetermined // Well, no IWAD found. + +} GameMode_t; + + +// Mission packs - might be useful for TC stuff? +typedef enum +{ + doom, // DOOM 1 + doom2, // DOOM 2 + pack_tnt, // TNT mission pack + pack_plut, // Plutonia pack + pack_master, // Master levels + pack_nerve, // Nerve levels + + none + +} GameMission_t; + + +// Identify language to use, software localization. +typedef enum +{ + english, + german, + unknown + +} Language_t; + + +// If rangecheck is undefined, +// most parameter validation debugging code will not be compiled +#ifdef _DEBUG +#define RANGECHECK +#endif + +// Do or do not use external soundserver. +// The sndserver binary to be run separately +// has been introduced by Dave Taylor. +// The integrated sound support is experimental, +// and unfinished. Default is synchronous. +// Experimental asynchronous timer based is +// handled by SNDINTR. +#define SNDSERV 1 +//#define SNDINTR 1 + + +// This one switches between MIT SHM (no proper mouse) +// and XFree86 DGA (mickey sampling). The original +// linuxdoom used SHM, which is default. +//#define X11_DGA 1 + + +// +// For resize of screen, at start of game. +// It will not work dynamically, see visplanes. +// +//#define BASE_WIDTH 320 + +// It is educational but futile to change this +// scaling e.g. to 2. Drawing of status bar, +// menues etc. is tied to the scale implied +// by the graphics. +#define SCREEN_MUL 1 +#define INV_ASPECT_RATIO 0.625 // 0.75, ideally + +// Defines suck. C sucks. +// C++ might sucks for OOP, but it sure is a better C. +// So there. +//#define SCREENWIDTH 320//320 +//SCREEN_MUL*BASE_WIDTH //320 +//#define SCREENHEIGHT 200//200 +//(int)(SCREEN_MUL*BASE_WIDTH*INV_ASPECT_RATIO) //200 + + + + +// The maximum number of players, multiplayer/networking. +#define MAXPLAYERS 4 + +// State updates, number of tics / second. +#define TICRATE 35 + +// The current state of the game: whether we are +// playing, gazing at the intermission screen, +// the game final animation, or a demo. +typedef enum +{ + GS_LEVEL, + GS_INTERMISSION, + GS_FINALE, + GS_DEMOSCREEN +} gamestate_t; + +// +// Difficulty/skill settings/filters. +// + +// Skill flags. +#define MTF_EASY 1 +#define MTF_NORMAL 2 +#define MTF_HARD 4 + +// Deaf monsters/do not react to sound. +#define MTF_AMBUSH 8 + +typedef enum +{ + sk_baby, + sk_easy, + sk_medium, + sk_hard, + sk_nightmare +} skill_t; + + + + +// +// Key cards. +// +typedef enum +{ + it_bluecard, + it_yellowcard, + it_redcard, + it_blueskull, + it_yellowskull, + it_redskull, + + NUMCARDS + +} card_t; + + + +// The defined weapons, +// including a marker indicating +// user has not changed weapon. +typedef enum +{ + wp_fist, + wp_pistol, + wp_shotgun, + wp_chaingun, + wp_missile, + wp_plasma, + wp_bfg, + wp_chainsaw, + wp_supershotgun, + + NUMWEAPONS, + + // No pending weapon change. + wp_nochange + +} weapontype_t; + + +// Ammunition types defined. +typedef enum +{ + am_clip, // Pistol / chaingun ammo. + am_shell, // Shotgun / double barreled shotgun. + am_cell, // Plasma rifle, BFG. + am_misl, // Missile launcher. + NUMAMMO, + am_noammo // Unlimited for chainsaw / fist. + +} ammotype_t; + + +// Power up artifacts. +typedef enum +{ + pw_invulnerability, + pw_strength, + pw_invisibility, + pw_ironfeet, + pw_allmap, + pw_infrared, + NUMPOWERS + +} powertype_t; + + + +// +// Power up durations, +// how many seconds till expiration, +// assuming TICRATE is 35 ticks/second. +// +typedef enum +{ + INVULNTICS = (30*TICRATE), + INVISTICS = (60*TICRATE), + INFRATICS = (120*TICRATE), + IRONTICS = (60*TICRATE) + +} powerduration_t; + + + + +// +// DOOM keyboard definition. +// This is the stuff configured by Setup.Exe. +// Most key data are simple ascii (uppercased). +// +#define KEY_RIGHTARROW K_RIGHTARROW +#define KEY_LEFTARROW K_LEFTARROW +#define KEY_UPARROW K_UPARROW +#define KEY_DOWNARROW K_DOWNARROW +#define KEY_ESCAPE K_ESCAPE +#define KEY_ENTER K_ENTER +#define KEY_TAB K_TAB +#define KEY_F1 K_F1 +#define KEY_F2 K_F2 +#define KEY_F3 K_F3 +#define KEY_F4 K_F4 +#define KEY_F5 K_F5 +#define KEY_F6 K_F6 +#define KEY_F7 K_F7 +#define KEY_F8 K_F8 +#define KEY_F9 K_F9 +#define KEY_F10 K_F10 +#define KEY_F11 K_F11 +#define KEY_F12 K_F12 + +#define KEY_BACKSPACE K_BACKSPACE +#define KEY_PAUSE 0xff + +#define KEY_EQUALS K_EQUALS +#define KEY_MINUS K_MINUS + +#define KEY_RSHIFT K_RSHIFT +#define KEY_RCTRL K_RCTRL +#define KEY_RALT K_RALT +#define KEY_LALT K_LALT + +// DOOM basic types (qboolean), +// and max/min values. +//#include "doomtype.h" + +// Fixed point. +//#include "m_fixed.h" + +// Endianess handling. +//#include "m_swap.h" + + +// Binary Angles, sine/cosine/atan lookups. +//#include "tables.h" + +// Event type. +//#include "d_event.h" + +// Game function, skills. +//#include "g_game.h" + +// All external data is defined here. +//#include "doomdata.h" + +// All important printed strings. +// Language selection (message strings). +//#include "dstrings.h" + +// Player is a special actor. +//struct player_s; + + +//#include "d_items.h" +//#include "d_player.h" +//#include "p_mobj.h" +//#include "d_net.h" + +// PLAY +//#include "p_tick.h" + + + + +// Header, generated by sound utility. +// The utility was written by Dave Taylor. +//#include "sounds.h" + + + + +#endif // __DOOMDEF__ + diff --git a/doomclassic/doom/doominterface.cpp b/doomclassic/doom/doominterface.cpp new file mode 100644 index 00000000..104d6461 --- /dev/null +++ b/doomclassic/doom/doominterface.cpp @@ -0,0 +1,305 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" + +#include + +#include "globaldata.h" +#include "doominterface.h" +#include "Main.h" +#include "m_menu.h" +#include "g_game.h" + +extern void I_SetTime( int ); + +bool waitingForWipe; + +static const int dargc = 7; +static char* dargv[4][7] = +{ + { "doomlauncher", "-net", "0", "127.0.0.1", "127.0.0.1", "127.0.0.1", "127.0.0.1" }, + { "doomlauncher", "-net", "1", "127.0.0.1", "127.0.0.1", "127.0.0.1", "127.0.0.1" }, + { "doomlauncher", "-net", "2", "127.0.0.1", "127.0.0.1", "127.0.0.1", "127.0.0.1" }, + { "doomlauncher", "-net", "3", "127.0.0.1", "127.0.0.1", "127.0.0.1", "127.0.0.1" }, +}; + +static int mpArgc[4]; +static char mpArgV[4][10][32]; +static char* mpArgVPtr[4][10]; + +static bool drawFullScreen = false; + +DoomInterface::DoomInterface() { + numplayers = 0; + bFinished[0] = bFinished[1] = bFinished[2] = bFinished[3] = false; + lastTicRun = 0; +} + +DoomInterface::~DoomInterface() { +} + + +void DoomInterface::Startup( int playerscount, bool multiplayer ) +{ + int i; + int localdargc = 1; // for the commandline + + numplayers = playerscount; + globalNetworking = multiplayer; + lastTicRun = 0; + + if (DoomLib::Z_Malloc == NULL) { + DoomLib::Z_Malloc = Z_Malloc; + } + + // Splitscreen + if ( !multiplayer && playerscount > 1 ) { + localdargc += 2; // for the '-net' and the console number + localdargc += playerscount; + } + + if ( multiplayer ) { + // Force online games to 1 local player for now. + // TODO: We should support local splitscreen and online. + numplayers = 1; + } + + // Start up DooM Classic + for ( i = 0; i < numplayers; ++i) + { + DoomLib::SetPlayer(i); + + bFinished[i] = false; + DoomLib::InitGlobals( NULL ); + + if ( globalNetworking ) { + printf( "Starting mulitplayer game, argv = " ); + for ( int j = 0; j < mpArgc[0]; ++j ) { + printf( " %s", mpArgVPtr[0][j] ); + } + printf( "\n" ); + DoomLib::InitGame(mpArgc[i], mpArgVPtr[i] ); + } else { + DoomLib::InitGame(localdargc, dargv[i] ); + } + + if( DoomLib::skipToLoad ) { + G_LoadGame( DoomLib::loadGamePath ); + DoomLib::skipToLoad = false; + ::g->menuactive = 0; + } + + if( DoomLib::skipToNew ) { + static int startLevel = 1; + G_DeferedInitNew((skill_t)DoomLib::chosenSkill,DoomLib::chosenEpisode+1, startLevel); + DoomLib::skipToNew = false; + ::g->menuactive = 0; + } + + DoomLib::SetPlayer(-1); + } +} + +bool DoomInterface::Frame( int iTime, idUserCmdMgr * userCmdMgr ) +{ + int i; + bool bAllFinished = true; + + if ( !globalNetworking || ( lastTicRun < iTime ) ) { + + drawFullScreen = false; + + DoomLib::SetPlayer( 0 ); + DoomLib::PollNetwork(); + + for (i = 0; i < numplayers; ++i) + { + DoomLib::SetPlayer( i ); + + I_SetTime( iTime ); + + if (bFinished[i] == false) { + bAllFinished = false; + bFinished[i] = DoomLib::Poll(); + } else { + + if (::g->wipedone) { + if ( !waitingForWipe ) { + const bool didRunTic = DoomLib::Tic( userCmdMgr ); + if ( didRunTic == false ) { + //printf( "Skipping tic and yielding because not enough time has passed.\n" ); + + // Give lower priority threads a chance to run. + Sys_Yield(); + } + } + DoomLib::Frame(); + } + if (::g->wipe) { + DoomLib::Wipe(); + // Draw the menus over the wipe. + M_Drawer(); + } + + if( ::g->gamestate != GS_LEVEL && GetNumPlayers() > 2 ) { + drawFullScreen = true; + } + } + + DoomLib::SetPlayer(-1); + } + + DoomLib::SetPlayer( 0 ); + DoomLib::SendNetwork(); + DoomLib::RunSound(); + DoomLib::SetPlayer( -1 ); + + lastTicRun = iTime; + } else { + printf( "Skipping this frame becase it's not time to run a tic yet.\n" ); + } + + return bAllFinished; +} + +void I_ShutdownNetwork(); + +void DoomInterface::Shutdown() { + int i; + + for ( i=0; i < numplayers; i++ ) { + DoomLib::SetPlayer( i ); + D_QuitNetGame(); + } + + // Shutdown local network state + I_ShutdownNetwork(); + + for ( i=0; i < numplayers; i++ ) { + DoomLib::SetPlayer( i ); + DoomLib::Shutdown(); + } + + DoomLib::SetPlayer( -1 ); + numplayers = 0; + lastTicRun = 0; +} + +qboolean G_CheckDemoStatus( void ); + +void DoomInterface::QuitCurrentGame() { + for ( int i = 0; i < numplayers; i++ ) { + DoomLib::SetPlayer( i ); + + if(::g->netgame) { + // Shut down networking + D_QuitNetGame(); + } + + G_CheckDemoStatus(); + + globalPauseTime = false; + ::g->menuactive = false; + ::g->usergame = false; + ::g->netgame = false; + + lastTicRun = 0; + + //if ( !gameLocal->IsSplitscreen() ) { + // Start background demos + D_StartTitle(); + //} + } + + // Shutdown local network state + I_ShutdownNetwork(); +} + +void DoomInterface::EndDMGame() { + + for ( int i = 0; i < numplayers; i++ ) { + DoomLib::SetPlayer( i ); + + if(::g->netgame) { + D_QuitNetGame(); + } + + G_CheckDemoStatus(); + + globalPauseTime = false; + ::g->menuactive = false; + ::g->usergame = false; + ::g->netgame = false; + + lastTicRun = 0; + + D_StartTitle(); + } +} + +//static +int DoomInterface::CurrentPlayer() { + return DoomLib::GetPlayer(); +} + +int DoomInterface::GetNumPlayers() const { + return numplayers; +} + +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING +void DoomInterface::SetNetworking( DoomLib::RecvFunc recv, DoomLib::SendFunc send, DoomLib::SendRemoteFunc sendRemote ) { + DoomLib::SetNetworking( recv, send, sendRemote ); +} +#endif + +void DoomInterface::SetMultiplayerPlayers(int localPlayerIndex, int playerCount, int localPlayer, std::vector playerAddresses) { + + for(int i = 0; i < 10; i++) { + mpArgVPtr[localPlayerIndex][i] = mpArgV[localPlayerIndex][i]; + } + + mpArgc[localPlayerIndex] = playerCount+5; + + strcpy(mpArgV[localPlayerIndex][0], "doomlauncher"); + strcpy(mpArgV[localPlayerIndex][1], "-dup"); + strcpy(mpArgV[localPlayerIndex][2], "1"); + strcpy(mpArgV[localPlayerIndex][3], "-net"); + + sprintf(mpArgV[localPlayerIndex][4], "%d", localPlayer); + strcpy(mpArgV[localPlayerIndex][5], playerAddresses[localPlayer].c_str()); + + int currentArg = 6; + for(int i = 0; i < playerCount; i++) { + if(i != localPlayer) { + strcpy(mpArgV[localPlayerIndex][currentArg], playerAddresses[i].c_str()); + currentArg++; + } + } +} + diff --git a/doomclassic/doom/doominterface.h b/doomclassic/doom/doominterface.h new file mode 100644 index 00000000..b9ffd2c3 --- /dev/null +++ b/doomclassic/doom/doominterface.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef DOOM_INTERFACE_H +#define DOOM_INTERFACE_H + +//#include "doomlib.h" + +#include +#include + +class idUserCmdMgr; + +class DoomInterface +{ +public: + DoomInterface(); + virtual ~DoomInterface(); + + typedef int ( *NoParamCallback)(); + + void Startup( int players, bool multiplayer = false ); + bool Frame( int time, idUserCmdMgr * userCmdMgr ); + void Shutdown(); + void QuitCurrentGame(); + void EndDMGame(); + + // PS3 + //void InitGraphics( int player = -1, int width = TEXTUREWIDTH, int height = TEXTUREHEIGHT, D3DCOLOR *pBuffer = NULL, D3DCOLOR *pBuffer2 = NULL ); + void SetPostGlobalsCallback( NoParamCallback cb ); +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + void SetNetworking( DoomLib::RecvFunc recv, DoomLib::SendFunc send, DoomLib::SendRemoteFunc sendRemote ); +#endif + int GetNumPlayers() const; + + static int CurrentPlayer(); + + void SetMultiplayerPlayers(int localPlayerIndex, int playerCount, int localPlayer, std::vector playerAddresses ); + +protected: + int numplayers; + + bool bFinished[4]; + + int lastTicRun; +}; + + + #endif diff --git a/doomclassic/doom/doomlib.cpp b/doomclassic/doom/doomlib.cpp new file mode 100644 index 00000000..5952897f --- /dev/null +++ b/doomclassic/doom/doomlib.cpp @@ -0,0 +1,554 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" +#include "doomlib.h" +#include +#include "Main.h" +#include "sys/sys_session.h" +#include "idlib/Thread.h" + + +#include + +// Store master volume settings in archived cvars, becausue we want them to apply +// even if a user isn't signed in. +// The range is from 0 to 15, which matches the setting in vanilla DOOM. +idCVar s_volume_sound( "s_volume_sound", "8", CVAR_ARCHIVE | CVAR_INTEGER, "sound volume", 0, 15 ); +idCVar s_volume_midi( "s_volume_midi", "8", CVAR_ARCHIVE | CVAR_INTEGER, "music volume", 0, 15 ); +idCVar m_show_messages( "m_show_messages", "1", CVAR_ARCHIVE | CVAR_INTEGER, "show messages", 0, 1 ); +idCVar m_inDemoMode( "m_inDemoMode", "1", CVAR_INTEGER, "in demo mode", 0, 1 ); + +bool globalNetworking = false; +bool globalPauseTime = false; +int PLAYERCOUNT = 1; + +#ifdef _DEBUG +bool debugOutput = true; +#else +bool debugOutput = false; +#endif + +namespace DoomLib +{ + static const char * Expansion_Names[] = { + "Ultimate DOOM", "DOOM II: Hell On Earth", "Final DOOM: TNT Evilution", "Final DOOM: Plutonia Experiment", "DOOM II: Master Levels", "DOOM II: No Rest For The Living" + }; + + static const char* Skill_Names[] = { + "I'm Too Young To Die!", "Hey, Not Too Rough!", "Hurt Me Plenty!", "Ultra-Violence", "Nightmare" + }; + + static const char* Filter_Names[] = { + "#str_friends", "#str_around", "#str_top15" + }; + + // Game-specific setup values. + static const char * Doom_MapNames[] = { + "E1M1: Hangar", "E1M2: Nuclear Plant", "E1M3: Toxin Refinery", "E1M4: Command Control", "E1M5: Phobos Lab", "E1M6: Central Processing", "E1M7: Computer Station", "E1M8: Phobos Anomaly", "E1M9: Military Base", + "E2M1: Deimos Anomaly", "E2M2: Containment Area", "E2M3: Refinery", "E2M4: Deimos Lab", "E2M5: Command Center", "E2M6: Halls of the Damned", "E2M7: Spawning Vats", "E2M8: Tower of Babel", "E2M9: Fortress of Mystery", + "E3M1: Hell Keep", "E3M2: Slough of Despair", "E3M3: Pandemonium", "E3M4: House of Pain", "E3M5: Unholy Cathedral", "E3M6: MT. Erebus", "E3M7: Gate to Limbo", "E3M8: DIS", "E3M9: Warrens", + "E4M1: Hell Beneath", "E4M2: Perfect Hatred", "E4M3: Sever The Wicked", "E4M4: Unruly Evil", "E4M5: They Will Repent", "E4M6: Against Thee Wickedly", "E4M7: And Hell Followed", "E4M8: Unto The Cruel", "E4M9: Fear" + }; + + static const char * Doom2_MapNames[] = { + "1: Entryway", "2: Underhalls", "3: The Gantlet", "4: The Focus", "5: The Waste Tunnels", "6: The Crusher", "7: Dead Simple", "8: Tricks and Traps", "9: The Pit", "10: Refueling Base", + "11: Circle of Death", "12: The Factory", "13: Downtown", "14: The Inmost Dens", "15: Industrial Zone", "16: Suburbs", "17: Tenements", "18: The Courtyard", "19: The Citadel", "20: Gotcha!", + "21: Nirvana", "22: The Catacombs", "23: Barrels O' Fun", "24: The Chasm", "25: Bloodfalls", "26: The Abandoned Mines", "27: Monster Condo", "28: The Spirit World", "29: The Living End", + "30: Icon of Sin", "31: IDKFA", "32: Keen" + }; + + static const char * TNT_MapNames[] = { + "1: System Control", "2: Human BBQ", "3: Power Control", "4: Wormhole", "5: Hangar", "6: Open Season", "7: Prison", "8: Metal", "9: Stronghold", "10: Redemption", "11: Storage Facility", + "12: Crater", "13: Nukage Processing", "14: Steel Works", "15: Dead Zone", "16: Deepest Reaches", "17: Processing Area", "18: Mill", "19: Shipping & Respawning", "20: Central Processing", + "21: Administration Center", "22: Habitat", "23: Lunar Mining Project", "24: Quarry", "25: Baron's Den", "26: Ballistyx", "27: Mount Pain", "28: Heck", "29: River Styx", "30: Last Call", "31: Pharaoh", "32: Caribbean" + }; + + static const char * Plut_MapNames[] = { + "1: Congo", "2: Well of Souls", "3: Aztec", "4: Caged", "5: Ghost Town", "6: Baron's Lair", "7: Caughtyard", "8: Realm", "9: Abattoire", "10: Onslaught", "11: Hunted", "12: Speed", "13: The Crypt", "14: Genesis", + "15: The Twilight", "16: The Omen", "17: Compound", "18: Neurosphere", "19: NME", "20: The Death Domain", "21: Slayer", "22: Impossible Mission", "23: Tombstone", "24: The Final Frontier", "25: The Temple of Darkness", + "26: Bunker", "27: Anti-Christ", "28: The Sewers", "29: Odyssey of Noises", "30: The Gateway of Hell", "31: Cyberden", "32: Go 2 It" + }; + + static const char * Mast_MapNames[] = { + "1: Attack", "2: Canyon","3: The Catwalk", "4: The Combine", "5: The Fistula", "6: The Garrison", "7: Titan Manor", "8: Paradox", "9: Subspace", "10: Subterra", "11: Trapped On Titan", "12: Virgil's Lead", "13: Minos' Judgement", + "14: Bloodsea Keep", "15: Mephisto's Maosoleum", "16: Nessus", "17: Geryon", "18: Vesperas", "19: Black Tower", "20: The Express Elevator To Hell", "21: Bad Dream" + }; + + static const char * Nerve_MapNames[] = { + "1: The Earth Base", "2: The Pain Labs", "3: Canyon of the Dead", "4: Hell Mountain", "5: Vivisection", "6: Inferno of Blood", "7: Baron's Banquet", "8: Tomb of Malevolence", "9: March of Demons" + }; + + const ExpansionData App_Expansion_Data_Local[] = { + { ExpansionData::IWAD, retail, doom, "DOOM", DOOMWADDIR"DOOM.WAD", NULL, "base/textures/DOOMICON.PNG" , Doom_MapNames }, + { ExpansionData::IWAD, commercial, doom2, "DOOM 2", DOOMWADDIR"DOOM2.WAD", NULL, "base/textures/DOOM2ICON.PNG" , Doom2_MapNames }, + { ExpansionData::IWAD, commercial, pack_tnt, "FINAL DOOM: TNT EVILUTION", DOOMWADDIR"TNT.WAD", NULL, "base/textures/TNTICON.PNG" , TNT_MapNames }, + { ExpansionData::IWAD, commercial, pack_plut, "FINAL DOOM: PLUTONIA EXPERIMENT", DOOMWADDIR"PLUTONIA.WAD", NULL, "base/textures/PLUTICON.PNG" , Plut_MapNames }, + { ExpansionData::PWAD, commercial, pack_master, "DOOM 2: MASTER LEVELS", DOOMWADDIR"DOOM2.WAD", DOOMWADDIR"MASTERLEVELS.WAD", "base/textures/MASTICON.PNG" , Mast_MapNames }, + { ExpansionData::PWAD, commercial, pack_nerve, "DOOM 2: NO REST FOR THE LIVING", DOOMWADDIR"DOOM2.WAD", DOOMWADDIR"NERVE.WAD", "base/textures/NERVEICON.PNG" , Nerve_MapNames }, + }; + + int classicRemap[K_LAST_KEY]; + + const ExpansionData * GetCurrentExpansion() { + return &App_Expansion_Data_Local[ DoomLib::expansionSelected ]; + } + + void SetCurrentExpansion( int expansion ) { + expansionDirty = true; + expansionSelected = expansion; + } + + void SetIdealExpansion( int expansion ) { + idealExpansion = expansion; + } + + idStr currentMapName; + idStr currentDifficulty; + + void SetCurrentMapName( idStr name ) { currentMapName = name; } + const idStr & GetCurrentMapName() { return currentMapName; } + void SetCurrentDifficulty( idStr name ) { currentDifficulty = name; } + const idStr & GetCurrentDifficulty() { return currentDifficulty; } + + int currentplayer = -1; + + Globals *globaldata[4]; + + RecvFunc Recv; + SendFunc Send; + SendRemoteFunc SendRemote; + + + bool Active = true; + DoomInterface Interface; + + int idealExpansion = 0; + int expansionSelected = 0; + bool expansionDirty = true; + + bool skipToLoad = false; + char loadGamePath[MAX_PATH]; + + bool skipToNew = false; + int chosenSkill = 0; + int chosenEpisode = 1; + + idMatchParameters matchParms; + + void * (*Z_Malloc)( int size, int tag, void* user ) = NULL; + void (*Z_FreeTag)(int lowtag ); + + idArray< idSysMutex, 4 > playerScreenMutexes; + + void ExitGame() { + // TODO: If we ever support splitscreen and online, + // we'll have to call D_QuitNetGame for all local players. + DoomLib::SetPlayer( 0 ); + D_QuitNetGame(); + + session->QuitMatch(); + } + + void ShowXToContinue( bool activate ) { + } + + /* + ======================== + DoomLib::GetGameSKU + ======================== + */ + gameSKU_t GetGameSKU() { + + if ( common->GetCurrentGame() == DOOM_CLASSIC ) { + return GAME_SKU_DOOM1_BFG; + } else if ( common->GetCurrentGame() == DOOM2_CLASSIC ) { + return GAME_SKU_DOOM2_BFG; + } + + assert( false && "Invalid basepath" ); + return GAME_SKU_DCC; + } + + /* + ======================== + DoomLib::ActivateGame + ======================== + */ + void ActivateGame() { + Active = true; + + // Turn off menu toggler + int originalPlayer = DoomLib::GetPlayer(); + + for ( int i = 0; i < Interface.GetNumPlayers(); i++ ) { + DoomLib::SetPlayer(i); + ::g->menuactive = false; + } + + globalPauseTime = false; + + DoomLib::SetPlayer( originalPlayer ); + } + + /* + ======================== + DoomLib::HandleEndMatch + ======================== + */ + void HandleEndMatch() { + if ( session->GetGameLobbyBase().IsHost() ) { + ShowXToContinue( false ); + session->EndMatch(); + } + } +}; + + +extern void I_InitGraphics(); +extern void D_DoomMain(); +extern bool D_DoomMainPoll(); +extern void I_InitInput(); +extern void D_RunFrame( bool ); +extern void I_ShutdownSound(); +extern void I_ShutdownMusic(); +extern void I_ShutdownGraphics(); +extern void I_ProcessSoundEvents( void ); + + +void DoomLib::InitGlobals( void *ptr /* = NULL */ ) +{ + if (ptr == NULL) + ptr = new Globals; + + globaldata[currentplayer] = static_cast(ptr); + + memset( globaldata[currentplayer], 0, sizeof(Globals) ); + g = globaldata[currentplayer]; + g->InitGlobals(); + +} + +void *DoomLib::GetGlobalData( int player ) { + return globaldata[player]; +} + +void DoomLib::InitControlRemap() { + + memset( classicRemap, K_NONE, sizeof( classicRemap ) ); + + classicRemap[K_JOY3] = KEY_TAB ; + classicRemap[K_JOY4] = K_MINUS; + classicRemap[K_JOY2] = K_EQUALS; + classicRemap[K_JOY9] = K_ESCAPE ; + classicRemap[K_JOY_STICK1_UP] = K_UPARROW ; + classicRemap[K_JOY_DPAD_UP] = K_UPARROW ; + classicRemap[K_JOY_STICK1_DOWN] = K_DOWNARROW ; + classicRemap[K_JOY_DPAD_DOWN] = K_DOWNARROW ; + classicRemap[K_JOY_STICK1_LEFT] = K_LEFTARROW ; + classicRemap[K_JOY_DPAD_LEFT] = K_LEFTARROW ; + classicRemap[K_JOY_STICK1_RIGHT] = K_RIGHTARROW ; + classicRemap[K_JOY_DPAD_RIGHT] = K_RIGHTARROW ; + classicRemap[K_JOY1] = K_ENTER; + + +} + +keyNum_t DoomLib::RemapControl( keyNum_t key ) { + + if( classicRemap[ key ] == K_NONE ) { + return key; + } else { + + if( ::g->menuactive && key == K_JOY2 ) { + return K_BACKSPACE; + } + + return (keyNum_t)classicRemap[ key ]; + } + +} + +void DoomLib::InitGame( int argc, char** argv ) +{ + ::g->myargc = argc; + ::g->myargv = argv; + + InitControlRemap(); + + + + D_DoomMain(); +} + +bool DoomLib::Poll() +{ + return D_DoomMainPoll(); +} + +bool TryRunTics( idUserCmdMgr * userCmdMgr ); +bool DoomLib::Tic( idUserCmdMgr * userCmdMgr ) +{ + return TryRunTics( userCmdMgr ); +} + +void D_Wipe(); +void DoomLib::Wipe() +{ + D_Wipe(); +} + +void DoomLib::Frame( int realoffset, int buffer ) +{ + ::g->realoffset = realoffset; + + // The render thread needs to read the player's screens[0] array, + // so updating it needs to be in a critical section. + // This may seem like a really broad mutex (which it is), and if performance + // suffers too much, we can try to narrow the scope. + // Just be careful, because the player's screen data is updated in many different + // places. + if ( 0 <= currentplayer && currentplayer <= 4 ) { + idScopedCriticalSection crit( playerScreenMutexes[currentplayer] ); + + D_RunFrame( true ); + } +} + +void DoomLib::Draw() +{ + R_RenderPlayerView (&::g->players[::g->displayplayer]); +} + +angle_t GetViewAngle() +{ + return g->viewangle; +} + +void SetViewAngle( angle_t ang ) +{ + g->viewangle = ang; + ::g->viewxoffset = (finesine[g->viewangle>>ANGLETOFINESHIFT]*::g->realoffset) >> 8; + ::g->viewyoffset = (finecosine[g->viewangle>>ANGLETOFINESHIFT]*::g->realoffset) >> 8; + +} + + +void SetViewX( fixed_t x ) +{ + ::g->viewx = x; +} + +void SetViewY( fixed_t y ) +{ + ::g->viewy = y; +} + + +fixed_t GetViewX() +{ + return ::g->viewx + ::g->viewxoffset; +} + +fixed_t GetViewY() +{ + return ::g->viewy + ::g->viewyoffset; +} + +void DoomLib::Shutdown() { + //D_QuitNetGame (); + I_ShutdownSound(); + I_ShutdownGraphics(); + + W_Shutdown(); + + // De-allocate the zone memory (never happened in original doom, until quit) + if ( ::g->mainzone ) { + free( ::g->mainzone ); + } + + // Delete the globals + if ( globaldata[currentplayer] ) { + delete globaldata[currentplayer]; + globaldata[currentplayer] = NULL; + } +} + +// static +void DoomLib::SetPlayer( int id ) +{ + currentplayer = id; + + if ( id < 0 || id >= MAX_PLAYERS ) { + g = NULL; + } + else { + + // Big Fucking hack. + if( globalNetworking && session->GetGameLobbyBase().GetMatchParms().matchFlags | MATCH_ONLINE ) { + currentplayer = 0; + } + + g = globaldata[currentplayer]; + } +} + +void DoomLib::SetNetworking( RecvFunc rf, SendFunc sf, SendRemoteFunc sendRemote ) +{ + Recv = rf; + Send = sf; + SendRemote = sendRemote; +} + +int DoomLib::GetPlayer() +{ + return currentplayer; +} + +byte DoomLib::BuildSourceDest( int toNode ) { + byte sourceDest = 0; + sourceDest |= ::g->consoleplayer << 2; + sourceDest |= RemoteNodeToPlayerIndex( toNode ); + return sourceDest; +} + +void I_Printf(char *error, ...); + +void DoomLib::GetSourceDest( byte sourceDest, int* source, int* dest ) { + + int src = (sourceDest & 12) >> 2; + int dst = sourceDest & 3; + + *source = PlayerIndexToRemoteNode( src ); + + //I_Printf( "GetSourceDest Current Player(%d) %d --> %d\n", GetPlayer(), src, *source ); + *dest = PlayerIndexToRemoteNode( dst ); +} + +int nodeMap[4][4] = { + {0, 1, 2, 3}, //Player 0 + {1, 0, 2, 3}, //Player 1 + {2, 0, 1, 3}, //Player 2 + {3, 0, 1, 2} //Player 3 +}; + +int DoomLib::RemoteNodeToPlayerIndex( int node ) { + //This needs to be called with the proper doom globals set so this calculation will work properly + + /* + int player = ::g->consoleplayer; + if (player == 2 && node == 2 ) { + int suck = 0; + } + if( node == player ) { + return 0; + } + if( node - player <= 0 ) { + return node+1; + } + return node;*/ + return nodeMap[::g->consoleplayer][node]; + +} + +int indexMap[4][4] = { + {0, 1, 2, 3}, //Player 0 + {1, 0, 2, 3}, //Player 1 + {1, 2, 0, 3}, //Player 2 + {1, 2, 3, 0} //Player 3 +}; + +int DoomLib::PlayerIndexToRemoteNode( int index ) { + /*int player = ::g->consoleplayer; + if( index == 0 ) { + return player; + } + if( index <= player ) { + return index-1; + } + return index;*/ + return indexMap[::g->consoleplayer][index]; +} + +void I_Error (char *error, ...); +extern bool useTech5Packets; + +void DoomLib::PollNetwork() { +#if 0 + if ( !useTech5Packets ) { + + if ( !globalNetworking ) { + return; + } + + int c; + struct sockaddr fromaddress; + socklen_t fromlen; + doomdata_t sw; + + while(1) { + int receivedSize = recvfrom( ::g->insocket, &sw, sizeof( doomdata_t ), MSG_DONTWAIT, &fromaddress, &fromlen ); + //c = WSARecvFrom(::g->insocket, &buffer, 1, &num_recieved, &flags, (struct sockaddr*)&fromaddress, &fromlen, 0, 0); + + if ( receivedSize < 0 ) + { + int err = sys_net_errno; + if (err != SYS_NET_EWOULDBLOCK ) { + I_Error ("GetPacket: %d", err ); + //I_Printf ("GetPacket: %s",strerror(errno)); + } + return; + } + + printf( "RECEIVED PACKET!!\n" ); + + int source; + int dest; + GetSourceDest( sw.sourceDest, &source, &dest ); + + //Push the packet onto the network stack to be processed. + DoomLib::Send( (char*)&sw, receivedSize, NULL, dest ); + } + } +#endif +} + +void DoomLib::SendNetwork() { + + if ( !globalNetworking ) { + return; + } + DoomLib::SendRemote(); +} + +void DoomLib::RunSound() { + + I_ProcessSoundEvents(); +} + diff --git a/doomclassic/doom/doomlib.h b/doomclassic/doom/doomlib.h new file mode 100644 index 00000000..1af57c05 --- /dev/null +++ b/doomclassic/doom/doomlib.h @@ -0,0 +1,169 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef _DOOM_LIB_H +#define _DOOM_LIB_H + + +#include "doomtype.h" +#include "doomdef.h" +#include "doominterface.h" + +#include "idlib/containers/Array.h" + +class idSysMutex; +class idUserCmdMgr; + +#define IN_NUM_DIGITAL_BUTTONS 8 +#define IN_NUM_ANALOG_BUTTONS 8 +// Cutoff where the analog buttons are considered to be "pressed" +// This should be smarter. +#define IN_ANALOG_BUTTON_THRESHOLD 64 + +extern idCVar s_volume_sound; +extern idCVar s_volume_midi; +extern idCVar m_show_messages; +extern idCVar m_inDemoMode; +struct rumble_t +{ + int feedback; // SMF // XINPUT_FEEDBACK feedback; + int endTime; + + // The following values are needed, becuase a rumble + // can fail, if it hasn't processed the previous one yet, + // so, it must be stored + bool waiting; + int left; + int right; + +}; + +enum gameSKU_t { + GAME_SKU_DCC = 0, // Doom Classic Complete + GAME_SKU_DOOM1_BFG, // Doom 1 Ran from BFG + GAME_SKU_DOOM2_BFG, // Doom 2 Ran from BFG + +}; + +/* +================================================================================================ + ExpansionData +================================================================================================ +*/ +struct ExpansionData { + + enum { IWAD = 0, PWAD = 1 } type; + GameMode_t gameMode; + GameMission_t pack_type; + const char * expansionName; + const char * iWadFilename; + const char * pWadFilename; + const char * saveImageFile; + const char ** mapNames; + + +}; + + + +namespace DoomLib +{ + typedef int ( *RecvFunc)( char* buff, DWORD *numRecv ); + typedef int ( *SendFunc)( const char* buff, DWORD size, sockaddr_in *target, int toNode ); + typedef int ( *SendRemoteFunc)(); + + void InitGlobals( void *ptr = NULL ); + void InitGame( int argc, char ** argv ); + void InitControlRemap(); + keyNum_t RemapControl( keyNum_t key ); + bool Poll(); + bool Tic( idUserCmdMgr * userCmdMgr ); + void Wipe(); + void Frame( int realoffset = 0, int buffer = 0 ); + void Draw(); + void Shutdown(); + + void SetNetworking( RecvFunc rf, SendFunc sf, SendRemoteFunc sendRemote ); + + void SetPlayer( int id ); + int GetPlayer(); + + byte BuildSourceDest( int toNode ); + void GetSourceDest( byte sourceDest, int* source, int* dest ); + + int RemoteNodeToPlayerIndex( int node ); + int PlayerIndexToRemoteNode( int index ); + + void PollNetwork(); + void SendNetwork(); + + void *GetGlobalData( int player ); + + void RunSound(); + + extern RecvFunc Recv; + extern SendFunc Send; + extern SendRemoteFunc SendRemote; + + extern void* (*Z_Malloc)( int size, int tag, void* user ); + extern void (*Z_FreeTag)(int lowtag ); + + extern DoomInterface Interface; + extern int idealExpansion; + extern int expansionSelected; + extern bool expansionDirty; + + extern bool skipToLoad; + extern char loadGamePath[MAX_PATH]; + + extern bool skipToNew; + extern int chosenSkill; + extern int chosenEpisode; + + extern idMatchParameters matchParms; + + const ExpansionData * GetCurrentExpansion(); + void SetCurrentExpansion( int expansion ); + + void SetIdealExpansion( int expansion ); + + void SetCurrentMapName( idStr name ); + const idStr & GetCurrentMapName(); + void SetCurrentDifficulty( idStr name ); + const idStr & GetCurrentDifficulty(); + + void ActivateGame(); + void ExitGame(); + void ShowXToContinue( bool activate ); + gameSKU_t GetGameSKU(); + void HandleEndMatch(); + +}; + + +#endif diff --git a/doomclassic/doom/doomstat.cpp b/doomclassic/doom/doomstat.cpp new file mode 100644 index 00000000..3b5d3b2a --- /dev/null +++ b/doomclassic/doom/doomstat.cpp @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#ifdef __GNUG__ +#pragma implementation "doomstat.h" +#endif +#include "doomstat.h" + + +// Game Mode - identify IWAD as shareware, retail etc. + +// Language. + +// Set if homebrew PWAD stuff has been added. + + + + + diff --git a/doomclassic/doom/doomstat.h b/doomclassic/doom/doomstat.h new file mode 100644 index 00000000..d6194ca0 --- /dev/null +++ b/doomclassic/doom/doomstat.h @@ -0,0 +1,285 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __D_STATE__ +#define __D_STATE__ + +// We need globally shared data structures, +// for defining the global state variables. +#include "doomdata.h" +#include "d_net.h" + +// We need the playr data structure as well. +#include "d_player.h" + + +#ifdef __GNUG__ +#pragma interface +#endif + + + +// ------------------------ +// Command line parameters. +// +extern qboolean nomonsters; // checkparm of -nomonsters +extern qboolean respawnparm; // checkparm of -respawn +extern qboolean fastparm; // checkparm of -fast + +extern qboolean devparm; // DEBUG: launched with -devparm + + + +// ----------------------------------------------------- +// Game Mode - identify IWAD as shareware, retail etc. +// +extern GameMode_t gamemode; +extern int gamemission; + +// Set if homebrew PWAD stuff has been added. +extern qboolean modifiedgame; + + +// ------------------------------------------- +// Language. +extern Language_t language; + + +// ------------------------------------------- +// Selected skill type, map etc. +// + +// Defaults for menu, methinks. +extern skill_t startskill; +extern int startepisode; +extern int startmap; + +extern qboolean autostart; + +// Selected by user. +extern skill_t gameskill; +extern int gameepisode; +extern int gamemap; + +// Nightmare mode flag, single player. +extern qboolean respawnmonsters; + +// Netgame? Only true if >1 player. +extern qboolean netgame; + +// Flag: true only if started as net deathmatch. +// An enum might handle altdeath/cooperative better. +extern qboolean deathmatch; + +// ------------------------- +// Internal parameters for sound rendering. +// These have been taken from the DOS version, +// but are not (yet) supported with Linux +// (e.g. no sound volume adjustment with menu. + + +// Current music/sfx card - index useless +// w/o a reference LUT in a sound module. +// Ideally, this would use indices found +// in: /usr/include/linux/soundcard.h +extern int snd_MusicDevice; +extern int snd_SfxDevice; +// Config file? Same disclaimer as above. +extern int snd_DesiredMusicDevice; +extern int snd_DesiredSfxDevice; + + +// ------------------------- +// Status flags for refresh. +// + +// Depending on view size - no status bar? +// Note that there is no way to disable the +// status bar explicitely. +extern qboolean statusbaractive; + +extern qboolean automapactive; // In AutoMap mode? +extern qboolean menuactive; // Menu overlayed? +extern qboolean paused; // Game Pause? + + +extern qboolean viewactive; + +extern qboolean nodrawers; +extern qboolean noblit; + +extern int viewwindowx; +extern int viewwindowy; +extern int viewheight; +extern int viewwidth; +extern int scaledviewwidth; + + + + + + +// This one is related to the 3-screen display mode. +// ANG90 = left side, ANG270 = right +extern int viewangleoffset; + +// Player taking events, and displaying. +extern int consoleplayer; +extern int displayplayer; + + +// ------------------------------------- +// Scores, rating. +// Statistics on a given map, for intermission. +// +extern int totalkills; +extern int totalitems; +extern int totalsecret; + +// Timer, for scores. +extern int levelstarttic; // gametic at level start +extern int leveltime; // tics in game play for par + + + +// -------------------------------------- +// DEMO playback/recording related stuff. +// No demo, there is a human player in charge? +// Disable save/end game? +extern qboolean usergame; + +//? +extern qboolean demoplayback; + +// Quit after playing a demo from cmdline. +extern qboolean singledemo; + + + + +//? +extern gamestate_t gamestate; + + + + + + +//----------------------------- +// Internal parameters, fixed. +// These are set by the engine, and not changed +// according to user inputs. Partly load from +// WAD, partly set at startup time. + + + +extern int gametic; + + +// Bookkeeping on players - state. +extern player_t players[MAXPLAYERS]; + +// Alive? Disconnected? +extern qboolean playeringame[MAXPLAYERS]; + + +// Player spawn spots for deathmatch. +#define MAX_DM_STARTS 10 +extern mapthing_t deathmatchstarts[MAX_DM_STARTS]; +extern mapthing_t* deathmatch_p; + +// Player spawn spots. +extern mapthing_t playerstarts[MAXPLAYERS]; + +// Intermission stats. +// Parameters for world map / intermission. +extern wbstartstruct_t wminfo; + + +// LUT of ammunition limits for each kind. +// This doubles with BackPack powerup item. +const extern int maxammo[NUMAMMO]; + + + + + +//----------------------------------------- +// Internal parameters, used for engine. +// + +// File handling stuff. +extern char basedefault[1024]; +extern FILE* debugfile; + +// if true, load all graphics at level load +extern qboolean precache; + + +// wipegamestate can be set to -1 +// to force a wipe on the next draw +extern gamestate_t wipegamestate; + +extern int mouseSensitivity; +//? +// debug flag to cancel adaptiveness +extern qboolean singletics; + +extern int bodyqueslot; + + + +// Needed to store the number of the dummy sky flat. +// Used for rendering, +// as well as tracking projectiles etc. +extern int skyflatnum; + + + +// Netgame stuff (buffers and pointers, i.e. indices). + +// This is ??? +extern doomcom_t doomcom; + +// This points inside doomcom. +extern doomdata_t* netbuffer; + + +extern ticcmd_t localcmds[BACKUPTICS]; +extern int rndindex; + +extern int maketic; +extern int nettics[MAXNETNODES]; + +extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; +extern int ticdup; + + + +#endif + diff --git a/doomclassic/doom/doomtype.h b/doomclassic/doom/doomtype.h new file mode 100644 index 00000000..18322e8b --- /dev/null +++ b/doomclassic/doom/doomtype.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DOOMTYPE__ +#define __DOOMTYPE__ + +#include + +#define false 0 +#define true 1 +typedef int qboolean; + + + +typedef float FLOAT; + + + + +#endif \ No newline at end of file diff --git a/doomclassic/doom/dstrings.cpp b/doomclassic/doom/dstrings.cpp new file mode 100644 index 00000000..9c419697 --- /dev/null +++ b/doomclassic/doom/dstrings.cpp @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#ifdef __GNUG__ +#pragma implementation "dstrings.h" +#endif +#include "dstrings.h" + + + +const char* endmsg[NUM_QUITMESSAGES+1]= +{ + // DOOM1 + QUITMSG, + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?" + + // QuitDOOM II messages + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?" + + // FinalDOOM? + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + "are you sure you want to quit?", + + // Internal debug. Different style, too. + "are you sure you want to quit?" +}; + + + + + + diff --git a/doomclassic/doom/dstrings.h b/doomclassic/doom/dstrings.h new file mode 100644 index 00000000..fd8943a1 --- /dev/null +++ b/doomclassic/doom/dstrings.h @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DSTRINGS__ +#define __DSTRINGS__ + + +// All important printed strings. +// Language selection (message strings). +// Use -DFRENCH etc. + +#include "d_englsh.h" + +// Misc. other strings. +#define SAVEGAMENAME "doomsav" + + +// +// File locations, +// relative to current position. +// Path names are OS-sensitive. +// +#define DEVMAPS "devmaps" +#define DEVDATA "devdata" + + +// QuitDOOM messages +#define NUM_QUITMESSAGES 22 + +extern const char* endmsg[]; + + +#endif + diff --git a/doomclassic/doom/f_finale.cpp b/doomclassic/doom/f_finale.cpp new file mode 100644 index 00000000..497f7eb6 --- /dev/null +++ b/doomclassic/doom/f_finale.cpp @@ -0,0 +1,816 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +// Functions. +#include "i_system.h" +#include "m_swap.h" +#include "z_zone.h" +#include "v_video.h" +#include "w_wad.h" +#include "s_sound.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +#include "doomstat.h" +#include "r_state.h" + +#include "Main.h" +#include "d3xp/Game_local.h" + +// ? +//#include "doomstat.h" +//#include "r_local.h" +//#include "f_finale.h" + +// Stage of animation: +// 0 = text, 1 = art screen, 2 = character cast + + + +const char* e1text = E1TEXT; +const char* e2text = E2TEXT; +const char* e3text = E3TEXT; +const char* e4text = E4TEXT; + +const char* c1text = C1TEXT; +const char* c2text = C2TEXT; +const char* c3text = C3TEXT; +const char* c4text = C4TEXT; +const char* c5text = C5TEXT; +const char* c6text = C6TEXT; +const char* c7text = C7TEXT; +const char* c8Text = C8TEXT; + +const char* p1text = P1TEXT; +const char* p2text = P2TEXT; +const char* p3text = P3TEXT; +const char* p4text = P4TEXT; +const char* p5text = P5TEXT; +const char* p6text = P6TEXT; + +const char* t1text = T1TEXT; +const char* t2text = T2TEXT; +const char* t3text = T3TEXT; +const char* t4text = T4TEXT; +const char* t5text = T5TEXT; +const char* t6text = T6TEXT; + +const char* finaletext; +const char* finaleflat; + +void F_StartCast (void); +void F_CastTicker (void); +qboolean F_CastResponder (event_t *ev); +void F_CastDrawer (void); + +// +// F_StartFinale +// +void F_StartFinale (void) +{ + ::g->gameaction = ga_nothing; + ::g->gamestate = GS_FINALE; + ::g->viewactive = false; + ::g->automapactive = false; + + // Check for end of episode/mission + bool endOfMission = false; + + if ( ( ::g->gamemission == doom || ::g->gamemission == doom2 || ::g->gamemission == pack_tnt || ::g->gamemission == pack_plut ) && ::g->gamemap == 30 ) { + endOfMission = true; + } + else if ( ::g->gamemission == pack_nerve && ::g->gamemap == 8 ) { + endOfMission = true; + } + else if ( ::g->gamemission == pack_master && ::g->gamemap == 21 ) { + endOfMission = true; + } + + localCalculateAchievements( endOfMission ); + + // Okay - IWAD dependend stuff. + // This has been changed severly, and + // some stuff might have changed in the process. + switch ( ::g->gamemode ) + { + + // DOOM 1 - E1, E3 or E4, but each nine missions + case shareware: + case registered: + case retail: + { + S_ChangeMusic(mus_victor, true); + + switch (::g->gameepisode) + { + case 1: + finaleflat = "FLOOR4_8"; + finaletext = e1text; + break; + case 2: + finaleflat = "SFLR6_1"; + finaletext = e2text; + break; + case 3: + finaleflat = "MFLR8_4"; + finaletext = e3text; + break; + case 4: + finaleflat = "MFLR8_3"; + finaletext = e4text; + break; + default: + // Ouch. + break; + } + break; + } + + // DOOM II and missions packs with E1, M34 + case commercial: + { + S_ChangeMusic(mus_read_m, true); + + if ( ::g->gamemission == doom2 || ::g->gamemission == pack_tnt || ::g->gamemission == pack_plut ) { + switch (::g->gamemap) + { + case 6: + finaleflat = "SLIME16"; + finaletext = c1text; + break; + case 11: + finaleflat = "RROCK14"; + finaletext = c2text; + break; + case 20: + finaleflat = "RROCK07"; + finaletext = c3text; + break; + case 30: + finaleflat = "RROCK17"; + finaletext = c4text; + break; + case 15: + finaleflat = "RROCK13"; + finaletext = c5text; + break; + case 31: + finaleflat = "RROCK19"; + finaletext = c6text; + break; + default: + // Ouch. + break; + } + } else if( ::g->gamemission == pack_master ) { + switch (::g->gamemap) + { + case 21: + finaleflat = "SLIME16"; + finaletext = c8Text; + break; + } + } else if ( ::g->gamemission == pack_nerve ) { + switch( ::g->gamemap ){ + case 8: + finaleflat = "SLIME16"; + finaletext = c7text; + break; + } + } + + break; + } + + // Indeterminate. + default: + S_ChangeMusic(mus_read_m, true); + finaleflat = "F_SKY1"; // Not used anywhere else. + finaletext = c1text; // FIXME - other text, music? + break; + } + + ::g->finalestage = 0; + ::g->finalecount = 0; +} + + +bool finaleButtonPressed = false; +bool startButtonPressed = false; +qboolean F_Responder (event_t *event) +{ + if( !common->IsMultiplayer() && event->type == ev_keydown && event->data1 == KEY_ESCAPE ) { + startButtonPressed = true; + return true; + } + + if (::g->finalestage == 2) + return F_CastResponder (event); + + return false; +} + + +// +// F_Ticker +// +void F_Ticker (void) +{ + int i; + + // check for skipping + if ( (::g->gamemode == commercial) && ( ::g->finalecount > 50) ) + { + // go on to the next level + for (i=0 ; iplayers[i].cmd.buttons) + break; + + if ( finaleButtonPressed || i < MAXPLAYERS) + { + bool castStarted = false; + if( ::g->gamemission == doom2 || ::g->gamemission == pack_plut || ::g->gamemission == pack_tnt ) { + if (::g->gamemap == 30) { + F_StartCast (); + castStarted = true; + } + + } else if( ::g->gamemission == pack_master ) { + if( :: g->gamemap == 21 ) { + F_StartCast (); + castStarted = true; + } + + } else if( ::g->gamemission == pack_nerve ) { + if( :: g->gamemap == 8 ) { + F_StartCast (); + castStarted = true; + } + + } + + if( castStarted == false ) { + ::g->gameaction = ga_worlddone; + } + } + } + + bool SkipTheText = finaleButtonPressed; + + // advance animation + ::g->finalecount++; + finaleButtonPressed = false; + + if (::g->finalestage == 2) + { + F_CastTicker (); + return; + } + + if ( ::g->gamemode == commercial) { + startButtonPressed = false; + return; + } + + if( SkipTheText && ( ::g->finalecount > 50) ) { + ::g->finalecount = static_cast(strlen(finaletext)) * TEXTSPEED + TEXTWAIT; + } + + if (!::g->finalestage && ::g->finalecount > static_cast(strlen(finaletext)) * TEXTSPEED + TEXTWAIT) + { + ::g->finalecount = 0; + ::g->finalestage = 1; + ::g->wipegamestate = (gamestate_t)-1; // force a wipe + if (::g->gameepisode == 3) + S_StartMusic (mus_bunny); + } + + startButtonPressed = false; + +} + + + +// +// F_TextWrite +// + +#include "hu_stuff.h" + + +void F_TextWrite (void) +{ + byte* src; + byte* dest; + + int x,y,w; + int count; + const char* ch; + int c; + int cx; + int cy; + + if(::g->finalecount == 60 ) { + DoomLib::ShowXToContinue( true ); + } + + // erase the entire screen to a tiled background + src = (byte*)W_CacheLumpName ( finaleflat , PU_CACHE_SHARED); + dest = ::g->screens[0]; + + for (y=0 ; yfinalecount - 10)/TEXTSPEED; + if (count < 0) + count = 0; + for ( ; count ; count-- ) + { + c = *ch++; + if (!c) + break; + if (c == '\n') + { + cx = 10; + cy += 11; + continue; + } + + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c> HU_FONTSIZE) + { + cx += 4; + continue; + } + + w = SHORT (::g->hu_font[c]->width); + if (cx+w > SCREENWIDTH) + break; + V_DrawPatch(cx, cy, 0, ::g->hu_font[c]); + cx+=w; + } + +} + +// +// Final DOOM 2 animation +// Casting by id Software. +// in order of appearance +// + +castinfo_t castorder[] = +{ + {CC_ZOMBIE, MT_POSSESSED}, + {CC_SHOTGUN, MT_SHOTGUY}, + {CC_HEAVY, MT_CHAINGUY}, + {CC_IMP, MT_TROOP}, + {CC_DEMON, MT_SERGEANT}, + {CC_LOST, MT_SKULL}, + {CC_CACO, MT_HEAD}, + {CC_HELL, MT_KNIGHT}, + {CC_BARON, MT_BRUISER}, + {CC_ARACH, MT_BABY}, + {CC_PAIN, MT_PAIN}, + {CC_REVEN, MT_UNDEAD}, + {CC_MANCU, MT_FATSO}, + {CC_ARCH, MT_VILE}, + {CC_SPIDER, MT_SPIDER}, + {CC_CYBER, MT_CYBORG}, + {CC_HERO, MT_PLAYER}, + + {NULL,(mobjtype_t)0} +}; + + + +// +// F_StartCast +// + + +void F_StartCast (void) +{ + if ( ::g->finalestage != 2 ) { + ::g->wipegamestate = (gamestate_t)-1; // force a screen wipe + ::g->castnum = 0; + ::g->caststate = &::g->states[mobjinfo[castorder[::g->castnum].type].seestate]; + ::g->casttics = ::g->caststate->tics; + ::g->castdeath = false; + ::g->finalestage = 2; + ::g->castframes = 0; + ::g->castonmelee = 0; + ::g->castattacking = false; + S_ChangeMusic(mus_evil, true); + + ::g->caststartmenu = ::g->finalecount + 50; + } +} + + +// +// F_CastTicker +// +void F_CastTicker (void) +{ + int st; + int sfx; + + if( ::g->finalecount == ::g->caststartmenu ) { + DoomLib::ShowXToContinue( true ); + } + + if (--::g->casttics > 0) + return; // not time to change state yet + + if (::g->caststate->tics == -1 || ::g->caststate->nextstate == S_NULL) + { + // switch from deathstate to next monster + ::g->castnum++; + ::g->castdeath = false; + if (castorder[::g->castnum].name == NULL) + ::g->castnum = 0; + if (mobjinfo[castorder[::g->castnum].type].seesound) + S_StartSound (NULL, mobjinfo[castorder[::g->castnum].type].seesound); + ::g->caststate = &::g->states[mobjinfo[castorder[::g->castnum].type].seestate]; + ::g->castframes = 0; + } + else + { + // just advance to next state in animation + if (::g->caststate == &::g->states[S_PLAY_ATK1]) + goto stopattack; // Oh, gross hack! + st = ::g->caststate->nextstate; + ::g->caststate = &::g->states[st]; + ::g->castframes++; + + // sound hacks.... + switch (st) + { + case S_PLAY_ATK1: sfx = sfx_dshtgn; break; + case S_POSS_ATK2: sfx = sfx_pistol; break; + case S_SPOS_ATK2: sfx = sfx_shotgn; break; + case S_VILE_ATK2: sfx = sfx_vilatk; break; + case S_SKEL_FIST2: sfx = sfx_skeswg; break; + case S_SKEL_FIST4: sfx = sfx_skepch; break; + case S_SKEL_MISS2: sfx = sfx_skeatk; break; + case S_FATT_ATK8: + case S_FATT_ATK5: + case S_FATT_ATK2: sfx = sfx_firsht; break; + case S_CPOS_ATK2: + case S_CPOS_ATK3: + case S_CPOS_ATK4: sfx = sfx_shotgn; break; + case S_TROO_ATK3: sfx = sfx_claw; break; + case S_SARG_ATK2: sfx = sfx_sgtatk; break; + case S_BOSS_ATK2: + case S_BOS2_ATK2: + case S_HEAD_ATK2: sfx = sfx_firsht; break; + case S_SKULL_ATK2: sfx = sfx_sklatk; break; + case S_SPID_ATK2: + case S_SPID_ATK3: sfx = sfx_shotgn; break; + case S_BSPI_ATK2: sfx = sfx_plasma; break; + case S_CYBER_ATK2: + case S_CYBER_ATK4: + case S_CYBER_ATK6: sfx = sfx_rlaunc; break; + case S_PAIN_ATK3: sfx = sfx_sklatk; break; + default: sfx = 0; break; + } + + if (sfx) + S_StartSound (NULL, sfx); + } + + if (::g->castframes == 12) + { + // go into attack frame + ::g->castattacking = true; + if (::g->castonmelee) + ::g->caststate=&::g->states[mobjinfo[castorder[::g->castnum].type].meleestate]; + else + ::g->caststate=&::g->states[mobjinfo[castorder[::g->castnum].type].missilestate]; + ::g->castonmelee ^= 1; + if (::g->caststate == &::g->states[S_NULL]) + { + if (::g->castonmelee) + ::g->caststate= + &::g->states[mobjinfo[castorder[::g->castnum].type].meleestate]; + else + ::g->caststate= + &::g->states[mobjinfo[castorder[::g->castnum].type].missilestate]; + } + } + + if (::g->castattacking) + { + if (::g->castframes == 24 + || ::g->caststate == &::g->states[mobjinfo[castorder[::g->castnum].type].seestate] ) + { + stopattack: + ::g->castattacking = false; + ::g->castframes = 0; + ::g->caststate = &::g->states[mobjinfo[castorder[::g->castnum].type].seestate]; + } + } + + ::g->casttics = ::g->caststate->tics; + if (::g->casttics == -1) + ::g->casttics = 15; +} + + +// +// F_CastResponder +// + +qboolean F_CastResponder (event_t* ev) +{ + if (ev->type != ev_keydown) + return false; + + if (::g->castdeath) + return true; // already in dying frames + + // go into death frame + ::g->castdeath = true; + ::g->caststate = &::g->states[mobjinfo[castorder[::g->castnum].type].deathstate]; + ::g->casttics = ::g->caststate->tics; + ::g->castframes = 0; + ::g->castattacking = false; + if (mobjinfo[castorder[::g->castnum].type].deathsound) + S_StartSound (NULL, mobjinfo[castorder[::g->castnum].type].deathsound); + + return true; +} + + +void F_CastPrint (char* text) +{ + char* ch; + int c; + int cx; + int w; + int width; + + // find width + ch = text; + width = 0; + + while (ch) + { + c = *ch++; + if (!c) + break; + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c> HU_FONTSIZE) + { + width += 4; + continue; + } + + w = SHORT (::g->hu_font[c]->width); + width += w; + } + + // draw it + cx = 160-width/2; + ch = text; + while (ch) + { + c = *ch++; + if (!c) + break; + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c> HU_FONTSIZE) + { + cx += 4; + continue; + } + + w = SHORT (::g->hu_font[c]->width); + V_DrawPatch(cx, 180, 0, ::g->hu_font[c]); + cx+=w; + } + +} + + +// +// F_CastDrawer +// +void V_DrawPatchFlipped (int x, int y, int scrn, patch_t *patch); + +void F_CastDrawer (void) +{ + spritedef_t* sprdef; + spriteframe_t* sprframe; + int lump; + qboolean flip; + patch_t* patch; + + // erase the entire screen to a background + V_DrawPatch (0,0,0, (patch_t*)W_CacheLumpName ("BOSSBACK", PU_CACHE_SHARED)); + + F_CastPrint (castorder[::g->castnum].name); + + // draw the current frame in the middle of the screen + sprdef = &::g->sprites[::g->caststate->sprite]; + sprframe = &sprdef->spriteframes[ ::g->caststate->frame & FF_FRAMEMASK]; + lump = sprframe->lump[0]; + flip = (qboolean)sprframe->flip[0]; + + patch = (patch_t*)W_CacheLumpNum (lump+::g->firstspritelump, PU_CACHE_SHARED); + if (flip) + V_DrawPatchFlipped (160,170,0,patch); + else + V_DrawPatch (160,170,0,patch); +} + + +// +// F_DrawPatchCol +// +void +F_DrawPatchCol( int x, patch_t* patch, int col ) { + postColumn_t* column; + byte* source; + int count; + + column = (postColumn_t *)((byte *)patch + LONG(patch->columnofs[col])); + + int destx = x; + int desty = 0; + + // step through the posts in a column + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + desty = column->topdelta; + count = column->length; + + while (count--) + { + int scaledx, scaledy; + scaledx = destx * GLOBAL_IMAGE_SCALER; + scaledy = desty * GLOBAL_IMAGE_SCALER; + byte src = *source++; + + for ( int i = 0; i < GLOBAL_IMAGE_SCALER; i++ ) { + for ( int j = 0; j < GLOBAL_IMAGE_SCALER; j++ ) { + ::g->screens[0][( scaledx + j ) + ( scaledy + i ) * SCREENWIDTH] = src; + } + } + + desty++; + } + column = (postColumn_t *)( (byte *)column + column->length + 4 ); + } +} + + +// +// F_BunnyScroll +// +void F_BunnyScroll (void) +{ + int scrolled; + int x; + patch_t* p1; + patch_t* p2; + char name[10]; + int stage; + + p1 = (patch_t*)W_CacheLumpName ("PFUB2", PU_LEVEL_SHARED); + p2 = (patch_t*)W_CacheLumpName ("PFUB1", PU_LEVEL_SHARED); + + V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT); + + scrolled = 320 - (::g->finalecount-230)/2; + if (scrolled > 320) + scrolled = 320; + if (scrolled < 0) + scrolled = 0; + + for ( x=0 ; xfinalecount < 1130) + return; + if (::g->finalecount < 1180) + { + V_DrawPatch ((ORIGINAL_WIDTH-13*8)/2, + (ORIGINAL_HEIGHT-8*8)/2,0, (patch_t*)W_CacheLumpName ("END0",PU_CACHE_SHARED)); + ::g->laststage = 0; + return; + } + + stage = (::g->finalecount-1180) / 5; + if (stage > 6) + stage = 6; + if (stage > ::g->laststage) + { + S_StartSound (NULL, sfx_pistol); + ::g->laststage = stage; + } + + sprintf (name,"END%i",stage); + V_DrawPatch ((ORIGINAL_WIDTH-13*8)/2, (ORIGINAL_HEIGHT-8*8)/2,0, (patch_t*)W_CacheLumpName (name,PU_CACHE_SHARED)); +} + + +// +// F_Drawer +// +void F_Drawer (void) +{ + if (::g->finalestage == 2) + { + F_CastDrawer (); + return; + } + + if (!::g->finalestage) + F_TextWrite (); + else + { + switch (::g->gameepisode) + { + case 1: + if ( ::g->gamemode == retail ) + V_DrawPatch (0,0,0, + (patch_t*)W_CacheLumpName("CREDIT",PU_CACHE_SHARED)); + else + V_DrawPatch (0,0,0, + (patch_t*)W_CacheLumpName("HELP2",PU_CACHE_SHARED)); + break; + case 2: + V_DrawPatch(0,0,0, + (patch_t*)W_CacheLumpName("VICTORY2",PU_CACHE_SHARED)); + break; + case 3: + F_BunnyScroll (); + break; + case 4: + V_DrawPatch (0,0,0, + (patch_t*)W_CacheLumpName("ENDPIC",PU_CACHE_SHARED)); + break; + } + } + +} + + + diff --git a/doomclassic/doom/f_finale.h b/doomclassic/doom/f_finale.h new file mode 100644 index 00000000..6d54d46b --- /dev/null +++ b/doomclassic/doom/f_finale.h @@ -0,0 +1,55 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __F_FINALE__ +#define __F_FINALE__ + + +#include "doomtype.h" +#include "d_event.h" +// +// FINALE +// + +// Called by main loop. +qboolean F_Responder (event_t* ev); + +// Called by main loop. +void F_Ticker (void); + +// Called by main loop. +void F_Drawer (void); + + +void F_StartFinale (void); + + + + +#endif + diff --git a/doomclassic/doom/f_wipe.cpp b/doomclassic/doom/f_wipe.cpp new file mode 100644 index 00000000..75c52e61 --- /dev/null +++ b/doomclassic/doom/f_wipe.cpp @@ -0,0 +1,235 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "z_zone.h" +#include "i_video.h" +#include "i_system.h" +#include "v_video.h" +#include "m_random.h" +#include "doomdef.h" +#include "f_wipe.h" + +// +// SCREEN WIPE PACKAGE +// + +// when zero, stop the wipe + + +void +wipe_shittyColMajorXform +( short* array, + int width, + int height ) +{ + int x; + int y; + short* dest; + + //dest = (short*) DoomLib::Z_Malloc(width*height*2, PU_STATIC, 0 ); + dest = new short[ width * height ]; + + for(y=0;ywipe_scr, ::g->wipe_scr_start, width*height); + + // makes this wipe faster (in theory) + // to have stuff in column-major format + wipe_shittyColMajorXform((short*)::g->wipe_scr_start, width/2, height); + wipe_shittyColMajorXform((short*)::g->wipe_scr_end, width/2, height); + + // setup initial column positions + // (::g->wipe_y<0 => not ready to scroll yet) + ::g->wipe_y = (int *) DoomLib::Z_Malloc(width*sizeof(int), PU_STATIC, 0); + + ::g->wipe_y[0] = -(M_Random()%16); + + for (i=1;iwipe_y[i] = ::g->wipe_y[i-1] + r; + + if (::g->wipe_y[i] > 0) + ::g->wipe_y[i] = 0; + else if (::g->wipe_y[i] == -16) + ::g->wipe_y[i] = -15; + } + + return 0; +} + +int wipe_doMelt( int width, int height, int ticks ) { + int i; + int j; + int dy; + int idx; + + short* s; + short* d; + qboolean done = true; + + width/=2; + + while (ticks--) + { + for (i=0;iwipe_y[i]<0) { + + ::g->wipe_y[i]++; + done = false; + } + else if (::g->wipe_y[i] < height) { + + dy = (::g->wipe_y[i] < 16 * GLOBAL_IMAGE_SCALER) ? ::g->wipe_y[i]+1 : 8 * GLOBAL_IMAGE_SCALER; + + if (::g->wipe_y[i]+dy >= height) + dy = height - ::g->wipe_y[i]; + + s = &((short *)::g->wipe_scr_end)[i*height+::g->wipe_y[i]]; + d = &((short *)::g->wipe_scr)[::g->wipe_y[i]*width+i]; + + idx = 0; + for (j=dy;j;j--) + { + d[idx] = *(s++); + idx += width; + } + + ::g->wipe_y[i] += dy; + + s = &((short *)::g->wipe_scr_start)[i*height]; + d = &((short *)::g->wipe_scr)[::g->wipe_y[i]*width+i]; + + idx = 0; + for (j=height-::g->wipe_y[i];j;j--) + { + d[idx] = *(s++); + idx += width; + } + + done = false; + } + } + } + + return done; +} + +int +wipe_exitMelt +( int width, + int height, + int ticks ) +{ + Z_Free(::g->wipe_y); + ::g->wipe_y = NULL; + return 0; +} + +int +wipe_StartScreen +( int x, + int y, + int width, + int height ) +{ + ::g->wipe_scr_start = ::g->screens[2]; + I_ReadScreen(::g->wipe_scr_start); + return 0; +} + +int +wipe_EndScreen +( int x, + int y, + int width, + int height ) +{ + ::g->wipe_scr_end = ::g->screens[3]; + I_ReadScreen(::g->wipe_scr_end); + V_DrawBlock(x, y, 0, width, height, ::g->wipe_scr_start); // restore start scr. + return 0; +} + +int +wipe_ScreenWipe +( int x, + int y, + int width, + int height, + int ticks ) +{ + int rc; + + // initial stuff + if (!::g->go) + { + ::g->go = 1; + ::g->wipe_scr = ::g->screens[0]; + + wipe_initMelt(width, height, ticks); + } + + // do a piece of wipe-in + V_MarkRect(0, 0, width, height); + + rc = wipe_doMelt(width, height, ticks); + + // final stuff + if (rc) + { + ::g->go = 0; + wipe_exitMelt(width, height, ticks); + } + + return !::g->go; +} + diff --git a/doomclassic/doom/f_wipe.h b/doomclassic/doom/f_wipe.h new file mode 100644 index 00000000..00723d55 --- /dev/null +++ b/doomclassic/doom/f_wipe.h @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __F_WIPE_H__ +#define __F_WIPE_H__ + +// +// SCREEN WIPE PACKAGE +// + +int +wipe_StartScreen +( int x, + int y, + int width, + int height ); + + +int +wipe_EndScreen +( int x, + int y, + int width, + int height ); + + +int +wipe_ScreenWipe +( int x, + int y, + int width, + int height, + int ticks ); + +#endif + diff --git a/doomclassic/doom/g_game.cpp b/doomclassic/doom/g_game.cpp new file mode 100644 index 00000000..2277dd4b --- /dev/null +++ b/doomclassic/doom/g_game.cpp @@ -0,0 +1,2015 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" +#include "Main.h" +#include "sys/sys_signin.h" +#include "d3xp/Game_local.h" + + +#include +#include + +#include "doomdef.h" +#include "doomstat.h" + +#include "z_zone.h" +#include "f_finale.h" +#include "m_argv.h" +#include "m_misc.h" +#include "m_menu.h" +#include "m_random.h" +#include "i_system.h" + +#include "p_setup.h" +#include "p_saveg.h" +#include "p_tick.h" + +#include "d_main.h" + +#include "wi_stuff.h" +#include "hu_stuff.h" +#include "st_stuff.h" +#include "am_map.h" + +// Needs access to LFB. +#include "v_video.h" + +#include "w_wad.h" + +#include "p_local.h" + +#include "s_sound.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +// SKY handling - still the wrong place. +#include "r_data.h" +#include "r_sky.h" + +#include "g_game.h" + +#include "framework/Common.h" +#include "sys/sys_lobby.h" + +#include + + +extern bool waitingForWipe; + +bool loadingGame = false; + +byte demoversion = 0; + +qboolean G_CheckDemoStatus (void); +void G_ReadDemoTiccmd (ticcmd_t* cmd); +void G_WriteDemoTiccmd (ticcmd_t* cmd); +void G_PlayerReborn (int player); +void G_InitNew (skill_t skill, int episode, int map ); + +void G_DoReborn (int playernum); + +void G_DoLoadLevel (); +void G_DoNewGame (void); +qboolean G_DoLoadGame (); +void G_DoPlayDemo (void); +void G_DoCompleted (void); +void G_DoVictory (void); +void G_DoWorldDone (void); +qboolean G_DoSaveGame (void); + + +#define DEBUG_DEMOS +#define DEBUG_DEMOS_WRITE + +#ifdef DEBUG_DEMOS +unsigned char testprndindex = 0; +int printErrorCount = 0; +bool demoDebugOn = false; +#endif + +// +// controls (have defaults) +// + +// mouse values are used once + +// joystick values are repeated + + +int G_CmdChecksum (ticcmd_t* cmd) +{ + int i; + int sum = 0; + + for (i=0 ; i< sizeof(*cmd)/4 - 1 ; i++) + sum += ((int *)cmd)[i]; + + return sum; +} + +// jedi academy meets doom hehehehehehehe +void G_MouseClamp(int *x, int *y) +{ + float ax = (float)fabs((float)*x); + float ay = (float)fabs((float)*y); + + ax = (ax-10)*(0.04676) * (ax-10) * (ax > 10); + ay = (ay-10)*(0.04676) * (ay-10) * (ay > 10); + if (*x < 0) + *x = static_cast(-ax); + else + *x = static_cast(ax); + if (*y < 0) + *y = static_cast(-ay); + else + *y = static_cast(ay); +} + +/* +======================== +Returns true if the player is holding down the run button, or +if they have set "Always run" in the options. Returns false otherwise. +======================== +*/ +bool IsPlayerRunning( const usercmd_t & command ) { + + if( DoomLib::GetPlayer() < 0 ) { + return false; + } + + // DHM - Nerve :: Always Run setting + idLocalUser * user = session->GetSignInManager().GetLocalUserByIndex( DoomLib::GetPlayer() ); + bool autorun = false; + + + // TODO: PC +#if 0 + if( user ) { + idPlayerProfileDoom * profile = static_cast< idPlayerProfileDoom * >( user->GetProfile() ); + + if( profile && profile->GetAlwaysRun() ) { + autorun = true; + } + } +#endif + + if ( command.buttons & BUTTON_RUN ) { + return !autorun; + } + + + + + + return autorun; +} + +/* +======================== +G_PerformImpulse +======================== +*/ +void G_PerformImpulse( const int impulse, ticcmd_t* cmd ) { + + if( impulse == IMPULSE_15 ) { + cmd->buttons |= BT_CHANGE; + cmd->nextPrevWeapon = 1 ; + } else if( impulse == IMPULSE_14 ) { + cmd->buttons |= BT_CHANGE; + cmd->nextPrevWeapon = 2 ; + } + +} + +/* +======================== +Converts a degree value to DOOM format angle value. +======================== +*/ +fixed_t DegreesToDoomAngleTurn( float degrees ) { + const float anglefrac = degrees / 360.0f; + const fixed_t doomangle = anglefrac * std::numeric_limits::max(); + + return doomangle; +} + +// +// G_BuildTiccmd +// Builds a ticcmd from all of the available inputs +// or reads it from the demo buffer. +// If recording a demo, write it out +// +void G_BuildTiccmd (ticcmd_t* cmd, idUserCmdMgr * userCmdMgr, int newTics ) +{ + int i; + int speed; + int tspeed; + int forward; + int side; + + ticcmd_t* base; + + base = I_BaseTiccmd (); // empty, or external driver + memcpy (cmd,base,sizeof(*cmd)); + + cmd->consistancy = ::g->consistancy[::g->consoleplayer][::g->maketic%BACKUPTICS]; + + // Grab the tech5 tic so we can convert it to a doom tic. + if ( userCmdMgr != NULL ) { + const int playerIndex = DoomLib::GetPlayer(); + + if( playerIndex < 0 ) { + return; + } + +#ifdef ID_ENABLE_NETWORKING + const int lobbyIndex = gameLocal->GetLobbyIndexFromDoomLibIndex( playerIndex ); + const idLocalUser * const localUser = session->GetGameLobbyBase().GetLocalUserFromLobbyUser( lobbyIndex ); +#else + const int lobbyIndex = 0; + const idLocalUser * const localUser = session->GetSignInManager().GetMasterLocalUser(); +#endif + + if ( localUser == NULL ) { + return; + } + + usercmd_t * tech5commands[2] = { 0, 0 }; + + const int numCommands = userCmdMgr->GetPlayerCmds( lobbyIndex, tech5commands, 2 ); + + usercmd_t prevTech5Command; + usercmd_t curTech5Command; + + // Use default commands if the manager didn't have enough. + if ( numCommands == 1 ) { + curTech5Command = *(tech5commands)[0]; + } + + if ( numCommands == 2 ) { + prevTech5Command = *(tech5commands)[0]; + curTech5Command = *(tech5commands)[1]; + } + + const bool isRunning = IsPlayerRunning( curTech5Command ); + + // tech5 move commands range from -127 o 127. Scale to doom range of -25 to 25. + const float scaledForward = curTech5Command.forwardmove / 127.0f; + + if ( isRunning ) { + cmd->forwardmove = scaledForward * 50.0f; + } else { + cmd->forwardmove = scaledForward * 25.0f; + } + + // tech5 move commands range from -127 o 127. Scale to doom range of -24 to 24. + const float scaledSide = curTech5Command.rightmove / 127.0f; + + if ( isRunning ) { + cmd->sidemove = scaledSide * 40.0f; + } else { + cmd->sidemove = scaledSide * 24.0f; + } + + idAngles angleDelta; + angleDelta.pitch = SHORT2ANGLE( curTech5Command.angles[ 0 ] ) - SHORT2ANGLE( prevTech5Command.angles[ 0 ] ); + angleDelta.yaw = SHORT2ANGLE( curTech5Command.angles[ 1 ] ) - SHORT2ANGLE( prevTech5Command.angles[ 1 ] ); + angleDelta.roll = 0.0f; + angleDelta.Normalize180(); + + // We will be running a number of tics equal to newTics before we get a new command from tech5. + // So to keep input smooth, divide the angles between all the newTics. + if ( newTics > 0 ) { + angleDelta.yaw /= newTics; + } + + // idAngles is stored in degrees. Convert to doom format. + cmd->angleturn = DegreesToDoomAngleTurn( angleDelta.yaw ); + + + // Translate buttons + //if ( curTech5Command.inhibited == false ) { + // Attack 1 attacks always, whether in the automap or not. + if ( curTech5Command.buttons & BUTTON_ATTACK ) { + cmd->buttons |= BT_ATTACK; + } + +#if 0 + // Attack 2 only attacks if not in the automap, because when in the automap, + // it is the zoom function. + if ( curTech5Command.buttons & BUTTON_ATTACK2 ) { + if ( !::g->automapactive ) { + cmd->buttons |= BT_ATTACK; + } + } +#endif + + // Try to read any impulses that have happened. + static int oldImpulseSequence = 0; + if( oldImpulseSequence != curTech5Command.impulseSequence ) { + G_PerformImpulse( curTech5Command.impulse, cmd ); + } + oldImpulseSequence = curTech5Command.impulseSequence; + + // weapon toggle + for (i=0 ; iKeyState( i + 1 ) ) + { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= (i - 1) <buttons |= BT_USE; + } + + // TODO: PC +#if 0 + if ( curTech5Command.buttons & BUTTON_WEAP_NEXT ) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= 1 << BT_WEAPONSHIFT; + } + + if ( curTech5Command.buttons & BUTTON_WEAP_PREV ) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= 0 << BT_WEAPONSHIFT; + } + + if( curTech5Command.buttons & BUTTON_WEAP_0 ) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= 2 << BT_WEAPONSHIFT; + } + + if( curTech5Command.buttons & BUTTON_WEAP_1 ) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= 3 << BT_WEAPONSHIFT; + } + + if( curTech5Command.buttons & BUTTON_WEAP_2 ) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= 4 << BT_WEAPONSHIFT; + } + + if( curTech5Command.buttons & BUTTON_WEAP_3 ) { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= 5 << BT_WEAPONSHIFT; + } +#endif + + //} + + return; + } + + // DHM - Nerve :: Always Run setting + idLocalUser * user = session->GetSignInManager().GetLocalUserByIndex( DoomLib::GetPlayer() ); + + if( user ) { + // TODO: PC +#if 0 + idPlayerProfileDoom * profile = static_cast< idPlayerProfileDoom * >( user->GetProfile() ); + + if( profile && profile->GetAlwaysRun() ) { + speed = !::g->gamekeydown[::g->key_speed]; + } else +#endif + { + speed = ::g->gamekeydown[::g->key_speed]; + } + + } else { + + // Should not happen. + speed = !::g->gamekeydown[::g->key_speed]; + } + + forward = side = 0; + + // use two stage accelerative turning + // on the keyboard and joystick + if (/*:g->joyxmove != 0 ||*/ ::g->gamekeydown[::g->key_right] || ::g->gamekeydown[::g->key_left] || ::g->mousex != 0) + ::g->turnheld += ::g->ticdup; + else + ::g->turnheld = 0; + + if (::g->turnheld < SLOWTURNTICS) + tspeed = 2; // slow turn + else + tspeed = speed; + + + // clamp for turning + int mousex = ::g->mousex; + int mousey = ::g->mousey; + G_MouseClamp( &mousex, &mousey ); + + if (::g->gamekeydown[::g->key_right] /*|| ::g->joyxmove > 0*/) + cmd->angleturn -= ::g->angleturn[tspeed]; + else if (::g->mousex > 0) { + cmd->angleturn -= tspeed == 1 ? 2 * mousex : mousex; + } + + if (::g->gamekeydown[::g->key_left] /*|| ::g->joyxmove < 0*/) + cmd->angleturn += ::g->angleturn[tspeed]; + else if (::g->mousex < 0) { + cmd->angleturn += tspeed == 1 ? -2 * mousex : -mousex; + } + + if (::g->mousey > 0 || ::g->mousey < 0) { + //forward += ::g->forwardmove[speed]; + forward += speed == 1 ? 2 * ::g->mousey : ::g->mousey; + } +/* + if (::g->mousey < 0) { + forward -= ::g->forwardmove[speed]; + } +*/ +/* + if (::g->gamekeydown[::g->key_straferight]) + side += ::g->sidemove[speed]; + if (::g->gamekeydown[::g->key_strafeleft]) + side -= ::g->sidemove[speed]; +*/ + + if ( ::g->joyxmove > 0 || ::g->joyxmove < 0 ) { + side += speed == 1 ? 2 * ::g->joyxmove : ::g->joyxmove; + } + + // buttons + if (::g->gamekeydown[::g->key_fire] || ::g->mousebuttons[::g->mousebfire] || ::g->joybuttons[::g->joybfire]) + cmd->buttons |= BT_ATTACK; + + if (::g->gamekeydown[::g->key_use] || ::g->joybuttons[::g->joybuse] ) + cmd->buttons |= BT_USE; + + // DHM - Nerve :: In the intermission or finale screens, make START also create a 'use' command. + if ( (::g->gamestate == GS_INTERMISSION || ::g->gamestate == GS_FINALE) && ::g->gamekeydown[KEY_ESCAPE] ) { + cmd->buttons |= BT_USE; + } + + // weapon toggle + for (i=0 ; igamekeydown['1'+i]) + { + cmd->buttons |= BT_CHANGE; + cmd->buttons |= i<mousex = ::g->mousey = 0; + + if (forward > MAXPLMOVE) + forward = MAXPLMOVE; + else if (forward < -MAXPLMOVE) + forward = -MAXPLMOVE; + if (side > MAXPLMOVE) + side = MAXPLMOVE; + else if (side < -MAXPLMOVE) + side = -MAXPLMOVE; + + cmd->forwardmove += forward; + cmd->sidemove += side; + + // special buttons + if (::g->sendpause) + { + ::g->sendpause = false; + cmd->buttons = BT_SPECIAL | BTS_PAUSE; + } + + if (::g->sendsave) + { + ::g->sendsave = false; + cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (::g->savegameslot<skyflatnum = R_FlatNumForName ( SKYFLATNAME ); + + // DOOM determines the sky texture to be used + // depending on the current episode, and the game version. + if ( ::g->gamemode == commercial ) + { + ::g->skytexture = R_TextureNumForName ("SKY3"); + + if (::g->gamemap < 12) { + ::g->skytexture = R_TextureNumForName ("SKY1"); + } + else if (::g->gamemap < 21) { + ::g->skytexture = R_TextureNumForName ("SKY2"); + } + } + + ::g->levelstarttic = ::g->gametic; // for time calculation + + if (::g->wipegamestate == GS_LEVEL) { + ::g->wipegamestate = (gamestate_t)-1; // force a wipe + } else if ( ::g->netgame ) { + ::g->wipegamestate = GS_LEVEL; + } + + ::g->gamestate = GS_LEVEL; + + for (i=0 ; iplayeringame[i] && ::g->players[i].playerstate == PST_DEAD) + ::g->players[i].playerstate = PST_REBORN; + memset (::g->players[i].frags,0,sizeof(::g->players[i].frags)); + memset (&(::g->players[i].cmd),0,sizeof(::g->players[i].cmd)); + } + + const char * difficultyNames[] = { "I'm Too Young To Die!", "Hey, Not Too Rough!", "Hurt Me Plenty!", "Ultra-Violence", "Nightmare" }; + const ExpansionData * expansion = DoomLib::GetCurrentExpansion(); + + int truemap = ::g->gamemap; + + if( ::g->gamemission == doom ) { + truemap = ( ::g->gameepisode - 1 ) * 9 + ( ::g->gamemap ); + } + + idMatchParameters newParms = session->GetActingGameStateLobbyBase().GetMatchParms(); + DoomLib::SetCurrentMapName( expansion->mapNames[ truemap - 1 ] ); + DoomLib::SetCurrentDifficulty( difficultyNames[ ::g->gameskill ] ); + + P_SetupLevel (::g->gameepisode, ::g->gamemap, 0, ::g->gameskill); + + ::g->displayplayer = ::g->consoleplayer; // view the guy you are playing + ::g->starttime = I_GetTime (); + ::g->gameaction = ga_nothing; + + // clear cmd building stuff + memset (::g->gamekeydown, 0, sizeof(::g->gamekeydown)); + ::g->joyxmove = ::g->joyymove = 0; + ::g->mousex = ::g->mousey = 0; + ::g->sendpause = ::g->sendsave = ::g->paused = false; + memset (::g->mousebuttons, 0, sizeof(::g->mousebuttons)); + memset (::g->joybuttons, 0, sizeof(::g->joybuttons)); +} + +// +// G_Responder +// Get info needed to make ticcmd_ts for the ::g->players. +// +qboolean G_Responder (event_t* ev) +{ + // allow spy mode changes even during the demo + if (::g->gamestate == GS_LEVEL && ev->type == ev_keydown + && ev->data1 == KEY_F12 && (::g->singledemo || !::g->deathmatch) ) + { + // spy mode + do + { + ::g->displayplayer++; + if (::g->displayplayer == MAXPLAYERS) + ::g->displayplayer = 0; + } while (!::g->playeringame[::g->displayplayer] && ::g->displayplayer != ::g->consoleplayer); + return true; + } + + // any other key pops up menu if in demos + if (::g->gameaction == ga_nothing && !::g->singledemo && + (::g->demoplayback || ::g->gamestate == GS_DEMOSCREEN) + ) + { + if (ev->type == ev_keydown || + (ev->type == ev_mouse && ev->data1) || + (ev->type == ev_joystick && ev->data1) ) + { + M_StartControlPanel (); + return true; + } + return false; + } + + if (::g->gamestate == GS_LEVEL && ( ::g->usergame || ::g->netgame || ::g->demoplayback )) + { +#if 0 + if (::g->devparm && ev->type == ev_keydown && ev->data1 == ';') + { + G_DeathMatchSpawnPlayer (0); + return true; + } +#endif + if (HU_Responder (ev)) + return true; // chat ate the event + if (ST_Responder (ev)) + return true; // status window ate it + if (AM_Responder (ev)) + return true; // automap ate it + } + + if (::g->gamestate == GS_FINALE) + { + if (F_Responder (ev)) + return true; // finale ate the event + } + + switch (ev->type) + { + case ev_keydown: + if (ev->data1 == KEY_PAUSE) + { + ::g->sendpause = true; + return true; + } + if (ev->data1 gamekeydown[ev->data1] = true; + return true; // eat key down ::g->events + + case ev_keyup: + // DHM - Nerve :: Old School! + //if ( ev->data1 == '-' ) { + //App->Renderer->oldSchool = !App->Renderer->oldSchool; + //} + + if (ev->data1 gamekeydown[ev->data1] = false; + return false; // always let key up ::g->events filter down + + case ev_mouse: + ::g->mousebuttons[0] = ev->data1 & 1; + ::g->mousebuttons[1] = ev->data1 & 2; + ::g->mousebuttons[2] = ev->data1 & 4; + ::g->mousex = ev->data2*(::g->mouseSensitivity+5)/10; + ::g->mousey = ev->data3*(::g->mouseSensitivity+5)/10; + return true; // eat ::g->events + + case ev_joystick: + ::g->joybuttons[0] = ev->data1 & 1; + ::g->joybuttons[1] = ev->data1 & 2; + ::g->joybuttons[2] = ev->data1 & 4; + ::g->joybuttons[3] = ev->data1 & 8; + ::g->joyxmove = ev->data2; +/* + ::g->gamekeydown[::g->key_straferight] = ::g->gamekeydown[::g->key_strafeleft] = 0; + if (ev->data2 > 0) + ::g->gamekeydown[::g->key_straferight] = 1; + else if (ev->data2 < 0) + ::g->gamekeydown[::g->key_strafeleft] = 1; +*/ + ::g->joyymove = ev->data3; + return true; // eat ::g->events + + default: + break; + } + + return false; +} + + + +// +// G_Ticker +// Make ticcmd_ts for the ::g->players. +// +void G_Ticker (void) +{ + int i; + int buf; + ticcmd_t* cmd; + + // do player reborns if needed + for (i=0 ; iplayeringame[i] && ::g->players[i].playerstate == PST_REBORN) + G_DoReborn (i); + + // do things to change the game state + while (::g->gameaction != ga_nothing) + { + switch (::g->gameaction) + { + case ga_loadlevel: + G_DoLoadLevel (); + break; + case ga_newgame: + G_DoNewGame (); + break; + case ga_loadgame: + G_DoLoadGame (); + break; + case ga_savegame: + G_DoSaveGame (); + break; + case ga_playdemo: + G_DoPlayDemo (); + break; + case ga_completed: + G_DoCompleted (); + break; + case ga_victory: + F_StartFinale (); + break; + case ga_worlddone: + G_DoWorldDone (); + break; + case ga_screenshot: + M_ScreenShot (); + ::g->gameaction = ga_nothing; + break; + case ga_nothing: + break; + } + } + + // get commands, check ::g->consistancy, + // and build new ::g->consistancy check + buf = (::g->gametic/::g->ticdup)%BACKUPTICS; + + for (i=0 ; iplayeringame[i]) + { + cmd = &::g->players[i].cmd; + + memcpy (cmd, &::g->netcmds[i][buf], sizeof(ticcmd_t)); + + if ( ::g->demoplayback ) { + G_ReadDemoTiccmd( cmd ); +#ifdef DEBUG_DEMOS + if ( demoDebugOn && testprndindex != ::g->prndindex && printErrorCount++ < 10 ) { + I_Printf( "time: %d, g->prndindex(%d) does not match demo prndindex(%d)!\n", ::g->leveltime, ::g->prndindex, testprndindex ); + } +#endif + } + + if ( ::g->demorecording ) { + G_WriteDemoTiccmd (cmd); + } + + // HACK ALERT ( the GS_FINALE CRAP IS A HACK.. ) + if (::g->netgame && !::g->netdemo && !(::g->gametic % ::g->ticdup) && !(::g->gamestate == GS_FINALE ) ) + { + if (::g->gametic > BACKUPTICS && ::g->consistancy[i][buf] != cmd->consistancy) + { + printf ("consistency failure (%i should be %i)", + cmd->consistancy, ::g->consistancy[i][buf]); + + // TODO: If we ever support splitscreen and online, + // we'll have to call D_QuitNetGame for all local players. + D_QuitNetGame(); + + session->QuitMatch(); + common->Dialog().AddDialog( GDM_CONNECTION_LOST_HOST, DIALOG_ACCEPT, NULL, NULL, false ); + } + + if (::g->players[i].mo) + ::g->consistancy[i][buf] = ::g->players[i].mo->x; + else + ::g->consistancy[i][buf] = ::g->rndindex; + } + } + } + + // check for special buttons + for (i=0 ; iplayeringame[i]) + { + if (::g->players[i].cmd.buttons & BT_SPECIAL) + { + switch (::g->players[i].cmd.buttons & BT_SPECIALMASK) + { + case BTS_PAUSE: + ::g->paused ^= 1; + + // DHM - Nerve :: Don't pause the music + /* + if (::g->paused) + S_PauseSound (); + else + S_ResumeSound (); + */ + break; + + case BTS_SAVEGAME: + + if (!::g->savedescription[0]) + strcpy (::g->savedescription, "NET GAME"); + ::g->savegameslot = (::g->players[i].cmd.buttons & BTS_SAVEMASK)>>BTS_SAVESHIFT; + ::g->gameaction = ga_savegame; + + break; + } + } + } + } + + // do main actions + switch (::g->gamestate) + { + case GS_LEVEL: + P_Ticker (); + ST_Ticker (); + AM_Ticker (); + HU_Ticker (); + break; + + case GS_INTERMISSION: + WI_Ticker (); + break; + + case GS_FINALE: + F_Ticker (); + break; + + case GS_DEMOSCREEN: + D_PageTicker (); + break; + } +} + + +// +// PLAYER STRUCTURE FUNCTIONS +// also see P_SpawnPlayer in P_Things +// + +// +// G_InitPlayer +// Called at the start. +// Called by the game initialization functions. +// +void G_InitPlayer (int player) +{ + player_t* p; + + // set up the saved info + p = &::g->players[player]; + + // clear everything else to defaults + G_PlayerReborn (player); + +} + + + +// +// G_PlayerFinishLevel +// Can when a player completes a level. +// +void G_PlayerFinishLevel (int player) +{ + player_t* p; + + p = &::g->players[player]; + + memset (p->powers, 0, sizeof (p->powers)); + memset (p->cards, 0, sizeof (p->cards)); + p->mo->flags &= ~MF_SHADOW; // cancel invisibility + p->extralight = 0; // cancel gun flashes + p->fixedcolormap = 0; // cancel ir gogles + p->damagecount = 0; // no palette changes + p->bonuscount = 0; +} + +// +// G_PlayerReborn +// Called after a player dies +// almost everything is cleared and initialized +// +void G_PlayerReborn (int player) +{ + player_t* p; + int i; + int frags[MAXPLAYERS]; + int killcount; + int itemcount; + int secretcount; + + // DHM - Nerve :: cards are saved across death in coop multiplayer + qboolean cards[NUMCARDS]; + bool hasMapPowerup = false; + + hasMapPowerup = ::g->players[player].powers[pw_allmap] != 0; + memcpy( cards, ::g->players[player].cards, sizeof(cards) ); + memcpy( frags, ::g->players[player].frags, sizeof(frags) ); + killcount = ::g->players[player].killcount; + itemcount = ::g->players[player].itemcount; + secretcount = ::g->players[player].secretcount; + + p = &::g->players[player]; + memset (p, 0, sizeof(*p)); + + // DHM - Nerve :: restore cards in multiplayer + // TODO: Networking +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + if ( common->IsMultiplayer() || gameLocal->IsSplitscreen() || (::g->demoplayback && ::g->netdemo) ) { + if ( hasMapPowerup ) { + ::g->players[player].powers[pw_allmap] = 1; + } + memcpy (::g->players[player].cards, cards, sizeof(::g->players[player].cards)); + } +#endif + memcpy (::g->players[player].frags, frags, sizeof(::g->players[player].frags)); + ::g->players[player].killcount = killcount; + ::g->players[player].itemcount = itemcount; + ::g->players[player].secretcount = secretcount; + + p->usedown = p->attackdown = true; // don't do anything immediately + p->playerstate = PST_LIVE; + p->health = MAXHEALTH; + p->readyweapon = p->pendingweapon = wp_pistol; + p->weaponowned[wp_fist] = true; + p->weaponowned[wp_pistol] = true; + p->ammo[am_clip] = 50; + // TODO: PC +#if 0 + p->cheats = gameLocal->cheats; +#else + p->cheats = 0; +#endif + + for (i=0 ; imaxammo[i] = maxammo[i]; +} + +// +// G_CheckSpot +// Returns false if the player cannot be respawned +// at the given mapthing_t spot +// because something is occupying it +// +void P_SpawnPlayer (mapthing_t* mthing); + +qboolean +G_CheckSpot +( int playernum, + mapthing_t* mthing ) +{ + fixed_t x; + fixed_t y; + subsector_t* ss; + unsigned an; + mobj_t* mo; + int i; + + if (!::g->players[playernum].mo) + { + // first spawn of level, before corpses + for (i=0 ; iplayers[i].mo->x == mthing->x << FRACBITS + && ::g->players[i].mo->y == mthing->y << FRACBITS) + return false; + return true; + } + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + if (!P_CheckPosition (::g->players[playernum].mo, x, y) ) + return false; + + // flush an old corpse if needed + if (::g->bodyqueslot >= BODYQUESIZE) + P_RemoveMobj (::g->bodyque[::g->bodyqueslot%BODYQUESIZE]); + ::g->bodyque[::g->bodyqueslot%BODYQUESIZE] = ::g->players[playernum].mo; + ::g->bodyqueslot++; + + // spawn a teleport fog + ss = R_PointInSubsector (x,y); + an = ( ANG45 * (mthing->angle/45) ) >> ANGLETOFINESHIFT; + + mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] + , ss->sector->floorheight + , MT_TFOG); + + if (::g->players[::g->consoleplayer].viewz != 1 && (playernum == ::g->consoleplayer)) + S_StartSound (::g->players[::g->consoleplayer].mo, sfx_telept); // don't start sound on first frame + + return true; +} + + +// +// G_DeathMatchSpawnPlayer +// Spawns a player at one of the random death match spots +// called at level load and each death +// +void G_DeathMatchSpawnPlayer (int playernum) +{ + int i,j; + int selections; + + selections = ::g->deathmatch_p - ::g->deathmatchstarts; + if (selections < 4) + I_Error ("Only %i ::g->deathmatch spots, 4 required", selections); + + for (j=0 ; j<20 ; j++) + { + i = P_Random() % selections; + if (G_CheckSpot (playernum, &::g->deathmatchstarts[i]) ) + { + ::g->deathmatchstarts[i].type = playernum+1; + P_SpawnPlayer (&::g->deathmatchstarts[i]); + return; + } + } + + // no good spot, so the player will probably get stuck + P_SpawnPlayer (&::g->playerstarts[playernum]); +} + + +// +// G_DoReborn +// +void G_DoReborn (int playernum) +{ + int i; + + if (!::g->netgame) + { + // reload the level from scratch + ::g->gameaction = ga_loadlevel; + } + else + { + // respawn at the start + + // first dissasociate the corpse + ::g->players[playernum].mo->player = NULL; + + // spawn at random spot if in death match + if (::g->deathmatch) + { + G_DeathMatchSpawnPlayer (playernum); + return; + } + + if (G_CheckSpot (playernum, &::g->playerstarts[playernum]) ) + { + P_SpawnPlayer (&::g->playerstarts[playernum]); + return; + } + + // try to spawn at one of the other ::g->players spots + for (i=0 ; iplayerstarts[i]) ) + { + ::g->playerstarts[i].type = playernum+1; // fake as other player + P_SpawnPlayer (&::g->playerstarts[i]); + ::g->playerstarts[i].type = i+1; // restore + return; + } + // he's going to be inside something. Too bad. + } + P_SpawnPlayer (&::g->playerstarts[playernum]); + } +} + + +void G_ScreenShot (void) +{ + ::g->gameaction = ga_screenshot; +} + + +// DHM - Nerve :: Added episode 4 par times +// DOOM Par Times +const int pars[5][10] = +{ + {0}, + {0,30,75,120,90,165,180,180,30,165}, + {0,90,90,90,120,90,360,240,30,170}, + {0,90,45,90,150,90,90,165,30,135}, + {0,165,255,135,150,180,390,135,360,180} +}; + +// DOOM II Par Times +const int cpars[32] = +{ + 30,90,120,120,90,150,120,120,270,90, // 1-10 + 210,150,150,150,210,150,420,150,210,150, // 11-20 + 240,150,180,150,150,300,330,420,300,180, // 21-30 + 120,30 // 31-32 +}; + + +// +// G_DoCompleted +// + +void G_ExitLevel (void) +{ + ::g->secretexit = false; + ::g->gameaction = ga_completed; +} + +// Here's for the german edition. +void G_SecretExitLevel (void) +{ + // IF NO WOLF3D LEVELS, NO SECRET EXIT! + if ( (::g->gamemode == commercial) + && (W_CheckNumForName("map31")<0)) + ::g->secretexit = false; + else + ::g->secretexit = true; + ::g->gameaction = ga_completed; +} + +void G_DoCompleted (void) +{ + int i; + + ::g->gameaction = ga_nothing; + + for (i=0 ; iplayeringame[i]) { + G_PlayerFinishLevel (i); // take away cards and stuff + } + } + + if (::g->automapactive) { + AM_Stop(); + } + + if ( ::g->demoplayback ) { + G_CheckDemoStatus(); + return; + } + + if ( ::g->demorecording ) { + G_CheckDemoStatus(); + } + + // DHM - Nerve :: Deathmatch doesn't go to finale screen, just do intermission + if ( ::g->gamemode != commercial && !::g->deathmatch ) { + switch(::g->gamemap) { + + case 8: + ::g->gameaction = ga_victory; + return; + case 9: + for (i=0 ; iplayers[i].didsecret = true; + break; + } + } + + ::g->wminfo.didsecret = ::g->players[::g->consoleplayer].didsecret; + ::g->wminfo.epsd = ::g->gameepisode -1; + ::g->wminfo.last = ::g->gamemap -1; + + // ::g->wminfo.next is 0 biased, unlike ::g->gamemap + if ( ::g->gamemode == commercial) + { + if (::g->secretexit) { + if ( ::g->gamemission == doom2 ) { + switch(::g->gamemap) + { + case 15: ::g->wminfo.next = 30; break; + case 31: ::g->wminfo.next = 31; break; + } + } else if( ::g->gamemission == pack_nerve ) { + + // DHM - Nerve :: Secret level is always level 9 on extra Doom2 missions + ::g->wminfo.next = 8; + } + } + else { + if ( ::g->gamemission == doom2 ) { + switch(::g->gamemap) + { + case 31: + case 32: ::g->wminfo.next = 15; break; + default: ::g->wminfo.next = ::g->gamemap; + } + } + else if( ::g->gamemission == pack_nerve) { + switch(::g->gamemap) + { case 9: + ::g->wminfo.next = 4; + break; + default: + ::g->wminfo.next = ::g->gamemap; + break; + } + } else { + ::g->wminfo.next = ::g->gamemap; + } + } + } + else + { + if (::g->secretexit) { + ::g->wminfo.next = 8; // go to secret level + } + else if (::g->gamemap == 9 ) + { + // returning from secret level + switch (::g->gameepisode) + { + case 1: + ::g->wminfo.next = 3; + break; + case 2: + ::g->wminfo.next = 5; + break; + case 3: + ::g->wminfo.next = 6; + break; + case 4: + ::g->wminfo.next = 2; + break; + } + } + else + ::g->wminfo.next = ::g->gamemap; // go to next level + } + + // DHM - Nerve :: In deathmatch, repeat the current level. User must exit and choose a new level. + if ( ::g->deathmatch ) { + ::g->wminfo.next = ::g->wminfo.last; + } + + ::g->wminfo.maxkills = ::g->totalkills; + ::g->wminfo.maxitems = ::g->totalitems; + ::g->wminfo.maxsecret = ::g->totalsecret; + ::g->wminfo.maxfrags = 0; + + if ( ::g->gamemode == commercial ) { + ::g->wminfo.partime = TICRATE *cpars[::g->gamemap-1]; + } + else + ::g->wminfo.partime = TICRATE * pars[::g->gameepisode][::g->gamemap]; + + ::g->wminfo.pnum = ::g->consoleplayer; + + for (i=0 ; iwminfo.plyr[i].in = ::g->playeringame[i]; + ::g->wminfo.plyr[i].skills = ::g->players[i].killcount; + ::g->wminfo.plyr[i].sitems = ::g->players[i].itemcount; + ::g->wminfo.plyr[i].ssecret = ::g->players[i].secretcount; + ::g->wminfo.plyr[i].stime = ::g->leveltime; + memcpy (::g->wminfo.plyr[i].frags, ::g->players[i].frags + , sizeof(::g->wminfo.plyr[i].frags)); + } + + ::g->gamestate = GS_INTERMISSION; + ::g->viewactive = false; + ::g->automapactive = false; + + WI_Start (&::g->wminfo); +} + + +// +// G_WorldDone +// +void G_WorldDone (void) +{ + ::g->gameaction = ga_worlddone; + + if (::g->secretexit) + ::g->players[::g->consoleplayer].didsecret = true; + + if ( ::g->gamemode == commercial ) + { + if ( ::g->gamemission == doom2 || ::g->gamemission == pack_tnt || ::g->gamemission == pack_plut ) { + switch (::g->gamemap) + { + case 15: + case 31: + if (!::g->secretexit) + break; + case 6: + case 11: + case 20: + case 30: + F_StartFinale (); + break; + } + } + else if ( ::g->gamemission == pack_nerve ) { + if ( ::g->gamemap == 8 ) { + F_StartFinale(); + } + } + else if ( ::g->gamemission == pack_master ) { + if ( ::g->gamemap == 21 ) { + F_StartFinale(); + } + } + else { + // DHM - NERVE :: Downloadable content needs to set these up if different than initial extended episode + if ( ::g->gamemap == 8 ) { + F_StartFinale(); + } + } + } +} + +void G_DoWorldDone (void) +{ + ::g->gamestate = GS_LEVEL; + + ::g->gamemap = ::g->wminfo.next+1; + + M_ClearRandom(); + + for ( int i = 0; i < MAXPLAYERS; i++ ) { + if ( ::g->playeringame[i] ) { + ::g->players[i].usedown = ::g->players[i].attackdown = true; // don't do anything immediately + } + } + + G_DoLoadLevel (); + ::g->gameaction = ga_nothing; + ::g->viewactive = true; + +} + + + +// +// G_InitFromSavegame +// Can be called by the startup code or the menu task. +// +void R_ExecuteSetViewSize (void); + + +void G_LoadGame (char* name) +{ + strcpy (::g->savename, name); + ::g->gameaction = ga_loadgame; +} + +qboolean G_DoLoadGame () +{ + int i; + int a,b,c; + char vcheck[VERSIONSIZE]; + + loadingGame = true; + + ::g->gameaction = ga_nothing; + + M_ReadFile (::g->savename, &::g->savebuffer); + + waitingForWipe = true; + + // DHM - Nerve :: Clear possible net demo state + ::g->netdemo = false; + ::g->netgame = false; + ::g->deathmatch = false; + ::g->playeringame[1] = ::g->playeringame[2] = ::g->playeringame[3] = 0; + ::g->respawnparm = false; + ::g->fastparm = false; + ::g->nomonsters = false; + ::g->consoleplayer = 0; + + ::g->save_p = ::g->savebuffer + SAVESTRINGSIZE; + + // skip the description field + memset (vcheck,0,sizeof(vcheck)); + sprintf (vcheck,"version %i",VERSION); + if (strcmp ((char *)::g->save_p, vcheck)) { + loadingGame = false; + waitingForWipe = false; + + return FALSE; // bad version + } + + ::g->save_p += VERSIONSIZE; + + ::g->gameskill = (skill_t)*::g->save_p++; + ::g->gameepisode = *::g->save_p++; + ::g->gamemission = *::g->save_p++; + ::g->gamemap = *::g->save_p++; + for (i=0 ; iplayeringame[i] = *::g->save_p++; + + // load a base level + G_InitNew (::g->gameskill, ::g->gameepisode, ::g->gamemap ); + + // get the times + a = *::g->save_p++; + b = *::g->save_p++; + c = *::g->save_p++; + ::g->leveltime = (a<<16) + (b<<8) + c; + + // dearchive all the modifications + P_UnArchivePlayers (); + P_UnArchiveWorld (); + P_UnArchiveThinkers (); + + // specials are archived with thinkers + //P_UnArchiveSpecials (); + + if (*::g->save_p != 0x1d) + I_Error ("Bad savegame"); + + if (::g->setsizeneeded) + R_ExecuteSetViewSize (); + + // draw the pattern into the back screen + R_FillBackScreen (); + + loadingGame = false; + + Z_Free(g->savebuffer); + + return TRUE; +} + + +// +// G_SaveGame +// Called by the menu task. +// Description is a 24 byte text string +// +void +G_SaveGame +( int slot, + char* description ) +{ + ::g->savegameslot = slot; + strcpy (::g->savedescription, description); + ::g->sendsave = true; + ::g->gameaction = ga_savegame; +} + +qboolean G_DoSaveGame (void) +{ + char name[100]; + char name2[VERSIONSIZE]; + char* description; + int length; + int i; + qboolean bResult = true; + + if ( ::g->gamestate != GS_LEVEL ) { + return false; + } + + description = ::g->savedescription; + + if( common->GetCurrentGame() == DOOM_CLASSIC ) { + sprintf(name,"DOOM\\%s%d.dsg", SAVEGAMENAME,::g->savegameslot ); + } else { + if( DoomLib::expansionSelected == doom2 ) { + sprintf(name,"DOOM2\\%s%d.dsg", SAVEGAMENAME,::g->savegameslot ); + } else { + sprintf(name,"DOOM2_NRFTL\\%s%d.dsg", SAVEGAMENAME,::g->savegameslot ); + } + + } + + ::g->save_p = ::g->savebuffer = ::g->screens[1]; + + memcpy (::g->save_p, description, SAVESTRINGSIZE); + ::g->save_p += SAVESTRINGSIZE; + + memset (name2,0,sizeof(name2)); + sprintf (name2,"version %i",VERSION); + memcpy (::g->save_p, name2, VERSIONSIZE); + ::g->save_p += VERSIONSIZE; + + *::g->save_p++ = ::g->gameskill; + *::g->save_p++ = ::g->gameepisode; + *::g->save_p++ = ::g->gamemission; + *::g->save_p++ = ::g->gamemap; + + for (i=0 ; isave_p++ = ::g->playeringame[i]; + } + + *::g->save_p++ = ::g->leveltime>>16; + *::g->save_p++ = ::g->leveltime>>8; + *::g->save_p++ = ::g->leveltime; + + P_ArchivePlayers (); + P_ArchiveWorld (); + P_ArchiveThinkers (); + + // specials are archived with thinkers + //P_ArchiveSpecials (); + + *::g->save_p++ = 0x1d; // ::g->consistancy marker + + length = ::g->save_p - ::g->savebuffer; + if (length > SAVEGAMESIZE) + I_Error ("Savegame buffer overrun"); + + ::g->savebufferSize = length; + + M_WriteFile (name, ::g->savebuffer, length); + + ::g->gameaction = ga_nothing; + ::g->savedescription[0] = 0; + + // draw the pattern into the back screen + R_FillBackScreen (); + + return bResult; +} + + +// +// G_InitNew +// Can be called by the startup code or the menu task, +// ::g->consoleplayer, ::g->displayplayer, ::g->playeringame[] should be set. +// + +void +G_DeferedInitNew +( skill_t skill, + int episode, + int map) +{ + ::g->d_skill = skill; + ::g->d_episode = episode; + ::g->d_map = map; + + //::g->d_map = 30; + + ::g->gameaction = ga_newgame; +} + + +void G_DoNewGame (void) +{ + ::g->demoplayback = false; + ::g->netdemo = false; + ::g->netgame = false; + ::g->deathmatch = false; + ::g->playeringame[1] = ::g->playeringame[2] = ::g->playeringame[3] = 0; + ::g->respawnparm = false; + ::g->fastparm = false; + ::g->nomonsters = false; + ::g->consoleplayer = 0; + G_InitNew (::g->d_skill, ::g->d_episode, ::g->d_map ); + ::g->gameaction = ga_nothing; +} + +// The sky texture to be used instead of the F_SKY1 dummy. + + +void +G_InitNew +( skill_t skill, + int episode, + int map + ) +{ + int i; + m_inDemoMode.SetBool( false ); + R_SetViewSize (::g->screenblocks, ::g->detailLevel); + + if (::g->paused) + { + ::g->paused = false; + S_ResumeSound (); + } + + if (skill > sk_nightmare) + skill = sk_nightmare; + + // This was quite messy with SPECIAL and commented parts. + // Supposedly hacks to make the latest edition work. + // It might not work properly. + if (episode < 1) + episode = 1; + + if ( ::g->gamemode == retail ) + { + if (episode > 4) + episode = 4; + } + else if ( ::g->gamemode == shareware ) + { + if (episode > 1) + episode = 1; // only start episode 1 on shareware + } + else + { + if (episode > 3) + episode = 3; + } + + if (map < 1) + map = 1; + + if (skill == sk_nightmare || ::g->respawnparm ) + ::g->respawnmonsters = true; + else + ::g->respawnmonsters = false; + + // force ::g->players to be initialized upon first level load + for (i=0 ; iplayers[i].playerstate = PST_REBORN; + + ::g->usergame = true; // will be set false if a demo + ::g->paused = false; + ::g->demoplayback = false; + ::g->advancedemo = false; + ::g->automapactive = false; + ::g->viewactive = true; + ::g->gameepisode = episode; + //::g->gamemission = expansion->pack_type; + ::g->gamemap = map; + ::g->gameskill = skill; + + ::g->viewactive = true; + + // set the sky map for the episode + if ( ::g->gamemode == commercial) + { + ::g->skytexture = R_TextureNumForName ("SKY3"); + + if (::g->gamemap < 12) { + ::g->skytexture = R_TextureNumForName ("SKY1"); + } + else if (::g->gamemap < 21) { + ::g->skytexture = R_TextureNumForName ("SKY2"); + } + } + else { + switch (episode) + { + case 1: + ::g->skytexture = R_TextureNumForName ("SKY1"); + break; + case 2: + ::g->skytexture = R_TextureNumForName ("SKY2"); + break; + case 3: + ::g->skytexture = R_TextureNumForName ("SKY3"); + break; + case 4: // Special Edition sky + ::g->skytexture = R_TextureNumForName ("SKY4"); + break; + default: + ::g->skytexture = R_TextureNumForName ("SKY1"); + break; + } + } + + G_DoLoadLevel( ); +} + + +// +// DEMO RECORDING +// + +void G_ReadDemoTiccmd (ticcmd_t* cmd) +{ + if (*::g->demo_p == DEMOMARKER) + { + // end of demo data stream + G_CheckDemoStatus (); + return; + } + + cmd->forwardmove = ((signed char)*::g->demo_p++); + cmd->sidemove = ((signed char)*::g->demo_p++); + + if ( demoversion == VERSION ) { + short *temp = (short *)(::g->demo_p); + cmd->angleturn = *temp; + ::g->demo_p += 2; + } + else { + // DHM - Nerve :: Old format + cmd->angleturn = ((unsigned char)*::g->demo_p++)<<8; + } + + cmd->buttons = (unsigned char)*::g->demo_p++; + +#ifdef DEBUG_DEMOS + // TESTING + if ( demoDebugOn ) { + testprndindex = (unsigned char)*::g->demo_p++; + } +#endif +} + + +void G_WriteDemoTiccmd (ticcmd_t* cmd) +{ + *::g->demo_p++ = cmd->forwardmove; + *::g->demo_p++ = cmd->sidemove; + + // NEW VERSION + short *temp = (short *)(::g->demo_p); + *temp = cmd->angleturn; + ::g->demo_p += 2; + + // OLD VERSION + //*::g->demo_p++ = (cmd->angleturn+128)>>8; + + *::g->demo_p++ = cmd->buttons; + + int cmdSize = 5; + +#ifdef DEBUG_DEMOS_WRITE + // TESTING + *::g->demo_p++ = ::g->prndindex; + cmdSize++; +#endif + + ::g->demo_p -= cmdSize; + if (::g->demo_p > ::g->demoend - (cmdSize * 4)) + { + // no more space + G_CheckDemoStatus (); + return; + } + + G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same +} + + + +// +// G_RecordDemo +// +void G_RecordDemo (char* name) +{ + //::g->usergame = false; + strcpy( ::g->demoname, name ); + strcat( ::g->demoname, ".lmp" ); + + ::g->demobuffer = new byte[ MAXDEMOSIZE ]; + ::g->demoend = ::g->demobuffer + MAXDEMOSIZE; + + demoversion = VERSION; + ::g->demorecording = true; +} + + +void G_BeginRecording (void) +{ + int i; + + ::g->demo_p = ::g->demobuffer; + +#ifdef DEBUG_DEMOS +#ifdef DEBUG_DEMOS_WRITE + demoDebugOn = true; + *::g->demo_p++ = VERSION + 1; +#else + *::g->demo_p++ = VERSION; +#endif +#endif + *::g->demo_p++ = ::g->gameskill; + *::g->demo_p++ = ::g->gameepisode; + *::g->demo_p++ = ::g->gamemission; + *::g->demo_p++ = ::g->gamemap; + *::g->demo_p++ = ::g->deathmatch; + *::g->demo_p++ = ::g->respawnparm; + *::g->demo_p++ = ::g->fastparm; + *::g->demo_p++ = ::g->nomonsters; + *::g->demo_p++ = ::g->consoleplayer; + + for ( i=0 ; idemo_p++ = ::g->playeringame[i]; + } + + for ( i=0 ; iplayeringame[i] ) { + int* dest = (int *)::g->demo_p; + *dest++ = ::g->players[i].health; + *dest++ = ::g->players[i].armorpoints; + *dest++ = ::g->players[i].armortype; + *dest++ = ::g->players[i].readyweapon; + for ( int j = 0; j < NUMWEAPONS; j++ ) { + *dest++ = ::g->players[i].weaponowned[j]; + } + for ( int j = 0; j < NUMAMMO; j++ ) { + *dest++ = ::g->players[i].ammo[j]; + *dest++ = ::g->players[i].maxammo[j]; + } + ::g->demo_p = (byte *)dest; + } + } +} + +// +// G_PlayDemo +// +void G_DeferedPlayDemo (char* name) +{ + ::g->defdemoname = name; + ::g->gameaction = ga_playdemo; +} + +void G_DoPlayDemo (void) +{ + skill_t skill; + int i, episode, map, mission; + + ::g->gameaction = ga_nothing; + + // TODO: Networking +#if ID_ENABLE_DOOM_CLASSIC_NETWORKING + if ( gameLocal->IsSplitscreen() && DoomLib::GetPlayer() > 0 ) { + return; + } +#endif + + + // DEMO Testing + bool useOriginalDemo = true; + + if ( useOriginalDemo ) { + int demolump = W_GetNumForName( ::g->defdemoname ); + int demosize = W_LumpLength( demolump ); + + ::g->demobuffer = ::g->demo_p = new byte[ demosize ]; + W_ReadLump( demolump, ::g->demobuffer ); + } + + // DHM - Nerve :: We support old and new demo versions + demoversion = *::g->demo_p++; + + skill = (skill_t)*::g->demo_p++; + episode = *::g->demo_p++; + if ( demoversion == VERSION ) { + mission = *::g->demo_p++; + } + else { + mission = 0; + } + map = *::g->demo_p++; + ::g->deathmatch = *::g->demo_p++; + ::g->respawnparm = *::g->demo_p++; + ::g->fastparm = *::g->demo_p++; + ::g->nomonsters = *::g->demo_p++; + ::g->consoleplayer = *::g->demo_p++; + + for ( i=0 ; iplayeringame[i] = *::g->demo_p++; + } + + ::g->netgame = false; + ::g->netdemo = false; + if (::g->playeringame[1]) + { + ::g->netgame = true; + ::g->netdemo = true; + } + + // don't spend a lot of time in loadlevel + ::g->precache = false; + G_InitNew (skill, episode, map ); + R_SetViewSize (::g->screenblocks + 1, ::g->detailLevel); + m_inDemoMode.SetBool( true ); + + // JAF - Dont show messages when in Demo Mode. ::g->showMessages = false; + ::g->precache = true; + + // DHM - Nerve :: We now read in the player state from the demo + if ( demoversion == VERSION ) { + for ( i=0 ; iplayeringame[i] ) { + int* src = (int *)::g->demo_p; + ::g->players[i].health = *src++; + ::g->players[i].mo->health = ::g->players[i].health; + ::g->players[i].armorpoints = *src++; + ::g->players[i].armortype = *src++; + ::g->players[i].readyweapon = (weapontype_t)*src++; + for ( int j = 0; j < NUMWEAPONS; j++ ) { + ::g->players[i].weaponowned[j] = *src++; + } + for ( int j = 0; j < NUMAMMO; j++ ) { + ::g->players[i].ammo[j] = *src++; + ::g->players[i].maxammo[j] = *src++; + } + ::g->demo_p = (byte *)src; + + P_SetupPsprites( &::g->players[i] ); + } + } + } + + ::g->usergame = false; + ::g->demoplayback = true; +} + +// +// G_TimeDemo +// +void G_TimeDemo (char* name) +{ + ::g->nodrawers = M_CheckParm ("-nodraw"); + ::g->noblit = M_CheckParm ("-noblit"); + ::g->timingdemo = true; + ::g->singletics = true; + + ::g->defdemoname = name; + ::g->gameaction = ga_playdemo; +} + + +/* +=================== += += G_CheckDemoStatus += += Called after a death or level completion to allow demos to be cleaned up += Returns true if a new demo loop action will take place +=================== +*/ + +qboolean G_CheckDemoStatus (void) +{ + if (::g->demoplayback) + { + delete ::g->demobuffer; + ::g->demobuffer = NULL; + ::g->demo_p = NULL; + ::g->demoend = NULL; + + ::g->demoplayback = false; + ::g->netdemo = false; + ::g->netgame = false; + ::g->deathmatch = false; + ::g->playeringame[1] = ::g->playeringame[2] = ::g->playeringame[3] = 0; + ::g->respawnparm = false; + ::g->fastparm = false; + ::g->nomonsters = false; + ::g->consoleplayer = 0; + D_AdvanceDemo (); + return true; + } + + /* + if (::g->demorecording) { + *::g->demo_p++ = DEMOMARKER; + + if ( ::g->leveltime > (TICRATE * 9) ) { + gameLocal->DoomStoreDemoBuffer( gameLocal->GetPortForPlayer( DoomLib::GetPlayer() ), ::g->demobuffer, ::g->demo_p - ::g->demobuffer ); + } + + delete ::g->demobuffer; + ::g->demobuffer = NULL; + ::g->demo_p = NULL; + ::g->demoend = NULL; + + ::g->demorecording = false; + } + */ + + return false; +} + + + + diff --git a/doomclassic/doom/g_game.h b/doomclassic/doom/g_game.h new file mode 100644 index 00000000..f7947ad1 --- /dev/null +++ b/doomclassic/doom/g_game.h @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __G_GAME__ +#define __G_GAME__ + +#include "doomdef.h" +#include "d_event.h" + + + +// +// GAME +// +void G_DeathMatchSpawnPlayer (int playernum); + +void G_InitNew ( skill_t skill, int episode, int map ); + +// Can be called by the startup code or M_Responder. +// A normal game starts at map 1, +// but a warp test can start elsewhere +void G_DeferedInitNew (skill_t skill, int episode, int map); + +void G_DeferedPlayDemo (char* demo); + +// Can be called by the startup code or M_Responder, +// calls P_SetupLevel or W_EnterWorld. +void G_LoadGame (char* name); + +qboolean G_DoLoadGame (); + +// Called by M_Responder. +void G_SaveGame (int slot, char* description); + +// Only called by startup code. +void G_RecordDemo (char* name); + +void G_BeginRecording (void); + +void G_PlayDemo (char* name); +void G_TimeDemo (char* name); +qboolean G_CheckDemoStatus (void); + +void G_ExitLevel (void); +void G_SecretExitLevel (void); + +void G_WorldDone (void); + +void G_Ticker (void); +qboolean G_Responder (event_t* ev); + +void G_ScreenShot (void); + +#define MAXDEMOSIZE 512 * 1024 +#define SAVEGAMESIZE 256 * 1024 + MAXDEMOSIZE + + +#endif + diff --git a/doomclassic/doom/globaldata.cpp b/doomclassic/doom/globaldata.cpp new file mode 100644 index 00000000..79632061 --- /dev/null +++ b/doomclassic/doom/globaldata.cpp @@ -0,0 +1,106 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" +#include "globaldata.h" +#include "Main.h" + +// +// PROTOTYPES +// +void M_NewGame(int choice); +void M_Episode(int choice); +void M_Expansion(int choice); +void M_ChooseSkill(int choice); +void M_LoadGame(int choice); +void M_LoadExpansion(int choice); +void M_SaveGame(int choice); +void M_Options(int choice); +void M_EndGame(int choice); +void M_ReadThis(int choice); +void M_ReadThis2(int choice); +void M_QuitDOOM(int choice); +void M_ExitGame(int choice); +void M_GameSelection(int choice); +void M_CancelExit(int choice); + +void M_ChangeMessages(int choice); +void M_ChangeGPad(int choice); +void M_FullScreen(int choice); +void M_ChangeSensitivity(int choice); +void M_SfxVol(int choice); +void M_MusicVol(int choice); +void M_ChangeDetail(int choice); +void M_SizeDisplay(int choice); +void M_StartGame(int choice); +void M_Sound(int choice); + +void M_FinishReadThis(int choice); +void M_LoadSelect(int choice); +void M_SaveSelect(int choice); +void M_ReadSaveStrings(void); +void M_QuickSave(void); +void M_QuickLoad(void); + +void M_DrawMainMenu(void); +void M_DrawQuit(void); +void M_DrawReadThis1(void); +void M_DrawReadThis2(void); +void M_DrawNewGame(void); +void M_DrawEpisode(void); +void M_DrawOptions(void); +void M_DrawSound(void); +void M_DrawLoad(void); +void M_DrawSave(void); + +void M_DrawSaveLoadBorder(int x,int y); +void M_SetupNextMenu(menu_t *menudef); +void M_DrawThermo(int x,int y,int thermWidth,int thermDot); +void M_DrawEmptyCell(menu_t *menu,int item); +void M_DrawSelCell(menu_t *menu,int item); +void M_WriteText(int x, int y, char *string); +int M_StringWidth(char *string); +int M_StringHeight(char *string); +void M_StartControlPanel(void); +void M_StartMessage(char *string,messageRoutine_t routine,qboolean input); +void M_StopMessage(void); +void M_ClearMenus (void); + +extern const anim_t temp_epsd0animinfo[10]; +extern const anim_t temp_epsd1animinfo[9]; +extern const anim_t temp_epsd2animinfo[6]; +extern const char* const temp_chat_macros[]; + +void Globals::InitGlobals() +{ +#include "constructs.h" +} + +Globals *g; + diff --git a/doomclassic/doom/globaldata.h b/doomclassic/doom/globaldata.h new file mode 100644 index 00000000..ce54c793 --- /dev/null +++ b/doomclassic/doom/globaldata.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef _GLOBAL_DATA_H +#define _GLOBAL_DATA_H + + +#include "doomtype.h" +#include "d_net.h" +#include "m_fixed.h" +#include "info.h" +#include "sounds.h" +#include "r_defs.h" +#include "z_zone.h" +#include "d_player.h" +#include "m_cheat.h" +#include "doomlib.h" +#include "d_main.h" +#include "hu_lib.h" +#include "hu_stuff.h" +#include "p_spec.h" +#include "p_local.h" +#include "r_bsp.h" +#include "st_stuff.h" +#include "st_lib.h" +#include "w_wad.h" +#include "dstrings.h" + +#include "typedefs.h" +#include "defs.h" +#include "structs.h" + +struct Globals { + void InitGlobals(); +#include "vars.h" +}; + +extern Globals *g; + +#define GLOBAL( type, name ) type name +#define GLOBAL_ARRAY( type, name, count ) type name[count] + +extern void localCalculateAchievements(bool epComplete); + + +#endif diff --git a/doomclassic/doom/hu_lib.cpp b/doomclassic/doom/hu_lib.cpp new file mode 100644 index 00000000..6125ad8e --- /dev/null +++ b/doomclassic/doom/hu_lib.cpp @@ -0,0 +1,358 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +#include "doomdef.h" + +#include "v_video.h" +#include "m_swap.h" + +#include "hu_lib.h" +#include "r_local.h" +#include "r_draw.h" + +// qboolean : whether the screen is always erased + + +void HUlib_init(void) +{ +} + +void HUlib_clearTextLine(hu_textline_t* t) +{ + t->len = 0; + t->l[0] = 0; + t->needsupdate = true; +} + +void +HUlib_initTextLine +( hu_textline_t* t, + int x, + int y, + patch_t** f, + int sc ) +{ + t->x = x; + t->y = y; + t->f = f; + t->sc = sc; + HUlib_clearTextLine(t); +} + +qboolean +HUlib_addCharToTextLine +( hu_textline_t* t, + char ch ) +{ + + if (t->len == HU_MAXLINELENGTH) + return false; + else + { + t->l[t->len++] = ch; + t->l[t->len] = 0; + t->needsupdate = 4; + return true; + } + +} + +qboolean HUlib_delCharFromTextLine(hu_textline_t* t) +{ + + if (!t->len) return false; + else + { + t->l[--t->len] = 0; + t->needsupdate = 4; + return true; + } + +} + +void +HUlib_drawTextLine +( hu_textline_t* l, + qboolean drawcursor ) +{ + + int i; + int w; + int x; + unsigned char c; + + // draw the new stuff + x = l->x; + for (i=0;ilen;i++) + { + c = toupper(l->l[i]); + if (c != ' ' + && c >= l->sc + && c <= '_') + { + w = SHORT(l->f[c - l->sc]->width); + if (x+w > SCREENWIDTH) + break; + V_DrawPatchDirect(x, l->y, FG, l->f[c - l->sc]); + x += w; + } + else + { + x += 4; + if (x >= SCREENWIDTH) + break; + } + } + + // draw the cursor if requested + if (drawcursor + && x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH) + { + V_DrawPatchDirect(x, l->y, FG, l->f['_' - l->sc]); + } +} + + +// sorta called by HU_Erase and just better darn get things straight +void HUlib_eraseTextLine(hu_textline_t* l) +{ + int lh; + int y; + int yoffset; + + // Only erases when NOT in automap and the screen is reduced, + // and the text must either need updating or refreshing + // (because of a recent change back from the automap) + + if (!::g->automapactive && + ::g->viewwindowx && l->needsupdate) + { + lh = SHORT(l->f[0]->height) + 1; + for (y=l->y,yoffset=y*SCREENWIDTH ; yy+lh ; y++,yoffset+=SCREENWIDTH) + { + if (y < ::g->viewwindowy || y >= ::g->viewwindowy + ::g->viewheight) + R_VideoErase(yoffset, SCREENWIDTH); // erase entire line + else + { + R_VideoErase(yoffset, ::g->viewwindowx); // erase left border + R_VideoErase(yoffset + ::g->viewwindowx + ::g->viewwidth, ::g->viewwindowx); + // erase right border + } + } + } + + ::g->lastautomapactive = ::g->automapactive; + if (l->needsupdate) l->needsupdate--; + +} + +void +HUlib_initSText +( hu_stext_t* s, + int x, + int y, + int h, + patch_t** font, + int startchar, + qboolean* on ) +{ + + int i; + + s->h = h; + s->on = on; + s->laston = true; + s->cl = 0; + for (i=0;il[i], + x, y - i*(SHORT(font[0]->height)+1), + font, startchar); + +} + +void HUlib_addLineToSText(hu_stext_t* s) +{ + + int i; + + // add a clear line + if (++s->cl == s->h) + s->cl = 0; + HUlib_clearTextLine(&s->l[s->cl]); + + // everything needs updating + for (i=0 ; ih ; i++) + s->l[i].needsupdate = 4; + +} + +void +HUlib_addMessageToSText +( hu_stext_t* s, + const char* prefix, + const char* msg ) +{ + HUlib_addLineToSText(s); + if (prefix) + while (*prefix) + HUlib_addCharToTextLine(&s->l[s->cl], *(prefix++)); + + while (*msg) + HUlib_addCharToTextLine(&s->l[s->cl], *(msg++)); +} + +void HUlib_drawSText(hu_stext_t* s) +{ + int i, idx; + hu_textline_t *l; + + if (!*s->on) + return; // if not on, don't draw + + // draw everything + for (i=0 ; ih ; i++) + { + idx = s->cl - i; + if (idx < 0) + idx += s->h; // handle queue of ::g->lines + + l = &s->l[idx]; + + // need a decision made here on whether to skip the draw + HUlib_drawTextLine(l, false); // no cursor, please + } + +} + +void HUlib_eraseSText(hu_stext_t* s) +{ + + int i; + + for (i=0 ; ih ; i++) + { + if (s->laston && !*s->on) + s->l[i].needsupdate = 4; + HUlib_eraseTextLine(&s->l[i]); + } + s->laston = *s->on; + +} + +void +HUlib_initIText +( hu_itext_t* it, + int x, + int y, + patch_t** font, + int startchar, + qboolean* on ) +{ + it->lm = 0; // default left margin is start of text + it->on = on; + it->laston = true; + HUlib_initTextLine(&it->l, x, y, font, startchar); +} + + +// The following deletion routines adhere to the left margin restriction +void HUlib_delCharFromIText(hu_itext_t* it) +{ + if (it->l.len != it->lm) + HUlib_delCharFromTextLine(&it->l); +} + +void HUlib_eraseLineFromIText(hu_itext_t* it) +{ + while (it->lm != it->l.len) + HUlib_delCharFromTextLine(&it->l); +} + +// Resets left margin as well +void HUlib_resetIText(hu_itext_t* it) +{ + it->lm = 0; + HUlib_clearTextLine(&it->l); +} + +void +HUlib_addPrefixToIText +( hu_itext_t* it, + char* str ) +{ + while (*str) + HUlib_addCharToTextLine(&it->l, *(str++)); + it->lm = it->l.len; +} + +// wrapper function for handling general keyed input. +// returns true if it ate the key +qboolean +HUlib_keyInIText +( hu_itext_t* it, + unsigned char ch ) +{ + + if (ch >= ' ' && ch <= '_') + HUlib_addCharToTextLine(&it->l, (char) ch); + else + if (ch == KEY_BACKSPACE) + HUlib_delCharFromIText(it); + else + if (ch != KEY_ENTER) + return false; // did not eat key + + return true; // ate the key + +} + +void HUlib_drawIText(hu_itext_t* it) +{ + + hu_textline_t *l = &it->l; + + if (!*it->on) + return; + HUlib_drawTextLine(l, true); // draw the line w/ cursor + +} + +void HUlib_eraseIText(hu_itext_t* it) +{ + if (it->laston && !*it->on) + it->l.needsupdate = 4; + HUlib_eraseTextLine(&it->l); + it->laston = *it->on; +} + + diff --git a/doomclassic/doom/hu_lib.h b/doomclassic/doom/hu_lib.h new file mode 100644 index 00000000..16c8bd3d --- /dev/null +++ b/doomclassic/doom/hu_lib.h @@ -0,0 +1,194 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __HULIB__ +#define __HULIB__ + +// We are referring to patches. +#include "r_defs.h" + +// font stuff +#define HU_CHARERASE KEY_BACKSPACE + +#define HU_MAXLINES 4 +#define HU_MAXLINELENGTH 80 + +// +// Typedefs of widgets +// + +// Text Line widget +// (parent of Scrolling Text and Input Text widgets) +typedef struct +{ + // left-justified position of scrolling text window + int x; + int y; + + patch_t** f; // font + int sc; // start character + char l[HU_MAXLINELENGTH+1]; // line of text + int len; // current line length + + // whether this line needs to be udpated + int needsupdate; + +} hu_textline_t; + + + +// Scrolling Text window widget +// (child of Text Line widget) +typedef struct +{ + hu_textline_t l[HU_MAXLINES]; // text lines to draw + int h; // height in lines + int cl; // current line number + + // pointer to qboolean stating whether to update window + qboolean* on; + qboolean laston; // last value of *->on. + +} hu_stext_t; + + + +// Input Text Line widget +// (child of Text Line widget) +typedef struct +{ + hu_textline_t l; // text line to input on + + // left margin past which I am not to delete characters + int lm; + + // pointer to qboolean stating whether to update window + qboolean* on; + qboolean laston; // last value of *->on; + +} hu_itext_t; + + +// +// Widget creation, access, and update routines +// + +// initializes heads-up widget library +void HUlib_init(void); + +// +// textline code +// + +// clear a line of text +void HUlib_clearTextLine(hu_textline_t *t); + +void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc); + +// returns success +qboolean HUlib_addCharToTextLine(hu_textline_t *t, char ch); + +// returns success +qboolean HUlib_delCharFromTextLine(hu_textline_t *t); + +// draws tline +void HUlib_drawTextLine(hu_textline_t *l, qboolean drawcursor); + +// erases text line +void HUlib_eraseTextLine(hu_textline_t *l); + + +// +// Scrolling Text window widget routines +// + +// ? +void +HUlib_initSText +( hu_stext_t* s, + int x, + int y, + int h, + patch_t** font, + int startchar, + qboolean* on ); + +// add a new line +void HUlib_addLineToSText(hu_stext_t* s); + +// ? +void +HUlib_addMessageToSText +( hu_stext_t* s, + const char* prefix, + const char* msg ); + +// draws stext +void HUlib_drawSText(hu_stext_t* s); + +// erases all stext lines +void HUlib_eraseSText(hu_stext_t* s); + +// Input Text Line widget routines +void +HUlib_initIText +( hu_itext_t* it, + int x, + int y, + patch_t** font, + int startchar, + qboolean* on ); + +// enforces left margin +void HUlib_delCharFromIText(hu_itext_t* it); + +// enforces left margin +void HUlib_eraseLineFromIText(hu_itext_t* it); + +// resets line and left margin +void HUlib_resetIText(hu_itext_t* it); + +// left of left-margin +void +HUlib_addPrefixToIText +( hu_itext_t* it, + char* str ); + +// whether eaten +qboolean +HUlib_keyInIText +( hu_itext_t* it, + unsigned char ch ); + +void HUlib_drawIText(hu_itext_t* it); + +// erases all itext lines +void HUlib_eraseIText(hu_itext_t* it); + +#endif + diff --git a/doomclassic/doom/hu_stuff.cpp b/doomclassic/doom/hu_stuff.cpp new file mode 100644 index 00000000..b379ef54 --- /dev/null +++ b/doomclassic/doom/hu_stuff.cpp @@ -0,0 +1,625 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +#include "doomdef.h" + +#include "z_zone.h" + +#include "m_swap.h" + +#include "hu_stuff.h" +#include "hu_lib.h" +#include "w_wad.h" + +#include "s_sound.h" + +#include "doomstat.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +#include "Main.h" + +// +// Locally used constants, shortcuts. +// + + + +extern const char* const temp_chat_macros[]; +const char* const temp_chat_macros[] = +{ + HUSTR_CHATMACRO0, + HUSTR_CHATMACRO1, + HUSTR_CHATMACRO2, + HUSTR_CHATMACRO3, + HUSTR_CHATMACRO4, + HUSTR_CHATMACRO5, + HUSTR_CHATMACRO6, + HUSTR_CHATMACRO7, + HUSTR_CHATMACRO8, + HUSTR_CHATMACRO9 +}; + +extern const char* const player_names[]; +const char* const player_names[] = +{ + HUSTR_PLRGREEN, + HUSTR_PLRINDIGO, + HUSTR_PLRBROWN, + HUSTR_PLRRED +}; + + + + + + + +// +// Builtin map names. +// The actual names can be found in DStrings.h. +// + +const char* mapnames[] = +{ + + HUSTR_E1M1, + HUSTR_E1M2, + HUSTR_E1M3, + HUSTR_E1M4, + HUSTR_E1M5, + HUSTR_E1M6, + HUSTR_E1M7, + HUSTR_E1M8, + HUSTR_E1M9, + + HUSTR_E2M1, + HUSTR_E2M2, + HUSTR_E2M3, + HUSTR_E2M4, + HUSTR_E2M5, + HUSTR_E2M6, + HUSTR_E2M7, + HUSTR_E2M8, + HUSTR_E2M9, + + HUSTR_E3M1, + HUSTR_E3M2, + HUSTR_E3M3, + HUSTR_E3M4, + HUSTR_E3M5, + HUSTR_E3M6, + HUSTR_E3M7, + HUSTR_E3M8, + HUSTR_E3M9, + + HUSTR_E4M1, + HUSTR_E4M2, + HUSTR_E4M3, + HUSTR_E4M4, + HUSTR_E4M5, + HUSTR_E4M6, + HUSTR_E4M7, + HUSTR_E4M8, + HUSTR_E4M9, + + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL", + "NEWLEVEL" +}; + +const char* mapnames2[] = +{ + HUSTR_1, + HUSTR_2, + HUSTR_3, + HUSTR_4, + HUSTR_5, + HUSTR_6, + HUSTR_7, + HUSTR_8, + HUSTR_9, + HUSTR_10, + HUSTR_11, + + HUSTR_12, + HUSTR_13, + HUSTR_14, + HUSTR_15, + HUSTR_16, + HUSTR_17, + HUSTR_18, + HUSTR_19, + HUSTR_20, + + HUSTR_21, + HUSTR_22, + HUSTR_23, + HUSTR_24, + HUSTR_25, + HUSTR_26, + HUSTR_27, + HUSTR_28, + HUSTR_29, + HUSTR_30, + HUSTR_31, + HUSTR_32, + HUSTR_33 + +}; + + +const char* mapnamesp[] = +{ + PHUSTR_1, + PHUSTR_2, + PHUSTR_3, + PHUSTR_4, + PHUSTR_5, + PHUSTR_6, + PHUSTR_7, + PHUSTR_8, + PHUSTR_9, + PHUSTR_10, + PHUSTR_11, + + PHUSTR_12, + PHUSTR_13, + PHUSTR_14, + PHUSTR_15, + PHUSTR_16, + PHUSTR_17, + PHUSTR_18, + PHUSTR_19, + PHUSTR_20, + + PHUSTR_21, + PHUSTR_22, + PHUSTR_23, + PHUSTR_24, + PHUSTR_25, + PHUSTR_26, + PHUSTR_27, + PHUSTR_28, + PHUSTR_29, + PHUSTR_30, + PHUSTR_31, + PHUSTR_32 +}; + + // TNT WAD map names. +const char *mapnamest[] = +{ + THUSTR_1, + THUSTR_2, + THUSTR_3, + THUSTR_4, + THUSTR_5, + THUSTR_6, + THUSTR_7, + THUSTR_8, + THUSTR_9, + THUSTR_10, + THUSTR_11, + + THUSTR_12, + THUSTR_13, + THUSTR_14, + THUSTR_15, + THUSTR_16, + THUSTR_17, + THUSTR_18, + THUSTR_19, + THUSTR_20, + + THUSTR_21, + THUSTR_22, + THUSTR_23, + THUSTR_24, + THUSTR_25, + THUSTR_26, + THUSTR_27, + THUSTR_28, + THUSTR_29, + THUSTR_30, + THUSTR_31, + THUSTR_32 +}; + + +const char* shiftxform; + +const char english_shiftxform[] = +{ + + 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, + ' ', '!', '"', '#', '$', '%', '&', + '"', // shift-' + '(', ')', '*', '+', + '<', // shift-, + '_', // shift-- + '>', // shift-. + '?', // shift-/ + ')', // shift-0 + '!', // shift-1 + '@', // shift-2 + '#', // shift-3 + '$', // shift-4 + '%', // shift-5 + '^', // shift-6 + '&', // shift-7 + '*', // shift-8 + '(', // shift-9 + ':', + ':', // shift-; + '<', + '+', // shift-= + '>', '?', '@', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '[', // shift-[ + '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK + ']', // shift-] + '"', '_', + '\'', // shift-` + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '{', '|', '}', '~', 127 +}; + +char ForeignTranslation(unsigned char ch) +{ + return ch; +} + +void HU_Init(void) +{ + + int i; + int j; + char buffer[9]; + + shiftxform = english_shiftxform; + + // load the heads-up font + j = HU_FONTSTART; + for (i=0;ihu_font[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC_SHARED); + } + +} + +void HU_Stop(void) +{ + ::g->headsupactive = false; +} + +void HU_Start(void) +{ + + int i; + const char* s; + + if (::g->headsupactive) + HU_Stop(); + + ::g->plr = &::g->players[::g->consoleplayer]; + ::g->message_on = false; + ::g->message_dontfuckwithme = false; + ::g->message_nottobefuckedwith = false; + ::g->chat_on = false; + + // create the message widget + HUlib_initSText(&::g->w_message, + HU_MSGX, HU_MSGY, HU_MSGHEIGHT, + ::g->hu_font, + HU_FONTSTART, &::g->message_on); + + // create the map title widget + HUlib_initTextLine(&::g->w_title, + HU_TITLEX, HU_TITLEY, + ::g->hu_font, + HU_FONTSTART); + + switch ( ::g->gamemode ) + { + case shareware: + case registered: + case retail: + s = HU_TITLE; + break; + case commercial: + default: + if( DoomLib::expansionSelected == 5 ) { + int map = ::g->gamemap; + if( ::g->gamemap > 9 ) { + map = 0; + } + + s = DoomLib::GetCurrentExpansion()->mapNames[ map - 1 ]; + } else { + s = DoomLib::GetCurrentExpansion()->mapNames[ ::g->gamemap - 1 ]; + } + + + break; + } + + while (*s) + HUlib_addCharToTextLine(&::g->w_title, *(s++)); + + // create the chat widget + HUlib_initIText(&::g->w_chat, + HU_INPUTX, HU_INPUTY, + ::g->hu_font, + HU_FONTSTART, &::g->chat_on); + + // create the inputbuffer widgets + for (i=0 ; iw_inputbuffer[i], 0, 0, 0, 0, &::g->always_off); + + ::g->headsupactive = true; + +} + +void HU_Drawer(void) +{ + + HUlib_drawSText(&::g->w_message); + HUlib_drawIText(&::g->w_chat); + if (::g->automapactive) + HUlib_drawTextLine(&::g->w_title, false); + +} + +void HU_Erase(void) +{ + + HUlib_eraseSText(&::g->w_message); + HUlib_eraseIText(&::g->w_chat); + HUlib_eraseTextLine(&::g->w_title); + +} + +void HU_Ticker(void) +{ + // tick down message counter if message is up + if (::g->message_counter && !--::g->message_counter) + { + ::g->message_on = false; + ::g->message_nottobefuckedwith = false; + } + + if ( ( m_inDemoMode.GetBool() == false && m_show_messages.GetBool() ) || ::g->message_dontfuckwithme) + { + + // display message if necessary + if ((::g->plr->message && !::g->message_nottobefuckedwith) + || (::g->plr->message && ::g->message_dontfuckwithme)) + { + HUlib_addMessageToSText(&::g->w_message, 0, ::g->plr->message); + ::g->plr->message = 0; + ::g->message_on = true; + ::g->message_counter = HU_MSGTIMEOUT; + ::g->message_nottobefuckedwith = ::g->message_dontfuckwithme; + ::g->message_dontfuckwithme = 0; + } + + } // else ::g->message_on = false; +} + + + + +void HU_queueChatChar(char c) +{ + if (((::g->head + 1) & (QUEUESIZE-1)) == ::g->tail) + { + ::g->plr->message = HUSTR_MSGU; + } + else + { + ::g->chatchars[::g->head] = c; + ::g->head = (::g->head + 1) & (QUEUESIZE-1); + } +} + +char HU_dequeueChatChar(void) +{ + char c; + + if (::g->head != ::g->tail) + { + c = ::g->chatchars[::g->tail]; + ::g->tail = (::g->tail + 1) & (QUEUESIZE-1); + } + else + { + c = 0; + } + + return c; +} + +qboolean HU_Responder(event_t *ev) +{ + + const char* macromessage; + qboolean eatkey = false; + unsigned char c; + int i; + int numplayers; + + const static char destination_keys[MAXPLAYERS] = + { + HUSTR_KEYGREEN, + HUSTR_KEYINDIGO, + HUSTR_KEYBROWN, + HUSTR_KEYRED + }; + + + numplayers = 0; + for (i=0 ; iplayeringame[i]; + + if (ev->data1 == KEY_RSHIFT) + { + ::g->shiftdown = ev->type == ev_keydown; + return false; + } + else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT) + { + ::g->altdown = ev->type == ev_keydown; + return false; + } + + if (ev->type != ev_keydown) + return false; + + if (!::g->chat_on) + { + if (ev->data1 == HU_MSGREFRESH) + { + ::g->message_on = true; + ::g->message_counter = HU_MSGTIMEOUT; + eatkey = true; + } + else if (::g->netgame && ev->data1 == HU_INPUTTOGGLE) + { + eatkey = ::g->chat_on = true; + HUlib_resetIText(&::g->w_chat); + HU_queueChatChar(HU_BROADCAST); + } + else if (::g->netgame && numplayers > 2) + { + for (i=0; idata1 == destination_keys[i]) + { + if (::g->playeringame[i] && i!=::g->consoleplayer) + { + eatkey = ::g->chat_on = true; + HUlib_resetIText(&::g->w_chat); + HU_queueChatChar(i+1); + break; + } + else if (i == ::g->consoleplayer) + { + ::g->num_nobrainers++; + if (::g->num_nobrainers < 3) + ::g->plr->message = HUSTR_TALKTOSELF1; + else if (::g->num_nobrainers < 6) + ::g->plr->message = HUSTR_TALKTOSELF2; + else if (::g->num_nobrainers < 9) + ::g->plr->message = HUSTR_TALKTOSELF3; + else if (::g->num_nobrainers < 32) + ::g->plr->message = HUSTR_TALKTOSELF4; + else + ::g->plr->message = HUSTR_TALKTOSELF5; + } + } + } + } + } + else + { + c = ev->data1; + // send a macro + if (::g->altdown) + { + c = c - '0'; + if (c > 9) + return false; + // I_PrintfE( "got here\n"); + macromessage = temp_chat_macros[c]; + + // kill last message with a '\n' + HU_queueChatChar(KEY_ENTER); // DEBUG!!! + + // send the macro message + while (*macromessage) + HU_queueChatChar(*macromessage++); + HU_queueChatChar(KEY_ENTER); + + // leave chat mode and notify that it was sent + ::g->chat_on = false; + strcpy(::g->lastmessage, temp_chat_macros[c]); + ::g->plr->message = ::g->lastmessage; + eatkey = true; + } + else + { + if (::g->shiftdown || (c >= 'a' && c <= 'z')) + c = shiftxform[c]; + eatkey = HUlib_keyInIText(&::g->w_chat, c); + if (eatkey) + { + // static unsigned char buf[20]; // DEBUG + HU_queueChatChar(c); + + // sprintf(buf, "KEY: %d => %d", ev->data1, c); + // ::g->plr->message = buf; + } + if (c == KEY_ENTER) + { + ::g->chat_on = false; + if (::g->w_chat.l.len) + { + strcpy(::g->lastmessage, ::g->w_chat.l.l); + ::g->plr->message = ::g->lastmessage; + } + } + else if (c == KEY_ESCAPE) + ::g->chat_on = false; + } + } + + return eatkey; + +} + diff --git a/doomclassic/doom/hu_stuff.h b/doomclassic/doom/hu_stuff.h new file mode 100644 index 00000000..c72a5f0f --- /dev/null +++ b/doomclassic/doom/hu_stuff.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __HU_STUFF_H__ +#define __HU_STUFF_H__ + +#include "d_event.h" + + +// +// Globally visible constants. +// +#define HU_FONTSTART '!' // the first font characters +#define HU_FONTEND '_' // the last font characters + +// Calculate # of glyphs in font. +#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1) + +#define HU_BROADCAST 5 + +#define HU_MSGREFRESH KEY_ENTER +#define HU_MSGX 0 +#define HU_MSGY 0 +#define HU_MSGWIDTH 64 // in characters +#define HU_MSGHEIGHT 1 // in lines + +#define HU_MSGTIMEOUT (4*TICRATE) + +// +// HEADS UP TEXT +// + +void HU_Init(void); +void HU_Start(void); + +qboolean HU_Responder(event_t* ev); + +void HU_Ticker(void); +void HU_Drawer(void); +char HU_dequeueChatChar(void); +void HU_Erase(void); + + +#endif + diff --git a/doomclassic/doom/i_input.cpp b/doomclassic/doom/i_input.cpp new file mode 100644 index 00000000..8e70a5de --- /dev/null +++ b/doomclassic/doom/i_input.cpp @@ -0,0 +1,474 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include +#include +#include + +#include "i_video.h" +#include "i_system.h" + +#include "doomstat.h" +#include "v_video.h" +#include "m_argv.h" +#include "d_main.h" + +#include "doomdef.h" + +#include "sys/sys_public.h" + +#define ALLOW_CHEATS 1 + + + +extern int PLAYERCOUNT; + +#define NUM_BUTTONS 4 + +static bool Cheat_God( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + ::g->plyr->cheats ^= CF_GODMODE; + if (::g->plyr->cheats & CF_GODMODE) + { + if (::g->plyr->mo) + ::g->plyr->mo->health = 100; + + ::g->plyr->health = 100; + ::g->plyr->message = STSTR_DQDON; + } + else + ::g->plyr->message = STSTR_DQDOFF; + return true; +} + +#include "g_game.h" +static bool Cheat_NextLevel( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + G_ExitLevel(); + + return true; +} + +static bool Cheat_GiveAll( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + ::g->plyr->armorpoints = 200; + ::g->plyr->armortype = 2; + + int i; + for (i=0;iplyr->weaponowned[i] = true; + + for (i=0;iplyr->ammo[i] = ::g->plyr->maxammo[i]; + + for (i=0;iplyr->cards[i] = true; + + ::g->plyr->message = STSTR_KFAADDED; + return true; +} + +static bool Cheat_GiveAmmo( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + ::g->plyr->armorpoints = 200; + ::g->plyr->armortype = 2; + + int i; + for (i=0;iplyr->weaponowned[i] = true; + + for (i=0;iplyr->ammo[i] = ::g->plyr->maxammo[i]; + + ::g->plyr->message = STSTR_KFAADDED; + return true; +} + +static bool Cheat_Choppers( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + ::g->plyr->weaponowned[wp_chainsaw] = true; + ::g->plyr->message = "Chainsaw!"; + return true; +} + +extern qboolean P_GivePower ( player_t* player, int /*powertype_t*/ power ); + +static void TogglePowerUp( int i ) { + if (!::g->plyr->powers[i]) + P_GivePower( ::g->plyr, i); + else if (i!=pw_strength) + ::g->plyr->powers[i] = 1; + else + ::g->plyr->powers[i] = 0; + + ::g->plyr->message = STSTR_BEHOLDX; +} + +static bool Cheat_GiveInvul( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + TogglePowerUp( 0 ); + return true; +} + +static bool Cheat_GiveBerserk( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + TogglePowerUp( 1 ); + return true; +} + +static bool Cheat_GiveBlur( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + TogglePowerUp( 2 ); + return true; +} + +static bool Cheat_GiveRad( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + TogglePowerUp( 3 ); + return true; +} + +static bool Cheat_GiveMap( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + TogglePowerUp( 4 ); + return true; +} + +static bool Cheat_GiveLight( void ) { + if( PLAYERCOUNT != 1 || ::g->netgame ) { + return false; + } + + TogglePowerUp( 5 ); + return true; +} + + + +#ifndef __PS3__ + +static bool tracking = false; +static int currentCode[NUM_BUTTONS]; +static int currentCheatLength; + +#endif + +typedef bool(*cheat_command)(void); +struct cheatcode_t +{ + int code[NUM_BUTTONS]; + cheat_command function; +}; + +static cheatcode_t codes[] = { + { {0, 1, 1, 0}, Cheat_God }, // a b b a + { {0, 0, 1, 1}, Cheat_NextLevel }, // a a b b + { {1, 0, 1, 0}, Cheat_GiveAmmo }, // b a b a + { {1, 1, 0, 0}, Cheat_Choppers}, // b b a a + { {0, 1, 0, 1}, Cheat_GiveAll }, // a b a b + { {2, 3, 3, 2}, Cheat_GiveInvul }, // x y y x + { {2, 2, 2, 3}, Cheat_GiveBerserk }, // x x x y + { {2, 2, 3, 3}, Cheat_GiveBlur }, // x x y y + { {2, 3, 3, 3}, Cheat_GiveRad }, // x y y y + { {3, 2, 3, 2}, Cheat_GiveMap }, // y x y x + { {3, 3, 3, 2}, Cheat_GiveLight}, // y y y x +}; + +const static int numberOfCodes = sizeof(codes) / sizeof(codes[0]); + + +void BeginTrackingCheat( void ) { +#if ALLOW_CHEATS + tracking = true; + currentCheatLength = 0; + memset( currentCode, 0, sizeof( currentCode ) ); +#endif +} + +void EndTrackingCheat( void ) { +#if ALLOW_CHEATS + tracking = false; +#endif +} + +extern void S_StartSound ( void* origin, int sfx_id ); + +void CheckCheat( int button ) { +#if ALLOW_CHEATS + if( tracking && !::g->netgame ) { + + currentCode[ currentCheatLength++ ] = button; + + if( currentCheatLength == NUM_BUTTONS ) { + for( int i = 0; i < numberOfCodes; ++i) { + if( memcmp( &codes[i].code[0], ¤tCode[0], sizeof(currentCode) ) == 0 ) { + if(codes[i].function()) { + S_StartSound(0, sfx_cybsit); + } + } + } + // reset the code + memset( currentCode, 0, sizeof( currentCode ) ); + currentCheatLength = 0; + } + } +#endif +} + + + +float xbox_deadzone = 0.28f; + +// input event storage +//PRIVATE TO THE INPUT THREAD! + + + +void I_InitInput(void) +{ +} + +void I_ShutdownInput( void ) +{ +} + + +static float _joyAxisConvert(short x, float xbxScale, float dScale, float deadZone) +{ + //const float signConverted = x - 127; + float y = x - 127; + y = y / xbxScale; + return (fabs(y) < deadZone) ? 0.f : (y * dScale); +} + + +int I_PollMouseInputEvents( controller_t *con) +{ + int numEvents = 0; + + return numEvents; +} + +int I_ReturnMouseInputEvent( const int n, event_t* e) { + e->type = ev_mouse; + e->data1 = e->data2 = e->data3 = 0; + + switch(::g->mouseEvents[n].type) { + case IETAxis: + switch (::g->mouseEvents[n].action) + { + case M_DELTAX: + e->data2 = ::g->mouseEvents[n].data; + break; + case M_DELTAY: + e->data3 = ::g->mouseEvents[n].data; + break; + } + return 1; + + default: + break; + } + return 0; +} + +int I_PollJoystickInputEvents( controller_t *con ) { + int numEvents = 0; + + return numEvents; +} + +// +// Translates the key currently in X_event +// +static int xlatekey(int key) +{ + int rc = KEY_F1; + + switch (key) + { + case 0: // A + //rc = KEY_ENTER; + rc = ' '; + break; + case 3: // Y + rc = '1'; + break; + case 1: // B + if( ::g->menuactive ) { + rc = KEY_BACKSPACE; + } + else { + rc = '2'; + } + break; + case 2: // X + //rc = ' '; + rc = KEY_TAB; + break; + case 4: // White + rc = KEY_MINUS; + break; + case 5: // Black + rc = KEY_EQUALS; + break; + case 6: // Left triggers + rc = KEY_RSHIFT; + break; + case 7: // Right + rc = KEY_RCTRL; + break; + case 8: // Up + if( ::g->menuactive ) { + rc = KEY_UPARROW; + } + else { + //rc = KEY_ENTER; + rc = '3'; + } + break; + case 9: + if( ::g->menuactive ) { + rc = KEY_DOWNARROW; + } + else { + //rc = KEY_TAB; + rc = '5'; + } + break; + case 10: + if( ::g->menuactive ) { + rc = KEY_UPARROW; + } + else { + //rc = '1'; + rc = '6'; + } + break; + case 11: + if( ::g->menuactive ) { + rc = KEY_DOWNARROW; + } + else { + //rc = '2'; + rc = '4'; + } + break; + case 12: // start + rc = KEY_ESCAPE; + break; + case 13: //select + //rc = KEY_ESCAPE; + break; + case 14: // lclick + case 15: // rclick + //rc = ' '; + break; + } + return rc; +} + +int I_ReturnJoystickInputEvent( const int n, event_t* e) { + + e->data1 = e->data2 = e->data3 = 0; + + switch(::g->joyEvents[n].type) + { + case IETAxis: + e->type = ev_joystick;//ev_mouse; + switch (::g->joyEvents[n].action) + { + case J_DELTAX: +/* + if (::g->joyEvents[n].data < 0) + e->data2 = -1; + else if (::g->joyEvents[n].data > 0) + e->data2 = 1; +*/ + e->data2 = ::g->joyEvents[n].data; + break; + case J_DELTAY: + e->type = ev_mouse; + e->data3 = ::g->joyEvents[n].data; + break; + } + return 1; + case IETButtonAnalog: + case IETButtonDigital: + if (::g->joyEvents[n].data) + e->type = ev_keydown; + else + e->type = ev_keyup; + e->data1 = xlatekey(::g->joyEvents[n].action); + return 1; + + case IETNone: + break; + } + + return 0; +} + +void I_EndJoystickInputEvents( void ) { + int i; + for(i = 0; i < 18; i++) + { + ::g->joyEvents[i].type = IETNone; + } + +} + diff --git a/doomclassic/doom/i_main.cpp b/doomclassic/doom/i_main.cpp new file mode 100644 index 00000000..98f8cc36 --- /dev/null +++ b/doomclassic/doom/i_main.cpp @@ -0,0 +1,31 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + diff --git a/doomclassic/doom/i_net.cpp b/doomclassic/doom/i_net.cpp new file mode 100644 index 00000000..ae3c640b --- /dev/null +++ b/doomclassic/doom/i_net.cpp @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include +#include +#include + +#include + +#include "i_system.h" +#include "d_event.h" +#include "d_net.h" +#include "m_argv.h" + +#include "doomstat.h" + +#ifdef __GNUG__ +#pragma implementation "i_net.h" +#endif +#include "i_net.h" + + + + + +// For some odd reason... + + +void NetSend (void); +qboolean NetListen (void); + diff --git a/doomclassic/doom/i_net.h b/doomclassic/doom/i_net.h new file mode 100644 index 00000000..31c46e65 --- /dev/null +++ b/doomclassic/doom/i_net.h @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __I_NET__ +#define __I_NET__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + + +// Called by D_DoomMain. + + +void I_InitNetwork (void); +void I_NetCmd (void); + +// DHM - Nerve +void I_ShutdownNetwork( void ); + +#endif + diff --git a/doomclassic/doom/i_net_ps3.cpp b/doomclassic/doom/i_net_ps3.cpp new file mode 100644 index 00000000..c4a8303c --- /dev/null +++ b/doomclassic/doom/i_net_ps3.cpp @@ -0,0 +1,423 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include +#include +#include + +#include + +// Sockets +#include +#include +#include +#include +#include + +#include "i_system.h" +#include "d_event.h" +#include "d_net.h" +#include "m_argv.h" + +#include "doomstat.h" + +#include "i_net.h" + +#include "doomlib.h" +#include "../Main/Main.h" + +void NetSend (void); +qboolean NetListen (void); + +namespace { + bool IsValidSocket( int socketDescriptor ); + int GetLastSocketError(); + + + + /* + ======================== + Returns true if the socket is valid. I made this function to help abstract the differences + between WinSock (used on Xbox) and BSD sockets, which the PS3 follows more closely. + ======================== + */ + bool IsValidSocket( int socketDescriptor ) { + return socketDescriptor >= 0; + } + + /* + ======================== + Returns the last error reported by the platform's socket library. + ======================== + */ + int GetLastSocketError() { + return sys_net_errno; + } +} + +// +// NETWORKING +// +int DOOMPORT = 1002; // DHM - Nerve :: On original XBox, ports 1000 - 1255 saved you a byte on every packet. 360 too? + + +unsigned long GetServerIP() { + return ::g->sendaddress[::g->doomcom.consoleplayer].sin_addr.s_addr; +} + +void (*netget) (void); +void (*netsend) (void); + + +// +// UDPsocket +// +int UDPsocket (void) +{ + int s; + + // allocate a socket + s = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if ( !IsValidSocket( s ) ) { + int err = GetLastSocketError(); + I_Error( "can't create socket, error %d", err ); + } + + return s; +} + +// +// BindToLocalPort +// +void BindToLocalPort( int s, int port ) +{ + int v; + struct sockaddr_in address; + + memset (&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = port; + + v = bind (s, (sockaddr*)&address, sizeof(address)); + //if (v == -1) + //I_Error ("BindToPort: bind: %s", strerror(errno)); +} + + +// +// PacketSend +// +void PacketSend (void) +{ + int c; + doomdata_t sw; + + // byte swap + sw.checksum = htonl(::g->netbuffer->checksum); + sw.sourceDest = DoomLib::BuildSourceDest(::g->doomcom.remotenode); + sw.player = ::g->netbuffer->player; + sw.retransmitfrom = ::g->netbuffer->retransmitfrom; + sw.starttic = ::g->netbuffer->starttic; + sw.numtics = ::g->netbuffer->numtics; + for (c=0 ; c< ::g->netbuffer->numtics ; c++) + { + sw.cmds[c].forwardmove = ::g->netbuffer->cmds[c].forwardmove; + sw.cmds[c].sidemove = ::g->netbuffer->cmds[c].sidemove; + sw.cmds[c].angleturn = htons(::g->netbuffer->cmds[c].angleturn); + sw.cmds[c].consistancy = htons(::g->netbuffer->cmds[c].consistancy); + sw.cmds[c].buttons = ::g->netbuffer->cmds[c].buttons; + } + + // Send Socket + { + //DWORD num_sent; + + //if ( globalNetworking ) { + // c = WSASendTo(::g->sendsocket, &buffer, 1, &num_sent, 0, (sockaddr*)&::g->sendaddress[::g->doomcom.remotenode], + // sizeof(::g->sendaddress[::g->doomcom.remotenode]), 0, 0); + //} else { + c = DoomLib::Send( (char*)&sw, ::g->doomcom.datalength, (sockaddr_in*)&::g->sendaddress[::g->doomcom.remotenode], ::g->doomcom.remotenode ); + //} + } +} + + +// +// PacketGet +// +void PacketGet (void) +{ + int i; + int c; + struct sockaddr_in fromaddress; + int fromlen; + doomdata_t sw; + DWORD num_recieved; //, flags = 0; + + // Try and read a socket + //buffer.buf = (char*)&sw; + //buffer.len = sizeof(sw); + fromlen = sizeof(fromaddress); + + //if ( globalNetworking ) { + // c = WSARecvFrom(::g->insocket, &buffer, 1, &num_recieved, &flags, (struct sockaddr*)&fromaddress, &fromlen, 0, 0); + //} else { + c = DoomLib::Recv( (char*)&sw, &num_recieved ); + //} + if ( c < 0 ) + { + /*if ( globalNetworking ) { + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) + I_Error ("GetPacket: %s",strerror(errno)); + }*/ + + ::g->doomcom.remotenode = -1; // no packet + return; + } + + + + // find remote node number + /*for (i=0 ; i<::g->doomcom.numnodes ; i++) + if ( fromaddress.sin_addr.s_addr == ::g->sendaddress[i].sin_addr.s_addr ) + break; + + if (i == ::g->doomcom.numnodes) + { + // packet is not from one of the ::g->players (new game broadcast) + ::g->doomcom.remotenode = -1; // no packet + return; + }*/ + + //if ( ::g->consoleplayer == 1 ) { + //int x = 0; + //} + + int source; + int dest; + DoomLib::GetSourceDest( sw.sourceDest, &source, &dest ); + + i = source; + + //if ( ::g->consoleplayer == 1 ) { + //if ( i == 2 ) { + //int suck = 0; + //} + //} + + ::g->doomcom.remotenode = i; // good packet from a game player + ::g->doomcom.datalength = (short)num_recieved; + + // byte swap + ::g->netbuffer->checksum = ntohl(sw.checksum); + ::g->netbuffer->player = sw.player; + ::g->netbuffer->retransmitfrom = sw.retransmitfrom; + ::g->netbuffer->starttic = sw.starttic; + ::g->netbuffer->numtics = sw.numtics; + + for ( c = 0; c < ::g->netbuffer->numtics; c++ ) + { + ::g->netbuffer->cmds[c].forwardmove = sw.cmds[c].forwardmove; + ::g->netbuffer->cmds[c].sidemove = sw.cmds[c].sidemove; + ::g->netbuffer->cmds[c].angleturn = ntohs(sw.cmds[c].angleturn); + ::g->netbuffer->cmds[c].consistancy = ntohs(sw.cmds[c].consistancy); + ::g->netbuffer->cmds[c].buttons = sw.cmds[c].buttons; + } +} + +static int I_TrySetupNetwork(void) +{ + // DHM - Moved to Session + return 1; +} + +// +// I_InitNetwork +// +void I_InitNetwork (void) +{ + + //qboolean trueval = true; + int i; + int p; + //int a = 0; + // struct hostent* hostentry; // host information entry + + memset (&::g->doomcom, 0, sizeof(::g->doomcom) ); + + // set up for network + i = M_CheckParm ("-dup"); + if (i && i< ::g->myargc-1) + { + ::g->doomcom.ticdup = ::g->myargv[i+1][0]-'0'; + if (::g->doomcom.ticdup < 1) + ::g->doomcom.ticdup = 1; + if (::g->doomcom.ticdup > 9) + ::g->doomcom.ticdup = 9; + } + else + ::g->doomcom.ticdup = 1; + + if (M_CheckParm ("-extratic")) + ::g->doomcom.extratics = 1; + else + ::g->doomcom.extratics = 0; + + p = M_CheckParm ("-port"); + if (p && p < ::g->myargc-1) + { + DOOMPORT = atoi (::g->myargv[p+1]); + I_Printf ("using alternate port %i\n",DOOMPORT); + } + + // parse network game options, + // -net <::g->consoleplayer> ... + i = M_CheckParm ("-net"); + if (!i || !I_TrySetupNetwork()) + { + // single player game + ::g->netgame = false; + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.numplayers = ::g->doomcom.numnodes = 1; + ::g->doomcom.deathmatch = false; + ::g->doomcom.consoleplayer = 0; + return; + } + + netsend = PacketSend; + netget = PacketGet; + + ::g->netgame = true; + + { + ++i; // skip the '-net' + ::g->doomcom.numnodes = 0; + ::g->doomcom.consoleplayer = atoi( ::g->myargv[i] ); + // skip the console number + ++i; + ::g->doomcom.numnodes = 0; + for (; i < ::g->myargc; ++i) + { + ::g->sendaddress[::g->doomcom.numnodes].sin_family = AF_INET; + ::g->sendaddress[::g->doomcom.numnodes].sin_port = htons(DOOMPORT); + + // Pull out the port number. + const std::string ipAddressWithPort( ::g->myargv[i] ); + const std::size_t colonPosition = ipAddressWithPort.find_last_of(':'); + std::string ipOnly; + + if( colonPosition != std::string::npos && colonPosition + 1 < ipAddressWithPort.size() ) { + const std::string portOnly( ipAddressWithPort.substr( colonPosition + 1 ) ); + + ::g->sendaddress[::g->doomcom.numnodes].sin_port = htons( atoi( portOnly.c_str() ) ); + + ipOnly = ipAddressWithPort.substr( 0, colonPosition ); + } else { + // Assume the address doesn't include a port. + ipOnly = ipAddressWithPort; + } + + in_addr_t ipAddress = inet_addr( ipOnly.c_str() ); + + if ( ipAddress == INADDR_NONE ) { + I_Error( "Invalid IP Address: %s\n", ipOnly.c_str() ); + session->QuitMatch(); + common->AddDialog( GDM_OPPONENT_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false ); + } + ::g->sendaddress[::g->doomcom.numnodes].sin_addr.s_addr = ipAddress; + ::g->doomcom.numnodes++; + } + + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.numplayers = ::g->doomcom.numnodes; + } + + if ( globalNetworking ) { + // Setup sockets + ::g->insocket = UDPsocket (); + BindToLocalPort (::g->insocket,htons(DOOMPORT)); + + // PS3 call to enable non-blocking mode + int nonblocking = 1; // Non-zero is nonblocking mode. + setsockopt( ::g->insocket, SOL_SOCKET, SO_NBIO, &nonblocking, sizeof(nonblocking)); + + ::g->sendsocket = UDPsocket (); + + I_Printf( "[+] Setting up sockets for player %d\n", DoomLib::GetPlayer() ); + } +} + +// DHM - Nerve +void I_ShutdownNetwork( void ) { + if ( globalNetworking && gameLocal != NULL ) { + + int curPlayer = DoomLib::GetPlayer(); + + for (int player = 0; player < gameLocal->Interface.GetNumPlayers(); ++player) + { + DoomLib::SetPlayer( player ); + + if ( IsValidSocket( ::g->insocket ) ) { + I_Printf( "[-] Shut down insocket for player %d\n", DoomLib::GetPlayer() ); + shutdown( ::g->insocket, SHUT_RDWR ); + socketclose( ::g->insocket ); + } + if ( IsValidSocket( ::g->sendsocket ) ) { + I_Printf( "[-] Shut down sendsocket for player %d\n", DoomLib::GetPlayer() ); + shutdown( ::g->sendsocket, SHUT_RDWR ); + socketclose( ::g->sendsocket ); + } + } + + DoomLib::SetPlayer(curPlayer); + + globalNetworking = false; + } +} + +void I_NetCmd (void) +{ + if (::g->doomcom.command == CMD_SEND) + { + netsend (); + } + else if (::g->doomcom.command == CMD_GET) + { + netget (); + } + else + I_Error ("Bad net cmd: %i\n",::g->doomcom.command); +} + diff --git a/doomclassic/doom/i_net_win32.cpp b/doomclassic/doom/i_net_win32.cpp new file mode 100644 index 00000000..b97a7506 --- /dev/null +++ b/doomclassic/doom/i_net_win32.cpp @@ -0,0 +1,278 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include +#include +#include + +#include + +#include "i_system.h" +#include "d_event.h" +#include "d_net.h" +#include "m_argv.h" + +#include "doomstat.h" + +#include "i_net.h" + +#include "doomlib.h" + +void NetSend (void); +qboolean NetListen (void); + +namespace { + bool IsValidSocket( int socketDescriptor ); + int GetLastSocketError(); + + + + /* + ======================== + Returns true if the socket is valid. I made this function to help abstract the differences + between WinSock (used on Xbox) and BSD sockets, which the PS3 follows more closely. + ======================== + */ + bool IsValidSocket( int socketDescriptor ) { + return false; + } + + /* + ======================== + Returns the last error reported by the platform's socket library. + ======================== + */ + int GetLastSocketError() { + return 0; + } +} + +// +// NETWORKING +// +int DOOMPORT = 1002; // DHM - Nerve :: On original XBox, ports 1000 - 1255 saved you a byte on every packet. 360 too? + + +unsigned long GetServerIP() { + return ::g->sendaddress[::g->doomcom.consoleplayer].sin_addr.s_addr; +} + +void (*netget) (void); +void (*netsend) (void); + + +// +// UDPsocket +// +int UDPsocket (void) +{ + int s; + + // allocate a socket + s = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if ( !IsValidSocket( s ) ) { + int err = GetLastSocketError(); + I_Error( "can't create socket, error %d", err ); + } + + return s; +} + +// +// BindToLocalPort +// +void BindToLocalPort( int s, int port ) +{ + +} + + +// +// PacketSend +// +void PacketSend (void) +{ + +} + + +// +// PacketGet +// +void PacketGet (void) +{ + +} + +static int I_TrySetupNetwork(void) +{ + // DHM - Moved to Session + return 1; +} + +// +// I_InitNetwork +// +void I_InitNetwork (void) +{ + //qboolean trueval = true; + int i; + int p; + //int a = 0; + // struct hostent* hostentry; // host information entry + + memset (&::g->doomcom, 0, sizeof(::g->doomcom) ); + + // set up for network + i = M_CheckParm ("-dup"); + if (i && i< ::g->myargc-1) + { + ::g->doomcom.ticdup = ::g->myargv[i+1][0]-'0'; + if (::g->doomcom.ticdup < 1) + ::g->doomcom.ticdup = 1; + if (::g->doomcom.ticdup > 9) + ::g->doomcom.ticdup = 9; + } + else + ::g->doomcom.ticdup = 1; + + if (M_CheckParm ("-extratic")) + ::g->doomcom.extratics = 1; + else + ::g->doomcom.extratics = 0; + + p = M_CheckParm ("-port"); + if (p && p < ::g->myargc-1) + { + DOOMPORT = atoi (::g->myargv[p+1]); + I_Printf ("using alternate port %i\n",DOOMPORT); + } + + // parse network game options, + // -net <::g->consoleplayer> ... + i = M_CheckParm ("-net"); + if (!i || !I_TrySetupNetwork()) + { + // single player game + ::g->netgame = false; + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.numplayers = ::g->doomcom.numnodes = 1; + ::g->doomcom.deathmatch = false; + ::g->doomcom.consoleplayer = 0; + return; + } + + netsend = PacketSend; + netget = PacketGet; + +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + ::g->netgame = true; + + { + ++i; // skip the '-net' + ::g->doomcom.numnodes = 0; + ::g->doomcom.consoleplayer = atoi( ::g->myargv[i] ); + // skip the console number + ++i; + ::g->doomcom.numnodes = 0; + for (; i < ::g->myargc; ++i) + { + ::g->sendaddress[::g->doomcom.numnodes].sin_family = AF_INET; + ::g->sendaddress[::g->doomcom.numnodes].sin_port = htons(DOOMPORT); + + // Pull out the port number. + const std::string ipAddressWithPort( ::g->myargv[i] ); + const std::size_t colonPosition = ipAddressWithPort.find_last_of(':'); + std::string ipOnly; + + if( colonPosition != std::string::npos && colonPosition + 1 < ipAddressWithPort.size() ) { + const std::string portOnly( ipAddressWithPort.substr( colonPosition + 1 ) ); + + ::g->sendaddress[::g->doomcom.numnodes].sin_port = htons( atoi( portOnly.c_str() ) ); + + ipOnly = ipAddressWithPort.substr( 0, colonPosition ); + } else { + // Assume the address doesn't include a port. + ipOnly = ipAddressWithPort; + } + + in_addr_t ipAddress = inet_addr( ipOnly.c_str() ); + + if ( ipAddress == INADDR_NONE ) { + I_Error( "Invalid IP Address: %s\n", ipOnly.c_str() ); + session->QuitMatch(); + common->AddDialog( GDM_OPPONENT_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false ); + } + ::g->sendaddress[::g->doomcom.numnodes].sin_addr.s_addr = ipAddress; + ::g->doomcom.numnodes++; + } + + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.numplayers = ::g->doomcom.numnodes; + } + + if ( globalNetworking ) { + // Setup sockets + ::g->insocket = UDPsocket (); + BindToLocalPort (::g->insocket,htons(DOOMPORT)); + + // PS3 call to enable non-blocking mode + int nonblocking = 1; // Non-zero is nonblocking mode. + setsockopt( ::g->insocket, SOL_SOCKET, SO_NBIO, &nonblocking, sizeof(nonblocking)); + + ::g->sendsocket = UDPsocket (); + + I_Printf( "[+] Setting up sockets for player %d\n", DoomLib::GetPlayer() ); + } +#endif +} + +// DHM - Nerve +void I_ShutdownNetwork( void ) { + +} + +void I_NetCmd (void) +{ + if (::g->doomcom.command == CMD_SEND) + { + netsend (); + } + else if (::g->doomcom.command == CMD_GET) + { + netget (); + } + else + I_Error ("Bad net cmd: %i\n",::g->doomcom.command); +} + diff --git a/doomclassic/doom/i_net_xbox.cpp b/doomclassic/doom/i_net_xbox.cpp new file mode 100644 index 00000000..f554c032 --- /dev/null +++ b/doomclassic/doom/i_net_xbox.cpp @@ -0,0 +1,365 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include +#include + +#include + +#include "i_system.h" +#include "d_event.h" +#include "d_net.h" +#include "m_argv.h" + +#include "doomstat.h" + +#include "i_net.h" + +#include "doomlib.h" +#include "../Main/Main.h" + +void NetSend (void); +qboolean NetListen (void); + +// +// NETWORKING +// +int DOOMPORT = 1002; // DHM - Nerve :: On original XBox, ports 1000 - 1255 saved you a byte on every packet. 360 too? + + + +unsigned long GetServerIP() { return ::g->sendaddress[::g->doomcom.consoleplayer].sin_addr.s_addr; } + +void (*netget) (void); +void (*netsend) (void); + + +// +// UDPsocket +// +int UDPsocket (void) +{ + int s; + + // allocate a socket + s = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s == INVALID_SOCKET) { + int err = WSAGetLastError (); + I_Error ("can't create socket: %s",strerror(errno)); + } + + return s; +} + +// +// BindToLocalPort +// +void BindToLocalPort( int s, int port ) +{ + int v; + struct sockaddr_in address; + + memset (&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = port; + + v = bind (s, (sockaddr*)&address, sizeof(address)); + //if (v == -1) + //I_Error ("BindToPort: bind: %s", strerror(errno)); +} + + +// +// PacketSend +// +void PacketSend (void) +{ + int c; + doomdata_t sw; + + // byte swap + sw.checksum = htonl(::g->netbuffer->checksum); + sw.sourceDest = DoomLib::BuildSourceDest(::g->doomcom.remotenode); + sw.player = ::g->netbuffer->player; + sw.retransmitfrom = ::g->netbuffer->retransmitfrom; + sw.starttic = ::g->netbuffer->starttic; + sw.numtics = ::g->netbuffer->numtics; + for (c=0 ; c< ::g->netbuffer->numtics ; c++) + { + sw.cmds[c].forwardmove = ::g->netbuffer->cmds[c].forwardmove; + sw.cmds[c].sidemove = ::g->netbuffer->cmds[c].sidemove; + sw.cmds[c].angleturn = htons(::g->netbuffer->cmds[c].angleturn); + //sw.cmds[c].consistancy = htons(::g->netbuffer->cmds[c].consistancy); + sw.cmds[c].buttons = ::g->netbuffer->cmds[c].buttons; + } + + // Send Socket + { + //DWORD num_sent; + WSABUF buffer; + + buffer.buf = (char*)&sw; + buffer.len = ::g->doomcom.datalength; + + //if ( globalNetworking ) { + // c = WSASendTo(::g->sendsocket, &buffer, 1, &num_sent, 0, (sockaddr*)&::g->sendaddress[::g->doomcom.remotenode], + // sizeof(::g->sendaddress[::g->doomcom.remotenode]), 0, 0); + //} else { + c = DoomLib::Send( buffer.buf, buffer.len, (sockaddr_in*)&::g->sendaddress[::g->doomcom.remotenode], ::g->doomcom.remotenode ); + //} + } +} + + +// +// PacketGet +// +void PacketGet (void) +{ + int i; + int c; + struct sockaddr_in fromaddress; + int fromlen; + doomdata_t sw; + WSABUF buffer; + DWORD num_recieved, flags = 0; + + // Try and read a socket + buffer.buf = (char*)&sw; + buffer.len = sizeof(sw); + fromlen = sizeof(fromaddress); + + //if ( globalNetworking ) { + // c = WSARecvFrom(::g->insocket, &buffer, 1, &num_recieved, &flags, (struct sockaddr*)&fromaddress, &fromlen, 0, 0); + //} else { + c = DoomLib::Recv( (char*)&sw, &num_recieved ); + //} + if (c == SOCKET_ERROR ) + { + /*if ( globalNetworking ) { + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) + I_Error ("GetPacket: %s",strerror(errno)); + }*/ + + ::g->doomcom.remotenode = -1; // no packet + return; + } + + + + // find remote node number + /*for (i=0 ; i<::g->doomcom.numnodes ; i++) + if ( fromaddress.sin_addr.s_addr == ::g->sendaddress[i].sin_addr.s_addr ) + break; + + if (i == ::g->doomcom.numnodes) + { + // packet is not from one of the ::g->players (new game broadcast) + ::g->doomcom.remotenode = -1; // no packet + return; + }*/ + + //if ( ::g->consoleplayer == 1 ) { + //int x = 0; + //} + + int source; + int dest; + DoomLib::GetSourceDest( sw.sourceDest, &source, &dest ); + + i = source; + + //if ( ::g->consoleplayer == 1 ) { + //if ( i == 2 ) { + //int suck = 0; + //} + //} + + ::g->doomcom.remotenode = i; // good packet from a game player + ::g->doomcom.datalength = (short)num_recieved; + + // byte swap + ::g->netbuffer->checksum = ntohl(sw.checksum); + ::g->netbuffer->player = sw.player; + ::g->netbuffer->retransmitfrom = sw.retransmitfrom; + ::g->netbuffer->starttic = sw.starttic; + ::g->netbuffer->numtics = sw.numtics; + + for (c=0 ; c< ::g->netbuffer->numtics ; c++) + { + ::g->netbuffer->cmds[c].forwardmove = sw.cmds[c].forwardmove; + ::g->netbuffer->cmds[c].sidemove = sw.cmds[c].sidemove; + ::g->netbuffer->cmds[c].angleturn = ntohs(sw.cmds[c].angleturn); + //::g->netbuffer->cmds[c].consistancy = ntohs(sw.cmds[c].consistancy); + ::g->netbuffer->cmds[c].buttons = sw.cmds[c].buttons; + } +} + +static int I_TrySetupNetwork(void) +{ + // DHM - Moved to Session + return 1; +} + +// +// I_InitNetwork +// +void I_InitNetwork (void) +{ + qboolean trueval = true; + int i; + int p; + int a = 0; + // struct hostent* hostentry; // host information entry + + memset (&::g->doomcom, 0, sizeof(::g->doomcom) ); + + // set up for network + i = M_CheckParm ("-dup"); + if (i && i< ::g->myargc-1) + { + ::g->doomcom.ticdup = ::g->myargv[i+1][0]-'0'; + if (::g->doomcom.ticdup < 1) + ::g->doomcom.ticdup = 1; + if (::g->doomcom.ticdup > 9) + ::g->doomcom.ticdup = 9; + } + else + ::g->doomcom.ticdup = 1; + + if (M_CheckParm ("-extratic")) + ::g->doomcom.extratics = 1; + else + ::g->doomcom.extratics = 0; + + p = M_CheckParm ("-port"); + if (p && p<::g->myargc-1) + { + DOOMPORT = atoi (::g->myargv[p+1]); + I_Printf ("using alternate port %i\n",DOOMPORT); + } + + // parse network game options, + // -net <::g->consoleplayer> ... + i = M_CheckParm ("-net"); + if (!i || !I_TrySetupNetwork()) + { + // single player game + ::g->netgame = false; + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.numplayers = ::g->doomcom.numnodes = 1; + ::g->doomcom.deathmatch = false; + ::g->doomcom.consoleplayer = 0; + return; + } + + netsend = PacketSend; + netget = PacketGet; + + ::g->netgame = true; + + { + ++i; // skip the '-net' + ::g->doomcom.numnodes = 0; + ::g->doomcom.consoleplayer = atoi( ::g->myargv[i] ); + // skip the console number + ++i; + ::g->doomcom.numnodes = 0; + for (; i < ::g->myargc; ++i) + { + ::g->sendaddress[::g->doomcom.numnodes].sin_family = AF_INET; + ::g->sendaddress[::g->doomcom.numnodes].sin_port = htons(DOOMPORT); + ::g->sendaddress[::g->doomcom.numnodes].sin_addr.s_addr = inet_addr (::g->myargv[i]);//39, 17 + ::g->doomcom.numnodes++; + } + + ::g->doomcom.id = DOOMCOM_ID; + ::g->doomcom.numplayers = ::g->doomcom.numnodes; + } + + if ( globalNetworking ) { + // Setup sockets + ::g->insocket = UDPsocket (); + BindToLocalPort (::g->insocket,htons(DOOMPORT)); + ioctlsocket (::g->insocket, FIONBIO, (u_long*)&trueval); + + ::g->sendsocket = UDPsocket (); + + I_Printf( "[+] Setting up sockets for player %d\n", DoomLib::GetPlayer() ); + } +} + +// DHM - Nerve +void I_ShutdownNetwork( void ) { + if ( globalNetworking ) { + + int curPlayer = DoomLib::GetPlayer(); + + for (int player = 0; player < App->Game->Interface.GetNumPlayers(); ++player) + { + DoomLib::SetPlayer( player ); + + if ( ::g->insocket != INVALID_SOCKET ) { + I_Printf( "[-] Shut down insocket for player %d\n", DoomLib::GetPlayer() ); + shutdown( ::g->insocket, SD_BOTH ); + closesocket( ::g->insocket ); + } + if ( ::g->sendsocket != INVALID_SOCKET ) { + I_Printf( "[-] Shut down sendsocket for player %d\n", DoomLib::GetPlayer() ); + shutdown( ::g->sendsocket, SD_BOTH ); + closesocket( ::g->sendsocket ); + } + } + + DoomLib::SetPlayer(curPlayer); + + globalNetworking = false; + } +} + +void I_NetCmd (void) +{ + if (::g->doomcom.command == CMD_SEND) + { + netsend (); + } + else if (::g->doomcom.command == CMD_GET) + { + netget (); + } + else + I_Error ("Bad net cmd: %i\n",::g->doomcom.command); +} + diff --git a/doomclassic/doom/i_sound.h b/doomclassic/doom/i_sound.h new file mode 100644 index 00000000..85c85bca --- /dev/null +++ b/doomclassic/doom/i_sound.h @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __I_SOUND__ +#define __I_SOUND__ + +#include "doomdef.h" + +// UNIX hack, to be removed. +#ifdef SNDSERV +#include +extern FILE* sndserver; +extern char* sndserver_filename; +#endif + +#include "doomstat.h" +#include "sounds.h" + + + +// Init at program start... +void I_InitSound(); +void I_InitSoundHardware( int numOutputChannels_, int channelMask ); + +// ... update sound buffer and audio device at runtime... +void I_UpdateSound(void); +void I_SubmitSound(void); + +// ... shut down and relase at program termination. +void I_ShutdownSound(void); +void I_ShutdownSoundHardware(); + +// +// SFX I/O +// + +// Initialize channels? +void I_SetChannels(); + +// Get raw data lump index for sound descriptor. +int I_GetSfxLumpNum (sfxinfo_t* sfxinfo ); + + +// Starts a sound in a particular sound channel. +int I_StartSound( int id, mobj_t *origin, mobj_t *listener_origin, int vol, int pitch, int priority ); + + +// Stops a sound channel. +void I_StopSound(int handle, int player = -1); + +// Called by S_*() functions +// to see if a channel is still playing. +// Returns 0 if no longer playing, 1 if playing. +int I_SoundIsPlaying(int handle); + +// Updates the volume, separation, +// and pitch of a sound channel. +void I_UpdateSoundParams( int handle, int vol, int sep, int pitch ); + +void I_SetSfxVolume( int ); +// +// MUSIC I/O +// +void I_InitMusic(void); +void I_ShutdownMusic(void); +// Volume. +void I_SetMusicVolume(int volume); +// PAUSE game handling. +void I_PauseSong(int handle); +void I_ResumeSong(int handle); +// Registers a song handle to song data. +int I_RegisterSong(void *data, int length); +// Called by anything that wishes to start music. +// plays a song, and when the song is done, +// starts playing it again in an endless loop. +// Horrible thing to do, considering. +void I_PlaySong( const char *songname, int looping ); +// Stops a song over 3 seconds. +void I_StopSong(int handle); +// See above (register), then think backwards +void I_UnRegisterSong(int handle); +// Update Music (XMP), check for notifications +void I_UpdateMusic(void); + +int Mus2Midi(unsigned char* bytes, unsigned char* out, int* len); + +#endif + diff --git a/doomclassic/doom/i_sound_win32.cpp b/doomclassic/doom/i_sound_win32.cpp new file mode 100644 index 00000000..d4da8187 --- /dev/null +++ b/doomclassic/doom/i_sound_win32.cpp @@ -0,0 +1,1084 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +// +// DESCRIPTION: +// System interface for sound. +// +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +// Timer stuff. Experimental. +#include +#include +#include "z_zone.h" +#include "i_system.h" +#include "i_sound.h" +#include "m_argv.h" +#include "m_misc.h" +#include "w_wad.h" +#include "d_main.h" +#include "doomdef.h" +#include "../timidity/timidity.h" +#include "../timidity/controls.h" + +#include "sound/snd_local.h" + +#include +#include + +#pragma warning ( disable : 4244 ) + +#define MIDI_CHANNELS 2 +#if 1 +#define MIDI_RATE 22050 +#define MIDI_SAMPLETYPE XAUDIOSAMPLETYPE_8BITPCM +#define MIDI_FORMAT AUDIO_U8 +#define MIDI_FORMAT_BYTES 1 +#else +#define MIDI_RATE 48000 +#define MIDI_SAMPLETYPE XAUDIOSAMPLETYPE_16BITPCM +#define MIDI_FORMAT AUDIO_S16MSB +#define MIDI_FORMAT_BYTES 2 +#endif + +IXAudio2SourceVoice* pMusicSourceVoice; +MidiSong* doomMusic; +byte* musicBuffer; +int totalBufferSize; + +HANDLE hMusicThread; +bool waitingForMusic; +bool musicReady; + + +typedef struct tagActiveSound_t { + IXAudio2SourceVoice* m_pSourceVoice; // Source voice + X3DAUDIO_DSP_SETTINGS m_DSPSettings; + X3DAUDIO_EMITTER m_Emitter; + X3DAUDIO_CONE m_Cone; + int id; + int valid; + int start; + int player; + bool localSound; + mobj_t *originator; +} activeSound_t; + + +// cheap little struct to hold a sound +typedef struct { + int vol; + int player; + int pitch; + int priority; + mobj_t *originator; + mobj_t *listener; +} soundEvent_t; + +// array of all the possible sounds +// in split screen we only process the loudest sound of each type per frame +soundEvent_t soundEvents[128]; +extern int PLAYERCOUNT; + +// Real volumes +const float GLOBAL_VOLUME_MULTIPLIER = 0.5f; + +float x_SoundVolume = GLOBAL_VOLUME_MULTIPLIER; +float x_MusicVolume = GLOBAL_VOLUME_MULTIPLIER; + +// The actual lengths of all sound effects. +static int lengths[NUMSFX]; +activeSound_t activeSounds[NUM_SOUNDBUFFERS] = {0}; + +int S_initialized = 0; +bool Music_initialized = false; + +// XAUDIO +float g_EmitterAzimuths [] = { 0.f }; +static int numOutputChannels = 0; +static bool soundHardwareInitialized = false; + + +X3DAUDIO_HANDLE X3DAudioInstance; + +X3DAUDIO_LISTENER doom_Listener; + +//float localSoundVolumeEntries[] = { 0.f, 0.f, 0.9f, 0.5f, 0.f, 0.f }; +float localSoundVolumeEntries[] = { 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f }; + + +void I_InitSoundChannel( int channel, int numOutputChannels_ ); + +/* +====================== +getsfx +====================== +*/ +// This function loads the sound data from the WAD lump, +// for single sound. +// +void* getsfx ( char* sfxname, int* len ) +{ + unsigned char* sfx; + unsigned char* sfxmem; + int size; + char name[20]; + int sfxlump; + float scale = 1.0f; + + // Get the sound data from the WAD, allocate lump + // in zone memory. + sprintf(name, "ds%s", sfxname); + + // Scale down the plasma gun, it clips + if ( strcmp( sfxname, "plasma" ) == 0 ) { + scale = 0.75f; + } + if ( strcmp( sfxname, "itemup" ) == 0 ) { + scale = 1.333f; + } + + // If sound requested is not found in current WAD, use pistol as default + if ( W_CheckNumForName(name) == -1 ) + sfxlump = W_GetNumForName("dspistol"); + else + sfxlump = W_GetNumForName(name); + + // Sound lump headers are 8 bytes. + const int SOUND_LUMP_HEADER_SIZE_IN_BYTES = 8; + + size = W_LumpLength( sfxlump ) - SOUND_LUMP_HEADER_SIZE_IN_BYTES; + + sfx = (unsigned char*)W_CacheLumpNum( sfxlump, PU_CACHE_SHARED ); + const unsigned char * sfxSampleStart = sfx + SOUND_LUMP_HEADER_SIZE_IN_BYTES; + + // Allocate from zone memory. + //sfxmem = (float*)DoomLib::Z_Malloc( size*(sizeof(float)), PU_SOUND_SHARED, 0 ); + sfxmem = (unsigned char*)malloc( size * sizeof(unsigned char) ); + + // Now copy, and convert to Xbox360 native float samples, do initial volume ramp, and scale + for ( int i=0; iname); + return W_GetNumForName(namebuf); +} + +/* +====================== +I_StartSound2 +====================== +*/ +// Starting a sound means adding it +// to the current list of active sounds +// in the internal channels. +// As the SFX info struct contains +// e.g. a pointer to the raw data, +// it is ignored. +// As our sound handling does not handle +// priority, it is ignored. +// Pitching (that is, increased speed of playback) is set +// +int I_StartSound2 ( int id, int player, mobj_t *origin, mobj_t *listener_origin, int pitch, int priority ) { + if ( !soundHardwareInitialized ) { + return id; + } + + int i; + XAUDIO2_VOICE_STATE state; + activeSound_t* sound = 0; + int oldest = 0, oldestnum = -1; + + // these id's should not overlap + if ( id == sfx_sawup || id == sfx_sawidl || id == sfx_sawful || id == sfx_sawhit || id == sfx_stnmov ) { + // Loop all channels, check. + for (i=0 ; i < NUM_SOUNDBUFFERS ; i++) + { + sound = &activeSounds[i]; + + if (sound->valid && ( sound->id == id && sound->player == player ) ) { + I_StopSound( sound->id, player ); + break; + } + } + } + + // find a valid channel, or one that has finished playing + for (i = 0; i < NUM_SOUNDBUFFERS; ++i) { + sound = &activeSounds[i]; + + if (!sound->valid) + break; + + if (!oldest || oldest > sound->start) { + oldestnum = i; + oldest = sound->start; + } + + sound->m_pSourceVoice->GetState( &state ); + if ( state.BuffersQueued == 0 ) { + break; + } + } + + // none found, so use the oldest one + if (i == NUM_SOUNDBUFFERS) + { + i = oldestnum; + sound = &activeSounds[i]; + } + + // stop the sound with a FlushPackets + sound->m_pSourceVoice->Stop(); + sound->m_pSourceVoice->FlushSourceBuffers(); + + // Set up packet + XAUDIO2_BUFFER Packet = { 0 }; + Packet.Flags = XAUDIO2_END_OF_STREAM; + Packet.AudioBytes = lengths[id]; + Packet.pAudioData = (BYTE*)S_sfx[id].data; + Packet.PlayBegin = 0; + Packet.PlayLength = 0; + Packet.LoopBegin = XAUDIO2_NO_LOOP_REGION; + Packet.LoopLength = 0; + Packet.LoopCount = 0; + Packet.pContext = NULL; + + + // Set voice volumes + sound->m_pSourceVoice->SetVolume( x_SoundVolume ); + + // Set voice pitch + sound->m_pSourceVoice->SetFrequencyRatio( 1 + ((float)pitch-128.f)/95.f ); + + // Set initial spatialization + if ( origin && origin != listener_origin ) { + // Update Emitter Position + sound->m_Emitter.Position.x = (float)(origin->x >> FRACBITS); + sound->m_Emitter.Position.y = 0.f; + sound->m_Emitter.Position.z = (float)(origin->y >> FRACBITS); + + // Calculate 3D positioned speaker volumes + DWORD dwCalculateFlags = X3DAUDIO_CALCULATE_MATRIX; + X3DAudioCalculate( X3DAudioInstance, &doom_Listener, &sound->m_Emitter, dwCalculateFlags, &sound->m_DSPSettings ); + + // Pan the voice according to X3DAudio calculation + sound->m_pSourceVoice->SetOutputMatrix( NULL, 1, numOutputChannels, sound->m_DSPSettings.pMatrixCoefficients ); + + sound->localSound = false; + } else { + // Local(or Global) sound, fixed speaker volumes + sound->m_pSourceVoice->SetOutputMatrix( NULL, 1, numOutputChannels, localSoundVolumeEntries ); + + sound->localSound = true; + } + + // Submit packet + HRESULT hr; + if( FAILED( hr = sound->m_pSourceVoice->SubmitSourceBuffer( &Packet ) ) ) { + int fail = 1; + } + + // Play the source voice + if( FAILED( hr = sound->m_pSourceVoice->Start( 0 ) ) ) { + int fail = 1; + } + + // set id, and start time + sound->id = id; + sound->start = ::g->gametic; + sound->valid = 1; + sound->player = player; + sound->originator = origin; + + return id; +} + +/* +====================== +I_ProcessSoundEvents +====================== +*/ +void I_ProcessSoundEvents( void ) { + for( int i = 0; i < 128; i++ ) { + if( soundEvents[i].pitch ) { + I_StartSound2( i, soundEvents[i].player, soundEvents[i].originator, soundEvents[i].listener, soundEvents[i].pitch, soundEvents[i].priority ); + } + } + memset( soundEvents, 0, sizeof( soundEvents ) ); +} + +/* +====================== +I_StartSound +====================== +*/ +int I_StartSound ( int id, mobj_t *origin, mobj_t *listener_origin, int vol, int pitch, int priority ) { + // only allow player 0s sounds in intermission and finale screens + if( ::g->gamestate != GS_LEVEL && DoomLib::GetPlayer() != 0 ) { + return 0; + } + + // if we're only one player or we're trying to play the chainsaw sound, do it normal + // otherwise only allow one sound of each type per frame + if( PLAYERCOUNT == 1 || id == sfx_sawup || id == sfx_sawidl || id == sfx_sawful || id == sfx_sawhit ) { + return I_StartSound2( id, ::g->consoleplayer, origin, listener_origin, pitch, priority ); + } + else { + if( soundEvents[ id ].vol < vol ) { + soundEvents[ id ].player = DoomLib::GetPlayer(); + soundEvents[ id ].pitch = pitch; + soundEvents[ id ].priority = priority; + soundEvents[ id ].vol = vol; + soundEvents[ id ].originator = origin; + soundEvents[ id ].listener = listener_origin; + } + return id; + } +} + +/* +====================== +I_StopSound +====================== +*/ +void I_StopSound (int handle, int player) +{ + // You need the handle returned by StartSound. + // Would be looping all channels, + // tracking down the handle, + // an setting the channel to zero. + + int i; + activeSound_t* sound = 0; + + for (i = 0; i < NUM_SOUNDBUFFERS; ++i) + { + sound = &activeSounds[i]; + if (!sound->valid || sound->id != handle || (player >= 0 && sound->player != player) ) + continue; + break; + } + + if (i == NUM_SOUNDBUFFERS) + return; + + // stop the sound + if ( sound->m_pSourceVoice != NULL ) { + sound->m_pSourceVoice->Stop( 0 ); + } + + sound->valid = 0; + sound->player = -1; +} + +/* +====================== +I_SoundIsPlaying +====================== +*/ +int I_SoundIsPlaying(int handle) { + if ( !soundHardwareInitialized ) { + return 0; + } + + int i; + XAUDIO2_VOICE_STATE state; + activeSound_t* sound; + + for (i = 0; i < NUM_SOUNDBUFFERS; ++i) + { + sound = &activeSounds[i]; + if (!sound->valid || sound->id != handle) + continue; + + sound->m_pSourceVoice->GetState( &state ); + if ( state.BuffersQueued > 0 ) { + return 1; + } + } + + return 0; +} + +/* +====================== +I_UpdateSound +====================== +*/ +// Update Listener Position and go through all the +// channels and update speaker volumes for 3D sound. +void I_UpdateSound( void ) { + if ( !soundHardwareInitialized ) { + return; + } + + int i; + XAUDIO2_VOICE_STATE state; + activeSound_t* sound; + + for ( i=0; i < NUM_SOUNDBUFFERS; i++ ) { + sound = &activeSounds[i]; + + if ( !sound->valid || sound->localSound ) { + continue; + } + + sound->m_pSourceVoice->GetState( &state ); + + if ( state.BuffersQueued > 0 ) { + mobj_t *playerObj = ::g->players[ sound->player ].mo; + + // Update Listener Orientation and Position + angle_t pAngle = playerObj->angle; + fixed_t fx, fz; + + pAngle >>= ANGLETOFINESHIFT; + + fx = finecosine[pAngle]; + fz = finesine[pAngle]; + + doom_Listener.OrientFront.x = (float)(fx) / 65535.f; + doom_Listener.OrientFront.y = 0.f; + doom_Listener.OrientFront.z = (float)(fz) / 65535.f; + + doom_Listener.Position.x = (float)(playerObj->x >> FRACBITS); + doom_Listener.Position.y = 0.f; + doom_Listener.Position.z = (float)(playerObj->y >> FRACBITS); + + // Update Emitter Position + sound->m_Emitter.Position.x = (float)(sound->originator->x >> FRACBITS); + sound->m_Emitter.Position.y = 0.f; + sound->m_Emitter.Position.z = (float)(sound->originator->y >> FRACBITS); + + // Calculate 3D positioned speaker volumes + DWORD dwCalculateFlags = X3DAUDIO_CALCULATE_MATRIX; + X3DAudioCalculate( X3DAudioInstance, &doom_Listener, &sound->m_Emitter, dwCalculateFlags, &sound->m_DSPSettings ); + + // Pan the voice according to X3DAudio calculation + sound->m_pSourceVoice->SetOutputMatrix( NULL, 1, numOutputChannels, sound->m_DSPSettings.pMatrixCoefficients ); + } + } +} + +/* +====================== +I_UpdateSoundParams +====================== +*/ +void I_UpdateSoundParams( int handle, int vol, int sep, int pitch) { +} + +/* +====================== +I_ShutdownSound +====================== +*/ +void I_ShutdownSound(void) { + int done = 0; + int i; + + if ( S_initialized ) { + // Stop all sounds, but don't destroy the XAudio2 buffers. + for ( i = 0; i < NUM_SOUNDBUFFERS; ++i ) { + activeSound_t * sound = &activeSounds[i]; + + if ( sound == NULL ) { + continue; + } + + I_StopSound( sound->id, 0 ); + + if ( sound->m_pSourceVoice ) { + sound->m_pSourceVoice->FlushSourceBuffers(); + } + } + + for (i=1 ; im_pSourceVoice ) { + sound->m_pSourceVoice->Stop(); + sound->m_pSourceVoice->FlushSourceBuffers(); + sound->m_pSourceVoice->DestroyVoice(); + sound->m_pSourceVoice = NULL; + } + + if ( sound->m_DSPSettings.pMatrixCoefficients ) { + delete [] sound->m_DSPSettings.pMatrixCoefficients; + sound->m_DSPSettings.pMatrixCoefficients = NULL; + } + } +} + +/* +====================== +I_InitSoundChannel +====================== +*/ +void I_InitSoundChannel( int channel, int numOutputChannels_ ) { + activeSound_t *soundchannel = &activeSounds[ channel ]; + + X3DAUDIO_VECTOR ZeroVector = { 0.0f, 0.0f, 0.0f }; + + // Set up emitter parameters + soundchannel->m_Emitter.OrientFront.x = 0.0f; + soundchannel->m_Emitter.OrientFront.y = 0.0f; + soundchannel->m_Emitter.OrientFront.z = 1.0f; + soundchannel->m_Emitter.OrientTop.x = 0.0f; + soundchannel->m_Emitter.OrientTop.y = 1.0f; + soundchannel->m_Emitter.OrientTop.z = 0.0f; + soundchannel->m_Emitter.Position = ZeroVector; + soundchannel->m_Emitter.Velocity = ZeroVector; + soundchannel->m_Emitter.pCone = &(soundchannel->m_Cone); + soundchannel->m_Emitter.pCone->InnerAngle = 0.0f; // Setting the inner cone angles to X3DAUDIO_2PI and + // outer cone other than 0 causes + // the emitter to act like a point emitter using the + // INNER cone settings only. + soundchannel->m_Emitter.pCone->OuterAngle = 0.0f; // Setting the outer cone angles to zero causes + // the emitter to act like a point emitter using the + // OUTER cone settings only. + soundchannel->m_Emitter.pCone->InnerVolume = 0.0f; + soundchannel->m_Emitter.pCone->OuterVolume = 1.0f; + soundchannel->m_Emitter.pCone->InnerLPF = 0.0f; + soundchannel->m_Emitter.pCone->OuterLPF = 1.0f; + soundchannel->m_Emitter.pCone->InnerReverb = 0.0f; + soundchannel->m_Emitter.pCone->OuterReverb = 1.0f; + + soundchannel->m_Emitter.ChannelCount = 1; + soundchannel->m_Emitter.ChannelRadius = 0.0f; + soundchannel->m_Emitter.pVolumeCurve = NULL; + soundchannel->m_Emitter.pLFECurve = NULL; + soundchannel->m_Emitter.pLPFDirectCurve = NULL; + soundchannel->m_Emitter.pLPFReverbCurve = NULL; + soundchannel->m_Emitter.pReverbCurve = NULL; + soundchannel->m_Emitter.CurveDistanceScaler = 1200.0f; + soundchannel->m_Emitter.DopplerScaler = 1.0f; + soundchannel->m_Emitter.pChannelAzimuths = g_EmitterAzimuths; + + soundchannel->m_DSPSettings.SrcChannelCount = 1; + soundchannel->m_DSPSettings.DstChannelCount = numOutputChannels_; + soundchannel->m_DSPSettings.pMatrixCoefficients = new FLOAT[ numOutputChannels_ ]; + + // Create Source voice + WAVEFORMATEX voiceFormat = {0}; + voiceFormat.wFormatTag = WAVE_FORMAT_PCM; + voiceFormat.nChannels = 1; + voiceFormat.nSamplesPerSec = 11025; + voiceFormat.nAvgBytesPerSec = 11025; + voiceFormat.nBlockAlign = 1; + voiceFormat.wBitsPerSample = 8; + voiceFormat.cbSize = 0; + + soundSystemLocal.hardware.GetIXAudio2()->CreateSourceVoice( &soundchannel->m_pSourceVoice, (WAVEFORMATEX *)&voiceFormat ); +} + +/* +====================== +I_InitSound +====================== +*/ +void I_InitSound() { + + if (S_initialized == 0) { + int i; + + X3DAUDIO_VECTOR ZeroVector = { 0.0f, 0.0f, 0.0f }; + + // Set up listener parameters + doom_Listener.OrientFront.x = 0.0f; + doom_Listener.OrientFront.y = 0.0f; + doom_Listener.OrientFront.z = 1.0f; + doom_Listener.OrientTop.x = 0.0f; + doom_Listener.OrientTop.y = 1.0f; + doom_Listener.OrientTop.z = 0.0f; + doom_Listener.Position = ZeroVector; + doom_Listener.Velocity = ZeroVector; + + for (i=1 ; idata; + lengths[i] = lengths[(S_sfx[i].link - S_sfx)/sizeof(sfxinfo_t)]; + } + } + + S_initialized = 1; + } +} + +/* +====================== +I_SubmitSound +====================== +*/ +void I_SubmitSound(void) +{ + // Only do this for player 0, it will still handle positioning + // for other players, but it can't be outside the game + // frame like the soundEvents are. + if ( DoomLib::GetPlayer() == 0 ) { + // Do 3D positioning of sounds + I_UpdateSound(); + + // Check for XMP notifications + I_UpdateMusic(); + } +} + + +// ========================================================= +// ========================================================= +// Background Music +// ========================================================= +// ========================================================= + +/* +====================== +I_SetMusicVolume +====================== +*/ +void I_SetMusicVolume(int volume) +{ + x_MusicVolume = (float)volume / 15.f; +} + +/* +====================== +I_InitMusic +====================== +*/ +void I_InitMusic(void) +{ + if ( !Music_initialized ) { + // Initialize Timidity + Timidity_Init( MIDI_RATE, MIDI_FORMAT, MIDI_CHANNELS, MIDI_RATE, "classicmusic/gravis.cfg" ); + + hMusicThread = NULL; + musicBuffer = NULL; + totalBufferSize = 0; + waitingForMusic = false; + musicReady = false; + + // Create Source voice + WAVEFORMATEX voiceFormat = {0}; + voiceFormat.wFormatTag = WAVE_FORMAT_PCM; + voiceFormat.nChannels = 2; + voiceFormat.nSamplesPerSec = MIDI_RATE; + voiceFormat.nAvgBytesPerSec = MIDI_RATE * MIDI_FORMAT_BYTES * 2; + voiceFormat.nBlockAlign = MIDI_FORMAT_BYTES * 2; + voiceFormat.wBitsPerSample = MIDI_FORMAT_BYTES * 8; + voiceFormat.cbSize = 0; + + soundSystemLocal.hardware.GetIXAudio2()->CreateSourceVoice( &pMusicSourceVoice, (WAVEFORMATEX *)&voiceFormat, XAUDIO2_VOICE_MUSIC ); + + Music_initialized = true; + } +} + +/* +====================== +I_ShutdownMusic +====================== +*/ +void I_ShutdownMusic(void) +{ + I_StopSong( 0 ); + + if ( Music_initialized ) { + if ( pMusicSourceVoice ) { + pMusicSourceVoice->Stop(); + pMusicSourceVoice->FlushSourceBuffers(); + pMusicSourceVoice->DestroyVoice(); + pMusicSourceVoice = NULL; + } + + if ( hMusicThread ) { + DWORD rc; + + do { + GetExitCodeThread( hMusicThread, &rc ); + if ( rc == STILL_ACTIVE ) { + Sleep( 1 ); + } + } while( rc == STILL_ACTIVE ); + + CloseHandle( hMusicThread ); + } + if ( musicBuffer ) { + free( musicBuffer ); + } + + Timidity_Shutdown(); + } + + pMusicSourceVoice = NULL; + hMusicThread = NULL; + musicBuffer = NULL; + + totalBufferSize = 0; + waitingForMusic = false; + musicReady = false; + + Music_initialized = false; +} + +int Mus2Midi(unsigned char* bytes, unsigned char* out, int* len); + +namespace { + const int MaxMidiConversionSize = 1024 * 1024; + unsigned char midiConversionBuffer[MaxMidiConversionSize]; +} + +/* +====================== +I_LoadSong +====================== +*/ +DWORD WINAPI I_LoadSong( LPVOID songname ) { + idStr lumpName = "d_"; + lumpName += static_cast< const char * >( songname ); + + unsigned char * musFile = static_cast< unsigned char * >( W_CacheLumpName( lumpName.c_str(), PU_STATIC_SHARED ) ); + + int length = 0; + Mus2Midi( musFile, midiConversionBuffer, &length ); + + doomMusic = Timidity_LoadSongMem( midiConversionBuffer, length ); + + if ( doomMusic ) { + musicBuffer = (byte *)malloc( MIDI_CHANNELS * MIDI_FORMAT_BYTES * doomMusic->samples ); + totalBufferSize = doomMusic->samples * MIDI_CHANNELS * MIDI_FORMAT_BYTES; + + Timidity_Start( doomMusic ); + + int rc = RC_NO_RETURN_VALUE; + int num_bytes = 0; + int offset = 0; + + do { + rc = Timidity_PlaySome( musicBuffer + offset, MIDI_RATE, &num_bytes ); + offset += num_bytes; + } while ( rc != RC_TUNE_END ); + + Timidity_Stop(); + Timidity_FreeSong( doomMusic ); + } + + musicReady = true; + + return ERROR_SUCCESS; +} + +/* +====================== +I_PlaySong +====================== +*/ +void I_PlaySong( const char *songname, int looping) +{ + if ( !Music_initialized ) { + return; + } + + if ( pMusicSourceVoice != NULL ) { + // Stop the voice and flush packets before freeing the musicBuffer + pMusicSourceVoice->Stop(); + pMusicSourceVoice->FlushSourceBuffers(); + } + + // Make sure voice is stopped before we free the buffer + bool isStopped = false; + int d = 0; + while ( !isStopped ) { + XAUDIO2_VOICE_STATE test; + + if ( pMusicSourceVoice != NULL ) { + pMusicSourceVoice->GetState( &test ); + } + + if ( test.pCurrentBufferContext == NULL && test.BuffersQueued == 0 ) { + isStopped = true; + } + //I_Printf( "waiting to stop (%d)\n", d++ ); + } + + // Clear old state + if ( musicBuffer != NULL ) { + free( musicBuffer ); + musicBuffer = NULL; + } + + musicReady = false; + I_LoadSong( (LPVOID)songname ); + waitingForMusic = true; + + if ( DoomLib::GetPlayer() >= 0 ) { + ::g->mus_looping = looping; + } +} + +/* +====================== +I_UpdateMusic +====================== +*/ +void I_UpdateMusic( void ) { + if ( !Music_initialized ) { + return; + } + + if ( waitingForMusic ) { + + if ( musicReady && pMusicSourceVoice != NULL ) { + + if ( musicBuffer ) { + // Set up packet + XAUDIO2_BUFFER Packet = { 0 }; + Packet.Flags = XAUDIO2_END_OF_STREAM; + Packet.AudioBytes = totalBufferSize; + Packet.pAudioData = (BYTE*)musicBuffer; + Packet.PlayBegin = 0; + Packet.PlayLength = 0; + Packet.LoopBegin = 0; + Packet.LoopLength = 0; + Packet.LoopCount = ::g->mus_looping ? XAUDIO2_LOOP_INFINITE : 0; + Packet.pContext = NULL; + + // Submit packet + HRESULT hr; + if( FAILED( hr = pMusicSourceVoice->SubmitSourceBuffer( &Packet ) ) ) { + int fail = 1; + } + + // Play the source voice + if( FAILED( hr = pMusicSourceVoice->Start( 0 ) ) ) { + int fail = 1; + } + } + + waitingForMusic = false; + } + } + + if ( pMusicSourceVoice != NULL ) { + // Set the volume + pMusicSourceVoice->SetVolume( x_MusicVolume * GLOBAL_VOLUME_MULTIPLIER ); + } +} + +/* +====================== +I_PauseSong +====================== +*/ +void I_PauseSong (int handle) +{ + if ( !Music_initialized ) { + return; + } + + if ( pMusicSourceVoice != NULL ) { + // Stop the music source voice + pMusicSourceVoice->Stop( 0 ); + } +} + +/* +====================== +I_ResumeSong +====================== +*/ +void I_ResumeSong (int handle) +{ + if ( !Music_initialized ) { + return; + } + + // Stop the music source voice + if ( pMusicSourceVoice != NULL ) { + pMusicSourceVoice->Start( 0 ); + } +} + +/* +====================== +I_StopSong +====================== +*/ +void I_StopSong(int handle) +{ + if ( !Music_initialized ) { + return; + } + + // Stop the music source voice + if ( pMusicSourceVoice != NULL ) { + pMusicSourceVoice->Stop( 0 ); + } +} + +/* +====================== +I_UnRegisterSong +====================== +*/ +void I_UnRegisterSong(int handle) +{ + // does nothing +} + +/* +====================== +I_RegisterSong +====================== +*/ +int I_RegisterSong(void* data, int length) +{ + // does nothing + return 0; +} + diff --git a/doomclassic/doom/i_system.cpp b/doomclassic/doom/i_system.cpp new file mode 100644 index 00000000..2a3bb735 --- /dev/null +++ b/doomclassic/doom/i_system.cpp @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include +#include + +#include + +#include "doomdef.h" +#include "m_misc.h" +#include "i_video.h" +#include "i_sound.h" + +#include "d_net.h" +#include "g_game.h" + +#ifdef __GNUG__ +#pragma implementation "i_system.h" +#endif +#include "i_system.h" + +#include "Main.h" + +#if 1 + + + +ticcmd_t* I_BaseTiccmd(void) +{ + return &::g->emptycmd; +} + + +int I_GetHeapSize (void) +{ + return ::g->mb_used*1024*1024; +} + + +// +// I_GetTime +// returns time in 1/70th second tics +// +int I_GetTime (void) +{ + return ::g->current_time; +} + +void I_SetTime( int time_in ) +{ + ::g->current_time = time_in; +} + + + +// +// I_Init +// +void I_Init (void) +{ + I_InitSound(); + // I_InitGraphics(); +} + +// +// I_Quit +// +void I_Quit (void) +{ + D_QuitNetGame (); + I_ShutdownSound(); + I_ShutdownMusic(); + M_SaveDefaults (); + I_ShutdownGraphics(); +// exit(0); + +// Exceptions disabled by default on PS3 +// throw; +} + +void I_WaitVBL(int count) +{ + // PS3 fixme + //Sleep(0); +} + +void I_BeginRead(void) +{ +} + +void I_EndRead(void) +{ +} + +// +// I_Error +// +extern bool debugOutput; +void I_Printf(char* msg, ...) +{ + char pmsg[1024]; + va_list argptr; + + // Message first. + if( debugOutput ) { + va_start (argptr,msg); + vsprintf (pmsg, msg, argptr); + + safeOutputDebug(pmsg); + + va_end (argptr); + } +} + + +void I_PrintfE(char* msg, ...) +{ + char pmsg[1024]; + va_list argptr; + + // Message first. + if( debugOutput ) { + va_start (argptr,msg); + vsprintf (pmsg, msg, argptr); + + safeOutputDebug("ERROR: "); + safeOutputDebug(pmsg); + + va_end (argptr); + } +} + +void I_Error(char *error, ...) +{ + const int ERROR_MSG_SIZE = 1024; + char error_msg[ERROR_MSG_SIZE]; + va_list argptr; + + // Message first. + if( debugOutput ) { + va_start (argptr,error); + idStr::vsnPrintf (error_msg, ERROR_MSG_SIZE, error, argptr); + + safeOutputDebug("Error: "); + safeOutputDebug(error_msg); + safeOutputDebug("\n"); + + va_end (argptr); + } + + // CRASH DUMP - enable this to get extra info on error from crash dumps + //*(int*)0x0 = 21; + DoomLib::Interface.QuitCurrentGame(); + idLib::Printf( "DOOM Classic error: %s", error_msg ); + common->SwitchToGame( DOOM3_BFG ); +} + +#endif + diff --git a/doomclassic/doom/i_system.h b/doomclassic/doom/i_system.h new file mode 100644 index 00000000..24a437fd --- /dev/null +++ b/doomclassic/doom/i_system.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __I_SYSTEM__ +#define __I_SYSTEM__ + +#include "d_ticcmd.h" +#include "d_event.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + +// Called by DoomMain. +void I_Init (void); + +// Called by D_DoomLoop, +// returns current time in tics. +int I_GetTime (void); + + +// +// Called by D_DoomLoop, +// called before processing any tics in a frame +// (just after displaying a frame). +// Time consuming syncronous operations +// are performed here (joystick reading). +// Can call D_PostEvent. +// +void I_StartFrame (void); + + +// +// Called by D_DoomLoop, +// called before processing each tic in a frame. +// Quick syncronous operations are performed here. +// Can call D_PostEvent. +#include "doomlib.h" + +// Asynchronous interrupt functions should maintain private queues +// that are read by the synchronous functions +// to be converted into events. + +// Either returns a null ticcmd, +// or calls a loadable driver to build it. +// This ticcmd will then be modified by the gameloop +// for normal input. +ticcmd_t* I_BaseTiccmd (void); + + +// Called by M_Responder when quit is selected. +// Clean exit, displays sell blurb. +void I_Quit (void); + + +void I_Error (char *error, ...); +void I_Printf(char *error, ...); +void I_PrintfE(char *error, ...); + + +#endif + diff --git a/doomclassic/doom/i_video.h b/doomclassic/doom/i_video.h new file mode 100644 index 00000000..302bb412 --- /dev/null +++ b/doomclassic/doom/i_video.h @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __I_VIDEO__ +#define __I_VIDEO__ + + +#include "doomtype.h" +#include "d_event.h" + +#ifdef __GNUG__ +#pragma interface +#endif + +// Called by D_DoomMain, +// determines the hardware configuration +// and sets up the video mode +void I_InitGraphics (void); + + +void I_ShutdownGraphics(void); + +// Takes full 8 bit values. +void I_SetPalette (byte* palette); + +void I_UpdateNoBlit (void); +void I_FinishUpdate (void); + +// Wait for vertical retrace or pause a bit. +void I_WaitVBL(int count); + +void I_ReadScreen (byte* scr); + +void I_BeginRead (void); +void I_EndRead (void); + +void I_InitInput (void); + +void I_ShutdownInput( void ) ; +void I_InputFrame( void ); + +void I_UpdateControllerState(void); +struct controller_t; +int I_PollMouseInputEvents( controller_t * ) ; +int I_ReturnMouseInputEvent( const int n, event_t* e); +int I_PollJoystickInputEvents( controller_t * ) ; +int I_ReturnJoystickInputEvent( const int n, event_t* e); +void I_EndJoystickInputEvents( void ); + +#endif + diff --git a/doomclassic/doom/i_video_ps3.cpp b/doomclassic/doom/i_video_ps3.cpp new file mode 100644 index 00000000..3da7e17f --- /dev/null +++ b/doomclassic/doom/i_video_ps3.cpp @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include +#include +#include + +#include "i_video.h" +#include "i_system.h" + +#include "doomstat.h" +#include "v_video.h" +#include "m_argv.h" +#include "d_main.h" + +#include "doomdef.h" + +//#include // SMF +//#include // SMF + + + + + +void I_ShutdownGraphics(void) +{ + // VVTODO: Shutdown Graphics +} + + + +// +// I_StartFrame +// +void I_StartFrame (void) +{ + // er? +} + +static void I_CombineMouseEvent(const event_t* in, event_t* out) +{ + if (fabs((float)in->data1) > fabs((float)out->data1)) + out->data1 = in->data1; + if (fabs((float)in->data2) > fabs((float)out->data2)) + out->data2 = in->data2; + if (fabs((float)in->data3) > fabs((float)out->data3)) + out->data3 = in->data3; +} + +void I_GetEvents( controller_t *controller ) +{ + event_t e_mouse, e_joystick; + int numEvents; + + e_mouse.type = ev_mouse; + e_mouse.data1 = e_mouse.data2 = e_mouse.data3 = 0; + e_joystick.type = ev_joystick; + e_joystick.data1 = e_joystick.data2 = e_joystick.data3 = 0; + + numEvents = I_PollMouseInputEvents( controller ); + if (numEvents) + { + int i; + event_t e; + + // right thumb stick + for (i = 0; i < numEvents; ++i) + { + I_ReturnMouseInputEvent(i, &e); + if (e.type == ev_mouse) + I_CombineMouseEvent(&e, &e_mouse); + else if (e.type == ev_joystick) + I_CombineMouseEvent(&e, &e_joystick); + } + } + + numEvents = I_PollJoystickInputEvents( controller ); + if (numEvents) + { + int i; + event_t e; + for (i = 0; i < numEvents; ++i) + { + I_ReturnJoystickInputEvent(i, &e); + if (e.type == ev_keydown || e.type == ev_keyup) { + D_PostEvent(&e); + } else if (e.type == ev_joystick) { + I_CombineMouseEvent(&e, &e_joystick); + } else if (e.type == ev_mouse) { + I_CombineMouseEvent(&e, &e_mouse); + } + } + } + + D_PostEvent(&e_mouse); + D_PostEvent(&e_joystick); +} + +// +// I_UpdateNoBlit +// +void I_UpdateNoBlit (void) +{ + // what is this? +} + +// +// I_FinishUpdate +// +void I_FinishUpdate (void) +{ +// DHM - These buffers are not used +} + + +// +// I_ReadScreen +// +void I_ReadScreen (byte* scr) +{ + memcpy(scr, ::g->screens[0], SCREENWIDTH*SCREENHEIGHT); +} + +inline unsigned int I_PackColor( unsigned int a, unsigned int r, unsigned int g, unsigned int b ) { + unsigned int color = 0; + + color |= (r & 255) << 24; + color |= (g & 255) << 16; + color |= (b & 255) << 8; + color |= (a & 255); + + return color; +} + +// +// I_SetPalette +// +void I_SetPalette (byte* palette) +{ + + int i; + + // set the X colormap entries + for (i=0 ; i<256 ; i++) + { + int r,b,g; + r = gammatable[::g->usegamma][*palette++]; + g = gammatable[::g->usegamma][*palette++]; + b = gammatable[::g->usegamma][*palette++]; + ::g->XColorMap[i] = I_PackColor(0xff, r, g, b); + } + +} + +void I_InitGraphics() +{ +} + diff --git a/doomclassic/doom/i_video_xbox.cpp b/doomclassic/doom/i_video_xbox.cpp new file mode 100644 index 00000000..45772f83 --- /dev/null +++ b/doomclassic/doom/i_video_xbox.cpp @@ -0,0 +1,174 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include +#include +#include +#include + +#include "i_video.h" +#include "i_system.h" + +#include "doomstat.h" +#include "v_video.h" +#include "m_argv.h" +#include "d_main.h" + +#include "doomdef.h" + +//#include // SMF +//#include // SMF + + + + + +void I_ShutdownGraphics(void) +{ + // VVTODO: Shutdown Graphics +} + + + +// +// I_StartFrame +// +void I_StartFrame (void) +{ + // er? +} + +static void I_CombineMouseEvent(const event_t* in, event_t* out) +{ + if (fabs((float)in->data1) > fabs((float)out->data1)) + out->data1 = in->data1; + if (fabs((float)in->data2) > fabs((float)out->data2)) + out->data2 = in->data2; + if (fabs((float)in->data3) > fabs((float)out->data3)) + out->data3 = in->data3; +} + +void I_GetEvents( controller_t *controller ) +{ + event_t e_mouse, e_joystick; + int numEvents; + + e_mouse.type = ev_mouse; + e_mouse.data1 = e_mouse.data2 = e_mouse.data3 = 0; + e_joystick.type = ev_joystick; + e_joystick.data1 = e_joystick.data2 = e_joystick.data3 = 0; + + numEvents = I_PollMouseInputEvents( controller ); + if (numEvents) + { + int i; + event_t e; + + // right thumb stick + for (i = 0; i < numEvents; ++i) + { + I_ReturnMouseInputEvent(i, &e); + if (e.type == ev_mouse) + I_CombineMouseEvent(&e, &e_mouse); + else if (e.type == ev_joystick) + I_CombineMouseEvent(&e, &e_joystick); + } + } + + numEvents = I_PollJoystickInputEvents( controller ); + if (numEvents) + { + int i; + event_t e; + for (i = 0; i < numEvents; ++i) + { + I_ReturnJoystickInputEvent(i, &e); + if (e.type == ev_keydown || e.type == ev_keyup) { + D_PostEvent(&e); + } else if (e.type == ev_joystick) { + I_CombineMouseEvent(&e, &e_joystick); + } else if (e.type == ev_mouse) { + I_CombineMouseEvent(&e, &e_mouse); + } + } + } + + D_PostEvent(&e_mouse); + D_PostEvent(&e_joystick); +} + +// +// I_UpdateNoBlit +// +void I_UpdateNoBlit (void) +{ + // what is this? +} + +// +// I_FinishUpdate +// +void I_FinishUpdate (void) +{ +// DHM - These buffers are not used +} + + +// +// I_ReadScreen +// +void I_ReadScreen (byte* scr) +{ + XMemCpy(scr, ::g->screens[0], SCREENWIDTH*SCREENHEIGHT); +} + +// +// I_SetPalette +// +void I_SetPalette (byte* palette) +{ + int i; + + // set the X colormap entries + for (i=0 ; i<256 ; i++) + { + int r,b,g; + r = gammatable[::g->usegamma][*palette++]; + g = gammatable[::g->usegamma][*palette++]; + b = gammatable[::g->usegamma][*palette++]; + ::g->XColorMap[i] = D3DCOLOR_ARGB(0xff, r, g, b); + } +} + +void I_InitGraphics() +{ +} + diff --git a/doomclassic/doom/info.cpp b/doomclassic/doom/info.cpp new file mode 100644 index 00000000..3023c66b --- /dev/null +++ b/doomclassic/doom/info.cpp @@ -0,0 +1,4675 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +// Data. +#include "sounds.h" +#include "m_fixed.h" + +#ifdef __GNUG__ +#pragma implementation "info.h" +#endif +#include "info.h" + +#include "p_mobj.h" + +const char * const sprnames[NUMSPRITES] = { + "TROO","SHTG","PUNG","PISG","PISF","SHTF","SHT2","CHGG","CHGF","MISG", + "MISF","SAWG","PLSG","PLSF","BFGG","BFGF","BLUD","PUFF","BAL1","BAL2", + "PLSS","PLSE","MISL","BFS1","BFE1","BFE2","TFOG","IFOG","PLAY","POSS", + "SPOS","VILE","FIRE","FATB","FBXP","SKEL","MANF","FATT","CPOS","SARG", + "HEAD","BAL7","BOSS","BOS2","SKUL","SPID","BSPI","APLS","APBX","CYBR", + "PAIN","SSWV","KEEN","BBRN","BOSF","ARM1","ARM2","BAR1","BEXP","FCAN", + "BON1","BON2","BKEY","RKEY","YKEY","BSKU","RSKU","YSKU","STIM","MEDI", + "SOUL","PINV","PSTR","PINS","MEGA","SUIT","PMAP","PVIS","CLIP","AMMO", + "ROCK","BROK","CELL","CELP","SHEL","SBOX","BPAK","BFUG","MGUN","CSAW", + "LAUN","PLAS","SHOT","SGN2","COLU","SMT2","GOR1","POL2","POL5","POL4", + "POL3","POL1","POL6","GOR2","GOR3","GOR4","GOR5","SMIT","COL1","COL2", + "COL3","COL4","CAND","CBRA","COL6","TRE1","TRE2","ELEC","CEYE","FSKU", + "COL5","TBLU","TGRN","TRED","SMBT","SMGT","SMRT","HDB1","HDB2","HDB3", + "HDB4","HDB5","HDB6","POB1","POB2","BRS1","TLMP","TLP2" +}; + +extern "C" +{ +// Doesn't work with g++, needs actionf_p1 +void A_Light0( void *p1, void *p2 ); +void A_WeaponReady( void *p1, void *p2 ); +void A_Lower( void *p1, void *p2 ); +void A_Raise( void *p1, void *p2 ); +void A_Punch( void *p1, void *p2 ); +void A_ReFire( void *p1, void *p2 ); +void A_FirePistol( void *p1, void *p2 ); +void A_Light1( void *p1, void *p2 ); +void A_FireShotgun( void *p1, void *p2 ); +void A_Light2( void *p1, void *p2 ); +void A_FireShotgun2( void *p1, void *p2 ); +void A_CheckReload( void *p1, void *p2 ); +void A_OpenShotgun2( void *p1, void *p2 ); +void A_LoadShotgun2( void *p1, void *p2 ); +void A_CloseShotgun2( void *p1, void *p2 ); +void A_FireCGun( void *p1, void *p2 ); +void A_GunFlash( void *p1, void *p2 ); +void A_FireMissile( void *p1, void *p2 ); +void A_Saw( void *p1, void *p2 ); +void A_FirePlasma( void *p1, void *p2 ); +void A_BFGsound( void *p1, void *p2 ); +void A_FireBFG( void *p1, void *p2 ); +void A_BFGSpray( void *p1, void *p2 ); +void A_Explode( void *p1, void *p2 ); +void A_Pain( void *p1, void *p2 ); +void A_PlayerScream( void *p1, void *p2 ); +void A_Fall( void *p1, void *p2 ); +void A_XScream( void *p1, void *p2 ); +void A_Look( void *p1, void *p2 ); +void A_Chase( void *p1, void *p2 ); +void A_FaceTarget( void *p1, void *p2 ); +void A_PosAttack( void *p1, void *p2 ); +void A_Scream( void *p1, void *p2 ); +void A_SPosAttack( void *p1, void *p2 ); +void A_VileChase( void *p1, void *p2 ); +void A_VileStart( void *p1, void *p2 ); +void A_VileTarget( void *p1, void *p2 ); +void A_VileAttack( void *p1, void *p2 ); +void A_StartFire( void *p1, void *p2 ); +void A_Fire( void *p1, void *p2 ); +void A_FireCrackle( void *p1, void *p2 ); +void A_Tracer( void *p1, void *p2 ); +void A_SkelWhoosh( void *p1, void *p2 ); +void A_SkelFist( void *p1, void *p2 ); +void A_SkelMissile( void *p1, void *p2 ); +void A_FatRaise( void *p1, void *p2 ); +void A_FatAttack1( void *p1, void *p2 ); +void A_FatAttack2( void *p1, void *p2 ); +void A_FatAttack3( void *p1, void *p2 ); +void A_BossDeath( void *p1, void *p2 ); +void A_CPosAttack( void *p1, void *p2 ); +void A_CPosRefire( void *p1, void *p2 ); +void A_TroopAttack( void *p1, void *p2 ); +void A_SargAttack( void *p1, void *p2 ); +void A_HeadAttack( void *p1, void *p2 ); +void A_BruisAttack( void *p1, void *p2 ); +void A_SkullAttack( void *p1, void *p2 ); +void A_Metal( void *p1, void *p2 ); +void A_SpidRefire( void *p1, void *p2 ); +void A_BabyMetal( void *p1, void *p2 ); +void A_BspiAttack( void *p1, void *p2 ); +void A_Hoof( void *p1, void *p2 ); +void A_CyberAttack( void *p1, void *p2 ); +void A_PainAttack( void *p1, void *p2 ); +void A_PainDie( void *p1, void *p2 ); +void A_KeenDie( void *p1, void *p2 ); +void A_BrainPain( void *p1, void *p2 ); +void A_BrainScream( void *p1, void *p2 ); +void A_BrainDie( void *p1, void *p2 ); +void A_BrainAwake( void *p1, void *p2 ); +void A_BrainSpit( void *p1, void *p2 ); +void A_SpawnSound( void *p1, void *p2 ); +void A_SpawnFly( void *p1, void *p2 ); +void A_BrainExplode( void *p1, void *p2 ); +}; + +const state_t tempStates[NUMSTATES] = { + {SPR_TROO,0,-1,{NULL},S_NULL,0,0}, // S_NULL + {SPR_SHTG,4,0,{(actionf_p2)A_Light0},S_NULL,0,0}, // S_LIGHTDONE + {SPR_PUNG,0,1,{(actionf_p2)A_WeaponReady},S_PUNCH,0,0}, // S_PUNCH + {SPR_PUNG,0,1,{(actionf_p2)A_Lower},S_PUNCHDOWN,0,0}, // S_PUNCHDOWN + {SPR_PUNG,0,1,{(actionf_p2)A_Raise},S_PUNCHUP,0,0}, // S_PUNCHUP + {SPR_PUNG,1,4,{NULL},S_PUNCH2,0,0}, // S_PUNCH1 + {SPR_PUNG,2,4,{(actionf_p2)A_Punch},S_PUNCH3,0,0}, // S_PUNCH2 + {SPR_PUNG,3,5,{NULL},S_PUNCH4,0,0}, // S_PUNCH3 + {SPR_PUNG,2,4,{NULL},S_PUNCH5,0,0}, // S_PUNCH4 + {SPR_PUNG,1,5,{(actionf_p2)A_ReFire},S_PUNCH,0,0}, // S_PUNCH5 + {SPR_PISG,0,1,{(actionf_p2)A_WeaponReady},S_PISTOL,0,0},// S_PISTOL + {SPR_PISG,0,1,{(actionf_p2)A_Lower},S_PISTOLDOWN,0,0}, // S_PISTOLDOWN + {SPR_PISG,0,1,{(actionf_p2)A_Raise},S_PISTOLUP,0,0}, // S_PISTOLUP + {SPR_PISG,0,4,{NULL},S_PISTOL2,0,0}, // S_PISTOL1 + {SPR_PISG,1,6,{(actionf_p2)A_FirePistol},S_PISTOL3,0,0},// S_PISTOL2 + {SPR_PISG,2,4,{NULL},S_PISTOL4,0,0}, // S_PISTOL3 + {SPR_PISG,1,5,{(actionf_p2)A_ReFire},S_PISTOL,0,0}, // S_PISTOL4 + {SPR_PISF,32768,7,{(actionf_p2)A_Light1},S_LIGHTDONE,0,0}, // S_PISTOLFLASH + {SPR_SHTG,0,1,{(actionf_p2)A_WeaponReady},S_SGUN,0,0}, // S_SGUN + {SPR_SHTG,0,1,{(actionf_p2)A_Lower},S_SGUNDOWN,0,0}, // S_SGUNDOWN + {SPR_SHTG,0,1,{(actionf_p2)A_Raise},S_SGUNUP,0,0}, // S_SGUNUP + {SPR_SHTG,0,3,{NULL},S_SGUN2,0,0}, // S_SGUN1 + {SPR_SHTG,0,7,{(actionf_p2)A_FireShotgun},S_SGUN3,0,0}, // S_SGUN2 + {SPR_SHTG,1,5,{NULL},S_SGUN4,0,0}, // S_SGUN3 + {SPR_SHTG,2,5,{NULL},S_SGUN5,0,0}, // S_SGUN4 + {SPR_SHTG,3,4,{NULL},S_SGUN6,0,0}, // S_SGUN5 + {SPR_SHTG,2,5,{NULL},S_SGUN7,0,0}, // S_SGUN6 + {SPR_SHTG,1,5,{NULL},S_SGUN8,0,0}, // S_SGUN7 + {SPR_SHTG,0,3,{NULL},S_SGUN9,0,0}, // S_SGUN8 + {SPR_SHTG,0,7,{(actionf_p2)A_ReFire},S_SGUN,0,0}, // S_SGUN9 + {SPR_SHTF,32768,4,{(actionf_p2)A_Light1},S_SGUNFLASH2,0,0}, // S_SGUNFLASH1 + {SPR_SHTF,32769,3,{(actionf_p2)A_Light2},S_LIGHTDONE,0,0}, // S_SGUNFLASH2 + {SPR_SHT2,0,1,{(actionf_p2)A_WeaponReady},S_DSGUN,0,0}, // S_DSGUN + {SPR_SHT2,0,1,{(actionf_p2)A_Lower},S_DSGUNDOWN,0,0}, // S_DSGUNDOWN + {SPR_SHT2,0,1,{(actionf_p2)A_Raise},S_DSGUNUP,0,0}, // S_DSGUNUP + {SPR_SHT2,0,3,{NULL},S_DSGUN2,0,0}, // S_DSGUN1 + {SPR_SHT2,0,7,{(actionf_p2)A_FireShotgun2},S_DSGUN3,0,0}, // S_DSGUN2 + {SPR_SHT2,1,7,{NULL},S_DSGUN4,0,0}, // S_DSGUN3 + {SPR_SHT2,2,7,{(actionf_p2)A_CheckReload},S_DSGUN5,0,0}, // S_DSGUN4 + {SPR_SHT2,3,7,{(actionf_p2)A_OpenShotgun2},S_DSGUN6,0,0}, // S_DSGUN5 + {SPR_SHT2,4,7,{NULL},S_DSGUN7,0,0}, // S_DSGUN6 + {SPR_SHT2,5,7,{(actionf_p2)A_LoadShotgun2},S_DSGUN8,0,0}, // S_DSGUN7 + {SPR_SHT2,6,6,{NULL},S_DSGUN9,0,0}, // S_DSGUN8 + {SPR_SHT2,7,6,{(actionf_p2)A_CloseShotgun2},S_DSGUN10,0,0}, // S_DSGUN9 + {SPR_SHT2,0,5,{(actionf_p2)A_ReFire},S_DSGUN,0,0}, // S_DSGUN10 + {SPR_SHT2,1,7,{NULL},S_DSNR2,0,0}, // S_DSNR1 + {SPR_SHT2,0,3,{NULL},S_DSGUNDOWN,0,0}, // S_DSNR2 + {SPR_SHT2,32776,5,{(actionf_p2)A_Light1},S_DSGUNFLASH2,0,0}, // S_DSGUNFLASH1 + {SPR_SHT2,32777,4,{(actionf_p2)A_Light2},S_LIGHTDONE,0,0}, // S_DSGUNFLASH2 + {SPR_CHGG,0,1,{(actionf_p2)A_WeaponReady},S_CHAIN,0,0}, // S_CHAIN + {SPR_CHGG,0,1,{(actionf_p2)A_Lower},S_CHAINDOWN,0,0}, // S_CHAINDOWN + {SPR_CHGG,0,1,{(actionf_p2)A_Raise},S_CHAINUP,0,0}, // S_CHAINUP + {SPR_CHGG,0,4,{(actionf_p2)A_FireCGun},S_CHAIN2,0,0}, // S_CHAIN1 + {SPR_CHGG,1,4,{(actionf_p2)A_FireCGun},S_CHAIN3,0,0}, // S_CHAIN2 + {SPR_CHGG,1,0,{(actionf_p2)A_ReFire},S_CHAIN,0,0}, // S_CHAIN3 + {SPR_CHGF,32768,5,{(actionf_p2)A_Light1},S_LIGHTDONE,0,0}, // S_CHAINFLASH1 + {SPR_CHGF,32769,5,{(actionf_p2)A_Light2},S_LIGHTDONE,0,0}, // S_CHAINFLASH2 + {SPR_MISG,0,1,{(actionf_p2)A_WeaponReady},S_MISSILE,0,0}, // S_MISSILE + {SPR_MISG,0,1,{(actionf_p2)A_Lower},S_MISSILEDOWN,0,0}, // S_MISSILEDOWN + {SPR_MISG,0,1,{(actionf_p2)A_Raise},S_MISSILEUP,0,0}, // S_MISSILEUP + {SPR_MISG,1,8,{(actionf_p2)A_GunFlash},S_MISSILE2,0,0}, // S_MISSILE1 + {SPR_MISG,1,12,{(actionf_p2)A_FireMissile},S_MISSILE3,0,0}, // S_MISSILE2 + {SPR_MISG,1,0,{(actionf_p2)A_ReFire},S_MISSILE,0,0}, // S_MISSILE3 + {SPR_MISF,32768,3,{(actionf_p2)A_Light1},S_MISSILEFLASH2,0,0}, // S_MISSILEFLASH1 + {SPR_MISF,32769,4,{NULL},S_MISSILEFLASH3,0,0}, // S_MISSILEFLASH2 + {SPR_MISF,32770,4,{(actionf_p2)A_Light2},S_MISSILEFLASH4,0,0}, // S_MISSILEFLASH3 + {SPR_MISF,32771,4,{(actionf_p2)A_Light2},S_LIGHTDONE,0,0}, // S_MISSILEFLASH4 + {SPR_SAWG,2,4,{(actionf_p2)A_WeaponReady},S_SAWB,0,0}, // S_SAW + {SPR_SAWG,3,4,{(actionf_p2)A_WeaponReady},S_SAW,0,0}, // S_SAWB + {SPR_SAWG,2,1,{(actionf_p2)A_Lower},S_SAWDOWN,0,0}, // S_SAWDOWN + {SPR_SAWG,2,1,{(actionf_p2)A_Raise},S_SAWUP,0,0}, // S_SAWUP + {SPR_SAWG,0,4,{(actionf_p2)A_Saw},S_SAW2,0,0}, // S_SAW1 + {SPR_SAWG,1,4,{(actionf_p2)A_Saw},S_SAW3,0,0}, // S_SAW2 + {SPR_SAWG,1,0,{(actionf_p2)A_ReFire},S_SAW,0,0}, // S_SAW3 + {SPR_PLSG,0,1,{(actionf_p2)A_WeaponReady},S_PLASMA,0,0}, // S_PLASMA + {SPR_PLSG,0,1,{(actionf_p2)A_Lower},S_PLASMADOWN,0,0}, // S_PLASMADOWN + {SPR_PLSG,0,1,{(actionf_p2)A_Raise},S_PLASMAUP,0,0}, // S_PLASMAUP + {SPR_PLSG,0,3,{(actionf_p2)A_FirePlasma},S_PLASMA2,0,0}, // S_PLASMA1 + {SPR_PLSG,1,20,{(actionf_p2)A_ReFire},S_PLASMA,0,0}, // S_PLASMA2 + {SPR_PLSF,32768,4,{(actionf_p2)A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH1 + {SPR_PLSF,32769,4,{(actionf_p2)A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH2 + {SPR_BFGG,0,1,{(actionf_p2)A_WeaponReady},S_BFG,0,0}, // S_BFG + {SPR_BFGG,0,1,{(actionf_p2)A_Lower},S_BFGDOWN,0,0}, // S_BFGDOWN + {SPR_BFGG,0,1,{(actionf_p2)A_Raise},S_BFGUP,0,0}, // S_BFGUP + {SPR_BFGG,0,20,{(actionf_p2)A_BFGsound},S_BFG2,0,0}, // S_BFG1 + {SPR_BFGG,1,10,{(actionf_p2)A_GunFlash},S_BFG3,0,0}, // S_BFG2 + {SPR_BFGG,1,10,{(actionf_p2)A_FireBFG},S_BFG4,0,0}, // S_BFG3 + {SPR_BFGG,1,20,{(actionf_p2)A_ReFire},S_BFG,0,0}, // S_BFG4 + {SPR_BFGF,32768,11,{(actionf_p2)A_Light1},S_BFGFLASH2,0,0}, // S_BFGFLASH1 + {SPR_BFGF,32769,6,{(actionf_p2)A_Light2},S_LIGHTDONE,0,0}, // S_BFGFLASH2 + {SPR_BLUD,2,8,{NULL},S_BLOOD2,0,0}, // S_BLOOD1 + {SPR_BLUD,1,8,{NULL},S_BLOOD3,0,0}, // S_BLOOD2 + {SPR_BLUD,0,8,{NULL},S_NULL,0,0}, // S_BLOOD3 + {SPR_PUFF,32768,4,{NULL},S_PUFF2,0,0}, // S_PUFF1 + {SPR_PUFF,1,4,{NULL},S_PUFF3,0,0}, // S_PUFF2 + {SPR_PUFF,2,4,{NULL},S_PUFF4,0,0}, // S_PUFF3 + {SPR_PUFF,3,4,{NULL},S_NULL,0,0}, // S_PUFF4 + {SPR_BAL1,32768,4,{NULL},S_TBALL2,0,0}, // S_TBALL1 + {SPR_BAL1,32769,4,{NULL},S_TBALL1,0,0}, // S_TBALL2 + {SPR_BAL1,32770,6,{NULL},S_TBALLX2,0,0}, // S_TBALLX1 + {SPR_BAL1,32771,6,{NULL},S_TBALLX3,0,0}, // S_TBALLX2 + {SPR_BAL1,32772,6,{NULL},S_NULL,0,0}, // S_TBALLX3 + {SPR_BAL2,32768,4,{NULL},S_RBALL2,0,0}, // S_RBALL1 + {SPR_BAL2,32769,4,{NULL},S_RBALL1,0,0}, // S_RBALL2 + {SPR_BAL2,32770,6,{NULL},S_RBALLX2,0,0}, // S_RBALLX1 + {SPR_BAL2,32771,6,{NULL},S_RBALLX3,0,0}, // S_RBALLX2 + {SPR_BAL2,32772,6,{NULL},S_NULL,0,0}, // S_RBALLX3 + {SPR_PLSS,32768,6,{NULL},S_PLASBALL2,0,0}, // S_PLASBALL + {SPR_PLSS,32769,6,{NULL},S_PLASBALL,0,0}, // S_PLASBALL2 + {SPR_PLSE,32768,4,{NULL},S_PLASEXP2,0,0}, // S_PLASEXP + {SPR_PLSE,32769,4,{NULL},S_PLASEXP3,0,0}, // S_PLASEXP2 + {SPR_PLSE,32770,4,{NULL},S_PLASEXP4,0,0}, // S_PLASEXP3 + {SPR_PLSE,32771,4,{NULL},S_PLASEXP5,0,0}, // S_PLASEXP4 + {SPR_PLSE,32772,4,{NULL},S_NULL,0,0}, // S_PLASEXP5 + {SPR_MISL,32768,1,{NULL},S_ROCKET,0,0}, // S_ROCKET + {SPR_BFS1,32768,4,{NULL},S_BFGSHOT2,0,0}, // S_BFGSHOT + {SPR_BFS1,32769,4,{NULL},S_BFGSHOT,0,0}, // S_BFGSHOT2 + {SPR_BFE1,32768,8,{NULL},S_BFGLAND2,0,0}, // S_BFGLAND + {SPR_BFE1,32769,8,{NULL},S_BFGLAND3,0,0}, // S_BFGLAND2 + {SPR_BFE1,32770,8,{(actionf_p2)A_BFGSpray},S_BFGLAND4,0,0}, // S_BFGLAND3 + {SPR_BFE1,32771,8,{NULL},S_BFGLAND5,0,0}, // S_BFGLAND4 + {SPR_BFE1,32772,8,{NULL},S_BFGLAND6,0,0}, // S_BFGLAND5 + {SPR_BFE1,32773,8,{NULL},S_NULL,0,0}, // S_BFGLAND6 + {SPR_BFE2,32768,8,{NULL},S_BFGEXP2,0,0}, // S_BFGEXP + {SPR_BFE2,32769,8,{NULL},S_BFGEXP3,0,0}, // S_BFGEXP2 + {SPR_BFE2,32770,8,{NULL},S_BFGEXP4,0,0}, // S_BFGEXP3 + {SPR_BFE2,32771,8,{NULL},S_NULL,0,0}, // S_BFGEXP4 + {SPR_MISL,32769,8,{(actionf_p2)A_Explode},S_EXPLODE2,0,0}, // S_EXPLODE1 + {SPR_MISL,32770,6,{NULL},S_EXPLODE3,0,0}, // S_EXPLODE2 + {SPR_MISL,32771,4,{NULL},S_NULL,0,0}, // S_EXPLODE3 + {SPR_TFOG,32768,6,{NULL},S_TFOG01,0,0}, // S_TFOG + {SPR_TFOG,32769,6,{NULL},S_TFOG02,0,0}, // S_TFOG01 + {SPR_TFOG,32768,6,{NULL},S_TFOG2,0,0}, // S_TFOG02 + {SPR_TFOG,32769,6,{NULL},S_TFOG3,0,0}, // S_TFOG2 + {SPR_TFOG,32770,6,{NULL},S_TFOG4,0,0}, // S_TFOG3 + {SPR_TFOG,32771,6,{NULL},S_TFOG5,0,0}, // S_TFOG4 + {SPR_TFOG,32772,6,{NULL},S_TFOG6,0,0}, // S_TFOG5 + {SPR_TFOG,32773,6,{NULL},S_TFOG7,0,0}, // S_TFOG6 + {SPR_TFOG,32774,6,{NULL},S_TFOG8,0,0}, // S_TFOG7 + {SPR_TFOG,32775,6,{NULL},S_TFOG9,0,0}, // S_TFOG8 + {SPR_TFOG,32776,6,{NULL},S_TFOG10,0,0}, // S_TFOG9 + {SPR_TFOG,32777,6,{NULL},S_NULL,0,0}, // S_TFOG10 + {SPR_IFOG,32768,6,{NULL},S_IFOG01,0,0}, // S_IFOG + {SPR_IFOG,32769,6,{NULL},S_IFOG02,0,0}, // S_IFOG01 + {SPR_IFOG,32768,6,{NULL},S_IFOG2,0,0}, // S_IFOG02 + {SPR_IFOG,32769,6,{NULL},S_IFOG3,0,0}, // S_IFOG2 + {SPR_IFOG,32770,6,{NULL},S_IFOG4,0,0}, // S_IFOG3 + {SPR_IFOG,32771,6,{NULL},S_IFOG5,0,0}, // S_IFOG4 + {SPR_IFOG,32772,6,{NULL},S_NULL,0,0}, // S_IFOG5 + {SPR_PLAY,0,-1,{NULL},S_NULL,0,0}, // S_PLAY + {SPR_PLAY,0,4,{NULL},S_PLAY_RUN2,0,0}, // S_PLAY_RUN1 + {SPR_PLAY,1,4,{NULL},S_PLAY_RUN3,0,0}, // S_PLAY_RUN2 + {SPR_PLAY,2,4,{NULL},S_PLAY_RUN4,0,0}, // S_PLAY_RUN3 + {SPR_PLAY,3,4,{NULL},S_PLAY_RUN1,0,0}, // S_PLAY_RUN4 + {SPR_PLAY,4,12,{NULL},S_PLAY,0,0}, // S_PLAY_ATK1 + {SPR_PLAY,32773,6,{NULL},S_PLAY_ATK1,0,0}, // S_PLAY_ATK2 + {SPR_PLAY,6,4,{NULL},S_PLAY_PAIN2,0,0}, // S_PLAY_PAIN + {SPR_PLAY,6,4,{(actionf_p2)A_Pain},S_PLAY,0,0}, // S_PLAY_PAIN2 + {SPR_PLAY,7,10,{NULL},S_PLAY_DIE2,0,0}, // S_PLAY_DIE1 + {SPR_PLAY,8,10,{(actionf_p2)A_PlayerScream},S_PLAY_DIE3,0,0}, // S_PLAY_DIE2 + {SPR_PLAY,9,10,{(actionf_p2)A_Fall},S_PLAY_DIE4,0,0}, // S_PLAY_DIE3 + {SPR_PLAY,10,10,{NULL},S_PLAY_DIE5,0,0}, // S_PLAY_DIE4 + {SPR_PLAY,11,10,{NULL},S_PLAY_DIE6,0,0}, // S_PLAY_DIE5 + {SPR_PLAY,12,10,{NULL},S_PLAY_DIE7,0,0}, // S_PLAY_DIE6 + {SPR_PLAY,13,-1,{NULL},S_NULL,0,0}, // S_PLAY_DIE7 + {SPR_PLAY,14,5,{NULL},S_PLAY_XDIE2,0,0}, // S_PLAY_XDIE1 + {SPR_PLAY,15,5,{(actionf_p2)A_XScream},S_PLAY_XDIE3,0,0}, // S_PLAY_XDIE2 + {SPR_PLAY,16,5,{(actionf_p2)A_Fall},S_PLAY_XDIE4,0,0}, // S_PLAY_XDIE3 + {SPR_PLAY,17,5,{NULL},S_PLAY_XDIE5,0,0}, // S_PLAY_XDIE4 + {SPR_PLAY,18,5,{NULL},S_PLAY_XDIE6,0,0}, // S_PLAY_XDIE5 + {SPR_PLAY,19,5,{NULL},S_PLAY_XDIE7,0,0}, // S_PLAY_XDIE6 + {SPR_PLAY,20,5,{NULL},S_PLAY_XDIE8,0,0}, // S_PLAY_XDIE7 + {SPR_PLAY,21,5,{NULL},S_PLAY_XDIE9,0,0}, // S_PLAY_XDIE8 + {SPR_PLAY,22,-1,{NULL},S_NULL,0,0}, // S_PLAY_XDIE9 + {SPR_POSS,0,10,{(actionf_p2)A_Look},S_POSS_STND2,0,0}, // S_POSS_STND + {SPR_POSS,1,10,{(actionf_p2)A_Look},S_POSS_STND,0,0}, // S_POSS_STND2 + {SPR_POSS,0,4,{(actionf_p2)A_Chase},S_POSS_RUN2,0,0}, // S_POSS_RUN1 + {SPR_POSS,0,4,{(actionf_p2)A_Chase},S_POSS_RUN3,0,0}, // S_POSS_RUN2 + {SPR_POSS,1,4,{(actionf_p2)A_Chase},S_POSS_RUN4,0,0}, // S_POSS_RUN3 + {SPR_POSS,1,4,{(actionf_p2)A_Chase},S_POSS_RUN5,0,0}, // S_POSS_RUN4 + {SPR_POSS,2,4,{(actionf_p2)A_Chase},S_POSS_RUN6,0,0}, // S_POSS_RUN5 + {SPR_POSS,2,4,{(actionf_p2)A_Chase},S_POSS_RUN7,0,0}, // S_POSS_RUN6 + {SPR_POSS,3,4,{(actionf_p2)A_Chase},S_POSS_RUN8,0,0}, // S_POSS_RUN7 + {SPR_POSS,3,4,{(actionf_p2)A_Chase},S_POSS_RUN1,0,0}, // S_POSS_RUN8 + {SPR_POSS,4,10,{(actionf_p2)A_FaceTarget},S_POSS_ATK2,0,0}, // S_POSS_ATK1 + {SPR_POSS,5,8,{(actionf_p2)A_PosAttack},S_POSS_ATK3,0,0}, // S_POSS_ATK2 + {SPR_POSS,4,8,{NULL},S_POSS_RUN1,0,0}, // S_POSS_ATK3 + {SPR_POSS,6,3,{NULL},S_POSS_PAIN2,0,0}, // S_POSS_PAIN + {SPR_POSS,6,3,{(actionf_p2)A_Pain},S_POSS_RUN1,0,0}, // S_POSS_PAIN2 + {SPR_POSS,7,5,{NULL},S_POSS_DIE2,0,0}, // S_POSS_DIE1 + {SPR_POSS,8,5,{(actionf_p2)A_Scream},S_POSS_DIE3,0,0}, // S_POSS_DIE2 + {SPR_POSS,9,5,{(actionf_p2)A_Fall},S_POSS_DIE4,0,0}, // S_POSS_DIE3 + {SPR_POSS,10,5,{NULL},S_POSS_DIE5,0,0}, // S_POSS_DIE4 + {SPR_POSS,11,-1,{NULL},S_NULL,0,0}, // S_POSS_DIE5 + {SPR_POSS,12,5,{NULL},S_POSS_XDIE2,0,0}, // S_POSS_XDIE1 + {SPR_POSS,13,5,{(actionf_p2)A_XScream},S_POSS_XDIE3,0,0}, // S_POSS_XDIE2 + {SPR_POSS,14,5,{(actionf_p2)A_Fall},S_POSS_XDIE4,0,0}, // S_POSS_XDIE3 + {SPR_POSS,15,5,{NULL},S_POSS_XDIE5,0,0}, // S_POSS_XDIE4 + {SPR_POSS,16,5,{NULL},S_POSS_XDIE6,0,0}, // S_POSS_XDIE5 + {SPR_POSS,17,5,{NULL},S_POSS_XDIE7,0,0}, // S_POSS_XDIE6 + {SPR_POSS,18,5,{NULL},S_POSS_XDIE8,0,0}, // S_POSS_XDIE7 + {SPR_POSS,19,5,{NULL},S_POSS_XDIE9,0,0}, // S_POSS_XDIE8 + {SPR_POSS,20,-1,{NULL},S_NULL,0,0}, // S_POSS_XDIE9 + {SPR_POSS,10,5,{NULL},S_POSS_RAISE2,0,0}, // S_POSS_RAISE1 + {SPR_POSS,9,5,{NULL},S_POSS_RAISE3,0,0}, // S_POSS_RAISE2 + {SPR_POSS,8,5,{NULL},S_POSS_RAISE4,0,0}, // S_POSS_RAISE3 + {SPR_POSS,7,5,{NULL},S_POSS_RUN1,0,0}, // S_POSS_RAISE4 + {SPR_SPOS,0,10,{(actionf_p2)A_Look},S_SPOS_STND2,0,0}, // S_SPOS_STND + {SPR_SPOS,1,10,{(actionf_p2)A_Look},S_SPOS_STND,0,0}, // S_SPOS_STND2 + {SPR_SPOS,0,3,{(actionf_p2)A_Chase},S_SPOS_RUN2,0,0}, // S_SPOS_RUN1 + {SPR_SPOS,0,3,{(actionf_p2)A_Chase},S_SPOS_RUN3,0,0}, // S_SPOS_RUN2 + {SPR_SPOS,1,3,{(actionf_p2)A_Chase},S_SPOS_RUN4,0,0}, // S_SPOS_RUN3 + {SPR_SPOS,1,3,{(actionf_p2)A_Chase},S_SPOS_RUN5,0,0}, // S_SPOS_RUN4 + {SPR_SPOS,2,3,{(actionf_p2)A_Chase},S_SPOS_RUN6,0,0}, // S_SPOS_RUN5 + {SPR_SPOS,2,3,{(actionf_p2)A_Chase},S_SPOS_RUN7,0,0}, // S_SPOS_RUN6 + {SPR_SPOS,3,3,{(actionf_p2)A_Chase},S_SPOS_RUN8,0,0}, // S_SPOS_RUN7 + {SPR_SPOS,3,3,{(actionf_p2)A_Chase},S_SPOS_RUN1,0,0}, // S_SPOS_RUN8 + {SPR_SPOS,4,10,{(actionf_p2)A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1 + {SPR_SPOS,32773,10,{(actionf_p2)A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2 + {SPR_SPOS,4,10,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_ATK3 + {SPR_SPOS,6,3,{NULL},S_SPOS_PAIN2,0,0}, // S_SPOS_PAIN + {SPR_SPOS,6,3,{(actionf_p2)A_Pain},S_SPOS_RUN1,0,0}, // S_SPOS_PAIN2 + {SPR_SPOS,7,5,{NULL},S_SPOS_DIE2,0,0}, // S_SPOS_DIE1 + {SPR_SPOS,8,5,{(actionf_p2)A_Scream},S_SPOS_DIE3,0,0}, // S_SPOS_DIE2 + {SPR_SPOS,9,5,{(actionf_p2)A_Fall},S_SPOS_DIE4,0,0}, // S_SPOS_DIE3 + {SPR_SPOS,10,5,{NULL},S_SPOS_DIE5,0,0}, // S_SPOS_DIE4 + {SPR_SPOS,11,-1,{NULL},S_NULL,0,0}, // S_SPOS_DIE5 + {SPR_SPOS,12,5,{NULL},S_SPOS_XDIE2,0,0}, // S_SPOS_XDIE1 + {SPR_SPOS,13,5,{(actionf_p2)A_XScream},S_SPOS_XDIE3,0,0}, // S_SPOS_XDIE2 + {SPR_SPOS,14,5,{(actionf_p2)A_Fall},S_SPOS_XDIE4,0,0}, // S_SPOS_XDIE3 + {SPR_SPOS,15,5,{NULL},S_SPOS_XDIE5,0,0}, // S_SPOS_XDIE4 + {SPR_SPOS,16,5,{NULL},S_SPOS_XDIE6,0,0}, // S_SPOS_XDIE5 + {SPR_SPOS,17,5,{NULL},S_SPOS_XDIE7,0,0}, // S_SPOS_XDIE6 + {SPR_SPOS,18,5,{NULL},S_SPOS_XDIE8,0,0}, // S_SPOS_XDIE7 + {SPR_SPOS,19,5,{NULL},S_SPOS_XDIE9,0,0}, // S_SPOS_XDIE8 + {SPR_SPOS,20,-1,{NULL},S_NULL,0,0}, // S_SPOS_XDIE9 + {SPR_SPOS,11,5,{NULL},S_SPOS_RAISE2,0,0}, // S_SPOS_RAISE1 + {SPR_SPOS,10,5,{NULL},S_SPOS_RAISE3,0,0}, // S_SPOS_RAISE2 + {SPR_SPOS,9,5,{NULL},S_SPOS_RAISE4,0,0}, // S_SPOS_RAISE3 + {SPR_SPOS,8,5,{NULL},S_SPOS_RAISE5,0,0}, // S_SPOS_RAISE4 + {SPR_SPOS,7,5,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_RAISE5 + {SPR_VILE,0,10,{(actionf_p2)A_Look},S_VILE_STND2,0,0}, // S_VILE_STND + {SPR_VILE,1,10,{(actionf_p2)A_Look},S_VILE_STND,0,0}, // S_VILE_STND2 + {SPR_VILE,0,2,{(actionf_p2)A_VileChase},S_VILE_RUN2,0,0}, // S_VILE_RUN1 + {SPR_VILE,0,2,{(actionf_p2)A_VileChase},S_VILE_RUN3,0,0}, // S_VILE_RUN2 + {SPR_VILE,1,2,{(actionf_p2)A_VileChase},S_VILE_RUN4,0,0}, // S_VILE_RUN3 + {SPR_VILE,1,2,{(actionf_p2)A_VileChase},S_VILE_RUN5,0,0}, // S_VILE_RUN4 + {SPR_VILE,2,2,{(actionf_p2)A_VileChase},S_VILE_RUN6,0,0}, // S_VILE_RUN5 + {SPR_VILE,2,2,{(actionf_p2)A_VileChase},S_VILE_RUN7,0,0}, // S_VILE_RUN6 + {SPR_VILE,3,2,{(actionf_p2)A_VileChase},S_VILE_RUN8,0,0}, // S_VILE_RUN7 + {SPR_VILE,3,2,{(actionf_p2)A_VileChase},S_VILE_RUN9,0,0}, // S_VILE_RUN8 + {SPR_VILE,4,2,{(actionf_p2)A_VileChase},S_VILE_RUN10,0,0}, // S_VILE_RUN9 + {SPR_VILE,4,2,{(actionf_p2)A_VileChase},S_VILE_RUN11,0,0}, // S_VILE_RUN10 + {SPR_VILE,5,2,{(actionf_p2)A_VileChase},S_VILE_RUN12,0,0}, // S_VILE_RUN11 + {SPR_VILE,5,2,{(actionf_p2)A_VileChase},S_VILE_RUN1,0,0}, // S_VILE_RUN12 + {SPR_VILE,32774,0,{(actionf_p2)A_VileStart},S_VILE_ATK2,0,0}, // S_VILE_ATK1 + {SPR_VILE,32774,10,{(actionf_p2)A_FaceTarget},S_VILE_ATK3,0,0}, // S_VILE_ATK2 + {SPR_VILE,32775,8,{(actionf_p2)A_VileTarget},S_VILE_ATK4,0,0}, // S_VILE_ATK3 + {SPR_VILE,32776,8,{(actionf_p2)A_FaceTarget},S_VILE_ATK5,0,0}, // S_VILE_ATK4 + {SPR_VILE,32777,8,{(actionf_p2)A_FaceTarget},S_VILE_ATK6,0,0}, // S_VILE_ATK5 + {SPR_VILE,32778,8,{(actionf_p2)A_FaceTarget},S_VILE_ATK7,0,0}, // S_VILE_ATK6 + {SPR_VILE,32779,8,{(actionf_p2)A_FaceTarget},S_VILE_ATK8,0,0}, // S_VILE_ATK7 + {SPR_VILE,32780,8,{(actionf_p2)A_FaceTarget},S_VILE_ATK9,0,0}, // S_VILE_ATK8 + {SPR_VILE,32781,8,{(actionf_p2)A_FaceTarget},S_VILE_ATK10,0,0}, // S_VILE_ATK9 + {SPR_VILE,32782,8,{(actionf_p2)A_VileAttack},S_VILE_ATK11,0,0}, // S_VILE_ATK10 + {SPR_VILE,32783,20,{NULL},S_VILE_RUN1,0,0}, // S_VILE_ATK11 + {SPR_VILE,32794,10,{NULL},S_VILE_HEAL2,0,0}, // S_VILE_HEAL1 + {SPR_VILE,32795,10,{NULL},S_VILE_HEAL3,0,0}, // S_VILE_HEAL2 + {SPR_VILE,32796,10,{NULL},S_VILE_RUN1,0,0}, // S_VILE_HEAL3 + {SPR_VILE,16,5,{NULL},S_VILE_PAIN2,0,0}, // S_VILE_PAIN + {SPR_VILE,16,5,{(actionf_p2)A_Pain},S_VILE_RUN1,0,0}, // S_VILE_PAIN2 + {SPR_VILE,16,7,{NULL},S_VILE_DIE2,0,0}, // S_VILE_DIE1 + {SPR_VILE,17,7,{(actionf_p2)A_Scream},S_VILE_DIE3,0,0}, // S_VILE_DIE2 + {SPR_VILE,18,7,{(actionf_p2)A_Fall},S_VILE_DIE4,0,0}, // S_VILE_DIE3 + {SPR_VILE,19,7,{NULL},S_VILE_DIE5,0,0}, // S_VILE_DIE4 + {SPR_VILE,20,7,{NULL},S_VILE_DIE6,0,0}, // S_VILE_DIE5 + {SPR_VILE,21,7,{NULL},S_VILE_DIE7,0,0}, // S_VILE_DIE6 + {SPR_VILE,22,7,{NULL},S_VILE_DIE8,0,0}, // S_VILE_DIE7 + {SPR_VILE,23,5,{NULL},S_VILE_DIE9,0,0}, // S_VILE_DIE8 + {SPR_VILE,24,5,{NULL},S_VILE_DIE10,0,0}, // S_VILE_DIE9 + {SPR_VILE,25,-1,{NULL},S_NULL,0,0}, // S_VILE_DIE10 + {SPR_FIRE,32768,2,{(actionf_p2)A_StartFire},S_FIRE2,0,0}, // S_FIRE1 + {SPR_FIRE,32769,2,{(actionf_p2)A_Fire},S_FIRE3,0,0}, // S_FIRE2 + {SPR_FIRE,32768,2,{(actionf_p2)A_Fire},S_FIRE4,0,0}, // S_FIRE3 + {SPR_FIRE,32769,2,{(actionf_p2)A_Fire},S_FIRE5,0,0}, // S_FIRE4 + {SPR_FIRE,32770,2,{(actionf_p2)A_FireCrackle},S_FIRE6,0,0}, // S_FIRE5 + {SPR_FIRE,32769,2,{(actionf_p2)A_Fire},S_FIRE7,0,0}, // S_FIRE6 + {SPR_FIRE,32770,2,{(actionf_p2)A_Fire},S_FIRE8,0,0}, // S_FIRE7 + {SPR_FIRE,32769,2,{(actionf_p2)A_Fire},S_FIRE9,0,0}, // S_FIRE8 + {SPR_FIRE,32770,2,{(actionf_p2)A_Fire},S_FIRE10,0,0}, // S_FIRE9 + {SPR_FIRE,32771,2,{(actionf_p2)A_Fire},S_FIRE11,0,0}, // S_FIRE10 + {SPR_FIRE,32770,2,{(actionf_p2)A_Fire},S_FIRE12,0,0}, // S_FIRE11 + {SPR_FIRE,32771,2,{(actionf_p2)A_Fire},S_FIRE13,0,0}, // S_FIRE12 + {SPR_FIRE,32770,2,{(actionf_p2)A_Fire},S_FIRE14,0,0}, // S_FIRE13 + {SPR_FIRE,32771,2,{(actionf_p2)A_Fire},S_FIRE15,0,0}, // S_FIRE14 + {SPR_FIRE,32772,2,{(actionf_p2)A_Fire},S_FIRE16,0,0}, // S_FIRE15 + {SPR_FIRE,32771,2,{(actionf_p2)A_Fire},S_FIRE17,0,0}, // S_FIRE16 + {SPR_FIRE,32772,2,{(actionf_p2)A_Fire},S_FIRE18,0,0}, // S_FIRE17 + {SPR_FIRE,32771,2,{(actionf_p2)A_Fire},S_FIRE19,0,0}, // S_FIRE18 + {SPR_FIRE,32772,2,{(actionf_p2)A_FireCrackle},S_FIRE20,0,0}, // S_FIRE19 + {SPR_FIRE,32773,2,{(actionf_p2)A_Fire},S_FIRE21,0,0}, // S_FIRE20 + {SPR_FIRE,32772,2,{(actionf_p2)A_Fire},S_FIRE22,0,0}, // S_FIRE21 + {SPR_FIRE,32773,2,{(actionf_p2)A_Fire},S_FIRE23,0,0}, // S_FIRE22 + {SPR_FIRE,32772,2,{(actionf_p2)A_Fire},S_FIRE24,0,0}, // S_FIRE23 + {SPR_FIRE,32773,2,{(actionf_p2)A_Fire},S_FIRE25,0,0}, // S_FIRE24 + {SPR_FIRE,32774,2,{(actionf_p2)A_Fire},S_FIRE26,0,0}, // S_FIRE25 + {SPR_FIRE,32775,2,{(actionf_p2)A_Fire},S_FIRE27,0,0}, // S_FIRE26 + {SPR_FIRE,32774,2,{(actionf_p2)A_Fire},S_FIRE28,0,0}, // S_FIRE27 + {SPR_FIRE,32775,2,{(actionf_p2)A_Fire},S_FIRE29,0,0}, // S_FIRE28 + {SPR_FIRE,32774,2,{(actionf_p2)A_Fire},S_FIRE30,0,0}, // S_FIRE29 + {SPR_FIRE,32775,2,{(actionf_p2)A_Fire},S_NULL,0,0}, // S_FIRE30 + {SPR_PUFF,1,4,{NULL},S_SMOKE2,0,0}, // S_SMOKE1 + {SPR_PUFF,2,4,{NULL},S_SMOKE3,0,0}, // S_SMOKE2 + {SPR_PUFF,1,4,{NULL},S_SMOKE4,0,0}, // S_SMOKE3 + {SPR_PUFF,2,4,{NULL},S_SMOKE5,0,0}, // S_SMOKE4 + {SPR_PUFF,3,4,{NULL},S_NULL,0,0}, // S_SMOKE5 + {SPR_FATB,32768,2,{(actionf_p2)A_Tracer},S_TRACER2,0,0}, // S_TRACER + {SPR_FATB,32769,2,{(actionf_p2)A_Tracer},S_TRACER,0,0}, // S_TRACER2 + {SPR_FBXP,32768,8,{NULL},S_TRACEEXP2,0,0}, // S_TRACEEXP1 + {SPR_FBXP,32769,6,{NULL},S_TRACEEXP3,0,0}, // S_TRACEEXP2 + {SPR_FBXP,32770,4,{NULL},S_NULL,0,0}, // S_TRACEEXP3 + {SPR_SKEL,0,10,{(actionf_p2)A_Look},S_SKEL_STND2,0,0}, // S_SKEL_STND + {SPR_SKEL,1,10,{(actionf_p2)A_Look},S_SKEL_STND,0,0}, // S_SKEL_STND2 + {SPR_SKEL,0,2,{(actionf_p2)A_Chase},S_SKEL_RUN2,0,0}, // S_SKEL_RUN1 + {SPR_SKEL,0,2,{(actionf_p2)A_Chase},S_SKEL_RUN3,0,0}, // S_SKEL_RUN2 + {SPR_SKEL,1,2,{(actionf_p2)A_Chase},S_SKEL_RUN4,0,0}, // S_SKEL_RUN3 + {SPR_SKEL,1,2,{(actionf_p2)A_Chase},S_SKEL_RUN5,0,0}, // S_SKEL_RUN4 + {SPR_SKEL,2,2,{(actionf_p2)A_Chase},S_SKEL_RUN6,0,0}, // S_SKEL_RUN5 + {SPR_SKEL,2,2,{(actionf_p2)A_Chase},S_SKEL_RUN7,0,0}, // S_SKEL_RUN6 + {SPR_SKEL,3,2,{(actionf_p2)A_Chase},S_SKEL_RUN8,0,0}, // S_SKEL_RUN7 + {SPR_SKEL,3,2,{(actionf_p2)A_Chase},S_SKEL_RUN9,0,0}, // S_SKEL_RUN8 + {SPR_SKEL,4,2,{(actionf_p2)A_Chase},S_SKEL_RUN10,0,0}, // S_SKEL_RUN9 + {SPR_SKEL,4,2,{(actionf_p2)A_Chase},S_SKEL_RUN11,0,0}, // S_SKEL_RUN10 + {SPR_SKEL,5,2,{(actionf_p2)A_Chase},S_SKEL_RUN12,0,0}, // S_SKEL_RUN11 + {SPR_SKEL,5,2,{(actionf_p2)A_Chase},S_SKEL_RUN1,0,0}, // S_SKEL_RUN12 + {SPR_SKEL,6,0,{(actionf_p2)A_FaceTarget},S_SKEL_FIST2,0,0}, // S_SKEL_FIST1 + {SPR_SKEL,6,6,{(actionf_p2)A_SkelWhoosh},S_SKEL_FIST3,0,0}, // S_SKEL_FIST2 + {SPR_SKEL,7,6,{(actionf_p2)A_FaceTarget},S_SKEL_FIST4,0,0}, // S_SKEL_FIST3 + {SPR_SKEL,8,6,{(actionf_p2)A_SkelFist},S_SKEL_RUN1,0,0}, // S_SKEL_FIST4 + {SPR_SKEL,32777,0,{(actionf_p2)A_FaceTarget},S_SKEL_MISS2,0,0}, // S_SKEL_MISS1 + {SPR_SKEL,32777,10,{(actionf_p2)A_FaceTarget},S_SKEL_MISS3,0,0}, // S_SKEL_MISS2 + {SPR_SKEL,10,10,{(actionf_p2)A_SkelMissile},S_SKEL_MISS4,0,0}, // S_SKEL_MISS3 + {SPR_SKEL,10,10,{(actionf_p2)A_FaceTarget},S_SKEL_RUN1,0,0}, // S_SKEL_MISS4 + {SPR_SKEL,11,5,{NULL},S_SKEL_PAIN2,0,0}, // S_SKEL_PAIN + {SPR_SKEL,11,5,{(actionf_p2)A_Pain},S_SKEL_RUN1,0,0}, // S_SKEL_PAIN2 + {SPR_SKEL,11,7,{NULL},S_SKEL_DIE2,0,0}, // S_SKEL_DIE1 + {SPR_SKEL,12,7,{NULL},S_SKEL_DIE3,0,0}, // S_SKEL_DIE2 + {SPR_SKEL,13,7,{(actionf_p2)A_Scream},S_SKEL_DIE4,0,0}, // S_SKEL_DIE3 + {SPR_SKEL,14,7,{(actionf_p2)A_Fall},S_SKEL_DIE5,0,0}, // S_SKEL_DIE4 + {SPR_SKEL,15,7,{NULL},S_SKEL_DIE6,0,0}, // S_SKEL_DIE5 + {SPR_SKEL,16,-1,{NULL},S_NULL,0,0}, // S_SKEL_DIE6 + {SPR_SKEL,16,5,{NULL},S_SKEL_RAISE2,0,0}, // S_SKEL_RAISE1 + {SPR_SKEL,15,5,{NULL},S_SKEL_RAISE3,0,0}, // S_SKEL_RAISE2 + {SPR_SKEL,14,5,{NULL},S_SKEL_RAISE4,0,0}, // S_SKEL_RAISE3 + {SPR_SKEL,13,5,{NULL},S_SKEL_RAISE5,0,0}, // S_SKEL_RAISE4 + {SPR_SKEL,12,5,{NULL},S_SKEL_RAISE6,0,0}, // S_SKEL_RAISE5 + {SPR_SKEL,11,5,{NULL},S_SKEL_RUN1,0,0}, // S_SKEL_RAISE6 + {SPR_MANF,32768,4,{NULL},S_FATSHOT2,0,0}, // S_FATSHOT1 + {SPR_MANF,32769,4,{NULL},S_FATSHOT1,0,0}, // S_FATSHOT2 + {SPR_MISL,32769,8,{NULL},S_FATSHOTX2,0,0}, // S_FATSHOTX1 + {SPR_MISL,32770,6,{NULL},S_FATSHOTX3,0,0}, // S_FATSHOTX2 + {SPR_MISL,32771,4,{NULL},S_NULL,0,0}, // S_FATSHOTX3 + {SPR_FATT,0,15,{(actionf_p2)A_Look},S_FATT_STND2,0,0}, // S_FATT_STND + {SPR_FATT,1,15,{(actionf_p2)A_Look},S_FATT_STND,0,0}, // S_FATT_STND2 + {SPR_FATT,0,4,{(actionf_p2)A_Chase},S_FATT_RUN2,0,0}, // S_FATT_RUN1 + {SPR_FATT,0,4,{(actionf_p2)A_Chase},S_FATT_RUN3,0,0}, // S_FATT_RUN2 + {SPR_FATT,1,4,{(actionf_p2)A_Chase},S_FATT_RUN4,0,0}, // S_FATT_RUN3 + {SPR_FATT,1,4,{(actionf_p2)A_Chase},S_FATT_RUN5,0,0}, // S_FATT_RUN4 + {SPR_FATT,2,4,{(actionf_p2)A_Chase},S_FATT_RUN6,0,0}, // S_FATT_RUN5 + {SPR_FATT,2,4,{(actionf_p2)A_Chase},S_FATT_RUN7,0,0}, // S_FATT_RUN6 + {SPR_FATT,3,4,{(actionf_p2)A_Chase},S_FATT_RUN8,0,0}, // S_FATT_RUN7 + {SPR_FATT,3,4,{(actionf_p2)A_Chase},S_FATT_RUN9,0,0}, // S_FATT_RUN8 + {SPR_FATT,4,4,{(actionf_p2)A_Chase},S_FATT_RUN10,0,0}, // S_FATT_RUN9 + {SPR_FATT,4,4,{(actionf_p2)A_Chase},S_FATT_RUN11,0,0}, // S_FATT_RUN10 + {SPR_FATT,5,4,{(actionf_p2)A_Chase},S_FATT_RUN12,0,0}, // S_FATT_RUN11 + {SPR_FATT,5,4,{(actionf_p2)A_Chase},S_FATT_RUN1,0,0}, // S_FATT_RUN12 + {SPR_FATT,6,20,{(actionf_p2)A_FatRaise},S_FATT_ATK2,0,0}, // S_FATT_ATK1 + {SPR_FATT,32775,10,{(actionf_p2)A_FatAttack1},S_FATT_ATK3,0,0}, // S_FATT_ATK2 + {SPR_FATT,8,5,{(actionf_p2)A_FaceTarget},S_FATT_ATK4,0,0}, // S_FATT_ATK3 + {SPR_FATT,6,5,{(actionf_p2)A_FaceTarget},S_FATT_ATK5,0,0}, // S_FATT_ATK4 + {SPR_FATT,32775,10,{(actionf_p2)A_FatAttack2},S_FATT_ATK6,0,0}, // S_FATT_ATK5 + {SPR_FATT,8,5,{(actionf_p2)A_FaceTarget},S_FATT_ATK7,0,0}, // S_FATT_ATK6 + {SPR_FATT,6,5,{(actionf_p2)A_FaceTarget},S_FATT_ATK8,0,0}, // S_FATT_ATK7 + {SPR_FATT,32775,10,{(actionf_p2)A_FatAttack3},S_FATT_ATK9,0,0}, // S_FATT_ATK8 + {SPR_FATT,8,5,{(actionf_p2)A_FaceTarget},S_FATT_ATK10,0,0}, // S_FATT_ATK9 + {SPR_FATT,6,5,{(actionf_p2)A_FaceTarget},S_FATT_RUN1,0,0}, // S_FATT_ATK10 + {SPR_FATT,9,3,{NULL},S_FATT_PAIN2,0,0}, // S_FATT_PAIN + {SPR_FATT,9,3,{(actionf_p2)A_Pain},S_FATT_RUN1,0,0}, // S_FATT_PAIN2 + {SPR_FATT,10,6,{NULL},S_FATT_DIE2,0,0}, // S_FATT_DIE1 + {SPR_FATT,11,6,{(actionf_p2)A_Scream},S_FATT_DIE3,0,0}, // S_FATT_DIE2 + {SPR_FATT,12,6,{(actionf_p2)A_Fall},S_FATT_DIE4,0,0}, // S_FATT_DIE3 + {SPR_FATT,13,6,{NULL},S_FATT_DIE5,0,0}, // S_FATT_DIE4 + {SPR_FATT,14,6,{NULL},S_FATT_DIE6,0,0}, // S_FATT_DIE5 + {SPR_FATT,15,6,{NULL},S_FATT_DIE7,0,0}, // S_FATT_DIE6 + {SPR_FATT,16,6,{NULL},S_FATT_DIE8,0,0}, // S_FATT_DIE7 + {SPR_FATT,17,6,{NULL},S_FATT_DIE9,0,0}, // S_FATT_DIE8 + {SPR_FATT,18,6,{NULL},S_FATT_DIE10,0,0}, // S_FATT_DIE9 + {SPR_FATT,19,-1,{(actionf_p2)A_BossDeath},S_NULL,0,0}, // S_FATT_DIE10 + {SPR_FATT,17,5,{NULL},S_FATT_RAISE2,0,0}, // S_FATT_RAISE1 + {SPR_FATT,16,5,{NULL},S_FATT_RAISE3,0,0}, // S_FATT_RAISE2 + {SPR_FATT,15,5,{NULL},S_FATT_RAISE4,0,0}, // S_FATT_RAISE3 + {SPR_FATT,14,5,{NULL},S_FATT_RAISE5,0,0}, // S_FATT_RAISE4 + {SPR_FATT,13,5,{NULL},S_FATT_RAISE6,0,0}, // S_FATT_RAISE5 + {SPR_FATT,12,5,{NULL},S_FATT_RAISE7,0,0}, // S_FATT_RAISE6 + {SPR_FATT,11,5,{NULL},S_FATT_RAISE8,0,0}, // S_FATT_RAISE7 + {SPR_FATT,10,5,{NULL},S_FATT_RUN1,0,0}, // S_FATT_RAISE8 + {SPR_CPOS,0,10,{(actionf_p2)A_Look},S_CPOS_STND2,0,0}, // S_CPOS_STND + {SPR_CPOS,1,10,{(actionf_p2)A_Look},S_CPOS_STND,0,0}, // S_CPOS_STND2 + {SPR_CPOS,0,3,{(actionf_p2)A_Chase},S_CPOS_RUN2,0,0}, // S_CPOS_RUN1 + {SPR_CPOS,0,3,{(actionf_p2)A_Chase},S_CPOS_RUN3,0,0}, // S_CPOS_RUN2 + {SPR_CPOS,1,3,{(actionf_p2)A_Chase},S_CPOS_RUN4,0,0}, // S_CPOS_RUN3 + {SPR_CPOS,1,3,{(actionf_p2)A_Chase},S_CPOS_RUN5,0,0}, // S_CPOS_RUN4 + {SPR_CPOS,2,3,{(actionf_p2)A_Chase},S_CPOS_RUN6,0,0}, // S_CPOS_RUN5 + {SPR_CPOS,2,3,{(actionf_p2)A_Chase},S_CPOS_RUN7,0,0}, // S_CPOS_RUN6 + {SPR_CPOS,3,3,{(actionf_p2)A_Chase},S_CPOS_RUN8,0,0}, // S_CPOS_RUN7 + {SPR_CPOS,3,3,{(actionf_p2)A_Chase},S_CPOS_RUN1,0,0}, // S_CPOS_RUN8 + {SPR_CPOS,4,10,{(actionf_p2)A_FaceTarget},S_CPOS_ATK2,0,0}, // S_CPOS_ATK1 + {SPR_CPOS,32773,4,{(actionf_p2)A_CPosAttack},S_CPOS_ATK3,0,0}, // S_CPOS_ATK2 + {SPR_CPOS,32772,4,{(actionf_p2)A_CPosAttack},S_CPOS_ATK4,0,0}, // S_CPOS_ATK3 + {SPR_CPOS,5,1,{(actionf_p2)A_CPosRefire},S_CPOS_ATK2,0,0}, // S_CPOS_ATK4 + {SPR_CPOS,6,3,{NULL},S_CPOS_PAIN2,0,0}, // S_CPOS_PAIN + {SPR_CPOS,6,3,{(actionf_p2)A_Pain},S_CPOS_RUN1,0,0}, // S_CPOS_PAIN2 + {SPR_CPOS,7,5,{NULL},S_CPOS_DIE2,0,0}, // S_CPOS_DIE1 + {SPR_CPOS,8,5,{(actionf_p2)A_Scream},S_CPOS_DIE3,0,0}, // S_CPOS_DIE2 + {SPR_CPOS,9,5,{(actionf_p2)A_Fall},S_CPOS_DIE4,0,0}, // S_CPOS_DIE3 + {SPR_CPOS,10,5,{NULL},S_CPOS_DIE5,0,0}, // S_CPOS_DIE4 + {SPR_CPOS,11,5,{NULL},S_CPOS_DIE6,0,0}, // S_CPOS_DIE5 + {SPR_CPOS,12,5,{NULL},S_CPOS_DIE7,0,0}, // S_CPOS_DIE6 + {SPR_CPOS,13,-1,{NULL},S_NULL,0,0}, // S_CPOS_DIE7 + {SPR_CPOS,14,5,{NULL},S_CPOS_XDIE2,0,0}, // S_CPOS_XDIE1 + {SPR_CPOS,15,5,{(actionf_p2)A_XScream},S_CPOS_XDIE3,0,0}, // S_CPOS_XDIE2 + {SPR_CPOS,16,5,{(actionf_p2)A_Fall},S_CPOS_XDIE4,0,0}, // S_CPOS_XDIE3 + {SPR_CPOS,17,5,{NULL},S_CPOS_XDIE5,0,0}, // S_CPOS_XDIE4 + {SPR_CPOS,18,5,{NULL},S_CPOS_XDIE6,0,0}, // S_CPOS_XDIE5 + {SPR_CPOS,19,-1,{NULL},S_NULL,0,0}, // S_CPOS_XDIE6 + {SPR_CPOS,13,5,{NULL},S_CPOS_RAISE2,0,0}, // S_CPOS_RAISE1 + {SPR_CPOS,12,5,{NULL},S_CPOS_RAISE3,0,0}, // S_CPOS_RAISE2 + {SPR_CPOS,11,5,{NULL},S_CPOS_RAISE4,0,0}, // S_CPOS_RAISE3 + {SPR_CPOS,10,5,{NULL},S_CPOS_RAISE5,0,0}, // S_CPOS_RAISE4 + {SPR_CPOS,9,5,{NULL},S_CPOS_RAISE6,0,0}, // S_CPOS_RAISE5 + {SPR_CPOS,8,5,{NULL},S_CPOS_RAISE7,0,0}, // S_CPOS_RAISE6 + {SPR_CPOS,7,5,{NULL},S_CPOS_RUN1,0,0}, // S_CPOS_RAISE7 + {SPR_TROO,0,10,{(actionf_p2)A_Look},S_TROO_STND2,0,0}, // S_TROO_STND + {SPR_TROO,1,10,{(actionf_p2)A_Look},S_TROO_STND,0,0}, // S_TROO_STND2 + {SPR_TROO,0,3,{(actionf_p2)A_Chase},S_TROO_RUN2,0,0}, // S_TROO_RUN1 + {SPR_TROO,0,3,{(actionf_p2)A_Chase},S_TROO_RUN3,0,0}, // S_TROO_RUN2 + {SPR_TROO,1,3,{(actionf_p2)A_Chase},S_TROO_RUN4,0,0}, // S_TROO_RUN3 + {SPR_TROO,1,3,{(actionf_p2)A_Chase},S_TROO_RUN5,0,0}, // S_TROO_RUN4 + {SPR_TROO,2,3,{(actionf_p2)A_Chase},S_TROO_RUN6,0,0}, // S_TROO_RUN5 + {SPR_TROO,2,3,{(actionf_p2)A_Chase},S_TROO_RUN7,0,0}, // S_TROO_RUN6 + {SPR_TROO,3,3,{(actionf_p2)A_Chase},S_TROO_RUN8,0,0}, // S_TROO_RUN7 + {SPR_TROO,3,3,{(actionf_p2)A_Chase},S_TROO_RUN1,0,0}, // S_TROO_RUN8 + {SPR_TROO,4,8,{(actionf_p2)A_FaceTarget},S_TROO_ATK2,0,0}, // S_TROO_ATK1 + {SPR_TROO,5,8,{(actionf_p2)A_FaceTarget},S_TROO_ATK3,0,0}, // S_TROO_ATK2 + {SPR_TROO,6,6,{(actionf_p2)A_TroopAttack},S_TROO_RUN1,0,0}, // S_TROO_ATK3 + {SPR_TROO,7,2,{NULL},S_TROO_PAIN2,0,0}, // S_TROO_PAIN + {SPR_TROO,7,2,{(actionf_p2)A_Pain},S_TROO_RUN1,0,0}, // S_TROO_PAIN2 + {SPR_TROO,8,8,{NULL},S_TROO_DIE2,0,0}, // S_TROO_DIE1 + {SPR_TROO,9,8,{(actionf_p2)A_Scream},S_TROO_DIE3,0,0}, // S_TROO_DIE2 + {SPR_TROO,10,6,{NULL},S_TROO_DIE4,0,0}, // S_TROO_DIE3 + {SPR_TROO,11,6,{(actionf_p2)A_Fall},S_TROO_DIE5,0,0}, // S_TROO_DIE4 + {SPR_TROO,12,-1,{NULL},S_NULL,0,0}, // S_TROO_DIE5 + {SPR_TROO,13,5,{NULL},S_TROO_XDIE2,0,0}, // S_TROO_XDIE1 + {SPR_TROO,14,5,{(actionf_p2)A_XScream},S_TROO_XDIE3,0,0}, // S_TROO_XDIE2 + {SPR_TROO,15,5,{NULL},S_TROO_XDIE4,0,0}, // S_TROO_XDIE3 + {SPR_TROO,16,5,{(actionf_p2)A_Fall},S_TROO_XDIE5,0,0}, // S_TROO_XDIE4 + {SPR_TROO,17,5,{NULL},S_TROO_XDIE6,0,0}, // S_TROO_XDIE5 + {SPR_TROO,18,5,{NULL},S_TROO_XDIE7,0,0}, // S_TROO_XDIE6 + {SPR_TROO,19,5,{NULL},S_TROO_XDIE8,0,0}, // S_TROO_XDIE7 + {SPR_TROO,20,-1,{NULL},S_NULL,0,0}, // S_TROO_XDIE8 + {SPR_TROO,12,8,{NULL},S_TROO_RAISE2,0,0}, // S_TROO_RAISE1 + {SPR_TROO,11,8,{NULL},S_TROO_RAISE3,0,0}, // S_TROO_RAISE2 + {SPR_TROO,10,6,{NULL},S_TROO_RAISE4,0,0}, // S_TROO_RAISE3 + {SPR_TROO,9,6,{NULL},S_TROO_RAISE5,0,0}, // S_TROO_RAISE4 + {SPR_TROO,8,6,{NULL},S_TROO_RUN1,0,0}, // S_TROO_RAISE5 + {SPR_SARG,0,10,{(actionf_p2)A_Look},S_SARG_STND2,0,0}, // S_SARG_STND + {SPR_SARG,1,10,{(actionf_p2)A_Look},S_SARG_STND,0,0}, // S_SARG_STND2 + {SPR_SARG,0,2,{(actionf_p2)A_Chase},S_SARG_RUN2,0,0}, // S_SARG_RUN1 + {SPR_SARG,0,2,{(actionf_p2)A_Chase},S_SARG_RUN3,0,0}, // S_SARG_RUN2 + {SPR_SARG,1,2,{(actionf_p2)A_Chase},S_SARG_RUN4,0,0}, // S_SARG_RUN3 + {SPR_SARG,1,2,{(actionf_p2)A_Chase},S_SARG_RUN5,0,0}, // S_SARG_RUN4 + {SPR_SARG,2,2,{(actionf_p2)A_Chase},S_SARG_RUN6,0,0}, // S_SARG_RUN5 + {SPR_SARG,2,2,{(actionf_p2)A_Chase},S_SARG_RUN7,0,0}, // S_SARG_RUN6 + {SPR_SARG,3,2,{(actionf_p2)A_Chase},S_SARG_RUN8,0,0}, // S_SARG_RUN7 + {SPR_SARG,3,2,{(actionf_p2)A_Chase},S_SARG_RUN1,0,0}, // S_SARG_RUN8 + {SPR_SARG,4,8,{(actionf_p2)A_FaceTarget},S_SARG_ATK2,0,0}, // S_SARG_ATK1 + {SPR_SARG,5,8,{(actionf_p2)A_FaceTarget},S_SARG_ATK3,0,0}, // S_SARG_ATK2 + {SPR_SARG,6,8,{(actionf_p2)A_SargAttack},S_SARG_RUN1,0,0}, // S_SARG_ATK3 + {SPR_SARG,7,2,{NULL},S_SARG_PAIN2,0,0}, // S_SARG_PAIN + {SPR_SARG,7,2,{(actionf_p2)A_Pain},S_SARG_RUN1,0,0}, // S_SARG_PAIN2 + {SPR_SARG,8,8,{NULL},S_SARG_DIE2,0,0}, // S_SARG_DIE1 + {SPR_SARG,9,8,{(actionf_p2)A_Scream},S_SARG_DIE3,0,0}, // S_SARG_DIE2 + {SPR_SARG,10,4,{NULL},S_SARG_DIE4,0,0}, // S_SARG_DIE3 + {SPR_SARG,11,4,{(actionf_p2)A_Fall},S_SARG_DIE5,0,0}, // S_SARG_DIE4 + {SPR_SARG,12,4,{NULL},S_SARG_DIE6,0,0}, // S_SARG_DIE5 + {SPR_SARG,13,-1,{NULL},S_NULL,0,0}, // S_SARG_DIE6 + {SPR_SARG,13,5,{NULL},S_SARG_RAISE2,0,0}, // S_SARG_RAISE1 + {SPR_SARG,12,5,{NULL},S_SARG_RAISE3,0,0}, // S_SARG_RAISE2 + {SPR_SARG,11,5,{NULL},S_SARG_RAISE4,0,0}, // S_SARG_RAISE3 + {SPR_SARG,10,5,{NULL},S_SARG_RAISE5,0,0}, // S_SARG_RAISE4 + {SPR_SARG,9,5,{NULL},S_SARG_RAISE6,0,0}, // S_SARG_RAISE5 + {SPR_SARG,8,5,{NULL},S_SARG_RUN1,0,0}, // S_SARG_RAISE6 + {SPR_HEAD,0,10,{(actionf_p2)A_Look},S_HEAD_STND,0,0}, // S_HEAD_STND + {SPR_HEAD,0,3,{(actionf_p2)A_Chase},S_HEAD_RUN1,0,0}, // S_HEAD_RUN1 + {SPR_HEAD,1,5,{(actionf_p2)A_FaceTarget},S_HEAD_ATK2,0,0}, // S_HEAD_ATK1 + {SPR_HEAD,2,5,{(actionf_p2)A_FaceTarget},S_HEAD_ATK3,0,0}, // S_HEAD_ATK2 + {SPR_HEAD,32771,5,{(actionf_p2)A_HeadAttack},S_HEAD_RUN1,0,0}, // S_HEAD_ATK3 + {SPR_HEAD,4,3,{NULL},S_HEAD_PAIN2,0,0}, // S_HEAD_PAIN + {SPR_HEAD,4,3,{(actionf_p2)A_Pain},S_HEAD_PAIN3,0,0}, // S_HEAD_PAIN2 + {SPR_HEAD,5,6,{NULL},S_HEAD_RUN1,0,0}, // S_HEAD_PAIN3 + {SPR_HEAD,6,8,{NULL},S_HEAD_DIE2,0,0}, // S_HEAD_DIE1 + {SPR_HEAD,7,8,{(actionf_p2)A_Scream},S_HEAD_DIE3,0,0}, // S_HEAD_DIE2 + {SPR_HEAD,8,8,{NULL},S_HEAD_DIE4,0,0}, // S_HEAD_DIE3 + {SPR_HEAD,9,8,{NULL},S_HEAD_DIE5,0,0}, // S_HEAD_DIE4 + {SPR_HEAD,10,8,{(actionf_p2)A_Fall},S_HEAD_DIE6,0,0}, // S_HEAD_DIE5 + {SPR_HEAD,11,-1,{NULL},S_NULL,0,0}, // S_HEAD_DIE6 + {SPR_HEAD,11,8,{NULL},S_HEAD_RAISE2,0,0}, // S_HEAD_RAISE1 + {SPR_HEAD,10,8,{NULL},S_HEAD_RAISE3,0,0}, // S_HEAD_RAISE2 + {SPR_HEAD,9,8,{NULL},S_HEAD_RAISE4,0,0}, // S_HEAD_RAISE3 + {SPR_HEAD,8,8,{NULL},S_HEAD_RAISE5,0,0}, // S_HEAD_RAISE4 + {SPR_HEAD,7,8,{NULL},S_HEAD_RAISE6,0,0}, // S_HEAD_RAISE5 + {SPR_HEAD,6,8,{NULL},S_HEAD_RUN1,0,0}, // S_HEAD_RAISE6 + {SPR_BAL7,32768,4,{NULL},S_BRBALL2,0,0}, // S_BRBALL1 + {SPR_BAL7,32769,4,{NULL},S_BRBALL1,0,0}, // S_BRBALL2 + {SPR_BAL7,32770,6,{NULL},S_BRBALLX2,0,0}, // S_BRBALLX1 + {SPR_BAL7,32771,6,{NULL},S_BRBALLX3,0,0}, // S_BRBALLX2 + {SPR_BAL7,32772,6,{NULL},S_NULL,0,0}, // S_BRBALLX3 + {SPR_BOSS,0,10,{(actionf_p2)A_Look},S_BOSS_STND2,0,0}, // S_BOSS_STND + {SPR_BOSS,1,10,{(actionf_p2)A_Look},S_BOSS_STND,0,0}, // S_BOSS_STND2 + {SPR_BOSS,0,3,{(actionf_p2)A_Chase},S_BOSS_RUN2,0,0}, // S_BOSS_RUN1 + {SPR_BOSS,0,3,{(actionf_p2)A_Chase},S_BOSS_RUN3,0,0}, // S_BOSS_RUN2 + {SPR_BOSS,1,3,{(actionf_p2)A_Chase},S_BOSS_RUN4,0,0}, // S_BOSS_RUN3 + {SPR_BOSS,1,3,{(actionf_p2)A_Chase},S_BOSS_RUN5,0,0}, // S_BOSS_RUN4 + {SPR_BOSS,2,3,{(actionf_p2)A_Chase},S_BOSS_RUN6,0,0}, // S_BOSS_RUN5 + {SPR_BOSS,2,3,{(actionf_p2)A_Chase},S_BOSS_RUN7,0,0}, // S_BOSS_RUN6 + {SPR_BOSS,3,3,{(actionf_p2)A_Chase},S_BOSS_RUN8,0,0}, // S_BOSS_RUN7 + {SPR_BOSS,3,3,{(actionf_p2)A_Chase},S_BOSS_RUN1,0,0}, // S_BOSS_RUN8 + {SPR_BOSS,4,8,{(actionf_p2)A_FaceTarget},S_BOSS_ATK2,0,0}, // S_BOSS_ATK1 + {SPR_BOSS,5,8,{(actionf_p2)A_FaceTarget},S_BOSS_ATK3,0,0}, // S_BOSS_ATK2 + {SPR_BOSS,6,8,{(actionf_p2)A_BruisAttack},S_BOSS_RUN1,0,0}, // S_BOSS_ATK3 + {SPR_BOSS,7,2,{NULL},S_BOSS_PAIN2,0,0}, // S_BOSS_PAIN + {SPR_BOSS,7,2,{(actionf_p2)A_Pain},S_BOSS_RUN1,0,0}, // S_BOSS_PAIN2 + {SPR_BOSS,8,8,{NULL},S_BOSS_DIE2,0,0}, // S_BOSS_DIE1 + {SPR_BOSS,9,8,{(actionf_p2)A_Scream},S_BOSS_DIE3,0,0}, // S_BOSS_DIE2 + {SPR_BOSS,10,8,{NULL},S_BOSS_DIE4,0,0}, // S_BOSS_DIE3 + {SPR_BOSS,11,8,{(actionf_p2)A_Fall},S_BOSS_DIE5,0,0}, // S_BOSS_DIE4 + {SPR_BOSS,12,8,{NULL},S_BOSS_DIE6,0,0}, // S_BOSS_DIE5 + {SPR_BOSS,13,8,{NULL},S_BOSS_DIE7,0,0}, // S_BOSS_DIE6 + {SPR_BOSS,14,-1,{(actionf_p2)A_BossDeath},S_NULL,0,0}, // S_BOSS_DIE7 + {SPR_BOSS,14,8,{NULL},S_BOSS_RAISE2,0,0}, // S_BOSS_RAISE1 + {SPR_BOSS,13,8,{NULL},S_BOSS_RAISE3,0,0}, // S_BOSS_RAISE2 + {SPR_BOSS,12,8,{NULL},S_BOSS_RAISE4,0,0}, // S_BOSS_RAISE3 + {SPR_BOSS,11,8,{NULL},S_BOSS_RAISE5,0,0}, // S_BOSS_RAISE4 + {SPR_BOSS,10,8,{NULL},S_BOSS_RAISE6,0,0}, // S_BOSS_RAISE5 + {SPR_BOSS,9,8,{NULL},S_BOSS_RAISE7,0,0}, // S_BOSS_RAISE6 + {SPR_BOSS,8,8,{NULL},S_BOSS_RUN1,0,0}, // S_BOSS_RAISE7 + {SPR_BOS2,0,10,{(actionf_p2)A_Look},S_BOS2_STND2,0,0}, // S_BOS2_STND + {SPR_BOS2,1,10,{(actionf_p2)A_Look},S_BOS2_STND,0,0}, // S_BOS2_STND2 + {SPR_BOS2,0,3,{(actionf_p2)A_Chase},S_BOS2_RUN2,0,0}, // S_BOS2_RUN1 + {SPR_BOS2,0,3,{(actionf_p2)A_Chase},S_BOS2_RUN3,0,0}, // S_BOS2_RUN2 + {SPR_BOS2,1,3,{(actionf_p2)A_Chase},S_BOS2_RUN4,0,0}, // S_BOS2_RUN3 + {SPR_BOS2,1,3,{(actionf_p2)A_Chase},S_BOS2_RUN5,0,0}, // S_BOS2_RUN4 + {SPR_BOS2,2,3,{(actionf_p2)A_Chase},S_BOS2_RUN6,0,0}, // S_BOS2_RUN5 + {SPR_BOS2,2,3,{(actionf_p2)A_Chase},S_BOS2_RUN7,0,0}, // S_BOS2_RUN6 + {SPR_BOS2,3,3,{(actionf_p2)A_Chase},S_BOS2_RUN8,0,0}, // S_BOS2_RUN7 + {SPR_BOS2,3,3,{(actionf_p2)A_Chase},S_BOS2_RUN1,0,0}, // S_BOS2_RUN8 + {SPR_BOS2,4,8,{(actionf_p2)A_FaceTarget},S_BOS2_ATK2,0,0}, // S_BOS2_ATK1 + {SPR_BOS2,5,8,{(actionf_p2)A_FaceTarget},S_BOS2_ATK3,0,0}, // S_BOS2_ATK2 + {SPR_BOS2,6,8,{(actionf_p2)A_BruisAttack},S_BOS2_RUN1,0,0}, // S_BOS2_ATK3 + {SPR_BOS2,7,2,{NULL},S_BOS2_PAIN2,0,0}, // S_BOS2_PAIN + {SPR_BOS2,7,2,{(actionf_p2)A_Pain},S_BOS2_RUN1,0,0}, // S_BOS2_PAIN2 + {SPR_BOS2,8,8,{NULL},S_BOS2_DIE2,0,0}, // S_BOS2_DIE1 + {SPR_BOS2,9,8,{(actionf_p2)A_Scream},S_BOS2_DIE3,0,0}, // S_BOS2_DIE2 + {SPR_BOS2,10,8,{NULL},S_BOS2_DIE4,0,0}, // S_BOS2_DIE3 + {SPR_BOS2,11,8,{(actionf_p2)A_Fall},S_BOS2_DIE5,0,0}, // S_BOS2_DIE4 + {SPR_BOS2,12,8,{NULL},S_BOS2_DIE6,0,0}, // S_BOS2_DIE5 + {SPR_BOS2,13,8,{NULL},S_BOS2_DIE7,0,0}, // S_BOS2_DIE6 + {SPR_BOS2,14,-1,{NULL},S_NULL,0,0}, // S_BOS2_DIE7 + {SPR_BOS2,14,8,{NULL},S_BOS2_RAISE2,0,0}, // S_BOS2_RAISE1 + {SPR_BOS2,13,8,{NULL},S_BOS2_RAISE3,0,0}, // S_BOS2_RAISE2 + {SPR_BOS2,12,8,{NULL},S_BOS2_RAISE4,0,0}, // S_BOS2_RAISE3 + {SPR_BOS2,11,8,{NULL},S_BOS2_RAISE5,0,0}, // S_BOS2_RAISE4 + {SPR_BOS2,10,8,{NULL},S_BOS2_RAISE6,0,0}, // S_BOS2_RAISE5 + {SPR_BOS2,9,8,{NULL},S_BOS2_RAISE7,0,0}, // S_BOS2_RAISE6 + {SPR_BOS2,8,8,{NULL},S_BOS2_RUN1,0,0}, // S_BOS2_RAISE7 + {SPR_SKUL,32768,10,{(actionf_p2)A_Look},S_SKULL_STND2,0,0}, // S_SKULL_STND + {SPR_SKUL,32769,10,{(actionf_p2)A_Look},S_SKULL_STND,0,0}, // S_SKULL_STND2 + {SPR_SKUL,32768,6,{(actionf_p2)A_Chase},S_SKULL_RUN2,0,0}, // S_SKULL_RUN1 + {SPR_SKUL,32769,6,{(actionf_p2)A_Chase},S_SKULL_RUN1,0,0}, // S_SKULL_RUN2 + {SPR_SKUL,32770,10,{(actionf_p2)A_FaceTarget},S_SKULL_ATK2,0,0}, // S_SKULL_ATK1 + {SPR_SKUL,32771,4,{(actionf_p2)A_SkullAttack},S_SKULL_ATK3,0,0}, // S_SKULL_ATK2 + {SPR_SKUL,32770,4,{NULL},S_SKULL_ATK4,0,0}, // S_SKULL_ATK3 + {SPR_SKUL,32771,4,{NULL},S_SKULL_ATK3,0,0}, // S_SKULL_ATK4 + {SPR_SKUL,32772,3,{NULL},S_SKULL_PAIN2,0,0}, // S_SKULL_PAIN + {SPR_SKUL,32772,3,{(actionf_p2)A_Pain},S_SKULL_RUN1,0,0}, // S_SKULL_PAIN2 + {SPR_SKUL,32773,6,{NULL},S_SKULL_DIE2,0,0}, // S_SKULL_DIE1 + {SPR_SKUL,32774,6,{(actionf_p2)A_Scream},S_SKULL_DIE3,0,0}, // S_SKULL_DIE2 + {SPR_SKUL,32775,6,{NULL},S_SKULL_DIE4,0,0}, // S_SKULL_DIE3 + {SPR_SKUL,32776,6,{(actionf_p2)A_Fall},S_SKULL_DIE5,0,0}, // S_SKULL_DIE4 + {SPR_SKUL,9,6,{NULL},S_SKULL_DIE6,0,0}, // S_SKULL_DIE5 + {SPR_SKUL,10,6,{NULL},S_NULL,0,0}, // S_SKULL_DIE6 + {SPR_SPID,0,10,{(actionf_p2)A_Look},S_SPID_STND2,0,0}, // S_SPID_STND + {SPR_SPID,1,10,{(actionf_p2)A_Look},S_SPID_STND,0,0}, // S_SPID_STND2 + {SPR_SPID,0,3,{(actionf_p2)A_Metal},S_SPID_RUN2,0,0}, // S_SPID_RUN1 + {SPR_SPID,0,3,{(actionf_p2)A_Chase},S_SPID_RUN3,0,0}, // S_SPID_RUN2 + {SPR_SPID,1,3,{(actionf_p2)A_Chase},S_SPID_RUN4,0,0}, // S_SPID_RUN3 + {SPR_SPID,1,3,{(actionf_p2)A_Chase},S_SPID_RUN5,0,0}, // S_SPID_RUN4 + {SPR_SPID,2,3,{(actionf_p2)A_Metal},S_SPID_RUN6,0,0}, // S_SPID_RUN5 + {SPR_SPID,2,3,{(actionf_p2)A_Chase},S_SPID_RUN7,0,0}, // S_SPID_RUN6 + {SPR_SPID,3,3,{(actionf_p2)A_Chase},S_SPID_RUN8,0,0}, // S_SPID_RUN7 + {SPR_SPID,3,3,{(actionf_p2)A_Chase},S_SPID_RUN9,0,0}, // S_SPID_RUN8 + {SPR_SPID,4,3,{(actionf_p2)A_Metal},S_SPID_RUN10,0,0}, // S_SPID_RUN9 + {SPR_SPID,4,3,{(actionf_p2)A_Chase},S_SPID_RUN11,0,0}, // S_SPID_RUN10 + {SPR_SPID,5,3,{(actionf_p2)A_Chase},S_SPID_RUN12,0,0}, // S_SPID_RUN11 + {SPR_SPID,5,3,{(actionf_p2)A_Chase},S_SPID_RUN1,0,0}, // S_SPID_RUN12 + {SPR_SPID,32768,20,{(actionf_p2)A_FaceTarget},S_SPID_ATK2,0,0}, // S_SPID_ATK1 + {SPR_SPID,32774,4,{(actionf_p2)A_SPosAttack},S_SPID_ATK3,0,0}, // S_SPID_ATK2 + {SPR_SPID,32775,4,{(actionf_p2)A_SPosAttack},S_SPID_ATK4,0,0}, // S_SPID_ATK3 + {SPR_SPID,32775,1,{(actionf_p2)A_SpidRefire},S_SPID_ATK2,0,0}, // S_SPID_ATK4 + {SPR_SPID,8,3,{NULL},S_SPID_PAIN2,0,0}, // S_SPID_PAIN + {SPR_SPID,8,3,{(actionf_p2)A_Pain},S_SPID_RUN1,0,0}, // S_SPID_PAIN2 + {SPR_SPID,9,20,{(actionf_p2)A_Scream},S_SPID_DIE2,0,0}, // S_SPID_DIE1 + {SPR_SPID,10,10,{(actionf_p2)A_Fall},S_SPID_DIE3,0,0}, // S_SPID_DIE2 + {SPR_SPID,11,10,{NULL},S_SPID_DIE4,0,0}, // S_SPID_DIE3 + {SPR_SPID,12,10,{NULL},S_SPID_DIE5,0,0}, // S_SPID_DIE4 + {SPR_SPID,13,10,{NULL},S_SPID_DIE6,0,0}, // S_SPID_DIE5 + {SPR_SPID,14,10,{NULL},S_SPID_DIE7,0,0}, // S_SPID_DIE6 + {SPR_SPID,15,10,{NULL},S_SPID_DIE8,0,0}, // S_SPID_DIE7 + {SPR_SPID,16,10,{NULL},S_SPID_DIE9,0,0}, // S_SPID_DIE8 + {SPR_SPID,17,10,{NULL},S_SPID_DIE10,0,0}, // S_SPID_DIE9 + {SPR_SPID,18,30,{NULL},S_SPID_DIE11,0,0}, // S_SPID_DIE10 + {SPR_SPID,18,-1,{(actionf_p2)A_BossDeath},S_NULL,0,0}, // S_SPID_DIE11 + {SPR_BSPI,0,10,{(actionf_p2)A_Look},S_BSPI_STND2,0,0}, // S_BSPI_STND + {SPR_BSPI,1,10,{(actionf_p2)A_Look},S_BSPI_STND,0,0}, // S_BSPI_STND2 + {SPR_BSPI,0,20,{NULL},S_BSPI_RUN1,0,0}, // S_BSPI_SIGHT + {SPR_BSPI,0,3,{(actionf_p2)A_BabyMetal},S_BSPI_RUN2,0,0}, // S_BSPI_RUN1 + {SPR_BSPI,0,3,{(actionf_p2)A_Chase},S_BSPI_RUN3,0,0}, // S_BSPI_RUN2 + {SPR_BSPI,1,3,{(actionf_p2)A_Chase},S_BSPI_RUN4,0,0}, // S_BSPI_RUN3 + {SPR_BSPI,1,3,{(actionf_p2)A_Chase},S_BSPI_RUN5,0,0}, // S_BSPI_RUN4 + {SPR_BSPI,2,3,{(actionf_p2)A_Chase},S_BSPI_RUN6,0,0}, // S_BSPI_RUN5 + {SPR_BSPI,2,3,{(actionf_p2)A_Chase},S_BSPI_RUN7,0,0}, // S_BSPI_RUN6 + {SPR_BSPI,3,3,{(actionf_p2)A_BabyMetal},S_BSPI_RUN8,0,0}, // S_BSPI_RUN7 + {SPR_BSPI,3,3,{(actionf_p2)A_Chase},S_BSPI_RUN9,0,0}, // S_BSPI_RUN8 + {SPR_BSPI,4,3,{(actionf_p2)A_Chase},S_BSPI_RUN10,0,0}, // S_BSPI_RUN9 + {SPR_BSPI,4,3,{(actionf_p2)A_Chase},S_BSPI_RUN11,0,0}, // S_BSPI_RUN10 + {SPR_BSPI,5,3,{(actionf_p2)A_Chase},S_BSPI_RUN12,0,0}, // S_BSPI_RUN11 + {SPR_BSPI,5,3,{(actionf_p2)A_Chase},S_BSPI_RUN1,0,0}, // S_BSPI_RUN12 + {SPR_BSPI,32768,20,{(actionf_p2)A_FaceTarget},S_BSPI_ATK2,0,0}, // S_BSPI_ATK1 + {SPR_BSPI,32774,4,{(actionf_p2)A_BspiAttack},S_BSPI_ATK3,0,0}, // S_BSPI_ATK2 + {SPR_BSPI,32775,4,{NULL},S_BSPI_ATK4,0,0}, // S_BSPI_ATK3 + {SPR_BSPI,32775,1,{(actionf_p2)A_SpidRefire},S_BSPI_ATK2,0,0}, // S_BSPI_ATK4 + {SPR_BSPI,8,3,{NULL},S_BSPI_PAIN2,0,0}, // S_BSPI_PAIN + {SPR_BSPI,8,3,{(actionf_p2)A_Pain},S_BSPI_RUN1,0,0}, // S_BSPI_PAIN2 + {SPR_BSPI,9,20,{(actionf_p2)A_Scream},S_BSPI_DIE2,0,0}, // S_BSPI_DIE1 + {SPR_BSPI,10,7,{(actionf_p2)A_Fall},S_BSPI_DIE3,0,0}, // S_BSPI_DIE2 + {SPR_BSPI,11,7,{NULL},S_BSPI_DIE4,0,0}, // S_BSPI_DIE3 + {SPR_BSPI,12,7,{NULL},S_BSPI_DIE5,0,0}, // S_BSPI_DIE4 + {SPR_BSPI,13,7,{NULL},S_BSPI_DIE6,0,0}, // S_BSPI_DIE5 + {SPR_BSPI,14,7,{NULL},S_BSPI_DIE7,0,0}, // S_BSPI_DIE6 + {SPR_BSPI,15,-1,{(actionf_p2)A_BossDeath},S_NULL,0,0}, // S_BSPI_DIE7 + {SPR_BSPI,15,5,{NULL},S_BSPI_RAISE2,0,0}, // S_BSPI_RAISE1 + {SPR_BSPI,14,5,{NULL},S_BSPI_RAISE3,0,0}, // S_BSPI_RAISE2 + {SPR_BSPI,13,5,{NULL},S_BSPI_RAISE4,0,0}, // S_BSPI_RAISE3 + {SPR_BSPI,12,5,{NULL},S_BSPI_RAISE5,0,0}, // S_BSPI_RAISE4 + {SPR_BSPI,11,5,{NULL},S_BSPI_RAISE6,0,0}, // S_BSPI_RAISE5 + {SPR_BSPI,10,5,{NULL},S_BSPI_RAISE7,0,0}, // S_BSPI_RAISE6 + {SPR_BSPI,9,5,{NULL},S_BSPI_RUN1,0,0}, // S_BSPI_RAISE7 + {SPR_APLS,32768,5,{NULL},S_ARACH_PLAZ2,0,0}, // S_ARACH_PLAZ + {SPR_APLS,32769,5,{NULL},S_ARACH_PLAZ,0,0}, // S_ARACH_PLAZ2 + {SPR_APBX,32768,5,{NULL},S_ARACH_PLEX2,0,0}, // S_ARACH_PLEX + {SPR_APBX,32769,5,{NULL},S_ARACH_PLEX3,0,0}, // S_ARACH_PLEX2 + {SPR_APBX,32770,5,{NULL},S_ARACH_PLEX4,0,0}, // S_ARACH_PLEX3 + {SPR_APBX,32771,5,{NULL},S_ARACH_PLEX5,0,0}, // S_ARACH_PLEX4 + {SPR_APBX,32772,5,{NULL},S_NULL,0,0}, // S_ARACH_PLEX5 + {SPR_CYBR,0,10,{(actionf_p2)A_Look},S_CYBER_STND2,0,0}, // S_CYBER_STND + {SPR_CYBR,1,10,{(actionf_p2)A_Look},S_CYBER_STND,0,0}, // S_CYBER_STND2 + {SPR_CYBR,0,3,{(actionf_p2)A_Hoof},S_CYBER_RUN2,0,0}, // S_CYBER_RUN1 + {SPR_CYBR,0,3,{(actionf_p2)A_Chase},S_CYBER_RUN3,0,0}, // S_CYBER_RUN2 + {SPR_CYBR,1,3,{(actionf_p2)A_Chase},S_CYBER_RUN4,0,0}, // S_CYBER_RUN3 + {SPR_CYBR,1,3,{(actionf_p2)A_Chase},S_CYBER_RUN5,0,0}, // S_CYBER_RUN4 + {SPR_CYBR,2,3,{(actionf_p2)A_Chase},S_CYBER_RUN6,0,0}, // S_CYBER_RUN5 + {SPR_CYBR,2,3,{(actionf_p2)A_Chase},S_CYBER_RUN7,0,0}, // S_CYBER_RUN6 + {SPR_CYBR,3,3,{(actionf_p2)A_Metal},S_CYBER_RUN8,0,0}, // S_CYBER_RUN7 + {SPR_CYBR,3,3,{(actionf_p2)A_Chase},S_CYBER_RUN1,0,0}, // S_CYBER_RUN8 + {SPR_CYBR,4,6,{(actionf_p2)A_FaceTarget},S_CYBER_ATK2,0,0}, // S_CYBER_ATK1 + {SPR_CYBR,5,12,{(actionf_p2)A_CyberAttack},S_CYBER_ATK3,0,0}, // S_CYBER_ATK2 + {SPR_CYBR,4,12,{(actionf_p2)A_FaceTarget},S_CYBER_ATK4,0,0}, // S_CYBER_ATK3 + {SPR_CYBR,5,12,{(actionf_p2)A_CyberAttack},S_CYBER_ATK5,0,0}, // S_CYBER_ATK4 + {SPR_CYBR,4,12,{(actionf_p2)A_FaceTarget},S_CYBER_ATK6,0,0}, // S_CYBER_ATK5 + {SPR_CYBR,5,12,{(actionf_p2)A_CyberAttack},S_CYBER_RUN1,0,0}, // S_CYBER_ATK6 + {SPR_CYBR,6,10,{(actionf_p2)A_Pain},S_CYBER_RUN1,0,0}, // S_CYBER_PAIN + {SPR_CYBR,7,10,{NULL},S_CYBER_DIE2,0,0}, // S_CYBER_DIE1 + {SPR_CYBR,8,10,{(actionf_p2)A_Scream},S_CYBER_DIE3,0,0}, // S_CYBER_DIE2 + {SPR_CYBR,9,10,{NULL},S_CYBER_DIE4,0,0}, // S_CYBER_DIE3 + {SPR_CYBR,10,10,{NULL},S_CYBER_DIE5,0,0}, // S_CYBER_DIE4 + {SPR_CYBR,11,10,{NULL},S_CYBER_DIE6,0,0}, // S_CYBER_DIE5 + {SPR_CYBR,12,10,{(actionf_p2)A_Fall},S_CYBER_DIE7,0,0}, // S_CYBER_DIE6 + {SPR_CYBR,13,10,{NULL},S_CYBER_DIE8,0,0}, // S_CYBER_DIE7 + {SPR_CYBR,14,10,{NULL},S_CYBER_DIE9,0,0}, // S_CYBER_DIE8 + {SPR_CYBR,15,30,{NULL},S_CYBER_DIE10,0,0}, // S_CYBER_DIE9 + {SPR_CYBR,15,-1,{(actionf_p2)A_BossDeath},S_NULL,0,0}, // S_CYBER_DIE10 + {SPR_PAIN,0,10,{(actionf_p2)A_Look},S_PAIN_STND,0,0}, // S_PAIN_STND + {SPR_PAIN,0,3,{(actionf_p2)A_Chase},S_PAIN_RUN2,0,0}, // S_PAIN_RUN1 + {SPR_PAIN,0,3,{(actionf_p2)A_Chase},S_PAIN_RUN3,0,0}, // S_PAIN_RUN2 + {SPR_PAIN,1,3,{(actionf_p2)A_Chase},S_PAIN_RUN4,0,0}, // S_PAIN_RUN3 + {SPR_PAIN,1,3,{(actionf_p2)A_Chase},S_PAIN_RUN5,0,0}, // S_PAIN_RUN4 + {SPR_PAIN,2,3,{(actionf_p2)A_Chase},S_PAIN_RUN6,0,0}, // S_PAIN_RUN5 + {SPR_PAIN,2,3,{(actionf_p2)A_Chase},S_PAIN_RUN1,0,0}, // S_PAIN_RUN6 + {SPR_PAIN,3,5,{(actionf_p2)A_FaceTarget},S_PAIN_ATK2,0,0}, // S_PAIN_ATK1 + {SPR_PAIN,4,5,{(actionf_p2)A_FaceTarget},S_PAIN_ATK3,0,0}, // S_PAIN_ATK2 + {SPR_PAIN,32773,5,{(actionf_p2)A_FaceTarget},S_PAIN_ATK4,0,0}, // S_PAIN_ATK3 + {SPR_PAIN,32773,0,{(actionf_p2)A_PainAttack},S_PAIN_RUN1,0,0}, // S_PAIN_ATK4 + {SPR_PAIN,6,6,{NULL},S_PAIN_PAIN2,0,0}, // S_PAIN_PAIN + {SPR_PAIN,6,6,{(actionf_p2)A_Pain},S_PAIN_RUN1,0,0}, // S_PAIN_PAIN2 + {SPR_PAIN,32775,8,{NULL},S_PAIN_DIE2,0,0}, // S_PAIN_DIE1 + {SPR_PAIN,32776,8,{(actionf_p2)A_Scream},S_PAIN_DIE3,0,0}, // S_PAIN_DIE2 + {SPR_PAIN,32777,8,{NULL},S_PAIN_DIE4,0,0}, // S_PAIN_DIE3 + {SPR_PAIN,32778,8,{NULL},S_PAIN_DIE5,0,0}, // S_PAIN_DIE4 + {SPR_PAIN,32779,8,{(actionf_p2)A_PainDie},S_PAIN_DIE6,0,0}, // S_PAIN_DIE5 + {SPR_PAIN,32780,8,{NULL},S_NULL,0,0}, // S_PAIN_DIE6 + {SPR_PAIN,12,8,{NULL},S_PAIN_RAISE2,0,0}, // S_PAIN_RAISE1 + {SPR_PAIN,11,8,{NULL},S_PAIN_RAISE3,0,0}, // S_PAIN_RAISE2 + {SPR_PAIN,10,8,{NULL},S_PAIN_RAISE4,0,0}, // S_PAIN_RAISE3 + {SPR_PAIN,9,8,{NULL},S_PAIN_RAISE5,0,0}, // S_PAIN_RAISE4 + {SPR_PAIN,8,8,{NULL},S_PAIN_RAISE6,0,0}, // S_PAIN_RAISE5 + {SPR_PAIN,7,8,{NULL},S_PAIN_RUN1,0,0}, // S_PAIN_RAISE6 + {SPR_SSWV,0,10,{(actionf_p2)A_Look},S_SSWV_STND2,0,0}, // S_SSWV_STND + {SPR_SSWV,1,10,{(actionf_p2)A_Look},S_SSWV_STND,0,0}, // S_SSWV_STND2 + {SPR_SSWV,0,3,{(actionf_p2)A_Chase},S_SSWV_RUN2,0,0}, // S_SSWV_RUN1 + {SPR_SSWV,0,3,{(actionf_p2)A_Chase},S_SSWV_RUN3,0,0}, // S_SSWV_RUN2 + {SPR_SSWV,1,3,{(actionf_p2)A_Chase},S_SSWV_RUN4,0,0}, // S_SSWV_RUN3 + {SPR_SSWV,1,3,{(actionf_p2)A_Chase},S_SSWV_RUN5,0,0}, // S_SSWV_RUN4 + {SPR_SSWV,2,3,{(actionf_p2)A_Chase},S_SSWV_RUN6,0,0}, // S_SSWV_RUN5 + {SPR_SSWV,2,3,{(actionf_p2)A_Chase},S_SSWV_RUN7,0,0}, // S_SSWV_RUN6 + {SPR_SSWV,3,3,{(actionf_p2)A_Chase},S_SSWV_RUN8,0,0}, // S_SSWV_RUN7 + {SPR_SSWV,3,3,{(actionf_p2)A_Chase},S_SSWV_RUN1,0,0}, // S_SSWV_RUN8 + {SPR_SSWV,4,10,{(actionf_p2)A_FaceTarget},S_SSWV_ATK2,0,0}, // S_SSWV_ATK1 + {SPR_SSWV,5,10,{(actionf_p2)A_FaceTarget},S_SSWV_ATK3,0,0}, // S_SSWV_ATK2 + {SPR_SSWV,32774,4,{(actionf_p2)A_CPosAttack},S_SSWV_ATK4,0,0}, // S_SSWV_ATK3 + {SPR_SSWV,5,6,{(actionf_p2)A_FaceTarget},S_SSWV_ATK5,0,0}, // S_SSWV_ATK4 + {SPR_SSWV,32774,4,{(actionf_p2)A_CPosAttack},S_SSWV_ATK6,0,0}, // S_SSWV_ATK5 + {SPR_SSWV,5,1,{(actionf_p2)A_CPosRefire},S_SSWV_ATK2,0,0}, // S_SSWV_ATK6 + {SPR_SSWV,7,3,{NULL},S_SSWV_PAIN2,0,0}, // S_SSWV_PAIN + {SPR_SSWV,7,3,{(actionf_p2)A_Pain},S_SSWV_RUN1,0,0}, // S_SSWV_PAIN2 + {SPR_SSWV,8,5,{NULL},S_SSWV_DIE2,0,0}, // S_SSWV_DIE1 + {SPR_SSWV,9,5,{(actionf_p2)A_Scream},S_SSWV_DIE3,0,0}, // S_SSWV_DIE2 + {SPR_SSWV,10,5,{(actionf_p2)A_Fall},S_SSWV_DIE4,0,0}, // S_SSWV_DIE3 + {SPR_SSWV,11,5,{NULL},S_SSWV_DIE5,0,0}, // S_SSWV_DIE4 + {SPR_SSWV,12,-1,{NULL},S_NULL,0,0}, // S_SSWV_DIE5 + {SPR_SSWV,13,5,{NULL},S_SSWV_XDIE2,0,0}, // S_SSWV_XDIE1 + {SPR_SSWV,14,5,{(actionf_p2)A_XScream},S_SSWV_XDIE3,0,0}, // S_SSWV_XDIE2 + {SPR_SSWV,15,5,{(actionf_p2)A_Fall},S_SSWV_XDIE4,0,0}, // S_SSWV_XDIE3 + {SPR_SSWV,16,5,{NULL},S_SSWV_XDIE5,0,0}, // S_SSWV_XDIE4 + {SPR_SSWV,17,5,{NULL},S_SSWV_XDIE6,0,0}, // S_SSWV_XDIE5 + {SPR_SSWV,18,5,{NULL},S_SSWV_XDIE7,0,0}, // S_SSWV_XDIE6 + {SPR_SSWV,19,5,{NULL},S_SSWV_XDIE8,0,0}, // S_SSWV_XDIE7 + {SPR_SSWV,20,5,{NULL},S_SSWV_XDIE9,0,0}, // S_SSWV_XDIE8 + {SPR_SSWV,21,-1,{NULL},S_NULL,0,0}, // S_SSWV_XDIE9 + {SPR_SSWV,12,5,{NULL},S_SSWV_RAISE2,0,0}, // S_SSWV_RAISE1 + {SPR_SSWV,11,5,{NULL},S_SSWV_RAISE3,0,0}, // S_SSWV_RAISE2 + {SPR_SSWV,10,5,{NULL},S_SSWV_RAISE4,0,0}, // S_SSWV_RAISE3 + {SPR_SSWV,9,5,{NULL},S_SSWV_RAISE5,0,0}, // S_SSWV_RAISE4 + {SPR_SSWV,8,5,{NULL},S_SSWV_RUN1,0,0}, // S_SSWV_RAISE5 + {SPR_KEEN,0,-1,{NULL},S_KEENSTND,0,0}, // S_KEENSTND + {SPR_KEEN,0,6,{NULL},S_COMMKEEN2,0,0}, // S_COMMKEEN + {SPR_KEEN,1,6,{NULL},S_COMMKEEN3,0,0}, // S_COMMKEEN2 + {SPR_KEEN,2,6,{(actionf_p2)A_Scream},S_COMMKEEN4,0,0}, // S_COMMKEEN3 + {SPR_KEEN,3,6,{NULL},S_COMMKEEN5,0,0}, // S_COMMKEEN4 + {SPR_KEEN,4,6,{NULL},S_COMMKEEN6,0,0}, // S_COMMKEEN5 + {SPR_KEEN,5,6,{NULL},S_COMMKEEN7,0,0}, // S_COMMKEEN6 + {SPR_KEEN,6,6,{NULL},S_COMMKEEN8,0,0}, // S_COMMKEEN7 + {SPR_KEEN,7,6,{NULL},S_COMMKEEN9,0,0}, // S_COMMKEEN8 + {SPR_KEEN,8,6,{NULL},S_COMMKEEN10,0,0}, // S_COMMKEEN9 + {SPR_KEEN,9,6,{NULL},S_COMMKEEN11,0,0}, // S_COMMKEEN10 + {SPR_KEEN,10,6,{(actionf_p2)A_KeenDie},S_COMMKEEN12,0,0},// S_COMMKEEN11 + {SPR_KEEN,11,-1,{NULL},S_NULL,0,0}, // S_COMMKEEN12 + {SPR_KEEN,12,4,{NULL},S_KEENPAIN2,0,0}, // S_KEENPAIN + {SPR_KEEN,12,8,{(actionf_p2)A_Pain},S_KEENSTND,0,0}, // S_KEENPAIN2 + {SPR_BBRN,0,-1,{NULL},S_NULL,0,0}, // S_BRAIN + {SPR_BBRN,1,36,{(actionf_p2)A_BrainPain},S_BRAIN,0,0}, // S_BRAIN_PAIN + {SPR_BBRN,0,100,{(actionf_p2)A_BrainScream},S_BRAIN_DIE2,0,0}, // S_BRAIN_DIE1 + {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE3,0,0}, // S_BRAIN_DIE2 + {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE4,0,0}, // S_BRAIN_DIE3 + {SPR_BBRN,0,-1,{(actionf_p2)A_BrainDie},S_NULL,0,0}, // S_BRAIN_DIE4 + {SPR_SSWV,0,10,{(actionf_p2)A_Look},S_BRAINEYE,0,0}, // S_BRAINEYE + {SPR_SSWV,0,181,{(actionf_p2)A_BrainAwake},S_BRAINEYE1,0,0}, // S_BRAINEYESEE + {SPR_SSWV,0,150,{(actionf_p2)A_BrainSpit},S_BRAINEYE1,0,0}, // S_BRAINEYE1 + {SPR_BOSF,32768,3,{(actionf_p2)A_SpawnSound},S_SPAWN2,0,0}, // S_SPAWN1 + {SPR_BOSF,32769,3,{(actionf_p2)A_SpawnFly},S_SPAWN3,0,0}, // S_SPAWN2 + {SPR_BOSF,32770,3,{(actionf_p2)A_SpawnFly},S_SPAWN4,0,0}, // S_SPAWN3 + {SPR_BOSF,32771,3,{(actionf_p2)A_SpawnFly},S_SPAWN1,0,0}, // S_SPAWN4 + {SPR_FIRE,32768,4,{(actionf_p2)A_Fire},S_SPAWNFIRE2,0,0}, // S_SPAWNFIRE1 + {SPR_FIRE,32769,4,{(actionf_p2)A_Fire},S_SPAWNFIRE3,0,0}, // S_SPAWNFIRE2 + {SPR_FIRE,32770,4,{(actionf_p2)A_Fire},S_SPAWNFIRE4,0,0}, // S_SPAWNFIRE3 + {SPR_FIRE,32771,4,{(actionf_p2)A_Fire},S_SPAWNFIRE5,0,0}, // S_SPAWNFIRE4 + {SPR_FIRE,32772,4,{(actionf_p2)A_Fire},S_SPAWNFIRE6,0,0}, // S_SPAWNFIRE5 + {SPR_FIRE,32773,4,{(actionf_p2)A_Fire},S_SPAWNFIRE7,0,0}, // S_SPAWNFIRE6 + {SPR_FIRE,32774,4,{(actionf_p2)A_Fire},S_SPAWNFIRE8,0,0}, // S_SPAWNFIRE7 + {SPR_FIRE,32775,4,{(actionf_p2)A_Fire},S_NULL,0,0}, // S_SPAWNFIRE8 + {SPR_MISL,32769,10,{NULL},S_BRAINEXPLODE2,0,0}, // S_BRAINEXPLODE1 + {SPR_MISL,32770,10,{NULL},S_BRAINEXPLODE3,0,0}, // S_BRAINEXPLODE2 + {SPR_MISL,32771,10,{(actionf_p2)A_BrainExplode},S_NULL,0,0}, // S_BRAINEXPLODE3 + {SPR_ARM1,0,6,{NULL},S_ARM1A,0,0}, // S_ARM1 + {SPR_ARM1,32769,7,{NULL},S_ARM1,0,0}, // S_ARM1A + {SPR_ARM2,0,6,{NULL},S_ARM2A,0,0}, // S_ARM2 + {SPR_ARM2,32769,6,{NULL},S_ARM2,0,0}, // S_ARM2A + {SPR_BAR1,0,6,{NULL},S_BAR2,0,0}, // S_BAR1 + {SPR_BAR1,1,6,{NULL},S_BAR1,0,0}, // S_BAR2 + {SPR_BEXP,32768,5,{NULL},S_BEXP2,0,0}, // S_BEXP + {SPR_BEXP,32769,5,{(actionf_p2)A_Scream},S_BEXP3,0,0}, // S_BEXP2 + {SPR_BEXP,32770,5,{NULL},S_BEXP4,0,0}, // S_BEXP3 + {SPR_BEXP,32771,10,{(actionf_p2)A_Explode},S_BEXP5,0,0}, // S_BEXP4 + {SPR_BEXP,32772,10,{NULL},S_NULL,0,0}, // S_BEXP5 + {SPR_FCAN,32768,4,{NULL},S_BBAR2,0,0}, // S_BBAR1 + {SPR_FCAN,32769,4,{NULL},S_BBAR3,0,0}, // S_BBAR2 + {SPR_FCAN,32770,4,{NULL},S_BBAR1,0,0}, // S_BBAR3 + {SPR_BON1,0,6,{NULL},S_BON1A,0,0}, // S_BON1 + {SPR_BON1,1,6,{NULL},S_BON1B,0,0}, // S_BON1A + {SPR_BON1,2,6,{NULL},S_BON1C,0,0}, // S_BON1B + {SPR_BON1,3,6,{NULL},S_BON1D,0,0}, // S_BON1C + {SPR_BON1,2,6,{NULL},S_BON1E,0,0}, // S_BON1D + {SPR_BON1,1,6,{NULL},S_BON1,0,0}, // S_BON1E + {SPR_BON2,0,6,{NULL},S_BON2A,0,0}, // S_BON2 + {SPR_BON2,1,6,{NULL},S_BON2B,0,0}, // S_BON2A + {SPR_BON2,2,6,{NULL},S_BON2C,0,0}, // S_BON2B + {SPR_BON2,3,6,{NULL},S_BON2D,0,0}, // S_BON2C + {SPR_BON2,2,6,{NULL},S_BON2E,0,0}, // S_BON2D + {SPR_BON2,1,6,{NULL},S_BON2,0,0}, // S_BON2E + {SPR_BKEY,0,10,{NULL},S_BKEY2,0,0}, // S_BKEY + {SPR_BKEY,32769,10,{NULL},S_BKEY,0,0}, // S_BKEY2 + {SPR_RKEY,0,10,{NULL},S_RKEY2,0,0}, // S_RKEY + {SPR_RKEY,32769,10,{NULL},S_RKEY,0,0}, // S_RKEY2 + {SPR_YKEY,0,10,{NULL},S_YKEY2,0,0}, // S_YKEY + {SPR_YKEY,32769,10,{NULL},S_YKEY,0,0}, // S_YKEY2 + {SPR_BSKU,0,10,{NULL},S_BSKULL2,0,0}, // S_BSKULL + {SPR_BSKU,32769,10,{NULL},S_BSKULL,0,0}, // S_BSKULL2 + {SPR_RSKU,0,10,{NULL},S_RSKULL2,0,0}, // S_RSKULL + {SPR_RSKU,32769,10,{NULL},S_RSKULL,0,0}, // S_RSKULL2 + {SPR_YSKU,0,10,{NULL},S_YSKULL2,0,0}, // S_YSKULL + {SPR_YSKU,32769,10,{NULL},S_YSKULL,0,0}, // S_YSKULL2 + {SPR_STIM,0,-1,{NULL},S_NULL,0,0}, // S_STIM + {SPR_MEDI,0,-1,{NULL},S_NULL,0,0}, // S_MEDI + {SPR_SOUL,32768,6,{NULL},S_SOUL2,0,0}, // S_SOUL + {SPR_SOUL,32769,6,{NULL},S_SOUL3,0,0}, // S_SOUL2 + {SPR_SOUL,32770,6,{NULL},S_SOUL4,0,0}, // S_SOUL3 + {SPR_SOUL,32771,6,{NULL},S_SOUL5,0,0}, // S_SOUL4 + {SPR_SOUL,32770,6,{NULL},S_SOUL6,0,0}, // S_SOUL5 + {SPR_SOUL,32769,6,{NULL},S_SOUL,0,0}, // S_SOUL6 + {SPR_PINV,32768,6,{NULL},S_PINV2,0,0}, // S_PINV + {SPR_PINV,32769,6,{NULL},S_PINV3,0,0}, // S_PINV2 + {SPR_PINV,32770,6,{NULL},S_PINV4,0,0}, // S_PINV3 + {SPR_PINV,32771,6,{NULL},S_PINV,0,0}, // S_PINV4 + {SPR_PSTR,32768,-1,{NULL},S_NULL,0,0}, // S_PSTR + {SPR_PINS,32768,6,{NULL},S_PINS2,0,0}, // S_PINS + {SPR_PINS,32769,6,{NULL},S_PINS3,0,0}, // S_PINS2 + {SPR_PINS,32770,6,{NULL},S_PINS4,0,0}, // S_PINS3 + {SPR_PINS,32771,6,{NULL},S_PINS,0,0}, // S_PINS4 + {SPR_MEGA,32768,6,{NULL},S_MEGA2,0,0}, // S_MEGA + {SPR_MEGA,32769,6,{NULL},S_MEGA3,0,0}, // S_MEGA2 + {SPR_MEGA,32770,6,{NULL},S_MEGA4,0,0}, // S_MEGA3 + {SPR_MEGA,32771,6,{NULL},S_MEGA,0,0}, // S_MEGA4 + {SPR_SUIT,32768,-1,{NULL},S_NULL,0,0}, // S_SUIT + {SPR_PMAP,32768,6,{NULL},S_PMAP2,0,0}, // S_PMAP + {SPR_PMAP,32769,6,{NULL},S_PMAP3,0,0}, // S_PMAP2 + {SPR_PMAP,32770,6,{NULL},S_PMAP4,0,0}, // S_PMAP3 + {SPR_PMAP,32771,6,{NULL},S_PMAP5,0,0}, // S_PMAP4 + {SPR_PMAP,32770,6,{NULL},S_PMAP6,0,0}, // S_PMAP5 + {SPR_PMAP,32769,6,{NULL},S_PMAP,0,0}, // S_PMAP6 + {SPR_PVIS,32768,6,{NULL},S_PVIS2,0,0}, // S_PVIS + {SPR_PVIS,1,6,{NULL},S_PVIS,0,0}, // S_PVIS2 + {SPR_CLIP,0,-1,{NULL},S_NULL,0,0}, // S_CLIP + {SPR_AMMO,0,-1,{NULL},S_NULL,0,0}, // S_AMMO + {SPR_ROCK,0,-1,{NULL},S_NULL,0,0}, // S_ROCK + {SPR_BROK,0,-1,{NULL},S_NULL,0,0}, // S_BROK + {SPR_CELL,0,-1,{NULL},S_NULL,0,0}, // S_CELL + {SPR_CELP,0,-1,{NULL},S_NULL,0,0}, // S_CELP + {SPR_SHEL,0,-1,{NULL},S_NULL,0,0}, // S_SHEL + {SPR_SBOX,0,-1,{NULL},S_NULL,0,0}, // S_SBOX + {SPR_BPAK,0,-1,{NULL},S_NULL,0,0}, // S_BPAK + {SPR_BFUG,0,-1,{NULL},S_NULL,0,0}, // S_BFUG + {SPR_MGUN,0,-1,{NULL},S_NULL,0,0}, // S_MGUN + {SPR_CSAW,0,-1,{NULL},S_NULL,0,0}, // S_CSAW + {SPR_LAUN,0,-1,{NULL},S_NULL,0,0}, // S_LAUN + {SPR_PLAS,0,-1,{NULL},S_NULL,0,0}, // S_PLAS + {SPR_SHOT,0,-1,{NULL},S_NULL,0,0}, // S_SHOT + {SPR_SGN2,0,-1,{NULL},S_NULL,0,0}, // S_SHOT2 + {SPR_COLU,32768,-1,{NULL},S_NULL,0,0}, // S_COLU + {SPR_SMT2,0,-1,{NULL},S_NULL,0,0}, // S_STALAG + {SPR_GOR1,0,10,{NULL},S_BLOODYTWITCH2,0,0}, // S_BLOODYTWITCH + {SPR_GOR1,1,15,{NULL},S_BLOODYTWITCH3,0,0}, // S_BLOODYTWITCH2 + {SPR_GOR1,2,8,{NULL},S_BLOODYTWITCH4,0,0}, // S_BLOODYTWITCH3 + {SPR_GOR1,1,6,{NULL},S_BLOODYTWITCH,0,0}, // S_BLOODYTWITCH4 + {SPR_PLAY,13,-1,{NULL},S_NULL,0,0}, // S_DEADTORSO + {SPR_PLAY,18,-1,{NULL},S_NULL,0,0}, // S_DEADBOTTOM + {SPR_POL2,0,-1,{NULL},S_NULL,0,0}, // S_HEADSONSTICK + {SPR_POL5,0,-1,{NULL},S_NULL,0,0}, // S_GIBS + {SPR_POL4,0,-1,{NULL},S_NULL,0,0}, // S_HEADONASTICK + {SPR_POL3,32768,6,{NULL},S_HEADCANDLES2,0,0}, // S_HEADCANDLES + {SPR_POL3,32769,6,{NULL},S_HEADCANDLES,0,0}, // S_HEADCANDLES2 + {SPR_POL1,0,-1,{NULL},S_NULL,0,0}, // S_DEADSTICK + {SPR_POL6,0,6,{NULL},S_LIVESTICK2,0,0}, // S_LIVESTICK + {SPR_POL6,1,8,{NULL},S_LIVESTICK,0,0}, // S_LIVESTICK2 + {SPR_GOR2,0,-1,{NULL},S_NULL,0,0}, // S_MEAT2 + {SPR_GOR3,0,-1,{NULL},S_NULL,0,0}, // S_MEAT3 + {SPR_GOR4,0,-1,{NULL},S_NULL,0,0}, // S_MEAT4 + {SPR_GOR5,0,-1,{NULL},S_NULL,0,0}, // S_MEAT5 + {SPR_SMIT,0,-1,{NULL},S_NULL,0,0}, // S_STALAGTITE + {SPR_COL1,0,-1,{NULL},S_NULL,0,0}, // S_TALLGRNCOL + {SPR_COL2,0,-1,{NULL},S_NULL,0,0}, // S_SHRTGRNCOL + {SPR_COL3,0,-1,{NULL},S_NULL,0,0}, // S_TALLREDCOL + {SPR_COL4,0,-1,{NULL},S_NULL,0,0}, // S_SHRTREDCOL + {SPR_CAND,32768,-1,{NULL},S_NULL,0,0}, // S_CANDLESTIK + {SPR_CBRA,32768,-1,{NULL},S_NULL,0,0}, // S_CANDELABRA + {SPR_COL6,0,-1,{NULL},S_NULL,0,0}, // S_SKULLCOL + {SPR_TRE1,0,-1,{NULL},S_NULL,0,0}, // S_TORCHTREE + {SPR_TRE2,0,-1,{NULL},S_NULL,0,0}, // S_BIGTREE + {SPR_ELEC,0,-1,{NULL},S_NULL,0,0}, // S_TECHPILLAR + {SPR_CEYE,32768,6,{NULL},S_EVILEYE2,0,0}, // S_EVILEYE + {SPR_CEYE,32769,6,{NULL},S_EVILEYE3,0,0}, // S_EVILEYE2 + {SPR_CEYE,32770,6,{NULL},S_EVILEYE4,0,0}, // S_EVILEYE3 + {SPR_CEYE,32769,6,{NULL},S_EVILEYE,0,0}, // S_EVILEYE4 + {SPR_FSKU,32768,6,{NULL},S_FLOATSKULL2,0,0}, // S_FLOATSKULL + {SPR_FSKU,32769,6,{NULL},S_FLOATSKULL3,0,0}, // S_FLOATSKULL2 + {SPR_FSKU,32770,6,{NULL},S_FLOATSKULL,0,0}, // S_FLOATSKULL3 + {SPR_COL5,0,14,{NULL},S_HEARTCOL2,0,0}, // S_HEARTCOL + {SPR_COL5,1,14,{NULL},S_HEARTCOL,0,0}, // S_HEARTCOL2 + {SPR_TBLU,32768,4,{NULL},S_BLUETORCH2,0,0}, // S_BLUETORCH + {SPR_TBLU,32769,4,{NULL},S_BLUETORCH3,0,0}, // S_BLUETORCH2 + {SPR_TBLU,32770,4,{NULL},S_BLUETORCH4,0,0}, // S_BLUETORCH3 + {SPR_TBLU,32771,4,{NULL},S_BLUETORCH,0,0}, // S_BLUETORCH4 + {SPR_TGRN,32768,4,{NULL},S_GREENTORCH2,0,0}, // S_GREENTORCH + {SPR_TGRN,32769,4,{NULL},S_GREENTORCH3,0,0}, // S_GREENTORCH2 + {SPR_TGRN,32770,4,{NULL},S_GREENTORCH4,0,0}, // S_GREENTORCH3 + {SPR_TGRN,32771,4,{NULL},S_GREENTORCH,0,0}, // S_GREENTORCH4 + {SPR_TRED,32768,4,{NULL},S_REDTORCH2,0,0}, // S_REDTORCH + {SPR_TRED,32769,4,{NULL},S_REDTORCH3,0,0}, // S_REDTORCH2 + {SPR_TRED,32770,4,{NULL},S_REDTORCH4,0,0}, // S_REDTORCH3 + {SPR_TRED,32771,4,{NULL},S_REDTORCH,0,0}, // S_REDTORCH4 + {SPR_SMBT,32768,4,{NULL},S_BTORCHSHRT2,0,0}, // S_BTORCHSHRT + {SPR_SMBT,32769,4,{NULL},S_BTORCHSHRT3,0,0}, // S_BTORCHSHRT2 + {SPR_SMBT,32770,4,{NULL},S_BTORCHSHRT4,0,0}, // S_BTORCHSHRT3 + {SPR_SMBT,32771,4,{NULL},S_BTORCHSHRT,0,0}, // S_BTORCHSHRT4 + {SPR_SMGT,32768,4,{NULL},S_GTORCHSHRT2,0,0}, // S_GTORCHSHRT + {SPR_SMGT,32769,4,{NULL},S_GTORCHSHRT3,0,0}, // S_GTORCHSHRT2 + {SPR_SMGT,32770,4,{NULL},S_GTORCHSHRT4,0,0}, // S_GTORCHSHRT3 + {SPR_SMGT,32771,4,{NULL},S_GTORCHSHRT,0,0}, // S_GTORCHSHRT4 + {SPR_SMRT,32768,4,{NULL},S_RTORCHSHRT2,0,0}, // S_RTORCHSHRT + {SPR_SMRT,32769,4,{NULL},S_RTORCHSHRT3,0,0}, // S_RTORCHSHRT2 + {SPR_SMRT,32770,4,{NULL},S_RTORCHSHRT4,0,0}, // S_RTORCHSHRT3 + {SPR_SMRT,32771,4,{NULL},S_RTORCHSHRT,0,0}, // S_RTORCHSHRT4 + {SPR_HDB1,0,-1,{NULL},S_NULL,0,0}, // S_HANGNOGUTS + {SPR_HDB2,0,-1,{NULL},S_NULL,0,0}, // S_HANGBNOBRAIN + {SPR_HDB3,0,-1,{NULL},S_NULL,0,0}, // S_HANGTLOOKDN + {SPR_HDB4,0,-1,{NULL},S_NULL,0,0}, // S_HANGTSKULL + {SPR_HDB5,0,-1,{NULL},S_NULL,0,0}, // S_HANGTLOOKUP + {SPR_HDB6,0,-1,{NULL},S_NULL,0,0}, // S_HANGTNOBRAIN + {SPR_POB1,0,-1,{NULL},S_NULL,0,0}, // S_COLONGIBS + {SPR_POB2,0,-1,{NULL},S_NULL,0,0}, // S_SMALLPOOL + {SPR_BRS1,0,-1,{NULL},S_NULL,0,0}, // S_BRAINSTEM + {SPR_TLMP,32768,4,{NULL},S_TECHLAMP2,0,0}, // S_TECHLAMP + {SPR_TLMP,32769,4,{NULL},S_TECHLAMP3,0,0}, // S_TECHLAMP2 + {SPR_TLMP,32770,4,{NULL},S_TECHLAMP4,0,0}, // S_TECHLAMP3 + {SPR_TLMP,32771,4,{NULL},S_TECHLAMP,0,0}, // S_TECHLAMP4 + {SPR_TLP2,32768,4,{NULL},S_TECH2LAMP2,0,0}, // S_TECH2LAMP + {SPR_TLP2,32769,4,{NULL},S_TECH2LAMP3,0,0}, // S_TECH2LAMP2 + {SPR_TLP2,32770,4,{NULL},S_TECH2LAMP4,0,0}, // S_TECH2LAMP3 + {SPR_TLP2,32771,4,{NULL},S_TECH2LAMP,0,0} // S_TECH2LAMP4 +}; + + +const mobjinfo_t mobjinfo[NUMMOBJTYPES] = { + + { // MT_PLAYER + -1, // doomednum + S_PLAY, // spawnstate + 100, // spawnhealth + S_PLAY_RUN1, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_PLAY_PAIN, // painstate + 255, // painchance + sfx_plpain, // painsound + S_NULL, // meleestate + S_PLAY_ATK1, // missilestate + S_PLAY_DIE1, // deathstate + S_PLAY_XDIE1, // xdeathstate + sfx_pldeth, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_POSSESSED + 3004, // doomednum + S_POSS_STND, // spawnstate + 20, // spawnhealth + S_POSS_RUN1, // seestate + sfx_posit1, // seesound + 8, // reactiontime + sfx_pistol, // attacksound + S_POSS_PAIN, // painstate + 200, // painchance + sfx_popain, // painsound + 0, // meleestate + S_POSS_ATK1, // missilestate + S_POSS_DIE1, // deathstate + S_POSS_XDIE1, // xdeathstate + sfx_podth1, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_POSS_RAISE1 // raisestate + }, + + { // MT_SHOTGUY + 9, // doomednum + S_SPOS_STND, // spawnstate + 30, // spawnhealth + S_SPOS_RUN1, // seestate + sfx_posit2, // seesound + 8, // reactiontime + 0, // attacksound + S_SPOS_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_SPOS_ATK1, // missilestate + S_SPOS_DIE1, // deathstate + S_SPOS_XDIE1, // xdeathstate + sfx_podth2, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SPOS_RAISE1 // raisestate + }, + + { // MT_VILE + 64, // doomednum + S_VILE_STND, // spawnstate + 700, // spawnhealth + S_VILE_RUN1, // seestate + sfx_vilsit, // seesound + 8, // reactiontime + 0, // attacksound + S_VILE_PAIN, // painstate + 10, // painchance + sfx_vipain, // painsound + 0, // meleestate + S_VILE_ATK1, // missilestate + S_VILE_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_vildth, // deathsound + 15, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 500, // mass + 0, // damage + sfx_vilact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_FIRE + -1, // doomednum + S_FIRE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_UNDEAD + 66, // doomednum + S_SKEL_STND, // spawnstate + 300, // spawnhealth + S_SKEL_RUN1, // seestate + sfx_skesit, // seesound + 8, // reactiontime + 0, // attacksound + S_SKEL_PAIN, // painstate + 100, // painchance + sfx_popain, // painsound + S_SKEL_FIST1, // meleestate + S_SKEL_MISS1, // missilestate + S_SKEL_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_skedth, // deathsound + 10, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 500, // mass + 0, // damage + sfx_skeact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SKEL_RAISE1 // raisestate + }, + + { // MT_TRACER + -1, // doomednum + S_TRACER, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_skeatk, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TRACEEXP1, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 10*FRACUNIT, // speed + 11*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 10, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_SMOKE + -1, // doomednum + S_SMOKE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_FATSO + 67, // doomednum + S_FATT_STND, // spawnstate + 600, // spawnhealth + S_FATT_RUN1, // seestate + sfx_mansit, // seesound + 8, // reactiontime + 0, // attacksound + S_FATT_PAIN, // painstate + 80, // painchance + sfx_mnpain, // painsound + 0, // meleestate + S_FATT_ATK1, // missilestate + S_FATT_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_mandth, // deathsound + 8, // speed + 48*FRACUNIT, // radius + 64*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_FATT_RAISE1 // raisestate + }, + + { // MT_FATSHOT + -1, // doomednum + S_FATSHOT1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_FATSHOTX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 20*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 8, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_CHAINGUY + 65, // doomednum + S_CPOS_STND, // spawnstate + 70, // spawnhealth + S_CPOS_RUN1, // seestate + sfx_posit2, // seesound + 8, // reactiontime + 0, // attacksound + S_CPOS_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_CPOS_ATK1, // missilestate + S_CPOS_DIE1, // deathstate + S_CPOS_XDIE1, // xdeathstate + sfx_podth2, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_CPOS_RAISE1 // raisestate + }, + + { // MT_TROOP + 3001, // doomednum + S_TROO_STND, // spawnstate + 60, // spawnhealth + S_TROO_RUN1, // seestate + sfx_bgsit1, // seesound + 8, // reactiontime + 0, // attacksound + S_TROO_PAIN, // painstate + 200, // painchance + sfx_popain, // painsound + S_TROO_ATK1, // meleestate + S_TROO_ATK1, // missilestate + S_TROO_DIE1, // deathstate + S_TROO_XDIE1, // xdeathstate + sfx_bgdth1, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_bgact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_TROO_RAISE1 // raisestate + }, + + { // MT_SERGEANT + 3002, // doomednum + S_SARG_STND, // spawnstate + 150, // spawnhealth + S_SARG_RUN1, // seestate + sfx_sgtsit, // seesound + 8, // reactiontime + sfx_sgtatk, // attacksound + S_SARG_PAIN, // painstate + 180, // painchance + sfx_dmpain, // painsound + S_SARG_ATK1, // meleestate + 0, // missilestate + S_SARG_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sgtdth, // deathsound + 10, // speed + 30*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SARG_RAISE1 // raisestate + }, + + { // MT_SHADOWS + 58, // doomednum + S_SARG_STND, // spawnstate + 150, // spawnhealth + S_SARG_RUN1, // seestate + sfx_sgtsit, // seesound + 8, // reactiontime + sfx_sgtatk, // attacksound + S_SARG_PAIN, // painstate + 180, // painchance + sfx_dmpain, // painsound + S_SARG_ATK1, // meleestate + 0, // missilestate + S_SARG_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_sgtdth, // deathsound + 10, // speed + 30*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_SHADOW|MF_COUNTKILL, // flags + S_SARG_RAISE1 // raisestate + }, + + { // MT_HEAD + 3005, // doomednum + S_HEAD_STND, // spawnstate + 400, // spawnhealth + S_HEAD_RUN1, // seestate + sfx_cacsit, // seesound + 8, // reactiontime + 0, // attacksound + S_HEAD_PAIN, // painstate + 128, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_HEAD_ATK1, // missilestate + S_HEAD_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_cacdth, // deathsound + 8, // speed + 31*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY|MF_COUNTKILL, // flags + S_HEAD_RAISE1 // raisestate + }, + + { // MT_BRUISER + 3003, // doomednum + S_BOSS_STND, // spawnstate + 1000, // spawnhealth + S_BOSS_RUN1, // seestate + sfx_brssit, // seesound + 8, // reactiontime + 0, // attacksound + S_BOSS_PAIN, // painstate + 50, // painchance + sfx_dmpain, // painsound + S_BOSS_ATK1, // meleestate + S_BOSS_ATK1, // missilestate + S_BOSS_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_brsdth, // deathsound + 8, // speed + 24*FRACUNIT, // radius + 64*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_BOSS_RAISE1 // raisestate + }, + + { // MT_BRUISERSHOT + -1, // doomednum + S_BRBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BRBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 15*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 8, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_KNIGHT + 69, // doomednum + S_BOS2_STND, // spawnstate + 500, // spawnhealth + S_BOS2_RUN1, // seestate + sfx_kntsit, // seesound + 8, // reactiontime + 0, // attacksound + S_BOS2_PAIN, // painstate + 50, // painchance + sfx_dmpain, // painsound + S_BOS2_ATK1, // meleestate + S_BOS2_ATK1, // missilestate + S_BOS2_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_kntdth, // deathsound + 8, // speed + 24*FRACUNIT, // radius + 64*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_BOS2_RAISE1 // raisestate + }, + + { // MT_SKULL + 3006, // doomednum + S_SKULL_STND, // spawnstate + 100, // spawnhealth + S_SKULL_RUN1, // seestate + 0, // seesound + 8, // reactiontime + sfx_sklatk, // attacksound + S_SKULL_PAIN, // painstate + 256, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_SKULL_ATK1, // missilestate + S_SKULL_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 8, // speed + 16*FRACUNIT, // radius + 56*FRACUNIT, // height + 50, // mass + 3, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_SPIDER + 7, // doomednum + S_SPID_STND, // spawnstate + 3000, // spawnhealth + S_SPID_RUN1, // seestate + sfx_spisit, // seesound + 8, // reactiontime + sfx_shotgn, // attacksound + S_SPID_PAIN, // painstate + 40, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_SPID_ATK1, // missilestate + S_SPID_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_spidth, // deathsound + 12, // speed + 128*FRACUNIT, // radius + 100*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_BABY + 68, // doomednum + S_BSPI_STND, // spawnstate + 500, // spawnhealth + S_BSPI_SIGHT, // seestate + sfx_bspsit, // seesound + 8, // reactiontime + 0, // attacksound + S_BSPI_PAIN, // painstate + 128, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_BSPI_ATK1, // missilestate + S_BSPI_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_bspdth, // deathsound + 12, // speed + 64*FRACUNIT, // radius + 64*FRACUNIT, // height + 600, // mass + 0, // damage + sfx_bspact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_BSPI_RAISE1 // raisestate + }, + + { // MT_CYBORG + 16, // doomednum + S_CYBER_STND, // spawnstate + 4000, // spawnhealth + S_CYBER_RUN1, // seestate + sfx_cybsit, // seesound + 8, // reactiontime + 0, // attacksound + S_CYBER_PAIN, // painstate + 20, // painchance + sfx_dmpain, // painsound + 0, // meleestate + S_CYBER_ATK1, // missilestate + S_CYBER_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_cybdth, // deathsound + 16, // speed + 40*FRACUNIT, // radius + 110*FRACUNIT, // height + 1000, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_PAIN + 71, // doomednum + S_PAIN_STND, // spawnstate + 400, // spawnhealth + S_PAIN_RUN1, // seestate + sfx_pesit, // seesound + 8, // reactiontime + 0, // attacksound + S_PAIN_PAIN, // painstate + 128, // painchance + sfx_pepain, // painsound + 0, // meleestate + S_PAIN_ATK1, // missilestate + S_PAIN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_pedth, // deathsound + 8, // speed + 31*FRACUNIT, // radius + 56*FRACUNIT, // height + 400, // mass + 0, // damage + sfx_dmact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY|MF_COUNTKILL, // flags + S_PAIN_RAISE1 // raisestate + }, + + { // MT_WOLFSS + 84, // doomednum + S_SSWV_STND, // spawnstate + 50, // spawnhealth + S_SSWV_RUN1, // seestate + sfx_sssit, // seesound + 8, // reactiontime + 0, // attacksound + S_SSWV_PAIN, // painstate + 170, // painchance + sfx_popain, // painsound + 0, // meleestate + S_SSWV_ATK1, // missilestate + S_SSWV_DIE1, // deathstate + S_SSWV_XDIE1, // xdeathstate + sfx_ssdth, // deathsound + 8, // speed + 20*FRACUNIT, // radius + 56*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_posact, // activesound + MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_SSWV_RAISE1 // raisestate + }, + + { // MT_KEEN + 72, // doomednum + S_KEENSTND, // spawnstate + 100, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_KEENPAIN, // painstate + 256, // painchance + sfx_keenpn, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_COMMKEEN, // deathstate + S_NULL, // xdeathstate + sfx_keendt, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 72*FRACUNIT, // height + 10000000, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SHOOTABLE|MF_COUNTKILL, // flags + S_NULL // raisestate + }, + + { // MT_BOSSBRAIN + 88, // doomednum + S_BRAIN, // spawnstate + 250, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_BRAIN_PAIN, // painstate + 255, // painchance + sfx_bospn, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BRAIN_DIE1, // deathstate + S_NULL, // xdeathstate + sfx_bosdth, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 10000000, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE, // flags + S_NULL // raisestate + }, + + { // MT_BOSSSPIT + 89, // doomednum + S_BRAINEYE, // spawnstate + 1000, // spawnhealth + S_BRAINEYESEE, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 32*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { // MT_BOSSTARGET + 87, // doomednum + S_NULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 32*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { // MT_SPAWNSHOT + -1, // doomednum + S_SPAWN1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_bospit, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10*FRACUNIT, // speed + 6*FRACUNIT, // radius + 32*FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY|MF_NOCLIP, // flags + S_NULL // raisestate + }, + + { // MT_SPAWNFIRE + -1, // doomednum + S_SPAWNFIRE1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BARREL + 2035, // doomednum + S_BAR1, // spawnstate + 20, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BEXP, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 0, // speed + 10*FRACUNIT, // radius + 42*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE|MF_NOBLOOD, // flags + S_NULL // raisestate + }, + + { // MT_TROOPSHOT + -1, // doomednum + S_TBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_TBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 3, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_HEADSHOT + -1, // doomednum + S_RBALL1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_firsht, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_RBALLX1, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 10*FRACUNIT, // speed + 6*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_ROCKET + -1, // doomednum + S_ROCKET, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_rlaunc, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_EXPLODE1, // deathstate + S_NULL, // xdeathstate + sfx_barexp, // deathsound + 20*FRACUNIT, // speed + 11*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 20, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_PLASMA + -1, // doomednum + S_PLASBALL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_plasma, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_PLASEXP, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 25*FRACUNIT, // speed + 13*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BFG + -1, // doomednum + S_BFGSHOT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + 0, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_BFGLAND, // deathstate + S_NULL, // xdeathstate + sfx_rxplod, // deathsound + 25*FRACUNIT, // speed + 13*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 100, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_ARACHPLAZ + -1, // doomednum + S_ARACH_PLAZ, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_plasma, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_ARACH_PLEX, // deathstate + S_NULL, // xdeathstate + sfx_firxpl, // deathsound + 25*FRACUNIT, // speed + 13*FRACUNIT, // radius + 8*FRACUNIT, // height + 100, // mass + 5, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_PUFF + -1, // doomednum + S_PUFF1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_BLOOD + -1, // doomednum + S_BLOOD1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { // MT_TFOG + -1, // doomednum + S_TFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_IFOG + -1, // doomednum + S_IFOG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_TELEPORTMAN + 14, // doomednum + S_NULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOSECTOR, // flags + S_NULL // raisestate + }, + + { // MT_EXTRABFG + -1, // doomednum + S_BFGEXP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC0 + 2018, // doomednum + S_ARM1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC1 + 2019, // doomednum + S_ARM2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC2 + 2014, // doomednum + S_BON1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC3 + 2015, // doomednum + S_BON2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC4 + 5, // doomednum + S_BKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC5 + 13, // doomednum + S_RKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC6 + 6, // doomednum + S_YKEY, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC7 + 39, // doomednum + S_YSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC8 + 38, // doomednum + S_RSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC9 + 40, // doomednum + S_BSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOTDMATCH, // flags + S_NULL // raisestate + }, + + { // MT_MISC10 + 2011, // doomednum + S_STIM, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC11 + 2012, // doomednum + S_MEDI, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC12 + 2013, // doomednum + S_SOUL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_INV + 2022, // doomednum + S_PINV, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC13 + 2023, // doomednum + S_PSTR, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_INS + 2024, // doomednum + S_PINS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC14 + 2025, // doomednum + S_SUIT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC15 + 2026, // doomednum + S_PMAP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MISC16 + 2045, // doomednum + S_PVIS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_MEGA + 83, // doomednum + S_MEGA, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_COUNTITEM, // flags + S_NULL // raisestate + }, + + { // MT_CLIP + 2007, // doomednum + S_CLIP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC17 + 2048, // doomednum + S_AMMO, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC18 + 2010, // doomednum + S_ROCK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC19 + 2046, // doomednum + S_BROK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC20 + 2047, // doomednum + S_CELL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC21 + 17, // doomednum + S_CELP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC22 + 2008, // doomednum + S_SHEL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC23 + 2049, // doomednum + S_SBOX, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC24 + 8, // doomednum + S_BPAK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC25 + 2006, // doomednum + S_BFUG, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_CHAINGUN + 2002, // doomednum + S_MGUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC26 + 2005, // doomednum + S_CSAW, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC27 + 2003, // doomednum + S_LAUN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC28 + 2004, // doomednum + S_PLAS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_SHOTGUN + 2001, // doomednum + S_SHOT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_SUPERSHOTGUN + 82, // doomednum + S_SHOT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL, // flags + S_NULL // raisestate + }, + + { // MT_MISC29 + 85, // doomednum + S_TECHLAMP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC30 + 86, // doomednum + S_TECH2LAMP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC31 + 2028, // doomednum + S_COLU, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC32 + 30, // doomednum + S_TALLGRNCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC33 + 31, // doomednum + S_SHRTGRNCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC34 + 32, // doomednum + S_TALLREDCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC35 + 33, // doomednum + S_SHRTREDCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC36 + 37, // doomednum + S_SKULLCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC37 + 36, // doomednum + S_HEARTCOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC38 + 41, // doomednum + S_EVILEYE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC39 + 42, // doomednum + S_FLOATSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC40 + 43, // doomednum + S_TORCHTREE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC41 + 44, // doomednum + S_BLUETORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC42 + 45, // doomednum + S_GREENTORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC43 + 46, // doomednum + S_REDTORCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC44 + 55, // doomednum + S_BTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC45 + 56, // doomednum + S_GTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC46 + 57, // doomednum + S_RTORCHSHRT, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC47 + 47, // doomednum + S_STALAGTITE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC48 + 48, // doomednum + S_TECHPILLAR, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC49 + 34, // doomednum + S_CANDLESTIK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC50 + 35, // doomednum + S_CANDELABRA, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC51 + 49, // doomednum + S_BLOODYTWITCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC52 + 50, // doomednum + S_MEAT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 84*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC53 + 51, // doomednum + S_MEAT3, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 84*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC54 + 52, // doomednum + S_MEAT4, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC55 + 53, // doomednum + S_MEAT5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 52*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC56 + 59, // doomednum + S_MEAT2, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 84*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC57 + 60, // doomednum + S_MEAT4, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC58 + 61, // doomednum + S_MEAT3, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 52*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC59 + 62, // doomednum + S_MEAT5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 52*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC60 + 63, // doomednum + S_BLOODYTWITCH, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 68*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC61 + 22, // doomednum + S_HEAD_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC62 + 15, // doomednum + S_PLAY_DIE7, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC63 + 18, // doomednum + S_POSS_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC64 + 21, // doomednum + S_SARG_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC65 + 23, // doomednum + S_SKULL_DIE6, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC66 + 20, // doomednum + S_TROO_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC67 + 19, // doomednum + S_SPOS_DIE5, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC68 + 10, // doomednum + S_PLAY_XDIE9, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC69 + 12, // doomednum + S_PLAY_XDIE9, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC70 + 28, // doomednum + S_HEADSONSTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC71 + 24, // doomednum + S_GIBS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + + { // MT_MISC72 + 27, // doomednum + S_HEADONASTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC73 + 29, // doomednum + S_HEADCANDLES, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC74 + 25, // doomednum + S_DEADSTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC75 + 26, // doomednum + S_LIVESTICK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC76 + 54, // doomednum + S_BIGTREE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC77 + 70, // doomednum + S_BBAR1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID, // flags + S_NULL // raisestate + }, + + { // MT_MISC78 + 73, // doomednum + S_HANGNOGUTS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 88*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC79 + 74, // doomednum + S_HANGBNOBRAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 88*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC80 + 75, // doomednum + S_HANGTLOOKDN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC81 + 76, // doomednum + S_HANGTSKULL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC82 + 77, // doomednum + S_HANGTLOOKUP, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC83 + 78, // doomednum + S_HANGTNOBRAIN, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 64*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + + { // MT_MISC84 + 79, // doomednum + S_COLONGIBS, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { // MT_MISC85 + 80, // doomednum + S_SMALLPOOL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + + { // MT_MISC86 + 81, // doomednum + S_BRAINSTEM, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 16*FRACUNIT, // height + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP, // flags + S_NULL // raisestate + } +}; + + diff --git a/doomclassic/doom/info.h b/doomclassic/doom/info.h new file mode 100644 index 00000000..dc07476b --- /dev/null +++ b/doomclassic/doom/info.h @@ -0,0 +1,1347 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __INFO__ +#define __INFO__ + +// Needed for action function pointer handling. +#include "d_think.h" + +typedef enum +{ + SPR_TROO, + SPR_SHTG, + SPR_PUNG, + SPR_PISG, + SPR_PISF, + SPR_SHTF, + SPR_SHT2, + SPR_CHGG, + SPR_CHGF, + SPR_MISG, + SPR_MISF, + SPR_SAWG, + SPR_PLSG, + SPR_PLSF, + SPR_BFGG, + SPR_BFGF, + SPR_BLUD, + SPR_PUFF, + SPR_BAL1, + SPR_BAL2, + SPR_PLSS, + SPR_PLSE, + SPR_MISL, + SPR_BFS1, + SPR_BFE1, + SPR_BFE2, + SPR_TFOG, + SPR_IFOG, + SPR_PLAY, + SPR_POSS, + SPR_SPOS, + SPR_VILE, + SPR_FIRE, + SPR_FATB, + SPR_FBXP, + SPR_SKEL, + SPR_MANF, + SPR_FATT, + SPR_CPOS, + SPR_SARG, + SPR_HEAD, + SPR_BAL7, + SPR_BOSS, + SPR_BOS2, + SPR_SKUL, + SPR_SPID, + SPR_BSPI, + SPR_APLS, + SPR_APBX, + SPR_CYBR, + SPR_PAIN, + SPR_SSWV, + SPR_KEEN, + SPR_BBRN, + SPR_BOSF, + SPR_ARM1, + SPR_ARM2, + SPR_BAR1, + SPR_BEXP, + SPR_FCAN, + SPR_BON1, + SPR_BON2, + SPR_BKEY, + SPR_RKEY, + SPR_YKEY, + SPR_BSKU, + SPR_RSKU, + SPR_YSKU, + SPR_STIM, + SPR_MEDI, + SPR_SOUL, + SPR_PINV, + SPR_PSTR, + SPR_PINS, + SPR_MEGA, + SPR_SUIT, + SPR_PMAP, + SPR_PVIS, + SPR_CLIP, + SPR_AMMO, + SPR_ROCK, + SPR_BROK, + SPR_CELL, + SPR_CELP, + SPR_SHEL, + SPR_SBOX, + SPR_BPAK, + SPR_BFUG, + SPR_MGUN, + SPR_CSAW, + SPR_LAUN, + SPR_PLAS, + SPR_SHOT, + SPR_SGN2, + SPR_COLU, + SPR_SMT2, + SPR_GOR1, + SPR_POL2, + SPR_POL5, + SPR_POL4, + SPR_POL3, + SPR_POL1, + SPR_POL6, + SPR_GOR2, + SPR_GOR3, + SPR_GOR4, + SPR_GOR5, + SPR_SMIT, + SPR_COL1, + SPR_COL2, + SPR_COL3, + SPR_COL4, + SPR_CAND, + SPR_CBRA, + SPR_COL6, + SPR_TRE1, + SPR_TRE2, + SPR_ELEC, + SPR_CEYE, + SPR_FSKU, + SPR_COL5, + SPR_TBLU, + SPR_TGRN, + SPR_TRED, + SPR_SMBT, + SPR_SMGT, + SPR_SMRT, + SPR_HDB1, + SPR_HDB2, + SPR_HDB3, + SPR_HDB4, + SPR_HDB5, + SPR_HDB6, + SPR_POB1, + SPR_POB2, + SPR_BRS1, + SPR_TLMP, + SPR_TLP2, + NUMSPRITES + +} spritenum_t; + +typedef enum +{ + S_NULL, + S_LIGHTDONE, + S_PUNCH, + S_PUNCHDOWN, + S_PUNCHUP, + S_PUNCH1, + S_PUNCH2, + S_PUNCH3, + S_PUNCH4, + S_PUNCH5, + S_PISTOL, + S_PISTOLDOWN, + S_PISTOLUP, + S_PISTOL1, + S_PISTOL2, + S_PISTOL3, + S_PISTOL4, + S_PISTOLFLASH, + S_SGUN, + S_SGUNDOWN, + S_SGUNUP, + S_SGUN1, + S_SGUN2, + S_SGUN3, + S_SGUN4, + S_SGUN5, + S_SGUN6, + S_SGUN7, + S_SGUN8, + S_SGUN9, + S_SGUNFLASH1, + S_SGUNFLASH2, + S_DSGUN, + S_DSGUNDOWN, + S_DSGUNUP, + S_DSGUN1, + S_DSGUN2, + S_DSGUN3, + S_DSGUN4, + S_DSGUN5, + S_DSGUN6, + S_DSGUN7, + S_DSGUN8, + S_DSGUN9, + S_DSGUN10, + S_DSNR1, + S_DSNR2, + S_DSGUNFLASH1, + S_DSGUNFLASH2, + S_CHAIN, + S_CHAINDOWN, + S_CHAINUP, + S_CHAIN1, + S_CHAIN2, + S_CHAIN3, + S_CHAINFLASH1, + S_CHAINFLASH2, + S_MISSILE, + S_MISSILEDOWN, + S_MISSILEUP, + S_MISSILE1, + S_MISSILE2, + S_MISSILE3, + S_MISSILEFLASH1, + S_MISSILEFLASH2, + S_MISSILEFLASH3, + S_MISSILEFLASH4, + S_SAW, + S_SAWB, + S_SAWDOWN, + S_SAWUP, + S_SAW1, + S_SAW2, + S_SAW3, + S_PLASMA, + S_PLASMADOWN, + S_PLASMAUP, + S_PLASMA1, + S_PLASMA2, + S_PLASMAFLASH1, + S_PLASMAFLASH2, + S_BFG, + S_BFGDOWN, + S_BFGUP, + S_BFG1, + S_BFG2, + S_BFG3, + S_BFG4, + S_BFGFLASH1, + S_BFGFLASH2, + S_BLOOD1, + S_BLOOD2, + S_BLOOD3, + S_PUFF1, + S_PUFF2, + S_PUFF3, + S_PUFF4, + S_TBALL1, + S_TBALL2, + S_TBALLX1, + S_TBALLX2, + S_TBALLX3, + S_RBALL1, + S_RBALL2, + S_RBALLX1, + S_RBALLX2, + S_RBALLX3, + S_PLASBALL, + S_PLASBALL2, + S_PLASEXP, + S_PLASEXP2, + S_PLASEXP3, + S_PLASEXP4, + S_PLASEXP5, + S_ROCKET, + S_BFGSHOT, + S_BFGSHOT2, + S_BFGLAND, + S_BFGLAND2, + S_BFGLAND3, + S_BFGLAND4, + S_BFGLAND5, + S_BFGLAND6, + S_BFGEXP, + S_BFGEXP2, + S_BFGEXP3, + S_BFGEXP4, + S_EXPLODE1, + S_EXPLODE2, + S_EXPLODE3, + S_TFOG, + S_TFOG01, + S_TFOG02, + S_TFOG2, + S_TFOG3, + S_TFOG4, + S_TFOG5, + S_TFOG6, + S_TFOG7, + S_TFOG8, + S_TFOG9, + S_TFOG10, + S_IFOG, + S_IFOG01, + S_IFOG02, + S_IFOG2, + S_IFOG3, + S_IFOG4, + S_IFOG5, + S_PLAY, + S_PLAY_RUN1, + S_PLAY_RUN2, + S_PLAY_RUN3, + S_PLAY_RUN4, + S_PLAY_ATK1, + S_PLAY_ATK2, + S_PLAY_PAIN, + S_PLAY_PAIN2, + S_PLAY_DIE1, + S_PLAY_DIE2, + S_PLAY_DIE3, + S_PLAY_DIE4, + S_PLAY_DIE5, + S_PLAY_DIE6, + S_PLAY_DIE7, + S_PLAY_XDIE1, + S_PLAY_XDIE2, + S_PLAY_XDIE3, + S_PLAY_XDIE4, + S_PLAY_XDIE5, + S_PLAY_XDIE6, + S_PLAY_XDIE7, + S_PLAY_XDIE8, + S_PLAY_XDIE9, + S_POSS_STND, + S_POSS_STND2, + S_POSS_RUN1, + S_POSS_RUN2, + S_POSS_RUN3, + S_POSS_RUN4, + S_POSS_RUN5, + S_POSS_RUN6, + S_POSS_RUN7, + S_POSS_RUN8, + S_POSS_ATK1, + S_POSS_ATK2, + S_POSS_ATK3, + S_POSS_PAIN, + S_POSS_PAIN2, + S_POSS_DIE1, + S_POSS_DIE2, + S_POSS_DIE3, + S_POSS_DIE4, + S_POSS_DIE5, + S_POSS_XDIE1, + S_POSS_XDIE2, + S_POSS_XDIE3, + S_POSS_XDIE4, + S_POSS_XDIE5, + S_POSS_XDIE6, + S_POSS_XDIE7, + S_POSS_XDIE8, + S_POSS_XDIE9, + S_POSS_RAISE1, + S_POSS_RAISE2, + S_POSS_RAISE3, + S_POSS_RAISE4, + S_SPOS_STND, + S_SPOS_STND2, + S_SPOS_RUN1, + S_SPOS_RUN2, + S_SPOS_RUN3, + S_SPOS_RUN4, + S_SPOS_RUN5, + S_SPOS_RUN6, + S_SPOS_RUN7, + S_SPOS_RUN8, + S_SPOS_ATK1, + S_SPOS_ATK2, + S_SPOS_ATK3, + S_SPOS_PAIN, + S_SPOS_PAIN2, + S_SPOS_DIE1, + S_SPOS_DIE2, + S_SPOS_DIE3, + S_SPOS_DIE4, + S_SPOS_DIE5, + S_SPOS_XDIE1, + S_SPOS_XDIE2, + S_SPOS_XDIE3, + S_SPOS_XDIE4, + S_SPOS_XDIE5, + S_SPOS_XDIE6, + S_SPOS_XDIE7, + S_SPOS_XDIE8, + S_SPOS_XDIE9, + S_SPOS_RAISE1, + S_SPOS_RAISE2, + S_SPOS_RAISE3, + S_SPOS_RAISE4, + S_SPOS_RAISE5, + S_VILE_STND, + S_VILE_STND2, + S_VILE_RUN1, + S_VILE_RUN2, + S_VILE_RUN3, + S_VILE_RUN4, + S_VILE_RUN5, + S_VILE_RUN6, + S_VILE_RUN7, + S_VILE_RUN8, + S_VILE_RUN9, + S_VILE_RUN10, + S_VILE_RUN11, + S_VILE_RUN12, + S_VILE_ATK1, + S_VILE_ATK2, + S_VILE_ATK3, + S_VILE_ATK4, + S_VILE_ATK5, + S_VILE_ATK6, + S_VILE_ATK7, + S_VILE_ATK8, + S_VILE_ATK9, + S_VILE_ATK10, + S_VILE_ATK11, + S_VILE_HEAL1, + S_VILE_HEAL2, + S_VILE_HEAL3, + S_VILE_PAIN, + S_VILE_PAIN2, + S_VILE_DIE1, + S_VILE_DIE2, + S_VILE_DIE3, + S_VILE_DIE4, + S_VILE_DIE5, + S_VILE_DIE6, + S_VILE_DIE7, + S_VILE_DIE8, + S_VILE_DIE9, + S_VILE_DIE10, + S_FIRE1, + S_FIRE2, + S_FIRE3, + S_FIRE4, + S_FIRE5, + S_FIRE6, + S_FIRE7, + S_FIRE8, + S_FIRE9, + S_FIRE10, + S_FIRE11, + S_FIRE12, + S_FIRE13, + S_FIRE14, + S_FIRE15, + S_FIRE16, + S_FIRE17, + S_FIRE18, + S_FIRE19, + S_FIRE20, + S_FIRE21, + S_FIRE22, + S_FIRE23, + S_FIRE24, + S_FIRE25, + S_FIRE26, + S_FIRE27, + S_FIRE28, + S_FIRE29, + S_FIRE30, + S_SMOKE1, + S_SMOKE2, + S_SMOKE3, + S_SMOKE4, + S_SMOKE5, + S_TRACER, + S_TRACER2, + S_TRACEEXP1, + S_TRACEEXP2, + S_TRACEEXP3, + S_SKEL_STND, + S_SKEL_STND2, + S_SKEL_RUN1, + S_SKEL_RUN2, + S_SKEL_RUN3, + S_SKEL_RUN4, + S_SKEL_RUN5, + S_SKEL_RUN6, + S_SKEL_RUN7, + S_SKEL_RUN8, + S_SKEL_RUN9, + S_SKEL_RUN10, + S_SKEL_RUN11, + S_SKEL_RUN12, + S_SKEL_FIST1, + S_SKEL_FIST2, + S_SKEL_FIST3, + S_SKEL_FIST4, + S_SKEL_MISS1, + S_SKEL_MISS2, + S_SKEL_MISS3, + S_SKEL_MISS4, + S_SKEL_PAIN, + S_SKEL_PAIN2, + S_SKEL_DIE1, + S_SKEL_DIE2, + S_SKEL_DIE3, + S_SKEL_DIE4, + S_SKEL_DIE5, + S_SKEL_DIE6, + S_SKEL_RAISE1, + S_SKEL_RAISE2, + S_SKEL_RAISE3, + S_SKEL_RAISE4, + S_SKEL_RAISE5, + S_SKEL_RAISE6, + S_FATSHOT1, + S_FATSHOT2, + S_FATSHOTX1, + S_FATSHOTX2, + S_FATSHOTX3, + S_FATT_STND, + S_FATT_STND2, + S_FATT_RUN1, + S_FATT_RUN2, + S_FATT_RUN3, + S_FATT_RUN4, + S_FATT_RUN5, + S_FATT_RUN6, + S_FATT_RUN7, + S_FATT_RUN8, + S_FATT_RUN9, + S_FATT_RUN10, + S_FATT_RUN11, + S_FATT_RUN12, + S_FATT_ATK1, + S_FATT_ATK2, + S_FATT_ATK3, + S_FATT_ATK4, + S_FATT_ATK5, + S_FATT_ATK6, + S_FATT_ATK7, + S_FATT_ATK8, + S_FATT_ATK9, + S_FATT_ATK10, + S_FATT_PAIN, + S_FATT_PAIN2, + S_FATT_DIE1, + S_FATT_DIE2, + S_FATT_DIE3, + S_FATT_DIE4, + S_FATT_DIE5, + S_FATT_DIE6, + S_FATT_DIE7, + S_FATT_DIE8, + S_FATT_DIE9, + S_FATT_DIE10, + S_FATT_RAISE1, + S_FATT_RAISE2, + S_FATT_RAISE3, + S_FATT_RAISE4, + S_FATT_RAISE5, + S_FATT_RAISE6, + S_FATT_RAISE7, + S_FATT_RAISE8, + S_CPOS_STND, + S_CPOS_STND2, + S_CPOS_RUN1, + S_CPOS_RUN2, + S_CPOS_RUN3, + S_CPOS_RUN4, + S_CPOS_RUN5, + S_CPOS_RUN6, + S_CPOS_RUN7, + S_CPOS_RUN8, + S_CPOS_ATK1, + S_CPOS_ATK2, + S_CPOS_ATK3, + S_CPOS_ATK4, + S_CPOS_PAIN, + S_CPOS_PAIN2, + S_CPOS_DIE1, + S_CPOS_DIE2, + S_CPOS_DIE3, + S_CPOS_DIE4, + S_CPOS_DIE5, + S_CPOS_DIE6, + S_CPOS_DIE7, + S_CPOS_XDIE1, + S_CPOS_XDIE2, + S_CPOS_XDIE3, + S_CPOS_XDIE4, + S_CPOS_XDIE5, + S_CPOS_XDIE6, + S_CPOS_RAISE1, + S_CPOS_RAISE2, + S_CPOS_RAISE3, + S_CPOS_RAISE4, + S_CPOS_RAISE5, + S_CPOS_RAISE6, + S_CPOS_RAISE7, + S_TROO_STND, + S_TROO_STND2, + S_TROO_RUN1, + S_TROO_RUN2, + S_TROO_RUN3, + S_TROO_RUN4, + S_TROO_RUN5, + S_TROO_RUN6, + S_TROO_RUN7, + S_TROO_RUN8, + S_TROO_ATK1, + S_TROO_ATK2, + S_TROO_ATK3, + S_TROO_PAIN, + S_TROO_PAIN2, + S_TROO_DIE1, + S_TROO_DIE2, + S_TROO_DIE3, + S_TROO_DIE4, + S_TROO_DIE5, + S_TROO_XDIE1, + S_TROO_XDIE2, + S_TROO_XDIE3, + S_TROO_XDIE4, + S_TROO_XDIE5, + S_TROO_XDIE6, + S_TROO_XDIE7, + S_TROO_XDIE8, + S_TROO_RAISE1, + S_TROO_RAISE2, + S_TROO_RAISE3, + S_TROO_RAISE4, + S_TROO_RAISE5, + S_SARG_STND, + S_SARG_STND2, + S_SARG_RUN1, + S_SARG_RUN2, + S_SARG_RUN3, + S_SARG_RUN4, + S_SARG_RUN5, + S_SARG_RUN6, + S_SARG_RUN7, + S_SARG_RUN8, + S_SARG_ATK1, + S_SARG_ATK2, + S_SARG_ATK3, + S_SARG_PAIN, + S_SARG_PAIN2, + S_SARG_DIE1, + S_SARG_DIE2, + S_SARG_DIE3, + S_SARG_DIE4, + S_SARG_DIE5, + S_SARG_DIE6, + S_SARG_RAISE1, + S_SARG_RAISE2, + S_SARG_RAISE3, + S_SARG_RAISE4, + S_SARG_RAISE5, + S_SARG_RAISE6, + S_HEAD_STND, + S_HEAD_RUN1, + S_HEAD_ATK1, + S_HEAD_ATK2, + S_HEAD_ATK3, + S_HEAD_PAIN, + S_HEAD_PAIN2, + S_HEAD_PAIN3, + S_HEAD_DIE1, + S_HEAD_DIE2, + S_HEAD_DIE3, + S_HEAD_DIE4, + S_HEAD_DIE5, + S_HEAD_DIE6, + S_HEAD_RAISE1, + S_HEAD_RAISE2, + S_HEAD_RAISE3, + S_HEAD_RAISE4, + S_HEAD_RAISE5, + S_HEAD_RAISE6, + S_BRBALL1, + S_BRBALL2, + S_BRBALLX1, + S_BRBALLX2, + S_BRBALLX3, + S_BOSS_STND, + S_BOSS_STND2, + S_BOSS_RUN1, + S_BOSS_RUN2, + S_BOSS_RUN3, + S_BOSS_RUN4, + S_BOSS_RUN5, + S_BOSS_RUN6, + S_BOSS_RUN7, + S_BOSS_RUN8, + S_BOSS_ATK1, + S_BOSS_ATK2, + S_BOSS_ATK3, + S_BOSS_PAIN, + S_BOSS_PAIN2, + S_BOSS_DIE1, + S_BOSS_DIE2, + S_BOSS_DIE3, + S_BOSS_DIE4, + S_BOSS_DIE5, + S_BOSS_DIE6, + S_BOSS_DIE7, + S_BOSS_RAISE1, + S_BOSS_RAISE2, + S_BOSS_RAISE3, + S_BOSS_RAISE4, + S_BOSS_RAISE5, + S_BOSS_RAISE6, + S_BOSS_RAISE7, + S_BOS2_STND, + S_BOS2_STND2, + S_BOS2_RUN1, + S_BOS2_RUN2, + S_BOS2_RUN3, + S_BOS2_RUN4, + S_BOS2_RUN5, + S_BOS2_RUN6, + S_BOS2_RUN7, + S_BOS2_RUN8, + S_BOS2_ATK1, + S_BOS2_ATK2, + S_BOS2_ATK3, + S_BOS2_PAIN, + S_BOS2_PAIN2, + S_BOS2_DIE1, + S_BOS2_DIE2, + S_BOS2_DIE3, + S_BOS2_DIE4, + S_BOS2_DIE5, + S_BOS2_DIE6, + S_BOS2_DIE7, + S_BOS2_RAISE1, + S_BOS2_RAISE2, + S_BOS2_RAISE3, + S_BOS2_RAISE4, + S_BOS2_RAISE5, + S_BOS2_RAISE6, + S_BOS2_RAISE7, + S_SKULL_STND, + S_SKULL_STND2, + S_SKULL_RUN1, + S_SKULL_RUN2, + S_SKULL_ATK1, + S_SKULL_ATK2, + S_SKULL_ATK3, + S_SKULL_ATK4, + S_SKULL_PAIN, + S_SKULL_PAIN2, + S_SKULL_DIE1, + S_SKULL_DIE2, + S_SKULL_DIE3, + S_SKULL_DIE4, + S_SKULL_DIE5, + S_SKULL_DIE6, + S_SPID_STND, + S_SPID_STND2, + S_SPID_RUN1, + S_SPID_RUN2, + S_SPID_RUN3, + S_SPID_RUN4, + S_SPID_RUN5, + S_SPID_RUN6, + S_SPID_RUN7, + S_SPID_RUN8, + S_SPID_RUN9, + S_SPID_RUN10, + S_SPID_RUN11, + S_SPID_RUN12, + S_SPID_ATK1, + S_SPID_ATK2, + S_SPID_ATK3, + S_SPID_ATK4, + S_SPID_PAIN, + S_SPID_PAIN2, + S_SPID_DIE1, + S_SPID_DIE2, + S_SPID_DIE3, + S_SPID_DIE4, + S_SPID_DIE5, + S_SPID_DIE6, + S_SPID_DIE7, + S_SPID_DIE8, + S_SPID_DIE9, + S_SPID_DIE10, + S_SPID_DIE11, + S_BSPI_STND, + S_BSPI_STND2, + S_BSPI_SIGHT, + S_BSPI_RUN1, + S_BSPI_RUN2, + S_BSPI_RUN3, + S_BSPI_RUN4, + S_BSPI_RUN5, + S_BSPI_RUN6, + S_BSPI_RUN7, + S_BSPI_RUN8, + S_BSPI_RUN9, + S_BSPI_RUN10, + S_BSPI_RUN11, + S_BSPI_RUN12, + S_BSPI_ATK1, + S_BSPI_ATK2, + S_BSPI_ATK3, + S_BSPI_ATK4, + S_BSPI_PAIN, + S_BSPI_PAIN2, + S_BSPI_DIE1, + S_BSPI_DIE2, + S_BSPI_DIE3, + S_BSPI_DIE4, + S_BSPI_DIE5, + S_BSPI_DIE6, + S_BSPI_DIE7, + S_BSPI_RAISE1, + S_BSPI_RAISE2, + S_BSPI_RAISE3, + S_BSPI_RAISE4, + S_BSPI_RAISE5, + S_BSPI_RAISE6, + S_BSPI_RAISE7, + S_ARACH_PLAZ, + S_ARACH_PLAZ2, + S_ARACH_PLEX, + S_ARACH_PLEX2, + S_ARACH_PLEX3, + S_ARACH_PLEX4, + S_ARACH_PLEX5, + S_CYBER_STND, + S_CYBER_STND2, + S_CYBER_RUN1, + S_CYBER_RUN2, + S_CYBER_RUN3, + S_CYBER_RUN4, + S_CYBER_RUN5, + S_CYBER_RUN6, + S_CYBER_RUN7, + S_CYBER_RUN8, + S_CYBER_ATK1, + S_CYBER_ATK2, + S_CYBER_ATK3, + S_CYBER_ATK4, + S_CYBER_ATK5, + S_CYBER_ATK6, + S_CYBER_PAIN, + S_CYBER_DIE1, + S_CYBER_DIE2, + S_CYBER_DIE3, + S_CYBER_DIE4, + S_CYBER_DIE5, + S_CYBER_DIE6, + S_CYBER_DIE7, + S_CYBER_DIE8, + S_CYBER_DIE9, + S_CYBER_DIE10, + S_PAIN_STND, + S_PAIN_RUN1, + S_PAIN_RUN2, + S_PAIN_RUN3, + S_PAIN_RUN4, + S_PAIN_RUN5, + S_PAIN_RUN6, + S_PAIN_ATK1, + S_PAIN_ATK2, + S_PAIN_ATK3, + S_PAIN_ATK4, + S_PAIN_PAIN, + S_PAIN_PAIN2, + S_PAIN_DIE1, + S_PAIN_DIE2, + S_PAIN_DIE3, + S_PAIN_DIE4, + S_PAIN_DIE5, + S_PAIN_DIE6, + S_PAIN_RAISE1, + S_PAIN_RAISE2, + S_PAIN_RAISE3, + S_PAIN_RAISE4, + S_PAIN_RAISE5, + S_PAIN_RAISE6, + S_SSWV_STND, + S_SSWV_STND2, + S_SSWV_RUN1, + S_SSWV_RUN2, + S_SSWV_RUN3, + S_SSWV_RUN4, + S_SSWV_RUN5, + S_SSWV_RUN6, + S_SSWV_RUN7, + S_SSWV_RUN8, + S_SSWV_ATK1, + S_SSWV_ATK2, + S_SSWV_ATK3, + S_SSWV_ATK4, + S_SSWV_ATK5, + S_SSWV_ATK6, + S_SSWV_PAIN, + S_SSWV_PAIN2, + S_SSWV_DIE1, + S_SSWV_DIE2, + S_SSWV_DIE3, + S_SSWV_DIE4, + S_SSWV_DIE5, + S_SSWV_XDIE1, + S_SSWV_XDIE2, + S_SSWV_XDIE3, + S_SSWV_XDIE4, + S_SSWV_XDIE5, + S_SSWV_XDIE6, + S_SSWV_XDIE7, + S_SSWV_XDIE8, + S_SSWV_XDIE9, + S_SSWV_RAISE1, + S_SSWV_RAISE2, + S_SSWV_RAISE3, + S_SSWV_RAISE4, + S_SSWV_RAISE5, + S_KEENSTND, + S_COMMKEEN, + S_COMMKEEN2, + S_COMMKEEN3, + S_COMMKEEN4, + S_COMMKEEN5, + S_COMMKEEN6, + S_COMMKEEN7, + S_COMMKEEN8, + S_COMMKEEN9, + S_COMMKEEN10, + S_COMMKEEN11, + S_COMMKEEN12, + S_KEENPAIN, + S_KEENPAIN2, + S_BRAIN, + S_BRAIN_PAIN, + S_BRAIN_DIE1, + S_BRAIN_DIE2, + S_BRAIN_DIE3, + S_BRAIN_DIE4, + S_BRAINEYE, + S_BRAINEYESEE, + S_BRAINEYE1, + S_SPAWN1, + S_SPAWN2, + S_SPAWN3, + S_SPAWN4, + S_SPAWNFIRE1, + S_SPAWNFIRE2, + S_SPAWNFIRE3, + S_SPAWNFIRE4, + S_SPAWNFIRE5, + S_SPAWNFIRE6, + S_SPAWNFIRE7, + S_SPAWNFIRE8, + S_BRAINEXPLODE1, + S_BRAINEXPLODE2, + S_BRAINEXPLODE3, + S_ARM1, + S_ARM1A, + S_ARM2, + S_ARM2A, + S_BAR1, + S_BAR2, + S_BEXP, + S_BEXP2, + S_BEXP3, + S_BEXP4, + S_BEXP5, + S_BBAR1, + S_BBAR2, + S_BBAR3, + S_BON1, + S_BON1A, + S_BON1B, + S_BON1C, + S_BON1D, + S_BON1E, + S_BON2, + S_BON2A, + S_BON2B, + S_BON2C, + S_BON2D, + S_BON2E, + S_BKEY, + S_BKEY2, + S_RKEY, + S_RKEY2, + S_YKEY, + S_YKEY2, + S_BSKULL, + S_BSKULL2, + S_RSKULL, + S_RSKULL2, + S_YSKULL, + S_YSKULL2, + S_STIM, + S_MEDI, + S_SOUL, + S_SOUL2, + S_SOUL3, + S_SOUL4, + S_SOUL5, + S_SOUL6, + S_PINV, + S_PINV2, + S_PINV3, + S_PINV4, + S_PSTR, + S_PINS, + S_PINS2, + S_PINS3, + S_PINS4, + S_MEGA, + S_MEGA2, + S_MEGA3, + S_MEGA4, + S_SUIT, + S_PMAP, + S_PMAP2, + S_PMAP3, + S_PMAP4, + S_PMAP5, + S_PMAP6, + S_PVIS, + S_PVIS2, + S_CLIP, + S_AMMO, + S_ROCK, + S_BROK, + S_CELL, + S_CELP, + S_SHEL, + S_SBOX, + S_BPAK, + S_BFUG, + S_MGUN, + S_CSAW, + S_LAUN, + S_PLAS, + S_SHOT, + S_SHOT2, + S_COLU, + S_STALAG, + S_BLOODYTWITCH, + S_BLOODYTWITCH2, + S_BLOODYTWITCH3, + S_BLOODYTWITCH4, + S_DEADTORSO, + S_DEADBOTTOM, + S_HEADSONSTICK, + S_GIBS, + S_HEADONASTICK, + S_HEADCANDLES, + S_HEADCANDLES2, + S_DEADSTICK, + S_LIVESTICK, + S_LIVESTICK2, + S_MEAT2, + S_MEAT3, + S_MEAT4, + S_MEAT5, + S_STALAGTITE, + S_TALLGRNCOL, + S_SHRTGRNCOL, + S_TALLREDCOL, + S_SHRTREDCOL, + S_CANDLESTIK, + S_CANDELABRA, + S_SKULLCOL, + S_TORCHTREE, + S_BIGTREE, + S_TECHPILLAR, + S_EVILEYE, + S_EVILEYE2, + S_EVILEYE3, + S_EVILEYE4, + S_FLOATSKULL, + S_FLOATSKULL2, + S_FLOATSKULL3, + S_HEARTCOL, + S_HEARTCOL2, + S_BLUETORCH, + S_BLUETORCH2, + S_BLUETORCH3, + S_BLUETORCH4, + S_GREENTORCH, + S_GREENTORCH2, + S_GREENTORCH3, + S_GREENTORCH4, + S_REDTORCH, + S_REDTORCH2, + S_REDTORCH3, + S_REDTORCH4, + S_BTORCHSHRT, + S_BTORCHSHRT2, + S_BTORCHSHRT3, + S_BTORCHSHRT4, + S_GTORCHSHRT, + S_GTORCHSHRT2, + S_GTORCHSHRT3, + S_GTORCHSHRT4, + S_RTORCHSHRT, + S_RTORCHSHRT2, + S_RTORCHSHRT3, + S_RTORCHSHRT4, + S_HANGNOGUTS, + S_HANGBNOBRAIN, + S_HANGTLOOKDN, + S_HANGTSKULL, + S_HANGTLOOKUP, + S_HANGTNOBRAIN, + S_COLONGIBS, + S_SMALLPOOL, + S_BRAINSTEM, + S_TECHLAMP, + S_TECHLAMP2, + S_TECHLAMP3, + S_TECHLAMP4, + S_TECH2LAMP, + S_TECH2LAMP2, + S_TECH2LAMP3, + S_TECH2LAMP4, + NUMSTATES +} statenum_t; + + +typedef struct +{ + spritenum_t sprite; + long frame; + long tics; + // void (*action) (); + + // GCC 4.1 for PS3 gives the error "braces around scalar initializer" + // for these actionf_p2s. So, let's make it not a scalar! + // The second value of the struct will be initalized to 0. + // struct { + actionf_p2 action; + // int filler; + // }; + + statenum_t nextstate; + long misc1, misc2; +} state_t; + +extern const state_t tempStates[NUMSTATES]; +extern const char * const sprnames[NUMSPRITES]; + +typedef enum { + MT_PLAYER, + MT_POSSESSED, + MT_SHOTGUY, + MT_VILE, + MT_FIRE, + MT_UNDEAD, + MT_TRACER, + MT_SMOKE, + MT_FATSO, + MT_FATSHOT, + MT_CHAINGUY, + MT_TROOP, + MT_SERGEANT, + MT_SHADOWS, + MT_HEAD, + MT_BRUISER, + MT_BRUISERSHOT, + MT_KNIGHT, + MT_SKULL, + MT_SPIDER, + MT_BABY, + MT_CYBORG, + MT_PAIN, + MT_WOLFSS, + MT_KEEN, + MT_BOSSBRAIN, + MT_BOSSSPIT, + MT_BOSSTARGET, + MT_SPAWNSHOT, + MT_SPAWNFIRE, + MT_BARREL, + MT_TROOPSHOT, + MT_HEADSHOT, + MT_ROCKET, + MT_PLASMA, + MT_BFG, + MT_ARACHPLAZ, + MT_PUFF, + MT_BLOOD, + MT_TFOG, + MT_IFOG, + MT_TELEPORTMAN, + MT_EXTRABFG, + MT_MISC0, + MT_MISC1, + MT_MISC2, + MT_MISC3, + MT_MISC4, + MT_MISC5, + MT_MISC6, + MT_MISC7, + MT_MISC8, + MT_MISC9, + MT_MISC10, + MT_MISC11, + MT_MISC12, + MT_INV, + MT_MISC13, + MT_INS, + MT_MISC14, + MT_MISC15, + MT_MISC16, + MT_MEGA, + MT_CLIP, + MT_MISC17, + MT_MISC18, + MT_MISC19, + MT_MISC20, + MT_MISC21, + MT_MISC22, + MT_MISC23, + MT_MISC24, + MT_MISC25, + MT_CHAINGUN, + MT_MISC26, + MT_MISC27, + MT_MISC28, + MT_SHOTGUN, + MT_SUPERSHOTGUN, + MT_MISC29, + MT_MISC30, + MT_MISC31, + MT_MISC32, + MT_MISC33, + MT_MISC34, + MT_MISC35, + MT_MISC36, + MT_MISC37, + MT_MISC38, + MT_MISC39, + MT_MISC40, + MT_MISC41, + MT_MISC42, + MT_MISC43, + MT_MISC44, + MT_MISC45, + MT_MISC46, + MT_MISC47, + MT_MISC48, + MT_MISC49, + MT_MISC50, + MT_MISC51, + MT_MISC52, + MT_MISC53, + MT_MISC54, + MT_MISC55, + MT_MISC56, + MT_MISC57, + MT_MISC58, + MT_MISC59, + MT_MISC60, + MT_MISC61, + MT_MISC62, + MT_MISC63, + MT_MISC64, + MT_MISC65, + MT_MISC66, + MT_MISC67, + MT_MISC68, + MT_MISC69, + MT_MISC70, + MT_MISC71, + MT_MISC72, + MT_MISC73, + MT_MISC74, + MT_MISC75, + MT_MISC76, + MT_MISC77, + MT_MISC78, + MT_MISC79, + MT_MISC80, + MT_MISC81, + MT_MISC82, + MT_MISC83, + MT_MISC84, + MT_MISC85, + MT_MISC86, + NUMMOBJTYPES + +} mobjtype_t; + +typedef struct +{ + int doomednum; + int spawnstate; + int spawnhealth; + int seestate; + int seesound; + int reactiontime; + int attacksound; + int painstate; + int painchance; + int painsound; + int meleestate; + int missilestate; + int deathstate; + int xdeathstate; + int deathsound; + int speed; + int radius; + int height; + int mass; + int damage; + int activesound; + int flags; + int raisestate; + +} mobjinfo_t; + +extern const mobjinfo_t mobjinfo[NUMMOBJTYPES]; + +#endif + diff --git a/doomclassic/doom/m_argv.cpp b/doomclassic/doom/m_argv.cpp new file mode 100644 index 00000000..582befef --- /dev/null +++ b/doomclassic/doom/m_argv.cpp @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include + + + + + +// +// M_CheckParm +// Checks for the given parameter +// in the program's command line arguments. +// Returns the argument number (1 to argc-1) +// or 0 if not present +int M_CheckParm (char *check) +{ + int i; + + for (i = 1; i < ::g->myargc; i++) + { + if ( !idStr::Icmp(check, ::g->myargv[i]) ) + return i; + } + + return 0; +} + + + + + diff --git a/doomclassic/doom/m_argv.h b/doomclassic/doom/m_argv.h new file mode 100644 index 00000000..b4d7630a --- /dev/null +++ b/doomclassic/doom/m_argv.h @@ -0,0 +1,44 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_ARGV__ +#define __M_ARGV__ + +// +// MISC +// +extern int myargc; +extern char** myargv; + +// Returns the position of the given parameter +// in the arg list (0 if not found). +int M_CheckParm (char* check); + + +#endif + diff --git a/doomclassic/doom/m_bbox.cpp b/doomclassic/doom/m_bbox.cpp new file mode 100644 index 00000000..23041a0b --- /dev/null +++ b/doomclassic/doom/m_bbox.cpp @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#ifdef __GNUG__ +#pragma implementation "m_bbox.h" +#endif +#include "m_bbox.h" + +#include "doomtype.h" + + + +void M_ClearBox (fixed_t *box) +{ + box[BOXTOP] = box[BOXRIGHT] = MININT; + box[BOXBOTTOM] = box[BOXLEFT] = MAXINT; +} + +void +M_AddToBox +( fixed_t* box, + fixed_t x, + fixed_t y ) +{ + if (xbox[BOXRIGHT]) + box[BOXRIGHT] = x; + if (ybox[BOXTOP]) + box[BOXTOP] = y; +} + + + + + + diff --git a/doomclassic/doom/m_bbox.h b/doomclassic/doom/m_bbox.h new file mode 100644 index 00000000..ab9e45bc --- /dev/null +++ b/doomclassic/doom/m_bbox.h @@ -0,0 +1,55 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_BBOX__ +#define __M_BBOX__ + +#include "m_fixed.h" + + +// Bounding box coordinate storage. +enum +{ + BOXTOP, + BOXBOTTOM, + BOXLEFT, + BOXRIGHT +}; // bbox coordinates + +// Bounding box functions. +void M_ClearBox (fixed_t* box); + +void +M_AddToBox +( fixed_t* box, + fixed_t x, + fixed_t y ); + + +#endif + diff --git a/doomclassic/doom/m_cheat.cpp b/doomclassic/doom/m_cheat.cpp new file mode 100644 index 00000000..ae0a5363 --- /dev/null +++ b/doomclassic/doom/m_cheat.cpp @@ -0,0 +1,127 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "m_cheat.h" + +// +// CHEAT SEQUENCE PACKAGE +// + + + +// +// Called in st_stuff module, which handles the input. +// Returns a 1 if the cheat was successful, 0 if failed. +// +int +cht_CheckCheat +( cheatseq_t* cht, + char key ) +{ + return 0; // ALAN : Checking the cheats CRASHES?? + int i; + int rc = 0; + + if (::g->firsttime) + { + ::g->firsttime = 0; + for (i=0;i<256;i++) ::g->cheat_xlate_table[i] = SCRAMBLE(i); + } + + if (!cht->p) + { + cht->p = ::g->cheatbuffer + ::g->usedcheatbuffer; + int isize = 0; + while(cht->sequence[isize] != 0xff) cht->p[isize] = cht->sequence[isize]; + cht->p[isize] = 0xff; + ::g->usedcheatbuffer += isize; + ::g->usedcheatbuffer ++; + } + + if (*cht->p == 0) + *(cht->p++) = key; + else if + (::g->cheat_xlate_table[(unsigned char)key] == *cht->p) cht->p++; + else + { + int isize = 0; + while(cht->sequence[isize] != 0xff) cht->p[isize] = cht->sequence[isize]; + cht->p[isize] = 0xff; + } + + if (*cht->p == 1) + cht->p++; + else if (*cht->p == 0xff) // end of sequence character + { + int isize = 0; + while(cht->sequence[isize] != 0xff) cht->p[isize] = cht->sequence[isize]; + cht->p[isize] = 0xff; + rc = 1; + } + + return rc; +} + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ) +{ + + + unsigned char pb[16]; + unsigned char *p; + unsigned char c; + + int isize = 0; + + while(cht->sequence[isize] != 0xff) pb[isize] = cht->sequence[isize]; + pb[isize] = 0xff; + p = &pb[0]; + + while (*(p++) != 1); + + do + { + c = *p; + *(buffer++) = c; + *(p++) = 0; + } + while (c && *p!=0xff ); + + if (*p==0xff) + *buffer = 0; + + +} + + + diff --git a/doomclassic/doom/m_cheat.h b/doomclassic/doom/m_cheat.h new file mode 100644 index 00000000..6f813ce6 --- /dev/null +++ b/doomclassic/doom/m_cheat.h @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_CHEAT__ +#define __M_CHEAT__ + +// +// CHEAT SEQUENCE PACKAGE +// + +#define SCRAMBLE(a) \ +((((a)&1)<<7) + (((a)&2)<<5) + ((a)&4) + (((a)&8)<<1) \ + + (((a)&16)>>1) + ((a)&32) + (((a)&64)>>5) + (((a)&128)>>7)) + +struct cheatseq_t +{ + cheatseq_t() {} + cheatseq_t( const unsigned char *seq, unsigned char *pin ) : sequence( seq ), p( pin ) {} + const unsigned char* sequence; + unsigned char* p; + +}; + +int +cht_CheckCheat +( cheatseq_t* cht, + char key ); + + +void +cht_GetParam +( cheatseq_t* cht, + char* buffer ); + + +#endif + diff --git a/doomclassic/doom/m_fixed.cpp b/doomclassic/doom/m_fixed.cpp new file mode 100644 index 00000000..4f72fd35 --- /dev/null +++ b/doomclassic/doom/m_fixed.cpp @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "stdlib.h" + +#include "doomtype.h" +#include "i_system.h" + +#ifdef __GNUG__ +#pragma implementation "m_fixed.h" +#endif +#include "m_fixed.h" + + + + +// Fixme. __USE_C_FIXED__ or something. + +fixed_t +FixedMul +( fixed_t a, + fixed_t b ) +{ + return fixed_t( ((long long) a * (long long) b) >> FRACBITS ); +} + + + +// +// FixedDiv, C version. +// + +fixed_t +FixedDiv +( fixed_t a, + fixed_t b ) +{ + if ( (abs(a)>>14) >= abs(b)) + return (a^b)<0 ? MININT : MAXINT; + return FixedDiv2 (a,b); +} + + + +fixed_t +FixedDiv2 +( fixed_t a, + fixed_t b ) +{ +#if 0 + long long c; + c = ((long long)a<<16) / ((long long)b); + return (fixed_t) c; +#endif + + double c; + + c = ((double)a) / ((double)b) * FRACUNIT; + + if (c >= 2147483648.0 || c < -2147483648.0) + I_Error("FixedDiv: divide by zero"); + return (fixed_t) c; +} + diff --git a/doomclassic/doom/m_fixed.h b/doomclassic/doom/m_fixed.h new file mode 100644 index 00000000..dce8d60d --- /dev/null +++ b/doomclassic/doom/m_fixed.h @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_FIXED__ +#define __M_FIXED__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// Fixed point, 32bit as 16.16. +// +#define FRACBITS 16 +#define FRACUNIT (1<. + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include +#include +#include +#include +#include + + +#include "doomdef.h" +#include "dstrings.h" + +#include "d_main.h" + +#include "i_system.h" +#include "i_video.h" +#include "z_zone.h" +#include "v_video.h" +#include "w_wad.h" +#include "m_misc.h" +#include "r_local.h" + + +#include "hu_stuff.h" + +#include "g_game.h" + +#include "m_argv.h" +#include "m_swap.h" + +#include "s_sound.h" + +#include "doomstat.h" + +// Data. +#include "sounds.h" + +#include "m_menu.h" + + +#include "Main.h" +//#include "../game/player/PlayerProfileDoom.h" +#include "sys/sys_session.h" +#include "sys/sys_signin.h" +#include "d3xp/Game_local.h" + +extern idCVar in_useJoystick; + +// +// defaulted values +// + +// Show messages has default, 0 = off, 1 = on + + +// Blocky mode, has default, 0 = high, 1 = normal + +// temp for ::g->screenblocks (0-9) + +// -1 = no quicksave slot picked! + +// 1 = message to be printed +// ...and here is the message string! + +// message x & y + +// timed message = no input from user + + + +const char gammamsg[5][26] = +{ + GAMMALVL0, + GAMMALVL1, + GAMMALVL2, + GAMMALVL3, + GAMMALVL4 +}; + +// we are going to be entering a savegame string +// old save description before edit + + + + + + +// +// MENU TYPEDEFS +// + + + + + +// graphic name of skulls +// warning: initializer-string for array of chars is too long +char skullName[2][/*8*/9] = +{ + "M_SKULL1","M_SKULL2" +}; + +// current menudef + +// +// PROTOTYPES +// +void M_NewGame(int choice); +void M_Episode(int choice); +void M_Expansion(int choice); +void M_ChooseSkill(int choice); +void M_LoadGame(int choice); +void M_LoadExpansion(int choice); +void M_SaveGame(int choice); +void M_Options(int choice); +void M_EndGame(int choice); +void M_ReadThis(int choice); +void M_ReadThis2(int choice); +void M_QuitDOOM(int choice); +void M_ExitGame(int choice); +void M_GameSelection(int choice); +void M_CancelExit(int choice); +void M_ChangeMessages(int choice); +void M_ChangeGPad(int choice); +void M_FullScreen(int choice); +void M_ChangeSensitivity(int choice); +void M_SfxVol(int choice); +void M_MusicVol(int choice); +void M_ChangeDetail(int choice); +void M_SizeDisplay(int choice); +void M_StartGame(int choice); +void M_Sound(int choice); + +void M_FinishReadThis(int choice); +void M_LoadSelect(int choice); +void M_SaveSelect(int choice); +void M_ReadSaveStrings(void); +void M_QuickSave(void); +void M_QuickLoad(void); + +void M_DrawMainMenu(void); +void M_DrawQuit(void); +void M_DrawReadThis1(void); +void M_DrawReadThis2(void); +void M_DrawNewGame(void); +void M_DrawEpisode(void); +void M_DrawOptions(void); +void M_DrawSound(void); +void M_DrawLoad(void); +void M_DrawSave(void); + +void M_DrawSaveLoadBorder(int x,int y); +void M_SetupNextMenu(menu_t *menudef); +void M_DrawThermo(int x,int y,int thermWidth,int thermDot); +void M_DrawEmptyCell(menu_t *menu,int item); +void M_DrawSelCell(menu_t *menu,int item); +void M_WriteText(int x, int y, char *string); +int M_StringWidth(char *string); +int M_StringHeight(char *string); +void M_StartControlPanel(void); +void M_StartMessage(char *string,messageRoutine_t routine,qboolean input); +void M_StopMessage(void); +void M_ClearMenus (void); + + + + +// +// DOOM MENU +// + + + + +// +// EPISODE SELECT +// + + + +// +// NEW GAME +// + + + + + +// +// OPTIONS MENU +// + + + +// +// Read This! MENU 1 & 2 +// + + + + + + +// +// SOUND VOLUME MENU +// + + + +// +// LOAD GAME MENU +// + + + +// +// SAVE GAME MENU +// + +// +// M_ReadSaveStrings +// read the strings from the savegame files +// +void M_ReadSaveStrings(void) +{ + idFile* handle; + int count; + int i; + char name[256]; + + for (i = 0;i < load_end;i++) + { + if( common->GetCurrentGame() == DOOM_CLASSIC ) { + sprintf(name,"DOOM\\%s%d.dsg", SAVEGAMENAME,i ); + } else { + if( DoomLib::idealExpansion == doom2 ) { + sprintf(name,"DOOM2\\%s%d.dsg", SAVEGAMENAME,i ); + } else { + sprintf(name,"DOOM2_NRFTL\\%s%d.dsg", SAVEGAMENAME,i ); + } + + } + + handle = fileSystem->OpenFileRead ( name, false ); + if (handle == NULL) + { + strcpy(&::g->savegamestrings[i][0],EMPTYSTRING); + ::g->LoadMenu[i].status = 0; + continue; + } + count = handle->Read( &::g->savegamestrings[i], SAVESTRINGSIZE ); + fileSystem->CloseFile( handle ); + strcpy( ::g->savegamepaths[i], name ); + ::g->LoadMenu[i].status = 1; + } +} + + +// +// M_LoadGame & Cie. +// +void M_DrawLoad(void) +{ + int i; + + V_DrawPatchDirect (72,28,0,(patch_t*)W_CacheLumpName("M_LOADG",PU_CACHE_SHARED)); + for (i = 0;i < load_end; i++) + { + M_DrawSaveLoadBorder(::g->LoadDef.x,::g->LoadDef.y+LINEHEIGHT*i); + M_WriteText(::g->LoadDef.x,::g->LoadDef.y+LINEHEIGHT*i,::g->savegamestrings[i]); + } +} + + + +// +// Draw border for the savegame description +// +void M_DrawSaveLoadBorder(int x,int y) +{ + int i; + + V_DrawPatchDirect (x-8,y+7,0,(patch_t*)W_CacheLumpName("M_LSLEFT",PU_CACHE_SHARED)); + + for (i = 0;i < 28;i++) + { + V_DrawPatchDirect (x,y+7,0,(patch_t*)W_CacheLumpName("M_LSCNTR",PU_CACHE_SHARED)); + x += 8; + } + + V_DrawPatchDirect (x,y+7,0,(patch_t*)W_CacheLumpName("M_LSRGHT",PU_CACHE_SHARED)); +} + + + +// +// User wants to load this game +// +void M_LoadSelect(int choice) +{ + if( ::g->gamemode != commercial ) { + G_LoadGame ( ::g->savegamepaths[ choice ] ); + } else { + strcpy( DoomLib::loadGamePath, ::g->savegamepaths[ choice ] ); + DoomLib::SetCurrentExpansion( DoomLib::idealExpansion ); + DoomLib::skipToLoad = true; + } + M_ClearMenus (); +} + + +void M_LoadExpansion(int choice) +{ + ::g->exp = choice; + + if( choice == 0 ) { + DoomLib::SetIdealExpansion( doom2 ); + }else { + DoomLib::SetIdealExpansion( pack_nerve ); + } + + M_SetupNextMenu(&::g->LoadDef); + M_ReadSaveStrings(); +} + +// +// Selected from DOOM menu +// +void M_LoadGame (int choice) +{ + if (::g->netgame) + { + M_StartMessage(LOADNET,NULL,false); + return; + } + + if (::g->gamemode == commercial) { + M_SetupNextMenu(&::g->LoadExpDef); + } else{ + M_SetupNextMenu(&::g->LoadDef); + M_ReadSaveStrings(); + } + +} + + +// +// M_SaveGame & Cie. +// +void M_DrawSave(void) +{ + int i; + + V_DrawPatchDirect (72,28,0,(patch_t*)W_CacheLumpName("M_SAVEG",PU_CACHE_SHARED)); + for (i = 0;i < load_end; i++) + { + M_DrawSaveLoadBorder(::g->LoadDef.x,::g->LoadDef.y+LINEHEIGHT*i); + M_WriteText(::g->LoadDef.x,::g->LoadDef.y+LINEHEIGHT*i,::g->savegamestrings[i]); + } + + if (::g->saveStringEnter) + { + i = M_StringWidth(::g->savegamestrings[::g->saveSlot]); + M_WriteText(::g->LoadDef.x + i,::g->LoadDef.y+LINEHEIGHT*::g->saveSlot,"_"); + } +} + +// +// M_Responder calls this when user is finished +// +void M_DoSave(int slot) +{ + G_SaveGame (slot,::g->savegamestrings[slot]); + M_ClearMenus (); + + // PICK QUICKSAVE SLOT YET? + if (::g->quickSaveSlot == -2) + ::g->quickSaveSlot = slot; +} + +// +// User wants to save. Start string input for M_Responder +// +// +// Locally used constants, shortcuts. +// +extern const char* mapnames[]; +extern const char* mapnames2[]; +void M_SaveSelect(int choice) +{ + const char* s; + const ExpansionData* exp = DoomLib::GetCurrentExpansion(); + + switch ( ::g->gamemode ) + { + case shareware: + case registered: + case retail: + s = (exp->mapNames[(::g->gameepisode-1)*9+::g->gamemap-1]); + break; + case commercial: + default: + s = (exp->mapNames[::g->gamemap-1]); + break; + } + + ::g->saveSlot = choice; + strcpy(::g->savegamestrings[::g->saveSlot], s); + M_DoSave(::g->saveSlot); +} + +// +// Selected from DOOM menu +// +void M_SaveGame (int choice) +{ + if (!::g->usergame) + { + M_StartMessage(SAVEDEAD,NULL,false); + return; + } + else if( ::g->plyr && ::g->plyr->mo && ::g->plyr->mo->health <= 0 ) { + M_StartMessage("you can't save if you're dead!\n\npress any button",NULL,false); + return; + } + + + if (::g->gamestate != GS_LEVEL) + return; + + // Reset back to what expansion we are currently playing. + DoomLib::SetIdealExpansion( DoomLib::expansionSelected ); + + M_SetupNextMenu(&::g->SaveDef); + M_ReadSaveStrings(); +} + + + +// +// M_QuickSave +// + +void M_QuickSaveResponse(int ch) +{ + if (ch == KEY_ENTER) + { + M_DoSave(::g->quickSaveSlot); + S_StartSound(NULL,sfx_swtchx); + } +} + +void M_QuickSave(void) +{ + if (!::g->usergame) + { + S_StartSound(NULL,sfx_oof); + return; + } + + if (::g->gamestate != GS_LEVEL) + return; + + if (::g->quickSaveSlot < 0) + { + M_StartControlPanel(); + M_ReadSaveStrings(); + M_SetupNextMenu(&::g->SaveDef); + ::g->quickSaveSlot = -2; // means to pick a slot now + return; + } + sprintf(::g->tempstring,QSPROMPT,::g->savegamestrings[::g->quickSaveSlot]); + M_StartMessage(::g->tempstring,M_QuickSaveResponse,true); +} + + + +// +// M_QuickLoad +// +void M_QuickLoadResponse(int ch) +{ + if (ch == KEY_ENTER) + { + M_LoadSelect(::g->quickSaveSlot); + S_StartSound(NULL,sfx_swtchx); + } +} + + +void M_QuickLoad(void) +{ + if (::g->netgame) + { + M_StartMessage(QLOADNET,NULL,false); + return; + } + + if (::g->quickSaveSlot < 0) + { + M_StartMessage(QSAVESPOT,NULL,false); + return; + } + sprintf(::g->tempstring,QLPROMPT,::g->savegamestrings[::g->quickSaveSlot]); + M_StartMessage(::g->tempstring,M_QuickLoadResponse,true); +} + + + + +// +// Read This Menus +// Had a "quick hack to fix romero bug" +// +void M_DrawReadThis1(void) +{ + ::g->inhelpscreens = true; + switch ( ::g->gamemode ) + { + case commercial: + V_DrawPatchDirect (0,0,0,(patch_t*)W_CacheLumpName("HELP",PU_CACHE_SHARED)); + break; + case shareware: + case registered: + case retail: + V_DrawPatchDirect (0,0,0,(patch_t*)W_CacheLumpName("HELP1",PU_CACHE_SHARED)); + break; + default: + break; + } + return; +} + + + +// +// Read This Menus - optional second page. +// +void M_DrawReadThis2(void) +{ + ::g->inhelpscreens = true; + switch ( ::g->gamemode ) + { + case retail: + case commercial: + // This hack keeps us from having to change menus. + V_DrawPatchDirect (0,0,0,(patch_t*)W_CacheLumpName("CREDIT",PU_CACHE_SHARED)); + break; + case shareware: + case registered: + V_DrawPatchDirect (0,0,0,(patch_t*)W_CacheLumpName("HELP2",PU_CACHE_SHARED)); + break; + default: + break; + } + return; +} + + +// +// Change Sfx & Music volumes +// +void M_DrawSound(void) +{ + V_DrawPatchDirect (60,38,0,(patch_t*)W_CacheLumpName("M_SVOL",PU_CACHE_SHARED)); + + M_DrawThermo( ::g->SoundDef.x,::g->SoundDef.y+LINEHEIGHT*(sfx_vol+1), + 16, s_volume_sound.GetInteger() ); + + M_DrawThermo(::g->SoundDef.x,::g->SoundDef.y+LINEHEIGHT*(music_vol+1), + 16, s_volume_midi.GetInteger() ); +} + +void M_Sound(int choice) +{ + M_SetupNextMenu(&::g->SoundDef); +} + +void M_SfxVol(int choice) +{ + switch(choice) + { + case 0: + s_volume_sound.SetInteger( s_volume_sound.GetInteger() - 1 ); + break; + case 1: + s_volume_sound.SetInteger( s_volume_sound.GetInteger() + 1 ); + break; + } + + S_SetSfxVolume( s_volume_sound.GetInteger() ); +} + +void M_MusicVol(int choice) +{ + switch(choice) + { + case 0: + s_volume_midi.SetInteger( s_volume_midi.GetInteger() - 1 ); + break; + case 1: + s_volume_midi.SetInteger( s_volume_midi.GetInteger() + 1 ); + break; + } + + S_SetMusicVolume( s_volume_midi.GetInteger() ); +} + + + + +// +// M_DrawMainMenu +// +void M_DrawMainMenu(void) +{ + V_DrawPatchDirect (94,2,0,(patch_t*)W_CacheLumpName("M_DOOM",PU_CACHE_SHARED)); +} + +// +// M_DrawQuit +// +void M_DrawQuit(void) { + V_DrawPatchDirect (54,38,0,(patch_t*)W_CacheLumpName("M_EXITO",PU_CACHE_SHARED)); +} + + + +// +// M_NewGame +// +void M_DrawNewGame(void) +{ + V_DrawPatchDirect (96,14,0,(patch_t*)W_CacheLumpName("M_NEWG",PU_CACHE_SHARED)); + V_DrawPatchDirect (54,38,0,(patch_t*)W_CacheLumpName("M_SKILL",PU_CACHE_SHARED)); +} + +void M_NewGame(int choice) +{ + if (::g->netgame && !::g->demoplayback) + { + M_StartMessage(NEWGAME,NULL,false); + return; + } + + if ( ::g->gamemode == commercial ) + M_SetupNextMenu(&::g->ExpDef); + else + M_SetupNextMenu(&::g->EpiDef); +} + + +// +// M_Episode +// + +void M_DrawEpisode(void) +{ + V_DrawPatchDirect (54,38,0,(patch_t*)W_CacheLumpName("M_EPISOD",PU_CACHE_SHARED)); +} + +void M_VerifyNightmare(int ch) +{ + if (ch != KEY_ENTER) + return; + + G_DeferedInitNew((skill_t)nightmare,::g->epi+1, 1); + M_ClearMenus (); +} + +void M_ChooseSkill(int choice) +{ + /* + if (choice == nightmare) + { + M_StartMessage(NIGHTMARE,M_VerifyNightmare,true); + return; + } + */ + if ( ::g->gamemode != commercial ) { + static int startLevel = 1; + G_DeferedInitNew((skill_t)choice,::g->epi+1, startLevel); + M_ClearMenus (); + } else { + DoomLib::SetCurrentExpansion( DoomLib::idealExpansion ); + DoomLib::skipToNew = true; + DoomLib::chosenSkill = choice; + DoomLib::chosenEpisode = ::g->epi+1; + } +} + +void M_Episode(int choice) +{ + // Yet another hack... + if ( (::g->gamemode == registered) + && (choice > 2)) + { + I_PrintfE("M_Episode: 4th episode requires UltimateDOOM\n"); + choice = 0; + } + + ::g->epi = choice; + M_SetupNextMenu(&::g->NewDef); +} + +void M_Expansion(int choice) +{ + ::g->exp = choice; + + if( choice == 0 ) { + DoomLib::SetIdealExpansion( doom2 ); + }else { + DoomLib::SetIdealExpansion( pack_nerve ); + } + + M_SetupNextMenu(&::g->NewDef); +} + +// +// M_Options +// +char detailNames[2][9] = +{ +"M_GDHIGH","M_GDLOW" +}; + +char msgNames[2][9] = +{ +"M_MSGOFF","M_MSGON" +}; + +int M_GetMouseSpeedForMenu( float cvarValue ) { + const float shiftedMouseSpeed = cvarValue - 0.25f; + const float normalizedMouseSpeed = shiftedMouseSpeed / ( 4.0f - 0.25 ); + const float scaledMouseSpeed = normalizedMouseSpeed * 15.0f; + const int roundedMouseSpeed = static_cast< int >( scaledMouseSpeed + 0.5f ); + + return roundedMouseSpeed; +} + +void M_DrawOptions(void) +{ + V_DrawPatchDirect (108,15,0,(patch_t*)W_CacheLumpName("M_OPTTTL",PU_CACHE_SHARED)); + + //V_DrawPatchDirect (::g->OptionsDef.x + 175,::g->OptionsDef.y+LINEHEIGHT*detail,0, + // (patch_t*)W_CacheLumpName(detailNames[::g->detailLevel],PU_CACHE_SHARED)); + + int fullscreenOnOff = r_fullscreen.GetInteger() >= 1 ? 1 : 0; + + V_DrawPatchDirect (::g->OptionsDef.x + 150,::g->OptionsDef.y+LINEHEIGHT*endgame,0, + (patch_t*)W_CacheLumpName(msgNames[fullscreenOnOff],PU_CACHE_SHARED)); + + V_DrawPatchDirect (::g->OptionsDef.x + 120,::g->OptionsDef.y+LINEHEIGHT*scrnsize,0, + (patch_t*)W_CacheLumpName(msgNames[in_useJoystick.GetInteger()],PU_CACHE_SHARED)); + + V_DrawPatchDirect (::g->OptionsDef.x + 120,::g->OptionsDef.y+LINEHEIGHT*messages,0, + (patch_t*)W_CacheLumpName(msgNames[m_show_messages.GetInteger()],PU_CACHE_SHARED)); + + extern idCVar in_mouseSpeed; + const int roundedMouseSpeed = M_GetMouseSpeedForMenu( in_mouseSpeed.GetFloat() ); + + M_DrawThermo( ::g->OptionsDef.x, ::g->OptionsDef.y + LINEHEIGHT * ( mousesens + 1 ), 16, roundedMouseSpeed ); + + //M_DrawThermo(::g->OptionsDef.x,::g->OptionsDef.y+LINEHEIGHT*(scrnsize+1), + // 9,::g->screenSize); +} + +void M_Options(int choice) +{ + M_SetupNextMenu(&::g->OptionsDef); +} + + + +// +// Toggle messages on/off +// +void M_ChangeMessages(int choice) +{ + // warning: unused parameter `int choice' + choice = 0; + m_show_messages.SetBool( !m_show_messages.GetBool() ); + + if (!m_show_messages.GetBool()) + ::g->players[::g->consoleplayer].message = MSGOFF; + else + ::g->players[::g->consoleplayer].message = MSGON ; + + ::g->message_dontfuckwithme = true; +} + +// +// Toggle messages on/off +// +void M_ChangeGPad(int choice) +{ + // warning: unused parameter `int choice' + choice = 0; + in_useJoystick.SetBool( !in_useJoystick.GetBool() ); + + ::g->message_dontfuckwithme = true; +} + +// +// Toggle Fullscreen +// +void M_FullScreen( int choice ) { + + r_fullscreen.SetInteger( r_fullscreen.GetInteger() ? 0: 1 ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); +} + +// +// M_EndGame +// +void M_EndGameResponse(int ch) +{ + if (ch != KEY_ENTER) + return; + + ::g->currentMenu->lastOn = ::g->itemOn; + M_ClearMenus (); + D_StartTitle (); +} + +void M_EndGame(int choice) +{ + choice = 0; + if (!::g->usergame) + { + S_StartSound(NULL,sfx_oof); + return; + } + + if (::g->netgame) + { + M_StartMessage(NETEND,NULL,false); + return; + } + + M_StartMessage(ENDGAME,M_EndGameResponse,true); +} + + + + +// +// M_ReadThis +// +void M_ReadThis(int choice) +{ + +} + +void M_ReadThis2(int choice) +{ + +} + +void M_FinishReadThis(int choice) +{ + choice = 0; + M_SetupNextMenu(&::g->MainDef); +} + + + + +// +// M_QuitDOOM +// +void M_QuitResponse(int ch) +{ + // Exceptions disabled by default on PS3 + //throw ""; +} + +void M_QuitDOOM(int choice) +{ + M_SetupNextMenu(&::g->QuitDef); + //M_StartMessage("are you sure?\npress A to quit, or B to cancel",M_QuitResponse,true); + //common->SwitchToGame( DOOM3_BFG ); +} + +void M_ExitGame(int choice) +{ + common->Quit(); +} + +void M_CancelExit(int choice) { + M_SetupNextMenu(&::g->MainDef); +} + +void M_GameSelection(int choice) +{ + common->SwitchToGame( DOOM3_BFG ); +} + +void M_ChangeSensitivity(int choice) +{ + extern idCVar in_mouseSpeed; + + int roundedMouseSpeed = M_GetMouseSpeedForMenu( in_mouseSpeed.GetFloat() ); + + switch(choice) + { + case 0: + if ( roundedMouseSpeed > 0 ) { + roundedMouseSpeed--; + } + break; + case 1: + if ( roundedMouseSpeed < 15 ) { + roundedMouseSpeed++; + } + break; + } + + const float normalizedNewMouseSpeed = roundedMouseSpeed / 15.0f; + const float rescaledNewMouseSpeed = 0.25f + ( ( 4.0f - 0.25 ) * normalizedNewMouseSpeed ); + in_mouseSpeed.SetFloat( rescaledNewMouseSpeed ); +} + + + + +void M_ChangeDetail(int choice) +{ + choice = 0; + ::g->detailLevel = 1 - ::g->detailLevel; + + // FIXME - does not work. Remove anyway? + I_PrintfE("M_ChangeDetail: low detail mode n.a.\n"); + + return; + + /*R_SetViewSize (::g->screenblocks, ::g->detailLevel); + + if (!::g->detailLevel) + ::g->players[::g->consoleplayer].message = DETAILHI; + else + ::g->players[::g->consoleplayer].message = DETAILLO;*/ +} + + + + +void M_SizeDisplay(int choice) +{ + switch(choice) + { + case 0: + if (::g->screenSize > 7) + { + ::g->screenblocks--; + ::g->screenSize--; + } + break; + case 1: + if (::g->screenSize < 8) + { + ::g->screenblocks++; + ::g->screenSize++; + } + break; + } + + + R_SetViewSize (::g->screenblocks, ::g->detailLevel); +} + + + + +// +// Menu Functions +// +void +M_DrawThermo +( int x, + int y, + int thermWidth, + int thermDot ) +{ + int xx; + int i; + + xx = x; + V_DrawPatchDirect (xx,y,0,(patch_t*)W_CacheLumpName("M_THERML",PU_CACHE_SHARED)); + xx += 8; + for (i=0;ix - 10, menu->y+item*LINEHEIGHT - 1, 0, + (patch_t*)W_CacheLumpName("M_CELL1",PU_CACHE_SHARED)); +} + +void +M_DrawSelCell +( menu_t* menu, + int item ) +{ + V_DrawPatchDirect (menu->x - 10, menu->y+item*LINEHEIGHT - 1, 0, + (patch_t*)W_CacheLumpName("M_CELL2",PU_CACHE_SHARED)); +} + + +void +M_StartMessage +( char* string, + messageRoutine_t routine, + qboolean input ) +{ + ::g->messageLastMenuActive = ::g->menuactive; + ::g->messageToPrint = 1; + ::g->messageString = string; + ::g->messageRoutine = (messageRoutine_t)routine; + ::g->messageNeedsInput = input; + ::g->menuactive = true; + return; +} + + + +void M_StopMessage(void) +{ + ::g->menuactive = ::g->messageLastMenuActive; + ::g->messageToPrint = 0; +} + + + +// +// Find string width from ::g->hu_font chars +// +int M_StringWidth(char* string) +{ + unsigned int i; + int w = 0; + int c; + + for (i = 0;i < strlen(string);i++) + { + c = toupper(string[i]) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE) + w += 4; + else + w += SHORT (::g->hu_font[c]->width); + } + + return w; +} + + + +// +// Find string height from ::g->hu_font chars +// +int M_StringHeight(char* string) +{ + unsigned int i; + int h; + int height = SHORT(::g->hu_font[0]->height); + + h = height; + for (i = 0;i < strlen(string);i++) + if (string[i] == '\n') + h += height; + + return h; +} + + +// +// Write a string using the ::g->hu_font +// +void +M_WriteText +( int x, + int y, + char* string) +{ + int w; + char* ch; + int c; + int cx; + int cy; + + + ch = string; + cx = x; + cy = y; + + while(1) + { + c = *ch++; + if (!c) + break; + if (c == '\n') + { + cx = x; + cy += 12; + continue; + } + + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c>= HU_FONTSIZE) + { + cx += 4; + continue; + } + + w = SHORT (::g->hu_font[c]->width); + if (cx+w > SCREENWIDTH) + break; + V_DrawPatchDirect(cx, cy, 0, ::g->hu_font[c]); + cx+=w; + } +} + + + +// +// CONTROL PANEL +// + +// +// M_Responder +// +qboolean M_Responder (event_t* ev) +{ + int ch; + int i; + + ch = -1; + + if (ev->type == ev_joystick && ::g->joywait < I_GetTime()) + { + if (ev->data3 == -1) + { + ch = KEY_UPARROW; + ::g->joywait = I_GetTime() + 5; + } + else if (ev->data3 == 1) + { + ch = KEY_DOWNARROW; + ::g->joywait = I_GetTime() + 5; + } + + if (ev->data2 == -1) + { + ch = KEY_LEFTARROW; + ::g->joywait = I_GetTime() + 2; + } + else if (ev->data2 == 1) + { + ch = KEY_RIGHTARROW; + ::g->joywait = I_GetTime() + 2; + } + + if (ev->data1&1) + { + ch = KEY_ENTER; + ::g->joywait = I_GetTime() + 5; + } + if (ev->data1&2) + { + ch = KEY_BACKSPACE; + ::g->joywait = I_GetTime() + 5; + } + } + else + { + if (ev->type == ev_mouse && ::g->mousewait < I_GetTime()) + { + ::g->mmenu_mousey += ev->data3; + if (::g->mmenu_mousey < ::g->lasty-30) + { + ch = KEY_DOWNARROW; + ::g->mousewait = I_GetTime() + 5; + ::g->mmenu_mousey = ::g->lasty -= 30; + } + else if (::g->mmenu_mousey > ::g->lasty+30) + { + ch = KEY_UPARROW; + ::g->mousewait = I_GetTime() + 5; + ::g->mmenu_mousey = ::g->lasty += 30; + } + + ::g->mmenu_mousex += ev->data2; + if (::g->mmenu_mousex < ::g->lastx-30) + { + ch = KEY_LEFTARROW; + ::g->mousewait = I_GetTime() + 5; + ::g->mmenu_mousex = ::g->lastx -= 30; + } + else if (::g->mmenu_mousex > ::g->lastx+30) + { + ch = KEY_RIGHTARROW; + ::g->mousewait = I_GetTime() + 5; + ::g->mmenu_mousex = ::g->lastx += 30; + } + + if (ev->data1&1) + { + ch = KEY_ENTER; + ::g->mousewait = I_GetTime() + 15; + } + + if (ev->data1&2) + { + ch = KEY_BACKSPACE; + ::g->mousewait = I_GetTime() + 15; + } + } else + if (ev->type == ev_keydown) + { + ch = ev->data1; + } + } + + if (ch == -1) + return false; + + + // Save Game string input + if (::g->saveStringEnter) + { + switch(ch) + { + case KEY_BACKSPACE: + if (::g->saveCharIndex > 0) + { + ::g->saveCharIndex--; + ::g->savegamestrings[::g->saveSlot][::g->saveCharIndex] = 0; + } + break; + + case KEY_ESCAPE: + ::g->saveStringEnter = 0; + strcpy(&::g->savegamestrings[::g->saveSlot][0],::g->saveOldString); + break; + + case KEY_ENTER: + ::g->saveStringEnter = 0; + if (::g->savegamestrings[::g->saveSlot][0]) + M_DoSave(::g->saveSlot); + break; + + default: + ch = toupper(ch); + if (ch != 32) + if (ch-HU_FONTSTART < 0 || ch-HU_FONTSTART >= HU_FONTSIZE) + break; + if (ch >= 32 && ch <= 127 && + ::g->saveCharIndex < SAVESTRINGSIZE-1 && + M_StringWidth(::g->savegamestrings[::g->saveSlot]) < + (SAVESTRINGSIZE-2)*8) + { + ::g->savegamestrings[::g->saveSlot][::g->saveCharIndex++] = ch; + ::g->savegamestrings[::g->saveSlot][::g->saveCharIndex] = 0; + } + break; + } + return true; + } + + // Take care of any messages that need input + if (::g->messageToPrint) + { + if (::g->messageNeedsInput == true && + !(ch == KEY_ENTER || ch == KEY_BACKSPACE || ch == KEY_ESCAPE)) + return false; + + ::g->menuactive = ::g->messageLastMenuActive; + ::g->messageToPrint = 0; + if (::g->messageRoutine) + ::g->messageRoutine(ch); + + S_StartSound(NULL,sfx_swtchx); + return true; + } +/* + if (::g->devparm && ch == KEY_F1) + { + G_ScreenShot (); + return true; + } + + // F-Keys + if (!::g->menuactive) + switch(ch) + { + case KEY_MINUS: // Screen size down + if (::g->automapactive || ::g->chat_on) + return false; + //M_SizeDisplay(0); + S_StartSound(NULL,sfx_stnmov); + return true; + + case KEY_EQUALS: // Screen size up + if (::g->automapactive || ::g->chat_on) + return false; + //M_SizeDisplay(1); + S_StartSound(NULL,sfx_stnmov); + return true; + + case KEY_F1: // Help key + M_StartControlPanel (); + + if ( ::g->gamemode == retail ) + ::g->currentMenu = &::g->ReadDef2; + else + ::g->currentMenu = &::g->ReadDef1; + + ::g->itemOn = 0; + S_StartSound(NULL,sfx_swtchn); + return true; + + case KEY_F2: // Save + M_StartControlPanel(); + S_StartSound(NULL,sfx_swtchn); + M_SaveGame(0); + return true; + + case KEY_F3: // Load + M_StartControlPanel(); + S_StartSound(NULL,sfx_swtchn); + M_LoadGame(0); + return true; + + case KEY_F4: // Sound Volume + M_StartControlPanel (); + ::g->currentMenu = &::g->SoundDef; + ::g->itemOn = sfx_vol; + S_StartSound(NULL,sfx_swtchn); + return true; + + case KEY_F5: // Detail toggle + M_ChangeDetail(0); + S_StartSound(NULL,sfx_swtchn); + return true; + + case KEY_F6: // Quicksave + S_StartSound(NULL,sfx_swtchn); + M_QuickSave(); + return true; + + case KEY_F7: // End game + S_StartSound(NULL,sfx_swtchn); + M_EndGame(0); + return true; + + case KEY_F8: // Toggle messages + M_ChangeMessages(0); + S_StartSound(NULL,sfx_swtchn); + return true; + + case KEY_F9: // Quickload + S_StartSound(NULL,sfx_swtchn); + M_QuickLoad(); + return true; + + case KEY_F10: // Quit DOOM + S_StartSound(NULL,sfx_swtchn); + M_QuitDOOM(0); + return true; + + case KEY_F11: // gamma toggle + ::g->usegamma++; + if (::g->usegamma > 4) + ::g->usegamma = 0; + ::g->players[::g->consoleplayer].message = gammamsg[::g->usegamma]; + I_SetPalette ((byte*)W_CacheLumpName ("PLAYPAL",PU_CACHE_SHARED)); + return true; + + } +*/ + + // Pop-up menu? + if (!::g->menuactive) + { + if (ch == KEY_ESCAPE && ( ::g->gamestate == GS_LEVEL || ::g->gamestate == GS_INTERMISSION || ::g->gamestate == GS_FINALE ) ) + { + M_StartControlPanel (); + + S_StartSound(NULL,sfx_swtchn); + return true; + } + + return false; + } + + // Keys usable within menu + switch (ch) + { + case KEY_DOWNARROW: + do + { + if (::g->itemOn+1 > ::g->currentMenu->numitems-1) + ::g->itemOn = 0; + else ::g->itemOn++; + S_StartSound(NULL,sfx_pstop); + } while(::g->currentMenu->menuitems[::g->itemOn].status==-1); + return true; + + case KEY_UPARROW: + do + { + if (!::g->itemOn) + ::g->itemOn = ::g->currentMenu->numitems-1; + else ::g->itemOn--; + S_StartSound(NULL,sfx_pstop); + } while(::g->currentMenu->menuitems[::g->itemOn].status==-1); + return true; + + case KEY_LEFTARROW: + if (::g->currentMenu->menuitems[::g->itemOn].routine && + ::g->currentMenu->menuitems[::g->itemOn].status == 2) + { + S_StartSound(NULL,sfx_stnmov); + ::g->currentMenu->menuitems[::g->itemOn].routine(0); + } + return true; + + case KEY_RIGHTARROW: + if (::g->currentMenu->menuitems[::g->itemOn].routine && + ::g->currentMenu->menuitems[::g->itemOn].status == 2) + { + S_StartSound(NULL,sfx_stnmov); + ::g->currentMenu->menuitems[::g->itemOn].routine(1); + } + return true; + + case KEY_ENTER: + if (::g->currentMenu->menuitems[::g->itemOn].routine && + ::g->currentMenu->menuitems[::g->itemOn].status) + { + ::g->currentMenu->lastOn = ::g->itemOn; + if (::g->currentMenu->menuitems[::g->itemOn].status == 2) + { + ::g->currentMenu->menuitems[::g->itemOn].routine(1); // right arrow + S_StartSound(NULL,sfx_stnmov); + } + else + { + ::g->currentMenu->menuitems[::g->itemOn].routine(::g->itemOn); + S_StartSound(NULL,sfx_pistol); + } + } + return true; + + case KEY_ESCAPE: + case KEY_BACKSPACE: + ::g->currentMenu->lastOn = ::g->itemOn; + if (::g->currentMenu->prevMenu) + { + ::g->currentMenu = ::g->currentMenu->prevMenu; + ::g->itemOn = ::g->currentMenu->lastOn; + S_StartSound(NULL,sfx_swtchn); + } else if ( ::g->currentMenu == &::g->MainDef && ( !::g->demoplayback && ::g->gamestate != GS_DEMOSCREEN ) ) { + M_ClearMenus(); + ::g->paused = false; + } + return true; + + default: + for (i = ::g->itemOn+1;i < ::g->currentMenu->numitems;i++) + if (::g->currentMenu->menuitems[i].alphaKey == ch) + { + ::g->itemOn = i; + S_StartSound(NULL,sfx_pstop); + return true; + } + for (i = 0;i <= ::g->itemOn;i++) + if (::g->currentMenu->menuitems[i].alphaKey == ch) + { + ::g->itemOn = i; + S_StartSound(NULL,sfx_pstop); + return true; + } + break; + + } + + return false; +} + + + +// +// M_StartControlPanel +// +void M_StartControlPanel (void) +{ + // intro might call this repeatedly + if (::g->menuactive) + return; + + ::g->menuactive = 1; + ::g->currentMenu = &::g->MainDef; + ::g->itemOn = ::g->currentMenu->lastOn; +} + + +// +// M_Drawer +// Called after the view has been rendered, +// but before it has been blitted. +// +void M_Drawer (void) +{ + unsigned short i; + short max; + char string[40]; + int start; + + ::g->inhelpscreens = false; + + + // Horiz. & Vertically center string and print it. + if (::g->messageToPrint) + { + start = 0; + ::g->md_y = 100 - M_StringHeight(::g->messageString)/2; + while(*(::g->messageString+start)) + { + for (i = 0;i < strlen(::g->messageString+start);i++) + if (*(::g->messageString+start+i) == '\n') + { + memset(string,0,40); + strncpy(string,::g->messageString+start,i); + start += i+1; + break; + } + + if (i == strlen(::g->messageString+start)) + { + strcpy(string,::g->messageString+start); + start += i; + } + + ::g->md_x = 160 - M_StringWidth(string)/2; + M_WriteText(::g->md_x,::g->md_y,string); + ::g->md_y += SHORT(::g->hu_font[0]->height); + } + return; + } + + + if (!::g->menuactive) + return; + + if (::g->currentMenu->routine) + ::g->currentMenu->routine(); // call Draw routine + + // DRAW MENU + ::g->md_x = ::g->currentMenu->x; + ::g->md_y = ::g->currentMenu->y; + max = ::g->currentMenu->numitems; + + for (i=0;icurrentMenu->menuitems[i].name[0]) + V_DrawPatchDirect (::g->md_x,::g->md_y,0, + (patch_t*)W_CacheLumpName(::g->currentMenu->menuitems[i].name ,PU_CACHE_SHARED)); + ::g->md_y += LINEHEIGHT; + } + + + // DRAW SKULL + V_DrawPatchDirect(::g->md_x + SKULLXOFF,::g->currentMenu->y - 5 + ::g->itemOn*LINEHEIGHT, 0, + (patch_t*)W_CacheLumpName(skullName[::g->whichSkull],PU_CACHE_SHARED)); +} + + +// +// M_ClearMenus +// +void M_ClearMenus (void) +{ + ::g->menuactive = 0; + // if (!::g->netgame && ::g->usergame && ::g->paused) + // ::g->sendpause = true; +} + + + + +// +// M_SetupNextMenu +// +void M_SetupNextMenu(menu_t *menudef) +{ + ::g->currentMenu = menudef; + ::g->itemOn = ::g->currentMenu->lastOn; +} + + +// +// M_Ticker +// +void M_Ticker (void) +{ + if (--::g->skullAnimCounter <= 0) + { + ::g->whichSkull ^= 1; + ::g->skullAnimCounter = 8; + } +} + + +// +// M_Init +// +void M_Init (void) +{ + + ::g->currentMenu = &::g->MainDef; + ::g->menuactive = 1; + ::g->itemOn = ::g->currentMenu->lastOn; + ::g->whichSkull = 0; + ::g->skullAnimCounter = 10; + ::g->screenSize = ::g->screenblocks - 3; + ::g->messageToPrint = 0; + ::g->messageString = NULL; + ::g->messageLastMenuActive = ::g->menuactive; + ::g->quickSaveSlot = -1; + + // Here we could catch other version dependencies, + // like HELP1/2, and four episodes. + + + switch ( ::g->gamemode ) + { + case commercial: + // This is used because DOOM 2 had only one HELP + // page. I use CREDIT as second page now, but + // kept this hack for educational purposes. + //::g->MainMenu[readthis] = ::g->MainMenu[quitdoom]; + //::g->MainDef.numitems--; + ::g->MainDef.y += 8; + ::g->NewDef.prevMenu = &::g->MainDef; + //::g->ReadDef1.routine = M_DrawReadThis1; + //::g->ReadDef1.x = 330; + //::g->ReadDef1.y = 165; + //::g->ReadMenu1[0].routine = M_FinishReadThis; + break; + case shareware: + // Episode 2 and 3 are handled, + // branching to an ad screen. + case registered: + // We need to remove the fourth episode. + ::g->EpiDef.numitems--; + break; + case retail: + // We are fine. + default: + break; + } +} + + diff --git a/doomclassic/doom/m_menu.h b/doomclassic/doom/m_menu.h new file mode 100644 index 00000000..aa7ad06f --- /dev/null +++ b/doomclassic/doom/m_menu.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_MENU__ +#define __M_MENU__ + + + +#include "d_event.h" + +// +// MENUS +// +// Called by main loop, +// saves config file and calls I_Quit when user exits. +// Even when the menu is not displayed, +// this can resize the view and change game parameters. +// Does all the real work of the menu interaction. +qboolean M_Responder (event_t *ev); + + +// Called by main loop, +// only used for menu (skull cursor) animation. +void M_Ticker (void); + +// Called by main loop, +// draws the menus directly into the screen buffer. +void M_Drawer (void); + +// Called by D_DoomMain, +// loads the config file. +void M_Init (void); + +// Called by intro code to force menu up upon a keypress, +// does nothing if menu is already up. +void M_StartControlPanel (void); + + + + + + +#endif + diff --git a/doomclassic/doom/m_misc.cpp b/doomclassic/doom/m_misc.cpp new file mode 100644 index 00000000..e45e4c5e --- /dev/null +++ b/doomclassic/doom/m_misc.cpp @@ -0,0 +1,372 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include +#include +#include + +#include + + +#include "doomdef.h" +#include "g_game.h" +#include "z_zone.h" + +#include "m_swap.h" +#include "m_argv.h" + +#include "w_wad.h" + +#include "i_system.h" +#include "i_video.h" +#include "v_video.h" + +#include "hu_stuff.h" + +// State. +#include "doomstat.h" + +// Data. +#include "dstrings.h" + +#include "m_misc.h" +#include "d3xp/Game_local.h" + +// +// M_DrawText +// Returns the final X coordinate +// HU_Init must have been called to init the font +// + +int +M_DrawText +( int x, + int y, + qboolean direct, + char* string ) +{ + int c; + int w; + + while (*string) + { + c = toupper(*string) - HU_FONTSTART; + string++; + if (c < 0 || c> HU_FONTSIZE) + { + x += 4; + continue; + } + + w = SHORT (::g->hu_font[c]->width); + if (x+w > SCREENWIDTH) + break; + if (direct) + V_DrawPatchDirect(x, y, 0, ::g->hu_font[c]); + else + V_DrawPatch(x, y, 0, ::g->hu_font[c]); + x+=w; + } + + return x; +} + + +// +// M_WriteFile +// +boolean M_WriteFile ( char const* name, void* source, int length ) { + + idFile * handle = NULL; + int count; + + handle = fileSystem->OpenFileWrite( name, "fs_savepath" ); + + if (handle == NULL ) + return false; + + count = handle->Write( source, length ); + fileSystem->CloseFile( handle ); + + if (count < length) + return false; + + return true; +} + + +// +// M_ReadFile +// +int M_ReadFile ( char const* name, byte** buffer ) { + int count, length; + idFile * handle = NULL; + byte *buf; + + handle = fileSystem->OpenFileRead( name, false ); + + if (handle == NULL ) { + I_Error ("Couldn't read file %s", name); + } + + length = handle->Length(); + + buf = ( byte* )Z_Malloc ( handle->Length(), PU_STATIC, NULL); + count = handle->Read( buf, length ); + + if (count < length ) { + I_Error ("Couldn't read file %s", name); + } + + fileSystem->CloseFile( handle ); + + *buffer = buf; + return length; +} + +// +// Write a save game to the specified device using the specified game name. +// +static qboolean SaveGame( void* source, DWORD length ) +{ + return false; +} + + +qboolean M_WriteSaveGame( void* source, int length ) +{ + return SaveGame( source, length ); +} + +int M_ReadSaveGame( byte** buffer ) +{ + return 0; +} + + +// +// DEFAULTS +// + + + + + + + + + + + +// machine-independent sound params + + +// UNIX hack, to be removed. +#ifdef SNDSERV +#endif + +#ifdef LINUX +#endif + +extern const char* const temp_chat_macros[]; + + + + + + + +// +// M_SaveDefaults +// +void M_SaveDefaults (void) +{ +/* + int i; + int v; + FILE* f; + + f = f o pen (::g->defaultfile, "w"); + if (!f) + return; // can't write the file, but don't complain + + for (i=0 ; i<::g->numdefaults ; i++) + { + if (::g->defaults[i].defaultvalue > -0xfff + && ::g->defaults[i].defaultvalue < 0xfff) + { + v = *::g->defaults[i].location; + fprintf (f,"%s\t\t%i\n",::g->defaults[i].name,v); + } else { + fprintf (f,"%s\t\t\"%s\"\n",::g->defaults[i].name, + * (char **) (::g->defaults[i].location)); + } + } + + fclose (f); +*/ +} + + +// +// M_LoadDefaults +// + +void M_LoadDefaults (void) +{ + int i; + //int len; + //FILE* f; + //char def[80]; + //char strparm[100]; + //char* newstring; + //int parm; + //qboolean isstring; + + // set everything to base values + ::g->numdefaults = sizeof(::g->defaults)/sizeof(::g->defaults[0]); + for (i=0 ; i < ::g->numdefaults ; i++) + *::g->defaults[i].location = ::g->defaults[i].defaultvalue; + + // check for a custom default file + i = M_CheckParm ("-config"); + if (i && i < ::g->myargc-1) + { + ::g->defaultfile = ::g->myargv[i+1]; + I_Printf (" default file: %s\n",::g->defaultfile); + } + else + ::g->defaultfile = ::g->basedefault; + +/* + // read the file in, overriding any set ::g->defaults + f = f o pen (::g->defaultfile, "r"); + if (f) + { + while (!feof(f)) + { + isstring = false; + if (fscanf (f, "%79s %[^\n]\n", def, strparm) == 2) + { + if (strparm[0] == '"') + { + // get a string default + isstring = true; + len = strlen(strparm); + newstring = (char *)DoomLib::Z_Malloc(len, PU_STATIC, 0); + strparm[len-1] = 0; + strcpy(newstring, strparm+1); + } + else if (strparm[0] == '0' && strparm[1] == 'x') + sscanf(strparm+2, "%x", &parm); + else + sscanf(strparm, "%i", &parm); + + for (i=0 ; i<::g->numdefaults ; i++) + if (!strcmp(def, ::g->defaults[i].name)) + { + if (!isstring) + *::g->defaults[i].location = parm; + else + *::g->defaults[i].location = (int) newstring; + break; + } + } + } + + fclose (f); + } +*/ +} + + +// +// SCREEN SHOTS +// + + + + +// +// WritePCXfile +// +void +WritePCXfile +( char* filename, + byte* data, + int width, + int height, + byte* palette ) +{ + I_Error( "depreciated" ); +} + + +// +// M_ScreenShot +// +void M_ScreenShot (void) +{ +/* + int i; + byte* linear; + char lbmname[12]; + + // munge planar buffer to linear + linear = ::g->screens[2]; + I_ReadScreen (linear); + + // find a file name to save it to + strcpy(lbmname,"DOOM00.pcx"); + + for (i=0 ; i<=99 ; i++) + { + lbmname[4] = i/10 + '0'; + lbmname[5] = i%10 + '0'; + if (_access(lbmname,0) == -1) + break; // file doesn't exist + } + if (i==100) + I_Error ("M_ScreenShot: Couldn't create a PCX"); + + // save the pcx file + WritePCXfile (lbmname, linear, + SCREENWIDTH, SCREENHEIGHT, + (byte*)W_CacheLumpName ("PLAYPAL",PU_CACHE_SHARED)); + + ::g->players[::g->consoleplayer].message = "screen shot"; +*/ +} + + + diff --git a/doomclassic/doom/m_misc.h b/doomclassic/doom/m_misc.h new file mode 100644 index 00000000..5c40531b --- /dev/null +++ b/doomclassic/doom/m_misc.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_MISC__ +#define __M_MISC__ + + +#include "doomtype.h" +// +// MISC +// +boolean +M_WriteFile +( char const* name, + void* source, + int length ); + +int + M_ReadFile + ( char const* name, + byte** buffer ); + +qboolean M_WriteSaveGame( void* source, int length ); +int M_ReadSaveGame ( byte** buffer ); + +void M_ScreenShot (void); + +void M_LoadDefaults (void); +void M_SaveDefaults (void); + + +int +M_DrawText +( int x, + int y, + qboolean direct, + char* string ); + + +#endif + diff --git a/doomclassic/doom/m_random.cpp b/doomclassic/doom/m_random.cpp new file mode 100644 index 00000000..d6de09cd --- /dev/null +++ b/doomclassic/doom/m_random.cpp @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +// +// M_Random +// Returns a 0-255 number +// +const unsigned char rndtable[256] = { + 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66 , + 74, 21, 211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36 , + 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188 , + 52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224 , + 149, 104, 25, 178, 252, 182, 202, 182, 141, 197, 4, 81, 181, 242 , + 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175, 249, 0 , + 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235 , + 25, 92, 20, 145, 138, 77, 69, 166, 78, 176, 173, 212, 166, 113 , + 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, 171, 75 , + 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196 , + 135, 106, 63, 197, 195, 86, 96, 203, 113, 101, 170, 247, 181, 113 , + 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, 166, 103, 241 , + 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224 , + 145, 224, 81, 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95 , + 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14, 109, 226 , + 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36 , + 17, 46, 52, 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106 , + 197, 242, 98, 43, 39, 175, 254, 145, 190, 84, 118, 222, 187, 136 , + 120, 163, 236, 249 +}; + + +// Which one is deterministic? +int P_Random (void) +{ + ::g->prndindex = (::g->prndindex+1)&0xff; + return rndtable[::g->prndindex]; +} + +int M_Random (void) +{ + ::g->rndindex = (::g->rndindex+1)&0xff; + return rndtable[::g->rndindex]; +} + +void M_ClearRandom (void) +{ + ::g->rndindex = ::g->prndindex = 0; +} + + + + + diff --git a/doomclassic/doom/m_random.h b/doomclassic/doom/m_random.h new file mode 100644 index 00000000..8be4593f --- /dev/null +++ b/doomclassic/doom/m_random.h @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_RANDOM__ +#define __M_RANDOM__ + + +#include "doomtype.h" + + + +// Returns a number from 0 to 255, +// from a lookup table. +int M_Random (void); + +// As M_Random, but used only by the play simulation. +int P_Random (void); + +// Fix randoms for demos. +void M_ClearRandom (void); + + +#endif + diff --git a/doomclassic/doom/m_swap.cpp b/doomclassic/doom/m_swap.cpp new file mode 100644 index 00000000..b7ddb1d1 --- /dev/null +++ b/doomclassic/doom/m_swap.cpp @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#ifdef __GNUG__ +#pragma implementation "m_swap.h" +#endif +#include "m_swap.h" + + +// Not needed with big endian. +#ifdef __BIG_ENDIAN__ + +#endif + + + diff --git a/doomclassic/doom/m_swap.h b/doomclassic/doom/m_swap.h new file mode 100644 index 00000000..9ecc3aff --- /dev/null +++ b/doomclassic/doom/m_swap.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __M_SWAP__ +#define __M_SWAP__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// Endianess handling. +// WAD files are stored little endian. +#ifdef __BIG_ENDIAN__ +//short SwapSHORT(short); +//long SwapLONG(long); + +// Swap 16bit, that is, MSB and LSB byte. +inline unsigned short SwapSHORT(unsigned short x) +{ + // No masking with 0xFF should be necessary. + return (x>>8) | (x<<8); +} + +// Swapping 32bit. +inline unsigned long SwapLONG( unsigned long x) +{ + return + (x>>24) + | ((x>>8) & 0xff00) + | ((x<<8) & 0xff0000) + | (x<<24); +} + + +#define SHORT(x) ((short)SwapSHORT((unsigned short) (x))) +#define LONG(x) ((long)SwapLONG((unsigned long) (x))) +#else +#define SHORT(x) (x) +#define LONG(x) (x) +#endif + + + + +#endif + diff --git a/doomclassic/doom/mus2midi.cpp b/doomclassic/doom/mus2midi.cpp new file mode 100644 index 00000000..6bc70341 --- /dev/null +++ b/doomclassic/doom/mus2midi.cpp @@ -0,0 +1,358 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" +#include +#include +#include +#include "idlib/sys/sys_defines.h" +// mus header + + +// reads a variable length integer +unsigned long ReadVarLen( char* buffer ) { + unsigned long value; + byte c; + + if ((value = *buffer++) & 0x80) { + value &= 0x7f; + do { + value = (value << 7) + ((c = *buffer++) & 0x7f); + } while (c & 0x80); + } + return value; +} + +// Writes a variable length integer to a buffer, and returns bytes written +int WriteVarLen( long value, byte* out ) +{ + long buffer, count = 0; + + buffer = value & 0x7f; + while ((value >>= 7) > 0) { + buffer <<= 8; + buffer += 0x80; + buffer += (value & 0x7f); + } + + while (1) { + ++count; + *out = (byte)buffer; + ++out; + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + return count; +} + +// writes a byte, and returns the buffer +unsigned char* WriteByte(void* buf, byte b) +{ + unsigned char* buffer = (unsigned char*)buf; + *buffer++ = b; + return buffer; +} + +unsigned char* WriteShort(void* b, unsigned short s) +{ + unsigned char* buffer = (unsigned char*)b; + *buffer++ = (s >> 8); + *buffer++ = (s & 0x00FF); + return buffer; +} + +unsigned char* WriteInt(void* b, unsigned int i) +{ + unsigned char* buffer = (unsigned char*)b; + *buffer++ = (i & 0xff000000) >> 24; + *buffer++ = (i & 0x00ff0000) >> 16; + *buffer++ = (i & 0x0000ff00) >> 8; + *buffer++ = (i & 0x000000ff); + return buffer; +} + +// Format - 0(1 track only), 1(1 or more tracks, each play same time), 2(1 or more, each play seperatly) +void Midi_CreateHeader(MidiHeaderChunk_t* header, short format, short track_count, short division) +{ + WriteInt(header->name, 'MThd'); + WriteInt(&header->length, 6); + WriteShort(&header->format, format); + WriteShort(&header->ntracks, track_count); + WriteShort(&header->division, division); +} + +unsigned char* Midi_WriteTempo(unsigned char* buffer, int tempo) +{ + buffer = WriteByte(buffer, 0x00); // delta time + buffer = WriteByte(buffer, 0xff); // sys command + buffer = WriteShort(buffer, 0x5103); // command - set tempo + + buffer = WriteByte(buffer, tempo & 0x000000ff); + buffer = WriteByte(buffer, (tempo & 0x0000ff00) >> 8); + buffer = WriteByte(buffer, (tempo & 0x00ff0000) >> 16); + + return buffer; +} + +int Midi_UpdateBytesWritten(int* bytes_written, int to_add, int max) +{ + *bytes_written += to_add; + if (max && *bytes_written > max) + { + assert(0); + return 0; + } + return 1; +} + +unsigned char MidiMap[] = +{ + 0, // prog change + 0, // bank sel + 1, //2 // mod pot + 0x07, //3 // volume + 0x0A, //4 // pan pot + 0x0B, //5 // expression pot + 0x5B, //6 // reverb depth + 0x5D, //7 // chorus depth + 0x40, //8 // sustain pedal + 0x43, //9 // soft pedal + 0x78, //10 // all sounds off + 0x7B, //11 // all notes off + 0x7E, //12 // mono(use numchannels + 1) + 0x7F, //13 // poly + 0x79, //14 // reset all controllers +}; + +// The MUS data is stored in little-endian. +namespace { + unsigned short LittleToNative( const unsigned short value ) { + return value; + } +} + +int Mus2Midi(unsigned char* bytes, unsigned char* out, int* len) +{ + // mus header and instruments + MUSheader_t header; + + // current position in read buffer + unsigned char* cur = bytes,* end; + + // Midi header(format 0) + MidiHeaderChunk_t midiHeader; + // Midi track header, only 1 needed(format 0) + MidiTrackChunk_t midiTrackHeader; + // Stores the position of the midi track header(to change the size) + byte* midiTrackHeaderOut; + + // Delta time for midi event + int delta_time = 0; + int temp; + int channel_volume[MIDI_MAXCHANNELS] = {0}; + int bytes_written = 0; + int channelMap[MIDI_MAXCHANNELS], currentChannel = 0; + byte last_status = 0; + + // read the mus header + memcpy(&header, cur, sizeof(header)); + cur += sizeof(header); + + header.scoreLen = LittleToNative( header.scoreLen ); + header.scoreStart = LittleToNative( header.scoreStart ); + header.channels = LittleToNative( header.channels ); + header.sec_channels = LittleToNative( header.sec_channels ); + header.instrCnt = LittleToNative( header.instrCnt ); + header.dummy = LittleToNative( header.dummy ); + + // only 15 supported + if (header.channels > MIDI_MAXCHANNELS - 1) + return 0; + + // Map channel 15 to 9(percussions) + for (temp = 0; temp < MIDI_MAXCHANNELS; ++temp) { + channelMap[temp] = -1; + channel_volume[temp] = 0x40; + } + channelMap[15] = 9; + + // Get current position, and end of position + cur = bytes + header.scoreStart; + end = cur + header.scoreLen; + + // Write out midi header + Midi_CreateHeader(&midiHeader, 0, 1, 0x0059); + Midi_UpdateBytesWritten(&bytes_written, MIDIHEADERSIZE, *len); + memcpy(out, &midiHeader, MIDIHEADERSIZE); // cannot use sizeof(packs it to 16 bytes) + out += MIDIHEADERSIZE; + + // Store this position, for later filling in the midiTrackHeader + Midi_UpdateBytesWritten(&bytes_written, sizeof(midiTrackHeader), *len); + midiTrackHeaderOut = out; + out += sizeof(midiTrackHeader); + + + // microseconds per quarter note(yikes) + Midi_UpdateBytesWritten(&bytes_written, 7, *len); + out = Midi_WriteTempo(out, 0x001aa309); + + // Percussions channel starts out at full volume + Midi_UpdateBytesWritten(&bytes_written, 4, *len); + out = WriteByte(out, 0x00); + out = WriteByte(out, 0xB9); + out = WriteByte(out, 0x07); + out = WriteByte(out, 127); + + // Main Loop + while (cur < end) { + byte channel; + byte event; + byte temp_buffer[32]; // temp buffer for current iterator + byte *out_local = temp_buffer; + byte status, bit1, bit2, bitc = 2; + + // Read in current bit + event = *cur++; + channel = (event & 15); // current channel + + // Write variable length delta time + out_local += WriteVarLen(delta_time, out_local); + + if (channelMap[channel] < 0) { + // Set all channels to 127 volume + out_local = WriteByte(out_local, 0xB0 + currentChannel); + out_local = WriteByte(out_local, 0x07); + out_local = WriteByte(out_local, 127); + out_local = WriteByte(out_local, 0x00); + + channelMap[channel] = currentChannel++; + if (currentChannel == 9) + ++currentChannel; + } + + status = channelMap[channel]; + + // Handle ::g->events + switch ((event & 122) >> 4) + { + default: + assert(0); + break; + case MUSEVENT_KEYOFF: + status |= 0x80; + bit1 = *cur++; + bit2 = 0x40; + break; + case MUSEVENT_KEYON: + status |= 0x90; + bit1 = *cur & 127; + if (*cur++ & 128) // volume bit? + channel_volume[channelMap[channel]] = *cur++; + bit2 = channel_volume[channelMap[channel]]; + break; + case MUSEVENT_PITCHWHEEL: + status |= 0xE0; + bit1 = (*cur & 1) >> 6; + bit2 = (*cur++ >> 1) & 127; + break; + case MUSEVENT_CHANNELMODE: + status |= 0xB0; + assert(*cur < sizeof(MidiMap) / sizeof(MidiMap[0])); + bit1 = MidiMap[*cur++]; + bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; + break; + case MUSEVENT_CONTROLLERCHANGE: + if (*cur == 0) { + cur++; + status |= 0xC0; + bit1 = *cur++; + bitc = 1; + } else { + status |= 0xB0; + assert(*cur < sizeof(MidiMap) / sizeof(MidiMap[0])); + bit1 = MidiMap[*cur++]; + bit2 = *cur++; + } + break; + case 5: // Unknown + assert(0); + break; + case MUSEVENT_END: // End + status = 0xff; + bit1 = 0x2f; + bit2 = 0x00; + assert(cur == end); + break; + case 7: // Unknown + assert(0); + break; + } + + // Write it out + out_local = WriteByte(out_local, status); + out_local = WriteByte(out_local, bit1); + if (bitc == 2) + out_local = WriteByte(out_local, bit2); + + + // Write out temp stuff + if (out_local != temp_buffer) + { + Midi_UpdateBytesWritten(&bytes_written, out_local - temp_buffer, *len); + memcpy(out, temp_buffer, out_local - temp_buffer); + out += out_local - temp_buffer; + } + + if (event & 128) { + delta_time = 0; + do { + delta_time = delta_time * 128 + (*cur & 127); + } while ((*cur++ & 128)); + } else { + delta_time = 0; + } + } + + // Write out track header + WriteInt(midiTrackHeader.name, 'MTrk'); + WriteInt(&midiTrackHeader.length, out - midiTrackHeaderOut - sizeof(midiTrackHeader)); + memcpy(midiTrackHeaderOut, &midiTrackHeader, sizeof(midiTrackHeader)); + + // Store length written + *len = bytes_written; + /*{ + FILE* file = f o pen("d:\\test.midi", "wb"); + fwrite(midiTrackHeaderOut - sizeof(MidiHeaderChunk_t), bytes_written, 1, file); + fclose(file); + }*/ + return 1; +} + diff --git a/doomclassic/doom/p_ceilng.cpp b/doomclassic/doom/p_ceilng.cpp new file mode 100644 index 00000000..4572580f --- /dev/null +++ b/doomclassic/doom/p_ceilng.cpp @@ -0,0 +1,341 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "z_zone.h" +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "sounds.h" + +// +// CEILINGS +// + + + + +// +// T_MoveCeiling +// + +void T_MoveCeiling (ceiling_t* ceiling) +{ + result_e res; + + switch(ceiling->direction) + { + case 0: + // IN STASIS + break; + case 1: + // UP + res = T_MovePlane(ceiling->sector, + ceiling->speed, + ceiling->topheight, + false,1,ceiling->direction); + + if (!(::g->leveltime&7)) + { + switch(ceiling->type) + { + case silentCrushAndRaise: + break; + default: + S_StartSound( &ceiling->sector->soundorg, + sfx_stnmov); + // ? + break; + } + } + + if (res == pastdest) + { + switch(ceiling->type) + { + case raiseToHighest: + P_RemoveActiveCeiling(ceiling); + break; + + case silentCrushAndRaise: + S_StartSound( &ceiling->sector->soundorg, + sfx_pstop); + case fastCrushAndRaise: + case crushAndRaise: + ceiling->direction = -1; + break; + + default: + break; + } + + } + break; + + case -1: + // DOWN + res = T_MovePlane(ceiling->sector, + ceiling->speed, + ceiling->bottomheight, + ceiling->crush,1,ceiling->direction); + + if (!(::g->leveltime&7)) + { + switch(ceiling->type) + { + case silentCrushAndRaise: break; + default: + S_StartSound( &ceiling->sector->soundorg, + sfx_stnmov); + } + } + + if (res == pastdest) + { + switch(ceiling->type) + { + case silentCrushAndRaise: + S_StartSound( &ceiling->sector->soundorg, + sfx_pstop); + case crushAndRaise: + ceiling->speed = CEILSPEED; + case fastCrushAndRaise: + ceiling->direction = 1; + break; + + case lowerAndCrush: + case lowerToFloor: + P_RemoveActiveCeiling(ceiling); + break; + + default: + break; + } + } + else // ( res != pastdest ) + { + if (res == crushed) + { + switch(ceiling->type) + { + case silentCrushAndRaise: + case crushAndRaise: + case lowerAndCrush: + ceiling->speed = CEILSPEED / 8; + break; + + default: + break; + } + } + } + break; + } +} + + +// +// EV_DoCeiling +// Move a ceiling up/down and all around! +// +int +EV_DoCeiling +( line_t* line, + ceiling_e type ) +{ + int secnum; + int rtn; + sector_t* sec; + ceiling_t* ceiling; + + secnum = -1; + rtn = 0; + + // Reactivate in-stasis ceilings...for certain types. + switch(type) + { + case fastCrushAndRaise: + case silentCrushAndRaise: + case crushAndRaise: + P_ActivateInStasisCeiling(line); + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = &::g->sectors[secnum]; + if (sec->specialdata) + continue; + + // new door thinker + rtn = 1; + ceiling = (ceiling_t*)DoomLib::Z_Malloc(sizeof(*ceiling), PU_LEVEL, 0); + P_AddThinker (&ceiling->thinker); + sec->specialdata = ceiling; + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + ceiling->sector = sec; + ceiling->crush = false; + + switch(type) + { + case fastCrushAndRaise: + ceiling->crush = true; + ceiling->topheight = sec->ceilingheight; + ceiling->bottomheight = sec->floorheight + (8*FRACUNIT); + ceiling->direction = -1; + ceiling->speed = CEILSPEED * 2; + break; + + case silentCrushAndRaise: + case crushAndRaise: + ceiling->crush = true; + ceiling->topheight = sec->ceilingheight; + case lowerAndCrush: + case lowerToFloor: + ceiling->bottomheight = sec->floorheight; + if (type != lowerToFloor) + ceiling->bottomheight += 8*FRACUNIT; + ceiling->direction = -1; + ceiling->speed = CEILSPEED; + break; + + case raiseToHighest: + ceiling->topheight = P_FindHighestCeilingSurrounding(sec); + ceiling->direction = 1; + ceiling->speed = CEILSPEED; + break; + } + + ceiling->tag = sec->tag; + ceiling->type = type; + P_AddActiveCeiling(ceiling); + } + return rtn; +} + + +// +// Add an active ceiling +// +void P_AddActiveCeiling(ceiling_t* c) +{ + int i; + + for (i = 0; i < MAXCEILINGS;i++) + { + if (::g->activeceilings[i] == NULL) + { + ::g->activeceilings[i] = c; + return; + } + } +} + + + +// +// Remove a ceiling's thinker +// +void P_RemoveActiveCeiling(ceiling_t* c) +{ + int i; + + for (i = 0;i < MAXCEILINGS;i++) + { + if (::g->activeceilings[i] == c) + { + ::g->activeceilings[i]->sector->specialdata = NULL; + P_RemoveThinker (&::g->activeceilings[i]->thinker); + ::g->activeceilings[i] = NULL; + break; + } + } +} + + + +// +// Restart a ceiling that's in-stasis +// +void P_ActivateInStasisCeiling(line_t* line) +{ + int i; + + for (i = 0;i < MAXCEILINGS;i++) + { + if (::g->activeceilings[i] + && (::g->activeceilings[i]->tag == line->tag) + && (::g->activeceilings[i]->direction == 0)) + { + ::g->activeceilings[i]->direction = ::g->activeceilings[i]->olddirection; + ::g->activeceilings[i]->thinker.function.acp1 + = (actionf_p1)T_MoveCeiling; + } + } +} + + + +// +// EV_CeilingCrushStop +// Stop a ceiling from crushing! +// +int EV_CeilingCrushStop(line_t *line) +{ + int i; + int rtn; + + rtn = 0; + for (i = 0;i < MAXCEILINGS;i++) + { + if (::g->activeceilings[i] + && (::g->activeceilings[i]->tag == line->tag) + && (::g->activeceilings[i]->direction != 0)) + { + ::g->activeceilings[i]->olddirection = ::g->activeceilings[i]->direction; + ::g->activeceilings[i]->thinker.function.acv = (actionf_v)NULL; + ::g->activeceilings[i]->direction = 0; // in-stasis + rtn = 1; + } + } + + + return rtn; +} + diff --git a/doomclassic/doom/p_doors.cpp b/doomclassic/doom/p_doors.cpp new file mode 100644 index 00000000..99fa4363 --- /dev/null +++ b/doomclassic/doom/p_doors.cpp @@ -0,0 +1,560 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "z_zone.h" +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + + +extern bool globalNetworking; + +// +// VERTICAL DOORS +// + +// +// T_VerticalDoor +// +void T_VerticalDoor (vldoor_t* door) +{ + result_e res; + + switch(door->direction) + { + case 0: + // WAITING + if (!--door->topcountdown) + { + switch(door->type) + { + case blazeRaise: + door->direction = -1; // time to go back down + S_StartSound( &door->sector->soundorg, + sfx_bdcls); + break; + + case normal: + door->direction = -1; // time to go back down + S_StartSound( &door->sector->soundorg, + sfx_dorcls); + break; + + case close30ThenOpen: + door->direction = 1; + S_StartSound( &door->sector->soundorg, + sfx_doropn); + break; + + default: + break; + } + } + break; + + case 2: + // INITIAL WAIT + if (!--door->topcountdown) + { + switch(door->type) + { + case raiseIn5Mins: + door->direction = 1; + door->type = normal; + S_StartSound( &door->sector->soundorg, + sfx_doropn); + break; + + default: + break; + } + } + break; + + case -1: + // DOWN + res = T_MovePlane(door->sector, + door->speed, + door->sector->floorheight, + false,1,door->direction); + if (res == pastdest) + { + switch(door->type) + { + case blazeRaise: + case blazeClose: + door->sector->specialdata = NULL; + P_RemoveThinker (&door->thinker); // unlink and free + S_StartSound( &door->sector->soundorg, + sfx_bdcls); + break; + + case normal: + case closed: + door->sector->specialdata = NULL; + P_RemoveThinker (&door->thinker); // unlink and free + break; + + case close30ThenOpen: + door->direction = 0; + door->topcountdown = TICRATE*30; + break; + + default: + break; + } + } + else if (res == crushed) + { + switch(door->type) + { + case blazeClose: + case closed: // DO NOT GO BACK UP! + break; + + default: + door->direction = 1; + S_StartSound( &door->sector->soundorg, + sfx_doropn); + break; + } + } + break; + + case 1: + // UP + res = T_MovePlane(door->sector, + door->speed, + door->topheight, + false,1,door->direction); + + if (res == pastdest) + { + switch(door->type) + { + case blazeRaise: + case normal: + door->direction = 0; // wait at top + door->topcountdown = door->topwait; + break; + + case close30ThenOpen: + case blazeOpen: + case opened: + door->sector->specialdata = NULL; + P_RemoveThinker (&door->thinker); // unlink and free + break; + + default: + break; + } + } + break; + } +} + + +// +// EV_DoLockedDoor +// Move a locked door up/down +// + +int +EV_DoLockedDoor +( line_t* line, + vldoor_e type, + mobj_t* thing ) +{ + player_t* p; + + p = thing->player; + + if (!p) + return 0; + + switch(line->special) + { + case 99: // Blue Lock + case 133: + if ( !p ) + return 0; + if (!p->cards[it_bluecard] && !p->cards[it_blueskull]) + { + p->message = PD_BLUEO; + if (p == &::g->players[::g->consoleplayer]) + S_StartSound(NULL,sfx_oof); + return 0; + } + break; + + case 134: // Red Lock + case 135: + if ( !p ) + return 0; + if (!p->cards[it_redcard] && !p->cards[it_redskull]) + { + p->message = PD_REDO; + if (p == &::g->players[::g->consoleplayer]) + S_StartSound(NULL,sfx_oof); + return 0; + } + break; + + case 136: // Yellow Lock + case 137: + if ( !p ) + return 0; + if (!p->cards[it_yellowcard] && + !p->cards[it_yellowskull]) + { + p->message = PD_YELLOWO; + if (p == &::g->players[::g->consoleplayer]) + S_StartSound(NULL,sfx_oof); + return 0; + } + break; + } + + return EV_DoDoor(line,type); +} + + +int +EV_DoDoor +( line_t* line, + vldoor_e type ) +{ + int secnum,rtn; + sector_t* sec; + vldoor_t* door; + + secnum = -1; + rtn = 0; + + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = &::g->sectors[secnum]; + if (sec->specialdata) + continue; + + + // new door thinker + rtn = 1; + door = (vldoor_t*)DoomLib::Z_Malloc(sizeof(*door), PU_LEVEL, 0); + P_AddThinker (&door->thinker); + sec->specialdata = door; + + door->thinker.function.acp1 = (actionf_p1) T_VerticalDoor; + door->sector = sec; + door->type = type; + door->topwait = VDOORWAIT; + door->speed = VDOORSPEED; + + switch(type) + { + case blazeClose: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->direction = -1; + door->speed = VDOORSPEED * 4; + S_StartSound( &door->sector->soundorg, + sfx_bdcls); + break; + + case closed: + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->direction = -1; + S_StartSound( &door->sector->soundorg, + sfx_dorcls); + break; + + case close30ThenOpen: + door->topheight = sec->ceilingheight; + door->direction = -1; + S_StartSound( &door->sector->soundorg, + sfx_dorcls); + break; + + case blazeRaise: + case blazeOpen: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->speed = VDOORSPEED * 4; + if (door->topheight != sec->ceilingheight) + S_StartSound( &door->sector->soundorg, + sfx_bdopn); + break; + + case normal: + case opened: + door->direction = 1; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + if (door->topheight != sec->ceilingheight) + S_StartSound( &door->sector->soundorg, + sfx_doropn); + break; + + default: + break; + } + + } + return rtn; +} + + +// +// EV_VerticalDoor : open a door manually, no tag value +// +void +EV_VerticalDoor +( line_t* line, + mobj_t* thing ) +{ + player_t* player; + int secnum; + sector_t* sec; + vldoor_t* door; + int side; + + side = 0; // only front ::g->sides can be used + + // Check for locks + player = thing->player; + + switch(line->special) + { + case 26: // Blue Lock + case 32: + if ( !player ) + return; + + if (!player->cards[it_bluecard] && !player->cards[it_blueskull]) + { + player->message = PD_BLUEK; + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound(player->mo,sfx_oof); + return; + } + break; + + case 27: // Yellow Lock + case 34: + if ( !player ) + return; + + if (!player->cards[it_yellowcard] && + !player->cards[it_yellowskull]) + { + player->message = PD_YELLOWK; + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound(player->mo,sfx_oof); + return; + } + break; + + case 28: // Red Lock + case 33: + if ( !player ) + return; + + if (!player->cards[it_redcard] && !player->cards[it_redskull]) + { + player->message = PD_REDK; + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound(player->mo,sfx_oof); + return; + } + break; + } + + // if the sector has an active thinker, use it + sec = ::g->sides[ line->sidenum[side^1]] .sector; + secnum = sec-::g->sectors; + + if (sec->specialdata) + { + door = (vldoor_t*)sec->specialdata; + switch(line->special) + { + case 1: // ONLY FOR "RAISE" DOORS, NOT "OPEN"s + case 26: + case 27: + case 28: + case 117: + if (door->direction == -1) + door->direction = 1; // go back up + else + { + if (!thing->player) + return; // JDC: bad guys never close doors + + door->direction = -1; // start going down immediately + } + return; + } + } + + // for proper sound + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) { + switch(line->special) + { + case 117: // BLAZING DOOR RAISE + case 118: // BLAZING DOOR OPEN + S_StartSound( &sec->soundorg,sfx_bdopn); + break; + + case 1: // NORMAL DOOR SOUND + case 31: + S_StartSound( &sec->soundorg,sfx_doropn); + break; + + default: // LOCKED DOOR SOUND + S_StartSound( &sec->soundorg,sfx_doropn); + break; + } + } + + + // new door thinker + door = (vldoor_t*)DoomLib::Z_Malloc(sizeof(*door), PU_LEVEL, 0); + P_AddThinker (&door->thinker); + sec->specialdata = door; + door->thinker.function.acp1 = (actionf_p1) T_VerticalDoor; + door->sector = sec; + door->direction = 1; + door->speed = VDOORSPEED; + door->topwait = VDOORWAIT; + + switch(line->special) + { + case 1: + case 26: + case 27: + case 28: + door->type = normal; + break; + + case 31: + case 32: + case 33: + case 34: + door->type = opened; + line->special = 0; + break; + + case 117: // blazing door raise + door->type = blazeRaise; + door->speed = VDOORSPEED*4; + break; + case 118: // blazing door open + door->type = blazeOpen; + line->special = 0; + door->speed = VDOORSPEED*4; + break; + } + + // find the top and bottom of the movement range + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; +} + + +// +// Spawn a door that closes after 30 seconds +// +void P_SpawnDoorCloseIn30 (sector_t* sec) +{ + vldoor_t* door; + + door = (vldoor_t*)DoomLib::Z_Malloc( sizeof(*door), PU_LEVEL, 0); + + P_AddThinker (&door->thinker); + + sec->specialdata = door; + sec->special = 0; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 0; + door->type = normal; + door->speed = VDOORSPEED; + door->topcountdown = 30 * TICRATE; +} + +// +// Spawn a door that opens after 5 minutes +// +void +P_SpawnDoorRaiseIn5Mins +( sector_t* sec, + int secnum ) +{ + vldoor_t* door; + + door = (vldoor_t*)DoomLib::Z_Malloc( sizeof(*door), PU_LEVEL, 0); + + P_AddThinker (&door->thinker); + + sec->specialdata = door; + sec->special = 0; + + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + door->sector = sec; + door->direction = 2; + door->type = raiseIn5Mins; + door->speed = VDOORSPEED; + door->topheight = P_FindLowestCeilingSurrounding(sec); + door->topheight -= 4*FRACUNIT; + door->topwait = VDOORWAIT; + door->topcountdown = 5 * 60 * TICRATE; +} + + + +// UNUSED +// Separate into p_slidoor.c? + + diff --git a/doomclassic/doom/p_enemy.cpp b/doomclassic/doom/p_enemy.cpp new file mode 100644 index 00000000..fc3e0e20 --- /dev/null +++ b/doomclassic/doom/p_enemy.cpp @@ -0,0 +1,2028 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +#include "m_random.h" +#include "i_system.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +#include "g_game.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "sounds.h" + + +extern bool globalNetworking; + + + +// +// P_NewChaseDir related LUT. +// +const dirtype_t opposite[] = +{ + DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, + DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR +}; + +const dirtype_t diags[] = +{ + DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST +}; + + +extern "C" void A_Fall (mobj_t *actor, void *); + + +// +// ENEMY THINKING +// Enemies are allways spawned +// with targetplayer = -1, threshold = 0 +// Most monsters are spawned unaware of all ::g->players, +// but some can be made preaware +// + + +// +// Called by P_NoiseAlert. +// Recursively traverse adjacent ::g->sectors, +// sound blocking ::g->lines cut off traversal. +// + + +void +P_RecursiveSound +( sector_t* sec, + int soundblocks ) +{ + int i; + line_t* check; + sector_t* other; + + // wake up all monsters in this sector + if (sec->validcount == ::g->validcount + && sec->soundtraversed <= soundblocks+1) + { + return; // already flooded + } + + sec->validcount = ::g->validcount; + sec->soundtraversed = soundblocks+1; + sec->soundtarget = ::g->soundtarget; + + for (i=0 ;ilinecount ; i++) + { + check = sec->lines[i]; + if (! (check->flags & ML_TWOSIDED) ) + continue; + + P_LineOpening (check); + + if (::g->openrange <= 0) + continue; // closed door + + if ( ::g->sides[ check->sidenum[0] ].sector == sec) + other = ::g->sides[ check->sidenum[1] ] .sector; + else + other = ::g->sides[ check->sidenum[0] ].sector; + + if (check->flags & ML_SOUNDBLOCK) + { + if (!soundblocks) + P_RecursiveSound (other, 1); + } + else + P_RecursiveSound (other, soundblocks); + } +} + + + +// +// P_NoiseAlert +// If a monster yells at a player, +// it will alert other monsters to the player. +// +void +P_NoiseAlert +( mobj_t* target, + mobj_t* emmiter ) +{ + ::g->soundtarget = target; + ::g->validcount++; + P_RecursiveSound (emmiter->subsector->sector, 0); +} + + + + +// +// P_CheckMeleeRange +// +qboolean P_CheckMeleeRange (mobj_t* actor) +{ + mobj_t* pl; + fixed_t dist; + + if (!actor->target) + return false; + + pl = actor->target; + dist = P_AproxDistance (pl->x-actor->x, pl->y-actor->y); + + if (dist >= MELEERANGE-20*FRACUNIT+pl->info->radius) + return false; + + if (! P_CheckSight (actor, actor->target) ) + return false; + + return true; +} + +// +// P_CheckMissileRange +// +qboolean P_CheckMissileRange (mobj_t* actor) +{ + fixed_t dist; + + if (! P_CheckSight (actor, actor->target) ) + return false; + + if ( actor->flags & MF_JUSTHIT ) + { + // the target just hit the enemy, + // so fight back! + actor->flags &= ~MF_JUSTHIT; + return true; + } + + if (actor->reactiontime) + return false; // do not attack yet + + // OPTIMIZE: get this from a global checksight + dist = P_AproxDistance ( actor->x-actor->target->x, + actor->y-actor->target->y) - 64*FRACUNIT; + + if (!actor->info->meleestate) + dist -= 128*FRACUNIT; // no melee attack, so fire more + + dist >>= 16; + + if (actor->type == MT_VILE) + { + if (dist > 14*64) + return false; // too far away + } + + + if (actor->type == MT_UNDEAD) + { + if (dist < 196) + return false; // close for fist attack + dist >>= 1; + } + + + if (actor->type == MT_CYBORG + || actor->type == MT_SPIDER + || actor->type == MT_SKULL) + { + dist >>= 1; + } + + if (dist > 200) + dist = 200; + + if (actor->type == MT_CYBORG && dist > 160) + dist = 160; + + if (P_Random () < dist) + return false; + + return true; +} + + +// +// P_Move +// Move in the current direction, +// returns false if the move is blocked. +// +const fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000}; +const fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000}; + + + +qboolean P_Move (mobj_t* actor) +{ + fixed_t tryx; + fixed_t tryy; + + line_t* ld; + + // warning: 'catch', 'throw', and 'try' + // are all C++ reserved words + qboolean try_ok; + qboolean good; + + if (actor->movedir == DI_NODIR) + return false; + + if ((unsigned)actor->movedir >= 8) + I_Error ("Weird actor->movedir!"); + + tryx = actor->x + actor->info->speed*xspeed[actor->movedir]; + tryy = actor->y + actor->info->speed*yspeed[actor->movedir]; + + try_ok = P_TryMove (actor, tryx, tryy); + + if (!try_ok) + { + // open any specials + if (actor->flags & MF_FLOAT && ::g->floatok) + { + // must adjust height + if (actor->z < ::g->tmfloorz) + actor->z += FLOATSPEED; + else + actor->z -= FLOATSPEED; + + actor->flags |= MF_INFLOAT; + return true; + } + + if (!::g->numspechit) + return false; + + actor->movedir = DI_NODIR; + good = false; + while (::g->numspechit--) + { + ld = ::g->spechit[::g->numspechit]; + // if the special is not a door + // that can be opened, + // return false + if (P_UseSpecialLine (actor, ld,0)) + good = true; + } + return good; + } + else + { + actor->flags &= ~MF_INFLOAT; + } + + + if (! (actor->flags & MF_FLOAT) ) + actor->z = actor->floorz; + return true; +} + + +// +// TryWalk +// Attempts to move actor on +// in its current (ob->moveangle) direction. +// If blocked by either a wall or an actor +// returns FALSE +// If move is either clear or blocked only by a door, +// returns TRUE and sets... +// If a door is in the way, +// an OpenDoor call is made to start it opening. +// +qboolean P_TryWalk (mobj_t* actor) +{ + if (!P_Move (actor)) + { + return false; + } + + actor->movecount = P_Random()&15; + return true; +} + + + + +void P_NewChaseDir (mobj_t* actor) +{ + fixed_t deltax; + fixed_t deltay; + + dirtype_t d[3]; + + int tdir; + dirtype_t olddir; + + dirtype_t turnaround; + + if (!actor->target) + I_Error ("P_NewChaseDir: called with no target"); + + olddir = (dirtype_t)actor->movedir; + turnaround=opposite[olddir]; + + deltax = actor->target->x - actor->x; + deltay = actor->target->y - actor->y; + + if (deltax>10*FRACUNIT) + d[1]= DI_EAST; + else if (deltax<-10*FRACUNIT) + d[1]= DI_WEST; + else + d[1]=DI_NODIR; + + if (deltay<-10*FRACUNIT) + d[2]= DI_SOUTH; + else if (deltay>10*FRACUNIT) + d[2]= DI_NORTH; + else + d[2]=DI_NODIR; + + // try direct route + if (d[1] != DI_NODIR + && d[2] != DI_NODIR) + { + actor->movedir = diags[((deltay<0)<<1)+(deltax>0)]; + if (actor->movedir != turnaround && P_TryWalk(actor)) + return; + } + + // try other directions + if (P_Random() > 200 + || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=(dirtype_t)tdir; + } + + if (d[1]==turnaround) + d[1]=DI_NODIR; + if (d[2]==turnaround) + d[2]=DI_NODIR; + + if (d[1]!=DI_NODIR) + { + actor->movedir = d[1]; + if (P_TryWalk(actor)) + { + // either moved forward or attacked + return; + } + } + + if (d[2]!=DI_NODIR) + { + actor->movedir =d[2]; + + if (P_TryWalk(actor)) + return; + } + + // there is no direct path to the player, + // so pick another direction. + if (olddir!=DI_NODIR) + { + actor->movedir =olddir; + + if (P_TryWalk(actor)) + return; + } + + // randomly determine direction of search + if (P_Random()&1) + { + for ( tdir=DI_EAST; + tdir<=DI_SOUTHEAST; + tdir++ ) + { + if (tdir!=turnaround) + { + actor->movedir =tdir; + + if ( P_TryWalk(actor) ) + return; + } + } + } + else + { + for ( tdir=DI_SOUTHEAST; + tdir != (DI_EAST-1); + tdir-- ) + { + if (tdir!=turnaround) + { + actor->movedir =tdir; + + if ( P_TryWalk(actor) ) + return; + } + } + } + + if (turnaround != DI_NODIR) + { + actor->movedir =turnaround; + if ( P_TryWalk(actor) ) + return; + } + + actor->movedir = DI_NODIR; // can not move +} + + + +// +// P_LookForPlayers +// If allaround is false, only look 180 degrees in front. +// Returns true if a player is targeted. +// +qboolean +P_LookForPlayers +( mobj_t* actor, + qboolean allaround ) +{ + int c; + int stop; + player_t* player; + sector_t* sector; + angle_t an; + fixed_t dist; + + sector = actor->subsector->sector; + + c = 0; + stop = (actor->lastlook-1)&3; + + for ( ; ; actor->lastlook = (actor->lastlook+1)&3 ) + { + if (!::g->playeringame[actor->lastlook]) + continue; + + if (c++ == 2 + || actor->lastlook == stop) + { + // done looking + return false; + } + + player = &::g->players[actor->lastlook]; + + if (player->health <= 0) + continue; // dead + + if (!P_CheckSight (actor, player->mo)) + continue; // out of sight + + if (!allaround) + { + an = R_PointToAngle2 (actor->x, + actor->y, + player->mo->x, + player->mo->y) + - actor->angle; + + if (an > ANG90 && an < ANG270) + { + dist = P_AproxDistance (player->mo->x - actor->x, + player->mo->y - actor->y); + // if real close, react anyway + if (dist > MELEERANGE) + continue; // behind back + } + } + + actor->target = player->mo; + return true; + } + + return false; +} + + +extern "C" { +// +// A_KeenDie +// DOOM II special, map 32. +// Uses special tag 666. +// +void A_KeenDie (mobj_t* mo, void * ) +{ + thinker_t* th; + mobj_t* mo2; + line_t junk; + + A_Fall (mo, 0); + + // scan the remaining thinkers + // to see if all Keens are dead + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mo2 = (mobj_t *)th; + if (mo2 != mo + && mo2->type == mo->type + && mo2->health > 0) + { + // other Keen not dead + return; + } + } + + junk.tag = 666; + EV_DoDoor(&junk,opened); +} + + +// +// ACTION ROUTINES +// + +// +// A_Look +// Stay in state until a player is sighted. +// +void A_Look (mobj_t* actor, void * ) +{ + mobj_t* targ; + + actor->threshold = 0; // any shot will wake up + targ = actor->subsector->sector->soundtarget; + + if (targ + && (targ->flags & MF_SHOOTABLE) ) + { + actor->target = targ; + + if ( actor->flags & MF_AMBUSH ) + { + if (P_CheckSight (actor, actor->target)) + goto seeyou; + } + else + goto seeyou; + } + + + if (!P_LookForPlayers (actor, false) ) + return; + + // go into chase state + seeyou: + if (actor->info->seesound) + { + int sound; + + switch (actor->info->seesound) + { + case sfx_posit1: + case sfx_posit2: + case sfx_posit3: + sound = sfx_posit1+P_Random()%3; + break; + + case sfx_bgsit1: + case sfx_bgsit2: + sound = sfx_bgsit1+P_Random()%2; + break; + + default: + sound = actor->info->seesound; + break; + } + + if (actor->type==MT_SPIDER + || actor->type == MT_CYBORG) + { + // full volume + S_StartSound (NULL, sound); + } + else + S_StartSound (actor, sound); + } + + P_SetMobjState (actor, (statenum_t)actor->info->seestate); +} + + +// +// A_Chase +// Actor has a melee attack, +// so it tries to close as fast as possible +// +void A_Chase (mobj_t* actor, void * ) +{ + int delta; + + if (actor->reactiontime) + actor->reactiontime--; + + + // modify target threshold + if (actor->threshold) + { + if (!actor->target + || actor->target->health <= 0) + { + actor->threshold = 0; + } + else + actor->threshold--; + } + + // turn towards movement direction if not there yet + if (actor->movedir < 8) + { + actor->angle &= (7<<29); + delta = actor->angle - (actor->movedir << 29); + + if (delta > 0) + actor->angle -= ANG90/2; + else if (delta < 0) + actor->angle += ANG90/2; + } + + if (!actor->target + || !(actor->target->flags&MF_SHOOTABLE)) + { + // look for a new target + if (P_LookForPlayers(actor,true)) + return; // got a new target + + P_SetMobjState (actor, (statenum_t)actor->info->spawnstate); + return; + } + + // do not attack twice in a row + if (actor->flags & MF_JUSTATTACKED) + { + actor->flags &= ~MF_JUSTATTACKED; + if (::g->gameskill != sk_nightmare && !::g->fastparm) + P_NewChaseDir (actor); + return; + } + + // check for melee attack + if (actor->info->meleestate && P_CheckMeleeRange (actor)) + { + if (actor->info->attacksound) + S_StartSound (actor, actor->info->attacksound); + + P_SetMobjState (actor, (statenum_t)actor->info->meleestate); + return; + } + + // check for missile attack + if (actor->info->missilestate) + { + if (::g->gameskill < sk_nightmare + && !::g->fastparm && actor->movecount) + { + goto nomissile; + } + + if (!P_CheckMissileRange (actor)) + goto nomissile; + + P_SetMobjState (actor, (statenum_t)actor->info->missilestate); + actor->flags |= MF_JUSTATTACKED; + return; + } + + // ? + nomissile: + // possibly choose another target + if (::g->netgame + && !actor->threshold + && !P_CheckSight (actor, actor->target) ) + { + if (P_LookForPlayers(actor,true)) + return; // got a new target + } + + // chase towards player + if (--actor->movecount<0 + || !P_Move (actor)) + { + P_NewChaseDir (actor); + } + + // make active sound + if (actor->info->activesound && P_Random () < 3) + { + S_StartSound (actor, actor->info->activesound); + } +} + + +// +// A_FaceTarget +// +void A_FaceTarget (mobj_t* actor, void * ) +{ + if (!actor->target) + return; + + actor->flags &= ~MF_AMBUSH; + + actor->angle = R_PointToAngle2 (actor->x, + actor->y, + actor->target->x, + actor->target->y); + + if (actor->target->flags & MF_SHADOW) + actor->angle += (P_Random()-P_Random())<<21; +} + + +// +// A_PosAttack +// +void A_PosAttack (mobj_t* actor, void * ) +{ + int angle; + int damage; + int slope; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + angle = actor->angle; + slope = P_AimLineAttack (actor, angle, MISSILERANGE); + + S_StartSound (actor, sfx_pistol); + angle += (P_Random()-P_Random())<<20; + damage = ((P_Random()%5)+1)*3; + P_LineAttack (actor, angle, MISSILERANGE, slope, damage); +} + +void A_SPosAttack (mobj_t* actor, void * ) +{ + int i; + int angle; + int bangle; + int damage; + int slope; + + if (!actor->target) + return; + + S_StartSound (actor, sfx_shotgn); + A_FaceTarget (actor, 0); + bangle = actor->angle; + slope = P_AimLineAttack (actor, bangle, MISSILERANGE); + + for (i=0 ; i<3 ; i++) + { + angle = bangle + ((P_Random()-P_Random())<<20); + damage = ((P_Random()%5)+1)*3; + P_LineAttack (actor, angle, MISSILERANGE, slope, damage); + } +} + +void A_CPosAttack (mobj_t* actor, void * ) +{ + int angle; + int bangle; + int damage; + int slope; + + if (!actor->target) + return; + + S_StartSound (actor, sfx_shotgn); + A_FaceTarget (actor, 0); + bangle = actor->angle; + slope = P_AimLineAttack (actor, bangle, MISSILERANGE); + + angle = bangle + ((P_Random()-P_Random())<<20); + damage = ((P_Random()%5)+1)*3; + P_LineAttack (actor, angle, MISSILERANGE, slope, damage); +} + +void A_CPosRefire (mobj_t* actor, void * ) +{ + // keep firing unless target got out of sight + A_FaceTarget (actor, 0); + + if (P_Random () < 40) + return; + + if (!actor->target + || actor->target->health <= 0 + || !P_CheckSight (actor, actor->target) ) + { + P_SetMobjState (actor, (statenum_t)actor->info->seestate); + } +} + + +void A_SpidRefire (mobj_t* actor, void * ) +{ + // keep firing unless target got out of sight + A_FaceTarget (actor, 0); + + if (P_Random () < 10) + return; + + if (!actor->target + || actor->target->health <= 0 + || !P_CheckSight (actor, actor->target) ) + { + P_SetMobjState (actor, (statenum_t)actor->info->seestate); + } +} + +void A_BspiAttack (mobj_t *actor, void * ) +{ + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_ARACHPLAZ); +} + + +// +// A_TroopAttack +// +void A_TroopAttack (mobj_t* actor, void * ) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + if (P_CheckMeleeRange (actor)) + { + S_StartSound (actor, sfx_claw); + damage = (P_Random()%8+1)*3; + P_DamageMobj (actor->target, actor, actor, damage); + return; + } + + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_TROOPSHOT); +} + + +void A_SargAttack (mobj_t* actor, void * ) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + if (P_CheckMeleeRange (actor)) + { + damage = ((P_Random()%10)+1)*4; + P_DamageMobj (actor->target, actor, actor, damage); + } +} + +void A_HeadAttack (mobj_t* actor, void * ) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + if (P_CheckMeleeRange (actor)) + { + damage = (P_Random()%6+1)*10; + P_DamageMobj (actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_HEADSHOT); +} + +void A_CyberAttack (mobj_t* actor, void * ) +{ + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + P_SpawnMissile (actor, actor->target, MT_ROCKET); +} + + +void A_BruisAttack (mobj_t* actor, void * ) +{ + int damage; + + if (!actor->target) + return; + + if (P_CheckMeleeRange (actor)) + { + S_StartSound (actor, sfx_claw); + damage = (P_Random()%8+1)*10; + P_DamageMobj (actor->target, actor, actor, damage); + return; + } + + // launch a missile + P_SpawnMissile (actor, actor->target, MT_BRUISERSHOT); +} + + +// +// A_SkelMissile +// +void A_SkelMissile (mobj_t* actor, void * ) +{ + mobj_t* mo; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + actor->z += 16*FRACUNIT; // so missile spawns higher + mo = P_SpawnMissile (actor, actor->target, MT_TRACER); + actor->z -= 16*FRACUNIT; // back to normal + + mo->x += mo->momx; + mo->y += mo->momy; + mo->tracer = actor->target; +} + + +void A_Tracer (mobj_t* actor, void * ) +{ + angle_t exact; + fixed_t dist; + fixed_t slope; + mobj_t* dest; + mobj_t* th; + + //if (::g->gametic & 3) + //return; + + // DHM - Nerve :: Demo fix - Keep the game state deterministic!!! + if ( ::g->leveltime & 3 ) { + return; + } + + // spawn a puff of smoke behind the rocket + P_SpawnPuff (actor->x, actor->y, actor->z); + + th = P_SpawnMobj (actor->x-actor->momx, + actor->y-actor->momy, + actor->z, MT_SMOKE); + + th->momz = FRACUNIT; + th->tics -= P_Random()&3; + if (th->tics < 1) + th->tics = 1; + + // adjust direction + dest = actor->tracer; + + if (!dest || dest->health <= 0) + return; + + // change angle + exact = R_PointToAngle2 (actor->x, + actor->y, + dest->x, + dest->y); + + if (exact != actor->angle) + { + if (exact - actor->angle > 0x80000000) + { + actor->angle -= ::g->TRACEANGLE; + if (exact - actor->angle < 0x80000000) + actor->angle = exact; + } + else + { + actor->angle += ::g->TRACEANGLE; + if (exact - actor->angle > 0x80000000) + actor->angle = exact; + } + } + + exact = actor->angle>>ANGLETOFINESHIFT; + actor->momx = FixedMul (actor->info->speed, finecosine[exact]); + actor->momy = FixedMul (actor->info->speed, finesine[exact]); + + // change slope + dist = P_AproxDistance (dest->x - actor->x, + dest->y - actor->y); + + dist = dist / actor->info->speed; + + if (dist < 1) + dist = 1; + slope = (dest->z+40*FRACUNIT - actor->z) / dist; + + if (slope < actor->momz) + actor->momz -= FRACUNIT/8; + else + actor->momz += FRACUNIT/8; +} + + +void A_SkelWhoosh (mobj_t* actor, void * ) +{ + if (!actor->target) + return; + A_FaceTarget (actor, 0); + S_StartSound (actor,sfx_skeswg); +} + +void A_SkelFist (mobj_t* actor, void * ) +{ + int damage; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + + if (P_CheckMeleeRange (actor)) + { + damage = ((P_Random()%10)+1)*6; + S_StartSound (actor, sfx_skepch); + P_DamageMobj (actor->target, actor, actor, damage); + } +} + + + +// +// PIT_VileCheck +// Detect a corpse that could be raised. +// + +qboolean PIT_VileCheck (mobj_t* thing ) +{ + int maxdist; + qboolean check; + + if (!(thing->flags & MF_CORPSE) ) + return true; // not a monster + + if (thing->tics != -1) + return true; // not lying still yet + + if (thing->info->raisestate == S_NULL) + return true; // monster doesn't have a raise state + + maxdist = thing->info->radius + mobjinfo[MT_VILE].radius; + + if ( abs(thing->x - ::g->viletryx) > maxdist + || abs(thing->y - ::g->viletryy) > maxdist ) + return true; // not actually touching + + ::g->corpsehit = thing; + ::g->corpsehit->momx = ::g->corpsehit->momy = 0; + ::g->corpsehit->height <<= 2; + check = P_CheckPosition (::g->corpsehit, ::g->corpsehit->x, ::g->corpsehit->y); + ::g->corpsehit->height >>= 2; + + if (!check) + return true; // doesn't fit here + + return false; // got one, so stop checking +} + + + +// +// A_VileChase +// Check for ressurecting a body +// +void A_VileChase (mobj_t* actor, void * ) +{ + int xl; + int xh; + int yl; + int yh; + + int bx; + int by; + + const mobjinfo_t* info; + mobj_t* temp; + + if (actor->movedir != DI_NODIR) + { + // check for corpses to raise + ::g->viletryx = + actor->x + actor->info->speed*xspeed[actor->movedir]; + ::g->viletryy = + actor->y + actor->info->speed*yspeed[actor->movedir]; + + xl = (::g->viletryx - ::g->bmaporgx - MAXRADIUS*2)>>MAPBLOCKSHIFT; + xh = (::g->viletryx - ::g->bmaporgx + MAXRADIUS*2)>>MAPBLOCKSHIFT; + yl = (::g->viletryy - ::g->bmaporgy - MAXRADIUS*2)>>MAPBLOCKSHIFT; + yh = (::g->viletryy - ::g->bmaporgy + MAXRADIUS*2)>>MAPBLOCKSHIFT; + + ::g->vileobj = actor; + for (bx=xl ; bx<=xh ; bx++) + { + for (by=yl ; by<=yh ; by++) + { + // Call PIT_VileCheck to check + // whether object is a corpse + // that canbe raised. + if (!P_BlockThingsIterator(bx,by,PIT_VileCheck)) + { + // got one! + temp = actor->target; + actor->target = ::g->corpsehit; + A_FaceTarget (actor, 0); + actor->target = temp; + + P_SetMobjState (actor, S_VILE_HEAL1); + S_StartSound (::g->corpsehit, sfx_slop); + info = ::g->corpsehit->info; + + P_SetMobjState (::g->corpsehit,(statenum_t)info->raisestate); + ::g->corpsehit->height <<= 2; + ::g->corpsehit->flags = info->flags; + ::g->corpsehit->health = info->spawnhealth; + ::g->corpsehit->target = NULL; + + return; + } + } + } + } + + // Return to normal attack. + A_Chase (actor, 0); +} + + +// +// A_VileStart +// +void A_VileStart (mobj_t* actor, void * ) +{ + S_StartSound (actor, sfx_vilatk); +} + + +// +// A_Fire +// Keep fire in front of player unless out of sight +// +void A_Fire (mobj_t* actor, void * ); + +void A_StartFire (mobj_t* actor, void * ) +{ + S_StartSound(actor,sfx_flamst); + A_Fire(actor, 0 ); +} + +void A_FireCrackle (mobj_t* actor, void * ) +{ + S_StartSound(actor,sfx_flame); + A_Fire(actor, 0); +} + +void A_Fire (mobj_t* actor, void * ) +{ + mobj_t* dest; + unsigned an; + + dest = actor->tracer; + if (!dest) + return; + + // don't move it if the vile lost sight + if (!P_CheckSight (actor->target, dest) ) + return; + + an = dest->angle >> ANGLETOFINESHIFT; + + P_UnsetThingPosition (actor); + actor->x = dest->x + FixedMul (24*FRACUNIT, finecosine[an]); + actor->y = dest->y + FixedMul (24*FRACUNIT, finesine[an]); + actor->z = dest->z; + P_SetThingPosition (actor); +} + + + +// +// A_VileTarget +// Spawn the hellfire +// +void A_VileTarget (mobj_t* actor, void * ) +{ + mobj_t* fog; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + + fog = P_SpawnMobj (actor->target->x, + actor->target->x, + actor->target->z, MT_FIRE); + + actor->tracer = fog; + fog->target = actor; + fog->tracer = actor->target; + A_Fire (fog, 0); +} + + + + +// +// A_VileAttack +// +void A_VileAttack (mobj_t* actor, void * ) +{ + mobj_t* fire; + int an; + + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + + if (!P_CheckSight (actor, actor->target) ) + return; + + S_StartSound (actor, sfx_barexp); + P_DamageMobj (actor->target, actor, actor, 20); + actor->target->momz = 1000*FRACUNIT/actor->target->info->mass; + + an = actor->angle >> ANGLETOFINESHIFT; + + fire = actor->tracer; + + if (!fire) + return; + + // move the fire between the vile and the player + fire->x = actor->target->x - FixedMul (24*FRACUNIT, finecosine[an]); + fire->y = actor->target->y - FixedMul (24*FRACUNIT, finesine[an]); + P_RadiusAttack (fire, actor, 70 ); +} + + + + +// +// Mancubus attack, +// firing three missiles (bruisers) +// in three different directions? +// Doesn't look like it. +// + +void A_FatRaise (mobj_t *actor, void * ) +{ + A_FaceTarget (actor, 0); + S_StartSound (actor, sfx_manatk); +} + + +void A_FatAttack1 (mobj_t* actor, void * ) +{ + mobj_t* mo; + int an; + + A_FaceTarget (actor, 0); + // Change direction to ... + actor->angle += FATSPREAD; + P_SpawnMissile (actor, actor->target, MT_FATSHOT); + + mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT); + mo->angle += FATSPREAD; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); +} + +void A_FatAttack2 (mobj_t* actor, void * ) +{ + mobj_t* mo; + int an; + + A_FaceTarget (actor, 0); + // Now here choose opposite deviation. + actor->angle -= FATSPREAD; + P_SpawnMissile (actor, actor->target, MT_FATSHOT); + + mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT); + mo->angle -= FATSPREAD*2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); +} + +void A_FatAttack3 (mobj_t* actor, void * ) +{ + mobj_t* mo; + int an; + + A_FaceTarget (actor, 0); + + mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT); + mo->angle -= FATSPREAD/2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); + + mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT); + mo->angle += FATSPREAD/2; + an = mo->angle >> ANGLETOFINESHIFT; + mo->momx = FixedMul (mo->info->speed, finecosine[an]); + mo->momy = FixedMul (mo->info->speed, finesine[an]); +} + + +// +// SkullAttack +// Fly at the player like a missile. +// + +void A_SkullAttack (mobj_t* actor, void * ) +{ + mobj_t* dest; + angle_t an; + int dist; + + if (!actor->target) + return; + + dest = actor->target; + actor->flags |= MF_SKULLFLY; + + S_StartSound (actor, actor->info->attacksound); + A_FaceTarget (actor, 0); + an = actor->angle >> ANGLETOFINESHIFT; + actor->momx = FixedMul (SKULLSPEED, finecosine[an]); + actor->momy = FixedMul (SKULLSPEED, finesine[an]); + dist = P_AproxDistance (dest->x - actor->x, dest->y - actor->y); + dist = dist / SKULLSPEED; + + if (dist < 1) + dist = 1; + actor->momz = (dest->z+(dest->height>>1) - actor->z) / dist; +} + + +// +// A_PainShootSkull +// Spawn a lost soul and launch it at the target +// +void +A_PainShootSkull +( mobj_t* actor, + angle_t angle ) +{ + fixed_t x; + fixed_t y; + fixed_t z; + + mobj_t* newmobj; + angle_t an; + int prestep; + int count; + thinker_t* currentthinker; + + // count total number of skull currently on the level + count = 0; + + currentthinker = ::g->thinkercap.next; + while (currentthinker != &::g->thinkercap) + { + if ( (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + && ((mobj_t *)currentthinker)->type == MT_SKULL) + count++; + currentthinker = currentthinker->next; + } + + // if there are allready 20 skulls on the level, + // don't spit another one + if (count > 20) + return; + + + // okay, there's playe for another one + an = angle >> ANGLETOFINESHIFT; + + prestep = + 4*FRACUNIT + + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2; + + x = actor->x + FixedMul (prestep, finecosine[an]); + y = actor->y + FixedMul (prestep, finesine[an]); + z = actor->z + 8*FRACUNIT; + + newmobj = P_SpawnMobj (x , y, z, MT_SKULL); + + // Check for movements. + if (!P_TryMove (newmobj, newmobj->x, newmobj->y)) + { + // kill it immediately + P_DamageMobj (newmobj,actor,actor,10000); + return; + } + + newmobj->target = actor->target; + A_SkullAttack (newmobj, 0); +} + + +// +// A_PainAttack +// Spawn a lost soul and launch it at the target +// +void A_PainAttack (mobj_t* actor, void * ) +{ + if (!actor->target) + return; + + A_FaceTarget (actor, 0); + A_PainShootSkull (actor, actor->angle); +} + + +void A_PainDie (mobj_t* actor, void * ) +{ + A_Fall (actor, 0); + A_PainShootSkull (actor, actor->angle+ANG90); + A_PainShootSkull (actor, actor->angle+ANG180); + A_PainShootSkull (actor, actor->angle+ANG270); +} + + + + + + +void A_Scream (mobj_t* actor, void * ) +{ + int sound; + + switch (actor->info->deathsound) + { + case 0: + return; + + case sfx_podth1: + case sfx_podth2: + case sfx_podth3: + sound = sfx_podth1 + P_Random ()%3; + break; + + case sfx_bgdth1: + case sfx_bgdth2: + sound = sfx_bgdth1 + P_Random ()%2; + break; + + default: + sound = actor->info->deathsound; + break; + } + + // Check for bosses. + if (actor->type==MT_SPIDER + || actor->type == MT_CYBORG) + { + // full volume + S_StartSound (NULL, sound); + } + else + S_StartSound (actor, sound); +} + + +void A_XScream (mobj_t* actor, void * ) +{ + S_StartSound (actor, sfx_slop); +} + +void A_Pain (mobj_t* actor, void * ) +{ + if (actor->info->painsound ) + S_StartSound (actor, actor->info->painsound); +} + + + +void A_Fall (mobj_t *actor, void * ) +{ + // actor is on ground, it can be walked over + actor->flags &= ~MF_SOLID; + + // So change this if corpse objects + // are meant to be obstacles. +} + + +// +// A_Explode +// +void A_Explode (mobj_t* thingy, void * ) +{ + P_RadiusAttack ( thingy, thingy->target, 128 ); +} + + +// +// A_BossDeath +// Possibly trigger special effects +// if on first boss level +// +void A_BossDeath (mobj_t* mo, void * ) +{ + thinker_t* th; + mobj_t* mo2; + line_t junk; + int i; + + if ( ::g->gamemode == commercial) + { + if (::g->gamemap != 7) + return; + + if ((mo->type != MT_FATSO) + && (mo->type != MT_BABY)) + return; + } + else + { + switch(::g->gameepisode) + { + case 1: + if (::g->gamemap != 8) + return; + + if (mo->type != MT_BRUISER) + return; + break; + + case 2: + if (::g->gamemap != 8) + return; + + if (mo->type != MT_CYBORG) + return; + break; + + case 3: + if (::g->gamemap != 8) + return; + + if (mo->type != MT_SPIDER) + return; + + break; + + case 4: + switch(::g->gamemap) + { + case 6: + if (mo->type != MT_CYBORG) + return; + break; + + case 8: + if (mo->type != MT_SPIDER) + return; + break; + + default: + return; + break; + } + break; + + default: + if (::g->gamemap != 8) + return; + break; + } + + } + + + // make sure there is a player alive for victory + for (i=0 ; iplayeringame[i] && ::g->players[i].health > 0) + break; + + if (i==MAXPLAYERS) + return; // no one left alive, so do not end game + + // scan the remaining thinkers to see + // if all bosses are dead + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + if (th->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + mo2 = (mobj_t *)th; + if (mo2 != mo + && mo2->type == mo->type + && mo2->health > 0) + { + // other boss not dead + return; + } + } + + // victory! + if ( ::g->gamemode == commercial) + { + if (::g->gamemap == 7) + { + if (mo->type == MT_FATSO) + { + junk.tag = 666; + EV_DoFloor(&junk,lowerFloorToLowest); + return; + } + + if (mo->type == MT_BABY) + { + junk.tag = 667; + EV_DoFloor(&junk,raiseToTexture); + return; + } + } + } + else + { + switch(::g->gameepisode) + { + case 1: + junk.tag = 666; + EV_DoFloor (&junk, lowerFloorToLowest); + return; + break; + + case 4: + switch(::g->gamemap) + { + case 6: + junk.tag = 666; + EV_DoDoor (&junk, blazeOpen); + return; + break; + + case 8: + junk.tag = 666; + EV_DoFloor (&junk, lowerFloorToLowest); + return; + break; + } + } + } + + G_ExitLevel (); +} + + +void A_Hoof (mobj_t* mo, void * ) +{ + S_StartSound (mo, sfx_hoof); + A_Chase (mo, 0); +} + +void A_Metal (mobj_t* mo, void * ) +{ + S_StartSound (mo, sfx_metal); + A_Chase (mo, 0); +} + +void A_BabyMetal (mobj_t* mo, void * ) +{ + S_StartSound (mo, sfx_bspwlk); + A_Chase (mo, 0); +} + +void +A_OpenShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_dbopn); +} + +void +A_LoadShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_dbload); +} + +void +A_ReFire +( player_t* player, + pspdef_t* psp ); + +void +A_CloseShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_dbcls); + A_ReFire(player,psp); +} + + + + +void A_BrainAwake (mobj_t* mo, void * ) +{ + thinker_t* thinker; + mobj_t* m; + + // find all the target spots + ::g->easy = 0; + ::g->numbraintargets = 0; + ::g->braintargeton = 0; + + thinker = ::g->thinkercap.next; + for (thinker = ::g->thinkercap.next ; + thinker != &::g->thinkercap ; + thinker = thinker->next) + { + if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + continue; // not a mobj + + m = (mobj_t *)thinker; + + if (m->type == MT_BOSSTARGET ) + { + ::g->braintargets[::g->numbraintargets] = m; + ::g->numbraintargets++; + } + } + + S_StartSound (NULL,sfx_bossit); +} + + +void A_BrainPain (mobj_t* mo, void * ) +{ + S_StartSound (NULL,sfx_bospn); +} + + +void A_BrainScream (mobj_t* mo, void * ) +{ + int x; + int y; + int z; + mobj_t* th; + + for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8) + { + y = mo->y - 320*FRACUNIT; + z = 128 + P_Random()*2*FRACUNIT; + th = P_SpawnMobj (x,y,z, MT_ROCKET); + th->momz = P_Random()*512; + + P_SetMobjState (th, S_BRAINEXPLODE1); + + th->tics -= P_Random()&7; + if (th->tics < 1) + th->tics = 1; + } + + S_StartSound (NULL,sfx_bosdth); +} + + + +void A_BrainExplode (mobj_t* mo, void * ) +{ + int x; + int y; + int z; + mobj_t* th; + + x = mo->x + (P_Random () - P_Random ())*2048; + y = mo->y; + z = 128 + P_Random()*2*FRACUNIT; + th = P_SpawnMobj (x,y,z, MT_ROCKET); + th->momz = P_Random()*512; + + P_SetMobjState (th, S_BRAINEXPLODE1); + + th->tics -= P_Random()&7; + if (th->tics < 1) + th->tics = 1; +} + + +void A_BrainDie (mobj_t* mo, void * ) +{ + G_ExitLevel (); +} + +void A_BrainSpit (mobj_t* mo, void * ) +{ + mobj_t* targ; + mobj_t* newmobj; + + ::g->easy ^= 1; + if (::g->gameskill <= sk_easy && (!::g->easy)) + return; + + if ( 1 ) { + // count number of thinkers + int numCorpse = 0; + int numEnemies = 0; + + for ( thinker_t* th = ::g->thinkercap.next; th != &::g->thinkercap; th = th->next ) { + if ( th->function.acp1 == (actionf_p1)P_MobjThinker ) { + mobj_t* obj = (mobj_t*)th; + + if ( obj->flags & MF_CORPSE ) { + numCorpse++; + } + else if ( obj->type > MT_PLAYER && obj->type < MT_KEEN ) { + numEnemies++; + } + } + } + + if ( numCorpse > 48 ) { + for ( int i = 0; i < 12; i++ ) { + for ( thinker_t* th = ::g->thinkercap.next; th != &::g->thinkercap; th = th->next ) { + if ( th->function.acp1 == (actionf_p1)P_MobjThinker ) { + mobj_t* obj = (mobj_t*)th; + + if ( obj->flags & MF_CORPSE ) { + P_RemoveMobj( obj ); + break; + } + } + } + } + } + + if ( numEnemies > 32 ) { + return; + } + } + + // shoot a cube at current target + targ = ::g->braintargets[::g->braintargeton]; + ::g->braintargeton = (::g->braintargeton+1) % ::g->numbraintargets; + + // spawn brain missile + newmobj = P_SpawnMissile (mo, targ, MT_SPAWNSHOT); + newmobj->target = targ; + newmobj->reactiontime = + ((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics; + + S_StartSound(NULL, sfx_bospit); +} + + + +void A_SpawnFly (mobj_t* mo, void * ); + +// travelling cube sound +void A_SpawnSound (mobj_t* mo, void * ) +{ + S_StartSound (mo,sfx_boscub); + A_SpawnFly(mo, 0); +} + +void A_SpawnFly (mobj_t* mo, void * ) +{ + mobj_t* newmobj; + mobj_t* fog; + mobj_t* targ; + int r; + mobjtype_t type; + + if (--mo->reactiontime) + return; // still flying + + targ = mo->target; + + // First spawn teleport fog. + fog = P_SpawnMobj (targ->x, targ->y, targ->z, MT_SPAWNFIRE); + S_StartSound (fog, sfx_telept); + + // Randomly select monster to spawn. + r = P_Random (); + + // Probability distribution (kind of :), + // decreasing likelihood. + if ( r<50 ) + type = MT_TROOP; + else if (r<90) + type = MT_SERGEANT; + else if (r<120) + type = MT_SHADOWS; + else if (r<130) + type = MT_PAIN; + else if (r<160) + type = MT_HEAD; + else if (r<162) + type = MT_VILE; + else if (r<172) + type = MT_UNDEAD; + else if (r<192) + type = MT_BABY; + else if (r<222) + type = MT_FATSO; + else if (r<246) + type = MT_KNIGHT; + else + type = MT_BRUISER; + + newmobj = P_SpawnMobj (targ->x, targ->y, targ->z, type); + if (P_LookForPlayers (newmobj, true) ) + P_SetMobjState (newmobj, (statenum_t)newmobj->info->seestate); + + // telefrag anything in this spot + P_TeleportMove (newmobj, newmobj->x, newmobj->y); + + // remove self (i.e., cube). + P_RemoveMobj (mo); +} + + + +void A_PlayerScream (mobj_t* mo, void * ) +{ + // Default death sound. + int sound = sfx_pldeth; + + if ( (::g->gamemode == commercial) + && (mo->health < -50)) + { + // IF THE PLAYER DIES + // LESS THAN -50% WITHOUT GIBBING + sound = sfx_pdiehi; + } + + if ( ::g->demoplayback || globalNetworking || (mo == ::g->players[::g->consoleplayer].mo)) + S_StartSound (mo, sound); +} + +}; // extern "C" + diff --git a/doomclassic/doom/p_floor.cpp b/doomclassic/doom/p_floor.cpp new file mode 100644 index 00000000..9310c0d1 --- /dev/null +++ b/doomclassic/doom/p_floor.cpp @@ -0,0 +1,561 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "z_zone.h" +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" +// Data. +#include "sounds.h" + + +// +// FLOORS +// + +// +// Move a plane (floor or ceiling) and check for crushing +// +result_e +T_MovePlane +( sector_t* sector, + fixed_t speed, + fixed_t dest, + qboolean crush, + int floorOrCeiling, + int direction ) +{ + qboolean flag; + fixed_t lastpos; + + switch(floorOrCeiling) + { + case 0: + // FLOOR + switch(direction) + { + case -1: + // DOWN + if (sector->floorheight - speed < dest) + { + lastpos = sector->floorheight; + sector->floorheight = dest; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->floorheight =lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + lastpos = sector->floorheight; + sector->floorheight -= speed; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->floorheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + } + break; + + case 1: + // UP + if (sector->floorheight + speed > dest) + { + lastpos = sector->floorheight; + sector->floorheight = dest; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->floorheight = lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + // COULD GET CRUSHED + lastpos = sector->floorheight; + sector->floorheight += speed; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + if (crush == true) + return crushed; + sector->floorheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + } + break; + } + break; + + case 1: + // CEILING + switch(direction) + { + case -1: + // DOWN + if (sector->ceilingheight - speed < dest) + { + lastpos = sector->ceilingheight; + sector->ceilingheight = dest; + flag = P_ChangeSector(sector,crush); + + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + // COULD GET CRUSHED + lastpos = sector->ceilingheight; + sector->ceilingheight -= speed; + flag = P_ChangeSector(sector,crush); + + if (flag == true) + { + if (crush == true) + return crushed; + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + } + break; + + case 1: + // UP + if (sector->ceilingheight + speed > dest) + { + lastpos = sector->ceilingheight; + sector->ceilingheight = dest; + flag = P_ChangeSector(sector,crush); + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + //return crushed; + } + return pastdest; + } + else + { + lastpos = sector->ceilingheight; + sector->ceilingheight += speed; + flag = P_ChangeSector(sector,crush); +// UNUSED +#if 0 + if (flag == true) + { + sector->ceilingheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } +#endif + } + break; + } + break; + + } + return ok; +} + + +// +// MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN) +// +void T_MoveFloor(floormove_t* floor) +{ + result_e res; + + res = T_MovePlane(floor->sector, + floor->speed, + floor->floordestheight, + floor->crush,0,floor->direction); + + if (!(::g->leveltime&7)) + S_StartSound( &floor->sector->soundorg, + sfx_stnmov); + + if (res == pastdest) + { + floor->sector->specialdata = NULL; + + if (floor->direction == 1) + { + switch(floor->type) + { + case donutRaise: + floor->sector->special = floor->newspecial; + floor->sector->floorpic = floor->texture; + default: + break; + } + } + else if (floor->direction == -1) + { + switch(floor->type) + { + case lowerAndChange: + floor->sector->special = floor->newspecial; + floor->sector->floorpic = floor->texture; + default: + break; + } + } + P_RemoveThinker(&floor->thinker); + + S_StartSound( &floor->sector->soundorg, + sfx_pstop); + } + +} + +// +// HANDLE FLOOR TYPES +// +int +EV_DoFloor +( line_t* line, + floor_e floortype ) +{ + int secnum; + int rtn; + int i; + sector_t* sec; + floormove_t* floor; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = &::g->sectors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // new floor thinker + rtn = 1; + floor = (floormove_t*)DoomLib::Z_Malloc(sizeof(*floor), PU_LEVEL, 0); + P_AddThinker (&floor->thinker); + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->type = floortype; + floor->crush = false; + + switch(floortype) + { + case lowerFloor: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindHighestFloorSurrounding(sec); + break; + + case lowerFloorToLowest: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindLowestFloorSurrounding(sec); + break; + + case turboLower: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED * 4; + floor->floordestheight = + P_FindHighestFloorSurrounding(sec); + if (floor->floordestheight != sec->floorheight) + floor->floordestheight += 8*FRACUNIT; + break; + + case raiseFloorCrush: + floor->crush = true; + case raiseFloor: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindLowestCeilingSurrounding(sec); + if (floor->floordestheight > sec->ceilingheight) + floor->floordestheight = sec->ceilingheight; + floor->floordestheight -= (8*FRACUNIT)* + (floortype == raiseFloorCrush); + break; + + case raiseFloorTurbo: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED*4; + floor->floordestheight = + P_FindNextHighestFloor(sec,sec->floorheight); + break; + + case raiseFloorToNearest: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindNextHighestFloor(sec,sec->floorheight); + break; + + case raiseFloor24: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + + 24 * FRACUNIT; + break; + case raiseFloor512: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + + 512 * FRACUNIT; + break; + + case raiseFloor24AndChange: + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = floor->sector->floorheight + + 24 * FRACUNIT; + sec->floorpic = line->frontsector->floorpic; + sec->special = line->frontsector->special; + break; + + case raiseToTexture: + { + int minsize = MAXINT; + side_t* side; + + floor->direction = 1; + floor->sector = sec; + floor->speed = FLOORSPEED; + for (i = 0; i < sec->linecount; i++) + { + if (twoSided (secnum, i) ) + { + side = getSide(secnum,i,0); + if (side->bottomtexture >= 0) + if (::g->s_textureheight[side->bottomtexture] < + minsize) + minsize = + ::g->s_textureheight[side->bottomtexture]; + side = getSide(secnum,i,1); + if (side->bottomtexture >= 0) + if (::g->s_textureheight[side->bottomtexture] < + minsize) + minsize = + ::g->s_textureheight[side->bottomtexture]; + } + } + floor->floordestheight = + floor->sector->floorheight + minsize; + } + break; + + case lowerAndChange: + floor->direction = -1; + floor->sector = sec; + floor->speed = FLOORSPEED; + floor->floordestheight = + P_FindLowestFloorSurrounding(sec); + floor->texture = sec->floorpic; + + for (i = 0; i < sec->linecount; i++) + { + if ( twoSided(secnum, i) ) + { + if (getSide(secnum,i,0)->sector-::g->sectors == secnum) + { + sec = getSector(secnum,i,1); + + if (sec->floorheight == floor->floordestheight) + { + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } + else + { + sec = getSector(secnum,i,0); + + if (sec->floorheight == floor->floordestheight) + { + floor->texture = sec->floorpic; + floor->newspecial = sec->special; + break; + } + } + } + } + default: + break; + } + } + return rtn; +} + + + + +// +// BUILD A STAIRCASE! +// +int +EV_BuildStairs +( line_t* line, + stair_e type ) +{ + int secnum; + int height; + int i; + int newsecnum; + int texture; + int ok; + int rtn; + + sector_t* sec; + sector_t* tsec; + + floormove_t* floor; + + fixed_t stairsize = 0; + fixed_t speed = 0; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = &::g->sectors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec->specialdata) + continue; + + // new floor thinker + rtn = 1; + floor = (floormove_t*)DoomLib::Z_Malloc(sizeof(*floor), PU_LEVEL, 0); + P_AddThinker (&floor->thinker); + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->direction = 1; + floor->sector = sec; + switch(type) + { + case build8: + speed = FLOORSPEED/4; + stairsize = 8*FRACUNIT; + break; + case turbo16: + speed = FLOORSPEED*4; + stairsize = 16*FRACUNIT; + break; + } + floor->speed = speed; + height = sec->floorheight + stairsize; + floor->floordestheight = height; + + texture = sec->floorpic; + + // Find next sector to raise + // 1. Find 2-sided line with same sector side[0] + // 2. Other side is the next sector to raise + do + { + ok = 0; + for (i = 0;i < sec->linecount;i++) + { + if ( !((sec->lines[i])->flags & ML_TWOSIDED) ) + continue; + + tsec = (sec->lines[i])->frontsector; + newsecnum = tsec-::g->sectors; + + if (secnum != newsecnum) + continue; + + tsec = (sec->lines[i])->backsector; + newsecnum = tsec - ::g->sectors; + + if (tsec->floorpic != texture) + continue; + + height += stairsize; + + if (tsec->specialdata) + continue; + + sec = tsec; + secnum = newsecnum; + floor = (floormove_t*)DoomLib::Z_Malloc(sizeof(*floor), PU_LEVEL, 0); + + P_AddThinker (&floor->thinker); + + sec->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->direction = 1; + floor->sector = sec; + floor->speed = speed; + floor->floordestheight = height; + ok = 1; + break; + } + } while(ok); + } + return rtn; +} + + diff --git a/doomclassic/doom/p_inter.cpp b/doomclassic/doom/p_inter.cpp new file mode 100644 index 00000000..a6aa68eb --- /dev/null +++ b/doomclassic/doom/p_inter.cpp @@ -0,0 +1,1073 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +// Data. +#include "doomdef.h" +#include "dstrings.h" +#include "sounds.h" + +#include "doomstat.h" + +#include "m_random.h" +#include "i_system.h" + +#include "am_map.h" + +#include "p_local.h" + +#include "s_sound.h" + +#ifdef __GNUG__ +#pragma implementation "p_inter.h" +#endif +#include "p_inter.h" + +#include "Main.h" + +#include "sys/sys_signin.h" + +#include "../../neo/d3xp/Game_Local.h" + +// a weapon is found with two clip loads, +// a big item has five clip loads +const int maxammo[NUMAMMO] = {200, 50, 300, 50}; +const int clipammo[NUMAMMO] = {10, 4, 20, 1}; + + +// +// GET STUFF +// + +// +// P_GiveAmmo +// Num is the number of clip loads, +// not the individual count (0= 1/2 clip). +// Returns false if the ammo can't be picked up at all +// + +qboolean +P_GiveAmmo +( player_t* player, + ammotype_t ammo, + int num ) +{ + int oldammo; + + if (ammo == am_noammo) + return false; + + if (ammo < 0 || ammo > NUMAMMO) + I_Error ("P_GiveAmmo: bad type %i", ammo); + + if ( player->ammo[ammo] == player->maxammo[ammo] ) + return false; + + if (num) + num *= clipammo[ammo]; + else + num = clipammo[ammo]/2; + + if (::g->gameskill == sk_baby + || ::g->gameskill == sk_nightmare) + { + // give double ammo in trainer mode, + // you'll need in nightmare + num <<= 1; + } + + + oldammo = player->ammo[ammo]; + player->ammo[ammo] += num; + + if (player->ammo[ammo] > player->maxammo[ammo]) + player->ammo[ammo] = player->maxammo[ammo]; + + // If non zero ammo, + // don't change up weapons, + // player was lower on purpose. + if (oldammo) + return true; + + // We were down to zero, + // so select a new weapon. + // Preferences are not user selectable. + switch (ammo) + { + case am_clip: + if (player->readyweapon == wp_fist) + { + if (player->weaponowned[wp_chaingun]) + player->pendingweapon = wp_chaingun; + else + player->pendingweapon = wp_pistol; + } + break; + + case am_shell: + if (player->readyweapon == wp_fist + || player->readyweapon == wp_pistol) + { + if (player->weaponowned[wp_shotgun]) + player->pendingweapon = wp_shotgun; + } + break; + + case am_cell: + if (player->readyweapon == wp_fist + || player->readyweapon == wp_pistol) + { + if (player->weaponowned[wp_plasma]) + player->pendingweapon = wp_plasma; + } + break; + + case am_misl: + if (player->readyweapon == wp_fist) + { + if (player->weaponowned[wp_missile]) + player->pendingweapon = wp_missile; + } + default: + break; + } + + return true; +} + + +// +// P_GiveWeapon +// The weapon name may have a MF_DROPPED flag ored in. +// +qboolean +P_GiveWeapon +( player_t* player, + weapontype_t weapon, + qboolean dropped ) +{ + qboolean gaveammo; + qboolean gaveweapon; + + if (::g->netgame + && (::g->deathmatch!=2) + && !dropped ) + { + // leave placed weapons forever on net games + if (player->weaponowned[weapon]) + return false; + + player->bonuscount += BONUSADD; + player->weaponowned[weapon] = true; + + if (::g->deathmatch) + P_GiveAmmo (player, weaponinfo[weapon].ammo, 5); + else + P_GiveAmmo (player, weaponinfo[weapon].ammo, 2); + player->pendingweapon = weapon; + + if (player == &::g->players[::g->consoleplayer]) + S_StartSound (player->mo, sfx_wpnup); + return false; + } + + if (weaponinfo[weapon].ammo != am_noammo) + { + // give one clip with a dropped weapon, + // two clips with a found weapon + if (dropped) + gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 1); + else + gaveammo = P_GiveAmmo (player, weaponinfo[weapon].ammo, 2); + } + else + gaveammo = false; + + if (player->weaponowned[weapon]) + gaveweapon = false; + else + { + gaveweapon = true; + player->weaponowned[weapon] = true; + player->pendingweapon = weapon; + } + + return (gaveweapon || gaveammo); +} + + + +// +// P_GiveBody +// Returns false if the body isn't needed at all +// +qboolean +P_GiveBody +( player_t* player, + int num ) +{ + if (player->health >= MAXHEALTH) + return false; + + player->health += num; + if (player->health > MAXHEALTH) + player->health = MAXHEALTH; + player->mo->health = player->health; + + return true; +} + + + +// +// P_GiveArmor +// Returns false if the armor is worse +// than the current armor. +// +qboolean +P_GiveArmor +( player_t* player, + int armortype ) +{ + int hits; + + hits = armortype*100; + if (player->armorpoints >= hits) + return false; // don't pick up + + player->armortype = armortype; + player->armorpoints = hits; + + return true; +} + + + +// +// P_GiveCard +// +void P_GiveCard( player_t* player, card_t card, const char *pickup_message ) { + + if ( ( ::g->demoplayback && ::g->netgame ) || common->IsMultiplayer() ) { + for ( int i=0; i < MAXPLAYERS; i++ ) { + if ( ::g->playeringame[i] ) { + player_t *thePlayer = &::g->players[i]; + + if (thePlayer->cards[card]) + continue; + + thePlayer->bonuscount = BONUSADD; + thePlayer->message = pickup_message; + thePlayer->cards[card] = 1; + } + } + } else { + if (player->cards[card]) + return; + + player->bonuscount = BONUSADD; + player->message = pickup_message; + player->cards[card] = 1; + } +} + + +// +// P_GivePower +// +qboolean +P_GivePower +( player_t* player, + int /*powertype_t*/ power ) +{ + if (power == pw_invulnerability) + { + player->powers[power] = INVULNTICS; + return true; + } + + if (power == pw_invisibility) + { + player->powers[power] = INVISTICS; + player->mo->flags |= MF_SHADOW; + return true; + } + + if (power == pw_infrared) + { + player->powers[power] = INFRATICS; + return true; + } + + if (power == pw_ironfeet) + { + player->powers[power] = IRONTICS; + return true; + } + + if (power == pw_strength) + { + P_GiveBody (player, 100); + player->powers[power] = 1; + return true; + } + + if (player->powers[power]) + return false; // already got it + + player->powers[power] = 1; + return true; +} + + + +// +// P_TouchSpecialThing +// +void +P_TouchSpecialThing +( mobj_t* special, + mobj_t* toucher ) +{ + player_t* player; + int i; + fixed_t delta; + int sound; + + delta = special->z - toucher->z; + + if (delta > toucher->height + || delta < -8*FRACUNIT) + { + // out of reach + return; + } + + + sound = sfx_itemup; + player = toucher->player; + + // Dead thing touching. + // Can happen with a sliding player corpse. + if (toucher->health <= 0) + return; + + // Identify by sprite. + switch (special->sprite) + { + // armor + case SPR_ARM1: + if (!P_GiveArmor (player, 1)) + return; + player->message = GOTARMOR; + break; + + case SPR_ARM2: + if (!P_GiveArmor (player, 2)) + return; + player->message = GOTMEGA; + break; + + // bonus items + case SPR_BON1: + player->health++; // can go over 100% + if (player->health > 200) + player->health = 200; + player->mo->health = player->health; + player->message = GOTHTHBONUS; + break; + + case SPR_BON2: + player->armorpoints++; // can go over 100% + if (player->armorpoints > 200) + player->armorpoints = 200; + if (!player->armortype) + player->armortype = 1; + player->message = GOTARMBONUS; + break; + + case SPR_SOUL: + player->health += 100; + if (player->health > 200) + player->health = 200; + player->mo->health = player->health; + player->message = GOTSUPER; + sound = sfx_getpow; + break; + + case SPR_MEGA: + if (::g->gamemode != commercial) + return; + player->health = 200; + player->mo->health = player->health; + P_GiveArmor (player,2); + player->message = GOTMSPHERE; + sound = sfx_getpow; + break; + + // cards + // leave cards for everyone + case SPR_BKEY: + //if (!player->cards[it_bluecard]) + //player->message = GOTBLUECARD; + P_GiveCard (player, it_bluecard, GOTBLUECARD); + if (!::g->netgame) + break; + return; + + case SPR_YKEY: + //if (!player->cards[it_yellowcard]) + //player->message = GOTYELWCARD; + P_GiveCard (player, it_yellowcard, GOTYELWCARD); + if (!::g->netgame) + break; + return; + + case SPR_RKEY: + //if (!player->cards[it_redcard]) + //player->message = GOTREDCARD; + P_GiveCard (player, it_redcard, GOTREDCARD); + if (!::g->netgame) + break; + return; + + case SPR_BSKU: + //if (!player->cards[it_blueskull]) + //player->message = GOTBLUESKUL; + P_GiveCard (player, it_blueskull, GOTBLUESKUL); + if (!::g->netgame) + break; + return; + + case SPR_YSKU: + //if (!player->cards[it_yellowskull]) + //player->message = GOTYELWSKUL; + P_GiveCard (player, it_yellowskull, GOTYELWSKUL); + if (!::g->netgame) + break; + return; + + case SPR_RSKU: + //if (!player->cards[it_redskull]) + //player->message = GOTREDSKULL; + P_GiveCard (player, it_redskull, GOTREDSKULL); + if (!::g->netgame) + break; + return; + + // medikits, heals + case SPR_STIM: + if (!P_GiveBody (player, 10)) + return; + player->message = GOTSTIM; + break; + + case SPR_MEDI: + if (!P_GiveBody (player, 25)) + return; + + if (player->health < 25) + player->message = GOTMEDINEED; + else + player->message = GOTMEDIKIT; + break; + + + // power ups + case SPR_PINV: + if (!P_GivePower (player, pw_invulnerability)) + return; + player->message = GOTINVUL; + sound = sfx_getpow; + break; + + case SPR_PSTR: + if (!P_GivePower (player, pw_strength)) + return; + player->message = GOTBERSERK; + if (player->readyweapon != wp_fist) + player->pendingweapon = wp_fist; + sound = sfx_getpow; + break; + + case SPR_PINS: + if (!P_GivePower (player, pw_invisibility)) + return; + player->message = GOTINVIS; + sound = sfx_getpow; + break; + + case SPR_SUIT: + if (!P_GivePower (player, pw_ironfeet)) + return; + player->message = GOTSUIT; + sound = sfx_getpow; + break; + + case SPR_PMAP: + if (!P_GivePower (player, pw_allmap)) + return; + player->message = GOTMAP; + sound = sfx_getpow; + break; + + case SPR_PVIS: + if (!P_GivePower (player, pw_infrared)) + return; + player->message = GOTVISOR; + sound = sfx_getpow; + break; + + // ammo + case SPR_CLIP: + if (special->flags & MF_DROPPED) + { + if (!P_GiveAmmo (player,am_clip,0)) + return; + } + else + { + if (!P_GiveAmmo (player,am_clip,1)) + return; + } + player->message = GOTCLIP; + break; + + case SPR_AMMO: + if (!P_GiveAmmo (player, am_clip,5)) + return; + player->message = GOTCLIPBOX; + break; + + case SPR_ROCK: + if (!P_GiveAmmo (player, am_misl,1)) + return; + player->message = GOTROCKET; + break; + + case SPR_BROK: + if (!P_GiveAmmo (player, am_misl,5)) + return; + player->message = GOTROCKBOX; + break; + + case SPR_CELL: + if (!P_GiveAmmo (player, am_cell,1)) + return; + player->message = GOTCELL; + break; + + case SPR_CELP: + if (!P_GiveAmmo (player, am_cell,5)) + return; + player->message = GOTCELLBOX; + break; + + case SPR_SHEL: + if (!P_GiveAmmo (player, am_shell,1)) + return; + player->message = GOTSHELLS; + break; + + case SPR_SBOX: + if (!P_GiveAmmo (player, am_shell,5)) + return; + player->message = GOTSHELLBOX; + break; + + case SPR_BPAK: + if (!player->backpack) + { + for (i=0 ; imaxammo[i] *= 2; + player->backpack = true; + } + for (i=0 ; imessage = GOTBACKPACK; + break; + + // weapons + case SPR_BFUG: + if (!P_GiveWeapon (player, wp_bfg, false) ) + return; + + // DHM - Nerve :: Give achievement + if ( !common->IsMultiplayer() ) { + switch( DoomLib::GetGameSKU() ) { + case GAME_SKU_DOOM2_BFG: { + idAchievementManager::LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM2_REALLY_BIG_GUN_FIND_BFG_SINGLEPLAYER ); + } + default: { + // No unlocks for other SKUs. + break; + } + } + } + + player->message = GOTBFG9000; + sound = sfx_wpnup; + break; + + case SPR_MGUN: + if (!P_GiveWeapon (player, wp_chaingun, special->flags&MF_DROPPED) ) + return; + player->message = GOTCHAINGUN; + sound = sfx_wpnup; + break; + + case SPR_CSAW: + if (!P_GiveWeapon (player, wp_chainsaw, false) ) + return; + player->message = GOTCHAINSAW; + sound = sfx_wpnup; + break; + + case SPR_LAUN: + if (!P_GiveWeapon (player, wp_missile, false) ) + return; + player->message = GOTLAUNCHER; + sound = sfx_wpnup; + break; + + case SPR_PLAS: + if (!P_GiveWeapon (player, wp_plasma, false) ) + return; + player->message = GOTPLASMA; + sound = sfx_wpnup; + break; + + case SPR_SHOT: + if (!P_GiveWeapon (player, wp_shotgun, special->flags&MF_DROPPED ) ) + return; + player->message = GOTSHOTGUN; + sound = sfx_wpnup; + break; + + case SPR_SGN2: + if (!P_GiveWeapon (player, wp_supershotgun, special->flags&MF_DROPPED ) ) + return; + + player->message = GOTSHOTGUN2; + sound = sfx_wpnup; + break; + + default: + I_Error ("P_SpecialThing: Unknown gettable thing"); + } + + if (special->flags & MF_COUNTITEM) + player->itemcount++; + P_RemoveMobj (special); + player->bonuscount += BONUSADD; + if (player == &::g->players[::g->consoleplayer]) + S_StartSound (player->mo, sound); +} + +// +// IsOnlineDeathmatchWithLocalProfile +// +// Helper to simplify the online frag stat tracking. Returns the +// master user's profile if successful, NULL if not. +// +idPlayerProfile * IsOnlineDeathmatchWithLocalProfile() { + if ( !MatchTypeIsOnline( session->GetGameLobbyBase().GetMatchParms().matchFlags ) ) { + return NULL; + } + + if ( !::g ) { + return NULL; + } + + if ( !::g->deathmatch ) { + return NULL; + } + + // Assume that the master local user is the one playing. + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user == NULL ) { + return NULL; + } + + idPlayerProfile * profile = user->GetProfile(); + + if ( profile == NULL ) { + return NULL; + } + + return profile; +} + +// +// KillMobj +// +void +P_KillMobj +( mobj_t* source, + mobj_t* target ) +{ + mobjtype_t item; + mobj_t* mo; + + target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY); + + if (target->type != MT_SKULL) + target->flags &= ~MF_NOGRAVITY; + + target->flags |= MF_CORPSE|MF_DROPOFF; + target->height >>= 2; + + if (source && source->player) + { + // count for intermission + if (target->flags & MF_COUNTKILL) + source->player->killcount++; + + if (target->player) { + source->player->frags[target->player-::g->players]++; + + // Keep track of the local player's total frags for trophy awards. + + // Make sure the killing player is the local player + if ( source->player == &(::g->players[::g->consoleplayer]) ) { + // Make sure this is an online game. + // TODO: PC + } + } + + // DHM - Nerve :: Check for killing cyberdemon with fists achievement + // JAF TROPHY int port = gameLocal->GetPortForPlayer( DoomLib::GetPlayer() ); + + if ( source->player->readyweapon == wp_fist && target->type == MT_CYBORG && !common->IsMultiplayer() ) { + switch( DoomLib::GetGameSKU() ) { + case GAME_SKU_DOOM2_BFG: { + // Removing trophies for DOOM and DOOM II BFG due to point limit. + //gameLocal->UnlockAchievement( Doom2BFG_Trophies::YOU_HAVE_HUGE_GUTS_KILL_CYBERDEMON_WITH_FISTS ); + break; + } + case GAME_SKU_DCC: { + // Not for PC. + //session->GetAchievementSystem().AchievementUnlock( session->GetSignInManager().GetMasterLocalUser(), DOOM_ACHIEVEMENT_KILL_CYBER_DEMON_WITH_FISTS ); + break; + } + default: { + // No unlocks for other SKUs. + break; + } + } + } + + // DHM - Nerve :: Chainsaw kills + if ( source->player->readyweapon == wp_chainsaw && !common->IsMultiplayer() ) { + source->player->chainsawKills++; + if ( source->player->chainsawKills == 20 ) { + switch( DoomLib::GetGameSKU() ) { + case GAME_SKU_DOOM2_BFG: { + // Removing trophies for DOOM and DOOM II BFG due to point limit. + //gameLocal->UnlockAchievement( Doom2BFG_Trophies::GREAT_COMMUNICATOR_20_CHAINSAW_KILLS ); + break; + } + case GAME_SKU_DCC: { + // Not for PC. + //gameLocal->UnlockAchievement( DOOM_ACHIEVEMENT_20KILLS_CHAINSAW ); + break; + } + default: { + // No unlocks for other SKUs. + break; + } + } + } + } + + // DHM - Nerve :: Berserker kills + if ( source->player->readyweapon == wp_fist && source->player->powers[pw_strength] && !common->IsMultiplayer()) { + source->player->berserkKills++; + idLib::Printf( "Player has %d berserk kills\n", source->player->berserkKills ); + if ( source->player->berserkKills == 20 ) { + switch( DoomLib::GetGameSKU() ) { + case GAME_SKU_DOOM2_BFG: { + // Removing trophies for DOOM and DOOM II BFG due to point limit. + //gameLocal->UnlockAchievement( Doom2BFG_Trophies::MAN_AND_A_HALF_20_BERSERK_KILLS ); + break; + } + case GAME_SKU_DCC: { + // Not for PC. + //gameLocal->UnlockAchievement( DOOM_ACHIEVEMENT_20KILLS_BERSERKER ); + break; + } + default: { + // No unlocks for other SKUs. + break; + } + } + } + } + } + else if (!::g->netgame && (target->flags & MF_COUNTKILL) ) + { + // count all monster deaths, + // even those caused by other monsters + ::g->players[0].killcount++; + } + + if (target->player) + { + // count environment kills against you + if (!source) + target->player->frags[target->player-::g->players]++; + + target->flags &= ~MF_SOLID; + target->player->playerstate = PST_DEAD; + P_DropWeapon (target->player); + + if (target->player == &::g->players[::g->consoleplayer] + && ::g->automapactive) + { + // don't die in auto map, + // switch view prior to dying + AM_Stop (); + } + + } + + if (target->health < -target->info->spawnhealth + && target->info->xdeathstate) + { + P_SetMobjState (target, (statenum_t)target->info->xdeathstate); + } + else + P_SetMobjState (target, (statenum_t)target->info->deathstate); + target->tics -= P_Random()&3; + + if (target->tics < 1) + target->tics = 1; + + // I_StartSound (&actor->r, actor->info->deathsound); + + + // Drop stuff. + // This determines the kind of object spawned + // during the death frame of a thing. + switch (target->type) + { + case MT_WOLFSS: + case MT_POSSESSED: + item = MT_CLIP; + break; + + case MT_SHOTGUY: + item = MT_SHOTGUN; + break; + + case MT_CHAINGUY: + item = MT_CHAINGUN; + break; + + default: + return; + } + + mo = P_SpawnMobj (target->x,target->y,ONFLOORZ, item); + mo->flags |= MF_DROPPED; // special versions of items +} + + + + +// +// P_DamageMobj +// Damages both enemies and ::g->players +// "inflictor" is the thing that caused the damage +// creature or missile, can be NULL (slime, etc) +// "source" is the thing to target after taking damage +// creature or NULL +// Source and inflictor are the same for melee attacks. +// Source can be NULL for slime, barrel explosions +// and other environmental stuff. +// +void +P_DamageMobj +( mobj_t* target, + mobj_t* inflictor, + mobj_t* source, + int damage ) +{ + unsigned ang; + int saved; + player_t* player; + fixed_t thrust; + int temp; + + if ( !(target->flags & MF_SHOOTABLE) ) + return; // shouldn't happen... + + if (target->health <= 0) + return; + + if ( target->flags & MF_SKULLFLY ) + { + target->momx = target->momy = target->momz = 0; + } + + player = target->player; + if (player && ::g->gameskill == sk_baby) + damage >>= 1; // take half damage in trainer mode + + + // Some close combat weapons should not + // inflict thrust and push the victim out of reach, + // thus kick away unless using the chainsaw. + if (inflictor + && !(target->flags & MF_NOCLIP) + && (!source + || !source->player + || source->player->readyweapon != wp_chainsaw)) + { + ang = R_PointToAngle2 ( inflictor->x, + inflictor->y, + target->x, + target->y); + + thrust = damage*(FRACUNIT>>3)*100/target->info->mass; + + // make fall forwards sometimes + if ( damage < 40 + && damage > target->health + && target->z - inflictor->z > 64*FRACUNIT + && (P_Random ()&1) ) + { + ang += ANG180; + thrust *= 4; + } + + ang >>= ANGLETOFINESHIFT; + target->momx += FixedMul (thrust, finecosine[ang]); + target->momy += FixedMul (thrust, finesine[ang]); + } + + // player specific + if (player) + { + + // end of game hell hack + if (target->subsector->sector->special == 11 + && damage >= target->health) + { + damage = target->health - 1; + } + + float baseShake_High = 0.5f; + int baseShake_High_Dur = 100; + float baseShake_Low = 0.5f; + int baseShake_Low_Dur = 100; + int damageClamp = Min( damage, 100 ); + float damageFloat = std::min( (float)damageClamp / 100.0f, 100.0f ); + float additional = 0.5f * damageFloat; + int additional_time = 500.0f * damageFloat; + + if( ::g->plyr == player ) { + } + + + // Below certain threshold, + // ignore damage in GOD mode, or with INVUL power. + if ( damage < 1000 + && ( (player->cheats&CF_GODMODE) + || player->powers[pw_invulnerability] ) ) + { + return; + } + + + if (player->armortype) + { + if (player->armortype == 1) + saved = damage/3; + else + saved = damage/2; + + if (player->armorpoints <= saved) + { + // armor is used up + saved = player->armorpoints; + player->armortype = 0; + } + player->armorpoints -= saved; + damage -= saved; + } + player->health -= damage; // mirror mobj health here for Dave + if (player->health < 0) + player->health = 0; + + player->attacker = source; + player->damagecount += damage; // add damage after armor / invuln + + if (player->damagecount > 100) + player->damagecount = 100; // teleport stomp does 10k points... + + temp = damage < 100 ? damage : 100; + } + + // do the damage + target->health -= damage; + if (target->health <= 0) + { + P_KillMobj (source, target); + return; + } + + if ( (P_Random () < target->info->painchance) + && !(target->flags&MF_SKULLFLY) ) + { + target->flags |= MF_JUSTHIT; // fight back! + + P_SetMobjState (target, (statenum_t)target->info->painstate); + } + + target->reactiontime = 0; // we're awake now... + + if ( (!target->threshold || target->type == MT_VILE) + && source && source != target + && source->type != MT_VILE) + { + // if not intent on another player, + // chase after this one + target->target = source; + target->threshold = BASETHRESHOLD; + if (target->state == &::g->states[target->info->spawnstate] + && target->info->seestate != S_NULL) + P_SetMobjState (target, (statenum_t)target->info->seestate); + } + +} + + diff --git a/doomclassic/doom/p_inter.h b/doomclassic/doom/p_inter.h new file mode 100644 index 00000000..da47aeba --- /dev/null +++ b/doomclassic/doom/p_inter.h @@ -0,0 +1,43 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_INTER__ +#define __P_INTER__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +qboolean P_GivePower(player_t*, int); + + + +#endif + diff --git a/doomclassic/doom/p_lights.cpp b/doomclassic/doom/p_lights.cpp new file mode 100644 index 00000000..a673855e --- /dev/null +++ b/doomclassic/doom/p_lights.cpp @@ -0,0 +1,362 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "z_zone.h" +#include "m_random.h" + +#include "doomdef.h" +#include "p_local.h" + + +// State. +#include "r_state.h" + +// +// FIRELIGHT FLICKER +// + +// +// T_FireFlicker +// +void T_FireFlicker (fireflicker_t* flick) +{ + int amount; + + if (--flick->count) + return; + + amount = (P_Random()&3)*16; + + if (flick->sector->lightlevel - amount < flick->minlight) + flick->sector->lightlevel = flick->minlight; + else + flick->sector->lightlevel = flick->maxlight - amount; + + flick->count = 4; +} + + + +// +// P_SpawnFireFlicker +// +void P_SpawnFireFlicker (sector_t* sector) +{ + fireflicker_t* flick; + + // Note that we are resetting sector attributes. + // Nothing special about it during gameplay. + sector->special = 0; + + flick = (fireflicker_t*)DoomLib::Z_Malloc( sizeof(*flick), PU_LEVEL, 0); + + P_AddThinker (&flick->thinker); + + flick->thinker.function.acp1 = (actionf_p1) T_FireFlicker; + flick->sector = sector; + flick->maxlight = sector->lightlevel; + flick->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel)+16; + flick->count = 4; +} + + + +// +// BROKEN LIGHT FLASHING +// + + +// +// T_LightFlash +// Do flashing lights. +// +void T_LightFlash (lightflash_t* flash) +{ + if (--flash->count) + return; + + if (flash->sector->lightlevel == flash->maxlight) + { + flash-> sector->lightlevel = flash->minlight; + flash->count = (P_Random()&flash->mintime)+1; + } + else + { + flash-> sector->lightlevel = flash->maxlight; + flash->count = (P_Random()&flash->maxtime)+1; + } + +} + + + + +// +// P_SpawnLightFlash +// After the map has been loaded, scan each sector +// for specials that spawn thinkers +// +void P_SpawnLightFlash (sector_t* sector) +{ + lightflash_t* flash; + + // nothing special about it during gameplay + sector->special = 0; + + flash = (lightflash_t*)DoomLib::Z_Malloc( sizeof(*flash), PU_LEVEL, 0); + + P_AddThinker (&flash->thinker); + + flash->thinker.function.acp1 = (actionf_p1) T_LightFlash; + flash->sector = sector; + flash->maxlight = sector->lightlevel; + + flash->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel); + flash->maxtime = 64; + flash->mintime = 7; + flash->count = (P_Random()&flash->maxtime)+1; +} + + + +// +// STROBE LIGHT FLASHING +// + + +// +// T_StrobeFlash +// +void T_StrobeFlash (strobe_t* flash) +{ + if (--flash->count) + return; + + if (flash->sector->lightlevel == flash->minlight) + { + flash-> sector->lightlevel = flash->maxlight; + flash->count = flash->brighttime; + } + else + { + flash-> sector->lightlevel = flash->minlight; + flash->count =flash->darktime; + } + +} + + + +// +// P_SpawnStrobeFlash +// After the map has been loaded, scan each sector +// for specials that spawn thinkers +// +void +P_SpawnStrobeFlash +( sector_t* sector, + int fastOrSlow, + int inSync ) +{ + strobe_t* flash; + + flash = (strobe_t*)DoomLib::Z_Malloc( sizeof(*flash), PU_LEVEL, 0); + + P_AddThinker (&flash->thinker); + + flash->sector = sector; + flash->darktime = fastOrSlow; + flash->brighttime = STROBEBRIGHT; + flash->thinker.function.acp1 = (actionf_p1) T_StrobeFlash; + flash->maxlight = sector->lightlevel; + flash->minlight = P_FindMinSurroundingLight(sector, sector->lightlevel); + + if (flash->minlight == flash->maxlight) + flash->minlight = 0; + + // nothing special about it during gameplay + sector->special = 0; + + if (!inSync) + flash->count = (P_Random()&7)+1; + else + flash->count = 1; +} + + +// +// Start strobing lights (usually from a trigger) +// +void EV_StartLightStrobing(line_t* line) +{ + int secnum; + sector_t* sec; + + secnum = -1; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = &::g->sectors[secnum]; + if (sec->specialdata) + continue; + + P_SpawnStrobeFlash (sec,SLOWDARK, 0); + } +} + + + +// +// TURN LINE'S TAG LIGHTS OFF +// +void EV_TurnTagLightsOff(line_t* line) +{ + int i; + int j; + int min; + sector_t* sector; + sector_t* tsec; + line_t* templine; + + sector = ::g->sectors; + + for (j = 0;j < ::g->numsectors; j++, sector++) + { + if (sector->tag == line->tag) + { + min = sector->lightlevel; + for (i = 0;i < sector->linecount; i++) + { + templine = sector->lines[i]; + tsec = getNextSector(templine,sector); + if (!tsec) + continue; + if (tsec->lightlevel < min) + min = tsec->lightlevel; + } + sector->lightlevel = min; + } + } +} + + +// +// TURN LINE'S TAG LIGHTS ON +// +void +EV_LightTurnOn +( line_t* line, + int bright ) +{ + int i; + int j; + sector_t* sector; + sector_t* temp; + line_t* templine; + + sector = ::g->sectors; + + for (i = 0; i < ::g->numsectors; i++, sector++) + { + if (sector->tag == line->tag) + { + // bright = 0 means to search + // for highest light level + // surrounding sector + if (!bright) + { + for (j = 0;j < sector->linecount; j++) + { + templine = sector->lines[j]; + temp = getNextSector(templine,sector); + + if (!temp) + continue; + + if (temp->lightlevel > bright) + bright = temp->lightlevel; + } + } + sector-> lightlevel = bright; + } + } +} + + +// +// Spawn glowing light +// + +void T_Glow(glow_t* g) +{ + switch(g->direction) + { + case -1: + // DOWN + g->sector->lightlevel -= GLOWSPEED; + if (g->sector->lightlevel <= g->minlight) + { + g->sector->lightlevel += GLOWSPEED; + g->direction = 1; + } + break; + + case 1: + // UP + g->sector->lightlevel += GLOWSPEED; + if (g->sector->lightlevel >= g->maxlight) + { + g->sector->lightlevel -= GLOWSPEED; + g->direction = -1; + } + break; + } +} + + +void P_SpawnGlowingLight(sector_t* sector) +{ + glow_t* g; + + g = (glow_t*)DoomLib::Z_Malloc( sizeof(*g), PU_LEVEL, 0); + + P_AddThinker(&g->thinker); + + g->sector = sector; + g->minlight = P_FindMinSurroundingLight(sector,sector->lightlevel); + g->maxlight = sector->lightlevel; + g->thinker.function.acp1 = (actionf_p1) T_Glow; + g->direction = -1; + + sector->special = 0; +} + + diff --git a/doomclassic/doom/p_local.h b/doomclassic/doom/p_local.h new file mode 100644 index 00000000..89c67394 --- /dev/null +++ b/doomclassic/doom/p_local.h @@ -0,0 +1,282 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_LOCAL__ +#define __P_LOCAL__ + +#ifndef __R_LOCAL__ +#include "r_local.h" +#endif + +#define FLOATSPEED (FRACUNIT*4) + + +#define MAXHEALTH 100 +#define VIEWHEIGHT (41*FRACUNIT) + +// mapblocks are used to check movement +// against lines and things +#define MAPBLOCKUNITS 128 +#define MAPBLOCKSIZE (MAPBLOCKUNITS*FRACUNIT) +#define MAPBLOCKSHIFT (FRACBITS+7) +#define MAPBMASK (MAPBLOCKSIZE-1) +#define MAPBTOFRAC (MAPBLOCKSHIFT-FRACBITS) + + +// player radius for movement checking +#define PLAYERRADIUS 16*FRACUNIT + +// MAXRADIUS is for precalculated sector block boxes +// the spider demon is larger, +// but we do not have any moving sectors nearby +#define MAXRADIUS 32*FRACUNIT + +#define GRAVITY FRACUNIT +#define MAXMOVE (30*FRACUNIT) + +#define USERANGE (64*FRACUNIT) +#define MELEERANGE (64*FRACUNIT) +#define MISSILERANGE (32*64*FRACUNIT) + +// follow a player exlusively for 3 seconds +#define BASETHRESHOLD 100 + + + +// +// P_TICK +// + +// both the head and tail of the thinker list +extern thinker_t thinkercap; + + +void P_InitThinkers (void); +void P_AddThinker (thinker_t* thinker); +void P_RemoveThinker (thinker_t* thinker); + + +// +// P_PSPR +// +void P_SetupPsprites (player_t* curplayer); +void P_MovePsprites (player_t* curplayer); +void P_DropWeapon (player_t* player); + + +// +// P_USER +// +void P_PlayerThink (player_t* player); + + +// +// P_MOBJ +// +#define ONFLOORZ MININT +#define ONCEILINGZ MAXINT + +// Time interval for item respawning. +#define ITEMQUESIZE 128 + +extern mapthing_t itemrespawnque[ITEMQUESIZE]; +extern int itemrespawntime[ITEMQUESIZE]; +extern int iquehead; +extern int iquetail; + + +void P_RespawnSpecials (void); + +mobj_t* +P_SpawnMobj +( fixed_t x, + fixed_t y, + fixed_t z, + mobjtype_t type ); + +void P_RemoveMobj (mobj_t* th); +qboolean P_SetMobjState (mobj_t* mobj, statenum_t state); +void P_MobjThinker (mobj_t* mobj); + +void P_SpawnPuff (fixed_t x, fixed_t y, fixed_t z); +void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage); +mobj_t* P_SpawnMissile (mobj_t* source, mobj_t* dest, mobjtype_t type); +void P_SpawnPlayerMissile (mobj_t* source, mobjtype_t type); + + +// +// P_ENEMY +// +void P_NoiseAlert (mobj_t* target, mobj_t* emmiter); + + +// +// P_MAPUTL +// +typedef struct +{ + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + +} divline_t; + +typedef struct +{ + fixed_t frac; // along trace line + qboolean isaline; + union { + mobj_t* thing; + line_t* line; + } d; +} intercept_t; + +#define MAXINTERCEPTS 128 + +extern intercept_t intercepts[MAXINTERCEPTS]; +extern intercept_t* intercept_p; + +typedef qboolean (*traverser_t) (intercept_t *in); + +fixed_t P_AproxDistance (fixed_t dx, fixed_t dy); +int P_PointOnLineSide (fixed_t x, fixed_t y, line_t* line); +int P_PointOnDivlineSide (fixed_t x, fixed_t y, divline_t* line); +void P_MakeDivline (line_t* li, divline_t* dl); +fixed_t P_InterceptVector (divline_t* v2, divline_t* v1); +int P_BoxOnLineSide (fixed_t* tmbox, line_t* ld); + +extern fixed_t opentop; +extern fixed_t openbottom; +extern fixed_t openrange; +extern fixed_t lowfloor; + +void P_LineOpening (line_t* linedef); + +qboolean P_BlockLinesIterator (int x, int y, qboolean(*func)(line_t*) ); +qboolean P_BlockThingsIterator (int x, int y, qboolean(*func)(mobj_t*) ); + +#define PT_ADDLINES 1 +#define PT_ADDTHINGS 2 +#define PT_EARLYOUT 4 + +extern divline_t trace; + +qboolean +P_PathTraverse +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2, + int flags, + qboolean (*trav) (intercept_t *)); + +void P_UnsetThingPosition (mobj_t* thing); +void P_SetThingPosition (mobj_t* thing); + + +// +// P_MAP +// + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". +extern qboolean floatok; +extern fixed_t tmfloorz; +extern fixed_t tmceilingz; + + +extern line_t* ceilingline; + +qboolean P_CheckPosition (mobj_t *thing, fixed_t x, fixed_t y); +qboolean P_TryMove (mobj_t* thing, fixed_t x, fixed_t y); +qboolean P_TeleportMove (mobj_t* thing, fixed_t x, fixed_t y); +void P_SlideMove (mobj_t* mo); +qboolean P_CheckSight (mobj_t* t1, mobj_t* t2); +void P_UseLines (player_t* player); + +qboolean P_ChangeSector (sector_t* sector, qboolean crunch); + +extern mobj_t* linetarget; // who got hit (or NULL) + +fixed_t +P_AimLineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance ); + +void +P_LineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance, + fixed_t slope, + int damage ); + +void +P_RadiusAttack +( mobj_t* spot, + mobj_t* source, + int damage ); + + + +// +// P_SETUP +// + + +// +// P_INTER +// +extern const int maxammo[NUMAMMO]; +extern const int clipammo[NUMAMMO]; + +void +P_TouchSpecialThing +( mobj_t* special, + mobj_t* toucher ); + +void +P_DamageMobj +( mobj_t* target, + mobj_t* inflictor, + mobj_t* source, + int damage ); + + +// +// P_SPEC +// +#include "p_spec.h" + + +#endif // __P_LOCAL__ + + + diff --git a/doomclassic/doom/p_map.cpp b/doomclassic/doom/p_map.cpp new file mode 100644 index 00000000..a2d71698 --- /dev/null +++ b/doomclassic/doom/p_map.cpp @@ -0,0 +1,1353 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +#include "m_bbox.h" +#include "m_random.h" +#include "i_system.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" +// Data. +#include "sounds.h" + +#include "Main.h" + + +// If "floatok" true, move would be ok +// if within "tmfloorz - tmceilingz". + + +// keep track of the line that lowers the ceiling, +// so missiles don't explode against sky hack walls + +// keep track of special ::g->lines as they are hit, +// but don't process them until the move is proven valid + + + + +// +// TELEPORT MOVE +// + +// +// PIT_StompThing +// +qboolean PIT_StompThing (mobj_t* thing) +{ + fixed_t blockdist; + + if (!(thing->flags & MF_SHOOTABLE) ) + return true; + + blockdist = thing->radius + ::g->tmthing->radius; + + if ( abs(thing->x - ::g->tmx) >= blockdist + || abs(thing->y - ::g->tmy) >= blockdist ) + { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == ::g->tmthing) + return true; + + // monsters don't stomp things except on boss level + if ( !::g->tmthing->player && ::g->gamemap != 30) + return false; + + P_DamageMobj (thing, ::g->tmthing, ::g->tmthing, 10000); + + return true; +} + + +// +// P_TeleportMove +// +qboolean +P_TeleportMove +( mobj_t* thing, + fixed_t x, + fixed_t y ) +{ + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + + subsector_t* newsubsec; + + // kill anything occupying the position + ::g->tmthing = thing; + ::g->tmflags = thing->flags; + + ::g->tmx = x; + ::g->tmy = y; + + ::g->tmbbox[BOXTOP] = y + ::g->tmthing->radius; + ::g->tmbbox[BOXBOTTOM] = y - ::g->tmthing->radius; + ::g->tmbbox[BOXRIGHT] = x + ::g->tmthing->radius; + ::g->tmbbox[BOXLEFT] = x - ::g->tmthing->radius; + + newsubsec = R_PointInSubsector (x,y); + ::g->ceilingline = NULL; + + // The base floor/ceiling is from the subsector + // that contains the point. + // Any contacted ::g->lines the step closer together + // will adjust them. + ::g->tmfloorz = ::g->tmdropoffz = newsubsec->sector->floorheight; + ::g->tmceilingz = newsubsec->sector->ceilingheight; + + ::g->validcount++; + ::g->numspechit = 0; + + // stomp on any things contacted + xl = (::g->tmbbox[BOXLEFT] - ::g->bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; + xh = (::g->tmbbox[BOXRIGHT] - ::g->bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; + yl = (::g->tmbbox[BOXBOTTOM] - ::g->bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; + yh = (::g->tmbbox[BOXTOP] - ::g->bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; + + for (bx=xl ; bx<=xh ; bx++) + for (by=yl ; by<=yh ; by++) + if (!P_BlockThingsIterator(bx,by,PIT_StompThing)) + return false; + + // the move is ok, + // so link the thing into its new position + P_UnsetThingPosition (thing); + + thing->floorz = ::g->tmfloorz; + thing->ceilingz = ::g->tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition (thing); + + return true; +} + + +// +// MOVEMENT ITERATOR FUNCTIONS +// + + +// +// PIT_CheckLine +// Adjusts ::g->tmfloorz and ::g->tmceilingz as ::g->lines are contacted +// +qboolean PIT_CheckLine (line_t* ld) +{ + if (::g->tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] + || ::g->tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] + || ::g->tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] + || ::g->tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] ) + return true; + + if (P_BoxOnLineSide (::g->tmbbox, ld) != -1) + return true; + + // A line has been hit + + // The moving thing's destination position will cross + // the given line. + // If this should not be allowed, return false. + // If the line is special, keep track of it + // to process later if the move is proven ok. + // NOTE: specials are NOT sorted by order, + // so two special ::g->lines that are only 8 pixels apart + // could be crossed in either order. + + if (!ld->backsector) + return false; // one sided line + + if (!(::g->tmthing->flags & MF_MISSILE) ) + { + if ( ld->flags & ML_BLOCKING ) + return false; // explicitly blocking everything + + if ( !::g->tmthing->player && ld->flags & ML_BLOCKMONSTERS ) + return false; // block monsters only + } + + // set ::g->openrange, ::g->opentop, ::g->openbottom + P_LineOpening (ld); + + // adjust floor / ceiling heights + if (::g->opentop < ::g->tmceilingz) + { + ::g->tmceilingz = ::g->opentop; + ::g->ceilingline = ld; + } + + if (::g->openbottom > ::g->tmfloorz) + ::g->tmfloorz = ::g->openbottom; + + if (::g->lowfloor < ::g->tmdropoffz) + ::g->tmdropoffz = ::g->lowfloor; + + // if contacted a special line, add it to the list + if (ld->special && ::g->numspechit < MAXSPECIALCROSS ) + { + ::g->spechit[::g->numspechit] = ld; + ::g->numspechit++; + } + + return true; +} + +// +// PIT_CheckThing +// +qboolean PIT_CheckThing (mobj_t* thing) +{ + fixed_t blockdist; + qboolean solid; + int damage; + + if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE) )) + return true; + + blockdist = thing->radius + ::g->tmthing->radius; + + if ( abs(thing->x - ::g->tmx) >= blockdist + || abs(thing->y - ::g->tmy) >= blockdist ) + { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == ::g->tmthing) + return true; + + // check for skulls slamming into things + if (::g->tmthing->flags & MF_SKULLFLY) + { + damage = ((P_Random()%8)+1)*::g->tmthing->info->damage; + + P_DamageMobj (thing, ::g->tmthing, ::g->tmthing, damage); + + ::g->tmthing->flags &= ~MF_SKULLFLY; + ::g->tmthing->momx = ::g->tmthing->momy = ::g->tmthing->momz = 0; + + P_SetMobjState (::g->tmthing, (statenum_t)::g->tmthing->info->spawnstate); + + return false; // stop moving + } + + + // missiles can hit other things + if (::g->tmthing->flags & MF_MISSILE) + { + // see if it went over / under + if (::g->tmthing->z > thing->z + thing->height) + return true; // overhead + if (::g->tmthing->z+::g->tmthing->height < thing->z) + return true; // underneath + + if (::g->tmthing->target && ( + ::g->tmthing->target->type == thing->type || + (::g->tmthing->target->type == MT_KNIGHT && thing->type == MT_BRUISER)|| + (::g->tmthing->target->type == MT_BRUISER && thing->type == MT_KNIGHT) ) ) + { + // Don't hit same species as originator. + if (thing == ::g->tmthing->target) + return true; + + if (thing->type != MT_PLAYER) + { + // Explode, but do no damage. + // Let ::g->players missile other ::g->players. + return false; + } + } + + if (! (thing->flags & MF_SHOOTABLE) ) + { + // didn't do any damage + return !(thing->flags & MF_SOLID); + } + + // damage / explode + damage = ((P_Random()%8)+1)*::g->tmthing->info->damage; + P_DamageMobj (thing, ::g->tmthing, ::g->tmthing->target, damage); + + // don't traverse any more + return false; + } + + // check for special pickup + if (thing->flags & MF_SPECIAL) + { + solid = thing->flags&MF_SOLID; + if (::g->tmflags&MF_PICKUP) + { + // can remove thing + P_TouchSpecialThing (thing, ::g->tmthing); + } + return !solid; + } + + return !(thing->flags & MF_SOLID); +} + + +// +// MOVEMENT CLIPPING +// + +// +// P_CheckPosition +// This is purely informative, nothing is modified +// (except things picked up). +// +// in: +// a mobj_t (can be valid or invalid) +// a position to be checked +// (doesn't need to be related to the mobj_t->x,y) +// +// during: +// special things are touched if MF_PICKUP +// early out on solid lines? +// +// out: +// newsubsec +// floorz +// ceilingz +// ::g->tmdropoffz +// the lowest point contacted +// (monsters won't move to a dropoff) +// speciallines[] +// numspeciallines +// +qboolean +P_CheckPosition +( mobj_t* thing, + fixed_t x, + fixed_t y ) +{ + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + subsector_t* newsubsec; + + ::g->tmthing = thing; + ::g->tmflags = thing->flags; + + ::g->tmx = x; + ::g->tmy = y; + + ::g->tmbbox[BOXTOP] = y + ::g->tmthing->radius; + ::g->tmbbox[BOXBOTTOM] = y - ::g->tmthing->radius; + ::g->tmbbox[BOXRIGHT] = x + ::g->tmthing->radius; + ::g->tmbbox[BOXLEFT] = x - ::g->tmthing->radius; + + newsubsec = R_PointInSubsector (x,y); + ::g->ceilingline = NULL; + + // The base floor / ceiling is from the subsector + // that contains the point. + // Any contacted ::g->lines the step closer together + // will adjust them. + ::g->tmfloorz = ::g->tmdropoffz = newsubsec->sector->floorheight; + ::g->tmceilingz = newsubsec->sector->ceilingheight; + + ::g->validcount++; + ::g->numspechit = 0; + + if ( ::g->tmflags & MF_NOCLIP ) + return true; + + // Check things first, possibly picking things up. + // The bounding box is extended by MAXRADIUS + // because mobj_ts are grouped into mapblocks + // based on their origin point, and can overlap + // into adjacent blocks by up to MAXRADIUS units. + xl = (::g->tmbbox[BOXLEFT] - ::g->bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; + xh = (::g->tmbbox[BOXRIGHT] - ::g->bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; + yl = (::g->tmbbox[BOXBOTTOM] - ::g->bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; + yh = (::g->tmbbox[BOXTOP] - ::g->bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; + + for (bx=xl ; bx<=xh ; bx++) + for (by=yl ; by<=yh ; by++) + if (!P_BlockThingsIterator(bx,by,PIT_CheckThing)) + return false; + + // check ::g->lines + xl = (::g->tmbbox[BOXLEFT] - ::g->bmaporgx)>>MAPBLOCKSHIFT; + xh = (::g->tmbbox[BOXRIGHT] - ::g->bmaporgx)>>MAPBLOCKSHIFT; + yl = (::g->tmbbox[BOXBOTTOM] - ::g->bmaporgy)>>MAPBLOCKSHIFT; + yh = (::g->tmbbox[BOXTOP] - ::g->bmaporgy)>>MAPBLOCKSHIFT; + + for (bx=xl ; bx<=xh ; bx++) + for (by=yl ; by<=yh ; by++) + if (!P_BlockLinesIterator (bx,by,PIT_CheckLine)) + return false; + + return true; +} + + +// +// P_TryMove +// Attempt to move to a new position, +// crossing special ::g->lines unless MF_TELEPORT is set. +// +qboolean +P_TryMove +( mobj_t* thing, + fixed_t x, + fixed_t y ) +{ + fixed_t oldx; + fixed_t oldy; + int side; + int oldside; + line_t* ld; + + ::g->floatok = false; + if (!P_CheckPosition (thing, x, y)) + return false; // solid wall or thing + + if ( !(thing->flags & MF_NOCLIP) ) + { + if (::g->tmceilingz - ::g->tmfloorz < thing->height) + return false; // doesn't fit + + ::g->floatok = true; + + if ( !(thing->flags&MF_TELEPORT) + &&::g->tmceilingz - thing->z < thing->height) + return false; // mobj must lower itself to fit + + if ( !(thing->flags&MF_TELEPORT) + && ::g->tmfloorz - thing->z > 24*FRACUNIT ) + return false; // too big a step up + + if ( !(thing->flags&(MF_DROPOFF|MF_FLOAT)) + && ::g->tmfloorz - ::g->tmdropoffz > 24*FRACUNIT ) + return false; // don't stand over a dropoff + } + + // the move is ok, + // so link the thing into its new position + P_UnsetThingPosition (thing); + + oldx = thing->x; + oldy = thing->y; + thing->floorz = ::g->tmfloorz; + thing->ceilingz = ::g->tmceilingz; + thing->x = x; + thing->y = y; + + P_SetThingPosition (thing); + + // if any special ::g->lines were hit, do the effect + if (! (thing->flags&(MF_TELEPORT|MF_NOCLIP)) ) + { + while (::g->numspechit--) + { + // see if the line was crossed + ld = ::g->spechit[::g->numspechit]; + side = P_PointOnLineSide (thing->x, thing->y, ld); + oldside = P_PointOnLineSide (oldx, oldy, ld); + if (side != oldside) + { + if (ld->special) + P_CrossSpecialLine (ld-::g->lines, oldside, thing); + } + } + } + + return true; +} + + +// +// P_ThingHeightClip +// Takes a valid thing and adjusts the thing->floorz, +// thing->ceilingz, and possibly thing->z. +// This is called for all nearby monsters +// whenever a sector changes height. +// If the thing doesn't fit, +// the z will be set to the lowest value +// and false will be returned. +// +qboolean P_ThingHeightClip (mobj_t* thing) +{ + qboolean onfloor; + + onfloor = (thing->z == thing->floorz); + + P_CheckPosition (thing, thing->x, thing->y); + // what about stranding a monster partially off an edge? + + thing->floorz = ::g->tmfloorz; + thing->ceilingz = ::g->tmceilingz; + + if (onfloor) + { + // walking monsters rise and fall with the floor + thing->z = thing->floorz; + } + else + { + // don't adjust a floating monster unless forced to + if (thing->z+thing->height > thing->ceilingz) + thing->z = thing->ceilingz - thing->height; + } + + if (thing->ceilingz - thing->floorz < thing->height) + return false; + + return true; +} + + + +// +// SLIDE MOVE +// Allows the player to slide along any angled walls. +// + + + + + + +// +// P_HitSlideLine +// Adjusts the xmove / ymove +// so that the next move will slide along the wall. +// +void P_HitSlideLine (line_t* ld) +{ + int side; + + angle_t lineangle; + angle_t moveangle; + angle_t deltaangle; + + fixed_t movelen; + fixed_t newlen; + + + if (ld->slopetype == ST_HORIZONTAL) + { + ::g->tmymove = 0; + return; + } + + if (ld->slopetype == ST_VERTICAL) + { + ::g->tmxmove = 0; + return; + } + + side = P_PointOnLineSide (::g->slidemo->x, ::g->slidemo->y, ld); + + lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy); + + if (side == 1) + lineangle += ANG180; + + moveangle = R_PointToAngle2 (0,0, ::g->tmxmove, ::g->tmymove); + deltaangle = moveangle-lineangle; + + if (deltaangle > ANG180) + deltaangle += ANG180; + // I_Error ("SlideLine: ang>ANG180"); + + lineangle >>= ANGLETOFINESHIFT; + deltaangle >>= ANGLETOFINESHIFT; + + movelen = P_AproxDistance (::g->tmxmove, ::g->tmymove); + newlen = FixedMul (movelen, finecosine[deltaangle]); + + ::g->tmxmove = FixedMul (newlen, finecosine[lineangle]); + ::g->tmymove = FixedMul (newlen, finesine[lineangle]); +} + + +// +// PTR_SlideTraverse +// +qboolean PTR_SlideTraverse (intercept_t* in) +{ + line_t* li; + + if (!in->isaline) + I_Error ("PTR_SlideTraverse: not a line?"); + + li = in->d.line; + + if ( ! (li->flags & ML_TWOSIDED) ) + { + if (P_PointOnLineSide (::g->slidemo->x, ::g->slidemo->y, li)) + { + // don't hit the back side + return true; + } + goto isblocking; + } + + // set ::g->openrange, ::g->opentop, ::g->openbottom + P_LineOpening (li); + + if (::g->openrange < ::g->slidemo->height) + goto isblocking; // doesn't fit + + if (::g->opentop - ::g->slidemo->z < ::g->slidemo->height) + goto isblocking; // mobj is too high + + if (::g->openbottom - ::g->slidemo->z > 24*FRACUNIT ) + goto isblocking; // too big a step up + + // this line doesn't block movement + return true; + + // the line does block movement, + // see if it is closer than best so far + isblocking: + if (in->frac < ::g->bestslidefrac) + { + ::g->secondslidefrac = ::g->bestslidefrac; + ::g->secondslideline = ::g->bestslideline; + ::g->bestslidefrac = in->frac; + ::g->bestslideline = li; + } + + return false; // stop +} + + + +// +// P_SlideMove +// The momx / momy move is bad, so try to slide +// along a wall. +// Find the first line hit, move flush to it, +// and slide along it +// +// This is a kludgy mess. +// +void P_SlideMove (mobj_t* mo) +{ + fixed_t leadx; + fixed_t leady; + fixed_t trailx; + fixed_t traily; + fixed_t newx; + fixed_t newy; + int hitcount; + + ::g->slidemo = mo; + hitcount = 0; + + retry: + if (++hitcount == 3) + goto stairstep; // don't loop forever + + + // ::g->trace along the three leading corners + if (mo->momx > 0) + { + leadx = mo->x + mo->radius; + trailx = mo->x - mo->radius; + } + else + { + leadx = mo->x - mo->radius; + trailx = mo->x + mo->radius; + } + + if (mo->momy > 0) + { + leady = mo->y + mo->radius; + traily = mo->y - mo->radius; + } + else + { + leady = mo->y - mo->radius; + traily = mo->y + mo->radius; + } + + ::g->bestslidefrac = FRACUNIT+1; + + P_PathTraverse ( leadx, leady, leadx+mo->momx, leady+mo->momy, + PT_ADDLINES, PTR_SlideTraverse ); + P_PathTraverse ( trailx, leady, trailx+mo->momx, leady+mo->momy, + PT_ADDLINES, PTR_SlideTraverse ); + P_PathTraverse ( leadx, traily, leadx+mo->momx, traily+mo->momy, + PT_ADDLINES, PTR_SlideTraverse ); + + // move up to the wall + if (::g->bestslidefrac == FRACUNIT+1) + { + // the move most have hit the middle, so stairstep + stairstep: + if (!P_TryMove (mo, mo->x, mo->y + mo->momy)) + P_TryMove (mo, mo->x + mo->momx, mo->y); + return; + } + + // fudge a bit to make sure it doesn't hit + ::g->bestslidefrac -= 0x800; + if (::g->bestslidefrac > 0) + { + newx = FixedMul (mo->momx, ::g->bestslidefrac); + newy = FixedMul (mo->momy, ::g->bestslidefrac); + + if (!P_TryMove (mo, mo->x+newx, mo->y+newy)) + goto stairstep; + } + + // Now continue along the wall. + // First calculate remainder. + ::g->bestslidefrac = FRACUNIT-(::g->bestslidefrac+0x800); + + if (::g->bestslidefrac > FRACUNIT) + ::g->bestslidefrac = FRACUNIT; + + if (::g->bestslidefrac <= 0) + return; + + ::g->tmxmove = FixedMul (mo->momx, ::g->bestslidefrac); + ::g->tmymove = FixedMul (mo->momy, ::g->bestslidefrac); + + P_HitSlideLine (::g->bestslideline); // clip the moves + + mo->momx = ::g->tmxmove; + mo->momy = ::g->tmymove; + + if (!P_TryMove (mo, mo->x+::g->tmxmove, mo->y+::g->tmymove)) + { + goto retry; + } +} + + +// +// P_LineAttack +// + +// Height if not aiming up or down +// ???: use slope for monsters? + + + +// slopes to top and bottom of target + + +// +// PTR_AimTraverse +// Sets linetaget and ::g->aimslope when a target is aimed at. +// +qboolean +PTR_AimTraverse (intercept_t* in) +{ + line_t* li; + mobj_t* th; + fixed_t slope; + fixed_t thingtopslope; + fixed_t thingbottomslope; + fixed_t dist; + + if (in->isaline) + { + li = in->d.line; + + if ( !(li->flags & ML_TWOSIDED) ) + return false; // stop + + // Crosses a two sided line. + // A two sided line will restrict + // the possible target ranges. + P_LineOpening (li); + + if (::g->openbottom >= ::g->opentop) + return false; // stop + + dist = FixedMul (::g->attackrange, in->frac); + + if (li->frontsector->floorheight != li->backsector->floorheight) + { + slope = FixedDiv (::g->openbottom - ::g->shootz , dist); + if (slope > ::g->bottomslope) + ::g->bottomslope = slope; + } + + if (li->frontsector->ceilingheight != li->backsector->ceilingheight) + { + slope = FixedDiv (::g->opentop - ::g->shootz , dist); + if (slope < ::g->topslope) + ::g->topslope = slope; + } + + if (::g->topslope <= ::g->bottomslope) + return false; // stop + + return true; // shot continues + } + + // shoot a thing + th = in->d.thing; + if (th == ::g->shootthing) + return true; // can't shoot self + + if (!(th->flags&MF_SHOOTABLE)) + return true; // corpse or something + + // check angles to see if the thing can be aimed at + dist = FixedMul (::g->attackrange, in->frac); + thingtopslope = FixedDiv (th->z+th->height - ::g->shootz , dist); + + if (thingtopslope < ::g->bottomslope) + return true; // shot over the thing + + thingbottomslope = FixedDiv (th->z - ::g->shootz, dist); + + if (thingbottomslope > ::g->topslope) + return true; // shot under the thing + + // this thing can be hit! + if (thingtopslope > ::g->topslope) + thingtopslope = ::g->topslope; + + if (thingbottomslope < ::g->bottomslope) + thingbottomslope = ::g->bottomslope; + + ::g->aimslope = (thingtopslope+thingbottomslope)/2; + ::g->linetarget = th; + + return false; // don't go any farther +} + + +// +// PTR_ShootTraverse +// +qboolean PTR_ShootTraverse (intercept_t* in) +{ + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t frac; + + line_t* li; + + mobj_t* th; + + fixed_t slope; + fixed_t dist; + fixed_t thingtopslope; + fixed_t thingbottomslope; + + if (in->isaline) + { + li = in->d.line; + + if (li->special) + P_ShootSpecialLine (::g->shootthing, li); + + if ( !(li->flags & ML_TWOSIDED) ) + goto hitline; + + // crosses a two sided line + P_LineOpening (li); + + dist = FixedMul (::g->attackrange, in->frac); + + if (li->frontsector->floorheight != li->backsector->floorheight) + { + slope = FixedDiv (::g->openbottom - ::g->shootz , dist); + if (slope > ::g->aimslope) + goto hitline; + } + + if (li->frontsector->ceilingheight != li->backsector->ceilingheight) + { + slope = FixedDiv (::g->opentop - ::g->shootz , dist); + if (slope < ::g->aimslope) + goto hitline; + } + + // shot continues + return true; + + + // hit line + hitline: + // position a bit closer + frac = in->frac - FixedDiv (4*FRACUNIT,::g->attackrange); + x = ::g->trace.x + FixedMul (::g->trace.dx, frac); + y = ::g->trace.y + FixedMul (::g->trace.dy, frac); + z = ::g->shootz + FixedMul (::g->aimslope, FixedMul(frac, ::g->attackrange)); + + if (li->frontsector->ceilingpic == ::g->skyflatnum) + { + // don't shoot the sky! + if (z > li->frontsector->ceilingheight) + return false; + + // it's a sky hack wall + if (li->backsector && li->backsector->ceilingpic == ::g->skyflatnum) + return false; + } + + mobj_t * sourceObject = ::g->shootthing; + if( sourceObject ) { + + if( ( sourceObject->player) == &(::g->players[DoomLib::GetPlayer()]) ) { + + // Fist Punch. + if( ::g->attackrange == MELEERANGE ) { + } + } + } + + // Spawn bullet puffs. + P_SpawnPuff (x,y,z); + + // don't go any farther + return false; + } + + // shoot a thing + th = in->d.thing; + if (th == ::g->shootthing) + return true; // can't shoot self + + if (!(th->flags&MF_SHOOTABLE)) + return true; // corpse or something + + // check angles to see if the thing can be aimed at + dist = FixedMul (::g->attackrange, in->frac); + thingtopslope = FixedDiv (th->z+th->height - ::g->shootz , dist); + + if (thingtopslope < ::g->aimslope) + return true; // shot over the thing + + thingbottomslope = FixedDiv (th->z - ::g->shootz, dist); + + if (thingbottomslope > ::g->aimslope) + return true; // shot under the thing + + + // hit thing + // position a bit closer + frac = in->frac - FixedDiv (10*FRACUNIT,::g->attackrange); + + x = ::g->trace.x + FixedMul (::g->trace.dx, frac); + y = ::g->trace.y + FixedMul (::g->trace.dy, frac); + z = ::g->shootz + FixedMul (::g->aimslope, FixedMul(frac, ::g->attackrange)); + + // check for friendly fire. +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + if( th && gameLocal->GetMatchParms().GetGameType() != GAME_TYPE_PVP ) { + player_t * hitPlayer = th->player; + + if( hitPlayer ) { + + mobj_t * sourceObject = ::g->shootthing; + + if( sourceObject ) { + player_t* sourcePlayer = sourceObject->player; + + if( sourcePlayer != NULL && sourcePlayer != hitPlayer && !gameLocal->GetMatchParms().AllowFriendlyFire() ) { + return true; + } + } + } + } +#endif + + mobj_t * sourceObject = ::g->shootthing; + if( sourceObject ) { + + if( ( sourceObject->player) == &(::g->players[DoomLib::GetPlayer()]) ) { + + // Fist Punch. + if( ::g->attackrange == MELEERANGE ) { + } + } + } + + + // Spawn bullet puffs or blod spots, + // depending on target type. + if (in->d.thing->flags & MF_NOBLOOD) + P_SpawnPuff (x,y,z); + else + P_SpawnBlood (x,y,z, ::g->la_damage); + + if (::g->la_damage) + P_DamageMobj (th, ::g->shootthing, ::g->shootthing, ::g->la_damage); + + // don't go any farther + return false; + +} + + +// +// P_AimLineAttack +// +fixed_t +P_AimLineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance ) +{ + fixed_t x2; + fixed_t y2; + + angle >>= ANGLETOFINESHIFT; + ::g->shootthing = t1; + + x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; + y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; + ::g->shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; + + // can't shoot outside view angles + ::g->topslope = 100*FRACUNIT/160; + ::g->bottomslope = -100*FRACUNIT/160; + + ::g->attackrange = distance; + ::g->linetarget = NULL; + + P_PathTraverse ( t1->x, t1->y, + x2, y2, + PT_ADDLINES|PT_ADDTHINGS, + PTR_AimTraverse ); + + if (::g->linetarget) + return ::g->aimslope; + + return 0; +} + + +// +// P_LineAttack +// If damage == 0, it is just a test ::g->trace +// that will leave ::g->linetarget set. +// +void +P_LineAttack +( mobj_t* t1, + angle_t angle, + fixed_t distance, + fixed_t slope, + int damage ) +{ + fixed_t x2; + fixed_t y2; + + angle >>= ANGLETOFINESHIFT; + ::g->shootthing = t1; + ::g->la_damage = damage; + x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; + y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; + ::g->shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; + ::g->attackrange = distance; + ::g->aimslope = slope; + + P_PathTraverse ( t1->x, t1->y, + x2, y2, + PT_ADDLINES|PT_ADDTHINGS, + PTR_ShootTraverse ); +} + + + +// +// USE LINES +// + +qboolean PTR_UseTraverse (intercept_t* in) +{ + int side; + + if (!in->d.line->special) + { + P_LineOpening (in->d.line); + if (::g->openrange <= 0) + { + S_StartSound (::g->usething, sfx_noway); + + // can't use through a wall + return false; + } + // not a special line, but keep checking + return true ; + } + + side = 0; + if (P_PointOnLineSide (::g->usething->x, ::g->usething->y, in->d.line) == 1) + side = 1; + + // return false; // don't use back side + + P_UseSpecialLine (::g->usething, in->d.line, side); + + // can't use for than one special line in a row + return false; +} + + +// +// P_UseLines +// Looks for special ::g->lines in front of the player to activate. +// +void P_UseLines (player_t* player) +{ + int angle; + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + ::g->usething = player->mo; + + angle = player->mo->angle >> ANGLETOFINESHIFT; + + x1 = player->mo->x; + y1 = player->mo->y; + x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle]; + y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle]; + + P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ); +} + + +// +// RADIUS ATTACK +// + + +// +// PIT_RadiusAttack +// "bombsource" is the creature +// that caused the explosion at "bombspot". +// +qboolean PIT_RadiusAttack (mobj_t* thing) +{ + fixed_t dx; + fixed_t dy; + fixed_t dist; + + if (!(thing->flags & MF_SHOOTABLE) ) + return true; + + // Boss spider and cyborg + // take no damage from concussion. + if (thing->type == MT_CYBORG + || thing->type == MT_SPIDER) + return true; + + dx = abs(thing->x - ::g->bombspot->x); + dy = abs(thing->y - ::g->bombspot->y); + + dist = dx>dy ? dx : dy; + dist = (dist - thing->radius) >> FRACBITS; + + if (dist < 0) + dist = 0; + + if (dist >= ::g->bombdamage) + return true; // out of range + + if ( P_CheckSight (thing, ::g->bombspot) ) + { + // must be in direct path + P_DamageMobj (thing, ::g->bombspot, ::g->bombsource, ::g->bombdamage - dist); + } + + return true; +} + + +// +// P_RadiusAttack +// Source is the creature that caused the explosion at spot. +// +void +P_RadiusAttack +( mobj_t* spot, + mobj_t* source, + int damage ) +{ + int x; + int y; + + int xl; + int xh; + int yl; + int yh; + + fixed_t dist; + + dist = (damage+MAXRADIUS)<y + dist - ::g->bmaporgy)>>MAPBLOCKSHIFT; + yl = (spot->y - dist - ::g->bmaporgy)>>MAPBLOCKSHIFT; + xh = (spot->x + dist - ::g->bmaporgx)>>MAPBLOCKSHIFT; + xl = (spot->x - dist - ::g->bmaporgx)>>MAPBLOCKSHIFT; + ::g->bombspot = spot; + ::g->bombsource = source; + ::g->bombdamage = damage; + + for (y=yl ; y<=yh ; y++) + for (x=xl ; x<=xh ; x++) + P_BlockThingsIterator (x, y, PIT_RadiusAttack ); +} + + + +// +// SECTOR HEIGHT CHANGING +// After modifying a ::g->sectors floor or ceiling height, +// call this routine to adjust the positions +// of all things that touch the sector. +// +// If anything doesn't fit anymore, true will be returned. +// If crunch is true, they will take damage +// as they are being crushed. +// If Crunch is false, you should set the sector height back +// the way it was and call P_ChangeSector again +// to undo the changes. +// + + +// +// PIT_ChangeSector +// +qboolean PIT_ChangeSector (mobj_t* thing) +{ + mobj_t* mo; + + if (P_ThingHeightClip (thing)) + { + // keep checking + return true; + } + + + // crunch bodies to giblets + if (thing->health <= 0) + { + P_SetMobjState (thing, S_GIBS); + + thing->flags &= ~MF_SOLID; + thing->height = 0; + thing->radius = 0; + + // keep checking + return true; + } + + // crunch dropped items + if (thing->flags & MF_DROPPED) + { + P_RemoveMobj (thing); + + // keep checking + return true; + } + + if (! (thing->flags & MF_SHOOTABLE) ) + { + // assume it is bloody gibs or something + return true; + } + + ::g->nofit = true; + + if (::g->crushchange && !(::g->leveltime&3) ) + { + P_DamageMobj(thing,NULL,NULL,10); + + // spray blood in a random direction + mo = P_SpawnMobj (thing->x, + thing->y, + thing->z + thing->height/2, MT_BLOOD); + + mo->momx = (P_Random() - P_Random ())<<12; + mo->momy = (P_Random() - P_Random ())<<12; + } + + // keep checking (crush other things) + return true; +} + + + +// +// P_ChangeSector +// +qboolean +P_ChangeSector +( sector_t* sector, + qboolean crunch ) +{ + int x; + int y; + + ::g->nofit = false; + ::g->crushchange = crunch; + + // re-check heights for all things near the moving sector + for (x=sector->blockbox[BOXLEFT] ; x<= sector->blockbox[BOXRIGHT] ; x++) + for (y=sector->blockbox[BOXBOTTOM];y<= sector->blockbox[BOXTOP] ; y++) + P_BlockThingsIterator (x, y, PIT_ChangeSector); + + + return ::g->nofit; +} + + diff --git a/doomclassic/doom/p_maputl.cpp b/doomclassic/doom/p_maputl.cpp new file mode 100644 index 00000000..c8174121 --- /dev/null +++ b/doomclassic/doom/p_maputl.cpp @@ -0,0 +1,877 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include + + +#include "m_bbox.h" + +#include "doomdef.h" +#include "p_local.h" + + +// State. +#include "r_state.h" + +// +// P_AproxDistance +// Gives an estimation of distance (not exact) +// + +fixed_t +P_AproxDistance +( fixed_t dx, + fixed_t dy ) +{ + dx = abs(dx); + dy = abs(dy); + if (dx < dy) + return dx+dy-(dx>>1); + return dx+dy-(dy>>1); +} + + +// +// P_PointOnLineSide +// Returns 0 or 1 +// +int +P_PointOnLineSide +( fixed_t x, + fixed_t y, + line_t* line ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) + { + if (x <= line->v1->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) + { + if (y <= line->v1->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->v1->x); + dy = (y - line->v1->y); + + left = FixedMul ( line->dy>>FRACBITS , dx ); + right = FixedMul ( dy , line->dx>>FRACBITS ); + + if (right < left) + return 0; // front side + return 1; // back side +} + + + +// +// P_BoxOnLineSide +// Considers the line to be infinite +// Returns side 0 or 1, -1 if box crosses the line. +// +int +P_BoxOnLineSide +( fixed_t* tmbox, + line_t* ld ) +{ + int p1 = 0; + int p2 = 0; + + switch (ld->slopetype) + { + case ST_HORIZONTAL: + p1 = tmbox[BOXTOP] > ld->v1->y; + p2 = tmbox[BOXBOTTOM] > ld->v1->y; + if (ld->dx < 0) + { + p1 ^= 1; + p2 ^= 1; + } + break; + + case ST_VERTICAL: + p1 = tmbox[BOXRIGHT] < ld->v1->x; + p2 = tmbox[BOXLEFT] < ld->v1->x; + if (ld->dy < 0) + { + p1 ^= 1; + p2 ^= 1; + } + break; + + case ST_POSITIVE: + p1 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXTOP], ld); + p2 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld); + break; + + case ST_NEGATIVE: + p1 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXTOP], ld); + p2 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld); + break; + } + + if (p1 == p2) + return p1; + return -1; +} + + +// +// P_PointOnDivlineSide +// Returns 0 or 1. +// +int +P_PointOnDivlineSide +( fixed_t x, + fixed_t y, + divline_t* line ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) + { + if (x <= line->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) + { + if (y <= line->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->x); + dy = (y - line->y); + + // try to quickly decide by looking at sign bits + if ( (line->dy ^ line->dx ^ dx ^ dy)&0x80000000 ) + { + if ( (line->dy ^ dx) & 0x80000000 ) + return 1; // (left is negative) + return 0; + } + + left = FixedMul ( line->dy>>8, dx>>8 ); + right = FixedMul ( dy>>8 , line->dx>>8 ); + + if (right < left) + return 0; // front side + return 1; // back side +} + + + +// +// P_MakeDivline +// +void +P_MakeDivline +( line_t* li, + divline_t* dl ) +{ + dl->x = li->v1->x; + dl->y = li->v1->y; + dl->dx = li->dx; + dl->dy = li->dy; +} + + + +// +// P_InterceptVector +// Returns the fractional intercept point +// along the first divline. +// This is only called by the addthings +// and addlines traversers. +// +fixed_t +P_InterceptVector +( divline_t* v2, + divline_t* v1 ) +{ +#if 1 + fixed_t frac; + fixed_t num; + fixed_t den; + + den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy); + + if (den == 0) + return 0; + // I_Error ("P_InterceptVector: parallel"); + + num = + FixedMul ( (v1->x - v2->x)>>8 ,v1->dy ) + +FixedMul ( (v2->y - v1->y)>>8, v1->dx ); + + frac = FixedDiv (num , den); + + return frac; +#else // UNUSED, float debug. + float frac; + float num; + float den; + float v1x; + float v1y; + float v1dx; + float v1dy; + float v2x; + float v2y; + float v2dx; + float v2dy; + + v1x = (float)v1->x/FRACUNIT; + v1y = (float)v1->y/FRACUNIT; + v1dx = (float)v1->dx/FRACUNIT; + v1dy = (float)v1->dy/FRACUNIT; + v2x = (float)v2->x/FRACUNIT; + v2y = (float)v2->y/FRACUNIT; + v2dx = (float)v2->dx/FRACUNIT; + v2dy = (float)v2->dy/FRACUNIT; + + den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0) + return 0; // parallel + + num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + frac = num / den; + + return frac*FRACUNIT; +#endif +} + + +// +// P_LineOpening +// Sets ::g->opentop and ::g->openbottom to the window +// through a two sided line. +// OPTIMIZE: keep this precalculated +// + + +void P_LineOpening (line_t* maputil_linedef) +{ + sector_t* front; + sector_t* back; + + if (maputil_linedef->sidenum[1] == -1) + { + // single sided line + ::g->openrange = 0; + return; + } + + front = maputil_linedef->frontsector; + back = maputil_linedef->backsector; + + if (front->ceilingheight < back->ceilingheight) + ::g->opentop = front->ceilingheight; + else + ::g->opentop = back->ceilingheight; + + if (front->floorheight > back->floorheight) + { + ::g->openbottom = front->floorheight; + ::g->lowfloor = back->floorheight; + } + else + { + ::g->openbottom = back->floorheight; + ::g->lowfloor = front->floorheight; + } + + ::g->openrange = ::g->opentop - ::g->openbottom; +} + + +// +// THING POSITION SETTING +// + + +// +// P_UnsetThingPosition +// Unlinks a thing from block map and ::g->sectors. +// On each position change, BLOCKMAP and other +// lookups maintaining lists ot things inside +// these structures need to be updated. +// +void P_UnsetThingPosition (mobj_t* thing) +{ + int blockx; + int blocky; + + if ( ! (thing->flags & MF_NOSECTOR) ) + { + // inert things don't need to be in blockmap? + // unlink from subsector + if (thing->snext) + thing->snext->sprev = thing->sprev; + + if (thing->sprev) + thing->sprev->snext = thing->snext; + else + thing->subsector->sector->thinglist = thing->snext; + } + + if ( ! (thing->flags & MF_NOBLOCKMAP) ) + { + // inert things don't need to be in ::g->blockmap + // unlink from block map + if (thing->bnext) + thing->bnext->bprev = thing->bprev; + + if (thing->bprev) + thing->bprev->bnext = thing->bnext; + else + { + blockx = (thing->x - ::g->bmaporgx)>>MAPBLOCKSHIFT; + blocky = (thing->y - ::g->bmaporgy)>>MAPBLOCKSHIFT; + + if (blockx>=0 && blockx < ::g->bmapwidth + && blocky>=0 && blocky < ::g->bmapheight) + { + ::g->blocklinks[blocky*::g->bmapwidth+blockx] = thing->bnext; + } + } + } +} + + +// +// P_SetThingPosition +// Links a thing into both a block and a subsector +// based on it's x y. +// Sets thing->subsector properly +// +void +P_SetThingPosition (mobj_t* thing) +{ + subsector_t* ss; + sector_t* sec; + int blockx; + int blocky; + mobj_t** link; + + + // link into subsector + ss = R_PointInSubsector (thing->x,thing->y); + thing->subsector = ss; + + if ( ! (thing->flags & MF_NOSECTOR) ) + { + // invisible things don't go into the sector links + sec = ss->sector; + + thing->sprev = NULL; + thing->snext = sec->thinglist; + + if (sec->thinglist) + sec->thinglist->sprev = thing; + + sec->thinglist = thing; + } + + + // link into ::g->blockmap + if ( ! (thing->flags & MF_NOBLOCKMAP) ) + { + // inert things don't need to be in ::g->blockmap + blockx = (thing->x - ::g->bmaporgx)>>MAPBLOCKSHIFT; + blocky = (thing->y - ::g->bmaporgy)>>MAPBLOCKSHIFT; + + if (blockx>=0 + && blockx < ::g->bmapwidth + && blocky>=0 + && blocky < ::g->bmapheight) + { + link = &::g->blocklinks[blocky*::g->bmapwidth+blockx]; + thing->bprev = NULL; + thing->bnext = *link; + if (*link) + (*link)->bprev = thing; + + *link = thing; + } + else + { + // thing is off the map + thing->bnext = thing->bprev = NULL; + } + } +} + + + +// +// BLOCK MAP ITERATORS +// For each line/thing in the given mapblock, +// call the passed PIT_* function. +// If the function returns false, +// exit with false without checking anything else. +// + + +// +// P_BlockLinesIterator +// The ::g->validcount flags are used to avoid checking ::g->lines +// that are marked in multiple mapblocks, +// so increment ::g->validcount before the first call +// to P_BlockLinesIterator, then make one or more calls +// to it. +// +qboolean +P_BlockLinesIterator +( int x, + int y, + qboolean(*func)(line_t*) ) +{ + int offset; + short* list; + line_t* ld; + + if (x<0 + || y<0 + || x>=::g->bmapwidth + || y>=::g->bmapheight) + { + return true; + } + + offset = y*::g->bmapwidth+x; + + offset = *(::g->blockmap+offset); + + for ( list = ::g->blockmaplump+offset ; *list != -1 ; list++) + { + ld = &::g->lines[*list]; + + if (ld->validcount == ::g->validcount) + continue; // line has already been checked + + ld->validcount = ::g->validcount; + + if ( !func(ld) ) + return false; + } + return true; // everything was checked +} + + +// +// P_BlockThingsIterator +// +qboolean +P_BlockThingsIterator +( int x, + int y, + qboolean(*func)(mobj_t*) ) +{ + mobj_t* mobj; + + if ( x<0 + || y<0 + || x>=::g->bmapwidth + || y>=::g->bmapheight) + { + return true; + } + + + for (mobj = ::g->blocklinks[y*::g->bmapwidth+x] ; + mobj ; + mobj = mobj->bnext) + { + if (!func( mobj ) ) + return false; + } + return true; +} + + + +// +// INTERCEPT ROUTINES +// + + +// +// PIT_AddLineIntercepts. +// Looks for ::g->lines in the given block +// that intercept the given ::g->trace +// to add to the ::g->intercepts list. +// +// A line is crossed if its endpoints +// are on opposite ::g->sides of the ::g->trace. +// Returns true if ::g->earlyout and a solid line hit. +// +qboolean +PIT_AddLineIntercepts (line_t* ld) +{ + int s1; + int s2; + fixed_t frac; + divline_t dl; + + // avoid precision problems with two routines + if ( ::g->trace.dx > FRACUNIT*16 + || ::g->trace.dy > FRACUNIT*16 + || ::g->trace.dx < -FRACUNIT*16 + || ::g->trace.dy < -FRACUNIT*16) + { + s1 = P_PointOnDivlineSide (ld->v1->x, ld->v1->y, &::g->trace); + s2 = P_PointOnDivlineSide (ld->v2->x, ld->v2->y, &::g->trace); + } + else + { + s1 = P_PointOnLineSide (::g->trace.x, ::g->trace.y, ld); + s2 = P_PointOnLineSide (::g->trace.x+::g->trace.dx, ::g->trace.y+::g->trace.dy, ld); + } + + if (s1 == s2) + return true; // line isn't crossed + + // hit the line + P_MakeDivline (ld, &dl); + frac = P_InterceptVector (&::g->trace, &dl); + + if (frac < 0) + return true; // behind source + + // try to early out the check + if (::g->earlyout + && frac < FRACUNIT + && !ld->backsector) + { + return false; // stop checking + } + + + ::g->intercept_p->frac = frac; + ::g->intercept_p->isaline = true; + ::g->intercept_p->d.line = ld; + ::g->intercept_p++; + + return true; // continue +} + + + +// +// PIT_AddThingIntercepts +// +qboolean PIT_AddThingIntercepts (mobj_t* thing) +{ + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + int s1; + int s2; + + qboolean tracepositive; + + divline_t dl; + + fixed_t frac; + + tracepositive = (::g->trace.dx ^ ::g->trace.dy)>0; + + // check a corner to corner crossection for hit + if (tracepositive) + { + x1 = thing->x - thing->radius; + y1 = thing->y + thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y - thing->radius; + } + else + { + x1 = thing->x - thing->radius; + y1 = thing->y - thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y + thing->radius; + } + + s1 = P_PointOnDivlineSide (x1, y1, &::g->trace); + s2 = P_PointOnDivlineSide (x2, y2, &::g->trace); + + if (s1 == s2) + return true; // line isn't crossed + + dl.x = x1; + dl.y = y1; + dl.dx = x2-x1; + dl.dy = y2-y1; + + frac = P_InterceptVector (&::g->trace, &dl); + + if (frac < 0) + return true; // behind source + + ::g->intercept_p->frac = frac; + ::g->intercept_p->isaline = false; + ::g->intercept_p->d.thing = thing; + ::g->intercept_p++; + + return true; // keep going +} + + +// +// P_TraverseIntercepts +// Returns true if the traverser function returns true +// for all ::g->lines. +// +qboolean +P_TraverseIntercepts +( traverser_t func, + fixed_t maxfrac ) +{ + int count; + fixed_t dist; + intercept_t* scan; + intercept_t* in; + + count = ::g->intercept_p - ::g->intercepts; + + in = 0; // shut up compiler warning + + while (count--) + { + dist = MAXINT; + for (scan = ::g->intercepts ; scan < ::g->intercept_p ; scan++) + { + if (scan->frac < dist) + { + dist = scan->frac; + in = scan; + } + } + + if (dist > maxfrac) + return true; // checked everything in range + +#if 0 // UNUSED + { + // don't check these yet, there may be others inserted + in = scan = ::g->intercepts; + for ( scan = ::g->intercepts ; scan<::g->intercept_p ; scan++) + if (scan->frac > maxfrac) + *in++ = *scan; + ::g->intercept_p = in; + return false; + } +#endif + + if ( !func (in) ) + return false; // don't bother going farther + + in->frac = MAXINT; + } + + return true; // everything was traversed +} + + + + +// +// P_PathTraverse +// Traces a line from x1,y1 to x2,y2, +// calling the traverser function for each. +// Returns true if the traverser function returns true +// for all ::g->lines. +// +qboolean +P_PathTraverse +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2, + int flags, + qboolean (*trav) (intercept_t *)) +{ + fixed_t xt1; + fixed_t yt1; + fixed_t xt2; + fixed_t yt2; + + fixed_t xstep; + fixed_t ystep; + + fixed_t partial; + + fixed_t xintercept; + fixed_t yintercept; + + int mapx; + int mapy; + + int mapxstep; + int mapystep; + + int count; + + ::g->earlyout = flags & PT_EARLYOUT; + + ::g->validcount++; + ::g->intercept_p = ::g->intercepts; + + if ( ((x1-::g->bmaporgx)&(MAPBLOCKSIZE-1)) == 0) + x1 += FRACUNIT; // don't side exactly on a line + + if ( ((y1-::g->bmaporgy)&(MAPBLOCKSIZE-1)) == 0) + y1 += FRACUNIT; // don't side exactly on a line + + ::g->trace.x = x1; + ::g->trace.y = y1; + ::g->trace.dx = x2 - x1; + ::g->trace.dy = y2 - y1; + + x1 -= ::g->bmaporgx; + y1 -= ::g->bmaporgy; + xt1 = x1>>MAPBLOCKSHIFT; + yt1 = y1>>MAPBLOCKSHIFT; + + x2 -= ::g->bmaporgx; + y2 -= ::g->bmaporgy; + xt2 = x2>>MAPBLOCKSHIFT; + yt2 = y2>>MAPBLOCKSHIFT; + + if (xt2 > xt1) + { + mapxstep = 1; + partial = FRACUNIT - ((x1>>MAPBTOFRAC)&(FRACUNIT-1)); + ystep = FixedDiv (y2-y1,abs(x2-x1)); + } + else if (xt2 < xt1) + { + mapxstep = -1; + partial = (x1>>MAPBTOFRAC)&(FRACUNIT-1); + ystep = FixedDiv (y2-y1,abs(x2-x1)); + } + else + { + mapxstep = 0; + partial = FRACUNIT; + ystep = 256*FRACUNIT; + } + + yintercept = (y1>>MAPBTOFRAC) + FixedMul (partial, ystep); + + + if (yt2 > yt1) + { + mapystep = 1; + partial = FRACUNIT - ((y1>>MAPBTOFRAC)&(FRACUNIT-1)); + xstep = FixedDiv (x2-x1,abs(y2-y1)); + } + else if (yt2 < yt1) + { + mapystep = -1; + partial = (y1>>MAPBTOFRAC)&(FRACUNIT-1); + xstep = FixedDiv (x2-x1,abs(y2-y1)); + } + else + { + mapystep = 0; + partial = FRACUNIT; + xstep = 256*FRACUNIT; + } + xintercept = (x1>>MAPBTOFRAC) + FixedMul (partial, xstep); + + // Step through map blocks. + // Count is present to prevent a round off error + // from skipping the break. + mapx = xt1; + mapy = yt1; + + for (count = 0 ; count < 64 ; count++) + { + if (flags & PT_ADDLINES) + { + if (!P_BlockLinesIterator (mapx, mapy,PIT_AddLineIntercepts)) + return false; // early out + } + + if (flags & PT_ADDTHINGS) + { + if (!P_BlockThingsIterator (mapx, mapy,PIT_AddThingIntercepts)) + return false; // early out + } + + if (mapx == xt2 + && mapy == yt2) + { + break; + } + + if ( (yintercept >> FRACBITS) == mapy) + { + yintercept += ystep; + mapx += mapxstep; + } + else if ( (xintercept >> FRACBITS) == mapx) + { + xintercept += xstep; + mapy += mapystep; + } + + } + // go through the sorted list + return P_TraverseIntercepts ( trav, FRACUNIT ); +} + + + + diff --git a/doomclassic/doom/p_mobj.cpp b/doomclassic/doom/p_mobj.cpp new file mode 100644 index 00000000..6224185c --- /dev/null +++ b/doomclassic/doom/p_mobj.cpp @@ -0,0 +1,1012 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "i_system.h" +#include "z_zone.h" +#include "m_random.h" + +#include "doomdef.h" +#include "p_local.h" +#include "sounds.h" + +#include "st_stuff.h" +#include "hu_stuff.h" + +#include "s_sound.h" + +#include "doomstat.h" + +extern bool globalNetworking; + +void G_PlayerReborn (int player); +void P_SpawnMapThing (mapthing_t* mthing); + + +// +// P_SetMobjState +// Returns true if the mobj is still present. +// + +qboolean +P_SetMobjState +( mobj_t* mobj, + statenum_t state ) +{ + const state_t* st; + + do + { + if (state == S_NULL) + { + mobj->state = (const state_t *) S_NULL; + P_RemoveMobj (mobj); + return false; + } + + st = &::g->states[state]; + mobj->state = st; + mobj->tics = st->tics; + mobj->sprite = st->sprite; + mobj->frame = st->frame; + + // Modified handling. + // Call action functions when the state is set + if (st->action) + st->action(mobj, NULL); + + state = st->nextstate; + } while (!mobj->tics); + + return true; +} + + +// +// P_ExplodeMissile +// +void P_ExplodeMissile (mobj_t* mo) +{ + mo->momx = mo->momy = mo->momz = 0; + + P_SetMobjState (mo, (statenum_t)mobjinfo[mo->type].deathstate); + + mo->tics -= P_Random()&3; + + if (mo->tics < 1) + mo->tics = 1; + + mo->flags &= ~MF_MISSILE; + + if (mo->info->deathsound) + S_StartSound (mo, mo->info->deathsound); +} + + +// +// P_XYMovement +// + +void P_XYMovement (mobj_t* mo) +{ + fixed_t ptryx; + fixed_t ptryy; + player_t* player; + fixed_t xmove; + fixed_t ymove; + + if (!mo->momx && !mo->momy) + { + if (mo->flags & MF_SKULLFLY) + { + // the skull slammed into something + mo->flags &= ~MF_SKULLFLY; + mo->momx = mo->momy = mo->momz = 0; + + P_SetMobjState (mo, (statenum_t)mo->info->spawnstate); + } + return; + } + + player = mo->player; + + if (mo->momx > MAXMOVE) + mo->momx = MAXMOVE; + else if (mo->momx < -MAXMOVE) + mo->momx = -MAXMOVE; + + if (mo->momy > MAXMOVE) + mo->momy = MAXMOVE; + else if (mo->momy < -MAXMOVE) + mo->momy = -MAXMOVE; + + xmove = mo->momx; + ymove = mo->momy; + + do + { + if (xmove > MAXMOVE/2 || ymove > MAXMOVE/2) + { + ptryx = mo->x + xmove/2; + ptryy = mo->y + ymove/2; + xmove >>= 1; + ymove >>= 1; + } + else + { + ptryx = mo->x + xmove; + ptryy = mo->y + ymove; + xmove = ymove = 0; + } + + if (!P_TryMove (mo, ptryx, ptryy)) + { + // blocked move + if (mo->player) + { // try to slide along it + P_SlideMove (mo); + } + else if (mo->flags & MF_MISSILE) + { + // explode a missile + if (::g->ceilingline && + ::g->ceilingline->backsector && + ::g->ceilingline->backsector->ceilingpic == ::g->skyflatnum) + { + // Hack to prevent missiles exploding + // against the sky. + // Does not handle sky floors. + P_RemoveMobj (mo); + return; + } + P_ExplodeMissile (mo); + } + else + mo->momx = mo->momy = 0; + } + } while (xmove || ymove); + + // slow down + if (player && player->cheats & CF_NOMOMENTUM) + { + // debug option for no sliding at all + mo->momx = mo->momy = 0; + return; + } + + if (mo->flags & (MF_MISSILE | MF_SKULLFLY) ) + return; // no friction for missiles ever + + if (mo->z > mo->floorz) + return; // no friction when airborne + + if (mo->flags & MF_CORPSE) + { + // do not stop sliding + // if halfway off a step with some momentum + if (mo->momx > FRACUNIT/4 + || mo->momx < -FRACUNIT/4 + || mo->momy > FRACUNIT/4 + || mo->momy < -FRACUNIT/4) + { + if (mo->floorz != mo->subsector->sector->floorheight) + return; + } + } + + if (mo->momx > -STOPSPEED + && mo->momx < STOPSPEED + && mo->momy > -STOPSPEED + && mo->momy < STOPSPEED + && (!player + || (player->cmd.forwardmove== 0 + && player->cmd.sidemove == 0 ) ) ) + { + // if in a walking frame, stop moving + if ( player&&(unsigned)((player->mo->state - ::g->states)- S_PLAY_RUN1) < 4) + P_SetMobjState (player->mo, S_PLAY); + + mo->momx = 0; + mo->momy = 0; + } + else + { + mo->momx = FixedMul (mo->momx, FRICTION); + mo->momy = FixedMul (mo->momy, FRICTION); + } +} + +// +// P_ZMovement +// +void P_ZMovement (mobj_t* mo) +{ + fixed_t dist; + fixed_t delta; + + // check for smooth step up + if (mo->player && mo->z < mo->floorz) + { + mo->player->viewheight -= mo->floorz-mo->z; + + mo->player->deltaviewheight + = (VIEWHEIGHT - mo->player->viewheight)>>3; + } + + // adjust height + mo->z += mo->momz; + + if ( mo->flags & MF_FLOAT + && mo->target) + { + // float down towards target if too close + if ( !(mo->flags & MF_SKULLFLY) + && !(mo->flags & MF_INFLOAT) ) + { + dist = P_AproxDistance (mo->x - mo->target->x, + mo->y - mo->target->y); + + delta =(mo->target->z + (mo->height>>1)) - mo->z; + + if (delta<0 && dist < -(delta*3) ) + mo->z -= FLOATSPEED; + else if (delta>0 && dist < (delta*3) ) + mo->z += FLOATSPEED; + } + + } + + // clip movement + if (mo->z <= mo->floorz) + { + // hit the floor + + // Note (id): + // somebody left this after the setting momz to 0, + // kinda useless there. + if (mo->flags & MF_SKULLFLY) + { + // the skull slammed into something + mo->momz = -mo->momz; + } + + if (mo->momz < 0) + { + if (mo->player + && mo->momz < -GRAVITY*8) + { + // Squat down. + // Decrease ::g->viewheight for a moment + // after hitting the ground (hard), + // and utter appropriate sound. + mo->player->deltaviewheight = mo->momz>>3; + if (globalNetworking || (mo->player == &::g->players[::g->consoleplayer])) + S_StartSound (mo, sfx_oof); + } + mo->momz = 0; + } + mo->z = mo->floorz; + + if ( (mo->flags & MF_MISSILE) + && !(mo->flags & MF_NOCLIP) ) + { + P_ExplodeMissile (mo); + return; + } + } + else if (! (mo->flags & MF_NOGRAVITY) ) + { + if (mo->momz == 0) + mo->momz = -GRAVITY*2; + else + mo->momz -= GRAVITY; + } + + if (mo->z + mo->height > mo->ceilingz) + { + // hit the ceiling + if (mo->momz > 0) + mo->momz = 0; + { + mo->z = mo->ceilingz - mo->height; + } + + if (mo->flags & MF_SKULLFLY) + { // the skull slammed into something + mo->momz = -mo->momz; + } + + if ( (mo->flags & MF_MISSILE) + && !(mo->flags & MF_NOCLIP) ) + { + P_ExplodeMissile (mo); + return; + } + } +} + + + +// +// P_NightmareRespawn +// +void +P_NightmareRespawn (mobj_t* mobj) +{ + fixed_t x; + fixed_t y; + fixed_t z; + subsector_t* ss; + mobj_t* mo; + mapthing_t* mthing; + + x = mobj->spawnpoint.x << FRACBITS; + y = mobj->spawnpoint.y << FRACBITS; + + // somthing is occupying it's position? + if (!P_CheckPosition (mobj, x, y) ) + return; // no respwan + + // spawn a teleport fog at old spot + // because of removal of the body? + mo = P_SpawnMobj (mobj->x, + mobj->y, + mobj->subsector->sector->floorheight , MT_TFOG); + // initiate teleport sound + S_StartSound (mo, sfx_telept); + + // spawn a teleport fog at the new spot + ss = R_PointInSubsector (x,y); + + mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_TFOG); + + S_StartSound (mo, sfx_telept); + + // spawn the new monster + mthing = &mobj->spawnpoint; + + // spawn it + if (mobj->info->flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + // inherit attributes from deceased one + mo = P_SpawnMobj (x,y,z, mobj->type); + mo->spawnpoint = mobj->spawnpoint; + mo->angle = ANG45 * (mthing->angle/45); + + if (mthing->options & MTF_AMBUSH) + mo->flags |= MF_AMBUSH; + + mo->reactiontime = 18; + + // remove the old monster, + P_RemoveMobj (mobj); +} + + +// +// P_MobjThinker +// +void P_MobjThinker (mobj_t* mobj) +{ + // momentum movement + if (mobj->momx + || mobj->momy + || (mobj->flags&MF_SKULLFLY) ) + { + P_XYMovement (mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function.acv == (actionf_v) (-1)) + return; // mobj was removed + } + if ( (mobj->z != mobj->floorz) + || mobj->momz ) + { + P_ZMovement (mobj); + + // FIXME: decent NOP/NULL/Nil function pointer please. + if (mobj->thinker.function.acv == (actionf_v) (-1)) + return; // mobj was removed + } + + + // cycle through states, + // calling action functions at transitions + if (mobj->tics != -1) + { + mobj->tics--; + + // you can cycle through multiple states in a tic + if (!mobj->tics) + if (!P_SetMobjState (mobj, mobj->state->nextstate) ) + return; // freed itself + } + else + { + // check for nightmare respawn + if (! (mobj->flags & MF_COUNTKILL) ) + return; + + if (!::g->respawnmonsters) + return; + + mobj->movecount++; + + if (mobj->movecount < 12*TICRATE) + return; + + if ( ::g->leveltime&31 ) + return; + + if (P_Random () > 4) + return; + + P_NightmareRespawn (mobj); + } + +} + + +// +// P_SpawnMobj +// +mobj_t* +P_SpawnMobj +( fixed_t x, + fixed_t y, + fixed_t z, + mobjtype_t type ) +{ + mobj_t* mobj; + const state_t* st; + const mobjinfo_t* info; + + mobj = (mobj_t*)DoomLib::Z_Malloc(sizeof(*mobj), PU_LEVEL, NULL); + memset (mobj, 0, sizeof (*mobj)); + info = &mobjinfo[type]; + + mobj->type = type; + mobj->info = info; + mobj->x = x; + mobj->y = y; + mobj->radius = info->radius; + mobj->height = info->height; + mobj->flags = info->flags; + mobj->health = info->spawnhealth; + + if (::g->gameskill != sk_nightmare) + mobj->reactiontime = info->reactiontime; + + mobj->lastlook = P_Random () % MAXPLAYERS; + // do not set the state with P_SetMobjState, + // because action routines can not be called yet + st = &::g->states[info->spawnstate]; + + mobj->state = st; + mobj->tics = st->tics; + mobj->sprite = st->sprite; + mobj->frame = st->frame; + + // set subsector and/or block links + P_SetThingPosition (mobj); + + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + + if (z == ONFLOORZ) + mobj->z = mobj->floorz; + else if (z == ONCEILINGZ) + mobj->z = mobj->ceilingz - mobj->info->height; + else + mobj->z = z; + + mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; + + P_AddThinker (&mobj->thinker); + + return mobj; +} + + +// +// P_RemoveMobj +// + + +void P_RemoveMobj (mobj_t* mobj) +{ + if ((mobj->flags & MF_SPECIAL) + && !(mobj->flags & MF_DROPPED) + && (mobj->type != MT_INV) + && (mobj->type != MT_INS)) + { + ::g->itemrespawnque[::g->iquehead] = mobj->spawnpoint; + ::g->itemrespawntime[::g->iquehead] = ::g->leveltime; + ::g->iquehead = (::g->iquehead+1)&(ITEMQUESIZE-1); + + // lose one off the end? + if (::g->iquehead == ::g->iquetail) + ::g->iquetail = (::g->iquetail+1)&(ITEMQUESIZE-1); + } + + // unlink from sector and block lists + P_UnsetThingPosition (mobj); + + // stop any playing sound + //S_StopSound (mobj); + + // free block + P_RemoveThinker ((thinker_t*)mobj); +} + + + + +// +// P_RespawnSpecials +// +void P_RespawnSpecials (void) +{ + fixed_t x; + fixed_t y; + fixed_t z; + + subsector_t* ss; + mobj_t* mo; + mapthing_t* mthing; + + int i; + + // only respawn items in ::g->deathmatch + if (::g->deathmatch != 2) + return; // + + // nothing left to respawn? + if (::g->iquehead == ::g->iquetail) + return; + + // wait at least 30 seconds + if (::g->leveltime - ::g->itemrespawntime[::g->iquetail] < 30*TICRATE) + return; + + mthing = &::g->itemrespawnque[::g->iquetail]; + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + // spawn a teleport fog at the new spot + ss = R_PointInSubsector (x,y); + mo = P_SpawnMobj (x, y, ss->sector->floorheight , MT_IFOG); + S_StartSound (mo, sfx_itmbk); + + // find which type to spawn + for (i=0 ; i< NUMMOBJTYPES ; i++) + { + if (mthing->type == mobjinfo[i].doomednum) + break; + } + + // spawn it + if (mobjinfo[i].flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + mo = (mobj_t*)P_SpawnMobj (x,y,z, (mobjtype_t)i); + mo->spawnpoint = *mthing; + mo->angle = ANG45 * (mthing->angle/45); + + // pull it from the que + ::g->iquetail = (::g->iquetail+1)&(ITEMQUESIZE-1); +} + + + + +// +// P_SpawnPlayer +// Called when a player is spawned on the level. +// Most of the player structure stays unchanged +// between levels. +// +void P_SpawnPlayer (mapthing_t* mthing) +{ + player_t* p; + fixed_t x; + fixed_t y; + fixed_t z; + + mobj_t* mobj; + + int i; + + // not playing? + if (!::g->playeringame[mthing->type-1]) + return; + + p = &::g->players[mthing->type-1]; + + if (p->playerstate == PST_REBORN) + G_PlayerReborn (mthing->type-1); + + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + z = ONFLOORZ; + mobj = P_SpawnMobj (x,y,z, MT_PLAYER); + + // set color translations for player ::g->sprites + if (mthing->type > 1) + mobj->flags |= (mthing->type-1)<angle = ANG45 * (mthing->angle/45); + mobj->player = p; + mobj->health = p->health; + + p->mo = mobj; + p->playerstate = PST_LIVE; + p->refire = 0; + p->message = NULL; + p->damagecount = 0; + p->bonuscount = 0; + p->extralight = 0; + p->fixedcolormap = 0; + p->viewheight = VIEWHEIGHT; + + // setup gun psprite + P_SetupPsprites (p); + + // give all cards in death match mode + if (::g->deathmatch) + for (i=0 ; icards[i] = true; + + if (mthing->type-1 == ::g->consoleplayer) + { + // wake up the status bar + ST_Start (); + // wake up the heads up text + HU_Start (); + } + + // Give him everything is Give All is on. + if( p->cheats & CF_GIVEALL ) { + p->armorpoints = 200; + p->armortype = 2; + + int i; + for (i=0;iweaponowned[i] = true; + + for (i=0;iammo[i] = p->maxammo[i]; + + for (i=0;icards[i] = true; + } + +} + + +// +// P_SpawnMapThing +// The fields of the mapthing should +// already be in host byte order. +// +void P_SpawnMapThing (mapthing_t* mthing) +{ + int i; + int bit; + mobj_t* mobj; + fixed_t x; + fixed_t y; + fixed_t z; + + // count ::g->deathmatch start positions + if (mthing->type == 11) + { + if (::g->deathmatch_p < &::g->deathmatchstarts[10]) + { + memcpy (::g->deathmatch_p, mthing, sizeof(*mthing)); + ::g->deathmatch_p++; + } + return; + } + + // check for ::g->players specially + if (mthing->type <= 4) + { + // save spots for respawning in network games + ::g->playerstarts[mthing->type-1] = *mthing; + if (!::g->deathmatch) + P_SpawnPlayer (mthing); + + return; + } + + // check for apropriate skill level + if (!::g->netgame && (mthing->options & 16) ) + return; + + if (::g->gameskill == sk_baby) + bit = 1; + else if (::g->gameskill == sk_nightmare) + bit = 4; + else + bit = 1<<(::g->gameskill-1); + + if (!(mthing->options & bit) ) + return; + + // find which type to spawn + for (i=0 ; i< NUMMOBJTYPES ; i++) + if (mthing->type == mobjinfo[i].doomednum) + break; + + if ( i==NUMMOBJTYPES ) { + //printf( "P_SpawnMapThing: Unknown type %i at (%i, %i)", mthing->type, mthing->x, mthing->y); + return; + //I_Error ("P_SpawnMapThing: Unknown type %i at (%i, %i)", + //mthing->type, + //mthing->x, mthing->y); + } + + // don't spawn keycards and ::g->players in ::g->deathmatch + if (::g->deathmatch && mobjinfo[i].flags & MF_NOTDMATCH) + return; + + // don't spawn any monsters if -::g->nomonsters + if (::g->nomonsters + && ( i == MT_SKULL + || (mobjinfo[i].flags & MF_COUNTKILL)) ) + { + return; + } + + // spawn it + x = mthing->x << FRACBITS; + y = mthing->y << FRACBITS; + + if (mobjinfo[i].flags & MF_SPAWNCEILING) + z = ONCEILINGZ; + else + z = ONFLOORZ; + + mobj = (mobj_t*)P_SpawnMobj (x,y,z, (mobjtype_t)i); + mobj->spawnpoint = *mthing; + + if (mobj->tics > 0) + mobj->tics = 1 + (P_Random () % mobj->tics); + if (mobj->flags & MF_COUNTKILL) + ::g->totalkills++; + if (mobj->flags & MF_COUNTITEM) + ::g->totalitems++; + + mobj->angle = ANG45 * (mthing->angle/45); + if (mthing->options & MTF_AMBUSH) + mobj->flags |= MF_AMBUSH; +} + + + +// +// GAME SPAWN FUNCTIONS +// + + +// +// P_SpawnPuff +// + +void +P_SpawnPuff +( fixed_t x, + fixed_t y, + fixed_t z ) +{ + mobj_t* th; + + z += ((P_Random()-P_Random())<<10); + + th = P_SpawnMobj (x,y,z, MT_PUFF); + th->momz = FRACUNIT; + th->tics -= P_Random()&3; + + if (th->tics < 1) + th->tics = 1; + + // don't make punches spark on the wall + if (::g->attackrange == MELEERANGE) { + + P_SetMobjState (th, S_PUFF3); + + } +} + + + +// +// P_SpawnBlood +// +void +P_SpawnBlood +( fixed_t x, + fixed_t y, + fixed_t z, + int damage ) +{ + mobj_t* th; + + z += ((P_Random()-P_Random())<<10); + th = P_SpawnMobj (x,y,z, MT_BLOOD); + th->momz = FRACUNIT*2; + th->tics -= P_Random()&3; + + if (th->tics < 1) + th->tics = 1; + + if (damage <= 12 && damage >= 9) + P_SetMobjState (th,S_BLOOD2); + else if (damage < 9) + P_SetMobjState (th,S_BLOOD3); +} + + + +// +// P_CheckMissileSpawn +// Moves the missile forward a bit +// and possibly explodes it right there. +// +void P_CheckMissileSpawn (mobj_t* th) +{ + th->tics -= P_Random()&3; + if (th->tics < 1) + th->tics = 1; + + // move a little forward so an angle can + // be computed if it immediately explodes + th->x += (th->momx>>1); + th->y += (th->momy>>1); + th->z += (th->momz>>1); + + if (!P_TryMove (th, th->x, th->y)) + P_ExplodeMissile (th); +} + + +// +// P_SpawnMissile +// +mobj_t* +P_SpawnMissile +( mobj_t* source, + mobj_t* dest, + mobjtype_t type ) +{ + mobj_t* th; + angle_t an; + int dist; + + th = P_SpawnMobj (source->x, + source->y, + source->z + 4*8*FRACUNIT, type); + + if (th->info->seesound) + S_StartSound (th, th->info->seesound); + + th->target = source; // where it came from + an = R_PointToAngle2 (source->x, source->y, dest->x, dest->y); + + // fuzzy player + if (dest->flags & MF_SHADOW) + an += (P_Random()-P_Random())<<20; + + th->angle = an; + an >>= ANGLETOFINESHIFT; + th->momx = FixedMul (th->info->speed, finecosine[an]); + th->momy = FixedMul (th->info->speed, finesine[an]); + + dist = P_AproxDistance (dest->x - source->x, dest->y - source->y); + dist = dist / th->info->speed; + + if (dist < 1) + dist = 1; + + th->momz = (dest->z - source->z) / dist; + P_CheckMissileSpawn (th); + + return th; +} + + +// +// P_SpawnPlayerMissile +// Tries to aim at a nearby monster +// +void +P_SpawnPlayerMissile +( mobj_t* source, + mobjtype_t type ) +{ + mobj_t* th; + angle_t an; + + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t slope; + + // see which target is to be aimed at + an = source->angle; + slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); + + if (!::g->linetarget) + { + an += 1<<26; + slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); + + if (!::g->linetarget) + { + an -= 2<<26; + slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); + } + + if (!::g->linetarget) + { + an = source->angle; + slope = 0; + } + } + + x = source->x; + y = source->y; + z = source->z + 4*8*FRACUNIT; + + th = P_SpawnMobj (x,y,z, type); + + if (th->info->seesound && (source->player == &::g->players[::g->consoleplayer]) ) { + S_StartSound (th, th->info->seesound); + } + + th->target = source; + th->angle = an; + th->momx = FixedMul( th->info->speed, + finecosine[an>>ANGLETOFINESHIFT]); + th->momy = FixedMul( th->info->speed, + finesine[an>>ANGLETOFINESHIFT]); + th->momz = FixedMul( th->info->speed, slope); + + P_CheckMissileSpawn (th); +} + + diff --git a/doomclassic/doom/p_mobj.h b/doomclassic/doom/p_mobj.h new file mode 100644 index 00000000..e80e8634 --- /dev/null +++ b/doomclassic/doom/p_mobj.h @@ -0,0 +1,297 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_MOBJ__ +#define __P_MOBJ__ + +// Basics. +#include "tables.h" +#include "m_fixed.h" + +// We need the thinker_t stuff. +#include "d_think.h" + +// We need the WAD data structure for Map things, +// from the THINGS lump. +#include "doomdata.h" + +// States are tied to finite states are +// tied to animation frames. +// Needs precompiled tables/data structures. +#include "info.h" + + + +#ifdef __GNUG__ +#pragma interface +#endif + + + +// +// NOTES: mobj_t +// +// mobj_ts are used to tell the refresh where to draw an image, +// tell the world simulation when objects are contacted, +// and tell the sound driver how to position a sound. +// +// The refresh uses the next and prev links to follow +// lists of things in sectors as they are being drawn. +// The sprite, frame, and angle elements determine which patch_t +// is used to draw the sprite if it is visible. +// The sprite and frame values are allmost allways set +// from state_t structures. +// The statescr.exe utility generates the states.h and states.c +// files that contain the sprite/frame numbers from the +// statescr.txt source file. +// The xyz origin point represents a point at the bottom middle +// of the sprite (between the feet of a biped). +// This is the default origin position for patch_ts grabbed +// with lumpy.exe. +// A walking creature will have its z equal to the floor +// it is standing on. +// +// The sound code uses the x,y, and subsector fields +// to do stereo positioning of any sound effited by the mobj_t. +// +// The play simulation uses the blocklinks, x,y,z, radius, height +// to determine when mobj_ts are touching each other, +// touching lines in the map, or hit by trace lines (gunshots, +// lines of sight, etc). +// The mobj_t->flags element has various bit flags +// used by the simulation. +// +// Every mobj_t is linked into a single sector +// based on its origin coordinates. +// The subsector_t is found with R_PointInSubsector(x,y), +// and the sector_t can be found with subsector->sector. +// The sector links are only used by the rendering code, +// the play simulation does not care about them at all. +// +// Any mobj_t that needs to be acted upon by something else +// in the play world (block movement, be shot, etc) will also +// need to be linked into the blockmap. +// If the thing has the MF_NOBLOCK flag set, it will not use +// the block links. It can still interact with other things, +// but only as the instigator (missiles will run into other +// things, but nothing can run into a missile). +// Each block in the grid is 128*128 units, and knows about +// every line_t that it contains a piece of, and every +// interactable mobj_t that has its origin contained. +// +// A valid mobj_t is a mobj_t that has the proper subsector_t +// filled in for its xy coordinates and is linked into the +// sector from which the subsector was made, or has the +// MF_NOSECTOR flag set (the subsector_t needs to be valid +// even if MF_NOSECTOR is set), and is linked into a blockmap +// block or has the MF_NOBLOCKMAP flag set. +// Links should only be modified by the P_[Un]SetThingPosition() +// functions. +// Do not change the MF_NO? flags while a thing is valid. +// +// Any questions? +// + +// +// Misc. mobj flags +// +typedef enum +{ + // Call P_SpecialThing when touched. + MF_SPECIAL = 1, + // Blocks. + MF_SOLID = 2, + // Can be hit. + MF_SHOOTABLE = 4, + // Don't use the sector links (invisible but touchable). + MF_NOSECTOR = 8, + // Don't use the blocklinks (inert but displayable) + MF_NOBLOCKMAP = 16, + + // Not to be activated by sound, deaf monster. + MF_AMBUSH = 32, + // Will try to attack right back. + MF_JUSTHIT = 64, + // Will take at least one step before attacking. + MF_JUSTATTACKED = 128, + // On level spawning (initial position), + // hang from ceiling instead of stand on floor. + MF_SPAWNCEILING = 256, + // Don't apply gravity (every tic), + // that is, object will float, keeping current height + // or changing it actively. + MF_NOGRAVITY = 512, + + // Movement flags. + // This allows jumps from high places. + MF_DROPOFF = 0x400, + // For players, will pick up items. + MF_PICKUP = 0x800, + // Player cheat. ??? + MF_NOCLIP = 0x1000, + // Player: keep info about sliding along walls. + MF_SLIDE = 0x2000, + // Allow moves to any height, no gravity. + // For active floaters, e.g. cacodemons, pain elementals. + MF_FLOAT = 0x4000, + // Don't cross lines + // ??? or look at heights on teleport. + MF_TELEPORT = 0x8000, + // Don't hit same species, explode on block. + // Player missiles as well as fireballs of various kinds. + MF_MISSILE = 0x10000, + // Dropped by a demon, not level spawned. + // E.g. ammo clips dropped by dying former humans. + MF_DROPPED = 0x20000, + // Use fuzzy draw (shadow demons or spectres), + // temporary player invisibility powerup. + MF_SHADOW = 0x40000, + // Flag: don't bleed when shot (use puff), + // barrels and shootable furniture shall not bleed. + MF_NOBLOOD = 0x80000, + // Don't stop moving halfway off a step, + // that is, have dead bodies slide down all the way. + MF_CORPSE = 0x100000, + // Floating to a height for a move, ??? + // don't auto float to target's height. + MF_INFLOAT = 0x200000, + + // On kill, count this enemy object + // towards intermission kill total. + // Happy gathering. + MF_COUNTKILL = 0x400000, + + // On picking up, count this item object + // towards intermission item total. + MF_COUNTITEM = 0x800000, + + // Special handling: skull in flight. + // Neither a cacodemon nor a missile. + MF_SKULLFLY = 0x1000000, + + // Don't spawn this object + // in death match mode (e.g. key cards). + MF_NOTDMATCH = 0x2000000, + + // Player sprites in multiplayer modes are modified + // using an internal color lookup table for re-indexing. + // If 0x4 0x8 or 0xc, + // use a translation table for player colormaps + MF_TRANSLATION = 0xc000000, + // Hmm ???. + MF_TRANSSHIFT = 26 + +} mobjflag_t; + + +// Map Object definition. +struct mobj_t +{ + // List: thinker links. + thinker_t thinker; + + // Info for drawing: position. + fixed_t x; + fixed_t y; + fixed_t z; + + // More list: links in sector (if needed) + mobj_t* snext; + mobj_t* sprev; + + //More drawing info: to determine current sprite. + angle_t angle; // orientation + spritenum_t sprite; // used to find patch_t and flip value + int frame; // might be ORed with FF_FULLBRIGHT + + // Interaction info, by BLOCKMAP. + // Links in blocks (if needed). + mobj_t* bnext; + mobj_t* bprev; + + struct subsector_s* subsector; + + // The closest interval over all contacted Sectors. + fixed_t floorz; + fixed_t ceilingz; + + // For movement checking. + fixed_t radius; + fixed_t height; + + // Momentums, used to update position. + fixed_t momx; + fixed_t momy; + fixed_t momz; + + // If == validcount, already checked. + int validcount; + + mobjtype_t type; + const mobjinfo_t* info; // &mobjinfo[mobj->type] + + int tics; // state tic counter + const state_t* state; + int flags; + int health; + + // Movement direction, movement generation (zig-zagging). + int movedir; // 0-7 + int movecount; // when 0, select a new dir + + // Thing being chased/attacked (or NULL), + // also the originator for missiles. + mobj_t* target; + + // Reaction time: if non 0, don't attack yet. + // Used by player to freeze a bit after teleporting. + int reactiontime; + + // If >0, the target will be chased + // no matter what (even if shot) + int threshold; + + // Additional info record for player avatars only. + // Only valid if type == MT_PLAYER + struct player_s* player; + + // Player number last looked for. + int lastlook; + + // For nightmare respawn. + mapthing_t spawnpoint; + + // Thing being chased/attacked for tracers. + mobj_t* tracer; + +}; + + + +#endif + diff --git a/doomclassic/doom/p_plats.cpp b/doomclassic/doom/p_plats.cpp new file mode 100644 index 00000000..3e7e1253 --- /dev/null +++ b/doomclassic/doom/p_plats.cpp @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "i_system.h" +#include "z_zone.h" +#include "m_random.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +// Data. +#include "sounds.h" + + + + + +// +// Move a plat up and down +// +void T_PlatRaise(plat_t* plat) +{ + result_e res; + + switch(plat->status) + { + case up: + res = T_MovePlane(plat->sector, + plat->speed, + plat->high, + plat->crush,0,1); + + if (plat->type == raiseAndChange + || plat->type == raiseToNearestAndChange) + { + if (!(::g->leveltime&7)) + S_StartSound( &plat->sector->soundorg, + sfx_stnmov); + } + + + if (res == crushed && (!plat->crush)) + { + plat->count = plat->wait; + plat->status = down; + S_StartSound( &plat->sector->soundorg, + sfx_pstart); + } + else + { + if (res == pastdest) + { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound( &plat->sector->soundorg, + sfx_pstop); + + switch(plat->type) + { + case blazeDWUS: + case downWaitUpStay: + P_RemoveActivePlat(plat); + break; + + case raiseAndChange: + case raiseToNearestAndChange: + P_RemoveActivePlat(plat); + break; + + default: + break; + } + } + } + break; + + case down: + res = T_MovePlane(plat->sector,plat->speed,plat->low,false,0,-1); + + if (res == pastdest) + { + plat->count = plat->wait; + plat->status = waiting; + S_StartSound( &plat->sector->soundorg,sfx_pstop); + } + break; + + case waiting: + if (!--plat->count) + { + if (plat->sector->floorheight == plat->low) + plat->status = up; + else + plat->status = down; + S_StartSound( &plat->sector->soundorg,sfx_pstart); + } + case in_stasis: + break; + } +} + + +// +// Do Platforms +// "amount" is only used for SOME platforms. +// +int +EV_DoPlat +( line_t* line, + plattype_e type, + int amount ) +{ + plat_t* plat; + int secnum; + int rtn; + sector_t* sec; + + secnum = -1; + rtn = 0; + + + // Activate all plats that are in_stasis + switch(type) + { + case perpetualRaise: + P_ActivateInStasis(line->tag); + break; + + default: + break; + } + + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + sec = &::g->sectors[secnum]; + + if (sec->specialdata) + continue; + + // Find lowest & highest floors around sector + rtn = 1; + plat = (plat_t*)DoomLib::Z_Malloc( sizeof(*plat), PU_LEVEL, 0); + P_AddThinker(&plat->thinker); + + plat->type = type; + plat->sector = sec; + plat->sector->specialdata = plat; + plat->thinker.function.acp1 = (actionf_p1) T_PlatRaise; + plat->crush = false; + plat->tag = line->tag; + + switch(type) + { + case raiseToNearestAndChange: + plat->speed = PLATSPEED/2; + sec->floorpic = ::g->sides[line->sidenum[0]].sector->floorpic; + plat->high = P_FindNextHighestFloor(sec,sec->floorheight); + plat->wait = 0; + plat->status = up; + // NO MORE DAMAGE, IF APPLICABLE + sec->special = 0; + + S_StartSound( &sec->soundorg,sfx_stnmov); + break; + + case raiseAndChange: + plat->speed = PLATSPEED/2; + sec->floorpic = ::g->sides[line->sidenum[0]].sector->floorpic; + plat->high = sec->floorheight + amount*FRACUNIT; + plat->wait = 0; + plat->status = up; + + S_StartSound( &sec->soundorg,sfx_stnmov); + break; + + case downWaitUpStay: + plat->speed = PLATSPEED * 4; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = sec->floorheight; + plat->wait = TICRATE*PLATWAIT; + plat->status = down; + S_StartSound( &sec->soundorg,sfx_pstart); + break; + + case blazeDWUS: + plat->speed = PLATSPEED * 8; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = sec->floorheight; + plat->wait = TICRATE*PLATWAIT; + plat->status = down; + S_StartSound( &sec->soundorg,sfx_pstart); + break; + + case perpetualRaise: + plat->speed = PLATSPEED; + plat->low = P_FindLowestFloorSurrounding(sec); + + if (plat->low > sec->floorheight) + plat->low = sec->floorheight; + + plat->high = P_FindHighestFloorSurrounding(sec); + + if (plat->high < sec->floorheight) + plat->high = sec->floorheight; + + plat->wait = TICRATE*PLATWAIT; + plat->status = (plat_e)(P_Random()&1); + + S_StartSound( &sec->soundorg,sfx_pstart); + break; + } + P_AddActivePlat(plat); + } + return rtn; +} + + + +void P_ActivateInStasis(int tag) +{ + int i; + + for (i = 0;i < MAXPLATS;i++) + if (::g->activeplats[i] + && (::g->activeplats[i])->tag == tag + && (::g->activeplats[i])->status == in_stasis) + { + (::g->activeplats[i])->status = (::g->activeplats[i])->oldstatus; + (::g->activeplats[i])->thinker.function.acp1 + = (actionf_p1) T_PlatRaise; + } +} + +void EV_StopPlat(line_t* line) +{ + int j; + + for (j = 0;j < MAXPLATS;j++) + if (::g->activeplats[j] + && ((::g->activeplats[j])->status != in_stasis) + && ((::g->activeplats[j])->tag == line->tag)) + { + (::g->activeplats[j])->oldstatus = (::g->activeplats[j])->status; + (::g->activeplats[j])->status = in_stasis; + (::g->activeplats[j])->thinker.function.acv = (actionf_v)NULL; + } +} + +void P_AddActivePlat(plat_t* plat) +{ + int i; + + for (i = 0;i < MAXPLATS;i++) + if (::g->activeplats[i] == NULL) + { + ::g->activeplats[i] = plat; + return; + } + I_Error ("P_AddActivePlat: no more plats!"); +} + +void P_RemoveActivePlat(plat_t* plat) +{ + int i; + for (i = 0;i < MAXPLATS;i++) + if (plat == ::g->activeplats[i]) + { + (::g->activeplats[i])->sector->specialdata = NULL; + P_RemoveThinker(&(::g->activeplats[i])->thinker); + ::g->activeplats[i] = NULL; + + return; + } + I_Error ("P_RemoveActivePlat: can't find plat!"); +} + diff --git a/doomclassic/doom/p_pspr.cpp b/doomclassic/doom/p_pspr.cpp new file mode 100644 index 00000000..c9b7bdc1 --- /dev/null +++ b/doomclassic/doom/p_pspr.cpp @@ -0,0 +1,976 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "doomdef.h" +#include "d_event.h" + + +#include "m_random.h" +#include "p_local.h" +#include "s_sound.h" + +// State. +#include "doomstat.h" + +// Data. +#include "sounds.h" + +#include "p_pspr.h" +#include "d3xp/Game_local.h" + +extern bool globalNetworking; + + + +static const float PISTOL_MAGNITUDE_HIGH = 0.5f; +static const int PISTOL_DURATION_HIGH = 250; +static const float PISTOL_MAGNITUDE_LOW = 1.0f; +static const int PISTOL_DURATION_LOW = 150; + +static const float SHOTGUN_MAGNITUDE_HIGH = 0.5f; +static const int SHOTGUN_DURATION_HIGH = 250; +static const float SHOTGUN_MAGNITUDE_LOW = 1.0f; +static const int SHOTGUN_DURATION_LOW = 350; + +static const float CHAINGUN_MAGNITUDE_HIGH = 0.5f; +static const int CHAINGUN_DURATION_HIGH = 250; +static const float CHAINGUN_MAGNITUDE_LOW = 1.0f; +static const int CHAINGUN_DURATION_LOW = 150; + +static const float PLASMAGUN_MAGNITUDE_HIGH = 0.5f; +static const int PLASMAGUN_DURATION_HIGH = 250; +static const float PLASMAGUN_MAGNITUDE_LOW = 1.0f; +static const int PLASMAGUN_DURATION_LOW = 150; + +static const float SUPERSHOTGUN_MAGNITUDE_HIGH = 1.0f; +static const int SUPERSHOTGUN_DURATION_HIGH = 250; +static const float SUPERSHOTGUN_MAGNITUDE_LOW = 1.0f; +static const int SUPERSHOTGUN_DURATION_LOW = 350; + +static const float ROCKET_MAGNITUDE_HIGH = 1.5f; +static const int ROCKET_DURATION_HIGH = 250; +static const float ROCKET_MAGNITUDE_LOW = 1.0f; +static const int ROCKET_DURATION_LOW = 350; + +static const float BFG_MAGNITUDE_HIGH = 1.5f; +static const int BFG_DURATION_HIGH = 250; +static const float BFG_MAGNITUDE_LOW = 1.0f; +static const int BFG_DURATION_LOW = 400; + + +static const float SAW_IDL_MAGNITUDE_HIGH = 0.0f; +static const int SAW_IDL_DURATION_HIGH = 0; +static const float SAW_IDL_MAGNITUDE_LOW = 0.4f; +static const int SAW_IDL_DURATION_LOW = 150; + +static const float SAW_ATK_MAGNITUDE_HIGH = 1.0f; +static const int SAW_ATK_DURATION_HIGH = 250; +static const float SAW_ATK_MAGNITUDE_LOW = 0.0f; +static const int SAW_ATK_DURATION_LOW = 0; + +// plasma cells for a bfg attack + + +// +// P_SetPsprite +// +void +P_SetPsprite +( player_t* player, + int position, + statenum_t stnum ) +{ + pspdef_t* psp; + const state_t* state; + + psp = &player->psprites[position]; + + do + { + if (!stnum) + { + // object removed itself + psp->state = NULL; + break; + } + + state = &::g->states[stnum]; + psp->state = state; + psp->tics = state->tics; // could be 0 + + if (state->misc1) + { + // coordinate set + psp->sx = state->misc1 << FRACBITS; + psp->sy = state->misc2 << FRACBITS; + } + + // Call action routine. + // Modified handling. + if (state->action) + { + state->action(player, psp); + if (!psp->state) + break; + } + + stnum = psp->state->nextstate; + + } while (!psp->tics); + // an initial state of 0 could cycle through +} + + + +// +// P_CalcSwing +// + +void P_CalcSwing (player_t* player) +{ + fixed_t swing; + int angle; + + // OPTIMIZE: tablify this. + // A LUT would allow for different modes, + // and add flexibility. + + swing = player->bob; + + angle = (FINEANGLES/70*::g->leveltime)&FINEMASK; + ::g->swingx = FixedMul ( swing, finesine[angle]); + + angle = (FINEANGLES/70*::g->leveltime+FINEANGLES/2)&FINEMASK; + ::g->swingy = -FixedMul ( ::g->swingx, finesine[angle]); +} + + + +// +// P_BringUpWeapon +// Starts bringing the pending weapon up +// from the bottom of the screen. +// Uses player +// +void P_BringUpWeapon (player_t* player) +{ + statenum_t newstate; + + if (player->pendingweapon == wp_nochange) + player->pendingweapon = player->readyweapon; + + if (player->pendingweapon == wp_chainsaw && (globalNetworking || (player == &::g->players[::g->consoleplayer])) ) + S_StartSound (player->mo, sfx_sawup); + + newstate = (statenum_t)(weaponinfo[player->pendingweapon].upstate); + + player->pendingweapon = wp_nochange; + player->psprites[ps_weapon].sy = WEAPONBOTTOM; + + P_SetPsprite (player, ps_weapon, newstate); +} + +// +// P_CheckAmmo +// Returns true if there is enough ammo to shoot. +// If not, selects the next weapon to use. +// +qboolean P_CheckAmmo (player_t* player) +{ + ammotype_t ammo; + int count; + + ammo = weaponinfo[player->readyweapon].ammo; + + // Minimal amount for one shot varies. + if (player->readyweapon == wp_bfg) + count = BFGCELLS; + else if (player->readyweapon == wp_supershotgun) + count = 2; // Double barrel. + else + count = 1; // Regular. + + // Some do not need ammunition anyway. + // Return if current ammunition sufficient. + if (ammo == am_noammo || player->ammo[ammo] >= count) + return true; + + // Out of ammo, pick a weapon to change to. + // Preferences are set here. + do + { + if (player->weaponowned[wp_plasma] + && player->ammo[am_cell] + && (::g->gamemode != shareware) ) + { + player->pendingweapon = wp_plasma; + } + else if (player->weaponowned[wp_supershotgun] + && player->ammo[am_shell]>2 + && (::g->gamemode == commercial) ) + { + player->pendingweapon = wp_supershotgun; + } + else if (player->weaponowned[wp_chaingun] + && player->ammo[am_clip]) + { + player->pendingweapon = wp_chaingun; + } + else if (player->weaponowned[wp_shotgun] + && player->ammo[am_shell]) + { + player->pendingweapon = wp_shotgun; + } + else if (player->ammo[am_clip]) + { + player->pendingweapon = wp_pistol; + } + else if (player->weaponowned[wp_chainsaw]) + { + player->pendingweapon = wp_chainsaw; + } + else if (player->weaponowned[wp_missile] + && player->ammo[am_misl]) + { + player->pendingweapon = wp_missile; + } + else if (player->weaponowned[wp_bfg] + && player->ammo[am_cell]>40 + && (::g->gamemode != shareware) ) + { + player->pendingweapon = wp_bfg; + } + else + { + // If everything fails. + player->pendingweapon = wp_fist; + } + + } while (player->pendingweapon == wp_nochange); + + // Now set appropriate weapon overlay. + P_SetPsprite (player, + ps_weapon, + (statenum_t)(weaponinfo[player->readyweapon].downstate)); + + return false; +} + + +// +// P_FireWeapon. +// +void P_FireWeapon (player_t* player) +{ + statenum_t newstate; + + if (!P_CheckAmmo (player)) + return; + + P_SetMobjState (player->mo, S_PLAY_ATK1); + newstate = (statenum_t)weaponinfo[player->readyweapon].atkstate; + P_SetPsprite (player, ps_weapon, newstate); + P_NoiseAlert (player->mo, player->mo); + + if (player->readyweapon == wp_chainsaw ) + { + if( ::g->plyr == player ) { + } + } + +} + + + +// +// P_DropWeapon +// Player died, so put the weapon away. +// +void P_DropWeapon (player_t* player) +{ + P_SetPsprite (player, + ps_weapon, + (statenum_t)weaponinfo[player->readyweapon].downstate); +} + + + +extern "C" { +// +// A_WeaponReady +// The player can fire the weapon +// or change to another weapon at this time. +// Follows after getting weapon up, +// or after previous attack/fire sequence. +// +void +A_WeaponReady +( player_t* player, + pspdef_t* psp ) +{ + statenum_t newstate; + int angle; + + // get out of attack state + if (player->mo->state == &::g->states[S_PLAY_ATK1] + || player->mo->state == &::g->states[S_PLAY_ATK2] ) + { + P_SetMobjState (player->mo, S_PLAY); + } + + if (player->readyweapon == wp_chainsaw + && psp->state == &::g->states[S_SAW]) + { + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_sawidl); + } + + // check for change + // if player is dead, put the weapon away + if (player->pendingweapon != wp_nochange || !player->health) + { + // change weapon + // (pending weapon should allready be validated) + newstate = (statenum_t)weaponinfo[player->readyweapon].downstate; + P_SetPsprite (player, ps_weapon, newstate); + return; + } + + // check for fire + // the missile launcher and bfg do not auto fire + if (player->cmd.buttons & BT_ATTACK) + { + if ( !player->attackdown + || (player->readyweapon != wp_missile + && player->readyweapon != wp_bfg) ) + { + player->attackdown = true; + P_FireWeapon (player); + return; + } + } + else + player->attackdown = false; + + // bob the weapon based on movement speed + angle = (128*::g->leveltime)&FINEMASK; + psp->sx = FRACUNIT + FixedMul (player->bob, finecosine[angle]); + angle &= FINEANGLES/2-1; + psp->sy = WEAPONTOP + FixedMul (player->bob, finesine[angle]); +} + + + +// +// A_ReFire +// The player can re-fire the weapon +// without lowering it entirely. +// +void A_ReFire +( player_t* player, + pspdef_t* psp ) +{ + + // check for fire + // (if a weaponchange is pending, let it go through instead) + if ( (player->cmd.buttons & BT_ATTACK) + && player->pendingweapon == wp_nochange + && player->health) + { + player->refire++; + P_FireWeapon (player); + } + else + { + player->refire = 0; + P_CheckAmmo (player); + } +} + + +void +A_CheckReload +( player_t* player, + pspdef_t* psp ) +{ + P_CheckAmmo (player); +#if 0 + if (player->ammo[am_shell]<2) + P_SetPsprite (player, ps_weapon, S_DSNR1); +#endif +} + + + +// +// A_Lower +// Lowers current weapon, +// and changes weapon at bottom. +// +void +A_Lower +( player_t* player, + pspdef_t* psp ) +{ + psp->sy += LOWERSPEED; + + // Is already down. + if (psp->sy < WEAPONBOTTOM ) + return; + + // Player is dead. + if (player->playerstate == PST_DEAD) + { + psp->sy = WEAPONBOTTOM; + + // don't bring weapon back up + return; + } + + // The old weapon has been lowered off the screen, + // so change the weapon and start raising it + if (!player->health) + { + // Player is dead, so keep the weapon off screen. + P_SetPsprite (player, ps_weapon, S_NULL); + return; + } + + player->readyweapon = player->pendingweapon; + + P_BringUpWeapon (player); +} + + +// +// A_Raise +// +void +A_Raise +( player_t* player, + pspdef_t* psp ) +{ + statenum_t newstate; + + psp->sy -= RAISESPEED; + + if (psp->sy > WEAPONTOP ) + return; + + psp->sy = WEAPONTOP; + + // The weapon has been raised all the way, + // so change to the ready state. + newstate = (statenum_t)weaponinfo[player->readyweapon].readystate; + + P_SetPsprite (player, ps_weapon, newstate); +} + + + +// +// A_GunFlash +// +void +A_GunFlash +( player_t* player, + pspdef_t* psp ) +{ + P_SetMobjState (player->mo, S_PLAY_ATK2); + P_SetPsprite (player,ps_flash,(statenum_t)weaponinfo[player->readyweapon].flashstate); +} + + + +// +// WEAPON ATTACKS +// + + +// +// A_Punch +// +void +A_Punch +( player_t* player, + pspdef_t* psp ) +{ + angle_t angle; + int damage; + int slope; + + damage = (P_Random ()%10+1)<<1; + + if (player->powers[pw_strength]) + damage *= 10; + + angle = player->mo->angle; + angle += (P_Random()-P_Random())<<18; + slope = P_AimLineAttack (player->mo, angle, MELEERANGE); + P_LineAttack (player->mo, angle, MELEERANGE, slope, damage); + + // turn to face target + if (::g->linetarget) + { + S_StartSound (player->mo, sfx_punch); + player->mo->angle = R_PointToAngle2 (player->mo->x, + player->mo->y, + ::g->linetarget->x, + ::g->linetarget->y); + } +} + + +// +// A_Saw +// +void +A_Saw +( player_t* player, + pspdef_t* psp ) +{ + angle_t angle; + int damage; + int slope; + + damage = 2*(P_Random ()%10+1); + angle = player->mo->angle; + angle += (P_Random()-P_Random())<<18; + + // use meleerange + 1 se the puff doesn't skip the flash + slope = P_AimLineAttack (player->mo, angle, MELEERANGE+1); + P_LineAttack (player->mo, angle, MELEERANGE+1, slope, damage); + + if (!::g->linetarget) + { + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_sawful); + return; + } + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_sawhit); + + // turn to face target + angle = R_PointToAngle2 (player->mo->x, player->mo->y, + ::g->linetarget->x, ::g->linetarget->y); + if (angle - player->mo->angle > ANG180) + { + if (angle - player->mo->angle < -ANG90/20) + player->mo->angle = angle + ANG90/21; + else + player->mo->angle -= ANG90/20; + } + else + { + if (angle - player->mo->angle > ANG90/20) + player->mo->angle = angle - ANG90/21; + else + player->mo->angle += ANG90/20; + } + player->mo->flags |= MF_JUSTATTACKED; +} + + + +// +// A_FireMissile +// +void +A_FireMissile +( player_t* player, + pspdef_t* psp ) +{ + if( (player->cheats & CF_INFAMMO) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo]--; + } + P_SpawnPlayerMissile (player->mo, MT_ROCKET); + + if( ::g->plyr == player ) { + } + +} + + +// +// A_FireBFG +// +void +A_FireBFG +( player_t* player, + pspdef_t* psp ) +{ + if( (player->cheats & CF_INFAMMO) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo] -= BFGCELLS; + } + + P_SpawnPlayerMissile (player->mo, MT_BFG); + + if( ::g->plyr == player ) { + } +} + + + +// +// A_FirePlasma +// +void +A_FirePlasma +( player_t* player, + pspdef_t* psp ) +{ + if( (player->cheats & CF_INFAMMO) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo]--; + } + + P_SetPsprite (player, + ps_flash, + (statenum_t)(weaponinfo[player->readyweapon].flashstate+(P_Random ()&1)) ); + + P_SpawnPlayerMissile (player->mo, MT_PLASMA); + + if( ::g->plyr == player ) { + } +} + + + +// +// P_BulletSlope +// Sets a slope so a near miss is at aproximately +// the height of the intended target +// + + +void P_BulletSlope (mobj_t* mo) +{ + angle_t an; + + // see which target is to be aimed at + an = mo->angle; + ::g->bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); + + if (!::g->linetarget) + { + an += 1<<26; + ::g->bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); + if (!::g->linetarget) + { + an -= 2<<26; + ::g->bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); + } + } +} + + +// +// P_GunShot +// +void +P_GunShot +( mobj_t* mo, + qboolean accurate ) +{ + angle_t angle; + int damage; + + damage = 5*(P_Random ()%3+1); + angle = mo->angle; + + if (!accurate) + angle += (P_Random()-P_Random())<<18; + + P_LineAttack (mo, angle, MISSILERANGE, ::g->bulletslope, damage); +} + + +// +// A_FirePistol +// +void +A_FirePistol +( player_t* player, + pspdef_t* psp ) +{ + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_pistol); + + P_SetMobjState (player->mo, S_PLAY_ATK2); + if( (player->cheats & CF_INFAMMO ) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo]--; + } + + P_SetPsprite (player, + ps_flash, + (statenum_t)weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope (player->mo); + P_GunShot (player->mo, !player->refire); + + if( ::g->plyr == player ) { + } +} + + +// +// A_FireShotgun +// +void +A_FireShotgun +( player_t* player, + pspdef_t* psp ) +{ + int i; + + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_shotgn); + P_SetMobjState (player->mo, S_PLAY_ATK2); + + if( ( player->cheats & CF_INFAMMO ) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo]--; + } + + P_SetPsprite (player, + ps_flash, + (statenum_t)weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope (player->mo); + + for (i=0 ; i<7 ; i++) + P_GunShot (player->mo, false); + + if( ::g->plyr == player ) { + } +} + + + +// +// A_FireShotgun2 +// +void +A_FireShotgun2 +( player_t* player, + pspdef_t* psp ) +{ + int i; + angle_t angle; + int damage; + + + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_dshtgn); + P_SetMobjState (player->mo, S_PLAY_ATK2); + + if( (player->cheats & CF_INFAMMO) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo]-=2; + } + + P_SetPsprite (player, + ps_flash, + (statenum_t)weaponinfo[player->readyweapon].flashstate); + + P_BulletSlope (player->mo); + + for (i=0 ; i<20 ; i++) + { + damage = 5*(P_Random ()%3+1); + angle = player->mo->angle; + angle += (P_Random()-P_Random())<<19; + P_LineAttack (player->mo, + angle, + MISSILERANGE, + ::g->bulletslope + ((P_Random()-P_Random())<<5), damage); + } + + if( ::g->plyr == player ) { + } +} + + +// +// A_FireCGun +// +void +A_FireCGun +( player_t* player, + pspdef_t* psp ) +{ + if (globalNetworking || (player == &::g->players[::g->consoleplayer])) + S_StartSound (player->mo, sfx_pistol); + + if (!player->ammo[weaponinfo[player->readyweapon].ammo]) + return; + + P_SetMobjState (player->mo, S_PLAY_ATK2); + if( (player->cheats & CF_INFAMMO) == false ) { + player->ammo[weaponinfo[player->readyweapon].ammo]--; + } + P_SetPsprite (player, + ps_flash, + + (statenum_t)( + weaponinfo[player->readyweapon].flashstate + + psp->state + - &::g->states[S_CHAIN1] )); + + P_BulletSlope (player->mo); + + P_GunShot (player->mo, !player->refire); + + if( ::g->plyr == player ) { + } +} + + + +// +// ? +// +void A_Light0 (player_t *player, pspdef_t *psp) +{ + player->extralight = 0; +} + +void A_Light1 (player_t *player, pspdef_t *psp) +{ + player->extralight = 1; +} + +void A_Light2 (player_t *player, pspdef_t *psp) +{ + player->extralight = 2; +} + + +// +// A_BFGSpray +// Spawn a BFG explosion on every monster in view +// +void A_BFGSpray (mobj_t* mo, void * ) +{ + int i; + int j; + int damage; + angle_t an; + + // offset angles from its attack angle + for (i=0 ; i<40 ; i++) + { + an = mo->angle - ANG90/2 + ANG90/40*i; + + // mo->target is the originator (player) + // of the missile + P_AimLineAttack (mo->target, an, 16*64*FRACUNIT); + + if (!::g->linetarget) + continue; + + P_SpawnMobj (::g->linetarget->x, + ::g->linetarget->y, + ::g->linetarget->z + (::g->linetarget->height>>2), + MT_EXTRABFG); + + damage = 0; + for (j=0;j<15;j++) + damage += (P_Random()&7) + 1; + + P_DamageMobj (::g->linetarget, mo->target,mo->target, damage); + } +} + + +// +// A_BFGsound +// +void +A_BFGsound +( player_t* player, + pspdef_t* psp ) +{ + S_StartSound (player->mo, sfx_bfg); +} + +}; // extern "C" + + +// +// P_SetupPsprites +// Called at start of level for each player. +// +void P_SetupPsprites (player_t* player) +{ + int i; + + // remove all psprites + for (i=0 ; ipsprites[i].state = NULL; + + // spawn the gun + player->pendingweapon = player->readyweapon; + P_BringUpWeapon (player); +} + + + + +// +// P_MovePsprites +// Called every tic by player thinking routine. +// +void P_MovePsprites (player_t* player) +{ + int i; + pspdef_t* psp; + const state_t* state; + + psp = &player->psprites[0]; + for (i=0 ; istate) ) + { + // drop tic count and possibly change state + + // a -1 tic count never changes + if (psp->tics != -1) + { + psp->tics--; + if (!psp->tics) + P_SetPsprite (player, i, psp->state->nextstate); + } + } + } + + player->psprites[ps_flash].sx = player->psprites[ps_weapon].sx; + player->psprites[ps_flash].sy = player->psprites[ps_weapon].sy; +} + diff --git a/doomclassic/doom/p_pspr.h b/doomclassic/doom/p_pspr.h new file mode 100644 index 00000000..a6d32275 --- /dev/null +++ b/doomclassic/doom/p_pspr.h @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_PSPR__ +#define __P_PSPR__ + +// Basic data types. +// Needs fixed point, and BAM angles. +#include "m_fixed.h" +#include "tables.h" + + +// +// Needs to include the precompiled +// sprite animation tables. +// Header generated by multigen utility. +// This includes all the data for thing animation, +// i.e. the Thing Atrributes table +// and the Frame Sequence table. +#include "info.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// Frame flags: +// handles maximum brightness (torches, muzzle flare, light sources) +// +#define FF_FULLBRIGHT 0x8000 // flag in thing->frame +#define FF_FRAMEMASK 0x7fff + + + +// +// Overlay psprites are scaled shapes +// drawn directly on the view screen, +// coordinates are given for a 320*200 view screen. +// +typedef enum +{ + ps_weapon, + ps_flash, + NUMPSPRITES + +} psprnum_t; + +typedef struct +{ + const state_t* state; // a NULL state means not active + int tics; + fixed_t sx; + fixed_t sy; + +} pspdef_t; + +#endif + diff --git a/doomclassic/doom/p_saveg.cpp b/doomclassic/doom/p_saveg.cpp new file mode 100644 index 00000000..4076f29d --- /dev/null +++ b/doomclassic/doom/p_saveg.cpp @@ -0,0 +1,1038 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "i_system.h" +#include "z_zone.h" +#include "p_local.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + + + +// Pads ::g->save_p to a 4-byte boundary +// so that the load/save works on SGI&Gecko. + + + +// +// P_ArchivePlayers +// +void P_ArchivePlayers (void) +{ + int i; + int j; + player_t* dest; + + for (i=0 ; iplayeringame[i]) + continue; + + PADSAVEP(); + + dest = (player_t *)::g->save_p; + memcpy (dest,&::g->players[i],sizeof(player_t)); + ::g->save_p += sizeof(player_t); + for (j=0 ; jpsprites[j].state) + { + dest->psprites[j].state + = (state_t *)(dest->psprites[j].state-::g->states); + } + } + } +} + + + +// +// P_UnArchivePlayers +// +void P_UnArchivePlayers (void) +{ + int i; + int j; + + for (i=0 ; iplayeringame[i]) + continue; + + PADSAVEP(); + + memcpy (&::g->players[i],::g->save_p, sizeof(player_t)); + ::g->save_p += sizeof(player_t); + + // will be set when unarc thinker + ::g->players[i].mo = NULL; + ::g->players[i].message = NULL; + ::g->players[i].attacker = NULL; + + for (j=0 ; jplayers[i]. psprites[j].state) + { + ::g->players[i]. psprites[j].state + = &::g->states[ (int)::g->players[i].psprites[j].state ]; + } + } + } +} + + +// +// P_ArchiveWorld +// +void P_ArchiveWorld (void) +{ + int i; + int j; + sector_t* sec; + line_t* li; + side_t* si; + short* put; + + put = (short *)::g->save_p; + + // do ::g->sectors + for (i=0, sec = ::g->sectors ; i < ::g->numsectors ; i++,sec++) + { + *put++ = sec->floorheight >> FRACBITS; + *put++ = sec->ceilingheight >> FRACBITS; + *put++ = sec->floorpic; + *put++ = sec->ceilingpic; + *put++ = sec->lightlevel; + *put++ = sec->special; // needed? + *put++ = sec->tag; // needed? + } + + + // do ::g->lines + for (i=0, li = ::g->lines ; i < ::g->numlines ; i++,li++) + { + *put++ = li->flags; + *put++ = li->special; + *put++ = li->tag; + for (j=0 ; j<2 ; j++) + { + if (li->sidenum[j] == -1) + continue; + + si = &::g->sides[li->sidenum[j]]; + + *put++ = si->textureoffset >> FRACBITS; + *put++ = si->rowoffset >> FRACBITS; + *put++ = si->toptexture; + *put++ = si->bottomtexture; + *put++ = si->midtexture; + } + } + + // Doom 2 level 30 requires some global pointers, wheee! + *put++ = ::g->braintargeton; + *put++ = ::g->easy; + + ::g->save_p = (byte *)put; +} + + + +// +// P_UnArchiveWorld +// +void P_UnArchiveWorld (void) +{ + int i; + int j; + sector_t* sec; + line_t* li; + side_t* si; + short* get; + + get = (short *)::g->save_p; + + // do ::g->sectors + for (i=0, sec = ::g->sectors ; i < ::g->numsectors ; i++,sec++) + { + sec->floorheight = *get++ << FRACBITS; + sec->ceilingheight = *get++ << FRACBITS; + sec->floorpic = *get++; + sec->ceilingpic = *get++; + sec->lightlevel = *get++; + sec->special = *get++; // needed? + sec->tag = *get++; // needed? + sec->specialdata = 0; + sec->soundtarget = 0; + } + + // do ::g->lines + for (i=0, li = ::g->lines ; i < ::g->numlines ; i++,li++) + { + li->flags = *get++; + li->special = *get++; + li->tag = *get++; + for (j=0 ; j<2 ; j++) + { + if (li->sidenum[j] == -1) + continue; + si = &::g->sides[li->sidenum[j]]; + si->textureoffset = *get++ << FRACBITS; + si->rowoffset = *get++ << FRACBITS; + si->toptexture = *get++; + si->bottomtexture = *get++; + si->midtexture = *get++; + } + } + + // Doom 2 level 30 requires some global pointers, wheee! + ::g->braintargeton = *get++; + ::g->easy = *get++; + + ::g->save_p = (byte *)get; +} + + + + + +// +// Thinkers +// + +int GetMOIndex( mobj_t* findme ) { + thinker_t* th; + mobj_t* mobj; + int index = 0; + + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) { + index++; + mobj = (mobj_t*)th; + + if ( mobj == findme ) { + return index; + } + } + } + + return 0; +} + +mobj_t* GetMO( int index ) { + thinker_t* th; + int testindex = 0; + + if ( !index ) { + return NULL; + } + + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) { + testindex++; + + if ( testindex == index ) { + return (mobj_t*)th; + } + } + } + + return NULL; +} + +// +// P_ArchiveThinkers +// +void P_ArchiveThinkers (void) +{ + thinker_t* th; + mobj_t* mobj; + ceiling_t* ceiling; + vldoor_t* door; + floormove_t* floor; + plat_t* plat; + fireflicker_t* fire; + lightflash_t* flash; + strobe_t* strobe; + glow_t* glow; + + int i; + + // save off the current thinkers + //I_Printf( "Savegame on leveltime %d\n====================\n", ::g->leveltime ); + + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + //mobj_t* test = (mobj_t*)th; + //I_Printf( "%3d: %x == function\n", index++, th->function.acp1 ); + + if (th->function.acp1 == (actionf_p1)P_MobjThinker) + { + *::g->save_p++ = tc_mobj; + PADSAVEP(); + + mobj = (mobj_t *)::g->save_p; + memcpy (mobj, th, sizeof(*mobj)); + ::g->save_p += sizeof(*mobj); + mobj->state = (state_t *)(mobj->state - ::g->states); + + if (mobj->player) + mobj->player = (player_t *)((mobj->player-::g->players) + 1); + + // Save out 'target' + int moIndex = GetMOIndex( mobj->target ); + *::g->save_p++ = moIndex >> 8; + *::g->save_p++ = moIndex; + + // Save out 'tracer' + moIndex = GetMOIndex( mobj->tracer ); + *::g->save_p++ = moIndex >> 8; + *::g->save_p++ = moIndex; + + moIndex = GetMOIndex( mobj->snext ); + *::g->save_p++ = moIndex >> 8; + *::g->save_p++ = moIndex; + + moIndex = GetMOIndex( mobj->sprev ); + *::g->save_p++ = moIndex >> 8; + *::g->save_p++ = moIndex; + + // Is this the head of a sector list? + if ( mobj->subsector->sector->thinglist == (mobj_t*)th ) { + *::g->save_p++ = 1; + } + else { + *::g->save_p++ = 0; + } + + moIndex = GetMOIndex( mobj->bnext ); + *::g->save_p++ = moIndex >> 8; + *::g->save_p++ = moIndex; + + moIndex = GetMOIndex( mobj->bprev ); + *::g->save_p++ = moIndex >> 8; + *::g->save_p++ = moIndex; + + // Is this the head of a block list? + int blockx = (mobj->x - ::g->bmaporgx)>>MAPBLOCKSHIFT; + int blocky = (mobj->y - ::g->bmaporgy)>>MAPBLOCKSHIFT; + if ( blockx >= 0 && blockx < ::g->bmapwidth && blocky >= 0 && blocky < ::g->bmapheight + && (mobj_t*)th == ::g->blocklinks[blocky*::g->bmapwidth+blockx] ) { + + *::g->save_p++ = 1; + } + else { + *::g->save_p++ = 0; + } + continue; + } + + if (th->function.acv == (actionf_v)NULL) + { + for (i = 0; i < MAXCEILINGS;i++) + if (::g->activeceilings[i] == (ceiling_t *)th) + break; + + if (isave_p++ = tc_ceiling; + PADSAVEP(); + ceiling = (ceiling_t *)::g->save_p; + memcpy (ceiling, th, sizeof(*ceiling)); + ::g->save_p += sizeof(*ceiling); + ceiling->sector = (sector_t *)(ceiling->sector - ::g->sectors); + } + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveCeiling) + { + *::g->save_p++ = tc_ceiling; + PADSAVEP(); + ceiling = (ceiling_t *)::g->save_p; + memcpy (ceiling, th, sizeof(*ceiling)); + ::g->save_p += sizeof(*ceiling); + ceiling->sector = (sector_t *)(ceiling->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_VerticalDoor) + { + *::g->save_p++ = tc_door; + PADSAVEP(); + door = (vldoor_t *)::g->save_p; + memcpy (door, th, sizeof(*door)); + ::g->save_p += sizeof(*door); + door->sector = (sector_t *)(door->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveFloor) + { + *::g->save_p++ = tc_floor; + PADSAVEP(); + floor = (floormove_t *)::g->save_p; + memcpy (floor, th, sizeof(*floor)); + ::g->save_p += sizeof(*floor); + floor->sector = (sector_t *)(floor->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_PlatRaise) + { + *::g->save_p++ = tc_plat; + PADSAVEP(); + plat = (plat_t *)::g->save_p; + memcpy (plat, th, sizeof(*plat)); + ::g->save_p += sizeof(*plat); + plat->sector = (sector_t *)(plat->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_FireFlicker) + { + *::g->save_p++ = tc_fire; + PADSAVEP(); + fire = (fireflicker_t *)::g->save_p; + memcpy (fire, th, sizeof(*fire)); + ::g->save_p += sizeof(*fire); + fire->sector = (sector_t *)(fire->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_LightFlash) + { + *::g->save_p++ = tc_flash; + PADSAVEP(); + flash = (lightflash_t *)::g->save_p; + memcpy (flash, th, sizeof(*flash)); + ::g->save_p += sizeof(*flash); + flash->sector = (sector_t *)(flash->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_StrobeFlash) + { + *::g->save_p++ = tc_strobe; + PADSAVEP(); + strobe = (strobe_t *)::g->save_p; + memcpy (strobe, th, sizeof(*strobe)); + ::g->save_p += sizeof(*strobe); + strobe->sector = (sector_t *)(strobe->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_Glow) + { + *::g->save_p++ = tc_glow; + PADSAVEP(); + glow = (glow_t *)::g->save_p; + memcpy (glow, th, sizeof(*glow)); + ::g->save_p += sizeof(*glow); + glow->sector = (sector_t *)(glow->sector - ::g->sectors); + continue; + } + } + + // add a terminating marker + *::g->save_p++ = tc_end; + + sector_t* sec; + short* put = (short *)::g->save_p; + for (i=0, sec = ::g->sectors ; i < ::g->numsectors ; i++,sec++) { + *put++ = (short)GetMOIndex( sec->soundtarget ); + } + + ::g->save_p = (byte *)put; + + // add a terminating marker + *::g->save_p++ = tc_end; +} + + + +// +// P_UnArchiveThinkers +// +void P_UnArchiveThinkers (void) +{ + byte tclass; + thinker_t* currentthinker; + thinker_t* next; + mobj_t* mobj; + ceiling_t* ceiling; + vldoor_t* door; + floormove_t* floor; + plat_t* plat; + fireflicker_t* fire; + lightflash_t* flash; + strobe_t* strobe; + glow_t* glow; + + thinker_t* th; + + int count = 0; + sector_t* ss = NULL; + + int mo_index = 0; + int mo_targets[1024]; + int mo_tracers[1024]; + int mo_snext[1024]; + int mo_sprev[1024]; + bool mo_shead[1024]; + int mo_bnext[1024]; + int mo_bprev[1024]; + bool mo_bhead[1024]; + + // remove all the current thinkers + currentthinker = ::g->thinkercap.next; + while (currentthinker != &::g->thinkercap) + { + next = currentthinker->next; + + if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + P_RemoveMobj ((mobj_t *)currentthinker); + else + Z_Free(currentthinker); + + currentthinker = next; + } + + P_InitThinkers (); + + // read in saved thinkers + while (1) + { + tclass = *::g->save_p++; + switch (tclass) + { + case tc_end: + + // clear sector thing lists + ss = ::g->sectors; + for (int i=0 ; i < ::g->numsectors ; i++, ss++) { + ss->thinglist = NULL; + } + + // clear blockmap thing lists + count = sizeof(*::g->blocklinks) * ::g->bmapwidth * ::g->bmapheight; + memset (::g->blocklinks, 0, count); + + // Doom 2 level 30 requires some global pointers, wheee! + ::g->numbraintargets = 0; + + // fixup mobj_t pointers now that all thinkers have been restored + mo_index = 0; + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) { + mobj = (mobj_t*)th; + + mobj->target = GetMO( mo_targets[mo_index] ); + mobj->tracer = GetMO( mo_tracers[mo_index] ); + + mobj->snext = GetMO( mo_snext[mo_index] ); + mobj->sprev = GetMO( mo_sprev[mo_index] ); + + if ( mo_shead[mo_index] ) { + mobj->subsector->sector->thinglist = mobj; + } + + mobj->bnext = GetMO( mo_bnext[mo_index] ); + mobj->bprev = GetMO( mo_bprev[mo_index] ); + + if ( mo_bhead[mo_index] ) { + // Is this the head of a block list? + int blockx = (mobj->x - ::g->bmaporgx)>>MAPBLOCKSHIFT; + int blocky = (mobj->y - ::g->bmaporgy)>>MAPBLOCKSHIFT; + if ( blockx >= 0 && blockx < ::g->bmapwidth && blocky >= 0 && blocky < ::g->bmapheight ) { + ::g->blocklinks[blocky*::g->bmapwidth+blockx] = mobj; + } + } + + // Doom 2 level 30 requires some global pointers, wheee! + if ( mobj->type == MT_BOSSTARGET ) { + ::g->braintargets[::g->numbraintargets] = mobj; + ::g->numbraintargets++; + } + + mo_index++; + } + } + + int i; + sector_t* sec; + short* get; + + get = (short *)::g->save_p; + for (i=0, sec = ::g->sectors ; i < ::g->numsectors ; i++,sec++) + { + sec->soundtarget = GetMO( *get++ ); + } + ::g->save_p = (byte *)get; + + tclass = *::g->save_p++; + if ( tclass != tc_end ) { + I_Error( "Savegame error after loading sector soundtargets." ); + } + + // print the current thinkers + //I_Printf( "Loadgame on leveltime %d\n====================\n", ::g->leveltime ); + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + //mobj_t* test = (mobj_t*)th; + //I_Printf( "%3d: %x == function\n", index++, th->function.acp1 ); + } + + return; // end of list + + case tc_mobj: + PADSAVEP(); + mobj = (mobj_t*)DoomLib::Z_Malloc(sizeof(*mobj), PU_LEVEL, NULL); + memcpy (mobj, ::g->save_p, sizeof(*mobj)); + ::g->save_p += sizeof(*mobj); + mobj->state = &::g->states[(int)mobj->state]; + + mobj->target = NULL; + mobj->tracer = NULL; + + if (mobj->player) + { + mobj->player = &::g->players[(int)mobj->player-1]; + mobj->player->mo = mobj; + } + + P_SetThingPosition (mobj); + + mobj->info = &mobjinfo[mobj->type]; + mobj->floorz = mobj->subsector->sector->floorheight; + mobj->ceilingz = mobj->subsector->sector->ceilingheight; + mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; + + // Read in 'target' and store for fixup + int a, b, foundIndex; + a = *::g->save_p++; + b = *::g->save_p++; + foundIndex = (a << 8) + b; + mo_targets[mo_index] = foundIndex; + + // Read in 'tracer' and store for fixup + a = *::g->save_p++; + b = *::g->save_p++; + foundIndex = (a << 8) + b; + mo_tracers[mo_index] = foundIndex; + + // Read in 'snext' and store for fixup + a = *::g->save_p++; + b = *::g->save_p++; + foundIndex = (a << 8) + b; + mo_snext[mo_index] = foundIndex; + + // Read in 'sprev' and store for fixup + a = *::g->save_p++; + b = *::g->save_p++; + foundIndex = (a << 8) + b; + mo_sprev[mo_index] = foundIndex; + + foundIndex = *::g->save_p++; + mo_shead[mo_index] = foundIndex == 1; + + // Read in 'bnext' and store for fixup + a = *::g->save_p++; + b = *::g->save_p++; + foundIndex = (a << 8) + b; + mo_bnext[mo_index] = foundIndex; + + // Read in 'bprev' and store for fixup + a = *::g->save_p++; + b = *::g->save_p++; + foundIndex = (a << 8) + b; + mo_bprev[mo_index] = foundIndex; + + foundIndex = *::g->save_p++; + mo_bhead[mo_index] = foundIndex == 1; + + mo_index++; + + P_AddThinker (&mobj->thinker); + break; + + case tc_ceiling: + PADSAVEP(); + ceiling = (ceiling_t*)DoomLib::Z_Malloc(sizeof(*ceiling), PU_LEVEL, NULL); + memcpy (ceiling, ::g->save_p, sizeof(*ceiling)); + ::g->save_p += sizeof(*ceiling); + ceiling->sector = &::g->sectors[(int)ceiling->sector]; + ceiling->sector->specialdata = ceiling; + + if (ceiling->thinker.function.acp1) + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + + P_AddThinker (&ceiling->thinker); + P_AddActiveCeiling(ceiling); + break; + + case tc_door: + PADSAVEP(); + door = (vldoor_t*)DoomLib::Z_Malloc(sizeof(*door), PU_LEVEL, NULL); + memcpy (door, ::g->save_p, sizeof(*door)); + ::g->save_p += sizeof(*door); + door->sector = &::g->sectors[(int)door->sector]; + door->sector->specialdata = door; + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + P_AddThinker (&door->thinker); + break; + + case tc_floor: + PADSAVEP(); + floor = (floormove_t*)DoomLib::Z_Malloc (sizeof(*floor), PU_LEVEL, NULL); + memcpy (floor, ::g->save_p, sizeof(*floor)); + ::g->save_p += sizeof(*floor); + floor->sector = &::g->sectors[(int)floor->sector]; + floor->sector->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + P_AddThinker (&floor->thinker); + break; + + case tc_plat: + PADSAVEP(); + plat = (plat_t*)DoomLib::Z_Malloc (sizeof(*plat), PU_LEVEL, NULL); + memcpy (plat, ::g->save_p, sizeof(*plat)); + ::g->save_p += sizeof(*plat); + plat->sector = &::g->sectors[(int)plat->sector]; + plat->sector->specialdata = plat; + + if (plat->thinker.function.acp1) + plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + + P_AddThinker (&plat->thinker); + P_AddActivePlat(plat); + break; + + case tc_fire: + PADSAVEP(); + fire = (fireflicker_t*)DoomLib::Z_Malloc (sizeof(*fire), PU_LEVEL, NULL); + memcpy (fire, ::g->save_p, sizeof(*fire)); + ::g->save_p += sizeof(*fire); + fire->sector = &::g->sectors[(int)fire->sector]; + fire->thinker.function.acp1 = (actionf_p1)T_FireFlicker; + P_AddThinker (&fire->thinker); + break; + + case tc_flash: + PADSAVEP(); + flash = (lightflash_t*)DoomLib::Z_Malloc (sizeof(*flash), PU_LEVEL, NULL); + memcpy (flash, ::g->save_p, sizeof(*flash)); + ::g->save_p += sizeof(*flash); + flash->sector = &::g->sectors[(int)flash->sector]; + flash->thinker.function.acp1 = (actionf_p1)T_LightFlash; + P_AddThinker (&flash->thinker); + break; + + case tc_strobe: + PADSAVEP(); + strobe = (strobe_t*)DoomLib::Z_Malloc (sizeof(*strobe), PU_LEVEL, NULL); + memcpy (strobe, ::g->save_p, sizeof(*strobe)); + ::g->save_p += sizeof(*strobe); + strobe->sector = &::g->sectors[(int)strobe->sector]; + strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash; + P_AddThinker (&strobe->thinker); + break; + + case tc_glow: + PADSAVEP(); + glow = (glow_t*)DoomLib::Z_Malloc (sizeof(*glow), PU_LEVEL, NULL); + memcpy (glow, ::g->save_p, sizeof(*glow)); + ::g->save_p += sizeof(*glow); + glow->sector = &::g->sectors[(int)glow->sector]; + glow->thinker.function.acp1 = (actionf_p1)T_Glow; + P_AddThinker (&glow->thinker); + break; + + default: + I_Error ("Unknown tclass %i in savegame",tclass); + } + } +} + + +// +// P_ArchiveSpecials +// + + + +// +// Things to handle: +// +// T_MoveCeiling, (ceiling_t: sector_t * swizzle), - active list +// T_VerticalDoor, (vldoor_t: sector_t * swizzle), +// T_MoveFloor, (floormove_t: sector_t * swizzle), +// T_LightFlash, (lightflash_t: sector_t * swizzle), +// T_StrobeFlash, (strobe_t: sector_t *), +// T_Glow, (glow_t: sector_t *), +// T_PlatRaise, (plat_t: sector_t *), - active list +// +void P_ArchiveSpecials (void) +{ + thinker_t* th; + ceiling_t* ceiling; + vldoor_t* door; + floormove_t* floor; + plat_t* plat; + lightflash_t* flash; + strobe_t* strobe; + glow_t* glow; + int i; + + // save off the current thinkers + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + if (th->function.acv == (actionf_v)NULL) + { + for (i = 0; i < MAXCEILINGS;i++) + if (::g->activeceilings[i] == (ceiling_t *)th) + break; + + if (isave_p++ = tc_ceiling; + PADSAVEP(); + ceiling = (ceiling_t *)::g->save_p; + memcpy (ceiling, th, sizeof(*ceiling)); + ::g->save_p += sizeof(*ceiling); + ceiling->sector = (sector_t *)(ceiling->sector - ::g->sectors); + } + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveCeiling) + { + *::g->save_p++ = tc_ceiling; + PADSAVEP(); + ceiling = (ceiling_t *)::g->save_p; + memcpy (ceiling, th, sizeof(*ceiling)); + ::g->save_p += sizeof(*ceiling); + ceiling->sector = (sector_t *)(ceiling->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_VerticalDoor) + { + *::g->save_p++ = tc_door; + PADSAVEP(); + door = (vldoor_t *)::g->save_p; + memcpy (door, th, sizeof(*door)); + ::g->save_p += sizeof(*door); + door->sector = (sector_t *)(door->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_MoveFloor) + { + *::g->save_p++ = tc_floor; + PADSAVEP(); + floor = (floormove_t *)::g->save_p; + memcpy (floor, th, sizeof(*floor)); + ::g->save_p += sizeof(*floor); + floor->sector = (sector_t *)(floor->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_PlatRaise) + { + *::g->save_p++ = tc_plat; + PADSAVEP(); + plat = (plat_t *)::g->save_p; + memcpy (plat, th, sizeof(*plat)); + ::g->save_p += sizeof(*plat); + plat->sector = (sector_t *)(plat->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_LightFlash) + { + *::g->save_p++ = tc_flash; + PADSAVEP(); + flash = (lightflash_t *)::g->save_p; + memcpy (flash, th, sizeof(*flash)); + ::g->save_p += sizeof(*flash); + flash->sector = (sector_t *)(flash->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_StrobeFlash) + { + *::g->save_p++ = tc_strobe; + PADSAVEP(); + strobe = (strobe_t *)::g->save_p; + memcpy (strobe, th, sizeof(*strobe)); + ::g->save_p += sizeof(*strobe); + strobe->sector = (sector_t *)(strobe->sector - ::g->sectors); + continue; + } + + if (th->function.acp1 == (actionf_p1)T_Glow) + { + *::g->save_p++ = tc_glow; + PADSAVEP(); + glow = (glow_t *)::g->save_p; + memcpy (glow, th, sizeof(*glow)); + ::g->save_p += sizeof(*glow); + glow->sector = (sector_t *)(glow->sector - ::g->sectors); + continue; + } + } + + // add a terminating marker + *::g->save_p++ = tc_endspecials; + +} + + +// +// P_UnArchiveSpecials +// +void P_UnArchiveSpecials (void) +{ + byte tclass; + ceiling_t* ceiling; + vldoor_t* door; + floormove_t* floor; + plat_t* plat; + lightflash_t* flash; + strobe_t* strobe; + glow_t* glow; + + // read in saved thinkers + while (1) + { + tclass = *::g->save_p++; + switch (tclass) + { + case tc_endspecials: + return; // end of list + + case tc_ceiling: + PADSAVEP(); + ceiling = (ceiling_t*)DoomLib::Z_Malloc(sizeof(*ceiling), PU_LEVEL, NULL); + memcpy (ceiling, ::g->save_p, sizeof(*ceiling)); + ::g->save_p += sizeof(*ceiling); + ceiling->sector = &::g->sectors[(int)ceiling->sector]; + ceiling->sector->specialdata = ceiling; + + if (ceiling->thinker.function.acp1) + ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling; + + P_AddThinker (&ceiling->thinker); + P_AddActiveCeiling(ceiling); + break; + + case tc_door: + PADSAVEP(); + door = (vldoor_t*)DoomLib::Z_Malloc(sizeof(*door), PU_LEVEL, NULL); + memcpy (door, ::g->save_p, sizeof(*door)); + ::g->save_p += sizeof(*door); + door->sector = &::g->sectors[(int)door->sector]; + door->sector->specialdata = door; + door->thinker.function.acp1 = (actionf_p1)T_VerticalDoor; + P_AddThinker (&door->thinker); + break; + + case tc_floor: + PADSAVEP(); + floor = (floormove_t*)DoomLib::Z_Malloc (sizeof(*floor), PU_LEVEL, NULL); + memcpy (floor, ::g->save_p, sizeof(*floor)); + ::g->save_p += sizeof(*floor); + floor->sector = &::g->sectors[(int)floor->sector]; + floor->sector->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1)T_MoveFloor; + P_AddThinker (&floor->thinker); + break; + + case tc_plat: + PADSAVEP(); + plat = (plat_t*)DoomLib::Z_Malloc (sizeof(*plat), PU_LEVEL, NULL); + memcpy (plat, ::g->save_p, sizeof(*plat)); + ::g->save_p += sizeof(*plat); + plat->sector = &::g->sectors[(int)plat->sector]; + plat->sector->specialdata = plat; + + if (plat->thinker.function.acp1) + plat->thinker.function.acp1 = (actionf_p1)T_PlatRaise; + + P_AddThinker (&plat->thinker); + P_AddActivePlat(plat); + break; + + case tc_flash: + PADSAVEP(); + flash = (lightflash_t*)DoomLib::Z_Malloc (sizeof(*flash), PU_LEVEL, NULL); + memcpy (flash, ::g->save_p, sizeof(*flash)); + ::g->save_p += sizeof(*flash); + flash->sector = &::g->sectors[(int)flash->sector]; + flash->thinker.function.acp1 = (actionf_p1)T_LightFlash; + P_AddThinker (&flash->thinker); + break; + + case tc_strobe: + PADSAVEP(); + strobe = (strobe_t*)DoomLib::Z_Malloc (sizeof(*strobe), PU_LEVEL, NULL); + memcpy (strobe, ::g->save_p, sizeof(*strobe)); + ::g->save_p += sizeof(*strobe); + strobe->sector = &::g->sectors[(int)strobe->sector]; + strobe->thinker.function.acp1 = (actionf_p1)T_StrobeFlash; + P_AddThinker (&strobe->thinker); + break; + + case tc_glow: + PADSAVEP(); + glow = (glow_t*)DoomLib::Z_Malloc (sizeof(*glow), PU_LEVEL, NULL); + memcpy (glow, ::g->save_p, sizeof(*glow)); + ::g->save_p += sizeof(*glow); + glow->sector = &::g->sectors[(int)glow->sector]; + glow->thinker.function.acp1 = (actionf_p1)T_Glow; + P_AddThinker (&glow->thinker); + break; + + default: + I_Error ("P_UnarchiveSpecials:Unknown tclass %i " + "in savegame",tclass); + } + + } + +} + + diff --git a/doomclassic/doom/p_saveg.h b/doomclassic/doom/p_saveg.h new file mode 100644 index 00000000..91c0f3f1 --- /dev/null +++ b/doomclassic/doom/p_saveg.h @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_SAVEG__ +#define __P_SAVEG__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// Persistent storage/archiving. +// These are the load / save game routines. +void P_ArchivePlayers (void); +void P_UnArchivePlayers (void); +void P_ArchiveWorld (void); +void P_UnArchiveWorld (void); +void P_ArchiveThinkers (void); +void P_UnArchiveThinkers (void); +void P_ArchiveSpecials (void); +void P_UnArchiveSpecials (void); + +extern byte* save_p; + + +#endif + diff --git a/doomclassic/doom/p_setup.cpp b/doomclassic/doom/p_setup.cpp new file mode 100644 index 00000000..3bf5bfe9 --- /dev/null +++ b/doomclassic/doom/p_setup.cpp @@ -0,0 +1,738 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include + +#include "z_zone.h" + +#include "m_swap.h" +#include "m_bbox.h" + +#include "g_game.h" + +#include "i_system.h" +#include "w_wad.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "s_sound.h" + +#include "doomstat.h" + + +void P_SpawnMapThing (mapthing_t* mthing); + + +// +// MAP related Lookup tables. +// Store VERTEXES, LINEDEFS, SIDEDEFS, etc. +// + + + + + + + + +// BLOCKMAP +// Created from axis aligned bounding box +// of the map, a rectangular array of +// blocks of size ... +// Used to speed up collision detection +// by spatial subdivision in 2D. +// +// Blockmap size. +// offsets in ::g->blockmap are from here +// origin of block map +// for thing chains + + +// REJECT +// For fast sight rejection. +// Speeds up enemy AI by skipping detailed +// LineOf Sight calculation. +// Without special effect, this could be +// used as a PVS lookup as well. +// + + +// Maintain single and multi player starting spots. + + + + + + +// +// P_LoadVertexes +// +void P_LoadVertexes (int lump) +{ + byte* data; + int i; + mapvertex_t* ml; + vertex_t* li; + + // Determine number of lumps: + // total lump length / vertex record length. + ::g->numvertexes = W_LumpLength (lump) / sizeof(mapvertex_t); + + // Allocate zone memory for buffer. +// ::g->vertexes = (vertex_t*)Z_Malloc (::g->numvertexes*sizeof(vertex_t),PU_LEVEL,0); + if (MallocForLump( lump, ::g->numvertexes*sizeof(vertex_t ), ::g->vertexes, PU_LEVEL_SHARED )) + { + // Load data into cache. + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + ml = (mapvertex_t *)data; + li = ::g->vertexes; + + // Copy and convert vertex coordinates, + // internal representation as fixed. + for (i=0 ; i < ::g->numvertexes ; i++, li++, ml++) + { + li->x = SHORT(ml->x)<y = SHORT(ml->y)<numsegs = W_LumpLength (lump) / sizeof(mapseg_t); +// ::g->segs = (seg_t*)Z_Malloc (::g->numsegs*sizeof(seg_t),PU_LEVEL,0); + + if (MallocForLump( lump, ::g->numsegs*sizeof(seg_t), ::g->segs, PU_LEVEL_SHARED )) + { + memset (::g->segs, 0, ::g->numsegs*sizeof(seg_t)); + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + ml = (mapseg_t *)data; + li = ::g->segs; + for (i=0 ; i < ::g->numsegs ; i++, li++, ml++) + { + li->v1 = &::g->vertexes[SHORT(ml->v1)]; + li->v2 = &::g->vertexes[SHORT(ml->v2)]; + + li->angle = (SHORT(ml->angle))<<16; + li->offset = (SHORT(ml->offset))<<16; + psetup_linedef = SHORT(ml->linedef); + ldef = &::g->lines[psetup_linedef]; + li->linedef = ldef; + side = SHORT(ml->side); + li->sidedef = &::g->sides[ldef->sidenum[side]]; + li->frontsector = ::g->sides[ldef->sidenum[side]].sector; + if (ldef-> flags & ML_TWOSIDED) + li->backsector = ::g->sides[ldef->sidenum[side^1]].sector; + else + li->backsector = 0; + } + + Z_Free(data); + } +} + + +// +// P_LoadSubsectors +// +void P_LoadSubsectors (int lump) +{ + byte* data; + int i; + mapsubsector_t* ms; + subsector_t* ss; + + ::g->numsubsectors = W_LumpLength (lump) / sizeof(mapsubsector_t); + + if (MallocForLump( lump, ::g->numsubsectors*sizeof(subsector_t), ::g->subsectors, PU_LEVEL_SHARED )) + { + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + ms = (mapsubsector_t *)data; + memset (::g->subsectors,0, ::g->numsubsectors*sizeof(subsector_t)); + ss = ::g->subsectors; + + for (i=0 ; i < ::g->numsubsectors ; i++, ss++, ms++) + { + ss->numlines = SHORT(ms->numsegs); + ss->firstline = SHORT(ms->firstseg); + } + + Z_Free(data); + } +} + + + +// +// P_LoadSectors +// +void P_LoadSectors (int lump) +{ + byte* data; + int i; + mapsector_t* ms; + sector_t* ss; + + ::g->numsectors = W_LumpLength (lump) / sizeof(mapsector_t); + + ::g->sectors = (sector_t*)Z_Malloc( ::g->numsectors*sizeof(sector_t), PU_LEVEL, NULL ); + memset (::g->sectors, 0, ::g->numsectors*sizeof(sector_t)); + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + ms = (mapsector_t *)data; + ss = ::g->sectors; + for (i=0 ; i < ::g->numsectors ; i++, ss++, ms++) + { + ss->floorheight = SHORT(ms->floorheight)<ceilingheight = SHORT(ms->ceilingheight)<floorpic = R_FlatNumForName(ms->floorpic); + ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); + ss->lightlevel = SHORT(ms->lightlevel); + ss->special = SHORT(ms->special); + ss->tag = SHORT(ms->tag); + ss->thinglist = NULL; + } + + Z_Free(data); + +/* + if (MallocForLump( lump, ::g->numsectors*sizeof(sector_t), (void**)&::g->sectors, PU_LEVEL_SHARED )) + { + memset (::g->sectors, 0, ::g->numsectors*sizeof(sector_t)); + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + ms = (mapsector_t *)data; + ss = ::g->sectors; + for (i=0 ; i < ::g->numsectors ; i++, ss++, ms++) + { + ss->floorheight = SHORT(ms->floorheight)<ceilingheight = SHORT(ms->ceilingheight)<floorpic = R_FlatNumForName(ms->floorpic); + ss->ceilingpic = R_FlatNumForName(ms->ceilingpic); + ss->lightlevel = SHORT(ms->lightlevel); + ss->special = SHORT(ms->special); + ss->tag = SHORT(ms->tag); + ss->thinglist = NULL; + } + + DoomLib::Z_Free(data); + } +*/ +} + + +// +// P_LoadNodes +// +void P_LoadNodes (int lump) +{ + byte* data; + int i; + int j; + int k; + mapnode_t* mn; + node_t* no; + + ::g->numnodes = W_LumpLength (lump) / sizeof(mapnode_t); + if (MallocForLump( lump, ::g->numnodes*sizeof(node_t), ::g->nodes, PU_LEVEL_SHARED )) + { + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + mn = (mapnode_t *)data; + no = ::g->nodes; + + for (i=0 ; i < ::g->numnodes ; i++, no++, mn++) + { + no->x = SHORT(mn->x)<y = SHORT(mn->y)<dx = SHORT(mn->dx)<dy = SHORT(mn->dy)<children[j] = SHORT(mn->children[j]); + for (k=0 ; k<4 ; k++) + no->bbox[j][k] = SHORT(mn->bbox[j][k])<gamemode != commercial) + { + switch(mt->type) + { + case 68: // Arachnotron + case 64: // Archvile + case 88: // Boss Brain + case 89: // Boss Shooter + case 69: // Hell Knight + case 67: // Mancubus + case 71: // Pain Elemental + case 65: // Former Human Commando + case 66: // Revenant + case 84: // Wolf SS + spawn = false; + break; + } + } + if (spawn == false) + break; + + // Do spawn all other stuff. + mt->x = SHORT(mt->x); + mt->y = SHORT(mt->y); + mt->angle = SHORT(mt->angle); + mt->type = SHORT(mt->type); + mt->options = SHORT(mt->options); + + P_SpawnMapThing (mt); + } + + Z_Free(data); +} + + +// +// P_LoadLineDefs +// Also counts secret ::g->lines for intermissions. +// +void P_LoadLineDefs (int lump) +{ + byte* data; + int i; + maplinedef_t* mld; + line_t* ld; + vertex_t* v1; + vertex_t* v2; + + ::g->numlines = W_LumpLength (lump) / sizeof(maplinedef_t); + if (MallocForLump( lump, ::g->numlines*sizeof(line_t), ::g->lines, PU_LEVEL_SHARED )) + { + memset (::g->lines, 0, ::g->numlines*sizeof(line_t)); + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + mld = (maplinedef_t *)data; + ld = ::g->lines; + for (i=0 ; i < ::g->numlines ; i++, mld++, ld++) + { + ld->flags = SHORT(mld->flags); + ld->special = SHORT(mld->special); + ld->tag = SHORT(mld->tag); + v1 = ld->v1 = &::g->vertexes[SHORT(mld->v1)]; + v2 = ld->v2 = &::g->vertexes[SHORT(mld->v2)]; + ld->dx = v2->x - v1->x; + ld->dy = v2->y - v1->y; + + if (!ld->dx) + ld->slopetype = ST_VERTICAL; + else if (!ld->dy) + ld->slopetype = ST_HORIZONTAL; + else + { + if (FixedDiv (ld->dy , ld->dx) > 0) + ld->slopetype = ST_POSITIVE; + else + ld->slopetype = ST_NEGATIVE; + } + + if (v1->x < v2->x) + { + ld->bbox[BOXLEFT] = v1->x; + ld->bbox[BOXRIGHT] = v2->x; + } + else + { + ld->bbox[BOXLEFT] = v2->x; + ld->bbox[BOXRIGHT] = v1->x; + } + + if (v1->y < v2->y) + { + ld->bbox[BOXBOTTOM] = v1->y; + ld->bbox[BOXTOP] = v2->y; + } + else + { + ld->bbox[BOXBOTTOM] = v2->y; + ld->bbox[BOXTOP] = v1->y; + } + + ld->sidenum[0] = SHORT(mld->sidenum[0]); + ld->sidenum[1] = SHORT(mld->sidenum[1]); + + if (ld->sidenum[0] != -1) + ld->frontsector = ::g->sides[ld->sidenum[0]].sector; + else + ld->frontsector = 0; + + if (ld->sidenum[1] != -1) + ld->backsector = ::g->sides[ld->sidenum[1]].sector; + else + ld->backsector = 0; + } + + Z_Free(data); + } +} + + +// +// P_LoadSideDefs +// +void P_LoadSideDefs (int lump) +{ + byte* data; + int i; + mapsidedef_t* msd; + side_t* sd; + + ::g->numsides = W_LumpLength (lump) / sizeof(mapsidedef_t); + if (MallocForLump( lump, ::g->numsides*sizeof(side_t), ::g->sides, PU_LEVEL_SHARED)) + { + memset (::g->sides, 0, ::g->numsides*sizeof(side_t)); + data = (byte*)W_CacheLumpNum (lump,PU_CACHE_SHARED); // ALAN: LOADTIME + + msd = (mapsidedef_t *)data; + sd = ::g->sides; + for (i=0 ; i < ::g->numsides ; i++, msd++, sd++) + { + sd->textureoffset = SHORT(msd->textureoffset)<rowoffset = SHORT(msd->rowoffset)<toptexture = R_TextureNumForName(msd->toptexture); + sd->bottomtexture = R_TextureNumForName(msd->bottomtexture); + sd->midtexture = R_TextureNumForName(msd->midtexture); + sd->sector = &::g->sectors[SHORT(msd->sector)]; + } + + Z_Free(data); + } +} + + +// +// P_LoadBlockMap +// +void P_LoadBlockMap (int lump) +{ + int i; + int count; + + bool firstTime = false; + if (!lumpcache[lump]) { // SMF - solution for double endian conversion issue + firstTime = true; + } + + ::g->blockmaplump = (short*)W_CacheLumpNum (lump,PU_LEVEL_SHARED); // ALAN: This is initialized somewhere else as shared... + ::g->blockmap = ::g->blockmaplump+4; + count = W_LumpLength (lump)/2; + + if ( firstTime ) { // SMF + for (i=0 ; iblockmaplump[i] = SHORT(::g->blockmaplump[i]); + } + + ::g->bmaporgx = ( ::g->blockmaplump[0] )<bmaporgy = ( ::g->blockmaplump[1] )<bmapwidth = ( ::g->blockmaplump[2] ); + ::g->bmapheight = ( ::g->blockmaplump[3] ); + + // clear out mobj chains + count = sizeof(*::g->blocklinks)* ::g->bmapwidth*::g->bmapheight; + ::g->blocklinks = (mobj_t**)Z_Malloc (count,PU_LEVEL, 0); + memset (::g->blocklinks, 0, count); +} + + + +// +// P_GroupLines +// Builds sector line lists and subsector sector numbers. +// Finds block bounding boxes for ::g->sectors. +// +void P_GroupLines (void) +{ + line_t** linebuffer; + int i; + int j; + int total; + line_t* li; + sector_t* sector; + subsector_t* ss; + seg_t* seg; + fixed_t bbox[4]; + int block; + + + // look up sector number for each subsector + ss = ::g->subsectors; + for (i=0 ; i < ::g->numsubsectors ; i++, ss++) + { + seg = &::g->segs[ss->firstline]; + ss->sector = seg->sidedef->sector; + } + + // count number of ::g->lines in each sector + li = ::g->lines; + total = 0; + for (i=0 ; i < ::g->numlines ; i++, li++) + { + total++; + li->frontsector->linecount++; + + if (li->backsector && li->backsector != li->frontsector) + { + li->backsector->linecount++; + total++; + } + } + + // build line tables for each sector + linebuffer = (line_t**)Z_Malloc (total*4, PU_LEVEL, 0); + sector = ::g->sectors; + for (i=0 ; i < ::g->numsectors ; i++, sector++) + { + M_ClearBox (bbox); + sector->lines = linebuffer; + li = ::g->lines; + for (j=0 ; j < ::g->numlines ; j++, li++) + { + if (li->frontsector == sector || li->backsector == sector) + { + *linebuffer++ = li; + M_AddToBox (bbox, li->v1->x, li->v1->y); + M_AddToBox (bbox, li->v2->x, li->v2->y); + } + } + if (linebuffer - sector->lines != sector->linecount) + I_Error ("P_GroupLines: miscounted"); + + // set the degenmobj_t to the middle of the bounding box + sector->soundorg.x = (bbox[BOXRIGHT]+bbox[BOXLEFT])/2; + sector->soundorg.y = (bbox[BOXTOP]+bbox[BOXBOTTOM])/2; + + // adjust bounding box to map blocks + block = (bbox[BOXTOP]-::g->bmaporgy+MAXRADIUS)>>MAPBLOCKSHIFT; + block = block >= ::g->bmapheight ? ::g->bmapheight-1 : block; + sector->blockbox[BOXTOP]=block; + + block = (bbox[BOXBOTTOM]-::g->bmaporgy-MAXRADIUS)>>MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector->blockbox[BOXBOTTOM]=block; + + block = (bbox[BOXRIGHT]-::g->bmaporgx+MAXRADIUS)>>MAPBLOCKSHIFT; + block = block >= ::g->bmapwidth ? ::g->bmapwidth-1 : block; + sector->blockbox[BOXRIGHT]=block; + + block = (bbox[BOXLEFT]-::g->bmaporgx-MAXRADIUS)>>MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector->blockbox[BOXLEFT]=block; + } + +} + + +// +// P_SetupLevel +// +void +P_SetupLevel +( int episode, + int map, + int playermask, + skill_t skill) +{ + int i; + char lumpname[9]; + int lumpnum; + + ::g->totalkills = ::g->totalitems = ::g->totalsecret = ::g->wminfo.maxfrags = 0; + ::g->wminfo.partime = 180; + for (i=0 ; iplayers[i].killcount = ::g->players[i].secretcount + = ::g->players[i].itemcount = 0; + + ::g->players[i].chainsawKills = 0; + ::g->players[i].berserkKills = 0; + } + + // Initial height of PointOfView + // will be set by player think. + ::g->players[::g->consoleplayer].viewz = 1; + + // Make sure all sounds are stopped before Z_FreeTags. + S_Start (); + + Z_FreeTags( PU_LEVEL, PU_PURGELEVEL-1 ); + + // UNUSED W_Profile (); + P_InitThinkers (); + + // if working with a devlopment map, reload it + // W_Reload (); + + // DHM - NERVE :: Update the cached asset pointers in case the wad files were reloaded + { + void ST_loadData(void); + ST_loadData(); + + void HU_Init(void); + HU_Init(); + } + + // find map name + if ( ::g->gamemode == commercial) + { + if (map<10) + sprintf (lumpname,"map0%i", map); + else + sprintf (lumpname,"map%i", map); + } + else + { + lumpname[0] = 'E'; + lumpname[1] = '0' + episode; + lumpname[2] = 'M'; + lumpname[3] = '0' + map; + lumpname[4] = 0; + } + + lumpnum = W_GetNumForName (lumpname); + + ::g->leveltime = 0; + + // note: most of this ordering is important + P_LoadBlockMap (lumpnum+ML_BLOCKMAP); + P_LoadVertexes (lumpnum+ML_VERTEXES); + P_LoadSectors (lumpnum+ML_SECTORS); + P_LoadSideDefs (lumpnum+ML_SIDEDEFS); + + P_LoadLineDefs (lumpnum+ML_LINEDEFS); + P_LoadSubsectors (lumpnum+ML_SSECTORS); + P_LoadNodes (lumpnum+ML_NODES); + P_LoadSegs (lumpnum+ML_SEGS); + + ::g->rejectmatrix = (byte*)W_CacheLumpNum (lumpnum+ML_REJECT,PU_LEVEL); + + P_GroupLines (); + + ::g->bodyqueslot = 0; + ::g->deathmatch_p = ::g->deathmatchstarts; + P_LoadThings (lumpnum+ML_THINGS); + + // if ::g->deathmatch, randomly spawn the active ::g->players + if (::g->deathmatch) + { + for (i=0 ; iplayeringame[i]) + { + // DHM - Nerve :: In deathmatch, reset every player at match start + ::g->players[i].playerstate = PST_REBORN; + + ::g->players[i].mo = NULL; + G_DeathMatchSpawnPlayer (i); + } + + } + + // clear special respawning que + ::g->iquehead = ::g->iquetail = 0; + + // set up world state + P_SpawnSpecials (); + + // build subsector connect matrix + // UNUSED P_ConnectSubsectors (); + + // preload graphics + if (::g->precache) + R_PrecacheLevel (); +} + + + +// +// P_Init +// +void P_Init (void) +{ + P_InitSwitchList (); + P_InitPicAnims (); + R_InitSprites (sprnames); +} + + + + diff --git a/doomclassic/doom/p_setup.h b/doomclassic/doom/p_setup.h new file mode 100644 index 00000000..5daf5c31 --- /dev/null +++ b/doomclassic/doom/p_setup.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_SETUP__ +#define __P_SETUP__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// NOT called by W_Ticker. Fixme. +void +P_SetupLevel +( int episode, + int map, + int playermask, + skill_t skill); + +// Called by startup code. +void P_Init (void); + +#endif + diff --git a/doomclassic/doom/p_sight.cpp b/doomclassic/doom/p_sight.cpp new file mode 100644 index 00000000..982625de --- /dev/null +++ b/doomclassic/doom/p_sight.cpp @@ -0,0 +1,348 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "doomdef.h" + +#include "i_system.h" +#include "p_local.h" + +// State. +#include "r_state.h" + +// +// P_CheckSight +// + + + + +// +// P_DivlineSide +// Returns side 0 (front), 1 (back), or 2 (on). +// +int +P_DivlineSide +( fixed_t x, + fixed_t y, + divline_t* node ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!node->dx) + { + if (x==node->x) + return 2; + + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + + if (!node->dy) + { + if (x==node->y) + return 2; + + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + left = (node->dy>>FRACBITS) * (dx>>FRACBITS); + right = (dy>>FRACBITS) * (node->dx>>FRACBITS); + + if (right < left) + return 0; // front side + + if (left == right) + return 2; + return 1; // back side +} + + +// +// P_InterceptVector2 +// Returns the fractional intercept point +// along the first divline. +// This is only called by the addthings and addlines traversers. +// +fixed_t +P_InterceptVector2 +( divline_t* v2, + divline_t* v1 ) +{ + fixed_t frac; + fixed_t num; + fixed_t den; + + den = FixedMul (v1->dy>>8,v2->dx) - FixedMul(v1->dx>>8,v2->dy); + + if (den == 0) + return 0; + // I_Error ("P_InterceptVector: parallel"); + + num = FixedMul ( (v1->x - v2->x)>>8 ,v1->dy) + + FixedMul ( (v2->y - v1->y)>>8 , v1->dx); + frac = FixedDiv (num , den); + + return frac; +} + +// +// P_CrossSubsector +// Returns true +// if ::g->strace crosses the given subsector successfully. +// +qboolean P_CrossSubsector (int num) +{ + seg_t* seg; + line_t* line; + int s1; + int s2; + int count; + subsector_t* sub; + sector_t* front; + sector_t* back; + fixed_t psight_opentop; + fixed_t psight_openbottom; + divline_t divl; + vertex_t* v1; + vertex_t* v2; + fixed_t frac; + fixed_t slope; + +#ifdef RANGECHECK + if (num>=::g->numsubsectors) + I_Error ("P_CrossSubsector: ss %i with numss = %i", + num, + ::g->numsubsectors); +#endif + + sub = &::g->subsectors[num]; + + // check ::g->lines + count = sub->numlines; + seg = &::g->segs[sub->firstline]; + + for ( ; count ; seg++, count--) + { + line = seg->linedef; + + // allready checked other side? + if (line->validcount == ::g->validcount) + continue; + + line->validcount = ::g->validcount; + + v1 = line->v1; + v2 = line->v2; + s1 = P_DivlineSide (v1->x,v1->y, &::g->strace); + s2 = P_DivlineSide (v2->x, v2->y, &::g->strace); + + // line isn't crossed? + if (s1 == s2) + continue; + + divl.x = v1->x; + divl.y = v1->y; + divl.dx = v2->x - v1->x; + divl.dy = v2->y - v1->y; + s1 = P_DivlineSide (::g->strace.x, ::g->strace.y, &divl); + s2 = P_DivlineSide (::g->t2x, ::g->t2y, &divl); + + // line isn't crossed? + if (s1 == s2) + continue; + + // stop because it is not two sided anyway + // might do this after updating validcount? + if ( !(line->flags & ML_TWOSIDED) ) + return false; + + // crosses a two sided line + front = seg->frontsector; + back = seg->backsector; + + // no wall to block sight with? + if (front->floorheight == back->floorheight + && front->ceilingheight == back->ceilingheight) + continue; + + // possible occluder + // because of ceiling height differences + if (front->ceilingheight < back->ceilingheight) + psight_opentop = front->ceilingheight; + else + psight_opentop = back->ceilingheight; + + // because of ceiling height differences + if (front->floorheight > back->floorheight) + psight_openbottom = front->floorheight; + else + psight_openbottom = back->floorheight; + + // quick test for totally closed doors + if (psight_openbottom >= psight_opentop) + return false; // stop + + frac = P_InterceptVector2 (&::g->strace, &divl); + + if (front->floorheight != back->floorheight) + { + slope = FixedDiv (psight_openbottom - ::g->sightzstart , frac); + if (slope > ::g->bottomslope) + ::g->bottomslope = slope; + } + + if (front->ceilingheight != back->ceilingheight) + { + slope = FixedDiv (psight_opentop - ::g->sightzstart , frac); + if (slope < ::g->topslope) + ::g->topslope = slope; + } + + if (::g->topslope <= ::g->bottomslope) + return false; // stop + } + // passed the subsector ok + return true; +} + + + +// +// P_CrossBSPNode +// Returns true +// if ::g->strace crosses the given node successfully. +// +qboolean P_CrossBSPNode (int bspnum) +{ + node_t* bsp; + int side; + + if (bspnum & NF_SUBSECTOR) + { + if (bspnum == -1) + return P_CrossSubsector (0); + else + return P_CrossSubsector (bspnum&(~NF_SUBSECTOR)); + } + + bsp = &::g->nodes[bspnum]; + + // decide which side the start point is on + side = P_DivlineSide (::g->strace.x, ::g->strace.y, (divline_t *)bsp); + if (side == 2) + side = 0; // an "on" should cross both ::g->sides + + // cross the starting side + if (!P_CrossBSPNode (bsp->children[side]) ) + return false; + + // the partition plane is crossed here + if (side == P_DivlineSide (::g->t2x, ::g->t2y,(divline_t *)bsp)) + { + // the line doesn't touch the other side + return true; + } + + // cross the ending side + return P_CrossBSPNode (bsp->children[side^1]); +} + + +// +// P_CheckSight +// Returns true +// if a straight line between t1 and t2 is unobstructed. +// Uses REJECT. +// +qboolean +P_CheckSight +( mobj_t* t1, + mobj_t* t2 ) +{ + int s1; + int s2; + int pnum; + int bytenum; + int bitnum; + + // First check for trivial rejection. + + // Determine subsector entries in REJECT table. + s1 = (t1->subsector->sector - ::g->sectors); + s2 = (t2->subsector->sector - ::g->sectors); + pnum = s1*::g->numsectors + s2; + bytenum = pnum>>3; + bitnum = 1 << (pnum&7); + + // Check in REJECT table. + if (::g->rejectmatrix[bytenum]&bitnum) + { + ::g->sightcounts[0]++; + + // can't possibly be connected + return false; + } + + // An unobstructed LOS is possible. + // Now look from eyes of t1 to any part of t2. + ::g->sightcounts[1]++; + + ::g->validcount++; + + ::g->sightzstart = t1->z + t1->height - (t1->height>>2); + ::g->topslope = (t2->z+t2->height) - ::g->sightzstart; + ::g->bottomslope = (t2->z) - ::g->sightzstart; + + ::g->strace.x = t1->x; + ::g->strace.y = t1->y; + ::g->t2x = t2->x; + ::g->t2y = t2->y; + ::g->strace.dx = t2->x - t1->x; + ::g->strace.dy = t2->y - t1->y; + + // the head node is the last node output + return P_CrossBSPNode (::g->numnodes-1); +} + + + diff --git a/doomclassic/doom/p_spec.cpp b/doomclassic/doom/p_spec.cpp new file mode 100644 index 00000000..cc048115 --- /dev/null +++ b/doomclassic/doom/p_spec.cpp @@ -0,0 +1,1424 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" +#include "Main.h" + +#include + +#include "doomdef.h" +#include "doomstat.h" + +#include "i_system.h" +#include "z_zone.h" +#include "m_argv.h" +#include "m_random.h" +#include "w_wad.h" + +#include "r_local.h" +#include "p_local.h" + +#include "g_game.h" + +#include "s_sound.h" + +// State. +#include "r_state.h" + +// Data. +#include "sounds.h" + +#include "../../neo/d3xp/Game_Local.h" + +// +// Animating textures and planes +// There is another anim_t used in wi_stuff, unrelated. BLAH! +// we now use anim_t2 +// + +// +// source animation definition +// + + + + + +// +// P_InitPicAnims +// + +// Floor/ceiling animation sequences, +// defined by first and last frame, +// i.e. the flat (64x64 tile) name to +// be used. +// The full animation sequence is given +// using all the flats between the start +// and end entry, in the order found in +// the WAD file. +// +const animdef_t animdefs[] = +{ + {false, "NUKAGE3", "NUKAGE1", 8}, + {false, "FWATER4", "FWATER1", 8}, + {false, "SWATER4", "SWATER1", 8}, + {false, "LAVA4", "LAVA1", 8}, + {false, "BLOOD3", "BLOOD1", 8}, + + // DOOM II flat animations. + {false, "RROCK08", "RROCK05", 8}, + {false, "SLIME04", "SLIME01", 8}, + {false, "SLIME08", "SLIME05", 8}, + {false, "SLIME12", "SLIME09", 8}, + + {true, "BLODGR4", "BLODGR1", 8}, + {true, "SLADRIP3", "SLADRIP1", 8}, + + {true, "BLODRIP4", "BLODRIP1", 8}, + {true, "FIREWALL", "FIREWALA", 8}, + {true, "GSTFONT3", "GSTFONT1", 8}, + {true, "FIRELAVA", "FIRELAV3", 8}, + {true, "FIREMAG3", "FIREMAG1", 8}, + {true, "FIREBLU2", "FIREBLU1", 8}, + {true, "ROCKRED3", "ROCKRED1", 8}, + + {true, "BFALL4", "BFALL1", 8}, + {true, "SFALL4", "SFALL1", 8}, + {true, "WFALL4", "WFALL1", 8}, + {true, "DBRAIN4", "DBRAIN1", 8}, + + {-1} +}; + + + +// +// Animating line specials +// + + + + +void P_InitPicAnims (void) +{ + int i; + + + // Init animation + ::g->lastanim = ::g->anims; + for (i=0 ; animdefs[i].istexture != (qboolean)-1 ; i++) + { + if (animdefs[i].istexture) + { + // different episode ? + if (R_CheckTextureNumForName(animdefs[i].startname) == -1) + continue; + + ::g->lastanim->picnum = R_TextureNumForName (animdefs[i].endname); + ::g->lastanim->basepic = R_TextureNumForName (animdefs[i].startname); + } + else + { + if (W_CheckNumForName(animdefs[i].startname) == -1) + continue; + + ::g->lastanim->picnum = R_FlatNumForName (animdefs[i].endname); + ::g->lastanim->basepic = R_FlatNumForName (animdefs[i].startname); + } + + ::g->lastanim->istexture = animdefs[i].istexture; + ::g->lastanim->numpics = ::g->lastanim->picnum - ::g->lastanim->basepic + 1; + + if (::g->lastanim->numpics < 2) + I_Error ("P_InitPicAnims: bad cycle from %s to %s", + animdefs[i].startname, + animdefs[i].endname); + + ::g->lastanim->speed = animdefs[i].speed; + ::g->lastanim++; + } + +} + + + +// +// UTILITIES +// + + + +// +// getSide() +// Will return a side_t* +// given the number of the current sector, +// the line number, and the side (0/1) that you want. +// +side_t* +getSide +( int currentSector, + int line, + int side ) +{ + return &::g->sides[ (::g->sectors[currentSector].lines[line])->sidenum[side] ]; +} + + +// +// getSector() +// Will return a sector_t* +// given the number of the current sector, +// the line number and the side (0/1) that you want. +// +sector_t* +getSector +( int currentSector, + int line, + int side ) +{ + return ::g->sides[ (::g->sectors[currentSector].lines[line])->sidenum[side] ].sector; +} + + +// +// twoSided() +// Given the sector number and the line number, +// it will tell you whether the line is two-sided or not. +// +int +twoSided +( int sector, + int line ) +{ + return (::g->sectors[sector].lines[line])->flags & ML_TWOSIDED; +} + + + + +// +// getNextSector() +// Return sector_t * of sector next to current. +// NULL if not two-sided line +// +sector_t* +getNextSector +( line_t* line, + sector_t* sec ) +{ + if (!(line->flags & ML_TWOSIDED)) + return NULL; + + if (line->frontsector == sec) + return line->backsector; + + return line->frontsector; +} + + + +// +// P_FindLowestFloorSurrounding() +// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS +// +fixed_t P_FindLowestFloorSurrounding(sector_t* sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t floor = sec->floorheight; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->floorheight < floor) + floor = other->floorheight; + } + return floor; +} + + + +// +// P_FindHighestFloorSurrounding() +// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS +// +fixed_t P_FindHighestFloorSurrounding(sector_t *sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t floor = -500*FRACUNIT; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->floorheight > floor) + floor = other->floorheight; + } + return floor; +} + + + +// +// P_FindNextHighestFloor +// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS +// Note: this should be doable w/o a fixed array. + +// 20 adjoining ::g->sectors max! + +fixed_t +P_FindNextHighestFloor +( sector_t* sec, + int currentheight ) +{ + int i; + int h; + int min; + line_t* check; + sector_t* other; + fixed_t height = currentheight; + + + fixed_t heightlist[MAX_ADJOINING_SECTORS]; + + for (i=0, h=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->floorheight > height) + heightlist[h++] = other->floorheight; + + // Check for overflow. Exit. + if ( h >= MAX_ADJOINING_SECTORS ) + { + I_PrintfE("Sector with more than 20 adjoining sectors\n" ); + break; + } + } + + // Find lowest height in list + if (!h) + return currentheight; + + min = heightlist[0]; + + // Range checking? + for (i = 1;i < h;i++) + if (heightlist[i] < min) + min = heightlist[i]; + + return min; +} + + +// +// FIND LOWEST CEILING IN THE SURROUNDING SECTORS +// +fixed_t +P_FindLowestCeilingSurrounding(sector_t* sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t height = MAXINT; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->ceilingheight < height) + height = other->ceilingheight; + } + return height; +} + + +// +// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS +// +fixed_t P_FindHighestCeilingSurrounding(sector_t* sec) +{ + int i; + line_t* check; + sector_t* other; + fixed_t height = 0; + + for (i=0 ;i < sec->linecount ; i++) + { + check = sec->lines[i]; + other = getNextSector(check,sec); + + if (!other) + continue; + + if (other->ceilingheight > height) + height = other->ceilingheight; + } + return height; +} + + + +// +// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO +// +int +P_FindSectorFromLineTag +( line_t* line, + int start ) +{ + int i; + + for (i = start+1; i < ::g->numsectors; i++) + if (::g->sectors[i].tag == line->tag) + return i; + + return -1; +} + + + + +// +// Find minimum light from an adjacent sector +// +int +P_FindMinSurroundingLight +( sector_t* sector, + int max ) +{ + int i; + int min; + line_t* line; + sector_t* check; + + min = max; + for (i=0 ; i < sector->linecount ; i++) + { + line = sector->lines[i]; + check = getNextSector(line,sector); + + if (!check) + continue; + + if (check->lightlevel < min) + min = check->lightlevel; + } + return min; +} + + + +// +// EVENTS +// Events are operations triggered by using, crossing, +// or shooting special ::g->lines, or by timed thinkers. +// + +// +// P_CrossSpecialLine - TRIGGER +// Called every time a thing origin is about +// to cross a line with a non 0 special. +// +void +P_CrossSpecialLine +( int linenum, + int side, + mobj_t* thing ) +{ + line_t* line; + int ok; + + line = &::g->lines[linenum]; + + // Triggers that other things can activate + if (!thing->player) + { + // Things that should NOT trigger specials... + switch(thing->type) + { + case MT_ROCKET: + case MT_PLASMA: + case MT_BFG: + case MT_TROOPSHOT: + case MT_HEADSHOT: + case MT_BRUISERSHOT: + return; + break; + + default: break; + } + + ok = 0; + switch(line->special) + { + case 39: // TELEPORT TRIGGER + case 97: // TELEPORT RETRIGGER + case 125: // TELEPORT MONSTERONLY TRIGGER + case 126: // TELEPORT MONSTERONLY RETRIGGER + case 4: // RAISE DOOR + case 10: // PLAT DOWN-WAIT-UP-STAY TRIGGER + case 88: // PLAT DOWN-WAIT-UP-STAY RETRIGGER + ok = 1; + break; + } + if (!ok) + return; + } + + + // Note: could use some const's here. + switch (line->special) + { + // TRIGGERS. + // All from here to RETRIGGERS. + case 2: + // Open Door + EV_DoDoor(line,opened); + line->special = 0; + break; + + case 3: + // Close Door + EV_DoDoor(line,closed); + line->special = 0; + break; + + case 4: + // Raise Door + EV_DoDoor(line,normal); + line->special = 0; + break; + + case 5: + // Raise Floor + EV_DoFloor(line,raiseFloor); + line->special = 0; + break; + + case 6: + // Fast Ceiling Crush & Raise + EV_DoCeiling(line,fastCrushAndRaise); + line->special = 0; + break; + + case 8: + // Build Stairs + EV_BuildStairs(line,build8); + line->special = 0; + break; + + case 10: + // PlatDownWaitUp + EV_DoPlat(line,downWaitUpStay,0); + line->special = 0; + break; + + case 12: + // Light Turn On - brightest near + EV_LightTurnOn(line,0); + line->special = 0; + break; + + case 13: + // Light Turn On 255 + EV_LightTurnOn(line,255); + line->special = 0; + break; + + case 16: + // Close Door 30 + EV_DoDoor(line,close30ThenOpen); + line->special = 0; + break; + + case 17: + // Start Light Strobing + EV_StartLightStrobing(line); + line->special = 0; + break; + + case 19: + // Lower Floor + EV_DoFloor(line,lowerFloor); + line->special = 0; + break; + + case 22: + // Raise floor to nearest height and change texture + EV_DoPlat(line,raiseToNearestAndChange,0); + line->special = 0; + break; + + case 25: + // Ceiling Crush and Raise + EV_DoCeiling(line,crushAndRaise); + line->special = 0; + break; + + case 30: + // Raise floor to shortest texture height + // on either side of ::g->lines. + EV_DoFloor(line,raiseToTexture); + line->special = 0; + break; + + case 35: + // Lights Very Dark + EV_LightTurnOn(line,35); + line->special = 0; + break; + + case 36: + // Lower Floor (TURBO) + EV_DoFloor(line,turboLower); + line->special = 0; + break; + + case 37: + // LowerAndChange + EV_DoFloor(line,lowerAndChange); + line->special = 0; + break; + + case 38: + // Lower Floor To Lowest + EV_DoFloor( line, lowerFloorToLowest ); + line->special = 0; + break; + + case 39: + // TELEPORT! + EV_Teleport( line, side, thing ); + line->special = 0; + break; + + case 40: + // RaiseCeilingLowerFloor + EV_DoCeiling( line, raiseToHighest ); + EV_DoFloor( line, lowerFloorToLowest ); + line->special = 0; + break; + + case 44: + // Ceiling Crush + EV_DoCeiling( line, lowerAndCrush ); + line->special = 0; + break; + + case 52: + // EXIT! + // DHM - Nerve :: Don't exit level in death match, timelimit and fraglimit only + if ( !::g->deathmatch && ::g->gameaction != ga_completed ) { + G_ExitLevel(); + } + break; + + case 53: + // Perpetual Platform Raise + EV_DoPlat(line,perpetualRaise,0); + line->special = 0; + break; + + case 54: + // Platform Stop + EV_StopPlat(line); + line->special = 0; + break; + + case 56: + // Raise Floor Crush + EV_DoFloor(line,raiseFloorCrush); + line->special = 0; + break; + + case 57: + // Ceiling Crush Stop + EV_CeilingCrushStop(line); + line->special = 0; + break; + + case 58: + // Raise Floor 24 + EV_DoFloor(line,raiseFloor24); + line->special = 0; + break; + + case 59: + // Raise Floor 24 And Change + EV_DoFloor(line,raiseFloor24AndChange); + line->special = 0; + break; + + case 104: + // Turn lights off in sector(tag) + EV_TurnTagLightsOff(line); + line->special = 0; + break; + + case 108: + // Blazing Door Raise (faster than TURBO!) + EV_DoDoor (line,blazeRaise); + line->special = 0; + break; + + case 109: + // Blazing Door Open (faster than TURBO!) + EV_DoDoor (line,blazeOpen); + line->special = 0; + break; + + case 100: + // Build Stairs Turbo 16 + EV_BuildStairs(line,turbo16); + line->special = 0; + break; + + case 110: + // Blazing Door Close (faster than TURBO!) + EV_DoDoor (line,blazeClose); + line->special = 0; + break; + + case 119: + // Raise floor to nearest surr. floor + EV_DoFloor(line,raiseFloorToNearest); + line->special = 0; + break; + + case 121: + // Blazing PlatDownWaitUpStay + EV_DoPlat(line,blazeDWUS,0); + line->special = 0; + break; + + case 124: + // Secret EXIT + if ( !::g->deathmatch && ::g->gameaction != ga_completed ) { + G_SecretExitLevel (); + } + break; + + case 125: + // TELEPORT MonsterONLY + if (!thing->player) + { + EV_Teleport( line, side, thing ); + line->special = 0; + } + break; + + case 130: + // Raise Floor Turbo + EV_DoFloor(line,raiseFloorTurbo); + line->special = 0; + break; + + case 141: + // Silent Ceiling Crush & Raise + EV_DoCeiling(line,silentCrushAndRaise); + line->special = 0; + break; + + // RETRIGGERS. All from here till end. + case 72: + // Ceiling Crush + EV_DoCeiling( line, lowerAndCrush ); + break; + + case 73: + // Ceiling Crush and Raise + EV_DoCeiling(line,crushAndRaise); + break; + + case 74: + // Ceiling Crush Stop + EV_CeilingCrushStop(line); + break; + + case 75: + // Close Door + EV_DoDoor(line,closed); + break; + + case 76: + // Close Door 30 + EV_DoDoor(line,close30ThenOpen); + break; + + case 77: + // Fast Ceiling Crush & Raise + EV_DoCeiling(line,fastCrushAndRaise); + break; + + case 79: + // Lights Very Dark + EV_LightTurnOn(line,35); + break; + + case 80: + // Light Turn On - brightest near + EV_LightTurnOn(line,0); + break; + + case 81: + // Light Turn On 255 + EV_LightTurnOn(line,255); + break; + + case 82: + // Lower Floor To Lowest + EV_DoFloor( line, lowerFloorToLowest ); + break; + + case 83: + // Lower Floor + EV_DoFloor(line,lowerFloor); + break; + + case 84: + // LowerAndChange + EV_DoFloor(line,lowerAndChange); + break; + + case 86: + // Open Door + EV_DoDoor(line,opened); + break; + + case 87: + // Perpetual Platform Raise + EV_DoPlat(line,perpetualRaise,0); + break; + + case 88: + // PlatDownWaitUp + EV_DoPlat(line,downWaitUpStay,0); + break; + + case 89: + // Platform Stop + EV_StopPlat(line); + break; + + case 90: + // Raise Door + EV_DoDoor(line,normal); + break; + + case 91: + // Raise Floor + EV_DoFloor(line,raiseFloor); + break; + + case 92: + // Raise Floor 24 + EV_DoFloor(line,raiseFloor24); + break; + + case 93: + // Raise Floor 24 And Change + EV_DoFloor(line,raiseFloor24AndChange); + break; + + case 94: + // Raise Floor Crush + EV_DoFloor(line,raiseFloorCrush); + break; + + case 95: + // Raise floor to nearest height + // and change texture. + EV_DoPlat(line,raiseToNearestAndChange,0); + break; + + case 96: + // Raise floor to shortest texture height + // on either side of ::g->lines. + EV_DoFloor(line,raiseToTexture); + break; + + case 97: + // TELEPORT! + EV_Teleport( line, side, thing ); + break; + + case 98: + // Lower Floor (TURBO) + EV_DoFloor(line,turboLower); + break; + + case 105: + // Blazing Door Raise (faster than TURBO!) + EV_DoDoor (line,blazeRaise); + break; + + case 106: + // Blazing Door Open (faster than TURBO!) + EV_DoDoor (line,blazeOpen); + break; + + case 107: + // Blazing Door Close (faster than TURBO!) + EV_DoDoor (line,blazeClose); + break; + + case 120: + // Blazing PlatDownWaitUpStay. + EV_DoPlat(line,blazeDWUS,0); + break; + + case 126: + // TELEPORT MonsterONLY. + if (!thing->player) + EV_Teleport( line, side, thing ); + break; + + case 128: + // Raise To Nearest Floor + EV_DoFloor(line,raiseFloorToNearest); + break; + + case 129: + // Raise Floor Turbo + EV_DoFloor(line,raiseFloorTurbo); + break; + } +} + + + +// +// P_ShootSpecialLine - IMPACT SPECIALS +// Called when a thing shoots a special line. +// +void +P_ShootSpecialLine +( mobj_t* thing, + line_t* line ) +{ + int ok; + + // Impacts that other things can activate. + if (!thing->player) + { + ok = 0; + switch(line->special) + { + case 46: + // OPEN DOOR IMPACT + ok = 1; + break; + } + if (!ok) + return; + } + + switch(line->special) + { + case 24: + // RAISE FLOOR + EV_DoFloor(line,raiseFloor); + P_ChangeSwitchTexture(line,0); + break; + + case 46: + // OPEN DOOR + EV_DoDoor(line,opened); + P_ChangeSwitchTexture(line,1); + break; + + case 47: + // RAISE FLOOR NEAR AND CHANGE + EV_DoPlat(line,raiseToNearestAndChange,0); + P_ChangeSwitchTexture(line,0); + break; + } +} + + + +// +// P_PlayerInSpecialSector +// Called every tic frame +// that the player origin is in a special sector +// +void P_PlayerInSpecialSector (player_t* player) +{ + sector_t* sector; + + sector = player->mo->subsector->sector; + + // Falling, not all the way down yet? + if (player->mo->z != sector->floorheight) + return; + + // Has hitten ground. + switch (sector->special) + { + case 5: + // HELLSLIME DAMAGE + if (!player->powers[pw_ironfeet]) + if (!(::g->leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 10); + break; + + case 7: + // NUKAGE DAMAGE + if (!player->powers[pw_ironfeet]) + if (!(::g->leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 5); + break; + + case 16: + // SUPER HELLSLIME DAMAGE + case 4: + // STROBE HURT + if (!player->powers[pw_ironfeet] + || (P_Random()<5) ) + { + if (!(::g->leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 20); + } + break; + + case 9: + // SECRET SECTOR + player->secretcount++; + sector->special = 0; + + + if ( !::g->demoplayback && ( ::g->usergame && !::g->netgame ) ) { + // DHM - Nerve :: Let's give achievements in real time in Doom 2 + if ( !common->IsMultiplayer() ) { + switch( DoomLib::GetGameSKU() ) { + case GAME_SKU_DOOM1_BFG: { + // Removing trophies for DOOM and DOOM II BFG due to point limit. + //gameLocal->UnlockAchievement( Doom1BFG_Trophies::SCOUT_FIND_ANY_SECRET ); + break; + } + case GAME_SKU_DOOM2_BFG: { + //gameLocal->UnlockAchievement( Doom2BFG_Trophies::IMPORTANT_LOOKING_DOOR_FIND_ANY_SECRET ); + idAchievementManager::LocalUser_CompleteAchievement(ACHIEVEMENT_DOOM2_IMPORTANT_LOOKING_DOOR_FIND_ANY_SECRET ); + break; + } + case GAME_SKU_DCC: { + // Not on PC. + //gameLocal->UnlockAchievement( DOOM_ACHIEVEMENT_FIND_SECRET ); + break; + } + default: { + // No unlocks for other SKUs. + break; + } + } + } + } + + + break; + + case 11: + // EXIT SUPER DAMAGE! (for E1M8 finale) + player->cheats &= ~CF_GODMODE; + + if (!(::g->leveltime&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 20); + + if (player->health <= 10) + G_ExitLevel(); + break; + + default: + I_Error ("P_PlayerInSpecialSector: " + "unknown special %i", + sector->special); + break; + }; +} + + + + +// +// P_UpdateSpecials +// Animate planes, scroll walls, etc. +// +int PlayerFrags( int playernum ) { + int frags = 0; + + for( int i=0 ; iplayers[playernum].frags[i]; + } + } + + frags -= ::g->players[playernum].frags[playernum]; + + return frags; +} + +void P_UpdateSpecials (void) +{ + anim_t2* anim; + int pic; + int i; + line_t* line; + + + // LEVEL TIMER + if (::g->levelTimer == true) + { + ::g->levelTimeCount--; + if (!::g->levelTimeCount) + G_ExitLevel(); + } + + // DHM - Nerve :: FRAG COUNT + if ( ::g->deathmatch && ::g->levelFragCount > 0 ) { + bool fragCountHit = false; + + for ( int i=0; iplayeringame[i] ) { + if ( PlayerFrags(i) >= ::g->levelFragCount ) { + fragCountHit = true; + } + } + } + + if ( fragCountHit ) { + G_ExitLevel(); + } + } + + // ANIMATE FLATS AND TEXTURES GLOBALLY + for (anim = ::g->anims ; anim < ::g->lastanim ; anim++) + { + for (i=anim->basepic ; ibasepic+anim->numpics ; i++) + { + pic = anim->basepic + ( (::g->leveltime/anim->speed + i)%anim->numpics ); + if (anim->istexture) + ::g->texturetranslation[i] = pic; + else + ::g->flattranslation[i] = pic; + } + } + + + // ANIMATE LINE SPECIALS + for (i = 0; i < ::g->numlinespecials; i++) + { + line = ::g->linespeciallist[i]; + switch(line->special) + { + case 48: + // EFFECT FIRSTCOL SCROLL + + ::g->sides[line->sidenum[0]].textureoffset += FRACUNIT; + break; + } + } + + + // DO BUTTONS + for (i = 0; i < MAXBUTTONS; i++) + if (::g->buttonlist[i].btimer) + { + ::g->buttonlist[i].btimer--; + if (!::g->buttonlist[i].btimer) + { + switch(::g->buttonlist[i].where) + { + case top: + ::g->sides[::g->buttonlist[i].line->sidenum[0]].toptexture = + ::g->buttonlist[i].btexture; + break; + + case middle: + ::g->sides[::g->buttonlist[i].line->sidenum[0]].midtexture = + ::g->buttonlist[i].btexture; + break; + + case bottom: + ::g->sides[::g->buttonlist[i].line->sidenum[0]].bottomtexture = + ::g->buttonlist[i].btexture; + break; + } + S_StartSound((mobj_t *)&::g->buttonlist[i].soundorg,sfx_swtchn); + memset(&::g->buttonlist[i],0,sizeof(button_t)); + } + } + +} + + + +// +// Special Stuff that can not be categorized +// +int EV_DoDonut(line_t* line) +{ + sector_t* s1; + sector_t* s2; + sector_t* s3; + int secnum; + int rtn; + int i; + floormove_t* floor; + + secnum = -1; + rtn = 0; + while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0) + { + s1 = &::g->sectors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (s1->specialdata) + continue; + + rtn = 1; + s2 = getNextSector(s1->lines[0],s1); + for (i = 0;i < s2->linecount;i++) + { + if ((!(s2->lines[i]->flags & ML_TWOSIDED)) || + (s2->lines[i]->backsector == s1)) + continue; + s3 = s2->lines[i]->backsector; + + // Spawn rising slime + floor = (floormove_t*)DoomLib::Z_Malloc (sizeof(*floor), PU_LEVEL, 0); + P_AddThinker (&floor->thinker); + s2->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->type = donutRaise; + floor->crush = false; + floor->direction = 1; + floor->sector = s2; + floor->speed = FLOORSPEED / 2; + floor->texture = s3->floorpic; + floor->newspecial = 0; + floor->floordestheight = s3->floorheight; + + // Spawn lowering donut-hole + floor = (floormove_t*)DoomLib::Z_Malloc (sizeof(*floor), PU_LEVEL, 0); + P_AddThinker (&floor->thinker); + s1->specialdata = floor; + floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor; + floor->type = lowerFloor; + floor->crush = false; + floor->direction = -1; + floor->sector = s1; + floor->speed = FLOORSPEED / 2; + floor->floordestheight = s3->floorheight; + break; + } + } + return rtn; +} + + + +// +// SPECIAL SPAWNING +// + +// +// P_SpawnSpecials +// After the map has been loaded, scan for specials +// that spawn thinkers +// + + +// Parses command line parameters. +void P_SpawnSpecials (void) +{ + sector_t* sector; + int i; + int episode; + + episode = 1; + if (W_CheckNumForName("texture2") >= 0) + episode = 2; + + + // See if -TIMER needs to be used. + ::g->levelTimer = false; + + i = M_CheckParm("-avg"); + if (i && ::g->deathmatch) + { + ::g->levelTimer = true; + ::g->levelTimeCount = 20 * 60 * TICRATE; + } + + //i = M_CheckParm("-timer"); + //if (i && ::g->deathmatch) +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + const int timeLimit = session->GetActingGameStateLobbyBase().GetMatchParms().gameTimeLimit; +#else + const int timeLimit = 0; +#endif + if (timeLimit != 0 && g->deathmatch) + { + int time; + //time = atoi(::g->myargv[i+1]) * 60 * 35; + time = timeLimit * 60 * TICRATE; + ::g->levelTimer = true; + ::g->levelTimeCount = time; + } + + //i = M_CheckParm("-fraglimit"); + //if (i && ::g->deathmatch) +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + const int fragLimit = gameLocal->GetMatchParms().GetScoreLimit(); +#else + const int fragLimit = 0; +#endif + if (fragLimit != 0 && ::g->deathmatch) + { + //::g->levelFragCount = atoi(::g->myargv[i+1]); + ::g->levelFragCount = fragLimit; + } else { + ::g->levelFragCount = 0; + } + + // Init special SECTORs. + sector = ::g->sectors; + for (i=0 ; i < ::g->numsectors ; i++, sector++) + { + if (!sector->special) + continue; + + switch (sector->special) + { + case 1: + // FLICKERING LIGHTS + P_SpawnLightFlash (sector); + break; + + case 2: + // STROBE FAST + P_SpawnStrobeFlash(sector,FASTDARK,0); + break; + + case 3: + // STROBE SLOW + P_SpawnStrobeFlash(sector,SLOWDARK,0); + break; + + case 4: + // STROBE FAST/DEATH SLIME + P_SpawnStrobeFlash(sector,FASTDARK,0); + sector->special = 4; + break; + + case 8: + // GLOWING LIGHT + P_SpawnGlowingLight(sector); + break; + case 9: + // SECRET SECTOR + ::g->totalsecret++; + break; + + case 10: + // DOOR CLOSE IN 30 SECONDS + P_SpawnDoorCloseIn30 (sector); + break; + + case 12: + // SYNC STROBE SLOW + P_SpawnStrobeFlash (sector, SLOWDARK, 1); + break; + + case 13: + // SYNC STROBE FAST + P_SpawnStrobeFlash (sector, FASTDARK, 1); + break; + + case 14: + // DOOR RAISE IN 5 MINUTES + P_SpawnDoorRaiseIn5Mins (sector, i); + break; + + case 17: + P_SpawnFireFlicker(sector); + break; + } + } + + + // Init line EFFECTs + ::g->numlinespecials = 0; + for (i = 0;i < ::g->numlines; i++) + { + switch(::g->lines[i].special) + { + case 48: + // EFFECT FIRSTCOL SCROLL+ + ::g->linespeciallist[::g->numlinespecials] = &::g->lines[i]; + ::g->numlinespecials++; + break; + } + } + + + // Init other misc stuff + for (i = 0;i < MAXCEILINGS;i++) + ::g->activeceilings[i] = NULL; + + for (i = 0;i < MAXPLATS;i++) + ::g->activeplats[i] = NULL; + + for (i = 0;i < MAXBUTTONS;i++) + memset(&::g->buttonlist[i],0,sizeof(button_t)); + + // UNUSED: no horizonal sliders. + // P_InitSlidingDoorFrames(); +} + diff --git a/doomclassic/doom/p_spec.h b/doomclassic/doom/p_spec.h new file mode 100644 index 00000000..8e99fc67 --- /dev/null +++ b/doomclassic/doom/p_spec.h @@ -0,0 +1,647 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_SPEC__ +#define __P_SPEC__ + + +// +// End-level timer (-TIMER option) +// +extern qboolean levelTimer; +extern int levelTimeCount; + + +// Define values for map objects +#define MO_TELEPORTMAN 14 + + +// at game start +void P_InitPicAnims (void); + +// at map load +void P_SpawnSpecials (void); + +// every tic +void P_UpdateSpecials (void); + +// when needed +qboolean +P_UseSpecialLine +( mobj_t* thing, + line_t* line, + int side ); + +void +P_ShootSpecialLine +( mobj_t* thing, + line_t* line ); + +void +P_CrossSpecialLine +( int linenum, + int side, + mobj_t* thing ); + +void P_PlayerInSpecialSector (player_t* player); + +int +twoSided +( int sector, + int line ); + +sector_t* +getSector +( int currentSector, + int line, + int side ); + +side_t* +getSide +( int currentSector, + int line, + int side ); + +fixed_t P_FindLowestFloorSurrounding(sector_t* sec); +fixed_t P_FindHighestFloorSurrounding(sector_t* sec); + +fixed_t +P_FindNextHighestFloor +( sector_t* sec, + int currentheight ); + +fixed_t P_FindLowestCeilingSurrounding(sector_t* sec); +fixed_t P_FindHighestCeilingSurrounding(sector_t* sec); + +int +P_FindSectorFromLineTag +( line_t* line, + int start ); + +int +P_FindMinSurroundingLight +( sector_t* sector, + int max ); + +sector_t* +getNextSector +( line_t* line, + sector_t* sec ); + + +// +// SPECIAL +// +int EV_DoDonut(line_t* line); + + + +// +// P_LIGHTS +// +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int count; + int maxlight; + int minlight; + +} fireflicker_t; + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int count; + int maxlight; + int minlight; + int maxtime; + int mintime; + +} lightflash_t; + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int count; + int minlight; + int maxlight; + int darktime; + int brighttime; + +} strobe_t; + + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + int minlight; + int maxlight; + int direction; + +} glow_t; + + +#define GLOWSPEED 8 +#define STROBEBRIGHT 5 +#define FASTDARK 15 +#define SLOWDARK 35 + +void T_FireFlicker (fireflicker_t* flick); +void P_SpawnFireFlicker (sector_t* sector); +void T_LightFlash (lightflash_t* flash); +void P_SpawnLightFlash (sector_t* sector); +void T_StrobeFlash (strobe_t* flash); + +void +P_SpawnStrobeFlash +( sector_t* sector, + int fastOrSlow, + int inSync ); + +void EV_StartLightStrobing(line_t* line); +void EV_TurnTagLightsOff(line_t* line); + +void +EV_LightTurnOn +( line_t* line, + int bright ); + +void T_Glow(glow_t* g); +void P_SpawnGlowingLight(sector_t* sector); + + + + +// +// P_SWITCH +// +typedef struct +{ + char name1[9]; + char name2[9]; + short episode; + +} switchlist_t; + + +typedef enum +{ + top, + middle, + bottom + +} bwhere_e; + + +typedef struct +{ + line_t* line; + bwhere_e where; + int btexture; + int btimer; + union { + mobj_t * soundorg; + degenmobj_t * degensoundorg; + }; +} button_t; + + + + + // max # of wall switches in a level +#define MAXSWITCHES 50 + + // 4 players, 4 buttons each at once, max. +#define MAXBUTTONS 16 + + // 1 second, in ticks. +#define BUTTONTIME TICRATE + +extern button_t buttonlist[MAXBUTTONS]; + +void +P_ChangeSwitchTexture +( line_t* line, + int useAgain ); + +void P_InitSwitchList(void); + + +// +// P_PLATS +// +typedef enum +{ + up, + down, + waiting, + in_stasis + +} plat_e; + + + +typedef enum +{ + perpetualRaise, + downWaitUpStay, + raiseAndChange, + raiseToNearestAndChange, + blazeDWUS + +} plattype_e; + + + +typedef struct +{ + thinker_t thinker; + sector_t* sector; + fixed_t speed; + fixed_t low; + fixed_t high; + int wait; + int count; + plat_e status; + plat_e oldstatus; + qboolean crush; + int tag; + plattype_e type; + +} plat_t; + + + +#define PLATWAIT 3 +#define PLATSPEED FRACUNIT +#define MAXPLATS 30 + + +extern plat_t* activeplats[MAXPLATS]; + +void T_PlatRaise(plat_t* plat); + +int +EV_DoPlat +( line_t* line, + plattype_e type, + int amount ); + +void P_AddActivePlat(plat_t* plat); +void P_RemoveActivePlat(plat_t* plat); +void EV_StopPlat(line_t* line); +void P_ActivateInStasis(int tag); + + +// +// P_DOORS +// +typedef enum +{ + normal, + close30ThenOpen, + closed, + opened, + raiseIn5Mins, + blazeRaise, + blazeOpen, + blazeClose + +} vldoor_e; + + + +typedef struct +{ + thinker_t thinker; + vldoor_e type; + sector_t* sector; + fixed_t topheight; + fixed_t speed; + + // 1 = up, 0 = waiting at top, -1 = down + int direction; + + // tics to wait at the top + int topwait; + // (keep in case a door going down is reset) + // when it reaches 0, start going down + int topcountdown; + +} vldoor_t; + + + +#define VDOORSPEED FRACUNIT*2 +#define VDOORWAIT 150 + +void +EV_VerticalDoor +( line_t* line, + mobj_t* thing ); + +int +EV_DoDoor +( line_t* line, + vldoor_e type ); + +int +EV_DoLockedDoor +( line_t* line, + vldoor_e type, + mobj_t* thing ); + +void T_VerticalDoor (vldoor_t* door); +void P_SpawnDoorCloseIn30 (sector_t* sec); + +void +P_SpawnDoorRaiseIn5Mins +( sector_t* sec, + int secnum ); + + + +#if 0 // UNUSED +// +// Sliding doors... +// +typedef enum +{ + sd_opening, + sd_waiting, + sd_closing + +} sd_e; + + + +typedef enum +{ + sdt_openOnly, + sdt_closeOnly, + sdt_openAndClose + +} sdt_e; + + + + +typedef struct +{ + thinker_t thinker; + sdt_e type; + line_t* line; + int frame; + int whichDoorIndex; + int timer; + sector_t* frontsector; + sector_t* backsector; + sd_e status; + +} slidedoor_t; + + + +typedef struct +{ + char frontFrame1[9]; + char frontFrame2[9]; + char frontFrame3[9]; + char frontFrame4[9]; + char backFrame1[9]; + char backFrame2[9]; + char backFrame3[9]; + char backFrame4[9]; + +} slidename_t; + + + +typedef struct +{ + int frontFrames[4]; + int backFrames[4]; + +} slideframe_t; + + + +// how many frames of animation +#define SNUMFRAMES 4 + +#define SDOORWAIT TICRATE*3 +#define SWAITTICS 4 + +// how many diff. types of anims +#define MAXSLIDEDOORS 5 + +void P_InitSlidingDoorFrames(void); + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ); +#endif + + + +// +// P_CEILNG +// +typedef enum +{ + lowerToFloor, + raiseToHighest, + lowerAndCrush, + crushAndRaise, + fastCrushAndRaise, + silentCrushAndRaise + +} ceiling_e; + + + +typedef struct +{ + thinker_t thinker; + ceiling_e type; + sector_t* sector; + fixed_t bottomheight; + fixed_t topheight; + fixed_t speed; + qboolean crush; + + // 1 = up, 0 = waiting, -1 = down + int direction; + + // ID + int tag; + int olddirection; + +} ceiling_t; + + + + + +#define CEILSPEED FRACUNIT +#define CEILWAIT 150 +#define MAXCEILINGS 30 + +extern ceiling_t* activeceilings[MAXCEILINGS]; + +int +EV_DoCeiling +( line_t* line, + ceiling_e type ); + +void T_MoveCeiling (ceiling_t* ceiling); +void P_AddActiveCeiling(ceiling_t* c); +void P_RemoveActiveCeiling(ceiling_t* c); +int EV_CeilingCrushStop(line_t* line); +void P_ActivateInStasisCeiling(line_t* line); + + +// +// P_FLOOR +// +typedef enum +{ + // lower floor to highest surrounding floor + lowerFloor, + + // lower floor to lowest surrounding floor + lowerFloorToLowest, + + // lower floor to highest surrounding floor VERY FAST + turboLower, + + // raise floor to lowest surrounding CEILING + raiseFloor, + + // raise floor to next highest surrounding floor + raiseFloorToNearest, + + // raise floor to shortest height texture around it + raiseToTexture, + + // lower floor to lowest surrounding floor + // and change floorpic + lowerAndChange, + + raiseFloor24, + raiseFloor24AndChange, + raiseFloorCrush, + + // raise to next highest floor, turbo-speed + raiseFloorTurbo, + donutRaise, + raiseFloor512 + +} floor_e; + + + + +typedef enum +{ + build8, // slowly build by 8 + turbo16 // quickly build by 16 + +} stair_e; + + + +typedef struct +{ + thinker_t thinker; + floor_e type; + qboolean crush; + sector_t* sector; + int direction; + int newspecial; + short texture; + fixed_t floordestheight; + fixed_t speed; + +} floormove_t; + + + +#define FLOORSPEED FRACUNIT + +typedef enum +{ + ok, + crushed, + pastdest + +} result_e; + +result_e +T_MovePlane +( sector_t* sector, + fixed_t speed, + fixed_t dest, + qboolean crush, + int floorOrCeiling, + int direction ); + +int +EV_BuildStairs +( line_t* line, + stair_e type ); + +int +EV_DoFloor +( line_t* line, + floor_e floortype ); + +void T_MoveFloor( floormove_t* floor); + +// +// P_TELEPT +// +int +EV_Teleport +( line_t* line, + int side, + mobj_t* thing ); + +#endif + diff --git a/doomclassic/doom/p_switch.cpp b/doomclassic/doom/p_switch.cpp new file mode 100644 index 00000000..894b476b --- /dev/null +++ b/doomclassic/doom/p_switch.cpp @@ -0,0 +1,660 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "i_system.h" +#include "doomdef.h" +#include "p_local.h" + +#include "g_game.h" + +#include "s_sound.h" + +// Data. +#include "sounds.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + + +// +// CHANGE THE TEXTURE OF A WALL SWITCH TO ITS OPPOSITE +// +const switchlist_t alphSwitchList[] = +{ + // Doom shareware episode 1 switches + {"SW1BRCOM", "SW2BRCOM", 1}, + {"SW1BRN1", "SW2BRN1", 1}, + {"SW1BRN2", "SW2BRN2", 1}, + {"SW1BRNGN", "SW2BRNGN", 1}, + {"SW1BROWN", "SW2BROWN", 1}, + {"SW1COMM", "SW2COMM", 1}, + {"SW1COMP", "SW2COMP", 1}, + {"SW1DIRT", "SW2DIRT", 1}, + {"SW1EXIT", "SW2EXIT", 1}, + {"SW1GRAY", "SW2GRAY", 1}, + {"SW1GRAY1", "SW2GRAY1", 1}, + {"SW1METAL", "SW2METAL", 1}, + {"SW1PIPE", "SW2PIPE", 1}, + {"SW1SLAD", "SW2SLAD", 1}, + {"SW1STARG", "SW2STARG", 1}, + {"SW1STON1", "SW2STON1", 1}, + {"SW1STON2", "SW2STON2", 1}, + {"SW1STONE", "SW2STONE", 1}, + {"SW1STRTN", "SW2STRTN", 1}, + + // Doom registered episodes 2&3 switches + {"SW1BLUE", "SW2BLUE", 2}, + {"SW1CMT", "SW2CMT", 2}, + {"SW1GARG", "SW2GARG", 2}, + {"SW1GSTON", "SW2GSTON", 2}, + {"SW1HOT", "SW2HOT", 2}, + {"SW1LION", "SW2LION", 2}, + {"SW1SATYR", "SW2SATYR", 2}, + {"SW1SKIN", "SW2SKIN", 2}, + {"SW1VINE", "SW2VINE", 2}, + {"SW1WOOD", "SW2WOOD", 2}, + + // Doom II switches + {"SW1PANEL", "SW2PANEL", 3}, + {"SW1ROCK", "SW2ROCK", 3}, + {"SW1MET2", "SW2MET2", 3}, + {"SW1WDMET", "SW2WDMET", 3}, + {"SW1BRIK", "SW2BRIK", 3}, + {"SW1MOD1", "SW2MOD1", 3}, + {"SW1ZIM", "SW2ZIM", 3}, + {"SW1STON6", "SW2STON6", 3}, + {"SW1TEK", "SW2TEK", 3}, + {"SW1MARB", "SW2MARB", 3}, + {"SW1SKULL", "SW2SKULL", 3}, + + {"\0", "\0", 0} +}; + + +// +// P_InitSwitchList +// Only called at game initialization. +// +void P_InitSwitchList(void) +{ + int i; + int index; + int episode; + + episode = 1; + + if (::g->gamemode == registered || ::g->gamemode == retail) + episode = 2; + else if ( ::g->gamemode == commercial ) + episode = 3; + + for (index = 0,i = 0;i < MAXSWITCHES;i++) + { + if (!alphSwitchList[i].episode) + { + ::g->numswitches = index/2; + ::g->switchlist[index] = -1; + break; + } + + if (alphSwitchList[i].episode <= episode) + { + #if 0 // UNUSED - debug? + int value; + + if (R_CheckTextureNumForName(alphSwitchList[i].name1) < 0) + { + I_Error("Can't find switch texture '%s'!", + alphSwitchList[i].name1); + continue; + } + + value = R_TextureNumForName(alphSwitchList[i].name1); + #endif + ::g->switchlist[index++] = R_TextureNumForName(alphSwitchList[i].name1); + ::g->switchlist[index++] = R_TextureNumForName(alphSwitchList[i].name2); + } + } +} + + +// +// Start a button counting down till it turns off. +// +void +P_StartButton +( line_t* line, + bwhere_e w, + int texture, + int time ) +{ + int i; + + // See if button is already pressed + for (i = 0;i < MAXBUTTONS;i++) + { + if (::g->buttonlist[i].btimer + && ::g->buttonlist[i].line == line) + { + + return; + } + } + + + + for (i = 0;i < MAXBUTTONS;i++) + { + if (!::g->buttonlist[i].btimer) + { + ::g->buttonlist[i].line = line; + ::g->buttonlist[i].where = w; + ::g->buttonlist[i].btexture = texture; + ::g->buttonlist[i].btimer = time; + ::g->buttonlist[i].degensoundorg = &line->frontsector->soundorg; + return; + } + } + + I_Error("P_StartButton: no button slots left!"); +} + + + + + +// +// Function that changes wall texture. +// Tell it if switch is ok to use again (1=yes, it's a button). +// +void +P_ChangeSwitchTexture +( line_t* line, + int useAgain ) +{ + int texTop; + int texMid; + int texBot; + int i; + int sound; + + if (!useAgain) + line->special = 0; + + texTop = ::g->sides[line->sidenum[0]].toptexture; + texMid = ::g->sides[line->sidenum[0]].midtexture; + texBot = ::g->sides[line->sidenum[0]].bottomtexture; + + sound = sfx_swtchn; + + // EXIT SWITCH? + if (line->special == 11) + sound = sfx_swtchx; + + for (i = 0;i < ::g->numswitches*2;i++) + { + if (::g->switchlist[i] == texTop) + { + S_StartSound(::g->buttonlist->soundorg,sound); + ::g->sides[line->sidenum[0]].toptexture = ::g->switchlist[i^1]; + + if (useAgain) + P_StartButton(line,top,::g->switchlist[i],BUTTONTIME); + + return; + } + else + { + if (::g->switchlist[i] == texMid) + { + S_StartSound(::g->buttonlist->soundorg,sound); + ::g->sides[line->sidenum[0]].midtexture = ::g->switchlist[i^1]; + + if (useAgain) + P_StartButton(line, middle,::g->switchlist[i],BUTTONTIME); + + return; + } + else + { + if (::g->switchlist[i] == texBot) + { + S_StartSound(::g->buttonlist->soundorg,sound); + ::g->sides[line->sidenum[0]].bottomtexture = ::g->switchlist[i^1]; + + if (useAgain) + P_StartButton(line, bottom,::g->switchlist[i],BUTTONTIME); + + return; + } + } + } + } +} + + + + + + +// +// P_UseSpecialLine +// Called when a thing uses a special line. +// Only the front ::g->sides of ::g->lines are usable. +// +qboolean +P_UseSpecialLine +( mobj_t* thing, + line_t* line, + int side ) +{ + + // Err... + // Use the back ::g->sides of VERY SPECIAL ::g->lines... + if (side) + { + switch(line->special) + { + case 124: + // Sliding door open&close + // UNUSED? + break; + + default: + return false; + break; + } + } + + + // Switches that other things can activate. + if (!thing->player) + { + // never open secret doors + if (line->flags & ML_SECRET) + return false; + + switch(line->special) + { + case 1: // MANUAL DOOR RAISE + case 32: // MANUAL BLUE + case 33: // MANUAL RED + case 34: // MANUAL YELLOW + break; + + default: + return false; + break; + } + } + + + // do something + switch (line->special) + { + // MANUALS + case 1: // Vertical Door + case 26: // Blue Door/Locked + case 27: // Yellow Door /Locked + case 28: // Red Door /Locked + + case 31: // Manual door open + case 32: // Blue locked door open + case 33: // Red locked door open + case 34: // Yellow locked door open + + case 117: // Blazing door raise + case 118: // Blazing door open + EV_VerticalDoor (line, thing); + break; + + //UNUSED - Door Slide Open&Close + // case 124: + // EV_SlidingDoor (line, thing); + // break; + + // SWITCHES + case 7: + // Build Stairs + if (EV_BuildStairs(line,build8)) + P_ChangeSwitchTexture(line,0); + break; + + case 9: + // Change Donut + if (EV_DoDonut(line)) + P_ChangeSwitchTexture(line,0); + break; + + case 11: + // Exit level + // DHM - Nerve :: Not in deathmatch, stay in level until timelimit or fraglimit + if ( !::g->deathmatch && ::g->gameaction != ga_completed ) { + P_ChangeSwitchTexture(line,0); + G_ExitLevel (); + } + break; + + case 14: + // Raise Floor 32 and change texture + if (EV_DoPlat(line,raiseAndChange,32)) + P_ChangeSwitchTexture(line,0); + break; + + case 15: + // Raise Floor 24 and change texture + if (EV_DoPlat(line,raiseAndChange,24)) + P_ChangeSwitchTexture(line,0); + break; + + case 18: + // Raise Floor to next highest floor + if (EV_DoFloor(line, raiseFloorToNearest)) + P_ChangeSwitchTexture(line,0); + break; + + case 20: + // Raise Plat next highest floor and change texture + if (EV_DoPlat(line,raiseToNearestAndChange,0)) + P_ChangeSwitchTexture(line,0); + break; + + case 21: + // PlatDownWaitUpStay + if (EV_DoPlat(line,downWaitUpStay,0)) + P_ChangeSwitchTexture(line,0); + break; + + case 23: + // Lower Floor to Lowest + if (EV_DoFloor(line,lowerFloorToLowest)) + P_ChangeSwitchTexture(line,0); + break; + + case 29: + // Raise Door + if (EV_DoDoor(line,normal)) + P_ChangeSwitchTexture(line,0); + break; + + case 41: + // Lower Ceiling to Floor + if (EV_DoCeiling(line,lowerToFloor)) + P_ChangeSwitchTexture(line,0); + break; + + case 71: + // Turbo Lower Floor + if (EV_DoFloor(line,turboLower)) + P_ChangeSwitchTexture(line,0); + break; + + case 49: + // Ceiling Crush And Raise + if (EV_DoCeiling(line,crushAndRaise)) + P_ChangeSwitchTexture(line,0); + break; + + case 50: + // Close Door + if (EV_DoDoor(line,closed)) + P_ChangeSwitchTexture(line,0); + break; + + case 51: + // Secret EXIT + if ( !::g->deathmatch && ::g->gameaction != ga_completed ) { + P_ChangeSwitchTexture(line,0); + G_SecretExitLevel (); + } + break; + + case 55: + // Raise Floor Crush + if (EV_DoFloor(line,raiseFloorCrush)) + P_ChangeSwitchTexture(line,0); + break; + + case 101: + // Raise Floor + if (EV_DoFloor(line,raiseFloor)) + P_ChangeSwitchTexture(line,0); + break; + + case 102: + // Lower Floor to Surrounding floor height + if (EV_DoFloor(line,lowerFloor)) + P_ChangeSwitchTexture(line,0); + break; + + case 103: + // Open Door + if (EV_DoDoor(line,opened)) + P_ChangeSwitchTexture(line,0); + break; + + case 111: + // Blazing Door Raise (faster than TURBO!) + if (EV_DoDoor (line,blazeRaise)) + P_ChangeSwitchTexture(line,0); + break; + + case 112: + // Blazing Door Open (faster than TURBO!) + if (EV_DoDoor (line,blazeOpen)) + P_ChangeSwitchTexture(line,0); + break; + + case 113: + // Blazing Door Close (faster than TURBO!) + if (EV_DoDoor (line,blazeClose)) + P_ChangeSwitchTexture(line,0); + break; + + case 122: + // Blazing PlatDownWaitUpStay + if (EV_DoPlat(line,blazeDWUS,0)) + P_ChangeSwitchTexture(line,0); + break; + + case 127: + // Build Stairs Turbo 16 + if (EV_BuildStairs(line,turbo16)) + P_ChangeSwitchTexture(line,0); + break; + + case 131: + // Raise Floor Turbo + if (EV_DoFloor(line,raiseFloorTurbo)) + P_ChangeSwitchTexture(line,0); + break; + + case 133: + // BlzOpenDoor BLUE + case 135: + // BlzOpenDoor RED + case 137: + // BlzOpenDoor YELLOW + if (EV_DoLockedDoor (line,blazeOpen,thing)) + P_ChangeSwitchTexture(line,0); + break; + + case 140: + // Raise Floor 512 + if (EV_DoFloor(line,raiseFloor512)) + P_ChangeSwitchTexture(line,0); + break; + + // BUTTONS + case 42: + // Close Door + if (EV_DoDoor(line,closed)) + P_ChangeSwitchTexture(line,1); + break; + + case 43: + // Lower Ceiling to Floor + if (EV_DoCeiling(line,lowerToFloor)) + P_ChangeSwitchTexture(line,1); + break; + + case 45: + // Lower Floor to Surrounding floor height + if (EV_DoFloor(line,lowerFloor)) + P_ChangeSwitchTexture(line,1); + break; + + case 60: + // Lower Floor to Lowest + if (EV_DoFloor(line,lowerFloorToLowest)) + P_ChangeSwitchTexture(line,1); + break; + + case 61: + // Open Door + if (EV_DoDoor(line,opened)) + P_ChangeSwitchTexture(line,1); + break; + + case 62: + // PlatDownWaitUpStay + if (EV_DoPlat(line,downWaitUpStay,1)) + P_ChangeSwitchTexture(line,1); + break; + + case 63: + // Raise Door + if (EV_DoDoor(line,normal)) + P_ChangeSwitchTexture(line,1); + break; + + case 64: + // Raise Floor to ceiling + if (EV_DoFloor(line,raiseFloor)) + P_ChangeSwitchTexture(line,1); + break; + + case 66: + // Raise Floor 24 and change texture + if (EV_DoPlat(line,raiseAndChange,24)) + P_ChangeSwitchTexture(line,1); + break; + + case 67: + // Raise Floor 32 and change texture + if (EV_DoPlat(line,raiseAndChange,32)) + P_ChangeSwitchTexture(line,1); + break; + + case 65: + // Raise Floor Crush + if (EV_DoFloor(line,raiseFloorCrush)) + P_ChangeSwitchTexture(line,1); + break; + + case 68: + // Raise Plat to next highest floor and change texture + if (EV_DoPlat(line,raiseToNearestAndChange,0)) + P_ChangeSwitchTexture(line,1); + break; + + case 69: + // Raise Floor to next highest floor + if (EV_DoFloor(line, raiseFloorToNearest)) + P_ChangeSwitchTexture(line,1); + break; + + case 70: + // Turbo Lower Floor + if (EV_DoFloor(line,turboLower)) + P_ChangeSwitchTexture(line,1); + break; + + case 114: + // Blazing Door Raise (faster than TURBO!) + if (EV_DoDoor (line,blazeRaise)) + P_ChangeSwitchTexture(line,1); + break; + + case 115: + // Blazing Door Open (faster than TURBO!) + if (EV_DoDoor (line,blazeOpen)) + P_ChangeSwitchTexture(line,1); + break; + + case 116: + // Blazing Door Close (faster than TURBO!) + if (EV_DoDoor (line,blazeClose)) + P_ChangeSwitchTexture(line,1); + break; + + case 123: + // Blazing PlatDownWaitUpStay + if (EV_DoPlat(line,blazeDWUS,0)) + P_ChangeSwitchTexture(line,1); + break; + + case 132: + // Raise Floor Turbo + if (EV_DoFloor(line,raiseFloorTurbo)) + P_ChangeSwitchTexture(line,1); + break; + + case 99: + // BlzOpenDoor BLUE + case 134: + // BlzOpenDoor RED + case 136: + // BlzOpenDoor YELLOW + if (EV_DoLockedDoor (line,blazeOpen,thing)) + P_ChangeSwitchTexture(line,1); + break; + + case 138: + // Light Turn On + EV_LightTurnOn(line,255); + P_ChangeSwitchTexture(line,1); + break; + + case 139: + // Light Turn Off + EV_LightTurnOn(line,35); + P_ChangeSwitchTexture(line,1); + break; + + } + + return true; +} + + diff --git a/doomclassic/doom/p_telept.cpp b/doomclassic/doom/p_telept.cpp new file mode 100644 index 00000000..3dbea4e2 --- /dev/null +++ b/doomclassic/doom/p_telept.cpp @@ -0,0 +1,138 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + + +#include "doomdef.h" + +#include "s_sound.h" + +#include "p_local.h" + + +// Data. +#include "sounds.h" + +// State. +#include "r_state.h" + + + +// +// TELEPORTATION +// +int +EV_Teleport +( line_t* line, + int side, + mobj_t* thing ) +{ + int i; + int tag; + mobj_t* m; + mobj_t* fog; + unsigned an; + thinker_t* thinker; + sector_t* sector; + fixed_t oldx; + fixed_t oldy; + fixed_t oldz; + + // don't teleport missiles + if (thing->flags & MF_MISSILE) + return 0; + + // Don't teleport if hit back of line, + // so you can get out of teleporter. + if (side == 1) + return 0; + + + tag = line->tag; + for (i = 0; i < ::g->numsectors; i++) + { + if (::g->sectors[ i ].tag == tag ) + { + thinker = ::g->thinkercap.next; + for (thinker = ::g->thinkercap.next; + thinker != &::g->thinkercap; + thinker = thinker->next) + { + // not a mobj + if (thinker->function.acp1 != (actionf_p1)P_MobjThinker) + continue; + + m = (mobj_t *)thinker; + + // not a teleportman + if (m->type != MT_TELEPORTMAN ) + continue; + + sector = m->subsector->sector; + // wrong sector + if (sector-::g->sectors != i ) + continue; + + oldx = thing->x; + oldy = thing->y; + oldz = thing->z; + + if (!P_TeleportMove (thing, m->x, m->y)) + return 0; + + thing->z = thing->floorz; //fixme: not needed? + if (thing->player) + thing->player->viewz = thing->z+thing->player->viewheight; + + // spawn teleport fog at source and destination + fog = P_SpawnMobj (oldx, oldy, oldz, MT_TFOG); + S_StartSound (fog, sfx_telept); + an = m->angle >> ANGLETOFINESHIFT; + fog = P_SpawnMobj (m->x+20*finecosine[an], m->y+20*finesine[an] + , thing->z, MT_TFOG); + + // emit sound, where? + S_StartSound (fog, sfx_telept); + + // don't move for a bit + if (thing->player) + thing->reactiontime = 18; + + thing->angle = m->angle; + thing->momx = thing->momy = thing->momz = 0; + return 1; + } + } + } + return 0; +} + + diff --git a/doomclassic/doom/p_tick.cpp b/doomclassic/doom/p_tick.cpp new file mode 100644 index 00000000..392c3f70 --- /dev/null +++ b/doomclassic/doom/p_tick.cpp @@ -0,0 +1,169 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "z_zone.h" +#include "p_local.h" + +#include "doomstat.h" + + + +// +// THINKERS +// All thinkers should be allocated by Z_Malloc +// so they can be operated on uniformly. +// The actual structures will vary in size, +// but the first element must be thinker_t. +// + + + +// Both the head and tail of the thinker list. + + +// +// P_InitThinkers +// +void P_InitThinkers (void) +{ + ::g->thinkercap.prev = ::g->thinkercap.next = &::g->thinkercap; +} + + + + +// +// P_AddThinker +// Adds a new thinker at the end of the list. +// +void P_AddThinker (thinker_t* thinker) +{ + ::g->thinkercap.prev->next = thinker; + thinker->next = &::g->thinkercap; + thinker->prev = ::g->thinkercap.prev; + ::g->thinkercap.prev = thinker; +} + + + +// +// P_RemoveThinker +// Deallocation is lazy -- it will not actually be freed +// until its thinking turn comes up. +// +void P_RemoveThinker (thinker_t* thinker) +{ + // FIXME: NOP. + thinker->function.acv = (actionf_v)(-1); +} + + + +// +// P_AllocateThinker +// Allocates memory and adds a new thinker at the end of the list. +// +void P_AllocateThinker (thinker_t* thinker) +{ +} + + + +// +// P_RunThinkers +// +void P_RunThinkers (void) +{ + thinker_t* currentthinker; + + currentthinker = ::g->thinkercap.next; + while (currentthinker != &::g->thinkercap) + { + if ( currentthinker->function.acv == (actionf_v)(-1) ) + { + // time to remove it + currentthinker->next->prev = currentthinker->prev; + currentthinker->prev->next = currentthinker->next; + Z_Free(currentthinker); + } + else + { + if (currentthinker->function.acp1) + currentthinker->function.acp1 ((mobj_t*)currentthinker); + } + currentthinker = currentthinker->next; + } +} + + + +// +// P_Ticker +// +extern byte demoversion; + +void P_Ticker (void) +{ + int i; + + // run the tic + if (::g->paused) + return; + + // don't think during wipe + if ( !::g->netgame && (!::g->demoplayback || demoversion == VERSION ) && ::g->wipe ) { + return; + } + + // pause if in menu and at least one tic has been run + if ( !::g->netgame + && ::g->menuactive + && !::g->demoplayback + && ::g->players[::g->consoleplayer].viewz != 1) + { + return; + } + + + for (i=0 ; iplayeringame[i]) { + P_PlayerThink (&::g->players[i]); + } + } + + P_RunThinkers (); + P_UpdateSpecials (); + P_RespawnSpecials (); + + // for par times + ::g->leveltime++; +} + diff --git a/doomclassic/doom/p_tick.h b/doomclassic/doom/p_tick.h new file mode 100644 index 00000000..94256212 --- /dev/null +++ b/doomclassic/doom/p_tick.h @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __P_TICK__ +#define __P_TICK__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// Called by C_Ticker, +// can call G_PlayerExited. +// Carries out all thinking of monsters and players. +void P_Ticker (void); + + + +#endif + diff --git a/doomclassic/doom/p_user.cpp b/doomclassic/doom/p_user.cpp new file mode 100644 index 00000000..936f3049 --- /dev/null +++ b/doomclassic/doom/p_user.cpp @@ -0,0 +1,450 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "doomdef.h" +#include "d_event.h" + +#include "p_local.h" + +#include "doomstat.h" + + + +// Index of the special effects (INVUL inverse) map. + + +// +// Movement. +// + +// 16 pixels of bob + + + +// +// P_Thrust +// Moves the given origin along a given angle. +// +void +P_Thrust +( player_t* player, + angle_t angle, + fixed_t move ) +{ + angle >>= ANGLETOFINESHIFT; + + player->mo->momx += FixedMul(move,finecosine[angle]); + player->mo->momy += FixedMul(move,finesine[angle]); +} + + + + +// +// P_CalcHeight +// Calculate the walking / running height adjustment +// +void P_CalcHeight (player_t* player) +{ + int angle; + fixed_t bob; + + // Regular movement bobbing + // (needs to be calculated for gun swing + // even if not on ground) + // OPTIMIZE: tablify angle + // Note: a LUT allows for effects + // like a ramp with low health. + player->bob = + FixedMul (player->mo->momx, player->mo->momx) + + FixedMul (player->mo->momy,player->mo->momy); + + player->bob >>= 2; + + // DHM - NERVE :: player bob reduced by 25%, MAXBOB reduced by 25% as well + player->bob = (fixed_t)( (float)(player->bob) * 0.75f ); + if (player->bob>MAXBOB) + player->bob = MAXBOB; + + if ((player->cheats & CF_NOMOMENTUM) || !::g->onground) + { + player->viewz = player->mo->z + VIEWHEIGHT; + + if (player->viewz > player->mo->ceilingz-4*FRACUNIT) + player->viewz = player->mo->ceilingz-4*FRACUNIT; + + player->viewz = player->mo->z + player->viewheight; + return; + } + + angle = (FINEANGLES/20*::g->leveltime)&FINEMASK; + bob = FixedMul ( player->bob/2, finesine[angle]); + + + // move ::g->viewheight + if (player->playerstate == PST_LIVE) + { + player->viewheight += player->deltaviewheight; + + if (player->viewheight > VIEWHEIGHT) + { + player->viewheight = VIEWHEIGHT; + player->deltaviewheight = 0; + } + + if (player->viewheight < VIEWHEIGHT/2) + { + player->viewheight = VIEWHEIGHT/2; + if (player->deltaviewheight <= 0) + player->deltaviewheight = 1; + } + + if (player->deltaviewheight) + { + player->deltaviewheight += FRACUNIT/4; + if (!player->deltaviewheight) + player->deltaviewheight = 1; + } + } + player->viewz = player->mo->z + player->viewheight + bob; + + if (player->viewz > player->mo->ceilingz-4*FRACUNIT) + player->viewz = player->mo->ceilingz-4*FRACUNIT; +} + + + +// +// P_MovePlayer +// +void P_MovePlayer (player_t* player) +{ + ticcmd_t* cmd; + + cmd = &player->cmd; + + player->mo->angle += (cmd->angleturn<<16); + + // Do not let the player control movement + // if not ::g->onground. + ::g->onground = (player->mo->z <= player->mo->floorz); + + if (cmd->forwardmove && ::g->onground) + P_Thrust (player, player->mo->angle, cmd->forwardmove*2048); + + if (cmd->sidemove && ::g->onground) + P_Thrust (player, player->mo->angle-ANG90, cmd->sidemove*2048); + + if ( (cmd->forwardmove || cmd->sidemove) + && player->mo->state == &::g->states[S_PLAY] ) + { + P_SetMobjState (player->mo, S_PLAY_RUN1); + } +} + + + +// +// P_DeathThink +// Fall on your face when dying. +// Decrease POV height to floor height. +// +extern byte demoversion; + +void P_DeathThink (player_t* player) +{ + angle_t angle; + angle_t delta; + + P_MovePsprites (player); + + // fall to the ground + if (player->viewheight > 6*FRACUNIT) + player->viewheight -= FRACUNIT; + + if (player->viewheight < 6*FRACUNIT) + player->viewheight = 6*FRACUNIT; + + player->deltaviewheight = 0; + ::g->onground = (player->mo->z <= player->mo->floorz); + P_CalcHeight (player); + + if (player->attacker && player->attacker != player->mo) + { + angle = R_PointToAngle2 (player->mo->x, + player->mo->y, + player->attacker->x, + player->attacker->y); + + delta = angle - player->mo->angle; + + if (delta < ANG5 || delta > (unsigned)-ANG5) + { + // Looking at killer, + // so fade damage flash down. + player->mo->angle = angle; + + if (player->damagecount) + player->damagecount--; + } + else if (delta < ANG180) + player->mo->angle += ANG5; + else + player->mo->angle -= ANG5; + } + else if (player->damagecount) + player->damagecount--; + + + if (player->cmd.buttons & BT_USE) + player->playerstate = PST_REBORN; +} + + + +// +// P_PlayerThink +// +void P_PlayerThink (player_t* player) +{ + ticcmd_t* cmd; + weapontype_t newweapon = wp_fist; + + // fixme: do this in the cheat code + if (player->cheats & CF_NOCLIP) + player->mo->flags |= MF_NOCLIP; + else + player->mo->flags &= ~MF_NOCLIP; + + // chain saw run forward + cmd = &player->cmd; + if (player->mo->flags & MF_JUSTATTACKED) + { + cmd->angleturn = 0; + cmd->forwardmove = 0xc800/512; + cmd->sidemove = 0; + player->mo->flags &= ~MF_JUSTATTACKED; + } + + + if (player->playerstate == PST_DEAD) + { + P_DeathThink (player); + return; + } + + // Move around. + // Reactiontime is used to prevent movement + // for a bit after a teleport. + if (player->mo->reactiontime) + player->mo->reactiontime--; + else + P_MovePlayer (player); + + P_CalcHeight (player); + + if (player->mo->subsector->sector->special) + P_PlayerInSpecialSector (player); + + // Check for weapon change. + + // A special event has no other buttons. + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + + if (::g->demoplayback && demoversion < VERSION ) + { + if ( cmd->buttons & BT_CHANGE) + { + // The actual changing of the weapon is done + // when the weapon psprite can do it + // (read: not in the middle of an attack). + newweapon = (weapontype_t)((cmd->buttons&BT_WEAPONMASK)>>BT_WEAPONSHIFT); + + if (newweapon == wp_fist + && player->weaponowned[wp_chainsaw] + && !(player->readyweapon == wp_chainsaw + && player->powers[pw_strength])) + { + newweapon = wp_chainsaw; + } + + if ( (::g->gamemode == commercial) + && newweapon == wp_shotgun + && player->weaponowned[wp_supershotgun] + && player->readyweapon != wp_supershotgun) + { + newweapon = wp_supershotgun; + } + + + if (player->weaponowned[newweapon] + && newweapon != player->readyweapon) + { + // Do not go to plasma or BFG in shareware, + // even if cheated. + if ((newweapon != wp_plasma + && newweapon != wp_bfg) + || (::g->gamemode != shareware) ) + { + player->pendingweapon = newweapon; + } + } + } + } + else if ( cmd->buttons & BT_CHANGE ) + { + int k, which; + // The actual changing of the weapon is done + // when the weapon psprite can do it + // (read: not in the middle of an attack). + which = ((cmd->buttons&BT_WEAPONMASK)>>BT_WEAPONSHIFT); + + if ( cmd->nextPrevWeapon > 0) { + newweapon = player->readyweapon; + + for ( k = 0; k < NUMWEAPONS; ++k) + { + newweapon = (weapontype_t)( (cmd->nextPrevWeapon - 1) ? (newweapon + 1) : (newweapon - 1)); + + if (newweapon == wp_nochange) + continue; + + weapontype_t maxweapon = (::g->gamemode == retail) ? wp_chainsaw : wp_supershotgun; + + if (newweapon < 0) + newweapon = maxweapon; + + if (newweapon > maxweapon) + newweapon = wp_fist; + + + if (player->weaponowned[newweapon] && newweapon != player->readyweapon) + { + player->pendingweapon = newweapon; + break; + } + } + } + else { + + newweapon = (weapontype_t)((cmd->buttons&BT_WEAPONMASK)>>BT_WEAPONSHIFT); + + if (newweapon == wp_fist + && player->weaponowned[wp_chainsaw] + && !(player->readyweapon == wp_chainsaw + && player->powers[pw_strength])) + { + newweapon = wp_chainsaw; + } + + if ( (::g->gamemode == commercial) + && newweapon == wp_shotgun + && player->weaponowned[wp_supershotgun] + && player->readyweapon != wp_supershotgun) + { + newweapon = wp_supershotgun; + } + + if ( player->weaponowned[ newweapon ] && newweapon != player->readyweapon ) { + player->pendingweapon = newweapon; + } + } + } + + // check for use + if (cmd->buttons & BT_USE) + { + if (!player->usedown) + { + P_UseLines (player); + player->usedown = true; + } + } + else + player->usedown = false; + + // cycle psprites + P_MovePsprites (player); + + // Counters, time dependend power ups. + + // Strength counts up to diminish fade. + if (player->powers[pw_strength]) + player->powers[pw_strength]++; + + if (player->powers[pw_invulnerability]) + player->powers[pw_invulnerability]--; + + if (player->powers[pw_invisibility]) + if (! --player->powers[pw_invisibility] ) + player->mo->flags &= ~MF_SHADOW; + + if (player->powers[pw_infrared]) + player->powers[pw_infrared]--; + + if (player->powers[pw_ironfeet]) + player->powers[pw_ironfeet]--; + + if (player->damagecount) + player->damagecount--; + + if (player->bonuscount) + player->bonuscount--; + + + // Handling ::g->colormaps. + if (player->powers[pw_invulnerability]) + { + if (player->powers[pw_invulnerability] > 4*32 + || (player->powers[pw_invulnerability]&8) ) + player->fixedcolormap = INVERSECOLORMAP; + else + player->fixedcolormap = 0; + } + else if (player->powers[pw_infrared]) + { + if (player->powers[pw_infrared] > 4*32 + || (player->powers[pw_infrared]&8) ) + { + // almost full bright + player->fixedcolormap = 1; + } + else + player->fixedcolormap = 0; + } + else + player->fixedcolormap = 0; +} + + + diff --git a/doomclassic/doom/r_bsp.cpp b/doomclassic/doom/r_bsp.cpp new file mode 100644 index 00000000..d3bab330 --- /dev/null +++ b/doomclassic/doom/r_bsp.cpp @@ -0,0 +1,559 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "doomdef.h" + +#include "m_bbox.h" + +#include "i_system.h" + +#include "r_main.h" +#include "r_plane.h" +#include "r_things.h" + +// State. +#include "doomstat.h" +#include "r_state.h" + +//#include "r_local.h" + + + + + + +void +R_StoreWallRange +( int start, + int stop ); + + + + +// +// R_ClearDrawSegs +// +void R_ClearDrawSegs (void) +{ + ::g->ds_p = ::g->drawsegs; +} + + + +// +// ClipWallSegment +// Clips the given range of columns +// and includes it in the new clip list. +// + + + +// ::g->newend is one past the last valid seg + + + + +// +// R_ClipSolidWallSegment +// Does handle solid walls, +// e.g. single sided LineDefs (middle texture) +// that entirely block the view. +// +void +R_ClipSolidWallSegment +( int first, + int last ) +{ + cliprange_t* next; + cliprange_t* start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = ::g->solidsegs; + while (start->last < first-1) + start++; + + if (first < start->first) + { + if (last < start->first-1) + { + // Post is entirely visible (above start), + // so insert a new clippost. + R_StoreWallRange (first, last); + next = ::g->newend; + ::g->newend++; + + while (next != start) + { + *next = *(next-1); + next--; + } + next->first = first; + next->last = last; + return; + } + + // There is a fragment above *start. + R_StoreWallRange (first, start->first - 1); + // Now adjust the clip size. + start->first = first; + } + + // Bottom contained in start? + if (last <= start->last) + return; + + next = start; + while (last >= (next+1)->first-1) + { + // There is a fragment between two posts. + R_StoreWallRange (next->last + 1, (next+1)->first - 1); + next++; + + if (last <= next->last) + { + // Bottom is contained in next. + // Adjust the clip size. + start->last = next->last; + goto crunch; + } + } + + // There is a fragment after *next. + R_StoreWallRange (next->last + 1, last); + // Adjust the clip size. + start->last = last; + + // Remove start+1 to next from the clip list, + // because start now covers their area. + crunch: + if (next == start) + { + // Post just extended past the bottom of one post. + return; + } + + + while (next++ != ::g->newend) + { + // Remove a post. + *++start = *next; + } + + ::g->newend = start+1; +} + + + +// +// R_ClipPassWallSegment +// Clips the given range of columns, +// but does not includes it in the clip list. +// Does handle windows, +// e.g. LineDefs with upper and lower texture. +// +void +R_ClipPassWallSegment +( int first, + int last ) +{ + cliprange_t* start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = ::g->solidsegs; + while (start->last < first-1) + start++; + + if (first < start->first) + { + if (last < start->first-1) + { + // Post is entirely visible (above start). + R_StoreWallRange (first, last); + return; + } + + // There is a fragment above *start. + R_StoreWallRange (first, start->first - 1); + } + + // Bottom contained in start? + if (last <= start->last) + return; + + while (last >= (start+1)->first-1) + { + // There is a fragment between two posts. + R_StoreWallRange (start->last + 1, (start+1)->first - 1); + start++; + + if (last <= start->last) + return; + } + + // There is a fragment after *next. + R_StoreWallRange (start->last + 1, last); +} + + + +// +// R_ClearClipSegs +// +void R_ClearClipSegs (void) +{ + ::g->solidsegs[0].first = -0x7fffffff; + ::g->solidsegs[0].last = -1; + ::g->solidsegs[1].first = ::g->viewwidth; + ::g->solidsegs[1].last = 0x7fffffff; + ::g->newend = ::g->solidsegs+2; +} + +// +// R_AddLine +// Clips the given segment +// and adds any visible pieces to the line list. +// +void R_AddLine (seg_t* line) +{ + int x1; + int x2; + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + ::g->curline = line; + + // OPTIMIZE: quickly reject orthogonal back ::g->sides. + angle1 = R_PointToAngle (line->v1->x, line->v1->y); + angle2 = R_PointToAngle (line->v2->x, line->v2->y); + + // Clip to view edges. + // OPTIMIZE: make constant out of 2*::g->clipangle (FIELDOFVIEW). + span = angle1 - angle2; + + // Back side? I.e. backface culling? + if (span >= ANG180) + return; + + extern angle_t GetViewAngle(); + // Global angle needed by segcalc. + ::g->rw_angle1 = angle1; + angle1 -= GetViewAngle(); + angle2 -= GetViewAngle(); + + tspan = angle1 + ::g->clipangle; + if (tspan > 2*::g->clipangle) + { + tspan -= 2*::g->clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + + angle1 = ::g->clipangle; + } + tspan = ::g->clipangle - angle2; + if (tspan > 2*::g->clipangle) + { + tspan -= 2*::g->clipangle; + + // Totally off the left edge? + if (tspan >= span) + return; + angle2 = -::g->clipangle; // ALANHACK UNSIGNED + } + + // The seg is in the view range, + // but not necessarily visible. + angle1 = (angle1+ANG90)>>ANGLETOFINESHIFT; + angle2 = (angle2+ANG90)>>ANGLETOFINESHIFT; + x1 = ::g->viewangletox[angle1]; + x2 = ::g->viewangletox[angle2]; + + // Does not cross a pixel? + if (x1 == x2) + return; + + ::g->backsector = line->backsector; + + // Single sided line? + if (!::g->backsector) + goto clipsolid; + + // Closed door. + if (::g->backsector->ceilingheight <= ::g->frontsector->floorheight + || ::g->backsector->floorheight >= ::g->frontsector->ceilingheight) + goto clipsolid; + + // Window. + if (::g->backsector->ceilingheight != ::g->frontsector->ceilingheight + || ::g->backsector->floorheight != ::g->frontsector->floorheight) + goto clippass; + + // Reject empty ::g->lines used for triggers + // and special ::g->events. + // Identical floor and ceiling on both ::g->sides, + // identical light levels on both ::g->sides, + // and no middle texture. + if (::g->backsector->ceilingpic == ::g->frontsector->ceilingpic + && ::g->backsector->floorpic == ::g->frontsector->floorpic + && ::g->backsector->lightlevel == ::g->frontsector->lightlevel + && ::g->curline->sidedef->midtexture == 0) + { + return; + } + + + clippass: + R_ClipPassWallSegment (x1, x2-1); + return; + + clipsolid: + R_ClipSolidWallSegment (x1, x2-1); +} + + +// +// R_CheckBBox +// Checks BSP node/subtree bounding box. +// Returns true +// if some part of the bbox might be visible. +// + + +qboolean R_CheckBBox (fixed_t* bspcoord) +{ + int boxx; + int boxy; + int boxpos; + + fixed_t x1; + fixed_t y1; + fixed_t x2; + fixed_t y2; + + angle_t angle1; + angle_t angle2; + angle_t span; + angle_t tspan; + + cliprange_t* start; + + int sx1; + int sx2; + + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + // Find the corners of the box + // that define the edges from current viewpoint. + if (GetViewX() <= bspcoord[BOXLEFT]) + boxx = 0; + else if (GetViewX() < bspcoord[BOXRIGHT]) + boxx = 1; + else + boxx = 2; + + if (GetViewY() >= bspcoord[BOXTOP]) + boxy = 0; + else if (GetViewY() > bspcoord[BOXBOTTOM]) + boxy = 1; + else + boxy = 2; + + boxpos = (boxy<<2)+boxx; + if (boxpos == 5) + return true; + + x1 = bspcoord[::g->checkcoord[boxpos][0]]; + y1 = bspcoord[::g->checkcoord[boxpos][1]]; + x2 = bspcoord[::g->checkcoord[boxpos][2]]; + y2 = bspcoord[::g->checkcoord[boxpos][3]]; + + // check clip list for an open space + extern angle_t GetViewAngle(); + angle1 = R_PointToAngle (x1, y1) - GetViewAngle(); + angle2 = R_PointToAngle (x2, y2) - GetViewAngle(); + + span = angle1 - angle2; + + // Sitting on a line? + if (span >= ANG180) + return true; + + tspan = angle1 + ::g->clipangle; + + if (tspan > 2*::g->clipangle) + { + tspan -= 2*::g->clipangle; + + // Totally off the left edge? + if (tspan >= span) + return false; + + angle1 = ::g->clipangle; + } + tspan = ::g->clipangle - angle2; + if (tspan > 2*::g->clipangle) + { + tspan -= 2*::g->clipangle; + + // Totally off the left edge? + if (tspan >= span) + return false; + + angle2 = -::g->clipangle;// ALANHACK UNSIGNED + } + + + // Find the first clippost + // that touches the source post + // (adjacent pixels are touching). + angle1 = (angle1+ANG90)>>ANGLETOFINESHIFT; + angle2 = (angle2+ANG90)>>ANGLETOFINESHIFT; + sx1 = ::g->viewangletox[angle1]; + sx2 = ::g->viewangletox[angle2]; + + // Does not cross a pixel. + if (sx1 == sx2) + return false; + sx2--; + + start = ::g->solidsegs; + while (start->last < sx2) + start++; + + if (sx1 >= start->first + && sx2 <= start->last) + { + // The clippost contains the new span. + return false; + } + + return true; +} + + + +// +// R_Subsector +// Determine floor/ceiling planes. +// Add ::g->sprites of things in sector. +// Draw one or more line segments. +// +void R_Subsector (int num) +{ + int count; + seg_t* line; + subsector_t* sub; + +#ifdef RANGECHECK + if (num>=::g->numsubsectors) + I_Error ("R_Subsector: ss %i with numss = %i", + num, + ::g->numsubsectors); +#endif + + ::g->sscount++; + sub = &::g->subsectors[num]; + ::g->frontsector = sub->sector; + count = sub->numlines; + line = &::g->segs[sub->firstline]; + + if (::g->frontsector->floorheight < ::g->viewz) + { + ::g->floorplane = R_FindPlane (::g->frontsector->floorheight, + ::g->frontsector->floorpic, + ::g->frontsector->lightlevel); + } + else + ::g->floorplane = NULL; + + if (::g->frontsector->ceilingheight > ::g->viewz + || ::g->frontsector->ceilingpic == ::g->skyflatnum) + { + ::g->ceilingplane = R_FindPlane (::g->frontsector->ceilingheight, + ::g->frontsector->ceilingpic, + ::g->frontsector->lightlevel); + } + else + ::g->ceilingplane = NULL; + + R_AddSprites (::g->frontsector); + + while (count--) + { + R_AddLine (line); + line++; + } +} + + + + +// +// RenderBSPNode +// Renders all ::g->subsectors below a given node, +// traversing subtree recursively. +// Just call with BSP root. +void R_RenderBSPNode (int bspnum) +{ + node_t* bsp; + int side; + + // Found a subsector? + if (bspnum & NF_SUBSECTOR) + { + if (bspnum == -1) + R_Subsector (0); + else + R_Subsector (bspnum&(~NF_SUBSECTOR)); + return; + } + + bsp = &::g->nodes[bspnum]; + + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + // Decide which side the view point is on. + side = R_PointOnSide (GetViewX(), GetViewY(), bsp); + + // Recursively divide front space. + R_RenderBSPNode (bsp->children[side]); + + // Possibly divide back space. + if (R_CheckBBox (bsp->bbox[side^1])) + R_RenderBSPNode (bsp->children[side^1]); +} + + + diff --git a/doomclassic/doom/r_bsp.h b/doomclassic/doom/r_bsp.h new file mode 100644 index 00000000..b1e13c07 --- /dev/null +++ b/doomclassic/doom/r_bsp.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_BSP__ +#define __R_BSP__ + +#ifdef __GNUG__ +#pragma interface +#endif + + +extern seg_t* curline; +extern side_t* sidedef; +extern line_t* linedef; +extern sector_t* frontsector; +extern sector_t* backsector; + +extern int rw_x; +extern int rw_stopx; + +extern qboolean segtextured; + +// false if the back side is the same plane +extern qboolean markfloor; +extern qboolean markceiling; + +extern qboolean skymap; + +extern drawseg_t drawsegs[MAXDRAWSEGS]; +extern drawseg_t* ds_p; + +extern lighttable_t** hscalelight; +extern lighttable_t** vscalelight; +extern lighttable_t** dscalelight; + + +typedef void (*drawfunc_t) (int start, int stop); + + +// BSP? +void R_ClearClipSegs (void); +void R_ClearDrawSegs (void); + + +void R_RenderBSPNode (int bspnum); + + +#endif + diff --git a/doomclassic/doom/r_data.cpp b/doomclassic/doom/r_data.cpp new file mode 100644 index 00000000..72e9ab16 --- /dev/null +++ b/doomclassic/doom/r_data.cpp @@ -0,0 +1,776 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "i_system.h" +#include "z_zone.h" + +#include "m_swap.h" + +#include "w_wad.h" + +#include "doomdef.h" +#include "r_local.h" +#include "p_local.h" + +#include "doomstat.h" +#include "r_sky.h" + +#ifdef LINUX +#include +#endif + + +#include "r_data.h" + +#include + +// +// Graphics. +// DOOM graphics for walls and ::g->sprites +// is stored in vertical runs of opaque pixels (posts). +// A column is composed of zero or more posts, +// a patch or sprite is composed of zero or more columns. +// + + + +// +// Texture definition. +// Each texture is composed of one or more patches, +// with patches being lumps stored in the WAD. +// The lumps are referenced by number, and patched +// into the rectangular texture space using origin +// and possibly other attributes. +// + + +// +// Texture definition. +// A DOOM wall texture is a list of patches +// which are to be combined in a predefined order. +// + + +// A single patch from a texture definition, +// basically a rectangular area within +// the texture rectangle. + + +// A maptexturedef_t describes a rectangular texture, +// which is composed of one or more mappatch_t structures +// that arrange graphic patches. + + + + + + +// for global animation + +// needed for pre rendering + + + +// +// MAPTEXTURE_T CACHING +// When a texture is first needed, +// it counts the number of composite columns +// required in the texture and allocates space +// for a column directory and any new columns. +// The directory will simply point inside other patches +// if there is only one patch in a given column, +// but any columns with multiple patches +// will have new postColumn_ts generated. +// + + + +// +// R_DrawColumnInCache +// Clip and draw a column +// from a patch into a cached post. +// +void +R_DrawColumnInCache +( postColumn_t* patch, + byte* cache, + int originy, + int cacheheight ) +{ + int count; + int position; + byte* source; + byte* dest; + + dest = (byte *)cache + 3; + + while (patch->topdelta != 0xff) + { + source = (byte *)patch + 3; + count = patch->length; + position = originy + patch->topdelta; + + if (position < 0) + { + count += position; + position = 0; + } + + if (position + count > cacheheight) + count = cacheheight - position; + + if (count > 0) + memcpy (cache + position, source, count); + + patch = (postColumn_t *)( (byte *)patch + patch->length + 4); + } +} + + + +// +// R_GenerateComposite +// Using the texture definition, +// the composite texture is created from the patches, +// and each column is cached. +// +void R_GenerateComposite (int texnum) +{ + byte* block; + texture_t* texture; + texpatch_t* patch; + patch_t* realpatch; + int x; + int x1; + int x2; + int i; + postColumn_t* patchcol; + short* collump; + unsigned short* colofs; + + texture = ::g->s_textures[texnum]; + + block = (byte*)DoomLib::Z_Malloc (::g->s_texturecompositesize[texnum], + PU_CACHE_SHARED, + &::g->s_texturecomposite[texnum]); + + collump = ::g->s_texturecolumnlump[texnum]; + colofs = ::g->s_texturecolumnofs[texnum]; + + // Composite the columns together. + patch = texture->patches; + + for (i=0 , patch = texture->patches; + ipatchcount; + i++, patch++) + { + realpatch = (patch_t*)W_CacheLumpNum (patch->patch, PU_CACHE_SHARED); + x1 = patch->originx; + x2 = x1 + SHORT(realpatch->width); + + if (x1<0) + x = 0; + else + x = x1; + + if (x2 > texture->width) + x2 = texture->width; + + for ( ; x= 0) + continue; + + patchcol = (postColumn_t *)((byte *)realpatch + + LONG(realpatch->columnofs[x-x1])); + R_DrawColumnInCache (patchcol, + block + colofs[x], + patch->originy, + texture->height); + } + + } +} + + + +// +// R_GenerateLookup +// +void R_GenerateLookup (int texnum) +{ + texture_t* texture; + texpatch_t* patch; + patch_t* realpatch; + int x; + int x1; + int x2; + int i; + short* collump; + unsigned short* colofs; + + texture = ::g->s_textures[texnum]; + + // Composited texture not created yet. + ::g->s_texturecomposite[texnum] = 0; + + ::g->s_texturecompositesize[texnum] = 0; + collump = ::g->s_texturecolumnlump[texnum]; + colofs = ::g->s_texturecolumnofs[texnum]; + + // Now count the number of columns + // that are covered by more than one patch. + // Fill in the lump / offset, so columns + // with only a single patch are all done. + std::vector patchcount(texture->width, 0); + patch = texture->patches; + + for (i=0 , patch = texture->patches; + ipatchcount; + i++, patch++) + { + realpatch = (patch_t*)W_CacheLumpNum (patch->patch, PU_CACHE_SHARED); + x1 = patch->originx; + x2 = x1 + SHORT(realpatch->width); + + if (x1 < 0) + x = 0; + else + x = x1; + + if (x2 > texture->width) + x2 = texture->width; + for ( ; xpatch; + colofs[x] = LONG(realpatch->columnofs[x-x1])+3; + } + } + + for (x=0 ; xwidth ; x++) + { + if (!patchcount[x]) + { + I_Printf ("R_GenerateLookup: column without a patch (%s)\n", + texture->name); + return; + } + // I_Error ("R_GenerateLookup: column without a patch"); + + if (patchcount[x] > 1) + { + // Use the cached block. + collump[x] = -1; + colofs[x] = ::g->s_texturecompositesize[texnum]; + + if (::g->s_texturecompositesize[texnum] > 0x10000-texture->height) + { + I_Error ("R_GenerateLookup: texture %i is >64k", + texnum); + } + + ::g->s_texturecompositesize[texnum] += texture->height; + } + } +} + + + + +// +// R_GetColumn +// +byte* +R_GetColumn +( int tex, + int col ) +{ + int lump; + int ofs; + + col &= ::g->s_texturewidthmask[tex]; + lump = ::g->s_texturecolumnlump[tex][col]; + ofs = ::g->s_texturecolumnofs[tex][col]; + + if (lump > 0) + return (byte *)W_CacheLumpNum(lump,PU_CACHE_SHARED)+ofs; + + if (!::g->s_texturecomposite[tex]) + R_GenerateComposite (tex); + + return ::g->s_texturecomposite[tex] + ofs; +} + + + + +// +// R_InitTextures +// Initializes the texture list +// with the s_textures from the world map. +// +void R_InitTextures (void) +{ + maptexture_t* mtexture; + texture_t* texture; + mappatch_t* mpatch; + texpatch_t* patch; + + int i; + int j; + + int* maptex; + int* maptex2; + int* maptex1; + + char name[9]; + char* names; + char* name_p; + + int totalwidth; + int nummappatches; + int offset; + int maxoff; + int maxoff2; + int numtextures1; + int numtextures2; + + int* directory; + + int temp1; + int temp2; + int temp3; + + + // Load the patch names from pnames.lmp. + name[8] = 0; + names = (char*)W_CacheLumpName ("PNAMES", PU_CACHE_SHARED); + nummappatches = LONG ( *((int *)names) ); + name_p = names+4; + + std::vector patchlookup(nummappatches); + + for (i=0 ; is_numtextures == 0) + { + + // Load the map texture definitions from textures.lmp. + // The data is contained in one or two lumps, + // TEXTURE1 for shareware, plus TEXTURE2 for commercial. + maptex = maptex1 = (int*)W_CacheLumpName ("TEXTURE1", PU_CACHE_SHARED); // ALAN: LOADTIME + numtextures1 = LONG(*maptex); + maxoff = W_LumpLength (W_GetNumForName ("TEXTURE1")); + directory = maptex+1; + + if (W_CheckNumForName ("TEXTURE2") != -1) + { + maptex2 = (int*)W_CacheLumpName ("TEXTURE2", PU_CACHE_SHARED); // ALAN: LOADTIME + numtextures2 = LONG(*maptex2); + maxoff2 = W_LumpLength (W_GetNumForName ("TEXTURE2")); + } + else + { + maptex2 = NULL; + numtextures2 = 0; + maxoff2 = 0; + } + + + ::g->s_numtextures = numtextures1 + numtextures2; + + ::g->s_textures = (texture_t**)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + ::g->s_texturecolumnlump = (short**)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + ::g->s_texturecolumnofs = (unsigned short**)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + ::g->s_texturewidthmask = (int*)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + ::g->s_textureheight = (fixed_t*)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + ::g->s_texturecomposite = (byte**)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + ::g->s_texturecompositesize = (int*)DoomLib::Z_Malloc (::g->s_numtextures*4, PU_STATIC_SHARED, 0); + + totalwidth = 0; + + // Really complex printing shit... + temp1 = W_GetNumForName ("S_START"); // P_??????? + temp2 = W_GetNumForName ("S_END") - 1; + temp3 = ((temp2-temp1+63)/64) + ((::g->s_numtextures+63)/64); + I_Printf("["); + for (i = 0; i < temp3; i++) + I_Printf(" "); + I_Printf(" ]"); + for (i = 0; i < temp3; i++) + I_Printf("\x8"); + I_Printf("\x8\x8\x8\x8\x8\x8\x8\x8\x8\x8"); + + for (i=0 ; i < ::g->s_numtextures ; i++, directory++) + { + if (!(i&63)) + I_Printf ("."); + + if (i == numtextures1) + { + // Start looking in second texture file. + maptex = maptex2; + maxoff = maxoff2; + directory = maptex+1; + } + + offset = LONG(*directory); + + if (offset > maxoff) + I_Error ("R_InitTextures: bad texture directory"); + + mtexture = (maptexture_t *) ( (byte *)maptex + offset); + + texture = ::g->s_textures[i] = (texture_t*)DoomLib::Z_Malloc (sizeof(texture_t) + + sizeof(texpatch_t)*(SHORT(mtexture->patchcount)-1), PU_STATIC_SHARED, 0); + + texture->width = SHORT(mtexture->width); + texture->height = SHORT(mtexture->height); + texture->patchcount = SHORT(mtexture->patchcount); + + memcpy (texture->name, mtexture->name, sizeof(texture->name)); + mpatch = &mtexture->patches[0]; + patch = &texture->patches[0]; + + for (j=0 ; jpatchcount ; j++, mpatch++, patch++) + { + patch->originx = SHORT(mpatch->originx); + patch->originy = SHORT(mpatch->originy); + patch->patch = patchlookup[SHORT(mpatch->patch)]; + if (patch->patch == -1) + { + I_Error ("R_InitTextures: Missing patch in texture %s", + texture->name); + } + } + ::g->s_texturecolumnlump[i] = (short*)DoomLib::Z_Malloc (texture->width*2, PU_STATIC_SHARED,0); + ::g->s_texturecolumnofs[i] = (unsigned short*)DoomLib::Z_Malloc (texture->width*2, PU_STATIC_SHARED,0); + + j = 1; + while (j*2 <= texture->width) + j<<=1; + + ::g->s_texturewidthmask[i] = j-1; + ::g->s_textureheight[i] = texture->height<width; + } + + Z_Free(maptex1); + if (maptex2) + Z_Free(maptex2); + + + // Precalculate whatever possible. + for (i=0 ; i < ::g->s_numtextures ; i++) + R_GenerateLookup (i); + } + + // ALAN: These animations are done globally -- can it be shared? + // Create translation table for global animation. + ::g->texturetranslation = (int*)DoomLib::Z_Malloc ((::g->s_numtextures+1)*4, PU_STATIC, 0); + + for (i=0 ; i < ::g->s_numtextures ; i++) + ::g->texturetranslation[i] = i; +} + + + +// +// R_InitFlats +// +void R_InitFlats (void) +{ + int i; + + ::g->firstflat = W_GetNumForName ("F_START") + 1; + ::g->lastflat = W_GetNumForName ("F_END") - 1; + ::g->numflats = ::g->lastflat - ::g->firstflat + 1; + + // Create translation table for global animation. + ::g->flattranslation = (int*)DoomLib::Z_Malloc ((::g->numflats+1)*4, PU_STATIC, 0); + + for (i=0 ; i < ::g->numflats ; i++) + ::g->flattranslation[i] = i; +} + + +// +// R_InitSpriteLumps +// Finds the width and hoffset of all ::g->sprites in the wad, +// so the sprite does not need to be cached completely +// just for having the header info ready during rendering. +// +void R_InitSpriteLumps (void) +{ + int i; + patch_t *patch; + + ::g->firstspritelump = W_GetNumForName ("S_START") + 1; + ::g->lastspritelump = W_GetNumForName ("S_END") - 1; + + ::g->numspritelumps = ::g->lastspritelump - ::g->firstspritelump + 1; + ::g->spritewidth = (fixed_t*)DoomLib::Z_Malloc (::g->numspritelumps*4, PU_STATIC, 0); + ::g->spriteoffset = (fixed_t*)DoomLib::Z_Malloc (::g->numspritelumps*4, PU_STATIC, 0); + ::g->spritetopoffset = (fixed_t*)DoomLib::Z_Malloc (::g->numspritelumps*4, PU_STATIC, 0); + + for (i=0 ; i< ::g->numspritelumps ; i++) + { + if (!(i&63)) + I_Printf ("."); + + patch = (patch_t*)W_CacheLumpNum (::g->firstspritelump+i, PU_CACHE_SHARED); + ::g->spritewidth[i] = SHORT(patch->width)<spriteoffset[i] = SHORT(patch->leftoffset)<spritetopoffset[i] = SHORT(patch->topoffset)<colormaps = (lighttable_t*)DoomLib::Z_Malloc (length, PU_STATIC, 0); + ::g->colormaps = (byte *)( ((int)::g->colormaps + 255)&~0xff); + W_ReadLump (lump,::g->colormaps); +} + + + +// +// R_InitData +// Locates all the lumps +// that will be used by all views +// Must be called after W_Init. +// +void R_InitData (void) +{ + R_InitTextures (); + I_Printf ("\nInitTextures"); + R_InitFlats (); + I_Printf ("\nInitFlats"); + R_InitSpriteLumps (); + I_Printf ("\nInitSprites"); + R_InitColormaps (); + I_Printf ("\nInitColormaps"); +} + + + +// +// R_FlatNumForName +// Retrieval, get a flat number for a flat name. +// +int R_FlatNumForName (const char* name) +{ + int i; + char namet[9]; + + i = W_CheckNumForName (name); + + if (i == -1) + { + namet[8] = 0; + memcpy (namet, name,8); + I_Error ("R_FlatNumForName: %s not found",namet); + } + return i - ::g->firstflat; +} + + + + +// +// R_CheckTextureNumForName +// Check whether texture is available. +// Filter out NoTexture indicator. +// +int R_CheckTextureNumForName (const char *name) +{ + int i; + + // "NoTexture" marker. + if (name[0] == '-') + return 0; + + for (i=0 ; i < ::g->s_numtextures ; i++) + if ( !idStr::Icmpn( ::g->s_textures[i]->name, name, 8 ) ) + return i; + + return -1; +} + + + +// +// R_TextureNumForName +// Calls R_CheckTextureNumForName, +// aborts with error message. +// +int R_TextureNumForName (const char* name) +{ + int i; + + i = R_CheckTextureNumForName (name); + + if (i==-1) + { + I_Error ("R_TextureNumForName: %s not found", + name); + } + return i; +} + + + + +// +// R_PrecacheLevel +// Preloads all relevant graphics for the level. +// + +void R_PrecacheLevel (void) +{ + int i; + int j; + int k; + int lump; + + texture_t* texture; + thinker_t* th; + spriteframe_t* sf; + + if (::g->demoplayback) + return; + + // Precache flats. + std::vector flatpresent(::g->numflats, 0); + + for (i=0 ; i < ::g->numsectors ; i++) + { + flatpresent[::g->sectors[i].floorpic] = 1; + flatpresent[::g->sectors[i].ceilingpic] = 1; + } + + ::g->flatmemory = 0; + + for (i=0 ; i < ::g->numflats ; i++) + { + if (flatpresent[i]) + { + lump = ::g->firstflat + i; + ::g->flatmemory += lumpinfo[lump].size; + W_CacheLumpNum(lump, PU_CACHE_SHARED); + } + } + + // Precache textures. + std::vector texturepresent(::g->s_numtextures, 0); + + for (i=0 ; i < ::g->numsides ; i++) + { + texturepresent[::g->sides[i].toptexture] = 1; + texturepresent[::g->sides[i].midtexture] = 1; + texturepresent[::g->sides[i].bottomtexture] = 1; + } + + // Sky texture is always present. + // Note that F_SKY1 is the name used to + // indicate a sky floor/ceiling as a flat, + // while the sky texture is stored like + // a wall texture, with an episode dependend + // name. + texturepresent[::g->skytexture] = 1; + + ::g->texturememory = 0; + for (i=0 ; i < ::g->s_numtextures ; i++) + { + if (!texturepresent[i]) + continue; + + texture = ::g->s_textures[i]; + + for (j=0 ; jpatchcount ; j++) + { + lump = texture->patches[j].patch; + ::g->texturememory += lumpinfo[lump].size; + W_CacheLumpNum(lump , PU_CACHE_SHARED); + } + } + + // Precache ::g->sprites. + std::vector spritepresent(::g->numsprites, 0); + + for (th = ::g->thinkercap.next ; th != &::g->thinkercap ; th=th->next) + { + if (th->function.acp1 == (actionf_p1)P_MobjThinker) + spritepresent[((mobj_t *)th)->sprite] = 1; + } + + ::g->spritememory = 0; + for (i=0 ; i < ::g->numsprites ; i++) + { + if (!spritepresent[i]) + continue; + + for (j=0 ; j < ::g->sprites[i].numframes ; j++) + { + sf = &::g->sprites[i].spriteframes[j]; + for (k=0 ; k<8 ; k++) + { + lump = ::g->firstspritelump + sf->lump[k]; + ::g->spritememory += lumpinfo[lump].size; + W_CacheLumpNum(lump , PU_CACHE_SHARED); + } + } + } +} + + + + + diff --git a/doomclassic/doom/r_data.h b/doomclassic/doom/r_data.h new file mode 100644 index 00000000..3018c41f --- /dev/null +++ b/doomclassic/doom/r_data.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_DATA__ +#define __R_DATA__ + +#include "r_defs.h" +#include "r_state.h" + +#ifdef __GNUG__ +#pragma interface +#endif + +// Retrieve column data for span blitting. +byte* +R_GetColumn +( int tex, + int col ); + + +// I/O, setting up the stuff. +void R_InitData (void); +void R_PrecacheLevel (void); + + +// Retrieval. +// Floor/ceiling opaque texture tiles, +// lookup by name. For animation? +int R_FlatNumForName ( const char* name); + + +// Called by P_Ticker for switches and animations, +// returns the texture number for the texture name. +int R_TextureNumForName (const char *name); +int R_CheckTextureNumForName ( const char *name); + +#endif + diff --git a/doomclassic/doom/r_defs.h b/doomclassic/doom/r_defs.h new file mode 100644 index 00000000..25e1a15e --- /dev/null +++ b/doomclassic/doom/r_defs.h @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_DEFS__ +#define __R_DEFS__ + +#include "Precompiled.h" + +// Screenwidth. +#include "doomdef.h" + +// Some more or less basic data types +// we depend on. +#include "m_fixed.h" + +// We rely on the thinker data struct +// to handle sound origins in sectors. +#include "d_think.h" +// SECTORS do store MObjs anyway. +#include "p_mobj.h" + + +// Silhouette, needed for clipping Segs (mainly) +// and sprites representing things. +#define SIL_NONE 0 +#define SIL_BOTTOM 1 +#define SIL_TOP 2 +#define SIL_BOTH 3 + +#define MAXDRAWSEGS 1280 + + + + + +// +// INTERNAL MAP TYPES +// used by play and refresh +// + +// +// Your plain vanilla vertex. +// Note: transformed values not buffered locally, +// like some DOOM-alikes ("wt", "WebView") did. +// +typedef struct +{ + fixed_t x; + fixed_t y; + +} vertex_t; + + +// Forward of LineDefs, for Sectors. +struct line_s; + +// Each sector has a degenmobj_t in its center +// for sound origin purposes. +// I suppose this does not handle sound from +// moving objects (doppler), because +// position is prolly just buffered, not +// updated. +typedef struct +{ + thinker_t thinker; // not used for anything + fixed_t x; + fixed_t y; + fixed_t z; + +} degenmobj_t; + +// +// The SECTORS record, at runtime. +// Stores things/mobjs. +// +typedef struct +{ + fixed_t floorheight; + fixed_t ceilingheight; + short floorpic; + short ceilingpic; + short lightlevel; + short special; + short tag; + + // 0 = untraversed, 1,2 = sndlines -1 + int soundtraversed; + + // thing that made a sound (or null) + mobj_t* soundtarget; + + // mapblock bounding box for height changes + int blockbox[4]; + + // origin for any sounds played by the sector + degenmobj_t soundorg; + + // if == validcount, already checked + int validcount; + + // list of mobjs in sector + mobj_t* thinglist; + + // thinker_t for reversable actions + void* specialdata; + + int linecount; + struct line_s** lines; // [linecount] size + +} sector_t; + + + + +// +// The SideDef. +// + +typedef struct +{ + // add this to the calculated texture column + fixed_t textureoffset; + + // add this to the calculated texture top + fixed_t rowoffset; + + // Texture indices. + // We do not maintain names here. + short toptexture; + short bottomtexture; + short midtexture; + + // Sector the SideDef is facing. + sector_t* sector; + +} side_t; + + + +// +// Move clipping aid for LineDefs. +// +typedef enum +{ + ST_HORIZONTAL, + ST_VERTICAL, + ST_POSITIVE, + ST_NEGATIVE + +} slopetype_t; + + + +typedef struct line_s +{ + // Vertices, from v1 to v2. + vertex_t* v1; + vertex_t* v2; + + // Precalculated v2 - v1 for side checking. + fixed_t dx; + fixed_t dy; + + // Animation related. + short flags; + short special; + short tag; + + // Visual appearance: SideDefs. + // sidenum[1] will be -1 if one sided + short sidenum[2]; + + // Neat. Another bounding box, for the extent + // of the LineDef. + fixed_t bbox[4]; + + // To aid move clipping. + slopetype_t slopetype; + + // Front and back sector. + // Note: redundant? Can be retrieved from SideDefs. + sector_t* frontsector; + sector_t* backsector; + + // if == validcount, already checked + int validcount; + + // thinker_t for reversable actions + void* specialdata; +} line_t; + + + + +// +// A SubSector. +// References a Sector. +// Basically, this is a list of LineSegs, +// indicating the visible walls that define +// (all or some) sides of a convex BSP leaf. +// +typedef struct subsector_s +{ + sector_t* sector; + short numlines; + short firstline; + +} subsector_t; + + + +// +// The LineSeg. +// +typedef struct +{ + vertex_t* v1; + vertex_t* v2; + + fixed_t offset; + + angle_t angle; + + side_t* sidedef; + line_t* linedef; + + // Sector references. + // Could be retrieved from linedef, too. + // backsector is NULL for one sided lines + sector_t* frontsector; + sector_t* backsector; + +} seg_t; + + + +// +// BSP node. +// +typedef struct +{ + // Partition line. + fixed_t x; + fixed_t y; + fixed_t dx; + fixed_t dy; + + // Bounding box for each child. + fixed_t bbox[2][4]; + + // If NF_SUBSECTOR its a subsector. + unsigned short children[2]; + +} node_t; + + + + +// posts are runs of non masked source pixels +typedef struct +{ + byte topdelta; // -1 is the last post in a column + byte length; // length data bytes follows +} post_t; + +// postColumn_t is a list of 0 or more post_t, (byte)-1 terminated +typedef post_t postColumn_t; + + + +// PC direct to screen pointers +//B UNUSED - keep till detailshift in r_draw.c resolved +//extern byte* destview; +//extern byte* destscreen; + + + + + +// +// OTHER TYPES +// + +// This could be wider for >8 bit display. +// Indeed, true color support is posibble +// precalculating 24bpp lightmap/colormap LUT. +// from darkening PLAYPAL to all black. +// Could even us emore than 32 levels. +typedef byte lighttable_t; + + + + +// +// ? +// +typedef struct drawseg_s +{ + seg_t* curline; + int x1; + int x2; + + fixed_t scale1; + fixed_t scale2; + fixed_t scalestep; + + // 0=none, 1=bottom, 2=top, 3=both + int silhouette; + + // do not clip sprites above this + fixed_t bsilheight; + + // do not clip sprites below this + fixed_t tsilheight; + + // Pointers to lists for sprite clipping, + // all three adjusted so [x1] is first value. + short* sprtopclip; + short* sprbottomclip; + short* maskedtexturecol; + +} drawseg_t; + + + +// Patches. +// A patch holds one or more columns. +// Patches are used for sprites and all masked pictures, +// and we compose textures from the TEXTURE1/2 lists +// of patches. +struct patch_t +{ + short width; // bounding box size + short height; + short leftoffset; // pixels to the left of origin + short topoffset; // pixels below the origin + int columnofs[8]; // only [width] used + // the [0] is &columnofs[width] +}; + + + + + + + +// A vissprite_t is a thing +// that will be drawn during a refresh. +// I.e. a sprite object that is partly visible. +typedef struct vissprite_s +{ + int suck; //Compiler opt fix...wtf + + // Doubly linked list. + struct vissprite_s* prev; + struct vissprite_s* next; + + int x1; + int x2; + + // for line side calculation + fixed_t gx; + fixed_t gy; + + // global bottom / top for silhouette clipping + fixed_t gz; + fixed_t gzt; + + // horizontal position of x1 + fixed_t startfrac; + + fixed_t scale; + + // negative if flipped + fixed_t xiscale; + + fixed_t texturemid; + int patch; + + // for color translation and shadow draw, + // maxbright frames as well + lighttable_t* colormap; + + int mobjflags; +} vissprite_t; + + +// +// Sprites are patches with a special naming convention +// so they can be recognized by R_InitSprites. +// The base name is NNNNFx or NNNNFxFx, with +// x indicating the rotation, x = 0, 1-7. +// The sprite and frame specified by a thing_t +// is range checked at run time. +// A sprite is a patch_t that is assumed to represent +// a three dimensional object and may have multiple +// rotations pre drawn. +// Horizontal flipping is used to save space, +// thus NNNNF2F5 defines a mirrored patch. +// Some sprites will only have one picture used +// for all views: NNNNF0 +// +typedef struct +{ + // If false use 0 for any position. + // Note: as eight entries are available, + // we might as well insert the same name eight times. + qboolean rotate; + + // Lump to use for view angles 0-7. + short lump[8]; + + // Flip bit (1 = flip) to use for view angles 0-7. + byte flip[8]; + +} spriteframe_t; + + + +// +// A sprite definition: +// a number of animation frames. +// +typedef struct +{ + int numframes; + spriteframe_t* spriteframes; + +} spritedef_t; + + + +// +// Now what is a visplane, anyway? +// +typedef struct +{ + fixed_t height; + int picnum; + int lightlevel; + int minx; + int maxx; + + // leave pads for [minx-1]/[maxx+1] + int nervePad1; + int nervePad2; + byte nervePad3; + byte nervePad4; + byte nervePad5; + + byte pad1; + // Here lies the rub for all + // dynamic resize/change of resolution. + //unsigned short top[65535]; // [SCREENWIDTH]; + unsigned short top[SCREENWIDTH]; + byte pad2; + byte pad3; + // See above. + //unsigned short bottom[65535]; // [SCREENWIDTH]; + unsigned short bottom[SCREENWIDTH]; + byte pad4; + +} visplane_t; + + + + +#endif + diff --git a/doomclassic/doom/r_draw.cpp b/doomclassic/doom/r_draw.cpp new file mode 100644 index 00000000..4f69c4f3 --- /dev/null +++ b/doomclassic/doom/r_draw.cpp @@ -0,0 +1,839 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include "doomdef.h" + +#include "i_system.h" +#include "z_zone.h" +#include "w_wad.h" + +#include "r_local.h" + +// Needs access to LFB (guess what). +#include "v_video.h" + +// State. +#include "doomstat.h" + + +// ? + +// status bar height at bottom of screen + +// +// All drawing to the view buffer is accomplished in this file. +// The other refresh files only know about ccordinates, +// not the architecture of the frame buffer. +// Conveniently, the frame buffer is a linear one, +// and we need only the base address, +// and the total size == width*height*depth/8., +// + + + +// Color tables for different ::g->players, +// translate a limited part to another +// (color ramps used for suit colors). +// + + + + +// +// R_DrawColumn +// Source is the top of the column to scale. +// + +// first pixel in a column (possibly virtual) + +// just for profiling + +// +// A column is a vertical slice/span from a wall texture that, +// given the DOOM style restrictions on the view orientation, +// will always have constant z depth. +// Thus a special case loop for very fast rendering can +// be used. It has also been used with Wolfenstein 3D. +// +void R_DrawColumn ( lighttable_t * dc_colormap, + byte * dc_source ) +{ + int count; + byte* dest; + fixed_t frac; + fixed_t fracstep; + + count = ::g->dc_yh - ::g->dc_yl; + + // Zero length, column does not exceed a pixel. + if (count >= 0) { + //return; + + #ifdef RANGECHECK + if ((unsigned)::g->dc_x >= SCREENWIDTH + || ::g->dc_yl < 0 + || ::g->dc_yh >= SCREENHEIGHT) + I_Error ("R_DrawColumn: %i to %i at %i", ::g->dc_yl, ::g->dc_yh, ::g->dc_x); + #endif + + // Framebuffer destination address. + // Use ::g->ylookup LUT to avoid multiply with ScreenWidth. + // Use ::g->columnofs LUT for subwindows? + dest = ::g->ylookup[::g->dc_yl] + ::g->columnofs[::g->dc_x]; + + // Determine scaling, + // which is the only mapping to be done. + fracstep = ::g->dc_iscale; + frac = ::g->dc_texturemid + (::g->dc_yl-::g->centery)*fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do + { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + const int truncated1 = frac >> FRACBITS; + const int wrapped1 = truncated1 & 127; + + *dest = dc_colormap[dc_source[wrapped1]]; + + frac += fracstep; + dest += SCREENWIDTH; + } while (count--); + } +} + + + +// UNUSED. +// Loop unrolled. +#if 0 +void R_DrawColumn (void) +{ + int count; + byte* source; + byte* dest; + byte* colormap; + + unsigned frac; + unsigned fracstep; + unsigned fracstep2; + unsigned fracstep3; + unsigned fracstep4; + + count = ::g->dc_yh - ::g->dc_yl + 1; + + source = ::g->dc_source; + colormap = ::g->dc_colormap; + dest = ::g->ylookup[::g->dc_yl] + ::g->columnofs[::g->dc_x]; + + fracstep = ::g->dc_iscale<<9; + frac = (::g->dc_texturemid + (::g->dc_yl-::g->centery)*::g->dc_iscale)<<9; + + fracstep2 = fracstep+fracstep; + fracstep3 = fracstep2+fracstep; + fracstep4 = fracstep3+fracstep; + + while (count >= 8) + { + dest[0] = colormap[source[frac>>25]]; + dest[SCREENWIDTH] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*2] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*3] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + + dest[SCREENWIDTH*4] = colormap[source[frac>>25]]; + dest[SCREENWIDTH*5] = colormap[source[(frac+fracstep)>>25]]; + dest[SCREENWIDTH*6] = colormap[source[(frac+fracstep2)>>25]]; + dest[SCREENWIDTH*7] = colormap[source[(frac+fracstep3)>>25]]; + + frac += fracstep4; + dest += SCREENWIDTH*8; + count -= 8; + } + + while (count > 0) + { + *dest = colormap[source[frac>>25]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } +} +#endif + + +void R_DrawColumnLow ( lighttable_t * dc_colormap, + byte * dc_source ) +{ + int count; + byte* dest; + byte* dest2; + fixed_t frac; + fixed_t fracstep; + + count = ::g->dc_yh - ::g->dc_yl; + + // Zero length. + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned)::g->dc_x >= SCREENWIDTH + || ::g->dc_yl < 0 + || ::g->dc_yh >= SCREENHEIGHT) + { + + I_Error ("R_DrawColumn: %i to %i at %i", ::g->dc_yl, ::g->dc_yh, ::g->dc_x); + } + // ::g->dccount++; +#endif + // Blocky mode, need to multiply by 2. + ::g->dc_x <<= 1; + + dest = ::g->ylookup[::g->dc_yl] + ::g->columnofs[::g->dc_x]; + dest2 = ::g->ylookup[::g->dc_yl] + ::g->columnofs[::g->dc_x+1]; + + fracstep = ::g->dc_iscale; + frac = ::g->dc_texturemid + (::g->dc_yl-::g->centery)*fracstep; + + do + { + // Hack. Does not work corretly. + *dest2 = *dest = ::g->dc_colormap[::g->dc_source[(frac>>FRACBITS)&127]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + } while (count--); +} + + +// +// Spectre/Invisibility. +// + + + + + +// +// Framebuffer postprocessing. +// Creates a fuzzy image by copying pixels +// from adjacent ones to left and right. +// Used with an all black colormap, this +// could create the SHADOW effect, +// i.e. spectres and invisible ::g->players. +// +void R_DrawFuzzColumn ( lighttable_t * dc_colormap, + byte * dc_source ) +{ + int count; + byte* dest; + fixed_t frac; + fixed_t fracstep; + + // Adjust borders. Low... + if (!::g->dc_yl) + ::g->dc_yl = 1; + + // .. and high. + if (::g->dc_yh == ::g->viewheight-1) + ::g->dc_yh = ::g->viewheight - 2; + + count = ::g->dc_yh - ::g->dc_yl; + + // Zero length. + if (count < 0) + return; + + +#ifdef RANGECHECK + if ((unsigned)::g->dc_x >= SCREENWIDTH + || ::g->dc_yl < 0 || ::g->dc_yh >= SCREENHEIGHT) + { + I_Error ("R_DrawFuzzColumn: %i to %i at %i", + ::g->dc_yl, ::g->dc_yh, ::g->dc_x); + } +#endif + + + // Keep till ::g->detailshift bug in blocky mode fixed, + // or blocky mode removed. + /* WATCOM code + if (::g->detailshift) + { + if (::g->dc_x & 1) + { + outpw (GC_INDEX,GC_READMAP+(2<<8) ); + outp (SC_INDEX+1,12); + } + else + { + outpw (GC_INDEX,GC_READMAP); + outp (SC_INDEX+1,3); + } + dest = destview + ::g->dc_yl*80 + (::g->dc_x>>1); + } + else + { + outpw (GC_INDEX,GC_READMAP+((::g->dc_x&3)<<8) ); + outp (SC_INDEX+1,1<<(::g->dc_x&3)); + dest = destview + ::g->dc_yl*80 + (::g->dc_x>>2); + }*/ + + + // Does not work with blocky mode. + dest = ::g->ylookup[::g->dc_yl] + ::g->columnofs[::g->dc_x]; + + // Looks familiar. + fracstep = ::g->dc_iscale; + frac = ::g->dc_texturemid + (::g->dc_yl-::g->centery)*fracstep; + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + do + { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + *dest = ::g->colormaps[6*256+dest[::g->fuzzoffset[::g->fuzzpos]]]; + + // Clamp table lookup index. + if (++::g->fuzzpos == FUZZTABLE) + ::g->fuzzpos = 0; + + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + + + + +// +// R_DrawTranslatedColumn +// Used to draw player ::g->sprites +// with the green colorramp mapped to others. +// Could be used with different translation +// tables, e.g. the lighter colored version +// of the BaronOfHell, the HellKnight, uses +// identical ::g->sprites, kinda brightened up. +// + +void R_DrawTranslatedColumn ( lighttable_t * dc_colormap, + byte * dc_source ) +{ + int count; + byte* dest; + fixed_t frac; + fixed_t fracstep; + + count = ::g->dc_yh - ::g->dc_yl; + if (count < 0) + return; + +#ifdef RANGECHECK + if ((unsigned)::g->dc_x >= SCREENWIDTH + || ::g->dc_yl < 0 + || ::g->dc_yh >= SCREENHEIGHT) + { + I_Error ( "R_DrawColumn: %i to %i at %i", + ::g->dc_yl, ::g->dc_yh, ::g->dc_x); + } + +#endif + + + // WATCOM VGA specific. + /* Keep for fixing. + if (::g->detailshift) + { + if (::g->dc_x & 1) + outp (SC_INDEX+1,12); + else + outp (SC_INDEX+1,3); + + dest = destview + ::g->dc_yl*80 + (::g->dc_x>>1); + } + else + { + outp (SC_INDEX+1,1<<(::g->dc_x&3)); + + dest = destview + ::g->dc_yl*80 + (::g->dc_x>>2); + }*/ + + + // FIXME. As above. + dest = ::g->ylookup[::g->dc_yl] + ::g->columnofs[::g->dc_x]; + + // Looks familiar. + fracstep = ::g->dc_iscale; + frac = ::g->dc_texturemid + (::g->dc_yl-::g->centery)*fracstep; + + // Here we do an additional index re-mapping. + do + { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY ::g->sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + *dest = dc_colormap[::g->dc_translation[dc_source[frac>>FRACBITS]]]; + dest += SCREENWIDTH; + + frac += fracstep; + } while (count--); +} + + + + +// +// R_InitTranslationTables +// Creates the translation tables to map +// the green color ramp to gray, brown, red. +// Assumes a given structure of the PLAYPAL. +// Could be read from a lump instead. +// +void R_InitTranslationTables (void) +{ + int i; + + ::g->translationtables = (byte*)DoomLib::Z_Malloc (256*3+255, PU_STATIC, 0); + ::g->translationtables = (byte *)(( (int)::g->translationtables + 255 )& ~255); + + // translate just the 16 green colors + for (i=0 ; i<256 ; i++) + { + if (i >= 0x70 && i<= 0x7f) + { + // map green ramp to gray, brown, red + ::g->translationtables[i] = 0x60 + (i&0xf); + ::g->translationtables [i+256] = 0x40 + (i&0xf); + ::g->translationtables [i+512] = 0x20 + (i&0xf); + } + else + { + // Keep all other colors as is. + ::g->translationtables[i] = ::g->translationtables[i+256] + = ::g->translationtables[i+512] = i; + } + } +} + + + + +// +// R_DrawSpan +// With DOOM style restrictions on view orientation, +// the floors and ceilings consist of horizontal slices +// or spans with constant z depth. +// However, rotation around the world z axis is possible, +// thus this mapping, while simpler and faster than +// perspective correct texture mapping, has to traverse +// the texture at an angle in all but a few cases. +// In consequence, flats are not stored by column (like walls), +// and the inner loop has to step in texture space u and v. +// + + + +// start of a 64*64 tile image + +// just for profiling + + +// +// Draws the actual span. +void R_DrawSpan ( fixed_t xfrac, + fixed_t yfrac, + fixed_t ds_y, + int ds_x1, + int ds_x2, + fixed_t ds_xstep, + fixed_t ds_ystep, + lighttable_t * ds_colormap, + byte * ds_source ) +{ + byte* dest; + int count; + int spot; + +#ifdef RANGECHECK + if (::g->ds_x2 < ::g->ds_x1 + || ::g->ds_x1<0 + || ::g->ds_x2>=SCREENWIDTH + || (unsigned)::g->ds_y>SCREENHEIGHT) + { + I_Error( "R_DrawSpan: %i to %i at %i", + ::g->ds_x1,::g->ds_x2,::g->ds_y); + } + // ::g->dscount++; +#endif + + dest = ::g->ylookup[::g->ds_y] + ::g->columnofs[::g->ds_x1]; + + // We do not check for zero spans here? + count = ds_x2 - g->ds_x1; + + if ( ds_x2 < ds_x1 ) { + return; // SMF - think this is the sky + } + + do + { + // Current texture index in u,v. + spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + *dest++ = ds_colormap[ds_source[spot]]; + + // Next step in u,v. + xfrac += ds_xstep; + yfrac += ds_ystep; + + } while (count--); +} + + + +// UNUSED. +// Loop unrolled by 4. +#if 0 +void R_DrawSpan (void) +{ + unsigned position, step; + + byte* source; + byte* colormap; + byte* dest; + + unsigned count; + usingned spot; + unsigned value; + unsigned temp; + unsigned xtemp; + unsigned ytemp; + + position = ((::g->ds_xfrac<<10)&0xffff0000) | ((::g->ds_yfrac>>6)&0xffff); + step = ((::g->ds_xstep<<10)&0xffff0000) | ((::g->ds_ystep>>6)&0xffff); + + source = ::g->ds_source; + colormap = ::g->ds_colormap; + dest = ::g->ylookup[::g->ds_y] + ::g->columnofs[::g->ds_x1]; + count = ::g->ds_x2 - ::g->ds_x1 + 1; + + while (count >= 4) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[0] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[1] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[2] = colormap[source[spot]]; + + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + dest[3] = colormap[source[spot]]; + + count -= 4; + dest += 4; + } + while (count > 0) + { + ytemp = position>>4; + ytemp = ytemp & 4032; + xtemp = position>>26; + spot = xtemp | ytemp; + position += step; + *dest++ = colormap[source[spot]]; + count--; + } +} +#endif + + +// +// Again.. +// +void R_DrawSpanLow ( fixed_t xfrac, + fixed_t yfrac, + fixed_t ds_y, + int ds_x1, + int ds_x2, + fixed_t ds_xstep, + fixed_t ds_ystep, + lighttable_t * ds_colormap, + byte * ds_source ) +{ + byte* dest; + int count; + int spot; + +#ifdef RANGECHECK + if (::g->ds_x2 < ::g->ds_x1 + || ::g->ds_x1<0 + || ::g->ds_x2>=SCREENWIDTH + || (unsigned)::g->ds_y>SCREENHEIGHT) + { + I_Error( "R_DrawSpan: %i to %i at %i", + ::g->ds_x1,::g->ds_x2,::g->ds_y); + } + // ::g->dscount++; +#endif + + // Blocky mode, need to multiply by 2. + ::g->ds_x1 <<= 1; + ::g->ds_x2 <<= 1; + + dest = ::g->ylookup[::g->ds_y] + ::g->columnofs[::g->ds_x1]; + + + count = ::g->ds_x2 - ::g->ds_x1; + do + { + spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63); + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + *dest++ = ::g->ds_colormap[::g->ds_source[spot]]; + *dest++ = ::g->ds_colormap[::g->ds_source[spot]]; + + xfrac += ::g->ds_xstep; + yfrac += ::g->ds_ystep; + + } while (count--); +} + +// +// R_InitBuffer +// Creats lookup tables that avoid +// multiplies and other hazzles +// for getting the framebuffer address +// of a pixel to draw. +// +void +R_InitBuffer +( int width, + int height ) +{ + int i; + + // Handle resize, + // e.g. smaller view windows + // with border and/or status bar. + ::g->viewwindowx = (SCREENWIDTH-width) >> 1; + + // Column offset. For windows. + for (i=0 ; icolumnofs[i] = ::g->viewwindowx + i; + + // Samw with base row offset. + if (width == SCREENWIDTH) + ::g->viewwindowy = 0; + else + ::g->viewwindowy = (SCREENHEIGHT-SBARHEIGHT-height) >> 1; + + // Preclaculate all row offsets. + for (i=0 ; iylookup[i] = ::g->screens[0] + (i+::g->viewwindowy)*SCREENWIDTH; +} + + + + +// +// R_FillBackScreen +// Fills the back screen with a pattern +// for variable screen sizes +// Also draws a beveled edge. +// +void R_FillBackScreen (void) +{ + byte* src; + byte* dest; + int x; + int y; + int width, height, windowx, windowy; + patch_t* patch; + + // DOOM border patch. + char name1[] = "FLOOR7_2"; + // DOOM II border patch. + char name2[] = "GRNROCK"; + + char* name; + + if (::g->scaledviewwidth == SCREENWIDTH) + return; + + if ( ::g->gamemode == commercial) + name = name2; + else + name = name1; + + src = (byte*)W_CacheLumpName (name, PU_CACHE_SHARED); + dest = ::g->screens[1]; + + for (y=0 ; yscaledviewwidth / GLOBAL_IMAGE_SCALER; + height = ::g->viewheight / GLOBAL_IMAGE_SCALER; + windowx = ::g->viewwindowx / GLOBAL_IMAGE_SCALER; + windowy = ::g->viewwindowy / GLOBAL_IMAGE_SCALER; + + patch = (patch_t*)W_CacheLumpName ("brdr_t",PU_CACHE_SHARED); + for (x=0 ; xscreens[0]+ofs, ::g->screens[1]+ofs, count); +} + + +// +// R_DrawViewBorder +// Draws the border around the view +// for different size windows? +// +void +V_MarkRect +( int x, + int y, + int width, + int height ); + +void R_DrawViewBorder (void) +{ + int top; + int side; + int ofs; + int i; + + if (::g->scaledviewwidth == SCREENWIDTH) + return; + + top = ((SCREENHEIGHT-SBARHEIGHT)-::g->viewheight)/2; + side = (SCREENWIDTH-::g->scaledviewwidth)/2; + + // copy top and one line of left side + R_VideoErase (0, top*SCREENWIDTH+side); + + // copy one line of right side and bottom + ofs = (::g->viewheight+top)*SCREENWIDTH-side; + R_VideoErase (ofs, top*SCREENWIDTH+side); + + // copy ::g->sides using wraparound + ofs = top*SCREENWIDTH + SCREENWIDTH-side; + side <<= 1; + + for (i=1 ; i < ::g->viewheight ; i++) + { + R_VideoErase (ofs, side); + ofs += SCREENWIDTH; + } + + // ? + V_MarkRect (0,0,SCREENWIDTH, SCREENHEIGHT-SBARHEIGHT); +} + + + diff --git a/doomclassic/doom/r_draw.h b/doomclassic/doom/r_draw.h new file mode 100644 index 00000000..97132e26 --- /dev/null +++ b/doomclassic/doom/r_draw.h @@ -0,0 +1,141 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_DRAW__ +#define __R_DRAW__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +extern lighttable_t* dc_colormap; +extern int dc_x; +extern int dc_yl; +extern int dc_yh; +extern fixed_t dc_iscale; +extern fixed_t dc_texturemid; + +// first pixel in a column +extern byte* dc_source; + + +// The span blitting interface. +// Hook in assembler or system specific BLT +// here. +void R_DrawColumn ( lighttable_t * dc_colormap, + byte * dc_source ); + +void R_DrawColumnLow ( lighttable_t * dc_colormap, + byte * dc_source ); + +// The Spectre/Invisibility effect. +void R_DrawFuzzColumn ( lighttable_t * dc_colormap, + byte * dc_source ); +void R_DrawFuzzColumnLow ( lighttable_t * dc_colormap, + byte * dc_source ); + +// Draw with color translation tables, +// for player sprite rendering, +// Green/Red/Blue/Indigo shirts. +void R_DrawTranslatedColumn ( lighttable_t * dc_colormap, + byte * dc_source ); +void R_DrawTranslatedColumnLow ( lighttable_t * dc_colormap, + byte * dc_source ); + +void +R_VideoErase +( unsigned ofs, + int count ); + +extern int ds_y; +extern int ds_x1; +extern int ds_x2; + +extern lighttable_t* ds_colormap; + +extern fixed_t ds_xfrac; +extern fixed_t ds_yfrac; +extern fixed_t ds_xstep; +extern fixed_t ds_ystep; + +// start of a 64*64 tile image +extern byte* ds_source; + +extern byte* translationtables; +extern byte* dc_translation; + + +// Span blitting for rows, floor/ceiling. +// No Sepctre effect needed. +void R_DrawSpan ( + fixed_t xfrac, + fixed_t yfrac, + fixed_t ds_y, + int ds_x1, + int ds_x2, + fixed_t ds_xstep, + fixed_t ds_ystep, + lighttable_t * ds_colormap, + byte * ds_source ); + +// Low resolution mode, 160x200? +void R_DrawSpanLow ( fixed_t xfrac, + fixed_t yfrac, + fixed_t ds_y, + int ds_x1, + int ds_x2, + fixed_t ds_xstep, + fixed_t ds_ystep, + lighttable_t * ds_colormap, + byte * ds_source ); + + +void +R_InitBuffer +( int width, + int height ); + + +// Initialize color translation tables, +// for player rendering etc. +void R_InitTranslationTables (void); + + + +// Rendering function. +void R_FillBackScreen (void); + +// If the view size is not full screen, draws a border around it. +void R_DrawViewBorder (void); + + + +#endif + diff --git a/doomclassic/doom/r_local.h b/doomclassic/doom/r_local.h new file mode 100644 index 00000000..aeec07d0 --- /dev/null +++ b/doomclassic/doom/r_local.h @@ -0,0 +1,55 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_LOCAL__ +#define __R_LOCAL__ + +// Binary Angles, sine/cosine/atan lookups. +#include "tables.h" + +// Screen size related parameters. +#include "doomdef.h" + +// Include the refresh/render data structs. +#include "r_data.h" + + + +// +// Separate header file for each module. +// +#include "r_main.h" +#include "r_bsp.h" +#include "r_segs.h" +#include "r_plane.h" +#include "r_data.h" +#include "r_things.h" +#include "r_draw.h" + +#endif // __R_LOCAL__ + diff --git a/doomclassic/doom/r_main.cpp b/doomclassic/doom/r_main.cpp new file mode 100644 index 00000000..5d55909f --- /dev/null +++ b/doomclassic/doom/r_main.cpp @@ -0,0 +1,897 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + + +#include +#include + + +#include "doomdef.h" +#include "d_net.h" + +#include "m_bbox.h" + +#include "r_local.h" +#include "r_sky.h" +#include "i_system.h" + + + + +// Fineangles in the SCREENWIDTH wide window. + + + + +// increment every time a check is made + + + + + +// just for profiling purposes + + + + + + +// 0 = high, 1 = low + +// +// precalculated math tables +// + +// The ::g->viewangletox[::g->viewangle + FINEANGLES/4] lookup +// maps the visible view angles to screen X coordinates, +// flattening the arc to a flat ::g->projection plane. +// There will be many angles mapped to the same X. + +// The xtoviewangleangle[] table maps a screen pixel +// to the lowest ::g->viewangle that maps back to x ranges +// from ::g->clipangle to -::g->clipangle. + + +// UNUSED. +// The finetangentgent[angle+FINEANGLES/4] table +// holds the fixed_t tangent values for view angles, +// ranging from MININT to 0 to MAXINT. +// fixed_t finetangent[FINEANGLES/2]; + +// fixed_t finesine[5*FINEANGLES/4]; +const fixed_t* finecosine = &finesine[FINEANGLES/4]; + + + +// bumped light from gun blasts + + + +void (*colfunc) (lighttable_t * dc_colormap, + byte * dc_source); +void (*basecolfunc) (lighttable_t * dc_colormap, + byte * dc_source); +void (*fuzzcolfunc) (lighttable_t * dc_colormap, + byte * dc_source); +void (*transcolfunc) (lighttable_t * dc_colormap, + byte * dc_source); +void (*spanfunc) (fixed_t xfrac, + fixed_t yfrac, + fixed_t ds_y, + int ds_x1, + int ds_x2, + fixed_t ds_xstep, + fixed_t ds_ystep, + lighttable_t * ds_colormap, + byte * ds_source); + + + +// +// R_AddPointToBox +// Expand a given bbox +// so that it encloses a given point. +// +void +R_AddPointToBox +( int x, + int y, + fixed_t* box ) +{ + if (x< box[BOXLEFT]) + box[BOXLEFT] = x; + if (x> box[BOXRIGHT]) + box[BOXRIGHT] = x; + if (y< box[BOXBOTTOM]) + box[BOXBOTTOM] = y; + if (y> box[BOXTOP]) + box[BOXTOP] = y; +} + + +// +// R_PointOnSide +// Traverse BSP (sub) tree, +// check point against partition plane. +// Returns side 0 (front) or 1 (back). +// +int +R_PointOnSide +( fixed_t x, + fixed_t y, + node_t* node ) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!node->dx) + { + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + if (!node->dy) + { + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + // Try to quickly decide by looking at sign bits. + if ( (node->dy ^ node->dx ^ dx ^ dy)&0x80000000 ) + { + if ( (node->dy ^ dx) & 0x80000000 ) + { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul ( node->dy>>FRACBITS , dx ); + right = FixedMul ( dy , node->dx>>FRACBITS ); + + if (right < left) + { + // front side + return 0; + } + // back side + return 1; +} + + +int +R_PointOnSegSide +( fixed_t x, + fixed_t y, + seg_t* line ) +{ + fixed_t lx; + fixed_t ly; + fixed_t ldx; + fixed_t ldy; + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + lx = line->v1->x; + ly = line->v1->y; + + ldx = line->v2->x - lx; + ldy = line->v2->y - ly; + + if (!ldx) + { + if (x <= lx) + return ldy > 0; + + return ldy < 0; + } + if (!ldy) + { + if (y <= ly) + return ldx < 0; + + return ldx > 0; + } + + dx = (x - lx); + dy = (y - ly); + + // Try to quickly decide by looking at sign bits. + if ( (ldy ^ ldx ^ dx ^ dy)&0x80000000 ) + { + if ( (ldy ^ dx) & 0x80000000 ) + { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul ( ldy>>FRACBITS , dx ); + right = FixedMul ( dy , ldx>>FRACBITS ); + + if (right < left) + { + // front side + return 0; + } + // back side + return 1; +} + + +// +// R_PointToAngle +// To get a global angle from cartesian coordinates, +// the coordinates are flipped until they are in +// the first octant of the coordinate system, then +// the y (<=x) is scaled and divided by x to get a +// tangent (slope) value which is looked up in the +// tantoangle[] table. + +// + + + + +angle_t +R_PointToAngle +( fixed_t x, + fixed_t y ) +{ + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + x -= GetViewX(); + y -= GetViewY(); + + if ( (!x) && (!y) ) + return 0; + + if (x>= 0) + { + // x >=0 + if (y>= 0) + { + // y>= 0 + + if (x>y) + { + // octant 0 + return tantoangle[ SlopeDiv(y,x)]; + } + else + { + // octant 1 + return ANG90-1-tantoangle[ SlopeDiv(x,y)]; + } + } + else + { + // y<0 + y = -y; + + if (x>y) + { + // octant 8 + return -tantoangle[SlopeDiv(y,x)]; // // ALANHACK UNSIGNED + } + else + { + // octant 7 + return ANG270+tantoangle[ SlopeDiv(x,y)]; + } + } + } + else + { + // x<0 + x = -x; + + if (y>= 0) + { + // y>= 0 + if (x>y) + { + // octant 3 + return ANG180-1-tantoangle[ SlopeDiv(y,x)]; + } + else + { + // octant 2 + return ANG90+ tantoangle[ SlopeDiv(x,y)]; + } + } + else + { + // y<0 + y = -y; + + if (x>y) + { + // octant 4 + return ANG180+tantoangle[ SlopeDiv(y,x)]; + } + else + { + // octant 5 + return ANG270-1-tantoangle[ SlopeDiv(x,y)]; + } + } + } + return 0; +} + + +angle_t +R_PointToAngle2 +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2 ) +{ + extern void SetViewX( fixed_t ); extern void SetViewY( fixed_t ); + SetViewX( x1 ); + SetViewY( y1 ); + + return R_PointToAngle (x2, y2); +} + + +fixed_t +R_PointToDist +( fixed_t x, + fixed_t y ) +{ + int angle; + fixed_t dx; + fixed_t dy; + fixed_t temp; + fixed_t dist; + + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + dx = abs(x - GetViewX()); + dy = abs(y - GetViewY()); + + if (dy>dx) + { + temp = dx; + dx = dy; + dy = temp; + } + + angle = (tantoangle[ FixedDiv(dy,dx)>>DBITS ]+ANG90) >> ANGLETOFINESHIFT; + + // use as cosine + dist = FixedDiv (dx, finesine[angle] ); + + return dist; +} + + + + +// +// R_InitPointToAngle +// +void R_InitPointToAngle (void) +{ + // UNUSED - now getting from tables.c +#if 0 + int i; + long t; + float f; + // + // slope (tangent) to angle lookup + // + for (i=0 ; i<=SLOPERANGE ; i++) + { + f = atan( (float)i/SLOPERANGE )/(3.141592657*2); + t = 0xffffffff*f; + tantoangle[i] = t; + } +#endif +} + + +// +// R_ScaleFromGlobalAngle +// Returns the texture mapping scale +// for the current line (horizontal span) +// at the given angle. +// ::g->rw_distance must be calculated first. +// +fixed_t R_ScaleFromGlobalAngle (angle_t visangle) +{ + fixed_t scale; + //int anglea; + //int angleb; + angle_t anglea; + angle_t angleb; + int sinea; + int sineb; + fixed_t num; + int den; + + // UNUSED +#if 0 + { + fixed_t dist; + fixed_t z; + fixed_t sinv; + fixed_t cosv; + + sinv = finesine[(visangle-::g->rw_normalangle)>>ANGLETOFINESHIFT]; + dist = FixedDiv (::g->rw_distance, sinv); + cosv = finecosine[(::g->viewangle-visangle)>>ANGLETOFINESHIFT]; + z = abs(FixedMul (dist, cosv)); + scale = FixedDiv(::g->projection, z); + return scale; + } +#endif + + extern angle_t GetViewAngle(); + anglea = ANG90 + (visangle-GetViewAngle()); + angleb = ANG90 + (visangle-::g->rw_normalangle); + + // both sines are allways positive + sinea = finesine[anglea>>ANGLETOFINESHIFT]; + sineb = finesine[angleb>>ANGLETOFINESHIFT]; + num = FixedMul(::g->projection,sineb) << ::g->detailshift; + den = FixedMul(::g->rw_distance,sinea); + + // DHM - Nerve :: If the den is pretty much 0, don't try the divide + if (den>>8 > 0 && den > num>>16) + { + scale = FixedDiv (num, den); + + if (scale > 64*FRACUNIT) + scale = 64*FRACUNIT; + else if (scale < 256) + scale = 256; + } + else + scale = 64*FRACUNIT; + + return scale; +} + + + +// +// R_InitTables +// +void R_InitTables (void) +{ + // UNUSED: now getting from tables.c +#if 0 + int i; + float a; + float fv; + int t; + + // ::g->viewangle tangent table + for (i=0 ; iviewangletox will give the next greatest x + // after the view angle. + // + // Calc focallength + // so FIELDOFVIEW angles covers SCREENWIDTH. + focallength = FixedDiv (::g->centerxfrac, + finetangent[FINEANGLES/4+FIELDOFVIEW/2] ); + + for (i=0 ; i FRACUNIT*2) + t = -1; + else if (finetangent[i] < -FRACUNIT*2) + t = ::g->viewwidth+1; + else + { + t = FixedMul (finetangent[i], focallength); + t = (::g->centerxfrac - t+FRACUNIT-1)>>FRACBITS; + + if (t < -1) + t = -1; + else if (t>::g->viewwidth+1) + t = ::g->viewwidth+1; + } + ::g->viewangletox[i] = t; + } + + // Scan ::g->viewangletox[] to generate ::g->xtoviewangle[]: + // ::g->xtoviewangle will give the smallest view angle + // that maps to x. + for (x=0;x<=::g->viewwidth;x++) + { + i = 0; + while (::g->viewangletox[i]>x) + i++; + ::g->xtoviewangle[x] = (i<viewangletox. + for (i=0 ; icenterx - t; + + if (::g->viewangletox[i] == -1) + ::g->viewangletox[i] = 0; + else if (::g->viewangletox[i] == ::g->viewwidth+1) + ::g->viewangletox[i] = ::g->viewwidth; + } + + ::g->clipangle = ::g->xtoviewangle[0]; +} + + + +// +// R_InitLightTables +// Only inits the ::g->zlight table, +// because the ::g->scalelight table changes with view size. +// + +void R_InitLightTables (void) +{ + int i; + int j; + int level; + int nocollide_startmap; + int scale; + + // Calculate the light levels to use + // for each level / distance combination. + for (i=0 ; i< LIGHTLEVELS ; i++) + { + nocollide_startmap = ((LIGHTLEVELS-1-i)*2)*NUMCOLORMAPS/LIGHTLEVELS; + for (j=0 ; j>= LIGHTSCALESHIFT; + level = nocollide_startmap - scale/DISTMAP; + + if (level < 0) + level = 0; + + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS-1; + + ::g->zlight[i][j] = ::g->colormaps + level*256; + } + } +} + + + +// +// R_SetViewSize +// Do not really change anything here, +// because it might be in the middle of a refresh. +// The change will take effect next refresh. +// + + +void +R_SetViewSize +( int blocks, + int detail ) +{ + ::g->setsizeneeded = true; + ::g->setblocks = blocks; + ::g->setdetail = detail; +} + + +// +// R_ExecuteSetViewSize +// +void R_ExecuteSetViewSize (void) +{ + fixed_t cosadj; + fixed_t dy; + int i; + int j; + int level; + int nocollide_startmap; + + ::g->setsizeneeded = false; + + if (::g->setblocks == 11) + { + ::g->scaledviewwidth = ORIGINAL_WIDTH; + ::g->viewheight = ORIGINAL_HEIGHT; + } + else + { + ::g->scaledviewwidth = ::g->setblocks*32; + ::g->viewheight = (::g->setblocks*168/10)&~7; + } + + // SMF - temp + ::g->scaledviewwidth *= GLOBAL_IMAGE_SCALER; + ::g->viewheight *= GLOBAL_IMAGE_SCALER; + + ::g->detailshift = ::g->setdetail; + ::g->viewwidth = ::g->scaledviewwidth>>::g->detailshift; + + ::g->centery = ::g->viewheight/2; + ::g->centerx = ::g->viewwidth/2; + ::g->centerxfrac = ::g->centerx<centeryfrac = ::g->centery<projection = ::g->centerxfrac; + + if (!::g->detailshift) + { + colfunc = basecolfunc = R_DrawColumn; + fuzzcolfunc = R_DrawFuzzColumn; + transcolfunc = R_DrawTranslatedColumn; + spanfunc = R_DrawSpan; + } + else + { + colfunc = basecolfunc = R_DrawColumnLow; + fuzzcolfunc = R_DrawFuzzColumn; + transcolfunc = R_DrawTranslatedColumn; + spanfunc = R_DrawSpanLow; + } + + R_InitBuffer (::g->scaledviewwidth, ::g->viewheight); + + R_InitTextureMapping (); + + // psprite scales + ::g->pspritescale = FRACUNIT*::g->viewwidth/ORIGINAL_WIDTH; + ::g->pspriteiscale = FRACUNIT*ORIGINAL_WIDTH/::g->viewwidth; + + // thing clipping + for (i=0 ; i < ::g->viewwidth ; i++) + ::g->screenheightarray[i] = ::g->viewheight; + + // planes + for (i=0 ; i < ::g->viewheight ; i++) + { + dy = ((i-::g->viewheight/2)<yslope[i] = FixedDiv ( (::g->viewwidth << ::g->detailshift)/2*FRACUNIT, dy); + } + + for (i=0 ; i < ::g->viewwidth ; i++) + { + cosadj = abs(finecosine[::g->xtoviewangle[i]>>ANGLETOFINESHIFT]); + ::g->distscale[i] = FixedDiv (FRACUNIT,cosadj); + } + + // Calculate the light levels to use + // for each level / scale combination. + for (i=0 ; i< LIGHTLEVELS ; i++) + { + nocollide_startmap = ((LIGHTLEVELS-1-i)*2)*NUMCOLORMAPS/LIGHTLEVELS; + for (j=0 ; jviewwidth << ::g->detailshift)/DISTMAP; + + if (level < 0) + level = 0; + + if (level >= NUMCOLORMAPS) + level = NUMCOLORMAPS-1; + + ::g->scalelight[i][j] = ::g->colormaps + level*256; + } + } +} + + + +// +// R_Init +// + + + +void R_Init (void) +{ + R_InitData (); + I_Printf ("\nR_InitData"); + R_InitPointToAngle (); + I_Printf ("\nR_InitPointToAngle"); + R_InitTables (); + // ::g->viewwidth / ::g->viewheight / ::g->detailLevel are set by the defaults + I_Printf ("\nR_InitTables"); + + R_SetViewSize (::g->screenblocks, ::g->detailLevel); + R_InitPlanes (); + I_Printf ("\nR_InitPlanes"); + R_InitLightTables (); + I_Printf ("\nR_InitLightTables"); + R_InitSkyMap (); + I_Printf ("\nR_InitSkyMap"); + R_InitTranslationTables (); + I_Printf ("\nR_InitTranslationsTables"); + + ::g->framecount = 0; +} + + +// +// R_PointInSubsector +// +subsector_t* +R_PointInSubsector +( fixed_t x, + fixed_t y ) +{ + node_t* node; + int side; + int nodenum; + + // single subsector is a special case + if (!::g->numnodes) + return ::g->subsectors; + + nodenum = ::g->numnodes-1; + + while (! (nodenum & NF_SUBSECTOR) ) + { + node = &::g->nodes[nodenum]; + side = R_PointOnSide (x, y, node); + nodenum = node->children[side]; + } + + return &::g->subsectors[nodenum & ~NF_SUBSECTOR]; +} + + + +// +// R_SetupFrame +// +void R_SetupFrame (player_t* player) +{ + int i; + + ::g->viewplayer = player; + extern void SetViewX( fixed_t ); extern void SetViewY( fixed_t ); extern void SetViewAngle( angle_t ); + SetViewX( player->mo->x ); + SetViewY( player->mo->y ); + SetViewAngle( player->mo->angle + ::g->viewangleoffset ); + ::g->extralight = player->extralight; + + ::g->viewz = player->viewz; + + extern angle_t GetViewAngle(); + + ::g->viewsin = finesine[GetViewAngle()>>ANGLETOFINESHIFT]; + ::g->viewcos = finecosine[GetViewAngle()>>ANGLETOFINESHIFT]; + + ::g->sscount = 0; + + if (player->fixedcolormap) + { + ::g->fixedcolormap = + ::g->colormaps + + player->fixedcolormap*256*sizeof(lighttable_t); + + ::g->walllights = ::g->scalelightfixed; + + for (i=0 ; iscalelightfixed[i] = ::g->fixedcolormap; + } + else + ::g->fixedcolormap = 0; + + ::g->framecount++; + ::g->validcount++; +} + + + +// +// R_RenderView +// +void R_RenderPlayerView (player_t* player) +{ + if ( player->mo == NULL ) { + return; + } + + R_SetupFrame (player); + + // Clear buffers. + R_ClearClipSegs (); + R_ClearDrawSegs (); + R_ClearPlanes (); + R_ClearSprites (); + + // check for new console commands. + NetUpdate ( NULL ); + + // The head node is the last node output. + R_RenderBSPNode (::g->numnodes-1); + + // Check for new console commands. + NetUpdate ( NULL ); + + R_DrawPlanes (); + + // Check for new console commands. + NetUpdate ( NULL ); + + R_DrawMasked (); + + // Check for new console commands. + NetUpdate ( NULL ); +} + diff --git a/doomclassic/doom/r_main.h b/doomclassic/doom/r_main.h new file mode 100644 index 00000000..14816fd0 --- /dev/null +++ b/doomclassic/doom/r_main.h @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_MAIN__ +#define __R_MAIN__ + +#include "d_player.h" +#include "r_data.h" + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// POV related. +// +extern fixed_t viewcos; +extern fixed_t viewsin; + +extern int viewwidth; +extern int viewheight; +extern int viewwindowx; +extern int viewwindowy; + + + +extern int centerx; +extern int centery; + +extern fixed_t centerxfrac; +extern fixed_t centeryfrac; +extern fixed_t projection; + +extern int validcount; + +extern int linecount; +extern int loopcount; + + +// +// Lighting LUT. +// Used for z-depth cuing per column/row, +// and other lighting effects (sector ambient, flash). +// + +// Lighting constants. +// Now why not 32 levels here? +#define LIGHTLEVELS 16 +#define LIGHTSEGSHIFT 4 + +#define MAXLIGHTSCALE 48 +#define LIGHTSCALESHIFT 12 +#define MAXLIGHTZ 128 +#define LIGHTZSHIFT 20 + +extern lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +extern lighttable_t* scalelightfixed[MAXLIGHTSCALE]; +extern lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ]; + +extern int extralight; +extern lighttable_t* fixedcolormap; + + +// Number of diminishing brightness levels. +// There a 0-31, i.e. 32 LUT in the COLORMAP lump. +#define NUMCOLORMAPS 32 + + +// Blocky/low detail mode. +//B remove this? +// 0 = high, 1 = low +extern int detailshift; + + +// +// Function pointers to switch refresh/drawing functions. +// Used to select shadow mode etc. +// +extern void (*colfunc) ( lighttable_t * ds_colormap, + byte * ds_source ); +extern void (*basecolfunc) ( lighttable_t * ds_colormap, + byte * ds_source ); +extern void (*fuzzcolfunc) ( lighttable_t * ds_colormap, + byte * ds_source ); +// No shadow effects on floors. +extern void (*spanfunc) ( + fixed_t xfrac, + fixed_t yfrac, + fixed_t ds_y, + int ds_x1, + int ds_x2, + fixed_t ds_xstep, + fixed_t ds_ystep, + lighttable_t * ds_colormap, + byte * ds_source ); + + +// +// Utility functions. +int +R_PointOnSide +( fixed_t x, + fixed_t y, + node_t* node ); + +int +R_PointOnSegSide +( fixed_t x, + fixed_t y, + seg_t* line ); + +angle_t +R_PointToAngle +( fixed_t x, + fixed_t y ); + +angle_t +R_PointToAngle2 +( fixed_t x1, + fixed_t y1, + fixed_t x2, + fixed_t y2 ); + +fixed_t +R_PointToDist +( fixed_t x, + fixed_t y ); + + +fixed_t R_ScaleFromGlobalAngle (angle_t visangle); + +subsector_t* +R_PointInSubsector +( fixed_t x, + fixed_t y ); + +void +R_AddPointToBox +( int x, + int y, + fixed_t* box ); + + + +// +// REFRESH - the actual rendering functions. +// + +// Called by G_Drawer. +void R_RenderPlayerView (player_t *player); + +// Called by startup code. +void R_Init (void); + +// Called by M_Responder. +void R_SetViewSize (int blocks, int detail); + +#endif + diff --git a/doomclassic/doom/r_plane.cpp b/doomclassic/doom/r_plane.cpp new file mode 100644 index 00000000..938d6337 --- /dev/null +++ b/doomclassic/doom/r_plane.cpp @@ -0,0 +1,437 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +#include "i_system.h" +#include "z_zone.h" +#include "w_wad.h" + +#include "doomdef.h" +#include "doomstat.h" + +#include "r_local.h" +#include "r_sky.h" + + + + +// +// opening +// + +// Here comes the obnoxious "visplane". + + + +// +// Clip values are the solid pixel bounding the range. +// ::g->floorclip starts out SCREENHEIGHT +// ::g->ceilingclip starts out -1 +// + +// +// ::g->spanstart holds the start of a plane span +// initialized to 0 at start +// + +// +// texture mapping +// + + + + + +// +// R_InitPlanes +// Only at game startup. +// +void R_InitPlanes (void) +{ + // Doh! +} + + +// +// R_MapPlane +// +// Uses global vars: +// ::g->planeheight +// ::g->ds_source +// ::g->basexscale +// ::g->baseyscale +// ::g->viewx +// ::g->viewy +// +// BASIC PRIMITIVE +// +void +R_MapPlane +( int y, + int x1, + int x2 ) +{ + angle_t angle; + fixed_t distance; + fixed_t length; + unsigned index; + +//#ifdef RANGECHECK + if ( x2 < x1 || x1<0 || x2>=::g->viewwidth || y>::g->viewheight ) + { + //I_Error ("R_MapPlane: %i, %i at %i",x1,x2,y); + return; + } +//#endif + + if (::g->planeheight != ::g->cachedheight[y]) + { + ::g->cachedheight[y] = ::g->planeheight; + distance = ::g->cacheddistance[y] = FixedMul (::g->planeheight, ::g->yslope[y]); + ::g->ds_xstep = ::g->cachedxstep[y] = FixedMul (distance,::g->basexscale); + ::g->ds_ystep = ::g->cachedystep[y] = FixedMul (distance,::g->baseyscale); + } + else + { + distance = ::g->cacheddistance[y]; + ::g->ds_xstep = ::g->cachedxstep[y]; + ::g->ds_ystep = ::g->cachedystep[y]; + } + + extern angle_t GetViewAngle(); + length = FixedMul (distance,::g->distscale[x1]); + angle = (GetViewAngle() + ::g->xtoviewangle[x1])>>ANGLETOFINESHIFT; + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + ::g->ds_xfrac = GetViewX() + FixedMul(finecosine[angle], length); + ::g->ds_yfrac = -GetViewY() - FixedMul(finesine[angle], length); + + if (::g->fixedcolormap) + ::g->ds_colormap = ::g->fixedcolormap; + else + { + index = distance >> LIGHTZSHIFT; + + if (index >= MAXLIGHTZ ) + index = MAXLIGHTZ-1; + + ::g->ds_colormap = ::g->planezlight[index]; + } + + ::g->ds_y = y; + ::g->ds_x1 = x1; + ::g->ds_x2 = x2; + + // high or low detail + spanfunc ( + ::g->ds_xfrac, + ::g->ds_yfrac, + ::g->ds_y, + ::g->ds_x1, + ::g->ds_x2, + ::g->ds_xstep, + ::g->ds_ystep, + ::g->ds_colormap, + ::g->ds_source ); +} + + +// +// R_ClearPlanes +// At begining of frame. +// +void R_ClearPlanes (void) +{ + int i; + angle_t angle; + + // opening / clipping determination + for (i=0 ; i < ::g->viewwidth ; i++) + { + ::g->floorclip[i] = ::g->viewheight; + ::g->ceilingclip[i] = -1; + } + + ::g->lastvisplane = ::g->visplanes; + ::g->lastopening = ::g->openings; + + // texture calculation + memset (::g->cachedheight, 0, sizeof(::g->cachedheight)); + + // left to right mapping + extern angle_t GetViewAngle(); + angle = (GetViewAngle()-ANG90)>>ANGLETOFINESHIFT; + + // scale will be unit scale at SCREENWIDTH/2 distance + ::g->basexscale = FixedDiv (finecosine[angle],::g->centerxfrac); + ::g->baseyscale = -FixedDiv (finesine[angle],::g->centerxfrac); +} + + + + +// +// R_FindPlane +// +visplane_t* R_FindPlane( fixed_t height, int picnum, int lightlevel ) { + visplane_t* check; + + if (picnum == ::g->skyflatnum) { + height = 0; // all skys map together + lightlevel = 0; + } + + for (check=::g->visplanes; check < ::g->lastvisplane; check++) { + if (height == check->height && picnum == check->picnum && lightlevel == check->lightlevel) { + break; + } + } + + if (check < ::g->lastvisplane) + return check; + + //if (::g->lastvisplane - ::g->visplanes == MAXVISPLANES) + //I_Error ("R_FindPlane: no more visplanes"); + if ( ::g->lastvisplane - ::g->visplanes == MAXVISPLANES ) { + check = ::g->visplanes; + return check; + } + + ::g->lastvisplane++; + + check->height = height; + check->picnum = picnum; + check->lightlevel = lightlevel; + check->minx = SCREENWIDTH; + check->maxx = -1; + + memset(check->top,0xff,sizeof(check->top)); + + return check; +} + + +// +// R_CheckPlane +// +visplane_t* +R_CheckPlane +( visplane_t* pl, + int start, + int stop ) +{ + int intrl; + int intrh; + int unionl; + int unionh; + int x; + + if (start < pl->minx) + { + intrl = pl->minx; + unionl = start; + } + else + { + unionl = pl->minx; + intrl = start; + } + + if (stop > pl->maxx) + { + intrh = pl->maxx; + unionh = stop; + } + else + { + unionh = pl->maxx; + intrh = stop; + } + + for (x=intrl ; x<= intrh ; x++) + if (pl->top[x] != 0xffff) + break; + + if (x > intrh) + { + pl->minx = unionl; + pl->maxx = unionh; + + // use the same one + return pl; + } + + if ( ::g->lastvisplane - ::g->visplanes == MAXVISPLANES ) { + return pl; + } + + // make a new visplane + ::g->lastvisplane->height = pl->height; + ::g->lastvisplane->picnum = pl->picnum; + ::g->lastvisplane->lightlevel = pl->lightlevel; + + pl = ::g->lastvisplane++; + pl->minx = start; + pl->maxx = stop; + + memset(pl->top,0xff,sizeof(pl->top)); + + return pl; +} + + +// +// R_MakeSpans +// +void +R_MakeSpans +( int x, + int t1, + int b1, + int t2, + int b2 ) +{ + while (t1 < t2 && t1<=b1) + { + R_MapPlane (t1,::g->spanstart[t1],x-1); + t1++; + } + while (b1 > b2 && b1>=t1) + { + R_MapPlane (b1,::g->spanstart[b1],x-1); + b1--; + } + + while (t2 < t1 && t2<=b2) + { + ::g->spanstart[t2] = x; + t2++; + } + while (b2 > b1 && b2>=t2) + { + ::g->spanstart[b2] = x; + b2--; + } +} + + + +// +// R_DrawPlanes +// At the end of each frame. +// +void R_DrawPlanes (void) +{ + visplane_t* pl; + int light; + int x; + int stop; + int angle; + +#ifdef RANGECHECK + if (::g->ds_p - ::g->drawsegs > MAXDRAWSEGS) + I_Error ("R_DrawPlanes: ::g->drawsegs overflow (%i)", + ::g->ds_p - ::g->drawsegs); + + if (::g->lastvisplane - ::g->visplanes > MAXVISPLANES) + I_Error ("R_DrawPlanes: visplane overflow (%i)", + ::g->lastvisplane - ::g->visplanes); + + if (::g->lastopening - ::g->openings > MAXOPENINGS) + I_Error ("R_DrawPlanes: opening overflow (%i)", + ::g->lastopening - ::g->openings); +#endif + + for (pl = ::g->visplanes ; pl < ::g->lastvisplane ; pl++) + { + if (pl->minx > pl->maxx) + continue; + + + // sky flat + if (pl->picnum == ::g->skyflatnum) + { + ::g->dc_iscale = ::g->pspriteiscale>>::g->detailshift; + + // Sky is allways drawn full bright, + // i.e. ::g->colormaps[0] is used. + // Because of this hack, sky is not affected + // by INVUL inverse mapping. + ::g->dc_colormap = ::g->colormaps; + ::g->dc_texturemid = ::g->skytexturemid; + for (x=pl->minx ; x <= pl->maxx ; x++) + { + ::g->dc_yl = pl->top[x]; + ::g->dc_yh = pl->bottom[x]; + + if (::g->dc_yl <= ::g->dc_yh) + { + extern angle_t GetViewAngle(); + angle = (GetViewAngle() + ::g->xtoviewangle[x])>>ANGLETOSKYSHIFT; + ::g->dc_x = x; + ::g->dc_source = R_GetColumn(::g->skytexture, angle); + colfunc ( ::g->dc_colormap, ::g->dc_source ); + } + } + continue; + } + + // regular flat + ::g->ds_source = (byte*)W_CacheLumpNum(::g->firstflat + + ::g->flattranslation[pl->picnum], + PU_CACHE_SHARED); + + ::g->planeheight = abs(pl->height-::g->viewz); + light = (pl->lightlevel >> LIGHTSEGSHIFT)+::g->extralight; + + if (light >= LIGHTLEVELS) + light = LIGHTLEVELS-1; + + if (light < 0) + light = 0; + + ::g->planezlight = ::g->zlight[light]; + + pl->top[pl->maxx+1] = 0xffff; + pl->top[pl->minx-1] = 0xffff; + + stop = pl->maxx + 1; + + for (x=pl->minx ; x<= stop ; x++) + { + R_MakeSpans(x,pl->top[x-1], + pl->bottom[x-1], + pl->top[x], + pl->bottom[x]); + } + } +} + diff --git a/doomclassic/doom/r_plane.h b/doomclassic/doom/r_plane.h new file mode 100644 index 00000000..a17847c7 --- /dev/null +++ b/doomclassic/doom/r_plane.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_PLANE__ +#define __R_PLANE__ + + +#include "r_data.h" + +#ifdef __GNUG__ +#pragma interface +#endif + + +// Visplane related. +extern short* lastopening; + + +typedef void (*planefunction_t) (int top, int bottom); + +extern planefunction_t floorfunc; +extern planefunction_t ceilingfunc_t; + +extern short floorclip[SCREENWIDTH]; +extern short ceilingclip[SCREENWIDTH]; + +extern fixed_t yslope[SCREENHEIGHT]; +extern fixed_t distscale[SCREENWIDTH]; + +void R_InitPlanes (void); +void R_ClearPlanes (void); + +void +R_MapPlane +( int y, + int x1, + int x2 ); + +void +R_MakeSpans +( int x, + int t1, + int b1, + int t2, + int b2 ); + +void R_DrawPlanes (void); + +visplane_t* +R_FindPlane +( fixed_t height, + int picnum, + int lightlevel ); + +visplane_t* +R_CheckPlane +( visplane_t* pl, + int start, + int stop ); + + + +#endif + diff --git a/doomclassic/doom/r_segs.cpp b/doomclassic/doom/r_segs.cpp new file mode 100644 index 00000000..1465aa0e --- /dev/null +++ b/doomclassic/doom/r_segs.cpp @@ -0,0 +1,718 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + + + + +#include + +#include "i_system.h" + +#include "doomdef.h" +#include "doomstat.h" + +#include "r_local.h" +#include "r_sky.h" + + +// OPTIMIZE: closed two sided ::g->lines as single sided + +// True if any of the ::g->segs textures might be visible. + +// False if the back side is the same plane. + + + +// angle to line origin + +// +// regular wall +// + + + + + + + + + + +// +// R_RenderMaskedSegRange +// +void +R_RenderMaskedSegRange +( drawseg_t* ds, + int x1, + int x2 ) +{ + unsigned index; + postColumn_t* col; + int lightnum; + int texnum; + + // Calculate light table. + // Use different light tables + // for horizontal / vertical / diagonal. Diagonal? + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + ::g->curline = ds->curline; + ::g->frontsector = ::g->curline->frontsector; + ::g->backsector = ::g->curline->backsector; + texnum = ::g->texturetranslation[::g->curline->sidedef->midtexture]; + + lightnum = (::g->frontsector->lightlevel >> LIGHTSEGSHIFT)+::g->extralight; + + if (::g->curline->v1->y == ::g->curline->v2->y) + lightnum--; + else if (::g->curline->v1->x == ::g->curline->v2->x) + lightnum++; + + if (lightnum < 0) + ::g->walllights = ::g->scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + ::g->walllights = ::g->scalelight[LIGHTLEVELS-1]; + else + ::g->walllights = ::g->scalelight[lightnum]; + + ::g->maskedtexturecol = ds->maskedtexturecol; + + ::g->rw_scalestep = ds->scalestep; + ::g->spryscale = ds->scale1 + (x1 - ds->x1)*::g->rw_scalestep; + ::g->mfloorclip = ds->sprbottomclip; + ::g->mceilingclip = ds->sprtopclip; + + // find positioning + if (::g->curline->linedef->flags & ML_DONTPEGBOTTOM) + { + ::g->dc_texturemid = ::g->frontsector->floorheight > ::g->backsector->floorheight + ? ::g->frontsector->floorheight : ::g->backsector->floorheight; + ::g->dc_texturemid = ::g->dc_texturemid + ::g->s_textureheight[texnum] - ::g->viewz; + } + else + { + ::g->dc_texturemid =::g->frontsector->ceilingheight < ::g->backsector->ceilingheight + ? ::g->frontsector->ceilingheight : ::g->backsector->ceilingheight; + ::g->dc_texturemid = ::g->dc_texturemid - ::g->viewz; + } + ::g->dc_texturemid += ::g->curline->sidedef->rowoffset; + + if (::g->fixedcolormap) + ::g->dc_colormap = ::g->fixedcolormap; + + // draw the columns + for (::g->dc_x = x1 ; ::g->dc_x <= x2 ; ::g->dc_x++) + { + // calculate lighting + if (::g->maskedtexturecol[::g->dc_x] != SHRT_MAX) + { + if (!::g->fixedcolormap) + { + index = ::g->spryscale>>LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE ) + index = MAXLIGHTSCALE-1; + + ::g->dc_colormap = ::g->walllights[index]; + } + + ::g->sprtopscreen = ::g->centeryfrac - FixedMul(::g->dc_texturemid, ::g->spryscale); + ::g->dc_iscale = 0xffffffffu / (unsigned)::g->spryscale; + + // draw the texture + col = (postColumn_t *)( + (byte *)R_GetColumn(texnum,::g->maskedtexturecol[::g->dc_x]) -3); + + R_DrawMaskedColumn (col); + ::g->maskedtexturecol[::g->dc_x] = SHRT_MAX; + } + ::g->spryscale += ::g->rw_scalestep; + } + +} + + + + +// +// R_RenderSegLoop +// Draws zero, one, or two textures (and possibly a masked +// texture) for walls. +// Can draw or mark the starting pixel of floor and ceiling +// textures. +// CALLED: CORE LOOPING ROUTINE. +// + +void R_RenderSegLoop (void) +{ + angle_t angle; + unsigned index; + int yl; + int yh; + int mid; + fixed_t texturecolumn; + int top; + int bottom; + + texturecolumn = 0; // shut up compiler warning + + for ( ; ::g->rw_x < ::g->rw_stopx ; ::g->rw_x++) + { + // mark floor / ceiling areas + yl = (::g->topfrac+HEIGHTUNIT-1)>>HEIGHTBITS; + + // no space above wall? + if (yl < ::g->ceilingclip[::g->rw_x]+1) + yl = ::g->ceilingclip[::g->rw_x]+1; + + if (::g->markceiling) + { + top = ::g->ceilingclip[::g->rw_x]+1; + bottom = yl-1; + + if (bottom >= ::g->floorclip[::g->rw_x]) + bottom = ::g->floorclip[::g->rw_x]-1; + + if (top <= bottom) + { + ::g->ceilingplane->top[::g->rw_x] = top; + ::g->ceilingplane->bottom[::g->rw_x] = bottom; + } + } + + yh = ::g->bottomfrac>>HEIGHTBITS; + + if (yh >= ::g->floorclip[::g->rw_x]) + yh = ::g->floorclip[::g->rw_x]-1; + + if (::g->markfloor) + { + top = yh+1; + bottom = ::g->floorclip[::g->rw_x]-1; + if (top <= ::g->ceilingclip[::g->rw_x]) + top = ::g->ceilingclip[::g->rw_x]+1; + if (top <= bottom) + { + ::g->floorplane->top[::g->rw_x] = top; + ::g->floorplane->bottom[::g->rw_x] = bottom; + } + } + + // texturecolumn and lighting are independent of wall tiers + if (::g->segtextured) + { + // calculate texture offset + angle = (::g->rw_centerangle + ::g->xtoviewangle[::g->rw_x])>>ANGLETOFINESHIFT; + texturecolumn = ::g->rw_offset-FixedMul(finetangent[angle],::g->rw_distance); + texturecolumn >>= FRACBITS; + // calculate lighting + index = ::g->rw_scale>>LIGHTSCALESHIFT; + + if (index >= MAXLIGHTSCALE ) + index = MAXLIGHTSCALE-1; + + ::g->dc_colormap = ::g->walllights[index]; + ::g->dc_x = ::g->rw_x; + ::g->dc_iscale = 0xffffffffu / (unsigned)::g->rw_scale; + } + + // draw the wall tiers + if (::g->midtexture) + { + // single sided line + ::g->dc_yl = yl; + ::g->dc_yh = yh; + ::g->dc_texturemid = ::g->rw_midtexturemid; + ::g->dc_source = R_GetColumn(::g->midtexture,texturecolumn); + colfunc ( ::g->dc_colormap, ::g->dc_source ); + ::g->ceilingclip[::g->rw_x] = ::g->viewheight; + ::g->floorclip[::g->rw_x] = -1; + } + else + { + // two sided line + if (::g->toptexture) + { + // top wall + mid = ::g->pixhigh>>HEIGHTBITS; + ::g->pixhigh += ::g->pixhighstep; + + if (mid >= ::g->floorclip[::g->rw_x]) + mid = ::g->floorclip[::g->rw_x]-1; + + if (mid >= yl) + { + ::g->dc_yl = yl; + ::g->dc_yh = mid; + ::g->dc_texturemid = ::g->rw_toptexturemid; + ::g->dc_source = R_GetColumn(::g->toptexture,texturecolumn); + colfunc ( ::g->dc_colormap, ::g->dc_source ); + ::g->ceilingclip[::g->rw_x] = mid; + } + else + ::g->ceilingclip[::g->rw_x] = yl-1; + } + else + { + // no top wall + if (::g->markceiling) + ::g->ceilingclip[::g->rw_x] = yl-1; + } + + if (::g->bottomtexture) + { + // bottom wall + mid = (::g->pixlow+HEIGHTUNIT-1)>>HEIGHTBITS; + ::g->pixlow += ::g->pixlowstep; + + // no space above wall? + if (mid <= ::g->ceilingclip[::g->rw_x]) + mid = ::g->ceilingclip[::g->rw_x]+1; + + if (mid <= yh) + { + ::g->dc_yl = mid; + ::g->dc_yh = yh; + ::g->dc_texturemid = ::g->rw_bottomtexturemid; + ::g->dc_source = R_GetColumn(::g->bottomtexture, + texturecolumn); + colfunc ( ::g->dc_colormap, ::g->dc_source ); + ::g->floorclip[::g->rw_x] = mid; + } + else + ::g->floorclip[::g->rw_x] = yh+1; + } + else + { + // no bottom wall + if (::g->markfloor) + ::g->floorclip[::g->rw_x] = yh+1; + } + + if (::g->maskedtexture) + { + // save texturecol + // for backdrawing of masked mid texture + ::g->maskedtexturecol[::g->rw_x] = texturecolumn; + } + } + + ::g->rw_scale += ::g->rw_scalestep; + ::g->topfrac += ::g->topstep; + ::g->bottomfrac += ::g->bottomstep; + } +} + + + + +// +// R_StoreWallRange +// A wall segment will be drawn +// between start and stop pixels (inclusive). +// +void +R_StoreWallRange +( int start, + int stop ) +{ + fixed_t hyp; + fixed_t sineval; + angle_t distangle, offsetangle; + fixed_t vtop; + int lightnum; + + // don't overflow and crash + if (::g->ds_p == &::g->drawsegs[MAXDRAWSEGS]) + return; + +#ifdef RANGECHECK + if (start >=::g->viewwidth || start > stop) + I_Error ("Bad R_RenderWallRange: %i to %i", start , stop); +#endif + + ::g->sidedef = ::g->curline->sidedef; + ::g->linedef = ::g->curline->linedef; + + // mark the segment as visible for auto map + ::g->linedef->flags |= ML_MAPPED; + + // calculate ::g->rw_distance for scale calculation + ::g->rw_normalangle = ::g->curline->angle + ANG90; + offsetangle = abs((long)(::g->rw_normalangle-::g->rw_angle1)); + + if (offsetangle > ANG90) + offsetangle = ANG90; + + distangle = ANG90 - offsetangle; + hyp = R_PointToDist (::g->curline->v1->x, ::g->curline->v1->y); + sineval = finesine[distangle>>ANGLETOFINESHIFT]; + ::g->rw_distance = FixedMul (hyp, sineval); + + + ::g->ds_p->x1 = ::g->rw_x = start; + ::g->ds_p->x2 = stop; + ::g->ds_p->curline = ::g->curline; + ::g->rw_stopx = stop+1; + + // calculate scale at both ends and step + extern angle_t GetViewAngle(); + ::g->ds_p->scale1 = ::g->rw_scale = + R_ScaleFromGlobalAngle (GetViewAngle() + ::g->xtoviewangle[start]); + + if (stop > start ) + { + ::g->ds_p->scale2 = R_ScaleFromGlobalAngle (GetViewAngle() + ::g->xtoviewangle[stop]); + ::g->ds_p->scalestep = ::g->rw_scalestep = + (::g->ds_p->scale2 - ::g->rw_scale) / (stop-start); + } + else + { + // UNUSED: try to fix the stretched line bug +#if 0 + if (::g->rw_distance < FRACUNIT/2) + { + fixed_t trx,try; + fixed_t gxt,gyt; + + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + trx = ::g->curline->v1->x - GetViewX(); + try = ::g->curline->v1->y - GetVewY(); + + gxt = FixedMul(trx,::g->viewcos); + gyt = -FixedMul(try,::g->viewsin); + ::g->ds_p->scale1 = FixedDiv(::g->projection, gxt-gyt) << ::g->detailshift; + } +#endif + ::g->ds_p->scale2 = ::g->ds_p->scale1; + } + + // calculate texture boundaries + // and decide if floor / ceiling marks are needed + ::g->worldtop = ::g->frontsector->ceilingheight - ::g->viewz; + ::g->worldbottom = ::g->frontsector->floorheight - ::g->viewz; + + ::g->midtexture = ::g->toptexture = ::g->bottomtexture = ::g->maskedtexture = 0; + ::g->ds_p->maskedtexturecol = NULL; + + if (!::g->backsector) + { + // single sided line + ::g->midtexture = ::g->texturetranslation[::g->sidedef->midtexture]; + // a single sided line is terminal, so it must mark ends + ::g->markfloor = ::g->markceiling = true; + if (::g->linedef->flags & ML_DONTPEGBOTTOM) + { + vtop = ::g->frontsector->floorheight + + ::g->s_textureheight[::g->sidedef->midtexture]; + // bottom of texture at bottom + ::g->rw_midtexturemid = vtop - ::g->viewz; + } + else + { + // top of texture at top + ::g->rw_midtexturemid = ::g->worldtop; + } + ::g->rw_midtexturemid += ::g->sidedef->rowoffset; + + ::g->ds_p->silhouette = SIL_BOTH; + ::g->ds_p->sprtopclip = ::g->screenheightarray; + ::g->ds_p->sprbottomclip = ::g->negonearray; + ::g->ds_p->bsilheight = MAXINT; + ::g->ds_p->tsilheight = MININT; + } + else + { + // two sided line + ::g->ds_p->sprtopclip = ::g->ds_p->sprbottomclip = NULL; + ::g->ds_p->silhouette = 0; + + if (::g->frontsector->floorheight > ::g->backsector->floorheight) + { + ::g->ds_p->silhouette = SIL_BOTTOM; + ::g->ds_p->bsilheight = ::g->frontsector->floorheight; + } + else if (::g->backsector->floorheight > ::g->viewz) + { + ::g->ds_p->silhouette = SIL_BOTTOM; + ::g->ds_p->bsilheight = MAXINT; + // ::g->ds_p->sprbottomclip = ::g->negonearray; + } + + if (::g->frontsector->ceilingheight < ::g->backsector->ceilingheight) + { + ::g->ds_p->silhouette |= SIL_TOP; + ::g->ds_p->tsilheight = ::g->frontsector->ceilingheight; + } + else if (::g->backsector->ceilingheight < ::g->viewz) + { + ::g->ds_p->silhouette |= SIL_TOP; + ::g->ds_p->tsilheight = MININT; + // ::g->ds_p->sprtopclip = ::g->screenheightarray; + } + + if (::g->backsector->ceilingheight <= ::g->frontsector->floorheight) + { + ::g->ds_p->sprbottomclip = ::g->negonearray; + ::g->ds_p->bsilheight = MAXINT; + ::g->ds_p->silhouette |= SIL_BOTTOM; + } + + if (::g->backsector->floorheight >= ::g->frontsector->ceilingheight) + { + ::g->ds_p->sprtopclip = ::g->screenheightarray; + ::g->ds_p->tsilheight = MININT; + ::g->ds_p->silhouette |= SIL_TOP; + } + + ::g->worldhigh = ::g->backsector->ceilingheight - ::g->viewz; + ::g->worldlow = ::g->backsector->floorheight - ::g->viewz; + + // hack to allow height changes in outdoor areas + if (::g->frontsector->ceilingpic == ::g->skyflatnum + && ::g->backsector->ceilingpic == ::g->skyflatnum) + { + ::g->worldtop = ::g->worldhigh; + } + + + if (::g->worldlow != ::g->worldbottom + || ::g->backsector->floorpic != ::g->frontsector->floorpic + || ::g->backsector->lightlevel != ::g->frontsector->lightlevel) + { + ::g->markfloor = true; + } + else + { + // same plane on both ::g->sides + ::g->markfloor = false; + } + + + if (::g->worldhigh != ::g->worldtop + || ::g->backsector->ceilingpic != ::g->frontsector->ceilingpic + || ::g->backsector->lightlevel != ::g->frontsector->lightlevel) + { + ::g->markceiling = true; + } + else + { + // same plane on both ::g->sides + ::g->markceiling = false; + } + + if (::g->backsector->ceilingheight <= ::g->frontsector->floorheight + || ::g->backsector->floorheight >= ::g->frontsector->ceilingheight) + { + // closed door + ::g->markceiling = ::g->markfloor = true; + } + + + if (::g->worldhigh < ::g->worldtop) + { + // top texture + ::g->toptexture = ::g->texturetranslation[::g->sidedef->toptexture]; + if (::g->linedef->flags & ML_DONTPEGTOP) + { + // top of texture at top + ::g->rw_toptexturemid = ::g->worldtop; + } + else + { + vtop = + ::g->backsector->ceilingheight + + ::g->s_textureheight[::g->sidedef->toptexture]; + + // bottom of texture + ::g->rw_toptexturemid = vtop - ::g->viewz; + } + } + if (::g->worldlow > ::g->worldbottom) + { + // bottom texture + ::g->bottomtexture = ::g->texturetranslation[::g->sidedef->bottomtexture]; + + if (::g->linedef->flags & ML_DONTPEGBOTTOM ) + { + // bottom of texture at bottom + // top of texture at top + ::g->rw_bottomtexturemid = ::g->worldtop; + } + else // top of texture at top + ::g->rw_bottomtexturemid = ::g->worldlow; + } + ::g->rw_toptexturemid += ::g->sidedef->rowoffset; + ::g->rw_bottomtexturemid += ::g->sidedef->rowoffset; + + // allocate space for masked texture tables + if (::g->sidedef->midtexture) + { + // masked ::g->midtexture + ::g->maskedtexture = true; + ::g->ds_p->maskedtexturecol = ::g->maskedtexturecol = ::g->lastopening - ::g->rw_x; + ::g->lastopening += ::g->rw_stopx - ::g->rw_x; + } + } + + // calculate ::g->rw_offset (only needed for textured ::g->lines) + ::g->segtextured = ::g->midtexture | ::g->toptexture | ::g->bottomtexture | ::g->maskedtexture; + + if (::g->segtextured) + { + offsetangle = ::g->rw_normalangle-::g->rw_angle1; + + if (offsetangle > ANG180) + offsetangle = -offsetangle; // ALANHACK UNSIGNED + + if (offsetangle > ANG90) + offsetangle = ANG90; + + sineval = finesine[offsetangle >>ANGLETOFINESHIFT]; + ::g->rw_offset = FixedMul (hyp, sineval); + + if (::g->rw_normalangle-::g->rw_angle1 < ANG180) + ::g->rw_offset = -::g->rw_offset; + + ::g->rw_offset += ::g->sidedef->textureoffset + ::g->curline->offset; + ::g->rw_centerangle = ANG90 + GetViewAngle() - ::g->rw_normalangle; + + // calculate light table + // use different light tables + // for horizontal / vertical / diagonal + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + if (!::g->fixedcolormap) + { + lightnum = (::g->frontsector->lightlevel >> LIGHTSEGSHIFT)+::g->extralight; + + if (::g->curline->v1->y == ::g->curline->v2->y) + lightnum--; + else if (::g->curline->v1->x == ::g->curline->v2->x) + lightnum++; + + if (lightnum < 0) + ::g->walllights = ::g->scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + ::g->walllights = ::g->scalelight[LIGHTLEVELS-1]; + else + ::g->walllights = ::g->scalelight[lightnum]; + } + } + + // if a floor / ceiling plane is on the wrong side + // of the view plane, it is definitely invisible + // and doesn't need to be marked. + + + if (::g->frontsector->floorheight >= ::g->viewz) + { + // above view plane + ::g->markfloor = false; + } + + if (::g->frontsector->ceilingheight <= ::g->viewz + && ::g->frontsector->ceilingpic != ::g->skyflatnum) + { + // below view plane + ::g->markceiling = false; + } + + + // calculate incremental stepping values for texture edges + ::g->worldtop >>= 4; + ::g->worldbottom >>= 4; + + ::g->topstep = -FixedMul (::g->rw_scalestep, ::g->worldtop); + ::g->topfrac = (::g->centeryfrac>>4) - FixedMul (::g->worldtop, ::g->rw_scale); + + ::g->bottomstep = -FixedMul (::g->rw_scalestep,::g->worldbottom); + ::g->bottomfrac = (::g->centeryfrac>>4) - FixedMul (::g->worldbottom, ::g->rw_scale); + + if (::g->backsector) + { + ::g->worldhigh >>= 4; + ::g->worldlow >>= 4; + + if (::g->worldhigh < ::g->worldtop) + { + ::g->pixhigh = (::g->centeryfrac>>4) - FixedMul (::g->worldhigh, ::g->rw_scale); + ::g->pixhighstep = -FixedMul (::g->rw_scalestep,::g->worldhigh); + } + + if (::g->worldlow > ::g->worldbottom) + { + ::g->pixlow = (::g->centeryfrac>>4) - FixedMul (::g->worldlow, ::g->rw_scale); + ::g->pixlowstep = -FixedMul (::g->rw_scalestep,::g->worldlow); + } + } + + // render it + if (::g->markceiling) + ::g->ceilingplane = R_CheckPlane (::g->ceilingplane, ::g->rw_x, ::g->rw_stopx-1); + + if (::g->markfloor) + ::g->floorplane = R_CheckPlane (::g->floorplane, ::g->rw_x, ::g->rw_stopx-1); + + R_RenderSegLoop (); + + + // save sprite clipping info + if ( ((::g->ds_p->silhouette & SIL_TOP) || ::g->maskedtexture) + && !::g->ds_p->sprtopclip) + { + memcpy (::g->lastopening, ::g->ceilingclip+start, 2*(::g->rw_stopx-start)); + ::g->ds_p->sprtopclip = ::g->lastopening - start; + ::g->lastopening += ::g->rw_stopx - start; + } + + if ( ((::g->ds_p->silhouette & SIL_BOTTOM) || ::g->maskedtexture) + && !::g->ds_p->sprbottomclip) + { + memcpy (::g->lastopening, ::g->floorclip+start, 2*(::g->rw_stopx-start)); + ::g->ds_p->sprbottomclip = ::g->lastopening - start; + ::g->lastopening += ::g->rw_stopx - start; + } + + if (::g->maskedtexture && !(::g->ds_p->silhouette&SIL_TOP)) + { + ::g->ds_p->silhouette |= SIL_TOP; + ::g->ds_p->tsilheight = MININT; + } + if (::g->maskedtexture && !(::g->ds_p->silhouette&SIL_BOTTOM)) + { + ::g->ds_p->silhouette |= SIL_BOTTOM; + ::g->ds_p->bsilheight = MAXINT; + } + ::g->ds_p++; +} + + diff --git a/doomclassic/doom/r_segs.h b/doomclassic/doom/r_segs.h new file mode 100644 index 00000000..37f6765b --- /dev/null +++ b/doomclassic/doom/r_segs.h @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_SEGS__ +#define __R_SEGS__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +void +R_RenderMaskedSegRange +( drawseg_t* ds, + int x1, + int x2 ); + + +#endif + diff --git a/doomclassic/doom/r_sky.cpp b/doomclassic/doom/r_sky.cpp new file mode 100644 index 00000000..ec6d7f81 --- /dev/null +++ b/doomclassic/doom/r_sky.cpp @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +// Needed for FRACUNIT. +#include "m_fixed.h" + +// Needed for Flat retrieval. +#include "r_data.h" + + +#ifdef __GNUG__ +#pragma implementation "r_sky.h" +#endif +#include "r_sky.h" + +// +// sky mapping +// + + + +// +// R_InitSkyMap +// Called whenever the view size changes. +// +void R_InitSkyMap (void) +{ + // ::g->skyflatnum = R_FlatNumForName ( SKYFLATNAME ); + ::g->skytexturemid = 100*FRACUNIT; +} + + diff --git a/doomclassic/doom/r_sky.h b/doomclassic/doom/r_sky.h new file mode 100644 index 00000000..c6d04ccb --- /dev/null +++ b/doomclassic/doom/r_sky.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_SKY__ +#define __R_SKY__ + + +#ifdef __GNUG__ +#pragma interface +#endif + +// SKY, store the number for name. +#define SKYFLATNAME "F_SKY1" + +// The sky map is 256*128*4 maps. +#define ANGLETOSKYSHIFT 22 + +extern int skytexture; +extern int skytexturemid; + +// Called whenever the view size changes. +void R_InitSkyMap (void); + +#endif + diff --git a/doomclassic/doom/r_state.h b/doomclassic/doom/r_state.h new file mode 100644 index 00000000..92f07e8e --- /dev/null +++ b/doomclassic/doom/r_state.h @@ -0,0 +1,137 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_STATE__ +#define __R_STATE__ + +// Need data structure definitions. +#include "d_player.h" +#include "r_data.h" + + + +#ifdef __GNUG__ +#pragma interface +#endif + + + +// +// Refresh internal data structures, +// for rendering. +// + +// needed for texture pegging + +// needed for pre rendering (fracs) +extern fixed_t* spritewidth; + +extern fixed_t* spriteoffset; +extern fixed_t* spritetopoffset; + +extern lighttable_t* colormaps; + +extern int viewwidth; +extern int scaledviewwidth; +extern int viewheight; + +extern int firstflat; + +// for global animation +extern int* flattranslation; +extern int* texturetranslation; + + +// Sprite.... +extern int firstspritelump; +extern int lastspritelump; +extern int numspritelumps; + + + +// +// Lookup tables for map data. +// +extern int numsprites; +extern spritedef_t* sprites; + +extern int numvertexes; +extern vertex_t* vertexes; + +extern int numsegs; +extern seg_t* segs; + +extern int numsectors; +extern sector_t* sectors; + +extern int numsubsectors; +extern subsector_t* subsectors; + +extern int numnodes; +extern node_t* nodes; + +extern int numlines; +extern line_t* lines; + +extern int numsides; +extern side_t* sides; + + +// +// POV data. +// +extern fixed_t viewx; +extern fixed_t viewy; +extern fixed_t viewz; + +extern angle_t viewangle; +extern player_t* viewplayer; + + +// ? +extern angle_t clipangle; + +extern int viewangletox[FINEANGLES/2]; +extern angle_t xtoviewangle[SCREENWIDTH+1]; +//extern fixed_t finetangent[FINEANGLES/2]; + +extern fixed_t rw_distance; +extern angle_t rw_normalangle; + + + +// angle to line origin +extern int rw_angle1; + +// Segs count? +extern int sscount; + + + +#endif + diff --git a/doomclassic/doom/r_things.cpp b/doomclassic/doom/r_things.cpp new file mode 100644 index 00000000..ae92fcbf --- /dev/null +++ b/doomclassic/doom/r_things.cpp @@ -0,0 +1,969 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include + + +#include "doomdef.h" +#include "m_swap.h" + +#include "i_system.h" +#include "z_zone.h" +#include "w_wad.h" + +#include "r_local.h" + +#include "doomstat.h" + + + + +//void R_DrawColumn (void); +//void R_DrawFuzzColumn (void); + + + + + + +// +// Sprite rotation 0 is facing the viewer, +// rotation 1 is one angle turn CLOCKWISE around the axis. +// This is not the same as the angle, +// which increases counter clockwise (protractor). +// There was a lot of stuff grabbed wrong, so I changed it... +// + + +// constant arrays +// used for psprite clipping and initializing clipping + + +// +// INITIALIZATION FUNCTIONS +// + +// variables used to look up +// and range check thing_t ::g->sprites patches + + + + + + +// +// R_InstallSpriteLump +// Local function for R_InitSprites. +// +void +R_InstallSpriteLump +( int lump, + unsigned frame, + unsigned rotation, + qboolean flipped ) +{ + int r; + + if (frame >= 29 || rotation > 8) + I_Error("R_InstallSpriteLump: " + "Bad frame characters in lump %i", lump); + + if ((int)frame > ::g->maxframe) + ::g->maxframe = frame; + + if (rotation == 0) + { + // the lump should be used for all rotations + if (::g->sprtemp[frame].rotate == false) + I_Error ("R_InitSprites: Sprite %s frame %c has " + "multip rot=0 lump", ::g->spritename, 'A'+frame); + + if (::g->sprtemp[frame].rotate == true) + I_Error ("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", ::g->spritename, 'A'+frame); + + ::g->sprtemp[frame].rotate = false; + for (r=0 ; r<8 ; r++) + { + ::g->sprtemp[frame].lump[r] = lump - ::g->firstspritelump; + ::g->sprtemp[frame].flip[r] = (byte)flipped; + } + return; + } + + // the lump is only used for one rotation + if (::g->sprtemp[frame].rotate == false) + I_Error ("R_InitSprites: Sprite %s frame %c has rotations " + "and a rot=0 lump", ::g->spritename, 'A'+frame); + + ::g->sprtemp[frame].rotate = true; + + // make 0 based + rotation--; + if (::g->sprtemp[frame].lump[rotation] != -1) + I_Error ("R_InitSprites: Sprite %s : %c : %c " + "has two lumps mapped to it", + ::g->spritename, 'A'+frame, '1'+rotation); + + ::g->sprtemp[frame].lump[rotation] = lump - ::g->firstspritelump; + ::g->sprtemp[frame].flip[rotation] = (byte)flipped; +} + + + + +// +// R_InitSpriteDefs +// Pass a null terminated list of sprite names +// (4 chars exactly) to be used. +// Builds the sprite rotation matrixes to account +// for horizontally flipped ::g->sprites. +// Will report an error if the lumps are inconsistant. +// Only called at startup. +// +// Sprite lump names are 4 characters for the actor, +// a letter for the frame, and a number for the rotation. +// A sprite that is flippable will have an additional +// letter/number appended. +// The rotation character can be 0 to signify no rotations. +// +void R_InitSpriteDefs (const char* const* namelist) +{ + const char* const* check; + int i; + int l; + int intname; + int frame; + int rotation; + int start; + int end; + int patched; + + // count the number of sprite names + check = namelist; + while (*check != NULL) + check++; + + ::g->numsprites = check-namelist; + + if (!::g->numsprites) + return; + + ::g->sprites = (spritedef_t*)DoomLib::Z_Malloc(::g->numsprites *sizeof(*::g->sprites), PU_STATIC, NULL); + + start = ::g->firstspritelump-1; + end = ::g->lastspritelump+1; + + // scan all the lump names for each of the names, + // noting the highest frame letter. + // Just compare 4 characters as ints + for (i=0 ; i < ::g->numsprites ; i++) + { + ::g->spritename = namelist[i]; + memset (::g->sprtemp,-1, sizeof(::g->sprtemp)); + + ::g->maxframe = -1; + intname = *(int *)namelist[i]; + + // scan the lumps, + // filling in the frames for whatever is found + for (l=start+1 ; lmodifiedgame) + patched = W_GetNumForName (lumpinfo[l].name); + else + patched = l; + + R_InstallSpriteLump (patched, frame, rotation, false); + + if (lumpinfo[l].name[6]) + { + frame = lumpinfo[l].name[6] - 'A'; + rotation = lumpinfo[l].name[7] - '0'; + R_InstallSpriteLump (l, frame, rotation, true); + } + } + } + + // check the frames that were found for completeness + if (::g->maxframe == -1) + { + ::g->sprites[i].numframes = 0; + continue; + } + + ::g->maxframe++; + + for (frame = 0 ; frame < ::g->maxframe ; frame++) + { + switch ((int)::g->sprtemp[frame].rotate) + { + case -1: + // no rotations were found for that frame at all + I_Error ("R_InitSprites: No patches found " + "for %s frame %c", namelist[i], frame+'A'); + break; + + case 0: + // only the first rotation is needed + break; + + case 1: + // must have all 8 frames + for (rotation=0 ; rotation<8 ; rotation++) + if (::g->sprtemp[frame].lump[rotation] == -1) + I_Error ("R_InitSprites: Sprite %s frame %c " + "is missing rotations", + namelist[i], frame+'A'); + break; + } + } + + // allocate space for the frames present and copy ::g->sprtemp to it + ::g->sprites[i].numframes = ::g->maxframe; + ::g->sprites[i].spriteframes = + (spriteframe_t*)DoomLib::Z_Malloc (::g->maxframe * sizeof(spriteframe_t), PU_STATIC, NULL); + memcpy (::g->sprites[i].spriteframes, ::g->sprtemp, ::g->maxframe*sizeof(spriteframe_t)); + } + +} + + + + +// +// GAME FUNCTIONS +// + + + +// +// R_InitSprites +// Called at program start. +// +void R_InitSprites (const char* const* namelist) +{ + int i; + + for (i=0 ; inegonearray[i] = -1; + } + + R_InitSpriteDefs (namelist); +} + + + +// +// R_ClearSprites +// Called at frame start. +// +void R_ClearSprites (void) +{ + ::g->vissprite_p = ::g->vissprites; +} + + +// +// R_NewVisSprite +// + +vissprite_t* R_NewVisSprite (void) +{ + if (::g->vissprite_p == &::g->vissprites[MAXVISSPRITES]) + return &::g->overflowsprite; + + ::g->vissprite_p++; + return ::g->vissprite_p-1; +} + + + +// +// R_DrawMaskedColumn +// Used for ::g->sprites and masked mid textures. +// Masked means: partly transparent, i.e. stored +// in posts/runs of opaque pixels. +// + + +void R_DrawMaskedColumn (postColumn_t* column) +{ + int topscreen; + int bottomscreen; + fixed_t basetexturemid; + + basetexturemid = ::g->dc_texturemid; + + for ( ; column->topdelta != 0xff ; ) + { + // calculate unclipped screen coordinates + // for post + topscreen = ::g->sprtopscreen + ::g->spryscale*column->topdelta; + bottomscreen = topscreen + ::g->spryscale*column->length; + + ::g->dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS; + ::g->dc_yh = (bottomscreen-1)>>FRACBITS; + + if (::g->dc_yh >= ::g->mfloorclip[::g->dc_x]) + ::g->dc_yh = ::g->mfloorclip[::g->dc_x]-1; + if (::g->dc_yl <= ::g->mceilingclip[::g->dc_x]) + ::g->dc_yl = ::g->mceilingclip[::g->dc_x]+1; + + if (::g->dc_yl <= ::g->dc_yh) + { + ::g->dc_source = (byte *)column + 3; + ::g->dc_texturemid = basetexturemid - (column->topdelta<dc_source = (byte *)column + 3 - column->topdelta; + + // Drawn by either R_DrawColumn + // or (SHADOW) R_DrawFuzzColumn. + colfunc ( ::g->dc_colormap, ::g->dc_source ); + } + column = (postColumn_t *)( (byte *)column + column->length + 4); + } + + ::g->dc_texturemid = basetexturemid; +} + + + +// +// R_DrawVisSprite +// ::g->mfloorclip and ::g->mceilingclip should also be set. +// +void +R_DrawVisSprite +( vissprite_t* vis, + int x1, + int x2 ) +{ + postColumn_t* column; + int texturecolumn; + fixed_t frac; + patch_t* patch; + + + patch = (patch_t*)W_CacheLumpNum (vis->patch+::g->firstspritelump, PU_CACHE_SHARED); + + ::g->dc_colormap = vis->colormap; + + if (!::g->dc_colormap) + { + // NULL colormap = shadow draw + colfunc = fuzzcolfunc; + } + else if (vis->mobjflags & MF_TRANSLATION) + { + colfunc = R_DrawTranslatedColumn; + ::g->dc_translation = ::g->translationtables - 256 + + ( (vis->mobjflags & MF_TRANSLATION) >> (MF_TRANSSHIFT-8) ); + } + + ::g->dc_iscale = abs(vis->xiscale)>>::g->detailshift; + ::g->dc_texturemid = vis->texturemid; + frac = vis->startfrac; + ::g->spryscale = vis->scale; + ::g->sprtopscreen = ::g->centeryfrac - FixedMul(::g->dc_texturemid,::g->spryscale); + + for (::g->dc_x=vis->x1 ; ::g->dc_x<=vis->x2 ; ::g->dc_x++, frac += vis->xiscale) + { + texturecolumn = frac>>FRACBITS; +#ifdef RANGECHECK + if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width)) + I_Error ("R_DrawSpriteRange: bad texturecolumn"); +#endif + column = (postColumn_t *) ((byte *)patch + + LONG(patch->columnofs[texturecolumn])); + R_DrawMaskedColumn (column); + } + + colfunc = basecolfunc; +} + + + + + + + +// +// R_ProjectSprite +// Generates a vissprite for a thing +// if it might be visible. +// +void R_ProjectSprite (mobj_t* thing) +{ + fixed_t tr_x; + fixed_t tr_y; + + fixed_t gxt; + fixed_t gyt; + + fixed_t tx; + fixed_t tz; + + fixed_t xscale; + + int x1; + int x2; + + spritedef_t* sprdef; + spriteframe_t* sprframe; + int lump; + + unsigned rot; + qboolean flip; + + int index; + + vissprite_t* vis; + + angle_t ang; + fixed_t iscale; + + // transform the origin point + extern fixed_t GetViewX(); extern fixed_t GetViewY(); + tr_x = thing->x - GetViewX(); + tr_y = thing->y - GetViewY(); + + gxt = FixedMul(tr_x,::g->viewcos); + gyt = -FixedMul(tr_y,::g->viewsin); + + tz = gxt-gyt; + + // thing is behind view plane? + if (tz < MINZ) + return; + + xscale = FixedDiv(::g->projection, tz); + + gxt = -FixedMul(tr_x,::g->viewsin); + gyt = FixedMul(tr_y,::g->viewcos); + tx = -(gyt+gxt); + + // too far off the side? + if (abs(tx)>(tz<<2)) + return; + + // decide which patch to use for sprite relative to player +#ifdef RANGECHECK + if (thing->sprite >= ::g->numsprites) + I_Error ("R_ProjectSprite: invalid sprite number %i ", + thing->sprite); +#endif + sprdef = &::g->sprites[thing->sprite]; +#ifdef RANGECHECK + if ( (thing->frame&FF_FRAMEMASK) >= sprdef->numframes ) + I_Error ("R_ProjectSprite: invalid sprite frame %i : %i ", + thing->sprite, thing->frame); +#endif + sprframe = &sprdef->spriteframes[ thing->frame & FF_FRAMEMASK]; + + if (sprframe->rotate) + { + // choose a different rotation based on player view + ang = R_PointToAngle (thing->x, thing->y); + rot = (ang-thing->angle+(unsigned)(ANG45/2)*9)>>29; + lump = sprframe->lump[rot]; + flip = (qboolean)sprframe->flip[rot]; + } + else + { + // use single rotation for all views + lump = sprframe->lump[0]; + flip = (qboolean)sprframe->flip[0]; + } + + // calculate edges of the shape + tx -= ::g->spriteoffset[lump]; + x1 = (::g->centerxfrac + FixedMul (tx,xscale) ) >>FRACBITS; + + // off the right side? + if (x1 > ::g->viewwidth) + return; + + tx += ::g->spritewidth[lump]; + x2 = ((::g->centerxfrac + FixedMul (tx,xscale) ) >>FRACBITS) - 1; + + // off the left side + if (x2 < 0) + return; + + // store information in a vissprite + vis = R_NewVisSprite (); + vis->mobjflags = thing->flags; + vis->scale = xscale << ::g->detailshift; + vis->gx = thing->x; + vis->gy = thing->y; + vis->gz = thing->z; + vis->gzt = thing->z + ::g->spritetopoffset[lump]; + vis->texturemid = vis->gzt - ::g->viewz; + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= ::g->viewwidth ? ::g->viewwidth-1 : x2; + iscale = FixedDiv (FRACUNIT, xscale); + + if (flip) + { + vis->startfrac = ::g->spritewidth[lump]-1; + vis->xiscale = -iscale; + } + else + { + vis->startfrac = 0; + vis->xiscale = iscale; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale*(vis->x1-x1); + vis->patch = lump; + + // get light level + if (thing->flags & MF_SHADOW) + { + // shadow draw + vis->colormap = NULL; + } + else if (::g->fixedcolormap) + { + // fixed map + vis->colormap = ::g->fixedcolormap; + } + else if (thing->frame & FF_FULLBRIGHT) + { + // full bright + vis->colormap = ::g->colormaps; + } + + else + { + // diminished light + index = xscale>>(LIGHTSCALESHIFT-::g->detailshift); + + if (index >= MAXLIGHTSCALE) + index = MAXLIGHTSCALE-1; + + vis->colormap = ::g->spritelights[index]; + } +} + + + + +// +// R_AddSprites +// During BSP traversal, this adds ::g->sprites by sector. +// +void R_AddSprites (sector_t* sec) +{ + mobj_t* thing; + int lightnum; + + // BSP is traversed by subsector. + // A sector might have been split into several + // ::g->subsectors during BSP building. + // Thus we check whether its already added. + if (sec->validcount == ::g->validcount) + return; + + // Well, now it will be done. + sec->validcount = ::g->validcount; + + lightnum = (sec->lightlevel >> LIGHTSEGSHIFT)+::g->extralight; + + if (lightnum < 0) + ::g->spritelights = ::g->scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + ::g->spritelights = ::g->scalelight[LIGHTLEVELS-1]; + else + ::g->spritelights = ::g->scalelight[lightnum]; + + // Handle all things in sector. + for (thing = sec->thinglist ; thing ; thing = thing->snext) + R_ProjectSprite (thing); +} + + +// +// R_DrawPSprite +// +void R_DrawPSprite (pspdef_t* psp) +{ + fixed_t tx; + int x1; + int x2; + spritedef_t* sprdef; + spriteframe_t* sprframe; + int lump; + qboolean flip; + vissprite_t* vis; + vissprite_t avis; + + // decide which patch to use +#ifdef RANGECHECK + if ( psp->state->sprite >= ::g->numsprites) + I_Error ("R_ProjectSprite: invalid sprite number %i ", + psp->state->sprite); +#endif + sprdef = &::g->sprites[psp->state->sprite]; +#ifdef RANGECHECK + if ( (psp->state->frame & FF_FRAMEMASK) >= sprdef->numframes) + I_Error ("R_ProjectSprite: invalid sprite frame %i : %i ", + psp->state->sprite, psp->state->frame); +#endif + sprframe = &sprdef->spriteframes[ psp->state->frame & FF_FRAMEMASK ]; + + lump = sprframe->lump[0]; + flip = (qboolean)sprframe->flip[0]; + + // calculate edges of the shape + tx = psp->sx-160*FRACUNIT; + + tx -= ::g->spriteoffset[lump]; + x1 = (::g->centerxfrac + FixedMul (tx,::g->pspritescale) ) >>FRACBITS; + + // off the right side + if (x1 > ::g->viewwidth) + return; + + tx += ::g->spritewidth[lump]; + x2 = ((::g->centerxfrac + FixedMul (tx, ::g->pspritescale) ) >>FRACBITS) - 1; + + // off the left side + if (x2 < 0) + return; + + // store information in a vissprite + vis = &avis; + vis->mobjflags = 0; + vis->texturemid = (BASEYCENTER<sy-::g->spritetopoffset[lump]); + vis->x1 = x1 < 0 ? 0 : x1; + vis->x2 = x2 >= ::g->viewwidth ? ::g->viewwidth-1 : x2; + vis->scale = ::g->pspritescale << ::g->detailshift; + + if (flip) + { + vis->xiscale = -::g->pspriteiscale; + vis->startfrac = ::g->spritewidth[lump]-1; + } + else + { + vis->xiscale = ::g->pspriteiscale; + vis->startfrac = 0; + } + + if (vis->x1 > x1) + vis->startfrac += vis->xiscale*(vis->x1-x1); + + vis->patch = lump; + + if (::g->viewplayer->powers[pw_invisibility] > 4*32 + || ::g->viewplayer->powers[pw_invisibility] & 8) + { + // shadow draw + vis->colormap = NULL; + } + else if (::g->fixedcolormap) + { + // fixed color + vis->colormap = ::g->fixedcolormap; + } + else if (psp->state->frame & FF_FULLBRIGHT) + { + // full bright + vis->colormap = ::g->colormaps; + } + else + { + // local light + vis->colormap = ::g->spritelights[MAXLIGHTSCALE-1]; + } + + R_DrawVisSprite (vis, vis->x1, vis->x2); +} + + + +// +// R_DrawPlayerSprites +// +void R_DrawPlayerSprites (void) +{ + int i; + int lightnum; + pspdef_t* psp; + + // get light level + lightnum = + (::g->viewplayer->mo->subsector->sector->lightlevel >> LIGHTSEGSHIFT) + +::g->extralight; + + if (lightnum < 0) + ::g->spritelights = ::g->scalelight[0]; + else if (lightnum >= LIGHTLEVELS) + ::g->spritelights = ::g->scalelight[LIGHTLEVELS-1]; + else + ::g->spritelights = ::g->scalelight[lightnum]; + + // clip to screen bounds + ::g->mfloorclip = ::g->screenheightarray; + ::g->mceilingclip = ::g->negonearray; + + // add all active psprites + for (i=0, psp=::g->viewplayer->psprites; + istate) + R_DrawPSprite (psp); + } +} + + + + +// +// R_SortVisSprites +// + + +void R_SortVisSprites (void) +{ + int i; + int count; + vissprite_t* ds = NULL; + vissprite_t* best = NULL; + vissprite_t unsorted; + fixed_t bestscale; + + count = ::g->vissprite_p - ::g->vissprites; + + unsorted.next = unsorted.prev = &unsorted; + + if (!count) + return; + + for (ds=::g->vissprites ; ds < ::g->vissprite_p ; ds++) + { + ds->next = ds+1; + ds->prev = ds-1; + } + + ::g->vissprites[0].prev = &unsorted; + unsorted.next = &::g->vissprites[0]; + (::g->vissprite_p-1)->next = &unsorted; + unsorted.prev = ::g->vissprite_p-1; + + // pull the ::g->vissprites out by scale + //best = 0; // shut up the compiler warning + ::g->vsprsortedhead.next = ::g->vsprsortedhead.prev = &::g->vsprsortedhead; + for (i=0 ; inext) + { + if (ds->scale < bestscale) + { + bestscale = ds->scale; + best = ds; + } + } + best->next->prev = best->prev; + best->prev->next = best->next; + best->next = &::g->vsprsortedhead; + best->prev = ::g->vsprsortedhead.prev; + ::g->vsprsortedhead.prev->next = best; + ::g->vsprsortedhead.prev = best; + } +} + + + +// +// R_DrawSprite +// +void R_DrawSprite (vissprite_t* spr) +{ + drawseg_t* ds; + short clipbot[SCREENWIDTH]; + short cliptop[SCREENWIDTH]; + int x; + int r1; + int r2; + fixed_t scale; + fixed_t lowscale; + int silhouette; + + for (x = spr->x1 ; x<=spr->x2 ; x++) + clipbot[x] = cliptop[x] = -2; + + // Scan ::g->drawsegs from end to start for obscuring ::g->segs. + // The first drawseg that has a greater scale + // is the clip seg. + for (ds=::g->ds_p-1 ; ds >= ::g->drawsegs ; ds--) + { + // determine if the drawseg obscures the sprite + if (ds->x1 > spr->x2 + || ds->x2 < spr->x1 + || (!ds->silhouette + && !ds->maskedtexturecol) ) + { + // does not cover sprite + continue; + } + + r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1; + r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2; + + if (ds->scale1 > ds->scale2) + { + lowscale = ds->scale2; + scale = ds->scale1; + } + else + { + lowscale = ds->scale1; + scale = ds->scale2; + } + + if (scale < spr->scale + || ( lowscale < spr->scale + && !R_PointOnSegSide (spr->gx, spr->gy, ds->curline) ) ) + { + // masked mid texture? + if (ds->maskedtexturecol) + R_RenderMaskedSegRange (ds, r1, r2); + // seg is behind sprite + continue; + } + + + // clip this piece of the sprite + silhouette = ds->silhouette; + + if (spr->gz >= ds->bsilheight) + silhouette &= ~SIL_BOTTOM; + + if (spr->gzt <= ds->tsilheight) + silhouette &= ~SIL_TOP; + + if (silhouette == 1) + { + // bottom sil + for (x=r1 ; x<=r2 ; x++) + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + } + else if (silhouette == 2) + { + // top sil + for (x=r1 ; x<=r2 ; x++) + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } + else if (silhouette == 3) + { + // both + for (x=r1 ; x<=r2 ; x++) + { + if (clipbot[x] == -2) + clipbot[x] = ds->sprbottomclip[x]; + if (cliptop[x] == -2) + cliptop[x] = ds->sprtopclip[x]; + } + } + + } + + // all clipping has been performed, so draw the sprite + + // check for unclipped columns + for (x = spr->x1 ; x<=spr->x2 ; x++) + { + if (clipbot[x] == -2) + clipbot[x] = ::g->viewheight; + + if (cliptop[x] == -2) + cliptop[x] = -1; + } + + ::g->mfloorclip = clipbot; + ::g->mceilingclip = cliptop; + R_DrawVisSprite (spr, spr->x1, spr->x2); +} + + + + +// +// R_DrawMasked +// +void R_DrawMasked (void) +{ + vissprite_t* spr; + drawseg_t* ds; + + R_SortVisSprites (); + + if (::g->vissprite_p > ::g->vissprites) + { + // draw all ::g->vissprites back to front + for (spr = ::g->vsprsortedhead.next ; + spr != &::g->vsprsortedhead ; + spr=spr->next) + { + + R_DrawSprite (spr); + } + } + + // render any remaining masked mid textures + for (ds=::g->ds_p-1 ; ds >= ::g->drawsegs ; ds--) + if (ds->maskedtexturecol) + R_RenderMaskedSegRange (ds, ds->x1, ds->x2); + + // draw the psprites on top of everything + // but does not draw on side views + if (!::g->viewangleoffset) + R_DrawPlayerSprites (); +} + + + + diff --git a/doomclassic/doom/r_things.h b/doomclassic/doom/r_things.h new file mode 100644 index 00000000..34c084dd --- /dev/null +++ b/doomclassic/doom/r_things.h @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __R_THINGS__ +#define __R_THINGS__ + + +#ifdef __GNUG__ +#pragma interface +#endif + +#define MAXVISSPRITES 128 + +extern vissprite_t vissprites[MAXVISSPRITES]; +extern vissprite_t* vissprite_p; +extern vissprite_t vsprsortedhead; + +// Constant arrays used for psprite clipping +// and initializing clipping. +extern short negonearray[SCREENWIDTH]; +extern short screenheightarray[SCREENWIDTH]; + +// vars for R_DrawMaskedColumn +extern short* mfloorclip; +extern short* mceilingclip; +extern fixed_t spryscale; +extern fixed_t sprtopscreen; + +extern fixed_t pspritescale; +extern fixed_t pspriteiscale; + + +void R_DrawMaskedColumn (postColumn_t* column); + + +void R_SortVisSprites (void); + +void R_AddSprites (sector_t* sec); +void R_AddPSprites (void); +void R_DrawSprites (void); +void R_InitSprites (const char* const* namelist); +void R_ClearSprites (void); +void R_DrawMasked (void); + +void +R_ClipVisSprite +( vissprite_t* vis, + int xl, + int xh ); + + +#endif + diff --git a/doomclassic/doom/s_sound.cpp b/doomclassic/doom/s_sound.cpp new file mode 100644 index 00000000..efd8a0d6 --- /dev/null +++ b/doomclassic/doom/s_sound.cpp @@ -0,0 +1,681 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + + +#include +#include + +#include "i_system.h" +#include "i_sound.h" +#include "sounds.h" +#include "s_sound.h" + +#include "z_zone.h" +#include "m_random.h" +#include "w_wad.h" + +#include "doomdef.h" +#include "p_local.h" + +#include "doomstat.h" +#include "Main.h" + +// Purpose? +const char snd_prefixen[] += { 'P', 'P', 'A', 'S', 'S', 'S', 'M', 'M', 'M', 'S', 'S', 'S' }; + + +// when to clip out sounds +// Does not fit the large outdoor areas. + +// Distance tp origin when sounds should be maxed out. +// This should relate to movement clipping resolution +// (see BLOCKMAP handling). +// Originally: (200*0x10000). + + + +// Adjustable by menu. + + + +// percent attenuation from front to back + + + +// Current music/sfx card - index useless +// w/o a reference LUT in a sound module. +// Config file? Same disclaimer as above. + + + + + +// the set of ::g->channels available + +// These are not used, but should be (menu). +// Maximum volume of a sound effect. +// Internal default is max out of 0-15. + +// Maximum volume of music. Useless so far. + + + +// whether songs are ::g->mus_paused + +// music currently being played + +// following is set +// by the defaults code in M_misc: +// number of ::g->channels available + + + + +// +// Internals. +// +int +S_getChannel +( void* origin, + sfxinfo_t* sfxinfo ); + + +int +S_AdjustSoundParams +( mobj_t* listener, + mobj_t* source, + int* vol, + int* sep, + int* pitch ); + +void S_StopChannel(int cnum); + + + +// +// Initializes sound stuff, including volume +// Sets ::g->channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// +void S_Init +( int sfxVolume, + int musicVolume ) +{ + int i; + + // Whatever these did with DMX, these are rather dummies now. + I_SetChannels(); + + S_SetSfxVolume(sfxVolume); + S_SetMusicVolume(musicVolume); + + // Allocating the internal ::g->channels for mixing + // (the maximum numer of sounds rendered + // simultaneously) within zone memory. + ::g->channels = + (channel_t *) DoomLib::Z_Malloc(::g->numChannels*sizeof(channel_t), PU_STATIC, 0); + + // Free all ::g->channels for use + for (i=0 ; i < ::g->numChannels ; i++) + ::g->channels[i].sfxinfo = 0; + + // no sounds are playing, and they are not ::g->mus_paused + ::g->mus_paused = 0; + ::g->mus_looping = 0; + + // Note that sounds have not been cached (yet). + for (i=1 ; ichannels ) { + for (cnum=0 ; cnum < ::g->numChannels ; cnum++) + if (::g->channels[cnum].sfxinfo) + S_StopChannel(cnum); + } + + // start new music for the level + ::g->mus_paused = 0; + + if (::g->gamemode == commercial) { + + mnum = mus_runnin + ::g->gamemap - 1; + + /* + Is this necessary? + + if ( ::g->gamemission == 0 ) { + mnum = mus_runnin + ::g->gamemap - 1; + } + else { + mnum = mus_runnin + ( gameLocal->GetMissionData( ::g->gamemission )->mapMusic[ ::g->gamemap-1 ] - 1 ); + } + */ + } + else + { + int spmus[] = { + // Song - Who? - Where? + mus_e3m4, // American e4m1 + mus_e3m2, // Romero e4m2 + mus_e3m3, // Shawn e4m3 + mus_e1m5, // American e4m4 + mus_e2m7, // Tim e4m5 + mus_e2m4, // Romero e4m6 + mus_e2m6, // J.Anderson e4m7 CHIRON.WAD + mus_e2m5, // Shawn e4m8 + mus_e1m9 // Tim e4m9 + }; + + if (::g->gameepisode < 4) + mnum = mus_e1m1 + (::g->gameepisode-1)*9 + ::g->gamemap-1; + else + mnum = spmus[::g->gamemap-1]; + } + + S_StopMusic(); + S_ChangeMusic(mnum, true); + + ::g->nextcleanup = 15; +} + + + + + +void +S_StartSoundAtVolume +( void* origin_p, + int sfx_id, + int volume ) +{ + + int rc; + int sep; + int pitch; + int priority; + sfxinfo_t* sfx; + int cnum; + + mobj_t* origin = (mobj_t *) origin_p; + + + // Debug. + /*I_PrintfE + "S_StartSoundAtVolume: playing sound %d (%s)\n", + sfx_id, S_sfx[sfx_id].name );*/ + + // check for bogus sound # + if (sfx_id < 1 || sfx_id > NUMSFX) + I_Error("Bad sfx #: %d", sfx_id); + + sfx = &S_sfx[sfx_id]; + + // Initialize sound parameters + if (sfx->link) + { + pitch = sfx->pitch; + priority = sfx->priority; + volume += sfx->volume; + + if (volume < 1) + return; + + if ( volume > s_volume_sound.GetInteger() ) + volume = s_volume_sound.GetInteger(); + } + else + { + pitch = NORM_PITCH; + priority = NORM_PRIORITY; + + if (volume < 1) + return; + + if ( volume > s_volume_sound.GetInteger() ) + volume = s_volume_sound.GetInteger(); + + } + + + // Check to see if it is audible, + // and if not, modify the params + // DHM - Nerve :: chainsaw!!! + if (origin && ::g->players[::g->consoleplayer].mo && origin != ::g->players[::g->consoleplayer].mo) + { + rc = S_AdjustSoundParams(::g->players[::g->consoleplayer].mo, + origin, + &volume, + &sep, + &pitch); + + if ( origin->x == ::g->players[::g->consoleplayer].mo->x + && origin->y == ::g->players[::g->consoleplayer].mo->y) + { + sep = NORM_SEP; + } + + if (!rc) + return; + } + else + { + sep = NORM_SEP; + } + + // hacks to vary the sfx pitches + const bool isChainsawSound = sfx_id >= sfx_sawup && sfx_id <= sfx_sawhit; + + if ( !isChainsawSound && sfx_id != sfx_itemup && sfx_id != sfx_tink) + { + pitch += 16 - (M_Random()&31); + + if (pitch<0) + pitch = 0; + else if (pitch>255) + pitch = 255; + } + + // kill old sound + //S_StopSound(origin); + + // try to find a channel + cnum = S_getChannel(origin, sfx); + + if (cnum<0) { + printf( "No sound channels available for sfxid %d.\n", sfx_id ); + return; + } + + // get lumpnum if necessary + if (sfx->lumpnum < 0) + sfx->lumpnum = I_GetSfxLumpNum(sfx); + + // increase the usefulness + if (sfx->usefulness++ < 0) + sfx->usefulness = 1; + + // Assigns the handle to one of the ::g->channels in the + // mix/output buffer. + ::g->channels[cnum].handle = I_StartSound(sfx_id, origin, ::g->players[::g->consoleplayer].mo, volume, pitch, priority); +} + +void S_StartSound ( void* origin, int sfx_id ) +{ + S_StartSoundAtVolume(origin, sfx_id, s_volume_sound.GetInteger() ); +} + + + + +void S_StopSound(void *origin) +{ + + int cnum; + + for (cnum=0 ; cnum < ::g->numChannels ; cnum++) + { + if (::g->channels[cnum].sfxinfo && ::g->channels[cnum].origin == origin) + { + S_StopChannel(cnum); + break; + } + } +} + + + + + +// +// Stop and resume music, during game PAUSE. +// +void S_PauseSound(void) +{ + if (::g->mus_playing && !::g->mus_paused) + { + I_PauseSong(::g->mus_playing->handle); + ::g->mus_paused = true; + } +} + +void S_ResumeSound(void) +{ + if (::g->mus_playing && ::g->mus_paused) + { + I_ResumeSong(::g->mus_playing->handle); + ::g->mus_paused = false; + } +} + + +// +// Updates music & sounds +// +void S_UpdateSounds(void* listener_p) +{ + int audible; + int cnum; + int volume; + int sep; + int pitch; + sfxinfo_t* sfx; + channel_t* c; + + mobj_t* listener = (mobj_t*)listener_p; + + for (cnum=0 ; cnum < ::g->numChannels ; cnum++) + { + c = &::g->channels[cnum]; + sfx = c->sfxinfo; + + if(sfx) + { + if (I_SoundIsPlaying(c->handle)) + { + // initialize parameters + volume = s_volume_sound.GetInteger(); + pitch = NORM_PITCH; + sep = NORM_SEP; + + if (sfx->link) + { + pitch = sfx->pitch; + volume += sfx->volume; + + if (volume < 1) { + S_StopChannel(cnum); + continue; + + } else if ( volume > s_volume_sound.GetInteger() ) { + volume = s_volume_sound.GetInteger(); + } + } + + // check non-local sounds for distance clipping or modify their params + if (c->origin && listener_p != c->origin) + { + audible = S_AdjustSoundParams(listener, (mobj_t*)c->origin, &volume, &sep, &pitch); + if (!audible) { + S_StopChannel(cnum); + } + } + } + else + { + // if channel is allocated but sound has stopped, free it + S_StopChannel(cnum); + } + } + } +} + + +void S_SetMusicVolume(int volume) +{ + I_SetMusicVolume(volume); + s_volume_midi.SetInteger( volume ); +} + + + +void S_SetSfxVolume(int volume) +{ + I_SetSfxVolume(volume); + s_volume_sound.SetInteger( volume ); +} + +// +// Starts some music with the music id found in sounds.h. +// +void S_StartMusic(int m_id) +{ + S_ChangeMusic(m_id, false); +} + +void S_ChangeMusic ( int musicnum, int looping ) +{ +#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING + if (gameLocal->IsSplitscreen() && DoomLib::GetPlayer() > 0 ) + { + // we aren't the key player... we have no control over music + return; + } +#endif + + musicinfo_t* music = NULL; + + if ( (musicnum <= mus_None) + || (musicnum >= NUMMUSIC) ) + { + I_Error("Bad music number %d", musicnum); + } + else + music = &::g->S_music[musicnum]; + + if (::g->mus_playing == music) + return; + + //I_Printf("S_ChangeMusic: Playing new track: '%s'\n", music->name); + + I_PlaySong( music->name, looping ); + + ::g->mus_playing = music; +} + + +void S_StopMusic(void) +{ + if (::g->doomcom.consoleplayer) + { + // we aren't the key player... we have no control over music + return; + } + + if (::g->mus_playing) + { + if (::g->mus_paused) + I_ResumeSong(::g->mus_playing->handle); + + I_StopSong(::g->mus_playing->handle); + I_UnRegisterSong(::g->mus_playing->handle); + //Z_FreeTags( PU_MUSIC_SHARED, PU_MUSIC_SHARED ); + + ::g->mus_playing->data = 0; + ::g->mus_playing = 0; + } +} + + + + +void S_StopChannel(int cnum) +{ + + int i; + channel_t* c = &::g->channels[cnum]; + + if (c->sfxinfo) + { + // stop the sound playing + if (I_SoundIsPlaying(c->handle)) + { +#ifdef SAWDEBUG + if (c->sfxinfo == &S_sfx[sfx_sawful]) + I_PrintfE( "stopped\n"); +#endif + I_StopSound(c->handle); + } + + // check to see + // if other ::g->channels are playing the sound + for (i=0 ; i < ::g->numChannels ; i++) + { + if (cnum != i + && c->sfxinfo == ::g->channels[i].sfxinfo) + { + break; + } + } + + // degrade usefulness of sound data + c->sfxinfo->usefulness--; + + c->sfxinfo = 0; + } +} + + + +// +// Changes volume, stereo-separation, and pitch variables +// from the norm of a sound effect to be played. +// If the sound is not audible, returns a 0. +// Otherwise, modifies parameters and returns 1. +// +int S_AdjustSoundParams( mobj_t* listener, mobj_t* source, int* vol, int* sep, int* pitch ) { + fixed_t approx_dist; + fixed_t adx; + fixed_t ady; + + // DHM - Nerve :: Could happen in multiplayer if a player exited the level holding the chainsaw + if ( listener == NULL || source == NULL ) { + return 0; + } + + // calculate the distance to sound origin + // and clip it if necessary + adx = abs(listener->x - source->x); + ady = abs(listener->y - source->y); + + // From _GG1_ p.428. Appox. eucledian distance fast. + approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1); + + if ( approx_dist > S_CLIPPING_DIST) { + return 0; + } + + // angle of source to listener + *sep = NORM_SEP; + + // volume calculation + if (approx_dist < S_CLOSE_DIST) { + *vol = s_volume_sound.GetInteger(); + } + else { + // distance effect + *vol = ( s_volume_sound.GetInteger() + * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS)) + / S_ATTENUATOR; + } + + return (*vol > 0); +} + + + + +// +// S_getChannel : +// If none available, return -1. Otherwise channel #. +// +int +S_getChannel +( void* origin, + sfxinfo_t* sfxinfo ) +{ + // channel number to use + int cnum; + + channel_t* c; + + // Find an open channel + for (cnum=0 ; cnum < ::g->numChannels ; cnum++) + { + if (!::g->channels[cnum].sfxinfo) + break; + else if ( origin && ::g->channels[cnum].origin == origin && + (::g->channels[cnum].handle == sfx_sawidl || ::g->channels[cnum].handle == sfx_sawful) ) + { + S_StopChannel(cnum); + break; + } + } + + // None available + if (cnum == ::g->numChannels) + { + // Look for lower priority + for (cnum=0 ; cnum < ::g->numChannels ; cnum++) + if (::g->channels[cnum].sfxinfo->priority >= sfxinfo->priority) break; + + if (cnum == ::g->numChannels) + { + // FUCK! No lower priority. Sorry, Charlie. + return -1; + } + else + { + // Otherwise, kick out lower priority. + S_StopChannel(cnum); + } + } + + c = &::g->channels[cnum]; + + // channel is decided to be cnum. + c->sfxinfo = sfxinfo; + c->origin = origin; + + return cnum; +} + + + + + diff --git a/doomclassic/doom/s_sound.h b/doomclassic/doom/s_sound.h new file mode 100644 index 00000000..54994340 --- /dev/null +++ b/doomclassic/doom/s_sound.h @@ -0,0 +1,111 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __S_SOUND__ +#define __S_SOUND__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + + +// +// Initializes sound stuff, including volume +// Sets channels, SFX and music volume, +// allocates channel buffer, sets S_sfx lookup. +// +void +S_Init +( int sfxVolume, + int musicVolume ); + + + + +// +// Per level startup code. +// Kills playing sounds at start of level, +// determines music if any, changes music. +// +void S_Start(void); + + +// +// Start sound for thing at +// using from sounds.h +// +void +S_StartSound +( void* origin, + int sound_id ); + + + +// Will start a sound at a given volume. +void +S_StartSoundAtVolume +( void* origin, + int sound_id, + int volume ); + + +// Stop sound for thing at +void S_StopSound(void* origin); + + +// Start music using from sounds.h +void S_StartMusic(int music_id); + +// Start music using from sounds.h, +// and set whether looping +void +S_ChangeMusic +( int music_id, + int looping ); + +// Stops the music fer sure. +void S_StopMusic(void); + +// Stop and resume music, during game PAUSE. +void S_PauseSound(void); +void S_ResumeSound(void); + + +// +// Updates music & sounds +// +void S_UpdateSounds(void* listener); + +void S_SetMusicVolume(int volume); +void S_SetSfxVolume(int volume); + + +#endif + diff --git a/doomclassic/doom/sounds.cpp b/doomclassic/doom/sounds.cpp new file mode 100644 index 00000000..40c7eb02 --- /dev/null +++ b/doomclassic/doom/sounds.cpp @@ -0,0 +1,161 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "doomtype.h" +#include "sounds.h" + +// +// Information about all the music +// + + + +// +// Information about all the sfx +// + +sfxinfo_t S_sfx[128] = +{ + // S_sfx[0] needs to be a dummy for odd reasons. + { "none", false, 0, 0, -1, -1, 0 }, + + { "pistol", false, 64, 0, -1, -1, 0 }, + { "shotgn", false, 64, 0, -1, -1, 0 }, + { "sgcock", false, 64, 0, -1, -1, 0 }, + { "dshtgn", false, 64, 0, -1, -1, 0 }, + { "dbopn", false, 64, 0, -1, -1, 0 }, + { "dbcls", false, 64, 0, -1, -1, 0 }, + { "dbload", false, 64, 0, -1, -1, 0 }, + { "plasma", false, 64, 0, -1, -1, 0 }, + { "bfg", false, 64, 0, -1, -1, 0 }, + { "sawup", false, 64, 0, -1, -1, 0 }, + { "sawidl", false, 118, 0, -1, -1, 0 }, + { "sawful", false, 64, 0, -1, -1, 0 }, + { "sawhit", false, 64, 0, -1, -1, 0 }, + { "rlaunc", false, 64, 0, -1, -1, 0 }, + { "rxplod", false, 70, 0, -1, -1, 0 }, + { "firsht", false, 70, 0, -1, -1, 0 }, + { "firxpl", false, 70, 0, -1, -1, 0 }, + { "pstart", false, 100, 0, -1, -1, 0 }, + { "pstop", false, 100, 0, -1, -1, 0 }, + { "doropn", false, 100, 0, -1, -1, 0 }, + { "dorcls", false, 100, 0, -1, -1, 0 }, + { "stnmov", false, 119, 0, -1, -1, 0 }, + { "swtchn", false, 78, 0, -1, -1, 0 }, + { "swtchx", false, 78, 0, -1, -1, 0 }, + { "plpain", false, 96, 0, -1, -1, 0 }, + { "dmpain", false, 96, 0, -1, -1, 0 }, + { "popain", false, 96, 0, -1, -1, 0 }, + { "vipain", false, 96, 0, -1, -1, 0 }, + { "mnpain", false, 96, 0, -1, -1, 0 }, + { "pepain", false, 96, 0, -1, -1, 0 }, + { "slop", false, 78, 0, -1, -1, 0 }, + { "itemup", true, 78, 0, -1, -1, 0 }, + { "wpnup", true, 78, 0, -1, -1, 0 }, + { "oof", false, 96, 0, -1, -1, 0 }, + { "telept", false, 32, 0, -1, -1, 0 }, + { "posit1", true, 98, 0, -1, -1, 0 }, + { "posit2", true, 98, 0, -1, -1, 0 }, + { "posit3", true, 98, 0, -1, -1, 0 }, + { "bgsit1", true, 98, 0, -1, -1, 0 }, + { "bgsit2", true, 98, 0, -1, -1, 0 }, + { "sgtsit", true, 98, 0, -1, -1, 0 }, + { "cacsit", true, 98, 0, -1, -1, 0 }, + { "brssit", true, 94, 0, -1, -1, 0 }, + { "cybsit", true, 92, 0, -1, -1, 0 }, + { "spisit", true, 90, 0, -1, -1, 0 }, + { "bspsit", true, 90, 0, -1, -1, 0 }, + { "kntsit", true, 90, 0, -1, -1, 0 }, + { "vilsit", true, 90, 0, -1, -1, 0 }, + { "mansit", true, 90, 0, -1, -1, 0 }, + { "pesit", true, 90, 0, -1, -1, 0 }, + { "sklatk", false, 70, 0, -1, -1, 0 }, + { "sgtatk", false, 70, 0, -1, -1, 0 }, + { "skepch", false, 70, 0, -1, -1, 0 }, + { "vilatk", false, 70, 0, -1, -1, 0 }, + { "claw", false, 70, 0, -1, -1, 0 }, + { "skeswg", false, 70, 0, -1, -1, 0 }, + { "pldeth", false, 32, 0, -1, -1, 0 }, + { "pdiehi", false, 32, 0, -1, -1, 0 }, + { "podth1", false, 70, 0, -1, -1, 0 }, + { "podth2", false, 70, 0, -1, -1, 0 }, + { "podth3", false, 70, 0, -1, -1, 0 }, + { "bgdth1", false, 70, 0, -1, -1, 0 }, + { "bgdth2", false, 70, 0, -1, -1, 0 }, + { "sgtdth", false, 70, 0, -1, -1, 0 }, + { "cacdth", false, 70, 0, -1, -1, 0 }, + { "skldth", false, 70, 0, -1, -1, 0 }, + { "brsdth", false, 32, 0, -1, -1, 0 }, + { "cybdth", false, 32, 0, -1, -1, 0 }, + { "spidth", false, 32, 0, -1, -1, 0 }, + { "bspdth", false, 32, 0, -1, -1, 0 }, + { "vildth", false, 32, 0, -1, -1, 0 }, + { "kntdth", false, 32, 0, -1, -1, 0 }, + { "pedth", false, 32, 0, -1, -1, 0 }, + { "skedth", false, 32, 0, -1, -1, 0 }, + { "posact", true, 120, 0, -1, -1, 0 }, + { "bgact", true, 120, 0, -1, -1, 0 }, + { "dmact", true, 120, 0, -1, -1, 0 }, + { "bspact", true, 100, 0, -1, -1, 0 }, + { "bspwlk", true, 100, 0, -1, -1, 0 }, + { "vilact", true, 100, 0, -1, -1, 0 }, + { "noway", false, 78, 0, -1, -1, 0 }, + { "barexp", false, 60, 0, -1, -1, 0 }, + { "punch", false, 64, 0, -1, -1, 0 }, + { "hoof", false, 70, 0, -1, -1, 0 }, + { "metal", false, 70, 0, -1, -1, 0 }, + { "chgun", false, 64, &S_sfx[sfx_pistol], 150, 0, 0 }, + { "tink", false, 60, 0, -1, -1, 0 }, + { "bdopn", false, 100, 0, -1, -1, 0 }, + { "bdcls", false, 100, 0, -1, -1, 0 }, + { "itmbk", false, 100, 0, -1, -1, 0 }, + { "flame", false, 32, 0, -1, -1, 0 }, + { "flamst", false, 32, 0, -1, -1, 0 }, + { "getpow", false, 60, 0, -1, -1, 0 }, + { "bospit", false, 70, 0, -1, -1, 0 }, + { "boscub", false, 70, 0, -1, -1, 0 }, + { "bossit", false, 70, 0, -1, -1, 0 }, + { "bospn", false, 70, 0, -1, -1, 0 }, + { "bosdth", false, 70, 0, -1, -1, 0 }, + { "manatk", false, 70, 0, -1, -1, 0 }, + { "mandth", false, 70, 0, -1, -1, 0 }, + { "sssit", false, 70, 0, -1, -1, 0 }, + { "ssdth", false, 70, 0, -1, -1, 0 }, + { "keenpn", false, 70, 0, -1, -1, 0 }, + { "keendt", false, 70, 0, -1, -1, 0 }, + { "skeact", false, 70, 0, -1, -1, 0 }, + { "skesit", false, 70, 0, -1, -1, 0 }, + { "skeatk", false, 70, 0, -1, -1, 0 }, + { "radio", false, 60, 0, -1, -1, 0 } +}; + + diff --git a/doomclassic/doom/sounds.h b/doomclassic/doom/sounds.h new file mode 100644 index 00000000..8ff5825a --- /dev/null +++ b/doomclassic/doom/sounds.h @@ -0,0 +1,299 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SOUNDS__ +#define __SOUNDS__ + + +// +// SoundFX struct. +// +typedef struct sfxinfo_struct sfxinfo_t; + +struct sfxinfo_struct +{ + // up to 6-character name + char* name; + + // Sfx singularity (only one at a time) + int singularity; + + // Sfx priority + int priority; + + // referenced sound if a link + sfxinfo_t* link; + + // pitch if a link + int pitch; + + // volume if a link + int volume; + + // sound data + void* data; + + // this is checked every second to see if sound + // can be thrown out (if 0, then decrement, if -1, + // then throw out, if > 0, then it is in use) + int usefulness; + + // lump number of sfx + int lumpnum; +}; + + + + +// +// MusicInfo struct. +// +typedef struct +{ + // up to 6-character name + const char* name; + + // lump number of music + int lumpnum; + + // music data + void* data; + + // music handle once registered + int handle; + +} musicinfo_t; + + + + +// the complete set of sound effects +extern sfxinfo_t S_sfx[]; + +// the complete set of music +extern musicinfo_t S_music[]; + +// +// Identifiers for all music in game. +// + +typedef enum +{ + mus_None, + mus_e1m1, + mus_e1m2, + mus_e1m3, + mus_e1m4, + mus_e1m5, + mus_e1m6, + mus_e1m7, + mus_e1m8, + mus_e1m9, + mus_e2m1, + mus_e2m2, + mus_e2m3, + mus_e2m4, + mus_e2m5, + mus_e2m6, + mus_e2m7, + mus_e2m8, + mus_e2m9, + mus_e3m1, + mus_e3m2, + mus_e3m3, + mus_e3m4, + mus_e3m5, + mus_e3m6, + mus_e3m7, + mus_e3m8, + mus_e3m9, + mus_inter, + mus_intro, + mus_bunny, + mus_victor, + mus_introa, + mus_runnin, + mus_stalks, + mus_countd, + mus_betwee, + mus_doom, + mus_the_da, + mus_shawn, + mus_ddtblu, + mus_in_cit, + mus_dead, + mus_stlks2, + mus_theda2, + mus_doom2, + mus_ddtbl2, + mus_runni2, + mus_dead2, + mus_stlks3, + mus_romero, + mus_shawn2, + mus_messag, + mus_count2, + mus_ddtbl3, + mus_ampie, + mus_theda3, + mus_adrian, + mus_messg2, + mus_romer2, + mus_tense, + mus_shawn3, + mus_openin, + mus_evil, + mus_ultima, + mus_read_m, + mus_dm2ttl, + mus_dm2int, + NUMMUSIC +} musicenum_t; + + +// +// Identifiers for all sfx in game. +// + +typedef enum +{ + sfx_None, + sfx_pistol, + sfx_shotgn, + sfx_sgcock, + sfx_dshtgn, + sfx_dbopn, + sfx_dbcls, + sfx_dbload, + sfx_plasma, + sfx_bfg, + sfx_sawup, + sfx_sawidl, + sfx_sawful, + sfx_sawhit, + sfx_rlaunc, + sfx_rxplod, + sfx_firsht, + sfx_firxpl, + sfx_pstart, + sfx_pstop, + sfx_doropn, + sfx_dorcls, + sfx_stnmov, + sfx_swtchn, + sfx_swtchx, + sfx_plpain, + sfx_dmpain, + sfx_popain, + sfx_vipain, + sfx_mnpain, + sfx_pepain, + sfx_slop, + sfx_itemup, + sfx_wpnup, + sfx_oof, + sfx_telept, + sfx_posit1, + sfx_posit2, + sfx_posit3, + sfx_bgsit1, + sfx_bgsit2, + sfx_sgtsit, + sfx_cacsit, + sfx_brssit, + sfx_cybsit, + sfx_spisit, + sfx_bspsit, + sfx_kntsit, + sfx_vilsit, + sfx_mansit, + sfx_pesit, + sfx_sklatk, + sfx_sgtatk, + sfx_skepch, + sfx_vilatk, + sfx_claw, + sfx_skeswg, + sfx_pldeth, + sfx_pdiehi, + sfx_podth1, + sfx_podth2, + sfx_podth3, + sfx_bgdth1, + sfx_bgdth2, + sfx_sgtdth, + sfx_cacdth, + sfx_skldth, + sfx_brsdth, + sfx_cybdth, + sfx_spidth, + sfx_bspdth, + sfx_vildth, + sfx_kntdth, + sfx_pedth, + sfx_skedth, + sfx_posact, + sfx_bgact, + sfx_dmact, + sfx_bspact, + sfx_bspwlk, + sfx_vilact, + sfx_noway, + sfx_barexp, + sfx_punch, + sfx_hoof, + sfx_metal, + sfx_chgun, + sfx_tink, + sfx_bdopn, + sfx_bdcls, + sfx_itmbk, + sfx_flame, + sfx_flamst, + sfx_getpow, + sfx_bospit, + sfx_boscub, + sfx_bossit, + sfx_bospn, + sfx_bosdth, + sfx_manatk, + sfx_mandth, + sfx_sssit, + sfx_ssdth, + sfx_keenpn, + sfx_keendt, + sfx_skeact, + sfx_skesit, + sfx_skeatk, + sfx_radio, + NUMSFX +} sfxenum_t; + +#endif + + diff --git a/doomclassic/doom/st_lib.cpp b/doomclassic/doom/st_lib.cpp new file mode 100644 index 00000000..5b0d23fc --- /dev/null +++ b/doomclassic/doom/st_lib.cpp @@ -0,0 +1,296 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + +#include + +#include "doomdef.h" + +#include "z_zone.h" +#include "v_video.h" + +#include "m_swap.h" + +#include "i_system.h" + +#include "w_wad.h" + +#include "st_stuff.h" +#include "st_lib.h" +#include "r_local.h" + + +// in AM_map.c + + + + +// +// Hack display negative frags. +// Loads and store the stminus lump. +// + +void STlib_init(void) +{ + ::g->sttminus = (patch_t *) W_CacheLumpName("STTMINUS", PU_STATIC_SHARED); +} + + +// ? +void +STlib_initNum +( st_number_t* n, + int x, + int y, + patch_t** pl, + int* num, + qboolean* on, + int width ) +{ + n->x = x; + n->y = y; + n->oldnum = 0; + n->width = width; + n->num = num; + n->on = on; + n->p = pl; +} + + +// +// A fairly efficient way to draw a number +// based on differences from the old number. +// Note: worth the trouble? +// +void +STlib_drawNum +( st_number_t* n, + qboolean refresh ) +{ + + int numdigits = n->width; + int num = *n->num; + + int w = SHORT(n->p[0]->width); + int h = SHORT(n->p[0]->height); + int x = n->x; + + int neg; + + n->oldnum = *n->num; + + neg = num < 0; + + if (neg) + { + if (numdigits == 2 && num < -9) + num = -9; + else if (numdigits == 3 && num < -99) + num = -99; + + num = -num; + } + + // clear the area + x = n->x - numdigits*w; + + if (n->y - ST_Y < 0) + I_Error("drawNum: n->y - ST_Y < 0"); + + V_CopyRect(x, n->y - ST_Y, BG, w*numdigits, h, x, n->y, FG); + + // if non-number, do not draw it + if (num == 1994) + return; + + x = n->x; + + // in the special case of 0, you draw 0 + if (!num) + V_DrawPatch(x - w, n->y, FG, n->p[ 0 ]); + + // draw the new number + while (num && numdigits--) + { + x -= w; + V_DrawPatch(x, n->y, FG, n->p[ num % 10 ]); + num /= 10; + } + + // draw a minus sign if necessary + if (neg) + V_DrawPatch(x - 8, n->y, FG, ::g->sttminus); +} + + +// +void +STlib_updateNum +( st_number_t* n, + qboolean refresh ) +{ + if (*n->on) STlib_drawNum(n, refresh); +} + + +// +void +STlib_initPercent +( st_percent_t* p, + int x, + int y, + patch_t** pl, + int* num, + qboolean* on, + patch_t* percent ) +{ + STlib_initNum(&p->n, x, y, pl, num, on, 3); + p->p = percent; +} + + + + +void +STlib_updatePercent +( st_percent_t* per, + int refresh ) +{ + if (refresh && *per->n.on) + V_DrawPatch(per->n.x, per->n.y, FG, per->p); + + STlib_updateNum(&per->n, refresh); +} + + + +void +STlib_initMultIcon +( st_multicon_t* i, + int x, + int y, + patch_t** il, + int* inum, + qboolean* on ) +{ + i->x = x; + i->y = y; + i->oldinum = -1; + i->inum = inum; + i->on = on; + i->p = il; +} + + + +void +STlib_updateMultIcon +( st_multicon_t* mi, + qboolean refresh ) +{ + int w; + int h; + int x; + int y; + + if (*mi->on + && (mi->oldinum != *mi->inum || refresh) + && (*mi->inum!=-1)) + { + if (mi->oldinum != -1) + { + x = mi->x - SHORT(mi->p[mi->oldinum]->leftoffset); + y = mi->y - SHORT(mi->p[mi->oldinum]->topoffset); + w = SHORT(mi->p[mi->oldinum]->width); + h = SHORT(mi->p[mi->oldinum]->height); + + if (y - ST_Y < 0) + I_Error("updateMultIcon: y - ST_Y < 0"); + + V_CopyRect(x, y-ST_Y, BG, w, h, x, y, FG); + } + V_DrawPatch(mi->x, mi->y, FG, mi->p[*mi->inum]); + mi->oldinum = *mi->inum; + } +} + + + +void +STlib_initBinIcon +( st_binicon_t* b, + int x, + int y, + patch_t* i, + qboolean* val, + qboolean* on ) +{ + b->x = x; + b->y = y; + b->oldval = 0; + b->val = val; + b->on = on; + b->p = i; +} + + + +void +STlib_updateBinIcon +( st_binicon_t* bi, + qboolean refresh ) +{ + int x; + int y; + int w; + int h; + + if (*bi->on + && (bi->oldval != *bi->val || refresh)) + { + x = bi->x - SHORT(bi->p->leftoffset); + y = bi->y - SHORT(bi->p->topoffset); + w = SHORT(bi->p->width); + h = SHORT(bi->p->height); + + if (y - ST_Y < 0) + I_Error("updateBinIcon: y - ST_Y < 0"); + + if (*bi->val) + V_DrawPatch(bi->x, bi->y, FG, bi->p); + else + V_CopyRect(x, y-ST_Y, BG, w, h, x, y, FG); + + bi->oldval = *bi->val; + } + +} + + diff --git a/doomclassic/doom/st_lib.h b/doomclassic/doom/st_lib.h new file mode 100644 index 00000000..d61f8ddc --- /dev/null +++ b/doomclassic/doom/st_lib.h @@ -0,0 +1,229 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STLIB__ +#define __STLIB__ + + +// We are referring to patches. +#include "r_defs.h" + + +// +// Background and foreground screen numbers +// +#define BG 4 +#define FG 0 + + + +// +// Typedefs of widgets +// + +// Number widget + +typedef struct +{ + // upper right-hand corner + // of the number (right-justified) + int x; + int y; + + // max # of digits in number + int width; + + // last number value + int oldnum; + + // pointer to current value + int* num; + + // pointer to qboolean stating + // whether to update number + qboolean* on; + + // list of patches for 0-9 + patch_t** p; + + // user data + int data; + +} st_number_t; + + + +// Percent widget ("child" of number widget, +// or, more precisely, contains a number widget.) +typedef struct +{ + // number information + st_number_t n; + + // percent sign graphic + patch_t* p; + +} st_percent_t; + + + +// Multiple Icon widget +typedef struct +{ + // center-justified location of icons + int x; + int y; + + // last icon number + int oldinum; + + // pointer to current icon + int* inum; + + // pointer to qboolean stating + // whether to update icon + qboolean* on; + + // list of icons + patch_t** p; + + // user data + int data; + +} st_multicon_t; + + + + +// Binary Icon widget + +typedef struct +{ + // center-justified location of icon + int x; + int y; + + // last icon value + int oldval; + + // pointer to current icon status + qboolean* val; + + // pointer to qboolean + // stating whether to update icon + qboolean* on; + + + patch_t* p; // icon + int data; // user data + +} st_binicon_t; + + + +// +// Widget creation, access, and update routines +// + +// Initializes widget library. +// More precisely, initialize STMINUS, +// everything else is done somewhere else. +// +void STlib_init(void); + + + +// Number widget routines +void +STlib_initNum +( st_number_t* n, + int x, + int y, + patch_t** pl, + int* num, + qboolean* on, + int width ); + +void +STlib_updateNum +( st_number_t* n, + qboolean refresh ); + + +// Percent widget routines +void +STlib_initPercent +( st_percent_t* p, + int x, + int y, + patch_t** pl, + int* num, + qboolean* on, + patch_t* percent ); + + +void +STlib_updatePercent +( st_percent_t* per, + int refresh ); + + +// Multiple Icon widget routines +void +STlib_initMultIcon +( st_multicon_t* mi, + int x, + int y, + patch_t** il, + int* inum, + qboolean* on ); + + +void +STlib_updateMultIcon +( st_multicon_t* mi, + qboolean refresh ); + +// Binary Icon widget routines + +void +STlib_initBinIcon +( st_binicon_t* b, + int x, + int y, + patch_t* i, + qboolean* val, + qboolean* on ); + +void +STlib_updateBinIcon +( st_binicon_t* bi, + qboolean refresh ); + +#endif + diff --git a/doomclassic/doom/st_stuff.cpp b/doomclassic/doom/st_stuff.cpp new file mode 100644 index 00000000..481ec823 --- /dev/null +++ b/doomclassic/doom/st_stuff.cpp @@ -0,0 +1,1474 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include + +#include "i_system.h" +#include "i_video.h" +#include "z_zone.h" +#include "m_random.h" +#include "w_wad.h" + +#include "doomdef.h" + +#include "g_game.h" + +#include "st_stuff.h" +#include "st_lib.h" +#include "r_local.h" + +#include "p_local.h" +#include "p_inter.h" + +#include "am_map.h" +#include "m_cheat.h" + +#include "s_sound.h" + +// Needs access to LFB. +#include "v_video.h" + +// State. +#include "doomstat.h" + +// Data. +#include "dstrings.h" +#include "sounds.h" + +// +// STATUS BAR DATA +// + + +// Palette indices. +// For damage/bonus red-/gold-shifts +// Radiation suit, green shift. + +// N/256*100% probability +// that the normal face state will change + +// For Responder + +// Location of status bar + + +// Should be set to patch width +// for tall numbers later on + +// Number of status ::g->faces. + + + + + + + + + +// Location and size of statistics, +// justified according to widget type. +// Problem is, within which space? STbar? Screen? +// Note: this could be read in by a lump. +// Problem is, is the stuff rendered +// into a buffer, +// or into the frame buffer? + +// AMMO number pos. + +// HEALTH number pos. + +// Weapon pos. + +// Frags pos. + +// ARMOR number pos. + +// Key icon positions. + +// Ammunition counter. + +// Indicate maximum ammunition. +// Only needed because backpack exists. + +// pistol + +// shotgun + +// chain gun + +// missile launcher + +// plasma gun + +// bfg + +// WPNS title + +// DETH title + +//Incoming messages window location +//UNUSED +// #define ST_MSGTEXTX (::g->viewwindowx) +// #define ST_MSGTEXTY (::g->viewwindowy+::g->viewheight-18) +// Dimensions given in characters. +// Or shall I say, in lines? + + +// Width, in characters again. +// Height, in ::g->lines. + + + + + +// main player in game + +// ST_Start() has just been called + +// used to execute ST_Init() only once + +// lump number for PLAYPAL + +// used for timing + +// used for making messages go away + +// used when in chat + +// States for the intermission + + +// whether in automap or first-person + +// whether left-side main status bar is active + +// whether status bar chat is active + +// value of ::g->st_chat before message popped up + +// whether chat window has the cursor on + +// !::g->deathmatch + +// !::g->deathmatch && ::g->st_statusbaron + +// !::g->deathmatch + +// main bar left + +// 0-9, tall numbers + +// tall % sign + +// 0-9, short, yellow (,different!) numbers + +// 3 key-cards, 3 skulls + +// face status patches + +// face background + +// main bar right + +// weapon ownership patches + +// ready-weapon widget + +// in ::g->deathmatch only, summary of frags stats + +// health widget + +// ::g->arms background + + +// weapon ownership widgets + +// face status widget + +// keycard widgets + +// armor widget + +// ammo widgets + +// max ammo widgets + + + +// number of frags so far in ::g->deathmatch + +// used to use appopriately pained face + +// used for evil grin + +// count until face changes + +// current face index, used by ::g->w_faces + +// holds key-type for each key box on bar + +// a random number per tick + + + +// Massive bunches of cheat shit +// to keep it from being easy to figure them out. +// Yeah, right... +const unsigned char cheat_mus_seq[] = +{ + 0xb2, 0x26, 0xb6, 0xae, 0xea, 1, 0, 0, 0xff +}; + +const unsigned char cheat_choppers_seq[] = +{ + 0xb2, 0x26, 0xe2, 0x32, 0xf6, 0x2a, 0x2a, 0xa6, 0x6a, 0xea, 0xff // id... +}; + +const unsigned char cheat_god_seq[] = +{ + 0xb2, 0x26, 0x26, 0xaa, 0x26, 0xff // iddqd +}; + +const unsigned char cheat_ammo_seq[] = +{ + 0xb2, 0x26, 0xf2, 0x66, 0xa2, 0xff // idkfa +}; + +const unsigned char cheat_ammonokey_seq[] = +{ + 0xb2, 0x26, 0x66, 0xa2, 0xff // idfa +}; + + +// Smashing Pumpkins Into Samml Piles Of Putried Debris. +const unsigned char cheat_noclip_seq[] = +{ + 0xb2, 0x26, 0xea, 0x2a, 0xb2, // idspispopd + 0xea, 0x2a, 0xf6, 0x2a, 0x26, 0xff +}; + +// +const unsigned char cheat_commercial_noclip_seq[] = +{ + 0xb2, 0x26, 0xe2, 0x36, 0xb2, 0x2a, 0xff // idclip +}; + + + +const unsigned char cheat_powerup_seq[7][10] = +{ + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x6e, 0xff }, // beholdv + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xea, 0xff }, // beholds + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xb2, 0xff }, // beholdi + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x6a, 0xff }, // beholdr + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xa2, 0xff }, // beholda + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x36, 0xff }, // beholdl + { 0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xff } // behold +}; + + +const unsigned char cheat_clev_seq[] = +{ + 0xb2, 0x26, 0xe2, 0x36, 0xa6, 0x6e, 1, 0, 0, 0xff // idclev +}; + + +// my position cheat +const unsigned char cheat_mypos_seq[] = +{ + 0xb2, 0x26, 0xb6, 0xba, 0x2a, 0xf6, 0xea, 0xff // idmypos +}; + + +// Now what? +cheatseq_t cheat_mus = cheatseq_t( cheat_mus_seq, 0 ); +cheatseq_t cheat_god = cheatseq_t( cheat_god_seq, 0 ); +cheatseq_t cheat_ammo = cheatseq_t( cheat_ammo_seq, 0 ); +cheatseq_t cheat_ammonokey = cheatseq_t( cheat_ammonokey_seq, 0 ); +cheatseq_t cheat_noclip = cheatseq_t( cheat_noclip_seq, 0 ); +cheatseq_t cheat_commercial_noclip = cheatseq_t( cheat_commercial_noclip_seq, 0 ); + +// ALAN + +// DISABLED cheatseq_t( cheat_powerup_seq[0], 0 ), cheatseq_t( cheat_powerup_seq[1], 0 ), +// cheatseq_t( cheat_powerup_seq[2], 0 ), +// DISABLED cheatseq_t( cheat_powerup_seq[3], 0 ), +// cheatseq_t( cheat_powerup_seq[4], 0 ),cheatseq_t( cheat_powerup_seq[5], 0 ),cheatseq_t( cheat_powerup_seq[6], 0 ) }; + +cheatseq_t cheat_choppers = cheatseq_t( cheat_choppers_seq, 0 ); +cheatseq_t cheat_clev = cheatseq_t( cheat_clev_seq, 0 ); +cheatseq_t cheat_mypos = cheatseq_t( cheat_mypos_seq, 0 ); + + +// +const extern char* mapnames[]; + + +// +// STATUS BAR CODE +// +void ST_Stop(void); + +void ST_refreshBackground(void) +{ + + if (::g->st_statusbaron) + { + V_DrawPatch(ST_X, 0, BG, ::g->sbar); + + if (::g->netgame) + V_DrawPatch(ST_FX, 0, BG, ::g->faceback); + + V_CopyRect(ST_X, 0, BG, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y, FG); + } + +} + + +// Respond to keyboard input ::g->events, +// intercept cheats. +qboolean +ST_Responder (event_t* ev) +{ + int i; + + // Filter automap on/off. + if (ev->type == ev_keyup + && ((ev->data1 & 0xffff0000) == AM_MSGHEADER)) + { + switch(ev->data1) + { + case AM_MSGENTERED: + ::g->st_gamestate = AutomapState; + ::g->st_firsttime = true; + break; + + case AM_MSGEXITED: + // I_PrintfE( "AM exited\n"); + ::g->st_gamestate = FirstPersonState; + break; + } + } + + // if a user keypress... + else if (ev->type == ev_keydown) + { + if (!::g->netgame) + { + // b. - enabled for more debug fun. + // if (::g->gameskill != sk_nightmare) { + + // 'dqd' cheat for toggleable god mode + if (cht_CheckCheat(&cheat_god, ev->data1)) + { + ::g->plyr->cheats ^= CF_GODMODE; + if (::g->plyr->cheats & CF_GODMODE) + { + if (::g->plyr->mo) + ::g->plyr->mo->health = 100; + + ::g->plyr->health = 100; + ::g->plyr->message = STSTR_DQDON; + } + else + ::g->plyr->message = STSTR_DQDOFF; + } + // 'fa' cheat for killer fucking arsenal + else if (cht_CheckCheat(&cheat_ammonokey, ev->data1)) + { + ::g->plyr->armorpoints = 200; + ::g->plyr->armortype = 2; + + for (i=0;iplyr->weaponowned[i] = true; + + for (i=0;iplyr->ammo[i] = ::g->plyr->maxammo[i]; + + ::g->plyr->message = STSTR_FAADDED; + } + // 'kfa' cheat for key full ammo + else if (cht_CheckCheat(&cheat_ammo, ev->data1)) + { + ::g->plyr->armorpoints = 200; + ::g->plyr->armortype = 2; + + for (i=0;iplyr->weaponowned[i] = true; + + for (i=0;iplyr->ammo[i] = ::g->plyr->maxammo[i]; + + for (i=0;iplyr->cards[i] = true; + + ::g->plyr->message = STSTR_KFAADDED; + } + // 'mus' cheat for changing music + else if (cht_CheckCheat(&cheat_mus, ev->data1)) + { + + char buf[3]; + int musnum; + + ::g->plyr->message = STSTR_MUS; + cht_GetParam(&cheat_mus, buf); + + if (::g->gamemode == commercial) + { + musnum = mus_runnin + (buf[0]-'0')*10 + buf[1]-'0' - 1; + + if (((buf[0]-'0')*10 + buf[1]-'0') > 35) + ::g->plyr->message = STSTR_NOMUS; + else + S_ChangeMusic(musnum, 1); + } + else + { + musnum = mus_e1m1 + (buf[0]-'1')*9 + (buf[1]-'1'); + + if (((buf[0]-'1')*9 + buf[1]-'1') > 31) + ::g->plyr->message = STSTR_NOMUS; + else + S_ChangeMusic(musnum, 1); + } + } + // Simplified, accepting both "noclip" and "idspispopd". + // no clipping mode cheat + else if ( cht_CheckCheat(&cheat_noclip, ev->data1) + || cht_CheckCheat(&cheat_commercial_noclip,ev->data1) ) + { + ::g->plyr->cheats ^= CF_NOCLIP; + + if (::g->plyr->cheats & CF_NOCLIP) + ::g->plyr->message = STSTR_NCON; + else + ::g->plyr->message = STSTR_NCOFF; + } + // 'behold?' power-up cheats + for (i=0;i<6;i++) + { + if (cht_CheckCheat(&::g->cheat_powerup[i], ev->data1)) + { + if (!::g->plyr->powers[i]) + P_GivePower( ::g->plyr, i); + else if (i!=pw_strength) + ::g->plyr->powers[i] = 1; + else + ::g->plyr->powers[i] = 0; + + ::g->plyr->message = STSTR_BEHOLDX; + } + } + + // 'behold' power-up menu + if (cht_CheckCheat(&::g->cheat_powerup[6], ev->data1)) + { + ::g->plyr->message = STSTR_BEHOLD; + } + // 'choppers' invulnerability & chainsaw + else if (cht_CheckCheat(&cheat_choppers, ev->data1)) + { + ::g->plyr->weaponowned[wp_chainsaw] = true; + ::g->plyr->powers[pw_invulnerability] = true; + ::g->plyr->message = STSTR_CHOPPERS; + } + // 'mypos' for player position + else if (cht_CheckCheat(&cheat_mypos, ev->data1)) + { + static char buf[ST_MSGWIDTH]; + sprintf(buf, "ang=0x%x;x,y=(0x%x,0x%x)", + ::g->players[::g->consoleplayer].mo->angle, + ::g->players[::g->consoleplayer].mo->x, + ::g->players[::g->consoleplayer].mo->y); + ::g->plyr->message = buf; + } + } + + // 'clev' change-level cheat +// ALAN NETWORKING + if (false) // cht_CheckCheat(&cheat_clev, ev->data1)) + { + char buf[3]; + int epsd; + int map; + + cht_GetParam(&cheat_clev, buf); + + if (::g->gamemode == commercial) + { + epsd = 0; + map = (buf[0] - '0')*10 + buf[1] - '0'; + } + else + { + epsd = buf[0] - '0'; + map = buf[1] - '0'; + } + + // Catch invalid maps. + if (epsd < 1) + return false; + + if (map < 1) + return false; + + // Ohmygod - this is not going to work. + if ((::g->gamemode == retail) + && ((epsd > 4) || (map > 9))) + return false; + + if ((::g->gamemode == registered) + && ((epsd > 3) || (map > 9))) + return false; + + if ((::g->gamemode == shareware) + && ((epsd > 1) || (map > 9))) + return false; + + if ((::g->gamemode == commercial) + && (( epsd > 1) || (map > 34))) + return false; + + // So be it. + ::g->plyr->message = STSTR_CLEV; + G_DeferedInitNew(::g->gameskill, epsd, map); + } + } + return false; +} + + + +int ST_calcPainOffset(void) +{ + int health; + + health = ::g->plyr->health > 100 ? 100 : ::g->plyr->health; + + if (health != ::g->oldhealth) + { + ::g->lastcalc = ST_FACESTRIDE * (((100 - health) * ST_NUMPAINFACES) / 101); + ::g->oldhealth = health; + } + return ::g->lastcalc; +} + + +// +// This is a not-very-pretty routine which handles +// the face states and their timing. +// the precedence of expressions is: +// dead > evil grin > turned head > straight ahead +// +void ST_updateFaceWidget(void) +{ + int i; + angle_t badguyangle; + angle_t diffang; + qboolean doevilgrin; + + if (::g->priority < 10) + { + // dead + if (!::g->plyr->health) + { + ::g->priority = 9; + ::g->st_faceindex = ST_DEADFACE; + ::g->st_facecount = 1; + } + } + + if (::g->priority < 9) + { + if (::g->plyr->bonuscount) + { + // picking up bonus + doevilgrin = false; + + for (i=0;ioldweaponsowned[i] != ::g->plyr->weaponowned[i]) + { + doevilgrin = true; + ::g->oldweaponsowned[i] = ::g->plyr->weaponowned[i]; + } + } + if (doevilgrin) + { + // evil grin if just picked up weapon + ::g->priority = 8; + ::g->st_facecount = ST_EVILGRINCOUNT; + ::g->st_faceindex = ST_calcPainOffset() + ST_EVILGRINOFFSET; + } + } + + } + + if (::g->priority < 8) + { + if (::g->plyr->damagecount + && ::g->plyr->attacker + && ::g->plyr->attacker != ::g->plyr->mo) + { + // being attacked + ::g->priority = 7; + + if (::g->plyr->health - ::g->st_oldhealth > ST_MUCHPAIN) + { + ::g->st_facecount = ST_TURNCOUNT; + ::g->st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET; + } + else + { + badguyangle = R_PointToAngle2(::g->plyr->mo->x, + ::g->plyr->mo->y, + ::g->plyr->attacker->x, + ::g->plyr->attacker->y); + + if (badguyangle > ::g->plyr->mo->angle) + { + // whether right or left + diffang = badguyangle - ::g->plyr->mo->angle; + i = diffang > ANG180; + } + else + { + // whether left or right + diffang = ::g->plyr->mo->angle - badguyangle; + i = diffang <= ANG180; + } // confusing, aint it? + + + ::g->st_facecount = ST_TURNCOUNT; + ::g->st_faceindex = ST_calcPainOffset(); + + if (diffang < ANG45) + { + // head-on + ::g->st_faceindex += ST_RAMPAGEOFFSET; + } + else if (i) + { + // turn face right + ::g->st_faceindex += ST_TURNOFFSET; + } + else + { + // turn face left + ::g->st_faceindex += ST_TURNOFFSET+1; + } + } + } + } + + if (::g->priority < 7) + { + // getting hurt because of your own damn stupidity + if (::g->plyr->damagecount) + { + if (::g->plyr->health - ::g->st_oldhealth > ST_MUCHPAIN) + { + ::g->priority = 7; + ::g->st_facecount = ST_TURNCOUNT; + ::g->st_faceindex = ST_calcPainOffset() + ST_OUCHOFFSET; + } + else + { + ::g->priority = 6; + ::g->st_facecount = ST_TURNCOUNT; + ::g->st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET; + } + + } + + } + + if (::g->priority < 6) + { + // rapid firing + if (::g->plyr->attackdown) + { + if (::g->lastattackdown==-1) + ::g->lastattackdown = ST_RAMPAGEDELAY; + else if (!--::g->lastattackdown) + { + ::g->priority = 5; + ::g->st_faceindex = ST_calcPainOffset() + ST_RAMPAGEOFFSET; + ::g->st_facecount = 1; + ::g->lastattackdown = 1; + } + } + else + ::g->lastattackdown = -1; + + } + + if (::g->priority < 5) + { + // invulnerability + if ((::g->plyr->cheats & CF_GODMODE) + || ::g->plyr->powers[pw_invulnerability]) + { + ::g->priority = 4; + + ::g->st_faceindex = ST_GODFACE; + ::g->st_facecount = 1; + + } + + } + + // look left or look right if the facecount has timed out + if (!::g->st_facecount) + { + ::g->st_faceindex = ST_calcPainOffset() + (::g->st_randomnumber % 3); + ::g->st_facecount = ST_STRAIGHTFACECOUNT; + ::g->priority = 0; + } + + ::g->st_facecount--; + +} + +void ST_updateWidgets(void) +{ + int i; + + // must redirect the pointer if the ready weapon has changed. + // if (::g->w_ready.data != ::g->plyr->readyweapon) + // { + if (weaponinfo[::g->plyr->readyweapon].ammo == am_noammo) + ::g->w_ready.num = &::g->largeammo; + else + ::g->w_ready.num = &::g->plyr->ammo[weaponinfo[::g->plyr->readyweapon].ammo]; + //{ + // static int tic=0; + // static int dir=-1; + // if (!(tic&15)) + // ::g->plyr->ammo[weaponinfo[::g->plyr->readyweapon].ammo]+=dir; + // if (::g->plyr->ammo[weaponinfo[::g->plyr->readyweapon].ammo] == -100) + // dir = 1; + // tic++; + // } + ::g->w_ready.data = ::g->plyr->readyweapon; + + // if (*::g->w_ready.on) + // STlib_updateNum(&::g->w_ready, true); + // refresh weapon change + // } + + // update keycard multiple widgets + for (i=0;i<3;i++) + { + ::g->keyboxes[i] = ::g->plyr->cards[i] ? i : -1; + + if (::g->plyr->cards[i+3]) + ::g->keyboxes[i] = i+3; + } + + // refresh everything if this is him coming back to life + ST_updateFaceWidget(); + + // used by the ::g->w_armsbg widget + ::g->st_notdeathmatch = !::g->deathmatch; + + // used by ::g->w_arms[] widgets + ::g->st_armson = ::g->st_statusbaron && !::g->deathmatch; + + // used by ::g->w_frags widget + ::g->st_fragson = ::g->deathmatch && ::g->st_statusbaron; + ::g->st_fragscount = 0; + + for (i=0 ; iconsoleplayer) + ::g->st_fragscount += ::g->plyr->frags[i]; + else + ::g->st_fragscount -= ::g->plyr->frags[i]; + } + + // get rid of chat window if up because of message + if (!--::g->st_msgcounter) + ::g->st_chat = ::g->st_oldchat; + +} + +void ST_Ticker (void) +{ + + ::g->st_clock++; + ::g->st_randomnumber = M_Random(); + ST_updateWidgets(); + ::g->st_oldhealth = ::g->plyr->health; + +} + + +void ST_doPaletteStuff(void) +{ + + int palette; + byte* pal; + int cnt; + int bzc; + + cnt = ::g->plyr->damagecount; + + if (::g->plyr->powers[pw_strength]) + { + // slowly fade the berzerk out + bzc = 12 - (::g->plyr->powers[pw_strength]>>6); + + if (bzc > cnt) + cnt = bzc; + } + + if (cnt) + { + palette = (cnt+7)>>3; + + if (palette >= NUMREDPALS) + palette = NUMREDPALS-1; + + palette += STARTREDPALS; + } + + else if (::g->plyr->bonuscount) + { + palette = (::g->plyr->bonuscount+7)>>3; + + if (palette >= NUMBONUSPALS) + palette = NUMBONUSPALS-1; + + palette += STARTBONUSPALS; + } + + else if ( ::g->plyr->powers[pw_ironfeet] > 4*32 + || ::g->plyr->powers[pw_ironfeet]&8) + palette = RADIATIONPAL; + else + palette = 0; + + if (palette != ::g->st_palette) + { + ::g->st_palette = palette; + pal = (byte *) W_CacheLumpNum (::g->lu_palette, PU_CACHE_SHARED)+palette*768; + I_SetPalette (pal); + } + +} + +void ST_drawWidgets(qboolean refresh) +{ + int i; + + ::g->st_notdeathmatch = !::g->deathmatch; + + // used by ::g->w_arms[] widgets + ::g->st_armson = ::g->st_statusbaron && !::g->deathmatch; + + // used by ::g->w_frags widget + ::g->st_fragson = ::g->deathmatch && ::g->st_statusbaron; + + STlib_updateNum(&::g->w_ready, refresh); + + for (i=0;i<4;i++) + { + STlib_updateNum(&::g->w_ammo[i], refresh); + STlib_updateNum(&::g->w_maxammo[i], refresh); + } + + STlib_updatePercent(&::g->w_health, refresh); + STlib_updatePercent(&::g->w_armor, refresh); + + STlib_updateBinIcon(&::g->w_armsbg, refresh); + + for (i=0;i<6;i++) + STlib_updateMultIcon(&::g->w_arms[i], refresh); + + STlib_updateMultIcon(&::g->w_faces, refresh); + + for (i=0;i<3;i++) + STlib_updateMultIcon(&::g->w_keyboxes[i], refresh); + + STlib_updateNum(&::g->w_frags, refresh); + +} + +void ST_doRefresh(void) +{ + ::g->st_firsttime = false; + + // draw status bar background to off-screen buff + ST_refreshBackground(); + + // and refresh all widgets + ST_drawWidgets(true); +} + +void ST_diffDraw(void) +{ + // update all widgets + ST_drawWidgets(false); +} + +void ST_Drawer (qboolean fullscreen, qboolean refresh) +{ + ::g->st_statusbaron = (!fullscreen) || ::g->automapactive; + ::g->st_firsttime = ::g->st_firsttime || refresh; + + // Do red-/gold-shifts from damage/items + ST_doPaletteStuff(); + + // If just after ST_Start(), refresh all + if (::g->st_firsttime) ST_doRefresh(); + // Otherwise, update as little as possible + else ST_diffDraw(); +} + +void ST_loadGraphics(void) +{ + static bool ST_HasBeenCalled = false; + +// if (ST_HasBeenCalled == true) +// return; + ST_HasBeenCalled = true; + + int i; + int j; + int facenum; + + char namebuf[9]; + + // Load the numbers, tall and short + for (i=0;i<10;i++) + { + sprintf(namebuf, "STTNUM%d", i); + ::g->tallnum[i] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC_SHARED); + + sprintf(namebuf, "STYSNUM%d", i); + ::g->shortnum[i] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC_SHARED); + } + + // Load percent key. + //Note: why not load STMINUS here, too? + ::g->tallpercent = (patch_t *) W_CacheLumpName("STTPRCNT", PU_STATIC_SHARED); + + // key cards + for (i=0;ikeys[i] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC_SHARED); + } + + // ::g->arms background + ::g->armsbg = (patch_t *) W_CacheLumpName("STARMS", PU_STATIC_SHARED); + + // ::g->arms ownership widgets + for (i=0;i<6;i++) + { + sprintf(namebuf, "STGNUM%d", i+2); + + // gray # + ::g->arms[i][0] = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC_SHARED); + + // yellow # + ::g->arms[i][1] = ::g->shortnum[i+2]; + } + + // face backgrounds for different color ::g->players + sprintf(namebuf, "STFB%d", ::g->consoleplayer); + ::g->faceback = (patch_t *) W_CacheLumpName(namebuf, PU_STATIC_SHARED); + + // status bar background bits + ::g->sbar = (patch_t *) W_CacheLumpName("STBAR", PU_STATIC_SHARED); + + // face states + facenum = 0; + for (i=0;ifaces[facenum++] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + } + sprintf(namebuf, "STFTR%d0", i); // turn right + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + sprintf(namebuf, "STFTL%d0", i); // turn left + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + sprintf(namebuf, "STFOUCH%d", i); // ouch! + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + sprintf(namebuf, "STFEVL%d", i); // evil grin ;) + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + sprintf(namebuf, "STFKILL%d", i); // pissed off + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName(namebuf, PU_STATIC_SHARED); + } + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName("STFGOD0", PU_STATIC_SHARED); + ::g->faces[facenum++] = (patch_t*)W_CacheLumpName("STFDEAD0", PU_STATIC_SHARED); + +} + +void ST_loadData(void) +{ + ::g->lu_palette = W_GetNumForName ("PLAYPAL"); + ST_loadGraphics(); +} + +void ST_unloadGraphics(void) +{ + // These things are always reloaded... so just don't bother to clean them up! +} + +void ST_unloadData(void) +{ + ST_unloadGraphics(); +} + +void ST_initData(void) +{ + + int i; + + ::g->st_firsttime = true; + ::g->plyr = &::g->players[::g->consoleplayer]; + + ::g->st_clock = 0; + ::g->st_chatstate = StartChatState; + ::g->st_gamestate = FirstPersonState; + + ::g->st_statusbaron = true; + ::g->st_oldchat = ::g->st_chat = false; + ::g->st_cursoron = false; + + ::g->st_faceindex = 0; + ::g->st_palette = -1; + + ::g->st_oldhealth = -1; + + for (i=0;ioldweaponsowned[i] = ::g->plyr->weaponowned[i]; + + for (i=0;i<3;i++) + ::g->keyboxes[i] = -1; + + STlib_init(); + +} + + + +void ST_createWidgets(void) +{ + + int i; + + // ready weapon ammo + STlib_initNum(&::g->w_ready, + ST_AMMOX, + ST_AMMOY, + ::g->tallnum, + &::g->plyr->ammo[weaponinfo[::g->plyr->readyweapon].ammo], + &::g->st_statusbaron, + ST_AMMOWIDTH ); + + // the last weapon type + ::g->w_ready.data = ::g->plyr->readyweapon; + + // health percentage + STlib_initPercent(&::g->w_health, + ST_HEALTHX, + ST_HEALTHY, + ::g->tallnum, + &::g->plyr->health, + &::g->st_statusbaron, + ::g->tallpercent); + + // ::g->arms background + STlib_initBinIcon(&::g->w_armsbg, + ST_ARMSBGX, + ST_ARMSBGY, + ::g->armsbg, + &::g->st_notdeathmatch, + &::g->st_statusbaron); + + // weapons owned + for(i=0;i<6;i++) + { + STlib_initMultIcon(&::g->w_arms[i], + ST_ARMSX+(i%3)*ST_ARMSXSPACE, + ST_ARMSY+(i/3)*ST_ARMSYSPACE, + ::g->arms[i], (int *) &::g->plyr->weaponowned[i+1], + &::g->st_armson); + } + + // frags sum + STlib_initNum(&::g->w_frags, + ST_FRAGSX, + ST_FRAGSY, + ::g->tallnum, + &::g->st_fragscount, + &::g->st_fragson, + ST_FRAGSWIDTH); + + // ::g->faces + STlib_initMultIcon(&::g->w_faces, + ST_FACESX, + ST_FACESY, + ::g->faces, + &::g->st_faceindex, + &::g->st_statusbaron); + + // armor percentage - should be colored later + STlib_initPercent(&::g->w_armor, + ST_ARMORX, + ST_ARMORY, + ::g->tallnum, + &::g->plyr->armorpoints, + &::g->st_statusbaron, ::g->tallpercent); + + // ::g->keyboxes 0-2 + STlib_initMultIcon(&::g->w_keyboxes[0], + ST_KEY0X, + ST_KEY0Y, + ::g->keys, + &::g->keyboxes[0], + &::g->st_statusbaron); + + STlib_initMultIcon(&::g->w_keyboxes[1], + ST_KEY1X, + ST_KEY1Y, + ::g->keys, + &::g->keyboxes[1], + &::g->st_statusbaron); + + STlib_initMultIcon(&::g->w_keyboxes[2], + ST_KEY2X, + ST_KEY2Y, + ::g->keys, + &::g->keyboxes[2], + &::g->st_statusbaron); + + // ammo count (all four kinds) + STlib_initNum(&::g->w_ammo[0], + ST_AMMO0X, + ST_AMMO0Y, + ::g->shortnum, + &::g->plyr->ammo[0], + &::g->st_statusbaron, + ST_AMMO0WIDTH); + + STlib_initNum(&::g->w_ammo[1], + ST_AMMO1X, + ST_AMMO1Y, + ::g->shortnum, + &::g->plyr->ammo[1], + &::g->st_statusbaron, + ST_AMMO1WIDTH); + + STlib_initNum(&::g->w_ammo[2], + ST_AMMO2X, + ST_AMMO2Y, + ::g->shortnum, + &::g->plyr->ammo[2], + &::g->st_statusbaron, + ST_AMMO2WIDTH); + + STlib_initNum(&::g->w_ammo[3], + ST_AMMO3X, + ST_AMMO3Y, + ::g->shortnum, + &::g->plyr->ammo[3], + &::g->st_statusbaron, + ST_AMMO3WIDTH); + + // max ammo count (all four kinds) + STlib_initNum(&::g->w_maxammo[0], + ST_MAXAMMO0X, + ST_MAXAMMO0Y, + ::g->shortnum, + &::g->plyr->maxammo[0], + &::g->st_statusbaron, + ST_MAXAMMO0WIDTH); + + STlib_initNum(&::g->w_maxammo[1], + ST_MAXAMMO1X, + ST_MAXAMMO1Y, + ::g->shortnum, + &::g->plyr->maxammo[1], + &::g->st_statusbaron, + ST_MAXAMMO1WIDTH); + + STlib_initNum(&::g->w_maxammo[2], + ST_MAXAMMO2X, + ST_MAXAMMO2Y, + ::g->shortnum, + &::g->plyr->maxammo[2], + &::g->st_statusbaron, + ST_MAXAMMO2WIDTH); + + STlib_initNum(&::g->w_maxammo[3], + ST_MAXAMMO3X, + ST_MAXAMMO3Y, + ::g->shortnum, + &::g->plyr->maxammo[3], + &::g->st_statusbaron, + ST_MAXAMMO3WIDTH); + +} + + + +void ST_Start (void) +{ + + if (!::g->st_stopped) + ST_Stop(); + + ST_initData(); + ST_createWidgets(); + ::g->st_stopped = false; + +} + +void ST_Stop (void) +{ + if (::g->st_stopped) + return; + + I_SetPalette ((byte*)W_CacheLumpNum ((int)::g->lu_palette, PU_CACHE_SHARED)); + + ::g->st_stopped = true; +} + +void ST_Init (void) +{ + ::g->veryfirsttime = 0; + ST_loadData(); + ::g->screens[4] = (byte *) DoomLib::Z_Malloc( SCREENWIDTH * SCREENHEIGHT /*ST_WIDTH*ST_HEIGHT*/, PU_STATIC, 0); + memset( ::g->screens[4], 0, SCREENWIDTH * SCREENHEIGHT ); +} + + +CONSOLE_COMMAND_SHIP( idqd, "cheat for toggleable god mode", 0 ) { + int oldPlayer = DoomLib::GetPlayer(); + DoomLib::SetPlayer( 0 ); + if ( ::g == NULL ) { + return; + } + + if (::g->gamestate != GS_LEVEL) { + return; + } + + ::g->plyr->cheats ^= CF_GODMODE; + if (::g->plyr->cheats & CF_GODMODE) + { + if (::g->plyr->mo) + ::g->plyr->mo->health = 100; + + ::g->plyr->health = 100; + ::g->plyr->message = STSTR_DQDON; + } + else + ::g->plyr->message = STSTR_DQDOFF; + + DoomLib::SetPlayer( oldPlayer ); +} + +CONSOLE_COMMAND_SHIP( idfa, "cheat for killer fucking arsenal", 0 ) { + int oldPlayer = DoomLib::GetPlayer(); + DoomLib::SetPlayer( 0 ); + if ( ::g == NULL ) { + return; + } + + if (::g->gamestate != GS_LEVEL) { + return; + } + + int i = 0; + ::g->plyr->armorpoints = 200; + ::g->plyr->armortype = 2; + + for (i=0;iplyr->weaponowned[i] = true; + + for (i=0;iplyr->ammo[i] = ::g->plyr->maxammo[i]; + + ::g->plyr->message = STSTR_FAADDED; + + DoomLib::SetPlayer( oldPlayer ); +} + +CONSOLE_COMMAND_SHIP( idkfa, "cheat for key full ammo", 0 ) { + int oldPlayer = DoomLib::GetPlayer(); + DoomLib::SetPlayer( 0 ); + if ( ::g == NULL ) { + return; + } + + if (::g->gamestate != GS_LEVEL) { + return; + } + + int i = 0; + ::g->plyr->armorpoints = 200; + ::g->plyr->armortype = 2; + + for (i=0;iplyr->weaponowned[i] = true; + + for (i=0;iplyr->ammo[i] = ::g->plyr->maxammo[i]; + + for (i=0;iplyr->cards[i] = true; + + ::g->plyr->message = STSTR_KFAADDED; + + DoomLib::SetPlayer( oldPlayer ); +} + + +CONSOLE_COMMAND_SHIP( idclip, "cheat for no clip", 0 ) { + int oldPlayer = DoomLib::GetPlayer(); + DoomLib::SetPlayer( 0 ); + if ( ::g == NULL ) { + return; + } + + if (::g->gamestate != GS_LEVEL) { + return; + } + + ::g->plyr->cheats ^= CF_NOCLIP; + + if (::g->plyr->cheats & CF_NOCLIP) + ::g->plyr->message = STSTR_NCON; + else + ::g->plyr->message = STSTR_NCOFF; + + DoomLib::SetPlayer( oldPlayer ); +} +CONSOLE_COMMAND_SHIP( idmypos, "for player position", 0 ) { + int oldPlayer = DoomLib::GetPlayer(); + DoomLib::SetPlayer( 0 ); + if ( ::g == NULL ) { + return; + } + + if (::g->gamestate != GS_LEVEL) { + return; + } + + static char buf[ST_MSGWIDTH]; + sprintf(buf, "ang=0x%x;x,y=(0x%x,0x%x)", + ::g->players[::g->consoleplayer].mo->angle, + ::g->players[::g->consoleplayer].mo->x, + ::g->players[::g->consoleplayer].mo->y); + ::g->plyr->message = buf; + + DoomLib::SetPlayer( oldPlayer ); +} + +CONSOLE_COMMAND_SHIP( idclev, "warp to next level", 0 ) { + int oldPlayer = DoomLib::GetPlayer(); + DoomLib::SetPlayer( 0 ); + if ( ::g == NULL ) { + return; + } + + if (::g->gamestate != GS_LEVEL) { + return; + } + + int epsd; + int map; + + if (::g->gamemode == commercial) + { + + if( args.Argc() > 1 ) { + epsd = 1; + map = atoi( args.Argv( 1 ) ); + } else { + idLib::Printf( "idclev takes map as first argument \n" ); + return; + } + + if( map > 32 ) { + map = 1; + } + } + else + { + if( args.Argc() > 2 ) { + epsd = atoi( args.Argv( 1 ) ); + map = atoi( args.Argv( 2 ) ); + } else { + idLib::Printf( "idclev takes episode and map as first two arguments \n" ); + return; + } + } + + // Catch invalid maps. + if (epsd < 1) + return; + + if (map < 1) + return; + + // Ohmygod - this is not going to work. + if ((::g->gamemode == retail) + && ((epsd > 4) || (map > 9))) + return; + + if ((::g->gamemode == registered) + && ((epsd > 3) || (map > 9))) + return; + + if ((::g->gamemode == shareware) + && ((epsd > 1) || (map > 9))) + return; + + if ((::g->gamemode == commercial) + && (( epsd > 1) || (map > 34))) + return; + + // So be it. + ::g->plyr->message = STSTR_CLEV; + G_DeferedInitNew(::g->gameskill, epsd, map); + + DoomLib::SetPlayer( oldPlayer ); +} \ No newline at end of file diff --git a/doomclassic/doom/st_stuff.h b/doomclassic/doom/st_stuff.h new file mode 100644 index 00000000..b8d9826f --- /dev/null +++ b/doomclassic/doom/st_stuff.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STSTUFF_H__ +#define __STSTUFF_H__ + +#include "doomtype.h" +#include "d_event.h" + +// Size of statusbar. +// Now sensitive for scaling. +#define ST_HEIGHT 32*SCREEN_MUL +#define ST_WIDTH ORIGINAL_WIDTH +#define ST_Y (ORIGINAL_HEIGHT - ST_HEIGHT) + + +// +// STATUS BAR +// + +// Called by main loop. +qboolean ST_Responder (event_t* ev); + +// Called by main loop. +void ST_Ticker (void); + +// Called by main loop. +void ST_Drawer (qboolean fullscreen, qboolean refresh); + +// Called when the console player is spawned on each level. +void ST_Start (void); + +// Called by startup code. +void ST_Init (void); + + + +// States for status bar code. +typedef enum +{ + AutomapState, + FirstPersonState + +} st_stateenum_t; + + +// States for the chat code. +typedef enum +{ + StartChatState, + WaitDestState, + GetChatState + +} st_chatstateenum_t; + + +qboolean ST_Responder(event_t* ev); + + + +#endif + diff --git a/doomclassic/doom/structs.h b/doomclassic/doom/structs.h new file mode 100644 index 00000000..af8dfac7 --- /dev/null +++ b/doomclassic/doom/structs.h @@ -0,0 +1,487 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma once + +// am_map.structs begin // +typedef struct +{ + int x, y; +} fpoint_t; +typedef struct +{ + fpoint_t a, b; +} fline_t; +typedef struct +{ + fixed_t x,y; +} mpoint_t; +typedef struct +{ + mpoint_t a, b; +} mline_t; +typedef struct +{ + fixed_t slp, islp; +} islope_t; +// am_map.structs end // +// f_finale.structs begin // +typedef struct +{ + char *name; + mobjtype_t type; +} castinfo_t; +// f_finale.structs end // +// i_input.structs begin // + +enum { + J_DELTAX, + J_DELTAY, +}; +enum InputEventType +{ + IETAxis, + IETButtonDigital, + IETButtonAnalog, + IETNone, +} ; +struct InputEvent +{ + InputEventType type; + int data; + int action; + int port; +} ; +// i_input.structs end // + +// mus2midi.structs begin // +typedef struct tagMUSheader_t { + char ID[4]; // identifier "MUS" 0x1A + WORD scoreLen; + WORD scoreStart; + WORD channels; // count of primary channels + WORD sec_channels; // count of secondary channels + WORD instrCnt; + WORD dummy; + //// variable-length part starts here +} MUSheader_t ; +typedef struct tagMidiHeaderChunk_t { + char name[4]; + int length; + + short format; // make 0 + short ntracks; // make 1 + short division; // 0xe250?? +} MidiHeaderChunk_t; +typedef struct tagMidiTrackChunk_t { + char name[4]; + int length; +} MidiTrackChunk_t; +// mus2midi.structs end // +// m_menu.structs begin // +typedef struct +{ + // 0 = no cursor here, 1 = ok, 2 = arrows ok + short status; + + char name[10]; + + // choice = menu item #. + // if status = 2, + // choice=0:leftarrow,1:rightarrow + void (*routine)(int choice); + + // hotkey in menu + char alphaKey; +} menuitem_t; +typedef struct menu_s +{ + short numitems; // # of menu items + struct menu_s* prevMenu; // previous menu + menuitem_t* menuitems; // menu items + void (*routine)(); // draw routine + short x; + short y; // x,y of menu + short lastOn; // last item user was on in menu +} menu_t; +typedef enum +{ + newgame = 0, + options, + loadgame, + savegame, + quitdoom, + main_end +} main_e; +typedef enum +{ + g_accept, + g_cancel, + g_change, + qut_end +} quit_e; +typedef enum +{ + ep1, + ep2, + ep3, + ep4, + ep_end +} episodes_e; +typedef enum +{ + ex1, + ex2, + ex_end +} expansions_e; +typedef enum +{ + killthings, + toorough, + hurtme, + violence, + nightmare, + newg_end +} newgame_e; +typedef enum +{ + endgame, + scrnsize, + messages, + //detail, + option_empty1, + mousesens, + option_empty2, + soundvol, + opt_end +} options_e; +typedef enum +{ + rdthsempty1, + read1_end +} read_e; +typedef enum +{ + rdthsempty2, + read2_end +} read_e2; +typedef enum +{ + sfx_vol, + sfx_empty1, + music_vol, + sfx_empty2, + sound_end +} sound_e; +typedef enum +{ + load1, + load2, + load3, + load4, + load5, + load6, + load_end +} load_e; +// m_menu.structs end // +// m_misc.structs begin // +struct default_t +{ + char* name; + union { + int * location; + const char * * charLocation; + }; + union { + int defaultvalue; + const char * charDefault; + }; + int scantranslate; // PC scan code hack + int untranslated; // lousy hack + + default_t( ) : + name( NULL ), + location( NULL ), + defaultvalue( 0 ), + scantranslate( 0 ), + untranslated( 0 ) { + } + + default_t( char * name_, int * location_, int defaultvalue_ ) : + name( name_ ), + location( location_ ), + defaultvalue( defaultvalue_ ) { + } + + default_t( char * name_, const char * * charLocation_, const char * charDefault_ ) : + name( name_ ), + charLocation( charLocation_ ), + charDefault( charDefault_ ) { + } +}; +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + + unsigned short xmin; + unsigned short ymin; + unsigned short xmax; + unsigned short ymax; + + unsigned short hres; + unsigned short vres; + + unsigned char palette[48]; + + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + + char filler[58]; + unsigned char data; // unbounded +} pcx_t; +// m_misc.structs end // +// p_enemy.structs begin // +typedef enum +{ + DI_EAST, + DI_NORTHEAST, + DI_NORTH, + DI_NORTHWEST, + DI_WEST, + DI_SOUTHWEST, + DI_SOUTH, + DI_SOUTHEAST, + DI_NODIR, + NUMDIRS + +} dirtype_t; +// p_enemy.structs end // +// p_saveg.structs begin // +typedef enum +{ + tc_end = 0, + tc_mobj + +} thinkerclass_t; +typedef enum +{ + tc_ceiling = 2, + tc_door, + tc_floor, + tc_plat, + tc_flash, + tc_strobe, + tc_glow, + tc_endspecials, + tc_fire + +} specials_e; +// p_saveg.structs end // +// p_spec.structs begin // +typedef struct +{ + qboolean istexture; + int picnum; + int basepic; + int numpics; + int speed; + +} anim_t2; +typedef struct +{ + qboolean istexture; // if false, it is a flat + char endname[9]; + char startname[9]; + int speed; +} animdef_t; +// p_spec.structs end // +// r_bsp.structs begin // +typedef struct +{ + int first; + int last; + +} cliprange_t; +// r_bsp.structs end // +// r_data.structs begin // +typedef struct +{ + short originx; + short originy; + short patch; + short stepdir; + short colormap; +} mappatch_t; +typedef struct +{ + char name[8]; + int masked; + short width; + short height; + void **columndirectory; // OBSOLETE + short patchcount; + mappatch_t patches[1]; +} maptexture_t; +typedef struct +{ + // Block origin (allways UL), + // which has allready accounted + // for the internal origin of the patch. + int originx; + int originy; + int patch; +} texpatch_t; +typedef struct +{ + // Keep name for switch changing, etc. + char name[8]; + short width; + short height; + + // All the patches[patchcount] + // are drawn back to front into the cached texture. + short patchcount; + texpatch_t patches[1]; + +} texture_t; +// r_data.structs end // +// r_things.structs begin // +typedef struct +{ + int x1; + int x2; + + int column; + int topclip; + int bottomclip; + +} maskdraw_t; +// r_things.structs end // +// st_stuff.structs begin // +typedef enum +{ + NoState = -1, + StatCount, + ShowNextLoc + +} stateenum_t; +// st_stuff.structs end // +// s_sound.structs begin // +typedef struct +{ + // sound information (if null, channel avail.) + sfxinfo_t* sfxinfo; + + // origin of sound + void* origin; + + // handle of the sound being played + int handle; + +} channel_t; +// s_sound.structs end // +// wi_stuff.structs begin // +typedef enum +{ + ANIM_ALWAYS, + ANIM_RANDOM, + ANIM_LEVEL + +} animenum_t; +typedef struct +{ + int x; + int y; + +} point_t; +typedef struct +{ + animenum_t type; + + // period in tics between animations + int period; + + // number of animation frames + int nanims; + + // location of animation + point_t loc; + + // ALWAYS: n/a, + // RANDOM: period deviation (<256), + // LEVEL: level + int data1; + + // ALWAYS: n/a, + // RANDOM: random base period, + // LEVEL: n/a + int data2; + + // actual graphics for frames of animations + patch_t* p[3]; + + // following must be initialized to zero before use! + + // next value of bcnt (used in conjunction with period) + int nexttic; + + // last drawn animation frame + int lastdrawn; + + // next frame number to animate + int ctr; + + // used by RANDOM and LEVEL when animating + int state; + +} anim_t; +// wi_stuff.structs end // +// z_zone.structs begin // +struct lumplookup +{ + int lump; + lumplookup *next; + lumplookup *prev; +}; +typedef struct +{ + // total bytes malloced, including header + int size; + + // start / end cap for linked list + memblock_t blocklist; + + memblock_t* rover; + +} memzone_t; +// z_zone.structs end // diff --git a/doomclassic/doom/tables.cpp b/doomclassic/doom/tables.cpp new file mode 100644 index 00000000..5982c1c0 --- /dev/null +++ b/doomclassic/doom/tables.cpp @@ -0,0 +1,2121 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + + +#include "tables.h" + + + + +int +SlopeDiv +( unsigned num, + unsigned den) +{ + unsigned ans; + + if (den < 512) + return SLOPERANGE; + + ans = (num<<3)/(den>>8); + + return ans <= SLOPERANGE ? ans : SLOPERANGE; +} + + + + +const int finetangent[4096] = +{ + -170910304,-56965752,-34178904,-24413316,-18988036,-15535599,-13145455,-11392683, + -10052327,-8994149,-8137527,-7429880,-6835455,-6329090,-5892567,-5512368, + -5178251,-4882318,-4618375,-4381502,-4167737,-3973855,-3797206,-3635590, + -3487165,-3350381,-3223918,-3106651,-2997613,-2895966,-2800983,-2712030, + -2628549,-2550052,-2476104,-2406322,-2340362,-2277919,-2218719,-2162516, + -2109087,-2058233,-2009771,-1963536,-1919378,-1877161,-1836758,-1798063, + -1760956,-1725348,-1691149,-1658278,-1626658,-1596220,-1566898,-1538632, + -1511367,-1485049,-1459630,-1435065,-1411312,-1388330,-1366084,-1344537, + -1323658,-1303416,-1283783,-1264730,-1246234,-1228269,-1210813,-1193846, + -1177345,-1161294,-1145673,-1130465,-1115654,-1101225,-1087164,-1073455, + -1060087,-1047046,-1034322,-1021901,-1009774,-997931,-986361,-975054, + -964003,-953199,-942633,-932298,-922186,-912289,-902602,-893117, + -883829,-874730,-865817,-857081,-848520,-840127,-831898,-823827, + -815910,-808143,-800521,-793041,-785699,-778490,-771411,-764460, + -757631,-750922,-744331,-737853,-731486,-725227,-719074,-713023, + -707072,-701219,-695462,-689797,-684223,-678737,-673338,-668024, + -662792,-657640,-652568,-647572,-642651,-637803,-633028,-628323, + -623686,-619117,-614613,-610174,-605798,-601483,-597229,-593033, + -588896,-584815,-580789,-576818,-572901,-569035,-565221,-561456, + -557741,-554074,-550455,-546881,-543354,-539870,-536431,-533034, + -529680,-526366,-523094,-519861,-516667,-513512,-510394,-507313, + -504269,-501261,-498287,-495348,-492443,-489571,-486732,-483925, + -481150,-478406,-475692,-473009,-470355,-467730,-465133,-462565, + -460024,-457511,-455024,-452564,-450129,-447720,-445337,-442978, + -440643,-438332,-436045,-433781,-431540,-429321,-427125,-424951, + -422798,-420666,-418555,-416465,-414395,-412344,-410314,-408303, + -406311,-404338,-402384,-400448,-398530,-396630,-394747,-392882, + -391034,-389202,-387387,-385589,-383807,-382040,-380290,-378555, + -376835,-375130,-373440,-371765,-370105,-368459,-366826,-365208, + -363604,-362013,-360436,-358872,-357321,-355783,-354257,-352744, + -351244,-349756,-348280,-346816,-345364,-343924,-342495,-341078, + -339671,-338276,-336892,-335519,-334157,-332805,-331464,-330133, + -328812,-327502,-326201,-324910,-323629,-322358,-321097,-319844, + -318601,-317368,-316143,-314928,-313721,-312524,-311335,-310154, + -308983,-307819,-306664,-305517,-304379,-303248,-302126,-301011, + -299904,-298805,-297714,-296630,-295554,-294485,-293423,-292369, + -291322,-290282,-289249,-288223,-287204,-286192,-285186,-284188, + -283195,-282210,-281231,-280258,-279292,-278332,-277378,-276430, + -275489,-274553,-273624,-272700,-271782,-270871,-269965,-269064, + -268169,-267280,-266397,-265519,-264646,-263779,-262917,-262060, + -261209,-260363,-259522,-258686,-257855,-257029,-256208,-255392, + -254581,-253774,-252973,-252176,-251384,-250596,-249813,-249035, + -248261,-247492,-246727,-245966,-245210,-244458,-243711,-242967, + -242228,-241493,-240763,-240036,-239314,-238595,-237881,-237170, + -236463,-235761,-235062,-234367,-233676,-232988,-232304,-231624, + -230948,-230275,-229606,-228941,-228279,-227621,-226966,-226314, + -225666,-225022,-224381,-223743,-223108,-222477,-221849,-221225, + -220603,-219985,-219370,-218758,-218149,-217544,-216941,-216341, + -215745,-215151,-214561,-213973,-213389,-212807,-212228,-211652, + -211079,-210509,-209941,-209376,-208815,-208255,-207699,-207145, + -206594,-206045,-205500,-204956,-204416,-203878,-203342,-202809, + -202279,-201751,-201226,-200703,-200182,-199664,-199149,-198636, + -198125,-197616,-197110,-196606,-196105,-195606,-195109,-194614, + -194122,-193631,-193143,-192658,-192174,-191693,-191213,-190736, + -190261,-189789,-189318,-188849,-188382,-187918,-187455,-186995, + -186536,-186080,-185625,-185173,-184722,-184274,-183827,-183382, + -182939,-182498,-182059,-181622,-181186,-180753,-180321,-179891, + -179463,-179037,-178612,-178190,-177769,-177349,-176932,-176516, + -176102,-175690,-175279,-174870,-174463,-174057,-173653,-173251, + -172850,-172451,-172053,-171657,-171263,-170870,-170479,-170089, + -169701,-169315,-168930,-168546,-168164,-167784,-167405,-167027, + -166651,-166277,-165904,-165532,-165162,-164793,-164426,-164060, + -163695,-163332,-162970,-162610,-162251,-161893,-161537,-161182, + -160828,-160476,-160125,-159775,-159427,-159079,-158734,-158389, + -158046,-157704,-157363,-157024,-156686,-156349,-156013,-155678, + -155345,-155013,-154682,-154352,-154024,-153697,-153370,-153045, + -152722,-152399,-152077,-151757,-151438,-151120,-150803,-150487, + -150172,-149859,-149546,-149235,-148924,-148615,-148307,-148000, + -147693,-147388,-147084,-146782,-146480,-146179,-145879,-145580, + -145282,-144986,-144690,-144395,-144101,-143808,-143517,-143226, + -142936,-142647,-142359,-142072,-141786,-141501,-141217,-140934, + -140651,-140370,-140090,-139810,-139532,-139254,-138977,-138701, + -138426,-138152,-137879,-137607,-137335,-137065,-136795,-136526, + -136258,-135991,-135725,-135459,-135195,-134931,-134668,-134406, + -134145,-133884,-133625,-133366,-133108,-132851,-132594,-132339, + -132084,-131830,-131576,-131324,-131072,-130821,-130571,-130322, + -130073,-129825,-129578,-129332,-129086,-128841,-128597,-128353, + -128111,-127869,-127627,-127387,-127147,-126908,-126669,-126432, + -126195,-125959,-125723,-125488,-125254,-125020,-124787,-124555, + -124324,-124093,-123863,-123633,-123404,-123176,-122949,-122722, + -122496,-122270,-122045,-121821,-121597,-121374,-121152,-120930, + -120709,-120489,-120269,-120050,-119831,-119613,-119396,-119179, + -118963,-118747,-118532,-118318,-118104,-117891,-117678,-117466, + -117254,-117044,-116833,-116623,-116414,-116206,-115998,-115790, + -115583,-115377,-115171,-114966,-114761,-114557,-114354,-114151, + -113948,-113746,-113545,-113344,-113143,-112944,-112744,-112546, + -112347,-112150,-111952,-111756,-111560,-111364,-111169,-110974, + -110780,-110586,-110393,-110200,-110008,-109817,-109626,-109435, + -109245,-109055,-108866,-108677,-108489,-108301,-108114,-107927, + -107741,-107555,-107369,-107184,-107000,-106816,-106632,-106449, + -106266,-106084,-105902,-105721,-105540,-105360,-105180,-105000, + -104821,-104643,-104465,-104287,-104109,-103933,-103756,-103580, + -103404,-103229,-103054,-102880,-102706,-102533,-102360,-102187, + -102015,-101843,-101671,-101500,-101330,-101159,-100990,-100820, + -100651,-100482,-100314,-100146,-99979,-99812,-99645,-99479, + -99313,-99148,-98982,-98818,-98653,-98489,-98326,-98163, + -98000,-97837,-97675,-97513,-97352,-97191,-97030,-96870, + -96710,-96551,-96391,-96233,-96074,-95916,-95758,-95601, + -95444,-95287,-95131,-94975,-94819,-94664,-94509,-94354, + -94200,-94046,-93892,-93739,-93586,-93434,-93281,-93129, + -92978,-92826,-92675,-92525,-92375,-92225,-92075,-91926, + -91777,-91628,-91480,-91332,-91184,-91036,-90889,-90742, + -90596,-90450,-90304,-90158,-90013,-89868,-89724,-89579, + -89435,-89292,-89148,-89005,-88862,-88720,-88577,-88435, + -88294,-88152,-88011,-87871,-87730,-87590,-87450,-87310, + -87171,-87032,-86893,-86755,-86616,-86479,-86341,-86204, + -86066,-85930,-85793,-85657,-85521,-85385,-85250,-85114, + -84980,-84845,-84710,-84576,-84443,-84309,-84176,-84043, + -83910,-83777,-83645,-83513,-83381,-83250,-83118,-82987, + -82857,-82726,-82596,-82466,-82336,-82207,-82078,-81949, + -81820,-81691,-81563,-81435,-81307,-81180,-81053,-80925, + -80799,-80672,-80546,-80420,-80294,-80168,-80043,-79918, + -79793,-79668,-79544,-79420,-79296,-79172,-79048,-78925, + -78802,-78679,-78557,-78434,-78312,-78190,-78068,-77947, + -77826,-77705,-77584,-77463,-77343,-77223,-77103,-76983, + -76864,-76744,-76625,-76506,-76388,-76269,-76151,-76033, + -75915,-75797,-75680,-75563,-75446,-75329,-75213,-75096, + -74980,-74864,-74748,-74633,-74517,-74402,-74287,-74172, + -74058,-73944,-73829,-73715,-73602,-73488,-73375,-73262, + -73149,-73036,-72923,-72811,-72699,-72587,-72475,-72363, + -72252,-72140,-72029,-71918,-71808,-71697,-71587,-71477, + -71367,-71257,-71147,-71038,-70929,-70820,-70711,-70602, + -70494,-70385,-70277,-70169,-70061,-69954,-69846,-69739, + -69632,-69525,-69418,-69312,-69205,-69099,-68993,-68887, + -68781,-68676,-68570,-68465,-68360,-68255,-68151,-68046, + -67942,-67837,-67733,-67629,-67526,-67422,-67319,-67216, + -67113,-67010,-66907,-66804,-66702,-66600,-66498,-66396, + -66294,-66192,-66091,-65989,-65888,-65787,-65686,-65586, + -65485,-65385,-65285,-65185,-65085,-64985,-64885,-64786, + -64687,-64587,-64488,-64389,-64291,-64192,-64094,-63996, + -63897,-63799,-63702,-63604,-63506,-63409,-63312,-63215, + -63118,-63021,-62924,-62828,-62731,-62635,-62539,-62443, + -62347,-62251,-62156,-62060,-61965,-61870,-61775,-61680, + -61585,-61491,-61396,-61302,-61208,-61114,-61020,-60926, + -60833,-60739,-60646,-60552,-60459,-60366,-60273,-60181, + -60088,-59996,-59903,-59811,-59719,-59627,-59535,-59444, + -59352,-59261,-59169,-59078,-58987,-58896,-58805,-58715, + -58624,-58534,-58443,-58353,-58263,-58173,-58083,-57994, + -57904,-57815,-57725,-57636,-57547,-57458,-57369,-57281, + -57192,-57104,-57015,-56927,-56839,-56751,-56663,-56575, + -56487,-56400,-56312,-56225,-56138,-56051,-55964,-55877, + -55790,-55704,-55617,-55531,-55444,-55358,-55272,-55186, + -55100,-55015,-54929,-54843,-54758,-54673,-54587,-54502, + -54417,-54333,-54248,-54163,-54079,-53994,-53910,-53826, + -53741,-53657,-53574,-53490,-53406,-53322,-53239,-53156, + -53072,-52989,-52906,-52823,-52740,-52657,-52575,-52492, + -52410,-52327,-52245,-52163,-52081,-51999,-51917,-51835, + -51754,-51672,-51591,-51509,-51428,-51347,-51266,-51185, + -51104,-51023,-50942,-50862,-50781,-50701,-50621,-50540, + -50460,-50380,-50300,-50221,-50141,-50061,-49982,-49902, + -49823,-49744,-49664,-49585,-49506,-49427,-49349,-49270, + -49191,-49113,-49034,-48956,-48878,-48799,-48721,-48643, + -48565,-48488,-48410,-48332,-48255,-48177,-48100,-48022, + -47945,-47868,-47791,-47714,-47637,-47560,-47484,-47407, + -47331,-47254,-47178,-47102,-47025,-46949,-46873,-46797, + -46721,-46646,-46570,-46494,-46419,-46343,-46268,-46193, + -46118,-46042,-45967,-45892,-45818,-45743,-45668,-45593, + -45519,-45444,-45370,-45296,-45221,-45147,-45073,-44999, + -44925,-44851,-44778,-44704,-44630,-44557,-44483,-44410, + -44337,-44263,-44190,-44117,-44044,-43971,-43898,-43826, + -43753,-43680,-43608,-43535,-43463,-43390,-43318,-43246, + -43174,-43102,-43030,-42958,-42886,-42814,-42743,-42671, + -42600,-42528,-42457,-42385,-42314,-42243,-42172,-42101, + -42030,-41959,-41888,-41817,-41747,-41676,-41605,-41535, + -41465,-41394,-41324,-41254,-41184,-41113,-41043,-40973, + -40904,-40834,-40764,-40694,-40625,-40555,-40486,-40416, + -40347,-40278,-40208,-40139,-40070,-40001,-39932,-39863, + -39794,-39726,-39657,-39588,-39520,-39451,-39383,-39314, + -39246,-39178,-39110,-39042,-38973,-38905,-38837,-38770, + -38702,-38634,-38566,-38499,-38431,-38364,-38296,-38229, + -38161,-38094,-38027,-37960,-37893,-37826,-37759,-37692, + -37625,-37558,-37491,-37425,-37358,-37291,-37225,-37158, + -37092,-37026,-36959,-36893,-36827,-36761,-36695,-36629, + -36563,-36497,-36431,-36365,-36300,-36234,-36168,-36103, + -36037,-35972,-35907,-35841,-35776,-35711,-35646,-35580, + -35515,-35450,-35385,-35321,-35256,-35191,-35126,-35062, + -34997,-34932,-34868,-34803,-34739,-34675,-34610,-34546, + -34482,-34418,-34354,-34289,-34225,-34162,-34098,-34034, + -33970,-33906,-33843,-33779,-33715,-33652,-33588,-33525, + -33461,-33398,-33335,-33272,-33208,-33145,-33082,-33019, + -32956,-32893,-32830,-32767,-32705,-32642,-32579,-32516, + -32454,-32391,-32329,-32266,-32204,-32141,-32079,-32017, + -31955,-31892,-31830,-31768,-31706,-31644,-31582,-31520, + -31458,-31396,-31335,-31273,-31211,-31150,-31088,-31026, + -30965,-30904,-30842,-30781,-30719,-30658,-30597,-30536, + -30474,-30413,-30352,-30291,-30230,-30169,-30108,-30048, + -29987,-29926,-29865,-29805,-29744,-29683,-29623,-29562, + -29502,-29441,-29381,-29321,-29260,-29200,-29140,-29080, + -29020,-28959,-28899,-28839,-28779,-28719,-28660,-28600, + -28540,-28480,-28420,-28361,-28301,-28241,-28182,-28122, + -28063,-28003,-27944,-27884,-27825,-27766,-27707,-27647, + -27588,-27529,-27470,-27411,-27352,-27293,-27234,-27175, + -27116,-27057,-26998,-26940,-26881,-26822,-26763,-26705, + -26646,-26588,-26529,-26471,-26412,-26354,-26295,-26237, + -26179,-26120,-26062,-26004,-25946,-25888,-25830,-25772, + -25714,-25656,-25598,-25540,-25482,-25424,-25366,-25308, + -25251,-25193,-25135,-25078,-25020,-24962,-24905,-24847, + -24790,-24732,-24675,-24618,-24560,-24503,-24446,-24389, + -24331,-24274,-24217,-24160,-24103,-24046,-23989,-23932, + -23875,-23818,-23761,-23704,-23647,-23591,-23534,-23477, + -23420,-23364,-23307,-23250,-23194,-23137,-23081,-23024, + -22968,-22911,-22855,-22799,-22742,-22686,-22630,-22573, + -22517,-22461,-22405,-22349,-22293,-22237,-22181,-22125, + -22069,-22013,-21957,-21901,-21845,-21789,-21733,-21678, + -21622,-21566,-21510,-21455,-21399,-21343,-21288,-21232, + -21177,-21121,-21066,-21010,-20955,-20900,-20844,-20789, + -20734,-20678,-20623,-20568,-20513,-20457,-20402,-20347, + -20292,-20237,-20182,-20127,-20072,-20017,-19962,-19907, + -19852,-19797,-19742,-19688,-19633,-19578,-19523,-19469, + -19414,-19359,-19305,-19250,-19195,-19141,-19086,-19032, + -18977,-18923,-18868,-18814,-18760,-18705,-18651,-18597, + -18542,-18488,-18434,-18380,-18325,-18271,-18217,-18163, + -18109,-18055,-18001,-17946,-17892,-17838,-17784,-17731, + -17677,-17623,-17569,-17515,-17461,-17407,-17353,-17300, + -17246,-17192,-17138,-17085,-17031,-16977,-16924,-16870, + -16817,-16763,-16710,-16656,-16603,-16549,-16496,-16442, + -16389,-16335,-16282,-16229,-16175,-16122,-16069,-16015, + -15962,-15909,-15856,-15802,-15749,-15696,-15643,-15590, + -15537,-15484,-15431,-15378,-15325,-15272,-15219,-15166, + -15113,-15060,-15007,-14954,-14901,-14848,-14795,-14743, + -14690,-14637,-14584,-14531,-14479,-14426,-14373,-14321, + -14268,-14215,-14163,-14110,-14057,-14005,-13952,-13900, + -13847,-13795,-13742,-13690,-13637,-13585,-13533,-13480, + -13428,-13375,-13323,-13271,-13218,-13166,-13114,-13062, + -13009,-12957,-12905,-12853,-12800,-12748,-12696,-12644, + -12592,-12540,-12488,-12436,-12383,-12331,-12279,-12227, + -12175,-12123,-12071,-12019,-11967,-11916,-11864,-11812, + -11760,-11708,-11656,-11604,-11552,-11501,-11449,-11397, + -11345,-11293,-11242,-11190,-11138,-11086,-11035,-10983, + -10931,-10880,-10828,-10777,-10725,-10673,-10622,-10570, + -10519,-10467,-10415,-10364,-10312,-10261,-10209,-10158, + -10106,-10055,-10004,-9952,-9901,-9849,-9798,-9747, + -9695,-9644,-9592,-9541,-9490,-9438,-9387,-9336, + -9285,-9233,-9182,-9131,-9080,-9028,-8977,-8926, + -8875,-8824,-8772,-8721,-8670,-8619,-8568,-8517, + -8466,-8414,-8363,-8312,-8261,-8210,-8159,-8108, + -8057,-8006,-7955,-7904,-7853,-7802,-7751,-7700, + -7649,-7598,-7547,-7496,-7445,-7395,-7344,-7293, + -7242,-7191,-7140,-7089,-7038,-6988,-6937,-6886, + -6835,-6784,-6733,-6683,-6632,-6581,-6530,-6480, + -6429,-6378,-6327,-6277,-6226,-6175,-6124,-6074, + -6023,-5972,-5922,-5871,-5820,-5770,-5719,-5668, + -5618,-5567,-5517,-5466,-5415,-5365,-5314,-5264, + -5213,-5162,-5112,-5061,-5011,-4960,-4910,-4859, + -4808,-4758,-4707,-4657,-4606,-4556,-4505,-4455, + -4404,-4354,-4303,-4253,-4202,-4152,-4101,-4051, + -4001,-3950,-3900,-3849,-3799,-3748,-3698,-3648, + -3597,-3547,-3496,-3446,-3395,-3345,-3295,-3244, + -3194,-3144,-3093,-3043,-2992,-2942,-2892,-2841, + -2791,-2741,-2690,-2640,-2590,-2539,-2489,-2439, + -2388,-2338,-2288,-2237,-2187,-2137,-2086,-2036, + -1986,-1935,-1885,-1835,-1784,-1734,-1684,-1633, + -1583,-1533,-1483,-1432,-1382,-1332,-1281,-1231, + -1181,-1131,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1131,1181, + 1231,1281,1332,1382,1432,1483,1533,1583, + 1633,1684,1734,1784,1835,1885,1935,1986, + 2036,2086,2137,2187,2237,2288,2338,2388, + 2439,2489,2539,2590,2640,2690,2741,2791, + 2841,2892,2942,2992,3043,3093,3144,3194, + 3244,3295,3345,3395,3446,3496,3547,3597, + 3648,3698,3748,3799,3849,3900,3950,4001, + 4051,4101,4152,4202,4253,4303,4354,4404, + 4455,4505,4556,4606,4657,4707,4758,4808, + 4859,4910,4960,5011,5061,5112,5162,5213, + 5264,5314,5365,5415,5466,5517,5567,5618, + 5668,5719,5770,5820,5871,5922,5972,6023, + 6074,6124,6175,6226,6277,6327,6378,6429, + 6480,6530,6581,6632,6683,6733,6784,6835, + 6886,6937,6988,7038,7089,7140,7191,7242, + 7293,7344,7395,7445,7496,7547,7598,7649, + 7700,7751,7802,7853,7904,7955,8006,8057, + 8108,8159,8210,8261,8312,8363,8414,8466, + 8517,8568,8619,8670,8721,8772,8824,8875, + 8926,8977,9028,9080,9131,9182,9233,9285, + 9336,9387,9438,9490,9541,9592,9644,9695, + 9747,9798,9849,9901,9952,10004,10055,10106, + 10158,10209,10261,10312,10364,10415,10467,10519, + 10570,10622,10673,10725,10777,10828,10880,10931, + 10983,11035,11086,11138,11190,11242,11293,11345, + 11397,11449,11501,11552,11604,11656,11708,11760, + 11812,11864,11916,11967,12019,12071,12123,12175, + 12227,12279,12331,12383,12436,12488,12540,12592, + 12644,12696,12748,12800,12853,12905,12957,13009, + 13062,13114,13166,13218,13271,13323,13375,13428, + 13480,13533,13585,13637,13690,13742,13795,13847, + 13900,13952,14005,14057,14110,14163,14215,14268, + 14321,14373,14426,14479,14531,14584,14637,14690, + 14743,14795,14848,14901,14954,15007,15060,15113, + 15166,15219,15272,15325,15378,15431,15484,15537, + 15590,15643,15696,15749,15802,15856,15909,15962, + 16015,16069,16122,16175,16229,16282,16335,16389, + 16442,16496,16549,16603,16656,16710,16763,16817, + 16870,16924,16977,17031,17085,17138,17192,17246, + 17300,17353,17407,17461,17515,17569,17623,17677, + 17731,17784,17838,17892,17946,18001,18055,18109, + 18163,18217,18271,18325,18380,18434,18488,18542, + 18597,18651,18705,18760,18814,18868,18923,18977, + 19032,19086,19141,19195,19250,19305,19359,19414, + 19469,19523,19578,19633,19688,19742,19797,19852, + 19907,19962,20017,20072,20127,20182,20237,20292, + 20347,20402,20457,20513,20568,20623,20678,20734, + 20789,20844,20900,20955,21010,21066,21121,21177, + 21232,21288,21343,21399,21455,21510,21566,21622, + 21678,21733,21789,21845,21901,21957,22013,22069, + 22125,22181,22237,22293,22349,22405,22461,22517, + 22573,22630,22686,22742,22799,22855,22911,22968, + 23024,23081,23137,23194,23250,23307,23364,23420, + 23477,23534,23591,23647,23704,23761,23818,23875, + 23932,23989,24046,24103,24160,24217,24274,24331, + 24389,24446,24503,24560,24618,24675,24732,24790, + 24847,24905,24962,25020,25078,25135,25193,25251, + 25308,25366,25424,25482,25540,25598,25656,25714, + 25772,25830,25888,25946,26004,26062,26120,26179, + 26237,26295,26354,26412,26471,26529,26588,26646, + 26705,26763,26822,26881,26940,26998,27057,27116, + 27175,27234,27293,27352,27411,27470,27529,27588, + 27647,27707,27766,27825,27884,27944,28003,28063, + 28122,28182,28241,28301,28361,28420,28480,28540, + 28600,28660,28719,28779,28839,28899,28959,29020, + 29080,29140,29200,29260,29321,29381,29441,29502, + 29562,29623,29683,29744,29805,29865,29926,29987, + 30048,30108,30169,30230,30291,30352,30413,30474, + 30536,30597,30658,30719,30781,30842,30904,30965, + 31026,31088,31150,31211,31273,31335,31396,31458, + 31520,31582,31644,31706,31768,31830,31892,31955, + 32017,32079,32141,32204,32266,32329,32391,32454, + 32516,32579,32642,32705,32767,32830,32893,32956, + 33019,33082,33145,33208,33272,33335,33398,33461, + 33525,33588,33652,33715,33779,33843,33906,33970, + 34034,34098,34162,34225,34289,34354,34418,34482, + 34546,34610,34675,34739,34803,34868,34932,34997, + 35062,35126,35191,35256,35321,35385,35450,35515, + 35580,35646,35711,35776,35841,35907,35972,36037, + 36103,36168,36234,36300,36365,36431,36497,36563, + 36629,36695,36761,36827,36893,36959,37026,37092, + 37158,37225,37291,37358,37425,37491,37558,37625, + 37692,37759,37826,37893,37960,38027,38094,38161, + 38229,38296,38364,38431,38499,38566,38634,38702, + 38770,38837,38905,38973,39042,39110,39178,39246, + 39314,39383,39451,39520,39588,39657,39726,39794, + 39863,39932,40001,40070,40139,40208,40278,40347, + 40416,40486,40555,40625,40694,40764,40834,40904, + 40973,41043,41113,41184,41254,41324,41394,41465, + 41535,41605,41676,41747,41817,41888,41959,42030, + 42101,42172,42243,42314,42385,42457,42528,42600, + 42671,42743,42814,42886,42958,43030,43102,43174, + 43246,43318,43390,43463,43535,43608,43680,43753, + 43826,43898,43971,44044,44117,44190,44263,44337, + 44410,44483,44557,44630,44704,44778,44851,44925, + 44999,45073,45147,45221,45296,45370,45444,45519, + 45593,45668,45743,45818,45892,45967,46042,46118, + 46193,46268,46343,46419,46494,46570,46646,46721, + 46797,46873,46949,47025,47102,47178,47254,47331, + 47407,47484,47560,47637,47714,47791,47868,47945, + 48022,48100,48177,48255,48332,48410,48488,48565, + 48643,48721,48799,48878,48956,49034,49113,49191, + 49270,49349,49427,49506,49585,49664,49744,49823, + 49902,49982,50061,50141,50221,50300,50380,50460, + 50540,50621,50701,50781,50862,50942,51023,51104, + 51185,51266,51347,51428,51509,51591,51672,51754, + 51835,51917,51999,52081,52163,52245,52327,52410, + 52492,52575,52657,52740,52823,52906,52989,53072, + 53156,53239,53322,53406,53490,53574,53657,53741, + 53826,53910,53994,54079,54163,54248,54333,54417, + 54502,54587,54673,54758,54843,54929,55015,55100, + 55186,55272,55358,55444,55531,55617,55704,55790, + 55877,55964,56051,56138,56225,56312,56400,56487, + 56575,56663,56751,56839,56927,57015,57104,57192, + 57281,57369,57458,57547,57636,57725,57815,57904, + 57994,58083,58173,58263,58353,58443,58534,58624, + 58715,58805,58896,58987,59078,59169,59261,59352, + 59444,59535,59627,59719,59811,59903,59996,60088, + 60181,60273,60366,60459,60552,60646,60739,60833, + 60926,61020,61114,61208,61302,61396,61491,61585, + 61680,61775,61870,61965,62060,62156,62251,62347, + 62443,62539,62635,62731,62828,62924,63021,63118, + 63215,63312,63409,63506,63604,63702,63799,63897, + 63996,64094,64192,64291,64389,64488,64587,64687, + 64786,64885,64985,65085,65185,65285,65385,65485, + 65586,65686,65787,65888,65989,66091,66192,66294, + 66396,66498,66600,66702,66804,66907,67010,67113, + 67216,67319,67422,67526,67629,67733,67837,67942, + 68046,68151,68255,68360,68465,68570,68676,68781, + 68887,68993,69099,69205,69312,69418,69525,69632, + 69739,69846,69954,70061,70169,70277,70385,70494, + 70602,70711,70820,70929,71038,71147,71257,71367, + 71477,71587,71697,71808,71918,72029,72140,72252, + 72363,72475,72587,72699,72811,72923,73036,73149, + 73262,73375,73488,73602,73715,73829,73944,74058, + 74172,74287,74402,74517,74633,74748,74864,74980, + 75096,75213,75329,75446,75563,75680,75797,75915, + 76033,76151,76269,76388,76506,76625,76744,76864, + 76983,77103,77223,77343,77463,77584,77705,77826, + 77947,78068,78190,78312,78434,78557,78679,78802, + 78925,79048,79172,79296,79420,79544,79668,79793, + 79918,80043,80168,80294,80420,80546,80672,80799, + 80925,81053,81180,81307,81435,81563,81691,81820, + 81949,82078,82207,82336,82466,82596,82726,82857, + 82987,83118,83250,83381,83513,83645,83777,83910, + 84043,84176,84309,84443,84576,84710,84845,84980, + 85114,85250,85385,85521,85657,85793,85930,86066, + 86204,86341,86479,86616,86755,86893,87032,87171, + 87310,87450,87590,87730,87871,88011,88152,88294, + 88435,88577,88720,88862,89005,89148,89292,89435, + 89579,89724,89868,90013,90158,90304,90450,90596, + 90742,90889,91036,91184,91332,91480,91628,91777, + 91926,92075,92225,92375,92525,92675,92826,92978, + 93129,93281,93434,93586,93739,93892,94046,94200, + 94354,94509,94664,94819,94975,95131,95287,95444, + 95601,95758,95916,96074,96233,96391,96551,96710, + 96870,97030,97191,97352,97513,97675,97837,98000, + 98163,98326,98489,98653,98818,98982,99148,99313, + 99479,99645,99812,99979,100146,100314,100482,100651, + 100820,100990,101159,101330,101500,101671,101843,102015, + 102187,102360,102533,102706,102880,103054,103229,103404, + 103580,103756,103933,104109,104287,104465,104643,104821, + 105000,105180,105360,105540,105721,105902,106084,106266, + 106449,106632,106816,107000,107184,107369,107555,107741, + 107927,108114,108301,108489,108677,108866,109055,109245, + 109435,109626,109817,110008,110200,110393,110586,110780, + 110974,111169,111364,111560,111756,111952,112150,112347, + 112546,112744,112944,113143,113344,113545,113746,113948, + 114151,114354,114557,114761,114966,115171,115377,115583, + 115790,115998,116206,116414,116623,116833,117044,117254, + 117466,117678,117891,118104,118318,118532,118747,118963, + 119179,119396,119613,119831,120050,120269,120489,120709, + 120930,121152,121374,121597,121821,122045,122270,122496, + 122722,122949,123176,123404,123633,123863,124093,124324, + 124555,124787,125020,125254,125488,125723,125959,126195, + 126432,126669,126908,127147,127387,127627,127869,128111, + 128353,128597,128841,129086,129332,129578,129825,130073, + 130322,130571,130821,131072,131324,131576,131830,132084, + 132339,132594,132851,133108,133366,133625,133884,134145, + 134406,134668,134931,135195,135459,135725,135991,136258, + 136526,136795,137065,137335,137607,137879,138152,138426, + 138701,138977,139254,139532,139810,140090,140370,140651, + 140934,141217,141501,141786,142072,142359,142647,142936, + 143226,143517,143808,144101,144395,144690,144986,145282, + 145580,145879,146179,146480,146782,147084,147388,147693, + 148000,148307,148615,148924,149235,149546,149859,150172, + 150487,150803,151120,151438,151757,152077,152399,152722, + 153045,153370,153697,154024,154352,154682,155013,155345, + 155678,156013,156349,156686,157024,157363,157704,158046, + 158389,158734,159079,159427,159775,160125,160476,160828, + 161182,161537,161893,162251,162610,162970,163332,163695, + 164060,164426,164793,165162,165532,165904,166277,166651, + 167027,167405,167784,168164,168546,168930,169315,169701, + 170089,170479,170870,171263,171657,172053,172451,172850, + 173251,173653,174057,174463,174870,175279,175690,176102, + 176516,176932,177349,177769,178190,178612,179037,179463, + 179891,180321,180753,181186,181622,182059,182498,182939, + 183382,183827,184274,184722,185173,185625,186080,186536, + 186995,187455,187918,188382,188849,189318,189789,190261, + 190736,191213,191693,192174,192658,193143,193631,194122, + 194614,195109,195606,196105,196606,197110,197616,198125, + 198636,199149,199664,200182,200703,201226,201751,202279, + 202809,203342,203878,204416,204956,205500,206045,206594, + 207145,207699,208255,208815,209376,209941,210509,211079, + 211652,212228,212807,213389,213973,214561,215151,215745, + 216341,216941,217544,218149,218758,219370,219985,220603, + 221225,221849,222477,223108,223743,224381,225022,225666, + 226314,226966,227621,228279,228941,229606,230275,230948, + 231624,232304,232988,233676,234367,235062,235761,236463, + 237170,237881,238595,239314,240036,240763,241493,242228, + 242967,243711,244458,245210,245966,246727,247492,248261, + 249035,249813,250596,251384,252176,252973,253774,254581, + 255392,256208,257029,257855,258686,259522,260363,261209, + 262060,262917,263779,264646,265519,266397,267280,268169, + 269064,269965,270871,271782,272700,273624,274553,275489, + 276430,277378,278332,279292,280258,281231,282210,283195, + 284188,285186,286192,287204,288223,289249,290282,291322, + 292369,293423,294485,295554,296630,297714,298805,299904, + 301011,302126,303248,304379,305517,306664,307819,308983, + 310154,311335,312524,313721,314928,316143,317368,318601, + 319844,321097,322358,323629,324910,326201,327502,328812, + 330133,331464,332805,334157,335519,336892,338276,339671, + 341078,342495,343924,345364,346816,348280,349756,351244, + 352744,354257,355783,357321,358872,360436,362013,363604, + 365208,366826,368459,370105,371765,373440,375130,376835, + 378555,380290,382040,383807,385589,387387,389202,391034, + 392882,394747,396630,398530,400448,402384,404338,406311, + 408303,410314,412344,414395,416465,418555,420666,422798, + 424951,427125,429321,431540,433781,436045,438332,440643, + 442978,445337,447720,450129,452564,455024,457511,460024, + 462565,465133,467730,470355,473009,475692,478406,481150, + 483925,486732,489571,492443,495348,498287,501261,504269, + 507313,510394,513512,516667,519861,523094,526366,529680, + 533034,536431,539870,543354,546881,550455,554074,557741, + 561456,565221,569035,572901,576818,580789,584815,588896, + 593033,597229,601483,605798,610174,614613,619117,623686, + 628323,633028,637803,642651,647572,652568,657640,662792, + 668024,673338,678737,684223,689797,695462,701219,707072, + 713023,719074,725227,731486,737853,744331,750922,757631, + 764460,771411,778490,785699,793041,800521,808143,815910, + 823827,831898,840127,848520,857081,865817,874730,883829, + 893117,902602,912289,922186,932298,942633,953199,964003, + 975054,986361,997931,1009774,1021901,1034322,1047046,1060087, + 1073455,1087164,1101225,1115654,1130465,1145673,1161294,1177345, + 1193846,1210813,1228269,1246234,1264730,1283783,1303416,1323658, + 1344537,1366084,1388330,1411312,1435065,1459630,1485049,1511367, + 1538632,1566898,1596220,1626658,1658278,1691149,1725348,1760956, + 1798063,1836758,1877161,1919378,1963536,2009771,2058233,2109087, + 2162516,2218719,2277919,2340362,2406322,2476104,2550052,2628549, + 2712030,2800983,2895966,2997613,3106651,3223918,3350381,3487165, + 3635590,3797206,3973855,4167737,4381502,4618375,4882318,5178251, + 5512368,5892567,6329090,6835455,7429880,8137527,8994149,10052327, + 11392683,13145455,15535599,18988036,24413316,34178904,56965752,170910304 +}; + + +const int finesine[10240] = +{ + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3541,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5748,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22433, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30426,30471,30515, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36555,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40300, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47860,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50065,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53911,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54308,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64601,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535, + 65535,65535,65535,65535,65535,65535,65535,65534, + 65534,65534,65533,65533,65532,65532,65531,65531, + 65530,65530,65529,65528,65527,65527,65526,65525, + 65524,65523,65522,65521,65520,65519,65518,65516, + 65515,65514,65513,65511,65510,65508,65507,65505, + 65504,65502,65501,65499,65497,65496,65494,65492, + 65490,65488,65486,65484,65482,65480,65478,65476, + 65474,65472,65470,65467,65465,65463,65460,65458, + 65455,65453,65450,65448,65445,65442,65440,65437, + 65434,65431,65429,65426,65423,65420,65417,65414, + 65411,65408,65404,65401,65398,65395,65391,65388, + 65385,65381,65378,65374,65371,65367,65363,65360, + 65356,65352,65349,65345,65341,65337,65333,65329, + 65325,65321,65317,65313,65309,65305,65300,65296, + 65292,65287,65283,65279,65274,65270,65265,65260, + 65256,65251,65246,65242,65237,65232,65227,65222, + 65217,65212,65207,65202,65197,65192,65187,65182, + 65177,65171,65166,65161,65155,65150,65144,65139, + 65133,65128,65122,65117,65111,65105,65099,65094, + 65088,65082,65076,65070,65064,65058,65052,65046, + 65040,65033,65027,65021,65015,65008,65002,64995, + 64989,64982,64976,64969,64963,64956,64949,64943, + 64936,64929,64922,64915,64908,64902,64895,64887, + 64880,64873,64866,64859,64852,64844,64837,64830, + 64822,64815,64808,64800,64793,64785,64777,64770, + 64762,64754,64747,64739,64731,64723,64715,64707, + 64699,64691,64683,64675,64667,64659,64651,64642, + 64634,64626,64617,64609,64600,64592,64584,64575, + 64566,64558,64549,64540,64532,64523,64514,64505, + 64496,64487,64478,64469,64460,64451,64442,64433, + 64424,64414,64405,64396,64387,64377,64368,64358, + 64349,64339,64330,64320,64310,64301,64291,64281, + 64271,64261,64252,64242,64232,64222,64212,64202, + 64192,64181,64171,64161,64151,64140,64130,64120, + 64109,64099,64088,64078,64067,64057,64046,64035, + 64025,64014,64003,63992,63981,63971,63960,63949, + 63938,63927,63915,63904,63893,63882,63871,63859, + 63848,63837,63825,63814,63803,63791,63779,63768, + 63756,63745,63733,63721,63709,63698,63686,63674, + 63662,63650,63638,63626,63614,63602,63590,63578, + 63565,63553,63541,63528,63516,63504,63491,63479, + 63466,63454,63441,63429,63416,63403,63390,63378, + 63365,63352,63339,63326,63313,63300,63287,63274, + 63261,63248,63235,63221,63208,63195,63182,63168, + 63155,63141,63128,63114,63101,63087,63074,63060, + 63046,63032,63019,63005,62991,62977,62963,62949, + 62935,62921,62907,62893,62879,62865,62850,62836, + 62822,62808,62793,62779,62764,62750,62735,62721, + 62706,62692,62677,62662,62648,62633,62618,62603, + 62588,62573,62558,62543,62528,62513,62498,62483, + 62468,62453,62437,62422,62407,62391,62376,62360, + 62345,62329,62314,62298,62283,62267,62251,62236, + 62220,62204,62188,62172,62156,62141,62125,62108, + 62092,62076,62060,62044,62028,62012,61995,61979, + 61963,61946,61930,61913,61897,61880,61864,61847, + 61831,61814,61797,61780,61764,61747,61730,61713, + 61696,61679,61662,61645,61628,61611,61594,61577, + 61559,61542,61525,61507,61490,61473,61455,61438, + 61420,61403,61385,61367,61350,61332,61314,61297, + 61279,61261,61243,61225,61207,61189,61171,61153, + 61135,61117,61099,61081,61062,61044,61026,61007, + 60989,60971,60952,60934,60915,60897,60878,60859, + 60841,60822,60803,60785,60766,60747,60728,60709, + 60690,60671,60652,60633,60614,60595,60576,60556, + 60537,60518,60499,60479,60460,60441,60421,60402, + 60382,60363,60343,60323,60304,60284,60264,60244, + 60225,60205,60185,60165,60145,60125,60105,60085, + 60065,60045,60025,60004,59984,59964,59944,59923, + 59903,59883,59862,59842,59821,59801,59780,59759, + 59739,59718,59697,59677,59656,59635,59614,59593, + 59572,59551,59530,59509,59488,59467,59446,59425, + 59404,59382,59361,59340,59318,59297,59276,59254, + 59233,59211,59190,59168,59146,59125,59103,59081, + 59059,59038,59016,58994,58972,58950,58928,58906, + 58884,58862,58840,58818,58795,58773,58751,58729, + 58706,58684,58662,58639,58617,58594,58572,58549, + 58527,58504,58481,58459,58436,58413,58390,58367, + 58345,58322,58299,58276,58253,58230,58207,58183, + 58160,58137,58114,58091,58067,58044,58021,57997, + 57974,57950,57927,57903,57880,57856,57833,57809, + 57785,57762,57738,57714,57690,57666,57642,57618, + 57594,57570,57546,57522,57498,57474,57450,57426, + 57402,57377,57353,57329,57304,57280,57255,57231, + 57206,57182,57157,57133,57108,57083,57059,57034, + 57009,56984,56959,56935,56910,56885,56860,56835, + 56810,56785,56760,56734,56709,56684,56659,56633, + 56608,56583,56557,56532,56507,56481,56456,56430, + 56404,56379,56353,56328,56302,56276,56250,56225, + 56199,56173,56147,56121,56095,56069,56043,56017, + 55991,55965,55938,55912,55886,55860,55833,55807, + 55781,55754,55728,55701,55675,55648,55622,55595, + 55569,55542,55515,55489,55462,55435,55408,55381, + 55354,55327,55300,55274,55246,55219,55192,55165, + 55138,55111,55084,55056,55029,55002,54974,54947, + 54920,54892,54865,54837,54810,54782,54755,54727, + 54699,54672,54644,54616,54588,54560,54533,54505, + 54477,54449,54421,54393,54365,54337,54308,54280, + 54252,54224,54196,54167,54139,54111,54082,54054, + 54026,53997,53969,53940,53911,53883,53854,53826, + 53797,53768,53739,53711,53682,53653,53624,53595, + 53566,53537,53508,53479,53450,53421,53392,53363, + 53334,53304,53275,53246,53216,53187,53158,53128, + 53099,53069,53040,53010,52981,52951,52922,52892, + 52862,52832,52803,52773,52743,52713,52683,52653, + 52624,52594,52564,52534,52503,52473,52443,52413, + 52383,52353,52322,52292,52262,52231,52201,52171, + 52140,52110,52079,52049,52018,51988,51957,51926, + 51896,51865,51834,51803,51773,51742,51711,51680, + 51649,51618,51587,51556,51525,51494,51463,51432, + 51401,51369,51338,51307,51276,51244,51213,51182, + 51150,51119,51087,51056,51024,50993,50961,50929, + 50898,50866,50834,50803,50771,50739,50707,50675, + 50644,50612,50580,50548,50516,50484,50452,50420, + 50387,50355,50323,50291,50259,50226,50194,50162, + 50129,50097,50065,50032,50000,49967,49935,49902, + 49869,49837,49804,49771,49739,49706,49673,49640, + 49608,49575,49542,49509,49476,49443,49410,49377, + 49344,49311,49278,49244,49211,49178,49145,49112, + 49078,49045,49012,48978,48945,48911,48878,48844, + 48811,48777,48744,48710,48676,48643,48609,48575, + 48542,48508,48474,48440,48406,48372,48338,48304, + 48271,48237,48202,48168,48134,48100,48066,48032, + 47998,47963,47929,47895,47860,47826,47792,47757, + 47723,47688,47654,47619,47585,47550,47516,47481, + 47446,47412,47377,47342,47308,47273,47238,47203, + 47168,47133,47098,47063,47028,46993,46958,46923, + 46888,46853,46818,46783,46747,46712,46677,46642, + 46606,46571,46536,46500,46465,46429,46394,46358, + 46323,46287,46252,46216,46180,46145,46109,46073, + 46037,46002,45966,45930,45894,45858,45822,45786, + 45750,45714,45678,45642,45606,45570,45534,45498, + 45462,45425,45389,45353,45316,45280,45244,45207, + 45171,45135,45098,45062,45025,44989,44952,44915, + 44879,44842,44806,44769,44732,44695,44659,44622, + 44585,44548,44511,44474,44437,44400,44363,44326, + 44289,44252,44215,44178,44141,44104,44067,44029, + 43992,43955,43918,43880,43843,43806,43768,43731, + 43693,43656,43618,43581,43543,43506,43468,43430, + 43393,43355,43317,43280,43242,43204,43166,43128, + 43091,43053,43015,42977,42939,42901,42863,42825, + 42787,42749,42711,42672,42634,42596,42558,42520, + 42481,42443,42405,42366,42328,42290,42251,42213, + 42174,42136,42097,42059,42020,41982,41943,41904, + 41866,41827,41788,41750,41711,41672,41633,41595, + 41556,41517,41478,41439,41400,41361,41322,41283, + 41244,41205,41166,41127,41088,41048,41009,40970, + 40931,40891,40852,40813,40773,40734,40695,40655, + 40616,40576,40537,40497,40458,40418,40379,40339, + 40300,40260,40220,40180,40141,40101,40061,40021, + 39982,39942,39902,39862,39822,39782,39742,39702, + 39662,39622,39582,39542,39502,39462,39422,39382, + 39341,39301,39261,39221,39180,39140,39100,39059, + 39019,38979,38938,38898,38857,38817,38776,38736, + 38695,38655,38614,38573,38533,38492,38451,38411, + 38370,38329,38288,38248,38207,38166,38125,38084, + 38043,38002,37961,37920,37879,37838,37797,37756, + 37715,37674,37633,37592,37551,37509,37468,37427, + 37386,37344,37303,37262,37220,37179,37137,37096, + 37055,37013,36972,36930,36889,36847,36805,36764, + 36722,36681,36639,36597,36556,36514,36472,36430, + 36388,36347,36305,36263,36221,36179,36137,36095, + 36053,36011,35969,35927,35885,35843,35801,35759, + 35717,35675,35633,35590,35548,35506,35464,35421, + 35379,35337,35294,35252,35210,35167,35125,35082, + 35040,34997,34955,34912,34870,34827,34785,34742, + 34699,34657,34614,34571,34529,34486,34443,34400, + 34358,34315,34272,34229,34186,34143,34100,34057, + 34015,33972,33929,33886,33843,33799,33756,33713, + 33670,33627,33584,33541,33498,33454,33411,33368, + 33325,33281,33238,33195,33151,33108,33065,33021, + 32978,32934,32891,32847,32804,32760,32717,32673, + 32630,32586,32542,32499,32455,32411,32368,32324, + 32280,32236,32193,32149,32105,32061,32017,31974, + 31930,31886,31842,31798,31754,31710,31666,31622, + 31578,31534,31490,31446,31402,31357,31313,31269, + 31225,31181,31136,31092,31048,31004,30959,30915, + 30871,30826,30782,30738,30693,30649,30604,30560, + 30515,30471,30426,30382,30337,30293,30248,30204, + 30159,30114,30070,30025,29980,29936,29891,29846, + 29801,29757,29712,29667,29622,29577,29533,29488, + 29443,29398,29353,29308,29263,29218,29173,29128, + 29083,29038,28993,28948,28903,28858,28812,28767, + 28722,28677,28632,28586,28541,28496,28451,28405, + 28360,28315,28269,28224,28179,28133,28088,28042, + 27997,27952,27906,27861,27815,27770,27724,27678, + 27633,27587,27542,27496,27450,27405,27359,27313, + 27268,27222,27176,27131,27085,27039,26993,26947, + 26902,26856,26810,26764,26718,26672,26626,26580, + 26534,26488,26442,26396,26350,26304,26258,26212, + 26166,26120,26074,26028,25982,25936,25889,25843, + 25797,25751,25705,25658,25612,25566,25520,25473, + 25427,25381,25334,25288,25241,25195,25149,25102, + 25056,25009,24963,24916,24870,24823,24777,24730, + 24684,24637,24591,24544,24497,24451,24404,24357, + 24311,24264,24217,24171,24124,24077,24030,23984, + 23937,23890,23843,23796,23750,23703,23656,23609, + 23562,23515,23468,23421,23374,23327,23280,23233, + 23186,23139,23092,23045,22998,22951,22904,22857, + 22810,22763,22716,22668,22621,22574,22527,22480, + 22433,22385,22338,22291,22243,22196,22149,22102, + 22054,22007,21960,21912,21865,21817,21770,21723, + 21675,21628,21580,21533,21485,21438,21390,21343, + 21295,21248,21200,21153,21105,21057,21010,20962, + 20915,20867,20819,20772,20724,20676,20629,20581, + 20533,20485,20438,20390,20342,20294,20246,20199, + 20151,20103,20055,20007,19959,19912,19864,19816, + 19768,19720,19672,19624,19576,19528,19480,19432, + 19384,19336,19288,19240,19192,19144,19096,19048, + 19000,18951,18903,18855,18807,18759,18711,18663, + 18614,18566,18518,18470,18421,18373,18325,18277, + 18228,18180,18132,18084,18035,17987,17939,17890, + 17842,17793,17745,17697,17648,17600,17551,17503, + 17455,17406,17358,17309,17261,17212,17164,17115, + 17067,17018,16970,16921,16872,16824,16775,16727, + 16678,16629,16581,16532,16484,16435,16386,16338, + 16289,16240,16191,16143,16094,16045,15997,15948, + 15899,15850,15802,15753,15704,15655,15606,15557, + 15509,15460,15411,15362,15313,15264,15215,15167, + 15118,15069,15020,14971,14922,14873,14824,14775, + 14726,14677,14628,14579,14530,14481,14432,14383, + 14334,14285,14236,14187,14138,14089,14040,13990, + 13941,13892,13843,13794,13745,13696,13646,13597, + 13548,13499,13450,13401,13351,13302,13253,13204, + 13154,13105,13056,13007,12957,12908,12859,12810, + 12760,12711,12662,12612,12563,12514,12464,12415, + 12366,12316,12267,12218,12168,12119,12069,12020, + 11970,11921,11872,11822,11773,11723,11674,11624, + 11575,11525,11476,11426,11377,11327,11278,11228, + 11179,11129,11080,11030,10981,10931,10882,10832, + 10782,10733,10683,10634,10584,10534,10485,10435, + 10386,10336,10286,10237,10187,10137,10088,10038, + 9988,9939,9889,9839,9790,9740,9690,9640, + 9591,9541,9491,9442,9392,9342,9292,9243, + 9193,9143,9093,9043,8994,8944,8894,8844, + 8794,8745,8695,8645,8595,8545,8496,8446, + 8396,8346,8296,8246,8196,8147,8097,8047, + 7997,7947,7897,7847,7797,7747,7697,7648, + 7598,7548,7498,7448,7398,7348,7298,7248, + 7198,7148,7098,7048,6998,6948,6898,6848, + 6798,6748,6698,6648,6598,6548,6498,6448, + 6398,6348,6298,6248,6198,6148,6098,6048, + 5998,5948,5898,5848,5798,5748,5697,5647, + 5597,5547,5497,5447,5397,5347,5297,5247, + 5197,5146,5096,5046,4996,4946,4896,4846, + 4796,4745,4695,4645,4595,4545,4495,4445, + 4394,4344,4294,4244,4194,4144,4093,4043, + 3993,3943,3893,3843,3792,3742,3692,3642, + 3592,3541,3491,3441,3391,3341,3291,3240, + 3190,3140,3090,3039,2989,2939,2889,2839, + 2788,2738,2688,2638,2587,2537,2487,2437, + 2387,2336,2286,2236,2186,2135,2085,2035, + 1985,1934,1884,1834,1784,1733,1683,1633, + 1583,1532,1482,1432,1382,1331,1281,1231, + 1181,1130,1080,1030,980,929,879,829, + 779,728,678,628,578,527,477,427, + 376,326,276,226,175,125,75,25, + -25,-75,-125,-175,-226,-276,-326,-376, + -427,-477,-527,-578,-628,-678,-728,-779, + -829,-879,-929,-980,-1030,-1080,-1130,-1181, + -1231,-1281,-1331,-1382,-1432,-1482,-1532,-1583, + -1633,-1683,-1733,-1784,-1834,-1884,-1934,-1985, + -2035,-2085,-2135,-2186,-2236,-2286,-2336,-2387, + -2437,-2487,-2537,-2588,-2638,-2688,-2738,-2788, + -2839,-2889,-2939,-2989,-3039,-3090,-3140,-3190, + -3240,-3291,-3341,-3391,-3441,-3491,-3541,-3592, + -3642,-3692,-3742,-3792,-3843,-3893,-3943,-3993, + -4043,-4093,-4144,-4194,-4244,-4294,-4344,-4394, + -4445,-4495,-4545,-4595,-4645,-4695,-4745,-4796, + -4846,-4896,-4946,-4996,-5046,-5096,-5146,-5197, + -5247,-5297,-5347,-5397,-5447,-5497,-5547,-5597, + -5647,-5697,-5748,-5798,-5848,-5898,-5948,-5998, + -6048,-6098,-6148,-6198,-6248,-6298,-6348,-6398, + -6448,-6498,-6548,-6598,-6648,-6698,-6748,-6798, + -6848,-6898,-6948,-6998,-7048,-7098,-7148,-7198, + -7248,-7298,-7348,-7398,-7448,-7498,-7548,-7598, + -7648,-7697,-7747,-7797,-7847,-7897,-7947,-7997, + -8047,-8097,-8147,-8196,-8246,-8296,-8346,-8396, + -8446,-8496,-8545,-8595,-8645,-8695,-8745,-8794, + -8844,-8894,-8944,-8994,-9043,-9093,-9143,-9193, + -9243,-9292,-9342,-9392,-9442,-9491,-9541,-9591, + -9640,-9690,-9740,-9790,-9839,-9889,-9939,-9988, + -10038,-10088,-10137,-10187,-10237,-10286,-10336,-10386, + -10435,-10485,-10534,-10584,-10634,-10683,-10733,-10782, + -10832,-10882,-10931,-10981,-11030,-11080,-11129,-11179, + -11228,-11278,-11327,-11377,-11426,-11476,-11525,-11575, + -11624,-11674,-11723,-11773,-11822,-11872,-11921,-11970, + -12020,-12069,-12119,-12168,-12218,-12267,-12316,-12366, + -12415,-12464,-12514,-12563,-12612,-12662,-12711,-12760, + -12810,-12859,-12908,-12957,-13007,-13056,-13105,-13154, + -13204,-13253,-13302,-13351,-13401,-13450,-13499,-13548, + -13597,-13647,-13696,-13745,-13794,-13843,-13892,-13941, + -13990,-14040,-14089,-14138,-14187,-14236,-14285,-14334, + -14383,-14432,-14481,-14530,-14579,-14628,-14677,-14726, + -14775,-14824,-14873,-14922,-14971,-15020,-15069,-15118, + -15167,-15215,-15264,-15313,-15362,-15411,-15460,-15509, + -15557,-15606,-15655,-15704,-15753,-15802,-15850,-15899, + -15948,-15997,-16045,-16094,-16143,-16191,-16240,-16289, + -16338,-16386,-16435,-16484,-16532,-16581,-16629,-16678, + -16727,-16775,-16824,-16872,-16921,-16970,-17018,-17067, + -17115,-17164,-17212,-17261,-17309,-17358,-17406,-17455, + -17503,-17551,-17600,-17648,-17697,-17745,-17793,-17842, + -17890,-17939,-17987,-18035,-18084,-18132,-18180,-18228, + -18277,-18325,-18373,-18421,-18470,-18518,-18566,-18614, + -18663,-18711,-18759,-18807,-18855,-18903,-18951,-19000, + -19048,-19096,-19144,-19192,-19240,-19288,-19336,-19384, + -19432,-19480,-19528,-19576,-19624,-19672,-19720,-19768, + -19816,-19864,-19912,-19959,-20007,-20055,-20103,-20151, + -20199,-20246,-20294,-20342,-20390,-20438,-20485,-20533, + -20581,-20629,-20676,-20724,-20772,-20819,-20867,-20915, + -20962,-21010,-21057,-21105,-21153,-21200,-21248,-21295, + -21343,-21390,-21438,-21485,-21533,-21580,-21628,-21675, + -21723,-21770,-21817,-21865,-21912,-21960,-22007,-22054, + -22102,-22149,-22196,-22243,-22291,-22338,-22385,-22433, + -22480,-22527,-22574,-22621,-22668,-22716,-22763,-22810, + -22857,-22904,-22951,-22998,-23045,-23092,-23139,-23186, + -23233,-23280,-23327,-23374,-23421,-23468,-23515,-23562, + -23609,-23656,-23703,-23750,-23796,-23843,-23890,-23937, + -23984,-24030,-24077,-24124,-24171,-24217,-24264,-24311, + -24357,-24404,-24451,-24497,-24544,-24591,-24637,-24684, + -24730,-24777,-24823,-24870,-24916,-24963,-25009,-25056, + -25102,-25149,-25195,-25241,-25288,-25334,-25381,-25427, + -25473,-25520,-25566,-25612,-25658,-25705,-25751,-25797, + -25843,-25889,-25936,-25982,-26028,-26074,-26120,-26166, + -26212,-26258,-26304,-26350,-26396,-26442,-26488,-26534, + -26580,-26626,-26672,-26718,-26764,-26810,-26856,-26902, + -26947,-26993,-27039,-27085,-27131,-27176,-27222,-27268, + -27313,-27359,-27405,-27450,-27496,-27542,-27587,-27633, + -27678,-27724,-27770,-27815,-27861,-27906,-27952,-27997, + -28042,-28088,-28133,-28179,-28224,-28269,-28315,-28360, + -28405,-28451,-28496,-28541,-28586,-28632,-28677,-28722, + -28767,-28812,-28858,-28903,-28948,-28993,-29038,-29083, + -29128,-29173,-29218,-29263,-29308,-29353,-29398,-29443, + -29488,-29533,-29577,-29622,-29667,-29712,-29757,-29801, + -29846,-29891,-29936,-29980,-30025,-30070,-30114,-30159, + -30204,-30248,-30293,-30337,-30382,-30426,-30471,-30515, + -30560,-30604,-30649,-30693,-30738,-30782,-30826,-30871, + -30915,-30959,-31004,-31048,-31092,-31136,-31181,-31225, + -31269,-31313,-31357,-31402,-31446,-31490,-31534,-31578, + -31622,-31666,-31710,-31754,-31798,-31842,-31886,-31930, + -31974,-32017,-32061,-32105,-32149,-32193,-32236,-32280, + -32324,-32368,-32411,-32455,-32499,-32542,-32586,-32630, + -32673,-32717,-32760,-32804,-32847,-32891,-32934,-32978, + -33021,-33065,-33108,-33151,-33195,-33238,-33281,-33325, + -33368,-33411,-33454,-33498,-33541,-33584,-33627,-33670, + -33713,-33756,-33799,-33843,-33886,-33929,-33972,-34015, + -34057,-34100,-34143,-34186,-34229,-34272,-34315,-34358, + -34400,-34443,-34486,-34529,-34571,-34614,-34657,-34699, + -34742,-34785,-34827,-34870,-34912,-34955,-34997,-35040, + -35082,-35125,-35167,-35210,-35252,-35294,-35337,-35379, + -35421,-35464,-35506,-35548,-35590,-35633,-35675,-35717, + -35759,-35801,-35843,-35885,-35927,-35969,-36011,-36053, + -36095,-36137,-36179,-36221,-36263,-36305,-36347,-36388, + -36430,-36472,-36514,-36555,-36597,-36639,-36681,-36722, + -36764,-36805,-36847,-36889,-36930,-36972,-37013,-37055, + -37096,-37137,-37179,-37220,-37262,-37303,-37344,-37386, + -37427,-37468,-37509,-37551,-37592,-37633,-37674,-37715, + -37756,-37797,-37838,-37879,-37920,-37961,-38002,-38043, + -38084,-38125,-38166,-38207,-38248,-38288,-38329,-38370, + -38411,-38451,-38492,-38533,-38573,-38614,-38655,-38695, + -38736,-38776,-38817,-38857,-38898,-38938,-38979,-39019, + -39059,-39100,-39140,-39180,-39221,-39261,-39301,-39341, + -39382,-39422,-39462,-39502,-39542,-39582,-39622,-39662, + -39702,-39742,-39782,-39822,-39862,-39902,-39942,-39982, + -40021,-40061,-40101,-40141,-40180,-40220,-40260,-40299, + -40339,-40379,-40418,-40458,-40497,-40537,-40576,-40616, + -40655,-40695,-40734,-40773,-40813,-40852,-40891,-40931, + -40970,-41009,-41048,-41087,-41127,-41166,-41205,-41244, + -41283,-41322,-41361,-41400,-41439,-41478,-41517,-41556, + -41595,-41633,-41672,-41711,-41750,-41788,-41827,-41866, + -41904,-41943,-41982,-42020,-42059,-42097,-42136,-42174, + -42213,-42251,-42290,-42328,-42366,-42405,-42443,-42481, + -42520,-42558,-42596,-42634,-42672,-42711,-42749,-42787, + -42825,-42863,-42901,-42939,-42977,-43015,-43053,-43091, + -43128,-43166,-43204,-43242,-43280,-43317,-43355,-43393, + -43430,-43468,-43506,-43543,-43581,-43618,-43656,-43693, + -43731,-43768,-43806,-43843,-43880,-43918,-43955,-43992, + -44029,-44067,-44104,-44141,-44178,-44215,-44252,-44289, + -44326,-44363,-44400,-44437,-44474,-44511,-44548,-44585, + -44622,-44659,-44695,-44732,-44769,-44806,-44842,-44879, + -44915,-44952,-44989,-45025,-45062,-45098,-45135,-45171, + -45207,-45244,-45280,-45316,-45353,-45389,-45425,-45462, + -45498,-45534,-45570,-45606,-45642,-45678,-45714,-45750, + -45786,-45822,-45858,-45894,-45930,-45966,-46002,-46037, + -46073,-46109,-46145,-46180,-46216,-46252,-46287,-46323, + -46358,-46394,-46429,-46465,-46500,-46536,-46571,-46606, + -46642,-46677,-46712,-46747,-46783,-46818,-46853,-46888, + -46923,-46958,-46993,-47028,-47063,-47098,-47133,-47168, + -47203,-47238,-47273,-47308,-47342,-47377,-47412,-47446, + -47481,-47516,-47550,-47585,-47619,-47654,-47688,-47723, + -47757,-47792,-47826,-47860,-47895,-47929,-47963,-47998, + -48032,-48066,-48100,-48134,-48168,-48202,-48236,-48271, + -48304,-48338,-48372,-48406,-48440,-48474,-48508,-48542, + -48575,-48609,-48643,-48676,-48710,-48744,-48777,-48811, + -48844,-48878,-48911,-48945,-48978,-49012,-49045,-49078, + -49112,-49145,-49178,-49211,-49244,-49278,-49311,-49344, + -49377,-49410,-49443,-49476,-49509,-49542,-49575,-49608, + -49640,-49673,-49706,-49739,-49771,-49804,-49837,-49869, + -49902,-49935,-49967,-50000,-50032,-50065,-50097,-50129, + -50162,-50194,-50226,-50259,-50291,-50323,-50355,-50387, + -50420,-50452,-50484,-50516,-50548,-50580,-50612,-50644, + -50675,-50707,-50739,-50771,-50803,-50834,-50866,-50898, + -50929,-50961,-50993,-51024,-51056,-51087,-51119,-51150, + -51182,-51213,-51244,-51276,-51307,-51338,-51369,-51401, + -51432,-51463,-51494,-51525,-51556,-51587,-51618,-51649, + -51680,-51711,-51742,-51773,-51803,-51834,-51865,-51896, + -51926,-51957,-51988,-52018,-52049,-52079,-52110,-52140, + -52171,-52201,-52231,-52262,-52292,-52322,-52353,-52383, + -52413,-52443,-52473,-52503,-52534,-52564,-52594,-52624, + -52653,-52683,-52713,-52743,-52773,-52803,-52832,-52862, + -52892,-52922,-52951,-52981,-53010,-53040,-53069,-53099, + -53128,-53158,-53187,-53216,-53246,-53275,-53304,-53334, + -53363,-53392,-53421,-53450,-53479,-53508,-53537,-53566, + -53595,-53624,-53653,-53682,-53711,-53739,-53768,-53797, + -53826,-53854,-53883,-53911,-53940,-53969,-53997,-54026, + -54054,-54082,-54111,-54139,-54167,-54196,-54224,-54252, + -54280,-54308,-54337,-54365,-54393,-54421,-54449,-54477, + -54505,-54533,-54560,-54588,-54616,-54644,-54672,-54699, + -54727,-54755,-54782,-54810,-54837,-54865,-54892,-54920, + -54947,-54974,-55002,-55029,-55056,-55084,-55111,-55138, + -55165,-55192,-55219,-55246,-55274,-55300,-55327,-55354, + -55381,-55408,-55435,-55462,-55489,-55515,-55542,-55569, + -55595,-55622,-55648,-55675,-55701,-55728,-55754,-55781, + -55807,-55833,-55860,-55886,-55912,-55938,-55965,-55991, + -56017,-56043,-56069,-56095,-56121,-56147,-56173,-56199, + -56225,-56250,-56276,-56302,-56328,-56353,-56379,-56404, + -56430,-56456,-56481,-56507,-56532,-56557,-56583,-56608, + -56633,-56659,-56684,-56709,-56734,-56760,-56785,-56810, + -56835,-56860,-56885,-56910,-56935,-56959,-56984,-57009, + -57034,-57059,-57083,-57108,-57133,-57157,-57182,-57206, + -57231,-57255,-57280,-57304,-57329,-57353,-57377,-57402, + -57426,-57450,-57474,-57498,-57522,-57546,-57570,-57594, + -57618,-57642,-57666,-57690,-57714,-57738,-57762,-57785, + -57809,-57833,-57856,-57880,-57903,-57927,-57950,-57974, + -57997,-58021,-58044,-58067,-58091,-58114,-58137,-58160, + -58183,-58207,-58230,-58253,-58276,-58299,-58322,-58345, + -58367,-58390,-58413,-58436,-58459,-58481,-58504,-58527, + -58549,-58572,-58594,-58617,-58639,-58662,-58684,-58706, + -58729,-58751,-58773,-58795,-58818,-58840,-58862,-58884, + -58906,-58928,-58950,-58972,-58994,-59016,-59038,-59059, + -59081,-59103,-59125,-59146,-59168,-59190,-59211,-59233, + -59254,-59276,-59297,-59318,-59340,-59361,-59382,-59404, + -59425,-59446,-59467,-59488,-59509,-59530,-59551,-59572, + -59593,-59614,-59635,-59656,-59677,-59697,-59718,-59739, + -59759,-59780,-59801,-59821,-59842,-59862,-59883,-59903, + -59923,-59944,-59964,-59984,-60004,-60025,-60045,-60065, + -60085,-60105,-60125,-60145,-60165,-60185,-60205,-60225, + -60244,-60264,-60284,-60304,-60323,-60343,-60363,-60382, + -60402,-60421,-60441,-60460,-60479,-60499,-60518,-60537, + -60556,-60576,-60595,-60614,-60633,-60652,-60671,-60690, + -60709,-60728,-60747,-60766,-60785,-60803,-60822,-60841, + -60859,-60878,-60897,-60915,-60934,-60952,-60971,-60989, + -61007,-61026,-61044,-61062,-61081,-61099,-61117,-61135, + -61153,-61171,-61189,-61207,-61225,-61243,-61261,-61279, + -61297,-61314,-61332,-61350,-61367,-61385,-61403,-61420, + -61438,-61455,-61473,-61490,-61507,-61525,-61542,-61559, + -61577,-61594,-61611,-61628,-61645,-61662,-61679,-61696, + -61713,-61730,-61747,-61764,-61780,-61797,-61814,-61831, + -61847,-61864,-61880,-61897,-61913,-61930,-61946,-61963, + -61979,-61995,-62012,-62028,-62044,-62060,-62076,-62092, + -62108,-62125,-62141,-62156,-62172,-62188,-62204,-62220, + -62236,-62251,-62267,-62283,-62298,-62314,-62329,-62345, + -62360,-62376,-62391,-62407,-62422,-62437,-62453,-62468, + -62483,-62498,-62513,-62528,-62543,-62558,-62573,-62588, + -62603,-62618,-62633,-62648,-62662,-62677,-62692,-62706, + -62721,-62735,-62750,-62764,-62779,-62793,-62808,-62822, + -62836,-62850,-62865,-62879,-62893,-62907,-62921,-62935, + -62949,-62963,-62977,-62991,-63005,-63019,-63032,-63046, + -63060,-63074,-63087,-63101,-63114,-63128,-63141,-63155, + -63168,-63182,-63195,-63208,-63221,-63235,-63248,-63261, + -63274,-63287,-63300,-63313,-63326,-63339,-63352,-63365, + -63378,-63390,-63403,-63416,-63429,-63441,-63454,-63466, + -63479,-63491,-63504,-63516,-63528,-63541,-63553,-63565, + -63578,-63590,-63602,-63614,-63626,-63638,-63650,-63662, + -63674,-63686,-63698,-63709,-63721,-63733,-63745,-63756, + -63768,-63779,-63791,-63803,-63814,-63825,-63837,-63848, + -63859,-63871,-63882,-63893,-63904,-63915,-63927,-63938, + -63949,-63960,-63971,-63981,-63992,-64003,-64014,-64025, + -64035,-64046,-64057,-64067,-64078,-64088,-64099,-64109, + -64120,-64130,-64140,-64151,-64161,-64171,-64181,-64192, + -64202,-64212,-64222,-64232,-64242,-64252,-64261,-64271, + -64281,-64291,-64301,-64310,-64320,-64330,-64339,-64349, + -64358,-64368,-64377,-64387,-64396,-64405,-64414,-64424, + -64433,-64442,-64451,-64460,-64469,-64478,-64487,-64496, + -64505,-64514,-64523,-64532,-64540,-64549,-64558,-64566, + -64575,-64584,-64592,-64601,-64609,-64617,-64626,-64634, + -64642,-64651,-64659,-64667,-64675,-64683,-64691,-64699, + -64707,-64715,-64723,-64731,-64739,-64747,-64754,-64762, + -64770,-64777,-64785,-64793,-64800,-64808,-64815,-64822, + -64830,-64837,-64844,-64852,-64859,-64866,-64873,-64880, + -64887,-64895,-64902,-64908,-64915,-64922,-64929,-64936, + -64943,-64949,-64956,-64963,-64969,-64976,-64982,-64989, + -64995,-65002,-65008,-65015,-65021,-65027,-65033,-65040, + -65046,-65052,-65058,-65064,-65070,-65076,-65082,-65088, + -65094,-65099,-65105,-65111,-65117,-65122,-65128,-65133, + -65139,-65144,-65150,-65155,-65161,-65166,-65171,-65177, + -65182,-65187,-65192,-65197,-65202,-65207,-65212,-65217, + -65222,-65227,-65232,-65237,-65242,-65246,-65251,-65256, + -65260,-65265,-65270,-65274,-65279,-65283,-65287,-65292, + -65296,-65300,-65305,-65309,-65313,-65317,-65321,-65325, + -65329,-65333,-65337,-65341,-65345,-65349,-65352,-65356, + -65360,-65363,-65367,-65371,-65374,-65378,-65381,-65385, + -65388,-65391,-65395,-65398,-65401,-65404,-65408,-65411, + -65414,-65417,-65420,-65423,-65426,-65429,-65431,-65434, + -65437,-65440,-65442,-65445,-65448,-65450,-65453,-65455, + -65458,-65460,-65463,-65465,-65467,-65470,-65472,-65474, + -65476,-65478,-65480,-65482,-65484,-65486,-65488,-65490, + -65492,-65494,-65496,-65497,-65499,-65501,-65502,-65504, + -65505,-65507,-65508,-65510,-65511,-65513,-65514,-65515, + -65516,-65518,-65519,-65520,-65521,-65522,-65523,-65524, + -65525,-65526,-65527,-65527,-65528,-65529,-65530,-65530, + -65531,-65531,-65532,-65532,-65533,-65533,-65534,-65534, + -65534,-65535,-65535,-65535,-65535,-65535,-65535,-65535, + -65535,-65535,-65535,-65535,-65535,-65535,-65535,-65534, + -65534,-65534,-65533,-65533,-65532,-65532,-65531,-65531, + -65530,-65530,-65529,-65528,-65527,-65527,-65526,-65525, + -65524,-65523,-65522,-65521,-65520,-65519,-65518,-65516, + -65515,-65514,-65513,-65511,-65510,-65508,-65507,-65505, + -65504,-65502,-65501,-65499,-65497,-65496,-65494,-65492, + -65490,-65488,-65486,-65484,-65482,-65480,-65478,-65476, + -65474,-65472,-65470,-65467,-65465,-65463,-65460,-65458, + -65455,-65453,-65450,-65448,-65445,-65442,-65440,-65437, + -65434,-65431,-65429,-65426,-65423,-65420,-65417,-65414, + -65411,-65408,-65404,-65401,-65398,-65395,-65391,-65388, + -65385,-65381,-65378,-65374,-65371,-65367,-65363,-65360, + -65356,-65352,-65349,-65345,-65341,-65337,-65333,-65329, + -65325,-65321,-65317,-65313,-65309,-65305,-65300,-65296, + -65292,-65287,-65283,-65279,-65274,-65270,-65265,-65260, + -65256,-65251,-65246,-65242,-65237,-65232,-65227,-65222, + -65217,-65212,-65207,-65202,-65197,-65192,-65187,-65182, + -65177,-65171,-65166,-65161,-65155,-65150,-65144,-65139, + -65133,-65128,-65122,-65117,-65111,-65105,-65099,-65094, + -65088,-65082,-65076,-65070,-65064,-65058,-65052,-65046, + -65040,-65033,-65027,-65021,-65015,-65008,-65002,-64995, + -64989,-64982,-64976,-64969,-64963,-64956,-64949,-64943, + -64936,-64929,-64922,-64915,-64908,-64902,-64895,-64887, + -64880,-64873,-64866,-64859,-64852,-64844,-64837,-64830, + -64822,-64815,-64808,-64800,-64793,-64785,-64777,-64770, + -64762,-64754,-64747,-64739,-64731,-64723,-64715,-64707, + -64699,-64691,-64683,-64675,-64667,-64659,-64651,-64642, + -64634,-64626,-64617,-64609,-64601,-64592,-64584,-64575, + -64566,-64558,-64549,-64540,-64532,-64523,-64514,-64505, + -64496,-64487,-64478,-64469,-64460,-64451,-64442,-64433, + -64424,-64414,-64405,-64396,-64387,-64377,-64368,-64358, + -64349,-64339,-64330,-64320,-64310,-64301,-64291,-64281, + -64271,-64261,-64252,-64242,-64232,-64222,-64212,-64202, + -64192,-64181,-64171,-64161,-64151,-64140,-64130,-64120, + -64109,-64099,-64088,-64078,-64067,-64057,-64046,-64035, + -64025,-64014,-64003,-63992,-63981,-63971,-63960,-63949, + -63938,-63927,-63915,-63904,-63893,-63882,-63871,-63859, + -63848,-63837,-63825,-63814,-63803,-63791,-63779,-63768, + -63756,-63745,-63733,-63721,-63709,-63698,-63686,-63674, + -63662,-63650,-63638,-63626,-63614,-63602,-63590,-63578, + -63565,-63553,-63541,-63528,-63516,-63504,-63491,-63479, + -63466,-63454,-63441,-63429,-63416,-63403,-63390,-63378, + -63365,-63352,-63339,-63326,-63313,-63300,-63287,-63274, + -63261,-63248,-63235,-63221,-63208,-63195,-63182,-63168, + -63155,-63141,-63128,-63114,-63101,-63087,-63074,-63060, + -63046,-63032,-63019,-63005,-62991,-62977,-62963,-62949, + -62935,-62921,-62907,-62893,-62879,-62865,-62850,-62836, + -62822,-62808,-62793,-62779,-62764,-62750,-62735,-62721, + -62706,-62692,-62677,-62662,-62648,-62633,-62618,-62603, + -62588,-62573,-62558,-62543,-62528,-62513,-62498,-62483, + -62468,-62453,-62437,-62422,-62407,-62391,-62376,-62360, + -62345,-62329,-62314,-62298,-62283,-62267,-62251,-62236, + -62220,-62204,-62188,-62172,-62156,-62141,-62125,-62108, + -62092,-62076,-62060,-62044,-62028,-62012,-61995,-61979, + -61963,-61946,-61930,-61913,-61897,-61880,-61864,-61847, + -61831,-61814,-61797,-61780,-61764,-61747,-61730,-61713, + -61696,-61679,-61662,-61645,-61628,-61611,-61594,-61577, + -61559,-61542,-61525,-61507,-61490,-61473,-61455,-61438, + -61420,-61403,-61385,-61367,-61350,-61332,-61314,-61297, + -61279,-61261,-61243,-61225,-61207,-61189,-61171,-61153, + -61135,-61117,-61099,-61081,-61062,-61044,-61026,-61007, + -60989,-60971,-60952,-60934,-60915,-60897,-60878,-60859, + -60841,-60822,-60803,-60785,-60766,-60747,-60728,-60709, + -60690,-60671,-60652,-60633,-60614,-60595,-60576,-60556, + -60537,-60518,-60499,-60479,-60460,-60441,-60421,-60402, + -60382,-60363,-60343,-60323,-60304,-60284,-60264,-60244, + -60225,-60205,-60185,-60165,-60145,-60125,-60105,-60085, + -60065,-60045,-60025,-60004,-59984,-59964,-59944,-59923, + -59903,-59883,-59862,-59842,-59821,-59801,-59780,-59759, + -59739,-59718,-59697,-59677,-59656,-59635,-59614,-59593, + -59572,-59551,-59530,-59509,-59488,-59467,-59446,-59425, + -59404,-59382,-59361,-59340,-59318,-59297,-59276,-59254, + -59233,-59211,-59189,-59168,-59146,-59125,-59103,-59081, + -59059,-59038,-59016,-58994,-58972,-58950,-58928,-58906, + -58884,-58862,-58840,-58818,-58795,-58773,-58751,-58729, + -58706,-58684,-58662,-58639,-58617,-58594,-58572,-58549, + -58527,-58504,-58481,-58459,-58436,-58413,-58390,-58367, + -58345,-58322,-58299,-58276,-58253,-58230,-58207,-58183, + -58160,-58137,-58114,-58091,-58067,-58044,-58021,-57997, + -57974,-57950,-57927,-57903,-57880,-57856,-57833,-57809, + -57785,-57762,-57738,-57714,-57690,-57666,-57642,-57618, + -57594,-57570,-57546,-57522,-57498,-57474,-57450,-57426, + -57402,-57377,-57353,-57329,-57304,-57280,-57255,-57231, + -57206,-57182,-57157,-57133,-57108,-57083,-57059,-57034, + -57009,-56984,-56959,-56935,-56910,-56885,-56860,-56835, + -56810,-56785,-56760,-56734,-56709,-56684,-56659,-56633, + -56608,-56583,-56557,-56532,-56507,-56481,-56456,-56430, + -56404,-56379,-56353,-56328,-56302,-56276,-56250,-56225, + -56199,-56173,-56147,-56121,-56095,-56069,-56043,-56017, + -55991,-55965,-55938,-55912,-55886,-55860,-55833,-55807, + -55781,-55754,-55728,-55701,-55675,-55648,-55622,-55595, + -55569,-55542,-55515,-55489,-55462,-55435,-55408,-55381, + -55354,-55327,-55300,-55274,-55246,-55219,-55192,-55165, + -55138,-55111,-55084,-55056,-55029,-55002,-54974,-54947, + -54920,-54892,-54865,-54837,-54810,-54782,-54755,-54727, + -54699,-54672,-54644,-54616,-54588,-54560,-54533,-54505, + -54477,-54449,-54421,-54393,-54365,-54337,-54308,-54280, + -54252,-54224,-54196,-54167,-54139,-54111,-54082,-54054, + -54026,-53997,-53969,-53940,-53911,-53883,-53854,-53826, + -53797,-53768,-53739,-53711,-53682,-53653,-53624,-53595, + -53566,-53537,-53508,-53479,-53450,-53421,-53392,-53363, + -53334,-53304,-53275,-53246,-53216,-53187,-53158,-53128, + -53099,-53069,-53040,-53010,-52981,-52951,-52922,-52892, + -52862,-52832,-52803,-52773,-52743,-52713,-52683,-52653, + -52624,-52594,-52564,-52534,-52503,-52473,-52443,-52413, + -52383,-52353,-52322,-52292,-52262,-52231,-52201,-52171, + -52140,-52110,-52079,-52049,-52018,-51988,-51957,-51926, + -51896,-51865,-51834,-51803,-51773,-51742,-51711,-51680, + -51649,-51618,-51587,-51556,-51525,-51494,-51463,-51432, + -51401,-51369,-51338,-51307,-51276,-51244,-51213,-51182, + -51150,-51119,-51087,-51056,-51024,-50993,-50961,-50929, + -50898,-50866,-50834,-50803,-50771,-50739,-50707,-50675, + -50644,-50612,-50580,-50548,-50516,-50484,-50452,-50420, + -50387,-50355,-50323,-50291,-50259,-50226,-50194,-50162, + -50129,-50097,-50065,-50032,-50000,-49967,-49935,-49902, + -49869,-49837,-49804,-49771,-49739,-49706,-49673,-49640, + -49608,-49575,-49542,-49509,-49476,-49443,-49410,-49377, + -49344,-49311,-49278,-49244,-49211,-49178,-49145,-49112, + -49078,-49045,-49012,-48978,-48945,-48911,-48878,-48844, + -48811,-48777,-48744,-48710,-48676,-48643,-48609,-48575, + -48542,-48508,-48474,-48440,-48406,-48372,-48338,-48305, + -48271,-48237,-48202,-48168,-48134,-48100,-48066,-48032, + -47998,-47963,-47929,-47895,-47860,-47826,-47792,-47757, + -47723,-47688,-47654,-47619,-47585,-47550,-47516,-47481, + -47446,-47412,-47377,-47342,-47307,-47273,-47238,-47203, + -47168,-47133,-47098,-47063,-47028,-46993,-46958,-46923, + -46888,-46853,-46818,-46783,-46747,-46712,-46677,-46642, + -46606,-46571,-46536,-46500,-46465,-46429,-46394,-46358, + -46323,-46287,-46251,-46216,-46180,-46145,-46109,-46073, + -46037,-46002,-45966,-45930,-45894,-45858,-45822,-45786, + -45750,-45714,-45678,-45642,-45606,-45570,-45534,-45498, + -45462,-45425,-45389,-45353,-45316,-45280,-45244,-45207, + -45171,-45135,-45098,-45062,-45025,-44989,-44952,-44915, + -44879,-44842,-44806,-44769,-44732,-44695,-44659,-44622, + -44585,-44548,-44511,-44474,-44437,-44400,-44363,-44326, + -44289,-44252,-44215,-44178,-44141,-44104,-44067,-44029, + -43992,-43955,-43918,-43880,-43843,-43806,-43768,-43731, + -43693,-43656,-43618,-43581,-43543,-43506,-43468,-43430, + -43393,-43355,-43317,-43280,-43242,-43204,-43166,-43128, + -43091,-43053,-43015,-42977,-42939,-42901,-42863,-42825, + -42787,-42749,-42711,-42672,-42634,-42596,-42558,-42520, + -42481,-42443,-42405,-42366,-42328,-42290,-42251,-42213, + -42174,-42136,-42097,-42059,-42020,-41982,-41943,-41904, + -41866,-41827,-41788,-41750,-41711,-41672,-41633,-41595, + -41556,-41517,-41478,-41439,-41400,-41361,-41322,-41283, + -41244,-41205,-41166,-41127,-41087,-41048,-41009,-40970, + -40931,-40891,-40852,-40813,-40773,-40734,-40695,-40655, + -40616,-40576,-40537,-40497,-40458,-40418,-40379,-40339, + -40299,-40260,-40220,-40180,-40141,-40101,-40061,-40021, + -39982,-39942,-39902,-39862,-39822,-39782,-39742,-39702, + -39662,-39622,-39582,-39542,-39502,-39462,-39422,-39382, + -39341,-39301,-39261,-39221,-39180,-39140,-39100,-39059, + -39019,-38979,-38938,-38898,-38857,-38817,-38776,-38736, + -38695,-38655,-38614,-38573,-38533,-38492,-38451,-38411, + -38370,-38329,-38288,-38248,-38207,-38166,-38125,-38084, + -38043,-38002,-37961,-37920,-37879,-37838,-37797,-37756, + -37715,-37674,-37633,-37592,-37550,-37509,-37468,-37427, + -37386,-37344,-37303,-37262,-37220,-37179,-37137,-37096, + -37055,-37013,-36972,-36930,-36889,-36847,-36805,-36764, + -36722,-36681,-36639,-36597,-36556,-36514,-36472,-36430, + -36388,-36347,-36305,-36263,-36221,-36179,-36137,-36095, + -36053,-36011,-35969,-35927,-35885,-35843,-35801,-35759, + -35717,-35675,-35633,-35590,-35548,-35506,-35464,-35421, + -35379,-35337,-35294,-35252,-35210,-35167,-35125,-35082, + -35040,-34997,-34955,-34912,-34870,-34827,-34785,-34742, + -34699,-34657,-34614,-34571,-34529,-34486,-34443,-34400, + -34358,-34315,-34272,-34229,-34186,-34143,-34100,-34057, + -34015,-33972,-33929,-33886,-33843,-33799,-33756,-33713, + -33670,-33627,-33584,-33541,-33498,-33454,-33411,-33368, + -33325,-33281,-33238,-33195,-33151,-33108,-33065,-33021, + -32978,-32934,-32891,-32847,-32804,-32760,-32717,-32673, + -32630,-32586,-32542,-32499,-32455,-32411,-32368,-32324, + -32280,-32236,-32193,-32149,-32105,-32061,-32017,-31974, + -31930,-31886,-31842,-31798,-31754,-31710,-31666,-31622, + -31578,-31534,-31490,-31446,-31402,-31357,-31313,-31269, + -31225,-31181,-31136,-31092,-31048,-31004,-30959,-30915, + -30871,-30826,-30782,-30738,-30693,-30649,-30604,-30560, + -30515,-30471,-30426,-30382,-30337,-30293,-30248,-30204, + -30159,-30114,-30070,-30025,-29980,-29936,-29891,-29846, + -29801,-29757,-29712,-29667,-29622,-29577,-29533,-29488, + -29443,-29398,-29353,-29308,-29263,-29218,-29173,-29128, + -29083,-29038,-28993,-28948,-28903,-28858,-28812,-28767, + -28722,-28677,-28632,-28586,-28541,-28496,-28451,-28405, + -28360,-28315,-28269,-28224,-28179,-28133,-28088,-28042, + -27997,-27952,-27906,-27861,-27815,-27770,-27724,-27678, + -27633,-27587,-27542,-27496,-27450,-27405,-27359,-27313, + -27268,-27222,-27176,-27131,-27085,-27039,-26993,-26947, + -26902,-26856,-26810,-26764,-26718,-26672,-26626,-26580, + -26534,-26488,-26442,-26396,-26350,-26304,-26258,-26212, + -26166,-26120,-26074,-26028,-25982,-25936,-25889,-25843, + -25797,-25751,-25705,-25658,-25612,-25566,-25520,-25473, + -25427,-25381,-25334,-25288,-25241,-25195,-25149,-25102, + -25056,-25009,-24963,-24916,-24870,-24823,-24777,-24730, + -24684,-24637,-24591,-24544,-24497,-24451,-24404,-24357, + -24311,-24264,-24217,-24171,-24124,-24077,-24030,-23984, + -23937,-23890,-23843,-23796,-23750,-23703,-23656,-23609, + -23562,-23515,-23468,-23421,-23374,-23327,-23280,-23233, + -23186,-23139,-23092,-23045,-22998,-22951,-22904,-22857, + -22810,-22763,-22716,-22668,-22621,-22574,-22527,-22480, + -22432,-22385,-22338,-22291,-22243,-22196,-22149,-22102, + -22054,-22007,-21960,-21912,-21865,-21817,-21770,-21723, + -21675,-21628,-21580,-21533,-21485,-21438,-21390,-21343, + -21295,-21248,-21200,-21153,-21105,-21057,-21010,-20962, + -20915,-20867,-20819,-20772,-20724,-20676,-20629,-20581, + -20533,-20485,-20438,-20390,-20342,-20294,-20246,-20199, + -20151,-20103,-20055,-20007,-19959,-19912,-19864,-19816, + -19768,-19720,-19672,-19624,-19576,-19528,-19480,-19432, + -19384,-19336,-19288,-19240,-19192,-19144,-19096,-19048, + -19000,-18951,-18903,-18855,-18807,-18759,-18711,-18663, + -18614,-18566,-18518,-18470,-18421,-18373,-18325,-18277, + -18228,-18180,-18132,-18084,-18035,-17987,-17939,-17890, + -17842,-17793,-17745,-17697,-17648,-17600,-17551,-17503, + -17455,-17406,-17358,-17309,-17261,-17212,-17164,-17115, + -17067,-17018,-16970,-16921,-16872,-16824,-16775,-16727, + -16678,-16629,-16581,-16532,-16484,-16435,-16386,-16338, + -16289,-16240,-16191,-16143,-16094,-16045,-15997,-15948, + -15899,-15850,-15802,-15753,-15704,-15655,-15606,-15557, + -15509,-15460,-15411,-15362,-15313,-15264,-15215,-15167, + -15118,-15069,-15020,-14971,-14922,-14873,-14824,-14775, + -14726,-14677,-14628,-14579,-14530,-14481,-14432,-14383, + -14334,-14285,-14236,-14187,-14138,-14089,-14040,-13990, + -13941,-13892,-13843,-13794,-13745,-13696,-13647,-13597, + -13548,-13499,-13450,-13401,-13351,-13302,-13253,-13204, + -13154,-13105,-13056,-13007,-12957,-12908,-12859,-12810, + -12760,-12711,-12662,-12612,-12563,-12514,-12464,-12415, + -12366,-12316,-12267,-12217,-12168,-12119,-12069,-12020, + -11970,-11921,-11872,-11822,-11773,-11723,-11674,-11624, + -11575,-11525,-11476,-11426,-11377,-11327,-11278,-11228, + -11179,-11129,-11080,-11030,-10981,-10931,-10882,-10832, + -10782,-10733,-10683,-10634,-10584,-10534,-10485,-10435, + -10386,-10336,-10286,-10237,-10187,-10137,-10088,-10038, + -9988,-9939,-9889,-9839,-9790,-9740,-9690,-9640, + -9591,-9541,-9491,-9442,-9392,-9342,-9292,-9243, + -9193,-9143,-9093,-9043,-8994,-8944,-8894,-8844, + -8794,-8745,-8695,-8645,-8595,-8545,-8496,-8446, + -8396,-8346,-8296,-8246,-8196,-8147,-8097,-8047, + -7997,-7947,-7897,-7847,-7797,-7747,-7697,-7648, + -7598,-7548,-7498,-7448,-7398,-7348,-7298,-7248, + -7198,-7148,-7098,-7048,-6998,-6948,-6898,-6848, + -6798,-6748,-6698,-6648,-6598,-6548,-6498,-6448, + -6398,-6348,-6298,-6248,-6198,-6148,-6098,-6048, + -5998,-5948,-5898,-5848,-5798,-5747,-5697,-5647, + -5597,-5547,-5497,-5447,-5397,-5347,-5297,-5247, + -5197,-5146,-5096,-5046,-4996,-4946,-4896,-4846, + -4796,-4745,-4695,-4645,-4595,-4545,-4495,-4445, + -4394,-4344,-4294,-4244,-4194,-4144,-4093,-4043, + -3993,-3943,-3893,-3843,-3792,-3742,-3692,-3642, + -3592,-3541,-3491,-3441,-3391,-3341,-3291,-3240, + -3190,-3140,-3090,-3039,-2989,-2939,-2889,-2839, + -2788,-2738,-2688,-2638,-2588,-2537,-2487,-2437, + -2387,-2336,-2286,-2236,-2186,-2135,-2085,-2035, + -1985,-1934,-1884,-1834,-1784,-1733,-1683,-1633, + -1583,-1532,-1482,-1432,-1382,-1331,-1281,-1231, + -1181,-1130,-1080,-1030,-980,-929,-879,-829, + -779,-728,-678,-628,-578,-527,-477,-427, + -376,-326,-276,-226,-175,-125,-75,-25, + 25,75,125,175,226,276,326,376, + 427,477,527,578,628,678,728,779, + 829,879,929,980,1030,1080,1130,1181, + 1231,1281,1331,1382,1432,1482,1532,1583, + 1633,1683,1733,1784,1834,1884,1934,1985, + 2035,2085,2135,2186,2236,2286,2336,2387, + 2437,2487,2537,2587,2638,2688,2738,2788, + 2839,2889,2939,2989,3039,3090,3140,3190, + 3240,3291,3341,3391,3441,3491,3542,3592, + 3642,3692,3742,3792,3843,3893,3943,3993, + 4043,4093,4144,4194,4244,4294,4344,4394, + 4445,4495,4545,4595,4645,4695,4745,4796, + 4846,4896,4946,4996,5046,5096,5146,5197, + 5247,5297,5347,5397,5447,5497,5547,5597, + 5647,5697,5747,5798,5848,5898,5948,5998, + 6048,6098,6148,6198,6248,6298,6348,6398, + 6448,6498,6548,6598,6648,6698,6748,6798, + 6848,6898,6948,6998,7048,7098,7148,7198, + 7248,7298,7348,7398,7448,7498,7548,7598, + 7648,7697,7747,7797,7847,7897,7947,7997, + 8047,8097,8147,8196,8246,8296,8346,8396, + 8446,8496,8545,8595,8645,8695,8745,8794, + 8844,8894,8944,8994,9043,9093,9143,9193, + 9243,9292,9342,9392,9442,9491,9541,9591, + 9640,9690,9740,9790,9839,9889,9939,9988, + 10038,10088,10137,10187,10237,10286,10336,10386, + 10435,10485,10534,10584,10634,10683,10733,10782, + 10832,10882,10931,10981,11030,11080,11129,11179, + 11228,11278,11327,11377,11426,11476,11525,11575, + 11624,11674,11723,11773,11822,11872,11921,11970, + 12020,12069,12119,12168,12218,12267,12316,12366, + 12415,12464,12514,12563,12612,12662,12711,12760, + 12810,12859,12908,12957,13007,13056,13105,13154, + 13204,13253,13302,13351,13401,13450,13499,13548, + 13597,13647,13696,13745,13794,13843,13892,13941, + 13990,14040,14089,14138,14187,14236,14285,14334, + 14383,14432,14481,14530,14579,14628,14677,14726, + 14775,14824,14873,14922,14971,15020,15069,15118, + 15167,15215,15264,15313,15362,15411,15460,15509, + 15557,15606,15655,15704,15753,15802,15850,15899, + 15948,15997,16045,16094,16143,16191,16240,16289, + 16338,16386,16435,16484,16532,16581,16629,16678, + 16727,16775,16824,16872,16921,16970,17018,17067, + 17115,17164,17212,17261,17309,17358,17406,17455, + 17503,17551,17600,17648,17697,17745,17793,17842, + 17890,17939,17987,18035,18084,18132,18180,18228, + 18277,18325,18373,18421,18470,18518,18566,18614, + 18663,18711,18759,18807,18855,18903,18951,19000, + 19048,19096,19144,19192,19240,19288,19336,19384, + 19432,19480,19528,19576,19624,19672,19720,19768, + 19816,19864,19912,19959,20007,20055,20103,20151, + 20199,20246,20294,20342,20390,20438,20485,20533, + 20581,20629,20676,20724,20772,20819,20867,20915, + 20962,21010,21057,21105,21153,21200,21248,21295, + 21343,21390,21438,21485,21533,21580,21628,21675, + 21723,21770,21817,21865,21912,21960,22007,22054, + 22102,22149,22196,22243,22291,22338,22385,22432, + 22480,22527,22574,22621,22668,22716,22763,22810, + 22857,22904,22951,22998,23045,23092,23139,23186, + 23233,23280,23327,23374,23421,23468,23515,23562, + 23609,23656,23703,23750,23796,23843,23890,23937, + 23984,24030,24077,24124,24171,24217,24264,24311, + 24357,24404,24451,24497,24544,24591,24637,24684, + 24730,24777,24823,24870,24916,24963,25009,25056, + 25102,25149,25195,25241,25288,25334,25381,25427, + 25473,25520,25566,25612,25658,25705,25751,25797, + 25843,25889,25936,25982,26028,26074,26120,26166, + 26212,26258,26304,26350,26396,26442,26488,26534, + 26580,26626,26672,26718,26764,26810,26856,26902, + 26947,26993,27039,27085,27131,27176,27222,27268, + 27313,27359,27405,27450,27496,27542,27587,27633, + 27678,27724,27770,27815,27861,27906,27952,27997, + 28042,28088,28133,28179,28224,28269,28315,28360, + 28405,28451,28496,28541,28586,28632,28677,28722, + 28767,28812,28858,28903,28948,28993,29038,29083, + 29128,29173,29218,29263,29308,29353,29398,29443, + 29488,29533,29577,29622,29667,29712,29757,29801, + 29846,29891,29936,29980,30025,30070,30114,30159, + 30204,30248,30293,30337,30382,30427,30471,30516, + 30560,30604,30649,30693,30738,30782,30826,30871, + 30915,30959,31004,31048,31092,31136,31181,31225, + 31269,31313,31357,31402,31446,31490,31534,31578, + 31622,31666,31710,31754,31798,31842,31886,31930, + 31974,32017,32061,32105,32149,32193,32236,32280, + 32324,32368,32411,32455,32499,32542,32586,32630, + 32673,32717,32760,32804,32847,32891,32934,32978, + 33021,33065,33108,33151,33195,33238,33281,33325, + 33368,33411,33454,33498,33541,33584,33627,33670, + 33713,33756,33799,33843,33886,33929,33972,34015, + 34057,34100,34143,34186,34229,34272,34315,34358, + 34400,34443,34486,34529,34571,34614,34657,34699, + 34742,34785,34827,34870,34912,34955,34997,35040, + 35082,35125,35167,35210,35252,35294,35337,35379, + 35421,35464,35506,35548,35590,35633,35675,35717, + 35759,35801,35843,35885,35927,35969,36011,36053, + 36095,36137,36179,36221,36263,36305,36347,36388, + 36430,36472,36514,36556,36597,36639,36681,36722, + 36764,36805,36847,36889,36930,36972,37013,37055, + 37096,37137,37179,37220,37262,37303,37344,37386, + 37427,37468,37509,37551,37592,37633,37674,37715, + 37756,37797,37838,37879,37920,37961,38002,38043, + 38084,38125,38166,38207,38248,38288,38329,38370, + 38411,38451,38492,38533,38573,38614,38655,38695, + 38736,38776,38817,38857,38898,38938,38979,39019, + 39059,39100,39140,39180,39221,39261,39301,39341, + 39382,39422,39462,39502,39542,39582,39622,39662, + 39702,39742,39782,39822,39862,39902,39942,39982, + 40021,40061,40101,40141,40180,40220,40260,40299, + 40339,40379,40418,40458,40497,40537,40576,40616, + 40655,40695,40734,40773,40813,40852,40891,40931, + 40970,41009,41048,41087,41127,41166,41205,41244, + 41283,41322,41361,41400,41439,41478,41517,41556, + 41595,41633,41672,41711,41750,41788,41827,41866, + 41904,41943,41982,42020,42059,42097,42136,42174, + 42213,42251,42290,42328,42366,42405,42443,42481, + 42520,42558,42596,42634,42672,42711,42749,42787, + 42825,42863,42901,42939,42977,43015,43053,43091, + 43128,43166,43204,43242,43280,43317,43355,43393, + 43430,43468,43506,43543,43581,43618,43656,43693, + 43731,43768,43806,43843,43880,43918,43955,43992, + 44029,44067,44104,44141,44178,44215,44252,44289, + 44326,44363,44400,44437,44474,44511,44548,44585, + 44622,44659,44695,44732,44769,44806,44842,44879, + 44915,44952,44989,45025,45062,45098,45135,45171, + 45207,45244,45280,45316,45353,45389,45425,45462, + 45498,45534,45570,45606,45642,45678,45714,45750, + 45786,45822,45858,45894,45930,45966,46002,46037, + 46073,46109,46145,46180,46216,46252,46287,46323, + 46358,46394,46429,46465,46500,46536,46571,46606, + 46642,46677,46712,46747,46783,46818,46853,46888, + 46923,46958,46993,47028,47063,47098,47133,47168, + 47203,47238,47273,47308,47342,47377,47412,47446, + 47481,47516,47550,47585,47619,47654,47688,47723, + 47757,47792,47826,47861,47895,47929,47963,47998, + 48032,48066,48100,48134,48168,48202,48237,48271, + 48305,48338,48372,48406,48440,48474,48508,48542, + 48575,48609,48643,48676,48710,48744,48777,48811, + 48844,48878,48911,48945,48978,49012,49045,49078, + 49112,49145,49178,49211,49244,49278,49311,49344, + 49377,49410,49443,49476,49509,49542,49575,49608, + 49640,49673,49706,49739,49771,49804,49837,49869, + 49902,49935,49967,50000,50032,50064,50097,50129, + 50162,50194,50226,50259,50291,50323,50355,50387, + 50420,50452,50484,50516,50548,50580,50612,50644, + 50675,50707,50739,50771,50803,50834,50866,50898, + 50929,50961,50993,51024,51056,51087,51119,51150, + 51182,51213,51244,51276,51307,51338,51369,51401, + 51432,51463,51494,51525,51556,51587,51618,51649, + 51680,51711,51742,51773,51803,51834,51865,51896, + 51926,51957,51988,52018,52049,52079,52110,52140, + 52171,52201,52231,52262,52292,52322,52353,52383, + 52413,52443,52473,52503,52534,52564,52594,52624, + 52653,52683,52713,52743,52773,52803,52832,52862, + 52892,52922,52951,52981,53010,53040,53069,53099, + 53128,53158,53187,53216,53246,53275,53304,53334, + 53363,53392,53421,53450,53479,53508,53537,53566, + 53595,53624,53653,53682,53711,53739,53768,53797, + 53826,53854,53883,53912,53940,53969,53997,54026, + 54054,54082,54111,54139,54167,54196,54224,54252, + 54280,54309,54337,54365,54393,54421,54449,54477, + 54505,54533,54560,54588,54616,54644,54672,54699, + 54727,54755,54782,54810,54837,54865,54892,54920, + 54947,54974,55002,55029,55056,55084,55111,55138, + 55165,55192,55219,55246,55274,55300,55327,55354, + 55381,55408,55435,55462,55489,55515,55542,55569, + 55595,55622,55648,55675,55701,55728,55754,55781, + 55807,55833,55860,55886,55912,55938,55965,55991, + 56017,56043,56069,56095,56121,56147,56173,56199, + 56225,56250,56276,56302,56328,56353,56379,56404, + 56430,56456,56481,56507,56532,56557,56583,56608, + 56633,56659,56684,56709,56734,56760,56785,56810, + 56835,56860,56885,56910,56935,56959,56984,57009, + 57034,57059,57083,57108,57133,57157,57182,57206, + 57231,57255,57280,57304,57329,57353,57377,57402, + 57426,57450,57474,57498,57522,57546,57570,57594, + 57618,57642,57666,57690,57714,57738,57762,57785, + 57809,57833,57856,57880,57903,57927,57950,57974, + 57997,58021,58044,58067,58091,58114,58137,58160, + 58183,58207,58230,58253,58276,58299,58322,58345, + 58367,58390,58413,58436,58459,58481,58504,58527, + 58549,58572,58594,58617,58639,58662,58684,58706, + 58729,58751,58773,58795,58818,58840,58862,58884, + 58906,58928,58950,58972,58994,59016,59038,59059, + 59081,59103,59125,59146,59168,59190,59211,59233, + 59254,59276,59297,59318,59340,59361,59382,59404, + 59425,59446,59467,59488,59509,59530,59551,59572, + 59593,59614,59635,59656,59677,59697,59718,59739, + 59759,59780,59801,59821,59842,59862,59883,59903, + 59923,59944,59964,59984,60004,60025,60045,60065, + 60085,60105,60125,60145,60165,60185,60205,60225, + 60244,60264,60284,60304,60323,60343,60363,60382, + 60402,60421,60441,60460,60479,60499,60518,60537, + 60556,60576,60595,60614,60633,60652,60671,60690, + 60709,60728,60747,60766,60785,60803,60822,60841, + 60859,60878,60897,60915,60934,60952,60971,60989, + 61007,61026,61044,61062,61081,61099,61117,61135, + 61153,61171,61189,61207,61225,61243,61261,61279, + 61297,61314,61332,61350,61367,61385,61403,61420, + 61438,61455,61473,61490,61507,61525,61542,61559, + 61577,61594,61611,61628,61645,61662,61679,61696, + 61713,61730,61747,61764,61780,61797,61814,61831, + 61847,61864,61880,61897,61913,61930,61946,61963, + 61979,61995,62012,62028,62044,62060,62076,62092, + 62108,62125,62141,62156,62172,62188,62204,62220, + 62236,62251,62267,62283,62298,62314,62329,62345, + 62360,62376,62391,62407,62422,62437,62453,62468, + 62483,62498,62513,62528,62543,62558,62573,62588, + 62603,62618,62633,62648,62662,62677,62692,62706, + 62721,62735,62750,62764,62779,62793,62808,62822, + 62836,62850,62865,62879,62893,62907,62921,62935, + 62949,62963,62977,62991,63005,63019,63032,63046, + 63060,63074,63087,63101,63114,63128,63141,63155, + 63168,63182,63195,63208,63221,63235,63248,63261, + 63274,63287,63300,63313,63326,63339,63352,63365, + 63378,63390,63403,63416,63429,63441,63454,63466, + 63479,63491,63504,63516,63528,63541,63553,63565, + 63578,63590,63602,63614,63626,63638,63650,63662, + 63674,63686,63698,63709,63721,63733,63745,63756, + 63768,63779,63791,63803,63814,63825,63837,63848, + 63859,63871,63882,63893,63904,63915,63927,63938, + 63949,63960,63971,63981,63992,64003,64014,64025, + 64035,64046,64057,64067,64078,64088,64099,64109, + 64120,64130,64140,64151,64161,64171,64181,64192, + 64202,64212,64222,64232,64242,64252,64261,64271, + 64281,64291,64301,64310,64320,64330,64339,64349, + 64358,64368,64377,64387,64396,64405,64414,64424, + 64433,64442,64451,64460,64469,64478,64487,64496, + 64505,64514,64523,64532,64540,64549,64558,64566, + 64575,64584,64592,64600,64609,64617,64626,64634, + 64642,64651,64659,64667,64675,64683,64691,64699, + 64707,64715,64723,64731,64739,64747,64754,64762, + 64770,64777,64785,64793,64800,64808,64815,64822, + 64830,64837,64844,64852,64859,64866,64873,64880, + 64887,64895,64902,64908,64915,64922,64929,64936, + 64943,64949,64956,64963,64969,64976,64982,64989, + 64995,65002,65008,65015,65021,65027,65033,65040, + 65046,65052,65058,65064,65070,65076,65082,65088, + 65094,65099,65105,65111,65117,65122,65128,65133, + 65139,65144,65150,65155,65161,65166,65171,65177, + 65182,65187,65192,65197,65202,65207,65212,65217, + 65222,65227,65232,65237,65242,65246,65251,65256, + 65260,65265,65270,65274,65279,65283,65287,65292, + 65296,65300,65305,65309,65313,65317,65321,65325, + 65329,65333,65337,65341,65345,65349,65352,65356, + 65360,65363,65367,65371,65374,65378,65381,65385, + 65388,65391,65395,65398,65401,65404,65408,65411, + 65414,65417,65420,65423,65426,65429,65431,65434, + 65437,65440,65442,65445,65448,65450,65453,65455, + 65458,65460,65463,65465,65467,65470,65472,65474, + 65476,65478,65480,65482,65484,65486,65488,65490, + 65492,65494,65496,65497,65499,65501,65502,65504, + 65505,65507,65508,65510,65511,65513,65514,65515, + 65516,65518,65519,65520,65521,65522,65523,65524, + 65525,65526,65527,65527,65528,65529,65530,65530, + 65531,65531,65532,65532,65533,65533,65534,65534, + 65534,65535,65535,65535,65535,65535,65535,65535 +}; + + + +const angle_t tantoangle[2049] = +{ + 0,333772,667544,1001315,1335086,1668857,2002626,2336395, + 2670163,3003929,3337694,3671457,4005219,4338979,4672736,5006492, + 5340245,5673995,6007743,6341488,6675230,7008968,7342704,7676435, + 8010164,8343888,8677609,9011325,9345037,9678744,10012447,10346145, + 10679838,11013526,11347209,11680887,12014558,12348225,12681885,13015539, + 13349187,13682829,14016464,14350092,14683714,15017328,15350936,15684536, + 16018129,16351714,16685291,17018860,17352422,17685974,18019518,18353054, + 18686582,19020100,19353610,19687110,20020600,20354080,20687552,21021014, + 21354466,21687906,22021338,22354758,22688168,23021568,23354956,23688332, + 24021698,24355052,24688396,25021726,25355046,25688352,26021648,26354930, + 26688200,27021456,27354702,27687932,28021150,28354356,28687548,29020724, + 29353888,29687038,30020174,30353296,30686404,31019496,31352574,31685636, + 32018684,32351718,32684734,33017736,33350722,33683692,34016648,34349584, + 34682508,35015412,35348300,35681172,36014028,36346868,36679688,37012492, + 37345276,37678044,38010792,38343524,38676240,39008936,39341612,39674272, + 40006912,40339532,40672132,41004716,41337276,41669820,42002344,42334848, + 42667332,42999796,43332236,43664660,43997060,44329444,44661800,44994140, + 45326456,45658752,45991028,46323280,46655512,46987720,47319908,47652072, + 47984212,48316332,48648428,48980500,49312548,49644576,49976580,50308556, + 50640512,50972444,51304352,51636236,51968096,52299928,52631740,52963524, + 53295284,53627020,53958728,54290412,54622068,54953704,55285308,55616888, + 55948444,56279972,56611472,56942948,57274396,57605816,57937212,58268576, + 58599916,58931228,59262512,59593768,59924992,60256192,60587364,60918508, + 61249620,61580704,61911760,62242788,62573788,62904756,63235692,63566604, + 63897480,64228332,64559148,64889940,65220696,65551424,65882120,66212788, + 66543420,66874024,67204600,67535136,67865648,68196120,68526568,68856984, + 69187360,69517712,69848024,70178304,70508560,70838776,71168960,71499112, + 71829224,72159312,72489360,72819376,73149360,73479304,73809216,74139096, + 74468936,74798744,75128520,75458264,75787968,76117632,76447264,76776864, + 77106424,77435952,77765440,78094888,78424304,78753688,79083032,79412336, + 79741608,80070840,80400032,80729192,81058312,81387392,81716432,82045440, + 82374408,82703336,83032224,83361080,83689896,84018664,84347400,84676096, + 85004760,85333376,85661952,85990488,86318984,86647448,86975864,87304240, + 87632576,87960872,88289128,88617344,88945520,89273648,89601736,89929792, + 90257792,90585760,90913688,91241568,91569408,91897200,92224960,92552672, + 92880336,93207968,93535552,93863088,94190584,94518040,94845448,95172816, + 95500136,95827416,96154648,96481832,96808976,97136080,97463136,97790144, + 98117112,98444032,98770904,99097736,99424520,99751256,100077944,100404592, + 100731192,101057744,101384248,101710712,102037128,102363488,102689808,103016080, + 103342312,103668488,103994616,104320696,104646736,104972720,105298656,105624552, + 105950392,106276184,106601928,106927624,107253272,107578872,107904416,108229920, + 108555368,108880768,109206120,109531416,109856664,110181872,110507016,110832120, + 111157168,111482168,111807112,112132008,112456856,112781648,113106392,113431080, + 113755720,114080312,114404848,114729328,115053760,115378136,115702464,116026744, + 116350960,116675128,116999248,117323312,117647320,117971272,118295176,118619024, + 118942816,119266560,119590248,119913880,120237456,120560984,120884456,121207864, + 121531224,121854528,122177784,122500976,122824112,123147200,123470224,123793200, + 124116120,124438976,124761784,125084528,125407224,125729856,126052432,126374960, + 126697424,127019832,127342184,127664472,127986712,128308888,128631008,128953072, + 129275080,129597024,129918912,130240744,130562520,130884232,131205888,131527480, + 131849016,132170496,132491912,132813272,133134576,133455816,133776992,134098120, + 134419184,134740176,135061120,135382000,135702816,136023584,136344272,136664912, + 136985488,137306016,137626464,137946864,138267184,138587456,138907664,139227808, + 139547904,139867920,140187888,140507776,140827616,141147392,141467104,141786752, + 142106336,142425856,142745312,143064720,143384048,143703312,144022512,144341664, + 144660736,144979744,145298704,145617584,145936400,146255168,146573856,146892480, + 147211040,147529536,147847968,148166336,148484640,148802880,149121056,149439152, + 149757200,150075168,150393072,150710912,151028688,151346400,151664048,151981616, + 152299136,152616576,152933952,153251264,153568496,153885680,154202784,154519824, + 154836784,155153696,155470528,155787296,156104000,156420624,156737200,157053696, + 157370112,157686480,158002768,158318976,158635136,158951216,159267232,159583168, + 159899040,160214848,160530592,160846256,161161840,161477376,161792832,162108208, + 162423520,162738768,163053952,163369040,163684080,163999040,164313936,164628752, + 164943504,165258176,165572784,165887312,166201776,166516160,166830480,167144736, + 167458912,167773008,168087040,168400992,168714880,169028688,169342432,169656096, + 169969696,170283216,170596672,170910032,171223344,171536576,171849728,172162800, + 172475808,172788736,173101600,173414384,173727104,174039728,174352288,174664784, + 174977200,175289536,175601792,175913984,176226096,176538144,176850096,177161984, + 177473792,177785536,178097200,178408784,178720288,179031728,179343088,179654368, + 179965568,180276704,180587744,180898720,181209616,181520448,181831184,182141856, + 182452448,182762960,183073408,183383760,183694048,184004240,184314368,184624416, + 184934400,185244288,185554096,185863840,186173504,186483072,186792576,187102000, + 187411344,187720608,188029808,188338912,188647936,188956896,189265760,189574560, + 189883264,190191904,190500448,190808928,191117312,191425632,191733872,192042016, + 192350096,192658096,192966000,193273840,193581584,193889264,194196848,194504352, + 194811792,195119136,195426400,195733584,196040688,196347712,196654656,196961520, + 197268304,197574992,197881616,198188144,198494592,198800960,199107248,199413456, + 199719584,200025616,200331584,200637456,200943248,201248960,201554576,201860128, + 202165584,202470960,202776256,203081456,203386592,203691632,203996592,204301472, + 204606256,204910976,205215600,205520144,205824592,206128960,206433248,206737456, + 207041584,207345616,207649568,207953424,208257216,208560912,208864512,209168048, + 209471488,209774832,210078112,210381296,210684384,210987408,211290336,211593184, + 211895936,212198608,212501184,212803680,213106096,213408432,213710672,214012816, + 214314880,214616864,214918768,215220576,215522288,215823920,216125472,216426928, + 216728304,217029584,217330784,217631904,217932928,218233856,218534704,218835472, + 219136144,219436720,219737216,220037632,220337952,220638192,220938336,221238384, + 221538352,221838240,222138032,222437728,222737344,223036880,223336304,223635664, + 223934912,224234096,224533168,224832160,225131072,225429872,225728608,226027232, + 226325776,226624240,226922608,227220880,227519056,227817152,228115168,228413088, + 228710912,229008640,229306288,229603840,229901312,230198688,230495968,230793152, + 231090256,231387280,231684192,231981024,232277760,232574416,232870960,233167440, + 233463808,233760096,234056288,234352384,234648384,234944304,235240128,235535872, + 235831504,236127056,236422512,236717888,237013152,237308336,237603424,237898416, + 238193328,238488144,238782864,239077488,239372016,239666464,239960816,240255072, + 240549232,240843312,241137280,241431168,241724960,242018656,242312256,242605776, + 242899200,243192512,243485744,243778896,244071936,244364880,244657744,244950496, + 245243168,245535744,245828224,246120608,246412912,246705104,246997216,247289216, + 247581136,247872960,248164688,248456320,248747856,249039296,249330640,249621904, + 249913056,250204128,250495088,250785968,251076736,251367424,251658016,251948512, + 252238912,252529200,252819408,253109520,253399536,253689456,253979280,254269008, + 254558640,254848176,255137632,255426976,255716224,256005376,256294432,256583392, + 256872256,257161024,257449696,257738272,258026752,258315136,258603424,258891600, + 259179696,259467696,259755600,260043392,260331104,260618704,260906224,261193632, + 261480960,261768176,262055296,262342320,262629248,262916080,263202816,263489456, + 263776000,264062432,264348784,264635024,264921168,265207216,265493168,265779024, + 266064784,266350448,266636000,266921472,267206832,267492096,267777264,268062336, + 268347312,268632192,268916960,269201632,269486208,269770688,270055072,270339360, + 270623552,270907616,271191616,271475488,271759296,272042976,272326560,272610048, + 272893440,273176736,273459936,273743040,274026048,274308928,274591744,274874432, + 275157024,275439520,275721920,276004224,276286432,276568512,276850528,277132416, + 277414240,277695936,277977536,278259040,278540448,278821728,279102944,279384032, + 279665056,279945952,280226752,280507456,280788064,281068544,281348960,281629248, + 281909472,282189568,282469568,282749440,283029248,283308960,283588544,283868032, + 284147424,284426720,284705920,284985024,285264000,285542912,285821696,286100384, + 286378976,286657440,286935840,287214112,287492320,287770400,288048384,288326240, + 288604032,288881696,289159264,289436768,289714112,289991392,290268576,290545632, + 290822592,291099456,291376224,291652896,291929440,292205888,292482272,292758528, + 293034656,293310720,293586656,293862496,294138240,294413888,294689440,294964864, + 295240192,295515424,295790560,296065600,296340512,296615360,296890080,297164704, + 297439200,297713632,297987936,298262144,298536256,298810240,299084160,299357952, + 299631648,299905248,300178720,300452128,300725408,300998592,301271680,301544640, + 301817536,302090304,302362976,302635520,302908000,303180352,303452608,303724768, + 303996800,304268768,304540608,304812320,305083968,305355520,305626944,305898272, + 306169472,306440608,306711616,306982528,307253344,307524064,307794656,308065152, + 308335552,308605856,308876032,309146112,309416096,309685984,309955744,310225408, + 310494976,310764448,311033824,311303072,311572224,311841280,312110208,312379040, + 312647776,312916416,313184960,313453376,313721696,313989920,314258016,314526016, + 314793920,315061728,315329408,315597024,315864512,316131872,316399168,316666336, + 316933408,317200384,317467232,317733984,318000640,318267200,318533632,318799968, + 319066208,319332352,319598368,319864288,320130112,320395808,320661408,320926912, + 321192320,321457632,321722816,321987904,322252864,322517760,322782528,323047200, + 323311744,323576192,323840544,324104800,324368928,324632992,324896928,325160736, + 325424448,325688096,325951584,326215008,326478304,326741504,327004608,327267584, + 327530464,327793248,328055904,328318496,328580960,328843296,329105568,329367712, + 329629760,329891680,330153536,330415264,330676864,330938400,331199808,331461120, + 331722304,331983392,332244384,332505280,332766048,333026752,333287296,333547776, + 333808128,334068384,334328544,334588576,334848512,335108352,335368064,335627712, + 335887200,336146624,336405920,336665120,336924224,337183200,337442112,337700864, + 337959552,338218112,338476576,338734944,338993184,339251328,339509376,339767296, + 340025120,340282848,340540480,340797984,341055392,341312704,341569888,341826976, + 342083968,342340832,342597600,342854272,343110848,343367296,343623648,343879904, + 344136032,344392064,344648000,344903808,345159520,345415136,345670656,345926048, + 346181344,346436512,346691616,346946592,347201440,347456224,347710880,347965440, + 348219872,348474208,348728448,348982592,349236608,349490528,349744320,349998048, + 350251648,350505152,350758528,351011808,351264992,351518048,351771040,352023872, + 352276640,352529280,352781824,353034272,353286592,353538816,353790944,354042944, + 354294880,354546656,354798368,355049952,355301440,355552800,355804096,356055264, + 356306304,356557280,356808128,357058848,357309504,357560032,357810464,358060768, + 358311008,358561088,358811104,359060992,359310784,359560480,359810048,360059520, + 360308896,360558144,360807296,361056352,361305312,361554144,361802880,362051488, + 362300032,362548448,362796736,363044960,363293056,363541024,363788928,364036704, + 364284384,364531936,364779392,365026752,365274016,365521152,365768192,366015136, + 366261952,366508672,366755296,367001792,367248192,367494496,367740704,367986784, + 368232768,368478656,368724416,368970080,369215648,369461088,369706432,369951680, + 370196800,370441824,370686752,370931584,371176288,371420896,371665408,371909792, + 372154080,372398272,372642336,372886304,373130176,373373952,373617600,373861152, + 374104608,374347936,374591168,374834304,375077312,375320224,375563040,375805760, + 376048352,376290848,376533248,376775520,377017696,377259776,377501728,377743584, + 377985344,378227008,378468544,378709984,378951328,379192544,379433664,379674688, + 379915584,380156416,380397088,380637696,380878176,381118560,381358848,381599040, + 381839104,382079072,382318912,382558656,382798304,383037856,383277280,383516640, + 383755840,383994976,384233984,384472896,384711712,384950400,385188992,385427488, + 385665888,385904160,386142336,386380384,386618368,386856224,387093984,387331616, + 387569152,387806592,388043936,388281152,388518272,388755296,388992224,389229024, + 389465728,389702336,389938816,390175200,390411488,390647680,390883744,391119712, + 391355584,391591328,391826976,392062528,392297984,392533312,392768544,393003680, + 393238720,393473632,393708448,393943168,394177760,394412256,394646656,394880960, + 395115136,395349216,395583200,395817088,396050848,396284512,396518080,396751520, + 396984864,397218112,397451264,397684288,397917248,398150080,398382784,398615424, + 398847936,399080320,399312640,399544832,399776928,400008928,400240832,400472608, + 400704288,400935872,401167328,401398720,401629984,401861120,402092192,402323136, + 402553984,402784736,403015360,403245888,403476320,403706656,403936896,404167008, + 404397024,404626944,404856736,405086432,405316032,405545536,405774912,406004224, + 406233408,406462464,406691456,406920320,407149088,407377760,407606336,407834784, + 408063136,408291392,408519520,408747584,408975520,409203360,409431072,409658720, + 409886240,410113664,410340992,410568192,410795296,411022304,411249216,411476032, + 411702720,411929312,412155808,412382176,412608480,412834656,413060736,413286720, + 413512576,413738336,413964000,414189568,414415040,414640384,414865632,415090784, + 415315840,415540800,415765632,415990368,416215008,416439552,416663968,416888288, + 417112512,417336640,417560672,417784576,418008384,418232096,418455712,418679200, + 418902624,419125920,419349120,419572192,419795200,420018080,420240864,420463552, + 420686144,420908608,421130976,421353280,421575424,421797504,422019488,422241344, + 422463104,422684768,422906336,423127776,423349120,423570400,423791520,424012576, + 424233536,424454368,424675104,424895744,425116288,425336736,425557056,425777280, + 425997408,426217440,426437376,426657184,426876928,427096544,427316064,427535488, + 427754784,427974016,428193120,428412128,428631040,428849856,429068544,429287168, + 429505664,429724064,429942368,430160576,430378656,430596672,430814560,431032352, + 431250048,431467616,431685120,431902496,432119808,432336992,432554080,432771040, + 432987936,433204736,433421408,433637984,433854464,434070848,434287104,434503296, + 434719360,434935360,435151232,435367008,435582656,435798240,436013696,436229088, + 436444352,436659520,436874592,437089568,437304416,437519200,437733856,437948416, + 438162880,438377248,438591520,438805696,439019744,439233728,439447584,439661344, + 439875008,440088576,440302048,440515392,440728672,440941824,441154880,441367872, + 441580736,441793472,442006144,442218720,442431168,442643552,442855808,443067968, + 443280032,443492000,443703872,443915648,444127296,444338880,444550336,444761696, + 444972992,445184160,445395232,445606176,445817056,446027840,446238496,446449088, + 446659552,446869920,447080192,447290400,447500448,447710432,447920320,448130112, + 448339776,448549376,448758848,448968224,449177536,449386720,449595808,449804800, + 450013664,450222464,450431168,450639776,450848256,451056640,451264960,451473152, + 451681248,451889248,452097152,452304960,452512672,452720288,452927808,453135232, + 453342528,453549760,453756864,453963904,454170816,454377632,454584384,454791008, + 454997536,455203968,455410304,455616544,455822688,456028704,456234656,456440512, + 456646240,456851904,457057472,457262912,457468256,457673536,457878688,458083744, + 458288736,458493600,458698368,458903040,459107616,459312096,459516480,459720768, + 459924960,460129056,460333056,460536960,460740736,460944448,461148064,461351584, + 461554976,461758304,461961536,462164640,462367680,462570592,462773440,462976160, + 463178816,463381344,463583776,463786144,463988384,464190560,464392608,464594560, + 464796448,464998208,465199872,465401472,465602944,465804320,466005600,466206816, + 466407904,466608896,466809824,467010624,467211328,467411936,467612480,467812896, + 468013216,468213440,468413600,468613632,468813568,469013440,469213184,469412832, + 469612416,469811872,470011232,470210528,470409696,470608800,470807776,471006688, + 471205472,471404192,471602784,471801312,471999712,472198048,472396288,472594400, + 472792448,472990400,473188256,473385984,473583648,473781216,473978688,474176064, + 474373344,474570528,474767616,474964608,475161504,475358336,475555040,475751648, + 475948192,476144608,476340928,476537184,476733312,476929376,477125344,477321184, + 477516960,477712640,477908224,478103712,478299104,478494400,478689600,478884704, + 479079744,479274656,479469504,479664224,479858880,480053408,480247872,480442240, + 480636512,480830656,481024736,481218752,481412640,481606432,481800128,481993760, + 482187264,482380704,482574016,482767264,482960416,483153472,483346432,483539296, + 483732064,483924768,484117344,484309856,484502240,484694560,484886784,485078912, + 485270944,485462880,485654720,485846464,486038144,486229696,486421184,486612576, + 486803840,486995040,487186176,487377184,487568096,487758912,487949664,488140320, + 488330880,488521312,488711712,488901984,489092160,489282240,489472256,489662176, + 489851968,490041696,490231328,490420896,490610336,490799712,490988960,491178144, + 491367232,491556224,491745120,491933920,492122656,492311264,492499808,492688256, + 492876608,493064864,493253056,493441120,493629120,493817024,494004832,494192544, + 494380160,494567712,494755136,494942496,495129760,495316928,495504000,495691008, + 495877888,496064704,496251424,496438048,496624608,496811040,496997408,497183680, + 497369856,497555936,497741920,497927840,498113632,498299360,498484992,498670560, + 498856000,499041376,499226656,499411840,499596928,499781920,499966848,500151680, + 500336416,500521056,500705600,500890080,501074464,501258752,501442944,501627040, + 501811072,501995008,502178848,502362592,502546240,502729824,502913312,503096704, + 503280000,503463232,503646368,503829408,504012352,504195200,504377984,504560672, + 504743264,504925760,505108192,505290496,505472736,505654912,505836960,506018944, + 506200832,506382624,506564320,506745952,506927488,507108928,507290272,507471552, + 507652736,507833824,508014816,508195744,508376576,508557312,508737952,508918528, + 509099008,509279392,509459680,509639904,509820032,510000064,510180000,510359872, + 510539648,510719328,510898944,511078432,511257856,511437216,511616448,511795616, + 511974688,512153664,512332576,512511392,512690112,512868768,513047296,513225792, + 513404160,513582432,513760640,513938784,514116800,514294752,514472608,514650368, + 514828064,515005664,515183168,515360608,515537952,515715200,515892352,516069440, + 516246432,516423328,516600160,516776896,516953536,517130112,517306592,517482976, + 517659264,517835488,518011616,518187680,518363648,518539520,518715296,518891008, + 519066624,519242144,519417600,519592960,519768256,519943424,520118528,520293568, + 520468480,520643328,520818112,520992800,521167392,521341888,521516320,521690656, + 521864896,522039072,522213152,522387168,522561056,522734912,522908640,523082304, + 523255872,523429376,523602784,523776096,523949312,524122464,524295552,524468512, + 524641440,524814240,524986976,525159616,525332192,525504640,525677056,525849344, + 526021568,526193728,526365792,526537760,526709632,526881440,527053152,527224800, + 527396352,527567840,527739200,527910528,528081728,528252864,528423936,528594880, + 528765760,528936576,529107296,529277920,529448480,529618944,529789344,529959648, + 530129856,530300000,530470048,530640000,530809888,530979712,531149440,531319072, + 531488608,531658080,531827488,531996800,532166016,532335168,532504224,532673184, + 532842080,533010912,533179616,533348288,533516832,533685312,533853728,534022048, + 534190272,534358432,534526496,534694496,534862400,535030240,535197984,535365632, + 535533216,535700704,535868128,536035456,536202720,536369888,536536992,536704000, + 536870912 +}; + + diff --git a/doomclassic/doom/tables.h b/doomclassic/doom/tables.h new file mode 100644 index 00000000..e6361a31 --- /dev/null +++ b/doomclassic/doom/tables.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TABLES__ +#define __TABLES__ + + + +#ifdef LINUX +#include +#else +const float PI = 3.141592657f; +#endif + + +#include "m_fixed.h" + +#define FINEANGLES 8192 +#define FINEMASK (FINEANGLES-1) + + +// 0x100000000 to 0x2000 +#define ANGLETOFINESHIFT 19 + +// Effective size is 10240. +const extern fixed_t finesine[5*FINEANGLES/4]; + +// Re-use data, is just PI/2 pahse shift. +const extern fixed_t* finecosine; + + +// Effective size is 4096. +const extern fixed_t finetangent[FINEANGLES/2]; + +// Binary Angle Measument, BAM. +#define ANG45 0x20000000u +#define ANG90 0x40000000u +#define ANG180 0x80000000u +#define ANG270 0xc0000000u + + +#define SLOPERANGE 2048 +#define SLOPEBITS 11 +#define DBITS (FRACBITS-SLOPEBITS) + +typedef unsigned angle_t; + + +// Effective size is 2049; +// The +1 size is to handle the case when x==y +// without additional checking. +const extern angle_t tantoangle[SLOPERANGE+1]; + + +// Utility function, +// called by R_PointToAngle. +int +SlopeDiv +( unsigned num, + unsigned den); + + +#endif + diff --git a/doomclassic/doom/typedefs.h b/doomclassic/doom/typedefs.h new file mode 100644 index 00000000..4ac4404f --- /dev/null +++ b/doomclassic/doom/typedefs.h @@ -0,0 +1,2513 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// i_sound.typedefs begin // +typedef int tSigSet; +// i_sound.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // +// mus2midi.typedefs begin // +typedef unsigned char byte; +typedef unsigned short WORD; +// mus2midi.typedefs end // +// m_menu.typedefs begin // +typedef void (*messageRoutine_t)(int response); +// m_menu.typedefs end // diff --git a/doomclassic/doom/v_video.cpp b/doomclassic/doom/v_video.cpp new file mode 100644 index 00000000..4624237f --- /dev/null +++ b/doomclassic/doom/v_video.cpp @@ -0,0 +1,519 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include "i_system.h" +#include "r_local.h" + +#include "doomdef.h" +#include "doomdata.h" + +#include "m_bbox.h" +#include "m_swap.h" + +#include "v_video.h" + + +// Each screen is [ORIGINAL_WIDTH*ORIGINALHEIGHT]; + + + + +// Now where did these came from? +const byte gammatable[5][256] = +{ + {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, + 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32, + 33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64, + 65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80, + 81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96, + 97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112, + 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255}, + + {2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23,24,25,26,27,29,30,31, + 32,33,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,54,55, + 56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77, + 78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98, + 99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114, + 115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,129, + 130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145, + 146,147,148,148,149,150,151,152,153,154,155,156,157,158,159,160, + 161,162,163,163,164,165,166,167,168,169,170,171,172,173,174,175, + 175,176,177,178,179,180,181,182,183,184,185,186,186,187,188,189, + 190,191,192,193,194,195,196,196,197,198,199,200,201,202,203,204, + 205,205,206,207,208,209,210,211,212,213,214,214,215,216,217,218, + 219,220,221,222,222,223,224,225,226,227,228,229,230,230,231,232, + 233,234,235,236,237,237,238,239,240,241,242,243,244,245,245,246, + 247,248,249,250,251,252,252,253,254,255}, + + {4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32,33,35,36,38,39,40,42, + 43,45,46,47,48,50,51,52,54,55,56,57,59,60,61,62,63,65,66,67,68,69, + 70,72,73,74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90,91,92,93, + 94,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112, + 113,114,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128, + 129,130,131,132,133,133,134,135,136,137,138,139,140,141,142,143,144, + 144,145,146,147,148,149,150,151,152,153,153,154,155,156,157,158,159, + 160,160,161,162,163,164,165,166,166,167,168,169,170,171,172,172,173, + 174,175,176,177,178,178,179,180,181,182,183,183,184,185,186,187,188, + 188,189,190,191,192,193,193,194,195,196,197,197,198,199,200,201,201, + 202,203,204,205,206,206,207,208,209,210,210,211,212,213,213,214,215, + 216,217,217,218,219,220,221,221,222,223,224,224,225,226,227,228,228, + 229,230,231,231,232,233,234,235,235,236,237,238,238,239,240,241,241, + 242,243,244,244,245,246,247,247,248,249,250,251,251,252,253,254,254, + 255}, + + {8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45,47,49,50,52,53,55, + 57,58,60,61,63,64,65,67,68,70,71,72,74,75,76,77,79,80,81,82,84,85, + 86,87,88,90,91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124, + 125,126,127,128,129,130,131,132,133,134,135,135,136,137,138,139,140, + 141,142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155, + 155,156,157,158,159,160,160,161,162,163,164,165,165,166,167,168,169, + 169,170,171,172,173,173,174,175,176,176,177,178,179,180,180,181,182, + 183,183,184,185,186,186,187,188,189,189,190,191,192,192,193,194,195, + 195,196,197,197,198,199,200,200,201,202,202,203,204,205,205,206,207, + 207,208,209,210,210,211,212,212,213,214,214,215,216,216,217,218,219, + 219,220,221,221,222,223,223,224,225,225,226,227,227,228,229,229,230, + 231,231,232,233,233,234,235,235,236,237,237,238,238,239,240,240,241, + 242,242,243,244,244,245,246,246,247,247,248,249,249,250,251,251,252, + 253,253,254,254,255}, + + {16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64,66,68,69,71,73,75,76, + 78,80,81,83,84,86,87,89,90,92,93,94,96,97,98,100,101,102,103,105,106, + 107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124, + 125,126,128,128,129,130,131,132,133,134,135,136,137,138,139,140,141, + 142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,155, + 156,157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169, + 169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180,181, + 182,182,183,184,184,185,186,187,187,188,189,189,190,191,191,192,193, + 193,194,195,195,196,196,197,198,198,199,200,200,201,202,202,203,203, + 204,205,205,206,207,207,208,208,209,210,210,211,211,212,213,213,214, + 214,215,216,216,217,217,218,219,219,220,220,221,221,222,223,223,224, + 224,225,225,226,227,227,228,228,229,229,230,230,231,232,232,233,233, + 234,234,235,235,236,236,237,237,238,239,239,240,240,241,241,242,242, + 243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251, + 251,252,252,253,254,254,255,255} +}; + + + + +// +// V_MarkRect +// +void +V_MarkRect +( int x, + int y, + int width, + int height ) +{ + M_AddToBox (::g->dirtybox, x, y); + M_AddToBox (::g->dirtybox, x+width-1, y+height-1); +} + + +// +// V_CopyRect +// +void +V_CopyRect +( int srcx, + int srcy, + int srcscrn, + int width, + int height, + int destx, + int desty, + int destscrn ) +{ + byte* src; + byte* dest; + +#ifdef RANGECHECK + if (srcx<0 + ||srcx+width >ORIGINAL_WIDTH + || srcy<0 + || srcy+height>ORIGINAL_HEIGHT + ||destx<0||destx+width >ORIGINAL_WIDTH + || desty<0 + || desty+height>ORIGINAL_HEIGHT + || (unsigned)srcscrn>4 + || (unsigned)destscrn>4) + { + I_Error ("Bad V_CopyRect"); + } +#endif + V_MarkRect (destx, desty, width, height); + + // SMF - rewritten for scaling + srcx *= GLOBAL_IMAGE_SCALER; + srcy *= GLOBAL_IMAGE_SCALER; + destx *= GLOBAL_IMAGE_SCALER; + desty *= GLOBAL_IMAGE_SCALER; + width *= GLOBAL_IMAGE_SCALER; + height *= GLOBAL_IMAGE_SCALER; + + src = ::g->screens[srcscrn] + srcy * SCREENWIDTH + srcx; + dest = ::g->screens[destscrn] + desty * SCREENWIDTH + destx; + + for ( ; height>0 ; height--) { + memcpy(dest, src, width); + src += SCREENWIDTH; + dest += SCREENWIDTH; + } +} + + +// +// V_DrawPatch +// Masks a column based masked pic to the screen. +// +void +V_DrawPatch +( int x, + int y, + int scrn, + patch_t* patch ) +{ + + int count; + int col; + postColumn_t* column; + byte* source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); +#ifdef RANGECHECK + if (x<0 + ||x+SHORT(patch->width) >ORIGINAL_WIDTH + || y<0 + || y+SHORT(patch->height)>ORIGINAL_HEIGHT + || (unsigned)scrn>4) + { + I_PrintfE("Patch at %d,%d exceeds LFB\n", x,y ); + // No I_Error abort - what is up with TNT.WAD? + I_PrintfE("V_DrawPatch: bad patch (ignored)\n"); + return; + } +#endif + + if (!scrn) + V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + int destx = x; + int desty = y; + + w = SHORT(patch->width); + + // SMF - rewritten for scaling + for ( ; col < w ; x++, col++ ) { + column = (postColumn_t *)((byte *)patch + LONG(patch->columnofs[col])); + + destx = x; + + // step through the posts in a column + while (column->topdelta != 0xff ) { + source = (byte *)column + 3; + desty = y + column->topdelta; + count = column->length; + + while (count--) { + int scaledx, scaledy; + scaledx = destx * GLOBAL_IMAGE_SCALER; + scaledy = desty * GLOBAL_IMAGE_SCALER; + byte src = *source++; + + for ( int i = 0; i < GLOBAL_IMAGE_SCALER; i++ ) { + for ( int j = 0; j < GLOBAL_IMAGE_SCALER; j++ ) { + ::g->screens[scrn][( scaledx + j ) + ( scaledy + i ) * SCREENWIDTH] = src; + } + } + + desty++; + } + + column = (postColumn_t *)( (byte *)column + column->length + 4 ); + } + } +} + +// +// V_DrawPatchFlipped +// Masks a column based masked pic to the screen. +// Flips horizontally, e.g. to mirror face. +// +void +V_DrawPatchFlipped +( int x, + int y, + int scrn, + patch_t* patch ) +{ + + int count; + int col; + postColumn_t* column; + byte* source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); +#ifdef RANGECHECK + if (x<0 + ||x+SHORT(patch->width) >ORIGINAL_WIDTH + || y<0 + || y+SHORT(patch->height)>ORIGINAL_HEIGHT + || (unsigned)scrn>4) + { + I_PrintfE("Patch origin %d,%d exceeds LFB\n", x,y ); + I_Error ("Bad V_DrawPatch in V_DrawPatchFlipped"); + } +#endif + + if (!scrn) + V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + + col = 0; + int destx = x; + int desty = y; + + w = SHORT(patch->width); + + for ( ; colcolumnofs[w-1-col])); + + destx = x; + + // step through the posts in a column + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + desty = y + column->topdelta; + count = column->length; + + while (count--) + { + int scaledx, scaledy; + scaledx = destx * GLOBAL_IMAGE_SCALER; + scaledy = desty * GLOBAL_IMAGE_SCALER; + byte src = *source++; + + for ( int i = 0; i < GLOBAL_IMAGE_SCALER; i++ ) { + for ( int j = 0; j < GLOBAL_IMAGE_SCALER; j++ ) { + ::g->screens[scrn][( scaledx + j ) + ( scaledy + i ) * SCREENWIDTH] = src; + } + } + + desty++; + } + column = (postColumn_t *)( (byte *)column + column->length + 4 ); + } + } +} + + + +// +// V_DrawPatchDirect +// Draws directly to the screen on the pc. +// +void +V_DrawPatchDirect +( int x, + int y, + int scrn, + patch_t* patch ) +{ + V_DrawPatch (x,y,scrn, patch); + + /* + int count; + int col; + postColumn_t* column; + byte* desttop; + byte* dest; + byte* source; + int w; + + y -= SHORT(patch->topoffset); + x -= SHORT(patch->leftoffset); + +#ifdef RANGECHECK + if (x<0 + ||x+SHORT(patch->width) >ORIGINAL_WIDTH + || y<0 + || y+SHORT(patch->height)>ORIGINAL_HEIGHT + || (unsigned)scrn>4) + { + I_Error ("Bad V_DrawPatchDirect"); + } +#endif + + // V_MarkRect (x, y, SHORT(patch->width), SHORT(patch->height)); + desttop = destscreen + y*ORIGINAL_WIDTH/4 + (x>>2); + + w = SHORT(patch->width); + for ( col = 0 ; colcolumnofs[col])); + + // step through the posts in a column + + while (column->topdelta != 0xff ) + { + source = (byte *)column + 3; + dest = desttop + column->topdelta*ORIGINAL_WIDTH/4; + count = column->length; + + while (count--) + { + *dest = *source++; + dest += ORIGINAL_WIDTH/4; + } + column = (postColumn_t *)( (byte *)column + column->length + + 4 ); + } + if ( ((++x)&3) == 0 ) + desttop++; // go to next byte, not next plane + }*/ +} + + + +// +// V_DrawBlock +// Draw a linear block of pixels into the view buffer. +// +void +V_DrawBlock +( int x, + int y, + int scrn, + int width, + int height, + byte* src ) +{ + byte* dest; + +#ifdef RANGECHECK + if (x<0 + ||x+width >SCREENWIDTH + || y<0 + || y+height>SCREENHEIGHT + || (unsigned)scrn>4 ) + { + I_Error ("Bad V_DrawBlock"); + } +#endif + + V_MarkRect (x, y, width, height); + + dest = ::g->screens[scrn] + y*SCREENWIDTH+x; + + while (height--) + { + memcpy(dest, src, width); + src += width; + dest += SCREENWIDTH; + } +} + + + +// +// V_GetBlock +// Gets a linear block of pixels from the view buffer. +// +void +V_GetBlock +( int x, + int y, + int scrn, + int width, + int height, + byte* dest ) +{ + byte* src; + +#ifdef RANGECHECK + if (x<0 + ||x+width >SCREENWIDTH + || y<0 + || y+height>SCREENHEIGHT + || (unsigned)scrn>4 ) + { + I_Error ("Bad V_DrawBlock"); + } +#endif + + src = ::g->screens[scrn] + y*SCREENWIDTH+x; + + while (height--) + { + memcpy(dest, src, width); + src += SCREENWIDTH; + dest += width; + } +} + + + + +// +// V_Init +// +void V_Init (void) +{ + int i; + byte* base; + + // stick these in low dos memory on PCs + + base = (byte*)DoomLib::Z_Malloc(SCREENWIDTH*SCREENHEIGHT*4, PU_STATIC, 0); + + for (i=0 ; i<4 ; i++) + ::g->screens[i] = base + i*SCREENWIDTH*SCREENHEIGHT; +} + diff --git a/doomclassic/doom/v_video.h b/doomclassic/doom/v_video.h new file mode 100644 index 00000000..826108a5 --- /dev/null +++ b/doomclassic/doom/v_video.h @@ -0,0 +1,119 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __V_VIDEO__ +#define __V_VIDEO__ + +#include "doomtype.h" + +#include "doomdef.h" + +// Needed because we are refering to patches. +#include "r_data.h" + +// +// VIDEO +// + +#define CENTERY (SCREENHEIGHT/2) + + +// Screen 0 is the screen updated by I_Update screen. +// Screen 1 is an extra buffer. + + + +extern byte* screens[5]; + +extern int dirtybox[4]; + +const extern byte gammatable[5][256]; +extern int usegamma; + + + +// Allocates buffer screens, call before R_Init. +void V_Init (void); + + +void +V_CopyRect +( int srcx, + int srcy, + int srcscrn, + int width, + int height, + int destx, + int desty, + int destscrn ); + +void +V_DrawPatch +( int x, + int y, + int scrn, + patch_t* patch); + +void +V_DrawPatchDirect +( int x, + int y, + int scrn, + patch_t* patch ); + + +// Draw a linear block of pixels into the view buffer. +void +V_DrawBlock +( int x, + int y, + int scrn, + int width, + int height, + byte* src ); + +// Reads a linear block of pixels into the view buffer. +void +V_GetBlock +( int x, + int y, + int scrn, + int width, + int height, + byte* dest ); + + +void +V_MarkRect +( int x, + int y, + int width, + int height ); + +#endif + diff --git a/doomclassic/doom/vars.h b/doomclassic/doom/vars.h new file mode 100644 index 00000000..7e1b4427 --- /dev/null +++ b/doomclassic/doom/vars.h @@ -0,0 +1,909 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +// the all-important zone // +memzone_t* mainzone; + +idFile * wadFileHandles[MAXWADFILES]; +int numWadFiles; + +// am_map.vars begin // +int cheating ; +int grid ; +int leveljuststarted ; +qboolean automapactive ; +int finit_width ; +int finit_height ; +int f_x; +int f_y; +int f_w; +int f_h; +int lightlev; // used for funky strobing effect +byte* fb; // pseudo-frame buffer +int amclock; +mpoint_t m_paninc; // how far the window pans each tic (map coords) +fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords) +fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords) +fixed_t m_x, m_y; // LL x,y where the window is on the map (map coords) +fixed_t m_x2, m_y2; // UR x,y where the window is on the map (map coords) +fixed_t m_w; +fixed_t m_h; +fixed_t min_x; +fixed_t min_y; +fixed_t max_x; +fixed_t max_y; +fixed_t max_w; // max_x-min_x, +fixed_t max_h; // max_y-min_y +fixed_t min_w; +fixed_t min_h; +fixed_t min_scale_mtof; // used to tell when to stop zooming out +fixed_t max_scale_mtof; // used to tell when to stop zooming in +fixed_t old_m_w, old_m_h; +fixed_t old_m_x, old_m_y; +mpoint_t f_oldloc; +fixed_t scale_mtof ; +fixed_t scale_ftom; +player_t *amap_plr; // the player represented by an arrow +patch_t *marknums[10]; // numbers used for marking by the automap +mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are +int markpointnum ; +int followplayer ; +qboolean stopped ; +int lastlevel ; +int lastepisode ; +int cheatstate; +int bigstate; +char buffer[20]; +int nexttic ; +int litelevelscnt ; +// am_map.vars end // +// doomlib.vars begin // +fixed_t realoffset; +fixed_t viewxoffset; +fixed_t viewyoffset; +// doomlib.vars end // +// doomstat.vars begin // +GameMode_t gamemode ; +int gamemission ; +Language_t language ; +qboolean modifiedgame; +// doomstat.vars end // +// d_main.vars begin // +qboolean devparm; // started game with -devparm +qboolean nomonsters; // checkparm of -nomonsters +qboolean respawnparm; // checkparm of -respawn +qboolean fastparm; // checkparm of -fast +qboolean drone; +qboolean singletics ; +skill_t startskill; +int startepisode; +int startmap; +qboolean autostart; +FILE* debugfile; +qboolean advancedemo; +char wadfile[1024]; // primary wad file +char mapdir[1024]; // directory of development maps +char basedefault[1024]; // default file +event_t events[MAXEVENTS]; +int eventhead; +int eventtail; +gamestate_t wipegamestate ; + qboolean viewactivestate ; + qboolean menuactivestate ; + qboolean inhelpscreensstate ; + qboolean fullscreen ; + int borderdrawcount; +qboolean wipe ; +int wipestart; +qboolean wipedone; +int demosequence; +int pagetic; +char *pagename; +char title[128]; +// d_main.vars end // +// d_net.vars begin // +doomcom_t doomcom; +doomdata_t* netbuffer; // points inside doomcom +ticcmd_t localcmds[BACKUPTICS]; +ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; +int nettics[MAXNETNODES]; +qboolean nodeingame[MAXNETNODES]; // set false as nodes leave game +qboolean remoteresend[MAXNETNODES]; // set when local needs tics +int resendto[MAXNETNODES]; // set when remote needs tics +int resendcount[MAXNETNODES]; +int nodeforplayer[MAXPLAYERS]; +int maketic; +int lastnettic; +int skiptics; +int ticdup; +int maxsend; // BACKUPTICS/(2*ticdup)-1 +qboolean reboundpacket; +doomdata_t reboundstore; +char exitmsg[80]; +int gametime; +qboolean gotinfo[MAXNETNODES]; +int frametics[4]; +int frameon; +int frameskip[4]; +int oldnettics; +int oldtrt_entertics; +int trt_phase ; +int trt_lowtic; +int trt_entertic; +int trt_realtics; +int trt_availabletics; +int trt_counts; +int trt_numplaying; +// d_net.vars end // +// f_finale.vars begin // +int finalestage; +int finalecount; +int castnum; +int casttics; +state_t* caststate; +qboolean castdeath; +int castframes; +int castonmelee; +int caststartmenu; +qboolean castattacking; +int laststage; +// f_finale.vars end // +// f_wipe.vars begin // +qboolean go ; +byte* wipe_scr_start; +byte* wipe_scr_end; +byte* wipe_scr; +void *g_tempPointer; +int* wipe_y; +// f_wipe.vars end // +// g_game.vars begin // +gameaction_t gameaction; +gamestate_t gamestate; +gamestate_t oldgamestate; +skill_t gameskill; +qboolean respawnmonsters; +int gameepisode; +int gamemap; +qboolean paused; +qboolean sendpause; // send a pause event next tic +qboolean sendsave; // send a save event next tic +qboolean usergame; // ok to save / end game +qboolean timingdemo; // if true, exit with report on completion +qboolean nodrawers; // for comparative timing purposes +qboolean noblit; // for comparative timing purposes +int starttime; // for comparative timing purposes +qboolean viewactive; +qboolean deathmatch; // only if started as net death +qboolean netgame; // only true if packets are broadcast +qboolean playeringame[MAXPLAYERS]; +player_t players[MAXPLAYERS]; +int consoleplayer; // player taking events and displaying +int displayplayer; // view being displayed +int gametic; +int levelstarttic; // gametic at level start +int totalkills, totalitems, totalsecret; // for intermission +char demoname[32]; +qboolean demoplayback; +qboolean demorecording; +qboolean netdemo; +byte* demobuffer; +byte* demo_p; +byte* demoend; +qboolean singledemo; // quit after playing a demo from cmdline +qboolean precache ; +wbstartstruct_t wminfo; // parms for world map / intermission +short consistancy[MAXPLAYERS][BACKUPTICS]; +byte* savebuffer; +int savebufferSize; +int key_right; +int key_left; +int key_up; +int key_down; +int key_strafeleft; +int key_straferight; +int key_fire; +int key_use; +int key_strafe; +int key_speed; +int mousebfire; +int mousebstrafe; +int mousebforward; +int joybfire; +int joybstrafe; +int joybuse; +int joybspeed; +fixed_t forwardmove[2]; +fixed_t sidemove[2]; +fixed_t angleturn[3]; +qboolean gamekeydown[NUMKEYS]; +int turnheld; // for accelerative turning +qboolean mousearray[4]; +qboolean* mousebuttons ; +int mousex; +int mousey; +int dclicktime; +int dclickstate; +int dclicks; +int dclicktime2; +int dclickstate2; +int dclicks2; +int joyxmove; +int joyymove; +qboolean joyarray[5]; +qboolean* joybuttons ; +int savegameslot; +char savedescription[32]; +mobj_t* bodyque[BODYQUESIZE]; +int bodyqueslot; +char turbomessage[80]; +qboolean secretexit; +char savename[256]; +skill_t d_skill; +int d_episode; +int d_map; +int d_mission; +char* defdemoname; +// g_game.vars end // +// hu_lib.vars begin // +qboolean lastautomapactive ; +// hu_lib.vars end // +// hu_stuff.vars begin // +char chat_char; // remove later. +player_t* plr; +patch_t* hu_font[HU_FONTSIZE]; +hu_textline_t w_title; +qboolean chat_on; +hu_itext_t w_chat; +qboolean always_off ; +char chat_dest[MAXPLAYERS]; +hu_itext_t w_inputbuffer[MAXPLAYERS]; +qboolean message_on; +qboolean message_dontfuckwithme; +qboolean message_nottobefuckedwith; +hu_stext_t w_message; +int message_counter; +qboolean headsupactive ; +char chatchars[QUEUESIZE]; +int head ; +int tail ; +char lastmessage[HU_MAXLINELENGTH+1]; +qboolean shiftdown ; +qboolean altdown ; +int num_nobrainers ; +// hu_stuff.vars end // +// i_input.vars begin // +InputEvent mouseEvents[2]; +InputEvent joyEvents[18]; +// i_input.vars end // +// i_net_xbox.vars begin // +int sendsocket; +int insocket; +struct sockaddr_in sendaddress[MAXNETNODES]; +// i_net_xbox.vars end // +// i_system.vars begin // +int mb_used ; +ticcmd_t emptycmd; +int current_time ; +// i_system.vars end // +// i_video_xbox.vars begin // + +unsigned int XColorMap[256]; +unsigned int *ImageBuff; +unsigned int *ImageBuffs[2]; + +// i_video_xbox.vars end // +// m_argv.vars begin // +int myargc; +char** myargv; +// m_argv.vars end // +// m_cheat.vars begin // +int firsttime ; +unsigned char cheat_xlate_table[256]; +unsigned char cheatbuffer[256]; +int usedcheatbuffer ; +// m_cheat.vars end // +// m_menu.vars begin // +int mouseSensitivity; // has default +int showMessages; +int detailLevel; +int screenblocks; // has default +int screenSize; +int quickSaveSlot; +int messageToPrint; +char* messageString; +int messx; +int messy; +int messageLastMenuActive; +qboolean messageNeedsInput; +messageRoutine_t messageRoutine; +int saveStringEnter; +int saveSlot; // which slot to save in +int saveCharIndex; // which char we're editing +char saveOldString[SAVESTRINGSIZE]; +qboolean inhelpscreens; +qboolean menuactive; +char savegamestrings[10][SAVESTRINGSIZE]; +char savegamepaths[10][MAX_PATH]; +char endstring[160]; +short itemOn; // menu item skull is on +short skullAnimCounter; // skull animation counter +short whichSkull; // which skull to draw +menu_t* currentMenu; +menuitem_t MainMenu[5]; +menu_t QuitDef; +menuitem_t QuitMenu[3]; +menu_t MainDef; +menuitem_t EpisodeMenu[4]; +menu_t EpiDef; +menuitem_t ExpansionMenu[2]; +menu_t ExpDef; +menuitem_t NewGameMenu[5]; +menu_t NewDef; +menuitem_t OptionsMenu[8]; +menu_t OptionsDef; +menuitem_t SoundMenu[4]; +menu_t SoundDef; +menuitem_t LoadMenu[6]; +menu_t LoadDef; +menuitem_t LoadExpMenu[2]; +menu_t LoadExpDef; +menuitem_t SaveMenu[6]; +menu_t SaveDef; +char tempstring[80]; +int epi; +int exp; +int quitsounds[8]; +int quitsounds2[8]; + int joywait ; + int mousewait ; + int mmenu_mousey ; + int lasty ; + int mmenu_mousex ; + int lastx ; +short md_x; +short md_y; +// m_menu.vars end // +// m_misc.vars begin // +const char * g_pszSaveFile ; +const char * g_pszImagePath ; +const char * g_pszImageMeta ; +int usemouse; +int usejoystick; +char* mousetype; +char* mousedev; +default_t defaults[35]; +int numdefaults; +const char* defaultfile; +// m_misc.vars end // +// m_random.vars begin // +int rndindex ; +int prndindex ; +// m_random.vars end // +// p_ceilng.vars begin // +ceiling_t* activeceilings[MAXCEILINGS]; +// p_ceilng.vars end // +// p_enemy.vars begin // +mobj_t* soundtarget; +int TRACEANGLE ; +mobj_t* corpsehit; +mobj_t* vileobj; +fixed_t viletryx; +fixed_t viletryy; +mobj_t* braintargets[32]; +int numbraintargets; +int braintargeton; +int easy ; +// p_enemy.vars end // +// p_map.vars begin // +fixed_t tmbbox[4]; +mobj_t* tmthing; +int tmflags; +fixed_t tmx; +fixed_t tmy; +qboolean floatok; +fixed_t tmfloorz; +fixed_t tmceilingz; +fixed_t tmdropoffz; +line_t* ceilingline; +line_t* spechit[MAXSPECIALCROSS]; +int numspechit; +fixed_t bestslidefrac; +fixed_t secondslidefrac; +line_t* bestslideline; +line_t* secondslideline; +mobj_t* slidemo; +fixed_t tmxmove; +fixed_t tmymove; +mobj_t* linetarget; // who got hit (or NULL) +mobj_t* shootthing; +fixed_t shootz; +int la_damage; +fixed_t attackrange; +fixed_t aimslope; +mobj_t* usething; +mobj_t* bombsource; +mobj_t* bombspot; +int bombdamage; +qboolean crushchange; +qboolean nofit; +// p_map.vars end // +// p_maputl.vars begin // +fixed_t opentop; +fixed_t openbottom; +fixed_t openrange; +fixed_t lowfloor; +intercept_t intercepts[MAXINTERCEPTS]; +intercept_t* intercept_p; +divline_t trace; +qboolean earlyout; +int ptflags; +// p_maputl.vars end // +// p_mobj.vars begin // +int test; +mapthing_t itemrespawnque[ITEMQUESIZE]; +int itemrespawntime[ITEMQUESIZE]; +int iquehead; +int iquetail; +// p_mobj.vars end // +// p_plats.vars begin // +plat_t* activeplats[MAXPLATS]; +// p_plats.vars end // +// p_pspr.vars begin // +fixed_t swingx; +fixed_t swingy; +fixed_t bulletslope; +// p_pspr.vars end // +// p_saveg.vars begin // +byte* save_p; +// p_saveg.vars end // +// p_setup.vars begin // +int numvertexes; +vertex_t* vertexes; +int numsegs; +seg_t* segs; +int numsectors; +sector_t* sectors; +int numsubsectors; +subsector_t* subsectors; +int numnodes; +node_t* nodes; +int numlines; +line_t* lines; +int numsides; +side_t* sides; +int bmapwidth; +int bmapheight; // size in mapblocks +short* blockmap; // int for larger maps +short* blockmaplump; +fixed_t bmaporgx; +fixed_t bmaporgy; +mobj_t** blocklinks; +mapthing_t deathmatchstarts[MAX_DEATHMATCH_STARTS]; +mapthing_t* deathmatch_p; +mapthing_t playerstarts[MAXPLAYERS]; +// p_setup.vars end // +// p_sight.vars begin // +fixed_t sightzstart; // eye z of looker +fixed_t topslope; +fixed_t bottomslope; // slopes to top and bottom of target +divline_t strace; // from t1 to t2 +fixed_t t2x; +fixed_t t2y; +int sightcounts[2]; +// p_sight.vars end // +// p_spec.vars begin // +anim_t2 anims[MAXANIMS]; +anim_t2* lastanim; +qboolean levelTimer; +int levelTimeCount; +int levelFragCount; +short numlinespecials; +line_t* linespeciallist[MAXLINEANIMS]; +// p_spec.vars end // +// p_switch.vars begin // +int switchlist[MAXSWITCHES * 2]; +int numswitches; +button_t buttonlist[MAXBUTTONS]; +// p_switch.vars end // +// p_tick.vars begin // +int leveltime; +thinker_t thinkercap; +// p_tick.vars end // +// p_user.vars begin // +qboolean onground; +// p_user.vars end // +// r_bsp.vars begin // +seg_t* curline; +side_t* sidedef; +line_t* linedef; +sector_t* frontsector; +sector_t* backsector; +drawseg_t drawsegs[MAXDRAWSEGS]; +drawseg_t* ds_p; +cliprange_t* newend; +cliprange_t solidsegs[MAXSEGS]; +int checkcoord[12][4]; +// r_bsp.vars end // +// r_data.vars begin // +int firstflat; +int lastflat; +int numflats; +int firstpatch; +int lastpatch; +int numpatches; +int firstspritelump; +int lastspritelump; +int numspritelumps; +int* flattranslation; +int* texturetranslation; +fixed_t* spritewidth; +fixed_t* spriteoffset; +fixed_t* spritetopoffset; +lighttable_t *colormaps; +int flatmemory; +int texturememory; +int spritememory; +// r_data.vars end // +// r_draw.vars begin // +byte* viewimage; +int viewwidth; +int scaledviewwidth; +int viewheight; +int viewwindowx; +int viewwindowy; +byte* ylookup[MAXHEIGHT]; +int columnofs[MAXWIDTH]; +byte translations[3][256]; +lighttable_t* dc_colormap; +int dc_x; +int dc_yl; +int dc_yh; +fixed_t dc_iscale; +fixed_t dc_texturemid; +byte* dc_source; +int dccount; +int fuzzoffset[FUZZTABLE]; +int fuzzpos ; +byte* dc_translation; +byte* translationtables; +int ds_y; +int ds_x1; +int ds_x2; +lighttable_t* ds_colormap; +fixed_t ds_xfrac; +fixed_t ds_yfrac; +fixed_t ds_xstep; +fixed_t ds_ystep; +byte* ds_source; +int dscount; +// r_draw.vars end // +// r_main.vars begin // +int viewangleoffset; +int validcount ; +lighttable_t* fixedcolormap; +int centerx; +int centery; +fixed_t centerxfrac; +fixed_t centeryfrac; +fixed_t projection; +int framecount; +int sscount; +int linecount; +int loopcount; +fixed_t viewx; +fixed_t viewy; +fixed_t viewz; +angle_t viewangle; +fixed_t viewcos; +fixed_t viewsin; +player_t* viewplayer; +int detailshift; +angle_t clipangle; +int viewangletox[FINEANGLES/2]; +angle_t xtoviewangle[SCREENWIDTH+1]; +lighttable_t* scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; +lighttable_t* scalelightfixed[MAXLIGHTSCALE]; +lighttable_t* zlight[LIGHTLEVELS][MAXLIGHTZ]; +int extralight; +qboolean setsizeneeded; +int setblocks; +int setdetail; +// r_main.vars end // +// r_plane.vars begin // +planefunction_t floorfunc; +planefunction_t ceilingfunc; +short openings[MAXOPENINGS]; +short* lastopening; +short floorclip[SCREENWIDTH]; +short ceilingclip[SCREENWIDTH]; +int spanstart[SCREENHEIGHT]; +int spanstop[SCREENHEIGHT]; +lighttable_t** planezlight; +fixed_t planeheight; +fixed_t yslope[SCREENHEIGHT]; +fixed_t distscale[SCREENWIDTH]; +fixed_t basexscale; +fixed_t baseyscale; +fixed_t cachedheight[SCREENHEIGHT]; +fixed_t cacheddistance[SCREENHEIGHT]; +fixed_t cachedxstep[SCREENHEIGHT]; +fixed_t cachedystep[SCREENHEIGHT]; +// r_plane.vars end // +// r_segs.vars begin // +qboolean segtextured; +qboolean markfloor; +qboolean markceiling; +qboolean maskedtexture; +int toptexture; +int bottomtexture; +int midtexture; +angle_t rw_normalangle; +int rw_angle1; +int rw_x; +int rw_stopx; +angle_t rw_centerangle; +fixed_t rw_offset; +fixed_t rw_distance; +fixed_t rw_scale; +fixed_t rw_scalestep; +fixed_t rw_midtexturemid; +fixed_t rw_toptexturemid; +fixed_t rw_bottomtexturemid; +int worldtop; +int worldbottom; +int worldhigh; +int worldlow; +fixed_t pixhigh; +fixed_t pixlow; +fixed_t pixhighstep; +fixed_t pixlowstep; +fixed_t topfrac; +fixed_t topstep; +fixed_t bottomfrac; +fixed_t bottomstep; +lighttable_t** walllights; +short* maskedtexturecol; +// r_segs.vars end // +// r_sky.vars begin // +int skyflatnum; +int skytexture; +int skytexturemid; +// r_sky.vars end // +// r_things.vars begin // +fixed_t pspritescale; +fixed_t pspriteiscale; +lighttable_t** spritelights; +short negonearray[SCREENWIDTH]; +short screenheightarray[SCREENWIDTH]; +spritedef_t* sprites; +int numsprites; +spriteframe_t sprtemp[29]; +int maxframe; +vissprite_t vissprites[MAXVISSPRITES]; +vissprite_t* vissprite_p; +int newvissprite; +vissprite_t overflowsprite; +short* mfloorclip; +short* mceilingclip; +fixed_t spryscale; +fixed_t sprtopscreen; +vissprite_t vsprsortedhead; +// r_things.vars end // +// sounds.vars begin // +musicinfo_t S_music[80]; +// sounds.vars end // +// st_lib.vars begin // +patch_t* sttminus; +// st_lib.vars end // +// st_stuff.vars begin // +player_t* plyr; +qboolean st_firsttime; +int veryfirsttime ; +int lu_palette; +unsigned int st_clock; +int st_msgcounter; +st_chatstateenum_t st_chatstate; +st_stateenum_t st_gamestate; +qboolean st_statusbaron; +qboolean st_chat; +qboolean st_oldchat; +qboolean st_cursoron; +qboolean st_notdeathmatch; +qboolean st_armson; +qboolean st_fragson; +patch_t* sbar; // AffLANHACK IGNORE +patch_t* tallnum[10]; // AffLANHACK IGNORE +patch_t* tallpercent; // AffLANHACK IGNORE +patch_t* shortnum[10]; // AffLANHACK IGNORE +patch_t* keys[NUMCARDS]; // AffLANHACK IGNORE +patch_t* faces[ST_NUMFACES]; // AffLANHACK IGNORE +patch_t* faceback; // AffLANHACK IGNORE +patch_t* armsbg; // AffLANHACK IGNORE +patch_t* arms[6][2]; // AffLANHACK IGNORE +st_number_t w_ready; +st_number_t w_frags; +st_percent_t w_health; +st_binicon_t w_armsbg; +st_multicon_t w_arms[6]; +st_multicon_t w_faces; +st_multicon_t w_keyboxes[3]; +st_percent_t w_armor; +st_number_t w_ammo[4]; +st_number_t w_maxammo[4]; +int st_fragscount; +int st_oldhealth ; +qboolean oldweaponsowned[NUMWEAPONS]; +int st_facecount ; +int st_faceindex ; +int keyboxes[3]; +int st_randomnumber; +cheatseq_t cheat_powerup[7]; +int lastcalc; +int oldhealth ; +int lastattackdown ; +int priority ; +int largeammo ; +int st_palette ; +qboolean st_stopped ; +// st_stuff.vars end // +// s_sound.vars begin // +channel_t* channels; +qboolean mus_paused; +qboolean mus_looping; +musicinfo_t* mus_playing; +int numChannels; +int nextcleanup; +// s_sound.vars end // +// v_video.vars begin // +byte* screens[5]; +int dirtybox[4]; +int usegamma; +// v_video.vars end // +// wi_stuff.vars begin // +anim_t epsd0animinfo[10]; +anim_t epsd1animinfo[9]; +anim_t epsd2animinfo[6]; +anim_t* wi_stuff_anims[NUMEPISODES]; +int NUMANIMS[NUMEPISODES]; +const char* chat_macros[10]; +int acceleratestage; +int me; +stateenum_t state; +wbstartstruct_t* wbs; +int cnt; +int bcnt; +int firstrefresh; +int cnt_kills[MAXPLAYERS]; +int cnt_items[MAXPLAYERS]; +int cnt_secret[MAXPLAYERS]; +int cnt_time; +int cnt_par; +int cnt_pause; +int NUMCMAPS; +patch_t* colon; +qboolean snl_pointeron ; +int dm_state; +int dm_frags[MAXPLAYERS][MAXPLAYERS]; +int dm_totals[MAXPLAYERS]; +int cnt_frags[MAXPLAYERS]; +int dofrags; +int ng_state; +int sp_state; +// wi_stuff.vars end // +// w_wad.vars begin // +int reloadlump; +// w_wad.vars end // +// z_zone.vars begin // +int sizes[NUM_ZONES+1]; +memzone_t* zones[NUM_ZONES] ; +int NumAlloc ; +// z_zone.vars end // +// info vars begin // +state_t states[NUMSTATES]; +// info vars end // +// p_local begin // +byte* rejectmatrix; +// p_local end // +// r_data begin // +int s_numtextures; +texture_t** s_textures; +int* s_texturewidthmask; +// needed for texture pegging +fixed_t* s_textureheight; +short** s_texturecolumnlump; +unsigned short** s_texturecolumnofs; +byte** s_texturecomposite; +int* s_texturecompositesize; +// r_data end // +// r_plane begin // +visplane_t visplanes[MAXVISPLANES]; +visplane_t* lastvisplane; +visplane_t* floorplane; +visplane_t* ceilingplane; +// r_plane end // + +// wi_stuff +// background (map of levels). +patch_t* bg; + +// You Are Here graphic +patch_t* yah[2]; + +// splat +patch_t* splat; + +// %, : graphics +patch_t* percent; + +// 0-9 graphic +patch_t* num[10]; + +// minus sign +patch_t* wiminus; + +// "Finished!" graphics +patch_t* finished; + +// "Entering" graphic +patch_t* entering; + +// "secret" +patch_t* sp_secret; + + // "Kills", "Scrt", "Items", "Frags" +patch_t* kills; +patch_t* secret; +patch_t* items; +patch_t* wistuff_frags; + +// Time sucks. +patch_t* time; +patch_t* par; +patch_t* sucks; + +// "killers", "victims" +patch_t* killers; +patch_t* victims; + +// "Total", your face, your dead face +patch_t* total; +patch_t* star; +patch_t* bstar; + +// "red P[1..MAXPLAYERS]" +patch_t* wistuff_p[MAXPLAYERS]; + +// "gray P[1..MAXPLAYERS]" +patch_t* wistuff_bp[MAXPLAYERS]; + + // Name graphics of each level (centered) +patch_t** lnames; + +const char* spritename; + + diff --git a/doomclassic/doom/w_wad.cpp b/doomclassic/doom/w_wad.cpp new file mode 100644 index 00000000..4e827a92 --- /dev/null +++ b/doomclassic/doom/w_wad.cpp @@ -0,0 +1,497 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" + + +#include +#include +#include +#include +#include +#include + +#include "doomtype.h" +#include "m_swap.h" +#include "i_system.h" +#include "z_zone.h" + +#include "idlib/precompiled.h" + +#ifdef __GNUG__ +#pragma implementation "w_wad.h" +#endif +#include "w_wad.h" + + + +// +// GLOBALS +// + +lumpinfo_t* lumpinfo = NULL; +int numlumps; +void** lumpcache; + + + +int filelength (FILE* handle) +{ + // DHM - not used :: development tool (loading single lump not in a WAD file) + return 0; +} + + +void +ExtractFileBase +( const char* path, + char* dest ) +{ + const char* src; + int length; + + src = path + strlen(path) - 1; + + // back up until a \ or the start + while (src != path + && *(src-1) != '\\' + && *(src-1) != '/') + { + src--; + } + + // copy up to eight characters + memset (dest,0,8); + length = 0; + + while (*src && *src != '.') + { + if (++length == 9) + I_Error ("Filename base of %s >8 chars",path); + + *dest++ = toupper((int)*src++); + } +} + + + + + +// +// LUMP BASED ROUTINES. +// + +// +// W_AddFile +// All files are optional, but at least one file must be +// found (PWAD, if all required lumps are present). +// Files with a .wad extension are wadlink files +// with multiple lumps. +// Other files are single lumps with the base filename +// for the lump name. +// +// If filename starts with a tilde, the file is handled +// specially to allow map reloads. +// But: the reload feature is a fragile hack... + +const char* reloadname; + + +void W_AddFile ( const char *filename) +{ + wadinfo_t header; + lumpinfo_t* lump_p; + int i; + idFile * handle; + int length; + int startlump; + std::vector fileinfo( 1 ); + + // open the file and add to directory + if ( (handle = fileSystem->OpenFileRead(filename)) == 0) + { + I_Printf (" couldn't open %s\n",filename); + return; + } + + I_Printf (" adding %s\n",filename); + startlump = numlumps; + + if ( idStr::Icmp( filename+strlen(filename)-3 , "wad" ) ) + { + // single lump file + fileinfo[0].filepos = 0; + fileinfo[0].size = 0; + ExtractFileBase (filename, fileinfo[0].name); + numlumps++; + } + else + { + // WAD file + handle->Read( &header, sizeof( header ) ); + if ( idStr::Cmpn( header.identification,"IWAD",4 ) ) + { + // Homebrew levels? + if ( idStr::Cmpn( header.identification, "PWAD", 4 ) ) + { + I_Error ("Wad file %s doesn't have IWAD " + "or PWAD id\n", filename); + } + + // ???modifiedgame = true; + } + header.numlumps = LONG(header.numlumps); + header.infotableofs = LONG(header.infotableofs); + length = header.numlumps*sizeof(filelump_t); + fileinfo.resize(header.numlumps); + handle->Seek( header.infotableofs, FS_SEEK_SET ); + handle->Read( &fileinfo[0], length ); + numlumps += header.numlumps; + } + + + // Fill in lumpinfo + if (lumpinfo == NULL) { + lumpinfo = (lumpinfo_t*)malloc( numlumps*sizeof(lumpinfo_t) ); + } else { + lumpinfo = (lumpinfo_t*)realloc( lumpinfo, numlumps*sizeof(lumpinfo_t) ); + } + + if (!lumpinfo) + I_Error ("Couldn't realloc lumpinfo"); + + lump_p = &lumpinfo[startlump]; + + ::g->wadFileHandles[ ::g->numWadFiles++ ] = handle; + + filelump_t * filelumpPointer = &fileinfo[0]; + for (i=startlump ; ihandle = handle; + lump_p->position = LONG(filelumpPointer->filepos); + lump_p->size = LONG(filelumpPointer->size); + strncpy (lump_p->name, filelumpPointer->name, 8); + } +} + + + + +// +// W_Reload +// Flushes any of the reloadable lumps in memory +// and reloads the directory. +// +void W_Reload (void) +{ + // DHM - unused development tool +} + +// +// W_FreeLumps +// Frees all lump data +// +void W_FreeLumps() { + if ( lumpcache != NULL ) { + for ( int i = 0; i < numlumps; i++ ) { + if ( lumpcache[i] ) { + Z_Free( lumpcache[i] ); + } + } + + Z_Free( lumpcache ); + lumpcache = NULL; + } + + if ( lumpinfo != NULL ) { + free( lumpinfo ); + lumpinfo = NULL; + numlumps = 0; + } +} + +// +// W_FreeWadFiles +// Free this list of wad files so that a new list can be created +// +void W_FreeWadFiles() { + for (int i = 0 ; i < MAXWADFILES ; i++) { + wadfiles[i] = NULL; + if ( ::g->wadFileHandles[i] ) { + delete ::g->wadFileHandles[i]; + } + ::g->wadFileHandles[i] = NULL; + } + ::g->numWadFiles = 0; + extraWad = 0; +} + + + +// +// W_InitMultipleFiles +// Pass a null terminated list of files to use. +// All files are optional, but at least one file +// must be found. +// Files with a .wad extension are idlink files +// with multiple lumps. +// Other files are single lumps with the base filename +// for the lump name. +// Lump names can appear multiple times. +// The name searcher looks backwards, so a later file +// does override all earlier ones. +// +void W_InitMultipleFiles (const char** filenames) +{ + int size; + + if (lumpinfo == NULL) + { + // open all the files, load headers, and count lumps + numlumps = 0; + + // will be realloced as lumps are added + lumpinfo = NULL; + + for ( ; *filenames ; filenames++) + { + W_AddFile (*filenames); + } + + if (!numlumps) + I_Error ("W_InitMultipleFiles: no files found"); + + // set up caching + size = numlumps * sizeof(*lumpcache); + lumpcache = (void**)DoomLib::Z_Malloc(size, PU_STATIC_SHARED, 0 ); + + if (!lumpcache) + I_Error ("Couldn't allocate lumpcache"); + + memset (lumpcache,0, size); + } else { + // set up caching + size = numlumps * sizeof(*lumpcache); + lumpcache = (void**)DoomLib::Z_Malloc(size, PU_STATIC_SHARED, 0 ); + + if (!lumpcache) + I_Error ("Couldn't allocate lumpcache"); + + memset (lumpcache,0, size); + } +} + + +void W_Shutdown( void ) { +/* + for (int i = 0 ; i < MAXWADFILES ; i++) { + if ( ::g->wadFileHandles[i] ) { + doomFiles->FClose( ::g->wadFileHandles[i] ); + } + } + + if ( lumpinfo != NULL ) { + free( lumpinfo ); + lumpinfo = NULL; + } +*/ + W_FreeLumps(); + W_FreeWadFiles(); +} + +// +// W_NumLumps +// +int W_NumLumps (void) +{ + return numlumps; +} + + + +// +// W_CheckNumForName +// Returns -1 if name not found. +// + +int W_CheckNumForName (const char* name) +{ + const int NameLength = 9; + + union { + char s[NameLength]; + int x[2]; + + } name8; + + int v1; + int v2; + lumpinfo_t* lump_p; + + // make the name into two integers for easy compares + strncpy (name8.s,name, NameLength - 1); + + // in case the name was a fill 8 chars + name8.s[NameLength - 1] = 0; + + // case insensitive + for ( int i = 0; i < NameLength; ++i ) { + name8.s[i] = toupper( name8.s[i] ); + } + + v1 = name8.x[0]; + v2 = name8.x[1]; + + + // scan backwards so patch lump files take precedence + lump_p = lumpinfo + numlumps; + + while (lump_p-- != lumpinfo) + { + if ( *(int *)lump_p->name == v1 + && *(int *)&lump_p->name[4] == v2) + { + return lump_p - lumpinfo; + } + } + + // TFB. Not found. + return -1; +} + + + + +// +// W_GetNumForName +// Calls W_CheckNumForName, but bombs out if not found. +// +int W_GetNumForName ( const char* name) +{ + int i; + + i = W_CheckNumForName ( name); + + if (i == -1) + I_Error ("W_GetNumForName: %s not found!", name); + + return i; +} + + +// +// W_LumpLength +// Returns the buffer size needed to load the given lump. +// +int W_LumpLength (int lump) +{ + if (lump >= numlumps) + I_Error ("W_LumpLength: %i >= numlumps",lump); + + return lumpinfo[lump].size; +} + + + +// +// W_ReadLump +// Loads the lump into the given buffer, +// which must be >= W_LumpLength(). +// +void +W_ReadLump +( int lump, + void* dest ) +{ + int c; + lumpinfo_t* l; + idFile * handle; + + if (lump >= numlumps) + I_Error ("W_ReadLump: %i >= numlumps",lump); + + l = lumpinfo+lump; + + handle = l->handle; + + handle->Seek( l->position, FS_SEEK_SET ); + c = handle->Read( dest, l->size ); + + if (c < l->size) + I_Error ("W_ReadLump: only read %i of %i on lump %i", c,l->size,lump); +} + + + + +// +// W_CacheLumpNum +// +void* +W_CacheLumpNum +( int lump, + int tag ) +{ +#ifdef RANGECHECK + if (lump >= numlumps) + I_Error ("W_CacheLumpNum: %i >= numlumps",lump); +#endif + + if (!lumpcache[lump]) + { + byte* ptr; + // read the lump in + //I_Printf ("cache miss on lump %i\n",lump); + ptr = (byte*)DoomLib::Z_Malloc(W_LumpLength (lump), tag, &lumpcache[lump]); + W_ReadLump (lump, lumpcache[lump]); + } + + return lumpcache[lump]; +} + + + +// +// W_CacheLumpName +// +void* +W_CacheLumpName +( const char* name, + int tag ) +{ + return W_CacheLumpNum (W_GetNumForName(name), tag); +} + + +void W_Profile (void) +{ +} + + + diff --git a/doomclassic/doom/w_wad.h b/doomclassic/doom/w_wad.h new file mode 100644 index 00000000..fa90121c --- /dev/null +++ b/doomclassic/doom/w_wad.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __W_WAD__ +#define __W_WAD__ + + +#ifdef __GNUG__ +#pragma interface +#endif + + +// +// TYPES +// +typedef struct +{ + // Should be "IWAD" or "PWAD". + char identification[4]; + int numlumps; + int infotableofs; + +} wadinfo_t; + + +typedef struct +{ + int filepos; + int size; + char name[8]; + +} filelump_t; + +// +// WADFILE I/O related stuff. +// +typedef struct +{ + char name[8]; + idFile * handle; + int position; + int size; +} lumpinfo_t; + + +extern void** lumpcache; +extern lumpinfo_t* lumpinfo; +extern int numlumps; + +void W_InitMultipleFiles (const char** filenames); +void W_Reload (void); +void W_FreeLumps(); +void W_FreeWadFiles(); + +int W_CheckNumForName (const char* name); +int W_GetNumForName (const char* name); + +int W_LumpLength (int lump); +void W_ReadLump (int lump, void *dest); + +void* W_CacheLumpNum (int lump, int tag); +void* W_CacheLumpName (const char* name, int tag); + +void W_Shutdown( void ); + + +#endif + diff --git a/doomclassic/doom/wi_stuff.cpp b/doomclassic/doom/wi_stuff.cpp new file mode 100644 index 00000000..f0c41444 --- /dev/null +++ b/doomclassic/doom/wi_stuff.cpp @@ -0,0 +1,1756 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" +#include "globaldata.h" +#include "Main.h" +#include "sys/sys_session.h" +#include "sys/sys_signin.h" +#include "DoomLeaderboards.h" +#include "d3xp/Game_Local.h" + + +#include + +#include "z_zone.h" + +#include "m_random.h" +#include "m_swap.h" + +#include "i_system.h" + +#include "w_wad.h" + +#include "g_game.h" + +#include "r_local.h" +#include "s_sound.h" + +#include "doomstat.h" + +// Data. +#include "sounds.h" + +// Needs access to LFB. +#include "v_video.h" + +#include "wi_stuff.h" + + +// +// Data needed to add patches to full screen intermission pics. +// Patches are statistics messages, and animations. +// Loads of by-pixel layout and placement, offsets etc. +// + + +// +// Different vetween registered DOOM (1994) and +// Ultimate DOOM - Final edition (retail, 1995?). +// This is supposedly ignored for commercial +// release (aka DOOM II), which had 34 maps +// in one episode. So there. + + +// in tics +//U #define PAUSELEN (TICRATE*2) +//U #define SCORESTEP 100 +//U #define ANIMPERIOD 32 +// pixel distance from "(YOU)" to "PLAYER N" +//U #define STARDIST 10 +//U #define WK 1 + + +// GLOBAL LOCATIONS + +// SINGPLE-PLAYER STUFF + + + +// NET GAME STUFF + + + +// DEATHMATCH STUFF + + + + + + + + + + +// +// Animation. +// There is another anim_t used in p_spec. +// + + +const point_t lnodes[NUMEPISODES][NUMMAPS] = +{ + // Episode 0 World Map + { + { 185, 164 }, // location of level 0 (CJ) + { 148, 143 }, // location of level 1 (CJ) + { 69, 122 }, // location of level 2 (CJ) + { 209, 102 }, // location of level 3 (CJ) + { 116, 89 }, // location of level 4 (CJ) + { 166, 55 }, // location of level 5 (CJ) + { 71, 56 }, // location of level 6 (CJ) + { 135, 29 }, // location of level 7 (CJ) + { 71, 24 } // location of level 8 (CJ) + }, + + // Episode 1 World Map should go here + { + { 254, 25 }, // location of level 0 (CJ) + { 97, 50 }, // location of level 1 (CJ) + { 188, 64 }, // location of level 2 (CJ) + { 128, 78 }, // location of level 3 (CJ) + { 214, 92 }, // location of level 4 (CJ) + { 133, 130 }, // location of level 5 (CJ) + { 208, 136 }, // location of level 6 (CJ) + { 148, 140 }, // location of level 7 (CJ) + { 235, 158 } // location of level 8 (CJ) + }, + + // Episode 2 World Map should go here + { + { 156, 168 }, // location of level 0 (CJ) + { 48, 154 }, // location of level 1 (CJ) + { 174, 95 }, // location of level 2 (CJ) + { 265, 75 }, // location of level 3 (CJ) + { 130, 48 }, // location of level 4 (CJ) + { 279, 23 }, // location of level 5 (CJ) + { 198, 48 }, // location of level 6 (CJ) + { 140, 25 }, // location of level 7 (CJ) + { 281, 136 } // location of level 8 (CJ) + } + +}; + + +// +// Animation locations for episode 0 (1). +// Using patches saves a lot of space, +// as they replace 320x200 full screen frames. +// +extern const anim_t temp_epsd0animinfo[10]; +const anim_t temp_epsd0animinfo[10] = +{ + { ANIM_ALWAYS, TICRATE/3, 3, { 224, 104 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 184, 160 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 112, 136 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 72, 112 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 88, 96 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 64, 48 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 192, 40 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 136, 16 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 80, 16 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 64, 24 } } +}; + +extern const anim_t temp_epsd1animinfo[9]; +const anim_t temp_epsd1animinfo[9] = +{ + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 1 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 2 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 3 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 4 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 5 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 6 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 7 }, + { ANIM_LEVEL, TICRATE/3, 3, { 192, 144 }, 8 }, + { ANIM_LEVEL, TICRATE/3, 1, { 128, 136 }, 8 } +}; + +extern const anim_t temp_epsd2animinfo[6]; +const anim_t temp_epsd2animinfo[6] = +{ + { ANIM_ALWAYS, TICRATE/3, 3, { 104, 168 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 40, 136 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 160, 96 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 104, 80 } }, + { ANIM_ALWAYS, TICRATE/3, 3, { 120, 32 } }, + { ANIM_ALWAYS, TICRATE/4, 3, { 40, 0 } } +}; + + +// +// GENERAL DATA +// + +// +// Locally used stuff. +// + + +// States for single-player + + +// in seconds +//#define SHOWLASTLOCDELAY SHOWNEXTLOCDELAY + + +// used to accelerate or skip a stage + +// ::g->wbs->pnum + + // specifies current ::g->state + +// contains information passed into intermission + +const wbplayerstruct_t* plrs; // ::g->wbs->plyr[] + +// used for general timing + +// used for timing of background animation + +// signals to refresh everything for one frame + + +// # of commercial levels + + +// +// GRAPHICS +// + + +// +// CODE +// + + + +void localCalculateAchievements(bool epComplete) +{ + + if( !common->IsMultiplayer() ) { + + player_t *player = &::g->players[::g->consoleplayer]; + + // Calculate Any Achievements earned from stat cumulation. + idAchievementManager::CheckDoomClassicsAchievements( player->killcount, player->itemcount, player->secretcount, ::g->gameskill, ::g->gamemission, ::g->gamemap, ::g->gameepisode, ::g->totalkills, ::g->totalitems, ::g->totalsecret ); + + + } +} + +// slam background +// UNUSED static unsigned char *background=0; + + +void WI_slamBackground(void) +{ + memcpy(::g->screens[0], ::g->screens[1], SCREENWIDTH * SCREENHEIGHT); + V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT); +} + +// The ticker is used to detect keys +// because of timing issues in netgames. +qboolean WI_Responder(event_t* ev) +{ + return false; +} + +// Draws " Finished!" +void WI_drawLF(void) +{ + int y = WI_TITLEY; + + // draw + V_DrawPatch((ORIGINAL_WIDTH - SHORT(::g->lnames[::g->wbs->last]->width))/2, + y, FB, ::g->lnames[::g->wbs->last]); + + // draw "Finished!" + y += (5*SHORT(::g->lnames[::g->wbs->last]->height))/4; + + V_DrawPatch((ORIGINAL_WIDTH - SHORT(::g->finished->width))/2, + y, FB, ::g->finished); +} + + + +// Draws "Entering " +void WI_drawEL(void) +{ + int y = WI_TITLEY; + + // draw "Entering" + V_DrawPatch((ORIGINAL_WIDTH - SHORT(::g->entering->width))/2, + y, FB, ::g->entering); + + // draw level + y += (5*SHORT(::g->lnames[::g->wbs->next]->height))/4; + + V_DrawPatch((ORIGINAL_WIDTH - SHORT(::g->lnames[::g->wbs->next]->width))/2, + y, FB, ::g->lnames[::g->wbs->next]); + +} + +void +WI_drawOnLnode +( int n, + patch_t* c[] ) +{ + + int i; + int left; + int top; + int right; + int bottom; + qboolean fits = false; + + i = 0; + do + { + left = lnodes[::g->wbs->epsd][n].x - SHORT(c[i]->leftoffset); + top = lnodes[::g->wbs->epsd][n].y - SHORT(c[i]->topoffset); + right = left + SHORT(c[i]->width); + bottom = top + SHORT(c[i]->height); + + if (left >= 0 + && right < SCREENWIDTH + && top >= 0 + && bottom < SCREENHEIGHT) + { + fits = true; + } + else + { + i++; + } + } while (!fits && i!=2); + + if (fits && i<2) + { + V_DrawPatch(lnodes[::g->wbs->epsd][n].x, lnodes[::g->wbs->epsd][n].y, + FB, c[i]); + } + else + { + // DEBUG + I_Printf("Could not place patch on level %d", n+1); + } +} + + + +void WI_initAnimatedBack(void) +{ + int i; + anim_t* a; + + if (::g->gamemode == commercial) + return; + + if (::g->wbs->epsd > 2) + return; + + for (i=0;i < ::g->NUMANIMS[::g->wbs->epsd];i++) + { + a = &::g->wi_stuff_anims[::g->wbs->epsd][i]; + + // init variables + a->ctr = -1; + + // specify the next time to draw it + if (a->type == ANIM_ALWAYS) + a->nexttic = ::g->bcnt + 1 + (M_Random()%a->period); + else if (a->type == ANIM_RANDOM) + a->nexttic = ::g->bcnt + 1 + a->data2+(M_Random()%a->data1); + else if (a->type == ANIM_LEVEL) + a->nexttic = ::g->bcnt + 1; + } + +} + + +void WI_updateAnimatedBack(void) +{ + int i; + anim_t* a; + + if (::g->gamemode == commercial) + return; + + if (::g->wbs->epsd > 2) + return; + + for (i=0;i < ::g->NUMANIMS[::g->wbs->epsd];i++) + { + a = &::g->wi_stuff_anims[::g->wbs->epsd][i]; + + if (::g->bcnt == a->nexttic) + { + switch (a->type) + { + case ANIM_ALWAYS: + if (++a->ctr >= a->nanims) a->ctr = 0; + a->nexttic = ::g->bcnt + a->period; + break; + + case ANIM_RANDOM: + a->ctr++; + if (a->ctr == a->nanims) + { + a->ctr = -1; + a->nexttic = ::g->bcnt+a->data2+(M_Random()%a->data1); + } + else a->nexttic = ::g->bcnt + a->period; + break; + + case ANIM_LEVEL: + // gawd-awful hack for level anims + if (!(::g->state == StatCount && i == 7) + && ::g->wbs->next == a->data1) + { + a->ctr++; + if (a->ctr == a->nanims) a->ctr--; + a->nexttic = ::g->bcnt + a->period; + } + break; + } + } + + } + +} + +void WI_drawAnimatedBack(void) +{ + int i; + anim_t* a; + + if (commercial) + return; + + if (::g->wbs->epsd > 2) + return; + + for (i=0 ; i < ::g->NUMANIMS[::g->wbs->epsd] ; i++) + { + a = &::g->wi_stuff_anims[::g->wbs->epsd][i]; + + if (a->ctr >= 0) + V_DrawPatch(a->loc.x, a->loc.y, FB, a->p[a->ctr]); + } + +} + +// +// Draws a number. +// If digits > 0, then use that many digits minimum, +// otherwise only use as many as necessary. +// Returns new x position. +// + +int +WI_drawNum +( int x, + int y, + int n, + int digits ) +{ + + int fontwidth = SHORT(::g->num[0]->width); + int neg; + int temp; + + if (digits < 0) + { + if (!n) + { + // make variable-length zeros 1 digit long + digits = 1; + } + else + { + // figure out # of digits in # + digits = 0; + temp = n; + + while (temp) + { + temp /= 10; + digits++; + } + } + } + + neg = n < 0; + if (neg) + n = -n; + + // if non-number, do not draw it + if (n == 1994) + return 0; + + // draw the new number + while (digits--) + { + x -= fontwidth; + V_DrawPatch(x, y, FB, ::g->num[ n % 10 ]); + n /= 10; + } + + // draw a minus sign if necessary + if (neg) + V_DrawPatch(x-=8, y, FB, ::g->wiminus); + + return x; + +} + +void +WI_drawPercent +( int x, + int y, + int p ) +{ + if (p < 0) + return; + + V_DrawPatch(x, y, FB, ::g->percent); + WI_drawNum(x, y, p, -1); +} + + + +// +// Display level completion time and par, +// or "sucks" message if overflow. +// +void +WI_drawTime +( int x, + int y, + int t ) +{ + + int div; + int n; + + if (t<0) + return; + + if (t <= 61*59) + { + div = 1; + + do + { + n = (t / div) % 60; + x = WI_drawNum(x, y, n, 2) - SHORT(::g->colon->width); + div *= 60; + + // draw + if (div==60 || t / div) + V_DrawPatch(x, y, FB, ::g->colon); + + } while (t / div); + } + else + { + // "sucks" + V_DrawPatch(x - SHORT(::g->sucks->width), y, FB, ::g->sucks); + } +} + + +void WI_End(void) +{ + void WI_unloadData(void); + WI_unloadData(); +} + +void WI_initNoState(void) +{ + ::g->state = NoState; + ::g->acceleratestage = 0; + ::g->cnt = 10; +} + +void WI_updateNoState(void) { + + WI_updateAnimatedBack(); + + if (!--::g->cnt) { + // Unload data + WI_End(); + G_WorldDone(); + } + + DoomLib::ActivateGame(); +} + + + +void WI_initShowNextLoc(void) +{ + ::g->state = ShowNextLoc; + ::g->acceleratestage = 0; + ::g->cnt = SHOWNEXTLOCDELAY * TICRATE; + + WI_initAnimatedBack(); + + DoomLib::ActivateGame(); +} + +void WI_updateShowNextLoc(void) +{ + WI_updateAnimatedBack(); + + if (!--::g->cnt || ::g->acceleratestage) { + WI_initNoState(); + DoomLib::ShowXToContinue( false ); + } else { + ::g->snl_pointeron = (::g->cnt & 31) < 20; + } +} + +void WI_drawShowNextLoc(void) +{ + + int i; + int last; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + if ( ::g->gamemode != commercial) + { + if (::g->wbs->epsd > 2) + { + WI_drawEL(); + return; + } + + last = (::g->wbs->last == 8) ? ::g->wbs->next - 1 : ::g->wbs->last; + + // don't draw any splats for extra secret levels + if( last == 9 ) { + for (i=0 ; iplayers[i].didsecret = false; + } + ::g->wbs->didsecret = false; + last = -1; + } + + // draw a splat on taken cities. + for (i=0 ; i<=last ; i++) + WI_drawOnLnode(i, &::g->splat); + + // splat the secret level? + if (::g->wbs->didsecret) + WI_drawOnLnode(8, &::g->splat); + + // draw flashing ptr + if (::g->snl_pointeron) + WI_drawOnLnode(::g->wbs->next, ::g->yah); + } + + // draws which level you are entering.. + if ( (::g->gamemode != commercial) + || ::g->wbs->next != 30) + WI_drawEL(); + +} + +void WI_drawNoState(void) +{ + ::g->snl_pointeron = true; + WI_drawShowNextLoc(); +} + +int WI_fragSum(int playernum) +{ + int i; + int frags = 0; + + for (i=0 ; iplayeringame[i] &&*/ i!=playernum) + { + frags += plrs[playernum].frags[i]; + } + } + + + // JDC hack - negative frags. + frags -= plrs[playernum].frags[playernum]; + // UNUSED if (frags < 0) + // frags = 0; + + return frags; +} + +int WI_fragOnlySum(int playernum) +{ + int i; + int frags = 0; + + for (i=0 ; iplayeringame[i] &&*/ i!=playernum) + { + frags += plrs[playernum].frags[i]; + } + } + + return frags; +} + +int WI_deathSum(int playernum) +{ + int i; + int deaths = 0; + + for (i=0 ; iplayeringame[i]*/) + { + deaths += plrs[i].frags[playernum]; + } + } + + return deaths; +} + + + + + + +void WI_initDeathmatchStats(void) +{ + + int i; + int j; + + ::g->state = StatCount; + ::g->acceleratestage = 0; + ::g->dm_state = 1; + + ::g->cnt_pause = TICRATE; + + for (i=0 ; iplayeringame[i]) + { + for (j=0 ; jplayeringame[j]) + ::g->dm_frags[i][j] = 0; + + ::g->dm_totals[i] = 0; + } + } + + WI_initAnimatedBack(); + + if ( common->IsMultiplayer() ) { + localCalculateAchievements(false); + + /* JAF PS3 + gameLocal->liveSession.GetDMSession().SetEndOfMatchStats(); + gameLocal->liveSession.GetDMSession().WriteTrueskill(); + + // Write stats + if ( gameLocal->liveSession.IsHost( ::g->consoleplayer ) ) { + gameLocal->liveSession.GetDMSession().BeginEndLevel(); + } + */ + } + + DoomLib::ShowXToContinue( true ); +} + + + +void WI_updateDeathmatchStats(void) +{ + + int i; + int j; + + qboolean stillticking; + + WI_updateAnimatedBack(); + + if (::g->acceleratestage && ::g->dm_state != 4) + { + ::g->acceleratestage = 0; + + for (i=0 ; iplayeringame[i]) + { + for (j=0 ; jplayeringame[j]) + ::g->dm_frags[i][j] = plrs[i].frags[j]; + + ::g->dm_totals[i] = WI_fragSum(i); + } + } + + + S_StartSound(0, sfx_barexp); + ::g->dm_state = 4; + } + + + if (::g->dm_state == 2) + { + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; iplayeringame[i]) + { + for (j=0 ; jplayeringame[j] + && ::g->dm_frags[i][j] != plrs[i].frags[j]) + { + if (plrs[i].frags[j] < 0) + ::g->dm_frags[i][j]--; + else + ::g->dm_frags[i][j]++; + + if (::g->dm_frags[i][j] > 99) + ::g->dm_frags[i][j] = 99; + + if (::g->dm_frags[i][j] < -99) + ::g->dm_frags[i][j] = -99; + + stillticking = true; + } + } + ::g->dm_totals[i] = WI_fragSum(i); + + if (::g->dm_totals[i] > 99) + ::g->dm_totals[i] = 99; + + if (::g->dm_totals[i] < -99) + ::g->dm_totals[i] = -99; + } + + } + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ::g->dm_state++; + } + + } + else if (::g->dm_state == 4) + { + if (::g->acceleratestage) + { + if ( !::g->demoplayback && ( ::g->usergame || ::g->netgame ) ) { + // This sound plays repeatedly after a player continues at the end of a deathmatch, + // and sounds bad. Quick fix is to just not play it. + //S_StartSound(0, sfx_slop); + + DoomLib::HandleEndMatch(); + } + } + } + else if (::g->dm_state & 1) + { + if (!--::g->cnt_pause) + { + ::g->dm_state++; + ::g->cnt_pause = TICRATE; + } + } +} + + + +void WI_drawDeathmatchStats(void) +{ + + int i; + int j; + int x; + int y; + int w; + + int lh; // line height + + lh = WI_SPACINGY; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + WI_drawLF(); + + // draw stat titles (top line) + V_DrawPatch(DM_TOTALSX-SHORT(::g->total->width)/2, + DM_MATRIXY-WI_SPACINGY+10, + FB, + ::g->total); + + V_DrawPatch(DM_KILLERSX, DM_KILLERSY, FB, ::g->killers); + V_DrawPatch(DM_VICTIMSX, DM_VICTIMSY, FB, ::g->victims); + + // draw P? + x = DM_MATRIXX + DM_SPACINGX; + y = DM_MATRIXY; + + for (i=0 ; iplayeringame[i]) + { + V_DrawPatch(x-SHORT(::g->wistuff_p[i]->width)/2, + DM_MATRIXY - WI_SPACINGY, + FB, + ::g->wistuff_p[i]); + + V_DrawPatch(DM_MATRIXX-SHORT(::g->wistuff_p[i]->width)/2, + y, + FB, + ::g->wistuff_p[i]); + + // No splitscreen on PC currently + if (i == ::g->me /* && !gameLocal->IsSplitscreen() */ ) + { + V_DrawPatch(x-SHORT(::g->wistuff_p[i]->width)/2, + DM_MATRIXY - WI_SPACINGY, + FB, + ::g->bstar); + + V_DrawPatch(DM_MATRIXX-SHORT(::g->wistuff_p[i]->width)/2, + y, + FB, + ::g->star); + } + } + else + { + //V_DrawPatch(x-SHORT(::g->wistuff_bp[i]->width)/2, + // DM_MATRIXY - WI_SPACINGY, FB, ::g->wistuff_bp[i]); + //V_DrawPatch(DM_MATRIXX-SHORT(::g->wistuff_bp[i]->width)/2, + // y, FB, ::g->wistuff_bp[i]); + } + x += DM_SPACINGX; + y += WI_SPACINGY; + } + + // draw stats + y = DM_MATRIXY+10; + w = SHORT(::g->num[0]->width); + + for (i=0 ; iplayeringame[i]) + { + for (j=0 ; jplayeringame[j]) + WI_drawNum(x+w, y, ::g->dm_frags[i][j], 2); + + x += DM_SPACINGX; + } + WI_drawNum(DM_TOTALSX+w, y, ::g->dm_totals[i], 2); + } + y += WI_SPACINGY; + } +} + + +void WI_initNetgameStats(void) +{ + + int i; + + ::g->state = StatCount; + ::g->acceleratestage = 0; + ::g->ng_state = 1; + + ::g->cnt_pause = TICRATE; + + for (i=0 ; iplayeringame[i]) + continue; + + ::g->cnt_kills[i] = ::g->cnt_items[i] = ::g->cnt_secret[i] = ::g->cnt_frags[i] = 0; + + ::g->dofrags += WI_fragSum(i); + } + + ::g->dofrags = !!::g->dofrags; + + WI_initAnimatedBack(); + + // JAF PS3 + /* + if ( gameLocal->IsMultiplayer() ) { + if(gameLocal->IsFullVersion() && gameLocal->liveSession.IsHost( ::g->consoleplayer )) { + bool endOfMission = false; + + if ( ::g->gamemission == 0 && ::g->gamemap == 30 ) { + endOfMission = true; + } + else if ( ::g->gamemission > 0 && ::g->gamemap == 8 ) { + endOfMission = true; + } + + gameLocal->liveSession.GetCoopSession().BeginEndLevel( endOfMission ); + } + } + */ + + DoomLib::ShowXToContinue( true ); + +} + + + +void WI_updateNetgameStats(void) +{ + + int i; + int fsum; + + qboolean stillticking; + + WI_updateAnimatedBack(); + + if (::g->acceleratestage && ::g->ng_state != 10) + { + ::g->acceleratestage = 0; + + for (i=0 ; iplayeringame[i]) + continue; + + ::g->cnt_kills[i] = (plrs[i].skills * 100) / ::g->wbs->maxkills; + ::g->cnt_items[i] = (plrs[i].sitems * 100) / ::g->wbs->maxitems; + ::g->cnt_secret[i] = (plrs[i].ssecret * 100) / ::g->wbs->maxsecret; + + if (::g->dofrags) + ::g->cnt_frags[i] = WI_fragSum(i); + } + S_StartSound(0, sfx_barexp); + ::g->ng_state = 10; + } + + if (::g->ng_state == 2) + { + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; iplayeringame[i]) + continue; + + ::g->cnt_kills[i] += 2; + + if (::g->cnt_kills[i] >= (plrs[i].skills * 100) / ::g->wbs->maxkills) + ::g->cnt_kills[i] = (plrs[i].skills * 100) / ::g->wbs->maxkills; + else + stillticking = true; + } + + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ::g->ng_state++; + } + } + else if (::g->ng_state == 4) + { + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; iplayeringame[i]) + continue; + + ::g->cnt_items[i] += 2; + if (::g->cnt_items[i] >= (plrs[i].sitems * 100) / ::g->wbs->maxitems) + ::g->cnt_items[i] = (plrs[i].sitems * 100) / ::g->wbs->maxitems; + else + stillticking = true; + } + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ::g->ng_state++; + } + } + else if (::g->ng_state == 6) + { + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; iplayeringame[i]) + continue; + + ::g->cnt_secret[i] += 2; + + if (::g->cnt_secret[i] >= (plrs[i].ssecret * 100) / ::g->wbs->maxsecret) + ::g->cnt_secret[i] = (plrs[i].ssecret * 100) / ::g->wbs->maxsecret; + else + stillticking = true; + } + + if (!stillticking) + { + S_StartSound(0, sfx_barexp); + ::g->ng_state += 1 + 2*!::g->dofrags; + } + } + else if (::g->ng_state == 8) + { + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + stillticking = false; + + for (i=0 ; iplayeringame[i]) + continue; + + ::g->cnt_frags[i] += 1; + + if (::g->cnt_frags[i] >= (fsum = WI_fragSum(i))) + ::g->cnt_frags[i] = fsum; + else + stillticking = true; + } + + if (!stillticking) + { + S_StartSound(0, sfx_pldeth); + ::g->ng_state++; + } + } + else if (::g->ng_state == 10) + { + if (::g->acceleratestage) + { + if ( !::g->demoplayback && ( ::g->usergame || ::g->netgame ) ) { + S_StartSound(0, sfx_sgcock); + + // need to do this again if they buy it + localCalculateAchievements(false); + if (::g->gamemode == commercial){ + WI_initNoState(); + DoomLib::ShowXToContinue( false ); + } + else{ + WI_initShowNextLoc(); + } + } + } + } + else if (::g->ng_state & 1) + { + if (!--::g->cnt_pause) + { + ::g->ng_state++; + ::g->cnt_pause = TICRATE; + } + } +} + + + +void WI_drawNetgameStats(void) +{ + int i; + int x; + int y; + int pwidth = SHORT(::g->percent->width); + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + WI_drawLF(); + + // draw stat titles (top line) + V_DrawPatch(NG_STATSX+NG_SPACINGX-SHORT(::g->kills->width), + NG_STATSY, FB, ::g->kills); + + V_DrawPatch(NG_STATSX+2*NG_SPACINGX-SHORT(::g->items->width), + NG_STATSY, FB, ::g->items); + + V_DrawPatch(NG_STATSX+3*NG_SPACINGX-SHORT(::g->secret->width), + NG_STATSY, FB, ::g->secret); + + if (::g->dofrags) + V_DrawPatch(NG_STATSX+4*NG_SPACINGX-SHORT(::g->wistuff_frags->width), + NG_STATSY, FB, ::g->wistuff_frags); + + // draw stats + y = NG_STATSY + SHORT(::g->kills->height); + + for (i=0 ; iplayeringame[i]) + continue; + + x = NG_STATSX; + V_DrawPatch(x-SHORT(::g->wistuff_p[i]->width), y, FB, ::g->wistuff_p[i]); + + // No splitscreen on PC + if (i == ::g->me /* && !gameLocal->IsSplitscreen() */ ) + V_DrawPatch(x-SHORT(::g->wistuff_p[i]->width), y, FB, ::g->star); + + x += NG_SPACINGX; + WI_drawPercent(x-pwidth, y+10, ::g->cnt_kills[i]); x += NG_SPACINGX; + WI_drawPercent(x-pwidth, y+10, ::g->cnt_items[i]); x += NG_SPACINGX; + WI_drawPercent(x-pwidth, y+10, ::g->cnt_secret[i]); x += NG_SPACINGX; + + if (::g->dofrags) + WI_drawNum(x, y+10, ::g->cnt_frags[i], -1); + + y += WI_SPACINGY; + } + +} + + +void WI_initStats(void) +{ + ::g->state = StatCount; + ::g->acceleratestage = 0; + ::g->sp_state = 1; + ::g->cnt_kills[0] = ::g->cnt_items[0] = ::g->cnt_secret[0] = -1; + ::g->cnt_time = ::g->cnt_par = -1; + ::g->cnt_pause = TICRATE; + + WI_initAnimatedBack(); + + DoomLib::ShowXToContinue( true ); +} + +void WI_updateStats(void) +{ + + WI_updateAnimatedBack(); + + if (::g->acceleratestage && ::g->sp_state != 10) + { + ::g->acceleratestage = 0; + ::g->cnt_kills[0] = (plrs[::g->me].skills * 100) / ::g->wbs->maxkills; + ::g->cnt_items[0] = (plrs[::g->me].sitems * 100) / ::g->wbs->maxitems; + ::g->cnt_secret[0] = (plrs[::g->me].ssecret * 100) / ::g->wbs->maxsecret; + ::g->cnt_time = plrs[::g->me].stime / TICRATE; + ::g->cnt_par = ::g->wbs->partime / TICRATE; + S_StartSound(0, sfx_barexp); + ::g->sp_state = 10; + } + + if (::g->sp_state == 2) + { + ::g->cnt_kills[0] += 2; + + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + if (::g->cnt_kills[0] >= (plrs[::g->me].skills * 100) / ::g->wbs->maxkills) + { + ::g->cnt_kills[0] = (plrs[::g->me].skills * 100) / ::g->wbs->maxkills; + S_StartSound(0, sfx_barexp); + ::g->sp_state++; + } + } + else if (::g->sp_state == 4) + { + ::g->cnt_items[0] += 2; + + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + if (::g->cnt_items[0] >= (plrs[::g->me].sitems * 100) / ::g->wbs->maxitems) + { + ::g->cnt_items[0] = (plrs[::g->me].sitems * 100) / ::g->wbs->maxitems; + S_StartSound(0, sfx_barexp); + ::g->sp_state++; + } + } + else if (::g->sp_state == 6) + { + ::g->cnt_secret[0] += 2; + + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + if (::g->cnt_secret[0] >= (plrs[::g->me].ssecret * 100) / ::g->wbs->maxsecret) + { + ::g->cnt_secret[0] = (plrs[::g->me].ssecret * 100) / ::g->wbs->maxsecret; + S_StartSound(0, sfx_barexp); + ::g->sp_state++; + } + } + + else if (::g->sp_state == 8) + { + if (!(::g->bcnt&3)) + S_StartSound(0, sfx_pistol); + + ::g->cnt_time += 3; + + if (::g->cnt_time >= plrs[::g->me].stime / TICRATE) + ::g->cnt_time = plrs[::g->me].stime / TICRATE; + + ::g->cnt_par += 3; + + if (::g->cnt_par >= ::g->wbs->partime / TICRATE) + { + ::g->cnt_par = ::g->wbs->partime / TICRATE; + + if (::g->cnt_time >= plrs[::g->me].stime / TICRATE) + { + S_StartSound(0, sfx_barexp); + ::g->sp_state++; + } + } + } + else if (::g->sp_state == 10) + { + if (::g->acceleratestage) + { + if ( !::g->demoplayback && ( ::g->usergame || ::g->netgame ) ) { + + S_StartSound(0, sfx_sgcock); + + // need to do this again if they buy it + localCalculateAchievements(false); + + if (::g->gamemode == commercial) { + WI_initNoState(); + } + else{ + WI_initShowNextLoc(); + } + } + } + } + else if (::g->sp_state & 1) + { + if (!--::g->cnt_pause) + { + ::g->sp_state++; + ::g->cnt_pause = TICRATE; + } + } + +} + +void WI_drawStats(void) +{ + // line height + int lh; + + lh = (3*SHORT(::g->num[0]->height))/2; + + WI_slamBackground(); + + // draw animated background + WI_drawAnimatedBack(); + + WI_drawLF(); + + V_DrawPatch(SP_STATSX, SP_STATSY, FB, ::g->kills); + WI_drawPercent(ORIGINAL_WIDTH - SP_STATSX, SP_STATSY, ::g->cnt_kills[0]); + + V_DrawPatch(SP_STATSX, SP_STATSY+lh, FB, ::g->items); + WI_drawPercent(ORIGINAL_WIDTH - SP_STATSX, SP_STATSY+lh, ::g->cnt_items[0]); + + V_DrawPatch(SP_STATSX, SP_STATSY+2*lh, FB, ::g->sp_secret); + WI_drawPercent(ORIGINAL_WIDTH - SP_STATSX, SP_STATSY+2*lh, ::g->cnt_secret[0]); + + V_DrawPatch(SP_TIMEX, SP_TIMEY, FB, ::g->time); + WI_drawTime(ORIGINAL_WIDTH/2 - SP_TIMEX, SP_TIMEY, ::g->cnt_time); + + // DHM - Nerve :: Added episode 4 par times + //if (::g->wbs->epsd < 3) + //{ + V_DrawPatch(ORIGINAL_WIDTH/2 + SP_TIMEX, SP_TIMEY, FB, ::g->par); + WI_drawTime(ORIGINAL_WIDTH - SP_TIMEX, SP_TIMEY, ::g->cnt_par); + //} + +} + +void WI_checkForAccelerate(void) +{ + int i; + player_t *player; + + // check for button presses to skip delays + for (i=0, player = ::g->players ; iplayeringame[i]) + { + if (player->cmd.buttons & BT_ATTACK) + { + if (!player->attackdown) { + ::g->acceleratestage = 1; + } + player->attackdown = true; + } else { + player->attackdown = false; + } + if (player->cmd.buttons & BT_USE) + { + if (!player->usedown) { + ::g->acceleratestage = 1; + } + player->usedown = true; + + } else { + player->usedown = false; + } + } + } +} + + + +// Updates stuff each tick +void WI_Ticker(void) +{ + // counter for general background animation + ::g->bcnt++; + + if (::g->bcnt == 1) + { + // intermission music + if ( ::g->gamemode == commercial ) + S_ChangeMusic(mus_dm2int, true); + else + S_ChangeMusic(mus_inter, true); + } + + WI_checkForAccelerate(); + + switch (::g->state) + { + case StatCount: + if (::g->deathmatch) WI_updateDeathmatchStats(); + else if (::g->netgame) WI_updateNetgameStats(); + else WI_updateStats(); + break; + + case ShowNextLoc: + WI_updateShowNextLoc(); + break; + + case NoState: + WI_updateNoState(); + break; + } + +} + +void WI_loadData(void) +{ + int i; + int j; + char name[9]; + anim_t* a; + + if (::g->gamemode == commercial) + strcpy(name, "INTERPIC"); + // DHM - Nerve :: Use our background image + //strcpy(name, "DMENUPIC"); + else + sprintf(name, "WIMAP%d", ::g->wbs->epsd); + + if ( ::g->gamemode == retail ) + { + if (::g->wbs->epsd == 3) + strcpy(name,"INTERPIC"); + } + + // background + ::g->bg = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + + V_DrawPatch(0, 0, 1, ::g->bg); + + + // UNUSED unsigned char *pic = ::g->screens[1]; + // if (::g->gamemode == commercial) + // { + // darken the background image + // while (pic != ::g->screens[1] + SCREENHEIGHT*SCREENWIDTH) + // { + // *pic = ::g->colormaps[256*25 + *pic]; + // pic++; + // } + //} + + if (::g->gamemode == commercial) + { + ::g->NUMCMAPS = 32; + ::g->lnames = (patch_t **) DoomLib::Z_Malloc(sizeof(patch_t*) * ::g->NUMCMAPS, PU_LEVEL_SHARED, 0); + for (i=0 ; i < ::g->NUMCMAPS ; i++) + { + sprintf(name, "CWILV%2.2d", i); + ::g->lnames[i] = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + } + } + else + { + ::g->lnames = (patch_t **) DoomLib::Z_Malloc(sizeof(patch_t*) * ( NUMMAPS ), PU_LEVEL_SHARED, 0); + for (i=0 ; iwbs->epsd, i); + ::g->lnames[i] = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + } + + // you are here + ::g->yah[0] = (patch_t*)W_CacheLumpName("WIURH0", PU_LEVEL_SHARED); + + // you are here (alt.) + ::g->yah[1] = (patch_t*)W_CacheLumpName("WIURH1", PU_LEVEL_SHARED); + + // splat + ::g->splat = (patch_t*)W_CacheLumpName("WISPLAT", PU_LEVEL_SHARED); + + if (::g->wbs->epsd < 3) + { + for (j=0;j < ::g->NUMANIMS[::g->wbs->epsd];j++) + { + a = &::g->wi_stuff_anims[::g->wbs->epsd][j]; + for (i=0;inanims;i++) + { + // MONDO HACK! + if (::g->wbs->epsd != 1 || j != 8) + { + // animations + sprintf(name, "WIA%d%.2d%.2d", ::g->wbs->epsd, j, i); + a->p[i] = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + } + else + { + // HACK ALERT! + a->p[i] = ::g->wi_stuff_anims[1][4].p[i]; + } + } + } + } + } + + // More hacks on minus sign. + ::g->wiminus = (patch_t*)W_CacheLumpName("WIMINUS", PU_LEVEL_SHARED); + + for (i=0;i<10;i++) + { + // numbers 0-9 + sprintf(name, "WINUM%d", i); + ::g->num[i] = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + } + + // percent sign + ::g->percent = (patch_t*)W_CacheLumpName("WIPCNT", PU_LEVEL_SHARED); + + // "finished" + ::g->finished = (patch_t*)W_CacheLumpName("WIF", PU_LEVEL_SHARED); + + // "entering" + ::g->entering = (patch_t*)W_CacheLumpName("WIENTER", PU_LEVEL_SHARED); + + // "kills" + ::g->kills = (patch_t*)W_CacheLumpName("WIOSTK", PU_LEVEL_SHARED); + + // "scrt" + ::g->secret = (patch_t*)W_CacheLumpName("WIOSTS", PU_LEVEL_SHARED); + + // "secret" + ::g->sp_secret = (patch_t*)W_CacheLumpName("WISCRT2", PU_LEVEL_SHARED); + + ::g->items = (patch_t*)W_CacheLumpName("WIOSTI", PU_LEVEL_SHARED); + + // "frgs" + ::g->wistuff_frags = (patch_t*)W_CacheLumpName("WIFRGS", PU_LEVEL_SHARED); + + // ":" + ::g->colon = (patch_t*)W_CacheLumpName("WICOLON", PU_LEVEL_SHARED); + + // "time" + ::g->time = (patch_t*)W_CacheLumpName("WITIME", PU_LEVEL_SHARED); + + // "sucks" + ::g->sucks = (patch_t*)W_CacheLumpName("WISUCKS", PU_LEVEL_SHARED); + + // "par" + ::g->par = (patch_t*)W_CacheLumpName("WIPAR", PU_LEVEL_SHARED); + + // "killers" (vertical) + ::g->killers = (patch_t*)W_CacheLumpName("WIKILRS", PU_LEVEL_SHARED); + + // "victims" (horiz) + ::g->victims = (patch_t*)W_CacheLumpName("WIVCTMS", PU_LEVEL_SHARED); + + // "total" + ::g->total = (patch_t*)W_CacheLumpName("WIMSTT", PU_LEVEL_SHARED); + + // your face + ::g->star = (patch_t*)W_CacheLumpName("STFST01", PU_STATIC_SHARED); // ALAN: this is statically in the game... + + // dead face + ::g->bstar = (patch_t*)W_CacheLumpName("STFDEAD0", PU_STATIC_SHARED); + + for (i=0 ; iwistuff_p[i] = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + + // "1,2,3,4" + sprintf(name, "WIBP%d", i+1); + ::g->wistuff_bp[i] = (patch_t*)W_CacheLumpName(name, PU_LEVEL_SHARED); + } + +} + +void WI_unloadData(void) +{ + Z_FreeTags( PU_LEVEL_SHARED, PU_LEVEL_SHARED ); + // HACK ALERT - reset these to help stability? they are used for consistency checking + for (int i=0 ; iplayeringame[i]) + { + ::g->players[i].mo = NULL; + } + } + ::g->bg = NULL; +} + +void WI_Drawer (void) +{ + switch (::g->state) + { + case StatCount: + if (::g->deathmatch) + WI_drawDeathmatchStats(); + else if (::g->netgame) + WI_drawNetgameStats(); + else + WI_drawStats(); + break; + + case ShowNextLoc: + WI_drawShowNextLoc(); + break; + + case NoState: + WI_drawNoState(); + break; + } +} + + +void WI_initVariables(wbstartstruct_t* wbstartstruct) +{ + + ::g->wbs = wbstartstruct; + +#ifdef RANGECHECKING + if (::g->gamemode != commercial) + { + if ( ::g->gamemode == retail ) + RNGCHECK(::g->wbs->epsd, 0, 3); + else + RNGCHECK(::g->wbs->epsd, 0, 2); + } + else + { + RNGCHECK(::g->wbs->last, 0, 8); + RNGCHECK(::g->wbs->next, 0, 8); + } + RNGCHECK(::g->wbs->pnum, 0, MAXPLAYERS); + RNGCHECK(::g->wbs->pnum, 0, MAXPLAYERS); +#endif + + ::g->acceleratestage = 0; + ::g->cnt = ::g->bcnt = 0; + ::g->firstrefresh = 1; + ::g->me = ::g->wbs->pnum; + plrs = ::g->wbs->plyr; + + if (!::g->wbs->maxkills) + ::g->wbs->maxkills = 1; + + if (!::g->wbs->maxitems) + ::g->wbs->maxitems = 1; + + if (!::g->wbs->maxsecret) + ::g->wbs->maxsecret = 1; + + if ( ::g->gamemode != retail ) + if (::g->wbs->epsd > 2) + ::g->wbs->epsd -= 3; +} + +void WI_Start(wbstartstruct_t* wbstartstruct) +{ + + WI_initVariables(wbstartstruct); + WI_loadData(); + + if (::g->deathmatch) + WI_initDeathmatchStats(); + else if (::g->netgame) + WI_initNetgameStats(); + else + WI_initStats(); +} diff --git a/doomclassic/doom/wi_stuff.h b/doomclassic/doom/wi_stuff.h new file mode 100644 index 00000000..176e32bc --- /dev/null +++ b/doomclassic/doom/wi_stuff.h @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __WI_STUFF__ +#define __WI_STUFF__ + +//#include "v_video.h" + +#include "doomdef.h" + +// Called by main loop, animate the intermission. +void WI_Ticker (void); + +// Called by main loop, +// draws the intermission directly into the screen buffer. +void WI_Drawer (void); + +// Setup for an intermission screen. +void WI_Start(wbstartstruct_t* wbstartstruct); + +#endif + diff --git a/doomclassic/doom/z_zone.cpp b/doomclassic/doom/z_zone.cpp new file mode 100644 index 00000000..8284bfdf --- /dev/null +++ b/doomclassic/doom/z_zone.cpp @@ -0,0 +1,477 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "Precompiled.h" + +#include "z_zone.h" +#include "i_system.h" +#include "doomdef.h" +#include "globaldata.h" + + +// +// ZONE MEMORY ALLOCATION +// +// There is never any space between memblocks, +// and there will never be two contiguous free memblocks. +// The rover can be left pointing at a non-empty block. +// +// It is of no value to free a cachable block, +// because it will get overwritten automatically if needed. +// + +#define ZONEID 0x1d4a11 + + +// +// Z_ClearZone +// +void Z_ClearZone (memzone_t* zone) +{ + memblock_t* block; + + // set the entire zone to one free block + zone->blocklist.next = + zone->blocklist.prev = + block = (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + + zone->blocklist.user = (void **)zone; + zone->blocklist.tag = PU_STATIC; + zone->rover = block; + + block->prev = block->next = &zone->blocklist; + + // NULL indicates a free block. + block->user = NULL; + + block->size = zone->size - sizeof(memzone_t); +} + +void *I_ZoneBase( int *size ) +{ + enum + { + HEAP_SIZE = 15 * 1024 * 1024 // SMF - was 10 * 1024 * 1024 + }; + *size = HEAP_SIZE; + return malloc( HEAP_SIZE ); +} + +// +// Z_Init +// +void Z_Init (void) +{ + memblock_t* block; + int size; + + ::g->mainzone = (memzone_t *)I_ZoneBase (&size); + + memset( ::g->mainzone, 0, size ); + ::g->mainzone->size = size; + + // set the entire zone to one free block + ::g->mainzone->blocklist.next = + ::g->mainzone->blocklist.prev = + block = (memblock_t *)( (byte *)::g->mainzone + sizeof(memzone_t) ); + + ::g->mainzone->blocklist.user = (void **)::g->mainzone; + ::g->mainzone->blocklist.tag = PU_STATIC; + ::g->mainzone->rover = block; + + block->prev = block->next = &::g->mainzone->blocklist; + + // NULL indicates a free block. + block->user = NULL; + + block->size = ::g->mainzone->size - sizeof(memzone_t); +} + +int NumAlloc = 0; + +// +// Z_Free +// +void Z_Free (void* ptr) +{ + memblock_t* block; + memblock_t* other; + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + + NumAlloc -= block->size; + + if (block->id != ZONEID) + I_Error ("Z_Free: freed a pointer without ZONEID"); + + if (block->user > (void **)0x100) + { + // smaller values are not pointers + // Note: OS-dependend? + + // clear the user's mark + *block->user = 0; + } + + // mark as free + block->user = NULL; + block->tag = 0; + block->id = 0; + + other = block->prev; + + if (!other->user) + { + // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + + if (block == ::g->mainzone->rover) + ::g->mainzone->rover = other; + + block = other; + } + + other = block->next; + if (!other->user) + { + // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + + if (other == ::g->mainzone->rover) + ::g->mainzone->rover = block; + } +} + + + +// +// Z_Malloc +// You can pass a NULL user if the tag is < PU_PURGELEVEL. +// +#define MINFRAGMENT 64 + +void* +Z_Malloc +( int size, + int tag, + void* user ) +{ + + int extra; + memblock_t* start; + memblock_t* rover; + memblock_t* newblock; + memblock_t* base; + NumAlloc += size; + + size = (size + 3) & ~3; + + // scan through the block list, + // looking for the first free block + // of sufficient size, + // throwing out any purgable blocks along the way. + + // account for size of block header + size += sizeof(memblock_t); + + // if there is a free block behind the rover, + // back up over them + base = ::g->mainzone->rover; + + if (!base->prev->user) + base = base->prev; + + rover = base; + start = base->prev; + + do + { + if (rover == start) + { + // scanned all the way around the list + I_Error ("Z_Malloc: failed on allocation of %i bytes", size); + } + + if (rover->user) + { + if (rover->tag < PU_PURGELEVEL) + { + // hit a block that can't be purged, + // so move base past it + base = rover = rover->next; + } + else + { + // free the rover block (adding the size to base) + + // the rover can be the base block + base = base->prev; + Z_Free ((byte *)rover+sizeof(memblock_t)); + base = base->next; + rover = base->next; + } + } + else + rover = rover->next; + } while (base->user || base->size < size); + + + // found a block big enough + extra = base->size - size; + + if (extra > MINFRAGMENT) + { + // there will be a free fragment after the allocated block + newblock = (memblock_t *) ((byte *)base + size ); + newblock->size = extra; + + // NULL indicates free block. + newblock->user = NULL; + newblock->tag = 0; + newblock->prev = base; + newblock->next = base->next; + newblock->next->prev = newblock; + + base->next = newblock; + base->size = size; + } + + if (user) + { + // mark as an in use block + base->user = (void**)user; + *(void **)user = (void *) ((byte *)base + sizeof(memblock_t)); + } + else + { + if (tag >= PU_PURGELEVEL) + I_Error ("Z_Malloc: an owner is required for purgable blocks"); + + // mark as in use, but unowned + base->user = (void **)2; + } + base->tag = tag; + + // next allocation will start looking here + ::g->mainzone->rover = base->next; + + base->id = ZONEID; + + return (void *) ((byte *)base + sizeof(memblock_t)); +} + + + +// +// Z_FreeTags +// +void +Z_FreeTags +( int lowtag, + int hightag ) +{ + memblock_t* block; + memblock_t* next; + + for (block = ::g->mainzone->blocklist.next ; + block != &::g->mainzone->blocklist ; + block = next) + { + // get link before freeing + next = block->next; + + // free block? + if (!block->user) + continue; + + if (block->tag >= lowtag && block->tag <= hightag) + Z_Free ( (byte *)block+sizeof(memblock_t)); + } +} + + + +// +// Z_DumpHeap +// Note: TFileDumpHeap( stdout ) ? +// +void +Z_DumpHeap +( int lowtag, + int hightag ) +{ + memblock_t* block; + + I_Printf ("zone size: %i location: %p\n", + ::g->mainzone->size,::g->mainzone); + + I_Printf ("tag range: %i to %i\n", + lowtag, hightag); + + for (block = ::g->mainzone->blocklist.next ; ; block = block->next) + { + if (block->tag >= lowtag && block->tag <= hightag) + I_Printf ("block:%p size:%7i user:%p tag:%3i\n", + block, block->size, block->user, block->tag); + + if (block->next == &::g->mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + I_Printf ("ERROR: block size does not touch the next block\n"); + + if ( block->next->prev != block) + I_Printf ("ERROR: next block doesn't have proper back link\n"); + + if (!block->user && !block->next->user) + I_Printf ("ERROR: two consecutive free blocks\n"); + } +} + + +// +// Z_FileDumpHeap +// +void Z_FileDumpHeap (FILE* f) +{ + memblock_t* block; + + fprintf (f,"zone size: %i location: %p\n",::g->mainzone->size,::g->mainzone); + + for (block = ::g->mainzone->blocklist.next ; ; block = block->next) + { + fprintf (f,"block:%p size:%7i user:%p tag:%3i\n", + block, block->size, block->user, block->tag); + + if (block->next == &::g->mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + fprintf (f,"ERROR: block size does not touch the next block\n"); + + if ( block->next->prev != block) + fprintf (f,"ERROR: next block doesn't have proper back link\n"); + + if (!block->user && !block->next->user) + fprintf (f,"ERROR: two consecutive free blocks\n"); + } +} + + + +// +// Z_CheckHeap +// +void Z_CheckHeap (void) +{ + memblock_t* block; + + for (block = ::g->mainzone->blocklist.next ; ; block = block->next) + { + if (block->next == &::g->mainzone->blocklist) + { + // all blocks have been hit + break; + } + + if ( (byte *)block + block->size != (byte *)block->next) + I_Error ("Z_CheckHeap: block size does not touch the next block\n"); + + if ( block->next->prev != block) + I_Error ("Z_CheckHeap: next block doesn't have proper back link\n"); + + if (!block->user && !block->next->user) + I_Error ("Z_CheckHeap: two consecutive free blocks\n"); + } +} + + + + +// +// Z_ChangeTag +// +void +Z_ChangeTag2 +( void* ptr, + int tag ) +{ + memblock_t* block; + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + + if (block->id != ZONEID) + I_Error ("Z_ChangeTag: freed a pointer without ZONEID"); + + if (tag >= PU_PURGELEVEL && (unsigned)block->user < 0x100) + I_Error ("Z_ChangeTag: an owner is required for purgable blocks"); + + block->tag = tag; +} + +void Z_ChangeTag2( void** pp, int tag ) { Z_ChangeTag2( *pp, tag ); } + + +// +// Z_FreeMemory +// +int Z_FreeMemory (void) +{ + memblock_t* block; + int free; + + free = 0; + + for (block = ::g->mainzone->blocklist.next ; + block != &::g->mainzone->blocklist; + block = block->next) + { + if (!block->user || block->tag >= PU_PURGELEVEL) + free += block->size; + } + return free; +} + +/* +bool MallocForLump( int lump, unsigned int size, void **data, int tag ) +{ + *data = Z_Malloc( size, tag, 0 ); + + return true; +} +*/ diff --git a/doomclassic/doom/z_zone.h b/doomclassic/doom/z_zone.h new file mode 100644 index 00000000..cb25ae5b --- /dev/null +++ b/doomclassic/doom/z_zone.h @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __Z_ZONE__ +#define __Z_ZONE__ + +#include + +// +// ZONE MEMORY +// PU - purge tags. +// Tags < 100 are not overwritten until freed. +#define PU_STATIC 1 // static entire execution time +#define PU_SOUND 2 // static while playing +#define PU_MUSIC 3 // static while playing +#define PU_LEVEL 50 // static until level exited +#define PU_LEVSPEC 51 // a special thinker in a level +// Tags >= 100 are purgable whenever needed. +#define PU_PURGELEVEL 100 +#define PU_CACHE 101 + +/* +#define PU_STATIC_SHARED 4 // static entire execution time +#define PU_SOUND_SHARED 5 // static while playing +#define PU_MUSIC_SHARED 6 // static while playing +#define PU_LEVEL_SHARED 52 // static until level exited +#define PU_LEVSPEC_SHARED 53 // a special thinker in a level +#define PU_CACHE_SHARED 102 +*/ +#define PU_STATIC_SHARED PU_STATIC // static entire execution time +#define PU_SOUND_SHARED PU_SOUND // static while playing +#define PU_MUSIC_SHARED PU_MUSIC // static while playing +#define PU_LEVEL_SHARED PU_LEVEL // static until level exited +#define PU_LEVSPEC_SHARED PU_LEVSPEC // a special thinker in a level +#define PU_CACHE_SHARED PU_CACHE + + +bool Z_IsStatic( int tag ); + +void Z_Init (void); +void* Z_Malloc (int size, int tag, void *ptr); +void Z_Free (void *ptr); +void Z_FreeTag(int lowtag ); +void Z_FreeTags(int lowtag, int hightag ); +void Z_DumpHeap (int lowtag, int hightag); +void Z_FileDumpHeap (FILE *f); +void Z_CheckHeap (void); +void Z_ChangeTag2 (void **ptr, int tag); +int Z_FreeMemory (void); + + +//bool MallocForLump( int lump, size_t size, void **ptr, int tag ); + + +template< class _type_ > +bool MallocForLump( int lump, size_t size, _type_ * & ptr, int tag ) { + ptr = static_cast< _type_ * >( Z_Malloc( size, tag, 0 ) ); + + return true; +} + + +typedef struct memblock_s +{ + int size; // including the header and possibly tiny fragments + void** user; // NULL if a free block + int tag; // purgelevel + int id; // should be ZONEID + struct memblock_s* next; + struct memblock_s* prev; +} memblock_t; + + +// +// This is used to get the local FILE:LINE info from CPP +// prior to really call the function in question. +// +#define Z_ChangeTag(p,t) \ +{ \ + if (( (memblock_t *)( (byte *)(p) - sizeof(memblock_t)))->id!=0x1d4a11) \ + I_Error("Z_CT at "__FILE__":%i",__LINE__); \ + Z_ChangeTag2((void**)&p,t); \ +}; + + + +#endif + diff --git a/doomclassic/doomclassic.vcxproj b/doomclassic/doomclassic.vcxproj new file mode 100644 index 00000000..e3f1304f --- /dev/null +++ b/doomclassic/doomclassic.vcxproj @@ -0,0 +1,228 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {D87ADC61-3968-4A89-824F-01365AB6BD88} + Win32Proj + doomclassic + + + + + + + + + + + + StaticLibrary + true + Unicode + + + StaticLibrary + false + true + Unicode + + + + + + + + + + + + + + + $(VCInstallDir)PlatformSDK\include;$(ProjectDir)\..\neo\dxsdk_June2010\include;$(IncludePath) + + + $(VCInstallDir)PlatformSDK\include;$(ProjectDir)\..\neo\dxsdk_June2010\include;$(IncludePath) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebug + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + + + + + + \ No newline at end of file diff --git a/doomclassic/doomclassic.vcxproj.filters b/doomclassic/doomclassic.vcxproj.filters new file mode 100644 index 00000000..24047884 --- /dev/null +++ b/doomclassic/doomclassic.vcxproj.filters @@ -0,0 +1,411 @@ + + + + + {c3363280-c58f-49d0-95c0-eb5e3fecb552} + + + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + Doom + + + \ No newline at end of file diff --git a/doomclassic/doomclassic.vcxproj.vspscc b/doomclassic/doomclassic.vcxproj.vspscc new file mode 100644 index 00000000..b6d32892 --- /dev/null +++ b/doomclassic/doomclassic.vcxproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/doomclassic/timidity/FAQ b/doomclassic/timidity/FAQ new file mode 100644 index 00000000..f1f8e237 --- /dev/null +++ b/doomclassic/timidity/FAQ @@ -0,0 +1,112 @@ +---------------------------*-indented-text-*------------------------------ + + TiMidity -- Experimental MIDI to WAVE converter + Copyright (C) 1995 Tuukka Toivonen + +-------------------------------------------------------------------------- + + Frequently Asked Questions with answers: + +-------------------------------------------------------------------------- +Q: What is it? + +A: Where? Well Chris, TiMidity is a software-only synthesizer, MIDI + renderer, MIDI to WAVE converter, realtime MIDI player for UNIX machines, + even (I've heard) a Netscape helper application. It takes a MIDI file + and writes a WAVE or raw PCM data or plays it on your digital audio + device. It sounds much more realistic than FM synthesis, but you need a + ~100Mhz processor to listen to 32kHz stereo music in the background while + you work. 11kHz mono can be played on a low-end 486, and, to some, it + still sounds better than FM. + +-------------------------------------------------------------------------- +Q: I don't have a GUS, can I use TiMidity? + +A: Yes. That's the point. You don't need a Gravis Ultrasound to use + TiMidity, you just need GUS-compatible patches, which are freely + available on the Internet. See below for pointers. + +-------------------------------------------------------------------------- +Q: I have a GUS, can I use TiMidity? + +A: The DOS port doesn't have GUS support, and TiMidity won't be taking + advantage of the board's internal synthesizer under other operating + systems either. So it kind of defeats the purpose. But you can use it. + +-------------------------------------------------------------------------- +Q: I tried playing a MIDI file I got off the Net but all I got was a + dozen warnings saying "No instrument mapped to tone bank 0, program + xx - this instrument will not be heard". What's wrong? + +A: The General MIDI standard specifies 128 melodic instruments and + some sixty percussion sounds. If you wish to play arbitrary General + MIDI files, you'll need to get more patch files. + + There's a program called Midia for SGI's, which also plays MIDI + files and has a lot more bells and whistles than TiMidity. It uses + GUS-compatible patches, too -- so you can get the 8 MB set at + ftp://archive.cs.umbc.edu/pub/midia for pretty good GM compatibility. + + There are also many excellent patches on the Ultrasound FTP sites. + I can recommend Dustin McCartney's collections gsdrum*.zip and + wow*.zip in the "[.../]sound/patches/files" directory. The huge + ProPats series (pp3-*.zip) contains good patches as well. General + MIDI files can also be found on these sites. + + This site list is from the GUS FAQ: + +> FTP Sites Archive Directories +> --------- ------------------- +> Main N.American Site: archive.orst.edu pub/packages/gravis +> wuarchive.wustl.edu systems/ibmpc/ultrasound +> Main Asian Site: nctuccca.edu.tw PC/ultrasound +> Main European Site: src.doc.ic.ac.uk packages/ultrasound +> Main Australian Site: ftp.mpx.com.au /ultrasound/general +> /ultrasound/submit +> South African Site: ftp.sun.ac.za /pub/packages/ultrasound +> Submissions: archive.epas.utoronto.ca pub/pc/ultrasound/submit +> Newly Validated Files: archive.epas.utoronto.ca pub/pc/ultrasound +> +> Mirrors: garbo.uwasa.fi mirror/ultrasound +> ftp.st.nepean.uws.edu.au pc/ultrasound +> ftp.luth.se pub/msdos/ultrasound + +-------------------------------------------------------------------------- +Q: Some files have awful clicks and pops. + +A: Find out which patch is responsible for the clicking (try "timidity + -P ". Add "strip=tail" in + the config file after its name. If this doesn't fix it, mail me the + patch. + +-------------------------------------------------------------------------- +Q: I'm playing Fantasie Impromptu in the background. When I run Netscape, + the sound gets choppy and it takes ten minutes to load. What can I do? + +A: Here are some things to try: + + - Use a lower sampling rate. + + - Use mono output. This can improve performance by 10-30%. + (Using 8-bit instead of 16-bit output makes no difference.) + + - Use a smaller number of simultaneous voices. + + - Make sure you compiled with FAST_DECAY and PRECALC_LOOPS enabled + in config.h + + - If you don't have hardware to compute sines, recompile with + LOOKUP_SINE enabled in config.h + + - Recompile with LOOKUP_HACK enabled in config.h. + + - Recompile with LINEAR_INTERPOLATION disabled in config.h. + + - Recompile with DANGEROUS_RENICE enabled in config.h, and make + TiMidity setuid root. This will help only if you frequently play + music while other processes are running. + + - Recompile with an Intel-optimized gcc for a 5-15% + performance increase. + +-------------------------------------------------------------------------- diff --git a/doomclassic/timidity/README b/doomclassic/timidity/README new file mode 100644 index 00000000..e428a611 --- /dev/null +++ b/doomclassic/timidity/README @@ -0,0 +1,60 @@ +[This version of timidity has been stripped for simplicity in porting to SDL] +---------------------------------*-text-*--------------------------------- + + From http://www.cgs.fi/~tt/discontinued.html : + + If you'd like to continue hacking on TiMidity, feel free. I'm + hereby extending the TiMidity license agreement: you can now + select the most convenient license for your needs from (1) the + GNU GPL, (2) the GNU LGPL, or (3) the Perl Artistic License. + +-------------------------------------------------------------------------- + + This is the README file for TiMidity v0.2i + + TiMidity is a MIDI to WAVE converter that uses Gravis +Ultrasound(*)-compatible patch files to generate digital audio data +from General MIDI files. The audio data can be played through any +sound device or stored on disk. On a fast machine, music can be +played in real time. TiMidity runs under Linux, FreeBSD, HP-UX, SunOS, and +Win32, and porting to other systems with gcc should be easy. + + TiMidity Features: + + * 32 or more dynamically allocated fully independent voices + * Compatibility with GUS patch files + * Output to 16- or 8-bit PCM or uLaw audio device, file, or + stdout at any sampling rate + * Optional interactive mode with real-time status display + under ncurses and SLang terminal control libraries. Also + a user friendly motif interface since version 0.2h + * Support for transparent loading of compressed MIDI files and + patch files + + * Support for the following MIDI events: + - Program change + - Key pressure + - Channel main volume + - Tempo + - Panning + - Damper pedal (Sustain) + - Pitch wheel + - Pitch wheel sensitivity + - Change drum set + +* The GNU General Public License can, as always, be found in the file + "../COPYING". + +* TiMidity requires sampled instruments (patches) to play MIDI files. You + should get the file "timidity-lib-0.1.tar.gz" and unpack it in the same + directory where you unpacked the source code archive. You'll want more + patches later -- read the file "FAQ" for pointers. + +* Timidity is no longer supported, but can be found by searching the web. + + + Tuukka Toivonen + +[(*) Any Registered Trademarks used anywhere in the documentation or +source code for TiMidity are acknowledged as belonging to their +respective owners.] diff --git a/doomclassic/timidity/common.cpp b/doomclassic/timidity/common.cpp new file mode 100644 index 00000000..bc80cc80 --- /dev/null +++ b/doomclassic/timidity/common.cpp @@ -0,0 +1,178 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +common.c + +*/ + +#include "../../neo/idlib/precompiled.h" + +#include +#include +#include + +#include +#include "config.h" +#include "common.h" +#include "output.h" +#include "controls.h" + +/* I guess "rb" should be right for any libc */ +#define OPEN_MODE "rb" + +char current_filename[1024]; + +#ifdef DEFAULT_PATH +/* The paths in this list will be tried whenever we're reading a file */ +static PathList defaultpathlist={DEFAULT_PATH,0}; +static PathList *pathlist=&defaultpathlist; /* This is a linked list */ +#else +static PathList *pathlist=0; +#endif + +/* Try to open a file for reading. If the filename ends in one of the +defined compressor extensions, pipe the file through the decompressor */ +static idFile * try_to_open(char *name, int decompress, int noise_mode) +{ + idFile * fp; + + fp = fileSystem->OpenFileRead( name ); + + if (!fp) + return 0; + + return fp; +} + +/* This is meant to find and open files for reading, possibly piping +them through a decompressor. */ +idFile * open_file(const char *name, int decompress, int noise_mode) +{ + idFile * fp; + PathList *plp=pathlist; + int l; + + if (!name || !(*name)) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Attempted to open nameless file."); + return 0; + } + + /* First try the given name */ + + strncpy(current_filename, name, 1023); + current_filename[1023]='\0'; + + ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Trying to open %s", current_filename); + if ((fp=try_to_open(current_filename, decompress, noise_mode))) + return fp; + + if (name[0] != PATH_SEP) + while (plp) /* Try along the path then */ + { + *current_filename=0; + l=strlen(plp->path); + if(l) + { + strcpy(current_filename, plp->path); + if(current_filename[l-1]!=PATH_SEP) + strcat(current_filename, PATH_STRING); + } + strcat(current_filename, name); + ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Trying to open %s", current_filename); + if ((fp=try_to_open(current_filename, decompress, noise_mode))) + return fp; + + plp=(PathList*)plp->next; + } + + /* Nothing could be opened. */ + + *current_filename=0; + + if (noise_mode>=2) + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s", name, strerror(errno)); + + return 0; +} + +/* This closes files opened with open_file */ +void close_file(idFile * fp) +{ + delete fp; +} + +/* This is meant for skipping a few bytes in a file or fifo. */ +void skip(idFile * fp, size_t len) +{ + size_t c; + char tmp[1024]; + while (len>0) + { + c=len; + if (c>1024) c=1024; + len-=c; + if (c!=fp->Read(tmp, c )) + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: skip: %s", + current_filename, strerror(errno)); + } +} + +//extern void *Real_Tim_Malloc( size_t ); +/* This'll allocate memory or die. */ +void *safe_malloc(size_t count) +{ + void *p; + if (count > (1<<21)) + { + ctl->cmsg(CMSG_FATAL, VERB_NORMAL, + "Strange, I feel like allocating %d bytes. This must be a bug.", + count); + } + else if ((p=Real_Tim_Malloc(count))) + return p; + else + ctl->cmsg(CMSG_FATAL, VERB_NORMAL, "Sorry. Couldn't malloc %d bytes.", count); + + ctl->close(); + //exit(10); + return(NULL); +} + +/* This adds a directory to the path list */ +void add_to_pathlist(char *s) +{ + PathList *plp=(PathList*)safe_malloc(sizeof(PathList)); + strcpy((plp->path=(char *)safe_malloc(strlen(s)+1)),s); + plp->next=pathlist; + pathlist=plp; +} + +/* Required memory management functions */ +void *Real_Tim_Malloc( int sz ) { + return malloc( sz ); +} + +void Real_Tim_Free( void *pt ) { + free( pt ); +} + +void* Real_Malloc( unsigned int sz ) { + return malloc( sz ); +} diff --git a/doomclassic/timidity/common.h b/doomclassic/timidity/common.h new file mode 100644 index 00000000..e3a5c151 --- /dev/null +++ b/doomclassic/timidity/common.h @@ -0,0 +1,42 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +common.h +*/ +class idFile; +extern char *program_name, current_filename[]; + +extern FILE *msgfp; + +typedef struct { + char *path; + void *next; +} PathList; + +/* Noise modes for open_file */ +#define OF_SILENT 0 +#define OF_NORMAL 1 +#define OF_VERBOSE 2 + +extern idFile * open_file(const char *name, int decompress, int noise_mode); +extern void add_to_pathlist(char *s); +extern void close_file(idFile * fp); +extern void skip(idFile * fp, size_t len); +extern void *safe_malloc(size_t count); diff --git a/doomclassic/timidity/config.h b/doomclassic/timidity/config.h new file mode 100644 index 00000000..e1b365c7 --- /dev/null +++ b/doomclassic/timidity/config.h @@ -0,0 +1,206 @@ +/* +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* This is for use with the SDL library */ +#define SDL +#if (defined(WIN32) || defined(_WIN32)) && !defined(__WIN32__) +#define __WIN32__ +#endif + +#define LITTLE_ENDIAN + +#include + +/* When a patch file can't be opened, one of these extensions is +appended to the filename and the open is tried again. +*/ +#define PATCH_EXT_LIST { ".pat", 0 } + +/* Acoustic Grand Piano seems to be the usual default instrument. */ +#define DEFAULT_PROGRAM 0 + +/* 9 here is MIDI channel 10, which is the standard percussion channel. +Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too. +On the other hand, some files know that 16 is not a drum channel and +try to play music on it. This is now a runtime option, so this isn't +a critical choice anymore. */ +//#define DEFAULT_DRUMCHANNELS ((1<<9) | (1<<15)) +#define DEFAULT_DRUMCHANNELS ((1<<9)) + +/* A somewhat arbitrary frequency range. The low end of this will +sound terrible as no lowpass filtering is performed on most +instruments before resampling. */ +#define MIN_OUTPUT_RATE 4000 +#define MAX_OUTPUT_RATE 65000 + +/* In percent. */ +#define DEFAULT_AMPLIFICATION 70 + +/* Default sampling rate, default polyphony, and maximum polyphony. +All but the last can be overridden from the command line. */ +#define DEFAULT_RATE 32000 +#define DEFAULT_VOICES 32 +#define MAX_VOICES 48 + +/* 1000 here will give a control ratio of 22:1 with 22 kHz output. +Higher CONTROLS_PER_SECOND values allow more accurate rendering +of envelopes and tremolo. The cost is CPU time. */ +#define CONTROLS_PER_SECOND 1000 + +/* Strongly recommended. This option increases CPU usage by half, but +without it sound quality is very poor. */ +#define LINEAR_INTERPOLATION + +/* This is an experimental kludge that needs to be done right, but if +you've got an 8-bit sound card, or cheap multimedia speakers hooked +to your 16-bit output device, you should definitely give it a try. + +Defining LOOKUP_HACK causes table lookups to be used in mixing +instead of multiplication. We convert the sample data to 8 bits at +load time and volumes to logarithmic 7-bit values before looking up +the product, which degrades sound quality noticeably. + +Defining LOOKUP_HACK should save ~20% of CPU on an Intel machine. +LOOKUP_INTERPOLATION might give another ~5% */ +/* #define LOOKUP_HACK +#define LOOKUP_INTERPOLATION */ + +/* Make envelopes twice as fast. Saves ~20% CPU time (notes decay +faster) and sounds more like a GUS. There is now a command line +option to toggle this as well. */ +#define FAST_DECAY + +/* How many bits to use for the fractional part of sample positions. +This affects tonal accuracy. The entire position counter must fit +in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of +a sample is 1048576 samples (2 megabytes in memory). The GUS gets +by with just 9 bits and a little help from its friends... +"The GUS does not SUCK!!!" -- a happy user :) */ +#define FRACTION_BITS 12 + +/* For some reason the sample volume is always set to maximum in all +patch files. Define this for a crude adjustment that may help +equalize instrument volumes. */ +#define ADJUST_SAMPLE_VOLUMES + +/* The number of samples to use for ramping out a dying note. Affects +click removal. */ +#define MAX_DIE_TIME 20 + +/* On some machines (especially PCs without math coprocessors), +looking up sine values in a table will be significantly faster than +computing them on the fly. Uncomment this to use lookups. */ +/* #define LOOKUP_SINE */ + +/* Shawn McHorse's resampling optimizations. These may not in fact be +faster on your particular machine and compiler. You'll have to run +a benchmark to find out. */ +#define PRECALC_LOOPS + +/* If calling ldexp() is faster than a floating point multiplication +on your machine/compiler/libm, uncomment this. It doesn't make much +difference either way, but hey -- it was on the TODO list, so it +got done. */ +/* #define USE_LDEXP */ + +/**************************************************************************/ +/* Anything below this shouldn't need to be changed unless you're porting +to a new machine with other than 32-bit, big-endian words. */ +/**************************************************************************/ + +/* change FRACTION_BITS above, not these */ +#define INTEGER_BITS (32 - FRACTION_BITS) +#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS) +#define FRACTION_MASK (~ INTEGER_MASK) + +/* This is enforced by some computations that must fit in an int */ +#define MAX_CONTROL_RATIO 255 + +/* Instrument files are little-endian, MIDI files big-endian, so we +need to do some conversions. */ + +#define XCHG_SHORT(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF)) +# define XCHG_LONG(x) ((((x)&0xFF)<<24) | \ + (((x)&0xFF00)<<8) | \ + (((x)&0xFF0000)>>8) | \ + (((x)>>24)&0xFF)) + +#ifdef LITTLE_ENDIAN +#define LE_SHORT(x) x +#define LE_LONG(x) x +#define BE_SHORT(x) XCHG_SHORT(x) +#define BE_LONG(x) XCHG_LONG(x) +#else +#define BE_SHORT(x) x +#define BE_LONG(x) x +#define LE_SHORT(x) XCHG_SHORT(x) +#define LE_LONG(x) XCHG_LONG(x) +#endif + +#define MAX_AMPLIFICATION 800 + +/* These affect general volume */ +#define GUARD_BITS 3 +#define AMP_BITS (15-GUARD_BITS) + +#ifdef LOOKUP_HACK +typedef int8_t sample_t; +typedef uint8_t final_volume_t; +# define FINAL_VOLUME(v) (~_l2u[v]) +# define MIXUP_SHIFT 5 +# define MAX_AMP_VALUE 4095 +#else +typedef int16_t sample_t; +typedef int32_t final_volume_t; +# define FINAL_VOLUME(v) (v) +# define MAX_AMP_VALUE ((1<<(AMP_BITS+1))-1) +#endif + +#ifdef USE_LDEXP +# define FSCALE(a,b) ldexp((a),(b)) +# define FSCALENEG(a,b) ldexp((a),-(b)) +#else +# define FSCALE(a,b) (float)((a) * (double)(1<<(b))) +# define FSCALENEG(a,b) (float)((a) * (1.0L / (double)(1<<(b)))) +#endif + +/* Vibrato and tremolo Choices of the Day */ +#define SWEEP_TUNING 38 +#define VIBRATO_AMPLITUDE_TUNING 1.0L +#define VIBRATO_RATE_TUNING 38 +#define TREMOLO_AMPLITUDE_TUNING 1.0L +#define TREMOLO_RATE_TUNING 38 + +#define SWEEP_SHIFT 16 +#define RATE_SHIFT 5 + +#define VIBRATO_SAMPLE_INCREMENTS 32 + +#ifndef PI +const float PI = 3.14159265358979323846f; +#endif + +/* The path separator (D.M.) */ +//#ifdef __WIN32__ +# define PATH_SEP '\\' +# define PATH_STRING "\\" +//#else +//# define PATH_SEP '/' +//# define PATH_STRING "/" +//#endif diff --git a/doomclassic/timidity/controls.cpp b/doomclassic/timidity/controls.cpp new file mode 100644 index 00000000..d3444cc1 --- /dev/null +++ b/doomclassic/timidity/controls.cpp @@ -0,0 +1,41 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +controls.c + +*/ + +#include "config.h" +#include "controls.h" + +#ifdef SDL +extern ControlMode sdl_control_mode; +# ifndef DEFAULT_CONTROL_MODE +# define DEFAULT_CONTROL_MODE &sdl_control_mode +# endif +#endif + +ControlMode *ctl_list[]={ +#ifdef SDL + &sdl_control_mode, +#endif + 0 +}; + +ControlMode *ctl=DEFAULT_CONTROL_MODE; diff --git a/doomclassic/timidity/controls.h b/doomclassic/timidity/controls.h new file mode 100644 index 00000000..95a911fd --- /dev/null +++ b/doomclassic/timidity/controls.h @@ -0,0 +1,89 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +controls.h +*/ + +/* Return values for ControlMode.read */ + +#define RC_ERROR -1 +#define RC_NO_RETURN_VALUE 0 +#define RC_QUIT 1 +#define RC_NEXT 2 +#define RC_PREVIOUS 3 /* Restart this song at beginning, or the previous +song if we're less than a second into this one. */ +#define RC_FORWARD 4 +#define RC_BACK 5 +#define RC_JUMP 6 +#define RC_TOGGLE_PAUSE 7 /* Pause/continue */ +#define RC_RESTART 8 /* Restart song at beginning */ + +#define RC_PAUSE 9 /* Really pause playing */ +#define RC_CONTINUE 10 /* Continue if paused */ +#define RC_REALLY_PREVIOUS 11 /* Really go to the previous song */ +#define RC_CHANGE_VOLUME 12 +#define RC_LOAD_FILE 13 /* Load a new midifile */ +#define RC_TUNE_END 14 /* The tune is over, play it again sam? */ + +#define CMSG_INFO 0 +#define CMSG_WARNING 1 +#define CMSG_ERROR 2 +#define CMSG_FATAL 3 +#define CMSG_TRACE 4 +#define CMSG_TIME 5 +#define CMSG_TOTAL 6 +#define CMSG_FILE 7 +#define CMSG_TEXT 8 + +#define VERB_NORMAL 0 +#define VERB_VERBOSE 1 +#define VERB_NOISY 2 +#define VERB_DEBUG 3 +#define VERB_DEBUG_SILLY 4 + +typedef struct { + char *id_name, id_character; + int verbosity, trace_playing, opened; + + int (*open)(int using_stdin, int using_stdout); + void (*pass_playing_list)(int number_of_files, char *list_of_files[]); + void (*close)(void); + int (*read)(int *valp); + int (*cmsg)(int type, int verbosity_level, char *fmt, ...); + + void (*refresh)(void); + void (*reset)(void); + void (*file_name)(char *name); + void (*total_time)(int tt); + void (*current_time)(int ct); + + void (*note)(int v); + void (*master_volume)(int mv); + void (*program)(int channel, int val); /* val<0 means drum set -val */ + void (*volume)(int channel, int val); + void (*expression)(int channel, int val); + void (*panning)(int channel, int val); + void (*sustain)(int channel, int val); + void (*pitch_bend)(int channel, int val); + +} ControlMode; + +extern ControlMode *ctl_list[], *ctl; +const int TIMIDITY_ERROR_MAX_CHARS = 1024; +extern char timidity_error[]; diff --git a/doomclassic/timidity/filter.cpp b/doomclassic/timidity/filter.cpp new file mode 100644 index 00000000..0dc3495e --- /dev/null +++ b/doomclassic/timidity/filter.cpp @@ -0,0 +1,205 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +filter.c: written by Vincent Pagel ( pagel@loria.fr ) + +implements fir antialiasing filter : should help when setting sample +rates as low as 8Khz. + +April 95 +- first draft + +22/5/95 +- modify "filter" so that it simulate leading and trailing 0 in the buffer +*/ + +#include +#include +#include +#include +#include "config.h" +#include "common.h" +#include "controls.h" +#include "instrum.h" +#include "filter.h" + +void Real_Tim_Free( void *pt ); + +/* bessel function */ +static float ino(float x) +{ + float y, de, e, sde; + int i; + + y = x / 2; + e = 1.0; + de = 1.0; + i = 1; + do { + de = de * y / (float) i; + sde = de * de; + e += sde; + } while (!( (e * 1.0e-08 - sde > 0) || (i++ > 25) )); + return(e); +} + +/* Kaiser Window (symetric) */ +static void kaiser(float *w,int n,float beta) +{ + float xind, xi; + int i; + + xind = (float)((2*n - 1) * (2*n - 1)); + for (i =0; i apply the filter given by coef[] to the data buffer +* Note that we simulate leading and trailing 0 at the border of the +* data buffer +*/ +static void filter(sample_t *result,sample_t *data, int32_t length,float coef[]) +{ + int32_t sample,i,sample_window; + int16_t peak = 0; + float sum; + + /* Simulate leading 0 at the begining of the buffer */ + for (sample = 0; sample < ORDER2 ; sample++ ) + { + sum = 0.0; + sample_window= sample - ORDER2; + + for (i = 0; i < ORDER ;i++) + sum += (float)(coef[i] * + ((sample_window<0)? 0.0 : data[sample_window++])) ; + + /* Saturation ??? */ + if (sum> 32767.) { sum=32767.; peak++; } + if (sum< -32768.) { sum=-32768; peak++; } + result[sample] = (sample_t) sum; + } + + /* The core of the buffer */ + for (sample = ORDER2; sample < length - ORDER + ORDER2 ; sample++ ) + { + sum = 0.0; + sample_window= sample - ORDER2; + + for (i = 0; i < ORDER ;i++) + sum += data[sample_window++] * coef[i]; + + /* Saturation ??? */ + if (sum> 32767.) { sum=32767.; peak++; } + if (sum< -32768.) { sum=-32768; peak++; } + result[sample] = (sample_t) sum; + } + + /* Simulate 0 at the end of the buffer */ + for (sample = length - ORDER + ORDER2; sample < length ; sample++ ) + { + sum = 0.0; + sample_window= sample - ORDER2; + + for (i = 0; i < ORDER ;i++) + sum += (float)(coef[i] * + ((sample_window>=length)? 0.0 : data[sample_window++])) ; + + /* Saturation ??? */ + if (sum> 32767.) { sum=32767.; peak++; } + if (sum< -32768.) { sum=-32768; peak++; } + result[sample] = (sample_t) sum; + } + + if (peak) + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Saturation %2.3f %%.", 100.0*peak/ (float) length); +} + +/***********************************************************************/ +/* Prevent aliasing by filtering any freq above the output_rate */ +/* */ +/* I don't worry about looping point -> they will remain soft if they */ +/* were already */ +/***********************************************************************/ +void antialiasing(Sample *sp, int32_t output_rate ) +{ + sample_t *temp; + int i; + float fir_symetric[ORDER]; + float fir_coef[ORDER2]; + float freq_cut; /* cutoff frequency [0..1.0] FREQ_CUT/SAMP_FREQ*/ + + + ctl->cmsg(CMSG_INFO, VERB_NOISY, "Antialiasing: Fsample=%iKHz", + sp->sample_rate); + + /* No oversampling */ + if (output_rate>=sp->sample_rate) + return; + + freq_cut= (float) output_rate / (float) sp->sample_rate; + ctl->cmsg(CMSG_INFO, VERB_NOISY, "Antialiasing: cutoff=%f%%", + freq_cut*100.); + + designfir(fir_coef,freq_cut); + + /* Make the filter symetric */ + for (i = 0 ; idata_length); + memcpy(temp,sp->data,sp->data_length); + + filter(sp->data,temp,sp->data_length/sizeof(sample_t),fir_symetric); + + Real_Tim_Free(temp); +} diff --git a/doomclassic/timidity/filter.h b/doomclassic/timidity/filter.h new file mode 100644 index 00000000..58271010 --- /dev/null +++ b/doomclassic/timidity/filter.h @@ -0,0 +1,35 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +filter.h : written by Vincent Pagel ( pagel@loria.fr ) + +implements fir antialiasing filter : should help when setting sample +rates as low as 8Khz. + +*/ + +/* Order of the FIR filter = 20 should be enough ! */ +#define ORDER 20 +#define ORDER2 ORDER/2 + +#ifndef PI +extern const float PI; +#endif + +extern void antialiasing(Sample *sp, int32_t output_rate); diff --git a/doomclassic/timidity/instrum.cpp b/doomclassic/timidity/instrum.cpp new file mode 100644 index 00000000..d5068f3b --- /dev/null +++ b/doomclassic/timidity/instrum.cpp @@ -0,0 +1,683 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +instrum.c + +Code to load and unload GUS-compatible instrument patches. + +*/ + +#include "../../neo/idlib/precompiled.h" + +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" +#include "output.h" +#include "controls.h" +#include "resample.h" +#include "tables.h" +#include "filter.h" + + + +//void Real_Tim_Free( void *pt ); + +/* Some functions get aggravated if not even the standard banks are +available. */ +static ToneBank standard_tonebank, standard_drumset; +ToneBank +*tonebank[128]={&standard_tonebank}, +*drumset[128]={&standard_drumset}; + +/* This is a special instrument, used for all melodic programs */ +Instrument *default_instrument=0; + +/* This is only used for tracks that don't specify a program */ +int default_program=DEFAULT_PROGRAM; + +int antialiasing_allowed=0; +#ifdef FAST_DECAY +int fast_decay=1; +#else +int fast_decay=0; +#endif + +static void free_instrument(Instrument *ip) +{ + Sample *sp; + int i; + if (!ip) return; + for (i=0; isamples; i++) + { + sp=&(ip->sample[i]); + Real_Tim_Free(sp->data); + } + Real_Tim_Free(ip->sample); + Real_Tim_Free(ip); +} + +static void free_bank(int dr, int b) +{ + int i; + ToneBank *bank=((dr) ? drumset[b] : tonebank[b]); + for (i=0; i<128; i++) { + if (bank->tone[i].instrument) + { + /* Not that this could ever happen, of course */ + if (bank->tone[i].instrument != MAGIC_LOAD_INSTRUMENT) + free_instrument(bank->tone[i].instrument); + bank->tone[i].instrument=0; + } + if (bank->tone[i].name) + { + Real_Tim_Free( bank->tone[i].name ); + bank->tone[i].name = NULL; + } + } +} + +static int32_t convert_envelope_rate(uint8_t rate) +{ + int32_t r; + + r=3-((rate>>6) & 0x3); + r*=3; + r = (int32_t)(rate & 0x3f) << r; /* 6.9 fixed point */ + + /* 15.15 fixed point. */ + return (((r * 44100) / play_mode->rate) * control_ratio) + << ((fast_decay) ? 10 : 9); +} + +static int32_t convert_envelope_offset(uint8_t offset) +{ + /* This is not too good... Can anyone tell me what these values mean? + Are they GUS-style "exponential" volumes? And what does that mean? */ + + /* 15.15 fixed point */ + return offset << (7+15); +} + +static int32_t convert_tremolo_sweep(uint8_t sweep) +{ + if (!sweep) + return 0; + + return + ((control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / + (play_mode->rate * sweep); +} + +static int32_t convert_vibrato_sweep(uint8_t sweep, int32_t vib_control_ratio) +{ + if (!sweep) + return 0; + + return + (int32_t) (FSCALE((double) (vib_control_ratio) * SWEEP_TUNING, SWEEP_SHIFT) + / (double)(play_mode->rate * sweep)); + + /* this was overflowing with seashore.pat + + ((vib_control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / + (play_mode->rate * sweep); */ +} + +static int32_t convert_tremolo_rate(uint8_t rate) +{ + return + ((SINE_CYCLE_LENGTH * control_ratio * rate) << RATE_SHIFT) / + (TREMOLO_RATE_TUNING * play_mode->rate); +} + +static int32_t convert_vibrato_rate(uint8_t rate) +{ + /* Return a suitable vibrato_control_ratio value */ + return + (VIBRATO_RATE_TUNING * play_mode->rate) / + (rate * 2 * VIBRATO_SAMPLE_INCREMENTS); +} + +static void reverse_data(int16_t *sp, int32_t ls, int32_t le) +{ + int16_t s, *ep=sp+le; + sp+=ls; + le-=ls; + le/=2; + while (le--) + { + s=*sp; + *sp++=*ep; + *ep--=s; + } +} + +/* +If panning or note_to_use != -1, it will be used for all samples, +instead of the sample-specific values in the instrument file. + +For note_to_use, any value <0 or >127 will be forced to 0. + +For other parameters, 1 means yes, 0 means no, other values are +undefined. + +TODO: do reverse loops right */ +static Instrument *load_instrument(char *name, int percussion, + int panning, int amp, int note_to_use, + int strip_loop, int strip_envelope, + int strip_tail) +{ + Instrument *ip; + Sample *sp; + idFile * fp; + uint8_t tmp[1024]; + int i,j,noluck=0; + char *path; + char filename[1024]; +#ifdef PATCH_EXT_LIST + static char *patch_ext[] = PATCH_EXT_LIST; +#endif + + if (!name) return 0; + + path = "classicmusic/instruments/"; + + idStr instName = name; + instName.ToUpper(); + + strcpy( filename, path ); + strcat( filename, instName.c_str() ); + strcat( filename, ".PAT" ); + + /* Open patch file */ + if ((fp=open_file(filename, 1, OF_VERBOSE)) == NULL) + { + noluck=1; +#ifdef PATCH_EXT_LIST + /* Try with various extensions */ + for (i=0; patch_ext[i]; i++) + { + if (strlen(name)+strlen(patch_ext[i])<1024) + { + strcpy(filename, path); + strcat(filename, name); + strcat(filename, patch_ext[i]); + if ((fp=open_file(filename, 1, OF_VERBOSE)) != NULL) + { + noluck=0; + break; + } + } + } +#endif + } + + if (noluck) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Instrument `%s' can't be found.", name); + return 0; + } + + ctl->cmsg(CMSG_INFO, VERB_NOISY, "Loading instrument %s", current_filename); + + /* Read some headers and do cursory sanity checks. There are loads + of magic offsets. This could be rewritten... */ + + if ((239 != fp->Read(tmp, 239)) || + (memcmp(tmp, "GF1PATCH110\0ID#000002", 22) && + memcmp(tmp, "GF1PATCH100\0ID#000002", 22))) /* don't know what the + differences are */ + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: not an instrument", name); + return 0; + } + + if (tmp[82] != 1 && tmp[82] != 0) /* instruments. To some patch makers, + 0 means 1 */ + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Can't handle patches with %d instruments", tmp[82]); + return 0; + } + + if (tmp[151] != 1 && tmp[151] != 0) /* layers. What's a layer? */ + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Can't handle instruments with %d layers", tmp[151]); + return 0; + } + + ip=(Instrument *)safe_malloc(sizeof(Instrument)); + ip->samples = tmp[198]; + ip->sample = (Sample *)safe_malloc(sizeof(Sample) * ip->samples); + for (i=0; isamples; i++) + { + + uint8_t fractions; + int32_t tmplong; + uint16_t tmpshort; + uint8_t tmpchar; + +#define READ_CHAR(thing) \ + if (1 != fp->Read(&tmpchar, 1)) goto fail; \ + thing = tmpchar; +#define READ_SHORT(thing) \ + if (2 != fp->Read(&tmpshort, 2 )) goto fail; \ + thing = LE_SHORT(tmpshort); +#define READ_LONG(thing) \ + if (4 != fp->Read(&tmplong, 4 )) goto fail; \ + thing = LE_LONG(tmplong); + + skip(fp, 7); /* Skip the wave name */ + + if (1 != fp->Read(&fractions, 1 )) + { +fail: + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Error reading sample %d", i); + for (j=0; jsample[j].data); + Real_Tim_Free(ip->sample); + Real_Tim_Free(ip); + return 0; + } + + sp=&(ip->sample[i]); + + READ_LONG(sp->data_length); + READ_LONG(sp->loop_start); + READ_LONG(sp->loop_end); + READ_SHORT(sp->sample_rate); + READ_LONG(sp->low_freq); + READ_LONG(sp->high_freq); + READ_LONG(sp->root_freq); + skip(fp, 2); /* Why have a "root frequency" and then "tuning"?? */ + + READ_CHAR(tmp[0]); + + if (panning==-1) + sp->panning = (tmp[0] * 8 + 4) & 0x7f; + else + sp->panning=(uint8)(panning & 0x7F); + + /* envelope, tremolo, and vibrato */ + if (18 != fp->Read(tmp, 18)) goto fail; + + if (!tmp[13] || !tmp[14]) + { + sp->tremolo_sweep_increment= + sp->tremolo_phase_increment=sp->tremolo_depth=0; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * no tremolo"); + } + else + { + sp->tremolo_sweep_increment=convert_tremolo_sweep(tmp[12]); + sp->tremolo_phase_increment=convert_tremolo_rate(tmp[13]); + sp->tremolo_depth=tmp[14]; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " * tremolo: sweep %d, phase %d, depth %d", + sp->tremolo_sweep_increment, sp->tremolo_phase_increment, + sp->tremolo_depth); + } + + if (!tmp[16] || !tmp[17]) + { + sp->vibrato_sweep_increment= + sp->vibrato_control_ratio=sp->vibrato_depth=0; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * no vibrato"); + } + else + { + sp->vibrato_control_ratio=convert_vibrato_rate(tmp[16]); + sp->vibrato_sweep_increment= + convert_vibrato_sweep(tmp[15], sp->vibrato_control_ratio); + sp->vibrato_depth=tmp[17]; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " * vibrato: sweep %d, ctl %d, depth %d", + sp->vibrato_sweep_increment, sp->vibrato_control_ratio, + sp->vibrato_depth); + + } + + READ_CHAR(sp->modes); + + skip(fp, 40); /* skip the useless scale frequency, scale factor + (what's it mean?), and reserved space */ + + /* Mark this as a fixed-pitch instrument if such a deed is desired. */ + if (note_to_use!=-1) + sp->note_to_use=(uint8)(note_to_use); + else + sp->note_to_use=0; + + /* seashore.pat in the Midia patch set has no Sustain. I don't + understand why, and fixing it by adding the Sustain flag to + all looped patches probably breaks something else. We do it + anyway. */ + + if (sp->modes & MODES_LOOPING) + sp->modes |= MODES_SUSTAIN; + + /* Strip any loops and envelopes we're permitted to */ + if ((strip_loop==1) && + (sp->modes & (MODES_SUSTAIN | MODES_LOOPING | + MODES_PINGPONG | MODES_REVERSE))) + { + ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Removing loop and/or sustain"); + sp->modes &=~(MODES_SUSTAIN | MODES_LOOPING | + MODES_PINGPONG | MODES_REVERSE); + } + + if (strip_envelope==1) + { + if (sp->modes & MODES_ENVELOPE) + ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Removing envelope"); + sp->modes &= ~MODES_ENVELOPE; + } + else if (strip_envelope != 0) + { + /* Have to make a guess. */ + if (!(sp->modes & (MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE))) + { + /* No loop? Then what's there to sustain? No envelope needed + either... */ + sp->modes &= ~(MODES_SUSTAIN|MODES_ENVELOPE); + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " - No loop, removing sustain and envelope"); + } + else if (!memcmp(tmp, "??????", 6) || tmp[11] >= 100) + { + /* Envelope rates all maxed out? Envelope end at a high "offset"? + That's a weird envelope. Take it out. */ + sp->modes &= ~MODES_ENVELOPE; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " - Weirdness, removing envelope"); + } + else if (!(sp->modes & MODES_SUSTAIN)) + { + /* No sustain? Then no envelope. I don't know if this is + justified, but patches without sustain usually don't need the + envelope either... at least the Gravis ones. They're mostly + drums. I think. */ + sp->modes &= ~MODES_ENVELOPE; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + " - No sustain, removing envelope"); + } + } + + for (j=0; j<6; j++) + { + sp->envelope_rate[j]= + convert_envelope_rate(tmp[j]); + sp->envelope_offset[j]= + convert_envelope_offset(tmp[6+j]); + } + + /* Then read the sample data */ + sp->data = (sample_t*)safe_malloc(sp->data_length); + if ( static_cast< size_t >( sp->data_length ) != fp->Read(sp->data, sp->data_length )) + goto fail; + + if (!(sp->modes & MODES_16BIT)) /* convert to 16-bit data */ + { + int32_t i=sp->data_length; + uint8_t *cp=(uint8_t *)(sp->data); + uint16_t *tmp,*anew; + tmp=anew=(uint16*)safe_malloc(sp->data_length*2); + while (i--) + *tmp++ = (uint16)(*cp++) << 8; + cp=(uint8_t *)(sp->data); + sp->data = (sample_t *)anew; + Real_Tim_Free(cp); + sp->data_length *= 2; + sp->loop_start *= 2; + sp->loop_end *= 2; + } +#ifndef LITTLE_ENDIAN + else + /* convert to machine byte order */ + { + int32_t i=sp->data_length/2; + int16_t *tmp=(int16_t *)sp->data,s; + while (i--) + { + s=LE_SHORT(*tmp); + *tmp++=s; + } + } +#endif + + if (sp->modes & MODES_UNSIGNED) /* convert to signed data */ + { + int32_t i=sp->data_length/2; + int16_t *tmp=(int16_t *)sp->data; + while (i--) + *tmp++ ^= 0x8000; + } + + /* Reverse reverse loops and pass them off as normal loops */ + if (sp->modes & MODES_REVERSE) + { + int32_t t; + /* The GUS apparently plays reverse loops by reversing the + whole sample. We do the same because the GUS does not SUCK. */ + + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, "Reverse loop in %s", name); + reverse_data((int16_t *)sp->data, 0, sp->data_length/2); + + t=sp->loop_start; + sp->loop_start=sp->data_length - sp->loop_end; + sp->loop_end=sp->data_length - t; + + sp->modes &= ~MODES_REVERSE; + sp->modes |= MODES_LOOPING; /* just in case */ + } + + /* If necessary do some anti-aliasing filtering */ + + if (antialiasing_allowed) + antialiasing(sp,play_mode->rate); + +#ifdef ADJUST_SAMPLE_VOLUMES + if (amp!=-1) + sp->volume=(float)((amp) / 100.0); + else + { + /* Try to determine a volume scaling factor for the sample. + This is a very crude adjustment, but things sound more + balanced with it. Still, this should be a runtime option. */ + int32_t i=sp->data_length/2; + int16_t maxamp=0,a; + int16_t *tmp=(int16_t *)sp->data; + while (i--) + { + a=*tmp++; + if (a<0) a=-a; + if (a>maxamp) + maxamp=a; + } + sp->volume=(float)(32768.0 / maxamp); + ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * volume comp: %f", sp->volume); + } +#else + if (amp!=-1) + sp->volume=(double)(amp) / 100.0; + else + sp->volume=1.0; +#endif + + sp->data_length /= 2; /* These are in bytes. Convert into samples. */ + sp->loop_start /= 2; + sp->loop_end /= 2; + + /* Then fractional samples */ + sp->data_length <<= FRACTION_BITS; + sp->loop_start <<= FRACTION_BITS; + sp->loop_end <<= FRACTION_BITS; + + /* Adjust for fractional loop points. This is a guess. Does anyone + know what "fractions" really stands for? */ + sp->loop_start |= + (fractions & 0x0F) << (FRACTION_BITS-4); + sp->loop_end |= + ((fractions>>4) & 0x0F) << (FRACTION_BITS-4); + + /* If this instrument will always be played on the same note, + and it's not looped, we can resample it now. */ + if (sp->note_to_use && !(sp->modes & MODES_LOOPING)) + pre_resample(sp); + +#ifdef LOOKUP_HACK + /* Squash the 16-bit data into 8 bits. */ + { + uint8_t *gulp,*ulp; + int16_t *swp; + int l=sp->data_length >> FRACTION_BITS; + gulp=ulp=safe_malloc(l+1); + swp=(int16_t *)sp->data; + while(l--) + *ulp++ = (*swp++ >> 8) & 0xFF; + Real_Tim_Free(sp->data); + sp->data=(sample_t *)gulp; + } +#endif + + if (strip_tail==1) + { + /* Let's not really, just say we did. */ + ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Stripping tail"); + sp->data_length = sp->loop_end; + } + } + + delete fp; + + return ip; +} + +static int fill_bank(int dr, int b) +{ + int i, errors=0; + ToneBank *bank=((dr) ? drumset[b] : tonebank[b]); + if (!bank) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Huh. Tried to load instruments in non-existent %s %d", + (dr) ? "drumset" : "tone bank", b); + return 0; + } + for (i=0; i<128; i++) + { + if (bank->tone[i].instrument==MAGIC_LOAD_INSTRUMENT) + { + if (!(bank->tone[i].name)) + { + ctl->cmsg(CMSG_WARNING, (b!=0) ? VERB_VERBOSE : VERB_NORMAL, + "No instrument mapped to %s %d, program %d%s", + (dr)? "drum set" : "tone bank", b, i, + (b!=0) ? "" : " - this instrument will not be heard"); + if (b!=0) + { + /* Mark the corresponding instrument in the default + bank / drumset for loading (if it isn't already) */ + if (!dr) + { + if (!(standard_tonebank.tone[i].instrument)) + standard_tonebank.tone[i].instrument= + MAGIC_LOAD_INSTRUMENT; + } + else + { + if (!(standard_drumset.tone[i].instrument)) + standard_drumset.tone[i].instrument= + MAGIC_LOAD_INSTRUMENT; + } + } + bank->tone[i].instrument=0; + errors++; + } + else if (!(bank->tone[i].instrument= + load_instrument(bank->tone[i].name, + (dr) ? 1 : 0, + bank->tone[i].pan, + bank->tone[i].amp, + (bank->tone[i].note!=-1) ? + bank->tone[i].note : + ((dr) ? i : -1), + (bank->tone[i].strip_loop!=-1) ? + bank->tone[i].strip_loop : + ((dr) ? 1 : -1), + (bank->tone[i].strip_envelope != -1) ? + bank->tone[i].strip_envelope : + ((dr) ? 1 : -1), + bank->tone[i].strip_tail ))) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Couldn't load instrument %s (%s %d, program %d)", + bank->tone[i].name, + (dr)? "drum set" : "tone bank", b, i); + errors++; + } + } + } + return errors; +} + +int load_missing_instruments(void) +{ + int i=128,errors=0; + while (i--) + { + if (tonebank[i]) + errors+=fill_bank(0,i); + if (drumset[i]) + errors+=fill_bank(1,i); + } + return errors; +} + +void free_instruments(void) +{ + int i=128; + while(i--) + { + if (tonebank[i]) + free_bank(0,i); + if (drumset[i]) + free_bank(1,i); + } +} + +int set_default_instrument(char *name) +{ + Instrument *ip; + if (!(ip=load_instrument(name, 0, -1, -1, -1, 0, 0, 0))) + return -1; + if (default_instrument) + free_instrument(default_instrument); + default_instrument=ip; + default_program=SPECIAL_PROGRAM; + return 0; +} diff --git a/doomclassic/timidity/instrum.h b/doomclassic/timidity/instrum.h new file mode 100644 index 00000000..d94ae120 --- /dev/null +++ b/doomclassic/timidity/instrum.h @@ -0,0 +1,84 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +instrum.h + +*/ + +typedef struct { + int32_t + loop_start, loop_end, data_length, + sample_rate, low_freq, high_freq, root_freq; + int32_t + envelope_rate[6], envelope_offset[6]; + float + volume; + sample_t *data; + int32_t + tremolo_sweep_increment, tremolo_phase_increment, + vibrato_sweep_increment, vibrato_control_ratio; + uint8_t + tremolo_depth, vibrato_depth, + modes; + int8_t + panning, note_to_use; +} Sample; + +/* Bits in modes: */ +#define MODES_16BIT (1<<0) +#define MODES_UNSIGNED (1<<1) +#define MODES_LOOPING (1<<2) +#define MODES_PINGPONG (1<<3) +#define MODES_REVERSE (1<<4) +#define MODES_SUSTAIN (1<<5) +#define MODES_ENVELOPE (1<<6) + +typedef struct { + int samples; + Sample *sample; +} Instrument; + +typedef struct { + char *name; + Instrument *instrument; + int note, amp, pan, strip_loop, strip_envelope, strip_tail; +} ToneBankElement; + +/* A hack to delay instrument loading until after reading the +entire MIDI file. */ +#define MAGIC_LOAD_INSTRUMENT ((Instrument *)(-1)) + +typedef struct { + ToneBankElement tone[128]; +} ToneBank; + +extern ToneBank *tonebank[], *drumset[]; + +extern Instrument *default_instrument; +extern int default_program; +extern int antialiasing_allowed; +extern int fast_decay; +extern int free_instruments_afterwards; + +#define SPECIAL_PROGRAM -1 + +extern int load_missing_instruments(void); +extern void free_instruments(void); +extern int set_default_instrument(char *name); + diff --git a/doomclassic/timidity/mix.cpp b/doomclassic/timidity/mix.cpp new file mode 100644 index 00000000..a6308167 --- /dev/null +++ b/doomclassic/timidity/mix.cpp @@ -0,0 +1,569 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +Suddenly, you realize that this program is free software; you get +an overwhelming urge to redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received another copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +I bet they'll be amazed. + +mix.c */ + +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" +#include "output.h" +#include "controls.h" +#include "tables.h" +#include "resample.h" +#include "mix.h" + +/* Returns 1 if envelope runs out */ +int recompute_envelope(int v) +{ + int stage; + + stage = voice[v].envelope_stage; + + if (stage>5) + { + /* Envelope ran out. */ + int tmp=(voice[v].status == VOICE_DIE); /* Already displayed as dead */ + voice[v].status = VOICE_FREE; + if(!tmp) + ctl->note(v); + return 1; + } + + if (voice[v].sample->modes & MODES_ENVELOPE) + { + if (voice[v].status==VOICE_ON || voice[v].status==VOICE_SUSTAINED) + { + if (stage>2) + { + /* Freeze envelope until note turns off. Trumpets want this. */ + voice[v].envelope_increment=0; + return 0; + } + } + } + voice[v].envelope_stage=stage+1; + + if (voice[v].envelope_volume==voice[v].sample->envelope_offset[stage]) + return recompute_envelope(v); + voice[v].envelope_target=voice[v].sample->envelope_offset[stage]; + voice[v].envelope_increment = voice[v].sample->envelope_rate[stage]; + if (voice[v].envelope_targetmodes & MODES_ENVELOPE) + { + lamp *= (float)vol_table[voice[v].envelope_volume>>23]; + ramp *= (float)vol_table[voice[v].envelope_volume>>23]; + } + + la = (int32_t)FSCALE(lamp,AMP_BITS); + + if (la>MAX_AMP_VALUE) + la=MAX_AMP_VALUE; + + ra = (int32_t)FSCALE(ramp,AMP_BITS); + if (ra>MAX_AMP_VALUE) + ra=MAX_AMP_VALUE; + + + voice[v].left_mix=FINAL_VOLUME(la); + voice[v].right_mix=FINAL_VOLUME(ra); + } + else + { + if (voice[v].tremolo_phase_increment) + lamp *= voice[v].tremolo_volume; + if (voice[v].sample->modes & MODES_ENVELOPE) + lamp *= (float)vol_table[voice[v].envelope_volume>>23]; + + la = (int32_t)FSCALE(lamp,AMP_BITS); + + if (la>MAX_AMP_VALUE) + la=MAX_AMP_VALUE; + + voice[v].left_mix=FINAL_VOLUME(la); + } +} + +static int update_envelope(int v) +{ + voice[v].envelope_volume += voice[v].envelope_increment; + /* Why is there no ^^ operator?? */ + if (((voice[v].envelope_increment < 0) && + (voice[v].envelope_volume <= voice[v].envelope_target)) || + ((voice[v].envelope_increment > 0) && + (voice[v].envelope_volume >= voice[v].envelope_target))) + { + voice[v].envelope_volume = voice[v].envelope_target; + if (recompute_envelope(v)) + return 1; + } + return 0; +} + +static void update_tremolo(int v) +{ + int32_t depth=voice[v].sample->tremolo_depth<<7; + + if (voice[v].tremolo_sweep) + { + /* Update sweep position */ + + voice[v].tremolo_sweep_position += voice[v].tremolo_sweep; + if (voice[v].tremolo_sweep_position>=(1<>= SWEEP_SHIFT; + } + } + + voice[v].tremolo_phase += voice[v].tremolo_phase_increment; + + /* if (voice[v].tremolo_phase >= (SINE_CYCLE_LENGTH<> RATE_SHIFT) + 1.0) + * depth * TREMOLO_AMPLITUDE_TUNING, + 17)); + + /* I'm not sure about the +1.0 there -- it makes tremoloed voices' + volumes on average the lower the higher the tremolo amplitude. */ +} + +/* Returns 1 if the note died */ +static int update_signal(int v) +{ + if (voice[v].envelope_increment && update_envelope(v)) + return 1; + + if (voice[v].tremolo_phase_increment) + update_tremolo(v); + + apply_envelope_to_amp(v); + return 0; +} + +#ifdef LOOKUP_HACK +# define MIXATION(a) *lp++ += mixup[(a<<8) | (uint8)s]; +#else +# define MIXATION(a) *lp++ += (a)*s; +#endif + +static void mix_mystery_signal(sample_t *sp, int32_t *lp, int v, int count) +{ + Voice *vp = voice + v; + final_volume_t + left=vp->left_mix, + right=vp->right_mix; + int cc; + sample_t s; + + if (!(cc = vp->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + right = vp->right_mix; + } + + while (count) + if (cc < count) + { + count -= cc; + while (cc--) + { + s = *sp++; + MIXATION(left); + MIXATION(right); + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + right = vp->right_mix; + } + else + { + vp->control_counter = cc - count; + while (count--) + { + s = *sp++; + MIXATION(left); + MIXATION(right); + } + return; + } +} + +static void mix_center_signal(sample_t *sp, int32_t *lp, int v, int count) +{ + Voice *vp = voice + v; + final_volume_t + left=vp->left_mix; + int cc; + sample_t s; + + if (!(cc = vp->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + } + + while (count) + if (cc < count) + { + count -= cc; + while (cc--) + { + s = *sp++; + MIXATION(left); + MIXATION(left); + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + } + else + { + vp->control_counter = cc - count; + while (count--) + { + s = *sp++; + MIXATION(left); + MIXATION(left); + } + return; + } +} + +static void mix_single_signal(sample_t *sp, int32_t *lp, int v, int count) +{ + Voice *vp = voice + v; + final_volume_t + left=vp->left_mix; + int cc; + sample_t s; + + if (!(cc = vp->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + } + + while (count) + if (cc < count) + { + count -= cc; + while (cc--) + { + s = *sp++; + MIXATION(left); + lp++; + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + } + else + { + vp->control_counter = cc - count; + while (count--) + { + s = *sp++; + MIXATION(left); + lp++; + } + return; + } +} + +static void mix_mono_signal(sample_t *sp, int32_t *lp, int v, int count) +{ + Voice *vp = voice + v; + final_volume_t + left=vp->left_mix; + int cc; + sample_t s; + + if (!(cc = vp->control_counter)) + { + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + } + + while (count) + if (cc < count) + { + count -= cc; + while (cc--) + { + s = *sp++; + MIXATION(left); + } + cc = control_ratio; + if (update_signal(v)) + return; /* Envelope ran out */ + left = vp->left_mix; + } + else + { + vp->control_counter = cc - count; + while (count--) + { + s = *sp++; + MIXATION(left); + } + return; + } +} + +static void mix_mystery(sample_t *sp, int32_t *lp, int v, int count) +{ + final_volume_t + left=voice[v].left_mix, + right=voice[v].right_mix; + sample_t s; + + while (count--) + { + s = *sp++; + MIXATION(left); + MIXATION(right); + } +} + +static void mix_center(sample_t *sp, int32_t *lp, int v, int count) +{ + final_volume_t + left=voice[v].left_mix; + sample_t s; + + while (count--) + { + s = *sp++; + MIXATION(left); + MIXATION(left); + } +} + +static void mix_single(sample_t *sp, int32_t *lp, int v, int count) +{ + final_volume_t + left=voice[v].left_mix; + sample_t s; + + while (count--) + { + s = *sp++; + MIXATION(left); + lp++; + } +} + +static void mix_mono(sample_t *sp, int32_t *lp, int v, int count) +{ + final_volume_t + left=voice[v].left_mix; + sample_t s; + + while (count--) + { + s = *sp++; + MIXATION(left); + } +} + +/* Ramp a note out in c samples */ +static void ramp_out(sample_t *sp, int32_t *lp, int v, int32_t c) +{ + + /* should be final_volume_t, but uint8_t gives trouble. */ + int32_t left, right, li, ri; + + sample_t s=0; /* silly warning about uninitialized s */ + + /* Fix by James Caldwell */ + if ( c == 0 ) c = 1; + + left=voice[v].left_mix; + li=-(left/c); + if (!li) li=-1; + + /* I_Printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */ + + if (!(play_mode->encoding & PE_MONO)) + { + if (voice[v].panned==PANNED_MYSTERY) + { + right=voice[v].right_mix; + ri=-(right/c); + while (c--) + { + left += li; + if (left<0) + left=0; + right += ri; + if (right<0) + right=0; + s=*sp++; + MIXATION(left); + MIXATION(right); + } + } + else if (voice[v].panned==PANNED_CENTER) + { + while (c--) + { + left += li; + if (left<0) + return; + s=*sp++; + MIXATION(left); + MIXATION(left); + } + } + else if (voice[v].panned==PANNED_LEFT) + { + while (c--) + { + left += li; + if (left<0) + return; + s=*sp++; + MIXATION(left); + lp++; + } + } + else if (voice[v].panned==PANNED_RIGHT) + { + while (c--) + { + left += li; + if (left<0) + return; + s=*sp++; + lp++; + MIXATION(left); + } + } + } + else + { + /* Mono output. */ + while (c--) + { + left += li; + if (left<0) + return; + s=*sp++; + MIXATION(left); + } + } +} + + +/**************** interface function ******************/ + +void mix_voice( int32_t *buf, int v, int32_t c) +{ + Voice *vp=voice+v; + sample_t *sp; + if (vp->status==VOICE_DIE) + { + if (c>=MAX_DIE_TIME) + c=MAX_DIE_TIME; + sp=resample_voice(v, &c); + ramp_out(sp, buf, v, c); + vp->status=VOICE_FREE; + } + else + { + sp=resample_voice(v, &c); + if (play_mode->encoding & PE_MONO) + { + /* Mono output. */ + if (vp->envelope_increment || vp->tremolo_phase_increment) + mix_mono_signal(sp, buf, v, c); + else + mix_mono(sp, buf, v, c); + } + else + { + if (vp->panned == PANNED_MYSTERY) + { + if (vp->envelope_increment || vp->tremolo_phase_increment) + mix_mystery_signal(sp, buf, v, c); + else + mix_mystery(sp, buf, v, c); + } + else if (vp->panned == PANNED_CENTER) + { + if (vp->envelope_increment || vp->tremolo_phase_increment) + mix_center_signal(sp, buf, v, c); + else + mix_center(sp, buf, v, c); + } + else + { + /* It's either full left or full right. In either case, + every other sample is 0. Just get the offset right: */ + if (vp->panned == PANNED_RIGHT) buf++; + + if (vp->envelope_increment || vp->tremolo_phase_increment) + mix_single_signal(sp, buf, v, c); + else + mix_single(sp, buf, v, c); + } + } + } +} diff --git a/doomclassic/timidity/mix.h b/doomclassic/timidity/mix.h new file mode 100644 index 00000000..7d7f065d --- /dev/null +++ b/doomclassic/timidity/mix.h @@ -0,0 +1,27 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +In case you haven't heard, this program is free software; +you can redistribute it and/or modify it under the terms of the +GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +mix.h + +*/ + +extern void mix_voice( int32_t *buf, int v, int32_t c); +extern int recompute_envelope(int v); +extern void apply_envelope_to_amp(int v); diff --git a/doomclassic/timidity/output.cpp b/doomclassic/timidity/output.cpp new file mode 100644 index 00000000..e8f5dab1 --- /dev/null +++ b/doomclassic/timidity/output.cpp @@ -0,0 +1,138 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +output.c + +Audio output (to file / device) functions. +*/ + +#include "config.h" +#include "output.h" +#include "tables.h" + + +#ifdef SDL +extern PlayMode sdl_play_mode; +#define DEFAULT_PLAY_MODE &sdl_play_mode +#endif + +PlayMode *play_mode_list[] = { +#ifdef DEFAULT_PLAY_MODE + DEFAULT_PLAY_MODE, +#endif + 0 +}; + +#ifdef DEFAULT_PLAY_MODE +PlayMode *play_mode=DEFAULT_PLAY_MODE; +#endif + +/*****************************************************************/ +/* Some functions to convert signed 32-bit data to other formats */ + +void s32tos8(void *dp, int32_t *lp, int32_t c) +{ + int8_t *cp=(int8_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-8-GUARD_BITS); + if (l>127) l=127; + else if (l<-128) l=-128; + *cp++ = (int8_t) (l); + } +} + +void s32tou8(void *dp, int32_t *lp, int32_t c) +{ + uint8_t *cp=(uint8_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-8-GUARD_BITS); + if (l>127) l=127; + else if (l<-128) l=-128; + *cp++ = 0x80 ^ ((uint8_t) l); + } +} + +void s32tos16(void *dp, int32_t *lp, int32_t c) +{ + int16_t *sp=(int16_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-16-GUARD_BITS); + if (l > 32767) l=32767; + else if (l<-32768) l=-32768; + *sp++ = (int16_t)(l); + } +} + +void s32tou16(void *dp, int32_t *lp, int32_t c) +{ + uint16_t *sp=(uint16_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-16-GUARD_BITS); + if (l > 32767) l=32767; + else if (l<-32768) l=-32768; + *sp++ = 0x8000 ^ (uint16_t)(l); + } +} + +void s32tos16x(void *dp, int32_t *lp, int32_t c) +{ + int16_t *sp=(int16_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-16-GUARD_BITS); + if (l > 32767) l=32767; + else if (l<-32768) l=-32768; + *sp++ = XCHG_SHORT((int16_t)(l)); + } +} + +void s32tou16x(void *dp, int32_t *lp, int32_t c) +{ + uint16_t *sp=(uint16_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-16-GUARD_BITS); + if (l > 32767) l=32767; + else if (l<-32768) l=-32768; + *sp++ = XCHG_SHORT(0x8000 ^ (uint16_t)(l)); + } +} + +void s32toulaw(void *dp, int32_t *lp, int32_t c) +{ + uint8_t *up=(uint8_t *)(dp); + int32_t l; + while (c--) + { + l=(*lp++)>>(32-13-GUARD_BITS); + if (l > 4095) l=4095; + else if (l<-4096) l=-4096; + *up++ = _l2u[l]; + } +} diff --git a/doomclassic/timidity/output.h b/doomclassic/timidity/output.h new file mode 100644 index 00000000..d3648952 --- /dev/null +++ b/doomclassic/timidity/output.h @@ -0,0 +1,70 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +output.h + +*/ +#include +#include "timidity.h" +/* Data format encoding bits */ + +#define PE_MONO 0x01 /* versus stereo */ +#define PE_SIGNED 0x02 /* versus unsigned */ +#define PE_16BIT 0x04 /* versus 8-bit */ +#define PE_ULAW 0x08 /* versus linear */ +#define PE_BYTESWAP 0x10 /* versus the other way */ + +extern int init_buffers(int kbytes); + +/* Conversion functions -- These overwrite the int32_t data in *lp with +data in another format */ + +/* The size of the output buffers */ +extern int AUDIO_BUFFER_SIZE; + +/* Actual copy function */ +extern void (*s32tobuf)(void *dp, int32_t *lp, int32_t c); + +/* 8-bit signed and unsigned*/ +extern void s32tos8(void *dp, int32_t *lp, int32_t c); +extern void s32tou8(void *dp, int32_t *lp, int32_t c); + +/* 16-bit */ +extern void s32tos16(void *dp, int32_t *lp, int32_t c); +extern void s32tou16(void *dp, int32_t *lp, int32_t c); + +/* byte-exchanged 16-bit */ +extern void s32tos16x(void *dp, int32_t *lp, int32_t c); +extern void s32tou16x(void *dp, int32_t *lp, int32_t c); + +/* uLaw (8 bits) */ +extern void s32toulaw(void *dp, int32_t *lp, int32_t c); + +/* little-endian and big-endian specific */ +#ifdef LITTLE_ENDIAN +#define s32tou16l s32tou16 +#define s32tou16b s32tou16x +#define s32tos16l s32tos16 +#define s32tos16b s32tos16x +#else +#define s32tou16l s32tou16x +#define s32tou16b s32tou16 +#define s32tos16l s32tos16x +#define s32tos16b s32tos16 +#endif diff --git a/doomclassic/timidity/playmidi.cpp b/doomclassic/timidity/playmidi.cpp new file mode 100644 index 00000000..caa6ae6a --- /dev/null +++ b/doomclassic/timidity/playmidi.cpp @@ -0,0 +1,1018 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +playmidi.c -- random stuff in need of rearrangement + +*/ + +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" +#include "readmidi.h" +#include "output.h" +#include "mix.h" +#include "controls.h" +#include "timidity.h" + +#include "tables.h" + +#include "structs.h" + +//void Real_Tim_Free( void *pt ); + +Channel channel[16]; +Voice voice[MAX_VOICES]; + +int +voices=DEFAULT_VOICES; + +int32_t +control_ratio=0, +amplification=DEFAULT_AMPLIFICATION; + +float +master_volume; + + int32_t drumchannels=DEFAULT_DRUMCHANNELS; +int adjust_panning_immediately=0; + +static int midi_playing = 0; +static int32_t lost_notes, cut_notes; +static int32_t *buffer_pointer; +static int32_t buffered_count; +extern int32_t *common_buffer; + +static MidiEvent *event_list, *current_event; +static int32_t sample_count, current_sample; + +static void adjust_amplification(void) +{ + master_volume = (float)(amplification) / (float)100.0; +} + +static void reset_voices(void) +{ + int i; + for (i=0; ivolume(c, channel[c].volume); + ctl->expression(c, channel[c].expression); + ctl->sustain(c, channel[c].sustain); + ctl->pitch_bend(c, channel[c].pitchbend); +} + +static void reset_midi(void) +{ + int i; + for (i=0; i<16; i++) + { + reset_controllers(i); + /* The rest of these are unaffected by the Reset All Controllers event */ + channel[i].program=default_program; + channel[i].panning=NO_PANNING; + channel[i].pitchsens=2; + channel[i].bank=0; /* tone bank or drum set */ + } + reset_voices(); +} + +static void select_sample(int v, Instrument *ip) +{ + int32_t f, cdiff, diff; + int s,i; + Sample *sp, *closest; + + s=ip->samples; + sp=ip->sample; + + if (s==1) + { + voice[v].sample=sp; + return; + } + + f=voice[v].orig_frequency; + for (i=0; ilow_freq <= f && sp->high_freq >= f) + { + voice[v].sample=sp; + return; + } + sp++; + } + + /* + No suitable sample found! We'll select the sample whose root + frequency is closest to the one we want. (Actually we should + probably convert the low, high, and root frequencies to MIDI note + values and compare those.) */ + + cdiff=0x7FFFFFFF; + closest=sp=ip->sample; + for(i=0; iroot_freq - f; + if (diff<0) diff=-diff; + if (diffsample_rate) + return; + + if (voice[v].vibrato_control_ratio) + { + /* This instrument has vibrato. Invalidate any precomputed + sample_increments. */ + + int i=VIBRATO_SAMPLE_INCREMENTS; + while (i--) + voice[v].vibrato_sample_increment[i]=0; + } + + if (pb==0x2000 || pb<0 || pb>0x3FFF) + voice[v].frequency=voice[v].orig_frequency; + else + { + pb-=0x2000; + if (!(channel[voice[v].channel].pitchfactor)) + { + /* Damn. Somebody bent the pitch. */ + int32_t i=pb*channel[voice[v].channel].pitchsens; + if (pb<0) + i=-i; + channel[voice[v].channel].pitchfactor= + (float)(bend_fine[(i>>5) & 0xFF] * bend_coarse[i>>13]); + } + if (pb>0) + voice[v].frequency= + (int32_t)(channel[voice[v].channel].pitchfactor * + (double)(voice[v].orig_frequency)); + else + voice[v].frequency= + (int32_t)((double)(voice[v].orig_frequency) / + channel[voice[v].channel].pitchfactor); + } + + a = FSCALE(((double)(voice[v].sample->sample_rate) * + (double)(voice[v].frequency)) / + ((double)(voice[v].sample->root_freq) * + (double)(play_mode->rate)), + FRACTION_BITS); + + if (sign) + a = -a; /* need to preserve the loop direction */ + + voice[v].sample_increment = (int32_t)(a); +} + +static void recompute_amp(int v) +{ + int32_t tempamp; + + /* TODO: use fscale */ + + tempamp= (voice[v].velocity * + channel[voice[v].channel].volume * + channel[voice[v].channel].expression); /* 21 bits */ + + if (!(play_mode->encoding & PE_MONO)) + { + if (voice[v].panning > 60 && voice[v].panning < 68) + { + voice[v].panned=PANNED_CENTER; + + voice[v].left_amp= + FSCALENEG((double)(tempamp) * voice[v].sample->volume * master_volume, + 21); + } + else if (voice[v].panning<5) + { + voice[v].panned = PANNED_LEFT; + + voice[v].left_amp= + FSCALENEG((double)(tempamp) * voice[v].sample->volume * master_volume, + 20); + } + else if (voice[v].panning>123) + { + voice[v].panned = PANNED_RIGHT; + + voice[v].left_amp= /* left_amp will be used */ + FSCALENEG((double)(tempamp) * voice[v].sample->volume * master_volume, + 20); + } + else + { + voice[v].panned = PANNED_MYSTERY; + + voice[v].left_amp= + FSCALENEG((double)(tempamp) * voice[v].sample->volume * master_volume, + 27); + voice[v].right_amp=voice[v].left_amp * (voice[v].panning); + voice[v].left_amp *= (float)(127-voice[v].panning); + } + } + else + { + voice[v].panned=PANNED_CENTER; + + voice[v].left_amp= + FSCALENEG((double)(tempamp) * voice[v].sample->volume * master_volume, + 21); + } +} + +static void start_note(MidiEvent *e, int i) +{ + Instrument *ip; + int j; + + if (ISDRUMCHANNEL(e->channel)) + { + if (!(ip=drumset[channel[e->channel].bank]->tone[e->a].instrument)) + { + if (!(ip=drumset[0]->tone[e->a].instrument)) + return; /* No instrument? Then we can't play. */ + } + if (ip->samples != 1) + { + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, + "Strange: percussion instrument with %d samples!", ip->samples); + } + + if (ip->sample->note_to_use) /* Do we have a fixed pitch? */ + voice[i].orig_frequency=freq_table[(int)(ip->sample->note_to_use)]; + else + voice[i].orig_frequency=freq_table[e->a & 0x7F]; + + /* drums are supposed to have only one sample */ + voice[i].sample=ip->sample; + } + else + { + if (channel[e->channel].program==SPECIAL_PROGRAM) + ip=default_instrument; + else if (!(ip=tonebank[channel[e->channel].bank]-> + tone[channel[e->channel].program].instrument)) + { + if (!(ip=tonebank[0]->tone[channel[e->channel].program].instrument)) + return; /* No instrument? Then we can't play. */ + } + + if (ip->sample->note_to_use) /* Fixed-pitch instrument? */ + voice[i].orig_frequency=freq_table[(int)(ip->sample->note_to_use)]; + else + voice[i].orig_frequency=freq_table[e->a & 0x7F]; + select_sample(i, ip); + } + + voice[i].status=VOICE_ON; + voice[i].channel=e->channel; + voice[i].note=e->a; + voice[i].velocity=e->b; + voice[i].sample_offset=0; + voice[i].sample_increment=0; /* make sure it isn't negative */ + + voice[i].tremolo_phase=0; + voice[i].tremolo_phase_increment=voice[i].sample->tremolo_phase_increment; + voice[i].tremolo_sweep=voice[i].sample->tremolo_sweep_increment; + voice[i].tremolo_sweep_position=0; + + voice[i].vibrato_sweep=voice[i].sample->vibrato_sweep_increment; + voice[i].vibrato_sweep_position=0; + voice[i].vibrato_control_ratio=voice[i].sample->vibrato_control_ratio; + voice[i].vibrato_control_counter=voice[i].vibrato_phase=0; + for (j=0; jchannel].panning != NO_PANNING) + voice[i].panning=channel[e->channel].panning; + else + voice[i].panning=voice[i].sample->panning; + + recompute_freq(i); + recompute_amp(i); + if (voice[i].sample->modes & MODES_ENVELOPE) + { + /* Ramp up from 0 */ + voice[i].envelope_stage=0; + voice[i].envelope_volume=0; + voice[i].control_counter=0; + recompute_envelope(i); + apply_envelope_to_amp(i); + } + else + { + voice[i].envelope_increment=0; + apply_envelope_to_amp(i); + } + ctl->note(i); +} + +static void kill_note(int i) +{ + voice[i].status=VOICE_DIE; + ctl->note(i); +} + +/* Only one instance of a note can be playing on a single channel. */ +static void note_on(MidiEvent *e) +{ + int i=voices, lowest=-1; + int32_t lv=0x7FFFFFFF, v; + + while (i--) + { + if (voice[i].status == VOICE_FREE) + lowest=i; /* Can't get a lower volume than silence */ + else if (voice[i].channel==e->channel && + (voice[i].note==e->a || channel[voice[i].channel].mono)) + kill_note(i); + } + + if (lowest != -1) + { + /* Found a free voice. */ + start_note(e,lowest); + return; + } + + /* Look for the decaying note with the lowest volume */ + i=voices; + while (i--) + { + if ((voice[i].status!=VOICE_ON) && + (voice[i].status!=VOICE_DIE)) + { + v=voice[i].left_mix; + if ((voice[i].panned==PANNED_MYSTERY) && (voice[i].right_mix>v)) + v=voice[i].right_mix; + if (vnote(lowest); + start_note(e,lowest); + } + else + lost_notes++; +} + +static void finish_note(int i) +{ + if (voice[i].sample->modes & MODES_ENVELOPE) + { + /* We need to get the envelope out of Sustain stage */ + voice[i].envelope_stage=3; + voice[i].status=VOICE_OFF; + recompute_envelope(i); + apply_envelope_to_amp(i); + ctl->note(i); + } + else + { + /* Set status to OFF so resample_voice() will let this voice out + of its loop, if any. In any case, this voice dies when it + hits the end of its data (ofs>=data_length). */ + voice[i].status=VOICE_OFF; + } +} + +static void note_off(MidiEvent *e) +{ + int i=voices; + while (i--) + if (voice[i].status==VOICE_ON && + voice[i].channel==e->channel && + voice[i].note==e->a) + { + if (channel[e->channel].sustain) + { + voice[i].status=VOICE_SUSTAINED; + ctl->note(i); + } + else + finish_note(i); + return; + } +} + +/* Process the All Notes Off event */ +static void all_notes_off(int c) +{ + int i=voices; + ctl->cmsg(CMSG_INFO, VERB_DEBUG, "All notes off on channel %d", c); + while (i--) + if (voice[i].status==VOICE_ON && + voice[i].channel==c) + { + if (channel[c].sustain) + { + voice[i].status=VOICE_SUSTAINED; + ctl->note(i); + } + else + finish_note(i); + } +} + +/* Process the All Sounds Off event */ +static void all_sounds_off(int c) +{ + int i=voices; + while (i--) + if (voice[i].channel==c && + voice[i].status != VOICE_FREE && + voice[i].status != VOICE_DIE) + { + kill_note(i); + } +} + +static void adjust_pressure(MidiEvent *e) +{ + int i=voices; + while (i--) + if (voice[i].status==VOICE_ON && + voice[i].channel==e->channel && + voice[i].note==e->a) + { + voice[i].velocity=e->b; + recompute_amp(i); + apply_envelope_to_amp(i); + return; + } +} + +static void adjust_panning(int c) +{ + int i=voices; + while (i--) + if ((voice[i].channel==c) && + (voice[i].status==VOICE_ON || voice[i].status==VOICE_SUSTAINED)) + { + voice[i].panning=channel[c].panning; + recompute_amp(i); + apply_envelope_to_amp(i); + } +} + +static void drop_sustain(int c) +{ + int i=voices; + while (i--) + if (voice[i].status==VOICE_SUSTAINED && voice[i].channel==c) + finish_note(i); +} + +static void adjust_pitchbend(int c) +{ + int i=voices; + while (i--) + if (voice[i].status!=VOICE_FREE && voice[i].channel==c) + { + recompute_freq(i); + } +} + +static void adjust_volume(int c) +{ + int i=voices; + while (i--) + if (voice[i].channel==c && + (voice[i].status==VOICE_ON || voice[i].status==VOICE_SUSTAINED)) + { + recompute_amp(i); + apply_envelope_to_amp(i); + } +} + +static void seek_forward( int32_t until_time) +{ + reset_voices(); + while (current_event->time < until_time) + { + switch(current_event->type) + { + /* All notes stay off. Just handle the parameter changes. */ + + case ME_PITCH_SENS: + channel[current_event->channel].pitchsens= + current_event->a; + channel[current_event->channel].pitchfactor=0; + break; + + case ME_PITCHWHEEL: + channel[current_event->channel].pitchbend= + current_event->a + current_event->b * 128; + channel[current_event->channel].pitchfactor=0; + break; + + case ME_MAINVOLUME: + channel[current_event->channel].volume=current_event->a; + break; + + case ME_PAN: + channel[current_event->channel].panning=current_event->a; + break; + + case ME_EXPRESSION: + channel[current_event->channel].expression=current_event->a; + break; + + case ME_PROGRAM: + if (ISDRUMCHANNEL(current_event->channel)) + /* Change drum set */ + channel[current_event->channel].bank=current_event->a; + else + channel[current_event->channel].program=current_event->a; + break; + + case ME_SUSTAIN: + channel[current_event->channel].sustain=current_event->a; + break; + + case ME_RESET_CONTROLLERS: + reset_controllers(current_event->channel); + break; + + case ME_TONE_BANK: + channel[current_event->channel].bank=current_event->a; + break; + + case ME_EOT: + current_sample=current_event->time; + return; + } + current_event++; + } + /*current_sample=current_event->time;*/ + if (current_event != event_list) + current_event--; + current_sample=until_time; +} + +static void skip_to( int32_t until_time) +{ + if (current_sample > until_time) + current_sample=0; + + reset_midi(); + buffered_count=0; + buffer_pointer=common_buffer; + current_event=event_list; + + if (until_time) + seek_forward(until_time); + ctl->reset(); +} + +static int apply_controls(void) +{ + int rc, i, did_skip=0; + int val; + /* ASCII renditions of CD player pictograms indicate approximate effect */ + do + switch(rc=ctl->read(&val)) + { + case RC_QUIT: /* [] */ + case RC_LOAD_FILE: + case RC_NEXT: /* >>| */ + case RC_REALLY_PREVIOUS: /* |<< */ + return rc; + + case RC_CHANGE_VOLUME: + if (val>0 || amplification > -val) + amplification += val; + else + amplification=0; + if (amplification > MAX_AMPLIFICATION) + amplification=MAX_AMPLIFICATION; + adjust_amplification(); + for (i=0; imaster_volume(amplification); + break; + + case RC_PREVIOUS: /* |<< */ + if (current_sample < 2*play_mode->rate) + return RC_REALLY_PREVIOUS; + return RC_RESTART; + + case RC_RESTART: /* |<< */ + skip_to(0); + did_skip=1; + break; + + case RC_JUMP: + if (val >= sample_count) + return RC_NEXT; + skip_to(val); + return rc; + + case RC_FORWARD: /* >> */ + if (val+current_sample >= sample_count) + return RC_NEXT; + skip_to(val+current_sample); + did_skip=1; + break; + + case RC_BACK: /* << */ + if (current_sample > val) + skip_to(current_sample-val); + else + skip_to(0); /* We can't seek to end of previous song. */ + did_skip=1; + break; + } + while (rc!= RC_NO_RETURN_VALUE); + + /* Advertise the skip so that we stop computing the audio buffer */ + if (did_skip) + return RC_JUMP; + else + return rc; +} + +static void do_compute_data( int32_t count) +{ + int i; + memset(buffer_pointer, 0, + (play_mode->encoding & PE_MONO) ? (count * 4) : (count * 8)); + for (i=0; iencoding & PE_MONO ) + channels = 1; + else + channels = 2; + + if (!count) + { + if (buffered_count) { + s32tobuf(stream, common_buffer, channels*buffered_count); + if (bytes_written && (play_mode->encoding & PE_16BIT)) + *bytes_written += channels * buffered_count * 2; + else + *bytes_written += channels * buffered_count; + + //No need anymore + //play_mode->output_data(stream, channels*buffered_count, bytes_written); + } + buffer_pointer=common_buffer; + buffered_count=0; + return RC_NO_RETURN_VALUE; + } + + while ((count+buffered_count) >= AUDIO_BUFFER_SIZE) + { + do_compute_data(AUDIO_BUFFER_SIZE-buffered_count); + count -= AUDIO_BUFFER_SIZE-buffered_count; + s32tobuf(stream, common_buffer, channels*AUDIO_BUFFER_SIZE); + if (bytes_written && (play_mode->encoding & PE_16BIT)) + *bytes_written += channels * AUDIO_BUFFER_SIZE * 2; + else + *bytes_written += channels * AUDIO_BUFFER_SIZE; + + //play_mode->output_data(stream, channels*AUDIO_BUFFER_SIZE, bytes_written); + buffer_pointer=common_buffer; + buffered_count=0; + + ctl->current_time(current_sample); + if ((rc=apply_controls())!=RC_NO_RETURN_VALUE) + return rc; + } + if (count>0) + { + do_compute_data(count); + buffered_count += count; + buffer_pointer += (play_mode->encoding & PE_MONO) ? count : count*2; + } + return RC_NO_RETURN_VALUE; +} + +int Timidity_PlaySome(void *stream, int samples, int* bytes_written) +{ + int rc = RC_NO_RETURN_VALUE; + int32_t end_sample; + bool endSong = false; + + if (bytes_written) + *bytes_written = 0; + + if ( ! midi_playing ){ + return RC_NO_RETURN_VALUE; + } + end_sample = current_sample+samples; + + while ( current_sample < end_sample ){ + + /* Handle all events that should happen at this time */ + while ( !endSong && current_event->time <= current_sample){ + switch(current_event->type){ + + /* Effects affecting a single note */ + case ME_NOTEON: + if (!(current_event->b)) /* Velocity 0? */ + note_off(current_event); + else + note_on(current_event); + break; + + case ME_NOTEOFF: + note_off(current_event); + break; + + case ME_KEYPRESSURE: + adjust_pressure(current_event); + break; + + /* Effects affecting a single channel */ + + case ME_PITCH_SENS: + channel[current_event->channel].pitchsens=current_event->a; + channel[current_event->channel].pitchfactor=0; + break; + + case ME_PITCHWHEEL: + channel[current_event->channel].pitchbend= + current_event->a + current_event->b * 128; + channel[current_event->channel].pitchfactor=0; + /* Adjust pitch for notes already playing */ + adjust_pitchbend(current_event->channel); + ctl->pitch_bend(current_event->channel, + channel[current_event->channel].pitchbend); + break; + + case ME_MAINVOLUME: + channel[current_event->channel].volume=current_event->a; + adjust_volume(current_event->channel); + ctl->volume(current_event->channel, current_event->a); + break; + + case ME_PAN: + channel[current_event->channel].panning=current_event->a; + if (adjust_panning_immediately) + adjust_panning(current_event->channel); + ctl->panning(current_event->channel, current_event->a); + break; + + case ME_EXPRESSION: + channel[current_event->channel].expression=current_event->a; + adjust_volume(current_event->channel); + ctl->expression(current_event->channel, current_event->a); + break; + + case ME_PROGRAM: + if (ISDRUMCHANNEL(current_event->channel)){ + /* Change drum set */ + channel[current_event->channel].bank=current_event->a; + } + else + { + channel[current_event->channel].program=current_event->a; + } + ctl->program(current_event->channel, current_event->a); + break; + + case ME_SUSTAIN: + channel[current_event->channel].sustain=current_event->a; + if (!current_event->a) + drop_sustain(current_event->channel); + ctl->sustain(current_event->channel, current_event->a); + break; + + case ME_RESET_CONTROLLERS: + reset_controllers(current_event->channel); + redraw_controllers(current_event->channel); + break; + + case ME_ALL_NOTES_OFF: + all_notes_off(current_event->channel); + break; + + case ME_ALL_SOUNDS_OFF: + all_sounds_off(current_event->channel); + break; + + case ME_TONE_BANK: + channel[current_event->channel].bank=current_event->a; + break; + + case ME_EOT: + /* Give the last notes a couple of seconds to decay */ + //ctl->cmsg(CMSG_INFO, VERB_VERBOSE, + // "Playing time: ~%d seconds, ", current_sample/play_mode->rate+2); + //ctl->cmsg(CMSG_INFO, VERB_VERBOSE, + // "Notes cut: %d, ", cut_notes); + //ctl->cmsg(CMSG_INFO, VERB_VERBOSE, + // "Notes lost totally: %d", lost_notes); + + midi_playing = 0; + rc = RC_TUNE_END; + endSong = true; + + current_event--; + } + + current_event++; + } + if (current_event->time > end_sample) + rc=compute_data(stream, end_sample-current_sample, bytes_written); + else + rc=compute_data(stream, current_event->time-current_sample, bytes_written); + ctl->refresh(); + if ( endSong || ((rc!=RC_NO_RETURN_VALUE) && (rc!=RC_JUMP))) + break; + } + + if ( endSong ) { + return RC_TUNE_END; + } + return rc; +} + + +void Timidity_SetVolume(int volume) +{ + int i; + if (volume > MAX_AMPLIFICATION) + amplification=MAX_AMPLIFICATION; + else + if (volume < 0) + amplification=0; + else + amplification=volume; + adjust_amplification(); + for (i=0; imaster_volume(amplification); +} + +MidiSong *Timidity_LoadSong(char *midifile) +{ + MidiSong *song; + int32_t events; + idFile * fp; + + /* Allocate memory for the song */ + song = (MidiSong *)safe_malloc(sizeof(*song)); + memset(song, 0, sizeof(*song)); + + /* Open the file */ + fp = open_file(midifile, 1, OF_VERBOSE); + if ( fp != NULL ) { + song->events=read_midi_file(fp, &events, &song->samples); + close_file(fp); + } + + /* Make sure everything is okay */ + if (!song->events) { + Real_Tim_Free(song); + song = NULL; + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, "Song had null events! Returning NULL."); + } + return(song); +} + +MidiSong *Timidity_LoadSongMem(unsigned char* buffer, size_t length) +{ + MidiSong *song; + int32_t events; + + song = (MidiSong *)safe_malloc(sizeof(*song)); + memset(song, 0, sizeof(*song)); + + song->events = read_midi_buffer(buffer, length, &events, &song->samples); + + if (!song->events) { + Real_Tim_Free(song); + song = NULL; + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, "Song had null events! Returning NULL."); + } + return(song); +} + +void Timidity_Start(MidiSong *song) +{ + load_missing_instruments(); + adjust_amplification(); + sample_count = song->samples; + event_list = song->events; + lost_notes=cut_notes=0; + + skip_to(0); + midi_playing = 1; +} + +int Timidity_Active(void) +{ + return(midi_playing); +} + +void Timidity_Stop(void) +{ + midi_playing = 0; +} + +void Timidity_FreeSong(MidiSong *song) +{ + //if (free_instruments_afterwards) + //free_instruments(); + + Real_Tim_Free(song->events); + Real_Tim_Free(song); +} + +extern sample_t *resample_buffer; +extern int32_t *common_buffer; + +void Timidity_Shutdown(void) { + free_instruments(); + + Real_Tim_Free( resample_buffer ); + Real_Tim_Free( common_buffer ); +} diff --git a/doomclassic/timidity/playmidi.h b/doomclassic/timidity/playmidi.h new file mode 100644 index 00000000..7de8e4dc --- /dev/null +++ b/doomclassic/timidity/playmidi.h @@ -0,0 +1,113 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +playmidi.h + +*/ + +#include "structs.h" + +/* Midi events */ +#define ME_NONE 0 +#define ME_NOTEON 1 +#define ME_NOTEOFF 2 +#define ME_KEYPRESSURE 3 +#define ME_MAINVOLUME 4 +#define ME_PAN 5 +#define ME_SUSTAIN 6 +#define ME_EXPRESSION 7 +#define ME_PITCHWHEEL 8 +#define ME_PROGRAM 9 +#define ME_TEMPO 10 +#define ME_PITCH_SENS 11 + +#define ME_ALL_SOUNDS_OFF 12 +#define ME_RESET_CONTROLLERS 13 +#define ME_ALL_NOTES_OFF 14 +#define ME_TONE_BANK 15 + +#define ME_LYRIC 16 + +#define ME_EOT 99 + +typedef struct { + int + bank, program, volume, sustain, panning, pitchbend, expression, + mono, /* one note only on this channel -- not implemented yet */ + pitchsens; + /* chorus, reverb... Coming soon to a 300-MHz, eight-way superscalar + processor near you */ + float + pitchfactor; /* precomputed pitch bend factor to save some fdiv's */ +} Channel; + +/* Causes the instrument's default panning to be used. */ +#define NO_PANNING -1 + +typedef struct { + uint8_t + status, channel, note, velocity; + Sample *sample; + int32_t + orig_frequency, frequency, + sample_offset, sample_increment, + envelope_volume, envelope_target, envelope_increment, + tremolo_sweep, tremolo_sweep_position, + tremolo_phase, tremolo_phase_increment, + vibrato_sweep, vibrato_sweep_position; + + final_volume_t left_mix, right_mix; + + float + left_amp, right_amp, tremolo_volume; + int32_t + vibrato_sample_increment[VIBRATO_SAMPLE_INCREMENTS]; + int + vibrato_phase, vibrato_control_ratio, vibrato_control_counter, + envelope_stage, control_counter, panning, panned; + +} Voice; + +/* Voice status options: */ +#define VOICE_FREE 0 +#define VOICE_ON 1 +#define VOICE_SUSTAINED 2 +#define VOICE_OFF 3 +#define VOICE_DIE 4 + +/* Voice panned options: */ +#define PANNED_MYSTERY 0 +#define PANNED_LEFT 1 +#define PANNED_RIGHT 2 +#define PANNED_CENTER 3 +/* Anything but PANNED_MYSTERY only uses the left volume */ + +extern Channel channel[16]; +extern Voice voice[MAX_VOICES]; + +extern int32_t control_ratio, amp_with_poly, amplification; +extern int32_t drumchannels; +extern int adjust_panning_immediately; +extern int voices; + +#define ISDRUMCHANNEL(c) ((drumchannels & (1<<(c)))) + +extern int play_midi(MidiEvent *el, int32_t events, int32_t samples); +extern int play_midi_file(char *fn); +extern void dumb_pass_playing_list(int number_of_files, char *list_of_files[]); diff --git a/doomclassic/timidity/readmidi.cpp b/doomclassic/timidity/readmidi.cpp new file mode 100644 index 00000000..cec26a5a --- /dev/null +++ b/doomclassic/timidity/readmidi.cpp @@ -0,0 +1,758 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "../../neo/idlib/precompiled.h" + +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" +#include "readmidi.h" +#include "output.h" +#include "controls.h" + + +void Real_Tim_Free( void *pt ); + + int32_t quietchannels=0; + +/* to avoid some unnecessary parameter passing */ +static MidiEventList *evlist; +static int32_t event_count; +static idFile * fp = NULL; +static int32_t at; +static unsigned char* local_buffer = 0; +static size_t local_buffer_length = 0; +static size_t local_buffer_cur = 0; + +/* These would both fit into 32 bits, but they are often added in +large multiples, so it's simpler to have two roomy ints */ +static int32_t sample_increment, sample_correction; /*samples per MIDI delta-t*/ + +static int32_t read_local(void* buffer, size_t len, size_t count) +{ + if (fp && len > 0) { + return (int32_t)fp->Read(buffer, len * count ) / len; + } else if( local_buffer != NULL ) { + if (len * count + local_buffer_cur > local_buffer_length) { + memcpy(buffer, &local_buffer[local_buffer_cur], local_buffer_length - local_buffer_cur); + return(int32_t)(local_buffer_length - local_buffer_cur)/len; + } else { + memcpy(buffer, &local_buffer[local_buffer_cur], len * count); + local_buffer_cur += len * count; + return(count); + } + } + + return 0; +} + +static void skip_local(size_t len) +{ + if (fp) { + skip(fp, len); + } else { + if (len + local_buffer_cur > local_buffer_length) { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "skip_local failed on memory buffer"); + } else { + local_buffer_cur += len; + } + } +} + +/* Computes how many (fractional) samples one MIDI delta-time unit contains */ +static void compute_sample_increment( int32_t tempo, int32_t divisions) +{ + double a; + a = (double) (tempo) * (double) (play_mode->rate) * (65536.0/1000000.0) / + (double)(divisions); + + sample_correction = (int32_t)(a) & 0xFFFF; + sample_increment = (int32_t)(a) >> 16; + + ctl->cmsg(CMSG_INFO, VERB_DEBUG, "Samples per delta-t: %d (correction %d)", + sample_increment, sample_correction); +} + +/* Read variable-length number (7 bits per byte, MSB first) */ +static int32_t getvl(void) +{ + int32_t l=0; + uint8_t c; + for (;;) + { + read_local(&c,1,1); + l += (c & 0x7f); + if (!(c & 0x80)) return l; + l<<=7; + } +} + +/* Print a string from the file, followed by a newline. Any non-ASCII +or unprintable characters will be converted to periods. */ +static int dumpstring( int32_t len, char *label) +{ + signed char *s=(signed char *)safe_malloc(len+1); + if (len != (int32_t)read_local(s, 1, len)) + { + Real_Tim_Free(s); + return -1; + } + s[len]='\0'; + while (len--) + { + if (s[len]<32) + s[len]='.'; + } + ctl->cmsg(CMSG_TEXT, VERB_VERBOSE, "%s%s", label, s); + Real_Tim_Free(s); + return 0; +} + +#define MIDIEVENT(at,t,ch,pa,pb) \ + newEventList=(MidiEventList*)safe_malloc(sizeof(MidiEventList)); \ + newEventList->event.time=at; newEventList->event.type=t; newEventList->event.channel=ch; \ + newEventList->event.a=pa; newEventList->event.b=pb; newEventList->next=0;\ + return newEventList; + +#define MAGIC_EOT ((MidiEventList *)(-1)) + +/* Read a MIDI event, returning a freshly allocated element that can +be linked to the event list */ +static MidiEventList *read_midi_event(void) +{ + static uint8_t laststatus, lastchan; + static uint8_t nrpn=0, rpn_msb[16], rpn_lsb[16]; /* one per channel */ + uint8_t me, type, a,b,c; + int32_t len; + MidiEventList *newEventList; + + for (;;) + { + at+=getvl(); + if (read_local(&me,1,1)!=1) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: read_midi_event: %s", + current_filename, strerror(errno)); + return 0; + } + + if(me==0xF0 || me == 0xF7) /* SysEx event */ + { + len=getvl(); + skip_local(len); + } + else if(me==0xFF) /* Meta event */ + { + read_local(&type,1,1); + len=getvl(); + if (type>0 && type<16) + { + static char *label[]={ + "Text event: ", "Text: ", "Copyright: ", "Track name: ", + "Instrument: ", "Lyric: ", "Marker: ", "Cue point: "}; + dumpstring(len, label[(type>7) ? 0 : type]); + } + else + switch(type) + { + case 0x2F: /* End of Track */ + return MAGIC_EOT; + + case 0x51: /* Tempo */ + read_local(&a,1,1); read_local(&b,1,1); read_local(&c,1,1); + MIDIEVENT(at, ME_TEMPO, c, a, b); + + default: + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + "(Meta event type 0x%02x, length %ld)", type, len); + skip_local(len); + break; + } + } + else + { + a=me; + if (a & 0x80) /* status byte */ + { + lastchan=a & 0x0F; + laststatus=(a>>4) & 0x07; + read_local(&a, 1,1); + a &= 0x7F; + } + switch(laststatus) + { + case 0: /* Note off */ + read_local(&b, 1,1); + b &= 0x7F; + MIDIEVENT(at, ME_NOTEOFF, lastchan, a,b); + + case 1: /* Note on */ + read_local(&b, 1,1); + b &= 0x7F; + MIDIEVENT(at, ME_NOTEON, lastchan, a,b); + + case 2: /* Key Pressure */ + read_local(&b, 1,1); + b &= 0x7F; + MIDIEVENT(at, ME_KEYPRESSURE, lastchan, a, b); + + case 3: /* Control change */ + read_local(&b, 1,1); + b &= 0x7F; + { + int control=255; + switch(a) + { + case 7: control=ME_MAINVOLUME; break; + case 10: control=ME_PAN; break; + case 11: control=ME_EXPRESSION; break; + case 64: control=ME_SUSTAIN; break; + case 120: control=ME_ALL_SOUNDS_OFF; break; + case 121: control=ME_RESET_CONTROLLERS; break; + case 123: control=ME_ALL_NOTES_OFF; break; + + /* These should be the SCC-1 tone bank switch + commands. I don't know why there are two, or + why the latter only allows switching to bank 0. + Also, some MIDI files use 0 as some sort of + continuous controller. This will cause lots of + warnings about undefined tone banks. */ + case 0: control=ME_TONE_BANK; break; + case 32: + if (b!=0) + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + "(Strange: tone bank change 0x20%02x)", b); + else + control=ME_TONE_BANK; + break; + + case 100: nrpn=0; rpn_msb[lastchan]=b; break; + case 101: nrpn=0; rpn_lsb[lastchan]=b; break; + case 99: nrpn=1; rpn_msb[lastchan]=b; break; + case 98: nrpn=1; rpn_lsb[lastchan]=b; break; + + case 6: + if (nrpn) + { + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + "(Data entry (MSB) for NRPN %02x,%02x: %ld)", + rpn_msb[lastchan], rpn_lsb[lastchan], + b); + break; + } + + switch((rpn_msb[lastchan]<<8) | rpn_lsb[lastchan]) + { + case 0x0000: /* Pitch bend sensitivity */ + control=ME_PITCH_SENS; + break; + + case 0x7F7F: /* RPN reset */ + /* reset pitch bend sensitivity to 2 */ + MIDIEVENT(at, ME_PITCH_SENS, lastchan, 2, 0); + + default: + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + "(Data entry (MSB) for RPN %02x,%02x: %ld)", + rpn_msb[lastchan], rpn_lsb[lastchan], + b); + break; + } + break; + + default: + ctl->cmsg(CMSG_INFO, VERB_DEBUG, + "(Control %d: %d)", a, b); + break; + } + if (control != 255) + { + MIDIEVENT(at, control, lastchan, b, 0); + } + } + break; + + case 4: /* Program change */ + a &= 0x7f; + MIDIEVENT(at, ME_PROGRAM, lastchan, a, 0); + + case 5: /* Channel pressure - NOT IMPLEMENTED */ + break; + + case 6: /* Pitch wheel */ + read_local(&b, 1,1); + b &= 0x7F; + MIDIEVENT(at, ME_PITCHWHEEL, lastchan, a, b); + + default: + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "*** Can't happen: status 0x%02X, channel 0x%02X", + laststatus, lastchan); + break; + } + } + } + + return newEventList; +} + +#undef MIDIEVENT + +/* Read a midi track into the linked list, either merging with any previous +tracks or appending to them. */ +static int read_track(int append) +{ + MidiEventList *meep; + MidiEventList *next, *newEventList; + int32_t len; + char tmp[4]; + + meep=evlist; + if (append && meep) + { + /* find the last event in the list */ + for (; meep->next; meep=(MidiEventList *)meep->next) + ; + at=meep->event.time; + } + else + at=0; + + /* Check the formalities */ + + if ((read_local(tmp,1,4) != 4) || (read_local(&len,4,1) != 1)) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: Can't read track header.", current_filename); + return -1; + } + len=BE_LONG(len); + if (memcmp(tmp, "MTrk", 4)) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: Corrupt MIDI file.", current_filename); + return -2; + } + + for (;;) + { + if (!(newEventList=read_midi_event())) /* Some kind of error */ + return -2; + + if (newEventList==MAGIC_EOT) /* End-of-track Hack. */ + { + return 0; + } + + next=(MidiEventList *)meep->next; + while (next && (next->event.time < newEventList->event.time)) + { + meep=next; + next=(MidiEventList *)meep->next; + } + + newEventList->next=next; + meep->next=newEventList; + + event_count++; /* Count the event. (About one?) */ + meep=newEventList; + } +} + +/* Free the linked event list from memory. */ +static void free_midi_list(void) +{ + MidiEventList *meep, *next; + if (!(meep=evlist)) return; + while (meep) + { + next=(MidiEventList *)meep->next; + Real_Tim_Free(meep); + meep=next; + } + evlist=0; +} + +/* Allocate an array of MidiEvents and fill it from the linked list of +events, marking used instruments for loading. Convert event times to +samples: handle tempo changes. Strip unnecessary events from the list. +Free the linked list. */ +static MidiEvent *groom_list( int32_t divisions, int32_t *eventsp, int32_t *samplesp) +{ + MidiEvent *groomed_list, *lp; + MidiEventList *meep; + int32_t i, our_event_count, tempo, skip_local_this_event, new_value; + int32_t sample_cum, samples_to_do, at, st, dt, counting_time; + + int current_bank[16], current_set[16], current_program[16]; + /* Or should each bank have its own current program? */ + + for (i=0; i<16; i++) + { + current_bank[i]=0; + current_set[i]=0; + current_program[i]=default_program; + } + + tempo=500000; + compute_sample_increment(tempo, divisions); + + /* This may allocate a bit more than we need */ + groomed_list=lp=(MidiEvent*)safe_malloc(sizeof(MidiEvent) * (event_count+1)); + meep=evlist; + + our_event_count=0; + st=at=sample_cum=0; + counting_time=2; /* We strip any silence before the first NOTE ON. */ + + for (i=0; icmsg(CMSG_INFO, VERB_DEBUG_SILLY, + "%6d: ch %2d: event %d (%d,%d)", + meep->event.time, meep->event.channel + 1, + meep->event.type, meep->event.a, meep->event.b); + + if (meep->event.type==ME_TEMPO) + { + tempo= + meep->event.channel + meep->event.b * 256 + meep->event.a * 65536; + compute_sample_increment(tempo, divisions); + skip_local_this_event=1; + } + else if ((quietchannels & (1<event.channel))) + skip_local_this_event=1; + else switch (meep->event.type) + { + case ME_PROGRAM: + if (ISDRUMCHANNEL(meep->event.channel)) + { + if (drumset[meep->event.a]) /* Is this a defined drumset? */ + new_value=meep->event.a; + else + { + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, + "Drum set %d is undefined", meep->event.a); + new_value=meep->event.a=0; + } + if (current_set[meep->event.channel] != new_value) + current_set[meep->event.channel]=new_value; + else + skip_local_this_event=1; + } + else + { + new_value=meep->event.a; + if ((current_program[meep->event.channel] != SPECIAL_PROGRAM) + && (current_program[meep->event.channel] != new_value)) + current_program[meep->event.channel] = new_value; + else + skip_local_this_event=1; + } + break; + + case ME_NOTEON: + if (counting_time) + counting_time=1; + if (ISDRUMCHANNEL(meep->event.channel)) + { + /* Mark this instrument to be loaded */ + if (!(drumset[current_set[meep->event.channel]] + ->tone[meep->event.a].instrument)) + drumset[current_set[meep->event.channel]] + ->tone[meep->event.a].instrument= + MAGIC_LOAD_INSTRUMENT; + } + else + { + if (current_program[meep->event.channel]==SPECIAL_PROGRAM) + break; + /* Mark this instrument to be loaded */ + if (!(tonebank[current_bank[meep->event.channel]] + ->tone[current_program[meep->event.channel]].instrument)) + tonebank[current_bank[meep->event.channel]] + ->tone[current_program[meep->event.channel]].instrument= + MAGIC_LOAD_INSTRUMENT; + } + break; + + case ME_TONE_BANK: + if (ISDRUMCHANNEL(meep->event.channel)) + { + skip_local_this_event=1; + break; + } + if (tonebank[meep->event.a]) /* Is this a defined tone bank? */ + new_value=meep->event.a; + else + { + ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, + "Tone bank %d is undefined", meep->event.a); + new_value=meep->event.a=0; + } + if (current_bank[meep->event.channel]!=new_value) + current_bank[meep->event.channel]=new_value; + else + skip_local_this_event=1; + break; + } + + /* Recompute time in samples*/ + if ((dt=meep->event.time - at) && !counting_time) + { + samples_to_do=sample_increment * dt; + sample_cum += sample_correction * dt; + if (sample_cum & 0xFFFF0000) + { + samples_to_do += ((sample_cum >> 16) & 0xFFFF); + sample_cum &= 0x0000FFFF; + } + st += samples_to_do; + } + else if (counting_time==1) counting_time=0; + if (!skip_local_this_event) + { + /* Add the event to the list */ + *lp=meep->event; + lp->time=st; + lp++; + our_event_count++; + } + at=meep->event.time; + meep=(MidiEventList *)meep->next; + } + /* Add an End-of-Track event */ + lp->time=st; + lp->type=ME_EOT; + our_event_count++; + free_midi_list(); + + *eventsp=our_event_count; + *samplesp=st; + return groomed_list; +} + +MidiEvent *read_midi_file(idFile * mfp, int32_t *count, int32_t *sp) +{ + int32_t len, divisions; + int16_t format, tracks, divisions_tmp; + int i; + char tmp[4]; + + fp = mfp; + local_buffer = 0; + local_buffer_length = 0; + local_buffer_cur = 0; + + event_count=0; + at=0; + evlist=0; + int first = (read_local(tmp,1,4)); + int second = (read_local(&len,4,1)); + if ( first != 4 || second != 1) + { + if (0) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: %s", current_filename, + strerror(errno)); + } + else + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: Not a MIDI file!", current_filename); + return 0; + } + len=BE_LONG(len); + if (memcmp(tmp, "MThd", 4) || len < 6) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: Not a MIDI file!", current_filename); + return 0; + } + + read_local(&format, 2, 1); + read_local(&tracks, 2, 1); + read_local(&divisions_tmp, 2, 1); + format=BE_SHORT(format); + tracks=BE_SHORT(tracks); + divisions_tmp=BE_SHORT(divisions_tmp); + + if (divisions_tmp<0) + { + /* SMPTE time -- totally untested. Got a MIDI file that uses this? */ + divisions= + (int32_t)(-(divisions_tmp/256)) * (int32_t)(divisions_tmp & 0xFF); + } + else divisions=(int32_t)(divisions_tmp); + + if (len > 6) + { + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, + "%s: MIDI file header size %ld bytes", + current_filename, len); + skip_local(len-6); /* skip_local the excess */ + } + if (format<0 || format >2) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: Unknown MIDI file format %d", current_filename, format); + return 0; + } + //ctl->cmsg(CMSG_INFO, VERB_VERBOSE, + // "Format: %d Tracks: %d Divisions: %d", format, tracks, divisions); + + /* Put a do-nothing event first in the list for easier processing */ + evlist=(MidiEventList *)safe_malloc(sizeof(MidiEventList)); + evlist->event.time=0; + evlist->event.type=ME_NONE; + evlist->next=0; + event_count++; + + switch(format) + { + case 0: + if (read_track(0)) + { + free_midi_list(); + return 0; + } + break; + + case 1: + for (i=0; icmsg(CMSG_ERROR, VERB_NORMAL, "Not a MIDI file!"); + return 0; + } + len=BE_LONG(len); + if (memcmp(tmp, "MThd", 4) || len < 6) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: Not a MIDI file!", current_filename); + return 0; + } + + read_local(&format, 2, 1); + read_local(&tracks, 2, 1); + read_local(&divisions_tmp, 2, 1); + format=BE_SHORT(format); + tracks=BE_SHORT(tracks); + divisions_tmp=BE_SHORT(divisions_tmp); + + if (divisions_tmp<0) { + /* SMPTE time -- totally untested. Got a MIDI file that uses this? */ + divisions= (int32_t)(-(divisions_tmp/256)) * (int32_t)(divisions_tmp & 0xFF); + } + else divisions=(int32_t)(divisions_tmp); + + if (len > 6) + { + ctl->cmsg(CMSG_WARNING, VERB_NORMAL, + "%s: MIDI file header size %ld bytes", + current_filename, len); + skip_local(len-6); /* skip_local the excess */ + } + if (format<0 || format >2) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: Unknown MIDI file format %d", current_filename, format); + return 0; + } + //ctl->cmsg(CMSG_INFO, VERB_VERBOSE, + // "Format: %d Tracks: %d Divisions: %d", format, tracks, divisions); + + /* Put a do-nothing event first in the list for easier processing */ + evlist=(MidiEventList *)safe_malloc(sizeof(MidiEventList)); + evlist->event.time=0; + evlist->event.type=ME_NONE; + evlist->next=0; + event_count++; + + switch(format) + { + case 0: + if (read_track(0)) + { + free_midi_list(); + return 0; + } + break; + + case 1: + for (i=0; i + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +readmidi.h + +*/ + +class idFile; + +typedef struct { + MidiEvent event; + void *next; +} MidiEventList; + +extern int32_t quietchannels; + +extern MidiEvent *read_midi_file(idFile * mfp, int32_t *count, int32_t *sp); +extern MidiEvent *read_midi_buffer(unsigned char* buffer, size_t length, int32_t *count, int32_t *sp); diff --git a/doomclassic/timidity/resample.cpp b/doomclassic/timidity/resample.cpp new file mode 100644 index 00000000..e9086e1f --- /dev/null +++ b/doomclassic/timidity/resample.cpp @@ -0,0 +1,738 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +resample.c +*/ + +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" +#include "output.h" +#include "controls.h" +#include "tables.h" +#include "resample.h" + +#ifdef LINEAR_INTERPOLATION +# if defined(LOOKUP_HACK) && defined(LOOKUP_INTERPOLATION) +# define RESAMPLATION \ + v1=src[ofs>>FRACTION_BITS];\ + v2=src[(ofs>>FRACTION_BITS)+1];\ + *dest++ = v1 + (iplookup[(((v2-v1)<<5) & 0x03FE0) | \ + ((ofs & FRACTION_MASK) >> (FRACTION_BITS-5))]); +# else +# define RESAMPLATION \ + v1=src[ofs>>FRACTION_BITS];\ + v2=src[(ofs>>FRACTION_BITS)+1];\ + *dest++ = v1 + (((v2-v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); +# endif +# define INTERPVARS sample_t v1, v2 +#else +/* Earplugs recommended for maximum listening enjoyment */ +# define RESAMPLATION *dest++=src[ofs>>FRACTION_BITS]; +# define INTERPVARS +#endif + +#define FINALINTERP if (ofs == le) *dest++=src[ofs>>FRACTION_BITS]; +/* So it isn't interpolation. At least it's final. */ + +extern sample_t *resample_buffer; +void Real_Tim_Free( void *pt ); + + +/*************** resampling with fixed increment *****************/ + +static sample_t *rs_plain(int v, int32_t *countptr) +{ + + /* Play sample until end, then free the voice. */ + + INTERPVARS; + Voice + *vp=&voice[v]; + sample_t + *dest=resample_buffer, + *src=vp->sample->data; + int32_t + ofs=vp->sample_offset, + incr=vp->sample_increment, + le=vp->sample->data_length, + count=*countptr; + +#ifdef PRECALC_LOOPS + int32_t i; + + if (incr<0) incr = -incr; /* In case we're coming out of a bidir loop */ + + /* Precalc how many times we should go through the loop. + NOTE: Assumes that incr > 0 and that ofs <= le */ + i = (le - ofs) / incr + 1; + + if (i > count) + { + i = count; + count = 0; + } + else count -= i; + + while (i--) + { + RESAMPLATION; + ofs += incr; + } + + if (ofs >= le) + { + FINALINTERP; + vp->status=VOICE_FREE; + ctl->note(v); + *countptr-=count+1; + } + +#else /* PRECALC_LOOPS */ + while (count--) + { + RESAMPLATION; + ofs += incr; + if (ofs >= le) + { + FINALINTERP; + vp->status=VOICE_FREE; + ctl->note(v); + *countptr-=count+1; + break; + } + } +#endif /* PRECALC_LOOPS */ + + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_loop(Voice *vp, int32_t count) +{ + + /* Play sample until end-of-loop, skip back and continue. */ + + INTERPVARS; + int32_t + ofs=vp->sample_offset, + incr=vp->sample_increment, + le=vp->sample->loop_end, + ll=le - vp->sample->loop_start; + sample_t + *dest=resample_buffer, + *src=vp->sample->data; + +#ifdef PRECALC_LOOPS + int32_t i; + + while (count) + { + if (ofs >= le) + /* NOTE: Assumes that ll > incr and that incr > 0. */ + ofs -= ll; + /* Precalc how many times we should go through the loop */ + i = (le - ofs) / incr + 1; + if (i > count) + { + i = count; + count = 0; + } + else count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + } +#else + while (count--) + { + RESAMPLATION; + ofs += incr; + if (ofs>=le) + ofs -= ll; /* Hopefully the loop is longer than an increment. */ + } +#endif + + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_bidir(Voice *vp, int32_t count) +{ + INTERPVARS; + int32_t + ofs=vp->sample_offset, + incr=vp->sample_increment, + le=vp->sample->loop_end, + ls=vp->sample->loop_start; + sample_t + *dest=resample_buffer, + *src=vp->sample->data; + +#ifdef PRECALC_LOOPS + int32_t + le2 = le<<1, + ls2 = ls<<1, + i; + /* Play normally until inside the loop region */ + + if (ofs <= ls) + { + /* NOTE: Assumes that incr > 0, which is NOT always the case + when doing bidirectional looping. I have yet to see a case + where both ofs <= ls AND incr < 0, however. */ + i = (ls - ofs) / incr + 1; + if (i > count) + { + i = count; + count = 0; + } + else count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + } + + /* Then do the bidirectional looping */ + + while(count) + { + /* Precalc how many times we should go through the loop */ + i = ((incr > 0 ? le : ls) - ofs) / incr + 1; + if (i > count) + { + i = count; + count = 0; + } + else count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (ofs>=le) + { + /* fold the overshoot back in */ + ofs = le2 - ofs; + incr *= -1; + } + else if (ofs <= ls) + { + ofs = ls2 - ofs; + incr *= -1; + } + } + +#else /* PRECALC_LOOPS */ + /* Play normally until inside the loop region */ + + if (ofs < ls) + { + while (count--) + { + RESAMPLATION; + ofs += incr; + if (ofs>=ls) + break; + } + } + + /* Then do the bidirectional looping */ + + if (count>0) + while (count--) + { + RESAMPLATION; + ofs += incr; + if (ofs>=le) + { + /* fold the overshoot back in */ + ofs = le - (ofs - le); + incr = -incr; + } + else if (ofs <= ls) + { + ofs = ls + (ls - ofs); + incr = -incr; + } + } +#endif /* PRECALC_LOOPS */ + vp->sample_increment=incr; + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +/*********************** vibrato versions ***************************/ + +/* We only need to compute one half of the vibrato sine cycle */ +static int vib_phase_to_inc_ptr(int phase) +{ + if (phase < VIBRATO_SAMPLE_INCREMENTS/2) + return VIBRATO_SAMPLE_INCREMENTS/2-1-phase; + else if (phase >= 3*VIBRATO_SAMPLE_INCREMENTS/2) + return 5*VIBRATO_SAMPLE_INCREMENTS/2-1-phase; + else + return phase-VIBRATO_SAMPLE_INCREMENTS/2; +} + +static int32_t update_vibrato(Voice *vp, int sign) +{ + int32_t depth; + int phase, pb; + double a; + + if (vp->vibrato_phase++ >= 2*VIBRATO_SAMPLE_INCREMENTS-1) + vp->vibrato_phase=0; + phase=vib_phase_to_inc_ptr(vp->vibrato_phase); + + if (vp->vibrato_sample_increment[phase]) + { + if (sign) + return -vp->vibrato_sample_increment[phase]; + else + return vp->vibrato_sample_increment[phase]; + } + + /* Need to compute this sample increment. */ + + depth=vp->sample->vibrato_depth<<7; + + if (vp->vibrato_sweep) + { + /* Need to update sweep */ + vp->vibrato_sweep_position += vp->vibrato_sweep; + if (vp->vibrato_sweep_position >= (1<vibrato_sweep=0; + else + { + /* Adjust depth */ + depth *= vp->vibrato_sweep_position; + depth >>= SWEEP_SHIFT; + } + } + + a = FSCALE(((double)(vp->sample->sample_rate) * + (double)(vp->frequency)) / + ((double)(vp->sample->root_freq) * + (double)(play_mode->rate)), + FRACTION_BITS); + + pb=(int)((sine(vp->vibrato_phase * + (SINE_CYCLE_LENGTH/(2*VIBRATO_SAMPLE_INCREMENTS))) + * (double)(depth) * VIBRATO_AMPLITUDE_TUNING)); + + if (pb<0) + { + pb=-pb; + a /= bend_fine[(pb>>5) & 0xFF] * bend_coarse[pb>>13]; + } + else + a *= bend_fine[(pb>>5) & 0xFF] * bend_coarse[pb>>13]; + + /* If the sweep's over, we can store the newly computed sample_increment */ + if (!vp->vibrato_sweep) + vp->vibrato_sample_increment[phase]=(int32_t) a; + + if (sign) + a = -a; /* need to preserve the loop direction */ + + return (int32_t) a; +} + +static sample_t *rs_vib_plain(int v, int32_t *countptr) +{ + + /* Play sample until end, then free the voice. */ + + INTERPVARS; + Voice *vp=&voice[v]; + sample_t + *dest=resample_buffer, + *src=vp->sample->data; + int32_t + le=vp->sample->data_length, + ofs=vp->sample_offset, + incr=vp->sample_increment, + count=*countptr; + int + cc=vp->vibrato_control_counter; + + /* This has never been tested */ + + if (incr<0) incr = -incr; /* In case we're coming out of a bidir loop */ + + while (count--) + { + if (!cc--) + { + cc=vp->vibrato_control_ratio; + incr=update_vibrato(vp, 0); + } + RESAMPLATION; + ofs += incr; + if (ofs >= le) + { + FINALINTERP; + vp->status=VOICE_FREE; + ctl->note(v); + *countptr-=count+1; + break; + } + } + + vp->vibrato_control_counter=cc; + vp->sample_increment=incr; + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_vib_loop(Voice *vp, int32_t count) +{ + + /* Play sample until end-of-loop, skip back and continue. */ + + INTERPVARS; + int32_t + ofs=vp->sample_offset, + incr=vp->sample_increment, + le=vp->sample->loop_end, + ll=le - vp->sample->loop_start; + sample_t + *dest=resample_buffer, + *src=vp->sample->data; + int + cc=vp->vibrato_control_counter; + +#ifdef PRECALC_LOOPS + int32_t i; + int + vibflag=0; + + while (count) + { + /* Hopefully the loop is longer than an increment */ + if(ofs >= le) + ofs -= ll; + /* Precalc how many times to go through the loop, taking + the vibrato control ratio into account this time. */ + i = (le - ofs) / incr + 1; + if(i > count) i = count; + if(i > cc) + { + i = cc; + vibflag = 1; + } + else cc -= i; + count -= i; + while(i--) + { + RESAMPLATION; + ofs += incr; + } + if(vibflag) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(vp, 0); + vibflag = 0; + } + } + +#else /* PRECALC_LOOPS */ + while (count--) + { + if (!cc--) + { + cc=vp->vibrato_control_ratio; + incr=update_vibrato(vp, 0); + } + RESAMPLATION; + ofs += incr; + if (ofs>=le) + ofs -= ll; /* Hopefully the loop is longer than an increment. */ + } +#endif /* PRECALC_LOOPS */ + + vp->vibrato_control_counter=cc; + vp->sample_increment=incr; + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +static sample_t *rs_vib_bidir(Voice *vp, int32_t count) +{ + INTERPVARS; + int32_t + ofs=vp->sample_offset, + incr=vp->sample_increment, + le=vp->sample->loop_end, + ls=vp->sample->loop_start; + sample_t + *dest=resample_buffer, + *src=vp->sample->data; + int + cc=vp->vibrato_control_counter; + +#ifdef PRECALC_LOOPS + int32_t + le2=le<<1, + ls2=ls<<1, + i; + int + vibflag = 0; + + /* Play normally until inside the loop region */ + while (count && (ofs <= ls)) + { + i = (ls - ofs) / incr + 1; + if (i > count) i = count; + if (i > cc) + { + i = cc; + vibflag = 1; + } + else cc -= i; + count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (vibflag) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(vp, 0); + vibflag = 0; + } + } + + /* Then do the bidirectional looping */ + + while (count) + { + /* Precalc how many times we should go through the loop */ + i = ((incr > 0 ? le : ls) - ofs) / incr + 1; + if(i > count) i = count; + if(i > cc) + { + i = cc; + vibflag = 1; + } + else cc -= i; + count -= i; + while (i--) + { + RESAMPLATION; + ofs += incr; + } + if (vibflag) + { + cc = vp->vibrato_control_ratio; + incr = update_vibrato(vp, (incr < 0)); + vibflag = 0; + } + if (ofs >= le) + { + /* fold the overshoot back in */ + ofs = le2 - ofs; + incr *= -1; + } + else if (ofs <= ls) + { + ofs = ls2 - ofs; + incr *= -1; + } + } + +#else /* PRECALC_LOOPS */ + /* Play normally until inside the loop region */ + + if (ofs < ls) + { + while (count--) + { + if (!cc--) + { + cc=vp->vibrato_control_ratio; + incr=update_vibrato(vp, 0); + } + RESAMPLATION; + ofs += incr; + if (ofs>=ls) + break; + } + } + + /* Then do the bidirectional looping */ + + if (count>0) + while (count--) + { + if (!cc--) + { + cc=vp->vibrato_control_ratio; + incr=update_vibrato(vp, (incr < 0)); + } + RESAMPLATION; + ofs += incr; + if (ofs>=le) + { + /* fold the overshoot back in */ + ofs = le - (ofs - le); + incr = -incr; + } + else if (ofs <= ls) + { + ofs = ls + (ls - ofs); + incr = -incr; + } + } +#endif /* PRECALC_LOOPS */ + + vp->vibrato_control_counter=cc; + vp->sample_increment=incr; + vp->sample_offset=ofs; /* Update offset */ + return resample_buffer; +} + +sample_t *resample_voice(int v, int32_t *countptr) +{ + int32_t ofs; + uint8_t modes; + Voice *vp=&voice[v]; + + if (!(vp->sample->sample_rate)) + { + /* Pre-resampled data -- just update the offset and check if + we're out of data. */ + ofs=vp->sample_offset >> FRACTION_BITS; /* Kind of silly to use + FRACTION_BITS here... */ + if (*countptr >= (vp->sample->data_length>>FRACTION_BITS) - ofs) + { + /* Note finished. Free the voice. */ + vp->status = VOICE_FREE; + ctl->note(v); + + /* Let the caller know how much data we had left */ + *countptr = (vp->sample->data_length>>FRACTION_BITS) - ofs; + } + else + vp->sample_offset += *countptr << FRACTION_BITS; + + return vp->sample->data+ofs; + } + + /* Need to resample. Use the proper function. */ + modes=vp->sample->modes; + + if (vp->vibrato_control_ratio) + { + if ((modes & MODES_LOOPING) && + ((modes & MODES_ENVELOPE) || + (vp->status==VOICE_ON || vp->status==VOICE_SUSTAINED))) + { + if (modes & MODES_PINGPONG) + return rs_vib_bidir(vp, *countptr); + else + return rs_vib_loop(vp, *countptr); + } + else + return rs_vib_plain(v, countptr); + } + else + { + if ((modes & MODES_LOOPING) && + ((modes & MODES_ENVELOPE) || + (vp->status==VOICE_ON || vp->status==VOICE_SUSTAINED))) + { + if (modes & MODES_PINGPONG) + return rs_bidir(vp, *countptr); + else + return rs_loop(vp, *countptr); + } + else + return rs_plain(v, countptr); + } +} + +void pre_resample(Sample * sp) +{ + double a, xdiff; + int32_t incr, ofs, newlen, count; + int16_t *newdata, *dest, *src = (int16_t *) sp->data; + int16_t v1, v2, v3, v4, *vptr; + static const char note_name[12][3] = + { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; + + ctl->cmsg(CMSG_INFO, VERB_NOISY, " * pre-resampling for note %d (%s%d)", + sp->note_to_use, + note_name[sp->note_to_use % 12], (sp->note_to_use & 0x7F) / 12); + + a = ((double) (sp->sample_rate) * freq_table[(int) (sp->note_to_use)]) / + ((double) (sp->root_freq) * play_mode->rate); + newlen = (int32_t)(sp->data_length / a); + dest = newdata = (int16_t*)safe_malloc(newlen >> (FRACTION_BITS - 1)); + + count = (newlen >> FRACTION_BITS) - 1; + ofs = incr = (sp->data_length - (1 << FRACTION_BITS)) / count; + + if (--count) + *dest++ = src[0]; + + /* Since we're pre-processing and this doesn't have to be done in + real-time, we go ahead and do the full sliding cubic interpolation. */ + while (--count) + { + vptr = src + (ofs >> FRACTION_BITS); + v1 = *(vptr - 1); + v2 = *vptr; + v3 = *(vptr + 1); + v4 = *(vptr + 2); + xdiff = FSCALENEG(ofs & FRACTION_MASK, FRACTION_BITS); + *dest++ = (int16_t)(v2 + (xdiff / 6.0) * (-2 * v1 - 3 * v2 + 6 * v3 - v4 + + xdiff * (3 * (v1 - 2 * v2 + v3) + xdiff * (-v1 + 3 * (v2 - v3) + v4)))); + ofs += incr; + } + + if (ofs & FRACTION_MASK) + { + v1 = src[ofs >> FRACTION_BITS]; + v2 = src[(ofs >> FRACTION_BITS) + 1]; + *dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS); + } + else + *dest++ = src[ofs >> FRACTION_BITS]; + + sp->data_length = newlen; + sp->loop_start = (int32_t)(sp->loop_start / a); + sp->loop_end = (int32_t)(sp->loop_end / a); + Real_Tim_Free(sp->data); + sp->data = (sample_t *) newdata; + sp->sample_rate = 0; +} diff --git a/doomclassic/timidity/resample.h b/doomclassic/timidity/resample.h new file mode 100644 index 00000000..49f03613 --- /dev/null +++ b/doomclassic/timidity/resample.h @@ -0,0 +1,24 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +resample.h +*/ + +extern sample_t *resample_voice(int v, int32_t *countptr); +extern void pre_resample(Sample *sp); diff --git a/doomclassic/timidity/sdl_a.cpp b/doomclassic/timidity/sdl_a.cpp new file mode 100644 index 00000000..f4218f26 --- /dev/null +++ b/doomclassic/timidity/sdl_a.cpp @@ -0,0 +1,59 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +sdl_a.c + +Functions to output RIFF WAVE format data to a file or stdout. + +*/ + +#include "config.h" +#include "output.h" + +/* export the playback mode */ + +#define dpm sdl_play_mode + +static int open_output(void); /* 0=success, 1=warning, -1=fatal error */ +static void close_output(void); +static void output_data(int *buf, int count, int* bytes_written); +static void flush_output(void); +static void purge_output(void); + + +PlayMode dpm = { + DEFAULT_RATE, PE_16BIT|PE_SIGNED, + "SDL audio", 0, "d:\\out.wav", + + open_output, + close_output, + output_data, + flush_output, + purge_output +}; + +/* Dummies */ +static int open_output(void){ + return 0; +} + +static void output_data(int *buf, int count, int* bytes_written){} +static void close_output(void){} +static void flush_output(void) { } +static void purge_output(void) { } diff --git a/doomclassic/timidity/sdl_c.cpp b/doomclassic/timidity/sdl_c.cpp new file mode 100644 index 00000000..08483024 --- /dev/null +++ b/doomclassic/timidity/sdl_c.cpp @@ -0,0 +1,138 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +sdl_c.c +Minimal control mode -- no interaction, just stores messages. +*/ + +#include "../../neo/idlib/precompiled.h" + +#include +#include +#include +#include + +#include "config.h" +#include "common.h" +#include "output.h" +#include "controls.h" +#include "instrum.h" +#include "playmidi.h" + +static void ctl_refresh(void); +static void ctl_total_time(int tt); +static void ctl_master_volume(int mv); +static void ctl_file_name(char *name); +static void ctl_current_time(int ct); +static void ctl_note(int v); +static void ctl_program(int ch, int val); +static void ctl_volume(int channel, int val); +static void ctl_expression(int channel, int val); +static void ctl_panning(int channel, int val); +static void ctl_sustain(int channel, int val); +static void ctl_pitch_bend(int channel, int val); +static void ctl_reset(void); +static int ctl_open(int using_stdin, int using_stdout); +static void ctl_close(void); +static int ctl_read(int *valp); +static int cmsg(int type, int verbosity_level, char *fmt, ...); + +#ifdef _DEBUG +#define safeOutputDebug(x) printf( "%s", x ) +#else +#define safeOutputDebug(x) +#endif + +/**********************************/ +/* export the interface functions */ + +#define ctl sdl_control_mode + +ControlMode ctl= +{ + "SDL interface", 's', + 1,0,0, + ctl_open,NULL, ctl_close, ctl_read, cmsg, + ctl_refresh, ctl_reset, ctl_file_name, ctl_total_time, ctl_current_time, + ctl_note, + ctl_master_volume, ctl_program, ctl_volume, + ctl_expression, ctl_panning, ctl_sustain, ctl_pitch_bend +}; + +static int ctl_open(int using_stdin, int using_stdout) +{ + ctl.opened=1; + return 0; +} + +static void ctl_close(void) +{ + ctl.opened=0; +} + +static int ctl_read(int *valp) +{ + return RC_NO_RETURN_VALUE; +} +extern void SendDebugMsg(const char*); +extern bool debugOutput; +static int cmsg(int type, int verbosity_level, char *fmt, ...) +{ +#ifdef _DEBUG + va_list ap; + + va_start(ap, fmt); + idStr::vsnPrintf(timidity_error, TIMIDITY_ERROR_MAX_CHARS - 1, fmt, ap); + va_end(ap); + + strcat( timidity_error, "\n" ); + + if ( debugOutput && verbosity_level <= VERB_VERBOSE ) { + safeOutputDebug( timidity_error ); + } +#endif + + return 0; +} + +static void ctl_refresh(void) { } + +static void ctl_total_time(int tt) {} + +static void ctl_master_volume(int mv) {} + +static void ctl_file_name(char *name) {} + +static void ctl_current_time(int ct) {} + +static void ctl_note(int v) {} + +static void ctl_program(int ch, int val) {} + +static void ctl_volume(int channel, int val) {} + +static void ctl_expression(int channel, int val) {} + +static void ctl_panning(int channel, int val) {} + +static void ctl_sustain(int channel, int val) {} + +static void ctl_pitch_bend(int channel, int val) {} + +static void ctl_reset(void) {} diff --git a/doomclassic/timidity/structs.h b/doomclassic/timidity/structs.h new file mode 100644 index 00000000..f0a204f4 --- /dev/null +++ b/doomclassic/timidity/structs.h @@ -0,0 +1,40 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +In case you haven't heard, this program is free software; +you can redistribute it and/or modify it under the terms of the +GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +structs.h + +*/ +#ifndef TIMIDITY_STRUCTS_H +#define TIMIDITY_STRUCTS_H + +#include + +typedef struct { + int32_t time; + uint8_t channel, type, a, b; +} MidiEvent; + +struct _MidiSong { + int32_t samples; + MidiEvent *events; +}; + + +#endif \ No newline at end of file diff --git a/doomclassic/timidity/tables.cpp b/doomclassic/timidity/tables.cpp new file mode 100644 index 00000000..daa532e4 --- /dev/null +++ b/doomclassic/timidity/tables.cpp @@ -0,0 +1,901 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +The 8-bit uLaw to 16-bit PCM and the 13-bit-PCM to 8-bit uLaw +tables were lifted from the rsynth-2.0 sources. The README says: + +> +> This is a text to speech system produced by integrating various pieces +> of code and tables of data, which are all (I believe) in the public domain. +> +> The bulk of the intergration was done by myself, that is Nick Ing-Simmons. +> I can be reached via my employer at nik@tiuk.ti.com. +> + +*/ + +#include +#include "config.h" +#include "common.h" +#include "tables.h" + + int32_t freq_table[128]= +{ + 8176, 8662, 9177, 9723, + 10301, 10913, 11562, 12250, + 12978, 13750, 14568, 15434, + + 16352, 17324, 18354, 19445, + 20602, 21827, 23125, 24500, + 25957, 27500, 29135, 30868, + + 32703, 34648, 36708, 38891, + 41203, 43654, 46249, 48999, + 51913, 55000, 58270, 61735, + + 65406, 69296, 73416, 77782, + 82407, 87307, 92499, 97999, + 103826, 110000, 116541, 123471, + + 130813, 138591, 146832, 155563, + 164814, 174614, 184997, 195998, + 207652, 220000, 233082, 246942, + + 261626, 277183, 293665, 311127, + 329628, 349228, 369994, 391995, + 415305, 440000, 466164, 493883, + + 523251, 554365, 587330, 622254, + 659255, 698456, 739989, 783991, + 830609, 880000, 932328, 987767, + + 1046502, 1108731, 1174659, 1244508, + 1318510, 1396913, 1479978, 1567982, + 1661219, 1760000, 1864655, 1975533, + + 2093005, 2217461, 2349318, 2489016, + 2637020, 2793826, 2959955, 3135963, + 3322438, 3520000, 3729310, 3951066, + + 4186009, 4434922, 4698636, 4978032, + 5274041, 5587652, 5919911, 6271927, + 6644875, 7040000, 7458620, 7902133, + + 8372018, 8869844, 9397273, 9956063, + 10548082, 11175303, 11839822, 12543854 +}; + +/* v=2.^((x/127-1) * 6) */ +double vol_table[128] = +{ + 0.015625, 0.016145143728351113, 0.016682602624583379, 0.017237953096759438, + 0.017811790741104401, 0.01840473098076444, 0.019017409725829021, 0.019650484055324921, + 0.020304632921913132, 0.020980557880044631, 0.021678983838355849, 0.02240065983711079, + 0.023146359851523596, 0.023916883621822989, 0.024713057510949051, 0.025535735390801884, + 0.026385799557992876, 0.027264161680080529, 0.028171763773305786, 0.029109579212875332, + 0.030078613776876421, 0.031079906724942836, 0.032114531912828696, 0.033183598944085631, + 0.034288254360078256, 0.035429682869614412, 0.036609108619508737, 0.037827796507442342, + 0.039087053538526394, 0.040388230227024875, 0.041732722044739302, 0.043121970917609151, + 0.044557466772132896, 0.046040749133268132, 0.047573408775524545, 0.049157089429020417, + 0.050793489542332405, 0.05248436410402918, 0.054231526524842463, 0.056036850582493913, + 0.057902272431264008, 0.059829792678457581, 0.061821478529993396, 0.063879466007418645, + 0.066005962238725971, 0.068203247825430205, 0.070473679288442961, 0.072819691595368496, + 0.075243800771931268, 0.077748606600335793, 0.080336795407452768, 0.083011142945821612, + 0.085774517370559328, 0.088629882315368294, 0.091580300070941839, 0.094628934869176312, + 0.097779056276712184, 0.10103404270144323, 0.1043973850157546, 0.1078726903003755, + 0.11146368571286204, 0.11517422248485852, 0.11900828005242428, 0.12296997032385605, + 0.12706354208958254, 0.13129338557886089, 0.13566403716816194, 0.14018018424629392, + 0.14484667024148207, 0.14966849981579558, 0.15465084423249356, 0.15979904690204472, + 0.16511862911277009, 0.17061529595225433, 0.17629494242587571, 0.18216365977901747, + 0.18822774202974024, 0.19449369271892172, 0.20096823188510385, 0.20765830327152621, + 0.21457108177307616, 0.22171398113114205, 0.2290946618846218, 0.23672103958561411, + 0.2446012932886038, 0.25274387432224471, 0.26115751535314891, 0.26985123975140174, + 0.27883437126784744, 0.28811654403352405, 0.29770771289197112, 0.30761816407549192, + 0.31785852623682015, 0.32843978184802081, 0.33937327897885317, 0.3506707434672246, + 0.36234429149478936, 0.37440644258117928, 0.38687013301080181, 0.39974872970660535, + 0.41305604456569134, 0.42680634927214656, 0.44101439060298442, 0.45569540624360722, + 0.47086514112975281, 0.48653986433345225, 0.50273638651110641, 0.51947207793239625, + 0.53676488710936021, 0.55463336004561792, 0.57309666012638816, 0.59217458867062556, + 0.61188760616732485, 0.63225685421876243, 0.65330417821421161, 0.67505215075844849, + 0.69752409588017272, 0.72074411404630734, 0.74473710800900605, 0.76952880951308478, + 0.79514580689252357, 0.82161557358563286, 0.84896649759946774, 0.87722791195508854, + 0.90643012614631979, 0.93660445864574493, 0.96778327049280244, 1 +}; + +double bend_fine[256] = { + 1, 1.0002256593050698, 1.0004513695322617, 1.0006771306930664, + 1.0009029427989777, 1.0011288058614922, 1.0013547198921082, 1.0015806849023274, + 1.0018067009036538, 1.002032767907594, 1.0022588859256572, 1.0024850549693551, + 1.0027112750502025, 1.0029375461797159, 1.0031638683694153, 1.0033902416308227, + 1.0036166659754628, 1.0038431414148634, 1.0040696679605541, 1.0042962456240678, + 1.0045228744169397, 1.0047495543507072, 1.0049762854369111, 1.0052030676870944, + 1.0054299011128027, 1.0056567857255843, 1.00588372153699, 1.006110708558573, + 1.0063377468018897, 1.0065648362784985, 1.0067919769999607, 1.0070191689778405, + 1.0072464122237039, 1.0074737067491204, 1.0077010525656616, 1.0079284496849015, + 1.0081558981184175, 1.008383397877789, 1.008610948974598, 1.0088385514204294, + 1.0090662052268706, 1.0092939104055114, 1.0095216669679448, 1.0097494749257656, + 1.009977334290572, 1.0102052450739643, 1.0104332072875455, 1.0106612209429215, + 1.0108892860517005, 1.0111174026254934, 1.0113455706759138, 1.0115737902145781, + 1.0118020612531047, 1.0120303838031153, 1.0122587578762337, 1.012487183484087, + 1.0127156606383041, 1.0129441893505169, 1.0131727696323602, 1.0134014014954713, + 1.0136300849514894, 1.0138588200120575, 1.0140876066888203, 1.0143164449934257, + 1.0145453349375237, 1.0147742765327674, 1.0150032697908125, 1.0152323147233171, + 1.015461411341942, 1.0156905596583505, 1.0159197596842091, 1.0161490114311862, + 1.0163783149109531, 1.0166076701351838, 1.0168370771155553, 1.0170665358637463, + 1.0172960463914391, 1.0175256087103179, 1.0177552228320703, 1.0179848887683858, + 1.0182146065309567, 1.0184443761314785, 1.0186741975816487, 1.0189040708931674, + 1.0191339960777379, 1.0193639731470658, 1.0195940021128593, 1.0198240829868295, + 1.0200542157806898, 1.0202844005061564, 1.0205146371749483, 1.0207449257987866, + 1.0209752663893958, 1.0212056589585028, 1.0214361035178368, 1.0216666000791297, + 1.0218971486541166, 1.0221277492545349, 1.0223584018921241, 1.0225891065786274, + 1.0228198633257899, 1.0230506721453596, 1.023281533049087, 1.0235124460487257, + 1.0237434111560313, 1.0239744283827625, 1.0242054977406807, 1.0244366192415495, + 1.0246677928971357, 1.0248990187192082, 1.025130296719539, 1.0253616269099028, + 1.0255930093020766, 1.0258244439078401, 1.0260559307389761, 1.0262874698072693, + 1.0265190611245079, 1.0267507047024822, 1.0269824005529853, 1.027214148687813, + 1.0274459491187637, 1.0276778018576387, 1.0279097069162415, 1.0281416643063788, + 1.0283736740398595, 1.0286057361284953, 1.0288378505841009, 1.0290700174184932, + 1.0293022366434921, 1.0295345082709197, 1.0297668323126017, 1.0299992087803651, + 1.030231637686041, 1.0304641190414621, 1.0306966528584645, 1.0309292391488862, + 1.0311618779245688, 1.0313945691973556, 1.0316273129790936, 1.0318601092816313, + 1.0320929581168212, 1.0323258594965172, 1.0325588134325767, 1.0327918199368598, + 1.0330248790212284, 1.0332579906975481, 1.0334911549776868, 1.033724371873515, + 1.0339576413969056, 1.0341909635597348, 1.0344243383738811, 1.0346577658512259, + 1.034891246003653, 1.0351247788430489, 1.0353583643813031, 1.0355920026303078, + 1.0358256936019572, 1.0360594373081489, 1.0362932337607829, 1.0365270829717617, + 1.0367609849529913, 1.0369949397163791, 1.0372289472738365, 1.0374630076372766, + 1.0376971208186156, 1.0379312868297725, 1.0381655056826686, 1.0383997773892284, + 1.0386341019613787, 1.0388684794110492, 1.0391029097501721, 1.0393373929906822, + 1.0395719291445176, 1.0398065182236185, 1.0400411602399278, 1.0402758552053915, + 1.0405106031319582, 1.0407454040315787, 1.0409802579162071, 1.0412151647977996, + 1.0414501246883161, 1.0416851375997183, 1.0419202035439705, 1.0421553225330404, + 1.042390494578898, 1.042625719693516, 1.0428609978888699, 1.043096329176938, + 1.0433317135697009, 1.0435671510791424, 1.0438026417172486, 1.0440381854960086, + 1.0442737824274138, 1.044509432523459, 1.044745135796141, 1.0449808922574599, + 1.0452167019194181, 1.0454525647940205, 1.0456884808932754, 1.0459244502291931, + 1.0461604728137874, 1.0463965486590741, 1.046632677777072, 1.0468688601798024, + 1.0471050958792898, 1.047341384887561, 1.0475777272166455, 1.047814122878576, + 1.048050571885387, 1.0482870742491166, 1.0485236299818055, 1.0487602390954964, + 1.0489969016022356, 1.0492336175140715, 1.0494703868430555, 1.0497072096012419, + 1.0499440858006872, 1.0501810154534512, 1.050417998571596, 1.0506550351671864, + 1.0508921252522903, 1.0511292688389782, 1.0513664659393229, 1.0516037165654004, + 1.0518410207292894, 1.0520783784430709, 1.0523157897188296, 1.0525532545686513, + 1.0527907730046264, 1.0530283450388465, 1.0532659706834067, 1.0535036499504049, + 1.0537413828519411, 1.0539791694001188, 1.0542170096070436, 1.0544549034848243, + 1.0546928510455722, 1.0549308523014012, 1.0551689072644284, 1.0554070159467728, + 1.0556451783605572, 1.0558833945179062, 1.0561216644309479, 1.0563599881118126, + 1.0565983655726334, 1.0568367968255465, 1.0570752818826903, 1.0573138207562065, + 1.057552413458239, 1.0577910600009348, 1.0580297603964437, 1.058268514656918, + 1.0585073227945128, 1.0587461848213857, 1.058985100749698, 1.0592240705916123 +}; + +double bend_coarse[128] = { + 1, 1.0594630943592953, 1.122462048309373, 1.189207115002721, + 1.2599210498948732, 1.3348398541700344, 1.4142135623730951, 1.4983070768766815, + 1.5874010519681994, 1.681792830507429, 1.7817974362806785, 1.8877486253633868, + 2, 2.1189261887185906, 2.244924096618746, 2.3784142300054421, + 2.5198420997897464, 2.6696797083400687, 2.8284271247461903, 2.996614153753363, + 3.1748021039363992, 3.363585661014858, 3.5635948725613571, 3.7754972507267741, + 4, 4.2378523774371812, 4.4898481932374912, 4.7568284600108841, + 5.0396841995794928, 5.3393594166801366, 5.6568542494923806, 5.993228307506727, + 6.3496042078727974, 6.727171322029716, 7.1271897451227151, 7.5509945014535473, + 8, 8.4757047548743625, 8.9796963864749824, 9.5136569200217682, + 10.079368399158986, 10.678718833360273, 11.313708498984761, 11.986456615013454, + 12.699208415745595, 13.454342644059432, 14.25437949024543, 15.101989002907095, + 16, 16.951409509748721, 17.959392772949972, 19.027313840043536, + 20.158736798317967, 21.357437666720553, 22.627416997969522, 23.972913230026901, + 25.398416831491197, 26.908685288118864, 28.508758980490853, 30.203978005814196, + 32, 33.902819019497443, 35.918785545899944, 38.054627680087073, + 40.317473596635935, 42.714875333441107, 45.254833995939045, 47.945826460053802, + 50.796833662982394, 53.817370576237728, 57.017517960981706, 60.407956011628393, + 64, 67.805638038994886, 71.837571091799887, 76.109255360174146, + 80.63494719327187, 85.429750666882214, 90.509667991878089, 95.891652920107603, + 101.59366732596479, 107.63474115247546, 114.03503592196341, 120.81591202325679, + 128, 135.61127607798977, 143.67514218359977, 152.21851072034829, + 161.26989438654374, 170.85950133376443, 181.01933598375618, 191.78330584021521, + 203.18733465192958, 215.26948230495091, 228.07007184392683, 241.63182404651357, + 256, 271.22255215597971, 287.35028436719938, 304.43702144069658, + 322.53978877308765, 341.71900266752868, 362.03867196751236, 383.56661168043064, + 406.37466930385892, 430.53896460990183, 456.14014368785394, 483.26364809302686, + 512, 542.44510431195943, 574.70056873439876, 608.87404288139317, + 645.0795775461753, 683.43800533505737, 724.07734393502471, 767.13322336086128, + 812.74933860771785, 861.07792921980365, 912.28028737570787, 966.52729618605372, + 1024, 1084.8902086239189, 1149.4011374687975, 1217.7480857627863, + 1290.1591550923506, 1366.8760106701147, 1448.1546878700494, 1534.2664467217226 +}; + +#ifdef LOOKUP_SINE +static double sine_table[257]= +{ + 0, 0.0061358846491544753, 0.012271538285719925, 0.01840672990580482, + 0.024541228522912288, 0.030674803176636626, 0.036807222941358832, 0.04293825693494082, + 0.049067674327418015, 0.055195244349689934, 0.061320736302208578, 0.067443919563664051, + 0.073564563599667426, 0.079682437971430126, 0.085797312344439894, 0.091908956497132724, + 0.098017140329560604, 0.10412163387205459, 0.11022220729388306, 0.11631863091190475, + 0.1224106751992162, 0.12849811079379317, 0.13458070850712617, 0.14065823933284921, + 0.14673047445536175, 0.15279718525844344, 0.15885814333386145, 0.16491312048996989, + 0.17096188876030122, 0.17700422041214875, 0.18303988795514095, 0.18906866414980619, + 0.19509032201612825, 0.2011046348420919, 0.20711137619221856, 0.21311031991609136, + 0.2191012401568698, 0.22508391135979283, 0.23105810828067111, 0.2370236059943672, + 0.24298017990326387, 0.24892760574572015, 0.25486565960451457, 0.26079411791527551, + 0.26671275747489837, 0.27262135544994898, 0.27851968938505306, 0.28440753721127188, + 0.29028467725446233, 0.29615088824362379, 0.30200594931922808, 0.30784964004153487, + 0.31368174039889152, 0.31950203081601569, 0.32531029216226293, 0.33110630575987643, + 0.33688985339222005, 0.34266071731199438, 0.34841868024943456, 0.35416352542049034, + 0.35989503653498811, 0.36561299780477385, 0.37131719395183754, 0.37700741021641826, + 0.38268343236508978, 0.38834504669882625, 0.3939920400610481, 0.39962419984564679, + 0.40524131400498986, 0.41084317105790391, 0.41642956009763715, 0.42200027079979968, + 0.42755509343028208, 0.43309381885315196, 0.43861623853852766, 0.4441221445704292, + 0.44961132965460654, 0.45508358712634384, 0.46053871095824001, 0.46597649576796618, + 0.47139673682599764, 0.47679923006332209, 0.48218377207912272, 0.487550160148436, + 0.49289819222978404, 0.49822766697278187, 0.50353838372571758, 0.50883014254310699, + 0.51410274419322166, 0.51935599016558964, 0.52458968267846895, 0.52980362468629461, + 0.53499761988709715, 0.54017147272989285, 0.54532498842204646, 0.55045797293660481, + 0.55557023301960218, 0.56066157619733603, 0.56573181078361312, 0.57078074588696726, + 0.57580819141784534, 0.58081395809576453, 0.58579785745643886, 0.59075970185887416, + 0.59569930449243336, 0.60061647938386897, 0.60551104140432555, 0.61038280627630948, + 0.61523159058062682, 0.6200572117632891, 0.62485948814238634, 0.62963823891492698, + 0.63439328416364549, 0.63912444486377573, 0.64383154288979139, 0.64851440102211244, + 0.65317284295377676, 0.65780669329707864, 0.66241577759017178, 0.66699992230363747, + 0.67155895484701833, 0.67609270357531592, 0.68060099779545302, 0.68508366777270036, + 0.68954054473706683, 0.693971460889654, 0.69837624940897292, 0.7027547444572253, + 0.70710678118654746, 0.71143219574521643, 0.71573082528381859, 0.72000250796138165, + 0.72424708295146689, 0.7284643904482252, 0.73265427167241282, 0.73681656887736979, + 0.74095112535495911, 0.74505778544146595, 0.74913639452345926, 0.75318679904361241, + 0.75720884650648446, 0.76120238548426178, 0.76516726562245896, 0.76910333764557959, + 0.77301045336273699, 0.77688846567323244, 0.78073722857209438, 0.78455659715557524, + 0.78834642762660623, 0.79210657730021239, 0.79583690460888346, 0.79953726910790501, + 0.80320753148064483, 0.80684755354379922, 0.81045719825259477, 0.8140363297059483, + 0.81758481315158371, 0.82110251499110465, 0.82458930278502529, 0.8280450452577558, + 0.83146961230254524, 0.83486287498638001, 0.83822470555483797, 0.84155497743689833, + 0.84485356524970701, 0.84812034480329712, 0.8513551931052652, 0.85455798836540053, + 0.85772861000027212, 0.86086693863776731, 0.8639728561215867, 0.86704624551569265, + 0.87008699110871135, 0.87309497841829009, 0.8760700941954066, 0.87901222642863341, + 0.88192126434835494, 0.88479709843093779, 0.88763962040285393, 0.89044872324475788, + 0.89322430119551532, 0.89596624975618511, 0.89867446569395382, 0.90134884704602203, + 0.90398929312344334, 0.90659570451491533, 0.90916798309052227, 0.91170603200542988, + 0.91420975570353069, 0.9166790599210427, 0.91911385169005777, 0.9215140393420419, + 0.92387953251128674, 0.92621024213831127, 0.92850608047321548, 0.93076696107898371, + 0.93299279883473885, 0.9351835099389475, 0.93733901191257496, 0.93945922360218992, + 0.94154406518302081, 0.94359345816196039, 0.94560732538052128, 0.94758559101774109, + 0.94952818059303667, 0.95143502096900834, 0.95330604035419375, 0.95514116830577067, + 0.95694033573220894, 0.9587034748958716, 0.96043051941556579, 0.96212140426904158, + 0.96377606579543984, 0.9653944416976894, 0.96697647104485207, 0.96852209427441727, + 0.97003125319454397, 0.97150389098625178, 0.97293995220556007, 0.97433938278557586, + 0.97570213003852857, 0.97702814265775439, 0.97831737071962765, 0.97956976568544052, + 0.98078528040323043, 0.98196386910955524, 0.98310548743121629, 0.98421009238692903, + 0.98527764238894122, 0.98630809724459867, 0.98730141815785843, 0.98825756773074946, + 0.98917650996478101, 0.99005821026229712, 0.99090263542778001, 0.99170975366909953, + 0.99247953459870997, 0.9932119492347945, 0.99390697000235606, 0.99456457073425542, + 0.99518472667219682, 0.99576741446765982, 0.996312612182778, 0.99682029929116567, + 0.99729045667869021, 0.99772306664419164, 0.99811811290014918, 0.99847558057329477, + 0.99879545620517241, 0.99907772775264536, 0.99932238458834954, 0.99952941750109314, + 0.99969881869620425, 0.9998305817958234, 0.9999247018391445, 0.99998117528260111, + 1 +}; + +/* +looks up sin(2 * Pi * x / 1024) +*/ +float sine(int x) +{ + int xx = x & 0xFF; + switch ((x>>8) & 0x03) + { + default: /* just to shut gcc up. */ + case 0: + return sine_table[xx]; + case 1: + return sine_table[0x100 - xx]; + case 2: + return -sine_table[xx]; + case 3: + return -sine_table[0x100 - xx]; + } +} +#endif /* LOOKUP_SINE */ + +#ifdef LOOKUP_HACK +int16_t _u2l[] = +{ + -32256, -31228, -30200, -29172, -28143, -27115, -26087, -25059, + -24031, -23002, -21974, -20946, -19918, -18889, -17861, -16833, + -16062, -15548, -15033, -14519, -14005, -13491, -12977, -12463, + -11949, -11435, -10920, -10406, -9892, -9378, -8864, -8350, + -7964, -7707, -7450, -7193, -6936, -6679, -6422, -6165, + -5908, -5651, -5394, -5137, -4880, -4623, -4365, -4108, + -3916, -3787, -3659, -3530, -3402, -3273, -3144, -3016, + -2887, -2759, -2630, -2502, -2373, -2245, -2116, -1988, + -1891, -1827, -1763, -1698, -1634, -1570, -1506, -1441, + -1377, -1313, -1249, -1184, -1120, -1056, -992, -927, + -879, -847, -815, -783, -751, -718, -686, -654, + -622, -590, -558, -526, -494, -461, -429, -397, + -373, -357, -341, -325, -309, -293, -277, -261, + -245, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32256, 31228, 30200, 29172, 28143, 27115, 26087, 25059, + 24031, 23002, 21974, 20946, 19918, 18889, 17861, 16833, + 16062, 15548, 15033, 14519, 14005, 13491, 12977, 12463, + 11949, 11435, 10920, 10406, 9892, 9378, 8864, 8350, + 7964, 7707, 7450, 7193, 6936, 6679, 6422, 6165, + 5908, 5651, 5394, 5137, 4880, 4623, 4365, 4108, + 3916, 3787, 3659, 3530, 3402, 3273, 3144, 3016, + 2887, 2759, 2630, 2502, 2373, 2245, 2116, 1988, + 1891, 1827, 1763, 1698, 1634, 1570, 1506, 1441, + 1377, 1313, 1249, 1184, 1120, 1056, 992, 927, + 879, 847, 815, 783, 751, 718, 686, 654, + 622, 590, 558, 526, 494, 461, 429, 397, + 373, 357, 341, 325, 309, 293, 277, 261, + 245, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + + int32_t *mixup; +#ifdef LOOKUP_INTERPOLATION +int8_t *iplookup; +#endif + +#endif + +void init_tables(void) +{ +#ifdef LOOKUP_HACK + int i,j,v; + mixup=safe_malloc(1<<(7+8+2)); /* Give your cache a workout! */ + + for (i=0; i<128; i++) + { + v=_u2l[255-i]; + for (j=-128; j<128; j++) + { + mixup[ ((i & 0x7F)<<8) | (j & 0xFF)] = + (v * j) << MIXUP_SHIFT; + } + } + +#ifdef LOOKUP_INTERPOLATION + iplookup=safe_malloc(1<<(9+5)); + for (i=-256; i<256; i++) + for(j=0; j<32; j++) + iplookup[((i<<5) & 0x3FE0) | j] = (i * j)>>5; + /* I don't know. Quantum bits? Magick? */ +#endif + +#endif +} + +uint8_t _l2u_[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, + 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, + 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, + 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, + 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, + 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, + 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, + 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, + 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1D, + 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, + 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, + 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, + 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1E, + 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, + 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, + 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, + 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2A, 0x2A, + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2B, 0x2B, + 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2D, 0x2D, + 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, + 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2F, 0x2F, + 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, + 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3A, + 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, 0x3B, + 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3C, + 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3D, + 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, + 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, + 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x4A, 0x4A, 0x4A, 0x4A, 0x4A, 0x4A, 0x4A, 0x4A, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, 0x4D, + 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, + 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, + 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x57, + 0x58, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x5A, 0x5A, 0x5A, 0x5A, 0x5B, 0x5B, 0x5B, 0x5B, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5D, 0x5D, 0x5D, 0x5D, 0x5E, 0x5E, 0x5E, 0x5E, 0x5F, 0x5F, 0x5F, 0x5F, + 0x60, 0x60, 0x61, 0x61, 0x62, 0x62, 0x63, 0x63, 0x64, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, + 0x68, 0x68, 0x68, 0x69, 0x69, 0x6A, 0x6A, 0x6B, 0x6B, 0x6C, 0x6C, 0x6D, 0x6D, 0x6E, 0x6E, 0x6F, + 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, + 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0, + 0xEF, 0xEF, 0xEE, 0xEE, 0xED, 0xED, 0xEC, 0xEC, 0xEB, 0xEB, 0xEA, 0xEA, 0xE9, 0xE9, 0xE8, 0xE8, + 0xE7, 0xE7, 0xE6, 0xE6, 0xE5, 0xE5, 0xE4, 0xE4, 0xE3, 0xE3, 0xE2, 0xE2, 0xE1, 0xE1, 0xE0, 0xE0, + 0xDF, 0xDF, 0xDF, 0xDF, 0xDE, 0xDE, 0xDE, 0xDE, 0xDD, 0xDD, 0xDD, 0xDD, 0xDC, 0xDC, 0xDC, 0xDC, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDA, 0xDA, 0xDA, 0xDA, 0xD9, 0xD9, 0xD9, 0xD9, 0xD8, 0xD8, 0xD8, 0xD8, + 0xD7, 0xD7, 0xD7, 0xD7, 0xD6, 0xD6, 0xD6, 0xD6, 0xD5, 0xD5, 0xD5, 0xD5, 0xD4, 0xD4, 0xD4, 0xD4, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD2, 0xD2, 0xD2, 0xD2, 0xD1, 0xD1, 0xD1, 0xD1, 0xD0, 0xD0, 0xD0, 0xD0, + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, + 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, + 0xC2, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, + 0xC0, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 +}; + +uint8_t *_l2u = _l2u_ + 4096; diff --git a/doomclassic/timidity/tables.h b/doomclassic/timidity/tables.h new file mode 100644 index 00000000..c9a66b1f --- /dev/null +++ b/doomclassic/timidity/tables.h @@ -0,0 +1,45 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +tables.h +*/ + +#ifdef LOOKUP_SINE +extern float sine(int x); +#else +#include +#define sine(x) (sin((2*PI/1024.0) * (x))) +#endif + +#define SINE_CYCLE_LENGTH 1024 +extern int32_t freq_table[]; +extern double vol_table[]; +extern double bend_fine[]; +extern double bend_coarse[]; +extern uint8_t *_l2u; /* 13-bit PCM to 8-bit u-law */ +extern uint8_t _l2u_[]; /* used in LOOKUP_HACK */ +#ifdef LOOKUP_HACK +extern int16_t _u2l[]; +extern int32_t *mixup; +#ifdef LOOKUP_INTERPOLATION +extern int8_t *iplookup; +#endif +#endif + +extern void init_tables(void); diff --git a/doomclassic/timidity/timidity.cpp b/doomclassic/timidity/timidity.cpp new file mode 100644 index 00000000..cbf8ac53 --- /dev/null +++ b/doomclassic/timidity/timidity.cpp @@ -0,0 +1,400 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "../../neo/idlib/precompiled.h" + +#include +#include +#include + +//#include "SDL.h" +#include "config.h" +#include "common.h" +#include "instrum.h" +#include "playmidi.h" +#include "readmidi.h" +#include "output.h" +#include "controls.h" +#include "timidity.h" +#include "tables.h" + + +//void *Real_Tim_Malloc( int sz ); +//void Real_Tim_Free( void *pt ); + +void (*s32tobuf)(void *dp, int32_t *lp, int32_t c); +int free_instruments_afterwards=0; +static char def_instr_name[256]=""; + +int AUDIO_BUFFER_SIZE; +sample_t *resample_buffer; + int32_t *common_buffer; + +#define MAXWORDS 10 + + // Alternative to FGets +char* Gets( idFile & file, char *buf, int bsize ) { + + int i; + char c; + int done = 0; + if (buf == 0 || bsize <= 0 ) + return 0; + for (i = 0; !done && i < bsize - 1; i++) { + int red = file.ReadChar(c); + + if (red == 0) { + done = 1; + i--; + } else { + buf[i] = c; + if (c == '\n') + done = 1; + } + } + buf[i] = '\0'; + if (i == 0) + return 0; + else + return buf; + +} + + +static int read_config_file(const char *name) +{ + idFile * fp; + char *w[MAXWORDS], *cp; + ToneBank *bank=0; + int i, j, k, line=0, words; + static int rcf_count=0; + + if (rcf_count>50) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "Probable source loop in configuration files"); + return (-1); + } + + if (!(fp=open_file(name, 1, OF_VERBOSE))) + return -2; + + char tokTmp[1024]; + + while ( Gets( *fp, tokTmp, 1024 ) ) + { + + line++; + w[words=0]=strtok(tokTmp, " \t\r\n\240"); + if (!w[0] || (*w[0]=='#')) continue; + while (w[words] && (words < MAXWORDS)) + w[++words]=strtok(0," \t\r\n\240"); + if (!strcmp(w[0], "dir")) + { + if (words < 2) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: No directory given\n", name, line); + return -3; + } + for (i=1; icmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: No file name given\n", name, line); + return -4; + } + for (i=1; icmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: Must specify exactly one patch name\n", + name, line); + return -5; + } + strncpy(def_instr_name, w[1], 255); + def_instr_name[255]='\0'; + } + else if (!strcmp(w[0], "drumset")) + { + if (words < 2) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: No drum set number given\n", + name, line); + return -6; + } + i=atoi(w[1]); + if (i<0 || i>127) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: Drum set must be between 0 and 127\n", + name, line); + return -7; + } + if (!drumset[i]) + { + drumset[i]=(ToneBank*)safe_malloc(sizeof(ToneBank)); + memset(drumset[i], 0, sizeof(ToneBank)); + } + bank=drumset[i]; + } + else if (!strcmp(w[0], "bank")) + { + if (words < 2) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: No bank number given\n", + name, line); + return -8; + } + i=atoi(w[1]); + if (i<0 || i>127) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: Tone bank must be between 0 and 127\n", + name, line); + return -9; + } + if (!tonebank[i]) + { + tonebank[i]=(ToneBank*)safe_malloc(sizeof(ToneBank)); + memset(tonebank[i], 0, sizeof(ToneBank)); + } + bank=tonebank[i]; + } + else { + if ((words < 2) || (*w[0] < '0' || *w[0] > '9')) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: syntax error\n", name, line); + return -10; + } + i=atoi(w[0]); + if (i<0 || i>127) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: Program must be between 0 and 127\n", + name, line); + return -11; + } + if (!bank) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: Must specify tone bank or drum set " + "before assignment\n", + name, line); + return -12; + } + if (bank->tone[i].name) + Real_Tim_Free(bank->tone[i].name); + strcpy((bank->tone[i].name=(char*)safe_malloc(strlen(w[1])+1)),w[1]); + bank->tone[i].note=bank->tone[i].amp=bank->tone[i].pan= + bank->tone[i].strip_loop=bank->tone[i].strip_envelope= + bank->tone[i].strip_tail=-1; + + for (j=2; j '9')) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: amplification must be between " + "0 and %d\n", name, line, MAX_AMPLIFICATION); + return -14; + } + bank->tone[i].amp=k; + } + else if (!strcmp(w[j], "note")) + { + k=atoi(cp); + if ((k<0 || k>127) || (*cp < '0' || *cp > '9')) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: note must be between 0 and 127\n", + name, line); + return -15; + } + bank->tone[i].note=k; + } + else if (!strcmp(w[j], "pan")) + { + if (!strcmp(cp, "center")) + k=64; + else if (!strcmp(cp, "left")) + k=0; + else if (!strcmp(cp, "right")) + k=127; + else + k=((atoi(cp)+100) * 100) / 157; + if ((k<0 || k>127) || + (k==0 && *cp!='-' && (*cp < '0' || *cp > '9'))) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: panning must be left, right, " + "center, or between -100 and 100\n", + name, line); + return -16; + } + bank->tone[i].pan=k; + } + else if (!strcmp(w[j], "keep")) + { + if (!strcmp(cp, "env")) + bank->tone[i].strip_envelope=0; + else if (!strcmp(cp, "loop")) + bank->tone[i].strip_loop=0; + else + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: keep must be env or loop\n", name, line); + return -17; + } + } + else if (!strcmp(w[j], "strip")) + { + if (!strcmp(cp, "env")) + bank->tone[i].strip_envelope=1; + else if (!strcmp(cp, "loop")) + bank->tone[i].strip_loop=1; + else if (!strcmp(cp, "tail")) + bank->tone[i].strip_tail=1; + else + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, + "%s: line %d: strip must be env, loop, or tail\n", + name, line); + return -18; + } + } + else + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: line %d: bad patch option %s\n", + name, line, w[j]); + return -19; + } + } + } + } + if ( fp == 0 ) //(ferror(fp)) + { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't read from %s\n", name); + close_file(fp); + return -20; + } + close_file(fp); + return 0; +} + +int Timidity_Init(int rate, int format, int channels, int samples, const char* config) +{ + int ret; + ret = read_config_file(config); + if (ret < 0) { + return(ret); + } + + /* Set play mode parameters */ + play_mode->rate = rate; + play_mode->encoding = 0; + if ( (format&0xFF) == 16 ) { + play_mode->encoding |= PE_16BIT; + } + if ( (format&0x8000) ) { + play_mode->encoding |= PE_SIGNED; + } + if ( channels == 1 ) { + play_mode->encoding |= PE_MONO; + } + switch (format) { + case AUDIO_S8: + s32tobuf = s32tos8; + break; + case AUDIO_U8: + s32tobuf = s32tou8; + break; + case AUDIO_S16LSB: + s32tobuf = s32tos16l; + break; + case AUDIO_S16MSB: + s32tobuf = s32tos16b; + break; + case AUDIO_U16LSB: + s32tobuf = s32tou16l; + break; + case AUDIO_U16MSB: + s32tobuf = s32tou16b; + break; + default: + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Unsupported audio format"); + return(-1); + } + AUDIO_BUFFER_SIZE = samples; + + /* Allocate memory for mixing (WARNING: Memory leak!) */ + resample_buffer = (sample_t*)safe_malloc(AUDIO_BUFFER_SIZE*sizeof(sample_t)); + common_buffer = (int32*)safe_malloc(AUDIO_BUFFER_SIZE*2*sizeof(int32_t)); + + init_tables(); + + if (ctl->open(0, 0)) { + ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Couldn't open %s\n", ctl->id_name); + return(-1); + } + + if (!control_ratio) { + control_ratio = play_mode->rate / CONTROLS_PER_SECOND; + if(control_ratio<1) + control_ratio=1; + else if (control_ratio > MAX_CONTROL_RATIO) + control_ratio=MAX_CONTROL_RATIO; + } + if (*def_instr_name) + set_default_instrument(def_instr_name); + return(0); +} + +char timidity_error[TIMIDITY_ERROR_MAX_CHARS] = ""; +char *Timidity_Error(void) +{ + return(timidity_error); +} diff --git a/doomclassic/timidity/timidity.h b/doomclassic/timidity/timidity.h new file mode 100644 index 00000000..06a8a17c --- /dev/null +++ b/doomclassic/timidity/timidity.h @@ -0,0 +1,70 @@ +/* + +TiMidity -- Experimental MIDI to WAVE converter +Copyright (C) 1995 Tuukka Toivonen + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _TIMIDITY_H_ +#define _TIMIDITY_H_ + +/* Audio format flags (defaults to LSB byte order) */ +#define AUDIO_U8 0x0008 /* Unsigned 8-bit samples */ +#define AUDIO_S8 0x8008 /* Signed 8-bit samples */ +#define AUDIO_U16LSB 0x0010 /* Unsigned 16-bit samples */ +#define AUDIO_S16LSB 0x8010 /* Signed 16-bit samples */ +#define AUDIO_U16MSB 0x1010 /* As above, but big-endian byte order */ +#define AUDIO_S16MSB 0x9010 /* As above, but big-endian byte order */ +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB + +#include "structs.h" + +typedef struct _MidiSong MidiSong; + +extern int Timidity_Init(int rate, int format, int channels, int samples, const char* config); +extern char *Timidity_Error(void); +extern void Timidity_SetVolume(int volume); +extern int Timidity_PlaySome(void *stream, int samples, int* bytes_written); +extern MidiSong *Timidity_LoadSong(char *midifile); +extern MidiSong *Timidity_LoadSongMem(unsigned char* buffer, size_t length); +extern void Timidity_Start(MidiSong *song); +extern int Timidity_Active(void); +extern void Timidity_Stop(void); +extern void Timidity_FreeSong(MidiSong *song); +extern void Timidity_Shutdown(void); + +extern void *Real_Tim_Malloc( int sz ); +extern void Real_Tim_Free( void *pt ); + + +typedef struct { + int rate, encoding; + char *id_name; + FILE* fp; + char *file_name; + + int (*open_output)(void); /* 0=success, 1=warning, -1=fatal error */ + void (*close_output)(void); + void (*output_data)(int *buf, int count, int* bytes_written); + void (*flush_output)(void); + void (*purge_output)(void); +} PlayMode; + +extern PlayMode *play_mode_list[], *play_mode; + +#endif \ No newline at end of file diff --git a/doomclassic/timidity/timidity.vcxproj b/doomclassic/timidity/timidity.vcxproj new file mode 100644 index 00000000..76f1ca7d --- /dev/null +++ b/doomclassic/timidity/timidity.vcxproj @@ -0,0 +1,116 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3267F0ED-FE57-4348-91D7-AA8A4976750F} + Win32Proj + timidity + + + + + + + + + + + + StaticLibrary + true + Unicode + + + StaticLibrary + false + true + Unicode + + + + + + + + + + + + + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebug + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + + + + + + \ No newline at end of file diff --git a/doomclassic/timidity/timidity.vcxproj.filters b/doomclassic/timidity/timidity.vcxproj.filters new file mode 100644 index 00000000..c0d3940d --- /dev/null +++ b/doomclassic/timidity/timidity.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doomclassic/timidity/timidity.vcxproj.vspscc b/doomclassic/timidity/timidity.vcxproj.vspscc new file mode 100644 index 00000000..b6d32892 --- /dev/null +++ b/doomclassic/timidity/timidity.vcxproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/neo/_Common.props b/neo/_Common.props new file mode 100644 index 00000000..8394752a --- /dev/null +++ b/neo/_Common.props @@ -0,0 +1,21 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Common Project Properties + ..\build\$(PlatformName)\$(Configuration)\ + ..\build\$(PlatformName)\$(Configuration)\intermediate\$(ProjectName)\ + $(OutDir)$(TargetName)$(TargetExt) + $(OutDir)$(ProjectName).xex + + + + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_USE_32BIT_TIME_T;%(PreprocessorDefinitions) + Level4 + + + true + $(SolutionDir) + + + \ No newline at end of file diff --git a/neo/_Debug.props b/neo/_Debug.props new file mode 100644 index 00000000..2f77bbd5 --- /dev/null +++ b/neo/_Debug.props @@ -0,0 +1,23 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Debug + false + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + false + MultiThreadedDebug + true + ProgramDatabase + + + true + true + + + \ No newline at end of file diff --git a/neo/_Dedicated.props b/neo/_Dedicated.props new file mode 100644 index 00000000..cea663fd --- /dev/null +++ b/neo/_Dedicated.props @@ -0,0 +1,12 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Dedicated + + + + ID_DEDICATED;%(PreprocessorDefinitions) + + + \ No newline at end of file diff --git a/neo/_DoomExe.props b/neo/_DoomExe.props new file mode 100644 index 00000000..8a438e3d --- /dev/null +++ b/neo/_DoomExe.props @@ -0,0 +1,18 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Doom III Executable + + + + __DOOM__;%(PreprocessorDefinitions) + Use + + + 16777216 + 16777216 + true + + + \ No newline at end of file diff --git a/neo/_Game-d3xp.props b/neo/_Game-d3xp.props new file mode 100644 index 00000000..1ff310e6 --- /dev/null +++ b/neo/_Game-d3xp.props @@ -0,0 +1,21 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Game d3xp Library + + + + __DOOM__;_D3XP;CTF;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + Use + + + $(OutDir)gamex86.dll + .\d3xp\game.def + + + ..\build\Win32\"$(Configuration)"\TypeInfo.exe + + + \ No newline at end of file diff --git a/neo/_Game.props b/neo/_Game.props new file mode 100644 index 00000000..cedac518 --- /dev/null +++ b/neo/_Game.props @@ -0,0 +1,22 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Game Library + + + + __DOOM__;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + + + + + $(OutDir)gamex86.dll + .\game\game.def + + + ..\build\Win32\"$(Configuration)"\TypeInfo.exe + + + \ No newline at end of file diff --git a/neo/_PCLibs.props b/neo/_PCLibs.props new file mode 100644 index 00000000..7d272487 --- /dev/null +++ b/neo/_PCLibs.props @@ -0,0 +1,25 @@ + + + + <_ProjectFileVersion>10.0.30319.1 + <_PropertySheetDisplayName>PC Libs + + + + libcmtd.lib;%(AdditionalDependencies) + libcmtd.lib;%(IgnoreSpecificDefaultLibraries) + + + libcmt.lib;%(AdditionalDependencies) + libcmt.lib;%(IgnoreSpecificDefaultLibraries) + + + xinput.lib;dbghelp.lib;dinput8.lib;dsound.lib;dxguid.lib;DxErr.lib;glu32.lib;iphlpapi.lib;odbc32.lib;odbccp32.lib;opengl32.lib;winmm.lib;wsock32.lib;x3daudio.lib;%(AdditionalDependencies) + $(DXSDK_DIR)\Lib\x86\;%(AdditionalLibraryDirectories) + Windows + + + $(DXSDK_DIR)\Include\;%(AdditionalIncludeDirectories) + + + \ No newline at end of file diff --git a/neo/_Release.props b/neo/_Release.props new file mode 100644 index 00000000..642c2f2c --- /dev/null +++ b/neo/_Release.props @@ -0,0 +1,27 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>Release + + + + MaxSpeed + AnySuitable + true + NDEBUG;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + true + ProgramDatabase + + + false + false + true + true + + + \ No newline at end of file diff --git a/neo/_WithInlines.props b/neo/_WithInlines.props new file mode 100644 index 00000000..ac88a6a6 --- /dev/null +++ b/neo/_WithInlines.props @@ -0,0 +1,13 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>With Inlines + + + + OnlyExplicitInline + _INLINEDEBUG;%(PreprocessorDefinitions) + + + \ No newline at end of file diff --git a/neo/_WithMemoryLog.props b/neo/_WithMemoryLog.props new file mode 100644 index 00000000..79867b55 --- /dev/null +++ b/neo/_WithMemoryLog.props @@ -0,0 +1,12 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>With Memory Log + + + + ID_REDIRECT_NEWDELETE;ID_DEBUG_MEMORY;ID_DEBUG_UNINITIALIZED_MEMORY;%(PreprocessorDefinitions) + + + \ No newline at end of file diff --git a/neo/_external.props b/neo/_external.props new file mode 100644 index 00000000..db4c1ecd --- /dev/null +++ b/neo/_external.props @@ -0,0 +1,14 @@ + + + + + + <_PropertySheetDisplayName>External + + + + $(SolutionDir)renderer\jpeg-6;%(AdditionalIncludeDirectories) + + + + \ No newline at end of file diff --git a/neo/_idlib.props b/neo/_idlib.props new file mode 100644 index 00000000..3d7dc2f9 --- /dev/null +++ b/neo/_idlib.props @@ -0,0 +1,13 @@ + + + + <_ProjectFileVersion>10.0.40219.1 + <_PropertySheetDisplayName>idlib + + + + __IDLIB__;%(PreprocessorDefinitions) + Use + + + \ No newline at end of file diff --git a/neo/aas/AASFile.cpp b/neo/aas/AASFile.cpp new file mode 100644 index 00000000..9bed65a6 --- /dev/null +++ b/neo/aas/AASFile.cpp @@ -0,0 +1,1317 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "AASFile.h" +#include "AASFile_local.h" + + +/* +=============================================================================== + + idReachability + +=============================================================================== +*/ + +/* +================ +Reachability_Write +================ +*/ +bool Reachability_Write( idFile *fp, idReachability *reach ) { + fp->WriteFloatString( "\t\t%d %d (%f %f %f) (%f %f %f) %d %d", + (int) reach->travelType, (int) reach->toAreaNum, reach->start.x, reach->start.y, reach->start.z, + reach->end.x, reach->end.y, reach->end.z, reach->edgeNum, (int) reach->travelTime ); + return true; +} + +/* +================ +Reachability_Read +================ +*/ +bool Reachability_Read( idLexer &src, idReachability *reach ) { + reach->travelType = src.ParseInt(); + reach->toAreaNum = src.ParseInt(); + src.Parse1DMatrix( 3, reach->start.ToFloatPtr() ); + src.Parse1DMatrix( 3, reach->end.ToFloatPtr() ); + reach->edgeNum = src.ParseInt(); + reach->travelTime = src.ParseInt(); + return true; +} + +/* +================ +idReachability::CopyBase +================ +*/ +void idReachability::CopyBase( idReachability &reach ) { + travelType = reach.travelType; + toAreaNum = reach.toAreaNum; + start = reach.start; + end = reach.end; + edgeNum = reach.edgeNum; + travelTime = reach.travelTime; +} + + +/* +=============================================================================== + + idReachability_Special + +=============================================================================== +*/ + +/* +================ +Reachability_Special_Write +================ +*/ +bool Reachability_Special_Write( idFile *fp, idReachability_Special *reach ) { + int i; + const idKeyValue *keyValue; + + fp->WriteFloatString( "\n\t\t{\n" ); + for ( i = 0; i < reach->dict.GetNumKeyVals(); i++ ) { + keyValue = reach->dict.GetKeyVal( i ); + fp->WriteFloatString( "\t\t\t\"%s\" \"%s\"\n", keyValue->GetKey().c_str(), keyValue->GetValue().c_str() ); + } + fp->WriteFloatString( "\t\t}\n" ); + + return true; +} + +/* +================ +Reachability_Special_Read +================ +*/ +bool Reachability_Special_Read( idLexer &src, idReachability_Special *reach ) { + idToken key, value; + + src.ExpectTokenString( "{" ); + while( src.ReadToken( &key ) ) { + if ( key == "}" ) { + return true; + } + src.ExpectTokenType( TT_STRING, 0, &value ); + reach->dict.Set( key, value ); + } + return false; +} + +/* +=============================================================================== + + idAASSettings + +=============================================================================== +*/ + +/* +============ +idAASSettings::idAASSettings +============ +*/ +idAASSettings::idAASSettings() { + numBoundingBoxes = 1; + boundingBoxes[0] = idBounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + usePatches = false; + writeBrushMap = false; + playerFlood = false; + noOptimize = false; + allowSwimReachabilities = false; + allowFlyReachabilities = false; + fileExtension = "aas48"; + // physics settings + gravity = idVec3( 0, 0, -1066 ); + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + maxStepHeight = 14.0f; + maxBarrierHeight = 32.0f; + maxWaterJumpHeight = 20.0f; + maxFallHeight = 64.0f; + minFloorCos = 0.7f; + // fixed travel times + tt_barrierJump = 100; + tt_startCrouching = 100; + tt_waterJump = 100; + tt_startWalkOffLedge = 100; +} + +/* +============ +idAASSettings::ParseBool +============ +*/ +bool idAASSettings::ParseBool( idLexer &src, bool &b ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + b = src.ParseBool(); + return true; +} + +/* +============ +idAASSettings::ParseInt +============ +*/ +bool idAASSettings::ParseInt( idLexer &src, int &i ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + i = src.ParseInt(); + return true; +} + +/* +============ +idAASSettings::ParseFloat +============ +*/ +bool idAASSettings::ParseFloat( idLexer &src, float &f ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + f = src.ParseFloat(); + return true; +} + +/* +============ +idAASSettings::ParseVector +============ +*/ +bool idAASSettings::ParseVector( idLexer &src, idVec3 &vec ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + return ( src.Parse1DMatrix( 3, vec.ToFloatPtr() ) != 0 ); +} + +/* +============ +idAASSettings::ParseBBoxes +============ +*/ +bool idAASSettings::ParseBBoxes( idLexer &src ) { + idToken token; + idBounds bounds; + + numBoundingBoxes = 0; + + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + while( src.ReadToken( &token ) ) { + if ( token == "}" ) { + return true; + } + src.UnreadToken( &token ); + src.Parse1DMatrix( 3, bounds[0].ToFloatPtr() ); + if ( !src.ExpectTokenString( "-" ) ) { + return false; + } + src.Parse1DMatrix( 3, bounds[1].ToFloatPtr() ); + + boundingBoxes[numBoundingBoxes++] = bounds; + } + return false; +} + +/* +============ +idAASSettings::FromParser +============ +*/ +bool idAASSettings::FromParser( idLexer &src ) { + idToken token; + + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + + // parse the file + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( token == "bboxes" ) { + if ( !ParseBBoxes( src ) ) { return false; } + } + else if ( token == "usePatches" ) { + if ( !ParseBool( src, usePatches ) ) { return false; } + } + else if ( token == "writeBrushMap" ) { + if ( !ParseBool( src, writeBrushMap ) ) { return false; } + } + else if ( token == "playerFlood" ) { + if ( !ParseBool( src, playerFlood ) ) { return false; } + } + else if ( token == "allowSwimReachabilities" ) { + if ( !ParseBool( src, allowSwimReachabilities ) ) { return false; } + } + else if ( token == "allowFlyReachabilities" ) { + if ( !ParseBool( src, allowFlyReachabilities ) ) { return false; } + } + else if ( token == "fileExtension" ) { + src.ExpectTokenString( "=" ); + src.ExpectTokenType( TT_STRING, 0, &token ); + fileExtension = token; + } + else if ( token == "gravity" ) { + ParseVector( src, gravity ); + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + } + else if ( token == "maxStepHeight" ) { + if ( !ParseFloat( src, maxStepHeight ) ) { return false; } + } + else if ( token == "maxBarrierHeight" ) { + if ( !ParseFloat( src, maxBarrierHeight ) ) { return false; } + } + else if ( token == "maxWaterJumpHeight" ) { + if ( !ParseFloat( src, maxWaterJumpHeight ) ) { return false; } + } + else if ( token == "maxFallHeight" ) { + if ( !ParseFloat( src, maxFallHeight ) ) { return false; } + } + else if ( token == "minFloorCos" ) { + if ( !ParseFloat( src, minFloorCos ) ) { return false; } + } + else if ( token == "tt_barrierJump" ) { + if ( !ParseInt( src, tt_barrierJump ) ) { return false; } + } + else if ( token == "tt_startCrouching" ) { + if ( !ParseInt( src, tt_startCrouching ) ) { return false; } + } + else if ( token == "tt_waterJump" ) { + if ( !ParseInt( src, tt_waterJump ) ) { return false; } + } + else if ( token == "tt_startWalkOffLedge" ) { + if ( !ParseInt( src, tt_startWalkOffLedge ) ) { return false; } + } + else { + src.Error( "invalid token '%s'", token.c_str() ); + } + } + + if ( numBoundingBoxes <= 0 ) { + src.Error( "no valid bounding box" ); + } + + return true; +} + +/* +============ +idAASSettings::FromFile +============ +*/ +bool idAASSettings::FromFile( const idStr &fileName ) { + idLexer src( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idStr name; + + name = fileName; + + common->Printf( "loading %s\n", name.c_str() ); + + if ( !src.LoadFile( name ) ) { + common->Error( "WARNING: couldn't load %s\n", name.c_str() ); + return false; + } + + if ( !src.ExpectTokenString( "settings" ) ) { + common->Error( "%s is not a settings file", name.c_str() ); + return false; + } + + if ( !FromParser( src ) ) { + common->Error( "failed to parse %s", name.c_str() ); + return false; + } + + return true; +} + +/* +============ +idAASSettings::FromDict +============ +*/ +bool idAASSettings::FromDict( const char *name, const idDict *dict ) { + idBounds bounds; + + if ( !dict->GetVector( "mins", "0 0 0", bounds[ 0 ] ) ) { + common->Error( "Missing 'mins' in entityDef '%s'", name ); + } + if ( !dict->GetVector( "maxs", "0 0 0", bounds[ 1 ] ) ) { + common->Error( "Missing 'maxs' in entityDef '%s'", name ); + } + + numBoundingBoxes = 1; + boundingBoxes[0] = bounds; + + if ( !dict->GetBool( "usePatches", "0", usePatches ) ) { + common->Error( "Missing 'usePatches' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "writeBrushMap", "0", writeBrushMap ) ) { + common->Error( "Missing 'writeBrushMap' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "playerFlood", "0", playerFlood ) ) { + common->Error( "Missing 'playerFlood' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "allowSwimReachabilities", "0", allowSwimReachabilities ) ) { + common->Error( "Missing 'allowSwimReachabilities' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "allowFlyReachabilities", "0", allowFlyReachabilities ) ) { + common->Error( "Missing 'allowFlyReachabilities' in entityDef '%s'", name ); + } + + if ( !dict->GetString( "fileExtension", "", fileExtension ) ) { + common->Error( "Missing 'fileExtension' in entityDef '%s'", name ); + } + + if ( !dict->GetVector( "gravity", "0 0 -1066", gravity ) ) { + common->Error( "Missing 'gravity' in entityDef '%s'", name ); + } + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + + if ( !dict->GetFloat( "maxStepHeight", "0", maxStepHeight ) ) { + common->Error( "Missing 'maxStepHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "maxBarrierHeight", "0", maxBarrierHeight ) ) { + common->Error( "Missing 'maxBarrierHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "maxWaterJumpHeight", "0", maxWaterJumpHeight ) ) { + common->Error( "Missing 'maxWaterJumpHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "maxFallHeight", "0", maxFallHeight ) ) { + common->Error( "Missing 'maxFallHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "minFloorCos", "0", minFloorCos ) ) { + common->Error( "Missing 'minFloorCos' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_barrierJump", "0", tt_barrierJump ) ) { + common->Error( "Missing 'tt_barrierJump' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_startCrouching", "0", tt_startCrouching ) ) { + common->Error( "Missing 'tt_startCrouching' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_waterJump", "0", tt_waterJump ) ) { + common->Error( "Missing 'tt_waterJump' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_startWalkOffLedge", "0", tt_startWalkOffLedge ) ) { + common->Error( "Missing 'tt_startWalkOffLedge' in entityDef '%s'", name ); + } + + return true; +} + + +/* +============ +idAASSettings::WriteToFile +============ +*/ +bool idAASSettings::WriteToFile( idFile *fp ) const { + int i; + + fp->WriteFloatString( "{\n" ); + fp->WriteFloatString( "\tbboxes\n\t{\n" ); + for ( i = 0; i < numBoundingBoxes; i++ ) { + fp->WriteFloatString( "\t\t(%f %f %f)-(%f %f %f)\n", boundingBoxes[i][0].x, boundingBoxes[i][0].y, + boundingBoxes[i][0].z, boundingBoxes[i][1].x, boundingBoxes[i][1].y, boundingBoxes[i][1].z ); + } + fp->WriteFloatString( "\t}\n" ); + fp->WriteFloatString( "\tusePatches = %d\n", usePatches ); + fp->WriteFloatString( "\twriteBrushMap = %d\n", writeBrushMap ); + fp->WriteFloatString( "\tplayerFlood = %d\n", playerFlood ); + fp->WriteFloatString( "\tallowSwimReachabilities = %d\n", allowSwimReachabilities ); + fp->WriteFloatString( "\tallowFlyReachabilities = %d\n", allowFlyReachabilities ); + fp->WriteFloatString( "\tfileExtension = \"%s\"\n", fileExtension.c_str() ); + fp->WriteFloatString( "\tgravity = (%f %f %f)\n", gravity.x, gravity.y, gravity.z ); + fp->WriteFloatString( "\tmaxStepHeight = %f\n", maxStepHeight ); + fp->WriteFloatString( "\tmaxBarrierHeight = %f\n", maxBarrierHeight ); + fp->WriteFloatString( "\tmaxWaterJumpHeight = %f\n", maxWaterJumpHeight ); + fp->WriteFloatString( "\tmaxFallHeight = %f\n", maxFallHeight ); + fp->WriteFloatString( "\tminFloorCos = %f\n", minFloorCos ); + fp->WriteFloatString( "\ttt_barrierJump = %d\n", tt_barrierJump ); + fp->WriteFloatString( "\ttt_startCrouching = %d\n", tt_startCrouching ); + fp->WriteFloatString( "\ttt_waterJump = %d\n", tt_waterJump ); + fp->WriteFloatString( "\ttt_startWalkOffLedge = %d\n", tt_startWalkOffLedge ); + fp->WriteFloatString( "}\n" ); + return true; +} + +/* +============ +idAASSettings::ValidForBounds +============ +*/ +bool idAASSettings::ValidForBounds( const idBounds &bounds ) const { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +============ +idAASSettings::ValidEntity +============ +*/ +bool idAASSettings::ValidEntity( const char *classname ) const { + idStr use_aas; + idVec3 size; + idBounds bounds; + + if ( playerFlood ) { + if ( !strcmp( classname, "info_player_start" ) || !strcmp( classname , "info_player_deathmatch" ) || !strcmp( classname, "func_teleporter" ) ) { + return true; + } + } + + const idDeclEntityDef *decl = static_cast( declManager->FindType( DECL_ENTITYDEF, classname, false ) ); + if ( ( decl != NULL ) && decl->dict.GetString( "use_aas", NULL, use_aas ) && !fileExtension.Icmp( use_aas ) ) { + if ( decl->dict.GetVector( "mins", NULL, bounds[0] ) ) { + decl->dict.GetVector( "maxs", NULL, bounds[1] ); + } else if ( decl->dict.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + } + + if ( !ValidForBounds( bounds ) ) { + common->Error( "%s cannot use %s\n", classname, fileExtension.c_str() ); + } + + return true; + } + + return false; +} + + +/* +=============================================================================== + + idAASFileLocal + +=============================================================================== +*/ + +#define AAS_LIST_GRANULARITY 1024 +#define AAS_INDEX_GRANULARITY 4096 +#define AAS_PLANE_GRANULARITY 4096 +#define AAS_VERTEX_GRANULARITY 4096 +#define AAS_EDGE_GRANULARITY 4096 + +/* +================ +idAASFileLocal::idAASFileLocal +================ +*/ +idAASFileLocal::idAASFileLocal() { + planeList.SetGranularity( AAS_PLANE_GRANULARITY ); + vertices.SetGranularity( AAS_VERTEX_GRANULARITY ); + edges.SetGranularity( AAS_EDGE_GRANULARITY ); + edgeIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + faces.SetGranularity( AAS_LIST_GRANULARITY ); + faceIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + areas.SetGranularity( AAS_LIST_GRANULARITY ); + nodes.SetGranularity( AAS_LIST_GRANULARITY ); + portals.SetGranularity( AAS_LIST_GRANULARITY ); + portalIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + clusters.SetGranularity( AAS_LIST_GRANULARITY ); +} + +/* +================ +idAASFileLocal::~idAASFileLocal +================ +*/ +idAASFileLocal::~idAASFileLocal() { + int i; + idReachability *reach, *next; + + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = next ) { + next = reach->next; + delete reach; + } + } +} + +/* +================ +idAASFileLocal::Clear +================ +*/ +void idAASFileLocal::Clear() { + planeList.Clear(); + vertices.Clear(); + edges.Clear(); + edgeIndex.Clear(); + faces.Clear(); + faceIndex.Clear(); + areas.Clear(); + nodes.Clear(); + portals.Clear(); + portalIndex.Clear(); + clusters.Clear(); +} + +/* +================ +idAASFileLocal::Write +================ +*/ +bool idAASFileLocal::Write( const idStr &fileName, unsigned int mapFileCRC ) { + int i, num; + idFile *aasFile; + idReachability *reach; + + common->Printf( "[Write AAS]\n" ); + common->Printf( "writing %s\n", fileName.c_str() ); + + name = fileName; + crc = mapFileCRC; + + aasFile = fileSystem->OpenFileWrite( fileName, "fs_basepath" ); + if ( !aasFile ) { + common->Error( "Error opening %s", fileName.c_str() ); + return false; + } + + aasFile->WriteFloatString( "%s \"%s\"\n\n", AAS_FILEID, AAS_FILEVERSION ); + aasFile->WriteFloatString( "%u\n\n", mapFileCRC ); + + // write out the settings + aasFile->WriteFloatString( "settings\n" ); + settings.WriteToFile( aasFile ); + + // write out planes + aasFile->WriteFloatString( "planes %d {\n", planeList.Num() ); + for ( i = 0; i < planeList.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %f %f %f %f )\n", i, + planeList[i].Normal().x, planeList[i].Normal().y, planeList[i].Normal().z, planeList[i].Dist() ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out vertices + aasFile->WriteFloatString( "vertices %d {\n", vertices.Num() ); + for ( i = 0; i < vertices.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %f %f %f )\n", i, vertices[i].x, vertices[i].y, vertices[i].z ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out edges + aasFile->WriteFloatString( "edges %d {\n", edges.Num() ); + for ( i = 0; i < edges.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d )\n", i, edges[i].vertexNum[0], edges[i].vertexNum[1] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out edgeIndex + aasFile->WriteFloatString( "edgeIndex %d {\n", edgeIndex.Num() ); + for ( i = 0; i < edgeIndex.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d )\n", i, edgeIndex[i] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out faces + aasFile->WriteFloatString( "faces %d {\n", faces.Num() ); + for ( i = 0; i < faces.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d %d )\n", i, faces[i].planeNum, faces[i].flags, + faces[i].areas[0], faces[i].areas[1], faces[i].firstEdge, faces[i].numEdges ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out faceIndex + aasFile->WriteFloatString( "faceIndex %d {\n", faceIndex.Num() ); + for ( i = 0; i < faceIndex.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d )\n", i, faceIndex[i] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out areas + aasFile->WriteFloatString( "areas %d {\n", areas.Num() ); + for ( i = 0; i < areas.Num(); i++ ) { + for ( num = 0, reach = areas[i].reach; reach; reach = reach->next ) { + num++; + } + aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d %d ) %d {\n", i, areas[i].flags, areas[i].contents, + areas[i].firstFace, areas[i].numFaces, areas[i].cluster, areas[i].clusterAreaNum, num ); + for ( reach = areas[i].reach; reach; reach = reach->next ) { + Reachability_Write( aasFile, reach ); + switch( reach->travelType ) { + case TFL_SPECIAL: + Reachability_Special_Write( aasFile, static_cast(reach) ); + break; + } + aasFile->WriteFloatString( "\n" ); + } + aasFile->WriteFloatString( "\t}\n" ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out nodes + aasFile->WriteFloatString( "nodes %d {\n", nodes.Num() ); + for ( i = 0; i < nodes.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d )\n", i, nodes[i].planeNum, nodes[i].children[0], nodes[i].children[1] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out portals + aasFile->WriteFloatString( "portals %d {\n", portals.Num() ); + for ( i = 0; i < portals.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d )\n", i, portals[i].areaNum, portals[i].clusters[0], + portals[i].clusters[1], portals[i].clusterAreaNum[0], portals[i].clusterAreaNum[1] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out portalIndex + aasFile->WriteFloatString( "portalIndex %d {\n", portalIndex.Num() ); + for ( i = 0; i < portalIndex.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d )\n", i, portalIndex[i] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out clusters + aasFile->WriteFloatString( "clusters %d {\n", clusters.Num() ); + for ( i = 0; i < clusters.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d %d )\n", i, clusters[i].numAreas, clusters[i].numReachableAreas, + clusters[i].firstPortal, clusters[i].numPortals ); + } + aasFile->WriteFloatString( "}\n" ); + + // close file + fileSystem->CloseFile( aasFile ); + + common->Printf( "done.\n" ); + + return true; +} + +/* +================ +idAASFileLocal::ParseIndex +================ +*/ +bool idAASFileLocal::ParseIndex( idLexer &src, idList &indexes ) { + int numIndexes, i; + aasIndex_t index; + + numIndexes = src.ParseInt(); + indexes.Resize( numIndexes ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numIndexes; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + index = src.ParseInt(); + src.ExpectTokenString( ")" ); + indexes.Append( index ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParsePlanes +================ +*/ +bool idAASFileLocal::ParsePlanes( idLexer &src ) { + int numPlanes, i; + idPlane plane; + idVec4 vec; + + numPlanes = src.ParseInt(); + planeList.Resize( numPlanes ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numPlanes; i++ ) { + src.ParseInt(); + if ( !src.Parse1DMatrix( 4, vec.ToFloatPtr() ) ) { + return false; + } + plane.SetNormal( vec.ToVec3() ); + plane.SetDist( vec[3] ); + planeList.Append( plane ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseVertices +================ +*/ +bool idAASFileLocal::ParseVertices( idLexer &src ) { + int numVertices, i; + idVec3 vec; + + numVertices = src.ParseInt(); + vertices.Resize( numVertices ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numVertices; i++ ) { + src.ParseInt(); + if ( !src.Parse1DMatrix( 3, vec.ToFloatPtr() ) ) { + return false; + } + vertices.Append( vec ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseEdges +================ +*/ +bool idAASFileLocal::ParseEdges( idLexer &src ) { + int numEdges, i; + aasEdge_t edge; + + numEdges = src.ParseInt(); + edges.Resize( numEdges ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numEdges; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + edge.vertexNum[0] = src.ParseInt(); + edge.vertexNum[1] = src.ParseInt(); + src.ExpectTokenString( ")" ); + edges.Append( edge ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseFaces +================ +*/ +bool idAASFileLocal::ParseFaces( idLexer &src ) { + int numFaces, i; + aasFace_t face; + + numFaces = src.ParseInt(); + faces.Resize( numFaces ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numFaces; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + face.planeNum = src.ParseInt(); + face.flags = src.ParseInt(); + face.areas[0] = src.ParseInt(); + face.areas[1] = src.ParseInt(); + face.firstEdge = src.ParseInt(); + face.numEdges = src.ParseInt(); + src.ExpectTokenString( ")" ); + faces.Append( face ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseReachabilities +================ +*/ +bool idAASFileLocal::ParseReachabilities( idLexer &src, int areaNum ) { + int num, j; + aasArea_t *area; + idReachability reach, *newReach; + idReachability_Special *special; + + area = &areas[areaNum]; + + num = src.ParseInt(); + src.ExpectTokenString( "{" ); + area->reach = NULL; + area->rev_reach = NULL; + area->travelFlags = AreaContentsTravelFlags( areaNum ); + for ( j = 0; j < num; j++ ) { + Reachability_Read( src, &reach ); + switch( reach.travelType ) { + case TFL_SPECIAL: + newReach = special = new (TAG_AAS) idReachability_Special(); + Reachability_Special_Read( src, special ); + break; + default: + newReach = new (TAG_AAS) idReachability(); + break; + } + newReach->CopyBase( reach ); + newReach->fromAreaNum = areaNum; + newReach->next = area->reach; + area->reach = newReach; + } + src.ExpectTokenString( "}" ); + return true; +} + +/* +================ +idAASFileLocal::LinkReversedReachability +================ +*/ +void idAASFileLocal::LinkReversedReachability() { + int i; + idReachability *reach; + + // link reversed reachabilities + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = reach->next ) { + reach->rev_next = areas[reach->toAreaNum].rev_reach; + areas[reach->toAreaNum].rev_reach = reach; + } + } +} + +/* +================ +idAASFileLocal::ParseAreas +================ +*/ +bool idAASFileLocal::ParseAreas( idLexer &src ) { + int numAreas, i; + aasArea_t area; + + numAreas = src.ParseInt(); + areas.Resize( numAreas ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numAreas; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + area.flags = src.ParseInt(); + area.contents = src.ParseInt(); + area.firstFace = src.ParseInt(); + area.numFaces = src.ParseInt(); + area.cluster = src.ParseInt(); + area.clusterAreaNum = src.ParseInt(); + src.ExpectTokenString( ")" ); + areas.Append( area ); + ParseReachabilities( src, i ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + + LinkReversedReachability(); + + return true; +} + +/* +================ +idAASFileLocal::ParseNodes +================ +*/ +bool idAASFileLocal::ParseNodes( idLexer &src ) { + int numNodes, i; + aasNode_t node; + + numNodes = src.ParseInt(); + nodes.Resize( numNodes ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numNodes; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + node.planeNum = src.ParseInt(); + node.children[0] = src.ParseInt(); + node.children[1] = src.ParseInt(); + src.ExpectTokenString( ")" ); + nodes.Append( node ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParsePortals +================ +*/ +bool idAASFileLocal::ParsePortals( idLexer &src ) { + int numPortals, i; + aasPortal_t portal; + + numPortals = src.ParseInt(); + portals.Resize( numPortals ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numPortals; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + portal.areaNum = src.ParseInt(); + portal.clusters[0] = src.ParseInt(); + portal.clusters[1] = src.ParseInt(); + portal.clusterAreaNum[0] = src.ParseInt(); + portal.clusterAreaNum[1] = src.ParseInt(); + src.ExpectTokenString( ")" ); + portals.Append( portal ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseClusters +================ +*/ +bool idAASFileLocal::ParseClusters( idLexer &src ) { + int numClusters, i; + aasCluster_t cluster; + + numClusters = src.ParseInt(); + clusters.Resize( numClusters ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numClusters; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + cluster.numAreas = src.ParseInt(); + cluster.numReachableAreas = src.ParseInt(); + cluster.firstPortal = src.ParseInt(); + cluster.numPortals = src.ParseInt(); + src.ExpectTokenString( ")" ); + clusters.Append( cluster ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::FinishAreas +================ +*/ +void idAASFileLocal::FinishAreas() { + int i; + + for ( i = 0; i < areas.Num(); i++ ) { + areas[i].center = AreaReachableGoal( i ); + areas[i].bounds = AreaBounds( i ); + } +} + +/* +================ +idAASFileLocal::Load +================ +*/ +bool idAASFileLocal::Load( const idStr &fileName, unsigned int mapFileCRC ) { + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES ); + idToken token; + int depth; + unsigned int c; + + name = fileName; + crc = mapFileCRC; + + common->Printf( "[Load AAS]\n" ); + common->Printf( "loading %s\n", name.c_str() ); + + if ( !src.LoadFile( name ) ) { + return false; + } + + if ( !src.ExpectTokenString( AAS_FILEID ) ) { + common->Warning( "Not an AAS file: '%s'", name.c_str() ); + return false; + } + + if ( !src.ReadToken( &token ) || token != AAS_FILEVERSION ) { + common->Warning( "AAS file '%s' has version %s instead of %s", name.c_str(), token.c_str(), AAS_FILEVERSION ); + return false; + } + + if ( !src.ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) { + common->Warning( "AAS file '%s' has no map file CRC", name.c_str() ); + return false; + } + + c = token.GetUnsignedLongValue(); + if ( mapFileCRC && c != mapFileCRC ) { + common->Warning( "AAS file '%s' is out of date", name.c_str() ); + return false; + } + + // clear the file in memory + Clear(); + + // parse the file + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "settings" ) { + if ( !settings.FromParser( src ) ) { return false; } + } + else if ( token == "planes" ) { + if ( !ParsePlanes( src ) ) { return false; } + } + else if ( token == "vertices" ) { + if ( !ParseVertices( src ) ) { return false; } + } + else if ( token == "edges" ) { + if ( !ParseEdges( src ) ) { return false; } + } + else if ( token == "edgeIndex" ) { + if ( !ParseIndex( src, edgeIndex ) ) { return false; } + } + else if ( token == "faces" ) { + if ( !ParseFaces( src ) ) { return false; } + } + else if ( token == "faceIndex" ) { + if ( !ParseIndex( src, faceIndex ) ) { return false; } + } + else if ( token == "areas" ) { + if ( !ParseAreas( src ) ) { return false; } + } + else if ( token == "nodes" ) { + if ( !ParseNodes( src ) ) { return false; } + } + else if ( token == "portals" ) { + if ( !ParsePortals( src ) ) { return false; } + } + else if ( token == "portalIndex" ) { + if ( !ParseIndex( src, portalIndex ) ) { return false; } + } + else if ( token == "clusters" ) { + if ( !ParseClusters( src ) ) { return false; } + } + else { + src.Error( "idAASFileLocal::Load: bad token \"%s\"", token.c_str() ); + return false; + } + } + + FinishAreas(); + + depth = MaxTreeDepth(); + if ( depth > MAX_AAS_TREE_DEPTH ) { + src.Error( "idAASFileLocal::Load: tree depth = %d", depth ); + } + + common->UpdateLevelLoadPacifier(); + + common->Printf( "done.\n" ); + + return true; +} + +/* +================ +idAASFileLocal::MemorySize +================ +*/ +int idAASFileLocal::MemorySize() const { + int size; + + size = planeList.Size(); + size += vertices.Size(); + size += edges.Size(); + size += edgeIndex.Size(); + size += faces.Size(); + size += faceIndex.Size(); + size += areas.Size(); + size += nodes.Size(); + size += portals.Size(); + size += portalIndex.Size(); + size += clusters.Size(); + size += sizeof( idReachability_Walk ) * NumReachabilities(); + + return size; +} + +/* +================ +idAASFileLocal::PrintInfo +================ +*/ +void idAASFileLocal::PrintInfo() const { + common->Printf( "%6d KB file size\n", MemorySize() >> 10 ); + common->Printf( "%6d areas\n", areas.Num() ); + common->Printf( "%6d max tree depth\n", MaxTreeDepth() ); + ReportRoutingEfficiency(); +} + +/* +================ +idAASFileLocal::NumReachabilities +================ +*/ +int idAASFileLocal::NumReachabilities() const { + int i, num; + idReachability *reach; + + num = 0; + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = reach->next ) { + num++; + } + } + return num; +} + +/* +================ +idAASFileLocal::ReportRoutingEfficiency +================ +*/ +void idAASFileLocal::ReportRoutingEfficiency() const { + int numReachableAreas, total, i, n; + + numReachableAreas = 0; + total = 0; + for ( i = 0; i < clusters.Num(); i++ ) { + n = clusters[i].numReachableAreas; + numReachableAreas += n; + total += n * n; + } + total += numReachableAreas * portals.Num(); + + common->Printf( "%6d reachable areas\n", numReachableAreas ); + common->Printf( "%6d reachabilities\n", NumReachabilities() ); + common->Printf( "%6d KB max routing cache\n", ( total * 3 ) >> 10 ); +} + +/* +================ +idAASFileLocal::DeleteReachabilities +================ +*/ +void idAASFileLocal::DeleteReachabilities() { + int i; + idReachability *reach, *nextReach; + + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = nextReach ) { + nextReach = reach->next; + delete reach; + } + areas[i].reach = NULL; + areas[i].rev_reach = NULL; + } +} + +/* +================ +idAASFileLocal::DeleteClusters +================ +*/ +void idAASFileLocal::DeleteClusters() { + aasPortal_t portal; + aasCluster_t cluster; + + portals.Clear(); + portalIndex.Clear(); + clusters.Clear(); + + // first portal is a dummy + memset( &portal, 0, sizeof( portal ) ); + portals.Append( portal ); + + // first cluster is a dummy + memset( &cluster, 0, sizeof( cluster ) ); + clusters.Append( cluster ); +} diff --git a/neo/aas/AASFile.h b/neo/aas/AASFile.h new file mode 100644 index 00000000..eeaed38f --- /dev/null +++ b/neo/aas/AASFile.h @@ -0,0 +1,351 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AASFILE_H__ +#define __AASFILE_H__ + +/* +=============================================================================== + + AAS File + +=============================================================================== +*/ + +#define AAS_FILEID "DewmAAS" +#define AAS_FILEVERSION "1.07" + +// travel flags +#define TFL_INVALID BIT(0) // not valid +#define TFL_WALK BIT(1) // walking +#define TFL_CROUCH BIT(2) // crouching +#define TFL_WALKOFFLEDGE BIT(3) // walking of a ledge +#define TFL_BARRIERJUMP BIT(4) // jumping onto a barrier +#define TFL_JUMP BIT(5) // jumping +#define TFL_LADDER BIT(6) // climbing a ladder +#define TFL_SWIM BIT(7) // swimming +#define TFL_WATERJUMP BIT(8) // jump out of the water +#define TFL_TELEPORT BIT(9) // teleportation +#define TFL_ELEVATOR BIT(10) // travel by elevator +#define TFL_FLY BIT(11) // fly +#define TFL_SPECIAL BIT(12) // special +#define TFL_WATER BIT(21) // travel through water +#define TFL_AIR BIT(22) // travel through air + +// face flags +#define FACE_SOLID BIT(0) // solid at the other side +#define FACE_LADDER BIT(1) // ladder surface +#define FACE_FLOOR BIT(2) // standing on floor when on this face +#define FACE_LIQUID BIT(3) // face seperating two areas with liquid +#define FACE_LIQUIDSURFACE BIT(4) // face seperating liquid and air + +// area flags +#define AREA_FLOOR BIT(0) // AI can stand on the floor in this area +#define AREA_GAP BIT(1) // area has a gap +#define AREA_LEDGE BIT(2) // if entered the AI bbox partly floats above a ledge +#define AREA_LADDER BIT(3) // area contains one or more ladder faces +#define AREA_LIQUID BIT(4) // area contains a liquid +#define AREA_CROUCH BIT(5) // AI cannot walk but can only crouch in this area +#define AREA_REACHABLE_WALK BIT(6) // area is reachable by walking or swimming +#define AREA_REACHABLE_FLY BIT(7) // area is reachable by flying + +// area contents flags +#define AREACONTENTS_SOLID BIT(0) // solid, not a valid area +#define AREACONTENTS_WATER BIT(1) // area contains water +#define AREACONTENTS_CLUSTERPORTAL BIT(2) // area is a cluster portal +#define AREACONTENTS_OBSTACLE BIT(3) // area contains (part of) a dynamic obstacle +#define AREACONTENTS_TELEPORTER BIT(4) // area contains (part of) a teleporter trigger + +// bits for different bboxes +#define AREACONTENTS_BBOX_BIT 24 + +#define MAX_REACH_PER_AREA 256 +#define MAX_AAS_TREE_DEPTH 128 + +#define MAX_AAS_BOUNDING_BOXES 4 + +// reachability to another area +class idReachability { +public: + int travelType; // type of travel required to get to the area + short toAreaNum; // number of the reachable area + short fromAreaNum; // number of area the reachability starts + idVec3 start; // start point of inter area movement + idVec3 end; // end point of inter area movement + int edgeNum; // edge crossed by this reachability + unsigned short travelTime; // travel time of the inter area movement + byte number; // reachability number within the fromAreaNum (must be < 256) + byte disableCount; // number of times this reachability has been disabled + idReachability * next; // next reachability in list + idReachability * rev_next; // next reachability in reversed list + unsigned short * areaTravelTimes; // travel times within the fromAreaNum from reachabilities that lead towards this area +public: + void CopyBase( idReachability &reach ); +}; + +class idReachability_Walk : public idReachability { +}; + +class idReachability_BarrierJump : public idReachability { +}; + +class idReachability_WaterJump : public idReachability { +}; + +class idReachability_WalkOffLedge : public idReachability { +}; + +class idReachability_Swim : public idReachability { +}; + +class idReachability_Fly : public idReachability { +}; + +class idReachability_Special : public idReachability { +public: + idDict dict; +}; + +// index +typedef int aasIndex_t; + +// vertex +typedef idVec3 aasVertex_t; + +// edge +typedef struct aasEdge_s { + int vertexNum[2]; // numbers of the vertexes of this edge +} aasEdge_t; + +// area boundary face +typedef struct aasFace_s { + unsigned short planeNum; // number of the plane this face is on + unsigned short flags; // face flags + int numEdges; // number of edges in the boundary of the face + int firstEdge; // first edge in the edge index + short areas[2]; // area at the front and back of this face +} aasFace_t; + +// area with a boundary of faces +typedef struct aasArea_s { + int numFaces; // number of faces used for the boundary of the area + int firstFace; // first face in the face index used for the boundary of the area + idBounds bounds; // bounds of the area + idVec3 center; // center of the area an AI can move towards + unsigned short flags; // several area flags + unsigned short contents; // contents of the area + short cluster; // cluster the area belongs to, if negative it's a portal + short clusterAreaNum; // number of the area in the cluster + int travelFlags; // travel flags for traveling through this area + idReachability * reach; // reachabilities that start from this area + idReachability * rev_reach; // reachabilities that lead to this area +} aasArea_t; + +// nodes of the bsp tree +typedef struct aasNode_s { + unsigned short planeNum; // number of the plane that splits the subspace at this node + int children[2]; // child nodes, zero is solid, negative is -(area number) +} aasNode_t; + +// cluster portal +typedef struct aasPortal_s { + short areaNum; // number of the area that is the actual portal + short clusters[2]; // number of cluster at the front and back of the portal + short clusterAreaNum[2]; // number of this portal area in the front and back cluster + unsigned short maxAreaTravelTime; // maximum travel time through the portal area +} aasPortal_t; + +// cluster +typedef struct aasCluster_s { + int numAreas; // number of areas in the cluster + int numReachableAreas; // number of areas with reachabilities + int numPortals; // number of cluster portals + int firstPortal; // first cluster portal in the index +} aasCluster_t; + +// trace through the world +typedef struct aasTrace_s { + // parameters + int flags; // areas with these flags block the trace + int travelFlags; // areas with these travel flags block the trace + int maxAreas; // size of the 'areas' array + int getOutOfSolid; // trace out of solid if the trace starts in solid + // output + float fraction; // fraction of trace completed + idVec3 endpos; // end position of trace + int planeNum; // plane hit + int lastAreaNum; // number of last area the trace went through + int blockingAreaNum; // area that could not be entered + int numAreas; // number of areas the trace went through + int * areas; // array to store areas the trace went through + idVec3 * points; // points where the trace entered each new area + aasTrace_s() { areas = NULL; points = NULL; getOutOfSolid = false; flags = travelFlags = maxAreas = 0; } +} aasTrace_t; + +// settings +class idAASSettings { +public: + // collision settings + int numBoundingBoxes; + idBounds boundingBoxes[MAX_AAS_BOUNDING_BOXES]; + bool usePatches; + bool writeBrushMap; + bool playerFlood; + bool noOptimize; + bool allowSwimReachabilities; + bool allowFlyReachabilities; + idStr fileExtension; + // physics settings + idVec3 gravity; + idVec3 gravityDir; + idVec3 invGravityDir; + float gravityValue; + float maxStepHeight; + float maxBarrierHeight; + float maxWaterJumpHeight; + float maxFallHeight; + float minFloorCos; + // fixed travel times + int tt_barrierJump; + int tt_startCrouching; + int tt_waterJump; + int tt_startWalkOffLedge; + +public: + idAASSettings(); + + bool FromFile( const idStr &fileName ); + bool FromParser( idLexer &src ); + bool FromDict( const char *name, const idDict *dict ); + bool WriteToFile( idFile *fp ) const; + bool ValidForBounds( const idBounds &bounds ) const; + bool ValidEntity( const char *classname ) const; + +private: + bool ParseBool( idLexer &src, bool &b ); + bool ParseInt( idLexer &src, int &i ); + bool ParseFloat( idLexer &src, float &f ); + bool ParseVector( idLexer &src, idVec3 &vec ); + bool ParseBBoxes( idLexer &src ); +}; + + +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + the cluster number set to the negative portal number +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +- portal zero is a dummy +- cluster zero is a dummy + +*/ + + +class idAASFile { +public: + virtual ~idAASFile() {} + + const char * GetName() const { return name.c_str(); } + unsigned int GetCRC() const { return crc; } + + int GetNumPlanes() const { return planeList.Num(); } + const idPlane & GetPlane( int index ) const { return planeList[index]; } + int GetNumVertices() const { return vertices.Num(); } + const aasVertex_t & GetVertex( int index ) const { return vertices[index]; } + int GetNumEdges() const { return edges.Num(); } + const aasEdge_t & GetEdge( int index ) const { return edges[index]; } + int GetNumEdgeIndexes() const { return edgeIndex.Num(); } + const aasIndex_t & GetEdgeIndex( int index ) const { return edgeIndex[index]; } + int GetNumFaces() const { return faces.Num(); } + const aasFace_t & GetFace( int index ) const { return faces[index]; } + int GetNumFaceIndexes() const { return faceIndex.Num(); } + const aasIndex_t & GetFaceIndex( int index ) const { return faceIndex[index]; } + int GetNumAreas() const { return areas.Num(); } + const aasArea_t & GetArea( int index ) { return areas[index]; } + int GetNumNodes() const { return nodes.Num(); } + const aasNode_t & GetNode( int index ) const { return nodes[index]; } + int GetNumPortals() const { return portals.Num(); } + const aasPortal_t & GetPortal( int index ) { return portals[index]; } + int GetNumPortalIndexes() const { return portalIndex.Num(); } + const aasIndex_t & GetPortalIndex( int index ) const { return portalIndex[index]; } + int GetNumClusters() const { return clusters.Num(); } + const aasCluster_t & GetCluster( int index ) const { return clusters[index]; } + + const idAASSettings & GetSettings() const { return settings; } + + void SetPortalMaxTravelTime( int index, int time ) { portals[index].maxAreaTravelTime = time; } + void SetAreaTravelFlag( int index, int flag ) { areas[index].travelFlags |= flag; } + void RemoveAreaTravelFlag( int index, int flag ) { areas[index].travelFlags &= ~flag; } + + virtual idVec3 EdgeCenter( int edgeNum ) const = 0; + virtual idVec3 FaceCenter( int faceNum ) const = 0; + virtual idVec3 AreaCenter( int areaNum ) const = 0; + + virtual idBounds EdgeBounds( int edgeNum ) const = 0; + virtual idBounds FaceBounds( int faceNum ) const = 0; + virtual idBounds AreaBounds( int areaNum ) const = 0; + + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const = 0; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const = 0; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &point ) const = 0; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + virtual void PrintInfo() const = 0; + +protected: + idStr name; + unsigned int crc; + + idPlaneSet planeList; + idList vertices; + idList edges; + idList edgeIndex; + idList faces; + idList faceIndex; + idList areas; + idList nodes; + idList portals; + idList portalIndex; + idList clusters; + idAASSettings settings; +}; + +#endif /* !__AASFILE_H__ */ diff --git a/neo/aas/AASFileManager.cpp b/neo/aas/AASFileManager.cpp new file mode 100644 index 00000000..99d8dad4 --- /dev/null +++ b/neo/aas/AASFileManager.cpp @@ -0,0 +1,77 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "AASFile.h" +#include "AASFile_local.h" + +/* +=============================================================================== + + AAS File Manager + +=============================================================================== +*/ + +class idAASFileManagerLocal : public idAASFileManager { +public: + virtual ~idAASFileManagerLocal() {} + + virtual idAASFile * LoadAAS( const char *fileName, unsigned int mapFileCRC ); + virtual void FreeAAS( idAASFile *file ); +}; + +idAASFileManagerLocal AASFileManagerLocal; +idAASFileManager * AASFileManager = &AASFileManagerLocal; + + +/* +================ +idAASFileManagerLocal::LoadAAS +================ +*/ +idAASFile *idAASFileManagerLocal::LoadAAS( const char *fileName, unsigned int mapFileCRC ) { + idAASFileLocal *file = new (TAG_AAS) idAASFileLocal(); + if ( !file->Load( fileName, mapFileCRC ) ) { + delete file; + return NULL; + } + return file; +} + +/* +================ +idAASFileManagerLocal::FreeAAS +================ +*/ +void idAASFileManagerLocal::FreeAAS( idAASFile *file ) { + delete file; +} diff --git a/neo/aas/AASFileManager.h b/neo/aas/AASFileManager.h new file mode 100644 index 00000000..ae54b9ab --- /dev/null +++ b/neo/aas/AASFileManager.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AASFILEMANAGER_H__ +#define __AASFILEMANAGER_H__ + +/* +=============================================================================== + + AAS File Manager + +=============================================================================== +*/ + +class idAASFileManager { +public: + virtual ~idAASFileManager() {} + + virtual idAASFile * LoadAAS( const char *fileName, unsigned int mapFileCRC ) = 0; + virtual void FreeAAS( idAASFile *file ) = 0; +}; + +extern idAASFileManager * AASFileManager; + +#endif /* !__AASFILEMANAGER_H__ */ diff --git a/neo/aas/AASFile_local.h b/neo/aas/AASFile_local.h new file mode 100644 index 00000000..cbf30918 --- /dev/null +++ b/neo/aas/AASFile_local.h @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AASFILELOCAL_H__ +#define __AASFILELOCAL_H__ + +/* +=============================================================================== + + AAS File Local + +=============================================================================== +*/ + +class idAASFileLocal : public idAASFile { + friend class idAASBuild; + friend class idAASReach; + friend class idAASCluster; +public: + idAASFileLocal(); + virtual ~idAASFileLocal(); + +public: + virtual idVec3 EdgeCenter( int edgeNum ) const; + virtual idVec3 FaceCenter( int faceNum ) const; + virtual idVec3 AreaCenter( int areaNum ) const; + + virtual idBounds EdgeBounds( int edgeNum ) const; + virtual idBounds FaceBounds( int faceNum ) const; + virtual idBounds AreaBounds( int areaNum ) const; + + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &point ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual void PrintInfo() const; + +public: + bool Load( const idStr &fileName, unsigned int mapFileCRC ); + bool Write( const idStr &fileName, unsigned int mapFileCRC ); + + int MemorySize() const; + void ReportRoutingEfficiency() const; + void Optimize(); + void LinkReversedReachability(); + void FinishAreas(); + + void Clear(); + void DeleteReachabilities(); + void DeleteClusters(); + +private: + bool ParseIndex( idLexer &src, idList &indexes ); + bool ParsePlanes( idLexer &src ); + bool ParseVertices( idLexer &src ); + bool ParseEdges( idLexer &src ); + bool ParseFaces( idLexer &src ); + bool ParseReachabilities( idLexer &src, int areaNum ); + bool ParseAreas( idLexer &src ); + bool ParseNodes( idLexer &src ); + bool ParsePortals( idLexer &src ); + bool ParseClusters( idLexer &src ); + +private: + int BoundsReachableAreaNum_r( int nodeNum, const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const; + void MaxTreeDepth_r( int nodeNum, int &depth, int &maxDepth ) const; + int MaxTreeDepth() const; + int AreaContentsTravelFlags( int areaNum ) const; + idVec3 AreaReachableGoal( int areaNum ) const; + int NumReachabilities() const; +}; + +#endif /* !__AASFILELOCAL_H__ */ diff --git a/neo/aas/AASFile_optimize.cpp b/neo/aas/AASFile_optimize.cpp new file mode 100644 index 00000000..83147fb8 --- /dev/null +++ b/neo/aas/AASFile_optimize.cpp @@ -0,0 +1,155 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "AASFile.h" +#include "AASFile_local.h" + + +//=============================================================== +// +// optimize file +// +//=============================================================== + +/* +================ +idAASFileLocal::Optimize +================ +*/ +void idAASFileLocal::Optimize() { + int i, j, k, faceNum, edgeNum, areaFirstFace, faceFirstEdge; + aasArea_t *area; + aasFace_t *face; + aasEdge_t *edge; + idReachability *reach; + idList vertexRemap; + idList edgeRemap; + idList faceRemap; + idList newVertices; + idList newEdges; + idList newEdgeIndex; + idList newFaces; + idList newFaceIndex; + + vertexRemap.AssureSize( vertices.Num(), -1 ); + edgeRemap.AssureSize( edges.Num(), 0 ); + faceRemap.AssureSize( faces.Num(), 0 ); + + newVertices.Resize( vertices.Num() ); + newEdges.Resize( edges.Num() ); + newEdges.SetNum( 1 ); + newEdgeIndex.Resize( edgeIndex.Num() ); + newFaces.Resize( faces.Num() ); + newFaces.SetNum( 1 ); + newFaceIndex.Resize( faceIndex.Num() ); + + for ( i = 0; i < areas.Num(); i++ ) { + area = &areas[i]; + + areaFirstFace = newFaceIndex.Num(); + for ( j = 0; j < area->numFaces; j++ ) { + faceNum = faceIndex[area->firstFace + j]; + face = &faces[ abs(faceNum) ]; + + // store face + if ( !faceRemap[ abs(faceNum) ] ) { + faceRemap[ abs(faceNum) ] = newFaces.Num(); + newFaces.Append( *face ); + + // don't store edges for faces we don't care about + if ( !( face->flags & ( FACE_FLOOR|FACE_LADDER ) ) ) { + + newFaces[ newFaces.Num()-1 ].firstEdge = 0; + newFaces[ newFaces.Num()-1 ].numEdges = 0; + + } else { + + // store edges + faceFirstEdge = newEdgeIndex.Num(); + for ( k = 0; k < face->numEdges; k++ ) { + edgeNum = edgeIndex[ face->firstEdge + k ]; + edge = &edges[ abs(edgeNum) ]; + + if ( !edgeRemap[ abs(edgeNum) ] ) { + if ( edgeNum < 0 ) { + edgeRemap[ abs(edgeNum) ] = -newEdges.Num(); + } + else { + edgeRemap[ abs(edgeNum) ] = newEdges.Num(); + } + + // remap vertices if not yet remapped + if ( vertexRemap[ edge->vertexNum[0] ] == -1 ) { + vertexRemap[ edge->vertexNum[0] ] = newVertices.Num(); + newVertices.Append( vertices[ edge->vertexNum[0] ] ); + } + if ( vertexRemap[ edge->vertexNum[1] ] == -1 ) { + vertexRemap[ edge->vertexNum[1] ] = newVertices.Num(); + newVertices.Append( vertices[ edge->vertexNum[1] ] ); + } + + newEdges.Append( *edge ); + newEdges[ newEdges.Num()-1 ].vertexNum[0] = vertexRemap[ edge->vertexNum[0] ]; + newEdges[ newEdges.Num()-1 ].vertexNum[1] = vertexRemap[ edge->vertexNum[1] ]; + } + + newEdgeIndex.Append( edgeRemap[ abs(edgeNum) ] ); + } + + newFaces[ newFaces.Num()-1 ].firstEdge = faceFirstEdge; + newFaces[ newFaces.Num()-1 ].numEdges = newEdgeIndex.Num() - faceFirstEdge; + } + } + + if ( faceNum < 0 ) { + newFaceIndex.Append( -faceRemap[ abs(faceNum) ] ); + } else { + newFaceIndex.Append( faceRemap[ abs(faceNum) ] ); + } + } + + area->firstFace = areaFirstFace; + area->numFaces = newFaceIndex.Num() - areaFirstFace; + + // remap the reachability edges + for ( reach = area->reach; reach; reach = reach->next ) { + reach->edgeNum = abs( edgeRemap[reach->edgeNum] ); + } + } + + // store new list + vertices = newVertices; + edges = newEdges; + edgeIndex = newEdgeIndex; + faces = newFaces; + faceIndex = newFaceIndex; +} diff --git a/neo/aas/AASFile_sample.cpp b/neo/aas/AASFile_sample.cpp new file mode 100644 index 00000000..a7fcc20e --- /dev/null +++ b/neo/aas/AASFile_sample.cpp @@ -0,0 +1,609 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "AASFile.h" +#include "AASFile_local.h" + + +//=============================================================== +// +// Environment Sampling +// +//=============================================================== + +/* +================ +idAASFileLocal::EdgeCenter +================ +*/ +idVec3 idAASFileLocal::EdgeCenter( int edgeNum ) const { + const aasEdge_t *edge; + edge = &edges[edgeNum]; + return ( vertices[edge->vertexNum[0]] + vertices[edge->vertexNum[1]] ) * 0.5f; +} + +/* +================ +idAASFileLocal::FaceCenter +================ +*/ +idVec3 idAASFileLocal::FaceCenter( int faceNum ) const { + int i, edgeNum; + const aasFace_t *face; + const aasEdge_t *edge; + idVec3 center; + + center = vec3_origin; + + face = &faces[faceNum]; + if ( face->numEdges > 0 ) { + for ( i = 0; i < face->numEdges; i++ ) { + edgeNum = edgeIndex[ face->firstEdge + i ]; + edge = &edges[ abs( edgeNum ) ]; + center += vertices[ edge->vertexNum[ INT32_SIGNBITSET(edgeNum) ] ]; + } + center /= face->numEdges; + } + return center; +} + +/* +================ +idAASFileLocal::AreaCenter +================ +*/ +idVec3 idAASFileLocal::AreaCenter( int areaNum ) const { + int i, faceNum; + const aasArea_t *area; + idVec3 center; + + center = vec3_origin; + + area = &areas[areaNum]; + if ( area->numFaces > 0 ) { + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + center += FaceCenter( abs(faceNum) ); + } + center /= area->numFaces; + } + return center; +} + +/* +============ +idAASFileLocal::AreaReachableGoal +============ +*/ +idVec3 idAASFileLocal::AreaReachableGoal( int areaNum ) const { + int i, faceNum, numFaces; + const aasArea_t *area; + idVec3 center; + idVec3 start, end; + aasTrace_t trace; + + area = &areas[areaNum]; + + if ( !(area->flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) || (area->flags & AREA_LIQUID) ) { + return AreaCenter( areaNum ); + } + + center = vec3_origin; + + numFaces = 0; + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + if ( !(faces[abs(faceNum)].flags & FACE_FLOOR) ) { + continue; + } + center += FaceCenter( abs(faceNum) ); + numFaces++; + } + if ( numFaces > 0 ) { + center /= numFaces; + } + center[2] += 1.0f; + end = center; + end[2] -= 1024; + Trace( trace, center, end ); + + return trace.endpos; +} + +/* +================ +idAASFileLocal::EdgeBounds +================ +*/ +idBounds idAASFileLocal::EdgeBounds( int edgeNum ) const { + const aasEdge_t *edge; + idBounds bounds; + + edge = &edges[ abs( edgeNum ) ]; + bounds[0] = bounds[1] = vertices[ edge->vertexNum[0] ]; + bounds += vertices[ edge->vertexNum[1] ]; + return bounds; +} + +/* +================ +idAASFileLocal::FaceBounds +================ +*/ +idBounds idAASFileLocal::FaceBounds( int faceNum ) const { + int i, edgeNum; + const aasFace_t *face; + const aasEdge_t *edge; + idBounds bounds; + + face = &faces[faceNum]; + bounds.Clear(); + + for ( i = 0; i < face->numEdges; i++ ) { + edgeNum = edgeIndex[ face->firstEdge + i ]; + edge = &edges[ abs( edgeNum ) ]; + bounds.AddPoint( vertices[ edge->vertexNum[ INT32_SIGNBITSET(edgeNum) ] ] ); + } + return bounds; +} + +/* +================ +idAASFileLocal::AreaBounds +================ +*/ +idBounds idAASFileLocal::AreaBounds( int areaNum ) const { + int i, faceNum; + const aasArea_t *area; + idBounds bounds; + + area = &areas[areaNum]; + bounds.Clear(); + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + bounds += FaceBounds( abs(faceNum) ); + } + return bounds; +} + +/* +============ +idAASFileLocal::PointAreaNum +============ +*/ +int idAASFileLocal::PointAreaNum( const idVec3 &origin ) const { + int nodeNum; + const aasNode_t *node; + + nodeNum = 1; + do { + node = &nodes[nodeNum]; + if ( planeList[node->planeNum].Side( origin ) == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else { + nodeNum = node->children[0]; + } + if ( nodeNum < 0 ) { + return -nodeNum; + } + } while( nodeNum ); + + return 0; +} + +/* +============ +idAASFileLocal::PointReachableAreaNum +============ +*/ +int idAASFileLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const { + int areaList[32], areaNum, i; + idVec3 start, end, pointList[32]; + aasTrace_t trace; + idBounds bounds; + float frac; + + start = origin; + + trace.areas = areaList; + trace.points = pointList; + trace.maxAreas = sizeof( areaList ) / sizeof( int ); + trace.getOutOfSolid = true; + + areaNum = PointAreaNum( start ); + if ( areaNum ) { + if ( ( areas[areaNum].flags & areaFlags ) && ( ( areas[areaNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return areaNum; + } + } + else { + // trace up + end = start; + end[2] += 32.0f; + Trace( trace, start, end ); + if ( trace.numAreas >= 1 ) { + if ( ( areas[0].flags & areaFlags ) && ( ( areas[0].travelFlags & excludeTravelFlags ) == 0 ) ) { + return areaList[0]; + } + start = pointList[0]; + start[2] += 1.0f; + } + } + + // trace down + end = start; + end[2] -= 32.0f; + Trace( trace, start, end ); + if ( trace.lastAreaNum ) { + if ( ( areas[trace.lastAreaNum].flags & areaFlags ) && ( ( areas[trace.lastAreaNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return trace.lastAreaNum; + } + start = trace.endpos; + } + + // expand bounds until an area is found + for ( i = 1; i <= 12; i++ ) { + frac = i * ( 1.0f / 12.0f ); + bounds[0] = origin + searchBounds[0] * frac; + bounds[1] = origin + searchBounds[1] * frac; + areaNum = BoundsReachableAreaNum( bounds, areaFlags, excludeTravelFlags ); + if ( areaNum && ( areas[areaNum].flags & areaFlags ) && ( ( areas[areaNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return areaNum; + } + } + return 0; +} + +/* +============ +idAASFileLocal::BoundsReachableAreaNum_r +============ +*/ +int idAASFileLocal::BoundsReachableAreaNum_r( int nodeNum, const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const { + int res; + const aasNode_t *node; + + while( nodeNum ) { + if ( nodeNum < 0 ) { + if ( ( areas[-nodeNum].flags & areaFlags ) && ( ( areas[-nodeNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return -nodeNum; + } + return 0; + } + node = &nodes[nodeNum]; + res = bounds.PlaneSide( planeList[node->planeNum] ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + nodeNum = BoundsReachableAreaNum_r( node->children[1], bounds, areaFlags, excludeTravelFlags ); + if ( nodeNum ) { + return nodeNum; + } + nodeNum = node->children[0]; + } + } + + return 0; +} + +/* +============ +idAASFileLocal::BoundsReachableAreaNum +============ +*/ +int idAASFileLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const { + + return BoundsReachableAreaNum_r( 1, bounds, areaFlags, excludeTravelFlags ); +} + +/* +============ +idAASFileLocal::PushPointIntoAreaNum +============ +*/ +void idAASFileLocal::PushPointIntoAreaNum( int areaNum, idVec3 &point ) const { + int i, faceNum; + const aasArea_t *area; + const aasFace_t *face; + + area = &areas[areaNum]; + + // push the point to the right side of all area face planes + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + face = &faces[abs( faceNum )]; + + const idPlane &plane = planeList[face->planeNum ^ INT32_SIGNBITSET( faceNum )]; + float dist = plane.Distance( point ); + + // project the point onto the face plane if it is on the wrong side + if ( dist < 0.0f ) { + point -= dist * plane.Normal(); + } + } +} + +/* +============ +idAASFileLocal::Trace +============ +*/ +#define TRACEPLANE_EPSILON 0.125f + +typedef struct aasTraceStack_s +{ + idVec3 start; + idVec3 end; + int planeNum; + int nodeNum; +} aasTraceStack_t; + +bool idAASFileLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + int side, nodeNum, tmpPlaneNum; + double front, back, frac; + idVec3 cur_start, cur_end, cur_mid, v1, v2; + aasTraceStack_t tracestack[MAX_AAS_TREE_DEPTH]; + aasTraceStack_t *tstack_p; + const aasNode_t *node; + const idPlane *plane; + + trace.numAreas = 0; + trace.lastAreaNum = 0; + trace.blockingAreaNum = 0; + + tstack_p = tracestack; + tstack_p->start = start; + tstack_p->end = end; + tstack_p->planeNum = 0; + tstack_p->nodeNum = 1; //start with the root of the tree + tstack_p++; + + while( 1 ) { + + tstack_p--; + // if the trace stack is empty + if ( tstack_p < tracestack ) { + if ( !trace.lastAreaNum ) { + // completely in solid + trace.fraction = 0.0f; + trace.endpos = start; + } + else { + // nothing was hit + trace.fraction = 1.0f; + trace.endpos = end; + } + trace.planeNum = 0; + return false; + } + + // number of the current node to test the line against + nodeNum = tstack_p->nodeNum; + + // if it is an area + if ( nodeNum < 0) { + // if can't enter the area + if ( ( areas[-nodeNum].flags & trace.flags ) || ( areas[-nodeNum].travelFlags & trace.travelFlags ) ) { + if ( !trace.lastAreaNum ) { + trace.fraction = 0.0f; + v1 = vec3_origin; + } else { + v1 = end - start; + v2 = tstack_p->start - start; + trace.fraction = v2.Length() / v1.Length(); + } + trace.endpos = tstack_p->start; + trace.blockingAreaNum = -nodeNum; + trace.planeNum = tstack_p->planeNum; + // always take the plane with normal facing towards the trace start + plane = &planeList[trace.planeNum]; + if ( v1 * plane->Normal() > 0.0f ) { + trace.planeNum ^= 1; + } + return true; + } + trace.lastAreaNum = -nodeNum; + if ( trace.numAreas < trace.maxAreas ) { + if ( trace.areas ) { + trace.areas[trace.numAreas] = -nodeNum; + } + if ( trace.points ) { + trace.points[trace.numAreas] = tstack_p->start; + } + trace.numAreas++; + } + continue; + } + + // if it is a solid leaf + if ( !nodeNum ) { + if ( !trace.lastAreaNum ) { + trace.fraction = 0.0f; + v1 = vec3_origin; + } else { + v1 = end - start; + v2 = tstack_p->start - start; + trace.fraction = v2.Length() / v1.Length(); + } + trace.endpos = tstack_p->start; + trace.blockingAreaNum = 0; // hit solid leaf + trace.planeNum = tstack_p->planeNum; + // always take the plane with normal facing towards the trace start + plane = &planeList[trace.planeNum]; + if ( v1 * plane->Normal() > 0.0f ) { + trace.planeNum ^= 1; + } + if ( !trace.lastAreaNum && trace.getOutOfSolid ) { + continue; + } + else { + return true; + } + } + + // the node to test against + node = &nodes[nodeNum]; + // start point of current line to test against node + cur_start = tstack_p->start; + // end point of the current line to test against node + cur_end = tstack_p->end; + // the current node plane + plane = &planeList[node->planeNum]; + + front = plane->Distance( cur_start ); + back = plane->Distance( cur_end ); + + // if the whole to be traced line is totally at the front of this node + // only go down the tree with the front child + if ( front >= -ON_EPSILON && back >= -ON_EPSILON ) { + // keep the current start and end point on the stack and go down the tree with the front child + tstack_p->nodeNum = node->children[0]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + } + // if the whole to be traced line is totally at the back of this node + // only go down the tree with the back child + else if ( front < ON_EPSILON && back < ON_EPSILON ) { + // keep the current start and end point on the stack and go down the tree with the back child + tstack_p->nodeNum = node->children[1]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + } + // go down the tree both at the front and back of the node + else { + tmpPlaneNum = tstack_p->planeNum; + // calculate the hit point with the node plane + // put the cross point TRACEPLANE_EPSILON on the near side + if (front < 0) { + frac = (front + TRACEPLANE_EPSILON) / ( front - back ); + } + else { + frac = (front - TRACEPLANE_EPSILON) / ( front - back ); + } + + if (frac < 0) { + frac = 0.001f; //0 + } + else if (frac > 1) { + frac = 0.999f; //1 + } + + cur_mid = cur_start + ( cur_end - cur_start ) * frac; + + // side the front part of the line is on + side = front < 0; + + // first put the end part of the line on the stack (back side) + tstack_p->start = cur_mid; + tstack_p->planeNum = node->planeNum; + tstack_p->nodeNum = node->children[!side]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + // now put the part near the start of the line on the stack so we will + // continue with that part first. + tstack_p->start = cur_start; + tstack_p->end = cur_mid; + tstack_p->planeNum = tmpPlaneNum; + tstack_p->nodeNum = node->children[side]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + } + } + return false; +} + +/* +============ +idAASLocal::AreaContentsTravelFlags +============ +*/ +int idAASFileLocal::AreaContentsTravelFlags( int areaNum ) const { + if ( areas[areaNum].contents & AREACONTENTS_WATER ) { + return TFL_WATER; + } + return TFL_AIR; +} + +/* +============ +idAASFileLocal::MaxTreeDepth_r +============ +*/ +void idAASFileLocal::MaxTreeDepth_r( int nodeNum, int &depth, int &maxDepth ) const { + const aasNode_t *node; + + if ( nodeNum <= 0 ) { + return; + } + + depth++; + if ( depth > maxDepth ) { + maxDepth = depth; + } + + node = &nodes[nodeNum]; + MaxTreeDepth_r( node->children[0], depth, maxDepth ); + MaxTreeDepth_r( node->children[1], depth, maxDepth ); + + depth--; +} + +/* +============ +idAASFileLocal::MaxTreeDepth +============ +*/ +int idAASFileLocal::MaxTreeDepth() const { + int depth, maxDepth; + + depth = maxDepth = 0; + MaxTreeDepth_r( 1, depth, maxDepth ); + return maxDepth; +} diff --git a/neo/amplitude/amplitude.cpp b/neo/amplitude/amplitude.cpp new file mode 100644 index 00000000..db0e7c11 --- /dev/null +++ b/neo/amplitude/amplitude.cpp @@ -0,0 +1,343 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include +#include +#include +#include +#include + +static const int SAMPLE_RATE = 60; + +enum errorCodes_t { + E_OK = 0, + E_ARGS, + E_OPEN_IN, + E_OPEN_OUT, + E_PROCESSING +}; + +struct chunk_t { + unsigned int id; + unsigned int size; + unsigned int offset; +}; + +static unsigned short FORMAT_PCM = 0x0001; + +struct format_t { + unsigned short formatTag; + unsigned short numChannels; + unsigned int samplesPerSec; + unsigned int avgBytesPerSec; + unsigned short sampleSize; + unsigned short bitsPerSample; +}; + +#define SwapBytes( x, y ) { unsigned char t = (x); (x) = (y); (y) = t; } + +template static void Swap( type &c ) { + if ( sizeof( type ) == 1 ) { + } else if ( sizeof( type ) == 2 ) { + unsigned char * b = (unsigned char *)&c; + SwapBytes( b[0], b[1] ); + } else if ( sizeof( type ) == 4 ) { + unsigned char * b = (unsigned char *)&c; + SwapBytes( b[0], b[3] ); + SwapBytes( b[1], b[2] ); + } else if ( sizeof( type ) == 8 ) { + unsigned char * b = (unsigned char *)&c; + SwapBytes( b[0], b[7] ); + SwapBytes( b[1], b[6]); + SwapBytes( b[2], b[5] ); + SwapBytes( b[3], b[4] ); + } else { + int * null = 0; + c = *null; + } +} + +int WAVE_ReadHeader( FILE * f ) { + struct header_t { + unsigned int id; + unsigned int size; + unsigned int format; + } header; + + fread( &header, sizeof( header ), 1, f ); + Swap( header.id ); + Swap( header.format ); + + if ( header.id != 'RIFF' || header.format != 'WAVE' || header.size < 4 ) { + return 0; + } + + return header.size; +} + +int WAVE_ReadChunks( FILE * f, unsigned int fileSize, chunk_t * chunks, int maxChunks ) { + unsigned int offset = ftell( f ); + int numChunks = 0; + + while ( offset < fileSize ) { + struct chuckHeader_t { + unsigned int id; + unsigned int size; + } chunkHeader; + if ( fread( &chunkHeader, sizeof( chunkHeader ), 1, f ) != 1 ) { + return numChunks; + } + Swap( chunkHeader.id ); + offset += sizeof( chunkHeader ); + + if ( numChunks == maxChunks ) { + return maxChunks + 1; + } + + chunks[numChunks].id = chunkHeader.id; + chunks[numChunks].size = chunkHeader.size; + chunks[numChunks].offset = offset; + numChunks++; + + offset += chunkHeader.size; + fseek( f, offset, SEEK_SET ); + } + + return numChunks; +} + +bool Process( FILE * in, FILE * out ) { + + int headerSize = WAVE_ReadHeader( in ); + if ( headerSize == 0 ) { + printf( "Header invalid\n" ); + return false; + } + + static const int MAX_CHUNKS = 32; + chunk_t chunks[MAX_CHUNKS] = {}; + + int numChunks = WAVE_ReadChunks( in, headerSize + 8, chunks, MAX_CHUNKS ); + if ( numChunks == 0 ) { + printf( "No chunks\n" ); + return false; + } + if ( numChunks > MAX_CHUNKS ) { + printf( "Too many chunks\n" ); + return false; + } + + format_t format; + bool foundFormat = false; + unsigned int dataOffset = 0; + unsigned int dataSize = 0; + for ( int i = 0; i < numChunks; i++ ) { + if ( chunks[i].id == 'fmt ' ) { + if ( foundFormat ) { + printf( "Found multiple format chunks\n" ); + return false; + } + if ( chunks[i].size < sizeof( format ) ) { + printf( "Format chunk too small\n" ); + return false; + } + fseek( in, chunks[i].offset, SEEK_SET ); + fread( &format, sizeof( format ), 1, in ); + foundFormat = true; + } + if ( chunks[i].id == 'data' ) { + if ( dataOffset > 0 ) { + printf( "Found multiple data chunks\n" ); + return false; + } + dataOffset = chunks[i].offset; + dataSize = chunks[i].size; + } + } + if ( dataOffset == 0 ) { + printf( "Colud not find data chunk\n" ); + return false; + } + if ( !foundFormat ) { + printf( "Could not find fmt chunk\n" ); + return false; + } + if ( format.formatTag != FORMAT_PCM ) { + printf( "Only PCM files supported (%d)\n", format.formatTag ); + return false; + } + if ( format.bitsPerSample != 8 && format.bitsPerSample != 16 ) { + printf( "Only 8 or 16 bit files supported (%d)\n", format.bitsPerSample ); + return false; + } + if ( format.numChannels != 1 && format.numChannels != 2 ) { + printf( "Only stereo or mono files supported (%d)\n", format.numChannels ); + return false; + } + unsigned short expectedSampleSize = format.numChannels * format.bitsPerSample / 8; + if ( format.sampleSize != expectedSampleSize ) { + printf( "Invalid sampleSize (%d, expected %d)\n", format.sampleSize, expectedSampleSize ); + return false; + } + unsigned int numSamples = dataSize / expectedSampleSize; + + void * inputData = malloc( dataSize ); + if ( inputData == NULL ) { + printf( "Out of memory\n" ); + return false; + } + fseek( in, dataOffset, SEEK_SET ); + fread( inputData, dataSize, 1, in ); + + int numOutputSamples = 1 + ( numSamples * SAMPLE_RATE / format.samplesPerSec ); + float * min = (float *)malloc( numOutputSamples * sizeof( float ) ); + float * max = (float *)malloc( numOutputSamples * sizeof( float ) ); + unsigned char * outputData = (unsigned char *)malloc( numOutputSamples ); + if ( min == NULL || max == NULL || outputData == NULL ) { + printf( "Out of memory\n" ); + free( inputData ); + free( min ); + free( max ); + free( outputData ); + return false; + } + for ( int i = 0; i < numOutputSamples; i++ ) { + max[i] = -1.0f; + min[i] = 1.0f; + } + + if ( format.bitsPerSample == 16 ) { + short * sdata = (short *)inputData; + if ( format.numChannels == 1 ) { + for ( unsigned int i = 0; i < numSamples; i++ ) { + unsigned int index = i * SAMPLE_RATE / format.samplesPerSec; + float fdata = (float)sdata[i] / 32767.0f; + min[index] = __min( min[index], fdata ); + max[index] = __max( max[index], fdata ); + } + } else { + unsigned int j = 0; + for ( unsigned int i = 0; i < numSamples; i++ ) { + unsigned int index = i * SAMPLE_RATE / format.samplesPerSec; + for ( unsigned int c = 0; c < format.numChannels; c++ ) { + float fdata = (float)sdata[j++] / 32767.0f; + min[index] = __min( min[index], fdata ); + max[index] = __max( max[index], fdata ); + } + } + } + } else { + unsigned char * bdata = (unsigned char *)inputData; + if ( format.numChannels == 1 ) { + for ( unsigned int i = 0; i < numSamples; i++ ) { + unsigned int index = i * SAMPLE_RATE / format.samplesPerSec; + float fdata = ( (float)bdata[i] - 128.0f ) / 127.0f; + min[index] = __min( min[index], fdata ); + max[index] = __max( max[index], fdata ); + } + } else { + unsigned int j = 0; + for ( unsigned int i = 0; i < numSamples; i++ ) { + unsigned int index = i * SAMPLE_RATE / format.samplesPerSec; + for ( unsigned int c = 0; c < format.numChannels; c++ ) { + float fdata = ( (float)bdata[j++] - 128.0f ) / 127.0f; + min[index] = __min( min[index], fdata ); + max[index] = __max( max[index], fdata ); + } + } + } + } + for ( int i = 0; i < numOutputSamples; i++ ) { + float amp = atan( max[i] - min[i] ) / 0.7853981633974483f; + int o = (int)( amp * 255.0f ); + if ( o > 255 ) { + outputData[i] = 255; + } else if ( o < 0 ) { + outputData[i] = 0; + } else { + outputData[i] = (unsigned char)o; + } + } + fwrite( outputData, numOutputSamples, 1, out ); + + free( inputData ); + free( min ); + free( max ); + free( outputData ); + + printf( "Success\n" ); + return true; +} + +int main(int argc, char * argv[]) { + + if ( argc < 2 ) { + printf( "Usage: %s \n", argv[0] ); + return E_ARGS; + } + const char * inputFileName = argv[1]; + + printf( "Processing %s: ", inputFileName ); + + FILE * in = NULL; + if ( fopen_s( &in, inputFileName, "rb" ) != 0 ) { + printf( "Could not open input file\n" ); + return E_OPEN_IN; + } + char outputFileName[1024] = {0}; + if ( strcpy_s( outputFileName, inputFileName ) != 0 ) { + printf( "Filename too long\n" ); + return E_ARGS; + } + char * dot = strrchr( outputFileName, '.' ); + if ( dot == NULL ) { + dot = outputFileName + strlen( outputFileName ); + } + if ( strcpy_s( dot, sizeof( outputFileName ) - ( dot - outputFileName ), ".amp" ) != 0 ) { + printf( "Filename too long\n" ); + return E_ARGS; + } + + FILE * out = NULL; + if ( fopen_s( &out, outputFileName, "wb" ) != 0 ) { + printf( "Could not open output file %s\n", outputFileName ); + return E_OPEN_OUT; + } + + bool success = Process( in, out ); + + fclose( in ); + fclose( out ); + + if ( !success ) { + remove( outputFileName ); + return E_PROCESSING; + } + + return E_OK; +} diff --git a/neo/amplitude/amplitude.vcxproj b/neo/amplitude/amplitude.vcxproj new file mode 100644 index 00000000..b16f24b7 --- /dev/null +++ b/neo/amplitude/amplitude.vcxproj @@ -0,0 +1,126 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Retail + Win32 + + + + + + + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD} + Win32Proj + amplitude + + + + + + + + + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + + + + true + $(Configuration)\ + + + false + $(Configuration)\ + + + false + $(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + \ No newline at end of file diff --git a/neo/amplitude/amplitude.vcxproj.filters b/neo/amplitude/amplitude.vcxproj.filters new file mode 100644 index 00000000..fb74522f --- /dev/null +++ b/neo/amplitude/amplitude.vcxproj.filters @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/neo/amplitude/amplitude.vcxproj.vspscc b/neo/amplitude/amplitude.vcxproj.vspscc new file mode 100644 index 00000000..b6d32892 --- /dev/null +++ b/neo/amplitude/amplitude.vcxproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/neo/cm/CollisionModel.h b/neo/cm/CollisionModel.h new file mode 100644 index 00000000..7c30806e --- /dev/null +++ b/neo/cm/CollisionModel.h @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __COLLISIONMODELMANAGER_H__ +#define __COLLISIONMODELMANAGER_H__ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + + Short translations are the least expensive. Retrieving contact points is + about as cheap as a short translation. Position tests are more expensive + and rotations are most expensive. + + There is no position test at the start of a translation or rotation. In other + words if a translation with start != end or a rotation with angle != 0 starts + in solid, this goes unnoticed and the collision result is undefined. + + A translation with start == end or a rotation with angle == 0 performs + a position test and fills in the trace_t structure accordingly. + +=============================================================================== +*/ + +// contact type +typedef enum { + CONTACT_NONE, // no contact + CONTACT_EDGE, // trace model edge hits model edge + CONTACT_MODELVERTEX, // model vertex hits trace model polygon + CONTACT_TRMVERTEX // trace model vertex hits model polygon +} contactType_t; + +// contact info +typedef struct { + contactType_t type; // contact type + idVec3 point; // point of contact + idVec3 normal; // contact plane normal + float dist; // contact plane distance + int contents; // contents at other side of surface + const idMaterial * material; // surface material + int modelFeature; // contact feature on model + int trmFeature; // contact feature on trace model + int entityNum; // entity the contact surface is a part of + int id; // id of clip model the contact surface is part of +} contactInfo_t; + +// trace result +typedef struct trace_s { + float fraction; // fraction of movement completed, 1.0 = didn't hit anything + idVec3 endpos; // final position of trace model + idMat3 endAxis; // final axis of trace model + contactInfo_t c; // contact information, only valid if fraction < 1.0 +} trace_t; + +typedef int cmHandle_t; + +#define CM_CLIP_EPSILON 0.25f // always stay this distance away from any model +#define CM_BOX_EPSILON 1.0f // should always be larger than clip epsilon +#define CM_MAX_TRACE_DIST 4096.0f // maximum distance a trace model may be traced, point traces are unlimited + +class idCollisionModelManager { +public: + virtual ~idCollisionModelManager() {} + + // Loads collision models from a map file. + virtual void LoadMap( const idMapFile *mapFile ) = 0; + // Frees all the collision models. + virtual void FreeMap() = 0; + + virtual void Preload( const char *mapName ) = 0; + + // Gets the clip handle for a model. + virtual cmHandle_t LoadModel( const char *modelName ) = 0; + // Sets up a trace model for collision with other trace models. + virtual cmHandle_t SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) = 0; + // Creates a trace model from a collision model, returns true if succesfull. + virtual bool TrmFromModel( const char *modelName, idTraceModel &trm ) = 0; + + // Gets the name of a model. + virtual const char * GetModelName( cmHandle_t model ) const = 0; + // Gets the bounds of a model. + virtual bool GetModelBounds( cmHandle_t model, idBounds &bounds ) const = 0; + // Gets all contents flags of brushes and polygons of a model ored together. + virtual bool GetModelContents( cmHandle_t model, int &contents ) const = 0; + // Gets a vertex of a model. + virtual bool GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const = 0; + // Gets an edge of a model. + virtual bool GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Gets a polygon of a model. + virtual bool GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const = 0; + + // Translates a trace model and reports the first collision if any. + virtual void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Rotates a trace model and reports the first collision if any. + virtual void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Returns the contents touched by the trace model or 0 if the trace model is in free space. + virtual int Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Stores all contact points of the trace model with the model, returns the number of contacts. + virtual int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + + // Tests collision detection. + virtual void DebugOutput( const idVec3 &origin ) = 0; + // Draws a model. + virtual void DrawModel( cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const float radius ) = 0; + // Prints model information, use -1 handle for accumulated model info. + virtual void ModelInfo( cmHandle_t model ) = 0; + // Lists all loaded models. + virtual void ListModels() = 0; + // Writes a collision model file for the given map entity. + virtual bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ) = 0; +}; + +extern idCollisionModelManager * collisionModelManager; + +#endif /* !__COLLISIONMODELMANAGER_H__ */ diff --git a/neo/cm/CollisionModel_contacts.cpp b/neo/cm/CollisionModel_contacts.cpp new file mode 100644 index 00000000..f5523c45 --- /dev/null +++ b/neo/cm/CollisionModel_contacts.cpp @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Retrieving contacts + +=============================================================================== +*/ + +/* +================== +idCollisionModelManagerLocal::Contacts +================== +*/ +int idCollisionModelManagerLocal::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ) { + trace_t results; + idVec3 end; + + // same as Translation but instead of storing the first collision we store all collisions as contacts + idCollisionModelManagerLocal::getContacts = true; + idCollisionModelManagerLocal::contacts = contacts; + idCollisionModelManagerLocal::maxContacts = maxContacts; + idCollisionModelManagerLocal::numContacts = 0; + end = start + dir.SubVec3(0) * depth; + idCollisionModelManagerLocal::Translation( &results, start, end, trm, trmAxis, contentMask, model, origin, modelAxis ); + if ( dir.SubVec3(1).LengthSqr() != 0.0f ) { + // FIXME: rotational contacts + } + idCollisionModelManagerLocal::getContacts = false; + idCollisionModelManagerLocal::maxContacts = 0; + + return idCollisionModelManagerLocal::numContacts; +} diff --git a/neo/cm/CollisionModel_contents.cpp b/neo/cm/CollisionModel_contents.cpp new file mode 100644 index 00000000..8cfab106 --- /dev/null +++ b/neo/cm/CollisionModel_contents.cpp @@ -0,0 +1,636 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Contents test + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::TestTrmVertsInBrush + + returns true if any of the trm vertices is inside the brush +================ +*/ +bool idCollisionModelManagerLocal::TestTrmVertsInBrush( cm_traceWork_t *tw, cm_brush_t *b ) { + int i, j, numVerts, bestPlane; + float d, bestd; + idVec3 *p; + + if ( b->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + b->checkcount = idCollisionModelManagerLocal::checkCount; + + if ( !(b->contents & tw->contents) ) { + return false; + } + + // if the brush bounds don't intersect the trace bounds + if ( !b->bounds.IntersectsBounds( tw->bounds ) ) { + return false; + } + + if ( tw->pointTrace ) { + numVerts = 1; + } + else { + numVerts = tw->numVerts; + } + + for ( j = 0; j < numVerts; j++ ) { + p = &tw->vertices[j].p; + + // see if the point is inside the brush + bestPlane = 0; + bestd = -idMath::INFINITY; + for ( i = 0; i < b->numPlanes; i++ ) { + d = b->planes[i].Distance( *p ); + if ( d >= 0.0f ) { + break; + } + if ( d > bestd ) { + bestd = d; + bestPlane = i; + } + } + if ( i >= b->numPlanes ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.normal = b->planes[bestPlane].Normal(); + tw->trace.c.dist = b->planes[bestPlane].Dist(); + tw->trace.c.contents = b->contents; + tw->trace.c.material = b->material; + tw->trace.c.point = *p; + tw->trace.c.modelFeature = 0; + tw->trace.c.trmFeature = j; + return true; + } + } + return false; +} + +/* +================ +CM_SetTrmEdgeSidedness +================ +*/ +#define CM_SetTrmEdgeSidedness( edge, bpl, epl, bitNum ) { \ + const int mask = 1 << bitNum; \ + if ( ( edge->sideSet & mask ) == 0 ) { \ + const float fl = (bpl).PermutedInnerProduct( epl ); \ + edge->side = ( edge->side & ~mask ) | ( ( fl < 0.0f ) ? mask : 0 ); \ + edge->sideSet |= mask; \ + } \ +} + +/* +================ +CM_SetTrmPolygonSidedness +================ +*/ +#define CM_SetTrmPolygonSidedness( v, plane, bitNum ) { \ + const int mask = 1 << bitNum; \ + if ( ( (v)->sideSet & mask ) == 0 ) { \ + const float fl = plane.Distance( (v)->p ); \ + (v)->side = ( (v)->side & ~mask ) | ( ( fl < 0.0f ) ? mask : 0 ); \ + (v)->sideSet |= mask; \ + } \ +} + +/* +================ +idCollisionModelManagerLocal::TestTrmInPolygon + + returns true if the trm intersects the polygon +================ +*/ +bool idCollisionModelManagerLocal::TestTrmInPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) { + int i, j, k, edgeNum, flip, trmEdgeNum, bitNum, bestPlane; + int sides[MAX_TRACEMODEL_VERTS]; + float d, bestd; + cm_trmEdge_t *trmEdge; + cm_edge_t *edge; + cm_vertex_t *v, *v1, *v2; + + // if already checked this polygon + if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + p->checkcount = idCollisionModelManagerLocal::checkCount; + + // if this polygon does not have the right contents behind it + if ( !(p->contents & tw->contents) ) { + return false; + } + + // if the polygon bounds don't intersect the trace bounds + if ( !p->bounds.IntersectsBounds( tw->bounds ) ) { + return false; + } + + // bounds should cross polygon plane + switch( tw->bounds.PlaneSide( p->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( tw->model->isConvex ) { + tw->quickExit = true; + return true; + } + default: + return false; + } + + // if the trace model is convex + if ( tw->isConvex ) { + // test if any polygon vertices are inside the trm + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // if this edge is already tested + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + + for ( j = 0; j < 2; j++ ) { + v = &tw->model->vertices[edge->vertexNum[j]]; + // if this vertex is already tested + if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + + bestPlane = 0; + bestd = -idMath::INFINITY; + for ( k = 0; k < tw->numPolys; k++ ) { + d = tw->polys[k].plane.Distance( v->p ); + if ( d >= 0.0f ) { + break; + } + if ( d > bestd ) { + bestd = d; + bestPlane = k; + } + } + if ( k >= tw->numPolys ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_MODELVERTEX; + tw->trace.c.normal = -tw->polys[bestPlane].plane.Normal(); + tw->trace.c.dist = -tw->polys[bestPlane].plane.Dist(); + tw->trace.c.contents = p->contents; + tw->trace.c.material = p->material; + tw->trace.c.point = v->p; + tw->trace.c.modelFeature = edge->vertexNum[j]; + tw->trace.c.trmFeature = 0; + return true; + } + } + } + } + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // reset sidedness cache if this is the first time we encounter this edge + if ( edge->checkcount != idCollisionModelManagerLocal::checkCount ) { + edge->sideSet = 0; + } + // pluecker coordinate for edge + tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[edge->vertexNum[0]].p, + tw->model->vertices[edge->vertexNum[1]].p ); + v = &tw->model->vertices[edge->vertexNum[INT32_SIGNBITSET( edgeNum )]]; + // reset sidedness cache if this is the first time we encounter this vertex + if ( v->checkcount != idCollisionModelManagerLocal::checkCount ) { + v->sideSet = 0; + } + v->checkcount = idCollisionModelManagerLocal::checkCount; + } + + // get side of polygon for each trm vertex + for ( i = 0; i < tw->numVerts; i++ ) { + d = p->plane.Distance( tw->vertices[i].p ); + sides[i] = d < 0.0f ? -1 : 1; + } + + // test if any trm edges go through the polygon + for ( i = 1; i <= tw->numEdges; i++ ) { + // if the trm edge does not cross the polygon plane + if ( sides[tw->edges[i].vertexNum[0]] == sides[tw->edges[i].vertexNum[1]] ) { + continue; + } + // check from which side to which side the trm edge goes + flip = INT32_SIGNBITSET( sides[tw->edges[i].vertexNum[0]] ); + // test if trm edge goes through the polygon between the polygon edges + for ( j = 0; j < p->numEdges; j++ ) { + edgeNum = p->edges[j]; + edge = tw->model->edges + abs(edgeNum); +#if 1 + CM_SetTrmEdgeSidedness( edge, tw->edges[i].pl, tw->polygonEdgePlueckerCache[j], i ); + if ( INT32_SIGNBITSET( edgeNum ) ^ ( ( edge->side >> i ) & 1 ) ^ flip ) { + break; + } +#else + d = tw->edges[i].pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[j] ); + if ( flip ) { + d = -d; + } + if ( edgeNum > 0 ) { + if ( d <= 0.0f ) { + break; + } + } + else { + if ( d >= 0.0f ) { + break; + } + } +#endif + } + if ( j >= p->numEdges ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.normal = p->plane.Normal(); + tw->trace.c.dist = p->plane.Dist(); + tw->trace.c.contents = p->contents; + tw->trace.c.material = p->material; + tw->trace.c.point = tw->vertices[tw->edges[i].vertexNum[ !flip ]].p; + tw->trace.c.modelFeature = *reinterpret_cast(&p); + tw->trace.c.trmFeature = i; + return true; + } + } + + // test if any polygon edges go through the trm polygons + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = tw->model->edges + abs(edgeNum); + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + edge->checkcount = idCollisionModelManagerLocal::checkCount; + + for ( j = 0; j < tw->numPolys; j++ ) { +#if 1 + v1 = tw->model->vertices + edge->vertexNum[0]; + CM_SetTrmPolygonSidedness( v1, tw->polys[j].plane, j ); + v2 = tw->model->vertices + edge->vertexNum[1]; + CM_SetTrmPolygonSidedness( v2, tw->polys[j].plane, j ); + // if the polygon edge does not cross the trm polygon plane + if ( !(((v1->side ^ v2->side) >> j) & 1) ) { + continue; + } + flip = (v1->side >> j) & 1; +#else + float d1, d2; + + v1 = tw->model->vertices + edge->vertexNum[0]; + d1 = tw->polys[j].plane.Distance( v1->p ); + v2 = tw->model->vertices + edge->vertexNum[1]; + d2 = tw->polys[j].plane.Distance( v2->p ); + // if the polygon edge does not cross the trm polygon plane + if ( (d1 >= 0.0f && d2 >= 0.0f) || (d1 <= 0.0f && d2 <= 0.0f) ) { + continue; + } + flip = false; + if ( d1 < 0.0f ) { + flip = true; + } +#endif + // test if polygon edge goes through the trm polygon between the trm polygon edges + for ( k = 0; k < tw->polys[j].numEdges; k++ ) { + trmEdgeNum = tw->polys[j].edges[k]; + trmEdge = tw->edges + abs(trmEdgeNum); +#if 1 + bitNum = abs(trmEdgeNum); + CM_SetTrmEdgeSidedness( edge, trmEdge->pl, tw->polygonEdgePlueckerCache[i], bitNum ); + if ( INT32_SIGNBITSET( trmEdgeNum ) ^ ( ( edge->side >> bitNum ) & 1 ) ^ flip ) { + break; + } +#else + d = trmEdge->pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ); + if ( flip ) { + d = -d; + } + if ( trmEdgeNum > 0 ) { + if ( d <= 0.0f ) { + break; + } + } + else { + if ( d >= 0.0f ) { + break; + } + } +#endif + } + if ( k >= tw->polys[j].numEdges ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.normal = -tw->polys[j].plane.Normal(); + tw->trace.c.dist = -tw->polys[j].plane.Dist(); + tw->trace.c.contents = p->contents; + tw->trace.c.material = p->material; + tw->trace.c.point = tw->model->vertices[edge->vertexNum[ !flip ]].p; + tw->trace.c.modelFeature = edgeNum; + tw->trace.c.trmFeature = j; + return true; + } + } + } + return false; +} + +/* +================ +idCollisionModelManagerLocal::PointNode +================ +*/ +cm_node_t *idCollisionModelManagerLocal::PointNode( const idVec3 &p, cm_model_t *model ) { + cm_node_t *node; + + node = model->node; + while ( node->planeType != -1 ) { + if (p[node->planeType] > node->planeDist) { + node = node->children[0]; + } + else { + node = node->children[1]; + } + + assert( node != NULL ); + } + return node; +} + +/* +================ +idCollisionModelManagerLocal::PointContents +================ +*/ +int idCollisionModelManagerLocal::PointContents( const idVec3 p, cmHandle_t model ) { + int i; + float d; + cm_node_t *node; + cm_brushRef_t *bref; + cm_brush_t *b; + idPlane *plane; + + node = idCollisionModelManagerLocal::PointNode( p, idCollisionModelManagerLocal::models[model] ); + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + // test if the point is within the brush bounds + for ( i = 0; i < 3; i++ ) { + if ( p[i] < b->bounds[0][i] ) { + break; + } + if ( p[i] > b->bounds[1][i] ) { + break; + } + } + if ( i < 3 ) { + continue; + } + // test if the point is inside the brush + plane = b->planes; + for ( i = 0; i < b->numPlanes; i++, plane++ ) { + d = plane->Distance( p ); + if ( d >= 0.0f ) { + break; + } + } + if ( i >= b->numPlanes ) { + return b->contents; + } + } + return 0; +} + +/* +================== +idCollisionModelManagerLocal::TransformedPointContents +================== +*/ +int idCollisionModelManagerLocal::TransformedPointContents( const idVec3 &p, cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ) { + idVec3 p_l; + + // subtract origin offset + p_l = p - origin; + if ( modelAxis.IsRotated() ) { + p_l *= modelAxis; + } + return idCollisionModelManagerLocal::PointContents( p_l, model ); +} + + +/* +================== +idCollisionModelManagerLocal::ContentsTrm +================== +*/ +int idCollisionModelManagerLocal::ContentsTrm( trace_t *results, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + int i; + bool model_rotated, trm_rotated; + idMat3 invModelAxis, tmpAxis; + idVec3 dir; + ALIGN16( cm_traceWork_t tw ); + + // fast point case + if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f && + trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f && + trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + + results->c.contents = idCollisionModelManagerLocal::TransformedPointContents( start, model, modelOrigin, modelAxis ); + results->fraction = ( results->c.contents == 0 ); + results->endpos = start; + results->endAxis = trmAxis; + + return results->c.contents; + } + + idCollisionModelManagerLocal::checkCount++; + + tw.trace.fraction = 1.0f; + tw.trace.c.contents = 0; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.isConvex = true; + tw.rotation = false; + tw.positionTest = true; + tw.pointTrace = false; + tw.quickExit = false; + tw.numContacts = 0; + tw.model = idCollisionModelManagerLocal::models[model]; + tw.start = start - modelOrigin; + tw.end = tw.start; + + model_rotated = modelAxis.IsRotated(); + if ( model_rotated ) { + invModelAxis = modelAxis.Transpose(); + } + + // setup trm structure + idCollisionModelManagerLocal::SetupTrm( &tw, trm ); + + trm_rotated = trmAxis.IsRotated(); + + // calculate vertex positions + if ( trm_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around the start position + tw.vertices[i].p *= trmAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // set trm at start position + tw.vertices[i].p += tw.start; + } + if ( model_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around model instead of rotating the model + tw.vertices[i].p *= invModelAxis; + } + } + + // add offset to start point + if ( trm_rotated ) { + dir = trm->offset * trmAxis; + tw.start += dir; + tw.end += dir; + } else { + tw.start += trm->offset; + tw.end += trm->offset; + } + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + tw.end *= invModelAxis; + } + + + // setup trm vertices + tw.size.Clear(); + for ( i = 0; i < tw.numVerts; i++ ) { + // get axial trm size after rotations + tw.size.AddPoint( tw.vertices[i].p - tw.start ); + } + + // setup trm edges + for ( i = 1; i <= tw.numEdges; i++ ) { + // edge start, end and pluecker coordinate + tw.edges[i].start = tw.vertices[tw.edges[i].vertexNum[0]].p; + tw.edges[i].end = tw.vertices[tw.edges[i].vertexNum[1]].p; + tw.edges[i].pl.FromLine( tw.edges[i].start, tw.edges[i].end ); + } + + // setup trm polygons + if ( trm_rotated & model_rotated ) { + tmpAxis = trmAxis * invModelAxis; + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane *= tmpAxis; + } + } else if ( trm_rotated ) { + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane *= trmAxis; + } + } else if ( model_rotated ) { + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane *= invModelAxis; + } + } + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane.FitThroughPoint( tw.edges[abs(tw.polys[i].edges[0])].start ); + } + + // bounds for full trace, a little bit larger for epsilons + for ( i = 0; i < 3; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + CM_BOX_EPSILON; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + CM_BOX_EPSILON; + } + if ( idMath::Fabs(tw.size[0][i]) > idMath::Fabs(tw.size[1][i]) ) { + tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + CM_BOX_EPSILON; + } else { + tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + CM_BOX_EPSILON; + } + } + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + *results = tw.trace; + results->fraction = ( results->c.contents == 0 ); + results->endpos = start; + results->endAxis = trmAxis; + + return results->c.contents; +} + +/* +================== +idCollisionModelManagerLocal::Contents +================== +*/ +int idCollisionModelManagerLocal::Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + trace_t results; + + if ( model < 0 || model > idCollisionModelManagerLocal::maxModels || model > MAX_SUBMODELS ) { + common->Printf("idCollisionModelManagerLocal::Contents: invalid model handle\n"); + return 0; + } + if ( !idCollisionModelManagerLocal::models || !idCollisionModelManagerLocal::models[model] ) { + common->Printf("idCollisionModelManagerLocal::Contents: invalid model\n"); + return 0; + } + + return ContentsTrm( &results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} diff --git a/neo/cm/CollisionModel_debug.cpp b/neo/cm/CollisionModel_debug.cpp new file mode 100644 index 00000000..6d46de0e --- /dev/null +++ b/neo/cm/CollisionModel_debug.cpp @@ -0,0 +1,489 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + + +/* +=============================================================================== + +Visualisation code + +=============================================================================== +*/ + +const char *cm_contentsNameByIndex[] = { + "none", // 0 + "solid", // 1 + "opaque", // 2 + "water", // 3 + "playerclip", // 4 + "monsterclip", // 5 + "moveableclip", // 6 + "ikclip", // 7 + "blood", // 8 + "body", // 9 + "corpse", // 10 + "trigger", // 11 + "aas_solid", // 12 + "aas_obstacle", // 13 + "flashlight_trigger", // 14 + NULL +}; + +int cm_contentsFlagByIndex[] = { + -1, // 0 + CONTENTS_SOLID, // 1 + CONTENTS_OPAQUE, // 2 + CONTENTS_WATER, // 3 + CONTENTS_PLAYERCLIP, // 4 + CONTENTS_MONSTERCLIP, // 5 + CONTENTS_MOVEABLECLIP, // 6 + CONTENTS_IKCLIP, // 7 + CONTENTS_BLOOD, // 8 + CONTENTS_BODY, // 9 + CONTENTS_CORPSE, // 10 + CONTENTS_TRIGGER, // 11 + CONTENTS_AAS_SOLID, // 12 + CONTENTS_AAS_OBSTACLE, // 13 + CONTENTS_FLASHLIGHT_TRIGGER, // 14 + 0 +}; + +idCVar cm_drawMask( "cm_drawMask", "none", CVAR_GAME, "collision mask", cm_contentsNameByIndex, idCmdSystem::ArgCompletion_String ); +idCVar cm_drawColor( "cm_drawColor", "1 0 0 .5", CVAR_GAME, "color used to draw the collision models" ); +idCVar cm_drawFilled( "cm_drawFilled", "0", CVAR_GAME | CVAR_BOOL, "draw filled polygons" ); +idCVar cm_drawInternal( "cm_drawInternal", "1", CVAR_GAME | CVAR_BOOL, "draw internal edges green" ); +idCVar cm_drawNormals( "cm_drawNormals", "0", CVAR_GAME | CVAR_BOOL, "draw polygon and edge normals" ); +idCVar cm_backFaceCull( "cm_backFaceCull", "0", CVAR_GAME | CVAR_BOOL, "cull back facing polygons" ); +idCVar cm_debugCollision( "cm_debugCollision", "0", CVAR_GAME | CVAR_BOOL, "debug the collision detection" ); + +static idVec4 cm_color; + +/* +================ +idCollisionModelManagerLocal::ContentsFromString +================ +*/ +int idCollisionModelManagerLocal::ContentsFromString( const char *string ) const { + int i, contents = 0; + idLexer src( string, idStr::Length( string ), "ContentsFromString" ); + idToken token; + + while( src.ReadToken( &token ) ) { + if ( token == "," ) { + continue; + } + for ( i = 1; cm_contentsNameByIndex[i] != NULL; i++ ) { + if ( token.Icmp( cm_contentsNameByIndex[i] ) == 0 ) { + contents |= cm_contentsFlagByIndex[i]; + break; + } + } + } + + return contents; +} + +/* +================ +idCollisionModelManagerLocal::StringFromContents +================ +*/ +const char *idCollisionModelManagerLocal::StringFromContents( const int contents ) const { + int i, length = 0; + static char contentsString[MAX_STRING_CHARS]; + + contentsString[0] = '\0'; + + for ( i = 1; cm_contentsFlagByIndex[i] != 0; i++ ) { + if ( contents & cm_contentsFlagByIndex[i] ) { + if ( length != 0 ) { + length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, "," ); + } + length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, cm_contentsNameByIndex[i] ); + } + } + + return contentsString; +} + +/* +================ +idCollisionModelManagerLocal::DrawEdge +================ +*/ +void idCollisionModelManagerLocal::DrawEdge( cm_model_t *model, int edgeNum, const idVec3 &origin, const idMat3 &axis ) { + int side; + cm_edge_t *edge; + idVec3 start, end, mid; + bool isRotated; + + isRotated = axis.IsRotated(); + + edge = model->edges + abs(edgeNum); + side = edgeNum < 0; + + start = model->vertices[edge->vertexNum[side]].p; + end = model->vertices[edge->vertexNum[!side]].p; + if ( isRotated ) { + start *= axis; + end *= axis; + } + start += origin; + end += origin; + + if ( edge->internal ) { + if ( cm_drawInternal.GetBool() ) { + common->RW()->DebugArrow( colorGreen, start, end, 1 ); + } + } else { + if ( edge->numUsers > 2 ) { + common->RW()->DebugArrow( colorBlue, start, end, 1 ); + } else { + common->RW()->DebugArrow( cm_color, start, end, 1 ); + } + } + + if ( cm_drawNormals.GetBool() ) { + mid = (start + end) * 0.5f; + if ( isRotated ) { + end = mid + 5 * (axis * edge->normal); + } else { + end = mid + 5 * edge->normal; + } + common->RW()->DebugArrow( colorCyan, mid, end, 1 ); + } +} + +/* +================ +idCollisionModelManagerLocal::DrawPolygon +================ +*/ +void idCollisionModelManagerLocal::DrawPolygon( cm_model_t *model, cm_polygon_t *p, const idVec3 &origin, const idMat3 &axis, const idVec3 &viewOrigin ) { + int i, edgeNum; + cm_edge_t *edge; + idVec3 center, end, dir; + + if ( cm_backFaceCull.GetBool() ) { + edgeNum = p->edges[0]; + edge = model->edges + abs(edgeNum); + dir = model->vertices[edge->vertexNum[0]].p - viewOrigin; + if ( dir * p->plane.Normal() > 0.0f ) { + return; + } + } + + if ( cm_drawNormals.GetBool() ) { + center = vec3_origin; + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + center += model->vertices[edge->vertexNum[edgeNum < 0]].p; + } + center *= (1.0f / p->numEdges); + if ( axis.IsRotated() ) { + center = center * axis + origin; + end = center + 5 * (axis * p->plane.Normal()); + } else { + center += origin; + end = center + 5 * p->plane.Normal(); + } + common->RW()->DebugArrow( colorMagenta, center, end, 1 ); + } + + if ( cm_drawFilled.GetBool() ) { + idFixedWinding winding; + for ( i = p->numEdges - 1; i >= 0; i-- ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + winding += origin + model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p * axis; + } + common->RW()->DebugPolygon( cm_color, winding ); + } else { + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + if ( edge->checkcount == checkCount ) { + continue; + } + edge->checkcount = checkCount; + DrawEdge( model, edgeNum, origin, axis ); + } + } +} + +/* +================ +idCollisionModelManagerLocal::DrawNodePolygons +================ +*/ +void idCollisionModelManagerLocal::DrawNodePolygons( cm_model_t *model, cm_node_t *node, + const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin, const float radius ) { + int i; + cm_polygon_t *p; + cm_polygonRef_t *pref; + + while (1) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + if ( radius ) { + // polygon bounds should overlap with trace bounds + for ( i = 0; i < 3; i++ ) { + if ( p->bounds[0][i] > viewOrigin[i] + radius ) { + break; + } + if ( p->bounds[1][i] < viewOrigin[i] - radius ) { + break; + } + } + if ( i < 3 ) { + continue; + } + } + if ( p->checkcount == checkCount ) { + continue; + } + if ( !( p->contents & cm_contentsFlagByIndex[cm_drawMask.GetInteger()] ) ) { + continue; + } + + DrawPolygon( model, p, origin, axis, viewOrigin ); + p->checkcount = checkCount; + } + if ( node->planeType == -1 ) { + break; + } + if ( radius && viewOrigin[node->planeType] > node->planeDist + radius ) { + node = node->children[0]; + } else if ( radius && viewOrigin[node->planeType] < node->planeDist - radius ) { + node = node->children[1]; + } else { + DrawNodePolygons( model, node->children[1], origin, axis, viewOrigin, radius ); + node = node->children[0]; + } + } +} + +/* +================ +idCollisionModelManagerLocal::DrawModel +================ +*/ +void idCollisionModelManagerLocal::DrawModel( cmHandle_t handle, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const float radius ) { + + cm_model_t *model; + idVec3 viewPos; + + if ( handle < 0 && handle >= numModels ) { + return; + } + + if ( cm_drawColor.IsModified() ) { + sscanf( cm_drawColor.GetString(), "%f %f %f %f", &cm_color.x, &cm_color.y, &cm_color.z, &cm_color.w ); + cm_drawColor.ClearModified(); + } + + model = models[ handle ]; + viewPos = (viewOrigin - modelOrigin) * modelAxis.Transpose(); + checkCount++; + DrawNodePolygons( model, model->node, modelOrigin, modelAxis, viewPos, radius ); +} + +/* +=============================================================================== + +Speed test code + +=============================================================================== +*/ + +static idCVar cm_testCollision( "cm_testCollision", "0", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testRotation( "cm_testRotation", "1", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testModel( "cm_testModel", "0", CVAR_GAME | CVAR_INTEGER, "" ); +static idCVar cm_testTimes( "cm_testTimes", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +static idCVar cm_testRandomMany( "cm_testRandomMany", "0", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testOrigin( "cm_testOrigin", "0 0 0", CVAR_GAME, "" ); +static idCVar cm_testReset( "cm_testReset", "0", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testBox( "cm_testBox", "-16 -16 0 16 16 64", CVAR_GAME, "" ); +static idCVar cm_testBoxRotation( "cm_testBoxRotation", "0 0 0", CVAR_GAME, "" ); +static idCVar cm_testWalk( "cm_testWalk", "1", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testLength( "cm_testLength", "1024", CVAR_GAME | CVAR_FLOAT, "" ); +static idCVar cm_testRadius( "cm_testRadius", "64", CVAR_GAME | CVAR_FLOAT, "" ); +static idCVar cm_testAngle( "cm_testAngle", "60", CVAR_GAME | CVAR_FLOAT, "" ); + +static int total_translation; +static int min_translation = 999999; +static int max_translation = -999999; +static int num_translation = 0; +static int total_rotation; +static int min_rotation = 999999; +static int max_rotation = -999999; +static int num_rotation = 0; +static idVec3 start; +static idVec3 *testend; + +#include "../sys/sys_public.h" + +void idCollisionModelManagerLocal::DebugOutput( const idVec3 &origin ) { + int i, k, t; + char buf[128]; + idVec3 end; + idAngles boxAngles; + idMat3 modelAxis, boxAxis; + idBounds bounds; + trace_t trace; + + if ( !cm_testCollision.GetBool() ) { + return; + } + + testend = (idVec3 *) Mem_Alloc( cm_testTimes.GetInteger() * sizeof(idVec3), TAG_COLLISION ); + + if ( cm_testReset.GetBool() || ( cm_testWalk.GetBool() && !start.Compare( start ) ) ) { + total_translation = total_rotation = 0; + min_translation = min_rotation = 999999; + max_translation = max_rotation = -999999; + num_translation = num_rotation = 0; + cm_testReset.SetBool( false ); + } + + if ( cm_testWalk.GetBool() ) { + start = origin; + cm_testOrigin.SetString( va( "%1.2f %1.2f %1.2f", start[0], start[1], start[2] ) ); + } else { + sscanf( cm_testOrigin.GetString(), "%f %f %f", &start[0], &start[1], &start[2] ); + } + + sscanf( cm_testBox.GetString(), "%f %f %f %f %f %f", &bounds[0][0], &bounds[0][1], &bounds[0][2], + &bounds[1][0], &bounds[1][1], &bounds[1][2] ); + sscanf( cm_testBoxRotation.GetString(), "%f %f %f", &boxAngles[0], &boxAngles[1], &boxAngles[2] ); + boxAxis = boxAngles.ToMat3(); + modelAxis.Identity(); + + idTraceModel itm( bounds ); + idRandom random( 0 ); + idTimer timer; + + if ( cm_testRandomMany.GetBool() ) { + // if many traces in one random direction + for ( i = 0; i < 3; i++ ) { + testend[0][i] = start[i] + random.CRandomFloat() * cm_testLength.GetFloat(); + } + for ( k = 1; k < cm_testTimes.GetInteger(); k++ ) { + testend[k] = testend[0]; + } + } else { + // many traces each in a different random direction + for ( k = 0; k < cm_testTimes.GetInteger(); k++ ) { + for ( i = 0; i < 3; i++ ) { + testend[k][i] = start[i] + random.CRandomFloat() * cm_testLength.GetFloat(); + } + } + } + + // translational collision detection + timer.Clear(); + timer.Start(); + for ( i = 0; i < cm_testTimes.GetInteger(); i++ ) { + Translation( &trace, start, testend[i], &itm, boxAxis, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, cm_testModel.GetInteger(), vec3_origin, modelAxis ); + } + timer.Stop(); + t = timer.Milliseconds(); + if ( t < min_translation ) min_translation = t; + if ( t > max_translation ) max_translation = t; + num_translation++; + total_translation += t; + if ( cm_testTimes.GetInteger() > 9999 ) { + sprintf( buf, "%3dK", (int ) ( cm_testTimes.GetInteger() / 1000 ) ); + } else { + sprintf( buf, "%4d", cm_testTimes.GetInteger() ); + } + common->Printf("%s translations: %4d milliseconds, (min = %d, max = %d, av = %1.1f)\n", buf, t, min_translation, max_translation, (float) total_translation / num_translation ); + + if ( cm_testRandomMany.GetBool() ) { + // if many traces in one random direction + for ( i = 0; i < 3; i++ ) { + testend[0][i] = start[i] + random.CRandomFloat() * cm_testRadius.GetFloat(); + } + for ( k = 1; k < cm_testTimes.GetInteger(); k++ ) { + testend[k] = testend[0]; + } + } else { + // many traces each in a different random direction + for ( k = 0; k < cm_testTimes.GetInteger(); k++ ) { + for ( i = 0; i < 3; i++ ) { + testend[k][i] = start[i] + random.CRandomFloat() * cm_testRadius.GetFloat(); + } + } + } + + if ( cm_testRotation.GetBool() ) { + // rotational collision detection + idVec3 vec( random.CRandomFloat(), random.CRandomFloat(), random.RandomFloat() ); + vec.Normalize(); + idRotation rotation( vec3_origin, vec, cm_testAngle.GetFloat() ); + + timer.Clear(); + timer.Start(); + for ( i = 0; i < cm_testTimes.GetInteger(); i++ ) { + rotation.SetOrigin( testend[i] ); + Rotation( &trace, start, rotation, &itm, boxAxis, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, cm_testModel.GetInteger(), vec3_origin, modelAxis ); + } + timer.Stop(); + t = timer.Milliseconds(); + if ( t < min_rotation ) min_rotation = t; + if ( t > max_rotation ) max_rotation = t; + num_rotation++; + total_rotation += t; + if ( cm_testTimes.GetInteger() > 9999 ) { + sprintf( buf, "%3dK", (int ) ( cm_testTimes.GetInteger() / 1000 ) ); + } else { + sprintf( buf, "%4d", cm_testTimes.GetInteger() ); + } + common->Printf("%s rotation: %4d milliseconds, (min = %d, max = %d, av = %1.1f)\n", buf, t, min_rotation, max_rotation, (float) total_rotation / num_rotation ); + } + + Mem_Free( testend ); + testend = NULL; +} diff --git a/neo/cm/CollisionModel_files.cpp b/neo/cm/CollisionModel_files.cpp new file mode 100644 index 00000000..d98ae3a4 --- /dev/null +++ b/neo/cm/CollisionModel_files.cpp @@ -0,0 +1,671 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +#define CM_FILE_EXT "cm" +#define CM_BINARYFILE_EXT "bcm" +#define CM_FILEID "CM" +#define CM_FILEVERSION "1.00" + + +/* +=============================================================================== + +Writing of collision model file + +=============================================================================== +*/ + +void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ); +int CM_GetNodeContents( cm_node_t *node ); + + +/* +================ +idCollisionModelManagerLocal::WriteNodes +================ +*/ +void idCollisionModelManagerLocal::WriteNodes( idFile *fp, cm_node_t *node ) { + fp->WriteFloatString( "\t( %d %f )\n", node->planeType, node->planeDist ); + if ( node->planeType != -1 ) { + WriteNodes( fp, node->children[0] ); + WriteNodes( fp, node->children[1] ); + } +} + +/* +================ +idCollisionModelManagerLocal::CountPolygonMemory +================ +*/ +int idCollisionModelManagerLocal::CountPolygonMemory( cm_node_t *node ) const { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int memory; + + memory = 0; + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + + memory += sizeof( cm_polygon_t ) + ( p->numEdges - 1 ) * sizeof( p->edges[0] ); + } + if ( node->planeType != -1 ) { + memory += CountPolygonMemory( node->children[0] ); + memory += CountPolygonMemory( node->children[1] ); + } + return memory; +} + +/* +================ +idCollisionModelManagerLocal::WritePolygons +================ +*/ +void idCollisionModelManagerLocal::WritePolygons( idFile *fp, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int i; + + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + fp->WriteFloatString( "\t%d (", p->numEdges ); + for ( i = 0; i < p->numEdges; i++ ) { + fp->WriteFloatString( " %d", p->edges[i] ); + } + fp->WriteFloatString( " ) ( %f %f %f ) %f", p->plane.Normal()[0], p->plane.Normal()[1], p->plane.Normal()[2], p->plane.Dist() ); + fp->WriteFloatString( " ( %f %f %f )", p->bounds[0][0], p->bounds[0][1], p->bounds[0][2] ); + fp->WriteFloatString( " ( %f %f %f )", p->bounds[1][0], p->bounds[1][1], p->bounds[1][2] ); + fp->WriteFloatString( " \"%s\"\n", p->material->GetName() ); + } + if ( node->planeType != -1 ) { + WritePolygons( fp, node->children[0] ); + WritePolygons( fp, node->children[1] ); + } +} + +/* +================ +idCollisionModelManagerLocal::CountBrushMemory +================ +*/ +int idCollisionModelManagerLocal::CountBrushMemory( cm_node_t *node ) const { + cm_brushRef_t *bref; + cm_brush_t *b; + int memory; + + memory = 0; + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + if ( b->checkcount == checkCount ) { + continue; + } + b->checkcount = checkCount; + + memory += sizeof( cm_brush_t ) + ( b->numPlanes - 1 ) * sizeof( b->planes[0] ); + } + if ( node->planeType != -1 ) { + memory += CountBrushMemory( node->children[0] ); + memory += CountBrushMemory( node->children[1] ); + } + return memory; +} + +/* +================ +idCollisionModelManagerLocal::WriteBrushes +================ +*/ +void idCollisionModelManagerLocal::WriteBrushes( idFile *fp, cm_node_t *node ) { + cm_brushRef_t *bref; + cm_brush_t *b; + int i; + + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + if ( b->checkcount == checkCount ) { + continue; + } + b->checkcount = checkCount; + fp->WriteFloatString( "\t%d {\n", b->numPlanes ); + for ( i = 0; i < b->numPlanes; i++ ) { + fp->WriteFloatString( "\t\t( %f %f %f ) %f\n", b->planes[i].Normal()[0], b->planes[i].Normal()[1], b->planes[i].Normal()[2], b->planes[i].Dist() ); + } + fp->WriteFloatString( "\t} ( %f %f %f )", b->bounds[0][0], b->bounds[0][1], b->bounds[0][2] ); + fp->WriteFloatString( " ( %f %f %f ) \"%s\"\n", b->bounds[1][0], b->bounds[1][1], b->bounds[1][2], StringFromContents( b->contents ) ); + } + if ( node->planeType != -1 ) { + WriteBrushes( fp, node->children[0] ); + WriteBrushes( fp, node->children[1] ); + } +} + +/* +================ +idCollisionModelManagerLocal::WriteCollisionModel +================ +*/ +void idCollisionModelManagerLocal::WriteCollisionModel( idFile *fp, cm_model_t *model ) { + int i, polygonMemory, brushMemory; + + fp->WriteFloatString( "collisionModel \"%s\" {\n", model->name.c_str() ); + // vertices + fp->WriteFloatString( "\tvertices { /* numVertices = */ %d\n", model->numVertices ); + for ( i = 0; i < model->numVertices; i++ ) { + fp->WriteFloatString( "\t/* %d */ ( %f %f %f )\n", i, model->vertices[i].p[0], model->vertices[i].p[1], model->vertices[i].p[2] ); + } + fp->WriteFloatString( "\t}\n" ); + // edges + fp->WriteFloatString( "\tedges { /* numEdges = */ %d\n", model->numEdges ); + for ( i = 0; i < model->numEdges; i++ ) { + fp->WriteFloatString( "\t/* %d */ ( %d %d ) %d %d\n", i, model->edges[i].vertexNum[0], model->edges[i].vertexNum[1], model->edges[i].internal, model->edges[i].numUsers ); + } + fp->WriteFloatString( "\t}\n" ); + // nodes + fp->WriteFloatString( "\tnodes {\n" ); + WriteNodes( fp, model->node ); + fp->WriteFloatString( "\t}\n" ); + // polygons + checkCount++; + polygonMemory = CountPolygonMemory( model->node ); + fp->WriteFloatString( "\tpolygons /* polygonMemory = */ %d {\n", polygonMemory ); + checkCount++; + WritePolygons( fp, model->node ); + fp->WriteFloatString( "\t}\n" ); + // brushes + checkCount++; + brushMemory = CountBrushMemory( model->node ); + fp->WriteFloatString( "\tbrushes /* brushMemory = */ %d {\n", brushMemory ); + checkCount++; + WriteBrushes( fp, model->node ); + fp->WriteFloatString( "\t}\n" ); + // closing brace + fp->WriteFloatString( "}\n" ); +} + +/* +================ +idCollisionModelManagerLocal::WriteCollisionModelsToFile +================ +*/ +void idCollisionModelManagerLocal::WriteCollisionModelsToFile( const char *filename, int firstModel, int lastModel, unsigned int mapFileCRC ) { + int i; + idFile *fp; + idStr name; + + name = filename; + name.SetFileExtension( CM_FILE_EXT ); + + common->Printf( "writing %s\n", name.c_str() ); + fp = fileSystem->OpenFileWrite( name, "fs_basepath" ); + if ( !fp ) { + common->Warning( "idCollisionModelManagerLocal::WriteCollisionModelsToFile: Error opening file %s\n", name.c_str() ); + return; + } + + // write file id and version + fp->WriteFloatString( "%s \"%s\"\n\n", CM_FILEID, CM_FILEVERSION ); + // write the map file crc + fp->WriteFloatString( "%u\n\n", mapFileCRC ); + + // write the collision models + for ( i = firstModel; i < lastModel; i++ ) { + WriteCollisionModel( fp, models[ i ] ); + } + + fileSystem->CloseFile( fp ); +} + +/* +================ +idCollisionModelManagerLocal::WriteCollisionModelForMapEntity +================ +*/ +bool idCollisionModelManagerLocal::WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel ) { + idFile *fp; + idStr name; + cm_model_t *model; + + SetupHash(); + model = CollisionModelForMapEntity( mapEnt ); + model->name = filename; + + name = filename; + name.SetFileExtension( CM_FILE_EXT ); + + common->Printf( "writing %s\n", name.c_str() ); + fp = fileSystem->OpenFileWrite( name, "fs_basepath" ); + if ( !fp ) { + common->Printf( "idCollisionModelManagerLocal::WriteCollisionModelForMapEntity: Error opening file %s\n", name.c_str() ); + FreeModel( model ); + return false; + } + + // write file id and version + fp->WriteFloatString( "%s \"%s\"\n\n", CM_FILEID, CM_FILEVERSION ); + // write the map file crc + fp->WriteFloatString( "%u\n\n", 0 ); + + // write the collision model + WriteCollisionModel( fp, model ); + + fileSystem->CloseFile( fp ); + + if ( testTraceModel ) { + idTraceModel trm; + TrmFromModel( model, trm ); + } + + FreeModel( model ); + + return true; +} + + +/* +=============================================================================== + +Loading of collision model file + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::ParseVertices +================ +*/ +void idCollisionModelManagerLocal::ParseVertices( idLexer *src, cm_model_t *model ) { + int i; + + src->ExpectTokenString( "{" ); + model->numVertices = src->ParseInt(); + model->maxVertices = model->numVertices; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof( cm_vertex_t ), TAG_COLLISION ); + for ( i = 0; i < model->numVertices; i++ ) { + src->Parse1DMatrix( 3, model->vertices[i].p.ToFloatPtr() ); + model->vertices[i].side = 0; + model->vertices[i].sideSet = 0; + model->vertices[i].checkcount = 0; + } + src->ExpectTokenString( "}" ); +} + +/* +================ +idCollisionModelManagerLocal::ParseEdges +================ +*/ +void idCollisionModelManagerLocal::ParseEdges( idLexer *src, cm_model_t *model ) { + int i; + + src->ExpectTokenString( "{" ); + model->numEdges = src->ParseInt(); + model->maxEdges = model->numEdges; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof( cm_edge_t ), TAG_COLLISION ); + for ( i = 0; i < model->numEdges; i++ ) { + src->ExpectTokenString( "(" ); + model->edges[i].vertexNum[0] = src->ParseInt(); + model->edges[i].vertexNum[1] = src->ParseInt(); + src->ExpectTokenString( ")" ); + model->edges[i].side = 0; + model->edges[i].sideSet = 0; + model->edges[i].internal = src->ParseInt(); + model->edges[i].numUsers = src->ParseInt(); + model->edges[i].normal = vec3_origin; + model->edges[i].checkcount = 0; + model->numInternalEdges += model->edges[i].internal; + } + src->ExpectTokenString( "}" ); +} + +/* +================ +idCollisionModelManagerLocal::ParseNodes +================ +*/ +cm_node_t *idCollisionModelManagerLocal::ParseNodes( idLexer *src, cm_model_t *model, cm_node_t *parent ) { + cm_node_t *node; + + model->numNodes++; + node = AllocNode( model, model->numNodes < NODE_BLOCK_SIZE_SMALL ? NODE_BLOCK_SIZE_SMALL : NODE_BLOCK_SIZE_LARGE ); + node->brushes = NULL; + node->polygons = NULL; + node->parent = parent; + src->ExpectTokenString( "(" ); + node->planeType = src->ParseInt(); + node->planeDist = src->ParseFloat(); + src->ExpectTokenString( ")" ); + if ( node->planeType != -1 ) { + node->children[0] = ParseNodes( src, model, node ); + node->children[1] = ParseNodes( src, model, node ); + } + return node; +} + +/* +================ +idCollisionModelManagerLocal::ParsePolygons +================ +*/ +void idCollisionModelManagerLocal::ParsePolygons( idLexer *src, cm_model_t *model ) { + cm_polygon_t *p; + int i, numEdges; + idVec3 normal; + idToken token; + + if ( src->CheckTokenType( TT_NUMBER, 0, &token ) ) { + model->polygonBlock = (cm_polygonBlock_t *) Mem_ClearedAlloc( sizeof( cm_polygonBlock_t ) + token.GetIntValue(), TAG_COLLISION ); + model->polygonBlock->bytesRemaining = token.GetIntValue(); + model->polygonBlock->next = ( (byte *) model->polygonBlock ) + sizeof( cm_polygonBlock_t ); + } + + src->ExpectTokenString( "{" ); + while ( !src->CheckTokenString( "}" ) ) { + // parse polygon + numEdges = src->ParseInt(); + p = AllocPolygon( model, numEdges ); + p->numEdges = numEdges; + src->ExpectTokenString( "(" ); + for ( i = 0; i < p->numEdges; i++ ) { + p->edges[i] = src->ParseInt(); + } + src->ExpectTokenString( ")" ); + src->Parse1DMatrix( 3, normal.ToFloatPtr() ); + p->plane.SetNormal( normal ); + p->plane.SetDist( src->ParseFloat() ); + src->Parse1DMatrix( 3, p->bounds[0].ToFloatPtr() ); + src->Parse1DMatrix( 3, p->bounds[1].ToFloatPtr() ); + src->ExpectTokenType( TT_STRING, 0, &token ); + // get material + p->material = declManager->FindMaterial( token ); + p->contents = p->material->GetContentFlags(); + p->checkcount = 0; + // filter polygon into tree + R_FilterPolygonIntoTree( model, model->node, NULL, p ); + } +} + +/* +================ +idCollisionModelManagerLocal::ParseBrushes +================ +*/ +void idCollisionModelManagerLocal::ParseBrushes( idLexer *src, cm_model_t *model ) { + cm_brush_t *b; + int i, numPlanes; + idVec3 normal; + idToken token; + + if ( src->CheckTokenType( TT_NUMBER, 0, &token ) ) { + model->brushBlock = (cm_brushBlock_t *) Mem_ClearedAlloc( sizeof( cm_brushBlock_t ) + token.GetIntValue(), TAG_COLLISION ); + model->brushBlock->bytesRemaining = token.GetIntValue(); + model->brushBlock->next = ( (byte *) model->brushBlock ) + sizeof( cm_brushBlock_t ); + } + + src->ExpectTokenString( "{" ); + while ( !src->CheckTokenString( "}" ) ) { + // parse brush + numPlanes = src->ParseInt(); + b = AllocBrush( model, numPlanes ); + b->numPlanes = numPlanes; + src->ExpectTokenString( "{" ); + for ( i = 0; i < b->numPlanes; i++ ) { + src->Parse1DMatrix( 3, normal.ToFloatPtr() ); + b->planes[i].SetNormal( normal ); + b->planes[i].SetDist( src->ParseFloat() ); + } + src->ExpectTokenString( "}" ); + src->Parse1DMatrix( 3, b->bounds[0].ToFloatPtr() ); + src->Parse1DMatrix( 3, b->bounds[1].ToFloatPtr() ); + src->ReadToken( &token ); + if ( token.type == TT_NUMBER ) { + b->contents = token.GetIntValue(); // old .cm files use a single integer + } else { + b->contents = ContentsFromString( token ); + } + b->checkcount = 0; + b->primitiveNum = 0; + b->material = NULL; + // filter brush into tree + R_FilterBrushIntoTree( model, model->node, NULL, b ); + } +} + +/* +================ +idCollisionModelManagerLocal::ParseCollisionModel +================ +*/ +cm_model_t * idCollisionModelManagerLocal::ParseCollisionModel( idLexer *src ) { + + cm_model_t *model; + idToken token; + + if ( numModels >= MAX_SUBMODELS ) { + common->Error( "LoadModel: no free slots" ); + return NULL; + } + model = AllocModel(); + models[numModels ] = model; + numModels++; + // parse the file + src->ExpectTokenType( TT_STRING, 0, &token ); + model->name = token; + src->ExpectTokenString( "{" ); + while ( !src->CheckTokenString( "}" ) ) { + + src->ReadToken( &token ); + + if ( token == "vertices" ) { + ParseVertices( src, model ); + continue; + } + + if ( token == "edges" ) { + ParseEdges( src, model ); + continue; + } + + if ( token == "nodes" ) { + src->ExpectTokenString( "{" ); + model->node = ParseNodes( src, model, NULL ); + src->ExpectTokenString( "}" ); + continue; + } + + if ( token == "polygons" ) { + ParsePolygons( src, model ); + continue; + } + + if ( token == "brushes" ) { + ParseBrushes( src, model ); + continue; + } + + src->Error( "ParseCollisionModel: bad token \"%s\"", token.c_str() ); + } + // calculate edge normals + checkCount++; + CalculateEdgeNormals( model, model->node ); + // get model bounds from brush and polygon bounds + CM_GetNodeBounds( &model->bounds, model->node ); + // get model contents + model->contents = CM_GetNodeContents( model->node ); + // total memory used by this model + model->usedMemory = model->numVertices * sizeof(cm_vertex_t) + + model->numEdges * sizeof(cm_edge_t) + + model->polygonMemory + + model->brushMemory + + model->numNodes * sizeof(cm_node_t) + + model->numPolygonRefs * sizeof(cm_polygonRef_t) + + model->numBrushRefs * sizeof(cm_brushRef_t); + + return model; +} + +/* +================ +idCollisionModelManagerLocal::LoadCollisionModelFile +================ +*/ +bool idCollisionModelManagerLocal::LoadCollisionModelFile( const char *name, unsigned int mapFileCRC ) { + idToken token; + idLexer *src; + unsigned int crc; + + // load it + idStrStatic< MAX_OSPATH > fileName = name; + + // check for generated file + idStrStatic< MAX_OSPATH > generatedFileName = fileName; + generatedFileName.Insert( "generated/", 0 ); + generatedFileName.SetFileExtension( CM_BINARYFILE_EXT ); + + // if we are reloading the same map, check the timestamp + // and try to skip all the work + ID_TIME_T currentTimeStamp = fileSystem->GetTimestamp( fileName ); + + // see if we have a generated version of this + bool loaded = false; + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + if ( file != NULL ) { + int numEntries = 0; + file->ReadBig( numEntries ); + file->ReadString( mapName ); + file->ReadBig( crc ); + idStrStatic< 32 > fileID; + idStrStatic< 32 > fileVersion; + file->ReadString( fileID ); + file->ReadString( fileVersion ); + if ( fileID == CM_FILEID && fileVersion == CM_FILEVERSION && crc == mapFileCRC && numEntries > 0 ) { + for ( int i = 0; i < numEntries; i++ ) { + cm_model_t *model = LoadBinaryModelFromFile( file, currentTimeStamp ); + models[ numModels ] = model; + numModels++; + } + loaded = true; + } + } + + if ( !loaded ) { + + fileName.SetFileExtension( CM_FILE_EXT ); + src = new (TAG_COLLISION) idLexer( fileName ); + src->SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if ( !src->IsLoaded() ) { + delete src; + return false; + } + + int numEntries = 0; + idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); + if ( outputFile != NULL ) { + outputFile->WriteBig( numEntries ); + outputFile->WriteString( mapName ); + outputFile->WriteBig( mapFileCRC ); + outputFile->WriteString( CM_FILEID ); + outputFile->WriteString( CM_FILEVERSION ); + } + + if ( !src->ExpectTokenString( CM_FILEID ) ) { + common->Warning( "%s is not an CM file.", fileName.c_str() ); + delete src; + return false; + } + + if ( !src->ReadToken( &token ) || token != CM_FILEVERSION ) { + common->Warning( "%s has version %s instead of %s", fileName.c_str(), token.c_str(), CM_FILEVERSION ); + delete src; + return false; + } + + if ( !src->ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) { + common->Warning( "%s has no map file CRC", fileName.c_str() ); + delete src; + return false; + } + + crc = token.GetUnsignedLongValue(); + if ( mapFileCRC && crc != mapFileCRC ) { + common->Printf( "%s is out of date\n", fileName.c_str() ); + delete src; + return false; + } + + // parse the file + while ( 1 ) { + if ( !src->ReadToken( &token ) ) { + break; + } + + if ( token == "collisionModel" ) { + cm_model_t *model = ParseCollisionModel( src ); + if ( model == NULL ) { + delete src; + return false; + } + if ( outputFile != NULL ) { + WriteBinaryModelToFile( model, outputFile, currentTimeStamp ); + numEntries++; + } + continue; + } + + src->Error( "idCollisionModelManagerLocal::LoadCollisionModelFile: bad token \"%s\"", token.c_str() ); + } + delete src; + if ( outputFile != NULL ) { + outputFile->Seek( 0, FS_SEEK_SET ); + outputFile->WriteBig( numEntries ); + } + } + + return true; +} diff --git a/neo/cm/CollisionModel_load.cpp b/neo/cm/CollisionModel_load.cpp new file mode 100644 index 00000000..b96d23d4 --- /dev/null +++ b/neo/cm/CollisionModel_load.cpp @@ -0,0 +1,4068 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + + It is more important to minimize the number of collision polygons + than it is to minimize the number of edges used for collision + detection (total edges - internal edges). + + Stitching the world tends to minimize the number of edges used + for collision detection (more internal edges). However stitching + also results in more collision polygons which usually makes a + stitched world slower. + + In an average map over 30% of all edges is internal. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +#define CMODEL_BINARYFILE_EXT "bcmodel" + +idCollisionModelManagerLocal collisionModelManagerLocal; +idCollisionModelManager * collisionModelManager = &collisionModelManagerLocal; + +cm_windingList_t * cm_windingList; +cm_windingList_t * cm_outList; +cm_windingList_t * cm_tmpList; + +idHashIndex * cm_vertexHash; +idHashIndex * cm_edgeHash; + +idBounds cm_modelBounds; +int cm_vertexShift; + + +idCVar preLoad_Collision( "preLoad_Collision", "1", CVAR_SYSTEM | CVAR_BOOL, "preload collision beginlevelload" ); + +/* +=============================================================================== + +Proc BSP tree for data pruning + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::ParseProcNodes +================ +*/ +void idCollisionModelManagerLocal::ParseProcNodes( idLexer *src ) { + int i; + + src->ExpectTokenString( "{" ); + + numProcNodes = src->ParseInt(); + if ( numProcNodes < 0 ) { + src->Error( "ParseProcNodes: bad numProcNodes" ); + } + procNodes = (cm_procNode_t *)Mem_ClearedAlloc( numProcNodes * sizeof( cm_procNode_t ), TAG_COLLISION ); + + for ( i = 0; i < numProcNodes; i++ ) { + cm_procNode_t *node; + + node = &procNodes[i]; + + src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); + node->children[0] = src->ParseInt(); + node->children[1] = src->ParseInt(); + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idCollisionModelManagerLocal::LoadProcBSP + + FIXME: if the nodes would be at the start of the .proc file it would speed things up considerably +================ +*/ +void idCollisionModelManagerLocal::LoadProcBSP( const char *name ) { + idStr filename; + idToken token; + idLexer *src; + + // load it + filename = name; + filename.SetFileExtension( PROC_FILE_EXT ); + src = new (TAG_COLLISION) idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if ( !src->IsLoaded() ) { + common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: couldn't load %s", filename.c_str() ); + delete src; + return; + } + + if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) { + common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID ); + delete src; + return; + } + + // parse the file + while ( 1 ) { + if ( !src->ReadToken( &token ) ) { + break; + } + + if ( token == "model" ) { + src->SkipBracedSection(); + continue; + } + + if ( token == "shadowModel" ) { + src->SkipBracedSection(); + continue; + } + + if ( token == "interAreaPortals" ) { + src->SkipBracedSection(); + continue; + } + + if ( token == "nodes" ) { + ParseProcNodes( src ); + break; + } + + src->Error( "idCollisionModelManagerLocal::LoadProcBSP: bad token \"%s\"", token.c_str() ); + } + + delete src; +} + +/* +=============================================================================== + +Free map + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::Clear +================ +*/ +void idCollisionModelManagerLocal::Clear() { + mapName.Clear(); + mapFileTime = 0; + loaded = 0; + checkCount = 0; + maxModels = 0; + numModels = 0; + models = NULL; + memset( trmPolygons, 0, sizeof( trmPolygons ) ); + trmBrushes[0] = NULL; + trmMaterial = NULL; + numProcNodes = 0; + procNodes = NULL; + getContacts = false; + contacts = NULL; + maxContacts = 0; + numContacts = 0; +} + +/* +================ +idCollisionModelManagerLocal::RemovePolygonReferences_r +================ +*/ +void idCollisionModelManagerLocal::RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p ) { + cm_polygonRef_t *pref; + + while( node ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( pref->p == p ) { + pref->p = NULL; + // cannot return here because we can have links down the tree due to polygon merging + //return; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( p->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( p->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + RemovePolygonReferences_r( node->children[1], p ); + node = node->children[0]; + } + } +} + +/* +================ +idCollisionModelManagerLocal::RemoveBrushReferences_r +================ +*/ +void idCollisionModelManagerLocal::RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b ) { + cm_brushRef_t *bref; + + while( node ) { + for ( bref = node->brushes; bref; bref = bref->next ) { + if ( bref->b == b ) { + bref->b = NULL; + return; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( b->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( b->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + RemoveBrushReferences_r( node->children[1], b ); + node = node->children[0]; + } + } +} + +/* +================ +idCollisionModelManagerLocal::FreeNode +================ +*/ +void idCollisionModelManagerLocal::FreeNode( cm_node_t *node ) { + // don't free the node here + // the nodes are allocated in blocks which are freed when the model is freed +} + +/* +================ +idCollisionModelManagerLocal::FreePolygonReference +================ +*/ +void idCollisionModelManagerLocal::FreePolygonReference( cm_polygonRef_t *pref ) { + // don't free the polygon reference here + // the polygon references are allocated in blocks which are freed when the model is freed +} + +/* +================ +idCollisionModelManagerLocal::FreeBrushReference +================ +*/ +void idCollisionModelManagerLocal::FreeBrushReference( cm_brushRef_t *bref ) { + // don't free the brush reference here + // the brush references are allocated in blocks which are freed when the model is freed +} + +/* +================ +idCollisionModelManagerLocal::FreePolygon +================ +*/ +void idCollisionModelManagerLocal::FreePolygon( cm_model_t *model, cm_polygon_t *poly ) { + model->numPolygons--; + model->polygonMemory -= sizeof( cm_polygon_t ) + ( poly->numEdges - 1 ) * sizeof( poly->edges[0] ); + if ( model->polygonBlock == NULL ) { + Mem_Free( poly ); + } +} + +/* +================ +idCollisionModelManagerLocal::FreeBrush +================ +*/ +void idCollisionModelManagerLocal::FreeBrush( cm_model_t *model, cm_brush_t *brush ) { + model->numBrushes--; + model->brushMemory -= sizeof( cm_brush_t ) + ( brush->numPlanes - 1 ) * sizeof( brush->planes[0] ); + if ( model->brushBlock == NULL ) { + Mem_Free( brush ); + } +} + +/* +================ +idCollisionModelManagerLocal::FreeTree_r +================ +*/ +void idCollisionModelManagerLocal::FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + cm_brushRef_t *bref; + cm_brush_t *b; + + // free all polygons at this node + for ( pref = node->polygons; pref; pref = node->polygons ) { + p = pref->p; + if ( p ) { + // remove all other references to this polygon + RemovePolygonReferences_r( headNode, p ); + FreePolygon( model, p ); + } + node->polygons = pref->next; + FreePolygonReference( pref ); + } + // free all brushes at this node + for ( bref = node->brushes; bref; bref = node->brushes ) { + b = bref->b; + if ( b ) { + // remove all other references to this brush + RemoveBrushReferences_r( headNode, b ); + FreeBrush( model, b ); + } + node->brushes = bref->next; + FreeBrushReference( bref ); + } + // recurse down the tree + if ( node->planeType != -1 ) { + FreeTree_r( model, headNode, node->children[0] ); + node->children[0] = NULL; + FreeTree_r( model, headNode, node->children[1] ); + node->children[1] = NULL; + } + FreeNode( node ); +} + +/* +================ +idCollisionModelManagerLocal::FreeModel +================ +*/ +void idCollisionModelManagerLocal::FreeModel( cm_model_t *model ) { + cm_polygonRefBlock_t *polygonRefBlock, *nextPolygonRefBlock; + cm_brushRefBlock_t *brushRefBlock, *nextBrushRefBlock; + cm_nodeBlock_t *nodeBlock, *nextNodeBlock; + + // free the tree structure + if ( model->node ) { + FreeTree_r( model, model->node, model->node ); + } + // free blocks with polygon references + for ( polygonRefBlock = model->polygonRefBlocks; polygonRefBlock; polygonRefBlock = nextPolygonRefBlock ) { + nextPolygonRefBlock = polygonRefBlock->next; + Mem_Free( polygonRefBlock ); + } + // free blocks with brush references + for ( brushRefBlock = model->brushRefBlocks; brushRefBlock; brushRefBlock = nextBrushRefBlock ) { + nextBrushRefBlock = brushRefBlock->next; + Mem_Free( brushRefBlock ); + } + // free blocks with nodes + for ( nodeBlock = model->nodeBlocks; nodeBlock; nodeBlock = nextNodeBlock ) { + nextNodeBlock = nodeBlock->next; + Mem_Free( nodeBlock ); + } + // free block allocated polygons + Mem_Free( model->polygonBlock ); + // free block allocated brushes + Mem_Free( model->brushBlock ); + // free edges + Mem_Free( model->edges ); + // free vertices + Mem_Free( model->vertices ); + // free the model + delete model; +} + +/* +================ +idCollisionModelManagerLocal::FreeMap +================ +*/ +void idCollisionModelManagerLocal::FreeMap() { + int i; + + if ( !loaded ) { + Clear(); + return; + } + + for ( i = 0; i < maxModels; i++ ) { + if ( !models[i] ) { + continue; + } + FreeModel( models[i] ); + } + + FreeTrmModelStructure(); + + Mem_Free( models ); + + Clear(); + + ShutdownHash(); +} + +/* +================ +idCollisionModelManagerLocal::FreeTrmModelStructure +================ +*/ +void idCollisionModelManagerLocal::FreeTrmModelStructure() { + int i; + + assert( models ); + if ( !models[MAX_SUBMODELS] ) { + return; + } + + for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) { + FreePolygon( models[MAX_SUBMODELS], trmPolygons[i]->p ); + } + FreeBrush( models[MAX_SUBMODELS], trmBrushes[0]->b ); + + models[MAX_SUBMODELS]->node->polygons = NULL; + models[MAX_SUBMODELS]->node->brushes = NULL; + FreeModel( models[MAX_SUBMODELS] ); +} + + +/* +=============================================================================== + +Edge normals + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::CalculateEdgeNormals +================ +*/ +#define SHARP_EDGE_DOT -0.7f + +void idCollisionModelManagerLocal::CalculateEdgeNormals( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + cm_edge_t *edge; + float dot, s; + int i, edgeNum; + idVec3 dir; + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs( edgeNum ); + if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) { + // if the edge is only used by this polygon + if ( edge->numUsers == 1 ) { + dir = model->vertices[ edge->vertexNum[edgeNum < 0]].p - model->vertices[ edge->vertexNum[edgeNum > 0]].p; + edge->normal = p->plane.Normal().Cross( dir ); + edge->normal.Normalize(); + } else { + // the edge is used by more than one polygon + edge->normal = p->plane.Normal(); + } + } else { + dot = edge->normal * p->plane.Normal(); + // if the two planes make a very sharp edge + if ( dot < SHARP_EDGE_DOT ) { + // max length normal pointing outside both polygons + dir = model->vertices[ edge->vertexNum[edgeNum > 0]].p - model->vertices[ edge->vertexNum[edgeNum < 0]].p; + edge->normal = edge->normal.Cross( dir ) + p->plane.Normal().Cross( -dir ); + edge->normal *= ( 0.5f / ( 0.5f + 0.5f * SHARP_EDGE_DOT ) ) / edge->normal.Length(); + model->numSharpEdges++; + } else { + s = 0.5f / ( 0.5f + 0.5f * dot ); + edge->normal = s * ( edge->normal + p->plane.Normal() ); + } + } + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + CalculateEdgeNormals( model, node->children[1] ); + node = node->children[0]; + } +} + +/* +=============================================================================== + +Trace model to general collision model + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::AllocModel +================ +*/ +cm_model_t *idCollisionModelManagerLocal::AllocModel() { + cm_model_t *model; + + model = new (TAG_COLLISION) cm_model_t; + model->contents = 0; + model->isConvex = false; + model->maxVertices = 0; + model->numVertices = 0; + model->vertices = NULL; + model->maxEdges = 0; + model->numEdges = 0; + model->edges= NULL; + model->node = NULL; + model->nodeBlocks = NULL; + model->polygonRefBlocks = NULL; + model->brushRefBlocks = NULL; + model->polygonBlock = NULL; + model->brushBlock = NULL; + model->numPolygons = model->polygonMemory = + model->numBrushes = model->brushMemory = + model->numNodes = model->numBrushRefs = + model->numPolygonRefs = model->numInternalEdges = + model->numSharpEdges = model->numRemovedPolys = + model->numMergedPolys = model->usedMemory = 0; + + return model; +} + +/* +================ +idCollisionModelManagerLocal::AllocNode +================ +*/ +cm_node_t *idCollisionModelManagerLocal::AllocNode( cm_model_t *model, int blockSize ) { + int i; + cm_node_t *node; + cm_nodeBlock_t *nodeBlock; + + if ( !model->nodeBlocks || !model->nodeBlocks->nextNode ) { + nodeBlock = (cm_nodeBlock_t *) Mem_ClearedAlloc( sizeof( cm_nodeBlock_t ) + blockSize * sizeof(cm_node_t), TAG_COLLISION ); + nodeBlock->nextNode = (cm_node_t *) ( ( (byte *) nodeBlock ) + sizeof( cm_nodeBlock_t ) ); + nodeBlock->next = model->nodeBlocks; + model->nodeBlocks = nodeBlock; + node = nodeBlock->nextNode; + for ( i = 0; i < blockSize - 1; i++ ) { + node->parent = node + 1; + node = node->parent; + } + node->parent = NULL; + } + + node = model->nodeBlocks->nextNode; + model->nodeBlocks->nextNode = node->parent; + node->parent = NULL; + + return node; +} + +/* +================ +idCollisionModelManagerLocal::AllocPolygonReference +================ +*/ +cm_polygonRef_t *idCollisionModelManagerLocal::AllocPolygonReference( cm_model_t *model, int blockSize ) { + int i; + cm_polygonRef_t *pref; + cm_polygonRefBlock_t *prefBlock; + + if ( !model->polygonRefBlocks || !model->polygonRefBlocks->nextRef ) { + prefBlock = (cm_polygonRefBlock_t *) Mem_ClearedAlloc( sizeof( cm_polygonRefBlock_t ) + blockSize * sizeof(cm_polygonRef_t), TAG_COLLISION ); + prefBlock->nextRef = (cm_polygonRef_t *) ( ( (byte *) prefBlock ) + sizeof( cm_polygonRefBlock_t ) ); + prefBlock->next = model->polygonRefBlocks; + model->polygonRefBlocks = prefBlock; + pref = prefBlock->nextRef; + for ( i = 0; i < blockSize - 1; i++ ) { + pref->next = pref + 1; + pref = pref->next; + } + pref->next = NULL; + } + + pref = model->polygonRefBlocks->nextRef; + model->polygonRefBlocks->nextRef = pref->next; + + return pref; +} + +/* +================ +idCollisionModelManagerLocal::AllocBrushReference +================ +*/ +cm_brushRef_t *idCollisionModelManagerLocal::AllocBrushReference( cm_model_t *model, int blockSize ) { + int i; + cm_brushRef_t *bref; + cm_brushRefBlock_t *brefBlock; + + if ( !model->brushRefBlocks || !model->brushRefBlocks->nextRef ) { + brefBlock = (cm_brushRefBlock_t *) Mem_ClearedAlloc( sizeof(cm_brushRefBlock_t) + blockSize * sizeof(cm_brushRef_t), TAG_COLLISION ); + brefBlock->nextRef = (cm_brushRef_t *) ( ( (byte *) brefBlock ) + sizeof(cm_brushRefBlock_t) ); + brefBlock->next = model->brushRefBlocks; + model->brushRefBlocks = brefBlock; + bref = brefBlock->nextRef; + for ( i = 0; i < blockSize - 1; i++ ) { + bref->next = bref + 1; + bref = bref->next; + } + bref->next = NULL; + } + + bref = model->brushRefBlocks->nextRef; + model->brushRefBlocks->nextRef = bref->next; + + return bref; +} + +/* +================ +idCollisionModelManagerLocal::AllocPolygon +================ +*/ +cm_polygon_t *idCollisionModelManagerLocal::AllocPolygon( cm_model_t *model, int numEdges ) { + cm_polygon_t *poly; + int size; + + size = sizeof( cm_polygon_t ) + ( numEdges - 1 ) * sizeof( poly->edges[0] ); + model->numPolygons++; + model->polygonMemory += size; + if ( model->polygonBlock && model->polygonBlock->bytesRemaining >= size ) { + poly = (cm_polygon_t *) model->polygonBlock->next; + model->polygonBlock->next += size; + model->polygonBlock->bytesRemaining -= size; + } else { + poly = (cm_polygon_t *) Mem_ClearedAlloc( size, TAG_COLLISION ); + } + return poly; +} + +/* +================ +idCollisionModelManagerLocal::AllocBrush +================ +*/ +cm_brush_t *idCollisionModelManagerLocal::AllocBrush( cm_model_t *model, int numPlanes ) { + cm_brush_t *brush; + int size; + + size = sizeof( cm_brush_t ) + ( numPlanes - 1 ) * sizeof( brush->planes[0] ); + model->numBrushes++; + model->brushMemory += size; + if ( model->brushBlock && model->brushBlock->bytesRemaining >= size ) { + brush = (cm_brush_t *) model->brushBlock->next; + model->brushBlock->next += size; + model->brushBlock->bytesRemaining -= size; + } else { + brush = (cm_brush_t *) Mem_ClearedAlloc( size, TAG_COLLISION ); + } + return brush; +} + +/* +================ +idCollisionModelManagerLocal::AddPolygonToNode +================ +*/ +void idCollisionModelManagerLocal::AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p ) { + cm_polygonRef_t *pref; + + pref = AllocPolygonReference( model, model->numPolygonRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE ); + pref->p = p; + pref->next = node->polygons; + node->polygons = pref; + model->numPolygonRefs++; +} + +/* +================ +idCollisionModelManagerLocal::AddBrushToNode +================ +*/ +void idCollisionModelManagerLocal::AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b ) { + cm_brushRef_t *bref; + + bref = AllocBrushReference( model, model->numBrushRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE ); + bref->b = b; + bref->next = node->brushes; + node->brushes = bref; + model->numBrushRefs++; +} + +/* +================ +idCollisionModelManagerLocal::SetupTrmModelStructure +================ +*/ +void idCollisionModelManagerLocal::SetupTrmModelStructure() { + int i; + cm_node_t *node; + cm_model_t *model; + + // setup model + model = AllocModel(); + + assert( models ); + models[MAX_SUBMODELS] = model; + // create node to hold the collision data + node = (cm_node_t *) AllocNode( model, 1 ); + node->planeType = -1; + model->node = node; + // allocate vertex and edge arrays + model->numVertices = 0; + model->maxVertices = MAX_TRACEMODEL_VERTS; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t), TAG_COLLISION ); + model->numEdges = 0; + model->maxEdges = MAX_TRACEMODEL_EDGES+1; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t), TAG_COLLISION ); + // create a material for the trace model polygons + trmMaterial = declManager->FindMaterial( "_tracemodel", false ); + if ( !trmMaterial ) { + common->FatalError( "_tracemodel material not found" ); + } + + // allocate polygons + for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) { + trmPolygons[i] = AllocPolygonReference( model, MAX_TRACEMODEL_POLYS ); + trmPolygons[i]->p = AllocPolygon( model, MAX_TRACEMODEL_POLYEDGES ); + trmPolygons[i]->p->bounds.Clear(); + trmPolygons[i]->p->plane.Zero(); + trmPolygons[i]->p->checkcount = 0; + trmPolygons[i]->p->contents = -1; // all contents + trmPolygons[i]->p->material = trmMaterial; + trmPolygons[i]->p->numEdges = 0; + } + // allocate brush for position test + trmBrushes[0] = AllocBrushReference( model, 1 ); + trmBrushes[0]->b = AllocBrush( model, MAX_TRACEMODEL_POLYS ); + trmBrushes[0]->b->primitiveNum = 0; + trmBrushes[0]->b->bounds.Clear(); + trmBrushes[0]->b->checkcount = 0; + trmBrushes[0]->b->contents = -1; // all contents + trmBrushes[ 0 ]->b->material = trmMaterial; + trmBrushes[0]->b->numPlanes = 0; +} + +/* +================ +idCollisionModelManagerLocal::SetupTrmModel + +Trace models (item boxes, etc) are converted to collision models on the fly, using the last model slot +as a reusable temporary buffer +================ +*/ +cmHandle_t idCollisionModelManagerLocal::SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) { + int i, j; + cm_vertex_t *vertex; + cm_edge_t *edge; + cm_polygon_t *poly; + cm_model_t *model; + const traceModelVert_t *trmVert; + const traceModelEdge_t *trmEdge; + const traceModelPoly_t *trmPoly; + + assert( models ); + + if ( material == NULL ) { + material = trmMaterial; + } + + model = models[MAX_SUBMODELS]; + model->node->brushes = NULL; + model->node->polygons = NULL; + // if not a valid trace model + if ( trm.type == TRM_INVALID || !trm.numPolys ) { + return TRACE_MODEL_HANDLE; + } + // vertices + model->numVertices = trm.numVerts; + vertex = model->vertices; + trmVert = trm.verts; + for ( i = 0; i < trm.numVerts; i++, vertex++, trmVert++ ) { + vertex->p = *trmVert; + vertex->sideSet = 0; + } + // edges + model->numEdges = trm.numEdges; + edge = model->edges + 1; + trmEdge = trm.edges + 1; + for ( i = 0; i < trm.numEdges; i++, edge++, trmEdge++ ) { + edge->vertexNum[0] = trmEdge->v[0]; + edge->vertexNum[1] = trmEdge->v[1]; + edge->normal = trmEdge->normal; + edge->internal = false; + edge->sideSet = 0; + } + // polygons + model->numPolygons = trm.numPolys; + trmPoly = trm.polys; + for ( i = 0; i < trm.numPolys; i++, trmPoly++ ) { + poly = trmPolygons[i]->p; + poly->numEdges = trmPoly->numEdges; + for ( j = 0; j < trmPoly->numEdges; j++ ) { + poly->edges[j] = trmPoly->edges[j]; + } + poly->plane.SetNormal( trmPoly->normal ); + poly->plane.SetDist( trmPoly->dist ); + poly->bounds = trmPoly->bounds; + poly->material = material; + // link polygon at node + trmPolygons[i]->next = model->node->polygons; + model->node->polygons = trmPolygons[i]; + } + // if the trace model is convex + if ( trm.isConvex ) { + // setup brush for position test + trmBrushes[0]->b->numPlanes = trm.numPolys; + for ( i = 0; i < trm.numPolys; i++ ) { + trmBrushes[0]->b->planes[i] = trmPolygons[i]->p->plane; + } + trmBrushes[0]->b->bounds = trm.bounds; + // link brush at node + trmBrushes[0]->next = model->node->brushes; + trmBrushes[ 0 ]->b->material = material; + model->node->brushes = trmBrushes[0]; + } + // model bounds + model->bounds = trm.bounds; + // convex + model->isConvex = trm.isConvex; + + return TRACE_MODEL_HANDLE; +} + +/* +=============================================================================== + +Optimisation, removal of polygons contained within brushes or solid + +=============================================================================== +*/ + +/* +============ +idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP +============ +*/ +int idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) { + int res; + idFixedWinding back; + cm_procNode_t *node; + float dist; + + do { + node = procNodes + nodeNum; + dist = node->plane.Normal() * origin + node->plane[3]; + if ( dist > radius ) { + res = SIDE_FRONT; + } + else if ( dist < -radius ) { + res = SIDE_BACK; + } + else { + res = w->Split( &back, node->plane, CHOP_EPSILON ); + } + if ( res == SIDE_FRONT ) { + nodeNum = node->children[0]; + } + else if ( res == SIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == SIDE_ON ) { + // continue with the side the winding faces + if ( node->plane.Normal() * normal > 0.0f ) { + nodeNum = node->children[0]; + } + else { + nodeNum = node->children[1]; + } + } + else { + // if either node is not solid + if ( node->children[0] < 0 || node->children[1] < 0 ) { + return false; + } + // only recurse if the node is not solid + if ( node->children[1] > 0 ) { + if ( !R_ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) { + return false; + } + } + nodeNum = node->children[0]; + } + } while ( nodeNum > 0 ); + if ( nodeNum < 0 ) { + return false; + } + return true; +} + +/* +============ +idCollisionModelManagerLocal::ChoppedAwayByProcBSP +============ +*/ +int idCollisionModelManagerLocal::ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents ) { + idFixedWinding neww; + idBounds bounds; + float radius; + idVec3 origin; + + // if the .proc file has no BSP tree + if ( procNodes == NULL ) { + return false; + } + // don't chop if the polygon is not solid + if ( !(contents & CONTENTS_SOLID) ) { + return false; + } + // make a local copy of the winding + neww = w; + neww.GetBounds( bounds ); + origin = (bounds[1] - bounds[0]) * 0.5f; + radius = origin.Length() + CHOP_EPSILON; + origin = bounds[0] + origin; + // + return R_ChoppedAwayByProcBSP( 0, &neww, plane.Normal(), origin, radius ); +} + +/* +============= +idCollisionModelManagerLocal::ChopWindingWithBrush + + returns the least number of winding fragments outside the brush +============= +*/ +void idCollisionModelManagerLocal::ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b ) { + int i, k, res, startPlane, planeNum, bestNumWindings; + idFixedWinding back, front; + idPlane plane; + bool chopped; + int sidedness[MAX_POINTS_ON_WINDING]; + float dist; + + if ( b->numPlanes > MAX_POINTS_ON_WINDING ) { + return; + } + + // get sidedness for the list of windings + for ( i = 0; i < b->numPlanes; i++ ) { + plane = -b->planes[i]; + + dist = plane.Distance( list->origin ); + if ( dist > list->radius ) { + sidedness[i] = SIDE_FRONT; + } + else if ( dist < -list->radius ) { + sidedness[i] = SIDE_BACK; + } + else { + sidedness[i] = list->bounds.PlaneSide( plane ); + if ( sidedness[i] == PLANESIDE_FRONT ) { + sidedness[i] = SIDE_FRONT; + } + else if ( sidedness[i] == PLANESIDE_BACK ) { + sidedness[i] = SIDE_BACK; + } + else { + sidedness[i] = SIDE_CROSS; + } + } + } + + cm_outList->numWindings = 0; + for ( k = 0; k < list->numWindings; k++ ) { + // + startPlane = 0; + bestNumWindings = 1 + b->numPlanes; + chopped = false; + do { + front = list->w[k]; + cm_tmpList->numWindings = 0; + for ( planeNum = startPlane, i = 0; i < b->numPlanes; i++, planeNum++ ) { + + if ( planeNum >= b->numPlanes ) { + planeNum = 0; + } + + res = sidedness[planeNum]; + + if ( res == SIDE_CROSS ) { + plane = -b->planes[planeNum]; + res = front.Split( &back, plane, CHOP_EPSILON ); + } + + // NOTE: disabling this can create gaps at places where Z-fighting occurs + // Z-fighting should not occur but what if there is a decal brush side + // with exactly the same size as another brush side ? + // only leave windings on a brush if the winding plane and brush side plane face the same direction + if ( res == SIDE_ON && list->primitiveNum >= 0 && (list->normal * b->planes[planeNum].Normal()) > 0 ) { + // return because all windings in the list will be on this brush side plane + return; + } + + if ( res == SIDE_BACK ) { + if ( cm_outList->numWindings >= MAX_WINDING_LIST ) { + common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST ); + return; + } + // winding and brush didn't intersect, store the original winding + cm_outList->w[cm_outList->numWindings] = list->w[k]; + cm_outList->numWindings++; + chopped = false; + break; + } + + if ( res == SIDE_CROSS ) { + if ( cm_tmpList->numWindings >= MAX_WINDING_LIST ) { + common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST ); + return; + } + // store the front winding in the temporary list + cm_tmpList->w[cm_tmpList->numWindings] = back; + cm_tmpList->numWindings++; + chopped = true; + } + + // if already found a start plane which generates less fragments + if ( cm_tmpList->numWindings >= bestNumWindings ) { + break; + } + } + + // find the best start plane to get the least number of fragments outside the brush + if ( cm_tmpList->numWindings < bestNumWindings ) { + bestNumWindings = cm_tmpList->numWindings; + // store windings from temporary list in the out list + for ( i = 0; i < cm_tmpList->numWindings; i++ ) { + if ( cm_outList->numWindings + i >= MAX_WINDING_LIST ) { + common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST ); + return; + } + cm_outList->w[cm_outList->numWindings+i] = cm_tmpList->w[i]; + } + // if only one winding left then we can't do any better + if ( bestNumWindings == 1 ) { + break; + } + } + + // try the next start plane + startPlane++; + + } while ( chopped && startPlane < b->numPlanes ); + // + if ( chopped ) { + cm_outList->numWindings += bestNumWindings; + } + } + for ( k = 0; k < cm_outList->numWindings; k++ ) { + list->w[k] = cm_outList->w[k]; + } + list->numWindings = cm_outList->numWindings; +} + +/* +============ +idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes +============ +*/ +void idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node ) { + int i; + cm_brushRef_t *bref; + cm_brush_t *b; + + while( 1 ) { + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + // if we checked this brush already + if ( b->checkcount == checkCount ) { + continue; + } + b->checkcount = checkCount; + // if the windings in the list originate from this brush + if ( b->primitiveNum == list->primitiveNum ) { + continue; + } + // if brush has a different contents + if ( b->contents != list->contents ) { + continue; + } + // brush bounds and winding list bounds should overlap + for ( i = 0; i < 3; i++ ) { + if ( list->bounds[0][i] > b->bounds[1][i] ) { + break; + } + if ( list->bounds[1][i] < b->bounds[0][i] ) { + break; + } + } + if ( i < 3 ) { + continue; + } + // chop windings in the list with brush + ChopWindingListWithBrush( list, b ); + // if all windings are chopped away we're done + if ( !list->numWindings ) { + return; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( list->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( list->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + R_ChopWindingListWithTreeBrushes( list, node->children[1] ); + if ( !list->numWindings ) { + return; + } + node = node->children[0]; + } + } +} + +/* +============ +idCollisionModelManagerLocal::WindingOutsideBrushes + + Returns one winding which is not fully contained in brushes. + We always favor less polygons over a stitched world. + If the winding is partly contained and the contained pieces can be chopped off + without creating multiple winding fragments then the chopped winding is returned. +============ +*/ +idFixedWinding *idCollisionModelManagerLocal::WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int primitiveNum, cm_node_t *headNode ) { + int i, windingLeft; + + cm_windingList->bounds.Clear(); + for ( i = 0; i < w->GetNumPoints(); i++ ) { + cm_windingList->bounds.AddPoint( (*w)[i].ToVec3() ); + } + + cm_windingList->origin = (cm_windingList->bounds[1] - cm_windingList->bounds[0]) * 0.5; + cm_windingList->radius = cm_windingList->origin.Length() + CHOP_EPSILON; + cm_windingList->origin = cm_windingList->bounds[0] + cm_windingList->origin; + cm_windingList->bounds[0] -= idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON ); + cm_windingList->bounds[1] += idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON ); + + cm_windingList->w[0] = *w; + cm_windingList->numWindings = 1; + cm_windingList->normal = plane.Normal(); + cm_windingList->contents = contents; + cm_windingList->primitiveNum = primitiveNum; + // + checkCount++; + R_ChopWindingListWithTreeBrushes( cm_windingList, headNode ); + // + if ( !cm_windingList->numWindings ) { + return NULL; + } + if ( cm_windingList->numWindings == 1 ) { + return &cm_windingList->w[0]; + } + // if not the world model + if ( numModels != 0 ) { + return w; + } + // check if winding fragments would be chopped away by the proc BSP tree + windingLeft = -1; + for ( i = 0; i < cm_windingList->numWindings; i++ ) { + if ( !ChoppedAwayByProcBSP( cm_windingList->w[i], plane, contents ) ) { + if ( windingLeft >= 0 ) { + return w; + } + windingLeft = i; + } + } + if ( windingLeft >= 0 ) { + return &cm_windingList->w[windingLeft]; + } + return NULL; +} + +/* +=============================================================================== + +Merging polygons + +=============================================================================== +*/ + +/* +============= +idCollisionModelManagerLocal::ReplacePolygons + + does not allow for a node to have multiple references to the same polygon +============= +*/ +void idCollisionModelManagerLocal::ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp ) { + cm_polygonRef_t *pref, *lastpref, *nextpref; + cm_polygon_t *p; + bool linked; + + while( 1 ) { + linked = false; + lastpref = NULL; + for ( pref = node->polygons; pref; pref = nextpref ) { + nextpref = pref->next; + // + p = pref->p; + // if this polygon reference should change + if ( p == p1 || p == p2 ) { + // if the new polygon is already linked at this node + if ( linked ) { + if ( lastpref ) { + lastpref->next = nextpref; + } + else { + node->polygons = nextpref; + } + FreePolygonReference( pref ); + model->numPolygonRefs--; + } + else { + pref->p = newp; + linked = true; + lastpref = pref; + } + } + else { + lastpref = pref; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( p1->bounds[0][node->planeType] > node->planeDist && p2->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( p1->bounds[1][node->planeType] < node->planeDist && p2->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + ReplacePolygons( model, node->children[1], p1, p2, newp ); + node = node->children[0]; + } + } +} + +/* +============= +idCollisionModelManagerLocal::TryMergePolygons +============= +*/ +#define CONTINUOUS_EPSILON 0.005f +#define NORMAL_EPSILON 0.01f + +cm_polygon_t *idCollisionModelManagerLocal::TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) { + int i, j, nexti, prevj; + int p1BeforeShare, p1AfterShare, p2BeforeShare, p2AfterShare; + int newEdges[CM_MAX_POLYGON_EDGES], newNumEdges; + int edgeNum, edgeNum1, edgeNum2, newEdgeNum1, newEdgeNum2; + cm_edge_t *edge; + cm_polygon_t *newp; + idVec3 delta, normal; + float dot; + bool keep1, keep2; + + if ( p1->material != p2->material ) { + return NULL; + } + if ( idMath::Fabs( p1->plane.Dist() - p2->plane.Dist() ) > NORMAL_EPSILON ) { + return NULL; + } + for ( i = 0; i < 3; i++ ) { + if ( idMath::Fabs( p1->plane.Normal()[i] - p2->plane.Normal()[i] ) > NORMAL_EPSILON ) { + return NULL; + } + if ( p1->bounds[0][i] > p2->bounds[1][i] ) { + return NULL; + } + if ( p1->bounds[1][i] < p2->bounds[0][i] ) { + return NULL; + } + } + // this allows for merging polygons with multiple shared edges + // polygons with multiple shared edges probably never occur tho ;) + p1BeforeShare = p1AfterShare = p2BeforeShare = p2AfterShare = -1; + for ( i = 0; i < p1->numEdges; i++ ) { + nexti = (i+1)%p1->numEdges; + for ( j = 0; j < p2->numEdges; j++ ) { + prevj = (j+p2->numEdges-1)%p2->numEdges; + // + if ( abs(p1->edges[i]) != abs(p2->edges[j]) ) { + // if the next edge of p1 and the previous edge of p2 are the same + if ( abs(p1->edges[nexti]) == abs(p2->edges[prevj]) ) { + // if both polygons don't use the edge in the same direction + if ( p1->edges[nexti] != p2->edges[prevj] ) { + p1BeforeShare = i; + p2AfterShare = j; + } + break; + } + } + // if both polygons don't use the edge in the same direction + else if ( p1->edges[i] != p2->edges[j] ) { + // if the next edge of p1 and the previous edge of p2 are not the same + if ( abs(p1->edges[nexti]) != abs(p2->edges[prevj]) ) { + p1AfterShare = nexti; + p2BeforeShare = prevj; + break; + } + } + } + } + if ( p1BeforeShare < 0 || p1AfterShare < 0 || p2BeforeShare < 0 || p2AfterShare < 0 ) { + return NULL; + } + + // check if the new polygon would still be convex + edgeNum = p1->edges[p1BeforeShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + normal = p1->plane.Normal().Cross( delta ); + normal.Normalize(); + + edgeNum = p2->edges[p2AfterShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + + dot = delta * normal; + if (dot < -CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (bool)(dot > CONTINUOUS_EPSILON); + + edgeNum = p2->edges[p2BeforeShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + normal = p1->plane.Normal().Cross( delta ); + normal.Normalize(); + + edgeNum = p1->edges[p1AfterShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + + dot = delta * normal; + if (dot < -CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (bool)(dot > CONTINUOUS_EPSILON); + + newEdgeNum1 = newEdgeNum2 = 0; + // get new edges if we need to replace colinear ones + if ( !keep1 ) { + edgeNum1 = p1->edges[p1BeforeShare]; + edgeNum2 = p2->edges[p2AfterShare]; + GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INT32_SIGNBITSET(edgeNum1)]].p, + model->vertices[model->edges[abs(edgeNum2)].vertexNum[INT32_SIGNBITNOTSET(edgeNum2)]].p, + &newEdgeNum1, -1 ); + if ( newEdgeNum1 == 0 ) { + keep1 = true; + } + } + if ( !keep2 ) { + edgeNum1 = p2->edges[p2BeforeShare]; + edgeNum2 = p1->edges[p1AfterShare]; + GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INT32_SIGNBITSET(edgeNum1)]].p, + model->vertices[model->edges[abs(edgeNum2)].vertexNum[INT32_SIGNBITNOTSET(edgeNum2)]].p, + &newEdgeNum2, -1 ); + if ( newEdgeNum2 == 0 ) { + keep2 = true; + } + } + // set edges for new polygon + newNumEdges = 0; + if ( !keep2 ) { + newEdges[newNumEdges++] = newEdgeNum2; + } + if ( p1AfterShare < p1BeforeShare ) { + for ( i = p1AfterShare + (!keep2); i <= p1BeforeShare - (!keep1); i++ ) { + newEdges[newNumEdges++] = p1->edges[i]; + } + } + else { + for ( i = p1AfterShare + (!keep2); i < p1->numEdges; i++ ) { + newEdges[newNumEdges++] = p1->edges[i]; + } + for ( i = 0; i <= p1BeforeShare - (!keep1); i++ ) { + newEdges[newNumEdges++] = p1->edges[i]; + } + } + if ( !keep1 ) { + newEdges[newNumEdges++] = newEdgeNum1; + } + if ( p2AfterShare < p2BeforeShare ) { + for ( i = p2AfterShare + (!keep1); i <= p2BeforeShare - (!keep2); i++ ) { + newEdges[newNumEdges++] = p2->edges[i]; + } + } + else { + for ( i = p2AfterShare + (!keep1); i < p2->numEdges; i++ ) { + newEdges[newNumEdges++] = p2->edges[i]; + } + for ( i = 0; i <= p2BeforeShare - (!keep2); i++ ) { + newEdges[newNumEdges++] = p2->edges[i]; + } + } + + newp = AllocPolygon( model, newNumEdges ); + memcpy( newp, p1, sizeof(cm_polygon_t) ); + memcpy( newp->edges, newEdges, newNumEdges * sizeof(int) ); + newp->numEdges = newNumEdges; + newp->checkcount = 0; + // increase usage count for the edges of this polygon + for ( i = 0; i < newp->numEdges; i++ ) { + if ( !keep1 && newp->edges[i] == newEdgeNum1 ) { + continue; + } + if ( !keep2 && newp->edges[i] == newEdgeNum2 ) { + continue; + } + model->edges[abs(newp->edges[i])].numUsers++; + } + // create new bounds from the merged polygons + newp->bounds = p1->bounds + p2->bounds; + + return newp; +} + +/* +============= +idCollisionModelManagerLocal::MergePolygonWithTreePolygons +============= +*/ +bool idCollisionModelManagerLocal::MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) { + int i; + cm_polygonRef_t *pref; + cm_polygon_t *p, *newp; + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // + if ( p == polygon ) { + continue; + } + // + newp = TryMergePolygons( model, polygon, p ); + // if polygons were merged + if ( newp ) { + model->numMergedPolys++; + // replace links to the merged polygons with links to the new polygon + ReplacePolygons( model, model->node, polygon, p, newp ); + // decrease usage count for edges of both merged polygons + for ( i = 0; i < polygon->numEdges; i++ ) { + model->edges[abs(polygon->edges[i])].numUsers--; + } + for ( i = 0; i < p->numEdges; i++ ) { + model->edges[abs(p->edges[i])].numUsers--; + } + // free merged polygons + FreePolygon( model, polygon ); + FreePolygon( model, p ); + + return true; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( polygon->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( polygon->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + if ( MergePolygonWithTreePolygons( model, node->children[1], polygon ) ) { + return true; + } + node = node->children[0]; + } + } + return false; +} + +/* +============= +idCollisionModelManagerLocal::MergeTreePolygons + + try to merge any two polygons with the same surface flags and the same contents +============= +*/ +void idCollisionModelManagerLocal::MergeTreePolygons( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + bool merge; + + while( 1 ) { + do { + merge = false; + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + // try to merge this polygon with other polygons in the tree + if ( MergePolygonWithTreePolygons( model, model->node, p ) ) { + merge = true; + break; + } + } + } while (merge); + // if leaf node + if ( node->planeType == -1 ) { + break; + } + MergeTreePolygons( model, node->children[1] ); + node = node->children[0]; + } +} + +/* +=============================================================================== + +Find internal edges + +=============================================================================== +*/ + +/* + + if (two polygons have the same contents) + if (the normals of the two polygon planes face towards each other) + if (an edge is shared between the polygons) + if (the edge is not shared in the same direction) + then this is an internal edge + else + if (this edge is on the plane of the other polygon) + if (this edge if fully inside the winding of the other polygon) + then this edge is an internal edge + +*/ + +/* +============= +idCollisionModelManagerLocal::PointInsidePolygon +============= +*/ +bool idCollisionModelManagerLocal::PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v ) { + int i, edgeNum; + idVec3 *v1, *v2, dir1, dir2, vec; + cm_edge_t *edge; + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + // + v1 = &model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + v2 = &model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p; + dir1 = (*v2) - (*v1); + vec = v - (*v1); + dir2 = dir1.Cross( p->plane.Normal() ); + if ( vec * dir2 > VERTEX_EPSILON ) { + return false; + } + } + return true; +} + +/* +============= +idCollisionModelManagerLocal::FindInternalEdgesOnPolygon +============= +*/ +void idCollisionModelManagerLocal::FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) { + int i, j, k, edgeNum; + cm_edge_t *edge; + idVec3 *v1, *v2, dir1, dir2; + float d; + + // bounds of polygons should overlap or touch + for ( i = 0; i < 3; i++ ) { + if ( p1->bounds[0][i] > p2->bounds[1][i] ) { + return; + } + if ( p1->bounds[1][i] < p2->bounds[0][i] ) { + return; + } + } + // + // FIXME: doubled geometry causes problems + // + for ( i = 0; i < p1->numEdges; i++ ) { + edgeNum = p1->edges[i]; + edge = model->edges + abs(edgeNum); + // if already an internal edge + if ( edge->internal ) { + continue; + } + // + v1 = &model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + v2 = &model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p; + // if either of the two vertices is outside the bounds of the other polygon + for ( k = 0; k < 3; k++ ) { + d = p2->bounds[1][k] + VERTEX_EPSILON; + if ( (*v1)[k] > d || (*v2)[k] > d ) { + break; + } + d = p2->bounds[0][k] - VERTEX_EPSILON; + if ( (*v1)[k] < d || (*v2)[k] < d ) { + break; + } + } + if ( k < 3 ) { + continue; + } + // + k = abs(edgeNum); + for ( j = 0; j < p2->numEdges; j++ ) { + if ( k == abs(p2->edges[j]) ) { + break; + } + } + // if the edge is shared between the two polygons + if ( j < p2->numEdges ) { + // if the edge is used by more than 2 polygons + if ( edge->numUsers > 2 ) { + // could still be internal but we'd have to test all polygons using the edge + continue; + } + // if the edge goes in the same direction for both polygons + if ( edgeNum == p2->edges[j] ) { + // the polygons can lay ontop of each other or one can obscure the other + continue; + } + } + // the edge was not shared + else { + // both vertices should be on the plane of the other polygon + d = p2->plane.Distance( *v1 ); + if ( idMath::Fabs(d) > VERTEX_EPSILON ) { + continue; + } + d = p2->plane.Distance( *v2 ); + if ( idMath::Fabs(d) > VERTEX_EPSILON ) { + continue; + } + } + // the two polygon plane normals should face towards each other + dir1 = (*v2) - (*v1); + dir2 = p1->plane.Normal().Cross( dir1 ); + if ( p2->plane.Normal() * dir2 < 0 ) { + //continue; + break; + } + // if the edge was not shared + if ( j >= p2->numEdges ) { + // both vertices of the edge should be inside the winding of the other polygon + if ( !PointInsidePolygon( model, p2, *v1 ) ) { + continue; + } + if ( !PointInsidePolygon( model, p2, *v2 ) ) { + continue; + } + } + // we got another internal edge + edge->internal = true; + model->numInternalEdges++; + } +} + +/* +============= +idCollisionModelManagerLocal::FindInternalPolygonEdges +============= +*/ +void idCollisionModelManagerLocal::FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + + if ( polygon->material->GetCullType() == CT_TWO_SIDED || polygon->material->ShouldCreateBackSides() ) { + return; + } + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // + // FIXME: use some sort of additional checkcount because currently + // polygons can be checked multiple times + // + // if the polygons don't have the same contents + if ( p->contents != polygon->contents ) { + continue; + } + if ( p == polygon ) { + continue; + } + FindInternalEdgesOnPolygon( model, polygon, p ); + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( polygon->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( polygon->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + FindInternalPolygonEdges( model, node->children[1], polygon ); + node = node->children[0]; + } + } +} + +/* +============= +idCollisionModelManagerLocal::FindContainedEdges +============= +*/ +void idCollisionModelManagerLocal::FindContainedEdges( cm_model_t *model, cm_polygon_t *p ) { + int i, edgeNum; + cm_edge_t *edge; + idFixedWinding w; + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + if ( edge->internal ) { + continue; + } + w.Clear(); + w += model->vertices[edge->vertexNum[INT32_SIGNBITSET(edgeNum)]].p; + w += model->vertices[edge->vertexNum[INT32_SIGNBITNOTSET(edgeNum)]].p; + if ( ChoppedAwayByProcBSP( w, p->plane, p->contents ) ) { + edge->internal = true; + } + } +} + +/* +============= +idCollisionModelManagerLocal::FindInternalEdges +============= +*/ +void idCollisionModelManagerLocal::FindInternalEdges( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + + FindInternalPolygonEdges( model, model->node, p ); + + //FindContainedEdges( model, p ); + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + FindInternalEdges( model, node->children[1] ); + node = node->children[0]; + } +} + +/* +=============================================================================== + +Spatial subdivision + +=============================================================================== +*/ + +/* +================ +CM_FindSplitter +================ +*/ +static int CM_FindSplitter( const cm_node_t *node, const idBounds &bounds, int *planeType, float *planeDist ) { + int i, j, type, axis[3], polyCount; + float dist, t, bestt, size[3]; + cm_brushRef_t *bref; + cm_polygonRef_t *pref; + const cm_node_t *n; + bool forceSplit = false; + + for ( i = 0; i < 3; i++ ) { + size[i] = bounds[1][i] - bounds[0][i]; + axis[i] = i; + } + // sort on largest axis + for ( i = 0; i < 2; i++ ) { + if ( size[i] < size[i+1] ) { + t = size[i]; + size[i] = size[i+1]; + size[i+1] = t; + j = axis[i]; + axis[i] = axis[i+1]; + axis[i+1] = j; + i = -1; + } + } + // if the node is too small for further splits + if ( size[0] < MIN_NODE_SIZE ) { + polyCount = 0; + for ( pref = node->polygons; pref; pref = pref->next) { + polyCount++; + } + if ( polyCount > MAX_NODE_POLYGONS ) { + forceSplit = true; + } + } + // find an axial aligned splitter + for ( i = 0; i < 3; i++ ) { + // start with the largest axis first + type = axis[i]; + bestt = size[i]; + // if the node is small anough in this axis direction + if ( !forceSplit && bestt < MIN_NODE_SIZE ) { + break; + } + // find an axial splitter from the brush bounding boxes + // also try brushes from parent nodes + for ( n = node; n; n = n->parent ) { + for ( bref = n->brushes; bref; bref = bref->next) { + for ( j = 0; j < 2; j++ ) { + dist = bref->b->bounds[j][type]; + // if the splitter is already used or outside node bounds + if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) { + continue; + } + // find the most centered splitter + t = abs((bounds[1][type] - dist) - (dist - bounds[0][type])); + if ( t < bestt ) { + bestt = t; + *planeType = type; + *planeDist = dist; + } + } + } + } + // find an axial splitter from the polygon bounding boxes + // also try brushes from parent nodes + for ( n = node; n; n = n->parent ) { + for ( pref = n->polygons; pref; pref = pref->next) { + for ( j = 0; j < 2; j++ ) { + dist = pref->p->bounds[j][type]; + // if the splitter is already used or outside node bounds + if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) { + continue; + } + // find the most centered splitter + t = abs((bounds[1][type] - dist) - (dist - bounds[0][type])); + if ( t < bestt ) { + bestt = t; + *planeType = type; + *planeDist = dist; + } + } + } + } + // if we found a splitter on the largest axis + if ( bestt < size[i] ) { + // if forced split due to lots of polygons + if ( forceSplit ) { + return true; + } + // don't create splitters real close to the bounds + if ( bounds[1][type] - *planeDist > (MIN_NODE_SIZE*0.5f) && + *planeDist - bounds[0][type] > (MIN_NODE_SIZE*0.5f) ) { + return true; + } + } + } + return false; +} + +/* +================ +CM_R_InsideAllChildren +================ +*/ +static int CM_R_InsideAllChildren( cm_node_t *node, const idBounds &bounds ) { + assert(node != NULL); + if ( node->planeType != -1 ) { + if ( bounds[0][node->planeType] >= node->planeDist ) { + return false; + } + if ( bounds[1][node->planeType] <= node->planeDist ) { + return false; + } + if ( !CM_R_InsideAllChildren( node->children[0], bounds ) ) { + return false; + } + if ( !CM_R_InsideAllChildren( node->children[1], bounds ) ) { + return false; + } + } + return true; +} + +/* +================ +idCollisionModelManagerLocal::R_FilterPolygonIntoTree +================ +*/ +void idCollisionModelManagerLocal::R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p ) { + assert(node != NULL); + while ( node->planeType != -1 ) { + if ( CM_R_InsideAllChildren( node, p->bounds ) ) { + break; + } + if ( p->bounds[0][node->planeType] >= node->planeDist ) { + node = node->children[0]; + } + else if ( p->bounds[1][node->planeType] <= node->planeDist ) { + node = node->children[1]; + } + else { + R_FilterPolygonIntoTree( model, node->children[1], NULL, p ); + node = node->children[0]; + } + } + if ( pref ) { + pref->next = node->polygons; + node->polygons = pref; + } + else { + AddPolygonToNode( model, node, p ); + } +} + +/* +================ +idCollisionModelManagerLocal::R_FilterBrushIntoTree +================ +*/ +void idCollisionModelManagerLocal::R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b ) { + assert(node != NULL); + while ( node->planeType != -1 ) { + if ( CM_R_InsideAllChildren( node, b->bounds ) ) { + break; + } + if ( b->bounds[0][node->planeType] >= node->planeDist ) { + node = node->children[0]; + } + else if ( b->bounds[1][node->planeType] <= node->planeDist ) { + node = node->children[1]; + } + else { + R_FilterBrushIntoTree( model, node->children[1], NULL, b ); + node = node->children[0]; + } + } + if ( pref ) { + pref->next = node->brushes; + node->brushes = pref; + } + else { + AddBrushToNode( model, node, b ); + } +} + +/* +================ +idCollisionModelManagerLocal::R_CreateAxialBSPTree + + a brush or polygon is linked in the node closest to the root where + the brush or polygon is inside all children +================ +*/ +cm_node_t *idCollisionModelManagerLocal::R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds ) { + int planeType; + float planeDist; + cm_polygonRef_t *pref, *nextpref, *prevpref; + cm_brushRef_t *bref, *nextbref, *prevbref; + cm_node_t *frontNode, *backNode, *n; + idBounds frontBounds, backBounds; + + if ( !CM_FindSplitter( node, bounds, &planeType, &planeDist ) ) { + node->planeType = -1; + return node; + } + // create two child nodes + frontNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE ); + memset( frontNode, 0, sizeof(cm_node_t) ); + frontNode->parent = node; + frontNode->planeType = -1; + // + backNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE ); + memset( backNode, 0, sizeof(cm_node_t) ); + backNode->parent = node; + backNode->planeType = -1; + // + model->numNodes += 2; + // set front node bounds + frontBounds = bounds; + frontBounds[0][planeType] = planeDist; + // set back node bounds + backBounds = bounds; + backBounds[1][planeType] = planeDist; + // + node->planeType = planeType; + node->planeDist = planeDist; + node->children[0] = frontNode; + node->children[1] = backNode; + // filter polygons and brushes down the tree if necesary + for ( n = node; n; n = n->parent ) { + prevpref = NULL; + for ( pref = n->polygons; pref; pref = nextpref) { + nextpref = pref->next; + // if polygon is not inside all children + if ( !CM_R_InsideAllChildren( n, pref->p->bounds ) ) { + // filter polygon down the tree + R_FilterPolygonIntoTree( model, n, pref, pref->p ); + if ( prevpref ) { + prevpref->next = nextpref; + } + else { + n->polygons = nextpref; + } + } + else { + prevpref = pref; + } + } + prevbref = NULL; + for ( bref = n->brushes; bref; bref = nextbref) { + nextbref = bref->next; + // if brush is not inside all children + if ( !CM_R_InsideAllChildren( n, bref->b->bounds ) ) { + // filter brush down the tree + R_FilterBrushIntoTree( model, n, bref, bref->b ); + if ( prevbref ) { + prevbref->next = nextbref; + } + else { + n->brushes = nextbref; + } + } + else { + prevbref = bref; + } + } + } + R_CreateAxialBSPTree( model, frontNode, frontBounds ); + R_CreateAxialBSPTree( model, backNode, backBounds ); + return node; +} + +/* +int cm_numSavedPolygonLinks; +int cm_numSavedBrushLinks; + +int CM_R_CountChildren( cm_node_t *node ) { + if ( node->planeType == -1 ) { + return 0; + } + return 2 + CM_R_CountChildren(node->children[0]) + CM_R_CountChildren(node->children[1]); +} + +void CM_R_TestOptimisation( cm_node_t *node ) { + int polyCount, brushCount, numChildren; + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + if ( node->planeType == -1 ) { + return; + } + polyCount = 0; + for ( pref = node->polygons; pref; pref = pref->next) { + polyCount++; + } + brushCount = 0; + for ( bref = node->brushes; bref; bref = bref->next) { + brushCount++; + } + if ( polyCount || brushCount ) { + numChildren = CM_R_CountChildren( node ); + cm_numSavedPolygonLinks += (numChildren - 1) * polyCount; + cm_numSavedBrushLinks += (numChildren - 1) * brushCount; + } + CM_R_TestOptimisation( node->children[0] ); + CM_R_TestOptimisation( node->children[1] ); +} +*/ + +/* +================ +idCollisionModelManagerLocal::CreateAxialBSPTree +================ +*/ +cm_node_t *idCollisionModelManagerLocal::CreateAxialBSPTree( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + idBounds bounds; + + // get head node bounds + bounds.Clear(); + for ( pref = node->polygons; pref; pref = pref->next) { + bounds += pref->p->bounds; + } + for ( bref = node->brushes; bref; bref = bref->next) { + bounds += bref->b->bounds; + } + + // create axial BSP tree from head node + node = R_CreateAxialBSPTree( model, node, bounds ); + + return node; +} + +/* +=============================================================================== + +Raw polygon and brush data + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::SetupHash +================ +*/ +void idCollisionModelManagerLocal::SetupHash() { + if ( !cm_vertexHash ) { + cm_vertexHash = new (TAG_COLLISION) idHashIndex( VERTEX_HASH_SIZE, 1024 ); + } + if ( !cm_edgeHash ) { + cm_edgeHash = new (TAG_COLLISION) idHashIndex( EDGE_HASH_SIZE, 1024 ); + } + // init variables used during loading and optimization + if ( !cm_windingList ) { + cm_windingList = new (TAG_COLLISION) cm_windingList_t; + } + if ( !cm_outList ) { + cm_outList = new (TAG_COLLISION) cm_windingList_t; + } + if ( !cm_tmpList ) { + cm_tmpList = new (TAG_COLLISION) cm_windingList_t; + } +} + +/* +================ +idCollisionModelManagerLocal::ShutdownHash +================ +*/ +void idCollisionModelManagerLocal::ShutdownHash() { + delete cm_vertexHash; + cm_vertexHash = NULL; + delete cm_edgeHash; + cm_edgeHash = NULL; + delete cm_tmpList; + cm_tmpList = NULL; + delete cm_outList; + cm_outList = NULL; + delete cm_windingList; + cm_windingList = NULL; +} + +/* +================ +idCollisionModelManagerLocal::ClearHash +================ +*/ +void idCollisionModelManagerLocal::ClearHash( idBounds &bounds ) { + int i; + float f, max; + + cm_vertexHash->Clear(); + cm_edgeHash->Clear(); + + cm_modelBounds = bounds; + max = bounds[1].x - bounds[0].x; + f = bounds[1].y - bounds[0].y; + if ( f > max ) { + max = f; + } + cm_vertexShift = (float) max / VERTEX_HASH_BOXSIZE; + for ( i = 0; (1<> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1); + y = (((int)(vec[1] - cm_modelBounds[0].y + 0.5 )) >> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1); + + assert (x >= 0 && x < VERTEX_HASH_BOXSIZE && y >= 0 && y < VERTEX_HASH_BOXSIZE); + + return y * VERTEX_HASH_BOXSIZE + x; + */ + int x, y, z; + + x = (((int) (vec[0] - cm_modelBounds[0].x + 0.5)) + 2) >> 2; + y = (((int) (vec[1] - cm_modelBounds[0].y + 0.5)) + 2) >> 2; + z = (((int) (vec[2] - cm_modelBounds[0].z + 0.5)) + 2) >> 2; + return (x + y * VERTEX_HASH_BOXSIZE + z) & (VERTEX_HASH_SIZE-1); +} + +/* +================ +idCollisionModelManagerLocal::GetVertex +================ +*/ +int idCollisionModelManagerLocal::GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum ) { + int i, hashKey, vn; + idVec3 vert, *p; + + for (i = 0; i < 3; i++) { + if ( idMath::Fabs(v[i] - idMath::Rint(v[i])) < INTEGRAL_EPSILON ) + vert[i] = idMath::Rint(v[i]); + else + vert[i] = v[i]; + } + + hashKey = HashVec( vert ); + + for (vn = cm_vertexHash->First( hashKey ); vn >= 0; vn = cm_vertexHash->Next( vn ) ) { + p = &model->vertices[vn].p; + // first compare z-axis because hash is based on x-y plane + if (idMath::Fabs(vert[2] - (*p)[2]) < VERTEX_EPSILON && + idMath::Fabs(vert[0] - (*p)[0]) < VERTEX_EPSILON && + idMath::Fabs(vert[1] - (*p)[1]) < VERTEX_EPSILON ) + { + *vertexNum = vn; + return true; + } + } + + if ( model->numVertices >= model->maxVertices ) { + cm_vertex_t *oldVertices; + + // resize vertex array + model->maxVertices = (float) model->maxVertices * 1.5f + 1; + oldVertices = model->vertices; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t), TAG_COLLISION ); + memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) ); + Mem_Free( oldVertices ); + + cm_vertexHash->ResizeIndex( model->maxVertices ); + } + model->vertices[model->numVertices].p = vert; + model->vertices[model->numVertices].checkcount = 0; + *vertexNum = model->numVertices; + // add vertice to hash + cm_vertexHash->Add( hashKey, model->numVertices ); + // + model->numVertices++; + return false; +} + +/* +================ +idCollisionModelManagerLocal::GetEdge +================ +*/ +int idCollisionModelManagerLocal::GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ) { + int v2num, hashKey, e; + int found, *vertexNum; + + // the first edge is a dummy + if ( model->numEdges == 0 ) { + model->numEdges = 1; + } + + if ( v1num != -1 ) { + found = 1; + } + else { + found = GetVertex( model, v1, &v1num ); + } + found &= GetVertex( model, v2, &v2num ); + // if both vertices are the same or snapped onto each other + if ( v1num == v2num ) { + *edgeNum = 0; + return true; + } + hashKey = cm_edgeHash->GenerateKey( v1num, v2num ); + // if both vertices where already stored + if (found) { + for (e = cm_edgeHash->First( hashKey ); e >= 0; e = cm_edgeHash->Next( e ) ) + { + // NOTE: only allow at most two users that use the edge in opposite direction + if ( model->edges[e].numUsers != 1 ) { + continue; + } + + vertexNum = model->edges[e].vertexNum; + if ( vertexNum[0] == v2num ) { + if ( vertexNum[1] == v1num ) { + // negative for a reversed edge + *edgeNum = -e; + break; + } + } + /* + else if ( vertexNum[0] == v1num ) { + if ( vertexNum[1] == v2num ) { + *edgeNum = e; + break; + } + } + */ + } + // if edge found in hash + if ( e >= 0 ) { + model->edges[e].numUsers++; + return true; + } + } + if ( model->numEdges >= model->maxEdges ) { + cm_edge_t *oldEdges; + + // resize edge array + model->maxEdges = (float) model->maxEdges * 1.5f + 1; + oldEdges = model->edges; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t), TAG_COLLISION ); + memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) ); + Mem_Free( oldEdges ); + + cm_edgeHash->ResizeIndex( model->maxEdges ); + } + // setup edge + model->edges[model->numEdges].vertexNum[0] = v1num; + model->edges[model->numEdges].vertexNum[1] = v2num; + model->edges[model->numEdges].internal = false; + model->edges[model->numEdges].checkcount = 0; + model->edges[model->numEdges].numUsers = 1; // used by one polygon atm + model->edges[model->numEdges].normal.Zero(); + // + *edgeNum = model->numEdges; + // add edge to hash + cm_edgeHash->Add( hashKey, model->numEdges ); + + model->numEdges++; + + return false; +} + +/* +================ +idCollisionModelManagerLocal::CreatePolygon +================ +*/ +void idCollisionModelManagerLocal::CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) { + int i, j, edgeNum, v1num; + int numPolyEdges, polyEdges[MAX_POINTS_ON_WINDING]; + idBounds bounds; + cm_polygon_t *p; + + // turn the winding into a sequence of edges + numPolyEdges = 0; + v1num = -1; // first vertex unknown + for ( i = 0, j = 1; i < w->GetNumPoints(); i++, j++ ) { + if ( j >= w->GetNumPoints() ) { + j = 0; + } + GetEdge( model, (*w)[i].ToVec3(), (*w)[j].ToVec3(), &polyEdges[numPolyEdges], v1num ); + if ( polyEdges[numPolyEdges] ) { + // last vertex of this edge is the first vertex of the next edge + v1num = model->edges[ abs(polyEdges[numPolyEdges]) ].vertexNum[ INT32_SIGNBITNOTSET(polyEdges[numPolyEdges]) ]; + // this edge is valid so keep it + numPolyEdges++; + } + } + // should have at least 3 edges + if ( numPolyEdges < 3 ) { + return; + } + // the polygon is invalid if some edge is found twice + for ( i = 0; i < numPolyEdges; i++ ) { + for ( j = i+1; j < numPolyEdges; j++ ) { + if ( abs(polyEdges[i]) == abs(polyEdges[j]) ) { + return; + } + } + } + // don't overflow max edges + if ( numPolyEdges > CM_MAX_POLYGON_EDGES ) { + common->Warning( "idCollisionModelManagerLocal::CreatePolygon: polygon has more than %d edges", numPolyEdges ); + numPolyEdges = CM_MAX_POLYGON_EDGES; + } + + w->GetBounds( bounds ); + + p = AllocPolygon( model, numPolyEdges ); + p->numEdges = numPolyEdges; + p->contents = material->GetContentFlags(); + p->material = material; + p->checkcount = 0; + p->plane = plane; + p->bounds = bounds; + for ( i = 0; i < numPolyEdges; i++ ) { + edgeNum = polyEdges[i]; + p->edges[i] = edgeNum; + } + R_FilterPolygonIntoTree( model, model->node, NULL, p ); +} + +/* +================ +idCollisionModelManagerLocal::PolygonFromWinding + + NOTE: for patches primitiveNum < 0 and abs(primitiveNum) is the real number +================ +*/ +void idCollisionModelManagerLocal::PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) { + int contents; + + contents = material->GetContentFlags(); + + // if this polygon is part of the world model + if ( numModels == 0 ) { + // if the polygon is fully chopped away by the proc bsp tree + if ( ChoppedAwayByProcBSP( *w, plane, contents ) ) { + model->numRemovedPolys++; + return; + } + } + + // get one winding that is not or only partly contained in brushes + w = WindingOutsideBrushes( w, plane, contents, primitiveNum, model->node ); + + // if the polygon is fully contained within a brush + if ( !w ) { + model->numRemovedPolys++; + return; + } + + if ( w->IsHuge() ) { + common->Warning( "idCollisionModelManagerLocal::PolygonFromWinding: model %s primitive %d is degenerate", model->name.c_str(), abs(primitiveNum) ); + return; + } + + CreatePolygon( model, w, plane, material, primitiveNum ); + + if ( material->GetCullType() == CT_TWO_SIDED || material->ShouldCreateBackSides() ) { + w->ReverseSelf(); + CreatePolygon( model, w, -plane, material, primitiveNum ); + } +} + +/* +================= +idCollisionModelManagerLocal::CreatePatchPolygons +================= +*/ +void idCollisionModelManagerLocal::CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum ) { + int i, j; + float dot; + int v1, v2, v3, v4; + idFixedWinding w; + idPlane plane; + idVec3 d1, d2; + + for ( i = 0; i < mesh.GetWidth() - 1; i++ ) { + for ( j = 0; j < mesh.GetHeight() - 1; j++ ) { + + v1 = j * mesh.GetWidth() + i; + v2 = v1 + 1; + v3 = v1 + mesh.GetWidth() + 1; + v4 = v1 + mesh.GetWidth(); + + d1 = mesh[v2].xyz - mesh[v1].xyz; + d2 = mesh[v3].xyz - mesh[v1].xyz; + plane.SetNormal( d1.Cross(d2) ); + if ( plane.Normalize() != 0.0f ) { + plane.FitThroughPoint( mesh[v1].xyz ); + dot = plane.Distance( mesh[v4].xyz ); + // if we can turn it into a quad + if ( idMath::Fabs(dot) < 0.1f ) { + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v2].xyz; + w += mesh[v3].xyz; + w += mesh[v4].xyz; + + PolygonFromWinding( model, &w, plane, material, -primitiveNum ); + continue; + } + else { + // create one of the triangles + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v2].xyz; + w += mesh[v3].xyz; + + PolygonFromWinding( model, &w, plane, material, -primitiveNum ); + } + } + // create the other triangle + d1 = mesh[v3].xyz - mesh[v1].xyz; + d2 = mesh[v4].xyz - mesh[v1].xyz; + plane.SetNormal( d1.Cross(d2) ); + if ( plane.Normalize() != 0.0f ) { + plane.FitThroughPoint( mesh[v1].xyz ); + + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v3].xyz; + w += mesh[v4].xyz; + + PolygonFromWinding( model, &w, plane, material, -primitiveNum ); + } + } + } +} + +/* +================= +CM_EstimateVertsAndEdges +================= +*/ +static void CM_EstimateVertsAndEdges( const idMapEntity *mapEnt, int *numVerts, int *numEdges ) { + int j, width, height; + + *numVerts = *numEdges = 0; + for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) { + const idMapPrimitive *mapPrim; + mapPrim = mapEnt->GetPrimitive(j); + if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) { + // assume maximum tesselation without adding verts + width = static_cast(mapPrim)->GetWidth(); + height = static_cast(mapPrim)->GetHeight(); + *numVerts += width * height; + *numEdges += (width-1) * height + width * (height-1) + (width-1) * (height-1); + continue; + } + if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { + // assume cylinder with a polygon with (numSides - 2) edges ontop and on the bottom + *numVerts += (static_cast(mapPrim)->GetNumSides() - 2) * 2; + *numEdges += (static_cast(mapPrim)->GetNumSides() - 2) * 3; + continue; + } + } +} + +/* +================= +idCollisionModelManagerLocal::ConverPatch +================= +*/ +void idCollisionModelManagerLocal::ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum ) { + const idMaterial *material; + idSurface_Patch *cp; + + material = declManager->FindMaterial( patch->GetMaterial() ); + if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + return; + } + + // copy the patch + cp = new (TAG_COLLISION) idSurface_Patch( *patch ); + + // if the patch has an explicit number of subdivisions use it to avoid cracks + if ( patch->GetExplicitlySubdivided() ) { + cp->SubdivideExplicit( patch->GetHorzSubdivisions(), patch->GetVertSubdivisions(), false, true ); + } else { + cp->Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false ); + } + + // create collision polygons for the patch + CreatePatchPolygons( model, *cp, material, primitiveNum ); + + delete cp; +} + +/* +================ +idCollisionModelManagerLocal::ConvertBrushSides +================ +*/ +void idCollisionModelManagerLocal::ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) { + int i, j; + idMapBrushSide *mapSide; + idFixedWinding w; + idPlane *planes; + const idMaterial *material; + + // fix degenerate planes + planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) ); + for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { + planes[i] = mapBrush->GetSide(i)->GetPlane(); + planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON ); + } + + // create a collision polygon for each brush side + for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { + mapSide = mapBrush->GetSide(i); + material = declManager->FindMaterial( mapSide->GetMaterial() ); + if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + continue; + } + w.BaseForPlane( -planes[i] ); + for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) { + if ( i == j ) { + continue; + } + w.ClipInPlace( -planes[j], 0 ); + } + + if ( w.GetNumPoints() ) { + PolygonFromWinding( model, &w, planes[i], material, primitiveNum ); + } + } +} + +/* +================ +idCollisionModelManagerLocal::ConvertBrush +================ +*/ +void idCollisionModelManagerLocal::ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) { + int i, j, contents; + idBounds bounds; + idMapBrushSide *mapSide; + cm_brush_t *brush; + idPlane *planes; + idFixedWinding w; + const idMaterial *material = NULL; + + contents = 0; + bounds.Clear(); + + // fix degenerate planes + planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) ); + for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { + planes[i] = mapBrush->GetSide(i)->GetPlane(); + planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON ); + } + + // we are only getting the bounds for the brush so there's no need + // to create a winding for the last brush side + for ( i = 0; i < mapBrush->GetNumSides() - 1; i++ ) { + mapSide = mapBrush->GetSide(i); + material = declManager->FindMaterial( mapSide->GetMaterial() ); + contents |= ( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ); + w.BaseForPlane( -planes[i] ); + for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) { + if ( i == j ) { + continue; + } + w.ClipInPlace( -planes[j], 0 ); + } + + for ( j = 0; j < w.GetNumPoints(); j++ ) { + bounds.AddPoint( w[j].ToVec3() ); + } + } + if ( !contents ) { + return; + } + // create brush for position test + brush = AllocBrush( model, mapBrush->GetNumSides() ); + brush->checkcount = 0; + brush->contents = contents; + brush->material = material; + brush->primitiveNum = primitiveNum; + brush->bounds = bounds; + brush->numPlanes = mapBrush->GetNumSides(); + for (i = 0; i < mapBrush->GetNumSides(); i++) { + brush->planes[i] = planes[i]; + } + AddBrushToNode( model, model->node, brush ); +} + +/* +================ +CM_CountNodeBrushes +================ +*/ +static int CM_CountNodeBrushes( const cm_node_t *node ) { + int count; + cm_brushRef_t *bref; + + count = 0; + for ( bref = node->brushes; bref; bref = bref->next ) { + count++; + } + return count; +} + +/* +================ +CM_R_GetModelBounds +================ +*/ +static void CM_R_GetNodeBounds( idBounds *bounds, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + bounds->AddPoint( pref->p->bounds[0] ); + bounds->AddPoint( pref->p->bounds[1] ); + } + for ( bref = node->brushes; bref; bref = bref->next ) { + bounds->AddPoint( bref->b->bounds[0] ); + bounds->AddPoint( bref->b->bounds[1] ); + } + if ( node->planeType == -1 ) { + break; + } + CM_R_GetNodeBounds( bounds, node->children[1] ); + node = node->children[0]; + } +} + +/* +================ +CM_GetNodeBounds +================ +*/ +void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ) { + bounds->Clear(); + CM_R_GetNodeBounds( bounds, node ); + if ( bounds->IsCleared() ) { + bounds->Zero(); + } +} + +/* +================ +CM_GetNodeContents +================ +*/ +int CM_GetNodeContents( cm_node_t *node ) { + int contents; + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + contents = 0; + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + contents |= pref->p->contents; + } + for ( bref = node->brushes; bref; bref = bref->next ) { + contents |= bref->b->contents; + } + if ( node->planeType == -1 ) { + break; + } + contents |= CM_GetNodeContents( node->children[1] ); + node = node->children[0]; + } + return contents; +} + +/* +================== +idCollisionModelManagerLocal::RemapEdges +================== +*/ +void idCollisionModelManagerLocal::RemapEdges( cm_node_t *node, int *edgeRemap ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int i; + + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + for ( i = 0; i < p->numEdges; i++ ) { + if ( p->edges[i] < 0 ) { + p->edges[i] = -edgeRemap[ abs(p->edges[i]) ]; + } + else { + p->edges[i] = edgeRemap[ p->edges[i] ]; + } + } + } + if ( node->planeType == -1 ) { + break; + } + + RemapEdges( node->children[1], edgeRemap ); + node = node->children[0]; + } +} + +/* +================== +idCollisionModelManagerLocal::OptimizeArrays + + due to polygon merging and polygon removal the vertex and edge array + can have a lot of unused entries. +================== +*/ +void idCollisionModelManagerLocal::OptimizeArrays( cm_model_t *model ) { + int i, newNumVertices, newNumEdges, *v; + int *remap; + cm_edge_t *oldEdges; + cm_vertex_t *oldVertices; + + remap = (int *) Mem_ClearedAlloc( Max( model->numVertices, model->numEdges ) * sizeof( int ), TAG_COLLISION ); + // get all used vertices + for ( i = 0; i < model->numEdges; i++ ) { + remap[ model->edges[i].vertexNum[0] ] = true; + remap[ model->edges[i].vertexNum[1] ] = true; + } + // create remap index and move vertices + newNumVertices = 0; + for ( i = 0; i < model->numVertices; i++ ) { + if ( remap[ i ] ) { + remap[ i ] = newNumVertices; + model->vertices[ newNumVertices ] = model->vertices[ i ]; + newNumVertices++; + } + } + model->numVertices = newNumVertices; + // change edge vertex indexes + for ( i = 1; i < model->numEdges; i++ ) { + v = model->edges[i].vertexNum; + v[0] = remap[ v[0] ]; + v[1] = remap[ v[1] ]; + } + + // create remap index and move edges + newNumEdges = 1; + for ( i = 1; i < model->numEdges; i++ ) { + // if the edge is used + if ( model->edges[ i ].numUsers ) { + remap[ i ] = newNumEdges; + model->edges[ newNumEdges ] = model->edges[ i ]; + newNumEdges++; + } + } + // change polygon edge indexes + checkCount++; + RemapEdges( model->node, remap ); + model->numEdges = newNumEdges; + + Mem_Free( remap ); + + // realloc vertices + oldVertices = model->vertices; + model->maxVertices = model->numVertices; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->numVertices * sizeof(cm_vertex_t), TAG_COLLISION ); + if ( oldVertices ) { + memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) ); + Mem_Free( oldVertices ); + } + + // realloc edges + oldEdges = model->edges; + model->maxEdges = model->numEdges; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->numEdges * sizeof(cm_edge_t), TAG_COLLISION ); + if ( oldEdges ) { + memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) ); + Mem_Free( oldEdges ); + } +} + +/* +================ +idCollisionModelManagerLocal::FinishModel +================ +*/ +void idCollisionModelManagerLocal::FinishModel( cm_model_t *model ) { + // try to merge polygons + checkCount++; + MergeTreePolygons( model, model->node ); + // find internal edges (no mesh can ever collide with internal edges) + checkCount++; + FindInternalEdges( model, model->node ); + // calculate edge normals + checkCount++; + CalculateEdgeNormals( model, model->node ); + + //common->Printf( "%s vertex hash spread is %d\n", model->name.c_str(), cm_vertexHash->GetSpread() ); + //common->Printf( "%s edge hash spread is %d\n", model->name.c_str(), cm_edgeHash->GetSpread() ); + + // remove all unused vertices and edges + OptimizeArrays( model ); + // get model bounds from brush and polygon bounds + CM_GetNodeBounds( &model->bounds, model->node ); + // get model contents + model->contents = CM_GetNodeContents( model->node ); + // total memory used by this model + model->usedMemory = model->numVertices * sizeof(cm_vertex_t) + + model->numEdges * sizeof(cm_edge_t) + + model->polygonMemory + + model->brushMemory + + model->numNodes * sizeof(cm_node_t) + + model->numPolygonRefs * sizeof(cm_polygonRef_t) + + model->numBrushRefs * sizeof(cm_brushRef_t); +} + +static const byte BCM_VERSION = 100; +static const unsigned int BCM_MAGIC = ( 'B' << 24 ) | ( 'C' << 16 ) | ( 'M' << 16 ) | BCM_VERSION; + +/* +================ +idCollisionModelManagerLocal::LoadBinaryModel +================ +*/ +cm_model_t * idCollisionModelManagerLocal::LoadBinaryModelFromFile( idFile *file, ID_TIME_T sourceTimeStamp ) { + + unsigned int magic = 0; + file->ReadBig( magic ); + if ( magic != BCM_MAGIC ) { + return NULL; + } + ID_TIME_T storedTimeStamp = FILE_NOT_FOUND_TIMESTAMP; + file->ReadBig( storedTimeStamp ); + if ( !fileSystem->InProductionMode() && storedTimeStamp != sourceTimeStamp ) { + return NULL; + } + cm_model_t * model = AllocModel(); + file->ReadString( model->name ); + file->ReadBig( model->bounds ); + file->ReadBig( model->contents ); + file->ReadBig( model->isConvex ); + file->ReadBig( model->numVertices ); + file->ReadBig( model->numEdges ); + file->ReadBig( model->numPolygons ); + file->ReadBig( model->numBrushes ); + file->ReadBig( model->numNodes ); + file->ReadBig( model->numBrushRefs ); + file->ReadBig( model->numPolygonRefs ); + file->ReadBig( model->numInternalEdges ); + file->ReadBig( model->numSharpEdges ); + file->ReadBig( model->numRemovedPolys ); + file->ReadBig( model->numMergedPolys ); + + model->maxVertices = model->numVertices; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t), TAG_COLLISION ); + for ( int i = 0; i < model->numVertices; i++ ) { + file->ReadBig( model->vertices[i].p ); + file->ReadBig( model->vertices[i].checkcount ); + file->ReadBig( model->vertices[i].side ); + file->ReadBig( model->vertices[i].sideSet ); + } + + model->maxEdges = model->numEdges; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t), TAG_COLLISION ); + for ( int i = 0; i < model->numEdges; i++ ) { + file->ReadBig( model->edges[i].checkcount ); + file->ReadBig( model->edges[i].internal ); + file->ReadBig( model->edges[i].numUsers ); + file->ReadBig( model->edges[i].side ); + file->ReadBig( model->edges[i].sideSet ); + file->ReadBig( model->edges[i].vertexNum[0] ); + file->ReadBig( model->edges[i].vertexNum[1] ); + file->ReadBig( model->edges[i].normal ); + } + + file->ReadBig( model->polygonMemory ); + model->polygonBlock = (cm_polygonBlock_t *) Mem_ClearedAlloc( sizeof( cm_polygonBlock_t ) + model->polygonMemory, TAG_COLLISION ); + model->polygonBlock->bytesRemaining = model->polygonMemory; + model->polygonBlock->next = ( (byte *) model->polygonBlock ) + sizeof( cm_polygonBlock_t ); + + file->ReadBig( model->brushMemory ); + model->brushBlock = (cm_brushBlock_t *) Mem_ClearedAlloc( sizeof( cm_brushBlock_t ) + model->brushMemory, TAG_COLLISION ); + model->brushBlock->bytesRemaining = model->brushMemory; + model->brushBlock->next = ( (byte *) model->brushBlock ) + sizeof( cm_brushBlock_t ); + + int numMaterials = 0; + file->ReadBig( numMaterials ); + + idList< const idMaterial * > materials; + materials.SetNum( numMaterials ); + idStr materialName; + for ( int i = 0; i < materials.Num(); i++ ) { + file->ReadString( materialName ); + if ( materialName.IsEmpty() ) { + materials[i] = NULL; + } else { + materials[i] = declManager->FindMaterial( materialName ); + } + } + idList< cm_polygon_t * > polys; + idList< cm_brush_t * > brushes; + polys.SetNum( model->numPolygons ); + brushes.SetNum( model->numBrushes ); + for ( int i = 0; i < polys.Num(); i++ ) { + int materialIndex = 0; + file->ReadBig( materialIndex ); + int numEdges = 0; + file->ReadBig( numEdges ); + polys[i] = AllocPolygon( model, numEdges ); + polys[i]->numEdges = numEdges; + polys[i]->material = materials[materialIndex]; + file->ReadBig( polys[i]->bounds ); + file->ReadBig( polys[i]->checkcount ); + file->ReadBig( polys[i]->contents ); + file->ReadBig( polys[i]->plane ); + file->ReadBigArray( polys[i]->edges, polys[i]->numEdges ); + } + for ( int i = 0; i < brushes.Num(); i++ ) { + int materialIndex = 0; + file->ReadBig( materialIndex ); + int numPlanes = 0; + file->ReadBig( numPlanes ); + brushes[i] = AllocBrush( model, numPlanes ); + brushes[i]->numPlanes = numPlanes; + brushes[i]->material = materials[materialIndex]; + file->ReadBig( brushes[i]->checkcount ); + file->ReadBig( brushes[i]->bounds ); + file->ReadBig( brushes[i]->contents ); + file->ReadBig( brushes[i]->primitiveNum ); + file->ReadBigArray( brushes[i]->planes, brushes[i]->numPlanes ); + } + struct local { + static void ReadNodeTree( idFile * file, cm_model_t * model, cm_node_t * node, idList< cm_polygon_t * > & polys, idList< cm_brush_t * > & brushes ) { + file->ReadBig( node->planeType ); + file->ReadBig( node->planeDist ); + int i = 0; + while ( file->ReadBig( i ) == sizeof( i ) && ( i >= 0 ) ) { + cm_polygonRef_t * pref = collisionModelManagerLocal.AllocPolygonReference( model, model->numPolygonRefs ); + pref->p = polys[i]; + pref->next = node->polygons; + node->polygons = pref; + } + while ( file->ReadBig( i ) == sizeof( i ) && ( i >= 0 ) ) { + cm_brushRef_t * bref = collisionModelManagerLocal.AllocBrushReference( model, model->numBrushRefs ); + bref->b = brushes[i]; + bref->next = node->brushes; + node->brushes = bref; + } + if ( node->planeType != -1 ) { + node->children[0] = collisionModelManagerLocal.AllocNode( model, model->numNodes ); + node->children[1] = collisionModelManagerLocal.AllocNode( model, model->numNodes ); + node->children[0]->parent = node; + node->children[1]->parent = node; + ReadNodeTree( file, model, node->children[0], polys, brushes ); + ReadNodeTree( file, model, node->children[1], polys, brushes ); + } + } + }; + model->node = AllocNode( model, model->numNodes + 1 ); + local::ReadNodeTree( file, model, model->node, polys, brushes ); + + // We should have only allocated a single block, and used every entry in the block + // assert( model->nodeBlocks != NULL && model->nodeBlocks->next == NULL && model->nodeBlocks->nextNode == NULL ); + assert( model->brushRefBlocks == NULL || ( model->brushRefBlocks->next == NULL && model->brushRefBlocks->nextRef == NULL ) ); + assert( model->polygonRefBlocks == NULL || ( model->polygonRefBlocks->next == NULL && model->polygonRefBlocks->nextRef == NULL ) ); + assert( model->polygonBlock->bytesRemaining == 0 ); + assert( model->brushBlock->bytesRemaining == 0 ); + + model->usedMemory = model->numVertices * sizeof(cm_vertex_t) + + model->numEdges * sizeof(cm_edge_t) + + model->polygonMemory + + model->brushMemory + + model->numNodes * sizeof(cm_node_t) + + model->numPolygonRefs * sizeof(cm_polygonRef_t) + + model->numBrushRefs * sizeof(cm_brushRef_t); + return model; +} + +/* +================ +idCollisionModelManagerLocal::LoadBinaryModel +================ +*/ +cm_model_t * idCollisionModelManagerLocal::LoadBinaryModel( const char *fileName, ID_TIME_T sourceTimeStamp ) { + idFileLocal file( fileSystem->OpenFileReadMemory( fileName ) ); + if ( file == NULL ) { + return NULL; + } + return LoadBinaryModelFromFile( file, sourceTimeStamp ); +} + +/* +================ +idCollisionModelManagerLocal::WriteBinaryModel +================ +*/ +void idCollisionModelManagerLocal::WriteBinaryModelToFile( cm_model_t *model, idFile *file, ID_TIME_T sourceTimeStamp ) { + + file->WriteBig( BCM_MAGIC ); + file->WriteBig( sourceTimeStamp ); + file->WriteString( model->name ); + file->WriteBig( model->bounds ); + file->WriteBig( model->contents ); + file->WriteBig( model->isConvex ); + file->WriteBig( model->numVertices ); + file->WriteBig( model->numEdges ); + file->WriteBig( model->numPolygons ); + file->WriteBig( model->numBrushes ); + file->WriteBig( model->numNodes ); + file->WriteBig( model->numBrushRefs ); + file->WriteBig( model->numPolygonRefs ); + file->WriteBig( model->numInternalEdges ); + file->WriteBig( model->numSharpEdges ); + file->WriteBig( model->numRemovedPolys ); + file->WriteBig( model->numMergedPolys ); + for ( int i = 0; i < model->numVertices; i++ ) { + file->WriteBig( model->vertices[i].p ); + file->WriteBig( model->vertices[i].checkcount ); + file->WriteBig( model->vertices[i].side ); + file->WriteBig( model->vertices[i].sideSet ); + } + for ( int i = 0; i < model->numEdges; i++ ) { + file->WriteBig( model->edges[i].checkcount ); + file->WriteBig( model->edges[i].internal ); + file->WriteBig( model->edges[i].numUsers ); + file->WriteBig( model->edges[i].side ); + file->WriteBig( model->edges[i].sideSet ); + file->WriteBig( model->edges[i].vertexNum[0] ); + file->WriteBig( model->edges[i].vertexNum[1] ); + file->WriteBig( model->edges[i].normal ); + } + file->WriteBig( model->polygonMemory ); + file->WriteBig( model->brushMemory ); + struct local { + static void BuildUniqueLists( cm_node_t * node, idList< cm_polygon_t * > & polys, idList< cm_brush_t * > & brushes ) { + for ( cm_polygonRef_t * pr = node->polygons; pr != NULL; pr = pr->next ) { + polys.AddUnique( pr->p ); + } + for ( cm_brushRef_t * br = node->brushes; br != NULL; br = br->next ) { + brushes.AddUnique( br->b ); + } + if ( node->planeType != -1 ) { + BuildUniqueLists( node->children[0], polys, brushes ); + BuildUniqueLists( node->children[1], polys, brushes ); + } + } + static void WriteNodeTree( idFile * file, cm_node_t * node, idList< cm_polygon_t * > & polys, idList< cm_brush_t * > & brushes ) { + file->WriteBig( node->planeType ); + file->WriteBig( node->planeDist ); + for ( cm_polygonRef_t * pr = node->polygons; pr != NULL; pr = pr->next ) { + file->WriteBig( polys.FindIndex( pr->p ) ); + } + file->WriteBig( -1 ); + for ( cm_brushRef_t * br = node->brushes; br != NULL; br = br->next ) { + file->WriteBig( brushes.FindIndex( br->b ) ); + } + file->WriteBig( -1 ); + if ( node->planeType != -1 ) { + WriteNodeTree( file, node->children[0], polys, brushes ); + WriteNodeTree( file, node->children[1], polys, brushes ); + } + } + }; + idList< cm_polygon_t * > polys; + idList< cm_brush_t * > brushes; + local::BuildUniqueLists( model->node, polys, brushes ); + assert( polys.Num() == model->numPolygons ); + assert( brushes.Num() == model->numBrushes ); + + idList< const idMaterial * > materials; + for ( int i = 0; i < polys.Num(); i++ ) { + materials.AddUnique( polys[i]->material ); + } + for ( int i = 0; i < brushes.Num(); i++ ) { + materials.AddUnique( brushes[i]->material ); + } + file->WriteBig( materials.Num() ); + for ( int i = 0; i < materials.Num(); i++ ) { + if ( materials[i] == NULL ) { + file->WriteString( "" ); + } else { + file->WriteString( materials[i]->GetName() ); + } + } + for ( int i = 0; i < polys.Num(); i++ ) { + file->WriteBig( ( int )materials.FindIndex( polys[i]->material ) ); + file->WriteBig( polys[i]->numEdges ); + file->WriteBig( polys[i]->bounds ); + file->WriteBig( polys[i]->checkcount ); + file->WriteBig( polys[i]->contents ); + file->WriteBig( polys[i]->plane ); + file->WriteBigArray( polys[i]->edges, polys[i]->numEdges ); + } + for ( int i = 0; i < brushes.Num(); i++ ) { + file->WriteBig( ( int )materials.FindIndex( brushes[i]->material ) ); + file->WriteBig( brushes[i]->numPlanes ); + file->WriteBig( brushes[i]->checkcount ); + file->WriteBig( brushes[i]->bounds ); + file->WriteBig( brushes[i]->contents ); + file->WriteBig( brushes[i]->primitiveNum ); + file->WriteBigArray( brushes[i]->planes, brushes[i]->numPlanes ); + } + local::WriteNodeTree( file, model->node, polys, brushes ); +} + +/* +================ +idCollisionModelManagerLocal::WriteBinaryModel +================ +*/ +void idCollisionModelManagerLocal::WriteBinaryModel( cm_model_t *model, const char *fileName, ID_TIME_T sourceTimeStamp ) { + idFileLocal file( fileSystem->OpenFileWrite( fileName, "fs_basepath" ) ); + if ( file == NULL ) { + common->Printf( "Failed to open %s\n", fileName ); + return; + } + WriteBinaryModelToFile( model, file, sourceTimeStamp ); +} + +/* +================ +idCollisionModelManagerLocal::LoadRenderModel +================ +*/ +cm_model_t *idCollisionModelManagerLocal::LoadRenderModel( const char *fileName ) { + int i, j; + idRenderModel *renderModel; + const modelSurface_t *surf; + idFixedWinding w; + cm_node_t *node; + cm_model_t *model; + idPlane plane; + idBounds bounds; + bool collisionSurface; + idStr extension; + + // only load ASE and LWO models + idStr( fileName ).ExtractFileExtension( extension ); + if ( ( extension.Icmp( "ase" ) != 0 ) && ( extension.Icmp( "lwo" ) != 0 ) && ( extension.Icmp( "ma" ) != 0 ) ) { + return NULL; + } + + renderModel = renderModelManager->CheckModel( fileName ); + if ( !renderModel ) { + return NULL; + } + + idStrStatic< MAX_OSPATH > generatedFileName = "generated/collision/"; + generatedFileName.AppendPath( fileName ); + generatedFileName.SetFileExtension( CMODEL_BINARYFILE_EXT ); + + ID_TIME_T sourceTimeStamp = renderModel->Timestamp(); + model = LoadBinaryModel( generatedFileName, sourceTimeStamp ); + if ( model != NULL ) { + return model; + } + idLib::Printf( "Writing %s\n", generatedFileName.c_str() ); + + model = AllocModel(); + model->name = fileName; + node = AllocNode( model, NODE_BLOCK_SIZE_SMALL ); + node->planeType = -1; + model->node = node; + + model->maxVertices = 0; + model->numVertices = 0; + model->maxEdges = 0; + model->numEdges = 0; + + bounds = renderModel->Bounds( NULL ); + + collisionSurface = false; + for ( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) { + collisionSurface = true; + } + } + + for ( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + // if this surface has no contents + if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + continue; + } + // if the model has a collision surface and this surface is not a collision surface + if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) { + continue; + } + // get max verts and edges + model->maxVertices += surf->geometry->numVerts; + model->maxEdges += surf->geometry->numIndexes; + } + + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t), TAG_COLLISION ); + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t), TAG_COLLISION ); + + // setup hash to speed up finding shared vertices and edges + SetupHash(); + + cm_vertexHash->ResizeIndex( model->maxVertices ); + cm_edgeHash->ResizeIndex( model->maxEdges ); + + ClearHash( bounds ); + + for ( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + // if this surface has no contents + if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + continue; + } + // if the model has a collision surface and this surface is not a collision surface + if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) { + continue; + } + + for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) { + w.Clear(); + w += surf->geometry->verts[ surf->geometry->indexes[ j + 2 ] ].xyz; + w += surf->geometry->verts[ surf->geometry->indexes[ j + 1 ] ].xyz; + w += surf->geometry->verts[ surf->geometry->indexes[ j + 0 ] ].xyz; + w.GetPlane( plane ); + plane = -plane; + PolygonFromWinding( model, &w, plane, surf->shader, 1 ); + } + } + + // create a BSP tree for the model + model->node = CreateAxialBSPTree( model, model->node ); + + model->isConvex = false; + + FinishModel( model ); + + // shutdown the hash + ShutdownHash(); + + WriteBinaryModel( model, generatedFileName, sourceTimeStamp ); + + return model; +} + +/* +================ +idCollisionModelManagerLocal::CollisionModelForMapEntity +================ +*/ +cm_model_t *idCollisionModelManagerLocal::CollisionModelForMapEntity( const idMapEntity *mapEnt ) { + + cm_model_t *model; + idBounds bounds; + const char *name; + int i, brushCount; + + // if the entity has no primitives + if ( mapEnt->GetNumPrimitives() < 1 ) { + return NULL; + } + + // get a name for the collision model + mapEnt->epairs.GetString( "model", "", &name ); + if ( !name[0] ) { + mapEnt->epairs.GetString( "name", "", &name ); + if ( !name[0] ) { + if ( !numModels ) { + // first model is always the world + name = "worldMap"; + } + else { + name = "unnamed inline model"; + } + } + } + + model = AllocModel(); + model->node = AllocNode( model, NODE_BLOCK_SIZE_SMALL ); + + CM_EstimateVertsAndEdges( mapEnt, &model->maxVertices, &model->maxEdges ); + model->numVertices = 0; + model->numEdges = 0; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t), TAG_COLLISION ); + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t), TAG_COLLISION ); + + cm_vertexHash->ResizeIndex( model->maxVertices ); + cm_edgeHash->ResizeIndex( model->maxEdges ); + + model->name = name; + model->isConvex = false; + + // convert brushes + for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) { + idMapPrimitive *mapPrim; + + mapPrim = mapEnt->GetPrimitive(i); + if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { + ConvertBrush( model, static_cast(mapPrim), i ); + continue; + } + } + + // create an axial bsp tree for the model if it has more than just a bunch brushes + brushCount = CM_CountNodeBrushes( model->node ); + if ( brushCount > 4 ) { + model->node = CreateAxialBSPTree( model, model->node ); + } else { + model->node->planeType = -1; + } + + // get bounds for hash + if ( brushCount ) { + CM_GetNodeBounds( &bounds, model->node ); + } else { + bounds[0].Set( -256, -256, -256 ); + bounds[1].Set( 256, 256, 256 ); + } + + // different models do not share edges and vertices with each other, so clear the hash + ClearHash( bounds ); + + // create polygons from patches and brushes + for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) { + idMapPrimitive *mapPrim; + + mapPrim = mapEnt->GetPrimitive(i); + if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) { + ConvertPatch( model, static_cast(mapPrim), i ); + continue; + } + if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { + ConvertBrushSides( model, static_cast(mapPrim), i ); + continue; + } + } + + FinishModel( model ); + + return model; +} + +/* +================ +idCollisionModelManagerLocal::FindModel +================ +*/ +cmHandle_t idCollisionModelManagerLocal::FindModel( const char *name ) { + int i; + + // check if this model is already loaded + for ( i = 0; i < numModels; i++ ) { + if ( !models[i]->name.Icmp( name ) ) { + break; + } + } + // if the model is already loaded + if ( i < numModels ) { + return i; + } + return -1; +} + +/* +================== +idCollisionModelManagerLocal::PrintModelInfo +================== +*/ +void idCollisionModelManagerLocal::PrintModelInfo( const cm_model_t *model ) { + common->Printf( "%6i vertices (%i KB)\n", model->numVertices, (model->numVertices * sizeof(cm_vertex_t))>>10 ); + common->Printf( "%6i edges (%i KB)\n", model->numEdges, (model->numEdges * sizeof(cm_edge_t))>>10 ); + common->Printf( "%6i polygons (%i KB)\n", model->numPolygons, model->polygonMemory>>10 ); + common->Printf( "%6i brushes (%i KB)\n", model->numBrushes, model->brushMemory>>10 ); + common->Printf( "%6i nodes (%i KB)\n", model->numNodes, (model->numNodes * sizeof(cm_node_t))>>10 ); + common->Printf( "%6i polygon refs (%i KB)\n", model->numPolygonRefs, (model->numPolygonRefs * sizeof(cm_polygonRef_t))>>10 ); + common->Printf( "%6i brush refs (%i KB)\n", model->numBrushRefs, (model->numBrushRefs * sizeof(cm_brushRef_t))>>10 ); + common->Printf( "%6i internal edges\n", model->numInternalEdges ); + common->Printf( "%6i sharp edges\n", model->numSharpEdges ); + common->Printf( "%6i contained polygons removed\n", model->numRemovedPolys ); + common->Printf( "%6i polygons merged\n", model->numMergedPolys ); + common->Printf( "%6i KB total memory used\n", model->usedMemory>>10 ); +} + +/* +================ +idCollisionModelManagerLocal::AccumulateModelInfo +================ +*/ +void idCollisionModelManagerLocal::AccumulateModelInfo( cm_model_t *model ) { + int i; + + memset( model, 0, sizeof( *model ) ); + // accumulate statistics of all loaded models + for ( i = 0; i < numModels; i++ ) { + model->numVertices += models[i]->numVertices; + model->numEdges += models[i]->numEdges; + model->numPolygons += models[i]->numPolygons; + model->polygonMemory += models[i]->polygonMemory; + model->numBrushes += models[i]->numBrushes; + model->brushMemory += models[i]->brushMemory; + model->numNodes += models[i]->numNodes; + model->numBrushRefs += models[i]->numBrushRefs; + model->numPolygonRefs += models[i]->numPolygonRefs; + model->numInternalEdges += models[i]->numInternalEdges; + model->numSharpEdges += models[i]->numSharpEdges; + model->numRemovedPolys += models[i]->numRemovedPolys; + model->numMergedPolys += models[i]->numMergedPolys; + model->usedMemory += models[i]->usedMemory; + } +} + +/* +================ +idCollisionModelManagerLocal::ModelInfo +================ +*/ +void idCollisionModelManagerLocal::ModelInfo( cmHandle_t model ) { + cm_model_t modelInfo; + + if ( model == -1 ) { + AccumulateModelInfo( &modelInfo ); + PrintModelInfo( &modelInfo ); + return; + } + if ( model < 0 || model > MAX_SUBMODELS || model > maxModels ) { + common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model handle\n" ); + return; + } + if ( !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model\n" ); + return; + } + + PrintModelInfo( models[model] ); +} + +/* +================ +idCollisionModelManagerLocal::ListModels +================ +*/ +void idCollisionModelManagerLocal::ListModels() { + int i, totalMemory; + + totalMemory = 0; + for ( i = 0; i < numModels; i++ ) { + common->Printf( "%4d: %5d KB %s\n", i, (models[i]->usedMemory>>10), models[i]->name.c_str() ); + totalMemory += models[i]->usedMemory; + } + common->Printf( "%4d KB in %d models\n", (totalMemory>>10), numModels ); +} + +/* +================ +idCollisionModelManagerLocal::BuildModels +================ +*/ +void idCollisionModelManagerLocal::BuildModels( const idMapFile *mapFile ) { + int i; + const idMapEntity *mapEnt; + + idTimer timer; + timer.Start(); + + if ( !LoadCollisionModelFile( mapFile->GetName(), mapFile->GetGeometryCRC() ) ) { + + if ( !mapFile->GetNumEntities() ) { + return; + } + + // load the .proc file bsp for data optimisation + LoadProcBSP( mapFile->GetName() ); + + // convert brushes and patches to collision data + for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + mapEnt = mapFile->GetEntity(i); + + if ( numModels >= MAX_SUBMODELS ) { + common->Error( "idCollisionModelManagerLocal::BuildModels: more than %d collision models", MAX_SUBMODELS ); + break; + } + models[numModels] = CollisionModelForMapEntity( mapEnt ); + if ( models[ numModels] ) { + numModels++; + } + } + + // free the proc bsp which is only used for data optimization + Mem_Free( procNodes ); + procNodes = NULL; + + // write the collision models to a file + WriteCollisionModelsToFile( mapFile->GetName(), 0, numModels, mapFile->GetGeometryCRC() ); + } + + timer.Stop(); + + // print statistics on collision data + cm_model_t model; + AccumulateModelInfo( &model ); + common->Printf( "collision data:\n" ); + common->Printf( "%6i models\n", numModels ); + PrintModelInfo( &model ); + common->Printf( "%.0f msec to load collision data.\n", timer.Milliseconds() ); +} + + +/* +================ +idCollisionModelManagerLocal::Preload +================ +*/ +void idCollisionModelManagerLocal::Preload( const char *mapName ) { + + if ( !preLoad_Collision.GetBool() ) { + return; + } + idStrStatic< MAX_OSPATH > manifestName = mapName; + manifestName.Replace( "game/", "maps/" ); + manifestName.Replace( "maps/maps/", "maps/" ); + manifestName.SetFileExtension( ".preload" ); + idPreloadManifest manifest; + manifest.LoadManifest( manifestName ); + if ( manifest.NumResources() >= 0 ) { + common->Printf( "Preloading collision models...\n" ); + int start = Sys_Milliseconds(); + int numLoaded = 0; + for ( int i = 0; i < manifest.NumResources(); i++ ) { + const preloadEntry_s & p = manifest.GetPreloadByIndex( i ); + if ( p.resType == PRELOAD_COLLISION ) { + LoadModel( p.resourceName ); + numLoaded++; + } + } + int end = Sys_Milliseconds(); + common->Printf( "%05d collision models preloaded ( or were already loaded ) in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 ); + common->Printf( "----------------------------------------\n" ); + } +} + +/* +================ +idCollisionModelManagerLocal::LoadMap +================ +*/ +void idCollisionModelManagerLocal::LoadMap( const idMapFile *mapFile ) { + + if ( mapFile == NULL ) { + common->Error( "idCollisionModelManagerLocal::LoadMap: NULL mapFile" ); + return; + } + + // check whether we can keep the current collision map based on the mapName and mapFileTime + if ( loaded ) { + if ( mapName.Icmp( mapFile->GetName() ) == 0 ) { + if ( mapFile->GetFileTime() == mapFileTime ) { + common->DPrintf( "Using loaded version\n" ); + return; + } + common->DPrintf( "Reloading modified map\n" ); + } + FreeMap(); + } + + // clear the collision map + Clear(); + + // models + maxModels = MAX_SUBMODELS; + numModels = 0; + models = (cm_model_t **) Mem_ClearedAlloc( (maxModels+1) * sizeof(cm_model_t *), TAG_COLLISION ); + + // setup hash to speed up finding shared vertices and edges + SetupHash(); + + common->UpdateLevelLoadPacifier(); + + // setup trace model structure + SetupTrmModelStructure(); + + common->UpdateLevelLoadPacifier(); + + // build collision models + BuildModels( mapFile ); + + common->UpdateLevelLoadPacifier(); + + // save name and time stamp + mapName = mapFile->GetName(); + mapFileTime = mapFile->GetFileTime(); + loaded = true; + + // shutdown the hash + ShutdownHash(); +} + +/* +=================== +idCollisionModelManagerLocal::GetModelName +=================== +*/ +const char *idCollisionModelManagerLocal::GetModelName( cmHandle_t model ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" ); + return ""; + } + return models[model]->name.c_str(); +} + +/* +=================== +idCollisionModelManagerLocal::GetModelBounds +=================== +*/ +bool idCollisionModelManagerLocal::GetModelBounds( cmHandle_t model, idBounds &bounds ) const { + + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" ); + return false; + } + + bounds = models[model]->bounds; + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelContents +=================== +*/ +bool idCollisionModelManagerLocal::GetModelContents( cmHandle_t model, int &contents ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelContents: invalid model handle\n" ); + return false; + } + + contents = models[model]->contents; + + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelVertex +=================== +*/ +bool idCollisionModelManagerLocal::GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid model handle\n" ); + return false; + } + + if ( vertexNum < 0 || vertexNum >= models[model]->numVertices ) { + common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid vertex number\n" ); + return false; + } + + vertex = models[model]->vertices[vertexNum].p; + + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelEdge +=================== +*/ +bool idCollisionModelManagerLocal::GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid model handle\n" ); + return false; + } + + edgeNum = abs( edgeNum ); + if ( edgeNum >= models[model]->numEdges ) { + common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid edge number\n" ); + return false; + } + + start = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[0]].p; + end = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[1]].p; + + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelPolygon +=================== +*/ +bool idCollisionModelManagerLocal::GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const { + int i, edgeNum; + cm_polygon_t *poly; + + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelPolygon: invalid model handle\n" ); + return false; + } + + poly = *reinterpret_cast(&polygonNum); + winding.Clear(); + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + winding += models[model]->vertices[ models[model]->edges[abs(edgeNum)].vertexNum[INT32_SIGNBITSET(edgeNum)] ].p; + } + + return true; +} + +/* +================== +idCollisionModelManagerLocal::LoadModel +================== +*/ +cmHandle_t idCollisionModelManagerLocal::LoadModel( const char *modelName ) { + int handle; + + handle = FindModel( modelName ); + if ( handle >= 0 ) { + return handle; + } + + if ( numModels >= MAX_SUBMODELS ) { + common->Error( "idCollisionModelManagerLocal::LoadModel: no free slots\n" ); + return 0; + } + + idStrStatic< MAX_OSPATH > generatedFileName = "generated/collision/"; + generatedFileName.AppendPath( modelName ); + generatedFileName.SetFileExtension( CMODEL_BINARYFILE_EXT ); + + ID_TIME_T sourceTimeStamp = fileSystem->GetTimestamp( modelName ); + + models[ numModels ] = LoadBinaryModel( generatedFileName, sourceTimeStamp ); + if ( models[ numModels ] != NULL ) { + numModels++; + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + // for resource gathering write this model to the preload file for this map + fileSystem->AddCollisionPreload( modelName ); + } + return ( numModels - 1 ); + } + + // try to load a .cm file + if ( LoadCollisionModelFile( modelName, 0 ) ) { + handle = FindModel( modelName ); + if ( handle >= 0 && handle < numModels ) { + cm_model_t * cm = models[ handle ]; + WriteBinaryModel( cm, generatedFileName, sourceTimeStamp ); + return handle; + } else { + common->Warning( "idCollisionModelManagerLocal::LoadModel: collision file for '%s' contains different model", modelName ); + } + } + + // try to load a .ASE or .LWO model and convert it to a collision model + models[ numModels ] = LoadRenderModel( modelName ); + if ( models[ numModels ] != NULL ) { + numModels++; + return ( numModels - 1 ); + } + + return 0; +} + +/* +================== +idCollisionModelManagerLocal::TrmFromModel_r +================== +*/ +bool idCollisionModelManagerLocal::TrmFromModel_r( idTraceModel &trm, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int i; + + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + + if ( p->checkcount == checkCount ) { + continue; + } + + p->checkcount = checkCount; + + if ( trm.numPolys >= MAX_TRACEMODEL_POLYS ) { + return false; + } + // copy polygon properties + trm.polys[ trm.numPolys ].bounds = p->bounds; + trm.polys[ trm.numPolys ].normal = p->plane.Normal(); + trm.polys[ trm.numPolys ].dist = p->plane.Dist(); + trm.polys[ trm.numPolys ].numEdges = p->numEdges; + // copy edge index + for ( i = 0; i < p->numEdges; i++ ) { + trm.polys[ trm.numPolys ].edges[ i ] = p->edges[ i ]; + } + trm.numPolys++; + } + if ( node->planeType == -1 ) { + break; + } + if ( !TrmFromModel_r( trm, node->children[1] ) ) { + return false; + } + node = node->children[0]; + } + return true; +} + +/* +================== +idCollisionModelManagerLocal::TrmFromModel + + NOTE: polygon merging can merge colinear edges and as such might cause dangling edges. +================== +*/ +bool idCollisionModelManagerLocal::TrmFromModel( const cm_model_t *model, idTraceModel &trm ) { + int i, j, numEdgeUsers[MAX_TRACEMODEL_EDGES+1]; + + // if the model has too many vertices to fit in a trace model + if ( model->numVertices > MAX_TRACEMODEL_VERTS ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many vertices.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + // plus one because the collision model accounts for the first unused edge + if ( model->numEdges > MAX_TRACEMODEL_EDGES+1 ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many edges.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + trm.type = TRM_CUSTOM; + trm.numVerts = 0; + trm.numEdges = 1; + trm.numPolys = 0; + trm.bounds.Clear(); + + // copy polygons + checkCount++; + if ( !TrmFromModel_r( trm, model->node ) ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many polygons.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + // copy vertices + for ( i = 0; i < model->numVertices; i++ ) { + trm.verts[ i ] = model->vertices[ i ].p; + trm.bounds.AddPoint( trm.verts[ i ] ); + } + trm.numVerts = model->numVertices; + + // copy edges + for ( i = 0; i < model->numEdges; i++ ) { + trm.edges[ i ].v[0] = model->edges[ i ].vertexNum[0]; + trm.edges[ i ].v[1] = model->edges[ i ].vertexNum[1]; + } + // minus one because the collision model accounts for the first unused edge + trm.numEdges = model->numEdges - 1; + + // each edge should be used exactly twice + memset( numEdgeUsers, 0, sizeof(numEdgeUsers) ); + for ( i = 0; i < trm.numPolys; i++ ) { + for ( j = 0; j < trm.polys[i].numEdges; j++ ) { + numEdgeUsers[ abs( trm.polys[i].edges[j] ) ]++; + } + } + for ( i = 1; i <= trm.numEdges; i++ ) { + if ( numEdgeUsers[i] != 2 ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has dangling edges, the model has to be an enclosed hull.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + } + + // assume convex + trm.isConvex = true; + // check if really convex + for ( i = 0; i < trm.numPolys; i++ ) { + // to be convex no vertices should be in front of any polygon plane + for ( j = 0; j < trm.numVerts; j++ ) { + if ( trm.polys[ i ].normal * trm.verts[ j ] - trm.polys[ i ].dist > 0.01f ) { + trm.isConvex = false; + break; + } + } + if ( j < trm.numVerts ) { + break; + } + } + + // offset to center of model + trm.offset = trm.bounds.GetCenter(); + + trm.GenerateEdgeNormals(); + + return true; +} + +/* +================== +idCollisionModelManagerLocal::TrmFromModel +================== +*/ +bool idCollisionModelManagerLocal::TrmFromModel( const char *modelName, idTraceModel &trm ) { + cmHandle_t handle; + + handle = LoadModel( modelName ); + if ( !handle ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s not found.\n", modelName ); + return false; + } + + return TrmFromModel( models[ handle ], trm ); +} diff --git a/neo/cm/CollisionModel_local.h b/neo/cm/CollisionModel_local.h new file mode 100644 index 00000000..863db98d --- /dev/null +++ b/neo/cm/CollisionModel_local.h @@ -0,0 +1,539 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "CollisionModel.h" + +#define MIN_NODE_SIZE 64.0f +#define MAX_NODE_POLYGONS 128 +#define CM_MAX_POLYGON_EDGES 64 +#define CIRCLE_APPROXIMATION_LENGTH 64.0f + +#define MAX_SUBMODELS 2048 +#define TRACE_MODEL_HANDLE MAX_SUBMODELS + +#define VERTEX_HASH_BOXSIZE (1<<6) // must be power of 2 +#define VERTEX_HASH_SIZE (VERTEX_HASH_BOXSIZE*VERTEX_HASH_BOXSIZE) +#define EDGE_HASH_SIZE (1<<14) + +#define NODE_BLOCK_SIZE_SMALL 8 +#define NODE_BLOCK_SIZE_LARGE 256 +#define REFERENCE_BLOCK_SIZE_SMALL 8 +#define REFERENCE_BLOCK_SIZE_LARGE 256 + +#define MAX_WINDING_LIST 128 // quite a few are generated at times +#define INTEGRAL_EPSILON 0.01f +#define VERTEX_EPSILON 0.1f +#define CHOP_EPSILON 0.1f + + +typedef struct cm_windingList_s { + int numWindings; // number of windings + idFixedWinding w[MAX_WINDING_LIST]; // windings + idVec3 normal; // normal for all windings + idBounds bounds; // bounds of all windings in list + idVec3 origin; // origin for radius + float radius; // radius relative to origin for all windings + int contents; // winding surface contents + int primitiveNum; // number of primitive the windings came from +} cm_windingList_t; + +/* +=============================================================================== + +Collision model + +=============================================================================== +*/ + +typedef struct cm_vertex_s { + idVec3 p; // vertex point + int checkcount; // for multi-check avoidance + unsigned long side; // each bit tells at which side this vertex passes one of the trace model edges + unsigned long sideSet; // each bit tells if sidedness for the trace model edge has been calculated yet +} cm_vertex_t; + +typedef struct cm_edge_s { + int checkcount; // for multi-check avoidance + unsigned short internal; // a trace model can never collide with internal edges + unsigned short numUsers; // number of polygons using this edge + unsigned long side; // each bit tells at which side of this edge one of the trace model vertices passes + unsigned long sideSet; // each bit tells if sidedness for the trace model vertex has been calculated yet + int vertexNum[2]; // start and end point of edge + idVec3 normal; // edge normal +} cm_edge_t; + +typedef struct cm_polygonBlock_s { + int bytesRemaining; + byte * next; +} cm_polygonBlock_t; + +typedef struct cm_polygon_s { + idBounds bounds; // polygon bounds + int checkcount; // for multi-check avoidance + int contents; // contents behind polygon + const idMaterial * material; // material + idPlane plane; // polygon plane + int numEdges; // number of edges + int edges[1]; // variable sized, indexes into cm_edge_t list +} cm_polygon_t; + +typedef struct cm_polygonRef_s { + cm_polygon_t * p; // pointer to polygon + struct cm_polygonRef_s *next; // next polygon in chain +} cm_polygonRef_t; + +typedef struct cm_polygonRefBlock_s { + cm_polygonRef_t * nextRef; // next polygon reference in block + struct cm_polygonRefBlock_s *next; // next block with polygon references +} cm_polygonRefBlock_t; + +typedef struct cm_brushBlock_s { + int bytesRemaining; + byte * next; +} cm_brushBlock_t; + +typedef struct cm_brush_s { + cm_brush_s() { + checkcount = 0; + contents = 0; + material = NULL; + primitiveNum = 0; + numPlanes = 0; + } + int checkcount; // for multi-check avoidance + idBounds bounds; // brush bounds + int contents; // contents of brush + const idMaterial * material; // material + int primitiveNum; // number of brush primitive + int numPlanes; // number of bounding planes + idPlane planes[1]; // variable sized +} cm_brush_t; + +typedef struct cm_brushRef_s { + cm_brush_t * b; // pointer to brush + struct cm_brushRef_s * next; // next brush in chain +} cm_brushRef_t; + +typedef struct cm_brushRefBlock_s { + cm_brushRef_t * nextRef; // next brush reference in block + struct cm_brushRefBlock_s *next; // next block with brush references +} cm_brushRefBlock_t; + +typedef struct cm_node_s { + int planeType; // node axial plane type + float planeDist; // node plane distance + cm_polygonRef_t * polygons; // polygons in node + cm_brushRef_t * brushes; // brushes in node + struct cm_node_s * parent; // parent of this node + struct cm_node_s * children[2]; // node children +} cm_node_t; + +typedef struct cm_nodeBlock_s { + cm_node_t * nextNode; // next node in block + struct cm_nodeBlock_s *next; // next block with nodes +} cm_nodeBlock_t; + +typedef struct cm_model_s { + idStr name; // model name + idBounds bounds; // model bounds + int contents; // all contents of the model ored together + bool isConvex; // set if model is convex + // model geometry + int maxVertices; // size of vertex array + int numVertices; // number of vertices + cm_vertex_t * vertices; // array with all vertices used by the model + int maxEdges; // size of edge array + int numEdges; // number of edges + cm_edge_t * edges; // array with all edges used by the model + cm_node_t * node; // first node of spatial subdivision + // blocks with allocated memory + cm_nodeBlock_t * nodeBlocks; // list with blocks of nodes + cm_polygonRefBlock_t * polygonRefBlocks; // list with blocks of polygon references + cm_brushRefBlock_t * brushRefBlocks; // list with blocks of brush references + cm_polygonBlock_t * polygonBlock; // memory block with all polygons + cm_brushBlock_t * brushBlock; // memory block with all brushes + // statistics + int numPolygons; + int polygonMemory; + int numBrushes; + int brushMemory; + int numNodes; + int numBrushRefs; + int numPolygonRefs; + int numInternalEdges; + int numSharpEdges; + int numRemovedPolys; + int numMergedPolys; + int usedMemory; +} cm_model_t; + +/* +=============================================================================== + +Data used during collision detection calculations + +=============================================================================== +*/ + +typedef struct cm_trmVertex_s { + int used; // true if this vertex is used for collision detection + idVec3 p; // vertex position + idVec3 endp; // end point of vertex after movement + int polygonSide; // side of polygon this vertex is on (rotational collision) + idPluecker pl; // pluecker coordinate for vertex movement + idVec3 rotationOrigin; // rotation origin for this vertex + idBounds rotationBounds; // rotation bounds for this vertex +} cm_trmVertex_t; + +typedef struct cm_trmEdge_s { + int used; // true when vertex is used for collision detection + idVec3 start; // start of edge + idVec3 end; // end of edge + int vertexNum[2]; // indexes into cm_traceWork_t->vertices + idPluecker pl; // pluecker coordinate for edge + idVec3 cross; // (z,-y,x) of cross product between edge dir and movement dir + idBounds rotationBounds; // rotation bounds for this edge + idPluecker plzaxis; // pluecker coordinate for rotation about the z-axis + unsigned short bitNum; // vertex bit number +} cm_trmEdge_t; + +typedef struct cm_trmPolygon_s { + int used; + idPlane plane; // polygon plane + int numEdges; // number of edges + int edges[MAX_TRACEMODEL_POLYEDGES]; // index into cm_traceWork_t->edges + idBounds rotationBounds; // rotation bounds for this polygon +} cm_trmPolygon_t; + +typedef struct cm_traceWork_s { + int numVerts; + cm_trmVertex_t vertices[MAX_TRACEMODEL_VERTS]; // trm vertices + int numEdges; + cm_trmEdge_t edges[MAX_TRACEMODEL_EDGES+1]; // trm edges + int numPolys; + cm_trmPolygon_t polys[MAX_TRACEMODEL_POLYS]; // trm polygons + cm_model_t *model; // model colliding with + idVec3 start; // start of trace + idVec3 end; // end of trace + idVec3 dir; // trace direction + idBounds bounds; // bounds of full trace + idBounds size; // bounds of transformed trm relative to start + idVec3 extents; // largest of abs(size[0]) and abs(size[1]) for BSP trace + int contents; // ignore polygons that do not have any of these contents flags + trace_t trace; // collision detection result + + bool rotation; // true if calculating rotational collision + bool pointTrace; // true if only tracing a point + bool positionTest; // true if not tracing but doing a position test + bool isConvex; // true if the trace model is convex + bool axisIntersectsTrm; // true if the rotation axis intersects the trace model + bool getContacts; // true if retrieving contacts + bool quickExit; // set to quickly stop the collision detection calculations + + idVec3 origin; // origin of rotation in model space + idVec3 axis; // rotation axis in model space + idMat3 matrix; // rotates axis of rotation to the z-axis + float angle; // angle for rotational collision + float maxTan; // max tangent of half the positive angle used instead of fraction + float radius; // rotation radius of trm start + idRotation modelVertexRotation; // inverse rotation for model vertices + + contactInfo_t *contacts; // array with contacts + int maxContacts; // max size of contact array + int numContacts; // number of contacts found + + idPlane heartPlane1; // polygons should be near anough the trace heart planes + float maxDistFromHeartPlane1; + idPlane heartPlane2; + float maxDistFromHeartPlane2; + idPluecker polygonEdgePlueckerCache[CM_MAX_POLYGON_EDGES]; + idPluecker polygonVertexPlueckerCache[CM_MAX_POLYGON_EDGES]; + idVec3 polygonRotationOriginCache[CM_MAX_POLYGON_EDGES]; +} cm_traceWork_t; + +/* +=============================================================================== + +Collision Map + +=============================================================================== +*/ + +typedef struct cm_procNode_s { + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid +} cm_procNode_t; + +class idCollisionModelManagerLocal : public idCollisionModelManager { +public: + // load collision models from a map file + void LoadMap( const idMapFile *mapFile ); + // frees all the collision models + void FreeMap(); + + void Preload( const char *mapName ); + // get clip handle for model + cmHandle_t LoadModel( const char *modelName ); + // sets up a trace model for collision with other trace models + cmHandle_t SetupTrmModel( const idTraceModel &trm, const idMaterial *material ); + // create trace model from a collision model, returns true if succesfull + bool TrmFromModel( const char *modelName, idTraceModel &trm ); + + // name of the model + const char * GetModelName( cmHandle_t model ) const; + // bounds of the model + bool GetModelBounds( cmHandle_t model, idBounds &bounds ) const; + // all contents flags of brushes and polygons ored together + bool GetModelContents( cmHandle_t model, int &contents ) const; + // get the vertex of a model + bool GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const; + // get the edge of a model + bool GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const; + // get the polygon of a model + bool GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const; + + // translates a trm and reports the first collision if any + void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // rotates a trm and reports the first collision if any + void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // returns the contents the trm is stuck in or 0 if the trm is in free space + int Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // stores all contact points of the trm with the model, returns the number of contacts + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // test collision detection + void DebugOutput( const idVec3 &origin ); + // draw a model + void DrawModel( cmHandle_t model, const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin, const float radius ); + // print model information, use -1 handle for accumulated model info + void ModelInfo( cmHandle_t model ); + // list all loaded models + void ListModels(); + // write a collision model file for the map entity + bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ); + +private: // CollisionMap_translate.cpp + int TranslateEdgeThroughEdge( idVec3 &cross, idPluecker &l1, idPluecker &l2, float *fraction ); + void TranslateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ); + void TranslateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int bitNum ); + void TranslatePointThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v ); + void TranslateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &endp, idPluecker &pl ); + bool TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ); + void SetupTranslationHeartPlanes( cm_traceWork_t *tw ); + void SetupTrm( cm_traceWork_t *tw, const idTraceModel *trm ); + +private: // CollisionMap_rotate.cpp + int CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb, + const idVec3 &vc, const idVec3 &vd, float tanHalfAngle, + idVec3 &collisionPoint, idVec3 &collisionNormal ); + int RotateEdgeThroughEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + const float minTan, float &tanHalfAngle ); + int EdgeFurthestFromEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + float &tanHalfAngle, float &dir ); + void RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ); + int RotatePointThroughPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, const float minTan, float &tanHalfAngle ); + int PointFurthestFromPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, float &tanHalfAngle, float &dir ); + int RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint, + const idPlane &plane, const float angle, const idVec3 &origin, + float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ); + void RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int vertexNum); + void RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, + cm_vertex_t *v, idVec3 &rotationOrigin ); + bool RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ); + void BoundsForRotation( const idVec3 &origin, const idVec3 &axis, const idVec3 &start, const idVec3 &end, idBounds &bounds ); + void Rotation180( trace_t *results, const idVec3 &rorg, const idVec3 &axis, + const float startAngle, const float endAngle, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ); + +private: // CollisionMap_contents.cpp + bool TestTrmVertsInBrush( cm_traceWork_t *tw, cm_brush_t *b ); + bool TestTrmInPolygon( cm_traceWork_t *tw, cm_polygon_t *p ); + cm_node_t * PointNode( const idVec3 &p, cm_model_t *model ); + int PointContents( const idVec3 p, cmHandle_t model ); + int TransformedPointContents( const idVec3 &p, cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ); + int ContentsTrm( trace_t *results, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + +private: // CollisionMap_trace.cpp + void TraceTrmThroughNode( cm_traceWork_t *tw, cm_node_t *node ); + void TraceThroughAxialBSPTree_r( cm_traceWork_t *tw, cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2); + void TraceThroughModel( cm_traceWork_t *tw ); + void RecurseProcBSP_r( trace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ); + +private: // CollisionMap_load.cpp + void Clear(); + void FreeTrmModelStructure(); + // model deallocation + void RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p ); + void RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b ); + void FreeNode( cm_node_t *node ); + void FreePolygonReference( cm_polygonRef_t *pref ); + void FreeBrushReference( cm_brushRef_t *bref ); + void FreePolygon( cm_model_t *model, cm_polygon_t *poly ); + void FreeBrush( cm_model_t *model, cm_brush_t *brush ); + void FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node ); + void FreeModel( cm_model_t *model ); + // merging polygons + void ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp ); + cm_polygon_t * TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ); + bool MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ); + void MergeTreePolygons( cm_model_t *model, cm_node_t *node ); + // finding internal edges + bool PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v ); + void FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ); + void FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ); + void FindInternalEdges( cm_model_t *model, cm_node_t *node ); + void FindContainedEdges( cm_model_t *model, cm_polygon_t *p ); + // loading of proc BSP tree + void ParseProcNodes( idLexer *src ); + void LoadProcBSP( const char *name ); + // removal of contained polygons + int R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ); + int ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents ); + void ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b ); + void R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node ); + idFixedWinding *WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int patch, cm_node_t *headNode ); + // creation of axial BSP tree + cm_model_t * AllocModel(); + cm_node_t * AllocNode( cm_model_t *model, int blockSize ); + cm_polygonRef_t*AllocPolygonReference( cm_model_t *model, int blockSize ); + cm_brushRef_t * AllocBrushReference( cm_model_t *model, int blockSize ); + cm_polygon_t * AllocPolygon( cm_model_t *model, int numEdges ); + cm_brush_t * AllocBrush( cm_model_t *model, int numPlanes ); + void AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p ); + void AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b ); + void SetupTrmModelStructure(); + void R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p ); + void R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b ); + cm_node_t * R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds ); + cm_node_t * CreateAxialBSPTree( cm_model_t *model, cm_node_t *node ); + // creation of raw polygons + void SetupHash(); + void ShutdownHash(); + void ClearHash( idBounds &bounds ); + int HashVec(const idVec3 &vec); + int GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum ); + int GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ); + void CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ); + void PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ); + void CalculateEdgeNormals( cm_model_t *model, cm_node_t *node ); + void CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum ); + void ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum ); + void ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ); + void ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ); + void PrintModelInfo( const cm_model_t *model ); + void AccumulateModelInfo( cm_model_t *model ); + void RemapEdges( cm_node_t *node, int *edgeRemap ); + void OptimizeArrays( cm_model_t *model ); + void FinishModel( cm_model_t *model ); + void BuildModels( const idMapFile *mapFile ); + cmHandle_t FindModel( const char *name ); + cm_model_t * CollisionModelForMapEntity( const idMapEntity *mapEnt ); // brush/patch model from .map + cm_model_t * LoadRenderModel( const char *fileName ); // ASE/LWO models + cm_model_t * LoadBinaryModel( const char *fileName, ID_TIME_T sourceTimeStamp ); + cm_model_t * LoadBinaryModelFromFile( idFile *fileIn, ID_TIME_T sourceTimeStamp ); + void WriteBinaryModel( cm_model_t *model, const char *fileName, ID_TIME_T sourceTimeStamp ); + void WriteBinaryModelToFile( cm_model_t *model, idFile *fileOut, ID_TIME_T sourceTimeStamp ); + bool TrmFromModel_r( idTraceModel &trm, cm_node_t *node ); + bool TrmFromModel( const cm_model_t *model, idTraceModel &trm ); + +private: // CollisionMap_files.cpp + // writing + void WriteNodes( idFile *fp, cm_node_t *node ); + int CountPolygonMemory( cm_node_t *node ) const; + void WritePolygons( idFile *fp, cm_node_t *node ); + int CountBrushMemory( cm_node_t *node ) const; + void WriteBrushes( idFile *fp, cm_node_t *node ); + void WriteCollisionModel( idFile *fp, cm_model_t *model ); + void WriteCollisionModelsToFile( const char *filename, int firstModel, int lastModel, unsigned int mapFileCRC ); + // loading + cm_node_t * ParseNodes( idLexer *src, cm_model_t *model, cm_node_t *parent ); + void ParseVertices( idLexer *src, cm_model_t *model ); + void ParseEdges( idLexer *src, cm_model_t *model ); + void ParsePolygons( idLexer *src, cm_model_t *model ); + void ParseBrushes( idLexer *src, cm_model_t *model ); + cm_model_t * ParseCollisionModel( idLexer *src ); + bool LoadCollisionModelFile( const char *name, unsigned int mapFileCRC ); + +private: // CollisionMap_debug + int ContentsFromString( const char *string ) const; + const char * StringFromContents( const int contents ) const; + void DrawEdge( cm_model_t *model, int edgeNum, const idVec3 &origin, const idMat3 &axis ); + void DrawPolygon( cm_model_t *model, cm_polygon_t *p, const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin ); + void DrawNodePolygons( cm_model_t *model, cm_node_t *node, const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin, const float radius ); + +private: // collision map data + idStr mapName; + ID_TIME_T mapFileTime; + int loaded; + // for multi-check avoidance + int checkCount; + // models + int maxModels; + int numModels; + cm_model_t ** models; + // polygons and brush for trm model + cm_polygonRef_t*trmPolygons[MAX_TRACEMODEL_POLYS]; + cm_brushRef_t * trmBrushes[1]; + const idMaterial *trmMaterial; + // for data pruning + int numProcNodes; + cm_procNode_t * procNodes; + // for retrieving contact points + bool getContacts; + contactInfo_t * contacts; + int maxContacts; + int numContacts; +}; + +// for debugging +extern idCVar cm_debugCollision; diff --git a/neo/cm/CollisionModel_rotate.cpp b/neo/cm/CollisionModel_rotate.cpp new file mode 100644 index 00000000..ee1e8f24 --- /dev/null +++ b/neo/cm/CollisionModel_rotate.cpp @@ -0,0 +1,1695 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Collision detection for rotational motion + +=============================================================================== +*/ + +// epsilon for round-off errors in epsilon calculations +#define CM_PL_RANGE_EPSILON 1e-4f +// if the collision point is this close to the rotation axis it is not considered a collision +#define ROTATION_AXIS_EPSILON (CM_CLIP_EPSILON*0.25f) + + +/* +================ +CM_RotatePoint + + rotates a point about an arbitrary axis using the tangent of half the rotation angle +================ +*/ +void CM_RotatePoint( idVec3 &point, const idVec3 &origin, const idVec3 &axis, const float tanHalfAngle ) { + double d, t, s, c; + idVec3 proj, v1, v2; + + point -= origin; + proj = axis * ( point * axis ); + v1 = point - proj; + v2 = axis.Cross( v1 ); + + // r = tan( a / 2 ); + // sin(a) = 2*r/(1+r*r); + // cos(a) = (1-r*r)/(1+r*r); + t = tanHalfAngle * tanHalfAngle; + d = 1.0f / ( 1.0f + t ); + s = 2.0f * tanHalfAngle * d; + c = ( 1.0f - t ) * d; + + point = v1 * c - v2 * s + proj + origin; +} + +/* +================ +CM_RotateEdge + + rotates an edge about an arbitrary axis using the tangent of half the rotation angle +================ +*/ +void CM_RotateEdge( idVec3 &start, idVec3 &end, const idVec3 &origin, const idVec3 &axis, const float tanHalfAngle ) { + double d, t, s, c; + idVec3 proj, v1, v2; + + // r = tan( a / 2 ); + // sin(a) = 2*r/(1+r*r); + // cos(a) = (1-r*r)/(1+r*r); + t = tanHalfAngle * tanHalfAngle; + d = 1.0f / ( 1.0f + t ); + s = 2.0f * tanHalfAngle * d; + c = ( 1.0f - t ) * d; + + start -= origin; + proj = axis * ( start * axis ); + v1 = start - proj; + v2 = axis.Cross( v1 ); + start = v1 * c - v2 * s + proj + origin; + + end -= origin; + proj = axis * ( end * axis ); + v1 = end - proj; + v2 = axis.Cross( v1 ); + end = v1 * c - v2 * s + proj + origin; +} + +/* +================ +idCollisionModelManagerLocal::CollisionBetweenEdgeBounds + + verifies if the collision of two edges occurs between the edge bounds + also calculates the collision point and collision plane normal if the collision occurs between the bounds +================ +*/ +int idCollisionModelManagerLocal::CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb, + const idVec3 &vc, const idVec3 &vd, float tanHalfAngle, + idVec3 &collisionPoint, idVec3 &collisionNormal ) { + float d1, d2, d; + idVec3 at, bt, dir, dir1, dir2; + idPluecker pl1, pl2; + + at = va; + bt = vb; + if ( tanHalfAngle != 0.0f ) { + CM_RotateEdge( at, bt, tw->origin, tw->axis, tanHalfAngle ); + } + + dir1 = (at - tw->origin).Cross( tw->axis ); + dir2 = (bt - tw->origin).Cross( tw->axis ); + if ( dir1 * dir1 > dir2 * dir2 ) { + dir = dir1; + } + else { + dir = dir2; + } + if ( tw->angle < 0.0f ) { + dir = -dir; + } + + pl1.FromLine( at, bt ); + pl2.FromRay( vc, dir ); + d1 = pl1.PermutedInnerProduct( pl2 ); + pl2.FromRay( vd, dir ); + d2 = pl1.PermutedInnerProduct( pl2 ); + if ( ( d1 > 0.0f && d2 > 0.0f ) || ( d1 < 0.0f && d2 < 0.0f ) ) { + return false; + } + + pl1.FromLine( vc, vd ); + pl2.FromRay( at, dir ); + d1 = pl1.PermutedInnerProduct( pl2 ); + pl2.FromRay( bt, dir ); + d2 = pl1.PermutedInnerProduct( pl2 ); + if ( ( d1 > 0.0f && d2 > 0.0f ) || ( d1 < 0.0f && d2 < 0.0f ) ) { + return false; + } + + // collision point on the edge at-bt + dir1 = (vd - vc).Cross( dir ); + d = dir1 * vc; + d1 = dir1 * at - d; + d2 = dir1 * bt - d; + if ( d1 == d2 ) { + return false; + } + collisionPoint = at + ( d1 / (d1 - d2) ) * ( bt - at ); + + // normal is cross product of the rotated edge va-vb and the edge vc-vd + collisionNormal.Cross( bt-at, vd-vc ); + + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotateEdgeThroughEdge + + calculates the tangent of half the rotation angle at which the edges collide +================ +*/ +int idCollisionModelManagerLocal::RotateEdgeThroughEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + const float minTan, float &tanHalfAngle ) { + double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 ct, dt; + idPluecker pl2; + + /* + + a = start of line being rotated + b = end of line being rotated + pl1 = pluecker coordinate for line (a - b) + pl2 = pluecker coordinate for edge we might collide with (c - d) + t = rotation angle around the z-axis + solve pluecker inner product for t of rotating line a-b and line l2 + + // start point of rotated line during rotation + an[0] = a[0] * cos(t) + a[1] * sin(t) + an[1] = a[0] * -sin(t) + a[1] * cos(t) + an[2] = a[2]; + // end point of rotated line during rotation + bn[0] = b[0] * cos(t) + b[1] * sin(t) + bn[1] = b[0] * -sin(t) + b[1] * cos(t) + bn[2] = b[2]; + + pl1[0] = a[0] * b[1] - b[0] * a[1]; + pl1[1] = a[0] * b[2] - b[0] * a[2]; + pl1[2] = a[0] - b[0]; + pl1[3] = a[1] * b[2] - b[1] * a[2]; + pl1[4] = a[2] - b[2]; + pl1[5] = b[1] - a[1]; + + v[0] = (a[0] * cos(t) + a[1] * sin(t)) * (b[0] * -sin(t) + b[1] * cos(t)) - (b[0] * cos(t) + b[1] * sin(t)) * (a[0] * -sin(t) + a[1] * cos(t)); + v[1] = (a[0] * cos(t) + a[1] * sin(t)) * b[2] - (b[0] * cos(t) + b[1] * sin(t)) * a[2]; + v[2] = (a[0] * cos(t) + a[1] * sin(t)) - (b[0] * cos(t) + b[1] * sin(t)); + v[3] = (a[0] * -sin(t) + a[1] * cos(t)) * b[2] - (b[0] * -sin(t) + b[1] * cos(t)) * a[2]; + v[4] = a[2] - b[2]; + v[5] = (b[0] * -sin(t) + b[1] * cos(t)) - (a[0] * -sin(t) + a[1] * cos(t)); + + pl2[0] * v[4] + pl2[1] * v[5] + pl2[2] * v[3] + pl2[4] * v[0] + pl2[5] * v[1] + pl2[3] * v[2] = 0; + + v[0] = (a[0] * cos(t) + a[1] * sin(t)) * (b[0] * -sin(t) + b[1] * cos(t)) - (b[0] * cos(t) + b[1] * sin(t)) * (a[0] * -sin(t) + a[1] * cos(t)); + v[0] = (a[1] * b[1] - a[0] * b[0]) * cos(t) * sin(t) + (a[0] * b[1] + a[1] * b[0] * cos(t)^2) - (a[1] * b[0]) - ((b[1] * a[1] - b[0] * a[0]) * cos(t) * sin(t) + (b[0] * a[1] + b[1] * a[0]) * cos(t)^2 - (b[1] * a[0])) + v[0] = - (a[1] * b[0]) - ( - (b[1] * a[0])) + v[0] = (b[1] * a[0]) - (a[1] * b[0]) + + v[0] = (a[0]*b[1]) - (a[1]*b[0]); + v[1] = (a[0]*b[2] - b[0]*a[2]) * cos(t) + (a[1]*b[2] - b[1]*a[2]) * sin(t); + v[2] = (a[0]-b[0]) * cos(t) + (a[1]-b[1]) * sin(t); + v[3] = (b[0]*a[2] - a[0]*b[2]) * sin(t) + (a[1]*b[2] - b[1]*a[2]) * cos(t); + v[4] = a[2] - b[2]; + v[5] = (a[0]-b[0]) * sin(t) + (b[1]-a[1]) * cos(t); + + v[0] = (a[0]*b[1]) - (a[1]*b[0]); + v[1] = (a[0]*b[2] - b[0]*a[2]) * cos(t) + (a[1]*b[2] - b[1]*a[2]) * sin(t); + v[2] = (a[0]-b[0]) * cos(t) - (b[1]-a[1]) * sin(t); + v[3] = (a[0]*b[2] - b[0]*a[2]) * -sin(t) + (a[1]*b[2] - b[1]*a[2]) * cos(t); + v[4] = a[2] - b[2]; + v[5] = (a[0]-b[0]) * sin(t) + (b[1]-a[1]) * cos(t); + + v[0] = pl1[0]; + v[1] = pl1[1] * cos(t) + pl1[3] * sin(t); + v[2] = pl1[2] * cos(t) - pl1[5] * sin(t); + v[3] = pl1[3] * cos(t) - pl1[1] * sin(t); + v[4] = pl1[4]; + v[5] = pl1[5] * cos(t) + pl1[2] * sin(t); + + pl2[0] * v[4] + pl2[1] * v[5] + pl2[2] * v[3] + pl2[4] * v[0] + pl2[5] * v[1] + pl2[3] * v[2] = 0; + + 0 = pl2[0] * pl1[4] + + pl2[1] * (pl1[5] * cos(t) + pl1[2] * sin(t)) + + pl2[2] * (pl1[3] * cos(t) - pl1[1] * sin(t)) + + pl2[4] * pl1[0] + + pl2[5] * (pl1[1] * cos(t) + pl1[3] * sin(t)) + + pl2[3] * (pl1[2] * cos(t) - pl1[5] * sin(t)); + + v2 * cos(t) + v1 * sin(t) + v0 = 0; + + // rotation about the z-axis + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + // rotation about the x-axis + //v0 = pl2[3] * pl1[2] + pl2[2] * pl1[3]; + //v1 = -pl2[5] * pl1[0] + pl2[4] * pl1[1] - pl2[1] * pl1[4] + pl2[0] * pl1[5]; + //v2 = pl2[4] * pl1[0] + pl2[5] * pl1[1] + pl2[0] * pl1[4] + pl2[1] * pl1[5]; + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + v1 * 2 * r / (1 + r*r) + v2 * (1 - r*r) / (1 + r*r) + v0 = 0 + (v1 * 2 * r + v2 * (1 - r*r)) / (1 + r*r) = -v0 + (v1 * 2 * r + v2 - v2 * r*r) / (1 + r*r) = -v0 + v1 * 2 * r + v2 - v2 * r*r = -v0 * (1 + r*r) + v1 * 2 * r + v2 - v2 * r*r = -v0 + -v0 * r*r + (v0 - v2) * r * r + (2 * v1) * r + (v0 + v2) = 0; + + MrE gives Pluecker a banana.. good monkey + + */ + + tanHalfAngle = tw->maxTan; + + // transform rotation axis to z-axis + ct = (vc - tw->origin) * tw->matrix; + dt = (vd - tw->origin) * tw->matrix; + + pl2.FromLine( ct, dt ); + + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + a = v0 - v2; + b = v1; + c = v0 + v2; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( tw->angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + // get smallest tangent for which a collision occurs + if ( frac1 >= minTan && frac1 < tanHalfAngle ) { + tanHalfAngle = frac1; + } + if ( frac2 >= minTan && frac2 < tanHalfAngle ) { + tanHalfAngle = frac2; + } + + if ( tw->angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::EdgeFurthestFromEdge + + calculates the direction of motion at the initial position, where dir < 0 means the edges move towards each other + if the edges move away from each other the tangent of half the rotation angle at which + the edges are furthest apart is also calculated +================ +*/ +int idCollisionModelManagerLocal::EdgeFurthestFromEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + float &tanHalfAngle, float &dir ) { + double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 ct, dt; + idPluecker pl2; + + /* + + v2 * cos(t) + v1 * sin(t) + v0 = 0; + + // rotation about the z-axis + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + derivative: + v1 * cos(t) - v2 * sin(t) = 0; + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + -v2 * 2 * r / (1 + r*r) + v1 * (1 - r*r)/(1+r*r); + -v2 * 2 * r + v1 * (1 - r*r) / (1 + r*r) = 0; + -v2 * 2 * r + v1 * (1 - r*r) = 0; + (-v1) * r * r + (-2 * v2) * r + (v1) = 0; + + */ + + tanHalfAngle = 0.0f; + + // transform rotation axis to z-axis + ct = (vc - tw->origin) * tw->matrix; + dt = (vd - tw->origin) * tw->matrix; + + pl2.FromLine( ct, dt ); + + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + // get the direction of motion at the initial position + c = v0 + v2; + if ( tw->angle > 0.0f ) { + if ( c > 0.0f ) { + dir = v1; + } + else { + dir = -v1; + } + } + else { + if ( c > 0.0f ) { + dir = -v1; + } + else { + dir = v1; + } + } + // negative direction means the edges move towards each other at the initial position + if ( dir <= 0.0f ) { + return true; + } + + a = -v1; + b = -v2; + c = v1; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( tw->angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + if ( frac1 < 0.0f && frac2 < 0.0f ) { + return false; + } + + if ( frac1 > frac2 ) { + tanHalfAngle = frac1; + } + else { + tanHalfAngle = frac2; + } + + if ( tw->angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) { + int i, j, edgeNum; + float f1, f2, startTan, dir, tanHalfAngle; + cm_edge_t *edge; + cm_vertex_t *v1, *v2; + idVec3 collisionPoint, collisionNormal, origin, epsDir; + idPluecker epsPl; + idBounds bounds; + + // if the trm is convex and the rotation axis intersects the trm + if ( tw->isConvex && tw->axisIntersectsTrm ) { + // if both points are behind the polygon the edge cannot collide within a 180 degrees rotation + if ( tw->vertices[trmEdge->vertexNum[0]].polygonSide & tw->vertices[trmEdge->vertexNum[1]].polygonSide ) { + return; + } + } + + // if the trace model edge rotation bounds do not intersect the polygon bounds + if ( !trmEdge->rotationBounds.IntersectsBounds( poly->bounds ) ) { + return; + } + + // edge rotation bounds should cross polygon plane + if ( trmEdge->rotationBounds.PlaneSide( poly->plane ) != SIDE_CROSS ) { + return; + } + + // check edges for a collision + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + + // if this edge is already checked + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + + // can never collide with internal edges + if ( edge->internal ) { + continue; + } + + v1 = tw->model->vertices + edge->vertexNum[INT32_SIGNBITSET( edgeNum )]; + v2 = tw->model->vertices + edge->vertexNum[INT32_SIGNBITNOTSET( edgeNum )]; + + // edge bounds + for ( j = 0; j < 3; j++ ) { + if ( v1->p[j] > v2->p[j] ) { + bounds[0][j] = v2->p[j]; + bounds[1][j] = v1->p[j]; + } + else { + bounds[0][j] = v1->p[j]; + bounds[1][j] = v2->p[j]; + } + } + + // if the trace model edge rotation bounds do not intersect the polygon edge bounds + if ( !trmEdge->rotationBounds.IntersectsBounds( bounds ) ) { + continue; + } + + f1 = trmEdge->pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ); + + // pluecker coordinate for epsilon expanded edge + epsDir = edge->normal * (CM_CLIP_EPSILON+CM_PL_RANGE_EPSILON); + epsPl.FromLine( tw->model->vertices[edge->vertexNum[0]].p + epsDir, + tw->model->vertices[edge->vertexNum[1]].p + epsDir ); + + f2 = trmEdge->pl.PermutedInnerProduct( epsPl ); + + // if the rotating edge is inbetween the polygon edge and the epsilon expanded edge + if ( ( f1 < 0.0f && f2 > 0.0f ) || ( f1 > 0.0f && f2 < 0.0f ) ) { + + if ( !EdgeFurthestFromEdge( tw, trmEdge->plzaxis, v1->p, v2->p, startTan, dir ) ) { + continue; + } + + if ( dir <= 0.0f ) { + // moving towards the polygon edge so stop immediately + tanHalfAngle = 0.0f; + } + else if ( idMath::Fabs( startTan ) >= tw->maxTan ) { + // never going to get beyond the start tangent during the current rotation + continue; + } + else { + // collide with the epsilon expanded edge + if ( !RotateEdgeThroughEdge(tw, trmEdge->plzaxis, v1->p + epsDir, v2->p + epsDir, idMath::Fabs( startTan ), tanHalfAngle ) ) { + tanHalfAngle = startTan; + } + } + } + else { + // collide with the epsilon expanded edge + epsDir = edge->normal * CM_CLIP_EPSILON; + if ( !RotateEdgeThroughEdge(tw, trmEdge->plzaxis, v1->p + epsDir, v2->p + epsDir, 0.0f, tanHalfAngle ) ) { + continue; + } + } + + if ( idMath::Fabs( tanHalfAngle ) >= tw->maxTan ) { + continue; + } + + // check if the collision is between the edge bounds + if ( !CollisionBetweenEdgeBounds( tw, trmEdge->start, trmEdge->end, v1->p, v2->p, + tanHalfAngle, collisionPoint, collisionNormal ) ) { + continue; + } + + // allow rotation if the rotation axis goes through the collisionPoint + origin = tw->origin + tw->axis * ( tw->axis * ( collisionPoint - tw->origin ) ); + if ( ( collisionPoint - origin ).LengthSqr() < ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) { + continue; + } + + // fill in trace structure + tw->maxTan = idMath::Fabs( tanHalfAngle ); + tw->trace.c.normal = collisionNormal; + tw->trace.c.normal.Normalize(); + tw->trace.c.dist = tw->trace.c.normal * v1->p; + // make sure the collision plane faces the trace model + if ( (tw->trace.c.normal * trmEdge->start) - tw->trace.c.dist < 0 ) { + tw->trace.c.normal = -tw->trace.c.normal; + tw->trace.c.dist = -tw->trace.c.dist; + } + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.modelFeature = edgeNum; + tw->trace.c.trmFeature = trmEdge - tw->edges; + tw->trace.c.point = collisionPoint; + // if no collision can be closer + if ( tw->maxTan == 0.0f ) { + break; + } + } +} + +/* +================ +idCollisionModelManagerLocal::RotatePointThroughPlane + + calculates the tangent of half the rotation angle at which the point collides with the plane +================ +*/ +int idCollisionModelManagerLocal::RotatePointThroughPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, const float minTan, float &tanHalfAngle ) { + double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 p, normal; + + /* + + p[0] = point[0] * cos(t) + point[1] * sin(t) + p[1] = point[0] * -sin(t) + point[1] * cos(t) + p[2] = point[2]; + + normal[0] * (p[0] * cos(t) + p[1] * sin(t)) + + normal[1] * (p[0] * -sin(t) + p[1] * cos(t)) + + normal[2] * p[2] + dist = 0 + + normal[0] * p[0] * cos(t) + normal[0] * p[1] * sin(t) + + -normal[1] * p[0] * sin(t) + normal[1] * p[1] * cos(t) + + normal[2] * p[2] + dist = 0 + + v2 * cos(t) + v1 * sin(t) + v0 + + // rotation about the z-axis + v0 = normal[2] * p[2] + dist + v1 = normal[0] * p[1] - normal[1] * p[0] + v2 = normal[0] * p[0] + normal[1] * p[1] + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + v1 * 2 * r / (1 + r*r) + v2 * (1 - r*r) / (1 + r*r) + v0 = 0 + (v1 * 2 * r + v2 * (1 - r*r)) / (1 + r*r) = -v0 + (v1 * 2 * r + v2 - v2 * r*r) / (1 + r*r) = -v0 + v1 * 2 * r + v2 - v2 * r*r = -v0 * (1 + r*r) + v1 * 2 * r + v2 - v2 * r*r = -v0 + -v0 * r*r + (v0 - v2) * r * r + (2 * v1) * r + (v0 + v2) = 0; + + */ + + tanHalfAngle = tw->maxTan; + + // transform rotation axis to z-axis + p = (point - tw->origin) * tw->matrix; + d = plane[3] + plane.Normal() * tw->origin; + normal = plane.Normal() * tw->matrix; + + v0 = normal[2] * p[2] + d; + v1 = normal[0] * p[1] - normal[1] * p[0]; + v2 = normal[0] * p[0] + normal[1] * p[1]; + + a = v0 - v2; + b = v1; + c = v0 + v2; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + // get smallest tangent for which a collision occurs + if ( frac1 >= minTan && frac1 < tanHalfAngle ) { + tanHalfAngle = frac1; + } + if ( frac2 >= minTan && frac2 < tanHalfAngle ) { + tanHalfAngle = frac2; + } + + if ( angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::PointFurthestFromPlane + + calculates the direction of motion at the initial position, where dir < 0 means the point moves towards the plane + if the point moves away from the plane the tangent of half the rotation angle at which + the point is furthest away from the plane is also calculated +================ +*/ +int idCollisionModelManagerLocal::PointFurthestFromPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, float &tanHalfAngle, float &dir ) { + + double v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 p, normal; + + /* + + v2 * cos(t) + v1 * sin(t) + v0 = 0; + + // rotation about the z-axis + v0 = normal[2] * p[2] + dist + v1 = normal[0] * p[1] - normal[1] * p[0] + v2 = normal[0] * p[0] + normal[1] * p[1] + + derivative: + v1 * cos(t) - v2 * sin(t) = 0; + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + -v2 * 2 * r / (1 + r*r) + v1 * (1 - r*r)/(1+r*r); + -v2 * 2 * r + v1 * (1 - r*r) / (1 + r*r) = 0; + -v2 * 2 * r + v1 * (1 - r*r) = 0; + (-v1) * r * r + (-2 * v2) * r + (v1) = 0; + + */ + + tanHalfAngle = 0.0f; + + // transform rotation axis to z-axis + p = (point - tw->origin) * tw->matrix; + normal = plane.Normal() * tw->matrix; + + v1 = normal[0] * p[1] - normal[1] * p[0]; + v2 = normal[0] * p[0] + normal[1] * p[1]; + + // the point will always start at the front of the plane, therefore v0 + v2 > 0 is always true + if ( angle < 0.0f ) { + dir = -v1; + } + else { + dir = v1; + } + // negative direction means the point moves towards the plane at the initial position + if ( dir <= 0.0f ) { + return true; + } + + a = -v1; + b = -v2; + c = v1; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + if ( frac1 < 0.0f && frac2 < 0.0f ) { + return false; + } + + if ( frac1 > frac2 ) { + tanHalfAngle = frac1; + } + else { + tanHalfAngle = frac2; + } + + if ( angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane +================ +*/ +int idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint, + const idPlane &plane, const float angle, const idVec3 &origin, + float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ) { + float d, dir, startTan; + idVec3 vec, startDir; + idPlane epsPlane; + + // epsilon expanded plane + epsPlane = plane; + epsPlane.SetDist( epsPlane.Dist() + CM_CLIP_EPSILON ); + + // if the rotation sphere at the rotation origin is too far away from the polygon plane + d = epsPlane.Distance( origin ); + vec = point - origin; + if ( d * d > vec * vec ) { + return false; + } + + // calculate direction of motion at vertex start position + startDir = ( point - origin ).Cross( tw->axis ); + if ( angle < 0.0f ) { + startDir = -startDir; + } + // if moving away from plane at start position + if ( startDir * epsPlane.Normal() >= 0.0f ) { + // if end position is outside epsilon range + d = epsPlane.Distance( endPoint ); + if ( d >= 0.0f ) { + return false; // no collision + } + // calculate direction of motion at vertex end position + endDir = ( endPoint - origin ).Cross( tw->axis ); + if ( angle < 0.0f ) { + endDir = -endDir; + } + // if also moving away from plane at end position + if ( endDir * epsPlane.Normal() > 0.0f ) { + return false; // no collision + } + } + + // if the start position is in the epsilon range + d = epsPlane.Distance( point ); + if ( d <= CM_PL_RANGE_EPSILON ) { + + // calculate tangent of half the rotation for which the vertex is furthest away from the plane + if ( !PointFurthestFromPlane( tw, point, plane, angle, startTan, dir ) ) { + return false; + } + + if ( dir <= 0.0f ) { + // moving towards the polygon plane so stop immediately + tanHalfAngle = 0.0f; + } + else if ( idMath::Fabs( startTan ) >= tw->maxTan ) { + // never going to get beyond the start tangent during the current rotation + return false; + } + else { + // calculate collision with epsilon expanded plane + if ( !RotatePointThroughPlane( tw, point, epsPlane, angle, idMath::Fabs( startTan ), tanHalfAngle ) ) { + tanHalfAngle = startTan; + } + } + } + else { + // calculate collision with epsilon expanded plane + if ( !RotatePointThroughPlane( tw, point, epsPlane, angle, 0.0f, tanHalfAngle ) ) { + return false; + } + } + + // calculate collision point + collisionPoint = point; + if ( tanHalfAngle != 0.0f ) { + CM_RotatePoint( collisionPoint, tw->origin, tw->axis, tanHalfAngle ); + } + // calculate direction of motion at collision point + endDir = ( collisionPoint - origin ).Cross( tw->axis ); + if ( angle < 0.0f ) { + endDir = -endDir; + } + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int vertexNum ) { + int i; + float tanHalfAngle; + idVec3 endDir, collisionPoint; + idPluecker pl; + + // if the trm vertex is behind the polygon plane it cannot collide with the polygon within a 180 degrees rotation + if ( tw->isConvex && tw->axisIntersectsTrm && v->polygonSide ) { + return; + } + + // if the trace model vertex rotation bounds do not intersect the polygon bounds + if ( !v->rotationBounds.IntersectsBounds( poly->bounds ) ) { + return; + } + + // vertex rotation bounds should cross polygon plane + if ( v->rotationBounds.PlaneSide( poly->plane ) != SIDE_CROSS ) { + return; + } + + // rotate the vertex through the epsilon plane + if ( !RotatePointThroughEpsilonPlane( tw, v->p, v->endp, poly->plane, tw->angle, v->rotationOrigin, + tanHalfAngle, collisionPoint, endDir ) ) { + return; + } + + if ( idMath::Fabs( tanHalfAngle ) < tw->maxTan ) { + // verify if 'collisionPoint' moving along 'endDir' moves between polygon edges + pl.FromRay( collisionPoint, endDir ); + for ( i = 0; i < poly->numEdges; i++ ) { + if ( poly->edges[i] < 0 ) { + if ( pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ) > 0.0f ) { + return; + } + } + else { + if ( pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ) < 0.0f ) { + return; + } + } + } + tw->maxTan = idMath::Fabs( tanHalfAngle ); + // collision plane is the polygon plane + tw->trace.c.normal = poly->plane.Normal(); + tw->trace.c.dist = poly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.modelFeature = *reinterpret_cast(&poly); + tw->trace.c.trmFeature = v - tw->vertices; + tw->trace.c.point = collisionPoint; + } +} + +/* +================ +idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon +================ +*/ +void idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &rotationOrigin ) { + int i, edgeNum; + float tanHalfAngle; + idVec3 dir, endp, endDir, collisionPoint; + idPluecker pl; + cm_trmEdge_t *edge; + + // if the polygon vertex is behind the trm plane it cannot collide with the trm polygon within a 180 degrees rotation + if ( tw->isConvex && tw->axisIntersectsTrm && trmpoly->plane.Distance( v->p ) < 0.0f ) { + return; + } + + // if the model vertex is outside the trm polygon rotation bounds + if ( !trmpoly->rotationBounds.ContainsPoint( v->p ) ) { + return; + } + + // if the rotation axis goes through the polygon vertex + dir = v->p - rotationOrigin; + if ( dir * dir < ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) { + return; + } + + // calculate vertex end position + endp = v->p; + tw->modelVertexRotation.RotatePoint( endp ); + + // rotate the vertex through the epsilon plane + if ( !RotatePointThroughEpsilonPlane( tw, v->p, endp, trmpoly->plane, -tw->angle, rotationOrigin, + tanHalfAngle, collisionPoint, endDir ) ) { + return; + } + + if ( idMath::Fabs( tanHalfAngle ) < tw->maxTan ) { + // verify if 'collisionPoint' moving along 'endDir' moves between polygon edges + pl.FromRay( collisionPoint, endDir ); + for ( i = 0; i < trmpoly->numEdges; i++ ) { + edgeNum = trmpoly->edges[i]; + edge = tw->edges + abs(edgeNum); + if ( edgeNum < 0 ) { + if ( pl.PermutedInnerProduct( edge->pl ) > 0.0f ) { + return; + } + } + else { + if ( pl.PermutedInnerProduct( edge->pl ) < 0.0f ) { + return; + } + } + } + tw->maxTan = idMath::Fabs( tanHalfAngle ); + // collision plane is the flipped trm polygon plane + tw->trace.c.normal = -trmpoly->plane.Normal(); + tw->trace.c.dist = tw->trace.c.normal * v->p; + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_MODELVERTEX; + tw->trace.c.modelFeature = v - tw->model->vertices; + tw->trace.c.trmFeature = trmpoly - tw->polys; + tw->trace.c.point = v->p; + } +} + +/* +================ +idCollisionModelManagerLocal::RotateTrmThroughPolygon + + returns true if the polygon blocks the complete rotation +================ +*/ +bool idCollisionModelManagerLocal::RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) { + int i, j, k, edgeNum; + float d; + cm_trmVertex_t *bv; + cm_trmEdge_t *be; + cm_trmPolygon_t *bp; + cm_vertex_t *v; + cm_edge_t *e; + idVec3 *rotationOrigin; + + // if already checked this polygon + if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + p->checkcount = idCollisionModelManagerLocal::checkCount; + + // if this polygon does not have the right contents behind it + if ( !(p->contents & tw->contents) ) { + return false; + } + + // if the the trace bounds do not intersect the polygon bounds + if ( !tw->bounds.IntersectsBounds( p->bounds ) ) { + return false; + } + + // back face culling + if ( tw->isConvex ) { + // if the center of the convex trm is behind the polygon plane + if ( p->plane.Distance( tw->start ) < 0.0f ) { + // if the rotation axis intersects the trace model + if ( tw->axisIntersectsTrm ) { + return false; + } + else { + // if the direction of motion at the start and end position of the + // center of the trm both go towards or away from the polygon plane + // or if the intersections of the rotation axis with the expanded heart planes + // are both in front of the polygon plane + } + } + } + + // if the polygon is too far from the first heart plane + d = p->bounds.PlaneDistance( tw->heartPlane1 ); + if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane1 ) { + return false; + } + + // rotation bounds should cross polygon plane + switch( tw->bounds.PlaneSide( p->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( tw->model->isConvex ) { + tw->quickExit = true; + return true; + } + default: + return false; + } + + for ( i = 0; i < tw->numVerts; i++ ) { + bv = tw->vertices + i; + // calculate polygon side this vertex is on + d = p->plane.Distance( bv->p ); + bv->polygonSide = ( d < 0.0f ); + } + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + v = tw->model->vertices + e->vertexNum[INT32_SIGNBITSET( edgeNum )]; + + // pluecker coordinate for edge + tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[e->vertexNum[0]].p, + tw->model->vertices[e->vertexNum[1]].p ); + + // calculate rotation origin projected into rotation plane through the vertex + tw->polygonRotationOriginCache[i] = tw->origin + tw->axis * ( tw->axis * ( v->p - tw->origin ) ); + } + // copy first to last so we can easily cycle through + tw->polygonRotationOriginCache[p->numEdges] = tw->polygonRotationOriginCache[0]; + + // fast point rotation + if ( tw->pointTrace ) { + RotateTrmVertexThroughPolygon( tw, p, &tw->vertices[0], 0 ); + } + else { + // rotate trm vertices through polygon + for ( i = 0; i < tw->numVerts; i++ ) { + bv = tw->vertices + i; + if ( bv->used ) { + RotateTrmVertexThroughPolygon( tw, p, bv, i ); + } + } + + // rotate trm edges through polygon + for ( i = 1; i <= tw->numEdges; i++ ) { + be = tw->edges + i; + if ( be->used ) { + RotateTrmEdgeThroughPolygon( tw, p, be ); + } + } + + // rotate all polygon vertices through the trm + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + + if ( e->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set edge check count + e->checkcount = idCollisionModelManagerLocal::checkCount; + // can never collide with internal edges + if ( e->internal ) { + continue; + } + // got to check both vertices because we skip internal edges + for ( k = 0; k < 2; k++ ) { + + v = tw->model->vertices + e->vertexNum[k ^ INT32_SIGNBITSET( edgeNum )]; + + // if this vertex is already checked + if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set vertex check count + v->checkcount = idCollisionModelManagerLocal::checkCount; + + // if the vertex is outside the trm rotation bounds + if ( !tw->bounds.ContainsPoint( v->p ) ) { + continue; + } + + rotationOrigin = &tw->polygonRotationOriginCache[i+k]; + + for ( j = 0; j < tw->numPolys; j++ ) { + bp = tw->polys + j; + if ( bp->used ) { + RotateVertexThroughTrmPolygon( tw, bp, p, v, *rotationOrigin ); + } + } + } + } + } + + return ( tw->maxTan == 0.0f ); +} + +/* +================ +idCollisionModelManagerLocal::BoundsForRotation + + only for rotations < 180 degrees +================ +*/ +void idCollisionModelManagerLocal::BoundsForRotation( const idVec3 &origin, const idVec3 &axis, const idVec3 &start, const idVec3 &end, idBounds &bounds ) { + int i; + float radiusSqr; + idVec3 v1, v2; + + radiusSqr = ( start - origin ).LengthSqr(); + v1 = ( start - origin ).Cross( axis ); + v2 = ( end - origin ).Cross( axis ); + + for ( i = 0; i < 3; i++ ) { + // if the derivative changes sign along this axis during the rotation from start to end + if ( ( v1[i] > 0.0f && v2[i] < 0.0f ) || ( v1[i] < 0.0f && v2[i] > 0.0f ) ) { + if ( ( 0.5f * (start[i] + end[i]) - origin[i] ) > 0.0f ) { + bounds[0][i] = Min( start[i], end[i] ); + bounds[1][i] = origin[i] + idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } + else { + bounds[0][i] = origin[i] - idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + bounds[1][i] = Max( start[i], end[i] ); + } + } + else if ( start[i] > end[i] ) { + bounds[0][i] = end[i]; + bounds[1][i] = start[i]; + } + else { + bounds[0][i] = start[i]; + bounds[1][i] = end[i]; + } + // expand for epsilons + bounds[0][i] -= CM_BOX_EPSILON; + bounds[1][i] += CM_BOX_EPSILON; + } +} + +/* +================ +idCollisionModelManagerLocal::Rotation180 +================ +*/ +void idCollisionModelManagerLocal::Rotation180( trace_t *results, const idVec3 &rorg, const idVec3 &axis, + const float startAngle, const float endAngle, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + int i, j, edgeNum; + float d, maxErr, initialTan; + bool model_rotated, trm_rotated; + idVec3 dir, dir1, dir2, tmp, vr, vup, org, at, bt; + idMat3 invModelAxis, endAxis, tmpAxis; + idRotation startRotation, endRotation; + idPluecker plaxis; + cm_trmPolygon_t *poly; + cm_trmEdge_t *edge; + cm_trmVertex_t *vert; + ALIGN16( static cm_traceWork_t tw ); + + if ( model < 0 || model > MAX_SUBMODELS || model > idCollisionModelManagerLocal::maxModels ) { + common->Printf("idCollisionModelManagerLocal::Rotation180: invalid model handle\n"); + return; + } + if ( !idCollisionModelManagerLocal::models[model] ) { + common->Printf("idCollisionModelManagerLocal::Rotation180: invalid model\n"); + return; + } + + idCollisionModelManagerLocal::checkCount++; + + tw.trace.fraction = 1.0f; + tw.trace.c.contents = 0; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.isConvex = true; + tw.rotation = true; + tw.positionTest = false; + tw.axisIntersectsTrm = false; + tw.quickExit = false; + tw.angle = endAngle - startAngle; + assert( tw.angle > -180.0f && tw.angle < 180.0f ); + tw.maxTan = initialTan = idMath::Fabs( tan( ( idMath::PI / 360.0f ) * tw.angle ) ); + tw.model = idCollisionModelManagerLocal::models[model]; + tw.start = start - modelOrigin; + // rotation axis, axis is assumed to be normalized + tw.axis = axis; +// assert( tw.axis[0] * tw.axis[0] + tw.axis[1] * tw.axis[1] + tw.axis[2] * tw.axis[2] > 0.99f ); + // rotation origin projected into rotation plane through tw.start + tw.origin = rorg - modelOrigin; + d = (tw.axis * tw.origin) - ( tw.axis * tw.start ); + tw.origin = tw.origin - d * tw.axis; + // radius of rotation + tw.radius = ( tw.start - tw.origin ).Length(); + // maximum error of the circle approximation traced through the axial BSP tree + d = tw.radius * tw.radius - (CIRCLE_APPROXIMATION_LENGTH*CIRCLE_APPROXIMATION_LENGTH*0.25f); + if ( d > 0.0f ) { + maxErr = tw.radius - idMath::Sqrt( d ); + } else { + maxErr = tw.radius; + } + + model_rotated = modelAxis.IsRotated(); + if ( model_rotated ) { + invModelAxis = modelAxis.Transpose(); + tw.axis *= invModelAxis; + tw.origin *= invModelAxis; + } + + startRotation.Set( tw.origin, tw.axis, startAngle ); + endRotation.Set( tw.origin, tw.axis, endAngle ); + + // create matrix which rotates the rotation axis to the z-axis + tw.axis.NormalVectors( vr, vup ); + tw.matrix[0][0] = vr[0]; + tw.matrix[1][0] = vr[1]; + tw.matrix[2][0] = vr[2]; + tw.matrix[0][1] = -vup[0]; + tw.matrix[1][1] = -vup[1]; + tw.matrix[2][1] = -vup[2]; + tw.matrix[0][2] = tw.axis[0]; + tw.matrix[1][2] = tw.axis[1]; + tw.matrix[2][2] = tw.axis[2]; + + // if optimized point trace + if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f && + trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f && + trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + } + tw.end = tw.start; + // if we start at a specific angle + if ( startAngle != 0.0f ) { + startRotation.RotatePoint( tw.start ); + } + // calculate end position of rotation + endRotation.RotatePoint( tw.end ); + + // calculate rotation origin projected into rotation plane through the vertex + tw.numVerts = 1; + tw.vertices[0].p = tw.start; + tw.vertices[0].endp = tw.end; + tw.vertices[0].used = true; + tw.vertices[0].rotationOrigin = tw.origin + tw.axis * ( tw.axis * ( tw.vertices[0].p - tw.origin ) ); + BoundsForRotation( tw.vertices[0].rotationOrigin, tw.axis, tw.start, tw.end, tw.vertices[0].rotationBounds ); + // rotation bounds + tw.bounds = tw.vertices[0].rotationBounds; + tw.numEdges = tw.numPolys = 0; + + // collision with single point + tw.pointTrace = true; + + // extents is set to maximum error of the circle approximation traced through the axial BSP tree + tw.extents[0] = tw.extents[1] = tw.extents[2] = maxErr + CM_BOX_EPSILON; + + // setup rotation heart plane + tw.heartPlane1.SetNormal( tw.axis ); + tw.heartPlane1.FitThroughPoint( tw.start ); + tw.maxDistFromHeartPlane1 = CM_BOX_EPSILON; + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + // store results + *results = tw.trace; + results->endpos = start; + if ( tw.maxTan == initialTan ) { + results->fraction = 1.0f; + } else { + results->fraction = idMath::Fabs( atan( tw.maxTan ) * ( 2.0f * 180.0f / idMath::PI ) / tw.angle ); + } + assert( results->fraction <= 1.0f ); + endRotation.Set( rorg, axis, startAngle + (endAngle-startAngle) * results->fraction ); + endRotation.RotatePoint( results->endpos ); + results->endAxis.Identity(); + + if ( results->fraction < 1.0f ) { + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + return; + } + + tw.pointTrace = false; + + // setup trm structure + idCollisionModelManagerLocal::SetupTrm( &tw, trm ); + + trm_rotated = trmAxis.IsRotated(); + + // calculate vertex positions + if ( trm_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around the start position + tw.vertices[i].p *= trmAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // set trm at start position + tw.vertices[i].p += tw.start; + } + if ( model_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + tw.vertices[i].p *= invModelAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + tw.vertices[i].endp = tw.vertices[i].p; + } + // if we start at a specific angle + if ( startAngle != 0.0f ) { + for ( i = 0; i < tw.numVerts; i++ ) { + startRotation.RotatePoint( tw.vertices[i].p ); + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // end position of vertex + endRotation.RotatePoint( tw.vertices[i].endp ); + } + + // add offset to start point + if ( trm_rotated ) { + tw.start += trm->offset * trmAxis; + } else { + tw.start += trm->offset; + } + // if the model is rotated + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + } + tw.end = tw.start; + // if we start at a specific angle + if ( startAngle != 0.0f ) { + startRotation.RotatePoint( tw.start ); + } + // calculate end position of rotation + endRotation.RotatePoint( tw.end ); + + // setup trm vertices + for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) { + // calculate rotation origin projected into rotation plane through the vertex + vert->rotationOrigin = tw.origin + tw.axis * ( tw.axis * ( vert->p - tw.origin ) ); + // calculate rotation bounds for this vertex + BoundsForRotation( vert->rotationOrigin, tw.axis, vert->p, vert->endp, vert->rotationBounds ); + // if the rotation axis goes through the vertex then the vertex is not used + d = ( vert->p - vert->rotationOrigin ).LengthSqr(); + if ( d > ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) { + vert->used = true; + } + } + + // setup trm edges + for ( edge = tw.edges + 1, i = 1; i <= tw.numEdges; i++, edge++ ) { + // if the rotation axis goes through both the edge vertices then the edge is not used + if ( tw.vertices[edge->vertexNum[0]].used | tw.vertices[edge->vertexNum[1]].used ) { + edge->used = true; + } + // edge start, end and pluecker coordinate + edge->start = tw.vertices[edge->vertexNum[0]].p; + edge->end = tw.vertices[edge->vertexNum[1]].p; + edge->pl.FromLine( edge->start, edge->end ); + // pluecker coordinate for edge being rotated about the z-axis + at = ( edge->start - tw.origin ) * tw.matrix; + bt = ( edge->end - tw.origin ) * tw.matrix; + edge->plzaxis.FromLine( at, bt ); + // get edge rotation bounds from the rotation bounds of both vertices + edge->rotationBounds = tw.vertices[edge->vertexNum[0]].rotationBounds; + edge->rotationBounds.AddBounds( tw.vertices[edge->vertexNum[1]].rotationBounds ); + // used to calculate if the rotation axis intersects the trm + edge->bitNum = 0; + } + + tw.bounds.Clear(); + + // rotate trm polygon planes + if ( trm_rotated & model_rotated ) { + tmpAxis = trmAxis * invModelAxis; + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= tmpAxis; + } + } else if ( trm_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= trmAxis; + } + } else if ( model_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= invModelAxis; + } + } + + // setup trm polygons + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->used = true; + // set trm polygon plane distance + poly->plane.FitThroughPoint( tw.edges[abs(poly->edges[0])].start ); + // get polygon bounds from edge bounds + poly->rotationBounds.Clear(); + for ( j = 0; j < poly->numEdges; j++ ) { + // add edge rotation bounds to polygon rotation bounds + edge = &tw.edges[abs( poly->edges[j] )]; + poly->rotationBounds.AddBounds( edge->rotationBounds ); + } + // get trace bounds from polygon bounds + tw.bounds.AddBounds( poly->rotationBounds ); + } + + // extents including the maximum error of the circle approximation traced through the axial BSP tree + for ( i = 0; i < 3; i++ ) { + tw.size[0][i] = tw.bounds[0][i] - tw.start[i]; + tw.size[1][i] = tw.bounds[1][i] - tw.start[i]; + if ( idMath::Fabs( tw.size[0][i] ) > idMath::Fabs( tw.size[1][i] ) ) { + tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + maxErr + CM_BOX_EPSILON; + } else { + tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + maxErr + CM_BOX_EPSILON; + } + } + + // for back-face culling + if ( tw.isConvex ) { + if ( tw.start == tw.origin ) { + tw.axisIntersectsTrm = true; + } else { + // determine if the rotation axis intersects the trm + plaxis.FromRay( tw.origin, tw.axis ); + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + // back face cull polygons + if ( poly->plane.Normal() * tw.axis > 0.0f ) { + continue; + } + // test if the axis goes between the polygon edges + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = tw.edges + abs( edgeNum ); + if ( ( edge->bitNum & 2 ) == 0 ) { + d = plaxis.PermutedInnerProduct( edge->pl ); + edge->bitNum = ( ( d < 0.0f ) ? 1 : 0 ) | 2; + } + if ( ( edge->bitNum ^ INT32_SIGNBITSET( edgeNum ) ) & 1 ) { + break; + } + } + if ( j >= poly->numEdges ) { + tw.axisIntersectsTrm = true; + break; + } + } + } + } + + // setup rotation heart plane + tw.heartPlane1.SetNormal( tw.axis ); + tw.heartPlane1.FitThroughPoint( tw.start ); + tw.maxDistFromHeartPlane1 = 0.0f; + for ( i = 0; i < tw.numVerts; i++ ) { + d = idMath::Fabs( tw.heartPlane1.Distance( tw.vertices[i].p ) ); + if ( d > tw.maxDistFromHeartPlane1 ) { + tw.maxDistFromHeartPlane1 = d; + } + } + tw.maxDistFromHeartPlane1 += CM_BOX_EPSILON; + + // inverse rotation to rotate model vertices towards trace model + tw.modelVertexRotation.Set( tw.origin, tw.axis, -tw.angle ); + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + // store results + *results = tw.trace; + results->endpos = start; + if ( tw.maxTan == initialTan ) { + results->fraction = 1.0f; + } else { + results->fraction = idMath::Fabs( atan( tw.maxTan ) * ( 2.0f * 180.0f / idMath::PI ) / tw.angle ); + } + assert( results->fraction <= 1.0f ); + endRotation.Set( rorg, axis, startAngle + (endAngle-startAngle) * results->fraction ); + endRotation.RotatePoint( results->endpos ); + results->endAxis = trmAxis * endRotation.ToMat3(); + + if ( results->fraction < 1.0f ) { + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } +} + +/* +================ +idCollisionModelManagerLocal::Rotation +================ +*/ +#ifdef _DEBUG +static int entered = 0; +#endif + +void idCollisionModelManagerLocal::Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idVec3 tmp; + float maxa, stepa, a, lasta; + + assert( ((byte *)&start) < ((byte *)results) || ((byte *)&start) > (((byte *)results) + sizeof( trace_t )) ); + assert( ((byte *)&trmAxis) < ((byte *)results) || ((byte *)&trmAxis) > (((byte *)results) + sizeof( trace_t )) ); + + memset( results, 0, sizeof( *results ) ); + + // if special position test + if ( rotation.GetAngle() == 0.0f ) { + idCollisionModelManagerLocal::ContentsTrm( results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + return; + } + +#ifdef _DEBUG + bool startsolid = false; + // test whether or not stuck to begin with + if ( cm_debugCollision.GetBool() ) { + if ( !entered ) { + entered = 1; + // if already messed up to begin with + if ( idCollisionModelManagerLocal::Contents( start, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + startsolid = true; + } + entered = 0; + } + } +#endif + + if ( rotation.GetAngle() >= 180.0f || rotation.GetAngle() <= -180.0f) { + if ( rotation.GetAngle() >= 360.0f ) { + maxa = 360.0f; + stepa = 120.0f; // three steps strictly < 180 degrees + } else if ( rotation.GetAngle() <= -360.0f ) { + maxa = -360.0f; + stepa = -120.0f; // three steps strictly < 180 degrees + } else { + maxa = rotation.GetAngle(); + stepa = rotation.GetAngle() * 0.5f; // two steps strictly < 180 degrees + } + for ( lasta = 0.0f, a = stepa; fabs( a ) < fabs( maxa ) + 1.0f; lasta = a, a += stepa ) { + // partial rotation + idCollisionModelManagerLocal::Rotation180( results, rotation.GetOrigin(), rotation.GetVec(), lasta, a, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + // if there is a collision + if ( results->fraction < 1.0f ) { + // fraction of total rotation + results->fraction = (lasta + stepa * results->fraction) / rotation.GetAngle(); + return; + } + } + results->fraction = 1.0f; + return; + } + + idCollisionModelManagerLocal::Rotation180( results, rotation.GetOrigin(), rotation.GetVec(), 0.0f, rotation.GetAngle(), start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + +#ifdef _DEBUG + // test for missed collisions + if ( cm_debugCollision.GetBool() ) { + if ( !entered ) { + entered = 1; + // if the trm is stuck in the model + if ( idCollisionModelManagerLocal::Contents( results->endpos, trm, results->endAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + trace_t tr; + + // test where the trm is stuck in the model + idCollisionModelManagerLocal::Contents( results->endpos, trm, results->endAxis, -1, model, modelOrigin, modelAxis ); + // re-run collision detection to find out where it failed + idCollisionModelManagerLocal::Rotation( &tr, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + entered = 0; + } + } +#endif +} diff --git a/neo/cm/CollisionModel_trace.cpp b/neo/cm/CollisionModel_trace.cpp new file mode 100644 index 00000000..2d9453e2 --- /dev/null +++ b/neo/cm/CollisionModel_trace.cpp @@ -0,0 +1,257 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Trace through the spatial subdivision + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::TraceTrmThroughNode +================ +*/ +void idCollisionModelManagerLocal::TraceTrmThroughNode( cm_traceWork_t *tw, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + // position test + if ( tw->positionTest ) { + // if already stuck in solid + if ( tw->trace.fraction == 0.0f ) { + return; + } + // test if any of the trm vertices is inside a brush + for ( bref = node->brushes; bref; bref = bref->next ) { + if ( idCollisionModelManagerLocal::TestTrmVertsInBrush( tw, bref->b ) ) { + return; + } + } + // if just testing a point we're done + if ( tw->pointTrace ) { + return; + } + // test if the trm is stuck in any polygons + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( idCollisionModelManagerLocal::TestTrmInPolygon( tw, pref->p ) ) { + return; + } + } + } + else if ( tw->rotation ) { + // rotate through all polygons in this leaf + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( idCollisionModelManagerLocal::RotateTrmThroughPolygon( tw, pref->p ) ) { + return; + } + } + } + else { + // trace through all polygons in this leaf + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( idCollisionModelManagerLocal::TranslateTrmThroughPolygon( tw, pref->p ) ) { + return; + } + } + } +} + +/* +================ +idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r +================ +*/ +//#define NO_SPATIAL_SUBDIVISION + +void idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( cm_traceWork_t *tw, cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2) { + float t1, t2, offset; + float frac, frac2; + float idist; + idVec3 mid; + int side; + float midf; + + if ( !node ) { + return; + } + + if ( tw->quickExit ) { + return; // stop immediately + } + + if ( tw->trace.fraction <= p1f ) { + return; // already hit something nearer + } + + // if we need to test this node for collisions + if ( node->polygons || (tw->positionTest && node->brushes) ) { + // trace through node with collision data + idCollisionModelManagerLocal::TraceTrmThroughNode( tw, node ); + } + // if already stuck in solid + if ( tw->positionTest && tw->trace.fraction == 0.0f ) { + return; + } + // if this is a leaf node + if ( node->planeType == -1 ) { + return; + } +#ifdef NO_SPATIAL_SUBDIVISION + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[0], p1f, p2f, p1, p2 ); + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[1], p1f, p2f, p1, p2 ); + return; +#endif + // distance from plane for trace start and end + t1 = p1[node->planeType] - node->planeDist; + t2 = p2[node->planeType] - node->planeDist; + // adjust the plane distance appropriately for mins/maxs + offset = tw->extents[node->planeType]; + // see which sides we need to consider + if ( t1 >= offset && t2 >= offset ) { + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[0], p1f, p2f, p1, p2 ); + return; + } + + if ( t1 < -offset && t2 < -offset ) { + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + if ( t1 < t2 ) { + idist = 1.0f / (t1-t2); + side = 1; + frac2 = (t1 + offset) * idist; + frac = (t1 - offset) * idist; + } else if (t1 > t2) { + idist = 1.0f / (t1-t2); + side = 0; + frac2 = (t1 - offset) * idist; + frac = (t1 + offset) * idist; + } else { + side = 0; + frac = 1.0f; + frac2 = 0.0f; + } + + // move up to the node + if ( frac < 0.0f ) { + frac = 0.0f; + } + else if ( frac > 1.0f ) { + frac = 1.0f; + } + + midf = p1f + (p2f - p1f)*frac; + + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0.0f ) { + frac2 = 0.0f; + } + else if ( frac2 > 1.0f ) { + frac2 = 1.0f; + } + + midf = p1f + (p2f - p1f)*frac2; + + mid[0] = p1[0] + frac2*(p2[0] - p1[0]); + mid[1] = p1[1] + frac2*(p2[1] - p1[1]); + mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[side^1], midf, p2f, mid, p2 ); +} + +/* +================ +idCollisionModelManagerLocal::TraceThroughModel +================ +*/ +void idCollisionModelManagerLocal::TraceThroughModel( cm_traceWork_t *tw ) { + float d; + int i, numSteps; + idVec3 start, end; + idRotation rot; + + if ( !tw->rotation ) { + // trace through spatial subdivision and then through leafs + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, tw->start, tw->end ); + } + else { + // approximate the rotation with a series of straight line movements + // total length covered along circle + d = tw->radius * DEG2RAD( tw->angle ); + // if more than one step + if ( d > CIRCLE_APPROXIMATION_LENGTH ) { + // number of steps for the approximation + numSteps = (int) (CIRCLE_APPROXIMATION_LENGTH / d); + // start of approximation + start = tw->start; + // trace circle approximation steps through the BSP tree + for ( i = 0; i < numSteps; i++ ) { + // calculate next point on approximated circle + rot.Set( tw->origin, tw->axis, tw->angle * ((float) (i+1) / numSteps) ); + end = start * rot; + // trace through spatial subdivision and then through leafs + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, start, end ); + // no need to continue if something was hit already + if ( tw->trace.fraction < 1.0f ) { + return; + } + start = end; + } + } + else { + start = tw->start; + } + // last step of the approximation + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, start, tw->end ); + } +} diff --git a/neo/cm/CollisionModel_translate.cpp b/neo/cm/CollisionModel_translate.cpp new file mode 100644 index 00000000..cc2f6b14 --- /dev/null +++ b/neo/cm/CollisionModel_translate.cpp @@ -0,0 +1,1086 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Collision detection for translational motion + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::TranslateEdgeThroughEdge + + calculates fraction of the translation completed at which the edges collide +================ +*/ +ID_INLINE int idCollisionModelManagerLocal::TranslateEdgeThroughEdge( idVec3 &cross, idPluecker &l1, idPluecker &l2, float *fraction ) { + + float d, t; + + /* + + a = start of line + b = end of line + dir = movement direction + l1 = pluecker coordinate for line + l2 = pluecker coordinate for edge we might collide with + a+dir = start of line after movement + b+dir = end of line after movement + t = scale factor + solve pluecker inner product for t of line (a+t*dir : b+t*dir) and line l2 + + v[0] = (a[0]+t*dir[0]) * (b[1]+t*dir[1]) - (b[0]+t*dir[0]) * (a[1]+t*dir[1]); + v[1] = (a[0]+t*dir[0]) * (b[2]+t*dir[2]) - (b[0]+t*dir[0]) * (a[2]+t*dir[2]); + v[2] = (a[0]+t*dir[0]) - (b[0]+t*dir[0]); + v[3] = (a[1]+t*dir[1]) * (b[2]+t*dir[2]) - (b[1]+t*dir[1]) * (a[2]+t*dir[2]); + v[4] = (a[2]+t*dir[2]) - (b[2]+t*dir[2]); + v[5] = (b[1]+t*dir[1]) - (a[1]+t*dir[1]); + + l2[0] * v[4] + l2[1] * v[5] + l2[2] * v[3] + l2[4] * v[0] + l2[5] * v[1] + l2[3] * v[2] = 0; + + solve t + + v[0] = (a[0]+t*dir[0]) * (b[1]+t*dir[1]) - (b[0]+t*dir[0]) * (a[1]+t*dir[1]); + v[0] = (a[0]*b[1]) + a[0]*t*dir[1] + b[1]*t*dir[0] + (t*t*dir[0]*dir[1]) - + ((b[0]*a[1]) + b[0]*t*dir[1] + a[1]*t*dir[0] + (t*t*dir[0]*dir[1])); + v[0] = a[0]*b[1] + a[0]*t*dir[1] + b[1]*t*dir[0] - b[0]*a[1] - b[0]*t*dir[1] - a[1]*t*dir[0]; + + v[1] = (a[0]+t*dir[0]) * (b[2]+t*dir[2]) - (b[0]+t*dir[0]) * (a[2]+t*dir[2]); + v[1] = (a[0]*b[2]) + a[0]*t*dir[2] + b[2]*t*dir[0] + (t*t*dir[0]*dir[2]) - + ((b[0]*a[2]) + b[0]*t*dir[2] + a[2]*t*dir[0] + (t*t*dir[0]*dir[2])); + v[1] = a[0]*b[2] + a[0]*t*dir[2] + b[2]*t*dir[0] - b[0]*a[2] - b[0]*t*dir[2] - a[2]*t*dir[0]; + + v[2] = (a[0]+t*dir[0]) - (b[0]+t*dir[0]); + v[2] = a[0] - b[0]; + + v[3] = (a[1]+t*dir[1]) * (b[2]+t*dir[2]) - (b[1]+t*dir[1]) * (a[2]+t*dir[2]); + v[3] = (a[1]*b[2]) + a[1]*t*dir[2] + b[2]*t*dir[1] + (t*t*dir[1]*dir[2]) - + ((b[1]*a[2]) + b[1]*t*dir[2] + a[2]*t*dir[1] + (t*t*dir[1]*dir[2])); + v[3] = a[1]*b[2] + a[1]*t*dir[2] + b[2]*t*dir[1] - b[1]*a[2] - b[1]*t*dir[2] - a[2]*t*dir[1]; + + v[4] = (a[2]+t*dir[2]) - (b[2]+t*dir[2]); + v[4] = a[2] - b[2]; + + v[5] = (b[1]+t*dir[1]) - (a[1]+t*dir[1]); + v[5] = b[1] - a[1]; + + + v[0] = a[0]*b[1] + a[0]*t*dir[1] + b[1]*t*dir[0] - b[0]*a[1] - b[0]*t*dir[1] - a[1]*t*dir[0]; + v[1] = a[0]*b[2] + a[0]*t*dir[2] + b[2]*t*dir[0] - b[0]*a[2] - b[0]*t*dir[2] - a[2]*t*dir[0]; + v[2] = a[0] - b[0]; + v[3] = a[1]*b[2] + a[1]*t*dir[2] + b[2]*t*dir[1] - b[1]*a[2] - b[1]*t*dir[2] - a[2]*t*dir[1]; + v[4] = a[2] - b[2]; + v[5] = b[1] - a[1]; + + v[0] = (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) * t + a[0]*b[1] - b[0]*a[1]; + v[1] = (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) * t + a[0]*b[2] - b[0]*a[2]; + v[2] = a[0] - b[0]; + v[3] = (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]) * t + a[1]*b[2] - b[1]*a[2]; + v[4] = a[2] - b[2]; + v[5] = b[1] - a[1]; + + l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) * t + l2[4] * (a[0]*b[1] - b[0]*a[1]) + + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) * t + l2[5] * (a[0]*b[2] - b[0]*a[2]) + + l2[3] * (a[0] - b[0]) + + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]) * t + l2[2] * (a[1]*b[2] - b[1]*a[2]) + + l2[0] * (a[2] - b[2]) + + l2[1] * (b[1] - a[1]) = 0 + + t = (- l2[4] * (a[0]*b[1] - b[0]*a[1]) - + l2[5] * (a[0]*b[2] - b[0]*a[2]) - + l2[3] * (a[0] - b[0]) - + l2[2] * (a[1]*b[2] - b[1]*a[2]) - + l2[0] * (a[2] - b[2]) - + l2[1] * (b[1] - a[1])) / + (l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) + + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) + + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1])); + + d = l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) + + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) + + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]); + + t = - ( l2[4] * (a[0]*b[1] - b[0]*a[1]) + + l2[5] * (a[0]*b[2] - b[0]*a[2]) + + l2[3] * (a[0] - b[0]) + + l2[2] * (a[1]*b[2] - b[1]*a[2]) + + l2[0] * (a[2] - b[2]) + + l2[1] * (b[1] - a[1])); + t /= d; + + MrE pats Pluecker on the head.. good monkey + + edgeDir = a - b; + d = l2[4] * (edgeDir[0]*dir[1] - edgeDir[1]*dir[0]) + + l2[5] * (edgeDir[0]*dir[2] - edgeDir[2]*dir[0]) + + l2[2] * (edgeDir[1]*dir[2] - edgeDir[2]*dir[1]); + */ + + d = l2[4] * cross[0] + l2[5] * cross[1] + l2[2] * cross[2]; + + if ( d == 0.0f ) { + *fraction = 1.0f; + // no collision ever + return false; + } + + t = -l1.PermutedInnerProduct( l2 ); + // if the lines cross each other to begin with + if ( fabs( t ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + *fraction = 0.0f; + return true; + } + // fraction of movement at the time the lines cross each other + *fraction = t / d; + return true; +} + +/* +================ +CM_AddContact +================ +*/ +ID_INLINE void CM_AddContact( cm_traceWork_t *tw ) { + + if ( tw->numContacts >= tw->maxContacts ) { + return; + } + // copy contact information from trace_t + tw->contacts[tw->numContacts] = tw->trace.c; + tw->numContacts++; + // set fraction back to 1 to find all other contacts + tw->trace.fraction = 1.0f; +} + +/* +================ +CM_SetVertexSidedness + + stores for the given model vertex at which side of one of the trm edges it passes +================ +*/ +ID_INLINE void CM_SetVertexSidedness( cm_vertex_t *v, const idPluecker &vpl, const idPluecker &epl, const int bitNum ) { + const int mask = 1 << bitNum; + if ( ( v->sideSet & mask ) == 0 ) { + const float fl = vpl.PermutedInnerProduct( epl ); + v->side = ( v->side & ~mask ) | ( ( fl < 0.0f ) ? mask : 0 ); + v->sideSet |= mask; + } +} + +/* +================ +CM_SetEdgeSidedness + + stores for the given model edge at which side one of the trm vertices +================ +*/ +ID_INLINE void CM_SetEdgeSidedness( cm_edge_t *edge, const idPluecker &vpl, const idPluecker &epl, const int bitNum ) { + const int mask = 1 << bitNum; + if ( ( edge->sideSet & mask ) == 0 ) { + const float fl = vpl.PermutedInnerProduct( epl ); + edge->side = ( edge->side & ~mask ) | ( ( fl < 0.0f ) ? mask : 0 ); + edge->sideSet |= mask; + } +} + +/* +================ +idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) { + int i, edgeNum; + float f1, f2, dist, d1, d2; + idVec3 start, end, normal; + cm_edge_t *edge; + cm_vertex_t *v1, *v2; + idPluecker *pl, epsPl; + + // check edges for a collision + for ( i = 0; i < poly->numEdges; i++) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // if this edge is already checked + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // can never collide with internal edges + if ( edge->internal ) { + continue; + } + pl = &tw->polygonEdgePlueckerCache[i]; + // get the sides at which the trm edge vertices pass the polygon edge + CM_SetEdgeSidedness( edge, *pl, tw->vertices[trmEdge->vertexNum[0]].pl, trmEdge->vertexNum[0] ); + CM_SetEdgeSidedness( edge, *pl, tw->vertices[trmEdge->vertexNum[1]].pl, trmEdge->vertexNum[1] ); + // if the trm edge start and end vertex do not pass the polygon edge at different sides + if ( !(((edge->side >> trmEdge->vertexNum[0]) ^ (edge->side >> trmEdge->vertexNum[1])) & 1) ) { + continue; + } + // get the sides at which the polygon edge vertices pass the trm edge + v1 = tw->model->vertices + edge->vertexNum[INT32_SIGNBITSET( edgeNum )]; + CM_SetVertexSidedness( v1, tw->polygonVertexPlueckerCache[i], trmEdge->pl, trmEdge->bitNum ); + v2 = tw->model->vertices + edge->vertexNum[INT32_SIGNBITNOTSET( edgeNum )]; + CM_SetVertexSidedness( v2, tw->polygonVertexPlueckerCache[i+1], trmEdge->pl, trmEdge->bitNum ); + // if the polygon edge start and end vertex do not pass the trm edge at different sides + if ( !((v1->side ^ v2->side) & (1<bitNum)) ) { + continue; + } + // if there is no possible collision between the trm edge and the polygon edge + if ( !idCollisionModelManagerLocal::TranslateEdgeThroughEdge( trmEdge->cross, trmEdge->pl, *pl, &f1 ) ) { + continue; + } + // if moving away from edge + if ( f1 < 0.0f ) { + continue; + } + + // pluecker coordinate for epsilon expanded edge + epsPl.FromLine( tw->model->vertices[edge->vertexNum[0]].p + edge->normal * CM_CLIP_EPSILON, + tw->model->vertices[edge->vertexNum[1]].p + edge->normal * CM_CLIP_EPSILON ); + // calculate collision fraction with epsilon expanded edge + if ( !idCollisionModelManagerLocal::TranslateEdgeThroughEdge( trmEdge->cross, trmEdge->pl, epsPl, &f2 ) ) { + continue; + } + // if no collision with epsilon edge or moving away from edge + if ( f2 > 1.0f || f1 < f2 ) { + continue; + } + + if ( f2 < 0.0f ) { + f2 = 0.0f; + } + + if ( f2 < tw->trace.fraction ) { + tw->trace.fraction = f2; + // create plane with normal vector orthogonal to both the polygon edge and the trm edge + start = tw->model->vertices[edge->vertexNum[0]].p; + end = tw->model->vertices[edge->vertexNum[1]].p; + tw->trace.c.normal = ( end - start ).Cross( trmEdge->end - trmEdge->start ); + // FIXME: do this normalize when we know the first collision + tw->trace.c.normal.Normalize(); + tw->trace.c.dist = tw->trace.c.normal * start; + // make sure the collision plane faces the trace model + if ( tw->trace.c.normal * trmEdge->start - tw->trace.c.dist < 0.0f ) { + tw->trace.c.normal = -tw->trace.c.normal; + tw->trace.c.dist = -tw->trace.c.dist; + } + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.modelFeature = edgeNum; + tw->trace.c.trmFeature = trmEdge - tw->edges; + // calculate collision point + normal[0] = trmEdge->cross[2]; + normal[1] = -trmEdge->cross[1]; + normal[2] = trmEdge->cross[0]; + dist = normal * trmEdge->start; + d1 = normal * start - dist; + d2 = normal * end - dist; + f1 = d1 / ( d1 - d2 ); + //assert( f1 >= 0.0f && f1 <= 1.0f ); + tw->trace.c.point = start + f1 * ( end - start ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + } + } + } +} + +/* +================ +CM_TranslationPlaneFraction +================ +*/ +float CM_TranslationPlaneFraction( const idPlane &plane, const idVec3 &start, const idVec3 &end ) { + const float d2 = plane.Distance( end ); + // if the end point is closer to the plane than an epsilon we still take it for a collision + if ( d2 >= CM_CLIP_EPSILON ) { + return 1.0f; + } + const float d1 = plane.Distance( start ); + + // if completely behind the polygon + if ( d1 <= 0.0f ) { + return 1.0f; + } + // leaves polygon + if ( d1 - d2 < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return 1.0f; + } + return ( d1 - CM_CLIP_EPSILON ) / ( d1 - d2 ); +} + +/* +================ +idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int bitNum ) { + int i, edgeNum; + float f; + cm_edge_t *edge; + + f = CM_TranslationPlaneFraction( poly->plane, v->p, v->endp ); + if ( f < tw->trace.fraction ) { + + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + CM_SetEdgeSidedness( edge, tw->polygonEdgePlueckerCache[i], v->pl, bitNum ); + if ( INT32_SIGNBITSET( edgeNum ) ^ ( ( edge->side >> bitNum ) & 1 ) ) { + return; + } + } + if ( f < 0.0f ) { + f = 0.0f; + } + tw->trace.fraction = f; + // collision plane is the polygon plane + tw->trace.c.normal = poly->plane.Normal(); + tw->trace.c.dist = poly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.modelFeature = *reinterpret_cast(&poly); + tw->trace.c.trmFeature = v - tw->vertices; + tw->trace.c.point = v->p + tw->trace.fraction * ( v->endp - v->p ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + // no need to store the trm vertex more than once as a contact + v->used = false; + } + } +} + +/* +================ +idCollisionModelManagerLocal::TranslatePointThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslatePointThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v ) { + int i, edgeNum; + float f; + cm_edge_t *edge; + idPluecker pl; + + f = CM_TranslationPlaneFraction( poly->plane, v->p, v->endp ); + if ( f < tw->trace.fraction ) { + + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // if we didn't yet calculate the sidedness for this edge + if ( edge->checkcount != idCollisionModelManagerLocal::checkCount ) { + float fl; + edge->checkcount = idCollisionModelManagerLocal::checkCount; + pl.FromLine(tw->model->vertices[edge->vertexNum[0]].p, tw->model->vertices[edge->vertexNum[1]].p); + fl = v->pl.PermutedInnerProduct( pl ); + edge->side = ( fl < 0.0f ); + } + // if the point passes the edge at the wrong side + //if ( (edgeNum > 0) == edge->side ) { + if ( INT32_SIGNBITSET( edgeNum ) ^ edge->side ) { + return; + } + } + if ( f < 0.0f ) { + f = 0.0f; + } + tw->trace.fraction = f; + // collision plane is the polygon plane + tw->trace.c.normal = poly->plane.Normal(); + tw->trace.c.dist = poly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.modelFeature = *reinterpret_cast(&poly); + tw->trace.c.trmFeature = v - tw->vertices; + tw->trace.c.point = v->p + tw->trace.fraction * ( v->endp - v->p ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + // no need to store the trm vertex more than once as a contact + v->used = false; + } + } +} + +/* +================ +idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &endp, idPluecker &pl ) { + int i, edgeNum; + float f; + cm_trmEdge_t *edge; + + f = CM_TranslationPlaneFraction( trmpoly->plane, v->p, endp ); + if ( f < tw->trace.fraction ) { + + for ( i = 0; i < trmpoly->numEdges; i++ ) { + edgeNum = trmpoly->edges[i]; + edge = tw->edges + abs(edgeNum); + + CM_SetVertexSidedness( v, pl, edge->pl, edge->bitNum ); + if ( INT32_SIGNBITSET( edgeNum ) ^ ( ( v->side >> edge->bitNum ) & 1 ) ) { + return; + } + } + if ( f < 0.0f ) { + f = 0.0f; + } + tw->trace.fraction = f; + // collision plane is the inverse trm polygon plane + tw->trace.c.normal = -trmpoly->plane.Normal(); + tw->trace.c.dist = -trmpoly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_MODELVERTEX; + tw->trace.c.modelFeature = v - tw->model->vertices; + tw->trace.c.trmFeature = trmpoly - tw->polys; + tw->trace.c.point = v->p + tw->trace.fraction * ( endp - v->p ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + } + } +} + +/* +================ +idCollisionModelManagerLocal::TranslateTrmThroughPolygon + + returns true if the polygon blocks the complete translation +================ +*/ +bool idCollisionModelManagerLocal::TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) { + int i, j, k, edgeNum; + float fraction, d; + idVec3 endp; + idPluecker *pl; + cm_trmVertex_t *bv; + cm_trmEdge_t *be; + cm_trmPolygon_t *bp; + cm_vertex_t *v; + cm_edge_t *e; + + // if already checked this polygon + if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + p->checkcount = idCollisionModelManagerLocal::checkCount; + + // if this polygon does not have the right contents behind it + if ( !(p->contents & tw->contents) ) { + return false; + } + + // if the the trace bounds do not intersect the polygon bounds + if ( !tw->bounds.IntersectsBounds( p->bounds ) ) { + return false; + } + + // only collide with the polygon if approaching at the front + if ( ( p->plane.Normal() * tw->dir ) > 0.0f ) { + return false; + } + + // if the polygon is too far from the first heart plane + d = p->bounds.PlaneDistance( tw->heartPlane1 ); + if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane1 ) { + return false; + } + + // if the polygon is too far from the second heart plane + d = p->bounds.PlaneDistance( tw->heartPlane2 ); + if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane2 ) { + return false; + } + fraction = tw->trace.fraction; + + // fast point trace + if ( tw->pointTrace ) { + idCollisionModelManagerLocal::TranslatePointThroughPolygon( tw, p, &tw->vertices[0] ); + } + else { + + // trace bounds should cross polygon plane + switch ( tw->bounds.PlaneSide( p->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( tw->model->isConvex ) { + tw->quickExit = true; + return true; + } + default: + return false; + } + + // calculate pluecker coordinates for the polygon edges and polygon vertices + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + // reset sidedness cache if this is the first time we encounter this edge during this trace + if ( e->checkcount != idCollisionModelManagerLocal::checkCount ) { + e->sideSet = 0; + } + // pluecker coordinate for edge + tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[e->vertexNum[0]].p, + tw->model->vertices[e->vertexNum[1]].p ); + + v = &tw->model->vertices[e->vertexNum[INT32_SIGNBITSET( edgeNum )]]; + // reset sidedness cache if this is the first time we encounter this vertex during this trace + if ( v->checkcount != idCollisionModelManagerLocal::checkCount ) { + v->sideSet = 0; + } + // pluecker coordinate for vertex movement vector + tw->polygonVertexPlueckerCache[i].FromRay( v->p, -tw->dir ); + } + // copy first to last so we can easily cycle through for the edges + tw->polygonVertexPlueckerCache[p->numEdges] = tw->polygonVertexPlueckerCache[0]; + + // trace trm vertices through polygon + for ( i = 0; i < tw->numVerts; i++ ) { + bv = tw->vertices + i; + if ( bv->used ) { + idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon( tw, p, bv, i ); + } + } + + // trace trm edges through polygon + for ( i = 1; i <= tw->numEdges; i++ ) { + be = tw->edges + i; + if ( be->used ) { + idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon( tw, p, be); + } + } + + // trace all polygon vertices through the trm + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + + if ( e->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set edge check count + e->checkcount = idCollisionModelManagerLocal::checkCount; + // can never collide with internal edges + if ( e->internal ) { + continue; + } + // got to check both vertices because we skip internal edges + for ( k = 0; k < 2; k++ ) { + + v = tw->model->vertices + e->vertexNum[k ^ INT32_SIGNBITSET( edgeNum )]; + // if this vertex is already checked + if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set vertex check count + v->checkcount = idCollisionModelManagerLocal::checkCount; + + // if the vertex is outside the trace bounds + if ( !tw->bounds.ContainsPoint( v->p ) ) { + continue; + } + + // vertex end point after movement + endp = v->p - tw->dir; + // pluecker coordinate for vertex movement vector + pl = &tw->polygonVertexPlueckerCache[i+k]; + + for ( j = 0; j < tw->numPolys; j++ ) { + bp = tw->polys + j; + if ( bp->used ) { + idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon( tw, bp, p, v, endp, *pl ); + } + } + } + } + } + + // if there was a collision with this polygon and we are not retrieving contacts + if ( tw->trace.fraction < fraction && !tw->getContacts ) { + fraction = tw->trace.fraction; + endp = tw->start + fraction * tw->dir; + // decrease bounds + for ( i = 0; i < 3; i++ ) { + if ( tw->start[i] < endp[i] ) { + tw->bounds[0][i] = tw->start[i] + tw->size[0][i] - CM_BOX_EPSILON; + tw->bounds[1][i] = endp[i] + tw->size[1][i] + CM_BOX_EPSILON; + } + else { + tw->bounds[0][i] = endp[i] + tw->size[0][i] - CM_BOX_EPSILON; + tw->bounds[1][i] = tw->start[i] + tw->size[1][i] + CM_BOX_EPSILON; + } + } + } + + return ( tw->trace.fraction == 0.0f ); +} + +/* +================ +idCollisionModelManagerLocal::SetupTrm +================ +*/ +void idCollisionModelManagerLocal::SetupTrm( cm_traceWork_t *tw, const idTraceModel *trm ) { + int i, j; + + // vertices + tw->numVerts = trm->numVerts; + for ( i = 0; i < trm->numVerts; i++ ) { + tw->vertices[i].p = trm->verts[i]; + tw->vertices[i].used = false; + } + // edges + tw->numEdges = trm->numEdges; + for ( i = 1; i <= trm->numEdges; i++ ) { + tw->edges[i].vertexNum[0] = trm->edges[i].v[0]; + tw->edges[i].vertexNum[1] = trm->edges[i].v[1]; + tw->edges[i].used = false; + } + // polygons + tw->numPolys = trm->numPolys; + for ( i = 0; i < trm->numPolys; i++ ) { + tw->polys[i].numEdges = trm->polys[i].numEdges; + for ( j = 0; j < trm->polys[i].numEdges; j++ ) { + tw->polys[i].edges[j] = trm->polys[i].edges[j]; + } + tw->polys[i].plane.SetNormal( trm->polys[i].normal ); + tw->polys[i].used = false; + } + // is the trace model convex or not + tw->isConvex = trm->isConvex; +} + +/* +================ +idCollisionModelManagerLocal::SetupTranslationHeartPlanes +================ +*/ +void idCollisionModelManagerLocal::SetupTranslationHeartPlanes( cm_traceWork_t *tw ) { + idVec3 dir, normal1, normal2; + + // calculate trace heart planes + dir = tw->dir; + dir.Normalize(); + dir.NormalVectors( normal1, normal2 ); + tw->heartPlane1.SetNormal( normal1 ); + tw->heartPlane1.FitThroughPoint( tw->start ); + tw->heartPlane2.SetNormal( normal2 ); + tw->heartPlane2.FitThroughPoint( tw->start ); +} + +/* +================ +idCollisionModelManagerLocal::Translation +================ +*/ +#ifdef _DEBUG +static int entered = 0; +#endif + +void idCollisionModelManagerLocal::Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + + int i, j; + float dist; + bool model_rotated, trm_rotated; + idVec3 dir1, dir2, dir; + idMat3 invModelAxis, tmpAxis; + cm_trmPolygon_t *poly; + cm_trmEdge_t *edge; + cm_trmVertex_t *vert; + ALIGN16( static cm_traceWork_t tw ); + + assert( ((byte *)&start) < ((byte *)results) || ((byte *)&start) >= (((byte *)results) + sizeof( trace_t )) ); + assert( ((byte *)&end) < ((byte *)results) || ((byte *)&end) >= (((byte *)results) + sizeof( trace_t )) ); + assert( ((byte *)&trmAxis) < ((byte *)results) || ((byte *)&trmAxis) >= (((byte *)results) + sizeof( trace_t )) ); + + memset( results, 0, sizeof( *results ) ); + + if ( model < 0 || model > MAX_SUBMODELS || model > idCollisionModelManagerLocal::maxModels ) { + common->Printf("idCollisionModelManagerLocal::Translation: invalid model handle\n"); + return; + } + if ( !idCollisionModelManagerLocal::models[model] ) { + common->Printf("idCollisionModelManagerLocal::Translation: invalid model\n"); + return; + } + + // if case special position test + if ( start[0] == end[0] && start[1] == end[1] && start[2] == end[2] ) { + idCollisionModelManagerLocal::ContentsTrm( results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + return; + } + +#ifdef _DEBUG + bool startsolid = false; + // test whether or not stuck to begin with + if ( cm_debugCollision.GetBool() ) { + if ( !entered && !idCollisionModelManagerLocal::getContacts ) { + entered = 1; + // if already messed up to begin with + if ( idCollisionModelManagerLocal::Contents( start, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + startsolid = true; + } + entered = 0; + } + } +#endif + + idCollisionModelManagerLocal::checkCount++; + + tw.trace.fraction = 1.0f; + tw.trace.c.contents = 0; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.isConvex = true; + tw.rotation = false; + tw.positionTest = false; + tw.quickExit = false; + tw.getContacts = idCollisionModelManagerLocal::getContacts; + tw.contacts = idCollisionModelManagerLocal::contacts; + tw.maxContacts = idCollisionModelManagerLocal::maxContacts; + tw.numContacts = 0; + tw.model = idCollisionModelManagerLocal::models[model]; + tw.start = start - modelOrigin; + tw.end = end - modelOrigin; + tw.dir = end - start; + + model_rotated = modelAxis.IsRotated(); + if ( model_rotated ) { + invModelAxis = modelAxis.Transpose(); + } + + // if optimized point trace + if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f && + trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f && + trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + tw.end *= invModelAxis; + tw.dir *= invModelAxis; + } + + // trace bounds + for ( i = 0; i < 3; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.end[i] + CM_BOX_EPSILON; + } + else { + tw.bounds[0][i] = tw.end[i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.start[i] + CM_BOX_EPSILON; + } + } + tw.extents[0] = tw.extents[1] = tw.extents[2] = CM_BOX_EPSILON; + tw.size.Zero(); + + // setup trace heart planes + idCollisionModelManagerLocal::SetupTranslationHeartPlanes( &tw ); + tw.maxDistFromHeartPlane1 = CM_BOX_EPSILON; + tw.maxDistFromHeartPlane2 = CM_BOX_EPSILON; + // collision with single point + tw.numVerts = 1; + tw.vertices[0].p = tw.start; + tw.vertices[0].endp = tw.vertices[0].p + tw.dir; + tw.vertices[0].pl.FromRay( tw.vertices[0].p, tw.dir ); + tw.numEdges = tw.numPolys = 0; + tw.pointTrace = true; + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + // store results + *results = tw.trace; + results->endpos = start + results->fraction * (end - start); + results->endAxis = mat3_identity; + + if ( results->fraction < 1.0f ) { + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + idCollisionModelManagerLocal::numContacts = tw.numContacts; + return; + } + + // the trace fraction is too inaccurate to describe translations over huge distances + if ( tw.dir.LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + results->fraction = 0.0f; + results->endpos = start; + results->endAxis = trmAxis; + results->c.normal = vec3_origin; + results->c.material = NULL; + results->c.point = start; + if ( common->RW() ) { + common->RW()->DebugArrow( colorRed, start, end, 1 ); + } + common->Printf( "idCollisionModelManagerLocal::Translation: huge translation\n" ); + return; + } + + tw.pointTrace = false; + tw.size.Clear(); + + // setup trm structure + idCollisionModelManagerLocal::SetupTrm( &tw, trm ); + + trm_rotated = trmAxis.IsRotated(); + + // calculate vertex positions + if ( trm_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around the start position + tw.vertices[i].p *= trmAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // set trm at start position + tw.vertices[i].p += tw.start; + } + if ( model_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around model instead of rotating the model + tw.vertices[i].p *= invModelAxis; + } + } + + // add offset to start point + if ( trm_rotated ) { + dir = trm->offset * trmAxis; + tw.start += dir; + tw.end += dir; + } else { + tw.start += trm->offset; + tw.end += trm->offset; + } + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + tw.end *= invModelAxis; + tw.dir *= invModelAxis; + } + + // rotate trm polygon planes + if ( trm_rotated & model_rotated ) { + tmpAxis = trmAxis * invModelAxis; + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= tmpAxis; + } + } else if ( trm_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= trmAxis; + } + } else if ( model_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= invModelAxis; + } + } + + // setup trm polygons + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + // if the trm poly plane is facing in the movement direction + dist = poly->plane.Normal() * tw.dir; + if ( dist > 0.0f || ( !trm->isConvex && dist == 0.0f ) ) { + // this trm poly and it's edges and vertices need to be used for collision + poly->used = true; + for ( j = 0; j < poly->numEdges; j++ ) { + edge = &tw.edges[abs( poly->edges[j] )]; + edge->used = true; + tw.vertices[edge->vertexNum[0]].used = true; + tw.vertices[edge->vertexNum[1]].used = true; + } + } + } + + // setup trm vertices + for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) { + if ( !vert->used ) { + continue; + } + // get axial trm size after rotations + tw.size.AddPoint( vert->p - tw.start ); + // calculate the end position of each vertex for a full trace + vert->endp = vert->p + tw.dir; + // pluecker coordinate for vertex movement line + vert->pl.FromRay( vert->p, tw.dir ); + } + + // setup trm edges + for ( edge = tw.edges + 1, i = 1; i <= tw.numEdges; i++, edge++ ) { + if ( !edge->used ) { + continue; + } + // edge start, end and pluecker coordinate + edge->start = tw.vertices[edge->vertexNum[0]].p; + edge->end = tw.vertices[edge->vertexNum[1]].p; + edge->pl.FromLine( edge->start, edge->end ); + // calculate normal of plane through movement plane created by the edge + dir = edge->start - edge->end; + edge->cross[0] = dir[0] * tw.dir[1] - dir[1] * tw.dir[0]; + edge->cross[1] = dir[0] * tw.dir[2] - dir[2] * tw.dir[0]; + edge->cross[2] = dir[1] * tw.dir[2] - dir[2] * tw.dir[1]; + // bit for vertex sidedness bit cache + edge->bitNum = i; + } + + // set trm plane distances + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + if ( poly->used ) { + poly->plane.FitThroughPoint( tw.edges[abs(poly->edges[0])].start ); + } + } + + // bounds for full trace, a little bit larger for epsilons + for ( i = 0; i < 3; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + CM_BOX_EPSILON; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + CM_BOX_EPSILON; + } + if ( idMath::Fabs( tw.size[0][i] ) > idMath::Fabs( tw.size[1][i] ) ) { + tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + CM_BOX_EPSILON; + } else { + tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + CM_BOX_EPSILON; + } + } + + // setup trace heart planes + idCollisionModelManagerLocal::SetupTranslationHeartPlanes( &tw ); + tw.maxDistFromHeartPlane1 = 0; + tw.maxDistFromHeartPlane2 = 0; + // calculate maximum trm vertex distance from both heart planes + for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) { + if ( !vert->used ) { + continue; + } + dist = idMath::Fabs( tw.heartPlane1.Distance( vert->p ) ); + if ( dist > tw.maxDistFromHeartPlane1 ) { + tw.maxDistFromHeartPlane1 = dist; + } + dist = idMath::Fabs( tw.heartPlane2.Distance( vert->p ) ); + if ( dist > tw.maxDistFromHeartPlane2 ) { + tw.maxDistFromHeartPlane2 = dist; + } + } + // for epsilons + tw.maxDistFromHeartPlane1 += CM_BOX_EPSILON; + tw.maxDistFromHeartPlane2 += CM_BOX_EPSILON; + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + // if we're getting contacts + if ( tw.getContacts ) { + // move all contacts to world space + if ( model_rotated ) { + for ( i = 0; i < tw.numContacts; i++ ) { + tw.contacts[i].normal *= modelAxis; + tw.contacts[i].point *= modelAxis; + } + } + if ( modelOrigin != vec3_origin ) { + for ( i = 0; i < tw.numContacts; i++ ) { + tw.contacts[i].point += modelOrigin; + tw.contacts[i].dist += modelOrigin * tw.contacts[i].normal; + } + } + idCollisionModelManagerLocal::numContacts = tw.numContacts; + } else { + // store results + *results = tw.trace; + results->endpos = start + results->fraction * ( end - start ); + results->endAxis = trmAxis; + + if ( results->fraction < 1.0f ) { + // if the fraction is tiny the actual movement could end up zero + if ( results->fraction > 0.0f && results->endpos.Compare( start ) ) { + results->fraction = 0.0f; + } + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + } + +#ifdef _DEBUG + // test for missed collisions + if ( cm_debugCollision.GetBool() ) { + if ( !entered && !idCollisionModelManagerLocal::getContacts ) { + entered = 1; + // if the trm is stuck in the model + if ( idCollisionModelManagerLocal::Contents( results->endpos, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + trace_t tr; + + // test where the trm is stuck in the model + idCollisionModelManagerLocal::Contents( results->endpos, trm, trmAxis, -1, model, modelOrigin, modelAxis ); + // re-run collision detection to find out where it failed + idCollisionModelManagerLocal::Translation( &tr, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + entered = 0; + } + } +#endif +} diff --git a/neo/d3xp/AF.cpp b/neo/d3xp/AF.cpp new file mode 100644 index 00000000..6c6f2d51 --- /dev/null +++ b/neo/d3xp/AF.cpp @@ -0,0 +1,1272 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ +#define ARTICULATED_FIGURE_ANIM "af_pose" +#define POSE_BOUNDS_EXPANSION 5.0f + +/* +================ +idAF::idAF +================ +*/ +idAF::idAF() { + self = NULL; + animator = NULL; + modifiedAnim = 0; + baseOrigin.Zero(); + baseAxis.Identity(); + poseTime = -1; + restStartTime = -1; + isLoaded = false; + isActive = false; + hasBindConstraints = false; +} + +/* +================ +idAF::~idAF +================ +*/ +idAF::~idAF() { +} + +/* +================ +idAF::Save +================ +*/ +void idAF::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + savefile->WriteString( GetName() ); + savefile->WriteBool( hasBindConstraints ); + savefile->WriteVec3( baseOrigin ); + savefile->WriteMat3( baseAxis ); + savefile->WriteInt( poseTime ); + savefile->WriteInt( restStartTime ); + savefile->WriteBool( isLoaded ); + savefile->WriteBool( isActive ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idAF::Restore +================ +*/ +void idAF::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( name ); + savefile->ReadBool( hasBindConstraints ); + savefile->ReadVec3( baseOrigin ); + savefile->ReadMat3( baseAxis ); + savefile->ReadInt( poseTime ); + savefile->ReadInt( restStartTime ); + savefile->ReadBool( isLoaded ); + savefile->ReadBool( isActive ); + + animator = NULL; + modifiedAnim = 0; + + if ( self ) { + SetAnimator( self->GetAnimator() ); + Load( self, name ); + if ( hasBindConstraints ) { + AddBindConstraints(); + } + } + + savefile->ReadStaticObject( physicsObj ); + + if ( self ) { + if ( isActive ) { + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + + // switch to articulated figure physics + self->RestorePhysics( &physicsObj ); + physicsObj.EnableClip(); + } + UpdateAnimation(); + } +} + +/* +================ +idAF::UpdateAnimation +================ +*/ +bool idAF::UpdateAnimation() { + int i; + idVec3 origin, renderOrigin, bodyOrigin; + idMat3 axis, renderAxis, bodyAxis; + renderEntity_t *renderEntity; + + if ( !IsLoaded() ) { + return false; + } + + if ( !IsActive() ) { + return false; + } + + renderEntity = self->GetRenderEntity(); + if ( !renderEntity ) { + return false; + } + + if ( physicsObj.IsAtRest() ) { + if ( restStartTime == physicsObj.GetRestStartTime() ) { + return false; + } + restStartTime = physicsObj.GetRestStartTime(); + } + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // create an animation frame which reflects the current pose of the articulated figure + animator->InitAFPose(); + for ( i = 0; i < jointMods.Num(); i++ ) { + // check for the origin joint + if ( jointMods[i].jointHandle == 0 ) { + continue; + } + bodyOrigin = physicsObj.GetOrigin( jointMods[i].bodyId ); + bodyAxis = physicsObj.GetAxis( jointMods[i].bodyId ); + axis = jointMods[i].jointBodyAxis.Transpose() * ( bodyAxis * renderAxis.Transpose() ); + origin = ( bodyOrigin - jointMods[i].jointBodyOrigin * axis - renderOrigin ) * renderAxis.Transpose(); + animator->SetAFPoseJointMod( jointMods[i].jointHandle, jointMods[i].jointMod, axis, origin ); + } + animator->FinishAFPose( modifiedAnim, GetBounds().Expand( POSE_BOUNDS_EXPANSION ), gameLocal.time ); + animator->SetAFPoseBlendWeight( 1.0f ); + + return true; +} + +/* +================ +idAF::GetBounds + + returns bounds for the current pose +================ +*/ +idBounds idAF::GetBounds() const { + int i; + idAFBody *body; + idVec3 origin, entityOrigin; + idMat3 axis, entityAxis; + idBounds bounds, b; + + bounds.Clear(); + + // get model base transform + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + + entityAxis = baseAxis.Transpose() * axis; + entityOrigin = origin - baseOrigin * entityAxis; + + // get bounds relative to base + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + origin = ( body->GetWorldOrigin() - entityOrigin ) * entityAxis.Transpose(); + axis = body->GetWorldAxis() * entityAxis.Transpose(); + b.FromTransformedBounds( body->GetClipModel()->GetBounds(), origin, axis ); + + bounds += b; + } + + return bounds; +} + +/* +================ +idAF::SetupPose + + Transforms the articulated figure to match the current animation pose of the given entity. +================ +*/ +void idAF::SetupPose( idEntity *ent, int time ) { + int i; + idAFBody *body; + idVec3 origin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + } + + if ( isActive ) { + physicsObj.UpdateClipModels(); + } +} + +/* +================ +idAF::ChangePose + + Change the articulated figure to match the current animation pose of the given entity + and set the velocity relative to the previous pose. +================ +*/ +void idAF::ChangePose( idEntity *ent, int time ) { + int i; + float invDelta; + idAFBody *body; + idVec3 origin, lastOrigin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + invDelta = 1.0f / MS2SEC( time - poseTime ); + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + lastOrigin = body->GetWorldOrigin(); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + body->SetLinearVelocity( ( body->GetWorldOrigin() - lastOrigin ) * invDelta ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::EntitiesTouchingAF +================ +*/ +int idAF::EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const { + int i, j, numClipModels; + idAFBody *body; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + int numTouching; + + if ( !IsLoaded() ) { + return 0; + } + + numTouching = 0; + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), -1, clipModels, MAX_GENTITIES ); + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + for ( j = 0; j < numClipModels; j++ ) { + cm = clipModels[j]; + + if ( !cm || cm->GetEntity() == self ) { + continue; + } + + if ( !cm->IsTraceModel() ) { + continue; + } + + if ( !body->GetClipModel()->GetAbsBounds().IntersectsBounds( cm->GetAbsBounds() ) ) { + continue; + } + + if ( gameLocal.clip.ContentsModel( body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), -1, cm->Handle(), cm->GetOrigin(), cm->GetAxis() ) ) { + touchList[ numTouching ].touchedByBody = body; + touchList[ numTouching ].touchedClipModel = cm; + touchList[ numTouching ].touchedEnt = cm->GetEntity(); + numTouching++; + clipModels[j] = NULL; + } + } + } + + return numTouching; +} + +/* +================ +idAF::BodyForClipModelId +================ +*/ +int idAF::BodyForClipModelId( int id ) const { + if ( id >= 0 ) { + return id; + } else { + id = CLIPMODEL_ID_TO_JOINT_HANDLE( id ); + if ( id < jointBody.Num() ) { + return jointBody[id]; + } else { + return 0; + } + } +} + +/* +================ +idAF::GetPhysicsToVisualTransform +================ +*/ +void idAF::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const { + origin = - baseOrigin; + axis = baseAxis.Transpose(); +} + +/* +================ +idAF::GetImpactInfo +================ +*/ +void idAF::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + SetupPose( self, gameLocal.time ); + physicsObj.GetImpactInfo( BodyForClipModelId( id ), point, info ); +} + +/* +================ +idAF::ApplyImpulse +================ +*/ +void idAF::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + SetupPose( self, gameLocal.time ); + physicsObj.ApplyImpulse( BodyForClipModelId( id ), point, impulse ); +} + +/* +================ +idAF::AddForce +================ +*/ +void idAF::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + SetupPose( self, gameLocal.time ); + physicsObj.AddForce( BodyForClipModelId( id ), point, force ); +} + +/* +================ +idAF::AddBody + + Adds a body. +================ +*/ +void idAF::AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ) { + int index; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + handle = animator->GetJointHandle( jointName ); + if ( handle == INVALID_JOINT ) { + gameLocal.Error( "idAF for entity '%s' at (%s) modifies unknown joint '%s'", self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), jointName ); + } + + assert( handle < animator->NumJoints() ); + origin = joints[ handle ].ToVec3(); + axis = joints[ handle ].ToMat3(); + + index = jointMods.Num(); + jointMods.SetNum( index + 1 ); + jointMods[index].bodyId = physicsObj.GetBodyId( body ); + jointMods[index].jointHandle = handle; + jointMods[index].jointMod = mod; + jointMods[index].jointBodyOrigin = ( body->GetWorldOrigin() - origin ) * axis.Transpose(); + jointMods[index].jointBodyAxis = body->GetWorldAxis() * axis.Transpose(); +} + +/* +================ +idAF::SetBase + + Sets the base body. +================ +*/ +void idAF::SetBase( idAFBody *body, const idJointMat *joints ) { + physicsObj.ForceBodyId( body, 0 ); + baseOrigin = body->GetWorldOrigin(); + baseAxis = body->GetWorldAxis(); + AddBody( body, joints, animator->GetJointName( animator->GetFirstChild( "origin" ) ), AF_JOINTMOD_AXIS ); +} + +/* +================ +idAF::LoadBody +================ +*/ +bool idAF::LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ) { + int id, i; + float length, mass; + idTraceModel trm; + idClipModel *clip; + idAFBody *body; + idMat3 axis, inertiaTensor; + idVec3 centerOfMass, origin; + idBounds bounds; + idList jointList; + + origin = fb->origin.ToVec3(); + axis = fb->angles.ToMat3(); + bounds[0] = fb->v1.ToVec3(); + bounds[1] = fb->v2.ToVec3(); + + switch( fb->modelType ) { + case TRM_BOX: { + trm.SetupBox( bounds ); + break; + } + case TRM_OCTAHEDRON: { + trm.SetupOctahedron( bounds ); + break; + } + case TRM_DODECAHEDRON: { + trm.SetupDodecahedron( bounds ); + break; + } + case TRM_CYLINDER: { + trm.SetupCylinder( bounds, fb->numSides ); + break; + } + case TRM_CONE: { + // place the apex at the origin + bounds[0].z -= bounds[1].z; + bounds[1].z = 0.0f; + trm.SetupCone( bounds, fb->numSides ); + break; + } + case TRM_BONE: { + // direction of bone + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + length = axis[2].Normalize(); + // axis of bone trace model + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + // create bone trace model + trm.SetupBone( length, fb->width ); + break; + } + default: + assert( 0 ); + break; + } + trm.GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + trm.Translate( -centerOfMass ); + origin += centerOfMass * axis; + + body = physicsObj.GetBody( fb->name ); + if ( body ) { + clip = body->GetClipModel(); + if ( !clip->IsEqual( trm ) ) { + clip = new (TAG_PHYSICS_CLIP_AF) idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( gameLocal.clip, self, 0, origin, axis ); + body->SetClipModel( clip ); + } + clip->SetContents( fb->contents ); + body->SetDensity( fb->density, fb->inertiaScale ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( axis ); + id = physicsObj.GetBodyId( body ); + } + else { + clip = new (TAG_PHYSICS_CLIP_AF) idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( gameLocal.clip, self, 0, origin, axis ); + body = new (TAG_PHYSICS_AF) idAFBody( fb->name, clip, fb->density ); + if ( fb->inertiaScale != mat3_identity ) { + body->SetDensity( fb->density, fb->inertiaScale ); + } + id = physicsObj.AddBody( body ); + } + if ( fb->linearFriction != -1.0f ) { + body->SetFriction( fb->linearFriction, fb->angularFriction, fb->contactFriction ); + } + body->SetClipMask( fb->clipMask ); + body->SetSelfCollision( fb->selfCollision ); + + if ( fb->jointName == "origin" ) { + SetBase( body, joints ); + } else { + AFJointModType_t mod; + if ( fb->jointMod == DECLAF_JOINTMOD_AXIS ) { + mod = AF_JOINTMOD_AXIS; + } else if ( fb->jointMod == DECLAF_JOINTMOD_ORIGIN ) { + mod = AF_JOINTMOD_ORIGIN; + } else if ( fb->jointMod == DECLAF_JOINTMOD_BOTH ) { + mod = AF_JOINTMOD_BOTH; + } else { + mod = AF_JOINTMOD_AXIS; + } + AddBody( body, joints, fb->jointName, mod ); + } + + if ( fb->frictionDirection.ToVec3() != vec3_origin ) { + body->SetFrictionDirection( fb->frictionDirection.ToVec3() ); + } + if ( fb->contactMotorDirection.ToVec3() != vec3_origin ) { + body->SetContactMotorDirection( fb->contactMotorDirection.ToVec3() ); + } + + // update table to find the nearest articulated figure body for a joint of the skeletal model + animator->GetJointList( fb->containedJoints, jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + if ( jointBody[ jointList[ i ] ] != -1 ) { + gameLocal.Warning( "%s: joint '%s' is already contained by body '%s'", + name.c_str(), animator->GetJointName( (jointHandle_t)jointList[i] ), + physicsObj.GetBody( jointBody[ jointList[ i ] ] )->GetName().c_str() ); + } + jointBody[ jointList[ i ] ] = id; + } + + return true; +} + +/* +================ +idAF::LoadConstraint +================ +*/ +bool idAF::LoadConstraint( const idDeclAF_Constraint *fc ) { + idAFBody *body1, *body2; + idAngles angles; + idMat3 axis; + + body1 = physicsObj.GetBody( fc->body1 ); + body2 = physicsObj.GetBody( fc->body2 ); + + switch( fc->type ) { + case DECLAF_CONSTRAINT_FIXED: { + idAFConstraint_Fixed *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new (TAG_PHYSICS_AF) idAFConstraint_Fixed( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + break; + } + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new (TAG_PHYSICS_AF) idAFConstraint_BallAndSocketJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0], fc->shaft[0].ToVec3() ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1], fc->shaft[0].ToVec3() ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new (TAG_PHYSICS_AF) idAFConstraint_UniversalJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetShafts( fc->shaft[0].ToVec3(), fc->shaft[1].ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0] ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1] ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_HINGE: { + idAFConstraint_Hinge *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new (TAG_PHYSICS_AF) idAFConstraint_Hinge( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetAxis( fc->axis.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + idVec3 left, up, axis, shaft; + fc->axis.ToVec3().OrthogonalBasis( left, up ); + axis = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[0] ); + shaft = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[2] ); + c->SetLimit( axis, fc->limitAngles[1], shaft ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_SLIDER: { + idAFConstraint_Slider *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new (TAG_PHYSICS_AF) idAFConstraint_Slider( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAxis( fc->axis.ToVec3() ); + break; + } + case DECLAF_CONSTRAINT_SPRING: { + idAFConstraint_Spring *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new (TAG_PHYSICS_AF) idAFConstraint_Spring( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3(), fc->anchor2.ToVec3() ); + c->SetSpring( fc->stretch, fc->compress, fc->damping, fc->restLength ); + c->SetLimit( fc->minLength, fc->maxLength ); + break; + } + } + return true; +} + +/* +================ +GetJointTransform +================ +*/ +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + jointHandle_t joint; + + joint = reinterpret_cast(model)->GetJointHandle( jointName ); + if ( ( joint >= 0 ) && ( joint < reinterpret_cast(model)->NumJoints() ) ) { + origin = frame[ joint ].ToVec3(); + axis = frame[ joint ].ToMat3(); + return true; + } else { + return false; + } +} + +/* +================ +idAF::Load +================ +*/ +bool idAF::Load( idEntity *ent, const char *fileName ) { + int i, j; + const idDeclAF *file; + const idDeclModelDef *modelDef; + idRenderModel *model; + int numJoints; + idJointMat *joints; + + assert( ent ); + + self = ent; + physicsObj.SetSelf( self ); + + if ( animator == NULL ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s): NULL animator\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + name = fileName; + name.StripFileExtension(); + + file = static_cast( declManager->FindType( DECL_AF, name ) ); + if ( !file ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s)\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + if ( file->bodies.Num() == 0 || file->bodies[0]->jointName != "origin" ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no body which modifies the origin joint.", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + modelDef = animator->ModelDef(); + if ( modelDef == NULL || modelDef->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted modelDef '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), modelDef ? modelDef->GetName() : "" ); + return false; + } + + model = animator->ModelHandle(); + if ( model == NULL || model->IsDefaultModel() ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted model '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), model ? model->Name() : "" ); + return false; + } + + // get the modified animation + modifiedAnim = animator->GetAnim( ARTICULATED_FIGURE_ANIM ); + if ( !modifiedAnim ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no modified animation '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), ARTICULATED_FIGURE_ANIM ); + return false; + } + + // create the animation frame used to setup the articulated figure + numJoints = animator->NumJoints(); + joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + gameEdit->ANIM_CreateAnimFrame( model, animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset(), animator->RemoveOrigin() ); + + // set all vector positions from model joints + file->Finish( GetJointTransform, joints, animator ); + + // initialize articulated figure physics + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( file->clipMask ); + physicsObj.SetDefaultFriction( file->defaultLinearFriction, file->defaultAngularFriction, file->defaultContactFriction ); + physicsObj.SetSuspendSpeed( file->suspendVelocity, file->suspendAcceleration ); + physicsObj.SetSuspendTolerance( file->noMoveTime, file->noMoveTranslation, file->noMoveRotation ); + physicsObj.SetSuspendTime( file->minMoveTime, file->maxMoveTime ); + physicsObj.SetSelfCollision( file->selfCollision ); + + // clear the list with transforms from joints to bodies + jointMods.SetNum( 0 ); + + // clear the joint to body conversion list + jointBody.AssureSize( animator->NumJoints() ); + for ( i = 0; i < jointBody.Num(); i++ ) { + jointBody[i] = -1; + } + + // delete any bodies in the physicsObj that are no longer in the idDeclAF + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + idAFBody *body = physicsObj.GetBody( i ); + for ( j = 0; j < file->bodies.Num(); j++ ) { + if ( file->bodies[j]->name.Icmp( body->GetName() ) == 0 ) { + break; + } + } + if ( j >= file->bodies.Num() ) { + physicsObj.DeleteBody( i ); + i--; + } + } + + // delete any constraints in the physicsObj that are no longer in the idDeclAF + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + idAFConstraint *constraint = physicsObj.GetConstraint( i ); + for ( j = 0; j < file->constraints.Num(); j++ ) { + // idADConstraint enum is a superset of declADConstraint, so the cast is valid + if ( file->constraints[j]->name.Icmp( constraint->GetName() ) == 0 && + (constraintType_t)(file->constraints[j]->type) == constraint->GetType() ) { + break; + } + } + if ( j >= file->constraints.Num() ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } + + // load bodies from the file + for ( i = 0; i < file->bodies.Num(); i++ ) { + LoadBody( file->bodies[i], joints ); + } + + // load constraints from the file + for ( i = 0; i < file->constraints.Num(); i++ ) { + LoadConstraint( file->constraints[i] ); + } + + physicsObj.UpdateClipModels(); + + // check if each joint is contained by a body + for( i = 0; i < animator->NumJoints(); i++ ) { + if ( jointBody[i] == -1 ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) joint '%s' is not contained by a body", + name.c_str(), self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), animator->GetJointName( (jointHandle_t)i ) ); + } + } + + physicsObj.SetMass( file->totalMass ); + physicsObj.SetChanged(); + + // disable the articulated figure for collision detection until activated + physicsObj.DisableClip(); + + isLoaded = true; + + return true; +} + +/* +================ +idAF::Start +================ +*/ +void idAF::Start() { + if ( !IsLoaded() ) { + return; + } + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + // switch to articulated figure physics + self->SetPhysics( &physicsObj ); + // start the articulated figure physics simulation + physicsObj.EnableClip(); + physicsObj.Activate(); + isActive = true; +} + +/* +================ +idAF::TestSolid +================ +*/ +bool idAF::TestSolid() const { + int i; + idAFBody *body; + trace_t trace; + idStr str; + bool solid; + + if ( !IsLoaded() ) { + return false; + } + + if ( !af_testSolid.GetBool() ) { + return false; + } + + solid = false; + + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + body = physicsObj.GetBody( i ); + if ( gameLocal.clip.Translation( trace, body->GetWorldOrigin(), body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), body->GetClipMask(), self ) ) { + float depth = idMath::Fabs( trace.c.point * trace.c.normal - trace.c.dist ); + + body->SetWorldOrigin( body->GetWorldOrigin() + trace.c.normal * ( depth + 8.0f ) ); + + gameLocal.DWarning( "%s: body '%s' stuck in %d (normal = %.2f %.2f %.2f, depth = %.2f)", self->name.c_str(), + body->GetName().c_str(), trace.c.contents, trace.c.normal.x, trace.c.normal.y, trace.c.normal.z, depth ); + solid = true; + + } + } + return solid; +} + +/* +================ +idAF::StartFromCurrentPose +================ +*/ +void idAF::StartFromCurrentPose( int inheritVelocityTime ) { + + if ( !IsLoaded() ) { + return; + } + + // if the ragdoll should inherit velocity from the animation + if ( inheritVelocityTime > 0 ) { + + // make sure the ragdoll is at rest + physicsObj.PutToRest(); + + // set the pose for some time back + SetupPose( self, gameLocal.time - inheritVelocityTime ); + + // change the pose for the current time and set velocities + ChangePose( self, gameLocal.time ); + } + else { + // transform the articulated figure to reflect the current animation pose + SetupPose( self, gameLocal.time ); + } + + physicsObj.UpdateClipModels(); + + TestSolid(); + + Start(); + + UpdateAnimation(); + + // update the render entity origin and axis + self->UpdateModel(); + + // make sure the renderer gets the updated origin and axis + self->Present(); +} + +/* +================ +idAF::Stop +================ +*/ +void idAF::Stop() { + // disable the articulated figure for collision detection + physicsObj.UnlinkClip(); + isActive = false; +} + +/* +================ +idAF::Rest +================ +*/ +void idAF::Rest() { + physicsObj.PutToRest(); +} + +/* +================ +idAF::SetConstraintPosition + + Only moves constraints that bind the entity to another entity. +================ +*/ +void idAF::SetConstraintPosition( const char *name, const idVec3 &pos ) { + idAFConstraint *constraint; + + constraint = GetPhysics()->GetConstraint( name ); + + if ( !constraint ) { + gameLocal.Warning( "can't find a constraint with the name '%s'", name ); + return; + } + + if ( constraint->GetBody2() != NULL ) { + gameLocal.Warning( "constraint '%s' does not bind to another entity", name ); + return; + } + + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *bs = static_cast(constraint); + bs->Translate( pos - bs->GetAnchor() ); + break; + } + case CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *uj = static_cast(constraint); + uj->Translate( pos - uj->GetAnchor() ); + break; + } + case CONSTRAINT_HINGE: { + idAFConstraint_Hinge *hinge = static_cast(constraint); + hinge->Translate( pos - hinge->GetAnchor() ); + break; + } + default: { + gameLocal.Warning( "cannot set the constraint position for '%s'", name ); + break; + } + } +} + +/* +================ +idAF::SaveState +================ +*/ +void idAF::SaveState( idDict &args ) const { + int i; + idAFBody *body; + idStr key, value; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + key = "body " + body->GetName(); + value = body->GetWorldOrigin().ToString( 8 ); + value += " "; + value += body->GetWorldAxis().ToAngles().ToString( 8 ); + args.Set( key, value ); + } +} + +/* +================ +idAF::LoadState +================ +*/ +void idAF::LoadState( const idDict &args ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idVec3 origin; + idAngles angles; + + kv = args.MatchPrefix( "body ", NULL ); + while ( kv ) { + + name = kv->GetKey(); + name.Strip( "body " ); + body = physicsObj.GetBody( name ); + if ( body ) { + sscanf( kv->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( angles.ToMat3() ); + } else { + gameLocal.Warning("Unknown body part %s in articulated figure %s", name.c_str(), this->name.c_str()); + } + + kv = args.MatchPrefix( "body ", kv ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::AddBindConstraints +================ +*/ +void idAF::AddBindConstraints() { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idLexer lexer; + idToken type, bodyName, jointName; + idVec3 origin, renderOrigin; + idMat3 axis, renderAxis; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // parse all the bind constraints + for ( kv = args.MatchPrefix( "bindConstraint ", NULL ); kv; kv = args.MatchPrefix( "bindConstraint ", kv ) ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + + lexer.ReadToken( &bodyName ); + body = physicsObj.GetBody( bodyName ); + if ( !body ) { + gameLocal.Warning( "idAF::AddBindConstraints: body '%s' not found on entity '%s'", bodyName.c_str(), self->name.c_str() ); + lexer.FreeSource(); + continue; + } + + if ( type.Icmp( "fixed" ) == 0 ) { + idAFConstraint_Fixed *c; + + c = new (TAG_PHYSICS_AF) idAFConstraint_Fixed( name, body, NULL ); + physicsObj.AddConstraint( c ); + } + else if ( type.Icmp( "ballAndSocket" ) == 0 ) { + idAFConstraint_BallAndSocketJoint *c; + + c = new (TAG_PHYSICS_AF) idAFConstraint_BallAndSocketJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + } + else if ( type.Icmp( "universal" ) == 0 ) { + idAFConstraint_UniversalJoint *c; + + c = new (TAG_PHYSICS_AF) idAFConstraint_UniversalJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + } + else { + gameLocal.Warning( "idAF::AddBindConstraints: unknown constraint type '%s' on entity '%s'", type.c_str(), self->name.c_str() ); + } + + lexer.FreeSource(); + } + + hasBindConstraints = true; +} + +/* +================ +idAF::RemoveBindConstraints +================ +*/ +void idAF::RemoveBindConstraints() { + const idKeyValue *kv; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + idStr name; + + kv = args.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + if ( physicsObj.GetConstraint( name ) ) { + physicsObj.DeleteConstraint( name ); + } + + kv = args.MatchPrefix( "bindConstraint ", kv ); + } + + hasBindConstraints = false; +} diff --git a/neo/d3xp/AF.h b/neo/d3xp/AF.h new file mode 100644 index 00000000..da2da023 --- /dev/null +++ b/neo/d3xp/AF.h @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_AF_H__ +#define __GAME_AF_H__ + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ + +typedef struct jointConversion_s { + int bodyId; // id of the body + jointHandle_t jointHandle; // handle of joint this body modifies + AFJointModType_t jointMod; // modify joint axis, origin or both + idVec3 jointBodyOrigin; // origin of body relative to joint + idMat3 jointBodyAxis; // axis of body relative to joint +} jointConversion_t; + +typedef struct afTouch_s { + idEntity * touchedEnt; + idClipModel * touchedClipModel; + idAFBody * touchedByBody; +} afTouch_t; + +class idAF { +public: + idAF(); + ~idAF(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetAnimator( idAnimator *a ) { animator = a; } + bool Load( idEntity *ent, const char *fileName ); + bool IsLoaded() const { return isLoaded && self != NULL; } + const char * GetName() const { return name.c_str(); } + void SetupPose( idEntity *ent, int time ); + void ChangePose( idEntity *ent, int time ); + int EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const; + void Start(); + void StartFromCurrentPose( int inheritVelocityTime ); + void Stop(); + void Rest(); + bool IsActive() const { return isActive; } + void SetConstraintPosition( const char *name, const idVec3 &pos ); + + idPhysics_AF * GetPhysics() { return &physicsObj; } + const idPhysics_AF * GetPhysics() const { return &physicsObj; } + idBounds GetBounds() const; + bool UpdateAnimation(); + + void GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const; + void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints(); + void RemoveBindConstraints(); + +protected: + idStr name; // name of the loaded .af file + idPhysics_AF physicsObj; // articulated figure physics + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // anim to modify + idVec3 baseOrigin; // offset of base body relative to skeletal model origin + idMat3 baseAxis; // axis of base body relative to skeletal model origin + idList jointMods; // list with transforms from skeletal model joints to articulated figure bodies + idList jointBody; // table to find the nearest articulated figure body for a joint of the skeletal model + int poseTime; // last time the articulated figure was transformed to reflect the current animation pose + int restStartTime; // time the articulated figure came to rest + bool isLoaded; // true when the articulated figure is properly loaded + bool isActive; // true if the articulated figure physics is active + bool hasBindConstraints; // true if the bind constraints have been added + +protected: + void SetBase( idAFBody *body, const idJointMat *joints ); + void AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ); + + bool LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ); + bool LoadConstraint( const idDeclAF_Constraint *fc ); + + bool TestSolid() const; +}; + +#endif /* !__GAME_AF_H__ */ diff --git a/neo/d3xp/AFEntity.cpp b/neo/d3xp/AFEntity.cpp new file mode 100644 index 00000000..be7378df --- /dev/null +++ b/neo/d3xp/AFEntity.cpp @@ -0,0 +1,3676 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idMultiModelAF + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMultiModelAF ) +END_CLASS + +/* +================ +idMultiModelAF::Spawn +================ +*/ +void idMultiModelAF::Spawn() { + physicsObj.SetSelf( this ); +} + +/* +================ +idMultiModelAF::~idMultiModelAF +================ +*/ +idMultiModelAF::~idMultiModelAF() { + int i; + + for ( i = 0; i < modelDefHandles.Num(); i++ ) { + if ( modelDefHandles[i] != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandles[i] ); + modelDefHandles[i] = -1; + } + } +} + +/* +================ +idMultiModelAF::SetModelForId +================ +*/ +void idMultiModelAF::SetModelForId( int id, const idStr &modelName ) { + modelHandles.AssureSize( id+1, NULL ); + modelDefHandles.AssureSize( id+1, -1 ); + modelHandles[id] = renderModelManager->FindModel( modelName ); +} + +/* +================ +idMultiModelAF::Present +================ +*/ +void idMultiModelAF::Present() { + int i; + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + for ( i = 0; i < modelHandles.Num(); i++ ) { + + if ( !modelHandles[i] ) { + continue; + } + + renderEntity.origin = physicsObj.GetOrigin( i ); + renderEntity.axis = physicsObj.GetAxis( i ); + renderEntity.hModel = modelHandles[i]; + renderEntity.bodyId = i; + + // add to refresh list + if ( modelDefHandles[i] == -1 ) { + modelDefHandles[i] = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandles[i], &renderEntity ); + } + } +} + +/* +================ +idMultiModelAF::Think +================ +*/ +void idMultiModelAF::Think() { + RunPhysics(); + Present(); +} + + +/* +=============================================================================== + + idChain + +=============================================================================== +*/ + +CLASS_DECLARATION( idMultiModelAF, idChain ) +END_CLASS + +/* +================ +idChain::BuildChain + + builds a chain hanging down from the ceiling + the highest link is a child of the link below it etc. + this allows an object to be attached to multiple chains while keeping a single tree structure +================ +*/ +void idChain::BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld ) { + int i; + float halfLinkLength = linkLength * 0.5f; + idTraceModel trm; + idClipModel *clip; + idAFBody *body, *lastBody; + idAFConstraint_BallAndSocketJoint *bsj; + idAFConstraint_UniversalJoint *uj; + idVec3 org; + + // create a trace model + trm = idTraceModel( linkLength, linkWidth ); + trm.Translate( -trm.offset ); + + org = origin - idVec3( 0, 0, halfLinkLength ); + + lastBody = NULL; + for ( i = 0; i < numLinks; i++ ) { + + // add body + clip = new (TAG_PHYSICS_CLIP_AF) idClipModel( trm ); + clip->SetContents( CONTENTS_SOLID ); + clip->Link( gameLocal.clip, this, 0, org, mat3_identity ); + body = new (TAG_PHYSICS_AF) idAFBody( name + idStr(i), clip, density ); + physicsObj.AddBody( body ); + + // visual model for body + SetModelForId( physicsObj.GetBodyId( body ), spawnArgs.GetString( "model" ) ); + + // add constraint + if ( bindToWorld ) { + if ( !lastBody ) { + uj = new (TAG_PHYSICS_AF) idAFConstraint_UniversalJoint( name + idStr(i), body, lastBody ); + uj->SetShafts( idVec3( 0, 0, -1 ), idVec3( 0, 0, 1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, -1 ), 30.0f ); + //uj->SetPyramidLimit( idVec3( 0, 0, -1 ), idVec3( 1, 0, 0 ), 90.0f, 30.0f ); + } + else { + uj = new (TAG_PHYSICS_AF) idAFConstraint_UniversalJoint( name + idStr(i), lastBody, body ); + uj->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, 1 ), 30.0f ); + } + uj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + uj->SetFriction( 0.9f ); + physicsObj.AddConstraint( uj ); + } + else { + if ( lastBody ) { + bsj = new (TAG_PHYSICS_AF) idAFConstraint_BallAndSocketJoint( "joint" + idStr(i), lastBody, body ); + bsj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + bsj->SetConeLimit( idVec3( 0, 0, 1 ), 60.0f, idVec3( 0, 0, 1 ) ); + physicsObj.AddConstraint( bsj ); + } + } + + org[2] -= linkLength; + + lastBody = body; + } +} + +/* +================ +idChain::Spawn +================ +*/ +void idChain::Spawn() { + int numLinks; + float length, linkLength, linkWidth, density; + bool drop; + idVec3 origin; + + spawnArgs.GetBool( "drop", "0", drop ); + spawnArgs.GetInt( "links", "3", numLinks ); + spawnArgs.GetFloat( "length", idStr( numLinks * 32.0f ), length ); + spawnArgs.GetFloat( "width", "8", linkWidth ); + spawnArgs.GetFloat( "density", "0.2", density ); + linkLength = length / numLinks; + origin = GetPhysics()->GetOrigin(); + + // initialize physics + physicsObj.SetSelf( this ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY ); + SetPhysics( &physicsObj ); + + BuildChain( "link", origin, linkLength, linkWidth, density, numLinks, !drop ); +} + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +CLASS_DECLARATION( idAnimatedEntity, idAFAttachment ) +END_CLASS + +/* +===================== +idAFAttachment::idAFAttachment +===================== +*/ +idAFAttachment::idAFAttachment() { + body = NULL; + combatModel = NULL; + idleAnim = 0; + attachJoint = INVALID_JOINT; +} + +/* +===================== +idAFAttachment::~idAFAttachment +===================== +*/ +idAFAttachment::~idAFAttachment() { + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; +} + +/* +===================== +idAFAttachment::Spawn +===================== +*/ +void idAFAttachment::Spawn() { + idleAnim = animator.GetAnim( "idle" ); +} + +/* +===================== +idAFAttachment::SetBody +===================== +*/ +void idAFAttachment::SetBody( idEntity *bodyEnt, const char *model, jointHandle_t attachJoint ) { + bool bleed; + + body = bodyEnt; + this->attachJoint = attachJoint; + SetModel( model ); + fl.takedamage = true; + + bleed = body->spawnArgs.GetBool( "bleed" ); + spawnArgs.SetBool( "bleed", bleed ); +} + +/* +===================== +idAFAttachment::ClearBody +===================== +*/ +void idAFAttachment::ClearBody() { + body = NULL; + attachJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +idAFAttachment::GetBody +===================== +*/ +idEntity *idAFAttachment::GetBody() const { + return body; +} + +/* +================ +idAFAttachment::Save + +archive object for savegame file +================ +*/ +void idAFAttachment::Save( idSaveGame *savefile ) const { + savefile->WriteObject( body ); + savefile->WriteInt( idleAnim ); + savefile->WriteJoint( attachJoint ); +} + +/* +================ +idAFAttachment::Restore + +unarchives object from save game file +================ +*/ +void idAFAttachment::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( body ) ); + savefile->ReadInt( idleAnim ); + savefile->ReadJoint( attachJoint ); + + SetCombatModel(); + LinkCombat(); +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void idAFAttachment::Hide() { + idEntity::Hide(); + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void idAFAttachment::Show() { + idEntity::Show(); + LinkCombat(); +} + +/* +============ +idAFAttachment::Damage + +Pass damage to body at the bindjoint +============ +*/ +void idAFAttachment::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if ( body ) { + body->Damage( inflictor, attacker, dir, damageDefName, damageScale, attachJoint ); + } +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void idAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( attachJoint ); + body->AddDamageEffect( c, velocity, damageDefName ); + } +} + +/* +================ +idAFAttachment::GetImpactInfo +================ +*/ +void idAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( attachJoint ), point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFAttachment::ApplyImpulse +================ +*/ +void idAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( attachJoint ), point, impulse ); + } else { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFAttachment::AddForce +================ +*/ +void idAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( attachJoint ), point, force ); + } else { + idEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +idAFAttachment::PlayIdleAnim +================ +*/ +void idAFAttachment::PlayIdleAnim( int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( ANIMCHANNEL_ALL )->AnimNum() ) ) { + animator.CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, blendTime ); + } +} + +/* +================ +idAfAttachment::Think +================ +*/ +void idAFAttachment::Think() { + idAnimatedEntity::Think(); + if ( thinkFlags & TH_UPDATEPARTICLES ) { + UpdateDamageEffects(); + } +} + +/* +================ +idAFAttachment::SetCombatModel +================ +*/ +void idAFAttachment::SetCombatModel() { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { + combatModel = new (TAG_PHYSICS_CLIP_AF) idClipModel( modelDefHandle ); + } + combatModel->SetOwner( body ); +} + +/* +================ +idAFAttachment::GetCombatModel +================ +*/ +idClipModel *idAFAttachment::GetCombatModel() const { + return combatModel; +} + +/* +================ +idAFAttachment::LinkCombat +================ +*/ +void idAFAttachment::LinkCombat() { + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { + combatModel->Link( gameLocal.clip, this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); + } +} + +/* +================ +idAFAttachment::UnlinkCombat +================ +*/ +void idAFAttachment::UnlinkCombat() { + if ( combatModel ) { + combatModel->Unlink(); + } +} + + +/* +=============================================================================== + + idAFEntity_Base + +=============================================================================== +*/ + +const idEventDef EV_SetConstraintPosition( "SetConstraintPosition", "sv" ); + +CLASS_DECLARATION( idAnimatedEntity, idAFEntity_Base ) + EVENT( EV_SetConstraintPosition, idAFEntity_Base::Event_SetConstraintPosition ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +idAFEntity_Base::idAFEntity_Base +================ +*/ +idAFEntity_Base::idAFEntity_Base() { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); +} + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +idAFEntity_Base::~idAFEntity_Base() { + delete combatModel; + combatModel = NULL; +} + +/* +================ +idAFEntity_Base::Save +================ +*/ +void idAFEntity_Base::Save( idSaveGame *savefile ) const { + savefile->WriteInt( combatModelContents ); + savefile->WriteClipModel( combatModel ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( nextSoundTime ); + af.Save( savefile ); +} + +/* +================ +idAFEntity_Base::Restore +================ +*/ +void idAFEntity_Base::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( combatModelContents ); + savefile->ReadClipModel( combatModel ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( nextSoundTime ); + LinkCombat(); + + af.Restore( savefile ); +} + +/* +================ +idAFEntity_Base::Spawn +================ +*/ +void idAFEntity_Base::Spawn() { + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + nextSoundTime = 0; +} + +/* +================ +idAFEntity_Base::LoadAF +================ +*/ +bool idAFEntity_Base::LoadAF() { + idStr fileName; + + if ( !spawnArgs.GetString( "articulatedFigure", "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + if ( !af.Load( this, fileName ) ) { + gameLocal.Error( "idAFEntity_Base::LoadAF: Couldn't load af file '%s' on entity '%s'", fileName.c_str(), name.c_str() ); + } + float mass = spawnArgs.GetFloat( "mass", "-1" ); + if ( mass > 0.0f ) { + af.GetPhysics()->SetMass( mass ); + } + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +idAFEntity_Base::Think +================ +*/ +void idAFEntity_Base::Think() { + RunPhysics(); + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + +/* +================ +idAFEntity_Base::BodyForClipModelId +================ +*/ +int idAFEntity_Base::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +idAFEntity_Base::SaveState +================ +*/ +void idAFEntity_Base::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +idAFEntity_Base::LoadState +================ +*/ +void idAFEntity_Base::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +idAFEntity_Base::AddBindConstraints +================ +*/ +void idAFEntity_Base::AddBindConstraints() { + af.AddBindConstraints(); +} + +/* +================ +idAFEntity_Base::RemoveBindConstraints +================ +*/ +void idAFEntity_Base::RemoveBindConstraints() { + af.RemoveBindConstraints(); +} + +/* +================ +idAFEntity_Base::AddDamageEffect +================ +*/ +void idAFEntity_Base::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) { + idAnimatedEntity::AddDamageEffect( collision, velocity, damageDefName ); +} + +/* +================ +idAFEntity_Base::GetImpactInfo +================ +*/ +void idAFEntity_Base::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFEntity_Base::ApplyImpulse +================ +*/ +void idAFEntity_Base::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFEntity_Base::AddForce +================ +*/ +void idAFEntity_Base::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + idEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +idAFEntity_Base::Collide +================ +*/ +bool idAFEntity_Base::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { + f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +idAFEntity_Base::GetPhysicsToVisualTransform +================ +*/ +bool idAFEntity_Base::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return idEntity::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +idAFEntity_Base::UpdateAnimationControllers +================ +*/ +bool idAFEntity_Base::UpdateAnimationControllers() { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +idAFEntity_Base::SetCombatModel +================ +*/ +void idAFEntity_Base::SetCombatModel() { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { + combatModel = new (TAG_PHYSICS_CLIP_AF) idClipModel( modelDefHandle ); + } +} + +/* +================ +idAFEntity_Base::GetCombatModel +================ +*/ +idClipModel *idAFEntity_Base::GetCombatModel() const { + return combatModel; +} + +/* +================ +idAFEntity_Base::SetCombatContents +================ +*/ +void idAFEntity_Base::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +idAFEntity_Base::LinkCombat +================ +*/ +void idAFEntity_Base::LinkCombat() { + if ( fl.hidden ) { + return; + } + if ( combatModel ) { + combatModel->Link( gameLocal.clip, this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); + } +} + +/* +================ +idAFEntity_Base::UnlinkCombat +================ +*/ +void idAFEntity_Base::UnlinkCombat() { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +idAFEntity_Base::FreeModelDef +================ +*/ +void idAFEntity_Base::FreeModelDef() { + UnlinkCombat(); + idEntity::FreeModelDef(); +} + +/* +=============== +idAFEntity_Base::ShowEditingDialog +=============== +*/ +void idAFEntity_Base::ShowEditingDialog() { +} + +/* +================ +idAFEntity_Base::DropAFs + + The entity should have the following key/value pairs set: + "def_dropAF" "af def" + "dropSkin" "skin name" + To drop multiple articulated figures the following key/value pairs can be used: + "def_dropAF*" "af def" + where * is an aribtrary string. +================ +*/ +void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName; + idEntity *newEnt; + idAFEntity_Base *af; + idDict args; + const idDeclSkin *skin; + + // drop the articulated figures + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), NULL ); + while ( kv ) { + + args.Set( "classname", kv->GetValue() ); + gameLocal.SpawnEntityDef( args, &newEnt ); + + if ( newEnt && newEnt->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(newEnt); + af->GetPhysics()->SetOrigin( ent->GetPhysics()->GetOrigin() ); + af->GetPhysics()->SetAxis( ent->GetPhysics()->GetAxis() ); + af->af.SetupPose( ent, gameLocal.time ); + if ( list ) { + list->Append( af ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), kv ); + } + + // change the skin to hide all the dropped articulated figures + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +================ +idAFEntity_Base::Event_SetConstraintPosition +================ +*/ +void idAFEntity_Base::Event_SetConstraintPosition( const char *name, const idVec3 &pos ) { + af.SetConstraintPosition( name, pos ); +} + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +const idEventDef EV_Gib( "gib", "s" ); +const idEventDef EV_Gibbed( "" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Gibbable ) + EVENT( EV_Gib, idAFEntity_Gibbable::Event_Gib ) + EVENT( EV_Gibbed, idAFEntity_Base::Event_Remove ) +END_CLASS + + +/* +================ +idAFEntity_Gibbable::idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::idAFEntity_Gibbable() { + skeletonModel = NULL; + skeletonModelDefHandle = -1; + gibbed = false; + wasThrown = false; +} + +/* +================ +idAFEntity_Gibbable::~idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::~idAFEntity_Gibbable() { + if ( skeletonModelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( skeletonModelDefHandle ); + skeletonModelDefHandle = -1; + } +} + +/* +================ +idAFEntity_Gibbable::Save +================ +*/ +void idAFEntity_Gibbable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( gibbed ); + savefile->WriteBool( combatModel != NULL ); + savefile->WriteBool( wasThrown ); +} + +/* +================ +idAFEntity_Gibbable::Restore +================ +*/ +void idAFEntity_Gibbable::Restore( idRestoreGame *savefile ) { + bool hasCombatModel; + + savefile->ReadBool( gibbed ); + savefile->ReadBool( hasCombatModel ); + savefile->ReadBool( wasThrown ); + + InitSkeletonModel(); + + if ( hasCombatModel ) { + SetCombatModel(); + LinkCombat(); + } +} + +/* +================ +idAFEntity_Gibbable::Spawn +================ +*/ +void idAFEntity_Gibbable::Spawn() { + InitSkeletonModel(); + + gibbed = false; + wasThrown = false; +} + +/* +================ +idAFEntity_Gibbable::InitSkeletonModel +================ +*/ +void idAFEntity_Gibbable::InitSkeletonModel() { + const char *modelName; + const idDeclModelDef *modelDef; + + skeletonModel = NULL; + skeletonModelDefHandle = -1; + + modelName = spawnArgs.GetString( "model_gib" ); + + modelDef = NULL; + if ( modelName[0] != '\0' ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + skeletonModel = modelDef->ModelHandle(); + } else { + skeletonModel = renderModelManager->FindModel( modelName ); + } + if ( skeletonModel != NULL && renderEntity.hModel != NULL ) { + if ( skeletonModel->NumJoints() != renderEntity.hModel->NumJoints() ) { + gameLocal.Error( "gib model '%s' has different number of joints than model '%s'", + skeletonModel->Name(), renderEntity.hModel->Name() ); + } + } + } +} + +/* +================ +idAFEntity_Gibbable::Present +================ +*/ +void idAFEntity_Gibbable::Present() { + renderEntity_t skeleton; + + if ( !gameLocal.isNewFrame ) { + return; + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // update skeleton model + if ( gibbed && !IsHidden() && skeletonModel != NULL ) { + skeleton = renderEntity; + skeleton.hModel = skeletonModel; + // add to refresh list + if ( skeletonModelDefHandle == -1 ) { + skeletonModelDefHandle = gameRenderWorld->AddEntityDef( &skeleton ); + } else { + gameRenderWorld->UpdateEntityDef( skeletonModelDefHandle, &skeleton ); + } + } + + idEntity::Present(); +} + +/* +================ +idAFEntity_Gibbable::Damage +================ +*/ +void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( health < -20 && spawnArgs.GetBool( "gib" ) ) { + Gib( dir, damageDefName ); + } +} + +/* +===================== +idAFEntity_Gibbable::SetThrown +===================== +*/ +void idAFEntity_Gibbable::SetThrown( bool isThrown ) { + + if ( isThrown ) { + int i, num = af.GetPhysics()->GetNumBodies(); + + for ( i=0; iGetBody( i ); + body->SetClipMask( MASK_MONSTERSOLID ); + } + } + + wasThrown = isThrown; +} + +/* +===================== +idAFEntity_Gibbable::Collide +===================== +*/ +bool idAFEntity_Gibbable::Collide( const trace_t &collision, const idVec3 &velocity ) { + + if ( !gibbed && wasThrown ) { + + // Everything gibs (if possible) + if ( spawnArgs.GetBool( "gib" ) ) { + idEntity *ent; + + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent->fl.takedamage ) { + ent->Damage( this, gameLocal.GetLocalPlayer(), collision.c.normal, "damage_thrown_ragdoll", 1.f, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } + + idVec3 vel = velocity; + vel.NormalizeFast(); + Gib( vel, "damage_gib" ); + } + } + + return idAFEntity_Base::Collide( collision, velocity ); +} + +/* +===================== +idAFEntity_Gibbable::SpawnGibs +===================== +*/ +void idAFEntity_Gibbable::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + int i; + bool gibNonSolid; + idVec3 entityCenter, velocity; + idList list; + + assert( !common->IsClient() ); + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef == NULL ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + return; + } + + // spawn gib articulated figures + idAFEntity_Base::DropAFs( this, "gib", &list ); + + // spawn gib items + idMoveableItem::DropItems( this, "gib", &list ); + + // blow out the gibs in the given direction away from the center of the entity + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + gibNonSolid = damageDef->GetBool( "gibNonSolid" ); + for ( i = 0; i < list.Num(); i++ ) { + if ( gibNonSolid ) { + list[i]->GetPhysics()->SetContents( 0 ); + list[i]->GetPhysics()->SetClipMask( 0 ); + list[i]->GetPhysics()->UnlinkClip(); + list[i]->GetPhysics()->PutToRest(); + } else { + list[i]->GetPhysics()->SetContents( 0 ); + list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + list[i]->GetPhysics()->SetLinearVelocity( velocity * 75.0f ); + } + // Don't allow grabber to pick up temporary gibs + list[i]->noGrab = true; + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + list[i]->PostEventSec( &EV_Remove, 4.0f ); + } +} + +/* +============ +idAFEntity_Gibbable::Gib +============ +*/ +void idAFEntity_Gibbable::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + + // Don't grab this ent after it's been gibbed (and now invisible!) + noGrab = true; + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef == NULL ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->GetBool( "gibNonSolid" ) ) { + GetAFPhysics()->SetContents( 0 ); + GetAFPhysics()->SetClipMask( 0 ); + GetAFPhysics()->UnlinkClip(); + GetAFPhysics()->PutToRest(); + } else { + GetAFPhysics()->SetContents( CONTENTS_CORPSE ); + GetAFPhysics()->SetClipMask( CONTENTS_SOLID ); + } + + UnlinkCombat(); + + if ( g_bloodEffects.GetBool() ) { + if ( gameLocal.time > gameLocal.GetGibTime() ) { + gameLocal.SetGibTime( gameLocal.time + GIB_DELAY ); + SpawnGibs( dir, damageDefName ); + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + StartSound( "snd_gibbed", SND_CHANNEL_ANY, 0, false, NULL ); + gibbed = true; + } + } else { + gibbed = true; + } + + + PostEventSec( &EV_Gibbed, 4.0f ); +} + +/* +============ +idAFEntity_Gibbable::Event_Gib +============ +*/ +void idAFEntity_Gibbable::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_Generic ) + EVENT( EV_Activate, idAFEntity_Generic::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_Generic::idAFEntity_Generic +================ +*/ +idAFEntity_Generic::idAFEntity_Generic() { + keepRunningPhysics = false; +} + +/* +================ +idAFEntity_Generic::~idAFEntity_Generic +================ +*/ +idAFEntity_Generic::~idAFEntity_Generic() { +} + +/* +================ +idAFEntity_Generic::Save +================ +*/ +void idAFEntity_Generic::Save( idSaveGame *savefile ) const { + savefile->WriteBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Restore +================ +*/ +void idAFEntity_Generic::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Think +================ +*/ +void idAFEntity_Generic::Think() { + idAFEntity_Base::Think(); + + if ( keepRunningPhysics ) { + BecomeActive( TH_PHYSICS ); + } +} + +/* +================ +idAFEntity_Generic::Spawn +================ +*/ +void idAFEntity_Generic::Spawn() { + if ( !LoadAF() ) { + gameLocal.Error( "Couldn't load af file on entity '%s'", name.c_str() ); + } + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; +} + +/* +================ +idAFEntity_Generic::Event_Activate +================ +*/ +void idAFEntity_Generic::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_WithAttachedHead ) + EVENT( EV_Gib, idAFEntity_WithAttachedHead::Event_Gib ) + EVENT( EV_Activate, idAFEntity_WithAttachedHead::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead() { + head = NULL; +} + +/* +================ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead() { + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Spawn +================ +*/ +void idAFEntity_WithAttachedHead::Spawn() { + SetupHead(); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; + + if ( head.GetEntity() ) { + int anim = head.GetEntity()->GetAnimator()->GetAnim( "dead" ); + + if ( anim ) { + head.GetEntity()->GetAnimator()->SetFrame( ANIMCHANNEL_ALL, anim, 0, gameLocal.time, 0 ); + } + } +} + +/* +================ +idAFEntity_WithAttachedHead::Save +================ +*/ +void idAFEntity_WithAttachedHead::Save( idSaveGame *savefile ) const { + head.Save( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::Restore +================ +*/ +void idAFEntity_WithAttachedHead::Restore( idRestoreGame *savefile ) { + head.Restore( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::SetupHead +================ +*/ +void idAFEntity_WithAttachedHead::SetupHead() { + idAFAttachment *headEnt; + idStr jointName; + const char *headModel; + jointHandle_t joint; + idVec3 origin; + idMat3 axis; + + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { + jointName = spawnArgs.GetString( "head_joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'head_joint' on '%s'", jointName.c_str(), name.c_str() ); + } + + headEnt = static_cast( gameLocal.SpawnEntityType( idAFAttachment::Type, NULL ) ); + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody( this, headModel, joint ); + headEnt->SetCombatModel(); + head = headEnt; + + idStr xSkin; + if ( spawnArgs.GetString( "skin_head_xray", "", xSkin ) ) { + headEnt->xraySkin = declManager->FindSkin( xSkin.c_str() ); + headEnt->UpdateModel(); + } + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + headEnt->SetOrigin( origin ); + headEnt->SetAxis( renderEntity.axis ); + headEnt->BindToJoint( this, joint, true ); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Think +================ +*/ +void idAFEntity_WithAttachedHead::Think() { + idAFEntity_Base::Think(); +} + +/* +================ +idAFEntity_WithAttachedHead::LinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::LinkCombat() { + idAFAttachment *headEnt; + + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { + combatModel->Link( gameLocal.clip, this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::UnlinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::UnlinkCombat() { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Hide +================ +*/ +void idAFEntity_WithAttachedHead::Hide() { + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + UnlinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::Show +================ +*/ +void idAFEntity_WithAttachedHead::Show() { + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + LinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::ProjectOverlay +================ +*/ +void idAFEntity_WithAttachedHead::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + + idEntity::ProjectOverlay( origin, dir, size, material ); + + if ( head.GetEntity() ) { + head.GetEntity()->ProjectOverlay( origin, dir, size, material ); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Gib +============ +*/ +void idAFEntity_WithAttachedHead::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Event_Gib +============ +*/ +void idAFEntity_WithAttachedHead::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +================ +idAFEntity_WithAttachedHead::Event_Activate +================ +*/ +void idAFEntity_WithAttachedHead::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_Vehicle + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Vehicle ) +END_CLASS + +/* +================ +idAFEntity_Vehicle::idAFEntity_Vehicle +================ +*/ +idAFEntity_Vehicle::idAFEntity_Vehicle() { + player = NULL; + eyesJoint = INVALID_JOINT; + steeringWheelJoint = INVALID_JOINT; + wheelRadius = 0.0f; + steerAngle = 0.0f; + steerSpeed = 0.0f; + dustSmoke = NULL; +} + +/* +================ +idAFEntity_Vehicle::Spawn +================ +*/ +void idAFEntity_Vehicle::Spawn() { + const char *eyesJointName = spawnArgs.GetString( "eyesJoint", "eyes" ); + const char *steeringWheelJointName = spawnArgs.GetString( "steeringWheelJoint", "steeringWheel" ); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + if ( !eyesJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no eyes joint specified", name.c_str() ); + } + eyesJoint = animator.GetJointHandle( eyesJointName ); + if ( !steeringWheelJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no steering wheel joint specified", name.c_str() ); + } + steeringWheelJoint = animator.GetJointHandle( steeringWheelJointName ); + + spawnArgs.GetFloat( "wheelRadius", "20", wheelRadius ); + spawnArgs.GetFloat( "steerSpeed", "5", steerSpeed ); + + player = NULL; + steerAngle = 0.0f; + + const char *smokeName = spawnArgs.GetString( "smoke_vehicle_dust", "muzzlesmoke" ); + if ( *smokeName != '\0' ) { + dustSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } +} + +/* +================ +idAFEntity_Vehicle::Use +================ +*/ +void idAFEntity_Vehicle::Use( idPlayer *other ) { + idVec3 origin; + idMat3 axis; + + if ( player ) { + if ( player == other ) { + other->Unbind(); + player = NULL; + + af.GetPhysics()->SetComeToRest( true ); + } + } + else { + player = other; + animator.GetJointTransform( eyesJoint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + player->GetPhysics()->SetOrigin( origin ); + player->BindToBody( this, 0, true ); + + af.GetPhysics()->SetComeToRest( false ); + af.GetPhysics()->Activate(); + } +} + +/* +================ +idAFEntity_Vehicle::GetSteerAngle +================ +*/ +float idAFEntity_Vehicle::GetSteerAngle() { + float idealSteerAngle, angleDelta; + + idealSteerAngle = player->usercmd.rightmove * ( 30.0f / 128.0f ); + angleDelta = idealSteerAngle - steerAngle; + + if ( angleDelta > steerSpeed ) { + steerAngle += steerSpeed; + } else if ( angleDelta < -steerSpeed ) { + steerAngle -= steerSpeed; + } else { + steerAngle = idealSteerAngle; + } + + return steerAngle; +} + + +/* +=============================================================================== + + idAFEntity_VehicleSimple + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleSimple ) +END_CLASS + +/* +================ +idAFEntity_VehicleSimple::idAFEntity_VehicleSimple +================ +*/ +idAFEntity_VehicleSimple::idAFEntity_VehicleSimple() { + int i; + for ( i = 0; i < 4; i++ ) { + suspension[i] = NULL; + } +} + +/* +================ +idAFEntity_VehicleSimple::~idAFEntity_VehicleSimple +================ +*/ +idAFEntity_VehicleSimple::~idAFEntity_VehicleSimple() { + delete wheelModel; + wheelModel = NULL; +} + +/* +================ +idAFEntity_VehicleSimple::Spawn +================ +*/ +void idAFEntity_VehicleSimple::Spawn() { + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static idVec3 wheelPoly[4] = { idVec3( 2, 2, 0 ), idVec3( 2, -2, 0 ), idVec3( -2, -2, 0 ), idVec3( -2, 2, 0 ) }; + + int i; + idVec3 origin; + idMat3 axis; + idTraceModel trm; + + trm.SetupPolygon( wheelPoly, 4 ); + trm.Translate( idVec3( 0, 0, -wheelRadius ) ); + wheelModel = new (TAG_PHYSICS_CLIP_AF) idClipModel( trm ); + + for ( i = 0; i < 4; i++ ) { + const char *wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSimple '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleSimple '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + + GetAnimator()->GetJointTransform( wheelJoints[i], 0, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + + suspension[i] = new (TAG_PHYSICS_AF) idAFConstraint_Suspension(); + suspension[i]->Setup( va( "suspension%d", i ), af.GetPhysics()->GetBody( 0 ), origin, af.GetPhysics()->GetAxis( 0 ), wheelModel ); + suspension[i]->SetSuspension( g_vehicleSuspensionUp.GetFloat(), + g_vehicleSuspensionDown.GetFloat(), + g_vehicleSuspensionKCompress.GetFloat(), + g_vehicleSuspensionDamping.GetFloat(), + g_vehicleTireFriction.GetFloat() ); + + af.GetPhysics()->AddConstraint( suspension[i] ); + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleSimple::Think +================ +*/ +void idAFEntity_VehicleSimple::Think() { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation wheelRotation, steerRotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force and steering + for ( i = 0; i < 2; i++ ) { + + // front wheel drive + if ( velocity != 0.0f ) { + suspension[i]->EnableMotor( true ); + } else { + suspension[i]->EnableMotor( false ); + } + suspension[i]->SetMotorVelocity( velocity ); + suspension[i]->SetMotorForce( force ); + + // update the wheel steering + suspension[i]->SetSteerAngle( steerAngle ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + suspension[0]->SetMotorVelocity( velocity * 0.5f ); + } else if ( steerAngle > 0.0f ) { + suspension[1]->SetMotorVelocity( velocity * 0.5f ); + } + + // update suspension with latest cvar settings + for ( i = 0; i < 4; i++ ) { + suspension[i]->SetSuspension( g_vehicleSuspensionUp.GetFloat(), + g_vehicleSuspensionDown.GetFloat(), + g_vehicleSuspensionKCompress.GetFloat(), + g_vehicleSuspensionDamping.GetFloat(), + g_vehicleTireFriction.GetFloat() ); + } + + // run the physics + RunPhysics(); + + // move and rotate the wheels visually + for ( i = 0; i < 4; i++ ) { + idAFBody *body = af.GetPhysics()->GetBody( 0 ); + + origin = suspension[i]->GetWheelOrigin(); + velocity = body->GetPointVelocity( origin ) * body->GetWorldAxis()[0]; + wheelAngles[i] += velocity * MS2SEC( gameLocal.time - gameLocal.previousTime ) / wheelRadius; + + // additional rotation about the wheel axis + wheelRotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + wheelRotation.SetVec( 0, -1, 0 ); + + if ( i < 2 ) { + // rotate the wheel for steering + steerRotation.SetAngle( steerAngle ); + steerRotation.SetVec( 0, 0, 1 ); + // set wheel rotation + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, wheelRotation.ToMat3() * steerRotation.ToMat3() ); + } else { + // set wheel rotation + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, wheelRotation.ToMat3() ); + } + + // set wheel position for suspension + origin = ( origin - renderEntity.origin ) * renderEntity.axis.Transpose(); + GetAnimator()->SetJointPos( wheelJoints[i], JOINTMOD_WORLD_OVERRIDE, origin ); + } +/* + // spawn dust particle effects + if ( force != 0.0f && !( gameLocal.framenum & 7 ) ) { + int numContacts; + idAFConstraint_Contact *contacts[2]; + for ( i = 0; i < 4; i++ ) { + numContacts = af.GetPhysics()->GetBodyContactConstraints( wheels[i]->GetClipModel()->GetId(), contacts, 2 ); + for ( int j = 0; j < numContacts; j++ ) { + gameLocal.smokeParticles->EmitSmoke( dustSmoke, gameLocal.time, gameLocal.random.RandomFloat(), contacts[j]->GetContact().point, contacts[j]->GetContact().normal.ToMat3() ); + } + } + } +*/ + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleFourWheels ) +END_CLASS + + +/* +================ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels +================ +*/ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels() { + int i; + + for ( i = 0; i < 4; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; +} + +/* +================ +idAFEntity_VehicleFourWheels::Spawn +================ +*/ +void idAFEntity_VehicleFourWheels::Spawn() { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 4; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 2; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleFourWheels::Think +================ +*/ +void idAFEntity_VehicleFourWheels::Think() { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 2; i++ ) { + wheels[2+i]->SetContactMotorVelocity( velocity ); + wheels[2+i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + wheels[2]->SetContactMotorVelocity( velocity * 0.5f ); + } + else if ( steerAngle > 0.0f ) { + wheels[3]->SetContactMotorVelocity( velocity * 0.5f ); + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + for ( i = 0; i < 2; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 4; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } + wheelAngles[i] += velocity * MS2SEC( gameLocal.time - gameLocal.previousTime ) / wheelRadius; + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + + // spawn dust particle effects + if ( force != 0.0f && !( gameLocal.framenum & 7 ) ) { + int numContacts; + idAFConstraint_Contact *contacts[2]; + for ( i = 0; i < 4; i++ ) { + numContacts = af.GetPhysics()->GetBodyContactConstraints( wheels[i]->GetClipModel()->GetId(), contacts, 2 ); + for ( int j = 0; j < numContacts; j++ ) { + gameLocal.smokeParticles->EmitSmoke( dustSmoke, gameLocal.time, gameLocal.random.RandomFloat(), contacts[j]->GetContact().point, contacts[j]->GetContact().normal.ToMat3(), timeGroup /* D3XP */ ); + } + } + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleSixWheels ) +END_CLASS + + /* +================ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels +================ +*/ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels() { + int i; + + for ( i = 0; i < 6; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; + steering[2] = NULL; + steering[3] = NULL; +} + +/* +================ +idAFEntity_VehicleSixWheels::Spawn +================ +*/ +void idAFEntity_VehicleSixWheels::Spawn() { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyMiddleLeft", + "wheelBodyMiddleRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointMiddleLeft", + "wheelJointMiddleRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + "steeringHingeRearLeft", + "steeringHingeRearRight" + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 6; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 4; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleSixWheels::Think +================ +*/ +void idAFEntity_VehicleSixWheels::Think() { + int i; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 6; i++ ) { + wheels[i]->SetContactMotorVelocity( velocity ); + wheels[i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + else if ( steerAngle > 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[1+(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + steering[2]->SetSteerAngle( -steerAngle ); + steering[3]->SetSteerAngle( -steerAngle ); + for ( i = 0; i < 4; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 6; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } + wheelAngles[i] += velocity * MS2SEC( gameLocal.time - gameLocal.previousTime ) / wheelRadius; + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + + // spawn dust particle effects + if ( force != 0.0f && !( gameLocal.framenum & 7 ) ) { + int numContacts; + idAFConstraint_Contact *contacts[2]; + for ( i = 0; i < 6; i++ ) { + numContacts = af.GetPhysics()->GetBodyContactConstraints( wheels[i]->GetClipModel()->GetId(), contacts, 2 ); + for ( int j = 0; j < numContacts; j++ ) { + gameLocal.smokeParticles->EmitSmoke( dustSmoke, gameLocal.time, gameLocal.random.RandomFloat(), contacts[j]->GetContact().point, contacts[j]->GetContact().normal.ToMat3(), timeGroup /* D3XP */ ); + } + } + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + +/* +=============================================================================== + +idAFEntity_VehicleAutomated + +=============================================================================== +*/ +const idEventDef EV_Vehicle_setVelocity( "setVelocity", "f" ); +const idEventDef EV_Vehicle_setTorque( "setTorque", "f" ); +const idEventDef EV_Vehicle_setSteeringSpeed( "setSteeringSpeed", "f" ); +const idEventDef EV_Vehicle_setWaypoint( "setWaypoint", "e" ); + +CLASS_DECLARATION( idAFEntity_VehicleSixWheels, idAFEntity_VehicleAutomated ) +EVENT( EV_PostSpawn, idAFEntity_VehicleAutomated::PostSpawn ) +EVENT( EV_Vehicle_setVelocity, idAFEntity_VehicleAutomated::Event_SetVelocity ) +EVENT( EV_Vehicle_setTorque, idAFEntity_VehicleAutomated::Event_SetTorque ) +EVENT( EV_Vehicle_setSteeringSpeed, idAFEntity_VehicleAutomated::Event_SetSteeringSpeed ) +EVENT( EV_Vehicle_setWaypoint, idAFEntity_VehicleAutomated::Event_SetWayPoint ) +END_CLASS + +/* +================ +idAFEntity_VehicleAutomated::Spawn +================ +*/ +void idAFEntity_VehicleAutomated::Spawn() { + + velocity = force = steerAngle = 0.f; + currentSteering = steeringSpeed = 0.f; + originHeight = 0.f; + waypoint = NULL; + + spawnArgs.GetFloat( "velocity", "150", velocity ); + spawnArgs.GetFloat( "torque", "200000", force ); + spawnArgs.GetFloat( "steeringSpeed", "1", steeringSpeed ); + spawnArgs.GetFloat( "originHeight", "0", originHeight ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idAFEntity_VehicleAutomated::PostSpawn +================ +*/ +void idAFEntity_VehicleAutomated::PostSpawn() { + + if ( targets.Num() ) { + waypoint = targets[0].GetEntity(); + } +} + +/* +================ +idAFEntity_VehicleAutomated::Event_SetVelocity +================ +*/ +void idAFEntity_VehicleAutomated::Event_SetVelocity( float _velocity ) { + velocity = _velocity; +} + +/* +================ +idAFEntity_VehicleAutomated::Event_SetTorque +================ +*/ +void idAFEntity_VehicleAutomated::Event_SetTorque( float _torque ) { + force = _torque; +} + +/* +================ +idAFEntity_VehicleAutomated::Event_SetSteeringSpeed +================ +*/ +void idAFEntity_VehicleAutomated::Event_SetSteeringSpeed( float _steeringSpeed ) { + steeringSpeed = _steeringSpeed; +} + +/* +================ +idAFEntity_VehicleAutomated::Event_SetWayPoint +================ +*/ +void idAFEntity_VehicleAutomated::Event_SetWayPoint( idEntity *_waypoint ) { + waypoint = _waypoint; +} + +/* +================ +idAFEntity_VehicleAutomated::Think +================ +*/ +#define HIT_WAYPOINT_THRESHOLD 80.f + +void idAFEntity_VehicleAutomated::Think() { + + // If we don't have a waypoint, coast to a stop + if ( !waypoint ) { + velocity = force = steerAngle = 0.f; + idAFEntity_VehicleSixWheels::Think(); + return; + } + + idVec3 waypoint_origin, vehicle_origin; + idVec3 travel_vector; + float distance_from_waypoint; + + // Set up the vector from the vehicle origin, to the waypoint + vehicle_origin = GetPhysics()->GetOrigin(); + vehicle_origin.z -= originHeight; + + waypoint_origin = waypoint->GetPhysics()->GetOrigin(); + + travel_vector = waypoint_origin - vehicle_origin; + distance_from_waypoint = travel_vector.Length(); + + // Check if we've hit the waypoint (within a certain threshold) + if ( distance_from_waypoint < HIT_WAYPOINT_THRESHOLD ) { + idStr callfunc; + const function_t *func; + idThread *thread; + + // Waypoints can call script functions + waypoint->spawnArgs.GetString( "call", "", callfunc ); + if ( callfunc.Length() ) { + func = gameLocal.program.FindFunction( callfunc ); + if ( func != NULL ) { + thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + + // Get next waypoint + if ( waypoint->targets.Num() ) { + waypoint = waypoint->targets[0].GetEntity(); + } else { + waypoint = NULL; + } + + // We are switching waypoints, adjust steering next frame + idAFEntity_VehicleSixWheels::Think(); + return; + } + + idAngles vehicle_angles, travel_angles; + + // Get the angles we need to steer towards + travel_angles = travel_vector.ToAngles().Normalize360(); + vehicle_angles = this->GetPhysics()->GetAxis().ToAngles().Normalize360(); + + float delta_yaw; + + // Get the shortest steering angle towards the travel angles + delta_yaw = vehicle_angles.yaw - travel_angles.yaw; + if ( idMath::Fabs( delta_yaw ) > 180.f ) { + if ( delta_yaw > 0 ) { + delta_yaw = delta_yaw - 360; + } else { + delta_yaw = delta_yaw + 360; + } + } + + // Maximum steering angle is 35 degrees + delta_yaw = idMath::ClampFloat( -35.f, 35.f, delta_yaw ); + + idealSteering = delta_yaw; + + // Adjust steering incrementally so it doesn't snap to the ideal angle + if ( idMath::Fabs( (idealSteering - currentSteering) ) > steeringSpeed ) { + if ( idealSteering > currentSteering ) { + currentSteering += steeringSpeed; + } else { + currentSteering -= steeringSpeed; + } + } else { + currentSteering = idealSteering; + } + + // DEBUG + if ( g_vehicleDebug.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-4,-4,-4),idVec3(4,4,4)), vehicle_origin ); + gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-4,-4,-4),idVec3(4,4,4)), waypoint_origin ); + gameRenderWorld->DrawText( waypoint->name.c_str(), waypoint_origin + idVec3(0,0,16), 0.25f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + gameRenderWorld->DebugArrow( colorWhite, vehicle_origin, waypoint_origin, 12.f ); + } + + // Set the final steerAngle for the vehicle + steerAngle = currentSteering; + + idAFEntity_VehicleSixWheels::Think(); +} + +/* +=============================================================================== + + idAFEntity_SteamPipe + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_SteamPipe ) +END_CLASS + + +/* +================ +idAFEntity_SteamPipe::idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::idAFEntity_SteamPipe() { + steamBody = 0; + steamForce = 0.0f; + steamUpForce = 0.0f; + steamModelDefHandle = -1; + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); +} + +/* +================ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe() { + if ( steamModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( steamModelDefHandle ); + } +} + +/* +================ +idAFEntity_SteamPipe::Save +================ +*/ +void idAFEntity_SteamPipe::Save( idSaveGame *savefile ) const { +} + +/* +================ +idAFEntity_SteamPipe::Restore +================ +*/ +void idAFEntity_SteamPipe::Restore( idRestoreGame *savefile ) { + Spawn(); +} + +/* +================ +idAFEntity_SteamPipe::Spawn +================ +*/ +void idAFEntity_SteamPipe::Spawn() { + idVec3 steamDir; + const char *steamBodyName; + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + steamBodyName = spawnArgs.GetString( "steamBody", "" ); + steamForce = spawnArgs.GetFloat( "steamForce", "2000" ); + steamUpForce = spawnArgs.GetFloat( "steamUpForce", "10" ); + steamDir = af.GetPhysics()->GetAxis( steamBody )[2]; + steamBody = af.GetPhysics()->GetBodyId( steamBodyName ); + force.SetPosition( af.GetPhysics(), steamBody, af.GetPhysics()->GetOrigin( steamBody ) ); + force.SetForce( steamDir * -steamForce ); + + InitSteamRenderEntity(); + + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_SteamPipe::InitSteamRenderEntity +================ +*/ +void idAFEntity_SteamPipe::InitSteamRenderEntity() { + const char *temp; + const idDeclModelDef *modelDef; + + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); + steamRenderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + modelDef = NULL; + temp = spawnArgs.GetString ( "model_steam" ); + if ( *temp != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + steamRenderEntity.hModel = modelDef->ModelHandle(); + } + } + + if ( !steamRenderEntity.hModel ) { + steamRenderEntity.hModel = renderModelManager->FindModel( temp ); + } + + if ( steamRenderEntity.hModel ) { + steamRenderEntity.bounds = steamRenderEntity.hModel->Bounds( &steamRenderEntity ); + } else { + steamRenderEntity.bounds.Zero(); + } + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + steamModelDefHandle = gameRenderWorld->AddEntityDef( &steamRenderEntity ); + } +} + +/* +================ +idAFEntity_SteamPipe::Think +================ +*/ +void idAFEntity_SteamPipe::Think() { + idVec3 steamDir; + + if ( thinkFlags & TH_THINK ) { + steamDir.x = gameLocal.random.CRandomFloat() * steamForce; + steamDir.y = gameLocal.random.CRandomFloat() * steamForce; + steamDir.z = steamUpForce; + force.SetForce( steamDir ); + force.Evaluate( gameLocal.time ); + //gameRenderWorld->DebugArrow( colorWhite, af.GetPhysics()->GetOrigin( steamBody ), af.GetPhysics()->GetOrigin( steamBody ) - 10.0f * steamDir, 4 ); + } + + if ( steamModelDefHandle >= 0 ){ + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + gameRenderWorld->UpdateEntityDef( steamModelDefHandle, &steamRenderEntity ); + } + + idAFEntity_Base::Think(); +} + + +/* +=============================================================================== + + idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +const idEventDef EV_SetFingerAngle( "setFingerAngle", "f" ); +const idEventDef EV_StopFingers( "stopFingers" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_ClawFourFingers ) + EVENT( EV_SetFingerAngle, idAFEntity_ClawFourFingers::Event_SetFingerAngle ) + EVENT( EV_StopFingers, idAFEntity_ClawFourFingers::Event_StopFingers ) +END_CLASS + +static const char *clawConstraintNames[] = { + "claw1", "claw2", "claw3", "claw4" +}; + +/* +================ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers +================ +*/ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers() { + fingers[0] = NULL; + fingers[1] = NULL; + fingers[2] = NULL; + fingers[3] = NULL; +} + +/* +================ +idAFEntity_ClawFourFingers::Save +================ +*/ +void idAFEntity_ClawFourFingers::Save( idSaveGame *savefile ) const { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->Save( savefile ); + } +} + +/* +================ +idAFEntity_ClawFourFingers::Restore +================ +*/ +void idAFEntity_ClawFourFingers::Restore( idRestoreGame *savefile ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + fingers[i]->Restore( savefile ); + } + + SetCombatModel(); + LinkCombat(); +} + +/* +================ +idAFEntity_ClawFourFingers::Spawn +================ +*/ +void idAFEntity_ClawFourFingers::Spawn() { + int i; + + LoadAF(); + + SetCombatModel(); + + af.GetPhysics()->LockWorldConstraints( true ); + af.GetPhysics()->SetForcePushable( true ); + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + if ( !fingers[i] ) { + gameLocal.Error( "idClaw_FourFingers '%s': can't find claw constraint '%s'", name.c_str(), clawConstraintNames[i] ); + } + } +} + +/* +================ +idAFEntity_ClawFourFingers::Event_SetFingerAngle +================ +*/ +void idAFEntity_ClawFourFingers::Event_SetFingerAngle( float angle ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( angle ); + fingers[i]->SetSteerSpeed( 0.5f ); + } + af.GetPhysics()->Activate(); +} + +/* +================ +idAFEntity_ClawFourFingers::Event_StopFingers +================ +*/ +void idAFEntity_ClawFourFingers::Event_StopFingers() { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( fingers[i]->GetAngle() ); + } +} + + +/* +=============================================================================== + + editor support routines + +=============================================================================== +*/ + + +/* +================ +idGameEdit::AF_SpawnEntity +================ +*/ +bool idGameEdit::AF_SpawnEntity( const char *fileName ) { + idDict args; + idPlayer *player; + idAFEntity_Generic *ent; + const idDeclAF *af; + idVec3 org; + float yaw; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return false; + } + + af = static_cast( declManager->FindType( DECL_AF, fileName ) ); + if ( !af ) { + return false; + } + + yaw = player->viewAngles.yaw; + args.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + args.Set( "origin", org.ToString() ); + args.Set( "spawnclass", "idAFEntity_Generic" ); + if ( af->model[0] ) { + args.Set( "model", af->model.c_str() ); + } else { + args.Set( "model", fileName ); + } + if ( af->skin[0] ) { + args.Set( "skin", af->skin.c_str() ); + } + args.Set( "articulatedFigure", fileName ); + args.Set( "nodrop", "1" ); + ent = static_cast(gameLocal.SpawnEntityType( idAFEntity_Generic::Type, &args)); + + // always update this entity + ent->BecomeActive( TH_THINK ); + ent->KeepRunningPhysics(); + ent->fl.forcePhysicsUpdate = true; + + player->dragEntity.SetSelected( ent ); + + return true; +} + +/* +================ +idGameEdit::AF_UpdateEntities +================ +*/ +void idGameEdit::AF_UpdateEntities( const char *fileName ) { + idEntity *ent; + idAFEntity_Base *af; + idStr name; + + name = fileName; + name.StripFileExtension(); + + // reload any idAFEntity_Generic which uses the given articulated figure file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(ent); + if ( name.Icmp( af->GetAFName() ) == 0 ) { + af->LoadAF(); + af->GetAFPhysics()->PutToRest(); + } + } + } +} + +/* +================ +idGameEdit::AF_UndoChanges +================ +*/ +void idGameEdit::AF_UndoChanges() { + int i, c; + idEntity *ent; + idAFEntity_Base *af; + idDeclAF *decl; + + c = declManager->GetNumDecls( DECL_AF ); + for ( i = 0; i < c; i++ ) { + decl = static_cast( const_cast( declManager->DeclByIndex( DECL_AF, i, false ) ) ); + if ( !decl->modified ) { + continue; + } + + decl->Invalidate(); + declManager->FindType( DECL_AF, decl->GetName() ); + + // reload all AF entities using the file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(ent); + if ( idStr::Icmp( decl->GetName(), af->GetAFName() ) == 0 ) { + af->LoadAF(); + } + } + } + } +} + +/* +================ +GetJointTransform +================ +*/ +typedef struct { + renderEntity_t *ent; + const idMD5Joint *joints; +} jointTransformData_t; + +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + int i; + jointTransformData_t *data = reinterpret_cast(model); + + for ( i = 0; i < data->ent->numJoints; i++ ) { + if ( data->joints[i].name.Icmp( jointName ) == 0 ) { + break; + } + } + if ( i >= data->ent->numJoints ) { + return false; + } + origin = frame[i].ToVec3(); + axis = frame[i].ToMat3(); + return true; +} + +/* +================ +GetArgString +================ +*/ +static const char *GetArgString( const idDict &args, const idDict *defArgs, const char *key ) { + const char *s; + + s = args.GetString( key ); + if ( !s[0] && defArgs ) { + s = defArgs->GetString( key ); + } + return s; +} + +/* +================ +idGameEdit::AF_CreateMesh +================ +*/ +idRenderModel *idGameEdit::AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ) { + int i, jointNum; + const idDeclAF *af = NULL; + const idDeclAF_Body *fb = NULL; + renderEntity_t ent; + idVec3 origin, *bodyOrigin = NULL, *newBodyOrigin = NULL, *modifiedOrigin = NULL; + idMat3 axis, *bodyAxis = NULL, *newBodyAxis = NULL, *modifiedAxis = NULL; + declAFJointMod_t *jointMod = NULL; + idAngles angles; + const idDict *defArgs = NULL; + const idKeyValue *arg = NULL; + idStr name; + jointTransformData_t data; + const char *classname = NULL, *afName = NULL, *modelName = NULL; + idRenderModel *md5 = NULL; + const idDeclModelDef *modelDef = NULL; + const idMD5Anim *MD5anim = NULL; + const idMD5Joint *MD5joint = NULL; + const idMD5Joint *MD5joints = NULL; + int numMD5joints; + idJointMat *originalJoints = NULL; + int parentNum; + + poseIsSet = false; + meshOrigin.Zero(); + meshAxis.Identity(); + + classname = args.GetString( "classname" ); + defArgs = gameLocal.FindEntityDefDict( classname ); + + // get the articulated figure + afName = GetArgString( args, defArgs, "articulatedFigure" ); + af = static_cast( declManager->FindType( DECL_AF, afName ) ); + if ( !af ) { + return NULL; + } + + // get the md5 model + modelName = GetArgString( args, defArgs, "model" ); + modelDef = static_cast< const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( !modelDef ) { + return NULL; + } + + // make sure model hasn't been purged + if ( modelDef->ModelHandle() && !modelDef->ModelHandle()->IsLoaded() ) { + modelDef->ModelHandle()->LoadModel(); + } + + // get the md5 + md5 = modelDef->ModelHandle(); + if ( !md5 || md5->IsDefaultModel() ) { + return NULL; + } + + // get the articulated figure pose anim + int animNum = modelDef->GetAnim( "af_pose" ); + if ( !animNum ) { + return NULL; + } + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + MD5anim = anim->MD5Anim( 0 ); + MD5joints = md5->GetJoints(); + numMD5joints = md5->NumJoints(); + + // setup a render entity + memset( &ent, 0, sizeof( ent ) ); + ent.customSkin = modelDef->GetSkin(); + ent.bounds.Clear(); + ent.numJoints = numMD5joints; + ent.joints = ( idJointMat * )_alloca16( ent.numJoints * sizeof( *ent.joints ) ); + + // create animation from of the af_pose + ANIM_CreateAnimFrame( md5, MD5anim, ent.numJoints, ent.joints, 1, modelDef->GetVisualOffset(), false ); + + // buffers to store the initial origin and axis for each body + bodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + bodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + newBodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + newBodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + + // finish the AF positions + data.ent = &ent; + data.joints = MD5joints; + af->Finish( GetJointTransform, ent.joints, &data ); + + // get the initial origin and axis for each AF body + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->modelType == TRM_BONE ) { + // axis of bone trace model + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + } else { + axis = fb->angles.ToMat3(); + } + + newBodyOrigin[i] = bodyOrigin[i] = fb->origin.ToVec3(); + newBodyAxis[i] = bodyAxis[i] = axis; + } + + // get any new body transforms stored in the key/value pairs + for ( arg = args.MatchPrefix( "body ", NULL ); arg; arg = args.MatchPrefix( "body ", arg ) ) { + name = arg->GetKey(); + name.Strip( "body " ); + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + if ( fb->name.Icmp( name ) == 0 ) { + break; + } + } + if ( i >= af->bodies.Num() ) { + continue; + } + sscanf( arg->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + + if ( fb != NULL && fb->jointName.Icmp( "origin" ) == 0 ) { + meshAxis = bodyAxis[i].Transpose() * angles.ToMat3(); + meshOrigin = origin - bodyOrigin[i] * meshAxis; + poseIsSet = true; + } else { + newBodyOrigin[i] = origin; + newBodyAxis[i] = angles.ToMat3(); + } + } + + // save the original joints + originalJoints = ( idJointMat * )_alloca16( numMD5joints * sizeof( originalJoints[0] ) ); + memcpy( originalJoints, ent.joints, numMD5joints * sizeof( originalJoints[0] ) ); + + // buffer to store the joint mods + jointMod = (declAFJointMod_t *) _alloca16( numMD5joints * sizeof( declAFJointMod_t ) ); + memset( jointMod, -1, numMD5joints * sizeof( declAFJointMod_t ) ); + modifiedOrigin = (idVec3 *) _alloca16( numMD5joints * sizeof( idVec3 ) ); + memset( modifiedOrigin, 0, numMD5joints * sizeof( idVec3 ) ); + modifiedAxis = (idMat3 *) _alloca16( numMD5joints * sizeof( idMat3 ) ); + memset( modifiedAxis, 0, numMD5joints * sizeof( idMat3 ) ); + + // get all the joint modifications + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + continue; + } + + for ( jointNum = 0; jointNum < numMD5joints; jointNum++ ) { + if ( MD5joints[jointNum].name.Icmp( fb->jointName ) == 0 ) { + break; + } + } + + if ( jointNum >= 0 && jointNum < ent.numJoints ) { + jointMod[ jointNum ] = fb->jointMod; + modifiedAxis[ jointNum ] = ( bodyAxis[i] * originalJoints[jointNum].ToMat3().Transpose() ).Transpose() * ( newBodyAxis[i] * meshAxis.Transpose() ); + // FIXME: calculate correct modifiedOrigin + modifiedOrigin[ jointNum ] = originalJoints[ jointNum ].ToVec3(); + } + } + + // apply joint modifications to the skeleton + MD5joint = MD5joints + 1; + for( i = 1; i < numMD5joints; i++, MD5joint++ ) { + + parentNum = MD5joint->parent - MD5joints; + idMat3 parentAxis = originalJoints[ parentNum ].ToMat3(); + idMat3 localm = originalJoints[i].ToMat3() * parentAxis.Transpose(); + idVec3 localt = ( originalJoints[i].ToVec3() - originalJoints[ parentNum ].ToVec3() ) * parentAxis.Transpose(); + + switch( jointMod[i] ) { + case DECLAF_JOINTMOD_ORIGIN: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + case DECLAF_JOINTMOD_AXIS: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + case DECLAF_JOINTMOD_BOTH: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + default: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + } + } + + // instantiate a mesh using the joint information from the render entity + return md5->InstantiateDynamicModel( &ent, NULL, NULL ); +} + + +/* +=============================================================================== +idHarvestable +=============================================================================== +*/ + +const idEventDef EV_Harvest_SpawnHarvestTrigger( "", NULL ); + +CLASS_DECLARATION( idEntity, idHarvestable ) +EVENT( EV_Harvest_SpawnHarvestTrigger, idHarvestable::Event_SpawnHarvestTrigger ) +EVENT( EV_Touch, idHarvestable::Event_Touch ) +END_CLASS + +idHarvestable::idHarvestable() { + trigger = NULL; + parentEnt = NULL; +} + +idHarvestable::~idHarvestable() { + if ( trigger ) { + delete trigger; + trigger = NULL; + } +} + +void idHarvestable::Spawn() { + + startTime = 0; + + spawnArgs.GetFloat( "triggersize", "120", triggersize ); + spawnArgs.GetFloat( "give_delay", "3", giveDelay); + giveDelay *= 1000; + given = false; + + removeDelay = spawnArgs.GetFloat( "remove_delay") * 1000.0f; + + fxFollowPlayer = spawnArgs.GetBool("fx_follow_player", "1"); + fxOrient = spawnArgs.GetString("fx_orient"); + + +} + +void idHarvestable::Init(idEntity* parent) { + + assert(parent); + + parentEnt = parent; + + GetPhysics()->SetOrigin( parent->GetPhysics()->GetOrigin() ); + this->Bind(parent, true); + + //Set the skin of the entity to the harvest skin + idStr skin = parent->spawnArgs.GetString("skin_harvest", ""); + if(skin.Length()) { + parent->SetSkin(declManager->FindSkin(skin.c_str())); + } + + idEntity* head = NULL; + if(parent->IsType(idActor::Type)) { + idActor* withHead = (idActor*)parent; + head = withHead->GetHeadEntity(); + } + if(parent->IsType(idAFEntity_WithAttachedHead::Type)) { + idAFEntity_WithAttachedHead* withHead = (idAFEntity_WithAttachedHead*)parent; + head = withHead->head.GetEntity(); + } + if(head) { + idStr headskin = parent->spawnArgs.GetString("skin_harvest_head", ""); + if(headskin.Length()) { + head->SetSkin(declManager->FindSkin(headskin.c_str())); + } + } + + idStr sound = parent->spawnArgs.GetString("harvest_sound"); + if(sound.Length() > 0) { + parent->StartSound( sound.c_str(), SND_CHANNEL_ANY, 0, false, NULL); + } + + + PostEventMS( &EV_Harvest_SpawnHarvestTrigger, 0 ); +} + +void idHarvestable::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( triggersize ); + savefile->WriteClipModel( trigger ); + savefile->WriteFloat( giveDelay ); + savefile->WriteFloat( removeDelay ); + savefile->WriteBool( given ); + + player.Save( savefile ); + savefile->WriteInt( startTime ); + + savefile->WriteBool( fxFollowPlayer ); + fx.Save( savefile ); + savefile->WriteString( fxOrient ); + + parentEnt.Save(savefile); +} + +void idHarvestable::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( triggersize ); + savefile->ReadClipModel( trigger ); + savefile->ReadFloat( giveDelay ); + savefile->ReadFloat( removeDelay ); + savefile->ReadBool( given ); + + player.Restore( savefile ); + savefile->ReadInt( startTime ); + + savefile->ReadBool( fxFollowPlayer ); + fx.Restore( savefile ); + savefile->ReadString( fxOrient ); + + parentEnt.Restore(savefile); +} + +void idHarvestable::SetParent(idEntity* parent) { + parentEnt = parent; +} + +void idHarvestable::Think() { + + idEntity* parent = parentEnt.GetEntity(); + if(!parent) { + return; + } + + //Update the orientation of the box + if(trigger && parent && !parent->GetPhysics()->IsAtRest()) { + trigger->Link( gameLocal.clip, this, 0, parent->GetPhysics()->GetOrigin(), parent->GetPhysics()->GetAxis()); + } + + if(startTime && gameLocal.slow.time - startTime > giveDelay && ! given) { + idPlayer *thePlayer = player.GetEntity(); + + thePlayer->Give(spawnArgs.GetString("give_item"), spawnArgs.GetString("give_value"), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + thePlayer->harvest_lock = false; + given = true; + } + + if(startTime && gameLocal.slow.time - startTime > removeDelay) { + parent->PostEventMS( &EV_Remove, 0 ); + PostEventMS( &EV_Remove, 0 ); + } + + if(fxFollowPlayer) { + idEntityFx* fxEnt = fx.GetEntity(); + + if(fxEnt) { + idMat3 orientAxisLocal; + if(GetFxOrientationAxis(orientAxisLocal)) { + //gameRenderWorld->DebugAxis(fxEnt->GetPhysics()->GetOrigin(), orientAxisLocal); + fxEnt->GetPhysics()->SetAxis(orientAxisLocal); + } + } + } +} + +/* +================ +idAFEntity_Harvest::Gib +Called when the parent object has been gibbed. +================ +*/ +void idHarvestable::Gib() { + //Stop any looping sound that was playing + idEntity* parent = parentEnt.GetEntity(); + if(parent) { + idStr sound = parent->spawnArgs.GetString("harvest_sound"); + if(sound.Length() > 0) { + parent->StopSound(SND_CHANNEL_ANY, false); + } + } +} + +/* +================ +idAFEntity_Harvest::BeginBurn +================ +*/ +void idHarvestable::BeginBurn() { + + idEntity* parent = parentEnt.GetEntity(); + if(!parent) { + return; + } + + if(!spawnArgs.GetBool("burn")) { + return; + } + + + //Switch Skins if the parent would like us to. + idStr skin = parent->spawnArgs.GetString("skin_harvest_burn", ""); + if(skin.Length()) { + parent->SetSkin(declManager->FindSkin(skin.c_str())); + } + parent->GetRenderEntity()->noShadow = true; + parent->SetShaderParm( SHADERPARM_TIME_OF_DEATH, gameLocal.slow.time * 0.001f ); + + idEntity* head = NULL; + if(parent->IsType(idActor::Type)) { + idActor* withHead = (idActor*)parent; + head = withHead->GetHeadEntity(); + } + if(parent->IsType(idAFEntity_WithAttachedHead::Type)) { + idAFEntity_WithAttachedHead* withHead = (idAFEntity_WithAttachedHead*)parent; + head = withHead->head.GetEntity(); + } + if(head) { + idStr headskin = parent->spawnArgs.GetString("skin_harvest_burn_head", ""); + if(headskin.Length()) { + head->SetSkin(declManager->FindSkin(headskin.c_str())); + } + + head->GetRenderEntity()->noShadow = true; + head->SetShaderParm( SHADERPARM_TIME_OF_DEATH, gameLocal.slow.time * 0.001f ); + } + + + +} + +/* +================ +idAFEntity_Harvest::BeginFX +================ +*/ +void idHarvestable::BeginFX() { + if(strlen(spawnArgs.GetString("fx")) <= 0) { + return; + } + + idMat3* orientAxis = NULL; + idMat3 orientAxisLocal; + + if(GetFxOrientationAxis(orientAxisLocal)) { + orientAxis = &orientAxisLocal; + } + fx = idEntityFx::StartFx( spawnArgs.GetString("fx"), NULL, orientAxis, this, spawnArgs.GetBool("fx_bind") ); +} + +/* +================ +idAFEntity_Harvest::CalcTriggerBounds +================ +*/ +void idHarvestable::CalcTriggerBounds( float size, idBounds &bounds ) { + + idEntity* parent = parentEnt.GetEntity(); + if(!parent) { + return; + } + + //Simple trigger bounds is the absolute bounds of the AF plus a defined size + bounds = parent->GetPhysics()->GetAbsBounds(); + bounds.ExpandSelf(size); + bounds[0] -= parent->GetPhysics()->GetOrigin(); + bounds[1] -= parent->GetPhysics()->GetOrigin(); +} + +bool idHarvestable::GetFxOrientationAxis(idMat3& mat) { + + idEntity* parent = parentEnt.GetEntity(); + if(!parent) { + return false; + } + + idPlayer *thePlayer = player.GetEntity(); + + if(!fxOrient.Icmp("up")) { + //Orient up + idVec3 grav = parent->GetPhysics()->GetGravityNormal()*-1; + idVec3 left, up; + + grav.OrthogonalBasis(left, up); + idMat3 temp(left.x, left.y, left.z, up.x, up.y, up.z, grav.x, grav.y, grav.z); + mat = temp; + + return true; + + } else if(!fxOrient.Icmp("weapon")) { + //Orient the fx towards the muzzle of the weapon + jointHandle_t joint; + idVec3 joint_origin; + idMat3 joint_axis; + + joint = thePlayer->weapon.GetEntity()->GetAnimator()->GetJointHandle( spawnArgs.GetString("fx_weapon_joint") ); + if ( joint != INVALID_JOINT ) { + thePlayer->weapon.GetEntity()->GetJointWorldTransform( joint, gameLocal.slow.time, joint_origin, joint_axis ); + } else { + joint_origin = thePlayer->GetPhysics()->GetOrigin(); + } + + idVec3 toPlayer = joint_origin-parent->GetPhysics()->GetOrigin(); + toPlayer.NormalizeFast(); + + idVec3 left, up; + toPlayer.OrthogonalBasis(left, up); + idMat3 temp(left.x, left.y, left.z, up.x, up.y, up.z, toPlayer.x, toPlayer.y, toPlayer.z); + mat = temp; + + return true; + + } else if(!fxOrient.Icmp("player")) { + //Orient the fx towards the eye of the player + idVec3 eye = thePlayer->GetEyePosition(); + idVec3 toPlayer = eye-parent->GetPhysics()->GetOrigin(); + + toPlayer.Normalize(); + + idVec3 left, up; + up.Set(0, 1, 0); + left = toPlayer.Cross(up); + up = left.Cross(toPlayer); + + + //common->Printf("%.2f %.2f %.2f - %.2f %.2f %.2f - %.2f %.2f %.2f\n", toPlayer.x, toPlayer.y, toPlayer.z, left.x, left.y, left.z, up.x, up.y, up.z ); + + idMat3 temp(left.x, left.y, left.z, up.x, up.y, up.z, toPlayer.x, toPlayer.y, toPlayer.z); + + mat = temp; + + return true; + } + + //Returning false indicates that the orientation is not used; + return false; +} + +/* +================ +idAFEntity_Harvest::Event_SpawnHarvestTrigger +================ +*/ +void idHarvestable::Event_SpawnHarvestTrigger() { + idBounds bounds; + + idEntity* parent = parentEnt.GetEntity(); + if(!parent) { + return; + } + + CalcTriggerBounds( triggersize, bounds ); + + // create a trigger clip model + trigger = new (TAG_PHYSICS_CLIP_AF) idClipModel( idTraceModel( bounds ) ); + trigger->Link( gameLocal.clip, this, 255, parent->GetPhysics()->GetOrigin(), mat3_identity); + trigger->SetContents( CONTENTS_TRIGGER ); + + startTime = 0; +} + +/* +================ +idAFEntity_Harvest::Event_Touch +================ +*/ +void idHarvestable::Event_Touch( idEntity *other, trace_t *trace ) { + + idEntity* parent = parentEnt.GetEntity(); + if(!parent) { + return; + } + if(parent->IsType(idAFEntity_Gibbable::Type)) { + idAFEntity_Gibbable* gibParent = (idAFEntity_Gibbable*)parent; + if(gibParent->IsGibbed()) + return; + } + + + if(!startTime && other && other->IsType(idPlayer::Type)) { + idPlayer *thePlayer = static_cast(other); + + if(thePlayer->harvest_lock) { + //Don't harvest if the player is in mid harvest + return; + } + + player = thePlayer; + + bool okToGive = true; + idStr requiredWeapons = spawnArgs.GetString("required_weapons"); + + if(requiredWeapons.Length() > 0) { + idStr playerWeap = thePlayer->GetCurrentWeapon(); + if(playerWeap.Length() == 0 || requiredWeapons.Find(playerWeap, false) == -1) { + okToGive = false; + } + } + + if(okToGive) { + if(thePlayer->CanGive(spawnArgs.GetString("give_item"), spawnArgs.GetString("give_value"))) { + + startTime = gameLocal.slow.time; + + //Lock the player from harvesting to prevent multiple harvests when only one is needed + thePlayer->harvest_lock = true; + + idWeapon* weap = (idWeapon*)thePlayer->weapon.GetEntity(); + if(weap) { + //weap->PostEventMS(&EV_Weapon_State, 0, "Charge", 8); + weap->ProcessEvent(&EV_Weapon_State, "Charge", 8); + } + + BeginBurn(); + BeginFX(); + + //Stop any looping sound that was playing + idStr sound = parent->spawnArgs.GetString("harvest_sound"); + if(sound.Length() > 0) { + parent->StopSound(SND_CHANNEL_ANY, false); + } + + //Make the parent object non-solid + parent->GetPhysics()->SetContents( 0 ); + parent->GetPhysics()->GetClipModel()->Unlink(); + + //Turn of the trigger so it doesn't process twice + trigger->SetContents( 0 ); + } + } + } +} + + +/* +=============================================================================== + +idAFEntity_Harvest + +=============================================================================== +*/ + +const idEventDef EV_Harvest_SpawnHarvestEntity( "", NULL ); + +CLASS_DECLARATION( idAFEntity_WithAttachedHead, idAFEntity_Harvest ) +EVENT( EV_Harvest_SpawnHarvestEntity, idAFEntity_Harvest::Event_SpawnHarvestEntity ) +END_CLASS + +/* +================ +idAFEntity_Harvest::idAFEntity_Harvest +================ +*/ +idAFEntity_Harvest::idAFEntity_Harvest() { + harvestEnt = NULL; +} + +/* +================ +idAFEntity_Harvest::~idAFEntity_Harvest +================ +*/ +idAFEntity_Harvest::~idAFEntity_Harvest() { + + if ( harvestEnt.GetEntity() ) { + harvestEnt.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + +} + +/* +================ +idAFEntity_Harvest::Save +================ +*/ +void idAFEntity_Harvest::Save( idSaveGame *savefile ) const { + harvestEnt.Save(savefile); +} + +/* +================ +idAFEntity_Harvest::Restore +================ +*/ +void idAFEntity_Harvest::Restore( idRestoreGame *savefile ) { + harvestEnt.Restore(savefile); + //if(harvestEnt.GetEntity()) { + // harvestEnt.GetEntity()->SetParent(this); + //} +} + +/* +================ +idAFEntity_Harvest::Spawn +================ +*/ +void idAFEntity_Harvest::Spawn() { + + PostEventMS( &EV_Harvest_SpawnHarvestEntity, 0 ); +} + +/* +================ +idAFEntity_Harvest::Think +================ +*/ +void idAFEntity_Harvest::Think() { + + idAFEntity_WithAttachedHead::Think(); + +} + +void idAFEntity_Harvest::Event_SpawnHarvestEntity() { + + const idDict *harvestDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_harvest_type"), false ); + if ( harvestDef ) { + idEntity *temp; + gameLocal.SpawnEntityDef( *harvestDef, &temp, false ); + harvestEnt = static_cast(temp); + } + + if(harvestEnt.GetEntity()) { + //Let the harvest entity set itself up + harvestEnt.GetEntity()->Init(this); + harvestEnt.GetEntity()->BecomeActive( TH_THINK ); + } +} + +void idAFEntity_Harvest::Gib( const idVec3 &dir, const char *damageDefName ) { + if(harvestEnt.GetEntity()) { + //Let the harvest ent know that we gibbed + harvestEnt.GetEntity()->Gib(); + } + idAFEntity_WithAttachedHead::Gib(dir, damageDefName); +} + diff --git a/neo/d3xp/AFEntity.h b/neo/d3xp/AFEntity.h new file mode 100644 index 00000000..981a4dfc --- /dev/null +++ b/neo/d3xp/AFEntity.h @@ -0,0 +1,597 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_AFENTITY_H__ +#define __GAME_AFENTITY_H__ + + +/* +=============================================================================== + +idMultiModelAF + +Entity using multiple separate visual models animated with a single +articulated figure. Only used for debugging! + +=============================================================================== +*/ +const int GIB_DELAY = 200; // only gib this often to keep performace hits when blowing up several mobs + +class idMultiModelAF : public idEntity { +public: + CLASS_PROTOTYPE( idMultiModelAF ); + + void Spawn(); + ~idMultiModelAF(); + + virtual void Think(); + virtual void Present(); + +protected: + idPhysics_AF physicsObj; + + void SetModelForId( int id, const idStr &modelName ); + +private: + idList modelHandles; + idList modelDefHandles; +}; + + +/* +=============================================================================== + +idChain + +Chain hanging down from the ceiling. Only used for debugging! + +=============================================================================== +*/ + +class idChain : public idMultiModelAF { +public: + CLASS_PROTOTYPE( idChain ); + + void Spawn(); + +protected: + void BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld = true ); +}; + + +/* +=============================================================================== + +idAFAttachment + +=============================================================================== +*/ + +class idAFAttachment : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFAttachment ); + + idAFAttachment(); + virtual ~idAFAttachment(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetBody( idEntity *bodyEnt, const char *headModel, jointHandle_t attachJoint ); + void ClearBody(); + idEntity * GetBody() const; + + virtual void Think(); + + virtual void Hide(); + virtual void Show(); + + void PlayIdleAnim( int blendTime ); + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + + void SetCombatModel(); + idClipModel * GetCombatModel() const; + virtual void LinkCombat(); + virtual void UnlinkCombat(); + +protected: + idEntity * body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t attachJoint; +}; + + +/* +=============================================================================== + +idAFEntity_Base + +=============================================================================== +*/ + +class idAFEntity_Base : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFEntity_Base ); + + idAFEntity_Base(); + virtual ~idAFEntity_Base(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers(); + virtual void FreeModelDef(); + + virtual bool LoadAF(); + bool IsActiveAF() const { return af.IsActive(); } + const char * GetAFName() const { return af.GetName(); } + idPhysics_AF * GetAFPhysics() { return af.GetPhysics(); } + + void SetCombatModel(); + idClipModel * GetCombatModel() const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat(); + virtual void UnlinkCombat(); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints(); + void RemoveBindConstraints(); + + virtual void ShowEditingDialog(); + + static void DropAFs( idEntity *ent, const char *type, idList *list ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound + + void Event_SetConstraintPosition( const char *name, const idVec3 &pos ); +}; + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +extern const idEventDef EV_Gib; +extern const idEventDef EV_Gibbed; + +class idAFEntity_Gibbable : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Gibbable ); + + idAFEntity_Gibbable(); + ~idAFEntity_Gibbable(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void SetThrown( bool isThrown ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + + bool IsGibbed() { return gibbed; }; + +protected: + idRenderModel * skeletonModel; + int skeletonModelDefHandle; + bool gibbed; + + bool wasThrown; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void InitSkeletonModel(); + + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +class idAFEntity_Generic : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_Generic ); + + idAFEntity_Generic(); + ~idAFEntity_Generic(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + void KeepRunningPhysics() { keepRunningPhysics = true; } + +private: + void Event_Activate( idEntity *activator ); + + bool keepRunningPhysics; +}; + + +/* +=============================================================================== + +idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +class idAFEntity_WithAttachedHead : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_WithAttachedHead ); + + idAFEntity_WithAttachedHead(); + ~idAFEntity_WithAttachedHead(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetupHead(); + + virtual void Think(); + + virtual void Hide(); + virtual void Show(); + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual void LinkCombat(); + virtual void UnlinkCombat(); + +protected: + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + +public: + idEntityPtr head; + + void Event_Gib( const char *damageDefName ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idAFEntity_Vehicle + +=============================================================================== +*/ + +class idAFEntity_Vehicle : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Vehicle ); + + idAFEntity_Vehicle(); + + void Spawn(); + void Use( idPlayer *player ); + +protected: + idPlayer * player; + jointHandle_t eyesJoint; + jointHandle_t steeringWheelJoint; + float wheelRadius; + float steerAngle; + float steerSpeed; + const idDeclParticle * dustSmoke; + + float GetSteerAngle(); +}; + + +/* +=============================================================================== + +idAFEntity_VehicleSimple + +=============================================================================== +*/ + +class idAFEntity_VehicleSimple : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleSimple ); + + idAFEntity_VehicleSimple(); + ~idAFEntity_VehicleSimple(); + + void Spawn(); + virtual void Think(); + +protected: + idClipModel * wheelModel; + idAFConstraint_Suspension * suspension[4]; + jointHandle_t wheelJoints[4]; + float wheelAngles[4]; +}; + + +/* +=============================================================================== + +idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleFourWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleFourWheels ); + + idAFEntity_VehicleFourWheels(); + + void Spawn(); + virtual void Think(); + +protected: + idAFBody * wheels[4]; + idAFConstraint_Hinge * steering[2]; + jointHandle_t wheelJoints[4]; + float wheelAngles[4]; +}; + + +/* +=============================================================================== + +idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleSixWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleSixWheels ); + + idAFEntity_VehicleSixWheels(); + + void Spawn(); + virtual void Think(); + + float force; + float velocity; + float steerAngle; + +private: + idAFBody * wheels[6]; + idAFConstraint_Hinge * steering[4]; + jointHandle_t wheelJoints[6]; + float wheelAngles[6]; +}; + +/* +=============================================================================== + +idAFEntity_VehicleAutomated + +=============================================================================== +*/ + +class idAFEntity_VehicleAutomated : public idAFEntity_VehicleSixWheels { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleAutomated ); + + void Spawn(); + void PostSpawn(); + virtual void Think(); + +private: + + idEntity *waypoint; + float steeringSpeed; + float currentSteering; + float idealSteering; + float originHeight; + + void Event_SetVelocity( float _velocity ); + void Event_SetTorque( float _torque ); + void Event_SetSteeringSpeed( float _steeringSpeed ); + void Event_SetWayPoint( idEntity *_waypoint ); +}; + +/* +=============================================================================== + +idAFEntity_SteamPipe + +=============================================================================== +*/ + +class idAFEntity_SteamPipe : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_SteamPipe ); + + idAFEntity_SteamPipe(); + ~idAFEntity_SteamPipe(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + +private: + int steamBody; + float steamForce; + float steamUpForce; + idForce_Constant force; + renderEntity_t steamRenderEntity; + qhandle_t steamModelDefHandle; + + void InitSteamRenderEntity(); +}; + + +/* +=============================================================================== + +idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +class idAFEntity_ClawFourFingers : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_ClawFourFingers ); + + idAFEntity_ClawFourFingers(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idAFConstraint_Hinge * fingers[4]; + + void Event_SetFingerAngle( float angle ); + void Event_StopFingers(); +}; + + +/** +* idHarvestable contains all of the code required to turn an entity into a harvestable +* entity. The entity must create an instance of this class and call the appropriate +* interface methods at the correct time. +*/ +class idHarvestable : public idEntity { +public: + CLASS_PROTOTYPE( idHarvestable ); + + idHarvestable(); + ~idHarvestable(); + + void Spawn(); + void Init(idEntity* parent); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetParent(idEntity* parent); + + void Think(); + void Gib(); + +protected: + idEntityPtr parentEnt; + float triggersize; + idClipModel * trigger; + float giveDelay; + float removeDelay; + bool given; + + idEntityPtr player; + int startTime; + + bool fxFollowPlayer; + idEntityPtr fx; + idStr fxOrient; + +protected: + void BeginBurn(); + void BeginFX(); + void CalcTriggerBounds( float size, idBounds &bounds ); + + bool GetFxOrientationAxis(idMat3& mat); + + void Event_SpawnHarvestTrigger(); + void Event_Touch( idEntity *other, trace_t *trace ); +} ; + + +/* +=============================================================================== + +idAFEntity_Harvest + +=============================================================================== +*/ + + + +class idAFEntity_Harvest : public idAFEntity_WithAttachedHead { +public: + CLASS_PROTOTYPE( idAFEntity_Harvest ); + + idAFEntity_Harvest(); + ~idAFEntity_Harvest(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + +protected: + idEntityPtr harvestEnt; +protected: + void Event_SpawnHarvestEntity(); + +}; + +#endif /* !__GAME_AFENTITY_H__ */ diff --git a/neo/d3xp/Achievements.cpp b/neo/d3xp/Achievements.cpp new file mode 100644 index 00000000..f0ced887 --- /dev/null +++ b/neo/d3xp/Achievements.cpp @@ -0,0 +1,537 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "..\..\doomclassic\doom\doomdef.h" + +idCVar achievements_Verbose( "achievements_Verbose", "1", CVAR_BOOL, "debug spam" ); +idCVar g_demoMode( "g_demoMode", "0", CVAR_INTEGER, "this is a demo" ); + +bool idAchievementManager::cheatingDialogShown = false; + +const struct achievementInfo_t { + int required; + bool lifetime; // true means the current count is stored on the player profile. Doesn't matter for single count achievements. +} achievementInfo [ACHIEVEMENTS_NUM] = { + { 50, true }, // ACHIEVEMENT_EARN_ALL_50_TROPHIES + { 1, true }, // ACHIEVEMENT_COMPLETED_DIFFICULTY_0 + { 1, true }, // ACHIEVEMENT_COMPLETED_DIFFICULTY_1 + { 1, true }, // ACHIEVEMENT_COMPLETED_DIFFICULTY_2 + { 1, true }, // ACHIEVEMENT_COMPLETED_DIFFICULTY_3 + { 64, false }, // ACHIEVEMENT_PDAS_BASE + { 14, false }, // ACHIEVEMENT_WATCH_ALL_VIDEOS + { 1, false }, // ACHIEVEMENT_KILL_MONSTER_WITH_1_HEALTH_LEFT + { 35, false }, // ACHIEVEMENT_OPEN_ALL_LOCKERS + { 20, true }, // ACHIEVEMENT_KILL_20_ENEMY_FISTS_HANDS + { 1, true }, // ACHIEVEMENT_KILL_SCI_NEXT_TO_RCR + { 1, true }, // ACHIEVEMENT_KILL_TWO_IMPS_ONE_SHOTGUN + { 1, true }, // ACHIEVEMENT_SCORE_25000_TURKEY_PUNCHER + { 50, true }, // ACHIEVEMENT_DESTROY_BARRELS + { 1, true }, // ACHIEVEMENT_GET_BFG_FROM_SECURITY_OFFICE + { 1, true }, // ACHIEVEMENT_COMPLETE_LEVEL_WITHOUT_TAKING_DMG + { 1, true }, // ACHIEVEMENT_FIND_RAGE_LOGO + { 1, true }, // ACHIEVEMENT_SPEED_RUN + { 1, true }, // ACHIEVEMENT_DEFEAT_VAGARY_BOSS + { 1, true }, // ACHIEVEMENT_DEFEAT_GUARDIAN_BOSS + { 1, true }, // ACHIEVEMENT_DEFEAT_SABAOTH_BOSS + { 1, true }, // ACHIEVEMENT_DEFEAT_CYBERDEMON_BOSS + { 1, true }, // ACHIEVEMENT_SENTRY_BOT_ALIVE_TO_DEST + { 20, true }, // ACHIEVEMENT_KILL_20_ENEMY_WITH_CHAINSAW + { 1, true }, // ACHIEVEMENT_ID_LOGO_SECRET_ROOM + { 1, true }, // ACHIEVEMENT_BLOODY_HANDWORK_OF_BETRUGER + { 1, true }, // ACHIEVEMENT_TWO_DEMONS_FIGHT_EACH_OTHER + { 20, true }, // ACHIEVEMENT_USE_SOUL_CUBE_TO_DEFEAT_20_ENEMY + { 1, true }, // ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_0 + { 1, true }, // ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_1 + { 1, true }, // ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_2 + { 1, true }, // ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_3 + { 22, false }, // ACHIEVEMENT_PDAS_ROE + { 1, true }, // ACHIEVEMENT_KILL_5_ENEMY_HELL_TIME + { 1, true }, // ACHIEVEMENT_DEFEAT_HELLTIME_HUNTER + { 1, true }, // ACHIEVEMENT_DEFEAT_BERSERK_HUNTER + { 1, true }, // ACHIEVEMENT_DEFEAT_INVULNERABILITY_HUNTER + { 1, true }, // ACHIEVEMENT_DEFEAT_MALEDICT_BOSS + { 20, true }, // ACHIEVEMENT_GRABBER_KILL_20_ENEMY + { 20, true }, // ACHIEVEMENT_ARTIFACT_WITH_BERSERK_PUNCH_20 + { 1, true }, // ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_0 + { 1, true }, // ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_1 + { 1, true }, // ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_2 + { 1, true }, // ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_3 + { 10, false }, // ACHIEVEMENT_PDAS_LE + { 1, true }, // ACHIEVEMENT_MP_KILL_PLAYER_VIA_TELEPORT + { 1, true }, // ACHIEVEMENT_MP_CATCH_ENEMY_IN_ROFC + { 5, true }, // ACHIEVEMENT_MP_KILL_5_PLAYERS_USING_INVIS + { 1, true }, // ACHIEVEMENT_MP_COMPLETE_MATCH_WITHOUT_DYING + { 1, true }, // ACHIEVEMENT_MP_USE_BERSERK_TO_KILL_PLAYER + { 1, true }, // ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG +}; + +/* +================================================================================================ + + idAchievementManager + +================================================================================================ +*/ + +/* +======================== +idAchievementManager::idAchievementManager +======================== +*/ +idAchievementManager::idAchievementManager() : + lastImpKilledTime( 0 ), + lastPlayerKilledTime( 0 ), + playerTookDamage( false ) { + counts.Zero(); + ResetHellTimeKills(); +} + +/* +======================== +idAchievementManager::Init +======================== +*/ +void idAchievementManager::Init( idPlayer * player ) { + owner = player; + SyncAchievments(); +} + +/* +======================== +idAchievementManager::SyncAchievments +======================== +*/ +void idAchievementManager::SyncAchievments() { + idLocalUser * user = GetLocalUser(); + if ( user == NULL || user->GetProfile() == NULL ) { + return; + } + + // Set achievement counts + for ( int i = 0; i < counts.Num(); i++ ) { + if ( user->GetProfile()->GetAchievement( i ) ) { + counts[i] = achievementInfo[i].required; + } else if ( achievementInfo[i].lifetime ) { + counts[i] = user->GetStatInt( i ); + } + } +} + +/* +======================== +idAchievementManager::GetLocalUser +======================== +*/ +idLocalUser * idAchievementManager::GetLocalUser() { + if ( !verify( owner != NULL ) ) { + return NULL; + } + return session->GetGameLobbyBase().GetLocalUserFromLobbyUser( gameLocal.lobbyUserIDs[ owner->GetEntityNumber() ] ); +} + +/* +======================== +idAchievementManager::Save +======================== +*/ +void idAchievementManager::Save( idSaveGame * savefile ) const { + owner.Save( savefile ); + + for ( int i = 0; i < ACHIEVEMENTS_NUM; i++ ) { + savefile->WriteInt( counts[i] ); + } + + savefile->WriteInt( lastImpKilledTime ); + savefile->WriteInt( lastPlayerKilledTime ); + savefile->WriteBool( playerTookDamage ); + savefile->WriteInt( currentHellTimeKills ); +} + +/* +======================== +idAchievementManager::Restore +======================== +*/ +void idAchievementManager::Restore( idRestoreGame * savefile ) { + owner.Restore( savefile ); + + for ( int i = 0; i < ACHIEVEMENTS_NUM; i++ ) { + savefile->ReadInt( counts[i] ); + } + + savefile->ReadInt( lastImpKilledTime ); + savefile->ReadInt( lastPlayerKilledTime ); + savefile->ReadBool( playerTookDamage ); + savefile->ReadInt( currentHellTimeKills ); + + SyncAchievments(); +} + +/* +======================== +idAchievementManager::EventCompletesAchievement +======================== +*/ +void idAchievementManager::EventCompletesAchievement( const achievement_t eventId ) { + if ( g_demoMode.GetBool() ) { + return; + } + + idLocalUser * localUser = GetLocalUser(); + if ( localUser == NULL || localUser->GetProfile() == NULL ) { + + // Send a Reliable Message to the User that needs to unlock this. + if ( owner != NULL ) { + int playerId = owner->entityNumber; + const int bufferSize = sizeof( playerId ) + sizeof( eventId ); + byte buffer[ bufferSize ]; + idBitMsg msg; + msg.InitWrite( buffer, bufferSize ); + + msg.WriteByte( playerId ); + msg.WriteByte( eventId ); + + msg.WriteByteAlign(); + idLib::Printf( "Host Sending Achievement\n"); + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[ owner->entityNumber ], GAME_RELIABLE_MESSAGE_ACHIEVEMENT_UNLOCK, msg ); + } + + return; // Remote user or build game + } + + // Check to see if we've already given the achievement. + // If so, don't do again because we don't want to autosave every time a trigger is hit + if ( localUser->GetProfile()->GetAchievement( eventId ) ) { + return; + } + +#ifdef ID_RETAIL + if ( common->GetConsoleUsed() ) { + if ( !cheatingDialogShown ) { + common->Dialog().AddDialog( GDM_ACHIEVEMENTS_DISABLED_DUE_TO_CHEATING, DIALOG_ACCEPT, NULL, NULL, true ); + cheatingDialogShown = true; + } + return; + } +#endif + + counts[eventId]++; + + if ( counts[eventId] >= achievementInfo[eventId].required ) { + session->GetAchievementSystem().AchievementUnlock( localUser, eventId ); + } else { + if ( achievementInfo[eventId].lifetime ) { + localUser->SetStatInt( eventId, counts[eventId] ); + } + } +} + +/* +======================== +idAchievementManager::IncrementHellTimeKills +======================== +*/ +void idAchievementManager::IncrementHellTimeKills() { + currentHellTimeKills++; + if ( currentHellTimeKills >= 5 ) { + EventCompletesAchievement( ACHIEVEMENT_KILL_5_ENEMY_HELL_TIME ); + } +} + +/* +======================== +idAchievementManager::SavePersistentData +======================== +*/ +void idAchievementManager::SavePersistentData( idDict & playerInfo ) { + for ( int i = 0; i < ACHIEVEMENTS_NUM; ++i ) { + playerInfo.SetInt( va( "ach_%d", i ), counts[i] ); + } +} + +/* +======================== +idAchievementManager::RestorePersistentData +======================== +*/ +void idAchievementManager::RestorePersistentData( const idDict & spawnArgs ) { + for( int i = 0; i < ACHIEVEMENTS_NUM; ++i ) { + counts[i] = spawnArgs.GetInt( va( "ach_%d", i), "0" ); + } +} + + +/* +======================== +idAchievementManager::LocalUser_CompleteAchievement +======================== +*/ +void idAchievementManager::LocalUser_CompleteAchievement( achievement_t id ) { + idLocalUser * localUser = session->GetSignInManager().GetMasterLocalUser(); + + // Check to see if we've already given the achievement. + // If so, don't do again because we don't want to autosave every time a trigger is hit + if( localUser == NULL || localUser->GetProfile()->GetAchievement( id ) ) { + return; + } + +#ifdef ID_RETAIL + if ( common->GetConsoleUsed() ) { + if ( !cheatingDialogShown ) { + common->Dialog().AddDialog( GDM_ACHIEVEMENTS_DISABLED_DUE_TO_CHEATING, DIALOG_ACCEPT, NULL, NULL, true ); + cheatingDialogShown = true; + } + return; + } +#endif + + session->GetAchievementSystem().AchievementUnlock( localUser, id ); +} + +/* +======================== +idAchievementManager::CheckDoomClassicsAchievements + +Processed when the player finishes a level. +======================== +*/ +void idAchievementManager::CheckDoomClassicsAchievements( int killcount, int itemcount, int secretcount, int skill, int mission, int map, int episode, int totalkills, int totalitems, int totalsecret ) { + + const skill_t difficulty = (skill_t)skill; + const currentGame_t currentGame = common->GetCurrentGame(); + const GameMission_t expansion = (GameMission_t)mission; + + + idLocalUser * localUser = session->GetSignInManager().GetMasterLocalUser(); + if ( localUser != NULL && localUser->GetProfile() != NULL ) { + + // GENERAL ACHIEVEMENT UNLOCKING. + if( currentGame == DOOM_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_NEOPHYTE_COMPLETE_ANY_LEVEL ); + } else if( currentGame == DOOM2_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM2_JUST_GETTING_STARTED_COMPLETE_ANY_LEVEL ); + } + + // Complete Any Level on Nightmare. + if ( difficulty == sk_nightmare && currentGame == DOOM_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_NIGHTMARE_COMPLETE_ANY_LEVEL_NIGHTMARE ); + } + + const bool gotAllKills = killcount >= totalkills; + const bool gotAllItems = itemcount >= totalitems; + const bool gotAllSecrets = secretcount >= totalsecret; + + if ( gotAllItems && gotAllKills && gotAllSecrets ) { + if( currentGame == DOOM_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_BURNING_OUT_OF_CONTROL_COMPLETE_KILLS_ITEMS_SECRETS ); + } else if( currentGame == DOOM2_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM2_BURNING_OUT_OF_CONTROL_COMPLETE_KILLS_ITEMS_SECRETS ); + } + } + + // DOOM EXPANSION ACHIEVEMENTS + if( expansion == doom ) { + + if( map == 8 ) { + + // Medium or higher skill level. + if( difficulty >= sk_medium ) { + localUser->SetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_MEDIUM + ( episode - 1 ), 1 ); + } + + // Hard or higher skill level. + if( difficulty >= sk_hard ) { + localUser->SetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_HARD + ( episode - 1 ), 1 ); + localUser->SetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_MEDIUM + ( episode - 1 ), 1 ); + } + + if ( difficulty == sk_nightmare ) { + localUser->SetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_HARD + ( episode - 1 ), 1 ); + localUser->SetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_MEDIUM + ( episode - 1 ), 1 ); + } + + // Save the Settings. + localUser->SaveProfileSettings(); + } + + // Check to see if we've completed all episodes. + const int episode1completed = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_MEDIUM ); + const int episode2completed = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_2_MEDIUM ); + const int episode3completed = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_3_MEDIUM ); + const int episode4completed = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_4_MEDIUM ); + + const int episode1completed_hard = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_1_HARD ); + const int episode2completed_hard = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_2_HARD ); + const int episode3completed_hard = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_3_HARD ); + const int episode4completed_hard = localUser->GetStatInt( STAT_DOOM_COMPLETED_EPISODE_4_HARD ); + + if ( currentGame == DOOM_CLASSIC ) { + if ( episode1completed ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_EPISODE1_COMPLETE_MEDIUM ); + } + + if ( episode2completed ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_EPISODE2_COMPLETE_MEDIUM ); + } + + if ( episode3completed ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_EPISODE3_COMPLETE_MEDIUM ); + } + + if ( episode4completed ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_EPISODE4_COMPLETE_MEDIUM ); + } + + if ( episode1completed_hard && episode2completed_hard && episode3completed_hard && episode4completed_hard ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM1_RAMPAGE_COMPLETE_ALL_HARD ); + } + } + } else if( expansion == doom2 ) { + + if( map == 30 ) { + + if ( currentGame == DOOM2_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM2_FROM_EARTH_TO_HELL_COMPLETE_HELL_ON_EARTH ); + + if ( difficulty >= sk_hard ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM2_SUPERIOR_FIREPOWER_COMPLETE_ALL_HARD ); + } + } + } + } else if( expansion == pack_nerve ) { + if( map == 8 ) { + + if ( currentGame == DOOM2_CLASSIC ) { + LocalUser_CompleteAchievement( ACHIEVEMENT_DOOM2_AND_BACK_AGAIN_COMPLETE_NO_REST ); + } + } + } + + } +} + +/* +================= +AchievementsReset +================= +*/ +CONSOLE_COMMAND( AchievementsReset, "Lock an achievement", NULL ) { + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user == NULL ) { + idLib::Printf( "Must be signed in\n" ); + return; + } + if ( args.Argc() == 1 ) { + for ( int i = 0; i < ACHIEVEMENTS_NUM; i++ ) { + user->SetStatInt( i, 0 ); + session->GetAchievementSystem().AchievementLock( user, i ); + } + } else { + int i = atoi( args.Argv( 1 ) ); + user->SetStatInt( i, 0 ); + session->GetAchievementSystem().AchievementLock( user, i ); + } + user->SaveProfileSettings(); +} + +/* +================= +AchievementsUnlock +================= +*/ +CONSOLE_COMMAND( AchievementsUnlock, "Unlock an achievement", NULL ) { + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user == NULL ) { + idLib::Printf( "Must be signed in\n" ); + return; + } + if ( args.Argc() == 1 ) { + for ( int i = 0; i < ACHIEVEMENTS_NUM; i++ ) { + user->SetStatInt( i, achievementInfo[i].required ); + session->GetAchievementSystem().AchievementUnlock( user, i ); + } + } else { + int i = atoi( args.Argv( 1 ) ); + user->SetStatInt( i, achievementInfo[i].required ); + session->GetAchievementSystem().AchievementUnlock( user, i ); + } + user->SaveProfileSettings(); +} + +/* +================= +AchievementsList +================= +*/ +CONSOLE_COMMAND( AchievementsList, "Lists achievements and status", NULL ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + idLocalUser * user = ( player == NULL ) ? session->GetSignInManager().GetMasterLocalUser() : session->GetGameLobbyBase().GetLocalUserFromLobbyUser( gameLocal.lobbyUserIDs[ player->GetEntityNumber() ] ); + if ( user == NULL ) { + idLib::Printf( "Must be signed in\n" ); + return; + } + idPlayerProfile * profile = user->GetProfile(); + + idArray achievementState; + bool achievementStateValid = session->GetAchievementSystem().GetAchievementState( user, achievementState ); + + for ( int i = 0; i < ACHIEVEMENTS_NUM; i++ ) { + const char * pInfo = ""; + if ( profile == NULL ) { + pInfo = S_COLOR_RED "unknown" S_COLOR_DEFAULT; + } else if ( !profile->GetAchievement( i ) ) { + pInfo = S_COLOR_YELLOW "locked" S_COLOR_DEFAULT; + } else { + pInfo = S_COLOR_GREEN "unlocked" S_COLOR_DEFAULT; + } + const char * sInfo = ""; + if ( !achievementStateValid ) { + sInfo = S_COLOR_RED "unknown" S_COLOR_DEFAULT; + } else if ( !achievementState[i] ) { + sInfo = S_COLOR_YELLOW "locked" S_COLOR_DEFAULT; + } else { + sInfo = S_COLOR_GREEN "unlocked" S_COLOR_DEFAULT; + } + int count = 0; + if ( achievementInfo[i].lifetime ) { + count = user->GetStatInt( i ); + } else if ( player != NULL ) { + count = player->GetAchievementManager().GetCount( (achievement_t) i ); + } else { + count = 0; + } + + achievementDescription_t data; + bool descriptionValid = session->GetAchievementSystem().GetAchievementDescription( user, i, data ); + + idLib::Printf( "%02d: %2d/%2d | %12.12s | %12.12s | %s%s\n", i, count, achievementInfo[i].required, pInfo, sInfo, descriptionValid ? data.hidden ? "(hidden) " : "" : "(unknown) ", descriptionValid ? data.name : "" ); + } +} diff --git a/neo/d3xp/Achievements.h b/neo/d3xp/Achievements.h new file mode 100644 index 00000000..0072d400 --- /dev/null +++ b/neo/d3xp/Achievements.h @@ -0,0 +1,184 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __ACHIEVEMENTS_H__ +#define __ACHIEVEMENTS_H__ + +enum achievement_t { + + ACHIEVEMENT_INVALID = -1, + + ACHIEVEMENT_EARN_ALL_50_TROPHIES, // 0 // DONE -- (automagic?) + + ACHIEVEMENT_COMPLETED_DIFFICULTY_0, // 1 // DONE -- Recruit + ACHIEVEMENT_COMPLETED_DIFFICULTY_1, // 2 // DONE -- Marine + ACHIEVEMENT_COMPLETED_DIFFICULTY_2, // 3 // DONE -- Veteran + ACHIEVEMENT_COMPLETED_DIFFICULTY_3, // 4 // DONE -- Nightmare + + ACHIEVEMENT_PDAS_BASE, // 5 // DONE -- + ACHIEVEMENT_WATCH_ALL_VIDEOS, // 6 // DONE -- + ACHIEVEMENT_KILL_MONSTER_WITH_1_HEALTH_LEFT, // 7 // DONE -- + ACHIEVEMENT_OPEN_ALL_LOCKERS, // 8 // DONE -- + ACHIEVEMENT_KILL_20_ENEMY_FISTS_HANDS, // 9 // DONE --- kill 20 enemies with fists & hands + ACHIEVEMENT_KILL_SCI_NEXT_TO_RCR, // 10 // DONE -----> ADD TARGET TO MAP kill scientist trapped next to reactor control room + ACHIEVEMENT_KILL_TWO_IMPS_ONE_SHOTGUN, // 11 // DONE -- + ACHIEVEMENT_SCORE_25000_TURKEY_PUNCHER, // 12 // DONE -- + ACHIEVEMENT_DESTROY_BARRELS, // 13 // DONE -- + ACHIEVEMENT_GET_BFG_FROM_SECURITY_OFFICE, // 14 // DONE -----> ADD TARGET TO MAP + ACHIEVEMENT_COMPLETE_LEVEL_WITHOUT_TAKING_DMG, // 15 // DONE -- + ACHIEVEMENT_FIND_RAGE_LOGO, // 16 // DONE -----> ADD TARGET TO MAP (jerry) + ACHIEVEMENT_SPEED_RUN, // 17 // DONE -- + + ACHIEVEMENT_DEFEAT_VAGARY_BOSS, // 18 // DONE -- + ACHIEVEMENT_DEFEAT_GUARDIAN_BOSS, // 19 // DONE -- + ACHIEVEMENT_DEFEAT_SABAOTH_BOSS, // 20 // DONE -- + ACHIEVEMENT_DEFEAT_CYBERDEMON_BOSS, // 21 // DONE -- + + ACHIEVEMENT_SENTRY_BOT_ALIVE_TO_DEST, // 22 // DONE -----> ADD TARGET TO MAP + ACHIEVEMENT_KILL_20_ENEMY_WITH_CHAINSAW, // 23 // DONE -- + ACHIEVEMENT_ID_LOGO_SECRET_ROOM, // 24 // DONE -----> ADD TARGET TO MAP + ACHIEVEMENT_BLOODY_HANDWORK_OF_BETRUGER, // 25 // DONE -----> ADD TARGET TO MAP + ACHIEVEMENT_TWO_DEMONS_FIGHT_EACH_OTHER, // 26 // DONE -- + ACHIEVEMENT_USE_SOUL_CUBE_TO_DEFEAT_20_ENEMY, // 27 // DONE -- + + ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_0, // 28 // DONE -- Recruit + ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_1, // 29 // DONE -- Marine + ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_2, // 30 // DONE -- Veteran + ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_3, // 31 // DONE -- Nightmare + + ACHIEVEMENT_PDAS_ROE, // 32 // DONE -- read all pdas in RoE + ACHIEVEMENT_KILL_5_ENEMY_HELL_TIME, // 33 // DONE -- + ACHIEVEMENT_DEFEAT_HELLTIME_HUNTER, // 34 // DONE -- + ACHIEVEMENT_DEFEAT_BERSERK_HUNTER, // 35 // DONE -- + ACHIEVEMENT_DEFEAT_INVULNERABILITY_HUNTER, // 36 // DONE -- + ACHIEVEMENT_DEFEAT_MALEDICT_BOSS, // 37 // DONE -- + ACHIEVEMENT_GRABBER_KILL_20_ENEMY, // 38 // DONE -- + ACHIEVEMENT_ARTIFACT_WITH_BERSERK_PUNCH_20, // 39 // DONE -- + + ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_0, // 40 // DONE -- Recruit + ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_1, // 41 // DONE -- Marine + ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_2, // 42 // DONE -- Veteran + ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_3, // 43 // DONE -- Nightmare + + ACHIEVEMENT_PDAS_LE, // 44 // DONE -- read all pdas in LE + + ACHIEVEMENT_MP_KILL_PLAYER_VIA_TELEPORT, // 45 // DONE -- + ACHIEVEMENT_MP_CATCH_ENEMY_IN_ROFC, // 46 // DONE -- needs to be tested -- Reactor of Frag Chamber + ACHIEVEMENT_MP_KILL_5_PLAYERS_USING_INVIS, // 47 // DONE -- + ACHIEVEMENT_MP_COMPLETE_MATCH_WITHOUT_DYING, // 48 // DONE -- + ACHIEVEMENT_MP_USE_BERSERK_TO_KILL_PLAYER, // 49 // DONE -- + ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG, // 50 // DONE -- + + ACHIEVEMENT_DOOM1_NEOPHYTE_COMPLETE_ANY_LEVEL, // 51 + ACHIEVEMENT_DOOM1_EPISODE1_COMPLETE_MEDIUM, // 52 + ACHIEVEMENT_DOOM1_EPISODE2_COMPLETE_MEDIUM, // 53 + ACHIEVEMENT_DOOM1_EPISODE3_COMPLETE_MEDIUM, // 54 + ACHIEVEMENT_DOOM1_EPISODE4_COMPLETE_MEDIUM, // 55 + ACHIEVEMENT_DOOM1_RAMPAGE_COMPLETE_ALL_HARD, // 56 + ACHIEVEMENT_DOOM1_NIGHTMARE_COMPLETE_ANY_LEVEL_NIGHTMARE, // 57 + ACHIEVEMENT_DOOM1_BURNING_OUT_OF_CONTROL_COMPLETE_KILLS_ITEMS_SECRETS, // 58 + + ACHIEVEMENT_DOOM2_JUST_GETTING_STARTED_COMPLETE_ANY_LEVEL, // 59 + ACHIEVEMENT_DOOM2_FROM_EARTH_TO_HELL_COMPLETE_HELL_ON_EARTH, // 60 + ACHIEVEMENT_DOOM2_AND_BACK_AGAIN_COMPLETE_NO_REST, // 61 + ACHIEVEMENT_DOOM2_SUPERIOR_FIREPOWER_COMPLETE_ALL_HARD, // 62 + ACHIEVEMENT_DOOM2_REALLY_BIG_GUN_FIND_BFG_SINGLEPLAYER, // 63 + ACHIEVEMENT_DOOM2_BURNING_OUT_OF_CONTROL_COMPLETE_KILLS_ITEMS_SECRETS, // 64 + ACHIEVEMENT_DOOM2_IMPORTANT_LOOKING_DOOR_FIND_ANY_SECRET, // 65 + + ACHIEVEMENTS_NUM, + + STAT_DOOM_COMPLETED_EPISODE_1_MEDIUM, + STAT_DOOM_COMPLETED_EPISODE_2_MEDIUM, + STAT_DOOM_COMPLETED_EPISODE_3_MEDIUM, + STAT_DOOM_COMPLETED_EPISODE_4_MEDIUM, + + STAT_DOOM_COMPLETED_EPISODE_1_HARD, + STAT_DOOM_COMPLETED_EPISODE_2_HARD, + STAT_DOOM_COMPLETED_EPISODE_3_HARD, + STAT_DOOM_COMPLETED_EPISODE_4_HARD, +}; + +compile_time_assert( ACHIEVEMENTS_NUM <= idPlayerProfile::MAX_PLAYER_PROFILE_STATS ); + +/* +================================================ +idAchievementManager + +Manages a List of Achievements associated with a particular Player. + +This is setup to only have one achievement manager per game. +================================================ +*/ +class idAchievementManager { +public: + idAchievementManager(); + + void Init( idPlayer * player ); + bool IsInitialized() const { return owner != NULL; } + + // save games + void Save( idSaveGame * savefile ) const; // archives object for save game file + void Restore( idRestoreGame * savefile ); // unarchives object from save game file + + // Debug tool to reset achievement state and counts + void Reset(); + int GetCount( const achievement_t eventId ) const { return counts[eventId]; } + + // Adds a count to the tracked number of events, these events can be applied to multiple achievements + void EventCompletesAchievement( const achievement_t eventId ); + + int GetLastImpKilledTime() { return lastImpKilledTime; } + void SetLastImpKilledTime( int time) { lastImpKilledTime = time; } + int GetLastPlayerKilledTime() { return lastPlayerKilledTime; } + void SetLastPlayerKilledTime( int time ) { lastPlayerKilledTime = time; } + bool GetPlayerTookDamage() { return playerTookDamage; } + void SetPlayerTookDamage( bool bl ) { playerTookDamage = bl; } + void IncrementHellTimeKills(); + void ResetHellTimeKills() { currentHellTimeKills = 0; } + void SavePersistentData( idDict & playerInfo ); + void RestorePersistentData( const idDict & spawnArgs ); + + static void LocalUser_CompleteAchievement( achievement_t id ); + static void CheckDoomClassicsAchievements( int killcount, int itemcount, int secretcount, int skill, int mission, int map, int episode, int totalkills, int totalitems, int totalsecret ); + +private: + idEntityPtr< idPlayer > owner; + idArray counts; // How many times has each achievement been given + + int lastPlayerKilledTime; + int lastImpKilledTime; + bool playerTookDamage; + int currentHellTimeKills; + + static bool cheatingDialogShown; + + idLocalUser * GetLocalUser(); + void SyncAchievments(); +}; + +#endif // !__ACHIEVEMENTS_H__ diff --git a/neo/d3xp/Actor.cpp b/neo/d3xp/Actor.cpp new file mode 100644 index 00000000..5884bb04 --- /dev/null +++ b/neo/d3xp/Actor.cpp @@ -0,0 +1,3474 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/*********************************************************************** + + idAnimState + +***********************************************************************/ + +/* +===================== +idAnimState::idAnimState +===================== +*/ +idAnimState::idAnimState() { + self = NULL; + animator = NULL; + thread = NULL; + idleAnim = true; + disabled = true; + channel = ANIMCHANNEL_ALL; + animBlendFrames = 0; + lastAnimBlendFrames = 0; +} + +/* +===================== +idAnimState::~idAnimState +===================== +*/ +idAnimState::~idAnimState() { + delete thread; +} + +/* +===================== +idAnimState::Save +===================== +*/ +void idAnimState::Save( idSaveGame *savefile ) const { + + savefile->WriteObject( self ); + + // Save the entity owner of the animator + savefile->WriteObject( animator->GetEntity() ); + + savefile->WriteObject( thread ); + + savefile->WriteString( state ); + + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( lastAnimBlendFrames ); + savefile->WriteInt( channel ); + savefile->WriteBool( idleAnim ); + savefile->WriteBool( disabled ); +} + +/* +===================== +idAnimState::Restore +===================== +*/ +void idAnimState::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + + idEntity *animowner; + savefile->ReadObject( reinterpret_cast( animowner ) ); + if ( animowner ) { + animator = animowner->GetAnimator(); + } + + savefile->ReadObject( reinterpret_cast( thread ) ); + + savefile->ReadString( state ); + + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( lastAnimBlendFrames ); + savefile->ReadInt( channel ); + savefile->ReadBool( idleAnim ); + savefile->ReadBool( disabled ); +} + +/* +===================== +idAnimState::Init +===================== +*/ +void idAnimState::Init( idActor *owner, idAnimator *_animator, int animchannel ) { + assert( owner ); + assert( _animator ); + self = owner; + animator = _animator; + channel = animchannel; + + if ( !thread ) { + thread = new idThread(); + thread->ManualDelete(); + } + thread->EndThread(); + thread->ManualControl(); +} + +/* +===================== +idAnimState::Shutdown +===================== +*/ +void idAnimState::Shutdown() { + delete thread; + thread = NULL; +} + +/* +===================== +idAnimState::SetState +===================== +*/ +void idAnimState::SetState( const char *statename, int blendFrames ) { + const function_t *func; + + func = self->scriptObject.GetFunction( statename ); + if ( !func ) { + assert( 0 ); + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, self->scriptObject.GetTypeName() ); + } + + state = statename; + disabled = false; + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + thread->CallFunction( self, func, true ); + + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + disabled = false; + idleAnim = false; + + if ( ai_debugScript.GetInteger() == self->entityNumber ) { + gameLocal.Printf( "%d: %s: Animstate: %s\n", gameLocal.time, self->name.c_str(), state.c_str() ); + } +} + +/* +===================== +idAnimState::StopAnim +===================== +*/ +void idAnimState::StopAnim( int frames ) { + animBlendFrames = 0; + animator->Clear( channel, gameLocal.time, FRAME2MS( frames ) ); +} + +/* +===================== +idAnimState::PlayAnim +===================== +*/ +void idAnimState::PlayAnim( int anim ) { + if ( anim ) { + animator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::CycleAnim +===================== +*/ +void idAnimState::CycleAnim( int anim ) { + if ( anim ) { + animator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::BecomeIdle +===================== +*/ +void idAnimState::BecomeIdle() { + idleAnim = true; +} + +/* +===================== +idAnimState::Disabled +===================== +*/ +bool idAnimState::Disabled() const { + return disabled; +} + +/* +===================== +idAnimState::AnimDone +===================== +*/ +bool idAnimState::AnimDone( int blendFrames ) const { + int animDoneTime; + + animDoneTime = animator->CurrentAnim( channel )->GetEndTime(); + if ( animDoneTime < 0 ) { + // playing a cycle + return false; + } else if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } else { + return false; + } +} + +/* +===================== +idAnimState::IsIdle +===================== +*/ +bool idAnimState::IsIdle() const { + return disabled || idleAnim; +} + +/* +===================== +idAnimState::GetAnimFlags +===================== +*/ +animFlags_t idAnimState::GetAnimFlags() const { + animFlags_t flags; + + memset( &flags, 0, sizeof( flags ) ); + if ( !disabled && !AnimDone( 0 ) ) { + flags = animator->GetAnimFlags( animator->CurrentAnim( channel )->AnimNum() ); + } + + return flags; +} + +/* +===================== +idAnimState::Enable +===================== +*/ +void idAnimState::Enable( int blendFrames ) { + if ( disabled ) { + disabled = false; + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + if ( state.Length() ) { + SetState( state.c_str(), blendFrames ); + } + } +} + +/* +===================== +idAnimState::Disable +===================== +*/ +void idAnimState::Disable() { + disabled = true; + idleAnim = false; +} + +/* +===================== +idAnimState::UpdateState +===================== +*/ +bool idAnimState::UpdateState() { + if ( disabled ) { + return false; + } + + if ( ai_debugScript.GetInteger() == self->entityNumber ) { + thread->EnableDebugInfo(); + } else { + thread->DisableDebugInfo(); + } + + thread->Execute(); + + return true; +} + +/*********************************************************************** + + idActor + +***********************************************************************/ + +const idEventDef AI_EnableEyeFocus( "enableEyeFocus" ); +const idEventDef AI_DisableEyeFocus( "disableEyeFocus" ); +const idEventDef EV_Footstep( "footstep" ); +const idEventDef EV_FootstepLeft( "leftFoot" ); +const idEventDef EV_FootstepRight( "rightFoot" ); +const idEventDef EV_EnableWalkIK( "EnableWalkIK" ); +const idEventDef EV_DisableWalkIK( "DisableWalkIK" ); +const idEventDef EV_EnableLegIK( "EnableLegIK", "d" ); +const idEventDef EV_DisableLegIK( "DisableLegIK", "d" ); +const idEventDef AI_StopAnim( "stopAnim", "dd" ); +const idEventDef AI_PlayAnim( "playAnim", "ds", 'd' ); +const idEventDef AI_PlayCycle( "playCycle", "ds", 'd' ); +const idEventDef AI_IdleAnim( "idleAnim", "ds", 'd' ); +const idEventDef AI_SetSyncedAnimWeight( "setSyncedAnimWeight", "ddf" ); +const idEventDef AI_SetBlendFrames( "setBlendFrames", "dd" ); +const idEventDef AI_GetBlendFrames( "getBlendFrames", "d", 'd' ); +const idEventDef AI_AnimState( "animState", "dsd" ); +const idEventDef AI_GetAnimState( "getAnimState", "d", 's' ); +const idEventDef AI_InAnimState( "inAnimState", "ds", 'd' ); +const idEventDef AI_FinishAction( "finishAction", "s" ); +const idEventDef AI_AnimDone( "animDone", "dd", 'd' ); +const idEventDef AI_OverrideAnim( "overrideAnim", "d" ); +const idEventDef AI_EnableAnim( "enableAnim", "dd" ); +const idEventDef AI_PreventPain( "preventPain", "f" ); +const idEventDef AI_DisablePain( "disablePain" ); +const idEventDef AI_EnablePain( "enablePain" ); +const idEventDef AI_GetPainAnim( "getPainAnim", NULL, 's' ); +const idEventDef AI_SetAnimPrefix( "setAnimPrefix", "s" ); +const idEventDef AI_HasAnim( "hasAnim", "ds", 'f' ); +const idEventDef AI_CheckAnim( "checkAnim", "ds" ); +const idEventDef AI_ChooseAnim( "chooseAnim", "ds", 's' ); +const idEventDef AI_AnimLength( "animLength", "ds", 'f' ); +const idEventDef AI_AnimDistance( "animDistance", "ds", 'f' ); +const idEventDef AI_HasEnemies( "hasEnemies", NULL, 'd' ); +const idEventDef AI_NextEnemy( "nextEnemy", "E", 'e' ); +const idEventDef AI_ClosestEnemyToPoint( "closestEnemyToPoint", "v", 'e' ); +const idEventDef AI_SetNextState( "setNextState", "s" ); +const idEventDef AI_SetState( "setState", "s" ); +const idEventDef AI_GetState( "getState", NULL, 's' ); +const idEventDef AI_GetHead( "getHead", NULL, 'e' ); +const idEventDef EV_SetDamageGroupScale( "setDamageGroupScale", "sf" ); +const idEventDef EV_SetDamageGroupScaleAll( "setDamageGroupScaleAll", "f" ); +const idEventDef EV_GetDamageGroupScale( "getDamageGroupScale", "s", 'f' ); +const idEventDef EV_SetDamageCap( "setDamageCap", "f" ); +const idEventDef EV_SetWaitState( "setWaitState" , "s" ); +const idEventDef EV_GetWaitState( "getWaitState", NULL, 's' ); + +CLASS_DECLARATION( idAFEntity_Gibbable, idActor ) + EVENT( AI_EnableEyeFocus, idActor::Event_EnableEyeFocus ) + EVENT( AI_DisableEyeFocus, idActor::Event_DisableEyeFocus ) + EVENT( EV_Footstep, idActor::Event_Footstep ) + EVENT( EV_FootstepLeft, idActor::Event_Footstep ) + EVENT( EV_FootstepRight, idActor::Event_Footstep ) + EVENT( EV_EnableWalkIK, idActor::Event_EnableWalkIK ) + EVENT( EV_DisableWalkIK, idActor::Event_DisableWalkIK ) + EVENT( EV_EnableLegIK, idActor::Event_EnableLegIK ) + EVENT( EV_DisableLegIK, idActor::Event_DisableLegIK ) + EVENT( AI_PreventPain, idActor::Event_PreventPain ) + EVENT( AI_DisablePain, idActor::Event_DisablePain ) + EVENT( AI_EnablePain, idActor::Event_EnablePain ) + EVENT( AI_GetPainAnim, idActor::Event_GetPainAnim ) + EVENT( AI_SetAnimPrefix, idActor::Event_SetAnimPrefix ) + EVENT( AI_StopAnim, idActor::Event_StopAnim ) + EVENT( AI_PlayAnim, idActor::Event_PlayAnim ) + EVENT( AI_PlayCycle, idActor::Event_PlayCycle ) + EVENT( AI_IdleAnim, idActor::Event_IdleAnim ) + EVENT( AI_SetSyncedAnimWeight, idActor::Event_SetSyncedAnimWeight ) + EVENT( AI_SetBlendFrames, idActor::Event_SetBlendFrames ) + EVENT( AI_GetBlendFrames, idActor::Event_GetBlendFrames ) + EVENT( AI_AnimState, idActor::Event_AnimState ) + EVENT( AI_GetAnimState, idActor::Event_GetAnimState ) + EVENT( AI_InAnimState, idActor::Event_InAnimState ) + EVENT( AI_FinishAction, idActor::Event_FinishAction ) + EVENT( AI_AnimDone, idActor::Event_AnimDone ) + EVENT( AI_OverrideAnim, idActor::Event_OverrideAnim ) + EVENT( AI_EnableAnim, idActor::Event_EnableAnim ) + EVENT( AI_HasAnim, idActor::Event_HasAnim ) + EVENT( AI_CheckAnim, idActor::Event_CheckAnim ) + EVENT( AI_ChooseAnim, idActor::Event_ChooseAnim ) + EVENT( AI_AnimLength, idActor::Event_AnimLength ) + EVENT( AI_AnimDistance, idActor::Event_AnimDistance ) + EVENT( AI_HasEnemies, idActor::Event_HasEnemies ) + EVENT( AI_NextEnemy, idActor::Event_NextEnemy ) + EVENT( AI_ClosestEnemyToPoint, idActor::Event_ClosestEnemyToPoint ) + EVENT( EV_StopSound, idActor::Event_StopSound ) + EVENT( AI_SetNextState, idActor::Event_SetNextState ) + EVENT( AI_SetState, idActor::Event_SetState ) + EVENT( AI_GetState, idActor::Event_GetState ) + EVENT( AI_GetHead, idActor::Event_GetHead ) + EVENT( EV_SetDamageGroupScale, idActor::Event_SetDamageGroupScale ) + EVENT( EV_SetDamageGroupScaleAll, idActor::Event_SetDamageGroupScaleAll ) + EVENT( EV_GetDamageGroupScale, idActor::Event_GetDamageGroupScale ) + EVENT( EV_SetDamageCap, idActor::Event_SetDamageCap ) + EVENT( EV_SetWaitState, idActor::Event_SetWaitState ) + EVENT( EV_GetWaitState, idActor::Event_GetWaitState ) +END_CLASS + +/* +===================== +idActor::idActor +===================== +*/ +idActor::idActor() { + viewAxis.Identity(); + + scriptThread = NULL; // initialized by ConstructScriptObject, which is called by idEntity::Spawn + + use_combat_bbox = false; + head = NULL; + + team = 0; + rank = 0; + fovDot = 0.0f; + eyeOffset.Zero(); + pain_debounce_time = 0; + pain_delay = 0; + pain_threshold = 0; + + state = NULL; + idealState = NULL; + + leftEyeJoint = INVALID_JOINT; + rightEyeJoint = INVALID_JOINT; + soundJoint = INVALID_JOINT; + + modelOffset.Zero(); + deltaViewAngles.Zero(); + + painTime = 0; + allowPain = false; + allowEyeFocus = false; + + waitState = ""; + + blink_anim = NULL; + blink_time = 0; + blink_min = 0; + blink_max = 0; + + finalBoss = false; + damageNotByFists = false; // for killed by fists achievement + + attachments.SetGranularity( 1 ); + + enemyNode.SetOwner( this ); + enemyList.SetOwner( this ); + + aimAssistNode.SetOwner( this ); + aimAssistNode.AddToEnd( gameLocal.aimAssistEntities ); + + damageCap = -1; +} + +/* +===================== +idActor::~idActor +===================== +*/ +idActor::~idActor() { + int i; + idEntity *ent; + + DeconstructScriptObject(); + scriptObject.Free(); + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + aimAssistNode.Remove(); + + ShutdownThreads(); +} + +/* +===================== +idActor::Spawn +===================== +*/ +void idActor::Spawn() { + idEntity *ent; + idStr jointName; + float fovDegrees; + copyJoints_t copyJoint; + + animPrefix = ""; + state = NULL; + idealState = NULL; + + spawnArgs.GetInt( "rank", "0", rank ); + spawnArgs.GetInt( "team", "0", team ); + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + spawnArgs.GetBool( "use_combat_bbox", "0", use_combat_bbox ); + + viewAxis = GetPhysics()->GetAxis(); + + spawnArgs.GetFloat( "fov", "90", fovDegrees ); + SetFOV( fovDegrees ); + + pain_debounce_time = 0; + + pain_delay = SEC2MS( spawnArgs.GetFloat( "pain_delay" ) ); + pain_threshold = spawnArgs.GetInt( "pain_threshold" ); + + LoadAF(); + + walkIK.Init( this, IK_ANIM, modelOffset ); + + // the animation used to be set to the IK_ANIM at this point, but that was fixed, resulting in + // attachments not binding correctly, so we're stuck setting the IK_ANIM before attaching things. + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.SetFrame( ANIMCHANNEL_ALL, animator.GetAnim( IK_ANIM ), 0, 0, 0 ); + + // spawn any attachments we might have + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_attach", NULL ); + while ( kv ) { + idDict args; + + args.Set( "classname", kv->GetValue().c_str() ); + + // make items non-touchable so the player can't take them out of the character's hands + args.Set( "no_touch", "1" ); + + // don't let them drop to the floor + args.Set( "dropToFloor", "0" ); + + gameLocal.SpawnEntityDef( args, &ent ); + if ( !ent ) { + gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() ); + } else { + Attach( ent ); + } + kv = spawnArgs.MatchPrefix( "def_attach", kv ); + } + + SetupDamageGroups(); + SetupHead(); + + // clear the bind anim + animator.ClearAllAnims( gameLocal.time, 0 ); + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } + + if ( headEnt ) { + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + jointName = kv->GetKey(); + if ( jointName.StripLeadingOnce( "copy_joint_world " ) ) { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } else { + jointName.StripLeadingOnce( "copy_joint " ); + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + } + + copyJoint.from = animator.GetJointHandle( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on entity %s", jointName.c_str(), name.c_str() ); + continue; + } + + jointName = kv->GetValue(); + copyJoint.to = headAnimator->GetJointHandle( jointName ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %s", jointName.c_str(), name.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } + } + + // set up blinking + blink_anim = headAnimator->GetAnim( "blink" ); + blink_time = 0; // it's ok to blink right away + blink_min = SEC2MS( spawnArgs.GetFloat( "blink_min", "0.5" ) ); + blink_max = SEC2MS( spawnArgs.GetFloat( "blink_max", "8" ) ); + + // set up the head anim if necessary + int headAnim = headAnimator->GetAnim( "def_head" ); + if ( headAnim ) { + if ( headEnt ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, 0 ); + } else { + headAnimator->CycleAnim( ANIMCHANNEL_HEAD, headAnim, gameLocal.time, 0 ); + } + } + + if ( spawnArgs.GetString( "sound_bone", "", jointName ) ) { + soundJoint = animator.GetJointHandle( jointName ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + finalBoss = spawnArgs.GetBool( "finalBoss" ); + + FinishSetup(); +} + +/* +================ +idActor::FinishSetup +================ +*/ +void idActor::FinishSetup() { + const char *scriptObjectName; + + // setup script object + if ( spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } + + SetupBody(); +} + +/* +================ +idActor::SetupHead +================ +*/ +void idActor::SetupHead() { + idAFAttachment *headEnt; + idStr jointName; + const char *headModel; + jointHandle_t joint; + jointHandle_t damageJoint; + int i; + const idKeyValue *sndKV; + + if ( common->IsClient() ) { + return; + } + + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { + jointName = spawnArgs.GetString( "head_joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'head_joint' on '%s'", jointName.c_str(), name.c_str() ); + } + + // set the damage joint to be part of the head damage group + damageJoint = joint; + for( i = 0; i < damageGroups.Num(); i++ ) { + if ( damageGroups[ i ] == "head" ) { + damageJoint = static_cast( i ); + break; + } + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + // copy slowmo param to the head + args.SetBool( "slowmo", spawnArgs.GetBool("slowmo", "1") ); + + + headEnt = static_cast( gameLocal.SpawnEntityType( idAFAttachment::Type, &args ) ); + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody( this, headModel, damageJoint ); + head = headEnt; + + idStr xSkin; + if ( spawnArgs.GetString( "skin_head_xray", "", xSkin ) ) { + headEnt->xraySkin = declManager->FindSkin( xSkin.c_str() ); + headEnt->UpdateModel(); + } + + idVec3 origin; + idMat3 axis; + idAttachInfo &attach = attachments.Alloc(); + attach.channel = animator.GetChannelForJoint( joint ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + ( origin + modelOffset ) * renderEntity.axis; + attach.ent = headEnt; + headEnt->SetOrigin( origin ); + headEnt->SetAxis( renderEntity.axis ); + headEnt->BindToJoint( this, joint, true ); + } +} + +/* +================ +idActor::CopyJointsFromBodyToHead +================ +*/ +void idActor::CopyJointsFromBodyToHead() { + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !headEnt ) { + return; + } + + headAnimator = headEnt->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = headEnt->GetPhysics()->GetAxis().Transpose(); + GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + animator.GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +================ +idActor::Restart +================ +*/ +void idActor::Restart() { + assert( !head.GetEntity() ); + SetupHead(); + FinishSetup(); +} + +/* +================ +idActor::Save + +archive object for savegame file +================ +*/ +void idActor::Save( idSaveGame *savefile ) const { + idActor *ent; + int i; + + savefile->WriteInt( team ); + savefile->WriteInt( rank ); + savefile->WriteMat3( viewAxis ); + + savefile->WriteInt( enemyList.Num() ); + for ( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + savefile->WriteObject( ent ); + } + + savefile->WriteFloat( fovDot ); + savefile->WriteVec3( eyeOffset ); + savefile->WriteVec3( modelOffset ); + savefile->WriteAngles( deltaViewAngles ); + + savefile->WriteInt( pain_debounce_time ); + savefile->WriteInt( pain_delay ); + savefile->WriteInt( pain_threshold ); + + savefile->WriteInt( damageGroups.Num() ); + for( i = 0; i < damageGroups.Num(); i++ ) { + savefile->WriteString( damageGroups[ i ] ); + } + + savefile->WriteInt( damageScale.Num() ); + for( i = 0; i < damageScale.Num(); i++ ) { + savefile->WriteFloat( damageScale[ i ] ); + } + + savefile->WriteBool( use_combat_bbox ); + head.Save( savefile ); + + savefile->WriteInt( copyJoints.Num() ); + for( i = 0; i < copyJoints.Num(); i++ ) { + savefile->WriteInt( copyJoints[i].mod ); + savefile->WriteJoint( copyJoints[i].from ); + savefile->WriteJoint( copyJoints[i].to ); + } + + savefile->WriteJoint( leftEyeJoint ); + savefile->WriteJoint( rightEyeJoint ); + savefile->WriteJoint( soundJoint ); + + walkIK.Save( savefile ); + + savefile->WriteString( animPrefix ); + savefile->WriteString( painAnim ); + + savefile->WriteInt( blink_anim ); + savefile->WriteInt( blink_time ); + savefile->WriteInt( blink_min ); + savefile->WriteInt( blink_max ); + + // script variables + savefile->WriteObject( scriptThread ); + + savefile->WriteString( waitState ); + + headAnim.Save( savefile ); + torsoAnim.Save( savefile ); + legsAnim.Save( savefile ); + + savefile->WriteBool( allowPain ); + savefile->WriteBool( allowEyeFocus ); + + savefile->WriteInt( painTime ); + + savefile->WriteInt( attachments.Num() ); + for ( i = 0; i < attachments.Num(); i++ ) { + attachments[i].ent.Save( savefile ); + savefile->WriteInt( attachments[i].channel ); + } + + savefile->WriteBool( finalBoss ); + + idToken token; + + //FIXME: this is unneccesary + if ( state ) { + idLexer src( state->Name(), idStr::Length( state->Name() ), "idAI::Save" ); + + src.ReadTokenOnLine( &token ); + src.ExpectTokenString( "::" ); + src.ReadTokenOnLine( &token ); + + savefile->WriteString( token ); + } else { + savefile->WriteString( "" ); + } + + if ( idealState ) { + idLexer src( idealState->Name(), idStr::Length( idealState->Name() ), "idAI::Save" ); + + src.ReadTokenOnLine( &token ); + src.ExpectTokenString( "::" ); + src.ReadTokenOnLine( &token ); + + savefile->WriteString( token ); + } else { + savefile->WriteString( "" ); + } + + savefile->WriteInt(damageCap); + +} + +/* +================ +idActor::Restore + +unarchives object from save game file +================ +*/ +void idActor::Restore( idRestoreGame *savefile ) { + int i, num; + idActor *ent; + + savefile->ReadInt( team ); + savefile->ReadInt( rank ); + savefile->ReadMat3( viewAxis ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->enemyNode.AddToEnd( enemyList ); + } + } + + savefile->ReadFloat( fovDot ); + savefile->ReadVec3( eyeOffset ); + savefile->ReadVec3( modelOffset ); + savefile->ReadAngles( deltaViewAngles ); + + savefile->ReadInt( pain_debounce_time ); + savefile->ReadInt( pain_delay ); + savefile->ReadInt( pain_threshold ); + + savefile->ReadInt( num ); + damageGroups.SetGranularity( 1 ); + damageGroups.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( damageGroups[ i ] ); + } + + savefile->ReadInt( num ); + damageScale.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadFloat( damageScale[ i ] ); + } + + savefile->ReadBool( use_combat_bbox ); + head.Restore( savefile ); + + savefile->ReadInt( num ); + copyJoints.SetNum( num ); + for( i = 0; i < num; i++ ) { + int val; + savefile->ReadInt( val ); + copyJoints[i].mod = static_cast( val ); + savefile->ReadJoint( copyJoints[i].from ); + savefile->ReadJoint( copyJoints[i].to ); + } + + savefile->ReadJoint( leftEyeJoint ); + savefile->ReadJoint( rightEyeJoint ); + savefile->ReadJoint( soundJoint ); + + walkIK.Restore( savefile ); + + savefile->ReadString( animPrefix ); + savefile->ReadString( painAnim ); + + savefile->ReadInt( blink_anim ); + savefile->ReadInt( blink_time ); + savefile->ReadInt( blink_min ); + savefile->ReadInt( blink_max ); + + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + + savefile->ReadString( waitState ); + + headAnim.Restore( savefile ); + torsoAnim.Restore( savefile ); + legsAnim.Restore( savefile ); + + savefile->ReadBool( allowPain ); + savefile->ReadBool( allowEyeFocus ); + + savefile->ReadInt( painTime ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idAttachInfo &attach = attachments.Alloc(); + attach.ent.Restore( savefile ); + savefile->ReadInt( attach.channel ); + } + + savefile->ReadBool( finalBoss ); + + idStr statename; + + savefile->ReadString( statename ); + if ( statename.Length() > 0 ) { + state = GetScriptFunction( statename ); + } + + savefile->ReadString( statename ); + if ( statename.Length() > 0 ) { + idealState = GetScriptFunction( statename ); + } + + savefile->ReadInt(damageCap); +} + +/* +================ +idActor::Hide +================ +*/ +void idActor::Hide() { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Hide(); + if ( ent->IsType( idLight::Type ) ) { + static_cast( ent )->Off(); + } + } + } + UnlinkCombat(); +} + +/* +================ +idActor::Show +================ +*/ +void idActor::Show() { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Show(); + if ( ent->IsType( idLight::Type ) ) { + if(!spawnArgs.GetBool("lights_off", "0")) { + static_cast( ent )->On(); + } + + + } + } + } + LinkCombat(); +} + +/* +============== +idActor::GetDefaultSurfaceType +============== +*/ +int idActor::GetDefaultSurfaceType() const { + return SURFTYPE_FLESH; +} + +/* +================ +idActor::ProjectOverlay +================ +*/ +void idActor::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + idEntity *ent; + idEntity *next; + + idEntity::ProjectOverlay( origin, dir, size, material ); + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + if ( ent->fl.takedamage && ent->spawnArgs.GetBool( "bleed" ) ) { + ent->ProjectOverlay( origin, dir, size, material ); + } + } + } +} + +/* +================ +idActor::LoadAF +================ +*/ +bool idActor::LoadAF() { + idStr fileName; + + if ( !spawnArgs.GetString( "ragdoll", "*unknown*", fileName ) || !fileName.Length() ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName ); +} + +/* +===================== +idActor::SetupBody +===================== +*/ +void idActor::SetupBody() { + const char *jointname; + + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.ClearAllJoints(); + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + jointname = spawnArgs.GetString( "bone_leftEye" ); + leftEyeJoint = headEnt->GetAnimator()->GetJointHandle( jointname ); + + jointname = spawnArgs.GetString( "bone_rightEye" ); + rightEyeJoint = headEnt->GetAnimator()->GetJointHandle( jointname ); + + // set up the eye height. check if it's specified in the def. + if ( !spawnArgs.GetFloat( "eye_height", "0", eyeOffset.z ) ) { + // if not in the def, then try to base it off the idle animation + int anim = headEnt->GetAnimator()->GetAnim( "idle" ); + if ( anim && ( leftEyeJoint != INVALID_JOINT ) ) { + idVec3 pos; + idMat3 axis; + headEnt->GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + headEnt->GetAnimator()->GetJointTransform( leftEyeJoint, gameLocal.time, pos, axis ); + headEnt->GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + headEnt->GetAnimator()->ForceUpdate(); + pos += headEnt->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + eyeOffset = pos + modelOffset; + } else { + // just base it off the bounding box size + eyeOffset.z = GetPhysics()->GetBounds()[ 1 ].z - 6; + } + } + headAnim.Init( this, headEnt->GetAnimator(), ANIMCHANNEL_ALL ); + } else { + jointname = spawnArgs.GetString( "bone_leftEye" ); + leftEyeJoint = animator.GetJointHandle( jointname ); + + jointname = spawnArgs.GetString( "bone_rightEye" ); + rightEyeJoint = animator.GetJointHandle( jointname ); + + // set up the eye height. check if it's specified in the def. + if ( !spawnArgs.GetFloat( "eye_height", "0", eyeOffset.z ) ) { + // if not in the def, then try to base it off the idle animation + int anim = animator.GetAnim( "idle" ); + if ( anim && ( leftEyeJoint != INVALID_JOINT ) ) { + idVec3 pos; + idMat3 axis; + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + animator.GetJointTransform( leftEyeJoint, gameLocal.time, pos, axis ); + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.ForceUpdate(); + eyeOffset = pos + modelOffset; + } else { + // just base it off the bounding box size + eyeOffset.z = GetPhysics()->GetBounds()[ 1 ].z - 6; + } + } + headAnim.Init( this, &animator, ANIMCHANNEL_HEAD ); + } + + waitState = ""; + + torsoAnim.Init( this, &animator, ANIMCHANNEL_TORSO ); + legsAnim.Init( this, &animator, ANIMCHANNEL_LEGS ); +} + +/* +===================== +idActor::CheckBlink +===================== +*/ +void idActor::CheckBlink() { + // check if it's time to blink + if ( !blink_anim || ( health <= 0 ) || !allowEyeFocus || ( blink_time > gameLocal.time ) ) { + return; + } + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetAnimator()->PlayAnim( ANIMCHANNEL_EYELIDS, blink_anim, gameLocal.time, 1 ); + } else { + animator.PlayAnim( ANIMCHANNEL_EYELIDS, blink_anim, gameLocal.time, 1 ); + } + + // set the next blink time + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +================ +idActor::GetPhysicsToVisualTransform +================ +*/ +bool idActor::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +idActor::GetPhysicsToSoundTransform +================ +*/ +bool idActor::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + origin += modelOffset; + axis = viewAxis; + } else { + origin = GetPhysics()->GetGravityNormal() * -eyeOffset.z; + axis.Identity(); + } + return true; +} + +/*********************************************************************** + + script state management + +***********************************************************************/ + +/* +================ +idActor::ShutdownThreads +================ +*/ +void idActor::ShutdownThreads() { + headAnim.Shutdown(); + torsoAnim.Shutdown(); + legsAnim.Shutdown(); + + if ( scriptThread ) { + scriptThread->EndThread(); + scriptThread->PostEventMS( &EV_Remove, 0 ); + delete scriptThread; + scriptThread = NULL; + } +} + +/* +================ +idActor::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idActor::ShouldConstructScriptObjectAtSpawn() const { + return false; +} + +/* +================ +idActor::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idActor::ConstructScriptObject() { + const function_t *constructor; + + // make sure we have a scriptObject + if ( !scriptObject.HasObject() ) { + gameLocal.Error( "No scriptobject set on '%s'. Check the '%s' entityDef.", name.c_str(), GetEntityDefName() ); + } + + if ( !scriptThread ) { + // create script thread + scriptThread = new idThread(); + scriptThread->ManualDelete(); + scriptThread->ManualControl(); + scriptThread->SetThreadName( name.c_str() ); + } else { + scriptThread->EndThread(); + } + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( !constructor ) { + gameLocal.Error( "Missing constructor on '%s' for entity '%s'", scriptObject.GetTypeName(), name.c_str() ); + } + + // init the script object's data + scriptObject.ClearObject(); + + // just set the current function on the script. we'll execute in the subclasses. + scriptThread->CallFunction( this, constructor, true ); + + return scriptThread; +} + +/* +===================== +idActor::GetScriptFunction +===================== +*/ +const function_t *idActor::GetScriptFunction( const char *funcname ) { + const function_t *func; + + func = scriptObject.GetFunction( funcname ); + if ( !func ) { + scriptThread->Error( "Unknown function '%s' in '%s'", funcname, scriptObject.GetTypeName() ); + } + + return func; +} + +/* +===================== +idActor::SetState +===================== +*/ +void idActor::SetState( const function_t *newState ) { + if ( newState == NULL ) { + gameLocal.Error( "idActor::SetState: Null state" ); + return; + } + + if ( ai_debugScript.GetInteger() == entityNumber ) { + gameLocal.Printf( "%d: %s: State: %s\n", gameLocal.time, name.c_str(), newState->Name() ); + } + + state = newState; + idealState = state; + scriptThread->CallFunction( this, state, true ); +} + +/* +===================== +idActor::SetState +===================== +*/ +void idActor::SetState( const char *statename ) { + const function_t *newState; + + newState = GetScriptFunction( statename ); + SetState( newState ); +} + +/* +===================== +idActor::UpdateScript +===================== +*/ +void idActor::UpdateScript() { + int i; + + if ( ai_debugScript.GetInteger() == entityNumber ) { + scriptThread->EnableDebugInfo(); + } else { + scriptThread->DisableDebugInfo(); + } + + // a series of state changes can happen in a single frame. + // this loop limits them in case we've entered an infinite loop. + for( i = 0; i < 20; i++ ) { + if ( idealState != state ) { + SetState( idealState ); + } + + // don't call script until it's done waiting + if ( scriptThread->IsWaiting() ) { + break; + } + + scriptThread->Execute(); + if ( idealState == state ) { + break; + } + } + + if ( i == 20 ) { + scriptThread->Warning( "idActor::UpdateScript: exited loop to prevent lockup" ); + } +} + +/*********************************************************************** + + vision + +***********************************************************************/ + +/* +===================== +idActor::setFov +===================== +*/ +void idActor::SetFOV( float fov ) { + fovDot = (float)cos( DEG2RAD( fov * 0.5f ) ); +} + +/* +===================== +idActor::SetEyeHeight +===================== +*/ +void idActor::SetEyeHeight( float height ) { + eyeOffset.z = height; +} + +/* +===================== +idActor::EyeHeight +===================== +*/ +float idActor::EyeHeight() const { + return eyeOffset.z; +} + +/* +===================== +idActor::EyeOffset +===================== +*/ +idVec3 idActor::EyeOffset() const { + return GetPhysics()->GetGravityNormal() * -eyeOffset.z; +} + +/* +===================== +idActor::GetEyePosition +===================== +*/ +idVec3 idActor::GetEyePosition() const { + return GetPhysics()->GetOrigin() + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); +} + +/* +===================== +idActor::GetViewPos +===================== +*/ +void idActor::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + origin = GetEyePosition(); + axis = viewAxis; +} + +/* +===================== +idActor::CheckFOV +===================== +*/ +bool idActor::CheckFOV( const idVec3 &pos ) const { + if ( fovDot == 1.0f ) { + return true; + } + + float dot; + idVec3 delta; + + delta = pos - GetEyePosition(); + + // get our gravity normal + const idVec3 &gravityDir = GetPhysics()->GetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + dot = viewAxis[ 0 ] * delta; + + return ( dot >= fovDot ); +} + +/* +===================== +idActor::CanSee +===================== +*/ +bool idActor::CanSee( idEntity *ent, bool useFov ) const { + trace_t tr; + idVec3 eye; + idVec3 toPos; + + if ( ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )ent )->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetOrigin(); + } + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + + eye = GetEyePosition(); + + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_OPAQUE, this ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + + return false; +} + +/* +===================== +idActor::PointVisible +===================== +*/ +bool idActor::PointVisible( const idVec3 &point ) const { + trace_t results; + idVec3 start, end; + + start = GetEyePosition(); + end = point; + end[2] += 1.0f; + + gameLocal.clip.TracePoint( results, start, end, MASK_OPAQUE, this ); + return ( results.fraction >= 1.0f ); +} + +/* +===================== +idActor::GetAIAimTargets + +Returns positions for the AI to aim at. +===================== +*/ +void idActor::GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ) { + headPos = lastSightPos + EyeOffset(); + chestPos = ( headPos + lastSightPos + GetPhysics()->GetBounds().GetCenter() ) * 0.5f; +} + +/* +===================== +idActor::GetRenderView +===================== +*/ +renderView_t *idActor::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->viewaxis = viewAxis; + rv->vieworg = GetEyePosition(); + return rv; +} + +/*********************************************************************** + + Model/Ragdoll + +***********************************************************************/ + +/* +================ +idActor::SetCombatModel +================ +*/ +void idActor::SetCombatModel() { + idAFAttachment *headEnt; + + if ( !use_combat_bbox ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { + combatModel = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( modelDefHandle ); + } + + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->SetCombatModel(); + } + } +} + +/* +================ +idActor::GetCombatModel +================ +*/ +idClipModel *idActor::GetCombatModel() const { + return combatModel; +} + +/* +================ +idActor::LinkCombat +================ +*/ +void idActor::LinkCombat() { + idAFAttachment *headEnt; + + if ( fl.hidden || use_combat_bbox ) { + return; + } + + if ( combatModel ) { + combatModel->Link( gameLocal.clip, this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idActor::UnlinkCombat +================ +*/ +void idActor::UnlinkCombat() { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idActor::StartRagdoll +================ +*/ +bool idActor::StartRagdoll() { + float slomoStart, slomoEnd; + float jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd; + float contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd; + + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // disable the monster bounding box + GetPhysics()->DisableClip(); + + // start using the AF + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + slomoStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoStart", "-1.6" ); + slomoEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoEnd", "0.8" ); + + // do the first part of the death in slow motion + af.GetPhysics()->SetTimeScaleRamp( slomoStart, slomoEnd ); + + jointFrictionDent = spawnArgs.GetFloat( "ragdoll_jointFrictionDent", "0.1" ); + jointFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionStart", "0.2" ); + jointFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionEnd", "1.2" ); + + // set joint friction dent + af.GetPhysics()->SetJointFrictionDent( jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd ); + + contactFrictionDent = spawnArgs.GetFloat( "ragdoll_contactFrictionDent", "0.1" ); + contactFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionStart", "1.0" ); + contactFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionEnd", "2.0" ); + + // set contact friction dent + af.GetPhysics()->SetContactFrictionDent( contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd ); + + // drop any items the actor is holding + idMoveableItem::DropItems( this, "death", NULL ); + + // drop any articulated figures the actor is holding + idAFEntity_Base::DropAFs( this, "death", NULL ); + + RemoveAttachments(); + + return true; +} + +/* +================ +idActor::StopRagdoll +================ +*/ +void idActor::StopRagdoll() { + if ( af.IsActive() ) { + af.Stop(); + } +} + +/* +================ +idActor::UpdateAnimationControllers +================ +*/ +bool idActor::UpdateAnimationControllers() { + + if ( af.IsActive() ) { + return idAFEntity_Base::UpdateAnimationControllers(); + } else { + animator.ClearAFPose(); + } + + if ( walkIK.IsInitialized() ) { + walkIK.Evaluate(); + return true; + } + + return false; +} + +/* +================ +idActor::RemoveAttachments +================ +*/ +void idActor::RemoveAttachments() { + int i; + idEntity *ent; + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent != NULL && ent->spawnArgs.GetBool( "remove" ) ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } +} + +/* +================ +idActor::Attach +================ +*/ +void idActor::Attach( idEntity *ent ) { + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + idStr jointName; + idAttachInfo &attach = attachments.Alloc(); + idAngles angleOffset; + idVec3 originOffset; + + jointName = ent->spawnArgs.GetString( "joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() ); + } + + angleOffset = ent->spawnArgs.GetAngles( "angles" ); + originOffset = ent->spawnArgs.GetVector( "origin" ); + + attach.channel = animator.GetChannelForJoint( joint ); + GetJointWorldTransform( joint, gameLocal.time, origin, axis ); + attach.ent = ent; + + ent->SetOrigin( origin + originOffset * renderEntity.axis ); + idMat3 rotate = angleOffset.ToMat3(); + idMat3 newAxis = rotate * axis; + ent->SetAxis( newAxis ); + ent->BindToJoint( this, joint, true ); + ent->cinematic = cinematic; +} + +/* +================ +idActor::Teleport +================ +*/ +void idActor::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + viewAxis = angles.ToMat3(); + + UpdateVisuals(); + + if ( !IsHidden() ) { + // kill anything at the new position + gameLocal.KillBox( this ); + } +} + +/* +================ +idActor::GetDeltaViewAngles +================ +*/ +const idAngles &idActor::GetDeltaViewAngles() const { + return deltaViewAngles; +} + +/* +================ +idActor::SetDeltaViewAngles +================ +*/ +void idActor::SetDeltaViewAngles( const idAngles &delta ) { + deltaViewAngles = delta; +} + +/* +================ +idActor::HasEnemies +================ +*/ +bool idActor::HasEnemies() const { + idActor *ent; + + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden ) { + return true; + } + } + + return false; +} + +/* +================ +idActor::ClosestEnemyToPoint +================ +*/ +idActor *idActor::ClosestEnemyToPoint( const idVec3 &pos ) { + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + + bestDistSquared = idMath::INFINITY; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - pos; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + bestEnt = ent; + bestDistSquared = distSquared; + } + } + + return bestEnt; +} + +/* +================ +idActor::EnemyWithMostHealth +================ +*/ +idActor *idActor::EnemyWithMostHealth() { + idActor *ent; + idActor *bestEnt; + + int most = -9999; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden && ( ent->health > most ) ) { + bestEnt = ent; + most = ent->health; + } + } + return bestEnt; +} + +/* +================ +idActor::OnLadder +================ +*/ +bool idActor::OnLadder() const { + return false; +} + +/* +============== +idActor::GetAASLocation +============== +*/ +void idActor::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + idVec3 size; + idBounds bounds; + + GetFloorPos( 64.0f, pos ); + if ( !aas ) { + areaNum = 0; + return; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aas->PushPointIntoAreaNum( areaNum, pos ); + } +} + +/*********************************************************************** + + animation state + +***********************************************************************/ + +/* +===================== +idActor::SetAnimState +===================== +*/ +void idActor::SetAnimState( int channel, const char *statename, int blendFrames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + assert( 0 ); + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.SetState( statename, blendFrames ); + allowEyeFocus = true; + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.SetState( statename, blendFrames ); + legsAnim.Enable( blendFrames ); + allowPain = true; + allowEyeFocus = true; + break; + + case ANIMCHANNEL_LEGS : + legsAnim.SetState( statename, blendFrames ); + torsoAnim.Enable( blendFrames ); + allowPain = true; + allowEyeFocus = true; + break; + + default: + gameLocal.Error( "idActor::SetAnimState: Unknown anim group" ); + break; + } +} + +/* +===================== +idActor::GetAnimState +===================== +*/ +const char *idActor::GetAnimState( int channel ) const { + switch( channel ) { + case ANIMCHANNEL_HEAD : + return headAnim.state; + break; + + case ANIMCHANNEL_TORSO : + return torsoAnim.state; + break; + + case ANIMCHANNEL_LEGS : + return legsAnim.state; + break; + + default: + gameLocal.Error( "idActor::GetAnimState: Unknown anim group" ); + return NULL; + break; + } +} + +/* +===================== +idActor::InAnimState +===================== +*/ +bool idActor::InAnimState( int channel, const char *statename ) const { + switch( channel ) { + case ANIMCHANNEL_HEAD : + if ( headAnim.state == statename ) { + return true; + } + break; + + case ANIMCHANNEL_TORSO : + if ( torsoAnim.state == statename ) { + return true; + } + break; + + case ANIMCHANNEL_LEGS : + if ( legsAnim.state == statename ) { + return true; + } + break; + + default: + gameLocal.Error( "idActor::InAnimState: Unknown anim group" ); + break; + } + + return false; +} + +/* +===================== +idActor::WaitState +===================== +*/ +const char *idActor::WaitState() const { + if ( waitState.Length() ) { + return waitState; + } else { + return NULL; + } +} + +/* +===================== +idActor::SetWaitState +===================== +*/ +void idActor::SetWaitState( const char *_waitstate ) { + waitState = _waitstate; +} + +/* +===================== +idActor::UpdateAnimState +===================== +*/ +void idActor::UpdateAnimState() { + headAnim.UpdateState(); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); +} + +/* +===================== +idActor::GetAnim +===================== +*/ +int idActor::GetAnim( int channel, const char *animname ) { + int anim; + const char *temp; + idAnimator *animatorPtr; + + if ( channel == ANIMCHANNEL_HEAD ) { + if ( !head.GetEntity() ) { + return 0; + } + animatorPtr = head.GetEntity()->GetAnimator(); + } else { + animatorPtr = &animator; + } + + if ( animPrefix.Length() ) { + temp = va( "%s_%s", animPrefix.c_str(), animname ); + anim = animatorPtr->GetAnim( temp ); + if ( anim ) { + return anim; + } + } + + anim = animatorPtr->GetAnim( animname ); + + return anim; +} + +/* +=============== +idActor::SyncAnimChannels +=============== +*/ +void idActor::SyncAnimChannels( int channel, int syncToChannel, int blendFrames ) { + idAnimator *headAnimator; + idAFAttachment *headEnt; + int anim; + idAnimBlend *syncAnim; + int starttime; + int blendTime; + int cycle; + + blendTime = FRAME2MS( blendFrames ); + if ( channel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = animator.CurrentAnim( syncToChannel ); + if ( syncAnim ) { + anim = headAnimator->GetAnim( syncAnim->AnimFullName() ); + if ( !anim ) { + anim = headAnimator->GetAnim( syncAnim->AnimName() ); + } + if ( anim ) { + cycle = animator.CurrentAnim( syncToChannel )->GetCycleCount(); + starttime = animator.CurrentAnim( syncToChannel )->GetStartTime(); + headAnimator->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, blendTime ); + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->SetStartTime( starttime ); + } else { + headEnt->PlayIdleAnim( blendTime ); + } + } + } + } else if ( syncToChannel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = headAnimator->CurrentAnim( ANIMCHANNEL_ALL ); + if ( syncAnim ) { + anim = GetAnim( channel, syncAnim->AnimFullName() ); + if ( !anim ) { + anim = GetAnim( channel, syncAnim->AnimName() ); + } + if ( anim ) { + cycle = headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetCycleCount(); + starttime = headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime(); + animator.PlayAnim( channel, anim, gameLocal.time, blendTime ); + animator.CurrentAnim( channel )->SetCycleCount( cycle ); + animator.CurrentAnim( channel )->SetStartTime( starttime ); + } + } + } + } else { + animator.SyncAnimChannels( channel, syncToChannel, gameLocal.time, blendTime ); + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idActor::Gib +============ +*/ +void idActor::Gib( const idVec3 &dir, const char *damageDefName ) { + // no gibbing in multiplayer - by self damage or by moving objects + if ( common->IsMultiplayer() ) { + return; + } + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + StopSound( SND_CHANNEL_VOICE, false ); +} + + +/* +============ +idActor::Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +Bleeding wounds and surface overlays are applied in the collision code that +calls Damage() +============ +*/ +idCVar actor_noDamage( "actor_noDamage", "0", CVAR_BOOL, "actors don't take damage -- for testing" ); +void idActor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage || actor_noDamage.GetBool() ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + if ( finalBoss && idStr::FindText( GetEntityDefName(), "monster_boss_cyberdemon" ) == 0 && !inflictor->IsType( idSoulCubeMissile::Type ) ) { + return; + } + + // for killed by fists achievement + if ( attacker->IsType( idPlayer::Type ) && idStr::Cmp( "damage_fists", damageDefName ) ) { + damageNotByFists = true; + } + + SetTimeState ts( timeGroup ); + + // Helltime boss is immune to all projectiles except the helltime killer + if ( finalBoss && idStr::Icmp( GetEntityDefName(), "monster_hunter_helltime" ) == 0 && idStr::Icmp(inflictor->GetEntityDefName(), "projectile_helltime_killer") ) { + return; + } + + // Maledict is immume to the falling asteroids + if ( !idStr::Icmp( GetEntityDefName(), "monster_boss_d3xp_maledict" ) && + (!idStr::Icmp( damageDefName, "damage_maledict_asteroid" ) || !idStr::Icmp( damageDefName, "damage_maledict_asteroid_splash" ) ) ) { + return; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef == NULL ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + return; + } + + int damage = damageDef->GetInt( "damage" ) * damageScale; + damage = GetDamageForLocation( damage, location ); + + // inform the attacker that they hit someone + if ( attacker ) { + attacker->DamageFeedback( this, inflictor, damage ); + } + if ( damage > 0 ) { + int oldHealth = health; + health -= damage; + + //Check the health against any damage cap that is currently set + if(damageCap >= 0 && health < damageCap) { + health = damageCap; + } + + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + if ( oldHealth > 0 ) { + idPlayer *player = NULL; + if ( ( attacker && attacker->IsType( idPlayer::Type ) ) ) { + player = static_cast< idPlayer* >( attacker ); + } + + if ( player != NULL ) { + if ( !damageNotByFists && player->GetExpansionType() == GAME_BASE ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_KILL_20_ENEMY_FISTS_HANDS ); + } + if ( player->PowerUpActive( HELLTIME ) ) { + player->GetAchievementManager().IncrementHellTimeKills(); + } + if ( player->PowerUpActive( BERSERK ) && player->GetExpansionType() == GAME_D3XP && !damageNotByFists ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_ARTIFACT_WITH_BERSERK_PUNCH_20 ); + } + if ( player->GetCurrentWeaponSlot() == player->weapon_chainsaw ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_KILL_20_ENEMY_WITH_CHAINSAW ); + } + if ( ( !name.Find( "monster_boss_vagary") || !name.Find( "vagaryaftercin") ) && player->GetExpansionType() == GAME_BASE ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_VAGARY_BOSS ); + } + if ( !name.Find( "monster_boss_sabaoth") ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_SABAOTH_BOSS ); + } + if ( !name.Find( "monster_boss_cyberdemon") ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_CYBERDEMON_BOSS ); + } + if ( name.Icmp( "hunter_berzerk") == 0 ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_BERSERK_HUNTER ); + } + if ( !name.Find( "monster_hunter_helltime") ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_HELLTIME_HUNTER ); + } + if ( name.Icmp( "hunter") == 0 ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_INVULNERABILITY_HUNTER ); + } + if ( inflictor && inflictor->IsType( idSoulCubeMissile::Type ) ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_USE_SOUL_CUBE_TO_DEFEAT_20_ENEMY ); + } + if ( inflictor && inflictor->IsType( idMoveable::Type ) ) { + idMoveable * moveable = static_cast< idMoveable * >( inflictor ); + // if a moveable is doing damage + // AND it has an attacker (set when the grabber picks up a moveable ) + // AND the moveable's attacker is the attacker here (the player) + // then the player has killed an enemy with a launched moveable from the Grabber + if ( moveable != NULL && moveable->GetAttacker() != NULL && moveable->GetAttacker()->IsType( idPlayer::Type ) && moveable->GetAttacker() == attacker && player->GetExpansionType() == GAME_D3XP && team != player->team ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_GRABBER_KILL_20_ENEMY ); + } + } + + idProjectile *projectile = NULL; + if ( inflictor != NULL && inflictor->IsType( idProjectile::Type ) ) { + projectile = static_cast< idProjectile* >( inflictor ); + if ( projectile != NULL ) { + if ( projectile->GetLaunchedFromGrabber() && player->GetExpansionType() == GAME_D3XP && team != player->team ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_GRABBER_KILL_20_ENEMY ); + } + if ( renderEntity.hModel && idStr::Icmp( renderEntity.hModel->Name(), "models/md5/monsters/imp/imp.md5mesh" ) == 0 ) { + if ( idStr::FindText( inflictor->GetName(), "shotgun" ) > -1 ) { + idStr impName; + int lastKilledImpTime = player->GetAchievementManager().GetLastImpKilledTime(); + if ( ( gameLocal.GetTime() - lastKilledImpTime ) <= 100 && ( impName.Icmp( name ) != 0 ) ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_KILL_TWO_IMPS_ONE_SHOTGUN ); + } else { + player->GetAchievementManager().SetLastImpKilledTime( gameLocal.GetTime() ); + } + } + } + } + } + + if ( player->health == 1 && player->team != this->team ) { // make sure it doesn't unlock if you kill a friendly dude when you have 1 heath.... + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_KILL_MONSTER_WITH_1_HEALTH_LEFT ); + } + } + } + + Killed( inflictor, attacker, damage, dir, location ); + if ( ( health < -20 ) && spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" ) ) { + Gib( dir, damageDefName ); + } + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } else { + // don't accumulate knockback + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } +} + +/* +===================== +idActor::ClearPain +===================== +*/ +void idActor::ClearPain() { + pain_debounce_time = 0; +} + +/* +===================== +idActor::Pain +===================== +*/ +bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + if ( gameLocal.time < pain_debounce_time ) { + return false; + } + + // don't play pain sounds more than necessary + pain_debounce_time = gameLocal.time + pain_delay; + + if ( health > 75 ) { + StartSound( "snd_pain_small", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( health > 50 ) { + StartSound( "snd_pain_medium", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( health > 25 ) { + StartSound( "snd_pain_large", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + StartSound( "snd_pain_huge", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + if ( !allowPain || ( gameLocal.time < painTime ) ) { + // don't play a pain anim + return false; + } + + if ( pain_threshold && ( damage < pain_threshold ) ) { + return false; + } + + // set the pain anim + idStr damageGroup = GetDamageGroup( location ); + + painAnim = ""; + if ( animPrefix.Length() ) { + if ( damageGroup.Length() && ( damageGroup != "legs" ) ) { + sprintf( painAnim, "%s_pain_%s", animPrefix.c_str(), damageGroup.c_str() ); + if ( !animator.HasAnim( painAnim ) ) { + sprintf( painAnim, "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim( painAnim ) ) { + painAnim = ""; + } + } + } + + if ( !painAnim.Length() ) { + sprintf( painAnim, "%s_pain", animPrefix.c_str() ); + if ( !animator.HasAnim( painAnim ) ) { + painAnim = ""; + } + } + } else if ( damageGroup.Length() && ( damageGroup != "legs" ) ) { + sprintf( painAnim, "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim( painAnim ) ) { + sprintf( painAnim, "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim( painAnim ) ) { + painAnim = ""; + } + } + } + + if ( !painAnim.Length() ) { + painAnim = "pain"; + } + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + damageGroup.c_str(), painAnim.c_str() ); + } + + return true; +} + +/* +===================== +idActor::SpawnGibs +===================== +*/ +void idActor::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + idAFEntity_Gibbable::SpawnGibs( dir, damageDefName ); + RemoveAttachments(); +} + +/* +===================== +idActor::SetupDamageGroups + +FIXME: only store group names once and store an index for each joint +===================== +*/ +void idActor::SetupDamageGroups() { + int i; + const idKeyValue *arg; + idStr groupname; + idList jointList; + int jointnum; + float scale; + + // create damage zones + damageGroups.SetNum( animator.NumJoints() ); + arg = spawnArgs.MatchPrefix( "damage_zone ", NULL ); + while ( arg ) { + groupname = arg->GetKey(); + groupname.Strip( "damage_zone " ); + animator.GetJointList( arg->GetValue(), jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + damageGroups[ jointnum ] = groupname; + } + jointList.Clear(); + arg = spawnArgs.MatchPrefix( "damage_zone ", arg ); + } + + // initilize the damage zones to normal damage + damageScale.SetNum( animator.NumJoints() ); + for( i = 0; i < damageScale.Num(); i++ ) { + damageScale[ i ] = 1.0f; + } + + // set the percentage on damage zones + arg = spawnArgs.MatchPrefix( "damage_scale ", NULL ); + while ( arg ) { + scale = atof( arg->GetValue() ); + groupname = arg->GetKey(); + groupname.Strip( "damage_scale " ); + for( i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupname ) { + damageScale[ i ] = scale; + } + } + arg = spawnArgs.MatchPrefix( "damage_scale ", arg ); + } +} + +/* +===================== +idActor::GetDamageForLocation +===================== +*/ +int idActor::GetDamageForLocation( int damage, int location ) { + if ( ( location < 0 ) || ( location >= damageScale.Num() ) ) { + return damage; + } + + return (int)ceil( damage * damageScale[ location ] ); +} + +/* +===================== +idActor::GetDamageGroup +===================== +*/ +const char *idActor::GetDamageGroup( int location ) { + if ( ( location < 0 ) || ( location >= damageGroups.Num() ) ) { + return ""; + } + + return damageGroups[ location ]; +} + + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +===================== +idActor::Event_EnableEyeFocus +===================== +*/ +void idActor::PlayFootStepSound() { + const char *sound = NULL; + const idMaterial *material; + + if ( !GetPhysics()->HasGroundContacts() ) { + return; + } + + // start footstep sound based on material type + material = GetPhysics()->GetContact( 0 ).material; + if ( material != NULL ) { + sound = spawnArgs.GetString( va( "snd_footstep_%s", gameLocal.sufaceTypeNames[ material->GetSurfaceType() ] ) ); + } + if ( sound != NULL && *sound == '\0' ) { + sound = spawnArgs.GetString( "snd_footstep" ); + } + if ( sound != NULL && *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +===================== +idActor::Event_EnableEyeFocus +===================== +*/ +void idActor::Event_EnableEyeFocus() { + allowEyeFocus = true; + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableEyeFocus +===================== +*/ +void idActor::Event_DisableEyeFocus() { + allowEyeFocus = false; + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetAnimator()->Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } else { + animator.Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } +} + +/* +=============== +idActor::Event_Footstep +=============== +*/ +void idActor::Event_Footstep() { + PlayFootStepSound(); +} + +/* +===================== +idActor::Event_EnableWalkIK +===================== +*/ +void idActor::Event_EnableWalkIK() { + walkIK.EnableAll(); +} + +/* +===================== +idActor::Event_DisableWalkIK +===================== +*/ +void idActor::Event_DisableWalkIK() { + walkIK.DisableAll(); +} + +/* +===================== +idActor::Event_EnableLegIK +===================== +*/ +void idActor::Event_EnableLegIK( int num ) { + walkIK.EnableLeg( num ); +} + +/* +===================== +idActor::Event_DisableLegIK +===================== +*/ +void idActor::Event_DisableLegIK( int num ) { + walkIK.DisableLeg( num ); +} + +/* +===================== +idActor::Event_PreventPain +===================== +*/ +void idActor::Event_PreventPain( float duration ) { + painTime = gameLocal.time + SEC2MS( duration ); +} + +/* +=============== +idActor::Event_DisablePain +=============== +*/ +void idActor::Event_DisablePain() { + allowPain = false; +} + +/* +=============== +idActor::Event_EnablePain +=============== +*/ +void idActor::Event_EnablePain() { + allowPain = true; +} + +/* +===================== +idActor::Event_GetPainAnim +===================== +*/ +void idActor::Event_GetPainAnim() { + if ( !painAnim.Length() ) { + idThread::ReturnString( "pain" ); + } else { + idThread::ReturnString( painAnim ); + } +} + +/* +===================== +idActor::Event_SetAnimPrefix +===================== +*/ +void idActor::Event_SetAnimPrefix( const char *prefix ) { + animPrefix = prefix; +} + +/* +=============== +idActor::Event_StopAnim +=============== +*/ +void idActor::Event_StopAnim( int channel, int frames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.StopAnim( frames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_PlayAnim +=============== +*/ +void idActor::Event_PlayAnim( int channel, const char *animname ) { + animFlags_t flags; + idEntity *headEnt; + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.DPrintf( "missing '%s' animation on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.DPrintf( "missing '%s' animation on '%s' (%s)\n", animname, name.c_str(), GetEntityDefName() ); + } + idThread::ReturnInt( 0 ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnim.idleAnim = false; + headAnim.PlayAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.PlayAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.PlayAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default : + gameLocal.Error( "Unknown anim group" ); + break; + } + idThread::ReturnInt( 1 ); +} + +/* +=============== +idActor::Event_PlayCycle +=============== +*/ +void idActor::Event_PlayCycle( int channel, const char *animname ) { + animFlags_t flags; + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.DPrintf( "missing '%s' animation on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.DPrintf( "missing '%s' animation on '%s' (%s)\n", animname, name.c_str(), GetEntityDefName() ); + } + idThread::ReturnInt( false ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.idleAnim = false; + headAnim.CycleAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.CycleAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.CycleAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( true ); +} + +/* +=============== +idActor::Event_IdleAnim +=============== +*/ +void idActor::Event_IdleAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.DPrintf( "missing '%s' animation on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.DPrintf( "missing '%s' animation on '%s' (%s)\n", animname, name.c_str(), GetEntityDefName() ); + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( false ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso body if it doesn't override idle anims + headAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + // everything is idle, so play the anim on the head and copy it to the torso and legs + headAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } else if ( torsoAnim.IsIdle() ) { + // sync the head and torso to the legs + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, headAnim.animBlendFrames ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } else { + // sync the head to the torso + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, headAnim.animBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + if ( legsAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to legs if legs anim doesn't override idle anims + torsoAnim.CycleAnim( anim ); + } else if ( legsAnim.IsIdle() ) { + // play the anim in both legs and torso + torsoAnim.CycleAnim( anim ); + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + // sync the anim to the legs + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } + + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso if torso anim doesn't override idle anims + legsAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() ) { + // play the anim in both legs and torso + legsAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } else { + // sync the anim to the torso + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, legsAnim.animBlendFrames ); + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( true ); +} + +/* +================ +idActor::Event_SetSyncedAnimWeight +================ +*/ +void idActor::Event_SetSyncedAnimWeight( int channel, int anim, float weight ) { + idEntity *headEnt; + + headEnt = head.GetEntity(); + switch( channel ) { + case ANIMCHANNEL_HEAD : + if ( headEnt ) { + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( anim, weight ); + } else { + animator.CurrentAnim( ANIMCHANNEL_HEAD )->SetSyncedAnimWeight( anim, weight ); + } + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + case ANIMCHANNEL_TORSO : + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + if ( headEnt && headAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( anim, weight ); + } + break; + + case ANIMCHANNEL_LEGS : + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( headEnt && headAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } +} + +/* +=============== +idActor::Event_OverrideAnim +=============== +*/ +void idActor::Event_OverrideAnim( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Disable(); + if ( !torsoAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_EnableAnim +=============== +*/ +void idActor::Event_EnableAnim( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Enable( blendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_SetBlendFrames +=============== +*/ +void idActor::Event_SetBlendFrames( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.animBlendFrames = blendFrames; + headAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.animBlendFrames = blendFrames; + torsoAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_LEGS : + legsAnim.animBlendFrames = blendFrames; + legsAnim.lastAnimBlendFrames = blendFrames; + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_GetBlendFrames +=============== +*/ +void idActor::Event_GetBlendFrames( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + idThread::ReturnInt( headAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_TORSO : + idThread::ReturnInt( torsoAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_LEGS : + idThread::ReturnInt( legsAnim.animBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_AnimState +=============== +*/ +void idActor::Event_AnimState( int channel, const char *statename, int blendFrames ) { + SetAnimState( channel, statename, blendFrames ); +} + +/* +=============== +idActor::Event_GetAnimState +=============== +*/ +void idActor::Event_GetAnimState( int channel ) { + const char *state; + + state = GetAnimState( channel ); + idThread::ReturnString( state ); +} + +/* +=============== +idActor::Event_InAnimState +=============== +*/ +void idActor::Event_InAnimState( int channel, const char *statename ) { + bool instate; + + instate = InAnimState( channel, statename ); + idThread::ReturnInt( instate ); +} + +/* +=============== +idActor::Event_FinishAction +=============== +*/ +void idActor::Event_FinishAction( const char *actionname ) { + if ( waitState == actionname ) { + SetWaitState( "" ); + } +} + +/* +=============== +idActor::Event_AnimDone +=============== +*/ +void idActor::Event_AnimDone( int channel, int blendFrames ) { + bool result; + + switch( channel ) { + case ANIMCHANNEL_HEAD : + result = headAnim.AnimDone( blendFrames ); + idThread::ReturnInt( result ); + break; + + case ANIMCHANNEL_TORSO : + result = torsoAnim.AnimDone( blendFrames ); + idThread::ReturnInt( result ); + break; + + case ANIMCHANNEL_LEGS : + result = legsAnim.AnimDone( blendFrames ); + idThread::ReturnInt( result ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } +} + +/* +================ +idActor::Event_HasAnim +================ +*/ +void idActor::Event_HasAnim( int channel, const char *animname ) { + if ( GetAnim( channel, animname ) != NULL ) { + idThread::ReturnFloat( 1.0f ); + } else { + idThread::ReturnFloat( 0.0f ); + } +} + +/* +================ +idActor::Event_CheckAnim +================ +*/ +void idActor::Event_CheckAnim( int channel, const char *animname ) { + if ( !GetAnim( channel, animname ) ) { + if ( animPrefix.Length() ) { + gameLocal.Error( "Can't find anim '%s_%s' for '%s'", animPrefix.c_str(), animname, name.c_str() ); + } else { + gameLocal.Error( "Can't find anim '%s' for '%s'", animname, name.c_str() ); + } + } +} + +/* +================ +idActor::Event_ChooseAnim +================ +*/ +void idActor::Event_ChooseAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + idThread::ReturnString( head.GetEntity()->GetAnimator()->AnimFullName( anim ) ); + return; + } + } else { + idThread::ReturnString( animator.AnimFullName( anim ) ); + return; + } + } + + idThread::ReturnString( "" ); +} + +/* +================ +idActor::Event_AnimLength +================ +*/ +void idActor::Event_AnimLength( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + idThread::ReturnFloat( MS2SEC( head.GetEntity()->GetAnimator()->AnimLength( anim ) ) ); + return; + } + } else { + idThread::ReturnFloat( MS2SEC( animator.AnimLength( anim ) ) ); + return; + } + } + + idThread::ReturnFloat( 0.0f ); +} + +/* +================ +idActor::Event_AnimDistance +================ +*/ +void idActor::Event_AnimDistance( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + idThread::ReturnFloat( head.GetEntity()->GetAnimator()->TotalMovementDelta( anim ).Length() ); + return; + } + } else { + idThread::ReturnFloat( animator.TotalMovementDelta( anim ).Length() ); + return; + } + } + + idThread::ReturnFloat( 0.0f ); +} + +/* +================ +idActor::Event_HasEnemies +================ +*/ +void idActor::Event_HasEnemies() { + bool hasEnemy; + + hasEnemy = HasEnemies(); + idThread::ReturnInt( hasEnemy ); +} + +/* +================ +idActor::Event_NextEnemy +================ +*/ +void idActor::Event_NextEnemy( idEntity *ent ) { + idActor *actor; + + if ( !ent || ( ent == this ) ) { + actor = enemyList.Next(); + } else { + if ( !ent->IsType( idActor::Type ) ) { + gameLocal.Error( "'%s' cannot be an enemy", ent->name.c_str() ); + } + + actor = static_cast( ent ); + if ( actor->enemyNode.ListHead() != &enemyList ) { + gameLocal.Error( "'%s' is not in '%s' enemy list", actor->name.c_str(), name.c_str() ); + } + } + + for( ; actor != NULL; actor = actor->enemyNode.Next() ) { + if ( !actor->fl.hidden ) { + idThread::ReturnEntity( actor ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idActor::Event_ClosestEnemyToPoint +================ +*/ +void idActor::Event_ClosestEnemyToPoint( const idVec3 &pos ) { + idActor *bestEnt = ClosestEnemyToPoint( pos ); + idThread::ReturnEntity( bestEnt ); +} + +/* +================ +idActor::Event_StopSound +================ +*/ +void idActor::Event_StopSound( int channel, int netSync ) { + if ( channel == SND_CHANNEL_VOICE ) { + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->StopSound( channel, ( netSync != 0 ) ); + } + } + StopSound( channel, ( netSync != 0 ) ); +} + +/* +===================== +idActor::Event_SetNextState +===================== +*/ +void idActor::Event_SetNextState( const char *name ) { + idealState = GetScriptFunction( name ); + if ( idealState == state ) { + state = NULL; + } +} + +/* +===================== +idActor::Event_SetState +===================== +*/ +void idActor::Event_SetState( const char *name ) { + idealState = GetScriptFunction( name ); + if ( idealState == state ) { + state = NULL; + } + scriptThread->DoneProcessing(); +} + +/* +===================== +idActor::Event_GetState +===================== +*/ +void idActor::Event_GetState() { + if ( state ) { + idThread::ReturnString( state->Name() ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +===================== +idActor::Event_GetHead +===================== +*/ +void idActor::Event_GetHead() { + idThread::ReturnEntity( head.GetEntity() ); +} + +/* +================ +idActor::Event_SetDamageGroupScale +================ +*/ +void idActor::Event_SetDamageGroupScale( const char* groupName, float scale) { + + for( int i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupName ) { + damageScale[ i ] = scale; + } + } +} + +/* +================ +idActor::Event_SetDamageGroupScaleAll +================ +*/ +void idActor::Event_SetDamageGroupScaleAll( float scale ) { + + for( int i = 0; i < damageScale.Num(); i++ ) { + damageScale[ i ] = scale; + } +} + +void idActor::Event_GetDamageGroupScale( const char* groupName ) { + + for( int i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupName ) { + idThread::ReturnFloat(damageScale[i]); + return; + } + } + + idThread::ReturnFloat(0); +} + +void idActor::Event_SetDamageCap( float _damageCap ) { + damageCap = _damageCap; +} + +void idActor::Event_SetWaitState( const char* waitState) { + SetWaitState(waitState); +} + +void idActor::Event_GetWaitState() { + if(WaitState()) { + idThread::ReturnString(WaitState()); + } else { + idThread::ReturnString(""); + } +} diff --git a/neo/d3xp/Actor.h b/neo/d3xp/Actor.h new file mode 100644 index 00000000..47327486 --- /dev/null +++ b/neo/d3xp/Actor.h @@ -0,0 +1,333 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_ACTOR_H__ +#define __GAME_ACTOR_H__ + +/* +=============================================================================== + + idActor + +=============================================================================== +*/ + +extern const idEventDef AI_EnableEyeFocus; +extern const idEventDef AI_DisableEyeFocus; +extern const idEventDef EV_Footstep; +extern const idEventDef EV_FootstepLeft; +extern const idEventDef EV_FootstepRight; +extern const idEventDef EV_EnableWalkIK; +extern const idEventDef EV_DisableWalkIK; +extern const idEventDef EV_EnableLegIK; +extern const idEventDef EV_DisableLegIK; +extern const idEventDef AI_SetAnimPrefix; +extern const idEventDef AI_PlayAnim; +extern const idEventDef AI_PlayCycle; +extern const idEventDef AI_AnimDone; +extern const idEventDef AI_SetBlendFrames; +extern const idEventDef AI_GetBlendFrames; + +extern const idEventDef AI_SetState; + +class idDeclParticle; + +class idAnimState { +public: + bool idleAnim; + idStr state; + int animBlendFrames; + int lastAnimBlendFrames; // allows override anims to blend based on the last transition time + +public: + idAnimState(); + ~idAnimState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Init( idActor *owner, idAnimator *_animator, int animchannel ); + void Shutdown(); + void SetState( const char *name, int blendFrames ); + void StopAnim( int frames ); + void PlayAnim( int anim ); + void CycleAnim( int anim ); + void BecomeIdle(); + bool UpdateState(); + bool Disabled() const; + void Enable( int blendFrames ); + void Disable(); + bool AnimDone( int blendFrames ) const; + bool IsIdle() const; + animFlags_t GetAnimFlags() const; + +private: + idActor * self; + idAnimator * animator; + idThread * thread; + int channel; + bool disabled; +}; + +class idAttachInfo { +public: + idEntityPtr ent; + int channel; +}; + +typedef struct { + jointModTransform_t mod; + jointHandle_t from; + jointHandle_t to; +} copyJoints_t; + +class idActor : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idActor ); + + int team; + int rank; // monsters don't fight back if the attacker's rank is higher + idMat3 viewAxis; // view axis of the actor + + idLinkList enemyNode; // node linked into an entity's enemy list for quick lookups of who is attacking him + idLinkList enemyList; // list of characters that have targeted the player as their enemy + +public: + idActor(); + virtual ~idActor(); + + void Spawn(); + virtual void Restart(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide(); + virtual void Show(); + virtual int GetDefaultSurfaceType() const; + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual bool LoadAF(); + void SetupBody(); + + void CheckBlink(); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + // script state management + void ShutdownThreads(); + virtual bool ShouldConstructScriptObjectAtSpawn() const; + virtual idThread * ConstructScriptObject(); + void UpdateScript(); + const function_t *GetScriptFunction( const char *funcname ); + void SetState( const function_t *newState ); + void SetState( const char *statename ); + + // vision testing + void SetEyeHeight( float height ); + float EyeHeight() const; + idVec3 EyeOffset() const; + idVec3 GetEyePosition() const; + virtual void GetViewPos( idVec3 &origin, idMat3 &axis ) const; + void SetFOV( float fov ); + bool CheckFOV( const idVec3 &pos ) const; + bool CanSee( idEntity *ent, bool useFOV ) const; + bool PointVisible( const idVec3 &point ) const; + virtual void GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ); + + // damage + void SetupDamageGroups(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + int GetDamageForLocation( int damage, int location ); + const char * GetDamageGroup( int location ); + void ClearPain(); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // model/combat model/ragdoll + void SetCombatModel(); + idClipModel * GetCombatModel() const; + virtual void LinkCombat(); + virtual void UnlinkCombat(); + bool StartRagdoll(); + void StopRagdoll(); + virtual bool UpdateAnimationControllers(); + + // delta view angles to allow movers to rotate the view of the actor + const idAngles & GetDeltaViewAngles() const; + void SetDeltaViewAngles( const idAngles &delta ); + + bool HasEnemies() const; + idActor * ClosestEnemyToPoint( const idVec3 &pos ); + idActor * EnemyWithMostHealth(); + + virtual bool OnLadder() const; + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + + void Attach( idEntity *ent ); + + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual renderView_t * GetRenderView(); + + // animation state control + int GetAnim( int channel, const char *name ); + void UpdateAnimState(); + void SetAnimState( int channel, const char *name, int blendFrames ); + const char * GetAnimState( int channel ) const; + bool InAnimState( int channel, const char *name ) const; + const char * WaitState() const; + void SetWaitState( const char *_waitstate ); + bool AnimDone( int channel, int blendFrames ) const; + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + + idEntity* GetHeadEntity() { return head.GetEntity(); }; + +protected: + friend class idAnimState; + + float fovDot; // cos( fovDegrees ) + idVec3 eyeOffset; // offset of eye relative to physics origin + idVec3 modelOffset; // offset of visual model relative to the physics origin + + idAngles deltaViewAngles; // delta angles relative to view input angles + + int pain_debounce_time; // next time the actor can show pain + int pain_delay; // time between playing pain sound + int pain_threshold; // how much damage monster can take at any one time before playing pain animation + + idStrList damageGroups; // body damage groups + idList damageScale; // damage scale per damage gruop + + bool use_combat_bbox; // whether to use the bounding box for combat collision + idEntityPtr head; + idList copyJoints; // copied from the body animation to the head model + + // state variables + const function_t *state; + const function_t *idealState; + + // joint handles + jointHandle_t leftEyeJoint; + jointHandle_t rightEyeJoint; + jointHandle_t soundJoint; + + idIK_Walk walkIK; + + idStr animPrefix; + idStr painAnim; + + // blinking + int blink_anim; + int blink_time; + int blink_min; + int blink_max; + + // script variables + idThread * scriptThread; + idStr waitState; + idAnimState headAnim; + idAnimState torsoAnim; + idAnimState legsAnim; + + bool allowPain; + bool allowEyeFocus; + bool finalBoss; + + int painTime; + bool damageNotByFists; + + idList attachments; + + int damageCap; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + + // removes attachments with "remove" set for when character dies + void RemoveAttachments(); + + // copies animation from body to head joints + void CopyJointsFromBodyToHead(); + +private: + void SyncAnimChannels( int channel, int syncToChannel, int blendFrames ); + void FinishSetup(); + void SetupHead(); + void PlayFootStepSound(); + + void Event_EnableEyeFocus(); + void Event_DisableEyeFocus(); + void Event_Footstep(); + void Event_EnableWalkIK(); + void Event_DisableWalkIK(); + void Event_EnableLegIK( int num ); + void Event_DisableLegIK( int num ); + void Event_SetAnimPrefix( const char *name ); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_PreventPain( float duration ); + void Event_DisablePain(); + void Event_EnablePain(); + void Event_GetPainAnim(); + void Event_StopAnim( int channel, int frames ); + void Event_PlayAnim( int channel, const char *name ); + void Event_PlayCycle( int channel, const char *name ); + void Event_IdleAnim( int channel, const char *name ); + void Event_SetSyncedAnimWeight( int channel, int anim, float weight ); + void Event_OverrideAnim( int channel ); + void Event_EnableAnim( int channel, int blendFrames ); + void Event_SetBlendFrames( int channel, int blendFrames ); + void Event_GetBlendFrames( int channel ); + void Event_AnimState( int channel, const char *name, int blendFrames ); + void Event_GetAnimState( int channel ); + void Event_InAnimState( int channel, const char *name ); + void Event_FinishAction( const char *name ); + void Event_AnimDone( int channel, int blendFrames ); + void Event_HasAnim( int channel, const char *name ); + void Event_CheckAnim( int channel, const char *animname ); + void Event_ChooseAnim( int channel, const char *animname ); + void Event_AnimLength( int channel, const char *animname ); + void Event_AnimDistance( int channel, const char *animname ); + void Event_HasEnemies(); + void Event_NextEnemy( idEntity *ent ); + void Event_ClosestEnemyToPoint( const idVec3 &pos ); + void Event_StopSound( int channel, int netsync ); + void Event_SetNextState( const char *name ); + void Event_SetState( const char *name ); + void Event_GetState(); + void Event_GetHead(); + void Event_SetDamageGroupScale( const char* groupName, float scale); + void Event_SetDamageGroupScaleAll( float scale ); + void Event_GetDamageGroupScale( const char* groupName ); + void Event_SetDamageCap( float _damageCap ); + void Event_SetWaitState( const char* waitState); + void Event_GetWaitState(); + +}; + +#endif /* !__GAME_ACTOR_H__ */ diff --git a/neo/d3xp/AimAssist.cpp b/neo/d3xp/AimAssist.cpp new file mode 100644 index 00000000..b168c03b --- /dev/null +++ b/neo/d3xp/AimAssist.cpp @@ -0,0 +1,436 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +================================================================================================ +Contains the AimAssist implementation. +================================================================================================ +*/ + +idCVar aa_targetAimAssistEnable ( "aa_targetAimAssistEnable", "0", CVAR_BOOL | CVAR_ARCHIVE, "Enables/Disables the entire Aim Assist system" ); + +idCVar aa_targetAdhesionEnable ( "aa_targetAdhesionEnable", "1", CVAR_BOOL, "Enables Target Adhesion" ); +idCVar aa_targetFrictionEnable ( "aa_targetFrictionEnable", "1", CVAR_BOOL, "Enables Target Friction" ); + +// Selection +idCVar aa_targetMaxDistance ( "aa_targetMaxDistance", "3000", CVAR_FLOAT, "The Maximum Distance away for a target to be considered for adhesion, friction and target lock-to" ); +idCVar aa_targetSelectionRadius ( "aa_targetSelectionRadius", "128.0", CVAR_FLOAT, "Radius used to select targets for auto aiming" ); + +// Adhesion +idCVar aa_targetAdhesionRadius ( "aa_targetAdhesionRadius", "96.0", CVAR_FLOAT, "Radius used to apply adhesion amount" ); +idCVar aa_targetAdhesionYawSpeedMax ( "aa_targetAdhesionYawSpeedMax", "0.6", CVAR_FLOAT, "Max Yaw Adhesion Speed" ); +idCVar aa_targetAdhesionPitchSpeedMax ( "aa_targetAdhesionPitchSpeedMax", "0.6", CVAR_FLOAT, "Max Pitch Adhesion Speed" ); +idCVar aa_targetAdhesionContributionPctMax ( "aa_targetAdhesionContributionPctMax", "0.25", CVAR_FLOAT, "Max Adhesion Contribution Percentage - Range 0.0 - 1.0" ); +idCVar aa_targetAdhesionPlayerSpeedThreshold ( "aa_targetAdhesionPlayerSpeedThreshold", "10.0", CVAR_FLOAT, "Speed Threshold that determines how fast the player needs to move before adhesion is allowed to kick in" ); + +// Friction +idCVar aa_targetFrictionMaxDistance ( "aa_targetFrictionMaxDistance", "1024.0", CVAR_FLOAT, "Minimum Distance Friction takes effect" ); +idCVar aa_targetFrictionOptimalDistance ( "aa_targetFrictionOptimalDistance", "768.0", CVAR_FLOAT, "Optimal Distance for Friction to take an effect" ); +idCVar aa_targetFrictionRadius ( "aa_targetFrictionRadius", "96.0", CVAR_FLOAT, "Friction Collision Sphere Radius" ); +idCVar aa_targetFrictionOptimalRadius ( "aa_targetFrictionOptimalRadius", "192.0", CVAR_FLOAT, "Friction Collision Sphere Radius when at Optimal Distance" ); +idCVar aa_targetFrictionMultiplierMin ( "aa_targetFrictionMultiplierMin", "1.0", CVAR_FLOAT, "Minimum Friction Scalar" ); +idCVar aa_targetFrictionMultiplierMax ( "aa_targetFrictionMultiplierMax", "0.4", CVAR_FLOAT, "Maximum Friction Scalar" ); + +/* +======================== +idAimAssist::Init +======================== +*/ +void idAimAssist::Init( idPlayer *player_ ) { + player = player_; + angleCorrection = ang_zero; + frictionScalar = 1.0f; + lastTargetPos = vec3_zero; +} + +/* +======================== +idAimAssist::Update +======================== +*/ +void idAimAssist::Update() { + angleCorrection = ang_zero; + + UpdateNewAimAssist(); +} + +/* +======================== +idAimAssist::UpdateNewAimAssist +======================== +*/ +void idAimAssist::UpdateNewAimAssist() { + + angleCorrection = ang_zero; + frictionScalar = 1.0f; + idEntity* lastTarget = targetEntity; + targetEntity = NULL; + + // is aim assisting allowed? If not then just bail out + if ( !aa_targetAimAssistEnable.GetBool() ) { + return; + } + + bool forceLastTarget = false; + idVec3 targetPos; + + idEntity * entity = NULL; + if ( forceLastTarget ) { + entity = lastTarget; + targetPos = lastTargetPos; + } else { + entity = FindAimAssistTarget( targetPos ); + } + + if ( entity != NULL ) { + + UpdateFriction( entity, targetPos ); + + // by default we don't allow adhesion when we are standing still + const float playerMovementSpeedThreshold = Square( aa_targetAdhesionPlayerSpeedThreshold.GetFloat() ); + float playerSpeed = player->GetPhysics()->GetLinearVelocity().LengthSqr(); + + // only allow adhesion on actors (ai) or players. Disallow adhesion on any static world entities such as explosive barrels + if ( playerSpeed > playerMovementSpeedThreshold ) { + UpdateAdhesion( entity, targetPos ); + } + + targetEntity = entity; + } + + lastTargetPos = targetPos; +} + +/* +======================== +idAimAssist::FindAimAssistTarget +======================== +*/ +idEntity* idAimAssist::FindAimAssistTarget( idVec3& targetPos ) { + if ( player == NULL ) { + return NULL; + } + + //TO DO: Make this faster + //TO DO: Defer Traces + idEntity * optimalTarget = NULL; + float currentBestScore = -idMath::INFINITY; + targetPos = vec3_zero; + + idVec3 cameraPos; + idMat3 cameraAxis; + player->GetViewPos( cameraPos, cameraAxis ); + + float maxDistanceSquared = Square( aa_targetMaxDistance.GetFloat() ); + + idVec3 dirToTarget; + float distanceToTargetSquared; + idVec3 primaryTargetPos; + idVec3 secondaryTargetPos; + + for ( idEntity * entity = gameLocal.aimAssistEntities.Next(); entity != NULL; entity = entity->aimAssistNode.Next() ) { + if ( !entity->IsActive() ) { + continue; + } + + if ( entity->IsHidden() ) { + continue; + } + + if ( entity->IsType( idActor::Type ) ) { + idActor * actor = static_cast( entity ); + if ( actor->team == player->team ) { + // In DM, LMS, and Tourney, all players are on the same team + if ( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_SP ) { + continue; + } + } + } + + if ( entity->IsType( idAI::Type ) ) { + idAI * aiEntity = static_cast( entity ); + if ( aiEntity->ReactionTo( player ) == ATTACK_IGNORE ) { + continue; + } + } + + // check whether we have a valid target position for this entity - skip it if we don't + if ( !ComputeTargetPos( entity, primaryTargetPos, secondaryTargetPos ) ) { + continue; + } + + // is it close enough to us + dirToTarget = primaryTargetPos-cameraPos; + distanceToTargetSquared = dirToTarget.LengthSqr(); + if ( distanceToTargetSquared > maxDistanceSquared ) { + continue; + } + + // Compute a score in the range of 0..1 for how much are looking towards the target. + idVec3 forward = cameraAxis[ 0 ]; + forward.Normalize(); + dirToTarget.Normalize(); + float ViewDirDotTargetDir = idMath::ClampFloat( -1.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error + + // throw out anything thats outside of weapon's global FOV. + if ( ViewDirDotTargetDir < 0.0f ) { + continue; + } + + // to be consistent we always use the primaryTargetPos to compute the score for this entity + float computedScore = ComputeEntityAimAssistScore( primaryTargetPos, cameraPos, cameraAxis ); + + // check if the current score beats our current best score and we have line of sight to it. + if ( computedScore > currentBestScore ) { + + // determine if the current target is in our line of sight + trace_t tr; + gameLocal.clip.TracePoint( tr, cameraPos, primaryTargetPos, MASK_MONSTERSOLID, player ); + + // did our trace fail? + if ( ( ( tr.fraction < 1.0f ) && ( tr.c.entityNum != entity->entityNumber ) ) || ( tr.fraction >= 1.0f ) ) { + + // if the collision test failed for the primary position -- check the secondary position + trace_t tr2; + gameLocal.clip.TracePoint( tr2, cameraPos, secondaryTargetPos, MASK_MONSTERSOLID, player ); + + if ( ( ( tr2.fraction < 1.0f ) && ( tr2.c.entityNum != entity->entityNumber ) ) || ( tr2.fraction >= 1.0f ) ) { + // if the secondary position is also not visible then give up + continue; + } + + // we can see the secondary target position so we should consider this entity but use + // the secondary position as the target position + targetPos = secondaryTargetPos; + } else { + targetPos = primaryTargetPos; + } + + // if we got here then this is our new best score + optimalTarget = entity; + currentBestScore = computedScore; + } + } + + return optimalTarget; +} + +/* +======================== +idAimAssist::ComputeEntityAimAssistScore +======================== +*/ +float idAimAssist::ComputeEntityAimAssistScore( const idVec3& targetPos, const idVec3& cameraPos, const idMat3& cameraAxis ) { + + float score = 0.0f; + + idVec3 dirToTarget = targetPos - cameraPos; + float distanceToTarget = dirToTarget.Length(); + + // Compute a score in the range of 0..1 for how much are looking towards the target. + idVec3 forward = cameraAxis[0]; + forward.Normalize(); + dirToTarget.Normalize(); + float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error + + // the more we look at the target the higher our score + score = ViewDirDotTargetDir; + + // weigh the score from the view angle higher than the distance score + static float aimWeight = 0.8f; + score *= aimWeight; + // Add a score of 0..1 for how close the target is to the player + if ( distanceToTarget < aa_targetMaxDistance.GetFloat() ) { + float distanceScore = 1.0f - ( distanceToTarget / aa_targetMaxDistance.GetFloat() ); + float distanceWeight = 1.0f - aimWeight; + score += ( distanceScore * distanceWeight ); + } + + return score * 1000.0f; +} + +/* +======================== +idAimAssist::UpdateAdhesion +======================== +*/ +void idAimAssist::UpdateAdhesion( idEntity* pTarget, const idVec3& targetPos ) { + + if ( !aa_targetAdhesionEnable.GetBool() ) { + return; + } + + if ( !pTarget ) { + return; + } + + float contributionPctMax = aa_targetAdhesionContributionPctMax.GetFloat(); + + idVec3 cameraPos; + idMat3 cameraAxis; + player->GetViewPos(cameraPos, cameraAxis); + + idAngles cameraViewAngles = cameraAxis.ToAngles(); + cameraViewAngles.Normalize180(); + + idVec3 cameraViewPos = cameraPos; + idVec3 dirToTarget = targetPos - cameraViewPos; + float distanceToTarget = dirToTarget.Length(); + + idAngles aimAngles = dirToTarget.ToAngles(); + aimAngles.Normalize180(); + + // find the delta + aimAngles -= cameraViewAngles; + + // clamp velocities to some max values + aimAngles.yaw = idMath::ClampFloat( -aa_targetAdhesionYawSpeedMax.GetFloat(), aa_targetAdhesionYawSpeedMax.GetFloat(), aimAngles.yaw ); + aimAngles.pitch = idMath::ClampFloat( -aa_targetAdhesionPitchSpeedMax.GetFloat(), aa_targetAdhesionPitchSpeedMax.GetFloat(), aimAngles.pitch ); + + idVec3 forward = cameraAxis[0]; + forward.Normalize(); + dirToTarget.Normalize(); + float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error + float aimLength = ViewDirDotTargetDir * distanceToTarget; + idVec3 aimPoint = cameraPos + ( forward * aimLength ); + float delta = idMath::Sqrt( Square( distanceToTarget ) - Square( aimLength ) ); + + float contribution = idMath::ClampFloat( 0.0f, contributionPctMax, 1.0f - ( delta / aa_targetAdhesionRadius.GetFloat() ) ); + angleCorrection.yaw = aimAngles.yaw * contribution; + angleCorrection.pitch = aimAngles.pitch * contribution; +} + +/* +======================== +idAimAssist::ComputeFrictionRadius +======================== +*/ +float idAimAssist::ComputeFrictionRadius( float distanceToTarget ) { + + if ( ( distanceToTarget <= idMath::FLT_SMALLEST_NON_DENORMAL ) || distanceToTarget > aa_targetFrictionMaxDistance.GetFloat() ) { + return aa_targetFrictionRadius.GetFloat(); + } + + float distanceContributionScalar = ( aa_targetFrictionOptimalDistance.GetFloat() > 0.0f ) ? ( distanceToTarget / aa_targetFrictionOptimalDistance.GetFloat() ) : 0.0f; + + if ( distanceToTarget > aa_targetFrictionOptimalDistance.GetFloat() ) { + const float range = idMath::ClampFloat( 0.0f, aa_targetFrictionMaxDistance.GetFloat(), aa_targetFrictionMaxDistance.GetFloat() - aa_targetFrictionOptimalDistance.GetFloat() ); + if ( range > idMath::FLT_SMALLEST_NON_DENORMAL ) { + distanceContributionScalar = 1.0f - ( ( distanceToTarget - aa_targetFrictionOptimalDistance.GetFloat() ) / range ); + } + } + + return Lerp( aa_targetFrictionRadius.GetFloat(), aa_targetFrictionOptimalRadius.GetFloat(), distanceContributionScalar ); +} + +/* +======================== +idAimAssist::UpdateFriction +======================== +*/ +void idAimAssist::UpdateFriction( idEntity* pTarget, const idVec3& targetPos ) { + + if ( !aa_targetFrictionEnable.GetBool() ) { + return; + } + + if ( pTarget == NULL ) { + return; + } + + idVec3 cameraPos; + idMat3 cameraAxis; + player->GetViewPos(cameraPos, cameraAxis); + idVec3 dirToTarget = targetPos - cameraPos; + float distanceToTarget = dirToTarget.Length(); + idVec3 forward = cameraAxis[0]; + forward.Normalize(); + dirToTarget.Normalize(); + float ViewDirDotTargetDir = idMath::ClampFloat( 0.0f, 1.0f, forward * dirToTarget ); // compute the dot and clamp to account for floating point error + float aimLength = ViewDirDotTargetDir * distanceToTarget; + idVec3 aimPoint = cameraPos + ( forward * aimLength ); + float delta = idMath::Sqrt( Square( distanceToTarget ) - Square( aimLength ) ); + + const float radius = ComputeFrictionRadius( distanceToTarget ); + if ( delta < radius ) { + float alpha = 1.0f - ( delta / radius ); + frictionScalar = Lerp( aa_targetFrictionMultiplierMin.GetFloat(), aa_targetFrictionMultiplierMax.GetFloat(), alpha ); + } +} + +/* +======================== +idAimAssist::ComputeTargetPos +======================== +*/ +bool idAimAssist::ComputeTargetPos( idEntity* entity, idVec3& primaryTargetPos, idVec3& secondaryTargetPos ) { + + primaryTargetPos = vec3_zero; + secondaryTargetPos = vec3_zero; + + if ( entity == NULL ) { + return false; + } + + // The target point on actors can now be either the head or the torso + idActor * actor = NULL; + if ( entity->IsType( idActor::Type ) ) { + actor = ( idActor *) entity; + } + if ( actor != NULL ) { + // Actor AimPoint + + idVec3 torsoPos; + idVec3 headPos = actor->GetEyePosition(); + + if ( actor->GetHeadEntity() != NULL ) { + torsoPos = actor->GetHeadEntity()->GetPhysics()->GetOrigin(); + } else { + const float offsetScale = 0.9f; + torsoPos = actor->GetPhysics()->GetOrigin() + ( actor->EyeOffset() * offsetScale ); + } + + primaryTargetPos = torsoPos; + secondaryTargetPos = headPos; + return true; + + } else if ( entity->GetPhysics()->GetClipModel() != NULL ) { + + const idBounds& box = entity->GetPhysics()->GetClipModel()->GetAbsBounds(); + primaryTargetPos = box.GetCenter(); + secondaryTargetPos = box.GetCenter(); + return true; + } + + return false; +} diff --git a/neo/d3xp/AimAssist.h b/neo/d3xp/AimAssist.h new file mode 100644 index 00000000..f4669f41 --- /dev/null +++ b/neo/d3xp/AimAssist.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AIMASSIST_H__ +#define __AIMASSIST_H__ + +/* +================================================================================================ +Contains the AimAssist declaration. +================================================================================================ +*/ + +class idEntity; +class idPlayer; + +/* +================================================ +idAimAssist modifies the angle of Weapon firing to help the Player +hit a Target. +================================================ +*/ +class idAimAssist { +public: + + idAimAssist() : angleCorrection( ang_zero ), frictionScalar( 1.0f ), lastTargetPos( vec3_zero ), player( NULL ) {} + + void Init( idPlayer * player ); + void Update(); + void GetAngleCorrection( idAngles & correction ) const { correction = angleCorrection; } + float GetFrictionScalar () const { return frictionScalar; } + + idEntity * GetLastTarget() { return targetEntity; } + idEntity * FindAimAssistTarget( idVec3 & targetPos ); + +private: + void UpdateNewAimAssist(); + float ComputeEntityAimAssistScore( const idVec3 & targetPos, const idVec3 & cameraPos, const idMat3 & cameraAxis ); + bool ComputeTargetPos( idEntity * pTarget, idVec3 & primaryTargetPos, idVec3 & secondaryTargetPos ); + float ComputeFrictionRadius( float distanceToTarget ); + void UpdateAdhesion( idEntity * pTarget, const idVec3 & targetPos); + void UpdateFriction( idEntity * pTarget, const idVec3 & targetPos); + + idPlayer * player; // player associated with this object + idAngles angleCorrection; // the angle delta to apply for aim assistance + float frictionScalar; // friction scalar + idEntityPtr targetEntity; // the last target we had (updated every frame) + idVec3 lastTargetPos; // the last target position ( updated every frame ); +}; + +#endif // !__AIMASSIST_H__ diff --git a/neo/d3xp/BrittleFracture.cpp b/neo/d3xp/BrittleFracture.cpp new file mode 100644 index 00000000..2be1e4d9 --- /dev/null +++ b/neo/d3xp/BrittleFracture.cpp @@ -0,0 +1,1386 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +CLASS_DECLARATION( idEntity, idBrittleFracture ) + EVENT( EV_Activate, idBrittleFracture::Event_Activate ) + EVENT( EV_Touch, idBrittleFracture::Event_Touch ) +END_CLASS + +const int SHARD_ALIVE_TIME = 5000; +const int SHARD_FADE_START = 2000; + +static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_"; + +/* +================ +idBrittleFracture::idBrittleFracture +================ +*/ +idBrittleFracture::idBrittleFracture() { + material = NULL; + decalMaterial = NULL; + decalSize = 0.0f; + maxShardArea = 0.0f; + maxShatterRadius = 0.0f; + minShatterRadius = 0.0f; + linearVelocityScale = 0.0f; + angularVelocityScale = 0.0f; + shardMass = 0.0f; + density = 0.0f; + friction = 0.0f; + bouncyness = 0.0f; + fxFracture.Clear(); + + bounds.Clear(); + disableFracture = false; + + lastRenderEntityUpdate = -1; + changed = false; + + fl.networkSync = true; + + isXraySurface = false; +} + +/* +================ +idBrittleFracture::~idBrittleFracture +================ +*/ +idBrittleFracture::~idBrittleFracture() { + int i; + + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->decals.DeleteContents( true ); + delete shards[i]; + } + + // make sure the render entity is freed before the model is freed + FreeModelDef(); + renderModelManager->FreeModel( renderEntity.hModel ); + + // Free our events list memory + storedEvents.Clear(); +} + +/* +================ +idBrittleFracture::Save +================ +*/ +void idBrittleFracture::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( health ); + entityFlags_s flags = fl; + LittleBitField( &flags, sizeof( flags ) ); + savefile->Write( &flags, sizeof( flags ) ); + + // setttings + savefile->WriteMaterial( material ); + savefile->WriteMaterial( decalMaterial ); + savefile->WriteFloat( decalSize ); + savefile->WriteFloat( maxShardArea ); + savefile->WriteFloat( maxShatterRadius ); + savefile->WriteFloat( minShatterRadius ); + savefile->WriteFloat( linearVelocityScale ); + savefile->WriteFloat( angularVelocityScale ); + savefile->WriteFloat( shardMass ); + savefile->WriteFloat( density ); + savefile->WriteFloat( friction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteString( fxFracture ); + + // state + savefile->WriteBounds( bounds ); + savefile->WriteBool( disableFracture ); + + savefile->WriteInt( lastRenderEntityUpdate ); + savefile->WriteBool( changed ); + + savefile->WriteModel( defaultRenderModel ); + + // So we can re-break the object on load if needed + savefile->WriteInt( storedEvents.Num() ); + for ( int i = 0; i < storedEvents.Num(); ++i ) { + savefile->WriteInt( storedEvents[i].eventType ); + savefile->WriteVec3( storedEvents[i].point ); + savefile->WriteVec3( storedEvents[i].vector ); + } + savefile->WriteBool( isXraySurface ); +} + +/* +================ +idBrittleFracture::Restore +================ +*/ +void idBrittleFracture::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( health ); + savefile->Read( &fl, sizeof( fl ) ); + LittleBitField( &fl, sizeof( fl ) ); + + // setttings + savefile->ReadMaterial( material ); + savefile->ReadMaterial( decalMaterial ); + savefile->ReadFloat( decalSize ); + savefile->ReadFloat( maxShardArea ); + savefile->ReadFloat( maxShatterRadius ); + savefile->ReadFloat( minShatterRadius ); + savefile->ReadFloat( linearVelocityScale ); + savefile->ReadFloat( angularVelocityScale ); + savefile->ReadFloat( shardMass ); + savefile->ReadFloat( density ); + savefile->ReadFloat( friction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadString( fxFracture ); + + // state + savefile->ReadBounds(bounds); + savefile->ReadBool( disableFracture ); + + savefile->ReadInt( lastRenderEntityUpdate ); + savefile->ReadBool( changed ); + + savefile->ReadModel( defaultRenderModel ); + + // Reset all brittle Fractures so we can re-break them if necessary + fl.takedamage = true; + CreateFractures( defaultRenderModel ); + FindNeighbours(); + + int numEvents = 0; + bool resolveBreaks = false; + savefile->ReadInt( numEvents ); + for( int i = 0; i < numEvents; i++ ) { + fractureEvent_s restoredEvent; + + savefile->ReadInt( restoredEvent.eventType ); + savefile->ReadVec3( restoredEvent.point ); + savefile->ReadVec3( restoredEvent.vector ); + + if ( restoredEvent.eventType == EVENT_PROJECT_DECAL ) { + ProjectDecal( restoredEvent.point, restoredEvent.vector, gameLocal.time, NULL ); + } else { + Shatter( restoredEvent.point, restoredEvent.vector, gameLocal.time ); + } + resolveBreaks = true; + } + + // remove any dropped shards + for ( int i = 0; resolveBreaks && i < shards.Num(); i++ ) { + if ( shards[i]->droppedTime!= -1 ) { + RemoveShard( i ); + i--; + } + } + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; + + savefile->ReadBool( isXraySurface ); +} + +/* +================ +idBrittleFracture::Spawn +================ +*/ +void idBrittleFracture::Spawn() { + + // get shard properties + decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) ); + decalSize = spawnArgs.GetFloat( "decalSize", "40" ); + maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" ) * 2.0f ; + maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea ); + maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" ); + minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" ); + linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" ); + angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" ); + fxFracture = spawnArgs.GetString( "fx" ); + + // make sure that max is greater than min ( otherwise negative number square root happens ) + if( maxShatterRadius < minShatterRadius ) { + idLib::Warning( "BrittleFracture, minShatterRadius(%2f) is greater than maxShatterRadius(%2f). Unknown results will ensue.", minShatterRadius, maxShatterRadius ); + } + + // get rigid body properties + shardMass = spawnArgs.GetFloat( "shardMass", "20" ); + shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass ); + spawnArgs.GetFloat( "density", "0.1", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.4", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + disableFracture = spawnArgs.GetBool( "disableFracture", "0" ); + health = spawnArgs.GetInt( "health", "40" ); + fl.takedamage = true; + + // FIXME: set "bleed" so idProjectile calls AddDamageEffect + spawnArgs.SetBool( "bleed", 1 ); + + // check for xray surface + if ( renderEntity.hModel != NULL ) { + const idRenderModel *model = renderEntity.hModel; + + isXraySurface = false; + + for ( int i = 0; i < model->NumSurfaces(); i++ ) { + const modelSurface_t *surf = model->Surface( i ); + + if ( idStr( surf->shader->GetName() ) == "textures/smf/window_scratch" ) { + isXraySurface = true; + break; + } + } + } + + CreateFractures( renderEntity.hModel ); + + FindNeighbours(); + + defaultRenderModel = renderEntity.hModel; + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; +} + +/* +================ +idBrittleFracture::AddShard +================ +*/ +void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) { + shard_t *shard = new (TAG_PARTICLE) shard_t; + shard->clipModel = clipModel; + shard->droppedTime = -1; + shard->winding = w; + shard->decals.Clear(); + shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false ); + shard->neighbours.Clear(); + shard->atEdge = false; + shards.Append( shard ); +} + +/* +================ +idBrittleFracture::RemoveShard +================ +*/ +void idBrittleFracture::RemoveShard( int index ) { + int i; + + delete shards[index]; + shards.RemoveIndex( index ); + physicsObj.RemoveIndex( index ); + + for ( i = index; i < shards.Num(); i++ ) { + shards[i]->clipModel->SetId( i ); + } +} + +/* +================ +idBrittleFracture::UpdateRenderEntity +================ +*/ +bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + int i, j, k, n, msec, numTris, numDecalTris; + float fade; + dword packedColor; + srfTriangles_t *tris, *decalTris; + modelSurface_t surface; + idDrawVert *v; + idPlane plane; + idMat3 tangents; + + // this may be triggered by a model trace or other non-view related source, + // to which we should look like an empty model + if ( !renderView ) { + return false; + } + + // don't regenerate it if it is current + if ( lastRenderEntityUpdate == gameLocal.time || !changed ) { + return false; + } + + lastRenderEntityUpdate = gameLocal.time; + changed = false; + + numTris = 0; + numDecalTris = 0; + for ( i = 0; i < shards.Num(); i++ ) { + n = shards[i]->winding.GetNumPoints(); + if ( n > 2 ) { + numTris += n - 2; + } + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + n = shards[i]->decals[k]->GetNumPoints(); + if ( n > 2 ) { + numDecalTris += n - 2; + } + } + } + + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName ); + + // allocate triangle surfaces for the fractures and decals + tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 ); + decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 ); + + for ( i = 0; i < shards.Num(); i++ ) { + const idVec3 &origin = shards[i]->clipModel->GetOrigin(); + const idMat3 &axis = shards[i]->clipModel->GetAxis(); + + fade = 1.0f; + if ( shards[i]->droppedTime >= 0 ) { + msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START; + if ( msec > 0 ) { + fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START ); + } + } + + packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade, + renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade, + renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade, + fade ) ); + + const idWinding &winding = shards[i]->winding; + + winding.GetPlane( plane ); + tangents = ( plane.Normal() * axis ).ToMat3(); + + for ( j = 2; j < winding.GetNumPoints(); j++ ) { + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[0].ToVec3() * axis; + v->SetTexCoord( winding[0].s, winding[0].t ); + v->SetNormal( tangents[0] ); + v->SetTangent( tangents[1] ); + v->SetBiTangent( tangents[2] ); + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j-1].ToVec3() * axis; + v->SetTexCoord( winding[j-1].s, winding[j-1].t ); + v->SetNormal( tangents[0] ); + v->SetTangent( tangents[1] ); + v->SetBiTangent( tangents[2] ); + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j].ToVec3() * axis; + v->SetTexCoord( winding[j].s, winding[j].t ); + v->SetNormal( tangents[0] ); + v->SetTangent( tangents[1] ); + v->SetBiTangent( tangents[2] ); + v->SetColor( packedColor ); + + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + + if ( material->ShouldCreateBackSides() ) { + + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + } + } + + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + const idWinding &decalWinding = *shards[i]->decals[k]; + + for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) { + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[0].ToVec3() * axis; + v->SetTexCoord( decalWinding[0].s, decalWinding[0].t ); + v->SetNormal( tangents[0] ); + v->SetTangent( tangents[1] ); + v->SetBiTangent( tangents[2] ); + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j-1].ToVec3() * axis; + v->SetTexCoord( decalWinding[j-1].s, decalWinding[j-1].t ); + v->SetNormal( tangents[0] ); + v->SetTangent( tangents[1] ); + v->SetBiTangent( tangents[2] ); + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j].ToVec3() * axis; + v->SetTexCoord( decalWinding[j].s, decalWinding[j].t ); + v->SetNormal( tangents[0] ); + v->SetTangent( tangents[1] ); + v->SetBiTangent( tangents[2] ); + v->SetColor( packedColor ); + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + + if ( decalMaterial->ShouldCreateBackSides() ) { + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + } + } + } + } + + tris->tangentsCalculated = true; + decalTris->tangentsCalculated = true; + + SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts ); + SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts ); + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = material; + surface.id = 0; + surface.geometry = tris; + renderEntity->hModel->AddSurface( surface ); + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = decalMaterial; + surface.id = 1; + surface.geometry = decalTris; + renderEntity->hModel->AddSurface( surface ); + + return true; +} + +/* +================ +idBrittleFracture::ModelCallback +================ +*/ +bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + const idBrittleFracture *ent; + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( ent == NULL ) { + gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" ); + return false; + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idBrittleFracture::Present +================ +*/ +void idBrittleFracture::Present() { + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + renderEntity.bounds = bounds; + renderEntity.origin.Zero(); + renderEntity.axis.Identity(); + + // force an update because the bounds/origin/axis may stay the same while the model changes + renderEntity.forceUpdate = true; + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + + changed = true; +} + +/* +================ +idBrittleFracture::Think +================ +*/ +void idBrittleFracture::Think() { + int i, startTime, endTime, droppedTime; + shard_t *shard; + bool atRest = true, fading = false; + + // remove overdue shards + for ( i = 0; i < shards.Num(); i++ ) { + droppedTime = shards[i]->droppedTime; + if ( droppedTime != -1 ) { + if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) { + RemoveShard( i ); + i--; + } + fading = true; + } + } + + // remove the entity when nothing is visible + if ( !shards.Num() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + if ( thinkFlags & TH_PHYSICS ) { + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + // run physics on shards + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime == -1 ) { + continue; + } + + shard->physicsObj.Evaluate( endTime - startTime, endTime ); + + if ( !shard->physicsObj.IsAtRest() ) { + atRest = false; + } + } + + if ( atRest ) { + BecomeInactive( TH_PHYSICS ); + } else { + BecomeActive( TH_PHYSICS ); + } + } + + if ( !atRest || bounds.IsCleared() ) { + bounds.Clear(); + for ( i = 0; i < shards.Num(); i++ ) { + bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() ); + } + } + + if ( fading ) { + BecomeActive( TH_UPDATEVISUALS | TH_THINK ); + } else { + BecomeInactive( TH_THINK ); + } + + RunPhysics(); + Present(); +} + +/* +================ +idBrittleFracture::ApplyImpulse +================ +*/ +void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.ApplyImpulse( 0, point, impulse ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, impulse, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::AddForce +================ +*/ +void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.AddForce( 0, point, force ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, force, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::ProjectDecal +================ +*/ +void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) { + int i, j, bits, clipBits; + float a, c, s; + idVec2 st[MAX_POINTS_ON_WINDING]; + idVec3 origin; + idMat3 axis, axistemp; + idPlane textureAxis[2]; + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( dir[0] ); + msg.WriteFloat( dir[1] ); + msg.WriteFloat( dir[2] ); + ServerSendEvent( EVENT_PROJECT_DECAL, &msg, true ); + } + + // store the event so we can rebuilt the fracture after loading a save + fractureEvent_s fractureEvent; + fractureEvent.eventType = EVENT_PROJECT_DECAL; + fractureEvent.point = point; + fractureEvent.vector = dir; + storedEvents.Append( fractureEvent ); + + if ( time >= gameLocal.time ) { + // try to get the sound from the damage def + const idDeclEntityDef *damageDef = NULL; + const idSoundShader *sndShader = NULL; + if ( damageDefName ) { + damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + const char * sndName = damageDef->dict.GetString( "snd_shatter", "" ); + if ( sndName[0] != 0 ) { + sndShader = declManager->FindSound( sndName ); + } + } + } + + if ( sndShader ) { + StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); + } else { + StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = cos( a ); + s = -sin( a ); + + axis[2] = -dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + textureAxis[0] = axis[0] * ( 1.0f / decalSize ); + textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f; + + textureAxis[1] = axis[1] * ( 1.0f / decalSize ); + textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f; + + for ( i = 0; i < shards.Num(); i++ ) { + idFixedWinding &winding = shards[i]->winding; + origin = shards[i]->clipModel->GetOrigin(); + axis = shards[i]->clipModel->GetAxis(); + float d0, d1; + + clipBits = -1; + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + idVec3 p = origin + winding[j].ToVec3() * axis; + + st[j].x = d0 = textureAxis[0].Distance( p ); + st[j].y = d1 = textureAxis[1].Distance( p ); + + bits = IEEE_FLT_SIGNBITSET( d0 ); + d0 = 1.0f - d0; + bits |= IEEE_FLT_SIGNBITSET( d1 ) << 2; + d1 = 1.0f - d1; + bits |= IEEE_FLT_SIGNBITSET( d0 ) << 1; + bits |= IEEE_FLT_SIGNBITSET( d1 ) << 3; + + clipBits &= bits; + } + + if ( clipBits ) { + continue; + } + + idFixedWinding *decal = new (TAG_PARTICLE) idFixedWinding; + shards[i]->decals.Append( decal ); + + decal->SetNumPoints( winding.GetNumPoints() ); + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + (*decal)[j].ToVec3() = winding[j].ToVec3(); + (*decal)[j].s = st[j].x; + (*decal)[j].t = st[j].y; + } + } + + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idBrittleFracture::DropShard +================ +*/ +void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) { + int i, j, clipModelId; + float dist, f; + idVec3 dir2, origin; + idMat3 axis; + shard_t *neighbour; + + // don't display decals on dropped shards + shard->decals.DeleteContents( true ); + + // remove neighbour pointers of neighbours pointing to this shard + for ( i = 0; i < shard->neighbours.Num(); i++ ) { + neighbour = shard->neighbours[i]; + for ( j = 0; j < neighbour->neighbours.Num(); j++ ) { + if ( neighbour->neighbours[j] == shard ) { + neighbour->neighbours.RemoveIndex( j ); + break; + } + } + } + + // remove neighbour pointers + shard->neighbours.Clear(); + + // remove the clip model from the static physics object + clipModelId = shard->clipModel->GetId(); + physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false ); + + origin = shard->clipModel->GetOrigin(); + axis = shard->clipModel->GetAxis(); + + // set the dropped time for fading + shard->droppedTime = time; + + dir2 = origin - point; + dist = dir2.Normalize(); + f = dist > maxShatterRadius ? 1.0f : idMath::Sqrt( idMath::Fabs( dist - minShatterRadius ) ) * ( 1.0f / idMath::Sqrt( idMath::Fabs( maxShatterRadius - minShatterRadius ) ) ); + + // setup the physics + shard->physicsObj.SetSelf( this ); + shard->physicsObj.SetClipModel( shard->clipModel, density ); + shard->physicsObj.SetMass( shardMass ); + shard->physicsObj.SetOrigin( origin ); + shard->physicsObj.SetAxis( axis ); + shard->physicsObj.SetBouncyness( bouncyness ); + shard->physicsObj.SetFriction( 0.6f, 0.6f, friction ); + shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); + shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir ); + shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) ); + + shard->clipModel->SetId( clipModelId ); + + BecomeActive( TH_PHYSICS ); +} + +/* +================ +idBrittleFracture::Shatter +================ +*/ +void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i; + idVec3 dir; + shard_t *shard; + float m; + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( impulse[0] ); + msg.WriteFloat( impulse[1] ); + msg.WriteFloat( impulse[2] ); + ServerSendEvent( EVENT_SHATTER, &msg, true ); + } + + // Store off the event so we can rebuilt the object if we reload a savegame + fractureEvent_s fractureEvent; + fractureEvent.eventType = EVENT_SHATTER; + fractureEvent.point = point; + fractureEvent.vector = impulse; + storedEvents.Append( fractureEvent ); + + if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) { + StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL ); + } + + if ( !IsBroken() ) { + Break(); + } + + if ( fxFracture.Length() ) { + idEntityFx::StartFx( fxFracture, &point, &GetPhysics()->GetAxis(), this, true ); + } + + dir = impulse; + m = dir.Normalize(); + + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime != -1 ) { + continue; + } + + if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) { + continue; + } + + DropShard( shard, point, dir, m, time ); + } + + DropFloatingIslands( point, impulse, time ); +} + +/* +================ +idBrittleFracture::DropFloatingIslands +================ +*/ +void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i, j, numIslands; + int queueStart, queueEnd; + shard_t *curShard, *nextShard, **queue; + bool touchesEdge; + idVec3 dir; + + dir = impulse; + dir.Normalize(); + + numIslands = 0; + queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) ); + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->islandNum = 0; + } + + for ( i = 0; i < shards.Num(); i++ ) { + + if ( shards[i]->droppedTime != -1 ) { + continue; + } + + if ( shards[i]->islandNum ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = shards[i]; + shards[i]->islandNum = numIslands+1; + touchesEdge = false; + + if ( shards[i]->atEdge ) { + touchesEdge = true; + } + + for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) { + + for ( j = 0; j < curShard->neighbours.Num(); j++ ) { + + nextShard = curShard->neighbours[j]; + + if ( nextShard->droppedTime != -1 ) { + continue; + } + + if ( nextShard->islandNum ) { + continue; + } + + queue[queueEnd++] = nextShard; + nextShard->islandNum = numIslands+1; + + if ( nextShard->atEdge ) { + touchesEdge = true; + } + } + } + numIslands++; + + // if the island is not connected to the world at any edges + if ( !touchesEdge ) { + for ( j = 0; j < queueEnd; j++ ) { + DropShard( queue[j], point, dir, 0.0f, time ); + } + } + } +} + +/* +================ +idBrittleFracture::Break +================ +*/ +void idBrittleFracture::Break() { + fl.takedamage = false; + physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER ); +} + +/* +================ +idBrittleFracture::IsBroken +================ +*/ +bool idBrittleFracture::IsBroken() const { + return ( fl.takedamage == false ); +} + +/* +================ +idBrittleFracture::Killed +================ +*/ +void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !disableFracture ) { + ActivateTargets( this ); + Break(); + } +} + +/* +================ +idBrittleFracture::AddDamageEffect +================ +*/ +void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) { + if ( !disableFracture ) { + ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName ); + } +} + +static float fractureSplitTable[] = { 1365.123f, 5.324f, 1125.34f, 50.34f, 555.252f, 100.12f, 230.53f, 10000.87f, 10000.87f }; + +/* +================ +idBrittleFracture::Fracture_r +================ +*/ +void idBrittleFracture::Fracture_r( idFixedWinding &w, idRandom2 & random ) { + int i, j, bestPlane; + float a, c, s, dist, bestDist; + idVec3 origin; + idPlane windingPlane, splitPlanes[2]; + idMat3 axis, axistemp; + idFixedWinding back; + idTraceModel trm; + idClipModel *clipModel; + + + while( 1 ) { + origin = w.GetCenter(); + w.GetPlane( windingPlane ); + + if ( w.GetArea() < maxShardArea ) { + break; + } + + // randomly create a split plane + axis[2] = windingPlane.Normal(); + if ( isXraySurface ) { + a = idMath::TWO_PI / 2.f; + } + else { + a = random.RandomFloat() * idMath::TWO_PI; + } + c = cos( a ); + s = -sin( a ); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + // get the best split plane + bestDist = 0.0f; + bestPlane = 0; + for ( i = 0; i < 2; i++ ) { + splitPlanes[i].SetNormal( axis[i] ); + splitPlanes[i].FitThroughPoint( origin ); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + dist = splitPlanes[i].Distance( w[j].ToVec3() ); + if ( dist > bestDist ) { + bestDist = dist; + bestPlane = i; + } + } + } + + // split the winding + if ( !w.Split( &back, splitPlanes[bestPlane] ) ) { + break; + } + + // recursively create shards for the back winding + Fracture_r( back, random ); + } + + // translate the winding to it's center + origin = w.GetCenter(); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + w[j].ToVec3() -= origin; + } + w.RemoveEqualPoints(); + + trm.SetupPolygon( w ); + trm.Shrink( CM_CLIP_EPSILON ); + clipModel = new (TAG_PHYSICS) idClipModel( trm, false ); + + physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() ); + + AddShard( clipModel, w ); +} + +/* +================ +CompareVec5 +================ +*/ +bool CompareVec5( const idVec5 & v0, const idVec5 & v1 ) { + float dx = v0.x - v1.x; + float dy = v0.y - v1.y; + float dz = v0.z - v1.z; + float ds = v0.s - v1.s; + float dt = v0.t - v1.t; + float d = ( dx * dx ) + ( dy * dy ) + ( dz * dz ) + ( ds * ds ) + ( dt + dt ); + return ( d == 0.0f ); +} + +/* +================ +idBrittleFracture::CreateFractures +================ +*/ +void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) { + if ( !renderModel || renderModel->NumSurfaces() < 1 ) { + return; + } + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 ); + + const modelSurface_t * surf = renderModel->Surface( 0 ); + material = surf->shader; + + idMat3 physAxis; + physAxis = physicsObj.GetAxis(); + if ( isXraySurface ) { + idFixedWinding w; + + for ( int i = 0; i < 4; i++ ) { + const idDrawVert * v = &surf->geometry->verts[i]; + w.AddPoint( idVec5( v->xyz, v->GetTexCoord() ) ); + } + + idRandom2 random( entityNumber ); + Fracture_r( w , random ); + + } else + { + const idDrawVert * verts = surf->geometry->verts; + triIndex_t * indexes = surf->geometry->indexes; + + for ( int j = 0; j < surf->geometry->numIndexes; j += 3 ) { + int i0 = indexes[ j + 0 ]; + int i1 = indexes[ j + 1 ]; + int i2 = indexes[ j + 2 ]; + idFixedWinding w; + w.AddPoint( idVec5( verts[i2].xyz, verts[i2].GetTexCoord() ) ); + w.AddPoint( idVec5( verts[i1].xyz, verts[i1].GetTexCoord() ) ); + w.AddPoint( idVec5( verts[i0].xyz, verts[i0].GetTexCoord() ) ); + idPlane p1; + w.GetPlane( p1 ); + for ( int k = j + 3; k < surf->geometry->numIndexes && ( w.GetNumPoints() + 1 < MAX_POINTS_ON_WINDING ); k += 3 ) { + int i3 = indexes[ k + 0 ]; + int i4 = indexes[ k + 1 ]; + int i5 = indexes[ k + 2 ]; + idFixedWinding w2; + w2.AddPoint( idVec5( verts[i5].xyz, verts[i5].GetTexCoord() ) ); + w2.AddPoint( idVec5( verts[i4].xyz, verts[i4].GetTexCoord() ) ); + w2.AddPoint( idVec5( verts[i3].xyz, verts[i3].GetTexCoord() ) ); + idPlane p2; + w2.GetPlane( p2 ); + if ( p1 != p2 ) { + break; + } + bool found = false; + for ( int w1i = 0; w1i < w.GetNumPoints(); w1i++ ) { + for ( int w2i = 0; w2i < w2.GetNumPoints(); w2i++ ) { + if ( CompareVec5( w[w1i], w2[w2i] ) && CompareVec5( w[(w1i+1)%w.GetNumPoints()], w2[(w2i+2)%w2.GetNumPoints()] ) ) { + w.InsertPoint( w2[(w2i+1)%w2.GetNumPoints()], (w1i+1)%w.GetNumPoints() ); + j = k; + found = true; + break; + } + } + if ( found ) { + break; + } + } + if ( !found ) { + break; + } + } + + idRandom2 random( entityNumber ); + Fracture_r( w, random ); + } + } + + + physicsObj.SetContents( material->GetContentFlags() ); + SetPhysics( &physicsObj ); +} + +/* +================ +idBrittleFracture::FindNeighbours +================ +*/ +void idBrittleFracture::FindNeighbours() { + int i, j, k, l; + idVec3 p1, p2, dir; + idMat3 axis; + idPlane plane[4]; + + for ( i = 0; i < shards.Num(); i++ ) { + + shard_t *shard1 = shards[i]; + const idWinding &w1 = shard1->winding; + const idVec3 &origin1 = shard1->clipModel->GetOrigin(); + const idMat3 &axis1 = shard1->clipModel->GetAxis(); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + + p1 = origin1 + w1[k].ToVec3() * axis1; + p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1; + dir = p2 - p1; + dir.Normalize(); + axis = dir.ToMat3(); + + plane[0].SetNormal( dir ); + plane[0].FitThroughPoint( p1 ); + plane[1].SetNormal( -dir ); + plane[1].FitThroughPoint( p2 ); + plane[2].SetNormal( axis[1] ); + plane[2].FitThroughPoint( p1 ); + plane[3].SetNormal( axis[2] ); + plane[3].FitThroughPoint( p1 ); + + for ( j = 0; j < shards.Num(); j++ ) { + + if ( i == j ) { + continue; + } + + shard_t *shard2 = shards[j]; + + for ( l = 0; l < shard1->neighbours.Num(); l++ ) { + if ( shard1->neighbours[l] == shard2 ) { + break; + } + } + if ( l < shard1->neighbours.Num() ) { + continue; + } + + const idWinding &w2 = shard2->winding; + const idVec3 &origin2 = shard2->clipModel->GetOrigin(); + const idMat3 &axis2 = shard2->clipModel->GetAxis(); + + for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) { + p1 = origin2 + w2[l].ToVec3() * axis2; + p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2; + if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) { + if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) { + if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) { + shard1->neighbours.Append( shard2 ); + shard1->edgeHasNeighbour[k] = true; + shard2->neighbours.Append( shard1 ); + shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true; + break; + } + } + } + } + } + } + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + if ( !shard1->edgeHasNeighbour[k] ) { + break; + } + } + if ( k < w1.GetNumPoints() ) { + shard1->atEdge = true; + } else { + shard1->atEdge = false; + } + } +} + +/* +================ +idBrittleFracture::Event_Activate +================ +*/ +void idBrittleFracture::Event_Activate( idEntity *activator ) { + disableFracture = false; + if ( health <= 0 ) { + Break(); + } +} + +/* +================ +idBrittleFracture::Event_Touch +================ +*/ +void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 point, impulse; + + // Let the server handle this, clients dont' predict it + if ( common->IsClient() ) { + return; + } + + if ( !IsBroken() ) { + return; + } + + if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) { + return; + } + + point = shards[trace->c.id]->clipModel->GetOrigin(); + impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass(); + + Shatter( point, impulse, gameLocal.time ); +} + +/* +================ +idBrittleFracture::ClientThink +================ +*/ +void idBrittleFracture::ClientThink( const int curTime, const float fraction, const bool predict ) { + + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + + Think(); +} + +/* +================ +idBrittleFracture::ClientPredictionThink +================ +*/ +void idBrittleFracture::ClientPredictionThink() { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + + Think(); +} + +/* +================ +idBrittleFracture::ClientReceiveEvent +================ +*/ +bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 point, dir; + + switch( event ) { + case EVENT_PROJECT_DECAL: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + ProjectDecal( point, dir, time, NULL ); + return true; + } + case EVENT_SHATTER: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + Shatter( point, dir, time ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} diff --git a/neo/d3xp/BrittleFracture.h b/neo/d3xp/BrittleFracture.h new file mode 100644 index 00000000..5f0c4fa4 --- /dev/null +++ b/neo/d3xp/BrittleFracture.h @@ -0,0 +1,141 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_BRITTLEFRACTURE_H__ +#define __GAME_BRITTLEFRACTURE_H__ + + +/* +=============================================================================== + +B-rep Brittle Fracture - Static entity using the boundary representation +of the render model which can fracture. + +=============================================================================== +*/ + +typedef struct shard_s { + idClipModel * clipModel; + idFixedWinding winding; + idList decals; + idList edgeHasNeighbour; + idList neighbours; + idPhysics_RigidBody physicsObj; + int droppedTime; + bool atEdge; + int islandNum; +} shard_t; + + +class idBrittleFracture : public idEntity { + +public: + CLASS_PROTOTYPE( idBrittleFracture ); + + idBrittleFracture(); + virtual ~idBrittleFracture(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + + virtual void Present(); + virtual void Think(); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ); + bool IsBroken() const; + + enum { + EVENT_PROJECT_DECAL = idEntity::EVENT_MAXEVENTS, + EVENT_SHATTER, + EVENT_MAXEVENTS + }; + + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void ClientPredictionThink(); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + // setttings + const idMaterial * material; + const idMaterial * decalMaterial; + float decalSize; + float maxShardArea; + float maxShatterRadius; + float minShatterRadius; + float linearVelocityScale; + float angularVelocityScale; + float shardMass; + float density; + float friction; + float bouncyness; + idStr fxFracture; + + struct fractureEvent_s { + int eventType; + idVec3 point; + idVec3 vector; + }; + idList storedEvents; + bool processStoredEvents; + idRenderModel * defaultRenderModel; + bool isXraySurface; + + // state + idPhysics_StaticMulti physicsObj; + idList shards; + idBounds bounds; + bool disableFracture; + + // for rendering + mutable int lastRenderEntityUpdate; + mutable bool changed; + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + void AddShard( idClipModel *clipModel, idFixedWinding &w ); + void RemoveShard( int index ); + void DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ); + void Shatter( const idVec3 &point, const idVec3 &impulse, const int time ); + void DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ); + void Break(); + void Fracture_r( idFixedWinding &w, idRandom2 & random ); + void CreateFractures( const idRenderModel *renderModel ); + void FindNeighbours(); + + void Event_Activate( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif /* !__GAME_BRITTLEFRACTURE_H__ */ diff --git a/neo/d3xp/Camera.cpp b/neo/d3xp/Camera.cpp new file mode 100644 index 00000000..3fa30fe7 --- /dev/null +++ b/neo/d3xp/Camera.cpp @@ -0,0 +1,636 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idCamera + + Base class for cameras + +=============================================================================== +*/ + +ABSTRACT_DECLARATION( idEntity, idCamera ) +END_CLASS + +/* +===================== +idCamera::Spawn +===================== +*/ +void idCamera::Spawn() { +} + +/* +===================== +idCamera::GetRenderView +===================== +*/ +renderView_t *idCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + GetViewParms( rv ); + return rv; +} + +/*********************************************************************** + + idCameraView + +***********************************************************************/ +const idEventDef EV_Camera_SetAttachments( "", NULL ); + +CLASS_DECLARATION( idCamera, idCameraView ) + EVENT( EV_Activate, idCameraView::Event_Activate ) + EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments ) +END_CLASS + + +/* +=============== +idCameraView::idCameraView +================ +*/ +idCameraView::idCameraView() { + fov = 90.0f; + attachedTo = NULL; + attachedView = NULL; +} + +/* +=============== +idCameraView::Save +================ +*/ +void idCameraView::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( fov ); + savefile->WriteObject( attachedTo ); + savefile->WriteObject( attachedView ); +} + +/* +=============== +idCameraView::Restore +================ +*/ +void idCameraView::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( fov ); + savefile->ReadObject( reinterpret_cast( attachedTo ) ); + savefile->ReadObject( reinterpret_cast( attachedView ) ); +} + +/* +=============== +idCameraView::Event_SetAttachments +================ +*/ +void idCameraView::Event_SetAttachments( ) { + SetAttachment( &attachedTo, "attachedTo" ); + SetAttachment( &attachedView, "attachedView" ); +} + +/* +=============== +idCameraView::Event_Activate +================ +*/ +void idCameraView::Event_Activate( idEntity *activator ) { + if (spawnArgs.GetBool("trigger")) { + if (gameLocal.GetCamera() != this) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + gameLocal.SetCamera(this); + } else { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + } + } +} + +/* +===================== +idCameraView::Stop +===================== +*/ +void idCameraView::Stop() { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + ActivateTargets( gameLocal.GetLocalPlayer() ); +} + + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::SetAttachment( idEntity **e, const char *p ) { + const char *cam = spawnArgs.GetString( p ); + if ( strlen ( cam ) ) { + *e = gameLocal.FindEntity( cam ); + } +} + + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::Spawn() { + // if no target specified use ourself + const char *cam = spawnArgs.GetString("cameraTarget"); + if ( strlen ( cam ) == 0) { + spawnArgs.Set("cameraTarget", spawnArgs.GetString("name")); + } + fov = spawnArgs.GetFloat("fov", "90"); + + PostEventMS( &EV_Camera_SetAttachments, 0 ); + + UpdateChangeableSpawnArgs(NULL); +} + +/* +===================== +idCameraView::GetViewParms +===================== +*/ +void idCameraView::GetViewParms( renderView_t *view ) { + assert( view ); + + if (view == NULL) { + return; + } + + idVec3 dir; + idEntity *ent; + + if ( attachedTo ) { + ent = attachedTo; + } else { + ent = this; + } + + view->vieworg = ent->GetPhysics()->GetOrigin(); + if ( attachedView ) { + dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg; + dir.Normalize(); + view->viewaxis = dir.ToMat3(); + } else { + view->viewaxis = ent->GetPhysics()->GetAxis(); + } + + gameLocal.CalcFov( fov, view->fov_x, view->fov_y ); +} + +/* +=============================================================================== + + idCameraAnim + +=============================================================================== +*/ + +const idEventDef EV_Camera_Start( "start", NULL ); +const idEventDef EV_Camera_Stop( "stop", NULL ); + +CLASS_DECLARATION( idCamera, idCameraAnim ) + EVENT( EV_Thread_SetCallback, idCameraAnim::Event_SetCallback ) + EVENT( EV_Camera_Stop, idCameraAnim::Event_Stop ) + EVENT( EV_Camera_Start, idCameraAnim::Event_Start ) + EVENT( EV_Activate, idCameraAnim::Event_Activate ) +END_CLASS + + +/* +===================== +idCameraAnim::idCameraAnim +===================== +*/ +idCameraAnim::idCameraAnim() { + threadNum = 0; + offset.Zero(); + frameRate = 0; + cycle = 1; + starttime = 0; + activator = NULL; + +} + +/* +===================== +idCameraAnim::~idCameraAnim +===================== +*/ +idCameraAnim::~idCameraAnim() { + if ( gameLocal.GetCamera() == this ) { + gameLocal.SetCamera( NULL ); + } +} + +/* +=============== +idCameraAnim::Save +================ +*/ +void idCameraAnim::Save( idSaveGame *savefile ) const { + savefile->WriteInt( threadNum ); + savefile->WriteVec3( offset ); + savefile->WriteInt( frameRate ); + savefile->WriteInt( starttime ); + savefile->WriteInt( cycle ); + activator.Save( savefile ); +} + +/* +=============== +idCameraAnim::Restore +================ +*/ +void idCameraAnim::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( threadNum ); + savefile->ReadVec3( offset ); + savefile->ReadInt( frameRate ); + savefile->ReadInt( starttime ); + savefile->ReadInt( cycle ); + activator.Restore( savefile ); + + LoadAnim(); +} + +/* +===================== +idCameraAnim::Spawn +===================== +*/ +void idCameraAnim::Spawn() { + if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) { + offset = GetPhysics()->GetOrigin() - offset; + } else { + offset.Zero(); + } + + // always think during cinematics + cinematic = true; + + LoadAnim(); +} + +/* +================ +idCameraAnim::Load +================ +*/ +void idCameraAnim::LoadAnim() { + int version; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + int numFrames; + int numCuts; + int i; + idStr filename; + const char *key; + + key = spawnArgs.GetString( "anim" ); + if ( !key ) { + gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); + } + + filename = spawnArgs.GetString( va( "anim %s", key ) ); + if ( !filename.Length() ) { + gameLocal.Error( "Missing 'anim %s' key on '%s'", key, name.c_str() ); + } + + filename.SetFileExtension( MD5_CAMERA_EXT ); + if ( !parser.LoadFile( filename ) ) { + gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); + } + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse framerate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate <= 0 ) { + parser.Error( "Invalid framerate: %d", frameRate ); + } + + // parse num cuts + parser.ExpectTokenString( "numCuts" ); + numCuts = parser.ParseInt(); + if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) { + parser.Error( "Invalid number of camera cuts: %d", numCuts ); + } + + // parse the camera cuts + parser.ExpectTokenString( "cuts" ); + parser.ExpectTokenString( "{" ); + cameraCuts.SetNum( numCuts ); + for( i = 0; i < numCuts; i++ ) { + cameraCuts[ i ] = parser.ParseInt(); + if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) { + parser.Error( "Invalid camera cut" ); + } + } + parser.ExpectTokenString( "}" ); + + // parse the camera frames + parser.ExpectTokenString( "camera" ); + parser.ExpectTokenString( "{" ); + camera.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); + camera[ i ].fov = parser.ParseFloat(); + } + parser.ExpectTokenString( "}" ); +} + +/* +=============== +idCameraAnim::Start +================ +*/ +void idCameraAnim::Start() { + cycle = spawnArgs.GetInt( "cycle" ); + if ( !cycle ) { + cycle = 1; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + starttime = gameLocal.time; + gameLocal.SetCamera( this ); + BecomeActive( TH_THINK ); + + // if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame + if ( gameLocal.GetLocalPlayer()->GetRenderView()->time[TIME_GROUP2] == gameLocal.fast.time ) { + gameLocal.GetLocalPlayer()->CalculateRenderView(); + } +} + +/* +===================== +idCameraAnim::Stop +===================== +*/ +void idCameraAnim::Stop() { + if ( gameLocal.GetCamera() == this ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + + BecomeInactive( TH_THINK ); + gameLocal.SetCamera( NULL ); + if ( threadNum ) { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; + } + ActivateTargets( activator.GetEntity() ); + } +} + +/* +===================== +idCameraAnim::Think +===================== +*/ +void idCameraAnim::Think() { +} + +/* +===================== +idCameraAnim::GetViewParms +===================== +*/ +void idCameraAnim::GetViewParms( renderView_t *view ) { + int realFrame; + int frame; + int frameTime; + float lerp; + float invlerp; + cameraFrame_t *camFrame; + int i; + int cut; + idQuat q1, q2, q3; + + assert( view ); + if ( !view ) { + return; + } + + if ( camera.Num() == 0 ) { + // we most likely are in the middle of a restore + // FIXME: it would be better to fix it so this doesn't get called during a restore + return; + } + + SetTimeState ts( timeGroup ); + + frameTime = ( gameLocal.time - starttime ) * frameRate; + frame = frameTime / 1000; + lerp = ( frameTime % 1000 ) * 0.001f; + + // skip any frames where camera cuts occur + realFrame = frame; + cut = 0; + for( i = 0; i < cameraCuts.Num(); i++ ) { + if ( frame < cameraCuts[ i ] ) { + break; + } + frame++; + cut++; + } + + if ( g_debugCinematic.GetBool() ) { + int prevFrameTime = ( gameLocal.previousTime - starttime ) * frameRate; + int prevFrame = prevFrameTime / 1000; + int prevCut; + + prevCut = 0; + for( i = 0; i < cameraCuts.Num(); i++ ) { + if ( prevFrame < cameraCuts[ i ] ) { + break; + } + prevFrame++; + prevCut++; + } + + if ( prevCut != cut ) { + gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut ); + } + } + + // clamp to the first frame. also check if this is a one frame anim. one frame anims would end immediately, + // but since they're mainly used for static cams anyway, just stay on it infinitely. + if ( ( frame < 0 ) || ( camera.Num() < 2 ) ) { + view->viewaxis = camera[ 0 ].q.ToQuat().ToMat3(); + view->vieworg = camera[ 0 ].t + offset; + view->fov_x = camera[ 0 ].fov; + } else if ( frame > camera.Num() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( camera.Num() - cameraCuts.Num() ) * 1000 ) / frameRate; + GetViewParms( view ); + return; + } + + Stop(); + if ( gameLocal.GetCamera() != NULL ) { + // we activated another camera when we stopped, so get it's viewparms instead + gameLocal.GetCamera()->GetViewParms( view ); + return; + } else { + // just use our last frame + camFrame = &camera[ camera.Num() - 1 ]; + view->viewaxis = camFrame->q.ToQuat().ToMat3(); + view->vieworg = camFrame->t + offset; + view->fov_x = camFrame->fov; + } + } else if ( lerp == 0.0f ) { + camFrame = &camera[ frame ]; + view->viewaxis = camFrame[ 0 ].q.ToMat3(); + view->vieworg = camFrame[ 0 ].t + offset; + view->fov_x = camFrame[ 0 ].fov; + } else { + camFrame = &camera[ frame ]; + invlerp = 1.0f - lerp; + q1 = camFrame[ 0 ].q.ToQuat(); + q2 = camFrame[ 1 ].q.ToQuat(); + q3.Slerp( q1, q2, lerp ); + view->viewaxis = q3.ToMat3(); + view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset; + view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp; + } + + gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y ); + + // setup the pvs for this frame + UpdatePVSAreas( view->vieworg ); + +#if 0 + static int lastFrame = 0; + static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f ); + if ( gameLocal.time != lastFrame ) { + gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, 1 ); + gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false ); + gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false ); + gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + lastFrameVec = view->vieworg; + lastFrame = gameLocal.time; + } +#endif + + if ( g_showcamerainfo.GetBool() ) { + gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, camera.Num() - cameraCuts.Num() ); + } +} + +/* +=============== +idCameraAnim::Event_Activate +================ +*/ +void idCameraAnim::Event_Activate( idEntity *_activator ) { + activator = _activator; + if ( thinkFlags & TH_THINK ) { + Stop(); + } else { + Start(); + } +} + +/* +=============== +idCameraAnim::Event_Start +================ +*/ +void idCameraAnim::Event_Start() { + Start(); +} + +/* +=============== +idCameraAnim::Event_Stop +================ +*/ +void idCameraAnim::Event_Stop() { + Stop(); +} + +/* +================ +idCameraAnim::Event_SetCallback +================ +*/ +void idCameraAnim::Event_SetCallback() { + if ( ( gameLocal.GetCamera() == this ) && !threadNum ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} diff --git a/neo/d3xp/Camera.h b/neo/d3xp/Camera.h new file mode 100644 index 00000000..3f6c4916 --- /dev/null +++ b/neo/d3xp/Camera.h @@ -0,0 +1,132 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_CAMERA_H__ +#define __GAME_CAMERA_H__ + + +/* +=============================================================================== + +Camera providing an alternative view of the level. + +=============================================================================== +*/ + +class idCamera : public idEntity { +public: + ABSTRACT_PROTOTYPE( idCamera ); + + void Spawn(); + virtual void GetViewParms( renderView_t *view ) = 0; + virtual renderView_t * GetRenderView(); + virtual void Stop(){} ; +}; + +/* +=============================================================================== + +idCameraView + +=============================================================================== +*/ + +class idCameraView : public idCamera { +public: + CLASS_PROTOTYPE( idCameraView ); + idCameraView(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( ); + virtual void GetViewParms( renderView_t *view ); + virtual void Stop(); + +protected: + void Event_Activate( idEntity *activator ); + void Event_SetAttachments(); + void SetAttachment( idEntity **e, const char *p ); + float fov; + idEntity *attachedTo; + idEntity *attachedView; +}; + + + +/* +=============================================================================== + +A camera which follows a path defined by an animation. + +=============================================================================== +*/ + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +class idCameraAnim : public idCamera { +public: + CLASS_PROTOTYPE( idCameraAnim ); + + idCameraAnim(); + ~idCameraAnim(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn(); + virtual void GetViewParms( renderView_t *view ); + +private: + int threadNum; + idVec3 offset; + int frameRate; + int starttime; + int cycle; + idList cameraCuts; + idList camera; + idEntityPtr activator; + + void Start(); + void Stop(); + void Think(); + + void LoadAnim(); + void Event_Start(); + void Event_Stop(); + void Event_SetCallback(); + void Event_Activate( idEntity *activator ); +}; + +#endif /* !__GAME_CAMERA_H__ */ diff --git a/neo/d3xp/EndLevel.cpp b/neo/d3xp/EndLevel.cpp new file mode 100644 index 00000000..b2d8bdff --- /dev/null +++ b/neo/d3xp/EndLevel.cpp @@ -0,0 +1,185 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* + + game_endlevel.cpp + + This entity is targeted to complete a level, and it also handles + running the stats and moving the camera. + +*/ + + +CLASS_DECLARATION( idEntity, idTarget_EndLevel ) + EVENT( EV_Activate, idTarget_EndLevel::Event_Trigger ) +END_CLASS + +/* +================ +idTarget_EndLevel::Spawn +================ +*/ +void idTarget_EndLevel::Spawn( void ) { + idStr guiName; + + gui = NULL; + noGui = spawnArgs.GetBool("noGui"); + if (!noGui) { + spawnArgs.GetString( "guiName", "guis/EndLevel.gui", guiName ); + + if (guiName.Length()) { + gui = idUserInterface::FindGui( guiName, true, false, true ); + } + } + + buttonsReleased = false; + readyToExit = false; + + exitCommand = ""; +} + +/* +================ +idTarget_EndLevel::~idTarget_EndLevel() +================ +*/ +idTarget_EndLevel::~idTarget_EndLevel() { + //FIXME: need to go to smart ptrs for gui allocs or the unique method + //delete gui; +} + +/* +================ +idTarget_EndLevel::Event_Trigger +================ +*/ +void idTarget_EndLevel::Event_Trigger( idEntity *activator ) { + if ( gameLocal.endLevel ) { + return; + } + + // mark the endLevel, which will modify some game actions + // and pass control to us for drawing the stats and camera position + gameLocal.endLevel = this; + + // grab the activating player view position + idPlayer *player = (idPlayer *)(activator); + + initialViewOrg = player->GetEyePosition(); + initialViewAngles = idVec3( player->viewAngles[0], player->viewAngles[1], player->viewAngles[2] ); + + // kill all the sounds + gameSoundWorld->StopAllSounds(); + + if ( noGui ) { + readyToExit = true; + } +} + +/* +================ +idTarget_EndLevel::Draw +================ +*/ +void idTarget_EndLevel::Draw() { + + if (noGui) { + return; + } + + renderView_t renderView; + + memset( &renderView, 0, sizeof( renderView ) ); + + renderView.width = SCREEN_WIDTH; + renderView.height = SCREEN_HEIGHT; + renderView.x = 0; + renderView.y = 0; + + renderView.fov_x = 90; + renderView.fov_y = gameLocal.CalcFovY( renderView.fov_x ); + renderView.time = gameLocal.time; + +#if 0 + renderView.vieworg = initialViewOrg; + renderView.viewaxis = idAngles(initialViewAngles).toMat3(); +#else + renderView.vieworg = renderEntity.origin; + renderView.viewaxis = renderEntity.axis; +#endif + + gameRenderWorld->RenderScene( &renderView ); + + // draw the gui on top of the 3D view + gui->Redraw(gameLocal.time); +} + +/* +================ +idTarget_EndLevel::PlayerCommand +================ +*/ +void idTarget_EndLevel::PlayerCommand( int buttons ) { + if ( !( buttons & BUTTON_ATTACK ) ) { + buttonsReleased = true; + return; + } + if ( !buttonsReleased ) { + return; + } + + // we will exit at the end of the next game frame + readyToExit = true; +} + +/* +================ +idTarget_EndLevel::ExitCommand +================ +*/ +const char *idTarget_EndLevel::ExitCommand() { + if ( !readyToExit ) { + return NULL; + } + + idStr nextMap; + + if (spawnArgs.GetString( "nextMap", "", nextMap )) { + sprintf( exitCommand, "map %s", nextMap.c_str() ); + } else { + exitCommand = ""; + } + + return exitCommand; +} diff --git a/neo/d3xp/EndLevel.h b/neo/d3xp/EndLevel.h new file mode 100644 index 00000000..4ccc8604 --- /dev/null +++ b/neo/d3xp/EndLevel.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +class idTarget_EndLevel : public idEntity { +public: + CLASS_PROTOTYPE( idTarget_EndLevel ); + + void Spawn( void ); + ~idTarget_EndLevel(); + + void Draw(); + // the endLevel will be responsible for drawing the entire screen + // when it is active + + void PlayerCommand( int buttons ); + // when an endlevel is active, plauer buttons get sent here instead + // of doing anything to the player, which will allow moving to + // the next level + + const char *ExitCommand(); + // the game will check this each frame, and return it to the + // session when there is something to give + +private: + idStr exitCommand; + + idVec3 initialViewOrg; + idVec3 initialViewAngles; + // set when the player triggers the exit + + idUserInterface *gui; + + bool buttonsReleased; + // don't skip out until buttons are released, then pressed + + bool readyToExit; + bool noGui; + + void Event_Trigger( idEntity *activator ); +}; + diff --git a/neo/d3xp/Entity.cpp b/neo/d3xp/Entity.cpp new file mode 100644 index 00000000..cbeae97d --- /dev/null +++ b/neo/d3xp/Entity.cpp @@ -0,0 +1,5858 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idEntity + +=============================================================================== +*/ + +idCVar net_errorSmoothingMaxDecay( "net_errorSmoothingMaxDecay", "25.0", CVAR_FLOAT, "Max rate at which origin error smoothing decays (in units per game frame)" ); +idCVar net_errorSmoothingDecay( "net_errorSmoothingDecay", "0.06", CVAR_FLOAT, "Rate at which error smoothing decays (in percent per game frame)" ); + +// overridable events +const idEventDef EV_PostSpawn( "", NULL ); +const idEventDef EV_FindTargets( "", NULL ); +const idEventDef EV_Touch( "", "et" ); +const idEventDef EV_GetName( "getName", NULL, 's' ); +const idEventDef EV_SetName( "setName", "s" ); +const idEventDef EV_Activate( "activate", "e" ); +const idEventDef EV_ActivateTargets( "activateTargets", "e" ); +const idEventDef EV_NumTargets( "numTargets", NULL, 'f' ); +const idEventDef EV_GetTarget( "getTarget", "f", 'e' ); +const idEventDef EV_RandomTarget( "randomTarget", "s", 'e' ); +const idEventDef EV_Bind( "bind", "e" ); +const idEventDef EV_BindPosition( "bindPosition", "e" ); +const idEventDef EV_BindToJoint( "bindToJoint", "esf" ); +const idEventDef EV_Unbind( "unbind", NULL ); +const idEventDef EV_RemoveBinds( "removeBinds" ); +const idEventDef EV_SpawnBind( "", NULL ); +const idEventDef EV_SetOwner( "setOwner", "e" ); +const idEventDef EV_SetModel( "setModel", "s" ); +const idEventDef EV_SetSkin( "setSkin", "s" ); +const idEventDef EV_GetWorldOrigin( "getWorldOrigin", NULL, 'v' ); +const idEventDef EV_SetWorldOrigin( "setWorldOrigin", "v" ); +const idEventDef EV_GetOrigin( "getOrigin", NULL, 'v' ); +const idEventDef EV_SetOrigin( "setOrigin", "v" ); +const idEventDef EV_GetAngles( "getAngles", NULL, 'v' ); +const idEventDef EV_SetAngles( "setAngles", "v" ); +const idEventDef EV_GetLinearVelocity( "getLinearVelocity", NULL, 'v' ); +const idEventDef EV_SetLinearVelocity( "setLinearVelocity", "v" ); +const idEventDef EV_GetAngularVelocity( "getAngularVelocity", NULL, 'v' ); +const idEventDef EV_SetAngularVelocity( "setAngularVelocity", "v" ); +const idEventDef EV_GetSize( "getSize", NULL, 'v' ); +const idEventDef EV_SetSize( "setSize", "vv" ); +const idEventDef EV_GetMins( "getMins", NULL, 'v' ); +const idEventDef EV_GetMaxs( "getMaxs", NULL, 'v' ); +const idEventDef EV_IsHidden( "isHidden", NULL, 'd' ); +const idEventDef EV_Hide( "hide", NULL ); +const idEventDef EV_Show( "show", NULL ); +const idEventDef EV_Touches( "touches", "E", 'd' ); +const idEventDef EV_ClearSignal( "clearSignal", "d" ); +const idEventDef EV_GetShaderParm( "getShaderParm", "d", 'f' ); +const idEventDef EV_SetShaderParm( "setShaderParm", "df" ); +const idEventDef EV_SetShaderParms( "setShaderParms", "ffff" ); +const idEventDef EV_SetColor( "setColor", "fff" ); +const idEventDef EV_GetColor( "getColor", NULL, 'v' ); +const idEventDef EV_CacheSoundShader( "cacheSoundShader", "s" ); +const idEventDef EV_StartSoundShader( "startSoundShader", "sd", 'f' ); +const idEventDef EV_StartSound( "startSound", "sdd", 'f' ); +const idEventDef EV_StopSound( "stopSound", "dd" ); +const idEventDef EV_FadeSound( "fadeSound", "dff" ); +const idEventDef EV_SetGuiParm( "setGuiParm", "ss" ); +const idEventDef EV_SetGuiFloat( "setGuiFloat", "sf" ); +const idEventDef EV_GetNextKey( "getNextKey", "ss", 's' ); +const idEventDef EV_SetKey( "setKey", "ss" ); +const idEventDef EV_GetKey( "getKey", "s", 's' ); +const idEventDef EV_GetIntKey( "getIntKey", "s", 'f' ); +const idEventDef EV_GetFloatKey( "getFloatKey", "s", 'f' ); +const idEventDef EV_GetVectorKey( "getVectorKey", "s", 'v' ); +const idEventDef EV_GetEntityKey( "getEntityKey", "s", 'e' ); +const idEventDef EV_RestorePosition( "restorePosition" ); +const idEventDef EV_UpdateCameraTarget( "", NULL ); +const idEventDef EV_DistanceTo( "distanceTo", "E", 'f' ); +const idEventDef EV_DistanceToPoint( "distanceToPoint", "v", 'f' ); +const idEventDef EV_StartFx( "startFx", "s" ); +const idEventDef EV_HasFunction( "hasFunction", "s", 'd' ); +const idEventDef EV_CallFunction( "callFunction", "s" ); +const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" ); +const idEventDef EV_SetGui ( "setGui", "ds" ); +const idEventDef EV_PrecacheGui ( "precacheGui", "s" ); +const idEventDef EV_GetGuiParm ( "getGuiParm", "ds", 's' ); +const idEventDef EV_GetGuiParmFloat ( "getGuiParmFloat", "ds", 'f' ); +const idEventDef EV_MotionBlurOn( "motionBlurOn" ); +const idEventDef EV_MotionBlurOff( "motionBlurOff" ); +const idEventDef EV_GuiNamedEvent ( "guiNamedEvent", "ds" ); + +ABSTRACT_DECLARATION( idClass, idEntity ) + EVENT( EV_GetName, idEntity::Event_GetName ) + EVENT( EV_SetName, idEntity::Event_SetName ) + EVENT( EV_FindTargets, idEntity::Event_FindTargets ) + EVENT( EV_ActivateTargets, idEntity::Event_ActivateTargets ) + EVENT( EV_NumTargets, idEntity::Event_NumTargets ) + EVENT( EV_GetTarget, idEntity::Event_GetTarget ) + EVENT( EV_RandomTarget, idEntity::Event_RandomTarget ) + EVENT( EV_BindToJoint, idEntity::Event_BindToJoint ) + EVENT( EV_RemoveBinds, idEntity::Event_RemoveBinds ) + EVENT( EV_Bind, idEntity::Event_Bind ) + EVENT( EV_BindPosition, idEntity::Event_BindPosition ) + EVENT( EV_Unbind, idEntity::Event_Unbind ) + EVENT( EV_SpawnBind, idEntity::Event_SpawnBind ) + EVENT( EV_SetOwner, idEntity::Event_SetOwner ) + EVENT( EV_SetModel, idEntity::Event_SetModel ) + EVENT( EV_SetSkin, idEntity::Event_SetSkin ) + EVENT( EV_GetShaderParm, idEntity::Event_GetShaderParm ) + EVENT( EV_SetShaderParm, idEntity::Event_SetShaderParm ) + EVENT( EV_SetShaderParms, idEntity::Event_SetShaderParms ) + EVENT( EV_SetColor, idEntity::Event_SetColor ) + EVENT( EV_GetColor, idEntity::Event_GetColor ) + EVENT( EV_IsHidden, idEntity::Event_IsHidden ) + EVENT( EV_Hide, idEntity::Event_Hide ) + EVENT( EV_Show, idEntity::Event_Show ) + EVENT( EV_CacheSoundShader, idEntity::Event_CacheSoundShader ) + EVENT( EV_StartSoundShader, idEntity::Event_StartSoundShader ) + EVENT( EV_StartSound, idEntity::Event_StartSound ) + EVENT( EV_StopSound, idEntity::Event_StopSound ) + EVENT( EV_FadeSound, idEntity::Event_FadeSound ) + EVENT( EV_GetWorldOrigin, idEntity::Event_GetWorldOrigin ) + EVENT( EV_SetWorldOrigin, idEntity::Event_SetWorldOrigin ) + EVENT( EV_GetOrigin, idEntity::Event_GetOrigin ) + EVENT( EV_SetOrigin, idEntity::Event_SetOrigin ) + EVENT( EV_GetAngles, idEntity::Event_GetAngles ) + EVENT( EV_SetAngles, idEntity::Event_SetAngles ) + EVENT( EV_GetLinearVelocity, idEntity::Event_GetLinearVelocity ) + EVENT( EV_SetLinearVelocity, idEntity::Event_SetLinearVelocity ) + EVENT( EV_GetAngularVelocity, idEntity::Event_GetAngularVelocity ) + EVENT( EV_SetAngularVelocity, idEntity::Event_SetAngularVelocity ) + EVENT( EV_GetSize, idEntity::Event_GetSize ) + EVENT( EV_SetSize, idEntity::Event_SetSize ) + EVENT( EV_GetMins, idEntity::Event_GetMins) + EVENT( EV_GetMaxs, idEntity::Event_GetMaxs ) + EVENT( EV_Touches, idEntity::Event_Touches ) + EVENT( EV_SetGuiParm, idEntity::Event_SetGuiParm ) + EVENT( EV_SetGuiFloat, idEntity::Event_SetGuiFloat ) + EVENT( EV_GetNextKey, idEntity::Event_GetNextKey ) + EVENT( EV_SetKey, idEntity::Event_SetKey ) + EVENT( EV_GetKey, idEntity::Event_GetKey ) + EVENT( EV_GetIntKey, idEntity::Event_GetIntKey ) + EVENT( EV_GetFloatKey, idEntity::Event_GetFloatKey ) + EVENT( EV_GetVectorKey, idEntity::Event_GetVectorKey ) + EVENT( EV_GetEntityKey, idEntity::Event_GetEntityKey ) + EVENT( EV_RestorePosition, idEntity::Event_RestorePosition ) + EVENT( EV_UpdateCameraTarget, idEntity::Event_UpdateCameraTarget ) + EVENT( EV_DistanceTo, idEntity::Event_DistanceTo ) + EVENT( EV_DistanceToPoint, idEntity::Event_DistanceToPoint ) + EVENT( EV_StartFx, idEntity::Event_StartFx ) + EVENT( EV_Thread_WaitFrame, idEntity::Event_WaitFrame ) + EVENT( EV_Thread_Wait, idEntity::Event_Wait ) + EVENT( EV_HasFunction, idEntity::Event_HasFunction ) + EVENT( EV_CallFunction, idEntity::Event_CallFunction ) + EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant ) + EVENT( EV_SetGui, idEntity::Event_SetGui ) + EVENT( EV_PrecacheGui, idEntity::Event_PrecacheGui ) + EVENT( EV_GetGuiParm, idEntity::Event_GetGuiParm ) + EVENT( EV_GetGuiParmFloat, idEntity::Event_GetGuiParmFloat ) + EVENT( EV_GuiNamedEvent, idEntity::Event_GuiNamedEvent ) +END_CLASS + +/* +================ +UpdateGuiParms +================ +*/ +void UpdateGuiParms( idUserInterface *gui, const idDict *args ) { + if ( gui == NULL || args == NULL ) { + return; + } + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + while( kv ) { + gui->SetStateString( kv->GetKey(), kv->GetValue() ); + kv = args->MatchPrefix( "gui_parm", kv ); + } + gui->SetStateBool( "noninteractive", args->GetBool( "gui_noninteractive" ) ) ; + gui->StateChanged( gameLocal.time ); +} + +/* +================ +AddRenderGui +================ +*/ +void AddRenderGui( const char *name, idUserInterface **gui, const idDict *args ) { + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + *gui = uiManager->FindGui( name, true, ( kv != NULL ) ); + UpdateGuiParms( *gui, args ); +} + +/* +================ +idGameEdit::ParseSpawnArgsToRenderEntity + +parse the static model parameters +this is the canonical renderEntity parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ) { + int i; + const char *temp; + idVec3 color; + float angle; + const idDeclModelDef *modelDef; + + memset( renderEntity, 0, sizeof( *renderEntity ) ); + + temp = args->GetString( "model" ); + + modelDef = NULL; + if ( temp[0] != '\0' ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + renderEntity->hModel = modelDef->ModelHandle(); + } + if ( !renderEntity->hModel ) { + renderEntity->hModel = renderModelManager->FindModel( temp ); + } + } + if ( renderEntity->hModel ) { + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + } else { + renderEntity->bounds.Zero(); + } + + temp = args->GetString( "skin" ); + if ( temp[0] != '\0' ) { + renderEntity->customSkin = declManager->FindSkin( temp ); + } else if ( modelDef ) { + renderEntity->customSkin = modelDef->GetDefaultSkin(); + } + + temp = args->GetString( "shader" ); + if ( temp[0] != '\0' ) { + renderEntity->customShader = declManager->FindMaterial( temp ); + } + + args->GetVector( "origin", "0 0 0", renderEntity->origin ); + + // get the rotation matrix in either full form, or single angle form + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", renderEntity->axis ) ) { + angle = args->GetFloat( "angle" ); + if ( angle != 0.0f ) { + renderEntity->axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + renderEntity->axis.Identity(); + } + } + + renderEntity->referenceSound = NULL; + + // get shader parms + args->GetVector( "_color", "1 1 1", color ); + renderEntity->shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity->shaderParms[ 3 ] = args->GetFloat( "shaderParm3", "1" ); + renderEntity->shaderParms[ 4 ] = args->GetFloat( "shaderParm4", "0" ); + renderEntity->shaderParms[ 5 ] = args->GetFloat( "shaderParm5", "0" ); + renderEntity->shaderParms[ 6 ] = args->GetFloat( "shaderParm6", "0" ); + renderEntity->shaderParms[ 7 ] = args->GetFloat( "shaderParm7", "0" ); + renderEntity->shaderParms[ 8 ] = args->GetFloat( "shaderParm8", "0" ); + renderEntity->shaderParms[ 9 ] = args->GetFloat( "shaderParm9", "0" ); + renderEntity->shaderParms[ 10 ] = args->GetFloat( "shaderParm10", "0" ); + renderEntity->shaderParms[ 11 ] = args->GetFloat( "shaderParm11", "0" ); + + // check noDynamicInteractions flag + renderEntity->noDynamicInteractions = args->GetBool( "noDynamicInteractions" ); + + // check noshadows flag + renderEntity->noShadow = args->GetBool( "noshadows" ); + + // check noselfshadows flag + renderEntity->noSelfShadow = args->GetBool( "noselfshadows" ); + + // init any guis, including entity-specific states + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + temp = args->GetString( i == 0 ? "gui" : va( "gui%d", i + 1 ) ); + if ( temp[ 0 ] != '\0' ) { + AddRenderGui( temp, &renderEntity->gui[ i ], args ); + } + } +} + +/* +================ +idGameEdit::ParseSpawnArgsToRefSound + +parse the sound parameters +this is the canonical refSound parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ) { + const char *temp; + + memset( refSound, 0, sizeof( *refSound ) ); + + refSound->parms.minDistance = args->GetFloat( "s_mindistance" ); + refSound->parms.maxDistance = args->GetFloat( "s_maxdistance" ); + refSound->parms.volume = args->GetFloat( "s_volume" ); + refSound->parms.shakes = args->GetFloat( "s_shakes" ); + + args->GetVector( "origin", "0 0 0", refSound->origin ); + + refSound->referenceSound = NULL; + + // if a diversity is not specified, every sound start will make + // a random one. Specifying diversity is usefull to make multiple + // lights all share the same buzz sound offset, for instance. + refSound->diversity = args->GetFloat( "s_diversity", "-1" ); + refSound->waitfortrigger = args->GetBool( "s_waitfortrigger" ); + + if ( args->GetBool( "s_omni" ) ) { + refSound->parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + if ( args->GetBool( "s_looping" ) ) { + refSound->parms.soundShaderFlags |= SSF_LOOPING; + } + if ( args->GetBool( "s_occlusion" ) ) { + refSound->parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + if ( args->GetBool( "s_global" ) ) { + refSound->parms.soundShaderFlags |= SSF_GLOBAL; + } + if ( args->GetBool( "s_unclamped" ) ) { + refSound->parms.soundShaderFlags |= SSF_UNCLAMPED; + } + refSound->parms.soundClass = args->GetInt( "s_soundClass" ); + + temp = args->GetString( "s_shader" ); + if ( temp[0] != '\0' ) { + refSound->shader = declManager->FindSound( temp ); + } +} + +/* +=============== +idEntity::UpdateChangeableSpawnArgs + +Any key val pair that might change during the course of the game ( via a gui or whatever ) +should be initialize here so a gui or other trigger can change something and have it updated +properly. An optional source may be provided if the values reside in an outside dictionary and +first need copied over to spawnArgs +=============== +*/ +void idEntity::UpdateChangeableSpawnArgs( const idDict *source ) { + int i; + const char *target; + + if ( !source ) { + source = &spawnArgs; + } + cameraTarget = NULL; + target = source->GetString( "cameraTarget" ); + if ( target != NULL && target[0] != NULL ) { + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], source ); + } +} + +/* +================ +idEntity::idEntity +================ +*/ +idEntity::idEntity(): + useClientInterpolation( true ), + predictionKey( INVALID_PREDICTION_KEY ), + originDelta( vec3_zero ), + axisDelta( mat3_identity ), + interpolationBehavior( USE_NO_INTERPOLATION ) { + + entityNumber = ENTITYNUM_NONE; + entityDefNumber = -1; + + spawnNode.SetOwner( this ); + activeNode.SetOwner( this ); + + snapshotNode.SetOwner( this ); + snapshotChanged = -1; + snapshotStale = false; + snapshotBits = 0; + + thinkFlags = 0; + dormantStart = 0; + cinematic = false; + renderView = NULL; + cameraTarget = NULL; + health = 0; + + physics = NULL; + bindMaster = NULL; + bindJoint = INVALID_JOINT; + bindBody = -1; + teamMaster = NULL; + teamChain = NULL; + signals = NULL; + + snapshotsReceived = 0; + + memset( PVSAreas, 0, sizeof( PVSAreas ) ); + numPVSAreas = -1; + + memset( &fl, 0, sizeof( fl ) ); + fl.neverDormant = true; // most entities never go dormant + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + modelDefHandle = -1; + memset( &refSound, 0, sizeof( refSound ) ); + + mpGUIState = -1; + + memset( &xrayEntity, 0, sizeof( xrayEntity ) ); + + timeGroup = TIME_GROUP1; + xrayEntityHandle = -1; + xraySkin = NULL; + + noGrab = false; +} + +/* +================ +idEntity::FixupLocalizedStrings +================ +*/ +void idEntity::FixupLocalizedStrings() { + for ( int i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = spawnArgs.GetKeyVal( i ); + if ( idStr::Cmpn( kv->GetValue(), STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ){ + spawnArgs.Set( kv->GetKey(), idLocalization::GetString( kv->GetValue() ) ); + } + } +} + +/* +================ +idEntity::Spawn +================ +*/ +void idEntity::Spawn() { + int i; + const char *temp; + idVec3 origin; + idMat3 axis; + const idKeyValue *networkSync; + const char *classname; + const char *scriptObjectName; + + gameLocal.RegisterEntity( this, -1, gameLocal.GetSpawnArgs() ); + + spawnArgs.GetString( "classname", NULL, &classname ); + const idDeclEntityDef *def = gameLocal.FindEntityDef( classname, false ); + if ( def ) { + entityDefNumber = def->Index(); + } + + FixupLocalizedStrings(); + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + renderEntity.entityNum = entityNumber; + + noGrab = spawnArgs.GetBool( "noGrab", "0" ); + + xraySkin = NULL; + renderEntity.xrayIndex = 1; + + idStr str; + if ( spawnArgs.GetString( "skin_xray", "", str ) ) { + xraySkin = declManager->FindSkin( str.c_str() ); + } + + // go dormant within 100ms so that when the map starts most monsters are dormant + dormantStart = gameLocal.time - DELAY_DORMANT_TIME + 100; + + origin = renderEntity.origin; + axis = renderEntity.axis; + + // do the audio parsing the same way dmap and the editor do + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + // only play SCHANNEL_PRIVATE when sndworld->PlaceListener() is called with this listenerId + // don't spatialize sounds from the same entity + refSound.listenerId = entityNumber + 1; + + cameraTarget = NULL; + temp = spawnArgs.GetString( "cameraTarget" ); + if ( temp != NULL && temp[0] != NULL ) { + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], &spawnArgs ); + } + + fl.solidForTeam = spawnArgs.GetBool( "solidForTeam", "0" ); + fl.neverDormant = spawnArgs.GetBool( "neverDormant", "0" ); + fl.hidden = spawnArgs.GetBool( "hide", "0" ); + if ( fl.hidden ) { + // make sure we're hidden, since a spawn function might not set it up right + PostEventMS( &EV_Hide, 0 ); + } + cinematic = spawnArgs.GetBool( "cinematic", "0" ); + + networkSync = spawnArgs.FindKey( "networkSync" ); + if ( networkSync ) { + fl.networkSync = ( atoi( networkSync->GetValue() ) != 0 ); + } + +#if 0 + if ( !common->IsClient() ) { + // common->DPrintf( "NET: DBG %s - %s is synced: %s\n", spawnArgs.GetString( "classname", "" ), GetType()->classname, fl.networkSync ? "true" : "false" ); + if ( spawnArgs.GetString( "classname", "" )[ 0 ] == '\0' && !fl.networkSync ) { + common->DPrintf( "NET: WRN %s entity, no classname, and no networkSync?\n", GetType()->classname ); + } + } +#endif + + // every object will have a unique name + temp = spawnArgs.GetString( "name", va( "%s_%s_%d", GetClassname(), spawnArgs.GetString( "classname" ), entityNumber ) ); + SetName( temp ); + + // if we have targets, wait until all entities are spawned to get them + if ( spawnArgs.MatchPrefix( "target" ) || spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); + + temp = spawnArgs.GetString( "model" ); + if ( temp != NULL && *temp != NULL ) { + SetModel( temp ); + } + + if ( spawnArgs.GetString( "bind", "", &temp ) ) { + PostEventMS( &EV_SpawnBind, 0 ); + } + + // auto-start a sound on the entity + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + // setup script object + if ( ShouldConstructScriptObjectAtSpawn() && spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } + + // determine time group + DetermineTimeGroup( spawnArgs.GetBool( "slowmo", "1" ) ); +} + +/* +================ +idEntity::~idEntity +================ +*/ +idEntity::~idEntity() { + DeconstructScriptObject(); + scriptObject.Free(); + + if ( thinkFlags ) { + BecomeInactive( thinkFlags ); + } + activeNode.Remove(); + + Signal( SIG_REMOVED ); + + // we have to set back the default physics object before unbinding because the entity + // specific physics object might be an entity variable and as such could already be destroyed. + SetPhysics( NULL ); + + // remove any entities that are bound to me + RemoveBinds(); + + // unbind from master + Unbind(); + QuitTeam(); + + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + + delete renderView; + renderView = NULL; + + delete signals; + signals = NULL; + + FreeModelDef(); + FreeSoundEmitter( false ); + + if ( xrayEntityHandle != -1) { + gameRenderWorld->FreeEntityDef( xrayEntityHandle ); + xrayEntityHandle = -1; + } + + gameLocal.UnregisterEntity( this ); +} + +/* +================ +idEntity::Save +================ +*/ +void idEntity::Save( idSaveGame *savefile ) const { + int i, j; + + savefile->WriteInt( entityNumber ); + savefile->WriteInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + savefile->WriteDict( &spawnArgs ); + savefile->WriteString( name ); + scriptObject.Save( savefile ); + + savefile->WriteInt( thinkFlags ); + savefile->WriteInt( dormantStart ); + savefile->WriteBool( cinematic ); + + savefile->WriteObject( cameraTarget ); + + savefile->WriteInt( health ); + + savefile->WriteInt( targets.Num() ); + for( i = 0; i < targets.Num(); i++ ) { + targets[ i ].Save( savefile ); + } + + entityFlags_s flags = fl; + LittleBitField( &flags, sizeof( flags ) ); + savefile->Write( &flags, sizeof( flags ) ); + + savefile->WriteInt( timeGroup ); + savefile->WriteBool( noGrab ); + savefile->WriteRenderEntity( xrayEntity ); + savefile->WriteInt( xrayEntityHandle ); + savefile->WriteSkin( xraySkin ); + + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( modelDefHandle ); + savefile->WriteRefSound( refSound ); + + savefile->WriteObject( bindMaster ); + savefile->WriteJoint( bindJoint ); + savefile->WriteInt( bindBody ); + savefile->WriteObject( teamMaster ); + savefile->WriteObject( teamChain ); + + savefile->WriteStaticObject( defaultPhysicsObj ); + + savefile->WriteInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->WriteInt( PVSAreas[ i ] ); + } + + if ( !signals ) { + savefile->WriteBool( false ); + } else { + savefile->WriteBool( true ); + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->WriteInt( signals->signal[ i ].Num() ); + for( j = 0; j < signals->signal[ i ].Num(); j++ ) { + savefile->WriteInt( signals->signal[ i ][ j ].threadnum ); + savefile->WriteString( signals->signal[ i ][ j ].function->Name() ); + } + } + } + + savefile->WriteInt( mpGUIState ); +} + +/* +================ +idEntity::Restore +================ +*/ +void idEntity::Restore( idRestoreGame *savefile ) { + int i, j; + int num; + idStr funcname; + + savefile->ReadInt( entityNumber ); + savefile->ReadInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + savefile->ReadDict( &spawnArgs ); + savefile->ReadString( name ); + SetName( name ); + + scriptObject.Restore( savefile ); + + savefile->ReadInt( thinkFlags ); + savefile->ReadInt( dormantStart ); + savefile->ReadBool( cinematic ); + + savefile->ReadObject( reinterpret_cast( cameraTarget ) ); + + savefile->ReadInt( health ); + + targets.Clear(); + savefile->ReadInt( num ); + targets.SetNum( num ); + for( i = 0; i < num; i++ ) { + targets[ i ].Restore( savefile ); + } + + savefile->Read( &fl, sizeof( fl ) ); + LittleBitField( &fl, sizeof( fl ) ); + + savefile->ReadInt( timeGroup ); + savefile->ReadBool( noGrab ); + savefile->ReadRenderEntity( xrayEntity ); + savefile->ReadInt( xrayEntityHandle ); + if ( xrayEntityHandle != -1 ) { + xrayEntityHandle = gameRenderWorld->AddEntityDef( &xrayEntity ); + } + savefile->ReadSkin( xraySkin ); + + savefile->ReadRenderEntity( renderEntity ); + savefile->ReadInt( modelDefHandle ); + savefile->ReadRefSound( refSound ); + + savefile->ReadObject( reinterpret_cast( bindMaster ) ); + savefile->ReadJoint( bindJoint ); + savefile->ReadInt( bindBody ); + savefile->ReadObject( reinterpret_cast( teamMaster ) ); + savefile->ReadObject( reinterpret_cast( teamChain ) ); + + savefile->ReadStaticObject( defaultPhysicsObj ); + RestorePhysics( &defaultPhysicsObj ); + + savefile->ReadInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->ReadInt( PVSAreas[ i ] ); + } + + bool readsignals; + savefile->ReadBool( readsignals ); + if ( readsignals ) { + signals = new (TAG_ENTITY) signalList_t; + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->ReadInt( num ); + signals->signal[ i ].SetNum( num ); + for( j = 0; j < num; j++ ) { + savefile->ReadInt( signals->signal[ i ][ j ].threadnum ); + savefile->ReadString( funcname ); + signals->signal[ i ][ j ].function = gameLocal.program.FindFunction( funcname ); + if ( !signals->signal[ i ][ j ].function ) { + savefile->Error( "Function '%s' not found", funcname.c_str() ); + } + } + } + } + + savefile->ReadInt( mpGUIState ); + + // restore must retrieve modelDefHandle from the renderer + if ( modelDefHandle != -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } +} + +/* +================ +idEntity::GetEntityDefName +================ +*/ +const char * idEntity::GetEntityDefName() const { + if ( entityDefNumber < 0 ) { + return "*unknown*"; + } + return declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); +} + +/* +================ +idEntity::SetName +================ +*/ +void idEntity::SetName( const char *newname ) { + if ( name.Length() ) { + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, NULL ); + } + + name = newname; + if ( name.Length() ) { + if ( ( name == "NULL" ) || ( name == "null_entity" ) ) { + gameLocal.Error( "Cannot name entity '%s'. '%s' is reserved for script.", name.c_str(), name.c_str() ); + } + gameLocal.AddEntityToHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, this ); + } +} + +/* +================ +idEntity::GetName +================ +*/ +const char * idEntity::GetName() const { + return name.c_str(); +} + + +/*********************************************************************** + + Thinking + +***********************************************************************/ + +/* +================ +idEntity::Think +================ +*/ +void idEntity::Think() { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::DoDormantTests + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::DoDormantTests() { + + if ( fl.neverDormant ) { + return false; + } + + // if the monster area is not topologically connected to a player + if ( !gameLocal.InPlayerConnectedArea( this ) ) { + if ( dormantStart == 0 ) { + dormantStart = gameLocal.time; + } + if ( gameLocal.time - dormantStart < DELAY_DORMANT_TIME ) { + // just got closed off, don't go dormant yet + return false; + } + return true; + } else { + // the monster area is topologically connected to a player, but if + // the monster hasn't been woken up before, do the more precise PVS check + if ( !fl.hasAwakened ) { + if ( !gameLocal.InPlayerPVS( this ) ) { + return true; // stay dormant + } + } + + // wake up + dormantStart = 0; + fl.hasAwakened = true; // only go dormant when area closed off now, not just out of PVS + return false; + } +} + +/* +================ +idEntity::CheckDormant + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::CheckDormant() { + bool dormant; + + dormant = DoDormantTests(); + if ( dormant && !fl.isDormant ) { + fl.isDormant = true; + DormantBegin(); + } else if ( !dormant && fl.isDormant ) { + fl.isDormant = false; + DormantEnd(); + } + + return dormant; +} + +/* +================ +idEntity::DormantBegin + +called when entity becomes dormant +================ +*/ +void idEntity::DormantBegin() { +} + +/* +================ +idEntity::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idEntity::DormantEnd() { +} + +/* +================ +idEntity::IsActive +================ +*/ +bool idEntity::IsActive() const { + return activeNode.InList(); +} + +/* +================ +idEntity::BecomeActive +================ +*/ +void idEntity::BecomeActive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // enable the team master if this entity is part of a physics team + if ( teamMaster && teamMaster != this ) { + teamMaster->BecomeActive( TH_PHYSICS ); + } else if ( !( thinkFlags & TH_PHYSICS ) ) { + // if this is a pusher + if ( physics->IsType( idPhysics_Parametric::Type ) || physics->IsType( idPhysics_Actor::Type ) ) { + gameLocal.sortPushers = true; + } + } + } + + int oldFlags = thinkFlags; + thinkFlags |= flags; + if ( thinkFlags ) { + if ( !IsActive() ) { + activeNode.AddToEnd( gameLocal.activeEntities ); + } else if ( !oldFlags ) { + // we became inactive this frame, so we have to decrease the count of entities to deactivate + gameLocal.numEntitiesToDeactivate--; + } + } +} + +/* +================ +idEntity::BecomeInactive +================ +*/ +void idEntity::BecomeInactive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // may only disable physics on a team master if no team members are running physics or bound to a joints + if ( teamMaster == this ) { + for ( idEntity *ent = teamMaster->teamChain; ent; ent = ent->teamChain ) { + if ( ( ent->thinkFlags & TH_PHYSICS ) || ( ( ent->bindMaster == this ) && ( ent->bindJoint != INVALID_JOINT ) ) ) { + flags &= ~TH_PHYSICS; + break; + } + } + } + } + + if ( thinkFlags ) { + thinkFlags &= ~flags; + if ( !thinkFlags && IsActive() ) { + gameLocal.numEntitiesToDeactivate++; + } + } + + if ( ( flags & TH_PHYSICS ) ) { + // if this entity has a team master + if ( teamMaster && teamMaster != this ) { + // if the team master is at rest + if ( teamMaster->IsAtRest() ) { + teamMaster->BecomeInactive( TH_PHYSICS ); + } + } + // Becoming inactive automagically turns on motion blur again + renderEntity.skipMotionBlur = false; + BecomeActive( TH_UPDATEVISUALS ); + } +} + +/*********************************************************************** + + Visuals + +***********************************************************************/ + +/* +================ +idEntity::SetShaderParm +================ +*/ +void idEntity::SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + renderEntity.shaderParms[ parmnum ] = value; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( float red, float green, float blue ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = red; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = green; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = blue; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec3 &color ) { + SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec3 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec4 &color ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec4 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idEntity::UpdateAnimationControllers +================ +*/ +bool idEntity::UpdateAnimationControllers() { + // any ragdoll and IK animation controllers should be updated here + return false; +} + +/* +================ +idEntity::SetModel +================ +*/ +void idEntity::SetModel( const char *modelname ) { + assert( modelname ); + + FreeModelDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetSkin +================ +*/ +void idEntity::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} + +/* +================ +idEntity::GetSkin +================ +*/ +const idDeclSkin *idEntity::GetSkin() const { + return renderEntity.customSkin; +} + +/* +================ +idEntity::FreeModelDef +================ +*/ +void idEntity::FreeModelDef() { + if ( modelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandle ); + modelDefHandle = -1; + } +} + +/* +================ +idEntity::FreeLightDef +================ +*/ +void idEntity::FreeLightDef() { +} + +/* +================ +idEntity::IsHidden +================ +*/ +bool idEntity::IsHidden() const { + return fl.hidden; +} + +/* +================ +idEntity::Hide +================ +*/ +void idEntity::Hide() { + if ( !IsHidden() ) { + fl.hidden = true; + FreeModelDef(); + UpdateVisuals(); + } +} + +/* +================ +idEntity::Show +================ +*/ +void idEntity::Show() { + if ( IsHidden() ) { + fl.hidden = false; + UpdateVisuals(); + } +} + +/* +================ +idEntity::UpdateModelTransform +================ +*/ +void idEntity::UpdateModelTransform() { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + // Add the deltas here, used for projectiles in MP. These deltas should only affect the visuals. + renderEntity.axis = GetPhysics()->GetAxis() * axisDelta; + renderEntity.origin = GetPhysics()->GetOrigin() + originDelta; + } +} + +/* +================ +idEntity::UpdateModel +================ +*/ +void idEntity::UpdateModel() { + renderEntity.timeGroup = timeGroup; + + UpdateModelTransform(); + + // check if the entity has an MD5 model + idAnimator *animator = GetAnimator(); + if ( animator != NULL && animator->ModelHandle() != NULL ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + } + + // set to invalid number to force an update the next time the PVS areas are retrieved + ClearPVSAreas(); + + // ensure that we call Present this frame + BecomeActive( TH_UPDATEVISUALS ); + + // If the entity has an xray skin, go ahead and add it + if ( xraySkin != NULL ) { + xrayEntity = renderEntity; + xrayEntity.xrayIndex = 2; + xrayEntity.customSkin = xraySkin; + + if ( xrayEntityHandle == -1 ) { + xrayEntityHandle = gameRenderWorld->AddEntityDef( &xrayEntity ); + } else { + gameRenderWorld->UpdateEntityDef( xrayEntityHandle, &xrayEntity ); + } + } +} + +/* +================ +idEntity::UpdateVisuals +================ +*/ +void idEntity::UpdateVisuals() { + UpdateModel(); + UpdateSound(); +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas() { + int localNumPVSAreas, localPVSAreas[32]; + idBounds modelAbsBounds; + int i; + + modelAbsBounds.FromTransformedBounds( renderEntity.bounds, renderEntity.origin, renderEntity.axis ); + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( modelAbsBounds, localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + + // FIXME: some particle systems may have huge bounds and end up in many PVS areas + // the first MAX_PVS_AREAS may not be visible to a network client and as a result the particle system may not show up when it should + if ( localNumPVSAreas > MAX_PVS_AREAS ) { + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( renderEntity.origin ).Expand( 64.0f ), localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + } + + for ( numPVSAreas = 0; numPVSAreas < MAX_PVS_AREAS && numPVSAreas < localNumPVSAreas; numPVSAreas++ ) { + PVSAreas[numPVSAreas] = localPVSAreas[numPVSAreas]; + } + + for( i = numPVSAreas; i < MAX_PVS_AREAS; i++ ) { + PVSAreas[ i ] = 0; + } +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( const idVec3 &pos ) { + int i; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( pos ), PVSAreas, MAX_PVS_AREAS ); + i = numPVSAreas; + while ( i < MAX_PVS_AREAS ) { + PVSAreas[ i++ ] = 0; + } +} + +/* +================ +idEntity::GetNumPVSAreas +================ +*/ +int idEntity::GetNumPVSAreas() { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return numPVSAreas; +} + +/* +================ +idEntity::GetPVSAreas +================ +*/ +const int *idEntity::GetPVSAreas() { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return PVSAreas; +} + +/* +================ +idEntity::ClearPVSAreas +================ +*/ +void idEntity::ClearPVSAreas() { + numPVSAreas = -1; +} + +/* +================ +idEntity::PhysicsTeamInPVS + + FIXME: for networking also return true if any of the entity shadows is in the PVS +================ +*/ +bool idEntity::PhysicsTeamInPVS( pvsHandle_t pvsHandle ) { + idEntity *part; + + if ( teamMaster ) { + for ( part = teamMaster; part; part = part->teamChain ) { + if ( gameLocal.pvs.InCurrentPVS( pvsHandle, part->GetPVSAreas(), part->GetNumPVSAreas() ) ) { + return true; + } + } + } else { + return gameLocal.pvs.InCurrentPVS( pvsHandle, GetPVSAreas(), GetNumPVSAreas() ); + } + return false; +} + +/* +============== +idEntity::BecomeReplicated +============== +*/ +void idEntity::BecomeReplicated() { + fl.skipReplication = false; + + if ( GetPhysics() ) { + GetPhysics()->ResetInterpolationState( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } +} + +/* +============== +idEntity::ProjectOverlay +============== +*/ +void idEntity::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( modelDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr, gameLocal.slow.time ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +idEntity::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idEntity::Present() { + + if ( !gameLocal.isNewFrame ) { + return; + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views + if ( cameraTarget && gameLocal.InPlayerPVS( this ) ) { + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idEntity::GetRenderEntity +================ +*/ +renderEntity_t *idEntity::GetRenderEntity() { + return &renderEntity; +} + +/* +================ +idEntity::GetModelDefHandle +================ +*/ +int idEntity::GetModelDefHandle() { + return modelDefHandle; +} + +/* +================ +idEntity::UpdateRenderEntity +================ +*/ +bool idEntity::UpdateRenderEntity( renderEntity_s * renderEntity, const renderView_t * renderView ) { + + idAnimator * animator = GetAnimator(); + if ( animator != NULL ) { + SetTimeState ts( timeGroup ); + int currentTime = gameLocal.time; + if ( renderEntity != NULL ) { + currentTime = gameLocal.GetTimeGroupTime( renderEntity->timeGroup ); + } + return animator->CreateFrame( currentTime, false ); + } + + return false; +} + +/* +================ +idEntity::ModelCallback + + NOTE: may not change the game state whatsoever! +================ +*/ +bool idEntity::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + idEntity *ent; + + ent = gameLocal.entities[ renderEntity->entityNum ]; + if ( ent == NULL ) { + gameLocal.Error( "idEntity::ModelCallback: callback with NULL game entity" ); + return false; + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idEntity::GetAnimator + +Subclasses will be responsible for allocating animator. +================ +*/ +idAnimator *idEntity::GetAnimator() { + return NULL; +} + +/* +============= +idEntity::GetRenderView + +This is used by remote camera views to look from an entity +============= +*/ +renderView_t *idEntity::GetRenderView() { + if ( !renderView ) { + renderView = new (TAG_ENTITY) renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + renderView->vieworg = GetPhysics()->GetOrigin(); + renderView->fov_x = 120; + renderView->fov_y = 120; + renderView->viewaxis = GetPhysics()->GetAxis(); + + // copy global shader parms + for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + + renderView->time[0] = gameLocal.slow.time; + renderView->time[1] = gameLocal.fast.time; + + return renderView; +} + +/*********************************************************************** + + Sound + +***********************************************************************/ + +/* +================ +idEntity::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idEntity::CanPlayChatterSounds() const { + return true; +} + +/* +================ +idEntity::StartSound +================ +*/ +bool idEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 ); + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( sound[0] == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); +} + +/* +================ +idEntity::StartSoundShader +================ +*/ +bool idEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + float diversity; + int len; + + if ( length ) { + *length = 0; + } + + if ( !shader ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + return true; + } + + if ( common->IsServer() && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteLong( gameLocal.ServerRemapDecl( -1, DECL_SOUND, shader->Index() ) ); + msg.WriteByte( channel ); + ServerSendEvent( EVENT_STARTSOUNDSHADER, &msg, false ); + } + + // set a random value for diversity unless one was parsed from the entity + if ( refSound.diversity < 0.0f ) { + diversity = gameLocal.random.RandomFloat(); + } else { + diversity = refSound.diversity; + } + + // if we don't have a soundEmitter allocated yet, get one now + if ( !refSound.referenceSound ) { + refSound.referenceSound = gameSoundWorld->AllocSoundEmitter(); + } + + UpdateSound(); + + len = refSound.referenceSound->StartSound( shader, channel, diversity, soundShaderFlags, !timeGroup /*_D3XP*/ ); + if ( length ) { + *length = len; + } + + // set reference to the sound for shader synced effects + renderEntity.referenceSound = refSound.referenceSound; + + return true; +} + +/* +================ +idEntity::StopSound +================ +*/ +void idEntity::StopSound( const s_channelType channel, bool broadcast ) { + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( common->IsServer() && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( channel ); + ServerSendEvent( EVENT_STOPSOUNDSHADER, &msg, false ); + } + + if ( refSound.referenceSound ) { + refSound.referenceSound->StopSound( channel ); + } +} + +/* +================ +idEntity::SetSoundVolume + + Must be called before starting a new sound. +================ +*/ +void idEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + +/* +================ +idEntity::UpdateSound +================ +*/ +void idEntity::UpdateSound() { + if ( refSound.referenceSound ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + + refSound.referenceSound->UpdateEmitter( refSound.origin, refSound.listenerId, &refSound.parms ); + } +} + +/* +================ +idEntity::GetListenerId +================ +*/ +int idEntity::GetListenerId() const { + return refSound.listenerId; +} + +/* +================ +idEntity::GetSoundEmitter +================ +*/ +idSoundEmitter *idEntity::GetSoundEmitter() const { + return refSound.referenceSound; +} + +/* +================ +idEntity::FreeSoundEmitter +================ +*/ +void idEntity::FreeSoundEmitter( bool immediate ) { + if ( refSound.referenceSound ) { + refSound.referenceSound->Free( immediate ); + refSound.referenceSound = NULL; + } +} + +/*********************************************************************** + + entity binding + +***********************************************************************/ + +/* +================ +idEntity::PreBind +================ +*/ +void idEntity::PreBind() { +} + +/* +================ +idEntity::PostBind +================ +*/ +void idEntity::PostBind() { +} + +/* +================ +idEntity::PreUnbind +================ +*/ +void idEntity::PreUnbind() { +} + +/* +================ +idEntity::PostUnbind +================ +*/ +void idEntity::PostUnbind() { +} + +/* +================ +idEntity::InitBind +================ +*/ +bool idEntity::InitBind( idEntity *master ) { + + if ( master == this ) { + gameLocal.Error( "Tried to bind an object to itself." ); + return false; + } + + if ( this == gameLocal.world ) { + gameLocal.Error( "Tried to bind world to another entity" ); + return false; + } + + // unbind myself from my master + Unbind(); + + // add any bind constraints to an articulated figure + if ( master && IsType( idAFEntity_Base::Type ) ) { + static_cast(this)->AddBindConstraints(); + } + + if ( !master || master == gameLocal.world ) { + // this can happen in scripts, so safely exit out. + return false; + } + + return true; +} + +/* +================ +idEntity::FinishBind +================ +*/ +void idEntity::FinishBind() { + + // set the master on the physics object + physics->SetMaster( bindMaster, fl.bindOrientated ); + + // We are now separated from our previous team and are either + // an individual, or have a team of our own. Now we can join + // the new bindMaster's team. Bindmaster must be set before + // joining the team, or we will be placed in the wrong position + // on the team. + JoinTeam( bindMaster ); + + // if our bindMaster is enabled during a cinematic, we must be, too + cinematic = bindMaster->cinematic; + + // make sure the team master is active so that physics get run + teamMaster->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idEntity::Bind + + bind relative to the visual position of the master +================ +*/ +void idEntity::Bind( idEntity *master, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind( ); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, const char *jointname, bool orientated ) { + jointHandle_t jointnum; + idAnimator *masterAnimator; + + if ( !InitBind( master ) ) { + return; + } + + masterAnimator = master->GetAnimator(); + if ( !masterAnimator ) { + gameLocal.Warning( "idEntity::BindToJoint: entity '%s' cannot support skeletal models.", master->GetName() ); + return; + } + + jointnum = masterAnimator->GetJointHandle( jointname ); + if ( jointnum == INVALID_JOINT ) { + gameLocal.Warning( "idEntity::BindToJoint: joint '%s' not found on entity '%s'.", jointname, master->GetName() ); + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToBody + + bind relative to a collision model used by the physics of the master +================ +*/ +void idEntity::BindToBody( idEntity *master, int bodyId, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + if ( bodyId < 0 ) { + gameLocal.Warning( "idEntity::BindToBody: body '%d' not found.", bodyId ); + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = bodyId; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::Unbind +================ +*/ +void idEntity::Unbind() { + idEntity * prev; + idEntity * next; + idEntity * last; + idEntity * ent; + + // remove any bind constraints from an articulated figure + if ( IsType( idAFEntity_Base::Type ) ) { + static_cast(this)->RemoveBindConstraints(); + } + + if ( !bindMaster ) { + return; + } + + if ( !teamMaster ) { + // Teammaster already has been freed + bindMaster = NULL; + return; + } + + PreUnbind(); + + if ( physics ) { + physics->SetMaster( NULL, fl.bindOrientated ); + } + + // We're still part of a team, so that means I have to extricate myself + // and any entities that are bound to me from the old team. + // Find the node previous to me in the team + prev = teamMaster; + for( ent = teamMaster->teamChain; ent && ( ent != this ); ent = ent->teamChain ) { + prev = ent; + } + + assert( ent == this ); // If ent is not pointing to this, then something is very wrong. + + // Find the last node in my team that is bound to me. + // Also find the first node not bound to me, if one exists. + last = this; + for( next = teamChain; next != NULL; next = next->teamChain ) { + if ( !next->IsBoundTo( this ) ) { + break; + } + + // Tell them I'm now the teamMaster + next->teamMaster = this; + last = next; + } + + // disconnect the last member of our team from the old team + last->teamChain = NULL; + + // connect up the previous member of the old team to the node that + // follow the last node bound to me (if one exists). + if ( teamMaster != this ) { + prev->teamChain = next; + if ( !next && ( teamMaster == prev ) ) { + prev->teamMaster = NULL; + } + } else if ( next ) { + // If we were the teamMaster, then the nodes that were not bound to me are now + // a disconnected chain. Make them into their own team. + for( ent = next; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamMaster = next; + } + next->teamMaster = next; + } + + // If we don't have anyone on our team, then clear the team variables. + if ( teamChain ) { + // make myself my own team + teamMaster = this; + } else { + // no longer a team + teamMaster = NULL; + } + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = NULL; + + PostUnbind(); +} + +/* +================ +idEntity::RemoveBinds +================ +*/ +void idEntity::RemoveBinds() { + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->teamChain; + if ( ent->bindMaster == this ) { + ent->Unbind(); + ent->PostEventMS( &EV_Remove, 0 ); + next = teamChain; + } + } +} + +/* +================ +idEntity::IsBound +================ +*/ +bool idEntity::IsBound() const { + if ( bindMaster ) { + return true; + } + return false; +} + +/* +================ +idEntity::IsBoundTo +================ +*/ +bool idEntity::IsBoundTo( idEntity *master ) const { + idEntity *ent; + + if ( !bindMaster ) { + return false; + } + + for ( ent = bindMaster; ent != NULL; ent = ent->bindMaster ) { + if ( ent == master ) { + return true; + } + } + + return false; +} + +/* +================ +idEntity::GetBindMaster +================ +*/ +idEntity *idEntity::GetBindMaster() const { + return bindMaster; +} + +/* +================ +idEntity::GetBindJoint +================ +*/ +jointHandle_t idEntity::GetBindJoint() const { + return bindJoint; +} + +/* +================ +idEntity::GetBindBody +================ +*/ +int idEntity::GetBindBody() const { + return bindBody; +} + +/* +================ +idEntity::GetTeamMaster +================ +*/ +idEntity *idEntity::GetTeamMaster() const { + return teamMaster; +} + +/* +================ +idEntity::GetNextTeamEntity +================ +*/ +idEntity *idEntity::GetNextTeamEntity() const { + return teamChain; +} + +/* +===================== +idEntity::ConvertLocalToWorldTransform +===================== +*/ +void idEntity::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + UpdateModelTransform(); + + offset = renderEntity.origin + offset * renderEntity.axis; + axis *= renderEntity.axis; +} + +/* +================ +idEntity::GetLocalVector + +Takes a vector in worldspace and transforms it into the parent +object's localspace. + +Note: Does not take origin into acount. Use getLocalCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetLocalVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetLocalCoordinates + +Takes a vector in world coordinates and transforms it into the parent +object's local coordinates. +================ +*/ +idVec3 idEntity::GetLocalCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec - masterOrigin, pos ); + + return pos; +} + +/* +================ +idEntity::GetWorldVector + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. + +Note: Does not take origin into acount. Use getWorldCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetWorldVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetWorldCoordinates + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. +================ +*/ +idVec3 idEntity::GetWorldCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + pos += masterOrigin; + + return pos; +} + +/* +================ +idEntity::GetMasterPosition +================ +*/ +bool idEntity::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idVec3 localOrigin; + idMat3 localAxis; + idAnimator *masterAnimator; + + if ( bindMaster ) { + // if bound to a joint of an animated model + if ( bindJoint != INVALID_JOINT ) { + masterAnimator = bindMaster->GetAnimator(); + if ( !masterAnimator ) { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } else { + masterAnimator->GetJointTransform( bindJoint, gameLocal.time, masterOrigin, masterAxis ); + masterAxis *= bindMaster->renderEntity.axis; + masterOrigin = bindMaster->renderEntity.origin + masterOrigin * bindMaster->renderEntity.axis; + } + } else if ( bindBody >= 0 && bindMaster->GetPhysics() ) { + masterOrigin = bindMaster->GetPhysics()->GetOrigin( bindBody ); + masterAxis = bindMaster->GetPhysics()->GetAxis( bindBody ); + } else { + masterOrigin = bindMaster->renderEntity.origin; + masterAxis = bindMaster->renderEntity.axis; + } + return true; + } else { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } +} + +/* +================ +idEntity::GetWorldVelocities +================ +*/ +void idEntity::GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const { + + linearVelocity = physics->GetLinearVelocity(); + angularVelocity = physics->GetAngularVelocity(); + + if ( bindMaster ) { + idVec3 masterOrigin, masterLinearVelocity, masterAngularVelocity; + idMat3 masterAxis; + + // get position of master + GetMasterPosition( masterOrigin, masterAxis ); + + // get master velocities + bindMaster->GetWorldVelocities( masterLinearVelocity, masterAngularVelocity ); + + // linear velocity relative to master plus master linear and angular velocity + linearVelocity = linearVelocity * masterAxis + masterLinearVelocity + + masterAngularVelocity.Cross( GetPhysics()->GetOrigin() - masterOrigin ); + } +} + +/* +================ +idEntity::JoinTeam +================ +*/ +void idEntity::JoinTeam( idEntity *teammember ) { + idEntity *ent; + idEntity *master; + idEntity *prev; + idEntity *next; + + // if we're already on a team, quit it so we can join this one + if ( teamMaster && ( teamMaster != this ) ) { + QuitTeam(); + } + + assert( teammember ); + + if ( teammember == this ) { + teamMaster = this; + return; + } + + // check if our new team mate is already on a team + master = teammember->teamMaster; + if ( !master ) { + // he's not on a team, so he's the new teamMaster + master = teammember; + teammember->teamMaster = teammember; + teammember->teamChain = this; + + // make anyone who's bound to me part of the new team + for( ent = teamChain; ent != NULL; ent = ent->teamChain ) { + ent->teamMaster = master; + } + } else { + // skip past the chain members bound to the entity we're teaming up with + prev = teammember; + next = teammember->teamChain; + if ( bindMaster ) { + // if we have a bindMaster, join after any entities bound to the entity + // we're joining + while( next && next->IsBoundTo( teammember ) ) { + prev = next; + next = next->teamChain; + } + } else { + // if we're not bound to someone, then put us at the end of the team + while( next ) { + prev = next; + next = next->teamChain; + } + } + + // make anyone who's bound to me part of the new team and + // also find the last member of my team + for( ent = this; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamChain->teamMaster = master; + } + + prev->teamChain = this; + ent->teamChain = next; + } + + teamMaster = master; + + // reorder the active entity list + gameLocal.sortTeamMasters = true; +} + +/* +================ +idEntity::QuitTeam +================ +*/ +void idEntity::QuitTeam() { + idEntity *ent; + + if ( !teamMaster ) { + return; + } + + // check if I'm the teamMaster + if ( teamMaster == this ) { + // do we have more than one teammate? + if ( !teamChain->teamChain ) { + // no, break up the team + teamChain->teamMaster = NULL; + } else { + // yes, so make the first teammate the teamMaster + for( ent = teamChain; ent; ent = ent->teamChain ) { + ent->teamMaster = teamChain; + } + } + } else { + assert( teamMaster ); + assert( teamMaster->teamChain ); + + // find the previous member of the teamChain + ent = teamMaster; + while( ent->teamChain != this ) { + assert( ent->teamChain ); // this should never happen + ent = ent->teamChain; + } + + // remove this from the teamChain + ent->teamChain = teamChain; + + // if no one is left on the team, break it up + if ( !teamMaster->teamChain ) { + teamMaster->teamMaster = NULL; + } + } + + teamMaster = NULL; + teamChain = NULL; +} + +/*********************************************************************** + + Physics. + +***********************************************************************/ + +/* +================ +idEntity::InitDefaultPhysics +================ +*/ +void idEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { + if ( idClipModel::CheckModel( temp ) ) { + clipModel = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( temp ); + } + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on entity '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on entity '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); + } else { + trm.SetupBox( bounds ); + } + clipModel = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ); + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { + if ( idClipModel::CheckModel( temp ) ) { + clipModel = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( temp ); + } + } + } + } + + defaultPhysicsObj.SetSelf( this ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; +} + +/* +================ +idEntity::SetPhysics +================ +*/ +void idEntity::SetPhysics( idPhysics *phys ) { + // clear any contacts the current physics object has + if ( physics ) { + physics->ClearContacts(); + } + // set new physics object or set the default physics if NULL + if ( phys != NULL ) { + defaultPhysicsObj.SetClipModel( NULL, 1.0f ); + physics = phys; + physics->Activate(); + } else { + physics = &defaultPhysicsObj; + } + physics->UpdateTime( gameLocal.time ); + physics->SetMaster( bindMaster, fl.bindOrientated ); +} + +/* +================ +idEntity::RestorePhysics +================ +*/ +void idEntity::RestorePhysics( idPhysics *phys ) { + assert( phys != NULL ); + // restore physics pointer + physics = phys; +} + +/* +================ +idEntity::GetPhysics +================ +*/ +idPhysics *idEntity::GetPhysics() const { + return physics; +} + +/* +================ +idEntity::RunPhysics +================ +*/ +bool idEntity::RunPhysics() { + int i, reachedTime; + idEntity * part = NULL, *blockedPart = NULL, *blockingEntity = NULL; + trace_t results; + bool moved; + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } + return false; + } + + // if this entity is a team slave don't do anything because the team master will handle everything + if ( teamMaster && teamMaster != this ) { + return false; + } + + const int startTime = gameLocal.previousTime; + const int endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->DisableClip(); + } + part->physics->SaveState(); + } + } + + // move the whole team + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + // run physics + moved = part->physics->Evaluate( GetPhysicsTimeStep(), endTime ); + + // check if the object is blocked + blockingEntity = part->physics->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->EnableClip(); + } + } + } + + // if one of the team entities is a pusher and blocked + if ( blockedPart ) { + // move the parts back to the previous position + for ( part = this; part != blockedPart; part = part->teamChain ) { + + if ( part->physics ) { + + // restore the physics state + part->physics->RestoreState(); + + // move back the visual position and orientation + part->UpdateFromPhysics( true ); + } + } + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + // update the physics time without moving + part->physics->UpdateTime( endTime ); + } + } + + // restore the positions of any pushed entities + gameLocal.push.RestorePushedEntityPositions(); + + if ( common->IsClient() ) { + return false; + } + + // if the master pusher has a "blocked" function, call it + Signal( SIG_BLOCKED ); + ProcessEvent( &EV_TeamBlocked, blockedPart, blockingEntity ); + // call the blocked function on the blocked part + blockedPart->ProcessEvent( &EV_PartBlocked, blockingEntity ); + return false; + } + + // This is to hack around an issue when reloading a game saved on certain movers would + // eject the player randomly through the world due to the first couple of frames imparting + // large pushVelocities. + const bool useAbnormalVelocityHack = ( idStr::Cmp( name, "houndola" ) == 0 ); + + // Disable motion blur if this object pushes the local player + renderEntity.skipMotionBlur = false; + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + if ( ent->physics->IsType( idPhysics_Player::Type ) ) { + if ( gameLocal.GetLocalClientNum() == ent->entityNumber ) { + renderEntity.skipMotionBlur = true; + } + if ( useAbnormalVelocityHack ) { + idPhysics_Player * physics = static_cast< idPhysics_Player * >( ent->physics ); + physics->SetPushedWithAbnormalVelocityHack( GetPhysicsTimeStep() ); + } else { + ent->physics->SetPushed( endTime - startTime ); + } + } else { + ent->physics->SetPushed( endTime - startTime ); + } + } + + // Propogate skipMotionBlur to all team members + for ( part = this; part != NULL; part = part->teamChain ) { + part->renderEntity.skipMotionBlur = renderEntity.skipMotionBlur; + } + + if ( common->IsClient() ) { + return true; + } + + // post reached event if the current time is at or past the end point of the motion + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + reachedTime = part->physics->GetLinearEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedPos ); + } + reachedTime = part->physics->GetAngularEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedAng ); + } + } + } + + return true; +} + +/* +================ +idEntity::InterpolatePhysics +================ +*/ +void idEntity::InterpolatePhysics( const float fraction ) { + + int i, startTime, endTime; + idEntity * part = NULL, *blockedPart = NULL, *blockingEntity = NULL; + trace_t results; + bool moved; + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } + return; + } + // if this entity is a team slave, we still need to interpolate it's current position from the snapshot. + // The team master probably depends on the current physics state, and may be unaware of prev/next or interpolation. + if ( teamMaster && teamMaster != this ) { + if ( physics != NULL && useClientInterpolation ) { + if ( physics->Interpolate( fraction ) ) { + UpdateFromPhysics( false ); + } + } + return; + } + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->DisableClip(); + } + part->physics->SaveState(); + } + } + + // move the whole team + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + // run physics + moved = part->physics->Evaluate( GetPhysicsTimeStep(), endTime ); + + // check if the object is blocked + blockingEntity = part->physics->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->EnableClip(); + } + } + } + + // if one of the team entities is a pusher and blocked + if ( blockedPart ) { + // move the parts back to the previous position + for ( part = this; part != blockedPart; part = part->teamChain ) { + + if ( part->physics ) { + + // restore the physics state + part->physics->RestoreState(); + + // move back the visual position and orientation + part->UpdateFromPhysics( true ); + } + } + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + // update the physics time without moving + part->physics->UpdateTime( endTime ); + } + } + + // restore the positions of any pushed entities + gameLocal.push.RestorePushedEntityPositions(); + } + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + ent->physics->SetPushed( GetPhysicsTimeStep() ); + } + + if ( physics && useClientInterpolation ) { + if ( physics->Interpolate( fraction ) ) { + UpdateFromPhysics( false ); + } + } +} + +/* +================ +idEntity::InterpolatePhysicsOnly +================ +*/ +void idEntity::InterpolatePhysicsOnly( const float fraction, bool updateTeam ) { + if ( physics && useClientInterpolation ) { + if ( physics->Interpolate( fraction ) ) { + UpdateFromPhysics( false ); + } + } + + if( updateTeam ) { + + int endTime = gameLocal.time; + + // move the whole team + for ( idEntity* part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics && part != this ) { + + // run physics + bool moved = part->physics->Evaluate( GetPhysicsTimeStep(), endTime ); + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + } +} + +/* +================ +idEntity::UpdateFromPhysics +================ +*/ +void idEntity::UpdateFromPhysics( bool moveBack ) { + + if ( IsType( idActor::Type ) ) { + idActor *actor = static_cast( this ); + + // set master delta angles for actors + if ( GetBindMaster() ) { + idAngles delta = actor->GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= static_cast(physics)->GetMasterDeltaYaw(); + } else { + delta.yaw += static_cast(physics)->GetMasterDeltaYaw(); + } + actor->SetDeltaViewAngles( delta ); + } + } + + UpdateVisuals(); +} + +/* +================ +idEntity::GetPhysicsTimeStep +================ +*/ +int idEntity::GetPhysicsTimeStep() const { + return gameLocal.time - gameLocal.previousTime; +} + +/* +================ +idEntity::SetOrigin +================ +*/ +void idEntity::SetOrigin( const idVec3 &org ) { + + GetPhysics()->SetOrigin( org ); + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAxis +================ +*/ +void idEntity::SetAxis( const idMat3 &axis ) { + + if ( GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + static_cast(this)->viewAxis = axis; + } else { + GetPhysics()->SetAxis( axis ); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAngles +================ +*/ +void idEntity::SetAngles( const idAngles &ang ) { + SetAxis( ang.ToMat3() ); +} + +/* +================ +idEntity::GetFloorPos +================ +*/ +bool idEntity::GetFloorPos( float max_dist, idVec3 &floorpos ) const { + trace_t result; + + if ( !GetPhysics()->HasGroundContacts() ) { + GetPhysics()->ClipTranslation( result, GetPhysics()->GetGravityNormal() * max_dist, NULL ); + if ( result.fraction < 1.0f ) { + floorpos = result.endpos; + return true; + } else { + floorpos = GetPhysics()->GetOrigin(); + return false; + } + } else { + floorpos = GetPhysics()->GetOrigin(); + return true; + } +} + +/* +================ +idEntity::GetPhysicsToVisualTransform +================ +*/ +bool idEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +idEntity::GetPhysicsToSoundTransform +================ +*/ +bool idEntity::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + // by default play the sound at the center of the bounding box of the first clip model + if ( GetPhysics()->GetNumClipModels() > 0 ) { + origin = GetPhysics()->GetBounds().GetCenter(); + axis.Identity(); + return true; + } + return false; +} + +/* +================ +idEntity::Collide +================ +*/ +bool idEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + // this entity collides with collision.c.entityNum + return false; +} + +/* +================ +idEntity::GetImpactInfo +================ +*/ +void idEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +idEntity::ApplyImpulse +================ +*/ +void idEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +idEntity::AddForce +================ +*/ +void idEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +idEntity::ActivatePhysics +================ +*/ +void idEntity::ActivatePhysics( idEntity *ent ) { + GetPhysics()->Activate(); +} + +/* +================ +idEntity::IsAtRest +================ +*/ +bool idEntity::IsAtRest() const { + return GetPhysics()->IsAtRest(); +} + +/* +================ +idEntity::GetRestStartTime +================ +*/ +int idEntity::GetRestStartTime() const { + return GetPhysics()->GetRestStartTime(); +} + +/* +================ +idEntity::AddContactEntity +================ +*/ +void idEntity::AddContactEntity( idEntity *ent ) { + GetPhysics()->AddContactEntity( ent ); +} + +/* +================ +idEntity::RemoveContactEntity +================ +*/ +void idEntity::RemoveContactEntity( idEntity *ent ) { + GetPhysics()->RemoveContactEntity( ent ); +} + + + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idEntity::CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +bool idEntity::CanDamage( const idVec3 &origin, idVec3 &damagePoint ) const { + idVec3 dest; + trace_t tr; + idVec3 midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin at 0,0,0 + midpoint = ( GetPhysics()->GetAbsBounds()[0] + GetPhysics()->GetAbsBounds()[1] ) * 0.5; + + dest = midpoint; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + // this should probably check in the plane of projection, rather than in world coordinate + dest = midpoint; + dest[0] += 15.0; + dest[1] += 15.0; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] += 15.0; + dest[1] -= 15.0; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] += 15.0; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] -= 15.0; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] += 15.0; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] -= 15.0; + gameLocal.clip.TracePoint( tr, origin, dest, MASK_SOLID, NULL ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + return false; +} + +/* +================ +idEntity::DamageFeedback + +callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller. +================ +*/ +void idEntity::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + // implemented in subclasses +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +============ +*/ +void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + + SetTimeState ts( timeGroup ); + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef == NULL ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + return; + } + + int damage = damageDef->GetInt( "damage" ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage ) { + // do the damage + health -= damage; + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } +} + +/* +================ +idEntity::AddDamageEffect +================ +*/ +void idEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) { + const char *sound, *decal, *key; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); + if ( def == NULL ) { + return; + } + + const char *materialType = gameLocal.sufaceTypeNames[ collision.c.material->GetSurfaceType() ]; + + // start impact sound based on material type + key = va( "snd_%s", materialType ); + sound = spawnArgs.GetString( key ); + if ( *sound == '\0' ) { + sound = def->dict.GetString( key ); + } + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + if ( g_decals.GetBool() ) { + // place a wound overlay on the model + key = va( "mtr_wound_%s", materialType ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *decal == '\0' ) { + decal = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *decal != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + } + } +} + +/* +============ +idEntity::Pain + +Called whenever an entity recieves damage. Returns whether the entity responds to the pain. +This is a virtual function that subclasses are expected to implement. +============ +*/ +bool idEntity::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return false; +} + +/* +============ +idEntity::Killed + +Called whenever an entity's health is reduced to 0 or less. +This is a virtual function that subclasses are expected to implement. +============ +*/ +void idEntity::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +} + + +/*********************************************************************** + + Script functions + +***********************************************************************/ + +/* +================ +idEntity::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idEntity::ShouldConstructScriptObjectAtSpawn() const { + return true; +} + +/* +================ +idEntity::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idEntity::ConstructScriptObject() { + idThread *thread; + const function_t *constructor; + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called + thread = new idThread(); + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, constructor, true ); + thread->DelayedStart( 0 ); + } else { + thread = NULL; + } + + // clear out the object's memory + scriptObject.ClearObject(); + + return thread; +} + +/* +================ +idEntity::DeconstructScriptObject + +Called during idEntity::~idEntity. Calls the destructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +Not called during idGameLocal::MapShutdown. +================ +*/ +void idEntity::DeconstructScriptObject() { + idThread *thread; + const function_t *destructor; + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and be destroyed + thread = new idThread(); + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, destructor, true ); + thread->Execute(); + delete thread; + } +} + +/* +================ +idEntity::HasSignal +================ +*/ +bool idEntity::HasSignal( signalNum_t signalnum ) const { + if ( !signals ) { + return false; + } + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + return ( signals->signal[ signalnum ].Num() > 0 ); +} + +/* +================ +idEntity::SetSignal +================ +*/ +void idEntity::SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ) { + int i; + int num; + signal_t sig; + int threadnum; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + signals = new (TAG_ENTITY) signalList_t; + } + + assert( thread ); + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ][ i ].function = function; + return; + } + } + + if ( num >= MAX_SIGNAL_THREADS ) { + thread->Error( "Exceeded maximum number of signals per object" ); + } + + sig.threadnum = threadnum; + sig.function = function; + signals->signal[ signalnum ].Append( sig ); +} + +/* +================ +idEntity::ClearSignal +================ +*/ +void idEntity::ClearSignal( idThread *thread, signalNum_t signalnum ) { + assert( thread ); + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + return; + } + + if ( signals == NULL ) { + return; + } + + signals->signal[ signalnum ].Clear(); +} + +/* +================ +idEntity::ClearSignalThread +================ +*/ +void idEntity::ClearSignalThread( signalNum_t signalnum, idThread *thread ) { + int i; + int num; + int threadnum; + + assert( thread ); + + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + return; + } + + if ( signals == NULL ) { + return; + } + + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ].RemoveIndex( i ); + return; + } + } +} + +/* +================ +idEntity::Signal +================ +*/ +void idEntity::Signal( signalNum_t signalnum ) { + int i; + int num; + signal_t sigs[ MAX_SIGNAL_THREADS ]; + idThread *thread; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + return; + } + + // we copy the signal list since each thread has the potential + // to end any of the threads in the list. By copying the list + // we don't have to worry about the list changing as we're + // processing it. + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + sigs[ i ] = signals->signal[ signalnum ][ i ]; + } + + // clear out the signal list so that we don't get into an infinite loop + signals->signal[ signalnum ].Clear(); + + for( i = 0; i < num; i++ ) { + thread = idThread::GetThread( sigs[ i ].threadnum ); + if ( thread ) { + thread->CallFunction( this, sigs[ i ].function, true ); + thread->Execute(); + } + } +} + +/* +================ +idEntity::SignalEvent +================ +*/ +void idEntity::SignalEvent( idThread *thread, signalNum_t signalnum ) { + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + Signal( signalnum ); +} + +/*********************************************************************** + + Guis. + +***********************************************************************/ + + +/* +================ +idEntity::TriggerGuis +================ +*/ +void idEntity::TriggerGuis() { + int i; + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->Trigger( gameLocal.time ); + } + } +} + +/* +================ +idEntity::HandleGuiCommands +================ +*/ +bool idEntity::HandleGuiCommands( idEntity *entityGui, const char *cmds ) { + idEntity *targetEnt; + bool ret = false; + if ( entityGui && cmds && *cmds ) { + idLexer src; + idToken token, token2, token3, token4; + src.LoadMemory( cmds, strlen( cmds ), "guiCommands" ); + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + return ret; + } + + if ( token == ";" ) { + continue; + } + + if ( token.Icmp( "activate" ) == 0 ) { + bool targets = true; + if ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + } else { + targets = false; + } + } + + if ( targets ) { + entityGui->ActivateTargets( this ); + } else { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + } + } + + entityGui->renderEntity.shaderParms[ SHADERPARM_MODE ] = 1.0f; + continue; + } + + + if ( token.Icmp( "runScript" ) == 0 ) { + if ( src.ReadToken( &token2 ) ) { + while( src.CheckTokenString( "::" ) ) { + idToken token3; + if ( !src.ReadToken( &token3 ) ) { + gameLocal.Error( "Expecting function name following '::' in gui for entity '%s'", entityGui->name.c_str() ); + } + token2 += "::" + token3; + } + const function_t *func = gameLocal.program.FindFunction( token2 ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' for gui in entity '%s'", token2.c_str(), entityGui->name.c_str() ); + } else { + idThread *thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + continue; + } + + if ( token.Icmp("play") == 0 ) { + if ( src.ReadToken( &token2 ) ) { + const idSoundShader *shader = declManager->FindSound(token2); + entityGui->StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + continue; + } + + if ( token.Icmp( "setkeyval" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) && src.ReadToken( &token4 ) ) { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->spawnArgs.Set( token3, token4 ); + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + } + } + continue; + } + + if ( token.Icmp( "setshaderparm" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) ) { + entityGui->SetShaderParm( atoi( token2 ), atof( token3 ) ); + entityGui->UpdateVisuals(); + } + continue; + } + + if ( token.Icmp("close") == 0 ) { + ret = true; + continue; + } + + if ( !token.Icmp( "turkeyscore" ) ) { + if ( src.ReadToken( &token2 ) && entityGui->renderEntity.gui[0] ) { + int score = entityGui->renderEntity.gui[0]->State().GetInt( "score" ); + score += atoi( token2 ); + entityGui->renderEntity.gui[0]->SetStateInt( "score", score ); + if ( gameLocal.GetLocalPlayer() && score >= 25000 ) { + gameLocal.GetLocalPlayer()->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_SCORE_25000_TURKEY_PUNCHER ); + gameLocal.GetLocalPlayer()->GiveEmail( static_cast( declManager->FindType( DECL_EMAIL, "highScore", false ) ) ); + } + } + continue; + } + + + if ( !token.Icmp( "martianbuddycomplete" ) ) { + gameLocal.GetLocalPlayer()->GiveEmail( static_cast( declManager->FindType( DECL_EMAIL, "MartianBuddyGameComplete", false ) ) ); + continue; + } + + + + // handy for debugging GUI stuff + if ( !token.Icmp( "print" ) ) { + idStr msg; + while ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + break; + } + msg += token2.c_str(); + } + common->Printf( "ent gui 0x%x '%s': %s\n", entityNumber, name.c_str(), msg.c_str() ); + continue; + } + + // if we get to this point we don't know how to handle it + src.UnreadToken(&token); + if ( !HandleSingleGuiCommand( entityGui, &src ) ) { + // not handled there see if entity or any of its targets can handle it + // this will only work for one target atm + if ( entityGui->HandleSingleGuiCommand( entityGui, &src ) ) { + continue; + } + + int c = entityGui->targets.Num(); + int i; + for ( i = 0; i < c; i++) { + targetEnt = entityGui->targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->HandleSingleGuiCommand( entityGui, &src ) ) { + break; + } + } + + if ( i == c ) { + // not handled + common->DPrintf( "idEntity::HandleGuiCommands: '%s' not handled\n", token.c_str() ); + src.ReadToken( &token ); + } + } + + } + } + return ret; +} + +/* +================ +idEntity::HandleSingleGuiCommand +================ +*/ +bool idEntity::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + return false; +} + +/*********************************************************************** + + Targets + +***********************************************************************/ + +/* +=============== +idEntity::FindTargets + +We have to wait until all entities are spawned +Used to build lists of targets after the entity is spawned. Since not all entities +have been spawned when the entity is created at map load time, we have to wait +=============== +*/ +void idEntity::FindTargets() { + int i; + + // targets can be a list of multiple names + gameLocal.GetTargets( spawnArgs, targets, "target" ); + + // ensure that we don't target ourselves since that could cause an infinite loop when activating entities + for( i = 0; i < targets.Num(); i++ ) { + if ( targets[ i ].GetEntity() == this ) { + gameLocal.Error( "Entity '%s' is targeting itself", name.c_str() ); + } + } +} + +/* +================ +idEntity::RemoveNullTargets +================ +*/ +void idEntity::RemoveNullTargets() { + int i; + + for( i = targets.Num() - 1; i >= 0; i-- ) { + if ( !targets[ i ].GetEntity() ) { + targets.RemoveIndex( i ); + } + } +} + +/* +============================== +idEntity::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idEntity::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->renderEntity.gui[ j ] ) { + ent->renderEntity.gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +/*********************************************************************** + + Misc. + +***********************************************************************/ + +/* +================ +idEntity::Teleport +================ +*/ +void idEntity::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( angles.ToMat3() ); + + UpdateVisuals(); +} + +/* +============ +idEntity::TouchTriggers + + Activate all trigger entities touched at the current position. +============ +*/ +bool idEntity::TouchTriggers() const { + int i, numClipModels, numEntities; + idClipModel * cm; + idClipModel * clipModels[ MAX_GENTITIES ]; + idEntity * ent; + trace_t trace; + + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = GetPhysics()->GetOrigin(); + trace.endAxis = GetPhysics()->GetAxis(); + + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, clipModels, MAX_GENTITIES ); + numEntities = 0; + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + // don't touch it if we're the owner + if ( cm->GetOwner() == this ) { + continue; + } + + ent = cm->GetEntity(); + + if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) { + continue; + } + + if ( !GetPhysics()->ClipContents( cm ) ) { + continue; + } + + SetTimeState ts( ent->timeGroup ); + + numEntities++; + + trace.c.contents = cm->GetContents(); + trace.c.entityNum = cm->GetEntity()->entityNumber; + trace.c.id = cm->GetId(); + + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, this, &trace ); + + if ( !gameLocal.entities[ entityNumber ] ) { + gameLocal.Printf( "entity was removed while touching triggers\n" ); + return true; + } + } + + return ( numEntities != 0 ); +} + +/* +================ +idEntity::GetSpline +================ +*/ +idCurve_Spline *idEntity::GetSpline() const { + int i, numPoints, t; + const idKeyValue *kv; + idLexer lex; + idVec3 v; + idCurve_Spline *spline; + const char *curveTag = "curve_"; + + kv = spawnArgs.MatchPrefix( curveTag ); + if ( !kv ) { + return NULL; + } + + idStr str = kv->GetKey().Right( kv->GetKey().Length() - strlen( curveTag ) ); + if ( str.Icmp( "CatmullRomSpline" ) == 0 ) { + spline = new (TAG_ENTITY) idCurve_CatmullRomSpline(); + } else if ( str.Icmp( "nubs" ) == 0 ) { + spline = new (TAG_ENTITY) idCurve_NonUniformBSpline(); + } else if ( str.Icmp( "nurbs" ) == 0 ) { + spline = new (TAG_ENTITY) idCurve_NURBS(); + } else { + spline = new (TAG_ENTITY) idCurve_BSpline(); + } + + spline->SetBoundaryType( idCurve_Spline::BT_CLAMPED ); + + lex.LoadMemory( kv->GetValue(), kv->GetValue().Length(), curveTag ); + numPoints = lex.ParseInt(); + lex.ExpectTokenString( "(" ); + for ( t = i = 0; i < numPoints; i++, t += 100 ) { + v.x = lex.ParseFloat(); + v.y = lex.ParseFloat(); + v.z = lex.ParseFloat(); + spline->AddValue( t, v ); + } + lex.ExpectTokenString( ")" ); + + return spline; +} + +/* +=============== +idEntity::ShowEditingDialog +=============== +*/ +void idEntity::ShowEditingDialog() { +} + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +================ +idEntity::Event_GetName +================ +*/ +void idEntity::Event_GetName() { + idThread::ReturnString( name.c_str() ); +} + +/* +================ +idEntity::Event_SetName +================ +*/ +void idEntity::Event_SetName( const char *newname ) { + SetName( newname ); +} + +/* +=============== +idEntity::Event_FindTargets +=============== +*/ +void idEntity::Event_FindTargets() { + FindTargets(); +} + +/* +============ +idEntity::Event_ActivateTargets + +Activates any entities targeted by this entity. Mainly used as an +event to delay activating targets. +============ +*/ +void idEntity::Event_ActivateTargets( idEntity *activator ) { + ActivateTargets( activator ); +} + +/* +================ +idEntity::Event_NumTargets +================ +*/ +void idEntity::Event_NumTargets() { + idThread::ReturnFloat( targets.Num() ); +} + +/* +================ +idEntity::Event_GetTarget +================ +*/ +void idEntity::Event_GetTarget( float index ) { + int i; + + i = ( int )index; + if ( ( i < 0 ) || i >= targets.Num() ) { + idThread::ReturnEntity( NULL ); + } else { + idThread::ReturnEntity( targets[ i ].GetEntity() ); + } +} + +/* +================ +idEntity::Event_RandomTarget +================ +*/ +void idEntity::Event_RandomTarget( const char *ignore ) { + int num; + idEntity *ent; + int i; + int ignoreNum; + + RemoveNullTargets(); + if ( !targets.Num() ) { + idThread::ReturnEntity( NULL ); + return; + } + + ignoreNum = -1; + if ( ignore && ( ignore[ 0 ] != 0 ) && ( targets.Num() > 1 ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ( ent->name == ignore ) ) { + ignoreNum = i; + break; + } + } + } + + if ( ignoreNum >= 0 ) { + num = gameLocal.random.RandomInt( targets.Num() - 1 ); + if ( num >= ignoreNum ) { + num++; + } + } else { + num = gameLocal.random.RandomInt( targets.Num() ); + } + + ent = targets[ num ].GetEntity(); + idThread::ReturnEntity( ent ); +} + +/* +================ +idEntity::Event_BindToJoint +================ +*/ +void idEntity::Event_BindToJoint( idEntity *master, const char *jointname, float orientated ) { + BindToJoint( master, jointname, ( orientated != 0.0f ) ); +} + +/* +================ +idEntity::Event_RemoveBinds +================ +*/ +void idEntity::Event_RemoveBinds() { + RemoveBinds(); +} + +/* +================ +idEntity::Event_Bind +================ +*/ +void idEntity::Event_Bind( idEntity *master ) { + Bind( master, true ); +} + +/* +================ +idEntity::Event_BindPosition +================ +*/ +void idEntity::Event_BindPosition( idEntity *master ) { + Bind( master, false ); +} + +/* +================ +idEntity::Event_Unbind +================ +*/ +void idEntity::Event_Unbind() { + Unbind(); +} + +/* +================ +idEntity::Event_SpawnBind +================ +*/ +void idEntity::Event_SpawnBind() { + idEntity *parent; + const char *bind, *joint, *bindanim; + jointHandle_t bindJoint; + bool bindOrientated; + int id; + const idAnim *anim; + int animNum; + idAnimator *parentAnimator; + + if ( spawnArgs.GetString( "bind", "", &bind ) ) { + if ( idStr::Icmp( bind, "worldspawn" ) == 0 ) { + //FIXME: Completely unneccessary since the worldspawn is called "world" + parent = gameLocal.world; + } else { + parent = gameLocal.FindEntity( bind ); + } + bindOrientated = spawnArgs.GetBool( "bindOrientated", "1" ); + if ( parent ) { + // bind to a joint of the skeletal model of the parent + if ( spawnArgs.GetString( "bindToJoint", "", &joint ) && *joint ) { + parentAnimator = parent->GetAnimator(); + if ( parentAnimator == NULL ) { + gameLocal.Error( "Cannot bind to joint '%s' on '%s'. Entity does not support skeletal models.", joint, name.c_str() ); + return; + } + bindJoint = parentAnimator->GetJointHandle( joint ); + if ( bindJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for bind on '%s'", joint, name.c_str() ); + } + + // bind it relative to a specific anim + if ( ( parent->spawnArgs.GetString( "bindanim", "", &bindanim ) || parent->spawnArgs.GetString( "anim", "", &bindanim ) ) && *bindanim ) { + animNum = parentAnimator->GetAnim( bindanim ); + if ( !animNum ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + anim = parentAnimator->GetAnim( animNum ); + if ( !anim ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + + // make sure parent's render origin has been set + parent->UpdateModelTransform(); + + //FIXME: need a BindToJoint that accepts a joint position + parentAnimator->CreateFrame( gameLocal.time, true ); + idJointMat *frame = parent->renderEntity.joints; + gameEdit->ANIM_CreateAnimFrame( parentAnimator->ModelHandle(), anim->MD5Anim( 0 ), parent->renderEntity.numJoints, frame, 0, parentAnimator->ModelDef()->GetVisualOffset(), parentAnimator->RemoveOrigin() ); + BindToJoint( parent, joint, bindOrientated ); + parentAnimator->ForceUpdate(); + } else { + BindToJoint( parent, joint, bindOrientated ); + } + } + // bind to a body of the physics object of the parent + else if ( spawnArgs.GetInt( "bindToBody", "0", id ) ) { + BindToBody( parent, id, bindOrientated ); + } + // bind to the parent + else { + Bind( parent, bindOrientated ); + } + } + } +} + +/* +================ +idEntity::Event_SetOwner +================ +*/ +void idEntity::Event_SetOwner( idEntity *owner ) { + int i; + + for ( i = 0; i < GetPhysics()->GetNumClipModels(); i++ ) { + GetPhysics()->GetClipModel( i )->SetOwner( owner ); + } +} + +/* +================ +idEntity::Event_SetModel +================ +*/ +void idEntity::Event_SetModel( const char *modelname ) { + SetModel( modelname ); +} + +/* +================ +idEntity::Event_SetSkin +================ +*/ +void idEntity::Event_SetSkin( const char *skinname ) { + renderEntity.customSkin = declManager->FindSkin( skinname ); + UpdateVisuals(); +} + +/* +================ +idEntity::Event_GetShaderParm +================ +*/ +void idEntity::Event_GetShaderParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + return; + } + + idThread::ReturnFloat( renderEntity.shaderParms[ parmnum ] ); +} + +/* +================ +idEntity::Event_SetShaderParm +================ +*/ +void idEntity::Event_SetShaderParm( int parmnum, float value ) { + SetShaderParm( parmnum, value ); +} + +/* +================ +idEntity::Event_SetShaderParms +================ +*/ +void idEntity::Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + UpdateVisuals(); +} + + +/* +================ +idEntity::Event_SetColor +================ +*/ +void idEntity::Event_SetColor( float red, float green, float blue ) { + SetColor( red, green, blue ); +} + +/* +================ +idEntity::Event_GetColor +================ +*/ +void idEntity::Event_GetColor() { + idVec3 out; + + GetColor( out ); + idThread::ReturnVector( out ); +} + +/* +================ +idEntity::Event_IsHidden +================ +*/ +void idEntity::Event_IsHidden() { + idThread::ReturnInt( fl.hidden ); +} + +/* +================ +idEntity::Event_Hide +================ +*/ +void idEntity::Event_Hide() { + Hide(); +} + +/* +================ +idEntity::Event_Show +================ +*/ +void idEntity::Event_Show() { + Show(); +} + +/* +================ +idEntity::Event_CacheSoundShader +================ +*/ +void idEntity::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idEntity::Event_StartSoundShader +================ +*/ +void idEntity::Event_StartSoundShader( const char *soundName, int channel ) { + int length = 0; + if ( soundName == NULL || soundName[0] == 0 ) { + StopSound( channel, false ); + } else { + StartSoundShader( declManager->FindSound( soundName ), (s_channelType)channel, 0, false, &length ); + } + idThread::ReturnFloat( MS2SEC( length ) ); +} + +/* +================ +idEntity::Event_StopSound +================ +*/ +void idEntity::Event_StopSound( int channel, int netSync ) { + StopSound( channel, ( netSync != 0 ) ); +} + +/* +================ +idEntity::Event_StartSound +================ +*/ +void idEntity::Event_StartSound( const char *soundName, int channel, int netSync ) { + int time; + + StartSound( soundName, ( s_channelType )channel, 0, ( netSync != 0 ), &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +/* +================ +idEntity::Event_FadeSound +================ +*/ +void idEntity::Event_FadeSound( int channel, float to, float over ) { + if ( refSound.referenceSound ) { + refSound.referenceSound->FadeSound( channel, to, over ); + } +} + +/* +================ +idEntity::Event_GetWorldOrigin +================ +*/ +void idEntity::Event_GetWorldOrigin() { + idThread::ReturnVector( GetPhysics()->GetOrigin() ); +} + +/* +================ +idEntity::Event_SetWorldOrigin +================ +*/ +void idEntity::Event_SetWorldOrigin( idVec3 const &org ) { + idVec3 neworg = GetLocalCoordinates( org ); + SetOrigin( neworg ); +} + +/* +================ +idEntity::Event_SetOrigin +================ +*/ +void idEntity::Event_SetOrigin( idVec3 const &org ) { + SetOrigin( org ); +} + +/* +================ +idEntity::Event_GetOrigin +================ +*/ +void idEntity::Event_GetOrigin() { + idThread::ReturnVector( GetLocalCoordinates( GetPhysics()->GetOrigin() ) ); +} + +/* +================ +idEntity::Event_SetAngles +================ +*/ +void idEntity::Event_SetAngles( idAngles const &ang ) { + SetAngles( ang ); +} + +/* +================ +idEntity::Event_GetAngles +================ +*/ +void idEntity::Event_GetAngles() { + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +/* +================ +idEntity::Event_SetLinearVelocity +================ +*/ +void idEntity::Event_SetLinearVelocity( const idVec3 &velocity ) { + GetPhysics()->SetLinearVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetLinearVelocity +================ +*/ +void idEntity::Event_GetLinearVelocity() { + idThread::ReturnVector( GetPhysics()->GetLinearVelocity() ); +} + +/* +================ +idEntity::Event_SetAngularVelocity +================ +*/ +void idEntity::Event_SetAngularVelocity( const idVec3 &velocity ) { + GetPhysics()->SetAngularVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetAngularVelocity +================ +*/ +void idEntity::Event_GetAngularVelocity() { + idThread::ReturnVector( GetPhysics()->GetAngularVelocity() ); +} + +/* +================ +idEntity::Event_SetSize +================ +*/ +void idEntity::Event_SetSize( idVec3 const &mins, idVec3 const &maxs ) { + GetPhysics()->SetClipBox( idBounds( mins, maxs ), 1.0f ); +} + +/* +================ +idEntity::Event_GetSize +================ +*/ +void idEntity::Event_GetSize() { + idBounds bounds; + + bounds = GetPhysics()->GetBounds(); + idThread::ReturnVector( bounds[1] - bounds[0] ); +} + +/* +================ +idEntity::Event_GetMins +================ +*/ +void idEntity::Event_GetMins() { + idThread::ReturnVector( GetPhysics()->GetBounds()[0] ); +} + +/* +================ +idEntity::Event_GetMaxs +================ +*/ +void idEntity::Event_GetMaxs() { + idThread::ReturnVector( GetPhysics()->GetBounds()[1] ); +} + +/* +================ +idEntity::Event_Touches +================ +*/ +void idEntity::Event_Touches( idEntity *ent ) { + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + const idBounds &myBounds = GetPhysics()->GetAbsBounds(); + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + idThread::ReturnInt( myBounds.IntersectsBounds( entBounds ) ); +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiParm( const char *key, const char *val ) { + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) { + spawnArgs.Set( key, val ); + } + renderEntity.gui[ i ]->SetStateString( key, val ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiFloat( const char *key, float f ) { + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( key, va( "%f", f ) ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_GetNextKey +================ +*/ +void idEntity::Event_GetNextKey( const char *prefix, const char *lastMatch ) { + const idKeyValue *kv; + const idKeyValue *previous; + + if ( *lastMatch ) { + previous = spawnArgs.FindKey( lastMatch ); + } else { + previous = NULL; + } + + kv = spawnArgs.MatchPrefix( prefix, previous ); + if ( !kv ) { + idThread::ReturnString( "" ); + } else { + idThread::ReturnString( kv->GetKey() ); + } +} + +/* +================ +idEntity::Event_SetKey +================ +*/ +void idEntity::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); + UpdateChangeableSpawnArgs( NULL ); +} + +/* +================ +idEntity::Event_GetKey +================ +*/ +void idEntity::Event_GetKey( const char *key ) { + const char *value; + + spawnArgs.GetString( key, "", &value ); + idThread::ReturnString( value ); +} + +/* +================ +idEntity::Event_GetIntKey +================ +*/ +void idEntity::Event_GetIntKey( const char *key ) { + int value; + + spawnArgs.GetInt( key, "0", value ); + + // scripts only support floats + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetFloatKey +================ +*/ +void idEntity::Event_GetFloatKey( const char *key ) { + float value; + + spawnArgs.GetFloat( key, "0", value ); + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetVectorKey +================ +*/ +void idEntity::Event_GetVectorKey( const char *key ) { + idVec3 value; + + spawnArgs.GetVector( key, "0 0 0", value ); + idThread::ReturnVector( value ); +} + +/* +================ +idEntity::Event_GetEntityKey +================ +*/ +void idEntity::Event_GetEntityKey( const char *key ) { + idEntity *ent; + const char *entname; + + if ( !spawnArgs.GetString( key, NULL, &entname ) ) { + idThread::ReturnEntity( NULL ); + return; + } + + ent = gameLocal.FindEntity( entname ); + if ( !ent ) { + gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); + } + + idThread::ReturnEntity( ent ); +} + +/* +================ +idEntity::Event_RestorePosition +================ +*/ +void idEntity::Event_RestorePosition() { + idVec3 org; + idAngles angles; + idMat3 axis; + idEntity * part; + + spawnArgs.GetVector( "origin", "0 0 0", org ); + + // get the rotation matrix in either full form, or single angle form + if ( spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { + angles = axis.ToAngles(); + } else { + angles[ 0 ] = 0; + angles[ 1 ] = spawnArgs.GetFloat( "angle" ); + angles[ 2 ] = 0; + } + + Teleport( org, angles, NULL ); + + for ( part = teamChain; part != NULL; part = part->teamChain ) { + if ( part->bindMaster != this ) { + continue; + } + if ( part->GetPhysics()->IsType( idPhysics_Parametric::Type ) ) { + if ( static_cast(part->GetPhysics())->IsPusher() ) { + gameLocal.Warning( "teleported '%s' which has the pushing mover '%s' bound to it\n", GetName(), part->GetName() ); + } + } else if ( part->GetPhysics()->IsType( idPhysics_AF::Type ) ) { + gameLocal.Warning( "teleported '%s' which has the articulated figure '%s' bound to it\n", GetName(), part->GetName() ); + } + } +} + +/* +================ +idEntity::Event_UpdateCameraTarget +================ +*/ +void idEntity::Event_UpdateCameraTarget() { + const char *target; + const idKeyValue *kv; + idVec3 dir; + + target = spawnArgs.GetString( "cameraTarget" ); + + cameraTarget = gameLocal.FindEntity( target ); + + if ( cameraTarget != NULL ) { + kv = cameraTarget->spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent != NULL && idStr::Icmp( ent->GetEntityDefName(), "target_null" ) == 0) { + dir = ent->GetPhysics()->GetOrigin() - cameraTarget->GetPhysics()->GetOrigin(); + dir.Normalize(); + cameraTarget->SetAxis( dir.ToMat3() ); + SetAxis(dir.ToMat3()); + break; + } + kv = cameraTarget->spawnArgs.MatchPrefix( "target", kv ); + } + } + UpdateVisuals(); +} + +/* +================ +idEntity::Event_DistanceTo +================ +*/ +void idEntity::Event_DistanceTo( idEntity *ent ) { + if ( !ent ) { + // just say it's really far away + idThread::ReturnFloat( MAX_WORLD_SIZE ); + } else { + float dist = ( GetPhysics()->GetOrigin() - ent->GetPhysics()->GetOrigin() ).LengthFast(); + idThread::ReturnFloat( dist ); + } +} + +/* +================ +idEntity::Event_DistanceToPoint +================ +*/ +void idEntity::Event_DistanceToPoint( const idVec3 &point ) { + float dist = ( GetPhysics()->GetOrigin() - point ).LengthFast(); + idThread::ReturnFloat( dist ); +} + +/* +================ +idEntity::Event_StartFx +================ +*/ +void idEntity::Event_StartFx( const char *fx ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); +} + +/* +================ +idEntity::Event_WaitFrame +================ +*/ +void idEntity::Event_WaitFrame() { + idThread *thread; + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->WaitFrame(); + } +} + +/* +===================== +idEntity::Event_Wait +===================== +*/ +void idEntity::Event_Wait( float time ) { + idThread *thread = idThread::CurrentThread(); + + if ( thread == NULL ) { + gameLocal.Error( "Event 'wait' called from outside thread" ); + return; + } + + thread->WaitSec( time ); +} + +/* +===================== +idEntity::Event_HasFunction +===================== +*/ +void idEntity::Event_HasFunction( const char *name ) { + const function_t *func; + + func = scriptObject.GetFunction( name ); + if ( func ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idEntity::Event_CallFunction +===================== +*/ +void idEntity::Event_CallFunction( const char *funcname ) { + const function_t *func; + idThread *thread; + + thread = idThread::CurrentThread(); + if ( thread == NULL ) { + gameLocal.Error( "Event 'callFunction' called from outside thread" ); + return; + } + + func = scriptObject.GetFunction( funcname ); + if ( func == NULL ) { + gameLocal.Error( "Unknown function '%s' in '%s'", funcname, scriptObject.GetTypeName() ); + return; + } + + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function '%s' has the wrong number of parameters for 'callFunction'", funcname ); + return; + } + if ( !scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function '%s' is the wrong type for 'callFunction'", funcname ); + return; + } + + // function args will be invalid after this call + thread->CallFunction( this, func, false ); +} + +/* +================ +idEntity::Event_SetNeverDormant +================ +*/ +void idEntity::Event_SetNeverDormant( int enable ) { + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + +/* +================ +idEntity::Event_SetGui +================ +* BSM Nerve: Allows guis to be changed at runtime. Guis that are +* loaded after the level loads should be precahced using PrecacheGui. +*/ +void idEntity::Event_SetGui( int guiNum, const char *guiName) { + idUserInterface** gui = NULL; + + if ( guiNum >= 1 && guiNum <= MAX_RENDERENTITY_GUI ) { + gui = &renderEntity.gui[ guiNum-1 ]; + } + + if( gui ) { + *gui = uiManager->FindGui( guiName, true, false ); + UpdateGuiParms( *gui, &spawnArgs ); + UpdateChangeableSpawnArgs( NULL ); + gameRenderWorld->UpdateEntityDef(modelDefHandle, &renderEntity); + + } else { + gameLocal.Error( "Entity '%s' doesn't have a GUI %d", name.c_str(), guiNum ); + } + +} + +/* +================ +idEntity::Event_PrecacheGui +================ +* BSM Nerve: Forces the engine to initialize a gui even if it is not specified as used in a level. +* This is useful for preventing load hitches when switching guis during the game using "setGui" +*/ +void idEntity::Event_PrecacheGui( const char *guiName ) { + uiManager->FindGui( guiName, true, true ); +} + +void idEntity::Event_GetGuiParm(int guiNum, const char *key) { + if(renderEntity.gui[guiNum-1]) { + idThread::ReturnString(renderEntity.gui[guiNum-1]->GetStateString(key)); + return; + } + idThread::ReturnString(""); +} + +void idEntity::Event_GetGuiParmFloat(int guiNum, const char *key) { + if(renderEntity.gui[guiNum-1]) { + idThread::ReturnFloat(renderEntity.gui[guiNum-1]->GetStateFloat(key)); + return; + } + idThread::ReturnFloat(0.0f); +} + +void idEntity::Event_GuiNamedEvent(int guiNum, const char *event) { + if(renderEntity.gui[guiNum-1]) { + renderEntity.gui[guiNum-1]->HandleNamedEvent(event); + } +} + + +/*********************************************************************** + + Network + +***********************************************************************/ + +/* +================ +idEntity::ClientThink +================ +*/ +void idEntity::ClientThink( const int curTime, const float fraction, const bool predict ) { + InterpolatePhysics( fraction ); + Present(); +} + +/* +================ +idEntity::ClientPredictionThink +================ +*/ +void idEntity::ClientPredictionThink() { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::WriteBindToSnapshot +================ +*/ +void idEntity::WriteBindToSnapshot( idBitMsg &msg ) const { + int bindInfo; + + if ( bindMaster ) { + bindInfo = bindMaster->entityNumber; + bindInfo |= ( fl.bindOrientated & 1 ) << GENTITYNUM_BITS; + if ( bindJoint != INVALID_JOINT ) { + bindInfo |= 1 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindJoint << ( 3 + GENTITYNUM_BITS ); + } else if ( bindBody != -1 ) { + bindInfo |= 2 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindBody << ( 3 + GENTITYNUM_BITS ); + } + } else { + bindInfo = ENTITYNUM_NONE; + } + msg.WriteBits( bindInfo, GENTITYNUM_BITS + 3 + 9 ); +} + +/* +================ +idEntity::ReadBindFromSnapshot +================ +*/ +void idEntity::ReadBindFromSnapshot( const idBitMsg &msg ) { + int bindInfo, bindEntityNum, bindPos; + bool bindOrientated; + idEntity *master; + + bindInfo = msg.ReadBits( GENTITYNUM_BITS + 3 + 9 ); + bindEntityNum = bindInfo & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + + if ( ( bindEntityNum != ENTITYNUM_NONE ) && ( bindEntityNum < MAX_GENTITIES ) ) { + master = gameLocal.entities[ bindEntityNum ]; + + bindOrientated = ( bindInfo >> GENTITYNUM_BITS ) & 1; + bindPos = ( bindInfo >> ( GENTITYNUM_BITS + 3 ) ); + switch( ( bindInfo >> ( GENTITYNUM_BITS + 1 ) ) & 3 ) { + case 1: { + BindToJoint( master, (jointHandle_t) bindPos, bindOrientated ); + break; + } + case 2: { + BindToBody( master, bindPos, bindOrientated ); + break; + } + default: { + Bind( master, bindOrientated ); + break; + } + } + } else if ( bindMaster ) { + Unbind(); + } +} + +/* +================ +idEntity::WriteColorToSnapshot +================ +*/ +void idEntity::WriteColorToSnapshot( idBitMsg &msg ) const { + idVec4 color; + + color[0] = renderEntity.shaderParms[ SHADERPARM_RED ]; + color[1] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + color[2] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + color[3] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; + msg.WriteLong( PackColor( color ) ); +} + +/* +================ +idEntity::ReadColorFromSnapshot +================ +*/ +void idEntity::ReadColorFromSnapshot( const idBitMsg &msg ) { + idVec4 color; + + UnpackColor( msg.ReadLong(), color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[3]; +} + +/* +================ +idEntity::WriteGUIToSnapshot +================ +*/ +void idEntity::WriteGUIToSnapshot( idBitMsg &msg ) const { + // no need to loop over MAX_RENDERENTITY_GUI at this time + if ( renderEntity.gui[ 0 ] ) { + msg.WriteByte( renderEntity.gui[ 0 ]->State().GetInt( "networkState" ) ); + } else { + msg.WriteByte( 0 ); + } +} + +/* +================ +idEntity::ReadGUIFromSnapshot +================ +*/ +void idEntity::ReadGUIFromSnapshot( const idBitMsg &msg ) { + int state; + idUserInterface *gui; + state = msg.ReadByte( ); + gui = renderEntity.gui[ 0 ]; + if ( gui && state != mpGUIState ) { + mpGUIState = state; + gui->SetStateInt( "networkState", state ); + gui->HandleNamedEvent( "networkState" ); + } +} + +/* +================ +idEntity::WriteToSnapshot +================ +*/ +void idEntity::WriteToSnapshot( idBitMsg &msg ) const { +} + +/* +================ +idEntity::ReadFromSnapshot +================ +*/ +void idEntity::ReadFromSnapshot( const idBitMsg &msg ) { +} + +/* +================ +idEntity::ReadFromSnapshot_Ex +Increments the snapshot counter for the entity. +================ +*/ +void idEntity::ReadFromSnapshot_Ex( const idBitMsg &msg ) { + snapshotsReceived += 1; + ReadFromSnapshot( msg ); +} + +/* +================ +idEntity::FlagNewSnapshot +Updates the interpolationBehavior so that subclasses will know if it's safe to interpolate. +Only call this when a new snapshot has been received for this entity! +================ +*/ +void idEntity::FlagNewSnapshot() { + switch( interpolationBehavior ) { + case USE_NO_INTERPOLATION: { + interpolationBehavior = USE_LATEST_SNAP_ONLY; + break; + } + case USE_LATEST_SNAP_ONLY: { + interpolationBehavior = USE_INTERPOLATION; + break; + } + default: { + break; + } + } +} + +/* +================ +idEntity::ServerSendEvent + + Saved events are also sent to any client that connects late so all clients + always receive the events nomatter what time they join the game. +================ +*/ +void idEntity::ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, lobbyUserID_t excluding ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !common->IsServer() ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetReadData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + peerMask_t peerMask = MAX_UNSIGNED_TYPE( peerMask_t ); + if ( excluding.IsValid() ) { + peerMask = ~(peerMask_t)lobby.PeerIndexFromLobbyUser( excluding ); + } + lobby.SendReliable( GAME_RELIABLE_MESSAGE_EVENT, outMsg, false, peerMask ); + + if ( saveEvent ) { + gameLocal.SaveEntityNetworkEvent( this, eventId, msg ); + } +} + +/* +================ +idEntity::ClientSendEvent +================ +*/ +void idEntity::ClientSendEvent( int eventId, const idBitMsg *msg ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !common->IsClient() ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.serverTime ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetReadData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_EVENT, outMsg ); +} + +/* +================ +idEntity::ServerReceiveEvent +================ +*/ +bool idEntity::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case 0: { + } + default: { + return false; + } + } +} + +/* +================ +idEntity::ClientReceiveEvent +================ +*/ +bool idEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int index; + const idSoundShader *shader; + s_channelType channel; + + switch( event ) { + case EVENT_STARTSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + if ( time < gameLocal.realClientTime - 1000 ) { + // too old, skip it ( reliable messages don't need to be parsed in full ) + common->DPrintf( "ent 0x%x: start sound shader too old (%d ms)\n", entityNumber, gameLocal.realClientTime - time ); + return true; + } + index = gameLocal.ClientRemapDecl( DECL_SOUND, msg.ReadLong() ); + if ( index >= 0 && index < declManager->GetNumDecls( DECL_SOUND ) ) { + shader = declManager->SoundByIndex( index, false ); + channel = (s_channelType)msg.ReadByte(); + StartSoundShader( shader, channel, 0, false, NULL ); + } + return true; + } + case EVENT_STOPSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + channel = (s_channelType)msg.ReadByte(); + StopSound( channel, false ); + return true; + } + default: { + return false; + } + } +} + +/* +================ +idEntity::DetermineTimeGroup +================ +*/ +void idEntity::DetermineTimeGroup( bool slowmo ) { + if ( slowmo || common->IsMultiplayer() ) { + timeGroup = TIME_GROUP1; + } + else { + timeGroup = TIME_GROUP2; + } +} + +/* +================ +idEntity::SetGrabbedState +================ +*/ +void idEntity::SetGrabbedState( bool grabbed ) { + fl.grabbed = grabbed; +} + +/* +================ +idEntity::IsGrabbed +================ +*/ +bool idEntity::IsGrabbed() { + return fl.grabbed; +} + +/* +======================== +idEntity::DecayOriginAndAxisDelta +======================== +*/ +void idEntity::DecayOriginAndAxisDelta() { + idVec3 delta = vec3_zero - originDelta; + float length = delta.Length(); + + if ( length > 0.01f ) { + length *= net_errorSmoothingDecay.GetFloat(); + if ( length > net_errorSmoothingMaxDecay.GetFloat() ) { + length = net_errorSmoothingMaxDecay.GetFloat(); + } + delta.Normalize(); + delta *= length; + + originDelta += delta; + } else { + originDelta = vec3_zero; + } + + idQuat q; + q.Slerp( axisDelta.ToQuat(), mat3_identity.ToQuat(), net_errorSmoothingDecay.GetFloat() ); + axisDelta = q.ToMat3(); +} + +/* +======================== +idEntity::CreateDeltasFromOldOriginAndAxis +======================== +*/ +void idEntity::CreateDeltasFromOldOriginAndAxis( const idVec3 & oldOrigin, const idMat3 & oldAxis ) { + // Set smooth values so we transition from the old position/axis to what we are now (visual only) + if ( GetPhysics() ) { + originDelta = oldOrigin - GetPhysics()->GetOrigin(); + axisDelta = oldAxis.Inverse() * GetPhysics()->GetAxis(); + } +} + +/* +=============================================================================== + + idAnimatedEntity + +=============================================================================== +*/ + +const idEventDef EV_GetJointHandle( "getJointHandle", "s", 'd' ); +const idEventDef EV_ClearAllJoints( "clearAllJoints" ); +const idEventDef EV_ClearJoint( "clearJoint", "d" ); +const idEventDef EV_SetJointPos( "setJointPos", "ddv" ); +const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" ); +const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' ); +const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' ); + +CLASS_DECLARATION( idEntity, idAnimatedEntity ) + EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle ) + EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints ) + EVENT( EV_ClearJoint, idAnimatedEntity::Event_ClearJoint ) + EVENT( EV_SetJointPos, idAnimatedEntity::Event_SetJointPos ) + EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle ) + EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos ) + EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle ) +END_CLASS + +/* +================ +idAnimatedEntity::idAnimatedEntity +================ +*/ +idAnimatedEntity::idAnimatedEntity() { + animator.SetEntity( this ); + damageEffects = NULL; +} + +/* +================ +idAnimatedEntity::~idAnimatedEntity +================ +*/ +idAnimatedEntity::~idAnimatedEntity() { + damageEffect_t *de; + + for ( de = damageEffects; de; de = damageEffects ) { + damageEffects = de->next; + delete de; + } +} + +/* +================ +idAnimatedEntity::Save + +archives object for save game file +================ +*/ +void idAnimatedEntity::Save( idSaveGame *savefile ) const { + animator.Save( savefile ); + + // Wounds are very temporary, ignored at this time + //damageEffect_t *damageEffects; +} + +/* +================ +idAnimatedEntity::Restore + +unarchives object from save game file +================ +*/ +void idAnimatedEntity::Restore( idRestoreGame *savefile ) { + animator.Restore( savefile ); + + // check if the entity has an MD5 model + if ( animator.ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( modelDefHandle != -1 ) { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + } +} + +/* +================ +idAnimatedEntity::ClientPredictionThink +================ +*/ +void idAnimatedEntity::ClientPredictionThink() { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::ClientThink +================ +*/ +void idAnimatedEntity::ClientThink( const int curTime, const float fraction, const bool predict ) { + InterpolatePhysics( fraction ); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::Think +================ +*/ +void idAnimatedEntity::Think() { + RunPhysics(); + UpdateAnimation(); + Present(); + UpdateDamageEffects(); +} + +/* +================ +idAnimatedEntity::UpdateAnimation +================ +*/ +void idAnimatedEntity::UpdateAnimation() { + // don't do animations if they're not enabled + if ( !( thinkFlags & TH_ANIMATE ) ) { + return; + } + + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + + // call any frame commands that have happened in the past frame + if ( !fl.hidden ) { + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() && !fl.hidden ) { + gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +idAnimatedEntity::GetAnimator +================ +*/ +idAnimator *idAnimatedEntity::GetAnimator() { + return &animator; +} + +/* +================ +idAnimatedEntity::SetModel +================ +*/ +void idAnimatedEntity::SetModel( const char *modelname ) { + FreeModelDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + idEntity::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + UpdateVisuals(); +} + +/* +===================== +idAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool idAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !animator.GetJointTransform( jointHandle, currentTime, offset, axis ) ) { + return false; + } + + ConvertLocalToWorldTransform( offset, axis ); + return true; +} + +/* +============== +idAnimatedEntity::GetJointTransformForAnim +============== +*/ +bool idAnimatedEntity::GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int frameTime, idVec3 &offset, idMat3 &axis ) const { + const idAnim *anim; + int numJoints; + idJointMat *frame; + + anim = animator.GetAnim( animNum ); + if ( !anim ) { + assert( 0 ); + return false; + } + + numJoints = animator.NumJoints(); + if ( ( jointHandle < 0 ) || ( jointHandle >= numJoints ) ) { + assert( 0 ); + return false; + } + + frame = ( idJointMat * )_alloca16( numJoints * sizeof( idJointMat ) ); + gameEdit->ANIM_CreateAnimFrame( animator.ModelHandle(), anim->MD5Anim( 0 ), renderEntity.numJoints, frame, frameTime, animator.ModelDef()->GetVisualOffset(), animator.RemoveOrigin() ); + + offset = frame[ jointHandle ].ToVec3(); + axis = frame[ jointHandle ].ToMat3(); + + return true; +} + +/* +============== +idAnimatedEntity::AddDamageEffect + + Dammage effects track the animating impact position, spitting out particles. +============== +*/ +void idAnimatedEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) { + jointHandle_t jointNum; + idVec3 origin, dir, localDir, localOrigin, localNormal; + idMat3 axis; + + if ( !g_bloodEffects.GetBool() || renderEntity.joints == NULL ) { + return; + } + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); + if ( def == NULL ) { + return; + } + + jointNum = CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ); + if ( jointNum == INVALID_JOINT ) { + return; + } + + dir = velocity; + dir.Normalize(); + + axis = renderEntity.joints[jointNum].ToMat3() * renderEntity.axis; + origin = renderEntity.origin + renderEntity.joints[jointNum].ToVec3() * renderEntity.axis; + + localOrigin = ( collision.c.point - origin ) * axis.Transpose(); + localNormal = collision.c.normal * axis.Transpose(); + localDir = dir * axis.Transpose(); + + AddLocalDamageEffect( jointNum, localOrigin, localNormal, localDir, def, collision.c.material ); +} + +/* +============== +idAnimatedEntity::GetDefaultSurfaceType +============== +*/ +int idAnimatedEntity::GetDefaultSurfaceType() const { + return SURFTYPE_METAL; +} + +/* +============== +idAnimatedEntity::AddLocalDamageEffect +============== +*/ +void idAnimatedEntity::AddLocalDamageEffect( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, const idDeclEntityDef *def, const idMaterial *collisionMaterial ) { + const char *sound, *splat, *decal, *bleed, *key; + damageEffect_t *de; + idVec3 origin, dir; + idMat3 axis; + + SetTimeState ts( timeGroup ); + + axis = renderEntity.joints[jointNum].ToMat3() * renderEntity.axis; + origin = renderEntity.origin + renderEntity.joints[jointNum].ToVec3() * renderEntity.axis; + + origin = origin + localOrigin * axis; + dir = localDir * axis; + + int type = collisionMaterial->GetSurfaceType(); + if ( type == SURFTYPE_NONE ) { + type = GetDefaultSurfaceType(); + } + + const char *materialType = gameLocal.sufaceTypeNames[ type ]; + + // start impact sound based on material type + key = va( "snd_%s", materialType ); + sound = spawnArgs.GetString( key ); + if ( *sound == '\0' ) { + sound = def->dict.GetString( key ); + } + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + // blood splats are thrown onto nearby surfaces + key = va( "mtr_splat_%s", materialType ); + splat = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *splat == '\0' ) { + splat = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *splat != '\0' ) { + gameLocal.BloodSplat( origin, dir, 64.0f, splat ); + } + + // can't see wounds on the player model in single player mode + if ( !( IsType( idPlayer::Type ) && !common->IsMultiplayer() ) ) { + // place a wound overlay on the model + key = va( "mtr_wound_%s", materialType ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *decal == '\0' ) { + decal = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *decal != '\0' ) { + ProjectOverlay( origin, dir, 20.0f, decal ); + } + } + + // a blood spurting wound is added + key = va( "smoke_wound_%s", materialType ); + bleed = spawnArgs.GetString( key ); + if ( *bleed == '\0' ) { + bleed = def->dict.GetString( key ); + } + if ( *bleed != '\0' ) { + de = new (TAG_ENTITY) damageEffect_t; + de->next = this->damageEffects; + this->damageEffects = de; + + de->jointNum = jointNum; + de->localOrigin = localOrigin; + de->localNormal = localNormal; + de->type = static_cast( declManager->FindType( DECL_PARTICLE, bleed ) ); + de->time = gameLocal.time; + } +} + +/* +============== +idAnimatedEntity::UpdateDamageEffects +============== +*/ +void idAnimatedEntity::UpdateDamageEffects() { + damageEffect_t *de, **prev; + + // free any that have timed out + prev = &this->damageEffects; + while ( *prev ) { + de = *prev; + if ( de->time == 0 ) { // FIXME:SMOKE + *prev = de->next; + delete de; + } else { + prev = &de->next; + } + } + + if ( !g_bloodEffects.GetBool() ) { + return; + } + + // emit a particle for each bleeding wound + for ( de = this->damageEffects; de; de = de->next ) { + idVec3 origin, start; + idMat3 axis; + + animator.GetJointTransform( de->jointNum, gameLocal.time, origin, axis ); + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + start = origin + de->localOrigin * axis; + if ( !gameLocal.smokeParticles->EmitSmoke( de->type, de->time, gameLocal.random.CRandomFloat(), start, axis, timeGroup /*_D3XP*/ ) ) { + de->time = 0; + } + } +} + +/* +================ +idAnimatedEntity::ClientReceiveEvent +================ +*/ +bool idAnimatedEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int damageDefIndex; + int materialIndex; + jointHandle_t jointNum; + idVec3 localOrigin, localNormal, localDir; + + switch( event ) { + case EVENT_ADD_DAMAGE_EFFECT: { + jointNum = (jointHandle_t) msg.ReadShort(); + localOrigin[0] = msg.ReadFloat(); + localOrigin[1] = msg.ReadFloat(); + localOrigin[2] = msg.ReadFloat(); + localNormal = msg.ReadDir( 24 ); + localDir = msg.ReadDir( 24 ); + damageDefIndex = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadLong() ); + materialIndex = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() ); + const idDeclEntityDef *damageDef = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex ) ); + const idMaterial *collisionMaterial = static_cast( declManager->DeclByIndex( DECL_MATERIAL, materialIndex ) ); + AddLocalDamageEffect( jointNum, localOrigin, localNormal, localDir, damageDef, collisionMaterial ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} + +/* +================ +idAnimatedEntity::Event_GetJointHandle + +looks up the number of the specified joint. returns INVALID_JOINT if the joint is not found. +================ +*/ +void idAnimatedEntity::Event_GetJointHandle( const char *jointname ) { + jointHandle_t joint; + + joint = animator.GetJointHandle( jointname ); + idThread::ReturnInt( joint ); +} + +/* +================ +idAnimatedEntity::Event_ClearAllJoints + +removes any custom transforms on all joints +================ +*/ +void idAnimatedEntity::Event_ClearAllJoints() { + animator.ClearAllJoints(); +} + +/* +================ +idAnimatedEntity::Event_ClearJoint + +removes any custom transforms on the specified joint +================ +*/ +void idAnimatedEntity::Event_ClearJoint( jointHandle_t jointnum ) { + animator.ClearJoint( jointnum ); +} + +/* +================ +idAnimatedEntity::Event_SetJointPos + +modifies the position of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { + animator.SetJointPos( jointnum, transform_type, pos ); +} + +/* +================ +idAnimatedEntity::Event_SetJointAngle + +modifies the orientation of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) { + idMat3 mat; + + mat = angles.ToMat3(); + animator.SetJointAxis( jointnum, transform_type, mat ); +} + +/* +================ +idAnimatedEntity::Event_GetJointPos + +returns the position of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idThread::ReturnVector( offset ); +} + +/* +================ +idAnimatedEntity::Event_GetJointAngle + +returns the orientation of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idAngles ang = axis.ToAngles(); + idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] ); + idThread::ReturnVector( vec ); +} diff --git a/neo/d3xp/Entity.h b/neo/d3xp/Entity.h new file mode 100644 index 00000000..8023339c --- /dev/null +++ b/neo/d3xp/Entity.h @@ -0,0 +1,711 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_ENTITY_H__ +#define __GAME_ENTITY_H__ + +/* +=============================================================================== + + Game entity base class. + +=============================================================================== +*/ + +static const int DELAY_DORMANT_TIME = 3000; + +extern const idEventDef EV_PostSpawn; +extern const idEventDef EV_FindTargets; +extern const idEventDef EV_Touch; +extern const idEventDef EV_Use; +extern const idEventDef EV_Activate; +extern const idEventDef EV_ActivateTargets; +extern const idEventDef EV_Hide; +extern const idEventDef EV_Show; +extern const idEventDef EV_GetShaderParm; +extern const idEventDef EV_SetShaderParm; +extern const idEventDef EV_SetOwner; +extern const idEventDef EV_GetAngles; +extern const idEventDef EV_SetAngles; +extern const idEventDef EV_SetLinearVelocity; +extern const idEventDef EV_SetAngularVelocity; +extern const idEventDef EV_SetSkin; +extern const idEventDef EV_StartSoundShader; +extern const idEventDef EV_StopSound; +extern const idEventDef EV_CacheSoundShader; + +// Think flags +enum { + TH_ALL = -1, + TH_THINK = 1, // run think function each frame + TH_PHYSICS = 2, // run physics each frame + TH_ANIMATE = 4, // update animation each frame + TH_UPDATEVISUALS = 8, // update renderEntity + TH_UPDATEPARTICLES = 16 +}; + +// +// Signals +// make sure to change script/doom_defs.script if you add any, or change their order +// +typedef enum { + SIG_TOUCH, // object was touched + SIG_USE, // object was used + SIG_TRIGGER, // object was activated + SIG_REMOVED, // object was removed from the game + SIG_DAMAGE, // object was damaged + SIG_BLOCKED, // object was blocked + + SIG_MOVER_POS1, // mover at position 1 (door closed) + SIG_MOVER_POS2, // mover at position 2 (door open) + SIG_MOVER_1TO2, // mover changing from position 1 to 2 + SIG_MOVER_2TO1, // mover changing from position 2 to 1 + + NUM_SIGNALS +} signalNum_t; + +// FIXME: At some point we may want to just limit it to one thread per signal, but +// for now, I'm allowing multiple threads. We should reevaluate this later in the project +#define MAX_SIGNAL_THREADS 16 // probably overkill, but idList uses a granularity of 16 + +struct signal_t { + int threadnum; + const function_t *function; +}; + +class signalList_t { +public: + idList signal[ NUM_SIGNALS ]; +}; + + +/* +================================================ +idNetEvent + +Utility for detecting a bool state change: +-server calls ::Set +-client ::Get will return true (once only) + +Useful because: +-Hides client from having to manually declare "last" state and manually checking against it +-using int counter prevents problems w/ dropped snapshots + +(ie if we just serialized a bool to true for a single ss, if that ss is dropped,skipped,whatever +the client would never handle it. By incrementing a wrapped counter, we are guaranteed to detect +the state change no matter what happens at the net layer). +================================================ +*/ +template < int max > +struct idNetEvent { + idNetEvent() : count(0), lastCount(0) { } + void Set() { count = ( ( count + 1 ) % max ); } + bool Get() { + if ( count != lastCount ) { + lastCount = count; + return true; + } + return false; + } + void Serialize( idSerializer &ser ) { + if ( count >= max ) { + idLib::Warning("idNetEvent. count %d > max %d", count, max ); + } + ser.SerializeUMax( count, max ); + } + +public: + static const int Maximum = max; + int count; + int lastCount; +}; + +typedef idNetEvent< 7 > netBoolEvent_t; + +inline void WriteToBitMsg( const netBoolEvent_t & netEvent, idBitMsg & msg ) { + msg.WriteBits( netEvent.count, idMath::BitsForInteger( netBoolEvent_t::Maximum ) ); + + assert( netEvent.count <= netBoolEvent_t::Maximum ); +} + +inline void ReadFromBitMsg( netBoolEvent_t & netEvent, const idBitMsg & msg ) { + netEvent.count = msg.ReadBits( idMath::BitsForInteger( netBoolEvent_t::Maximum ) ); + + assert( netEvent.count <= netBoolEvent_t::Maximum ); +} + + +class idEntity : public idClass { +public: + static const int MAX_PVS_AREAS = 4; + static const uint32 INVALID_PREDICTION_KEY = 0xFFFFFFFF; + + int entityNumber; // index into the entity list + int entityDefNumber; // index into the entity def list + + idLinkList spawnNode; // for being linked into spawnedEntities list + idLinkList activeNode; // for being linked into activeEntities list + idLinkList aimAssistNode; // linked into gameLocal.aimAssistEntities + + idLinkList snapshotNode; // for being linked into snapshotEntities list + int snapshotChanged; // used to detect snapshot state changes + int snapshotBits; // number of bits this entity occupied in the last snapshot + bool snapshotStale; // Set to true if this entity is considered stale in the snapshot + + idStr name; // name of entity + idDict spawnArgs; // key/value pairs used to spawn and initialize entity + idScriptObject scriptObject; // contains all script defined data for this entity + + int thinkFlags; // TH_? flags + int dormantStart; // time that the entity was first closed off from player + bool cinematic; // during cinematics, entity will only think if cinematic is set + + renderView_t * renderView; // for camera views from this entity + idEntity * cameraTarget; // any remoteRenderMap shaders will use this + + idList< idEntityPtr, TAG_ENTITY > targets; // when this entity is activated these entities entity are activated + + int health; // FIXME: do all objects really need health? + + struct entityFlags_s { + bool notarget :1; // if true never attack or target this entity + bool noknockback :1; // if true no knockback from hits + bool takedamage :1; // if true this entity can be damaged + bool hidden :1; // if true this entity is not visible + bool bindOrientated :1; // if true both the master orientation is used for binding + bool solidForTeam :1; // if true this entity is considered solid when a physics team mate pushes entities + bool forcePhysicsUpdate :1; // if true always update from the physics whether the object moved or not + bool selected :1; // if true the entity is selected for editing + bool neverDormant :1; // if true the entity never goes dormant + bool isDormant :1; // if true the entity is dormant + bool hasAwakened :1; // before a monster has been awakened the first time, use full PVS for dormant instead of area-connected + bool networkSync :1; // if true the entity is synchronized over the network + bool grabbed :1; // if true object is currently being grabbed + bool skipReplication :1; // don't replicate this entity over the network. + } fl; + + int timeGroup; + + bool noGrab; + + renderEntity_t xrayEntity; + qhandle_t xrayEntityHandle; + const idDeclSkin * xraySkin; + + void DetermineTimeGroup( bool slowmo ); + + void SetGrabbedState( bool grabbed ); + bool IsGrabbed(); + +public: + ABSTRACT_PROTOTYPE( idEntity ); + + idEntity(); + ~idEntity(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + const char * GetEntityDefName() const; + void SetName( const char *name ); + const char * GetName() const; + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + int GetEntityNumber() const { return entityNumber; } + + // clients generate views based on all the player specific options, + // cameras have custom code, and everything else just uses the axis orientation + virtual renderView_t * GetRenderView(); + + // thinking + virtual void Think(); + bool CheckDormant(); // dormant == on the active list, but out of PVS + virtual void DormantBegin(); // called when entity becomes dormant + virtual void DormantEnd(); // called when entity wakes from being dormant + bool IsActive() const; + void BecomeActive( int flags ); + void BecomeInactive( int flags ); + void UpdatePVSAreas( const idVec3 &pos ); + void BecomeReplicated(); + + // visuals + virtual void Present(); + virtual renderEntity_t *GetRenderEntity(); + virtual int GetModelDefHandle(); + virtual void SetModel( const char *modelname ); + void SetSkin( const idDeclSkin *skin ); + const idDeclSkin * GetSkin() const; + void SetShaderParm( int parmnum, float value ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec3 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec4 &out ) const; + virtual void FreeModelDef(); + virtual void FreeLightDef(); + virtual void Hide(); + virtual void Show(); + bool IsHidden() const; + void UpdateVisuals(); + void UpdateModel(); + void UpdateModelTransform(); + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + int GetNumPVSAreas(); + const int * GetPVSAreas(); + void ClearPVSAreas(); + bool PhysicsTeamInPVS( pvsHandle_t pvsHandle ); + + // animation + virtual bool UpdateAnimationControllers(); + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + virtual idAnimator * GetAnimator(); // returns animator object used by this entity + + // sound + virtual bool CanPlayChatterSounds() const; + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound( const s_channelType channel, bool broadcast ); // pass SND_CHANNEL_ANY to stop all sounds + void SetSoundVolume( float volume ); + void UpdateSound(); + int GetListenerId() const; + idSoundEmitter * GetSoundEmitter() const; + void FreeSoundEmitter( bool immediate ); + + // entity binding + virtual void PreBind(); + virtual void PostBind(); + virtual void PreUnbind(); + virtual void PostUnbind(); + void JoinTeam( idEntity *teammember ); + void Bind( idEntity *master, bool orientated ); + void BindToJoint( idEntity *master, const char *jointname, bool orientated ); + void BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ); + void BindToBody( idEntity *master, int bodyId, bool orientated ); + void Unbind(); + bool IsBound() const; + bool IsBoundTo( idEntity *master ) const; + idEntity * GetBindMaster() const; + jointHandle_t GetBindJoint() const; + int GetBindBody() const; + idEntity * GetTeamMaster() const; + idEntity * GetNextTeamEntity() const; + void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); + idVec3 GetLocalVector( const idVec3 &vec ) const; + idVec3 GetLocalCoordinates( const idVec3 &vec ) const; + idVec3 GetWorldVector( const idVec3 &vec ) const; + idVec3 GetWorldCoordinates( const idVec3 &vec ) const; + bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + void GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const; + + // physics + // set a new physics object to be used by this entity + void SetPhysics( idPhysics *phys ); + // get the physics object used by this entity + idPhysics * GetPhysics() const; + // restore physics pointer for save games + void RestorePhysics( idPhysics *phys ); + // run the physics for this entity + bool RunPhysics(); + // Interpolates the physics, used on MP clients. + void InterpolatePhysics( const float fraction ); + // InterpolatePhysics actually calls evaluate, this version doesn't. + void InterpolatePhysicsOnly( const float fraction, bool updateTeam = false ); + // set the origin of the physics object (relative to bindMaster if not NULL) + void SetOrigin( const idVec3 &org ); + // set the axis of the physics object (relative to bindMaster if not NULL) + void SetAxis( const idMat3 &axis ); + // use angles to set the axis of the physics object (relative to bindMaster if not NULL) + void SetAngles( const idAngles &ang ); + // get the floor position underneath the physics object + bool GetFloorPos( float max_dist, idVec3 &floorpos ) const; + // retrieves the transformation going from the physics origin/axis to the visual origin/axis + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + // retrieves the transformation going from the physics origin/axis to the sound origin/axis + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + // called from the physics object when colliding, should return true if the physics simulation should stop + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + // retrieves impact information, 'ent' is the entity retrieving the info + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + // apply an impulse to the physics object, 'ent' is the entity applying the impulse + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + // add a force to the physics object, 'ent' is the entity adding the force + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + // activate the physics object, 'ent' is the entity activating this entity + virtual void ActivatePhysics( idEntity *ent ); + // returns true if the physics object is at rest + virtual bool IsAtRest() const; + // returns the time the physics object came to rest + virtual int GetRestStartTime() const; + // add a contact entity + virtual void AddContactEntity( idEntity *ent ); + // remove a touching entity + virtual void RemoveContactEntity( idEntity *ent ); + + // damage + // returns true if this entity can be damaged from the given origin + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint ) const; + // applies damage to this entity + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + // adds a damage effect like overlays, blood, sparks, debris etc. + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + // callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller. + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + // notifies this entity that it is in pain + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + // notifies this entity that is has been killed + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // scripting + virtual bool ShouldConstructScriptObjectAtSpawn() const; + virtual idThread * ConstructScriptObject(); + virtual void DeconstructScriptObject(); + void SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ); + void ClearSignal( idThread *thread, signalNum_t signalnum ); + void ClearSignalThread( signalNum_t signalnum, idThread *thread ); + bool HasSignal( signalNum_t signalnum ) const; + void Signal( signalNum_t signalnum ); + void SignalEvent( idThread *thread, signalNum_t signalnum ); + + // gui + void TriggerGuis(); + bool HandleGuiCommands( idEntity *entityGui, const char *cmds ); + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + + // targets + void FindTargets(); + void RemoveNullTargets(); + void ActivateTargets( idEntity *activator ) const; + + // misc + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + bool TouchTriggers() const; + idCurve_Spline *GetSpline() const; + virtual void ShowEditingDialog(); + + enum { + EVENT_STARTSOUNDSHADER, + EVENT_STOPSOUNDSHADER, + EVENT_MAXEVENTS + }; + + // Called on clients in an MP game, does the actual interpolation for the entity. + // This function will eventually replace ClientPredictionThink completely. + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + + virtual void ClientPredictionThink(); + virtual void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot_Ex( const idBitMsg &msg ); + virtual void ReadFromSnapshot( const idBitMsg &msg ); + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + void WriteBindToSnapshot( idBitMsg &msg ) const; + void ReadBindFromSnapshot( const idBitMsg &msg ); + void WriteColorToSnapshot( idBitMsg &msg ) const; + void ReadColorFromSnapshot( const idBitMsg &msg ); + void WriteGUIToSnapshot( idBitMsg &msg ) const; + void ReadGUIFromSnapshot( const idBitMsg &msg ); + + void ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, lobbyUserID_t excluding = lobbyUserID_t() ) const; + void ClientSendEvent( int eventId, const idBitMsg *msg ) const; + + void SetUseClientInterpolation( bool use ) { useClientInterpolation = use; } + + void SetSkipReplication( const bool skip ) { fl.skipReplication = skip; } + bool GetSkipReplication() const { return fl.skipReplication; } + bool IsReplicated() const { return GetEntityNumber() < ENTITYNUM_FIRST_NON_REPLICATED; } + + void CreateDeltasFromOldOriginAndAxis( const idVec3 & oldOrigin, const idMat3 & oldAxis ); + void DecayOriginAndAxisDelta(); + uint32 GetPredictedKey() { return predictionKey; } + void SetPredictedKey( uint32 key_ ) { predictionKey = key_; } + + void FlagNewSnapshot(); + + idEntity* GetTeamChain() { return teamChain; } + + // It is only safe to interpolate if this entity has received two snapshots. + enum interpolationBehavior_t { + USE_NO_INTERPOLATION, + USE_LATEST_SNAP_ONLY, + USE_INTERPOLATION + }; + + interpolationBehavior_t GetInterpolationBehavior() const { return interpolationBehavior; } + unsigned int GetNumSnapshotsReceived() const { return snapshotsReceived; } + +protected: + renderEntity_t renderEntity; // used to present a model to the renderer + int modelDefHandle; // handle to static renderer model + refSound_t refSound; // used to present sound to the audio engine + + idVec3 GetOriginDelta() const { return originDelta; } + idMat3 GetAxisDelta() const { return axisDelta; } + +private: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics * physics; // physics used for this entity + idEntity * bindMaster; // entity bound to if unequal NULL + jointHandle_t bindJoint; // joint bound to if unequal INVALID_JOINT + int bindBody; // body bound to if unequal -1 + idEntity * teamMaster; // master of the physics team + idEntity * teamChain; // next entity in physics team + bool useClientInterpolation; // disables interpolation for some objects (handy for weapon world models) + int numPVSAreas; // number of renderer areas the entity covers + int PVSAreas[MAX_PVS_AREAS]; // numbers of the renderer areas the entity covers + + signalList_t * signals; + + int mpGUIState; // local cache to avoid systematic SetStateInt + + uint32 predictionKey; // Unique key used to sync predicted ents (projectiles) in MP. + + // Delta values that are set when the server or client disagree on where the render model should be. If this happens, + // they resolve it through DecayOriginAndAxisDelta() + idVec3 originDelta; + idMat3 axisDelta; + + interpolationBehavior_t interpolationBehavior; + unsigned int snapshotsReceived; + +private: + void FixupLocalizedStrings(); + + bool DoDormantTests(); // dormant == on the active list, but out of PVS + + // physics + // initialize the default physics + void InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ); + // update visual position from the physics + void UpdateFromPhysics( bool moveBack ); + // get physics timestep + virtual int GetPhysicsTimeStep() const; + + // entity binding + bool InitBind( idEntity *master ); // initialize an entity binding + void FinishBind(); // finish an entity binding + void RemoveBinds(); // deletes any entities bound to this object + void QuitTeam(); // leave the current team + + void UpdatePVSAreas(); + + // events + void Event_GetName(); + void Event_SetName( const char *name ); + void Event_FindTargets(); + void Event_ActivateTargets( idEntity *activator ); + void Event_NumTargets(); + void Event_GetTarget( float index ); + void Event_RandomTarget( const char *ignore ); + void Event_Bind( idEntity *master ); + void Event_BindPosition( idEntity *master ); + void Event_BindToJoint( idEntity *master, const char *jointname, float orientated ); + void Event_Unbind(); + void Event_RemoveBinds(); + void Event_SpawnBind(); + void Event_SetOwner( idEntity *owner ); + void Event_SetModel( const char *modelname ); + void Event_SetSkin( const char *skinname ); + void Event_GetShaderParm( int parmnum ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetColor( float red, float green, float blue ); + void Event_GetColor(); + void Event_IsHidden(); + void Event_Hide(); + void Event_Show(); + void Event_CacheSoundShader( const char *soundName ); + void Event_StartSoundShader( const char *soundName, int channel ); + void Event_StopSound( int channel, int netSync ); + void Event_StartSound( const char *soundName, int channel, int netSync ); + void Event_FadeSound( int channel, float to, float over ); + void Event_GetWorldOrigin(); + void Event_SetWorldOrigin( idVec3 const &org ); + void Event_GetOrigin(); + void Event_SetOrigin( const idVec3 &org ); + void Event_GetAngles(); + void Event_SetAngles( const idAngles &ang ); + void Event_SetLinearVelocity( const idVec3 &velocity ); + void Event_GetLinearVelocity(); + void Event_SetAngularVelocity( const idVec3 &velocity ); + void Event_GetAngularVelocity(); + void Event_SetSize( const idVec3 &mins, const idVec3 &maxs ); + void Event_GetSize(); + void Event_GetMins(); + void Event_GetMaxs(); + void Event_Touches( idEntity *ent ); + void Event_SetGuiParm( const char *key, const char *val ); + void Event_SetGuiFloat( const char *key, float f ); + void Event_GetNextKey( const char *prefix, const char *lastMatch ); + void Event_SetKey( const char *key, const char *value ); + void Event_GetKey( const char *key ); + void Event_GetIntKey( const char *key ); + void Event_GetFloatKey( const char *key ); + void Event_GetVectorKey( const char *key ); + void Event_GetEntityKey( const char *key ); + void Event_RestorePosition(); + void Event_UpdateCameraTarget(); + void Event_DistanceTo( idEntity *ent ); + void Event_DistanceToPoint( const idVec3 &point ); + void Event_StartFx( const char *fx ); + void Event_WaitFrame(); + void Event_Wait( float time ); + void Event_HasFunction( const char *name ); + void Event_CallFunction( const char *name ); + void Event_SetNeverDormant( int enable ); + void Event_SetGui( int guiNum, const char *guiName); + void Event_PrecacheGui( const char *guiName ); + void Event_GetGuiParm(int guiNum, const char *key); + void Event_GetGuiParmFloat(int guiNum, const char *key); + void Event_GuiNamedEvent(int guiNum, const char *event); +}; + +/* +=============================================================================== + + Animated entity base class. + +=============================================================================== +*/ + +typedef struct damageEffect_s { + jointHandle_t jointNum; + idVec3 localOrigin; + idVec3 localNormal; + int time; + const idDeclParticle* type; + struct damageEffect_s * next; +} damageEffect_t; + +class idAnimatedEntity : public idEntity { +public: + CLASS_PROTOTYPE( idAnimatedEntity ); + + idAnimatedEntity(); + ~idAnimatedEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void Think(); + + void UpdateAnimation(); + + virtual idAnimator * GetAnimator(); + virtual void SetModel( const char *modelname ); + + bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + bool GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int currentTime, idVec3 &offset, idMat3 &axis ) const; + + virtual int GetDefaultSurfaceType() const; + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + void AddLocalDamageEffect( jointHandle_t jointNum, const idVec3 &localPoint, const idVec3 &localNormal, const idVec3 &localDir, const idDeclEntityDef *def, const idMaterial *collisionMaterial ); + void UpdateDamageEffects(); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + enum { + EVENT_ADD_DAMAGE_EFFECT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + +protected: + idAnimator animator; + damageEffect_t * damageEffects; + +private: + void Event_GetJointHandle( const char *jointname ); + void Event_ClearAllJoints(); + void Event_ClearJoint( jointHandle_t jointnum ); + void Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); + void Event_GetJointPos( jointHandle_t jointnum ); + void Event_GetJointAngle( jointHandle_t jointnum ); +}; + + +class SetTimeState { + bool activated; + bool previousFast; + bool fast; + +public: + SetTimeState(); + SetTimeState( int timeGroup ); + ~SetTimeState(); + + void PushState( int timeGroup ); +}; + +ID_INLINE SetTimeState::SetTimeState() { + activated = false; +} + +ID_INLINE SetTimeState::SetTimeState( int timeGroup ) { + activated = false; + PushState( timeGroup ); +} + +ID_INLINE void SetTimeState::PushState( int timeGroup ) { + + // Don't mess with time in Multiplayer + if ( !common->IsMultiplayer() ) { + + activated = true; + + // determine previous fast setting + if ( gameLocal.time == gameLocal.slow.time ) { + previousFast = false; + } else { + previousFast = true; + } + + // determine new fast setting + if ( timeGroup ) { + fast = true; + } else { + fast = false; + } + + // set correct time + gameLocal.SelectTimeGroup( timeGroup ); + } +} + +ID_INLINE SetTimeState::~SetTimeState() { + if ( activated && !common->IsMultiplayer() ) { + // set previous correct time + gameLocal.SelectTimeGroup( previousFast ); + } +} + +#endif /* !__GAME_ENTITY_H__ */ diff --git a/neo/d3xp/Fx.cpp b/neo/d3xp/Fx.cpp new file mode 100644 index 00000000..f998265a --- /dev/null +++ b/neo/d3xp/Fx.cpp @@ -0,0 +1,833 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idEntityFx + +=============================================================================== +*/ + +const idEventDef EV_Fx_KillFx( "_killfx" ); +const idEventDef EV_Fx_Action( "_fxAction", "e" ); // implemented by subclasses + +CLASS_DECLARATION( idEntity, idEntityFx ) +EVENT( EV_Activate, idEntityFx::Event_Trigger ) +EVENT( EV_Fx_KillFx, idEntityFx::Event_ClearFx ) +END_CLASS + + +/* +================ +idEntityFx::Save +================ +*/ +void idEntityFx::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( started ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteFX( fxEffect ); + savefile->WriteString( systemName ); + + savefile->WriteInt( actions.Num() ); + + for ( i = 0; i < actions.Num(); i++ ) { + + if ( actions[i].lightDefHandle >= 0 ) { + savefile->WriteBool( true ); + savefile->WriteRenderLight( actions[i].renderLight ); + } else { + savefile->WriteBool( false ); + } + + if ( actions[i].modelDefHandle >= 0 ) { + savefile->WriteBool( true ); + savefile->WriteRenderEntity( actions[i].renderEntity ); + } else { + savefile->WriteBool( false ); + } + + savefile->WriteFloat( actions[i].delay ); + savefile->WriteInt( actions[i].start ); + savefile->WriteBool( actions[i].soundStarted ); + savefile->WriteBool( actions[i].shakeStarted ); + savefile->WriteBool( actions[i].decalDropped ); + savefile->WriteBool( actions[i].launched ); + } +} + +/* +================ +idEntityFx::Restore +================ +*/ +void idEntityFx::Restore( idRestoreGame *savefile ) { + int i; + int num; + bool hasObject; + + savefile->ReadInt( started ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadFX( fxEffect ); + savefile->ReadString( systemName ); + + savefile->ReadInt( num ); + + actions.SetNum( num ); + for ( i = 0; i < num; i++ ) { + + savefile->ReadBool( hasObject ); + if ( hasObject ) { + savefile->ReadRenderLight( actions[i].renderLight ); + actions[i].lightDefHandle = gameRenderWorld->AddLightDef( &actions[i].renderLight ); + } else { + memset( &actions[i].renderLight, 0, sizeof( renderLight_t ) ); + actions[i].lightDefHandle = -1; + } + + savefile->ReadBool( hasObject ); + if ( hasObject ) { + savefile->ReadRenderEntity( actions[i].renderEntity ); + actions[i].modelDefHandle = gameRenderWorld->AddEntityDef( &actions[i].renderEntity ); + } else { + memset( &actions[i].renderEntity, 0, sizeof( renderEntity_t ) ); + actions[i].modelDefHandle = -1; + } + + savefile->ReadFloat( actions[i].delay ); + + // let the FX regenerate the particleSystem + actions[i].particleSystem = -1; + + savefile->ReadInt( actions[i].start ); + savefile->ReadBool( actions[i].soundStarted ); + savefile->ReadBool( actions[i].shakeStarted ); + savefile->ReadBool( actions[i].decalDropped ); + savefile->ReadBool( actions[i].launched ); + } +} + +/* +================ +idEntityFx::Setup +================ +*/ +void idEntityFx::Setup( const char *fx ) { + + if ( started >= 0 ) { + return; // already started + } + + // early during MP Spawn() with no information. wait till we ReadFromSnapshot for more + if ( common->IsClient() && ( !fx || fx[0] == '\0' ) ) { + return; + } + + systemName = fx; + started = 0; + + fxEffect = static_cast( declManager->FindType( DECL_FX, systemName.c_str() ) ); + + if ( fxEffect ) { + idFXLocalAction localAction; + + memset( &localAction, 0, sizeof( idFXLocalAction ) ); + + actions.AssureSize( fxEffect->events.Num(), localAction ); + + for( int i = 0; ievents.Num(); i++ ) { + const idFXSingleAction& fxaction = fxEffect->events[i]; + + idFXLocalAction& laction = actions[i]; + if ( fxaction.random1 || fxaction.random2 ) { + laction.delay = fxaction.random1 + gameLocal.random.RandomFloat() * ( fxaction.random2 - fxaction.random1 ); + } else { + laction.delay = fxaction.delay; + } + laction.start = -1; + laction.lightDefHandle = -1; + laction.modelDefHandle = -1; + laction.particleSystem = -1; + laction.shakeStarted = false; + laction.decalDropped = false; + laction.launched = false; + } + } +} + +/* +================ +idEntityFx::EffectName +================ +*/ +const char *idEntityFx::EffectName() { + return fxEffect ? fxEffect->GetName() : NULL; +} + +/* +================ +idEntityFx::Joint +================ +*/ +const char *idEntityFx::Joint() { + return fxEffect ? fxEffect->joint.c_str() : NULL; +} + +/* +================ +idEntityFx::CleanUp +================ +*/ +void idEntityFx::CleanUp() { + if ( !fxEffect ) { + return; + } + for( int i = 0; i < fxEffect->events.Num(); i++ ) { + const idFXSingleAction& fxaction = fxEffect->events[i]; + idFXLocalAction& laction = actions[i]; + CleanUpSingleAction( fxaction, laction ); + } +} + +/* +================ +idEntityFx::CleanUpSingleAction +================ +*/ +void idEntityFx::CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ) { + if ( laction.lightDefHandle != -1 && fxaction.sibling == -1 && fxaction.type != FX_ATTACHLIGHT ) { + gameRenderWorld->FreeLightDef( laction.lightDefHandle ); + laction.lightDefHandle = -1; + } + if ( laction.modelDefHandle != -1 && fxaction.sibling == -1 && fxaction.type != FX_ATTACHENTITY ) { + gameRenderWorld->FreeEntityDef( laction.modelDefHandle ); + laction.modelDefHandle = -1; + } + laction.start = -1; +} + +/* +================ +idEntityFx::Start +================ +*/ +void idEntityFx::Start( int time ) { + if ( !fxEffect ) { + return; + } + started = time; + for( int i = 0; i < fxEffect->events.Num(); i++ ) { + idFXLocalAction& laction = actions[i]; + laction.start = time; + laction.soundStarted = false; + laction.shakeStarted = false; + laction.particleSystem = -1; + laction.decalDropped = false; + laction.launched = false; + } +} + +/* +================ +idEntityFx::Stop +================ +*/ +void idEntityFx::Stop() { + CleanUp(); + started = -1; +} + +/* +================ +idEntityFx::Duration +================ +*/ +const int idEntityFx::Duration() { + int max = 0; + + if ( !fxEffect ) { + return max; + } + for( int i = 0; i < fxEffect->events.Num(); i++ ) { + const idFXSingleAction& fxaction = fxEffect->events[i]; + int d = ( fxaction.delay + fxaction.duration ) * 1000.0f; + if ( d > max ) { + max = d; + } + } + + return max; +} + + +/* +================ +idEntityFx::Done +================ +*/ +const bool idEntityFx::Done() { + if (started > 0 && gameLocal.time > started + Duration()) { + return true; + } + return false; +} + +/* +================ +idEntityFx::ApplyFade +================ +*/ +void idEntityFx::ApplyFade( const idFXSingleAction& fxaction, idFXLocalAction& laction, const int time, const int actualStart ) { + if ( fxaction.fadeInTime || fxaction.fadeOutTime ) { + float fadePct = (float)( time - actualStart ) / ( 1000.0f * ( ( fxaction.fadeInTime != 0 ) ? fxaction.fadeInTime : fxaction.fadeOutTime ) ); + if (fadePct > 1.0) { + fadePct = 1.0; + } + if ( laction.modelDefHandle != -1 ) { + laction.renderEntity.shaderParms[SHADERPARM_RED] = (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct; + laction.renderEntity.shaderParms[SHADERPARM_GREEN] = (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct; + laction.renderEntity.shaderParms[SHADERPARM_BLUE] = (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct; + + gameRenderWorld->UpdateEntityDef( laction.modelDefHandle, &laction.renderEntity ); + } + if ( laction.lightDefHandle != -1 ) { + laction.renderLight.shaderParms[SHADERPARM_RED] = fxaction.lightColor.x * ( (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct ); + laction.renderLight.shaderParms[SHADERPARM_GREEN] = fxaction.lightColor.y * ( (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct ); + laction.renderLight.shaderParms[SHADERPARM_BLUE] = fxaction.lightColor.z * ( (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct ); + + gameRenderWorld->UpdateLightDef( laction.lightDefHandle, &laction.renderLight ); + } + } +} + +/* +================ +idEntityFx::Run +================ +*/ +void idEntityFx::Run( int time ) { + int ieff, j; + idEntity *ent = NULL; + const idDict *projectileDef = NULL; + idProjectile *projectile = NULL; + + if ( !fxEffect ) { + return; + } + + for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) { + const idFXSingleAction& fxaction = fxEffect->events[ieff]; + idFXLocalAction& laction = actions[ieff]; + + // + // if we're currently done with this one + // + if ( laction.start == -1 ) { + continue; + } + + // + // see if it's delayed + // + if ( laction.delay ) { + if ( laction.start + (time - laction.start) < laction.start + (laction.delay * 1000) ) { + continue; + } + } + + // + // each event can have it's own delay and restart + // + int actualStart = laction.delay ? laction.start + (int)( laction.delay * 1000 ) : laction.start; + float pct = (float)( time - actualStart ) / (1000 * fxaction.duration ); + if ( pct >= 1.0f ) { + laction.start = -1; + float totalDelay = 0.0f; + if ( fxaction.restart ) { + if ( fxaction.random1 || fxaction.random2 ) { + totalDelay = fxaction.random1 + gameLocal.random.RandomFloat() * (fxaction.random2 - fxaction.random1); + } else { + totalDelay = fxaction.delay; + } + laction.delay = totalDelay; + laction.start = time; + } + continue; + } + + if ( fxaction.fire.Length() ) { + for( j = 0; j < fxEffect->events.Num(); j++ ) { + if ( fxEffect->events[j].name.Icmp( fxaction.fire ) == 0 ) { + actions[j].delay = 0; + } + } + } + + idFXLocalAction *useAction; + if ( fxaction.sibling == -1 ) { + useAction = &laction; + } else { + useAction = &actions[fxaction.sibling]; + } + assert( useAction ); + + switch( fxaction.type ) { + case FX_ATTACHLIGHT: + case FX_LIGHT: { + if ( useAction->lightDefHandle == -1 ) { + if ( fxaction.type == FX_LIGHT ) { + memset( &useAction->renderLight, 0, sizeof( renderLight_t ) ); + useAction->renderLight.origin = GetPhysics()->GetOrigin() + fxaction.offset; + useAction->renderLight.axis = GetPhysics()->GetAxis(); + useAction->renderLight.lightRadius[0] = fxaction.lightRadius; + useAction->renderLight.lightRadius[1] = fxaction.lightRadius; + useAction->renderLight.lightRadius[2] = fxaction.lightRadius; + useAction->renderLight.shader = declManager->FindMaterial( fxaction.data, false ); + useAction->renderLight.shaderParms[ SHADERPARM_RED ] = fxaction.lightColor.x; + useAction->renderLight.shaderParms[ SHADERPARM_GREEN ] = fxaction.lightColor.y; + useAction->renderLight.shaderParms[ SHADERPARM_BLUE ] = fxaction.lightColor.z; + useAction->renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + useAction->renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); + useAction->renderLight.referenceSound = refSound.referenceSound; + useAction->renderLight.pointLight = true; + if ( fxaction.noshadows ) { + useAction->renderLight.noShadows = true; + } + useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight ); + } + if ( fxaction.noshadows ) { + for( j = 0; j < fxEffect->events.Num(); j++ ) { + idFXLocalAction& laction2 = actions[j]; + if ( laction2.modelDefHandle != -1 ) { + laction2.renderEntity.noShadow = true; + } + } + } + } + ApplyFade( fxaction, *useAction, time, actualStart ); + break; + } + case FX_SOUND: { + if ( !useAction->soundStarted ) { + useAction->soundStarted = true; + const idSoundShader *shader = declManager->FindSound(fxaction.data); + StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + for( j = 0; j < fxEffect->events.Num(); j++ ) { + idFXLocalAction& laction2 = actions[j]; + if ( laction2.lightDefHandle != -1 ) { + laction2.renderLight.referenceSound = refSound.referenceSound; + gameRenderWorld->UpdateLightDef( laction2.lightDefHandle, &laction2.renderLight ); + } + } + } + break; + } + case FX_DECAL: { + if ( !useAction->decalDropped ) { + useAction->decalDropped = true; + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 8.0f, true, fxaction.size, fxaction.data ); + } + break; + } + case FX_SHAKE: { + if ( !useAction->shakeStarted ) { + idDict args; + args.Clear(); + args.SetFloat( "kick_time", fxaction.shakeTime ); + args.SetFloat( "kick_amplitude", fxaction.shakeAmplitude ); + for ( j = 0; j < gameLocal.numClients; j++ ) { + idPlayer *player = gameLocal.GetClientByNum( j ); + if ( player && ( player->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr() < Square( fxaction.shakeDistance ) ) { + if ( !common->IsMultiplayer() || !fxaction.shakeIgnoreMaster || GetBindMaster() != player ) { + player->playerView.DamageImpulse( fxaction.offset, &args ); + } + } + } + if ( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) { + idEntity *ignore_ent = NULL; + if ( common->IsMultiplayer() ) { + ignore_ent = this; + if ( fxaction.shakeIgnoreMaster ) { + ignore_ent = GetBindMaster(); + } + } + // lookup the ent we are bound to? + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true ); + } + useAction->shakeStarted = true; + } + break; + } + case FX_ATTACHENTITY: + case FX_PARTICLE: + case FX_MODEL: { + if ( useAction->modelDefHandle == -1 ) { + memset( &useAction->renderEntity, 0, sizeof( renderEntity_t ) ); + useAction->renderEntity.origin = GetPhysics()->GetOrigin() + fxaction.offset; + useAction->renderEntity.axis = (fxaction.explicitAxis) ? fxaction.axis : GetPhysics()->GetAxis(); + useAction->renderEntity.hModel = renderModelManager->FindModel( fxaction.data ); + useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); + useAction->renderEntity.shaderParms[3] = 1.0f; + useAction->renderEntity.shaderParms[5] = 0.0f; + if ( useAction->renderEntity.hModel ) { + useAction->renderEntity.bounds = useAction->renderEntity.hModel->Bounds( &useAction->renderEntity ); + } + useAction->modelDefHandle = gameRenderWorld->AddEntityDef( &useAction->renderEntity ); + } else if ( fxaction.trackOrigin ) { + useAction->renderEntity.origin = GetPhysics()->GetOrigin() + fxaction.offset; + useAction->renderEntity.axis = fxaction.explicitAxis ? fxaction.axis : GetPhysics()->GetAxis(); + gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity ); + } + ApplyFade( fxaction, *useAction, time, actualStart ); + break; + } + case FX_LAUNCH: { + if ( common->IsClient() ) { + // client never spawns entities outside of ClientReadSnapshot + useAction->launched = true; + break; + } + if ( !useAction->launched ) { + useAction->launched = true; + projectile = NULL; + // FIXME: may need to cache this if it is slow + projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false ); + if ( !projectileDef ) { + gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() ); + } else { + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); + if ( ent && ent->IsType( idProjectile::Type ) ) { + projectile = ( idProjectile * )ent; + projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] ); + projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin ); + } + } + } + break; + } + case FX_SHOCKWAVE: { + if ( common->IsClient() ) { + useAction->shakeStarted = true; + break; + } + if ( !useAction->shakeStarted ) { + idStr shockDefName; + useAction->shakeStarted = true; + + shockDefName = fxaction.data; + if ( !shockDefName.Length() ) { + shockDefName = "func_shockwave"; + } + + projectileDef = gameLocal.FindEntityDefDict( shockDefName, false ); + if ( !projectileDef ) { + gameLocal.Warning( "shockwave \'%s\' not found", shockDefName.c_str() ); + } else { + gameLocal.SpawnEntityDef( *projectileDef, &ent ); + ent->SetOrigin( GetPhysics()->GetOrigin() + fxaction.offset ); + ent->PostEventMS( &EV_Remove, ent->spawnArgs.GetInt( "duration" ) ); + } + } + break; + } + } + } +} + +/* +================ +idEntityFx::idEntityFx +================ +*/ +idEntityFx::idEntityFx() { + fxEffect = NULL; + started = -1; + nextTriggerTime = -1; + fl.networkSync = true; +} + +/* +================ +idEntityFx::~idEntityFx +================ +*/ +idEntityFx::~idEntityFx() { + CleanUp(); + fxEffect = NULL; +} + +/* +================ +idEntityFx::Spawn +================ +*/ +void idEntityFx::Spawn() { + + if ( g_skipFX.GetBool() ) { + return; + } + + const char *fx; + nextTriggerTime = 0; + fxEffect = NULL; + if ( spawnArgs.GetString( "fx", "", &fx ) ) { + systemName = fx; + } + if ( !spawnArgs.GetBool( "triggered" ) ) { + Setup( fx ); + if ( spawnArgs.GetBool( "test" ) || spawnArgs.GetBool( "start" ) || spawnArgs.GetFloat ( "restart" ) ) { + PostEventMS( &EV_Activate, 0, this ); + } + } +} + +/* +================ +idEntityFx::Think + + Clears any visual fx started when {item,mob,player} was spawned +================ +*/ +void idEntityFx::Think() { + if ( g_skipFX.GetBool() ) { + return; + } + + if ( thinkFlags & TH_THINK ) { + Run( gameLocal.time ); + } + + RunPhysics(); + Present(); +} + +/* +================ +idEntityFx::Event_ClearFx + + Clears any visual fx started when item(mob) was spawned +================ +*/ +void idEntityFx::Event_ClearFx() { + + if ( g_skipFX.GetBool() ) { + return; + } + + Stop(); + CleanUp(); + BecomeInactive( TH_THINK ); + + if ( spawnArgs.GetBool("test") ) { + PostEventMS( &EV_Activate, 0, this ); + } else { + if ( spawnArgs.GetFloat( "restart" ) || !spawnArgs.GetBool( "triggered")) { + float rest = spawnArgs.GetFloat( "restart", "0" ); + if ( rest == 0.0f ) { + PostEventSec( &EV_Remove, 0.1f ); + } else { + rest *= gameLocal.random.RandomFloat(); + PostEventSec( &EV_Activate, rest, this ); + } + } + } +} + +/* +================ +idEntityFx::Event_Trigger +================ +*/ +void idEntityFx::Event_Trigger( idEntity *activator ) { + + if ( g_skipFX.GetBool() ) { + return; + } + + float fxActionDelay; + const char *fx; + + if ( gameLocal.time < nextTriggerTime ) { + return; + } + + if ( spawnArgs.GetString( "fx", "", &fx) ) { + Setup( fx ); + Start( gameLocal.time ); + PostEventMS( &EV_Fx_KillFx, Duration() ); + BecomeActive( TH_THINK ); + } + + fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" ); + if ( fxActionDelay != 0.0f ) { + nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay ); + } else { + // prevent multiple triggers on same frame + nextTriggerTime = gameLocal.time + 1; + } + PostEventSec( &EV_Fx_Action, fxActionDelay, activator ); +} + + +/* +================ +idEntityFx::StartFx +================ +*/ +idEntityFx *idEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) { + + if ( g_skipFX.GetBool() || !fx || !*fx ) { + return NULL; + } + + idDict args; + args.SetBool( "start", true ); + args.Set( "fx", fx ); + idEntityFx *nfx = static_cast( gameLocal.SpawnEntityType( idEntityFx::Type, &args ) ); + if ( nfx->Joint() && *nfx->Joint() ) { + nfx->BindToJoint( ent, nfx->Joint(), true ); + nfx->SetOrigin( vec3_origin ); + } else { + nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() ); + nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() ); + } + + if ( bind ) { + // never bind to world spawn + if ( ent != gameLocal.world ) { + nfx->Bind( ent, true ); + } + } + nfx->Show(); + return nfx; +} + +/* +================= +idEntityFx::WriteToSnapshot +================= +*/ +void idEntityFx::WriteToSnapshot( idBitMsg &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + msg.WriteLong( ( fxEffect != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_FX, fxEffect->Index() ) : -1 ); + msg.WriteLong( started ); +} + +/* +================= +idEntityFx::ReadFromSnapshot +================= +*/ +void idEntityFx::ReadFromSnapshot( const idBitMsg &msg ) { + int fx_index, start_time, max_lapse; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + fx_index = gameLocal.ClientRemapDecl( DECL_FX, msg.ReadLong() ); + start_time = msg.ReadLong(); + + if ( fx_index != -1 && start_time > 0 && !fxEffect && started < 0 ) { + spawnArgs.GetInt( "effect_lapse", "1000", max_lapse ); + if ( gameLocal.time - start_time > max_lapse ) { + // too late, skip the effect completely + started = 0; + return; + } + const idDeclFX *fx = static_cast( declManager->DeclByIndex( DECL_FX, fx_index ) ); + if ( !fx ) { + gameLocal.Error( "FX at index %d not found", fx_index ); + } + fxEffect = fx; + Setup( fx->GetName() ); + Start( start_time ); + } +} + +/* +================= +idEntityFx::ClientThink +================= +*/ +void idEntityFx::ClientThink( const int curTime, const float fraction, const bool predict ) { + + if ( gameLocal.isNewFrame ) { + Run( gameLocal.serverTime ); + } + + InterpolatePhysics( fraction ); + Present(); +} + +/* +================= +idEntityFx::ClientPredictionThink +================= +*/ +void idEntityFx::ClientPredictionThink() { + if ( gameLocal.isNewFrame ) { + Run( gameLocal.time ); + } + RunPhysics(); + Present(); +} + +/* +=============================================================================== + + idTeleporter + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntityFx, idTeleporter ) + EVENT( EV_Fx_Action, idTeleporter::Event_DoAction ) +END_CLASS + +/* +================ +idTeleporter::Event_DoAction +================ +*/ +void idTeleporter::Event_DoAction( idEntity *activator ) { + float angle; + + angle = spawnArgs.GetFloat( "angle" ); + idAngles a( 0, spawnArgs.GetFloat( "angle" ), 0 ); + activator->Teleport( GetPhysics()->GetOrigin(), a, NULL ); +} diff --git a/neo/d3xp/Fx.h b/neo/d3xp/Fx.h new file mode 100644 index 00000000..a6087a9a --- /dev/null +++ b/neo/d3xp/Fx.h @@ -0,0 +1,107 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_FX_H__ +#define __GAME_FX_H__ + +/* +=============================================================================== + + Special effects. + +=============================================================================== +*/ + +typedef struct { + renderLight_t renderLight; // light presented to the renderer + qhandle_t lightDefHandle; // handle to renderer light def + renderEntity_t renderEntity; // used to present a model to the renderer + int modelDefHandle; // handle to static renderer model + float delay; + int particleSystem; + int start; + bool soundStarted; + bool shakeStarted; + bool decalDropped; + bool launched; +} idFXLocalAction; + +class idEntityFx : public idEntity { +public: + CLASS_PROTOTYPE( idEntityFx ); + + idEntityFx(); + virtual ~idEntityFx(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + void Setup( const char *fx ); + void Run( int time ); + void Start( int time ); + void Stop(); + const int Duration(); + const char * EffectName(); + const char * Joint(); + const bool Done(); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void ClientPredictionThink(); + + static idEntityFx * StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_ClearFx(); + + void CleanUp(); + void CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ); + void ApplyFade( const idFXSingleAction& fxaction, idFXLocalAction& laction, const int time, const int actualStart ); + + int started; + int nextTriggerTime; + const idDeclFX * fxEffect; // GetFX() should be called before using fxEffect as a pointer + idList actions; + idStr systemName; +}; + +class idTeleporter : public idEntityFx { +public: + CLASS_PROTOTYPE( idTeleporter ); + +private: + // teleporters to this location + void Event_DoAction( idEntity *activator ); +}; + +#endif /* !__GAME_FX_H__ */ diff --git a/neo/d3xp/Game.def b/neo/d3xp/Game.def new file mode 100644 index 00000000..44612438 --- /dev/null +++ b/neo/d3xp/Game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/neo/d3xp/Game.h b/neo/d3xp/Game.h new file mode 100644 index 00000000..c029bb39 --- /dev/null +++ b/neo/d3xp/Game.h @@ -0,0 +1,343 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_H__ +#define __GAME_H__ + +/* +=============================================================================== + + Public game interface with methods to run the game. + +=============================================================================== +*/ + +// default scripts +#define SCRIPT_DEFAULTDEFS "script/doom_defs.script" +#define SCRIPT_DEFAULT "script/doom_main.script" +#define SCRIPT_DEFAULTFUNC "doom_main" + +struct gameReturn_t { + + gameReturn_t() : + syncNextGameFrame( false ), + vibrationLow( 0 ), + vibrationHigh( 0 ) { + + } + + char sessionCommand[MAX_STRING_CHARS]; // "map", "disconnect", "victory", etc + bool syncNextGameFrame; // used when cinematics are skipped to prevent session from simulating several game frames to + // keep the game time in sync with real time + int vibrationLow; + int vibrationHigh; +}; + +#define TIME_GROUP1 0 +#define TIME_GROUP2 1 + +class idGame { +public: + virtual ~idGame() {} + + // Initialize the game for the first time. + virtual void Init() = 0; + + // Shut down the entire game. + virtual void Shutdown() = 0; + + // Sets the serverinfo at map loads and when it changes. + virtual void SetServerInfo( const idDict &serverInfo ) = 0; + + // Gets the serverinfo, common calls this before saving the game + virtual const idDict & GetServerInfo() = 0; + + // Interpolated server time + virtual void SetServerGameTimeMs( const int time ) = 0; + + // Interpolated server time + virtual int GetServerGameTimeMs() const = 0; + + virtual int GetSSEndTime() const = 0; + virtual int GetSSStartTime() const = 0; + + // common calls this before moving the single player game to a new level. + virtual const idDict & GetPersistentPlayerInfo( int clientNum ) = 0; + + // common calls this right before a new level is loaded. + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) = 0; + + // Loads a map and spawns all the entities. + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, int gameMode, int randseed ) = 0; + + // Loads a map from a savegame file. + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, idFile *saveGameFile, idFile *stringTableFile, int saveGameVersion ) = 0; + + // Saves the current game state, common may have written some data to the file already. + virtual void SaveGame( idFile *saveGameFile, idFile *stringTableFile ) = 0; + + // Pulls the current player location from the game information + virtual void GetSaveGameDetails( idSaveGameDetails & gameDetails ) = 0; + + // Shut down the current map. + virtual void MapShutdown() = 0; + + // Caches media referenced from in key/value pairs in the given dictionary. + virtual void CacheDictionaryMedia( const idDict *dict ) = 0; + + virtual void Preload( const idPreloadManifest &manifest ) = 0; + + // Runs a game frame, may return a session command for level changing, etc + virtual void RunFrame( idUserCmdMgr & cmdMgr, gameReturn_t & gameReturn ) = 0; + + // Makes rendering and sound system calls to display for a given clientNum. + virtual bool Draw( int clientNum ) = 0; + + virtual bool HandlePlayerGuiEvent( const sysEvent_t * ev ) = 0; + + // Writes a snapshot of the server game state. + virtual void ServerWriteSnapshot( idSnapShot & ss ) = 0; + + // Processes a reliable message + virtual void ProcessReliableMessage( int clientNum, int type, const idBitMsg &msg ) = 0; + + virtual void SetInterpolation( const float fraction, const int serverGameMS, const int ssStartTime, const int ssEndTime ) = 0; + + // Reads a snapshot and updates the client game state. + virtual void ClientReadSnapshot( const idSnapShot & ss ) = 0; + + // Runs prediction on entities at the client. + virtual void ClientRunFrame( idUserCmdMgr & cmdMgr, bool lastPredictFrame, gameReturn_t & ret ) = 0; + + // Used to manage divergent time-lines + virtual int GetTimeGroupTime( int timeGroup ) = 0; + + // Returns a list of available multiplayer game modes + virtual int GetMPGameModes( const char *** gameModes, const char *** gameModesDisplay ) = 0; + + // Returns a summary of stats for a given client + virtual void GetClientStats( int clientNum, char *data, const int len ) = 0; + + virtual bool IsInGame() const = 0; + + // Get the player entity number for a network peer. + virtual int MapPeerToClient( int peer ) const = 0; + + // Get the player entity number of the local player. + virtual int GetLocalClientNum() const = 0; + + // compute an angle offset to be applied to the given client's aim + virtual void GetAimAssistAngles( idAngles & angles ) = 0; + virtual float GetAimAssistSensitivity() = 0; + + // Release the mouse when the PDA is open + virtual bool IsPDAOpen() const = 0; + virtual bool IsPlayerChatting() const = 0; + + // Creates leaderboards for each map/mode defined. + virtual void Leaderboards_Init() = 0; + virtual void Leaderboards_Shutdown() = 0; + + // MAIN MENU FUNCTIONS + virtual bool InhibitControls() = 0; + virtual void Shell_Init( const char * filename, idSoundWorld * sw ) = 0; + virtual void Shell_Cleanup() = 0; + virtual void Shell_CreateMenu( bool inGame ) = 0; + virtual void Shell_ClosePause() = 0; + virtual void Shell_Show( bool show ) = 0; + virtual bool Shell_IsActive() const = 0; + virtual bool Shell_HandleGuiEvent( const sysEvent_t * sev ) = 0; + virtual void Shell_Render() = 0; + virtual void Shell_ResetMenu() = 0; + virtual void Shell_SyncWithSession() = 0; + virtual void Shell_UpdateSavedGames() = 0; + virtual void Shell_SetCanContinue( bool valid ) = 0; + virtual void Shell_UpdateClientCountdown( int countdown ) = 0; + virtual void Shell_UpdateLeaderboard( const idLeaderboardCallback * callback ) = 0; + virtual void Shell_SetGameComplete() = 0; +}; + +extern idGame * game; + + +/* +=============================================================================== + + Public game interface with methods for in-game editing. + +=============================================================================== +*/ + +typedef struct { + idSoundEmitter * referenceSound; // this is the interface to the sound system, created + // with idSoundWorld::AllocSoundEmitter() when needed + idVec3 origin; + int listenerId; // SSF_PRIVATE_SOUND only plays if == listenerId from PlaceListener + // no spatialization will be performed if == listenerID + const idSoundShader * shader; // this really shouldn't be here, it is a holdover from single channel behavior + float diversity; // 0.0 to 1.0 value used to select which + // samples in a multi-sample list from the shader are used + bool waitfortrigger; // don't start it at spawn time + soundShaderParms_t parms; // override volume, flags, etc +} refSound_t; + +enum { + TEST_PARTICLE_MODEL = 0, + TEST_PARTICLE_IMPACT, + TEST_PARTICLE_MUZZLE, + TEST_PARTICLE_FLIGHT, + TEST_PARTICLE_SELECTED +}; + +class idEntity; +class idMD5Anim; + +// FIXME: this interface needs to be reworked but it properly separates code for the time being +class idGameEdit { +public: + virtual ~idGameEdit() {} + + // These are the canonical idDict to parameter parsing routines used by both the game and tools. + virtual void ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ); + virtual void ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ); + virtual void ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ); + + // Animation system calls for non-game based skeletal rendering. + virtual idRenderModel * ANIM_GetModelFromEntityDef( const char *classname ); + virtual const idVec3 &ANIM_GetModelOffsetFromEntityDef( const char *classname ); + virtual idRenderModel * ANIM_GetModelFromEntityDef( const idDict *args ); + virtual idRenderModel * ANIM_GetModelFromName( const char *modelName ); + virtual const idMD5Anim * ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ); + virtual int ANIM_GetNumAnimsFromEntityDef( const idDict *args ); + virtual const char * ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ); + virtual const idMD5Anim * ANIM_GetAnim( const char *fileName ); + virtual int ANIM_GetLength( const idMD5Anim *anim ); + virtual int ANIM_GetNumFrames( const idMD5Anim *anim ); + virtual void ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *frame, int time, const idVec3 &offset, bool remove_origin_offset ); + virtual idRenderModel * ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ); + + // Articulated Figure calls for AF editor and Radiant. + virtual bool AF_SpawnEntity( const char *fileName ); + virtual void AF_UpdateEntities( const char *fileName ); + virtual void AF_UndoChanges(); + virtual idRenderModel * AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ); + + + // Entity selection. + virtual void ClearEntitySelection(); + virtual int GetSelectedEntities( idEntity *list[], int max ); + virtual void AddSelectedEntity( idEntity *ent ); + + // Selection methods + virtual void TriggerSelected(); + + // Entity defs and spawning. + virtual const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + virtual void SpawnEntityDef( const idDict &args, idEntity **ent ); + virtual idEntity * FindEntity( const char *name ) const; + virtual const char * GetUniqueEntityName( const char *classname ) const; + + // Entity methods. + virtual void EntityGetOrigin( idEntity *ent, idVec3 &org ) const; + virtual void EntityGetAxis( idEntity *ent, idMat3 &axis ) const; + virtual void EntitySetOrigin( idEntity *ent, const idVec3 &org ); + virtual void EntitySetAxis( idEntity *ent, const idMat3 &axis ); + virtual void EntityTranslate( idEntity *ent, const idVec3 &org ); + virtual const idDict * EntityGetSpawnArgs( idEntity *ent ) const; + virtual void EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ); + virtual void EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ); + virtual void EntityUpdateVisuals( idEntity *ent ); + virtual void EntitySetModel( idEntity *ent, const char *val ); + virtual void EntityStopSound( idEntity *ent ); + virtual void EntityDelete( idEntity *ent ); + virtual void EntitySetColor( idEntity *ent, const idVec3 color ); + + // Player methods. + virtual bool PlayerIsValid() const; + virtual void PlayerGetOrigin( idVec3 &org ) const; + virtual void PlayerGetAxis( idMat3 &axis ) const; + virtual void PlayerGetViewAngles( idAngles &angles ) const; + virtual void PlayerGetEyePosition( idVec3 &org ) const; + + // In game map editing support. + virtual const idDict * MapGetEntityDict( const char *name ) const; + virtual void MapSave( const char *path = NULL ) const; + virtual void MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const ; + virtual void MapCopyDictToEntity( const char *name, const idDict *dict ) const; + virtual int MapGetUniqueMatchingKeyVals( const char *key, const char *list[], const int max ) const; + virtual void MapAddEntity( const idDict *dict ) const; + virtual int MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const; + virtual void MapRemoveEntity( const char *name ) const; + virtual void MapEntityTranslate( const char *name, const idVec3 &v ) const; + +}; + +extern idGameEdit * gameEdit; + + +/* +=============================================================================== + + Game API. + +=============================================================================== +*/ + +const int GAME_API_VERSION = 8; + +typedef struct { + + int version; // API version + idSys * sys; // non-portable system services + idCommon * common; // common + idCmdSystem * cmdSystem; // console command system + idCVarSystem * cvarSystem; // console variable system + idFileSystem * fileSystem; // file system + idRenderSystem * renderSystem; // render system + idSoundSystem * soundSystem; // sound system + idRenderModelManager * renderModelManager; // render model manager + idUserInterfaceManager * uiManager; // user interface manager + idDeclManager * declManager; // declaration manager + idAASFileManager * AASFileManager; // AAS file manager + idCollisionModelManager * collisionModelManager; // collision model manager + +} gameImport_t; + +typedef struct { + + int version; // API version + idGame * game; // interface to run the game + idGameEdit * gameEdit; // interface for in-game editing + +} gameExport_t; + +extern "C" { +typedef gameExport_t * (*GetGameAPI_t)( gameImport_t *import ); +} + +#endif /* !__GAME_H__ */ diff --git a/neo/d3xp/GameEdit.cpp b/neo/d3xp/GameEdit.cpp new file mode 100644 index 00000000..2460fbc2 --- /dev/null +++ b/neo/d3xp/GameEdit.cpp @@ -0,0 +1,1142 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idCursor3D ) +END_CLASS + +/* +=============== +idCursor3D::idCursor3D +=============== +*/ +idCursor3D::idCursor3D() { + draggedPosition.Zero(); +} + +/* +=============== +idCursor3D::~idCursor3D +=============== +*/ +idCursor3D::~idCursor3D() { +} + +/* +=============== +idCursor3D::Spawn +=============== +*/ +void idCursor3D::Spawn() { +} + +/* +=============== +idCursor3D::Present +=============== +*/ +void idCursor3D::Present() { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + const idVec3 &origin = GetPhysics()->GetOrigin(); + const idMat3 &axis = GetPhysics()->GetAxis(); + gameRenderWorld->DebugArrow( colorYellow, origin + axis[1] * -5.0f + axis[2] * 5.0f, origin, 2 ); + gameRenderWorld->DebugArrow( colorRed, origin, draggedPosition, 2 ); +} + +/* +=============== +idCursor3D::Think +=============== +*/ +void idCursor3D::Think() { + if ( thinkFlags & TH_THINK ) { + drag.Evaluate( gameLocal.time ); + } + Present(); +} + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +#define MAX_DRAG_TRACE_DISTANCE 2048.0f + +/* +============== +idDragEntity::idDragEntity +============== +*/ +idDragEntity::idDragEntity() { + cursor = NULL; + Clear(); +} + +/* +============== +idDragEntity::~idDragEntity +============== +*/ +idDragEntity::~idDragEntity() { + StopDrag(); + selected = NULL; + delete cursor; + cursor = NULL; +} + + +/* +============== +idDragEntity::Clear +============== +*/ +void idDragEntity::Clear() { + dragEnt = NULL; + joint = INVALID_JOINT; + id = 0; + localEntityPoint.Zero(); + localPlayerPoint.Zero(); + bodyName.Clear(); + selected = NULL; +} + +/* +============== +idDragEntity::StopDrag +============== +*/ +void idDragEntity::StopDrag() { + dragEnt = NULL; + if ( cursor ) { + cursor->BecomeInactive( TH_THINK ); + } +} + +/* +============== +idDragEntity::Update +============== +*/ +void idDragEntity::Update( idPlayer *player ) { + idVec3 viewPoint, origin; + idMat3 viewAxis, axis; + trace_t trace; + idEntity *newEnt = NULL; + idAngles angles; + jointHandle_t newJoint = INVALID_JOINT; + idStr newBodyName; + + player->GetViewPos( viewPoint, viewAxis ); + + // if no entity selected for dragging + if ( !dragEnt.GetEntity() ) { + + if ( player->usercmd.buttons & BUTTON_ATTACK ) { + + gameLocal.clip.TracePoint( trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player ); + if ( trace.fraction < 1.0f ) { + + newEnt = gameLocal.entities[ trace.c.entityNum ]; + if ( newEnt ) { + + if ( newEnt->GetBindMaster() ) { + if ( newEnt->GetBindJoint() ) { + trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() ); + } else { + trace.c.id = newEnt->GetBindBody(); + } + newEnt = newEnt->GetBindMaster(); + } + + if ( newEnt->IsType( idAFEntity_Base::Type ) && static_cast(newEnt)->IsActiveAF() ) { + idAFEntity_Base *af = static_cast(newEnt); + + // joint being dragged + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + // get the body id from the trace model id which might be a joint handle + trace.c.id = af->BodyForClipModelId( trace.c.id ); + // get the name of the body being dragged + newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName(); + + } else if ( !newEnt->IsType( idWorldspawn::Type ) ) { + + if ( trace.c.id < 0 ) { + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + } else { + newJoint = INVALID_JOINT; + } + newBodyName = ""; + + } else { + + newJoint = INVALID_JOINT; + newEnt = NULL; + } + } + if ( newEnt ) { + dragEnt = newEnt; + selected = newEnt; + joint = newJoint; + id = trace.c.id; + bodyName = newBodyName; + + if ( !cursor ) { + cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::Type ); + } + + idPhysics *phys = dragEnt.GetEntity()->GetPhysics(); + localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose(); + origin = phys->GetOrigin( id ); + axis = phys->GetAxis( id ); + localEntityPoint = ( trace.c.point - origin ) * axis.Transpose(); + + cursor->drag.Init( g_dragDamping.GetFloat() ); + cursor->drag.SetPhysics( phys, id, localEntityPoint ); + cursor->Show(); + + if ( phys->IsType( idPhysics_AF::Type ) || + phys->IsType( idPhysics_RigidBody::Type ) || + phys->IsType( idPhysics_Monster::Type ) ) { + cursor->BecomeActive( TH_THINK ); + } + } + } + } + } + + // if there is an entity selected for dragging + idEntity *drag = dragEnt.GetEntity(); + if ( drag ) { + + if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { + StopDrag(); + return; + } + + cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis ); + cursor->SetAxis( viewAxis ); + + cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() ); + + renderEntity_t *renderEntity = drag->GetRenderEntity(); + idAnimator *dragAnimator = drag->GetAnimator(); + + if ( joint != INVALID_JOINT && renderEntity != NULL && dragAnimator != NULL ) { + dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis ); + cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis; + gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } else { + cursor->draggedPosition = cursor->GetPhysics()->GetOrigin(); + gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } + } + + // if there is a selected entity + if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) { + // draw the bbox of the selected entity + renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity(); + if ( renderEntity ) { + gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) ); + } + } +} + +/* +============== +idDragEntity::SetSelected +============== +*/ +void idDragEntity::SetSelected( idEntity *ent ) { + selected = ent; + StopDrag(); +} + +/* +============== +idDragEntity::DeleteSelected +============== +*/ +void idDragEntity::DeleteSelected() { + delete selected.GetEntity(); + selected = NULL; + StopDrag(); +} + +/* +============== +idDragEntity::BindSelected +============== +*/ +void idDragEntity::BindSelected() { + int num, largestNum; + idLexer lexer; + idToken type, bodyName; + idStr key, value, bindBodyName; + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(dragEnt.GetEntity()); + + if ( !af || !af->IsType( idAFEntity_Base::Type ) || !af->IsActiveAF() ) { + return; + } + + bindBodyName = af->GetAFPhysics()->GetBody( id )->GetName(); + largestNum = 1; + + // parse all the bind constraints + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + key = kv->GetKey(); + key.Strip( "bindConstraint " ); + if ( sscanf( key, "bind%d", &num ) ) { + if ( num >= largestNum ) { + largestNum = num + 1; + } + } + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + lexer.ReadToken( &bodyName ); + lexer.FreeSource(); + + // if there already exists a bind constraint for this body + if ( bodyName.Icmp( bindBodyName ) == 0 ) { + // delete the bind constraint + af->spawnArgs.Delete( kv->GetKey() ); + kv = NULL; + } + + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + sprintf( key, "bindConstraint bind%d", largestNum ); + sprintf( value, "ballAndSocket %s %s", bindBodyName.c_str(), af->GetAnimator()->GetJointName( joint ) ); + + af->spawnArgs.Set( key, value ); + af->spawnArgs.Set( "bind", "worldspawn" ); + af->Bind( gameLocal.world, true ); +} + +/* +============== +idDragEntity::UnbindSelected +============== +*/ +void idDragEntity::UnbindSelected() { + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(selected.GetEntity()); + + if ( !af || !af->IsType( idAFEntity_Base::Type ) || !af->IsActiveAF() ) { + return; + } + + // unbind the selected entity + af->Unbind(); + + // delete all the bind constraints + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + selected.GetEntity()->spawnArgs.Delete( kv->GetKey() ); + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + } + + // delete any bind information + af->spawnArgs.Delete( "bind" ); + af->spawnArgs.Delete( "bindToJoint" ); + af->spawnArgs.Delete( "bindToBody" ); +} + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ + +/* +============== +idEditEntities::idEditEntities +============== +*/ +idEditEntities::idEditEntities() { + selectableEntityClasses.Clear(); + nextSelectTime = 0; +} + +/* +============= +idEditEntities::SelectEntity +============= +*/ +bool idEditEntities::SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ) { + idVec3 end; + idEntity *ent; + + if ( !g_editEntityMode.GetInteger() || selectableEntityClasses.Num() == 0 ) { + return false; + } + + if ( gameLocal.time < nextSelectTime ) { + return true; + } + nextSelectTime = gameLocal.time + 300; + + end = origin + dir * 4096.0f; + + ent = NULL; + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + ent = gameLocal.FindTraceEntity( origin, end, *selectableEntityClasses[i].typeInfo, skip ); + if ( ent ) { + break; + } + } + if ( ent ) { + ClearSelectedEntities(); + if ( EntityIsSelectable( ent ) ) { + AddSelectedEntity( ent ); + gameLocal.Printf( "entity #%d: %s '%s'\n", ent->entityNumber, ent->GetClassname(), ent->name.c_str() ); + ent->ShowEditingDialog(); + return true; + } + } + return false; +} + +/* +============= +idEditEntities::AddSelectedEntity +============= +*/ +void idEditEntities::AddSelectedEntity(idEntity *ent) { + ent->fl.selected = true; + selectedEntities.AddUnique(ent); +} + +/* +============== +idEditEntities::RemoveSelectedEntity +============== +*/ +void idEditEntities::RemoveSelectedEntity( idEntity *ent ) { + if ( selectedEntities.Find( ent ) ) { + selectedEntities.Remove( ent ); + } +} + +/* +============= +idEditEntities::ClearSelectedEntities +============= +*/ +void idEditEntities::ClearSelectedEntities() { + int i, count; + + count = selectedEntities.Num(); + for ( i = 0; i < count; i++ ) { + selectedEntities[i]->fl.selected = false; + } + selectedEntities.Clear(); +} + + +/* +============= +idEditEntities::EntityIsSelectable +============= +*/ +bool idEditEntities::EntityIsSelectable( idEntity *ent, idVec4 *color, idStr *text ) { + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + if ( ent->GetType() == selectableEntityClasses[i].typeInfo ) { + if ( text ) { + *text = selectableEntityClasses[i].textKey; + } + if ( color ) { + if ( ent->fl.selected ) { + *color = colorRed; + } else { + switch( i ) { + case 1 : + *color = colorYellow; + break; + case 2 : + *color = colorBlue; + break; + default: + *color = colorGreen; + } + } + } + return true; + } + } + return false; +} + +/* +============= +idEditEntities::DisplayEntities +============= +*/ +void idEditEntities::DisplayEntities() { + idEntity *ent; + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + selectableEntityClasses.Clear(); + selectedTypeInfo_t sit; + + switch( g_editEntityMode.GetInteger() ) { + case 1: + sit.typeInfo = &idLight::Type; + sit.textKey = "texture"; + selectableEntityClasses.Append( sit ); + break; + case 2: + sit.typeInfo = &idSound::Type; + sit.textKey = "s_shader"; + selectableEntityClasses.Append( sit ); + sit.typeInfo = &idLight::Type; + sit.textKey = "texture"; + selectableEntityClasses.Append( sit ); + break; + case 3: + sit.typeInfo = &idAFEntity_Base::Type; + sit.textKey = "articulatedFigure"; + selectableEntityClasses.Append( sit ); + break; + case 4: + sit.typeInfo = &idFuncEmitter::Type; + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; + case 5: + sit.typeInfo = &idAI::Type; + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 6: + sit.typeInfo = &idEntity::Type; + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 7: + sit.typeInfo = &idEntity::Type; + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; + default: + return; + } + + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + + viewBounds.ExpandSelf( 512 ); + viewTextBounds.ExpandSelf( 128 ); + + idStr textKey; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + idVec4 color; + + textKey = ""; + if ( !EntityIsSelectable( ent, &color, &textKey ) ) { + continue; + } + + bool drawArrows = false; + if ( ent->GetType() == &idAFEntity_Base::Type ) { + if ( !static_cast(ent)->IsActiveAF() ) { + continue; + } + } else if ( ent->GetType() == &idSound::Type ) { + if ( ent->fl.selected ) { + drawArrows = true; + } + const idSoundShader * ss = declManager->FindSound( ent->spawnArgs.GetString( textKey ) ); + if ( ss->HasDefaultSound() || ss->base->GetState() == DS_DEFAULTED ) { + color.Set( 1.0f, 0.0f, 1.0f, 1.0f ); + } + } else if ( ent->GetType() == &idFuncEmitter::Type ) { + if ( ent->fl.selected ) { + drawArrows = true; + } + } + + if ( !viewBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + continue; + } + + gameRenderWorld->DebugBounds( color, idBounds( ent->GetPhysics()->GetOrigin() ).Expand( 8 ) ); + if ( drawArrows ) { + idVec3 start = ent->GetPhysics()->GetOrigin(); + idVec3 end = start + idVec3( 1, 0, 0 ) * 20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x+", end + idVec3( 4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 1, 0, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x-", end + idVec3( -4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * +20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y+", end + idVec3( 0, 4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y-", end + idVec3( 0, -4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * +20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z+", end + idVec3( 0, 0, 4 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * -20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z-", end + idVec3( 0, 0, -4 ), 0.15f, colorWhite, axis ); + } + + if ( textKey.Length() ) { + const char *text = ent->spawnArgs.GetString( textKey ); + if ( viewTextBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + gameRenderWorld->DrawText( text, ent->GetPhysics()->GetOrigin() + idVec3(0, 0, 12), 0.25, colorWhite, axis, 1 ); + } + } + } +} + + +/* +=============================================================================== + + idGameEdit + +=============================================================================== +*/ + +idGameEdit gameEditLocal; +idGameEdit * gameEdit = &gameEditLocal; + + +/* +============= +idGameEdit::GetSelectedEntities +============= +*/ +int idGameEdit::GetSelectedEntities( idEntity *list[], int max ) { + int num = 0; + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + list[num++] = ent; + if ( num >= max ) { + break; + } + } + } + return num; +} + +/* +============= +idGameEdit::TriggerSelected +============= +*/ +void idGameEdit::TriggerSelected() { + idEntity *ent; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + } + } +} + +/* +================ +idGameEdit::ClearEntitySelection +================ +*/ +void idGameEdit::ClearEntitySelection() { + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + ent->fl.selected = false; + } + gameLocal.editEntities->ClearSelectedEntities(); +} + +/* +================ +idGameEdit::AddSelectedEntity +================ +*/ +void idGameEdit::AddSelectedEntity( idEntity *ent ) { + if ( ent ) { + gameLocal.editEntities->AddSelectedEntity( ent ); + } +} + +/* +================ +idGameEdit::FindEntityDefDict +================ +*/ +const idDict *idGameEdit::FindEntityDefDict( const char *name, bool makeDefault ) const { + return gameLocal.FindEntityDefDict( name, makeDefault ); +} + +/* +================ +idGameEdit::SpawnEntityDef +================ +*/ +void idGameEdit::SpawnEntityDef( const idDict &args, idEntity **ent ) { + gameLocal.SpawnEntityDef( args, ent ); +} + +/* +================ +idGameEdit::FindEntity +================ +*/ +idEntity *idGameEdit::FindEntity( const char *name ) const { + return gameLocal.FindEntity( name ); +} + +/* +============= +idGameEdit::GetUniqueEntityName + +generates a unique name for a given classname +============= +*/ +const char *idGameEdit::GetUniqueEntityName( const char *classname ) const { + int id; + static char name[1024]; + + // can only have MAX_GENTITIES, so if we have a spot available, we're guaranteed to find one + for( id = 0; id < MAX_GENTITIES; id++ ) { + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + if ( !gameLocal.FindEntity( name ) ) { + return name; + } + } + + // id == MAX_GENTITIES + 1, which can't be in use if we get here + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + return name; +} + +/* +================ +idGameEdit::EntityGetOrigin +================ +*/ +void idGameEdit::EntityGetOrigin( idEntity *ent, idVec3 &org ) const { + if ( ent ) { + org = ent->GetPhysics()->GetOrigin(); + } +} + +/* +================ +idGameEdit::EntityGetAxis +================ +*/ +void idGameEdit::EntityGetAxis( idEntity *ent, idMat3 &axis ) const { + if ( ent ) { + axis = ent->GetPhysics()->GetAxis(); + } +} + +/* +================ +idGameEdit::EntitySetOrigin +================ +*/ +void idGameEdit::EntitySetOrigin( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->SetOrigin( org ); + } +} + +/* +================ +idGameEdit::EntitySetAxis +================ +*/ +void idGameEdit::EntitySetAxis( idEntity *ent, const idMat3 &axis ) { + if ( ent ) { + ent->SetAxis( axis ); + } +} + +/* +================ +idGameEdit::EntitySetColor +================ +*/ +void idGameEdit::EntitySetColor( idEntity *ent, const idVec3 color ) { + if ( ent ) { + ent->SetColor( color ); + } +} + +/* +================ +idGameEdit::EntityTranslate +================ +*/ +void idGameEdit::EntityTranslate( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->GetPhysics()->Translate( org ); + } +} + +/* +================ +idGameEdit::EntityGetSpawnArgs +================ +*/ +const idDict *idGameEdit::EntityGetSpawnArgs( idEntity *ent ) const { + if ( ent ) { + return &ent->spawnArgs; + } + return NULL; +} + +/* +================ +idGameEdit::EntityUpdateChangeableSpawnArgs +================ +*/ +void idGameEdit::EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ) { + if ( ent ) { + ent->UpdateChangeableSpawnArgs( dict ); + } +} + +/* +================ +idGameEdit::EntityChangeSpawnArgs +================ +*/ +void idGameEdit::EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ) { + if ( ent ) { + for ( int i = 0 ; i < newArgs->GetNumKeyVals () ; i ++ ) { + const idKeyValue *kv = newArgs->GetKeyVal( i ); + + if ( kv->GetValue().Length() > 0 ) { + ent->spawnArgs.Set ( kv->GetKey() ,kv->GetValue() ); + } else { + ent->spawnArgs.Delete ( kv->GetKey() ); + } + } + } +} + +/* +================ +idGameEdit::EntityUpdateVisuals +================ +*/ +void idGameEdit::EntityUpdateVisuals( idEntity *ent ) { + if ( ent ) { + ent->UpdateVisuals(); + } +} + +/* +================ +idGameEdit::EntitySetModel +================ +*/ +void idGameEdit::EntitySetModel( idEntity *ent, const char *val ) { + if ( ent ) { + ent->spawnArgs.Set( "model", val ); + ent->SetModel( val ); + } +} + +/* +================ +idGameEdit::EntityStopSound +================ +*/ +void idGameEdit::EntityStopSound( idEntity *ent ) { + if ( ent ) { + ent->StopSound( SND_CHANNEL_ANY, false ); + } +} + +/* +================ +idGameEdit::EntityDelete +================ +*/ +void idGameEdit::EntityDelete( idEntity *ent ) { + delete ent; +} + +/* +================ +idGameEdit::PlayerIsValid +================ +*/ +bool idGameEdit::PlayerIsValid() const { + return ( gameLocal.GetLocalPlayer() != NULL ); +} + +/* +================ +idGameEdit::PlayerGetOrigin +================ +*/ +void idGameEdit::PlayerGetOrigin( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); +} + +/* +================ +idGameEdit::PlayerGetAxis +================ +*/ +void idGameEdit::PlayerGetAxis( idMat3 &axis ) const { + axis = gameLocal.GetLocalPlayer()->GetPhysics()->GetAxis(); +} + +/* +================ +idGameEdit::PlayerGetViewAngles +================ +*/ +void idGameEdit::PlayerGetViewAngles( idAngles &angles ) const { + angles = gameLocal.GetLocalPlayer()->viewAngles; +} + +/* +================ +idGameEdit::PlayerGetEyePosition +================ +*/ +void idGameEdit::PlayerGetEyePosition( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetEyePosition(); +} + + +/* +================ +idGameEdit::MapGetEntityDict +================ +*/ +const idDict *idGameEdit::MapGetEntityDict( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + return &mapent->epairs; + } + } + return NULL; +} + +/* +================ +idGameEdit::MapSave +================ +*/ +void idGameEdit::MapSave( const char *path ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { + mapFile->Write( (path) ? path : mapFile->GetName(), ".map"); + } +} + +/* +================ +idGameEdit::MapSetEntityKeyVal +================ +*/ +void idGameEdit::MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + mapent->epairs.Set( key, val ); + } + } +} + +/* +================ +idGameEdit::MapCopyDictToEntity +================ +*/ +void idGameEdit::MapCopyDictToEntity( const char *name, const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + for ( int i = 0; i < dict->GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + const char *key = kv->GetKey(); + const char *val = kv->GetValue(); + mapent->epairs.Set( key, val ); + } + } + } +} + + + +/* +================ +idGameEdit::MapGetUniqueMatchingKeyVals +================ +*/ +int idGameEdit::MapGetUniqueMatchingKeyVals( const char *key, const char *list[], int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *ent = mapFile->GetEntity( i ); + if ( ent ) { + const char *k = ent->epairs.GetString( key ); + if ( k != NULL && *k != NULL && count < max ) { + list[count++] = k; + } + } + } + } + return count; +} + +/* +================ +idGameEdit::MapAddEntity +================ +*/ +void idGameEdit::MapAddEntity( const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = new (TAG_GAME) idMapEntity(); + ent->epairs = *dict; + mapFile->AddEntity( ent ); + } +} + +/* +================ +idGameEdit::MapRemoveEntity +================ +*/ +void idGameEdit::MapRemoveEntity( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = mapFile->FindEntity( name ); + if ( ent ) { + mapFile->RemoveEntity( ent ); + } + } +} + + +/* +================ +idGameEdit::MapGetEntitiesMatchignClassWithString +================ +*/ +int idGameEdit::MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + int entCount = mapFile->GetNumEntities(); + for ( int i = 0 ; i < entCount; i++ ) { + idMapEntity *ent = mapFile->GetEntity(i); + if (ent) { + idStr work = ent->epairs.GetString("classname"); + if ( work.Icmp( classname ) == 0 ) { + if ( match && *match ) { + work = ent->epairs.GetString( "soundgroup" ); + if ( count < max && work.Icmp( match ) == 0 ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } else if ( count < max ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } + } + } + } + return count; +} + + +/* +================ +idGameEdit::MapEntityTranslate +================ +*/ +void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + idVec3 origin; + mapent->epairs.GetVector( "origin", "", origin ); + origin += v; + mapent->epairs.SetVector( "origin", origin ); + } + } +} diff --git a/neo/d3xp/GameEdit.h b/neo/d3xp/GameEdit.h new file mode 100644 index 00000000..333e4303 --- /dev/null +++ b/neo/d3xp/GameEdit.h @@ -0,0 +1,119 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_EDIT_H__ +#define __GAME_EDIT_H__ + + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +class idCursor3D : public idEntity { +public: + CLASS_PROTOTYPE( idCursor3D ); + + idCursor3D(); + ~idCursor3D(); + + void Spawn(); + void Present(); + void Think(); + + idForce_Drag drag; + idVec3 draggedPosition; +}; + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +class idDragEntity { +public: + idDragEntity(); + ~idDragEntity(); + + void Clear(); + void Update( idPlayer *player ); + void SetSelected( idEntity *ent ); + idEntity * GetSelected() const { return selected.GetEntity(); } + void DeleteSelected(); + void BindSelected(); + void UnbindSelected(); + +private: + idEntityPtr dragEnt; // entity being dragged + jointHandle_t joint; // joint being dragged + int id; // id of body being dragged + idVec3 localEntityPoint; // dragged point in entity space + idVec3 localPlayerPoint; // dragged point in player space + idStr bodyName; // name of the body being dragged + idCursor3D * cursor; // cursor entity + idEntityPtr selected; // last dragged entity + + void StopDrag(); +}; + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ +typedef struct selectedTypeInfo_s { + idTypeInfo *typeInfo; + idStr textKey; +} selectedTypeInfo_t; + +class idEditEntities { +public: + idEditEntities(); + bool SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ); + void AddSelectedEntity( idEntity *ent ); + void RemoveSelectedEntity( idEntity *ent ); + void ClearSelectedEntities(); + void DisplayEntities(); + bool EntityIsSelectable( idEntity *ent, idVec4 *color = NULL, idStr *text = NULL ); +private: + int nextSelectTime; + idList selectableEntityClasses; + idList selectedEntities; +}; + +#endif /* !__GAME_EDIT_H__ */ diff --git a/neo/d3xp/Game_local.cpp b/neo/d3xp/Game_local.cpp new file mode 100644 index 00000000..a003c87c --- /dev/null +++ b/neo/d3xp/Game_local.cpp @@ -0,0 +1,5085 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#ifdef GAME_DLL + +idSys * sys = NULL; +idCommon * common = NULL; +idCmdSystem * cmdSystem = NULL; +idCVarSystem * cvarSystem = NULL; +idFileSystem * fileSystem = NULL; +idRenderSystem * renderSystem = NULL; +idSoundSystem * soundSystem = NULL; +idRenderModelManager * renderModelManager = NULL; +idUserInterfaceManager * uiManager = NULL; +idDeclManager * declManager = NULL; +idAASFileManager * AASFileManager = NULL; +idCollisionModelManager * collisionModelManager = NULL; +idCVar * idCVar::staticVars = NULL; + +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" ); + +#endif + +idRenderWorld * gameRenderWorld = NULL; // all drawing is done to this world +idSoundWorld * gameSoundWorld = NULL; // all audio goes to this world + +static gameExport_t gameExport; + +// global animation lib +idAnimManager animationLib; + +// the rest of the engine will only reference the "game" variable, while all local aspects stay hidden +idGameLocal gameLocal; +idGame * game = &gameLocal; // statically pointed at an idGameLocal + +const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = { + "none", "metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic", + "ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15" +}; + +idCVar net_usercmd_timing_debug( "net_usercmd_timing_debug", "0", CVAR_BOOL, "Print messages about usercmd timing." ); + + +// List of all defs used by the player that will stay on the fast timeline +static char* fastEntityList[] = { + "player_doommarine", + "weapon_chainsaw", + "weapon_fists", + "weapon_flashlight", + "weapon_rocketlauncher", + "projectile_rocket", + "weapon_machinegun", + "projectile_bullet_machinegun", + "weapon_pistol", + "projectile_bullet_pistol", + "weapon_handgrenade", + "projectile_grenade", + "weapon_bfg", + "projectile_bfg", + "weapon_chaingun", + "projectile_chaingunbullet", + "weapon_pda", + "weapon_plasmagun", + "projectile_plasmablast", + "weapon_shotgun", + "projectile_bullet_shotgun", + "weapon_soulcube", + "projectile_soulblast", + "weapon_shotgun_double", + "projectile_shotgunbullet_double", + "weapon_grabber", + "weapon_bloodstone_active1", + "weapon_bloodstone_active2", + "weapon_bloodstone_active3", + "weapon_bloodstone_passive", + NULL }; +/* +=========== +GetGameAPI +============ +*/ +#if __MWERKS__ +#pragma export on +#endif +#if __GNUC__ >= 4 +#pragma GCC visibility push(default) +#endif +extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) { +#if __MWERKS__ +#pragma export off +#endif + + if ( import->version == GAME_API_VERSION ) { + + // set interface pointers used by the game + sys = import->sys; + common = import->common; + cmdSystem = import->cmdSystem; + cvarSystem = import->cvarSystem; + fileSystem = import->fileSystem; + renderSystem = import->renderSystem; + soundSystem = import->soundSystem; + renderModelManager = import->renderModelManager; + uiManager = import->uiManager; + declManager = import->declManager; + AASFileManager = import->AASFileManager; + collisionModelManager = import->collisionModelManager; + } + + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // setup export interface + gameExport.version = GAME_API_VERSION; + gameExport.game = game; + gameExport.gameEdit = gameEdit; + + return &gameExport; +} +#if __GNUC__ >= 4 +#pragma GCC visibility pop +#endif + +/* +=========== +TestGameAPI +============ +*/ +void TestGameAPI() { + gameImport_t testImport; + gameExport_t testExport; + + testImport.sys = ::sys; + testImport.common = ::common; + testImport.cmdSystem = ::cmdSystem; + testImport.cvarSystem = ::cvarSystem; + testImport.fileSystem = ::fileSystem; + testImport.renderSystem = ::renderSystem; + testImport.soundSystem = ::soundSystem; + testImport.renderModelManager = ::renderModelManager; + testImport.uiManager = ::uiManager; + testImport.declManager = ::declManager; + testImport.AASFileManager = ::AASFileManager; + testImport.collisionModelManager = ::collisionModelManager; + + testExport = *GetGameAPI( &testImport ); +} + +/* +=========== +idGameLocal::idGameLocal +============ +*/ +idGameLocal::idGameLocal() { + Clear(); +} + +/* +=========== +idGameLocal::Clear +============ +*/ +void idGameLocal::Clear() { + int i; + + serverInfo.Clear(); + numClients = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + persistentPlayerInfo[i].Clear(); + } + memset( entities, 0, sizeof( entities ) ); + memset( spawnIds, -1, sizeof( spawnIds ) ); + firstFreeEntityIndex[0] = 0; + firstFreeEntityIndex[1] = ENTITYNUM_FIRST_NON_REPLICATED; + num_entities = 0; + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortPushers = false; + sortTeamMasters = false; + persistentLevelInfo.Clear(); + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + random.SetSeed( 0 ); + world = NULL; + frameCommandThread = NULL; + testmodel = NULL; + testFx = NULL; + clip.Shutdown(); + pvs.Shutdown(); + sessionCommand.Clear(); + locationEntities = NULL; + smokeParticles = NULL; + editEntities = NULL; + entityHash.Clear( 1024, MAX_GENTITIES ); + inCinematic = false; + framenum = 0; + previousTime = 0; + time = 0; + vacuumAreaNum = 0; + mapFileName.Clear(); + mapFile = NULL; + spawnCount = INITIAL_SPAWN_COUNT; + mapSpawnCount = 0; + camera = NULL; + aasList.Clear(); + aasNames.Clear(); + lastAIAlertEntity = NULL; + lastAIAlertTime = 0; + spawnArgs.Clear(); + gravity.Set( 0, 0, -1 ); + playerPVS.h = (unsigned int)-1; + playerConnectedAreas.h = (unsigned int)-1; + gamestate = GAMESTATE_UNINITIALIZED; + influenceActive = false; + + realClientTime = 0; + isNewFrame = true; + clientSmoothing = 0.1f; + entityDefBits = 0; + + nextGibTime = 0; + globalMaterial = NULL; + newInfo.Clear(); + lastGUIEnt = NULL; + lastGUI = 0; + + eventQueue.Init(); + savedEventQueue.Init(); + + shellHandler = NULL; + selectedGroup = 0; + portalSkyEnt = NULL; + portalSkyActive = false; + + ResetSlowTimeVars(); + + lastCmdRunTimeOnClient.Zero(); + lastCmdRunTimeOnServer.Zero(); +} + +/* +=========== +idGameLocal::Init + + initialize the game object, only happens once at startup, not each level load +============ +*/ +void idGameLocal::Init() { + const idDict *dict; + idAAS *aas; + +#ifndef GAME_DLL + + TestGameAPI(); + +#else + + // initialize idLib + idLib::Init(); + + // register static cvars declared in the game + idCVar::RegisterStaticVars(); + + // initialize processor specific SIMD + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + +#endif + + Printf( "--------- Initializing Game ----------\n" ); + Printf( "gamename: %s\n", GAME_VERSION ); + Printf( "gamedate: %s\n", __DATE__ ); + + // register game specific decl types + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); + declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); + + // register game specific decl folders + declManager->RegisterDeclFolder( "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); + declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); + declManager->RegisterDeclFolder( "af", ".af", DECL_AF ); + declManager->RegisterDeclFolder( "newpdas", ".pda", DECL_PDA ); + + cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); + cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); + + Clear(); + + idEvent::Init(); + idClass::Init(); + + InitConsoleCommands(); + + shellHandler = new (TAG_SWF) idMenuHandler_Shell(); + + if(!g_xp_bind_run_once.GetBool()) { + //The default config file contains remapped controls that support the XP weapons + //We want to run this once after the base doom config file has run so we can + //have the correct xp binds + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec default.cfg\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "seta g_xp_bind_run_once 1\n" ); + cmdSystem->ExecuteCommandBuffer(); + } + + // load default scripts + program.Startup( SCRIPT_DEFAULT ); + + smokeParticles = new (TAG_PARTICLE) idSmokeParticles; + + // set up the aas + dict = FindEntityDefDict( "aas_types" ); + if ( dict == NULL ) { + Error( "Unable to find entityDef for 'aas_types'" ); + return; + } + + // allocate space for the aas + const idKeyValue *kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) { + aas = idAAS::Alloc(); + aasList.Append( aas ); + aasNames.Append( kv->GetValue() ); + kv = dict->MatchPrefix( "type", kv ); + } + + gamestate = GAMESTATE_NOMAP; + + Printf( "...%d aas types\n", aasList.Num() ); + Printf( "game initialized.\n" ); + Printf( "--------------------------------------\n" ); + +} + +/* +=========== +idGameLocal::Shutdown + + shut down the entire game +============ +*/ +void idGameLocal::Shutdown() { + + if ( !common ) { + return; + } + + Printf( "------------ Game Shutdown -----------\n" ); + + Shell_Cleanup(); + + mpGame.Shutdown(); + + MapShutdown(); + + aasList.DeleteContents( true ); + aasNames.Clear(); + + idAI::FreeObstacleAvoidanceNodes(); + + idEvent::Shutdown(); + + delete[] locationEntities; + locationEntities = NULL; + + delete smokeParticles; + smokeParticles = NULL; + + idClass::Shutdown(); + + // clear list with forces + idForce::ClearForceList(); + + // free the program data + program.FreeData(); + + // delete the .map file + delete mapFile; + mapFile = NULL; + + // free the collision map + collisionModelManager->FreeMap(); + + ShutdownConsoleCommands(); + + // free memory allocated by class objects + Clear(); + + // shut down the animation manager + animationLib.Shutdown(); + + Printf( "--------------------------------------\n" ); + +#ifdef GAME_DLL + + // remove auto-completion function pointers pointing into this DLL + cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME ); + + // enable leak test + Mem_EnableLeakTest( "game" ); + + // shutdown idLib + idLib::ShutDown(); + +#endif +} + +idCVar g_recordSaveGameTrace( "g_recordSaveGameTrace", "0", CVAR_BOOL, "" ); + +/* +=========== +idGameLocal::SaveGame + +save the current player state, level name, and level state +the session may have written some data to the file already +============ +*/ +void idGameLocal::SaveGame( idFile *f, idFile *strings ) { + int i; + idEntity *ent; + idEntity *link; + + int startTimeMs = Sys_Milliseconds(); + if ( g_recordSaveGameTrace.GetBool() ) { + bool result = BeginTraceRecording( "e:\\savegame_trace.pix2" ); + if ( !result ) { + //idLib::Printf( "BeginTraceRecording: error %d\n", GetLastError() ); + } + } + + idSaveGame savegame( f, strings, BUILD_NUMBER ); + + if ( g_flushSave.GetBool( ) == true ) { + // force flushing with each write... for tracking down + // save game bugs. + f->ForceFlush(); + } + + // go through all entities and threads and add them to the object list + for( i = 0; i < MAX_GENTITIES; i++ ) { + ent = entities[i]; + + if ( ent ) { + if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) { + continue; + } + for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) { + savegame.AddObject( link ); + } + } + } + + idList threads; + threads = idThread::GetThreads(); + + for( i = 0; i < threads.Num(); i++ ) { + savegame.AddObject( threads[i] ); + } + + // write out complete object list + savegame.WriteObjectList(); + + program.Save( &savegame ); + + savegame.WriteInt( g_skill.GetInteger() ); + + savegame.WriteDecls(); + + savegame.WriteDict( &serverInfo ); + + savegame.WriteInt( numClients ); + for( i = 0; i < numClients; i++ ) { + //savegame.WriteUsercmd( usercmds[ i ] ); + // Now that usercmds are handled by the idUserCmdMgr, + // do we need another solution here? + usercmd_t dummy; + savegame.WriteUsercmd( dummy ); + + savegame.WriteDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.WriteObject( entities[ i ] ); + savegame.WriteInt( spawnIds[ i ] ); + } + + // There shouldn't be any non-replicated entities in SP, + // so we shouldn't have to save the first free replicated entity index. + savegame.WriteInt( firstFreeEntityIndex[0] ); + savegame.WriteInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.WriteObject( world ); + + savegame.WriteInt( spawnedEntities.Num() ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( activeEntities.Num() ); + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( numEntitiesToDeactivate ); + savegame.WriteBool( sortPushers ); + savegame.WriteBool( sortTeamMasters ); + savegame.WriteDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.WriteFloat( globalShaderParms[ i ] ); + } + + savegame.WriteInt( random.GetSeed() ); + savegame.WriteObject( frameCommandThread ); + + // clip + // push + // pvs + + testmodel = NULL; + testFx = NULL; + + savegame.WriteString( sessionCommand ); + + // FIXME: save smoke particles + + savegame.WriteBool( inCinematic ); + + savegame.WriteInt( gameType ); + + savegame.WriteInt( framenum ); + savegame.WriteInt( previousTime ); + savegame.WriteInt( time ); + + savegame.WriteInt( vacuumAreaNum ); + + savegame.WriteInt( entityDefBits ); + + savegame.WriteInt( GetLocalClientNum() ); + + // snapshotEntities is used for multiplayer only + + savegame.WriteInt( realClientTime ); + savegame.WriteBool( isNewFrame ); + savegame.WriteFloat( clientSmoothing ); + + portalSkyEnt.Save( &savegame ); + savegame.WriteBool( portalSkyActive ); + + fast.Save( &savegame ); + slow.Save( &savegame ); + + savegame.WriteInt( slowmoState ); + savegame.WriteFloat( slowmoScale ); + savegame.WriteBool( quickSlowmoReset ); + + savegame.WriteBool( mapCycleLoaded ); + savegame.WriteInt( spawnCount ); + + if ( !locationEntities ) { + savegame.WriteInt( 0 ); + } else { + savegame.WriteInt( gameRenderWorld->NumAreas() ); + for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) { + savegame.WriteObject( locationEntities[ i ] ); + } + } + + savegame.WriteObject( camera ); + + savegame.WriteMaterial( globalMaterial ); + + lastAIAlertEntity.Save( &savegame ); + savegame.WriteInt( lastAIAlertTime ); + + savegame.WriteDict( &spawnArgs ); + + savegame.WriteInt( playerPVS.i ); + savegame.WriteInt( playerPVS.h ); + savegame.WriteInt( playerConnectedAreas.i ); + savegame.WriteInt( playerConnectedAreas.h ); + + savegame.WriteVec3( gravity ); + + // gamestate + + savegame.WriteBool( influenceActive ); + savegame.WriteInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // write out pending events + idEvent::Save( &savegame ); + + savegame.Close(); + + int endTimeMs = Sys_Milliseconds(); + idLib::Printf( "Save time: %dms\n", ( endTimeMs - startTimeMs ) ); + + if ( g_recordSaveGameTrace.GetBool() ) { + EndTraceRecording(); + g_recordSaveGameTrace.SetBool( false ); + } +} + +/* +=========== +idGameLocal::GetSaveGameDetails +============ +*/ +void idGameLocal::GetSaveGameDetails( idSaveGameDetails & gameDetails ) { + idLocationEntity * locationEnt = LocationForPoint( gameLocal.GetLocalPlayer()->GetEyePosition() ); + const char * locationStr = locationEnt ? locationEnt->GetLocation() : idLocalization::GetString( "#str_02911" ); + + idStrStatic< MAX_OSPATH > shortMapName = mapFileName; + shortMapName.StripFileExtension(); + shortMapName.StripLeading( "maps/" ); + + const idDeclEntityDef * mapDef = static_cast(declManager->FindType( DECL_MAPDEF, shortMapName, false )); + const char * mapPrettyName = mapDef ? idLocalization::GetString( mapDef->dict.GetString( "name", shortMapName ) ) : shortMapName.c_str(); + idPlayer * player = GetClientByNum( 0 ); + int playTime = player ? player->GetPlayedTime() : 0; + gameExpansionType_t expansionType = player ? player->GetExpansionType() : GAME_BASE; + + gameDetails.descriptors.Clear(); + gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_EXPANSION, expansionType ); + gameDetails.descriptors.Set( SAVEGAME_DETAIL_FIELD_MAP, mapPrettyName ); + gameDetails.descriptors.Set( SAVEGAME_DETAIL_FIELD_MAP_LOCATE, locationStr ); + gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_SAVE_VERSION, BUILD_NUMBER ); + gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_DIFFICULTY, g_skill.GetInteger() ); + gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_PLAYTIME, playTime ); + + // PS3 only strings that use the dict just set + + // even though we don't use this when we enumerate, when we save, we use this descriptors file later so we need the date populated now + gameDetails.date = ::time( NULL ); +} + +/* +=========== +idGameLocal::GetPersistentPlayerInfo +============ +*/ +const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) { + idEntity *ent; + + persistentPlayerInfo[ clientNum ].Clear(); + ent = entities[ clientNum ]; + if ( ent && ent->IsType( idPlayer::Type ) ) { + static_cast(ent)->SavePersistantInfo(); + } + + return persistentPlayerInfo[ clientNum ]; +} + +/* +=========== +idGameLocal::SetPersistentPlayerInfo +============ +*/ +void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) { + persistentPlayerInfo[ clientNum ] = playerInfo; +} + +/* +============ +idGameLocal::Printf +============ +*/ +void idGameLocal::Printf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::DPrintf +============ +*/ +void idGameLocal::DPrintf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::Warning +============ +*/ +void idGameLocal::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Warning( "%s", text ); + } else { + common->Warning( "%s", text ); + } +} + +/* +============ +idGameLocal::DWarning +============ +*/ +void idGameLocal::DWarning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Warning( "%s", text ); + } else { + common->DWarning( "%s", text ); + } +} + +/* +============ +idGameLocal::Error +============ +*/ +void idGameLocal::Error( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Error( "%s", text ); + } else { + common->Error( "%s", text ); + } +} + +/* +=============== +gameError +=============== +*/ +void gameError( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + gameLocal.Error( "%s", text ); +} + +/* +======================== +idGameLocal::SetServerGameTimeMs +======================== +*/ +void idGameLocal::SetServerGameTimeMs( const int time ) { + previousServerTime = this->serverTime; + this->serverTime = time; +} + +/* +======================== +idGameLocal::GetServerGameTimeMs +======================== +*/ +int idGameLocal::GetServerGameTimeMs() const { + return serverTime; +} + +/* +=========== +idGameLocal::SetServerInfo +============ +*/ +void idGameLocal::SetServerInfo( const idDict &_serverInfo ) { + serverInfo = _serverInfo; + + if ( gameType == GAME_LASTMAN ) { + if ( serverInfo.GetInt( "si_fraglimit" ) <= 0 ) { + common->Warning( "Last Man Standing - setting fraglimit 1" ); + serverInfo.SetInt( "si_fraglimit", 1 ); + } + } +} + +/* +=========== +idGameLocal::GetServerInfo +============ +*/ +const idDict & idGameLocal::GetServerInfo() { + return serverInfo; +} + +/* +=================== +idGameLocal::LoadMap + +Initializes all map variables common to both save games and spawned games. +=================== +*/ +void idGameLocal::LoadMap( const char * mapName, int randseed ) { + bool sameMap = (mapFile && idStr::Icmp(mapFileName, mapName) == 0); + + // clear the sound system + gameSoundWorld->ClearAllSoundEmitters(); + + // clear envirosuit sound fx + gameSoundWorld->SetEnviroSuit( false ); + gameSoundWorld->SetSlowmoSpeed( 1.0f ); + + InitAsyncNetwork(); + + if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) { + // load the .map file + if ( mapFile ) { + delete mapFile; + } + mapFile = new (TAG_GAME) idMapFile; + if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) { + delete mapFile; + mapFile = NULL; + Error( "Couldn't load %s", mapName ); + } + } + mapFileName = mapFile->GetName(); + + // load the collision map + collisionModelManager->LoadMap( mapFile ); + collisionModelManager->Preload( mapName ); + + numClients = 0; + + // initialize all entities for this game + memset( entities, 0, sizeof( entities ) ); + memset( spawnIds, -1, sizeof( spawnIds ) ); + spawnCount = INITIAL_SPAWN_COUNT; + + spawnedEntities.Clear(); + activeEntities.Clear(); + aimAssistEntities.Clear(); + numEntitiesToDeactivate = 0; + sortTeamMasters = false; + sortPushers = false; + lastGUIEnt = NULL; + lastGUI = 0; + + globalMaterial = NULL; + + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + + // These used to be a non-pot adjustment for portal skies + // they're no longer needed, but we can't update the materials + globalShaderParms[4] = 1.0f; + globalShaderParms[5] = 1.0f; + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + num_entities = MAX_CLIENTS; + firstFreeEntityIndex[0] = MAX_CLIENTS; + + // reset the random number generator. + random.SetSeed( common->IsMultiplayer() ? randseed : 0 ); + + camera = NULL; + world = NULL; + testmodel = NULL; + testFx = NULL; + + lastAIAlertEntity = NULL; + lastAIAlertTime = 0; + + previousTime = 0; + time = 0; + framenum = 0; + sessionCommand = ""; + nextGibTime = 0; + + portalSkyEnt = NULL; + portalSkyActive = false; + + ResetSlowTimeVars(); + + vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this + + if ( !editEntities ) { + editEntities = new (TAG_GAME) idEditEntities; + } + + gravity.Set( 0, 0, -g_gravity.GetFloat() ); + + spawnArgs.Clear(); + + inCinematic = false; + + clip.Init(); + + common->UpdateLevelLoadPacifier(); + + pvs.Init(); + + common->UpdateLevelLoadPacifier(); + + playerPVS.i = -1; + playerConnectedAreas.i = -1; + + // load navigation system for all the different monster sizes + for ( int i = 0; i < aasNames.Num(); i++ ) { + aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); + } + + // clear the smoke particle free list + smokeParticles->Init(); + + common->UpdateLevelLoadPacifier(); + + // cache miscellaneous media references + FindEntityDef( "preCacheExtras", false ); + FindEntityDef( "ammo_types", false ); + FindEntityDef( "ammo_names", false ); + FindEntityDef( "ammo_types_d3xp", false ); + + FindEntityDef( "damage_noair", false ); + FindEntityDef( "damage_moverCrush", false ); + FindEntityDef( "damage_crush", false ); + FindEntityDef( "damage_triggerhurt_1000", false ); + FindEntityDef( "damage_telefrag", false ); + FindEntityDef( "damage_suicide", false ); + FindEntityDef( "damage_explosion", false ); + FindEntityDef( "damage_generic", false ); + FindEntityDef( "damage_painTrigger", false ); + FindEntityDef( "damage_thrown_ragdoll", false ); + FindEntityDef( "damage_gib", false ); + FindEntityDef( "damage_softfall", false ); + FindEntityDef( "damage_hardfall", false ); + FindEntityDef( "damage_fatalfall", false ); + + FindEntityDef( "envirosuit_light", false ); + + declManager->FindType( DECL_EMAIL, "highScore", false ); + declManager->FindType( DECL_EMAIL, "MartianBuddyGameComplete", false ); + + declManager->FindMaterial( "itemHighlightShell" ); + + common->UpdateLevelLoadPacifier(); + + if ( !sameMap ) { + mapFile->RemovePrimitiveData(); + } +} + +/* +=================== +idGameLocal::LocalMapRestart +=================== +*/ +void idGameLocal::LocalMapRestart( ) { + int i, latchSpawnCount; + + Printf( "----------- Game Map Restart ------------\n" ); + + gamestate = GAMESTATE_SHUTDOWN; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::Type ) ) { + static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart(); + } + } + + eventQueue.Shutdown(); + savedEventQueue.Shutdown(); + + MapClear( false ); + + + + // clear the smoke particle free list + smokeParticles->Init(); + + // clear the sound system + if ( gameSoundWorld ) { + gameSoundWorld->ClearAllSoundEmitters(); + // clear envirosuit sound fx + gameSoundWorld->SetEnviroSuit( false ); + gameSoundWorld->SetSlowmoSpeed( 1.0f ); + } + + // the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId + // if we don't do that, network clients are confused and don't show any map entities + latchSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + + gamestate = GAMESTATE_STARTUP; + + program.Restart(); + + InitScriptForMap(); + + MapPopulate(); + + // once the map is populated, set the spawnCount back to where it was so we don't risk any collision + // (note that if there are no players in the game, we could just leave it at it's current value) + spawnCount = latchSpawnCount; + + // setup the client entities again + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::Type ) ) { + static_cast< idPlayer * >( entities[ i ] )->Restart(); + } + } + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); +} + +/* +=================== +idGameLocal::MapRestart +=================== +*/ +void idGameLocal::MapRestart() { + if ( common->IsClient() ) { + LocalMapRestart(); + } else { + idBitMsg msg; + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_RESTART, msg, false ); + + LocalMapRestart(); + mpGame.MapRestart(); + } + + if ( common->IsMultiplayer() ) { + gameLocal.mpGame.ReloadScoreboard(); + } +} + +/* +=================== +idGameLocal::MapRestart_f +=================== +*/ +void idGameLocal::MapRestart_f( const idCmdArgs &args ) { + if ( !common->IsMultiplayer() || common->IsClient() ) { + common->Printf( "server is not running - use spawnServer\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + return; + } + + gameLocal.MapRestart( ); +} + +/* +=================== +idGameLocal::MapPopulate +=================== +*/ +void idGameLocal::MapPopulate() { + + if ( common->IsMultiplayer() ) { + cvarSystem->SetCVarBool( "r_skipSpecular", false ); + } + // parse the key/value pairs and spawn entities + SpawnMapEntities(); + + // mark location entities in all connected areas + SpreadLocations(); + + // prepare the list of randomized initial spawn spots + RandomizeInitialSpawns(); + + // spawnCount - 1 is the number of entities spawned into the map, their indexes started at MAX_CLIENTS (included) + // mapSpawnCount is used as the max index of map entities, it's the first index of non-map entities + mapSpawnCount = MAX_CLIENTS + spawnCount - 1; + + // execute pending events before the very first game frame + // this makes sure the map script main() function is called + // before the physics are run so entities can bind correctly + Printf( "==== Processing events ====\n" ); + idEvent::ServiceEvents(); + + // Must set GAME_FPS for script after populating, because some maps run their own scripts + // when spawning the world, and GAME_FPS will not be found before then. + SetScriptFPS( com_engineHz_latched ); +} + +/* +=================== +idGameLocal::InitFromNewMap +=================== +*/ +void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, int gameMode, int randseed ) { + + this->gameType = (gameType_t)idMath::ClampInt( GAME_SP, GAME_COUNT-1, gameMode ); + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "----------- Game Map Init ------------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + gameSoundWorld = soundWorld; + + if ( common->IsMultiplayer() ) { + g_skill.SetInteger( 1 ); + } else { + g_skill.SetInteger( idMath::ClampInt( 0, 3, g_skill.GetInteger() ) ); + } + + LoadMap( mapName, randseed ); + + InitScriptForMap(); + + MapPopulate(); + + mpGame.Reset(); + mpGame.Precache(); + + SyncPlayersWithLobbyUsers( true ); + + // free up any unused animations + animationLib.FlushUnusedAnims(); + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); +} + +/* +================= +idGameLocal::InitFromSaveGame +================= +*/ +bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, idFile * saveGameFile, idFile * stringTableFile, int saveGameVersion ) { + int i; + int num; + idEntity *ent; + idDict si; + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "------- Game Map Init SaveGame -------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + gameSoundWorld = soundWorld; + + SetScriptFPS( com_engineHz_latched ); + + // load the map needed for this savegame + LoadMap( mapName, 0 ); + + idFile_SaveGamePipelined * pipelineFile = new (TAG_SAVEGAMES) idFile_SaveGamePipelined(); + pipelineFile->OpenForReading( saveGameFile ); + idRestoreGame savegame( pipelineFile, stringTableFile, saveGameVersion ); + + // Create the list of all objects in the game + savegame.CreateObjects(); + + // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame + if ( program.Restore( &savegame ) == false ) { + + // Abort the load process, and let the session know so that it can restart the level + // with the player persistent data. + savegame.DeleteObjects(); + program.Restart(); + return false; + } + + savegame.ReadInt( i ); + g_skill.SetInteger( i ); + + // precache any media specified in the map + savegame.ReadDecls(); + + savegame.ReadDict( &si ); + SetServerInfo( si ); + + savegame.ReadInt( numClients ); + for( i = 0; i < numClients; i++ ) { + //savegame.ReadUsercmd( usercmds[ i ] ); + // Now that usercmds are handled by the idUserCmdMgr, + // do we need another solution here? + usercmd_t dummy; + savegame.ReadUsercmd( dummy ); + savegame.ReadDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); + savegame.ReadInt( spawnIds[ i ] ); + + // restore the entityNumber + if ( entities[ i ] != NULL ) { + entities[ i ]->entityNumber = i; + } + } + + // Connect players with lobby users + // There should only be 1 player and 1 lobby user, but I'm using a loop just to be safe + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + int numLobbyUsers = lobby.GetNumLobbyUsers(); + int lobbyUserNum = 0; + assert( numLobbyUsers == 1 ); + for ( int i = 0; i < MAX_PLAYERS && lobbyUserNum < numLobbyUsers; i++ ) { + if ( entities[i] == NULL ) { + continue; + } + lobbyUserIDs[i] = lobby.GetLobbyUserIdByOrdinal( lobbyUserNum++ ); + } + + savegame.ReadInt( firstFreeEntityIndex[0] ); + savegame.ReadInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.ReadObject( reinterpret_cast( world ) ); + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->spawnNode.AddToEnd( spawnedEntities ); + } + } + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->activeNode.AddToEnd( activeEntities ); + } + } + + savegame.ReadInt( numEntitiesToDeactivate ); + savegame.ReadBool( sortPushers ); + savegame.ReadBool( sortTeamMasters ); + savegame.ReadDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.ReadFloat( globalShaderParms[ i ] ); + } + + savegame.ReadInt( i ); + random.SetSeed( i ); + + savegame.ReadObject( reinterpret_cast( frameCommandThread ) ); + + // clip + // push + // pvs + + // testmodel = "" + // testFx = "" + + savegame.ReadString( sessionCommand ); + + // FIXME: save smoke particles + + savegame.ReadBool( inCinematic ); + + savegame.ReadInt( (int &)gameType ); + + savegame.ReadInt( framenum ); + savegame.ReadInt( previousTime ); + savegame.ReadInt( time ); + + savegame.ReadInt( vacuumAreaNum ); + + savegame.ReadInt( entityDefBits ); + + // the localClientNum member of idGameLocal was removed, + // but to preserve savegame compatibility, we still need + // to read an int here even though it's not used. + int dummyLocalClientNum = 0; + savegame.ReadInt( dummyLocalClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.ReadInt( realClientTime ); + savegame.ReadBool( isNewFrame ); + savegame.ReadFloat( clientSmoothing ); + + portalSkyEnt.Restore( &savegame ); + savegame.ReadBool( portalSkyActive ); + + fast.Restore( &savegame ); + slow.Restore( &savegame ); + + framenum = MSEC_TO_FRAME_FLOOR( fast.time ); + + int blah; + savegame.ReadInt( blah ); + slowmoState = (slowmoState_t)blah; + + savegame.ReadFloat( slowmoScale ); + savegame.ReadBool( quickSlowmoReset ); + + if ( gameSoundWorld ) { + gameSoundWorld->SetSlowmoSpeed( slowmoScale ); + } + + savegame.ReadBool( mapCycleLoaded ); + savegame.ReadInt( spawnCount ); + + savegame.ReadInt( num ); + if ( num ) { + if ( num != gameRenderWorld->NumAreas() ) { + savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." ); + } + + locationEntities = new (TAG_GAME) idLocationEntity *[ num ]; + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); + } + } + + savegame.ReadObject( reinterpret_cast( camera ) ); + + savegame.ReadMaterial( globalMaterial ); + + lastAIAlertEntity.Restore( &savegame ); + savegame.ReadInt( lastAIAlertTime ); + + savegame.ReadDict( &spawnArgs ); + + savegame.ReadInt( playerPVS.i ); + savegame.ReadInt( (int &)playerPVS.h ); + savegame.ReadInt( playerConnectedAreas.i ); + savegame.ReadInt( (int &)playerConnectedAreas.h ); + + savegame.ReadVec3( gravity ); + + // gamestate is restored after restoring everything else + + savegame.ReadBool( influenceActive ); + savegame.ReadInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // Read out pending events + idEvent::Restore( &savegame ); + + savegame.RestoreObjects(); + + mpGame.Reset(); + + mpGame.Precache(); + + // free up any unused animations + animationLib.FlushUnusedAnims(); + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); + + delete pipelineFile; + pipelineFile = NULL; + + return true; +} + +/* +=========== +idGameLocal::MapClear +=========== +*/ +void idGameLocal::MapClear( bool clearClients ) { + int i; + + for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); + + if ( !clearClients ) { + // add back the hashes of the clients + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); + } + } + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; +} + +/* +=========== +idGameLocal::MapShutdown +============ +*/ +void idGameLocal::MapShutdown() { + Printf( "--------- Game Map Shutdown ----------\n" ); + + gamestate = GAMESTATE_SHUTDOWN; + + if ( gameRenderWorld ) { + // clear any debug lines, text, and polygons + gameRenderWorld->DebugClearLines( 0 ); + gameRenderWorld->DebugClearPolygons( 0 ); + } + + // clear out camera if we're in a cinematic + if ( inCinematic ) { + camera = NULL; + inCinematic = false; + } + + MapClear( true ); + + common->UpdateLevelLoadPacifier(); + + // reset the script to the state it was before the map was started + program.Restart(); + + if ( smokeParticles ) { + smokeParticles->Shutdown(); + } + + pvs.Shutdown(); + + common->UpdateLevelLoadPacifier(); + + clip.Shutdown(); + idClipModel::ClearTraceModelCache(); + + common->UpdateLevelLoadPacifier(); + + collisionModelManager->FreeMap(); // Fixes an issue where when maps were reloaded the materials wouldn't get their surfaceFlags re-set. Now we free the map collision model forcing materials to be reparsed. + + common->UpdateLevelLoadPacifier(); + + ShutdownAsyncNetwork(); + + idStrStatic< MAX_OSPATH > mapName = mapFileName; + mapName.StripPath(); + mapName.StripFileExtension(); + fileSystem->UnloadMapResources( mapName ); + + mapFileName.Clear(); + + gameRenderWorld = NULL; + gameSoundWorld = NULL; + + gamestate = GAMESTATE_NOMAP; + + Printf( "--------------------------------------\n" ); +} + +/* +======================== +idGameLocal::GetAimAssistAngles +======================== +*/ +void idGameLocal::GetAimAssistAngles( idAngles & angles ) { + angles.Zero(); + + // Take a look at serializing this to the clients + idPlayer * player = GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + idAimAssist * aimAssist = player->GetAimAssist(); + if ( aimAssist == NULL ) { + return; + } + + aimAssist->GetAngleCorrection( angles ); +} + +/* +======================== +idGameLocal::GetAimAssistSensitivity +======================== +*/ +float idGameLocal::GetAimAssistSensitivity() { + // Take a look at serializing this to the clients + idPlayer * player = GetLocalPlayer(); + if ( player == NULL ) { + return 1.0f; + } + + idAimAssist * aimAssist = player->GetAimAssist(); + if ( aimAssist == NULL ) { + return 1.0f; + } + + return aimAssist->GetFrictionScalar(); +} + +/* +======================== +idGameLocal::MapPeerToClient +======================== +*/ +int idGameLocal::MapPeerToClient( int peer ) const { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + for ( int userNum = 0; userNum < lobbyUserIDs.Num(); ++userNum ) { + const int peerForUser = lobby.PeerIndexFromLobbyUser( lobbyUserIDs[userNum] ); + if ( peerForUser == peer ) { + return userNum; + } + } + + return -1; +} + +/* +======================== +idGameLocal::GetLocalClientNum +======================== +*/ +int idGameLocal::GetLocalClientNum() const { + localUserHandle_t localUserHandle = session->GetSignInManager().GetMasterLocalUserHandle(); + if ( !localUserHandle.IsValid() ) { + return 0; + } + + for ( int i = 0; i < lobbyUserIDs.Num(); i++ ) { + lobbyUserID_t lobbyUserID = lobbyUserIDs[i]; + if ( localUserHandle == lobbyUserID.GetLocalUserHandle() ) { + return i; + } + } + return 0; +} + +/* +=================== +idGameLocal::Preload + +=================== +*/ +void idGameLocal::Preload( const idPreloadManifest &manifest ) { + animationLib.Preload( manifest ); +} + +/* +=================== +idGameLocal::CacheDictionaryMedia + +This is called after parsing an EntityDef and for each entity spawnArgs before +merging the entitydef. It could be done post-merge, but that would +avoid the fast pre-cache check associated with each entityDef +=================== +*/ +void idGameLocal::CacheDictionaryMedia( const idDict *dict ) { + const idKeyValue *kv; + + kv = dict->MatchPrefix( "model" ); + while( kv ) { + if ( kv->GetValue().Length() ) { + declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() ); + // precache model/animations + if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( kv->GetValue() ); + // precache .cm files only + collisionModelManager->LoadModel( kv->GetValue() ); + } + } + kv = dict->MatchPrefix( "model", kv ); + } + + kv = dict->FindKey( "s_shader" ); + if ( kv != NULL && kv->GetValue().Length() ) { + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } + + kv = dict->MatchPrefix( "snd", NULL ); + while ( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } + kv = dict->MatchPrefix( "snd", kv ); + } + + + kv = dict->MatchPrefix( "gui", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" ) + || !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 ) + || !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) { + // unfortunate flag names, they aren't actually a gui + } else { + declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() ); + idUserInterface *gui = uiManager->Alloc(); + if ( gui ) { + gui->InitFromFile( kv->GetValue() ); + uiManager->DeAlloc( gui ); + } + } + } + kv = dict->MatchPrefix( "gui", kv ); + } + + kv = dict->FindKey( "texture" ); + if ( kv != NULL && kv->GetValue().Length() ) { + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } + + kv = dict->MatchPrefix( "mtr", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } + kv = dict->MatchPrefix( "mtr", kv ); + } + + // handles hud icons + kv = dict->MatchPrefix( "inv_icon", NULL ); + while ( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } + kv = dict->MatchPrefix( "inv_icon", kv ); + } + + // handles teleport fx.. this is not ideal but the actual decision on which fx to use + // is handled by script code based on the teleport number + kv = dict->MatchPrefix( "teleport", NULL ); + if ( kv != NULL && kv->GetValue().Length() ) { + int teleportType = atoi( kv->GetValue() ); + const char *p = ( teleportType ) ? va( "fx/teleporter%i.fx", teleportType ) : "fx/teleporter.fx"; + declManager->FindType( DECL_FX, p ); + } + + kv = dict->MatchPrefix( "fx", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() ); + declManager->FindType( DECL_FX, kv->GetValue() ); + } + kv = dict->MatchPrefix( "fx", kv ); + } + + kv = dict->MatchPrefix( "smoke", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + idStr prtName = kv->GetValue(); + int dash = prtName.Find('-'); + if ( dash > 0 ) { + prtName = prtName.Left( dash ); + } + declManager->FindType( DECL_PARTICLE, prtName ); + } + kv = dict->MatchPrefix( "smoke", kv ); + } + + kv = dict->MatchPrefix( "skin", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() ); + declManager->FindType( DECL_SKIN, kv->GetValue() ); + } + kv = dict->MatchPrefix( "skin", kv ); + } + + kv = dict->MatchPrefix( "def", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + FindEntityDef( kv->GetValue().c_str(), false ); + } + kv = dict->MatchPrefix( "def", kv ); + } + + // Precache all available grabber "catch" damage decls + kv = dict->MatchPrefix( "def_damage", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + FindEntityDef( kv->GetValue() + "_catch", false ); + } + kv = dict->MatchPrefix( "def_damage", kv ); + } + + // Should have been def_monster_damage!! + kv = dict->FindKey( "monster_damage" ); + if ( kv != NULL && kv->GetValue().Length() ) { + FindEntityDef( kv->GetValue(), false ); + } + + kv = dict->MatchPrefix( "item", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + FindEntityDefDict( kv->GetValue().c_str(), false ); + } + kv = dict->MatchPrefix( "item", kv ); + } + + kv = dict->MatchPrefix( "pda_name", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_PDA, kv->GetValue().c_str(), false ); + } + kv = dict->MatchPrefix( "pda_name", kv ); + } + + kv = dict->MatchPrefix( "video", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_VIDEO, kv->GetValue().c_str(), false ); + } + kv = dict->MatchPrefix( "video", kv ); + } + + kv = dict->MatchPrefix( "audio", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_AUDIO, kv->GetValue().c_str(), false ); + } + kv = dict->MatchPrefix( "audio", kv ); + } + + kv = dict->MatchPrefix( "email", NULL ); + while( kv != NULL ) { + if ( kv->GetValue().Length() ) { + declManager->FindType( DECL_EMAIL, kv->GetValue().c_str(), false ); + } + kv = dict->MatchPrefix( "email", kv ); + } +} + +/* +=========== +idGameLocal::InitScriptForMap +============ +*/ +void idGameLocal::InitScriptForMap() { + // create a thread to run frame commands on + frameCommandThread = new idThread(); + frameCommandThread->ManualDelete(); + frameCommandThread->SetThreadName( "frameCommands" ); + + // run the main game script function (not the level specific main) + const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC ); + if ( func != NULL ) { + idThread *thread = new idThread( func ); + if ( thread->Start() ) { + // thread has finished executing, so delete it + delete thread; + } + } +} + +/* +=================== +idGameLocal::SetScriptFPS +=================== +*/ +void idGameLocal::SetScriptFPS( const float engineHz ) { + idVarDef * fpsDef = program.GetDef( &type_float, "GAME_FPS", &def_namespace ); + if ( fpsDef != NULL ) { + eval_t fpsValue; + fpsValue._float = engineHz; + fpsDef->SetValue( fpsValue, false ); + } +} + +/* +=========== +idGameLocal::GetMPPlayerDefName +============ +*/ +const char * idGameLocal::GetMPPlayerDefName() const { + if ( gameType == GAME_CTF ) { + return "player_doommarine_ctf"; + } + + return "player_doommarine_mp"; +} + +/* +=========== +idGameLocal::SpawnPlayer +============ +*/ +void idGameLocal::SpawnPlayer( int clientNum ) { + idEntity *ent; + idDict args; + + // they can connect + Printf( "SpawnPlayer: %i\n", clientNum ); + + args.SetInt( "spawn_entnum", clientNum ); + args.Set( "name", va( "player%d", clientNum + 1 ) ); + if ( common->IsMultiplayer() ) { + args.Set( "classname", GetMPPlayerDefName() ); + } else { + // precache the player + args.Set( "classname", gameLocal.world->spawnArgs.GetString( "def_player", "player_doommarine" ) ); + } + + // It's important that we increment numClients before calling SpawnEntityDef, because some + // entities want to check gameLocal.numClients to see who to operate on (such as target_removeweapons) + if ( clientNum >= numClients ) { + numClients = clientNum + 1; + } + + if ( !SpawnEntityDef( args, &ent ) || clientNum >= MAX_GENTITIES || entities[ clientNum ] == NULL ) { + Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) ); + } + + // make sure it's a compatible class + if ( !ent->IsType( idPlayer::Type ) ) { + Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() ); + } + mpGame.SpawnPlayer( clientNum ); +} + +/* +================ +idGameLocal::GetClientByNum +================ +*/ +idPlayer *idGameLocal::GetClientByNum( int current ) const { + if ( current < 0 || current >= numClients ) { + current = 0; + } + if ( entities[current] ) { + return static_cast( entities[ current ] ); + } + return NULL; +} + +/* +================ +idGameLocal::GetNextClientNum +================ +*/ +int idGameLocal::GetNextClientNum( int _current ) const { + int i, current; + + current = 0; + for ( i = 0; i < numClients; i++) { + current = ( _current + i + 1 ) % numClients; + if ( entities[ current ] && entities[ current ]->IsType( idPlayer::Type ) ) { + return current; + } + } + + return current; +} + +/* +================ +idGameLocal::GetLocalPlayer + +Nothing in the game tic should EVER make a decision based on what the +local client number is, it shouldn't even be aware that there is a +draw phase even happening. This just returns client 0, which will +be correct for single player. +================ +*/ +idPlayer *idGameLocal::GetLocalPlayer() const { + if ( GetLocalClientNum() < 0 ) { + return NULL; + } + + if ( !entities[ GetLocalClientNum() ] || !entities[ GetLocalClientNum() ]->IsType( idPlayer::Type ) ) { + // not fully in game yet + return NULL; + } + return static_cast( entities[ GetLocalClientNum() ] ); +} + +/* +================ +idGameLocal::SetupClientPVS +================ +*/ +pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) { + if ( player->GetPrivateCameraView() ) { + return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() ); + } else if ( camera ) { + return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() ); + } else { + return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() ); + } +} + +/* +================ +idGameLocal::SetupPlayerPVS +================ +*/ +void idGameLocal::SetupPlayerPVS() { + int i; + idEntity * ent; + idPlayer * player; + pvsHandle_t otherPVS, newPVS; + + playerPVS.i = -1; + for ( i = 0; i < numClients; i++ ) { + ent = entities[i]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + + player = static_cast(ent); + + if ( playerPVS.i == -1 ) { + playerPVS = GetClientPVS( player, PVS_NORMAL ); + } else { + otherPVS = GetClientPVS( player, PVS_NORMAL ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + pvs.FreeCurrentPVS( playerPVS ); + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + } + + if ( playerConnectedAreas.i == -1 ) { + playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS ); + } else { + otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + + // if portalSky is preset, then merge into pvs so we get rotating brushes, etc + if ( portalSkyEnt.GetEntity() ) { + idEntity *skyEnt = portalSkyEnt.GetEntity(); + + otherPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + pvs.FreeCurrentPVS( playerPVS ); + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + + otherPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + } +} + +/* +================ +idGameLocal::FreePlayerPVS +================ +*/ +void idGameLocal::FreePlayerPVS() { + if ( playerPVS.i != -1 ) { + pvs.FreeCurrentPVS( playerPVS ); + playerPVS.i = -1; + } + if ( playerConnectedAreas.i != -1 ) { + pvs.FreeCurrentPVS( playerConnectedAreas ); + playerConnectedAreas.i = -1; + } +} + +/* +================ +idGameLocal::InPlayerPVS + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerPVS( idEntity *ent ) const { + if ( playerPVS.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + +/* +================ +idGameLocal::InPlayerConnectedArea + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const { + if ( playerConnectedAreas.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + +/* +================ +idGameLocal::UpdateGravity +================ +*/ +void idGameLocal::UpdateGravity() { + idEntity *ent; + + if ( g_gravity.IsModified() ) { + if ( g_gravity.GetFloat() == 0.0f ) { + g_gravity.SetFloat( 1.0f ); + } + gravity.Set( 0, 0, -g_gravity.GetFloat() ); + + // update all physics objects + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idAFEntity_Generic::Type ) ) { + idPhysics *phys = ent->GetPhysics(); + if ( phys ) { + phys->SetGravity( gravity ); + } + } + } + g_gravity.ClearModified(); + } +} + +/* +================ +idGameLocal::GetGravity +================ +*/ +const idVec3 &idGameLocal::GetGravity() const { + return gravity; +} + +/* +================ +idGameLocal::SortActiveEntityList + + Sorts the active entity list such that pushing entities come first, + actors come next and physics team slaves appear after their master. +================ +*/ +void idGameLocal::SortActiveEntityList() { + idEntity *ent, *next_ent, *master, *part; + + // if the active entity list needs to be reordered to place physics team masters at the front + if ( sortTeamMasters ) { + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( master && master == ent ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + // if the active entity list needs to be reordered to place pushers at the front + if ( sortPushers ) { + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an actor on the team + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { + if ( part->GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + break; + } + } + // if there is an actor on the team + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an entity on the team using parametric physics + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { + if ( part->GetPhysics()->IsType( idPhysics_Parametric::Type ) ) { + break; + } + } + // if there is an entity on the team using parametric physics + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + } + + sortTeamMasters = false; + sortPushers = false; +} + + + +/* +======================== +idGameLocal::SetInterpolation +======================== +*/ +void idGameLocal::SetInterpolation( const float fraction, const int serverGameMS, const int ssStartTime, const int ssEndTime ) { + netInterpolationInfo.previousServerGameMs = netInterpolationInfo.serverGameMs; + netInterpolationInfo.pct = fraction; + netInterpolationInfo.serverGameMs = serverGameMS; + netInterpolationInfo.ssStartTime = ssStartTime; + netInterpolationInfo.ssEndTime = ssEndTime; +} + + +/* +================ +idGameLocal::RunTimeGroup2 +================ +*/ +void idGameLocal::RunTimeGroup2( idUserCmdMgr & userCmdMgr ) { + idEntity *ent; + int num = 0; + + SelectTimeGroup( true ); + + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->timeGroup != TIME_GROUP2 ) { + continue; + } + RunEntityThink( *ent, userCmdMgr ); + num++; + } + + SelectTimeGroup( false ); +} + +/* +================ +idGameLocal::RunEntityThink +================ +*/ +void idGameLocal::RunEntityThink( idEntity & ent, idUserCmdMgr & userCmdMgr ) { + if ( ent.entityNumber < MAX_PLAYERS ) { + // Players may run more than one think per frame in MP, + // if there is a large buffer of usercmds from the network. + // Players will always run exactly one think in singleplayer. + RunAllUserCmdsForPlayer( userCmdMgr, ent.entityNumber ); + } else { + // Non-player entities always run one think. + ent.Think(); + } +} + +idCVar g_recordTrace( "g_recordTrace", "0", CVAR_BOOL, "" ); + +/* +================ +idGameLocal::RunFrame +================ +*/ +void idGameLocal::RunFrame( idUserCmdMgr & cmdMgr, gameReturn_t & ret ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + + idPlayer *player; + const renderView_t *view; + + if ( g_recordTrace.GetBool() ) { + bool result = BeginTraceRecording( "e:\\gametrace.pix2" ); + if ( !result ) { + //idLib::Printf( "BeginTraceRecording: error %d\n", GetLastError() ); + } + } + +#ifdef _DEBUG + if ( common->IsMultiplayer() ) { + assert( !common->IsClient() ); + } +#endif + + if ( gameRenderWorld == NULL ) { + return; + } + + SyncPlayersWithLobbyUsers( false ); + ServerSendNetworkSyncCvars(); + + player = GetLocalPlayer(); + + if ( !common->IsMultiplayer() && g_stopTime.GetBool() ) { + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time + 1 ); + + // set the user commands for this frame + if ( player ) { + player->HandleUserCmds( cmdMgr.GetUserCmdForPlayer( GetLocalClientNum() ) ); + cmdMgr.MakeReadPtrCurrentForPlayer( GetLocalClientNum() ); + player->Think(); + } + } else { + // update the game time + framenum++; + fast.previousTime = FRAME_TO_MSEC( framenum - 1 ); + fast.time = FRAME_TO_MSEC( framenum ); + fast.realClientTime = fast.time; + SetServerGameTimeMs( fast.time ); + + ComputeSlowScale(); + + slow.previousTime = slow.time; + slow.time += idMath::Ftoi( ( fast.time - fast.previousTime ) * slowmoScale ); + slow.realClientTime = slow.time; + + SelectTimeGroup( false ); + +#ifdef GAME_DLL + // allow changing SIMD usage on the fly + if ( com_forceGenericSIMD.IsModified() ) { + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + } +#endif + + // make sure the random number counter is used each frame so random events + // are influenced by the player's actions + random.RandomInt(); + + if ( player ) { + // update the renderview so that any gui videos play from the right frame + view = player->GetRenderView(); + if ( view ) { + gameRenderWorld->SetRenderView( view ); + } + } + + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time ); + + // clear any debug polygons from a previous frame + gameRenderWorld->DebugClearPolygons( time ); + + // free old smoke particles + smokeParticles->FreeSmokes(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + // create a merged pvs for all players + SetupPlayerPVS(); + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + + // let entities think + if ( g_timeentities.GetFloat() ) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + timer_singlethink.Clear(); + timer_singlethink.Start(); + RunEntityThink( *ent, cmdMgr ); + timer_singlethink.Stop(); + ms = timer_singlethink.Milliseconds(); + if ( ms >= g_timeentities.GetFloat() ) { + Printf( "%d: entity '%s': %.1f ms\n", time, ent->name.c_str(), ms ); + } + num++; + } + } else { + if ( inCinematic ) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + RunEntityThink( *ent, cmdMgr ); + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->timeGroup != TIME_GROUP1 ) { + continue; + } + RunEntityThink( *ent, cmdMgr ); + num++; + } + } + } + + RunTimeGroup2( cmdMgr ); + + // Run catch-up for any client projectiles. + // This is done after the main think so that all projectiles will be up-to-date + // when snapshots are created. + //if ( common->IsMultiplayer() ) { + //while ( SimulateProjectiles() ) { + //clientGame.gameLibEffects.Update( clientGame.GetGameMs(), clientGame.GetGameMsPerFrame(), clientGame.GetServerGameTime() ); + //} + //} + + + // remove any entities that have stopped thinking + if ( numEntitiesToDeactivate ) { + idEntity *next_ent; + int c = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + if ( !ent->thinkFlags ) { + ent->activeNode.Remove(); + c++; + } + } + //assert( numEntitiesToDeactivate == c ); + numEntitiesToDeactivate = 0; + } + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + // service any pending events + idEvent::ServiceEvents(); + + // service pending fast events + SelectTimeGroup( true ); + idEvent::ServiceFastEvents(); + SelectTimeGroup( false ); + + timer_events.Stop(); + + // free the player pvs + FreePlayerPVS(); + + // do multiplayer related stuff + if ( common->IsMultiplayer() ) { + mpGame.Run(); + } + + // display how long it took to calculate the current game frame + if ( g_frametime.GetBool() ) { + Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", + time, timer_think.Milliseconds() + timer_events.Milliseconds(), + timer_think.Milliseconds(), timer_events.Milliseconds(), num ); + } + + BuildReturnValue( ret ); + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + if ( g_recordTrace.GetBool() ) { + EndTraceRecording(); + g_recordTrace.SetBool( false ); + } +} + +/* +==================== +idGameLocal::BuildReturnValue + +Fills out gameReturn_t, called on server and clients. +==================== +*/ +void idGameLocal::BuildReturnValue( gameReturn_t & ret ) { + ret.sessionCommand[0] = 0; + + if ( GetLocalPlayer() != NULL ) { + GetLocalPlayer()->GetControllerShake( ret.vibrationLow, ret.vibrationHigh ); + } else { + // Dedicated server? + ret.vibrationLow = 0; + ret.vibrationHigh = 0; + } + + // see if a target_sessionCommand has forced a changelevel + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand.Clear(); // Fixes a double loading bug for the e3 demo. Since we run the game thread in SMP mode, we could run this twice and re-set the ret.sessionCommand to a stale sessionCommand (since that doesn't get cleared until LoadMap is called!) + } +} + +/* +==================== +idGameLocal::RunSinglelUserCmd + +Runs a Think or a ClientThink for a player. Will write the client's +position and firecount to the usercmd. +==================== +*/ +void idGameLocal::RunSingleUserCmd( usercmd_t & cmd, idPlayer & player ) { + player.HandleUserCmds( cmd ); + + // To fix the stupid chaingun script that depends on frame counts instead of + // milliseconds in the case of the server running at 60Hz and the client running + // at 120Hz, we need to set the script's GAME_FPS value to the client's effective rate. + // (I'd like to just fix the script to use milliseconds, but we don't want to change assets + // at this point.) + if ( !player.IsLocallyControlled() ) { + const float usercmdMillisecondDelta = player.usercmd.clientGameMilliseconds - player.oldCmd.clientGameMilliseconds; + const float clientEngineHz = 1000.0f / usercmdMillisecondDelta; + + // Force to 60 or 120, those are the only values allowed in multiplayer. + const float forcedClientEngineHz = ( clientEngineHz < 90.0f ) ? 60.0f : 120.0f; + SetScriptFPS( forcedClientEngineHz ); + } + + if ( !common->IsMultiplayer() || common->IsServer() ) { + player.Think(); + + // Keep track of the client time of the usercmd we just ran. We will send this back to clients + // in a snapshot so they know when they can stop predicting certain things. + usercmdLastClientMilliseconds[ player.GetEntityNumber() ] = cmd.clientGameMilliseconds; + } else { + player.ClientThink( netInterpolationInfo.serverGameMs, netInterpolationInfo.pct, true ); + } + + // Since the client is authoritative on its position, we have to update the usercmd + // that will be sent over the network after the player thinks. + cmd.pos = player.usercmd.pos; + cmd.fireCount = player.GetClientFireCount(); + if ( player.GetPhysics() ) { + cmd.speedSquared = player.GetPhysics()->GetLinearVelocity().LengthSqr(); + } +} + +/* +==================== +idGameLocal::RunAllUserCmdsForPlayer +Runs a Think or ClientThink for each usercmd, but leaves a few cmds in the buffer +so that we have something to process while we wait for more from the network. +==================== +*/ +void idGameLocal::RunAllUserCmdsForPlayer( idUserCmdMgr & cmdMgr, const int playerNumber ) { +//idLib::Printf( "Frame: %i = [%i-%i] ", gameLocal.framenum, +//cmdMgr.readFrame[0], cmdMgr.writeFrame[0] ); // !@# + + // Run thinks on any players that have queued up usercmds for networking. + assert( playerNumber < MAX_PLAYERS ); + + if ( entities[ playerNumber ] == NULL ) { + return; + } + + idPlayer & player = static_cast< idPlayer & >( *entities[ playerNumber ] ); + + // Only run a single userCmd each game frame for local players, otherwise when + // we are running < 60fps things like footstep sounds may get started right on top + // of each other instead of spread out in time. + if ( player.IsLocallyControlled() ) { + if ( cmdMgr.HasUserCmdForPlayer( playerNumber ) ) { + if ( net_usercmd_timing_debug.GetBool() ) { + idLib::Printf( "[%d]Running local cmd for player %d, %d buffered.\n", + common->GetGameFrame(), playerNumber, cmdMgr.GetNumUnreadFrames( playerNumber ) ); + } + RunSingleUserCmd( cmdMgr.GetWritableUserCmdForPlayer( playerNumber ), player ); + } else { + RunSingleUserCmd( player.usercmd, player ); + } + return; + } + + // Only the server runs remote commands. + if ( common->IsClient() ) { + return; + } + + // Make sure to run a command for remote players. May duplicate the previous command + // if the server is running faster, or run an "empty" command if the buffer + // underflows. + if ( cmdMgr.HasUserCmdForPlayer( player.GetEntityNumber() ) ) { + const int clientTimeOfNextCommand = cmdMgr.GetNextUserCmdClientTime( playerNumber ); + const int timeDeltaBetweenClientCommands = clientTimeOfNextCommand - lastCmdRunTimeOnClient[ playerNumber ]; + const int timeSinceServerRanLastCommand = gameLocal.time - lastCmdRunTimeOnServer[ playerNumber ]; + int clientTimeRunSoFar = 0; + + // Handle clients who may be running faster than the server. Potentiallly runs multiple + // usercmds so that the server can catch up. + if ( timeDeltaBetweenClientCommands - timeSinceServerRanLastCommand <= 1 ) { + while ( clientTimeRunSoFar < ( timeSinceServerRanLastCommand - 1 ) && cmdMgr.HasUserCmdForPlayer( player.GetEntityNumber() ) ) { + usercmd_t & currentCommand = cmdMgr.GetWritableUserCmdForPlayer( playerNumber ); + RunSingleUserCmd( currentCommand, player ); + lastCmdRunTimeOnClient[ playerNumber ] = currentCommand.clientGameMilliseconds; + lastCmdRunTimeOnServer[ playerNumber ] = gameLocal.serverTime; + clientTimeRunSoFar += timeDeltaBetweenClientCommands; + if ( clientTimeRunSoFar == 0 ) { + // Hack to avoid infinite loop + break; + } + if ( net_usercmd_timing_debug.GetBool() ) { + idLib::Printf( "[%d]Running initial cmd for player %d, %d buffered, %d so far, %d serverDelta.\n", + common->GetGameFrame(), playerNumber, cmdMgr.GetNumUnreadFrames( playerNumber ), + clientTimeRunSoFar, timeSinceServerRanLastCommand ); + } + } + } else { + // If we get here, it is likely that the client is running at 60Hz but the server + // is running at 120Hz. Duplicate the previous + // usercmd and run it so that the server doesn't starve. + usercmd_t lastPlayerCmd = player.usercmd; + RunSingleUserCmd( lastPlayerCmd, player ); + if ( net_usercmd_timing_debug.GetBool() ) { + idLib::Printf( "[%d]Running duplicated command for player %d to prevent server from starving. clientCmdTimeDelta = %d, runCmdTimeDeltaOnServer = %d.\n", + common->GetGameFrame(), playerNumber, timeDeltaBetweenClientCommands, timeSinceServerRanLastCommand ); + } + } + } else { + // Run an "empty" cmd, ran out of buffer. + usercmd_t emptyCmd = player.usercmd; + emptyCmd.forwardmove = 0; + emptyCmd.rightmove = 0; + RunSingleUserCmd( emptyCmd, player ); + lastCmdRunTimeOnServer[ playerNumber ] = gameLocal.serverTime; + if ( net_usercmd_timing_debug.GetBool() ) { + idLib::Printf( "[%d]Ran out of commands for player %d.\n", common->GetGameFrame(), playerNumber ); + } + } + + // For remote players on the server, run enough commands + // to leave only a buffer that will hold us over for a + // number of milliseconds equal to the net_ucmdRate + one frame. + const int MaxExtraCommandsPerFrame = 15; + int numPasses = 0; + + for ( ; numPasses < MaxExtraCommandsPerFrame; numPasses++ ) { + // Run remote player extra commands + extern idCVar net_ucmdRate; + // Add some extra time to smooth out network inconsistencies. + const int extraFrameMilliseconds = FRAME_TO_MSEC( common->GetGameFrame() + 2 ) - FRAME_TO_MSEC( common->GetGameFrame() ); + const int millisecondBuffer = MSEC_ALIGN_TO_FRAME( net_ucmdRate.GetInteger() + extraFrameMilliseconds ); + + const bool hasNextCmd = cmdMgr.HasUserCmdForClientTimeBuffer( playerNumber, millisecondBuffer ); + + if ( hasNextCmd ) { + usercmd_t & currentCommand = cmdMgr.GetWritableUserCmdForPlayer( playerNumber ); + + if ( net_usercmd_timing_debug.GetBool() ) { + idLib::Printf( "[%d]Pass %d, running extra cmd for player %d, %d buffered\n", common->GetGameFrame(), numPasses, playerNumber, cmdMgr.GetNumUnreadFrames( playerNumber ) ); + } + + RunSingleUserCmd( currentCommand, player ); + lastCmdRunTimeOnClient[ playerNumber ] = currentCommand.clientGameMilliseconds; + lastCmdRunTimeOnServer[ playerNumber ] = gameLocal.serverTime; + } else { + break; + } + } + + // Reset the script FPS in case it was changed to accomodate an MP client + // running at a different framerate. + SetScriptFPS( com_engineHz_latched ); + +//idLib::Printf( "\n" );//!@# +} + + +/* +====================================================================== + + Game view drawing + +====================================================================== +*/ + +/* +==================== +idGameLocal::CalcFov + +Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio +==================== +*/ +void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const { + const int width = renderSystem->GetWidth(); + const int height = renderSystem->GetHeight(); + if ( width == height ) { + // this is the Rift, so don't mess with our aspect ratio corrections + fov_x = base_fov; + fov_y = base_fov; + return; + } + + // Calculate the fov_y based on an ideal aspect ratio + const float ideal_ratio_x = 16.0f; + const float ideal_ratio_y = 9.0f; + const float tanHalfX = idMath::Tan( DEG2RAD( base_fov * 0.5f ) ); + fov_y = 2.0f * RAD2DEG( idMath::ATan( ideal_ratio_y * tanHalfX, ideal_ratio_x ) ); + + // Then calculate fov_x based on the true aspect ratio + const float ratio_x = width * renderSystem->GetPixelAspect(); + const float ratio_y = height; + const float tanHalfY = idMath::Tan( DEG2RAD( fov_y * 0.5f ) ); + fov_x = 2.0f * RAD2DEG( idMath::ATan( ratio_x * tanHalfY, ratio_y ) ); +} + +/* +================ +idGameLocal::Draw + +makes rendering and sound system calls +================ +*/ +bool idGameLocal::Draw( int clientNum ) { + + if( clientNum == -1 ) { + return false; + } + + if ( common->IsMultiplayer() && session->GetState() == idSession::INGAME ) { + return mpGame.Draw( clientNum ); + } + + // chose the optimized or legacy device context code + uiManager->SetDrawingDC(); + + idPlayer *player = static_cast(entities[ clientNum ]); + + if ( ( player == NULL ) || ( player->GetRenderView() == NULL ) ) { + return false; + } + + // render the scene + player->playerView.RenderPlayerView( player->hudManager ); + + return true; +} + +/* +================ +idGameLocal::HandleGuiCommands +================ +*/ +bool idGameLocal::HandlePlayerGuiEvent( const sysEvent_t * ev ) { + + idPlayer * player = GetLocalPlayer(); + bool handled = false; + + if ( player != NULL ) { + handled = player->HandleGuiEvents( ev ); + } + + if ( common->IsMultiplayer() && !handled ) { + handled = mpGame.HandleGuiEvent( ev ); + } + + return handled; +} + +/* +================ +idGameLocal::GetLevelMap + + should only be used for in-game level editing +================ +*/ +idMapFile *idGameLocal::GetLevelMap() { + if ( mapFile && mapFile->HasPrimitiveData()) { + return mapFile; + } + if ( !mapFileName.Length() ) { + return NULL; + } + + if ( mapFile ) { + delete mapFile; + } + + mapFile = new (TAG_GAME) idMapFile; + if ( !mapFile->Parse( mapFileName ) ) { + delete mapFile; + mapFile = NULL; + } + + return mapFile; +} + +/* +================ +idGameLocal::GetMapName +================ +*/ +const char *idGameLocal::GetMapName() const { + return mapFileName.c_str(); +} + +/* +================ +idGameLocal::CallFrameCommand +================ +*/ +void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) { + frameCommandThread->CallFunction( ent, frameCommand, true ); + frameCommandThread->Execute(); +} + +/* +================ +idGameLocal::CallObjectFrameCommand +================ +*/ +void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) { + const function_t *func; + + func = ent->scriptObject.GetFunction( frameCommand ); + if ( !func ) { + if ( !ent->IsType( idTestModel::Type ) ) { + Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() ); + } + } else { + frameCommandThread->CallFunction( ent, func, true ); + frameCommandThread->Execute(); + } +} + +/* +================ +idGameLocal::ShowTargets +================ +*/ +void idGameLocal::ShowTargets() { + idMat3 axis = GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin(); + idBounds viewTextBounds( viewPos ); + idBounds viewBounds( viewPos ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + idBounds totalBounds; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + totalBounds = ent->GetPhysics()->GetAbsBounds(); + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() ); + } + } + + if ( !viewBounds.IntersectsBounds( totalBounds ) ) { + continue; + } + + float dist; + idVec3 dir = totalBounds.GetCenter() - viewPos; + dir.NormalizeFast(); + totalBounds.RayIntersection( viewPos, dir, dist ); + float frac = ( 512.0f - dist ) / 512.0f; + if ( frac < 0.0f ) { + continue; + } + + gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter(); + gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 ); + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() ); + } + } + } +} + +/* +================ +idGameLocal::RunDebugInfo +================ +*/ +void idGameLocal::RunDebugInfo() { + idEntity *ent; + idPlayer *player; + + player = GetLocalPlayer(); + if ( !player ) { + return; + } + + const idVec3 &origin = player->GetPhysics()->GetOrigin(); + + if ( g_showEntityInfo.GetBool() ) { + idMat3 axis = player->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + idBounds viewTextBounds( origin ); + idBounds viewBounds( origin ); + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // don't draw the worldspawn + if ( ent == world ) { + continue; + } + + // skip if the entity is very far away + if ( !viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + continue; + } + + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + int contents = ent->GetPhysics()->GetContents(); + if ( contents & CONTENTS_BODY ) { + gameRenderWorld->DebugBounds( colorCyan, entBounds ); + } else if ( contents & CONTENTS_TRIGGER ) { + gameRenderWorld->DebugBounds( colorOrange, entBounds ); + } else if ( contents & CONTENTS_SOLID ) { + gameRenderWorld->DebugBounds( colorGreen, entBounds ); + } else { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds.Expand( 8.0f ) ); + } else { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds ); + } + } + if ( viewTextBounds.IntersectsBounds( entBounds ) ) { + gameRenderWorld->DrawText( ent->name.c_str(), entBounds.GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), entBounds.GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + } + } + } + + // debug tool to draw bounding boxes around active entities + if ( g_showActiveEntities.GetBool() ) { + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + idBounds b = ent->GetPhysics()->GetBounds(); + if ( b.GetVolume() <= 0 ) { + b[0][0] = b[0][1] = b[0][2] = -8; + b[1][0] = b[1][1] = b[1][2] = 8; + } + if ( ent->fl.isDormant ) { + gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() ); + } else { + gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() ); + } + } + } + + if ( g_showTargets.GetBool() ) { + ShowTargets(); + } + + if ( g_showTriggers.GetBool() ) { + idTrigger::DrawDebugInfo(); + } + + if ( ai_showCombatNodes.GetBool() ) { + idCombatNode::DrawDebugInfo(); + } + + if ( ai_showPaths.GetBool() ) { + idPathCorner::DrawDebugInfo(); + } + + if ( g_editEntityMode.GetBool() ) { + editEntities->DisplayEntities(); + } + + if ( g_showCollisionWorld.GetBool() ) { + collisionModelManager->DrawModel( 0, vec3_origin, mat3_identity, origin, 128.0f ); + } + + if ( g_showCollisionModels.GetBool() ) { + clip.DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player ); + } + + if ( g_showCollisionTraces.GetBool() ) { + clip.PrintStatistics(); + } + + if ( g_showPVS.GetInteger() ) { + pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL ); + } + + if ( aas_test.GetInteger() >= 0 ) { + idAAS *aas = GetAAS( aas_test.GetInteger() ); + if ( aas ) { + aas->Test( origin ); + if ( ai_testPredictPath.GetBool() ) { + idVec3 velocity; + predictedPath_t path; + + velocity.x = cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.y = sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.z = 0.0f; + idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path ); + } + } + } + + if ( ai_showObstacleAvoidance.GetInteger() == 2 ) { + idAAS *aas = GetAAS( 0 ); + if ( aas ) { + idVec3 seekPos; + obstaclePath_t path; + + seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f; + idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path ); + } + } + + // collision map debug output + collisionModelManager->DebugOutput( player->GetEyePosition() ); +} + +/* +================== +idGameLocal::NumAAS +================== +*/ +int idGameLocal::NumAAS() const { + return aasList.Num(); +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( int num ) const { + if ( ( num >= 0 ) && ( num < aasList.Num() ) ) { + if ( aasList[ num ] && aasList[ num ]->GetSettings() ) { + return aasList[ num ]; + } + } + return NULL; +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( const char *name ) const { + int i; + + for ( i = 0; i < aasNames.Num(); i++ ) { + if ( aasNames[ i ] == name ) { + if ( !aasList[ i ]->GetSettings() ) { + return NULL; + } else { + return aasList[ i ]; + } + } + } + return NULL; +} + +/* +================== +idGameLocal::SetAASAreaState +================== +*/ +void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->SetAreaState( bounds, areaContents, closed ); + } +} + +/* +================== +idGameLocal::AddAASObstacle +================== +*/ +aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) { + int i; + aasHandle_t obstacle; + aasHandle_t check; + + if ( !aasList.Num() ) { + return -1; + } + + obstacle = aasList[ 0 ]->AddObstacle( bounds ); + for( i = 1; i < aasList.Num(); i++ ) { + check = aasList[ i ]->AddObstacle( bounds ); + assert( check == obstacle ); + } + + return obstacle; +} + +/* +================== +idGameLocal::RemoveAASObstacle +================== +*/ +void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveObstacle( handle ); + } +} + +/* +================== +idGameLocal::RemoveAllAASObstacles +================== +*/ +void idGameLocal::RemoveAllAASObstacles() { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveAllObstacles(); + } +} + +/* +================== +idGameLocal::CheatsOk +================== +*/ +bool idGameLocal::CheatsOk( bool requirePlayer ) { + extern idCVar net_allowCheats; + if ( common->IsMultiplayer() && !net_allowCheats.GetBool() ) { + Printf( "Not allowed in multiplayer.\n" ); + return false; + } + + if ( developer.GetBool() ) { + return true; + } + + idPlayer * player = GetLocalPlayer(); + if ( !requirePlayer || ( player != NULL && ( player->health > 0 ) ) ) { + return true; + } + + Printf( "You must be alive to use this command.\n" ); + + return false; +} + +/* +=================== +idGameLocal::RegisterEntity +=================== +*/ +void idGameLocal::RegisterEntity( idEntity *ent, int forceSpawnId, const idDict & spawnArgsToCopy ) { + int spawn_entnum; + + ent->fl.skipReplication = spawnArgsToCopy.GetBool( "net_skip_replication", false ); + + if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) { + Error( "idGameLocal::RegisterEntity: spawn count overflow" ); + } + + if ( !spawnArgsToCopy.GetInt( "spawn_entnum", "0", spawn_entnum ) ) { + const int freeListType = ( common->IsMultiplayer() && ent->GetSkipReplication() ) ? 1 : 0; + const int maxEntityNum = ( common->IsMultiplayer() && !ent->GetSkipReplication() ) ? ENTITYNUM_FIRST_NON_REPLICATED : ENTITYNUM_MAX_NORMAL; + int freeIndex = firstFreeEntityIndex[ freeListType ]; + + while( entities[ freeIndex ] != NULL && freeIndex < maxEntityNum ) { + freeIndex++; + } + if ( freeIndex >= maxEntityNum ) { + Error( "no free entities[%d]", freeListType ); + } + spawn_entnum = freeIndex++; + + firstFreeEntityIndex[ freeListType ] = freeIndex; + } + + entities[ spawn_entnum ] = ent; + spawnIds[ spawn_entnum ] = ( forceSpawnId >= 0 ) ? forceSpawnId : spawnCount++; + ent->entityNumber = spawn_entnum; + ent->spawnNode.AddToEnd( spawnedEntities ); + + // Make a copy because TransferKeyValues clears the input parameter. + idDict copiedArgs = spawnArgsToCopy; + ent->spawnArgs.TransferKeyValues( copiedArgs ); + + if ( spawn_entnum >= num_entities ) { + num_entities++; + } +} + +/* +=================== +idGameLocal::UnregisterEntity +=================== +*/ +void idGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( editEntities ) { + editEntities->RemoveSelectedEntity( ent ); + } + + if ( !verify( ent->entityNumber < MAX_GENTITIES ) ) { + idLib::Error( "invalid entity number" ); + return; + } + + if ( ( ent->entityNumber != ENTITYNUM_NONE ) && ( entities[ ent->entityNumber ] == ent ) ) { + ent->spawnNode.Remove(); + entities[ ent->entityNumber ] = NULL; + spawnIds[ ent->entityNumber ] = -1; + + int freeListType = ( common->IsMultiplayer() && ent->entityNumber >= ENTITYNUM_FIRST_NON_REPLICATED ) ? 1 : 0; + + if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeEntityIndex[ freeListType ] ) { + firstFreeEntityIndex[ freeListType ] = ent->entityNumber; + } + ent->entityNumber = ENTITYNUM_NONE; + } +} + +/* +================ +idGameLocal::SpawnEntityType +================ +*/ +idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) { + idClass *obj; + +#if _DEBUG + if ( common->IsClient() ) { + assert( bIsClientReadSnapshot ); + } +#endif + + if ( !classdef.IsType( idEntity::Type ) ) { + Error( "Attempted to spawn non-entity class '%s'", classdef.classname ); + } + + try { + if ( args ) { + spawnArgs = *args; + } else { + spawnArgs.Clear(); + } + obj = classdef.CreateInstance(); + obj->CallSpawn(); + } + + catch( idAllocError & ) { + obj = NULL; + } + spawnArgs.Clear(); + + return static_cast(obj); +} + +/* +=================== +idGameLocal::SpawnEntityDef + +Finds the spawn function for the entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) { + const char *classname; + const char *spawn; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + if ( ent ) { + *ent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetString( "name", "", &name ) ) { + sprintf( error, " on '%s'", name); + } + + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + + if ( !def ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + + if ( !spawnArgs.FindKey( "slowmo" ) ) { + bool slowmo = true; + + for ( int i = 0; fastEntityList[i]; i++ ) { + if ( !idStr::Cmp( classname, fastEntityList[i] ) ) { + slowmo = false; + break; + } + } + + if ( !slowmo ) { + spawnArgs.SetBool( "slowmo", slowmo ); + } + } + + // check if we should spawn a class object + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + + if ( ent && obj->IsType( idEntity::Type ) ) { + *ent = static_cast(obj); + } + + return true; + } + + // check if we should call a script function to spawn + spawnArgs.GetString( "spawnfunc", NULL, &spawn ); + if ( spawn ) { + const function_t *func = program.FindFunction( spawn ); + if ( !func ) { + Warning( "Could not spawn '%s'. Script function '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + idThread *thread = new idThread( func ); + thread->DelayedStart( 0 ); + return true; + } + + Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() ); + return false; +} + +/* +================ +idGameLocal::FindEntityDef +================ +*/ +const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const { + const idDecl *decl = NULL; + if ( common->IsMultiplayer() ) { + decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false ); + } + if ( !decl ) { + decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault ); + } + return static_cast( decl ); +} + +/* +================ +idGameLocal::FindEntityDefDict +================ +*/ +const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const { + const idDeclEntityDef *decl = FindEntityDef( name, makeDefault ); + return decl ? &decl->dict : NULL; +} + +/* +================ +idGameLocal::InhibitEntitySpawn +================ +*/ +bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + + bool result = false; + + if ( common->IsMultiplayer() ) { + spawnArgs.GetBool( "not_multiplayer", "0", result ); + } else if ( g_skill.GetInteger() == 0 ) { + spawnArgs.GetBool( "not_easy", "0", result ); + } else if ( g_skill.GetInteger() == 1 ) { + spawnArgs.GetBool( "not_medium", "0", result ); + } else { + spawnArgs.GetBool( "not_hard", "0", result ); + if ( !result && g_skill.GetInteger() == 3 ) { + spawnArgs.GetBool( "not_nightmare", "0", result ); + } + } + + if ( g_skill.GetInteger() == 3 ) { + const char * name = spawnArgs.GetString( "classname" ); + // _D3XP :: remove moveable medkit packs also + if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 || + idStr::Icmp( name, "moveable_item_medkit" ) == 0 || idStr::Icmp( name, "moveable_item_medkit_small" ) == 0 ) { + + result = true; + } + } + + if ( common->IsMultiplayer() ) { + const char * name = spawnArgs.GetString( "classname" ); + if ( idStr::Icmp( name, "weapon_bfg" ) == 0 || idStr::Icmp( name, "weapon_soulcube" ) == 0 ) { + result = true; + } + } + + return result; +} + +/* +============== +idGameLocal::GameState + +Used to allow entities to know if they're being spawned during the initial spawn. +============== +*/ +gameState_t idGameLocal::GameState() const { + return gamestate; +} + +/* +============== +idGameLocal::SpawnMapEntities + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +void idGameLocal::SpawnMapEntities() { + int i; + int num; + int inhibit; + idMapEntity *mapEnt; + int numEntities; + idDict args; + + Printf( "Spawning entities\n" ); + + if ( mapFile == NULL ) { + Printf("No mapfile present\n"); + return; + } + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + + // the worldspawn is a special that performs any global setup + // needed by a level + mapEnt = mapFile->GetEntity( 0 ); + args = mapEnt->epairs; + args.SetInt( "spawn_entnum", ENTITYNUM_WORLD ); + if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::Type ) ) { + Error( "Problem spawning world entity" ); + } + + num = 1; + inhibit = 0; + + for ( i = 1 ; i < numEntities ; i++ ) { + common->UpdateLevelLoadPacifier(); + + + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + + if ( !InhibitEntitySpawn( args ) ) { + // precache any media specified in the map entity + CacheDictionaryMedia( &args ); + + SpawnEntityDef( args ); + num++; + } else { + inhibit++; + } + } + + Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit ); +} + +/* +================ +idGameLocal::AddEntityToHash +================ +*/ +void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { + if ( FindEntity( name ) ) { + Error( "Multiple entities named '%s'", name ); + } + entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); +} + +/* +================ +idGameLocal::RemoveEntityFromHash +================ +*/ +bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { + entityHash.Remove( hash, i ); + return true; + } + } + return false; +} + +/* +================ +idGameLocal::GetTargets +================ +*/ +int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const { + int i, num, refLength; + const idKeyValue *arg; + idEntity *ent; + + list.Clear(); + + refLength = strlen( ref ); + num = args.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + + arg = args.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) { + + ent = FindEntity( arg->GetValue() ); + if ( ent ) { + idEntityPtr &entityPtr = list.Alloc(); + entityPtr = ent; + } + } + } + + return list.Num(); +} + +/* +============= +idGameLocal::GetTraceEntity + +returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. +============= +*/ +idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const { + idEntity *master; + + if ( !entities[ trace.c.entityNum ] ) { + return NULL; + } + master = entities[ trace.c.entityNum ]->GetBindMaster(); + if ( master ) { + return master; + } + return entities[ trace.c.entityNum ]; +} + +/* +============= +idGameLocal::ArgCompletion_EntityName + +Argument completion for entity names +============= +*/ +void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::FindEntity + +Returns the entity whose name matches the specified string. +============= +*/ +idEntity *idGameLocal::FindEntity( const char *name ) const { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) { + return entities[i]; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindEntityUsingDef + +Searches all active entities for the next one using the specified entityDef. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. +============= +*/ +idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const { + idEntity *ent; + + if ( !from ) { + ent = spawnedEntities.Next(); + } else { + ent = from->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + assert( ent ); + if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) { + return ent; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindTraceEntity + +Searches all active entities for the closest ( to start ) match that intersects +the line start,end +============= +*/ +idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( c ) && ent != skip ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} + +/* +================ +idGameLocal::EntitiesWithinRadius +================ +*/ +int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const { + idEntity *ent; + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + entityList[entCount++] = ent; + } + } + + return entCount; +} + +/* +================= +idGameLocal::KillBox + +Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed. +Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately. +If catch_teleport, this only marks teleport players for death on exit +================= +*/ +void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idPhysics *phys; + + phys = ent->GetPhysics(); + if ( !phys->GetNumClipModels() ) { + return; + } + + num = clip.ClipModelsTouchingBounds( phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES ); + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == ent ) || !hit->fl.takedamage ) { + continue; + } + + if ( !phys->ClipContents( cm ) ) { + continue; + } + + // nail it + idPlayer *otherPlayer = NULL; + if ( hit->IsType( idPlayer::Type ) ) { + otherPlayer = static_cast< idPlayer * >( hit ); + } + if ( otherPlayer != NULL ) { + if ( otherPlayer->IsInTeleport() ) { + otherPlayer->TeleportDeath( ent->entityNumber ); + } else if ( !catch_teleport ) { + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + } + } else if ( !catch_teleport ) { + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + } + } +} + +/* +================ +idGameLocal::RequirementMet +================ +*/ +bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) { + if ( requires.Length() ) { + if ( activator->IsType( idPlayer::Type ) ) { + idPlayer *player = static_cast(activator); + idDict *item = player->FindInventoryItem( requires ); + if ( item ) { + if ( removeItem ) { + player->RemoveInventoryItem( item ); + } + return true; + } else { + return false; + } + } + } + + return true; +} + +/* +============ +idGameLocal::AlertAI +============ +*/ +void idGameLocal::AlertAI( idEntity *ent ) { + if ( ent && ent->IsType( idActor::Type ) ) { + // alert them for the next frame + lastAIAlertTime = time + 1; + lastAIAlertEntity = static_cast( ent ); + } +} + +/* +============ +idGameLocal::GetAlertEntity +============ +*/ +idActor *idGameLocal::GetAlertEntity() { + int timeGroup = 0; + if ( lastAIAlertTime && lastAIAlertEntity.GetEntity() ) { + timeGroup = lastAIAlertEntity.GetEntity()->timeGroup; + } + SetTimeState ts( timeGroup ); + + if ( lastAIAlertTime >= time ) { + return lastAIAlertEntity.GetEntity(); + } + + return NULL; +} + +/* +============ +idGameLocal::RadiusDamage +============ +*/ +void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower ) { + float dist, damageScale, attackerDamageScale, attackerPushScale; + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 v, damagePoint, dir; + int i, e, damage, radius, push; + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + + if ( radius < 1 ) { + radius = 1; + } + + bounds = idBounds( origin ).Expand( radius ); + + // get all entities touching the bounds + numListedEntities = clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { + inflictor = static_cast(inflictor)->GetBody(); + } + if ( attacker && attacker->IsType( idAFAttachment::Type ) ) { + attacker = static_cast(attacker)->GetBody(); + } + if ( ignoreDamage && ignoreDamage->IsType( idAFAttachment::Type ) ) { + ignoreDamage = static_cast(ignoreDamage)->GetBody(); + } + + // apply damage to the entities + for ( e = 0; e < numListedEntities; e++ ) { + ent = entityList[ e ]; + assert( ent ); + + if ( !ent->fl.takedamage ) { + continue; + } + + if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor ) ) { + continue; + } + + if ( ent == ignoreDamage || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignoreDamage ) ) { + continue; + } + + // don't damage a dead player + if ( common->IsMultiplayer() && ent->entityNumber < MAX_CLIENTS && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >( ent )->health < 0 ) { + continue; + } + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < ent->GetPhysics()->GetAbsBounds()[0][ i ] ) { + v[ i ] = ent->GetPhysics()->GetAbsBounds()[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > ent->GetPhysics()->GetAbsBounds()[1][ i ] ) { + v[ i ] = origin[ i ] - ent->GetPhysics()->GetAbsBounds()[1][ i ]; + } else { + v[ i ] = 0; + } + } + + dist = v.Length(); + if ( dist >= radius ) { + continue; + } + + if ( ent->CanDamage( origin, damagePoint ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + dir = ent->GetPhysics()->GetOrigin() - origin; + dir[ 2 ] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - dist / radius ); + if ( ent == attacker || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == attacker ) ) { + damageScale *= attackerDamageScale; + } + + bool killedBySplash = true; + + if( ent->health <= 0 ) { + killedBySplash = false; + } + + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT ); + + + // If the player is local. SHAkkkkkkeeee! + if( !common->IsMultiplayer() && ent->entityNumber == GetLocalClientNum() ) { + + if( ent->IsType( idPlayer::Type ) ) { + idPlayer * player = static_cast< idPlayer* >( ent ); + if( player ) { + player->ControllerShakeFromDamage( damage ); + } + } + } + + // if our inflictor is a projectile, we want to count up how many kills the splash damage has chalked up. + if( ent->health <= 0 && killedBySplash ) { + if( inflictor && inflictor->IsType( idProjectile::Type ) ) { + if ( attacker && attacker->IsType( idPlayer::Type ) ) { + if ( ent->IsType( idActor::Type ) && ent != attacker ) { + idPlayer *player = static_cast( attacker ); + player->AddProjectileKills(); + } + } + } + } + + } + } + + // push physics objects + if ( push ) { + RadiusPush( origin, radius, push * dmgPower, attacker, ignorePush, attackerPushScale, false ); + } +} + +/* +============== +idGameLocal::RadiusPush +============== +*/ +void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { + int i, numListedClipModels; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 dir; + idBounds bounds; + modelTrace_t result; + idEntity *ent; + float scale; + + dir.Set( 0.0f, 0.0f, 1.0f ); + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds + numListedClipModels = clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { + inflictor = static_cast(inflictor)->GetBody(); + } + if ( ignore && ignore->IsType( idAFAttachment::Type ) ) { + ignore = static_cast(ignore)->GetBody(); + } + + // apply impact to all the clip models through their associated physics objects + for ( i = 0; i < numListedClipModels; i++ ) { + + clipModel = clipModelList[i]; + + // never push render models + if ( clipModel->IsRenderModel() ) { + continue; + } + + ent = clipModel->GetEntity(); + + // never push projectiles + if ( ent->IsType( idProjectile::Type ) ) { + continue; + } + + // players use "knockback" in idPlayer::Damage + if ( ent->IsType( idPlayer::Type ) && !quake ) { + continue; + } + + // don't push the ignore entity + if ( ent == ignore || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignore ) ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) { + continue; + } + + // scale the push for the inflictor + if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor ) ) { + scale = inflictorScale; + } else { + scale = 1.0f; + } + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { + RadiusPushClipModel( origin, scale * push, clipModel ); + } + } +} + +/* +============== +idGameLocal::RadiusPushClipModel +============== +*/ +void idGameLocal::RadiusPushClipModel( const idVec3 &origin, const float push, const idClipModel *clipModel ) { + int i, j; + float dot, dist, area; + const idTraceModel *trm; + const traceModelPoly_t *poly; + idFixedWinding w; + idVec3 v, localOrigin, center, impulse; + + trm = clipModel->GetTraceModel(); + if ( !trm || 1 ) { + impulse = clipModel->GetAbsBounds().GetCenter() - origin; + impulse.Normalize(); + impulse.z += 1.0f; + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), push * impulse ); + return; + } + + localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose(); + for ( i = 0; i < trm->numPolys; i++ ) { + poly = &trm->polys[i]; + + center.Zero(); + for ( j = 0; j < poly->numEdges; j++ ) { + v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INT32_SIGNBITSET( poly->edges[j] ) ] ]; + center += v; + v -= localOrigin; + v.NormalizeFast(); // project point on a unit sphere + w.AddPoint( v ); + } + center /= poly->numEdges; + v = center - localOrigin; + dist = v.NormalizeFast(); + dot = v * poly->normal; + if ( dot > 0.0f ) { + continue; + } + area = w.GetArea(); + // impulse in polygon normal direction + impulse = poly->normal * clipModel->GetAxis(); + // always push up for nicer effect + impulse.z -= 1.0f; + // scale impulse based on visible surface area and polygon angle + impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) ); + // scale away distance for nicer effect + impulse *= ( dist * 2.0f ); + // impulse is applied to the center of the polygon + center = clipModel->GetOrigin() + center * clipModel->GetAxis(); + + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), center, impulse ); + } +} + +/* +=============== +idGameLocal::ProjectDecal +=============== +*/ +void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) { + float s, c; + idMat3 axis, axistemp; + idFixedWinding winding; + idVec3 windingOrigin, projectionOrigin; + + static idVec3 decalWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !g_decals.GetBool() ) { + return; + } + + // randomly rotate the decal winding + idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c ); + + // winding orientation + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + windingOrigin = origin + depth * axis[2]; + if ( parallel ) { + projectionOrigin = origin - depth * axis[2]; + } else { + projectionOrigin = origin; + } + + size *= 0.5f; + + winding.Clear(); + winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) ); + gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), gameLocal.slow.time /* _D3XP */ ); +} + +/* +============== +idGameLocal::BloodSplat +============== +*/ +void idGameLocal::BloodSplat( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float halfSize = size * 0.5f; + idVec3 verts[] = { idVec3( 0.0f, +halfSize, +halfSize ), + idVec3( 0.0f, +halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, +halfSize ) }; + idTraceModel trm; + idClipModel mdl; + trace_t results; + + // FIXME: get from damage def + if ( !g_bloodEffects.GetBool() ) { + return; + } + + size = halfSize + random.RandomFloat() * halfSize; + trm.SetupPolygon( verts, 4 ); + mdl.LoadModel( trm ); + clip.Translation( results, origin, origin + dir * 64.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL ); + ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material ); +} + +/* +============= +idGameLocal::SetCamera +============= +*/ +void idGameLocal::SetCamera( idCamera *cam ) { + int i; + idEntity *ent; + idAI *ai; + + // this should fix going into a cinematic when dead.. rare but happens + idPlayer *client = GetLocalPlayer(); + if ( client->health <= 0 || client->AI_DEAD ) { + return; + } + + camera = cam; + if ( camera ) { + inCinematic = true; + + // set r_znear so that transitioning into/out of the player's head doesn't clip through the view + cvarSystem->SetCVarFloat( "r_znear", 1.0f ); + + // hide all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + client = static_cast< idPlayer* >( entities[ i ] ); + client->EnterCinematic(); + } + } + + if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) { + // kill any active monsters that are enemies of the player + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->cinematic || ent->fl.isDormant ) { + // only kill entities that aren't needed for cinematics and aren't dormant + continue; + } + + if ( ent->IsType( idAI::Type ) ) { + ai = static_cast( ent ); + if ( !ai->GetEnemy() || !ai->IsActive() ) { + // no enemy, or inactive, so probably safe to ignore + continue; + } + } else if ( ent->IsType( idProjectile::Type ) ) { + // remove all projectiles + } else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) { + // remove anything marked to be removed during cinematics + } else { + // ignore everything else + continue; + } + + // remove it + DPrintf( "removing '%s' for cinematic\n", ent->GetName() ); + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + } else { + inCinematic = false; + + // restore r_znear + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + + // show all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + idPlayer *client = static_cast< idPlayer* >( entities[ i ] ); + client->ExitCinematic(); + } + } + } +} + +/* +============= +idGameLocal::GetCamera +============= +*/ +idCamera *idGameLocal::GetCamera() const { + return camera; +} + +/* +====================== +idGameLocal::SpreadLocations + +Now that everything has been spawned, associate areas with location entities +====================== +*/ +void idGameLocal::SpreadLocations() { + idEntity *ent; + + // allocate the area table + int numAreas = gameRenderWorld->NumAreas(); + locationEntities = new (TAG_GAME) idLocationEntity *[ numAreas ]; + memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) ); + + // for each location entity, make pointers from every area it touches + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idLocationEntity::Type ) ) { + continue; + } + idVec3 point = ent->spawnArgs.GetVector( "origin" ); + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) ); + continue; + } + if ( areaNum >= numAreas ) { + Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ), + locationEntities[areaNum]->spawnArgs.GetString( "name" ) ); + continue; + } + locationEntities[areaNum] = static_cast(ent); + + // spread to all other connected areas + for ( int i = 0 ; i < numAreas ; i++ ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(ent); + } + } + } +} + +/* +=================== +idGameLocal::LocationForPoint + +The player checks the location each frame to update the HUD text display +May return NULL +=================== +*/ +idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) { + if ( !locationEntities ) { + // before SpreadLocations() has been called + return NULL; + } + + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" ); + } + + return locationEntities[ areaNum ]; +} + +/* +============ +idGameLocal::SetPortalState +============ +*/ +void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) { + gameRenderWorld->SetPortalState( portal, blockingBits ); +} + +/* +============ +idGameLocal::sortSpawnPoints +============ +*/ +int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) { + const spawnSpot_t *spot1 = static_cast( ptr1 ); + const spawnSpot_t *spot2 = static_cast( ptr2 ); + float diff; + + diff = spot1->dist - spot2->dist; + if ( diff < 0.0f ) { + return 1; + } else if ( diff > 0.0f ) { + return -1; + } else { + return 0; + } +} + +/* +=========== +idGameLocal::RandomizeInitialSpawns +randomize the order of the initial spawns +prepare for a sequence of initial player spawns +============ +*/ +void idGameLocal::RandomizeInitialSpawns() { + spawnSpot_t spot; + int i, j; + int k; + + idEntity *ent; + + if ( !common->IsServer() ) { + return; + } + spawnSpots.Clear(); + initialSpots.Clear(); + teamSpawnSpots[0].Clear(); + teamSpawnSpots[1].Clear(); + teamInitialSpots[0].Clear(); + teamInitialSpots[1].Clear(); + + spot.dist = 0; + spot.ent = FindEntityUsingDef( NULL, "info_player_deathmatch" ); + while( spot.ent ) { + spot.ent->spawnArgs.GetInt( "team", "-1", spot.team ); + + if ( mpGame.IsGametypeFlagBased() ) /* CTF */ + { + if ( spot.team == 0 || spot.team == 1 ) + teamSpawnSpots[spot.team].Append( spot ); + else + common->Warning( "info_player_deathmatch : invalid or no team attached to spawn point\n"); + } + spawnSpots.Append( spot ); + if ( spot.ent->spawnArgs.GetBool( "initial" ) ) { + if ( mpGame.IsGametypeFlagBased() ) /* CTF */ + { + assert( spot.team == 0 || spot.team == 1 ); + teamInitialSpots[ spot.team ].Append( spot.ent ); + } + + initialSpots.Append( spot.ent ); + } + spot.ent = FindEntityUsingDef( spot.ent, "info_player_deathmatch" ); + } + + if ( mpGame.IsGametypeFlagBased() ) { /* CTF */ + if ( !teamSpawnSpots[0].Num() ) + common->Warning( "red team : no info_player_deathmatch in map" ); + if ( !teamSpawnSpots[1].Num() ) + common->Warning( "blue team : no info_player_deathmatch in map" ); + + if ( !teamSpawnSpots[0].Num() || !teamSpawnSpots[1].Num() ) + return; + } + + if ( !spawnSpots.Num() ) { + common->Warning( "no info_player_deathmatch in map" ); + return; + } + + if ( mpGame.IsGametypeFlagBased() ) { /* CTF */ + common->Printf( "red team : %d spawns (%d initials)\n", teamSpawnSpots[ 0 ].Num(), teamInitialSpots[ 0 ].Num() ); + // if there are no initial spots in the map, consider they can all be used as initial + if ( !teamInitialSpots[ 0 ].Num() ) { + common->Warning( "red team : no info_player_deathmatch entities marked initial in map" ); + for ( i = 0; i < teamSpawnSpots[ 0 ].Num(); i++ ) { + teamInitialSpots[ 0 ].Append( teamSpawnSpots[ 0 ][ i ].ent ); + } + } + + common->Printf( "blue team : %d spawns (%d initials)\n", teamSpawnSpots[ 1 ].Num(), teamInitialSpots[ 1 ].Num() ); + // if there are no initial spots in the map, consider they can all be used as initial + if ( !teamInitialSpots[ 1 ].Num() ) { + common->Warning( "blue team : no info_player_deathmatch entities marked initial in map" ); + for ( i = 0; i < teamSpawnSpots[ 1 ].Num(); i++ ) { + teamInitialSpots[ 1 ].Append( teamSpawnSpots[ 1 ][ i ].ent ); + } + } + } + + common->Printf( "%d spawns (%d initials)\n", spawnSpots.Num(), initialSpots.Num() ); + // if there are no initial spots in the map, consider they can all be used as initial + if ( !initialSpots.Num() ) { + common->Warning( "no info_player_deathmatch entities marked initial in map" ); + for ( i = 0; i < spawnSpots.Num(); i++ ) { + initialSpots.Append( spawnSpots[ i ].ent ); + } + } + + for ( k = 0; k < 2; k++ ) { + for ( i = 0; i < teamInitialSpots[ k ].Num(); i++ ) { + j = random.RandomInt( teamInitialSpots[ k ].Num() ); + ent = teamInitialSpots[ k ][ i ]; + teamInitialSpots[ k ][ i ] = teamInitialSpots[ k ][ j ]; + teamInitialSpots[ k ][ j ] = ent; + } + } + + for ( i = 0; i < initialSpots.Num(); i++ ) { + j = random.RandomInt( initialSpots.Num() ); + ent = initialSpots[ i ]; + initialSpots[ i ] = initialSpots[ j ]; + initialSpots[ j ] = ent; + } + // reset the counter + currentInitialSpot = 0; + + teamCurrentInitialSpot[0] = 0; + teamCurrentInitialSpot[1] = 0; +} + +/* +=========== +idGameLocal::SelectInitialSpawnPoint +spectators are spawned randomly anywhere +in-game clients are spawned based on distance to active players (randomized on the first half) +upon map restart, initial spawns are used (randomized ordered list of spawns flagged "initial") + if there are more players than initial spots, overflow to regular spawning +============ +*/ +idEntity *idGameLocal::SelectInitialSpawnPoint( idPlayer *player ) { + int i, j, which; + spawnSpot_t spot; + idVec3 pos; + float dist; + bool alone; + + if ( !common->IsMultiplayer() || !spawnSpots.Num() || ( mpGame.IsGametypeFlagBased() && ( !teamSpawnSpots[0].Num() || !teamSpawnSpots[1].Num() ) ) ) { /* CTF */ + spot.ent = FindEntityUsingDef( NULL, "info_player_start" ); + if ( !spot.ent ) { + Error( "No info_player_start on map.\n" ); + } + return spot.ent; + } + + bool useInitialSpots = false; + if ( mpGame.IsGametypeFlagBased() ) { /* CTF */ + assert( player->team == 0 || player->team == 1 ); + useInitialSpots = player->useInitialSpawns && teamCurrentInitialSpot[ player->team ] < teamInitialSpots[ player->team ].Num(); + } else { + useInitialSpots = player->useInitialSpawns && currentInitialSpot < initialSpots.Num(); + } + + if ( player->spectating ) { + // plain random spot, don't bother + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ].ent; + } else if ( useInitialSpots ) { + if ( mpGame.IsGametypeFlagBased() ) { /* CTF */ + assert( player->team == 0 || player->team == 1 ); + player->useInitialSpawns = false; // only use the initial spawn once + return teamInitialSpots[ player->team ][ teamCurrentInitialSpot[ player->team ]++ ]; + } + return initialSpots[ currentInitialSpot++ ]; + } else { + // check if we are alone in map + alone = true; + for ( j = 0; j < MAX_CLIENTS; j++ ) { + if ( entities[ j ] && entities[ j ] != player ) { + alone = false; + break; + } + } + if ( alone ) { + if ( mpGame.IsGametypeFlagBased() ) { /* CTF */ + assert( player->team == 0 || player->team == 1 ); + return teamSpawnSpots[ player->team ][ random.RandomInt( teamSpawnSpots[ player->team ].Num() ) ].ent; + } + // don't do distance-based + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ].ent; + } + + if ( mpGame.IsGametypeFlagBased() ) { /* CTF */ + // TODO : make as reusable method, same code as below + int team = player->team; + assert( team == 0 || team == 1 ); + + // find the distance to the closest active player for each spawn spot + for ( i = 0; i < teamSpawnSpots[ team ].Num(); i++ ) { + pos = teamSpawnSpots[ team ][ i ].ent->GetPhysics()->GetOrigin(); + + // skip initial spawn points for CTF + if ( teamSpawnSpots[ team ][ i ].ent->spawnArgs.GetBool("initial") ) { + teamSpawnSpots[ team ][ i ].dist = 0x0; + continue; + } + + teamSpawnSpots[ team ][ i ].dist = 0x7fffffff; + + for( j = 0; j < MAX_CLIENTS; j++ ) { + if ( !entities[ j ] || !entities[ j ]->IsType( idPlayer::Type ) + || entities[ j ] == player + || static_cast< idPlayer * >( entities[ j ] )->spectating ) { + continue; + } + + dist = ( pos - entities[ j ]->GetPhysics()->GetOrigin() ).LengthSqr(); + if ( dist < teamSpawnSpots[ team ][ i ].dist ) { + teamSpawnSpots[ team ][ i ].dist = dist; + } + } + } + + // sort the list + qsort( ( void * )teamSpawnSpots[ team ].Ptr(), teamSpawnSpots[ team ].Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); + + // choose a random one in the top half + which = random.RandomInt( teamSpawnSpots[ team ].Num() / 2 ); + spot = teamSpawnSpots[ team ][ which ]; +// assert( teamSpawnSpots[ team ][ which ].dist != 0 ); + + return spot.ent; + } + + // find the distance to the closest active player for each spawn spot + for( i = 0; i < spawnSpots.Num(); i++ ) { + pos = spawnSpots[ i ].ent->GetPhysics()->GetOrigin(); + spawnSpots[ i ].dist = 0x7fffffff; + for( j = 0; j < MAX_CLIENTS; j++ ) { + if ( !entities[ j ] || !entities[ j ]->IsType( idPlayer::Type ) + || entities[ j ] == player + || static_cast< idPlayer * >( entities[ j ] )->spectating ) { + continue; + } + + dist = ( pos - entities[ j ]->GetPhysics()->GetOrigin() ).LengthSqr(); + if ( dist < spawnSpots[ i ].dist ) { + spawnSpots[ i ].dist = dist; + } + } + } + + // sort the list + qsort( ( void * )spawnSpots.Ptr(), spawnSpots.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); + + // choose a random one in the top half + which = random.RandomInt( spawnSpots.Num() / 2 ); + spot = spawnSpots[ which ]; + } + return spot.ent; +} + +/* +================ +idGameLocal::SetGlobalMaterial +================ +*/ +void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) { + globalMaterial = mat; +} + +/* +================ +idGameLocal::GetGlobalMaterial +================ +*/ +const idMaterial *idGameLocal::GetGlobalMaterial() { + return globalMaterial; +} + +/* +================ +idGameLocal::GetSpawnId +================ +*/ +int idGameLocal::GetSpawnId( const idEntity* ent ) const { + return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber; +} + + +/* +================= +idPlayer::SetPortalSkyEnt +================= +*/ +void idGameLocal::SetPortalSkyEnt( idEntity *ent ) { + portalSkyEnt = ent; +} + +/* +================= +idPlayer::IsPortalSkyAcive +================= +*/ +bool idGameLocal::IsPortalSkyAcive() { + return portalSkyActive; +} + +/* +=========== +idGameLocal::SelectTimeGroup +============ +*/ +void idGameLocal::SelectTimeGroup( int timeGroup ) { + if ( timeGroup ) { + fast.Get( time, previousTime, realClientTime ); + } else { + slow.Get( time, previousTime, realClientTime ); + } + + selectedGroup = timeGroup; +} + +/* +=========== +idGameLocal::GetTimeGroupTime +============ +*/ +int idGameLocal::GetTimeGroupTime( int timeGroup ) { + if ( timeGroup ) { + return fast.time; + } else { + return slow.time; + } +} + +/* +=========== +idGameLocal::ComputeSlowScale +============ +*/ +void idGameLocal::ComputeSlowScale() { + + // check if we need to do a quick reset + if ( quickSlowmoReset ) { + quickSlowmoReset = false; + + // stop the sounds + if ( gameSoundWorld ) { + gameSoundWorld->SetSlowmoSpeed( 1.0f ); + } + + // stop the state + slowmoState = SLOWMO_STATE_OFF; + slowmoScale = 1.0f; + } + + // check the player state + idPlayer * player = GetLocalPlayer(); + bool powerupOn = false; + + if ( player != NULL && player->PowerUpActive( HELLTIME ) ) { + powerupOn = true; + } + else if ( g_enableSlowmo.GetBool() ) { + powerupOn = true; + } + + // determine proper slowmo state + if ( powerupOn && slowmoState == SLOWMO_STATE_OFF ) { + slowmoState = SLOWMO_STATE_RAMPUP; + + if ( gameSoundWorld ) { + gameSoundWorld->SetSlowmoSpeed( slowmoScale ); + } + } + else if ( !powerupOn && slowmoState == SLOWMO_STATE_ON ) { + slowmoState = SLOWMO_STATE_RAMPDOWN; + + // play the stop sound + if ( player != NULL ) { + player->PlayHelltimeStopSound(); + } + } + + // do any necessary ramping + if ( slowmoState == SLOWMO_STATE_RAMPUP ) { + float delta = ( 0.25f - slowmoScale ); + + if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) { + slowmoScale = 0.25f; + slowmoState = SLOWMO_STATE_ON; + } else { + slowmoScale += delta * g_slowmoStepRate.GetFloat(); + } + + if ( gameSoundWorld != NULL ) { + gameSoundWorld->SetSlowmoSpeed( slowmoScale ); + } + } + else if ( slowmoState == SLOWMO_STATE_RAMPDOWN ) { + float delta = ( 1.0f - slowmoScale ); + + if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) { + slowmoScale = 1.0f; + slowmoState = SLOWMO_STATE_OFF; + } else { + slowmoScale += delta * g_slowmoStepRate.GetFloat(); + } + + if ( gameSoundWorld != NULL ) { + gameSoundWorld->SetSlowmoSpeed( slowmoScale ); + } + } +} + +/* +=========== +idGameLocal::ResetSlowTimeVars +============ +*/ +void idGameLocal::ResetSlowTimeVars() { + slowmoScale = 1.0f; + slowmoState = SLOWMO_STATE_OFF; + + fast.previousTime = 0; + fast.time = 0; + + slow.previousTime = 0; + slow.time = 0; +} + +/* +=========== +idGameLocal::QuickSlowmoReset +============ +*/ +void idGameLocal::QuickSlowmoReset() { + quickSlowmoReset = true; +} + + +/* +================ +idGameLocal::GetClientStats +================ +*/ +void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) { + mpGame.PlayerStats( clientNum, data, len ); +} + +/* +================ +idGameLocal::GetMPGameModes +================ +*/ +int idGameLocal::GetMPGameModes( const char *** gameModes, const char *** gameModesDisplay ) { + return mpGame.GetGameModes( gameModes, gameModesDisplay ); +} + +/* +======================== +idGameLocal::IsPDAOpen +======================== +*/ +bool idGameLocal::IsPDAOpen() const { + return GetLocalPlayer() && GetLocalPlayer()->objectiveSystemOpen; +} + +/* +======================== +idGameLocal::IsPlayerChatting +======================== +*/ +bool idGameLocal::IsPlayerChatting() const { + return GetLocalPlayer() && ( GetLocalPlayer()->isChatting > 0 ) && !GetLocalPlayer()->spectating; +} + +/* +======================== +idGameLocal::InhibitControls +======================== +*/ +bool idGameLocal::InhibitControls() { + return ( Shell_IsActive() || IsPDAOpen() || IsPlayerChatting() || ( common->IsMultiplayer() && mpGame.IsScoreboardActive() ) ); +} + +/* +=============================== +idGameLocal::Leaderboards_Init +=============================== +*/ +void idGameLocal::Leaderboards_Init() { + LeaderboardLocal_Init(); +} + +/* +=============================== +idGameLocal::Leaderboards_Shutdown +=============================== +*/ +void idGameLocal::Leaderboards_Shutdown() { + LeaderboardLocal_Shutdown(); +} + +/* +======================== +idGameLocal::Shell_ClearRepeater +======================== +*/ +void idGameLocal::Shell_ClearRepeater() { + if ( shellHandler != NULL ) { + shellHandler->ClearWidgetActionRepeater(); + } +} + +/* +======================== +idGameLocal::Shell_Init +======================== +*/ +void idGameLocal::Shell_Init( const char * filename, idSoundWorld * sw ) { + if ( shellHandler != NULL ) { + shellHandler->Initialize( filename, sw ); + } +} + +/* +======================== +idGameLocal::Shell_Init +======================== +*/ +void idGameLocal::Shell_Cleanup() { + if ( shellHandler != NULL ) { + delete shellHandler; + shellHandler = NULL; + } + + mpGame.CleanupScoreboard(); +} + +/* +======================== +idGameLocal::Shell_CreateMenu +======================== +*/ +void idGameLocal::Shell_CreateMenu( bool inGame ) { + Shell_ResetMenu(); + + if ( shellHandler != NULL ) { + if ( !inGame ) { + shellHandler->SetInGame( false ); + Shell_Init( "shell", common->MenuSW() ); + } else { + shellHandler->SetInGame( true ); + if ( common->IsMultiplayer() ) { + Shell_Init( "pause", common->SW() ); + } else { + Shell_Init( "pause", common->MenuSW() ); + } + } + } +} + +/* +======================== +idGameLocal::Shell_ClosePause +======================== +*/ +void idGameLocal::Shell_ClosePause() { + if ( shellHandler != NULL ) { + + if ( !common->IsMultiplayer() && GetLocalPlayer() && GetLocalPlayer()->health <= 0 ) { + return; + } + + if ( shellHandler->GetGameComplete() ) { + return; + } + + shellHandler->SetNextScreen( SHELL_AREA_INVALID, MENU_TRANSITION_SIMPLE ); + } +} + +/* +======================== +idGameLocal::Shell_Show +======================== +*/ +void idGameLocal::Shell_Show( bool show ) { + if ( shellHandler != NULL ) { + shellHandler->ActivateMenu( show ); + } +} + +/* +======================== +idGameLocal::Shell_IsActive +======================== +*/ +bool idGameLocal::Shell_IsActive() const { + if ( shellHandler != NULL ) { + return shellHandler->IsActive(); + } + return false; +} + +/* +======================== +idGameLocal::Shell_HandleGuiEvent +======================== +*/ +bool idGameLocal::Shell_HandleGuiEvent( const sysEvent_t * sev ) { + if ( shellHandler != NULL ) { + return shellHandler->HandleGuiEvent( sev ); + } + return false; +} + +/* +======================== +idGameLocal::Shell_Render +======================== +*/ +void idGameLocal::Shell_Render() { + if ( shellHandler != NULL ) { + shellHandler->Update(); + } +} + +/* +======================== +idGameLocal::Shell_ResetMenu +======================== +*/ +void idGameLocal::Shell_ResetMenu() { + if ( shellHandler != NULL ) { + delete shellHandler; + shellHandler = new (TAG_SWF) idMenuHandler_Shell(); + } +} + +/* +================= +idGameLocal::Shell_SyncWithSession +================= +*/ +void idGameLocal::Shell_SyncWithSession() { + if ( shellHandler == NULL ) { + return; + } + switch ( session->GetState() ) { + case idSession::PRESS_START: + shellHandler->SetShellState( SHELL_STATE_PRESS_START ); + break; + case idSession::INGAME: + shellHandler->SetShellState( SHELL_STATE_PAUSED ); + break; + case idSession::IDLE: + shellHandler->SetShellState( SHELL_STATE_IDLE ); + break; + case idSession::PARTY_LOBBY: + shellHandler->SetShellState( SHELL_STATE_PARTY_LOBBY ); + break; + case idSession::GAME_LOBBY: + shellHandler->SetShellState( SHELL_STATE_GAME_LOBBY ); + break; + case idSession::SEARCHING: + shellHandler->SetShellState( SHELL_STATE_SEARCHING ); + break; + case idSession::LOADING: + shellHandler->SetShellState( SHELL_STATE_LOADING ); + break; + case idSession::CONNECTING: + shellHandler->SetShellState( SHELL_STATE_CONNECTING ); + break; + case idSession::BUSY: + shellHandler->SetShellState( SHELL_STATE_BUSY ); + break; + } +} + +void idGameLocal::Shell_SetGameComplete() { + if ( shellHandler != NULL ) { + shellHandler->SetGameComplete(); + Shell_Show( true ); + } +} + +/* +======================== +idGameLocal::Shell_SetState_GameLobby +======================== +*/ +void idGameLocal::Shell_UpdateSavedGames() { + if ( shellHandler != NULL ) { + shellHandler->UpdateSavedGames(); + } +} + +/* +======================== +idGameLocal::Shell_SetCanContinue +======================== +*/ +void idGameLocal::Shell_SetCanContinue( bool valid ) { + if ( shellHandler != NULL ) { + shellHandler->SetCanContinue( valid ); + } +} + +/* +======================== +idGameLocal::Shell_SetState_GameLobby +======================== +*/ +void idGameLocal::Shell_UpdateClientCountdown( int countdown ) { + if ( shellHandler != NULL ) { + shellHandler->SetTimeRemaining( countdown ); + } +} + +/* +======================== +idGameLocal::Shell_SetState_GameLobby +======================== +*/ +void idGameLocal::Shell_UpdateLeaderboard( const idLeaderboardCallback * callback ) { + if ( shellHandler != NULL ) { + shellHandler->UpdateLeaderboard( callback ); + } +} + +/* +======================== +idGameLocal::SimulateProjectiles +======================== +*/ +bool idGameLocal::SimulateProjectiles() { + bool moreProjectiles = false; + // Simulate projectiles + for ( int i = 0; i < idProjectile::MAX_SIMULATED_PROJECTILES; i++ ) { + if ( idProjectile::projectilesToSimulate[i].projectile != NULL && idProjectile::projectilesToSimulate[i].startTime != 0 ) { + const int startTime = idProjectile::projectilesToSimulate[i].startTime; + const int startFrame = MSEC_TO_FRAME_FLOOR( startTime ); + const int endFrame = startFrame + 1; + const int endTime = FRAME_TO_MSEC( endFrame ); + + idProjectile::projectilesToSimulate[i].projectile->SimulateProjectileFrame( endTime - startTime, endTime ); + + if ( idProjectile::projectilesToSimulate[i].projectile != NULL ) { + if ( endTime >= previousServerTime ) { + idProjectile::projectilesToSimulate[i].projectile->PostSimulate( endTime ); + idProjectile::projectilesToSimulate[i].startTime = 0; + idProjectile::projectilesToSimulate[i].projectile = NULL; + } else { + idProjectile::projectilesToSimulate[i].startTime = endTime; + moreProjectiles = true; + } + } + } + } + + return moreProjectiles; +} diff --git a/neo/d3xp/Game_local.h b/neo/d3xp/Game_local.h new file mode 100644 index 00000000..465a63f8 --- /dev/null +++ b/neo/d3xp/Game_local.h @@ -0,0 +1,819 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_LOCAL_H__ +#define __GAME_LOCAL_H__ + +/* +=============================================================================== + + Local implementation of the public game interface. + +=============================================================================== +*/ + +#ifdef ID_DEBUG_UNINITIALIZED_MEMORY +// This is real evil but allows the code to inspect arbitrary class variables. +#define private public +#define protected public +#endif + +extern idRenderWorld * gameRenderWorld; +extern idSoundWorld * gameSoundWorld; + +// the "gameversion" client command will print this plus compile date +#define GAME_VERSION "baseDOOM-1" + +// classes used by idGameLocal +class idEntity; +class idActor; +class idPlayer; +class idCamera; +class idWorldspawn; +class idTestModel; +class idAAS; +class idAI; +class idSmokeParticles; +class idEntityFx; +class idTypeInfo; +class idProgram; +class idThread; +class idEditEntities; +class idLocationEntity; +class idMenuHandler_Shell; + +const int MAX_CLIENTS = MAX_PLAYERS; +const int MAX_CLIENTS_IN_PVS = MAX_CLIENTS >> 3; +const int GENTITYNUM_BITS = 12; +const int MAX_GENTITIES = 1 << GENTITYNUM_BITS; +const int ENTITYNUM_NONE = MAX_GENTITIES - 1; +const int ENTITYNUM_WORLD = MAX_GENTITIES - 2; +const int ENTITYNUM_MAX_NORMAL = MAX_GENTITIES - 2; +const int ENTITYNUM_FIRST_NON_REPLICATED = ENTITYNUM_MAX_NORMAL - 256; + +//============================================================================ + +void gameError( const char *fmt, ... ); + +#include "gamesys/Event.h" +#include "gamesys/Class.h" +#include "gamesys/SysCvar.h" +#include "gamesys/SysCmds.h" +#include "gamesys/SaveGame.h" + +#include "script/Script_Program.h" + +#include "anim/Anim.h" + +#include "ai/AAS.h" + +#include "physics/Clip.h" +#include "physics/Push.h" + +#include "Pvs.h" +#include "Leaderboards.h" +#include "MultiplayerGame.h" + + +class idWeapon; + +//============================================================================ + +const int MAX_GAME_MESSAGE_SIZE = 8192; +const int MAX_ENTITY_STATE_SIZE = 512; +const int ENTITY_PVS_SIZE = ((MAX_GENTITIES+31)>>5); +const int NUM_RENDER_PORTAL_BITS = idMath::BitsForInteger( PS_BLOCK_ALL ); + +const int MAX_EVENT_PARAM_SIZE = 128; + +typedef struct entityNetEvent_s { + int spawnId; + int event; + int time; + int paramsSize; + byte paramsBuf[MAX_EVENT_PARAM_SIZE]; + struct entityNetEvent_s *next; + struct entityNetEvent_s *prev; +} entityNetEvent_t; + +enum { + GAME_RELIABLE_MESSAGE_SYNCEDCVARS, + GAME_RELIABLE_MESSAGE_SPAWN_PLAYER, + GAME_RELIABLE_MESSAGE_CHAT, + GAME_RELIABLE_MESSAGE_TCHAT, + GAME_RELIABLE_MESSAGE_SOUND_EVENT, + GAME_RELIABLE_MESSAGE_SOUND_INDEX, + GAME_RELIABLE_MESSAGE_DB, + GAME_RELIABLE_MESSAGE_DROPWEAPON, + GAME_RELIABLE_MESSAGE_RESTART, + GAME_RELIABLE_MESSAGE_TOURNEYLINE, + GAME_RELIABLE_MESSAGE_VCHAT, + GAME_RELIABLE_MESSAGE_STARTSTATE, + GAME_RELIABLE_MESSAGE_WARMUPTIME, + GAME_RELIABLE_MESSAGE_SPECTATE, + GAME_RELIABLE_MESSAGE_EVENT, + GAME_RELIABLE_MESSAGE_LOBBY_COUNTDOWN, + GAME_RELIABLE_MESSAGE_RESPAWN_AVAILABLE, // Used just to show clients the respawn text on the hud. + GAME_RELIABLE_MESSAGE_MATCH_STARTED_TIME, + GAME_RELIABLE_MESSAGE_ACHIEVEMENT_UNLOCK, + GAME_RELIABLE_MESSAGE_CLIENT_HITSCAN_HIT +}; + +typedef enum { + GAMESTATE_UNINITIALIZED, // prior to Init being called + GAMESTATE_NOMAP, // no map loaded + GAMESTATE_STARTUP, // inside InitFromNewMap(). spawning map entities. + GAMESTATE_ACTIVE, // normal gameplay + GAMESTATE_SHUTDOWN // inside MapShutdown(). clearing memory. +} gameState_t; + +typedef struct { + idEntity *ent; + int dist; + int team; +} spawnSpot_t; + +//============================================================================ + +class idEventQueue { +public: + typedef enum { + OUTOFORDER_IGNORE, + OUTOFORDER_DROP, + OUTOFORDER_SORT + } outOfOrderBehaviour_t; + + idEventQueue() : start( NULL ), end( NULL ) {} + + entityNetEvent_t * Alloc(); + void Free( entityNetEvent_t *event ); + void Shutdown(); + + void Init(); + void Enqueue( entityNetEvent_t* event, outOfOrderBehaviour_t oooBehaviour ); + entityNetEvent_t * Dequeue(); + entityNetEvent_t * RemoveLast(); + + entityNetEvent_t * Start() { return start; } + +private: + entityNetEvent_t * start; + entityNetEvent_t * end; + idBlockAlloc eventAllocator; +}; + +//============================================================================ + +template< class type > +class idEntityPtr { +public: + idEntityPtr(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + idEntityPtr & operator=( const type * ent ); + idEntityPtr & operator=( const idEntityPtr & ep ); + + bool operator==( const idEntityPtr & ep ) { return spawnId == ep.spawnId; } + + type * operator->() const { return GetEntity(); } + operator type * () const { return GetEntity(); } + + // synchronize entity pointers over the network + int GetSpawnId() const { return spawnId; } + bool SetSpawnId( int id ); + bool UpdateSpawnId(); + + bool IsValid() const; + type * GetEntity() const; + int GetEntityNum() const; + +private: + int spawnId; +}; + +struct timeState_t { + int time; + int previousTime; + int realClientTime; + + void Set( int t, int pt, int rct ) { time = t; previousTime = pt; realClientTime = rct; }; + void Get( int & t, int & pt, int & rct ) { t = time; pt = previousTime; rct = realClientTime; }; + void Save( idSaveGame *savefile ) const { savefile->WriteInt( time ); savefile->WriteInt( previousTime ); savefile->WriteInt( realClientTime ); } + void Restore( idRestoreGame *savefile ) { savefile->ReadInt( time ); savefile->ReadInt( previousTime ); savefile->ReadInt( realClientTime ); } +}; + +enum slowmoState_t { + SLOWMO_STATE_OFF, + SLOWMO_STATE_RAMPUP, + SLOWMO_STATE_ON, + SLOWMO_STATE_RAMPDOWN +}; + +//============================================================================ + +class idGameLocal : public idGame { +public: + + int previousServerTime; // time in msec of last frame on the server + int serverTime; // in msec. ( on the client ) the server time. ( on the server ) the actual game time. + idDict serverInfo; // all the tunable parameters, like numclients, etc + int numClients; // pulled from serverInfo and verified + idArray< lobbyUserID_t, MAX_CLIENTS > lobbyUserIDs; // Maps from a client (player) number to a lobby user + idDict persistentPlayerInfo[MAX_CLIENTS]; + idEntity * entities[MAX_GENTITIES];// index to entities + int spawnIds[MAX_GENTITIES];// for use in idEntityPtr + idArray< int, 2 > firstFreeEntityIndex; // first free index in the entities array. [0] for replicated entities, [1] for non-replicated + int num_entities; // current number <= MAX_GENTITIES + idHashIndex entityHash; // hash table to quickly find entities by name + idWorldspawn * world; // world entity + idLinkList spawnedEntities; // all spawned entities + idLinkList activeEntities; // all thinking entities (idEntity::thinkFlags != 0) + idLinkList aimAssistEntities; // all aim Assist entities + int numEntitiesToDeactivate;// number of entities that became inactive in current frame + bool sortPushers; // true if active lists needs to be reordered to place pushers at the front + bool sortTeamMasters; // true if active lists needs to be reordered to place physics team masters before their slaves + idDict persistentLevelInfo; // contains args that are kept around between levels + + // can be used to automatically effect every material in the world that references globalParms + float globalShaderParms[ MAX_GLOBAL_SHADER_PARMS ]; + + idRandom random; // random number generator used throughout the game + + idProgram program; // currently loaded script and data space + idThread * frameCommandThread; + + idClip clip; // collision detection + idPush push; // geometric pushing + idPVS pvs; // potential visible set + + idTestModel * testmodel; // for development testing of models + idEntityFx * testFx; // for development testing of fx + + idStr sessionCommand; // a target_sessionCommand can set this to return something to the session + + idMultiplayerGame mpGame; // handles rules for standard dm + + idSmokeParticles * smokeParticles; // global smoke trails + idEditEntities * editEntities; // in game editing + + bool inCinematic; // game is playing cinematic (player controls frozen) + + int framenum; + int time; // in msec + int previousTime; // time in msec of last frame + + int vacuumAreaNum; // -1 if level doesn't have any outside areas + + gameType_t gameType; + idLinkList snapshotEntities; // entities from the last snapshot + int realClientTime; // real client time + bool isNewFrame; // true if this is a new game frame, not a rerun due to prediction + float clientSmoothing; // smoothing of other clients in the view + int entityDefBits; // bits required to store an entity def number + + static const char * sufaceTypeNames[ MAX_SURFACE_TYPES ]; // text names for surface types + + idEntityPtr lastGUIEnt; // last entity with a GUI, used by Cmd_NextGUI_f + int lastGUI; // last GUI on the lastGUIEnt + + idEntityPtr playerActivateFragChamber; // The player that activated the frag chamber + + idEntityPtr portalSkyEnt; + bool portalSkyActive; + + void SetPortalSkyEnt( idEntity *ent ); + bool IsPortalSkyAcive(); + + timeState_t fast; + timeState_t slow; + int selectedGroup; + + slowmoState_t slowmoState; + float slowmoScale; + + bool quickSlowmoReset; + + virtual void SelectTimeGroup( int timeGroup ); + virtual int GetTimeGroupTime( int timeGroup ); + + void ComputeSlowScale(); + void RunTimeGroup2( idUserCmdMgr & userCmdMgr ); + + void ResetSlowTimeVars(); + void QuickSlowmoReset(); + + + void Tokenize( idStrList &out, const char *in ); + + // ---------------------- Public idGame Interface ------------------- + + idGameLocal(); + + virtual void Init(); + virtual void Shutdown(); + virtual void SetServerInfo( const idDict &serverInfo ); + virtual const idDict & GetServerInfo(); + + virtual const idDict & GetPersistentPlayerInfo( int clientNum ); + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, int gameType, int randSeed ); + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, idFile * saveGameFile, idFile * stringTableFile, int saveGameVersion ); + virtual void SaveGame( idFile *saveGameFile, idFile *stringTableFile ); + virtual void GetSaveGameDetails( idSaveGameDetails & gameDetails ); + virtual void MapShutdown(); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual void Preload( const idPreloadManifest &manifest ); + virtual void RunFrame( idUserCmdMgr & cmdMgr, gameReturn_t & gameReturn ); + void RunAllUserCmdsForPlayer( idUserCmdMgr & cmdMgr, const int playerNumber ); + void RunSingleUserCmd( usercmd_t & cmd, idPlayer & player ); + void RunEntityThink( idEntity & ent, idUserCmdMgr & userCmdMgr ); + virtual bool Draw( int clientNum ); + virtual bool HandlePlayerGuiEvent( const sysEvent_t * ev ); + virtual void ServerWriteSnapshot( idSnapShot & ss ); + virtual void ProcessReliableMessage( int clientNum, int type, const idBitMsg &msg ); + virtual void ClientReadSnapshot( const idSnapShot & ss ); + virtual void ClientRunFrame( idUserCmdMgr & cmdMgr, bool lastPredictFrame, gameReturn_t & ret ); + void BuildReturnValue( gameReturn_t & ret ); + + virtual int GetMPGameModes( const char *** gameModes, const char *** gameModesDisplay ); + + virtual void GetClientStats( int clientNum, char *data, const int len ); + + virtual bool IsInGame() const { return GameState() == GAMESTATE_ACTIVE; } + + virtual int MapPeerToClient( int peer ) const; + virtual int GetLocalClientNum() const; + + virtual void GetAimAssistAngles( idAngles & angles ); + virtual float GetAimAssistSensitivity(); + + // ---------------------- Public idGameLocal Interface ------------------- + + void Printf( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void DPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void Warning( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void DWarning( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void Error( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + + // Initializes all map variables common to both save games and spawned games + void LoadMap( const char *mapName, int randseed ); + + void LocalMapRestart(); + void MapRestart(); + static void MapRestart_f( const idCmdArgs &args ); + + idMapFile * GetLevelMap(); + const char * GetMapName() const; + + int NumAAS() const; + idAAS * GetAAS( int num ) const; + idAAS * GetAAS( const char *name ) const; + void SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ); + aasHandle_t AddAASObstacle( const idBounds &bounds ); + void RemoveAASObstacle( const aasHandle_t handle ); + void RemoveAllAASObstacles(); + + bool CheatsOk( bool requirePlayer = true ); + gameState_t GameState() const; + idEntity * SpawnEntityType( const idTypeInfo &classdef, const idDict *args = NULL, bool bIsClientReadSnapshot = false ); + bool SpawnEntityDef( const idDict &args, idEntity **ent = NULL, bool setDefaults = true ); + int GetSpawnId( const idEntity *ent ) const; + + const idDeclEntityDef * FindEntityDef( const char *name, bool makeDefault = true ) const; + const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + + void RegisterEntity( idEntity *ent, int forceSpawnId, const idDict & spawnArgsToCopy ); + void UnregisterEntity( idEntity *ent ); + const idDict & GetSpawnArgs() const { return spawnArgs; } + + bool RequirementMet( idEntity *activator, const idStr &requires, int removeItem ); + + void AlertAI( idEntity *ent ); + idActor * GetAlertEntity(); + + bool InPlayerPVS( idEntity *ent ) const; + bool InPlayerConnectedArea( idEntity *ent ) const; + pvsHandle_t GetPlayerPVS() { return playerPVS; }; + + void SetCamera( idCamera *cam ); + idCamera * GetCamera() const; + void CalcFov( float base_fov, float &fov_x, float &fov_y ) const; + + void AddEntityToHash( const char *name, idEntity *ent ); + bool RemoveEntityFromHash( const char *name, idEntity *ent ); + int GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const; + + // returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. + idEntity * GetTraceEntity( const trace_t &trace ) const; + + static void ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ); + idEntity * FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const; + idEntity * FindEntity( const char *name ) const; + idEntity * FindEntityUsingDef( idEntity *from, const char *match ) const; + int EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const; + + void KillBox( idEntity *ent, bool catch_teleport = false ); + void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f ); + void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); + void RadiusPushClipModel( const idVec3 &origin, const float push, const idClipModel *clipModel ); + + void ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle = 0 ); + void BloodSplat( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + void CallFrameCommand( idEntity *ent, const function_t *frameCommand ); + void CallObjectFrameCommand( idEntity *ent, const char *frameCommand ); + + const idVec3 & GetGravity() const; + + // added the following to assist licensees with merge issues + int GetFrameNum() const { return framenum; }; + int GetTime() const { return time; }; + + int GetNextClientNum( int current ) const; + idPlayer * GetClientByNum( int current ) const; + + idPlayer * GetLocalPlayer() const; + + void SpreadLocations(); + idLocationEntity * LocationForPoint( const idVec3 &point ); // May return NULL + idEntity * SelectInitialSpawnPoint( idPlayer *player ); + + void SetPortalState( qhandle_t portal, int blockingBits ); + void SaveEntityNetworkEvent( const idEntity *ent, int event, const idBitMsg *msg ); + int ServerRemapDecl( int clientNum, declType_t type, int index ); + int ClientRemapDecl( declType_t type, int index ); + void SyncPlayersWithLobbyUsers( bool initial ); + void ServerWriteInitialReliableMessages( int clientNum, lobbyUserID_t lobbyUserID ); + void ServerSendNetworkSyncCvars(); + + virtual void SetInterpolation( const float fraction, const int serverGameMS, const int ssStartTime, const int ssEndTime ); + + void ServerProcessReliableMessage( int clientNum, int type, const idBitMsg &msg ); + void ClientProcessReliableMessage( int type, const idBitMsg &msg ); + + // Snapshot times - track exactly what times we are interpolating from and to + int GetSSEndTime() const { return netInterpolationInfo.ssEndTime; } + int GetSSStartTime() const { return netInterpolationInfo.ssStartTime; } + + virtual void SetServerGameTimeMs( const int time ); + virtual int GetServerGameTimeMs() const; + + idEntity * FindPredictedEntity( uint32 predictedKey, idTypeInfo * type ); + uint32 GeneratePredictionKey( idWeapon * weapon, idPlayer * playerAttacker, int overrideKey ); + + int GetLastClientUsercmdMilliseconds( int playerIndex ) const { return usercmdLastClientMilliseconds[ playerIndex ]; } + + void SetGlobalMaterial( const idMaterial *mat ); + const idMaterial * GetGlobalMaterial(); + + void SetGibTime( int _time ) { nextGibTime = _time; }; + int GetGibTime() { return nextGibTime; }; + + virtual bool InhibitControls(); + virtual bool IsPDAOpen() const; + virtual bool IsPlayerChatting() const; + + // Creates leaderboards for each map/mode defined. + virtual void Leaderboards_Init(); + virtual void Leaderboards_Shutdown(); + + // MAIN MENU FUNCTIONS + virtual void Shell_Init( const char * filename, idSoundWorld * sw ); + virtual void Shell_Cleanup(); + virtual void Shell_Show( bool show ); + virtual void Shell_ClosePause(); + virtual void Shell_CreateMenu( bool inGame ); + virtual bool Shell_IsActive() const; + virtual bool Shell_HandleGuiEvent( const sysEvent_t * sev ); + virtual void Shell_Render(); + virtual void Shell_ResetMenu(); + virtual void Shell_SyncWithSession() ; + virtual void Shell_SetCanContinue( bool valid ); + virtual void Shell_UpdateSavedGames(); + virtual void Shell_UpdateClientCountdown( int countdown ); + virtual void Shell_UpdateLeaderboard( const idLeaderboardCallback * callback ); + virtual void Shell_SetGameComplete(); + + void Shell_ClearRepeater(); + + const char * GetMapFileName() { return mapFileName.c_str(); } + + const char * GetMPPlayerDefName() const; + +private: + const static int INITIAL_SPAWN_COUNT = 1; + + idStr mapFileName; // name of the map, empty string if no map loaded + idMapFile * mapFile; // will be NULL during the game unless in-game editing is used + bool mapCycleLoaded; + + int spawnCount; + int mapSpawnCount; // it's handy to know which entities are part of the map + + idLocationEntity ** locationEntities; // for location names, etc + + idCamera * camera; + const idMaterial * globalMaterial; // for overriding everything + + idList aasList; // area system + + idMenuHandler_Shell * shellHandler; + + idStrList aasNames; + + idEntityPtr lastAIAlertEntity; + int lastAIAlertTime; + + idDict spawnArgs; // spawn args used during entity spawning FIXME: shouldn't be necessary anymore + + pvsHandle_t playerPVS; // merged pvs of all players + pvsHandle_t playerConnectedAreas; // all areas connected to any player area + + idVec3 gravity; // global gravity vector + gameState_t gamestate; // keeps track of whether we're spawning, shutting down, or normal gameplay + bool influenceActive; // true when a phantasm is happening + int nextGibTime; + + idEventQueue eventQueue; + idEventQueue savedEventQueue; + + idStaticList spawnSpots; + idStaticList initialSpots; + int currentInitialSpot; + + idStaticList teamSpawnSpots[2]; + idStaticList teamInitialSpots[2]; + int teamCurrentInitialSpot[2]; + + struct netInterpolationInfo_t { // Was in GameTimeManager.h in id5, needed common place to put this. + netInterpolationInfo_t() + : pct( 0.0f ) + , serverGameMs( 0 ) + , previousServerGameMs( 0 ) + , ssStartTime( 0 ) + , ssEndTime( 0 ) + {} + float pct; // % of current interpolation + int serverGameMs; // Interpolated server game time + int previousServerGameMs; // last frame's interpolated server game time + int ssStartTime; // Server time of old snapshot + int ssEndTime; // Server time of next snapshot + }; + + netInterpolationInfo_t netInterpolationInfo; + + idDict newInfo; + + idArray< int, MAX_PLAYERS > usercmdLastClientMilliseconds; // The latest client time the server has run. + idArray< int, MAX_PLAYERS > lastCmdRunTimeOnClient; + idArray< int, MAX_PLAYERS > lastCmdRunTimeOnServer; + + void Clear(); + // returns true if the entity shouldn't be spawned at all in this game type or difficulty level + bool InhibitEntitySpawn( idDict &spawnArgs ); + // spawn entities from the map file + void SpawnMapEntities(); + // commons used by init, shutdown, and restart + void MapPopulate(); + void MapClear( bool clearClients ); + + pvsHandle_t GetClientPVS( idPlayer *player, pvsType_t type ); + void SetupPlayerPVS(); + void FreePlayerPVS(); + void UpdateGravity(); + void SortActiveEntityList(); + void ShowTargets(); + void RunDebugInfo(); + + void InitScriptForMap(); + void SetScriptFPS( const float com_engineHz ); + void SpawnPlayer( int clientNum ); + + void InitConsoleCommands(); + void ShutdownConsoleCommands(); + + void InitAsyncNetwork(); + void ShutdownAsyncNetwork(); + void NetworkEventWarning( const entityNetEvent_t *event, VERIFY_FORMAT_STRING const char *fmt, ... ); + void ServerProcessEntityNetworkEventQueue(); + void ClientProcessEntityNetworkEventQueue(); + // call after any change to serverInfo. Will update various quick-access flags + void UpdateServerInfoFlags(); + void RandomizeInitialSpawns(); + static int sortSpawnPoints( const void *ptr1, const void *ptr2 ); + + bool SimulateProjectiles(); +}; + +//============================================================================ + +extern idGameLocal gameLocal; +extern idAnimManager animationLib; + +//============================================================================ + +class idGameError : public idException { +public: + idGameError( const char *text ) : idException( text ) {} +}; + +//============================================================================ + +template< class type > +ID_INLINE idEntityPtr::idEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE void idEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void idEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +template< class type > +ID_INLINE idEntityPtr &idEntityPtr::operator=( const type *ent ) { + if ( ent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE idEntityPtr< type > &idEntityPtr::operator=( const idEntityPtr & ep ) { + spawnId = ep.spawnId; + return *this; +} + + +template< class type > +ID_INLINE bool idEntityPtr::SetSpawnId( int id ) { + // the reason for this first check is unclear: + // the function returning false may mean the spawnId is already set right, or the entity is missing + if ( id == spawnId ) { + return false; + } + if ( ( id >> GENTITYNUM_BITS ) == gameLocal.spawnIds[ id & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool idEntityPtr::IsValid() const { + return ( gameLocal.spawnIds[ spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] == ( spawnId >> GENTITYNUM_BITS ) ); +} + +template< class type > +ID_INLINE type *idEntityPtr::GetEntity() const { + int entityNum = spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( spawnId >> GENTITYNUM_BITS ) ) ) { + return static_cast( gameLocal.entities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int idEntityPtr::GetEntityNum() const { + return ( spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ); +} + +// =========================================================================== + +// +// these defines work for all startsounds from all entity types +// make sure to change script/doom_defs.script if you add any channels, or change their order +// +typedef enum { + SND_CHANNEL_ANY = SCHANNEL_ANY, + SND_CHANNEL_VOICE = SCHANNEL_ONE, + SND_CHANNEL_VOICE2, + SND_CHANNEL_BODY, + SND_CHANNEL_BODY2, + SND_CHANNEL_BODY3, + SND_CHANNEL_WEAPON, + SND_CHANNEL_ITEM, + SND_CHANNEL_HEART, + SND_CHANNEL_PDA_AUDIO, + SND_CHANNEL_PDA_VIDEO, + SND_CHANNEL_DEMONIC, + SND_CHANNEL_RADIO, + + // internal use only. not exposed to script or framecommands. + SND_CHANNEL_AMBIENT, + SND_CHANNEL_DAMAGE +} gameSoundChannel_t; + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER) +#define MASK_OPAQUE (CONTENTS_OPAQUE) +#define MASK_SHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL) +#define MASK_SHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY) + +const float DEFAULT_GRAVITY = 1066.0f; +#define DEFAULT_GRAVITY_STRING "1066" +const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY ); + +const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f ); + +//============================================================================ + +#include "physics/Force.h" +#include "physics/Force_Constant.h" +#include "physics/Force_Drag.h" +#include "physics/Force_Grab.h" +#include "physics/Force_Field.h" +#include "physics/Force_Spring.h" +#include "physics/Physics.h" +#include "physics/Physics_Static.h" +#include "physics/Physics_StaticMulti.h" +#include "physics/Physics_Base.h" +#include "physics/Physics_Actor.h" +#include "physics/Physics_Monster.h" +#include "physics/Physics_Player.h" +#include "physics/Physics_Parametric.h" +#include "physics/Physics_RigidBody.h" +#include "physics/Physics_AF.h" + +#include "SmokeParticles.h" + +#include "Entity.h" +#include "GameEdit.h" +#include "Grabber.h" +#include "AF.h" +#include "IK.h" +#include "AFEntity.h" +#include "Misc.h" +#include "Actor.h" +#include "Projectile.h" +#include "Weapon.h" +#include "Light.h" +#include "WorldSpawn.h" +#include "Item.h" +#include "PlayerView.h" +#include "PlayerIcon.h" +#include "Achievements.h" +#include "AimAssist.h" +#include "Player.h" +#include "Mover.h" +#include "Camera.h" +#include "Moveable.h" +#include "Target.h" +#include "Trigger.h" +#include "Sound.h" +#include "Fx.h" +#include "SecurityCamera.h" +#include "BrittleFracture.h" + +#include "ai/AI.h" +#include "anim/Anim_Testmodel.h" + +// menus +#include "menus/MenuWidget.h" +#include "menus/MenuScreen.h" +#include "menus/MenuHandler.h" + +#include "script/Script_Compiler.h" +#include "script/Script_Interpreter.h" +#include "script/Script_Thread.h" + +#endif /* !__GAME_LOCAL_H__ */ diff --git a/neo/d3xp/Game_network.cpp b/neo/d3xp/Game_network.cpp new file mode 100644 index 00000000..ed2fdf93 --- /dev/null +++ b/neo/d3xp/Game_network.cpp @@ -0,0 +1,1328 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "..\framework\Common_local.h" + +static const int SNAP_GAMESTATE = 0; +static const int SNAP_SHADERPARMS = 1; +static const int SNAP_PORTALS = 2; +static const int SNAP_PLAYERSTATE = SNAP_PORTALS + 1; +static const int SNAP_PLAYERSTATE_END = SNAP_PLAYERSTATE + MAX_PLAYERS; +static const int SNAP_ENTITIES = SNAP_PLAYERSTATE_END; +static const int SNAP_ENTITIES_END = SNAP_ENTITIES + MAX_GENTITIES; +static const int SNAP_LAST_CLIENT_FRAME = SNAP_ENTITIES_END; +static const int SNAP_LAST_CLIENT_FRAME_END = SNAP_LAST_CLIENT_FRAME + MAX_PLAYERS; + +/* +=============================================================================== + + Client running game code: + - entity events don't work and should not be issued + - entities should never be spawned outside idGameLocal::ClientReadSnapshot + +=============================================================================== +*/ + +idCVar net_clientSmoothing( "net_clientSmoothing", "0.8", CVAR_GAME | CVAR_FLOAT, "smooth other clients angles and position.", 0.0f, 0.95f ); +idCVar net_clientSelfSmoothing( "net_clientSelfSmoothing", "0.6", CVAR_GAME | CVAR_FLOAT, "smooth self position if network causes prediction error.", 0.0f, 0.95f ); +extern idCVar net_clientMaxPrediction; + +idCVar cg_predictedSpawn_debug( "cg_predictedSpawn_debug", "0", CVAR_BOOL, "Debug predictive spawning of presentables" ); +idCVar g_clientFire_checkLineOfSightDebug( "g_clientFire_checkLineOfSightDebug", "0", CVAR_BOOL, "" ); + + +/* +================ +idGameLocal::InitAsyncNetwork +================ +*/ +void idGameLocal::InitAsyncNetwork() { + eventQueue.Init(); + savedEventQueue.Init(); + + entityDefBits = -( idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1 ); + realClientTime = 0; + fast.Set( 0, 0, 0 ); + slow.Set( 0, 0, 0 ); + isNewFrame = true; + clientSmoothing = net_clientSmoothing.GetFloat(); + + lastCmdRunTimeOnClient.Zero(); + lastCmdRunTimeOnServer.Zero(); + usercmdLastClientMilliseconds.Zero(); +} + +/* +================ +idGameLocal::ShutdownAsyncNetwork +================ +*/ +void idGameLocal::ShutdownAsyncNetwork() { + eventQueue.Shutdown(); + savedEventQueue.Shutdown(); +} + +/* +================ +idGameLocal::ServerRemapDecl +================ +*/ +int idGameLocal::ServerRemapDecl( int clientNum, declType_t type, int index ) { + return index; +} + +/* +================ +idGameLocal::ClientRemapDecl +================ +*/ +int idGameLocal::ClientRemapDecl( declType_t type, int index ) { + return index; +} + +/* +================ +idGameLocal::SyncPlayersWithLobbyUsers +================ +*/ +void idGameLocal::SyncPlayersWithLobbyUsers( bool initial ) { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + if ( !lobby.IsHost() ) { + return; + } + + idStaticList< lobbyUserID_t, MAX_CLIENTS > newLobbyUsers; + + // First, loop over lobby users, and see if we find a lobby user that we haven't registered + for ( int i = 0; i < lobby.GetNumLobbyUsers(); i++ ) { + lobbyUserID_t lobbyUserID1 = lobby.GetLobbyUserIdByOrdinal( i ); + + if ( !lobbyUserID1.IsValid() ) { + continue; + } + + if ( !initial && !lobby.IsLobbyUserLoaded( lobbyUserID1 ) ) { + continue; + } + + // Now, see if we find this lobby user in our list + bool found = false; + + for ( int j = 0; j < MAX_PLAYERS; j++ ) { + idPlayer * player = static_cast( entities[ j ] ); + if ( player == NULL ) { + continue; + } + + lobbyUserID_t lobbyUserID2 = lobbyUserIDs[j]; + + if ( lobbyUserID1 == lobbyUserID2 ) { + found = true; + break; + } + } + + if ( !found ) { + // If we didn't find it, we need to create a player and assign it to this new lobby user + newLobbyUsers.Append( lobbyUserID1 ); + } + } + + // Validate connected players + for ( int i = 0; i < MAX_PLAYERS; i++ ) { + idPlayer * player = static_cast( entities[ i ] ); + if ( player == NULL ) { + continue; + } + + lobbyUserID_t lobbyUserID = lobbyUserIDs[i]; + + if ( !lobby.IsLobbyUserValid( lobbyUserID ) ) { + delete entities[ i ]; + mpGame.DisconnectClient( i ); + lobbyUserIDs[i] = lobbyUserID_t(); + continue; + } + + lobby.EnableSnapshotsForLobbyUser( lobbyUserID ); + } + + while ( newLobbyUsers.Num() > 0 ) { + // Find a free player data slot to use for this new player + int freePlayerDataIndex = -1; + + for ( int i = 0; i < MAX_PLAYERS; ++i ) { + idPlayer * player = static_cast( entities[ i ] ); + if ( player == NULL ) { + freePlayerDataIndex = i; + break; + } + } + if ( freePlayerDataIndex == -1 ) { + break; // No player data slots (this shouldn't happen) + } + lobbyUserID_t lobbyUserID = newLobbyUsers[0]; + newLobbyUsers.RemoveIndex( 0 ); + + mpGame.ServerClientConnect( freePlayerDataIndex ); + Printf( "client %d connected.\n", freePlayerDataIndex ); + + lobbyUserIDs[ freePlayerDataIndex ] = lobbyUserID; + + // Clear this player's old usercmds. + common->ResetPlayerInput( freePlayerDataIndex ); + + common->UpdateLevelLoadPacifier(); + + + // spawn the player + SpawnPlayer( freePlayerDataIndex ); + + common->UpdateLevelLoadPacifier(); + + ServerWriteInitialReliableMessages( freePlayerDataIndex, lobbyUserID ); + } +} + +/* +================ +idGameLocal::ServerSendNetworkSyncCvars +================ +*/ +void idGameLocal::ServerSendNetworkSyncCvars() { + if ( ( cvarSystem->GetModifiedFlags() & CVAR_NETWORKSYNC ) == 0 ) { + return; + } + cvarSystem->ClearModifiedFlags( CVAR_NETWORKSYNC ); + + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + idDict syncedCvars; + cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC, syncedCvars, true ); + outMsg.WriteDeltaDict( syncedCvars, NULL ); + lobby.SendReliable( GAME_RELIABLE_MESSAGE_SYNCEDCVARS, outMsg, false ); + + idLib::Printf( "Sending networkSync cvars:\n" ); + syncedCvars.Print(); +} + +/* +================ +idGameLocal::ServerWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::ServerWriteInitialReliableMessages( int clientNum, lobbyUserID_t lobbyUserID ) { + if ( clientNum == GetLocalClientNum() ) { + // We don't need to send messages to ourself + return; + } + + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + idDict syncedCvars; + cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC, syncedCvars, true ); + outMsg.WriteDeltaDict( syncedCvars, NULL ); + lobby.SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_SYNCEDCVARS, outMsg ); + + idLib::Printf( "Sending initial networkSync cvars:\n" ); + syncedCvars.Print(); + + // send all saved events + for ( entityNetEvent_t * event = savedEventQueue.Start(); event; event = event->next ) { + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteBits( event->spawnId, 32 ); + outMsg.WriteByte( event->event ); + outMsg.WriteLong( event->time ); + outMsg.WriteBits( event->paramsSize, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + outMsg.WriteData( event->paramsBuf, event->paramsSize ); + } + lobby.SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_EVENT, outMsg ); + } + + mpGame.ServerWriteInitialReliableMessages( clientNum, lobbyUserID ); +} + +/* +================ +idGameLocal::SaveEntityNetworkEvent +================ +*/ +void idGameLocal::SaveEntityNetworkEvent( const idEntity *ent, int eventId, const idBitMsg *msg ) { + entityNetEvent_t * event = savedEventQueue.Alloc(); + event->spawnId = GetSpawnId( ent ); + event->event = eventId; + event->time = time; + if ( msg ) { + event->paramsSize = msg->GetSize(); + memcpy( event->paramsBuf, msg->GetReadData(), msg->GetSize() ); + } else { + event->paramsSize = 0; + } + + savedEventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); +} + +/* +================ +idGameLocal::ServerWriteSnapshot + + Write a snapshot of the current game state +================ +*/ +void idGameLocal::ServerWriteSnapshot( idSnapShot & ss ) { + + ss.SetTime( fast.time ); + + byte buffer[ MAX_ENTITY_STATE_SIZE ]; + idBitMsg msg; + + // First write the generic game state to the snapshot + msg.InitWrite( buffer, sizeof( buffer ) ); + mpGame.WriteToSnapshot( msg ); + ss.S_AddObject( SNAP_GAMESTATE, ~0U, msg, "Game State" ); + + // Update global shader parameters + msg.InitWrite( buffer, sizeof( buffer ) ); + for ( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + msg.WriteFloat( globalShaderParms[i] ); + } + ss.S_AddObject( SNAP_SHADERPARMS, ~0U, msg, "Shader Parms" ); + + // update portals for opened doors + msg.InitWrite( buffer, sizeof( buffer ) ); + int numPortals = gameRenderWorld->NumPortals(); + msg.WriteLong( numPortals ); + for ( int i = 0; i < numPortals; i++ ) { + msg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + ss.S_AddObject( SNAP_PORTALS, ~0U, msg, "Portal State" ); + + idEntity * skyEnt = portalSkyEnt.GetEntity(); + pvsHandle_t portalSkyPVS; + portalSkyPVS.i = -1; + if ( skyEnt != NULL ) { + portalSkyPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + } + + // Build PVS data for each player and write their player state to the snapshot as well + pvsHandle_t pvsHandles[ MAX_PLAYERS ]; + for ( int i = 0; i < MAX_PLAYERS; i++ ) { + idPlayer * player = static_cast( entities[ i ] ); + if ( player == NULL ) { + pvsHandles[i].i = -1; + continue; + } + idPlayer * spectated = player; + if ( player->spectating && player->spectator != i && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } + + msg.InitWrite( buffer, sizeof( buffer ) ); + spectated->WritePlayerStateToSnapshot( msg ); + ss.S_AddObject( SNAP_PLAYERSTATE + i, ~0U, msg, "Player State" ); + + int sourceAreas[ idEntity::MAX_PVS_AREAS ]; + int numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandles[i] = pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + if ( portalSkyPVS.i >= 0 ) { + pvsHandle_t tempPVS = pvs.MergeCurrentPVS( pvsHandles[i], portalSkyPVS ); + pvs.FreeCurrentPVS( pvsHandles[i] ); + pvsHandles[i] = tempPVS; + } + + // Write the last usercmd processed by the server so that clients know + // when to stop predicting. + msg.BeginWriting(); + msg.WriteLong( usercmdLastClientMilliseconds[i] ); + ss.S_AddObject( SNAP_LAST_CLIENT_FRAME + i, ~0U, msg, "Last client frame" ); + } + + if ( portalSkyPVS.i >= 0 ) { + pvs.FreeCurrentPVS( portalSkyPVS ); + } + + // Add all entities to the snapshot + for ( idEntity * ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetSkipReplication() ) { + continue; + } + + msg.InitWrite( buffer, sizeof( buffer ) ); + msg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + msg.WriteBits( ent->GetType()->typeNum, idClass::GetTypeNumBits() ); + msg.WriteBits( ServerRemapDecl( -1, DECL_ENTITYDEF, ent->entityDefNumber ), entityDefBits ); + + msg.WriteBits( ent->GetPredictedKey(), 32 ); + + if ( ent->fl.networkSync ) { + // write the class specific data to the snapshot + ent->WriteToSnapshot( msg ); + } + + ss.S_AddObject( SNAP_ENTITIES + ent->entityNumber, ~0U, msg, ent->GetName() ); + } + + // Free PVS handles for all the players + for ( int i = 0; i < MAX_PLAYERS; i++ ) { + if ( pvsHandles[i].i < 0 ) { + continue; + } + pvs.FreeCurrentPVS( pvsHandles[i] ); + } +} + +/* +================ +idGameLocal::NetworkEventWarning +================ +*/ +void idGameLocal::NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) { + char buf[1024]; + int length = 0; + va_list argptr; + + int entityNum = event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + int id = event->spawnId >> GENTITYNUM_BITS; + + length += idStr::snPrintf( buf+length, sizeof(buf)-1-length, "event %d for entity %d %d: ", event->event, entityNum, id ); + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf+length, sizeof(buf)-1-length, fmt, argptr ); + va_end( argptr ); + idStr::Append( buf, sizeof(buf), "\n" ); + + common->DWarning( buf ); +} + +/* +================ +idGameLocal::ServerProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ServerProcessEntityNetworkEventQueue() { + while ( eventQueue.Start() ) { + entityNetEvent_t * event = eventQueue.Start(); + + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if( !entPtr.SetSpawnId( event->spawnId ) ) { + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } else { + idEntity * ent = entPtr.GetEntity(); + assert( ent ); + + idBitMsg eventMsg; + eventMsg.InitRead( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ServerReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + verify( freedEvent == event ); + eventQueue.Free( event ); + } +} + +/* +================ +idGameLocal::ProcessReliableMessage +================ +*/ +void idGameLocal::ProcessReliableMessage( int clientNum, int type, const idBitMsg &msg ) { + if ( session->GetActingGameStateLobbyBase().IsPeer() ) { + ClientProcessReliableMessage( type, msg ); + } else { + ServerProcessReliableMessage( clientNum, type, msg ); + } +} + +/* +================ +idGameLocal::ServerProcessReliableMessage +================ +*/ +void idGameLocal::ServerProcessReliableMessage( int clientNum, int type, const idBitMsg &msg ) { + if ( clientNum < 0 ) { + return; + } + switch( type ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + + mpGame.ProcessChatMessage( clientNum, type == GAME_RELIABLE_MESSAGE_TCHAT, name, text, NULL ); + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + int index = msg.ReadLong(); + bool team = msg.ReadBits( 1 ) != 0; + mpGame.ProcessVoiceChat( clientNum, team, index ); + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + mpGame.DropWeapon( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT: { + // allocate new event + entityNetEvent_t * event = eventQueue.Alloc(); + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_DROP ); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + case GAME_RELIABLE_MESSAGE_SPECTATE: { + bool spec = msg.ReadBool(); + idPlayer * player = GetClientByNum( clientNum ); + if ( serverInfo.GetBool( "si_spectators" ) ) { + // never let spectators go back to game while sudden death is on + if ( mpGame.GetGameState() == idMultiplayerGame::SUDDENDEATH && !spec && player->wantSpectate ) { + // Don't allow the change + } else { + if ( player->wantSpectate && !spec ) { + player->forceRespawn = true; + } + player->wantSpectate = spec; + } + } else { + // If the server turned off si_spectators while a player is spectating, then any spectate message forces the player out of spectate mode + if ( player->wantSpectate ) { + player->forceRespawn = true; + } + player->wantSpectate = false; + } + break; + } + case GAME_RELIABLE_MESSAGE_CLIENT_HITSCAN_HIT: { + const int attackerNum = msg.ReadShort(); + const int victimNum = msg.ReadShort(); + idVec3 dir; + msg.ReadVectorFloat( dir ); + const int damageDefIndex = msg.ReadLong(); + const float damageScale = msg.ReadFloat(); + const int location = msg.ReadLong(); + + if ( gameLocal.entities[victimNum] == NULL ) { + break; + } + + if ( gameLocal.entities[attackerNum] == NULL ) { + break; + } + + idPlayer & victim = static_cast< idPlayer & >( *gameLocal.entities[victimNum] ); + idPlayer & attacker = static_cast< idPlayer & >( *gameLocal.entities[attackerNum] ); + + if ( victim.GetPhysics() == NULL ) { + break; + } + + if ( attacker.weapon.GetEntity() == NULL ) { + break; + } + + if ( location == INVALID_JOINT ) { + break; + } + + // Line of sight check. As a basic precaution against cheating, + // the server performs a ray intersection from the client's position + // to the joint he hit on the target. + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + attacker.weapon.GetEntity()->GetProjectileLaunchOriginAndAxis( muzzleOrigin, muzzleAxis ); + + idVec3 targetLocation = victim.GetRenderEntity()->origin + victim.GetRenderEntity()->joints[location].ToVec3() * victim.GetRenderEntity()->axis; + + trace_t tr; + gameLocal.clip.Translation( tr, muzzleOrigin, targetLocation, NULL, mat3_identity, MASK_SHOT_RENDERMODEL, &attacker ); + + idEntity * hitEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( hitEnt != &victim ) { + break; + } + const idDeclEntityDef *damageDef = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex, false ) ); + + if ( damageDef != NULL ) { + victim.Damage( NULL, gameLocal.entities[attackerNum], dir, damageDef->GetName(), damageScale, location ); + } + break; + } + default: { + Warning( "Unknown reliable message (%d) from client %d", type, clientNum ); + break; + } + } +} + +/* +================ +idGameLocal::ClientReadSnapshot +================ +*/ +void idGameLocal::ClientReadSnapshot( const idSnapShot & ss ) { + if ( GetLocalClientNum() < 0 ) { + return; + } + + // if prediction is off, enable local client smoothing + //localPlayer->SetSelfSmooth( dupeUsercmds > 2 ); + + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time ); + + // clear any debug polygons from a previous frame + gameRenderWorld->DebugClearPolygons( time ); + + SelectTimeGroup( false ); + + // so that StartSound/StopSound doesn't risk skipping + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + // read all entities from the snapshot + for ( int o = 0; o < ss.NumObjects(); o++ ) { + idBitMsg msg; + int snapObjectNum = ss.GetObjectMsgByIndex( o, msg ); + if ( snapObjectNum < 0 ) { + assert( false ); + continue; + } + if ( snapObjectNum == SNAP_GAMESTATE ) { + mpGame.ReadFromSnapshot( msg ); + continue; + } + if ( snapObjectNum == SNAP_SHADERPARMS ) { + for ( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + globalShaderParms[i] = msg.ReadFloat(); + } + continue; + } + if ( snapObjectNum == SNAP_PORTALS ) { + // update portals for opened doors + int numPortals = msg.ReadLong(); + assert( numPortals == gameRenderWorld->NumPortals() ); + for ( int i = 0; i < numPortals; i++ ) { + gameRenderWorld->SetPortalState( (qhandle_t) (i+1), msg.ReadBits( NUM_RENDER_PORTAL_BITS ) ); + } + continue; + } + if ( snapObjectNum >= SNAP_PLAYERSTATE && snapObjectNum < SNAP_PLAYERSTATE_END ) { + int playerNumber = snapObjectNum - SNAP_PLAYERSTATE; + idPlayer * otherPlayer = static_cast< idPlayer * >( entities[ playerNumber ] ); + + // Don't process Player Snapshots that are disconnected. + const int lobbyIndex = session->GetActingGameStateLobbyBase().GetLobbyUserIndexFromLobbyUserID( lobbyUserIDs[ playerNumber ] ); + if( lobbyIndex < 0 || session->GetActingGameStateLobbyBase().IsLobbyUserConnected( lobbyIndex ) == false ) { + continue; + } + + if ( otherPlayer != NULL ) { + otherPlayer->ReadPlayerStateFromSnapshot( msg ); + if ( otherPlayer != entities[ GetLocalClientNum() ] ) { // This happens when we spectate another player + idWeapon * weap = otherPlayer->weapon.GetEntity(); + if ( weap && ( weap->GetRenderEntity()->bounds[0] == weap->GetRenderEntity()->bounds[1] ) ) { + // update the weapon's viewmodel bounds so that the model doesn't flicker in the spectator's view + weap->GetAnimator()->GetBounds( gameLocal.time, weap->GetRenderEntity()->bounds ); + weap->UpdateVisuals(); + } + } + } + continue; + } + if ( snapObjectNum >= SNAP_LAST_CLIENT_FRAME && snapObjectNum < SNAP_LAST_CLIENT_FRAME_END ) { + int playerNumber = snapObjectNum - SNAP_LAST_CLIENT_FRAME; + + // Don't process Player Snapshots that are disconnected. + const int lobbyIndex = session->GetActingGameStateLobbyBase().GetLobbyUserIndexFromLobbyUserID( lobbyUserIDs[ playerNumber ] ); + if( lobbyIndex < 0 || session->GetActingGameStateLobbyBase().IsLobbyUserConnected( lobbyIndex ) == false ) { + continue; + } + + usercmdLastClientMilliseconds[playerNumber] = msg.ReadLong(); + continue; + } + if ( snapObjectNum < SNAP_ENTITIES || snapObjectNum >= SNAP_ENTITIES_END ) { + continue; + } + + int entityNumber = snapObjectNum - SNAP_ENTITIES; + + if ( msg.GetSize() == 0 ) { + delete entities[entityNumber]; + continue; + } + + bool debug = false; + + int spawnId = msg.ReadBits( 32 - GENTITYNUM_BITS ); + int typeNum = msg.ReadBits( idClass::GetTypeNumBits() ); + int entityDefNumber = ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( entityDefBits ) ); + const int predictedKey = msg.ReadBits( 32 ); + + idTypeInfo * typeInfo = idClass::GetType( typeNum ); + if ( !typeInfo ) { + idLib::Error( "Unknown type number %d for entity %d with class number %d", typeNum, entityNumber, entityDefNumber ); + } + + // If there is no entity on this client, but the server's entity matches a predictionKey, move the client's + // predicted entity to the normal, replicated area in the entity list. + if ( entities[entityNumber] == NULL ) { + if ( predictedKey != idEntity::INVALID_PREDICTION_KEY ) { + idLib::PrintfIf( debug, "Looking for predicted key %d.\n", predictedKey ); + idEntity * predictedEntity = FindPredictedEntity( predictedKey, typeInfo ); + + if ( predictedEntity != NULL ) { + // This presentable better be in the proper place in the list or bad things will happen if we move this presentable around + assert( predictedEntity->GetEntityNumber() >= ENTITYNUM_FIRST_NON_REPLICATED ); + continue; +#if 0 + idProjectile * predictedProjectile = idProjectile::CastTo( predictedEntity ); + if ( predictedProjectile != NULL ) { + for ( int i = 0; i < MAX_PLAYERS; i++ ) { + if ( entities[i] == NULL ) { + continue; + } + idPlayer * player = idPlayer::CastTo( entities[i] ); + if ( player != NULL ) { + if ( player->GetUniqueProjectile() == predictedProjectile ) { + // Set new spawn id + player->TrackUniqueProjectile( predictedProjectile ); + } + } + } + } + + idLib::PrintfIf( debug, "Found predicted EntNum old:%i new:%i spawnID:%i\n", predictedEntity->GetEntityNumber(), entityNumber, spawnId >> GENTITYNUM_BITS ); + + // move the entity + RemoveEntityFromHash( predictedEntity->name.c_str(), predictedEntity ); + UnregisterEntity( predictedEntity ); + assert( entities[predictedEntity->GetEntityNumber()] == NULL ); + predictedEntity->spawnArgs.SetInt( "spawn_entnum", entityNumber ); + RegisterEntity( predictedEntity, spawnId, predictedEntity->spawnArgs ); + predictedEntity->SetName( "" ); + + // now mark us as no longer predicted + predictedEntity->BecomeReplicated(); +#endif + } + //TODO make this work with non-client preditced entities + /* else { + idLib::Warning( "Could not find predicted entity - key: %d. EntityIndex: %d", predictedKey, entityNum ); + } */ + } + } + + idEntity * ent = entities[entityNumber]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->GetType()->typeNum != typeNum || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ entityNumber ] ) { + delete ent; + + spawnCount = spawnId; + + if ( entityNumber < MAX_CLIENTS ) { + commonLocal.GetUCmdMgr().ResetPlayer( entityNumber ); + SpawnPlayer( entityNumber ); + ent = entities[ entityNumber ]; + ent->FreeModelDef(); + } else { + idDict args; + args.SetInt( "spawn_entnum", entityNumber ); + args.Set( "name", va( "entity%d", entityNumber ) ); + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + const char * classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); + args.Set( "classname", classname ); + if ( !SpawnEntityDef( args, &ent ) || !entities[entityNumber] || entities[entityNumber]->GetType()->typeNum != typeNum ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", classname, typeInfo->classname ); + } + } else { + ent = SpawnEntityType( *typeInfo, &args, true ); + if ( !entities[entityNumber] || entities[entityNumber]->GetType()->typeNum != typeNum ) { + Error( "Failed to spawn entity of type '%s'", typeInfo->classname ); + } + } + if ( ent != NULL ) { + // Fixme: for now, force all think flags on. We'll need to figure out how we want dormancy to work on clients + // (but for now since clientThink is so light weight, this is ok) + ent->BecomeActive( TH_ANIMATE ); + ent->BecomeActive( TH_THINK ); + ent->BecomeActive( TH_PHYSICS ); + } + if ( entityNumber < MAX_CLIENTS && entityNumber >= numClients ) { + numClients = entityNumber + 1; + } + } + } + + if ( ss.ObjectIsStaleByIndex( o ) ) { + if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < mapSpawnCount && !ent->spawnArgs.GetBool("net_dynamic", "0")) { //_D3XP + // server says it's not in PVS + // if that happens on map entities, most likely something is wrong + // I can see that moving pieces along several PVS could be a legit situation though + // this is a band aid, which means something is not done right elsewhere + common->DWarning( "map entity 0x%x (%s) is stale", ent->entityNumber, ent->name.c_str() ); + } else { + ent->snapshotStale = true; + + ent->FreeModelDef(); + // possible fix for left over lights on CTF flag + ent->FreeLightDef(); + ent->UpdateVisuals(); + ent->GetPhysics()->UnlinkClip(); + } + } else { + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + int snapshotChanged = ss.ObjectChangedCountByIndex( o ); + msg.SetHasChanged( ent->snapshotChanged != snapshotChanged ); + ent->snapshotChanged = snapshotChanged; + + ent->FlagNewSnapshot(); + + // read the class specific data from the snapshot + if ( msg.GetRemainingReadBits() > 0 ) { + ent->ReadFromSnapshot_Ex( msg ); + ent->snapshotBits = msg.GetSize(); + } + + // Set after ReadFromSnapshot so we can detect coming unstale + ent->snapshotStale = false; + } + } + + // process entity events + ClientProcessEntityNetworkEventQueue(); +} + +/* +================ +idGameLocal::ClientProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ClientProcessEntityNetworkEventQueue() { + while( eventQueue.Start() ) { + entityNetEvent_t * event = eventQueue.Start(); + + // only process forward, in order + if ( event->time > this->serverTime ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if( !entPtr.SetSpawnId( event->spawnId ) ) { + if( !gameLocal.entities[ event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + // if new entity exists in this position, silently ignore + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } + } else { + idEntity * ent = entPtr.GetEntity(); + assert( ent ); + + idBitMsg eventMsg; + eventMsg.InitRead( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + + verify( eventQueue.Dequeue() == event ); + eventQueue.Free( event ); + } +} + +/* +================ +idGameLocal::ClientProcessReliableMessage +================ +*/ +void idGameLocal::ClientProcessReliableMessage( int type, const idBitMsg &msg ) { + switch( type ) { + case GAME_RELIABLE_MESSAGE_SYNCEDCVARS: { + idDict syncedCvars; + msg.ReadDeltaDict( syncedCvars, NULL ); + + idLib::Printf( "Got networkSync cvars:\n" ); + syncedCvars.Print(); + + cvarSystem->ResetFlaggedVariables( CVAR_NETWORKSYNC ); + cvarSystem->SetCVarsFromDict( syncedCvars ); + break; + } + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { // (client should never get a TCHAT though) + char name[128]; + char text[128]; + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + mpGame.AddChatLine( "%s^0: %s\n", name, text ); + break; + } + case GAME_RELIABLE_MESSAGE_SOUND_EVENT: { + snd_evt_t snd_evt = (snd_evt_t)msg.ReadByte(); + mpGame.PlayGlobalSound( -1, snd_evt ); + break; + } + case GAME_RELIABLE_MESSAGE_SOUND_INDEX: { + int index = gameLocal.ClientRemapDecl( DECL_SOUND, msg.ReadLong() ); + if ( index >= 0 && index < declManager->GetNumDecls( DECL_SOUND ) ) { + const idSoundShader *shader = declManager->SoundByIndex( index ); + mpGame.PlayGlobalSound( -1, SND_COUNT, shader->GetName() ); + } + break; + } + case GAME_RELIABLE_MESSAGE_DB: { + idMultiplayerGame::msg_evt_t msg_evt = (idMultiplayerGame::msg_evt_t)msg.ReadByte(); + int parm1, parm2; + parm1 = msg.ReadByte( ); + parm2 = msg.ReadByte( ); + mpGame.PrintMessageEvent( msg_evt, parm1, parm2 ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT: { + // allocate new event + entityNetEvent_t * event = eventQueue.Alloc(); + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + case GAME_RELIABLE_MESSAGE_RESTART: { + MapRestart(); + break; + } + case GAME_RELIABLE_MESSAGE_TOURNEYLINE: { + int line = msg.ReadByte( ); + idPlayer * p = static_cast< idPlayer * >( entities[ GetLocalClientNum() ] ); + if ( !p ) { + break; + } + p->tourneyLine = line; + break; + } + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + mpGame.ClientReadStartState( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_WARMUPTIME: { + mpGame.ClientReadWarmupTime( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_LOBBY_COUNTDOWN: { + int timeRemaining = msg.ReadLong(); + Shell_UpdateClientCountdown( timeRemaining ); + break; + } + case GAME_RELIABLE_MESSAGE_RESPAWN_AVAILABLE: { + idPlayer * p = static_cast< idPlayer * >( entities[ GetLocalClientNum() ] ); + if ( p ) { + p->ShowRespawnHudMessage(); + } + break; + } + case GAME_RELIABLE_MESSAGE_MATCH_STARTED_TIME: { + mpGame.ClientReadMatchStartedTime( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_ACHIEVEMENT_UNLOCK: { + mpGame.ClientReadAchievementUnlock( msg ); + break; + } + default: { + Error( "Unknown reliable message (%d) from host", type ); + break; + } + } +} + +/* +================ +idGameLocal::ClientRunFrame +================ +*/ +void idGameLocal::ClientRunFrame( idUserCmdMgr & cmdMgr, bool lastPredictFrame, gameReturn_t & ret ) { + idEntity *ent; + + // update the game time + previousTime = FRAME_TO_MSEC( framenum ); + framenum++; + time = FRAME_TO_MSEC( framenum ); + + idPlayer * player = static_cast( entities[GetLocalClientNum()] ); + if ( !player ) { + + // service any pending events + idEvent::ServiceEvents(); + + return; + } + + // check for local client lag + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + if ( lobby.GetPeerTimeSinceLastPacket( lobby.PeerIndexForHost() ) >= net_clientMaxPrediction.GetInteger() ) { + player->isLagged = true; + } else { + player->isLagged = false; + } + + // update the real client time and the new frame flag + if ( time > realClientTime ) { + realClientTime = time; + isNewFrame = true; + } else { + isNewFrame = false; + } + + slow.Set( time, previousTime, realClientTime ); + fast.Set( time, previousTime, realClientTime ); + + // run prediction on all active entities + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + ent->thinkFlags |= TH_PHYSICS; + + if ( ent->entityNumber != GetLocalClientNum() ) { + ent->ClientThink( netInterpolationInfo.serverGameMs, netInterpolationInfo.pct, true ); + } else { + RunAllUserCmdsForPlayer( cmdMgr, ent->entityNumber ); + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // show any debug info for this frame + if ( isNewFrame ) { + RunDebugInfo(); + D_DrawDebugLines(); + } + + BuildReturnValue( ret ); +} + +/* +=============== +idGameLocal::Tokenize +=============== +*/ +void idGameLocal::Tokenize( idStrList &out, const char *in ) { + char buf[ MAX_STRING_CHARS ]; + char *token, *next; + + idStr::Copynz( buf, in, MAX_STRING_CHARS ); + token = buf; + next = strchr( token, ';' ); + while ( token ) { + if ( next ) { + *next = '\0'; + } + idStr::ToLower( token ); + out.Append( token ); + if ( next ) { + token = next + 1; + next = strchr( token, ';' ); + } else { + token = NULL; + } + } +} + +/* +======================== +idGameLocal::FindPredictedEntity +======================== +*/ +idEntity * idGameLocal::FindPredictedEntity( uint32 predictedKey, idTypeInfo * type ) { + for ( idEntity * predictedEntity = activeEntities.Next(); predictedEntity != NULL; predictedEntity = predictedEntity->activeNode.Next() ) { + if ( !verify( predictedEntity != NULL ) ) { + continue; + } + if ( !predictedEntity->IsReplicated() && predictedEntity->GetPredictedKey() == predictedKey ) { + if ( predictedEntity->GetType() != type ) { + idLib::Warning("Mismatched presentable type. Predicted: %s Actual: %s", predictedEntity->GetType()->classname, type->classname ); + } + return predictedEntity; + } + } + return NULL; +} + +/* +======================== +idGameLocal::GeneratePredictionKey +======================== +*/ +uint32 idGameLocal::GeneratePredictionKey( idWeapon * weapon, idPlayer * playerAttacker, int overrideKey ) { + if ( overrideKey != -1 ) { + uint32 predictedKey = overrideKey; + int peerIndex = -1; + + if ( common->IsServer() ) { + peerIndex = session->GetActingGameStateLobbyBase().PeerIndexFromLobbyUser( lobbyUserIDs[ playerAttacker->entityNumber ] ); + } else { + peerIndex = session->GetActingGameStateLobbyBase().PeerIndexOnHost(); + } + + predictedKey |= ( peerIndex << 28 ); + + return predictedKey; + } + + uint32 predictedKey = idEntity::INVALID_PREDICTION_KEY; + int peerIndex = -1; + + // Get key - fireCount or throwCount + //if ( weapon != NULL ) { + if ( common->IsClient() ) { + predictedKey = playerAttacker->GetClientFireCount(); + } else { + predictedKey = playerAttacker->usercmd.fireCount; + } + //} else { + // predictedKey = ( playerAttacker->GetThrowCount() ); + //} + + // Get peer index + if ( common->IsServer() ) { + peerIndex = session->GetActingGameStateLobbyBase().PeerIndexFromLobbyUser( lobbyUserIDs[ playerAttacker->entityNumber ] ); + } else { + peerIndex = session->GetActingGameStateLobbyBase().PeerIndexOnHost(); + } + + if ( cg_predictedSpawn_debug.GetBool() ) { + idLib::Printf("GeneratePredictionKey. predictedKey: %d peedIndex: %d\n", predictedKey, peerIndex ); + } + + predictedKey |= ( peerIndex << 28 ); + return predictedKey; +} + +/* +=============== +idEventQueue::Alloc +=============== +*/ +entityNetEvent_t* idEventQueue::Alloc() { + entityNetEvent_t* event = eventAllocator.Alloc(); + event->prev = NULL; + event->next = NULL; + return event; +} + +/* +=============== +idEventQueue::Free +=============== +*/ +void idEventQueue::Free( entityNetEvent_t *event ) { + // should only be called on an unlinked event! + assert( !event->next && !event->prev ); + eventAllocator.Free( event ); +} + +/* +=============== +idEventQueue::Shutdown +=============== +*/ +void idEventQueue::Shutdown() { + eventAllocator.Shutdown(); + this->Init(); +} + +/* +=============== +idEventQueue::Init +=============== +*/ +void idEventQueue::Init() { + start = NULL; + end = NULL; +} + +/* +=============== +idEventQueue::Dequeue +=============== +*/ +entityNetEvent_t* idEventQueue::Dequeue() { + entityNetEvent_t* event = start; + if ( !event ) { + return NULL; + } + + start = start->next; + + if ( !start ) { + end = NULL; + } else { + start->prev = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::RemoveLast +=============== +*/ +entityNetEvent_t* idEventQueue::RemoveLast() { + entityNetEvent_t *event = end; + if ( !event ) { + return NULL; + } + + end = event->prev; + + if ( !end ) { + start = NULL; + } else { + end->next = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::Enqueue +=============== +*/ +void idEventQueue::Enqueue( entityNetEvent_t *event, outOfOrderBehaviour_t behaviour ) { + if ( behaviour == OUTOFORDER_DROP ) { + // go backwards through the queue and determine if there are + // any out-of-order events + while ( end && end->time > event->time ) { + entityNetEvent_t *outOfOrder = RemoveLast(); + common->DPrintf( "WARNING: new event with id %d ( time %d ) caused removal of event with id %d ( time %d ), game time = %d.\n", event->event, event->time, outOfOrder->event, outOfOrder->time, gameLocal.time ); + Free( outOfOrder ); + } + } else if ( behaviour == OUTOFORDER_SORT && end ) { + // NOT TESTED -- sorting out of order packets hasn't been + // tested yet... wasn't strictly necessary for + // the patch fix. + entityNetEvent_t *cur = end; + // iterate until we find a time < the new event's + while ( cur && cur->time > event->time ) { + cur = cur->prev; + } + if ( !cur ) { + // add to start + event->next = start; + event->prev = NULL; + start = event; + } else { + // insert + event->prev = cur; + event->next = cur->next; + cur->next = event; + } + return; + } + + // add the new event + event->next = NULL; + event->prev = NULL; + + if ( end ) { + end->next = event; + event->prev = end; + } else { + start = event; + } + end = event; +} diff --git a/neo/d3xp/Grabber.cpp b/neo/d3xp/Grabber.cpp new file mode 100644 index 00000000..60a7526b --- /dev/null +++ b/neo/d3xp/Grabber.cpp @@ -0,0 +1,739 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + + +#include "Game_local.h" +#include "Misc.h" + +#define MAX_DRAG_TRACE_DISTANCE 384.0f +#define TRACE_BOUNDS_SIZE 3.f +#define HOLD_DISTANCE 72.f +#define FIRING_DELAY 1000.0f +#define DRAG_FAIL_LEN 64.f +#define THROW_SCALE 1000 +#define MAX_PICKUP_VELOCITY 1500 * 1500 +#define MAX_PICKUP_SIZE 96 + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idGrabber ) +END_CLASS + +/* +============== +idGrabber::idGrabber +============== +*/ +idGrabber::idGrabber() { + dragEnt = NULL; + owner = NULL; + beam = NULL; + beamTarget = NULL; + oldImpulseSequence = 0; + shakeForceFlip = false; + holdingAF = false; + endTime = 0; + lastFiredTime = -FIRING_DELAY; + dragFailTime = 0; + startDragTime = 0; + warpId = -1; + dragTraceDist = MAX_DRAG_TRACE_DISTANCE; +} + +/* +============== +idGrabber::~idGrabber +============== +*/ +idGrabber::~idGrabber() { + StopDrag( true ); + if ( beam ) { + delete beam; + } + if ( beamTarget ) { + delete beamTarget; + } +} + +/* +============== +idGrabber::Save +============== +*/ +void idGrabber::Save( idSaveGame *savefile ) const { + + dragEnt.Save( savefile ); + savefile->WriteStaticObject( drag ); + + savefile->WriteVec3( saveGravity ); + savefile->WriteInt( id ); + + savefile->WriteVec3( localPlayerPoint ); + + owner.Save( savefile ); + + savefile->WriteBool( holdingAF ); + savefile->WriteBool( shakeForceFlip ); + + savefile->WriteInt( endTime ); + savefile->WriteInt( lastFiredTime ); + savefile->WriteInt( dragFailTime ); + savefile->WriteInt( startDragTime ); + savefile->WriteFloat( dragTraceDist ); + savefile->WriteInt( savedContents ); + savefile->WriteInt( savedClipmask ); + + savefile->WriteObject( beam ); + savefile->WriteObject( beamTarget ); + + savefile->WriteInt( warpId ); +} + +/* +============== +idGrabber::Restore +============== +*/ +void idGrabber::Restore( idRestoreGame *savefile ) { + //Spawn the beams + Initialize(); + + dragEnt.Restore( savefile ); + savefile->ReadStaticObject( drag ); + + savefile->ReadVec3( saveGravity ); + savefile->ReadInt( id ); + + // Restore the drag force's physics object + if ( dragEnt.IsValid() ) { + drag.SetPhysics( dragEnt.GetEntity()->GetPhysics(), id, dragEnt.GetEntity()->GetPhysics()->GetOrigin() ); + } + + savefile->ReadVec3( localPlayerPoint ); + + owner.Restore( savefile ); + + savefile->ReadBool( holdingAF ); + savefile->ReadBool( shakeForceFlip ); + + savefile->ReadInt( endTime ); + savefile->ReadInt( lastFiredTime ); + savefile->ReadInt( dragFailTime ); + savefile->ReadInt( startDragTime ); + savefile->ReadFloat( dragTraceDist ); + savefile->ReadInt( savedContents ); + savefile->ReadInt( savedClipmask ); + + savefile->ReadObject( reinterpret_cast(beam) ); + savefile->ReadObject( reinterpret_cast(beamTarget) ); + + savefile->ReadInt( warpId ); +} + +/* +============== +idGrabber::Initialize +============== +*/ +void idGrabber::Initialize() { + if ( !common->IsMultiplayer() ) { + idDict args; + + if ( !beamTarget ) { + args.SetVector( "origin", vec3_origin ); + args.SetBool( "start_off", true ); + beamTarget = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args ); + } + + if ( !beam ) { + args.Clear(); + args.Set( "target", beamTarget->name.c_str() ); + args.SetVector( "origin", vec3_origin ); + args.SetBool( "start_off", true ); + args.Set( "width", "6" ); + args.Set( "skin", "textures/smf/flareSizeable" ); + args.Set( "_color", "0.0235 0.843 0.969 0.2" ); + beam = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args ); + beam->SetShaderParm( 6, 1.0f ); + } + + endTime = 0; + dragTraceDist = MAX_DRAG_TRACE_DISTANCE; + } + else { + beam = NULL; + beamTarget = NULL; + endTime = 0; + dragTraceDist = MAX_DRAG_TRACE_DISTANCE; + }; +} + +/* +============== +idGrabber::SetDragDistance +============== +*/ +void idGrabber::SetDragDistance( float dist ) { + dragTraceDist = dist; +} + +/* +============== +idGrabber::StartDrag +============== +*/ +void idGrabber::StartDrag( idEntity *grabEnt, int id ) { + int clipModelId = id; + idPlayer *thePlayer = owner.GetEntity(); + + holdingAF = false; + dragFailTime = gameLocal.slow.time; + startDragTime = gameLocal.slow.time; + + oldImpulseSequence = thePlayer->usercmd.impulseSequence; + + // set grabbed state for networking + grabEnt->SetGrabbedState( true ); + + // This is the new object to drag around + dragEnt = grabEnt; + + // Show the beams! + UpdateBeams(); + if ( beam ) { + beam->Show(); + } + if ( beamTarget ) { + beamTarget->Show(); + } + + // Move the object to the fast group (helltime) + grabEnt->timeGroup = TIME_GROUP2; + + // Handle specific class types + if ( grabEnt->IsType( idProjectile::Type ) ) { + idProjectile* p = (idProjectile*)grabEnt; + + p->CatchProjectile( thePlayer, "_catch" ); + + // Make the projectile non-solid to other projectiles/enemies (special hack for helltime hunter) + if ( !idStr::Cmp( grabEnt->GetEntityDefName(), "projectile_helltime_killer" ) ) { + savedContents = CONTENTS_PROJECTILE; + savedClipmask = MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE; + } else { + savedContents = grabEnt->GetPhysics()->GetContents(); + savedClipmask = grabEnt->GetPhysics()->GetClipMask(); + } + grabEnt->GetPhysics()->SetContents( 0 ); + grabEnt->GetPhysics()->SetClipMask( CONTENTS_SOLID|CONTENTS_BODY ); + + } else if ( grabEnt->IsType( idExplodingBarrel::Type ) ) { + idExplodingBarrel *ebarrel = static_cast(grabEnt); + + ebarrel->StartBurning(); + + } else if ( grabEnt->IsType( idAFEntity_Gibbable::Type ) ) { + holdingAF = true; + clipModelId = 0; + + if ( grabbableAI( grabEnt->spawnArgs.GetString( "classname" ) ) ) { + idAI *aiEnt = static_cast(grabEnt); + + aiEnt->StartRagdoll(); + } + } else if ( grabEnt->IsType( idMoveableItem::Type ) ) { + grabEnt->PostEventMS( &EV_Touch, 250, thePlayer, NULL ); + } + + // Get the current physics object to manipulate + idPhysics *phys = grabEnt->GetPhysics(); + + // Turn off gravity on object + saveGravity = phys->GetGravity(); + phys->SetGravity( vec3_origin ); + + // hold it directly in front of player + localPlayerPoint = ( thePlayer->firstPersonViewAxis[0] * HOLD_DISTANCE ) * thePlayer->firstPersonViewAxis.Transpose(); + + // Set the ending time for the hold + endTime = gameLocal.time + g_grabberHoldSeconds.GetFloat() * 1000; + + // Start up the Force_Drag to bring it in + drag.Init( g_grabberDamping.GetFloat() ); + drag.SetPhysics( phys, clipModelId, thePlayer->firstPersonViewOrigin + localPlayerPoint * thePlayer->firstPersonViewAxis); + + // start the screen warp + warpId = thePlayer->playerView.AddWarp( phys->GetOrigin(), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 160, 2000 ); +} + +/* +============== +idGrabber::StopDrag +============== +*/ +void idGrabber::StopDrag( bool dropOnly ) { + idPlayer *thePlayer = owner.GetEntity(); + + if ( beam ) { + beam->Hide(); + } + if ( beamTarget ) { + beamTarget->Hide(); + } + + if ( dragEnt.IsValid() ) { + idEntity *ent = dragEnt.GetEntity(); + + // set grabbed state for networking + ent->SetGrabbedState( false ); + + // If a cinematic has started, allow dropped object to think in cinematics + if ( gameLocal.inCinematic ) { + ent->cinematic = true; + } + + // Restore Gravity + ent->GetPhysics()->SetGravity( saveGravity ); + + // Move the object back to the slow group (helltime) + ent->timeGroup = TIME_GROUP1; + + if ( holdingAF ) { + idAFEntity_Gibbable *af = static_cast(ent); + idPhysics_AF *af_Phys = static_cast(af->GetPhysics()); + + if ( grabbableAI( ent->spawnArgs.GetString( "classname" ) ) ) { + idAI *aiEnt = static_cast(ent); + + aiEnt->Damage( thePlayer, thePlayer, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + } + + af->SetThrown( !dropOnly ); + + // Reset timers so that it isn't forcibly put to rest in mid-air + af_Phys->PutToRest(); + af_Phys->Activate(); + + af_Phys->SetTimeScaleRamp( MS2SEC(gameLocal.slow.time) - 1.5f, MS2SEC(gameLocal.slow.time) + 1.0f ); + } + + // If the object isn't near its goal, just drop it in place. + if ( !ent->IsType( idProjectile::Type ) && ( dropOnly || drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) ) { + ent->GetPhysics()->SetLinearVelocity( vec3_origin ); + thePlayer->StartSoundShader( declManager->FindSound( "grabber_maindrop" ), SND_CHANNEL_WEAPON, 0, false, NULL ); + + if ( ent->IsType( idExplodingBarrel::Type ) ) { + idExplodingBarrel *ebarrel = static_cast(ent); + + ebarrel->SetStability( true ); + ebarrel->StopBurning(); + } + } else { + // Shoot the object forward + ent->ApplyImpulse( thePlayer, 0, ent->GetPhysics()->GetOrigin(), thePlayer->firstPersonViewAxis[0] * THROW_SCALE * ent->GetPhysics()->GetMass() ); + thePlayer->StartSoundShader( declManager->FindSound( "grabber_release" ), SND_CHANNEL_WEAPON, 0, false, NULL ); + + // Orient projectiles away from the player + if ( ent->IsType( idProjectile::Type ) ) { + idPlayer *player = owner.GetEntity(); + idAngles ang = player->firstPersonViewAxis[0].ToAngles(); + + ang.pitch += 90.f; + ent->GetPhysics()->SetAxis( ang.ToMat3() ); + ent->GetPhysics()->SetAngularVelocity( vec3_origin ); + + // Restore projectile contents + ent->GetPhysics()->SetContents( savedContents ); + ent->GetPhysics()->SetClipMask( savedClipmask ); + + idProjectile *projectile = static_cast< idProjectile* >( ent ); + if ( projectile != NULL ) { + projectile->SetLaunchedFromGrabber( true ); + } + + } else if ( ent->IsType( idMoveable::Type ) ) { + // Turn on damage for this object + idMoveable *obj = static_cast(ent); + obj->EnableDamage( true, 2.5f ); + obj->SetAttacker( thePlayer ); + + if ( ent->IsType( idExplodingBarrel::Type ) ) { + idExplodingBarrel *ebarrel = static_cast(ent); + ebarrel->SetStability( false ); + } + + } else if ( ent->IsType( idMoveableItem::Type ) ) { + ent->GetPhysics()->SetClipMask( MASK_MONSTERSOLID ); + } + } + + // Remove the Force_Drag's control of the entity + drag.RemovePhysics( ent->GetPhysics() ); + } + + if ( warpId != -1 ) { + thePlayer->playerView.FreeWarp( warpId ); + warpId = -1; + } + + lastFiredTime = gameLocal.time; + dragEnt = NULL; + endTime = 0; +} + +/* +============== +idGrabber::Update +============== +*/ +int idGrabber::Update( idPlayer *player, bool hide ) { + trace_t trace; + idEntity *newEnt; + + // pause before allowing refire + if ( lastFiredTime + FIRING_DELAY > gameLocal.time ) { + return 3; + } + + // Dead players release the trigger + if ( hide || player->health <= 0 ) { + StopDrag( true ); + if ( hide ) { + lastFiredTime = gameLocal.time - FIRING_DELAY + 250; + } + return 3; + } + + // Check if object being held has been removed (dead demon, projectile, etc.) + if ( endTime > gameLocal.time ) { + bool abort = !dragEnt.IsValid(); + + if ( !abort && dragEnt.GetEntity()->IsType( idProjectile::Type ) ) { + idProjectile *proj = (idProjectile *)dragEnt.GetEntity(); + + if ( proj->GetProjectileState() >= 3 ) { + abort = true; + } + } + if ( !abort && dragEnt.GetEntity() && dragEnt.GetEntity()->IsHidden() ) { + abort = true; + } + // Not in multiplayer :: Pressing "reload" lets you carefully drop an item + if ( !common->IsMultiplayer() && !abort && ( player->usercmd.impulseSequence != oldImpulseSequence ) && (player->usercmd.impulse == IMPULSE_13) ) { + abort = true; + } + + if ( abort ) { + StopDrag( true ); + return 3; + } + } + + owner = player; + + // if no entity selected for dragging + if ( !dragEnt.GetEntity() ) { + idBounds bounds; + idVec3 end = player->firstPersonViewOrigin + player->firstPersonViewAxis[0] * dragTraceDist; + + bounds.Zero(); + bounds.ExpandSelf( TRACE_BOUNDS_SIZE ); + + gameLocal.clip.TraceBounds( trace, player->firstPersonViewOrigin, end, bounds, MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE|CONTENTS_MOVEABLECLIP, player ); + // If the trace hit something + if ( trace.fraction < 1.0f ) { + newEnt = gameLocal.entities[ trace.c.entityNum ]; + + // if entity is already being grabbed then bypass + if ( common->IsMultiplayer() && newEnt && newEnt->IsGrabbed() ) { + return 0; + } + + // Check if this is a valid entity to hold + if ( newEnt && ( newEnt->IsType( idMoveable::Type ) || + newEnt->IsType( idMoveableItem::Type ) || + newEnt->IsType( idProjectile::Type ) || + newEnt->IsType( idAFEntity_Gibbable::Type ) + ) && + newEnt->noGrab == false && + newEnt->GetPhysics()->GetBounds().GetRadius() < MAX_PICKUP_SIZE && + newEnt->GetPhysics()->GetLinearVelocity().LengthSqr() < MAX_PICKUP_VELOCITY ) { + + bool validAF = true; + + if ( newEnt->IsType( idAFEntity_Gibbable::Type ) ) { + idAFEntity_Gibbable *afEnt = static_cast(newEnt); + + if ( grabbableAI( newEnt->spawnArgs.GetString( "classname" ) ) ) { + // Make sure it's also active + if ( !afEnt->IsActive() ) { + validAF = false; + } + } else if ( !afEnt->IsActiveAF() ) { + validAF = false; + } + } + + if ( validAF && player->usercmd.buttons & BUTTON_ATTACK ) { + // Grab this entity and start dragging it around + StartDrag( newEnt, trace.c.id ); + } else if ( validAF ) { + // A holdable object is ready to be grabbed + return 1; + } + } + } + } + + // check backwards server time in multiplayer + bool allow = true; + + if ( common->IsMultiplayer() ) { + + // if we've marched backwards + if ( gameLocal.slow.time < startDragTime ) { + allow = false; + } + } + + + // if there is an entity selected for dragging + if ( dragEnt.GetEntity() && allow ) { + idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics(); + idVec3 goalPos; + + // If the player lets go of attack, or time is up + if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { + StopDrag( false ); + return 3; + } + if ( gameLocal.time > endTime ) { + StopDrag( true ); + return 3; + } + + // Check if the player is standing on the object + if ( !holdingAF ) { + idBounds playerBounds; + idBounds objectBounds = entPhys->GetAbsBounds(); + idVec3 newPoint = player->GetPhysics()->GetOrigin(); + + // create a bounds at the players feet + playerBounds.Clear(); + playerBounds.AddPoint( newPoint ); + newPoint.z -= 1.f; + playerBounds.AddPoint( newPoint ); + playerBounds.ExpandSelf( 8.f ); + + // If it intersects the object bounds, then drop it + if ( playerBounds.IntersectsBounds( objectBounds ) ) { + StopDrag( true ); + return 3; + } + } + + // Shake the object at the end of the hold + if ( g_grabberEnableShake.GetBool() && !common->IsMultiplayer() ) { + ApplyShake(); + } + + // Set and evaluate drag force + goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis; + + drag.SetGoalPosition( goalPos ); + drag.Evaluate( gameLocal.time ); + + // If an object is flying too fast toward the player, stop it hard + if ( g_grabberHardStop.GetBool() ) { + idPlane theWall; + idVec3 toPlayerVelocity, objectCenter; + float toPlayerSpeed; + + toPlayerVelocity = -player->firstPersonViewAxis[0]; + toPlayerSpeed = entPhys->GetLinearVelocity() * toPlayerVelocity; + + if ( toPlayerSpeed > 64.f ) { + objectCenter = entPhys->GetAbsBounds().GetCenter(); + + theWall.SetNormal( player->firstPersonViewAxis[0] ); + theWall.FitThroughPoint( goalPos ); + + if ( theWall.Side( objectCenter, 0.1f ) == PLANESIDE_BACK ) { + int i, num; + + num = entPhys->GetNumClipModels(); + for ( i=0; iSetLinearVelocity( vec3_origin, i ); + } + } + } + + // Make sure the object isn't spinning too fast + const float MAX_ROTATION_SPEED = 12.f; + + idVec3 angVel = entPhys->GetAngularVelocity(); + float rotationSpeed = angVel.LengthFast(); + + if ( rotationSpeed > MAX_ROTATION_SPEED ) { + angVel.NormalizeFast(); + angVel *= MAX_ROTATION_SPEED; + entPhys->SetAngularVelocity( angVel ); + } + } + + // Orient projectiles away from the player + if ( dragEnt.GetEntity()->IsType( idProjectile::Type ) ) { + idAngles ang = player->firstPersonViewAxis[0].ToAngles(); + ang.pitch += 90.f; + entPhys->SetAxis( ang.ToMat3() ); + } + + // Some kind of effect from gun to object? + UpdateBeams(); + + // If the object is stuck away from its intended position for more than 500ms, let it go. + if ( drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) { + if ( dragFailTime < (gameLocal.slow.time - 500) ) { + StopDrag( true ); + return 3; + } + } else { + dragFailTime = gameLocal.slow.time; + } + + // Currently holding an object + return 2; + } + + // Not holding, nothing to hold + return 0; +} + +/* +====================== +idGrabber::UpdateBeams +====================== +*/ +void idGrabber::UpdateBeams() { + jointHandle_t muzzle_joint; + idVec3 muzzle_origin; + idMat3 muzzle_axis; + renderEntity_t *re; + + if ( !beam ) { + return; + } + + if ( dragEnt.IsValid() ) { + idPlayer *thePlayer = owner.GetEntity(); + + if ( beamTarget ) { + beamTarget->SetOrigin( dragEnt.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter() ); + } + + muzzle_joint = thePlayer->weapon.GetEntity()->GetAnimator()->GetJointHandle( "particle_upper" ); + if ( muzzle_joint != INVALID_JOINT ) { + thePlayer->weapon.GetEntity()->GetJointWorldTransform( muzzle_joint, gameLocal.time, muzzle_origin, muzzle_axis ); + } else { + muzzle_origin = thePlayer->GetPhysics()->GetOrigin(); + } + + beam->SetOrigin( muzzle_origin ); + re = beam->GetRenderEntity(); + re->origin = muzzle_origin; + + beam->UpdateVisuals(); + beam->Present(); + } +} + +/* +============== +idGrabber::ApplyShake +============== +*/ +void idGrabber::ApplyShake() { + float u = 1 - (float)( endTime - gameLocal.time ) / ( g_grabberHoldSeconds.GetFloat() * 1000 ); + + if ( u >= 0.8f ) { + idVec3 point, impulse; + float shakeForceMagnitude = 450.f; + float mass = dragEnt.GetEntity()->GetPhysics()->GetMass(); + + shakeForceFlip = !shakeForceFlip; + + // get point to rotate around + point = dragEnt.GetEntity()->GetPhysics()->GetOrigin(); + point.y += 1; + + // Articulated figures get less violent shake + if ( holdingAF ) { + shakeForceMagnitude = 120.f; + } + + // calc impulse + if ( shakeForceFlip ) { + impulse.Set( 0, 0, shakeForceMagnitude * u * mass ); + } + else { + impulse.Set( 0, 0, -shakeForceMagnitude * u * mass ); + } + + dragEnt.GetEntity()->ApplyImpulse( NULL, 0, point, impulse ); + } +} + +/* +============== +idGrabber::grabbableAI +============== +*/ +bool idGrabber::grabbableAI( const char *aiName ) { + // skip "monster_" + aiName += 8; + + if (!idStr::Cmpn( aiName, "flying_lostsoul", 15 ) || + !idStr::Cmpn( aiName, "demon_trite", 11 ) || + !idStr::Cmp( aiName, "flying_forgotten" ) || + !idStr::Cmp( aiName, "demon_cherub" ) || + !idStr::Cmp( aiName, "demon_tick" )) { + + return true; + } + + return false; +} + diff --git a/neo/d3xp/Grabber.h b/neo/d3xp/Grabber.h new file mode 100644 index 00000000..9c761c25 --- /dev/null +++ b/neo/d3xp/Grabber.h @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Grabber Object - Class to extend idWeapon to include functionality for + manipulating physics objects. + +=============================================================================== +*/ + +class idBeam; + +class idGrabber : public idEntity { +public: + CLASS_PROTOTYPE( idGrabber ); + + idGrabber(); + ~idGrabber(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Initialize(); + void SetDragDistance( float dist ); + int Update( idPlayer *player, bool hide ); + +private: + idEntityPtr dragEnt; // entity being dragged + idForce_Grab drag; + idVec3 saveGravity; + + int id; // id of body being dragged + idVec3 localPlayerPoint; // dragged point in player space + idEntityPtr owner; + int oldImpulseSequence; + bool holdingAF; + bool shakeForceFlip; + int endTime; + int lastFiredTime; + int dragFailTime; + int startDragTime; + float dragTraceDist; + int savedContents; + int savedClipmask; + + idBeam* beam; + idBeam* beamTarget; + + int warpId; + + bool grabbableAI( const char *aiName ); + void StartDrag( idEntity *grabEnt, int id ); + void StopDrag( bool dropOnly ); + void UpdateBeams(); + void ApplyShake(); +}; + diff --git a/neo/d3xp/IK.cpp b/neo/d3xp/IK.cpp new file mode 100644 index 00000000..cdc7c0a7 --- /dev/null +++ b/neo/d3xp/IK.cpp @@ -0,0 +1,1128 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idIK + +=============================================================================== +*/ + +/* +================ +idIK::idIK +================ +*/ +idIK::idIK() { + ik_activate = false; + initialized = false; + self = NULL; + animator = NULL; + modifiedAnim = 0; + modelOffset.Zero(); +} + +/* +================ +idIK::~idIK +================ +*/ +idIK::~idIK() { +} + +/* +================ +idIK::Save +================ +*/ +void idIK::Save( idSaveGame *savefile ) const { + savefile->WriteBool( initialized ); + savefile->WriteBool( ik_activate ); + savefile->WriteObject( self ); + savefile->WriteString( animator != NULL && animator->GetAnim( modifiedAnim ) ? animator->GetAnim( modifiedAnim )->Name() : "" ); + savefile->WriteVec3( modelOffset ); +} + +/* +================ +idIK::Restore +================ +*/ +void idIK::Restore( idRestoreGame *savefile ) { + idStr anim; + + savefile->ReadBool( initialized ); + savefile->ReadBool( ik_activate ); + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( anim ); + savefile->ReadVec3( modelOffset ); + + if ( self ) { + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return; + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + } else { + animator = NULL; + modifiedAnim = 0; + } +} + +/* +================ +idIK::IsInitialized +================ +*/ +bool idIK::IsInitialized() const { + return initialized && ik_enable.GetBool(); +} + +/* +================ +idIK::Init +================ +*/ +bool idIK::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + idRenderModel *model; + + if ( self == NULL ) { + return false; + } + + this->self = self; + + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + if ( animator->ModelDef()->ModelHandle() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) uses default model.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + model = animator->ModelHandle(); + if ( model == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + this->modelOffset = modelOffset; + + return true; +} + +/* +================ +idIK::Evaluate +================ +*/ +void idIK::Evaluate() { +} + +/* +================ +idIK::ClearJointMods +================ +*/ +void idIK::ClearJointMods() { + ik_activate = false; +} + +/* +================ +idIK::SolveTwoBones +================ +*/ +bool idIK::SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ) { + float length, lengthSqr, lengthInv, x, y; + idVec3 vec0, vec1; + + vec0 = endPos - startPos; + lengthSqr = vec0.LengthSqr(); + lengthInv = idMath::InvSqrt( lengthSqr ); + length = lengthInv * lengthSqr; + + // if the start and end position are too far out or too close to each other + if ( length > len0 + len1 || length < idMath::Fabs( len0 - len1 ) ) { + jointPos = startPos + 0.5f * vec0; + return false; + } + + vec0 *= lengthInv; + vec1 = dir - vec0 * dir * vec0; + vec1.Normalize(); + + x = ( length * length + len0 * len0 - len1 * len1 ) * ( 0.5f * lengthInv ); + y = idMath::Sqrt( len0 * len0 - x * x ); + + jointPos = startPos + x * vec0 + y * vec1; + + return true; +} + +/* +================ +idIK::GetBoneAxis +================ +*/ +float idIK::GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ) { + float length; + axis[0] = endPos - startPos; + length = axis[0].Normalize(); + axis[1] = dir - axis[0] * dir * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[1], axis[0] ); + return length; +} + + +/* +=============================================================================== + + idIK_Walk + +=============================================================================== +*/ + +/* +================ +idIK_Walk::idIK_Walk +================ +*/ +idIK_Walk::idIK_Walk() { + int i; + + initialized = false; + footModel = NULL; + numLegs = 0; + enabledLegs = 0; + for ( i = 0; i < MAX_LEGS; i++ ) { + footJoints[i] = INVALID_JOINT; + ankleJoints[i] = INVALID_JOINT; + kneeJoints[i] = INVALID_JOINT; + hipJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + hipForward[i].Zero(); + kneeForward[i].Zero(); + upperLegLength[i] = 0.0f; + lowerLegLength[i] = 0.0f; + upperLegToHipJoint[i].Identity(); + lowerLegToKneeJoint[i].Identity(); + oldAnkleHeights[i] = 0.0f; + } + waistJoint = INVALID_JOINT; + + smoothing = 0.75f; + waistSmoothing = 0.5f; + footShift = 0.0f; + waistShift = 0.0f; + minWaistFloorDist = 0.0f; + minWaistAnkleDist = 0.0f; + footUpTrace = 32.0f; + footDownTrace = 32.0f; + tiltWaist = false; + usePivot = false; + + pivotFoot = -1; + pivotYaw = 0.0f; + pivotPos.Zero(); + + oldHeightsValid = false; + oldWaistHeight = 0.0f; + waistOffset.Zero(); +} + +/* +================ +idIK_Walk::~idIK_Walk +================ +*/ +idIK_Walk::~idIK_Walk() { + if ( footModel ) { + delete footModel; + } +} + +/* +================ +idIK_Walk::Save +================ +*/ +void idIK_Walk::Save( idSaveGame *savefile ) const { + int i; + + idIK::Save( savefile ); + + savefile->WriteClipModel( footModel ); + + savefile->WriteInt( numLegs ); + savefile->WriteInt( enabledLegs ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteInt( footJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteInt( ankleJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteInt( kneeJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteInt( hipJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteInt( dirJoints[i] ); + savefile->WriteInt( waistJoint ); + + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteVec3( hipForward[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteVec3( kneeForward[i] ); + + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteFloat( upperLegLength[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteFloat( lowerLegLength[i] ); + + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteMat3( upperLegToHipJoint[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->WriteMat3( lowerLegToKneeJoint[i] ); + + savefile->WriteFloat( smoothing ); + savefile->WriteFloat( waistSmoothing ); + savefile->WriteFloat( footShift ); + savefile->WriteFloat( waistShift ); + savefile->WriteFloat( minWaistFloorDist ); + savefile->WriteFloat( minWaistAnkleDist ); + savefile->WriteFloat( footUpTrace ); + savefile->WriteFloat( footDownTrace ); + savefile->WriteBool( tiltWaist ); + savefile->WriteBool( usePivot ); + + savefile->WriteInt( pivotFoot ); + savefile->WriteFloat( pivotYaw ); + savefile->WriteVec3( pivotPos ); + savefile->WriteBool( oldHeightsValid ); + savefile->WriteFloat( oldWaistHeight ); + for ( i = 0; i < MAX_LEGS; i++ ) { + savefile->WriteFloat( oldAnkleHeights[i] ); + } + savefile->WriteVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Restore +================ +*/ +void idIK_Walk::Restore( idRestoreGame *savefile ) { + int i; + + idIK::Restore( savefile ); + + savefile->ReadClipModel( footModel ); + + savefile->ReadInt( numLegs ); + savefile->ReadInt( enabledLegs ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadInt( (int&)footJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadInt( (int&)ankleJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadInt( (int&)kneeJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadInt( (int&)hipJoints[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadInt( (int&)dirJoints[i] ); + savefile->ReadInt( (int&)waistJoint ); + + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadVec3( hipForward[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadVec3( kneeForward[i] ); + + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadFloat( upperLegLength[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadFloat( lowerLegLength[i] ); + + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadMat3( upperLegToHipJoint[i] ); + for ( i = 0; i < MAX_LEGS; i++ ) + savefile->ReadMat3( lowerLegToKneeJoint[i] ); + + savefile->ReadFloat( smoothing ); + savefile->ReadFloat( waistSmoothing ); + savefile->ReadFloat( footShift ); + savefile->ReadFloat( waistShift ); + savefile->ReadFloat( minWaistFloorDist ); + savefile->ReadFloat( minWaistAnkleDist ); + savefile->ReadFloat( footUpTrace ); + savefile->ReadFloat( footDownTrace ); + savefile->ReadBool( tiltWaist ); + savefile->ReadBool( usePivot ); + + savefile->ReadInt( pivotFoot ); + savefile->ReadFloat( pivotYaw ); + savefile->ReadVec3( pivotPos ); + savefile->ReadBool( oldHeightsValid ); + savefile->ReadFloat( oldWaistHeight ); + for ( i = 0; i < MAX_LEGS; i++ ) { + savefile->ReadFloat( oldAnkleHeights[i] ); + } + savefile->ReadVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Init +================ +*/ +bool idIK_Walk::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + float footSize; + idVec3 verts[4]; + idTraceModel trm; + const char *jointName; + idVec3 dir, ankleOrigin, kneeOrigin, hipOrigin, dirOrigin; + idMat3 axis, ankleAxis, kneeAxis, hipAxis; + + static idVec3 footWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !self ) { + return false; + } + + numLegs = Min( self->spawnArgs.GetInt( "ik_numLegs", "0" ), MAX_LEGS ); + if ( numLegs == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledLegs = 0; + + // get all the joints + for ( i = 0; i < numLegs; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_foot%d", i+1 ) ); + footJoints[i] = animator->GetJointHandle( jointName ); + if ( footJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid foot joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_ankle%d", i+1 ) ); + ankleJoints[i] = animator->GetJointHandle( jointName ); + if ( ankleJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid ankle joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_knee%d", i+1 ) ); + kneeJoints[i] = animator->GetJointHandle( jointName ); + if ( kneeJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid knee joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_hip%d", i+1 ) ); + hipJoints[i] = animator->GetJointHandle( jointName ); + if ( hipJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid hip joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_dir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledLegs |= 1 << i; + } + + jointName = self->spawnArgs.GetString( "ik_waist" ); + waistJoint = animator->GetJointHandle( jointName ); + if ( waistJoint == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid waist joint '%s'\n", jointName ); + } + + // get the leg bone lengths and rotation matrices + for ( i = 0; i < numLegs; i++ ) { + oldAnkleHeights[i] = 0.0f; + + ankleAxis = joints[ ankleJoints[ i ] ].ToMat3(); + ankleOrigin = joints[ ankleJoints[ i ] ].ToVec3(); + + kneeAxis = joints[ kneeJoints[ i ] ].ToMat3(); + kneeOrigin = joints[ kneeJoints[ i ] ].ToVec3(); + + hipAxis = joints[ hipJoints[ i ] ].ToMat3(); + hipOrigin = joints[ hipJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - kneeOrigin; + } else { + dir.Set( 1.0f, 0.0f, 0.0f ); + } + + hipForward[i] = dir * hipAxis.Transpose(); + kneeForward[i] = dir * kneeAxis.Transpose(); + + // conversion from upper leg bone axis to hip joint axis + upperLegLength[i] = GetBoneAxis( hipOrigin, kneeOrigin, dir, axis ); + upperLegToHipJoint[i] = hipAxis * axis.Transpose(); + + // conversion from lower leg bone axis to knee joint axis + lowerLegLength[i] = GetBoneAxis( kneeOrigin, ankleOrigin, dir, axis ); + lowerLegToKneeJoint[i] = kneeAxis * axis.Transpose(); + } + + smoothing = self->spawnArgs.GetFloat( "ik_smoothing", "0.75" ); + waistSmoothing = self->spawnArgs.GetFloat( "ik_waistSmoothing", "0.75" ); + footShift = self->spawnArgs.GetFloat( "ik_footShift", "0" ); + waistShift = self->spawnArgs.GetFloat( "ik_waistShift", "0" ); + minWaistFloorDist = self->spawnArgs.GetFloat( "ik_minWaistFloorDist", "0" ); + minWaistAnkleDist = self->spawnArgs.GetFloat( "ik_minWaistAnkleDist", "0" ); + footUpTrace = self->spawnArgs.GetFloat( "ik_footUpTrace", "32" ); + footDownTrace = self->spawnArgs.GetFloat( "ik_footDownTrace", "32" ); + tiltWaist = self->spawnArgs.GetBool( "ik_tiltWaist", "0" ); + usePivot = self->spawnArgs.GetBool( "ik_usePivot", "0" ); + + // setup a clip model for the feet + footSize = self->spawnArgs.GetFloat( "ik_footSize", "4" ) * 0.5f; + if ( footSize > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + verts[i] = footWinding[i] * footSize; + } + trm.SetupPolygon( verts, 4 ); + footModel = new (TAG_PHYSICS_CLIP) idClipModel( trm ); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Walk::Evaluate +================ +*/ +void idIK_Walk::Evaluate() { + int i, newPivotFoot = -1; + float modelHeight, jointHeight, lowestHeight, floorHeights[MAX_LEGS]; + float shift, smallestShift, newHeight, step, newPivotYaw, height, largestAnkleHeight; + idVec3 modelOrigin, normal, hipDir, kneeDir, start, end, jointOrigins[MAX_LEGS]; + idVec3 footOrigin, ankleOrigin, kneeOrigin, hipOrigin, waistOrigin; + idMat3 modelAxis, waistAxis, axis; + idMat3 hipAxis[MAX_LEGS], kneeAxis[MAX_LEGS], ankleAxis[MAX_LEGS]; + trace_t results; + + if ( !self || !gameLocal.isNewFrame ) { + return; + } + + // if no IK enabled on any legs + if ( !enabledLegs ) { + return; + } + + normal = - self->GetPhysics()->GetGravityNormal(); + modelOrigin = self->GetPhysics()->GetOrigin(); + modelAxis = self->GetRenderEntity()->axis; + modelHeight = modelOrigin * normal; + + modelOrigin += modelOffset * modelAxis; + + // create frame without joint mods + animator->CreateFrame( gameLocal.time, false ); + + // get the joint positions for the feet + lowestHeight = idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + animator->GetJointTransform( footJoints[i], gameLocal.time, footOrigin, axis ); + jointOrigins[i] = modelOrigin + footOrigin * modelAxis; + jointHeight = jointOrigins[i] * normal; + if ( jointHeight < lowestHeight ) { + lowestHeight = jointHeight; + newPivotFoot = i; + } + } + + if ( usePivot ) { + + newPivotYaw = modelAxis[0].ToYaw(); + + // change pivot foot + if ( newPivotFoot != pivotFoot || idMath::Fabs( idMath::AngleNormalize180( newPivotYaw - pivotYaw ) ) > 30.0f ) { + pivotFoot = newPivotFoot; + pivotYaw = newPivotYaw; + animator->GetJointTransform( footJoints[pivotFoot], gameLocal.time, footOrigin, axis ); + pivotPos = modelOrigin + footOrigin * modelAxis; + } + + // keep pivot foot in place + jointOrigins[pivotFoot] = pivotPos; + } + + // get the floor heights for the feet + for ( i = 0; i < numLegs; i++ ) { + + if ( !( enabledLegs & ( 1 << i ) ) ) { + continue; + } + + start = jointOrigins[i] + normal * footUpTrace; + end = jointOrigins[i] - normal * footDownTrace; + gameLocal.clip.Translation( results, start, end, footModel, mat3_identity, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); + floorHeights[i] = results.endpos * normal; + + if ( ik_debug.GetBool() && footModel ) { + idFixedWinding w; + for ( int j = 0; j < footModel->GetTraceModel()->numVerts; j++ ) { + w += footModel->GetTraceModel()->verts[j]; + } + gameRenderWorld->DebugWinding( colorRed, w, results.endpos, results.endAxis ); + } + } + + const idPhysics *phys = self->GetPhysics(); + + // test whether or not the character standing on the ground + bool onGround = phys->HasGroundContacts(); + + // test whether or not the character is standing on a plat + bool onPlat = false; + for ( i = 0; i < phys->GetNumContacts(); i++ ) { + idEntity *ent = gameLocal.entities[ phys->GetContact( i ).entityNum ]; + if ( ent != NULL && ent->IsType( idPlat::Type ) ) { + onPlat = true; + break; + } + } + + // adjust heights of the ankles + smallestShift = idMath::INFINITY; + largestAnkleHeight = -idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + + if ( onGround && ( enabledLegs & ( 1 << i ) ) ) { + shift = floorHeights[i] - modelHeight + footShift; + } else { + shift = 0.0f; + } + + if ( shift < smallestShift ) { + smallestShift = shift; + } + + animator->GetJointTransform( ankleJoints[i], gameLocal.time, ankleOrigin, ankleAxis[i] ); + jointOrigins[i] = modelOrigin + ankleOrigin * modelAxis; + + height = jointOrigins[i] * normal; + + if ( oldHeightsValid && !onPlat ) { + step = height + shift - oldAnkleHeights[i]; + shift -= smoothing * step; + } + + newHeight = height + shift; + if ( newHeight > largestAnkleHeight ) { + largestAnkleHeight = newHeight; + } + + oldAnkleHeights[i] = newHeight; + + jointOrigins[i] += shift * normal; + } + + animator->GetJointTransform( waistJoint, gameLocal.time, waistOrigin, waistAxis ); + waistOrigin = modelOrigin + waistOrigin * modelAxis; + + // adjust position of the waist + waistOffset = ( smallestShift + waistShift ) * normal; + + // if the waist should be at least a certain distance above the floor + if ( minWaistFloorDist > 0.0f && waistOffset * normal < 0.0f ) { + start = waistOrigin; + end = waistOrigin + waistOffset - normal * minWaistFloorDist; + gameLocal.clip.Translation( results, start, end, footModel, modelAxis, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); + height = ( waistOrigin + waistOffset - results.endpos ) * normal; + if ( height < minWaistFloorDist ) { + waistOffset += ( minWaistFloorDist - height ) * normal; + } + } + + // if the waist should be at least a certain distance above the ankles + if ( minWaistAnkleDist > 0.0f ) { + height = ( waistOrigin + waistOffset ) * normal; + if ( height - largestAnkleHeight < minWaistAnkleDist ) { + waistOffset += ( minWaistAnkleDist - ( height - largestAnkleHeight ) ) * normal; + } + } + + if ( oldHeightsValid ) { + // smoothly adjust height of waist + newHeight = ( waistOrigin + waistOffset ) * normal; + step = newHeight - oldWaistHeight; + waistOffset -= waistSmoothing * step * normal; + } + + // save height of waist for smoothing + oldWaistHeight = ( waistOrigin + waistOffset ) * normal; + + if ( !oldHeightsValid ) { + oldHeightsValid = true; + return; + } + + // solve IK + for ( i = 0; i < numLegs; i++ ) { + + // get the position of the hip in world space + animator->GetJointTransform( hipJoints[i], gameLocal.time, hipOrigin, axis ); + hipOrigin = modelOrigin + waistOffset + hipOrigin * modelAxis; + hipDir = hipForward[i] * axis * modelAxis; + + // get the IK bend direction + animator->GetJointTransform( kneeJoints[i], gameLocal.time, kneeOrigin, axis ); + kneeDir = kneeForward[i] * axis * modelAxis; + + // solve IK and calculate knee position + SolveTwoBones( hipOrigin, jointOrigins[i], kneeDir, upperLegLength[i], lowerLegLength[i], kneeOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, hipOrigin, kneeOrigin ); + gameRenderWorld->DebugLine( colorRed, kneeOrigin, jointOrigins[i] ); + gameRenderWorld->DebugLine( colorYellow, kneeOrigin, kneeOrigin + hipDir ); + gameRenderWorld->DebugLine( colorGreen, kneeOrigin, kneeOrigin + kneeDir ); + } + + // get the axis for the hip joint + GetBoneAxis( hipOrigin, kneeOrigin, hipDir, axis ); + hipAxis[i] = upperLegToHipJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the knee joint + GetBoneAxis( kneeOrigin, jointOrigins[i], kneeDir, axis ); + kneeAxis[i] = lowerLegToKneeJoint[i] * ( axis * modelAxis.Transpose() ); + } + + // set the joint mods + animator->SetJointAxis( waistJoint, JOINTMOD_WORLD_OVERRIDE, waistAxis ); + animator->SetJointPos( waistJoint, JOINTMOD_WORLD_OVERRIDE, ( waistOrigin + waistOffset - modelOrigin ) * modelAxis.Transpose() ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_WORLD_OVERRIDE, hipAxis[i] ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_WORLD_OVERRIDE, kneeAxis[i] ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_WORLD_OVERRIDE, ankleAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Walk::ClearJointMods +================ +*/ +void idIK_Walk::ClearJointMods() { + int i; + + if ( !self || !ik_activate ) { + return; + } + + animator->SetJointAxis( waistJoint, JOINTMOD_NONE, mat3_identity ); + animator->SetJointPos( waistJoint, JOINTMOD_NONE, vec3_origin ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} + +/* +================ +idIK_Walk::EnableAll +================ +*/ +void idIK_Walk::EnableAll() { + enabledLegs = ( 1 << numLegs ) - 1; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::DisableAll +================ +*/ +void idIK_Walk::DisableAll() { + enabledLegs = 0; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::EnableLeg +================ +*/ +void idIK_Walk::EnableLeg( int num ) { + enabledLegs |= 1 << num; +} + +/* +================ +idIK_Walk::DisableLeg +================ +*/ +void idIK_Walk::DisableLeg( int num ) { + enabledLegs &= ~( 1 << num ); +} + + +/* +=============================================================================== + + idIK_Reach + +=============================================================================== +*/ + +/* +================ +idIK_Reach::idIK_Reach +================ +*/ +idIK_Reach::idIK_Reach() { + int i; + + initialized = false; + numArms = 0; + enabledArms = 0; + for ( i = 0; i < MAX_ARMS; i++ ) { + handJoints[i] = INVALID_JOINT; + elbowJoints[i] = INVALID_JOINT; + shoulderJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + shoulderForward[i].Zero(); + elbowForward[i].Zero(); + upperArmLength[i] = 0.0f; + lowerArmLength[i] = 0.0f; + upperArmToShoulderJoint[i].Identity(); + lowerArmToElbowJoint[i].Identity(); + } +} + +/* +================ +idIK_Reach::~idIK_Reach +================ +*/ +idIK_Reach::~idIK_Reach() { +} + +/* +================ +idIK_Reach::Save +================ +*/ +void idIK_Reach::Save( idSaveGame *savefile ) const { + int i; + idIK::Save( savefile ); + + savefile->WriteInt( numArms ); + savefile->WriteInt( enabledArms ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteInt( handJoints[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteInt( elbowJoints[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteInt( shoulderJoints[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteInt( dirJoints[i] ); + + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteVec3( shoulderForward[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteVec3( elbowForward[i] ); + + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteFloat( upperArmLength[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteFloat( lowerArmLength[i] ); + + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteMat3( upperArmToShoulderJoint[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->WriteMat3( lowerArmToElbowJoint[i] ); +} + +/* +================ +idIK_Reach::Restore +================ +*/ +void idIK_Reach::Restore( idRestoreGame *savefile ) { + int i; + idIK::Restore( savefile ); + + savefile->ReadInt( numArms ); + savefile->ReadInt( enabledArms ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadInt( (int&)handJoints[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadInt( (int&)elbowJoints[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadInt( (int&)shoulderJoints[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadInt( (int&)dirJoints[i] ); + + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadVec3( shoulderForward[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadVec3( elbowForward[i] ); + + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadFloat( upperArmLength[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadFloat( lowerArmLength[i] ); + + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadMat3( upperArmToShoulderJoint[i] ); + for ( i = 0; i < MAX_ARMS; i++ ) + savefile->ReadMat3( lowerArmToElbowJoint[i] ); +} + +/* +================ +idIK_Reach::Init +================ +*/ +bool idIK_Reach::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + const char *jointName; + idTraceModel trm; + idVec3 dir, handOrigin, elbowOrigin, shoulderOrigin, dirOrigin; + idMat3 axis, handAxis, elbowAxis, shoulderAxis; + + if ( !self ) { + return false; + } + + numArms = Min( self->spawnArgs.GetInt( "ik_numArms", "0" ), MAX_ARMS ); + if ( numArms == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledArms = 0; + + // get all the joints + for ( i = 0; i < numArms; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_hand%d", i+1 ) ); + handJoints[i] = animator->GetJointHandle( jointName ); + if ( handJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid hand joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbow%d", i+1 ) ); + elbowJoints[i] = animator->GetJointHandle( jointName ); + if ( elbowJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid elbow joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_shoulder%d", i+1 ) ); + shoulderJoints[i] = animator->GetJointHandle( jointName ); + if ( shoulderJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid shoulder joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbowDir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledArms |= 1 << i; + } + + // get the arm bone lengths and rotation matrices + for ( i = 0; i < numArms; i++ ) { + + handAxis = joints[ handJoints[ i ] ].ToMat3(); + handOrigin = joints[ handJoints[ i ] ].ToVec3(); + + elbowAxis = joints[ elbowJoints[ i ] ].ToMat3(); + elbowOrigin = joints[ elbowJoints[ i ] ].ToVec3(); + + shoulderAxis = joints[ shoulderJoints[ i ] ].ToMat3(); + shoulderOrigin = joints[ shoulderJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - elbowOrigin; + } else { + dir.Set( -1.0f, 0.0f, 0.0f ); + } + + shoulderForward[i] = dir * shoulderAxis.Transpose(); + elbowForward[i] = dir * elbowAxis.Transpose(); + + // conversion from upper arm bone axis to should joint axis + upperArmLength[i] = GetBoneAxis( shoulderOrigin, elbowOrigin, dir, axis ); + upperArmToShoulderJoint[i] = shoulderAxis * axis.Transpose(); + + // conversion from lower arm bone axis to elbow joint axis + lowerArmLength[i] = GetBoneAxis( elbowOrigin, handOrigin, dir, axis ); + lowerArmToElbowJoint[i] = elbowAxis * axis.Transpose(); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Reach::Evaluate +================ +*/ +void idIK_Reach::Evaluate() { + int i; + idVec3 modelOrigin, shoulderOrigin, elbowOrigin, handOrigin, shoulderDir, elbowDir; + idMat3 modelAxis, axis; + idMat3 shoulderAxis[MAX_ARMS], elbowAxis[MAX_ARMS]; + trace_t trace; + + modelOrigin = self->GetRenderEntity()->origin; + modelAxis = self->GetRenderEntity()->axis; + + // solve IK + for ( i = 0; i < numArms; i++ ) { + + // get the position of the shoulder in world space + animator->GetJointTransform( shoulderJoints[i], gameLocal.time, shoulderOrigin, axis ); + shoulderOrigin = modelOrigin + shoulderOrigin * modelAxis; + shoulderDir = shoulderForward[i] * axis * modelAxis; + + // get the position of the hand in world space + animator->GetJointTransform( handJoints[i], gameLocal.time, handOrigin, axis ); + handOrigin = modelOrigin + handOrigin * modelAxis; + + // get first collision going from shoulder to hand + gameLocal.clip.TracePoint( trace, shoulderOrigin, handOrigin, CONTENTS_SOLID, self ); + handOrigin = trace.endpos; + + // get the IK bend direction + animator->GetJointTransform( elbowJoints[i], gameLocal.time, elbowOrigin, axis ); + elbowDir = elbowForward[i] * axis * modelAxis; + + // solve IK and calculate elbow position + SolveTwoBones( shoulderOrigin, handOrigin, elbowDir, upperArmLength[i], lowerArmLength[i], elbowOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, shoulderOrigin, elbowOrigin ); + gameRenderWorld->DebugLine( colorRed, elbowOrigin, handOrigin ); + gameRenderWorld->DebugLine( colorYellow, elbowOrigin, elbowOrigin + elbowDir ); + gameRenderWorld->DebugLine( colorGreen, elbowOrigin, elbowOrigin + shoulderDir ); + } + + // get the axis for the shoulder joint + GetBoneAxis( shoulderOrigin, elbowOrigin, shoulderDir, axis ); + shoulderAxis[i] = upperArmToShoulderJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the elbow joint + GetBoneAxis( elbowOrigin, handOrigin, elbowDir, axis ); + elbowAxis[i] = lowerArmToElbowJoint[i] * ( axis * modelAxis.Transpose() ); + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_WORLD_OVERRIDE, shoulderAxis[i] ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_WORLD_OVERRIDE, elbowAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Reach::ClearJointMods +================ +*/ +void idIK_Reach::ClearJointMods() { + int i; + + if ( !self || !ik_activate ) { + return; + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( handJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} diff --git a/neo/d3xp/IK.h b/neo/d3xp/IK.h new file mode 100644 index 00000000..021ac70c --- /dev/null +++ b/neo/d3xp/IK.h @@ -0,0 +1,182 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_IK_H__ +#define __GAME_IK_H__ + +/* +=============================================================================== + + IK base class with a simple fast two bone solver. + +=============================================================================== +*/ + +#define IK_ANIM "ik_pose" + +class idIK { +public: + idIK(); + virtual ~idIK(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsInitialized() const; + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate(); + virtual void ClearJointMods(); + + bool SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ); + float GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ); + +protected: + bool initialized; + bool ik_activate; + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // animation modified by the IK + idVec3 modelOffset; +}; + + +/* +=============================================================================== + + IK controller for a walking character with an arbitrary number of legs. + +=============================================================================== +*/ + +class idIK_Walk : public idIK { +public: + + idIK_Walk(); + virtual ~idIK_Walk(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate(); + virtual void ClearJointMods(); + + void EnableAll(); + void DisableAll(); + void EnableLeg( int num ); + void DisableLeg( int num ); + +private: + static const int MAX_LEGS = 8; + + idClipModel * footModel; + + int numLegs; + int enabledLegs; + jointHandle_t footJoints[MAX_LEGS]; + jointHandle_t ankleJoints[MAX_LEGS]; + jointHandle_t kneeJoints[MAX_LEGS]; + jointHandle_t hipJoints[MAX_LEGS]; + jointHandle_t dirJoints[MAX_LEGS]; + jointHandle_t waistJoint; + + idVec3 hipForward[MAX_LEGS]; + idVec3 kneeForward[MAX_LEGS]; + + float upperLegLength[MAX_LEGS]; + float lowerLegLength[MAX_LEGS]; + + idMat3 upperLegToHipJoint[MAX_LEGS]; + idMat3 lowerLegToKneeJoint[MAX_LEGS]; + + float smoothing; + float waistSmoothing; + float footShift; + float waistShift; + float minWaistFloorDist; + float minWaistAnkleDist; + float footUpTrace; + float footDownTrace; + bool tiltWaist; + bool usePivot; + + // state + int pivotFoot; + float pivotYaw; + idVec3 pivotPos; + bool oldHeightsValid; + float oldWaistHeight; + float oldAnkleHeights[MAX_LEGS]; + idVec3 waistOffset; +}; + + +/* +=============================================================================== + + IK controller for reaching a position with an arm or leg. + +=============================================================================== +*/ + +class idIK_Reach : public idIK { +public: + + idIK_Reach(); + virtual ~idIK_Reach(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate(); + virtual void ClearJointMods(); + +private: + + static const int MAX_ARMS = 2; + + int numArms; + int enabledArms; + jointHandle_t handJoints[MAX_ARMS]; + jointHandle_t elbowJoints[MAX_ARMS]; + jointHandle_t shoulderJoints[MAX_ARMS]; + jointHandle_t dirJoints[MAX_ARMS]; + + idVec3 shoulderForward[MAX_ARMS]; + idVec3 elbowForward[MAX_ARMS]; + + float upperArmLength[MAX_ARMS]; + float lowerArmLength[MAX_ARMS]; + + idMat3 upperArmToShoulderJoint[MAX_ARMS]; + idMat3 lowerArmToElbowJoint[MAX_ARMS]; +}; + +#endif /* !__GAME_IK_H__ */ diff --git a/neo/d3xp/Item.cpp b/neo/d3xp/Item.cpp new file mode 100644 index 00000000..220ef6f1 --- /dev/null +++ b/neo/d3xp/Item.cpp @@ -0,0 +1,2138 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idItem + +=============================================================================== +*/ + +const idEventDef EV_DropToFloor( "" ); +const idEventDef EV_RespawnItem( "respawn" ); +const idEventDef EV_RespawnFx( "" ); +const idEventDef EV_GetPlayerPos( "" ); +const idEventDef EV_HideObjective( "", "e" ); + +CLASS_DECLARATION( idEntity, idItem ) + EVENT( EV_DropToFloor, idItem::Event_DropToFloor ) + EVENT( EV_Touch, idItem::Event_Touch ) + EVENT( EV_Activate, idItem::Event_Trigger ) + EVENT( EV_RespawnItem, idItem::Event_Respawn ) + EVENT( EV_RespawnFx, idItem::Event_RespawnFx ) +END_CLASS + + +/* +================ +idItem::idItem +================ +*/ +idItem::idItem() : + clientPredictPickupMilliseconds( 0 ) { + spin = false; + inView = false; + inViewTime = 0; + lastCycle = 0; + lastRenderViewTime = -1; + itemShellHandle = -1; + shellMaterial = NULL; + orgOrigin.Zero(); + canPickUp = true; + fl.networkSync = true; +} + +/* +================ +idItem::~idItem +================ +*/ +idItem::~idItem() { + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + } +} + +/* +================ +idItem::Save +================ +*/ +void idItem::Save( idSaveGame *savefile ) const { + + savefile->WriteVec3( orgOrigin ); + savefile->WriteBool( spin ); + savefile->WriteBool( pulse ); + savefile->WriteBool( canPickUp ); + + savefile->WriteMaterial( shellMaterial ); + + savefile->WriteBool( inView ); + savefile->WriteInt( inViewTime ); + savefile->WriteInt( lastCycle ); + savefile->WriteInt( lastRenderViewTime ); +} + +/* +================ +idItem::Restore +================ +*/ +void idItem::Restore( idRestoreGame *savefile ) { + + savefile->ReadVec3( orgOrigin ); + savefile->ReadBool( spin ); + savefile->ReadBool( pulse ); + savefile->ReadBool( canPickUp ); + + savefile->ReadMaterial( shellMaterial ); + + savefile->ReadBool( inView ); + savefile->ReadInt( inViewTime ); + savefile->ReadInt( lastCycle ); + savefile->ReadInt( lastRenderViewTime ); + + itemShellHandle = -1; +} + +/* +================ +idItem::UpdateRenderEntity +================ +*/ +bool idItem::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + + if ( lastRenderViewTime == renderView->time[timeGroup] ) { + return false; + } + + lastRenderViewTime = renderView->time[timeGroup]; + + // check for glow highlighting if near the center of the view + idVec3 dir = renderEntity->origin - renderView->vieworg; + dir.Normalize(); + float d = dir * renderView->viewaxis[0]; + + // two second pulse cycle + float cycle = ( renderView->time[timeGroup] - inViewTime ) / 2000.0f; + + if ( d > 0.94f ) { + if ( !inView ) { + inView = true; + if ( cycle > lastCycle ) { + // restart at the beginning + inViewTime = renderView->time[timeGroup]; + cycle = 0.0f; + } + } + } else { + if ( inView ) { + inView = false; + lastCycle = ceil( cycle ); + } + } + + // fade down after the last pulse finishes + if ( !inView && cycle > lastCycle ) { + renderEntity->shaderParms[4] = 0.0f; + } else { + // pulse up in 1/4 second + cycle -= (int)cycle; + if ( cycle < 0.1f ) { + renderEntity->shaderParms[4] = cycle * 10.0f; + } else if ( cycle < 0.2f ) { + renderEntity->shaderParms[4] = 1.0f; + } else if ( cycle < 0.3f ) { + renderEntity->shaderParms[4] = 1.0f - ( cycle - 0.2f ) * 10.0f; + } else { + // stay off between pulses + renderEntity->shaderParms[4] = 0.0f; + } + } + + // update every single time this is in view + return true; +} + +/* +================ +idItem::ModelCallback +================ +*/ +bool idItem::ModelCallback( renderEntity_t *renderEntity, const renderView_t *renderView ) { + const idItem *ent; + + // this may be triggered by a model trace or other non-view related source + if ( !renderView ) { + return false; + } + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( ent == NULL ) { + gameLocal.Error( "idItem::ModelCallback: callback with NULL game entity" ); + return false; + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idItem::Think +================ +*/ +void idItem::Think() { + if ( thinkFlags & TH_THINK ) { + if ( spin ) { + idAngles ang; + idVec3 org; + + ang.pitch = ang.roll = 0.0f; + ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; + SetAngles( ang ); + + float scale = 0.005f + entityNumber * 0.00001f; + + org = orgOrigin; + org.z += 4.0f + cos( ( gameLocal.time + 2000 ) * scale ) * 4.0f; + SetOrigin( org ); + } + } + + Present(); +} + +/* +================ +idItem::Present +================ +*/ +void idItem::Present() { + idEntity::Present(); + + if ( !fl.hidden && pulse ) { + // also add a highlight shell model + renderEntity_t shell; + + shell = renderEntity; + + // we will mess with shader parms when the item is in view + // to give the "item pulse" effect + shell.callback = idItem::ModelCallback; + shell.entityNum = entityNumber; + shell.customShader = shellMaterial; + if ( itemShellHandle == -1 ) { + itemShellHandle = gameRenderWorld->AddEntityDef( &shell ); + } else { + gameRenderWorld->UpdateEntityDef( itemShellHandle, &shell ); + } + + } +} + +/* +================ +idItem::Spawn +================ +*/ +void idItem::Spawn() { + idStr giveTo; + idEntity * ent; + float tsize; + + if ( spawnArgs.GetBool( "dropToFloor" ) ) { + PostEventMS( &EV_DropToFloor, 0 ); + } + + if ( spawnArgs.GetFloat( "triggersize", "0", tsize ) ) { + GetPhysics()->GetClipModel()->LoadModel( idTraceModel( idBounds( vec3_origin ).Expand( tsize ) ) ); + GetPhysics()->GetClipModel()->Link( gameLocal.clip ); + } + + if ( spawnArgs.GetBool( "start_off" ) ) { + GetPhysics()->SetContents( 0 ); + Hide(); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + + giveTo = spawnArgs.GetString( "owner" ); + if ( giveTo.Length() ) { + ent = gameLocal.FindEntity( giveTo ); + if ( !ent ) { + gameLocal.Error( "Item couldn't find owner '%s'", giveTo.c_str() ); + } + PostEventMS( &EV_Touch, 0, ent, NULL ); + } + + // idItemTeam does not rotate and bob + if ( spawnArgs.GetBool( "spin" ) || (common->IsMultiplayer() && !this->IsType( idItemTeam::Type ) ) ) { + spin = true; + BecomeActive( TH_THINK ); + } + + //pulse = !spawnArgs.GetBool( "nopulse" ); + //temp hack for tim + pulse = false; + orgOrigin = GetPhysics()->GetOrigin(); + + canPickUp = !( spawnArgs.GetBool( "triggerFirst" ) || spawnArgs.GetBool( "no_touch" ) ); + + inViewTime = -1000; + lastCycle = -1; + itemShellHandle = -1; + shellMaterial = declManager->FindMaterial( "itemHighlightShell" ); +} + +/* +================ +idItem::GetAttributes +================ +*/ +void idItem::GetAttributes( idDict &attributes ) const { + int i; + const idKeyValue *arg; + + for( i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Left( 4 ) == "inv_" ) { + attributes.Set( arg->GetKey().Right( arg->GetKey().Length() - 4 ), arg->GetValue() ); + } + } +} + +/* +================ +idItem::GiveToPlayer +================ +*/ +bool idItem::GiveToPlayer( idPlayer *player, unsigned int giveFlags ) { + if ( player == NULL ) { + return false; + } + + if ( spawnArgs.GetBool( "inv_carry" ) ) { + return player->GiveInventoryItem( &spawnArgs, giveFlags ); + } + + return player->GiveItem( this, giveFlags ); +} + +/* +================ +idItem::Pickup +================ +*/ +bool idItem::Pickup( idPlayer *player ) { + + const bool didGiveSucceed = GiveToPlayer( player, ITEM_GIVE_FEEDBACK ); + if ( !didGiveSucceed ) { + return false; + } + + // Store the time so clients know when to stop predicting and let snapshots overwrite. + if ( player->IsLocallyControlled() ) { + clientPredictPickupMilliseconds = gameLocal.time; + } else { + clientPredictPickupMilliseconds = 0; + } + + // play pickup sound + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + // Clients need to bail out after some feedback, but + // before actually changing any values. The values + // will be updated in the next snapshot. + if ( common->IsClient() ) { + return didGiveSucceed; + } + + if ( !GiveToPlayer( player, ITEM_GIVE_UPDATE_STATE ) ) { + return false; + } + + // trigger our targets + ActivateTargets( player ); + + float respawn = spawnArgs.GetFloat( "respawn" ); + bool dropped = spawnArgs.GetBool( "dropped" ); + bool no_respawn = spawnArgs.GetBool( "no_respawn" ); + + if ( common->IsMultiplayer() && respawn == 0.0f ) { + respawn = 20.0f; + } + + if ( respawn && !dropped && !no_respawn ) { + const char *sfx = spawnArgs.GetString( "fxRespawn" ); + if ( sfx != NULL && *sfx != NULL ) { + PostEventSec( &EV_RespawnFx, respawn - 0.5f ); + } + PostEventSec( &EV_RespawnItem, respawn ); + } else if ( !spawnArgs.GetBool( "inv_objective" ) && !no_respawn ) { + // give some time for the pickup sound to play + // FIXME: Play on the owner + if ( !spawnArgs.GetBool( "inv_carry" ) ) { + PostEventMS( &EV_Remove, 5000 ); + } + } + + BecomeInactive( TH_THINK ); + return true; +} + +/* +================ +idItem::ClientThink +================ +*/ +void idItem::ClientThink( const int curTime, const float fraction, const bool predict ) { + + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + Think(); +} + + +/* +================ +idItem::ClientPredictionThink +================ +*/ +void idItem::ClientPredictionThink() { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + Think(); +} + +/* +================ +idItem::WriteFromSnapshot +================ +*/ +void idItem::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteBits( IsHidden(), 1 ); +} + +/* +================ +idItem::ReadFromSnapshot +================ +*/ +void idItem::ReadFromSnapshot( const idBitMsg &msg ) { + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else if ( clientPredictPickupMilliseconds != 0 ) { + // Fix mispredictions + if ( gameLocal.GetLastClientUsercmdMilliseconds( gameLocal.GetLocalClientNum() ) >= clientPredictPickupMilliseconds ) { + if ( GetPhysics()->GetContents() == 0 ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + Show(); + } + } +} + +/* +================ +idItem::ClientReceiveEvent +================ +*/ +bool idItem::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_RESPAWN: { + Event_Respawn(); + return true; + } + case EVENT_RESPAWNFX: { + Event_RespawnFx(); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} + +/* +================ +idItem::Event_DropToFloor +================ +*/ +void idItem::Event_DropToFloor() { + trace_t trace; + + // don't drop the floor if bound to another entity + if ( GetBindMaster() != NULL && GetBindMaster() != this ) { + return; + } + + gameLocal.clip.TraceBounds( trace, renderEntity.origin, renderEntity.origin - idVec3( 0, 0, 64 ), renderEntity.bounds, MASK_SOLID | CONTENTS_CORPSE, this ); + SetOrigin( trace.endpos ); +} + +/* +================ +idItem::Event_Touch +================ +*/ +void idItem::Event_Touch( idEntity *other, trace_t *trace ) { + if ( !other->IsType( idPlayer::Type ) ) { + return; + } + + if ( !canPickUp ) { + return; + } + + Pickup( static_cast(other) ); +} + +/* +================ +idItem::Event_Trigger +================ +*/ +void idItem::Event_Trigger( idEntity *activator ) { + + if ( !canPickUp && spawnArgs.GetBool( "triggerFirst" ) ) { + canPickUp = true; + return; + } + + if ( activator && activator->IsType( idPlayer::Type ) ) { + Pickup( static_cast( activator ) ); + } +} + +/* +================ +idItem::Event_Respawn +================ +*/ +void idItem::Event_Respawn() { + if ( common->IsServer() ) { + ServerSendEvent( EVENT_RESPAWN, NULL, false ); + } + BecomeActive( TH_THINK ); + Show(); + inViewTime = -1000; + lastCycle = -1; + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM, 0, false, NULL ); + CancelEvents( &EV_RespawnItem ); // don't double respawn +} + +/* +================ +idItem::Event_RespawnFx +================ +*/ +void idItem::Event_RespawnFx() { + if ( common->IsServer() ) { + ServerSendEvent( EVENT_RESPAWNFX, NULL, false ); + } + const char *sfx = spawnArgs.GetString( "fxRespawn" ); + if ( sfx != NULL && *sfx != NULL ) { + idEntityFx::StartFx( sfx, NULL, NULL, this, true ); + } +} + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +/* +=============== +idItemPowerup +=============== +*/ + +CLASS_DECLARATION( idItem, idItemPowerup ) +END_CLASS + +/* +================ +idItemPowerup::idItemPowerup +================ +*/ +idItemPowerup::idItemPowerup() { + time = 0; + type = 0; +} + +/* +================ +idItemPowerup::Save +================ +*/ +void idItemPowerup::Save( idSaveGame *savefile ) const { + savefile->WriteInt( time ); + savefile->WriteInt( type ); +} + +/* +================ +idItemPowerup::Restore +================ +*/ +void idItemPowerup::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( time ); + savefile->ReadInt( type ); +} + +/* +================ +idItemPowerup::Spawn +================ +*/ +void idItemPowerup::Spawn() { + time = spawnArgs.GetInt( "time", "30" ); + type = spawnArgs.GetInt( "type", "0" ); +} + +/* +================ +idItemPowerup::GiveToPlayer +================ +*/ +bool idItemPowerup::GiveToPlayer( idPlayer *player, unsigned int giveFlags ) { + if ( player->spectating ) { + return false; + } + player->GivePowerUp( type, time * 1000, giveFlags ); + return true; +} + +/* +=============================================================================== + + idItemTeam + + Used for flags in Capture the Flag + +=============================================================================== +*/ + +// temporarely removed these events + +const idEventDef EV_FlagReturn( "flagreturn", "e" ); +const idEventDef EV_TakeFlag( "takeflag", "e" ); +const idEventDef EV_DropFlag( "dropflag", "d" ); +const idEventDef EV_FlagCapture( "flagcapture" ); + +CLASS_DECLARATION( idItem, idItemTeam ) + EVENT( EV_FlagReturn, idItemTeam::Event_FlagReturn ) + EVENT( EV_TakeFlag, idItemTeam::Event_TakeFlag ) + EVENT( EV_DropFlag, idItemTeam::Event_DropFlag ) + EVENT( EV_FlagCapture, idItemTeam::Event_FlagCapture ) +END_CLASS + +/* +=============== +idItemTeam::idItemTeam +=============== +*/ +idItemTeam::idItemTeam() { + team = -1; + carried = false; + dropped = false; + lastDrop = 0; + + itemGlowHandle = -1; + + skinDefault = NULL; + skinCarried = NULL; + + scriptTaken = NULL; + scriptDropped = NULL; + scriptReturned = NULL; + scriptCaptured = NULL; + + lastNuggetDrop = 0; + nuggetName = 0; +} + +/* +=============== +idItemTeam::~idItemTeam +=============== +*/ +idItemTeam::~idItemTeam() { + FreeLightDef(); +} +/* +=============== +idItemTeam::Spawn +=============== +*/ +void idItemTeam::Spawn() { + team = spawnArgs.GetInt( "team" ); + returnOrigin = GetPhysics()->GetOrigin() + idVec3( 0, 0, 20 ); + returnAxis = GetPhysics()->GetAxis(); + + BecomeActive( TH_THINK ); + + const char * skinName; + skinName = spawnArgs.GetString( "skin", "" ); + if ( skinName[0] ) + skinDefault = declManager->FindSkin( skinName ); + + skinName = spawnArgs.GetString( "skin_carried", "" ); + if ( skinName[0] ) + skinCarried = declManager->FindSkin( skinName ); + + nuggetName = spawnArgs.GetString( "nugget_name", "" ); + if ( !nuggetName[0] ) { + nuggetName = NULL; + } + + scriptTaken = LoadScript( "script_taken" ); + scriptDropped = LoadScript( "script_dropped" ); + scriptReturned = LoadScript( "script_returned" ); + scriptCaptured = LoadScript( "script_captured" ); + + /* Spawn attached dlight */ + /* + idDict args; + idVec3 lightOffset( 0.0f, 20.0f, 0.0f ); + + // Set up the flag's dynamic light + memset( &itemGlow, 0, sizeof( itemGlow ) ); + itemGlow.axis = mat3_identity; + itemGlow.lightRadius.x = 128.0f; + itemGlow.lightRadius.y = itemGlow.lightRadius.z = itemGlow.lightRadius.x; + itemGlow.noShadows = true; + itemGlow.pointLight = true; + itemGlow.shaderParms[ SHADERPARM_RED ] = 0.0f; + itemGlow.shaderParms[ SHADERPARM_GREEN ] = 0.0f; + itemGlow.shaderParms[ SHADERPARM_BLUE ] = 0.0f; + itemGlow.shaderParms[ SHADERPARM_ALPHA ] = 0.0f; + + // Select a shader based on the team + if ( team == 0 ) + itemGlow.shader = declManager->FindMaterial( "lights/redflag" ); + else + itemGlow.shader = declManager->FindMaterial( "lights/blueflag" ); + */ + + idMoveableItem::Spawn(); + + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + physicsObj.SetGravity( idVec3( 0, 0, spawnArgs.GetInt("gravity", "-30" ) ) ); +} + + +/* +=============== +idItemTeam::LoadScript +=============== +*/ +function_t * idItemTeam::LoadScript( char * script ) { + function_t * function = NULL; + idStr funcname = spawnArgs.GetString( script, "" ); + if ( funcname.Length() ) { + function = gameLocal.program.FindFunction( funcname ); + if ( function == NULL ) { +#ifdef _DEBUG + gameLocal.Warning( "idItemTeam '%s' at (%s) calls unknown function '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), funcname.c_str() ); +#endif + } + } + return function; +} + + +/* +=============== +idItemTeam::Think +=============== +*/ +void idItemTeam::Think() { + idMoveableItem::Think(); + + TouchTriggers(); + + // TODO : only update on updatevisuals + /*idVec3 offset( 0.0f, 0.0f, 20.0f ); + itemGlow.origin = GetPhysics()->GetOrigin() + offset; + if ( itemGlowHandle == -1 ) { + itemGlowHandle = gameRenderWorld->AddLightDef( &itemGlow ); + } else { + gameRenderWorld->UpdateLightDef( itemGlowHandle, &itemGlow ); + }*/ + +#if 1 + // should only the server do this? + if ( common->IsServer() && nuggetName && carried && ( !lastNuggetDrop || (gameLocal.time - lastNuggetDrop) > spawnArgs.GetInt("nugget_frequency") ) ) { + + SpawnNugget( GetPhysics()->GetOrigin() ); + lastNuggetDrop = gameLocal.time; + } +#endif + + // return dropped flag after si_flagDropTimeLimit seconds + if ( dropped && !carried && lastDrop != 0 && (gameLocal.time - lastDrop) > ( si_flagDropTimeLimit.GetInteger()*1000 ) ) { + + Return(); // return flag after 30 seconds on ground + return; + } +} + +/* +=============== +idItemTeam::Pickup +=============== +*/ +bool idItemTeam::Pickup( idPlayer *player ) { + if ( !gameLocal.mpGame.IsGametypeFlagBased() ) /* CTF */ + return false; + + if ( gameLocal.mpGame.GetGameState() == idMultiplayerGame::WARMUP || + gameLocal.mpGame.GetGameState() == idMultiplayerGame::COUNTDOWN ) + return false; + + // wait 2 seconds after drop before beeing picked up again + if ( lastDrop != 0 && (gameLocal.time - lastDrop) < spawnArgs.GetInt("pickupDelay", "500") ) + return false; + + if ( carried == false && player->team != this->team ) { + + PostEventMS( &EV_TakeFlag, 0, player ); + + return true; + } else if ( carried == false && dropped == true && player->team == this->team ) { + + gameLocal.mpGame.PlayerScoreCTF( player->entityNumber, 5 ); + + // return flag + PostEventMS( &EV_FlagReturn, 0, player ); + + return false; + } + + return false; +} + +/* +=============== +idItemTeam::ClientReceiveEvent +=============== +*/ +bool idItemTeam::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + gameLocal.DPrintf("ClientRecieveEvent: %i\n", event ); + + switch ( event ) { + case EVENT_TAKEFLAG: { + idPlayer * player = static_cast(gameLocal.entities[ msg.ReadBits( GENTITYNUM_BITS ) ]); + if ( player == NULL ) { + gameLocal.Warning( "NULL player takes flag?\n" ); + return false; + } + + Event_TakeFlag( player ); + } + return true; + + case EVENT_DROPFLAG : { + bool death = bool( msg.ReadBits( 1 ) == 1 ); + Event_DropFlag( death ); + } + return true; + + case EVENT_FLAGRETURN : { + Hide(); + + FreeModelDef(); + FreeLightDef(); + + Event_FlagReturn(); + } + return true; + + case EVENT_FLAGCAPTURE : { + Hide(); + + FreeModelDef(); + FreeLightDef(); + + Event_FlagCapture(); + } + return true; + }; + + return false; +} + +/* +================ +idItemTeam::Drop +================ +*/ +void idItemTeam::Drop( bool death ) +{ +// PostEventMS( &EV_DropFlag, 0, int(death == true) ); +// had to remove the delayed drop because of drop flag on disconnect + Event_DropFlag( death ); +} + +/* +================ +idItemTeam::Return +================ +*/ +void idItemTeam::Return( idPlayer * player ) +{ + if ( team != 0 && team != 1 ) + return; + +// PostEventMS( &EV_FlagReturn, 0 ); + Event_FlagReturn(); +} + +/* +================ +idItemTeam::Capture +================ +*/ +void idItemTeam::Capture() +{ + if ( team != 0 && team != 1 ) + return; + + PostEventMS( &EV_FlagCapture, 0 ); +} + +/* +================ +idItemTeam::PrivateReturn +================ +*/ +void idItemTeam::PrivateReturn() +{ + Unbind(); + + if ( common->IsServer() && carried && !dropped ) { + int playerIdx = gameLocal.mpGame.GetFlagCarrier( 1-team ); + if ( playerIdx != -1 ) { + idPlayer * player = static_cast( gameLocal.entities[ playerIdx ] ); + player->carryingFlag = false; + } else { + gameLocal.Warning( "BUG: carried flag has no carrier before return" ); + } + } + + dropped = false; + carried = false; + + SetOrigin( returnOrigin ); + SetAxis( returnAxis ); + + trigger->Link( gameLocal.clip, this, 0, GetPhysics()->GetOrigin(), mat3_identity ); + + SetSkin( skinDefault ); + + // Turn off the light + /*itemGlow.shaderParms[ SHADERPARM_RED ] = 0.0f; + itemGlow.shaderParms[ SHADERPARM_GREEN ] = 0.0f; + itemGlow.shaderParms[ SHADERPARM_BLUE ] = 0.0f; + itemGlow.shaderParms[ SHADERPARM_ALPHA ] = 0.0f; + + if ( itemGlowHandle != -1 ) + gameRenderWorld->UpdateLightDef( itemGlowHandle, &itemGlow );*/ + + GetPhysics()->SetLinearVelocity( idVec3(0, 0, 0) ); + GetPhysics()->SetAngularVelocity( idVec3(0, 0, 0) ); +} + +/* +================ +idItemTeam::Event_TakeFlag +================ +*/ +void idItemTeam::Event_TakeFlag( idPlayer * player ) { + gameLocal.DPrintf("Event_TakeFlag()!\n"); + + assert( player != NULL ); + + if ( player->carryingFlag ) { + // Don't do anything if the player is already carrying the flag. + // Prevents duplicate messages. + return; + } + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + // Send the event + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( player->entityNumber, GENTITYNUM_BITS ); + ServerSendEvent( EVENT_TAKEFLAG, &msg, false ); + + gameLocal.mpGame.PlayTeamSound( player->team, SND_FLAG_TAKEN_THEIRS ); + gameLocal.mpGame.PlayTeamSound( team, SND_FLAG_TAKEN_YOURS ); + + gameLocal.mpGame.PrintMessageEvent( idMultiplayerGame::MSG_FLAGTAKEN, team, player->entityNumber ); + + // dont drop a nugget RIGHT away + lastNuggetDrop = gameLocal.time - gameLocal.random.RandomInt( 1000 ); + + } + + BindToJoint( player, g_flagAttachJoint.GetString(), true ); + idVec3 origin( g_flagAttachOffsetX.GetFloat(), g_flagAttachOffsetY.GetFloat(), g_flagAttachOffsetZ.GetFloat() ); + idAngles angle( g_flagAttachAngleX.GetFloat(), g_flagAttachAngleY.GetFloat(), g_flagAttachAngleZ.GetFloat() ); + SetAngles( angle ); + SetOrigin( origin ); + + // Turn the light on + /*itemGlow.shaderParms[ SHADERPARM_RED ] = 1.0f; + itemGlow.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + itemGlow.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + itemGlow.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + + if ( itemGlowHandle != -1 ) + gameRenderWorld->UpdateLightDef( itemGlowHandle, &itemGlow );*/ + + if ( scriptTaken ) { + idThread *thread = new idThread(); + thread->CallFunction( scriptTaken, false ); + thread->DelayedStart( 0 ); + } + + dropped = false; + carried = true; + player->carryingFlag = true; + + SetSkin( skinCarried ); + + UpdateVisuals(); + UpdateGuis(); + + if ( common->IsServer() ) { + if ( team == 0 ) + gameLocal.mpGame.player_red_flag = player->entityNumber; + else + gameLocal.mpGame.player_blue_flag = player->entityNumber; + } +} + +/* +================ +idItemTeam::Event_DropFlag +================ +*/ +void idItemTeam::Event_DropFlag( bool death ) { + gameLocal.DPrintf("Event_DropFlag()!\n"); + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + // Send the event + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( death, 1 ); + ServerSendEvent( EVENT_DROPFLAG, &msg, false ); + + if ( gameLocal.mpGame.IsFlagMsgOn() ) { + gameLocal.mpGame.PlayTeamSound( 1-team, SND_FLAG_DROPPED_THEIRS ); + gameLocal.mpGame.PlayTeamSound( team, SND_FLAG_DROPPED_YOURS ); + + gameLocal.mpGame.PrintMessageEvent( idMultiplayerGame::MSG_FLAGDROP, team ); + } + } + + lastDrop = gameLocal.time; + + BecomeActive( TH_THINK ); + Show(); + + if ( death ) + GetPhysics()->SetLinearVelocity( idVec3(0, 0, 0) ); + else + GetPhysics()->SetLinearVelocity( idVec3(0, 0, 20) ); + + GetPhysics()->SetAngularVelocity( idVec3(0, 0, 0) ); + +// GetPhysics()->SetLinearVelocity( ( GetPhysics()->GetLinearVelocity() * GetBindMaster()->GetPhysics()->GetAxis() ) + GetBindMaster()->GetPhysics()->GetLinearVelocity() ); + + if ( GetBindMaster() ) { + const idBounds bounds = GetPhysics()->GetBounds(); + idVec3 origin = GetBindMaster()->GetPhysics()->GetOrigin() + idVec3(0, 0, ( bounds[1].z-bounds[0].z )*0.6f ); + + Unbind(); + + SetOrigin( origin ); + } + + idAngles angle = GetPhysics()->GetAxis().ToAngles(); + angle.roll = 0; + angle.pitch = 0; + SetAxis( angle.ToMat3() ); + + dropped = true; + carried = false; + + if ( scriptDropped ) { + idThread *thread = new idThread(); + thread->CallFunction( scriptDropped, false ); + thread->DelayedStart( 0 ); + } + + SetSkin( skinDefault ); + UpdateVisuals(); + UpdateGuis(); + + + if ( common->IsServer() ) { + if ( team == 0 ) + gameLocal.mpGame.player_red_flag = -1; + else + gameLocal.mpGame.player_blue_flag = -1; + + } +} + +/* +================ +idItemTeam::Event_FlagReturn +================ +*/ +void idItemTeam::Event_FlagReturn( idPlayer * player ) { + gameLocal.DPrintf("Event_FlagReturn()!\n"); + + if ( common->IsServer() ) { + ServerSendEvent( EVENT_FLAGRETURN, NULL, false ); + + if ( gameLocal.mpGame.IsFlagMsgOn() ) { + gameLocal.mpGame.PlayTeamSound( 1-team, SND_FLAG_RETURN ); + gameLocal.mpGame.PlayTeamSound( team, SND_FLAG_RETURN ); + + int entitynum = 255; + if ( player ) { + entitynum = player->entityNumber; + } + + gameLocal.mpGame.PrintMessageEvent( idMultiplayerGame::MSG_FLAGRETURN, team, entitynum ); + } + } + + BecomeActive( TH_THINK ); + Show(); + + PrivateReturn(); + + if ( scriptReturned ) { + idThread *thread = new idThread(); + thread->CallFunction( scriptReturned, false ); + thread->DelayedStart( 0 ); + } + + UpdateVisuals(); + UpdateGuis(); +// Present(); + + if ( common->IsServer() ) { + if ( team == 0 ) + gameLocal.mpGame.player_red_flag = -1; + else + gameLocal.mpGame.player_blue_flag = -1; + } +} + +/* +================ +idItemTeam::Event_FlagCapture +================ +*/ +void idItemTeam::Event_FlagCapture() { + gameLocal.DPrintf("Event_FlagCapture()!\n"); + + if ( common->IsServer() ) { + int playerIdx = gameLocal.mpGame.GetFlagCarrier( 1-team ); + if ( playerIdx != -1 ) { + ServerSendEvent( EVENT_FLAGCAPTURE, NULL, false ); + + gameLocal.mpGame.PlayTeamSound( 1-team, SND_FLAG_CAPTURED_THEIRS ); + gameLocal.mpGame.PlayTeamSound( team, SND_FLAG_CAPTURED_YOURS ); + + gameLocal.mpGame.TeamScoreCTF( 1-team, 1 ); + + gameLocal.mpGame.PlayerScoreCTF( playerIdx, 10 ); + + gameLocal.mpGame.PrintMessageEvent( idMultiplayerGame::MSG_FLAGCAPTURE, team, playerIdx ); + } else { + playerIdx = 255; + } + } + + BecomeActive( TH_THINK ); + Show(); + + PrivateReturn(); + + if ( scriptCaptured ) { + idThread *thread = new idThread(); + thread->CallFunction( scriptCaptured, false ); + thread->DelayedStart( 0 ); + } + + UpdateVisuals(); + UpdateGuis(); + + + if ( common->IsServer() ) { + if ( team == 0 ) + gameLocal.mpGame.player_red_flag = -1; + else + gameLocal.mpGame.player_blue_flag = -1; + } + +} + +/* +================ +idItemTeam::FreeLightDef +================ +*/ +void idItemTeam::FreeLightDef() { + if ( itemGlowHandle != -1 ) { + gameRenderWorld->FreeLightDef( itemGlowHandle ); + itemGlowHandle = -1; + } +} + +/* +================ +idItemTeam::SpawnNugget +================ +*/ +void idItemTeam::SpawnNugget( idVec3 pos ) { + + idAngles angle( gameLocal.random.RandomInt(spawnArgs.GetInt("nugget_pitch", "30")), gameLocal.random.RandomInt(spawnArgs.GetInt("nugget_yaw", "360" )), 0 ); + float velocity = float(gameLocal.random.RandomInt( 40 )+15); + + velocity *= spawnArgs.GetFloat("nugget_velocity", "1" ); + + idEntity * ent = idMoveableItem::DropItem( nuggetName, pos, GetPhysics()->GetAxis(), angle.ToMat3()*idVec3(velocity, velocity, velocity), 0, spawnArgs.GetInt("nugget_removedelay") ); + idPhysics_RigidBody * physics = static_cast( ent->GetPhysics() ); + + if ( physics != NULL && physics->IsType( idPhysics_RigidBody::Type ) ) { + physics->DisableImpact(); + } +} + + + +/* +================ +idItemTeam::Event_FlagCapture +================ +*/ +void idItemTeam::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteBits( carried, 1 ); + msg.WriteBits( dropped, 1 ); + + WriteBindToSnapshot( msg ); + + idMoveableItem::WriteToSnapshot( msg ); +} + + +/* +================ +idItemTeam::ReadFromSnapshot +================ +*/ +void idItemTeam::ReadFromSnapshot( const idBitMsg &msg ) { + carried = msg.ReadBits( 1 ) == 1; + dropped = msg.ReadBits( 1 ) == 1; + + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) + { + UpdateGuis(); + + if ( carried == true ) + SetSkin( skinCarried ); + else + SetSkin( skinDefault ); + } + + idMoveableItem::ReadFromSnapshot( msg ); +} + +/* +================ +idItemTeam::UpdateGuis + +Update all client's huds wrt the flag status. +================ +*/ +void idItemTeam::UpdateGuis() { + idPlayer *player; + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast( gameLocal.entities[ i ] ); + + if ( player && player->hud ) { + + player->hud->SetFlagState( 0, gameLocal.mpGame.GetFlagStatus( 0 ) ); + player->hud->SetFlagState( 1, gameLocal.mpGame.GetFlagStatus( 1 ) ); + + player->hud->SetTeamScore( 0, gameLocal.mpGame.GetFlagPoints( 0 ) ); + player->hud->SetTeamScore( 1, gameLocal.mpGame.GetFlagPoints( 1 ) ); + } + } +} + +/* +================ +idItemTeam::Present +================ +*/ +void idItemTeam::Present() { + // hide the flag for localplayer if in first person + if ( carried && GetBindMaster() ) { + idPlayer * player = static_cast( GetBindMaster() ); + if ( player == gameLocal.GetLocalPlayer() && !pm_thirdPerson.GetBool() ) { + FreeModelDef(); + BecomeActive( TH_UPDATEVISUALS ); + return; + } + } + + idEntity::Present(); +} + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idObjective ) + EVENT( EV_Activate, idObjective::Event_Trigger ) + EVENT( EV_HideObjective, idObjective::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjective::Event_GetPlayerPos ) +END_CLASS + +/* +================ +idObjective::idObjective +================ +*/ +idObjective::idObjective() { + playerPos.Zero(); +} + +/* +================ +idObjective::Save +================ +*/ +void idObjective::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); + savefile->WriteMaterial( screenshot ); +} + +/* +================ +idObjective::Restore +================ +*/ +void idObjective::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); + savefile->ReadMaterial( screenshot ); +} + +/* +================ +idObjective::Spawn +================ +*/ +void idObjective::Spawn() { + Hide(); + idStr shotName; + shotName = gameLocal.GetMapName(); + shotName.StripFileExtension(); + shotName += "/"; + shotName += spawnArgs.GetString( "screenshot" ); + shotName.SetFileExtension( ".tga" ); + screenshot = declManager->FindMaterial( shotName ); +} + +/* +================ +idObjective::Event_Trigger +================ +*/ +void idObjective::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + + //Pickup( player ); + + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { + if ( player ) { + player->GiveObjective( spawnArgs.GetString( "objectivetitle" ), spawnArgs.GetString( "objectivetext" ), screenshot ); + + // a tad slow but keeps from having to update all objectives in all maps with a name ptr + for( int i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idObjectiveComplete::Type ) ) { + if ( idStr::Icmp( spawnArgs.GetString( "objectivetitle" ), gameLocal.entities[ i ]->spawnArgs.GetString( "objectivetitle" ) ) == 0 ){ + gameLocal.entities[ i ]->spawnArgs.SetBool( "objEnabled", true ); + break; + } + } + } + + PostEventMS( &EV_GetPlayerPos, 2000 ); + } + } + } +} + +/* +================ +idObjective::Event_GetPlayerPos +================ +*/ +void idObjective::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjective::Event_HideObjective +================ +*/ +void idObjective::Event_HideObjective(idEntity *e) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; + if ( v.Length() > 64.0f ) { + player->HideObjective(); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + idVideoCDItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idVideoCDItem ) +END_CLASS + +/* +================ +idVideoCDItem::GiveToPlayer +================ +*/ +bool idVideoCDItem::GiveToPlayer( idPlayer * player, unsigned int giveFlags ) { + if ( player == NULL ) { + return false; + } + const idDeclVideo * video = static_cast( declManager->FindType( DECL_VIDEO, spawnArgs.GetString( "video" ), false ) ); + if ( video == NULL ) { + return false; + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + player->GiveVideo( video, spawnArgs.GetString( "inv_name" ) ); + } + return true; +} + +/* +=============================================================================== + + idPDAItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idPDAItem ) +END_CLASS + +/* +================ +idPDAItem::GiveToPlayer +================ +*/ +bool idPDAItem::GiveToPlayer( idPlayer *player, unsigned int giveFlags ) { + if ( player == NULL ) { + return false; + } + const char * pdaName = spawnArgs.GetString( "pda_name" ); + const char * invName = spawnArgs.GetString( "inv_name" ); + const idDeclPDA * pda = NULL; + if ( pdaName != NULL && pdaName[0] != 0 ) { + // An empty PDA name is legitimate, it means the personal PDA + // But if the PDA name is not empty, it should be valid + pda = static_cast( declManager->FindType( DECL_PDA, pdaName, false ) ); + if ( pda == NULL ) { + idLib::Warning( "PDA Item '%s' references unknown PDA %s", GetName(), pdaName ); + return false; + } + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + player->GivePDA( pda, invName ); + } + return true; +} + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idMoveableItem ) + EVENT( EV_DropToFloor, idMoveableItem::Event_DropToFloor ) + EVENT( EV_Gib, idMoveableItem::Event_Gib ) +END_CLASS + +/* +================ +idMoveableItem::idMoveableItem +================ +*/ +idMoveableItem::idMoveableItem() { + trigger = NULL; + smoke = NULL; + smokeTime = 0; + nextSoundTime = 0; + repeatSmoke = false; +} + +/* +================ +idMoveableItem::~idMoveableItem +================ +*/ +idMoveableItem::~idMoveableItem() { + if ( trigger ) { + delete trigger; + } +} + +/* +================ +idMoveableItem::Save +================ +*/ +void idMoveableItem::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteClipModel( trigger ); + + savefile->WriteParticle( smoke ); + savefile->WriteInt( smokeTime ); + savefile->WriteInt( nextSoundTime ); +} + +/* +================ +idMoveableItem::Restore +================ +*/ +void idMoveableItem::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadClipModel( trigger ); + + savefile->ReadParticle( smoke ); + savefile->ReadInt( smokeTime ); + savefile->ReadInt( nextSoundTime ); +} + +/* +================ +idMoveableItem::Spawn +================ +*/ +void idMoveableItem::Spawn() { + idTraceModel trm; + float density, friction, bouncyness, tsize; + idStr clipModelName; + idBounds bounds; + SetTimeState ts( timeGroup ); + + // create a trigger for item pickup + spawnArgs.GetFloat( "triggersize", "16.0", tsize ); + trigger = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( idTraceModel( idBounds( vec3_origin ).Expand( tsize ) ) ); + trigger->Link( gameLocal.clip, this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + trigger->SetContents( CONTENTS_TRIGGER ); + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + // load the trace model + if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { + gameLocal.Error( "idMoveableItem '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + // if the model should be shrinked + if ( spawnArgs.GetBool( "clipshrink" ) ) { + trm.Shrink( CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + // setup the physics + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ), density ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } + + smoke = NULL; + smokeTime = 0; + nextSoundTime = 0; + const char *smokeName = spawnArgs.GetString( "smoke_trail" ); + if ( *smokeName != '\0' ) { + smoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeTime = gameLocal.time; + BecomeActive( TH_UPDATEPARTICLES ); + } + + repeatSmoke = spawnArgs.GetBool( "repeatSmoke", "0" ); +} + +/* +================ +idItem::ClientThink +================ +*/ +void idMoveableItem::ClientThink( const int curTime, const float fraction, const bool predict ) { + + InterpolatePhysicsOnly( fraction ); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + trigger->Link( gameLocal.clip, this, 0, GetPhysics()->GetOrigin(), mat3_identity ); + } + + Present(); +} + +/* +================ +idMoveableItem::Think +================ +*/ +void idMoveableItem::Think() { + + RunPhysics(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + trigger->Link( gameLocal.clip, this, 0, GetPhysics()->GetOrigin(), mat3_identity ); + } + + if ( thinkFlags & TH_UPDATEPARTICLES ) { + if ( !gameLocal.smokeParticles->EmitSmoke( smoke, smokeTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) { + if ( !repeatSmoke ) { + smokeTime = 0; + BecomeInactive( TH_UPDATEPARTICLES ); + } else { + smokeTime = gameLocal.time; + } + } + } + + Present(); +} + +/* +================= +idMoveableItem::Collide +================= +*/ +bool idMoveableItem::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + v = -( velocity * collision.c.normal ); + if ( v > 80 && gameLocal.time > nextSoundTime ) { + f = v > 200 ? 1.0f : idMath::Sqrt( v - 80 ) * 0.091f; + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + + return false; +} + +/* +================ +idMoveableItem::Pickup +================ +*/ +bool idMoveableItem::Pickup( idPlayer *player ) { + bool ret = idItem::Pickup( player ); + if ( ret ) { + trigger->SetContents( 0 ); + } + return ret; +} + +/* +================ +idMoveableItem::DropItem +================ +*/ +idEntity *idMoveableItem::DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ) { + idDict args; + idEntity *item; + + args.Set( "classname", classname ); + args.Set( "dropped", "1" ); + + // we sometimes drop idMoveables here, so set 'nodrop' to 1 so that it doesn't get put on the floor + args.Set( "nodrop", "1" ); + + if ( activateDelay ) { + args.SetBool( "triggerFirst", true ); + } + + gameLocal.SpawnEntityDef( args, &item ); + if ( item ) { + // set item position + item->GetPhysics()->SetOrigin( origin ); + item->GetPhysics()->SetAxis( axis ); + item->GetPhysics()->SetLinearVelocity( velocity ); + item->UpdateVisuals(); + if ( activateDelay ) { + item->PostEventMS( &EV_Activate, activateDelay, item ); + } + if ( !removeDelay ) { + removeDelay = 5 * 60 * 1000; + } + // always remove a dropped item after 5 minutes in case it dropped to an unreachable location + item->PostEventMS( &EV_Remove, removeDelay ); + } + return item; +} + +/* +================ +idMoveableItem::DropItems + + The entity should have the following key/value pairs set: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + "skin_drop" "skin name" + To drop multiple items the following key/value pairs can be used: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + where is an aribtrary string. +================ +*/ +void idMoveableItem::DropItems( idAnimatedEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName, *c, *jointName; + idStr key, key2; + idVec3 origin; + idMat3 axis; + idAngles angles; + const idDeclSkin *skin; + jointHandle_t joint; + idEntity *item; + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), NULL ); + while ( kv ) { + + c = kv->GetKey().c_str() + kv->GetKey().Length(); + if ( idStr::Icmp( c - 5, "Joint" ) != 0 && idStr::Icmp( c - 8, "Rotation" ) != 0 ) { + + key = kv->GetKey().c_str() + 4; + key2 = key; + key += "Joint"; + key2 += "Offset"; + jointName = ent->spawnArgs.GetString( key ); + joint = ent->GetAnimator()->GetJointHandle( jointName ); + if ( !ent->GetJointWorldTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", key.c_str(), jointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + if ( g_dropItemRotation.GetString()[0] ) { + angles.Zero(); + sscanf( g_dropItemRotation.GetString(), "%f %f %f", &angles.pitch, &angles.yaw, &angles.roll ); + } else { + key = kv->GetKey().c_str() + 4; + key += "Rotation"; + ent->spawnArgs.GetAngles( key, "0 0 0", angles ); + } + axis = angles.ToMat3() * axis; + + origin += ent->spawnArgs.GetVector( key2, "0 0 0" ); + + item = DropItem( kv->GetValue(), origin, axis, vec3_origin, 0, 0 ); + if ( list && item ) { + list->Append( item ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), kv ); + } + + // change the skin to hide all items + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +====================== +idMoveableItem::WriteToSnapshot +====================== +*/ +void idMoveableItem::WriteToSnapshot( idBitMsg &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBool( IsHidden() ); +} + +/* +====================== +idMoveableItem::ReadFromSnapshot +====================== +*/ +void idMoveableItem::ReadFromSnapshot( const idBitMsg &msg ) { + physicsObj.ReadFromSnapshot( msg ); + const bool snapshotHidden = msg.ReadBool(); + + if ( snapshotHidden ) { + Hide(); + } else if ( GetPredictPickupMilliseconds() != 0 ) { + if ( gameLocal.GetLastClientUsercmdMilliseconds( gameLocal.GetLocalClientNum() ) >= GetPredictPickupMilliseconds() ) { + if ( trigger->GetContents() == 0 ) { + trigger->SetContents( CONTENTS_TRIGGER ); + } + Show(); + } + } + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +============ +idMoveableItem::Gib +============ +*/ +void idMoveableItem::Gib( const idVec3 &dir, const char *damageDefName ) { + // spawn smoke puff + const char *smokeName = spawnArgs.GetString( "smoke_gib" ); + if ( *smokeName != '\0' ) { + const idDeclParticle *smoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + gameLocal.smokeParticles->EmitSmoke( smoke, gameLocal.time, gameLocal.random.CRandomFloat(), renderEntity.origin, renderEntity.axis, timeGroup /*_D3XP*/ ); + } + // remove the entity + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +idMoveableItem::Event_DropToFloor +================ +*/ +void idMoveableItem::Event_DropToFloor() { + // the physics will drop the moveable to the floor +} + +/* +============ +idMoveableItem::Event_Gib +============ +*/ +void idMoveableItem::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idMoveablePDAItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idMoveableItem, idMoveablePDAItem ) +END_CLASS + +/* +================ +idMoveablePDAItem::GiveToPlayer +================ +*/ +bool idMoveablePDAItem::GiveToPlayer( idPlayer * player, unsigned int giveFlags ) { + if ( player == NULL ) { + return false; + } + const char * pdaName = spawnArgs.GetString( "pda_name" ); + const char * invName = spawnArgs.GetString( "inv_name" ); + const idDeclPDA * pda = NULL; + if ( pdaName != NULL && pdaName[0] != 0 ) { + // An empty PDA name is legitimate, it means the personal PDA + // But if the PDA name is not empty, it should be valid + pda = static_cast( declManager->FindType( DECL_PDA, pdaName, false ) ); + if ( pda == NULL ) { + idLib::Warning( "PDA Item '%s' references unknown PDA %s", GetName(), pdaName ); + return false; + } + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + player->GivePDA( pda, invName ); + } + return true; +} + +/* +=============================================================================== + + idItemRemover + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idItemRemover ) + EVENT( EV_Activate, idItemRemover::Event_Trigger ) +END_CLASS + +/* +================ +idItemRemover::Spawn +================ +*/ +void idItemRemover::Spawn() { +} + +/* +================ +idItemRemover::RemoveItem +================ +*/ +void idItemRemover::RemoveItem( idPlayer *player ) { + const char *remove; + + remove = spawnArgs.GetString( "remove" ); + player->RemoveInventoryItem( remove ); +} + +/* +================ +idItemRemover::Event_Trigger +================ +*/ +void idItemRemover::Event_Trigger( idEntity *activator ) { + if ( activator->IsType( idPlayer::Type ) ) { + RemoveItem( static_cast(activator) ); + } +} + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +CLASS_DECLARATION( idItemRemover, idObjectiveComplete ) + EVENT( EV_Activate, idObjectiveComplete::Event_Trigger ) + EVENT( EV_HideObjective, idObjectiveComplete::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjectiveComplete::Event_GetPlayerPos ) +END_CLASS + +/* +================ +idObjectiveComplete::idObjectiveComplete +================ +*/ +idObjectiveComplete::idObjectiveComplete() { + playerPos.Zero(); +} + +/* +================ +idObjectiveComplete::Save +================ +*/ +void idObjectiveComplete::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Restore +================ +*/ +void idObjectiveComplete::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Spawn +================ +*/ +void idObjectiveComplete::Spawn() { + spawnArgs.SetBool( "objEnabled", false ); + Hide(); +} + +/* +================ +idObjectiveComplete::Event_Trigger +================ +*/ +void idObjectiveComplete::Event_Trigger( idEntity *activator ) { + if ( !spawnArgs.GetBool( "objEnabled" ) ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + RemoveItem( player ); + + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { + player->CompleteObjective( spawnArgs.GetString( "objectivetitle" ) ); + PostEventMS( &EV_GetPlayerPos, 2000 ); + } + } +} + +/* +================ +idObjectiveComplete::Event_GetPlayerPos +================ +*/ +void idObjectiveComplete::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjectiveComplete::Event_HideObjective +================ +*/ +void idObjectiveComplete::Event_HideObjective( idEntity *e ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin(); + v -= playerPos; + if ( v.Length() > 64.0f ) { + player->HideObjective(); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} diff --git a/neo/d3xp/Item.h b/neo/d3xp/Item.h new file mode 100644 index 00000000..28106dce --- /dev/null +++ b/neo/d3xp/Item.h @@ -0,0 +1,324 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_ITEM_H__ +#define __GAME_ITEM_H__ + + +/* +=============================================================================== + + Items the player can pick up or use. + +=============================================================================== +*/ + +/* +================================================ +These flags are passed to the Give functions +to set their behavior. We need to be able to +separate the feedback from the actual +state modification so that we can hide lag +on MP clients. + +For the previous behavior of functions which +take a giveFlags parameter (this is usually +desired on the server too) pass +ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE. +================================================ +*/ +enum itemGiveFlags_t { + ITEM_GIVE_FEEDBACK = BIT( 0 ), + ITEM_GIVE_UPDATE_STATE = BIT( 1 ), + ITEM_GIVE_FROM_WEAPON = BIT( 2 ), // indicates this was given via a weapon's launchPowerup (for bloodstone powerups) +}; + +class idItem : public idEntity { +public: + CLASS_PROTOTYPE( idItem ); + + idItem(); + virtual ~idItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + void GetAttributes( idDict &attributes ) const; + virtual bool GiveToPlayer( idPlayer *player, unsigned int giveFlags ); + virtual bool Pickup( idPlayer *player ); + virtual void Think(); + virtual void Present(); + + enum { + EVENT_PICKUP = idEntity::EVENT_MAXEVENTS, + EVENT_RESPAWN, + EVENT_RESPAWNFX, + EVENT_TAKEFLAG, + EVENT_DROPFLAG, + EVENT_FLAGRETURN, + EVENT_FLAGCAPTURE, + EVENT_MAXEVENTS + }; + + void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void ClientPredictionThink(); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + // networking + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +protected: + int GetPredictPickupMilliseconds() const { return clientPredictPickupMilliseconds; } + +private: + idVec3 orgOrigin; + bool spin; + bool pulse; + bool canPickUp; + + // for item pulse effect + int itemShellHandle; + const idMaterial * shellMaterial; + + // used to update the item pulse effect + mutable bool inView; + mutable int inViewTime; + mutable int lastCycle; + mutable int lastRenderViewTime; + + // used for prediction in mp + int clientPredictPickupMilliseconds; + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + void Event_DropToFloor(); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Trigger( idEntity *activator ); + void Event_Respawn(); + void Event_RespawnFx(); +}; + +class idItemPowerup : public idItem { +public: + CLASS_PROTOTYPE( idItemPowerup ); + + idItemPowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual bool GiveToPlayer( idPlayer *player, unsigned int giveFlags ); + +private: + int time; + int type; +}; + +class idObjective : public idItem { +public: + CLASS_PROTOTYPE( idObjective ); + + idObjective(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + const idMaterial * screenshot; + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); +}; + +class idVideoCDItem : public idItem { +public: + CLASS_PROTOTYPE( idVideoCDItem ); + + virtual bool GiveToPlayer( idPlayer *player, unsigned int giveFlags ); +}; + +class idPDAItem : public idItem { +public: + CLASS_PROTOTYPE( idPDAItem ); + + virtual bool GiveToPlayer( idPlayer *player, unsigned int giveFlags ); +}; + +class idMoveableItem : public idItem { +public: + CLASS_PROTOTYPE( idMoveableItem ); + + idMoveableItem(); + virtual ~idMoveableItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual void Think(); + void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Pickup( idPlayer *player ); + + static void DropItems( idAnimatedEntity *ent, const char *type, idList *list ); + static idEntity * DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +protected: + idPhysics_RigidBody physicsObj; + idClipModel * trigger; + const idDeclParticle * smoke; + int smokeTime; + + int nextSoundTime; + bool repeatSmoke; // never stop updating the particles + + void Gib( const idVec3 &dir, const char *damageDefName ); + + void Event_DropToFloor(); + void Event_Gib( const char *damageDefName ); +}; + +class idItemTeam : public idMoveableItem { +public: + CLASS_PROTOTYPE( idItemTeam ); + + idItemTeam(); + virtual ~idItemTeam(); + + void Spawn(); + virtual bool Pickup( idPlayer *player ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual void Think(void ); + + void Drop( bool death = false ); // was the drop caused by death of carrier? + void Return( idPlayer * player = NULL ); + void Capture(); + + virtual void FreeLightDef(); + virtual void Present(); + + // networking + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +public: + int team; + // TODO : turn this into a state : + bool carried; // is it beeing carried by a player? + bool dropped; // was it dropped? + +private: + idVec3 returnOrigin; + idMat3 returnAxis; + int lastDrop; + + const idDeclSkin * skinDefault; + const idDeclSkin * skinCarried; + + const function_t * scriptTaken; + const function_t * scriptDropped; + const function_t * scriptReturned; + const function_t * scriptCaptured; + + renderLight_t itemGlow; // Used by flags when they are picked up + int itemGlowHandle; + + int lastNuggetDrop; + const char * nuggetName; + +private: + + void Event_TakeFlag( idPlayer * player ); + void Event_DropFlag( bool death ); + void Event_FlagReturn( idPlayer * player = NULL ); + void Event_FlagCapture(); + + void PrivateReturn(); + function_t * LoadScript( char * script ); + + void SpawnNugget( idVec3 pos ); + void UpdateGuis(); +}; + +class idMoveablePDAItem : public idMoveableItem { +public: + CLASS_PROTOTYPE( idMoveablePDAItem ); + + virtual bool GiveToPlayer( idPlayer *player, unsigned int giveFlags ); +}; + +/* +=============================================================================== + + Item removers. + +=============================================================================== +*/ + +class idItemRemover : public idEntity { +public: + CLASS_PROTOTYPE( idItemRemover ); + + void Spawn(); + void RemoveItem( idPlayer *player ); + +private: + void Event_Trigger( idEntity *activator ); +}; + +class idObjectiveComplete : public idItemRemover { +public: + CLASS_PROTOTYPE( idObjectiveComplete ); + + idObjectiveComplete(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); +}; + +#endif /* !__GAME_ITEM_H__ */ diff --git a/neo/d3xp/Leaderboards.cpp b/neo/d3xp/Leaderboards.cpp new file mode 100644 index 00000000..b0ac0950 --- /dev/null +++ b/neo/d3xp/Leaderboards.cpp @@ -0,0 +1,332 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Leaderboards.h" +#include "MultiplayerGame.h" + +/* +================================================================================================ + + Leaderboard stats column definitions - per Game Type. + +================================================================================================ +*/ + + +static columnDef_t public_Deathmatch[] = { + { "Frags", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, + //{ "Deaths", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, + //{ "Wins", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, + //{ "Score", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, +}; + +static columnDef_t public_Tourney[] = { + { "Wins", 64, AGGREGATE_SUM, STATS_COLUMN_DISPLAY_NUMBER }, +}; + +static columnDef_t public_TeamDeathmatch[] = { + { "Wins", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, +}; + +static columnDef_t public_LastmanStanding[] = { + { "Wins", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, +}; + +static columnDef_t public_CaptureTheFlag[] = { + { "Wins", 64, AGGREGATE_MAX, STATS_COLUMN_DISPLAY_NUMBER }, +}; + +// This should match up to the ordering of the gameType_t. ( in MultiplayerGame.h ) +const columnGameMode_t gameMode_columnDefs[] = { + { public_Deathmatch, ARRAY_COUNT( public_Deathmatch ), RANK_GREATEST_FIRST, false, false, "DM" }, // DEATHMATCH + { public_Tourney, ARRAY_COUNT( public_Tourney ), RANK_GREATEST_FIRST, false, false, "TOURNEY" }, // TOURNEY + { public_TeamDeathmatch, ARRAY_COUNT( public_TeamDeathmatch ), RANK_GREATEST_FIRST, false, false, "TDM" }, // TEAM DEATHMATCH + { public_LastmanStanding, ARRAY_COUNT( public_LastmanStanding ), RANK_GREATEST_FIRST, false, false, "LMS" }, // LASTMAN STANDING + { public_CaptureTheFlag, ARRAY_COUNT( public_CaptureTheFlag ), RANK_GREATEST_FIRST, false, false, "CTF" }, // CAPTURE THE FLAG +}; + +/* +===================================== +RetreiveLeaderboardID + +Each map will move in blocks of n*modes. +ex. map 0 will have 0 - 4 Leaderboard id's blocked out. + map 1 will have 5 - 10 leaderboard id's blocked out. + + if gamemode is added it will move in blocks of ARRAY_COUNT( modes ) + +===================================== +*/ +int LeaderboardLocal_GetID( int mapIndex, int gametype ) { + assert( gametype > GAME_RANDOM ); + + return mapIndex * ARRAY_COUNT( gameMode_columnDefs ) + gametype; +} + +/* +===================================== +LeaderboardLocal_Init +===================================== +*/ +void LeaderboardLocal_Init() { + + const idList< mpMap_t > maps = common->GetMapList(); + + const char ** gameModes = NULL; + const char ** gameModesDisplay = NULL; + int numModes = game->GetMPGameModes( &gameModes, &gameModesDisplay ); + + // Iterate through all the available maps, and generate leaderboard Defs, and IDs for each. + for( int mapIdx = 0; mapIdx < maps.Num(); mapIdx++ ) { + + for( int modeIdx = 0; modeIdx < numModes; modeIdx++ ) { + + // Check the supported modes on the map. + if( maps[ mapIdx ].supportedModes & BIT( modeIdx ) ) { + + const columnGameMode_t gamemode = gameMode_columnDefs[ modeIdx ]; + + // Generate a Leaderboard ID for the map/mode + int boardID = LeaderboardLocal_GetID( mapIdx, modeIdx ); + + + // Create and Register the leaderboard with the sys_stats registered Leaderboards + leaderboardDefinition_t * newLeaderboardDef = Sys_CreateLeaderboardDef( boardID, + gamemode.numColumns, + gamemode.columnDef, + gamemode.rankOrder, + gamemode.supportsAttachments, + gamemode.checkAgainstCurrent ); + + + + // Set the leaderboard name. + const char* mapname = idLocalization::GetString( maps[ mapIdx ].mapName ); + newLeaderboardDef->boardName.Format( "%s %s", mapname, gamemode.abrevName ); + + // sanity check. + if( Sys_FindLeaderboardDef( boardID ) != newLeaderboardDef ) { + idLib::Error( "Leaderboards_Init leaderboard creation failed" ); + } + + } + } + } + +} + +/* +===================================== +LeaderboardLocal_Shutdown +===================================== +*/ +void LeaderboardLocal_Shutdown() { + + Sys_DestroyLeaderboardDefs(); +} + +/* +===================================== +LeaderboardLocal_Upload +===================================== +*/ + +const static int FRAG_MULTIPLIER = 100; +const static int DEATH_MULTIPLIER = -50; +const static int WINS_MULTIPLIER = 20; + +void LeaderboardLocal_Upload( lobbyUserID_t lobbyUserID,int gameType, leaderboardStats_t & stats ) { + assert( gameType > GAME_RANDOM ); + + int mapIdx = 0; + + // Need to figure out What stat columns we want to base rank on. ( for now we'll use wins ) + const column_t * gameTypeColumn = NULL; + const column_t defaultStats[] = { stats.wins }; + + // calculate DM score. + int dmScore = stats.frags * FRAG_MULTIPLIER + stats.deaths * DEATH_MULTIPLIER + stats.wins * WINS_MULTIPLIER ; + // TODO: Once leaderboard menu has correct columns- enable this -> const column_t dmStats[] = { stats.frags, stats.deaths, stats.wins, dmScore }; + const column_t dmStats[] = { dmScore }; + + // Calculate TDM score. + int tdmScore = stats.frags * FRAG_MULTIPLIER + stats.teamfrags * FRAG_MULTIPLIER + stats.deaths * DEATH_MULTIPLIER + stats.wins * WINS_MULTIPLIER ; + const column_t tdmStats[] = { tdmScore }; + + // Calculate Tourney score. + int tourneyScore = stats.wins; + const column_t tnyStats[] = { tourneyScore }; + + // Calculate LMS score. + int lmsFrags = stats.frags; + if( lmsFrags < 0 ) { + lmsFrags = 0; // LMS NO LIVES LEFT = -20 on fragCount. + } + + int lmsScore = lmsFrags * FRAG_MULTIPLIER + stats.wins * ( WINS_MULTIPLIER * 10 ) ; + const column_t lmsStats[] = { lmsScore }; + + // Calculate CTF score. + int ctfScore = stats.frags * FRAG_MULTIPLIER + stats.deaths * DEATH_MULTIPLIER + stats.wins * ( WINS_MULTIPLIER * 10 ); + const column_t ctfStats[] = { ctfScore }; + + switch( gameType ) { + case GAME_DM: + gameTypeColumn = dmStats; + break; + case GAME_TDM: + gameTypeColumn = tdmStats; + break; + case GAME_TOURNEY: + gameTypeColumn = tnyStats; + break; + case GAME_LASTMAN: + gameTypeColumn = lmsStats; + break; + case GAME_CTF: { + gameTypeColumn = ctfStats; + break; + } + default: + gameTypeColumn = defaultStats; + } + + const idMatchParameters & matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms(); + const idList< mpMap_t > maps = common->GetMapList(); + + // need to find the map Index number + for( mapIdx = 0; mapIdx < maps.Num(); mapIdx++ ) { + if( matchParameters.mapName.Cmp( maps[ mapIdx ].mapFile ) == 0 ) { + break; + } + } + + int boardID = LeaderboardLocal_GetID( mapIdx, gameType ); + const leaderboardDefinition_t * board = Sys_FindLeaderboardDef( boardID ); + + if( board ) { + session->LeaderboardUpload( lobbyUserID, board, gameTypeColumn ); + } else { + idLib::Warning( "LeaderboardLocal_Upload invalid leaderboard with id of %d", boardID ); + } +} + +class idLeaderboardCallbackTest : public idLeaderboardCallback { + void Call() { + idLib::Printf( "Leaderboard information retrieved in user callback.\n" ); + idLib::Printf( "%d total entries in leaderboard %d.\n", numRowsInLeaderboard, def->id ); + for ( int i = 0; i < rows.Num(); i++ ) { + idLib::Printf( "%d: %s rank:%lld", i, rows[i].name.c_str(), rows[i].rank ); + for ( int j = 0; j < def->numColumns; j++ ) { + idLib::Printf( ", score[%d]: %lld", j, rows[i].columns[j] ); + } + idLib::Printf( "\n" ); + } + } + idLeaderboardCallback * Clone() const { + return new (TAG_PSN) idLeaderboardCallbackTest( *this ); + } +}; + +CONSOLE_COMMAND( testLeaderboardDownload, " ", 0 ) { + idLeaderboardCallbackTest leaderboardCallbackTest; + + int leaderboardID = 0; + int start = 1; + int end = 100; + + if ( args.Argc() > 1 ) { + leaderboardID = atoi( args.Argv( 1 ) ); + } + + if ( args.Argc() > 2 ) { + start = atoi( args.Argv( 2 ) ); + } + + if ( args.Argc() > 3 ) { + end = atoi( args.Argv( 3 ) ); + } + + const leaderboardDefinition_t * leaderboardDef = Sys_FindLeaderboardDef( leaderboardID ); + + if( leaderboardDef ) { + session->LeaderboardDownload( 0, leaderboardDef, start, end, leaderboardCallbackTest ); + } else { + idLib::Warning( "Sys_FindLeaderboardDef() Unable to find board %d\n", leaderboardID ); + } + +} + +CONSOLE_COMMAND( testLeaderboardUpload, " ", 0 ) { + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + lobbyUserID_t user = lobby.GetLobbyUserIdByOrdinal( 0 ); + + gameType_t gameType = GAME_DM; + int frags = 0; + int wins = 1; + + if ( args.Argc() > 1 ) { + gameType = static_cast( atoi( args.Argv( 1 ) ) ); + } + + if ( args.Argc() > 2 ) { + frags = atoi( args.Argv( 2 ) ); + } + + if ( args.Argc() > 3 ) { + wins = atoi( args.Argv( 3 ) ); + } + + leaderboardStats_t stats = { frags, wins, 0, 0 }; + + LeaderboardLocal_Upload( user, gameType , stats ); + + session->LeaderboardFlush(); + +} + + + +CONSOLE_COMMAND( testLeaderboardUpload_SendToClients, " ", 0 ) { + + for( int playerIdx = 0; playerIdx < gameLocal.numClients; playerIdx++ ) { + + leaderboardStats_t stats = { 1, 1, 1, 1 }; + + LeaderboardLocal_Upload( gameLocal.lobbyUserIDs[ playerIdx ], gameLocal.gameType, stats ); + } + + // Dont do this more than once. + session->LeaderboardFlush(); +} + diff --git a/neo/d3xp/Leaderboards.h b/neo/d3xp/Leaderboards.h new file mode 100644 index 00000000..e8e0cfa8 --- /dev/null +++ b/neo/d3xp/Leaderboards.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LEADERBOARDS_LOCAL_H__ +#define __LEADERBOARDS_LOCAL_H__ + + +struct leaderboardStats_t { + int frags; + int wins; + int teamfrags; + int deaths; +}; + +struct columnGameMode_t { + + columnDef_t * columnDef; // The Column definition for the game mode. + int numColumns; + rankOrder_t rankOrder; // rank ordering of the game mode. ( RANK_GREATEST_FIRST, RANK_LEAST_FIRST ) + bool supportsAttachments; + bool checkAgainstCurrent; + const char * abrevName; // Leaderboard Game Mode Abrev. +}; + +/* +================================================================================================ + + Leaderboards + +================================================================================================ +*/ + + + +// creates and stores all the leaderboards inside the internal map ( see Sys_FindLeaderboardDef on retrieving definition ) +void LeaderboardLocal_Init(); + +// Destroys any leaderboard definitions allocated by LeaderboardLocal_Init() +void LeaderboardLocal_Shutdown(); + +// Gets a leaderboard ID with map index and game type. +int LeaderboardLocal_GetID( int mapIndex, int gametype ); + +// Do it all function. Will create the column_t with the correct stats from the game type, and upload it to the leaderboard system. +void LeaderboardLocal_Upload( lobbyUserID_t lobbyUserID, int gameType, leaderboardStats_t & stats ); + +extern const columnGameMode_t gameMode_columnDefs[]; + +#endif // __LEADERBOARDS_LOCAL_H__ diff --git a/neo/d3xp/Light.cpp b/neo/d3xp/Light.cpp new file mode 100644 index 00000000..7ad4391b --- /dev/null +++ b/neo/d3xp/Light.cpp @@ -0,0 +1,1182 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idLight + +=============================================================================== +*/ + +const idEventDef EV_Light_SetShader( "setShader", "s" ); +const idEventDef EV_Light_GetLightParm( "getLightParm", "d", 'f' ); +const idEventDef EV_Light_SetLightParm( "setLightParm", "df" ); +const idEventDef EV_Light_SetLightParms( "setLightParms", "ffff" ); +const idEventDef EV_Light_SetRadiusXYZ( "setRadiusXYZ", "fff" ); +const idEventDef EV_Light_SetRadius( "setRadius", "f" ); +const idEventDef EV_Light_On( "On", NULL ); +const idEventDef EV_Light_Off( "Off", NULL ); +const idEventDef EV_Light_FadeOut( "fadeOutLight", "f" ); +const idEventDef EV_Light_FadeIn( "fadeInLight", "f" ); + +CLASS_DECLARATION( idEntity, idLight ) + EVENT( EV_Light_SetShader, idLight::Event_SetShader ) + EVENT( EV_Light_GetLightParm, idLight::Event_GetLightParm ) + EVENT( EV_Light_SetLightParm, idLight::Event_SetLightParm ) + EVENT( EV_Light_SetLightParms, idLight::Event_SetLightParms ) + EVENT( EV_Light_SetRadiusXYZ, idLight::Event_SetRadiusXYZ ) + EVENT( EV_Light_SetRadius, idLight::Event_SetRadius ) + EVENT( EV_Hide, idLight::Event_Hide ) + EVENT( EV_Show, idLight::Event_Show ) + EVENT( EV_Light_On, idLight::Event_On ) + EVENT( EV_Light_Off, idLight::Event_Off ) + EVENT( EV_Activate, idLight::Event_ToggleOnOff ) + EVENT( EV_PostSpawn, idLight::Event_SetSoundHandles ) + EVENT( EV_Light_FadeOut, idLight::Event_FadeOut ) + EVENT( EV_Light_FadeIn, idLight::Event_FadeIn ) +END_CLASS + + +/* +================ +idGameEdit::ParseSpawnArgsToRenderLight + +parse the light parameters +this is the canonical renderLight parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ) { + bool gotTarget, gotUp, gotRight; + const char *texture; + idVec3 color; + + memset( renderLight, 0, sizeof( *renderLight ) ); + + if (!args->GetVector("light_origin", "", renderLight->origin)) { + args->GetVector( "origin", "", renderLight->origin ); + } + + gotTarget = args->GetVector( "light_target", "", renderLight->target ); + gotUp = args->GetVector( "light_up", "", renderLight->up ); + gotRight = args->GetVector( "light_right", "", renderLight->right ); + args->GetVector( "light_start", "0 0 0", renderLight->start ); + if ( !args->GetVector( "light_end", "", renderLight->end ) ) { + renderLight->end = renderLight->target; + } + + // we should have all of the target/right/up or none of them + if ( ( gotTarget || gotUp || gotRight ) != ( gotTarget && gotUp && gotRight ) ) { + gameLocal.Printf( "Light at (%f,%f,%f) has bad target info\n", + renderLight->origin[0], renderLight->origin[1], renderLight->origin[2] ); + return; + } + + if ( !gotTarget ) { + renderLight->pointLight = true; + + // allow an optional relative center of light and shadow offset + args->GetVector( "light_center", "0 0 0", renderLight->lightCenter ); + + // create a point light + if (!args->GetVector( "light_radius", "300 300 300", renderLight->lightRadius ) ) { + float radius; + + args->GetFloat( "light", "300", radius ); + renderLight->lightRadius[0] = renderLight->lightRadius[1] = renderLight->lightRadius[2] = radius; + } + } + + // get the rotation matrix in either full form, or single angle form + idAngles angles; + idMat3 mat; + if ( !args->GetMatrix( "light_rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + args->GetFloat( "angle", "0", angles[ 1 ] ); + angles[ 0 ] = 0; + angles[ 1 ] = idMath::AngleNormalize360( angles[ 1 ] ); + angles[ 2 ] = 0; + mat = angles.ToMat3(); + } + } + + // fix degenerate identity matrices + mat[0].FixDegenerateNormal(); + mat[1].FixDegenerateNormal(); + mat[2].FixDegenerateNormal(); + + renderLight->axis = mat; + + // check for other attributes + args->GetVector( "_color", "1 1 1", color ); + renderLight->shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight->shaderParms[ SHADERPARM_BLUE ] = color[2]; + args->GetFloat( "shaderParm3", "1", renderLight->shaderParms[ SHADERPARM_TIMESCALE ] ); + if ( !args->GetFloat( "shaderParm4", "0", renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] ) ) { + // offset the start time of the shader to sync it to the game time + renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + + args->GetFloat( "shaderParm5", "0", renderLight->shaderParms[5] ); + args->GetFloat( "shaderParm6", "0", renderLight->shaderParms[6] ); + args->GetFloat( "shaderParm7", "0", renderLight->shaderParms[ SHADERPARM_MODE ] ); + args->GetBool( "noshadows", "0", renderLight->noShadows ); + args->GetBool( "nospecular", "0", renderLight->noSpecular ); + args->GetBool( "parallel", "0", renderLight->parallel ); + + args->GetString( "texture", "lights/squarelight1", &texture ); + // allow this to be NULL + renderLight->shader = declManager->FindMaterial( texture, false ); +} + +/* +================ +idLight::UpdateChangeableSpawnArgs +================ +*/ +void idLight::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + source->Print(); + } + FreeSoundEmitter( true ); + gameEdit->ParseSpawnArgsToRefSound( source ? source : &spawnArgs, &refSound ); + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + gameEdit->ParseSpawnArgsToRenderLight( source ? source : &spawnArgs, &renderLight ); + + UpdateVisuals(); +} + +/* +================ +idLight::idLight +================ +*/ +idLight::idLight(): + previousBaseColor( vec3_zero ) , + nextBaseColor( vec3_zero ) { + memset( &renderLight, 0, sizeof( renderLight ) ); + localLightOrigin = vec3_zero; + localLightAxis = mat3_identity; + lightDefHandle = -1; + levels = 0; + currentLevel = 0; + baseColor = vec3_zero; + breakOnTrigger = false; + count = 0; + triggercount = 0; + lightParent = NULL; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + soundWasPlaying = false; +} + +/* +================ +idLight::~idLight +================ +*/ +idLight::~idLight() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + } +} + +/* +================ +idLight::Save + +archives object for save game file +================ +*/ +void idLight::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( renderLight ); + + savefile->WriteBool( renderLight.prelightModel != NULL ); + + savefile->WriteVec3( localLightOrigin ); + savefile->WriteMat3( localLightAxis ); + + savefile->WriteString( brokenModel ); + savefile->WriteInt( levels ); + savefile->WriteInt( currentLevel ); + + savefile->WriteVec3( baseColor ); + savefile->WriteBool( breakOnTrigger ); + savefile->WriteInt( count ); + savefile->WriteInt( triggercount ); + savefile->WriteObject( lightParent ); + + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + savefile->WriteBool( soundWasPlaying ); +} + +/* +================ +idLight::Restore + +unarchives object from save game file +================ +*/ +void idLight::Restore( idRestoreGame *savefile ) { + bool hadPrelightModel; + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadBool( hadPrelightModel ); + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + if ( ( renderLight.prelightModel == NULL ) && hadPrelightModel ) { + assert( 0 ); + if ( developer.GetBool() ) { + // we really want to know if this happens + gameLocal.Error( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } else { + // but let it slide after release + gameLocal.Warning( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } + } + + savefile->ReadVec3( localLightOrigin ); + savefile->ReadMat3( localLightAxis ); + + savefile->ReadString( brokenModel ); + savefile->ReadInt( levels ); + savefile->ReadInt( currentLevel ); + + savefile->ReadVec3( baseColor ); + savefile->ReadBool( breakOnTrigger ); + savefile->ReadInt( count ); + savefile->ReadInt( triggercount ); + savefile->ReadObject( reinterpret_cast( lightParent ) ); + + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); + savefile->ReadBool( soundWasPlaying ); + + lightDefHandle = -1; + + SetLightLevel(); +} + +/* +================ +idLight::Spawn +================ +*/ +void idLight::Spawn() { + bool start_off; + bool needBroken; + const char *demonic_shader; + + // do the parsing the same way dmap and the editor do + gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &renderLight ); + + // we need the origin and axis relative to the physics origin/axis + localLightOrigin = ( renderLight.origin - GetPhysics()->GetOrigin() ) * GetPhysics()->GetAxis().Transpose(); + localLightAxis = renderLight.axis * GetPhysics()->GetAxis().Transpose(); + + // set the base color from the shader parms + baseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + previousBaseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + nextBaseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + + // set the number of light levels + spawnArgs.GetInt( "levels", "1", levels ); + currentLevel = levels; + if ( levels <= 0 ) { + gameLocal.Error( "Invalid light level set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + // make sure the demonic shader is cached + if ( spawnArgs.GetString( "mat_demonic", NULL, &demonic_shader ) ) { + declManager->FindType( DECL_MATERIAL, demonic_shader ); + } + + // game specific functionality, not mirrored in + // editor or dmap light parsing + + // also put the light texture on the model, so light flares + // can get the current intensity of the light + renderEntity.referenceShader = renderLight.shader; + + lightDefHandle = -1; // no static version yet + + // see if an optimized shadow volume exists + // the renderer will ignore this value after a light has been moved, + // but there may still be a chance to get it wrong if the game moves + // a light before the first present, and doesn't clear the prelight + renderLight.prelightModel = 0; + if ( name[ 0 ] ) { + // this will return 0 if not found + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + } + + spawnArgs.GetBool( "start_off", "0", start_off ); + if ( start_off ) { + Off(); + } + + // Midnight CTF + if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") && !spawnArgs.GetBool("midnight_override") ) { + Off(); + } + + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + spawnArgs.GetBool( "break", "0", breakOnTrigger ); + spawnArgs.GetInt( "count", "1", count ); + + triggercount = 0; + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // if we have a health make light breakable + if ( health ) { + idStr model = spawnArgs.GetString( "model" ); // get the visual model + if ( !model.Length() ) { + gameLocal.Error( "Breakable light without a model set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + fl.takedamage = true; + + // see if we need to create a broken model name + needBroken = true; + if ( model.Length() && !brokenModel.Length() ) { + int pos; + + needBroken = false; + + pos = model.Find( "." ); + if ( pos < 0 ) { + pos = model.Length(); + } + if ( pos > 0 ) { + model.Left( pos, brokenModel ); + } + brokenModel += "_broken"; + if ( pos > 0 ) { + brokenModel += &model[ pos ]; + } + } + + // make sure the model gets cached + if ( !renderModelManager->CheckModel( brokenModel ) ) { + if ( needBroken ) { + gameLocal.Error( "Model '%s' not found for entity %d(%s)", brokenModel.c_str(), entityNumber, name.c_str() ); + } else { + brokenModel = ""; + } + } + + GetPhysics()->SetContents( spawnArgs.GetBool( "nonsolid" ) ? 0 : CONTENTS_SOLID ); + + // make sure the collision model gets cached + idClipModel::CheckModel( brokenModel ); + } + + PostEventMS( &EV_PostSpawn, 0 ); + + UpdateVisuals(); +} + +/* +================ +idLight::SetLightLevel +================ +*/ +void idLight::SetLightLevel() { + idVec3 color; + float intensity; + + intensity = ( float )currentLevel / ( float )levels; + color = baseColor * intensity; + renderLight.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec3 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec4 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderLight.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( float red, float green, float blue ) { + baseColor.Set( red, green, blue ); + SetLightLevel(); +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( const idVec4 &color ) { + baseColor = color.ToVec3(); + renderLight.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + SetLightLevel(); +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( const idVec3 &color ) { + baseColor = color; + SetLightLevel(); +} + +/* +================ +idLight::SetShader +================ +*/ +void idLight::SetShader( const char *shadername ) { + // allow this to be NULL + renderLight.shader = declManager->FindMaterial( shadername, false ); + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParm +================ +*/ +void idLight::SetLightParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + return; + } + + renderLight.shaderParms[ parmnum ] = value; + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParms +================ +*/ +void idLight::SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + renderLight.shaderParms[ SHADERPARM_RED ] = parm0; + renderLight.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderLight.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderLight.shaderParms[ SHADERPARM_ALPHA ] = parm3; + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::SetRadiusXYZ +================ +*/ +void idLight::SetRadiusXYZ( float x, float y, float z ) { + renderLight.lightRadius[0] = x; + renderLight.lightRadius[1] = y; + renderLight.lightRadius[2] = z; + PresentLightDefChange(); +} + +/* +================ +idLight::SetRadius +================ +*/ +void idLight::SetRadius( float radius ) { + renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = radius; + PresentLightDefChange(); +} + +/* +================ +idLight::On +================ +*/ +void idLight::On() { + currentLevel = levels; + // offset the start time of the shader to sync it to the game time + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + if ( ( soundWasPlaying || refSound.waitfortrigger ) && refSound.shader ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Off +================ +*/ +void idLight::Off() { + currentLevel = 0; + // kill any sound it was making + if ( refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying() ) { + StopSound( SND_CHANNEL_ANY, false ); + soundWasPlaying = true; + } + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Fade +================ +*/ +void idLight::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idLight::FadeOut +================ +*/ +void idLight::FadeOut( float time ) { + Fade( colorBlack, time ); +} + +/* +================ +idLight::FadeIn +================ +*/ +void idLight::FadeIn( float time ) { + idVec3 color; + idVec4 color4; + + currentLevel = levels; + spawnArgs.GetVector( "_color", "1 1 1", color ); + color4.Set( color.x, color.y, color.z, 1.0f ); + Fade( color4, time ); +} + +/* +================ +idLight::Killed +================ +*/ +void idLight::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + BecomeBroken( attacker ); +} + +/* +================ +idLight::BecomeBroken +================ +*/ +void idLight::BecomeBroken( idEntity *activator ) { + const char *damageDefName; + + fl.takedamage = false; + + if ( brokenModel.Length() ) { + SetModel( brokenModel ); + + if ( !spawnArgs.GetBool( "nonsolid" ) ) { + GetPhysics()->SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( brokenModel.c_str() ), 1.0f ); + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + } else if ( spawnArgs.GetBool( "hideModelOnBreak" ) ) { + SetModel( "" ); + GetPhysics()->SetContents( 0 ); + } + + if ( common->IsServer() ) { + + ServerSendEvent( EVENT_BECOMEBROKEN, NULL, true ); + + if ( spawnArgs.GetString( "def_damage", "", &damageDefName ) ) { + idVec3 origin = renderEntity.origin + renderEntity.bounds.GetCenter() * renderEntity.axis; + gameLocal.RadiusDamage( origin, activator, activator, this, this, damageDefName ); + } + + } + + ActivateTargets( activator ); + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + renderLight.shaderParms[ SHADERPARM_MODE ] = 1; + + // if the light has a sound, either start the alternate (broken) sound, or stop the sound + const char *parm = spawnArgs.GetString( "snd_broken" ); + if ( refSound.shader || ( parm != NULL && *parm != NULL ) ) { + StopSound( SND_CHANNEL_ANY, false ); + const idSoundShader *alternate = refSound.shader ? refSound.shader->GetAltSound() : declManager->FindSound( parm ); + if ( alternate ) { + // start it with no diversity, so the leadin break sound plays + refSound.referenceSound->StartSound( alternate, SND_CHANNEL_ANY, 0.0, 0 ); + } + } + + parm = spawnArgs.GetString( "mtr_broken" ); + if ( parm != NULL && *parm != NULL ) { + SetShader( parm ); + } + + UpdateVisuals(); +} + +/* +================ +idLight::PresentLightDefChange +================ +*/ +void idLight::PresentLightDefChange() { + // let the renderer apply it to the world + if ( ( lightDefHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } +} + +/* +================ +idLight::PresentModelDefChange +================ +*/ +void idLight::PresentModelDefChange() { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idLight::Present +================ +*/ +void idLight::Present() { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + idEntity::Present(); + + // current transformation + renderLight.axis = localLightAxis * GetPhysics()->GetAxis(); + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * localLightOrigin; + + // reference the sound for shader synced effects + if ( lightParent ) { + renderLight.referenceSound = lightParent->GetSoundEmitter(); + renderEntity.referenceSound = lightParent->GetSoundEmitter(); + } + else { + renderLight.referenceSound = refSound.referenceSound; + renderEntity.referenceSound = refSound.referenceSound; + } + + // update the renderLight and renderEntity to render the light and flare + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::Think +================ +*/ +void idLight::Think() { + idVec4 color; + + if ( thinkFlags & TH_THINK ) { + if ( fadeEnd > 0 ) { + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + } + + RunPhysics(); + Present(); +} + +/* +================ +idLight::ClientThink +================ +*/ +void idLight::ClientThink( const int curTime, const float fraction, const bool predict ) { + + InterpolatePhysics( fraction ); + + if( baseColor != nextBaseColor ) { + baseColor = Lerp( previousBaseColor, nextBaseColor, fraction ); + SetColor( baseColor ); + BecomeActive( TH_UPDATEVISUALS ); + } + + Present(); +} + +/* +================ +idLight::GetPhysicsToSoundTransform +================ +*/ +bool idLight::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = localLightOrigin + renderLight.lightCenter; + axis = localLightAxis * GetPhysics()->GetAxis(); + return true; +} + +/* +================ +idLight::FreeLightDef +================ +*/ +void idLight::FreeLightDef() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================ +idLight::SaveState +================ +*/ +void idLight::SaveState( idDict *args ) { + int i, c = spawnArgs.GetNumKeyVals(); + for ( i = 0; i < c; i++ ) { + const idKeyValue *pv = spawnArgs.GetKeyVal(i); + if ( pv->GetKey().Find( "editor_", false ) >= 0 || pv->GetKey().Find( "parse_", false ) >= 0 ) { + continue; + } + args->Set( pv->GetKey(), pv->GetValue() ); + } +} + +/* +=============== +idLight::ShowEditingDialog +=============== +*/ +void idLight::ShowEditingDialog() { +} + +/* +================ +idLight::Event_SetShader +================ +*/ +void idLight::Event_SetShader( const char *shadername ) { + SetShader( shadername ); +} + +/* +================ +idLight::Event_GetLightParm +================ +*/ +void idLight::Event_GetLightParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + return; + } + + idThread::ReturnFloat( renderLight.shaderParms[ parmnum ] ); +} + +/* +================ +idLight::Event_SetLightParm +================ +*/ +void idLight::Event_SetLightParm( int parmnum, float value ) { + SetLightParm( parmnum, value ); +} + +/* +================ +idLight::Event_SetLightParms +================ +*/ +void idLight::Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + SetLightParms( parm0, parm1, parm2, parm3 ); +} + +/* +================ +idLight::Event_SetRadiusXYZ +================ +*/ +void idLight::Event_SetRadiusXYZ( float x, float y, float z ) { + SetRadiusXYZ( x, y, z ); +} + +/* +================ +idLight::Event_SetRadius +================ +*/ +void idLight::Event_SetRadius( float radius ) { + SetRadius( radius ); +} + +/* +================ +idLight::Event_Hide +================ +*/ +void idLight::Event_Hide() { + Hide(); + PresentModelDefChange(); + Off(); +} + +/* +================ +idLight::Event_Show +================ +*/ +void idLight::Event_Show() { + Show(); + PresentModelDefChange(); + On(); +} + +/* +================ +idLight::Event_On +================ +*/ +void idLight::Event_On() { + On(); +} + +/* +================ +idLight::Event_Off +================ +*/ +void idLight::Event_Off() { + Off(); +} + +/* +================ +idLight::Event_ToggleOnOff +================ +*/ +void idLight::Event_ToggleOnOff( idEntity *activator ) { + triggercount++; + if ( triggercount < count ) { + return; + } + + // reset trigger count + triggercount = 0; + + if ( breakOnTrigger ) { + BecomeBroken( activator ); + breakOnTrigger = false; + return; + } + + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } +} + +/* +================ +idLight::Event_SetSoundHandles + + set the same sound def handle on all targeted lights +================ +*/ +void idLight::Event_SetSoundHandles() { + int i; + idEntity *targetEnt; + + if ( !refSound.referenceSound ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt != NULL && targetEnt->IsType( idLight::Type ) ) { + idLight *light = static_cast(targetEnt); + light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->renderEntity.referenceSound = renderEntity.referenceSound; + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } + } +} + +/* +================ +idLight::Event_FadeOut +================ +*/ +void idLight::Event_FadeOut( float time ) { + FadeOut( time ); +} + +/* +================ +idLight::Event_FadeIn +================ +*/ +void idLight::Event_FadeIn( float time ) { + FadeIn( time ); +} + +/* +================ +idLight::ClientPredictionThink +================ +*/ +void idLight::ClientPredictionThink() { + Think(); +} + +/* +================ +idLight::WriteToSnapshot +================ +*/ +void idLight::WriteToSnapshot( idBitMsg &msg ) const { + + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + + msg.WriteByte( currentLevel ); + msg.WriteLong( PackColor( baseColor ) ); + // msg.WriteBits( lightParent.GetEntityNum(), GENTITYNUM_BITS ); + +/* // only helps prediction + msg.WriteLong( PackColor( fadeFrom ) ); + msg.WriteLong( PackColor( fadeTo ) ); + msg.WriteLong( fadeStart ); + msg.WriteLong( fadeEnd ); +*/ + + // FIXME: send renderLight.shader + msg.WriteFloat( renderLight.lightRadius[0], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[1], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[2], 5, 10 ); + + msg.WriteLong( PackColor( idVec4( renderLight.shaderParms[SHADERPARM_RED], + renderLight.shaderParms[SHADERPARM_GREEN], + renderLight.shaderParms[SHADERPARM_BLUE], + renderLight.shaderParms[SHADERPARM_ALPHA] ) ) ); + + msg.WriteFloat( renderLight.shaderParms[SHADERPARM_TIMESCALE], 5, 10 ); + msg.WriteLong( renderLight.shaderParms[SHADERPARM_TIMEOFFSET] ); + //msg.WriteByte( renderLight.shaderParms[SHADERPARM_DIVERSITY] ); + msg.WriteShort( renderLight.shaderParms[SHADERPARM_MODE] ); + + WriteColorToSnapshot( msg ); +} + +/* +================ +idLight::ReadFromSnapshot +================ +*/ +void idLight::ReadFromSnapshot( const idBitMsg &msg ) { + idVec4 shaderColor; + int oldCurrentLevel = currentLevel; + idVec3 oldBaseColor = baseColor; + + previousBaseColor = nextBaseColor; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + currentLevel = msg.ReadByte(); + if ( currentLevel != oldCurrentLevel ) { + // need to call On/Off for flickering lights to start/stop the sound + // while doing it this way rather than through events, the flickering is out of sync between clients + // but at least there is no question about saving the event and having them happening globally in the world + if ( currentLevel ) { + On(); + } else { + Off(); + } + } + + UnpackColor( msg.ReadLong(), nextBaseColor ); + + // lightParentEntityNum = msg.ReadBits( GENTITYNUM_BITS ); + +/* // only helps prediction + UnpackColor( msg.ReadLong(), fadeFrom ); + UnpackColor( msg.ReadLong(), fadeTo ); + fadeStart = msg.ReadLong(); + fadeEnd = msg.ReadLong(); +*/ + + // FIXME: read renderLight.shader + renderLight.lightRadius[0] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[1] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[2] = msg.ReadFloat( 5, 10 ); + + UnpackColor( msg.ReadLong(), shaderColor ); + renderLight.shaderParms[SHADERPARM_RED] = shaderColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = shaderColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = shaderColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = shaderColor[3]; + + renderLight.shaderParms[SHADERPARM_TIMESCALE] = msg.ReadFloat( 5, 10 ); + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadLong(); + //renderLight.shaderParms[SHADERPARM_DIVERSITY] = msg.ReadFloat(); + renderLight.shaderParms[SHADERPARM_MODE] = msg.ReadShort(); + + ReadColorFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( ( currentLevel != oldCurrentLevel ) || ( previousBaseColor != nextBaseColor ) ) { + SetLightLevel(); + } else { + PresentLightDefChange(); + PresentModelDefChange(); + } + } +} + +/* +================ +idLight::ClientReceiveEvent +================ +*/ +bool idLight::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_BECOMEBROKEN: { + BecomeBroken( NULL ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} diff --git a/neo/d3xp/Light.h b/neo/d3xp/Light.h new file mode 100644 index 00000000..be2f97d6 --- /dev/null +++ b/neo/d3xp/Light.h @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_LIGHT_H__ +#define __GAME_LIGHT_H__ + +/* +=============================================================================== + + Generic light. + +=============================================================================== +*/ + +extern const idEventDef EV_Light_GetLightParm; +extern const idEventDef EV_Light_SetLightParm; +extern const idEventDef EV_Light_SetLightParms; + +class idLight : public idEntity { +public: + CLASS_PROTOTYPE( idLight ); + + idLight(); + ~idLight(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + virtual void Think(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void FreeLightDef(); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + void Present(); + + void SaveState( idDict *args ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec4 &color ); + void SetColor( const idVec3 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void GetColor( idVec4 &out ) const; + const idVec3 & GetBaseColor() const { return baseColor; } + void SetShader( const char *shadername ); + void SetLightParm( int parmnum, float value ); + void SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void SetRadiusXYZ( float x, float y, float z ); + void SetRadius( float radius ); + void On(); + void Off(); + void Fade( const idVec4 &to, float fadeTime ); + void FadeOut( float time ); + void FadeIn( float time ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void BecomeBroken( idEntity *activator ); + qhandle_t GetLightDefHandle() const { return lightDefHandle; } + void SetLightParent( idEntity *lparent ) { lightParent = lparent; } + void SetLightLevel(); + + virtual void ShowEditingDialog(); + + enum { + EVENT_BECOMEBROKEN = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink(); + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + renderLight_t renderLight; // light presented to the renderer + idVec3 localLightOrigin; // light origin relative to the physics origin + idMat3 localLightAxis; // light axis relative to physics axis + qhandle_t lightDefHandle; // handle to renderer light def + idStr brokenModel; + int levels; + int currentLevel; + idVec3 baseColor; + + // Colors used for client-side interpolation. + idVec3 previousBaseColor; + idVec3 nextBaseColor; + + bool breakOnTrigger; + int count; + int triggercount; + idEntity * lightParent; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; + bool soundWasPlaying; + +private: + void PresentLightDefChange(); + void PresentModelDefChange(); + + void Event_SetShader( const char *shadername ); + void Event_GetLightParm( int parmnum ); + void Event_SetLightParm( int parmnum, float value ); + void Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetRadiusXYZ( float x, float y, float z ); + void Event_SetRadius( float radius ); + void Event_Hide(); + void Event_Show(); + void Event_On(); + void Event_Off(); + void Event_ToggleOnOff( idEntity *activator ); + void Event_SetSoundHandles(); + void Event_FadeOut( float time ); + void Event_FadeIn( float time ); +}; + +#endif /* !__GAME_LIGHT_H__ */ diff --git a/neo/d3xp/Misc.cpp b/neo/d3xp/Misc.cpp new file mode 100644 index 00000000..bdce6a2d --- /dev/null +++ b/neo/d3xp/Misc.cpp @@ -0,0 +1,3934 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* + +Various utility objects and functions. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpawnableEntity ) +END_CLASS + +/* +====================== +idSpawnableEntity::Spawn +====================== +*/ +void idSpawnableEntity::Spawn() { + // this just holds dict information +} + +/* +=============================================================================== + + idPlayerStart + +=============================================================================== +*/ + +const idEventDef EV_TeleportStage( "", "e" ); + +CLASS_DECLARATION( idEntity, idPlayerStart ) + EVENT( EV_Activate, idPlayerStart::Event_TeleportPlayer ) + EVENT( EV_TeleportStage, idPlayerStart::Event_TeleportStage ) +END_CLASS + +/* +=============== +idPlayerStart::idPlayerStart +================ +*/ +idPlayerStart::idPlayerStart() { + teleportStage = 0; +} + +/* +=============== +idPlayerStart::Spawn +================ +*/ +void idPlayerStart::Spawn() { + teleportStage = 0; +} + +/* +================ +idPlayerStart::Save +================ +*/ +void idPlayerStart::Save( idSaveGame *savefile ) const { + savefile->WriteInt( teleportStage ); +} + +/* +================ +idPlayerStart::Restore +================ +*/ +void idPlayerStart::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( teleportStage ); +} + +/* +================ +idPlayerStart::ClientReceiveEvent +================ +*/ +bool idPlayerStart::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int entityNumber; + + switch( event ) { + case EVENT_TELEPORTPLAYER: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); + if ( player != NULL && player->IsType( idPlayer::Type ) ) { + Event_TeleportPlayer( player ); + } + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} + +/* +=============== +idPlayerStart::Event_TeleportStage + +FIXME: add functionality to fx system ( could be done with player scripting too ) +================ +*/ +void idPlayerStart::Event_TeleportStage( idEntity *_player ) { + idPlayer *player; + if ( !_player->IsType( idPlayer::Type ) ) { + common->Warning( "idPlayerStart::Event_TeleportStage: entity is not an idPlayer\n" ); + return; + } + player = static_cast(_player); + float teleportDelay = spawnArgs.GetFloat( "teleportDelay" ); + switch ( teleportStage ) { + case 0: + player->playerView.Flash( colorWhite, 125 ); + player->SetInfluenceLevel( INFLUENCE_LEVEL3 ); + player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL ); + gameSoundWorld->FadeSoundClasses( 0, -20.0f, teleportDelay ); + player->StartSound( "snd_teleport_start", SND_CHANNEL_BODY2, 0, false, NULL ); + teleportStage++; + PostEventSec( &EV_TeleportStage, teleportDelay, player ); + break; + case 1: + gameSoundWorld->FadeSoundClasses( 0, 0.0f, 0.25f ); + teleportStage++; + PostEventSec( &EV_TeleportStage, 0.25f, player ); + break; + case 2: + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + TeleportPlayer( player ); + player->StopSound( SND_CHANNEL_BODY2, false ); + player->SetInfluenceLevel( INFLUENCE_NONE ); + teleportStage = 0; + break; + default: + break; + } +} + +/* +=============== +idPlayerStart::TeleportPlayer +================ +*/ +void idPlayerStart::TeleportPlayer( idPlayer *player ) { + float pushVel = spawnArgs.GetFloat( "push", "300" ); + float f = spawnArgs.GetFloat( "visualEffect", "0" ); + const char *viewName = spawnArgs.GetString( "visualView", "" ); + idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL; + + SetTimeState ts( player->timeGroup ); + + if ( f && ent != NULL ) { + // place in private camera view for some time + // the entity needs to teleport to where the camera view is to have the PVS right + player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this ); + player->StartSound( "snd_teleport_enter", SND_CHANNEL_ANY, 0, false, NULL ); + player->SetPrivateCameraView( static_cast(ent) ); + // the player entity knows where to spawn from the previous Teleport call + if ( !common->IsClient() ) { + player->PostEventSec( &EV_Player_ExitTeleporter, f ); + } + } else { + // direct to exit, Teleport will take care of the killbox + player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL ); + + // multiplayer hijacked this entity, so only push the player in multiplayer + if ( common->IsMultiplayer() ) { + player->GetPhysics()->SetLinearVelocity( GetPhysics()->GetAxis()[0] * pushVel ); + } + } +} + +/* +=============== +idPlayerStart::Event_TeleportPlayer +================ +*/ +void idPlayerStart::Event_TeleportPlayer( idEntity *activator ) { + idPlayer *player; + + if ( activator->IsType( idPlayer::Type ) ) { + player = static_cast( activator ); + } else { + player = gameLocal.GetLocalPlayer(); + } + if ( player ) { + if ( spawnArgs.GetBool( "visualFx" ) ) { + + teleportStage = 0; + Event_TeleportStage( player ); + + } else { + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( player->entityNumber, GENTITYNUM_BITS ); + ServerSendEvent( EVENT_TELEPORTPLAYER, &msg, false ); + } + + TeleportPlayer( player ); + } + } +} + +/* +=============================================================================== + + idActivator + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idActivator ) + EVENT( EV_Activate, idActivator::Event_Activate ) +END_CLASS + +/* +=============== +idActivator::Save +================ +*/ +void idActivator::Save( idSaveGame *savefile ) const { + savefile->WriteBool( stay_on ); +} + +/* +=============== +idActivator::Restore +================ +*/ +void idActivator::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( stay_on ); + + if ( stay_on ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Spawn +================ +*/ +void idActivator::Spawn() { + bool start_off; + + spawnArgs.GetBool( "stay_on", "0", stay_on ); + spawnArgs.GetBool( "start_off", "0", start_off ); + + GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f ); + GetPhysics()->SetContents( 0 ); + + if ( !start_off ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Think +================ +*/ +void idActivator::Think() { + RunPhysics(); + if ( thinkFlags & TH_THINK ) { + if ( TouchTriggers() ) { + if ( !stay_on ) { + BecomeInactive( TH_THINK ); + } + } + } + Present(); +} + +/* +=============== +idActivator::Activate +================ +*/ +void idActivator::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + + +/* +=============================================================================== + +idPathCorner + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPathCorner ) + EVENT( AI_RandomPath, idPathCorner::Event_RandomPath ) +END_CLASS + +/* +===================== +idPathCorner::Spawn +===================== +*/ +void idPathCorner::Spawn() { +} + +/* +===================== +idPathCorner::DrawDebugInfo +===================== +*/ +void idPathCorner::DrawDebugInfo() { + idEntity *ent; + idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) ); + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idPathCorner::Type ) ) { + continue; + } + + idVec3 org = ent->GetPhysics()->GetOrigin(); + gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 ); + } +} + +/* +============ +idPathCorner::RandomPath +============ +*/ +idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) { + int i; + int num; + int which; + idEntity *ent; + idPathCorner *path[ MAX_GENTITIES ]; + + num = 0; + for( i = 0; i < source->targets.Num(); i++ ) { + ent = source->targets[ i ].GetEntity(); + if ( ent != NULL && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) { + path[ num++ ] = static_cast( ent ); + if ( num >= MAX_GENTITIES ) { + break; + } + } + } + + if ( !num ) { + return NULL; + } + + which = gameLocal.random.RandomInt( num ); + return path[ which ]; +} + +/* +===================== +idPathCorner::Event_RandomPath +===================== +*/ +void idPathCorner::Event_RandomPath() { + idPathCorner *path; + + path = RandomPath( this, NULL ); + idThread::ReturnEntity( path ); +} + +/* +=============================================================================== + + idDamagable + +=============================================================================== +*/ + +const idEventDef EV_RestoreDamagable( "" ); + +CLASS_DECLARATION( idEntity, idDamagable ) + EVENT( EV_Activate, idDamagable::Event_BecomeBroken ) + EVENT( EV_RestoreDamagable, idDamagable::Event_RestoreDamagable ) +END_CLASS + +/* +================ +idDamagable::idDamagable +================ +*/ +idDamagable::idDamagable() { + count = 0; + nextTriggerTime = 0; +} + +/* +================ +idDamagable::Save +================ +*/ +void idDamagable::Save( idSaveGame *savefile ) const { + savefile->WriteInt( count ); + savefile->WriteInt( nextTriggerTime ); +} + +/* +================ +idDamagable::Restore +================ +*/ +void idDamagable::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( count ); + savefile->ReadInt( nextTriggerTime ); +} + +/* +================ +idDamagable::Spawn +================ +*/ +void idDamagable::Spawn() { + idStr broken; + + health = spawnArgs.GetInt( "health", "5" ); + spawnArgs.GetInt( "count", "1", count ); + nextTriggerTime = 0; + + // make sure the model gets cached + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) { + gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() ); + } + + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); +} + +/* +================ +idDamagable::BecomeBroken +================ +*/ +void idDamagable::BecomeBroken( idEntity *activator ) { + float forceState; + int numStates; + int cycle; + float wait; + + if ( gameLocal.time < nextTriggerTime ) { + return; + } + + spawnArgs.GetFloat( "wait", "0.1", wait ); + nextTriggerTime = gameLocal.time + SEC2MS( wait ); + if ( count > 0 ) { + count--; + if ( !count ) { + fl.takedamage = false; + } else { + health = spawnArgs.GetInt( "health", "5" ); + } + } + + idStr broken; + + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() ) { + SetModel( broken ); + } + + // offset the start time of the shader to sync it to the gameLocal time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + spawnArgs.GetInt( "numstates", "1", numStates ); + spawnArgs.GetInt( "cycle", "0", cycle ); + spawnArgs.GetFloat( "forcestate", "0", forceState ); + + // set the state parm + if ( cycle ) { + renderEntity.shaderParms[ SHADERPARM_MODE ]++; + if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = 0; + } + } else if ( forceState ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState; + } else { + renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1; + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + ActivateTargets( activator ); + + if ( spawnArgs.GetBool( "hideWhenBroken" ) ) { + Hide(); + PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time ); + BecomeActive( TH_THINK ); + } +} + +/* +================ +idDamagable::Killed +================ +*/ +void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + BecomeBroken( attacker ); +} + +/* +================ +idDamagable::Hide +================ +*/ +void idDamagable::Hide() { + idEntity::Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idDamagable::Show +================ +*/ +void idDamagable::Show() { + idEntity::Show(); + GetPhysics()->SetContents( CONTENTS_SOLID ); +} + +/* +================ +idDamagable::Event_BecomeBroken +================ +*/ +void idDamagable::Event_BecomeBroken( idEntity *activator ) { + BecomeBroken( activator ); +} + +/* +================ +idDamagable::Event_RestoreDamagable +================ +*/ +void idDamagable::Event_RestoreDamagable() { + health = spawnArgs.GetInt( "health", "5" ); + Show(); +} + + +/* +=============================================================================== + + idExplodable + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idExplodable ) + EVENT( EV_Activate, idExplodable::Event_Explode ) +END_CLASS + +/* +================ +idExplodable::Spawn +================ +*/ +void idExplodable::Spawn() { + Hide(); +} + +/* +================ +idExplodable::Event_Explode +================ +*/ +void idExplodable::Event_Explode( idEntity *activator ) { + const char *temp; + + if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + // Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms + renderEntity.shaderParms[SHADERPARM_RED] = 1.0f; + renderEntity.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEntity.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = 0.0f; + Show(); + + PostEventMS( &EV_Remove, 2000 ); + + ActivateTargets( activator ); +} + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpring ) + EVENT( EV_PostSpawn, idSpring::Event_LinkSpring ) +END_CLASS + +/* +================ +idSpring::Think +================ +*/ +void idSpring::Think() { + idVec3 start, end, origin; + idMat3 axis; + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + // evaluate force + spring.Evaluate( gameLocal.time ); + + start = p1; + if ( ent1->GetPhysics() ) { + axis = ent1->GetPhysics()->GetAxis(); + origin = ent1->GetPhysics()->GetOrigin(); + start = origin + start * axis; + } + + end = p2; + if ( ent2->GetPhysics() ) { + axis = ent2->GetPhysics()->GetAxis(); + origin = ent2->GetPhysics()->GetOrigin(); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true ); + } + + Present(); +} + +/* +================ +idSpring::Event_LinkSpring +================ +*/ +void idSpring::Event_LinkSpring() { + idStr name1, name2; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + + if ( name1.Length() ) { + ent1 = gameLocal.FindEntity( name1 ); + if ( ent1 == NULL ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() ); + return; + } + } + else { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + + if ( name2.Length() ) { + ent2 = gameLocal.FindEntity( name2 ); + if ( ent2 == NULL ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() ); + return; + } + } + else { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + + spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 ); + BecomeActive( TH_THINK ); +} + +/* +================ +idSpring::Spawn +================ +*/ +void idSpring::Spawn() { + float Kstretch, damping, restLength; + + spawnArgs.GetInt( "id1", "0", id1 ); + spawnArgs.GetInt( "id2", "0", id2 ); + spawnArgs.GetVector( "point1", "0 0 0", p1 ); + spawnArgs.GetVector( "point2", "0 0 0", p2 ); + spawnArgs.GetFloat( "constant", "100.0f", Kstretch ); + spawnArgs.GetFloat( "damping", "10.0f", damping ); + spawnArgs.GetFloat( "restlength", "0.0f", restLength ); + + spring.InitSpring( Kstretch, 0.0f, damping, restLength ); + + ent1 = ent2 = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const idEventDef EV_Toggle( "Toggle", NULL ); + +CLASS_DECLARATION( idEntity, idForceField ) + EVENT( EV_Activate, idForceField::Event_Activate ) + EVENT( EV_Toggle, idForceField::Event_Toggle ) + EVENT( EV_FindTargets, idForceField::Event_FindTargets ) +END_CLASS + +/* +=============== +idForceField::Toggle +================ +*/ +void idForceField::Toggle() { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + + + +/* +================ +idForceField::Think +================ +*/ +void idForceField::ClientThink( const int curTime, const float fraction, const bool predict ) { + + // evaluate force + forceField.Evaluate( gameLocal.time ); + + Present(); +} + +/* +================ +idForceField::Think +================ +*/ +void idForceField::Think() { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + } + Present(); +} + +/* +================ +idForceField::Save +================ +*/ +void idForceField::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( forceField ); +} + +/* +================ +idForceField::Restore +================ +*/ +void idForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( forceField ); +} + +/* +================ +idForceField::Spawn +================ +*/ +void idForceField::Spawn() { + idVec3 uniform; + float explosion, implosion, randomTorque; + + if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) { + forceField.Uniform( uniform ); + } else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) { + forceField.Explosion( explosion ); + } else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) { + forceField.Implosion( implosion ); + } + + if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) { + forceField.RandomTorque( randomTorque ); + } + + if ( spawnArgs.GetBool( "applyForce", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_FORCE ); + } else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE ); + } else { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + } + + forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) ); + forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) ); + + // set the collision model on the force field + forceField.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ) ); + + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idForceField::Event_Toggle +================ +*/ +void idForceField::Event_Toggle() { + Toggle(); +} + +/* +================ +idForceField::Event_Activate +================ +*/ +void idForceField::Event_Activate( idEntity *activator ) { + float wait; + + Toggle(); + if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) { + PostEventSec( &EV_Toggle, wait ); + } +} + +/* +================ +idForceField::Event_FindTargets +================ +*/ +void idForceField::Event_FindTargets() { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ); + } +} + + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +const idEventDef EV_Animated_Start( "" ); +const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" ); +const idEventDef EV_LaunchMissilesUpdate( "", "dddd" ); +const idEventDef EV_AnimDone( "", "d" ); +const idEventDef EV_StartRagdoll( "startRagdoll" ); +const idEventDef EV_SetAnimation( "setAnimation", "s" ); +const idEventDef EV_GetAnimationLength( "getAnimationLength", NULL, 'f' ); + +CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated ) + EVENT( EV_Activate, idAnimated::Event_Activate ) + EVENT( EV_Animated_Start, idAnimated::Event_Start ) + EVENT( EV_StartRagdoll, idAnimated::Event_StartRagdoll ) + EVENT( EV_AnimDone, idAnimated::Event_AnimDone ) + EVENT( EV_Footstep, idAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, idAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, idAnimated::Event_Footstep ) + EVENT( EV_LaunchMissiles, idAnimated::Event_LaunchMissiles ) + EVENT( EV_LaunchMissilesUpdate, idAnimated::Event_LaunchMissilesUpdate ) + EVENT( EV_SetAnimation, idAnimated::Event_SetAnimation ) + EVENT( EV_GetAnimationLength, idAnimated::Event_GetAnimationLength ) +END_CLASS + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::idAnimated() { + anim = 0; + blendFrames = 0; + soundJoint = INVALID_JOINT; + activated = false; + combatModel = NULL; + activator = NULL; + current_anim_index = 0; + num_anims = 0; + achievement = -1; + +} + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::~idAnimated() { + delete combatModel; + combatModel = NULL; +} + +/* +=============== +idAnimated::Save +================ +*/ +void idAnimated::Save( idSaveGame *savefile ) const { + savefile->WriteInt( current_anim_index ); + savefile->WriteInt( num_anims ); + savefile->WriteInt( anim ); + savefile->WriteInt( blendFrames ); + savefile->WriteJoint( soundJoint ); + activator.Save( savefile ); + savefile->WriteBool( activated ); +} + +/* +=============== +idAnimated::Restore +================ +*/ +void idAnimated::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( current_anim_index ); + savefile->ReadInt( num_anims ); + savefile->ReadInt( anim ); + savefile->ReadInt( blendFrames ); + savefile->ReadJoint( soundJoint ); + activator.Restore( savefile ); + savefile->ReadBool( activated ); +} + +/* +=============== +idAnimated::Spawn +================ +*/ +void idAnimated::Spawn() { + idStr animname; + int anim2; + float wait; + const char *joint; + + joint = spawnArgs.GetString( "sound_bone", "origin" ); + soundJoint = animator.GetJointHandle( joint ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint ); + } + + LoadAF(); + + // allow bullets to collide with a combat model + if ( spawnArgs.GetBool( "combatModel", "0" ) ) { + combatModel = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( modelDefHandle ); + } + + // allow the entity to take damage + if ( spawnArgs.GetBool( "takeDamage", "0" ) ) { + fl.takedamage = true; + } + + current_anim_index = 0; + spawnArgs.GetInt( "num_anims", "0", num_anims ); + + blendFrames = spawnArgs.GetInt( "blend_in" ); + + animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" ); + if ( !animname.Length() ) { + anim = 0; + } else { + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + } + + if ( spawnArgs.GetBool( "hide" ) ) { + Hide(); + + if ( !num_anims ) { + blendFrames = 0; + } + } else if ( spawnArgs.GetString( "start_anim", "", animname ) ) { + anim2 = animator.GetAnim( animname ); + if ( !anim2 ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 ); + } else if ( anim ) { + // init joints to the first frame of the animation + animator.SetFrame( ANIMCHANNEL_ALL, anim, 1, gameLocal.time, 0 ); + + if ( !num_anims ) { + blendFrames = 0; + } + } + + spawnArgs.GetFloat( "wait", "-1", wait ); + + if ( wait >= 0 ) { + PostEventSec( &EV_Activate, wait, this ); + } +} + +/* +=============== +idAnimated::LoadAF +=============== +*/ +bool idAnimated::LoadAF() { + idStr fileName; + + if ( !spawnArgs.GetString( "ragdoll", "*unknown*", fileName ) ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName ); +} + +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = renderEntity.axis; + return true; +} + +/* +================ +idAnimated::StartRagdoll +================ +*/ +bool idAnimated::StartRagdoll() { + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // disable any collision model used + GetPhysics()->DisableClip(); + + // start using the AF + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + return true; +} + +/* +===================== +idAnimated::PlayNextAnim +===================== +*/ +void idAnimated::PlayNextAnim() { + const char *animname; + int len; + int cycle; + + if ( current_anim_index >= num_anims ) { + Hide(); + if ( spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } else { + current_anim_index = 0; + } + return; + } + + Show(); + current_anim_index++; + + spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname ); + if ( !animname ) { + anim = 0; + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) ); + return; + } + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() ); + return; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname ); + } + + spawnArgs.GetInt( "cycle", "1", cycle ); + if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) { + cycle = -1; + } + + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, current_anim_index ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_StartRagdoll +================ +*/ +void idAnimated::Event_StartRagdoll() { + StartRagdoll(); +} + +/* +=============== +idAnimated::Event_AnimDone +================ +*/ +void idAnimated::Event_AnimDone( int animindex ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + + if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); + } else if ( spawnArgs.GetBool( "auto_advance" ) ) { + PlayNextAnim(); + } else { + activated = false; + } + + ActivateTargets( activator.GetEntity() ); +} + +/* +=============== +idAnimated::Event_Activate +================ +*/ +void idAnimated::Event_Activate( idEntity *_activator ) { + if ( num_anims ) { + PlayNextAnim(); + activator = _activator; + return; + } + + if ( activated ) { + // already activated + return; + } + + // achievement associated with this entity (given on activation) + achievement = spawnArgs.GetInt( "achievement", "-1" ); + if ( achievement != -1 ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + bool shouldCountAction = true; + // only count unlocking lockers if we're in the base game + if ( achievement == ACHIEVEMENT_OPEN_ALL_LOCKERS && player->GetExpansionType() != GAME_BASE ) { + shouldCountAction = false; + } + + if ( shouldCountAction ) { + player->GetAchievementManager().EventCompletesAchievement( (achievement_t)achievement ); + } + } + } + + activated = true; + activator = _activator; + ProcessEvent( &EV_Animated_Start ); +} + +/* +=============== +idAnimated::Event_Start +================ +*/ +void idAnimated::Event_Start() { + int cycle; + int len; + + Show(); + + if ( num_anims ) { + PlayNextAnim(); + return; + } + + if ( anim ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + spawnArgs.GetInt( "cycle", "1", cycle ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, 1 ); + } + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_Footstep +=============== +*/ +void idAnimated::Event_Footstep() { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +===================== +idAnimated::Event_LaunchMissilesUpdate +===================== +*/ +void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) { + idVec3 launchPos; + idVec3 targetPos; + idMat3 axis; + idVec3 dir; + idEntity * ent; + idProjectile * projectile; + const idDict * projectileDef; + const char * projectilename; + + projectilename = spawnArgs.GetString( "projectilename" ); + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL ); + + animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis ); + launchPos = renderEntity.origin + launchPos * renderEntity.axis; + + animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis ); + targetPos = renderEntity.origin + targetPos * renderEntity.axis; + + dir = targetPos - launchPos; + dir.Normalize(); + + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); + if ( ent == NULL || !ent->IsType( idProjectile::Type ) ) { + gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + projectile = ( idProjectile * )ent; + projectile->Create( this, launchPos, dir ); + projectile->Launch( launchPos, dir, vec3_origin ); + + if ( numshots > 0 ) { + PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay ); + } +} + +/* +===================== +idAnimated::Event_LaunchMissiles +===================== +*/ +void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) { + const idDict * projectileDef; + jointHandle_t launch; + jointHandle_t target; + + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + launch = animator.GetJointHandle( launchjoint ); + if ( launch == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint ); + gameLocal.Error( "Unknown joint '%s'", launchjoint ); + } + + target = animator.GetJointHandle( targetjoint ); + if ( target == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint ); + } + + spawnArgs.Set( "projectilename", projectilename ); + spawnArgs.Set( "missilesound", sound ); + + CancelEvents( &EV_LaunchMissilesUpdate ); + ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay ); +} + +/* +===================== +idAnimated::Event_SetAnimation +===================== +*/ +void idAnimated::Event_SetAnimation( const char *animName ) { + + //BSM Nerve: Need to add some error checking so we don't change the animation + //in the middle of the existing animation + anim = animator.GetAnim( animName ); + if ( !anim ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animName ); + } + +} + +/* +===================== +idAnimated::Event_GetAnimationLength +===================== +*/ +void idAnimated::Event_GetAnimationLength() { + float length = 0; + + if(anim) { + length = (float)(animator.AnimLength( anim )) / 1000.f; + } + + idThread::ReturnFloat(length); +} + +/* +=============================================================================== + + idStaticEntity + + Some static entities may be optimized into inline geometry by dmap + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idStaticEntity ) + EVENT( EV_Activate, idStaticEntity::Event_Activate ) +END_CLASS + +/* +=============== +idStaticEntity::idStaticEntity +=============== +*/ +idStaticEntity::idStaticEntity() { + spawnTime = 0; + active = false; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + runGui = false; +} + +/* +=============== +idStaticEntity::Save +=============== +*/ +void idStaticEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnTime ); + savefile->WriteBool( active ); + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + savefile->WriteBool( runGui ); +} + +/* +=============== +idStaticEntity::Restore +=============== +*/ +void idStaticEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnTime ); + savefile->ReadBool( active ); + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); + savefile->ReadBool( runGui ); +} + +/* +=============== +idStaticEntity::Spawn +=============== +*/ +void idStaticEntity::Spawn() { + bool solid; + bool hidden; + + // an inline static model will not do anything at all + if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) { + Hide(); + return; + } + + solid = spawnArgs.GetBool( "solid" ); + hidden = spawnArgs.GetBool( "hide" ); + + if ( solid && !hidden ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + GetPhysics()->SetContents( 0 ); + } + + spawnTime = gameLocal.time; + active = false; + + idStr model = spawnArgs.GetString( "model" ); + if ( model.Find( ".prt" ) >= 0 ) { + // we want the parametric particles out of sync with each other + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 ); + } + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // NOTE: this should be used very rarely because it is expensive + runGui = spawnArgs.GetBool( "runGui" ); + if ( runGui ) { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idStaticEntity::ShowEditingDialog +================ +*/ +void idStaticEntity::ShowEditingDialog() { +} +/* +================ +idStaticEntity::Think +================ +*/ +void idStaticEntity::Think() { + idEntity::Think(); + if ( thinkFlags & TH_THINK ) { + if ( runGui && renderEntity.gui[0] ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->objectiveSystemOpen ) { + renderEntity.gui[0]->StateChanged( gameLocal.time, true ); + if ( renderEntity.gui[1] ) { + renderEntity.gui[1]->StateChanged( gameLocal.time, true ); + } + if ( renderEntity.gui[2] ) { + renderEntity.gui[2]->StateChanged( gameLocal.time, true ); + } + } + } + } + if ( fadeEnd > 0 ) { + idVec4 color; + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + } +} + +/* +================ +idStaticEntity::Fade +================ +*/ +void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idStaticEntity::Hide +================ +*/ +void idStaticEntity::Hide() { + idEntity::Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idStaticEntity::Show +================ +*/ +void idStaticEntity::Show() { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid" ) ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idStaticEntity::Event_Activate +================ +*/ +void idStaticEntity::Event_Activate( idEntity *activator ) { + idStr activateGui; + + spawnTime = gameLocal.time; + active = !active; + + const idKeyValue *kv = spawnArgs.FindKey( "hide" ); + if ( kv ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime ); + renderEntity.shaderParms[5] = active; + // this change should be a good thing, it will automatically turn on + // lights etc.. when triggered so that does not have to be specifically done + // with trigger parms.. it MIGHT break things so need to keep an eye on it + renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ? 0.0f : 1.0f; + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idStaticEntity::WriteToSnapshot +================ +*/ +void idStaticEntity::WriteToSnapshot( idBitMsg &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + WriteGUIToSnapshot( msg ); + msg.WriteBits( IsHidden()?1:0, 1 ); +} + +/* +================ +idStaticEntity::ReadFromSnapshot +================ +*/ +void idStaticEntity::ReadFromSnapshot( const idBitMsg &msg ) { + bool hidden; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + hidden = msg.ReadBits( 1 ) == 1; + if ( hidden != IsHidden() ) { + if ( hidden ) { + Hide(); + } else { + Show(); + } + } + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + + +CLASS_DECLARATION( idStaticEntity, idFuncEmitter ) +EVENT( EV_Activate, idFuncEmitter::Event_Activate ) +END_CLASS + +/* +=============== +idFuncEmitter::idFuncEmitter +=============== +*/ +idFuncEmitter::idFuncEmitter() { + hidden = false; +} + +/* +=============== +idFuncEmitter::Spawn +=============== +*/ +void idFuncEmitter::Spawn() { + if ( spawnArgs.GetBool( "start_off" ) ) { + hidden = true; + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 ); + UpdateVisuals(); + } else { + hidden = false; + } +} + +/* +=============== +idFuncEmitter::Save +=============== +*/ +void idFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteBool( hidden ); +} + +/* +=============== +idFuncEmitter::Restore +=============== +*/ +void idFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( hidden ); +} + +/* +================ +idFuncEmitter::Event_Activate +================ +*/ +void idFuncEmitter::Event_Activate( idEntity *activator ) { + if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + hidden = false; + } else { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + hidden = true; + } + UpdateVisuals(); +} + +/* +================ +idFuncEmitter::WriteToSnapshot +================ +*/ +void idFuncEmitter::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteBits( hidden ? 1 : 0, 1 ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +/* +================ +idFuncEmitter::ReadFromSnapshot +================ +*/ +void idFuncEmitter::ReadFromSnapshot( const idBitMsg &msg ) { + hidden = msg.ReadBits( 1 ) != 0; + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +=============================================================================== + +idFuncShootProjectile + +=============================================================================== +*/ + + +CLASS_DECLARATION( idStaticEntity, idFuncShootProjectile ) + EVENT( EV_Activate, idFuncShootProjectile::Event_Activate ) + END_CLASS + + /* + =============== + idFuncShootProjectile::idFuncShootProjectile + =============== + */ + idFuncShootProjectile::idFuncShootProjectile() { + mRespawnDelay = 1000; + mRespawnTime = 0; + mShootSpeed = 1000; + mShootDir = idVec3( 0.0f, 0.0f, 1.0f ); +} + +/* +=============== +idFuncShootProjectile::Spawn +=============== +*/ +void idFuncShootProjectile::Spawn() { +} + +/* +=============== +idFuncShootProjectile::Think +=============== +*/ +void idFuncShootProjectile::Think() { + if ( thinkFlags & TH_THINK ) { + // time to spawn a new projectile? + if ( mRespawnTime > 0 && mRespawnTime <= gameLocal.GetTime() ) { + const idDict *dict = gameLocal.FindEntityDefDict( mEntityDefName ); + idEntity *ent = NULL; + gameLocal.SpawnEntityDef( *dict, &ent ); + if ( ent != NULL ) { + idProjectile *proj = static_cast(ent); + + idVec3 pushVel = mShootDir * mShootSpeed; + proj->Create( this, GetPhysics()->GetOrigin(), mShootDir ); + proj->Launch( GetPhysics()->GetOrigin(), mShootDir, pushVel ); + if ( mShootSpeed == 0.0f ) { + proj->GetPhysics()->SetLinearVelocity( vec3_zero ); + } else { + proj->GetPhysics()->SetLinearVelocity( pushVel ); + } + + mLastProjectile = proj; + } + if ( mShootSpeed == 0.0f ) { + mRespawnTime = 0; // stationary, respawn when triggered + } else { + mRespawnTime = gameLocal.GetTime() + mRespawnDelay; // moving, respawn after delay + } + } + } +} + +/* +=============== +idFuncShootProjectile::Save +=============== +*/ +void idFuncShootProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteInt( mRespawnDelay ); + savefile->WriteInt( mRespawnTime ); + savefile->WriteFloat( mShootSpeed ); + savefile->WriteVec3( mShootDir ); + savefile->WriteString( mEntityDefName ); +} + +/* +=============== +idFuncShootProjectile::Restore +=============== +*/ +void idFuncShootProjectile::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( mRespawnDelay ); + savefile->ReadInt( mRespawnTime ); + savefile->ReadFloat( mShootSpeed ); + savefile->ReadVec3( mShootDir ); + savefile->ReadString( mEntityDefName ); +} + +/* +================ +idFuncShootProjectile::Event_Activate +================ +*/ +void idFuncShootProjectile::Event_Activate( idEntity *activator ) { + if ( ( thinkFlags & TH_THINK ) != 0 ) { + if ( mShootSpeed == 0.0f && mRespawnTime == 0 ) { + mRespawnTime = gameLocal.GetTime(); + return; + } + } + + mRespawnDelay = spawnArgs.GetInt( "spawn_delay_ms" ); + mShootSpeed = spawnArgs.GetFloat( "speed" ); + mEntityDefName = spawnArgs.GetString( "def_projectile" ); + if ( targets.Num() > 0 && targets[0].IsValid() ) { + mShootDir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + mShootDir.Normalize(); + } else { + // stationary projectile, doesn't move and only respawns when triggered + mShootSpeed = 0.0f; + mRespawnTime = 0; + } + + if ( ( thinkFlags & TH_THINK ) != 0 ) { + // currently active, deactivate + BecomeInactive( TH_THINK ); + } else { + // currently inactive, activate + BecomeActive( TH_THINK ); + mRespawnTime = gameLocal.GetTime(); + } +} + +/* +================ +idFuncShootProjectile::WriteToSnapshot +================ +*/ +void idFuncShootProjectile::WriteToSnapshot( idBitMsg &msg ) const { + // msg.WriteBits( hidden ? 1 : 0, 1 ); + // msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + // msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +/* +================ +idFuncShootProjectile::ReadFromSnapshot +================ +*/ +void idFuncShootProjectile::ReadFromSnapshot( const idBitMsg &msg ) { + // hidden = msg.ReadBits( 1 ) != 0; + // renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + // renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + // if ( msg.HasChanged() ) { + // UpdateVisuals(); + // } +} + + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + + +const idEventDef EV_Splat( "" ); +CLASS_DECLARATION( idFuncEmitter, idFuncSplat ) +EVENT( EV_Activate, idFuncSplat::Event_Activate ) +EVENT( EV_Splat, idFuncSplat::Event_Splat ) +END_CLASS + +/* +=============== +idFuncSplat::idFuncSplat +=============== +*/ +idFuncSplat::idFuncSplat() { +} + +/* +=============== +idFuncSplat::Spawn +=============== +*/ +void idFuncSplat::Spawn() { +} + +/* +================ +idFuncSplat::Event_Splat +================ +*/ +void idFuncSplat::Event_Splat() { + const char *splat = NULL; + int count = spawnArgs.GetInt( "splatCount", "1" ); + for ( int i = 0; i < count; i++ ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + if ( splat != NULL && *splat != NULL ) { + float size = spawnArgs.GetFloat( "splatSize", "128" ); + float dist = spawnArgs.GetFloat( "splatDistance", "128" ); + float angle = spawnArgs.GetFloat( "splatAngle", "0" ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle ); + } + } + StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +idFuncSplat::Event_Activate +================ +*/ +void idFuncSplat::Event_Activate( idEntity *activator ) { + idFuncEmitter::Event_Activate( activator ); + PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) ); + StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +=============================================================================== + +idFuncSmoke + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncSmoke ) +EVENT( EV_Activate, idFuncSmoke::Event_Activate ) +END_CLASS + +/* +=============== +idFuncSmoke::idFuncSmoke +=============== +*/ +idFuncSmoke::idFuncSmoke() { + smokeTime = 0; + smoke = NULL; + restart = false; +} + +/* +=============== +idFuncSmoke::Save +=============== +*/ +void idFuncSmoke::Save( idSaveGame *savefile ) const { + savefile->WriteInt( smokeTime ); + savefile->WriteParticle( smoke ); + savefile->WriteBool( restart ); +} + +/* +=============== +idFuncSmoke::Restore +=============== +*/ +void idFuncSmoke::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( smokeTime ); + savefile->ReadParticle( smoke ); + savefile->ReadBool( restart ); +} + +/* +=============== +idFuncSmoke::Spawn +=============== +*/ +void idFuncSmoke::Spawn() { + const char *smokeName = spawnArgs.GetString( "smoke" ); + if ( *smokeName != '\0' ) { + smoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } else { + smoke = NULL; + } + if ( spawnArgs.GetBool( "start_off" ) ) { + smokeTime = 0; + restart = false; + } else if ( smoke ) { + smokeTime = gameLocal.time; + BecomeActive( TH_UPDATEPARTICLES ); + restart = true; + } + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idFuncSmoke::Event_Activate +================ +*/ +void idFuncSmoke::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_UPDATEPARTICLES ) { + restart = false; + return; + } else { + BecomeActive( TH_UPDATEPARTICLES ); + restart = true; + smokeTime = gameLocal.time; + } +} + +/* +=============== +idFuncSmoke::Think +================ +*/ +void idFuncSmoke::Think() { + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() || smoke == NULL || smokeTime == -1 ) { + return; + } + + if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) { + if ( !gameLocal.smokeParticles->EmitSmoke( smoke, smokeTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) { + if ( restart ) { + smokeTime = gameLocal.time; + } else { + smokeTime = 0; + BecomeInactive( TH_UPDATEPARTICLES ); + } + } + } + +} + + +/* +=============================================================================== + + idTextEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTextEntity ) +END_CLASS + +/* +================ +idTextEntity::Spawn +================ +*/ +void idTextEntity::Spawn() { + // these are cached as the are used each frame + text = spawnArgs.GetString( "text" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + bool force = spawnArgs.GetBool( "force" ); + if ( developer.GetBool() || force ) { + BecomeActive(TH_THINK); + } +} + +/* +================ +idTextEntity::Save +================ +*/ +void idTextEntity::Save( idSaveGame *savefile ) const { + savefile->WriteString( text ); + savefile->WriteBool( playerOriented ); +} + +/* +================ +idTextEntity::Restore +================ +*/ +void idTextEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadString( text ); + savefile->ReadBool( playerOriented ); +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::Think() { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + + idVacuumSeperatorEntity + + Can be triggered to let vacuum through a portal (blown out window) + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity ) + EVENT( EV_Activate, idVacuumSeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +idVacuumSeparatorEntity::idVacuumSeparatorEntity +================ +*/ +idVacuumSeparatorEntity::idVacuumSeparatorEntity() { + portal = 0; +} + +/* +================ +idVacuumSeparatorEntity::Save +================ +*/ +void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +idVacuumSeparatorEntity::Restore +================ +*/ +void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +idVacuumSeparatorEntity::Spawn +================ +*/ +void idVacuumSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION ); +} + +/* +================ +idVacuumSeparatorEntity::Event_Activate +================ +*/ +void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_NONE ); +} + + +/* +=============================================================================== + +idLocationSeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationSeparatorEntity ) +END_CLASS + +/* +================ +idLocationSeparatorEntity::Spawn +================ +*/ +void idLocationSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + qhandle_t portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + } + gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION ); +} + + +/* +=============================================================================== + + idVacuumEntity + + Levels should only have a single vacuum entity. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumEntity ) +END_CLASS + +/* +================ +idVacuumEntity::Spawn +================ +*/ +void idVacuumEntity::Spawn() { + if ( gameLocal.vacuumAreaNum != -1 ) { + gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" ); + return; + } + + idVec3 org = spawnArgs.GetVector( "origin" ); + + gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org ); +} + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationEntity ) +END_CLASS + +/* +====================== +idLocationEntity::Spawn +====================== +*/ +void idLocationEntity::Spawn() { + idStr realName; + + // this just holds dict information + + // if "location" not already set, use the entity name. + if ( !spawnArgs.GetString( "location", "", realName ) ) { + spawnArgs.Set( "location", name ); + } +} + +/* +====================== +idLocationEntity::GetLocation +====================== +*/ +const char *idLocationEntity::GetLocation() const { + return spawnArgs.GetString( "location" ); +} + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idBeam ) + EVENT( EV_PostSpawn, idBeam::Event_MatchTarget ) + EVENT( EV_Activate, idBeam::Event_Activate ) +END_CLASS + +/* +=============== +idBeam::idBeam +=============== +*/ +idBeam::idBeam() { + target = NULL; + master = NULL; +} + +/* +=============== +idBeam::Save +=============== +*/ +void idBeam::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + master.Save( savefile ); +} + +/* +=============== +idBeam::Restore +=============== +*/ +void idBeam::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + master.Restore( savefile ); +} + +/* +=============== +idBeam::Spawn +=============== +*/ +void idBeam::Spawn() { + float width; + + if ( spawnArgs.GetFloat( "width", "0", width ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width; + } + + SetModel( "_BEAM" ); + Hide(); + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idBeam::Think +================ +*/ +void idBeam::Think() { + idBeam *masterEnt; + + if ( !IsHidden() && !target.GetEntity() ) { + // hide if our target is removed + Hide(); + } + + RunPhysics(); + + masterEnt = master.GetEntity(); + if ( masterEnt ) { + const idVec3 &origin = GetPhysics()->GetOrigin(); + masterEnt->SetBeamTarget( origin ); + } + Present(); +} + +/* +================ +idBeam::SetMaster +================ +*/ +void idBeam::SetMaster( idBeam *masterbeam ) { + master = masterbeam; +} + +/* +================ +idBeam::SetBeamTarget +================ +*/ +void idBeam::SetBeamTarget( const idVec3 &origin ) { + if ( ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] != origin.x ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] != origin.y ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] != origin.z ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z; + UpdateVisuals(); + } +} + +/* +================ +idBeam::Show +================ +*/ +void idBeam::Show() { + idBeam *targetEnt; + + idEntity::Show(); + + targetEnt = target.GetEntity(); + if ( targetEnt ) { + const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin(); + SetBeamTarget( origin ); + } +} + +/* +================ +idBeam::Event_MatchTarget +================ +*/ +void idBeam::Event_MatchTarget() { + int i; + idEntity *targetEnt; + idBeam *targetBeam; + + if ( !targets.Num() ) { + return; + } + + targetBeam = NULL; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt != NULL && targetEnt->IsType( idBeam::Type ) ) { + targetBeam = static_cast( targetEnt ); + break; + } + } + + if ( targetBeam == NULL ) { + gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() ); + return; + } + + target = targetBeam; + targetBeam->SetMaster( this ); + if ( !spawnArgs.GetBool( "start_off" ) ) { + Show(); + } +} + +/* +================ +idBeam::Event_Activate +================ +*/ +void idBeam::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } +} + +/* +================ +idBeam::WriteToSnapshot +================ +*/ +void idBeam::WriteToSnapshot( idBitMsg &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] ); +} + +/* +================ +idBeam::ReadFromSnapshot +================ +*/ +void idBeam::ReadFromSnapshot( const idBitMsg &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLiquid ) + EVENT( EV_Touch, idLiquid::Event_Touch ) +END_CLASS + +/* +================ +idLiquid::Save +================ +*/ +void idLiquid::Save( idSaveGame *savefile ) const { + // Nothing to save +} + +/* +================ +idLiquid::Restore +================ +*/ +void idLiquid::Restore( idRestoreGame *savefile ) { + //FIXME: NO! + Spawn(); +} + +/* +================ +idLiquid::Spawn +================ +*/ +void idLiquid::Spawn() { +/* + model = dynamic_cast( renderEntity.hModel ); + if ( !model ) { + gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() ); + } + model->Reset(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +*/ +} + +/* +================ +idLiquid::Event_Touch +================ +*/ +void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { + // FIXME: for QuakeCon +/* + if ( common->IsClient() ) { + return; + } + + idVec3 pos; + + pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f ); +*/ +} + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idShaking ) + EVENT( EV_Activate, idShaking::Event_Activate ) +END_CLASS + +/* +=============== +idShaking::idShaking +=============== +*/ +idShaking::idShaking() { + active = false; +} + +/* +=============== +idShaking::Save +=============== +*/ +void idShaking::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +=============== +idShaking::Restore +=============== +*/ +void idShaking::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +=============== +idShaking::Spawn +=============== +*/ +void idShaking::Spawn() { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + SetPhysics( &physicsObj ); + + active = false; + if ( !spawnArgs.GetBool( "start_off" ) ) { + BeginShaking(); + } +} + +/* +================ +idShaking::BeginShaking +================ +*/ +void idShaking::BeginShaking() { + int phase; + idAngles shake; + int period; + + active = true; + phase = gameLocal.random.RandomInt( 1000 ); + shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" ); + period = spawnArgs.GetFloat( "period", "0.05" ) * 1000; + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero ); +} + +/* +================ +idShaking::Event_Activate +================ +*/ +void idShaking::Event_Activate( idEntity *activator ) { + if ( !active ) { + BeginShaking(); + } else { + active = false; + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero ); + } +} + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idEarthQuake ) + EVENT( EV_Activate, idEarthQuake::Event_Activate ) +END_CLASS + +/* +=============== +idEarthQuake::idEarthQuake +=============== +*/ +idEarthQuake::idEarthQuake() { + wait = 0.0f; + random = 0.0f; + nextTriggerTime = 0; + shakeStopTime = 0; + triggered = false; + playerOriented = false; + disabled = false; + shakeTime = 0.0f; +} + +/* +=============== +idEarthQuake::Save +=============== +*/ +void idEarthQuake::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextTriggerTime ); + savefile->WriteInt( shakeStopTime ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteBool( triggered ); + savefile->WriteBool( playerOriented ); + savefile->WriteBool( disabled ); + savefile->WriteFloat( shakeTime ); +} + +/* +=============== +idEarthQuake::Restore +=============== +*/ +void idEarthQuake::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( nextTriggerTime ); + savefile->ReadInt( shakeStopTime ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadBool( triggered ); + savefile->ReadBool( playerOriented ); + savefile->ReadBool( disabled ); + savefile->ReadFloat( shakeTime ); + + if ( shakeStopTime > gameLocal.time ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idEarthQuake::Spawn +=============== +*/ +void idEarthQuake::Spawn() { + nextTriggerTime = 0; + shakeStopTime = 0; + wait = spawnArgs.GetFloat( "wait", "15" ); + random = spawnArgs.GetFloat( "random", "5" ); + triggered = spawnArgs.GetBool( "triggered" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + disabled = false; + shakeTime = spawnArgs.GetFloat( "shakeTime", "0" ); + + if ( !triggered ){ + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this ); + } + BecomeInactive( TH_THINK ); +} + +/* +================ +idEarthQuake::Event_Activate +================ +*/ +void idEarthQuake::Event_Activate( idEntity *activator ) { + + if ( nextTriggerTime > gameLocal.time ) { + return; + } + + if ( disabled && activator == this ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + nextTriggerTime = 0; + + if ( !triggered && activator != this ){ + // if we are not triggered ( i.e. random ), disable or enable + disabled ^= 1; + if (disabled) { + return; + } else { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } + } + + ActivateTargets( activator ); + + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) ); + if ( playerOriented ) { + player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } else { + StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + + if ( shakeTime > 0.0f ) { + shakeStopTime = gameLocal.time + SEC2MS( shakeTime ); + BecomeActive( TH_THINK ); + } + + if ( wait > 0.0f ) { + if ( !triggered ) { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } else { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } + } else if ( shakeTime == 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } +} + + +/* +=============== +idEarthQuake::Think +================ +*/ +void idEarthQuake::Think() { + if ( thinkFlags & TH_THINK ) { + if ( gameLocal.time > shakeStopTime ) { + BecomeInactive( TH_THINK ); + if ( wait <= 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } + return; + } + float shakeVolume = gameSoundWorld->CurrentShakeAmplitude(); + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true ); + } + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncPortal ) + EVENT( EV_Activate, idFuncPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncPortal::idFuncPortal +=============== +*/ +idFuncPortal::idFuncPortal() { + portal = 0; + state = false; +} + +/* +=============== +idFuncPortal::Save +=============== +*/ +void idFuncPortal::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteBool( state ); +} + +/* +=============== +idFuncPortal::Restore +=============== +*/ +void idFuncPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)portal ); + savefile->ReadBool( state ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); +} + +/* +=============== +idFuncPortal::Spawn +=============== +*/ +void idFuncPortal::Spawn() { + portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) ); + if ( portal > 0 ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +================ +idFuncPortal::Event_Activate +================ +*/ +void idFuncPortal::Event_Activate( idEntity *activator ) { + if ( portal > 0 ) { + state = !state; + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASPortal ) + EVENT( EV_Activate, idFuncAASPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASPortal::idFuncAASPortal +=============== +*/ +idFuncAASPortal::idFuncAASPortal() { + state = false; +} + +/* +=============== +idFuncAASPortal::Save +=============== +*/ +void idFuncAASPortal::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASPortal::Restore +=============== +*/ +void idFuncAASPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============== +idFuncAASPortal::Spawn +=============== +*/ +void idFuncAASPortal::Spawn() { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +================ +idFuncAASPortal::Event_Activate +================ +*/ +void idFuncAASPortal::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASObstacle ) + EVENT( EV_Activate, idFuncAASObstacle::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASObstacle::idFuncAASObstacle +=============== +*/ +idFuncAASObstacle::idFuncAASObstacle() { + state = false; +} + +/* +=============== +idFuncAASObstacle::Save +=============== +*/ +void idFuncAASObstacle::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASObstacle::Restore +=============== +*/ +void idFuncAASObstacle::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============== +idFuncAASObstacle::Spawn +=============== +*/ +void idFuncAASObstacle::Spawn() { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::Event_Activate +================ +*/ +void idFuncAASObstacle::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + + + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +const idEventDef EV_ResetRadioHud( "", "e" ); + + +CLASS_DECLARATION( idEntity, idFuncRadioChatter ) +EVENT( EV_Activate, idFuncRadioChatter::Event_Activate ) +EVENT( EV_ResetRadioHud, idFuncRadioChatter::Event_ResetRadioHud ) +END_CLASS + +/* +=============== +idFuncRadioChatter::idFuncRadioChatter +=============== +*/ +idFuncRadioChatter::idFuncRadioChatter() { + time = 0.0; +} + +/* +=============== +idFuncRadioChatter::Save +=============== +*/ +void idFuncRadioChatter::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( time ); +} + +/* +=============== +idFuncRadioChatter::Restore +=============== +*/ +void idFuncRadioChatter::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( time ); +} + +/* +=============== +idFuncRadioChatter::Spawn +=============== +*/ +void idFuncRadioChatter::Spawn() { + time = spawnArgs.GetFloat( "time", "5.0" ); +} + +/* +================ +idFuncRadioChatter::Event_Activate +================ +*/ +void idFuncRadioChatter::Event_Activate( idEntity *activator ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + + if ( player != NULL && player->hudManager ) { + player->hudManager->SetRadioMessage( true ); + } + + const char * sound = spawnArgs.GetString( "snd_radiochatter", "" ); + if ( sound != NULL && *sound != NULL ) { + const idSoundShader * shader = declManager->FindSound( sound ); + int length = 0; + player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length ); + time = MS2SEC( length + 150 ); + } + // we still put the hud up because this is used with no sound on + // certain frame commands when the chatter is triggered + PostEventSec( &EV_ResetRadioHud, time, player ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) { + idPlayer *player = ( activator->IsType( idPlayer::Type ) ) ? static_cast( activator ) : gameLocal.GetLocalPlayer(); + + if ( player != NULL && player->hudManager ) { + player->hudManager->SetRadioMessage( false ); + } + + ActivateTargets( activator ); +} + + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPhantomObjects ) + EVENT( EV_Activate, idPhantomObjects::Event_Activate ) +END_CLASS + +/* +=============== +idPhantomObjects::idPhantomObjects +=============== +*/ +idPhantomObjects::idPhantomObjects() { + target = NULL; + end_time = 0; + throw_time = 0.0f; + shake_time = 0.0f; + shake_ang.Zero(); + speed = 0.0f; + min_wait = 0; + max_wait = 0; + fl.neverDormant = false; +} + +/* +=============== +idPhantomObjects::Save +=============== +*/ +void idPhantomObjects::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( end_time ); + savefile->WriteFloat( throw_time ); + savefile->WriteFloat( shake_time ); + savefile->WriteVec3( shake_ang ); + savefile->WriteFloat( speed ); + savefile->WriteInt( min_wait ); + savefile->WriteInt( max_wait ); + target.Save( savefile ); + + savefile->WriteInt( targetTime.Num() ); + for( i = 0; i < targetTime.Num(); i++ ) { + savefile->WriteInt( targetTime[ i ] ); + } + for( i = 0; i < lastTargetPos.Num(); i++ ) { + savefile->WriteVec3( lastTargetPos[ i ] ); + } +} + +/* +=============== +idPhantomObjects::Restore +=============== +*/ +void idPhantomObjects::Restore( idRestoreGame *savefile ) { + int num; + int i; + + savefile->ReadInt( end_time ); + savefile->ReadFloat( throw_time ); + savefile->ReadFloat( shake_time ); + savefile->ReadVec3( shake_ang ); + savefile->ReadFloat( speed ); + savefile->ReadInt( min_wait ); + savefile->ReadInt( max_wait ); + target.Restore( savefile ); + + savefile->ReadInt( num ); + targetTime.SetGranularity( 1 ); + targetTime.SetNum( num ); + lastTargetPos.SetGranularity( 1 ); + lastTargetPos.SetNum( num ); + + for( i = 0; i < num; i++ ) { + savefile->ReadInt( targetTime[ i ] ); + } + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( lastTargetPos[ i ] ); + } +} + +/* +=============== +idPhantomObjects::Spawn +=============== +*/ +void idPhantomObjects::Spawn() { + throw_time = spawnArgs.GetFloat( "time", "5" ); + speed = spawnArgs.GetFloat( "speed", "1200" ); + shake_time = spawnArgs.GetFloat( "shake_time", "1" ); + throw_time -= shake_time; + if ( throw_time < 0.0f ) { + throw_time = 0.0f; + } + min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) ); + max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) ); + + shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" ); + Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idPhantomObjects::Event_Activate +================ +*/ +void idPhantomObjects::Event_Activate( idEntity *activator ) { + int i; + float time; + float frac; + float scale; + + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + return; + } + + RemoveNullTargets(); + if ( !targets.Num() ) { + return; + } + + if ( !activator || !activator->IsType( idActor::Type ) ) { + target = gameLocal.GetLocalPlayer(); + } else { + target = static_cast( activator ); + } + + end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) ); + + targetTime.SetNum( targets.Num() ); + lastTargetPos.SetNum( targets.Num() ); + + const idVec3 &toPos = target.GetEntity()->GetEyePosition(); + + // calculate the relative times of all the objects + time = 0.0f; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = SEC2MS( time ); + lastTargetPos[ i ] = toPos; + + frac = 1.0f - ( float )i / ( float )targetTime.Num(); + time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f; + } + + // scale up the times to fit within throw_time + scale = throw_time / time; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale; + } + + BecomeActive( TH_THINK ); +} + +/* +=============== +idPhantomObjects::Think +================ +*/ +void idPhantomObjects::Think() { + int i; + int num; + float time; + idVec3 vel; + idVec3 ang; + idEntity *ent; + idActor *targetEnt; + idPhysics *entPhys; + trace_t tr; + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + if ( !( thinkFlags & TH_THINK ) ) { + BecomeInactive( thinkFlags & ~TH_THINK ); + return; + } + + targetEnt = target.GetEntity(); + if ( targetEnt == NULL || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) { + BecomeInactive( TH_THINK ); + return; + } + + const idVec3 &toPos = targetEnt->GetEyePosition(); + + num = 0; + for ( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + + if ( ent->fl.hidden ) { + // don't throw hidden objects + continue; + } + + if ( !targetTime[ i ] ) { + // already threw this object + continue; + } + + num++; + + time = MS2SEC( targetTime[ i ] - gameLocal.time ); + if ( time > shake_time ) { + continue; + } + + entPhys = ent->GetPhysics(); + const idVec3 &entOrg = entPhys->GetOrigin(); + + gameLocal.clip.TracePoint( tr, entOrg, toPos, MASK_OPAQUE, ent ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) { + lastTargetPos[ i ] = toPos; + } + + if ( time < 0.0f ) { + idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel ); + vel *= speed; + entPhys->SetLinearVelocity( vel ); + if ( !end_time ) { + targetTime[ i ] = 0; + } else { + targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait; + } + if ( ent->IsType( idMoveable::Type ) ) { + idMoveable *ment = static_cast( ent ); + ment->EnableDamage( true, 2.5f ); + } + } else { + // this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :) + ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z ); + ang *= ( 1.0f - time / shake_time ); + entPhys->SetAngularVelocity( ang ); + } + } + + if ( !num ) { + BecomeInactive( TH_THINK ); + } +} + +/* +=============================================================================== + +idShockwave + +=============================================================================== +*/ +CLASS_DECLARATION( idEntity, idShockwave ) +EVENT( EV_Activate, idShockwave::Event_Activate ) +END_CLASS + +/* +=============== +idShockwave::idShockwave +=============== +*/ +idShockwave::idShockwave() { + isActive = false; + startTime = 0; + duration = 0; + startSize = 0.f; + endSize = 0.f; + currentSize = 0.f; + magnitude = 0.f; + + height = 0.0f; + playerDamaged = false; + playerDamageSize = 0.0f; +} + +/* +=============== +idShockwave::~idShockwave +=============== +*/ +idShockwave::~idShockwave() { +} + +/* +=============== +idShockwave::Save +=============== +*/ +void idShockwave::Save( idSaveGame *savefile ) const { + savefile->WriteBool( isActive ); + savefile->WriteInt( startTime ); + savefile->WriteInt( duration ); + + savefile->WriteFloat( startSize ); + savefile->WriteFloat( endSize ); + savefile->WriteFloat( currentSize ); + + savefile->WriteFloat( magnitude ); + + savefile->WriteFloat( height ); + savefile->WriteBool( playerDamaged ); + savefile->WriteFloat( playerDamageSize ); +} + +/* +=============== +idShockwave::Restore +=============== +*/ +void idShockwave::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( isActive ); + savefile->ReadInt( startTime ); + savefile->ReadInt( duration ); + + savefile->ReadFloat( startSize ); + savefile->ReadFloat( endSize ); + savefile->ReadFloat( currentSize ); + + savefile->ReadFloat( magnitude ); + + savefile->ReadFloat( height ); + savefile->ReadBool( playerDamaged ); + savefile->ReadFloat( playerDamageSize ); + +} + +/* +=============== +idShockwave::Spawn +=============== +*/ +void idShockwave::Spawn() { + + spawnArgs.GetInt( "duration", "1000", duration ); + spawnArgs.GetFloat( "startsize", "8", startSize ); + spawnArgs.GetFloat( "endsize", "512", endSize ); + spawnArgs.GetFloat( "magnitude", "100", magnitude ); + + spawnArgs.GetFloat( "height", "0", height); + spawnArgs.GetFloat( "player_damage_size", "20", playerDamageSize); + + if ( spawnArgs.GetBool( "start_on" ) ) { + ProcessEvent( &EV_Activate, this ); + } +} + +/* +=============== +idShockwave::Think +=============== +*/ +void idShockwave::Think() { + int endTime; + + if ( !isActive ) { + BecomeInactive( TH_THINK ); + return; + } + + endTime = startTime + duration; + + if ( gameLocal.time < endTime ) { + float u; + float newSize; + + // Expand shockwave + u = (float)(gameLocal.time - startTime) / (float)duration; + newSize = startSize + u * (endSize - startSize); + + // Find all clipmodels between currentSize and newSize + idVec3 pos, end; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idClipModel *clip; + idEntity *ent; + int i, listedClipModels; + + // Set bounds + pos = GetPhysics()->GetOrigin(); + + float zVal; + if(!height) { + zVal = newSize; + } else { + zVal = height/2.0f; + } + + //Expand in a sphere + end = pos + idVec3( newSize, newSize, zVal ); + idBounds bounds( end ); + end = pos + idVec3( -newSize, -newSize, -zVal ); + bounds.AddPoint( end ); + + if(g_debugShockwave.GetBool()) { + gameRenderWorld->DebugBounds(colorRed, bounds, vec3_origin); + } + + listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + if ( !ent->IsType( idMoveable::Type ) && !ent->IsType( idAFEntity_Base::Type ) && !ent->IsType( idPlayer::Type )) { + continue; + } + + idVec3 point = ent->GetPhysics()->GetOrigin(); + idVec3 force = point - pos; + + float dist = force.Normalize(); + + if(ent->IsType( idPlayer::Type )) { + + if(ent->GetPhysics()->GetAbsBounds().IntersectsBounds(bounds)) { + + //For player damage we check the current radius and a specified player damage ring size + if ( dist <= newSize && dist > newSize-playerDamageSize ) { + + idStr damageDef = spawnArgs.GetString("def_player_damage", ""); + if(damageDef.Length() > 0 && !playerDamaged) { + + playerDamaged = true; //Only damage once per shockwave + idPlayer* player = static_cast< idPlayer* >( ent ); + idVec3 dir = ent->GetPhysics()->GetOrigin() - pos; + dir.NormalizeFast(); + player->Damage(NULL, NULL, dir, damageDef, 1.0f, INVALID_JOINT); + } + } + } + + } else { + + // If the object is inside the current expansion... + if ( dist <= newSize && dist > currentSize ) { + force.z += 4.f; + force.NormalizeFast(); + + if ( ent->IsType( idAFEntity_Base::Type ) ) { + force = force * (ent->GetPhysics()->GetMass() * magnitude * 0.01f); + } else { + force = force * ent->GetPhysics()->GetMass() * magnitude; + } + + // Kick it up, move force point off object origin + float rad = ent->GetPhysics()->GetBounds().GetRadius(); + point.x += gameLocal.random.CRandomFloat() * rad; + point.y += gameLocal.random.CRandomFloat() * rad; + + int j; + for( j=0; j < ent->GetPhysics()->GetNumClipModels(); j++ ) { + ent->GetPhysics()->AddForce( j, point, force ); + } + } + } + } + + // Update currentSize for next frame + currentSize = newSize; + + } else { + + // turn off + isActive = false; + } +} + +/* +=============== +idShockwave::Event_Activate +=============== +*/ +void idShockwave::Event_Activate( idEntity *activator ) { + + isActive = true; + startTime = gameLocal.time; + playerDamaged = false; + + BecomeActive( TH_THINK ); +} + + +/* +=============================================================================== + +idFuncMountedObject + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncMountedObject ) +EVENT( EV_Touch, idFuncMountedObject::Event_Touch ) +EVENT( EV_Activate, idFuncMountedObject::Event_Activate ) +END_CLASS + +/* +=============== +idFuncMountedObject::idFuncMountedObject +=============== +*/ +idFuncMountedObject::idFuncMountedObject() { + isMounted = false; + scriptFunction = NULL; + mountedPlayer = NULL; + harc = 0; + varc = 0; +} + +/* +=============== +idFuncMountedObject::idFuncMountedObject +=============== +*/ +idFuncMountedObject::~idFuncMountedObject() { +} + +/* +=============== +idFuncMountedObject::Spawn +=============== +*/ +void idFuncMountedObject::Spawn() { + // Get viewOffset + spawnArgs.GetInt( "harc", "45", harc ); + spawnArgs.GetInt( "varc", "30", varc ); + + // Get script function + idStr funcName = spawnArgs.GetString( "call", "" ); + if ( funcName.Length() ) { + scriptFunction = gameLocal.program.FindFunction( funcName ); + if ( scriptFunction == NULL ) { + gameLocal.Warning( "idFuncMountedObject '%s' at (%s) calls unknown function '%s'\n", name.c_str(), GetPhysics()->GetOrigin().ToString(0), funcName.c_str() ); + } + } + + BecomeActive( TH_THINK ); +} + +/* +================ +idFuncMountedObject::Think +================ +*/ +void idFuncMountedObject::Think() { + + idEntity::Think(); +} + +/* +================ +idFuncMountedObject::GetViewInfo +================ +*/ +void idFuncMountedObject::GetAngleRestrictions( int &yaw_min, int &yaw_max, int &pitch ) { + idMat3 axis; + idAngles angs; + + axis = GetPhysics()->GetAxis(); + angs = axis.ToAngles(); + + yaw_min = angs.yaw - harc; + yaw_min = idMath::AngleNormalize180( yaw_min ); + + yaw_max = angs.yaw + harc; + yaw_max = idMath::AngleNormalize180( yaw_max ); + + pitch = varc; +} + +/* +================ +idFuncMountedObject::Event_Touch +================ +*/ +void idFuncMountedObject::Event_Touch( idEntity *other, trace_t *trace ) { + if ( common->IsClient() ) { + return; + } + + ProcessEvent( &EV_Activate, other ); +} + +/* +================ +idFuncMountedObject::Event_Activate +================ +*/ +void idFuncMountedObject::Event_Activate( idEntity *activator ) { + if ( !isMounted && activator->IsType( idPlayer::Type ) ) { + idPlayer *client = (idPlayer *)activator; + + mountedPlayer = client; + + /* + // Place player at path_corner targeted by mounted object + int i; + idPathCorner *spot; + + for ( i = 0; i < targets.Num(); i++ ) { + if ( targets[i]->IsType( idPathCorner::Type ) ) { + spot = (idPathCorner*)targets[i]; + break; + } + } + + mountedPlayer->GetPhysics()->SetOrigin( spot->GetPhysics()->GetOrigin() ); + mountedPlayer->GetPhysics()->SetAxis( spot->GetPhysics()->GetAxis() ); + */ + + mountedPlayer->Bind( this, true ); + mountedPlayer->mountedObject = this; + + // Call a script function + idThread *mountthread; + if ( scriptFunction ) { + mountthread = new idThread( scriptFunction ); + mountthread->DelayedStart( 0 ); + } + + isMounted = true; + } +} + +/* +=============================================================================== + +idFuncMountedWeapon + +=============================================================================== +*/ +CLASS_DECLARATION( idFuncMountedObject, idFuncMountedWeapon ) +EVENT( EV_PostSpawn, idFuncMountedWeapon::Event_PostSpawn ) +END_CLASS + +idFuncMountedWeapon::idFuncMountedWeapon() { + turret = NULL; + weaponLastFireTime = 0; + weaponFireDelay = 0; + projectile = NULL; +} + +idFuncMountedWeapon::~idFuncMountedWeapon() { +} + + +void idFuncMountedWeapon::Spawn() { + + // Get projectile info + projectile = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_projectile" ), false ); + if ( !projectile ) { + gameLocal.Warning( "Invalid projectile on func_mountedweapon." ); + } + + float firerate; + spawnArgs.GetFloat( "firerate", "3", firerate ); + weaponFireDelay = 1000.f / firerate; + + // Get the firing sound + idStr fireSound; + spawnArgs.GetString( "snd_fire", "", fireSound ); + soundFireWeapon = declManager->FindSound( fireSound ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +void idFuncMountedWeapon::Think() { + + if ( isMounted && turret ) { + idVec3 vec = mountedPlayer->viewAngles.ToForward(); + idAngles ang = mountedPlayer->GetLocalVector( vec ).ToAngles(); + + turret->GetPhysics()->SetAxis( ang.ToMat3() ); + turret->UpdateVisuals(); + + // Check for firing + if ( mountedPlayer->usercmd.buttons & BUTTON_ATTACK && ( gameLocal.time > weaponLastFireTime + weaponFireDelay ) ) { + // FIRE! + idEntity *ent; + idProjectile *proj; + idBounds projBounds; + idVec3 dir; + + gameLocal.SpawnEntityDef( *projectile, &ent ); + if ( !ent || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = spawnArgs.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + } + + mountedPlayer->GetViewPos( muzzleOrigin, muzzleAxis ); + + muzzleOrigin += ( muzzleAxis[0] * 128 ); + muzzleOrigin -= ( muzzleAxis[2] * 20 ); + + dir = muzzleAxis[0]; + + proj = static_cast(ent); + proj->Create( this, muzzleOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + proj->Launch( muzzleOrigin, dir, vec3_origin ); + StartSoundShader( soundFireWeapon, SND_CHANNEL_WEAPON, SSF_GLOBAL, false, NULL ); + + weaponLastFireTime = gameLocal.time; + } + } + + idFuncMountedObject::Think(); +} + +void idFuncMountedWeapon::Event_PostSpawn() { + + if ( targets.Num() >= 1 ) { + for ( int i=0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity()->IsType( idStaticEntity::Type ) ) { + turret = targets[i].GetEntity(); + break; + } + } + } else { + gameLocal.Warning( "idFuncMountedWeapon::Spawn: Please target one model for a turret\n" ); + } +} + + + + + + +/* +=============================================================================== + +idPortalSky + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPortalSky ) + EVENT( EV_PostSpawn, idPortalSky::Event_PostSpawn ) + EVENT( EV_Activate, idPortalSky::Event_Activate ) +END_CLASS + +/* +=============== +idPortalSky::idPortalSky +=============== +*/ +idPortalSky::idPortalSky() { + +} + +/* +=============== +idPortalSky::~idPortalSky +=============== +*/ +idPortalSky::~idPortalSky() { + +} + +/* +=============== +idPortalSky::Spawn +=============== +*/ +void idPortalSky::Spawn() { + if ( !spawnArgs.GetBool( "triggered" ) ) { + PostEventMS( &EV_PostSpawn, 1 ); + } +} + +/* +================ +idPortalSky::Event_PostSpawn +================ +*/ +void idPortalSky::Event_PostSpawn() { + gameLocal.SetPortalSkyEnt( this ); +} + +/* +================ +idPortalSky::Event_Activate +================ +*/ +void idPortalSky::Event_Activate( idEntity *activator ) { + gameLocal.SetPortalSkyEnt( this ); +} diff --git a/neo/d3xp/Misc.h b/neo/d3xp/Misc.h new file mode 100644 index 00000000..31527092 --- /dev/null +++ b/neo/d3xp/Misc.h @@ -0,0 +1,928 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_MISC_H__ +#define __GAME_MISC_H__ + + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +class idSpawnableEntity : public idEntity { +public: + CLASS_PROTOTYPE( idSpawnableEntity ); + + void Spawn(); + +private: +}; + +/* +=============================================================================== + + Potential spawning position for players. + The first time a player enters the game, they will be at an 'initial' spot. + Targets will be fired when someone spawns in on them. + + When triggered, will cause player to be teleported to spawn spot. + +=============================================================================== +*/ + +class idPlayerStart : public idEntity { +public: + CLASS_PROTOTYPE( idPlayerStart ); + + enum { + EVENT_TELEPORTPLAYER = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + idPlayerStart(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + int teleportStage; + + void Event_TeleportPlayer( idEntity *activator ); + void Event_TeleportStage( idEntity *player ); + void TeleportPlayer( idPlayer *player ); +}; + + +/* +=============================================================================== + + Non-displayed entity used to activate triggers when it touches them. + Bind to a mover to have the mover activate a trigger as it moves. + When target by triggers, activating the trigger will toggle the + activator on and off. Check "start_off" to have it spawn disabled. + +=============================================================================== +*/ + +class idActivator : public idEntity { +public: + CLASS_PROTOTYPE( idActivator ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + +private: + bool stay_on; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + Path entities for monsters to follow. + +=============================================================================== +*/ +class idPathCorner : public idEntity { +public: + CLASS_PROTOTYPE( idPathCorner ); + + void Spawn(); + + static void DrawDebugInfo(); + + static idPathCorner *RandomPath( const idEntity *source, const idEntity *ignore ); + +private: + void Event_RandomPath(); +}; + + +/* +=============================================================================== + + Object that fires targets and changes shader parms when damaged. + +=============================================================================== +*/ + +class idDamagable : public idEntity { +public: + CLASS_PROTOTYPE( idDamagable ); + + idDamagable(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void Hide(); + virtual void Show(); + +private: + int count; + int nextTriggerTime; + + void BecomeBroken( idEntity *activator ); + void Event_BecomeBroken( idEntity *activator ); + void Event_RestoreDamagable(); +}; + + +/* +=============================================================================== + + Hidden object that explodes when activated + +=============================================================================== +*/ + +class idExplodable : public idEntity { +public: + CLASS_PROTOTYPE( idExplodable ); + + void Spawn(); + +private: + void Event_Explode( idEntity *activator ); +}; + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +class idSpring : public idEntity { +public: + CLASS_PROTOTYPE( idSpring ); + + void Spawn(); + + virtual void Think(); + +private: + idEntity * ent1; + idEntity * ent2; + int id1; + int id2; + idVec3 p1; + idVec3 p2; + idForce_Spring spring; + + void Event_LinkSpring(); +}; + + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +class idForceField : public idEntity { +public: + CLASS_PROTOTYPE( idForceField ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + + virtual void Think(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ) ; +private: + idForce_Field forceField; + + void Toggle(); + + void Event_Activate( idEntity *activator ); + void Event_Toggle(); + void Event_FindTargets(); +}; + + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +class idAnimated : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAnimated ); + + idAnimated(); + ~idAnimated(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual bool LoadAF(); + bool StartRagdoll(); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +private: + int num_anims; + int current_anim_index; + int anim; + int blendFrames; + jointHandle_t soundJoint; + idEntityPtr activator; + bool activated; + int achievement; + + void PlayNextAnim(); + + void Event_Activate( idEntity *activator ); + void Event_Start(); + void Event_StartRagdoll(); + void Event_AnimDone( int animIndex ); + void Event_Footstep(); + void Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ); + void Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ); + void Event_SetAnimation( const char *animName ); + void Event_GetAnimationLength(); +}; + + +/* +=============================================================================== + + idStaticEntity + +=============================================================================== +*/ + +class idStaticEntity : public idEntity { +public: + CLASS_PROTOTYPE( idStaticEntity ); + + idStaticEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + void ShowEditingDialog(); + virtual void Hide(); + virtual void Show(); + void Fade( const idVec4 &to, float fadeTime ); + virtual void Think(); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +private: + void Event_Activate( idEntity *activator ); + + int spawnTime; + bool active; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; + bool runGui; +}; + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + +class idFuncEmitter : public idStaticEntity { +public: + CLASS_PROTOTYPE( idFuncEmitter ); + + idFuncEmitter(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + void Event_Activate( idEntity *activator ); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +private: + bool hidden; + +}; + + +/* +=============================================================================== + +idFuncShootProjectile + +=============================================================================== +*/ + +class idFuncShootProjectile : public idStaticEntity { +public: + CLASS_PROTOTYPE( idFuncShootProjectile ); + + idFuncShootProjectile(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + void Event_Activate( idEntity *activator ); + + virtual void Think(); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +private: + int mRespawnDelay; + int mRespawnTime; + float mShootSpeed; + idVec3 mShootDir; + idStr mEntityDefName; + idEntityPtr< idEntity > mLastProjectile; + +}; + + +/* +=============================================================================== + +idFuncSmoke + +=============================================================================== +*/ + +class idFuncSmoke : public idEntity { +public: + CLASS_PROTOTYPE( idFuncSmoke ); + + idFuncSmoke(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + void Event_Activate( idEntity *activator ); + +private: + int smokeTime; + const idDeclParticle * smoke; + bool restart; +}; + + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + +class idFuncSplat : public idFuncEmitter { +public: + CLASS_PROTOTYPE( idFuncSplat ); + + idFuncSplat(); + + void Spawn(); + +private: + void Event_Activate( idEntity *activator ); + void Event_Splat(); +}; + + +/* +=============================================================================== + +idTextEntity + +=============================================================================== +*/ + +class idTextEntity : public idEntity { +public: + CLASS_PROTOTYPE( idTextEntity ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + +private: + idStr text; + bool playerOriented; +}; + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +class idLocationEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationEntity ); + + void Spawn(); + + const char * GetLocation() const; + +private: +}; + +class idLocationSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationSeparatorEntity ); + + void Spawn(); + +private: +}; + +class idVacuumSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumSeparatorEntity ); + + idVacuumSeparatorEntity(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class idVacuumEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumEntity ); + + void Spawn(); + +private: +}; + + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +class idBeam : public idEntity { +public: + CLASS_PROTOTYPE( idBeam ); + + idBeam(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + void SetMaster( idBeam *masterbeam ); + void SetBeamTarget( const idVec3 &origin ); + + virtual void Show(); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +private: + void Event_MatchTarget(); + void Event_Activate( idEntity *activator ); + + idEntityPtr target; + idEntityPtr master; +}; + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +class idRenderModelLiquid; + +class idLiquid : public idEntity { +public: + CLASS_PROTOTYPE( idLiquid ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + void Event_Touch( idEntity *other, trace_t *trace ); + + + idRenderModelLiquid *model; +}; + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +class idShaking : public idEntity { +public: + CLASS_PROTOTYPE( idShaking ); + + idShaking(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idPhysics_Parametric physicsObj; + bool active; + + void BeginShaking(); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ + +class idEarthQuake : public idEntity { +public: + CLASS_PROTOTYPE( idEarthQuake ); + + idEarthQuake(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + +private: + int nextTriggerTime; + int shakeStopTime; + float wait; + float random; + bool triggered; + bool playerOriented; + bool disabled; + float shakeTime; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +class idFuncPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncPortal ); + + idFuncPortal(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + qhandle_t portal; + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +class idFuncAASPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASPortal ); + + idFuncAASPortal(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +class idFuncAASObstacle : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASObstacle ); + + idFuncAASObstacle(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +class idFuncRadioChatter : public idEntity { +public: + CLASS_PROTOTYPE( idFuncRadioChatter ); + + idFuncRadioChatter(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + float time; + void Event_Activate( idEntity *activator ); + void Event_ResetRadioHud( idEntity *activator ); +}; + + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +class idPhantomObjects : public idEntity { +public: + CLASS_PROTOTYPE( idPhantomObjects ); + + idPhantomObjects(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + +private: + void Event_Activate( idEntity *activator ); + void Event_Throw(); + void Event_ShakeObject( idEntity *object, int starttime ); + + int end_time; + float throw_time; + float shake_time; + idVec3 shake_ang; + float speed; + int min_wait; + int max_wait; + idEntityPtrtarget; + idList targetTime; + idList lastTargetPos; +}; + +/* +=============================================================================== + +idShockwave + +=============================================================================== +*/ +class idShockwave : public idEntity { +public: + CLASS_PROTOTYPE( idShockwave ); + + idShockwave(); + ~idShockwave(); + + void Spawn(); + void Think(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + void Event_Activate( idEntity *activator ); + + bool isActive; + int startTime; + int duration; + + float startSize; + float endSize; + float currentSize; + + float magnitude; + + float height; + bool playerDamaged; + float playerDamageSize; + +}; + +/* +=============================================================================== + +idFuncMountedObject + +=============================================================================== +*/ +class idFuncMountedObject : public idEntity { +public: + CLASS_PROTOTYPE( idFuncMountedObject ); + + idFuncMountedObject(); + ~idFuncMountedObject(); + + void Spawn(); + void Think(); + + void GetAngleRestrictions( int &yaw_min, int &yaw_max, int &pitch ); + +private: + int harc; + int varc; + + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Activate( idEntity *activator ); + +public: + bool isMounted; + function_t * scriptFunction; + idPlayer * mountedPlayer; +}; + + +class idFuncMountedWeapon : public idFuncMountedObject { +public: + CLASS_PROTOTYPE( idFuncMountedWeapon ); + + idFuncMountedWeapon(); + ~idFuncMountedWeapon(); + + void Spawn(); + void Think(); + +private: + + // The actual turret that moves with the player's view + idEntity * turret; + + // the muzzle bone's position, used for launching projectiles and trailing smoke + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + float weaponLastFireTime; + float weaponFireDelay; + + const idDict * projectile; + + const idSoundShader *soundFireWeapon; + + void Event_PostSpawn(); +}; + +/* +=============================================================================== + +idPortalSky + +=============================================================================== +*/ +class idPortalSky : public idEntity { +public: + CLASS_PROTOTYPE( idPortalSky ); + + idPortalSky(); + ~idPortalSky(); + + void Spawn(); + void Event_PostSpawn(); + void Event_Activate( idEntity *activator ); +}; + + +#endif /* !__GAME_MISC_H__ */ diff --git a/neo/d3xp/Moveable.cpp b/neo/d3xp/Moveable.cpp new file mode 100644 index 00000000..0ec78c6a --- /dev/null +++ b/neo/d3xp/Moveable.cpp @@ -0,0 +1,1355 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idMoveable + +=============================================================================== +*/ + +const idEventDef EV_BecomeNonSolid( "becomeNonSolid" ); +const idEventDef EV_SetOwnerFromSpawnArgs( "" ); +const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' ); +const idEventDef EV_EnableDamage( "enableDamage", "f" ); + +CLASS_DECLARATION( idEntity, idMoveable ) + EVENT( EV_Activate, idMoveable::Event_Activate ) + EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs ) + EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest ) + EVENT( EV_EnableDamage, idMoveable::Event_EnableDamage ) +END_CLASS + + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +idMoveable::idMoveable +================ +*/ +idMoveable::idMoveable() { + minDamageVelocity = 100.0f; + maxDamageVelocity = 200.0f; + nextCollideFxTime = 0; + nextDamageTime = 0; + nextSoundTime = 0; + initialSpline = NULL; + initialSplineDir = vec3_zero; + explode = false; + unbindOnDeath = false; + allowStep = false; + canDamage = false; + attacker = NULL; +} + +/* +================ +idMoveable::~idMoveable +================ +*/ +idMoveable::~idMoveable() { + delete initialSpline; + initialSpline = NULL; +} + +/* +================ +idMoveable::Spawn +================ +*/ +void idMoveable::Spawn() { + idTraceModel trm; + float density, friction, bouncyness, mass; + int clipShrink; + idStr clipModelName; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { + gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + // if the model should be shrinked + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + explode = spawnArgs.GetBool( "explode" ); + unbindOnDeath = spawnArgs.GetBool( "unbindondeath" ); + + fxCollide = spawnArgs.GetString( "fx_collide" ); + nextCollideFxTime = 0; + + fl.takedamage = true; + damage = spawnArgs.GetString( "def_damage", "" ); + monsterDamage = spawnArgs.GetString( "monster_damage", "" ); + fl.networkSync = true; + attacker = NULL; + canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true; + minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "300" ); // _D3XP + maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "700" ); // _D3XP + nextDamageTime = 0; + nextSoundTime = 0; + + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + + if ( health ) { + if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) { + gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() ); + } + } + + // setup the physics + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( trm ), density ); + physicsObj.GetClipModel()->SetMaterial( GetRenderModelMaterial() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetFloat( "mass", "10", mass ) ) { + physicsObj.SetMass( mass ); + } + + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + physicsObj.DropToFloor(); + } + + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + + allowStep = spawnArgs.GetBool( "allowStep", "1" ); + + PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 ); +} + +/* +================ +idMoveable::Save +================ +*/ +void idMoveable::Save( idSaveGame *savefile ) const { + + savefile->WriteString( brokenModel ); + savefile->WriteString( damage ); + savefile->WriteString( monsterDamage ); + savefile->WriteObject( attacker ); + savefile->WriteString( fxCollide ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteFloat( minDamageVelocity ); + savefile->WriteFloat( maxDamageVelocity ); + savefile->WriteBool( explode ); + savefile->WriteBool( unbindOnDeath ); + savefile->WriteBool( allowStep ); + savefile->WriteBool( canDamage ); + savefile->WriteInt( nextDamageTime ); + savefile->WriteInt( nextSoundTime ); + savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 ); + savefile->WriteVec3( initialSplineDir ); + + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idMoveable::Restore +================ +*/ +void idMoveable::Restore( idRestoreGame *savefile ) { + int initialSplineTime; + + savefile->ReadString( brokenModel ); + savefile->ReadString( damage ); + savefile->ReadString( monsterDamage ); + savefile->ReadObject( reinterpret_cast( attacker ) ); + savefile->ReadString( fxCollide ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadFloat( minDamageVelocity ); + savefile->ReadFloat( maxDamageVelocity ); + savefile->ReadBool( explode ); + savefile->ReadBool( unbindOnDeath ); + savefile->ReadBool( allowStep ); + savefile->ReadBool( canDamage ); + savefile->ReadInt( nextDamageTime ); + savefile->ReadInt( nextSoundTime ); + savefile->ReadInt( initialSplineTime ); + savefile->ReadVec3( initialSplineDir ); + + if ( initialSplineTime != -1 ) { + InitInitialSpline( initialSplineTime ); + } else { + initialSpline = NULL; + } + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +================ +idMoveable::Hide +================ +*/ +void idMoveable::Hide() { + idEntity::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMoveable::Show +================ +*/ +void idMoveable::Show() { + idEntity::Show(); + if ( !spawnArgs.GetBool( "nonsolid" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } +} + +/* +================= +idMoveable::Collide +================= +*/ +bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + idVec3 dir; + idEntity *ent; + + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { + f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + + // _D3XP :: changes relating to the addition of monsterDamage + if ( !common->IsClient() && canDamage && gameLocal.time > nextDamageTime ) { + bool hasDamage = damage.Length() > 0; + bool hasMonsterDamage = monsterDamage.Length() > 0; + + if ( hasDamage || hasMonsterDamage ) { + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent && v > minDamageVelocity ) { + f = v > maxDamageVelocity ? 1.0f : idMath::Sqrt( v - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) ); + dir = velocity; + dir.NormalizeFast(); + if ( ent->IsType( idAI::Type ) && hasMonsterDamage ) { + if ( attacker ) { + ent->Damage( this, attacker, dir, monsterDamage, f, INVALID_JOINT ); + } + else { + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, monsterDamage, f, INVALID_JOINT ); + } + } else if ( hasDamage ) { + // in multiplayer, scale damage wrt mass of object + if ( common->IsMultiplayer() ) { + f *= GetPhysics()->GetMass() * g_moveableDamageScale.GetFloat(); + } + + if ( attacker ) { + ent->Damage( this, attacker, dir, damage, f, INVALID_JOINT ); + } + else { + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT ); + } + } + + nextDamageTime = gameLocal.time + 1000; + } + } + } + + if ( this->IsType( idExplodingBarrel::Type ) ) { + idExplodingBarrel *ebarrel = static_cast(this); + + if ( !ebarrel->IsStable() ) { + PostEventSec( &EV_Explode, 0.04f ); + } + } + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime ) { + idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false ); + nextCollideFxTime = gameLocal.time + 3500; + } + + return false; +} + +/* +============ +idMoveable::Killed +============ +*/ +void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( unbindOnDeath ) { + Unbind(); + } + + if ( brokenModel != "" ) { + SetModel( brokenModel ); + } + + if ( explode ) { + if ( brokenModel == "" ) { + PostEventMS( &EV_Remove, 1000 ); + } + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets( this ); + + fl.takedamage = false; +} + +/* +================ +idMoveable::AllowStep +================ +*/ +bool idMoveable::AllowStep() const { + return allowStep; +} + +/* +================ +idMoveable::BecomeNonSolid +================ +*/ +void idMoveable::BecomeNonSolid() { + // set CONTENTS_RENDERMODEL so bullets still collide with the moveable + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +} + +/* +================ +idMoveable::EnableDamage +================ +*/ +void idMoveable::EnableDamage( bool enable, float duration ) { + if ( canDamage == enable ) { + return; + } + + canDamage = enable; + if ( duration ) { + PostEventSec( &EV_EnableDamage, duration, ( /*_D3XP*/enable ) ? 0.0f : 1.0f ); + } +} + +/* +================ +idMoveable::InitInitialSpline +================ +*/ +void idMoveable::InitInitialSpline( int startTime ) { + int initialSplineTime; + + initialSpline = GetSpline(); + initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" ); + + if ( initialSpline != NULL ) { + initialSpline->MakeUniform( initialSplineTime ); + initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) ); + initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime ); + initialSplineDir *= physicsObj.GetAxis().Transpose(); + initialSplineDir.Normalize(); + BecomeActive( TH_THINK ); + } +} + +/* +================ +idMoveable::FollowInitialSplinePath +================ +*/ +bool idMoveable::FollowInitialSplinePath() { + if ( initialSpline != NULL ) { + if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { + idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * com_engineHz_latched; + physicsObj.SetLinearVelocity( linearVelocity ); + + idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); + idVec3 dir = initialSplineDir * physicsObj.GetAxis(); + idVec3 angularVelocity = dir.Cross( splineDir ); + angularVelocity.Normalize(); + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * com_engineHz_latched; + physicsObj.SetAngularVelocity( angularVelocity ); + return true; + } else { + delete initialSpline; + initialSpline = NULL; + } + } + return false; +} + +/* +================ +idMoveable::ClientThink +================ +*/ +void idMoveable::ClientThink( const int curTime, const float fraction, const bool predict ) { + InterpolatePhysicsOnly( fraction ); + Present(); +} + +/* +================ +idMoveable::Think +================ +*/ +void idMoveable::Think() { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() ) { + BecomeInactive( TH_THINK ); + } + } + idEntity::Think(); +} + +/* +================ +idMoveable::GetRenderModelMaterial +================ +*/ +const idMaterial *idMoveable::GetRenderModelMaterial() const { + if ( renderEntity.customShader ) { + return renderEntity.customShader; + } + if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) { + return renderEntity.hModel->Surface( 0 )->shader; + } + return NULL; +} + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idMoveable::WriteToSnapshot( idBitMsg &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idMoveable::ReadFromSnapshot( const idBitMsg &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idMoveable::Event_BecomeNonSolid +================ +*/ +void idMoveable::Event_BecomeNonSolid() { + BecomeNonSolid(); +} + +/* +================ +idMoveable::SetAttacker +================ +*/ +void idMoveable::SetAttacker( idEntity *ent ) { + attacker = ent; +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idMoveable::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + if ( !spawnArgs.GetInt( "notPushable" ) ) { + physicsObj.EnableImpact(); + } + + physicsObj.Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } + + InitInitialSpline( gameLocal.time ); +} + +/* +================ +idMoveable::Event_SetOwnerFromSpawnArgs +================ +*/ +void idMoveable::Event_SetOwnerFromSpawnArgs() { + idStr owner; + + if ( spawnArgs.GetString( "owner", "", owner ) ) { + ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) ); + } +} + +/* +================ +idMoveable::Event_IsAtRest +================ +*/ +void idMoveable::Event_IsAtRest() { + idThread::ReturnInt( physicsObj.IsAtRest() ); +} + +/* +================ +idMoveable::Event_EnableDamage +================ +*/ +void idMoveable::Event_EnableDamage( float enable ) { + // clear out attacker + attacker = NULL; + + canDamage = ( enable != 0.0f ); +} + + +/* +=============================================================================== + + idBarrel + +=============================================================================== +*/ + +CLASS_DECLARATION( idMoveable, idBarrel ) +END_CLASS + +/* +================ +idBarrel::idBarrel +================ +*/ +idBarrel::idBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +idBarrel::Save +================ +*/ +void idBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +idBarrel::Restore +================ +*/ +void idBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +idBarrel::BarrelThink +================ +*/ +void idBarrel::BarrelThink() { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // run physics + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + onGround = GetPhysics()->HasGroundContacts(); + curOrigin = GetPhysics()->GetOrigin(); + curAxis = GetPhysics()->GetAxis(); + + // if the barrel is on the ground + if ( onGround ) { + gravityNormal = GetPhysics()->GetGravityNormal(); + + dir = curOrigin - lastOrigin; + dir -= gravityNormal * dir * gravityNormal; + movedDistance = dir.LengthSqr(); + + // if the barrel moved and the barrel is not aligned with the gravity direction + if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) { + + // barrel movement since last think frame orthogonal to the barrel axis + movedDistance = idMath::Sqrt( movedDistance ); + dir *= 1.0f / movedDistance; + movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance; + + // get rotation about barrel axis since last think frame + angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3]; + angle = idMath::ACos( angle ); + // distance along cylinder hull + rotatedDistance = angle * radius; + + // if the barrel moved further than it rotated about it's axis + if ( movedDistance > rotatedDistance ) { + + // additional rotation of the visual model to make it look + // like the barrel rolls instead of slides + angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI); + if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) { + additionalRotation += angle; + } else { + additionalRotation -= angle; + } + dir = vec3_origin; + dir[barrelAxis] = 1.0f; + additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3(); + } + } + } + + // save state for next think + lastOrigin = curOrigin; + lastAxis = curAxis; + } + + Present(); +} + +/* +================ +idBarrel::Think +================ +*/ +void idBarrel::Think() { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +idBarrel::GetPhysicsToVisualTransform +================ +*/ +bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +idBarrel::Spawn +================ +*/ +void idBarrel::Spawn() { + const idBounds &bounds = GetPhysics()->GetBounds(); + + // radius of the barrel cylinder + radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f; + + // always a vertical barrel with cylinder axis parallel to the z-axis + barrelAxis = 2; + + lastOrigin = GetPhysics()->GetOrigin(); + lastAxis = GetPhysics()->GetAxis(); + + additionalRotation = 0.0f; + additionalAxis.Identity(); + + fl.networkSync = true; +} + +/* +================ +idBarrel::ClientThink +================ +*/ +void idBarrel::ClientThink( const int curTime, const float fraction, const bool predict ) { + InterpolatePhysics( fraction ); + Present(); +} + + +/* +=============================================================================== + +idExplodingBarrel + +=============================================================================== +*/ +const idEventDef EV_Respawn( "" ); +const idEventDef EV_TriggerTargets( "" ); + +CLASS_DECLARATION( idBarrel, idExplodingBarrel ) + EVENT( EV_Activate, idExplodingBarrel::Event_Activate ) + EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn ) + EVENT( EV_Explode, idExplodingBarrel::Event_Explode ) + EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets ) +END_CLASS + +/* +================ +idExplodingBarrel::idExplodingBarrel +================ +*/ +idExplodingBarrel::idExplodingBarrel() { + spawnOrigin.Zero(); + spawnAxis.Zero(); + state = NORMAL; + isStable = true; + particleModelDefHandle = -1; + lightDefHandle = -1; + memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) ); + memset( &light, 0, sizeof( light ) ); + particleTime = 0; + lightTime = 0; + time = 0.0f; +} + +/* +================ +idExplodingBarrel::~idExplodingBarrel +================ +*/ +idExplodingBarrel::~idExplodingBarrel() { + if ( particleModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( particleModelDefHandle ); + } + if ( lightDefHandle >= 0 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + } +} + +/* +================ +idExplodingBarrel::Save +================ +*/ +void idExplodingBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + + savefile->WriteInt( state ); + savefile->WriteInt( particleModelDefHandle ); + savefile->WriteInt( lightDefHandle ); + + savefile->WriteRenderEntity( particleRenderEntity ); + savefile->WriteRenderLight( light ); + + savefile->WriteInt( particleTime ); + savefile->WriteInt( lightTime ); + savefile->WriteFloat( time ); + + savefile->WriteBool( isStable ); +} + +/* +================ +idExplodingBarrel::Restore +================ +*/ +void idExplodingBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + + savefile->ReadInt( (int &)state ); + savefile->ReadInt( (int &)particleModelDefHandle ); + savefile->ReadInt( (int &)lightDefHandle ); + + savefile->ReadRenderEntity( particleRenderEntity ); + savefile->ReadRenderLight( light ); + + savefile->ReadInt( particleTime ); + savefile->ReadInt( lightTime ); + savefile->ReadFloat( time ); + + savefile->ReadBool( isStable ); + + if ( lightDefHandle != -1 ) { + lightDefHandle = gameRenderWorld->AddLightDef( &light ); + } + if ( particleModelDefHandle != -1 ) { + particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity ); + } +} + +/* +================ +idExplodingBarrel::Spawn +================ +*/ +void idExplodingBarrel::Spawn() { + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = true; + isStable = true; + fl.networkSync = true; + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + state = NORMAL; + particleModelDefHandle = -1; + lightDefHandle = -1; + lightTime = 0; + particleTime = 0; + time = spawnArgs.GetFloat( "time" ); + memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) ); + memset( &light, 0, sizeof( light ) ); +} + +/* +================ +idExplodingBarrel::UpdateLight +================ +*/ +void idExplodingBarrel::UpdateLight() { + if ( lightDefHandle >= 0 ){ + if ( state == BURNING ) { + // ramp the color up over 250 ms + float pct = (gameLocal.serverTime - lightTime) / 250.f; + if ( pct > 1.0f ) { + pct = 1.0f; + } + light.origin = physicsObj.GetAbsBounds().GetCenter(); + light.axis = mat3_identity; + light.shaderParms[ SHADERPARM_RED ] = pct; + light.shaderParms[ SHADERPARM_GREEN ] = pct; + light.shaderParms[ SHADERPARM_BLUE ] = pct; + light.shaderParms[ SHADERPARM_ALPHA ] = pct; + gameRenderWorld->UpdateLightDef( lightDefHandle, &light ); + } else { + if ( gameLocal.serverTime - lightTime > 250 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } + return; + } + } +} + +/* +================ +idExplodingBarrel::ClientThink +================ +*/ +void idExplodingBarrel::ClientThink( const int curTime, const float fraction, const bool predict ) { + UpdateLight(); + InterpolatePhysics( fraction ); + Present(); +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Think() { + idBarrel::BarrelThink(); + + UpdateLight(); + + if ( !common->IsClient() && state != BURNING && state != EXPLODING ) { + BecomeInactive( TH_THINK ); + return; + } + + if ( particleModelDefHandle >= 0 ){ + particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + particleRenderEntity.axis = mat3_identity; + gameRenderWorld->UpdateEntityDef( particleModelDefHandle, &particleRenderEntity ); + } +} + +/* +================ +idExplodingBarrel::SetStability +================ +*/ +void idExplodingBarrel::SetStability( bool stability ) { + isStable = stability; +} + +/* +================ +idExplodingBarrel::IsStable +================ +*/ +bool idExplodingBarrel::IsStable() { + return isStable; +} + +/* +================ +idExplodingBarrel::StartBurning +================ +*/ +void idExplodingBarrel::StartBurning() { + state = BURNING; + AddParticles( "barrelfire.prt", true ); +} + +/* +================ +idExplodingBarrel::StartBurning +================ +*/ +void idExplodingBarrel::StopBurning() { + state = NORMAL; + + if ( particleModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( particleModelDefHandle ); + particleModelDefHandle = -1; + + particleTime = 0; + memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) ); + } +} + +/* +================ +idExplodingBarrel::AddParticles +================ +*/ +void idExplodingBarrel::AddParticles( const char *name, bool burn ) { + if ( name && *name ) { + int explicitTimeGroup = timeGroup; + SetTimeState explicitTS( explicitTimeGroup ); + if ( particleModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( particleModelDefHandle ); + } + memset( &particleRenderEntity, 0, sizeof ( particleRenderEntity ) ); + idRenderModel* modelDef = renderModelManager->FindModel( name ); + if ( modelDef ) { + particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + particleRenderEntity.axis = mat3_identity; + particleRenderEntity.hModel = modelDef; + float rgb = ( burn ) ? 0.0f : 1.0f; + particleRenderEntity.shaderParms[ SHADERPARM_RED ] = rgb; + particleRenderEntity.shaderParms[ SHADERPARM_GREEN ] = rgb; + particleRenderEntity.shaderParms[ SHADERPARM_BLUE ] = rgb; + particleRenderEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb; + particleRenderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.realClientTime ); + particleRenderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 ); + particleRenderEntity.timeGroup = explicitTimeGroup; + particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity ); + if ( burn ) { + BecomeActive( TH_THINK ); + } + particleTime = gameLocal.realClientTime; + } + } +} + +/* +================ +idExplodingBarrel::AddLight +================ +*/ +void idExplodingBarrel::AddLight( const char *name, bool burn ) { + if ( lightDefHandle >= 0 ){ + gameRenderWorld->FreeLightDef( lightDefHandle ); + } + memset( &light, 0, sizeof ( light ) ); + light.axis = mat3_identity; + light.lightRadius.x = spawnArgs.GetFloat( "light_radius" ); + light.lightRadius.y = light.lightRadius.z = light.lightRadius.x; + light.origin = physicsObj.GetOrigin(); + light.origin.z += 128; + light.pointLight = true; + light.shader = declManager->FindMaterial( name ); + light.shaderParms[ SHADERPARM_RED ] = 2.0f; + light.shaderParms[ SHADERPARM_GREEN ] = 2.0f; + light.shaderParms[ SHADERPARM_BLUE ] = 2.0f; + light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f; + lightDefHandle = gameRenderWorld->AddLightDef( &light ); + lightTime = gameLocal.serverTime; + BecomeActive( TH_THINK ); +} + +/* +================ +idExplodingBarrel::ExplodingEffects +================ +*/ +void idExplodingBarrel::ExplodingEffects() { + const char *temp; + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + temp = spawnArgs.GetString( "model_damage" ); + if ( *temp != '\0' ) { + SetModel( temp ); + Show(); + } + + temp = spawnArgs.GetString( "model_detonate" ); + if ( *temp != '\0' ) { + AddParticles( temp, false ); + } + + temp = spawnArgs.GetString( "mtr_lightexplode" ); + if ( *temp != '\0' ) { + AddLight( temp, false ); + } + + temp = spawnArgs.GetString( "mtr_burnmark" ); + if ( *temp != '\0' ) { + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, temp ); + } +} + +/* +================ +idExplodingBarrel::Killed +================ +*/ +void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if ( IsHidden() || state == EXPLODING || state == BURNING ) { + return; + } + + // Clients don't process killed + if( common->IsClient() ) { + return; + } + + float f = spawnArgs.GetFloat( "burn" ); + if ( f > 0.0f && state == NORMAL ) { + state = BURNING; + PostEventSec( &EV_Explode, f ); + StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL ); + AddParticles( spawnArgs.GetString ( "model_burn", "" ), true ); + return; + } else { + state = EXPLODING; + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( gameLocal.time ); + ServerSendEvent( EVENT_EXPLODE, &msg, false ); + } + } + + // do this before applying radius damage so the ent can trace to any damagable ents nearby + Hide(); + physicsObj.SetContents( 0 ); + + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash != NULL && *splash != NULL ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash ); + } + + ExplodingEffects( ); + + //FIXME: need to precache all the debris stuff here and in the projectiles + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" ); + // bool first = true; + while ( kv != NULL ) { + const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( debris_args ) { + idEntity *ent; + idVec3 dir; + idDebris *debris; + //if ( first ) { + dir = physicsObj.GetAxis()[1]; + // first = false; + //} else { + dir.x += gameLocal.random.CRandomFloat() * 4.0f; + dir.y += gameLocal.random.CRandomFloat() * 4.0f; + //dir.z = gameLocal.random.RandomFloat() * 8.0f; + //} + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris_args, &ent, false ); + if ( ent == NULL|| !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + break; + } + + debris = static_cast(ent); + debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f; + debris->UpdateVisuals(); + + } + kv = spawnArgs.MatchPrefix( "def_debris", kv ); + } + + physicsObj.PutToRest(); + CancelEvents( &EV_Explode ); + CancelEvents( &EV_Activate ); + + f = spawnArgs.GetFloat( "respawn" ); + if ( f > 0.0f ) { + PostEventSec( &EV_Respawn, f ); + } else { + PostEventMS( &EV_Remove, 5000 ); + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } + + // Any time a barrel explodes, attribute it towards the 'Boomtastic' achievement, since there's no way a barrel can explode without player interference + idPlayer *player = gameLocal.GetLocalPlayer(); + if( player != NULL && !common->IsMultiplayer() ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DESTROY_BARRELS ); + } +} + +/* +================ +idExplodingBarrel::Damage +================ +*/ +void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef == NULL ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + return; + } + if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) { + PostEventMS( &EV_Explode, 400 ); + } else { + idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + +/* +================ +idExplodingBarrel::Event_TriggerTargets +================ +*/ +void idExplodingBarrel::Event_TriggerTargets() { + ActivateTargets( this ); +} + +/* +================ +idExplodingBarrel::Event_Explode +================ +*/ +void idExplodingBarrel::Event_Explode() { + if ( state == NORMAL || state == BURNING ) { + state = BURNEXPIRED; + Killed( NULL, attacker, 0, vec3_zero, 0 ); + } +} + +/* +================ +idExplodingBarrel::Event_Respawn +================ +*/ +void idExplodingBarrel::Event_Respawn() { + int i; + int minRespawnDist = spawnArgs.GetInt( "respawn_range", "256" ); + if ( minRespawnDist ) { + float minDist = -1; + for ( i = 0; i < gameLocal.numClients; i++ ) { + if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) { + continue; + } + idVec3 v = gameLocal.entities[ i ]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + float dist = v.Length(); + if ( minDist < 0 || dist < minDist ) { + minDist = dist; + } + } + if ( minDist < minRespawnDist ) { + PostEventSec( &EV_Respawn, spawnArgs.GetInt( "respawn_again", "10" ) ); + return; + } + } + const char *temp = spawnArgs.GetString( "model" ); + if ( temp != NULL && *temp != NULL ) { + SetModel( temp ); + } + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = true; + physicsObj.SetOrigin( spawnOrigin ); + physicsObj.SetAxis( spawnAxis ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.DropToFloor(); + state = NORMAL; + Show(); + UpdateVisuals(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idExplodingBarrel::Event_Activate( idEntity *activator ) { + Killed( activator, activator, 0, vec3_origin, 0 ); +} + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idExplodingBarrel::WriteToSnapshot( idBitMsg &msg ) const { + idMoveable::WriteToSnapshot( msg ); + msg.WriteBits( IsHidden(), 1 ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idExplodingBarrel::ReadFromSnapshot( const idBitMsg &msg ) { + + idMoveable::ReadFromSnapshot( msg ); + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else { + Show(); + } +} + +/* +================ +idExplodingBarrel::ClientReceiveEvent +================ +*/ +bool idExplodingBarrel::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_EXPLODE: { + if ( gameLocal.realClientTime - msg.ReadLong() < spawnArgs.GetInt( "explode_lapse", "1000" ) ) { + ExplodingEffects( ); + } + return true; + } + default: { + return idBarrel::ClientReceiveEvent( event, time, msg ); + } + } +} diff --git a/neo/d3xp/Moveable.h b/neo/d3xp/Moveable.h new file mode 100644 index 00000000..839df2b4 --- /dev/null +++ b/neo/d3xp/Moveable.h @@ -0,0 +1,210 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_MOVEABLE_H__ +#define __GAME_MOVEABLE_H__ + +/* +=============================================================================== + + Entity using rigid body physics. + +=============================================================================== +*/ + +extern const idEventDef EV_BecomeNonSolid; +extern const idEventDef EV_IsAtRest; + +class idMoveable : public idEntity { +public: + CLASS_PROTOTYPE( idMoveable ); + + idMoveable(); + ~idMoveable(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void Hide(); + virtual void Show(); + + bool AllowStep() const; + void EnableDamage( bool enable, float duration ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + + void SetAttacker( idEntity *ent ); + const idEntity * GetAttacker() { return attacker; } + +protected: + idPhysics_RigidBody physicsObj; // physics object + idStr brokenModel; // model set when health drops down to or below zero + idStr damage; // if > 0 apply damage to hit entities + idStr monsterDamage; + idEntity *attacker; + idStr fxCollide; // fx system to start when collides with something + int nextCollideFxTime; // next time it is ok to spawn collision fx + float minDamageVelocity; // minimum velocity before moveable applies damage + float maxDamageVelocity; // velocity at which the maximum damage is applied + idCurve_Spline *initialSpline; // initial spline path the moveable follows + idVec3 initialSplineDir; // initial relative direction along the spline path + bool explode; // entity explodes when health drops down to or below zero + bool unbindOnDeath; // unbind from master when health drops down to or below zero + bool allowStep; // allow monsters to step on the object + bool canDamage; // only apply damage when this is set + int nextDamageTime; // next time the movable can hurt the player + int nextSoundTime; // next time the moveable can make a sound + + const idMaterial * GetRenderModelMaterial() const; + void BecomeNonSolid(); + void InitInitialSpline( int startTime ); + bool FollowInitialSplinePath(); + + void Event_Activate( idEntity *activator ); + void Event_BecomeNonSolid(); + void Event_SetOwnerFromSpawnArgs(); + void Event_IsAtRest(); + void Event_EnableDamage( float enable ); +}; + + +/* +=============================================================================== + + A barrel using rigid body physics. The barrel has special handling of + the view model orientation to make it look like it rolls instead of slides. + +=============================================================================== +*/ + +class idBarrel : public idMoveable { + +public: + CLASS_PROTOTYPE( idBarrel ); + idBarrel(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void BarrelThink(); + virtual void Think(); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + +private: + float radius; // radius of barrel + int barrelAxis; // one of the coordinate axes the barrel cylinder is parallel to + idVec3 lastOrigin; // origin of the barrel the last think frame + idMat3 lastAxis; // axis of the barrel the last think frame + float additionalRotation; // additional rotation of the barrel about it's axis + idMat3 additionalAxis; // additional rotation axis +}; + + +/* +=============================================================================== + + A barrel using rigid body physics and special handling of the view model + orientation to make it look like it rolls instead of slides. The barrel + can burn and explode when damaged. + +=============================================================================== +*/ + +class idExplodingBarrel : public idBarrel { +public: + CLASS_PROTOTYPE( idExplodingBarrel ); + + idExplodingBarrel(); + ~idExplodingBarrel(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsStable(); + void SetStability( bool stability ); + void StartBurning(); + void StopBurning(); + + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void Think(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + enum { + EVENT_EXPLODE = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + +private: + typedef enum { + NORMAL = 0, + BURNING, + BURNEXPIRED, + EXPLODING + } explode_state_t; + explode_state_t state; + + idVec3 spawnOrigin; + idMat3 spawnAxis; + qhandle_t particleModelDefHandle; + qhandle_t lightDefHandle; + renderEntity_t particleRenderEntity; + renderLight_t light; + int particleTime; + int lightTime; + float time; + bool isStable; + + void AddParticles( const char *name, bool burn ); + void AddLight( const char *name , bool burn ); + void ExplodingEffects(); + void UpdateLight(); + + void Event_Activate( idEntity *activator ); + void Event_Respawn(); + void Event_Explode(); + void Event_TriggerTargets(); +}; + +#endif /* !__GAME_MOVEABLE_H__ */ diff --git a/neo/d3xp/Mover.cpp b/neo/d3xp/Mover.cpp new file mode 100644 index 00000000..c4104a30 --- /dev/null +++ b/neo/d3xp/Mover.cpp @@ -0,0 +1,4852 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// _D3XP : rename all gameLocal.time to gameLocal.slow.time for merge! + +// a mover will update any gui entities in it's target list with +// a key/val pair of "mover" "state" from below.. guis can represent +// realtime info like this +// binary only +static const char *guiBinaryMoverStates[] = { + "1", // pos 1 + "2", // pos 2 + "3", // moving 1 to 2 + "4" // moving 2 to 1 +}; + + +/* +=============================================================================== + +idMover + +=============================================================================== +*/ + +const idEventDef EV_FindGuiTargets( "", NULL ); +const idEventDef EV_TeamBlocked( "", "ee" ); +const idEventDef EV_PartBlocked( "", "e" ); +const idEventDef EV_ReachedPos( "", NULL ); +const idEventDef EV_ReachedAng( "", NULL ); +const idEventDef EV_PostRestore( "", "ddddd" ); +const idEventDef EV_StopMoving( "stopMoving", NULL ); +const idEventDef EV_StopRotating( "stopRotating", NULL ); +const idEventDef EV_Speed( "speed", "f" ); +const idEventDef EV_Time( "time", "f" ); +const idEventDef EV_AccelTime( "accelTime", "f" ); +const idEventDef EV_DecelTime( "decelTime", "f" ); +const idEventDef EV_MoveTo( "moveTo", "e" ); +const idEventDef EV_MoveToPos( "moveToPos", "v" ); +const idEventDef EV_Move( "move", "ff" ); +const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" ); +const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" ); +const idEventDef EV_RotateDownTo( "rotateDownTo", "df" ); +const idEventDef EV_RotateUpTo( "rotateUpTo", "df" ); +const idEventDef EV_RotateTo( "rotateTo", "v" ); +const idEventDef EV_Rotate( "rotate", "v" ); +const idEventDef EV_RotateOnce( "rotateOnce", "v" ); +const idEventDef EV_Bob( "bob", "ffv" ); +const idEventDef EV_Sway( "sway", "ffv" ); +const idEventDef EV_Mover_OpenPortal( "openPortal" ); +const idEventDef EV_Mover_ClosePortal( "closePortal" ); +const idEventDef EV_AccelSound( "accelSound", "s" ); +const idEventDef EV_DecelSound( "decelSound", "s" ); +const idEventDef EV_MoveSound( "moveSound", "s" ); +const idEventDef EV_Mover_InitGuiTargets( "", NULL ); +const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL ); +const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL ); +const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL ); +const idEventDef EV_StartSpline( "startSpline", "e" ); +const idEventDef EV_StopSpline( "stopSpline", NULL ); +const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); +const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); + +CLASS_DECLARATION( idEntity, idMover ) + EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover::Event_SetCallback ) + EVENT( EV_TeamBlocked, idMover::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover::Event_PartBlocked ) + EVENT( EV_ReachedPos, idMover::Event_UpdateMove ) + EVENT( EV_ReachedAng, idMover::Event_UpdateRotation ) + EVENT( EV_PostRestore, idMover::Event_PostRestore ) + EVENT( EV_StopMoving, idMover::Event_StopMoving ) + EVENT( EV_StopRotating, idMover::Event_StopRotating ) + EVENT( EV_Speed, idMover::Event_SetMoveSpeed ) + EVENT( EV_Time, idMover::Event_SetMoveTime ) + EVENT( EV_AccelTime, idMover::Event_SetAccellerationTime ) + EVENT( EV_DecelTime, idMover::Event_SetDecelerationTime ) + EVENT( EV_MoveTo, idMover::Event_MoveTo ) + EVENT( EV_MoveToPos, idMover::Event_MoveToPos ) + EVENT( EV_Move, idMover::Event_MoveDir ) + EVENT( EV_MoveAccelerateTo, idMover::Event_MoveAccelerateTo ) + EVENT( EV_MoveDecelerateTo, idMover::Event_MoveDecelerateTo ) + EVENT( EV_RotateDownTo, idMover::Event_RotateDownTo ) + EVENT( EV_RotateUpTo, idMover::Event_RotateUpTo ) + EVENT( EV_RotateTo, idMover::Event_RotateTo ) + EVENT( EV_Rotate, idMover::Event_Rotate ) + EVENT( EV_RotateOnce, idMover::Event_RotateOnce ) + EVENT( EV_Bob, idMover::Event_Bob ) + EVENT( EV_Sway, idMover::Event_Sway ) + EVENT( EV_Mover_OpenPortal, idMover::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover::Event_ClosePortal ) + EVENT( EV_AccelSound, idMover::Event_SetAccelSound ) + EVENT( EV_DecelSound, idMover::Event_SetDecelSound ) + EVENT( EV_MoveSound, idMover::Event_SetMoveSound ) + EVENT( EV_Mover_InitGuiTargets, idMover::Event_InitGuiTargets ) + EVENT( EV_EnableSplineAngles, idMover::Event_EnableSplineAngles ) + EVENT( EV_DisableSplineAngles, idMover::Event_DisableSplineAngles ) + EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles ) + EVENT( EV_StartSpline, idMover::Event_StartSpline ) + EVENT( EV_StopSpline, idMover::Event_StopSpline ) + EVENT( EV_Activate, idMover::Event_Activate ) + EVENT( EV_IsMoving, idMover::Event_IsMoving ) + EVENT( EV_IsRotating, idMover::Event_IsRotating ) +END_CLASS + +/* +================ +idMover::idMover +================ +*/ +idMover::idMover() { + memset( &move, 0, sizeof( move ) ); + memset( &rot, 0, sizeof( rot ) ); + move_thread = 0; + rotate_thread = 0; + dest_angles.Zero(); + angle_delta.Zero(); + dest_position.Zero(); + move_delta.Zero(); + move_speed = 0.0f; + move_time = 0; + deceltime = 0; + acceltime = 0; + stopRotation = false; + useSplineAngles = true; + lastCommand = MOVER_NONE; + damage = 0.0f; + areaPortal = 0; + fl.networkSync = true; +} + +/* +================ +idMover::Save +================ +*/ +void idMover::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( move.stage ); + savefile->WriteInt( move.acceleration ); + savefile->WriteInt( move.movetime ); + savefile->WriteInt( move.deceleration ); + savefile->WriteVec3( move.dir ); + + savefile->WriteInt( rot.stage ); + savefile->WriteInt( rot.acceleration ); + savefile->WriteInt( rot.movetime ); + savefile->WriteInt( rot.deceleration ); + savefile->WriteFloat( rot.rot.pitch ); + savefile->WriteFloat( rot.rot.yaw ); + savefile->WriteFloat( rot.rot.roll ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( rotate_thread ); + + savefile->WriteAngles( dest_angles ); + savefile->WriteAngles( angle_delta ); + savefile->WriteVec3( dest_position ); + savefile->WriteVec3( move_delta ); + + savefile->WriteFloat( move_speed ); + savefile->WriteInt( move_time ); + savefile->WriteInt( deceltime ); + savefile->WriteInt( acceltime ); + savefile->WriteBool( stopRotation ); + savefile->WriteBool( useSplineAngles ); + savefile->WriteInt( lastCommand ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal > 0 ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } + + if ( splineEnt.GetEntity() && splineEnt.GetEntity()->GetSpline() ) { + idCurve_Spline *spline = physicsObj.GetSpline(); + + savefile->WriteBool( true ); + splineEnt.Save( savefile ); + savefile->WriteInt( spline->GetTime( 0 ) ); + savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) ); + savefile->WriteInt( physicsObj.GetSplineAcceleration() ); + savefile->WriteInt( physicsObj.GetSplineDeceleration() ); + savefile->WriteInt( (int)physicsObj.UsingSplineAngles() ); + + } else { + savefile->WriteBool( false ); + } +} + +/* +================ +idMover::Restore +================ +*/ +void idMover::Restore( idRestoreGame *savefile ) { + int i, num; + bool hasSpline = false; + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( (int&)move.stage ); + savefile->ReadInt( move.acceleration ); + savefile->ReadInt( move.movetime ); + savefile->ReadInt( move.deceleration ); + savefile->ReadVec3( move.dir ); + + savefile->ReadInt( (int&)rot.stage ); + savefile->ReadInt( rot.acceleration ); + savefile->ReadInt( rot.movetime ); + savefile->ReadInt( rot.deceleration ); + savefile->ReadFloat( rot.rot.pitch ); + savefile->ReadFloat( rot.rot.yaw ); + savefile->ReadFloat( rot.rot.roll ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( rotate_thread ); + + savefile->ReadAngles( dest_angles ); + savefile->ReadAngles( angle_delta ); + savefile->ReadVec3( dest_position ); + savefile->ReadVec3( move_delta ); + + savefile->ReadFloat( move_speed ); + savefile->ReadInt( move_time ); + savefile->ReadInt( deceltime ); + savefile->ReadInt( acceltime ); + savefile->ReadBool( stopRotation ); + savefile->ReadBool( useSplineAngles ); + savefile->ReadInt( (int &)lastCommand ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal > 0 ) { + int portalState = 0; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } + + savefile->ReadBool( hasSpline ); + if ( hasSpline ) { + int starttime; + int totaltime; + int accel; + int decel; + int useAngles; + + splineEnt.Restore( savefile ); + savefile->ReadInt( starttime ); + savefile->ReadInt( totaltime ); + savefile->ReadInt( accel ); + savefile->ReadInt( decel ); + savefile->ReadInt( useAngles ); + + PostEventMS( &EV_PostRestore, 0, starttime, totaltime, accel, decel, useAngles ); + } +} + +/* +================ +idMover::Event_PostRestore +================ +*/ +void idMover::Event_PostRestore( int start, int total, int accel, int decel, int useSplineAng ) { + idCurve_Spline *spline; + + idEntity *splineEntity = splineEnt.GetEntity(); + if ( !splineEntity ) { + // We should never get this event if splineEnt is invalid + common->Warning( "Invalid spline entity during restore\n" ); + return; + } + + spline = splineEntity->GetSpline(); + + spline->MakeUniform( total ); + spline->ShiftTime( start - spline->GetTime( 0 ) ); + + physicsObj.SetSpline( spline, accel, decel, ( useSplineAng != 0 ) ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); +} + +/* +================ +idMover::Spawn +================ +*/ +void idMover::Spawn() { + move_thread = 0; + rotate_thread = 0; + stopRotation = false; + lastCommand = MOVER_NONE; + + acceltime = 1000.0f * spawnArgs.GetFloat( "accel_time", "0" ); + deceltime = 1000.0f * spawnArgs.GetFloat( "decel_time", "0" ); + move_time = 1000.0f * spawnArgs.GetFloat( "move_time", "1" ); // safe default value + move_speed = spawnArgs.GetFloat( "move_speed", "0" ); + + spawnArgs.GetFloat( "damage" , "0", damage ); + + dest_position = GetPhysics()->GetOrigin(); + dest_angles = GetPhysics()->GetAxis().ToAngles(); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + if ( health ) { + fl.takedamage = true; + } + +} + +/* +================ +idMover::Hide +================ +*/ +void idMover::Hide() { + idEntity::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMover::Show +================ +*/ +void idMover::Show() { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } + SetPhysics( &physicsObj ); +} + +/* +============ +idMover::Killed +============ +*/ +void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + ActivateTargets( this ); +} + + +/* +================ +idMover::Event_SetCallback +================ +*/ +void idMover::Event_SetCallback() { + if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) { + lastCommand = MOVER_NONE; + rotate_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) { + lastCommand = MOVER_NONE; + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idMover::VectorForDir +================ +*/ +void idMover::VectorForDir( float angle, idVec3 &vec ) { + idAngles ang; + + switch( ( int )angle ) { + case DIR_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 90; + vec = ang.ToForward(); + break; + + case DIR_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw -= 90; + vec = ang.ToForward(); + break; + + case DIR_FORWARD : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + vec = ang.ToForward(); + break; + + case DIR_BACK : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 180; + vec = ang.ToForward(); + break; + + case DIR_REL_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_REL_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_REL_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + vec *= -1; + break; + + case DIR_REL_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + break; + + case DIR_REL_FORWARD : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward(); + break; + + case DIR_REL_BACK : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward() * -1; + break; + + default: + ang.Set( 0, angle, 0 ); + vec = GetWorldVector( ang.ToForward() ); + break; + } +} + +/* +================ +idMover::FindGuiTargets +================ +*/ +void idMover::FindGuiTargets() { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover::ClientThink +============================== +*/ +void idMover::ClientThink( const int curTime, const float fraction, const bool predict ) { + + // HACK. because I'm not sure all the other stuff this will screw up. + // There was a reason we weren't fully interpolating movers ( Which would evaluate bound objects ). + // I just cant remember what it was. + + // Evaluating the Team will update the parts that bound to the entity. + // but because we interpolate the master, we don't want to run evaluate on the mover itself. + // sending in true to the interpolatePhysicsOnly will run the TeamChain Evaluate, but only on + // Objects bound to the entity. + if( this->name == "blueshotty_door" || this->name == "redshotty_door" || + this->name == "Red_blastshield_mover" || this->name == "Blue_blastshield_mover" ) { + InterpolatePhysicsOnly( fraction, true ); + } else { + InterpolatePhysicsOnly( fraction ); + } + + Present(); +} + +/* +============================== +idMover::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover::SetGuiState( const char *key, const char *val ) const { + gameLocal.Printf( "Setting %s to %s\n", key, val ); + for( int i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_FindGuiTargets() { + FindGuiTargets(); +} + +/* +================ +idMover::SetGuiStates +================ +*/ +void idMover::SetGuiStates( const char *state ) { + int i; + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( "movestate", state ); + renderEntity.gui[ i ]->StateChanged( gameLocal.slow.time, true ); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_InitGuiTargets() { + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); +} + +/*********************************************************************** + + Translation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopMoving +================ +*/ +void idMover::Event_StopMoving() { + physicsObj.GetLocalOrigin( dest_position ); + DoneMoving(); +} + +/* +================ +idMover::DoneMoving +================ +*/ +void idMover::DoneMoving() { + + if ( lastCommand != MOVER_SPLINE ) { + // set our final position so that we get rid of any numerical inaccuracy + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + } + + lastCommand = MOVER_NONE; + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + StopSound( SND_CHANNEL_BODY, false ); +} + +/* +================ +idMover::UpdateMoveSound +================ +*/ +void idMover::UpdateMoveSound( moveStage_t stage ) { + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case LINEAR_STAGE: { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case DECELERATION_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + break; + } + } +} + +/* +================ +idMover::Event_UpdateMove +================ +*/ +void idMover::Event_UpdateMove() { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + + UpdateMoveSound( move.stage ); + + switch( move.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.slow.time, move.acceleration, org, move.dir, vec3_origin ); + if ( move.movetime > 0 ) { + move.stage = LINEAR_STAGE; + } else if ( move.deceleration > 0 ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.slow.time, move.movetime, org, move.dir, vec3_origin ); + if ( move.deceleration ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.slow.time, move.deceleration, org, move.dir, vec3_origin ); + move.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' move done\n", gameLocal.slow.time, name.c_str() ); + } + DoneMoving(); + break; + } + } +} + +/* +================ +idMover::BeginMove +================ +*/ +void idMover::BeginMove( idThread *thread ) { + moveStage_t stage; + idVec3 org; + float dist; + float acceldist; + int totalacceltime; + int at; + int dt; + + lastCommand = MOVER_MOVING; + move_thread = 0; + + physicsObj.GetLocalOrigin( org ); + + move_delta = dest_position - org; + if ( move_delta.Compare( vec3_zero ) ) { + DoneMoving(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + + // if we're moving at a specific speed, we need to calculate the move time + if ( move_speed ) { + dist = move_delta.Length(); + + totalacceltime = acceltime + deceltime; + + // calculate the distance we'll move during acceleration and deceleration + acceldist = totalacceltime * 0.5f * 0.001f * move_speed; + if ( acceldist >= dist ) { + // going too slow for this distance to move at a constant speed + move_time = totalacceltime; + } else { + // calculate move time taking acceleration into account + move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed; + } + } + + // scale time up to a whole physics frames + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + move.stage = stage; + move.acceleration = at; + move.movetime = move_time - at - dt; + move.deceleration = dt; + move.dir = move_delta; + + ProcessEvent( &EV_ReachedPos ); +} + +/*********************************************************************** + + Rotation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopRotating +================ +*/ +void idMover::Event_StopRotating() { + physicsObj.GetLocalAngles( dest_angles ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + DoneRotating(); +} + +/* +================ +idMover::DoneRotating +================ +*/ +void idMover::DoneRotating() { + lastCommand = MOVER_NONE; + idThread::ObjectMoveDone( rotate_thread, this ); + rotate_thread = 0; + + StopSound( SND_CHANNEL_BODY, false ); +} + +/* +================ +idMover::UpdateRotationSound +================ +*/ +void idMover::UpdateRotationSound( moveStage_t stage ) { + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case LINEAR_STAGE: { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case DECELERATION_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + break; + } + } +} + +/* +================ +idMover::Event_UpdateRotation +================ +*/ +void idMover::Event_UpdateRotation() { + idAngles ang; + + physicsObj.GetLocalAngles( ang ); + + UpdateRotationSound( rot.stage ); + + switch( rot.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.slow.time, rot.acceleration, ang, rot.rot, ang_zero ); + if ( rot.movetime > 0 ) { + rot.stage = LINEAR_STAGE; + } else if ( rot.deceleration > 0 ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + if ( !stopRotation && !rot.deceleration ) { + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, rot.movetime, ang, rot.rot, ang_zero ); + } else { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.slow.time, rot.movetime, ang, rot.rot, ang_zero ); + } + + if ( rot.deceleration ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.slow.time, rot.deceleration, ang, rot.rot, ang_zero ); + rot.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + lastCommand = MOVER_NONE; + if ( stopRotation ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + } else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) { + // keep our angular velocity constant + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, 0, ang, rot.rot, ang_zero ); + } + + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.slow.time, name.c_str() ); + } + + DoneRotating(); + break; + } + } +} + +/* +================ +idMover::BeginRotation +================ +*/ +void idMover::BeginRotation( idThread *thread, bool stopwhendone ) { + moveStage_t stage; + idAngles ang; + int at; + int dt; + + lastCommand = MOVER_ROTATING; + rotate_thread = 0; + + // rotation always uses move_time so that if a move was started before the rotation, + // the rotation will take the same amount of time as the move. If no move has been + // started and no time is set, the rotation takes 1 second. + if ( !move_time ) { + move_time = 1; + } + + physicsObj.GetLocalAngles( ang ); + angle_delta = dest_angles - ang; + if ( angle_delta == ang_zero ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + DoneRotating(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + stopRotation = stopwhendone || ( dt != 0 ); + + rot.stage = stage; + rot.acceleration = at; + rot.movetime = move_time - at - dt; + rot.deceleration = dt; + rot.rot = angle_delta; + + ProcessEvent( &EV_ReachedAng ); +} + + +/*********************************************************************** + + Script callable routines + +***********************************************************************/ + +/* +=============== +idMover::Event_TeamBlocked +=============== +*/ +void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.slow.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +=============== +idMover::Event_PartBlocked +=============== +*/ +void idMover::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.slow.time, name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +================ +idMover::Event_SetMoveSpeed +================ +*/ +void idMover::Event_SetMoveSpeed( float speed ) { + if ( speed <= 0 ) { + gameLocal.Error( "Cannot set speed less than or equal to 0." ); + } + + move_speed = speed; + move_time = 0; // move_time is calculated for each move when move_speed is non-0 +} + +/* +================ +idMover::Event_SetMoveTime +================ +*/ +void idMover::Event_SetMoveTime( float time ) { + if ( time <= 0 ) { + gameLocal.Error( "Cannot set time less than or equal to 0." ); + } + + move_speed = 0; + move_time = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetAccellerationTime +================ +*/ +void idMover::Event_SetAccellerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set acceleration time less than 0." ); + } + + acceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetDecelerationTime +================ +*/ +void idMover::Event_SetDecelerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set deceleration time less than 0." ); + } + + deceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_MoveTo +================ +*/ +void idMover::Event_MoveTo( idEntity *ent ) { + if ( ent == NULL ) { + gameLocal.Warning( "Entity not found" ); + return; + } + + dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::MoveToPos +================ +*/ +void idMover::MoveToPos( const idVec3 &pos ) { + dest_position = GetLocalCoordinates( pos ); + BeginMove( NULL ); +} + +/* +================ +idMover::Event_MoveToPos +================ +*/ +void idMover::Event_MoveToPos( idVec3 &pos ) { + MoveToPos( pos ); +} + +/* +================ +idMover::Event_MoveDir +================ +*/ +void idMover::Event_MoveDir( float angle, float distance ) { + idVec3 dir; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + VectorForDir( angle, dir ); + dest_position = org + dir * distance; + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAccelerateTo +================ +*/ +void idMover::Event_MoveAccelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int at; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." ); + } + + // if already moving faster than the desired speed + if ( v >= speed ) { + return; + } + + at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = ACCELERATION_STAGE; + move.acceleration = at; + move.movetime = 0; + move.deceleration = 0; + + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.slow.time, move.acceleration, org, dir * ( speed - v ), dir * v ); +} + +/* +================ +idMover::Event_MoveDecelerateTo +================ +*/ +void idMover::Event_MoveDecelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int dt; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." ); + } + + // if already moving slower than the desired speed + if ( v <= speed ) { + return; + } + + dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = DECELERATION_STAGE; + move.acceleration = 0; + move.movetime = 0; + move.deceleration = dt; + + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.slow.time, move.deceleration, org, dir * ( v - speed ), dir * speed ); +} + +/* +================ +idMover::Event_RotateDownTo +================ +*/ +void idMover::Event_RotateDownTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] > ang[ axis ] ) { + dest_angles[ axis ] -= 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateUpTo +================ +*/ +void idMover::Event_RotateUpTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] < ang[ axis ] ) { + dest_angles[ axis ] += 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateTo +================ +*/ +void idMover::Event_RotateTo( idAngles &angles ) { + dest_angles = angles; + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Rotate +================ +*/ +void idMover::Event_Rotate( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f; + + BeginRotation( idThread::CurrentThread(), false ); +} + +/* +================ +idMover::Event_RotateOnce +================ +*/ +void idMover::Event_RotateOnce( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles; + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Bob +================ +*/ +void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin ); +} + +/* +================ +idMover::Event_Sway +================ +*/ +void idMover::Event_Sway( float speed, float phase, idAngles &depth ) { + idAngles ang, angSpeed; + float duration; + + physicsObj.GetLocalAngles( ang ); + assert ( speed > 0.0f ); + duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed; + angSpeed = depth / ( duration * idMath::SQRT_1OVER2 ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero ); +} + +/* +================ +idMover::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover::Event_OpenPortal() { + if ( areaPortal ) { + SetPortalState( true ); + } +} + +/* +================ +idMover::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover::Event_ClosePortal() { + if ( areaPortal ) { + SetPortalState( false ); + } +} + +/* +================ +idMover::Event_SetAccelSound +================ +*/ +void idMover::Event_SetAccelSound( const char *sound ) { +// refSound.SetSound( "accel", sound ); +} + +/* +================ +idMover::Event_SetDecelSound +================ +*/ +void idMover::Event_SetDecelSound( const char *sound ) { +// refSound.SetSound( "decel", sound ); +} + +/* +================ +idMover::Event_SetMoveSound +================ +*/ +void idMover::Event_SetMoveSound( const char *sound ) { +// refSound.SetSound( "move", sound ); +} + +/* +================ +idMover::Event_EnableSplineAngles +================ +*/ +void idMover::Event_EnableSplineAngles() { + useSplineAngles = true; +} + +/* +================ +idMover::Event_DisableSplineAngles +================ +*/ +void idMover::Event_DisableSplineAngles() { + useSplineAngles = false; +} + +/* +================ +idMover::Event_RemoveInitialSplineAngles +================ +*/ +void idMover::Event_RemoveInitialSplineAngles() { + idCurve_Spline *spline; + idAngles ang; + + spline = physicsObj.GetSpline(); + if ( !spline ) { + return; + } + ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero ); +} + +/* +================ +idMover::Event_StartSpline +================ +*/ +void idMover::Event_StartSpline( idEntity *splineEntity ) { + idCurve_Spline *spline; + + if ( !splineEntity ) { + return; + } + + // Needed for savegames + splineEnt = splineEntity; + + spline = splineEntity->GetSpline(); + if ( !spline ) { + return; + } + + lastCommand = MOVER_SPLINE; + move_thread = 0; + + if ( acceltime + deceltime > move_time ) { + acceltime = move_time / 2; + deceltime = move_time - acceltime; + } + move.stage = FINISHED_STAGE; + move.acceleration = acceltime; + move.movetime = move_time; + move.deceleration = deceltime; + + spline->MakeUniform( move_time ); + spline->ShiftTime( gameLocal.slow.time - spline->GetTime( 0 ) ); + + physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); +} + +/* +================ +idMover::Event_StopSpline +================ +*/ +void idMover::Event_StopSpline() { + physicsObj.SetSpline( NULL, 0, 0, useSplineAngles ); + splineEnt = NULL; +} + +/* +================ +idMover::Event_Activate +================ +*/ +void idMover::Event_Activate( idEntity *activator ) { + Show(); + Event_StartSpline( this ); +} + +/* +================ +idMover::Event_IsMoving +================ +*/ +void idMover::Event_IsMoving() { + if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::Event_IsRotating +================ +*/ +void idMover::Event_IsRotating() { + if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::WriteToSnapshot +================ +*/ +void idMover::WriteToSnapshot( idBitMsg &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( move.stage, 3 ); + msg.WriteBits( rot.stage, 3 ); + WriteBindToSnapshot( msg ); + WriteGUIToSnapshot( msg ); +} + +/* +================ +idMover::ReadFromSnapshot +================ +*/ +void idMover::ReadFromSnapshot( const idBitMsg &msg ) { + moveStage_t oldMoveStage = move.stage; + moveStage_t oldRotStage = rot.stage; + + physicsObj.ReadFromSnapshot( msg ); + move.stage = (moveStage_t) msg.ReadBits( 3 ); + rot.stage = (moveStage_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( move.stage != oldMoveStage ) { + UpdateMoveSound( oldMoveStage ); + } + if ( rot.stage != oldRotStage ) { + UpdateRotationSound( oldRotStage ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover::SetPortalState +================ +*/ +void idMover::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +/* +=============================================================================== + + idSplinePath, holds a spline path to be used by an idMover + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSplinePath ) +END_CLASS + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::idSplinePath() { +} + +/* +================ +idSplinePath::Spawn +================ +*/ +void idSplinePath::Spawn() { +} + + +/* +=============================================================================== + +idElevator + +=============================================================================== +*/ +const idEventDef EV_PostArrival( "postArrival", NULL ); +const idEventDef EV_GotoFloor( "gotoFloor", "d" ); +const idEventDef EV_SetGuiStates( "setGuiStates" ); + +CLASS_DECLARATION( idMover, idElevator ) + EVENT( EV_Activate, idElevator::Event_Activate ) + EVENT( EV_TeamBlocked, idElevator::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idElevator::Event_PartBlocked ) + EVENT( EV_PostArrival, idElevator::Event_PostFloorArrival ) + EVENT( EV_GotoFloor, idElevator::Event_GotoFloor ) + EVENT( EV_Touch, idElevator::Event_Touch ) + EVENT( EV_SetGuiStates, idElevator::Event_SetGuiStates ) +END_CLASS + +/* +================ +idElevator::idElevator +================ +*/ +idElevator::idElevator() { + state = INIT; + floorInfo.Clear(); + currentFloor = 0; + pendingFloor = 0; + lastFloor = 0; + controlsDisabled = false; + lastTouchTime = 0; + returnFloor = 0; + returnTime = 0; +} + +/* +================ +idElevator::Save +================ +*/ +void idElevator::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( (int)state ); + + savefile->WriteInt( floorInfo.Num() ); + for ( i = 0; i < floorInfo.Num(); i++ ) { + savefile->WriteVec3( floorInfo[ i ].pos ); + savefile->WriteString( floorInfo[ i ].door ); + savefile->WriteInt( floorInfo[ i ].floor ); + } + + savefile->WriteInt( currentFloor ); + savefile->WriteInt( pendingFloor ); + savefile->WriteInt( lastFloor ); + savefile->WriteBool( controlsDisabled ); + savefile->WriteFloat( returnTime ); + savefile->WriteInt( returnFloor ); + savefile->WriteInt( lastTouchTime ); +} + +/* +================ +idElevator::Restore +================ +*/ +void idElevator::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)state ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + floorInfo_s floor; + + savefile->ReadVec3( floor.pos ); + savefile->ReadString( floor.door ); + savefile->ReadInt( floor.floor ); + + floorInfo.Append( floor ); + } + + savefile->ReadInt( currentFloor ); + savefile->ReadInt( pendingFloor ); + savefile->ReadInt( lastFloor ); + savefile->ReadBool( controlsDisabled ); + savefile->ReadFloat( returnTime ); + savefile->ReadInt( returnFloor ); + savefile->ReadInt( lastTouchTime ); +} + +/* +================ +idElevator::Spawn +================ +*/ +void idElevator::Spawn() { + idStr str; + int len1; + + lastFloor = 0; + currentFloor = 0; + pendingFloor = spawnArgs.GetInt( "floor", "1" ); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]); + + returnTime = spawnArgs.GetFloat( "returnTime" ); + returnFloor = spawnArgs.GetInt( "returnFloor" ); + + len1 = strlen( "floorPos_" ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL ); + while( kv ) { + str = kv->GetKey().Right( kv->GetKey().Length() - len1 ); + floorInfo_s fi; + fi.floor = atoi( str ); + fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) ); + fi.pos = spawnArgs.GetVector( kv->GetKey() ); + floorInfo.Append( fi ); + kv = spawnArgs.MatchPrefix( "floorPos_", kv ); + } + lastTouchTime = 0; + state = INIT; + BecomeActive( TH_THINK | TH_PHYSICS ); + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); + controlsDisabled = false; +} + +/* +============== +idElevator::Event_Touch +=============== +*/ +void idElevator::Event_Touch( idEntity *other, trace_t *trace ) { + + if ( common->IsClient() ) { + return; + } + + if ( gameLocal.slow.time < lastTouchTime + 2000 ) { + return; + } + + if ( !other->IsType( idPlayer::Type ) ) { + return; + } + + lastTouchTime = gameLocal.slow.time; + + if ( thinkFlags & TH_PHYSICS ) { + return; + } + + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor ); + } +} + +/* +================ +idElevator::Think +================ +*/ +void idElevator::Think() { + idVec3 masterOrigin; + idMat3 masterAxis; + idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( state == INIT ) { + state = IDLE; + if ( doorent ) { + doorent->BindTeam( this ); + doorent->spawnArgs.Set( "snd_open", "" ); + doorent->spawnArgs.Set( "snd_close", "" ); + doorent->spawnArgs.Set( "snd_opened", "" ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + idDoor *door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->SetCompanion( doorent ); + } + } + + Event_GotoFloor( pendingFloor ); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + } else if ( state == WAITING_ON_DOORS ) { + state = IDLE; + if ( doorent != NULL && doorent->IsOpen() ) { + state = WAITING_ON_DOORS; + } else { + for ( int i = 0; i < floorInfo.Num(); i++ ) { + idDoor *door = GetDoor( floorInfo[i].door ); + if ( door != NULL && door->IsOpen() ) { + state = WAITING_ON_DOORS; + break; + } + } + } + if ( state == IDLE ) { + lastFloor = currentFloor; + currentFloor = pendingFloor; + floorInfo_s *fi = GetFloorInfo( currentFloor ); + if ( fi ) { + MoveToPos( fi->pos ); + } + } + } + RunPhysics(); + Present(); +} + +/* +================ +idElevator::Event_Activate +================ +*/ +void idElevator::Event_Activate( idEntity *activator ) { + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + Event_GotoFloor( triggerFloor ); + } +} + +/* +================ +idElevator::Event_TeamBlocked +================ +*/ +void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( blockedEntity == this ) { + Event_GotoFloor( lastFloor ); + } else if ( blockedEntity && blockedEntity->IsType( idDoor::Type ) ) { + // open the inner doors if one is blocked + idDoor *blocked = static_cast( blockedEntity ); + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door != NULL && blocked->GetMoveMaster() == door->GetMoveMaster() ) { + door->SetBlocked(true); + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } + } +} + +/* +=============== +idElevator::HandleSingleGuiCommand +=============== +*/ +bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( controlsDisabled ) { + return false; + } + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "changefloor" ) == 0 ) { + if ( src->ReadToken( &token ) ) { + int newFloor = atoi( token ); + if ( newFloor == currentFloor ) { + // open currentFloor and interior doors + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } else { + ProcessEvent( &EV_GotoFloor, newFloor ); + } + return true; + } + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +idElevator::OpenFloorDoor +================ +*/ +void idElevator::OpenFloorDoor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( fi->door ); + if ( door ) { + door->Open(); + } + } +} + +/* +================ +idElevator::OpenInnerDoor +================ +*/ +void idElevator::OpenInnerDoor() { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Open(); + } +} + +/* +================ +idElevator::GetFloorInfo +================ +*/ +floorInfo_s *idElevator::GetFloorInfo( int floor ) { + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == floor ) { + return &floorInfo[i]; + } + } + return NULL; +} + +/* +================ +idElevator::Event_GotoFloor +================ +*/ +void idElevator::Event_GotoFloor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + DisableAllDoors(); + CloseAllDoors(); + state = WAITING_ON_DOORS; + pendingFloor = floor; + } + // If the inner door is blocked, repost this event + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + if ( door->IsBlocked() || door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + } + } +} + +/* +================ +idElevator::BeginMove +================ +*/ +void idElevator::BeginMove( idThread *thread ) { + controlsDisabled = true; + CloseAllDoors(); + DisableAllDoors(); + const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( "floor", "" ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true ); + } + } + ent->UpdateVisuals(); + } + kv = spawnArgs.MatchPrefix( "statusGui", kv ); + } + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] ); + idMover::BeginMove( thread ); +} + +/* +================ +idElevator::GetDoor +================ +*/ +idDoor *idElevator::GetDoor( const char *name ) { + idEntity *ent; + idEntity *master; + idDoor *doorEnt; + + doorEnt = NULL; + if ( name && *name ) { + ent = gameLocal.FindEntity( name ); + if ( ent && ent->IsType( idDoor::Type ) ) { + doorEnt = static_cast( ent ); + master = doorEnt->GetMoveMaster(); + if ( master != doorEnt ) { + if ( master->IsType( idDoor::Type ) ) { + doorEnt = static_cast( master ); + } else { + doorEnt = NULL; + } + } + } + } + + return doorEnt; +} + +/* +================ +idElevator::Event_PostFloorArrival +================ +*/ +void idElevator::Event_PostFloorArrival() { + OpenFloorDoor( currentFloor ); + OpenInnerDoor(); + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + controlsDisabled = false; + if ( returnTime > 0.0f && returnFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, returnTime, returnFloor ); + } +} + +void idElevator::Event_SetGuiStates() { + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); +} + +/* +================ +idElevator::DoneMoving +================ +*/ +void idElevator::DoneMoving() { + idMover::DoneMoving(); + EnableProperDoors(); + const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( "floor", va( "%i", currentFloor ) ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true ); + } + } + ent->UpdateVisuals(); + } + kv = spawnArgs.MatchPrefix( "statusGui", kv ); + } + if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) { + PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) ); + } else { + Event_PostFloorArrival(); + } +} + +/* +================ +idElevator::CloseAllDoors +================ +*/ +void idElevator::CloseAllDoors() { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Close(); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Close(); + } + } +} + +/* +================ +idElevator::DisableAllDoors +================ +*/ +void idElevator::DisableAllDoors() { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( false ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( false ); + } + } +} + +/* +================ +idElevator::EnableProperDoors +================ +*/ +void idElevator::EnableProperDoors() { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( true ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == currentFloor ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( true ); + break; + } + } + } +} + + +/* +=============================================================================== + +idMover_Binary + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" + +=============================================================================== +*/ + +const idEventDef EV_Mover_ReturnToPos1( "", NULL ); +const idEventDef EV_Mover_MatchTeam( "", "dd" ); +const idEventDef EV_Mover_Enable( "enable", NULL ); +const idEventDef EV_Mover_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idMover_Binary ) + EVENT( EV_FindGuiTargets, idMover_Binary::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover_Binary::Event_SetCallback ) + EVENT( EV_Mover_ReturnToPos1, idMover_Binary::Event_ReturnToPos1 ) + EVENT( EV_Activate, idMover_Binary::Event_Use_BinaryMover ) + EVENT( EV_ReachedPos, idMover_Binary::Event_Reached_BinaryMover ) + EVENT( EV_Mover_MatchTeam, idMover_Binary::Event_MatchActivateTeam ) + EVENT( EV_Mover_Enable, idMover_Binary::Event_Enable ) + EVENT( EV_Mover_Disable, idMover_Binary::Event_Disable ) + EVENT( EV_Mover_OpenPortal, idMover_Binary::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover_Binary::Event_ClosePortal ) + EVENT( EV_Mover_InitGuiTargets, idMover_Binary::Event_InitGuiTargets ) +END_CLASS + +/* +================ +idMover_Binary::idMover_Binary() +================ +*/ +idMover_Binary::idMover_Binary() { + pos1.Zero(); + pos2.Zero(); + moverState = MOVER_POS1; + moveMaster = NULL; + activateChain = NULL; + soundPos1 = 0; + sound1to2 = 0; + sound2to1 = 0; + soundPos2 = 0; + soundLoop = 0; + wait = 0.0f; + damage = 0.0f; + duration = 0; + accelTime = 0; + decelTime = 0; + activatedBy = this; + stateStartTime = 0; + team.Clear(); + enabled = false; + move_thread = 0; + updateStatus = 0; + areaPortal = 0; + blocked = false; + playerOnly = false; + fl.networkSync = true; +} + +/* +================ +idMover_Binary::~idMover_Binary +================ +*/ +idMover_Binary::~idMover_Binary() { + idMover_Binary *mover; + + // if this is the mover master + if ( this == moveMaster ) { + // make the next mover in the chain the move master + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + mover->moveMaster = this->activateChain; + } + } + else { + // remove mover from the activate chain + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + if ( mover->activateChain == this ) { + mover->activateChain = this->activateChain; + break; + } + } + } +} + +/* +================ +idMover_Binary::Save +================ +*/ +void idMover_Binary::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteVec3( pos1 ); + savefile->WriteVec3( pos2 ); + savefile->WriteInt( (moverState_t)moverState ); + + savefile->WriteObject( moveMaster ); + savefile->WriteObject( activateChain ); + + savefile->WriteInt( soundPos1 ); + savefile->WriteInt( sound1to2 ); + savefile->WriteInt( sound2to1 ); + savefile->WriteInt( soundPos2 ); + savefile->WriteInt( soundLoop ); + + savefile->WriteFloat( wait ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( duration ); + savefile->WriteInt( accelTime ); + savefile->WriteInt( decelTime ); + + activatedBy.Save( savefile ); + + savefile->WriteInt( stateStartTime ); + savefile->WriteString( team ); + savefile->WriteBool( enabled ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( updateStatus ); + + savefile->WriteInt( buddies.Num() ); + for ( i = 0; i < buddies.Num(); i++ ) { + savefile->WriteString( buddies[ i ] ); + } + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( blocked ); + savefile->WriteBool( playerOnly ); + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } +} + +/* +================ +idMover_Binary::Restore +================ +*/ +void idMover_Binary::Restore( idRestoreGame *savefile ) { + int i, num, portalState; + idStr temp; + + savefile->ReadVec3( pos1 ); + savefile->ReadVec3( pos2 ); + savefile->ReadInt( (int &)moverState ); + + savefile->ReadObject( reinterpret_cast( moveMaster ) ); + savefile->ReadObject( reinterpret_cast( activateChain ) ); + + savefile->ReadInt( soundPos1 ); + savefile->ReadInt( sound1to2 ); + savefile->ReadInt( sound2to1 ); + savefile->ReadInt( soundPos2 ); + savefile->ReadInt( soundLoop ); + + savefile->ReadFloat( wait ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( duration ); + savefile->ReadInt( accelTime ); + savefile->ReadInt( decelTime ); + + activatedBy.Restore( savefile ); + + savefile->ReadInt( stateStartTime ); + + savefile->ReadString( team ); + savefile->ReadBool( enabled ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( updateStatus ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( temp ); + buddies.Append( temp ); + } + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( blocked ); + savefile->ReadBool( playerOnly ); + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } +} + +/* +================ +idMover_Binary::Spawn + +Base class for all movers. + +"wait" wait before returning (3 default, -1 = never return) +"speed" movement speed +================ +*/ +void idMover_Binary::Spawn() { + idEntity *ent; + const char *temp; + + move_thread = 0; + enabled = true; + areaPortal = 0; + + activateChain = NULL; + + spawnArgs.GetFloat( "wait", "0", wait ); + + spawnArgs.GetInt( "updateStatus", "0", updateStatus ); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL ); + while( kv ) { + buddies.Append( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "buddy", kv ); + } + + spawnArgs.GetString( "team", "", &temp ); + team = temp; + + if ( !team.Length() ) { + ent = this; + } else { + // find the first entity spawned on this team (which could be us) + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast(ent)->team.c_str(), temp ) ) { + break; + } + } + if ( !ent ) { + ent = this; + } + } + moveMaster = static_cast(ent); + + // create a physics team for the binary mover parts + if ( ent != this ) { + JoinTeam( ent ); + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( moveMaster != this ) { + JoinActivateTeam( moveMaster ); + } + + idBounds soundOrigin; + idMover_Binary *slave; + + soundOrigin.Clear(); + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + soundOrigin += slave->GetPhysics()->GetAbsBounds(); + } + moveMaster->refSound.origin = soundOrigin.GetCenter(); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } +} + +/* +=============== +idMover_Binary::GetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) { + if ( angle == -1 ) { + movedir.Set( 0, 0, 1 ); + } else if ( angle == -2 ) { + movedir.Set( 0, 0, -1 ); + } else { + movedir = idAngles( 0, angle, 0 ).ToForward(); + } +} + +/* +================ +idMover_Binary::Event_SetCallback +================ +*/ +void idMover_Binary::Event_SetCallback() { + if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) { + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idMover_Binary::UpdateMoverSound +=============== +*/ +void idMover_Binary::UpdateMoverSound( moverState_t state ) { + if ( moveMaster == this ) { + switch( state ) { + case MOVER_POS1: + break; + case MOVER_POS2: + break; + case MOVER_1TO2: + StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL ); + break; + case MOVER_2TO1: + StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } + } +} + +/* +=============== +idMover_Binary::SetMoverState +=============== +*/ +void idMover_Binary::SetMoverState( moverState_t newstate, int time ) { + idVec3 delta; + + moverState = newstate; + move_thread = 0; + + UpdateMoverSound( newstate ); + + stateStartTime = time; + switch( moverState ) { + case MOVER_POS1: { + Signal( SIG_MOVER_POS1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin ); + break; + } + case MOVER_POS2: { + Signal( SIG_MOVER_POS2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin ); + break; + } + case MOVER_1TO2: { + Signal( SIG_MOVER_1TO2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + case MOVER_2TO1: { + Signal( SIG_MOVER_2TO1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + } +} + +/* +================ +idMover_Binary::MatchActivateTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) { + idMover_Binary *slave; + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->SetMoverState( newstate, time ); + } +} + +/* +================ +idMover_Binary::Enable +================ +*/ +void idMover_Binary::Enable( bool b ) { + enabled = b; +} + +/* +================ +idMover_Binary::Event_MatchActivateTeam +================ +*/ +void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) { + MatchActivateTeam( newstate, time ); +} + +/* +================ +idMover_Binary::BindTeam + +All entities in a mover team will be bound +================ +*/ +void idMover_Binary::BindTeam( idEntity *bindTo ) { + idMover_Binary *slave; + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->Bind( bindTo, true ); + } +} + +/* +================ +idMover_Binary::JoinActivateTeam + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) { + this->activateChain = master->activateChain; + master->activateChain = this; +} + +/* +================ +idMover_Binary::Event_Enable + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::Event_Enable() { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_Disable + +Set all entities in a mover team to be disabled +================ +*/ +void idMover_Binary::Event_Disable() { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover_Binary::Event_OpenPortal() { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( slave->areaPortal ) { + slave->SetPortalState( true ); + } + if ( slave->playerOnly ) { + gameLocal.SetAASAreaState( slave->GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, false ); + } + } +} + +/* +================ +idMover_Binary::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover_Binary::Event_ClosePortal() { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( !slave->IsHidden() ) { + if ( slave->areaPortal ) { + slave->SetPortalState( false ); + } + if ( slave->playerOnly ) { + gameLocal.SetAASAreaState( slave->GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, true ); + } + } + } +} + +/* +================ +idMover_Binary::Event_ReturnToPos1 +================ +*/ +void idMover_Binary::Event_ReturnToPos1() { + MatchActivateTeam( MOVER_2TO1, gameLocal.slow.time ); +} + +/* +================ +idMover_Binary::Event_Reached_BinaryMover +================ +*/ +void idMover_Binary::Event_Reached_BinaryMover() { + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + if ( moveMaster == this ) { + StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL ); + } + + SetMoverState( MOVER_POS2, gameLocal.slow.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + + UpdateBuddies( 1 ); + + if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) { + // return to pos1 after a delay + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + + SetBlocked(false); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.slow.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + + UpdateBuddies( 0 ); + + // close areaportals + if ( moveMaster == this ) { + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, wait, this ); + } + SetBlocked(false); + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +idMover_Binary::GotoPosition1 +================ +*/ +void idMover_Binary::GotoPosition1() { + idMover_Binary *slave; + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition1(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS2 ) { + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } + ProcessEvent( &EV_Mover_ReturnToPos1 ); + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + // if already at at position 1 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::GotoPosition2 +================ +*/ +void idMover_Binary::GotoPosition2() { + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition2(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS1 ) { + MatchActivateTeam( MOVER_1TO2, gameLocal.slow.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + return; + } + + + // only partway up before reversing + if ( moverState == MOVER_2TO1 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + // if already at at position 2 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::UpdateBuddies +================ +*/ +void idMover_Binary::UpdateBuddies( int val ) { + int i, c; + + if ( updateStatus == 2 ) { + c = buddies.Num(); + for ( i = 0; i < c; i++ ) { + idEntity *buddy = gameLocal.FindEntity( buddies[i] ); + if ( buddy ) { + buddy->SetShaderParm( SHADERPARM_MODE, val ); + buddy->UpdateVisuals(); + } + } + } +} + +/* +================ +idMover_Binary::SetGuiStates +================ +*/ +void idMover_Binary::SetGuiStates( const char *state ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + + idMover_Binary *mb = activateChain; + while( mb ) { + if ( mb->guiTargets.Num() ) { + mb->SetGuiState( "movestate", state ); + } + mb = mb->activateChain; + } +} + +/* +================ +idMover_Binary::Use_BinaryMover +================ +*/ +void idMover_Binary::Use_BinaryMover( idEntity *activator ) { + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + activatedBy = activator; + + if ( moverState == MOVER_POS1 ) { + // FIXME: start moving 1 ms later, because if this was player + // triggered, gameLocal.time hasn't been advanced yet + MatchActivateTeam( MOVER_1TO2, gameLocal.slow.time + 1 ); + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + return; + } + + // if all the way up, just delay before coming down + if ( moverState == MOVER_POS2 ) { + idMover_Binary *slave; + + if ( wait == -1 ) { + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait ); + } + return; + } + + // only partway down before reversing + if ( moverState == MOVER_2TO1 ) { + GotoPosition2(); + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + GotoPosition1(); + return; + } +} + +/* +================ +idMover_Binary::Event_Use_BinaryMover +================ +*/ +void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) { + Use_BinaryMover( activator ); +} + +/* +================ +idMover_Binary::PreBind +================ +*/ +void idMover_Binary::PreBind() { + pos1 = GetWorldCoordinates( pos1 ); + pos2 = GetWorldCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::PostBind +================ +*/ +void idMover_Binary::PostBind() { + pos1 = GetLocalCoordinates( pos1 ); + pos2 = GetLocalCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::FindGuiTargets +================ +*/ +void idMover_Binary::FindGuiTargets() { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover_Binary::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover_Binary::SetGuiState( const char *key, const char *val ) const { + int i; + + for( i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.slow.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_FindGuiTargets() { + FindGuiTargets(); +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_InitGuiTargets() { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] ); + } +} + +/* +================ +idMover_Binary::InitSpeed + +pos1, pos2, and speed are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) { + idVec3 move; + float distance; + float speed; + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + speed = mspeed ? mspeed : 100; + + // calculate time to reach second position from speed + move = pos2 - pos1; + distance = move.Length(); + duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::InitTime + +pos1, pos2, and time are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) { + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::SetBlocked +================ +*/ +void idMover_Binary::SetBlocked( bool b ) { + for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->blocked = b; + if ( b ) { + const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv ); + } + } + } +} + +/* +================ +idMover_Binary::IsBlocked +================ +*/ +bool idMover_Binary::IsBlocked() { + return blocked; +} + +/* +================ +idMover_Binary::GetActivator +================ +*/ +idEntity *idMover_Binary::GetActivator() const { + return activatedBy.GetEntity(); +} + +/* +================ +idMover_Binary::WriteToSnapshot +================ +*/ +void idMover_Binary::WriteToSnapshot( idBitMsg &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( moverState, 3 ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Binary::ReadFromSnapshot +================ +*/ +void idMover_Binary::ReadFromSnapshot( const idBitMsg &msg ) { + moverState_t oldMoverState = moverState; + + physicsObj.ReadFromSnapshot( msg ); + moverState = (moverState_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( moverState != oldMoverState ) { + UpdateMoverSound( moverState ); + MatchActivateTeam( moverState, gameLocal.slow.time ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover_Binary::SetPortalState +================ +*/ +void idMover_Binary::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +/* +=============================================================================== + +idDoor + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +const idEventDef EV_Door_StartOpen( "", NULL ); +const idEventDef EV_Door_SpawnDoorTrigger( "", NULL ); +const idEventDef EV_Door_SpawnSoundTrigger( "", NULL ); +const idEventDef EV_Door_Open( "open", NULL ); +const idEventDef EV_Door_Close( "close", NULL ); +const idEventDef EV_Door_Lock( "lock", "d" ); +const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' ); +const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' ); + +CLASS_DECLARATION( idMover_Binary, idDoor ) + EVENT( EV_TeamBlocked, idDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idDoor::Event_PartBlocked ) + EVENT( EV_Touch, idDoor::Event_Touch ) + EVENT( EV_Activate, idDoor::Event_Activate ) + EVENT( EV_Door_StartOpen, idDoor::Event_StartOpen ) + EVENT( EV_Door_SpawnDoorTrigger, idDoor::Event_SpawnDoorTrigger ) + EVENT( EV_Door_SpawnSoundTrigger, idDoor::Event_SpawnSoundTrigger ) + EVENT( EV_Door_Open, idDoor::Event_Open ) + EVENT( EV_Door_Close, idDoor::Event_Close ) + EVENT( EV_Door_Lock, idDoor::Event_Lock ) + EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) + EVENT( EV_Door_IsLocked, idDoor::Event_Locked ) + EVENT( EV_ReachedPos, idDoor::Event_Reached_BinaryMover ) + EVENT( EV_SpectatorTouch, idDoor::Event_SpectatorTouch ) + EVENT( EV_Mover_OpenPortal, idDoor::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idDoor::Event_ClosePortal ) +END_CLASS + +/* +================ +idDoor::idDoor +================ +*/ +idDoor::idDoor() { + triggersize = 1.0f; + crusher = false; + noTouch = false; + aas_area_closed = false; + buddyStr.Clear(); + trigger = NULL; + sndTrigger = NULL; + nextSndTriggerTime = 0; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); + requires.Clear(); + removeItem = 0; + syncLock.Clear(); + companionDoor = NULL; + normalAxisIndex = 0; +} + +/* +================ +idDoor::~idDoor +================ +*/ +idDoor::~idDoor() { + if ( trigger ) { + delete trigger; + } + if ( sndTrigger ) { + delete sndTrigger; + } +} + +/* +================ +idDoor::Save +================ +*/ +void idDoor::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( triggersize ); + savefile->WriteBool( crusher ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteString( buddyStr ); + savefile->WriteInt( nextSndTriggerTime ); + + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); + + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteString( syncLock ); + savefile->WriteInt( normalAxisIndex ); + + savefile->WriteClipModel( trigger ); + savefile->WriteClipModel( sndTrigger ); + + savefile->WriteObject( companionDoor ); +} + +/* +================ +idDoor::Restore +================ +*/ +void idDoor::Restore( idRestoreGame *savefile ) { + + savefile->ReadFloat( triggersize ); + savefile->ReadBool( crusher ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( aas_area_closed ); + SetAASAreaState( aas_area_closed ); + savefile->ReadString( buddyStr ); + savefile->ReadInt( nextSndTriggerTime ); + + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); + + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadString( syncLock ); + savefile->ReadInt( normalAxisIndex ); + + savefile->ReadClipModel( trigger ); + savefile->ReadClipModel( sndTrigger ); + + savefile->ReadObject( reinterpret_cast( companionDoor ) ); +} + +/* +================ +idDoor::Spawn +================ +*/ +void idDoor::Spawn() { + idVec3 abs_movedir; + float distance; + idVec3 size; + idVec3 movedir; + float dir; + float lip; + bool start_open; + float time; + float speed; + + // get the direction to move + if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) { + // no movedir, so angle defines movement direction and not orientation, + // a la oldschool Quake + SetAngles( ang_zero ); + spawnArgs.GetFloat( "angle", "0", dir ); + } + GetMovedir( dir, movedir ); + + // default speed of 400 + spawnArgs.GetFloat( "speed", "400", speed ); + + // default wait of 2 seconds + spawnArgs.GetFloat( "wait", "3", wait ); + + // default lip of 8 units + spawnArgs.GetFloat( "lip", "8", lip ); + + // by default no damage + spawnArgs.GetFloat( "damage", "0", damage ); + + // trigger size + spawnArgs.GetFloat( "triggersize", "120", triggersize ); + + spawnArgs.GetBool( "crusher", "0", crusher ); + spawnArgs.GetBool( "start_open", "0", start_open ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + spawnArgs.GetBool( "player_only", "0", playerOnly ); + + // expects syncLock to be a door that must be closed before this door will open + spawnArgs.GetString( "syncLock", "", syncLock ); + + spawnArgs.GetString( "buddy", "", buddyStr ); + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + + // ever separate piece of a door is considered solid when other team mates push entities + fl.solidForTeam = true; + + // first position at start + pos1 = GetPhysics()->GetOrigin(); + + // calculate second position + abs_movedir[0] = idMath::Fabs( movedir[ 0 ] ); + abs_movedir[1] = idMath::Fabs( movedir[ 1 ] ); + abs_movedir[2] = idMath::Fabs( movedir[ 2 ] ); + size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0]; + distance = ( abs_movedir * size ) - lip; + pos2 = pos1 + distance * movedir; + + // if "start_open", reverse position 1 and 2 + if ( start_open ) { + // post it after EV_SpawnBind + PostEventMS( &EV_Door_StartOpen, 1 ); + } + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } + + if ( moveMaster == this ) { + if ( health ) { + fl.takedamage = true; + } + if ( noTouch || health ) { + // non touch/shoot doors + PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.slow.time ); + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } else { + // spawn trigger + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + if ( !start_open ) { + // start closed + ProcessEvent( &EV_Mover_ClosePortal ); + + if ( playerOnly ) { + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, true ); + } + } + + int locked = spawnArgs.GetInt( "locked" ); + if ( locked ) { + // make sure all members of the team get locked + PostEventMS( &EV_Door_Lock, 0, locked ); + } + + if ( spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this ); + } + + // sounds have a habit of stuttering when portals close, so make them unoccluded + refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION; + + companionDoor = NULL; + + enabled = true; + blocked = false; +} + +/* +================ +idDoor::Think +================ +*/ +void idDoor::ClientThink( const int curTime, const float fraction, const bool predict ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::ClientThink( curTime, fraction, predict ); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { + trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); + } + if ( sndTrigger ) { + sndTrigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); + } + } + } +} + +/* +================ +idDoor::Think +================ +*/ +void idDoor::Think() { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { + trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); + } + if ( sndTrigger ) { + sndTrigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); + } + } + } +} + +/* +================ +idDoor::PreBind +================ +*/ +void idDoor::PreBind() { + idMover_Binary::PreBind(); +} + +/* +================ +idDoor::PostBind +================ +*/ +void idDoor::PostBind() { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ? trigger : sndTrigger ); +} + +/* +================ +idDoor::SetAASAreaState +================ +*/ +void idDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed ); +} + +/* +================ +idDoor::Hide +================ +*/ +void idDoor::Hide() { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Hide(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Hide(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Disable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Disable(); + } + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + slave->GetPhysics()->GetClipModel()->Disable(); + slave->idMover_Binary::Hide(); + } + } +} + +/* +================ +idDoor::Show +================ +*/ +void idDoor::Show() { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Show(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Show(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Enable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Enable(); + } + if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + slave->GetPhysics()->GetClipModel()->Enable(); + slave->idMover_Binary::Show(); + } + } +} + +/* +================ +idDoor::GetLocalTriggerPosition +================ +*/ +void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +================ +idDoor::Use +================ +*/ +void idDoor::Use( idEntity *other, idEntity *activator ) { + if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) { + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync != NULL && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + ActivateTargets( activator ); + Use_BinaryMover( activator ); + } +} + +/* +================ +idDoor::Open +================ +*/ +void idDoor::Open() { + GotoPosition2(); +} + +/* +================ +idDoor::Close +================ +*/ +void idDoor::Close() { + GotoPosition1(); +} + +/* +================ +idDoor::Lock +================ +*/ +void idDoor::Lock( int f ) { + idMover_Binary *other; + + // lock all the doors on the team + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + idDoor *door = static_cast( other ); + if ( other == moveMaster ) { + if ( door->sndTrigger == NULL ) { + // in this case the sound trigger never got spawned + const char *sndtemp = door->spawnArgs.GetString( "snd_locked" ); + if ( sndtemp != NULL && *sndtemp != NULL ) { + door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } + if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) { + door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + door->spawnArgs.SetInt( "locked", f ); + if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) { + door->SetAASAreaState( f != 0 ); + } + } + } + + if ( f ) { + Close(); + } +} + +/* +================ +idDoor::IsLocked +================ +*/ +int idDoor::IsLocked() { + return spawnArgs.GetInt( "locked" ); +} + +/* +================ +idDoor::IsOpen +================ +*/ +bool idDoor::IsOpen() { + return ( moverState != MOVER_POS1 ); +} + +/* +================ +idDoor::IsNoTouch +================ +*/ +bool idDoor::IsNoTouch() { + return noTouch; +} + +/* +================ +idDoor::AllowPlayerOnly +================ +*/ +bool idDoor::AllowPlayerOnly( idEntity *ent ) { + if ( playerOnly && !ent->IsType(idPlayer::Type) ) { + return false; + } + + return true; +} + +/* +====================== +idDoor::CalcTriggerBounds + +Calcs bounds for a trigger. +====================== +*/ +void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { + idMover_Binary *other; + int i; + int best; + + // find the bounds of everything on the team + bounds = GetPhysics()->GetAbsBounds(); + + fl.takedamage = true; + for( other = activateChain; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + // find the bounds of everything on the team + bounds.AddBounds( other->GetPhysics()->GetAbsBounds() ); + + // set all of the slaves as shootable + other->fl.takedamage = true; + } + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + normalAxisIndex = best; + bounds[0][ best ] -= size; + bounds[1][ best ] += size; + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); +} + +/* +====================== +idDoor::Event_StartOpen + +if "start_open", reverse position 1 and 2 +====================== +*/ +void idDoor::Event_StartOpen() { + float time; + float speed; + + // if "start_open", reverse position 1 and 2 + pos1 = pos2; + pos2 = GetPhysics()->GetOrigin(); + + spawnArgs.GetFloat( "speed", "400", speed ); + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } +} + +/* +====================== +idDoor::Event_SpawnDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them. +====================== +*/ +void idDoor::Event_SpawnDoorTrigger() { + idBounds bounds; + idMover_Binary *other; + bool toggle; + + if ( trigger ) { + // already have a trigger, so don't spawn a new one. + return; + } + + // check if any of the doors are marked as toggled + toggle = false; + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) { + toggle = true; + break; + } + } + + if ( toggle ) { + // mark them all as toggled + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + other->spawnArgs.Set( "toggle", "1" ); + } + } + // don't spawn trigger + return; + } + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp != NULL && *sndtemp != NULL ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + + CalcTriggerBounds( triggersize, bounds ); + + // create a trigger clip model + trigger = new (TAG_PHYSICS_CLIP_MOVER) idClipModel( idTraceModel( bounds ) ); + trigger->Link( gameLocal.clip, this, 255, GetPhysics()->GetOrigin(), mat3_identity ); + trigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( trigger ); + + MatchActivateTeam( moverState, gameLocal.slow.time ); +} + +/* +====================== +idDoor::Event_SpawnSoundTrigger + +Spawn a sound trigger to activate locked sound if it exists. +====================== +*/ +void idDoor::Event_SpawnSoundTrigger() { + idBounds bounds; + + if ( sndTrigger ) { + return; + } + + CalcTriggerBounds( triggersize * 0.5f, bounds ); + + // create a trigger clip model + sndTrigger = new (TAG_PHYSICS_CLIP_MOVER) idClipModel( idTraceModel( bounds ) ); + sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity ); + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( sndTrigger ); +} + +/* +================ +idDoor::Event_Reached_BinaryMover +================ +*/ +void idDoor::Event_Reached_BinaryMover() { + if ( moverState == MOVER_2TO1 ) { + SetBlocked( false ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerClosed", kv ); + } + } else if ( moverState == MOVER_1TO2 ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOpened", kv ); + } + } + idMover_Binary::Event_Reached_BinaryMover(); +} + +/* +================ +idDoor::Blocked_Door +================ +*/ +void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + SetBlocked( true ); + + if ( crusher ) { + return; // crushers don't reverse + } + + // reverse direction + Use_BinaryMover( moveMaster->GetActivator() ); + + if ( companionDoor ) { + companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity ); + } +} + +/* +=============== +idDoor::SetCompanion +=============== +*/ +void idDoor::SetCompanion( idDoor *door ) { + companionDoor = door; +} + +/* +=============== +idDoor::Event_PartBlocked +=============== +*/ +void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +/* +================ +idDoor::Event_Touch +================ +*/ +void idDoor::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate; + idVec3 planeaxis1, planeaxis2, normal; + idBounds bounds; + + if ( common->IsClient() ) { + return; + } + + if ( !enabled ) { + return; + } + + if ( trigger && trace->c.id == trigger->GetId() ) { + if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) { + if ( AllowPlayerOnly( other ) ) { + Use( this, other ); + } + } + } else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { + if ( other && other->IsType( idPlayer::Type ) && IsLocked() && gameLocal.slow.time > nextSndTriggerTime ) { + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.slow.time + 10000; + } + } +} + +/* +================ +idDoor::Event_SpectatorTouch +================ +*/ +void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate, normal; + idBounds bounds; + idPlayer *p; + + assert( other && other->IsType( idPlayer::Type ) && static_cast< idPlayer * >( other )->spectating ); + + p = static_cast< idPlayer * >( other ); + // avoid flicker when stopping right at clip box boundaries + if ( p->lastSpectateTeleport > gameLocal.slow.time - 1000 ) { + return; + } + if ( trigger && !IsOpen() ) { + // teleport to the other side, center to the middle of the trigger brush + bounds = trigger->GetAbsBounds(); + contact = trace->endpos - bounds.GetCenter(); + translate = bounds.GetCenter(); + normal.Zero(); + normal[ normalAxisIndex ] = 1.0f; + if ( normal * contact > 0 ) { + translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } else { + translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } + p->SetOrigin( translate ); + p->lastSpectateTeleport = gameLocal.slow.time; + } +} + +/* +================ +idDoor::Event_Activate +================ +*/ +void idDoor::Event_Activate( idEntity *activator ) { + int old_lock; + + if ( spawnArgs.GetInt( "locked" ) ) { + if ( !trigger ) { + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + if ( buddyStr.Length() ) { + idEntity *buddy = gameLocal.FindEntity( buddyStr ); + if ( buddy ) { + buddy->SetShaderParm( SHADERPARM_MODE, 1 ); + buddy->UpdateVisuals(); + } + } + + old_lock = spawnArgs.GetInt( "locked" ); + Lock( 0 ); + if ( old_lock == 2 ) { + return; + } + } + + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync != NULL && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + + ActivateTargets( activator ); + + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + UpdateVisuals(); + + Use_BinaryMover( activator ); +} + +/* +================ +idDoor::Event_Open +================ +*/ +void idDoor::Event_Open() { + Open(); +} + +/* +================ +idDoor::Event_Close +================ +*/ +void idDoor::Event_Close() { + Close(); +} + +/* +================ +idDoor::Event_Lock +================ +*/ +void idDoor::Event_Lock( int f ) { + Lock( f ); +} + +/* +================ +idDoor::Event_IsOpen +================ +*/ +void idDoor::Event_IsOpen() { + bool state; + + state = IsOpen(); + idThread::ReturnFloat( state ); +} + +/* +================ +idDoor::Event_Locked +================ +*/ +void idDoor::Event_Locked() { + idThread::ReturnFloat( spawnArgs.GetInt("locked") ); +} + +/* +================ +idDoor::Event_OpenPortal + +Sets the portal associtated with this door to be open +================ +*/ +void idDoor::Event_OpenPortal() { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + } +} + +/* +================ +idDoor::Event_ClosePortal + +Sets the portal associtated with this door to be closed +================ +*/ +void idDoor::Event_ClosePortal() { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( !slave->IsHidden() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + } + } +} + + +/* +=============================================================================== + +idPlat + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Binary, idPlat ) + EVENT( EV_Touch, idPlat::Event_Touch ) + EVENT( EV_TeamBlocked, idPlat::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idPlat::Event_PartBlocked ) +END_CLASS + +/* +=============== +idPlat::idPlat +=============== +*/ +idPlat::idPlat() { + trigger = NULL; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); +} + +/* +=============== +idPlat::~idPlat +=============== +*/ +idPlat::~idPlat() { + if ( trigger ) { + delete trigger; + } +} + +/* +=============== +idPlat::Save +=============== +*/ +void idPlat::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Restore +=============== +*/ +void idPlat::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Spawn +=============== +*/ +void idPlat::Spawn() { + float lip; + float height; + float time; + float speed; + float accel; + float decel; + bool noTouch; + + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetFloat( "damage", "0", damage ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetFloat( "lip", "8", lip ); + spawnArgs.GetFloat( "accel_time", "0.25", accel ); + spawnArgs.GetFloat( "decel_time", "0.25", decel ); + + // create second position + if ( !spawnArgs.GetFloat( "height", "0", height ) ) { + height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip; + } + + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // pos1 is the rest (bottom) position, pos2 is the top + pos2 = GetPhysics()->GetOrigin(); + pos1 = pos2; + pos1[2] -= height; + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, accel, decel ); + } else { + InitSpeed( pos1, pos2, speed, accel, decel ); + } + + SetMoverState( MOVER_POS1, gameLocal.slow.time ); + UpdateVisuals(); + + // spawn the trigger if one hasn't been custom made + if ( !noTouch ) { + // spawn trigger + SpawnPlatTrigger( pos1 ); + } +} + +/* +================ +idPlat::RunPhysics_NoBlocking +================ +*/ +void idPlat::RunPhysics_NoBlocking() { + int i, startTime, endTime; + idEntity * part = NULL, *blockedPart = NULL, *blockingEntity = NULL; + trace_t results; + bool moved; + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } + return; + } + + /* + // if this entity is a team slave don't do anything because the team master will handle everything + if ( teamMaster && teamMaster != this ) { + return false; + } + */ + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->GetTeamChain() ) { + if ( part->GetPhysics() ) { + if ( !part->fl.solidForTeam ) { + part->GetPhysics()->DisableClip(); + } + part->GetPhysics()->SaveState(); + } + } + + + // move the whole team + for ( part = this; part != NULL; part = part->GetTeamChain() ) { + + if ( part->GetPhysics() ) { + + // run physics + moved = part->GetPhysics()->Evaluate( endTime - startTime, endTime ); + + // check if the object is blocked + blockingEntity = part->GetPhysics()->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateVisuals(); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->GetTeamChain() ) { + if ( part->GetPhysics() ) { + if ( !part->fl.solidForTeam ) { + part->GetPhysics()->EnableClip(); + } + } + } + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + ent->GetPhysics()->SetPushed( endTime - startTime ); + } +} + +/* +================ +idPlat::ClientThink +================ +*/ +void idPlat::ClientThink( const int curTime, const float fraction, const bool predict ) { + InterpolatePhysicsOnly( fraction ); + + Present(); + + + + //idMover_Binary::ClientThink( curTime, fraction, predict ); + + /* + idVec3 masterOrigin; + idMat3 masterAxis; + + // Dont bother with blocking entities on clients.. host tells us our move state. + RunPhysics_NoBlocking(); + + Present(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { + trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); + } + } + } + */ +} + +/* +================ +idPlat::Think +================ +*/ +void idPlat::Think() { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { + trigger->Link( gameLocal.clip, this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); + } + } + } +} + +/* +================ +idPlat::PreBind +================ +*/ +void idPlat::PreBind() { + idMover_Binary::PreBind(); +} + +/* +================ +idPlat::PostBind +================ +*/ +void idPlat::PostBind() { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ); +} + +/* +================ +idPlat::GetLocalTriggerPosition +================ +*/ +void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +============== +idPlat::SpawnPlatTrigger +=============== +*/ +void idPlat::SpawnPlatTrigger( idVec3 &pos ) { + idBounds bounds; + idVec3 tmin; + idVec3 tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + + bounds = GetPhysics()->GetBounds(); + + tmin[0] = bounds[0][0] + 33; + tmin[1] = bounds[0][1] + 33; + tmin[2] = bounds[0][2]; + + tmax[0] = bounds[1][0] - 33; + tmax[1] = bounds[1][1] - 33; + tmax[2] = bounds[1][2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f; + tmax[1] = tmin[1] + 1; + } + + trigger = new (TAG_PHYSICS_CLIP_MOVER) idClipModel( idTraceModel( idBounds( tmin, tmax ) ) ); + trigger->Link( gameLocal.clip, this, 255, GetPhysics()->GetOrigin(), mat3_identity ); + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +============== +idPlat::Event_Touch +=============== +*/ +void idPlat::Event_Touch( idEntity *other, trace_t *trace ) { + if ( common->IsClient() ) { + return; + } + + if ( !other->IsType( idPlayer::Type ) ) { + return; + } + + if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) { + Use_BinaryMover( other ); + } +} + +/* +================ +idPlat::Event_TeamBlocked +================ +*/ +void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + Use_BinaryMover( activatedBy.GetEntity() ); +} + +/* +=============== +idPlat::Event_PartBlocked +=============== +*/ +void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + + +/* +=============================================================================== + +idMover_Periodic + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMover_Periodic ) + EVENT( EV_TeamBlocked, idMover_Periodic::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover_Periodic::Event_PartBlocked ) +END_CLASS + +/* +=============== +idMover_Periodic::idMover_Periodic +=============== +*/ +idMover_Periodic::idMover_Periodic() { + damage = 0.0f; + fl.neverDormant = false; +} + +/* +=============== +idMover_Periodic::Spawn +=============== +*/ +void idMover_Periodic::Spawn() { + spawnArgs.GetFloat( "damage", "0", damage ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + GetPhysics()->SetContents( 0 ); + } +} + +/* +=============== +idMover_Periodic::Save +=============== +*/ +void idMover_Periodic::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( damage ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +=============== +idMover_Periodic::Restore +=============== +*/ +void idMover_Periodic::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( damage ); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + + +/* +================ +idMover_Periodic::Think +================ +*/ +void idMover_Periodic::Think() { + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + RunPhysics(); + Present(); +} + +/* +=============== +idMover_Periodic::Event_TeamBlocked +=============== +*/ +void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { +} + +/* +=============== +idMover_Periodic::Event_PartBlocked +=============== +*/ +void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +/* +================ +idMover_Periodic::WriteToSnapshot +================ +*/ +void idMover_Periodic::WriteToSnapshot( idBitMsg &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Periodic::ReadFromSnapshot +================ +*/ +void idMover_Periodic::ReadFromSnapshot( const idBitMsg &msg ) { + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idRotater + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRotater ) + EVENT( EV_Activate, idRotater::Event_Activate ) +END_CLASS + +/* +=============== +idRotater::idRotater +=============== +*/ +idRotater::idRotater() { + activatedBy = this; +} + +/* +=============== +idRotater::Spawn +=============== +*/ +void idRotater::Spawn() { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.slow.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + ProcessEvent( &EV_Activate, this ); + } +} + +/* +=============== +idRotater::Save +=============== +*/ +void idRotater::Save( idSaveGame *savefile ) const { + activatedBy.Save( savefile ); +} + +/* +=============== +idRotater::Restore +=============== +*/ +void idRotater::Restore( idRestoreGame *savefile ) { + activatedBy.Restore( savefile ); +} + +/* +=============== +idRotater::Event_Activate +=============== +*/ +void idRotater::Event_Activate( idEntity *activator ) { + float speed; + bool x_axis; + bool y_axis; + idAngles delta; + + activatedBy = activator; + + delta.Zero(); + + if ( !spawnArgs.GetBool( "rotate" ) ) { + spawnArgs.Set( "rotate", "1" ); + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of rotation + if ( x_axis ) { + delta[2] = speed; + } else if ( y_axis ) { + delta[0] = speed; + } else { + delta[1] = speed; + } + } else { + spawnArgs.Set( "rotate", "0" ); + } + + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.slow.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idBobber ) +END_CLASS + +/* +=============== +idBobber::idBobber +=============== +*/ +idBobber::idBobber() { +} + +/* +=============== +idBobber::Spawn +=============== +*/ +void idBobber::Spawn() { + float speed; + float height; + float phase; + bool x_axis; + bool y_axis; + idVec3 delta; + + spawnArgs.GetFloat( "speed", "4", speed ); + spawnArgs.GetFloat( "height", "32", height ); + spawnArgs.GetFloat( "phase", "0", phase ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of bobbing + delta = vec3_origin; + if ( x_axis ) { + delta[ 0 ] = height; + } else if ( y_axis ) { + delta[ 1 ] = height; + } else { + delta[ 2 ] = height; + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idPendulum + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idPendulum ) +END_CLASS + +/* +=============== +idPendulum::idPendulum +=============== +*/ +idPendulum::idPendulum() { +} + +/* +=============== +idPendulum::Spawn +=============== +*/ +void idPendulum::Spawn() { + float speed; + float freq; + float length; + float phase; + + spawnArgs.GetFloat( "speed", "30", speed ); + spawnArgs.GetFloat( "phase", "0", phase ); + + if ( spawnArgs.GetFloat( "freq", "", freq ) ) { + if ( freq <= 0.0f ) { + gameLocal.Error( "Invalid frequency on entity '%s'", GetName() ); + } + } else { + // find pendulum length + length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] ); + if ( length < 8 ) { + length = 8; + } + + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) ); + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRiser ) +EVENT( EV_Activate, idRiser::Event_Activate ) +END_CLASS + +/* +=============== +idRiser::idRiser +=============== +*/ +idRiser::idRiser() { +} + +/* +=============== +idRiser::Spawn +=============== +*/ +void idRiser::Spawn() { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_MOVER) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + SetPhysics( &physicsObj ); +} + +/* +================ +idRiser::Event_Activate +================ +*/ +void idRiser::Event_Activate( idEntity *activator ) { + + if ( !IsHidden() && spawnArgs.GetBool("hide") ) { + Hide(); + } else { + Show(); + float time; + float height; + idVec3 delta; + + spawnArgs.GetFloat( "time", "4", time ); + spawnArgs.GetFloat( "height", "32", height ); + + delta = vec3_origin; + delta[ 2 ] = height; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.slow.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin ); + } +} diff --git a/neo/d3xp/Mover.h b/neo/d3xp/Mover.h new file mode 100644 index 00000000..338c8fa9 --- /dev/null +++ b/neo/d3xp/Mover.h @@ -0,0 +1,556 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_MOVER_H__ +#define __GAME_MOVER_H__ + +extern const idEventDef EV_TeamBlocked; +extern const idEventDef EV_PartBlocked; +extern const idEventDef EV_ReachedPos; +extern const idEventDef EV_ReachedAng; + +/* +=============================================================================== + + General movers. + +=============================================================================== +*/ + +class idMover : public idEntity { +public: + CLASS_PROTOTYPE( idMover ); + + idMover(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + + virtual void Hide(); + virtual void Show(); + + void SetPortalState( bool open ); + +protected: + typedef enum { + ACCELERATION_STAGE, + LINEAR_STAGE, + DECELERATION_STAGE, + FINISHED_STAGE + } moveStage_t; + + typedef enum { + MOVER_NONE, + MOVER_ROTATING, + MOVER_MOVING, + MOVER_SPLINE + } moverCommand_t; + + // + // mover directions. make sure to change script/doom_defs.script if you add any, or change their order + // + typedef enum { + DIR_UP = -1, + DIR_DOWN = -2, + DIR_LEFT = -3, + DIR_RIGHT = -4, + DIR_FORWARD = -5, + DIR_BACK = -6, + DIR_REL_UP = -7, + DIR_REL_DOWN = -8, + DIR_REL_LEFT = -9, + DIR_REL_RIGHT = -10, + DIR_REL_FORWARD = -11, + DIR_REL_BACK = -12 + } moverDir_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idVec3 dir; + } moveState_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idAngles rot; + } rotationState_t; + + idPhysics_Parametric physicsObj; + + void Event_OpenPortal(); + void Event_ClosePortal(); + void Event_PartBlocked( idEntity *blockingEntity ); + + void MoveToPos( const idVec3 &pos); + void UpdateMoveSound( moveStage_t stage ); + void UpdateRotationSound( moveStage_t stage ); + void SetGuiStates( const char *state ); + void FindGuiTargets(); + void SetGuiState( const char *key, const char *val ) const; + + virtual void DoneMoving(); + virtual void DoneRotating(); + virtual void BeginMove( idThread *thread = NULL ); + virtual void BeginRotation( idThread *thread, bool stopwhendone ); + moveState_t move; + +private: + rotationState_t rot; + + int move_thread; + int rotate_thread; + idAngles dest_angles; + idAngles angle_delta; + idVec3 dest_position; + idVec3 move_delta; + float move_speed; + int move_time; + int deceltime; + int acceltime; + bool stopRotation; + bool useSplineAngles; + idEntityPtr splineEnt; + moverCommand_t lastCommand; + float damage; + + qhandle_t areaPortal; // 0 = no portal + + idList< idEntityPtr, TAG_MOVER > guiTargets; + + void VectorForDir( float dir, idVec3 &vec ); + idCurve_Spline *GetSpline( idEntity *splineEntity ) const; + + void Event_SetCallback(); + void Event_TeamBlocked( idEntity *blockedPart, idEntity *blockingEntity ); + void Event_StopMoving(); + void Event_StopRotating(); + void Event_UpdateMove(); + void Event_UpdateRotation(); + void Event_SetMoveSpeed( float speed ); + void Event_SetMoveTime( float time ); + void Event_SetDecelerationTime( float time ); + void Event_SetAccellerationTime( float time ); + void Event_MoveTo( idEntity *ent ); + void Event_MoveToPos( idVec3 &pos ); + void Event_MoveDir( float angle, float distance ); + void Event_MoveAccelerateTo( float speed, float time ); + void Event_MoveDecelerateTo( float speed, float time ); + void Event_RotateDownTo( int axis, float angle ); + void Event_RotateUpTo( int axis, float angle ); + void Event_RotateTo( idAngles &angles ); + void Event_Rotate( idAngles &angles ); + void Event_RotateOnce( idAngles &angles ); + void Event_Bob( float speed, float phase, idVec3 &depth ); + void Event_Sway( float speed, float phase, idAngles &depth ); + void Event_SetAccelSound( const char *sound ); + void Event_SetDecelSound( const char *sound ); + void Event_SetMoveSound( const char *sound ); + void Event_FindGuiTargets(); + void Event_InitGuiTargets(); + void Event_EnableSplineAngles(); + void Event_DisableSplineAngles(); + void Event_RemoveInitialSplineAngles(); + void Event_StartSpline( idEntity *splineEntity ); + void Event_StopSpline(); + void Event_Activate( idEntity *activator ); + void Event_PostRestore( int start, int total, int accel, int decel, int useSplineAng ); + void Event_IsMoving(); + void Event_IsRotating(); +}; + +class idSplinePath : public idEntity { +public: + CLASS_PROTOTYPE( idSplinePath ); + + idSplinePath(); + + void Spawn(); +}; + + +struct floorInfo_s { + idVec3 pos; + idStr door; + int floor; +}; + +class idElevator : public idMover { +public: + CLASS_PROTOTYPE( idElevator ); + + idElevator(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + void Event_GotoFloor( int floor ); + floorInfo_s * GetFloorInfo( int floor ); + +protected: + virtual void DoneMoving(); + virtual void BeginMove( idThread *thread = NULL ); + void SpawnTrigger( const idVec3 &pos ); + void GetLocalTriggerPosition(); + void Event_Touch( idEntity *other, trace_t *trace ); + +private: + typedef enum { + INIT, + IDLE, + WAITING_ON_DOORS + } elevatorState_t; + + elevatorState_t state; + idList floorInfo; + int currentFloor; + int pendingFloor; + int lastFloor; + bool controlsDisabled; + float returnTime; + int returnFloor; + int lastTouchTime; + + class idDoor * GetDoor( const char *name ); + void Think(); + void OpenInnerDoor(); + void OpenFloorDoor( int floor ); + void CloseAllDoors(); + void DisableAllDoors(); + void EnableProperDoors(); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_Activate( idEntity *activator ); + void Event_PostFloorArrival(); + + void Event_SetGuiStates(); + +}; + + +/* +=============================================================================== + + Binary movers. + +=============================================================================== +*/ + +typedef enum { + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1 +} moverState_t; + +class idMover_Binary : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Binary ); + + idMover_Binary(); + ~idMover_Binary(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void PreBind(); + virtual void PostBind(); + + void Enable( bool b ); + void InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ); + void InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ); + void GotoPosition1(); + void GotoPosition2(); + void Use_BinaryMover( idEntity *activator ); + void SetGuiStates( const char *state ); + void UpdateBuddies( int val ); + idMover_Binary * GetActivateChain() const { return activateChain; } + idMover_Binary * GetMoveMaster() const { return moveMaster; } + void BindTeam( idEntity *bindTo ); + void SetBlocked( bool b ); + bool IsBlocked(); + idEntity * GetActivator() const; + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + + void SetPortalState( bool open ); + +protected: + idVec3 pos1; + idVec3 pos2; + moverState_t moverState; + idMover_Binary * moveMaster; + idMover_Binary * activateChain; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + float wait; + float damage; + int duration; + int accelTime; + int decelTime; + idEntityPtr activatedBy; + int stateStartTime; + idStr team; + bool enabled; + int move_thread; + int updateStatus; // 1 = lock behaviour, 2 = open close status + idStrList buddies; + idPhysics_Parametric physicsObj; + qhandle_t areaPortal; // 0 = no portal + bool blocked; + bool playerOnly; + idList< idEntityPtr, TAG_MOVER > guiTargets; + + void MatchActivateTeam( moverState_t newstate, int time ); + void JoinActivateTeam( idMover_Binary *master ); + + void UpdateMoverSound( moverState_t state ); + void SetMoverState( moverState_t newstate, int time ); + moverState_t GetMoverState() const { return moverState; } + void FindGuiTargets(); + void SetGuiState( const char *key, const char *val ) const; + + void Event_SetCallback(); + void Event_ReturnToPos1(); + void Event_Use_BinaryMover( idEntity *activator ); + void Event_Reached_BinaryMover(); + void Event_MatchActivateTeam( moverState_t newstate, int time ); + void Event_Enable(); + void Event_Disable(); + void Event_OpenPortal(); + void Event_ClosePortal(); + void Event_FindGuiTargets(); + void Event_InitGuiTargets(); + + static void GetMovedir( float dir, idVec3 &movedir ); +}; + +class idDoor : public idMover_Binary { +public: + CLASS_PROTOTYPE( idDoor ); + + idDoor(); + ~idDoor(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void Think(); + virtual void PreBind(); + virtual void PostBind(); + virtual void Hide(); + virtual void Show(); + + bool IsOpen(); + bool IsNoTouch(); + bool AllowPlayerOnly( idEntity *ent ); + int IsLocked(); + void Lock( int f ); + void Use( idEntity *other, idEntity *activator ); + void Close(); + void Open(); + void SetCompanion( idDoor *door ); + +private: + float triggersize; + bool crusher; + bool noTouch; + bool aas_area_closed; + idStr buddyStr; + idClipModel * trigger; + idClipModel * sndTrigger; + int nextSndTriggerTime; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + idStr requires; + int removeItem; + idStr syncLock; + int normalAxisIndex; // door faces X or Y for spectator teleports + idDoor * companionDoor; + + void SetAASAreaState( bool closed ); + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void CalcTriggerBounds( float size, idBounds &bounds ); + + void Event_Reached_BinaryMover(); + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Activate( idEntity *activator ); + void Event_StartOpen(); + void Event_SpawnDoorTrigger(); + void Event_SpawnSoundTrigger(); + void Event_Close(); + void Event_Open(); + void Event_Lock( int f ); + void Event_IsOpen(); + void Event_Locked(); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); + void Event_OpenPortal(); + void Event_ClosePortal(); +}; + +class idPlat : public idMover_Binary { +public: + CLASS_PROTOTYPE( idPlat ); + + idPlat(); + ~idPlat(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void RunPhysics_NoBlocking(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void Think(); + virtual void PreBind(); + virtual void PostBind(); + +private: + idClipModel * trigger; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void SpawnPlatTrigger( idVec3 &pos ); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Special periodic movers. + +=============================================================================== +*/ + +class idMover_Periodic : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Periodic ); + + idMover_Periodic(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + +protected: + idPhysics_Parametric physicsObj; + float damage; + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); +}; + +class idRotater : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRotater ); + + idRotater(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idEntityPtr activatedBy; + + void Event_Activate( idEntity *activator ); +}; + +class idBobber : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idBobber ); + + idBobber(); + + void Spawn(); + +private: +}; + +class idPendulum : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idPendulum ); + + idPendulum(); + + void Spawn(); + +private: +}; + +class idRiser : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRiser ); + + idRiser(); + + void Spawn(); + +private: + void Event_Activate( idEntity *activator ); +}; + +#endif /* !__GAME_MOVER_H__ */ diff --git a/neo/d3xp/MultiplayerGame.cpp b/neo/d3xp/MultiplayerGame.cpp new file mode 100644 index 00000000..a85d12c9 --- /dev/null +++ b/neo/d3xp/MultiplayerGame.cpp @@ -0,0 +1,3188 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// could be a problem if players manage to go down sudden deaths till this .. oh well +#define LASTMAN_NOLIVES -20 + +extern idCVar ui_skinIndex; + +const char * idMultiplayerGame::teamNames[] = { "#str_02499", "#str_02500" }; +const char * idMultiplayerGame::skinNames[] = { + "skins/characters/player/marine_mp", + "skins/characters/player/marine_mp_red", + "skins/characters/player/marine_mp_blue", + "skins/characters/player/marine_mp_green", + "skins/characters/player/marine_mp_yellow", + "skins/characters/player/marine_mp_purple", + "skins/characters/player/marine_mp_grey", + "skins/characters/player/marine_mp_orange" +}; +const idVec3 idMultiplayerGame::skinColors[] = { + idVec3( 0.25f, 0.25f, 0.25f ), // light grey + idVec3( 1.00f, 0.00f, 0.00f ), // red + idVec3( 0.20f, 0.50f, 0.80f ), // blue + idVec3( 0.00f, 0.80f, 0.10f ), // green + idVec3( 1.00f, 0.80f, 0.10f ), // yellow + idVec3( 0.39f, 0.199f, 0.3f ), // purple + idVec3( 0.425f, 0.484f, 0.445f ), // dark grey + idVec3( 0.484f, 0.312f, 0.074f) // orange +}; +const int idMultiplayerGame::numSkins = sizeof( idMultiplayerGame::skinNames ) / sizeof( idMultiplayerGame::skinNames[0] ); + +const char * idMultiplayerGame::GetTeamName( int team ) const { return teamNames[idMath::ClampInt( 0, 1, team )]; } +const char * idMultiplayerGame::GetSkinName( int skin ) const { return skinNames[idMath::ClampInt( 0, numSkins-1, skin )]; } +const idVec3 & idMultiplayerGame::GetSkinColor( int skin ) const { return skinColors[idMath::ClampInt( 0, numSkins-1, skin )]; } + +// make CTF not included in game modes for consoles + +const char * gameTypeNames_WithCTF[] = { "Deathmatch", "Tourney", "Team DM", "Last Man", "CTF", "" }; +const char * gameTypeDisplayNames_WithCTF[] = { "#str_04260", "#str_04261", "#str_04262", "#str_04263", "#str_swf_mode_ctf", "" }; + +const char * gameTypeNames_WithoutCTF[] = { "Deathmatch", "Tourney", "Team DM", "Last Man", "" }; +const char * gameTypeDisplayNames_WithoutCTF[] = { "#str_04260", "#str_04261", "#str_04262", "#str_04263", "" }; + +compile_time_assert( GAME_DM == 0 ); +compile_time_assert( GAME_TOURNEY == 1 ); +compile_time_assert( GAME_TDM == 2 ); +compile_time_assert( GAME_LASTMAN == 3 ); +compile_time_assert( GAME_CTF == 4 ); + +idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" ); + +// global sounds transmitted by index - 0 .. SND_COUNT +// sounds in this list get precached on MP start +const char *idMultiplayerGame::GlobalSoundStrings[] = { + "sound/vo/feedback/voc_youwin.wav", + "sound/vo/feedback/voc_youlose.wav", + "sound/vo/feedback/fight.wav", + "sound/vo/feedback/three.wav", + "sound/vo/feedback/two.wav", + "sound/vo/feedback/one.wav", + "sound/vo/feedback/sudden_death.wav", + "sound/vo/ctf/flag_capped_yours.wav", + "sound/vo/ctf/flag_capped_theirs.wav", + "sound/vo/ctf/flag_return.wav", + "sound/vo/ctf/flag_taken_yours.wav", + "sound/vo/ctf/flag_taken_theirs.wav", + "sound/vo/ctf/flag_dropped_yours.wav", + "sound/vo/ctf/flag_dropped_theirs.wav" +}; + +// handy verbose +const char *idMultiplayerGame::GameStateStrings[] = { + "INACTIVE", + "WARMUP", + "COUNTDOWN", + "GAMEON", + "SUDDENDEATH", + "GAMEREVIEW", + "NEXTGAME" +}; + +/* +================ +idMultiplayerGame::idMultiplayerGame +================ +*/ +idMultiplayerGame::idMultiplayerGame() { + + teamFlags[0] = NULL; + teamFlags[1] = NULL; + + teamPoints[0] = 0; + teamPoints[1] = 0; + + flagMsgOn = true; + + player_blue_flag = -1; + player_red_flag = -1; + + Clear(); + + scoreboardManager = NULL; +} + +/* +================ +idMultiplayerGame::Shutdown +================ +*/ +void idMultiplayerGame::Shutdown() { + Clear(); +} + +/* +================ +idMultiplayerGame::Reset +================ +*/ +void idMultiplayerGame::Reset() { + Clear(); + ClearChatData(); + + if ( common->IsMultiplayer() ) { + scoreboardManager = new idMenuHandler_Scoreboard(); + if ( scoreboardManager != NULL ) { + scoreboardManager->Initialize( "scoreboard", common->SW() ); + } + } + + ClearChatData(); + warmupEndTime = 0; + lastChatLineTime = 0; + +} + +/* +================ +idMultiplayerGame::ServerClientConnect +================ +*/ +void idMultiplayerGame::ServerClientConnect( int clientNum ) { + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); +} + +/* +================ +idMultiplayerGame::SpawnPlayer +================ +*/ +void idMultiplayerGame::SpawnPlayer( int clientNum ) { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + lobbyUserID_t & lobbyUserID = gameLocal.lobbyUserIDs[clientNum]; + + AddChatLine( idLocalization::GetString( "#str_07177" ), lobby.GetLobbyUserName( lobbyUserID ) ); + + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + if ( !common->IsClient() ) { + if ( gameLocal.gameType == GAME_LASTMAN ) { + // Players spawn with no lives to prevent them from getting the clean slate achievement if + // they didn't earn it. + playerState[ clientNum ].fragCount = LASTMAN_NOLIVES; + } + + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + p->spawnedTime = gameLocal.serverTime; + p->team = 0; + p->tourneyRank = 0; + if ( gameLocal.gameType == GAME_TOURNEY && gameState == GAMEON ) { + p->tourneyRank++; + } + } +} + +/* +================ +idMultiplayerGame::Clear +================ +*/ +void idMultiplayerGame::Clear() { + int i; + + gameState = INACTIVE; + nextState = INACTIVE; + nextStateSwitch = 0; + matchStartedTime = 0; + currentTourneyPlayer[ 0 ] = -1; + currentTourneyPlayer[ 1 ] = -1; + one = two = three = false; + memset( &playerState, 0 , sizeof( playerState ) ); + lastWinner = -1; + pureReady = false; + + CleanupScoreboard(); + + fragLimitTimeout = 0; + voiceChatThrottle = 0; + for ( i = 0; i < NUM_CHAT_NOTIFY; i++ ) { + chatHistory[ i ].line.Clear(); + } + startFragLimit = -1; +} + +/* +================ +idMultiplayerGame::Clear +================ +*/ +void idMultiplayerGame::CleanupScoreboard() { + if ( scoreboardManager != NULL ) { + delete scoreboardManager; + scoreboardManager = NULL; + } +} + +/* +================ +idMultiplayerGame::GetFlagPoints + +Gets number of captures in CTF game. + +0 = red team +1 = blue team +================ +*/ +int idMultiplayerGame::GetFlagPoints( int team ) +{ + assert( team <= 1 ); + + return teamPoints[ team ]; +} + +/* +================ +idMultiplayerGame::UpdatePlayerRanks +================ +*/ +void idMultiplayerGame::UpdatePlayerRanks() { + int i, j, k; + idPlayer *players[MAX_CLIENTS]; + idEntity *ent; + idPlayer *player; + + memset( players, 0, sizeof( players ) ); + numRankedPlayers = 0; + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + player = static_cast< idPlayer * >( ent ); + if ( !CanPlay( player ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY ) { + if ( i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) { + continue; + } + } + if ( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES ) { + continue; + } + for ( j = 0; j < numRankedPlayers; j++ ) { + bool insert = false; + + if ( IsGametypeTeamBased() ) { /* CTF */ + if ( player->team != players[ j ]->team ) { + if ( playerState[ i ].teamFragCount > playerState[ players[ j ]->entityNumber ].teamFragCount ) { + // team scores + insert = true; + } else if ( playerState[ i ].teamFragCount == playerState[ players[ j ]->entityNumber ].teamFragCount && player->team < players[ j ]->team ) { + // at equal scores, sort by team number + insert = true; + } + } else if ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount ) { + // in the same team, sort by frag count + insert = true; + } + } else { + insert = ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount ); + } + if ( insert ) { + for ( k = numRankedPlayers; k > j; k-- ) { + players[ k ] = players[ k-1 ]; + } + players[ j ] = player; + break; + } + } + if ( j == numRankedPlayers ) { + players[ numRankedPlayers ] = player; + } + numRankedPlayers++; + } + + memcpy( rankedPlayers, players, sizeof( players ) ); +} + + +/* +================ +idMultiplayerGame::UpdateRankColor +================ +*/ +void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) { + for ( int j = 1; j < 4; j++ ) { + gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] ); + } +} + +/* +================ +idMultiplayerGame::IsScoreboardActive +================ +*/ +bool idMultiplayerGame::IsScoreboardActive() { + + if ( scoreboardManager != NULL ) { + return scoreboardManager->IsActive(); + } + + return false; +} + +/* +================ +idMultiplayerGame::HandleGuiEvent +================ +*/ +bool idMultiplayerGame::HandleGuiEvent( const sysEvent_t * sev ) { + + if ( scoreboardManager != NULL && scoreboardManager->IsActive() ) { + scoreboardManager->HandleGuiEvent( sev ); + return true; + } + + return false; +} + +/* +================ +idMultiplayerGame::UpdateScoreboard +================ +*/ +void idMultiplayerGame::UpdateScoreboard( idMenuHandler_Scoreboard * scoreboard, idPlayer *owner ) { + + if ( owner == NULL ) { + return; + } + + int redScore = 0; + int blueScore = 0; + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + idList< mpScoreboardInfo > scoreboardInfo; + for ( int i = 0; i < MAX_CLIENTS; ++i ) { + + if ( i < gameLocal.numClients ) { + + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer *player = static_cast(ent); + if ( !player ) { + continue; + } + + idStr spectateData; + idStr playerName; + int score = 0; + int wins = 0; + int ping = 0; + int playerNum = player->entityNumber; + int team = - 1; + + lobbyUserID_t & lobbyUserID = gameLocal.lobbyUserIDs[ playerNum ]; + + if ( IsGametypeTeamBased() ) { + team = player->team; + if ( team == 0 ) { + redScore = playerState[ playerNum ].teamFragCount; + } else if ( team == 1 ) { + blueScore = playerState[ playerNum ].teamFragCount; + } + } + + score = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ playerNum ].fragCount ); + + // HACK - + if( gameLocal.gameType == GAME_LASTMAN && score == LASTMAN_NOLIVES ) { + score = 0; + } + + wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ playerNum ].wins ); + ping = playerState[ playerNum ].ping; + + if ( gameState == WARMUP ) { + if ( player->spectating ) { + spectateData = idLocalization::GetString( "#str_04246" ); + } else { + spectateData = idLocalization::GetString( "#str_04247" ); + } + } else { + if ( gameLocal.gameType == GAME_LASTMAN && playerState[ playerNum ].fragCount == LASTMAN_NOLIVES ) { + spectateData = idLocalization::GetString( "#str_06736" ); + } else if ( player->spectating ) { + spectateData = idLocalization::GetString( "#str_04246" ); + } + } + + if ( playerNum == owner->entityNumber ) { + playerName = "^3"; + playerName.Append( lobby.GetLobbyUserName( lobbyUserID ) ); + playerName.Append( "^0" ); + } else { + playerName = lobby.GetLobbyUserName( lobbyUserID ); + } + + mpScoreboardInfo info; + info.voiceState = session->GetDisplayStateFromVoiceState( session->GetLobbyUserVoiceState( lobbyUserID ) ); + info.name = playerName; + info.team = team; + info.score = score; + info.wins = wins; + info.ping = ping; + info.playerNum = playerNum; + info.spectateData = spectateData; + + bool added = false; + for ( int i = 0; i < scoreboardInfo.Num(); ++i ) { + if ( info.team == scoreboardInfo[i].team ) { + if ( info.score > scoreboardInfo[i].score ) { + scoreboardInfo.Insert( info, i ); + added = true; + break; + } + } else if ( info.team < scoreboardInfo[i].team ) { + scoreboardInfo.Insert( info, i ); + added = true; + break; + } + } + + if ( !added ) { + scoreboardInfo.Append( info ); + } + } + } + + idStr gameInfo; + if ( gameState == GAMEREVIEW ) { + int timeRemaining = nextStateSwitch - gameLocal.serverTime; + int ms = (int) ceilf( timeRemaining / 1000.0f ); + if ( ms == 1 ) { + gameInfo = idLocalization::GetString( "#str_online_game_starts_in_second" ); + gameInfo.Replace( "", idStr( ms ) ); + } else if ( ms > 0 && ms < 30 ) { + gameInfo = idLocalization::GetString( "#str_online_game_starts_in_seconds" ); + gameInfo.Replace( "", idStr( ms ) ); + } + } else { + if ( gameLocal.gameType == GAME_LASTMAN ) { + if ( gameState == GAMEON || gameState == SUDDENDEATH ) { + gameInfo = va( "%s: %i", idLocalization::GetString( "#str_04264" ), startFragLimit ); + } else { + gameInfo = va( "%s: %i", idLocalization::GetString( "#str_04264" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ); + } + } else if ( gameLocal.gameType == GAME_CTF ) { + int captureLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + gameInfo = va( idLocalization::GetString( "#str_11108" ), captureLimit ); + } else { + gameInfo = va( "%s: %i", idLocalization::GetString( "#str_01982" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ); + } + + if ( gameLocal.serverInfo.GetInt( "si_timeLimit" ) > 0 ) { + gameInfo.Append( va( " %s: %i", idLocalization::GetString( "#str_01983" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + } + } + + if ( scoreboardManager ) { + + if ( IsGametypeFlagBased() ) { + scoreboardManager->SetTeamScores( GetFlagPoints( 0 ), GetFlagPoints( 1 ) ); + } else if ( IsGametypeTeamBased() ) { + scoreboardManager->SetTeamScores( redScore, blueScore ); + } + + scoreboardManager->UpdateScoreboard( scoreboardInfo, gameInfo ); + scoreboardManager->UpdateScoreboardSelection(); + scoreboardManager->Update(); + } +} + +/* +================ +idMultiplayerGame::GameTime +================ +*/ +const char *idMultiplayerGame::GameTime() { + static char buff[16]; + int m, s, t, ms; + + if ( gameState == COUNTDOWN ) { + ms = warmupEndTime - gameLocal.serverTime; + + // we never want to show double dashes. + if( ms <= 0 ) { + + // Try to setup time again. + warmupEndTime = gameLocal.serverTime + 1000*cvarSystem->GetCVarInteger( "g_countDown" ); + ms = warmupEndTime - gameLocal.serverTime; + } + + s = ms / 1000 + 1; + if ( ms <= 0 ) { + strcpy( buff, "WMP --" ); + } else { + sprintf( buff, "WMP %i", s ); + } + } else { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + ms = ( timeLimit * 60000 ) - ( gameLocal.serverTime - matchStartedTime ); + } else { + ms = gameLocal.serverTime - matchStartedTime; + } + if ( ms < 0 ) { + ms = 0; + } + + s = ms / 1000; + m = s / 60; + s -= m * 60; + t = s / 10; + s -= t * 10; + + sprintf( buff, "%i:%i%i", m, t, s ); + } + return &buff[0]; +} + +/* +================ +idMultiplayerGame::NumActualClients +================ +*/ +int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) { + idPlayer *p; + int c = 0; + + if ( teamcounts ) { + teamcounts[ 0 ] = teamcounts[ 1 ] = 0; + } + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + p = static_cast< idPlayer * >( ent ); + if ( countSpectators || CanPlay( p ) ) { + c++; + } + if ( teamcounts && CanPlay( p ) ) { + teamcounts[ p->team ]++; + } + } + return c; +} + +/* +================ +idMultiplayerGame::EnoughClientsToPlay +================ +*/ +bool idMultiplayerGame::EnoughClientsToPlay() { + int teamCount[ 2 ]; + int clients = NumActualClients( false, teamCount ); + if ( IsGametypeTeamBased() ) { /* CTF */ + return clients >= 2 && teamCount[ 0 ] && teamCount[ 1 ]; + } else { + return clients >= 2; + } +} + +/* +================ +idMultiplayerGame::FragLimitHit +return the winning player (team player) +if there is no FragLeader(), the game is tied and we return NULL +================ +*/ +idPlayer *idMultiplayerGame::FragLimitHit() { + int i; + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + idPlayer *leader; + + if ( IsGametypeFlagBased() ) /* CTF */ + return NULL; + + leader = FragLeader(); + if ( !leader ) { + return NULL; + } + + if ( fragLimit <= 0 ) { + fragLimit = MP_PLAYER_MAXFRAGS; + } + + if ( gameLocal.gameType == GAME_LASTMAN ) { + // we have a leader, check if any other players have frags left + assert( !static_cast< idPlayer * >( leader )->lastManOver ); + for( i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + if ( !CanPlay( static_cast< idPlayer * >( ent ) ) ) { + continue; + } + if ( ent == leader ) { + continue; + } + if ( playerState[ ent->entityNumber ].fragCount > 0 ) { + return NULL; + } + } + // there is a leader, his score may even be negative, but no one else has frags left or is !lastManOver + return leader; + } else if ( IsGametypeTeamBased() ) { /* CTF */ + if ( playerState[ leader->entityNumber ].teamFragCount >= fragLimit ) { + return leader; + } + } else { + if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) { + return leader; + } + } + + return NULL; +} + +/* +================ +idMultiplayerGame::TimeLimitHit +================ +*/ +bool idMultiplayerGame::TimeLimitHit() { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.serverTime >= matchStartedTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +================ +idMultiplayerGame::WinningTeam +return winning team +-1 if tied or no players +================ +*/ +int idMultiplayerGame::WinningTeam() { + if ( teamPoints[0] > teamPoints[1] ) + return 0; + if ( teamPoints[0] < teamPoints[1] ) + return 1; + return -1; +} + +/* +================ +idMultiplayerGame::PointLimitHit +================ +*/ +bool idMultiplayerGame::PointLimitHit() { + int pointLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + + // default to MP_CTF_MAXPOINTS if needed + if ( pointLimit > MP_CTF_MAXPOINTS ) + pointLimit = MP_CTF_MAXPOINTS; + else if ( pointLimit <= 0 ) + pointLimit = MP_CTF_MAXPOINTS; + + if ( teamPoints[0] == teamPoints[1] ) + return false; + + if ( teamPoints[0] >= pointLimit || + teamPoints[1] >= pointLimit ) + return true; + + return false; +} + +/* +================ +idMultiplayerGame::FragLeader +return the current winner ( or a player from the winning team ) +NULL if even +================ +*/ +idPlayer *idMultiplayerGame::FragLeader() { + int i; + int frags[ MAX_CLIENTS ]; + idPlayer *leader = NULL; + idEntity *ent; + idPlayer *p; + int high = -9999; + int count = 0; + bool teamLead[ 2 ] = { false, false }; + + for ( i = 0 ; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + if ( !CanPlay( static_cast< idPlayer * >( ent ) ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] ) { + continue; + } + if ( static_cast< idPlayer * >( ent )->lastManOver ) { + continue; + } + + int fragc = ( IsGametypeTeamBased() ) ? playerState[i].teamFragCount : playerState[i].fragCount; /* CTF */ + if ( fragc > high ) { + high = fragc; + } + + frags[ i ] = fragc; + } + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + p = static_cast< idPlayer * >( ent ); + p->SetLeader( false ); + + if ( !CanPlay( p ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] ) { + continue; + } + if ( p->lastManOver ) { + continue; + } + if ( p->spectating ) { + continue; + } + + if ( frags[ i ] >= high ) { + leader = p; + count++; + p->SetLeader( true ); + if ( IsGametypeTeamBased() ) { /* CTF */ + teamLead[ p->team ] = true; + } + } + } + + if ( !IsGametypeTeamBased() ) { /* CTF */ + // more than one player at the highest frags + if ( count > 1 ) { + return NULL; + } else { + return leader; + } + } else { + if ( teamLead[ 0 ] && teamLead[ 1 ] ) { + // even game in team play + return NULL; + } + return leader; + } +} + +/* +================ +idGameLocal::UpdateWinsLosses +================ +*/ +void idMultiplayerGame::UpdateWinsLosses( idPlayer *winner ) { + if ( winner ) { + // run back through and update win/loss count + for( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer *player = static_cast(ent); + if ( IsGametypeTeamBased() ) { /* CTF */ + if ( player == winner || ( player != winner && player->team == winner->team ) ) { + playerState[ i ].wins++; + PlayGlobalSound( i, SND_YOUWIN ); + } else { + PlayGlobalSound( i, SND_YOULOSE ); + } + } else if ( gameLocal.gameType == GAME_LASTMAN ) { + if ( player == winner ) { + playerState[ i ].wins++; + PlayGlobalSound( i, SND_YOUWIN ); + } else if ( !player->wantSpectate ) { + PlayGlobalSound( i, SND_YOULOSE ); + } + } else if ( gameLocal.gameType == GAME_TOURNEY ) { + if ( player == winner ) { + playerState[ i ].wins++; + PlayGlobalSound( i, SND_YOUWIN ); + } else if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) { + PlayGlobalSound( i, SND_YOULOSE ); + } + } else { + if ( player == winner ) { + playerState[i].wins++; + PlayGlobalSound( i, SND_YOUWIN ); + } else if ( !player->wantSpectate ) { + PlayGlobalSound( i, SND_YOULOSE ); + } + } + } + } else if ( IsGametypeFlagBased() ) { /* CTF */ + int winteam = WinningTeam(); + + if ( winteam != -1 ) // TODO : print a message telling it why the hell the game ended with no winning team? + for( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer *player = static_cast(ent); + + if ( player->team == winteam ) { + PlayGlobalSound( i, SND_YOUWIN ); + playerState[ i ].wins++; + } else { + PlayGlobalSound( i, SND_YOULOSE ); + } + } + } + + if ( winner ) { + lastWinner = winner->entityNumber; + } else { + lastWinner = -1; + } +} + +/* +================ +idMultiplayerGame::TeamScoreCTF +================ +*/ +void idMultiplayerGame::TeamScoreCTF( int team, int delta ) { + if ( team < 0 || team > 1 ) + return; + + teamPoints[team] += delta; + + if ( gameState == GAMEON || gameState == SUDDENDEATH ) + PrintMessageEvent( MSG_SCOREUPDATE, teamPoints[0], teamPoints[1] ); +} + +/* +================ +idMultiplayerGame::PlayerScoreCTF +================ +*/ +void idMultiplayerGame::PlayerScoreCTF( int playerIdx, int delta ) { + if ( playerIdx < 0 || playerIdx >= MAX_CLIENTS ) + return; + + playerState[ playerIdx ].fragCount += delta; +} + +/* +================ +idMultiplayerGame::GetFlagCarrier +================ +*/ +int idMultiplayerGame::GetFlagCarrier( int team ) { + int iFlagCarrier = -1; + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity * ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + + idPlayer * player = static_cast( ent ); + if ( player->team != team ) + continue; + + if ( player->carryingFlag ) { + if ( iFlagCarrier != -1 ) + gameLocal.Warning( "BUG: more than one flag carrier on %s team", team == 0 ? "red" : "blue" ); + iFlagCarrier = i; + } + } + + return iFlagCarrier; +} + +/* +================ +idMultiplayerGame::TeamScore +================ +*/ +void idMultiplayerGame::TeamScore( int entityNumber, int team, int delta ) { + playerState[ entityNumber ].fragCount += delta; + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer *player = static_cast(ent); + if ( player->team == team ) { + playerState[ player->entityNumber ].teamFragCount += delta; + } + } +} + +/* +================ +idMultiplayerGame::PlayerDeath +================ +*/ +void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, bool telefrag ) { + + // don't do PrintMessageEvent and shit + assert( !common->IsClient() ); + + if( gameState == COUNTDOWN || gameState == WARMUP ) { + // No Kill scores are gained during warmup. + return; + } + + if( dead == NULL ) { + idLib::Warning( "idMultiplayerGame::PlayerDeath dead ptr == NULL, kill will not count" ); + return; + } + + playerState[ dead->entityNumber ].deaths++; + + if ( killer ) { + if ( gameLocal.gameType == GAME_LASTMAN ) { + playerState[ dead->entityNumber ].fragCount--; + + } else if ( IsGametypeTeamBased() ) { /* CTF */ + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + TeamScore( killer->entityNumber, killer->team, -1 ); + } else { + TeamScore( killer->entityNumber, killer->team, +1 ); + } + } else { + playerState[ killer->entityNumber ].fragCount += ( killer == dead ) ? -1 : 1; + } + } + + if ( killer && killer == dead ) { + PrintMessageEvent( MSG_SUICIDE, dead->entityNumber ); + } else if ( killer ) { + if ( telefrag ) { + killer->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_PLAYER_VIA_TELEPORT ); + PrintMessageEvent( MSG_TELEFRAGGED, dead->entityNumber, killer->entityNumber ); + } else if ( IsGametypeTeamBased() && dead->team == killer->team ) { /* CTF */ + PrintMessageEvent( MSG_KILLEDTEAM, dead->entityNumber, killer->entityNumber ); + } else { + if ( killer->PowerUpActive( INVISIBILITY ) ) { + killer->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_5_PLAYERS_USING_INVIS ); + } + if ( killer->PowerUpActive( BERSERK ) ) { + killer->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_USE_BERSERK_TO_KILL_PLAYER ); + } + PrintMessageEvent( MSG_KILLED, dead->entityNumber, killer->entityNumber ); + } + } else { + PrintMessageEvent( MSG_DIED, dead->entityNumber ); + playerState[ dead->entityNumber ].fragCount--; + } +} + +/* +================ +idMultiplayerGame::PlayerStats +================ +*/ +void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) { + + idEntity *ent; + int team; + + *data = 0; + + // make sure we don't exceed the client list + if ( clientNum < 0 || clientNum > gameLocal.numClients ) { + return; + } + + // find which team this player is on + ent = gameLocal.entities[ clientNum ]; + if ( ent && ent->IsType( idPlayer::Type ) ) { + team = static_cast< idPlayer * >(ent)->team; + } else { + return; + } + + idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount ); + + return; + +} + +/* +================ +idMultiplayerGame::DumpTourneyLine +================ +*/ +void idMultiplayerGame::DumpTourneyLine() { + int i; + for ( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) { + common->Printf( "client %d: rank %d\n", i, static_cast< idPlayer * >( gameLocal.entities[ i ] )->tourneyRank ); + } + } +} + +/* +================ +idMultiplayerGame::NewState +================ +*/ +void idMultiplayerGame::NewState( gameState_t news, idPlayer *player ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + int i; + + assert( news != gameState ); + assert( !common->IsClient() ); + assert( news < STATE_COUNT ); + gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ news ] ); + switch( news ) { + case GAMEON: { + gameLocal.LocalMapRestart(); + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteBits( 0, 1 ); + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_RESTART, outMsg, false ); + + teamPoints[0] = 0; + teamPoints[1] = 0; + + PlayGlobalSound( -1, SND_FIGHT ); + matchStartedTime = gameLocal.serverTime; + + idBitMsg matchStartedTimeMsg; + byte matchStartedTimeMsgBuf[ sizeof( matchStartedTime ) ]; + matchStartedTimeMsg.InitWrite( matchStartedTimeMsgBuf, sizeof( matchStartedTimeMsgBuf ) ); + matchStartedTimeMsg.WriteLong( matchStartedTime ); + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_MATCH_STARTED_TIME, matchStartedTimeMsg, false ); + + fragLimitTimeout = 0; + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->wantSpectate = false; // Make sure everyone is in the game. + p->SetLeader( false ); // don't carry the flag from previous games + if ( gameLocal.gameType == GAME_TOURNEY && currentTourneyPlayer[ 0 ] != i && currentTourneyPlayer[ 1 ] != i ) { + p->ServerSpectate( true ); + idLib::Printf( "TOURNEY NewState GAMEON :> Player %d Benched \n", p->entityNumber ); + p->tourneyRank++; + } else { + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + int startingCount = ( gameLocal.gameType == GAME_LASTMAN ) ? fragLimit : 0; + playerState[ i ].fragCount = startingCount; + playerState[ i ].teamFragCount = startingCount; + if ( !static_cast(ent)->wantSpectate ) { + static_cast(ent)->ServerSpectate( false ); + idLib::Printf( "TOURNEY NewState :> Player %d On Deck \n", ent->entityNumber ); + if ( gameLocal.gameType == GAME_TOURNEY ) { + p->tourneyRank = 0; + } + } + } + if ( CanPlay( p ) ) { + p->lastManPresent = true; + } else { + p->lastManPresent = false; + } + } + NewState_GameOn_ServerAndClient(); + break; + } + case GAMEREVIEW: { + SetFlagMsg( false ); + nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change + // set all players spectating + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + static_cast(ent)->ServerSpectate( true ); + idLib::Printf( "TOURNEY NewState GAMEREVIEW :> Player %d Benched \n", ent->entityNumber ); + } + UpdateWinsLosses( player ); + SetFlagMsg( true ); + NewState_GameReview_ServerAndClient(); + break; + } + case SUDDENDEATH: { + PrintMessageEvent( MSG_SUDDENDEATH ); + PlayGlobalSound( -1, SND_SUDDENDEATH ); + break; + } + case COUNTDOWN: { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + warmupEndTime = gameLocal.serverTime + 1000*cvarSystem->GetCVarInteger( "g_countDown" ); + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteLong( warmupEndTime ); + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_WARMUPTIME, outMsg, false ); + + // Reset all the scores. + for( i = 0; i < gameLocal.numClients; i++ ) { + + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + int startingCount = ( gameLocal.gameType == GAME_LASTMAN ) ? fragLimit : 0; + playerState[ i ].fragCount = startingCount; + playerState[ i ].teamFragCount = startingCount; + playerState[ i ].deaths = 0; + + } + + NewState_Countdown_ServerAndClient(); + break; + } + case WARMUP: { + teamPoints[0] = 0; + teamPoints[1] = 0; + if ( IsGametypeFlagBased() ) { + // reset player scores to zero, only required for CTF + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + playerState[ i ].fragCount = 0; + } + } + NewState_Warmup_ServerAndClient(); + break; + } + default: + break; + } + + gameState = news; +} + +/* +================ +idMultiplayerGame::NewState_Warmup_ServerAndClient +Called on both servers and clients once when the game state changes to WARMUP. +================ +*/ +void idMultiplayerGame::NewState_Warmup_ServerAndClient() { + SetScoreboardActive( false ); +} + +/* +================ +idMultiplayerGame::NewState_Countdown_ServerAndClient +Called on both servers and clients once when the game state changes to COUNTDOWN. +================ +*/ +void idMultiplayerGame::NewState_Countdown_ServerAndClient() { + SetScoreboardActive( false ); +} + +/* +================ +idMultiplayerGame::NewState_GameOn_ServerAndClient +Called on both servers and clients once when the game state changes to GAMEON. +================ +*/ +void idMultiplayerGame::NewState_GameOn_ServerAndClient() { + startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + SetScoreboardActive( false ); +} + +/* +================ +idMultiplayerGame::NewState_GameReview_ServerAndClient +Called on both servers and clients once when the game state changes to GAMEREVIEW. +================ +*/ +void idMultiplayerGame::NewState_GameReview_ServerAndClient() { + SetScoreboardActive( true ); +} + +/* +================ +idMultiplayerGame::FillTourneySlots +NOTE: called each frame during warmup to keep the tourney slots filled +================ +*/ +void idMultiplayerGame::FillTourneySlots( ) { + int i, j, rankmax, rankmaxindex; + idEntity *ent; + idPlayer *p; + + idLib::Printf( "TOURNEY :> Executing FillTourneySlots \n" ); + + // fill up the slots based on tourney ranks + for ( i = 0; i < 2; i++ ) { + if ( currentTourneyPlayer[ i ] != -1 ) { + continue; + } + rankmax = -1; + rankmaxindex = -1; + for ( j = 0; j < gameLocal.numClients; j++ ) { + ent = gameLocal.entities[ j ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + if ( currentTourneyPlayer[ 0 ] == j || currentTourneyPlayer[ 1 ] == j ) { + continue; + } + + p = static_cast< idPlayer * >( ent ); + if ( p->wantSpectate ) { + idLib::Printf( "FillTourneySlots: Skipping Player %d ( Wants Spectate )\n", p->entityNumber ); + continue; + } + + + if ( p->tourneyRank >= rankmax ) { + // when ranks are equal, use time in game + if ( p->tourneyRank == rankmax ) { + assert( rankmaxindex >= 0 ); + if ( p->spawnedTime > static_cast< idPlayer * >( gameLocal.entities[ rankmaxindex ] )->spawnedTime ) { + continue; + } + } + rankmax = static_cast< idPlayer * >( ent )->tourneyRank; + rankmaxindex = j; + } + } + currentTourneyPlayer[ i ] = rankmaxindex; // may be -1 if we found nothing + } + + idLib::Printf( "TOURNEY :> Player 1: %d Player 2: %d \n", currentTourneyPlayer[ 0 ], currentTourneyPlayer[ 1 ] ); + + +} + +/* +================ +idMultiplayerGame::UpdateTourneyLine +we manipulate tourneyRank on player entities for internal ranking. it's easier to deal with. +but we need a real wait list to be synced down to clients for GUI +ignore current players, ignore wantSpectate +================ +*/ +void idMultiplayerGame::UpdateTourneyLine() { + assert( !common->IsClient() ); + if ( gameLocal.gameType != GAME_TOURNEY ) { + return; + } + + int globalmax = -1; + for ( int j = 1; j <= gameLocal.numClients; j++ ) { + int max = -1; + int imax = -1; + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) { + continue; + } + idPlayer * p = static_cast< idPlayer * >( gameLocal.entities[ i ] ); + if ( !p || p->wantSpectate ) { + continue; + } + if ( p->tourneyRank > max && ( globalmax == -1 || p->tourneyRank < globalmax ) ) { + max = p->tourneyRank; + imax = i; + } + } + if ( imax == -1 ) { + break; + } + + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( j ); + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[imax], GAME_RELIABLE_MESSAGE_TOURNEYLINE, outMsg ); + + globalmax = max; + } +} + +/* +================ +idMultiplayerGame::CycleTourneyPlayers +================ +*/ +void idMultiplayerGame::CycleTourneyPlayers( ) { + int i; + idEntity *ent; + idPlayer *player; + + currentTourneyPlayer[ 0 ] = -1; + currentTourneyPlayer[ 1 ] = -1; + // if any, winner from last round will play again + if ( lastWinner != -1 ) { + idEntity *ent = gameLocal.entities[ lastWinner ]; + if ( ent && ent->IsType( idPlayer::Type ) ) { + currentTourneyPlayer[ 0 ] = lastWinner; + } + } + FillTourneySlots( ); + // force selected players in/out of the game and update the ranks + for ( i = 0 ; i < gameLocal.numClients ; i++ ) { + if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) { + player = static_cast( gameLocal.entities[ i ] ); + player->ServerSpectate( false ); + idLib::Printf( "TOURNEY CycleTourneyPlayers:> Player %d On Deck \n", player->entityNumber ); + } else { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) ) { + player = static_cast( gameLocal.entities[ i ] ); + player->ServerSpectate( true ); + idLib::Printf( "TOURNEY CycleTourneyPlayers:> Player %d Benched \n", player->entityNumber ); + } + } + } + UpdateTourneyLine(); +} + +/* +================ +idMultiplayerGame::Warmup +================ +*/ +bool idMultiplayerGame::Warmup() { + return ( gameState == WARMUP ); +} + + +void idMultiplayerGame::GameHasBeenWon() { + + + // Only allow leaderboard submissions within public games. + const idMatchParameters & matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms(); + + if( matchParameters.matchFlags & MATCH_RANKED ) { + + // Upload all player's scores to the leaderboard. + for( int playerIdx = 0; playerIdx < gameLocal.numClients; playerIdx++ ) { + + leaderboardStats_t stats = { playerState[ playerIdx ].fragCount, playerState[ playerIdx ].wins, playerState[ playerIdx ].teamFragCount, playerState[ playerIdx].deaths }; + + LeaderboardLocal_Upload( gameLocal.lobbyUserIDs[ playerIdx ], gameLocal.gameType, stats ); + } + + // Flush all the collectively queued leaderboards all at once. ( Otherwise you get a busy signal on the second "flush" ) + session->LeaderboardFlush(); + } + + // Award Any players that have not died. An Achievement + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer * player = static_cast< idPlayer* >( gameLocal.entities[ i ] ); + if ( player == NULL ) { + continue; + } + + // Only validate players that were in the tourney. + if ( gameLocal.gameType == GAME_TOURNEY ) { + if ( i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) { + continue; + } + } + + // In LMS, players that join mid-game will not have ever died + if ( gameLocal.gameType == GAME_LASTMAN ) { + if ( playerState[i].fragCount == LASTMAN_NOLIVES ) { + continue; + } + } + + if ( playerState[ i ].deaths == 0 ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_COMPLETE_MATCH_WITHOUT_DYING ); + } + } +} + +/* +================ +idMultiplayerGame::Run +================ +*/ +void idMultiplayerGame::Run() { + int i, timeLeft; + idPlayer *player; + int gameReviewPause; + + assert( common->IsMultiplayer() ); + assert( !common->IsClient() ); + + pureReady = true; + + if ( gameState == INACTIVE ) { + NewState( WARMUP ); + } + + CheckRespawns(); + + if ( nextState != INACTIVE && gameLocal.serverTime > nextStateSwitch ) { + NewState( nextState ); + nextState = INACTIVE; + } + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + for ( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer * player = static_cast( gameLocal.entities[i] ); + if ( player != NULL ) { + playerState[i].ping = lobby.GetLobbyUserQoS( gameLocal.lobbyUserIDs[i] ); + } + } + + switch( gameState ) { + case GAMEREVIEW: { + if ( nextState == INACTIVE ) { + gameReviewPause = cvarSystem->GetCVarInteger( "g_gameReviewPause" ); + nextState = NEXTGAME; + nextStateSwitch = gameLocal.serverTime + 1000 * gameReviewPause; + } + break; + } + case NEXTGAME: { + if ( nextState == INACTIVE ) { + // make sure flags are returned + if ( IsGametypeFlagBased() ) { + idItemTeam * flag; + flag = GetTeamFlag( 0 ); + if ( flag ) { + flag->Return(); + } + flag = GetTeamFlag( 1 ); + if ( flag ) { + flag->Return(); + } + } + NewState( WARMUP ); + if ( gameLocal.gameType == GAME_TOURNEY ) { + CycleTourneyPlayers(); + } + // put everyone back in from endgame spectate + for ( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) ) { + if ( !static_cast< idPlayer * >( ent )->wantSpectate ) { + CheckRespawns( static_cast( ent ) ); + } + } + } + } + break; + } + case WARMUP: { + if ( EnoughClientsToPlay() ) { + NewState( COUNTDOWN ); + nextState = GAMEON; + nextStateSwitch = gameLocal.serverTime + 1000 * cvarSystem->GetCVarInteger( "g_countDown" ); + } + one = two = three = false; + break; + } + case COUNTDOWN: { + timeLeft = ( nextStateSwitch - gameLocal.serverTime ) / 1000 + 1; + if ( timeLeft == 3 && !three ) { + PlayGlobalSound( -1, SND_THREE ); + three = true; + } else if ( timeLeft == 2 && !two ) { + PlayGlobalSound( -1, SND_TWO ); + two = true; + } else if ( timeLeft == 1 && !one ) { + PlayGlobalSound( -1, SND_ONE ); + one = true; + } + break; + } + case GAMEON: { + if ( IsGametypeFlagBased() ) { /* CTF */ + // totally different logic branch for CTF + if ( PointLimitHit() ) { + int team = WinningTeam(); + assert( team != -1 ); + + NewState( GAMEREVIEW, NULL ); + PrintMessageEvent( MSG_POINTLIMIT, team ); + GameHasBeenWon(); + + } else if ( TimeLimitHit() ) { + int team = WinningTeam(); + if ( EnoughClientsToPlay() && team == -1 ) { + NewState( SUDDENDEATH ); + } else { + NewState( GAMEREVIEW, NULL ); + PrintMessageEvent( MSG_TIMELIMIT ); + GameHasBeenWon(); + } + } + break; + } + + player = FragLimitHit(); + if ( player ) { + // delay between detecting frag limit and ending game. let the death anims play + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.serverTime + FRAGLIMIT_DELAY; + } + if ( gameLocal.serverTime > fragLimitTimeout ) { + NewState( GAMEREVIEW, player ); + PrintMessageEvent( MSG_FRAGLIMIT, IsGametypeTeamBased() ? player->team : player->entityNumber ); + GameHasBeenWon(); + } + } else { + if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + SuddenRespawn(); + PrintMessageEvent( MSG_HOLYSHIT ); + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else if ( TimeLimitHit() ) { + player = FragLeader(); + if ( !player ) { + NewState( SUDDENDEATH ); + } else { + NewState( GAMEREVIEW, player ); + PrintMessageEvent( MSG_TIMELIMIT ); + GameHasBeenWon(); + } + } else { + BalanceTeams(); + } + } + break; + } + case SUDDENDEATH: { + if ( IsGametypeFlagBased() ) { /* CTF */ + int team = WinningTeam(); + if ( team != -1 ) { + // TODO : implement pointLimitTimeout + NewState( GAMEREVIEW, NULL ); + PrintMessageEvent( MSG_POINTLIMIT, team ); + GameHasBeenWon(); + } + break; + } + + player = FragLeader(); + if ( player ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.serverTime + FRAGLIMIT_DELAY; + } + if ( gameLocal.serverTime > fragLimitTimeout ) { + NewState( GAMEREVIEW, player ); + PrintMessageEvent( MSG_FRAGLIMIT, IsGametypeTeamBased() ? player->team : player->entityNumber ); + GameHasBeenWon(); + } + } else if ( fragLimitTimeout ) { + SuddenRespawn(); + PrintMessageEvent( MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + } +} + +/* +================ +idMultiplayerGame::Draw +================ +*/ +bool idMultiplayerGame::Draw( int clientNum ) { + idPlayer *player, *viewPlayer; + + // clear the render entities for any players that don't need + // icons and which might not be thinking because they weren't in + // the last snapshot. + for ( int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast( gameLocal.entities[ i ] ); + if ( player && !player->NeedsIcon() ) { + player->HidePlayerIcons(); + } + } + + player = viewPlayer = static_cast( gameLocal.entities[ clientNum ] ); + + if ( player == NULL ) { + return false; + } + + if ( player->spectating ) { + viewPlayer = static_cast( gameLocal.entities[ player->spectator ] ); + if ( viewPlayer == NULL ) { + return false; + } + + // if the player you are viewing is holding a flag, hide it. + idEntity* flag = GetTeamFlag( viewPlayer->team ? 0 : 1 ); + if( flag ) { + if( viewPlayer->carryingFlag ) { + flag->Hide(); + } else { + flag->Show(); + } + } + } + + UpdatePlayerRanks(); + UpdateHud( viewPlayer, player->hudManager ); + // use the hud of the local player + viewPlayer->playerView.RenderPlayerView( player->hudManager ); + + idStr spectatetext[ 2 ]; + GetSpectateText( player, spectatetext, true ); + + if ( scoreboardManager != NULL ) { + scoreboardManager->UpdateSpectating( spectatetext[0].c_str(), spectatetext[1].c_str() ); + } + + DrawChat( player ); + DrawScoreBoard( player ); + + return true; +} + +/* +================ +idMultiplayerGame::GetSpectateText +================ +*/ +void idMultiplayerGame::GetSpectateText( idPlayer * player, idStr spectatetext[ 2 ], bool scoreboard ) { + if ( !player->spectating ) { + return; + } + if ( player->wantSpectate && !scoreboard ) { + if ( gameLocal.gameType == GAME_LASTMAN && gameState == GAMEON ) { + // If we're in GAMEON in lastman, you can't actully spawn, or you'll have an unfair + // advantage with more lives than everyone else. + spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" ); + spectatetext[ 0 ] += idLocalization::GetString( "#str_07003" ); + } else { + + if ( gameLocal.gameType == GAME_TOURNEY && + ( currentTourneyPlayer[0] != -1 && currentTourneyPlayer[1] != -1 ) && + ( currentTourneyPlayer[0] != player->entityNumber && currentTourneyPlayer[1] != player->entityNumber ) ) { + + spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" ); + switch ( player->tourneyLine ) { + case 0: + spectatetext[ 0 ] += idLocalization::GetString( "#str_07003" ); + break; + case 1: + spectatetext[ 0 ] += idLocalization::GetString( "#str_07004" ); + break; + case 2: + spectatetext[ 0 ] += idLocalization::GetString( "#str_07005" ); + break; + default: + spectatetext[ 0 ] += va( idLocalization::GetString( "#str_07006" ), player->tourneyLine ); + break; + } + + } else { + spectatetext[ 0 ] = idLocalization::GetString( "#str_respawn_message" ); + } + } + } else { + if ( gameLocal.gameType == GAME_TOURNEY ) { + spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" ); + switch ( player->tourneyLine ) { + case 0: + spectatetext[ 0 ] += idLocalization::GetString( "#str_07003" ); + break; + case 1: + spectatetext[ 0 ] += idLocalization::GetString( "#str_07004" ); + break; + case 2: + spectatetext[ 0 ] += idLocalization::GetString( "#str_07005" ); + break; + default: + spectatetext[ 0 ] += va( idLocalization::GetString( "#str_07006" ), player->tourneyLine ); + break; + } + } else if ( gameLocal.gameType == GAME_LASTMAN ) { + spectatetext[ 0 ] = idLocalization::GetString( "#str_07007" ); + } else { + spectatetext[ 0 ] = idLocalization::GetString( "#str_04246" ); + } + } + if ( player->spectator != player->entityNumber ) { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + spectatetext[ 1 ] = va( idLocalization::GetString( "#str_07008" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[ player->spectator ] ) ); + } +} + +/* +================ +idMultiplayerGame::UpdateHud +================ +*/ +void idMultiplayerGame::UpdateHud( idPlayer *player, idMenuHandler_HUD * hudManager ) { + + if ( hudManager && hudManager->GetHud() ) { + + idMenuScreen_HUD * hud = hudManager->GetHud(); + + if ( Warmup() ) { + hud->UpdateMessage( true, "#str_04251" ); + + if ( IsGametypeTeamBased() ) { + hud->SetTeamScore( 0, 0 ); + hud->SetTeamScore( 1, 0 ); + } + + } else if ( gameState == SUDDENDEATH ) { + hud->UpdateMessage( true, "#str_04252" ); + } else { + hud->UpdateGameTime( GameTime() ); + } + + if ( IsGametypeTeamBased() || IsGametypeFlagBased() ) { + hud->ToggleMPInfo( true, true, IsGametypeFlagBased() ); + } else { + hud->ToggleMPInfo( true, false ); + } + + if ( gameState == GAMEON || gameState == COUNTDOWN || gameState == WARMUP ) { + if ( IsGametypeTeamBased() && !IsGametypeFlagBased() ) { + for ( int i = 0; i < gameLocal.numClients; ++i ) { + idEntity * ent = gameLocal.entities[ i ]; + if ( ent == NULL || !ent->IsType( idPlayer::Type ) ) { + continue; + } + + idPlayer * player = static_cast< idPlayer * >( ent ); + hud->SetTeamScore( player->team, playerState[ player->entityNumber ].teamFragCount ); + } + } + } + + if ( IsGametypeFlagBased() || IsGametypeTeamBased() ) { + hud->SetTeam( player->team ); + } else { + hud->SetTeam( -1 ); + } + + } +} + +/* +================ +idMultiplayerGame::SetScoreboardActive +================ +*/ +void idMultiplayerGame::SetScoreboardActive( bool active ) { + if( scoreboardManager != NULL ) { + if( active ) { + if ( IsGametypeTeamBased() || IsGametypeFlagBased() ) { + scoreboardManager->SetActivationScreen( SCOREBOARD_AREA_TEAM, MENU_TRANSITION_SIMPLE ); + } else { + scoreboardManager->SetActivationScreen( SCOREBOARD_AREA_DEFAULT, MENU_TRANSITION_SIMPLE ); + } + + scoreboardManager->ActivateMenu( true ); + } else { + + scoreboardManager->ActivateMenu( false ); + } + } +} + +/* +================ +idMultiplayerGame::DrawScoreBoard +================ +*/ +void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) { + if ( scoreboardManager && scoreboardManager->IsActive() == true ) { + UpdateScoreboard( scoreboardManager, player ); + } +} + +/* +=============== +idMultiplayerGame::ClearChatData +=============== +*/ +void idMultiplayerGame::ClearChatData() { + chatHistoryIndex = 0; + chatHistorySize = 0; + chatDataUpdated = true; +} + +/* +=============== +idMultiplayerGame::AddChatLine +=============== +*/ +void idMultiplayerGame::AddChatLine( const char *fmt, ... ) { + idStr temp; + va_list argptr; + + va_start( argptr, fmt ); + vsprintf( temp, fmt, argptr ); + va_end( argptr ); + + gameLocal.Printf( "%s\n", temp.c_str() ); + + chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].line = temp; + chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].fade = 6; + + chatHistoryIndex++; + if ( chatHistorySize < NUM_CHAT_NOTIFY ) { + chatHistorySize++; + } + chatDataUpdated = true; + lastChatLineTime = Sys_Milliseconds(); +} + +/* +=============== +idMultiplayerGame::DrawChat +=============== +*/ +void idMultiplayerGame::DrawChat( idPlayer * player ) { + int i, j; + + if ( player ) { + if ( Sys_Milliseconds() - lastChatLineTime > CHAT_FADE_TIME ) { + if ( chatHistorySize > 0 ) { + for ( i = chatHistoryIndex - chatHistorySize; i < chatHistoryIndex; i++ ) { + chatHistory[ i % NUM_CHAT_NOTIFY ].fade--; + if ( chatHistory[ i % NUM_CHAT_NOTIFY ].fade < 0 ) { + chatHistorySize--; // this assumes the removals are always at the beginning + } + } + chatDataUpdated = true; + } + lastChatLineTime = Sys_Milliseconds(); + } + if ( chatDataUpdated ) { + j = 0; + i = chatHistoryIndex - chatHistorySize; + while ( i < chatHistoryIndex ) { + player->AddChatMessage( j, Min( 4, (int)chatHistory[ i % NUM_CHAT_NOTIFY ].fade ), chatHistory[ i % NUM_CHAT_NOTIFY ].line ); + j++; i++; + } + while ( j < NUM_CHAT_NOTIFY ) { + player->ClearChatMessage( j ); + j++; + } + chatDataUpdated = false; + } + + } +} + +//D3XP: Adding one to frag count to allow for the negative flag in numbers greater than 255 +const int ASYNC_PLAYER_FRAG_BITS = -(idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS )+1); // player can have negative frags +const int ASYNC_PLAYER_WINS_BITS = idMath::BitsForInteger( MP_PLAYER_MAXWINS ); +const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( MP_PLAYER_MAXPING ); + +/* +================ +idMultiplayerGame::WriteToSnapshot +================ +*/ +void idMultiplayerGame::WriteToSnapshot( idBitMsg &msg ) const { + int i; + int value; + + // This is a hack - I need a place to read the lobby ids before the player entities are + // read (SpawnPlayer requires a valid lobby id for the player). + for ( int i = 0; i < gameLocal.lobbyUserIDs.Num(); ++i ) { + gameLocal.lobbyUserIDs[i].WriteToMsg( msg ); + } + + msg.WriteByte( gameState ); + msg.WriteLong( nextStateSwitch ); + msg.WriteShort( currentTourneyPlayer[ 0 ] ); + msg.WriteShort( currentTourneyPlayer[ 1 ] ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + // clamp all values to min/max possible value that we can send over + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins ); + msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS ); + value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping ); + msg.WriteBits( value, ASYNC_PLAYER_PING_BITS ); + } + + msg.WriteShort( teamPoints[0] ); + msg.WriteShort( teamPoints[1] ); + msg.WriteShort( player_red_flag ); + msg.WriteShort( player_blue_flag ); +} + +/* +================ +idMultiplayerGame::ReadFromSnapshot +================ +*/ +void idMultiplayerGame::ReadFromSnapshot( const idBitMsg &msg ) { + int i; + gameState_t newState; + + // This is a hack - I need a place to read the lobby ids before the player entities are + // read (SpawnPlayer requires a valid lobby id for the player). + for ( int i = 0; i < gameLocal.lobbyUserIDs.Num(); ++i ) { + gameLocal.lobbyUserIDs[i].ReadFromMsg( msg ); + } + + newState = (idMultiplayerGame::gameState_t)msg.ReadByte(); + nextStateSwitch = msg.ReadLong(); + if ( newState != gameState && newState < STATE_COUNT ) { + gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ newState ] ); + gameState = newState; + + switch( gameState ) { + case GAMEON: { + NewState_GameOn_ServerAndClient(); + break; + } + case GAMEREVIEW: { + NewState_GameReview_ServerAndClient(); + break; + } + case WARMUP: { + NewState_Warmup_ServerAndClient(); + break; + } + case COUNTDOWN: { + NewState_Countdown_ServerAndClient(); + break; + } + } + } + currentTourneyPlayer[ 0 ] = msg.ReadShort(); + currentTourneyPlayer[ 1 ] = msg.ReadShort(); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + playerState[i].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[i].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[i].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS ); + playerState[i].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS ); + } + + teamPoints[0] = msg.ReadShort(); + teamPoints[1] = msg.ReadShort(); + + player_red_flag = msg.ReadShort(); + player_blue_flag = msg.ReadShort(); +} + +/* +================ +idMultiplayerGame::PlayGlobalSound +================ +*/ +void idMultiplayerGame::PlayGlobalSound( int toPlayerNum, snd_evt_t evt, const char *shader ) { + + if ( toPlayerNum < 0 || toPlayerNum == gameLocal.GetLocalClientNum() ) { + if ( shader ) { + if ( gameSoundWorld ) { + gameSoundWorld->PlayShaderDirectly( shader ); + } + } else { + if ( gameSoundWorld ) { + gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ evt ] ); + } + } + } + + if ( !common->IsClient() && toPlayerNum != gameLocal.GetLocalClientNum() ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + int type = 0; + + if ( shader ) { + const idSoundShader * shaderDecl = declManager->FindSound( shader ); + if ( !shaderDecl ) { + return; + } + outMsg.WriteLong( gameLocal.ServerRemapDecl( -1, DECL_SOUND, shaderDecl->Index() ) ); + type = GAME_RELIABLE_MESSAGE_SOUND_INDEX; + } else { + outMsg.WriteByte( evt ); + type = GAME_RELIABLE_MESSAGE_SOUND_EVENT; + } + if ( toPlayerNum >= 0 ) { + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[toPlayerNum], type, outMsg ); + } else { + session->GetActingGameStateLobbyBase().SendReliable( type, outMsg, false ); + } + } +} + +/* +================ +idMultiplayerGame::PlayTeamSound +================ +*/ +void idMultiplayerGame::PlayTeamSound( int toTeam, snd_evt_t evt, const char *shader ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer * player = static_cast(ent); + if ( player->team != toTeam ) + continue; + PlayGlobalSound( i, evt, shader ); + } +} + +/* +================ +idMultiplayerGame::PrintMessageEvent +================ +*/ +void idMultiplayerGame::PrintMessageEvent( msg_evt_t evt, int parm1, int parm2 ) { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + + switch ( evt ) { + case MSG_LEFTGAME: + assert( parm1 >= 0 ); + AddChatLine( idLocalization::GetString( "#str_11604" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) ); + break; + case MSG_SUICIDE: + assert( parm1 >= 0 ); + AddChatLine( idLocalization::GetString( "#str_04293" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) ); + break; + case MSG_KILLED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( idLocalization::GetString( "#str_04292" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); + break; + case MSG_KILLEDTEAM: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( idLocalization::GetString( "#str_04291" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); + break; + case MSG_TELEFRAGGED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( idLocalization::GetString( "#str_04290" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); + break; + case MSG_DIED: + assert( parm1 >= 0 ); + AddChatLine( idLocalization::GetString( "#str_04289" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) ); + break; + case MSG_SUDDENDEATH: + AddChatLine( idLocalization::GetString( "#str_04287" ) ); + break; + case MSG_JOINEDSPEC: + AddChatLine( idLocalization::GetString( "#str_04285" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) ); + break; + case MSG_TIMELIMIT: + AddChatLine( idLocalization::GetString( "#str_04284" ) ); + break; + case MSG_FRAGLIMIT: + if ( gameLocal.gameType == GAME_LASTMAN ) { + AddChatLine( idLocalization::GetString( "#str_04283" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) ); + } else if ( IsGametypeTeamBased() ) { /* CTF */ + AddChatLine( idLocalization::GetString( "#str_04282" ), idLocalization::GetString( teamNames[ parm1 ] ) ); + } else { + AddChatLine( idLocalization::GetString( "#str_04281" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ) ); + } + break; + case MSG_JOINTEAM: + AddChatLine( idLocalization::GetString( "#str_04280" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm1] ), parm2 ? idLocalization::GetString( "#str_02500" ) : idLocalization::GetString( "#str_02499" ) ); + break; + case MSG_HOLYSHIT: + AddChatLine( idLocalization::GetString( "#str_06732" ) ); + break; + case MSG_POINTLIMIT: + AddChatLine( idLocalization::GetString( "#str_11100" ), parm1 ? idLocalization::GetString( "#str_11110" ) : idLocalization::GetString( "#str_11111" ) ); + break; + + case MSG_FLAGTAKEN : + if ( gameLocal.GetLocalPlayer() == NULL ) + break; + + if ( parm2 < 0 || parm2 >= MAX_CLIENTS ) + break; + + if ( gameLocal.GetLocalPlayer()->team != parm1 ) { + AddChatLine( idLocalization::GetString( "#str_11101" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // your team + } else { + AddChatLine( idLocalization::GetString( "#str_11102" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // enemy + } + break; + + case MSG_FLAGDROP : + if ( gameLocal.GetLocalPlayer() == NULL ) + break; + + if ( gameLocal.GetLocalPlayer()->team != parm1 ) { + AddChatLine( idLocalization::GetString( "#str_11103" ) ); // your team + } else { + AddChatLine( idLocalization::GetString( "#str_11104" ) ); // enemy + } + break; + + case MSG_FLAGRETURN : + if ( gameLocal.GetLocalPlayer() == NULL ) + break; + + if ( parm2 >= 0 && parm2 < MAX_CLIENTS ) { + if ( gameLocal.GetLocalPlayer()->team != parm1 ) { + AddChatLine( idLocalization::GetString( "#str_11120" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // your team + } else { + AddChatLine( idLocalization::GetString( "#str_11121" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // enemy + } + } else { + AddChatLine( idLocalization::GetString( "#str_11105" ), parm1 ? idLocalization::GetString( "#str_11110" ) : idLocalization::GetString( "#str_11111" ) ); + } + break; + + case MSG_FLAGCAPTURE : + if ( gameLocal.GetLocalPlayer() == NULL ) + break; + + if ( parm2 < 0 || parm2 >= MAX_CLIENTS ) + break; + + if ( gameLocal.GetLocalPlayer()->team != parm1 ) { + AddChatLine( idLocalization::GetString( "#str_11122" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // your team + } else { + AddChatLine( idLocalization::GetString( "#str_11123" ), lobby.GetLobbyUserName( gameLocal.lobbyUserIDs[parm2] ) ); // enemy + } + +// AddChatLine( idLocalization::GetString( "#str_11106" ), parm1 ? idLocalization::GetString( "#str_11110" ) : idLocalization::GetString( "#str_11111" ) ); + break; + + case MSG_SCOREUPDATE: + AddChatLine( idLocalization::GetString( "#str_11107" ), parm1, parm2 ); + break; + default: + gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt ); + return; + } + if ( !common->IsClient() ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( evt ); + outMsg.WriteByte( parm1 ); + outMsg.WriteByte( parm2 ); + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_DB, outMsg, false ); + } +} + +/* +================ +idMultiplayerGame::SuddenRespawns +solely for LMN if an end game ( fragLimitTimeout ) was entered and aborted before expiration +LMN players which still have lives left need to be respawned without being marked lastManOver +================ +*/ +void idMultiplayerGame::SuddenRespawn() { + int i; + + if ( gameLocal.gameType != GAME_LASTMAN ) { + return; + } + + for ( i = 0; i < gameLocal.numClients; i++ ) { + if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) { + continue; + } + if ( !CanPlay( static_cast< idPlayer * >( gameLocal.entities[ i ] ) ) ) { + continue; + } + if ( static_cast< idPlayer * >( gameLocal.entities[ i ] )->lastManOver ) { + continue; + } + static_cast< idPlayer * >( gameLocal.entities[ i ] )->lastManPlayAgain = true; + } +} + +/* +================ +idMultiplayerGame::CheckSpawns +================ +*/ +void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) { + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer *p = static_cast(ent); + // once we hit sudden death, nobody respawns till game has ended + if ( WantRespawn( p ) || p == spectator ) { + if ( gameState == SUDDENDEATH && gameLocal.gameType != GAME_LASTMAN ) { + // respawn rules while sudden death are different + // sudden death may trigger while a player is dead, so there are still cases where we need to respawn + // don't do any respawns while we are in end game delay though + if ( !fragLimitTimeout ) { + if ( IsGametypeTeamBased() || p->IsLeader() ) { /* CTF */ +#ifdef _DEBUG + if ( gameLocal.gameType == GAME_TOURNEY ) { + assert( p->entityNumber == currentTourneyPlayer[ 0 ] || p->entityNumber == currentTourneyPlayer[ 1 ] ); + } +#endif + p->ServerSpectate( false ); + } else if ( !p->IsLeader() ) { + // sudden death is rolling, this player is not a leader, have him spectate + p->ServerSpectate( true ); + CheckAbortGame(); + } + } + } else { + if ( gameLocal.gameType == GAME_DM || // CTF : 3wave sboily, was DM really included before? + IsGametypeTeamBased() ) + { + if ( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON ) { + p->ServerSpectate( false ); + } + } else if ( gameLocal.gameType == GAME_TOURNEY ) { + if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) { + if ( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON ) { + p->ServerSpectate( false ); + idLib::Printf( "TOURNEY CheckRespawns :> Player %d On Deck \n", p->entityNumber ); + } + } else if ( gameState == WARMUP ) { + // make sure empty tourney slots get filled first + FillTourneySlots( ); + if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) { + p->ServerSpectate( false ); + idLib::Printf( "TOURNEY CheckRespawns WARMUP :> Player %d On Deck \n", p->entityNumber ); + } + } + } else if ( gameLocal.gameType == GAME_LASTMAN ) { + if ( gameState == WARMUP || gameState == COUNTDOWN ) { + // Player has spawned in game, give him lives. + playerState[ i ].fragCount = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + p->ServerSpectate( false ); + } else if ( gameState == GAMEON || gameState == SUDDENDEATH ) { + if ( gameState == GAMEON && playerState[ i ].fragCount > 0 && p->lastManPresent ) { + assert( !p->lastManOver ); + p->ServerSpectate( false ); + } else if ( p->lastManPlayAgain && p->lastManPresent ) { + assert( gameState == SUDDENDEATH ); + p->ServerSpectate( false ); + } else { + // if a fragLimitTimeout was engaged, do NOT mark lastManOver as that could mean + // everyone ends up spectator and game is stalled with no end + // if the frag limit delay is engaged and cancels out before expiring, LMN players are + // respawned to play the tie again ( through SuddenRespawn and lastManPlayAgain ) + if ( !fragLimitTimeout && !p->lastManOver ) { + common->DPrintf( "client %d has lost all last man lives\n", i ); + // end of the game for this guy, send him to spectators + p->lastManOver = true; + // clients don't have access to lastManOver + // so set the fragCount to something silly ( used in scoreboard and player ranking ) + playerState[ i ].fragCount = LASTMAN_NOLIVES; + p->ServerSpectate( true ); + + //Check for a situation where the last two player dies at the same time and don't + //try to respawn manually...This was causing all players to go into spectate mode + //and the server got stuck + { + int j; + for ( j = 0; j < gameLocal.numClients; j++ ) { + if ( !gameLocal.entities[ j ] ) { + continue; + } + if ( !CanPlay( static_cast< idPlayer * >( gameLocal.entities[ j ] ) ) ) { + continue; + } + if ( !static_cast< idPlayer * >( gameLocal.entities[ j ] )->lastManOver ) { + break; + } + } + if( j == gameLocal.numClients) { + //Everyone is dead so don't allow this player to spectate + //so the match will end + p->ServerSpectate( false ); + } + } + } + } + } + } + } + } else if ( p->wantSpectate && !p->spectating ) { + playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets + p->ServerSpectate( true ); + idLib::Printf( "TOURNEY CheckRespawns :> Player %d Wants Spectate \n", p->entityNumber ); + UpdateTourneyLine(); + CheckAbortGame(); + } + } +} + +/* +================ +idMultiplayerGame::DropWeapon +================ +*/ +void idMultiplayerGame::DropWeapon( int clientNum ) { + assert( !common->IsClient() ); + idEntity *ent = gameLocal.entities[ clientNum ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + return; + } + static_cast< idPlayer* >( ent )->DropWeapon( false ); +} + +/* +================ +idMultiplayerGame::DropWeapon_f +================ +*/ +void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) { + if ( !common->IsMultiplayer() ) { + common->Printf( "clientDropWeapon: only valid in multiplayer\n" ); + return; + } + idBitMsg outMsg; + session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_DROPWEAPON, outMsg ); +} + +/* +================ +idMultiplayerGame::MessageMode_f +================ +*/ +void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) { + if ( !common->IsMultiplayer() ) { + return; + } + gameLocal.mpGame.MessageMode( args ); +} + +/* +================ +idMultiplayerGame::MessageMode +================ +*/ +void idMultiplayerGame::MessageMode( const idCmdArgs &args ) { + idEntity *ent = gameLocal.entities[ gameLocal.GetLocalClientNum() ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + return; + } + idPlayer * player = static_cast< idPlayer* >( ent ); + if ( player && !player->spectating ) { + if ( args.Argc() != 2 ) { + player->isChatting = 1; + } else { + player->isChatting = 2; + } + } +} + +/* +================ +idMultiplayerGame::DisconnectClient +================ +*/ +void idMultiplayerGame::DisconnectClient( int clientNum ) { + if ( lastWinner == clientNum ) { + lastWinner = -1; + } + + // Show that the user has left the game. + PrintMessageEvent( MSG_LEFTGAME, clientNum, -1 ); + + UpdatePlayerRanks(); + CheckAbortGame(); +} + +/* +================ +idMultiplayerGame::CheckAbortGame +================ +*/ +void idMultiplayerGame::CheckAbortGame() { + int i; + if ( gameLocal.gameType == GAME_TOURNEY && gameState == WARMUP ) { + // if a tourney player joined spectators, let someone else have his spot + for ( i = 0; i < 2; i++ ) { + if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating ) { + currentTourneyPlayer[ i ] = -1; + } + } + } + // only checks for aborts -> game review below + if ( gameState != COUNTDOWN && gameState != GAMEON && gameState != SUDDENDEATH ) { + return; + } + switch ( gameLocal.gameType ) { + case GAME_TOURNEY: + for ( i = 0; i < 2; i++ ) { + + idPlayer * player = static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] ); + + if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || player->spectating ) { + NewState( GAMEREVIEW ); + return; + } + } + break; + default: + if ( !EnoughClientsToPlay() ) { + NewState( GAMEREVIEW ); + } + break; + } +} + +/* +================ +idMultiplayerGame::MapRestart +================ +*/ +void idMultiplayerGame::MapRestart() { + + assert( !common->IsClient() ); + if ( gameState != WARMUP ) { + NewState( WARMUP ); + nextState = INACTIVE; + nextStateSwitch = 0; + } + + teamPoints[0] = 0; + teamPoints[1] = 0; + + BalanceTeams(); +} + +/* +================ +idMultiplayerGame::BalanceTeams +================ +*/ +void idMultiplayerGame::BalanceTeams() { + if ( !IsGametypeTeamBased() ) { + return; + } + int teamCount[ 2 ] = { 0, 0 }; + NumActualClients( false, teamCount ); + int outOfBalance = abs( teamCount[0] - teamCount[1] ); + if ( outOfBalance <= 1 ) { + return; + } + + // Teams are out of balance + // Move N players from the large team to the small team + int numPlayersToMove = outOfBalance / 2; + int smallTeam = MinIndex( teamCount[0], teamCount[1] ); + int largeTeam = 1 - smallTeam; + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + + // First move players from the large team that match a party token on the small team + for ( int a = 0; a < gameLocal.numClients; a++ ) { + idPlayer * playerA = gameLocal.GetClientByNum( a ); + if ( playerA->team == largeTeam && CanPlay( playerA ) ) { + for ( int b = 0; b < gameLocal.numClients; b++ ) { + if ( a == b ) { + continue; + } + idPlayer * playerB = gameLocal.GetClientByNum( b ); + if ( playerB->team == smallTeam && CanPlay( playerB ) ) { + if ( lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ a ] ) == lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ b ] ) ) { + SwitchToTeam( a, largeTeam, smallTeam ); + numPlayersToMove--; + if ( numPlayersToMove == 0 ) { + return; + } + break; + } + } + } + } + } + + // Then move players from the large team that DON'T match party tokens on the large team + for ( int a = 0; a < gameLocal.numClients; a++ ) { + idPlayer * playerA = gameLocal.GetClientByNum( a ); + if ( playerA->team == largeTeam && CanPlay( playerA ) ) { + bool match = false; + for ( int b = 0; b < gameLocal.numClients; b++ ) { + if ( a == b ) { + continue; + } + idPlayer * playerB = gameLocal.GetClientByNum( b ); + if ( playerB->team == largeTeam && CanPlay( playerB ) ) { + if ( lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ a ] ) == lobby.GetLobbyUserPartyToken( gameLocal.lobbyUserIDs[ b ] ) ) { + match = true; + break; + } + } + } + if ( !match ) { + SwitchToTeam( a, largeTeam, smallTeam ); + numPlayersToMove--; + if ( numPlayersToMove == 0 ) { + return; + } + } + } + } + + // Then move any players from the large team to the small team + for ( int a = 0; a < gameLocal.numClients; a++ ) { + idPlayer * playerA = gameLocal.GetClientByNum( a ); + if ( playerA->team == largeTeam && CanPlay( playerA ) ) { + SwitchToTeam( a, largeTeam, smallTeam ); + numPlayersToMove--; + if ( numPlayersToMove == 0 ) { + return; + } + } + } +} + +/* +================ +idMultiplayerGame::SwitchToTeam +================ +*/ +void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) { + idPlayer * p = static_cast( gameLocal.entities[ clientNum ] ); + p->team = newteam; + session->GetActingGameStateLobbyBase().SetLobbyUserTeam( gameLocal.lobbyUserIDs[ clientNum ], newteam ); + session->SetVoiceGroupsToTeams(); + + assert( IsGametypeTeamBased() ); /* CTF */ + assert( oldteam != newteam ); + assert( !common->IsClient() ); + + if ( !common->IsClient() && newteam >= 0 ) { + PrintMessageEvent( MSG_JOINTEAM, clientNum, newteam ); + } + // assign the right teamFragCount + int i; + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( i == clientNum ) { + continue; + } + idEntity * ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >(ent)->team == newteam ) { + playerState[ clientNum ].teamFragCount = playerState[ i ].teamFragCount; + break; + } + } + if ( i == gameLocal.numClients ) { + // alone on this team + playerState[ clientNum ].teamFragCount = 0; + } + if ( ( gameState == GAMEON || ( IsGametypeFlagBased() && gameState == SUDDENDEATH ) ) && oldteam != -1 ) { + // when changing teams during game, kill and respawn + if ( p->IsInTeleport() ) { + p->ServerSendEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false ); + p->SetPrivateCameraView( NULL ); + } + p->Kill( true, true ); + if ( IsGametypeFlagBased() ) + p->DropFlag(); + CheckAbortGame(); + } else if ( IsGametypeFlagBased() && oldteam != -1 ) { + p->DropFlag(); + } +} + +/* +================ +idMultiplayerGame::ProcessChatMessage +================ +*/ +void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + const char *prefix = NULL; + int send_to; // 0 - all, 1 - specs, 2 - team + int i; + idEntity *ent; + idPlayer *pfrom; + idStr prefixed_name; + + assert( !common->IsClient() ); + + if ( clientNum >= 0 ) { + pfrom = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + if ( !( pfrom && pfrom->IsType( idPlayer::Type ) ) ) { + return; + } + + if ( pfrom->spectating ) { + prefix = "spectating"; + if ( team || ( !g_spectatorChat.GetBool() && ( gameState == GAMEON || gameState == SUDDENDEATH ) ) ) { + // to specs + send_to = 1; + } else { + // to all + send_to = 0; + } + } else if ( team ) { + prefix = "team"; + // to team + send_to = 2; + } else { + // to all + send_to = 0; + } + } else { + pfrom = NULL; + send_to = 0; + } + // put the message together + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + if ( prefix ) { + prefixed_name = va( "(%s) %s", prefix, name ); + } else { + prefixed_name = name; + } + outMsg.WriteString( prefixed_name ); + outMsg.WriteString( text, -1, false ); + if ( !send_to ) { + AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text ); + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_CHAT, outMsg, false ); + if ( sound ) { + PlayGlobalSound( -1, SND_COUNT, sound ); + } + } else { + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::Type ) ) { + continue; + } + idPlayer * pent = static_cast< idPlayer * >( ent ); + if ( send_to == 1 && pent->spectating ) { + if ( sound ) { + PlayGlobalSound( i, SND_COUNT, sound ); + } + if ( i == gameLocal.GetLocalClientNum() ) { + AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text ); + } else { + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[i], GAME_RELIABLE_MESSAGE_CHAT, outMsg ); + } + } else if ( send_to == 2 && pent->team == pfrom->team ) { + if ( sound ) { + PlayGlobalSound( i, SND_COUNT, sound ); + } + if ( i == gameLocal.GetLocalClientNum() ) { + AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text ); + } else { + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[i], GAME_RELIABLE_MESSAGE_CHAT, outMsg ); + } + } + } + } +} + +/* +================ +idMultiplayerGame::Precache +================ +*/ +void idMultiplayerGame::Precache() { + if ( !common->IsMultiplayer() ) { + return; + } + + // player + declManager->FindType( DECL_ENTITYDEF, gameLocal.GetMPPlayerDefName(), false ); + + // skins + for ( int i = 0; i < numSkins; i++ ) { + idStr baseSkinName = skinNames[ i ]; + declManager->FindSkin( baseSkinName, false ); + declManager->FindSkin( baseSkinName + "_berserk", false ); + declManager->FindSkin( baseSkinName + "_invuln", false ); + } + + // MP game sounds + for ( int i = 0; i < SND_COUNT; i++ ) { + declManager->FindSound( GlobalSoundStrings[ i ] ); + } +} + +/* +================ +idMultiplayerGame::ToggleSpectate +================ +*/ +void idMultiplayerGame::ToggleSpectate() { + assert( common->IsClient() || gameLocal.GetLocalClientNum() == 0 ); + + idPlayer * player = (idPlayer *)gameLocal.entities[gameLocal.GetLocalClientNum()]; + bool spectating = player->spectating; + // only allow toggling to spectate if spectators are enabled. + if ( !spectating && !gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + gameLocal.mpGame.AddChatLine( idLocalization::GetString( "#str_06747" ) ); + return; + } + + byte msgBuf[ 256 ]; + idBitMsg outMsg; + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteBool( !spectating ); + outMsg.WriteByteAlign(); + session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_SPECTATE, outMsg ); +} + +/* +================ +idMultiplayerGame::CanPlay +================ +*/ +bool idMultiplayerGame::CanPlay( idPlayer *p ) { + return !p->wantSpectate; +} + +/* +================ +idMultiplayerGame::WantRespawn +================ +*/ +bool idMultiplayerGame::WantRespawn( idPlayer *p ) { + return p->forceRespawn && !p->wantSpectate; +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, false ); +} + +/* +================ +idMultiplayerGame::VoiceChatTeam +================ +*/ +void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, true ); +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) { + idBitMsg outMsg; + byte msgBuf[128]; + const char *voc; + const idDict *spawnArgs; + const idKeyValue *keyval; + int index; + + if ( !common->IsMultiplayer() ) { + common->Printf( "clientVoiceChat: only valid in multiplayer\n" ); + return; + } + if ( args.Argc() != 2 ) { + common->Printf( "clientVoiceChat: bad args\n" ); + return; + } + // throttle + if ( gameLocal.realClientTime < voiceChatThrottle ) { + return; + } + if ( gameLocal.GetLocalPlayer() == NULL ) { + return; + } + + voc = args.Argv( 1 ); + + spawnArgs = &gameLocal.GetLocalPlayer()->spawnArgs; + + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + index = 0; + while ( keyval ) { + if ( !keyval->GetValue().Icmp( voc ) ) { + break; + } + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index++; + } + if ( !keyval ) { + common->Printf( "Voice command not found: %s\n", voc ); + return; + } + voiceChatThrottle = gameLocal.realClientTime + 1000; + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteLong( index ); + outMsg.WriteBits( team ? 1 : 0, 1 ); + session->GetActingGameStateLobbyBase().SendReliableToHost( GAME_RELIABLE_MESSAGE_VCHAT, outMsg ); +} + +/* +================ +idMultiplayerGame::ProcessVoiceChat +================ +*/ +void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) { + const idDict *spawnArgs; + const idKeyValue *keyval; + idStr name; + idStr snd_key; + idStr text_key; + idPlayer *p; + + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + if ( !( p && p->IsType( idPlayer::Type ) ) ) { + return; + } + + if ( p->spectating ) { + return; + } + + // lookup the sound def + spawnArgs = &p->spawnArgs; + + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + while ( index > 0 && keyval ) { + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index--; + } + if ( !keyval ) { + common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index ); + return; + } + snd_key = keyval->GetKey(); + name = session->GetActingGameStateLobbyBase().GetLobbyUserName( gameLocal.lobbyUserIDs[ clientNum ] ); + sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() ); + if ( team || gameState == COUNTDOWN || gameState == GAMEREVIEW ) { + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) ); + } else { + p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL ); + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL ); + } +} + +/* +================ +idMultiplayerGame::ServerWriteInitialReliableMessages +================ +*/ +void idMultiplayerGame::ServerWriteInitialReliableMessages( int clientNum, lobbyUserID_t lobbyUserID ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + // send the game state and start time + outMsg.WriteByte( gameState ); + outMsg.WriteLong( matchStartedTime ); + outMsg.WriteShort( startFragLimit ); + // send the powerup states and the spectate states + for( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity * ent = gameLocal.entities[ i ]; + if ( i != clientNum && ent && ent->IsType( idPlayer::Type ) ) { + outMsg.WriteByte( i ); + outMsg.WriteBits( static_cast< idPlayer * >( ent )->inventory.powerups, 15 ); + outMsg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 ); + } + } + outMsg.WriteByte( MAX_CLIENTS ); + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_STARTSTATE, outMsg ); + + // warmup time + if ( gameState == COUNTDOWN ) { + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME ); + outMsg.WriteLong( warmupEndTime ); + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( lobbyUserID, GAME_RELIABLE_MESSAGE_WARMUPTIME, outMsg ); + } +} + +/* +================ +idMultiplayerGame::ClientReadStartState +================ +*/ +void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) { + // read the state in preparation for reading snapshot updates + gameState = (idMultiplayerGame::gameState_t)msg.ReadByte(); + matchStartedTime = msg.ReadLong( ); + startFragLimit = msg.ReadShort( ); + + int client; + while ( ( client = msg.ReadByte() ) != MAX_CLIENTS ) { + + // Do not process players that are not here. + if( gameLocal.entities[ client ] == NULL ) { + continue; + } + + assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::Type ) ); + int powerup = msg.ReadBits( 15 ); + for ( int i = 0; i < MAX_POWERUPS; i++ ) { + if ( powerup & ( 1 << i ) ) { + static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + } + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate ); + } +} + +/* +================ +idMultiplayerGame::ClientReadWarmupTime +================ +*/ +void idMultiplayerGame::ClientReadWarmupTime( const idBitMsg &msg ) { + warmupEndTime = msg.ReadLong(); +} + +/* +================ +idMultiplayerGame::ClientReadWarmupTime +================ +*/ +void idMultiplayerGame::ClientReadMatchStartedTime( const idBitMsg & msg ) { + matchStartedTime = msg.ReadLong(); +} + + +/* +================ +idMultiplayerGame::ClientReadAchievementUnlock +================ +*/ +void idMultiplayerGame::ClientReadAchievementUnlock( const idBitMsg & msg ) { + + int playerid = msg.ReadByte(); + achievement_t achieve = ( achievement_t )msg.ReadByte(); + + idPlayer * player = static_cast( gameLocal.entities[ playerid ] ); + + if( player != NULL ) { + + idLib::Printf( "Client Receiving Achievement\n"); + player->GetAchievementManager().EventCompletesAchievement( achieve ); + } + +} + +/* +================ +idMultiplayerGame::IsGametypeTeamBased +================ +*/ +bool idMultiplayerGame::IsGametypeTeamBased() /* CTF */ +{ + switch ( gameLocal.gameType ) + { + case GAME_SP: + case GAME_DM: + case GAME_TOURNEY: + case GAME_LASTMAN: + return false; + case GAME_CTF: + case GAME_TDM: + return true; + + default: + assert( !"Add support for your new gametype here." ); + } + + return false; +} + +/* +================ +idMultiplayerGame::IsGametypeFlagBased +================ +*/ +bool idMultiplayerGame::IsGametypeFlagBased() { + switch ( gameLocal.gameType ) + { + case GAME_SP: + case GAME_DM: + case GAME_TOURNEY: + case GAME_LASTMAN: + case GAME_TDM: + return false; + case GAME_CTF: + return true; + default: + assert( !"Add support for your new gametype here." ); + } + + return false; + +} + +/* +================ +idMultiplayerGame::GetTeamFlag +================ +*/ +idItemTeam * idMultiplayerGame::GetTeamFlag( int team ) { + assert( team == 0 || team == 1 ); + + if ( !IsGametypeFlagBased() || ( team != 0 && team != 1 ) ) /* CTF */ + return NULL; + + // TODO : just call on map start + FindTeamFlags(); + + return teamFlags[team]; +} + +/* +================ +idMultiplayerGame::GetTeamFlag +================ +*/ +void idMultiplayerGame::FindTeamFlags() { + char * flagDefs[2] = + { + "team_CTF_redflag", + "team_CTF_blueflag" + }; + + for ( int i = 0; i < 2; i++) + { + idEntity * entity = gameLocal.FindEntityUsingDef( NULL, flagDefs[i] ); + do + { + if ( entity == NULL ) + return; + + idItemTeam * flag = static_cast(entity); + + if ( flag->team == i ) + { + teamFlags[i] = flag; + break; + } + + entity = gameLocal.FindEntityUsingDef( entity, flagDefs[i] ); + } while( entity ); + } +} + +/* +================ +idMultiplayerGame::GetFlagStatus +================ +*/ +flagStatus_t idMultiplayerGame::GetFlagStatus( int team ) { + //assert( IsGametypeFlagBased() ); + + idItemTeam *teamFlag = GetTeamFlag( team ); + //assert( teamFlag != NULL ); + + if ( teamFlag != NULL ) { + if ( teamFlag->carried == false && teamFlag->dropped == false ) + return FLAGSTATUS_INBASE; + + if ( teamFlag->carried == true ) + return FLAGSTATUS_TAKEN; + + if ( teamFlag->carried == false && teamFlag->dropped == true ) + return FLAGSTATUS_STRAY; + } + + //assert( !"Invalid flag state." ); + return FLAGSTATUS_NONE; +} + +/* +================ +idMultiplayerGame::SetFlagMsgs +================ +*/ +void idMultiplayerGame::SetFlagMsg( bool b ) { + flagMsgOn = b; +} + +/* +================ +idMultiplayerGame::IsFlagMsgOn +================ +*/ +bool idMultiplayerGame::IsFlagMsgOn() { + return ( GetGameState() == WARMUP || GetGameState() == GAMEON || GetGameState() == SUDDENDEATH ) && flagMsgOn; +} + +/* +================ +idMultiplayerGame::ReloadScoreboard +================ +*/ +void idMultiplayerGame::ReloadScoreboard() { + + if ( scoreboardManager != NULL ) { + scoreboardManager->Initialize( "scoreboard", common->SW() ); + } + + Precache(); +} + +/* +================ +idMultiplayerGame::GetGameModes +================ +*/ +int idMultiplayerGame::GetGameModes( const char *** gameModes, const char *** gameModesDisplay ) { + + bool defaultValue = true; + + if( session->GetTitleStorageBool("CTF_Enabled", defaultValue ) ) { + *gameModes = gameTypeNames_WithCTF; + *gameModesDisplay = gameTypeDisplayNames_WithCTF; + + return GAME_COUNT; + } else { + + *gameModes = gameTypeNames_WithoutCTF; + *gameModesDisplay = gameTypeDisplayNames_WithoutCTF; + + return GAME_COUNT - 1; + } +} diff --git a/neo/d3xp/MultiplayerGame.h b/neo/d3xp/MultiplayerGame.h new file mode 100644 index 00000000..f57aa795 --- /dev/null +++ b/neo/d3xp/MultiplayerGame.h @@ -0,0 +1,357 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MULTIPLAYERGAME_H__ +#define __MULTIPLAYERGAME_H__ + +/* +=============================================================================== + + Basic DOOM multiplayer + +=============================================================================== +*/ + +class idPlayer; +class idMenuHandler_HUD; +class idMenuHandler_Scoreboard; +class idItemTeam; + +enum gameType_t { + GAME_SP = -2, + GAME_RANDOM = -1, + GAME_DM = 0, + GAME_TOURNEY, + GAME_TDM, + GAME_LASTMAN, + GAME_CTF, + GAME_COUNT, +}; + +// Used by the UI +typedef enum { + FLAGSTATUS_INBASE = 0, + FLAGSTATUS_TAKEN = 1, + FLAGSTATUS_STRAY = 2, + FLAGSTATUS_NONE = 3 +} flagStatus_t; + +typedef struct mpPlayerState_s { + int ping; // player ping + int fragCount; // kills + int teamFragCount; // team kills + int wins; // wins + bool scoreBoardUp; // toggle based on player scoreboard button, used to activate de-activate the scoreboard gui + int deaths; +} mpPlayerState_t; + +const int NUM_CHAT_NOTIFY = 5; +const int CHAT_FADE_TIME = 400; +const int FRAGLIMIT_DELAY = 2000; + +const int MP_PLAYER_MINFRAGS = -100; +const int MP_PLAYER_MAXFRAGS = 400; // in CTF frags are player points +const int MP_PLAYER_MAXWINS = 100; +const int MP_PLAYER_MAXPING = 999; +const int MP_CTF_MAXPOINTS = 400; + +typedef struct mpChatLine_s { + idStr line; + short fade; // starts high and decreases, line is removed once reached 0 +} mpChatLine_t; + +typedef enum { + SND_YOUWIN = 0, + SND_YOULOSE, + SND_FIGHT, + SND_THREE, + SND_TWO, + SND_ONE, + SND_SUDDENDEATH, + SND_FLAG_CAPTURED_YOURS, + SND_FLAG_CAPTURED_THEIRS, + SND_FLAG_RETURN, + SND_FLAG_TAKEN_YOURS, + SND_FLAG_TAKEN_THEIRS, + SND_FLAG_DROPPED_YOURS, + SND_FLAG_DROPPED_THEIRS, + SND_COUNT +} snd_evt_t; + +class idMultiplayerGame { +public: + + idMultiplayerGame(); + + void Shutdown(); + + // resets everything and prepares for a match + void Reset(); + + // setup local data for a new player + void SpawnPlayer( int clientNum ); + + // checks rules and updates state of the mp game + void Run(); + + // draws mp hud, scoredboard, etc.. + bool Draw( int clientNum ); + + // updates frag counts and potentially ends the match in sudden death + void PlayerDeath( idPlayer *dead, idPlayer *killer, bool telefrag ); + + void AddChatLine( VERIFY_FORMAT_STRING const char *fmt, ... ); + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + + // game state + typedef enum { + INACTIVE = 0, // not running + WARMUP, // warming up + COUNTDOWN, // post warmup pre-game + GAMEON, // game is on + SUDDENDEATH, // game is on but in sudden death, first frag wins + GAMEREVIEW, // game is over, scoreboard is up. we wait si_gameReviewPause seconds (which has a min value) + NEXTGAME, + STATE_COUNT + } gameState_t; + static const char *GameStateStrings[ STATE_COUNT ]; + idMultiplayerGame::gameState_t GetGameState() const; + + static const char *GlobalSoundStrings[ SND_COUNT ]; + void PlayGlobalSound( int toPlayerNum, snd_evt_t evt, const char *shader = NULL ); + void PlayTeamSound( int toTeam, snd_evt_t evt, const char *shader = NULL ); // sound that's sent only to member of toTeam team + + // more compact than a chat line + typedef enum { + MSG_SUICIDE = 0, + MSG_KILLED, + MSG_KILLEDTEAM, + MSG_DIED, + MSG_SUDDENDEATH, + MSG_JOINEDSPEC, + MSG_TIMELIMIT, + MSG_FRAGLIMIT, + MSG_TELEFRAGGED, + MSG_JOINTEAM, + MSG_HOLYSHIT, + MSG_POINTLIMIT, + MSG_FLAGTAKEN, + MSG_FLAGDROP, + MSG_FLAGRETURN, + MSG_FLAGCAPTURE, + MSG_SCOREUPDATE, + MSG_LEFTGAME, + MSG_COUNT + } msg_evt_t; + void PrintMessageEvent( msg_evt_t evt, int parm1 = -1, int parm2 = -1 ); + + void DisconnectClient( int clientNum ); + static void DropWeapon_f( const idCmdArgs &args ); + static void MessageMode_f( const idCmdArgs &args ); + static void VoiceChat_f( const idCmdArgs &args ); + static void VoiceChatTeam_f( const idCmdArgs &args ); + + int NumActualClients( bool countSpectators, int *teamcount = NULL ); + void DropWeapon( int clientNum ); + void MapRestart(); + void BalanceTeams(); + void SwitchToTeam( int clientNum, int oldteam, int newteam ); + bool IsPureReady() const; + void ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ); + void ProcessVoiceChat( int clientNum, bool team, int index ); + bool HandleGuiEvent( const sysEvent_t * sev ); + bool IsScoreboardActive(); + void SetScoreboardActive( bool active ); + void CleanupScoreboard(); + + void Precache(); + + void ToggleSpectate(); + + void GetSpectateText( idPlayer * player, idStr spectatetext[ 2 ], bool scoreboard ); + + void ClearFrags( int clientNum ); + + bool CanPlay( idPlayer *p ); + bool WantRespawn( idPlayer *p ); + + void ServerWriteInitialReliableMessages( int clientNum, lobbyUserID_t lobbyUserID ); + void ClientReadStartState( const idBitMsg &msg ); + void ClientReadWarmupTime( const idBitMsg &msg ); + void ClientReadMatchStartedTime( const idBitMsg & msg ); + void ClientReadAchievementUnlock( const idBitMsg & msg ); + + void ServerClientConnect( int clientNum ); + int GetFlagPoints( int team ); // Team points in CTF + void SetFlagMsg( bool b ); // allow flag event messages to be sent + bool IsFlagMsgOn(); // should flag event messages go through? + + int player_red_flag; // Ent num of red flag carrier for HUD + int player_blue_flag; // Ent num of blue flag carrier for HUD + + void PlayerStats( int clientNum, char *data, const int len ); + +private: + static const char * teamNames[]; + static const char * skinNames[]; + static const idVec3 skinColors[]; + static const int numSkins; + + // state vars + gameState_t gameState; // what state the current game is in + gameState_t nextState; // state to switch to when nextStateSwitch is hit + + mpPlayerState_t playerState[ MAX_CLIENTS ]; + + // keep track of clients which are willingly in spectator mode + // time related + int nextStateSwitch; // time next state switch + int warmupEndTime; // warmup till.. + int matchStartedTime; // time current match started + + // tourney + int currentTourneyPlayer[2];// our current set of players + int lastWinner; // plays again + + // warmup + bool one, two, three; // keeps count down voice from repeating + + // guis + idMenuHandler_Scoreboard * scoreboardManager; + + // chat data + mpChatLine_t chatHistory[ NUM_CHAT_NOTIFY ]; + int chatHistoryIndex; + int chatHistorySize; // 0 <= x < NUM_CHAT_NOTIFY + bool chatDataUpdated; + int lastChatLineTime; + + // rankings are used by UpdateScoreboard and UpdateHud + int numRankedPlayers; // ranked players, others may be empty slots or spectators + idPlayer * rankedPlayers[MAX_CLIENTS]; + + bool pureReady; // defaults to false, set to true once server game is running with pure checksums + int fragLimitTimeout; + + int voiceChatThrottle; + + int startFragLimit; // synchronize to clients in initial state, set on -> GAMEON + + idItemTeam * teamFlags[ 2 ]; + int teamPoints[ 2 ]; + + bool flagMsgOn; + +private: + void UpdatePlayerRanks(); + void GameHasBeenWon(); + + // updates the passed gui with current score information + void UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ); + void UpdateScoreboard( idMenuHandler_Scoreboard * scoreboard, idPlayer *owner ); + + void DrawScoreBoard( idPlayer *player ); + + void UpdateHud( idPlayer *player, idMenuHandler_HUD * hudManager ); + bool Warmup(); + idPlayer * FragLimitHit(); + idPlayer * FragLeader(); + bool TimeLimitHit(); + bool PointLimitHit(); + // return team with most points + int WinningTeam(); + void NewState( gameState_t news, idPlayer *player = NULL ); + void UpdateWinsLosses( idPlayer *winner ); + // fill any empty tourney slots based on the current tourney ranks + void FillTourneySlots(); + void CycleTourneyPlayers(); + // walk through the tourneyRank to build a wait list for the clients + void UpdateTourneyLine(); + const char * GameTime(); + void Clear(); + bool EnoughClientsToPlay(); + void ClearChatData(); + void DrawChat( idPlayer * player ); + // go through the clients, and see if they want to be respawned, and if the game allows it + // called during normal gameplay for death -> respawn cycles + // and for a spectator who want back in the game (see param) + void CheckRespawns( idPlayer *spectator = NULL ); + // when clients disconnect or join spectate during game, check if we need to end the game + void CheckAbortGame(); + void MessageMode( const idCmdArgs &args ); + // scores in TDM + void TeamScore( int entityNumber, int team, int delta ); + void VoiceChat( const idCmdArgs &args, bool team ); + void DumpTourneyLine(); + void SuddenRespawn(); + + void FindTeamFlags(); + + void NewState_Warmup_ServerAndClient(); + void NewState_Countdown_ServerAndClient(); + void NewState_GameOn_ServerAndClient(); + void NewState_GameReview_ServerAndClient(); + +public: + + const char * GetTeamName( int team ) const; + const char * GetSkinName( int skin ) const; + const idVec3 & GetSkinColor( int skin ) const; + + idItemTeam * GetTeamFlag( int team ); + flagStatus_t GetFlagStatus( int team ); + void TeamScoreCTF( int team, int delta ); + void PlayerScoreCTF( int playerIdx, int delta ); + // returns entityNum to team flag carrier, -1 if no flag carrier + int GetFlagCarrier( int team ); + void UpdateScoreboardFlagStatus(); + void ReloadScoreboard(); + + int GetGameModes( const char *** gameModes, const char *** gameModesDisplay ); + + bool IsGametypeFlagBased(); + bool IsGametypeTeamBased(); + +}; + +ID_INLINE idMultiplayerGame::gameState_t idMultiplayerGame::GetGameState() const { + return gameState; +} + +ID_INLINE bool idMultiplayerGame::IsPureReady() const { + return pureReady; +} + +ID_INLINE void idMultiplayerGame::ClearFrags( int clientNum ) { + playerState[ clientNum ].fragCount = 0; +} + +#endif /* !__MULTIPLAYERGAME_H__ */ + diff --git a/neo/d3xp/Player.cpp b/neo/d3xp/Player.cpp new file mode 100644 index 00000000..4f594052 --- /dev/null +++ b/neo/d3xp/Player.cpp @@ -0,0 +1,10742 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "../framework/Common_local.h" +#include "PredictedValue_impl.h" + +idCVar flashlight_batteryDrainTimeMS( "flashlight_batteryDrainTimeMS", "30000", CVAR_INTEGER, "amount of time (in MS) it takes for full battery to drain (-1 == no battery drain)" ); +idCVar flashlight_batteryChargeTimeMS( "flashlight_batteryChargeTimeMS", "3000", CVAR_INTEGER, "amount of time (in MS) it takes to fully recharge battery" ); +idCVar flashlight_minActivatePercent( "flashlight_minActivatePercent", ".25", CVAR_FLOAT, "( 0.0 - 1.0 ) minimum amount of battery (%) needed to turn on flashlight" ); +idCVar flashlight_batteryFlickerPercent( "flashlight_batteryFlickerPercent", ".1", CVAR_FLOAT, "chance of flickering when battery is low" ); + +// No longer userinfo, but I don't want to rename the cvar +idCVar ui_showGun( "ui_showGun", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show gun" ); + +// Client-authoritative stuff +idCVar pm_clientAuthoritative_debug( "pm_clientAuthoritative_debug", "0", CVAR_BOOL, "" ); +idCVar pm_controllerShake_damageMaxMag( "pm_controllerShake_damageMaxMag", "60.0f", CVAR_FLOAT, "" ); +idCVar pm_controllerShake_damageMaxDur( "pm_controllerShake_damageMaxDur", "60.0f", CVAR_FLOAT, "" ); + +idCVar pm_clientAuthoritative_warnDist( "pm_clientAuthoritative_warnDist", "100.0f", CVAR_FLOAT, "" ); +idCVar pm_clientAuthoritative_minDistZ( "pm_clientAuthoritative_minDistZ", "1.0f", CVAR_FLOAT, "" ); +idCVar pm_clientAuthoritative_minDist( "pm_clientAuthoritative_minDist", "-1.0f", CVAR_FLOAT, "" ); +idCVar pm_clientAuthoritative_Lerp( "pm_clientAuthoritative_Lerp", "0.9f", CVAR_FLOAT, "" ); + +idCVar pm_clientAuthoritative_Divergence( "pm_clientAuthoritative_Divergence", "200.0f", CVAR_FLOAT, "" ); +idCVar pm_clientInterpolation_Divergence( "pm_clientInterpolation_Divergence", "5000.0f", CVAR_FLOAT, "" ); + +idCVar pm_clientAuthoritative_minSpeedSquared( "pm_clientAuthoritative_minSpeedSquared", "1000.0f", CVAR_FLOAT, "" ); + +extern idCVar g_demoMode; + +/* +=============================================================================== + + Player control of the Doom Marine. + This object handles all player movement and world interaction. + +=============================================================================== +*/ + +// distance between ladder rungs (actually is half that distance, but this sounds better) +const int LADDER_RUNG_DISTANCE = 32; + +// amount of health per dose from the health station +const int HEALTH_PER_DOSE = 10; + +// time before a weapon dropped to the floor disappears +const int WEAPON_DROP_TIME = 20 * 1000; + +// time before a next or prev weapon switch happens +const int WEAPON_SWITCH_DELAY = 150; + +// how many units to raise spectator above default view height so it's in the head of someone +const int SPECTATE_RAISE = 25; + +const int HEALTHPULSE_TIME = 333; + +// minimum speed to bob and play run/walk animations at +const float MIN_BOB_SPEED = 5.0f; + +// Special team used for spectators that we ONLY store on lobby. The local team property on player remains as 0 or 1. +const float LOBBY_SPECTATE_TEAM_FOR_VOICE_CHAT = 2; + +const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' ); +const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' ); +const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' ); +const idEventDef EV_Player_StopFxFov( "stopFxFov" ); +const idEventDef EV_Player_EnableWeapon( "enableWeapon" ); +const idEventDef EV_Player_DisableWeapon( "disableWeapon" ); +const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' ); +const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' ); +const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" ); +const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' ); +const idEventDef EV_Player_OpenPDA( "openPDA" ); +const idEventDef EV_Player_InPDA( "inPDA", NULL, 'd' ); +const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" ); +const idEventDef EV_Player_StopAudioLog( "stopAudioLog" ); +const idEventDef EV_Player_HideTip( "hideTip" ); +const idEventDef EV_Player_LevelTrigger( "levelTrigger" ); +const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" ); +const idEventDef EV_Player_GiveInventoryItem( "giveInventoryItem", "s" ); +const idEventDef EV_Player_RemoveInventoryItem( "removeInventoryItem", "s" ); +const idEventDef EV_Player_GetIdealWeapon( "getIdealWeapon", NULL, 's' ); +const idEventDef EV_Player_SetPowerupTime( "setPowerupTime", "dd" ); +const idEventDef EV_Player_IsPowerupActive( "isPowerupActive", "d", 'd' ); +const idEventDef EV_Player_WeaponAvailable( "weaponAvailable", "s", 'd'); +const idEventDef EV_Player_StartWarp( "startWarp" ); +const idEventDef EV_Player_StopHelltime( "stopHelltime", "d" ); +const idEventDef EV_Player_ToggleBloom( "toggleBloom", "d" ); +const idEventDef EV_Player_SetBloomParms( "setBloomParms", "ff" ); + +CLASS_DECLARATION( idActor, idPlayer ) + EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons ) + EVENT( EV_Player_GetMove, idPlayer::Event_GetMove ) + EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles ) + EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov ) + EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon ) + EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon ) + EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon ) + EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon ) + EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity ) + EVENT( EV_Player_OpenPDA, idPlayer::Event_OpenPDA ) + EVENT( EV_Player_InPDA, idPlayer::Event_InPDA ) + EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter ) + EVENT( EV_Player_StopAudioLog, idPlayer::Event_StopAudioLog ) + EVENT( EV_Player_HideTip, idPlayer::Event_HideTip ) + EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger ) + EVENT( EV_Gibbed, idPlayer::Event_Gibbed ) + EVENT( EV_Player_GiveInventoryItem, idPlayer::Event_GiveInventoryItem ) + EVENT( EV_Player_RemoveInventoryItem, idPlayer::Event_RemoveInventoryItem ) + EVENT( EV_Player_GetIdealWeapon, idPlayer::Event_GetIdealWeapon ) + EVENT( EV_Player_WeaponAvailable, idPlayer::Event_WeaponAvailable ) + EVENT( EV_Player_SetPowerupTime, idPlayer::Event_SetPowerupTime ) + EVENT( EV_Player_IsPowerupActive, idPlayer::Event_IsPowerupActive ) + EVENT( EV_Player_StartWarp, idPlayer::Event_StartWarp ) + EVENT( EV_Player_StopHelltime, idPlayer::Event_StopHelltime ) + EVENT( EV_Player_ToggleBloom, idPlayer::Event_ToggleBloom ) + EVENT( EV_Player_SetBloomParms, idPlayer::Event_SetBloomParms ) +END_CLASS + +const int MAX_RESPAWN_TIME = 10000; +const int RAGDOLL_DEATH_TIME = 3000; +const int MAX_PDAS = 64; +const int MAX_PDA_ITEMS = 128; +const int STEPUP_TIME = 200; +const int MAX_INVENTORY_ITEMS = 20; + +/* +============== +idInventory::Clear +============== +*/ +void idInventory::Clear() { + maxHealth = 0; + weapons = 0; + powerups = 0; + armor = 0; + maxarmor = 0; + deplete_armor = 0; + deplete_rate = 0.0f; + deplete_ammount = 0; + nextArmorDepleteTime = 0; + + for ( int i = 0; i < ammo.Num(); ++i ) { + ammo[i].Set( 0 ); + } + + ClearPowerUps(); + + // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level + for ( int i = 0; i < clip.Num(); ++i ) { + clip[i].Set( -1 ); + } + + items.DeleteContents( true ); + memset(pdasViewed, 0, 4 * sizeof( pdasViewed[0] ) ); + pdas.Clear(); + videos.Clear(); + emails.Clear(); + selVideo = 0; + selEMail = 0; + selPDA = 0; + selAudio = 0; + pdaOpened = false; + + levelTriggers.Clear(); + + nextItemPickup = 0; + nextItemNum = 1; + onePickupTime = 0; + pickupItemNames.Clear(); + objectiveNames.Clear(); + + ammoPredictTime = 0; + + lastGiveTime = 0; + + ammoPulse = false; + weaponPulse = false; + armorPulse = false; +} + +/* +============== +idInventory::GivePowerUp +============== +*/ +void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) { + powerups |= 1 << powerup; + powerupEndTime[ powerup ] = gameLocal.time + msec; +} + +/* +============== +idInventory::ClearPowerUps +============== +*/ +void idInventory::ClearPowerUps() { + int i; + for ( i = 0; i < MAX_POWERUPS; i++ ) { + powerupEndTime[ i ] = 0; + } + powerups = 0; +} + +/* +============== +idInventory::GetPersistantData +============== +*/ +void idInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // armor + dict.SetInt( "armor", armor ); + + // don't bother with powerups, maxhealth, maxarmor, or the clip + + // ammo + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); + if ( name ) { + dict.SetInt( name, ammo[ i ].Get() ); + } + } + + //Save the clip data + for( i = 0; i < MAX_WEAPONS; i++ ) { + dict.SetInt( va("clip%i", i), clip[ i ].Get() ); + } + + // items + num = 0; + for( i = 0; i < items.Num(); i++ ) { + item = items[ i ]; + + // copy all keys with "inv_" + kv = item->MatchPrefix( "inv_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "inv_", kv ); + } + num++; + } + } + dict.SetInt( "items", num ); + + // pdas viewed + for ( i = 0; i < 4; i++ ) { + dict.SetInt( va("pdasViewed_%i", i), pdasViewed[i] ); + } + + dict.SetInt( "selPDA", selPDA ); + dict.SetInt( "selVideo", selVideo ); + dict.SetInt( "selEmail", selEMail ); + dict.SetInt( "selAudio", selAudio ); + dict.SetInt( "pdaOpened", pdaOpened ); + + // pdas + for ( i = 0; i < pdas.Num(); i++ ) { + sprintf( key, "pda_%i", i ); + dict.Set( key, pdas[ i ]->GetName() ); + } + dict.SetInt( "pdas", pdas.Num() ); + + // video cds + for ( i = 0; i < videos.Num(); i++ ) { + sprintf( key, "video_%i", i ); + dict.Set( key, videos[ i ]->GetName() ); + } + dict.SetInt( "videos", videos.Num() ); + + // emails + for ( i = 0; i < emails.Num(); i++ ) { + sprintf( key, "email_%i", i ); + dict.Set( key, emails[ i ]->GetName() ); + } + dict.SetInt( "emails", emails.Num() ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + dict.SetInt( "levelTriggers", levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + sprintf( key, "levelTrigger_Level_%i", i ); + dict.Set( key, levelTriggers[i].levelName ); + sprintf( key, "levelTrigger_Trigger_%i", i ); + dict.Set( key, levelTriggers[i].triggerName ); + } +} + +/* +============== +idInventory::RestoreInventory +============== +*/ +void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + Clear(); + + // health/armor + maxHealth = dict.GetInt( "maxhealth", "100" ); + armor = dict.GetInt( "armor", "50" ); + maxarmor = dict.GetInt( "maxarmor", "100" ); + deplete_armor = dict.GetInt( "deplete_armor", "0" ); + deplete_rate = dict.GetFloat( "deplete_rate", "2.0" ); + deplete_ammount = dict.GetInt( "deplete_ammount", "1" ); + + // the clip and powerups aren't restored + + // ammo + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + //Restore the clip data + for( i = 0; i < MAX_WEAPONS; i++ ) { + clip[i] = dict.GetInt(va("clip%i", i), "-1"); + } + + // items + num = dict.GetInt( "items" ); + items.SetNum( num ); + for( i = 0; i < num; i++ ) { + item = new (TAG_ENTITY) idDict(); + items[ i ] = item; + sprintf( itemname, "item_%i ", i ); + kv = dict.MatchPrefix( itemname ); + while( kv ) { + key = kv->GetKey(); + key.Strip( itemname ); + item->Set( key, kv->GetValue() ); + kv = dict.MatchPrefix( itemname, kv ); + } + } + + // pdas viewed + for ( i = 0; i < 4; i++ ) { + pdasViewed[i] = dict.GetInt(va("pdasViewed_%i", i)); + } + + selPDA = dict.GetInt( "selPDA" ); + selEMail = dict.GetInt( "selEmail" ); + selVideo = dict.GetInt( "selVideo" ); + selAudio = dict.GetInt( "selAudio" ); + pdaOpened = dict.GetBool( "pdaOpened" ); + + // pdas + num = dict.GetInt( "pdas" ); + pdas.SetNum( num ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "pda_%i", i ); + pdas[i] = static_cast( declManager->FindType( DECL_PDA, dict.GetString( itemname, "default" ) ) ); + } + + // videos + num = dict.GetInt( "videos" ); + videos.SetNum( num ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "video_%i", i ); + videos[i] = static_cast( declManager->FindType( DECL_VIDEO, dict.GetString( itemname, "default" ) ) ); + } + + // emails + num = dict.GetInt( "emails" ); + emails.SetNum( num ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "email_%i", i ); + emails[i] = static_cast( declManager->FindType( DECL_EMAIL, dict.GetString( itemname, "default" ) ) ); + } + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + + if ( g_skill.GetInteger() >= 3 || cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + Give( owner, dict, "weapon", dict.GetString( "weapon_nightmare" ), NULL, false, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } else { + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + + num = dict.GetInt( "levelTriggers" ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "levelTrigger_Level_%i", i ); + idLevelTriggerInfo lti; + lti.levelName = dict.GetString( itemname ); + sprintf( itemname, "levelTrigger_Trigger_%i", i ); + lti.triggerName = dict.GetString( itemname ); + levelTriggers.Append( lti ); + } + +} + +/* +============== +idInventory::Save +============== +*/ +void idInventory::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( maxHealth ); + savefile->WriteInt( weapons ); + savefile->WriteInt( powerups ); + savefile->WriteInt( armor ); + savefile->WriteInt( maxarmor ); + savefile->WriteInt( ammoPredictTime ); + savefile->WriteInt( deplete_armor ); + savefile->WriteFloat( deplete_rate ); + savefile->WriteInt( deplete_ammount ); + savefile->WriteInt( nextArmorDepleteTime ); + + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + savefile->WriteInt( ammo[ i ].Get() ); + } + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->WriteInt( clip[ i ].Get() ); + } + for( i = 0; i < MAX_POWERUPS; i++ ) { + savefile->WriteInt( powerupEndTime[ i ] ); + } + + savefile->WriteInt( items.Num() ); + for( i = 0; i < items.Num(); i++ ) { + savefile->WriteDict( items[ i ] ); + } + + savefile->WriteInt( pdasViewed[0] ); + savefile->WriteInt( pdasViewed[1] ); + savefile->WriteInt( pdasViewed[2] ); + savefile->WriteInt( pdasViewed[3] ); + + savefile->WriteInt( selPDA ); + savefile->WriteInt( selVideo ); + savefile->WriteInt( selEMail ); + savefile->WriteInt( selAudio ); + savefile->WriteBool( pdaOpened ); + + savefile->WriteInt( pdas.Num() ); + for( i = 0; i < pdas.Num(); i++ ) { + savefile->WriteString( pdas[ i ]->GetName() ); + } + + savefile->WriteInt( pdaSecurity.Num() ); + for( i=0; i < pdaSecurity.Num(); i++ ) { + savefile->WriteString( pdaSecurity[ i ] ); + } + + savefile->WriteInt( videos.Num() ); + for( i = 0; i < videos.Num(); i++ ) { + savefile->WriteString( videos[ i ]->GetName() ); + } + + savefile->WriteInt( emails.Num() ); + for ( i = 0; i < emails.Num(); i++ ) { + savefile->WriteString( emails[ i ]->GetName() ); + } + + savefile->WriteInt( nextItemPickup ); + savefile->WriteInt( nextItemNum ); + savefile->WriteInt( onePickupTime ); + + savefile->WriteInt( pickupItemNames.Num() ); + for( i = 0; i < pickupItemNames.Num(); i++ ) { + savefile->WriteString( pickupItemNames[i] ); + } + + savefile->WriteInt( objectiveNames.Num() ); + for( i = 0; i < objectiveNames.Num(); i++ ) { + savefile->WriteMaterial( objectiveNames[i].screenshot ); + savefile->WriteString( objectiveNames[i].text ); + savefile->WriteString( objectiveNames[i].title ); + } + + savefile->WriteInt( levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + savefile->WriteString( levelTriggers[i].levelName ); + savefile->WriteString( levelTriggers[i].triggerName ); + } + + savefile->WriteBool( ammoPulse ); + savefile->WriteBool( weaponPulse ); + savefile->WriteBool( armorPulse ); + + savefile->WriteInt( lastGiveTime ); + + for(i = 0; i < AMMO_NUMTYPES; i++) { + savefile->WriteInt(rechargeAmmo[i].ammo); + savefile->WriteInt(rechargeAmmo[i].rechargeTime); + savefile->WriteString(rechargeAmmo[i].ammoName); + } +} + +/* +============== +idInventory::Restore +============== +*/ +void idInventory::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( maxHealth ); + savefile->ReadInt( weapons ); + savefile->ReadInt( powerups ); + savefile->ReadInt( armor ); + savefile->ReadInt( maxarmor ); + savefile->ReadInt( ammoPredictTime ); + savefile->ReadInt( deplete_armor ); + savefile->ReadFloat( deplete_rate ); + savefile->ReadInt( deplete_ammount ); + savefile->ReadInt( nextArmorDepleteTime ); + + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + int savedAmmo = 0; + savefile->ReadInt( savedAmmo ); + ammo[ i ].Set( savedAmmo ); + } + for( i = 0; i < MAX_WEAPONS; i++ ) { + int savedClip = 0; + savefile->ReadInt( savedClip ); + clip[ i ].Set( savedClip ); + } + for( i = 0; i < MAX_POWERUPS; i++ ) { + savefile->ReadInt( powerupEndTime[ i ] ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idDict *itemdict = new (TAG_ENTITY) idDict; + + savefile->ReadDict( itemdict ); + items.Append( itemdict ); + } + + // pdas + savefile->ReadInt( pdasViewed[0] ); + savefile->ReadInt( pdasViewed[1] ); + savefile->ReadInt( pdasViewed[2] ); + savefile->ReadInt( pdasViewed[3] ); + + savefile->ReadInt( selPDA ); + savefile->ReadInt( selVideo ); + savefile->ReadInt( selEMail ); + savefile->ReadInt( selAudio ); + savefile->ReadBool( pdaOpened ); + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idStr strPda; + savefile->ReadString( strPda ); + pdas.Append( static_cast( declManager->FindType( DECL_PDA, strPda ) ) ); + } + + // pda security clearances + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idStr invName; + savefile->ReadString( invName ); + pdaSecurity.Append( invName ); + } + + // videos + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idStr strVideo; + savefile->ReadString( strVideo ); + videos.Append( static_cast( declManager->FindType( DECL_VIDEO, strVideo ) ) ); + } + + // email + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idStr strEmail; + savefile->ReadString( strEmail ); + emails.Append( static_cast( declManager->FindType( DECL_EMAIL, strEmail ) ) ); + } + + savefile->ReadInt( nextItemPickup ); + savefile->ReadInt( nextItemNum ); + savefile->ReadInt( onePickupTime ); + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idStr itemName; + savefile->ReadString( itemName ); + pickupItemNames.Append( itemName ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idObjectiveInfo obj; + + savefile->ReadMaterial( obj.screenshot ); + savefile->ReadString( obj.text ); + savefile->ReadString( obj.title ); + + objectiveNames.Append( obj ); + } + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idLevelTriggerInfo lti; + savefile->ReadString( lti.levelName ); + savefile->ReadString( lti.triggerName ); + levelTriggers.Append( lti ); + } + + savefile->ReadBool( ammoPulse ); + savefile->ReadBool( weaponPulse ); + savefile->ReadBool( armorPulse ); + + savefile->ReadInt( lastGiveTime ); + + for(i = 0; i < AMMO_NUMTYPES; i++) { + savefile->ReadInt(rechargeAmmo[i].ammo); + savefile->ReadInt(rechargeAmmo[i].rechargeTime); + + idStr name; + savefile->ReadString(name); + strcpy(rechargeAmmo[i].ammoName, name); + } +} + +/* +============== +idInventory::AmmoIndexForAmmoClass +============== +*/ +ammo_t idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const { + return idWeapon::GetAmmoNumForName( ammo_classname ); +} + +/* +============== +idInventory::AmmoIndexForAmmoClass +============== +*/ +int idInventory::MaxAmmoForAmmoClass( const idPlayer *owner, const char *ammo_classname ) const { + return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ); +} + +/* +============== +idInventory::AmmoPickupNameForIndex +============== +*/ +const char *idInventory::AmmoPickupNameForIndex( ammo_t ammonum ) const { + return idWeapon::GetAmmoPickupNameForNum( ammonum ); +} + +/* +============== +idInventory::WeaponIndexForAmmoClass +mapping could be prepared in the constructor +============== +*/ +int idInventory::WeaponIndexForAmmoClass( const idDict & spawnArgs, const char *ammo_classname ) const { + int i; + const char *weapon_classname; + for( i = 0; i < MAX_WEAPONS; i++ ) { + weapon_classname = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !weapon_classname ) { + continue; + } + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + continue; + } + if ( !idStr::Icmp( ammo_classname, decl->dict.GetString( "ammoType" ) ) ) { + return i; + } + } + return -1; +} + +/* +============== +idInventory::AmmoIndexForWeaponClass +============== +*/ +ammo_t idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + //gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + return 0; + } + if ( ammoRequired ) { + *ammoRequired = decl->dict.GetInt( "ammoRequired" ); + } + ammo_t ammo_i = AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); + return ammo_i; +} + +/* +============== +idInventory::AddPickupName +============== +*/ +void idInventory::AddPickupName( const char * name, idPlayer * owner ) { //_D3XP + int num = pickupItemNames.Num(); + if ( ( num == 0 ) || ( pickupItemNames[ num - 1 ].Icmp( name ) != 0 ) ) { + if ( idStr::Cmpn( name, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + pickupItemNames.Append( idLocalization::GetString( name ) ); + } else { + pickupItemNames.Append( name ); + } + } +} + +/* +============== +idInventory::Give +============== +*/ +bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, + idPredictedValue< int > * idealWeapon, bool updateHud, unsigned int giveFlags ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + const idDeclEntityDef *weaponDecl; + bool tookWeapon; + int amount; + const char *name; + + if ( !idStr::Icmp( statname, "ammo_bloodstone" ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + + if(max <= 0) { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + //No Max + ammo[ i ] += atoi( value ); + } + } else { + //Already at or above the max so don't allow the give + if(ammo[ i ].Get() >= max) { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + ammo[ i ] = max; + } + return false; + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + //We were below the max so accept the give but cap it at the max + ammo[ i ] += atoi( value ); + if(ammo[ i ].Get() > max) { + ammo[ i ] = max; + } + } + } + } else if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + if ( ammo[ i ].Get() >= max ) { + return false; + } + // Add ammo for the feedback flag because it's predicted. + // If it is a misprediction, the client will be corrected in + // a snapshot. + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + amount = atoi( value ); + if ( amount ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ].Get() > max ) ) { + ammo[ i ] = max; + } + ammoPulse = true; + } + + name = AmmoPickupNameForIndex( i ); + if ( idStr::Length( name ) ) { + AddPickupName( name, owner ); //_D3XP + } + } + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( armor >= maxarmor ) { + return false; // can't hold any more, so leave the item + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + amount = atoi( value ); + if ( amount ) { + armor += amount; + if ( armor > maxarmor ) { + armor = maxarmor; + } + nextArmorDepleteTime = 0; + armorPulse = true; + } + } + } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + idStr temp = statname; + i = atoi(temp.Mid(7, 2)); + if ( i != -1 ) { + // set, don't add. not going over the clip size limit. + SetClipAmmoForWeapon( i, atoi( value ) ); + } + } + } else if ( !idStr::Icmp( statname, "invulnerability" ) ) { + owner->GivePowerUp( INVULNERABILITY, SEC2MS( atof( value ) ), giveFlags ); + } else if ( !idStr::Icmp( statname, "helltime" ) ) { + owner->GivePowerUp( HELLTIME, SEC2MS( atof( value ) ), giveFlags ); + } else if ( !idStr::Icmp( statname, "envirosuit" ) ) { + owner->GivePowerUp( ENVIROSUIT, SEC2MS( atof( value ) ), giveFlags ); + owner->GivePowerUp( ENVIROTIME, SEC2MS( atof( value ) ), giveFlags ); + } else if ( !idStr::Icmp( statname, "berserk" ) ) { + owner->GivePowerUp( BERSERK, SEC2MS( atof( value ) ), giveFlags ); + //} else if ( !idStr::Icmp( statname, "haste" ) ) { + // owner->GivePowerUp( HASTE, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "adrenaline" ) ) { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + GivePowerUp( owner, ADRENALINE, SEC2MS( atof( value ) ) ); + } + } else if ( !idStr::Icmp( statname, "mega" ) ) { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + GivePowerUp( owner, MEGAHEALTH, SEC2MS( atof( value ) ) ); + } + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + tookWeapon = false; + for( pos = value; pos != NULL; pos = end ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponName( pos, 0, len ); + + // find the number of the matching weapon name + for( i = 0; i < MAX_WEAPONS; i++ ) { + if ( weaponName == spawnArgs.GetString( va( "def_weapon%d", i ) ) ) { + break; + } + } + + if ( i >= MAX_WEAPONS ) { + gameLocal.Warning( "Unknown weapon '%s'", weaponName.c_str() ); + continue; + } + + // cache the media for this weapon + weaponDecl = gameLocal.FindEntityDef( weaponName, false ); + + // don't pickup "no ammo" weapon types twice + // not for D3 SP .. there is only one case in the game where you can get a no ammo + // weapon when you might already have it, in that case it is more conistent to pick it up + if ( common->IsMultiplayer() && ( weapons & ( 1 << i ) ) && ( weaponDecl != NULL ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) { + continue; + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) || ( weaponName == "weapon_soulcube" ) ) { + if ( ( weapons & ( 1 << i ) ) == 0 || common->IsMultiplayer() ) { + tookWeapon = true; + + // This is done during "feedback" so that clients can predict the ideal weapon. + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + lobbyUserID_t & lobbyUserID = gameLocal.lobbyUserIDs[owner->entityNumber]; + if ( lobby.GetLobbyUserWeaponAutoSwitch( lobbyUserID ) && idealWeapon != NULL && i != owner->weapon_bloodstone_active1 && i != owner->weapon_bloodstone_active2 && i != owner->weapon_bloodstone_active3) { + idealWeapon->Set( i ); + } + } + + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + if ( updateHud && lastGiveTime + 1000 < gameLocal.time ) { + if ( owner->hud ) { + owner->hud->GiveWeapon( owner, i ); + } + lastGiveTime = gameLocal.time; + } + + weaponPulse = true; + weapons |= ( 1 << i ); + + + if ( weaponName != "weapon_pda" ) { + for ( int index = 0; index < NUM_QUICK_SLOTS; ++index ) { + if ( owner->GetQuickSlot( index ) == -1 ) { + owner->SetQuickSlot( index, i ); + break; + } + } + } + } + } + } + } + return tookWeapon; + } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) { + // ignore these as they're handled elsewhere + return false; + } else { + // unknown item + gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname ); + return false; + } + + return true; +} + +/* +=============== +idInventoy::Drop +=============== +*/ +void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) { + // remove the weapon bit + // also remove the ammo associated with the weapon as we pushed it in the item + assert( weapon_index != -1 || weapon_classname ); + if ( weapon_index == -1 ) { + for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) { + if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) { + break; + } + } + if ( weapon_index >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weapon_classname ); + } + } else if ( !weapon_classname ) { + weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ); + } + weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) ); + ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL ); + if ( ammo_i && ammo_i < AMMO_NUMTYPES ) { + clip[ weapon_index ] = -1; + ammo[ ammo_i ] = 0; + } +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( ammo_t type, int amount ) { + if ( ( type == 0 ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ type ].Get() < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ type ].Get() / amount; + +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( const char *weapon_classname, bool includeClip, idPlayer* owner ) { //_D3XP + int ammoRequired; + ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + + int ammoCount = HasAmmo( ammo_i, ammoRequired ); + if(includeClip && owner) { + ammoCount += Max( 0, clip[owner->SlotForWeapon(weapon_classname)].Get() ); + } + return ammoCount; + +} + +/* +=============== +idInventory::HasEmptyClipCannotRefill +=============== +*/ +bool idInventory::HasEmptyClipCannotRefill(const char *weapon_classname, idPlayer* owner) { + + int clipSize = clip[owner->SlotForWeapon(weapon_classname)].Get(); + if(clipSize) { + return false; + } + + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( decl == NULL ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + return false; + } + int minclip = decl->dict.GetInt("minclipsize"); + if(!minclip) { + return false; + } + + ammo_t ammo_i = AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); + int ammoRequired = decl->dict.GetInt( "ammoRequired" ); + int ammoCount = HasAmmo( ammo_i, ammoRequired ); + if(ammoCount < minclip) { + return true; + } + return false; +} + +/* +=============== +idInventory::UseAmmo +=============== +*/ +bool idInventory::UseAmmo( ammo_t type, int amount ) { + if ( g_infiniteAmmo.GetBool() ) { + return true; + } + + if ( !HasAmmo( type, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ type ].Get() >= 0 ) { + const int currentAmmo = GetInventoryAmmoForType( type ); + SetInventoryAmmoForType( type, currentAmmo - amount ); + } + + return true; +} + +/* +=============== +idInventory::UpdateArmor +=============== +*/ +void idInventory::UpdateArmor() { + if ( deplete_armor != 0.0f && deplete_armor < armor ) { + if ( !nextArmorDepleteTime ) { + nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000; + } else if ( gameLocal.time > nextArmorDepleteTime ) { + armor -= deplete_ammount; + if ( armor < deplete_armor ) { + armor = deplete_armor; + } + nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000; + } + } +} + +/* +=============== +idInventory::InitRechargeAmmo +=============== +* Loads any recharge ammo definitions from the ammo_types entity definitions. +*/ +void idInventory::InitRechargeAmmo(idPlayer *owner) { + + memset (rechargeAmmo, 0, sizeof(rechargeAmmo)); + + const idKeyValue *kv = owner->spawnArgs.MatchPrefix( "ammorecharge_" ); + while( kv ) { + idStr key = kv->GetKey(); + idStr ammoname = key.Right(key.Length()- strlen("ammorecharge_")); + int ammoType = AmmoIndexForAmmoClass(ammoname); + rechargeAmmo[ammoType].ammo = (atof(kv->GetValue().c_str())*1000); + strcpy(rechargeAmmo[ammoType].ammoName, ammoname); + kv = owner->spawnArgs.MatchPrefix( "ammorecharge_", kv ); + } +} + +/* +=============== +idInventory::RechargeAmmo +=============== +* Called once per frame to update any ammo amount for ammo types that recharge. +*/ +void idInventory::RechargeAmmo(idPlayer *owner) { + + for(int i = 0; i < AMMO_NUMTYPES; i++) { + if(rechargeAmmo[i].ammo > 0) { + if(!rechargeAmmo[i].rechargeTime) { + //Initialize the recharge timer. + rechargeAmmo[i].rechargeTime = gameLocal.time; + } + int elapsed = gameLocal.time - rechargeAmmo[i].rechargeTime; + if(elapsed >= rechargeAmmo[i].ammo) { + int intervals = (gameLocal.time - rechargeAmmo[i].rechargeTime)/rechargeAmmo[i].ammo; + ammo[i] += intervals; + + int max = MaxAmmoForAmmoClass(owner, rechargeAmmo[i].ammoName); + if(max > 0) { + if(ammo[i].Get() > max) { + ammo[i] = max; + } + } + rechargeAmmo[i].rechargeTime += intervals*rechargeAmmo[i].ammo; + } + } + } +} + +/* +=============== +idInventory::CanGive +=============== +*/ +bool idInventory::CanGive( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value ) { + + if ( !idStr::Icmp( statname, "ammo_bloodstone" ) ) { + int max = MaxAmmoForAmmoClass(owner, statname); + int i = AmmoIndexForAmmoClass(statname); + + if(max <= 0) { + //No Max + return true; + } else { + //Already at or above the max so don't allow the give + if(ammo[ i ].Get() >= max) { + ammo[ i ] = max; + return false; + } + return true; + } + } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) { + // ignore these as they're handled elsewhere + //These items should not be considered as succesful gives because it messes up the max ammo items + return false; + } + return true; +} + +/* +=============== +idInventory::SetClipAmmoForWeapon + +Ammo is predicted on clients. This function ensures the frame the prediction occurs +is stored so the predicted value doesn't get overwritten by snapshots. Of course +the snapshot-reading function must check this value. +=============== +*/ +void idInventory::SetClipAmmoForWeapon( const int weapon, const int amount ) { + clip[weapon] = amount; +} + +/* +=============== +idInventory::SetInventoryAmmoForType + +Ammo is predicted on clients. This function ensures the frame the prediction occurs +is stored so the predicted value doesn't get overwritten by snapshots. Of course +the snapshot-reading function must check this value. +=============== +*/ +void idInventory::SetInventoryAmmoForType( int ammoType, const int amount ) { + ammo[ammoType] = amount; +} + +/* +=============== +idInventory::GetClipAmmoForWeapon +=============== +*/ +int idInventory::GetClipAmmoForWeapon( const int weapon ) const { + return clip[weapon].Get(); +} + +/* +=============== +idInventory::GetInventoryAmmoForType +=============== +*/ +int idInventory::GetInventoryAmmoForType( const int ammoType ) const { + return ammo[ammoType].Get(); +} + +/* +=============== +idInventory::WriteAmmoToSnapshot +=============== +*/ +void idInventory::WriteAmmoToSnapshot( idBitMsg & msg ) const { + for( int i = 0; i < AMMO_NUMTYPES; i++ ) { + msg.WriteBits( ammo[i].Get(), ASYNC_PLAYER_INV_AMMO_BITS ); + } + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteBits( clip[i].Get(), ASYNC_PLAYER_INV_CLIP_BITS ); + } +} + +/* +=============== +idInventory::ReadAmmoFromSnapshot +=============== +*/ +void idInventory::ReadAmmoFromSnapshot( const idBitMsg & msg, const int ownerEntityNumber ) { + for( int i = 0; i < ammo.Num(); i++ ) { + const int snapshotAmmo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS ); + ammo[i].UpdateFromSnapshot( snapshotAmmo, ownerEntityNumber ); + } + for( int i = 0; i < clip.Num(); i++ ) { + const int snapshotClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); + clip[i].UpdateFromSnapshot( snapshotClip, ownerEntityNumber ); + } +} + +/* +=============== +idInventory::ReadAmmoFromSnapshot + +Doesn't really matter what remote client's ammo count is, so just set it to 999. +=============== +*/ +void idInventory::SetRemoteClientAmmo( const int ownerEntityNumber ) { + for ( int i = 0; i < ammo.Num(); ++i ) { + ammo[i].UpdateFromSnapshot( 999, ownerEntityNumber ); + } +} + +/* +============== +idPlayer::idPlayer +============== +*/ +idPlayer::idPlayer(): + previousViewQuat( 0.0f, 0.0f, 0.0f, 1.0f ), + nextViewQuat( 0.0f, 0.0f, 0.0f, 1.0f ), + idealWeapon( -1 ), + serverOverridePositionTime( 0 ), + clientFireCount( 0 ) { + + noclip = false; + godmode = false; + + spawnAnglesSet = false; + spawnAngles = ang_zero; + viewAngles = ang_zero; + cmdAngles = ang_zero; + independentWeaponPitchAngle = 0.0f; + + oldButtons = 0; + buttonMask = 0; + oldImpulseSequence = 0; + + lastHitTime = 0; + lastSndHitTime = 0; + lastSavingThrowTime = 0; + + laserSightHandle = -1; + memset( &laserSightRenderEntity, 0, sizeof( laserSightRenderEntity ) ); + + weapon = NULL; + primaryObjective = NULL; + + hudManager = new idMenuHandler_HUD(); + hud = NULL; + objectiveSystemOpen = false; + memset( quickSlot, -1, sizeof( quickSlot ) ); + + pdaMenu = new (TAG_SWF) idMenuHandler_PDA(); + pdaVideoMat = NULL; + mpMessages = NULL; + + mountedObject = NULL; + enviroSuitLight = NULL; + + heartRate = BASE_HEARTRATE; + heartInfo.Init( 0, 0, 0, 0 ); + lastHeartAdjust = 0; + lastHeartBeat = 0; + lastDmgTime = 0; + deathClearContentsTime = 0; + lastArmorPulse = -10000; + stamina = 0.0f; + healthPool = 0.0f; + nextHealthPulse = 0; + healthPulse = false; + nextHealthTake = 0; + healthTake = false; + + forceScoreBoard = false; + forceRespawn = false; + spectating = false; + spectator = 0; + wantSpectate = true; + + carryingFlag = false; + + lastHitToggle = false; + + minRespawnTime = 0; + maxRespawnTime = 0; + + firstPersonViewOrigin = vec3_zero; + firstPersonViewAxis = mat3_identity; + + hipJoint = INVALID_JOINT; + chestJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + + bobFoot = 0; + bobFrac = 0.0f; + bobfracsin = 0.0f; + bobCycle = 0; + xyspeed = 0.0f; + stepUpTime = 0; + stepUpDelta = 0.0f; + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + viewBobAngles = ang_zero; + viewBob = vec3_zero; + landChange = 0; + landTime = 0; + + currentWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + weapon_soulcube = -1; + weapon_pda = -1; + weapon_fists = -1; + weapon_chainsaw = -1; + weapon_bloodstone = -1; + weapon_bloodstone_active1 = -1; + weapon_bloodstone_active2 = -1; + weapon_bloodstone_active3 = -1; + harvest_lock = false; + + hudPowerup = -1; + lastHudPowerup = -1; + hudPowerupDuration = 0; + + skinIndex = 0; + skin = NULL; + powerUpSkin = NULL; + + numProjectileKills = 0; + numProjectilesFired = 0; + numProjectileHits = 0; + + airless = false; + airMsec = 0; + lastAirDamage = 0; + + gibDeath = false; + gibsLaunched = false; + gibsDir = vec3_zero; + + zoomFov.Init( 0, 0, 0, 0 ); + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + privateCameraView = NULL; + + memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) ); + memset( loggedAccel, 0, sizeof( loggedAccel ) ); + currentLoggedAccel = 0; + + focusTime = 0; + focusGUIent = NULL; + focusUI = NULL; + focusCharacter = NULL; + talkCursor = 0; + focusVehicle = NULL; + cursor = NULL; + + oldMouseX = 0; + oldMouseY = 0; + + lastDamageDef = 0; + lastDamageDir = vec3_zero; + lastDamageLocation = 0; + smoothedFrame = 0; + smoothedOriginUpdated = false; + smoothedOrigin = vec3_zero; + smoothedAngles = ang_zero; + + fl.networkSync = true; + + doingDeathSkin = false; + weaponGone = false; + useInitialSpawns = false; + tourneyRank = 0; + lastSpectateTeleport = 0; + tourneyLine = 0; + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + teleportKiller = -1; + respawning = false; + leader = false; + lastSpectateChange = 0; + lastTeleFX = -9999; + weaponCatchup = false; + clientFireCount = 0; + + MPAim = -1; + lastMPAim = -1; + lastMPAimTime = 0; + MPAimFadeTime = 0; + MPAimHighlight = false; + + spawnedTime = 0; + lastManOver = false; + lastManPlayAgain = false; + lastManPresent = false; + + isTelefragged = false; + + isLagged = false; + isChatting = 0; + + selfSmooth = false; + + playedTimeSecs = 0; + playedTimeResidual = 0; + + ResetControllerShake(); + + memset( pdaHasBeenRead, 0, sizeof( pdaHasBeenRead ) ); + memset( videoHasBeenViewed, 0, sizeof( videoHasBeenViewed ) ); + memset( audioHasBeenHeard, 0, sizeof( audioHasBeenHeard ) ); +} + +/* +============== +idPlayer::LinkScriptVariables + +set up conditions for animation +============== +*/ +void idPlayer::LinkScriptVariables() { + AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" ); + AI_BACKWARD.LinkTo( scriptObject, "AI_BACKWARD" ); + AI_STRAFE_LEFT.LinkTo( scriptObject, "AI_STRAFE_LEFT" ); + AI_STRAFE_RIGHT.LinkTo( scriptObject, "AI_STRAFE_RIGHT" ); + AI_ATTACK_HELD.LinkTo( scriptObject, "AI_ATTACK_HELD" ); + AI_WEAPON_FIRED.LinkTo( scriptObject, "AI_WEAPON_FIRED" ); + AI_JUMP.LinkTo( scriptObject, "AI_JUMP" ); + AI_DEAD.LinkTo( scriptObject, "AI_DEAD" ); + AI_CROUCH.LinkTo( scriptObject, "AI_CROUCH" ); + AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" ); + AI_ONLADDER.LinkTo( scriptObject, "AI_ONLADDER" ); + AI_HARDLANDING.LinkTo( scriptObject, "AI_HARDLANDING" ); + AI_SOFTLANDING.LinkTo( scriptObject, "AI_SOFTLANDING" ); + AI_RUN.LinkTo( scriptObject, "AI_RUN" ); + AI_PAIN.LinkTo( scriptObject, "AI_PAIN" ); + AI_RELOAD.LinkTo( scriptObject, "AI_RELOAD" ); + AI_TELEPORT.LinkTo( scriptObject, "AI_TELEPORT" ); + AI_TURN_LEFT.LinkTo( scriptObject, "AI_TURN_LEFT" ); + AI_TURN_RIGHT.LinkTo( scriptObject, "AI_TURN_RIGHT" ); +} + +/* +============== +idPlayer::SetupWeaponEntity +============== +*/ +void idPlayer::SetupWeaponEntity() { + int w; + const char *weap; + + if ( weapon.GetEntity() ) { + // get rid of old weapon + weapon.GetEntity()->Clear(); + currentWeapon = -1; + } else if ( !common->IsClient() ) { + weapon = static_cast( gameLocal.SpawnEntityType( idWeapon::Type, NULL ) ); + weapon.GetEntity()->SetOwner( this ); + currentWeapon = -1; + + // flashlight + flashlight = static_cast( gameLocal.SpawnEntityType( idWeapon::Type, NULL ) ); + flashlight.GetEntity()->SetFlashlightOwner( this ); + //FlashlightOff(); + } + + for( w = 0; w < MAX_WEAPONS; w++ ) { + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( weap != NULL && *weap != NULL ) { + idWeapon::CacheWeapon( weap ); + } + } +} + +/* +============== +idPlayer::Init +============== +*/ +void idPlayer::Init() { + const char *value; + const idKeyValue *kv; + + noclip = false; + godmode = false; + + oldButtons = 0; + oldImpulseSequence = 0; + + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + weapon_soulcube = SlotForWeapon( "weapon_soulcube" ); + weapon_pda = SlotForWeapon( "weapon_pda" ); + weapon_fists = SlotForWeapon( "weapon_fists" ); + weapon_flashlight = SlotForWeapon( "weapon_flashlight" ); + weapon_chainsaw = SlotForWeapon( "weapon_chainsaw" ); + weapon_bloodstone = SlotForWeapon( "weapon_bloodstone_passive" ); + weapon_bloodstone_active1 = SlotForWeapon( "weapon_bloodstone_active1" ); + weapon_bloodstone_active2 = SlotForWeapon( "weapon_bloodstone_active2" ); + weapon_bloodstone_active3 = SlotForWeapon( "weapon_bloodstone_active3" ); + harvest_lock = false; + + lastDmgTime = 0; + lastArmorPulse = -10000; + lastHeartAdjust = 0; + lastHeartBeat = 0; + heartInfo.Init( 0, 0, 0, 0 ); + + bobCycle = 0; + bobFrac = 0.0f; + landChange = 0; + landTime = 0; + zoomFov.Init( 0, 0, 0, 0 ); + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + mountedObject = NULL; + if( enviroSuitLight.IsValid() ) { + enviroSuitLight.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + enviroSuitLight = NULL; + healthRecharge = false; + lastHealthRechargeTime = 0; + rechargeSpeed = 500; + new_g_damageScale = 1.f; + bloomEnabled = false; + bloomSpeed = 1.f; + bloomIntensity = -0.01f; + inventory.InitRechargeAmmo(this); + hudPowerup = -1; + lastHudPowerup = -1; + hudPowerupDuration = 0; + + currentLoggedAccel = 0; + + focusTime = 0; + focusGUIent = NULL; + focusUI = NULL; + focusCharacter = NULL; + talkCursor = 0; + focusVehicle = NULL; + + // remove any damage effects + playerView.ClearEffects(); + + // damage values + fl.takedamage = true; + ClearPain(); + + // restore persistent data + RestorePersistantInfo(); + + bobCycle = 0; + stamina = 0.0f; + healthPool = 0.0f; + nextHealthPulse = 0; + healthPulse = false; + nextHealthTake = 0; + healthTake = false; + + SetupWeaponEntity(); + currentWeapon = -1; + previousWeapon = -1; + + heartRate = BASE_HEARTRATE; + AdjustHeartRate( BASE_HEARTRATE, 0.0f, 0.0f, true ); + + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + + // set the pm_ cvars + if ( !common->IsMultiplayer() || common->IsServer() ) { + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + } + + // disable stamina on hell levels + if ( gameLocal.world && gameLocal.world->spawnArgs.GetBool( "no_stamina" ) ) { + pm_stamina.SetFloat( 0.0f ); + } + + // stamina always initialized to maximum + stamina = pm_stamina.GetFloat(); + + // air always initialized to maximum too + airMsec = pm_airMsec.GetFloat(); + airless = false; + + gibDeath = false; + gibsLaunched = false; + gibsDir.Zero(); + + // set the gravity + physicsObj.SetGravity( gameLocal.GetGravity() ); + + // start out standing + SetEyeHeight( pm_normalviewheight.GetFloat() ); + + stepUpTime = 0; + stepUpDelta = 0.0f; + viewBobAngles.Zero(); + viewBob.Zero(); + + value = spawnArgs.GetString( "model" ); + if ( value != NULL && ( *value != 0 ) ) { + SetModel( value ); + } + + if ( hud ) { + hud->SetCursorState( this, CURSOR_TALK, 0 ); + hud->SetCursorState( this, CURSOR_IN_COMBAT, 1 ); + hud->SetCursorState( this, CURSOR_ITEM, 0 ); + hud->SetCursorState( this, CURSOR_GRABBER, 0 ); + hud->SetCursorState( this, CURSOR_NONE, 0 ); + hud->UpdateCursorState(); + } + + if ( ( common->IsMultiplayer() || g_testDeath.GetBool() ) && skin ) { + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) { + skin = declManager->FindSkin( value ); + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } + + value = spawnArgs.GetString( "bone_hips", "" ); + hipJoint = animator.GetJointHandle( value ); + if ( hipJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'bone_hips' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "bone_chest", "" ); + chestJoint = animator.GetJointHandle( value ); + if ( chestJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'bone_chest' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "bone_head", "" ); + headJoint = animator.GetJointHandle( value ); + if ( headJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'bone_head' on '%s'", value, name.c_str() ); + } + + // initialize the script variables + AI_FORWARD = false; + AI_BACKWARD = false; + AI_STRAFE_LEFT = false; + AI_STRAFE_RIGHT = false; + AI_ATTACK_HELD = false; + AI_WEAPON_FIRED = false; + AI_JUMP = false; + AI_DEAD = false; + AI_CROUCH = false; + AI_ONGROUND = true; + AI_ONLADDER = false; + AI_HARDLANDING = false; + AI_SOFTLANDING = false; + AI_RUN = false; + AI_PAIN = false; + AI_RELOAD = false; + AI_TELEPORT = false; + AI_TURN_LEFT = false; + AI_TURN_RIGHT = false; + + // reset the script object + ConstructScriptObject(); + + // execute the script so the script object's constructor takes effect immediately + scriptThread->Execute(); + + forceScoreBoard = false; + + privateCameraView = NULL; + + lastSpectateChange = 0; + lastTeleFX = -9999; + + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + teleportKiller = -1; + leader = false; + + SetPrivateCameraView( NULL ); + + MPAim = -1; + lastMPAim = -1; + lastMPAimTime = 0; + MPAimFadeTime = 0; + MPAimHighlight = false; + + //isChatting = false; + + achievementManager.Init( this ); + + flashlightBattery = flashlight_batteryDrainTimeMS.GetInteger(); // fully charged + + aimAssist.Init( this ); + + // laser sight for 3DTV + memset( &laserSightRenderEntity, 0, sizeof( laserSightRenderEntity ) ); + laserSightRenderEntity.hModel = renderModelManager->FindModel( "_BEAM" ); + laserSightRenderEntity.customShader = declManager->FindMaterial( "stereoRenderLaserSight" ); +} + +/* +============== +idPlayer::Spawn + +Prepare any resources used by the player. +============== +*/ +void idPlayer::Spawn() { + idStr temp; + idBounds bounds; + + if ( entityNumber >= MAX_CLIENTS ) { + gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." ); + } + + // allow thinking during cinematics + cinematic = true; + + if ( common->IsMultiplayer() ) { + // always start in spectating state waiting to be spawned in + // do this before SetClipModel to get the right bounding box + spectating = true; + } + + // set our collision model + physicsObj.SetSelf( this ); + SetClipModel(); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + SetPhysics( &physicsObj ); + InitAASLocation(); + + skin = renderEntity.customSkin; + + // only the local player needs guis + if ( !common->IsMultiplayer() || IsLocallyControlled() ) { + + // load HUD + if ( hudManager != NULL ) { + hudManager->Initialize( "hud", common->SW() ); + hudManager->ActivateMenu( true ); + hud = hudManager->GetHud(); + } + + // load cursor + if ( spawnArgs.GetString( "cursor", "", temp ) ) { + cursor = uiManager->FindGui( temp, true, common->IsMultiplayer(), common->IsMultiplayer() ); + } + if ( cursor ) { + cursor->Activate( true, gameLocal.time ); + } + + if ( pdaMenu != NULL ) { + pdaMenu->Initialize( "pda", common->SW() ); + } + objectiveSystemOpen = false; + } + + if ( common->IsMultiplayer() && mpMessages == NULL ) { + mpMessages = new idSWF( "mp_messages", common->SW() ); + mpMessages->Activate( true ); + } + + SetLastHitTime( 0 ); + + // load the armor sound feedback + declManager->FindSound( "player_sounds_hitArmor" ); + + // set up conditions for animation + LinkScriptVariables(); + + animator.RemoveOriginOffset( true ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + // init the damage effects + playerView.SetPlayerEntity( this ); + + // supress model in non-player views, but allow it in mirrors and remote views + renderEntity.suppressSurfaceInViewID = entityNumber+1; + + // don't project shadow on self or weapon + renderEntity.noSelfShadow = true; + + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1; + headEnt->GetRenderEntity()->noSelfShadow = true; + } + + if ( common->IsMultiplayer() ) { + Init(); + Hide(); // properly hidden if starting as a spectator + if ( !common->IsClient() ) { + // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot + SetupWeaponEntity(); + SpawnFromSpawnSpot(); + forceRespawn = true; + wantSpectate = true; + assert( spectating ); + } + } else { + SetupWeaponEntity(); + SpawnFromSpawnSpot(); + } + + // trigger playtesting item gives, if we didn't get here from a previous level + // the devmap key will be set on the first devmap, but cleared on any level + // transitions + if ( !common->IsMultiplayer() && gameLocal.serverInfo.FindKey( "devmap" ) ) { + // fire a trigger with the name "devmap" + idEntity *ent = gameLocal.FindEntity( "devmap" ); + if ( ent ) { + ent->ActivateTargets( this ); + } + } + + if ( hud ) { + if ( weapon_soulcube > 0 && ( inventory.weapons & ( 1 << weapon_soulcube ) ) ) { + int max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" ); + if ( inventory.GetInventoryAmmoForType( idWeapon::GetAmmoNumForName( "ammo_souls" ) ) >= max_souls ) { + hud->SetShowSoulCubeOnLoad( true ); + } + } + } + + if ( GetPDA() ) { + // Add any emails from the inventory + for ( int i = 0; i < inventory.emails.Num(); i++ ) { + GetPDA()->AddEmail( inventory.emails[i] ); + } + GetPDA()->SetSecurity( idLocalization::GetString( "#str_00066" ) ); + } + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + hiddenWeapon = true; + if ( weapon.GetEntity() ) { + weapon.GetEntity()->LowerWeapon(); + } + idealWeapon = weapon_fists; + } else { + hiddenWeapon = false; + } + + UpdateHudWeapon(); + + tipUp = false; + objectiveUp = false; + + if ( inventory.levelTriggers.Num() ) { + PostEventMS( &EV_Player_LevelTrigger, 0 ); + } + + inventory.pdaOpened = false; + inventory.selPDA = 0; + + if ( !common->IsMultiplayer() ) { + int startingHealth = gameLocal.world->spawnArgs.GetInt( "startingHealth", health ); + if ( health > startingHealth ) { + health = startingHealth; + } + if ( g_skill.GetInteger() < 2 ) { + if ( health < 25 ) { + health = 25; + } + if ( g_useDynamicProtection.GetBool() ) { + new_g_damageScale = 1.0f; + } + } else { + new_g_damageScale = 1.0f; + g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); + if ( g_skill.GetInteger() == 3 ) { + nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000; + } + } + } + + //Setup the weapon toggle lists + const idKeyValue *kv; + kv = spawnArgs.MatchPrefix( "weapontoggle", NULL ); + while( kv ) { + WeaponToggle_t newToggle; + strcpy(newToggle.name, kv->GetKey().c_str()); + + idStr toggleData = kv->GetValue(); + + idLexer src; + idToken token; + src.LoadMemory(toggleData, toggleData.Length(), "toggleData"); + while(1) { + if(!src.ReadToken(&token)) { + break; + } + int index = atoi(token.c_str()); + newToggle.toggleList.Append(index); + + //Skip the , + src.ReadToken(&token); + } + newToggle.lastUsed = 0; + weaponToggles.Set(newToggle.name, newToggle); + + kv = spawnArgs.MatchPrefix( "weapontoggle", kv ); + } + + if( g_skill.GetInteger() >= 3 || cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + if(!WeaponAvailable("weapon_bloodstone_passive")) { + GiveInventoryItem("weapon_bloodstone_passive"); + } + if(!WeaponAvailable("weapon_bloodstone_active1")) { + GiveInventoryItem("weapon_bloodstone_active1"); + } + if(!WeaponAvailable("weapon_bloodstone_active2")) { + GiveInventoryItem("weapon_bloodstone_active2"); + } + if(!WeaponAvailable("weapon_bloodstone_active3")) { + GiveInventoryItem("weapon_bloodstone_active3"); + } + } + + bloomEnabled = false; + bloomSpeed = 1; + bloomIntensity = -0.01f; + + if ( g_demoMode.GetBool() && weapon.GetEntity() && weapon.GetEntity()->AmmoInClip() == 0 ) { + weapon.GetEntity()->ForceAmmoInClip(); + } + +} + +/* +============== +idPlayer::~idPlayer() + +Release any resources used by the player. +============== +*/ +idPlayer::~idPlayer() { + delete weapon.GetEntity(); + weapon = NULL; + + delete flashlight.GetEntity(); + flashlight = NULL; + + if ( enviroSuitLight.IsValid() ) { + enviroSuitLight.GetEntity()->ProcessEvent( &EV_Remove ); + } + // have to do this here, idMultiplayerGame::DisconnectClient() is too late + if ( common->IsMultiplayer() && gameLocal.mpGame.IsGametypeFlagBased() ) { + ReturnFlag(); + } + + delete hudManager; + hudManager = NULL; + + delete pdaMenu; + pdaMenu = NULL; + + delete mpMessages; + mpMessages = NULL; +} + +/* +=========== +idPlayer::Save +=========== +*/ +void idPlayer::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteUsercmd( usercmd ); + playerView.Save( savefile ); + + savefile->WriteBool( noclip ); + savefile->WriteBool( godmode ); + + // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame + savefile->WriteAngles( spawnAngles ); + savefile->WriteAngles( viewAngles ); + savefile->WriteAngles( cmdAngles ); + + savefile->WriteInt( buttonMask ); + savefile->WriteInt( oldButtons ); + savefile->WriteInt( oldImpulseSequence ); + + savefile->WriteInt( lastHitTime ); + savefile->WriteInt( lastSndHitTime ); + savefile->WriteInt( lastSavingThrowTime ); + + // idBoolFields don't need to be saved, just re-linked in Restore + + savefile->WriteObject( primaryObjective ); + inventory.Save( savefile ); + weapon.Save( savefile ); + + for ( int i = 0; i < NUM_QUICK_SLOTS; ++i ) { + savefile->WriteInt( quickSlot[ i ] ); + } + + savefile->WriteInt( weapon_soulcube ); + savefile->WriteInt( weapon_pda ); + savefile->WriteInt( weapon_fists ); + savefile->WriteInt( weapon_flashlight ); + savefile->WriteInt( weapon_chainsaw ); + savefile->WriteInt( weapon_bloodstone ); + savefile->WriteInt( weapon_bloodstone_active1 ); + savefile->WriteInt( weapon_bloodstone_active2 ); + savefile->WriteInt( weapon_bloodstone_active3 ); + savefile->WriteBool( harvest_lock ); + savefile->WriteInt( hudPowerup ); + savefile->WriteInt( lastHudPowerup ); + savefile->WriteInt( hudPowerupDuration ); + + + + savefile->WriteInt( heartRate ); + + savefile->WriteFloat( heartInfo.GetStartTime() ); + savefile->WriteFloat( heartInfo.GetDuration() ); + savefile->WriteFloat( heartInfo.GetStartValue() ); + savefile->WriteFloat( heartInfo.GetEndValue() ); + + savefile->WriteInt( lastHeartAdjust ); + savefile->WriteInt( lastHeartBeat ); + savefile->WriteInt( lastDmgTime ); + savefile->WriteInt( deathClearContentsTime ); + savefile->WriteBool( doingDeathSkin ); + savefile->WriteInt( lastArmorPulse ); + savefile->WriteFloat( stamina ); + savefile->WriteFloat( healthPool ); + savefile->WriteInt( nextHealthPulse ); + savefile->WriteBool( healthPulse ); + savefile->WriteInt( nextHealthTake ); + savefile->WriteBool( healthTake ); + + savefile->WriteBool( hiddenWeapon ); + soulCubeProjectile.Save( savefile ); + + savefile->WriteInt( spectator ); + savefile->WriteBool( forceScoreBoard ); + savefile->WriteBool( forceRespawn ); + savefile->WriteBool( spectating ); + savefile->WriteInt( lastSpectateTeleport ); + savefile->WriteBool( lastHitToggle ); + savefile->WriteBool( wantSpectate ); + savefile->WriteBool( weaponGone ); + savefile->WriteBool( useInitialSpawns ); + savefile->WriteInt( tourneyRank ); + savefile->WriteInt( tourneyLine ); + + teleportEntity.Save( savefile ); + savefile->WriteInt( teleportKiller ); + + savefile->WriteInt( minRespawnTime ); + savefile->WriteInt( maxRespawnTime ); + + savefile->WriteVec3( firstPersonViewOrigin ); + savefile->WriteMat3( firstPersonViewAxis ); + + // don't bother saving dragEntity since it's a dev tool + + savefile->WriteJoint( hipJoint ); + savefile->WriteJoint( chestJoint ); + savefile->WriteJoint( headJoint ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( aasLocation.Num() ); + for( i = 0; i < aasLocation.Num(); i++ ) { + savefile->WriteInt( aasLocation[ i ].areaNum ); + savefile->WriteVec3( aasLocation[ i ].pos ); + } + + savefile->WriteInt( bobFoot ); + savefile->WriteFloat( bobFrac ); + savefile->WriteFloat( bobfracsin ); + savefile->WriteInt( bobCycle ); + savefile->WriteFloat( xyspeed ); + savefile->WriteInt( stepUpTime ); + savefile->WriteFloat( stepUpDelta ); + savefile->WriteFloat( idealLegsYaw ); + savefile->WriteFloat( legsYaw ); + savefile->WriteBool( legsForward ); + savefile->WriteFloat( oldViewYaw ); + savefile->WriteAngles( viewBobAngles ); + savefile->WriteVec3( viewBob ); + savefile->WriteInt( landChange ); + savefile->WriteInt( landTime ); + + savefile->WriteInt( currentWeapon ); + savefile->WriteInt( idealWeapon.Get() ); + savefile->WriteInt( previousWeapon ); + savefile->WriteInt( weaponSwitchTime ); + savefile->WriteBool( weaponEnabled ); + + savefile->WriteInt( skinIndex ); + savefile->WriteSkin( skin ); + savefile->WriteSkin( powerUpSkin ); + + savefile->WriteInt( numProjectilesFired ); + savefile->WriteInt( numProjectileHits ); + + savefile->WriteBool( airless ); + savefile->WriteInt( airMsec ); + savefile->WriteInt( lastAirDamage ); + + savefile->WriteBool( gibDeath ); + savefile->WriteBool( gibsLaunched ); + savefile->WriteVec3( gibsDir ); + + savefile->WriteFloat( zoomFov.GetStartTime() ); + savefile->WriteFloat( zoomFov.GetDuration() ); + savefile->WriteFloat( zoomFov.GetStartValue() ); + savefile->WriteFloat( zoomFov.GetEndValue() ); + + savefile->WriteFloat( centerView.GetStartTime() ); + savefile->WriteFloat( centerView.GetDuration() ); + savefile->WriteFloat( centerView.GetStartValue() ); + savefile->WriteFloat( centerView.GetEndValue() ); + + savefile->WriteBool( fxFov ); + + savefile->WriteFloat( influenceFov ); + savefile->WriteInt( influenceActive ); + savefile->WriteFloat( influenceRadius ); + savefile->WriteObject( influenceEntity ); + savefile->WriteMaterial( influenceMaterial ); + savefile->WriteSkin( influenceSkin ); + + savefile->WriteObject( privateCameraView ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->WriteAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->WriteInt( loggedAccel[ i ].time ); + savefile->WriteVec3( loggedAccel[ i ].dir ); + } + savefile->WriteInt( currentLoggedAccel ); + + savefile->WriteObject( focusGUIent ); + // can't save focusUI + savefile->WriteObject( focusCharacter ); + savefile->WriteInt( talkCursor ); + savefile->WriteInt( focusTime ); + savefile->WriteObject( focusVehicle ); + savefile->WriteUserInterface( cursor, false ); + + savefile->WriteInt( oldMouseX ); + savefile->WriteInt( oldMouseY ); + + savefile->WriteBool( tipUp ); + savefile->WriteBool( objectiveUp ); + + savefile->WriteInt( lastDamageDef ); + savefile->WriteVec3( lastDamageDir ); + savefile->WriteInt( lastDamageLocation ); + savefile->WriteInt( smoothedFrame ); + savefile->WriteBool( smoothedOriginUpdated ); + savefile->WriteVec3( smoothedOrigin ); + savefile->WriteAngles( smoothedAngles ); + + savefile->WriteBool( respawning ); + savefile->WriteBool( leader ); + savefile->WriteInt( lastSpectateChange ); + savefile->WriteInt( lastTeleFX ); + + savefile->WriteFloat( pm_stamina.GetFloat() ); + + // TODO_SPARTY hook this up with new hud + //if ( hud ) { + // hud->SetStateString( "message", idLocalization::GetString( "#str_02916" ) ); + // hud->HandleNamedEvent( "Message" ); + //} + + savefile->WriteInt(weaponToggles.Num()); + for(i = 0; i < weaponToggles.Num(); i++) { + WeaponToggle_t* weaponToggle = weaponToggles.GetIndex(i); + savefile->WriteString(weaponToggle->name); + savefile->WriteInt(weaponToggle->toggleList.Num()); + for(int j = 0; j < weaponToggle->toggleList.Num(); j++) { + savefile->WriteInt(weaponToggle->toggleList[j]); + } + } + savefile->WriteObject( mountedObject ); + enviroSuitLight.Save( savefile ); + savefile->WriteBool( healthRecharge ); + savefile->WriteInt( lastHealthRechargeTime ); + savefile->WriteInt( rechargeSpeed ); + savefile->WriteFloat( new_g_damageScale ); + + savefile->WriteBool( bloomEnabled ); + savefile->WriteFloat( bloomSpeed ); + savefile->WriteFloat( bloomIntensity ); + + savefile->WriteObject( flashlight.GetEntity() ); + savefile->WriteInt( flashlightBattery ); + + achievementManager.Save( savefile ); + + savefile->WriteInt( playedTimeSecs ); + savefile->WriteInt( playedTimeResidual ); + + for ( int i=0; iWriteBool( pdaHasBeenRead[i] ); + } + + for ( int i=0; iWriteBool( videoHasBeenViewed[i] ); + } + + for ( int i=0; iWriteBool( audioHasBeenHeard[i][j] ); + } + } +} + +/* +=========== +idPlayer::Restore +=========== +*/ +void idPlayer::Restore( idRestoreGame *savefile ) { + int i; + int num; + float set; + + savefile->ReadUsercmd( usercmd ); + playerView.Restore( savefile ); + + savefile->ReadBool( noclip ); + savefile->ReadBool( godmode ); + + savefile->ReadAngles( spawnAngles ); + savefile->ReadAngles( viewAngles ); + savefile->ReadAngles( cmdAngles ); + + memset( usercmd.angles, 0, sizeof( usercmd.angles ) ); + SetViewAngles( viewAngles ); + spawnAnglesSet = true; + + savefile->ReadInt( buttonMask ); + savefile->ReadInt( oldButtons ); + savefile->ReadInt( oldImpulseSequence ); + + usercmd.impulseSequence = 0; + oldImpulseSequence = 0; + + savefile->ReadInt( lastHitTime ); + savefile->ReadInt( lastSndHitTime ); + savefile->ReadInt( lastSavingThrowTime ); + + // Re-link idBoolFields to the scriptObject, values will be restored in scriptObject's restore + LinkScriptVariables(); + + savefile->ReadObject( reinterpret_cast( primaryObjective ) ); + inventory.Restore( savefile ); + weapon.Restore( savefile ); + + if ( hudManager != NULL ) { + hudManager->Initialize( "hud", common->SW() ); + hudManager->ActivateMenu( true ); + hud = hudManager->GetHud(); + } + + if ( pdaMenu != NULL ) { + pdaMenu->Initialize( "pda", common->SW() ); + } + + for ( i = 0; i < inventory.emails.Num(); i++ ) { + GetPDA()->AddEmail( inventory.emails[i] ); + } + + + for ( int i = 0; i < NUM_QUICK_SLOTS; ++i ) { + savefile->ReadInt( quickSlot[ i ] ); + } + + savefile->ReadInt( weapon_soulcube ); + savefile->ReadInt( weapon_pda ); + savefile->ReadInt( weapon_fists ); + savefile->ReadInt( weapon_flashlight ); + savefile->ReadInt( weapon_chainsaw ); + savefile->ReadInt( weapon_bloodstone ); + savefile->ReadInt( weapon_bloodstone_active1 ); + savefile->ReadInt( weapon_bloodstone_active2 ); + savefile->ReadInt( weapon_bloodstone_active3 ); + + savefile->ReadBool( harvest_lock ); + savefile->ReadInt( hudPowerup ); + savefile->ReadInt( lastHudPowerup ); + savefile->ReadInt( hudPowerupDuration ); + + + + savefile->ReadInt( heartRate ); + + savefile->ReadFloat( set ); + heartInfo.SetStartTime( set ); + savefile->ReadFloat( set ); + heartInfo.SetDuration( set ); + savefile->ReadFloat( set ); + heartInfo.SetStartValue( set ); + savefile->ReadFloat( set ); + heartInfo.SetEndValue( set ); + + savefile->ReadInt( lastHeartAdjust ); + savefile->ReadInt( lastHeartBeat ); + savefile->ReadInt( lastDmgTime ); + savefile->ReadInt( deathClearContentsTime ); + savefile->ReadBool( doingDeathSkin ); + savefile->ReadInt( lastArmorPulse ); + savefile->ReadFloat( stamina ); + savefile->ReadFloat( healthPool ); + savefile->ReadInt( nextHealthPulse ); + savefile->ReadBool( healthPulse ); + savefile->ReadInt( nextHealthTake ); + savefile->ReadBool( healthTake ); + + savefile->ReadBool( hiddenWeapon ); + soulCubeProjectile.Restore( savefile ); + + savefile->ReadInt( spectator ); + savefile->ReadBool( forceScoreBoard ); + savefile->ReadBool( forceRespawn ); + savefile->ReadBool( spectating ); + savefile->ReadInt( lastSpectateTeleport ); + savefile->ReadBool( lastHitToggle ); + savefile->ReadBool( wantSpectate ); + savefile->ReadBool( weaponGone ); + savefile->ReadBool( useInitialSpawns ); + savefile->ReadInt( tourneyRank ); + savefile->ReadInt( tourneyLine ); + + teleportEntity.Restore( savefile ); + savefile->ReadInt( teleportKiller ); + + savefile->ReadInt( minRespawnTime ); + savefile->ReadInt( maxRespawnTime ); + + savefile->ReadVec3( firstPersonViewOrigin ); + savefile->ReadMat3( firstPersonViewAxis ); + + // don't bother saving dragEntity since it's a dev tool + dragEntity.Clear(); + + savefile->ReadJoint( hipJoint ); + savefile->ReadJoint( chestJoint ); + savefile->ReadJoint( headJoint ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( aasLocation[ i ].areaNum ); + savefile->ReadVec3( aasLocation[ i ].pos ); + } + + savefile->ReadInt( bobFoot ); + savefile->ReadFloat( bobFrac ); + savefile->ReadFloat( bobfracsin ); + savefile->ReadInt( bobCycle ); + savefile->ReadFloat( xyspeed ); + savefile->ReadInt( stepUpTime ); + savefile->ReadFloat( stepUpDelta ); + savefile->ReadFloat( idealLegsYaw ); + savefile->ReadFloat( legsYaw ); + savefile->ReadBool( legsForward ); + savefile->ReadFloat( oldViewYaw ); + savefile->ReadAngles( viewBobAngles ); + savefile->ReadVec3( viewBob ); + savefile->ReadInt( landChange ); + savefile->ReadInt( landTime ); + + savefile->ReadInt( currentWeapon ); + + int savedIdealWeapon = -1; + savefile->ReadInt( savedIdealWeapon ); + idealWeapon.Set( savedIdealWeapon ); + + savefile->ReadInt( previousWeapon ); + savefile->ReadInt( weaponSwitchTime ); + savefile->ReadBool( weaponEnabled ); + + savefile->ReadInt( skinIndex ); + savefile->ReadSkin( skin ); + savefile->ReadSkin( powerUpSkin ); + + savefile->ReadInt( numProjectilesFired ); + savefile->ReadInt( numProjectileHits ); + + savefile->ReadBool( airless ); + savefile->ReadInt( airMsec ); + savefile->ReadInt( lastAirDamage ); + + savefile->ReadBool( gibDeath ); + savefile->ReadBool( gibsLaunched ); + savefile->ReadVec3( gibsDir ); + + savefile->ReadFloat( set ); + zoomFov.SetStartTime( set ); + savefile->ReadFloat( set ); + zoomFov.SetDuration( set ); + savefile->ReadFloat( set ); + zoomFov.SetStartValue( set ); + savefile->ReadFloat( set ); + zoomFov.SetEndValue( set ); + + savefile->ReadFloat( set ); + centerView.SetStartTime( set ); + savefile->ReadFloat( set ); + centerView.SetDuration( set ); + savefile->ReadFloat( set ); + centerView.SetStartValue( set ); + savefile->ReadFloat( set ); + centerView.SetEndValue( set ); + + savefile->ReadBool( fxFov ); + + savefile->ReadFloat( influenceFov ); + savefile->ReadInt( influenceActive ); + savefile->ReadFloat( influenceRadius ); + savefile->ReadObject( reinterpret_cast( influenceEntity ) ); + savefile->ReadMaterial( influenceMaterial ); + savefile->ReadSkin( influenceSkin ); + + savefile->ReadObject( reinterpret_cast( privateCameraView ) ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->ReadAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->ReadInt( loggedAccel[ i ].time ); + savefile->ReadVec3( loggedAccel[ i ].dir ); + } + savefile->ReadInt( currentLoggedAccel ); + + savefile->ReadObject( reinterpret_cast( focusGUIent ) ); + // can't save focusUI + focusUI = NULL; + savefile->ReadObject( reinterpret_cast( focusCharacter ) ); + savefile->ReadInt( talkCursor ); + savefile->ReadInt( focusTime ); + savefile->ReadObject( reinterpret_cast( focusVehicle ) ); + savefile->ReadUserInterface( cursor ); + + savefile->ReadInt( oldMouseX ); + savefile->ReadInt( oldMouseY ); + + savefile->ReadBool( tipUp ); + savefile->ReadBool( objectiveUp ); + + savefile->ReadInt( lastDamageDef ); + savefile->ReadVec3( lastDamageDir ); + savefile->ReadInt( lastDamageLocation ); + savefile->ReadInt( smoothedFrame ); + savefile->ReadBool( smoothedOriginUpdated ); + savefile->ReadVec3( smoothedOrigin ); + savefile->ReadAngles( smoothedAngles ); + + savefile->ReadBool( respawning ); + savefile->ReadBool( leader ); + savefile->ReadInt( lastSpectateChange ); + savefile->ReadInt( lastTeleFX ); + + // set the pm_ cvars + const idKeyValue *kv; + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + + savefile->ReadFloat( set ); + pm_stamina.SetFloat( set ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + int weaponToggleCount; + savefile->ReadInt(weaponToggleCount); + for(i = 0; i < weaponToggleCount; i++) { + WeaponToggle_t newToggle; + memset(&newToggle, 0, sizeof(newToggle)); + + idStr name; + savefile->ReadString(name); + strcpy(newToggle.name, name.c_str()); + + int indexCount; + savefile->ReadInt(indexCount); + for(int j = 0; j < indexCount; j++) { + int temp; + savefile->ReadInt(temp); + newToggle.toggleList.Append(temp); + } + newToggle.lastUsed = 0; + weaponToggles.Set(newToggle.name, newToggle); + } + savefile->ReadObject(reinterpret_cast(mountedObject)); + enviroSuitLight.Restore( savefile ); + savefile->ReadBool( healthRecharge ); + savefile->ReadInt( lastHealthRechargeTime ); + savefile->ReadInt( rechargeSpeed ); + savefile->ReadFloat( new_g_damageScale ); + + savefile->ReadBool( bloomEnabled ); + savefile->ReadFloat( bloomSpeed ); + savefile->ReadFloat( bloomIntensity ); + + // flashlight + idWeapon *tempWeapon; + savefile->ReadObject( reinterpret_cast( tempWeapon ) ); + tempWeapon->SetIsPlayerFlashlight( true ); + flashlight = tempWeapon; + savefile->ReadInt( flashlightBattery ); + + achievementManager.Restore( savefile ); + + savefile->ReadInt( playedTimeSecs ); + savefile->ReadInt( playedTimeResidual ); + + aimAssist.Init( this ); + + laserSightHandle = -1; + + // re-init the laser model + memset( &laserSightRenderEntity, 0, sizeof( laserSightRenderEntity ) ); + laserSightRenderEntity.hModel = renderModelManager->FindModel( "_BEAM" ); + laserSightRenderEntity.customShader = declManager->FindMaterial( "stereoRenderLaserSight" ); + + for ( int i=0; iReadBool( pdaHasBeenRead[i] ); + } + + for ( int i=0; iReadBool( videoHasBeenViewed[i] ); + } + + for ( int i=0; iReadBool( audioHasBeenHeard[i][j] ); + } + } + + // Update the soul cube HUD indicator + if ( hud ) { + if ( weapon_soulcube > 0 && ( inventory.weapons & ( 1 << weapon_soulcube ) ) ) { + int max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" ); + if ( inventory.GetInventoryAmmoForType( idWeapon::GetAmmoNumForName( "ammo_souls" ) ) >= max_souls ) { + hud->SetShowSoulCubeOnLoad( true ); + } + } + } + +} + +/* +=============== +idPlayer::PrepareForRestart +================ +*/ +void idPlayer::PrepareForRestart() { + ClearPowerUps(); + + if( common->IsClient() == false ) { + ServerSpectate( true ); + } + + forceRespawn = true; + + // Confirm reset hud states + DropFlag(); + + if ( hud ) { + hud->SetFlagState( 0, 0 ); + hud->SetFlagState( 1, 0 ); + } + + // we will be restarting program, clear the client entities from program-related things first + ShutdownThreads(); + + // the sound world is going to be cleared, don't keep references to emitters + FreeSoundEmitter( false ); +} + +/* +=============== +idPlayer::Restart +================ +*/ +void idPlayer::Restart() { + idActor::Restart(); + + // client needs to setup the animation script object again + if ( common->IsClient() ) { + // Make sure the weapon spawnId gets re-linked on the next snapshot. + // Otherwise, its owner might not be set after the map restart, which causes asserts and crashes. + weapon = NULL; + flashlight = NULL; + enviroSuitLight = NULL; + Init(); + } else { + // choose a random spot and prepare the point of view in case player is left spectating + assert( spectating ); + SpawnFromSpawnSpot(); + } + + useInitialSpawns = true; + UpdateSkinSetup(); +} + +/* +=============== +idPlayer::ServerSpectate +================ +*/ +void idPlayer::ServerSpectate( bool spectate ) { + assert( !common->IsClient() ); + + if ( spectating != spectate ) { + Spectate( spectate ); + if ( !spectate ) { + // When coming out of spectate, join the team with the least number of players + if ( gameLocal.mpGame.IsGametypeTeamBased() ) { + int teamCounts[2] = { 0, 0 }; + gameLocal.mpGame.NumActualClients( false, teamCounts ); + teamCounts[team]--; + if ( teamCounts[0] < teamCounts[1] ) { + team = 0; + } else if ( teamCounts[1] < teamCounts[0] ) { + team = 1; + } + gameLocal.mpGame.SwitchToTeam( entityNumber, -1, team ); + } + if ( gameLocal.gameType == GAME_DM ) { + // make sure the scores are reset so you can't exploit by spectating and entering the game back + // other game types don't matter, as you either can't join back, or it's team scores + gameLocal.mpGame.ClearFrags( entityNumber ); + } + } + } + if ( !spectate ) { + SpawnFromSpawnSpot(); + } + + // drop the flag if player was carrying it + if ( spectate && common->IsMultiplayer() && gameLocal.mpGame.IsGametypeFlagBased() && + carryingFlag ) + { + DropFlag(); + } +} + +/* +=========== +idPlayer::SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +void idPlayer::SelectInitialSpawnPoint( idVec3 &origin, idAngles &angles ) { + idEntity *spot; + idStr skin; + + spot = gameLocal.SelectInitialSpawnPoint( this ); + + // set the player skin from the spawn location + if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) { + spawnArgs.Set( "spawn_skin", skin ); + } + + // activate the spawn locations targets + spot->PostEventMS( &EV_ActivateTargets, 0, this ); + + origin = spot->GetPhysics()->GetOrigin(); + origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor + angles = spot->GetPhysics()->GetAxis().ToAngles(); +} + +/* +=========== +idPlayer::SpawnFromSpawnSpot + +Chooses a spawn location and spawns the player +============ +*/ +void idPlayer::SpawnFromSpawnSpot() { + idVec3 spawn_origin; + idAngles spawn_angles; + + SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + SpawnToPoint( spawn_origin, spawn_angles ); +} + +/* +=========== +idPlayer::SpawnToPoint + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState + +when called here with spectating set to true, just place yourself and init +============ +*/ +void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) { + idVec3 spec_origin; + + assert( !common->IsClient() ); + + respawning = true; + + Init(); + + fl.noknockback = false; + + // stop any ragdolls being used + StopRagdoll(); + + // set back the player physics + SetPhysics( &physicsObj ); + + physicsObj.SetClipModelAxis(); + physicsObj.EnableClip(); + + if ( !spectating ) { + SetCombatContents( true ); + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + // setup our initial view + if ( !spectating ) { + SetOrigin( spawn_origin ); + } else { + spec_origin = spawn_origin; + spec_origin[ 2 ] += pm_normalheight.GetFloat(); + spec_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spec_origin ); + } + + // if this is the first spawn of the map, we don't have a usercmd yet, + // so the delta angles won't be correct. This will be fixed on the first think. + viewAngles = ang_zero; + SetDeltaViewAngles( ang_zero ); + SetViewAngles( spawn_angles ); + spawnAngles = spawn_angles; + spawnAnglesSet = false; + + legsForward = true; + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + + if ( spectating ) { + Hide(); + } else { + Show(); + } + + if ( common->IsMultiplayer() ) { + if ( !spectating ) { + // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof' + if ( lastTeleFX < gameLocal.time - 1000 ) { + idEntityFx::StartFx( spawnArgs.GetString( "fx_spawn" ), &spawn_origin, NULL, this, true ); + lastTeleFX = gameLocal.time; + } + } + AI_TELEPORT = true; + } else { + AI_TELEPORT = false; + } + + // kill anything at the new position + if ( !spectating ) { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it + gameLocal.KillBox( this ); + } + + // don't allow full run speed for a bit + physicsObj.SetKnockBack( 100 ); + + // set our respawn time and buttons so that if we're killed we don't respawn immediately + minRespawnTime = gameLocal.time; + maxRespawnTime = gameLocal.time; + if ( !spectating ) { + forceRespawn = false; + } + + Respawn_Shared(); + + privateCameraView = NULL; + + BecomeActive( TH_THINK ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + Think(); + + respawning = false; + lastManOver = false; + lastManPlayAgain = false; + isTelefragged = false; +} + +/* +=============== +idPlayer::Respawn_Shared +Called on server and client players when they respawn (including on initial spawn) +=============== +*/ +void idPlayer::Respawn_Shared() { + respawn_netEvent.Set(); + + serverOverridePositionTime = gameLocal.GetServerGameTimeMs(); + + // Remove the hud respawn message. + HideRespawnHudMessage(); + + FlashlightOff(); +} + +/* +=============== +idPlayer::SavePersistantInfo + +Saves any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::SavePersistantInfo() { + idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber]; + + playerInfo.Clear(); + inventory.GetPersistantData( playerInfo ); + playerInfo.SetInt( "health", health ); + playerInfo.SetInt( "current_weapon", currentWeapon ); + playerInfo.SetInt( "playedTime", playedTimeSecs ); + + achievementManager.SavePersistentData( playerInfo ); +} + +/* +=============== +idPlayer::RestorePersistantInfo + +Restores any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::RestorePersistantInfo() { + if ( common->IsMultiplayer() || g_demoMode.GetBool() ) { + gameLocal.persistentPlayerInfo[entityNumber].Clear(); + } + + spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] ); + + inventory.RestoreInventory( this, spawnArgs ); + health = spawnArgs.GetInt( "health", "100" ); + idealWeapon = spawnArgs.GetInt( "current_weapon", "1" ); + + playedTimeSecs = spawnArgs.GetInt( "playedTime" ); + + achievementManager.RestorePersistentData( spawnArgs ); +} + +/* +============== +idPlayer::UpdateSkinSetup +============== +*/ +void idPlayer::UpdateSkinSetup() { + if ( !common->IsMultiplayer() ) { + return; + } + + if ( gameLocal.mpGame.IsGametypeTeamBased() ) { /* CTF */ + skinIndex = team + 1; + } else { + // Each player will now have their Skin Index Reflect their entity number ( host = 0, client 1 = 1, client 2 = 2 etc ) + skinIndex = entityNumber; // session->GetActingGameStateLobbyBase().GetLobbyUserSkinIndex( gameLocal.lobbyUserIDs[entityNumber] ); + } + const char * baseSkinName = gameLocal.mpGame.GetSkinName( skinIndex ); + skin = declManager->FindSkin( baseSkinName, false ); + if ( PowerUpActive( BERSERK ) ) { + idStr powerSkinName = baseSkinName; + powerSkinName.Append( "_berserk" ); + powerUpSkin = declManager->FindSkin( powerSkinName ); + } + else if ( PowerUpActive( INVULNERABILITY ) ) { + idStr powerSkinName = baseSkinName; + powerSkinName.Append( "_invuln" ); + powerUpSkin = declManager->FindSkin( powerSkinName ); + } + else if ( PowerUpActive( INVISIBILITY ) ) { + const char * invisibleSkin = ""; + spawnArgs.GetString( "skin_invisibility", "", &invisibleSkin ); + powerUpSkin = declManager->FindSkin( invisibleSkin ); + } +} + +/* +=============== +idPlayer::UpdateHudStats +=============== +*/ +void idPlayer::UpdateHudStats( idMenuHandler_HUD * _hudManager ) { + + if ( _hudManager && _hudManager->GetHud() ) { + + idMenuScreen_HUD * hud = _hudManager->GetHud(); + hud->UpdateHealthArmor( this ); + hud->UpdateStamina( this ); + hud->UpdateWeaponInfo( this ); + + if ( inventory.weaponPulse ) { + UpdateHudWeapon(); + inventory.weaponPulse = false; + } + + if ( gameLocal.mpGame.IsGametypeFlagBased() ) + { + hud->SetFlagState( 0, gameLocal.mpGame.GetFlagStatus( 0 ) ); + hud->SetFlagState( 1, gameLocal.mpGame.GetFlagStatus( 1 ) ); + + hud->SetTeamScore( 0, gameLocal.mpGame.GetFlagPoints( 0 ) ); + hud->SetTeamScore( 1, gameLocal.mpGame.GetFlagPoints( 1 ) ); + + hud->SetTeam( team ); + } + + } +} + +/* +=============== +idPlayer::UpdateHudWeapon +=============== +*/ +void idPlayer::UpdateHudWeapon( bool flashWeapon ) { + + idMenuScreen_HUD * curDisplay = hud; + idPlayer *p = this; + if ( gameLocal.GetLocalClientNum() >= 0 && gameLocal.entities[ gameLocal.GetLocalClientNum() ] && gameLocal.entities[ gameLocal.GetLocalClientNum() ]->IsType( idPlayer::Type ) ) { + p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetLocalClientNum() ] ); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud ); + curDisplay = p->hud; + } + } + + if ( !curDisplay ) { + return; + } + + curDisplay->UpdateWeaponStates( p, flashWeapon ); +} + +/* +=============== +idPlayer::UpdateHudWeapon +=============== +*/ +void idPlayer::UpdateChattingHud() { + + idMenuScreen_HUD * curDisplay = hud; + idPlayer *p = this; + if ( gameLocal.GetLocalClientNum() >= 0 && gameLocal.entities[ gameLocal.GetLocalClientNum() ] && gameLocal.entities[ gameLocal.GetLocalClientNum() ]->IsType( idPlayer::Type ) ) { + p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetLocalClientNum() ] ); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud ); + curDisplay = p->hud; + } + } + + if ( !curDisplay ) { + return; + } + + curDisplay->UpdateChattingHud( p ); +} + + +/* +======================== +idMenuScreen_Scoreboard::UpdateSpectating +======================== +*/ +void idPlayer::UpdateSpectatingText() { + + idSWF * spectatorMessages = mpMessages; + idPlayer *p = this; + if ( gameLocal.GetLocalClientNum() >= 0 && gameLocal.entities[ gameLocal.GetLocalClientNum() ] && gameLocal.entities[ gameLocal.GetLocalClientNum() ]->IsType( idPlayer::Type ) ) { + p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetLocalClientNum() ] ); + if ( p && p->spectating ) { + spectatorMessages = p->mpMessages; + } + } + + if ( !spectatorMessages || !spectatorMessages->IsActive() ) { + return; + } + + idPlayer * viewPlayer = static_cast( gameLocal.entities[ p->spectator ] ); + if ( viewPlayer == NULL ) { + return; + } + + idStr spectatetext[ 2 ]; + if ( !gameLocal.mpGame.IsScoreboardActive() ) { + gameLocal.mpGame.GetSpectateText( p, spectatetext, false ); + } + + idSWFScriptObject & root = spectatorMessages->GetRootObject(); + idSWFTextInstance * txtVal = root.GetNestedText( "txtSpectating" ); + if ( txtVal != NULL ) { + txtVal->tooltip = true; + txtVal->SetText( spectatetext[0] ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + txtVal = root.GetNestedText( "txtFollow" ); + if ( txtVal != NULL ) { + txtVal->SetText( spectatetext[1] ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } +} + +/* +=============== +idPlayer::UpdateMpMessages +=============== +*/ +void idPlayer::AddChatMessage( int index, int alpha, const idStr & message ) { + + if ( mpMessages == NULL || !mpMessages->IsActive() ) { + return; + } + + idSWFScriptObject * mpChat = mpMessages->GetRootObject().GetNestedObj( "_left", "mpChat" ); + + idSWFSpriteInstance * info = mpChat->GetNestedSprite( va( "info%i", index ) ); + idSWFTextInstance * txtVal = mpChat->GetNestedText( va( "info%i", index ), "txtVal" ); + if ( info ) { + info->SetVisible( true ); + if ( alpha >= 4 ) { + info->SetAlpha( 1.0f ); + } else if ( alpha == 3 ) { + info->SetAlpha( 0.875f ); + } else if ( alpha == 2 ) { + info->SetAlpha( 0.75f ); + } else if ( alpha == 1 ) { + info->SetAlpha( 0.625f ); + } else { + info->SetAlpha( 0.5f ); + } + } + + if ( txtVal ) { + txtVal->SetIgnoreColor( false ); + txtVal->SetText( message ); + txtVal->SetStrokeInfo( true, 0.9f, 1.75f ); + } + +} + +/* +=============== +idPlayer::UpdateMpMessages +=============== +*/ +void idPlayer::ClearChatMessage( int index ) { + + if ( mpMessages == NULL || !mpMessages->IsActive() ) { + return; + } + + idSWFScriptObject * mpChat = mpMessages->GetRootObject().GetNestedObj( "_left", "mpChat" ); + + idSWFSpriteInstance * info = mpChat->GetNestedSprite( va( "info%i", index ) ); + idSWFTextInstance * txtVal = mpChat->GetNestedText( va( "info%i", index ), "txtVal" ); + if ( info ) { + info->SetVisible( false ); + } + + if ( txtVal ) { + txtVal->SetText( "" ); + } + +} + +/* +=============== +idPlayer::DrawHUD +=============== +*/ +void idPlayer::DrawHUD( idMenuHandler_HUD * _hudManager ) { + SCOPED_PROFILE_EVENT( "idPlayer::DrawHUD" ); + + if ( !weapon.GetEntity() || influenceActive != INFLUENCE_NONE || privateCameraView || gameLocal.GetCamera() || !g_showHud.GetBool() ) { + return; + } + + if ( common->IsMultiplayer() ) { + UpdateChattingHud(); + UpdateSpectatingText(); + } + + // Always draw the local client's messages so that chat works correctly while spectating another player. + idPlayer * localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetLocalClientNum() ] ); + + if ( localPlayer != NULL && localPlayer->mpMessages != NULL ) { + localPlayer->mpMessages->Render( renderSystem, Sys_Milliseconds() ); + } + + + UpdateHudStats( _hudManager ); + + if ( spectating ) { + return; + } + + if ( _hudManager ) { + _hudManager->Update(); + } + + weapon.GetEntity()->UpdateGUI(); + + // weapon targeting crosshair + if ( !GuiActive() ) { + // don't show the 2D crosshair in stereo rendering, use the + // laser sight model instead + if ( _hudManager && _hudManager->GetHud() ) { + + idMenuScreen_HUD * hud = _hudManager->GetHud(); + + if ( weapon.GetEntity()->ShowCrosshair() && !IsGameStereoRendered() ) { + if ( weapon.GetEntity()->GetGrabberState() == 1 || weapon.GetEntity()->GetGrabberState() == 2 ) { + hud->SetCursorState( this, CURSOR_GRABBER, 1 ); + hud->SetCursorState( this, CURSOR_IN_COMBAT, 0 ); + } else { + hud->SetCursorState( this, CURSOR_GRABBER, 0 ); + hud->SetCursorState( this, CURSOR_IN_COMBAT, 1 ); + } + } else { + hud->SetCursorState( this, CURSOR_NONE, 1 ); + } + + hud->UpdateCursorState(); + + } + } else if ( _hudManager && _hudManager->GetHud() ) { + + idMenuScreen_HUD * hud = _hudManager->GetHud(); + + hud->SetCursorState( this, CURSOR_NONE, 1 ); + hud->UpdateCursorState(); + } +} + +/* +=============== +idPlayer::EnterCinematic +=============== +*/ +void idPlayer::EnterCinematic() { + if ( PowerUpActive( HELLTIME ) ) { + StopHelltime(); + } + + Hide(); + StopSound( SND_CHANNEL_PDA_AUDIO, false ); + StopSound( SND_CHANNEL_PDA_VIDEO, false ); + + if ( hudManager ) { + hudManager->SetRadioMessage( false ); + } + physicsObj.SetLinearVelocity( vec3_origin ); + + SetState( "EnterCinematic" ); + UpdateScript(); + + if ( weaponEnabled && weapon.GetEntity() ) { + weapon.GetEntity()->EnterCinematic(); + } + if ( flashlight.GetEntity() ) { + flashlight.GetEntity()->EnterCinematic(); + } + + AI_FORWARD = false; + AI_BACKWARD = false; + AI_STRAFE_LEFT = false; + AI_STRAFE_RIGHT = false; + AI_RUN = false; + AI_ATTACK_HELD = false; + AI_WEAPON_FIRED = false; + AI_JUMP = false; + AI_CROUCH = false; + AI_ONGROUND = true; + AI_ONLADDER = false; + AI_DEAD = ( health <= 0 ); + AI_RUN = false; + AI_PAIN = false; + AI_HARDLANDING = false; + AI_SOFTLANDING = false; + AI_RELOAD = false; + AI_TELEPORT = false; + AI_TURN_LEFT = false; + AI_TURN_RIGHT = false; +} + +/* +=============== +idPlayer::ExitCinematic +=============== +*/ +void idPlayer::ExitCinematic() { + Show(); + + if ( weaponEnabled && weapon.GetEntity() ) { + weapon.GetEntity()->ExitCinematic(); + } + if ( flashlight.GetEntity() ) { + flashlight.GetEntity()->ExitCinematic(); + } + + // long cinematics would have surpassed the healthTakeTime, causing the player to take damage + // immediately after the cinematic ends. Instead we start the healthTake cooldown again once + // the cinematic ends. + if ( g_skill.GetInteger() == 3 ) { + nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000; + } + + SetState( "ExitCinematic" ); + UpdateScript(); +} + +/* +===================== +idPlayer::UpdateConditions +===================== +*/ +void idPlayer::UpdateConditions() { + idVec3 velocity; + float fallspeed; + float forwardspeed; + float sidespeed; + + // minus the push velocity to avoid playing the walking animation and sounds when riding a mover + velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity(); + fallspeed = velocity * physicsObj.GetGravityNormal(); + + if ( influenceActive ) { + AI_FORWARD = false; + AI_BACKWARD = false; + AI_STRAFE_LEFT = false; + AI_STRAFE_RIGHT = false; + } else if ( gameLocal.time - lastDmgTime < 500 ) { + forwardspeed = velocity * viewAxis[ 0 ]; + sidespeed = velocity * viewAxis[ 1 ]; + AI_FORWARD = AI_ONGROUND && ( forwardspeed > 20.01f ); + AI_BACKWARD = AI_ONGROUND && ( forwardspeed < -20.01f ); + AI_STRAFE_LEFT = AI_ONGROUND && ( sidespeed > 20.01f ); + AI_STRAFE_RIGHT = AI_ONGROUND && ( sidespeed < -20.01f ); + } else if ( xyspeed > MIN_BOB_SPEED ) { + AI_FORWARD = AI_ONGROUND && ( usercmd.forwardmove > 0 ); + AI_BACKWARD = AI_ONGROUND && ( usercmd.forwardmove < 0 ); + AI_STRAFE_LEFT = AI_ONGROUND && ( usercmd.rightmove < 0 ); + AI_STRAFE_RIGHT = AI_ONGROUND && ( usercmd.rightmove > 0 ); + } else { + AI_FORWARD = false; + AI_BACKWARD = false; + AI_STRAFE_LEFT = false; + AI_STRAFE_RIGHT = false; + } + + AI_RUN = ( usercmd.buttons & BUTTON_RUN ) && ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ); + AI_DEAD = ( health <= 0 ); +} + +/* +================== +WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) { + // force a blink + blink_time = 0; + + // play the fire animation + AI_WEAPON_FIRED = true; + + // update view feedback + playerView.WeaponFireFeedback( weaponDef ); + + // shake controller + float highMagnitude = weaponDef->GetFloat( "controllerShakeHighMag" ); + int highDuration = weaponDef->GetInt( "controllerShakeHighTime" ); + float lowMagnitude = weaponDef->GetFloat( "controllerShakeLowMag" ); + int lowDuration = weaponDef->GetInt( "controllerShakeLowTime" ); + //const char *name = weaponDef->GetString( "inv_name" ); + + if( IsLocallyControlled() ) { + SetControllerShake( highMagnitude, highDuration, lowMagnitude, lowDuration ); + } +} + +/* +=============== +idPlayer::StopFiring +=============== +*/ +void idPlayer::StopFiring() { + AI_ATTACK_HELD = false; + AI_WEAPON_FIRED = false; + AI_RELOAD = false; + if ( weapon.GetEntity() ) { + weapon.GetEntity()->EndAttack(); + } +} + +/* +=============== +idPlayer::FireWeapon +=============== +*/ +idCVar g_infiniteAmmo( "g_infiniteAmmo", "0", CVAR_GAME | CVAR_BOOL, "infinite ammo" ); +extern idCVar ui_autoSwitch; +void idPlayer::FireWeapon() { + idMat3 axis; + idVec3 muzzle; + + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) { + return; + } + } + + if ( !hiddenWeapon && weapon.GetEntity()->IsReady() ) { + if ( g_infiniteAmmo.GetBool() || weapon.GetEntity()->AmmoInClip() || weapon.GetEntity()->AmmoAvailable() ) { + AI_ATTACK_HELD = true; + weapon.GetEntity()->BeginAttack(); + if ( ( weapon_soulcube >= 0 ) && ( currentWeapon == weapon_soulcube ) ) { + if ( hud ) { + hud->UpdateSoulCube( false ); + } + SelectWeapon( previousWeapon, false ); + } + if( (weapon_bloodstone >= 0) && (currentWeapon == weapon_bloodstone) && inventory.weapons & ( 1 << weapon_bloodstone_active1 ) && weapon.GetEntity()->GetStatus() == WP_READY) { + // tell it to switch to the previous weapon. Only do this once to prevent + // weapon toggling messing up the previous weapon + if(idealWeapon == weapon_bloodstone) { + if(previousWeapon == weapon_bloodstone || previousWeapon == -1) { + NextBestWeapon(); + } else { + //Since this is a toggle weapon just select itself and it will toggle to the last weapon + SelectWeapon( weapon_bloodstone, false ); + } + } + } + } else { + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + lobbyUserID_t & lobbyUserID = gameLocal.lobbyUserIDs[ entityNumber ]; + bool autoSwitch = lobby.GetLobbyUserWeaponAutoSwitch( lobbyUserID ); + if ( !autoSwitch ) { + return; + } + + // update our ammo clip in our inventory + if ( ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.SetClipAmmoForWeapon( currentWeapon, weapon.GetEntity()->AmmoInClip() ); + } + + NextBestWeapon(); + } + } + + + if ( tipUp ) { + HideTip(); + } + + if ( objectiveUp ) { + HideObjective(); + } +} + +/* +=============== +idPlayer::CacheWeapons +=============== +*/ +void idPlayer::CacheWeapons() { + idStr weap; + int w; + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + for( w = 0; w < MAX_WEAPONS; w++ ) { + if ( inventory.weapons & ( 1 << w ) ) { + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( weap != "" ) { + idWeapon::CacheWeapon( weap ); + } else { + inventory.weapons &= ~( 1 << w ); + } + } + } +} + +/* +=============== +idPlayer::SetQuickSlot +=============== +*/ +void idPlayer::SetQuickSlot( int index, int val ) { + if ( index >= NUM_QUICK_SLOTS || index < 0 ) { + return; + } + + quickSlot[ index ] = val; +} + +/* +=============== +idPlayer::GetQuickSlot +=============== +*/ +int idPlayer::GetQuickSlot( int index ) { + + if ( index >= NUM_QUICK_SLOTS || index < 0 ) { + return -1; + } + + return quickSlot[ index ]; +} + +/* +=============== +idPlayer::Give +=============== +*/ +bool idPlayer::Give( const char *statname, const char *value, unsigned int giveFlags ) { + int amount; + + if ( AI_DEAD ) { + return false; + } + + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= inventory.maxHealth ) { + return false; + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + } + healthPulse = true; + } + } + } else if ( !idStr::Icmp( statname, "stamina" ) ) { + if ( stamina >= 100 ) { + return false; + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + stamina += atof( value ); + if ( stamina > 100 ) { + stamina = 100; + } + } + } else if ( !idStr::Icmp( statname, "heartRate" ) ) { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + heartRate += atoi( value ); + if ( heartRate > MAX_HEARTRATE ) { + heartRate = MAX_HEARTRATE; + } + } + } else if ( !idStr::Icmp( statname, "air" ) ) { + if ( airMsec >= pm_airMsec.GetInteger() ) { + return false; + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + airMsec += pm_airMsec.GetInteger() * atoi( value ) / 100; + if ( airMsec > pm_airMsec.GetInteger() ) { + airMsec = pm_airMsec.GetInteger(); + } + } + } else if ( !idStr::Icmp( statname, "enviroTime" ) ) { + if ( ( giveFlags & ITEM_GIVE_UPDATE_STATE ) && PowerUpActive( ENVIROTIME ) ) { + inventory.powerupEndTime[ ENVIROTIME ] += (atof(value) * 1000); + } else { + GivePowerUp( ENVIROTIME, atoi(value)*1000, giveFlags ); + } + } else { + bool ret = inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true, giveFlags ); + return ret; + } + return true; +} + + +/* +=============== +idPlayer::GiveHealthPool + +adds health to the player health pool +=============== +*/ +void idPlayer::GiveHealthPool( float amt ) { + + if ( AI_DEAD ) { + return; + } + + if ( health > 0 ) { + healthPool += amt; + if ( healthPool > inventory.maxHealth - health ) { + healthPool = inventory.maxHealth - health; + } + nextHealthPulse = gameLocal.time; + } +} + +/* +=============== +idPlayer::GiveItem + +Returns false if the item shouldn't be picked up +=============== +*/ +bool idPlayer::GiveItem( idItem *item, unsigned int giveFlags ) { + int i; + const idKeyValue *arg; + idDict attr; + bool gave; + int numPickup; + + if ( common->IsMultiplayer() && spectating ) { + return false; + } + + if ( idStr::FindText( item->GetName(), "weapon_flashlight_new" ) > -1 ) { + return false; + } + + if ( idStr::FindText( item->GetName(), "weapon_flashlight" ) > -1 ) { + // don't allow flashlight weapon unless classic mode is enabled + return false; + } + + item->GetAttributes( attr ); + + gave = false; + numPickup = inventory.pickupItemNames.Num(); + for( i = 0; i < attr.GetNumKeyVals(); i++ ) { + arg = attr.GetKeyVal( i ); + if ( Give( arg->GetKey(), arg->GetValue(), giveFlags ) ) { + gave = true; + } + } + + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL ); + if ( arg ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + UpdateHudWeapon( false ); + } + + // display the pickup feedback on the hud + if ( gave && ( numPickup == inventory.pickupItemNames.Num() ) ) { + inventory.AddPickupName( item->spawnArgs.GetString( "inv_name" ), this ); //_D3XP + } + } + + return gave; +} + +/* +=============== +idPlayer::PowerUpModifier +=============== +*/ +float idPlayer::PowerUpModifier( int type ) { + float mod = 1.0f; + + if ( PowerUpActive( BERSERK ) ) { + switch( type ) { + case SPEED: { + mod *= 1.7f; + break; + } + case PROJECTILE_DAMAGE: { + mod *= 2.0f; + break; + } + case MELEE_DAMAGE: { + mod *= 30.0f; + break; + } + case MELEE_DISTANCE: { + mod *= 2.0f; + break; + } + } + } + + if ( common->IsMultiplayer() && !common->IsClient() ) { + if ( PowerUpActive( MEGAHEALTH ) ) { + if ( healthPool <= 0 ) { + GiveHealthPool( 100 ); + } + } else { + healthPool = 0; + } + + /*if( PowerUpActive( HASTE ) ) { + switch( type ) { + case SPEED: { + mod = 1.7f; + break; + } + } + }*/ + } + + return mod; +} + +/* +=============== +idPlayer::PowerUpActive +=============== +*/ +bool idPlayer::PowerUpActive( int powerup ) const { + return ( inventory.powerups & ( 1 << powerup ) ) != 0; +} + +/* +=============== +idPlayer::GivePowerUp +=============== +*/ +bool idPlayer::GivePowerUp( int powerup, int time, unsigned int giveFlags ) { + const char *sound; + + if ( powerup >= 0 && powerup < MAX_POWERUPS ) { + + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( powerup ); + msg.WriteShort( time ); + ServerSendEvent( EVENT_POWERUP, &msg, false ); + } + + if ( powerup != MEGAHEALTH ) { + inventory.GivePowerUp( this, powerup, time ); + } + } + + switch( powerup ) { + case BERSERK: { + if ( giveFlags & ITEM_GIVE_FROM_WEAPON ) { + // Berserk is granted by the bloodstone in ROE, but we don't want any of the + // standard behavior (sound fx, switch to fists) when you get it this way. + } else { + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + inventory.AddPickupName( "#str_00100627", this ); + + if ( spawnArgs.GetString( "snd_berserk_third", "", &sound ) && sound[ 0 ] != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_DEMONIC, 0, false, NULL ); + } + } + + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + if ( !common->IsClient() ) { + idealWeapon = weapon_fists; + } + } + } + break; + } + case INVISIBILITY: { + if ( common->IsMultiplayer() && ( giveFlags & ITEM_GIVE_FEEDBACK ) ) { + inventory.AddPickupName("#str_00100628", this); + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + // remove any decals from the model + if ( modelDefHandle != -1 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + if ( weapon.GetEntity() ) { + weapon.GetEntity()->UpdateSkin(); + } + if( flashlight.GetEntity() ) { + flashlight.GetEntity()->UpdateSkin(); + } + } + +/* if ( spawnArgs.GetString( "snd_invisibility", "", &sound ) ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL ); + } */ + break; + } + case ADRENALINE: { + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + inventory.AddPickupName("#str_00100799", this); + } + + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + stamina = 100.0f; + } + break; + } + case MEGAHEALTH: { + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + if( common->IsMultiplayer() ) { + inventory.AddPickupName("#str_00100629", this); + } + if ( spawnArgs.GetString( "snd_megahealth", "", &sound ) ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + health = 200; + } + break; + } + case HELLTIME: { + if ( spawnArgs.GetString( "snd_helltime_start", "", &sound ) ) { + PostEventMS( &EV_StartSoundShader, 0, sound, SND_CHANNEL_ANY ); + } + if ( spawnArgs.GetString( "snd_helltime_loop", "", &sound ) ) { + PostEventMS( &EV_StartSoundShader, 0, sound, SND_CHANNEL_DEMONIC ); + } + break; + } + case ENVIROSUIT: { + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + // Turn on the envirosuit sound + if ( gameSoundWorld ) { + gameSoundWorld->SetEnviroSuit( true ); + } + } + + if( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + // Put the helmet and lights on the player + idDict args; + + // Light + const idDict *lightDef = gameLocal.FindEntityDefDict( "envirosuit_light", false ); + if ( lightDef ) { + idEntity *temp; + gameLocal.SpawnEntityDef( *lightDef, &temp, false ); + + idLight *eLight = static_cast(temp); + eLight->GetPhysics()->SetOrigin( firstPersonViewOrigin ); + eLight->UpdateVisuals(); + eLight->Present(); + + enviroSuitLight = eLight; + } + } + break; + } + case ENVIROTIME: { + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + hudPowerup = ENVIROTIME; + // The HUD display bar is fixed at 60 seconds + hudPowerupDuration = 60000; + } + break; + } + case INVULNERABILITY: { + if ( common->IsMultiplayer() && ( giveFlags & ITEM_GIVE_FEEDBACK ) ) { + inventory.AddPickupName("#str_00100630", this); + } + break; + } + } + + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + UpdateSkinSetup(); + } + + return true; + } else { + gameLocal.Warning( "Player given power up %i\n which is out of range", powerup ); + } + return false; +} + +/* +============== +idPlayer::ClearPowerup +============== +*/ +void idPlayer::ClearPowerup( int i ) { + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( i ); + msg.WriteShort( 0 ); + ServerSendEvent( EVENT_POWERUP, &msg, false ); + } + + powerUpSkin = NULL; + inventory.powerups &= ~( 1 << i ); + inventory.powerupEndTime[ i ] = 0; + switch( i ) { + case BERSERK: { + if(common->IsMultiplayer()) { + StopSound( SND_CHANNEL_DEMONIC, false ); + } + if(!common->IsMultiplayer()) { + StopHealthRecharge(); + } + break; + } + case INVISIBILITY: { + if ( weapon.GetEntity() ) { + weapon.GetEntity()->UpdateSkin(); + } + if ( flashlight.GetEntity() ) { + flashlight.GetEntity()->UpdateSkin(); + } + break; + } + case HELLTIME: { + GetAchievementManager().ResetHellTimeKills(); + StopSound( SND_CHANNEL_DEMONIC, false ); + break; + } + case ENVIROSUIT: { + + hudPowerup = -1; + + // Turn off the envirosuit sound + if ( gameSoundWorld ) { + gameSoundWorld->SetEnviroSuit( false ); + } + + // Take off the helmet and lights + if ( enviroSuitLight.IsValid() ) { + enviroSuitLight.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + enviroSuitLight = NULL; + break; + } + case INVULNERABILITY: { + if(common->IsMultiplayer()) { + StopSound( SND_CHANNEL_DEMONIC, false ); + } + } + /*case HASTE: { + if(common->IsMultiplayer()) { + StopSound( SND_CHANNEL_DEMONIC, false ); + } + }*/ + } +} + +/* +============== +idPlayer::UpdatePowerUps +============== +*/ +void idPlayer::UpdatePowerUps() { + int i; + + if ( !common->IsClient() ) { + for ( i = 0; i < MAX_POWERUPS; i++ ) { + if ( ( inventory.powerups & ( 1 << i ) ) && inventory.powerupEndTime[i] > gameLocal.time ) { + switch( i ) { + case ENVIROSUIT: { + if ( enviroSuitLight.IsValid() ) { + idAngles lightAng = firstPersonViewAxis.ToAngles(); + idVec3 lightOrg = firstPersonViewOrigin; + const idDict *lightDef = gameLocal.FindEntityDefDict( "envirosuit_light", false ); + + idVec3 enviroOffset = lightDef->GetVector( "enviro_offset" ); + idVec3 enviroAngleOffset = lightDef->GetVector( "enviro_angle_offset" ); + + lightOrg += (enviroOffset.x * firstPersonViewAxis[0]); + lightOrg += (enviroOffset.y * firstPersonViewAxis[1]); + lightOrg += (enviroOffset.z * firstPersonViewAxis[2]); + lightAng.pitch += enviroAngleOffset.x; + lightAng.yaw += enviroAngleOffset.y; + lightAng.roll += enviroAngleOffset.z; + + enviroSuitLight.GetEntity()->GetPhysics()->SetOrigin( lightOrg ); + enviroSuitLight.GetEntity()->GetPhysics()->SetAxis( lightAng.ToMat3() ); + enviroSuitLight.GetEntity()->UpdateVisuals(); + enviroSuitLight.GetEntity()->Present(); + } + break; + } + default: { + break; + } + } + } + if ( PowerUpActive( i ) && inventory.powerupEndTime[i] <= gameLocal.time ) { + ClearPowerup( i ); + } + } + } + + if ( health > 0 ) { + if ( powerUpSkin ) { + renderEntity.customSkin = powerUpSkin; + } else { + renderEntity.customSkin = skin; + } + } + + if ( healthPool && gameLocal.time > nextHealthPulse && !AI_DEAD && health > 0 ) { + assert( !common->IsClient() ); // healthPool never be set on client + int amt = ( healthPool > 5.0f ) ? 5 : healthPool; + health += amt; + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + healthPool = 0; + } else { + healthPool -= amt; + } + if ( healthPool < 1.0f ) { + healthPool = 0.0f; + } else { + nextHealthPulse = gameLocal.time + HEALTHPULSE_TIME; + healthPulse = true; + } + } + if ( !gameLocal.inCinematic && influenceActive == 0 && g_skill.GetInteger() == 3 && gameLocal.time > nextHealthTake && !AI_DEAD && health > g_healthTakeLimit.GetInteger() ) { + assert( !common->IsClient() ); // healthPool never be set on client + + if(!PowerUpActive(INVULNERABILITY)) { + health -= g_healthTakeAmt.GetInteger(); + if ( health < g_healthTakeLimit.GetInteger() ) { + health = g_healthTakeLimit.GetInteger(); + } + } + nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000; + healthTake = true; + } +} + +/* +=============== +idPlayer::ClearPowerUps +=============== +*/ +void idPlayer::ClearPowerUps() { + int i; + for ( i = 0; i < MAX_POWERUPS; i++ ) { + if ( PowerUpActive( i ) ) { + ClearPowerup( i ); + } + } + inventory.ClearPowerUps(); + + if ( common->IsMultiplayer() ) { + if ( enviroSuitLight.IsValid() ) { + enviroSuitLight.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + } +} + +/* +=============== +idPlayer::GiveInventoryItem +=============== +*/ +bool idPlayer::GiveInventoryItem( idDict * item, unsigned int giveFlags ) { + if ( common->IsMultiplayer() && spectating ) { + return false; + } + + if ( giveFlags & ITEM_GIVE_UPDATE_STATE ) { + inventory.items.Append( new (TAG_ENTITY) idDict( *item ) ); + } + + const char * itemName = item->GetString( "inv_name" ); + + if ( giveFlags & ITEM_GIVE_FEEDBACK ) { + if ( idStr::Cmpn( itemName, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + inventory.pickupItemNames.Append( idLocalization::GetString( itemName ) ); + } else { + inventory.pickupItemNames.Append( itemName ); + } + + const char * icon = item->GetString( "inv_icon" ); + if ( hud != NULL ) { + hud->ShowNewItem( itemName, icon ); + } + } + + // D3XP added to support powercells + if( ( giveFlags & ITEM_GIVE_UPDATE_STATE ) && item->GetInt("inv_powercell") && focusUI) { + //Reset the powercell count + int powerCellCount = 0; + for ( int j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + if(item->GetInt("inv_powercell")) { + powerCellCount++; + } + } + focusUI->SetStateInt( "powercell_count", powerCellCount ); + } + + return true; +} + +/* +============== +idPlayer::GiveInventoryItem +============== +*/ +bool idPlayer::GiveInventoryItem( const char *name ) { + idDict args; + + args.Set( "classname", name ); + args.Set( "owner", this->name.c_str() ); + gameLocal.SpawnEntityDef( args); + return true; +} + +/* +=============== +idPlayer::GiveObjective +=============== +*/ +void idPlayer::GiveObjective( const char * title, const char * text, const idMaterial * screenshot ) { + idObjectiveInfo & info = inventory.objectiveNames.Alloc(); + info.title = title; + info.text = text; + info.screenshot = screenshot; + + StartSound( "snd_objectiveup", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( hud ) { + hud->SetupObjective( title, text, screenshot ); + hud->ShowObjective( false ); + objectiveUp = true; + } +} + +/* +=============== +idPlayer::CompleteObjective +=============== +*/ +void idPlayer::CompleteObjective( const char *title ) { + int c = inventory.objectiveNames.Num(); + for ( int i = 0; i < c; i++ ) { + if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) { + inventory.objectiveNames.RemoveIndex( i ); + break; + } + } + + StartSound( "snd_objectiveup", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( hud ) { + hud->SetupObjectiveComplete( title ); + hud->ShowObjective( true ); + } +} + +/* +=============== +idPlayer::GiveVideo +=============== +*/ +void idPlayer::GiveVideo( const idDeclVideo * video, const char * itemName ) { + + if ( video == NULL ) { + return; + } + + int oldNumVideos = inventory.videos.Num(); + inventory.videos.AddUnique( video ); + + if ( oldNumVideos < inventory.videos.Num() ) { + GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_WATCH_ALL_VIDEOS ); + } + + if ( itemName != NULL && itemName[0] != 0 ) { + inventory.pickupItemNames.Append( itemName ); + } + + if ( hud ) { + hud->DownloadVideo(); + } +} + +/* +=============== +idPlayer::GiveSecurity +=============== +*/ +void idPlayer::GiveSecurity( const char *security ) { + GetPDA()->SetSecurity( security ); + + if ( hud ) { + hud->UpdatedSecurity(); + } +} + +/* +=============== +idPlayer::GiveEmail +=============== +*/ +void idPlayer::GiveEmail( const idDeclEmail * email ) { + if ( email == NULL ) { + return; + } + + inventory.emails.AddUnique( email ); + GetPDA()->AddEmail( email ); + + // TODO_SPARTY: hook up new email notification in new hud + //if ( hud ) { + // hud->HandleNamedEvent( "emailPickup" ); + //} +} + +/* +=============== +idPlayer::GivePDA +=============== +*/ +void idPlayer::GivePDA( const idDeclPDA * pda, const char * securityItem ) { + if ( common->IsMultiplayer() && spectating ) { + return; + } + + if ( securityItem != NULL && securityItem[0] != 0 ) { + inventory.pdaSecurity.AddUnique( securityItem ); + } + + // Just to make sure they want the default player spawn defined pda. + // Some what of a hack, so i dont have to change any map scripts that initially give + // the player "personal" pda. + if ( pda == NULL || idStr::Icmp( pda->GetName(), "personal" ) == 0 ) { + pda = static_cast( declManager->FindType( DECL_PDA, spawnArgs.GetString( "pda_name", "personal" ) ) ); + } + if ( pda == NULL ) { + return; + } + + int oldNumPDAs = inventory.pdas.Num(); + inventory.pdas.AddUnique( pda ); + int newNumPDAs = inventory.pdas.Num(); + + // Set the stat for # of PDAs... + // Only increment the PDA stat if we've added a new one.... + if ( oldNumPDAs < newNumPDAs ) { + switch ( GetExpansionType() ) { + case GAME_BASE: + GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_PDAS_BASE ); + break; + case GAME_D3XP: + GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_PDAS_ROE ); + break; + case GAME_D3LE: + GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_PDAS_LE ); + break; + } + } + + // Copy any videos over + for ( int i = 0; i < pda->GetNumVideos(); i++ ) { + const idDeclVideo * video = pda->GetVideoByIndex( i ); + if ( video != NULL ) { + inventory.videos.AddUnique( video ); + } + } + + // This is kind of a hack, but it works nicely + // We don't want to display the 'you got a new pda' message during a map load + if ( gameLocal.GetFrameNum() > 10 ) { + const char * sec = pda->GetSecurity(); + if ( hud ) { + hud->DownloadPDA( pda, ( sec != NULL && sec[0] != 0 ) ? true : false ); + } + if ( inventory.pdas.Num() == 1 ) { + GetPDA()->RemoveAddedEmailsAndVideos(); + if ( !objectiveSystemOpen ) { + TogglePDA(); + } + //ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_firstPDA" ), true ); + } + } +} + +/* +=============== +idPlayer::FindInventoryItem +=============== +*/ +idDict *idPlayer::FindInventoryItem( const char *name ) { + for ( int i = 0; i < inventory.items.Num(); i++ ) { + const char *iname = inventory.items[i]->GetString( "inv_name" ); + if ( iname != NULL && *iname != NULL ) { + if ( idStr::Icmp( name, iname ) == 0 ) { + return inventory.items[i]; + } + } + } + return NULL; +} + +/* +=============== +idPlayer::FindInventoryItem +=============== +*/ +idDict * idPlayer::FindInventoryItem( int index ) { + if ( index <= inventory.items.Num() ) { + return inventory.items[ index ]; + } + return NULL; +} + +/* +=============== +idPlayer::GetNumInventoryItems +=============== +*/ +int idPlayer::GetNumInventoryItems() { + return inventory.items.Num(); +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( const char *name ) { + //Hack for localization + if(!idStr::Icmp(name, "Pwr Cell")) { + name = idLocalization::GetString( "#str_00101056" ); + } + idDict *item = FindInventoryItem(name); + if ( item ) { + RemoveInventoryItem( item ); + } +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( idDict *item ) { + inventory.items.Remove( item ); + + if(item->GetInt("inv_powercell") && focusUI) { + //Reset the powercell count + int powerCellCount = 0; + for ( int j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + if(item->GetInt("inv_powercell")) { + powerCellCount++; + } + } + focusUI->SetStateInt( "powercell_count", powerCellCount ); + } + + delete item; +} + +/* +=============== +idPlayer::GiveItem +=============== +*/ +void idPlayer::GiveItem( const char *itemname ) { + idDict args; + + args.Set( "classname", itemname ); + args.Set( "owner", name.c_str() ); + gameLocal.SpawnEntityDef( args ); +} + +/* +================== +idPlayer::SlotForWeapon +================== +*/ +int idPlayer::SlotForWeapon( const char *weaponName ) { + int i; + + for( i = 0; i < MAX_WEAPONS; i++ ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + return i; + } + } + + // not found + return -1; +} + +/* +=============== +idPlayer::Reload +=============== +*/ +void idPlayer::Reload() { + if ( spectating || gameLocal.inCinematic || influenceActive ) { + return; + } + + if ( common->IsClient() && !IsLocallyControlled() ) { + return; + } + + if ( weapon.GetEntity() && weapon.GetEntity()->IsLinked() ) { + weapon.GetEntity()->Reload(); + } +} + +/* +=============== +idPlayer::NextBestWeapon +=============== +*/ +void idPlayer::NextBestWeapon() { + const char *weap; + int w = MAX_WEAPONS; + + if ( !weaponEnabled ) { + return; + } + + while ( w > 0 ) { + w--; + if ( w == weapon_flashlight ) { + continue; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap, true, this ) ) ) { + continue; + } + if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) { + continue; + } + + //Some weapons will report having ammo but the clip is empty and + //will not have enough to fill the clip (i.e. Double Barrel Shotgun with 1 round left) + //We need to skip these weapons because they cannot be used + if(inventory.HasEmptyClipCannotRefill(weap, this)) { + continue; + } + + break; + } + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::NextWeapon +=============== +*/ +void idPlayer::NextWeapon() { + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + int w = idealWeapon.Get(); + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + if ( w == idealWeapon ) { + w = weapon_fists; + break; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + const char * weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + + if ( inventory.HasAmmo( weap, true, this ) || w == weapon_bloodstone ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +idPlayer::PrevWeapon +=============== +*/ +void idPlayer::PrevWeapon() { + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + int w = idealWeapon.Get(); + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + if ( w == idealWeapon ) { + w = weapon_fists; + break; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + const char * weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( inventory.HasAmmo( weap, true, this ) || w == weapon_bloodstone ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( int num, bool force ) { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) { + return; + } + + if ( num == weapon_flashlight ) { + return; + } + + if ( ( num != weapon_pda ) && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + num = weapon_fists; + hiddenWeapon ^= 1; + if ( hiddenWeapon && weapon.GetEntity() ) { + weapon.GetEntity()->LowerWeapon(); + } else { + weapon.GetEntity()->RaiseWeapon(); + } + } + + //Is the weapon a toggle weapon + WeaponToggle_t* weaponToggle; + if(weaponToggles.Get(va("weapontoggle%d", num), &weaponToggle)) { + + int weaponToggleIndex = 0; + + //Find the current Weapon in the list + int currentIndex = -1; + for(int i = 0; i < weaponToggle->toggleList.Num(); i++) { + if(weaponToggle->toggleList[i] == idealWeapon) { + currentIndex = i; + break; + } + } + if ( currentIndex == -1 ) { + //Didn't find the current weapon so select the first item + weaponToggleIndex = weaponToggle->lastUsed; + } else { + //Roll to the next available item in the list + weaponToggleIndex = currentIndex; + weaponToggleIndex++; + if(weaponToggleIndex >= weaponToggle->toggleList.Num()) { + weaponToggleIndex = 0; + } + } + + for(int i = 0; i < weaponToggle->toggleList.Num(); i++) { + int weapNum = weaponToggle->toggleList[weaponToggleIndex]; + //Is it available + if(inventory.weapons & ( 1 << weapNum)) { + //Do we have ammo for it + if ( inventory.HasAmmo( spawnArgs.GetString( va( "def_weapon%d", weapNum ) ), true, this ) || spawnArgs.GetBool( va( "weapon%d_allowempty", weapNum ) ) ) { + break; + } + } + + weaponToggleIndex++; + if(weaponToggleIndex >= weaponToggle->toggleList.Num()) { + weaponToggleIndex = 0; + } + } + weaponToggle->lastUsed = weaponToggleIndex; + num = weaponToggle->toggleList[weaponToggleIndex]; + } + + weap = spawnArgs.GetString( va( "def_weapon%d", num ) ); + if ( !weap[ 0 ] ) { + gameLocal.Printf( "Invalid weapon\n" ); + return; + } + + if ( force || ( inventory.weapons & ( 1 << num ) ) ) { + if ( !inventory.HasAmmo( weap, true, this ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) { + return; + } + if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) { + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !inventory.HasAmmo( weap, true, this ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) { + return; + } + idealWeapon = previousWeapon; + } else if ( ( weapon_pda >= 0 ) && ( num == weapon_pda ) && ( inventory.pdas.Num() == 0 ) ) { + ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true ); + return; + } else { + idealWeapon = num; + } + UpdateHudWeapon(); + } +} + +/* +================= +idPlayer::DropWeapon +================= +*/ +void idPlayer::DropWeapon( bool died ) { + idVec3 forward, up; + int inclip, ammoavailable; + + if( died == false ){ + return; + } + + assert( !common->IsClient() ); + + if ( spectating || weaponGone || weapon.GetEntity() == NULL ) { + return; + } + + if ( ( !died && !weapon.GetEntity()->IsReady() ) || weapon.GetEntity()->IsReloading() ) { + return; + } + // ammoavailable is how many shots we can fire + // inclip is which amount is in clip right now + ammoavailable = weapon.GetEntity()->AmmoAvailable(); + inclip = weapon.GetEntity()->AmmoInClip(); + + // don't drop a grenade if we have none left + if ( !idStr::Icmp( idWeapon::GetAmmoNameForNum( weapon.GetEntity()->GetAmmoType() ), "ammo_grenades" ) && ( ammoavailable - inclip <= 0 ) ) { + return; + } + + ammoavailable += inclip; + + // expect an ammo setup that makes sense before doing any dropping + // ammoavailable is -1 for infinite ammo, and weapons like chainsaw + // a bad ammo config usually indicates a bad weapon state, so we should not drop + // used to be an assertion check, but it still happens in edge cases + + if ( ( ammoavailable != -1 ) && ( ammoavailable < 0 ) ) { + common->DPrintf( "idPlayer::DropWeapon: bad ammo setup\n" ); + return; + } + idEntity *item = NULL; + if ( died ) { + // ain't gonna throw you no weapon if I'm dead + item = weapon.GetEntity()->DropItem( vec3_origin, 0, WEAPON_DROP_TIME, died ); + } else { + viewAngles.ToVectors( &forward, NULL, &up ); + item = weapon.GetEntity()->DropItem( 250.0f * forward + 150.0f * up, 500, WEAPON_DROP_TIME, died ); + } + if ( !item ) { + return; + } + // set the appropriate ammo in the dropped object + const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" ); + if ( keyval ) { + item->spawnArgs.SetInt( keyval->GetKey(), ammoavailable ); + idStr inclipKey = keyval->GetKey(); + inclipKey.Insert( "inclip_", 4 ); + inclipKey.Insert( va("%.2d", currentWeapon), 11); + item->spawnArgs.SetInt( inclipKey, inclip ); + } + if ( !died ) { + // remove from our local inventory completely + inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 ); + weapon.GetEntity()->ResetAmmoClip(); + NextWeapon(); + weapon.GetEntity()->WeaponStolen(); + weaponGone = true; + } +} + +/* +================= +idPlayer::StealWeapon +steal the target player's current weapon +================= +*/ +void idPlayer::StealWeapon( idPlayer *player ) { + assert( !common->IsClient() ); + + // make sure there's something to steal + idWeapon *player_weapon = static_cast< idWeapon * >( player->weapon.GetEntity() ); + if ( !player_weapon || !player_weapon->CanDrop() || weaponGone ) { + return; + } + // steal - we need to effectively force the other player to abandon his weapon + int newweap = player->currentWeapon; + if ( newweap == -1 ) { + return; + } + // might be just dropped - check inventory + if ( ! ( player->inventory.weapons & ( 1 << newweap ) ) ) { + return; + } + const char *weapon_classname = spawnArgs.GetString( va( "def_weapon%d", newweap ) ); + assert( weapon_classname ); + int ammoavailable = player->weapon.GetEntity()->AmmoAvailable(); + int inclip = player->weapon.GetEntity()->AmmoInClip(); + + ammoavailable += inclip; + + if ( ( ammoavailable != -1 ) && ( ammoavailable < 0 ) ) { + // see DropWeapon + common->DPrintf( "idPlayer::StealWeapon: bad ammo setup\n" ); + // we still steal the weapon, so let's use the default ammo levels + inclip = -1; + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname ); + assert( decl ); + const idKeyValue *keypair = decl->dict.MatchPrefix( "inv_ammo_" ); + assert( keypair ); + ammoavailable = atoi( keypair->GetValue() ); + } + + player->weapon.GetEntity()->WeaponStolen(); + player->inventory.Drop( player->spawnArgs, NULL, newweap ); + player->SelectWeapon( weapon_fists, false ); + // in case the robbed player is firing rounds with a continuous fire weapon like the chaingun/plasma etc. + // this will ensure the firing actually stops + player->weaponGone = true; + + // give weapon, setup the ammo count + Give( "weapon", weapon_classname, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + ammo_t ammo_i = player->inventory.AmmoIndexForWeaponClass( weapon_classname, NULL ); + idealWeapon = newweap; + const int currentAmmo = inventory.GetInventoryAmmoForType( ammo_i ); + inventory.SetInventoryAmmoForType( ammo_i, currentAmmo + ammoavailable ); +} + +/* +=============== +idPlayer::ActiveGui +=============== +*/ +idUserInterface *idPlayer::ActiveGui() { + if ( objectiveSystemOpen ) { + return NULL; + } + + return focusUI; +} + +/* +=============== +idPlayer::Weapon_Combat +=============== +*/ +void idPlayer::Weapon_Combat() { + if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + weapon.GetEntity()->RaiseWeapon(); + if ( weapon.GetEntity()->IsReloading() ) { + if ( !AI_RELOAD ) { + AI_RELOAD = true; + SetState( "ReloadWeapon" ); + UpdateScript(); + } + } else { + AI_RELOAD = false; + } + + if ( idealWeapon == weapon_soulcube && soulCubeProjectile.GetEntity() != NULL ) { + idealWeapon = currentWeapon; + } + + if ( idealWeapon != currentWeapon && idealWeapon.Get() < MAX_WEAPONS ) { + if ( weaponCatchup ) { + assert( common->IsClient() ); + + currentWeapon = idealWeapon.Get(); + weaponGone = false; + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.GetClipAmmoForWeapon( currentWeapon ) ); + animPrefix.Strip( "weapon_" ); + + weapon.GetEntity()->NetCatchup(); + const function_t *newstate = GetScriptFunction( "NetCatchup" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } + weaponCatchup = false; + } else { + if ( weapon.GetEntity()->IsReady() ) { + weapon.GetEntity()->PutAway(); + } + + if ( weapon.GetEntity()->IsHolstered() ) { + assert( idealWeapon.Get() >= 0 ); + assert( idealWeapon.Get() < MAX_WEAPONS ); + + if ( currentWeapon != weapon_pda && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) { + previousWeapon = currentWeapon; + } + currentWeapon = idealWeapon.Get(); + weaponGone = false; + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.GetClipAmmoForWeapon( currentWeapon ) ); + animPrefix.Strip( "weapon_" ); + + weapon.GetEntity()->Raise(); + } + } + } else { + weaponGone = false; // if you drop and re-get weap, you may miss the = false above + if ( weapon.GetEntity()->IsHolstered() ) { + if ( !weapon.GetEntity()->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else { + weapon.GetEntity()->Raise(); + state = GetScriptFunction( "RaiseWeapon" ); + if ( state ) { + SetState( state ); + } + } + } + } + + // check for attack + AI_WEAPON_FIRED = false; + if ( !influenceActive ) { + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + AI_ATTACK_HELD = false; + weapon.GetEntity()->EndAttack(); + } + } + + // update our ammo clip in our inventory + if ( ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.SetClipAmmoForWeapon( currentWeapon, weapon.GetEntity()->AmmoInClip() ); + } +} + +/* +=============== +idPlayer::Weapon_NPC +=============== +*/ +void idPlayer::Weapon_NPC() { + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + StopFiring(); + weapon.GetEntity()->LowerWeapon(); + + bool wasDown = ( oldButtons & (BUTTON_ATTACK|BUTTON_USE) ) != 0; + bool isDown = ( usercmd.buttons & (BUTTON_ATTACK|BUTTON_USE) ) != 0; + if ( isDown && !wasDown ) { + buttonMask |= BUTTON_ATTACK; + focusCharacter->TalkTo( this ); + } +} + +/* +=============== +idPlayer::LowerWeapon +=============== +*/ +void idPlayer::LowerWeapon() { + if ( weapon.GetEntity() && !weapon.GetEntity()->IsHidden() ) { + weapon.GetEntity()->LowerWeapon(); + } +} + +/* +=============== +idPlayer::RaiseWeapon +=============== +*/ +void idPlayer::RaiseWeapon() { + if ( weapon.GetEntity() && weapon.GetEntity()->IsHidden() ) { + weapon.GetEntity()->RaiseWeapon(); + } +} + +/* +=============== +idPlayer::WeaponLoweringCallback +=============== +*/ +void idPlayer::WeaponLoweringCallback() { + SetState( "LowerWeapon" ); + UpdateScript(); +} + +/* +=============== +idPlayer::WeaponRisingCallback +=============== +*/ +void idPlayer::WeaponRisingCallback() { + SetState( "RaiseWeapon" ); + UpdateScript(); +} + +/* +=============== +idPlayer::Weapon_GUI +=============== +*/ +void idPlayer::Weapon_GUI() { + + if ( !objectiveSystemOpen ) { + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + StopFiring(); + weapon.GetEntity()->LowerWeapon(); + } + + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( common->IsClient() && !net_clientPredictGUI.GetBool() ) { + return; + } + + bool wasDown = ( oldButtons & (BUTTON_ATTACK|BUTTON_USE) ) != 0; + bool isDown = ( usercmd.buttons & (BUTTON_ATTACK|BUTTON_USE) ) != 0; + if ( isDown != wasDown ) { + const char * command = NULL; + idUserInterface * ui = ActiveGui(); + if ( ui ) { + bool updateVisuals = false; + sysEvent_t ev = sys->GenerateMouseButtonEvent( 1, isDown ); + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusGUIent && ui == focusUI ) { + focusGUIent->UpdateVisuals(); + } + } + if ( common->IsClient() ) { + // we predict enough, but don't want to execute commands + return; + } + + // HACK - Check to see who is activating the frag chamber. Im sorry. + if( common->IsMultiplayer() && focusGUIent ) { + if( strcmp( focusGUIent->GetName(), "chamber_gui_console" ) == 0 && strcmp( command, " ; runScript chamber_trigger" ) == 0 ){ + gameLocal.playerActivateFragChamber = this; + } + } + + if ( focusGUIent ) { + HandleGuiCommands( focusGUIent, command ); + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +=============== +idPlayer::UpdateWeapon +=============== +*/ +void idPlayer::UpdateWeapon() { + if ( health <= 0 ) { + return; + } + + assert( !spectating ); + + if ( common->IsClient() ) { + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( !weapon.GetEntity()->IsWorldModelReady() ) { + return; + } + } + + // always make sure the weapon is correctly setup before accessing it + if ( !weapon.GetEntity()->IsLinked() ) { + if ( idealWeapon != -1 ) { + animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon.Get() ) ); + int ammoInClip = inventory.GetClipAmmoForWeapon( idealWeapon.Get() ); + if ( common->IsMultiplayer() && respawning ) { + // Do not load ammo into the clip here on MP respawn, as it will be done + // elsewhere. If we take ammo out here then the player will end up losing + // a clip of ammo for their initial weapon upon respawn. + ammoInClip = 0; + } + weapon.GetEntity()->GetWeaponDef( animPrefix, ammoInClip ); + assert( weapon.GetEntity()->IsLinked() ); + } else { + return; + } + } + + if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) { + HideTip(); + } + + if ( g_dragEntity.GetBool() ) { + StopFiring(); + weapon.GetEntity()->LowerWeapon(); + dragEntity.Update( this ); + } else if ( ActiveGui() ) { + // gui handling overrides weapon use + Weapon_GUI(); + } else if ( focusCharacter && ( focusCharacter->health > 0 ) ) { + Weapon_NPC(); + } else { + Weapon_Combat(); + } + + if ( hiddenWeapon ) { + weapon.GetEntity()->LowerWeapon(); + } + + // update weapon state, particles, dlights, etc + weapon.GetEntity()->PresentWeapon( CanShowWeaponViewmodel() ); +} + +/* +=============== +idPlayer::UpdateFlashLight +=============== +*/ +void idPlayer::UpdateFlashlight() { + if ( idealWeapon == weapon_flashlight ) { + // force classic flashlight to go away + NextWeapon(); + } + + if ( !flashlight.IsValid() ) { + return; + } + + if ( !flashlight.GetEntity()->GetOwner() ) { + return; + } + + // Don't update the flashlight if dead in MP. + // Otherwise you can see a floating flashlight worldmodel near player's skeletons. + if ( common->IsMultiplayer() ) { + if ( health < 0 ) { + return; + } + } + + // Flashlight has an infinite battery in multiplayer. + if ( !common->IsMultiplayer() ) { + if ( flashlight.GetEntity()->lightOn ) { + if ( flashlight_batteryDrainTimeMS.GetInteger() > 0 ) { + flashlightBattery -= ( gameLocal.time - gameLocal.previousTime ); + if ( flashlightBattery < 0 ) { + FlashlightOff(); + flashlightBattery = 0; + } + } + } else { + if ( flashlightBattery < flashlight_batteryDrainTimeMS.GetInteger() ) { + flashlightBattery += ( gameLocal.time - gameLocal.previousTime ) * Max( 1, ( flashlight_batteryDrainTimeMS.GetInteger() / flashlight_batteryChargeTimeMS.GetInteger() ) ); + if ( flashlightBattery > flashlight_batteryDrainTimeMS.GetInteger() ) { + flashlightBattery = flashlight_batteryDrainTimeMS.GetInteger(); + } + } + } + } + + if ( hud ) { + hud->UpdateFlashlight( this ); + } + + if ( common->IsClient() ) { + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( !flashlight.GetEntity()->IsWorldModelReady() ) { + return; + } + } + + // always make sure the weapon is correctly setup before accessing it + if ( !flashlight.GetEntity()->IsLinked() ) { + flashlight.GetEntity()->GetWeaponDef( "weapon_flashlight_new", 0 ); + flashlight.GetEntity()->SetIsPlayerFlashlight( true ); + + // adjust position / orientation of flashlight + idAnimatedEntity *worldModel = flashlight.GetEntity()->GetWorldModel(); + worldModel->BindToJoint( this, "Chest", true ); + // Don't interpolate the flashlight world model in mp, let it bind like normal. + worldModel->SetUseClientInterpolation( false ); + + assert( flashlight.GetEntity()->IsLinked() ); + } + + // this positions the third person flashlight model! (as seen in the mirror) + idAnimatedEntity *worldModel = flashlight.GetEntity()->GetWorldModel(); + static const idVec3 fl_pos = idVec3( 3.0f, 9.0f, 2.0f ); + worldModel->GetPhysics()->SetOrigin( fl_pos ); + static float fl_pitch = 0.0f; + static float fl_yaw = 0.0f; + static float fl_roll = 0.0f; + static idAngles ang = ang_zero; + ang.Set( fl_pitch, fl_yaw, fl_roll ); + worldModel->GetPhysics()->SetAxis( ang.ToMat3() ); + + if ( flashlight.GetEntity()->lightOn ) { + if ( ( flashlightBattery < flashlight_batteryChargeTimeMS.GetInteger() / 2 ) && ( gameLocal.random.RandomFloat() < flashlight_batteryFlickerPercent.GetFloat() ) ) { + flashlight.GetEntity()->RemoveMuzzleFlashlight(); + } else { + flashlight.GetEntity()->MuzzleFlashLight(); + } + } + + flashlight.GetEntity()->PresentWeapon( true ); + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || gameLocal.inCinematic || spectating || fl.hidden ) { + worldModel->Hide(); + } else { + worldModel->Show(); + } +} + +/* +=============== +idPlayer::FlashlightOn +=============== +*/ +void idPlayer::FlashlightOn() { + if ( !flashlight.IsValid() ) { + return; + } + if ( flashlightBattery < idMath::Ftoi( flashlight_minActivatePercent.GetFloat() * flashlight_batteryDrainTimeMS.GetFloat() ) ) { + return; + } + if ( gameLocal.inCinematic ) { + return; + } + if ( flashlight.GetEntity()->lightOn ) { + return; + } + if ( health <= 0 ) { + return; + } + if ( spectating ) { + return; + } + + flashlight->FlashlightOn(); +} + +/* +=============== +idPlayer::FlashlightOff +=============== +*/ +void idPlayer::FlashlightOff() { + if ( !flashlight.IsValid() ) { + return; + } + if ( !flashlight.GetEntity()->lightOn ) { + return; + } + flashlight->FlashlightOff(); +} + +/* +=============== +idPlayer::SpectateFreeFly +=============== +*/ +void idPlayer::SpectateFreeFly( bool force ) { + idPlayer *player; + idVec3 newOrig; + idVec3 spawn_origin; + idAngles spawn_angles; + + player = gameLocal.GetClientByNum( spectator ); + if ( force || gameLocal.time > lastSpectateChange ) { + spectator = entityNumber; + if ( player != NULL && player != this && !player->spectating && !player->IsInTeleport() ) { + newOrig = player->GetPhysics()->GetOrigin(); + if ( player->physicsObj.IsCrouching() ) { + newOrig[ 2 ] += pm_crouchviewheight.GetFloat(); + } else { + newOrig[ 2 ] += pm_normalviewheight.GetFloat(); + } + newOrig[ 2 ] += SPECTATE_RAISE; + idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + idVec3 start = player->GetPhysics()->GetOrigin(); + start[2] += pm_spectatebbox.GetFloat() * 0.5f; + trace_t t; + // assuming spectate bbox is inside stand or crouch box + gameLocal.clip.TraceBounds( t, start, newOrig, b, MASK_PLAYERSOLID, player ); + newOrig.Lerp( start, newOrig, t.fraction ); + SetOrigin( newOrig ); + idAngles angle = player->viewAngles; + angle[ 2 ] = 0; + SetViewAngles( angle ); + } else { + SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + spawn_origin[ 2 ] += pm_normalviewheight.GetFloat(); + spawn_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spawn_origin ); + SetViewAngles( spawn_angles ); + // This may happen during GAMESTATE_STARTUP in mp, so we must set the spawnAngles too. + spawnAngles = spawn_angles; + + if( force == false ) { + // only do this if they hit the cycle button. + if ( common->IsServer() ) { + if( player != NULL ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.WriteFloat( GetPhysics()->GetOrigin()[0] ); + msg.WriteFloat( GetPhysics()->GetOrigin()[1] ); + msg.WriteFloat( GetPhysics()->GetOrigin()[2] ); + msg.WriteFloat( viewAngles[0] ); + msg.WriteFloat( viewAngles[1] ); + msg.WriteFloat( viewAngles[2] ); + + ServerSendEvent( idPlayer::EVENT_FORCE_ORIGIN, &msg, false ); + } + } + } + } + lastSpectateChange = gameLocal.time + 500; + } + + +} + +/* +=============== +idPlayer::SpectateCycle +=============== +*/ +void idPlayer::SpectateCycle() { + idPlayer *player; + + if ( gameLocal.time > lastSpectateChange ) { + int latchedSpectator = spectator; + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + assert( player ); // never call here when the current spectator is wrong + // ignore other spectators + while ( latchedSpectator != spectator && player->spectating ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + } + lastSpectateChange = gameLocal.time + 500; + } +} + +/* +=============== +idPlayer::UpdateSpectating +=============== +*/ +void idPlayer::UpdateSpectating() { + assert( spectating ); + assert( !common->IsClient() ); + assert( IsHidden() ); + idPlayer *player; + if ( !common->IsMultiplayer() ) { + return; + } + player = gameLocal.GetClientByNum( spectator ); + if ( !player || ( player->spectating && player != this ) ) { + SpectateFreeFly( true ); + } else if ( usercmd.buttons & BUTTON_JUMP ) { + SpectateFreeFly( false ); + } else if ( usercmd.buttons & BUTTON_USE ) { + SpectateCycle(); + } else if ( usercmd.buttons & BUTTON_ATTACK ) { + wantSpectate = false; + } +} + +/* +=============== +idPlayer::HandleSingleGuiCommand +=============== +*/ +bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "addhealth" ) == 0 ) { + if ( entityGui && health < 100 ) { + int _health = entityGui->spawnArgs.GetInt( "gui_parm1" ); + int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health; + _health -= amt; + entityGui->spawnArgs.SetInt( "gui_parm1", _health ); + if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) { + entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health ); + } + health += amt; + if ( health > 100 ) { + health = 100; + } + } + return true; + } + src->UnreadToken( &token ); + return false; +} + +/* +============== +idPlayer::PlayAudioLog +============== +*/ +void idPlayer::PlayAudioLog( const idSoundShader * shader ) { + EndVideoDisk(); + if ( name.Length() > 0 ) { + int ms; + StartSoundShader( shader, SND_CHANNEL_PDA_AUDIO, 0, false, &ms ); + CancelEvents( &EV_Player_StopAudioLog ); + PostEventMS( &EV_Player_StopAudioLog, ms + 150 ); + } +} + +/* +============== +idPlayer::EndAudioLog +============== +*/ +void idPlayer::EndAudioLog() { + StopSound( SND_CHANNEL_PDA_AUDIO, false ); +} + +/* +============== +idPlayer::PlayVideoDisk +============== +*/ +void idPlayer::PlayVideoDisk( const idDeclVideo * decl ) { + EndAudioLog(); + pdaVideoMat = decl->GetRoq(); + if ( pdaVideoMat ) { + int c = pdaVideoMat->GetNumStages(); + for ( int i = 0; i < c; i++ ) { + const shaderStage_t *stage = pdaVideoMat->GetStage( i ); + if ( stage != NULL && stage->texture.cinematic ) { + stage->texture.cinematic->ResetTime( Sys_Milliseconds() ); + } + } + if ( decl->GetWave() != NULL ) { + StartSoundShader( decl->GetWave(), SND_CHANNEL_PDA_VIDEO, 0, false, NULL ); + } + } +} + +/* +============== +idPlayer::EndVideoDisk +============== +*/ +void idPlayer::EndVideoDisk() { + pdaVideoMat = NULL; + StopSound( SND_CHANNEL_PDA_VIDEO, false ); +} + +/* +============== +idPlayer::Collide +============== +*/ +bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *other; + + if ( common->IsClient() && spectating == false ) { + return false; + } + + other = gameLocal.entities[ collision.c.entityNum ]; + if ( other ) { + other->Signal( SIG_TOUCH ); + if ( !spectating ) { + if ( other->RespondsTo( EV_Touch ) ) { + other->ProcessEvent( &EV_Touch, this, &collision ); + } + } else { + if ( other->RespondsTo( EV_SpectatorTouch ) ) { + other->ProcessEvent( &EV_SpectatorTouch, this, &collision ); + } + } + } + return false; +} + + +/* +================ +idPlayer::UpdateLocation + +Searches nearby locations +================ +*/ +void idPlayer::UpdateLocation() { + + if ( hud ) { + hud->UpdateLocation( this ); + } +} + +/* +================ +idPlayer::ClearFocus + +Clears the focus cursor +================ +*/ +void idPlayer::ClearFocus() { + focusCharacter = NULL; + focusGUIent = NULL; + focusUI = NULL; + focusVehicle = NULL; + talkCursor = 0; +} + +/* +================ +idPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event +================ +*/ +void idPlayer::UpdateFocus() { + idClipModel *clipModelList[ MAX_GENTITIES ]; + idClipModel *clip; + int listedClipModels; + idEntity *oldFocus; + idEntity *ent; + idUserInterface *oldUI; + idAI *oldChar; + int oldTalkCursor; + idAFEntity_Vehicle *oldVehicle; + int i, j; + idVec3 start, end; + bool allowFocus; + const char *command; + trace_t trace; + guiPoint_t pt; + const idKeyValue *kv; + sysEvent_t ev; + idUserInterface *ui; + + if ( gameLocal.inCinematic ) { + return; + } + + // only update the focus character when attack button isn't pressed so players + // can still chainsaw NPC's + if ( common->IsMultiplayer() || ( !focusCharacter && ( usercmd.buttons & BUTTON_ATTACK ) ) ) { + allowFocus = false; + } else { + allowFocus = true; + } + + oldFocus = focusGUIent; + oldUI = focusUI; + oldChar = focusCharacter; + oldTalkCursor = talkCursor; + oldVehicle = focusVehicle; + + if ( focusTime <= gameLocal.time ) { + ClearFocus(); + } + + // don't let spectators interact with GUIs + if ( spectating ) { + return; + } + + start = GetEyePosition(); + end = start + firstPersonViewAxis[0] * 80.0f; + + // player identification -> names to the hud + if ( common->IsMultiplayer() && IsLocallyControlled() ) { + idVec3 end = start + viewAngles.ToForward() * 768.0f; + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + int iclient = -1; + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) { + iclient = trace.c.entityNum; + } + if ( MPAim != iclient ) { + lastMPAim = MPAim; + MPAim = iclient; + lastMPAimTime = gameLocal.realClientTime; + } + } + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + if ( allowFocus ) { + if ( ent->IsType( idAFAttachment::Type ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body != NULL && body->IsType( idAI::Type ) && ( static_cast( body )->GetTalkState() >= TALK_OK ) ) { + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) { + ClearFocus(); + focusCharacter = static_cast( body ); + talkCursor = 1; + focusTime = gameLocal.time + FOCUS_TIME; + break; + } + } + continue; + } + + if ( ent->IsType( idAI::Type ) ) { + if ( static_cast( ent )->GetTalkState() >= TALK_OK ) { + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) { + ClearFocus(); + focusCharacter = static_cast( ent ); + talkCursor = 1; + focusTime = gameLocal.time + FOCUS_TIME; + break; + } + } + continue; + } + + if ( ent->IsType( idAFEntity_Vehicle::Type ) ) { + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) { + ClearFocus(); + focusVehicle = static_cast( ent ); + focusTime = gameLocal.time + FOCUS_TIME; + break; + } + continue; + } + } + + if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) { + continue; + } + + if ( ent->spawnArgs.GetBool( "inv_item" ) ) { + // don't allow guis on pickup items focus + continue; + } + + pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end ); + if ( pt.x != -1 ) { + // we have a hit + renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId == 1 ) { + ui = focusGUIrenderEntity->gui[ 0 ]; + } else if ( pt.guiId == 2 ) { + ui = focusGUIrenderEntity->gui[ 1 ]; + } else { + ui = focusGUIrenderEntity->gui[ 2 ]; + } + + if ( ui == NULL ) { + continue; + } + + ClearFocus(); + focusGUIent = ent; + focusUI = ui; + + if ( oldFocus != ent ) { + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + focusUI->SetStateInt( "inv_count", inventory.items.Num() ); + for ( j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + const char *iname = item->GetString( "inv_name" ); + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + focusUI->SetStateString( va( "inv_name_%i", j), iname ); + focusUI->SetStateString( va( "inv_icon_%i", j), iicon ); + focusUI->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + focusUI->SetStateInt( iname, 1 ); + } + + + for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) { + const char *p = inventory.pdaSecurity[ j ]; + if ( p && *p ) { + focusUI->SetStateInt( p, 1 ); + } + } + + int powerCellCount = 0; + for ( j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + if(item->GetInt("inv_powercell")) { + powerCellCount++; + } + } + focusUI->SetStateInt( "powercell_count", powerCellCount ); + + int staminapercentage = ( int )( 100.0f * stamina / pm_stamina.GetFloat() ); + focusUI->SetStateString( "player_health", va("%i", health ) ); + focusUI->SetStateString( "player_stamina", va( "%i%%", staminapercentage ) ); + focusUI->SetStateString( "player_armor", va( "%i%%", inventory.armor ) ); + + kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL ); + while ( kv ) { + focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); + kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv ); + } + } + + // clamp the mouse to the corner + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = focusUI->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = focusUI->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + focusTime = gameLocal.time + FOCUS_GUI_TIME; + break; + } + } + + if ( focusGUIent && focusUI ) { + if ( !oldFocus || oldFocus != focusGUIent ) { + command = focusUI->Activate( true, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + // HideTip(); + // HideObjective(); + } + } else if ( oldFocus && oldUI ) { + command = oldUI->Activate( false, gameLocal.time ); + HandleGuiCommands( oldFocus, command ); + StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } + + if ( hud ) { + hud->SetCursorState( this, CURSOR_TALK, talkCursor ); + } + + if ( oldChar != focusCharacter && hud ) { + if ( focusCharacter ) { + hud->SetCursorText( "#str_02036", focusCharacter->spawnArgs.GetString( "npc_name", "Joe" ) ); + hud->UpdateCursorState(); + } else { + hud->SetCursorText( "", "" ); + hud->UpdateCursorState(); + } + } +} + +/* +================= +idPlayer::CrashLand + +Check for hard landings that generate sound events +================= +*/ +void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + idVec3 origin, velocity; + idVec3 gravityVector, gravityNormal; + float delta; + float hardDelta, fatalDelta, softDelta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + waterLevel_t waterLevel; + bool noDamage; + + AI_SOFTLANDING = false; + AI_HARDLANDING = false; + + // if the player is not on the ground + if ( !physicsObj.HasGroundContacts() ) { + return; + } + + gravityNormal = physicsObj.GetGravityNormal(); + + // if the player wasn't going down + if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) { + return; + } + + waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + noDamage = false; + for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) { + const contactInfo_t &contact = physicsObj.GetContact( i ); + if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) { + noDamage = true; + StartSound( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } + } + + origin = GetPhysics()->GetOrigin(); + gravityVector = physicsObj.GetGravity(); + + // calculate the exact velocity on landing + dist = ( origin - oldOrigin ) * -gravityNormal; + vel = oldVelocity * -gravityNormal; + acc = -gravityVector.Length(); + + a = acc / 2.0f; + b = vel; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001; + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < 1.0f ) { + return; + } + + // allow falling a bit further for multiplayer + if ( common->IsMultiplayer() ) { + fatalDelta = 75.0f; + hardDelta = 50.0f; + softDelta = 45.0f; + } else { + fatalDelta = 65.0f; + hardDelta = 45.0f; + softDelta = 30.0f; + } + + if ( delta > fatalDelta ) { + AI_HARDLANDING = true; + landChange = -32; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 ); + } + } else if ( delta > hardDelta ) { + AI_HARDLANDING = true; + landChange = -24; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 ); + } + } else if ( delta > softDelta ) { + AI_HARDLANDING = true; + landChange = -16; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 ); + } + } else if ( delta > 7 ) { + AI_SOFTLANDING = true; + landChange = -8; + landTime = gameLocal.time; + } else if ( delta > 3 ) { + // just walk on + } +} + +/* +=============== +idPlayer::BobCycle +=============== +*/ +void idPlayer::BobCycle( const idVec3 &pushVelocity ) { + float bobmove; + int old, deltaTime; + idVec3 vel, gravityDir, velocity; + idMat3 viewaxis; + float bob; + float delta; + float speed; + float f; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + velocity = physicsObj.GetLinearVelocity() - pushVelocity; + + gravityDir = physicsObj.GetGravityNormal(); + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + // do not evaluate the bob for other clients + // when doing a spectate follow, don't do any weapon bobbing + if ( common->IsClient() && !IsLocallyControlled() ) { + viewBobAngles.Zero(); + viewBob.Zero(); + return; + } + + if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( common->IsMultiplayer() && spectating ) ) { + // airborne + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) { + // start at beginning of cycle again + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + if ( physicsObj.IsCrouching() ) { + bobmove = pm_crouchbob.GetFloat(); + // ducked characters never play footsteps + } else { + // vary the bobbing based on the speed of the player + bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac; + } + + // check for footstep / splash sounds + old = bobCycle; + bobCycle = (int)( old + bobmove * ( gameLocal.time - gameLocal.previousTime ) ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + + // add angles based on velocity + delta = velocity * viewaxis[0]; + viewBobAngles.pitch += delta * pm_runpitch.GetFloat(); + + delta = velocity * viewaxis[1]; + viewBobAngles.roll -= delta * pm_runroll.GetFloat(); + + // add angles based on bob + // make sure the bob is visible even at low speeds + speed = xyspeed > 200 ? xyspeed : 200; + + delta = bobfracsin * pm_bobpitch.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching + } + viewBobAngles.pitch += delta; + delta = bobfracsin * pm_bobroll.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching accentuates roll + } + if ( bobFoot & 1 ) { + delta = -delta; + } + viewBobAngles.roll += delta; + + // calculate position for view bobbing + viewBob.Zero(); + + if ( physicsObj.HasSteppedUp() ) { + + // check for stepping up before a previous step is completed + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp(); + } else { + stepUpDelta = physicsObj.GetStepUp(); + } + if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) { + stepUpDelta = 2.0f * pm_stepsize.GetFloat(); + } + stepUpTime = gameLocal.time; + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + + // if the player stepped up recently + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME ); + } + + // add bob height after any movement smoothing + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if ( bob > 6 ) { + bob = 6; + } + viewBob[2] += bob; + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + viewBob -= gravity * ( landChange * f ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + viewBob -= gravity * ( landChange * f ); + } +} + +/* +================ +idPlayer::UpdateDeltaViewAngles +================ +*/ +void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); + } + SetDeltaViewAngles( delta ); +} + +/* +================ +idPlayer::SetViewAngles +================ +*/ +void idPlayer::SetViewAngles( const idAngles &angles ) { + UpdateDeltaViewAngles( angles ); + viewAngles = angles; +} + +/* +================ +idPlayer::UpdateViewAngles +================ +*/ +void idPlayer::UpdateViewAngles() { + int i; + idAngles delta; + + if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 || objectiveSystemOpen ) ) { + // no view changes at all, but we still want to update the deltas or else when + // we get out of this mode, our view will snap to a kind of random angle + UpdateDeltaViewAngles( viewAngles ); + return; + } + + // if dead + if ( health <= 0 ) { + if ( pm_thirdPersonDeath.GetBool() ) { + viewAngles.roll = 0.0f; + viewAngles.pitch = 30.0f; + } else { + viewAngles.roll = 40.0f; + viewAngles.pitch = -15.0f; + } + return; + } + + // + + + // circularly clamp the angles with deltas + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); + if ( influenceActive == INFLUENCE_LEVEL3 ) { + viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) ); + } else { + viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ); + } + } + if ( !centerView.IsDone( gameLocal.time ) ) { + viewAngles.pitch = centerView.GetCurrentValue(gameLocal.time); + } + + // clamp the pitch + if ( noclip ) { + if ( viewAngles.pitch > 89.0f ) { + // don't let the player look down more than 89 degrees while noclipping + viewAngles.pitch = 89.0f; + } else if ( viewAngles.pitch < -89.0f ) { + // don't let the player look up more than 89 degrees while noclipping + viewAngles.pitch = -89.0f; + } + } else if ( mountedObject ) { + int yaw_min, yaw_max, varc; + + mountedObject->GetAngleRestrictions( yaw_min, yaw_max, varc ); + + if ( yaw_min < yaw_max ) { + viewAngles.yaw = idMath::ClampFloat( yaw_min, yaw_max, viewAngles.yaw ); + } else { + if ( viewAngles.yaw < 0 ) { + viewAngles.yaw = idMath::ClampFloat( -180.f, yaw_max, viewAngles.yaw ); + } else { + viewAngles.yaw = idMath::ClampFloat( yaw_min, 180.f, viewAngles.yaw ); + } + } + viewAngles.pitch = idMath::ClampFloat( -varc, varc, viewAngles.pitch ); + } else { + // don't let the player look up or down more than 90 degrees normally + const float restrict = 1.0f; + + viewAngles.pitch = std::min( viewAngles.pitch, pm_maxviewpitch.GetFloat() * restrict ); + viewAngles.pitch = std::max( viewAngles.pitch, pm_minviewpitch.GetFloat() * restrict ); + } + + UpdateDeltaViewAngles( viewAngles ); + + // orient the model towards the direction we're looking + SetAngles( idAngles( 0, viewAngles.yaw, 0 ) ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles; +} + +/* +============== +idPlayer::AdjustHeartRate + +Player heartrate works as follows + +DEF_HEARTRATE is resting heartrate + +Taking damage when health is above 75 adjusts heart rate by 1 beat per second +Taking damage when health is below 75 adjusts heart rate by 5 beats per second +Maximum heartrate from damage is MAX_HEARTRATE + +Firing a weapon adds 1 beat per second up to a maximum of COMBAT_HEARTRATE + +Being at less than 25% stamina adds 5 beats per second up to ZEROSTAMINA_HEARTRATE + +All heartrates are target rates.. the heart rate will start falling as soon as there have been no adjustments for 5 seconds +Once it starts falling it always tries to get to DEF_HEARTRATE + +The exception to the above rule is upon death at which point the rate is set to DYING_HEARTRATE and starts falling +immediately to zero + +Heart rate volumes go from zero ( -40 db for DEF_HEARTRATE to 5 db for MAX_HEARTRATE ) the volume is +scaled linearly based on the actual rate + +Exception to the above rule is once the player is dead, the dying heart rate starts at either the current volume if +it is audible or -10db and scales to 8db on the last few beats +============== +*/ +void idPlayer::AdjustHeartRate( int target, float timeInSecs, float delay, bool force ) { + + if ( heartInfo.GetEndValue() == target ) { + return; + } + + if ( AI_DEAD && !force ) { + return; + } + + lastHeartAdjust = gameLocal.time; + + heartInfo.Init( gameLocal.time + delay * 1000, timeInSecs * 1000, heartRate, target ); +} + +/* +============== +idPlayer::GetBaseHeartRate +============== +*/ +int idPlayer::GetBaseHeartRate() { + int base = idMath::Ftoi( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float)health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ ); + int rate = idMath::Ftoi( base + ( ZEROSTAMINA_HEARTRATE - base ) * ( 1.0f - stamina / pm_stamina.GetFloat() ) ); + int diff = ( lastDmgTime ) ? gameLocal.time - lastDmgTime : 99999; + rate += ( diff < 5000 ) ? ( diff < 2500 ) ? ( diff < 1000 ) ? 15 : 10 : 5 : 0; + return rate; +} + +/* +============== +idPlayer::SetCurrentHeartRate +============== +*/ +void idPlayer::SetCurrentHeartRate() { + + int base = idMath::Ftoi( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float) health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ ); + + if ( PowerUpActive( ADRENALINE )) { + heartRate = 135; + } else { + heartRate = idMath::Ftoi( heartInfo.GetCurrentValue( gameLocal.time ) ); + int currentRate = GetBaseHeartRate(); + if ( health >= 0 && gameLocal.time > lastHeartAdjust + 2500 ) { + AdjustHeartRate( currentRate, 2.5f, 0.0f, false ); + } + } + + int bps = idMath::Ftoi( 60.0f / heartRate * 1000.0f ); + if ( gameLocal.time - lastHeartBeat > bps ) { + int dmgVol = DMG_VOLUME; + int deathVol = DEATH_VOLUME; + int zeroVol = ZERO_VOLUME; + float pct = 0.0; + if ( heartRate > BASE_HEARTRATE && health > 0 ) { + pct = (float)(heartRate - base) / (MAX_HEARTRATE - base); + pct *= ((float)dmgVol - (float)zeroVol); + } else if ( health <= 0 ) { + pct = (float)(heartRate - DYING_HEARTRATE) / (BASE_HEARTRATE - DYING_HEARTRATE); + if ( pct > 1.0f ) { + pct = 1.0f; + } else if (pct < 0.0f) { + pct = 0.0f; + } + pct *= ((float)deathVol - (float)zeroVol); + } + + pct += (float)zeroVol; + + if ( pct != zeroVol ) { + StartSound( "snd_heartbeat", SND_CHANNEL_HEART, SSF_PRIVATE_SOUND, false, NULL ); + // modify just this channel to a custom volume + soundShaderParms_t parms; + memset( &parms, 0, sizeof( parms ) ); + parms.volume = pct; + refSound.referenceSound->ModifySound( SND_CHANNEL_HEART, &parms ); + } + + lastHeartBeat = gameLocal.time; + } +} + +/* +============== +idPlayer::UpdateAir +============== +*/ +void idPlayer::UpdateAir() { + if ( health <= 0 ) { + return; + } + + // see if the player is connected to the info_vacuum + bool newAirless = false; + + if ( gameLocal.vacuumAreaNum != -1 ) { + int num = GetNumPVSAreas(); + if ( num > 0 ) { + int areaNum; + + // if the player box spans multiple areas, get the area from the origin point instead, + // otherwise a rotating player box may poke into an outside area + if ( num == 1 ) { + const int *pvsAreas = GetPVSAreas(); + areaNum = pvsAreas[0]; + } else { + areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() ); + } + newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR ); + } + } + + if ( PowerUpActive( ENVIROTIME ) ) { + newAirless = false; + } + + if ( newAirless ) { + if ( !airless ) { + StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL ); + } + airMsec -= ( gameLocal.time - gameLocal.previousTime ); + if ( airMsec < 0 ) { + airMsec = 0; + // check for damage + const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false ); + int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f ); + if ( gameLocal.time > lastAirDamage + dmgTiming ) { + Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 ); + lastAirDamage = gameLocal.time; + } + } + + } else { + if ( airless ) { + StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + } + airMsec += ( gameLocal.time - gameLocal.previousTime ); // regain twice as fast as lose + if ( airMsec > pm_airMsec.GetInteger() ) { + airMsec = pm_airMsec.GetInteger(); + } + } + + airless = newAirless; + + if ( hud ) { + hud->UpdateOxygen( airless, 100 * airMsec / pm_airMsec.GetInteger() ); + } +} + +void idPlayer::UpdatePowerupHud() { + + if ( health <= 0 ) { + return; + } + + if( lastHudPowerup != hudPowerup ) { + + if( hudPowerup == -1 ) { + //The powerup hud should be turned off + // TODO_SPARTY: powerups?? + //if ( hud ) { + // hud->HandleNamedEvent( "noPowerup" ); + //} + } else { + //Turn the pwoerup hud on + // TODO_SPARTY: powerups?? + //if ( hud ) { + // hud->HandleNamedEvent( "Powerup" ); + //} + } + + lastHudPowerup = hudPowerup; + } + + if ( hudPowerup != -1 && hudPowerup < MAX_POWERUPS ) { + if ( PowerUpActive( hudPowerup ) ) { + //int remaining = inventory.powerupEndTime[ hudPowerup ] - gameLocal.time; + //int filledbar = idMath::ClampInt( 0, hudPowerupDuration, remaining ); + + // TODO_SPARTY: powerups?? + //if ( hud ) { + // hud->SetStateInt( "player_powerup", 100 * filledbar / hudPowerupDuration ); + // hud->SetStateInt( "player_poweruptime", remaining / 1000 ); + //} + } + } +} + +/* +============== +idPlayer::GetPDA +============== + */ +const idDeclPDA * idPlayer::GetPDA() const { + if ( inventory.pdas.Num() > 0 ) { + return inventory.pdas[ 0 ]; + } else { + return NULL; + } +} + + +/* +============== +idPlayer::GetVideo +============== +*/ +const idDeclVideo *idPlayer::GetVideo( int index ) { + if ( index >= 0 && index < inventory.videos.Num() ) { + return inventory.videos[index]; + } + return NULL; +} + +/* +============== +idPlayer::TogglePDA +============== +*/ +void idPlayer::TogglePDA() { + + if ( inventory.pdas.Num() == 0 ) { + ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true ); + return; + } + + if ( pdaMenu != NULL ) { + objectiveSystemOpen = !objectiveSystemOpen; + pdaMenu->ActivateMenu( objectiveSystemOpen ); + + if ( objectiveSystemOpen ) { + if ( hud ) { + hud->ClearNewPDAInfo(); + } + } + } +} + +/* +============== +idPlayer::Spectate +============== +*/ +void idPlayer::Spectate( bool spectate, bool force ) { + spectating = spectate; + + if ( spectating ) { + // join the spectators + ClearPowerUps(); + spectator = this->entityNumber; + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.DisableClip(); + FlashlightOff(); + Hide(); + Event_DisableWeapon(); + + // Raise me up by a little bit. if i'm the local client. + if( IsLocallyControlled() ) { + SetSpectateOrigin(); + } + + HideRespawnHudMessage(); + + idLib::Printf( "DMP _ GENERAL :> Player %d Spectating \n", entityNumber ); + } else { + // put everything back together again + currentWeapon = -1; // to make sure the def will be loaded if necessary + Show(); + Event_EnableWeapon(); + idLib::Printf( "DMP _ GENERAL :> Player %d Not Spectating \n", entityNumber ); + SetEyeHeight( pm_normalviewheight.GetFloat() ); + } + SetClipModel(); +} + +/* +============== +idPlayer::SetClipModel +============== +*/ +void idPlayer::SetClipModel() { + idBounds bounds; + + if ( spectating ) { + bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + } else { + bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 ); + bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() ); + } + // the origin of the clip model needs to be set before calling SetClipModel + // otherwise our physics object's current origin value gets reset to 0 + idClipModel *newClip; + if ( pm_usecylinder.GetBool() ) { + newClip = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( idTraceModel( bounds, 8 ) ); + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); + } else { + newClip = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( idTraceModel( bounds ) ); + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); + } +} + +/* +============== +idPlayer::UseVehicle +============== +*/ +void idPlayer::UseVehicle() { + trace_t trace; + idVec3 start, end; + idEntity *ent; + + if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) { + Show(); + static_cast(GetBindMaster())->Use( this ); + } else { + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 80.0f; + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this ); + if ( trace.fraction < 1.0f ) { + ent = gameLocal.entities[ trace.c.entityNum ]; + if ( ent && ent->IsType( idAFEntity_Vehicle::Type ) ) { + Hide(); + static_cast(ent)->Use( this ); + } + } + } +} + +/* +============== +idPlayer::PerformImpulse +============== +*/ +void idPlayer::PerformImpulse( int impulse ) { + bool isIntroMap = ( idStr::FindText( gameLocal.GetMapFileName(), "mars_city1" ) >= 0 ); + + // Normal 1 - 0 Keys. + if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 && !isIntroMap ) { + SelectWeapon( impulse, false ); + return; + } + + // DPAD Weapon Quick Select + if ( impulse >= IMPULSE_28 && impulse <= IMPULSE_31 && !isIntroMap ) { + SelectWeapon( impulse, false ); + return; + } + + switch( impulse ) { + case IMPULSE_13: { + Reload(); + break; + } + case IMPULSE_14: { + if ( !isIntroMap ) { + NextWeapon(); + } + break; + } + case IMPULSE_15: { + if ( !isIntroMap ) { + PrevWeapon(); + } + break; + } + case IMPULSE_16: { + if( flashlight.IsValid() ) { + if ( flashlight.GetEntity()->lightOn ) { + FlashlightOff(); + } else if ( !spectating && weaponEnabled && !hiddenWeapon && !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + FlashlightOn(); + } + } + break; + } + case IMPULSE_19: { + // when we're not in single player, IMPULSE_19 is used for showScores + // otherwise it opens the pda + if ( !common->IsMultiplayer() ) { +#if !defined(ID_RETAIL) && !defined(ID_RETAIL_INTERNAL) + if ( !common->KeyState( 56 ) ) { // don't toggle PDA when LEFT ALT is down +#endif + if ( objectiveSystemOpen ) { + TogglePDA(); + } else if ( weapon_pda >= 0 ) { + SelectWeapon( weapon_pda, true ); + } +#if !defined(ID_RETAIL) && !defined(ID_RETAIL_INTERNAL) + } +#endif + } else { + if ( IsLocallyControlled() ) { + gameLocal.mpGame.SetScoreboardActive( true ); + } + } + break; + } + case IMPULSE_22: { + if ( common->IsClient() || IsLocallyControlled() ) { + gameLocal.mpGame.ToggleSpectate(); + } + break; + } + case IMPULSE_25: { + if ( common->IsServer() && gameLocal.mpGame.IsGametypeFlagBased() && (gameLocal.serverInfo.GetInt( "si_midnight" ) == 2) ) { + if ( enviroSuitLight.IsValid() ) { + enviroSuitLight.GetEntity()->PostEventMS( &EV_Remove, 0 ); + enviroSuitLight = NULL; + } else { + const idDict *lightDef = gameLocal.FindEntityDefDict( "envirosuit_light", false ); + if ( lightDef ) { + idEntity *temp = static_cast(enviroSuitLight.GetEntity()); + idAngles lightAng = firstPersonViewAxis.ToAngles(); + idVec3 lightOrg = firstPersonViewOrigin; + + idVec3 enviroOffset = lightDef->GetVector( "enviro_offset" ); + idVec3 enviroAngleOffset = lightDef->GetVector( "enviro_angle_offset" ); + + gameLocal.SpawnEntityDef( *lightDef, &temp, false ); + enviroSuitLight = static_cast(temp); + + enviroSuitLight.GetEntity()->fl.networkSync = true; + + lightOrg += (enviroOffset.x * firstPersonViewAxis[0]); + lightOrg += (enviroOffset.y * firstPersonViewAxis[1]); + lightOrg += (enviroOffset.z * firstPersonViewAxis[2]); + lightAng.pitch += enviroAngleOffset.x; + lightAng.yaw += enviroAngleOffset.y; + lightAng.roll += enviroAngleOffset.z; + + enviroSuitLight.GetEntity()->GetPhysics()->SetOrigin( lightOrg ); + enviroSuitLight.GetEntity()->GetPhysics()->SetAxis( lightAng.ToMat3() ); + + enviroSuitLight.GetEntity()->UpdateVisuals(); + enviroSuitLight.GetEntity()->Present(); + } + } + } + break; + } + //Hack so the chainsaw will work in MP + case IMPULSE_27: { + SelectWeapon(18, false); + break; + } + } +} + +/* +============== +idPlayer::EvaluateControls +============== +*/ +void idPlayer::EvaluateControls() { + // check for respawning + if ( health <= 0 && !g_testDeath.GetBool() ) { + if ( common->IsMultiplayer() ) { + // in MP, idMultiplayerGame decides spawns + if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) { + forceRespawn = true; + } else if ( gameLocal.time > maxRespawnTime ) { + forceRespawn = true; + } + } else { + // in single player, we let the session handle restarting the level or loading a game + if ( gameLocal.time > minRespawnTime ) { + gameLocal.sessionCommand = "died"; + } + } + } + + if ( usercmd.impulseSequence != oldImpulseSequence ) { + PerformImpulse( usercmd.impulse ); + } + + if ( forceScoreBoard ) { + gameLocal.mpGame.SetScoreboardActive( true ); + } + + oldImpulseSequence = usercmd.impulseSequence; + + AdjustSpeed(); + + // update the viewangles + UpdateViewAngles(); +} + +/* +============== +idPlayer::AdjustSpeed +============== +*/ +void idPlayer::AdjustSpeed() { + float speed; + float rate; + + if ( spectating ) { + speed = pm_spectatespeed.GetFloat(); + bobFrac = 0.0f; + } else if ( noclip ) { + speed = pm_noclipspeed.GetFloat(); + bobFrac = 0.0f; + } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && !( usercmd.buttons & BUTTON_CROUCH ) ) { + if ( !common->IsMultiplayer() && !physicsObj.IsCrouching() && !PowerUpActive( ADRENALINE ) ) { + stamina -= MS2SEC( gameLocal.time - gameLocal.previousTime ); + } + if ( stamina < 0 ) { + stamina = 0; + } + if ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ) { + bobFrac = 1.0f; + } else if ( pm_staminathreshold.GetFloat() <= 0.0001f ) { + bobFrac = 0.0f; + } else { + bobFrac = stamina / pm_staminathreshold.GetFloat(); + } + speed = pm_walkspeed.GetFloat() * ( 1.0f - bobFrac ) + pm_runspeed.GetFloat() * bobFrac; + } else { + rate = pm_staminarate.GetFloat(); + + // increase 25% faster when not moving + if ( ( usercmd.forwardmove == 0 ) && ( usercmd.rightmove == 0 ) && ( !physicsObj.OnLadder() || ( ( usercmd.buttons & (BUTTON_CROUCH|BUTTON_JUMP) ) == 0 ) ) ) { + rate *= 1.25f; + } + + stamina += rate * MS2SEC( gameLocal.time - gameLocal.previousTime ); + if ( stamina > pm_stamina.GetFloat() ) { + stamina = pm_stamina.GetFloat(); + } + speed = pm_walkspeed.GetFloat(); + bobFrac = 0.0f; + } + + speed *= PowerUpModifier(SPEED); + + if ( influenceActive == INFLUENCE_LEVEL3 ) { + speed *= 0.33f; + } + + physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() ); +} + +/* +============== +idPlayer::AdjustBodyAngles +============== +*/ +void idPlayer::AdjustBodyAngles() { + idMat3 lookAxis; + idMat3 legsAxis; + bool blend; + float diff; + float frac; + float upBlend; + float forwardBlend; + float downBlend; + + if ( health < 0 ) { + return; + } + + blend = true; + + if ( !physicsObj.HasGroundContacts() ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else if ( usercmd.forwardmove < 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = false; + } else if ( usercmd.forwardmove > 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = true; + } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) { + if ( !legsForward ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() ); + } else { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() ); + } + } else if ( usercmd.rightmove != 0 ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else { + legsForward = true; + diff = idMath::Fabs( idealLegsYaw - legsYaw ); + idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw ); + if ( diff < 0.1f ) { + legsYaw = idealLegsYaw; + blend = false; + } + } + + if ( !physicsObj.IsCrouching() ) { + legsForward = true; + } + + oldViewYaw = viewAngles.yaw; + + AI_TURN_LEFT = false; + AI_TURN_RIGHT = false; + if ( idealLegsYaw < -45.0f ) { + idealLegsYaw = 0; + AI_TURN_RIGHT = true; + blend = true; + } else if ( idealLegsYaw > 45.0f ) { + idealLegsYaw = 0; + AI_TURN_LEFT = true; + blend = true; + } + + if ( blend ) { + legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f; + } + legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3(); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis ); + + // calculate the blending between down, straight, and up + frac = viewAngles.pitch / 90.0f; + if ( frac > 0.0f ) { + downBlend = frac; + forwardBlend = 1.0f - frac; + upBlend = 0.0f; + } else { + downBlend = 0.0f; + forwardBlend = 1.0f + frac; + upBlend = -frac; + } + + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend ); + + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend ); +} + +/* +============== +idPlayer::InitAASLocation +============== +*/ +void idPlayer::InitAASLocation() { + int i; + int num; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + GetFloorPos( 64.0f, origin ); + + num = gameLocal.NumAAS(); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < aasLocation.Num(); i++ ) { + aasLocation[ i ].areaNum = 0; + aasLocation[ i ].pos = origin; + aas = gameLocal.GetAAS( i ); + if ( aas != NULL && aas->GetSettings() ) { + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + } + } +} + +/* +============== +idPlayer::SetAASLocation +============== +*/ +void idPlayer::SetAASLocation() { + int i; + int areaNum; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + if ( !GetFloorPos( 64.0f, origin ) ) { + return; + } + + for( i = 0; i < aasLocation.Num(); i++ ) { + aas = gameLocal.GetAAS( i ); + if ( !aas ) { + continue; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aasLocation[ i ].pos = origin; + aasLocation[ i ].areaNum = areaNum; + } + } +} + +/* +============== +idPlayer::GetAASLocation +============== +*/ +void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + int i; + + if ( aas != NULL ) { + for( i = 0; i < aasLocation.Num(); i++ ) { + if ( aas == gameLocal.GetAAS( i ) ) { + areaNum = aasLocation[ i ].areaNum; + pos = aasLocation[ i ].pos; + return; + } + } + } + + areaNum = 0; + pos = physicsObj.GetOrigin(); +} + +/* +============== +idPlayer::Move_Interpolated +============== +*/ +void idPlayer::Move_Interpolated( float fraction ) { + + float newEyeOffset; + idVec3 oldOrigin; + idVec3 oldVelocity; + idVec3 pushVelocity; + + // save old origin and velocity for crashlanding + oldOrigin = physicsObj.GetOrigin(); + oldVelocity = physicsObj.GetLinearVelocity(); + pushVelocity = physicsObj.GetPushedLinearVelocity(); + + // set physics variables + physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() ); + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( health <= 0 ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FREEZE ); + } else if ( mountedObject ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else if ( health <= 0 ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + + { + idVec3 org; + idMat3 axis; + GetViewPos( org, axis ); + + physicsObj.SetPlayerInput( usercmd, axis[0] ); + } + + // FIXME: physics gets disabled somehow + BecomeActive( TH_PHYSICS ); + InterpolatePhysics( fraction ); + + // update our last valid AAS location for the AI + SetAASLocation(); + + if ( spectating ) { + newEyeOffset = 0.0f; + } else if ( health <= 0 ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + if ( spectating ) { + SetEyeHeight( newEyeOffset ); + } else { + // smooth out duck height changes + SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) ); + } + } + + if ( AI_JUMP ) { + // bounce the view weapon + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[2] = 200; + acc->dir[0] = acc->dir[1] = 0; + } + + if ( AI_ONLADDER ) { + int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE; + int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE; + + if ( old_rung != new_rung ) { + StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + BobCycle( pushVelocity ); + CrashLand( oldOrigin, oldVelocity ); + +} + +/* +============== +idPlayer::Move +============== +*/ +void idPlayer::Move() { + float newEyeOffset; + idVec3 oldOrigin; + idVec3 oldVelocity; + idVec3 pushVelocity; + + // save old origin and velocity for crashlanding + oldOrigin = physicsObj.GetOrigin(); + oldVelocity = physicsObj.GetLinearVelocity(); + pushVelocity = physicsObj.GetPushedLinearVelocity(); + + // set physics variables + physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() ); + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( health <= 0 ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FREEZE ); + } else if ( mountedObject ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else if ( health <= 0 ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + + { + idVec3 org; + idMat3 axis; + GetViewPos( org, axis ); + + physicsObj.SetPlayerInput( usercmd, axis[0] ); + } + + // FIXME: physics gets disabled somehow + BecomeActive( TH_PHYSICS ); + RunPhysics(); + + // update our last valid AAS location for the AI + SetAASLocation(); + + if ( spectating ) { + newEyeOffset = 0.0f; + } else if ( health <= 0 ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + if ( spectating ) { + SetEyeHeight( newEyeOffset ); + } else { + // smooth out duck height changes + SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) ); + } + } + + if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + AI_CROUCH = false; + AI_ONGROUND = ( influenceActive == INFLUENCE_LEVEL2 ); + AI_ONLADDER = false; + AI_JUMP = false; + } else { + AI_CROUCH = physicsObj.IsCrouching(); + AI_ONGROUND = physicsObj.HasGroundContacts(); + AI_ONLADDER = physicsObj.OnLadder(); + AI_JUMP = physicsObj.HasJumped(); + + // check if we're standing on top of a monster and give a push if we are + idEntity *groundEnt = physicsObj.GetGroundEntity(); + if ( groundEnt != NULL && groundEnt->IsType( idAI::Type ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() < 0.1f ) { + vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2(); + vel.ToVec2().NormalizeFast(); + vel.ToVec2() *= pm_walkspeed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( AI_JUMP ) { + // bounce the view weapon + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[2] = 200; + acc->dir[0] = acc->dir[1] = 0; + } + + if ( AI_ONLADDER ) { + int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE; + int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE; + + if ( old_rung != new_rung ) { + StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + BobCycle( pushVelocity ); + CrashLand( oldOrigin, oldVelocity ); +} + +/* +======================== +idPlayer::AllowClientAuthPhysics +======================== +*/ +bool idPlayer::AllowClientAuthPhysics() { + // note respawn count > 1: respawn should be called twice - once for initial spawn and once for actual respawn by game mode + // TODO: I don't think doom 3 will need to care about the respawn count. + return ( usercmd.serverGameMilliseconds > serverOverridePositionTime && commonLocal.GetUCmdMgr().HasUserCmdForPlayer( entityNumber ) ); +} + +/* +======================== +idPlayer::RunPhysics_RemoteClientCorrection +======================== +*/ +void idPlayer::RunPhysics_RemoteClientCorrection() { + + if ( !AllowClientAuthPhysics() ) { + // We are still overriding client's position + if ( pm_clientAuthoritative_debug.GetBool() ) { + //clientGame->renderWorld->DebugPoint( idColor::colorRed, GetOrigin() ); + idLib::Printf("[%d]Ignoring client auth: cmd.serverTime: %d overrideTime: %d \n", entityNumber, usercmd.serverGameMilliseconds, serverOverridePositionTime ); + } + return; + } + + + + // Client is on a pusher... ignore him so he doesn't lag behind + bool becameUnlocked = false; + if ( physicsObj.ClientPusherLocked( becameUnlocked ) ) { + + // Check and see how far we've diverged. + idVec3 cmdPos( usercmd.pos[0], usercmd.pos[1], usercmd.pos[2] ); + idVec3 newOrigin = physicsObj.GetOrigin(); + + idVec3 divergeVec = cmdPos - newOrigin; + idLib::Printf( "Client Divergence: %s Length: %2f\n", divergeVec.ToString( 3 ), divergeVec.Length() ); + + // if the client Diverges over a certain amount, snap him back + if( divergeVec.Length() < pm_clientAuthoritative_Divergence.GetFloat() ) { + return; + } + + } + if ( becameUnlocked ) { + // Client just got off of a mover, wait before listening to him + serverOverridePositionTime = gameLocal.GetServerGameTimeMs(); + return; + } + + + // Correction + { + idVec3 newOrigin = physicsObj.GetOrigin(); + idVec3 cmdPos( usercmd.pos[0], usercmd.pos[1], usercmd.pos[2] ); + idVec3 desiredPos = cmdPos; + + float delta = ( desiredPos - newOrigin ).Length(); + // ignore small differences in Z: this can cause player to not have proper ground contacts which messes up + // velocity/acceleration calculation. If this hack doesn't work out, will may need more precision for at least + // the Z component of the client's origin. + if ( idMath::Fabs( desiredPos.z - newOrigin.z ) < pm_clientAuthoritative_minDistZ.GetFloat() ) { + if ( pm_clientAuthoritative_debug.GetBool() ) { + //idLib::Printf("[%d]Remote client physics: ignore small z delta: %f\n", usercmd.clientGameFrame, ( desiredPos.z - newOrigin.z ) ); + } + desiredPos.z = newOrigin.z; + } + + // Origin + if ( delta > pm_clientAuthoritative_minDist.GetFloat() ) { + + if ( pm_clientAuthoritative_Lerp.GetFloat() > 0.0f ) { + desiredPos.x = idMath::LerpToWithScale( newOrigin.x, desiredPos.x, pm_clientAuthoritative_Lerp.GetFloat() ); + desiredPos.y = idMath::LerpToWithScale( newOrigin.y, desiredPos.y, pm_clientAuthoritative_Lerp.GetFloat() ); + } + + // Set corrected position immediately if non deferred + physicsObj.SetOrigin( desiredPos ); + + if ( pm_clientAuthoritative_debug.GetBool() && delta > pm_clientAuthoritative_warnDist.GetFloat() ) { + idLib::Warning("Remote client player physics: delta movement for frame was %f units", delta ); + gameRenderWorld->DebugLine( colorRed, newOrigin, desiredPos ); + } + } + if ( pm_clientAuthoritative_debug.GetBool() ) { + //idLib::Printf( "[%d]Remote client player physics delta: %.2f. forward: %d pos <%.2f, %.2f, %.2f> \n", usercmd.clientGameFrame, delta, (int)usercmd.forwardmove, desiredPos.x, desiredPos.y, desiredPos.z ); + gameRenderWorld->DebugLine( colorRed, newOrigin, desiredPos ); + //gameRenderWorld->DebugPoint( colorBlue, cmdPos ); + } + + // Set velocity if significantly different than client. + const float serverSpeedSquared = physicsObj.GetLinearVelocity().LengthSqr(); + const float clientSpeedSquared = usercmd.speedSquared; + + if ( std::abs( serverSpeedSquared - clientSpeedSquared ) > pm_clientAuthoritative_minSpeedSquared.GetFloat() ) { + idVec3 normalizedVelocity = physicsObj.GetLinearVelocity(); + + const float VELOCITY_EPSILON = 0.001f; + if ( normalizedVelocity.LengthSqr() > VELOCITY_EPSILON ) { + normalizedVelocity.Normalize(); + } + + physicsObj.SetLinearVelocity( normalizedVelocity * idMath::Sqrt( clientSpeedSquared ) ); + } + } +} + +/* +======================== +idPlayer::GetPhysicsTimeStep + +Uses the time from the usercmd in case the server is running at a slower engineHz +than the client. +======================== +*/ +int idPlayer::GetPhysicsTimeStep() const { + // if the ucDeltaMillisecond value looks wrong, use the game delta milliseconds + // This can happen if the user brings up the pause menu in SP + const int ucDeltaMilliseconds = usercmd.clientGameMilliseconds - oldCmd.clientGameMilliseconds; + if ( ucDeltaMilliseconds < 1 || ucDeltaMilliseconds > 20 ) { + return gameLocal.time - gameLocal.previousTime; + } else { + return ucDeltaMilliseconds; + } +} + +/* +============== +idPlayer::ShowRespawnHudMessage + +Called once when the minimum respawn time has passed after a player has died +so that we can display a message to the user. +============== +*/ +void idPlayer::ShowRespawnHudMessage() { + if ( IsLocallyControlled() ) { + hud->ShowRespawnMessage( true ); + } else { + // Clients show the hud message through a reliable message. + idBitMsg outMsg; + byte dummyData[1]; + outMsg.InitWrite( dummyData, sizeof( dummyData ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( 0 ); + session->GetActingGameStateLobbyBase().SendReliableToLobbyUser( gameLocal.lobbyUserIDs[entityNumber], GAME_RELIABLE_MESSAGE_RESPAWN_AVAILABLE, outMsg ); + } +} + +/* +============== +idPlayer::HideRespawnHudMessage + +Called once when we should remove the respawn message from the hud, +for example, when a player does respawn. +============== +*/ +void idPlayer::HideRespawnHudMessage() { + if ( IsLocallyControlled() ) { + hud->ShowRespawnMessage( false ); + } +} + +/* +============== +idPlayer::UpdateHud +============== +*/ +void idPlayer::UpdateHud() { + idPlayer *aimed; + + if ( !hud ) { + return; + } + + if ( !IsLocallyControlled() ) { + return; + } + + int c = inventory.pickupItemNames.Num(); + if ( c > 0 ) { + if ( hud != NULL && hud->IsPickupListReady() ) { + if ( inventory.nextItemPickup && gameLocal.time - inventory.nextItemPickup > 2000 ) { + inventory.nextItemNum = 1; + } + int i; + + int count = 5; + bool showNewPickups = false; + for ( i = 0; i < count; i++ ) { //_D3XP + if ( i < c ) { + hud->UpdatePickupInfo( i, inventory.pickupItemNames[0] ); + inventory.nextItemNum++; + showNewPickups = true; + } else { + hud->UpdatePickupInfo( i, "" ); + continue; + } + + inventory.nextItemPickup = gameLocal.time + 2500; + inventory.pickupItemNames.RemoveIndex( 0 ); + } + + if ( showNewPickups ) { + hud->ShowPickups(); + } + } + } + + if ( gameLocal.realClientTime == lastMPAimTime ) { + if ( MPAim != -1 && gameLocal.mpGame.IsGametypeTeamBased() /* CTF */ + && gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) + && static_cast< idPlayer * >( gameLocal.entities[ MPAim ] )->team == team ) { + aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] ); + + hud->TriggerHitTarget( true, session->GetActingGameStateLobbyBase().GetLobbyUserName( gameLocal.lobbyUserIDs[ MPAim ] ), aimed->team + 1 ); + MPAimHighlight = true; + MPAimFadeTime = 0; // no fade till loosing focus + } else if ( MPAimHighlight ) { + hud->TriggerHitTarget( false, "" ); + MPAimFadeTime = gameLocal.realClientTime; + MPAimHighlight = false; + } + } + if ( MPAimFadeTime ) { + assert( !MPAimHighlight ); + if ( gameLocal.realClientTime - MPAimFadeTime > 2000 ) { + MPAimFadeTime = 0; + } + } + + if ( common->IsMultiplayer() && IsLocallyControlled() ) { + + hud->ToggleLagged( isLagged ); + + // TODO_SPARTY: what is this projectile stuff for + //hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() ); + //if ( numProjectilesFired ) { + // hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) ); + //} else { + // hud->SetStateString( "projectilepct", "Hit % 0.0" ); + //} + + } +} + +/* +============== +idPlayer::UpdateDeathSkin +============== +*/ +void idPlayer::UpdateDeathSkin( bool state_hitch ) { + if ( !( common->IsMultiplayer() || g_testDeath.GetBool() ) ) { + return; + } + if ( health <= 0 ) { + if ( !doingDeathSkin ) { + deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" ); + doingDeathSkin = true; + renderEntity.noShadow = true; + if ( state_hitch ) { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + } else { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + } + UpdateVisuals(); + } + + // wait a bit before switching off the content + if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) { + SetCombatContents( false ); + deathClearContentsTime = 0; + } + } else { + renderEntity.noShadow = false; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + UpdateVisuals(); + doingDeathSkin = false; + } +} + +/* +============== +idPlayer::StartFxOnBone +============== +*/ +void idPlayer::StartFxOnBone( const char *fx, const char *bone ) { + idVec3 offset; + idMat3 axis; + jointHandle_t jointHandle = GetAnimator()->GetJointHandle( bone ); + + if ( jointHandle == INVALID_JOINT ) { + gameLocal.Printf( "Cannot find bone %s\n", bone ); + return; + } + + if ( GetAnimator()->GetJointTransform( jointHandle, gameLocal.time, offset, axis ) ) { + offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis(); + axis = axis * GetPhysics()->GetAxis(); + } + + idEntityFx::StartFx( fx, &offset, &axis, this, true ); +} + +/* +============== +idPlayer::HandleGuiEvents +============== +*/ +bool idPlayer::HandleGuiEvents( const sysEvent_t * ev ) { + + bool handled = false; + + if ( hudManager != NULL && hudManager->IsActive() ) { + handled = hudManager->HandleGuiEvent( ev ); + } + + if ( pdaMenu != NULL && pdaMenu->IsActive() ) { + handled = pdaMenu->HandleGuiEvent( ev ); + } + + return handled; +} + +/* +============== +idPlayer::UpdateLaserSight +============== +*/ +idCVar g_laserSightWidth( "g_laserSightWidth", "2.0", CVAR_FLOAT | CVAR_ARCHIVE, "laser sight beam width" ); +idCVar g_laserSightLength( "g_laserSightLength", "250", CVAR_FLOAT | CVAR_ARCHIVE, "laser sight beam length" ); + +void idPlayer::UpdateLaserSight() { + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + // In Multiplayer, weapon might not have been spawned yet. + if( weapon.GetEntity() == NULL ) { + return; + } + + if ( !IsGameStereoRendered() || + !weapon.GetEntity()->ShowCrosshair() || + AI_DEAD || + weapon->IsHidden() || + !weapon->GetMuzzlePositionWithHacks( muzzleOrigin, muzzleAxis ) ) { + // hide it + laserSightRenderEntity.allowSurfaceInViewID = -1; + if ( laserSightHandle == -1 ) { + laserSightHandle = gameRenderWorld->AddEntityDef( &laserSightRenderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( laserSightHandle, &laserSightRenderEntity ); + } + return; + } + + // program the beam model + + // only show in the player's view + laserSightRenderEntity.allowSurfaceInViewID = entityNumber+1; + laserSightRenderEntity.axis.Identity(); + + laserSightRenderEntity.origin = muzzleOrigin - muzzleAxis[0] * 2.0f; + idVec3 &target = *reinterpret_cast( &laserSightRenderEntity.shaderParms[SHADERPARM_BEAM_END_X] ); + target = muzzleOrigin + muzzleAxis[0] * g_laserSightLength.GetFloat(); + + laserSightRenderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = g_laserSightWidth.GetFloat(); + + if ( IsGameStereoRendered() && laserSightHandle == -1 ) { + laserSightHandle = gameRenderWorld->AddEntityDef( &laserSightRenderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( laserSightHandle, &laserSightRenderEntity ); + } +} + +/* +============== +idPlayer::Think + +Called every tic for each player +============== +*/ +void idPlayer::Think() { + playedTimeResidual += ( gameLocal.time - gameLocal.previousTime ); + playedTimeSecs += playedTimeResidual / 1000; + playedTimeResidual = playedTimeResidual % 1000; + + aimAssist.Update(); + + UpdatePlayerIcons(); + + UpdateSkinSetup(); + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // if this is the very first frame of the map, set the delta view angles + // based on the usercmd angles + if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) { + spawnAnglesSet = true; + SetViewAngles( spawnAngles ); + oldImpulseSequence = usercmd.impulseSequence; + } + + if ( mountedObject ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.buttons &= ~(BUTTON_JUMP|BUTTON_CROUCH); + } + + if ( objectiveSystemOpen || gameLocal.inCinematic || influenceActive ) { + if ( objectiveSystemOpen && AI_PAIN ) { + TogglePDA(); + } + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.buttons &= ~(BUTTON_JUMP|BUTTON_CROUCH); + } + + // log movement changes for weapon bobbing effects + if ( usercmd.forwardmove != oldCmd.forwardmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove; + acc->dir[1] = acc->dir[2] = 0; + } + + if ( usercmd.rightmove != oldCmd.rightmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[1] = usercmd.rightmove - oldCmd.rightmove; + acc->dir[0] = acc->dir[2] = 0; + } + + // zooming + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) { + if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) { + zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() ); + } else { + zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() ); + } + } + + // if we have an active gui, we will unrotate the view angles as + // we turn the mouse movements into gui events + idUserInterface *gui = ActiveGui(); + if ( gui && gui != focusUI ) { + RouteGuiMouse( gui ); + } + + // set the push velocity on the weapon before running the physics + if ( weapon.GetEntity() ) { + weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + CopyJointsFromBodyToHead(); + } + + if ( IsLocallyControlled() ) { + // Local player on the server. Do normal movement. + Move(); + } else { + // Server is processing a client. Run client's commands like normal... + Move(); + + // ...then correct if needed. + RunPhysics_RemoteClientCorrection(); + } + + if ( !g_stopTime.GetBool() ) { + + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers(); + } + + // not done on clients for various reasons. don't do it on server and save the sound channel for other things + if ( !common->IsMultiplayer() ) { + SetCurrentHeartRate(); + float scale = new_g_damageScale; + if ( g_useDynamicProtection.GetBool() && scale < 1.0f && gameLocal.time - lastDmgTime > 500 ) { + if ( scale < 1.0f ) { + scale += 0.05f; + } + if ( scale > 1.0f ) { + scale = 1.0f; + } + new_g_damageScale = scale; + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateScript(); + + // service animations + if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + AI_PAIN = false; + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeroson / camera view + CalculateRenderView(); + + inventory.UpdateArmor(); + + if ( spectating ) { + UpdateSpectating(); + } else if ( health > 0 ) { + UpdateWeapon(); + } + + UpdateFlashlight(); + + UpdateAir(); + + UpdatePowerupHud(); + + UpdateHud(); + + UpdatePowerUps(); + + UpdateDeathSkin( false ); + + if ( common->IsMultiplayer() ) { + DrawPlayerIcons(); + + if ( enviroSuitLight.IsValid() ) { + idAngles lightAng = firstPersonViewAxis.ToAngles(); + idVec3 lightOrg = firstPersonViewOrigin; + const idDict *lightDef = gameLocal.FindEntityDefDict( "envirosuit_light", false ); + + idVec3 enviroOffset = lightDef->GetVector( "enviro_offset" ); + idVec3 enviroAngleOffset = lightDef->GetVector( "enviro_angle_offset" ); + + lightOrg += (enviroOffset.x * firstPersonViewAxis[0]); + lightOrg += (enviroOffset.y * firstPersonViewAxis[1]); + lightOrg += (enviroOffset.z * firstPersonViewAxis[2]); + lightAng.pitch += enviroAngleOffset.x; + lightAng.yaw += enviroAngleOffset.y; + lightAng.roll += enviroAngleOffset.z; + + enviroSuitLight.GetEntity()->GetPhysics()->SetOrigin( lightOrg ); + enviroSuitLight.GetEntity()->GetPhysics()->SetAxis( lightAng.ToMat3() ); + enviroSuitLight.GetEntity()->UpdateVisuals(); + enviroSuitLight.GetEntity()->Present(); + } + } + + renderEntity_t * headRenderEnt = NULL; + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } + if ( headRenderEnt ) { + if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = NULL; + } + } + + if ( common->IsMultiplayer() || g_showPlayerShadow.GetBool() ) { + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } + // never cast shadows from our first-person muzzle flashes + renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + + Present(); + + UpdateDamageEffects(); + + LinkCombat(); + + playerView.CalculateShake(); + } + + if ( !( thinkFlags & TH_THINK ) ) { + gameLocal.Printf( "player %d not thinking?\n", entityNumber ); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + gameLocal.Printf( "%d: enemies\n", num ); + } + + inventory.RechargeAmmo(this); + + if(healthRecharge) { + int elapsed = gameLocal.time - lastHealthRechargeTime; + if(elapsed >= rechargeSpeed) { + int intervals = (gameLocal.time - lastHealthRechargeTime)/rechargeSpeed; + Give("health", va("%d", intervals), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + lastHealthRechargeTime += intervals*rechargeSpeed; + } + } + + // determine if portal sky is in pvs + gameLocal.portalSkyActive = gameLocal.pvs.CheckAreasForPortalSky( gameLocal.GetPlayerPVS(), GetPhysics()->GetOrigin() ); + + // stereo rendering laser sight that replaces the crosshair + UpdateLaserSight(); + + // Show the respawn hud message if necessary. + if ( common->IsMultiplayer() && ( minRespawnTime != maxRespawnTime ) ) { + if ( gameLocal.previousTime < minRespawnTime && minRespawnTime <= gameLocal.time ) { + // Server will show the hud message directly. + ShowRespawnHudMessage(); + } + } + + // Make sure voice groups are set to the right team + if ( common->IsMultiplayer() && session->GetState() >= idSession::INGAME && entityNumber < MAX_CLIENTS ) { // The entityNumber < MAX_CLIENTS seems to quiet the static analyzer + // Make sure we're on the right team (at the lobby level) + const int voiceTeam = spectating ? LOBBY_SPECTATE_TEAM_FOR_VOICE_CHAT : team; + + //idLib::Printf( "SERVER: Sending voice %i / %i\n", entityNumber, voiceTeam ); + + // Update lobby team + session->GetActingGameStateLobbyBase().SetLobbyUserTeam( gameLocal.lobbyUserIDs[ entityNumber ], voiceTeam ); + + // Update voice groups to match in case something changed + session->SetVoiceGroupsToTeams(); + } +} + +/* +================= +idPlayer::StartHealthRecharge +================= +*/ +void idPlayer::StartHealthRecharge(int speed) { + lastHealthRechargeTime = gameLocal.time; + healthRecharge = true; + rechargeSpeed = speed; +} + +/* +================= +idPlayer::StopHealthRecharge +================= +*/ +void idPlayer::StopHealthRecharge() { + healthRecharge = false; +} + +/* +================= +idPlayer::GetCurrentWeapon +================= +*/ +idStr idPlayer::GetCurrentWeapon() { + const char *weapon; + + if ( currentWeapon >= 0 ) { + weapon = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + return weapon; + } else { + return ""; + } +} + +/* +================= +idPlayer::CanGive +================= +*/ +bool idPlayer::CanGive( const char *statname, const char *value ) { + if ( AI_DEAD ) { + return false; + } + + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= inventory.maxHealth ) { + return false; + } + return true; + } else if ( !idStr::Icmp( statname, "stamina" ) ) { + if ( stamina >= 100 ) { + return false; + } + return true; + + } else if ( !idStr::Icmp( statname, "heartRate" ) ) { + return true; + + } else if ( !idStr::Icmp( statname, "air" ) ) { + if ( airMsec >= pm_airMsec.GetInteger() ) { + return false; + } + return true; + } else { + return inventory.CanGive( this, spawnArgs, statname, value ); + } +} + +/* +================= +idPlayer::StopHelltime + +provides a quick non-ramping way of stopping helltime +================= +*/ +void idPlayer::StopHelltime( bool quick ) { + if ( !PowerUpActive( HELLTIME ) ) { + return; + } + + // take away the powerups + if ( PowerUpActive( INVULNERABILITY ) ) { + ClearPowerup( INVULNERABILITY ); + } + + if ( PowerUpActive( BERSERK ) ) { + ClearPowerup( BERSERK ); + } + + if ( PowerUpActive( HELLTIME ) ) { + ClearPowerup( HELLTIME ); + } + + // stop the looping sound + StopSound( SND_CHANNEL_DEMONIC, false ); + + // reset the game vars + if ( quick ) { + gameLocal.QuickSlowmoReset(); + } +} + +/* +================= +idPlayer::Event_ToggleBloom +================= +*/ +void idPlayer::Event_ToggleBloom( int on ) { + if ( on ) { + bloomEnabled = true; + } + else { + bloomEnabled = false; + } +} + +/* +================= +idPlayer::Event_SetBloomParms +================= +*/ +void idPlayer::Event_SetBloomParms( float speed, float intensity ) { + bloomSpeed = speed; + bloomIntensity = intensity; +} + +/* +================= +idPlayer::PlayHelltimeStopSound +================= +*/ +void idPlayer::PlayHelltimeStopSound() { + const char* sound; + + if ( spawnArgs.GetString( "snd_helltime_stop", "", &sound ) ) { + PostEventMS( &EV_StartSoundShader, 0, sound, SND_CHANNEL_ANY ); + } +} + +/* +================= +idPlayer::RouteGuiMouse +================= +*/ +void idPlayer::RouteGuiMouse( idUserInterface *gui ) { + sysEvent_t ev; + const char *command; + + if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) { + ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY ); + command = gui->HandleEvent( &ev, gameLocal.time ); + oldMouseX = usercmd.mx; + oldMouseY = usercmd.my; + } +} + +/* +================== +idPlayer::LookAtKiller +================== +*/ +void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) { + idVec3 dir; + + if ( attacker && attacker != this ) { + dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else if ( inflictor && inflictor != this ) { + dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else { + dir = viewAxis[ 0 ]; + } + + idAngles ang( 0, dir.ToYaw(), 0 ); + SetViewAngles( ang ); +} + +/* +============== +idPlayer::Kill +============== +*/ +void idPlayer::Kill( bool delayRespawn, bool nodamage ) { + if ( spectating ) { + SpectateFreeFly( false ); + } else if ( health > 0 ) { + godmode = false; + if ( nodamage ) { + ServerSpectate( true ); + idLib::Printf( "TOURNEY Kill :> Player %d On Deck \n", entityNumber ); + forceRespawn = true; + } else { + Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + if ( delayRespawn ) { + forceRespawn = false; + int delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + } + } +} + +/* +================== +idPlayer::Killed +================== +*/ +void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + float delay; + + assert( !common->IsClient() ); + + // stop taking knockback once dead + fl.noknockback = true; + if ( health < -999 ) { + health = -999; + } + + if ( AI_DEAD ) { + AI_PAIN = true; + return; + } + + heartInfo.Init( 0, 0, 0, BASE_HEARTRATE ); + AdjustHeartRate( DEAD_HEARTRATE, 10.0f, 0.0f, true ); + + if ( !g_testDeath.GetBool() && !common->IsMultiplayer() ) { + playerView.Fade( colorBlack, 3000 ); + } + + AI_DEAD = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); + SetWaitState( "" ); + + animator.ClearAllJoints(); + + if ( StartRagdoll() ) { + pm_modelView.SetInteger( 0 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } else { + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + + physicsObj.SetMovementType( PM_DEAD ); + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + + fl.takedamage = true; // can still be gibbed + + // get rid of weapon + weapon.GetEntity()->OwnerDied(); + + // In multiplayer, get rid of the flashlight, or other players + // will see it floating after the player is dead. + if ( common->IsMultiplayer() ) { + FlashlightOff(); + if ( flashlight.GetEntity() ) { + flashlight.GetEntity()->OwnerDied(); + } + } + + // drop the weapon as an item + DropWeapon( true ); + + // drop the flag if player was carrying it + if ( common->IsMultiplayer() && gameLocal.mpGame.IsGametypeFlagBased() && carryingFlag ) { + DropFlag(); + } + + if ( !g_testDeath.GetBool() ) { + LookAtKiller( inflictor, attacker ); + } + + if ( common->IsMultiplayer() || g_testDeath.GetBool() ) { + idPlayer *killer = NULL; + // no gibbing in MP. Event_Gib will early out in MP + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + if ( health < -20 || killer->PowerUpActive( BERSERK ) ) { + gibDeath = true; + gibsDir = dir; + gibsLaunched = false; + } + } + gameLocal.mpGame.PlayerDeath( this, killer, isTelefragged ); + } else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } + + ClearPowerUps(); + + UpdateVisuals(); +} + +/* +===================== +idPlayer::GetAIAimTargets + +Returns positions for the AI to aim at. +===================== +*/ +void idPlayer::GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ) { + idVec3 offset; + idMat3 axis; + idVec3 origin; + + origin = lastSightPos - physicsObj.GetOrigin(); + + GetJointWorldTransform( chestJoint, gameLocal.time, offset, axis ); + headPos = offset + origin; + + GetJointWorldTransform( headJoint, gameLocal.time, offset, axis ); + chestPos = offset + origin; +} + +/* +================ +idPlayer::DamageFeedback + +callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller. +================ +*/ +void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + // Since we're predicting projectiles on the client now, we might actually get here + // (used be an assert for clients). + if ( common->IsClient() ) { + return; + } + + damage *= PowerUpModifier( BERSERK ); + if ( damage && ( victim != this ) && ( victim->IsType( idActor::Type ) || victim->IsType( idDamagable::Type ) ) ) { + + idPlayer *victimPlayer = NULL; + + /* No damage feedback sound for hitting friendlies in CTF */ + if ( victim->IsType( idPlayer::Type ) ) { + victimPlayer = static_cast(victim); + } + + if ( gameLocal.mpGame.IsGametypeFlagBased() && victimPlayer && this->team == victimPlayer->team ) { + /* Do nothing ... */ + } else { + SetLastHitTime( gameLocal.time ); + } + } +} + +/* +================= +idPlayer::CalcDamagePoints + +Calculates how many health and armor points will be inflicted, but +doesn't actually do anything with them. This is used to tell when an attack +would have killed the player, possibly allowing a "saving throw" +================= +*/ +void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ) { + int damage; + int armorSave; + + damageDef->GetInt( "damage", "20", damage ); + damage = GetDamageForLocation( damage, location ); + + idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast(attacker) : NULL; + if ( !common->IsMultiplayer() ) { + if ( inflictor != gameLocal.world ) { + switch ( g_skill.GetInteger() ) { + case 0: + damage *= 0.50f; + if ( damage < 1 ) { + damage = 1; + } + break; + case 2: + damage *= 1.70f; + break; + case 3: + damage *= 3.5f; + break; + default: + break; + } + } + } + + damage *= damageScale; + + // always give half damage if hurting self + if ( attacker == this ) { + if ( common->IsMultiplayer() ) { + // only do this in mp so single player plasma and rocket splash is very dangerous in close quarters + damage *= damageDef->GetFloat( "selfDamageScale", "0.5" ); + } else { + damage *= damageDef->GetFloat( "selfDamageScale", "1" ); + } + } + + // check for completely getting out of the damage + if ( !damageDef->GetBool( "noGod" ) ) { + // check for godmode + if ( godmode ) { + damage = 0; + } + //Invulnerability is just like god mode + if( PowerUpActive( INVULNERABILITY ) ) { + damage = 0; + } + } + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + + // save some from armor + if ( !damageDef->GetBool( "noArmor" ) ) { + float armor_protection; + + armor_protection = ( common->IsMultiplayer() ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat(); + + armorSave = ceil( damage * armor_protection ); + if ( armorSave >= inventory.armor ) { + armorSave = inventory.armor; + } + + if ( !damage ) { + armorSave = 0; + } else if ( armorSave >= damage ) { + armorSave = damage - 1; + damage = 1; + } else { + damage -= armorSave; + } + } else { + armorSave = 0; + } + + // check for team damage + if ( gameLocal.mpGame.IsGametypeTeamBased() /* CTF */ + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team ) { + damage = 0; + } + + *health = damage; + *armor = armorSave; +} + +/* +============ +idPlayer::ControllerShakeFromDamage +============ +*/ +void idPlayer::ControllerShakeFromDamage( int damage ) { + + // If the player is local. SHAkkkkkkeeee! + if( common->IsMultiplayer() && IsLocallyControlled() ) { + + int maxMagScale = pm_controllerShake_damageMaxMag.GetFloat(); + int maxDurScale = pm_controllerShake_damageMaxDur.GetFloat(); + + // determine rumble + // >= 100 damage - will be 300 Mag + float highMag = ( Max( damage, 100 ) / 100.0f ) * maxMagScale; + int highDuration = idMath::Ftoi( ( Max( damage, 100 ) / 100.0f ) * maxDurScale ); + float lowMag = highMag * 0.75f; + int lowDuration = idMath::Ftoi( highDuration ); + + SetControllerShake( highMag, highDuration, lowMag, lowDuration ); + } + +} + +/* +============ +AdjustDamageAmount + +Modifies the previously calculated damage to adjust for more factors. +============ +*/ +int idPlayer::AdjustDamageAmount( const int inputDamage ) { + int outputDamage = inputDamage; + + if ( inputDamage > 0 ) { + + if ( !common->IsMultiplayer() ) { + float scale = new_g_damageScale; + if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) { + if ( gameLocal.time > lastDmgTime + 500 && scale > 0.25f ) { + scale -= 0.05f; + new_g_damageScale = scale; + } + } + + if ( scale > 0.0f ) { + outputDamage *= scale; + } + } + + if ( g_demoMode.GetBool() ) { + outputDamage /= 2; + } + + if ( outputDamage < 1 ) { + outputDamage = 1; + } + } + + return outputDamage; +} + + +/* +============ +ServerDealDamage + +Only called on the server and in singleplayer. This is where +the player's health is actually modified, but the visual and +sound effects happen elsewhere so that clients can get instant +feedback and hide lag. +============ +*/ +void idPlayer::ServerDealDamage( int damage, idEntity & inflictor, idEntity & attacker, const idVec3 & dir, const char * damageDefName, const int location ) { + assert( !common->IsClient() ); + + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + // move the world direction vector to local coordinates + idVec3 damage_from; + idVec3 localDamageVector; + + damage_from = dir; + damage_from.Normalize(); + + viewAxis.ProjectVector( damage_from, localDamageVector ); + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if ( health > 0 ) { + playerView.DamageImpulse( localDamageVector, &damageDef->dict ); + } + + // do the damage + if ( damage > 0 ) { + GetAchievementManager().SetPlayerTookDamage( true ); + + int oldHealth = health; + health -= damage; + + if ( health <= 0 ) { + + if ( health < -999 ) { + health = -999; + } + + // HACK - A - LICIOUS - Check to see if we are being damaged by the frag chamber. + if ( oldHealth > 0 && strcmp( gameLocal.GetMapName(), "maps/game/mp/d3dm3.map" ) == 0 && strcmp( damageDefName, "damage_triggerhurt_1000_chamber" ) == 0 ) { + idPlayer * fragChamberActivator = gameLocal.playerActivateFragChamber; + if ( fragChamberActivator != NULL ) { + fragChamberActivator->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_CATCH_ENEMY_IN_ROFC ); + } + gameLocal.playerActivateFragChamber = NULL; + } + + isTelefragged = damageDef->dict.GetBool( "telefrag" ); + + lastDmgTime = gameLocal.time; + Killed( &inflictor, &attacker, damage, dir, location ); + } else { + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + } + } else { + // don't accumulate impulses + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space + +damageDef an idDict with all the options for damage effects + +inflictor, attacker, dir, and point can be NULL for environmental effects +============ +*/ +void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + idVec3 kick; + int damage; + int armorSave; + + SetTimeState ts( timeGroup ); + + if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + if ( attacker->IsType( idAI::Type ) ) { + if ( PowerUpActive( BERSERK ) ) { + return; + } + // don't take damage from monsters during influences + if ( influenceActive != 0 ) { + return; + } + } + + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->dict.GetBool( "ignore_player" ) ) { + return; + } + + // determine knockback + int knockback = 0; + damageDef->dict.GetInt( "knockback", "20", knockback ); + + if ( knockback != 0 && !fl.noknockback ) { + float attackerPushScale = 0.0f; + + if ( attacker == this ) { + damageDef->dict.GetFloat( "attackerPushScale", "0", attackerPushScale ); + } else { + attackerPushScale = 1.0f; + } + + idVec3 kick = dir; + kick.Normalize(); + kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f; + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick ); + + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) ); + + if ( common->IsServer() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.WriteFloat( physicsObj.GetLinearVelocity()[0] ); + msg.WriteFloat( physicsObj.GetLinearVelocity()[1] ); + msg.WriteFloat( physicsObj.GetLinearVelocity()[2] ); + msg.WriteByte( idMath::ClampInt( 50, 200, knockback * 2 ) ); + ServerSendEvent( idPlayer::EVENT_KNOCKBACK, &msg, false ); + } + } + + // If this is a locally controlled MP client, don't apply damage effects predictively here. + // Local clients will see the damage feedback (view kick, etc) when their health changes + // in a snapshot. This ensures that any feedback the local player sees is in sync with + // his actual health reported by the server. + if( common->IsMultiplayer() && common->IsClient() && IsLocallyControlled() ) { + return; + } + + CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave ); + + // give feedback on the player view and audibly when armor is helping + if ( armorSave ) { + inventory.armor -= armorSave; + + if ( gameLocal.time > lastArmorPulse + 200 ) { + StartSound( "snd_hitArmor", SND_CHANNEL_ITEM, 0, false, NULL ); + } + lastArmorPulse = gameLocal.time; + } + + if ( damageDef->dict.GetBool( "burn" ) ) { + StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL ); + } else if ( damageDef->dict.GetBool( "no_air" ) ) { + if ( !armorSave && health > 0 ) { + StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + + if ( g_debugDamage.GetInteger() ) { + gameLocal.Printf( "client:%02d\tdamage type:%s\t\thealth:%03d\tdamage:%03d\tarmor:%03d\n", entityNumber, damageDef->GetName(), health, damage, armorSave ); + } + + if ( common->IsMultiplayer() && IsLocallyControlled() ) { + ControllerShakeFromDamage( damage ); + } + + // The client needs to know the final damage amount for predictive pain animations. + const int finalDamage = AdjustDamageAmount( damage ); + + if ( health > 0 ) { + // force a blink + blink_time = 0; + + // let the anim script know we took damage + AI_PAIN = Pain( inflictor, attacker, damage, dir, location ); + } + + // Only actually deal the damage here in singleplayer and for locally controlled servers. + if ( !common->IsMultiplayer() || common->IsServer() ) { + // Server will deal his damage normally + ServerDealDamage( finalDamage, *inflictor, *attacker, dir, damageDefName, location ); + } else if ( attacker->GetEntityNumber() == gameLocal.GetLocalClientNum() ) { + // Clients send a reliable message to the server with the parameters of the hit. The + // server should make sure the client still has line-of-sight to its target before + // actually applying the damage. + + byte msgBuffer[MAX_GAME_MESSAGE_SIZE]; + idBitMsg msg; + + msg.InitWrite( msgBuffer, sizeof( msgBuffer ) ); + msg.BeginWriting(); + + msg.WriteShort( attacker->GetEntityNumber() ); + msg.WriteShort( GetEntityNumber() ); // victim + msg.WriteVectorFloat( dir ); + msg.WriteLong( damageDef->Index() ); + msg.WriteFloat( damageScale ); + msg.WriteLong( location ); + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + lobby.SendReliableToHost( GAME_RELIABLE_MESSAGE_CLIENT_HITSCAN_HIT, msg ); + } + + lastDamageDef = damageDef->Index(); + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageLocation = location; +} + +/* +=========== +idPlayer::Teleport +============ +*/ +void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + idVec3 org; + + if ( weapon.GetEntity() ) { + weapon.GetEntity()->LowerWeapon(); + } + + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + if ( !common->IsMultiplayer() && GetFloorPos( 16.0f, org ) ) { + SetOrigin( org ); + } + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + GetPhysics()->SetLinearVelocity( vec3_origin ); + + SetViewAngles( angles ); + + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + + if ( common->IsMultiplayer() ) { + playerView.Flash( colorWhite, 140 ); + } + + UpdateVisuals(); + + teleportEntity = destination; + + if ( !common->IsClient() && !noclip ) { + if ( common->IsMultiplayer() ) { + // kill anything at the new position or mark for kill depending on immediate or delayed teleport + gameLocal.KillBox( this, destination != NULL ); + } else { + // kill anything at the new position + gameLocal.KillBox( this, true ); + } + } + + if ( PowerUpActive( HELLTIME ) ) { + StopHelltime(); + } +} + +/* +==================== +idPlayer::SetPrivateCameraView +==================== +*/ +void idPlayer::SetPrivateCameraView( idCamera *camView ) { + privateCameraView = camView; + if ( camView ) { + StopFiring(); + Hide(); + } else { + if ( !spectating ) { + Show(); + } + } +} + +/* +==================== +idPlayer::DefaultFov + +Returns the base FOV +==================== +*/ +float idPlayer::DefaultFov() const { + float fov; + + fov = g_fov.GetFloat(); + if ( common->IsMultiplayer() ) { + if ( fov < 80.0f ) { + return 80.0f; + } else if ( fov > 120.0f ) { + return 120.0f; + } + } + + return fov; +} + +/* +==================== +idPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +float idPlayer::CalcFov( bool honorZoom ) { + float fov; + + if ( fxFov ) { + return DefaultFov() + 10.0f + cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f; + } + + if ( influenceFov ) { + return influenceFov; + } + + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ? weapon.GetEntity()->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + + // bound normal viewsize + if ( fov < 1 ) { + fov = 1; + } else if ( fov > 179 ) { + fov = 179; + } + + return fov; +} + +/* +============== +idPlayer::GunTurningOffset + +generate a rotational offset for the gun based on the view angle +history in loggedViewAngles +============== +*/ +idAngles idPlayer::GunTurningOffset() { + idAngles a; + + a.Zero(); + + if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) { + return a; + } + + idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles av, base; + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale, weaponAngleOffsetMax; + + weapon.GetEntity()->GetWeaponAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax ); + + av = current; + + // calcualte this so the wrap arounds work properly + for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) { + idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles delta = a2 - current; + + if ( delta[1] > 180 ) { + delta[1] -= 360; + } else if ( delta[1] < -180 ) { + delta[1] += 360; + } + + av += delta * ( 1.0f / weaponAngleOffsetAverages ); + } + + a = ( av - current ) * weaponAngleOffsetScale; + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( a[i] < -weaponAngleOffsetMax ) { + a[i] = -weaponAngleOffsetMax; + } else if ( a[i] > weaponAngleOffsetMax ) { + a[i] = weaponAngleOffsetMax; + } + } + + return a; +} + +/* +============== +idPlayer::GunAcceleratingOffset + +generate a positional offset for the gun based on the movement +history in loggedAccelerations +============== +*/ +idVec3 idPlayer::GunAcceleratingOffset() { + idVec3 ofs; + + float weaponOffsetTime, weaponOffsetScale; + + ofs.Zero(); + + weapon.GetEntity()->GetWeaponTimeOffsets( &weaponOffsetTime, &weaponOffsetScale ); + + int stop = currentLoggedAccel - NUM_LOGGED_ACCELS; + if ( stop < 0 ) { + stop = 0; + } + for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) { + loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)]; + + float f; + float t = gameLocal.time - acc->time; + if ( t >= weaponOffsetTime ) { + break; // remainder are too old to care about + } + + f = t / weaponOffsetTime; + f = ( cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f; + ofs += f * weaponOffsetScale * acc->dir; + } + + return ofs; +} + +/* +============== +idPlayer::CalculateViewWeaponPos + +Calculate the bobbing position of the view weapon +============== +*/ +void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) { + float scale; + float fracsin; + idAngles angles; + int delta; + + // CalculateRenderView must have been called first + const idVec3 &viewOrigin = firstPersonViewOrigin; + const idMat3 &viewAxis = firstPersonViewAxis; + + // these cvars are just for hand tweaking before moving a value to the weapon def + idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() ); + + // as the player changes direction, the gun will take a small lag + idVec3 gunOfs = GunAcceleratingOffset(); + origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis; + + // on odd legs, invert some angles + if ( bobCycle & 128 ) { + scale = -xyspeed; + } else { + scale = xyspeed; + } + + // gun angles from bobbing + angles.roll = scale * bobfracsin * 0.005f; + angles.yaw = scale * bobfracsin * 0.01f; + angles.pitch = xyspeed * bobfracsin * 0.005f; + + // gun angles from turning + if ( common->IsMultiplayer() ) { + idAngles offset = GunTurningOffset(); + offset *= g_mpWeaponAngleScale.GetFloat(); + angles += offset; + } else { + angles += GunTurningOffset(); + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + + // drop the weapon when landing after a jump / fall + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin -= gravity * ( landChange*0.25f * delta / LAND_DEFLECT_TIME ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin -= gravity * ( landChange*0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME ); + } + + // speed sensitive idle drift + scale = xyspeed + 40.0f; + fracsin = scale * sin( MS2SEC( gameLocal.time ) ) * 0.01f; + angles.roll += fracsin; + angles.yaw += fracsin; + angles.pitch += fracsin; + + // decoupled weapon aiming in head mounted displays + angles.pitch += independentWeaponPitchAngle; + + const idMat3 anglesMat = angles.ToMat3(); + const idMat3 scaledMat = anglesMat * g_gunScale.GetFloat(); + + axis = scaledMat * viewAxis; +} + +/* +=============== +idPlayer::OffsetThirdPersonView +=============== +*/ +void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + float forwardScale, sideScale; + idVec3 origin; + idAngles angles; + idMat3 axis; + idBounds bounds; + + angles = viewAngles; + GetViewPos( origin, axis ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + focusPoint.z += height; + view = origin; + view.z += 8 + height; + + angles.pitch *= 0.5f; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale ); + view -= range * forwardScale * renderView->viewaxis[ 0 ]; + view += range * sideScale * renderView->viewaxis[ 1 ]; + + if ( clip ) { + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) ); + gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + view = trace.endpos; + view.z += ( 1.0f - trace.fraction ) * 32.0f; + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out + gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this ); + view = trace.endpos; + } + } + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1.0f ) { + focusDist = 1.0f; // should never happen + } + + angles.pitch = - RAD2DEG( atan2( focusPoint.z, focusDist ) ); + angles.yaw -= angle; + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::GetEyePosition +=============== +*/ +idVec3 idPlayer::GetEyePosition() const { + idVec3 org; + + // use the smoothed origin if spectating another player in multiplayer + if ( common->IsClient() && !IsLocallyControlled() ) { + org = smoothedOrigin; + } else { + org = GetPhysics()->GetOrigin(); + } + return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); +} + +/* +=============== +idPlayer::GetViewPos +=============== +*/ +void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + idAngles angles; + + // if dead, fix the angle and don't add any kick + if ( health <= 0 ) { + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else { + origin = GetEyePosition() + viewBob; + angles = viewAngles + viewBobAngles + playerView.AngleOffset(); + + axis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + // Move pivot point down so looking straight ahead is a no-op on the Z + const idVec3 & gravityVector = physicsObj.GetGravityNormal(); + origin += gravityVector * g_viewNodalZ.GetFloat(); + + // adjust the origin based on the camera nodal distance (eye distance from neck) + origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat(); + } +} + +/* +=============== +idPlayer::CalculateFirstPersonView +=============== +*/ +void idPlayer::CalculateFirstPersonView() { + if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) { + // Displays the view from the point of view of the "camera" joint in the player model + + idMat3 axis; + idVec3 origin; + idAngles ang; + + ang = viewBobAngles + playerView.AngleOffset(); + ang.yaw += viewAxis[ 0 ].ToYaw(); + + jointHandle_t joint = animator.GetJointHandle( "camera" ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob; + firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis(); + } else { + // offset for local bobbing and kicks + GetViewPos( firstPersonViewOrigin, firstPersonViewAxis ); +#if 0 + // shakefrom sound stuff only happens in first person + firstPersonViewAxis = firstPersonViewAxis * playerView.ShakeAxis(); +#endif + } +} + +/* +================== +idPlayer::GetRenderView + +Returns the renderView that was calculated for this tic +================== +*/ +renderView_t *idPlayer::GetRenderView() { + return renderView; +} + +/* +================== +idPlayer::CalculateRenderView + +create the renderView for the current tic +================== +*/ +void idPlayer::CalculateRenderView() { + int i; + float range; + + if ( !renderView ) { + renderView = new (TAG_ENTITY) renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + // copy global shader parms + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + + renderView->time[0] = gameLocal.slow.time; + renderView->time[1] = gameLocal.fast.time; + + renderView->viewID = 0; + + // check if we should be drawing from a camera's POV + if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) { + // get origin, axis, and fov + if ( privateCameraView ) { + privateCameraView->GetViewParms( renderView ); + } else { + gameLocal.GetCamera()->GetViewParms( renderView ); + } + } else { + if ( g_stopTime.GetBool() ) { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + + if ( !pm_thirdPerson.GetBool() ) { + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } else if ( pm_thirdPerson.GetBool() ) { + OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() ); + } else if ( pm_thirdPersonDeath.GetBool() ) { + range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f; + OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false ); + } else { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + + gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y ); + } + + if ( renderView->fov_y == 0 ) { + common->Error( "renderView->fov_y == 0" ); + } + + if ( g_showviewpos.GetBool() ) { + gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() ); + } +} + +/* +============= +idPlayer::AddAIKill +============= +*/ +void idPlayer::AddAIKill() { + int max_souls; + int ammo_souls; + + if ( ( weapon_soulcube < 0 ) || ( inventory.weapons & ( 1 << weapon_soulcube ) ) == 0 ) { + return; + } + + ammo_souls = idWeapon::GetAmmoNumForName( "ammo_souls" ); + max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" ); + const int currentSoulAmmo = inventory.GetInventoryAmmoForType( ammo_souls ); + if ( currentSoulAmmo < max_souls ) { + inventory.SetInventoryAmmoForType( ammo_souls, currentSoulAmmo + 1 ); + if ( inventory.GetInventoryAmmoForType( ammo_souls ) >= max_souls ) { + + if ( hud ) { + hud->UpdateSoulCube( true ); + } + StartSound( "snd_soulcube_ready", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +============= +idPlayer::SetSoulCubeProjectile +============= +*/ +void idPlayer::SetSoulCubeProjectile( idProjectile *projectile ) { + soulCubeProjectile = projectile; +} + +/* +============= +idPlayer::AddProjectilesFired +============= +*/ +void idPlayer::AddProjectilesFired( int count ) { + numProjectilesFired += count; +} + +/* +============= +idPlayer::AddProjectileHites +============= +*/ +void idPlayer::AddProjectileHits( int count ) { + numProjectileHits += count; +} + +/* +============= +idPlayer::SetLastHitTime +============= +*/ +void idPlayer::SetLastHitTime( int time ) { + idPlayer *aimed = NULL; + + if ( time && lastHitTime != time ) { + lastHitToggle ^= 1; + } + lastHitTime = time; + if ( !time ) { + // level start and inits + return; + } + if ( common->IsMultiplayer() && ( time - lastSndHitTime ) > 10 ) { + lastSndHitTime = time; + StartSound( "snd_hit_feedback", SND_CHANNEL_ANY, SSF_PRIVATE_SOUND, false, NULL ); + } + + if ( hud ) { + hud->CombatCursorFlash(); + } + + if ( MPAim != -1 ) { + if ( gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) { + aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] ); + } + assert( aimed ); + // full highlight, no fade till loosing aim + + if ( hud ) { + int color = 0; + if ( aimed ) { + color = aimed->team + 1; + } + hud->TriggerHitTarget( true, session->GetActingGameStateLobbyBase().GetLobbyUserName( gameLocal.lobbyUserIDs[ MPAim ] ), color ); + } + MPAimHighlight = true; + MPAimFadeTime = 0; + } else if ( lastMPAim != -1 ) { + if ( gameLocal.entities[ lastMPAim ] && gameLocal.entities[ lastMPAim ]->IsType( idPlayer::Type ) ) { + aimed = static_cast< idPlayer * >( gameLocal.entities[ lastMPAim ] ); + } + assert( aimed ); + // start fading right away + if ( hud ) { + int color = 0; + if ( aimed ) { + color = aimed->team + 1; + } + hud->TriggerHitTarget( true, session->GetActingGameStateLobbyBase().GetLobbyUserName( gameLocal.lobbyUserIDs[ lastMPAim ] ), color ); + hud->TriggerHitTarget( false, "" ); + } + MPAimHighlight = false; + MPAimFadeTime = gameLocal.realClientTime; + } +} + +/* +============= +idPlayer::SetInfluenceLevel +============= +*/ +void idPlayer::SetInfluenceLevel( int level ) { + if ( level != influenceActive ) { + if ( level ) { + for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idProjectile::Type ) ) { + // remove all projectiles + ent->PostEventMS( &EV_Remove, 0 ); + } + } + if ( weaponEnabled && weapon.GetEntity() ) { + weapon.GetEntity()->EnterCinematic(); + } + } else { + physicsObj.SetLinearVelocity( vec3_origin ); + if ( weaponEnabled && weapon.GetEntity() ) { + weapon.GetEntity()->ExitCinematic(); + } + } + influenceActive = level; + } +} + +/* +============= +idPlayer::SetInfluenceView +============= +*/ +void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) { + influenceMaterial = NULL; + influenceEntity = NULL; + influenceSkin = NULL; + if ( mtr && *mtr ) { + influenceMaterial = declManager->FindMaterial( mtr ); + } + if ( skinname && *skinname ) { + influenceSkin = declManager->FindSkin( skinname ); + if ( head.GetEntity() ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + UpdateVisuals(); + } + influenceRadius = radius; + if ( radius > 0.0f ) { + influenceEntity = ent; + } +} + +/* +============= +idPlayer::SetInfluenceFov +============= +*/ +void idPlayer::SetInfluenceFov( float fov ) { + influenceFov = fov; +} + +/* +================ +idPlayer::OnLadder +================ +*/ +bool idPlayer::OnLadder() const { + return physicsObj.OnLadder(); +} + +/* +================== +idPlayer::Event_GetButtons +================== +*/ +void idPlayer::Event_GetButtons() { + idThread::ReturnInt( usercmd.buttons ); +} + +/* +================== +idPlayer::Event_GetMove +================== +*/ +void idPlayer::Event_GetMove() { + int upmove = ( ( usercmd.buttons & BUTTON_JUMP ) ? 127 : 0 ) - ( ( usercmd.buttons & BUTTON_CROUCH ) ? 127 : 0 ); + idVec3 move( usercmd.forwardmove, usercmd.rightmove, upmove ); + idThread::ReturnVector( move ); +} + +/* +================ +idPlayer::Event_GetViewAngles +================ +*/ +void idPlayer::Event_GetViewAngles() { + idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) ); +} + +/* +================== +idPlayer::Event_StopFxFov +================== +*/ +void idPlayer::Event_StopFxFov() { + fxFov = false; +} + +/* +================== +idPlayer::StartFxFov +================== +*/ +void idPlayer::StartFxFov( float duration ) { + fxFov = true; + PostEventSec( &EV_Player_StopFxFov, duration ); +} + +/* +================== +idPlayer::Event_EnableWeapon +================== +*/ +void idPlayer::Event_EnableWeapon() { + hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" ); + weaponEnabled = true; + if ( weapon.GetEntity() ) { + weapon.GetEntity()->ExitCinematic(); + } +} + +/* +================== +idPlayer::Event_DisableWeapon +================== +*/ +void idPlayer::Event_DisableWeapon() { + hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" ); + weaponEnabled = false; + if ( weapon.GetEntity() ) { + weapon.GetEntity()->EnterCinematic(); + } +} + +/* +================== +idPlayer::Event_GiveInventoryItem +================== +*/ +void idPlayer::Event_GiveInventoryItem( const char* name ) { + GiveInventoryItem(name); +} + +/* +================== +idPlayer::Event_RemoveInventoryItem +================== +*/ +void idPlayer::Event_RemoveInventoryItem( const char* name ) { + RemoveInventoryItem(name); +} + +/* +================== +idPlayer::Event_GetIdealWeapon +================== +*/ +void idPlayer::Event_GetIdealWeapon() { + const char *weapon; + + if ( idealWeapon.Get() >= 0 ) { + weapon = spawnArgs.GetString( va( "def_weapon%d", idealWeapon.Get() ) ); + idThread::ReturnString( weapon ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +================== +idPlayer::Event_SetPowerupTime +================== +*/ +void idPlayer::Event_SetPowerupTime( int powerup, int time ) { + if ( time > 0 ) { + GivePowerUp( powerup, time, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } else { + ClearPowerup( powerup ); + } +} + +/* +================== +idPlayer::Event_IsPowerupActive +================== +*/ +void idPlayer::Event_IsPowerupActive( int powerup ) { + idThread::ReturnInt(this->PowerUpActive(powerup) ? 1 : 0); +} + +/* +================== +idPlayer::Event_StartWarp +================== +*/ +void idPlayer::Event_StartWarp() { + playerView.AddWarp( idVec3( 0, 0, 0 ), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 100, 1000 ); +} + +/* +================== +idPlayer::Event_StopHelltime +================== +*/ +void idPlayer::Event_StopHelltime( int mode ) { + if ( mode == 1 ) { + StopHelltime( true ); + } + else { + StopHelltime( false ); + } +} + +/* +================== +idPlayer::Event_WeaponAvailable +================== +*/ +void idPlayer::Event_WeaponAvailable( const char* name ) { + + idThread::ReturnInt( WeaponAvailable(name) ? 1 : 0 ); +} + +bool idPlayer::WeaponAvailable( const char* name ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, name ) ) { + return true; + } + } + } + return false; +} + + +/* +================== +idPlayer::Event_GetCurrentWeapon +================== +*/ +void idPlayer::Event_GetCurrentWeapon() { + const char *weapon; + + if ( currentWeapon >= 0 ) { + weapon = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + idThread::ReturnString( weapon ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +================== +idPlayer::Event_GetPreviousWeapon +================== +*/ +void idPlayer::Event_GetPreviousWeapon() { + const char *weapon; + + if ( previousWeapon >= 0 ) { + int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon; + weapon = spawnArgs.GetString( va( "def_weapon%d", pw) ); + idThread::ReturnString( weapon ); + } else { + idThread::ReturnString( spawnArgs.GetString( "def_weapon0" ) ); + } +} + +/* +================== +idPlayer::Event_SelectWeapon +================== +*/ +void idPlayer::Event_SelectWeapon( const char *weaponName ) { + int i; + int weaponNum; + + if ( common->IsClient() ) { + gameLocal.Warning( "Cannot switch weapons from script in multiplayer" ); + return; + } + + if ( hiddenWeapon && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + idealWeapon = weapon_fists; + weapon.GetEntity()->HideWeapon(); + return; + } + + weaponNum = -1; + for( i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + weaponNum = i; + break; + } + } + } + + if ( weaponNum < 0 ) { + gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName ); + return; + } + + hiddenWeapon = false; + idealWeapon = weaponNum; + + UpdateHudWeapon(); +} + +/* +================== +idPlayer::Event_GetWeaponEntity +================== +*/ +void idPlayer::Event_GetWeaponEntity() { + idThread::ReturnEntity( weapon.GetEntity() ); +} + +/* +================== +idPlayer::Event_OpenPDA +================== +*/ +void idPlayer::Event_OpenPDA() { + if ( !common->IsMultiplayer() ) { + TogglePDA(); + } +} + +/* +================== +idPlayer::Event_InPDA +================== +*/ +void idPlayer::Event_InPDA() { + idThread::ReturnInt( objectiveSystemOpen ); +} + +/* +================== +idPlayer::TeleportDeath +================== +*/ +void idPlayer::TeleportDeath( int killer ) { + teleportKiller = killer; +} + +/* +================== +idPlayer::Event_ExitTeleporter +================== +*/ +void idPlayer::Event_ForceOrigin( idVec3 & origin, idAngles & angles ) { + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + //SetViewAngles( angles ); + + UpdateVisuals(); +} + +/* +================== +idPlayer::Event_ExitTeleporter +================== +*/ +void idPlayer::Event_ExitTeleporter() { + idEntity *exitEnt; + float pushVel; + + // verify and setup + exitEnt = teleportEntity.GetEntity(); + if ( !exitEnt ) { + common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber ); + return; + } + + pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" ); + + if ( common->IsServer() ) { + ServerSendEvent( EVENT_EXIT_TELEPORTER, NULL, false ); + } + + SetPrivateCameraView( NULL ); + // setup origin and push according to the exit target + SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() ); + physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel ); + physicsObj.ClearPushedVelocity(); + // teleport fx + playerView.Flash( colorWhite, 120 ); + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + UpdateVisuals(); + + StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( teleportKiller != -1 ) { + // we got killed while being teleported + Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + teleportKiller = -1; + } else { + // kill anything that would have waited at teleport exit + gameLocal.KillBox( this ); + } + teleportEntity = NULL; +} + +/* +================ +idPlayer::ClientThink +================ +*/ +void idPlayer::ClientThink( const int curTime, const float fraction, const bool predict ) { + if ( IsLocallyControlled() ) { + aimAssist.Update(); + } + + UpdateSkinSetup(); + + if ( !IsLocallyControlled() ) { + // ignore attack button of other clients. that's no good for predictions + usercmd.buttons &= ~BUTTON_ATTACK; + } + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( mountedObject ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.buttons &= ~(BUTTON_JUMP|BUTTON_CROUCH); + } + + if ( objectiveSystemOpen ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.buttons &= ~(BUTTON_JUMP|BUTTON_CROUCH); + } + + if ( IsLocallyControlled() ) { + // zooming + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) { + if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) { + zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() ); + } else { + zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() ); + } + } + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( usercmd.impulseSequence != oldImpulseSequence ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard ) { + gameLocal.mpGame.SetScoreboardActive( true ); + } + + AdjustSpeed(); + + if ( IsLocallyControlled() ) { + UpdateViewAngles(); + } else { + idQuat interpolatedAngles = Slerp( previousViewQuat, nextViewQuat, fraction ); + viewAngles = interpolatedAngles.ToAngles(); + } + + smoothedOriginUpdated = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + if ( !isLagged ) { + // don't allow client to move when lagged + if ( IsLocallyControlled() ) { + // Locally-controlled clients are authoritative on their positions, so they can move normally. + Move(); + usercmd.pos = physicsObj.GetOrigin(); + } else { + // Non-locally controlled players are interpolated. + Move_Interpolated(fraction); + } + } + + if ( !g_stopTime.GetBool() ) { + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers(); + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + AI_PAIN = false; + + UpdateLocation(); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + if ( !gameLocal.inCinematic && weapon.GetEntity() && ( health > 0 ) && !( common->IsMultiplayer() && spectating ) ) { + UpdateWeapon(); + } + + UpdateFlashlight(); + + UpdateHud(); + + if ( gameLocal.isNewFrame ) { + UpdatePowerUps(); + } + + UpdateDeathSkin( false ); + + renderEntity_t * headRenderEnt = NULL; + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } + + if ( headRenderEnt ) { + if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = NULL; + } + } + + if ( common->IsMultiplayer() || g_showPlayerShadow.GetBool() ) { + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } + // never cast shadows from our first-person muzzle flashes + renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + if ( enviroSuitLight.IsValid() ) { + idAngles lightAng = firstPersonViewAxis.ToAngles(); + idVec3 lightOrg = firstPersonViewOrigin; + const idDict *lightDef = gameLocal.FindEntityDefDict( "envirosuit_light", false ); + + idVec3 enviroOffset = lightDef->GetVector( "enviro_offset" ); + idVec3 enviroAngleOffset = lightDef->GetVector( "enviro_angle_offset" ); + + lightOrg += (enviroOffset.x * firstPersonViewAxis[0]); + lightOrg += (enviroOffset.y * firstPersonViewAxis[1]); + lightOrg += (enviroOffset.z * firstPersonViewAxis[2]); + lightAng.pitch += enviroAngleOffset.x; + lightAng.yaw += enviroAngleOffset.y; + lightAng.roll += enviroAngleOffset.z; + + enviroSuitLight.GetEntity()->GetPhysics()->SetOrigin( lightOrg ); + enviroSuitLight.GetEntity()->GetPhysics()->SetAxis( lightAng.ToMat3() ); + enviroSuitLight.GetEntity()->UpdateVisuals(); + enviroSuitLight.GetEntity()->Present(); + } + + if ( common->IsMultiplayer() ) { + DrawPlayerIcons(); + } + + Present(); + + UpdateDamageEffects(); + + LinkCombat(); + + // stereo rendering laser sight that replaces the crosshair + UpdateLaserSight(); + + if ( gameLocal.isNewFrame && IsLocallyControlled() ) { + playerView.CalculateShake(); + } + + // determine if portal sky is in pvs + pvsHandle_t clientPVS = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + gameLocal.portalSkyActive = gameLocal.pvs.CheckAreasForPortalSky( clientPVS, GetPhysics()->GetOrigin() ); + gameLocal.pvs.FreeCurrentPVS( clientPVS ); + + //InterpolatePhysics( fraction ); + + // Make sure voice groups are set to the right team + if ( common->IsMultiplayer() && session->GetState() >= idSession::INGAME && entityNumber < MAX_CLIENTS ) { // The entityNumber < MAX_CLIENTS seems to quiet the static analyzer + // Make sure we're on the right team (at the lobby level) + const int voiceTeam = spectating ? LOBBY_SPECTATE_TEAM_FOR_VOICE_CHAT : team; + + //idLib::Printf( "CLIENT: Sending voice %i / %i\n", entityNumber, voiceTeam ); + + // Update lobby team + session->GetActingGameStateLobbyBase().SetLobbyUserTeam( gameLocal.lobbyUserIDs[ entityNumber ], voiceTeam ); + + // Update voice groups to match in case something changed + session->SetVoiceGroupsToTeams(); + } +} + +/* +================ +idPlayer::GetPhysicsToVisualTransform +================ +*/ +bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + // smoothen the rendered origin and angles of other clients + // smooth self origin if snapshots are telling us prediction is off + if ( common->IsClient() && gameLocal.framenum >= smoothedFrame && ( !IsLocallyControlled() || selfSmooth ) ) { + // render origin and axis + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + + // update the smoothed origin + if ( !smoothedOriginUpdated ) { + idVec2 originDiff = renderOrigin.ToVec2() - smoothedOrigin.ToVec2(); + if ( originDiff.LengthSqr() < Square( 100.0f ) ) { + // smoothen by pushing back to the previous position + if ( selfSmooth ) { + assert( IsLocallyControlled() ); + renderOrigin.ToVec2() -= net_clientSelfSmoothing.GetFloat() * originDiff; + } else { + renderOrigin.ToVec2() -= gameLocal.clientSmoothing * originDiff; + } + } + smoothedOrigin = renderOrigin; + + smoothedFrame = gameLocal.framenum; + smoothedOriginUpdated = true; + } + + axis = idAngles( 0.0f, viewAngles.yaw, 0.0f ).ToMat3(); + origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose(); + + } else { + + axis = viewAxis; + origin = modelOffset; + } + return true; +} + +/* +================ +idPlayer::GetPhysicsToSoundTransform +================ +*/ +bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idCamera *camera; + + if ( privateCameraView ) { + camera = privateCameraView; + } else { + camera = gameLocal.GetCamera(); + } + + if ( camera ) { + renderView_t view; + + memset( &view, 0, sizeof( view ) ); + camera->GetViewParms( &view ); + origin = view.vieworg; + axis = view.viewaxis; + return true; + } else { + return idActor::GetPhysicsToSoundTransform( origin, axis ); + } +} + +/* +================ +idPlayer::HandleUserCmds +================ +*/ +void idPlayer::HandleUserCmds( const usercmd_t & newcmd ) { + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + oldCmd = usercmd; + oldImpulseSequence = usercmd.impulseSequence; + usercmd = newcmd; +} + +/* +================ +idPlayer::WriteToSnapshot +================ +*/ +void idPlayer::WriteToSnapshot( idBitMsg &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + // Only remote players will use these actual viewangles. + idCQuat snapViewCQuat( viewAngles.ToQuat().ToCQuat() ); + msg.WriteFloat( snapViewCQuat.x ); + msg.WriteFloat( snapViewCQuat.y ); + msg.WriteFloat( snapViewCQuat.z ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + msg.WriteShort( health ); + msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, lastDamageDef ), gameLocal.entityDefBits ); + msg.WriteDir( lastDamageDir, 9 ); + msg.WriteShort( lastDamageLocation ); + msg.WriteBits( idealWeapon.Get(), idMath::BitsForInteger( MAX_WEAPONS ) ); + msg.WriteBits( inventory.weapons, MAX_WEAPONS ); + msg.WriteBits( weapon.GetSpawnId(), 32 ); + msg.WriteBits( flashlight.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( lastHitToggle, 1 ); + msg.WriteBits( weaponGone, 1 ); + msg.WriteBits( isLagged, 1 ); + msg.WriteShort( team ); + WriteToBitMsg( respawn_netEvent, msg ); + + /* Needed for the scoreboard */ + msg.WriteBits( carryingFlag, 1 ); + msg.WriteBits( enviroSuitLight.GetSpawnId(), 32 ); + + msg.WriteBits( AI_CROUCH, 1 ); + msg.WriteBits( AI_ONGROUND, 1 ); + msg.WriteBits( AI_ONLADDER, 1 ); + msg.WriteBits( AI_JUMP, 1 ); + msg.WriteBits( AI_WEAPON_FIRED, 1 ); + msg.WriteBits( AI_ATTACK_HELD, 1 ); + + msg.WriteByte( usercmd.buttons ); + msg.WriteBits( usercmd.forwardmove, -8 ); + msg.WriteBits( usercmd.rightmove, -8 ); + + msg.WriteBool( spectating ); +} + +/* +================ +idPlayer::ReadFromSnapshot +================ +*/ +void idPlayer::ReadFromSnapshot( const idBitMsg &msg ) { + int oldHealth, newIdealWeapon, weaponSpawnId; + int flashlightSpawnId; + bool newHitToggle; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + // The remote players get updated view angles from the snapshot. + idCQuat snapViewCQuat; + snapViewCQuat.x = msg.ReadFloat(); + snapViewCQuat.y = msg.ReadFloat(); + snapViewCQuat.z = msg.ReadFloat(); + + idAngles tempDeltaViewAngles; + tempDeltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + tempDeltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + tempDeltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + + deltaViewAngles = tempDeltaViewAngles; + + health = msg.ReadShort(); + lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( gameLocal.entityDefBits ) ); + lastDamageDir = msg.ReadDir( 9 ); + lastDamageLocation = msg.ReadShort(); + newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) ); + inventory.weapons = msg.ReadBits( MAX_WEAPONS ); + weaponSpawnId = msg.ReadBits( 32 ); + flashlightSpawnId = msg.ReadBits( 32 ); + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + newHitToggle = msg.ReadBits( 1 ) != 0; + weaponGone = msg.ReadBits( 1 ) != 0; + isLagged = msg.ReadBits( 1 ) != 0; + team = msg.ReadShort(); + ReadFromBitMsg( respawn_netEvent, msg ); + + carryingFlag = msg.ReadBits( 1 ) != 0; + int enviroSpawnId; + enviroSpawnId = msg.ReadBits( 32 ); + enviroSuitLight.SetSpawnId( enviroSpawnId ); + + bool snapshotCrouch = msg.ReadBool(); + bool snapshotOnGround = msg.ReadBool(); + bool snapshotOnLadder = msg.ReadBool(); + bool snapshotJump = msg.ReadBool(); + bool snapShotFired = msg.ReadBool(); + bool snapShotAttackHeld = msg.ReadBool(); + + byte snapshotButtons = msg.ReadByte(); + signed char snapshotForward = msg.ReadBits( -8 ); + signed char snapshotRight = msg.ReadBits( -8 ); + + const bool snapshotSpectating = msg.ReadBool(); + + // no msg reading below this + + // Update remote remote player state. + if ( !IsLocallyControlled() ) { + previousViewQuat = nextViewQuat; + nextViewQuat = snapViewCQuat.ToQuat(); + + AI_CROUCH = snapshotCrouch; + AI_ONGROUND = snapshotOnGround; + AI_ONLADDER = snapshotOnLadder; + AI_JUMP = snapshotJump; + AI_WEAPON_FIRED = snapShotFired; + AI_ATTACK_HELD = snapShotAttackHeld; + + oldCmd = usercmd; + + usercmd.buttons = snapshotButtons; + usercmd.forwardmove = snapshotForward; + usercmd.rightmove = snapshotRight; + } + + if ( weapon.SetSpawnId( weaponSpawnId ) ) { + if ( weapon.GetEntity() ) { + // maintain ownership locally + weapon.GetEntity()->SetOwner( this ); + } + currentWeapon = -1; + } + + if ( flashlight.SetSpawnId( flashlightSpawnId ) ) { + if ( flashlight.GetEntity() ) { + flashlight.GetEntity()->SetFlashlightOwner( this ); + } + } + + /* + // if not a local client + if ( !IsLocallyControlled() ) { + // assume the client has all ammo types + inventory.SetRemoteClientAmmo( GetEntityNumber() ); + } + */ + + // Update spectating state + const bool wasSpectating = spectating; + spectating = snapshotSpectating; + + if ( spectating != wasSpectating ) { + Spectate( spectating, false ); + } + + if ( oldHealth > 0 && health <= 0 ) { + if ( snapshotStale ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + AI_DEAD = true; + ClearPowerUps(); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); + SetWaitState( "" ); + animator.ClearAllJoints(); + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + if ( !snapshotStale ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + if ( weapon.GetEntity() ) { + weapon.GetEntity()->OwnerDied(); + } + if ( flashlight.GetEntity() ) { + FlashlightOff(); + flashlight.GetEntity()->OwnerDied(); + } + + if( IsLocallyControlled() ) { + ControllerShakeFromDamage( oldHealth - health ); + } + + } else if ( health < oldHealth && health > 0 ) { + if ( snapshotStale ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + if ( IsLocallyControlled() ) { + playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict ); + AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + } + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef ); + } + + if( IsLocallyControlled() ) { + ControllerShakeFromDamage( oldHealth - health ); + } + + } + } else if ( health > oldHealth && PowerUpActive( MEGAHEALTH ) && !snapshotStale ) { + // just pulse, for any health raise + healthPulse = true; + } + + // handle respawns + if ( respawn_netEvent.Get() ) { + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + // Explicitly set the current origin, since locally-controlled clients + // don't interpolate. Reading the physics object from the snapshot only + // updates the next state, not the current state. + physicsObj.SnapToNextState(); + physicsObj.EnableClip(); + SetCombatContents( true ); + if ( flashlight.GetEntity() ) { + flashlight.GetEntity()->Show(); + } + Respawn_Shared(); + } + + // If the player is alive, restore proper physics object + if ( health > 0 && IsActiveAF() ) { + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } + + const int oldIdealWeapon = idealWeapon.Get(); + idealWeapon.UpdateFromSnapshot( newIdealWeapon, GetEntityNumber() ); + + if ( oldIdealWeapon != idealWeapon.Get() ) { + if ( snapshotStale ) { + weaponCatchup = true; + } + UpdateHudWeapon(); + } + + if ( lastHitToggle != newHitToggle ) { + SetLastHitTime( gameLocal.realClientTime ); + } + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idPlayer::WritePlayerStateToSnapshot +================ +*/ +void idPlayer::WritePlayerStateToSnapshot( idBitMsg &msg ) const { + msg.WriteByte( bobCycle ); + msg.WriteLong( stepUpTime ); + msg.WriteFloat( stepUpDelta ); + msg.WriteLong( inventory.weapons ); + msg.WriteByte( inventory.armor ); + + inventory.WriteAmmoToSnapshot( msg ); +} + +/* +================ +idPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsg &msg ) { + int newBobCycle = 0; + int newStepUpTime = 0; + int newStepUpDelta = 0; + + newBobCycle = msg.ReadByte(); + newStepUpTime = msg.ReadLong(); + newStepUpDelta = msg.ReadFloat(); + + inventory.weapons = msg.ReadLong(); + inventory.armor = msg.ReadByte(); + + inventory.ReadAmmoFromSnapshot( msg, GetEntityNumber() ); +} + +/* +================ +idPlayer::ServerReceiveEvent +================ +*/ +bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + + if ( idEntity::ServerReceiveEvent( event, time, msg ) ) { + return true; + } + + return false; +} + +/* +================ +idPlayer::ClientReceiveEvent +================ +*/ +bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_EXIT_TELEPORTER: + Event_ExitTeleporter(); + return true; + case EVENT_ABORT_TELEPORTER: + SetPrivateCameraView( NULL ); + return true; + case EVENT_POWERUP: { + int powerup = msg.ReadShort(); + int powertime = msg.ReadShort(); + if ( powertime > 0 ) { + GivePowerUp( powerup, powertime, ITEM_GIVE_UPDATE_STATE ); + } else { + ClearPowerup( powerup ); + } + return true; + } + case EVENT_PICKUPNAME: { + char buf[MAX_EVENT_PARAM_SIZE]; + msg.ReadString(buf, MAX_EVENT_PARAM_SIZE); + inventory.AddPickupName(buf, this); //_D3XP + return true; + } + case EVENT_SPECTATE: { + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + Spectate( spectate, true ); + return true; + } + case EVENT_ADD_DAMAGE_EFFECT: { + if ( spectating ) { + // if we're spectating, ignore + // happens if the event and the spectate change are written on the server during the same frame (fraglimit) + return true; + } + return idActor::ClientReceiveEvent( event, time, msg ); + } + case EVENT_FORCE_ORIGIN: { + + idVec3 forceOrigin = ReadFloatArray< idVec3 >( msg ); + idAngles forceAngles; + forceAngles[0] = msg.ReadFloat(); + forceAngles[1] = msg.ReadFloat(); + forceAngles[2] = msg.ReadFloat(); + + Event_ForceOrigin( forceOrigin, forceAngles ); + return true; + } + case EVENT_KNOCKBACK: { + idVec3 linearVelocity = ReadFloatArray< idVec3 >( msg ); + int knockbacktime = msg.ReadByte(); + physicsObj.SetLinearVelocity( linearVelocity ); + physicsObj.SetKnockBack( knockbacktime ); + return true; + } + default: { + return idActor::ClientReceiveEvent( event, time, msg ); + } + } +// return false; +} + +/* +================ +idPlayer::Hide +================ +*/ +void idPlayer::Hide() { + idWeapon *weap; + + idActor::Hide(); + weap = weapon.GetEntity(); + if ( weap ) { + weap->HideWorldModel(); + } + idWeapon * flash = flashlight.GetEntity(); + if( flash ) { + flash->HideWorldModel(); + } +} + +/* +================ +idPlayer::Show +================ +*/ +void idPlayer::Show() { + idWeapon *weap; + + idActor::Show(); + weap = weapon.GetEntity(); + if ( weap ) { + weap->ShowWorldModel(); + } + idWeapon * flash = flashlight.GetEntity(); + if( flash ) { + flash->ShowWorldModel(); + } +} + +/* +=============== +idPlayer::IsSoundChannelPlaying +=============== +*/ +bool idPlayer::IsSoundChannelPlaying( const s_channelType channel ) { + if ( GetSoundEmitter() != NULL ) { + return GetSoundEmitter()->CurrentlyPlaying( channel ); + } + + return false; +} + +/* +=============== +idPlayer::ShowTip +=============== +*/ +void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) { + if ( tipUp ) { + return; + } + + if ( hudManager ) { + hudManager->ShowTip( title, tip, autoHide ); + } + tipUp = true; +} + +/* +=============== +idPlayer::HideTip +=============== +*/ +void idPlayer::HideTip() { + if ( hudManager ) { + hudManager->HideTip(); + } + tipUp = false; +} + +/* +=============== +idPlayer::Event_HideTip +=============== +*/ +void idPlayer::Event_HideTip() { + HideTip(); +} + +/* +=============== +idPlayer::HideObjective +=============== +*/ +void idPlayer::HideObjective() { + + StartSound( "snd_objectivedown", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( hud ) { + if ( objectiveUp ) { + hud->HideObjective( false ); + objectiveUp = false; + } else { + hud->HideObjective( true ); + } + } +} + +/* +=============== +idPlayer::Event_StopAudioLog +=============== +*/ +void idPlayer::Event_StopAudioLog() { + //EndAudioLog(); +} + +/* +=============== +idPlayer::SetSpectateOrigin +=============== +*/ +void idPlayer::SetSpectateOrigin() { + idVec3 neworig; + + neworig = GetPhysics()->GetOrigin(); + neworig[ 2 ] += pm_normalviewheight.GetFloat(); + neworig[ 2 ] += SPECTATE_RAISE; + SetOrigin( neworig ); +} + +/* +=============== +idPlayer::RemoveWeapon +=============== +*/ +void idPlayer::RemoveWeapon( const char *weap ) { + if ( weap && *weap ) { + inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 ); + } +} + +/* +=============== +idPlayer::RemoveAllButEssentialWeapons +=============== +*/ +void idPlayer::RemoveAllButEssentialWeapons() { + const idKeyValue * kv = spawnArgs.MatchPrefix( "def_weapon", NULL ); + for ( ; kv != NULL; kv = spawnArgs.MatchPrefix( "def_weapon", kv ) ) { + // This list probably ought to be placed int the player's def + if ( kv->GetValue() == "weapon_fists" || kv->GetValue() == "weapon_soulcube" || kv->GetValue() == "weapon_pda" + || kv->GetValue() == "weapon_flashlight" || kv->GetValue() == "weapon_flashlight_new" ) { + continue; + } + inventory.Drop( spawnArgs, kv->GetValue(), -1 ); + } +} + +/* +=============== +idPlayer::CanShowWeaponViewmodel +=============== +*/ +bool idPlayer::CanShowWeaponViewmodel() const { + return ui_showGun.GetBool(); +} + +/* +=============== +idPlayer::SetLevelTrigger +=============== +*/ +void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) { + if ( levelName && *levelName && triggerName && *triggerName ) { + idLevelTriggerInfo lti; + lti.levelName = levelName; + lti.triggerName = triggerName; + inventory.levelTriggers.Append( lti ); + } +} + +/* +=============== +idPlayer::Event_LevelTrigger +=============== +*/ +void idPlayer::Event_LevelTrigger() { + idStr mapName = gameLocal.GetMapName(); + mapName.StripPath(); + mapName.StripFileExtension(); + for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) { + if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){ + idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 1, this ); + } + } + } +} + +/* +=============== +idPlayer::Event_Gibbed +=============== +*/ +void idPlayer::Event_Gibbed() { + // do nothing +} + +extern idCVar net_clientMaxPrediction; + +/* +=============== +idPlayer::UpdatePlayerIcons +=============== +*/ +void idPlayer::UpdatePlayerIcons() { + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + int lastPacketTime = lobby.GetPeerTimeSinceLastPacket( lobby.PeerIndexFromLobbyUser( gameLocal.lobbyUserIDs[entityNumber] ) ); + isLagged = ( lastPacketTime > net_clientMaxPrediction.GetInteger() ); + //isChatting = ( ( usercmd.buttons & BUTTON_CHATTING ) && ( health > 0 ) ); +} + +/* +=============== +idPlayer::DrawPlayerIcons +=============== +*/ +void idPlayer::DrawPlayerIcons() { + if ( !NeedsIcon() ) { + playerIcon.FreeIcon(); + return; + } + + // Never draw icons for hidden players. + if ( this->IsHidden() ) + return; + + playerIcon.Draw( this, headJoint ); +} + +/* +=============== +idPlayer::HidePlayerIcons +=============== +*/ +void idPlayer::HidePlayerIcons() { + playerIcon.FreeIcon(); +} + +/* +=============== +idPlayer::NeedsIcon +============== +*/ +bool idPlayer::NeedsIcon() { + // local clients don't render their own icons... they're only info for other clients + // always draw icons in CTF games + return !IsLocallyControlled() && ( ( g_CTFArrows.GetBool() && gameLocal.mpGame.IsGametypeFlagBased() && !IsHidden() && !AI_DEAD ) || ( isLagged ) ); +} + +/* +=============== +idPlayer::DropFlag() +============== +*/ +void idPlayer::DropFlag() { + if ( !carryingFlag || !common->IsMultiplayer() || !gameLocal.mpGame.IsGametypeFlagBased() ) /* CTF */ + return; + + idEntity * entity = gameLocal.mpGame.GetTeamFlag( 1 - team ); + if ( entity ) { + idItemTeam * item = static_cast(entity); + + if ( item->carried && !item->dropped ) { + item->Drop( health <= 0 ); + carryingFlag = false; + } + } + +} + +void idPlayer::ReturnFlag() { + + if ( !carryingFlag || !common->IsMultiplayer() || !gameLocal.mpGame.IsGametypeFlagBased() ) /* CTF */ + return; + + idEntity * entity = gameLocal.mpGame.GetTeamFlag( 1 - team ); + if ( entity ) { + idItemTeam * item = static_cast(entity); + + if ( item->carried && !item->dropped ) { + item->Return(); + carryingFlag = false; + } + } +} + +void idPlayer::FreeModelDef() { + idAFEntity_Base::FreeModelDef(); + if ( common->IsMultiplayer() && gameLocal.mpGame.IsGametypeFlagBased() ) + playerIcon.FreeIcon(); +} + +/* +======================== +idView::SetControllerShake +======================== +*/ +void idPlayer::SetControllerShake( float highMagnitude, int highDuration, float lowMagnitude, int lowDuration ) { + + // the main purpose of having these buffer is so multiple, individual shake events can co-exist with each other, + // for instance, a constant low rumble from the chainsaw when it's idle and a harsh rumble when it's being used. + + // find active buffer with similar magnitude values + int activeBufferWithSimilarMags = -1; + int inactiveBuffer = -1; + for ( int i=0; i -1 ) { + // average the magnitudes and adjust the time + controllerShakeHighMag[ activeBufferWithSimilarMags ] += highMagnitude; + controllerShakeHighMag[ activeBufferWithSimilarMags ] *= 0.5f; + + controllerShakeLowMag[ activeBufferWithSimilarMags ] += lowMagnitude; + controllerShakeLowMag[ activeBufferWithSimilarMags ] *= 0.5f; + + controllerShakeHighTime[ activeBufferWithSimilarMags ] = gameLocal.GetTime() + highDuration; + controllerShakeLowTime[ activeBufferWithSimilarMags ] = gameLocal.GetTime() + lowDuration; + controllerShakeTimeGroup = gameLocal.selectedGroup; + return; + } + + if ( inactiveBuffer == -1 ) { + inactiveBuffer = 0; // FIXME: probably want to use the oldest buffer.. + } + + controllerShakeHighMag[ inactiveBuffer ] = highMagnitude; + controllerShakeLowMag[ inactiveBuffer ] = lowMagnitude; + controllerShakeHighTime[ inactiveBuffer ] = gameLocal.GetTime() + highDuration; + controllerShakeLowTime[ inactiveBuffer ] = gameLocal.GetTime() + lowDuration; + controllerShakeTimeGroup = gameLocal.selectedGroup; +} + +/* +======================== +idView::ResetControllerShake +======================== +*/ +void idPlayer::ResetControllerShake() { + for ( int i=0; i lowMag ) { + lowMag = controllerShakeLowMag[i]; + } + } + if ( gameLocal.GetTimeGroupTime( controllerShakeTimeGroup ) < controllerShakeHighTime[i] ) { + if ( controllerShakeHighMag[i] > highMag ) { + highMag = controllerShakeHighMag[i]; + } + } + } + + lowMagnitude = idMath::Ftoi( lowMag * 65535.0f ); + highMagnitude = idMath::Ftoi( highMag * 65535.0f ); +} + +/* +======================== +idPlayer::GetExpansionType +======================== +*/ +gameExpansionType_t idPlayer::GetExpansionType() const { + const char * expansion = spawnArgs.GetString( "player_expansion", "d3" ); + if ( idStr::Icmp( expansion, "d3" ) == 0 ) { + return GAME_BASE; + } + if ( idStr::Icmp( expansion, "d3xp" ) == 0 ) { + return GAME_D3XP; + } + if ( idStr::Icmp( expansion, "d3le" ) == 0 ) { + return GAME_D3LE; + } + return GAME_UNKNOWN; +} diff --git a/neo/d3xp/Player.h b/neo/d3xp/Player.h new file mode 100644 index 00000000..375e471f --- /dev/null +++ b/neo/d3xp/Player.h @@ -0,0 +1,924 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_PLAYER_H__ +#define __GAME_PLAYER_H__ + +#include "PredictedValue.h" + +/* +=============================================================================== + + Player entity. + +=============================================================================== +*/ + +class idMenuHandler_PDA; +class idMenuHandler_HUD; +class idMenuScreen_HUD; +class idTarget_SetPrimaryObjective; + +extern const idEventDef EV_Player_GetButtons; +extern const idEventDef EV_Player_GetMove; +extern const idEventDef EV_Player_GetViewAngles; +extern const idEventDef EV_Player_EnableWeapon; +extern const idEventDef EV_Player_DisableWeapon; +extern const idEventDef EV_Player_ExitTeleporter; +extern const idEventDef EV_Player_SelectWeapon; +extern const idEventDef EV_SpectatorTouch; + +const float THIRD_PERSON_FOCUS_DISTANCE = 512.0f; +const int LAND_DEFLECT_TIME = 150; +const int LAND_RETURN_TIME = 300; +const int FOCUS_TIME = 300; +const int FOCUS_GUI_TIME = 500; +const int NUM_QUICK_SLOTS = 4; + +const int MAX_WEAPONS = 32; + +const int DEAD_HEARTRATE = 0; // fall to as you die +const int LOWHEALTH_HEARTRATE_ADJ = 20; // +const int DYING_HEARTRATE = 30; // used for volumen calc when dying/dead +const int BASE_HEARTRATE = 70; // default +const int ZEROSTAMINA_HEARTRATE = 115; // no stamina +const int MAX_HEARTRATE = 130; // maximum +const int ZERO_VOLUME = -40; // volume at zero +const int DMG_VOLUME = 5; // volume when taking damage +const int DEATH_VOLUME = 15; // volume at death + +const int SAVING_THROW_TIME = 5000; // maximum one "saving throw" every five seconds + +const int ASYNC_PLAYER_INV_AMMO_BITS = idMath::BitsForInteger( 3000 ); +const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range [-1, 60] + +enum gameExpansionType_t { + GAME_BASE, + GAME_D3XP, + GAME_D3LE, + GAME_UNKNOWN +}; + +struct idObjectiveInfo { + idStr title; + idStr text; + const idMaterial * screenshot; +}; + +struct idLevelTriggerInfo { + idStr levelName; + idStr triggerName; +}; + +// powerups - the "type" in item .def must match +enum { + BERSERK = 0, + INVISIBILITY, + MEGAHEALTH, + ADRENALINE, + INVULNERABILITY, + HELLTIME, + ENVIROSUIT, + //HASTE, + ENVIROTIME, + MAX_POWERUPS +}; + +// powerup modifiers +enum { + SPEED = 0, + PROJECTILE_DAMAGE, + MELEE_DAMAGE, + MELEE_DISTANCE +}; + +// influence levels +enum { + INFLUENCE_NONE = 0, // none + INFLUENCE_LEVEL1, // no gun or hud + INFLUENCE_LEVEL2, // no gun, hud, movement + INFLUENCE_LEVEL3, // slow player movement +}; + +typedef struct { + int ammo; + int rechargeTime; + char ammoName[128]; +} RechargeAmmo_t; + +typedef struct { + char name[64]; + idList toggleList; + int lastUsed; +} WeaponToggle_t; + +class idInventory { +public: + int maxHealth; + int weapons; + int powerups; + int armor; + int maxarmor; + int powerupEndTime[ MAX_POWERUPS ]; + + RechargeAmmo_t rechargeAmmo[ AMMO_NUMTYPES ]; + + // mp + int ammoPredictTime; // Unused now but kept for save file compatibility. + + int deplete_armor; + float deplete_rate; + int deplete_ammount; + int nextArmorDepleteTime; + + int pdasViewed[4]; // 128 bit flags for indicating if a pda has been viewed + + int selPDA; + int selEMail; + int selVideo; + int selAudio; + bool pdaOpened; + idList items; + idList pdaSecurity; + idList pdas; + idList videos; + idList emails; + + bool ammoPulse; + bool weaponPulse; + bool armorPulse; + int lastGiveTime; + + idList levelTriggers; + + idInventory() { Clear(); } + ~idInventory() { Clear(); } + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Clear(); + void GivePowerUp( idPlayer *player, int powerup, int msec ); + void ClearPowerUps(); + void GetPersistantData( idDict &dict ); + void RestoreInventory( idPlayer *owner, const idDict &dict ); + bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, + idPredictedValue< int > * idealWeapon, bool updateHud, unsigned int giveFlags ); + void Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ); + ammo_t AmmoIndexForAmmoClass( const char *ammo_classname ) const; + int MaxAmmoForAmmoClass( const idPlayer *owner, const char *ammo_classname ) const; + int WeaponIndexForAmmoClass( const idDict & spawnArgs, const char *ammo_classname ) const; + ammo_t AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ); + const char * AmmoPickupNameForIndex( ammo_t ammonum ) const; + void AddPickupName( const char * name, idPlayer * owner ); //_D3XP + + int HasAmmo( ammo_t type, int amount ); + bool UseAmmo( ammo_t type, int amount ); + int HasAmmo( const char *weapon_classname, bool includeClip = false, idPlayer* owner = NULL ); // _D3XP + + bool HasEmptyClipCannotRefill(const char *weapon_classname, idPlayer* owner); + + void UpdateArmor(); + + void SetInventoryAmmoForType( const int ammoType, const int amount ); + void SetClipAmmoForWeapon( const int weapon, const int amount ); + + int GetInventoryAmmoForType( const int ammoType ) const; + int GetClipAmmoForWeapon( const int weapon ) const; + + void WriteAmmoToSnapshot( idBitMsg & msg ) const; + void ReadAmmoFromSnapshot( const idBitMsg & msg, int ownerEntityNumber ); + + void SetRemoteClientAmmo( const int ownerEntityNumber ); + + int nextItemPickup; + int nextItemNum; + int onePickupTime; + idList pickupItemNames; + idList objectiveNames; + + void InitRechargeAmmo(idPlayer *owner); + void RechargeAmmo(idPlayer *owner); + bool CanGive( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value ); + +private: + idArray< idPredictedValue< int >, AMMO_NUMTYPES > ammo; + idArray< idPredictedValue< int >, MAX_WEAPONS > clip; +}; + +typedef struct { + int time; + idVec3 dir; // scaled larger for running +} loggedAccel_t; + +typedef struct { + int areaNum; + idVec3 pos; +} aasLocation_t; + +class idPlayer : public idActor { +public: + enum { + EVENT_IMPULSE = idEntity::EVENT_MAXEVENTS, + EVENT_EXIT_TELEPORTER, + EVENT_ABORT_TELEPORTER, + EVENT_POWERUP, + EVENT_SPECTATE, + EVENT_PICKUPNAME, + EVENT_FORCE_ORIGIN, + EVENT_KNOCKBACK, + EVENT_MAXEVENTS + }; + + static const int MAX_PLAYER_PDA = 100; + static const int MAX_PLAYER_VIDEO = 100; + static const int MAX_PLAYER_AUDIO = 100; + static const int MAX_PLAYER_AUDIO_ENTRIES = 2; + + usercmd_t oldCmd; + usercmd_t usercmd; + + class idPlayerView playerView; // handles damage kicks and effects + + renderEntity_t laserSightRenderEntity; // replace crosshair for 3DTV + qhandle_t laserSightHandle; + + bool noclip; + bool godmode; + + bool spawnAnglesSet; // on first usercmd, we must set deltaAngles + idAngles spawnAngles; + idAngles viewAngles; // player view angles + idAngles cmdAngles; // player cmd angles + float independentWeaponPitchAngle; // viewAngles[PITCH} when head tracking is active + + // For interpolating angles between snapshots + idQuat previousViewQuat; + idQuat nextViewQuat; + + int buttonMask; + int oldButtons; + int oldImpulseSequence; + + int lastHitTime; // last time projectile fired by player hit target + int lastSndHitTime; // MP hit sound - != lastHitTime because we throttle + int lastSavingThrowTime; // for the "free miss" effect + + bool pdaHasBeenRead[ MAX_PLAYER_PDA ]; + bool videoHasBeenViewed[ MAX_PLAYER_VIDEO ]; + bool audioHasBeenHeard[ MAX_PLAYER_AUDIO ][ MAX_PLAYER_AUDIO_ENTRIES ]; + + idScriptBool AI_FORWARD; + idScriptBool AI_BACKWARD; + idScriptBool AI_STRAFE_LEFT; + idScriptBool AI_STRAFE_RIGHT; + idScriptBool AI_ATTACK_HELD; + idScriptBool AI_WEAPON_FIRED; + idScriptBool AI_JUMP; + idScriptBool AI_CROUCH; + idScriptBool AI_ONGROUND; + idScriptBool AI_ONLADDER; + idScriptBool AI_DEAD; + idScriptBool AI_RUN; + idScriptBool AI_PAIN; + idScriptBool AI_HARDLANDING; + idScriptBool AI_SOFTLANDING; + idScriptBool AI_RELOAD; + idScriptBool AI_TELEPORT; + idScriptBool AI_TURN_LEFT; + idScriptBool AI_TURN_RIGHT; + + // inventory + idInventory inventory; + idTarget_SetPrimaryObjective * primaryObjective; + + int flashlightBattery; + idEntityPtr flashlight; + + idEntityPtr weapon; + idMenuHandler_HUD * hudManager; + idMenuScreen_HUD * hud; + idMenuHandler_PDA * pdaMenu; + idSWF * mpMessages; + bool objectiveSystemOpen; + int quickSlot[ NUM_QUICK_SLOTS ]; + + int weapon_soulcube; + int weapon_pda; + int weapon_fists; + int weapon_flashlight; + int weapon_chainsaw; + int weapon_bloodstone; + int weapon_bloodstone_active1; + int weapon_bloodstone_active2; + int weapon_bloodstone_active3; + bool harvest_lock; + + int heartRate; + idInterpolate heartInfo; + int lastHeartAdjust; + int lastHeartBeat; + int lastDmgTime; + int deathClearContentsTime; + bool doingDeathSkin; + int lastArmorPulse; // lastDmgTime if we had armor at time of hit + float stamina; + float healthPool; // amount of health to give over time + int nextHealthPulse; + bool healthPulse; + bool healthTake; + int nextHealthTake; + + //----------------------------------------------------------------- + // controller shake parms + //----------------------------------------------------------------- + + const static int MAX_SHAKE_BUFFER = 3; + float controllerShakeHighMag[ MAX_SHAKE_BUFFER ]; // magnitude of the high frequency controller shake + float controllerShakeLowMag[ MAX_SHAKE_BUFFER ]; // magnitude of the low frequency controller shake + int controllerShakeHighTime[ MAX_SHAKE_BUFFER ]; // time the controller shake ends for high frequency. + int controllerShakeLowTime[ MAX_SHAKE_BUFFER ]; // time the controller shake ends for low frequency. + int controllerShakeTimeGroup; + + bool hiddenWeapon; // if the weapon is hidden ( in noWeapons maps ) + idEntityPtr soulCubeProjectile; + + idAimAssist aimAssist; + + int spectator; + bool forceScoreBoard; + bool forceRespawn; + bool spectating; + int lastSpectateTeleport; + bool lastHitToggle; + bool wantSpectate; // from userInfo + bool weaponGone; // force stop firing + bool useInitialSpawns; // toggled by a map restart to be active for the first game spawn + int tourneyRank; // for tourney cycling - the higher, the more likely to play next - server + int tourneyLine; // client side - our spot in the wait line. 0 means no info. + int spawnedTime; // when client first enters the game + + bool carryingFlag; // is the player carrying the flag? + + idEntityPtr teleportEntity; // while being teleported, this is set to the entity we'll use for exit + int teleportKiller; // entity number of an entity killing us at teleporter exit + bool lastManOver; // can't respawn in last man anymore (srv only) + bool lastManPlayAgain; // play again when end game delay is cancelled out before expiring (srv only) + bool lastManPresent; // true when player was in when game started (spectators can't join a running LMS) + bool isLagged; // replicated from server, true if packets haven't been received from client. + int isChatting; // replicated from server, true if the player is chatting. + + // timers + int minRespawnTime; // can respawn when time > this, force after g_forcerespawn + int maxRespawnTime; // force respawn after this time + + // the first person view values are always calculated, even + // if a third person view is used + idVec3 firstPersonViewOrigin; + idMat3 firstPersonViewAxis; + + idDragEntity dragEntity; + + idFuncMountedObject * mountedObject; + idEntityPtr enviroSuitLight; + + bool healthRecharge; + int lastHealthRechargeTime; + int rechargeSpeed; + + float new_g_damageScale; + + bool bloomEnabled; + float bloomSpeed; + float bloomIntensity; + +public: + CLASS_PROTOTYPE( idPlayer ); + + idPlayer(); + virtual ~idPlayer(); + + void Spawn(); + void Think(); + + void UpdateLaserSight(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + virtual void Hide(); + virtual void Show(); + + void Init(); + void PrepareForRestart(); + virtual void Restart(); + void LinkScriptVariables(); + void SetupWeaponEntity(); + void SelectInitialSpawnPoint( idVec3 &origin, idAngles &angles ); + void SpawnFromSpawnSpot(); + void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + void SetClipModel(); // spectator mode uses a different bbox size + + void SavePersistantInfo(); + void RestorePersistantInfo(); + void SetLevelTrigger( const char *levelName, const char *triggerName ); + + void CacheWeapons(); + + void EnterCinematic(); + void ExitCinematic(); + + void UpdateConditions(); + void SetViewAngles( const idAngles &angles ); + + // Controller Shake + void ControllerShakeFromDamage( int damage ); + void SetControllerShake( float highMagnitude, int highDuration, float lowMagnitude, int lowDuration ); + void ResetControllerShake(); + void GetControllerShake( int & highMagnitude, int & lowMagnitude ) const; + + idAimAssist * GetAimAssist() { return &aimAssist; } + + // delta view angles to allow movers to rotate the view of the player + void UpdateDeltaViewAngles( const idAngles &angles ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + virtual void GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ); + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + void CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + // New damage path for instant client feedback. + void ServerDealDamage( int damage, idEntity & inflictor, idEntity & attacker, const idVec3 & dir, const char * damageDefName, const int location ); // Actually updates the player's health independent of feedback. + int AdjustDamageAmount( const int inputDamage ); + + // use exitEntityNum to specify a teleport with private camera view and delayed exit + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + void Kill( bool delayRespawn, bool nodamage ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void StartFxOnBone(const char *fx, const char *bone); + + renderView_t * GetRenderView(); + void CalculateRenderView(); // called every tic by player code + void CalculateFirstPersonView(); + + void AddChatMessage( int index, int alpha, const idStr & message ); + void UpdateSpectatingText(); + void ClearChatMessage( int index ); + + void DrawHUD( idMenuHandler_HUD * hudManager ); + + void WeaponFireFeedback( const idDict *weaponDef ); + + float DefaultFov() const; + float CalcFov( bool honorZoom ); + void CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ); + idVec3 GetEyePosition() const; + void GetViewPos( idVec3 &origin, idMat3 &axis ) const; + void OffsetThirdPersonView( float angle, float range, float height, bool clip ); + + bool Give( const char *statname, const char *value, unsigned int giveFlags ); + bool GiveItem( idItem *item, unsigned int giveFlags ); + void GiveItem( const char *name ); + void GiveHealthPool( float amt ); + + void SetPrimaryObjective( idTarget_SetPrimaryObjective * target ) { primaryObjective = target; } + idTarget_SetPrimaryObjective * GetPrimaryObjective() { return primaryObjective; } + + idInventory & GetInventory() { return inventory; } + bool GiveInventoryItem( idDict *item, unsigned int giveFlags ); + void RemoveInventoryItem( idDict *item ); + bool GiveInventoryItem( const char *name ); + void RemoveInventoryItem( const char *name ); + idDict * FindInventoryItem( const char *name ); + idDict * FindInventoryItem( int index ); + int GetNumInventoryItems(); + void PlayAudioLog( const idSoundShader * sound ); + void EndAudioLog(); + void PlayVideoDisk( const idDeclVideo * decl ); + void EndVideoDisk(); + const idMaterial * GetVideoMaterial() { return pdaVideoMat; } + + void SetQuickSlot( int index, int val ); + int GetQuickSlot( int index ); + + void GivePDA( const idDeclPDA * pda, const char * securityItem ); + void GiveVideo( const idDeclVideo * video, const char * itemName ); + void GiveEmail( const idDeclEmail * email ); + void GiveSecurity( const char * security ); + void GiveObjective( const char * title, const char * text, const idMaterial * screenshot ); + void CompleteObjective( const char * title ); + + bool GivePowerUp( int powerup, int time, unsigned int giveFlags ); + void ClearPowerUps(); + bool PowerUpActive( int powerup ) const; + float PowerUpModifier( int type ); + + int SlotForWeapon( const char *weaponName ); + void Reload(); + void NextWeapon(); + void NextBestWeapon(); + void PrevWeapon(); + void SetPreviousWeapon( int num ) { previousWeapon = num; } + void SelectWeapon( int num, bool force ); + void DropWeapon( bool died ) ; + void StealWeapon( idPlayer *player ); + void AddProjectilesFired( int count ); + void AddProjectileHits( int count ); + void SetLastHitTime( int time ); + void LowerWeapon(); + void RaiseWeapon(); + void WeaponLoweringCallback(); + void WeaponRisingCallback(); + void RemoveWeapon( const char *weap ); + void RemoveAllButEssentialWeapons(); + bool CanShowWeaponViewmodel() const; + + void AddAIKill(); + void SetSoulCubeProjectile( idProjectile *projectile ); + + void AdjustHeartRate( int target, float timeInSecs, float delay, bool force ); + void SetCurrentHeartRate(); + int GetBaseHeartRate(); + void UpdateAir(); + + void UpdatePowerupHud(); + + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + bool GuiActive() { return focusGUIent != NULL; } + + bool HandleGuiEvents( const sysEvent_t * ev ); + void PerformImpulse( int impulse ); + void Spectate( bool spectate, bool force = false ); + void TogglePDA(); + void RouteGuiMouse( idUserInterface *gui ); + void UpdateHud(); + const idDeclPDA * GetPDA() const; + bool GetPDAOpen() const { return objectiveSystemOpen; } + const idDeclVideo * GetVideo( int index ); + void SetInfluenceFov( float fov ); + void SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ); + void SetInfluenceLevel( int level ); + int GetInfluenceLevel() { return influenceActive; }; + void SetPrivateCameraView( idCamera *camView ); + idCamera * GetPrivateCameraView() const { return privateCameraView; } + void StartFxFov( float duration ); + void UpdateHudWeapon( bool flashWeapon = true ); + void UpdateChattingHud(); + void UpdateHudStats( idMenuHandler_HUD * hudManager ); + void Event_StopAudioLog(); + bool IsSoundChannelPlaying( const s_channelType channel = SND_CHANNEL_ANY ); + void ShowTip( const char *title, const char *tip, bool autoHide ); + void HideTip(); + bool IsTipVisible() { return tipUp; }; + void HideObjective(); + + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + void WritePlayerStateToSnapshot( idBitMsg &msg ) const; + void ReadPlayerStateFromSnapshot( const idBitMsg &msg ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + bool IsRespawning(); + bool IsInTeleport(); + + int GetSkinIndex() const { return skinIndex; } + + idEntity *GetInfluenceEntity() { return influenceEntity; }; + const idMaterial *GetInfluenceMaterial() { return influenceMaterial; }; + float GetInfluenceRadius() { return influenceRadius; }; + + // server side work for in/out of spectate. takes care of spawning it into the world as well + void ServerSpectate( bool spectate ); + // for very specific usage. != GetPhysics() + idPhysics *GetPlayerPhysics(); + void TeleportDeath( int killer ); + void SetLeader( bool lead ); + bool IsLeader(); + + void UpdateSkinSetup(); + + bool OnLadder() const; + + virtual void UpdatePlayerIcons(); + virtual void DrawPlayerIcons(); + virtual void HidePlayerIcons(); + bool NeedsIcon(); + + void StartHealthRecharge(int speed); + void StopHealthRecharge(); + + idStr GetCurrentWeapon(); + int GetCurrentWeaponSlot() { return currentWeapon; } + int GetIdealWeapon() { return idealWeapon.Get(); } + idHashTable GetWeaponToggles() const { return weaponToggles; } + + bool CanGive( const char *statname, const char *value ); + + void StopHelltime( bool quick = true ); + void PlayHelltimeStopSound(); + + void DropFlag(); // drop CTF item + void ReturnFlag(); + virtual void FreeModelDef(); + + bool SelfSmooth(); + void SetSelfSmooth( bool b ); + + const idAngles & GetViewBobAngles() { return viewBobAngles; } + const idVec3 & GetViewBob() { return viewBob; } + + idAchievementManager & GetAchievementManager() { return achievementManager; } + const idAchievementManager & GetAchievementManager() const { return achievementManager; } + + int GetPlayedTime() const { return playedTimeSecs; } + + void HandleUserCmds( const usercmd_t & newcmd ); + + int GetClientFireCount() const { return clientFireCount; } + void IncrementFireCount() { ++clientFireCount; } + + void ShowRespawnHudMessage(); + void HideRespawnHudMessage(); + + bool IsLocallyControlled() const { return entityNumber == gameLocal.GetLocalClientNum(); } + + gameExpansionType_t GetExpansionType() const; + + void AddProjectileKills() { numProjectileKills++; } + int GetProjectileKills() const { return numProjectileKills; } + void ResetProjectileKills() { numProjectileKills = 0; } +private: + // Stats & achievements + idAchievementManager achievementManager; + + int playedTimeSecs; + int playedTimeResidual; + + jointHandle_t hipJoint; + jointHandle_t chestJoint; + jointHandle_t headJoint; + + idPhysics_Player physicsObj; // player physics + + idList aasLocation; // for AI tracking the player + + int bobFoot; + float bobFrac; + float bobfracsin; + int bobCycle; // for view bobbing and footstep generation + float xyspeed; + int stepUpTime; + float stepUpDelta; + float idealLegsYaw; + float legsYaw; + bool legsForward; + float oldViewYaw; + idAngles viewBobAngles; + idVec3 viewBob; + int landChange; + int landTime; + + + int currentWeapon; + idPredictedValue< int > idealWeapon; + int previousWeapon; + int weaponSwitchTime; + bool weaponEnabled; + + int skinIndex; + const idDeclSkin * skin; + const idDeclSkin * powerUpSkin; + + int numProjectilesFired; // number of projectiles fired + int numProjectileHits; // number of hits on mobs + int numProjectileKills; // number of kills with a projectile. + + bool airless; + int airMsec; // set to pm_airMsec at start, drops in vacuum + int lastAirDamage; + + bool gibDeath; + bool gibsLaunched; + idVec3 gibsDir; + + idInterpolate zoomFov; + idInterpolate centerView; + bool fxFov; + + float influenceFov; + int influenceActive; // level of influence.. 1 == no gun or hud .. 2 == 1 + no movement + idEntity * influenceEntity; + const idMaterial * influenceMaterial; + float influenceRadius; + const idDeclSkin * influenceSkin; + + idCamera * privateCameraView; + + static const int NUM_LOGGED_VIEW_ANGLES = 64; // for weapon turning angle offsets + idAngles loggedViewAngles[NUM_LOGGED_VIEW_ANGLES]; // [gameLocal.framenum&(LOGGED_VIEW_ANGLES-1)] + static const int NUM_LOGGED_ACCELS = 16; // for weapon turning angle offsets + loggedAccel_t loggedAccel[NUM_LOGGED_ACCELS]; // [currentLoggedAccel & (NUM_LOGGED_ACCELS-1)] + int currentLoggedAccel; + + // if there is a focusGUIent, the attack button will be changed into mouse clicks + idEntity * focusGUIent; + idUserInterface * focusUI; // focusGUIent->renderEntity.gui, gui2, or gui3 + idAI * focusCharacter; + int talkCursor; // show the state of the focusCharacter (0 == can't talk/dead, 1 == ready to talk, 2 == busy talking) + int focusTime; + idAFEntity_Vehicle * focusVehicle; + idUserInterface * cursor; + + // full screen guis track mouse movements directly + int oldMouseX; + int oldMouseY; + + const idMaterial * pdaVideoMat; + + bool tipUp; + bool objectiveUp; + + int lastDamageDef; + idVec3 lastDamageDir; + int lastDamageLocation; + int smoothedFrame; + bool smoothedOriginUpdated; + idVec3 smoothedOrigin; + idAngles smoothedAngles; + + idHashTable weaponToggles; + + int hudPowerup; + int lastHudPowerup; + int hudPowerupDuration; + + // mp + bool respawning; // set to true while in SpawnToPoint for telefrag checks + bool leader; // for sudden death situations + int lastSpectateChange; + int lastTeleFX; + bool weaponCatchup; // raise up the weapon silently ( state catchups ) + int MPAim; // player num in aim + int lastMPAim; + int lastMPAimTime; // last time the aim changed + int MPAimFadeTime; // for GUI fade + bool MPAimHighlight; + bool isTelefragged; // proper obituaries + int serverOverridePositionTime; + int clientFireCount; + + idPlayerIcon playerIcon; + + bool selfSmooth; + + netBoolEvent_t respawn_netEvent; + + void LookAtKiller( idEntity *inflictor, idEntity *attacker ); + + void StopFiring(); + void FireWeapon(); + void Weapon_Combat(); + void Weapon_NPC(); + void Weapon_GUI(); + void UpdateWeapon(); + void UpdateFlashlight(); + void FlashlightOn(); + void FlashlightOff(); + void UpdateSpectating(); + void SpectateFreeFly( bool force ); // ignore the timeout to force when followed spec is no longer valid + void SpectateCycle(); + idAngles GunTurningOffset(); + idVec3 GunAcceleratingOffset(); + + void UseObjects(); + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + void BobCycle( const idVec3 &pushVelocity ); + void UpdateViewAngles(); + void EvaluateControls(); + void AdjustSpeed(); + void AdjustBodyAngles(); + void InitAASLocation(); + void SetAASLocation(); + void Move(); + void Move_Interpolated( float fraction ); + void RunPhysics_RemoteClientCorrection(); + void UpdatePowerUps(); + void UpdateDeathSkin( bool state_hitch ); + void ClearPowerup( int i ); + void SetSpectateOrigin(); + bool AllowClientAuthPhysics(); + virtual int GetPhysicsTimeStep() const; + + void ClearFocus(); + void UpdateFocus(); + void UpdateLocation(); + idUserInterface * ActiveGui(); + + // mp + void Respawn_Shared(); + + bool WeaponAvailable( const char* name ); + + void UseVehicle(); + + void Event_GetButtons(); + void Event_GetMove(); + void Event_GetViewAngles(); + void Event_StopFxFov(); + void Event_EnableWeapon(); + void Event_DisableWeapon(); + void Event_GetCurrentWeapon(); + void Event_GetPreviousWeapon(); + void Event_SelectWeapon( const char *weaponName ); + void Event_GetWeaponEntity(); + void Event_OpenPDA(); + void Event_PDAAvailable(); + void Event_InPDA(); + void Event_ExitTeleporter(); + void Event_HideTip(); + void Event_LevelTrigger(); + void Event_Gibbed(); + void Event_ForceOrigin( idVec3 & origin, idAngles & angles ); + void Event_GiveInventoryItem( const char* name ); + void Event_RemoveInventoryItem( const char* name ); + + void Event_GetIdealWeapon(); + void Event_WeaponAvailable( const char* name ); + void Event_SetPowerupTime( int powerup, int time ); + void Event_IsPowerupActive( int powerup ); + void Event_StartWarp(); + void Event_StopHelltime( int mode ); + void Event_ToggleBloom( int on ); + void Event_SetBloomParms( float speed, float intensity ); +}; + +ID_INLINE bool idPlayer::IsRespawning() { + return respawning; +} + +ID_INLINE idPhysics* idPlayer::GetPlayerPhysics() { + return &physicsObj; +} + +ID_INLINE bool idPlayer::IsInTeleport() { + return ( teleportEntity.GetEntity() != NULL ); +} + +ID_INLINE void idPlayer::SetLeader( bool lead ) { + leader = lead; +} + +ID_INLINE bool idPlayer::IsLeader() { + return leader; +} + +ID_INLINE bool idPlayer::SelfSmooth() { + return selfSmooth; +} + +ID_INLINE void idPlayer::SetSelfSmooth( bool b ) { + selfSmooth = b; +} + +extern idCVar g_infiniteAmmo; + +#endif /* !__GAME_PLAYER_H__ */ + diff --git a/neo/d3xp/PlayerIcon.cpp b/neo/d3xp/PlayerIcon.cpp new file mode 100644 index 00000000..1f13f77b --- /dev/null +++ b/neo/d3xp/PlayerIcon.cpp @@ -0,0 +1,190 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "PlayerIcon.h" + +static const char * iconKeys[ ICON_NONE ] = { + "mtr_icon_lag", + "mtr_icon_chat" + ,"mtr_icon_redteam", + "mtr_icon_blueteam" +}; + +/* +=============== +idPlayerIcon::idPlayerIcon +=============== +*/ +idPlayerIcon::idPlayerIcon() { + iconHandle = -1; + iconType = ICON_NONE; +} + +/* +=============== +idPlayerIcon::~idPlayerIcon +=============== +*/ +idPlayerIcon::~idPlayerIcon() { + FreeIcon(); +} + +/* +=============== +idPlayerIcon::Draw +=============== +*/ +void idPlayerIcon::Draw( idPlayer *player, jointHandle_t joint ) { + idVec3 origin; + idMat3 axis; + + if ( joint == INVALID_JOINT ) { + FreeIcon(); + return; + } + + player->GetJointWorldTransform( joint, gameLocal.time, origin, axis ); + origin.z += 16.0f; + + Draw( player, origin ); +} + +/* +=============== +idPlayerIcon::Draw +=============== +*/ +void idPlayerIcon::Draw( idPlayer *player, const idVec3 &origin ) { + idPlayer *localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer || !localPlayer->GetRenderView() ) { + FreeIcon(); + return; + } + + idMat3 axis = localPlayer->GetRenderView()->viewaxis; + + if ( player->isLagged && !player->spectating ) { + // create the icon if necessary, or update if already created + if ( !CreateIcon( player, ICON_LAG, origin, axis ) ) { + UpdateIcon( player, origin, axis ); + } + } else if ( g_CTFArrows.GetBool() && gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.GetLocalPlayer() && player->team == gameLocal.GetLocalPlayer()->team && !player->IsHidden() && !player->AI_DEAD ) { + int icon = ICON_TEAM_RED + player->team; + + if ( icon != ICON_TEAM_RED && icon != ICON_TEAM_BLUE ) + return; + + if ( !CreateIcon( player, ( playerIconType_t )icon, origin, axis ) ) { + UpdateIcon( player, origin, axis ); + } + } else { + FreeIcon(); + } +} + +/* +=============== +idPlayerIcon::FreeIcon +=============== +*/ +void idPlayerIcon::FreeIcon() { + if ( iconHandle != - 1 ) { + gameRenderWorld->FreeEntityDef( iconHandle ); + iconHandle = -1; + } + iconType = ICON_NONE; +} + +/* +=============== +idPlayerIcon::CreateIcon +=============== +*/ +bool idPlayerIcon::CreateIcon( idPlayer *player, playerIconType_t type, const idVec3 &origin, const idMat3 &axis ) { + assert( type < ICON_NONE ); + const char *mtr = player->spawnArgs.GetString( iconKeys[ type ], "_default" ); + return CreateIcon( player, type, mtr, origin, axis ); +} + +/* +=============== +idPlayerIcon::CreateIcon +=============== +*/ +bool idPlayerIcon::CreateIcon( idPlayer *player, playerIconType_t type, const char *mtr, const idVec3 &origin, const idMat3 &axis ) { + assert( type != ICON_NONE ); + + if ( type == iconType ) { + return false; + } + + FreeIcon(); + + memset( &renderEnt, 0, sizeof( renderEnt ) ); + renderEnt.origin = origin; + renderEnt.axis = axis; + renderEnt.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 16.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 16.0f; + renderEnt.hModel = renderModelManager->FindModel( "_sprite" ); + renderEnt.callback = NULL; + renderEnt.numJoints = 0; + renderEnt.joints = NULL; + renderEnt.customSkin = 0; + renderEnt.noShadow = true; + renderEnt.noSelfShadow = true; + renderEnt.customShader = declManager->FindMaterial( mtr ); + renderEnt.referenceShader = 0; + renderEnt.bounds = renderEnt.hModel->Bounds( &renderEnt ); + + iconHandle = gameRenderWorld->AddEntityDef( &renderEnt ); + iconType = type; + + return true; +} + +/* +=============== +idPlayerIcon::UpdateIcon +=============== +*/ +void idPlayerIcon::UpdateIcon( idPlayer *player, const idVec3 &origin, const idMat3 &axis ) { + assert( iconHandle >= 0 ); + + renderEnt.origin = origin; + renderEnt.axis = axis; + gameRenderWorld->UpdateEntityDef( iconHandle, &renderEnt ); +} + diff --git a/neo/d3xp/PlayerIcon.h b/neo/d3xp/PlayerIcon.h new file mode 100644 index 00000000..1567d80b --- /dev/null +++ b/neo/d3xp/PlayerIcon.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PLAYERICON_H__ +#define __PLAYERICON_H__ + +typedef enum { + ICON_LAG, + ICON_CHAT, + ICON_TEAM_RED, + ICON_TEAM_BLUE, + ICON_NONE +} playerIconType_t; + +class idPlayerIcon { +public: + +public: + idPlayerIcon(); + ~idPlayerIcon(); + + void Draw( idPlayer *player, jointHandle_t joint ); + void Draw( idPlayer *player, const idVec3 &origin ); + +public: + playerIconType_t iconType; + renderEntity_t renderEnt; + qhandle_t iconHandle; + +public: + void FreeIcon(); + bool CreateIcon( idPlayer* player, playerIconType_t type, const char *mtr, const idVec3 &origin, const idMat3 &axis ); + bool CreateIcon( idPlayer* player, playerIconType_t type, const idVec3 &origin, const idMat3 &axis ); + void UpdateIcon( idPlayer* player, const idVec3 &origin, const idMat3 &axis ); + +}; + + +#endif /* !_PLAYERICON_H_ */ + diff --git a/neo/d3xp/PlayerView.cpp b/neo/d3xp/PlayerView.cpp new file mode 100644 index 00000000..bb5d770d --- /dev/null +++ b/neo/d3xp/PlayerView.cpp @@ -0,0 +1,1749 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// _D3XP : rename all gameLocal.time to gameLocal.slow.time for merge! + +const int IMPULSE_DELAY = 150; +/* +============== +idPlayerView::idPlayerView +============== +*/ +idPlayerView::idPlayerView() { + memset( screenBlobs, 0, sizeof( screenBlobs ) ); + memset( &view, 0, sizeof( view ) ); + player = NULL; + tunnelMaterial = declManager->FindMaterial( "textures/decals/tunnel" ); + armorMaterial = declManager->FindMaterial( "armorViewEffect" ); + berserkMaterial = declManager->FindMaterial( "textures/decals/berserk" ); + irGogglesMaterial = declManager->FindMaterial( "textures/decals/irblend" ); + bloodSprayMaterial = declManager->FindMaterial( "textures/decals/bloodspray" ); + bfgMaterial = declManager->FindMaterial( "textures/decals/bfgvision" ); + bfgVision = false; + dvFinishTime = 0; + kickFinishTime = 0; + kickAngles.Zero(); + lastDamageTime = 0.0f; + fadeTime = 0; + fadeRate = 0.0; + fadeFromColor.Zero(); + fadeToColor.Zero(); + fadeColor.Zero(); + shakeAng.Zero(); + fxManager = NULL; + + if ( fxManager == NULL ) { + fxManager = new (TAG_ENTITY) FullscreenFXManager; + fxManager->Initialize( this ); + } + + ClearEffects(); +} + +/* +============== +idPlayerView::~idPlayerView +============== +*/ +idPlayerView::~idPlayerView() { + delete fxManager; +} +/* +============== +idPlayerView::Save +============== +*/ +void idPlayerView::Save( idSaveGame *savefile ) const { + int i; + const screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->WriteMaterial( blob->material ); + savefile->WriteFloat( blob->x ); + savefile->WriteFloat( blob->y ); + savefile->WriteFloat( blob->w ); + savefile->WriteFloat( blob->h ); + savefile->WriteFloat( blob->s1 ); + savefile->WriteFloat( blob->t1 ); + savefile->WriteFloat( blob->s2 ); + savefile->WriteFloat( blob->t2 ); + savefile->WriteInt( blob->finishTime ); + savefile->WriteInt( blob->startFadeTime ); + savefile->WriteFloat( blob->driftAmount ); + } + + savefile->WriteInt( dvFinishTime ); + savefile->WriteInt( kickFinishTime ); + savefile->WriteAngles( kickAngles ); + savefile->WriteBool( bfgVision ); + + savefile->WriteMaterial( tunnelMaterial ); + savefile->WriteMaterial( armorMaterial ); + savefile->WriteMaterial( berserkMaterial ); + savefile->WriteMaterial( irGogglesMaterial ); + savefile->WriteMaterial( bloodSprayMaterial ); + savefile->WriteMaterial( bfgMaterial ); + savefile->WriteFloat( lastDamageTime ); + + savefile->WriteVec4( fadeColor ); + savefile->WriteVec4( fadeToColor ); + savefile->WriteVec4( fadeFromColor ); + savefile->WriteFloat( fadeRate ); + savefile->WriteInt( fadeTime ); + + savefile->WriteAngles( shakeAng ); + + savefile->WriteObject( player ); + savefile->WriteRenderView( view ); + + if ( fxManager ) { + fxManager->Save( savefile ); + } +} + +/* +============== +idPlayerView::Restore +============== +*/ +void idPlayerView::Restore( idRestoreGame *savefile ) { + int i; + screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->ReadMaterial( blob->material ); + savefile->ReadFloat( blob->x ); + savefile->ReadFloat( blob->y ); + savefile->ReadFloat( blob->w ); + savefile->ReadFloat( blob->h ); + savefile->ReadFloat( blob->s1 ); + savefile->ReadFloat( blob->t1 ); + savefile->ReadFloat( blob->s2 ); + savefile->ReadFloat( blob->t2 ); + savefile->ReadInt( blob->finishTime ); + savefile->ReadInt( blob->startFadeTime ); + savefile->ReadFloat( blob->driftAmount ); + } + + savefile->ReadInt( dvFinishTime ); + savefile->ReadInt( kickFinishTime ); + savefile->ReadAngles( kickAngles ); + savefile->ReadBool( bfgVision ); + + savefile->ReadMaterial( tunnelMaterial ); + savefile->ReadMaterial( armorMaterial ); + savefile->ReadMaterial( berserkMaterial ); + savefile->ReadMaterial( irGogglesMaterial ); + savefile->ReadMaterial( bloodSprayMaterial ); + savefile->ReadMaterial( bfgMaterial ); + savefile->ReadFloat( lastDamageTime ); + + savefile->ReadVec4( fadeColor ); + savefile->ReadVec4( fadeToColor ); + savefile->ReadVec4( fadeFromColor ); + savefile->ReadFloat( fadeRate ); + savefile->ReadInt( fadeTime ); + + savefile->ReadAngles( shakeAng ); + + savefile->ReadObject( reinterpret_cast( player ) ); + savefile->ReadRenderView( view ); + + if ( fxManager ) { + fxManager->Restore( savefile ); + } +} + +/* +============== +idPlayerView::SetPlayerEntity +============== +*/ +void idPlayerView::SetPlayerEntity( idPlayer *playerEnt ) { + player = playerEnt; +} + +/* +============== +idPlayerView::ClearEffects +============== +*/ +void idPlayerView::ClearEffects() { + lastDamageTime = MS2SEC( gameLocal.slow.time - 99999 ); + + dvFinishTime = ( gameLocal.fast.time - 99999 ); + kickFinishTime = ( gameLocal.slow.time - 99999 ); + + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlobs[i].finishTime = gameLocal.fast.time; + } + + fadeTime = 0; + bfgVision = false; +} + +/* +============== +idPlayerView::GetScreenBlob +============== +*/ +screenBlob_t *idPlayerView::GetScreenBlob() { + screenBlob_t * oldest = &screenBlobs[0]; + + for ( int i = 1 ; i < MAX_SCREEN_BLOBS ; i++ ) { + if ( screenBlobs[i].finishTime < oldest->finishTime ) { + oldest = &screenBlobs[i]; + } + } + return oldest; +} + +/* +============== +idPlayerView::DamageImpulse + +LocalKickDir is the direction of force in the player's coordinate system, +which will determine the head kick direction +============== +*/ +void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef ) { + // + // double vision effect + // + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.slow.time ) { + // keep shotgun from obliterating the view + return; + } + + float dvTime = damageDef->GetFloat( "dv_time" ); + if ( dvTime ) { + if ( dvFinishTime < gameLocal.fast.time ) { + dvFinishTime = gameLocal.fast.time; + } + dvFinishTime += g_dvTime.GetFloat() * dvTime; + // don't let it add up too much in god mode + if ( dvFinishTime > gameLocal.fast.time + 5000 ) { + dvFinishTime = gameLocal.fast.time + 5000; + } + } + + // + // head angle kick + // + float kickTime = damageDef->GetFloat( "kick_time" ); + if ( kickTime ) { + kickFinishTime = gameLocal.slow.time + g_kickTime.GetFloat() * kickTime; + + // forward / back kick will pitch view + kickAngles[0] = localKickDir[0]; + + // side kick will yaw view + kickAngles[1] = localKickDir[1]*0.5f; + + // up / down kick will pitch view + kickAngles[0] += localKickDir[2]; + + // roll will come from side + kickAngles[2] = localKickDir[1]; + + float kickAmplitude = damageDef->GetFloat( "kick_amplitude" ); + if ( kickAmplitude ) { + kickAngles *= kickAmplitude; + } + } + + // + // screen blob + // + float blobTime = damageDef->GetFloat( "blob_time" ); + if ( blobTime ) { + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.fast.time; + blob->finishTime = gameLocal.fast.time + blobTime * g_blobTime.GetFloat(); + + const char *materialName = damageDef->GetString( "mtr_blob" ); + blob->material = declManager->FindMaterial( materialName ); + blob->x = damageDef->GetFloat( "blob_x" ); + blob->x += ( gameLocal.random.RandomInt()&63 ) - 32; + blob->y = damageDef->GetFloat( "blob_y" ); + blob->y += ( gameLocal.random.RandomInt()&63 ) - 32; + + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = damageDef->GetFloat( "blob_width" ) * g_blobSize.GetFloat() * scale; + blob->h = damageDef->GetFloat( "blob_height" ) * g_blobSize.GetFloat() * scale; + blob->s1 = 0.0f; + blob->t1 = 0.0f; + blob->s2 = 1.0f; + blob->t2 = 1.0f; + } + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.fast.time ); + +} + +/* +================== +idPlayerView::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + int recoilTime = weaponDef->GetInt( "recoilTime" ); + // don't shorten a damage kick in progress + if ( recoilTime && kickFinishTime < gameLocal.slow.time ) { + idAngles angles; + weaponDef->GetAngles( "recoilAngles", "5 0 0", angles ); + kickAngles = angles; + int finish = gameLocal.slow.time + g_kickTime.GetFloat() * recoilTime; + kickFinishTime = finish; + } + +} + +/* +=================== +idPlayerView::CalculateShake +=================== +*/ +void idPlayerView::CalculateShake() { + float shakeVolume = gameSoundWorld->CurrentShakeAmplitude(); + // + // shakeVolume should somehow be molded into an angle here + // it should be thought of as being in the range 0.0 -> 1.0, although + // since CurrentShakeAmplitudeForPosition() returns all the shake sounds + // the player can hear, it can go over 1.0 too. + // + shakeAng[0] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAng[1] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAng[2] = gameLocal.random.CRandomFloat() * shakeVolume; +} + +/* +=================== +idPlayerView::ShakeAxis +=================== +*/ +idMat3 idPlayerView::ShakeAxis() const { + return shakeAng.ToMat3(); +} + +/* +=================== +idPlayerView::AngleOffset + + kickVector, a world space direction that the attack should +=================== +*/ +idAngles idPlayerView::AngleOffset() const { + idAngles ang( 0.0f, 0.0f, 0.0f ); + + if ( gameLocal.slow.time < kickFinishTime ) { + float offset = kickFinishTime - gameLocal.slow.time; + + ang = kickAngles * offset * offset * g_kickAmplitude.GetFloat(); + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( ang[i] > 70.0f ) { + ang[i] = 70.0f; + } else if ( ang[i] < -70.0f ) { + ang[i] = -70.0f; + } + } + } + return ang; +} + +/* +================== +idPlayerView::SingleView +================== +*/ +void idPlayerView::SingleView( const renderView_t *view, idMenuHandler_HUD * hudManager ) { + + // normal rendering + if ( !view ) { + return; + } + + // place the sound origin for the player + gameSoundWorld->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1 ); + + // if the objective system is up, don't do normal drawing + if ( player->objectiveSystemOpen ) { + if ( player->pdaMenu != NULL ) { + player->pdaMenu->Update(); + } + return; + } + + // hack the shake in at the very last moment, so it can't cause any consistency problems + renderView_t hackedView = *view; + hackedView.viewaxis = hackedView.viewaxis * ShakeAxis(); + + if ( gameLocal.portalSkyEnt.GetEntity() && gameLocal.IsPortalSkyAcive() && g_enablePortalSky.GetBool() ) { + renderView_t portalView = hackedView; + portalView.vieworg = gameLocal.portalSkyEnt.GetEntity()->GetPhysics()->GetOrigin(); + gameRenderWorld->RenderScene( &portalView ); + renderSystem->CaptureRenderToImage( "_currentRender" ); + + hackedView.forceUpdate = true; // FIX: for smoke particles not drawing when portalSky present + } + + // process the frame + fxManager->Process( &hackedView ); + + if ( !hudManager ) { + return; + } + + // draw screen blobs + if ( !pm_thirdPerson.GetBool() && !g_skipViewEffects.GetBool() ) { + if ( !player->spectating ) { + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlob_t *blob = &screenBlobs[i]; + if ( blob->finishTime <= gameLocal.fast.time ) { + continue; + } + + blob->y += blob->driftAmount; + + float fade = (float)( blob->finishTime - gameLocal.fast.time ) / ( blob->finishTime - blob->startFadeTime ); + if ( fade > 1.0f ) { + fade = 1.0f; + } + if ( fade ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, fade ); + renderSystem->DrawStretchPic( blob->x, blob->y, blob->w, blob->h,blob->s1, blob->t1, blob->s2, blob->t2, blob->material ); + } + } + } + player->DrawHUD( hudManager ); + + if ( player->spectating ) { + return; + } + + // armor impulse feedback + float armorPulse = ( gameLocal.fast.time - player->lastArmorPulse ) / 250.0f; + + if ( armorPulse > 0.0f && armorPulse < 1.0f ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f - armorPulse ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, armorMaterial ); + } + + + // tunnel vision + float health = 0.0f; + if ( g_testHealthVision.GetFloat() != 0.0f ) { + health = g_testHealthVision.GetFloat(); + } else { + health = player->health; + } + float alpha = health / 100.0f; + if ( alpha < 0.0f ) { + alpha = 0.0f; + } + if ( alpha > 1.0f ) { + alpha = 1.0f; + } + + if ( alpha < 1.0f ) { + renderSystem->SetColor4( ( player->health <= 0.0f ) ? MS2SEC( gameLocal.slow.time ) : lastDamageTime, 1.0f, 1.0f, ( player->health <= 0.0f ) ? 0.0f : alpha ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } + + if ( bfgVision ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, bfgMaterial ); + } + + } + + // test a single material drawn over everything + if ( g_testPostProcess.GetString()[0] ) { + const idMaterial *mtr = declManager->FindMaterial( g_testPostProcess.GetString(), false ); + if ( !mtr ) { + common->Printf( "Material not found.\n" ); + g_testPostProcess.SetString( "" ); + } else { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, mtr ); + } + } +} + + +/* +================= +idPlayerView::Flash + +flashes the player view with the given color +================= +*/ +void idPlayerView::Flash(idVec4 color, int time ) { + Fade( idVec4( 0.0f, 0.0f, 0.0f, 0.0f ), time); + fadeFromColor = colorWhite; +} + +/* +================= +idPlayerView::Fade + +used for level transition fades +assumes: color.w is 0 or 1 +================= +*/ +void idPlayerView::Fade( idVec4 color, int time ) { + SetTimeState ts( player->timeGroup ); + + if ( !fadeTime ) { + fadeFromColor.Set( 0.0f, 0.0f, 0.0f, 1.0f - color[ 3 ] ); + } else { + fadeFromColor = fadeColor; + } + fadeToColor = color; + + if ( time <= 0 ) { + fadeRate = 0; + time = 0; + fadeColor = fadeToColor; + } else { + fadeRate = 1.0f / ( float )time; + } + + if ( gameLocal.realClientTime == 0 && time == 0 ) { + fadeTime = 1; + } else { + fadeTime = gameLocal.realClientTime + time; + } +} + +/* +================= +idPlayerView::ScreenFade +================= +*/ +void idPlayerView::ScreenFade() { + if ( !fadeTime ) { + return; + } + + SetTimeState ts( player->timeGroup ); + + int msec = fadeTime - gameLocal.realClientTime; + + if ( msec <= 0 ) { + fadeColor = fadeToColor; + if ( fadeColor[ 3 ] == 0.0f ) { + fadeTime = 0; + } + } else { + float t = ( float )msec * fadeRate; + fadeColor = fadeFromColor * t + fadeToColor * ( 1.0f - t ); + } + + if ( fadeColor[ 3 ] != 0.0f ) { + renderSystem->SetColor4( fadeColor[ 0 ], fadeColor[ 1 ], fadeColor[ 2 ], fadeColor[ 3 ] ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, declManager->FindMaterial( "_white" ) ); + } +} + +idCVar stereoRender_interOccularCentimeters( "stereoRender_interOccularCentimeters", "3.0", CVAR_ARCHIVE | CVAR_RENDERER, "Distance between eyes" ); +idCVar stereoRender_convergence( "stereoRender_convergence", "6", CVAR_RENDERER, "0 = head mounted display, otherwise world units to convergence plane" ); + +extern idCVar stereoRender_screenSeparation; // screen units from center to eyes +extern idCVar stereoRender_swapEyes; + +// In a head mounted display with separate displays for each eye, +// screen separation will be zero and world separation will be the eye distance. +struct stereoDistances_t { + // Offset to projection matrix, positive one eye, negative the other. + // Total distance is twice this, so 0.05 would give a 10% of screen width + // separation for objects at infinity. + float screenSeparation; + + // Game world units from one eye to the centerline. + // Total distance is twice this. + float worldSeparation; +}; + +float CentimetersToInches( const float cm ) { + return cm / 2.54f; +} + +float CentimetersToWorldUnits( const float cm ) { + // In Doom 3, one world unit == one inch + return CentimetersToInches( cm ); +} + +float CalculateWorldSeparation( + const float screenSeparation, + const float convergenceDistance, + const float fov_x_degrees ) { + + const float fovRadians = DEG2RAD( fov_x_degrees ); + const float screen = tan( fovRadians * 0.5f ) * fabs( screenSeparation ); + const float worldSeparation = screen * convergenceDistance / 0.5f; + + return worldSeparation; +} + +stereoDistances_t CaclulateStereoDistances( + const float interOcularCentimeters, // distance between two eyes, typically 6.0 - 7.0 + const float screenWidthCentimeters, // read from operating system + const float convergenceWorldUnits, // pass 0 for head mounted display mode + const float fov_x_degrees ) { // edge to edge horizontal field of view, typically 60 - 90 + + stereoDistances_t dists = {}; + + if ( convergenceWorldUnits == 0.0f ) { + // head mounted display mode + dists.worldSeparation = CentimetersToInches( interOcularCentimeters * 0.5 ); + dists.screenSeparation = 0.0f; + return dists; + } + + // 3DTV mode + dists.screenSeparation = 0.5f * interOcularCentimeters / screenWidthCentimeters; + dists.worldSeparation = CalculateWorldSeparation( dists.screenSeparation, convergenceWorldUnits, fov_x_degrees ); + + return dists; +} + +float GetScreenSeparationForGuis() { + const stereoDistances_t dists = CaclulateStereoDistances( + stereoRender_interOccularCentimeters.GetFloat(), + renderSystem->GetPhysicalScreenWidthInCentimeters(), + stereoRender_convergence.GetFloat(), + 80.0f /* fov */ ); + + return dists.screenSeparation; +} + +/* +=================== +idPlayerView::EmitStereoEyeView +=================== +*/ +void idPlayerView::EmitStereoEyeView( const int eye, idMenuHandler_HUD * hudManager ) { + renderView_t * view = player->GetRenderView(); + if ( view == NULL ) { + return; + } + + renderView_t eyeView = *view; + + const stereoDistances_t dists = CaclulateStereoDistances( + stereoRender_interOccularCentimeters.GetFloat(), + renderSystem->GetPhysicalScreenWidthInCentimeters(), + stereoRender_convergence.GetFloat(), + view->fov_x ); + + eyeView.vieworg += eye * dists.worldSeparation * eyeView.viewaxis[1]; + + eyeView.viewEyeBuffer = stereoRender_swapEyes.GetBool() ? eye : -eye; + eyeView.stereoScreenSeparation = eye * dists.screenSeparation; + + SingleView( &eyeView, hudManager ); +} + +/* +=================== +IsGameStereoRendered + +The crosshair is swapped for a laser sight in stereo rendering +=================== +*/ +bool IsGameStereoRendered() { + if ( renderSystem->GetStereo3DMode() != STEREO3D_OFF ) { + return true; + } + return false; +} + +int EyeForHalfRateFrame( const int frameCount ) { + return ( renderSystem->GetFrameCount() & 1 ) ? -1 : 1; +} + +/* +=================== +idPlayerView::RenderPlayerView +=================== +*/ +void idPlayerView::RenderPlayerView( idMenuHandler_HUD * hudManager ) { + const renderView_t *view = player->GetRenderView(); + if ( renderSystem->GetStereo3DMode() != STEREO3D_OFF ) { + // render both eye views each frame on the PC + for ( int eye = 1 ; eye >= -1 ; eye -= 2 ) { + EmitStereoEyeView( eye, hudManager ); + } + } else + { + SingleView( view, hudManager ); + } + ScreenFade(); +} + +/* +=================== +idPlayerView::WarpVision +=================== +*/ +int idPlayerView::AddWarp( idVec3 worldOrigin, float centerx, float centery, float initialRadius, float durationMsec ) { + FullscreenFX_Warp *fx = (FullscreenFX_Warp*)( fxManager->FindFX( "warp" ) ); + + if ( fx ) { + fx->EnableGrabber( true ); + return 1; + } + + return 1; +} + +void idPlayerView::FreeWarp( int id ) { + FullscreenFX_Warp *fx = (FullscreenFX_Warp*)( fxManager->FindFX( "warp" ) ); + + if ( fx ) { + fx->EnableGrabber( false ); + return; + } +} + + + + + +/* +================== +FxFader::FxFader +================== +*/ +FxFader::FxFader() { + time = 0; + state = FX_STATE_OFF; + alpha = 0; + msec = 1000; +} + +/* +================== +FxFader::SetTriggerState +================== +*/ +bool FxFader::SetTriggerState( bool active ) { + + // handle on/off states + if ( active && state == FX_STATE_OFF ) { + state = FX_STATE_RAMPUP; + time = gameLocal.slow.time + msec; + } + else if ( !active && state == FX_STATE_ON ) { + state = FX_STATE_RAMPDOWN; + time = gameLocal.slow.time + msec; + } + + // handle rampup/rampdown states + if ( state == FX_STATE_RAMPUP ) { + if ( gameLocal.slow.time >= time ) { + state = FX_STATE_ON; + } + } + else if ( state == FX_STATE_RAMPDOWN ) { + if ( gameLocal.slow.time >= time ) { + state = FX_STATE_OFF; + } + } + + // compute alpha + switch ( state ) { + case FX_STATE_ON: alpha = 1; break; + case FX_STATE_OFF: alpha = 0; break; + case FX_STATE_RAMPUP: alpha = 1 - (float)( time - gameLocal.slow.time ) / msec; break; + case FX_STATE_RAMPDOWN: alpha = (float)( time - gameLocal.slow.time ) / msec; break; + } + + if ( alpha > 0 ) { + return true; + } + else { + return false; + } +} + +/* +================== +FxFader::Save +================== +*/ +void FxFader::Save( idSaveGame *savefile ) { + savefile->WriteInt( time ); + savefile->WriteInt( state ); + savefile->WriteFloat( alpha ); + savefile->WriteInt( msec ); +} + +/* +================== +FxFader::Restore +================== +*/ +void FxFader::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( time ); + savefile->ReadInt( state ); + savefile->ReadFloat( alpha ); + savefile->ReadInt( msec ); +} + + + + + +/* +================== +FullscreenFX_Helltime::Save +================== +*/ +void FullscreenFX::Save( idSaveGame *savefile ) { + fader.Save( savefile ); +} + +/* +================== +FullscreenFX_Helltime::Restore +================== +*/ +void FullscreenFX::Restore( idRestoreGame *savefile ) { + fader.Restore( savefile ); +} + + +/* +================== +FullscreenFX_Helltime::Initialize +================== +*/ +void FullscreenFX_Helltime::Initialize() { + initMaterial = declManager->FindMaterial( "textures/d3bfg/bloodorb/init" ); + drawMaterial = declManager->FindMaterial( "textures/d3bfg/bloodorb/draw" ); + + captureMaterials[0] = declManager->FindMaterial( "textures/d3bfg/bloodorb1/capture" ); + captureMaterials[1] = declManager->FindMaterial( "textures/d3bfg/bloodorb2/capture" ); + captureMaterials[2] = declManager->FindMaterial( "textures/d3bfg/bloodorb3/capture" ); + + clearAccumBuffer = true; +} + +/* +================== +FullscreenFX_Helltime::DetermineLevel +================== +*/ +int FullscreenFX_Helltime::DetermineLevel() { + int testfx = g_testHelltimeFX.GetInteger(); + + // for testing purposes + if ( testfx >= 0 && testfx < 3 ) { + return testfx; + } + + idPlayer * player = fxman->GetPlayer(); + + if ( player != NULL && player->PowerUpActive( INVULNERABILITY ) ) { + return 2; + } + else if ( player != NULL && player->PowerUpActive( BERSERK ) ) { + return 1; + } + else if ( player != NULL && player->PowerUpActive( HELLTIME ) ) { + return 0; + } + + return -1; +} + +/* +================== +FullscreenFX_Helltime::Active +================== +*/ +bool FullscreenFX_Helltime::Active() { + + if ( gameLocal.inCinematic || common->IsMultiplayer() ) { + return false; + } + + if ( DetermineLevel() >= 0 ) { + return true; + } + else { + // latch the clear flag + if ( fader.GetAlpha() == 0 ) { + clearAccumBuffer = true; + } + } + + return false; +} + +/* +================== +FullscreenFX_Helltime::AccumPass +================== +*/ +void FullscreenFX_Helltime::AccumPass( const renderView_t *view ) { + + int level = DetermineLevel(); + + // for testing + if ( level < 0 || level > 2 ) { + level = 0; + } + + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + + float t0 = 1.0f; + float t1 = 0.0f; + + // capture pass + if ( clearAccumBuffer ) { + clearAccumBuffer = false; + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, t0, 1.0f, t1, initMaterial ); + } else { + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, t0, 1.0f, t1, captureMaterials[level] ); + } +} + +/* +================== +FullscreenFX_Helltime::HighQuality +================== +*/ +void FullscreenFX_Helltime::HighQuality() { + float t0 = 1.0f; + float t1 = 0.0f; + + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, t0, 1.0f, t1, drawMaterial ); +} + +/* +================== +FullscreenFX_Helltime::Restore +================== +*/ +void FullscreenFX_Helltime::Restore( idRestoreGame *savefile ) { + FullscreenFX::Restore( savefile ); + + // latch the clear flag + clearAccumBuffer = true; +} + +/* +================== +FullscreenFX_Multiplayer::Initialize +================== +*/ +void FullscreenFX_Multiplayer::Initialize() { + initMaterial = declManager->FindMaterial( "textures/d3bfg/multiplayer/init" ); + captureMaterial = declManager->FindMaterial( "textures/d3bfg/multiplayer/capture" ); + drawMaterial = declManager->FindMaterial( "textures/d3bfg/bloodorb/draw" ); + clearAccumBuffer = true; +} + +/* +================== +FullscreenFX_Multiplayer::DetermineLevel +================== +*/ +int FullscreenFX_Multiplayer::DetermineLevel() { + int testfx = g_testMultiplayerFX.GetInteger(); + + // for testing purposes + if ( testfx >= 0 && testfx < 3 ) { + return testfx; + } + + idPlayer * player = fxman->GetPlayer(); + + if ( player != NULL && player->PowerUpActive( INVULNERABILITY ) ) { + return 2; + } + //else if ( player->PowerUpActive( HASTE ) ) { + // return 1; + //} + else if ( player != NULL && player->PowerUpActive( BERSERK ) ) { + return 0; + } + + return -1; +} + +/* +================== +FullscreenFX_Multiplayer::Active +================== +*/ +bool FullscreenFX_Multiplayer::Active() { + + if ( !common->IsMultiplayer() && g_testMultiplayerFX.GetInteger() == -1 ) { + return false; + } + + if ( DetermineLevel() >= 0 ) { + return true; + } else { + // latch the clear flag + if ( fader.GetAlpha() == 0 ) { + clearAccumBuffer = true; + } + } + + return false; +} + +/* +================== +FullscreenFX_Multiplayer::AccumPass +================== +*/ +void FullscreenFX_Multiplayer::AccumPass( const renderView_t *view ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + + float t0 = 1.0f; + float t1 = 0.0f; + + // capture pass + if ( clearAccumBuffer ) { + clearAccumBuffer = false; + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, t0, 1.0f, t1, initMaterial ); + } else { + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, t0, 1.0f, t1, captureMaterial ); + } +} + +/* +================== +FullscreenFX_Multiplayer::HighQuality +================== +*/ +void FullscreenFX_Multiplayer::HighQuality() { + float t0 = 1.0f; + float t1 = 0.0f; + + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, t0, 1.0f, t1, drawMaterial ); +} + +/* +================== +FullscreenFX_Multiplayer::Restore +================== +*/ +void FullscreenFX_Multiplayer::Restore( idRestoreGame *savefile ) { + FullscreenFX::Restore( savefile ); + + // latch the clear flag + clearAccumBuffer = true; +} + + + + + +/* +================== +FullscreenFX_Warp::Initialize +================== +*/ +void FullscreenFX_Warp::Initialize() { + material = declManager->FindMaterial( "textures/d3bfg/warp" ); + grabberEnabled = false; + startWarpTime = 0; +} + +/* +================== +FullscreenFX_Warp::Active +================== +*/ +bool FullscreenFX_Warp::Active() { + if ( grabberEnabled ) { + return true; + } + + return false; +} + +/* +================== +FullscreenFX_Warp::Save +================== +*/ +void FullscreenFX_Warp::Save( idSaveGame *savefile ) { + FullscreenFX::Save( savefile ); + + savefile->WriteBool( grabberEnabled ); + savefile->WriteInt( startWarpTime ); +} + +/* +================== +FullscreenFX_Warp::Restore +================== +*/ +void FullscreenFX_Warp::Restore( idRestoreGame *savefile ) { + FullscreenFX::Restore( savefile ); + + savefile->ReadBool( grabberEnabled ); + savefile->ReadInt( startWarpTime ); +} + +/* +================== +FullscreenFX_Warp::DrawWarp +================== +*/ +void FullscreenFX_Warp::DrawWarp( WarpPolygon_t wp, float interp ) { + idVec4 mid1_uv, mid2_uv; + idVec4 mid1, mid2; + idVec2 drawPts[6]; + WarpPolygon_t trans; + + trans = wp; + + // compute mid points + mid1 = trans.outer1 * ( interp ) + trans.center * ( 1 - interp ); + mid2 = trans.outer2 * ( interp ) + trans.center * ( 1 - interp ); + mid1_uv = trans.outer1 * ( 0.5 ) + trans.center * ( 1 - 0.5 ); + mid2_uv = trans.outer2 * ( 0.5 ) + trans.center * ( 1 - 0.5 ); + + // draw [outer1, mid2, mid1] + drawPts[0].Set( trans.outer1.x, trans.outer1.y ); + drawPts[1].Set( mid2.x, mid2.y ); + drawPts[2].Set( mid1.x, mid1.y ); + drawPts[3].Set( trans.outer1.z, trans.outer1.w ); + drawPts[4].Set( mid2_uv.z, mid2_uv.w ); + drawPts[5].Set( mid1_uv.z, mid1_uv.w ); + renderSystem->DrawStretchTri( drawPts[0], drawPts[1], drawPts[2], drawPts[3], drawPts[4], drawPts[5], material ); + + // draw [outer1, outer2, mid2] + drawPts[0].Set( trans.outer1.x, trans.outer1.y ); + drawPts[1].Set( trans.outer2.x, trans.outer2.y ); + drawPts[2].Set( mid2.x, mid2.y ); + drawPts[3].Set( trans.outer1.z, trans.outer1.w ); + drawPts[4].Set( trans.outer2.z, trans.outer2.w ); + drawPts[5].Set( mid2_uv.z, mid2_uv.w ); + renderSystem->DrawStretchTri( drawPts[0], drawPts[1], drawPts[2], drawPts[3], drawPts[4], drawPts[5], material ); + + // draw [mid1, mid2, center] + drawPts[0].Set( mid1.x, mid1.y ); + drawPts[1].Set( mid2.x, mid2.y ); + drawPts[2].Set( trans.center.x, trans.center.y ); + drawPts[3].Set( mid1_uv.z, mid1_uv.w ); + drawPts[4].Set( mid2_uv.z, mid2_uv.w ); + drawPts[5].Set( trans.center.z, trans.center.w ); + renderSystem->DrawStretchTri( drawPts[0], drawPts[1], drawPts[2], drawPts[3], drawPts[4], drawPts[5], material ); +} + +/* +================== +FullscreenFX_Warp::HighQuality +================== +*/ +void FullscreenFX_Warp::HighQuality() { + float x1, y1, x2, y2, radius, interp; + idVec2 center; + int STEP = 9; + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + + interp = ( idMath::Sin( (float)( gameLocal.slow.time - startWarpTime ) / 1000 ) + 1 ) / 2.f; + interp = 0.7 * ( 1 - interp ) + 0.3 * ( interp ); + + // draw the warps + center.x = 320; + center.y = 240; + radius = 200; + + for ( float i = 0; i < 360; i += STEP ) { + // compute the values + x1 = idMath::Sin( DEG2RAD( i ) ); + y1 = idMath::Cos( DEG2RAD( i ) ); + + x2 = idMath::Sin( DEG2RAD( i + STEP ) ); + y2 = idMath::Cos( DEG2RAD( i + STEP ) ); + + // add warp polygon + WarpPolygon_t p; + + p.outer1.x = center.x + x1 * radius; + p.outer1.y = center.y + y1 * radius; + p.outer1.z = p.outer1.x / (float)SCREEN_WIDTH; + p.outer1.w = 1 - ( p.outer1.y / (float)SCREEN_HEIGHT ); + + p.outer2.x = center.x + x2 * radius; + p.outer2.y = center.y + y2 * radius; + p.outer2.z = p.outer2.x / (float)SCREEN_WIDTH; + p.outer2.w = 1 - ( p.outer2.y / (float)SCREEN_HEIGHT ); + + p.center.x = center.x; + p.center.y = center.y; + p.center.z = p.center.x / (float)SCREEN_WIDTH; + p.center.w = 1 - ( p.center.y / (float)SCREEN_HEIGHT ); + + // draw it + DrawWarp( p, interp ); + } +} + + + + + +/* +================== +FullscreenFX_EnviroSuit::Initialize +================== +*/ +void FullscreenFX_EnviroSuit::Initialize() { + material = declManager->FindMaterial( "textures/d3bfg/enviro_suit" ); +} + +/* +================== +FullscreenFX_EnviroSuit::Active +================== +*/ +bool FullscreenFX_EnviroSuit::Active() { + idPlayer * player = fxman->GetPlayer(); + + if ( player != NULL && player->PowerUpActive( ENVIROSUIT ) ) { + return true; + } + + return false; +} + +/* +================== +FullscreenFX_EnviroSuit::HighQuality +================== +*/ +void FullscreenFX_EnviroSuit::HighQuality() { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + float s0 = 0.0f; + float t0 = 1.0f; + float s1 = 1.0f; + float t1 = 0.0f; + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, s0, t0, s1, t1, material ); +} + +/* +================== +FullscreenFX_DoubleVision::Initialize +================== +*/ +void FullscreenFX_DoubleVision::Initialize() { + material = declManager->FindMaterial( "textures/d3bfg/doubleVision" ); +} + +/* +================== +FullscreenFX_DoubleVision::Active +================== +*/ +bool FullscreenFX_DoubleVision::Active() { + + if ( gameLocal.fast.time < fxman->GetPlayerView()->dvFinishTime ) { + return true; + } + + return false; +} + +/* +================== +FullscreenFX_DoubleVision::HighQuality +================== +*/ +void FullscreenFX_DoubleVision::HighQuality() { + int offset = fxman->GetPlayerView()->dvFinishTime - gameLocal.fast.time; + float scale = offset * g_dvAmplitude.GetFloat(); + + // for testing purposes + if ( !Active() ) { + static int test = 0; + if ( test > 312 ) { + test = 0; + } + + offset = test++; + scale = offset * g_dvAmplitude.GetFloat(); + } + + idPlayer * player = fxman->GetPlayer(); + + if( player == NULL ) { + return; + } + + offset *= 2; // crutch up for higher res + + // set the scale and shift + if ( scale > 0.5f ) { + scale = 0.5f; + } + float shift = scale * sin( sqrtf( (float)offset ) * g_dvFrequency.GetFloat() ); + shift = fabs( shift ); + + // carry red tint if in berserk mode + idVec4 color( 1.0f, 1.0f, 1.0f, 1.0f ); + if ( gameLocal.fast.time < player->inventory.powerupEndTime[ BERSERK ] ) { + color.y = 0.0f; + color.z = 0.0f; + } + + if ( !common->IsMultiplayer() && gameLocal.fast.time < player->inventory.powerupEndTime[ HELLTIME ] || gameLocal.fast.time < player->inventory.powerupEndTime[ INVULNERABILITY ]) { + color.y = 0.0f; + color.z = 0.0f; + } + + // uv coordinates + float s0 = shift; + float t0 = 1.0f; + float s1 = 1.0f; + float t1 = 0.0f; + + + renderSystem->SetColor4( color.x, color.y, color.z, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, s0, t0, s1, t1, material ); + + renderSystem->SetColor4( color.x, color.y, color.z, 0.5f ); + s0 = 0.0f; + t0 = 1.0f; + s1 = ( 1.0-shift ); + t1 = 0.0f; + + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, s0, t0, s1, t1, material ); +} + +/* +================== +FullscreenFX_InfluenceVision::Initialize +================== +*/ +void FullscreenFX_InfluenceVision::Initialize() { + +} + +/* +================== +FullscreenFX_InfluenceVision::Active +================== +*/ +bool FullscreenFX_InfluenceVision::Active() { + idPlayer * player = fxman->GetPlayer(); + + if ( player != NULL && ( player->GetInfluenceMaterial() || player->GetInfluenceEntity() ) ) { + return true; + } + + return false; +} + +/* +================== +FullscreenFX_InfluenceVision::HighQuality +================== +*/ +void FullscreenFX_InfluenceVision::HighQuality() { + float distance = 0.0f; + float pct = 1.0f; + idPlayer * player = fxman->GetPlayer(); + + if( player == NULL ) { + return; + } + + if ( player->GetInfluenceEntity() ) { + distance = ( player->GetInfluenceEntity()->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).Length(); + if ( player->GetInfluenceRadius() != 0.0f && distance < player->GetInfluenceRadius() ) { + pct = distance / player->GetInfluenceRadius(); + pct = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, pct ); + } + } + + if ( player->GetInfluenceMaterial() ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, pct ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, player->GetInfluenceMaterial() ); + } else if ( player->GetInfluenceEntity() == NULL ) { + return; + } else { +// int offset = 25 + sinf( gameLocal.slow.time ); +// DoubleVision( hud, view, pct * offset ); + } +} + + + + +/* +================== +FullscreenFX_Bloom::Initialize +================== +*/ +void FullscreenFX_Bloom::Initialize() { + drawMaterial = declManager->FindMaterial( "textures/d3bfg/bloom2/draw" ); + initMaterial = declManager->FindMaterial( "textures/d3bfg/bloom2/init" ); + + currentIntensity = 0; + targetIntensity = 0; +} + +/* +================== +FullscreenFX_Bloom::Active +================== +*/ +bool FullscreenFX_Bloom::Active() { + idPlayer * player = fxman->GetPlayer(); + + if ( player != NULL && player->bloomEnabled ) { + return true; + } + + return false; +} + +/* +================== +FullscreenFX_Bloom::HighQuality +================== +*/ +void FullscreenFX_Bloom::HighQuality() { + float shift = 1; + idPlayer * player = fxman->GetPlayer(); + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + + // if intensity value is different, start the blend + targetIntensity = g_testBloomIntensity.GetFloat(); + + if ( player != NULL && player->bloomEnabled ) { + targetIntensity = player->bloomIntensity; + } + + float delta = targetIntensity - currentIntensity; + float step = 0.001f; + + if ( step < fabs( delta ) ) { + if ( delta < 0 ) { + step = -step; + } + + currentIntensity += step; + } + + // draw the blends + int num = g_testBloomNumPasses.GetInteger(); + + for ( int i = 0; i < num; i++ ) { + float s1 = 0.0f, t1 = 0.0f, s2 = 1.0f, t2 = 1.0f; + float alpha; + + // do the center scale + s1 -= 0.5; + s1 *= shift; + s1 += 0.5; + + t1 -= 0.5; + t1 *= shift; + t1 += 0.5; + + s2 -= 0.5; + s2 *= shift; + s2 += 0.5; + + t2 -= 0.5; + t2 *= shift; + t2 += 0.5; + + // draw it + if ( num == 1 ) { + alpha = 1; + } else { + alpha = 1 - (float)i / ( num - 1 ); + } + + + float yScale = 1.0f; + + renderSystem->SetColor4( alpha, alpha, alpha, 1 ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, s1, t2 * yScale, s2, t1 * yScale, drawMaterial ); + + shift += currentIntensity; + } +} + +/* +================== +FullscreenFX_Bloom::Save +================== +*/ +void FullscreenFX_Bloom::Save( idSaveGame *savefile ) { + FullscreenFX::Save( savefile ); + savefile->WriteFloat( currentIntensity ); + savefile->WriteFloat( targetIntensity ); +} + +/* +================== +FullscreenFX_Bloom::Restore +================== +*/ +void FullscreenFX_Bloom::Restore( idRestoreGame *savefile ) { + FullscreenFX::Restore( savefile ); + savefile->ReadFloat( currentIntensity ); + savefile->ReadFloat( targetIntensity ); +} + + + + + + +/* +================== +FullscreenFXManager::FullscreenFXManager +================== +*/ +FullscreenFXManager::FullscreenFXManager() { + playerView = NULL; + blendBackMaterial = NULL; +} + +/* +================== +FullscreenFXManager::~FullscreenFXManager +================== +*/ +FullscreenFXManager::~FullscreenFXManager() { + fx.DeleteContents(); +} + +/* +================== +FullscreenFXManager::FindFX +================== +*/ +FullscreenFX* FullscreenFXManager::FindFX( idStr name ) { + for ( int i = 0; i < fx.Num(); i++ ) { + if ( fx[i]->GetName() == name ) { + return fx[i]; + } + } + + return NULL; +} + +/* +================== +FullscreenFXManager::CreateFX +================== +*/ +void FullscreenFXManager::CreateFX( idStr name, idStr fxtype, int fade ) { + FullscreenFX *pfx = NULL; + + if ( fxtype == "helltime" ) { + pfx = new (TAG_FX) FullscreenFX_Helltime; + } else if ( fxtype == "warp" ) { + pfx = new (TAG_FX) FullscreenFX_Warp; + } else if ( fxtype == "envirosuit" ) { + pfx = new (TAG_FX) FullscreenFX_EnviroSuit; + } else if ( fxtype == "doublevision" ) { + pfx = new (TAG_FX) FullscreenFX_DoubleVision; + } else if ( fxtype == "multiplayer" ) { + pfx = new (TAG_FX) FullscreenFX_Multiplayer; + } else if ( fxtype == "influencevision" ) { + pfx = new (TAG_FX) FullscreenFX_InfluenceVision; + } else if ( fxtype == "bloom" ) { + pfx = new (TAG_FX) FullscreenFX_Bloom; + } else { + assert( 0 ); + } + + if ( pfx ) { + pfx->Initialize(); + pfx->SetFXManager( this ); + pfx->SetName( name ); + pfx->SetFadeSpeed( fade ); + fx.Append( pfx ); + } +} + +/* +================== +FullscreenFXManager::Initialize +================== +*/ +void FullscreenFXManager::Initialize( idPlayerView *pv ) { + // set the playerview + playerView = pv; + blendBackMaterial = declManager->FindMaterial( "textures/d3bfg/blendBack" ); + + // allocate the fx + CreateFX( "helltime", "helltime", 1000 ); + CreateFX( "warp", "warp", 0 ); + CreateFX( "envirosuit", "envirosuit", 500 ); + CreateFX( "doublevision", "doublevision", 0 ); + CreateFX( "multiplayer", "multiplayer", 1000 ); + CreateFX( "influencevision", "influencevision", 1000 ); + CreateFX( "bloom", "bloom", 0 ); + + // pre-cache the texture grab so we dont hitch + renderSystem->CropRenderSize( 512, 512 ); + renderSystem->CaptureRenderToImage( "_accum" ); + renderSystem->UnCrop(); + + renderSystem->CaptureRenderToImage( "_currentRender" ); +} + +/* +================== +FullscreenFXManager::Blendback +================== +*/ +void FullscreenFXManager::Blendback( float alpha ) { + // alpha fade + if ( alpha < 1.f ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f - alpha ); + float s0 = 0.0f; + float t0 = 1.0f; + float s1 = 1.0f; + float t1 = 0.0f; + renderSystem->DrawStretchPic( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, s0, t0, s1, t1, blendBackMaterial ); + } +} + +/* +================== +FullscreenFXManager::Save +================== +*/ +void FullscreenFXManager::Save( idSaveGame *savefile ) { + for ( int i = 0; i < fx.Num(); i++ ) { + FullscreenFX *pfx = fx[i]; + pfx->Save( savefile ); + } +} + +/* +================== +FullscreenFXManager::Restore +================== +*/ +void FullscreenFXManager::Restore( idRestoreGame *savefile ) { + for ( int i = 0; i < fx.Num(); i++ ) { + FullscreenFX *pfx = fx[i]; + pfx->Restore( savefile ); + } +} + +idCVar player_allowScreenFXInStereo( "player_allowScreenFXInStereo", "1", CVAR_BOOL, "allow full screen fx in stereo mode" ); + +/* +================== +FullscreenFXManager::Process +================== +*/ +void FullscreenFXManager::Process( const renderView_t *view ) { + bool allpass = false; + bool atLeastOneFX = false; + + if ( g_testFullscreenFX.GetInteger() == -2 ) { + allpass = true; + } + + // do the first render + gameRenderWorld->RenderScene( view ); + + // we should consider these on a case-by-case basis for stereo rendering + // double vision could be implemented "for real" by shifting the + // eye views + if ( IsGameStereoRendered() && !player_allowScreenFXInStereo.GetBool() ) { + return; + } + + // do the process + for ( int i = 0; i < fx.Num(); i++ ) { + FullscreenFX *pfx = fx[i]; + bool drawIt = false; + + // determine if we need to draw + if ( pfx->Active() || g_testFullscreenFX.GetInteger() == i || allpass ) { + drawIt = pfx->SetTriggerState( true ); + } else { + drawIt = pfx->SetTriggerState( false ); + } + + // do the actual drawing + if ( drawIt ) { + atLeastOneFX = true; + + // we need to dump to _currentRender + renderSystem->CaptureRenderToImage( "_currentRender" ); + + // handle the accum pass if we have one + if ( pfx->HasAccum() ) { + // we need to crop the accum pass + renderSystem->CropRenderSize( 512, 512 ); + pfx->AccumPass( view ); + renderSystem->CaptureRenderToImage( "_accum" ); + renderSystem->UnCrop(); + } + + // do the high quality pass + pfx->HighQuality(); + + // do the blendback + Blendback( pfx->GetFadeAlpha() ); + } + } +} + + + diff --git a/neo/d3xp/PlayerView.h b/neo/d3xp/PlayerView.h new file mode 100644 index 00000000..4e5ec3b3 --- /dev/null +++ b/neo/d3xp/PlayerView.h @@ -0,0 +1,417 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_PLAYERVIEW_H__ +#define __GAME_PLAYERVIEW_H__ + +class idMenuHandler_HUD; + +/* +=============================================================================== + + Player view. + +=============================================================================== +*/ + +// screenBlob_t are for the on-screen damage claw marks, etc +typedef struct { + const idMaterial * material; + float x, y, w, h; + float s1, t1, s2, t2; + int finishTime; + int startFadeTime; + float driftAmount; +} screenBlob_t; + +#define MAX_SCREEN_BLOBS 8 + + + + + +class WarpPolygon_t { +public: + idVec4 outer1; + idVec4 outer2; + idVec4 center; +}; + +class Warp_t { +public: + int id; + bool active; + + int startTime; + float initialRadius; + + idVec3 worldOrigin; + idVec2 screenOrigin; + + int durationMsec; + + idList polys; +}; + + + + + + + + +class idPlayerView; +class FullscreenFXManager; + + +/* +================== +FxFader +================== +*/ +class FxFader { + enum { + FX_STATE_OFF, + FX_STATE_RAMPUP, + FX_STATE_RAMPDOWN, + FX_STATE_ON + }; + + int time; + int state; + float alpha; + int msec; + +public: + FxFader(); + + // primary functions + bool SetTriggerState( bool active ); + + virtual void Save( idSaveGame *savefile ); + virtual void Restore( idRestoreGame *savefile ); + + // fader functions + void SetFadeTime( int t ) { msec = t; }; + int GetFadeTime() { return msec; }; + + // misc functions + float GetAlpha() { return alpha; }; +}; + + +/* +================== +FullscreenFX +================== +*/ +class FullscreenFX { +protected: + idStr name; + FxFader fader; + FullscreenFXManager *fxman; + +public: + FullscreenFX() { fxman = NULL; }; + virtual ~FullscreenFX() { }; + + virtual void Initialize() = 0; + virtual bool Active() = 0; + virtual void HighQuality() = 0; + virtual void LowQuality() { }; + virtual void AccumPass( const renderView_t *view ) { }; + virtual bool HasAccum() { return false; }; + + void SetName( idStr n ) { name = n; }; + idStr GetName() { return name; }; + + void SetFXManager( FullscreenFXManager *fx ) { fxman = fx; }; + + bool SetTriggerState( bool state ) { return fader.SetTriggerState( state ); }; + void SetFadeSpeed( int msec ) { fader.SetFadeTime( msec ); }; + float GetFadeAlpha() { return fader.GetAlpha(); }; + + virtual void Save( idSaveGame *savefile ); + virtual void Restore( idRestoreGame *savefile ); +}; + +/* +================== +FullscreenFX_Helltime +================== +*/ +class FullscreenFX_Helltime : public FullscreenFX { + const idMaterial * initMaterial; + const idMaterial * captureMaterials[3]; + const idMaterial * drawMaterial; + bool clearAccumBuffer; + + int DetermineLevel(); + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); + virtual void AccumPass( const renderView_t *view ); + virtual bool HasAccum() { return true; }; + + virtual void Restore( idRestoreGame *savefile ); +}; + +/* +================== +FullscreenFX_Multiplayer +================== +*/ +class FullscreenFX_Multiplayer : public FullscreenFX { + const idMaterial * initMaterial; + const idMaterial * captureMaterial; + const idMaterial * drawMaterial; + bool clearAccumBuffer; + + int DetermineLevel(); + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); + virtual void AccumPass( const renderView_t *view ); + virtual bool HasAccum() { return true; }; + + virtual void Restore( idRestoreGame *savefile ); +}; + +/* +================== +FullscreenFX_Warp +================== +*/ +class FullscreenFX_Warp : public FullscreenFX { + const idMaterial* material; + bool grabberEnabled; + int startWarpTime; + + void DrawWarp( WarpPolygon_t wp, float interp ); + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); + + void EnableGrabber( bool active ) { grabberEnabled = active; startWarpTime = gameLocal.slow.time; }; + + virtual void Save( idSaveGame *savefile ); + virtual void Restore( idRestoreGame *savefile ); +}; + +/* +================== +FullscreenFX_EnviroSuit +================== +*/ +class FullscreenFX_EnviroSuit : public FullscreenFX { + const idMaterial* material; + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); +}; + +/* +================== +FullscreenFX_DoubleVision +================== +*/ +class FullscreenFX_DoubleVision : public FullscreenFX { + const idMaterial* material; + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); +}; + +/* +================== +FullscreenFX_InfluenceVision +================== +*/ +class FullscreenFX_InfluenceVision : public FullscreenFX { + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); +}; + +/* +================== +FullscreenFX_Bloom +================== +*/ +class FullscreenFX_Bloom : public FullscreenFX { + const idMaterial* drawMaterial; + const idMaterial* initMaterial; + + float currentIntensity; + float targetIntensity; + +public: + virtual void Initialize(); + virtual bool Active(); + virtual void HighQuality(); + + virtual void Save( idSaveGame *savefile ); + virtual void Restore( idRestoreGame *savefile ); +}; + + + +/* +================== +FullscreenFXManager +================== +*/ +class FullscreenFXManager { + idList fx; + + idPlayerView * playerView; + const idMaterial* blendBackMaterial; + + void CreateFX( idStr name, idStr fxtype, int fade ); + +public: + FullscreenFXManager(); + virtual ~FullscreenFXManager(); + + void Initialize( idPlayerView *pv ); + + void Process( const renderView_t *view ); + void Blendback( float alpha ); + + idPlayerView* GetPlayerView() { return playerView; }; + idPlayer* GetPlayer() { return gameLocal.GetLocalPlayer(); }; + + int GetNum() { return fx.Num(); }; + FullscreenFX* GetFX( int index ) { return fx[index]; }; + FullscreenFX* FindFX( idStr name ); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); +}; + + + + + + + + + + +class idPlayerView { +public: + idPlayerView(); + ~idPlayerView(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayerEntity( class idPlayer *playerEnt ); + + void ClearEffects(); + + void DamageImpulse( idVec3 localKickDir, const idDict *damageDef ); + + void WeaponFireFeedback( const idDict *weaponDef ); + + idAngles AngleOffset() const; // returns the current kick angle + + idMat3 ShakeAxis() const; // returns the current shake angle + + void CalculateShake(); + + // this may involve rendering to a texture and displaying + // that with a warp model or in double vision mode + void RenderPlayerView( idMenuHandler_HUD * hudManager ); + void EmitStereoEyeView( const int eye, idMenuHandler_HUD * hudManager ); + + void Fade( idVec4 color, int time ); + + void Flash( idVec4 color, int time ); + + // temp for view testing + void EnableBFGVision( bool b ) { bfgVision = b; }; + +private: + void SingleView( const renderView_t *view, idMenuHandler_HUD * hudManager ); + void ScreenFade(); + + screenBlob_t * GetScreenBlob(); + + screenBlob_t screenBlobs[MAX_SCREEN_BLOBS]; + +public: + int dvFinishTime; // double vision will be stopped at this time + + int kickFinishTime; // view kick will be stopped at this time + idAngles kickAngles; + + bool bfgVision; // + + const idMaterial * tunnelMaterial; // health tunnel vision + const idMaterial * armorMaterial; // armor damage view effect + const idMaterial * berserkMaterial; // berserk effect + const idMaterial * irGogglesMaterial; // ir effect + const idMaterial * bloodSprayMaterial; // blood spray + const idMaterial * bfgMaterial; // when targeted with BFG + float lastDamageTime; // accentuate the tunnel effect for a while + + idVec4 fadeColor; // fade color + idVec4 fadeToColor; // color to fade to + idVec4 fadeFromColor; // color to fade from + float fadeRate; // fade rate + int fadeTime; // fade time + + idAngles shakeAng; // from the sound sources + + idPlayer * player; + renderView_t view; + + FullscreenFXManager *fxManager; + +public: + int AddWarp( idVec3 worldOrigin, float centerx, float centery, float initialRadius, float durationMsec ); + void FreeWarp( int id ); +}; + +// the crosshair is swapped for a laser sight in stereo rendering +bool IsGameStereoRendered(); + +#endif /* !__GAME_PLAYERVIEW_H__ */ diff --git a/neo/d3xp/PredictedValue.h b/neo/d3xp/PredictedValue.h new file mode 100644 index 00000000..a202d030 --- /dev/null +++ b/neo/d3xp/PredictedValue.h @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef PREDICTED_VALUE_H_ +#define PREDICTED_VALUE_H_ + +#include "Game_local.h" + +/* +================================================ +A simple class to handle simple predictable values +on multiplayer clients. + +The class encapsulates the actual value to be stored +as well as the client frame number on which it is set. + +When reading predicted values from a snapshot, the actual +value is only updated if the server has processed the client's +usercmd for the frame in which the client predicted the value. +Got that? +================================================ +*/ +template< class type_ > +class idPredictedValue { +public: + explicit idPredictedValue(); + explicit idPredictedValue( const type_ & value_ ); + + void Set( const type_ & newValue ); + + idPredictedValue< type_ > & operator=( const type_ & value ); + + idPredictedValue< type_ > & operator+=( const type_ & toAdd ); + idPredictedValue< type_ > & operator-=( const type_ & toSubtract ); + + bool UpdateFromSnapshot( const type_ & valueFromSnapshot, int clientNumber ); + + type_ Get() const { return value; } + +private: + // Noncopyable + idPredictedValue( const idPredictedValue< type_ > & other ); + idPredictedValue< type_ > & operator=( const idPredictedValue< type_ > & other ); + + type_ value; + int clientPredictedMilliseconds; // The time in which the client predicted the value. + + void UpdatePredictionTime(); +}; + + + + +#endif diff --git a/neo/d3xp/PredictedValue_impl.h b/neo/d3xp/PredictedValue_impl.h new file mode 100644 index 00000000..a30b9920 --- /dev/null +++ b/neo/d3xp/PredictedValue_impl.h @@ -0,0 +1,220 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef PREDICTED_VALUE_IMPL_H_ +#define PREDICTED_VALUE_IMPL_H_ + +#include "PredictedValue.h" +#include "Player.h" + +/* +=============== +idPredictedValue::idPredictedValue +=============== +*/ +template< class type_ > +idPredictedValue< type_ >::idPredictedValue() : + value(), + clientPredictedMilliseconds( 0 ) { +} + +/* +=============== +idPredictedValue::idPredictedValue +=============== +*/ +template< class type_ > +idPredictedValue< type_ >::idPredictedValue( const type_ & value_ ) : + value( value_ ), + clientPredictedMilliseconds( 0 ) { +} + +/* +=============== +idPredictedValue::UpdatePredictionTime +=============== +*/ +template< class type_ > +void idPredictedValue< type_ >::UpdatePredictionTime() { + if ( gameLocal.GetLocalPlayer() != NULL ) { + clientPredictedMilliseconds = gameLocal.GetLocalPlayer()->usercmd.clientGameMilliseconds; + } +} + +/* +=============== +idPredictedValue::Set +=============== +*/ +template< class type_ > +void idPredictedValue< type_ >::Set( const type_ & newValue ) { + value = newValue; + UpdatePredictionTime(); +} + +/* +=============== +idPredictedValue::operator= +=============== +*/ +template< class type_ > +idPredictedValue< type_ > & idPredictedValue< type_ >::operator=( const type_ & newValue ) { + Set( newValue ); + return *this; +} + +/* +=============== +idPredictedValue::operator+= +=============== +*/ +template< class type_ > +idPredictedValue< type_ > & idPredictedValue< type_ >::operator+=( const type_ & toAdd ) { + Set( value + toAdd ); + return *this; +} + +/* +=============== +idPredictedValue::operator-= +=============== +*/ +template< class type_ > +idPredictedValue< type_ > & idPredictedValue< type_ >::operator-=( const type_ & toSubtract ) { + Set( value - toSubtract ); + return *this; +} + +/* +=============== +idPredictedValue::UpdateFromSnapshot + +Always updates the value for remote clients. + +Only updates the actual value if the snapshot usercmd frame is newer than the one in which +the client predicted this value. + +Returns true if the value was set, false if not. +=============== +*/ +template< class type_ > +bool idPredictedValue< type_ >::UpdateFromSnapshot( const type_ & valueFromSnapshot, int clientNumber ) { + if ( clientNumber != gameLocal.GetLocalClientNum() ) { + value = valueFromSnapshot; + return true; + } + + if ( gameLocal.GetLastClientUsercmdMilliseconds( clientNumber ) >= clientPredictedMilliseconds ) { + value = valueFromSnapshot; + return true; + } + + return false; +} + +/* +=============== +operator== + +Overload for idPredictedValue. +We only care if the values are equal, not the frame number. +=============== +*/ +template< class firstType_, class secondType_ > +bool operator==( const idPredictedValue< firstType_ > & lhs, const idPredictedValue< secondType_ > & rhs ) { + return lhs.Get() == rhs.Get(); +} + +/* +=============== +operator!= + +Overload for idPredictedValue. +We only care if the values are equal, not the frame number. +=============== +*/ +template< class firstType_, class secondType_ > +bool operator!=( const idPredictedValue< firstType_ > & lhs, const idPredictedValue< secondType_ > & rhs ) { + return lhs.Get() != rhs.Get(); +} + +/* +=============== +operator== + +Overload for idPredictedValue. +We only care if the values are equal, not the frame number. +=============== +*/ +template< class firstType_, class secondType_ > +bool operator==( const idPredictedValue< firstType_ > & lhs, const secondType_ & rhs ) { + return lhs.Get() == rhs; +} + +/* +=============== +operator== + +Overload for idPredictedValue. +We only care if the values are equal, not the frame number. +=============== +*/ +template< class firstType_, class secondType_ > +bool operator==( const firstType_ lhs, const idPredictedValue< secondType_ > & rhs ) { + return lhs == rhs.Get(); +} + +/* +=============== +operator!= + +Overload for idPredictedValue. +We only care if the values are equal, not the frame number. +=============== +*/ +template< class firstType_, class secondType_ > +bool operator!=( const idPredictedValue< firstType_ > & lhs, const secondType_ & rhs ) { + return lhs.Get() != rhs; +} + +/* +=============== +operator!= + +Overload for idPredictedValue. +We only care if the values are equal, not the frame number. +=============== +*/ +template< class firstType_, class secondType_ > +bool operator!=( const firstType_ lhs, const idPredictedValue< secondType_ > & rhs ) { + return lhs != rhs.Get(); +} + + + + +#endif diff --git a/neo/d3xp/Projectile.cpp b/neo/d3xp/Projectile.cpp new file mode 100644 index 00000000..2a30f9c7 --- /dev/null +++ b/neo/d3xp/Projectile.cpp @@ -0,0 +1,2989 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + + +idCVar g_projectileDebug( "g_projectileDebug", "0", CVAR_BOOL, "Debug projectiles" ); + + +// This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight. + +// This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight. +idArray< idProjectile::simulatedProjectile_t, idProjectile::MAX_SIMULATED_PROJECTILES > idProjectile::projectilesToSimulate; + +static const int BFG_DAMAGE_FREQUENCY = 333; +static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; + +const idEventDef EV_Explode( "", NULL ); +const idEventDef EV_Fizzle( "", NULL ); +const idEventDef EV_RadiusDamage( "", "e" ); +const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' ); + +const idEventDef EV_CreateProjectile( "projectileCreateProjectile", "evv" ); +const idEventDef EV_LaunchProjectile( "projectileLaunchProjectile", "vvv" ); +const idEventDef EV_SetGravity( "setGravity", "f" ); + +CLASS_DECLARATION( idEntity, idProjectile ) + EVENT( EV_Explode, idProjectile::Event_Explode ) + EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) + EVENT( EV_Touch, idProjectile::Event_Touch ) + EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) + EVENT( EV_GetProjectileState, idProjectile::Event_GetProjectileState ) + EVENT( EV_CreateProjectile, idProjectile::Event_CreateProjectile ) + EVENT( EV_LaunchProjectile, idProjectile::Event_LaunchProjectile ) + EVENT( EV_SetGravity, idProjectile::Event_SetGravity ) +END_CLASS + +/* +================ +idProjectile::idProjectile +================ +*/ +idProjectile::idProjectile() : + launchOrigin( 0.0f ), + launchAxis( mat3_identity ) { + owner = NULL; + lightDefHandle = -1; + thrust = 0.0f; + thrust_end = 0; + smokeFly = NULL; + smokeFlyTime = 0; + state = SPAWNED; + lightOffset = vec3_zero; + lightStartTime = 0; + lightEndTime = 0; + lightColor = vec3_zero; + damagePower = 1.0f; + launchedFromGrabber = false; + mTouchTriggers = false; + mNoExplodeDisappear = false; + memset( &projectileFlags, 0, sizeof( projectileFlags ) ); + memset( &renderLight, 0, sizeof( renderLight ) ); + + // note: for net_instanthit projectiles, we will force this back to false at spawn time + fl.networkSync = true; +} + +/* +================ +idProjectile::Spawn +================ +*/ +void idProjectile::Spawn() { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( 0 ); + physicsObj.PutToRest(); + SetPhysics( &physicsObj ); + mNoExplodeDisappear = spawnArgs.GetBool( "no_explode_disappear", mNoExplodeDisappear ); + mTouchTriggers = spawnArgs.GetBool( "touch_triggers", mTouchTriggers ); +} + +/* +================ +idProjectile::Save +================ +*/ +void idProjectile::Save( idSaveGame *savefile ) const { + + owner.Save( savefile ); + + projectileFlags_s flags = projectileFlags; + LittleBitField( &flags, sizeof( flags ) ); + savefile->Write( &flags, sizeof( flags ) ); + + savefile->WriteFloat( thrust ); + savefile->WriteInt( thrust_end ); + + savefile->WriteRenderLight( renderLight ); + savefile->WriteInt( (int)lightDefHandle ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + + savefile->WriteParticle( smokeFly ); + savefile->WriteInt( smokeFlyTime ); + + savefile->WriteInt( originalTimeGroup ); + + savefile->WriteInt( (int)state ); + + savefile->WriteFloat( damagePower ); + + savefile->WriteStaticObject( physicsObj ); + savefile->WriteStaticObject( thruster ); +} + +/* +================ +idProjectile::Restore +================ +*/ +void idProjectile::Restore( idRestoreGame *savefile ) { + + owner.Restore( savefile ); + + savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); + LittleBitField( &projectileFlags, sizeof( projectileFlags ) ); + + savefile->ReadFloat( thrust ); + savefile->ReadInt( thrust_end ); + + savefile->ReadRenderLight( renderLight ); + savefile->ReadInt( (int &)lightDefHandle ); + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + + savefile->ReadParticle( smokeFly ); + savefile->ReadInt( smokeFlyTime ); + + savefile->ReadInt( originalTimeGroup ); + + savefile->ReadInt( (int &)state ); + + savefile->ReadFloat( damagePower ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadStaticObject( thruster ); + thruster.SetPhysics( &physicsObj ); + + if ( smokeFly != NULL ) { + idVec3 dir; + dir = physicsObj.GetLinearVelocity(); + dir.NormalizeFast(); + gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); + } + + if ( lightDefHandle >= 0 ) { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } +} + +/* +================ +idProjectile::GetOwner +================ +*/ +idEntity *idProjectile::GetOwner() const { + return owner.GetEntity(); +} + +/* +================ +idProjectile::Create +================ +*/ +void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { + idDict args; + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + idVec3 tmp; + idMat3 axis; + + Unbind(); + + // align z-axis of model with the direction + axis = dir.ToMat3(); + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + + physicsObj.GetClipModel()->SetOwner( owner ); + + this->owner = owner; + + memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *(const char *)shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); +#ifdef ID_PC + renderLight.lightRadius *= 1.5f; + renderLight.forceShadows = true; +#endif + spawnArgs.GetVector( "light_color", "1 1 1", light_color ); + renderLight.shaderParms[0] = light_color[0]; + renderLight.shaderParms[1] = light_color[1]; + renderLight.shaderParms[2] = light_color[2]; + renderLight.shaderParms[3] = 1.0f; + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + lightStartTime = 0; + lightEndTime = 0; + smokeFlyTime = 0; + + damagePower = 1.0f; + + if(spawnArgs.GetBool("reset_time_offset", "0")) { + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + + UpdateVisuals(); + + state = CREATED; +} + +/* +================= +idProjectile::~idProjectile +================= +*/ +idProjectile::~idProjectile() { + StopSound( SND_CHANNEL_ANY, false ); + FreeLightDef(); +} + +/* +================= +idProjectile::FreeLightDef +================= +*/ +void idProjectile::FreeLightDef() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================= +idProjectile::Launch +================= +*/ +void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + float fuse; + float startthrust; + float endthrust; + idVec3 velocity; + idAngles angular_velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float speed; + float gravity; + idVec3 gravVec; + idVec3 tmp; + idMat3 axis; + int thrust_start; + int contents; + int clipMask; + + // allow characters to throw projectiles during cinematics, but not the player + if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) { + cinematic = owner.GetEntity()->cinematic; + } else { + cinematic = false; + } + + thrust = spawnArgs.GetFloat( "thrust" ); + startthrust = spawnArgs.GetFloat( "thrust_start" ); + endthrust = spawnArgs.GetFloat( "thrust_end" ); + + spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + + speed = velocity.Length() * launchPower; + + damagePower = dmgPower; + + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + angular_friction = spawnArgs.GetFloat( "angular_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + fuse = spawnArgs.GetFloat( "fuse" ); + + projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); + projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); + projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); + + if ( mass <= 0 ) { + gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); + } + + thrust *= mass; + thrust_start = SEC2MS( startthrust ) + gameLocal.time; + thrust_end = SEC2MS( endthrust ) + gameLocal.time; + + lightStartTime = 0; + lightEndTime = 0; + + if ( health ) { + fl.takedamage = true; + } + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + + Unbind(); + + // align z-axis of model with the direction + axis = dir.ToMat3(); + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + contents = 0; + clipMask = MASK_SHOT_RENDERMODEL; + if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { + contents |= CONTENTS_TRIGGER; + } + if ( !spawnArgs.GetBool( "no_contents" ) ) { + contents |= CONTENTS_PROJECTILE; + clipMask |= CONTENTS_PROJECTILE; + } + + if ( !idStr::Cmp( this->GetEntityDefName(), "projectile_helltime_killer" ) ) { + contents = CONTENTS_MOVEABLECLIP; + clipMask = CONTENTS_MOVEABLECLIP; + fuse = 10.0f; + } + + // don't do tracers on client, we don't know origin and direction + if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { + SetModel( spawnArgs.GetString( "model_tracer" ) ); + projectileFlags.isTracer = true; + } + + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + if ( contact_friction == 0.0f ) { + physicsObj.NoContact(); + } + physicsObj.SetBouncyness( bounce ); + physicsObj.SetGravity( gravVec * gravity ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask ); + physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity ); + physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + + launchOrigin = start; + launchAxis = axis; + + thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); + + if ( !common->IsClient() || fl.skipReplication ) { + if ( fuse <= 0 ) { + // run physics for 1 second + RunPhysics(); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); + } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Explode, fuse ); + } else { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Fizzle, fuse ); + } + } + + if ( projectileFlags.isTracer ) { + StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); + } else { + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); + } + + smokeFlyTime = 0; + const char *smokeName = spawnArgs.GetString( "smoke_fly" ); + if ( *smokeName != '\0' ) { + smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeFlyTime = gameLocal.time; + } + + originalTimeGroup = timeGroup; + + // used for the plasma bolts but may have other uses as well + if ( projectileFlags.randomShaderSpin ) { + float f = gameLocal.random.RandomFloat(); + f *= 0.5f; + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; + } + + UpdateVisuals(); + + state = LAUNCHED; +} + +/* +================ +idProjectile::Think +================ +*/ +void idProjectile::Think() { + + if ( thinkFlags & TH_THINK ) { + if ( thrust && ( gameLocal.time < thrust_end ) ) { + // evaluate force + thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust ); + thruster.Evaluate( gameLocal.time ); + } + } + + if ( mTouchTriggers ) { + TouchTriggers(); + } + + // if the projectile owner is a player + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + + // Remove any projectiles spectators threw. + if( player != NULL && player->spectating ) { + PostEventMS( &EV_Remove, 0 ); + } + } + + // run physics + RunPhysics(); + + DecayOriginAndAxisDelta(); + + Present(); + + AddParticlesAndLight(); +} + +/* +================= +idProjectile::AddParticlesAndLight +================= +*/ +void idProjectile::AddParticlesAndLight() { + // add the particles + if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) { + idVec3 dir = -GetPhysics()->GetLinearVelocity(); + dir.Normalize(); + SetTimeState ts(originalTimeGroup); + + if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3(), timeGroup /*_D3XP*/ ) ) { + smokeFlyTime = gameLocal.time; + } + } + + // add the light + if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; + renderLight.axis = GetPhysics()->GetAxis(); + if ( ( lightDefHandle != -1 ) ) { + if ( lightEndTime > 0 && gameLocal.time <= lightEndTime ) { + idVec3 color( 0, 0, 0 ); + if ( gameLocal.time < lightEndTime ) { + float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); + color.Lerp( lightColor, color, frac ); + } + renderLight.shaderParms[SHADERPARM_RED] = color.x; + renderLight.shaderParms[SHADERPARM_GREEN] = color.y; + renderLight.shaderParms[SHADERPARM_BLUE] = color.z; + } + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } +} + +/* +================= +idProjectile::Collide +================= +*/ +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *ent; + idEntity *ignore; + const char *damageDefName; + idVec3 dir; + float push; + float damageScale; + + if ( state == EXPLODED || state == FIZZLED ) { + return true; + } + + const bool isHitscan = spawnArgs.GetBool( "net_instanthit" ); + + // hanlde slow projectiles here. + if ( common->IsClient() && !isHitscan ) { + + // This is a replicated slow projectile, predict the explosion. + if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !isHitscan ) ) { + Explode( collision, NULL ); + return true; + } + + } + + // remove projectile when a 'noimpact' surface is hit + if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + PostEventMS( &EV_Remove, 0 ); + common->DPrintf( "Projectile collision no impact\n" ); + return true; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == owner.GetEntity() ) { + assert( 0 ); + return true; + } + + // just get rid of the projectile when it hits a player in noclip + if ( ent->IsType( idPlayer::Type ) && static_cast( ent )->noclip ) { + PostEventMS( &EV_Remove, 0 ); + return true; + } + + // direction of projectile + dir = velocity; + dir.Normalize(); + + // projectiles can apply an additional impulse next to the rigid body physics impulse + if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) { + if ( !common->IsClient() ) { + ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir ); + } + } + + // MP: projectiles open doors + if ( common->IsMultiplayer() && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { + if ( !common->IsClient() ) { + ent->ProcessEvent( &EV_Activate , this ); + } + } + + if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody()->IsType( idActor::Type ) ) ) { + if ( !projectileFlags.detonate_on_actor ) { + return false; + } + } else { + if ( !projectileFlags.detonate_on_world ) { + if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) { + float len = velocity.Length(); + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) ); + StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL ); + } + } + return false; + } + } + + SetOrigin( collision.endpos ); + SetAxis( collision.endAxis ); + + // To see the explosion on the collision surface instead of + // at the muzzle, clear the deltas. + CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + // unlink the clip model because we no longer need it + GetPhysics()->UnlinkClip(); + + damageDefName = spawnArgs.GetString( "def_damage" ); + + ignore = NULL; + + // if the projectile causes a damage effect + if ( spawnArgs.GetBool( "impact_damage_effect" ) ) { + // if the hit entity has a special damage effect + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + ent->AddDamageEffect( collision, velocity, damageDefName ); + } else { + AddDefaultDamageEffect( collision, velocity ); + } + } + + // if the hit entity takes damage + if ( ent->fl.takedamage ) { + if ( damagePower ) { + damageScale = damagePower; + } else { + damageScale = 1.0f; + } + + // if the projectile owner is a player + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { + // if the projectile hit an actor + if ( ent->IsType( idActor::Type ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileHits( 1 ); + damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); + } + } + + if ( damageDefName[0] != '\0' ) { + + bool killedByImpact = true; + + if( ent->health <= 0 ) { + killedByImpact = false; + } + + // Only handle the server's own attacks here. Attacks by other players on the server occur through + // reliable messages. + if ( !common->IsMultiplayer() || common->IsClient() || ( common->IsServer() && owner.GetEntityNum() == gameLocal.GetLocalClientNum() ) || ( common->IsServer() && !isHitscan ) ) { + ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } else { + if( ent->IsType( idPlayer::Type ) == false && common->IsServer() ) { + ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } + } + + // Check if we are hitting an actor. and see if we killed him. + if( !common->IsClient() && ent->health <= 0 && killedByImpact ) { + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { + if ( ent->IsType( idActor::Type ) && ent != owner.GetEntity() ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileKills(); + } + } + + } + + ignore = ent; + } + } + + Explode( collision, ignore ); + + if ( !common->IsClient() && owner.GetEntity() != NULL && owner.GetEntity()->IsType( idPlayer::Type ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + int kills = player->GetProjectileKills(); + + if( kills >= 2 && common->IsMultiplayer() && strstr( GetName(), "projectile_rocket" ) != 0 ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG ); + } + + // projectile is done dealing damage. + player->ResetProjectileKills(); + } + + return true; +} + +/* +================= +idProjectile::DefaultDamageEffect +================= +*/ +void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) { + const char *decal, *sound, *typeName; + surfTypes_t materialType; + + if ( collision.c.material != NULL ) { + materialType = collision.c.material->GetSurfaceType(); + } else { + materialType = SURFTYPE_METAL; + } + + // get material type name + typeName = gameLocal.sufaceTypeNames[ materialType ]; + + // play impact sound + sound = projectileDef.GetString( va( "snd_%s", typeName ) ); + if ( *sound == '\0' ) { + sound = projectileDef.GetString( "snd_metal" ); + } + if ( *sound == '\0' ) { + sound = projectileDef.GetString( "snd_impact" ); + } + if ( *sound != '\0' ) { + soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + // project decal + decal = projectileDef.GetString( va( "mtr_detonate_%s", typeName ) ); + if ( *decal == '\0' ) { + decal = projectileDef.GetString( "mtr_detonate" ); + } + if ( *decal != '\0' ) { + gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, projectileDef.GetFloat( "decal_size", "6.0" ), decal ); + } +} + +/* +================= +idProjectile::AddDefaultDamageEffect +================= +*/ +void idProjectile::AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ) { + + DefaultDamageEffect( this, spawnArgs, collision, velocity ); + + if ( common->IsServer() && fl.networkSync ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + lobbyUserID_t excluding; + + if ( spawnArgs.GetBool( "net_instanthit" ) && owner.GetEntityNum() < MAX_PLAYERS ) { + excluding = gameLocal.lobbyUserIDs[owner.GetEntityNum()]; + } + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( collision.c.point[0] ); + msg.WriteFloat( collision.c.point[1] ); + msg.WriteFloat( collision.c.point[2] ); + msg.WriteDir( collision.c.normal, 24 ); + msg.WriteLong( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 ); + msg.WriteFloat( velocity[0], 5, 10 ); + msg.WriteFloat( velocity[1], 5, 10 ); + msg.WriteFloat( velocity[2], 5, 10 ); + ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excluding ); + } +} + +/* +================ +idProjectile::Killed +================ +*/ +void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + Explode( collision, NULL ); + physicsObj.ClearContacts(); + physicsObj.PutToRest(); + } else { + Fizzle(); + } +} + +/* +================ +idProjectile::Fizzle +================ +*/ +void idProjectile::Fizzle() { + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); + + // fizzle FX + const char *psystem = spawnArgs.GetString( "smoke_fuse" ); + if ( psystem != NULL && *psystem != NULL ) { +//FIXME:SMOKE gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem ); + } + + // we need to work out how long the effects last and then remove them at that time + // for example, bullets have no real effects + if ( smokeFly && smokeFlyTime ) { + smokeFlyTime = 0; + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + Hide(); + FreeLightDef(); + + state = FIZZLED; + + if ( common->IsClient() && !fl.skipReplication ) { + return; + } + + CancelEvents( &EV_Fizzle ); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); +} + +/* +================ +idProjectile::Event_RadiusDamage +================ +*/ +void idProjectile::Event_RadiusDamage( idEntity *ignore ) { + const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner.GetEntity(), ignore, this, splash_damage, damagePower ); + } +} + +/* +================ +idProjectile::Event_RadiusDamage +================ +*/ +void idProjectile::Event_GetProjectileState() { + idThread::ReturnInt( state ); +} + +/* +================ +idProjectile::Explode +================ +*/ +void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) { + const char *fxname, *light_shader, *sndExplode; + float light_fadetime; + idVec3 normal; + int removeTime; + + if ( mNoExplodeDisappear ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + // activate rumble for player + idPlayer *player = gameLocal.GetLocalPlayer(); + const bool isHitscan = spawnArgs.GetBool( "net_instanthit" ); + if ( player != NULL && isHitscan == false ) { + + // damage + const char *damageDefName = spawnArgs.GetString( "def_damage" ); + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + int damage; + if ( damageDef != NULL ) { + damage = damageDef->GetInt( "damage" ); + } else { + damage = 200; + } + float damageScale = idMath::ClampFloat( 0.25f, 1.0f, (float)damage * ( 1.0f / 200.0f ) ); // 50...200 -> min...max rumble + + // distance + float dist = ( GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthFast(); + float distScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist * ( 1.0f / 4000.0f ) ) + 0.25f ); // 0...4000 -> max...min rumble + + distScale *= damageScale; // apply damage scale here, weaker damage produces less rumble + + // determine rumble + float highMag = distScale; + int highDuration = idMath::Ftoi( 300.0f * distScale ); + float lowMag = distScale * 0.75f; + int lowDuration = idMath::Ftoi( 500.0f * distScale ); + + player->SetControllerShake( highMag, highDuration, lowMag, lowDuration ); + } + + // stop sound + StopSound( SND_CHANNEL_BODY2, false ); + + // play explode sound + switch ( ( int ) damagePower ) { + case 2: sndExplode = "snd_explode2"; break; + case 3: sndExplode = "snd_explode3"; break; + case 4: sndExplode = "snd_explode4"; break; + default: sndExplode = "snd_explode"; break; + } + StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL ); + + // we need to work out how long the effects last and then remove them at that time + // for example, bullets have no real effects + if ( smokeFly && smokeFlyTime ) { + smokeFlyTime = 0; + } + + Hide(); + FreeLightDef(); + + if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { + GetPhysics()->SetAxis( normal.ToMat3() ); + } + GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal ); + + // default remove time + if( fl.skipReplication && !spawnArgs.GetBool( "net_instanthit" ) ) { + removeTime = spawnArgs.GetInt( "remove_time", "6000" ); + } else { + removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + } + + // change the model, usually to a PRT + fxname = NULL; + if ( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) { + fxname = g_testParticleName.GetString(); + } else { + fxname = spawnArgs.GetString( "model_detonate" ); + } + + int surfaceType = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL; + if ( !( fxname != NULL && *fxname != NULL ) ) { + if ( ( surfaceType == SURFTYPE_NONE ) || ( surfaceType == SURFTYPE_METAL ) || ( surfaceType == SURFTYPE_STONE ) ) { + fxname = spawnArgs.GetString( "model_smokespark" ); + } else if ( surfaceType == SURFTYPE_RICOCHET ) { + fxname = spawnArgs.GetString( "model_ricochet" ); + } else { + fxname = spawnArgs.GetString( "model_smoke" ); + } + } + + // If the explosion is in liquid, spawn a particle splash + idVec3 testOrg = GetPhysics()->GetOrigin(); + int testC = gameLocal.clip.Contents( testOrg, NULL, mat3_identity, CONTENTS_WATER, this ); + if ( testC & CONTENTS_WATER ) { + idFuncEmitter *splashEnt; + idDict splashArgs; + + splashArgs.Set( "model", "sludgebulletimpact.prt" ); + splashArgs.Set( "start_off", "1" ); + splashEnt = static_cast( gameLocal.SpawnEntityType( idFuncEmitter::Type, &splashArgs ) ); + + splashEnt->GetPhysics()->SetOrigin( testOrg ); + splashEnt->PostEventMS( &EV_Activate, 0, this ); + splashEnt->PostEventMS( &EV_Remove, 1500 ); + + // HACK - if this is a chaingun bullet, don't do the normal effect + if ( !idStr::Cmp( spawnArgs.GetString( "def_damage" ), "damage_bullet_chaingun" ) ) { + fxname = NULL; + } + } + + if ( fxname && *fxname ) { + SetModel( fxname ); + renderEntity.shaderParms[SHADERPARM_RED] = + renderEntity.shaderParms[SHADERPARM_GREEN] = + renderEntity.shaderParms[SHADERPARM_BLUE] = + renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat(); + Show(); + removeTime = ( removeTime > 3000 ) ? removeTime : 3000; + } + + // explosion light + light_shader = spawnArgs.GetString( "mtr_explode_light_shader" ); + + if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) + { + light_shader = "lights/midnight_grenade"; + } + + if ( *light_shader ) { + renderLight.shader = declManager->FindMaterial( light_shader, false ); + renderLight.pointLight = true; + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ); +#ifdef ID_PC + renderLight.lightRadius *= 2.0f; + renderLight.forceShadows = true; +#endif + + // Midnight ctf + if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) { + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ) * 2; + } + + spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor ); + renderLight.shaderParms[SHADERPARM_RED] = lightColor.x; + renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y; + renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z; + renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + + // Midnight ctf + if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) { + light_fadetime = 3.0f; + } else { + light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" ); + } + + lightStartTime = gameLocal.time; + lightEndTime = MSEC_ALIGN_TO_FRAME( gameLocal.time + SEC2MS( light_fadetime ) ); + BecomeActive( TH_THINK ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + state = EXPLODED; + + if ( common->IsClient() && !fl.skipReplication ) { + return; + } + + // alert the ai + gameLocal.AlertAI( owner.GetEntity() ); + + // bind the projectile to the impact entity if necesary + if ( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { + Bind( gameLocal.entities[collision.c.entityNum], true ); + } + + // splash damage + if ( !projectileFlags.noSplashDamage ) { + float delay = spawnArgs.GetFloat( "delay_splash" ); + if ( delay ) { + if ( removeTime < delay * 1000 ) { + removeTime = ( delay + 0.10 ) * 1000; + } + PostEventSec( &EV_RadiusDamage, delay, ignore ); + } else { + Event_RadiusDamage( ignore ); + } + } + + // spawn debris entities + int fxdebris = spawnArgs.GetInt( "debris_count" ); + if ( fxdebris ) { + const idDict *debris = gameLocal.FindEntityDefDict( "projectile_debris", false ); + if ( debris ) { + int amount = gameLocal.random.RandomInt( fxdebris ); + for ( int i = 0; i < amount; i++ ) { + idEntity *ent; + idVec3 dir; + dir.x = gameLocal.random.CRandomFloat() * 4.0f; + dir.y = gameLocal.random.CRandomFloat() * 4.0f; + dir.z = gameLocal.random.RandomFloat() * 8.0f; + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris, &ent, false ); + if ( ent == NULL || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + return; + } + + idDebris *debris = static_cast(ent); + debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + } + } + debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false ); + if ( debris ) { + int amount = gameLocal.random.RandomInt( fxdebris ); + for ( int i = 0; i < amount; i++ ) { + idEntity *ent; + idVec3 dir; + dir.x = gameLocal.random.CRandomFloat() * 8.0f; + dir.y = gameLocal.random.CRandomFloat() * 8.0f; + dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f; + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris, &ent, false ); + if ( ent == NULL || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_shrapnel' is not an idDebris" ); + break; + } + + idDebris *debris = static_cast(ent); + debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + } + } + } + + CancelEvents( &EV_Explode ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::GetVelocity +================ +*/ +idVec3 idProjectile::GetVelocity( const idDict *projectile ) { + idVec3 velocity; + + projectile->GetVector( "velocity", "0 0 0", velocity ); + return velocity; +} + +/* +================ +idProjectile::GetGravity +================ +*/ +idVec3 idProjectile::GetGravity( const idDict *projectile ) { + float gravity; + + gravity = projectile->GetFloat( "gravity" ); + return idVec3( 0, 0, -gravity ); +} + +/* +================ +idProjectile::Event_Explode +================ +*/ +void idProjectile::Event_Explode() { + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + AddDefaultDamageEffect( collision, collision.c.normal ); + Explode( collision, NULL ); +} + +/* +================ +idProjectile::Event_Fizzle +================ +*/ +void idProjectile::Event_Fizzle() { + Fizzle(); +} + +/* +================ +idProjectile::Event_Touch +================ +*/ +void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { + if ( common->IsClient() ) { + return; + } + + if ( IsHidden() ) { + return; + } + + // Projectiles do not collide with flags + if ( other->IsType( idItemTeam::Type ) ) { + return; + } + + if ( other != owner.GetEntity() ) { + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + AddDefaultDamageEffect( collision, collision.c.normal ); + Explode( collision, NULL ); + } +} + +/* +================ +idProjectile::CatchProjectile +================ +*/ +void idProjectile::CatchProjectile( idEntity* o, const char* reflectName ) { + idEntity *prevowner = owner.GetEntity(); + + owner = o; + physicsObj.GetClipModel()->SetOwner( o ); + + if ( this->IsType( idGuidedProjectile::Type ) ) { + idGuidedProjectile *proj = static_cast(this); + + proj->SetEnemy( prevowner ); + } + + idStr s = spawnArgs.GetString( "def_damage" ); + s += reflectName; + + const idDict *damageDef = gameLocal.FindEntityDefDict( s, false ); + if ( damageDef ) { + spawnArgs.Set( "def_damage", s ); + } +} + +/* +================ +idProjectile::GetProjectileState +================ +*/ +int idProjectile::GetProjectileState() { + + return (int)state; +} + +/* +================ +idProjectile::Event_CreateProjectile +================ +*/ +void idProjectile::Event_CreateProjectile( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { + Create(owner, start, dir); +} + +/* +================ +idProjectile::Event_LaunchProjectile +================ +*/ +void idProjectile::Event_LaunchProjectile( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity ) { + Launch(start, dir, pushVelocity); +} + +/* +================ +idProjectile::Event_SetGravity +================ +*/ +void idProjectile::Event_SetGravity( float gravity ) { + idVec3 gravVec; + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + physicsObj.SetGravity(gravVec * gravity); +} + +/* +================= +idProjectile::ClientPredictionCollide +================= +*/ +bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) { + idEntity *ent; + + // remove projectile when a 'noimpact' surface is hit + if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + return false; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == NULL ) { + return false; + } + + // don't do anything if hitting a noclip player + if ( ent->IsType( idPlayer::Type ) && static_cast( ent )->noclip ) { + return false; + } + + if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody()->IsType( idActor::Type ) ) ) { + if ( !projectileDef.GetBool( "detonate_on_actor" ) ) { + return false; + } + } else { + if ( !projectileDef.GetBool( "detonate_on_world" ) ) { + return false; + } + } + + // if the projectile causes a damage effect + if ( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) { + // if the hit entity does not have a special damage effect + if ( !ent->spawnArgs.GetBool( "bleed" ) ) { + // predict damage effect + DefaultDamageEffect( soundEnt, projectileDef, collision, velocity ); + } + } + return true; +} + +/* +================ +idProjectile::ClientThink +================ +*/ +void idProjectile::ClientThink( const int curTime, const float fraction, const bool predict ) { + if ( fl.skipReplication ) { + Think(); + } else { + if ( !renderEntity.hModel ) { + return; + } + InterpolatePhysicsOnly( fraction ); + Present(); + AddParticlesAndLight(); + } +} + +/* +================ +idProjectile::ClientPredictionThink +================ +*/ +void idProjectile::ClientPredictionThink() { + if ( !renderEntity.hModel ) { + return; + } + Think(); +} + +/* +================ +idProjectile::WriteToSnapshot +================ +*/ +void idProjectile::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteBits( owner.GetSpawnId(), 32 ); + msg.WriteBits( state, 3 ); + msg.WriteBits( fl.hidden, 1 ); + + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +idProjectile::ReadFromSnapshot +================ +*/ +void idProjectile::ReadFromSnapshot( const idBitMsg &msg ) { + projectileState_t newState; + + owner.SetSpawnId( msg.ReadBits( 32 ) ); + newState = (projectileState_t) msg.ReadBits( 3 ); + + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else { + Show(); + } + + while( state != newState ) { + switch( state ) { + case SPAWNED: { + Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) ); + break; + } + case CREATED: { + // the right origin and direction are required if you want bullet traces + Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin ); + break; + } + case LAUNCHED: { + if ( newState == FIZZLED ) { + Fizzle(); + } else { + trace_t collision; + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + Explode( collision, NULL ); + } + break; + } + case FIZZLED: + case EXPLODED: { + StopSound( SND_CHANNEL_BODY2, false ); + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + state = SPAWNED; + break; + } + } + } + + physicsObj.ReadFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idProjectile::ClientReceiveEvent +================ +*/ +bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + trace_t collision; + idVec3 velocity; + + switch( event ) { + case EVENT_DAMAGE_EFFECT: { + memset( &collision, 0, sizeof( collision ) ); + collision.c.point[0] = msg.ReadFloat(); + collision.c.point[1] = msg.ReadFloat(); + collision.c.point[2] = msg.ReadFloat(); + collision.c.normal = msg.ReadDir( 24 ); + int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() ); + collision.c.material = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL; + velocity[0] = msg.ReadFloat( 5, 10 ); + velocity[1] = msg.ReadFloat( 5, 10 ); + velocity[2] = msg.ReadFloat( 5, 10 ); + DefaultDamageEffect( this, spawnArgs, collision, velocity ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} + + +/* +======================== +idProjectile::QueueToSimulate +======================== +*/ +void idProjectile::QueueToSimulate( int startTime ) { + assert( common->IsMultiplayer() && common->IsServer() ); + + for ( int i = 0; i < MAX_SIMULATED_PROJECTILES; i++ ) { + if ( projectilesToSimulate[i].projectile == NULL ) { + projectilesToSimulate[i].projectile = this; + projectilesToSimulate[i].startTime= startTime; + if ( g_projectileDebug.GetBool() ) { + int delta = gameLocal.GetServerGameTimeMs() - startTime; + idLib::Printf( "Simulating projectile %d. Approx %d delay.\n", GetEntityNumber(), delta); + } + return; + } + } + + idLib::Warning("Unable to simulate more projectiles this frame"); +} + +/* +======================== +idProjectile::SimulateProjectileFrame +======================== +*/ +void idProjectile::SimulateProjectileFrame( int msec, int endTime ) { + idVec3 oldOrigin = GetPhysics()->GetOrigin(); + + GetPhysics()->Evaluate( msec, endTime ); + SetOrigin( GetPhysics()->GetOrigin() ); + SetAxis( GetPhysics()->GetAxis() ); + + if ( g_projectileDebug.GetBool() ) { + float delta = ( GetPhysics()->GetOrigin() - oldOrigin ).Length(); + idLib::Printf( "Simulated projectile %d. Delta: %.2f \n", GetEntityNumber(), delta ); + //clientGame->renderWorld->DebugLine( idColor::colorYellow, oldOrigin, GetPhysics()->GetOrigin(), 5000 ); + } +} + +/* +======================== +idProjectile::PostSimulate +======================== +*/ +void idProjectile::PostSimulate( int endTime ) { + if ( state == EXPLODED || state == FIZZLED ) { + // Already exploded. To see the explosion on the collision surface instead of + // at the muzzle, don't set the deltas to the launch origin and axis. + CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } else { + CreateDeltasFromOldOriginAndAxis( launchOrigin, launchAxis ); + } +} + + + +/* +=============================================================================== + + idGuidedProjectile + +=============================================================================== +*/ + +const idEventDef EV_SetEnemy( "setEnemy", "E" ); + +CLASS_DECLARATION( idProjectile, idGuidedProjectile ) + EVENT( EV_SetEnemy, idGuidedProjectile::Event_SetEnemy ) +END_CLASS + +/* +================ +idGuidedProjectile::idGuidedProjectile +================ +*/ +idGuidedProjectile::idGuidedProjectile() { + enemy = NULL; + speed = 0.0f; + turn_max = 0.0f; + clamp_dist = 0.0f; + rndScale = ang_zero; + rndAng = ang_zero; + rndUpdateTime = 0; + angles = ang_zero; + burstMode = false; + burstDist = 0; + burstVelocity = 0.0f; + unGuided = false; +} + +/* +================= +idGuidedProjectile::~idGuidedProjectile +================= +*/ +idGuidedProjectile::~idGuidedProjectile() { +} + +/* +================ +idGuidedProjectile::Spawn +================ +*/ +void idGuidedProjectile::Spawn() { +} + +/* +================ +idGuidedProjectile::Save +================ +*/ +void idGuidedProjectile::Save( idSaveGame *savefile ) const { + enemy.Save( savefile ); + savefile->WriteFloat( speed ); + savefile->WriteAngles( rndScale ); + savefile->WriteAngles( rndAng ); + savefile->WriteInt( rndUpdateTime ); + savefile->WriteFloat( turn_max ); + savefile->WriteFloat( clamp_dist ); + savefile->WriteAngles( angles ); + savefile->WriteBool( burstMode ); + savefile->WriteBool( unGuided ); + savefile->WriteFloat( burstDist ); + savefile->WriteFloat( burstVelocity ); +} + +/* +================ +idGuidedProjectile::Restore +================ +*/ +void idGuidedProjectile::Restore( idRestoreGame *savefile ) { + enemy.Restore( savefile ); + savefile->ReadFloat( speed ); + savefile->ReadAngles( rndScale ); + savefile->ReadAngles( rndAng ); + savefile->ReadInt( rndUpdateTime ); + savefile->ReadFloat( turn_max ); + savefile->ReadFloat( clamp_dist ); + savefile->ReadAngles( angles ); + savefile->ReadBool( burstMode ); + savefile->ReadBool( unGuided ); + savefile->ReadFloat( burstDist ); + savefile->ReadFloat( burstVelocity ); +} + + +/* +================ +idGuidedProjectile::GetSeekPos +================ +*/ +void idGuidedProjectile::GetSeekPos( idVec3 &out ) { + idEntity *enemyEnt = enemy.GetEntity(); + if ( enemyEnt ) { + if ( enemyEnt->IsType( idActor::Type ) ) { + out = static_cast(enemyEnt)->GetEyePosition(); + out.z -= 12.0f; + } else { + out = enemyEnt->GetPhysics()->GetOrigin(); + } + } else { + out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f; + } +} + +/* +================ +idGuidedProjectile::Think +================ +*/ +void idGuidedProjectile::Think() { + idVec3 dir; + idVec3 seekPos; + idVec3 velocity; + idVec3 nose; + idVec3 tmp; + idMat3 axis; + idAngles dirAng; + idAngles diff; + float dist; + float frac; + int i; + + if ( state == LAUNCHED && !unGuided ) { + + GetSeekPos( seekPos ); + + if ( rndUpdateTime < gameLocal.time ) { + rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat(); + rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat(); + rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat(); + rndUpdateTime = gameLocal.time + 200; + } + + nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0]; + + dir = seekPos - nose; + dist = dir.Normalize(); + dirAng = dir.ToAngles(); + + // make it more accurate as it gets closer + frac = dist / clamp_dist; + if ( frac > 1.0f ) { + frac = 1.0f; + } + + diff = dirAng - angles + rndAng * frac; + + // clamp the to the max turn rate + diff.Normalize180(); + for( i = 0; i < 3; i++ ) { + if ( diff[ i ] > turn_max ) { + diff[ i ] = turn_max; + } else if ( diff[ i ] < -turn_max ) { + diff[ i ] = -turn_max; + } + } + angles += diff; + + // make the visual model always points the dir we're traveling + dir = angles.ToForward(); + velocity = dir * speed; + + if ( burstMode && dist < burstDist ) { + unGuided = true; + velocity *= burstVelocity; + } + + physicsObj.SetLinearVelocity( velocity ); + + // align z-axis of model with the direction + axis = dir.ToMat3(); + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + GetPhysics()->SetAxis( axis ); + } + + idProjectile::Think(); +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); + if ( owner.GetEntity() ) { + if ( owner.GetEntity()->IsType( idAI::Type ) ) { + enemy = static_cast( owner.GetEntity() )->GetEnemy(); + } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) { + trace_t tr; + idPlayer *player = static_cast( owner.GetEntity() ); + idVec3 start = player->GetEyePosition(); + idVec3 end = start + player->viewAxis[0] * 1000.0f; + gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() ); + if ( tr.fraction < 1.0f ) { + enemy = gameLocal.GetTraceEntity( tr ); + } + // ignore actors on the player's team + if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast( enemy.GetEntity() )->team == player->team ) ) { + enemy = player->EnemyWithMostHealth(); + } + } + } + const idVec3 &vel = physicsObj.GetLinearVelocity(); + angles = vel.ToAngles(); + speed = vel.Length(); + rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); + turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched; + clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); + burstMode = spawnArgs.GetBool( "burstMode" ); + unGuided = false; + burstDist = spawnArgs.GetFloat( "burstDist", "64" ); + burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" ); + UpdateVisuals(); +} + +void idGuidedProjectile::SetEnemy( idEntity *ent ) { + enemy = ent; +} +void idGuidedProjectile::Event_SetEnemy(idEntity *ent) { + SetEnemy(ent); +} + +/* +=============================================================================== + +idSoulCubeMissile + +=============================================================================== +*/ + +CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile ) +END_CLASS + +/* +================ +idSoulCubeMissile::Spawn() +================ +*/ +void idSoulCubeMissile::Spawn() { + startingVelocity.Zero(); + endingVelocity.Zero(); + accelTime = 0.0f; + launchTime = 0; + killPhase = false; + returnPhase = false; + smokeKillTime = 0; + smokeKill = NULL; +} + +/* +================= +idSoulCubeMissile::~idSoulCubeMissile +================= +*/ +idSoulCubeMissile::~idSoulCubeMissile() { +} + +/* +================ +idSoulCubeMissile::Save +================ +*/ +void idSoulCubeMissile::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( startingVelocity ); + savefile->WriteVec3( endingVelocity ); + savefile->WriteFloat( accelTime ); + savefile->WriteInt( launchTime ); + savefile->WriteBool( killPhase ); + savefile->WriteBool( returnPhase ); + savefile->WriteVec3( destOrg); + savefile->WriteInt( orbitTime ); + savefile->WriteVec3( orbitOrg ); + savefile->WriteInt( smokeKillTime ); + savefile->WriteParticle( smokeKill ); +} + +/* +================ +idSoulCubeMissile::Restore +================ +*/ +void idSoulCubeMissile::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( startingVelocity ); + savefile->ReadVec3( endingVelocity ); + savefile->ReadFloat( accelTime ); + savefile->ReadInt( launchTime ); + savefile->ReadBool( killPhase ); + savefile->ReadBool( returnPhase ); + savefile->ReadVec3( destOrg); + savefile->ReadInt( orbitTime ); + savefile->ReadVec3( orbitOrg ); + savefile->ReadInt( smokeKillTime ); + savefile->ReadParticle( smokeKill ); +} + +/* +================ +idSoulCubeMissile::KillTarget +================ +*/ +void idSoulCubeMissile::KillTarget( const idVec3 &dir ) { + idEntity *ownerEnt; + const char *smokeName; + idActor *act; + + ReturnToOwner(); + if ( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) { + act = static_cast( enemy.GetEntity() ); + killPhase = true; + orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter(); + orbitTime = gameLocal.time; + smokeKillTime = 0; + smokeName = spawnArgs.GetString( "smoke_kill" ); + if ( *smokeName != '\0' ) { + smokeKill = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeKillTime = gameLocal.time; + } + ownerEnt = owner.GetEntity(); + if ( ( act->health > 0 ) && ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) { + static_cast( ownerEnt )->GiveHealthPool( act->health ); + } + act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT ); + act->GetAFPhysics()->SetTimeScale( 0.25 ); + StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +================ +idSoulCubeMissile::Think +================ +*/ +void idSoulCubeMissile::Think() { + float pct; + idVec3 seekPos; + idEntity *ownerEnt; + + if ( state == LAUNCHED ) { + if ( killPhase ) { + // orbit the mob, cascading down + if ( gameLocal.time < orbitTime + 1500 ) { + if ( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity, timeGroup /*_D3XP*/ ) ) { + smokeKillTime = gameLocal.time; + } + } + } else { + if ( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) { + pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 ); + speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length(); + } + } + idGuidedProjectile::Think(); + GetSeekPos( seekPos ); + if ( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) { + if ( returnPhase ) { + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL ); + Hide(); + PostEventSec( &EV_Remove, 2.0f ); + + ownerEnt = owner.GetEntity(); + if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { + static_cast( ownerEnt )->SetSoulCubeProjectile( NULL ); + } + + state = FIZZLED; + } else if ( !killPhase ){ + KillTarget( physicsObj.GetAxis()[0] ); + } + } + } +} + +/* +================ +idSoulCubeMissile::GetSeekPos +================ +*/ +void idSoulCubeMissile::GetSeekPos( idVec3 &out ) { + if ( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) { + idActor *act = static_cast( owner.GetEntity() ); + out = act->GetEyePosition(); + return; + } + if ( destOrg != vec3_zero ) { + out = destOrg; + return; + } + idGuidedProjectile::GetSeekPos( out ); +} + + +/* +================ +idSoulCubeMissile::Event_ReturnToOwner +================ +*/ +void idSoulCubeMissile::ReturnToOwner() { + speed *= 0.65f; + killPhase = false; + returnPhase = true; + smokeFlyTime = 0; +} + + +/* +================= +idSoulCubeMissile::Launch +================= +*/ +void idSoulCubeMissile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { + idVec3 newStart; + idVec3 offs; + idEntity *ownerEnt; + + // push it out a little + newStart = start + dir * spawnArgs.GetFloat( "launchDist" ); + offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" ); + newStart += offs; + idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); + if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) { + destOrg = start + dir * 256.0f; + } else { + destOrg.Zero(); + } + physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate + startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" ); + endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" ); + accelTime = spawnArgs.GetFloat( "accelTime", "5" ); + physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] ); + launchTime = gameLocal.time; + killPhase = false; + UpdateVisuals(); + + ownerEnt = owner.GetEntity(); + if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { + static_cast( ownerEnt )->SetSoulCubeProjectile( this ); + } + +} + + +/* +=============================================================================== + +idBFGProjectile + +=============================================================================== +*/ +const idEventDef EV_RemoveBeams( "", NULL ); + +CLASS_DECLARATION( idProjectile, idBFGProjectile ) + EVENT( EV_RemoveBeams, idBFGProjectile::Event_RemoveBeams ) +END_CLASS + + +/* +================= +idBFGProjectile::idBFGProjectile +================= +*/ +idBFGProjectile::idBFGProjectile() { + memset( &secondModel, 0, sizeof( secondModel ) ); + secondModelDefHandle = -1; + nextDamageTime = 0; +} + +/* +================= +idBFGProjectile::~idBFGProjectile +================= +*/ +idBFGProjectile::~idBFGProjectile() { + FreeBeams(); + + if ( secondModelDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef( secondModelDefHandle ); + secondModelDefHandle = -1; + } +} + +/* +================ +idBFGProjectile::Spawn +================ +*/ +void idBFGProjectile::Spawn() { + beamTargets.Clear(); + memset( &secondModel, 0, sizeof( secondModel ) ); + secondModelDefHandle = -1; + const char *temp = spawnArgs.GetString( "model_two" ); + if ( temp != NULL && *temp != NULL ) { + secondModel.hModel = renderModelManager->FindModel( temp ); + secondModel.bounds = secondModel.hModel->Bounds( &secondModel ); + secondModel.shaderParms[ SHADERPARM_RED ] = + secondModel.shaderParms[ SHADERPARM_GREEN ] = + secondModel.shaderParms[ SHADERPARM_BLUE ] = + secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + secondModel.noSelfShadow = true; + secondModel.noShadow = true; + } + nextDamageTime = 0; + damageFreq = NULL; +} + +/* +================ +idBFGProjectile::Save +================ +*/ +void idBFGProjectile::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( beamTargets.Num() ); + for ( i = 0; i < beamTargets.Num(); i++ ) { + beamTargets[i].target.Save( savefile ); + savefile->WriteRenderEntity( beamTargets[i].renderEntity ); + savefile->WriteInt( beamTargets[i].modelDefHandle ); + } + + savefile->WriteRenderEntity( secondModel ); + savefile->WriteInt( secondModelDefHandle ); + savefile->WriteInt( nextDamageTime ); + savefile->WriteString( damageFreq ); +} + +/* +================ +idBFGProjectile::Restore +================ +*/ +void idBFGProjectile::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( num ); + beamTargets.SetNum( num ); + for ( i = 0; i < num; i++ ) { + beamTargets[i].target.Restore( savefile ); + savefile->ReadRenderEntity( beamTargets[i].renderEntity ); + savefile->ReadInt( beamTargets[i].modelDefHandle ); + + if ( beamTargets[i].modelDefHandle >= 0 ) { + beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity ); + } + } + + savefile->ReadRenderEntity( secondModel ); + savefile->ReadInt( secondModelDefHandle ); + savefile->ReadInt( nextDamageTime ); + savefile->ReadString( damageFreq ); + + if ( secondModelDefHandle >= 0 ) { + secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel ); + } +} + +/* +================= +idBFGProjectile::FreeBeams +================= +*/ +void idBFGProjectile::FreeBeams() { + for ( int i = 0; i < beamTargets.Num(); i++ ) { + if ( beamTargets[i].modelDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle ); + beamTargets[i].modelDefHandle = -1; + } + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->playerView.EnableBFGVision( false ); + } +} + +/* +================ +idBFGProjectile::Think +================ +*/ +void idBFGProjectile::Think() { + if ( state == LAUNCHED ) { + + // update beam targets + for ( int i = 0; i < beamTargets.Num(); i++ ) { + if ( beamTargets[i].target.GetEntity() == NULL ) { + continue; + } + idPlayer *player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast( beamTargets[i].target.GetEntity() ) : NULL; + // Major hack for end boss. :( + idAnimatedEntity *beamEnt; + idVec3 org; + bool forceDamage = false; + + beamEnt = static_cast(beamTargets[i].target.GetEntity()); + if ( !idStr::Cmp( beamEnt->GetEntityDefName(), "monster_boss_d3xp_maledict" ) ) { + SetTimeState ts( beamEnt->timeGroup ); + idMat3 temp; + jointHandle_t bodyJoint; + + bodyJoint = beamEnt->GetAnimator()->GetJointHandle( "Chest1" ); + beamEnt->GetJointWorldTransform( bodyJoint, gameLocal.time, org, temp ); + + forceDamage = true; + } else { + org = beamEnt->GetPhysics()->GetAbsBounds().GetCenter(); + } + beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin(); + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x; + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y; + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z; + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + if ( gameLocal.time > nextDamageTime ) { + bool bfgVision = true; + if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && ( forceDamage || beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) ) { + org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + org.Normalize(); + beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT ); + } else { + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = + beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f; + bfgVision = false; + } + if ( player ) { + player->playerView.EnableBFGVision( bfgVision ); + } + nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY; + } + gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity ); + } + + if ( secondModelDefHandle >= 0 ) { + secondModel.origin = GetPhysics()->GetOrigin(); + gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel ); + } + + idAngles ang; + + ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; + ang.yaw = ang.pitch; + ang.roll = 0.0f; + SetAngles( ang ); + + ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f; + ang.yaw = ang.pitch; + ang.roll = 0.0f; + secondModel.axis = ang.ToMat3(); + + UpdateVisuals(); + } + + idProjectile::Think(); +} + +/* +================= +idBFGProjectile::Launch +================= +*/ +void idBFGProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower ); + + // dmgPower * radius is the target acquisition area + // acquisition should make sure that monsters are not dormant + // which will cut down on hitting monsters not actively fighting + // but saves on the traces making sure they are visible + // damage is not applied until the projectile explodes + + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 damagePoint; + + float radius; + spawnArgs.GetFloat( "damageRadius", "512", radius ); + bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius ); + + float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" ); + const char *skin = spawnArgs.GetString( "skin_beam" ); + + memset( &secondModel, 0, sizeof( secondModel ) ); + secondModelDefHandle = -1; + const char *temp = spawnArgs.GetString( "model_two" ); + if ( temp != NULL && *temp != NULL ) { + secondModel.hModel = renderModelManager->FindModel( temp ); + secondModel.bounds = secondModel.hModel->Bounds( &secondModel ); + secondModel.shaderParms[ SHADERPARM_RED ] = + secondModel.shaderParms[ SHADERPARM_GREEN ] = + secondModel.shaderParms[ SHADERPARM_BLUE ] = + secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + secondModel.noSelfShadow = true; + secondModel.noShadow = true; + secondModel.origin = GetPhysics()->GetOrigin(); + secondModel.axis = GetPhysics()->GetAxis(); + secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel ); + } + + idVec3 delta( 15.0f, 15.0f, 15.0f ); + //physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); + + // get all entities touching the bounds + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES ); + for ( int e = 0; e < numListedEntities; e++ ) { + ent = entityList[ e ]; + assert( ent ); + + if ( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) { + continue; + } + + if ( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) { + continue; + } + + if ( ent->IsType( idPlayer::Type ) ) { + idPlayer *player = static_cast( ent ); + player->playerView.EnableBFGVision( true ); + } + + beamTarget_t bt; + memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) ); + bt.renderEntity.origin = GetPhysics()->GetOrigin(); + bt.renderEntity.axis = GetPhysics()->GetAxis(); + bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth; + bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75; + bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" ); + bt.renderEntity.callback = NULL; + bt.renderEntity.numJoints = 0; + bt.renderEntity.joints = NULL; + bt.renderEntity.bounds.Clear(); + bt.renderEntity.customSkin = declManager->FindSkin( skin ); + bt.target = ent; + bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity ); + beamTargets.Append( bt ); + } + + // Major hack for end boss. :( + idAnimatedEntity *maledict = static_cast(gameLocal.FindEntity( "monster_boss_d3xp_maledict_1" )); + + if ( maledict ) { + SetTimeState ts( maledict->timeGroup ); + + idVec3 realPoint; + idMat3 temp; + float dist; + jointHandle_t bodyJoint; + + bodyJoint = maledict->GetAnimator()->GetJointHandle( "Chest1" ); + maledict->GetJointWorldTransform( bodyJoint, gameLocal.time, realPoint, temp ); + + dist = idVec3( realPoint - GetPhysics()->GetOrigin() ).Length(); + + if ( dist < radius ) { + beamTarget_t bt; + memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) ); + bt.renderEntity.origin = GetPhysics()->GetOrigin(); + bt.renderEntity.axis = GetPhysics()->GetAxis(); + bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth; + bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75; + bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" ); + bt.renderEntity.callback = NULL; + bt.renderEntity.numJoints = 0; + bt.renderEntity.joints = NULL; + bt.renderEntity.bounds.Clear(); + bt.renderEntity.customSkin = declManager->FindSkin( skin ); + bt.target = maledict; + bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity ); + beamTargets.Append( bt ); + + numListedEntities++; + } + } + + if ( numListedEntities ) { + StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL ); + } + damageFreq = spawnArgs.GetString( "def_damageFreq" ); + nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY; + UpdateVisuals(); +} + +/* +================ +idProjectile::Event_RemoveBeams +================ +*/ +void idBFGProjectile::Event_RemoveBeams() { + FreeBeams(); + UpdateVisuals(); +} + +/* +================ +idProjectile::Explode +================ +*/ +void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) { + int i; + idVec3 dmgPoint; + idVec3 dir; + float beamWidth; + float damageScale; + const char *damage; + idPlayer * player; + idEntity * ownerEnt; + + ownerEnt = owner.GetEntity(); + if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { + player = static_cast< idPlayer * >( ownerEnt ); + } else { + player = NULL; + } + + beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" ); + damage = spawnArgs.GetString( "def_damage" ); + + for ( i = 0; i < beamTargets.Num(); i++ ) { + if ( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) { + continue; + } + + if ( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) { + continue; + } + + beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth; + + // if the hit entity takes damage + if ( damagePower ) { + damageScale = damagePower; + } else { + damageScale = 1.0f; + } + + // if the projectile owner is a player + if ( player ) { + // if the projectile hit an actor + if ( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) { + player->SetLastHitTime( gameLocal.time ); + player->AddProjectileHits( 1 ); + damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); + } + } + + if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) { + dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT ); + } + } + + if ( secondModelDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef( secondModelDefHandle ); + secondModelDefHandle = -1; + } + + if ( ignore == NULL ) { + projectileFlags.noSplashDamage = true; + } + + if ( !common->IsClient() || fl.skipReplication ) { + if ( ignore != NULL ) { + PostEventMS( &EV_RemoveBeams, 750 ); + } else { + PostEventMS( &EV_RemoveBeams, 0 ); + } + } + + return idProjectile::Explode( collision, ignore ); +} + + +/* +=============================================================================== + + idDebris + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idDebris ) +EVENT( EV_Explode, idDebris::Event_Explode ) +EVENT( EV_Fizzle, idDebris::Event_Fizzle ) +END_CLASS + +/* +================ +idDebris::Spawn +================ +*/ +void idDebris::Spawn() { + owner = NULL; + smokeFly = NULL; + smokeFlyTime = 0; +} + +/* +================ +idDebris::Create +================ +*/ +void idDebris::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { + Unbind(); + GetPhysics()->SetOrigin( start ); + GetPhysics()->SetAxis( axis ); + GetPhysics()->SetContents( 0 ); + this->owner = owner; + smokeFly = NULL; + smokeFlyTime = 0; + sndBounce = NULL; + noGrab = true; + UpdateVisuals(); +} + +/* +================= +idDebris::idDebris +================= +*/ +idDebris::idDebris() { + owner = NULL; + smokeFly = NULL; + smokeFlyTime = 0; + sndBounce = NULL; +} + +/* +================= +idDebris::~idDebris +================= +*/ +idDebris::~idDebris() { +} + +/* +================= +idDebris::Save +================= +*/ +void idDebris::Save( idSaveGame *savefile ) const { + owner.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteParticle( smokeFly ); + savefile->WriteInt( smokeFlyTime ); + savefile->WriteSoundShader( sndBounce ); +} + +/* +================= +idDebris::Restore +================= +*/ +void idDebris::Restore( idRestoreGame *savefile ) { + owner.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadParticle( smokeFly ); + savefile->ReadInt( smokeFlyTime ); + savefile->ReadSoundShader( sndBounce ); +} + +/* +================= +idDebris::Launch +================= +*/ +void idDebris::Launch() { + float fuse; + idVec3 velocity; + idAngles angular_velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + idVec3 gravVec; + bool randomVelocity; + idMat3 axis; + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + angular_friction = spawnArgs.GetFloat( "angular_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + fuse = spawnArgs.GetFloat( "fuse" ); + randomVelocity = spawnArgs.GetBool ( "random_velocity" ); + + if ( mass <= 0 ) { + gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); + } + + if ( randomVelocity ) { + velocity.x *= gameLocal.random.RandomFloat() + 0.5f; + velocity.y *= gameLocal.random.RandomFloat() + 0.5f; + velocity.z *= gameLocal.random.RandomFloat() + 0.5f; + } + + if ( health ) { + fl.takedamage = true; + } + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + axis = GetPhysics()->GetAxis(); + + Unbind(); + + physicsObj.SetSelf( this ); + + // check if a clip model is set + const char *clipModelName; + idTraceModel trm; + spawnArgs.GetString( "clipmodel", "", &clipModelName ); + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + // load the trace model + if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { + // default to a box + physicsObj.SetClipBox( renderEntity.bounds, 1.0f ); + } else { + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ), 1.0f ); + } + + physicsObj.GetClipModel()->SetOwner( owner.GetEntity() ); + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + if ( contact_friction == 0.0f ) { + physicsObj.NoContact(); + } + physicsObj.SetBouncyness( bounce ); + physicsObj.SetGravity( gravVec * gravity ); + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( axis ); + SetPhysics( &physicsObj ); + + if ( !common->IsClient() ) { + if ( fuse <= 0 ) { + // run physics for 1 second + RunPhysics(); + PostEventMS( &EV_Remove, 0 ); + } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + RunPhysics(); + PostEventSec( &EV_Explode, fuse ); + } else { + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Fizzle, fuse ); + } + } + + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); + + smokeFly = NULL; + smokeFlyTime = 0; + const char *smokeName = spawnArgs.GetString( "smoke_fly" ); + if ( *smokeName != '\0' ) { + smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeFlyTime = gameLocal.time; + gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); + } + + const char *sndName = spawnArgs.GetString( "snd_bounce" ); + if ( *sndName != '\0' ) { + sndBounce = declManager->FindSound( sndName ); + } + + UpdateVisuals(); +} + +/* +================ +idDebris::Think +================ +*/ +void idDebris::Think() { + + // run physics + RunPhysics(); + Present(); + + if ( smokeFly && smokeFlyTime ) { + if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) { + smokeFlyTime = 0; + } + } +} + +/* +================ +idDebris::Killed +================ +*/ +void idDebris::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + Explode(); + } else { + Fizzle(); + } +} + +/* +================= +idDebris::Collide +================= +*/ +bool idDebris::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( sndBounce != NULL ) { + StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL ); + } + sndBounce = NULL; + return false; +} + + +/* +================ +idDebris::Fizzle +================ +*/ +void idDebris::Fizzle() { + if ( IsHidden() ) { + // already exploded + return; + } + + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); + + // fizzle FX + const char *smokeName = spawnArgs.GetString( "smoke_fuse" ); + if ( *smokeName != '\0' ) { + smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeFlyTime = gameLocal.time; + gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + Hide(); + + if ( common->IsClient() && !fl.skipReplication ) { + return; + } + + CancelEvents( &EV_Fizzle ); + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +idDebris::Explode +================ +*/ +void idDebris::Explode() { + if ( IsHidden() ) { + // already exploded + return; + } + + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); + + Hide(); + + // these must not be "live forever" particle systems + smokeFly = NULL; + smokeFlyTime = 0; + const char *smokeName = spawnArgs.GetString( "smoke_detonate" ); + if ( *smokeName != '\0' ) { + smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeFlyTime = gameLocal.time; + gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + CancelEvents( &EV_Explode ); + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +idDebris::Event_Explode +================ +*/ +void idDebris::Event_Explode() { + Explode(); +} + +/* +================ +idDebris::Event_Fizzle +================ +*/ +void idDebris::Event_Fizzle() { + Fizzle(); +} + +/* +=============================================================================== + + idHomingProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, idHomingProjectile ) + EVENT( EV_SetEnemy, idHomingProjectile::Event_SetEnemy ) +END_CLASS + +/* +================ +idHomingProjectile::idHomingProjectile +================ +*/ +idHomingProjectile::idHomingProjectile() { + enemy = NULL; + speed = 0.0f; + turn_max = 0.0f; + clamp_dist = 0.0f; + rndScale = ang_zero; + rndAng = ang_zero; + angles = ang_zero; + burstMode = false; + burstDist = 0; + burstVelocity = 0.0f; + unGuided = false; + seekPos = vec3_origin; +} + +/* +================= +idHomingProjectile::~idHomingProjectile +================= +*/ +idHomingProjectile::~idHomingProjectile() { +} + +/* +================ +idHomingProjectile::Spawn +================ +*/ +void idHomingProjectile::Spawn() { +} + +/* +================ +idHomingProjectile::Save +================ +*/ +void idHomingProjectile::Save( idSaveGame *savefile ) const { + enemy.Save( savefile ); + savefile->WriteFloat( speed ); + savefile->WriteAngles( rndScale ); + savefile->WriteAngles( rndAng ); + savefile->WriteFloat( turn_max ); + savefile->WriteFloat( clamp_dist ); + savefile->WriteAngles( angles ); + savefile->WriteBool( burstMode ); + savefile->WriteBool( unGuided ); + savefile->WriteFloat( burstDist ); + savefile->WriteFloat( burstVelocity ); + savefile->WriteVec3( seekPos ); +} + +/* +================ +idHomingProjectile::Restore +================ +*/ +void idHomingProjectile::Restore( idRestoreGame *savefile ) { + enemy.Restore( savefile ); + savefile->ReadFloat( speed ); + savefile->ReadAngles( rndScale ); + savefile->ReadAngles( rndAng ); + savefile->ReadFloat( turn_max ); + savefile->ReadFloat( clamp_dist ); + savefile->ReadAngles( angles ); + savefile->ReadBool( burstMode ); + savefile->ReadBool( unGuided ); + savefile->ReadFloat( burstDist ); + savefile->ReadFloat( burstVelocity ); + savefile->ReadVec3( seekPos ); +} + + +/* +================ +idHomingProjectile::Think +================ +*/ +void idHomingProjectile::Think() { + if ( seekPos == vec3_zero ) { + // ai def uses a single def_projectile .. guardian has two projectile types so when seekPos is zero, just run regular projectile + idProjectile::Think(); + return; + } + + idVec3 dir; + idVec3 velocity; + idVec3 nose; + idVec3 tmp; + idMat3 axis; + idAngles dirAng; + idAngles diff; + float dist; + float frac; + int i; + + + nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0]; + + dir = seekPos - nose; + dist = dir.Normalize(); + dirAng = dir.ToAngles(); + + // make it more accurate as it gets closer + frac = ( dist * 2.0f ) / clamp_dist; + if ( frac > 1.0f ) { + frac = 1.0f; + } + + diff = dirAng - angles * frac; + + // clamp the to the max turn rate + diff.Normalize180(); + for( i = 0; i < 3; i++ ) { + if ( diff[ i ] > turn_max ) { + diff[ i ] = turn_max; + } else if ( diff[ i ] < -turn_max ) { + diff[ i ] = -turn_max; + } + } + angles += diff; + + // make the visual model always points the dir we're traveling + dir = angles.ToForward(); + velocity = dir * speed; + + if ( burstMode && dist < burstDist ) { + unGuided = true; + velocity *= burstVelocity; + } + + physicsObj.SetLinearVelocity( velocity ); + + // align z-axis of model with the direction + axis = dir.ToMat3(); + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + GetPhysics()->SetAxis( axis ); + + idProjectile::Think(); +} + +/* +================= +idHomingProjectile::Launch +================= +*/ +void idHomingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); + if ( owner.GetEntity() ) { + if ( owner.GetEntity()->IsType( idAI::Type ) ) { + enemy = static_cast( owner.GetEntity() )->GetEnemy(); + } else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) { + trace_t tr; + idPlayer *player = static_cast( owner.GetEntity() ); + idVec3 start = player->GetEyePosition(); + idVec3 end = start + player->viewAxis[0] * 1000.0f; + gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() ); + if ( tr.fraction < 1.0f ) { + enemy = gameLocal.GetTraceEntity( tr ); + } + // ignore actors on the player's team + if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast( enemy.GetEntity() )->team == player->team ) ) { + enemy = player->EnemyWithMostHealth(); + } + } + } + const idVec3 &vel = physicsObj.GetLinearVelocity(); + angles = vel.ToAngles(); + speed = vel.Length(); + rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); + turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched; + clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); + burstMode = spawnArgs.GetBool( "burstMode" ); + unGuided = false; + burstDist = spawnArgs.GetFloat( "burstDist", "64" ); + burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" ); + UpdateVisuals(); +} + +void idHomingProjectile::SetEnemy( idEntity *ent ) { + enemy = ent; +} +void idHomingProjectile::SetSeekPos( idVec3 pos ) { + seekPos = pos; +} +void idHomingProjectile::Event_SetEnemy(idEntity *ent) { + SetEnemy(ent); +} diff --git a/neo/d3xp/Projectile.h b/neo/d3xp/Projectile.h new file mode 100644 index 00000000..2948032d --- /dev/null +++ b/neo/d3xp/Projectile.h @@ -0,0 +1,339 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_PROJECTILE_H__ +#define __GAME_PROJECTILE_H__ + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_Explode; + +class idProjectile : public idEntity { +public : + CLASS_PROTOTYPE( idProjectile ); + + idProjectile(); + virtual ~idProjectile(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + virtual void FreeLightDef(); + + idEntity * GetOwner() const; + void CatchProjectile( idEntity* o, const char* reflectName ); + int GetProjectileState(); + void Event_CreateProjectile( idEntity *owner, const idVec3 &start, const idVec3 &dir ); + void Event_LaunchProjectile( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity ); + void Event_SetGravity( float gravity ); + + virtual void Think(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Explode( const trace_t &collision, idEntity *ignore ); + void Fizzle(); + + static idVec3 GetVelocity( const idDict *projectile ); + static idVec3 GetGravity( const idDict *projectile ); + + enum { + EVENT_DAMAGE_EFFECT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + void SetLaunchedFromGrabber( bool bl ) { launchedFromGrabber = bl; } + bool GetLaunchedFromGrabber() { return launchedFromGrabber; } + + static void DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ); + static bool ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ); + virtual void ClientPredictionThink(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + void QueueToSimulate( int startTime ); + virtual void SimulateProjectileFrame( int msec, int endTime ); + virtual void PostSimulate( int endTime ); + + struct simulatedProjectile_t { + simulatedProjectile_t(): projectile( NULL ), startTime( 0 ) {} + idProjectile* projectile; + int startTime; + }; + + static const int MAX_SIMULATED_PROJECTILES = 64; + + // This list is used to "catch up" client projectiles to the current time on the server + static idArray< simulatedProjectile_t, MAX_SIMULATED_PROJECTILES > projectilesToSimulate; + +protected: + idEntityPtr owner; + + struct projectileFlags_s { + bool detonate_on_world : 1; + bool detonate_on_actor : 1; + bool randomShaderSpin : 1; + bool isTracer : 1; + bool noSplashDamage : 1; + } projectileFlags; + + bool launchedFromGrabber; + + float thrust; + int thrust_end; + float damagePower; + + renderLight_t renderLight; + qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + idForce_Constant thruster; + idPhysics_RigidBody physicsObj; + + const idDeclParticle * smokeFly; + int smokeFlyTime; + bool mNoExplodeDisappear; + bool mTouchTriggers; + + int originalTimeGroup; + + typedef enum { + // must update these in script/doom_defs.script if changed + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4 + } projectileState_t; + + projectileState_t state; + +private: + + idVec3 launchOrigin; + idMat3 launchAxis; + + void AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ); + void AddParticlesAndLight(); + + void Event_Explode(); + void Event_Fizzle(); + void Event_RadiusDamage( idEntity *ignore ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_GetProjectileState(); +}; + +class idGuidedProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idGuidedProjectile ); + + idGuidedProjectile(); + ~idGuidedProjectile(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual void Think(); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + void SetEnemy( idEntity *ent ); + void Event_SetEnemy(idEntity *ent); + +protected: + float speed; + idEntityPtr enemy; + virtual void GetSeekPos( idVec3 &out ); + +private: + idAngles rndScale; + idAngles rndAng; + idAngles angles; + int rndUpdateTime; + float turn_max; + float clamp_dist; + bool burstMode; + bool unGuided; + float burstDist; + float burstVelocity; +}; + +class idSoulCubeMissile : public idGuidedProjectile { +public: + CLASS_PROTOTYPE ( idSoulCubeMissile ); + ~idSoulCubeMissile(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual void Think(); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float power = 1.0f, const float dmgPower = 1.0f ); + +protected: + virtual void GetSeekPos( idVec3 &out ); + void ReturnToOwner(); + void KillTarget( const idVec3 &dir ); + +private: + idVec3 startingVelocity; + idVec3 endingVelocity; + float accelTime; + int launchTime; + bool killPhase; + bool returnPhase; + idVec3 destOrg; + idVec3 orbitOrg; + int orbitTime; + int smokeKillTime; + const idDeclParticle * smokeKill; +}; + +struct beamTarget_t { + idEntityPtr target; + renderEntity_t renderEntity; + qhandle_t modelDefHandle; +}; + +class idBFGProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idBFGProjectile ); + + idBFGProjectile(); + ~idBFGProjectile(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual void Think(); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + virtual void Explode( const trace_t &collision, idEntity *ignore ); + +private: + idList beamTargets; + renderEntity_t secondModel; + qhandle_t secondModelDefHandle; + int nextDamageTime; + idStr damageFreq; + + void FreeBeams(); + void Event_RemoveBeams(); + void ApplyDamage(); +}; + +class idHomingProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idHomingProjectile ); + + idHomingProjectile(); + ~idHomingProjectile(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual void Think(); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + void SetEnemy( idEntity *ent ); + void SetSeekPos( idVec3 pos ); + void Event_SetEnemy(idEntity *ent); + +protected: + float speed; + idEntityPtr enemy; + idVec3 seekPos; + +private: + idAngles rndScale; + idAngles rndAng; + idAngles angles; + float turn_max; + float clamp_dist; + bool burstMode; + bool unGuided; + float burstDist; + float burstVelocity; +}; + + +/* +=============================================================================== + + idDebris + +=============================================================================== +*/ + +class idDebris : public idEntity { +public : + CLASS_PROTOTYPE( idDebris ); + + idDebris(); + ~idDebris(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn(); + + void Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ); + void Launch(); + void Think(); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Explode(); + void Fizzle(); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + +private: + idEntityPtr owner; + idPhysics_RigidBody physicsObj; + const idDeclParticle * smokeFly; + int smokeFlyTime; + const idSoundShader * sndBounce; + + + void Event_Explode(); + void Event_Fizzle(); +}; + +#endif /* !__GAME_PROJECTILE_H__ */ diff --git a/neo/d3xp/Pvs.cpp b/neo/d3xp/Pvs.cpp new file mode 100644 index 00000000..bb0a80a3 --- /dev/null +++ b/neo/d3xp/Pvs.cpp @@ -0,0 +1,1421 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define MAX_BOUNDS_AREAS 16 + + +typedef struct pvsPassage_s { + byte * canSee; // bit set for all portals that can be seen through this passage +} pvsPassage_t; + + +typedef struct pvsPortal_s { + int areaNum; // area this portal leads to + idWinding * w; // winding goes counter clockwise seen from the area this portal is part of + idBounds bounds; // winding bounds + idPlane plane; // winding plane, normal points towards the area this portal leads to + pvsPassage_t * passages; // passages to portals in the area this portal leads to + bool done; // true if pvs is calculated for this portal + byte * vis; // PVS for this portal + byte * mightSee; // used during construction +} pvsPortal_t; + + +typedef struct pvsArea_s { + int numPortals; // number of portals in this area + idBounds bounds; // bounds of the whole area + pvsPortal_t ** portals; // array with pointers to the portals of this area +} pvsArea_t; + + +typedef struct pvsStack_s { + struct pvsStack_s * next; // next stack entry + byte * mightSee; // bit set for all portals that might be visible through this passage/portal stack +} pvsStack_t; + + +/* +================ +idPVS::idPVS +================ +*/ +idPVS::idPVS() { + int i; + + numAreas = 0; + numPortals = 0; + + connectedAreas = NULL; + areaQueue = NULL; + areaPVS = NULL; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = NULL; + } + + pvsAreas = NULL; + pvsPortals = NULL; +} + +/* +================ +idPVS::~idPVS +================ +*/ +idPVS::~idPVS() { + Shutdown(); +} + +/* +================ +idPVS::GetPortalCount +================ +*/ +int idPVS::GetPortalCount() const { + int i, na, np; + + na = gameRenderWorld->NumAreas(); + np = 0; + for ( i = 0; i < na; i++ ) { + np += gameRenderWorld->NumPortalsInArea( i ); + } + return np; +} + +/* +================ +idPVS::CreatePVSData +================ +*/ +void idPVS::CreatePVSData() { + int i, j, n, cp; + exitPortal_t portal; + pvsArea_t *area; + pvsPortal_t *p, **portalPtrs; + + if ( !numPortals ) { + return; + } + + pvsPortals = new (TAG_PVS) pvsPortal_t[numPortals]; + pvsAreas = new (TAG_PVS) pvsArea_t[numAreas]; + memset( pvsAreas, 0, numAreas * sizeof( *pvsAreas ) ); + + cp = 0; + portalPtrs = new (TAG_PVS) pvsPortal_t*[numPortals]; + + for ( i = 0; i < numAreas; i++ ) { + + area = &pvsAreas[i]; + area->bounds.Clear(); + area->portals = portalPtrs + cp; + + n = gameRenderWorld->NumPortalsInArea( i ); + + for ( j = 0; j < n; j++ ) { + + portal = gameRenderWorld->GetPortal( i, j ); + + p = &pvsPortals[cp++]; + // the winding goes counter clockwise seen from this area + p->w = portal.w->Copy(); + p->areaNum = portal.areas[1]; // area[1] is always the area the portal leads to + + p->vis = new (TAG_PVS) byte[portalVisBytes]; + memset( p->vis, 0, portalVisBytes ); + p->mightSee = new (TAG_PVS) byte[portalVisBytes]; + memset( p->mightSee, 0, portalVisBytes ); + p->w->GetBounds( p->bounds ); + p->w->GetPlane( p->plane ); + // plane normal points to outside the area + p->plane = -p->plane; + // no PVS calculated for this portal yet + p->done = false; + + area->portals[area->numPortals] = p; + area->numPortals++; + + area->bounds += p->bounds; + } + } +} + +/* +================ +idPVS::DestroyPVSData +================ +*/ +void idPVS::DestroyPVSData() { + int i; + + if ( !pvsAreas ) { + return; + } + + // delete portal pointer array + delete[] pvsAreas[0].portals; + + // delete all areas + delete[] pvsAreas; + pvsAreas = NULL; + + // delete portal data + for ( i = 0; i < numPortals; i++ ) { + delete[] pvsPortals[i].vis; + delete[] pvsPortals[i].mightSee; + delete pvsPortals[i].w; + } + + // delete portals + delete[] pvsPortals; + pvsPortals = NULL; +} + +/* +================ +idPVS::FloodFrontPortalPVS_r +================ +*/ +void idPVS::FloodFrontPortalPVS_r( pvsPortal_t *portal, int areaNum ) const { + int i, n; + pvsArea_t *area; + pvsPortal_t *p; + + area = &pvsAreas[ areaNum ]; + + for ( i = 0; i < area->numPortals; i++ ) { + p = area->portals[i]; + n = p - pvsPortals; + // don't flood through if this portal is not at the front + if ( !( portal->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + continue; + } + // don't flood through if already visited this portal + if ( portal->vis[ n>>3 ] & (1 << (n&7)) ) { + continue; + } + // this portal might be visible + portal->vis[ n>>3 ] |= (1 << (n&7)); + // flood through the portal + FloodFrontPortalPVS_r( portal, p->areaNum ); + } +} + +/* +================ +idPVS::FrontPortalPVS +================ +*/ +void idPVS::FrontPortalPVS() const { + int i, j, k, n, p, side1, side2, areaSide; + pvsPortal_t *p1, *p2; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + + for ( j = 0; j < numAreas; j++ ) { + + area = &pvsAreas[j]; + + areaSide = side1 = area->bounds.PlaneSide( p1->plane ); + + // if the whole area is at the back side of the portal + if ( areaSide == PLANESIDE_BACK ) { + continue; + } + + for ( p = 0; p < area->numPortals; p++ ) { + + p2 = area->portals[p]; + + // if we the whole area is not at the front we need to check + if ( areaSide != PLANESIDE_FRONT ) { + // if the second portal is completely at the back side of the first portal + side1 = p2->bounds.PlaneSide( p1->plane ); + if ( side1 == PLANESIDE_BACK ) { + continue; + } + } + + // if the first portal is completely at the front of the second portal + side2 = p1->bounds.PlaneSide( p2->plane ); + if ( side2 == PLANESIDE_FRONT ) { + continue; + } + + // if the second portal is not completely at the front of the first portal + if ( side1 != PLANESIDE_FRONT ) { + // more accurate check + for ( k = 0; k < p2->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the front side + if ( p1->plane.Side( (*p2->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_FRONT ) { + break; + } + } + if ( k >= p2->w->GetNumPoints() ) { + continue; // second portal is at the back of the first portal + } + } + + // if the first portal is not completely at the back side of the second portal + if ( side2 != PLANESIDE_BACK ) { + // more accurate check + for ( k = 0; k < p1->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the back side + if ( p2->plane.Side( (*p1->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_BACK ) { + break; + } + } + if ( k >= p1->w->GetNumPoints() ) { + continue; // first portal is at the front of the second portal + } + } + + // the portal might be visible at the front + n = p2 - pvsPortals; + p1->mightSee[ n >> 3 ] |= 1 << (n&7); + } + } + } + + // flood the front portal pvs for all portals + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + FloodFrontPortalPVS_r( p1, p1->areaNum ); + } +} + +/* +=============== +idPVS::FloodPassagePVS_r +=============== +*/ +pvsStack_t *idPVS::FloodPassagePVS_r( pvsPortal_t *source, const pvsPortal_t *portal, pvsStack_t *prevStack ) const { + int i, j, n, m; + pvsPortal_t *p; + pvsArea_t *area; + pvsStack_t *stack; + pvsPassage_t *passage; + long *sourceVis, *passageVis, *portalVis, *mightSee, *prevMightSee, more; + + area = &pvsAreas[portal->areaNum]; + + stack = prevStack->next; + // if no next stack entry allocated + if ( !stack ) { + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + prevStack->next = stack; + } + + // check all portals for flooding into other areas + for ( i = 0; i < area->numPortals; i++ ) { + + passage = &portal->passages[i]; + + // if this passage is completely empty + if ( !passage->canSee ) { + continue; + } + + p = area->portals[i]; + n = p - pvsPortals; + + // if this portal cannot be seen through our current portal/passage stack + if ( !( prevStack->mightSee[n >> 3] & (1 << (n & 7)) ) ) { + continue; + } + + // mark the portal as visible + source->vis[n >> 3] |= (1 << (n & 7)); + + // get pointers to vis data + prevMightSee = reinterpret_cast(prevStack->mightSee); + passageVis = reinterpret_cast(passage->canSee); + sourceVis = reinterpret_cast(source->vis); + mightSee = reinterpret_cast(stack->mightSee); + + more = 0; + // use the portal PVS if it has been calculated + if ( p->done ) { + portalVis = reinterpret_cast(p->vis); + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++ & *portalVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + else { + // the p->mightSee is implicitely stored in the passageVis + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + + // if nothing more can be seen + if ( !more ) { + continue; + } + + // go through the portal + stack->next = FloodPassagePVS_r( source, p, stack ); + } + + return stack; +} + +/* +=============== +idPVS::PassagePVS +=============== +*/ +void idPVS::PassagePVS() const { + int i; + pvsPortal_t *source; + pvsStack_t *stack, *s; + + // create the passages + CreatePassages(); + + // allocate first stack entry + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + + // calculate portal PVS by flooding through the passages + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + memset( source->vis, 0, portalVisBytes ); + memcpy( stack->mightSee, source->mightSee, portalVisBytes ); + FloodPassagePVS_r( source, source, stack ); + source->done = true; + } + + // free the allocated stack + for ( s = stack; s; s = stack ) { + stack = stack->next; + delete[] s; + } + + // destroy the passages + DestroyPassages(); +} + +/* +=============== +idPVS::AddPassageBoundaries +=============== +*/ +void idPVS::AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const { + int i, j, k, l; + idVec3 v1, v2, normal; + float d, dist; + bool flipTest, front; + idPlane plane; + + + // check all combinations + for ( i = 0; i < source.GetNumPoints(); i++ ) { + + l = (i + 1) % source.GetNumPoints(); + v1 = source[l].ToVec3() - source[i].ToVec3(); + + // find a vertex of pass that makes a plane that puts all of the + // vertices of pass on the front side and all of the vertices of + // source on the back side + for ( j = 0; j < pass.GetNumPoints(); j++ ) { + + v2 = pass[j].ToVec3() - source[i].ToVec3(); + + normal = v1.Cross( v2 ); + if ( normal.Normalize() < 0.01f ) { + continue; + } + dist = normal * pass[j].ToVec3(); + + // + // find out which side of the generated seperating plane has the + // source portal + // + flipTest = false; + for ( k = 0; k < source.GetNumPoints(); k++ ) { + if ( k == i || k == l ) { + continue; + } + d = source[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + // source is on the negative side, so we want all + // pass and target on the positive side + flipTest = false; + break; + } + else if ( d > ON_EPSILON ) { + // source is on the positive side, so we want all + // pass and target on the negative side + flipTest = true; + break; + } + } + if ( k == source.GetNumPoints() ) { + continue; // planar with source portal + } + + // flip the normal if the source portal is backwards + if (flipTest) { + normal = -normal; + dist = -dist; + } + + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + front = false; + for ( k = 0; k < pass.GetNumPoints(); k++ ) { + if ( k == j ) { + continue; + } + d = pass[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + break; + } + else if ( d > ON_EPSILON ) { + front = true; + } + } + if ( k < pass.GetNumPoints() ) { + continue; // points on negative side, not a seperating plane + } + if ( !front ) { + continue; // planar with seperating plane + } + + // flip the normal if we want the back side + if ( flipClip ) { + plane.SetNormal( -normal ); + plane.SetDist( -dist ); + } + else { + plane.SetNormal( normal ); + plane.SetDist( dist ); + } + + // check if the plane is already a passage boundary + for ( k = 0; k < numBounds; k++ ) { + if ( plane.Compare( bounds[k], 0.001f, 0.01f ) ) { + break; + } + } + if ( k < numBounds ) { + break; + } + + if ( numBounds >= maxBounds ) { + gameLocal.Warning( "max passage boundaries." ); + break; + } + bounds[numBounds] = plane; + numBounds++; + break; + } + } +} + +/* +================ +idPVS::CreatePassages +================ +*/ +#define MAX_PASSAGE_BOUNDS 128 + +void idPVS::CreatePassages() const { + int i, j, l, n, numBounds, front, passageMemory, byteNum, bitNum; + int sides[MAX_PASSAGE_BOUNDS]; + idPlane passageBounds[MAX_PASSAGE_BOUNDS]; + pvsPortal_t *source, *target, *p; + pvsArea_t *area; + pvsPassage_t *passage; + idFixedWinding winding; + byte canSee, mightSee, bit; + + passageMemory = 0; + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + area = &pvsAreas[source->areaNum]; + + source->passages = new (TAG_PVS) pvsPassage_t[area->numPortals]; + + for ( j = 0; j < area->numPortals; j++ ) { + target = area->portals[j]; + n = target - pvsPortals; + + passage = &source->passages[j]; + + // if the source portal cannot see this portal + if ( !( source->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + // not all portals in the area have to be visible because areas are not necesarily convex + // also no passage has to be created for the portal which is the opposite of the source + passage->canSee = NULL; + continue; + } + + passage->canSee = new (TAG_PVS) byte[portalVisBytes]; + passageMemory += portalVisBytes; + + // boundary plane normals point inwards + numBounds = 0; + AddPassageBoundaries( *(source->w), *(target->w), false, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + AddPassageBoundaries( *(target->w), *(source->w), true, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + + // get all portals visible through this passage + for ( byteNum = 0; byteNum < portalVisBytes; byteNum++) { + + canSee = 0; + mightSee = source->mightSee[byteNum] & target->mightSee[byteNum]; + + // go through eight portals at a time to speed things up + for ( bitNum = 0; bitNum < 8; bitNum++ ) { + + bit = 1 << bitNum; + + if ( !( mightSee & bit ) ) { + continue; + } + + p = &pvsPortals[(byteNum << 3) + bitNum]; + + if ( p->areaNum == source->areaNum ) { + continue; + } + + for ( front = 0, l = 0; l < numBounds; l++ ) { + sides[l] = p->bounds.PlaneSide( passageBounds[l] ); + // if completely at the back of the passage bounding plane + if ( sides[l] == PLANESIDE_BACK ) { + break; + } + // if completely at the front + if ( sides[l] == PLANESIDE_FRONT ) { + front++; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + + // if not at the front of all bounding planes and thus not completely inside the passage + if ( front != numBounds ) { + + winding = *p->w; + + for ( l = 0; l < numBounds; l++ ) { + // only clip if the winding possibly crosses this plane + if ( sides[l] != PLANESIDE_CROSS ) { + continue; + } + // clip away the part at the back of the bounding plane + winding.ClipInPlace( passageBounds[l] ); + // if completely clipped away + if ( !winding.GetNumPoints() ) { + break; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + } + + canSee |= bit; + } + + // store results of all eight portals + passage->canSee[byteNum] = canSee; + } + + // can always see the target portal + passage->canSee[n >> 3] |= (1 << (n&7)); + } + } + if ( passageMemory < 1024 ) { + gameLocal.Printf( "%5d bytes passage memory used to build PVS\n", passageMemory ); + } + else { + gameLocal.Printf( "%5d KB passage memory used to build PVS\n", passageMemory>>10 ); + } +} + +/* +================ +idPVS::DestroyPassages +================ +*/ +void idPVS::DestroyPassages() const { + int i, j; + pvsPortal_t *p; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; + area = &pvsAreas[p->areaNum]; + for ( j = 0; j < area->numPortals; j++ ) { + if ( p->passages[j].canSee ) { + delete[] p->passages[j].canSee; + } + } + delete[] p->passages; + } +} + +/* +================ +idPVS::CopyPortalPVSToMightSee +================ +*/ +void idPVS::CopyPortalPVSToMightSee() const { + int i; + pvsPortal_t *p; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; + memcpy( p->mightSee, p->vis, portalVisBytes ); + } +} + +/* +================ +idPVS::AreaPVSFromPortalPVS +================ +*/ +int idPVS::AreaPVSFromPortalPVS() const { + int i, j, k, areaNum, totalVisibleAreas; + long *p1, *p2; + byte *pvs, *portalPVS; + pvsArea_t *area; + + totalVisibleAreas = 0; + + if ( !numPortals ) { + return totalVisibleAreas; + } + + memset( areaPVS, 0, numAreas * areaVisBytes ); + + for ( i = 0; i < numAreas; i++ ) { + area = &pvsAreas[i]; + pvs = areaPVS + i * areaVisBytes; + + // the area is visible to itself + pvs[ i >> 3 ] |= 1 << (i & 7); + + if ( !area->numPortals ) { + continue; + } + + // store the PVS of all portals in this area at the first portal + for ( j = 1; j < area->numPortals; j++ ) { + p1 = reinterpret_cast(area->portals[0]->vis); + p2 = reinterpret_cast(area->portals[j]->vis); + for ( k = 0; k < portalVisLongs; k++ ) { + *p1++ |= *p2++; + } + } + + // the portals of this area are always visible + for ( j = 0; j < area->numPortals; j++ ) { + k = area->portals[j] - pvsPortals; + area->portals[0]->vis[ k >> 3 ] |= 1 << (k & 7); + } + + // set all areas to visible that can be seen from the portals of this area + portalPVS = area->portals[0]->vis; + for ( j = 0; j < numPortals; j++ ) { + // if this portal is visible + if ( portalPVS[j>>3] & (1 << (j&7)) ) { + areaNum = pvsPortals[j].areaNum; + pvs[ areaNum >> 3 ] |= 1 << (areaNum & 7); + } + } + + // count the number of visible areas + for ( j = 0; j < numAreas; j++ ) { + if ( pvs[j>>3] & (1 << (j&7)) ) { + totalVisibleAreas++; + } + } + } + return totalVisibleAreas; +} + +/* +================ +idPVS::Init +================ +*/ +void idPVS::Init() { + int totalVisibleAreas; + + Shutdown(); + + numAreas = gameRenderWorld->NumAreas(); + if ( numAreas <= 0 ) { + return; + } + + connectedAreas = new (TAG_PVS) bool[numAreas]; + areaQueue = new (TAG_PVS) int[numAreas]; + + areaVisBytes = ( ((numAreas+31)&~31) >> 3); + areaVisLongs = areaVisBytes/sizeof(long); + + areaPVS = new (TAG_PVS) byte[numAreas * areaVisBytes]; + memset( areaPVS, 0xFF, numAreas * areaVisBytes ); + + numPortals = GetPortalCount(); + + portalVisBytes = ( ((numPortals+31)&~31) >> 3); + portalVisLongs = portalVisBytes/sizeof(long); + + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = new (TAG_PVS) byte[areaVisBytes]; + memset( currentPVS[i].pvs, 0, areaVisBytes ); + } + + idTimer timer; + timer.Start(); + + CreatePVSData(); + + FrontPortalPVS(); + + CopyPortalPVSToMightSee(); + + PassagePVS(); + + totalVisibleAreas = AreaPVSFromPortalPVS(); + + DestroyPVSData(); + + timer.Stop(); + + gameLocal.Printf( "%5.0f msec to calculate PVS\n", timer.Milliseconds() ); + gameLocal.Printf( "%5d areas\n", numAreas ); + gameLocal.Printf( "%5d portals\n", numPortals ); + gameLocal.Printf( "%5d areas visible on average\n", totalVisibleAreas / numAreas ); + if ( numAreas * areaVisBytes < 1024 ) { + gameLocal.Printf( "%5d bytes PVS data\n", numAreas * areaVisBytes ); + } + else { + gameLocal.Printf( "%5d KB PVS data\n", (numAreas * areaVisBytes) >> 10 ); + } +} + +/* +================ +idPVS::Shutdown +================ +*/ +void idPVS::Shutdown() { + if ( connectedAreas ) { + delete connectedAreas; + connectedAreas = NULL; + } + if ( areaQueue ) { + delete areaQueue; + areaQueue = NULL; + } + if ( areaPVS ) { + delete areaPVS; + areaPVS = NULL; + } + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { + delete currentPVS[i].pvs; + currentPVS[i].pvs = NULL; + } +} + +/* +================ +idPVS::GetConnectedAreas + + assumes the 'areas' array is initialized to false +================ +*/ +void idPVS::GetConnectedAreas( int srcArea, bool *areas ) const { + int curArea, nextArea; + int queueStart, queueEnd; + int i, n; + exitPortal_t portal; + + queueStart = -1; + queueEnd = 0; + areas[srcArea] = true; + + for ( curArea = srcArea; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + n = gameRenderWorld->NumPortalsInArea( curArea ); + + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( curArea, i ); + + if ( portal.blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // area[1] is always the area the portal leads to + nextArea = portal.areas[1]; + + // if already visited this area + if ( areas[nextArea] ) { + continue; + } + + // add area to queue + areaQueue[queueEnd++] = nextArea; + areas[nextArea] = true; + } + } +} + +/* +================ +idPVS::GetPVSArea +================ +*/ +int idPVS::GetPVSArea( const idVec3 &point ) const { + return gameRenderWorld->PointInArea( point ); +} + +/* +================ +idPVS::GetPVSAreas +================ +*/ +int idPVS::GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + return gameRenderWorld->BoundsInAreas( bounds, areas, maxAreas ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idVec3 &source, const pvsType_t type ) const { + int sourceArea; + + sourceArea = gameRenderWorld->PointInArea( source ); + + return SetupCurrentPVS( sourceArea, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idBounds &source, const pvsType_t type ) const { + int numSourceAreas, sourceAreas[MAX_BOUNDS_AREAS]; + + numSourceAreas = gameRenderWorld->BoundsInAreas( source, sourceAreas, MAX_BOUNDS_AREAS ); + + return SetupCurrentPVS( sourceAreas, numSourceAreas, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int sourceArea, const pvsType_t type ) const { + int i; + pvsHandle_t handle; + + handle = AllocCurrentPVS( *reinterpret_cast(&sourceArea) ); + + if ( sourceArea < 0 || sourceArea >= numAreas ) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { + memcpy( currentPVS[handle.i].pvs, areaPVS + sourceArea * areaVisBytes, areaVisBytes ); + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + GetConnectedAreas( sourceArea, connectedAreas ); + + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type ) const { + int i, j; + unsigned int h; + long *vis, *pvs; + pvsHandle_t handle; + + h = 0; + for ( i = 0; i < numSourceAreas; i++ ) { + h ^= *reinterpret_cast(&sourceAreas[i]); + } + handle = AllocCurrentPVS( h ); + + if ( !numSourceAreas || sourceAreas[0] < 0 || sourceAreas[0] >= numAreas) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { + // merge PVS of all areas the source is in + memcpy( currentPVS[handle.i].pvs, areaPVS + sourceAreas[0] * areaVisBytes, areaVisBytes ); + for ( i = 1; i < numSourceAreas; i++ ) { + + assert( sourceAreas[i] >= 0 && sourceAreas[i] < numAreas ); + + vis = reinterpret_cast(areaPVS + sourceAreas[i] * areaVisBytes); + pvs = reinterpret_cast(currentPVS[handle.i].pvs); + for ( j = 0; j < areaVisLongs; j++ ) { + *pvs++ |= *vis++; + } + } + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + // get all areas connected to any of the source areas + for ( i = 0; i < numSourceAreas; i++ ) { + if ( !connectedAreas[sourceAreas[i]] ) { + GetConnectedAreas( sourceAreas[i], connectedAreas ); + } + } + + // remove unconnected areas from the PVS + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::MergeCurrentPVS +================ +*/ +pvsHandle_t idPVS::MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const { + int i; + long *pvs1Ptr, *pvs2Ptr, *ptr; + pvsHandle_t handle = { 0 }; + + if ( pvs1.i < 0 || pvs1.i >= MAX_CURRENT_PVS || pvs1.h != currentPVS[pvs1.i].handle.h || + pvs2.i < 0 || pvs2.i >= MAX_CURRENT_PVS || pvs2.h != currentPVS[pvs2.i].handle.h ) { + gameLocal.Error( "idPVS::MergeCurrentPVS: invalid handle" ); + return handle; + } + + handle = AllocCurrentPVS( pvs1.h ^ pvs2.h ); + + ptr = reinterpret_cast(currentPVS[handle.i].pvs); + pvs1Ptr = reinterpret_cast(currentPVS[pvs1.i].pvs); + pvs2Ptr = reinterpret_cast(currentPVS[pvs2.i].pvs); + + for ( i = 0; i < areaVisLongs; i++ ) { + *ptr++ = *pvs1Ptr++ | *pvs2Ptr++; + } + + return handle; +} + +/* +================ +idPVS::AllocCurrentPVS +================ +*/ +pvsHandle_t idPVS::AllocCurrentPVS( unsigned int h ) const { + int i; + pvsHandle_t handle; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + if ( currentPVS[i].handle.i == -1 ) { + currentPVS[i].handle.i = i; + currentPVS[i].handle.h = h; + return currentPVS[i].handle; + } + } + + gameLocal.Error( "idPVS::AllocCurrentPVS: no free PVS left" ); + + handle.i = -1; + handle.h = 0; + return handle; +} + +/* +================ +idPVS::FreeCurrentPVS +================ +*/ +void idPVS::FreeCurrentPVS( pvsHandle_t handle ) const { + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::FreeCurrentPVS: invalid handle" ); + return; + } + currentPVS[handle.i].handle.i = -1; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const { + int targetArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Warning( "idPVS::InCurrentPVS: invalid handle" ); + return false; + } + + targetArea = gameRenderWorld->PointInArea( target ); + + if ( targetArea == -1 ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const { + int i, numTargetAreas, targetAreas[MAX_BOUNDS_AREAS]; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Warning( "idPVS::InCurrentPVS: invalid handle" ); + return false; + } + + numTargetAreas = gameRenderWorld->BoundsInAreas( target, targetAreas, MAX_BOUNDS_AREAS ); + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const { + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Warning( "idPVS::InCurrentPVS: invalid handle" ); + return false; + } + + if ( targetArea < 0 || targetArea >= numAreas ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const { + int i; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Warning( "idPVS::InCurrentPVS: invalid handle" ); + return false; + } + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( targetAreas[i] < 0 || targetAreas[i] >= numAreas ) { + continue; + } + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idVec3 &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idBounds &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, num, areas[MAX_BOUNDS_AREAS]; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + num = gameRenderWorld->BoundsInAreas( source, areas, MAX_BOUNDS_AREAS ); + + if ( !num ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + for ( i = 0; i < num; i++ ) { + if ( j == areas[i] ) { + break; + } + } + if ( i < num ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::DrawCurrentPVS: invalid handle" ); + return; + } + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } +} + +/* +================ +idPVS::CheckAreasForPortalSky +================ +*/ +bool idPVS::CheckAreasForPortalSky( const pvsHandle_t handle, const idVec3 &origin ) { + int j, sourceArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + return false; + } + + sourceArea = gameRenderWorld->PointInArea( origin ); + + if ( sourceArea == -1 ) { + return false; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( gameRenderWorld->CheckAreaForPortalSky( j ) ) { + return true; + } + } + + return false; +} diff --git a/neo/d3xp/Pvs.h b/neo/d3xp/Pvs.h new file mode 100644 index 00000000..a33da014 --- /dev/null +++ b/neo/d3xp/Pvs.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_PVS_H__ +#define __GAME_PVS_H__ + +/* +=================================================================================== + + PVS + + Note: mirrors and other special view portals are not taken into account + +=================================================================================== +*/ + + +typedef struct pvsHandle_s { + int i; // index to current pvs + unsigned int h; // handle for current pvs +} pvsHandle_t; + + +typedef struct pvsCurrent_s { + pvsHandle_t handle; // current pvs handle + byte * pvs; // current pvs bit string +} pvsCurrent_t; + +#define MAX_CURRENT_PVS 64 // must be a power of 2 + +typedef enum { + PVS_NORMAL = 0, // PVS through portals taking portal states into account + PVS_ALL_PORTALS_OPEN = 1, // PVS through portals assuming all portals are open + PVS_CONNECTED_AREAS = 2 // PVS considering all topologically connected areas visible +} pvsType_t; + + +class idPVS { +public: + idPVS(); + ~idPVS(); + // setup for the current map + void Init(); + void Shutdown(); + // get the area(s) the source is in + int GetPVSArea( const idVec3 &point ) const; // returns the area number + int GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const; // returns number of areas + // setup current PVS for the source + pvsHandle_t SetupCurrentPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int sourceArea, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const; + void FreeCurrentPVS( pvsHandle_t handle ) const; + // returns true if the target is within the current PVS + bool InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const; + // draw all portals that are within the PVS of the source + void DrawPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + void DrawPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + // visualize the PVS the handle points to + void DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const; + + bool CheckAreasForPortalSky( const pvsHandle_t handle, const idVec3 &origin ); + +private: + int numAreas; + int numPortals; + bool * connectedAreas; + int * areaQueue; + byte * areaPVS; + // current PVS for a specific source possibly taking portal states (open/closed) into account + mutable pvsCurrent_t currentPVS[MAX_CURRENT_PVS]; + // used to create PVS + int portalVisBytes; + int portalVisLongs; + int areaVisBytes; + int areaVisLongs; + struct pvsPortal_s *pvsPortals; + struct pvsArea_s * pvsAreas; + +private: + int GetPortalCount() const; + void CreatePVSData(); + void DestroyPVSData(); + void CopyPortalPVSToMightSee() const; + void FloodFrontPortalPVS_r( struct pvsPortal_s *portal, int areaNum ) const; + void FrontPortalPVS() const; + struct pvsStack_s * FloodPassagePVS_r( struct pvsPortal_s *source, const struct pvsPortal_s *portal, struct pvsStack_s *prevStack ) const; + void PassagePVS() const; + void AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const; + void CreatePassages() const; + void DestroyPassages() const; + int AreaPVSFromPortalPVS() const; + void GetConnectedAreas( int srcArea, bool *connectedAreas ) const; + pvsHandle_t AllocCurrentPVS( unsigned int h ) const; +}; + +#endif /* !__GAME_PVS_H__ */ diff --git a/neo/d3xp/SecurityCamera.cpp b/neo/d3xp/SecurityCamera.cpp new file mode 100644 index 00000000..9d42cc35 --- /dev/null +++ b/neo/d3xp/SecurityCamera.cpp @@ -0,0 +1,587 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* + + SecurityCamera.cpp + + Security camera that triggers targets when player is in view + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/*********************************************************************** + + idSecurityCamera + +***********************************************************************/ + +const idEventDef EV_SecurityCam_ReverseSweep( "" ); +const idEventDef EV_SecurityCam_ContinueSweep( "" ); +const idEventDef EV_SecurityCam_Pause( "" ); +const idEventDef EV_SecurityCam_Alert( "" ); +const idEventDef EV_SecurityCam_AddLight( "" ); + +CLASS_DECLARATION( idEntity, idSecurityCamera ) + EVENT( EV_SecurityCam_ReverseSweep, idSecurityCamera::Event_ReverseSweep ) + EVENT( EV_SecurityCam_ContinueSweep, idSecurityCamera::Event_ContinueSweep ) + EVENT( EV_SecurityCam_Pause, idSecurityCamera::Event_Pause ) + EVENT( EV_SecurityCam_Alert, idSecurityCamera::Event_Alert ) + EVENT( EV_SecurityCam_AddLight, idSecurityCamera::Event_AddLight ) +END_CLASS + +/* +================ +idSecurityCamera::Save +================ +*/ +void idSecurityCamera::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( angle ); + savefile->WriteFloat( sweepAngle ); + savefile->WriteInt( modelAxis ); + savefile->WriteBool( flipAxis ); + savefile->WriteFloat( scanDist ); + savefile->WriteFloat( scanFov ); + + savefile->WriteFloat( sweepStart ); + savefile->WriteFloat( sweepEnd ); + savefile->WriteBool( negativeSweep ); + savefile->WriteBool( sweeping ); + savefile->WriteInt( alertMode ); + savefile->WriteFloat( stopSweeping ); + savefile->WriteFloat( scanFovCos ); + + savefile->WriteVec3( viewOffset ); + + savefile->WriteInt( pvsArea ); + savefile->WriteStaticObject( physicsObj ); + savefile->WriteTraceModel( trm ); +} + +/* +================ +idSecurityCamera::Restore +================ +*/ +void idSecurityCamera::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( angle ); + savefile->ReadFloat( sweepAngle ); + savefile->ReadInt( modelAxis ); + savefile->ReadBool( flipAxis ); + savefile->ReadFloat( scanDist ); + savefile->ReadFloat( scanFov ); + + savefile->ReadFloat( sweepStart ); + savefile->ReadFloat( sweepEnd ); + savefile->ReadBool( negativeSweep ); + savefile->ReadBool( sweeping ); + savefile->ReadInt( alertMode ); + savefile->ReadFloat( stopSweeping ); + savefile->ReadFloat( scanFovCos ); + + savefile->ReadVec3( viewOffset ); + + savefile->ReadInt( pvsArea ); + savefile->ReadStaticObject( physicsObj ); + savefile->ReadTraceModel( trm ); +} + +/* +================ +idSecurityCamera::Spawn +================ +*/ +void idSecurityCamera::Spawn() { + idStr str; + + sweepAngle = spawnArgs.GetFloat( "sweepAngle", "90" ); + health = spawnArgs.GetInt( "health", "100" ); + scanFov = spawnArgs.GetFloat( "scanFov", "90" ); + scanDist = spawnArgs.GetFloat( "scanDist", "200" ); + flipAxis = spawnArgs.GetBool( "flipAxis" ); + + modelAxis = spawnArgs.GetInt( "modelAxis" ); + if ( modelAxis < 0 || modelAxis > 2 ) { + modelAxis = 0; + } + + spawnArgs.GetVector( "viewOffset", "0 0 0", viewOffset ); + + if ( spawnArgs.GetBool( "spotLight" ) ) { + PostEventMS( &EV_SecurityCam_AddLight, 0 ); + } + + negativeSweep = ( sweepAngle < 0 ) ? true : false; + sweepAngle = abs( sweepAngle ); + + scanFovCos = cos( scanFov * idMath::PI / 360.0f ); + + angle = GetPhysics()->GetAxis().ToAngles().yaw; + StartSweep(); + SetAlertMode( SCANNING ); + BecomeActive( TH_THINK ); + + if ( health ) { + fl.takedamage = true; + } + + pvsArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + // if no target specified use ourself + str = spawnArgs.GetString( "cameraTarget" ); + if ( str.Length() == 0 ) { + spawnArgs.Set( "cameraTarget", spawnArgs.GetString( "name" ) ); + } + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", str ); + if ( !str[0] ) { + str = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( !collisionModelManager->TrmFromModel( str, trm ) ) { + gameLocal.Error( "idSecurityCamera '%s': cannot load collision model %s", name.c_str(), str.c_str() ); + return; + } + + GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + // setup the physics + UpdateChangeableSpawnArgs( NULL ); +} + +/* +================ +idSecurityCamera::Event_AddLight +================ +*/ +void idSecurityCamera::Event_AddLight() { + idDict args; + idVec3 right, up, target, temp; + idVec3 dir; + float radius; + idVec3 lightOffset; + idLight *spotLight; + + dir = GetAxis(); + dir.NormalVectors( right, up ); + target = GetPhysics()->GetOrigin() + dir * scanDist; + + radius = tan( scanFov * idMath::PI / 360.0f ); + up = dir + up * radius; + up.Normalize(); + up = GetPhysics()->GetOrigin() + up * scanDist; + up -= target; + + right = dir + right * radius; + right.Normalize(); + right = GetPhysics()->GetOrigin() + right * scanDist; + right -= target; + + spawnArgs.GetVector( "lightOffset", "0 0 0", lightOffset ); + + args.Set( "origin", ( GetPhysics()->GetOrigin() + lightOffset ).ToString() ); + args.Set( "light_target", target.ToString() ); + args.Set( "light_right", right.ToString() ); + args.Set( "light_up", up.ToString() ); + args.SetFloat( "angle", GetPhysics()->GetAxis()[0].ToYaw() ); + + spotLight = static_cast( gameLocal.SpawnEntityType( idLight::Type, &args ) ); + spotLight->Bind( this, true ); + spotLight->UpdateVisuals(); +} + +/* +================ +idSecurityCamera::DrawFov +================ +*/ +void idSecurityCamera::DrawFov() { + int i; + float radius, a, s, c, halfRadius; + idVec3 right, up; + idVec4 color(1, 0, 0, 1), color2(0, 0, 1, 1); + idVec3 lastPoint, point, lastHalfPoint, halfPoint, center; + + idVec3 dir = GetAxis(); + dir.NormalVectors( right, up ); + + radius = tan( scanFov * idMath::PI / 360.0f ); + halfRadius = radius * 0.5f; + lastPoint = dir + up * radius; + lastPoint.Normalize(); + lastPoint = GetPhysics()->GetOrigin() + lastPoint * scanDist; + lastHalfPoint = dir + up * halfRadius; + lastHalfPoint.Normalize(); + lastHalfPoint = GetPhysics()->GetOrigin() + lastHalfPoint * scanDist; + center = GetPhysics()->GetOrigin() + dir * scanDist; + for ( i = 1; i < 12; i++ ) { + a = idMath::TWO_PI * i / 12.0f; + idMath::SinCos( a, s, c ); + point = dir + right * s * radius + up * c * radius; + point.Normalize(); + point = GetPhysics()->GetOrigin() + point * scanDist; + gameRenderWorld->DebugLine( color, lastPoint, point ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), point ); + lastPoint = point; + + halfPoint = dir + right * s * halfRadius + up * c * halfRadius; + halfPoint.Normalize(); + halfPoint = GetPhysics()->GetOrigin() + halfPoint * scanDist; + gameRenderWorld->DebugLine( color2, point, halfPoint ); + gameRenderWorld->DebugLine( color2, lastHalfPoint, halfPoint ); + lastHalfPoint = halfPoint; + + gameRenderWorld->DebugLine( color2, halfPoint, center ); + } +} + +/* +================ +idSecurityCamera::GetRenderView +================ +*/ +renderView_t *idSecurityCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->fov_x = scanFov; + rv->fov_y = scanFov; + rv->viewaxis = GetAxis().ToAngles().ToMat3(); + rv->vieworg = GetPhysics()->GetOrigin() + viewOffset; + return rv; +} + +/* +================ +idSecurityCamera::CanSeePlayer +================ +*/ +bool idSecurityCamera::CanSeePlayer() { + int i; + float dist; + idPlayer *ent; + trace_t tr; + idVec3 dir; + pvsHandle_t handle; + + handle = gameLocal.pvs.SetupCurrentPVS( pvsArea ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = static_cast(gameLocal.entities[ i ]); + + if ( !ent || ( ent->fl.notarget ) ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.pvs.InCurrentPVS( handle, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dist = dir.Normalize(); + + if ( dist > scanDist ) { + continue; + } + + if ( dir * GetAxis() < scanFovCos ) { + continue; + } + + idVec3 eye; + + eye = ent->EyeOffset(); + + gameLocal.clip.TracePoint( tr, GetPhysics()->GetOrigin(), ent->GetPhysics()->GetOrigin() + eye, MASK_OPAQUE, this ); + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + gameLocal.pvs.FreeCurrentPVS( handle ); + return true; + } + } + + gameLocal.pvs.FreeCurrentPVS( handle ); + + return false; +} + +/* +================ +idSecurityCamera::SetAlertMode +================ +*/ +void idSecurityCamera::SetAlertMode( int alert ) { + if (alert >= SCANNING && alert <= ACTIVATED) { + alertMode = alert; + } + renderEntity.shaderParms[ SHADERPARM_MODE ] = alertMode; + UpdateVisuals(); +} + +/* +================ +idSecurityCamera::Think +================ +*/ +void idSecurityCamera::Think() { + float pct; + float travel; + + if ( thinkFlags & TH_THINK ) { + if ( g_showEntityInfo.GetBool() ) { + DrawFov(); + } + + if (health <= 0) { + BecomeInactive( TH_THINK ); + return; + } + } + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + if (CanSeePlayer()) { + if (alertMode == SCANNING) { + float sightTime; + + SetAlertMode(ALERT); + stopSweeping = gameLocal.time; + if (sweeping) { + CancelEvents( &EV_SecurityCam_Pause ); + } else { + CancelEvents( &EV_SecurityCam_ReverseSweep ); + } + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_sight", SND_CHANNEL_BODY, 0, false, NULL ); + + sightTime = spawnArgs.GetFloat( "sightTime", "5" ); + PostEventSec(&EV_SecurityCam_Alert, sightTime); + } + } else { + if (alertMode == ALERT) { + float sightResume; + + SetAlertMode(LOSINGINTEREST); + CancelEvents( &EV_SecurityCam_Alert ); + + sightResume = spawnArgs.GetFloat( "sightResume", "1.5" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, sightResume ); + } + + if ( sweeping ) { + idAngles a = GetPhysics()->GetAxis().ToAngles(); + + pct = ( gameLocal.time - sweepStart ) / ( sweepEnd - sweepStart ); + travel = pct * sweepAngle; + if ( negativeSweep ) { + a.yaw = angle + travel; + } else { + a.yaw = angle - travel; + } + + SetAngles( a ); + } + } + } + Present(); +} + +/* +================ +idSecurityCamera::GetAxis +================ +*/ +const idVec3 idSecurityCamera::GetAxis() const { + return (flipAxis) ? -GetPhysics()->GetAxis()[modelAxis] : GetPhysics()->GetAxis()[modelAxis]; +}; + +/* +================ +idSecurityCamera::SweepSpeed +================ +*/ +float idSecurityCamera::SweepSpeed() const { + return spawnArgs.GetFloat( "sweepSpeed", "5" ); +} + +/* +================ +idSecurityCamera::StartSweep +================ +*/ +void idSecurityCamera::StartSweep() { + int speed; + + sweeping = true; + sweepStart = gameLocal.time; + speed = SEC2MS( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed ); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idSecurityCamera::Event_ContinueSweep +================ +*/ +void idSecurityCamera::Event_ContinueSweep() { + float pct = (stopSweeping - sweepStart) / (sweepEnd - sweepStart); + float f = gameLocal.time - (sweepEnd - sweepStart) * pct; + int speed; + + sweepStart = f; + speed = MS2SEC( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed * (1.0 - pct)); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); + SetAlertMode(SCANNING); + sweeping = true; +} + +/* +================ +idSecurityCamera::Event_Alert +================ +*/ +void idSecurityCamera::Event_Alert() { + float wait; + + SetAlertMode(ACTIVATED); + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_activate", SND_CHANNEL_BODY, 0, false, NULL ); + ActivateTargets(this); + CancelEvents( &EV_SecurityCam_ContinueSweep ); + + wait = spawnArgs.GetFloat( "wait", "20" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, wait ); +} + +/* +================ +idSecurityCamera::Event_ReverseSweep +================ +*/ +void idSecurityCamera::Event_ReverseSweep() { + angle = GetPhysics()->GetAxis().ToAngles().yaw; + negativeSweep = !negativeSweep; + StartSweep(); +} + +/* +================ +idSecurityCamera::Event_Pause +================ +*/ +void idSecurityCamera::Event_Pause() { + float sweepWait; + + sweepWait = spawnArgs.GetFloat( "sweepWait", "0.5" ); + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_stop", SND_CHANNEL_BODY, 0, false, NULL ); + PostEventSec( &EV_SecurityCam_ReverseSweep, sweepWait ); +} + +/* +============ +idSecurityCamera::Killed +============ +*/ +void idSecurityCamera::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + const char *fx = spawnArgs.GetString( "fx_destroyed" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ), 0.02f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( 0.2f ); + physicsObj.SetFriction( 0.6f, 0.6f, 0.2f ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + physicsObj.DropToFloor(); +} + + +/* +============ +idSecurityCamera::Pain +============ +*/ +bool idSecurityCamera::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + const char *fx = spawnArgs.GetString( "fx_damage" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } + return true; +} + + +/* +================ +idSecurityCamera::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idSecurityCamera::Present() { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views + if ( cameraTarget ) { + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} diff --git a/neo/d3xp/SecurityCamera.h b/neo/d3xp/SecurityCamera.h new file mode 100644 index 00000000..a58d1ea3 --- /dev/null +++ b/neo/d3xp/SecurityCamera.h @@ -0,0 +1,97 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_SECURITYCAMERA_H__ +#define __GAME_SECURITYCAMERA_H__ + +/* +=================================================================================== + + Security camera + +=================================================================================== +*/ + + +class idSecurityCamera : public idEntity { +public: + CLASS_PROTOTYPE( idSecurityCamera ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + virtual renderView_t * GetRenderView(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Present(); + + +private: + + enum { SCANNING, LOSINGINTEREST, ALERT, ACTIVATED }; + + float angle; + float sweepAngle; + int modelAxis; + bool flipAxis; + float scanDist; + float scanFov; + + float sweepStart; + float sweepEnd; + bool negativeSweep; + bool sweeping; + int alertMode; + float stopSweeping; + float scanFovCos; + + idVec3 viewOffset; + + int pvsArea; + idPhysics_RigidBody physicsObj; + idTraceModel trm; + + void StartSweep(); + bool CanSeePlayer(); + void SetAlertMode( int status ); + void DrawFov(); + const idVec3 GetAxis() const; + float SweepSpeed() const; + + void Event_ReverseSweep(); + void Event_ContinueSweep(); + void Event_Pause(); + void Event_Alert(); + void Event_AddLight(); +}; + +#endif /* !__GAME_SECURITYCAMERA_H__ */ diff --git a/neo/d3xp/SmokeParticles.cpp b/neo/d3xp/SmokeParticles.cpp new file mode 100644 index 00000000..1de8dca0 --- /dev/null +++ b/neo/d3xp/SmokeParticles.cpp @@ -0,0 +1,438 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +static const char *smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_"; + +/* +================ +idSmokeParticles::idSmokeParticles +================ +*/ +idSmokeParticles::idSmokeParticles() { + initialized = false; + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntityHandle = -1; + memset( smokes, 0, sizeof( smokes ) ); + freeSmokes = NULL; + numActiveSmokes = 0; + currentParticleTime = -1; +} + +/* +================ +idSmokeParticles::Init +================ +*/ +void idSmokeParticles::Init() { + if ( initialized ) { + Shutdown(); + } + + // set up the free list + for ( int i = 0; i < MAX_SMOKE_PARTICLES-1; i++ ) { + smokes[i].next = &smokes[i+1]; + } + smokes[MAX_SMOKE_PARTICLES-1].next = NULL; + freeSmokes = &smokes[0]; + numActiveSmokes = 0; + + activeStages.Clear(); + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + + renderEntity.bounds.Clear(); + renderEntity.axis = mat3_identity; + renderEntity.shaderParms[ SHADERPARM_RED ] = 1; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1; + renderEntity.shaderParms[3] = 1; + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( smokeParticle_SnapshotName ); + + // we certainly don't want particle shadows + renderEntity.noShadow = 1; + + // huge bounds, so it will be present in every world area + renderEntity.bounds.AddPoint( idVec3(-100000, -100000, -100000) ); + renderEntity.bounds.AddPoint( idVec3( 100000, 100000, 100000) ); + + renderEntity.callback = idSmokeParticles::ModelCallback; + // add to renderer list + renderEntityHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + + currentParticleTime = -1; + + initialized = true; +} + +/* +================ +idSmokeParticles::Shutdown +================ +*/ +void idSmokeParticles::Shutdown() { + // make sure the render entity is freed before the model is freed + if ( renderEntityHandle != -1 ) { + gameRenderWorld->FreeEntityDef( renderEntityHandle ); + renderEntityHandle = -1; + } + if ( renderEntity.hModel != NULL ) { + renderModelManager->FreeModel( renderEntity.hModel ); + renderEntity.hModel = NULL; + } + initialized = false; +} + +/* +================ +idSmokeParticles::FreeSmokes +================ +*/ +void idSmokeParticles::FreeSmokes() { + for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) { + singleSmoke_t *smoke, *next, *last; + + activeSmokeStage_t *active = &activeStages[activeStageNum]; + const idParticleStage *stage = active->stage; + + for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) { + next = smoke->next; + + float frac; + + if ( smoke->timeGroup ) { + frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); + } + else { + frac = (float)( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); + } + if ( frac >= 1.0f ) { + // remove the particle from the stage list + if ( last != NULL ) { + last->next = smoke->next; + } else { + active->smokes = smoke->next; + } + // put the particle on the free list + smoke->next = freeSmokes; + freeSmokes = smoke; + numActiveSmokes--; + continue; + } + + last = smoke; + } + + if ( !active->smokes ) { + // remove this from the activeStages list + activeStages.RemoveIndex( activeStageNum ); + activeStageNum--; + } + } +} + +/* +================ +idSmokeParticles::EmitSmoke + +Called by game code to drop another particle into the list +================ +*/ +bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis, int timeGroup /*_D3XP*/ ) { + bool continues = false; + SetTimeState ts( timeGroup ); + + if ( !smoke ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + return false; + } + + // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed + if ( gameLocal.GetLocalClientNum() < 0 ) { + return false; + } + + assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time ); + if ( systemStartTime > gameLocal.time ) { + return false; + } + + idRandom steppingRandom( 0xffff * diversity ); + + // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t + for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) { + const idParticleStage *stage = smoke->stages[stageNum]; + + if ( !stage->cycleMsec ) { + continue; + } + + if ( !stage->material ) { + continue; + } + + if ( stage->particleLife <= 0 ) { + continue; + } + + // see how many particles we should emit this tic + // FIXME: smoke.privateStartTime += stage->timeOffset; + int finalParticleTime = stage->cycleMsec * stage->spawnBunching; + int deltaMsec = gameLocal.time - systemStartTime; + + int nowCount = 0, prevCount = 0; + if ( finalParticleTime == 0 ) { + // if spawnBunching is 0, they will all come out at once + if ( gameLocal.time == systemStartTime ) { + prevCount = -1; + nowCount = stage->totalParticles-1; + } else { + prevCount = stage->totalParticles; + } + } else { + nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles ); + if ( nowCount >= stage->totalParticles ) { + nowCount = stage->totalParticles-1; + } + prevCount = floor( ((float)( deltaMsec - ( gameLocal.time - gameLocal.previousTime ) ) / finalParticleTime) * stage->totalParticles ); + if ( prevCount < -1 ) { + prevCount = -1; + } + } + + if ( prevCount >= stage->totalParticles ) { + // no more particles from this stage + continue; + } + + if ( nowCount < stage->totalParticles-1 ) { + // the system will need to emit particles next frame as well + continues = true; + } + + // find an activeSmokeStage that matches this + activeSmokeStage_t *active = NULL; + int i; + for ( i = 0 ; i < activeStages.Num() ; i++ ) { + active = &activeStages[i]; + if ( active->stage == stage ) { + break; + } + } + if ( i == activeStages.Num() ) { + // add a new one + activeSmokeStage_t newActive; + + newActive.smokes = NULL; + newActive.stage = stage; + i = activeStages.Append( newActive ); + active = &activeStages[i]; + } + + // add all the required particles + for ( prevCount++ ; prevCount <= nowCount && active != NULL ; prevCount++ ) { + if ( !freeSmokes ) { + gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() ); + return true; + } + singleSmoke_t *newSmoke = freeSmokes; + freeSmokes = freeSmokes->next; + numActiveSmokes++; + + newSmoke->timeGroup = timeGroup; + newSmoke->index = prevCount; + newSmoke->axis = axis; + newSmoke->origin = origin; + newSmoke->random = steppingRandom; + newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles; + newSmoke->next = active->smokes; + active->smokes = newSmoke; + + steppingRandom.RandomInt(); // advance the random + } + } + + return continues; +} + +/* +================ +idSmokeParticles::UpdateRenderEntity +================ +*/ +bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + + // this may be triggered by a model trace or other non-view related source, + // to which we should look like an empty model + if ( !renderView ) { + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); + return false; + } + + // don't regenerate it if it is current + if ( renderView->time[renderEntity->timeGroup] == currentParticleTime && !renderView->forceUpdate ) { + return false; + } + + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); + + currentParticleTime = renderView->time[renderEntity->timeGroup]; + + particleGen_t g; + + g.renderEnt = renderEntity; + g.renderView = renderView; + + for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) { + singleSmoke_t *smoke, *next, *last; + + activeSmokeStage_t *active = &activeStages[activeStageNum]; + const idParticleStage *stage = active->stage; + + if ( !stage->material ) { + continue; + } + + // allocate a srfTriangles that can hold all the particles + int count = 0; + for ( smoke = active->smokes; smoke; smoke = smoke->next ) { + count++; + } + int quads = count * stage->NumQuadsPerParticle(); + srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 ); + tri->numIndexes = quads * 6; + tri->numVerts = quads * 4; + + // just always draw the particles + tri->bounds[0][0] = + tri->bounds[0][1] = + tri->bounds[0][2] = -99999; + tri->bounds[1][0] = + tri->bounds[1][1] = + tri->bounds[1][2] = 99999; + + tri->numVerts = 0; + for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) { + next = smoke->next; + + if ( smoke->timeGroup ) { + g.frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / (stage->particleLife * 1000); + } + else { + g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000); + } + if ( g.frac >= 1.0f ) { + // remove the particle from the stage list + if ( last != NULL ) { + last->next = smoke->next; + } else { + active->smokes = smoke->next; + } + // put the particle on the free list + smoke->next = freeSmokes; + freeSmokes = smoke; + numActiveSmokes--; + continue; + } + + g.index = smoke->index; + g.random = smoke->random; + + g.origin = smoke->origin; + g.axis = smoke->axis; + + g.originalRandom = g.random; + g.age = g.frac * stage->particleLife; + + tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts ); + + last = smoke; + } + if ( tri->numVerts > quads * 4 ) { + gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" ); + } + + if ( tri->numVerts == 0 ) { + + // they were all removed + renderEntity->hModel->FreeSurfaceTriangles( tri ); + + if ( !active->smokes ) { + // remove this from the activeStages list + activeStages.RemoveIndex( activeStageNum ); + activeStageNum--; + } + } else { + // build the index list + int indexes = 0; + for ( int i = 0 ; i < tri->numVerts ; i += 4 ) { + tri->indexes[indexes+0] = i; + tri->indexes[indexes+1] = i+2; + tri->indexes[indexes+2] = i+3; + tri->indexes[indexes+3] = i; + tri->indexes[indexes+4] = i+3; + tri->indexes[indexes+5] = i+1; + indexes += 6; + } + tri->numIndexes = indexes; + + modelSurface_t surf; + surf.geometry = tri; + surf.shader = stage->material; + surf.id = 0; + + renderEntity->hModel->AddSurface( surf ); + } + } + return true; +} + +/* +================ +idSmokeParticles::ModelCallback +================ +*/ +bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + // update the particles + if ( gameLocal.smokeParticles ) { + return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView ); + } + + return true; +} diff --git a/neo/d3xp/SmokeParticles.h b/neo/d3xp/SmokeParticles.h new file mode 100644 index 00000000..47de9dbf --- /dev/null +++ b/neo/d3xp/SmokeParticles.h @@ -0,0 +1,103 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SMOKEPARTICLES_H__ +#define __SMOKEPARTICLES_H__ + +/* +=============================================================================== + + Smoke systems are for particles that are emitted off of things that are + constantly changing position and orientation, like muzzle smoke coming + from a bone on a weapon, blood spurting from a wound, or particles + trailing from a monster limb. + + The smoke particles are always evaluated and rendered each tic, so there + is a performance cost with using them for continuous effects. The general + particle systems are completely parametric, and have no performance + overhead when not in view. + + All smoke systems share the same shaderparms, so any coloration must be + done in the particle definition. + + Each particle model has its own shaderparms, which can be used by the + particle materials. + +=============================================================================== +*/ + +typedef struct singleSmoke_s { + struct singleSmoke_s * next; + int privateStartTime; // start time for this particular particle + int index; // particle index in system, 0 <= index < stage->totalParticles + idRandom random; + idVec3 origin; + idMat3 axis; + int timeGroup; +} singleSmoke_t; + +typedef struct { + const idParticleStage * stage; + singleSmoke_t * smokes; +} activeSmokeStage_t; + + +class idSmokeParticles { +public: + idSmokeParticles(); + + // creats an entity covering the entire world that will call back each rendering + void Init(); + void Shutdown(); + + // spits out a particle, returning false if the system will not emit any more particles in the future + bool EmitSmoke( const idDeclParticle *smoke, const int startTime, const float diversity, + const idVec3 &origin, const idMat3 &axis, int timeGroup /*_D3XP*/ ); + + // free old smokes + void FreeSmokes(); + +private: + bool initialized; + + renderEntity_t renderEntity; // used to present a model to the renderer + int renderEntityHandle; // handle to static renderer model + + static const int MAX_SMOKE_PARTICLES = 10000; + singleSmoke_t smokes[MAX_SMOKE_PARTICLES]; + + idList activeStages; + singleSmoke_t * freeSmokes; + int numActiveSmokes; + int currentParticleTime; // don't need to recalculate if == view time + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); +}; + +#endif /* !__SMOKEPARTICLES_H__ */ diff --git a/neo/d3xp/Sound.cpp b/neo/d3xp/Sound.cpp new file mode 100644 index 00000000..dbfbf0b8 --- /dev/null +++ b/neo/d3xp/Sound.cpp @@ -0,0 +1,304 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + SOUND + +=============================================================================== +*/ + +const idEventDef EV_Speaker_On( "On", NULL ); +const idEventDef EV_Speaker_Off( "Off", NULL ); +const idEventDef EV_Speaker_Timer( "", NULL ); + +CLASS_DECLARATION( idEntity, idSound ) + EVENT( EV_Activate, idSound::Event_Trigger ) + EVENT( EV_Speaker_On, idSound::Event_On ) + EVENT( EV_Speaker_Off, idSound::Event_Off ) + EVENT( EV_Speaker_Timer, idSound::Event_Timer ) +END_CLASS + + +/* +================ +idSound::idSound +================ +*/ +idSound::idSound() { + lastSoundVol = 0.0f; + soundVol = 0.0f; + shakeTranslate.Zero(); + shakeRotate.Zero(); + random = 0.0f; + wait = 0.0f; + timerOn = false; + playingUntilTime = 0; +} + +/* +================ +idSound::Save +================ +*/ +void idSound::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( lastSoundVol ); + savefile->WriteFloat( soundVol ); + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( timerOn ); + savefile->WriteVec3( shakeTranslate ); + savefile->WriteAngles( shakeRotate ); + savefile->WriteInt( playingUntilTime ); +} + +/* +================ +idSound::Restore +================ +*/ +void idSound::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( lastSoundVol ); + savefile->ReadFloat( soundVol ); + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( timerOn ); + savefile->ReadVec3( shakeTranslate ); + savefile->ReadAngles( shakeRotate ); + savefile->ReadInt( playingUntilTime ); +} + +/* +================ +idSound::Spawn +================ +*/ +void idSound::Spawn() { + spawnArgs.GetVector( "move", "0 0 0", shakeTranslate ); + spawnArgs.GetAngles( "rotate", "0 0 0", shakeRotate ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + soundVol = 0.0f; + lastSoundVol = 0.0f; + + if ( ( shakeRotate != ang_zero ) || ( shakeTranslate != vec3_zero ) ) { + BecomeActive( TH_THINK ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } else { + timerOn = false; + } +} + +/* +================ +idSound::Event_Trigger + +this will toggle the idle idSound on and off +================ +*/ +void idSound::Event_Trigger( idEntity *activator ) { + if ( wait > 0.0f ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } else { + timerOn = true; + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + } else { + if ( common->IsMultiplayer() ) { + if ( refSound.referenceSound && ( gameLocal.time < playingUntilTime ) ) { + DoSound( false ); + } else { + DoSound( true ); + } + } else { + if ( refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying() ) { + DoSound( false ); + } else { + DoSound( true ); + } + } + } +} + +/* +================ +idSound::Event_Timer +================ +*/ +void idSound::Event_Timer() { + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idSound::Think +================ +*/ +void idSound::Think() { + idAngles ang; + + // run physics + RunPhysics(); + + // clear out our update visuals think flag since we never call Present + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============== +idSound::UpdateChangableSpawnArgs +=============== +*/ +void idSound::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + FreeSoundEmitter( true ); + spawnArgs.Copy( *source ); + idSoundEmitter *saveRef = refSound.referenceSound; + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + refSound.referenceSound = saveRef; + + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + DoSound( false ); + CancelEvents( &EV_Speaker_Timer ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } else if ( !refSound.waitfortrigger && !(refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying() ) ) { + // start it if it isn't already playing, and we aren't waitForTrigger + DoSound( true ); + timerOn = false; + } + } +} + +/* +=============== +idSound::SetSound +=============== +*/ +void idSound::SetSound( const char *sound, int channel ) { + const idSoundShader *shader = declManager->FindSound( sound ); + if ( shader != refSound.shader ) { + FreeSoundEmitter( true ); + } + gameEdit->ParseSpawnArgsToRefSound(&spawnArgs, &refSound); + refSound.shader = shader; + // start it if it isn't already playing, and we aren't waitForTrigger + if ( !refSound.waitfortrigger && !(refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying() ) ) { + DoSound( true ); + } +} + +/* +================ +idSound::DoSound +================ +*/ +void idSound::DoSound( bool play ) { + if ( play ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); + playingUntilTime += gameLocal.time; + } else { + StopSound( SND_CHANNEL_ANY, true ); + playingUntilTime = 0; + } +} + +/* +================ +idSound::Event_On +================ +*/ +void idSound::Event_On() { + if ( wait > 0.0f ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + DoSound( true ); +} + +/* +================ +idSound::Event_Off +================ +*/ +void idSound::Event_Off() { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } + DoSound( false ); +} + +/* +=============== +idSound::ShowEditingDialog +=============== +*/ +void idSound::ShowEditingDialog() { +} + diff --git a/neo/d3xp/Sound.h b/neo/d3xp/Sound.h new file mode 100644 index 00000000..bf245715 --- /dev/null +++ b/neo/d3xp/Sound.h @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_SOUND_H__ +#define __GAME_SOUND_H__ + +/* +=============================================================================== + + Generic sound emitter. + +=============================================================================== +*/ + +class idSound : public idEntity { +public: + CLASS_PROTOTYPE( idSound ); + + idSound(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + void Spawn(); + + void ToggleOnOff( idEntity *other, idEntity *activator ); + void Think(); + void SetSound( const char *sound, int channel = SND_CHANNEL_ANY ); + + virtual void ShowEditingDialog(); + +private: + float lastSoundVol; + float soundVol; + float random; + float wait; + bool timerOn; + idVec3 shakeTranslate; + idAngles shakeRotate; + int playingUntilTime; + + void Event_Trigger( idEntity *activator ); + void Event_Timer(); + void Event_On(); + void Event_Off(); + void DoSound( bool play ); +}; + +#endif /* !__GAME_SOUND_H__ */ diff --git a/neo/d3xp/Target.cpp b/neo/d3xp/Target.cpp new file mode 100644 index 00000000..34cd2fd6 --- /dev/null +++ b/neo/d3xp/Target.cpp @@ -0,0 +1,1921 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* + +Invisible entities that affect other entities or the world when activated. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTarget ) +END_CLASS + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Remove ) + EVENT( EV_Activate, idTarget_Remove::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Remove::Event_Activate +================ +*/ +void idTarget_Remove::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Show ) + EVENT( EV_Activate, idTarget_Show::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Show::Event_Activate +================ +*/ +void idTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Damage ) + EVENT( EV_Activate, idTarget_Damage::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Damage::Event_Activate +================ +*/ +void idTarget_Damage::Event_Activate( idEntity *activator ) { + int i; + const char *damage; + idEntity * ent; + + damage = spawnArgs.GetString( "def_damage", "damage_generic" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT ); + } + } +} + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SessionCommand ) + EVENT( EV_Activate, idTarget_SessionCommand::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SessionCommand::Event_Activate +================ +*/ +void idTarget_SessionCommand::Event_Activate( idEntity *activator ) { + gameLocal.sessionCommand = spawnArgs.GetString( "command" ); +} + + +/* +=============================================================================== + +idTarget_EndLevel + +Just a modified form of idTarget_SessionCommand +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EndLevel ) + EVENT( EV_Activate, idTarget_EndLevel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EndLevel::Event_Activate +================ +*/ +void idTarget_EndLevel::Event_Activate( idEntity *activator ) { + extern idCVar g_demoMode; + if ( g_demoMode.GetInteger() > 0 ) { + gameLocal.sessionCommand = "disconnect"; + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + + const bool isTutorialMap = ( idStr::FindText( gameLocal.GetMapFileName(), "mars_city1" ) >= 0 ) || + ( idStr::FindText( gameLocal.GetMapFileName(), "mars_city2" ) >= 0 ) || + ( idStr::FindText( gameLocal.GetMapFileName(), "mc_underground" ) >= 0 ); + + if ( !isTutorialMap && player != NULL ) { + if ( !player->GetAchievementManager().GetPlayerTookDamage() ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_COMPLETE_LEVEL_WITHOUT_TAKING_DMG ); + } + player->GetAchievementManager().SetPlayerTookDamage( false ); + } + + if ( !isTutorialMap && spawnArgs.GetBool( "endOfGame" ) ) { + + if ( player != NULL ) { + gameExpansionType_t expansion = player->GetExpansionType(); + switch ( expansion ) { + case GAME_D3XP: + // The fall-through is done here on purpose so compleating the game on one difficulty will unlock all the easier difficulties + switch ( g_skill.GetInteger() ) { + case 3: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_3 ); + case 2: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_2 ); + case 1: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_1 ); + case 0: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_ROE_COMPLETED_DIFFICULTY_0 ); + } + break; + case GAME_D3LE: + // The fall-through is done here on purpose so compleating the game on one difficulty will unlock all the easier difficulties + switch ( g_skill.GetInteger() ) { + case 3: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_3 ); + case 2: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_2 ); + case 1: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_1 ); + case 0: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_LE_COMPLETED_DIFFICULTY_0 ); + } + break; + case GAME_BASE: + // The fall-through is done here on purpose so compleating the game on one difficulty will unlock all the easier difficulties + switch ( g_skill.GetInteger() ) { + case 3: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_COMPLETED_DIFFICULTY_3 ); + case 2: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_COMPLETED_DIFFICULTY_2 ); + case 1: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_COMPLETED_DIFFICULTY_1 ); + case 0: player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_COMPLETED_DIFFICULTY_0 ); + } + break; + } + + if ( player->GetPlayedTime() <= 36000 && expansion == GAME_BASE ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_SPEED_RUN ); + } + + switch ( expansion ) { + case GAME_D3XP: { + cvarSystem->SetCVarBool( "g_roeNightmare", true ); + break; + } + case GAME_D3LE: { + cvarSystem->SetCVarBool( "g_leNightmare", true ); + break; + } + case GAME_BASE: { + cvarSystem->SetCVarBool( "g_nightmare", true ); + break; + } + } + } + gameLocal.Shell_SetGameComplete(); + return; + } + + idStr nextMap; + if ( !spawnArgs.GetString( "nextMap", "", nextMap ) ) { + gameLocal.Printf( "idTarget_SessionCommand::Event_Activate: no nextMap key\n" ); + return; + } + + if ( spawnArgs.GetInt( "devmap", "0" ) ) { + gameLocal.sessionCommand = "devmap "; // only for special demos + } else { + gameLocal.sessionCommand = "map "; + } + + gameLocal.sessionCommand += nextMap; +} + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_WaitForButton ) + EVENT( EV_Activate, idTarget_WaitForButton::Event_Activate ) +END_CLASS + +/* +================ +idTarget_WaitForButton::Event_Activate +================ +*/ +void idTarget_WaitForButton::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTarget_WaitForButton::Think +================ +*/ +void idTarget_WaitForButton::Think() { + idPlayer *player; + + if ( thinkFlags & TH_THINK ) { + player = gameLocal.GetLocalPlayer(); + if ( player != NULL && ( !( player->oldButtons & BUTTON_ATTACK ) ) && ( player->usercmd.buttons & BUTTON_ATTACK ) ) { + player->usercmd.buttons &= ~BUTTON_ATTACK; + BecomeInactive( TH_THINK ); + ActivateTargets( player ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetGlobalShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetGlobalShaderTime ) +EVENT( EV_Activate, idTarget_SetGlobalShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetGlobalShaderTime::Event_Activate +================ +*/ +void idTarget_SetGlobalShaderTime::Event_Activate( idEntity *activator ) { + int parm = spawnArgs.GetInt( "globalParm" ); + float time = -MS2SEC( gameLocal.time ); + if ( parm >= 0 && parm < MAX_GLOBAL_SHADER_PARMS ) { + gameLocal.globalShaderParms[parm] = time; + } +} + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderParm ) + EVENT( EV_Activate, idTarget_SetShaderParm::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderParm::Event_Activate +================ +*/ +void idTarget_SetShaderParm::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float value; + idVec3 color; + int parmnum; + + // set the color on the targets + if ( spawnArgs.GetVector( "_color", "1 1 1", color ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + } + } + } + + // set any shader parms on the targets + for( parmnum = 0; parmnum < MAX_ENTITY_SHADER_PARMS; parmnum++ ) { + if ( spawnArgs.GetFloat( va( "shaderParm%d", parmnum ), "0", value ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderTime ) + EVENT( EV_Activate, idTarget_SetShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderTime::Event_Activate +================ +*/ +void idTarget_SetShaderTime::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float time; + + time = -MS2SEC( gameLocal.time ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( SHADERPARM_TIMEOFFSET, time ); + if ( ent->IsType( idLight::Type ) ) { + static_cast(ent)->SetLightParm( SHADERPARM_TIMEOFFSET, time ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_FadeEntity ) + EVENT( EV_Activate, idTarget_FadeEntity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_FadeEntity::idTarget_FadeEntity +================ +*/ +idTarget_FadeEntity::idTarget_FadeEntity() { + fadeFrom.Zero(); + fadeStart = 0; + fadeEnd = 0; +} + +/* +================ +idTarget_FadeEntity::Save +================ +*/ +void idTarget_FadeEntity::Save( idSaveGame *savefile ) const { + savefile->WriteVec4( fadeFrom ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Restore +================ +*/ +void idTarget_FadeEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadVec4( fadeFrom ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Event_Activate +================ +*/ +void idTarget_FadeEntity::Event_Activate( idEntity *activator ) { + idEntity *ent; + int i; + + if ( !targets.Num() ) { + return; + } + + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->GetColor( fadeFrom ); + break; + } + } + + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "fadetime" ) ); +} + +/* +================ +idTarget_FadeEntity::Think +================ +*/ +void idTarget_FadeEntity::Think() { + int i; + idEntity *ent; + idVec4 color; + idVec4 fadeTo; + float frac; + + if ( thinkFlags & TH_THINK ) { + GetColor( fadeTo ); + if ( gameLocal.time >= fadeEnd ) { + color = fadeTo; + BecomeInactive( TH_THINK ); + } else { + frac = ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ); + color.Lerp( fadeFrom, fadeTo, frac ); + } + + // set the color on the targets + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeIn ) + EVENT( EV_Activate, idTarget_LightFadeIn::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeIn::Event_Activate +================ +*/ +void idTarget_LightFadeIn::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->IsType( idLight::Type ) ) { + light = static_cast( ent ); + light->FadeIn( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeOut ) + EVENT( EV_Activate, idTarget_LightFadeOut::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeOut::Event_Activate +================ +*/ +void idTarget_LightFadeOut::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->IsType( idLight::Type ) ) { + light = static_cast( ent ); + light->FadeOut( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Give ) + EVENT( EV_Activate, idTarget_Give::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Give::Spawn +================ +*/ +void idTarget_Give::Spawn() { + if ( spawnArgs.GetBool( "onSpawn" ) ) { + PostEventMS( &EV_Activate, 50 ); + } +} + +/* +================ +idTarget_Give::Event_Activate +================ +*/ +void idTarget_Give::Event_Activate( idEntity *activator ) { + + if ( spawnArgs.GetBool( "development" ) && developer.GetInteger() == 0 ) { + return; + } + + static int giveNum = 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( dict ) { + idDict d2; + d2.Copy( *dict ); + d2.Set( "name", va( "givenitem_%i", giveNum++ ) ); + idEntity *ent = NULL; + if ( gameLocal.SpawnEntityDef( d2, &ent ) && ent && ent->IsType( idItem::Type ) ) { + idItem *item = static_cast(ent); + item->GiveToPlayer( gameLocal.GetLocalPlayer(), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + } + kv = spawnArgs.MatchPrefix( "item", kv ); + } + } +} + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveEmail ) +EVENT( EV_Activate, idTarget_GiveEmail::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveEmail::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + const idDeclPDA *pda = player->GetPDA(); + if ( pda ) { + player->GiveEmail( static_cast( declManager->FindType( DECL_EMAIL, spawnArgs.GetString( "email" ), false ) ) ); + } else { + player->ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_PDANeeded" ), true ); + } +} + + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetModel ) + EVENT( EV_Activate, idTarget_SetModel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetModel::Spawn +================ +*/ +void idTarget_SetModel::Spawn() { + const char *model; + + model = spawnArgs.GetString( "newmodel" ); + if ( declManager->FindType( DECL_MODELDEF, model, false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( model ); + // precache .cm files only + collisionModelManager->LoadModel( model ); + } +} + +/* +================ +idTarget_SetModel::Event_Activate +================ +*/ +void idTarget_SetModel::Event_Activate( idEntity *activator ) { + for( int i = 0; i < targets.Num(); i++ ) { + idEntity *ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetModel( spawnArgs.GetString( "newmodel" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +const idEventDef EV_RestoreInfluence( "" ); +const idEventDef EV_GatherEntities( "" ); +const idEventDef EV_Flash( "", "fd" ); +const idEventDef EV_ClearFlash( "", "f" ); + +CLASS_DECLARATION( idTarget, idTarget_SetInfluence ) + EVENT( EV_Activate, idTarget_SetInfluence::Event_Activate ) + EVENT( EV_RestoreInfluence, idTarget_SetInfluence::Event_RestoreInfluence ) + EVENT( EV_GatherEntities, idTarget_SetInfluence::Event_GatherEntities ) + EVENT( EV_Flash, idTarget_SetInfluence::Event_Flash ) + EVENT( EV_ClearFlash, idTarget_SetInfluence::Event_ClearFlash ) +END_CLASS + +/* +================ +idTarget_SetInfluence::idTarget_SetInfluence +================ +*/ +idTarget_SetInfluence::idTarget_SetInfluence() { + flashIn = 0.0f; + flashOut = 0.0f; + delay = 0.0f; + switchToCamera = NULL; + soundFaded = false; + restoreOnTrigger = false; +} + +/* +================ +idTarget_SetInfluence::Save +================ +*/ +void idTarget_SetInfluence::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( lightList.Num() ); + for( i = 0; i < lightList.Num(); i++ ) { + savefile->WriteInt( lightList[ i ] ); + } + + savefile->WriteInt( guiList.Num() ); + for( i = 0; i < guiList.Num(); i++ ) { + savefile->WriteInt( guiList[ i ] ); + } + + savefile->WriteInt( soundList.Num() ); + for( i = 0; i < soundList.Num(); i++ ) { + savefile->WriteInt( soundList[ i ] ); + } + + savefile->WriteInt( genericList.Num() ); + for( i = 0; i < genericList.Num(); i++ ) { + savefile->WriteInt( genericList[ i ] ); + } + + savefile->WriteFloat( flashIn ); + savefile->WriteFloat( flashOut ); + + savefile->WriteFloat( delay ); + + savefile->WriteString( flashInSound ); + savefile->WriteString( flashOutSound ); + + savefile->WriteObject( switchToCamera ); + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); + + savefile->WriteBool( soundFaded ); + savefile->WriteBool( restoreOnTrigger ); + + savefile->WriteInt( savedGuiList.Num() ); + for( i = 0; i < savedGuiList.Num(); i++ ) { + for(int j = 0; j < MAX_RENDERENTITY_GUI; j++) { + savefile->WriteUserInterface(savedGuiList[i].gui[j], savedGuiList[i].gui[j] ? savedGuiList[i].gui[j]->IsUniqued() : false); + } + } +} + +/* +================ +idTarget_SetInfluence::Restore +================ +*/ +void idTarget_SetInfluence::Restore( idRestoreGame *savefile ) { + int i, num; + int itemNum; + float set; + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + lightList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + guiList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + soundList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + genericList.Append( itemNum ); + } + + savefile->ReadFloat( flashIn ); + savefile->ReadFloat( flashOut ); + + savefile->ReadFloat( delay ); + + savefile->ReadString( flashInSound ); + savefile->ReadString( flashOutSound ); + + savefile->ReadObject( reinterpret_cast( switchToCamera ) ); + + savefile->ReadFloat( set ); + fovSetting.SetStartTime( set ); + savefile->ReadFloat( set ); + fovSetting.SetDuration( set ); + savefile->ReadFloat( set ); + fovSetting.SetStartValue( set ); + savefile->ReadFloat( set ); + fovSetting.SetEndValue( set ); + + savefile->ReadBool( soundFaded ); + savefile->ReadBool( restoreOnTrigger ); + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + SavedGui_t temp; + for(int j = 0; j < MAX_RENDERENTITY_GUI; j++) { + savefile->ReadUserInterface(temp.gui[j]); + } + savedGuiList.Append( temp ); + } +} + +/* +================ +idTarget_SetInfluence::Spawn +================ +*/ +void idTarget_SetInfluence::Spawn() { + PostEventMS( &EV_GatherEntities, 0 ); + flashIn = spawnArgs.GetFloat( "flashIn", "0" ); + flashOut = spawnArgs.GetFloat( "flashOut", "0" ); + flashInSound = spawnArgs.GetString( "snd_flashin" ); + flashOutSound = spawnArgs.GetString( "snd_flashout" ); + delay = spawnArgs.GetFloat( "delay" ); + soundFaded = false; + restoreOnTrigger = false; + + // always allow during cinematics + cinematic = true; +} + +/* +================ +idTarget_SetInfluence::Event_Flash +================ +*/ +void idTarget_SetInfluence::Event_Flash( float flash, int out ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( idVec4( 1, 1, 1, 1 ), flash ); + const idSoundShader *shader = NULL; + if ( !out && flashInSound.Length() ){ + shader = declManager->FindSound( flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( out && ( flashOutSound.Length() || flashInSound.Length() ) ) { + shader = declManager->FindSound( flashOutSound.Length() ? flashOutSound : flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + PostEventSec( &EV_ClearFlash, flash, flash ); +} + + +/* +================ +idTarget_SetInfluence::Event_ClearFlash +================ +*/ +void idTarget_SetInfluence::Event_ClearFlash( float flash ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( vec4_zero , flash ); +} +/* +================ +idTarget_SetInfluence::Event_GatherEntities +================ +*/ +void idTarget_SetInfluence::Event_GatherEntities() { + int i, listedEntities; + idEntity *entityList[ MAX_GENTITIES ]; + + //bool demonicOnly = spawnArgs.GetBool( "effect_demonic" ); + bool lights = spawnArgs.GetBool( "effect_lights" ); + bool sounds = spawnArgs.GetBool( "effect_sounds" ); + bool guis = spawnArgs.GetBool( "effect_guis" ); + bool models = spawnArgs.GetBool( "effect_models" ); + bool vision = spawnArgs.GetBool( "effect_vision" ); + bool targetsOnly = spawnArgs.GetBool( "targetsOnly" ); + + lightList.Clear(); + guiList.Clear(); + soundList.Clear(); + savedGuiList.Clear(); + + if ( spawnArgs.GetBool( "effect_all" ) ) { + lights = sounds = guis = models = vision = true; + } + + if ( targetsOnly ) { + listedEntities = targets.Num(); + for ( i = 0; i < listedEntities; i++ ) { + entityList[i] = targets[i].GetEntity(); + } + } else { + float radius = spawnArgs.GetFloat( "radius" ); + listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + } + + for( i = 0; i < listedEntities; i++ ) { + idEntity *ent = entityList[ i ]; + if ( ent ) { + if ( lights && ent->IsType( idLight::Type ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { + lightList.Append( ent->entityNumber ); + continue; + } + if ( sounds && ent->IsType( idSound::Type ) && ent->spawnArgs.FindKey( "snd_demonic" ) ) { + soundList.Append( ent->entityNumber ); + continue; + } + if ( guis && ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ 0 ] && ent->spawnArgs.FindKey( "gui_demonic" ) ) { + guiList.Append( ent->entityNumber ); + SavedGui_t temp; + savedGuiList.Append(temp); + continue; + } + if ( ent->IsType( idStaticEntity::Type ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { + genericList.Append( ent->entityNumber ); + continue; + } + } + } + idStr temp; + temp = spawnArgs.GetString( "switchToView" ); + switchToCamera = ( temp.Length() ) ? gameLocal.FindEntity( temp ) : NULL; + +} + +/* +================ +idTarget_SetInfluence::Event_Activate +================ +*/ +void idTarget_SetInfluence::Event_Activate( idEntity *activator ) { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + const char *parm; + const char *skin; + bool update; + idVec3 color; + idVec4 colorTo; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + + if ( spawnArgs.GetBool( "triggerActivate" ) ) { + if ( restoreOnTrigger ) { + ProcessEvent( &EV_RestoreInfluence ); + restoreOnTrigger = false; + return; + } + restoreOnTrigger = true; + } + + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + + if ( delay > 0.0f ) { + PostEventSec( &EV_Activate, delay, activator ); + delay = 0.0f; + // start any sound fading now + if ( fadeTime ) { + gameSoundWorld->FadeSoundClasses( 0, -40.0f, fadeTime ); + soundFaded = true; + } + return; + } else if ( fadeTime && !soundFaded ) { + gameSoundWorld->FadeSoundClasses( 0, -40.0f, fadeTime ); + soundFaded = true; + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( activator ); + } + + if ( flashIn ) { + PostEventSec( &EV_Flash, 0.0f, flashIn, 0 ); + } + + parm = spawnArgs.GetString( "snd_influence" ); + if ( parm != NULL && *parm != NULL ) { + PostEventSec( &EV_StartSoundShader, flashIn, parm, SND_CHANNEL_ANY ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventSec( &EV_Activate, flashIn + 0.05f, this ); + } + + int fov = spawnArgs.GetInt( "fov" ); + if ( fov ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), player->DefaultFov(), fov ); + BecomeActive( TH_THINK ); + } + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + color = generic->spawnArgs.GetVector( "color_demonic" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; + if ( ent == NULL || !ent->IsType( idLight::Type ) ) { + continue; + } + light = static_cast(ent); + parm = light->spawnArgs.GetString( "mat_demonic" ); + if ( parm && *parm ) { + light->SetShader( parm ); + } + + color = light->spawnArgs.GetVector( "_color" ); + color = light->spawnArgs.GetVector( "color_demonic", color.ToString() ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; + if ( ent == NULL || !ent->IsType( idSound::Type ) ) { + continue; + } + sound = static_cast(ent); + parm = sound->spawnArgs.GetString( "snd_demonic" ); + if ( parm && *parm ) { + if ( sound->spawnArgs.GetBool( "overlayDemonic" ) ) { + sound->StartSound( "snd_demonic", SND_CHANNEL_DEMONIC, 0, false, NULL ); + } else { + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( parm ); + } + } + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || ent->GetRenderEntity() == NULL ) { + continue; + } + update = false; + + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] && ent->spawnArgs.FindKey( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ) ) { + //Backup the old one + savedGuiList[i].gui[j] = ent->GetRenderEntity()->gui[ j ]; + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ), true ); + update = true; + } + } + + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + } + + player->SetInfluenceLevel( spawnArgs.GetInt( "influenceLevel" ) ); + + int snapAngle = spawnArgs.GetInt( "snapAngle" ); + if ( snapAngle ) { + idAngles ang( 0, snapAngle, 0 ); + player->SetViewAngles( ang ); + player->SetAngles( ang ); + } + + if ( spawnArgs.GetBool( "effect_vision" ) ) { + parm = spawnArgs.GetString( "mtrVision" ); + skin = spawnArgs.GetString( "skinVision" ); + player->SetInfluenceView( parm, skin, spawnArgs.GetInt( "visionRadius" ), this ); + } + + parm = spawnArgs.GetString( "mtrWorld" ); + if ( parm != NULL && *parm != NULL ) { + gameLocal.SetGlobalMaterial( declManager->FindMaterial( parm ) ); + } + + if ( !restoreOnTrigger ) { + PostEventMS( &EV_RestoreInfluence, SEC2MS( spawnArgs.GetFloat( "time" ) ) ); + } +} + +/* +================ +idTarget_SetInfluence::Think +================ +*/ +void idTarget_SetInfluence::Think() { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + if ( !spawnArgs.GetBool( "leaveFOV" ) ) { + player->SetInfluenceFov( 0 ); + } + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +================ +idTarget_SetInfluence::Event_RestoreInfluence +================ +*/ +void idTarget_SetInfluence::Event_RestoreInfluence() { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + bool update; + idVec3 color; + idVec4 colorTo; + + if ( flashOut ) { + PostEventSec( &EV_Flash, 0.0f, flashOut, 1 ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventMS( &EV_Activate, 0.0f, this ); + } + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + color = generic->spawnArgs.GetVector( "_color", "1 1 1" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; + if ( ent == NULL || !ent->IsType( idLight::Type ) ) { + continue; + } + light = static_cast(ent); + if ( !light->spawnArgs.GetBool( "leave_demonic_mat" ) ) { + const char *texture = light->spawnArgs.GetString( "texture", "lights/squarelight1" ); + light->SetShader( texture ); + } + color = light->spawnArgs.GetVector( "_color" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; + if ( ent == NULL || !ent->IsType( idSound::Type ) ) { + continue; + } + sound = static_cast(ent); + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( sound->spawnArgs.GetString( "s_shader" ) ); + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || GetRenderEntity() == NULL ) { + continue; + } + update = false; + for( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ] = savedGuiList[i].gui[j]; + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceLevel( 0 ); + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + player->SetInfluenceFov( 0 ); + gameLocal.SetGlobalMaterial( NULL ); + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + if ( fadeTime ) { + gameSoundWorld->FadeSoundClasses( 0, 0.0f, fadeTime / 2.0f ); + } + +} + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetKeyVal ) + EVENT( EV_Activate, idTarget_SetKeyVal::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetKeyVal::Event_Activate +================ +*/ +void idTarget_SetKeyVal::Event_Activate( idEntity *activator ) { + int i; + idStr key, val; + idEntity *ent; + const idKeyValue *kv; + int n; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + kv = spawnArgs.MatchPrefix("keyval"); + while ( kv ) { + n = kv->GetValue().Find( ";" ); + if ( n > 0 ) { + key = kv->GetValue().Left( n ); + val = kv->GetValue().Right( kv->GetValue().Length() - n - 1 ); + ent->spawnArgs.Set( key, val ); + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time ); + } + } + } + } + kv = spawnArgs.MatchPrefix( "keyval", kv ); + } + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetFov ) + EVENT( EV_Activate, idTarget_SetFov::Event_Activate ) +END_CLASS + + +/* +================ +idTarget_SetFov::Save +================ +*/ +void idTarget_SetFov::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); +} + +/* +================ +idTarget_SetFov::Restore +================ +*/ +void idTarget_SetFov::Restore( idRestoreGame *savefile ) { + float setting; + + savefile->ReadFloat( setting ); + fovSetting.SetStartTime( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetDuration( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetStartValue( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetEndValue( setting ); + + fovSetting.GetCurrentValue( gameLocal.time ); +} + +/* +================ +idTarget_SetFov::Event_Activate +================ +*/ +void idTarget_SetFov::Event_Activate( idEntity *activator ) { + // always allow during cinematics + cinematic = true; + + idPlayer *player = gameLocal.GetLocalPlayer(); + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "time" ) ), player ? player->DefaultFov() : g_fov.GetFloat(), spawnArgs.GetFloat( "fov" ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTarget_SetFov::Think +================ +*/ +void idTarget_SetFov::Think() { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + player->SetInfluenceFov( 0.0f ); + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetPrimaryObjective ) + EVENT( EV_Activate, idTarget_SetPrimaryObjective::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetPrimaryObjective::Event_Activate +================ +*/ +void idTarget_SetPrimaryObjective::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + player->SetPrimaryObjective( this ); + } +} + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LockDoor ) + EVENT( EV_Activate, idTarget_LockDoor::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LockDoor::Event_Activate +================ +*/ +void idTarget_LockDoor::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + int lock; + + lock = spawnArgs.GetInt( "locked", "1" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent != NULL && ent->IsType( idDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_CallObjectFunction ) + EVENT( EV_Activate, idTarget_CallObjectFunction::Event_Activate ) +END_CLASS + +/* +================ +idTarget_CallObjectFunction::Event_Activate +================ +*/ +void idTarget_CallObjectFunction::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + + funcName = spawnArgs.GetString( "call" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent != NULL && ent->scriptObject.HasObject() ) { + func = ent->scriptObject.GetFunction( funcName ); + if ( func == NULL ) { + gameLocal.Error( "Function '%s' not found on entity '%s' for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + return; + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function '%s' on entity '%s' has the wrong number of parameters for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( !ent->scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function '%s' on entity '%s' is the wrong type for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + // create a thread and call the function + thread = new idThread(); + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableLevelWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableLevelWeapons ) + EVENT( EV_Activate, idTarget_EnableLevelWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableLevelWeapons::Event_Activate +================ +*/ +void idTarget_EnableLevelWeapons::Event_Activate( idEntity *activator ) { + int i; + const char *weap; + + gameLocal.world->spawnArgs.SetBool( "no_Weapons", spawnArgs.GetBool( "disable" ) ); + + if ( spawnArgs.GetBool( "disable" ) ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_DisableWeapon ); + } + } + } else { + weap = spawnArgs.GetString( "weapon" ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_EnableWeapon ); + if ( weap != NULL && weap[ 0 ] != NULL ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, weap ); + } + } + } + } +} + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +const idEventDef EV_TipOff( "" ); +extern const idEventDef EV_GetPlayerPos( "" ); + +CLASS_DECLARATION( idTarget, idTarget_Tip ) + EVENT( EV_Activate, idTarget_Tip::Event_Activate ) + EVENT( EV_TipOff, idTarget_Tip::Event_TipOff ) + EVENT( EV_GetPlayerPos, idTarget_Tip::Event_GetPlayerPos ) +END_CLASS + + +/* +================ +idTarget_Tip::idTarget_Tip +================ +*/ +idTarget_Tip::idTarget_Tip() { + playerPos.Zero(); +} + +/* +================ +idTarget_Tip::Spawn +================ +*/ +void idTarget_Tip::Spawn() { +} + +/* +================ +idTarget_Tip::Save +================ +*/ +void idTarget_Tip::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Restore +================ +*/ +void idTarget_Tip::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_TipOff, 100 ); + } +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->IsTipVisible() ) { + PostEventSec( &EV_Activate, 5.1f, activator ); + return; + } + player->ShowTip( spawnArgs.GetString( "text_title" ), spawnArgs.GetString( "text_tip" ), false ); + PostEventMS( &EV_GetPlayerPos, 2000 ); + } +} + +/* +================ +idTarget_Tip::Event_TipOff +================ +*/ +void idTarget_Tip::Event_TipOff() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; + if ( v.Length() > 96.0f ) { + player->HideTip(); + } else { + PostEventMS( &EV_TipOff, 100 ); + } + } +} + + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveSecurity ) +EVENT( EV_Activate, idTarget_GiveSecurity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveSecurity::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveSecurity( spawnArgs.GetString( "text_security" ) ); + } +} + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_RemoveWeapons ) +EVENT( EV_Activate, idTarget_RemoveWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_RemoveWeapons::Event_Activate +================ +*/ +void idTarget_RemoveWeapons::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + + // Everywhere that we use target_removeweapons the intent is to remove ALL of the + // weapons that hte player has (save a few: flashlights, fists, soul cube). + player->RemoveAllButEssentialWeapons(); + player->SelectWeapon( player->weapon_fists, true ); + } + } +} + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LevelTrigger ) +EVENT( EV_Activate, idTarget_LevelTrigger::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LevelTrigger::Event_Activate +================ +*/ +void idTarget_LevelTrigger::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + player->SetLevelTrigger( spawnArgs.GetString( "levelName" ), spawnArgs.GetString( "triggerName" ) ); + } + } +} + +/* +=============================================================================== + +idTarget_Checkpoint + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Checkpoint ) +EVENT( EV_Activate, idTarget_Checkpoint::Event_Activate ) +END_CLASS + +idCVar g_checkpoints( "g_checkpoints", "1", CVAR_BOOL | CVAR_ARCHIVE, "Enable/Disable checkpoints" ); + +/* +================ +idTarget_Checkpoint::Event_Activate +================ +*/ +void idTarget_Checkpoint::Event_Activate( idEntity *activator ) { + extern idCVar g_demoMode; // no saving in demo mode + if ( g_checkpoints.GetBool() && !g_demoMode.GetBool() ) { + cmdSystem->AppendCommandText( "savegame autosave\n" ); + } +} + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableStamina ) +EVENT( EV_Activate, idTarget_EnableStamina::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableStamina::Event_Activate +================ +*/ +void idTarget_EnableStamina::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + if ( spawnArgs.GetBool( "enable" ) ) { + pm_stamina.SetFloat( player->spawnArgs.GetFloat( "pm_stamina" ) ); + } else { + pm_stamina.SetFloat( 0.0f ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ + +const idEventDef EV_RestoreVolume( "" ); +CLASS_DECLARATION( idTarget, idTarget_FadeSoundClass ) +EVENT( EV_Activate, idTarget_FadeSoundClass::Event_Activate ) +EVENT( EV_RestoreVolume, idTarget_FadeSoundClass::Event_RestoreVolume ) +END_CLASS + +/* +================ +idTarget_FadeSoundClass::Event_Activate +================ +*/ +void idTarget_FadeSoundClass::Event_Activate( idEntity *activator ) { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); + float fadeDuration = spawnArgs.GetFloat( "fadeDuration" ); + int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // start any sound fading now + if ( fadeTime ) { + gameSoundWorld->FadeSoundClasses( fadeClass, spawnArgs.GetBool( "fadeIn" ) ? fadeDB : 0.0f - fadeDB, fadeTime ); + if ( fadeDuration ) { + PostEventSec( &EV_RestoreVolume, fadeDuration ); + } + } +} + +/* +================ +idTarget_FadeSoundClass::Event_RestoreVolume +================ +*/ +void idTarget_FadeSoundClass::Event_RestoreVolume() { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); + //int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // restore volume + gameSoundWorld->FadeSoundClasses( 0, fadeDB, fadeTime ); +} + +/* +=============================================================================== + +idTarget_RumbleJoystick + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_RumbleJoystick ) +EVENT( EV_Activate, idTarget_RumbleJoystick::Event_Activate ) +END_CLASS + +/* +================ +idTarget_RumbleJoystick::Event_Activate +================ +*/ +void idTarget_RumbleJoystick::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + float highMagnitude = spawnArgs.GetFloat( "high_magnitude" ); + int highDuration = spawnArgs.GetInt( "high_duration" ); + float lowMagnitude = spawnArgs.GetFloat( "low_magnitude" ); + int lowDuration = spawnArgs.GetInt( "low_duration" ); + + player->SetControllerShake( highMagnitude, highDuration, lowMagnitude, lowDuration ); + } + +} + +/* +=============================================================================== + +idTarget_Achievement + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Achievement ) +EVENT( EV_Activate, idTarget_Achievement::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Achievement::Event_Activate +================ +*/ +void idTarget_Achievement::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + int achievement = spawnArgs.GetFloat( "achievement" ); + player->GetAchievementManager().EventCompletesAchievement( (achievement_t)achievement ); + } +} diff --git a/neo/d3xp/Target.h b/neo/d3xp/Target.h new file mode 100644 index 00000000..12f590ca --- /dev/null +++ b/neo/d3xp/Target.h @@ -0,0 +1,615 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_TARGET_H__ +#define __GAME_TARGET_H__ + + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +class idTarget : public idEntity { +public: + CLASS_PROTOTYPE( idTarget ); +}; + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +class idTarget_Remove : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Remove ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +class idTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Show ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +class idTarget_Damage : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Damage ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +class idTarget_SessionCommand : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SessionCommand ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_EndLevel + +=============================================================================== +*/ + +class idTarget_EndLevel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EndLevel ); + +private: + void Event_Activate( idEntity *activator ); + +}; + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +class idTarget_WaitForButton : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_WaitForButton ); + + void Think(); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetGlobalShaderTime + +=============================================================================== +*/ + +class idTarget_SetGlobalShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetGlobalShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +class idTarget_SetShaderParm : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderParm ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +class idTarget_SetShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +class idTarget_FadeEntity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeEntity ); + + idTarget_FadeEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think(); + +private: + idVec4 fadeFrom; + int fadeStart; + int fadeEnd; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +class idTarget_LightFadeIn : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeIn ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +class idTarget_LightFadeOut : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeOut ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +class idTarget_Give : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Give ); + + void Spawn(); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +class idTarget_GiveEmail : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveEmail ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +class idTarget_SetModel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetModel ); + + void Spawn(); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +typedef struct SavedGui_s { + SavedGui_s() {memset(gui, 0, sizeof(idUserInterface*)*MAX_RENDERENTITY_GUI); }; + idUserInterface* gui[MAX_RENDERENTITY_GUI]; +} SavedGui_t; + +class idTarget_SetInfluence : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetInfluence ); + + idTarget_SetInfluence(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreInfluence(); + void Event_GatherEntities(); + void Event_Flash( float flash, int out ); + void Event_ClearFlash( float flash ); + void Think(); + + idList lightList; + idList guiList; + idList soundList; + idList genericList; + float flashIn; + float flashOut; + float delay; + idStr flashInSound; + idStr flashOutSound; + idEntity * switchToCamera; + idInterpolatefovSetting; + bool soundFaded; + bool restoreOnTrigger; + + idList savedGuiList; +}; + + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +class idTarget_SetKeyVal : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetKeyVal ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +class idTarget_SetFov : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetFov ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think(); + +private: + idInterpolate fovSetting; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +class idTarget_SetPrimaryObjective : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetPrimaryObjective ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +class idTarget_CallObjectFunction : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_CallObjectFunction ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_EnableLevelWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableLevelWeapons ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +class idTarget_Tip : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Tip ); + + idTarget_Tip(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idVec3 playerPos; + + void Event_Activate( idEntity *activator ); + void Event_TipOff(); + void Event_GetPlayerPos(); +}; + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ +class idTarget_GiveSecurity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveSecurity ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ +class idTarget_RemoveWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_RemoveWeapons ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ +class idTarget_LevelTrigger : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LevelTrigger ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Checkpoint + +=============================================================================== +*/ +class idTarget_Checkpoint : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Checkpoint ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ +class idTarget_EnableStamina : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableStamina ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ +class idTarget_FadeSoundClass : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeSoundClass ); +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreVolume(); +}; + +/* +=============================================================================== + +idTarget_RumbleJoystick + +=============================================================================== +*/ + +class idTarget_RumbleJoystick : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_RumbleJoystick ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Achievement + +=============================================================================== +*/ + +class idTarget_Achievement : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Achievement ); +private: + void Event_Activate( idEntity *activator ); +}; + +#endif /* !__GAME_TARGET_H__ */ diff --git a/neo/d3xp/Trigger.cpp b/neo/d3xp/Trigger.cpp new file mode 100644 index 00000000..f73bf39f --- /dev/null +++ b/neo/d3xp/Trigger.cpp @@ -0,0 +1,1357 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idTrigger + +=============================================================================== +*/ + +const idEventDef EV_Enable( "enable", NULL ); +const idEventDef EV_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idTrigger ) + EVENT( EV_Enable, idTrigger::Event_Enable ) + EVENT( EV_Disable, idTrigger::Event_Disable ) +END_CLASS + +/* +================ +idTrigger::DrawDebugInfo +================ +*/ +void idTrigger::DrawDebugInfo() { + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + bool show; + const function_t *func; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetContents() & ( CONTENTS_TRIGGER | CONTENTS_FLASHLIGHT_TRIGGER ) ) { + show = viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ); + if ( !show ) { + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target != NULL && viewBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + show = true; + break; + } + } + } + + if ( !show ) { + continue; + } + + gameRenderWorld->DebugBounds( colorOrange, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( ent->name.c_str(), ent->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), ent->GetPhysics()->GetAbsBounds().GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + if ( ent->IsType( idTrigger::Type ) ) { + func = static_cast( ent )->GetScriptFunction(); + } else { + func = NULL; + } + + if ( func ) { + gameRenderWorld->DrawText( va( "call script '%s'", func->Name() ), ent->GetPhysics()->GetAbsBounds().GetCenter() - up, 0.1f, colorWhite, axis, 1 ); + } + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen, box, target->GetPhysics()->GetOrigin() ); + if ( viewTextBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( target->name.c_str(), target->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + } + } + } + } + } +} + +/* +================ +idTrigger::Enable +================ +*/ +void idTrigger::Enable() { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); +} + +/* +================ +idTrigger::Disable +================ +*/ +void idTrigger::Disable() { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); +} + +/* +================ +idTrigger::CallScript +================ +*/ +void idTrigger::CallScript() const { + idThread *thread; + + if ( scriptFunction ) { + thread = new idThread( scriptFunction ); + thread->DelayedStart( 0 ); + } +} + +/* +================ +idTrigger::GetScriptFunction +================ +*/ +const function_t *idTrigger::GetScriptFunction() const { + return scriptFunction; +} + +/* +================ +idTrigger::Save +================ +*/ +void idTrigger::Save( idSaveGame *savefile ) const { + if ( scriptFunction ) { + savefile->WriteString( scriptFunction->Name() ); + } else { + savefile->WriteString( "" ); + } +} + +/* +================ +idTrigger::Restore +================ +*/ +void idTrigger::Restore( idRestoreGame *savefile ) { + idStr funcname; + savefile->ReadString( funcname ); + if ( funcname.Length() ) { + scriptFunction = gameLocal.program.FindFunction( funcname ); + if ( scriptFunction == NULL ) { + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) calls unknown function '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), funcname.c_str() ); + } + } else { + scriptFunction = NULL; + } +} + +/* +================ +idTrigger::Event_Enable +================ +*/ +void idTrigger::Event_Enable() { + Enable(); +} + +/* +================ +idTrigger::Event_Disable +================ +*/ +void idTrigger::Event_Disable() { + Disable(); +} + +/* +================ +idTrigger::idTrigger +================ +*/ +idTrigger::idTrigger() { + scriptFunction = NULL; +} + +/* +================ +idTrigger::Spawn +================ +*/ +void idTrigger::Spawn() { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + + idStr funcname = spawnArgs.GetString( "call", "" ); + if ( funcname.Length() ) { + scriptFunction = gameLocal.program.FindFunction( funcname ); + if ( scriptFunction == NULL ) { + gameLocal.Warning( "trigger '%s' at (%s) calls unknown function '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), funcname.c_str() ); + } + } else { + scriptFunction = NULL; + } +} + + +/* +=============================================================================== + + idTrigger_Multi + +=============================================================================== +*/ + +const idEventDef EV_TriggerAction( "", "e" ); + +CLASS_DECLARATION( idTrigger, idTrigger_Multi ) + EVENT( EV_Touch, idTrigger_Multi::Event_Touch ) + EVENT( EV_Activate, idTrigger_Multi::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Multi::Event_TriggerAction ) +END_CLASS + + +/* +================ +idTrigger_Multi::idTrigger_Multi +================ +*/ +idTrigger_Multi::idTrigger_Multi() { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + removeItem = 0; + touchClient = false; + touchOther = false; + triggerFirst = false; + triggerWithSelf = false; +} + +/* +================ +idTrigger_Multi::Save +================ +*/ +void idTrigger_Multi::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( touchClient ); + savefile->WriteBool( touchOther ); + savefile->WriteBool( triggerFirst ); + savefile->WriteBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Restore +================ +*/ +void idTrigger_Multi::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( touchClient ); + savefile->ReadBool( touchOther ); + savefile->ReadBool( triggerFirst ); + savefile->ReadBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Spawn + +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"call" : Script function to call when triggered +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +================ +*/ +void idTrigger_Multi::Spawn() { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf ); + + if ( spawnArgs.GetBool( "anyTouch" ) ) { + touchClient = true; + touchOther = true; + } else if ( spawnArgs.GetBool( "noTouch" ) ) { + touchClient = false; + touchOther = false; + } else if ( spawnArgs.GetBool( "noClient" ) ) { + touchClient = false; + touchOther = true; + } else { + touchClient = true; + touchOther = false; + } + + nextTriggerTime = 0; + + if ( spawnArgs.GetBool( "flashlight_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } +} + +/* +================ +idTrigger_Multi::CheckFacing +================ +*/ +bool idTrigger_Multi::CheckFacing( idEntity *activator ) { + if ( spawnArgs.GetBool( "facing" ) ) { + if ( !activator->IsType( idPlayer::Type ) ) { + return true; + } + idPlayer *player = static_cast< idPlayer* >( activator ); + float dot = player->viewAngles.ToForward() * GetPhysics()->GetAxis()[0]; + float angle = RAD2DEG( idMath::ACos( dot ) ); + if ( angle > spawnArgs.GetFloat( "angleLimit", "30" ) ) { + return false; + } + } + return true; +} + + +/* +================ +idTrigger_Multi::TriggerAction +================ +*/ +void idTrigger_Multi::TriggerAction( idEntity *activator ) { + ActivateTargets( triggerWithSelf ? this : activator ); + CallScript(); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + // If the player spawned inside the trigger, the player Spawn function called Think directly, + // allowing for multiple triggers on a trigger_once. Increasing the nextTriggerTime prevents it. + nextTriggerTime = gameLocal.time + 99999; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_Multi::Event_TriggerAction +================ +*/ +void idTrigger_Multi::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_Multi::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_Multi::Event_Trigger( idEntity *activator ) { + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( activator ) ) { + return; + } + + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) { + if ( common->IsClient() ) { + return; + } + + if( triggerFirst ) { + return; + } + + bool player = other->IsType( idPlayer::Type ); + if ( player ) { + if ( !touchClient ) { + return; + } + if ( static_cast< idPlayer * >( other )->spectating ) { + return; + } + } else if ( !touchOther ) { + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + if ( spawnArgs.GetBool( "toggleTriggerFirst" ) ) { + triggerFirst = true; + } + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +=============================================================================== + + idTrigger_EntityName + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_EntityName ) + EVENT( EV_Touch, idTrigger_EntityName::Event_Touch ) + EVENT( EV_Activate, idTrigger_EntityName::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_EntityName::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_EntityName::idTrigger_EntityName +================ +*/ +idTrigger_EntityName::idTrigger_EntityName() { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + triggerFirst = false; + testPartialName = false; +} + +/* +================ +idTrigger_EntityName::Save +================ +*/ +void idTrigger_EntityName::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( triggerFirst ); + savefile->WriteString( entityName ); + savefile->WriteBool( testPartialName ); +} + +/* +================ +idTrigger_EntityName::Restore +================ +*/ +void idTrigger_EntityName::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( triggerFirst ); + savefile->ReadString( entityName ); + savefile->ReadBool( testPartialName ); +} + +/* +================ +idTrigger_EntityName::Spawn +================ +*/ +void idTrigger_EntityName::Spawn() { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + + entityName = spawnArgs.GetString( "entityname" ); + if ( !entityName.Length() ) { + gameLocal.Error( "idTrigger_EntityName '%s' at (%s) doesn't have 'entityname' key specified", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + nextTriggerTime = 0; + + if ( !spawnArgs.GetBool( "noTouch" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + + testPartialName = spawnArgs.GetBool( "testPartialName", testPartialName ); +} + +/* +================ +idTrigger_EntityName::TriggerAction +================ +*/ +void idTrigger_EntityName::TriggerAction( idEntity *activator ) { + ActivateTargets( activator ); + CallScript(); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_EntityName::Event_TriggerAction +================ +*/ +void idTrigger_EntityName::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_EntityName::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_EntityName::Event_Trigger( idEntity *activator ) { + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + bool validEntity = false; + if ( activator ) { + if ( testPartialName ) { + if ( activator->name.Find( entityName, false ) >= 0 ) { + validEntity = true; + } + } + if ( activator->name == entityName ) { + validEntity = true; + } + } + + if ( !validEntity ) { + return; + } + + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + +/* +================ +idTrigger_EntityName::Event_Touch +================ +*/ +void idTrigger_EntityName::Event_Touch( idEntity *other, trace_t *trace ) { + if ( common->IsClient() ) { + return; + } + + if( triggerFirst ) { + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + bool validEntity = false; + if ( other ) { + if ( testPartialName ) { + if ( other->name.Find( entityName, false ) >= 0 ) { + validEntity = true; + } + } + if ( other->name == entityName ) { + validEntity = true; + } + } + + if ( !validEntity ) { + return; + } + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +=============================================================================== + + idTrigger_Timer + +=============================================================================== +*/ + +const idEventDef EV_Timer( "", NULL ); + +CLASS_DECLARATION( idTrigger, idTrigger_Timer ) + EVENT( EV_Timer, idTrigger_Timer::Event_Timer ) + EVENT( EV_Activate, idTrigger_Timer::Event_Use ) +END_CLASS + +/* +================ +idTrigger_Timer::idTrigger_Timer +================ +*/ +idTrigger_Timer::idTrigger_Timer() { + random = 0.0f; + wait = 0.0f; + on = false; + delay = 0.0f; +} + +/* +================ +idTrigger_Timer::Save +================ +*/ +void idTrigger_Timer::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteString( onName ); + savefile->WriteString( offName ); +} + +/* +================ +idTrigger_Timer::Restore +================ +*/ +void idTrigger_Timer::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadString( onName ); + savefile->ReadString( offName ); +} + +/* +================ +idTrigger_Timer::Spawn + +Repeatedly fires its targets. +Can be turned on or off by using. +================ +*/ +void idTrigger_Timer::Spawn() { + spawnArgs.GetFloat( "random", "1", random ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetBool( "start_on", "0", on ); + spawnArgs.GetFloat( "delay", "0", delay ); + onName = spawnArgs.GetString( "onName" ); + offName = spawnArgs.GetString( "offName" ); + + if ( random >= wait && wait >= 0 ) { + random = wait - 0.001; + gameLocal.Warning( "idTrigger_Timer '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( on ) { + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Enable +================ +*/ +void idTrigger_Timer::Enable() { + // if off, turn it on + if ( !on ) { + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Disable +================ +*/ +void idTrigger_Timer::Disable() { + // if on, turn it off + if ( on ) { + on = false; + CancelEvents( &EV_Timer ); + } +} + +/* +================ +idTrigger_Timer::Event_Timer +================ +*/ +void idTrigger_Timer::Event_Timer() { + ActivateTargets( this ); + + // set time before next firing + if ( wait >= 0.0f ) { + PostEventSec( &EV_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } +} + +/* +================ +idTrigger_Timer::Event_Use +================ +*/ +void idTrigger_Timer::Event_Use( idEntity *activator ) { + // if on, turn it off + if ( on ) { + if ( offName.Length() && offName.Icmp( activator->GetName() ) ) { + return; + } + on = false; + CancelEvents( &EV_Timer ); + } else { + // turn it on + if ( onName.Length() && onName.Icmp( activator->GetName() ) ) { + return; + } + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +=============================================================================== + + idTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Count ) + EVENT( EV_Activate, idTrigger_Count::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Count::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_Count::idTrigger_Count +================ +*/ +idTrigger_Count::idTrigger_Count() { + goal = 0; + count = 0; + delay = 0.0f; +} + +/* +================ +idTrigger_Count::Save +================ +*/ +void idTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +idTrigger_Count::Restore +================ +*/ +void idTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +idTrigger_Count::Spawn +================ +*/ +void idTrigger_Count::Spawn() { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +idTrigger_Count::Event_Trigger +================ +*/ +void idTrigger_Count::Event_Trigger( idEntity *activator ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + PostEventSec( &EV_TriggerAction, delay, activator ); + } + } +} + +/* +================ +idTrigger_Count::Event_TriggerAction +================ +*/ +void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { + ActivateTargets( activator ); + CallScript(); + if ( goal == -1 ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +=============================================================================== + + idTrigger_Hurt + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Hurt ) + EVENT( EV_Touch, idTrigger_Hurt::Event_Touch ) + EVENT( EV_Activate, idTrigger_Hurt::Event_Toggle ) +END_CLASS + + +/* +================ +idTrigger_Hurt::idTrigger_Hurt +================ +*/ +idTrigger_Hurt::idTrigger_Hurt() { + on = false; + delay = 0.0f; + nextTime = 0; +} + +/* +================ +idTrigger_Hurt::Save +================ +*/ +void idTrigger_Hurt::Save( idSaveGame *savefile ) const { + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteInt( nextTime ); +} + +/* +================ +idTrigger_Hurt::Restore +================ +*/ +void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadInt( nextTime ); +} + +/* +================ +idTrigger_Hurt::Spawn + + Damages activator + Can be turned on or off by using. +================ +*/ +void idTrigger_Hurt::Spawn() { + spawnArgs.GetBool( "on", "1", on ); + spawnArgs.GetFloat( "delay", "1.0", delay ); + nextTime = gameLocal.time; + Enable(); +} + +/* +================ +idTrigger_Hurt::Event_Touch +================ +*/ +void idTrigger_Hurt::Event_Touch( idEntity *other, trace_t *trace ) { + const char *damage; + + if ( common->IsClient() ) { + return; + } + + if ( on && other && gameLocal.time >= nextTime ) { + bool playerOnly = spawnArgs.GetBool( "playerOnly" ); + if ( playerOnly ) { + if ( !other->IsType( idPlayer::Type ) ) { + return; + } + } + damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); + + idVec3 dir = vec3_origin; + if(spawnArgs.GetBool("kick_from_center", "0")) { + dir = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } + other->Damage( NULL, NULL, dir, damage, 1.0f, INVALID_JOINT ); + + ActivateTargets( other ); + CallScript(); + + nextTime = gameLocal.time + SEC2MS( delay ); + } +} + +/* +================ +idTrigger_Hurt::Event_Toggle +================ +*/ +void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { + on = !on; +} + + +/* +=============================================================================== + + idTrigger_Fade + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Fade ) + EVENT( EV_Activate, idTrigger_Fade::Event_Trigger ) +END_CLASS + +/* +================ +idTrigger_Fade::Event_Trigger +================ +*/ +void idTrigger_Fade::Event_Trigger( idEntity *activator ) { + idVec4 fadeColor; + int fadeTime; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor = spawnArgs.GetVec4( "fadeColor", "0, 0, 0, 1" ); + fadeTime = SEC2MS( spawnArgs.GetFloat( "fadeTime", "0.5" ) ); + player->playerView.Fade( fadeColor, fadeTime ); + PostEventMS( &EV_ActivateTargets, fadeTime, activator ); + } +} + +/* +=============================================================================== + + idTrigger_Touch + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Touch ) + EVENT( EV_Activate, idTrigger_Touch::Event_Trigger ) +END_CLASS + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::idTrigger_Touch() { + clipModel = NULL; +} + +/* +================ +idTrigger_Touch::Spawn +================ +*/ +void idTrigger_Touch::Spawn() { + // get the clip model + clipModel = new (TAG_THREAD) idClipModel( GetPhysics()->GetClipModel() ); + + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTrigger_Touch::Save +================ +*/ +void idTrigger_Touch::Save( idSaveGame *savefile ) { + savefile->WriteClipModel( clipModel ); +} + +/* +================ +idTrigger_Touch::Restore +================ +*/ +void idTrigger_Touch::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( clipModel ); +} + +/* +================ +idTrigger_Touch::TouchEntities +================ +*/ +void idTrigger_Touch::TouchEntities() { + int numClipModels, i; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + + if ( clipModel == NULL || scriptFunction == NULL ) { + return; + } + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + + if ( !gameLocal.clip.ContentsModel( cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->Handle(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { + continue; + } + + ActivateTargets( entity ); + + idThread *thread = new idThread(); + thread->CallFunction( entity, scriptFunction, false ); + thread->DelayedStart( 0 ); + } +} + +/* +================ +idTrigger_Touch::Think +================ +*/ +void idTrigger_Touch::Think() { + if ( thinkFlags & TH_THINK ) { + TouchEntities(); + } + idEntity::Think(); +} + +/* +================ +idTrigger_Touch::Event_Trigger +================ +*/ +void idTrigger_Touch::Event_Trigger( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTrigger_Touch::Enable +================ +*/ +void idTrigger_Touch::Enable() { + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Touch::Disable +================ +*/ +void idTrigger_Touch::Disable() { + BecomeInactive( TH_THINK ); +} + +/* +=============================================================================== + + idTrigger_Flag + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger_Multi, idTrigger_Flag ) + EVENT( EV_Touch, idTrigger_Flag::Event_Touch ) +END_CLASS + +idTrigger_Flag::idTrigger_Flag() { + team = -1; + player = false; + eventFlag = NULL; +} + +void idTrigger_Flag::Spawn() { + team = spawnArgs.GetInt( "team", "0" ); + player = spawnArgs.GetBool( "player", "0" ); + + idStr funcname = spawnArgs.GetString( "eventflag", "" ); + if ( funcname.Length() ) { + eventFlag = idEventDef::FindEvent( funcname );// gameLocal.program.FindFunction( funcname );//, &idItemTeam::Type ); + if ( eventFlag == NULL ) { + gameLocal.Warning( "trigger '%s' at (%s) event unknown '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), funcname.c_str() ); + } + } else { + eventFlag = NULL; + } + + idTrigger_Multi::Spawn(); +} + +void idTrigger_Flag::Event_Touch( idEntity *other, trace_t *trace ) { + idItemTeam * flag = NULL; + + if ( common->IsClient() ) { + return; + } + + if ( player ) { + if ( !other->IsType( idPlayer::Type ) ) + return; + + idPlayer * player = static_cast(other); + if ( player->carryingFlag == false ) + return; + + if ( team != -1 && ( player->team != team || (player->team != 0 && player->team != 1)) ) + return; + + idItemTeam * flags[2]; + + flags[0] = gameLocal.mpGame.GetTeamFlag( 0 ); + flags[1] = gameLocal.mpGame.GetTeamFlag( 1 ); + + int iFriend = 1 - player->team; // index to the flag player team wants + int iOpp = player->team; // index to the flag opp team wants + + // flag is captured if : + // 1)flag is truely bound to the player + // 2)opponent flag has been return + if ( flags[iFriend]->carried && !flags[iFriend]->dropped && //flags[iFriend]->IsBoundTo( player ) && + !flags[iOpp]->carried && !flags[iOpp]->dropped ) + flag = flags[iFriend]; + else + return; + } else { + if ( !other->IsType( idItemTeam::Type ) ) + return; + + idItemTeam * item = static_cast( other ); + + if ( item->team == team || team == -1 ) { + flag = item; + } + else + return; + } + + if ( flag ) { + switch ( eventFlag->GetNumArgs() ) { + default : + case 0 : + flag->PostEventMS( eventFlag, 0 ); + break; + case 1 : + flag->PostEventMS( eventFlag, 0, NULL ); + break; + case 2 : + flag->PostEventMS( eventFlag, 0, NULL, NULL ); + break; + } + +/* + ServerSendEvent( eventFlag->GetEventNum(), NULL, true ); + + idThread *thread; + if ( scriptFlag ) { + thread = new idThread(); + thread->CallFunction( flag, scriptFlag, false ); + thread->DelayedStart( 0 ); + } +*/ + idTrigger_Multi::Event_Touch( other, trace ); + } +} diff --git a/neo/d3xp/Trigger.h b/neo/d3xp/Trigger.h new file mode 100644 index 00000000..8dd9b4de --- /dev/null +++ b/neo/d3xp/Trigger.h @@ -0,0 +1,312 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_TRIGGER_H__ +#define __GAME_TRIGGER_H__ + +extern const idEventDef EV_Enable; +extern const idEventDef EV_Disable; + +/* +=============================================================================== + + Trigger base. + +=============================================================================== +*/ + +class idTrigger : public idEntity { +public: + CLASS_PROTOTYPE( idTrigger ); + + static void DrawDebugInfo(); + + idTrigger(); + void Spawn(); + + const function_t * GetScriptFunction() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Enable(); + virtual void Disable(); + +protected: + void CallScript() const; + + void Event_Enable(); + void Event_Disable(); + + const function_t * scriptFunction; +}; + + +/* +=============================================================================== + + Trigger which can be activated multiple times. + +=============================================================================== +*/ + +class idTrigger_Multi : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Multi ); + + idTrigger_Multi(); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + idStr requires; + int removeItem; + bool touchClient; + bool touchOther; + bool triggerFirst; + bool triggerWithSelf; + + bool CheckFacing( idEntity *activator ); + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Trigger which can only be activated by an entity with a specific name. + +=============================================================================== +*/ + +class idTrigger_EntityName : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_EntityName ); + + idTrigger_EntityName(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + bool triggerFirst; + idStr entityName; + bool testPartialName; + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +/* +=============================================================================== + + Trigger which repeatedly fires targets. + +=============================================================================== +*/ + +class idTrigger_Timer : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Timer ); + + idTrigger_Timer(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + + virtual void Enable(); + virtual void Disable(); + +private: + float random; + float wait; + bool on; + float delay; + idStr onName; + idStr offName; + + void Event_Timer(); + void Event_Use( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ + +class idTrigger_Count : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Count ); + + idTrigger_Count(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + int goal; + int count; + float delay; + + void Event_Trigger( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which hurts touching entities. + +=============================================================================== +*/ + +class idTrigger_Hurt : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Hurt ); + + idTrigger_Hurt(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + bool on; + float delay; + int nextTime; + + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Toggle( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fades the player view. + +=============================================================================== +*/ + +class idTrigger_Fade : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Fade ); + +private: + void Event_Trigger( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which continuously tests whether other entities are touching it. + +=============================================================================== +*/ + +class idTrigger_Touch : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Touch ); + + idTrigger_Touch(); + + void Spawn(); + virtual void Think(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + virtual void Enable(); + virtual void Disable(); + + void TouchEntities(); + +private: + idClipModel * clipModel; + + void Event_Trigger( idEntity *activator ); +}; + +/* +=============================================================================== + + Trigger that responces to CTF flags + +=============================================================================== +*/ +class idTrigger_Flag : public idTrigger_Multi { +public: + CLASS_PROTOTYPE( idTrigger_Flag ); + + idTrigger_Flag(); + void Spawn(); + +private: + int team; + bool player; // flag must be attached/carried by player + + const idEventDef * eventFlag; + + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif /* !__GAME_TRIGGER_H__ */ diff --git a/neo/d3xp/Weapon.cpp b/neo/d3xp/Weapon.cpp new file mode 100644 index 00000000..e0b36a2a --- /dev/null +++ b/neo/d3xp/Weapon.cpp @@ -0,0 +1,4195 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "PredictedValue_impl.h" + +/*********************************************************************** + + idWeapon + +***********************************************************************/ + +// +// event defs +// +const idEventDef EV_Weapon_Clear( "" ); +const idEventDef EV_Weapon_GetOwner( "getOwner", NULL, 'e' ); +const idEventDef EV_Weapon_Next( "nextWeapon" ); +const idEventDef EV_Weapon_State( "weaponState", "sd" ); +const idEventDef EV_Weapon_UseAmmo( "useAmmo", "d" ); +const idEventDef EV_Weapon_AddToClip( "addToClip", "d" ); +const idEventDef EV_Weapon_AmmoInClip( "ammoInClip", NULL, 'f' ); +const idEventDef EV_Weapon_AmmoAvailable( "ammoAvailable", NULL, 'f' ); +const idEventDef EV_Weapon_TotalAmmoCount( "totalAmmoCount", NULL, 'f' ); +const idEventDef EV_Weapon_ClipSize( "clipSize", NULL, 'f' ); +const idEventDef EV_Weapon_WeaponOutOfAmmo( "weaponOutOfAmmo" ); +const idEventDef EV_Weapon_WeaponReady( "weaponReady" ); +const idEventDef EV_Weapon_WeaponReloading( "weaponReloading" ); +const idEventDef EV_Weapon_WeaponHolstered( "weaponHolstered" ); +const idEventDef EV_Weapon_WeaponRising( "weaponRising" ); +const idEventDef EV_Weapon_WeaponLowering( "weaponLowering" ); +const idEventDef EV_Weapon_Flashlight( "flashlight", "d" ); +const idEventDef EV_Weapon_LaunchProjectiles( "launchProjectiles", "dffff" ); +const idEventDef EV_Weapon_CreateProjectile( "createProjectile", NULL, 'e' ); +const idEventDef EV_Weapon_EjectBrass( "ejectBrass" ); +const idEventDef EV_Weapon_Melee( "melee", NULL, 'd' ); +const idEventDef EV_Weapon_GetWorldModel( "getWorldModel", NULL, 'e' ); +const idEventDef EV_Weapon_AllowDrop( "allowDrop", "d" ); +const idEventDef EV_Weapon_AutoReload( "autoReload", NULL, 'f' ); +const idEventDef EV_Weapon_NetReload( "netReload" ); +const idEventDef EV_Weapon_IsInvisible( "isInvisible", NULL, 'f' ); +const idEventDef EV_Weapon_NetEndReload( "netEndReload" ); +const idEventDef EV_Weapon_GrabberHasTarget( "grabberHasTarget", NULL, 'd' ); +const idEventDef EV_Weapon_Grabber( "grabber", "d" ); +const idEventDef EV_Weapon_Grabber_SetGrabDistance( "grabberGrabDistance", "f" ); +const idEventDef EV_Weapon_LaunchProjectilesEllipse( "launchProjectilesEllipse", "dffff" ); +const idEventDef EV_Weapon_LaunchPowerup( "launchPowerup", "sfd" ); +const idEventDef EV_Weapon_StartWeaponSmoke( "startWeaponSmoke" ); +const idEventDef EV_Weapon_StopWeaponSmoke( "stopWeaponSmoke" ); +const idEventDef EV_Weapon_StartWeaponParticle( "startWeaponParticle", "s" ); +const idEventDef EV_Weapon_StopWeaponParticle( "stopWeaponParticle", "s" ); +const idEventDef EV_Weapon_StartWeaponLight( "startWeaponLight", "s" ); +const idEventDef EV_Weapon_StopWeaponLight( "stopWeaponLight", "s" ); + +// +// class def +// +CLASS_DECLARATION( idAnimatedEntity, idWeapon ) + EVENT( EV_Weapon_Clear, idWeapon::Event_Clear ) + EVENT( EV_Weapon_GetOwner, idWeapon::Event_GetOwner ) + EVENT( EV_Weapon_State, idWeapon::Event_WeaponState ) + EVENT( EV_Weapon_WeaponReady, idWeapon::Event_WeaponReady ) + EVENT( EV_Weapon_WeaponOutOfAmmo, idWeapon::Event_WeaponOutOfAmmo ) + EVENT( EV_Weapon_WeaponReloading, idWeapon::Event_WeaponReloading ) + EVENT( EV_Weapon_WeaponHolstered, idWeapon::Event_WeaponHolstered ) + EVENT( EV_Weapon_WeaponRising, idWeapon::Event_WeaponRising ) + EVENT( EV_Weapon_WeaponLowering, idWeapon::Event_WeaponLowering ) + EVENT( EV_Weapon_UseAmmo, idWeapon::Event_UseAmmo ) + EVENT( EV_Weapon_AddToClip, idWeapon::Event_AddToClip ) + EVENT( EV_Weapon_AmmoInClip, idWeapon::Event_AmmoInClip ) + EVENT( EV_Weapon_AmmoAvailable, idWeapon::Event_AmmoAvailable ) + EVENT( EV_Weapon_TotalAmmoCount, idWeapon::Event_TotalAmmoCount ) + EVENT( EV_Weapon_ClipSize, idWeapon::Event_ClipSize ) + EVENT( AI_PlayAnim, idWeapon::Event_PlayAnim ) + EVENT( AI_PlayCycle, idWeapon::Event_PlayCycle ) + EVENT( AI_SetBlendFrames, idWeapon::Event_SetBlendFrames ) + EVENT( AI_GetBlendFrames, idWeapon::Event_GetBlendFrames ) + EVENT( AI_AnimDone, idWeapon::Event_AnimDone ) + EVENT( EV_Weapon_Next, idWeapon::Event_Next ) + EVENT( EV_SetSkin, idWeapon::Event_SetSkin ) + EVENT( EV_Weapon_Flashlight, idWeapon::Event_Flashlight ) + EVENT( EV_Light_GetLightParm, idWeapon::Event_GetLightParm ) + EVENT( EV_Light_SetLightParm, idWeapon::Event_SetLightParm ) + EVENT( EV_Light_SetLightParms, idWeapon::Event_SetLightParms ) + EVENT( EV_Weapon_LaunchProjectiles, idWeapon::Event_LaunchProjectiles ) + EVENT( EV_Weapon_CreateProjectile, idWeapon::Event_CreateProjectile ) + EVENT( EV_Weapon_EjectBrass, idWeapon::Event_EjectBrass ) + EVENT( EV_Weapon_Melee, idWeapon::Event_Melee ) + EVENT( EV_Weapon_GetWorldModel, idWeapon::Event_GetWorldModel ) + EVENT( EV_Weapon_AllowDrop, idWeapon::Event_AllowDrop ) + EVENT( EV_Weapon_AutoReload, idWeapon::Event_AutoReload ) + EVENT( EV_Weapon_NetReload, idWeapon::Event_NetReload ) + EVENT( EV_Weapon_IsInvisible, idWeapon::Event_IsInvisible ) + EVENT( EV_Weapon_NetEndReload, idWeapon::Event_NetEndReload ) + EVENT( EV_Weapon_Grabber, idWeapon::Event_Grabber ) + EVENT( EV_Weapon_GrabberHasTarget, idWeapon::Event_GrabberHasTarget ) + EVENT( EV_Weapon_Grabber_SetGrabDistance, idWeapon::Event_GrabberSetGrabDistance ) + EVENT( EV_Weapon_LaunchProjectilesEllipse, idWeapon::Event_LaunchProjectilesEllipse ) + EVENT( EV_Weapon_LaunchPowerup, idWeapon::Event_LaunchPowerup ) + EVENT( EV_Weapon_StartWeaponSmoke, idWeapon::Event_StartWeaponSmoke ) + EVENT( EV_Weapon_StopWeaponSmoke, idWeapon::Event_StopWeaponSmoke ) + EVENT( EV_Weapon_StartWeaponParticle, idWeapon::Event_StartWeaponParticle ) + EVENT( EV_Weapon_StopWeaponParticle, idWeapon::Event_StopWeaponParticle ) + EVENT( EV_Weapon_StartWeaponLight, idWeapon::Event_StartWeaponLight ) + EVENT( EV_Weapon_StopWeaponLight, idWeapon::Event_StopWeaponLight ) +END_CLASS + + +idCVar cg_projectile_clientAuthoritative_maxCatchup( "cg_projectile_clientAuthoritative_maxCatchup", "500", CVAR_INTEGER, "" ); + +idCVar g_useWeaponDepthHack( "g_useWeaponDepthHack", "1", CVAR_BOOL, "Crunch z depth on weapons" ); + +idCVar g_weaponShadows( "g_weaponShadows", "0", CVAR_BOOL | CVAR_ARCHIVE, "Cast shadows from weapons" ); + +extern idCVar cg_predictedSpawn_debug; + +/*********************************************************************** + + init + +***********************************************************************/ + +/* +================ +idWeapon::idWeapon() +================ +*/ +idWeapon::idWeapon() { + owner = NULL; + worldModel = NULL; + weaponDef = NULL; + thread = NULL; + + memset( &guiLight, 0, sizeof( guiLight ) ); + memset( &muzzleFlash, 0, sizeof( muzzleFlash ) ); + memset( &worldMuzzleFlash, 0, sizeof( worldMuzzleFlash ) ); + memset( &nozzleGlow, 0, sizeof( nozzleGlow ) ); + + muzzleFlashEnd = 0; + flashColor = vec3_origin; + muzzleFlashHandle = -1; + worldMuzzleFlashHandle = -1; + guiLightHandle = -1; + nozzleGlowHandle = -1; + modelDefHandle = -1; + grabberState = -1; + + berserk = 2; + brassDelay = 0; + + allowDrop = true; + isPlayerFlashlight = false; + + fraccos = 0.0f; + fraccos2 = 0.0f; + + Clear(); + + fl.networkSync = true; +} + +/* +================ +idWeapon::~idWeapon() +================ +*/ +idWeapon::~idWeapon() { + Clear(); + delete worldModel.GetEntity(); +} + + +/* +================ +idWeapon::Spawn +================ +*/ +void idWeapon::Spawn() { + if ( !common->IsClient() ) { + // setup the world model + worldModel = static_cast< idAnimatedEntity * >( gameLocal.SpawnEntityType( idAnimatedEntity::Type, NULL ) ); + worldModel.GetEntity()->fl.networkSync = true; + } + + if ( 1 /*!common->IsMultiplayer()*/ ) { + grabber.Initialize(); + } + + thread = new idThread(); + thread->ManualDelete(); + thread->ManualControl(); +} + +/* +================ +idWeapon::SetOwner + +Only called at player spawn time, not each weapon switch +================ +*/ +void idWeapon::SetOwner( idPlayer *_owner ) { + assert( !owner ); + owner = _owner; + SetName( va( "%s_weapon", owner->name.c_str() ) ); + + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetName( va( "%s_weapon_worldmodel", owner->name.c_str() ) ); + } +} + +/* +================ +idWeapon::SetFlashlightOwner + +Only called at player spawn time, not each weapon switch +================ +*/ +void idWeapon::SetFlashlightOwner( idPlayer *_owner ) { + assert( !owner ); + owner = _owner; + SetName( va( "%s_weapon_flashlight", owner->name.c_str() ) ); + + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetName( va( "%s_weapon_flashlight_worldmodel", owner->name.c_str() ) ); + } +} + +/* +================ +idWeapon::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idWeapon::ShouldConstructScriptObjectAtSpawn() const { + return false; +} + +/* +================ +idWeapon::CacheWeapon +================ +*/ +void idWeapon::CacheWeapon( const char *weaponName ) { + const idDeclEntityDef *weaponDef; + const char *brassDefName; + const char *clipModelName; + idTraceModel trm; + const char *guiName; + + weaponDef = gameLocal.FindEntityDef( weaponName, false ); + if ( !weaponDef ) { + return; + } + + // precache the brass collision model + brassDefName = weaponDef->dict.GetString( "def_ejectBrass" ); + if ( brassDefName[0] ) { + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false ); + if ( brassDef ) { + brassDef->dict.GetString( "clipmodel", "", &clipModelName ); + if ( !clipModelName[0] ) { + clipModelName = brassDef->dict.GetString( "model" ); // use the visual model + } + // load the trace model + collisionModelManager->TrmFromModel( clipModelName, trm ); + } + } + + guiName = weaponDef->dict.GetString( "gui" ); + if ( guiName[0] ) { + uiManager->FindGui( guiName, true, false, true ); + } +} + +/* +================ +idWeapon::Save +================ +*/ +void idWeapon::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( status ); + savefile->WriteObject( thread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( animDoneTime ); + savefile->WriteBool( isLinked ); + + savefile->WriteObject( owner ); + worldModel.Save( savefile ); + + savefile->WriteInt( hideTime ); + savefile->WriteFloat( hideDistance ); + savefile->WriteInt( hideStartTime ); + savefile->WriteFloat( hideStart ); + savefile->WriteFloat( hideEnd ); + savefile->WriteFloat( hideOffset ); + savefile->WriteBool( hide ); + savefile->WriteBool( disabled ); + + savefile->WriteInt( berserk ); + + savefile->WriteVec3( playerViewOrigin ); + savefile->WriteMat3( playerViewAxis ); + + savefile->WriteVec3( viewWeaponOrigin ); + savefile->WriteMat3( viewWeaponAxis ); + + savefile->WriteVec3( muzzleOrigin ); + savefile->WriteMat3( muzzleAxis ); + + savefile->WriteVec3( pushVelocity ); + + savefile->WriteString( weaponDef->GetName() ); + savefile->WriteFloat( meleeDistance ); + savefile->WriteString( meleeDefName ); + savefile->WriteInt( brassDelay ); + savefile->WriteString( icon ); + savefile->WriteString( pdaIcon ); + savefile->WriteString( displayName ); + savefile->WriteString( itemDesc ); + + savefile->WriteInt( guiLightHandle ); + savefile->WriteRenderLight( guiLight ); + + savefile->WriteInt( muzzleFlashHandle ); + savefile->WriteRenderLight( muzzleFlash ); + + savefile->WriteInt( worldMuzzleFlashHandle ); + savefile->WriteRenderLight( worldMuzzleFlash ); + + savefile->WriteVec3( flashColor ); + savefile->WriteInt( muzzleFlashEnd ); + savefile->WriteInt( flashTime ); + + savefile->WriteBool( lightOn ); + savefile->WriteBool( silent_fire ); + + savefile->WriteInt( kick_endtime ); + savefile->WriteInt( muzzle_kick_time ); + savefile->WriteInt( muzzle_kick_maxtime ); + savefile->WriteAngles( muzzle_kick_angles ); + savefile->WriteVec3( muzzle_kick_offset ); + + savefile->WriteInt( ammoType ); + savefile->WriteInt( ammoRequired ); + savefile->WriteInt( clipSize ); + savefile->WriteInt( ammoClip.Get() ); + savefile->WriteInt( lowAmmo ); + savefile->WriteBool( powerAmmo ); + + // savegames <= 17 + savefile->WriteInt( 0 ); + + savefile->WriteInt( zoomFov ); + + savefile->WriteJoint( barrelJointView ); + savefile->WriteJoint( flashJointView ); + savefile->WriteJoint( ejectJointView ); + savefile->WriteJoint( guiLightJointView ); + savefile->WriteJoint( ventLightJointView ); + + savefile->WriteJoint( flashJointWorld ); + savefile->WriteJoint( barrelJointWorld ); + savefile->WriteJoint( ejectJointWorld ); + + savefile->WriteBool( hasBloodSplat ); + + savefile->WriteSoundShader( sndHum ); + + savefile->WriteParticle( weaponSmoke ); + savefile->WriteInt( weaponSmokeStartTime ); + savefile->WriteBool( continuousSmoke ); + savefile->WriteParticle( strikeSmoke ); + savefile->WriteInt( strikeSmokeStartTime ); + savefile->WriteVec3( strikePos ); + savefile->WriteMat3( strikeAxis ); + savefile->WriteInt( nextStrikeFx ); + + savefile->WriteBool( nozzleFx ); + savefile->WriteInt( nozzleFxFade ); + + savefile->WriteInt( lastAttack ); + + savefile->WriteInt( nozzleGlowHandle ); + savefile->WriteRenderLight( nozzleGlow ); + + savefile->WriteVec3( nozzleGlowColor ); + savefile->WriteMaterial( nozzleGlowShader ); + savefile->WriteFloat( nozzleGlowRadius ); + + savefile->WriteInt( weaponAngleOffsetAverages ); + savefile->WriteFloat( weaponAngleOffsetScale ); + savefile->WriteFloat( weaponAngleOffsetMax ); + savefile->WriteFloat( weaponOffsetTime ); + savefile->WriteFloat( weaponOffsetScale ); + + savefile->WriteBool( allowDrop ); + savefile->WriteObject( projectileEnt ); + + savefile->WriteStaticObject( grabber ); + savefile->WriteInt( grabberState ); + + savefile->WriteJoint ( smokeJointView ); + + savefile->WriteInt(weaponParticles.Num()); + for(int i = 0; i < weaponParticles.Num(); i++) { + WeaponParticle_t* part = weaponParticles.GetIndex(i); + savefile->WriteString( part->name ); + savefile->WriteString( part->particlename ); + savefile->WriteBool( part->active ); + savefile->WriteInt( part->startTime ); + savefile->WriteJoint( part->joint ); + savefile->WriteBool( part->smoke ); + if(!part->smoke) { + savefile->WriteObject(part->emitter); + } + } + savefile->WriteInt(weaponLights.Num()); + for(int i = 0; i < weaponLights.Num(); i++) { + WeaponLight_t* light = weaponLights.GetIndex(i); + savefile->WriteString( light->name ); + savefile->WriteBool( light->active ); + savefile->WriteInt( light->startTime ); + savefile->WriteJoint( light->joint ); + savefile->WriteInt( light->lightHandle ); + savefile->WriteRenderLight( light->light ); + } + +} + +/* +================ +idWeapon::Restore +================ +*/ +void idWeapon::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( (int &)status ); + savefile->ReadObject( reinterpret_cast( thread ) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( animDoneTime ); + savefile->ReadBool( isLinked ); + + // Re-link script fields + WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); + WEAPON_NETRELOAD.LinkTo( scriptObject, "WEAPON_NETRELOAD" ); + WEAPON_NETENDRELOAD.LinkTo( scriptObject, "WEAPON_NETENDRELOAD" ); + WEAPON_NETFIRING.LinkTo( scriptObject, "WEAPON_NETFIRING" ); + WEAPON_RAISEWEAPON.LinkTo( scriptObject, "WEAPON_RAISEWEAPON" ); + WEAPON_LOWERWEAPON.LinkTo( scriptObject, "WEAPON_LOWERWEAPON" ); + + savefile->ReadObject( reinterpret_cast( owner ) ); + worldModel.Restore( savefile ); + + savefile->ReadInt( hideTime ); + savefile->ReadFloat( hideDistance ); + savefile->ReadInt( hideStartTime ); + savefile->ReadFloat( hideStart ); + savefile->ReadFloat( hideEnd ); + savefile->ReadFloat( hideOffset ); + savefile->ReadBool( hide ); + savefile->ReadBool( disabled ); + + savefile->ReadInt( berserk ); + + savefile->ReadVec3( playerViewOrigin ); + savefile->ReadMat3( playerViewAxis ); + + savefile->ReadVec3( viewWeaponOrigin ); + savefile->ReadMat3( viewWeaponAxis ); + + savefile->ReadVec3( muzzleOrigin ); + savefile->ReadMat3( muzzleAxis ); + + savefile->ReadVec3( pushVelocity ); + + idStr objectname; + savefile->ReadString( objectname ); + weaponDef = gameLocal.FindEntityDef( objectname ); + meleeDef = gameLocal.FindEntityDef( weaponDef->dict.GetString( "def_melee" ), false ); + + const idDeclEntityDef *projectileDef = gameLocal.FindEntityDef( weaponDef->dict.GetString( "def_projectile" ), false ); + if ( projectileDef ) { + projectileDict = projectileDef->dict; + } else { + projectileDict.Clear(); + } + + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( weaponDef->dict.GetString( "def_ejectBrass" ), false ); + if ( brassDef ) { + brassDict = brassDef->dict; + } else { + brassDict.Clear(); + } + + savefile->ReadFloat( meleeDistance ); + savefile->ReadString( meleeDefName ); + savefile->ReadInt( brassDelay ); + savefile->ReadString( icon ); + savefile->ReadString( pdaIcon ); + savefile->ReadString( displayName ); + savefile->ReadString( itemDesc ); + + savefile->ReadInt( guiLightHandle ); + savefile->ReadRenderLight( guiLight ); + if ( guiLightHandle >= 0 ) { + guiLightHandle = gameRenderWorld->AddLightDef( &guiLight ); + } + + savefile->ReadInt( muzzleFlashHandle ); + savefile->ReadRenderLight( muzzleFlash ); + if ( muzzleFlashHandle >= 0 ) { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + savefile->ReadInt( worldMuzzleFlashHandle ); + savefile->ReadRenderLight( worldMuzzleFlash ); + if ( worldMuzzleFlashHandle >= 0 ) { + worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash ); + } + + savefile->ReadVec3( flashColor ); + savefile->ReadInt( muzzleFlashEnd ); + savefile->ReadInt( flashTime ); + + savefile->ReadBool( lightOn ); + savefile->ReadBool( silent_fire ); + + savefile->ReadInt( kick_endtime ); + savefile->ReadInt( muzzle_kick_time ); + savefile->ReadInt( muzzle_kick_maxtime ); + savefile->ReadAngles( muzzle_kick_angles ); + savefile->ReadVec3( muzzle_kick_offset ); + + savefile->ReadInt( (int &)ammoType ); + savefile->ReadInt( ammoRequired ); + savefile->ReadInt( clipSize ); + + int savedAmmoClip = 0; + savefile->ReadInt( savedAmmoClip ); + ammoClip = savedAmmoClip; + + savefile->ReadInt( lowAmmo ); + savefile->ReadBool( powerAmmo ); + + // savegame versions <= 17 + int foo; + savefile->ReadInt( foo ); + + savefile->ReadInt( zoomFov ); + + savefile->ReadJoint( barrelJointView ); + savefile->ReadJoint( flashJointView ); + savefile->ReadJoint( ejectJointView ); + savefile->ReadJoint( guiLightJointView ); + savefile->ReadJoint( ventLightJointView ); + + savefile->ReadJoint( flashJointWorld ); + savefile->ReadJoint( barrelJointWorld ); + savefile->ReadJoint( ejectJointWorld ); + + savefile->ReadBool( hasBloodSplat ); + + savefile->ReadSoundShader( sndHum ); + + savefile->ReadParticle( weaponSmoke ); + savefile->ReadInt( weaponSmokeStartTime ); + savefile->ReadBool( continuousSmoke ); + savefile->ReadParticle( strikeSmoke ); + savefile->ReadInt( strikeSmokeStartTime ); + savefile->ReadVec3( strikePos ); + savefile->ReadMat3( strikeAxis ); + savefile->ReadInt( nextStrikeFx ); + + savefile->ReadBool( nozzleFx ); + savefile->ReadInt( nozzleFxFade ); + + savefile->ReadInt( lastAttack ); + + savefile->ReadInt( nozzleGlowHandle ); + savefile->ReadRenderLight( nozzleGlow ); + if ( nozzleGlowHandle >= 0 ) { + nozzleGlowHandle = gameRenderWorld->AddLightDef( &nozzleGlow ); + } + + savefile->ReadVec3( nozzleGlowColor ); + savefile->ReadMaterial( nozzleGlowShader ); + savefile->ReadFloat( nozzleGlowRadius ); + + savefile->ReadInt( weaponAngleOffsetAverages ); + savefile->ReadFloat( weaponAngleOffsetScale ); + savefile->ReadFloat( weaponAngleOffsetMax ); + savefile->ReadFloat( weaponOffsetTime ); + savefile->ReadFloat( weaponOffsetScale ); + + savefile->ReadBool( allowDrop ); + savefile->ReadObject( reinterpret_cast( projectileEnt ) ); + + savefile->ReadStaticObject( grabber ); + savefile->ReadInt( grabberState ); + + savefile->ReadJoint ( smokeJointView ); + + int particleCount; + savefile->ReadInt( particleCount ); + for(int i = 0; i < particleCount; i++) { + WeaponParticle_t newParticle; + memset(&newParticle, 0, sizeof(newParticle)); + + idStr name, particlename; + savefile->ReadString( name ); + savefile->ReadString( particlename ); + + strcpy( newParticle.name, name.c_str() ); + strcpy( newParticle.particlename, particlename.c_str() ); + + savefile->ReadBool( newParticle.active ); + savefile->ReadInt( newParticle.startTime ); + savefile->ReadJoint( newParticle.joint ); + savefile->ReadBool( newParticle.smoke ); + if(newParticle.smoke) { + newParticle.particle = static_cast( declManager->FindType( DECL_PARTICLE, particlename, false ) ); + } else { + savefile->ReadObject(reinterpret_cast(newParticle.emitter)); + } + + weaponParticles.Set(newParticle.name, newParticle); + } + + int lightCount; + savefile->ReadInt( lightCount ); + for(int i = 0; i < lightCount; i++) { + WeaponLight_t newLight; + memset(&newLight, 0, sizeof(newLight)); + + idStr name; + savefile->ReadString( name ); + strcpy( newLight.name, name.c_str() ); + + savefile->ReadBool( newLight.active ); + savefile->ReadInt( newLight.startTime ); + savefile->ReadJoint( newLight.joint ); + savefile->ReadInt( newLight.lightHandle ); + savefile->ReadRenderLight( newLight.light ); + if ( newLight.lightHandle >= 0 ) { + newLight.lightHandle = gameRenderWorld->AddLightDef( &newLight.light ); + } + weaponLights.Set(newLight.name, newLight); + } +} + +/*********************************************************************** + + Weapon definition management + +***********************************************************************/ + +/* +================ +idWeapon::Clear +================ +*/ +void idWeapon::Clear() { + CancelEvents( &EV_Weapon_Clear ); + + DeconstructScriptObject(); + scriptObject.Free(); + + WEAPON_ATTACK.Unlink(); + WEAPON_RELOAD.Unlink(); + WEAPON_NETRELOAD.Unlink(); + WEAPON_NETENDRELOAD.Unlink(); + WEAPON_NETFIRING.Unlink(); + WEAPON_RAISEWEAPON.Unlink(); + WEAPON_LOWERWEAPON.Unlink(); + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + if ( worldMuzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle ); + worldMuzzleFlashHandle = -1; + } + if ( guiLightHandle != -1 ) { + gameRenderWorld->FreeLightDef( guiLightHandle ); + guiLightHandle = -1; + } + if ( nozzleGlowHandle != -1 ) { + gameRenderWorld->FreeLightDef( nozzleGlowHandle ); + nozzleGlowHandle = -1; + } + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.entityNum = entityNumber; + + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customSkin = NULL; + + // set default shader parms + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[3] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = 0.0f; + renderEntity.shaderParms[5] = 0.0f; + renderEntity.shaderParms[6] = 0.0f; + renderEntity.shaderParms[7] = 0.0f; + + if ( refSound.referenceSound ) { + refSound.referenceSound->Free( true ); + } + memset( &refSound, 0, sizeof( refSound_t ) ); + + // setting diversity to 0 results in no random sound. -1 indicates random. + refSound.diversity = -1.0f; + + if ( owner ) { + // don't spatialize the weapon sounds + refSound.listenerId = owner->GetListenerId(); + } + + // clear out the sounds from our spawnargs since we'll copy them from the weapon def + const idKeyValue *kv = spawnArgs.MatchPrefix( "snd_" ); + while( kv ) { + spawnArgs.Delete( kv->GetKey() ); + kv = spawnArgs.MatchPrefix( "snd_" ); + } + + hideTime = 300; + hideDistance = -15.0f; + hideStartTime = gameLocal.time - hideTime; + hideStart = 0.0f; + hideEnd = 0.0f; + hideOffset = 0.0f; + hide = false; + disabled = false; + + weaponSmoke = NULL; + weaponSmokeStartTime = 0; + continuousSmoke = false; + strikeSmoke = NULL; + strikeSmokeStartTime = 0; + strikePos.Zero(); + strikeAxis = mat3_identity; + nextStrikeFx = 0; + + icon = ""; + pdaIcon = ""; + displayName = ""; + itemDesc = ""; + + playerViewAxis.Identity(); + playerViewOrigin.Zero(); + viewWeaponAxis.Identity(); + viewWeaponOrigin.Zero(); + muzzleAxis.Identity(); + muzzleOrigin.Zero(); + pushVelocity.Zero(); + + status = WP_HOLSTERED; + state = ""; + idealState = ""; + animBlendFrames = 0; + animDoneTime = 0; + + projectileDict.Clear(); + meleeDef = NULL; + meleeDefName = ""; + meleeDistance = 0.0f; + brassDict.Clear(); + + flashTime = 250; + lightOn = false; + silent_fire = false; + + grabberState = -1; + grabber.Update( owner, true ); + + ammoType = 0; + ammoRequired = 0; + ammoClip = 0; + clipSize = 0; + lowAmmo = 0; + powerAmmo = false; + + kick_endtime = 0; + muzzle_kick_time = 0; + muzzle_kick_maxtime = 0; + muzzle_kick_angles.Zero(); + muzzle_kick_offset.Zero(); + + zoomFov = 90; + + barrelJointView = INVALID_JOINT; + flashJointView = INVALID_JOINT; + ejectJointView = INVALID_JOINT; + guiLightJointView = INVALID_JOINT; + ventLightJointView = INVALID_JOINT; + + barrelJointWorld = INVALID_JOINT; + flashJointWorld = INVALID_JOINT; + ejectJointWorld = INVALID_JOINT; + + smokeJointView = INVALID_JOINT; + + //Clean up the weapon particles + for(int i = 0; i < weaponParticles.Num(); i++) { + WeaponParticle_t* part = weaponParticles.GetIndex(i); + if(!part->smoke) { + if ( part->emitter != NULL ) { + //Destroy the emitters + part->emitter->PostEventMS(&EV_Remove, 0 ); + } + } + } + weaponParticles.Clear(); + + //Clean up the weapon lights + for(int i = 0; i < weaponLights.Num(); i++) { + WeaponLight_t* light = weaponLights.GetIndex(i); + if ( light->lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( light->lightHandle ); + } + } + weaponLights.Clear(); + + hasBloodSplat = false; + nozzleFx = false; + nozzleFxFade = 1500; + lastAttack = 0; + nozzleGlowHandle = -1; + nozzleGlowShader = NULL; + nozzleGlowRadius = 10; + nozzleGlowColor.Zero(); + + weaponAngleOffsetAverages = 0; + weaponAngleOffsetScale = 0.0f; + weaponAngleOffsetMax = 0.0f; + weaponOffsetTime = 0.0f; + weaponOffsetScale = 0.0f; + + allowDrop = true; + + animator.ClearAllAnims( gameLocal.time, 0 ); + FreeModelDef(); + + sndHum = NULL; + + isLinked = false; + projectileEnt = NULL; + + isFiring = false; +} + +/* +================ +idWeapon::InitWorldModel +================ +*/ +void idWeapon::InitWorldModel( const idDeclEntityDef *def ) { + idEntity *ent; + + ent = worldModel.GetEntity(); + + assert( ent ); + assert( def ); + + const char *model = def->dict.GetString( "model_world" ); + const char *attach = def->dict.GetString( "joint_attach" ); + + ent->SetSkin( NULL ); + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + if ( ent->GetAnimator()->ModelDef() ) { + ent->SetSkin( ent->GetAnimator()->ModelDef()->GetDefaultSkin() ); + } + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner, attach, true ); + ent->GetPhysics()->SetOrigin( vec3_origin ); + ent->GetPhysics()->SetAxis( mat3_identity ); + + // We don't want to interpolate the world model of weapons, let them + // just bind normally to the player's joint and be driven by the player's + // animation so that the weapon and the player don't appear out of sync. + ent->SetUseClientInterpolation( false ); + + // supress model in player views, but allow it in mirrors and remote views + renderEntity_t *worldModelRenderEntity = ent->GetRenderEntity(); + if ( worldModelRenderEntity ) { + worldModelRenderEntity->suppressSurfaceInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } + + flashJointWorld = ent->GetAnimator()->GetJointHandle( "flash" ); + barrelJointWorld = ent->GetAnimator()->GetJointHandle( "muzzle" ); + ejectJointWorld = ent->GetAnimator()->GetJointHandle( "eject" ); +} + +/* +================ +idWeapon::GetWeaponDef +================ +*/ +void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) { + const char *shader; + const char *objectType; + const char *vmodel; + const char *guiName; + const char *projectileName; + const char *brassDefName; + const char *smokeName; + int ammoAvail; + + Clear(); + + if ( !objectname || !objectname[ 0 ] ) { + return; + } + + assert( owner ); + + weaponDef = gameLocal.FindEntityDef( objectname ); + + ammoType = GetAmmoNumForName( weaponDef->dict.GetString( "ammoType" ) ); + ammoRequired = weaponDef->dict.GetInt( "ammoRequired" ); + clipSize = weaponDef->dict.GetInt( "clipSize" ); + lowAmmo = weaponDef->dict.GetInt( "lowAmmo" ); + + icon = weaponDef->dict.GetString( "icon" ); + pdaIcon = weaponDef->dict.GetString( "pdaIcon" ); + displayName = weaponDef->dict.GetString( "display_name" ); + itemDesc = weaponDef->dict.GetString( "inv_desc" ); + + silent_fire = weaponDef->dict.GetBool( "silent_fire" ); + powerAmmo = weaponDef->dict.GetBool( "powerAmmo" ); + + muzzle_kick_time = SEC2MS( weaponDef->dict.GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( weaponDef->dict.GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = weaponDef->dict.GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = weaponDef->dict.GetVector( "muzzle_kick_offset" ); + + hideTime = SEC2MS( weaponDef->dict.GetFloat( "hide_time", "0.3" ) ); + hideDistance = weaponDef->dict.GetFloat( "hide_distance", "-15" ); + + // muzzle smoke + smokeName = weaponDef->dict.GetString( "smoke_muzzle" ); + if ( *smokeName != '\0' ) { + weaponSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } else { + weaponSmoke = NULL; + } + continuousSmoke = weaponDef->dict.GetBool( "continuousSmoke" ); + weaponSmokeStartTime = ( continuousSmoke ) ? gameLocal.time : 0; + + smokeName = weaponDef->dict.GetString( "smoke_strike" ); + if ( *smokeName != '\0' ) { + strikeSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } else { + strikeSmoke = NULL; + } + strikeSmokeStartTime = 0; + strikePos.Zero(); + strikeAxis = mat3_identity; + nextStrikeFx = 0; + + // setup gui light + memset( &guiLight, 0, sizeof( guiLight ) ); + const char *guiLightShader = weaponDef->dict.GetString( "mtr_guiLightShader" ); + if ( *guiLightShader != '\0' ) { + guiLight.shader = declManager->FindMaterial( guiLightShader, false ); + guiLight.lightRadius[0] = guiLight.lightRadius[1] = guiLight.lightRadius[2] = 3; + guiLight.pointLight = true; + } + + // setup the view model + vmodel = weaponDef->dict.GetString( "model_view" ); + SetModel( vmodel ); + + // setup the world model + InitWorldModel( weaponDef ); + + // copy the sounds from the weapon view model def into out spawnargs + const idKeyValue *kv = weaponDef->dict.MatchPrefix( "snd_" ); + while( kv ) { + spawnArgs.Set( kv->GetKey(), kv->GetValue() ); + kv = weaponDef->dict.MatchPrefix( "snd_", kv ); + } + + // find some joints in the model for locating effects + barrelJointView = animator.GetJointHandle( "barrel" ); + flashJointView = animator.GetJointHandle( "flash" ); + ejectJointView = animator.GetJointHandle( "eject" ); + guiLightJointView = animator.GetJointHandle( "guiLight" ); + ventLightJointView = animator.GetJointHandle( "ventLight" ); + + idStr smokeJoint = weaponDef->dict.GetString("smoke_joint"); + if(smokeJoint.Length() > 0) { + smokeJointView = animator.GetJointHandle( smokeJoint ); + } else { + smokeJointView = INVALID_JOINT; + } + + // get the projectile + projectileDict.Clear(); + + projectileName = weaponDef->dict.GetString( "def_projectile" ); + if ( projectileName[0] != '\0' ) { + const idDeclEntityDef *projectileDef = gameLocal.FindEntityDef( projectileName, false ); + if ( !projectileDef ) { + gameLocal.Warning( "Unknown projectile '%s' in weapon '%s'", projectileName, objectname ); + } else { + const char *spawnclass = projectileDef->dict.GetString( "spawnclass" ); + idTypeInfo *cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::Type ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' on projectile '%s' (used by weapon '%s')", spawnclass, projectileName, objectname ); + } else { + projectileDict = projectileDef->dict; + } + } + } + + // set up muzzleflash render light + const idMaterial*flashShader; + idVec3 flashTarget; + idVec3 flashUp; + idVec3 flashRight; + float flashRadius; + bool flashPointLight; + + weaponDef->dict.GetString( "mtr_flashShader", "", &shader ); + flashShader = declManager->FindMaterial( shader, false ); + flashPointLight = weaponDef->dict.GetBool( "flashPointLight", "1" ); + weaponDef->dict.GetVector( "flashColor", "0 0 0", flashColor ); + flashRadius = (float)weaponDef->dict.GetInt( "flashRadius" ); // if 0, no light will spawn + flashTime = SEC2MS( weaponDef->dict.GetFloat( "flashTime", "0.25" ) ); + flashTarget = weaponDef->dict.GetVector( "flashTarget" ); + flashUp = weaponDef->dict.GetVector( "flashUp" ); + flashRight = weaponDef->dict.GetVector( "flashRight" ); + + memset( &muzzleFlash, 0, sizeof( muzzleFlash ) ); + muzzleFlash.lightId = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + muzzleFlash.allowLightInViewID = owner->entityNumber+1; + + // the weapon lights will only be in first person + guiLight.allowLightInViewID = owner->entityNumber+1; + nozzleGlow.allowLightInViewID = owner->entityNumber+1; + + muzzleFlash.pointLight = flashPointLight; + muzzleFlash.shader = flashShader; + muzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + muzzleFlash.lightRadius[0] = flashRadius; + muzzleFlash.lightRadius[1] = flashRadius; + muzzleFlash.lightRadius[2] = flashRadius; + + if ( !flashPointLight ) { + muzzleFlash.target = flashTarget; + muzzleFlash.up = flashUp; + muzzleFlash.right = flashRight; + muzzleFlash.end = flashTarget; + } + + // the world muzzle flash is the same, just positioned differently + worldMuzzleFlash = muzzleFlash; + worldMuzzleFlash.suppressLightInViewID = owner->entityNumber+1; + worldMuzzleFlash.allowLightInViewID = 0; + worldMuzzleFlash.lightId = LIGHTID_WORLD_MUZZLE_FLASH + owner->entityNumber; + + //----------------------------------- + + nozzleFx = weaponDef->dict.GetBool("nozzleFx"); + nozzleFxFade = weaponDef->dict.GetInt("nozzleFxFade", "1500"); + nozzleGlowColor = weaponDef->dict.GetVector("nozzleGlowColor", "1 1 1"); + nozzleGlowRadius = weaponDef->dict.GetFloat("nozzleGlowRadius", "10"); + weaponDef->dict.GetString( "mtr_nozzleGlowShader", "", &shader ); + nozzleGlowShader = declManager->FindMaterial( shader, false ); + + // get the melee damage def + meleeDistance = weaponDef->dict.GetFloat( "melee_distance" ); + meleeDefName = weaponDef->dict.GetString( "def_melee" ); + if ( meleeDefName.Length() ) { + meleeDef = gameLocal.FindEntityDef( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName.c_str() ); + } + } + + // get the brass def + brassDict.Clear(); + brassDelay = weaponDef->dict.GetInt( "ejectBrassDelay", "0" ); + brassDefName = weaponDef->dict.GetString( "def_ejectBrass" ); + + if ( brassDefName[0] ) { + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false ); + if ( !brassDef ) { + gameLocal.Warning( "Unknown brass '%s'", brassDefName ); + } else { + brassDict = brassDef->dict; + } + } + + if ( ( ammoType < 0 ) || ( ammoType >= AMMO_NUMTYPES ) ) { + gameLocal.Warning( "Unknown ammotype in object '%s'", objectname ); + } + + ammoClip = ammoinclip; + if ( ( ammoClip.Get() < 0 ) || ( ammoClip.Get() > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoClip.Get() > ammoAvail ) { + ammoClip = ammoAvail; + } + //In D3XP we use ammo as soon as it is moved into the clip. This allows for weapons that share ammo + owner->inventory.UseAmmo( ammoType, ammoClip.Get() ); + } + + renderEntity.gui[ 0 ] = NULL; + guiName = weaponDef->dict.GetString( "gui" ); + if ( guiName[0] ) { + renderEntity.gui[ 0 ] = uiManager->FindGui( guiName, true, false, true ); + } + + zoomFov = weaponDef->dict.GetInt( "zoomFov", "70" ); + berserk = weaponDef->dict.GetInt( "berserk", "2" ); + + weaponAngleOffsetAverages = weaponDef->dict.GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = weaponDef->dict.GetFloat( "weaponAngleOffsetScale", "0.25" ); + weaponAngleOffsetMax = weaponDef->dict.GetFloat( "weaponAngleOffsetMax", "10" ); + + weaponOffsetTime = weaponDef->dict.GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = weaponDef->dict.GetFloat( "weaponOffsetScale", "0.005" ); + + if ( !weaponDef->dict.GetString( "weapon_scriptobject", NULL, &objectType ) ) { + gameLocal.Error( "No 'weapon_scriptobject' set on '%s'.", objectname ); + } + + // setup script object + if ( !scriptObject.SetType( objectType ) ) { + gameLocal.Error( "Script object '%s' not found on weapon '%s'.", objectType, objectname ); + } + + WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); + WEAPON_NETRELOAD.LinkTo( scriptObject, "WEAPON_NETRELOAD" ); + WEAPON_NETENDRELOAD.LinkTo( scriptObject, "WEAPON_NETENDRELOAD" ); + WEAPON_NETFIRING.LinkTo( scriptObject, "WEAPON_NETFIRING" ); + WEAPON_RAISEWEAPON.LinkTo( scriptObject, "WEAPON_RAISEWEAPON" ); + WEAPON_LOWERWEAPON.LinkTo( scriptObject, "WEAPON_LOWERWEAPON" ); + + spawnArgs = weaponDef->dict; + + shader = spawnArgs.GetString( "snd_hum" ); + if ( shader && *shader ) { + sndHum = declManager->FindSound( shader ); + StartSoundShader( sndHum, SND_CHANNEL_BODY, 0, false, NULL ); + } + + isLinked = true; + + // call script object's constructor + ConstructScriptObject(); + + // make sure we have the correct skin + UpdateSkin(); + + idEntity *ent = worldModel.GetEntity(); + DetermineTimeGroup( weaponDef->dict.GetBool( "slowmo", "0" ) ); + if ( ent ) { + ent->DetermineTimeGroup( weaponDef->dict.GetBool( "slowmo", "0" ) ); + } + + //Initialize the particles + if ( !common->IsMultiplayer() ) { + + const idKeyValue *pkv = weaponDef->dict.MatchPrefix( "weapon_particle", NULL ); + while( pkv ) { + WeaponParticle_t newParticle; + memset( &newParticle, 0, sizeof( newParticle ) ); + + idStr name = pkv->GetValue(); + + strcpy(newParticle.name, name.c_str()); + + idStr jointName = weaponDef->dict.GetString(va("%s_joint", name.c_str())); + newParticle.joint = animator.GetJointHandle(jointName.c_str()); + newParticle.smoke = weaponDef->dict.GetBool(va("%s_smoke", name.c_str())); + newParticle.active = false; + newParticle.startTime = 0; + + idStr particle = weaponDef->dict.GetString(va("%s_particle", name.c_str())); + strcpy(newParticle.particlename, particle.c_str()); + + if(newParticle.smoke) { + newParticle.particle = static_cast( declManager->FindType( DECL_PARTICLE, particle, false ) ); + } else { + idDict args; + + const idDeclEntityDef *emitterDef = gameLocal.FindEntityDef( "func_emitter", false ); + args = emitterDef->dict; + args.Set("model", particle.c_str()); + args.SetBool("start_off", true); + + idEntity* ent; + gameLocal.SpawnEntityDef(args, &ent, false); + newParticle.emitter = (idFuncEmitter*)ent; + + if ( newParticle.emitter != NULL ) { + newParticle.emitter->BecomeActive(TH_THINK); + } + } + + weaponParticles.Set(name.c_str(), newParticle); + + pkv = weaponDef->dict.MatchPrefix( "weapon_particle", pkv ); + } + + const idKeyValue *lkv = weaponDef->dict.MatchPrefix( "weapon_light", NULL ); + while( lkv ) { + WeaponLight_t newLight; + memset( &newLight, 0, sizeof( newLight ) ); + + newLight.lightHandle = -1; + newLight.active = false; + newLight.startTime = 0; + + idStr name = lkv->GetValue(); + strcpy(newLight.name, name.c_str()); + + idStr jointName = weaponDef->dict.GetString(va("%s_joint", name.c_str())); + newLight.joint = animator.GetJointHandle(jointName.c_str()); + + idStr shader = weaponDef->dict.GetString(va("%s_shader", name.c_str())); + newLight.light.shader = declManager->FindMaterial( shader, false ); + + float radius = weaponDef->dict.GetFloat(va("%s_radius", name.c_str())); + newLight.light.lightRadius[0] = newLight.light.lightRadius[1] = newLight.light.lightRadius[2] = radius; + newLight.light.pointLight = true; + newLight.light.noShadows = true; + + newLight.light.allowLightInViewID = owner->entityNumber+1; + + weaponLights.Set(name.c_str(), newLight); + + lkv = weaponDef->dict.MatchPrefix( "weapon_light", lkv ); + } + } +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +idWeapon::Icon +================ +*/ +const char *idWeapon::Icon() const { + return icon; +} + +/* +================ +idWeapon::PdaIcon +================ +*/ +const char *idWeapon::PdaIcon() const { + return pdaIcon; +} + +/* +================ +idWeapon::DisplayName +================ +*/ +const char * idWeapon::DisplayName() const { + return idLocalization::GetString( displayName ); +} + +/* +================ +idWeapon::Description +================ +*/ +const char * idWeapon::Description() const { + return idLocalization::GetString( itemDesc ); +} + +/* +================ +idWeapon::UpdateGUI +================ +*/ +void idWeapon::UpdateGUI() { + if ( !renderEntity.gui[ 0 ] ) { + return; + } + + if ( status == WP_HOLSTERED ) { + return; + } + + if ( owner->weaponGone ) { + // dropping weapons was implemented wierd, so we have to not update the gui when it happens or we'll get a negative ammo count + return; + } + + if ( !owner->IsLocallyControlled() ) { + // if updating the hud for a followed client + if ( gameLocal.GetLocalClientNum() >= 0 && gameLocal.entities[ gameLocal.GetLocalClientNum() ] && gameLocal.entities[ gameLocal.GetLocalClientNum() ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetLocalClientNum() ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } else { + return; + } + } + + int inclip = AmmoInClip(); + int ammoamount = AmmoAvailable(); + + if ( ammoamount < 0 ) { + // show infinite ammo + renderEntity.gui[ 0 ]->SetStateString( "player_ammo", "" ); + } else { + // show remaining ammo + renderEntity.gui[ 0 ]->SetStateString( "player_totalammo", va( "%i", ammoamount) ); + renderEntity.gui[ 0 ]->SetStateString( "player_ammo", ClipSize() ? va( "%i", inclip ) : "--" ); + renderEntity.gui[ 0 ]->SetStateString( "player_clips", ClipSize() ? va("%i", ammoamount / ClipSize()) : "--" ); + + renderEntity.gui[ 0 ]->SetStateString( "player_allammo", va( "%i/%i", inclip, ammoamount ) ); + } + renderEntity.gui[ 0 ]->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); + renderEntity.gui[ 0 ]->SetStateBool( "player_clip_empty", ( inclip == 0 ) ); + renderEntity.gui[ 0 ]->SetStateBool( "player_clip_low", ( inclip <= lowAmmo ) ); + + //Let the HUD know the total amount of ammo regardless of the ammo required value + renderEntity.gui[ 0 ]->SetStateString( "player_ammo_count", va("%i", AmmoCount())); + + //Grabber Gui Info + renderEntity.gui[ 0 ]->SetStateString( "grabber_state", va("%i", grabberState)); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ + +/* +================ +idWeapon::UpdateFlashPosition +================ +*/ +void idWeapon::UpdateFlashPosition() { + // the flash has an explicit joint for locating it + GetGlobalJointTransform( true, flashJointView, muzzleFlash.origin, muzzleFlash.axis ); + + if ( isPlayerFlashlight ) { + static float pscale = 2.0f; + static float yscale = 0.25f; + +// static idVec3 baseAdjustPos = vec3_zero; //idVec3( 0.0f, 10.0f, 0.0f ); +// idVec3 adjustPos = baseAdjustPos; +// muzzleFlash.origin += adjustPos.x * muzzleFlash.axis[1] + adjustPos.y * muzzleFlash.axis[0] + adjustPos.z * muzzleFlash.axis[2]; + muzzleFlash.origin += owner->GetViewBob(); + +// static idAngles baseAdjustAng = ang_zero; //idAngles( 0.0f, 10.0f, 0.0f ); + idAngles adjustAng = /*baseAdjustAng +*/ idAngles( fraccos * yscale, 0.0f, fraccos2 * pscale ); + idAngles bobAngles = owner->GetViewBobAngles(); + SwapValues( bobAngles.pitch, bobAngles.roll ); + adjustAng += bobAngles * 3.0f; + muzzleFlash.axis = adjustAng.ToMat3() * muzzleFlash.axis /** adjustAng.ToMat3()*/; + } + + // if the desired point is inside or very close to a wall, back it up until it is clear + idVec3 start = muzzleFlash.origin - playerViewAxis[0] * 16; + idVec3 end = muzzleFlash.origin + playerViewAxis[0] * 8; + trace_t tr; + gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL, owner ); + // be at least 8 units away from a solid + muzzleFlash.origin = tr.endpos - playerViewAxis[0] * 8; + + muzzleFlash.noShadows = !g_weaponShadows.GetBool(); + + // put the world muzzle flash on the end of the joint, no matter what + GetGlobalJointTransform( false, flashJointWorld, worldMuzzleFlash.origin, worldMuzzleFlash.axis ); +} + +/* +================ +idWeapon::MuzzleFlashLight +================ +*/ +void idWeapon::MuzzleFlashLight() { + + if ( !lightOn && ( !g_muzzleFlash.GetBool() || !muzzleFlash.lightRadius[0] ) ) { + return; + } + + if ( flashJointView == INVALID_JOINT ) { + return; + } + + UpdateFlashPosition(); + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; + + worldMuzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + worldMuzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + flashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash ); + } +} + +/* +================ +idWeapon::UpdateSkin +================ +*/ +bool idWeapon::UpdateSkin() { + const function_t *func; + + if ( !isLinked ) { + return false; + } + + func = scriptObject.GetFunction( "UpdateSkin" ); + if ( !func ) { + common->Warning( "Can't find function 'UpdateSkin' in object '%s'", scriptObject.GetTypeName() ); + return false; + } + + // use the frameCommandThread since it's safe to use outside of framecommands + gameLocal.frameCommandThread->CallFunction( this, func, true ); + gameLocal.frameCommandThread->Execute(); + + return true; +} + + +/* +================ +idWeapon::FlashlightOn +================ +*/ +void idWeapon::FlashlightOn() { + const function_t *func; + + if ( !isLinked ) { + return; + } + + func = scriptObject.GetFunction( "TurnOn" ); + if ( !func ) { + common->Warning( "Can't find function 'TurnOn' in object '%s'", scriptObject.GetTypeName() ); + return; + } + + // use the frameCommandThread since it's safe to use outside of framecommands + gameLocal.frameCommandThread->CallFunction( this, func, true ); + gameLocal.frameCommandThread->Execute(); + + return; +} + + +/* +================ +idWeapon::FlashlightOff +================ +*/ +void idWeapon::FlashlightOff() { + const function_t *func; + + if ( !isLinked ) { + return; + } + + func = scriptObject.GetFunction( "TurnOff" ); + if ( !func ) { + common->Warning( "Can't find function 'TurnOff' in object '%s'", scriptObject.GetTypeName() ); + return; + } + + // use the frameCommandThread since it's safe to use outside of framecommands + gameLocal.frameCommandThread->CallFunction( this, func, true ); + gameLocal.frameCommandThread->Execute(); + + return; +} + +/* +================ +idWeapon::SetModel +================ +*/ +void idWeapon::SetModel( const char *modelname ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + renderEntity.hModel = animator.SetModel( modelname ); + if ( renderEntity.hModel ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + } else { + renderEntity.customSkin = NULL; + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + } + + // hide the model until an animation is played + Hide(); +} + +/* +================ +idWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool idWeapon::GetGlobalJointTransform( bool viewModel, const jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ) { + if ( viewModel ) { + // view model + if ( animator.GetJointTransform( jointHandle, gameLocal.time, offset, axis ) ) { + offset = offset * viewWeaponAxis + viewWeaponOrigin; + axis = axis * viewWeaponAxis; + return true; + } + } else { + // world model + if ( worldModel.GetEntity() && worldModel.GetEntity()->GetAnimator()->GetJointTransform( jointHandle, gameLocal.time, offset, axis ) ) { + offset = worldModel.GetEntity()->GetPhysics()->GetOrigin() + offset * worldModel.GetEntity()->GetPhysics()->GetAxis(); + axis = axis * worldModel.GetEntity()->GetPhysics()->GetAxis(); + return true; + } + } + offset = viewWeaponOrigin; + axis = viewWeaponAxis; + return false; +} + +/* +================ +idWeapon::SetPushVelocity +================ +*/ +void idWeapon::SetPushVelocity( const idVec3 &pushVelocity ) { + this->pushVelocity = pushVelocity; +} + + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +idWeapon::Think +================ +*/ +void idWeapon::Think() { + // do nothing because the present is called from the player through PresentWeapon +} + +/* +================ +idWeapon::Raise +================ +*/ +void idWeapon::Raise() { + if ( isLinked ) { + WEAPON_RAISEWEAPON = true; + } +} + +/* +================ +idWeapon::PutAway +================ +*/ +void idWeapon::PutAway() { + hasBloodSplat = false; + if ( isLinked ) { + WEAPON_LOWERWEAPON = true; + } +} + +/* +================ +idWeapon::Reload +NOTE: this is only for impulse-triggered reload, auto reload is scripted +================ +*/ +void idWeapon::Reload() { + if ( isLinked ) { + WEAPON_RELOAD = true; + } +} + +/* +================ +idWeapon::LowerWeapon +================ +*/ +void idWeapon::LowerWeapon() { + if ( !hide ) { + hideStart = 0.0f; + hideEnd = hideDistance; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + hide = true; + } +} + +/* +================ +idWeapon::RaiseWeapon +================ +*/ +void idWeapon::RaiseWeapon() { + Show(); + + if ( hide ) { + hideStart = hideDistance; + hideEnd = 0.0f; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + hide = false; + } +} + +/* +================ +idWeapon::HideWeapon +================ +*/ +void idWeapon::HideWeapon() { + Hide(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } + muzzleFlashEnd = 0; +} + +/* +================ +idWeapon::ShowWeapon +================ +*/ +void idWeapon::ShowWeapon() { + Show(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Show(); + } + if ( lightOn ) { + MuzzleFlashLight(); + } +} + +/* +================ +idWeapon::HideWorldModel +================ +*/ +void idWeapon::HideWorldModel() { + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +idWeapon::ShowWorldModel +================ +*/ +void idWeapon::ShowWorldModel() { + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Show(); + } +} + +/* +================ +idWeapon::OwnerDied +================ +*/ +void idWeapon::OwnerDied() { + if ( isLinked ) { + SetState( "OwnerDied", 0 ); + thread->Execute(); + + // Update the grabber effects + if ( /*!common->IsMultiplayer() &&*/ grabberState != -1 ) { + grabber.Update( owner, hide ); + } + } + + Hide(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } + + // don't clear the weapon immediately since the owner might have killed himself by firing the weapon + // within the current stack frame + PostEventMS( &EV_Weapon_Clear, 0 ); +} + +/* +================ +idWeapon::BeginAttack +================ +*/ +void idWeapon::BeginAttack() { + if ( status != WP_OUTOFAMMO ) { + lastAttack = gameLocal.time; + } + + if ( !isLinked ) { + return; + } + + if ( !WEAPON_ATTACK ) { + if ( sndHum && grabberState == -1 ) { // _D3XP :: don't stop grabber hum + StopSound( SND_CHANNEL_BODY, false ); + } + } + WEAPON_ATTACK = true; +} + +/* +================ +idWeapon::EndAttack +================ +*/ +void idWeapon::EndAttack() { + if ( !WEAPON_ATTACK.IsLinked() ) { + return; + } + if ( WEAPON_ATTACK ) { + WEAPON_ATTACK = false; + if ( sndHum && grabberState == -1 ) { // _D3XP :: don't stop grabber hum + StartSoundShader( sndHum, SND_CHANNEL_BODY, 0, false, NULL ); + } + } +} + +/* +================ +idWeapon::isReady +================ +*/ +bool idWeapon::IsReady() const { + return !hide && !IsHidden() && ( ( status == WP_RELOAD ) || ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +idWeapon::IsReloading +================ +*/ +bool idWeapon::IsReloading() const { + return ( status == WP_RELOAD ); +} + +/* +================ +idWeapon::IsHolstered +================ +*/ +bool idWeapon::IsHolstered() const { + return ( status == WP_HOLSTERED ); +} + +/* +================ +idWeapon::ShowCrosshair +================ +*/ +bool idWeapon::ShowCrosshair() const { +// JDC: this code would never function as written, I'm assuming they wanted the following behavior +// return !( state == idStr( WP_RISING ) || state == idStr( WP_LOWERING ) || state == idStr( WP_HOLSTERED ) ); + return !( status == WP_RISING || status == WP_LOWERING || status == WP_HOLSTERED || status == WP_RELOAD ); +} + +/* +===================== +idWeapon::CanDrop +===================== +*/ +bool idWeapon::CanDrop() const { + if ( !weaponDef || !worldModel.GetEntity() ) { + return false; + } + const char *classname = weaponDef->dict.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/* +================ +idWeapon::WeaponStolen +================ +*/ +void idWeapon::WeaponStolen() { + assert( !common->IsClient() ); + if ( projectileEnt ) { + if ( isLinked ) { + SetState( "WeaponStolen", 0 ); + thread->Execute(); + } + projectileEnt = NULL; + } + + // set to holstered so we can switch weapons right away + status = WP_HOLSTERED; + + HideWeapon(); +} + +/* +===================== +idWeapon::DropItem +===================== +*/ +idEntity * idWeapon::DropItem( const idVec3 &velocity, int activateDelay, int removeDelay, bool died ) { + if ( !weaponDef || !worldModel.GetEntity() ) { + return NULL; + } + if ( !allowDrop ) { + return NULL; + } + const char *classname = weaponDef->dict.GetString( "def_dropItem" ); + if ( !classname[0] ) { + return NULL; + } + StopSound( SND_CHANNEL_BODY, true ); + StopSound( SND_CHANNEL_BODY3, true ); + + return idMoveableItem::DropItem( classname, worldModel.GetEntity()->GetPhysics()->GetOrigin(), worldModel.GetEntity()->GetPhysics()->GetAxis(), velocity, activateDelay, removeDelay ); +} + +/*********************************************************************** + + Script state management + +***********************************************************************/ + +/* +===================== +idWeapon::SetState +===================== +*/ +void idWeapon::SetState( const char *statename, int blendFrames ) { + const function_t *func; + + if ( !isLinked ) { + return; + } + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + assert( 0 ); + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + thread->CallFunction( this, func, true ); + state = statename; + + animBlendFrames = blendFrames; + if ( g_debugWeapon.GetBool() ) { + gameLocal.Printf( "%d: weapon state : %s\n", gameLocal.time, statename ); + } + + idealState = ""; +} + + +/*********************************************************************** + + Particles/Effects + +***********************************************************************/ + +/* +================ +idWeapon::UpdateNozzelFx +================ +*/ +void idWeapon::UpdateNozzleFx() { + if ( !nozzleFx ) { + return; + } + + // + // shader parms + // + int la = gameLocal.time - lastAttack + 1; + float s = 1.0f; + float l = 0.0f; + if ( la < nozzleFxFade ) { + s = ((float)la / nozzleFxFade); + l = 1.0f - s; + } + renderEntity.shaderParms[5] = s; + renderEntity.shaderParms[6] = l; + + if ( ventLightJointView == INVALID_JOINT ) { + return; + } + + // + // vent light + // + if ( nozzleGlowHandle == -1 ) { + memset(&nozzleGlow, 0, sizeof(nozzleGlow)); + if ( owner ) { + nozzleGlow.allowLightInViewID = owner->entityNumber+1; + } + nozzleGlow.pointLight = true; + nozzleGlow.noShadows = true; + nozzleGlow.lightRadius.x = nozzleGlowRadius; + nozzleGlow.lightRadius.y = nozzleGlowRadius; + nozzleGlow.lightRadius.z = nozzleGlowRadius; + nozzleGlow.shader = nozzleGlowShader; + nozzleGlow.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + nozzleGlow.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + GetGlobalJointTransform( true, ventLightJointView, nozzleGlow.origin, nozzleGlow.axis ); + nozzleGlowHandle = gameRenderWorld->AddLightDef(&nozzleGlow); + } + + GetGlobalJointTransform( true, ventLightJointView, nozzleGlow.origin, nozzleGlow.axis ); + + nozzleGlow.shaderParms[ SHADERPARM_RED ] = nozzleGlowColor.x * s; + nozzleGlow.shaderParms[ SHADERPARM_GREEN ] = nozzleGlowColor.y * s; + nozzleGlow.shaderParms[ SHADERPARM_BLUE ] = nozzleGlowColor.z * s; + gameRenderWorld->UpdateLightDef(nozzleGlowHandle, &nozzleGlow); +} + + +/* +================ +idWeapon::BloodSplat +================ +*/ +bool idWeapon::BloodSplat( float size ) { + float s, c; + idMat3 localAxis, axistemp; + idVec3 localOrigin, normal; + + if ( hasBloodSplat ) { + return true; + } + + hasBloodSplat = true; + + if ( modelDefHandle < 0 ) { + return false; + } + + if ( !GetGlobalJointTransform( true, ejectJointView, localOrigin, localAxis ) ) { + return false; + } + + localOrigin[0] += gameLocal.random.RandomFloat() * -10.0f; + localOrigin[1] += gameLocal.random.RandomFloat() * 1.0f; + localOrigin[2] += gameLocal.random.RandomFloat() * -2.0f; + + normal = idVec3( gameLocal.random.CRandomFloat(), -gameLocal.random.RandomFloat(), -1 ); + normal.Normalize(); + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + localAxis[2] = -normal; + localAxis[2].NormalVectors( axistemp[0], axistemp[1] ); + localAxis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + localAxis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + localAxis[0] *= 1.0f / size; + localAxis[1] *= 1.0f / size; + + idPlane localPlane[2]; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -(localOrigin * localAxis[0]) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -(localOrigin * localAxis[1]) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( "textures/decals/duffysplatgun" ); + + gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr, gameLocal.slow.time ); + + return true; +} + + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +idWeapon::MuzzleRise + +The machinegun and chaingun will incrementally back up as they are being fired +================ +*/ +void idWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = kick_endtime - gameLocal.time; + if ( time <= 0 ) { + return; + } + + if ( muzzle_kick_maxtime <= 0 ) { + return; + } + + if ( time > muzzle_kick_maxtime ) { + time = muzzle_kick_maxtime; + } + + amount = ( float )time / ( float )muzzle_kick_maxtime; + ang = muzzle_kick_angles * amount; + offset = muzzle_kick_offset * amount; + + origin = origin - axis * offset; + axis = ang.ToMat3() * axis; +} + +/* +================ +idWeapon::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idWeapon::ConstructScriptObject() { + const function_t *constructor; + + thread->EndThread(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( !constructor ) { + gameLocal.Error( "Missing constructor on '%s' for weapon", scriptObject.GetTypeName() ); + } + + // init the script object's data + scriptObject.ClearObject(); + thread->CallFunction( this, constructor, true ); + thread->Execute(); + + return thread; +} + +/* +================ +idWeapon::DeconstructScriptObject + +Called during idEntity::~idEntity. Calls the destructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +Not called during idGameLocal::MapShutdown. +================ +*/ +void idWeapon::DeconstructScriptObject() { + const function_t *destructor; + + if ( !thread ) { + return; + } + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + thread->EndThread(); + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and end + thread->CallFunction( this, destructor, true ); + thread->Execute(); + thread->EndThread(); + } + + // clear out the object's memory + scriptObject.ClearObject(); +} + +/* +================ +idWeapon::UpdateScript +================ +*/ +void idWeapon::UpdateScript() { + int count; + + if ( !isLinked ) { + return; + } + + // only update the script on new frames + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, animBlendFrames ); + } + + // update script state, which may call Event_LaunchProjectiles, among other things + count = 10; + while( ( thread->Execute() || idealState.Length() ) && count-- ) { + // happens for weapons with no clip (like grenades) + if ( idealState.Length() ) { + SetState( idealState, animBlendFrames ); + } + } + + WEAPON_RELOAD = false; +} + +/* +================ +idWeapon::AlertMonsters +================ +*/ +void idWeapon::AlertMonsters() { + trace_t tr; + idEntity *ent; + idVec3 end = muzzleFlash.origin + muzzleFlash.axis * muzzleFlash.target; + + gameLocal.clip.TracePoint( tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::Type ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::Type ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } + + // jitter the trace to try to catch cases where a trace down the center doesn't hit the monster + end += muzzleFlash.axis * muzzleFlash.right * idMath::Sin16( MS2SEC( gameLocal.time ) * 31.34f ); + end += muzzleFlash.axis * muzzleFlash.up * idMath::Sin16( MS2SEC( gameLocal.time ) * 12.17f ); + gameLocal.clip.TracePoint( tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::Type ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::Type ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } +} + +/* +================ +idWeapon::GetMuzzlePositionWithHacks + +Some weapons that have a barrel joint either have it pointing in the wrong +direction (rocket launcher), or don't animate it properly (pistol). + +For good 3D TV / head mounted display work, we need to display a laser sight +in the world. + +Fixing the animated meshes would be ideal, but hacking it in code is +the pragmatic move right now. + +Returns false for hands, grenades, and chainsaw. +================ +*/ +bool idWeapon::GetMuzzlePositionWithHacks( idVec3 & origin, idMat3 & axis ) { + // I couldn't find a simple enum to identify the weapons that need + // workaround hacks... + const idStr & weaponIconName = pdaIcon; + + origin = playerViewOrigin; + axis = playerViewAxis; + + if ( weaponIconName == "guis/assets/hud/icons/grenade_new.tga" ) { + return false; + } + + if ( weaponIconName == "guis/assets/hud/icons/chainsaw_new.tga" ) { + return false; + } + + if ( weaponIconName == "guis/assets/hud/icons/soul_cube.tga" ) { + return false; + } + + if ( barrelJointView != INVALID_JOINT ) { + GetGlobalJointTransform( true, barrelJointView, origin, axis ); + } else if ( guiLightJointView != INVALID_JOINT ) { + GetGlobalJointTransform( true, guiLightJointView, origin, axis ); + } else { + return false; + } + + // get better axis joints for weapons where the barrelJointView isn't + // animated properly + idVec3 discardedOrigin; + if ( weaponIconName == "guis/assets/hud/icons/pistol_new.tga" ) { + // muzzle doesn't animate during firing, Bod does + const jointHandle_t bodJoint = animator.GetJointHandle( "Bod" ); + GetGlobalJointTransform( true, bodJoint, discardedOrigin, axis ); + } + if ( weaponIconName == "guis/assets/hud/icons/rocketlauncher_new.tga" ) { + // joint doesn't point straight, so rotate it + std::swap( axis[0], axis[2] ); + } + if ( weaponIconName == "guis/assets/hud/icons/shotgun_new.tga" ) { + // joint doesn't point straight, so rotate it + const jointHandle_t bodJoint = animator.GetJointHandle( "trigger" ); + GetGlobalJointTransform( true, bodJoint, discardedOrigin, axis ); + std::swap( axis[0], axis[2] ); + axis[0] = -axis[0]; + } + + // we probably should fix the above hacks above that are based on texture names above at some + // point + if ( weaponDef != NULL ) { + if ( ( idStr::Icmp( "weapon_shotgun_double", weaponDef->GetName() ) == 0 ) || ( idStr::Icmp( "weapon_shotgun_double_mp", weaponDef->GetName() ) == 0 ) ) { + // joint doesn't point straight, so rotate it + std::swap( axis[0], axis[2] ); + } else if ( idStr::Icmp( "weapon_grabber", weaponDef->GetName() ) == 0 ) { + idVec3 forward = axis[0]; + forward.Normalize(); + const float scaleOffset = 4.0f; + forward *= scaleOffset; + origin += forward; + } + } + + return true; +} + +/* +================ +idWeapon::PresentWeapon +================ +*/ +void idWeapon::PresentWeapon( bool showViewModel ) { + playerViewOrigin = owner->firstPersonViewOrigin; + playerViewAxis = owner->firstPersonViewAxis; + + if ( isPlayerFlashlight ) { + viewWeaponOrigin = playerViewOrigin; + viewWeaponAxis = playerViewAxis; + + fraccos = cos( ( gameLocal.framenum & 255 ) / 127.0f * idMath::PI ); + + static unsigned int divisor = 32; + unsigned int val = ( gameLocal.framenum + gameLocal.framenum / divisor ) & 255; + fraccos2 = cos( val / 127.0f * idMath::PI ); + + static idVec3 baseAdjustPos = idVec3( -8.0f, -20.0f, -10.0f ); // rt, fwd, up + static float pscale = 0.5f; + static float yscale = 0.125f; + idVec3 adjustPos = baseAdjustPos;// + ( idVec3( fraccos, 0.0f, fraccos2 ) * scale ); + viewWeaponOrigin += adjustPos.x * viewWeaponAxis[1] + adjustPos.y * viewWeaponAxis[0] + adjustPos.z * viewWeaponAxis[2]; +// viewWeaponOrigin += owner->viewBob; + + static idAngles baseAdjustAng = idAngles( 88.0f, 10.0f, 0.0f ); // + idAngles adjustAng = baseAdjustAng + idAngles( fraccos * pscale, fraccos2 * yscale, 0.0f ); +// adjustAng += owner->GetViewBobAngles(); + viewWeaponAxis = adjustAng.ToMat3() * viewWeaponAxis; + } else { + // calculate weapon position based on player movement bobbing + owner->CalculateViewWeaponPos( viewWeaponOrigin, viewWeaponAxis ); + + // hide offset is for dropping the gun when approaching a GUI or NPC + // This is simpler to manage than doing the weapon put-away animation + if ( gameLocal.time - hideStartTime < hideTime ) { + float frac = ( float )( gameLocal.time - hideStartTime ) / ( float )hideTime; + if ( hideStart < hideEnd ) { + frac = 1.0f - frac; + frac = 1.0f - frac * frac; + } else { + frac = frac * frac; + } + hideOffset = hideStart + ( hideEnd - hideStart ) * frac; + } else { + hideOffset = hideEnd; + if ( hide && disabled ) { + Hide(); + } + } + viewWeaponOrigin += hideOffset * viewWeaponAxis[ 2 ]; + + // kick up based on repeat firing + MuzzleRise( viewWeaponOrigin, viewWeaponAxis ); + } + + // set the physics position and orientation + GetPhysics()->SetOrigin( viewWeaponOrigin ); + GetPhysics()->SetAxis( viewWeaponAxis ); + UpdateVisuals(); + + // update the weapon script + UpdateScript(); + + UpdateGUI(); + + // update animation + UpdateAnimation(); + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHack = g_useWeaponDepthHack.GetBool(); + + // present the model + if ( showViewModel ) { + Present(); + } else { + FreeModelDef(); + } + + if ( worldModel.GetEntity() && worldModel.GetEntity()->GetRenderEntity() ) { + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( common->IsMultiplayer() || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } + } + + if ( nozzleFx ) { + UpdateNozzleFx(); + } + + // muzzle smoke + if ( showViewModel && !disabled && weaponSmoke && ( weaponSmokeStartTime != 0 ) ) { + // use the barrel joint if available + + if(smokeJointView != INVALID_JOINT) { + GetGlobalJointTransform( true, smokeJointView, muzzleOrigin, muzzleAxis ); + } else if (barrelJointView != INVALID_JOINT) { + GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + } else { + // default to going straight out the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + } + // spit out a particle + if ( !gameLocal.smokeParticles->EmitSmoke( weaponSmoke, weaponSmokeStartTime, gameLocal.random.RandomFloat(), muzzleOrigin, muzzleAxis, timeGroup /*_D3XP*/ ) ) { + weaponSmokeStartTime = ( continuousSmoke ) ? gameLocal.time : 0; + } + } + + if ( showViewModel && strikeSmoke && strikeSmokeStartTime != 0 ) { + // spit out a particle + if ( !gameLocal.smokeParticles->EmitSmoke( strikeSmoke, strikeSmokeStartTime, gameLocal.random.RandomFloat(), strikePos, strikeAxis, timeGroup /*_D3XP*/ ) ) { + strikeSmokeStartTime = 0; + } + } + + if ( showViewModel && !hide ) { + + for( int i = 0; i < weaponParticles.Num(); i++ ) { + WeaponParticle_t* part = weaponParticles.GetIndex(i); + + if(part->active) { + if(part->smoke) { + if(part->joint != INVALID_JOINT) { + GetGlobalJointTransform( true, part->joint, muzzleOrigin, muzzleAxis ); + } else { + // default to going straight out the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + } + if ( !gameLocal.smokeParticles->EmitSmoke( part->particle, part->startTime, gameLocal.random.RandomFloat(), muzzleOrigin, muzzleAxis, timeGroup /*_D3XP*/ ) ) { + part->active = false; // all done + part->startTime = 0; + } + } else { + if ( part->emitter != NULL ) { + //Manually update the position of the emitter so it follows the weapon + renderEntity_t* rendEnt = part->emitter->GetRenderEntity(); + GetGlobalJointTransform( true, part->joint, rendEnt->origin, rendEnt->axis ); + + if ( part->emitter->GetModelDefHandle() != -1 ) { + gameRenderWorld->UpdateEntityDef( part->emitter->GetModelDefHandle(), rendEnt ); + } + } + } + } + } + + for(int i = 0; i < weaponLights.Num(); i++) { + WeaponLight_t* light = weaponLights.GetIndex(i); + + if(light->active) { + + GetGlobalJointTransform( true, light->joint, light->light.origin, light->light.axis ); + if ( ( light->lightHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( light->lightHandle, &light->light ); + } else { + light->lightHandle = gameRenderWorld->AddLightDef( &light->light ); + } + } + } + } + + // Update the grabber effects + if ( grabberState != -1 ) { + grabberState = grabber.Update( owner, hide ); + } + + // remove the muzzle flash light when it's done + if ( ( !lightOn && ( gameLocal.time >= muzzleFlashEnd ) ) || IsHidden() ) { + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + if ( worldMuzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle ); + worldMuzzleFlashHandle = -1; + } + } + + // update the muzzle flash light, so it moves with the gun + if ( muzzleFlashHandle != -1 ) { + UpdateFlashPosition(); + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); + + // wake up monsters with the flashlight + if ( !common->IsMultiplayer() && lightOn && !owner->fl.notarget ) { + AlertMonsters(); + } + } + + // update the gui light + if ( guiLight.lightRadius[0] && guiLightJointView != INVALID_JOINT ) { + GetGlobalJointTransform( true, guiLightJointView, guiLight.origin, guiLight.axis ); + + if ( ( guiLightHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( guiLightHandle, &guiLight ); + } else { + guiLightHandle = gameRenderWorld->AddLightDef( &guiLight ); + } + } + + if ( status != WP_READY && sndHum ) { + StopSound( SND_CHANNEL_BODY, false ); + } + + UpdateSound(); + + // constant rumble... + float highMagnitude = weaponDef->dict.GetFloat( "controllerConstantShakeHighMag" ); + int highDuration = weaponDef->dict.GetInt( "controllerConstantShakeHighTime" ); + float lowMagnitude = weaponDef->dict.GetFloat( "controllerConstantShakeLowMag" ); + int lowDuration = weaponDef->dict.GetInt( "controllerConstantShakeLowTime" ); + + if( owner->IsLocallyControlled() ) { + owner->SetControllerShake( highMagnitude, highDuration, lowMagnitude, lowDuration ); + } +} + +/* +================ +idWeapon::RemoveMuzzleFlashlight +================ +*/ +void idWeapon::RemoveMuzzleFlashlight() { + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + if ( worldMuzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle ); + worldMuzzleFlashHandle = -1; + } +} + +/* +================ +idWeapon::EnterCinematic +================ +*/ +void idWeapon::EnterCinematic() { + StopSound( SND_CHANNEL_ANY, false ); + + if ( isLinked ) { + SetState( "EnterCinematic", 0 ); + thread->Execute(); + + WEAPON_ATTACK = false; + WEAPON_RELOAD = false; + WEAPON_NETRELOAD = false; + WEAPON_NETENDRELOAD = false; + WEAPON_NETFIRING = false; + WEAPON_RAISEWEAPON = false; + WEAPON_LOWERWEAPON = false; + + grabber.Update( this->GetOwner(), true ); + } + + disabled = true; + + LowerWeapon(); +} + +/* +================ +idWeapon::ExitCinematic +================ +*/ +void idWeapon::ExitCinematic() { + disabled = false; + + if ( isLinked ) { + SetState( "ExitCinematic", 0 ); + thread->Execute(); + } + + RaiseWeapon(); +} + +/* +================ +idWeapon::NetCatchup +================ +*/ +void idWeapon::NetCatchup() { + if ( isLinked ) { + SetState( "NetCatchup", 0 ); + thread->Execute(); + } +} + +/* +================ +idWeapon::GetZoomFov +================ +*/ +int idWeapon::GetZoomFov() { + return zoomFov; +} + +/* +================ +idWeapon::GetWeaponAngleOffsets +================ +*/ +void idWeapon::GetWeaponAngleOffsets( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +idWeapon::GetWeaponTimeOffsets +================ +*/ +void idWeapon::GetWeaponTimeOffsets( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + + +/*********************************************************************** + + Ammo + +***********************************************************************/ + +/* +================ +idWeapon::GetAmmoNumForName +================ +*/ +ammo_t idWeapon::GetAmmoNumForName( const char *ammoname ) { + int num; + const idDict *ammoDict; + + assert( ammoname ); + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( ammoDict == NULL ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + return 0; + } + + if ( !ammoname[ 0 ] ) { + return 0; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + + } + + if ( ( num < 0 ) || ( num >= AMMO_NUMTYPES ) ) { + gameLocal.Warning( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, AMMO_NUMTYPES ); + num = 0; + } + + return ( ammo_t )num; +} + +/* +================ +idWeapon::GetAmmoNameForNum +================ +*/ +const char *idWeapon::GetAmmoNameForNum( ammo_t ammonum ) { + int i; + int num; + const idDict *ammoDict; + const idKeyValue *kv; + char text[ 32 ]; + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( ammoDict == NULL ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + return NULL; + } + + sprintf( text, "%d", ammonum ); + + num = ammoDict->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + kv = ammoDict->GetKeyVal( i ); + if ( kv->GetValue() == text ) { + return kv->GetKey(); + } + } + + return NULL; +} + +/* +================ +idWeapon::GetAmmoPickupNameForNum +================ +*/ +const char *idWeapon::GetAmmoPickupNameForNum( ammo_t ammonum ) { + int i; + int num; + const idDict *ammoDict; + const idKeyValue *kv; + + ammoDict = gameLocal.FindEntityDefDict( "ammo_names", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_names'\n" ); + } + + const char *name = GetAmmoNameForNum( ammonum ); + + if ( name != NULL && *name != NULL ) { + num = ammoDict->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + kv = ammoDict->GetKeyVal( i ); + if ( idStr::Icmp( kv->GetKey(), name) == 0 ) { + return kv->GetValue(); + } + } + } + + return ""; +} + +/* +================ +idWeapon::AmmoAvailable +================ +*/ +int idWeapon::AmmoAvailable() const { + if ( owner ) { + return owner->inventory.HasAmmo( ammoType, ammoRequired ); + } else { + if ( g_infiniteAmmo.GetBool() ) { + return 10; // arbitrary number, just so whatever's calling thinks there's sufficient ammo... + } else { + return 0; + } + } +} + +/* +================ +idWeapon::AmmoInClip +================ +*/ +int idWeapon::AmmoInClip() const { + return ammoClip.Get(); +} + +/* +================ +idWeapon::ResetAmmoClip +================ +*/ +void idWeapon::ResetAmmoClip() { + ammoClip = -1; +} + +/* +================ +idWeapon::GetAmmoType +================ +*/ +ammo_t idWeapon::GetAmmoType() const { + return ammoType; +} + +/* +================ +idWeapon::ClipSize +================ +*/ +int idWeapon::ClipSize() const { + return clipSize; +} + +/* +================ +idWeapon::LowAmmo +================ +*/ +int idWeapon::LowAmmo() const { + return lowAmmo; +} + +/* +================ +idWeapon::AmmoRequired +================ +*/ +int idWeapon::AmmoRequired() const { + return ammoRequired; +} + +/* +================ +idWeapon::GetGrabberState + +Returns the current grabberState +================ +*/ +int idWeapon::GetGrabberState() const { + + return grabberState; +} + +/* +================ +idWeapon::AmmoCount + +Returns the total number of rounds regardless of the required ammo +================ +*/ +int idWeapon::AmmoCount() const { + + if ( owner ) { + return owner->inventory.HasAmmo( ammoType, 1 ); + } else { + return 0; + } +} + +/* +================ +idWeapon::WriteToSnapshot +================ +*/ +void idWeapon::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteBits( ammoClip.Get(), ASYNC_PLAYER_INV_CLIP_BITS ); + msg.WriteBits( worldModel.GetSpawnId(), 32 ); + msg.WriteBits( lightOn, 1 ); + msg.WriteBits( isFiring ? 1 : 0, 1 ); +} + +/* +================ +idWeapon::ReadFromSnapshot +================ +*/ +void idWeapon::ReadFromSnapshot( const idBitMsg &msg ) { + const int snapshotAmmoClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); + worldModel.SetSpawnId( msg.ReadBits( 32 ) ); + const bool snapshotLightOn = msg.ReadBits( 1 ) != 0; + isFiring = msg.ReadBits( 1 ) != 0; + + // Local clients predict the ammo in the clip. Only use the ammo cpunt from the snapshot for local clients + // if the server has processed the same usercmd in which we predicted the ammo count. + if ( owner != NULL ) { + ammoClip.UpdateFromSnapshot( snapshotAmmoClip, owner->GetEntityNumber() ); + } + + // WEAPON_NETFIRING is only turned on for other clients we're predicting. not for local client + if ( owner && !owner->IsLocallyControlled() && WEAPON_NETFIRING.IsLinked() ) { + + // immediately go to the firing state so we don't skip fire animations + if ( !WEAPON_NETFIRING && isFiring ) { + idealState = "Fire"; + } + + // immediately switch back to idle + if ( WEAPON_NETFIRING && !isFiring ) { + idealState = "Idle"; + } + + WEAPON_NETFIRING = isFiring; + WEAPON_ATTACK = isFiring; + } + + // Only update the flashlight state if it has changed, and if this isn't the local player. + // The local player sets their flashlight immediately for responsiveness. + if ( owner != NULL && !owner->IsLocallyControlled() && lightOn != snapshotLightOn ) { + if ( snapshotLightOn ) { + FlashlightOn(); + } else { + FlashlightOff(); + } + } +} + +/* +================ +idWeapon::ClientReceiveEvent +================ +*/ +bool idWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_RELOAD: { + // Local clients predict reloads, only process this event for remote clients. + if ( owner != NULL && !owner->IsLocallyControlled() && ( gameLocal.time - time < 1000 ) ) { + if ( WEAPON_NETRELOAD.IsLinked() ) { + WEAPON_NETRELOAD = true; + WEAPON_NETENDRELOAD = false; + } + } + return true; + } + case EVENT_ENDRELOAD: { + // Local clients predict reloads, only process this event for remote clients. + if ( owner != NULL && !owner->IsLocallyControlled() && WEAPON_NETENDRELOAD.IsLinked() ) { + WEAPON_NETENDRELOAD = true; + } + return true; + } + case EVENT_CHANGESKIN: { + int index = gameLocal.ClientRemapDecl( DECL_SKIN, msg.ReadLong() ); + renderEntity.customSkin = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_SKIN, index ) ) : NULL; + UpdateVisuals(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( renderEntity.customSkin ); + } + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +=============== +idWeapon::Event_Clear +=============== +*/ +void idWeapon::Event_Clear() { + Clear(); +} + +/* +=============== +idWeapon::Event_GetOwner +=============== +*/ +void idWeapon::Event_GetOwner() { + idThread::ReturnEntity( owner ); +} + +/* +=============== +idWeapon::Event_WeaponState +=============== +*/ +void idWeapon::Event_WeaponState( const char *statename, int blendFrames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + assert( 0 ); + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + idealState = statename; + + // HACK, Fixes reload animation on player not playing on second reload ( on non local client players, and only with host viewing. ) + if( common->IsMultiplayer() && strcmp( weaponDef->GetName(), "weapon_shotgun_double_mp" ) == 0 ) { + if( strcmp( statename, "Reload" ) != 0 ) { + if( status == WP_RELOAD ) { + status = WP_READY; + } + } + } + + if ( !idealState.Icmp( "Fire" ) ) { + isFiring = true; + } else { + isFiring = false; + } + + animBlendFrames = blendFrames; + thread->DoneProcessing(); +} + +/* +=============== +idWeapon::Event_WeaponReady +=============== +*/ +void idWeapon::Event_WeaponReady() { + status = WP_READY; + idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_READY \n" ); + if ( isLinked ) { + WEAPON_RAISEWEAPON = false; + } + if ( sndHum ) { + StartSoundShader( sndHum, SND_CHANNEL_BODY, 0, false, NULL ); + } + +} + +/* +=============== +idWeapon::Event_WeaponOutOfAmmo +=============== +*/ +void idWeapon::Event_WeaponOutOfAmmo() { + status = WP_OUTOFAMMO; + idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_OUTOFAMMO \n" ); + if ( isLinked ) { + WEAPON_RAISEWEAPON = false; + } +} + +/* +=============== +idWeapon::Event_WeaponReloading +=============== +*/ +void idWeapon::Event_WeaponReloading() { + status = WP_RELOAD; + idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_RELOAD \n" ); +} + +/* +=============== +idWeapon::Event_WeaponHolstered +=============== +*/ +void idWeapon::Event_WeaponHolstered() { + status = WP_HOLSTERED; + idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_HOLSTERED \n" ); + if ( isLinked ) { + WEAPON_LOWERWEAPON = false; + } +} + +/* +=============== +idWeapon::Event_WeaponRising +=============== +*/ +void idWeapon::Event_WeaponRising() { + status = WP_RISING; + idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_RISING \n" ); + if ( isLinked ) { + WEAPON_LOWERWEAPON = false; + } + owner->WeaponRisingCallback(); +} + +/* +=============== +idWeapon::Event_WeaponLowering +=============== +*/ +void idWeapon::Event_WeaponLowering() { + status = WP_LOWERING; + idLib::PrintfIf( g_debugWeapon.GetBool(), "Weapon Status WP_LOWERING \n" ); + if ( isLinked ) { + WEAPON_RAISEWEAPON = false; + } + owner->WeaponLoweringCallback(); +} + +/* +=============== +idWeapon::Event_UseAmmo +=============== +*/ +void idWeapon::Event_UseAmmo( int amount ) { + if ( owner == NULL || ( common->IsClient() && !owner->IsLocallyControlled() ) ) { + return; + } + + owner->inventory.UseAmmo( ammoType, ( powerAmmo ) ? amount : ( amount * ammoRequired ) ); + if ( clipSize && ammoRequired ) { + ammoClip -= powerAmmo ? amount : ( amount * ammoRequired ); + if ( ammoClip.Get() < 0 ) { + ammoClip = 0; + } + } +} + +/* +=============== +idWeapon::Event_AddToClip +=============== +*/ +void idWeapon::Event_AddToClip( int amount ) { + int ammoAvail; + + if ( owner == NULL || ( common->IsClient() && !owner->IsLocallyControlled() ) ) { + return; + } + + int oldAmmo = ammoClip.Get(); + ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ) + AmmoInClip(); + + ammoClip += amount; + if ( ammoClip.Get() > clipSize ) { + ammoClip = clipSize; + } + + + if ( ammoClip.Get() > ammoAvail ) { + ammoClip = ammoAvail; + } + + // for shared ammo we need to use the ammo when it is moved into the clip + int usedAmmo = ammoClip.Get() - oldAmmo; + owner->inventory.UseAmmo(ammoType, usedAmmo); +} + +/* +=============== +idWeapon::Event_AmmoInClip +=============== +*/ +void idWeapon::Event_AmmoInClip() { + int ammo = AmmoInClip(); + idThread::ReturnFloat( ammo ); +} + +/* +=============== +idWeapon::Event_AmmoAvailable +=============== +*/ +void idWeapon::Event_AmmoAvailable() { + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + ammoAvail += AmmoInClip(); + + idThread::ReturnFloat( ammoAvail ); +} + +/* +=============== +idWeapon::Event_TotalAmmoCount +=============== +*/ +void idWeapon::Event_TotalAmmoCount() { + int ammoAvail = owner->inventory.HasAmmo( ammoType, 1 ); + idThread::ReturnFloat( ammoAvail ); +} + +/* +=============== +idWeapon::Event_ClipSize +=============== +*/ +void idWeapon::Event_ClipSize() { + idThread::ReturnFloat( clipSize ); +} + +/* +=============== +idWeapon::Event_AutoReload +=============== +*/ +void idWeapon::Event_AutoReload() { + assert( owner ); + + if ( common->IsClient() && owner != NULL && !owner->IsLocallyControlled() ) { + idThread::ReturnFloat( 0.0f ); + return; + } + + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + lobbyUserID_t & lobbyUserID = gameLocal.lobbyUserIDs[owner->entityNumber]; + idThread::ReturnFloat( lobby.GetLobbyUserWeaponAutoReload( lobbyUserID ) ); +} + +/* +=============== +idWeapon::Event_NetReload +=============== +*/ +void idWeapon::Event_NetReload() { + assert( owner ); + if ( common->IsServer() ) { + ServerSendEvent( EVENT_RELOAD, NULL, false ); + } +} + +/* +=============== +idWeapon::Event_NetEndReload +=============== +*/ +void idWeapon::Event_NetEndReload() { + assert( owner ); + if ( common->IsServer() ) { + ServerSendEvent( EVENT_ENDRELOAD, NULL, false ); + } +} + +/* +=============== +idWeapon::Event_PlayAnim +=============== +*/ +void idWeapon::Event_PlayAnim( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } else { + if ( !( owner && owner->GetInfluenceLevel() ) ) { + Show(); + } + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = animator.CurrentAnim( channel )->GetEndTime(); + if ( worldModel.GetEntity() ) { + anim = worldModel.GetEntity()->GetAnimator()->GetAnim( animname ); + if ( anim ) { + worldModel.GetEntity()->GetAnimator()->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + } + } + animBlendFrames = 0; + idThread::ReturnInt( 0 ); +} + +/* +=============== +idWeapon::Event_PlayCycle +=============== +*/ +void idWeapon::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } else { + if ( !( owner && owner->GetInfluenceLevel() ) ) { + Show(); + } + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = animator.CurrentAnim( channel )->GetEndTime(); + if ( worldModel.GetEntity() ) { + anim = worldModel.GetEntity()->GetAnimator()->GetAnim( animname ); + worldModel.GetEntity()->GetAnimator()->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + } + animBlendFrames = 0; + idThread::ReturnInt( 0 ); +} + +/* +=============== +idWeapon::Event_AnimDone +=============== +*/ +void idWeapon::Event_AnimDone( int channel, int blendFrames ) { + if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idWeapon::Event_SetBlendFrames +=============== +*/ +void idWeapon::Event_SetBlendFrames( int channel, int blendFrames ) { + animBlendFrames = blendFrames; +} + +/* +=============== +idWeapon::Event_GetBlendFrames +=============== +*/ +void idWeapon::Event_GetBlendFrames( int channel ) { + idThread::ReturnInt( animBlendFrames ); +} + +/* +================ +idWeapon::Event_Next +================ +*/ +void idWeapon::Event_Next() { + // change to another weapon if possible + owner->NextBestWeapon(); +} + +/* +================ +idWeapon::Event_SetSkin +================ +*/ +void idWeapon::Event_SetSkin( const char *skinname ) { + const idDeclSkin *skinDecl = NULL; + if ( !skinname || !skinname[ 0 ] ) { + skinDecl = NULL; + } else { + skinDecl = declManager->FindSkin( skinname ); + } + + // Don't update if the skin hasn't changed. + if ( renderEntity.customSkin == skinDecl && worldModel.GetEntity() != NULL && worldModel.GetEntity()->GetSkin() == skinDecl ) { + return; + } + + renderEntity.customSkin = skinDecl; + UpdateVisuals(); + + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( skinDecl ); + } + + // Hack, don't send message if flashlight, because clients process the flashlight instantly. + if ( common->IsServer() && !isPlayerFlashlight ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.InitWrite( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( ( skinDecl != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_SKIN, skinDecl->Index() ) : -1 ); + ServerSendEvent( EVENT_CHANGESKIN, &msg, false ); + } +} + +/* +================ +idWeapon::Event_Flashlight +================ +*/ +void idWeapon::Event_Flashlight( int enable ) { + if ( enable ) { + lightOn = true; + MuzzleFlashLight(); + } else { + lightOn = false; + muzzleFlashEnd = 0; + } +} + +/* +================ +idWeapon::Event_GetLightParm +================ +*/ +void idWeapon::Event_GetLightParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + return; + } + + idThread::ReturnFloat( muzzleFlash.shaderParms[ parmnum ] ); +} + +/* +================ +idWeapon::Event_SetLightParm +================ +*/ +void idWeapon::Event_SetLightParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + return; + } + + muzzleFlash.shaderParms[ parmnum ] = value; + worldMuzzleFlash.shaderParms[ parmnum ] = value; + UpdateVisuals(); +} + +/* +================ +idWeapon::Event_SetLightParms +================ +*/ +void idWeapon::Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + muzzleFlash.shaderParms[ SHADERPARM_RED ] = parm0; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = parm1; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = parm2; + muzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = parm3; + + worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = parm0; + worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = parm1; + worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = parm2; + worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = parm3; + + UpdateVisuals(); +} + +/* +================ +idWeapon::Event_Grabber +================ +*/ +void idWeapon::Event_Grabber( int enable ) { + if ( enable ) { + grabberState = 0; + } else { + grabberState = -1; + } +} + +/* +================ +idWeapon::Event_GrabberHasTarget +================ +*/ +void idWeapon::Event_GrabberHasTarget() { + idThread::ReturnInt( grabberState ); +} + +/* +================ +idWeapon::Event_GrabberSetGrabDistance +================ +*/ +void idWeapon::Event_GrabberSetGrabDistance( float dist ) { + + grabber.SetDragDistance( dist ); +} + +/* +================ +idWeapon::Event_CreateProjectile +================ +*/ +void idWeapon::Event_CreateProjectile() { + if ( !common->IsClient() ) { + projectileEnt = NULL; + gameLocal.SpawnEntityDef( projectileDict, &projectileEnt, false ); + if ( projectileEnt ) { + projectileEnt->SetOrigin( GetPhysics()->GetOrigin() ); + projectileEnt->Bind( owner, false ); + projectileEnt->Hide(); + } + idThread::ReturnEntity( projectileEnt ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +/* +================ +idWeapon::GetProjectileLaunchOriginAndAxis +================ +*/ +void idWeapon::GetProjectileLaunchOriginAndAxis( idVec3 & origin, idMat3 & axis ) { + assert( owner != NULL ); + + // calculate the muzzle position + if ( barrelJointView != INVALID_JOINT && projectileDict.GetBool( "launchFromBarrel" ) ) { + // there is an explicit joint for the muzzle + // GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + GetMuzzlePositionWithHacks( origin, axis ); + } else { + // go straight out of the view + origin = playerViewOrigin; + axis = playerViewAxis; + } + + axis = playerViewAxis; // Fix for plasma rifle not firing correctly on initial shot of a burst fire +} + +/* +================ +idWeapon::Event_LaunchProjectiles +================ +*/ +void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ) { + idProjectile *proj; + idEntity *ent; + int i; + idVec3 dir; + float ang; + float spin; + float distance; + trace_t tr; + idVec3 start; + idVec3 muzzle_pos; + idBounds ownerBounds, projBounds; + + assert( owner != NULL ); + + if ( IsHidden() ) { + return; + } + + if ( !projectileDict.GetNumKeyVals() ) { + const char *classname = weaponDef->dict.GetString( "classname" ); + gameLocal.Warning( "No projectile defined on '%s'", classname ); + return; + } + + // Predict clip ammo on locally controlled MP clients. + if ( common->IsServer() || owner->IsLocallyControlled() ) { + if ( ( clipSize != 0 ) && ( ammoClip.Get() <= 0 ) ) { + return; + } + + // if this is a power ammo weapon ( currently only the bfg ) then make sure + // we only fire as much power as available in each clip + if ( powerAmmo ) { + // power comes in as a float from zero to max + // if we use this on more than the bfg will need to define the max + // in the .def as opposed to just in the script so proper calcs + // can be done here. + dmgPower = ( int )dmgPower + 1; + if ( dmgPower > ammoClip.Get() ) { + dmgPower = ammoClip.Get(); + } + } + + if(clipSize == 0) { + //Weapons with a clip size of 0 launch straight from inventory without moving to a clip + + //In D3XP we used the ammo when the ammo was moved into the clip so we don't want to + //use it now. + owner->inventory.UseAmmo( ammoType, ( powerAmmo ) ? dmgPower : ammoRequired ); + } + + if ( clipSize && ammoRequired && !g_infiniteAmmo.GetBool() ) { + ammoClip -= powerAmmo ? dmgPower : ammoRequired; + } + } + + if ( !silent_fire ) { + // wake up nearby monsters + gameLocal.AlertAI( owner ); + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.realClientTime ); + + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetShaderParm( SHADERPARM_DIVERSITY, renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel.GetEntity()->SetShaderParm( SHADERPARM_TIMEOFFSET, renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // calculate the muzzle position + GetProjectileLaunchOriginAndAxis( muzzleOrigin, muzzleAxis ); + + // add some to the kick time, incrementally moving repeat firing weapons back + if ( kick_endtime < gameLocal.realClientTime ) { + kick_endtime = gameLocal.realClientTime; + } + kick_endtime += muzzle_kick_time; + if ( kick_endtime > gameLocal.realClientTime + muzzle_kick_maxtime ) { + kick_endtime = gameLocal.realClientTime + muzzle_kick_maxtime; + } + + // "Predict" damage effects on clients by just spawning a local projectile that deals no damage. Used only + // for sound & visual effects. Damage will be handled through reliable messages to the host. + const bool isHitscan = projectileDict.GetBool( "net_instanthit" ); + const bool attackerIsLocal = owner->IsLocallyControlled(); + const bool actuallySpawnProjectile = common->IsServer() || attackerIsLocal || isHitscan; + + if ( actuallySpawnProjectile ) { + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + + owner->AddProjectilesFired( num_projectiles ); + + float spreadRad = DEG2RAD( spread ); + for( i = 0; i < num_projectiles; i++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + if ( common->IsClient() ) { + // This is predicted on a client, don't replicate. + // Must be set before spawn, so that the entity can be spawned into the correct area of the entities array. + projectileDict.SetBool( "net_skip_replication", true ); + } else { + projectileDict.SetBool( "net_skip_replication", false ); + } + gameLocal.SpawnEntityDef( projectileDict, &ent, false ); + } + + if ( ent == NULL || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = weaponDef->dict.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + return; + } + + int predictedKey = idEntity::INVALID_PREDICTION_KEY; + + if ( projectileDict.GetBool( "net_instanthit" ) ) { + // don't synchronize this on top of the already predicted effect + ent->fl.networkSync = false; + } else if ( owner != NULL ) { + // Set the prediction key only for non-instanthit projectiles. + if ( common->IsClient() ) { + owner->IncrementFireCount(); + } + predictedKey = gameLocal.GeneratePredictionKey( this, owner, -1 ); + ent->SetPredictedKey( predictedKey ); + } + + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + muzzle_pos = muzzleOrigin + muzzleAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, muzzleAxis[0], distance ) ) { + start = muzzle_pos + distance * muzzleAxis[0]; + } else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); + muzzle_pos = tr.endpos; + } + + // If this is the server simulating a remote client, the client has spawned the projectile in the past. + // The server will catch-up the projectile so that its position will be as if the projectile had spawned + // when the client fired it. + if ( common->IsServer() && owner != NULL && !owner->IsLocallyControlled() && !projectileDict.GetBool( "net_instanthit" ) ) { + int serverTimeOnClient = owner->usercmd.serverGameMilliseconds; + + int delta = idMath::ClampInt( 0, cg_projectile_clientAuthoritative_maxCatchup.GetInteger(), gameLocal.GetServerGameTimeMs() - serverTimeOnClient ); + + int startTime = gameLocal.GetServerGameTimeMs() - delta; + + proj->Launch( muzzle_pos, dir, pushVelocity, fuseOffset, launchPower, dmgPower ); + + // predictively spawned, but no need to simulate - was needed for correct processing of client mines when spawned on the server (because we're futzing with the clip models) + proj->QueueToSimulate( startTime ); + + if ( cg_predictedSpawn_debug.GetBool() ) { + idLib::Printf( "Spawning throw item projectile for player %d. PredictiveKey: %d \n", owner->GetEntityNumber(), predictedKey ); + } + } else { + // Normal launch + proj->Launch( muzzle_pos, dir, pushVelocity, fuseOffset, launchPower, dmgPower ); + } + } + + // toss the brass + if ( brassDelay >= 0 ) { + PostEventMS( &EV_Weapon_EjectBrass, brassDelay ); + } + } + + // add the light for the muzzleflash + if ( !lightOn ) { + MuzzleFlashLight(); + } + + owner->WeaponFireFeedback( &weaponDef->dict ); + + // reset muzzle smoke + weaponSmokeStartTime = gameLocal.realClientTime; +} + +/* +================ +idWeapon::Event_LaunchProjectilesEllipse +================ +*/ +void idWeapon::Event_LaunchProjectilesEllipse( int num_projectiles, float spreada, float spreadb, float fuseOffset, float power ) { + idProjectile *proj; + idEntity *ent; + int i; + idVec3 dir; + float anga, angb; + float spin; + float distance; + trace_t tr; + idVec3 start; + idVec3 muzzle_pos; + idBounds ownerBounds, projBounds; + + if ( IsHidden() ) { + return; + } + + if ( !projectileDict.GetNumKeyVals() ) { + const char *classname = weaponDef->dict.GetString( "classname" ); + gameLocal.Warning( "No projectile defined on '%s'", classname ); + return; + } + + // avoid all ammo considerations on a client + if ( !common->IsClient() ) { + if ( ( clipSize != 0 ) && ( ammoClip.Get() <= 0 ) ) { + return; + } + + if( clipSize == 0 ) { + //Weapons with a clip size of 0 launch strait from inventory without moving to a clip + owner->inventory.UseAmmo( ammoType, ammoRequired ); + } + + if ( clipSize && ammoRequired ) { + ammoClip -= ammoRequired; + } + + if ( !silent_fire ) { + // wake up nearby monsters + gameLocal.AlertAI( owner ); + } + + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetShaderParm( SHADERPARM_DIVERSITY, renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel.GetEntity()->SetShaderParm( SHADERPARM_TIMEOFFSET, renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // calculate the muzzle position + if ( barrelJointView != INVALID_JOINT && projectileDict.GetBool( "launchFromBarrel" ) ) { + // there is an explicit joint for the muzzle + GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + } else { + // go straight out of the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + } + + // add some to the kick time, incrementally moving repeat firing weapons back + if ( kick_endtime < gameLocal.time ) { + kick_endtime = gameLocal.time; + } + kick_endtime += muzzle_kick_time; + if ( kick_endtime > gameLocal.time + muzzle_kick_maxtime ) { + kick_endtime = gameLocal.time + muzzle_kick_maxtime; + } + + if ( !common->IsClient() ) { + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + + owner->AddProjectilesFired( num_projectiles ); + + float spreadRadA = DEG2RAD( spreada ); + float spreadRadB = DEG2RAD( spreadb ); + + for( i = 0; i < num_projectiles; i++ ) { + //Ellipse Form + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + anga = idMath::Sin(spreadRadA * gameLocal.random.RandomFloat()); + angb = idMath::Sin(spreadRadB * gameLocal.random.RandomFloat()); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( angb*idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( anga*idMath::Cos( spin ) ); + dir.Normalize(); + + gameLocal.SpawnEntityDef( projectileDict, &ent ); + if ( ent == NULL || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = weaponDef->dict.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + return; + } + + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } + else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); + muzzle_pos = tr.endpos; + } + + proj->Launch( muzzle_pos, dir, pushVelocity, fuseOffset, power ); + } + + // toss the brass + if( brassDelay >= 0 ) { + PostEventMS( &EV_Weapon_EjectBrass, brassDelay ); + } + } + + // add the light for the muzzleflash + if ( !lightOn ) { + MuzzleFlashLight(); + } + + owner->WeaponFireFeedback( &weaponDef->dict ); + + // reset muzzle smoke + weaponSmokeStartTime = gameLocal.time; + +} + +/** +* Gives the player a powerup as if it were a weapon shot. It will use the ammo amount specified +* as ammoRequired. +*/ +void idWeapon::Event_LaunchPowerup( const char* powerup, float duration, int useAmmo ) { + + if ( IsHidden() ) { + return; + } + + // check if we're out of ammo + if(useAmmo) { + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( !ammoAvail ) { + return; + } + owner->inventory.UseAmmo( ammoType, ammoRequired ); + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetShaderParm( SHADERPARM_DIVERSITY, renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel.GetEntity()->SetShaderParm( SHADERPARM_TIMEOFFSET, renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // add the light for the muzzleflash + if ( !lightOn ) { + MuzzleFlashLight(); + } + + owner->Give(powerup, va("%f", duration), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE | ITEM_GIVE_FROM_WEAPON ); + + +} + +void idWeapon::Event_StartWeaponSmoke() { + + // reset muzzle smoke + weaponSmokeStartTime = gameLocal.time; +} + +void idWeapon::Event_StopWeaponSmoke() { + + // reset muzzle smoke + weaponSmokeStartTime = 0; +} + +void idWeapon::Event_StartWeaponParticle( const char* name) { + WeaponParticle_t* part; + weaponParticles.Get(name, &part); + if(part) { + part->active = true; + part->startTime = gameLocal.time; + + //Toggle the emitter + if( !part->smoke && part->emitter != NULL ) { + part->emitter->Show(); + part->emitter->PostEventMS(&EV_Activate, 0, this); + } + } +} + +void idWeapon::Event_StopWeaponParticle( const char* name) { + WeaponParticle_t* part; + weaponParticles.Get(name, &part); + if(part) { + part->active = false; + part->startTime = 0; + + //Toggle the emitter + if(!part->smoke) { + if ( part->emitter != NULL ) { + part->emitter->Hide(); + part->emitter->PostEventMS(&EV_Activate, 0, this); + } + } + } +} + +void idWeapon::Event_StartWeaponLight( const char* name) { + WeaponLight_t* light; + weaponLights.Get(name, &light); + if(light) { + light->active = true; + light->startTime = gameLocal.time; + } +} + +void idWeapon::Event_StopWeaponLight( const char* name) { + WeaponLight_t* light; + weaponLights.Get(name, &light); + if(light) { + light->active = false; + light->startTime = 0; + if(light->lightHandle != -1) { + gameRenderWorld->FreeLightDef( light->lightHandle ); + light->lightHandle = -1; + } + } +} +/* +===================== +idWeapon::Event_Melee +===================== +*/ +void idWeapon::Event_Melee() { + idEntity *ent; + trace_t tr; + + if ( weaponDef == NULL ) { + gameLocal.Error( "No weaponDef on '%s'", this->GetName() ); + return; + } + + if ( meleeDef == NULL ) { + gameLocal.Error( "No meleeDef on '%s'", weaponDef->dict.GetString( "classname" ) ); + return; + } + + if ( !common->IsClient() ) { + idVec3 start = playerViewOrigin; + idVec3 end = start + playerViewAxis[0] * ( meleeDistance * owner->PowerUpModifier( MELEE_DISTANCE ) ); + gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL, owner ); + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + } else { + ent = NULL; + } + + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, start, end, 100 ); + if ( ent != NULL ) { + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds(), ent->GetPhysics()->GetOrigin(), 100 ); + } + } + + bool hit = false; + const char *hitSound = meleeDef->dict.GetString( "snd_miss" ); + + if ( ent != NULL ) { + + float push = meleeDef->dict.GetFloat( "push" ); + idVec3 impulse = -push * owner->PowerUpModifier( SPEED ) * tr.c.normal; + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) && ( ent->IsType( idActor::Type ) || ent->IsType( idAFAttachment::Type) ) ) { + idThread::ReturnInt( 0 ); + return; + } + + ent->ApplyImpulse( this, tr.c.id, tr.c.point, impulse ); + + // weapon stealing - do this before damaging so weapons are not dropped twice + if ( common->IsMultiplayer() + && weaponDef->dict.GetBool( "stealing" ) + && ent->IsType( idPlayer::Type ) + && !owner->PowerUpActive( BERSERK ) + && ( (gameLocal.gameType != GAME_TDM ) || gameLocal.serverInfo.GetBool( "si_teamDamage" ) || ( owner->team != static_cast< idPlayer * >( ent )->team ) ) + ) { + + if ( !gameLocal.mpGame.IsGametypeFlagBased() ) { + owner->StealWeapon( static_cast< idPlayer * >( ent ) ); + } + } + + if ( ent->fl.takedamage ) { + idVec3 kickDir, globalKickDir; + meleeDef->dict.GetVector( "kickDir", "0 0 0", kickDir ); + globalKickDir = muzzleAxis * kickDir; + //Adjust the melee powerup modifier for the invulnerability boss fight + float mod = owner->PowerUpModifier( MELEE_DAMAGE ); + if(!strcmp(ent->GetEntityDefName(), "monster_hunter_invul")) { + //Only do a quater of the damage mod + mod *= 0.25f; + } + ent->Damage( owner, owner, globalKickDir, meleeDefName, mod, tr.c.id ); + hit = true; + } + + if ( weaponDef->dict.GetBool( "impact_damage_effect" ) ) { + + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + + hitSound = meleeDef->dict.GetString( owner->PowerUpActive( BERSERK ) ? "snd_hit_berserk" : "snd_hit" ); + + ent->AddDamageEffect( tr, impulse, meleeDef->dict.GetString( "classname" ) ); + + } else { + + int type = tr.c.material->GetSurfaceType(); + if ( type == SURFTYPE_NONE ) { + type = GetDefaultSurfaceType(); + } + + const char *materialType = gameLocal.sufaceTypeNames[ type ]; + + // start impact sound based on material type + hitSound = meleeDef->dict.GetString( va( "snd_%s", materialType ) ); + if ( *hitSound == '\0' ) { + hitSound = meleeDef->dict.GetString( "snd_metal" ); + } + + if ( gameLocal.time > nextStrikeFx ) { + const char *decal; + // project decal + decal = weaponDef->dict.GetString( "mtr_strike" ); + if ( decal != NULL && *decal != NULL ) { + gameLocal.ProjectDecal( tr.c.point, -tr.c.normal, 8.0f, true, 6.0, decal ); + } + nextStrikeFx = gameLocal.time + 200; + } else { + hitSound = ""; + } + + strikeSmokeStartTime = gameLocal.time; + strikePos = tr.c.point; + strikeAxis = -tr.endAxis; + } + } + } + + if ( *hitSound != '\0' ) { + const idSoundShader *snd = declManager->FindSound( hitSound ); + StartSoundShader( snd, SND_CHANNEL_BODY2, 0, true, NULL ); + } + + idThread::ReturnInt( hit ); + owner->WeaponFireFeedback( &weaponDef->dict ); + return; + } + + idThread::ReturnInt( 0 ); + owner->WeaponFireFeedback( &weaponDef->dict ); +} + +/* +===================== +idWeapon::Event_GetWorldModel +===================== +*/ +void idWeapon::Event_GetWorldModel() { + idThread::ReturnEntity( worldModel.GetEntity() ); +} + +/* +===================== +idWeapon::Event_AllowDrop +===================== +*/ +void idWeapon::Event_AllowDrop( int allow ) { + if ( allow ) { + allowDrop = true; + } else { + allowDrop = false; + } +} + +/* +================ +idWeapon::Event_EjectBrass + +Toss a shell model out from the breach if the bone is present +================ +*/ +void idWeapon::Event_EjectBrass() { + if ( !g_showBrass.GetBool() || !owner->CanShowWeaponViewmodel() ) { + return; + } + + if ( ejectJointView == INVALID_JOINT || !brassDict.GetNumKeyVals() ) { + return; + } + + if ( common->IsClient() ) { + return; + } + + idMat3 axis; + idVec3 origin, linear_velocity, angular_velocity; + idEntity *ent; + + if ( !GetGlobalJointTransform( true, ejectJointView, origin, axis ) ) { + return; + } + + gameLocal.SpawnEntityDef( brassDict, &ent, false ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'%s' is not an idDebris", weaponDef ? weaponDef->dict.GetString( "def_ejectBrass" ) : "def_ejectBrass" ); + } + idDebris *debris = static_cast(ent); + debris->Create( owner, origin, axis ); + debris->Launch(); + + linear_velocity = 40 * ( playerViewAxis[0] + playerViewAxis[1] + playerViewAxis[2] ); + angular_velocity.Set( 10 * gameLocal.random.CRandomFloat(), 10 * gameLocal.random.CRandomFloat(), 10 * gameLocal.random.CRandomFloat() ); + + debris->GetPhysics()->SetLinearVelocity( linear_velocity ); + debris->GetPhysics()->SetAngularVelocity( angular_velocity ); +} + +/* +=============== +idWeapon::Event_IsInvisible +=============== +*/ +void idWeapon::Event_IsInvisible() { + if ( !owner ) { + idThread::ReturnFloat( 0 ); + return; + } + idThread::ReturnFloat( owner->PowerUpActive( INVISIBILITY ) ? 1 : 0 ); +} + +/* +=============== +idWeapon::ClientThink +=============== +*/ +void idWeapon::ClientThink( const int curTime, const float fraction, const bool predict ) { + UpdateAnimation(); +} + +/* +=============== +idWeapon::ClientPredictionThink +=============== +*/ +void idWeapon::ClientPredictionThink() { + UpdateAnimation(); +} + + +void idWeapon::ForceAmmoInClip() { + ammoClip = clipSize; +} \ No newline at end of file diff --git a/neo/d3xp/Weapon.h b/neo/d3xp/Weapon.h new file mode 100644 index 00000000..fb7dbe33 --- /dev/null +++ b/neo/d3xp/Weapon.h @@ -0,0 +1,447 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_WEAPON_H__ +#define __GAME_WEAPON_H__ + +#include "PredictedValue.h" + +/* +=============================================================================== + + Player Weapon + +=============================================================================== +*/ + +extern const idEventDef EV_Weapon_State; + +typedef enum { + WP_READY, + WP_OUTOFAMMO, + WP_RELOAD, + WP_HOLSTERED, + WP_RISING, + WP_LOWERING +} weaponStatus_t; + +typedef int ammo_t; +static const int AMMO_NUMTYPES = 16; + +class idPlayer; + +static const int LIGHTID_WORLD_MUZZLE_FLASH = 1; +static const int LIGHTID_VIEW_MUZZLE_FLASH = 100; + +class idMoveableItem; + +typedef struct { + char name[64]; + char particlename[128]; + bool active; + int startTime; + jointHandle_t joint; //The joint on which to attach the particle + bool smoke; //Is this a smoke particle + const idDeclParticle* particle; //Used for smoke particles + idFuncEmitter* emitter; //Used for non-smoke particles +} WeaponParticle_t; + +typedef struct { + char name[64]; + bool active; + int startTime; + jointHandle_t joint; + int lightHandle; + renderLight_t light; +} WeaponLight_t; + +class idWeapon : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idWeapon ); + + idWeapon(); + virtual ~idWeapon(); + + // Init + void Spawn(); + void SetOwner( idPlayer *owner ); + idPlayer* GetOwner(); + virtual bool ShouldConstructScriptObjectAtSpawn() const; + void SetFlashlightOwner( idPlayer *owner ); + + static void CacheWeapon( const char *weaponName ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + // Weapon definition management + void Clear(); + void GetWeaponDef( const char *objectname, int ammoinclip ); + bool IsLinked(); + bool IsWorldModelReady(); + + // GUIs + const char * Icon() const; + void UpdateGUI(); + const char * PdaIcon() const; + const char * DisplayName() const; + const char * Description() const; + + virtual void SetModel( const char *modelname ); + bool GetGlobalJointTransform( bool viewModel, const jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ); + void SetPushVelocity( const idVec3 &pushVelocity ); + bool UpdateSkin(); + + // State control/player interface + void Think(); + void Raise(); + void PutAway(); + void Reload(); + void LowerWeapon(); + void RaiseWeapon(); + void HideWeapon(); + void ShowWeapon(); + void HideWorldModel(); + void ShowWorldModel(); + void OwnerDied(); + void BeginAttack(); + void EndAttack(); + bool IsReady() const; + bool IsReloading() const; + bool IsHolstered() const; + bool ShowCrosshair() const; + idEntity * DropItem( const idVec3 &velocity, int activateDelay, int removeDelay, bool died ); + bool CanDrop() const; + void WeaponStolen(); + void ForceAmmoInClip(); + + weaponStatus_t GetStatus() { return status; }; + + + // Script state management + virtual idThread * ConstructScriptObject(); + virtual void DeconstructScriptObject(); + void SetState( const char *statename, int blendFrames ); + void UpdateScript(); + void EnterCinematic(); + void ExitCinematic(); + void NetCatchup(); + + // Visual presentation + void PresentWeapon( bool showViewModel ); + int GetZoomFov(); + void GetWeaponAngleOffsets( int *average, float *scale, float *max ); + void GetWeaponTimeOffsets( float *time, float *scale ); + bool BloodSplat( float size ); + void SetIsPlayerFlashlight( bool bl ) { isPlayerFlashlight = bl; } + void FlashlightOn(); + void FlashlightOff(); + + // Ammo + static ammo_t GetAmmoNumForName( const char *ammoname ); + static const char *GetAmmoNameForNum( ammo_t ammonum ); + static const char *GetAmmoPickupNameForNum( ammo_t ammonum ); + ammo_t GetAmmoType() const; + int AmmoAvailable() const; + int AmmoInClip() const; + void ResetAmmoClip(); + int ClipSize() const; + int LowAmmo() const; + int AmmoRequired() const; + int AmmoCount() const; + int GetGrabberState() const; + + // Flashlight + idAnimatedEntity * GetWorldModel() { return worldModel.GetEntity(); } + + virtual void WriteToSnapshot( idBitMsg &msg ) const; + virtual void ReadFromSnapshot( const idBitMsg &msg ); + + enum { + EVENT_RELOAD = idEntity::EVENT_MAXEVENTS, + EVENT_ENDRELOAD, + EVENT_CHANGESKIN, + EVENT_MAXEVENTS + }; + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + virtual void ClientPredictionThink(); + virtual void ClientThink( const int curTime, const float fraction, const bool predict ); + void MuzzleFlashLight(); + void RemoveMuzzleFlashlight(); + + // Get a global origin and axis suitable for the laser sight or bullet tracing + // Returns false for hands, grenades, and chainsaw. + // Can't be const because a frame may need to be created. + bool GetMuzzlePositionWithHacks( idVec3 & origin, idMat3 & axis ); + + void GetProjectileLaunchOriginAndAxis( idVec3 & origin, idMat3 & axis ); + + const idDeclEntityDef * GetDeclEntityDef() { return weaponDef; } + + friend class idPlayer; +private: + // script control + idScriptBool WEAPON_ATTACK; + idScriptBool WEAPON_RELOAD; + idScriptBool WEAPON_NETRELOAD; + idScriptBool WEAPON_NETENDRELOAD; + idScriptBool WEAPON_NETFIRING; + idScriptBool WEAPON_RAISEWEAPON; + idScriptBool WEAPON_LOWERWEAPON; + weaponStatus_t status; + idThread * thread; + idStr state; + idStr idealState; + int animBlendFrames; + int animDoneTime; + bool isLinked; + bool isPlayerFlashlight; + + // precreated projectile + idEntity *projectileEnt; + + idPlayer * owner; + idEntityPtr worldModel; + + // hiding (for GUIs and NPCs) + int hideTime; + float hideDistance; + int hideStartTime; + float hideStart; + float hideEnd; + float hideOffset; + bool hide; + bool disabled; + + // berserk + int berserk; + + // these are the player render view parms, which include bobbing + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + + // the view weapon render entity parms + idVec3 viewWeaponOrigin; + idMat3 viewWeaponAxis; + + // the muzzle bone's position, used for launching projectiles and trailing smoke + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + idVec3 pushVelocity; + + // weapon definition + // we maintain local copies of the projectile and brass dictionaries so they + // do not have to be copied across the DLL boundary when entities are spawned + const idDeclEntityDef * weaponDef; + const idDeclEntityDef * meleeDef; + idDict projectileDict; + float meleeDistance; + idStr meleeDefName; + idDict brassDict; + int brassDelay; + idStr icon; + idStr pdaIcon; + idStr displayName; + idStr itemDesc; + + // view weapon gui light + renderLight_t guiLight; + int guiLightHandle; + + // muzzle flash + renderLight_t muzzleFlash; // positioned on view weapon bone + int muzzleFlashHandle; + + renderLight_t worldMuzzleFlash; // positioned on world weapon bone + int worldMuzzleFlashHandle; + + float fraccos; + float fraccos2; + + idVec3 flashColor; + int muzzleFlashEnd; + int flashTime; + bool lightOn; + bool silent_fire; + bool allowDrop; + + // effects + bool hasBloodSplat; + + // weapon kick + int kick_endtime; + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + + // ammo management + ammo_t ammoType; + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + int clipSize; // 0 means no reload + idPredictedValue< int > ammoClip; + int lowAmmo; // if ammo in clip hits this threshold, snd_ + bool powerAmmo; // true if the clip reduction is a factor of the power setting when + // a projectile is launched + // mp client + bool isFiring; + + // zoom + int zoomFov; // variable zoom fov per weapon + + // joints from models + jointHandle_t barrelJointView; + jointHandle_t flashJointView; + jointHandle_t ejectJointView; + jointHandle_t guiLightJointView; + jointHandle_t ventLightJointView; + + jointHandle_t flashJointWorld; + jointHandle_t barrelJointWorld; + jointHandle_t ejectJointWorld; + + jointHandle_t smokeJointView; + + idHashTable weaponParticles; + idHashTable weaponLights; + + // sound + const idSoundShader * sndHum; + + // new style muzzle smokes + const idDeclParticle * weaponSmoke; // null if it doesn't smoke + int weaponSmokeStartTime; // set to gameLocal.time every weapon fire + bool continuousSmoke; // if smoke is continuous ( chainsaw ) + const idDeclParticle * strikeSmoke; // striking something in melee + int strikeSmokeStartTime; // timing + idVec3 strikePos; // position of last melee strike + idMat3 strikeAxis; // axis of last melee strike + int nextStrikeFx; // used for sound and decal ( may use for strike smoke too ) + + // nozzle effects + bool nozzleFx; // does this use nozzle effects ( parm5 at rest, parm6 firing ) + // this also assumes a nozzle light atm + int nozzleFxFade; // time it takes to fade between the effects + int lastAttack; // last time an attack occured + renderLight_t nozzleGlow; // nozzle light + int nozzleGlowHandle; // handle for nozzle light + + idVec3 nozzleGlowColor; // color of the nozzle glow + const idMaterial * nozzleGlowShader; // shader for glow light + float nozzleGlowRadius; // radius of glow light + + // weighting for viewmodel angles + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // flashlight + void AlertMonsters(); + + // Visual presentation + void InitWorldModel( const idDeclEntityDef *def ); + void MuzzleRise( idVec3 &origin, idMat3 &axis ); + void UpdateNozzleFx(); + void UpdateFlashPosition(); + + // script events + void Event_Clear(); + void Event_GetOwner(); + void Event_WeaponState( const char *statename, int blendFrames ); + void Event_SetWeaponStatus( float newStatus ); + void Event_WeaponReady(); + void Event_WeaponOutOfAmmo(); + void Event_WeaponReloading(); + void Event_WeaponHolstered(); + void Event_WeaponRising(); + void Event_WeaponLowering(); + void Event_UseAmmo( int amount ); + void Event_AddToClip( int amount ); + void Event_AmmoInClip(); + void Event_AmmoAvailable(); + void Event_TotalAmmoCount(); + void Event_ClipSize(); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone( int channel, int blendFrames ); + void Event_SetBlendFrames( int channel, int blendFrames ); + void Event_GetBlendFrames( int channel ); + void Event_Next(); + void Event_SetSkin( const char *skinname ); + void Event_Flashlight( int enable ); + void Event_GetLightParm( int parmnum ); + void Event_SetLightParm( int parmnum, float value ); + void Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_LaunchProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ); + void Event_CreateProjectile(); + void Event_EjectBrass(); + void Event_Melee(); + void Event_GetWorldModel(); + void Event_AllowDrop( int allow ); + void Event_AutoReload(); + void Event_NetReload(); + void Event_IsInvisible(); + void Event_NetEndReload(); + + idGrabber grabber; + int grabberState; + + void Event_Grabber( int enable ); + void Event_GrabberHasTarget(); + void Event_GrabberSetGrabDistance( float dist ); + void Event_LaunchProjectilesEllipse( int num_projectiles, float spreada, float spreadb, float fuseOffset, float power ); + void Event_LaunchPowerup( const char* powerup, float duration, int useAmmo ); + + void Event_StartWeaponSmoke(); + void Event_StopWeaponSmoke(); + + void Event_StartWeaponParticle( const char* name); + void Event_StopWeaponParticle( const char* name); + + void Event_StartWeaponLight( const char* name); + void Event_StopWeaponLight( const char* name); +}; + +ID_INLINE bool idWeapon::IsLinked() { + return isLinked; +} + +ID_INLINE bool idWeapon::IsWorldModelReady() { + return ( worldModel.GetEntity() != NULL ); +} + +ID_INLINE idPlayer* idWeapon::GetOwner() { + return owner; +} + +#endif /* !__GAME_WEAPON_H__ */ diff --git a/neo/d3xp/WorldSpawn.cpp b/neo/d3xp/WorldSpawn.cpp new file mode 100644 index 00000000..e38acbc8 --- /dev/null +++ b/neo/d3xp/WorldSpawn.cpp @@ -0,0 +1,143 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +game_worldspawn.cpp + +Worldspawn class. Each map has one worldspawn which handles global spawnargs. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +================ +idWorldspawn + +Every map should have exactly one worldspawn. +================ +*/ +CLASS_DECLARATION( idEntity, idWorldspawn ) + EVENT( EV_Remove, idWorldspawn::Event_Remove ) + EVENT( EV_SafeRemove, idWorldspawn::Event_Remove ) +END_CLASS + +/* +================ +idWorldspawn::Spawn +================ +*/ +void idWorldspawn::Spawn() { + idStr scriptname; + idThread *thread; + const function_t *func; + const idKeyValue *kv; + + assert( gameLocal.world == NULL ); + gameLocal.world = this; + + g_gravity.SetFloat( spawnArgs.GetFloat( "gravity", va( "%f", DEFAULT_GRAVITY ) ) ); + + // disable stamina on hell levels + if ( spawnArgs.GetBool( "no_stamina" ) ) { + pm_stamina.SetFloat( 0.0f ); + } + + // load script + scriptname = gameLocal.GetMapName(); + scriptname.SetFileExtension( ".script" ); + if ( fileSystem->ReadFile( scriptname, NULL, NULL ) > 0 ) { + gameLocal.program.CompileFile( scriptname ); + + // call the main function by default + func = gameLocal.program.FindFunction( "main" ); + if ( func != NULL ) { + thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + + // call any functions specified in worldspawn + kv = spawnArgs.MatchPrefix( "call" ); + while( kv != NULL ) { + func = gameLocal.program.FindFunction( kv->GetValue() ); + if ( func == NULL ) { + gameLocal.Error( "Function '%s' not found in script for '%s' key on worldspawn", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + + thread = new idThread( func ); + thread->DelayedStart( 0 ); + kv = spawnArgs.MatchPrefix( "call", kv ); + } +} + +/* +================= +idWorldspawn::Save +================= +*/ +void idWorldspawn::Save( idSaveGame *savefile ) { +} + +/* +================= +idWorldspawn::Restore +================= +*/ +void idWorldspawn::Restore( idRestoreGame *savefile ) { + assert( gameLocal.world == this ); + + g_gravity.SetFloat( spawnArgs.GetFloat( "gravity", va( "%f", DEFAULT_GRAVITY ) ) ); + + // disable stamina on hell levels + if ( spawnArgs.GetBool( "no_stamina" ) ) { + pm_stamina.SetFloat( 0.0f ); + } +} + +/* +================ +idWorldspawn::~idWorldspawn +================ +*/ +idWorldspawn::~idWorldspawn() { + if ( gameLocal.world == this ) { + gameLocal.world = NULL; + } +} + +/* +================ +idWorldspawn::Event_Remove +================ +*/ +void idWorldspawn::Event_Remove() { + gameLocal.Error( "Tried to remove world" ); +} diff --git a/neo/d3xp/WorldSpawn.h b/neo/d3xp/WorldSpawn.h new file mode 100644 index 00000000..63663058 --- /dev/null +++ b/neo/d3xp/WorldSpawn.h @@ -0,0 +1,55 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAME_WORLDSPAWN_H__ +#define __GAME_WORLDSPAWN_H__ + +/* +=============================================================================== + + World entity. + +=============================================================================== +*/ + +class idWorldspawn : public idEntity { +public: + CLASS_PROTOTYPE( idWorldspawn ); + + ~idWorldspawn(); + + void Spawn(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + +private: + void Event_Remove(); +}; + +#endif /* !__GAME_WORLDSPAWN_H__ */ diff --git a/neo/d3xp/ai/AAS.cpp b/neo/d3xp/ai/AAS.cpp new file mode 100644 index 00000000..c902c762 --- /dev/null +++ b/neo/d3xp/ai/AAS.cpp @@ -0,0 +1,276 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "AAS_local.h" + +/* +============ +idAAS::Alloc +============ +*/ +idAAS *idAAS::Alloc() { + return new (TAG_AAS) idAASLocal; +} + +/* +============ +idAAS::idAAS +============ +*/ +idAAS::~idAAS() { +} + +/* +============ +idAASLocal::idAASLocal +============ +*/ +idAASLocal::idAASLocal() { + file = NULL; +} + +/* +============ +idAASLocal::~idAASLocal +============ +*/ +idAASLocal::~idAASLocal() { + Shutdown(); +} + +/* +============ +idAASLocal::Init +============ +*/ +bool idAASLocal::Init( const idStr &mapName, unsigned int mapFileCRC ) { + if ( file && mapName.Icmp( file->GetName() ) == 0 && mapFileCRC == file->GetCRC() ) { + common->Printf( "Keeping %s\n", file->GetName() ); + RemoveAllObstacles(); + } + else { + Shutdown(); + + file = AASFileManager->LoadAAS( mapName, mapFileCRC ); + if ( !file ) { + common->DWarning( "Couldn't load AAS file: '%s'", mapName.c_str() ); + return false; + } + SetupRouting(); + } + return true; +} + +/* +============ +idAASLocal::Shutdown +============ +*/ +void idAASLocal::Shutdown() { + if ( file ) { + ShutdownRouting(); + RemoveAllObstacles(); + AASFileManager->FreeAAS( file ); + file = NULL; + } +} + +/* +============ +idAASLocal::Stats +============ +*/ +void idAASLocal::Stats() const { + if ( !file ) { + return; + } + common->Printf( "[%s]\n", file->GetName() ); + file->PrintInfo(); + RoutingStats(); +} + +/* +============ +idAASLocal::GetSettings +============ +*/ +const idAASSettings *idAASLocal::GetSettings() const { + if ( !file ) { + return NULL; + } + return &file->GetSettings(); +} + +/* +============ +idAASLocal::PointAreaNum +============ +*/ +int idAASLocal::PointAreaNum( const idVec3 &origin ) const { + if ( !file ) { + return 0; + } + return file->PointAreaNum( origin ); +} + +/* +============ +idAASLocal::PointReachableAreaNum +============ +*/ +int idAASLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->PointReachableAreaNum( origin, searchBounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::BoundsReachableAreaNum +============ +*/ +int idAASLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->BoundsReachableAreaNum( bounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::PushPointIntoAreaNum +============ +*/ +void idAASLocal::PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const { + if ( !file ) { + return; + } + file->PushPointIntoAreaNum( areaNum, origin ); +} + +/* +============ +idAASLocal::AreaCenter +============ +*/ +idVec3 idAASLocal::AreaCenter( int areaNum ) const { + if ( !file ) { + return vec3_origin; + } + return file->GetArea( areaNum ).center; +} + +/* +============ +idAASLocal::AreaFlags +============ +*/ +int idAASLocal::AreaFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).flags; +} + +/* +============ +idAASLocal::AreaTravelFlags +============ +*/ +int idAASLocal::AreaTravelFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).travelFlags; +} + +/* +============ +idAASLocal::Trace +============ +*/ +bool idAASLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + if ( !file ) { + trace.fraction = 0.0f; + trace.lastAreaNum = 0; + trace.numAreas = 0; + return true; + } + return file->Trace( trace, start, end ); +} + +/* +============ +idAASLocal::GetPlane +============ +*/ +const idPlane &idAASLocal::GetPlane( int planeNum ) const { + if ( !file ) { + static idPlane dummy; + return dummy; + } + return file->GetPlane( planeNum ); +} + +/* +============ +idAASLocal::GetEdgeVertexNumbers +============ +*/ +void idAASLocal::GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const { + if ( !file ) { + verts[0] = verts[1] = 0; + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + verts[0] = v[INT32_SIGNBITSET(edgeNum)]; + verts[1] = v[INT32_SIGNBITNOTSET(edgeNum)]; +} + +/* +============ +idAASLocal::GetEdge +============ +*/ +void idAASLocal::GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( !file ) { + start.Zero(); + end.Zero(); + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + start = file->GetVertex( v[INT32_SIGNBITSET(edgeNum)] ); + end = file->GetVertex( v[INT32_SIGNBITNOTSET(edgeNum)] ); +} diff --git a/neo/d3xp/ai/AAS.h b/neo/d3xp/ai/AAS.h new file mode 100644 index 00000000..f2db8e53 --- /dev/null +++ b/neo/d3xp/ai/AAS.h @@ -0,0 +1,141 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AAS_H__ +#define __AAS_H__ + +/* +=============================================================================== + + Area Awareness System + +=============================================================================== +*/ + +enum { + PATHTYPE_WALK, + PATHTYPE_WALKOFFLEDGE, + PATHTYPE_BARRIERJUMP, + PATHTYPE_JUMP +}; + +typedef struct aasPath_s { + int type; // path type + idVec3 moveGoal; // point the AI should move towards + int moveAreaNum; // number of the area the AI should move towards + idVec3 secondaryGoal; // secondary move goal for complex navigation + const idReachability * reachability; // reachability used for navigation +} aasPath_t; + + +typedef struct aasGoal_s { + int areaNum; // area the goal is in + idVec3 origin; // position of goal +} aasGoal_t; + + +typedef struct aasObstacle_s { + idBounds absBounds; // absolute bounds of obstacle + idBounds expAbsBounds; // expanded absolute bounds of obstacle +} aasObstacle_t; + +class idAASCallback { +public: + virtual ~idAASCallback() {}; + virtual bool TestArea( const class idAAS *aas, int areaNum ) = 0; +}; + +typedef int aasHandle_t; + +class idAAS { +public: + static idAAS * Alloc(); + virtual ~idAAS() = 0; + // Initialize for the given map. + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ) = 0; + // Print AAS stats. + virtual void Stats() const = 0; + // Test from the given origin. + virtual void Test( const idVec3 &origin ) = 0; + // Get the AAS settings. + virtual const idAASSettings *GetSettings() const = 0; + // Returns the number of the area the origin is in. + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + // Returns the number of the nearest reachable area for the given point. + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &bounds, const int areaFlags ) const = 0; + // Returns the number of the first reachable area in or touching the bounds. + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const = 0; + // Push the point into the area. + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const = 0; + // Returns a reachable point inside the given area. + virtual idVec3 AreaCenter( int areaNum ) const = 0; + // Returns the area flags. + virtual int AreaFlags( int areaNum ) const = 0; + // Returns the travel flags for traveling through the area. + virtual int AreaTravelFlags( int areaNum ) const = 0; + // Trace through the areas and report the first collision. + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + // Get a plane for a trace. + virtual const idPlane & GetPlane( int planeNum ) const = 0; + // Get wall edges. + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const = 0; + // Sort the wall edges to create continuous sequences of walls. + virtual void SortWallEdges( int *edges, int numEdges ) const = 0; + // Get the vertex numbers for an edge. + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const = 0; + // Get an edge. + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Find all areas within or touching the bounds with the given contents and disable/enable them for routing. + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) = 0; + // Add an obstacle to the routing system. + virtual aasHandle_t AddObstacle( const idBounds &bounds ) = 0; + // Remove an obstacle from the routing system. + virtual void RemoveObstacle( const aasHandle_t handle ) = 0; + // Remove all obstacles from the routing system. + virtual void RemoveAllObstacles() = 0; + // Returns the travel time towards the goal area in 100th of a second. + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const = 0; + // Get the travel time and first reachability to be used towards the goal, returns true if there is a path. + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const = 0; + // Creates a walk path towards the goal. + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can walk along a straight line from the origin to the goal origin. + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Creates a fly path towards the goal. + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can fly along a straight line from the origin to the goal origin. + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Show the walk path from the origin towards the area. + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Show the fly path from the origin towards the area. + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Find the nearest goal which satisfies the callback. + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const = 0; +}; + +#endif /* !__AAS_H__ */ diff --git a/neo/d3xp/ai/AAS_debug.cpp b/neo/d3xp/ai/AAS_debug.cpp new file mode 100644 index 00000000..05a47a13 --- /dev/null +++ b/neo/d3xp/ai/AAS_debug.cpp @@ -0,0 +1,508 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "AAS_local.h" +#include "../Game_local.h" // for cvars and debug drawing + + +/* +============ +idAASLocal::DrawCone +============ +*/ +void idAASLocal::DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const { + int i; + idMat3 axis; + idVec3 center, top, p, lastp; + + axis[2] = dir; + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + center = origin + dir; + top = center + dir * (3.0f * radius); + lastp = center + radius * axis[1]; + + for ( i = 20; i <= 360; i += 20 ) { + p = center + sin( DEG2RAD(i) ) * radius * axis[0] + cos( DEG2RAD(i) ) * radius * axis[1]; + gameRenderWorld->DebugLine( color, lastp, p, 0 ); + gameRenderWorld->DebugLine( color, p, top, 0 ); + lastp = p; + } +} + +/* +============ +idAASLocal::DrawReachability +============ +*/ +void idAASLocal::DrawReachability( const idReachability *reach ) const { + gameRenderWorld->DebugArrow( colorCyan, reach->start, reach->end, 2 ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", reach->edgeNum ), ( reach->start + reach->end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis ); + } + + switch( reach->travelType ) { + case TFL_WALK: { + //const idReachability_Walk *walk = static_cast(reach); + break; + } + default: { + break; + } + } +} + +/* +============ +idAASLocal::DrawEdge +============ +*/ +void idAASLocal::DrawEdge( int edgeNum, bool arrow ) const { + const aasEdge_t *edge; + idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &colorRed; + if ( arrow ) { + gameRenderWorld->DebugArrow( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ), 1 ); + } else { + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); + } + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", edgeNum ), ( file->GetVertex( edge->vertexNum[0] ) + file->GetVertex( edge->vertexNum[1] ) ) * 0.5f + idVec3(0,0,4), 0.1f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } +} + +/* +============ +idAASLocal::DrawFace +============ +*/ +void idAASLocal::DrawFace( int faceNum, bool side ) const { + int i, j, numEdges, firstEdge; + const aasFace_t *face; + idVec3 mid, end; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + mid = vec3_origin; + for ( i = 0; i < numEdges; i++ ) { + DrawEdge( abs( file->GetEdgeIndex( firstEdge + i ) ), ( face->flags & FACE_FLOOR ) != 0 ); + j = file->GetEdgeIndex( firstEdge + i ); + mid += file->GetVertex( file->GetEdge( abs( j ) ).vertexNum[ j < 0 ] ); + } + + mid /= numEdges; + if ( side ) { + end = mid - 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } else { + end = mid + 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } + gameRenderWorld->DebugArrow( colorGreen, mid, end, 1 ); +} + +/* +============ +idAASLocal::DrawArea +============ +*/ +void idAASLocal::DrawArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idReachability *reach; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawFace( abs( file->GetFaceIndex( firstFace + i ) ), file->GetFaceIndex( firstFace + i ) < 0 ); + } + + for ( reach = area->reach; reach; reach = reach->next ) { + DrawReachability( reach ); + } +} + +/* +============ +idAASLocal::DefaultSearchBounds +============ +*/ +const idBounds &idAASLocal::DefaultSearchBounds() const { + return file->GetSettings().boundingBoxes[0]; +} + +/* +============ +idAASLocal::ShowArea +============ +*/ +void idAASLocal::ShowArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + const aasArea_t *area; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + org = origin; + PushPointIntoAreaNum( areaNum, org ); + + if ( aas_goalArea.GetInteger() ) { + int travelTime; + idReachability *reach; + + RouteToGoalArea( areaNum, org, aas_goalArea.GetInteger(), TFL_WALK|TFL_AIR, travelTime, &reach ); + gameLocal.Printf( "\rtt = %4d", travelTime ); + if ( reach ) { + gameLocal.Printf( " to area %4d", reach->toAreaNum ); + DrawArea( reach->toAreaNum ); + } + } + + if ( areaNum != lastAreaNum ) { + area = &file->GetArea( areaNum ); + gameLocal.Printf( "area %d: ", areaNum ); + if ( area->flags & AREA_LEDGE ) { + gameLocal.Printf( "AREA_LEDGE " ); + } + if ( area->flags & AREA_REACHABLE_WALK ) { + gameLocal.Printf( "AREA_REACHABLE_WALK " ); + } + if ( area->flags & AREA_REACHABLE_FLY ) { + gameLocal.Printf( "AREA_REACHABLE_FLY " ); + } + if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) { + gameLocal.Printf( "AREACONTENTS_CLUSTERPORTAL " ); + } + if ( area->contents & AREACONTENTS_OBSTACLE ) { + gameLocal.Printf( "AREACONTENTS_OBSTACLE " ); + } + gameLocal.Printf( "\n" ); + lastAreaNum = areaNum; + } + + if ( org != origin ) { + idBounds bnds = file->GetSettings().boundingBoxes[ 0 ]; + bnds[ 1 ].z = bnds[ 0 ].z; + gameRenderWorld->DebugBounds( colorYellow, bnds, org ); + } + + DrawArea( areaNum ); +} + +/* +============ +idAASLocal::ShowWalkPath +============ +*/ +void idAASLocal::ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_WALK ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorGreen, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( WalkPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowFlyPath +============ +*/ +void idAASLocal::ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_FLY ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_FLY|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorPurple, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( FlyPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_FLY|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowWallEdges +============ +*/ +void idAASLocal::ShowWallEdges( const idVec3 &origin ) const { + int i, areaNum, numEdges, edges[1024]; + idVec3 start, end; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + numEdges = GetWallEdges( areaNum, idBounds( origin ).Expand( 256.0f ), TFL_WALK, edges, 1024 ); + for ( i = 0; i < numEdges; i++ ) { + GetEdge( edges[i], start, end ); + gameRenderWorld->DebugLine( colorRed, start, end ); + gameRenderWorld->DrawText( va( "%d", edges[i] ), ( start + end ) * 0.5f, 0.1f, colorWhite, player->viewAxis ); + } +} + +/* +============ +idAASLocal::ShowHideArea +============ +*/ +void idAASLocal::ShowHideArea( const idVec3 &origin, int targetAreaNum ) const { + int areaNum, numObstacles; + idVec3 target; + aasGoal_t goal; + aasObstacle_t obstacles[10]; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + target = AreaCenter( targetAreaNum ); + + // consider the target an obstacle + obstacles[0].absBounds = idBounds( target ).Expand( 16 ); + numObstacles = 1; + + DrawCone( target, idVec3(0,0,1), 16.0f, colorYellow ); + + idAASFindCover findCover( target ); + if ( FindNearestGoal( goal, areaNum, origin, target, TFL_WALK|TFL_AIR, obstacles, numObstacles, findCover ) ) { + DrawArea( goal.areaNum ); + ShowWalkPath( origin, goal.areaNum, goal.origin ); + DrawCone( goal.origin, idVec3(0,0,1), 16.0f, colorWhite ); + } +} + +/* +============ +idAASLocal::PullPlayer +============ +*/ +bool idAASLocal::PullPlayer( const idVec3 &origin, int toAreaNum ) const { + int areaNum; + idVec3 areaCenter, dir, vel; + idAngles delta; + aasPath_t path; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return true; + } + + idPhysics *physics = player->GetPhysics(); + if ( !physics ) { + return true; + } + + if ( !toAreaNum ) { + return false; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + areaCenter = AreaCenter( toAreaNum ); + if ( player->GetPhysics()->GetAbsBounds().Expand( 8 ).ContainsPoint( areaCenter ) ) { + return false; + } + if ( WalkPathToGoal( path, areaNum, origin, toAreaNum, areaCenter, TFL_WALK|TFL_AIR ) ) { + dir = path.moveGoal - origin; + dir[2] *= 0.5f; + dir.Normalize(); + delta = dir.ToAngles() - player->cmdAngles - player->GetDeltaViewAngles(); + delta.Normalize180(); + player->SetDeltaViewAngles( player->GetDeltaViewAngles() + delta * 0.1f ); + dir[2] = 0.0f; + dir.Normalize(); + dir *= 100.0f; + vel = physics->GetLinearVelocity(); + dir[2] = vel[2]; + physics->SetLinearVelocity( dir ); + return true; + } + else { + return false; + } +} + +/* +============ +idAASLocal::RandomPullPlayer +============ +*/ +void idAASLocal::RandomPullPlayer( const idVec3 &origin ) const { + int rnd, i, n; + + if ( !PullPlayer( origin, aas_pullPlayer.GetInteger() ) ) { + + rnd = gameLocal.random.RandomFloat() * file->GetNumAreas(); + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + n = (rnd + i) % file->GetNumAreas(); + if ( file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) { + aas_pullPlayer.SetInteger( n ); + } + } + } else { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + } +} + +/* +============ +idAASLocal::ShowPushIntoArea +============ +*/ +void idAASLocal::ShowPushIntoArea( const idVec3 &origin ) const { + int areaNum; + idVec3 target; + + target = origin; + areaNum = PointReachableAreaNum( target, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + PushPointIntoAreaNum( areaNum, target ); + gameRenderWorld->DebugArrow( colorGreen, origin, target, 1 ); +} + +/* +============ +idAASLocal::Test +============ +*/ +void idAASLocal::Test( const idVec3 &origin ) { + + if ( !file ) { + return; + } + + if ( aas_randomPullPlayer.GetBool() ) { + RandomPullPlayer( origin ); + } + if ( ( aas_pullPlayer.GetInteger() > 0 ) && ( aas_pullPlayer.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + PullPlayer( origin, aas_pullPlayer.GetInteger() ); + } + if ( ( aas_showPath.GetInteger() > 0 ) && ( aas_showPath.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_showPath.GetInteger(), AreaCenter( aas_showPath.GetInteger() ) ); + } + if ( ( aas_showFlyPath.GetInteger() > 0 ) && ( aas_showFlyPath.GetInteger() < file->GetNumAreas() ) ) { + ShowFlyPath( origin, aas_showFlyPath.GetInteger(), AreaCenter( aas_showFlyPath.GetInteger() ) ); + } + if ( ( aas_showHideArea.GetInteger() > 0 ) && ( aas_showHideArea.GetInteger() < file->GetNumAreas() ) ) { + ShowHideArea( origin, aas_showHideArea.GetInteger() ); + } + if ( aas_showAreas.GetBool() ) { + ShowArea( origin ); + } + if ( aas_showWallEdges.GetBool() ) { + ShowWallEdges( origin ); + } + if ( aas_showPushIntoArea.GetBool() ) { + ShowPushIntoArea( origin ); + } +} diff --git a/neo/d3xp/ai/AAS_local.h b/neo/d3xp/ai/AAS_local.h new file mode 100644 index 00000000..3653678a --- /dev/null +++ b/neo/d3xp/ai/AAS_local.h @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AAS_LOCAL_H__ +#define __AAS_LOCAL_H__ + +#include "AAS.h" +#include "../Pvs.h" + + +class idRoutingCache { + friend class idAASLocal; + +public: + idRoutingCache( int size ); + ~idRoutingCache(); + + int Size() const; + +private: + int type; // portal or area cache + int size; // size of cache + int cluster; // cluster of the cache + int areaNum; // area of the cache + int travelFlags; // combinations of the travel flags + idRoutingCache * next; // next in list + idRoutingCache * prev; // previous in list + idRoutingCache * time_next; // next in time based list + idRoutingCache * time_prev; // previous in time based list + unsigned short startTravelTime; // travel time to start with + unsigned char * reachabilities; // reachabilities used for routing + unsigned short * travelTimes; // travel time for every area +}; + + +class idRoutingUpdate { + friend class idAASLocal; + +private: + int cluster; // cluster number of this update + int areaNum; // area number of this update + unsigned short tmpTravelTime; // temporary travel time + unsigned short * areaTravelTimes; // travel times within the area + idVec3 start; // start point into area + idRoutingUpdate * next; // next in list + idRoutingUpdate * prev; // prev in list + bool isInList; // true if the update is in the list +}; + + +class idRoutingObstacle { + friend class idAASLocal; + idRoutingObstacle() { } + +private: + idBounds bounds; // obstacle bounds + idList areas; // areas the bounds are in +}; + + +class idAASLocal : public idAAS { +public: + idAASLocal(); + virtual ~idAASLocal(); + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ); + virtual void Shutdown(); + virtual void Stats() const; + virtual void Test( const idVec3 &origin ); + virtual const idAASSettings *GetSettings() const; + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const; + virtual idVec3 AreaCenter( int areaNum ) const; + virtual int AreaFlags( int areaNum ) const; + virtual int AreaTravelFlags( int areaNum ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual const idPlane & GetPlane( int planeNum ) const; + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const; + virtual void SortWallEdges( int *edges, int numEdges ) const; + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const; + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const; + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ); + virtual aasHandle_t AddObstacle( const idBounds &bounds ); + virtual void RemoveObstacle( const aasHandle_t handle ); + virtual void RemoveAllObstacles(); + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const; + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const; + +private: + idAASFile * file; + idStr name; + +private: // routing data + idRoutingCache *** areaCacheIndex; // for each area in each cluster the travel times to all other areas in the cluster + int areaCacheIndexSize; // number of area cache entries + idRoutingCache ** portalCacheIndex; // for each area in the world the travel times from each portal + int portalCacheIndexSize; // number of portal cache entries + idRoutingUpdate * areaUpdate; // memory used to update the area routing cache + idRoutingUpdate * portalUpdate; // memory used to update the portal routing cache + unsigned short * goalAreaTravelTimes; // travel times to goal areas + unsigned short * areaTravelTimes; // travel times through the areas + int numAreaTravelTimes; // number of area travel times + mutable idRoutingCache * cacheListStart; // start of list with cache sorted from oldest to newest + mutable idRoutingCache * cacheListEnd; // end of list with cache sorted from oldest to newest + mutable int totalCacheMemory; // total cache memory used + idList obstacleList; // list with obstacles + +private: // routing + bool SetupRouting(); + void ShutdownRouting(); + unsigned short AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const; + void CalculateAreaTravelTimes(); + void DeleteAreaTravelTimes(); + void SetupRoutingCache(); + void DeleteClusterCache( int clusterNum ); + void DeletePortalCache(); + void ShutdownRoutingCache(); + void RoutingStats() const; + void LinkCache( idRoutingCache *cache ) const; + void UnlinkCache( idRoutingCache *cache ) const; + void DeleteOldestCache() const; + idReachability * GetAreaReachability( int areaNum, int reachabilityNum ) const; + int ClusterAreaNum( int clusterNum, int areaNum ) const; + void UpdateAreaRoutingCache( idRoutingCache *areaCache ) const; + idRoutingCache * GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void UpdatePortalRoutingCache( idRoutingCache *portalCache ) const; + idRoutingCache * GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void RemoveRoutingCacheUsingArea( int areaNum ); + void DisableArea( int areaNum ); + void EnableArea( int areaNum ); + bool SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ); + void GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const; + void SetObstacleState( const idRoutingObstacle *obstacle, bool enable ); + +private: // pathing + bool EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const; + bool FloorEdgeSplitPoint( idVec3 &split, int areaNum, const idPlane &splitPlane, const idPlane &frontPlane, bool closest ) const; + idVec3 SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + idVec3 SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + +private: // debug + const idBounds & DefaultSearchBounds() const; + void DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const; + void DrawArea( int areaNum ) const; + void DrawFace( int faceNum, bool side ) const; + void DrawEdge( int edgeNum, bool arrow ) const; + void DrawReachability( const idReachability *reach ) const; + void ShowArea( const idVec3 &origin ) const; + void ShowWallEdges( const idVec3 &origin ) const; + void ShowHideArea( const idVec3 &origin, int targerAreaNum ) const; + bool PullPlayer( const idVec3 &origin, int toAreaNum ) const; + void RandomPullPlayer( const idVec3 &origin ) const; + void ShowPushIntoArea( const idVec3 &origin ) const; +}; + +#endif /* !__AAS_LOCAL_H__ */ diff --git a/neo/d3xp/ai/AAS_pathing.cpp b/neo/d3xp/ai/AAS_pathing.cpp new file mode 100644 index 00000000..40459d76 --- /dev/null +++ b/neo/d3xp/ai/AAS_pathing.cpp @@ -0,0 +1,717 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "AAS_local.h" + +#define SUBSAMPLE_WALK_PATH 1 +#define SUBSAMPLE_FLY_PATH 0 + +const int maxWalkPathIterations = 10; +const float maxWalkPathDistance = 500.0f; +const float walkPathSampleDistance = 8.0f; + +const int maxFlyPathIterations = 10; +const float maxFlyPathDistance = 500.0f; +const float flyPathSampleDistance = 8.0f; + + +/* +============ +idAASLocal::EdgeSplitPoint + + calculates split point of the edge with the plane + returns true if the split point is between the edge vertices +============ +*/ +bool idAASLocal::EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const { + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + edge = &file->GetEdge( edgeNum ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * plane.Normal() - plane.Dist(); + d2 = v2 * plane.Normal() - plane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( IEEE_FLT_SIGNBITSET( d1 ) == IEEE_FLT_SIGNBITSET( d2 ) ) { + return false; + } + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + return true; +} + +/* +============ +idAASLocal::FloorEdgeSplitPoint + + calculates either the closest or furthest point on the floor of the area which also lies on the pathPlane + the point has to be on the front side of the frontPlane to be valid +============ +*/ +bool idAASLocal::FloorEdgeSplitPoint( idVec3 &bestSplit, int areaNum, const idPlane &pathPlane, const idPlane &frontPlane, bool closest ) const { + int i, j, faceNum, edgeNum; + const aasArea_t *area; + const aasFace_t *face; + idVec3 split; + float dist, bestDist; + + if ( closest ) { + bestDist = maxWalkPathDistance; + } else { + bestDist = -0.1f; + } + + area = &file->GetArea( areaNum ); + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = 0; j < face->numEdges; j++ ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + if ( !EdgeSplitPoint( split, abs( edgeNum ), pathPlane ) ) { + continue; + } + dist = frontPlane.Distance( split ); + if ( closest ) { + if ( dist >= -0.1f && dist < bestDist ) { + bestDist = dist; + bestSplit = split; + } + } else { + if ( dist > bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + } + + if ( closest ) { + return ( bestDist < maxWalkPathDistance ); + } else { + return ( bestDist > -0.1f ); + } +} + +/* +============ +idAASLocal::WalkPathValid + + returns true if one can walk in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + int curAreaNum, lastAreaNum, lastAreas[4], lastAreaIndex; + idPlane pathPlane, frontPlane, farPlane; + idReachability *reach; + const aasArea_t *area; + idVec3 p, dir; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + pathPlane.SetNormal( (goalOrigin - origin).Cross( file->GetSettings().gravityDir ) ); + pathPlane.Normalize(); + pathPlane.FitThroughPoint( origin ); + + frontPlane.SetNormal( goalOrigin - origin ); + frontPlane.Normalize(); + frontPlane.FitThroughPoint( origin ); + + farPlane.SetNormal( frontPlane.Normal() ); + farPlane.FitThroughPoint( goalOrigin ); + + curAreaNum = areaNum; + lastAreaNum = curAreaNum; + + while ( 1 ) { + + // find the furthest floor face split point on the path + if ( !FloorEdgeSplitPoint( endPos, curAreaNum, pathPlane, frontPlane, false ) ) { + endPos = origin; + } + + // if we found a point near or further than the goal we're done + if ( farPlane.Distance( endPos ) > -0.5f ) { + break; + } + + // if we reached the goal area we're done + if ( curAreaNum == goalAreaNum ) { + break; + } + + frontPlane.SetDist( frontPlane.Normal() * endPos ); + + area = &file->GetArea( curAreaNum ); + + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType != TFL_WALK ) { + continue; + } + + // if the reachability goes back to a previous area + if ( reach->toAreaNum == lastAreas[0] || reach->toAreaNum == lastAreas[1] || + reach->toAreaNum == lastAreas[2] || reach->toAreaNum == lastAreas[3] ) { + continue; + } + + // if undesired travel flags are required to travel through the area + if ( file->GetArea( reach->toAreaNum ).travelFlags & ~travelFlags ) { + continue; + } + + // don't optimize through an area near a ledge + if ( file->GetArea( reach->toAreaNum ).flags & AREA_LEDGE ) { + continue; + } + + // find the closest floor face split point on the path + if ( !FloorEdgeSplitPoint( p, reach->toAreaNum, pathPlane, frontPlane, true ) ) { + continue; + } + + // direction parallel to gravity + dir = ( file->GetSettings().gravityDir * endPos * file->GetSettings().gravityDir ) - + ( file->GetSettings().gravityDir * p * file->GetSettings().gravityDir ); + if ( dir.LengthSqr() > Square( file->GetSettings().maxStepHeight ) ) { + continue; + } + + // direction orthogonal to gravity + dir = endPos - p - dir; + if ( dir.LengthSqr() > Square( 0.2f ) ) { + continue; + } + + break; + } + + if ( !reach ) { + return false; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + } + + endAreaNum = curAreaNum; + + return true; +} + +/* +============ +idAASLocal::SubSampleWalkPath +============ +*/ +idVec3 idAASLocal::SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / walkPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxWalkPathDistance ) ) { + return point; + } + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::WalkPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability * reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxWalkPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + // only optimize a limited distance ahead + if ( (reach->start - origin).LengthSqr() > Square( maxWalkPathDistance ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( reach->travelType != TFL_WALK ) { + break; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::WalkPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( reach == NULL ) { + return false; + } + + switch( reach->travelType ) { + case TFL_WALKOFFLEDGE: + path.type = PATHTYPE_WALKOFFLEDGE; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_BARRIERJUMP: + path.type |= PATHTYPE_BARRIERJUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_JUMP: + path.type |= PATHTYPE_JUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + default: + break; + } + + return true; +} + +/* +============ +idAASLocal::FlyPathValid + + returns true if one can fly in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + aasTrace_t trace; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + file->Trace( trace, origin, goalOrigin ); + + endPos = trace.endpos; + endAreaNum = trace.lastAreaNum; + + if ( trace.fraction >= 1.0f ) { + return true; + } + + return false; +} + +/* +============ +idAASLocal::SubSampleFlyPath +============ +*/ +idVec3 idAASLocal::SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / flyPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxFlyPathDistance ) ) { + return point; + } + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::FlyPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxFlyPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + if ( (reach->start - origin).LengthSqr() > Square( maxFlyPathDistance ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::FlyPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( reach == NULL ) { + return false; + } + + return true; +} + +typedef struct wallEdge_s { + int edgeNum; + int verts[2]; + struct wallEdge_s * next; +} wallEdge_t; + +/* +============ +idAASLocal::SortWallEdges +============ +*/ +void idAASLocal::SortWallEdges( int *edges, int numEdges ) const { + int i, j, k, numSequences; + wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge; + + wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) ); + sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + + for ( i = 0; i < numEdges; i++ ) { + wallEdges[i].edgeNum = edges[i]; + GetEdgeVertexNumbers( edges[i], wallEdges[i].verts ); + wallEdges[i].next = NULL; + sequenceFirst[i] = &wallEdges[i]; + sequenceLast[i] = &wallEdges[i]; + } + numSequences = numEdges; + + for ( i = 0; i < numSequences; i++ ) { + for ( j = i+1; j < numSequences; j++ ) { + if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) { + sequenceLast[j]->next = sequenceFirst[i]; + sequenceFirst[i] = sequenceFirst[j]; + break; + } + if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) { + sequenceLast[i]->next = sequenceFirst[j]; + break; + } + } + if ( j < numSequences ) { + numSequences--; + for ( k = j; k < numSequences; k++ ) { + sequenceFirst[k] = sequenceFirst[k+1]; + sequenceLast[k] = sequenceLast[k+1]; + } + i = -1; + } + } + + k = 0; + for ( i = 0; i < numSequences; i++ ) { + for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) { + edges[k++] = wallEdge->edgeNum; + } + } +} + +/* +============ +idAASLocal::GetWallEdges +============ +*/ +int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const { + int i, j, k, l, face1Num, face2Num, edge1Num, edge2Num, numEdges, absEdge1Num; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + const aasFace_t *face1, *face2; + idReachability *reach; + + if ( !file ) { + return 0; + } + + numEdges = 0; + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = -1; + queueEnd = 0; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + area = &file->GetArea( curArea ); + + for ( i = 0; i < area->numFaces; i++ ) { + face1Num = file->GetFaceIndex( area->firstFace + i ); + face1 = &file->GetFace( abs(face1Num) ); + + if ( !(face1->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = 0; j < face1->numEdges; j++ ) { + edge1Num = file->GetEdgeIndex( face1->firstEdge + j ); + absEdge1Num = abs( edge1Num ); + + // test if the edge is shared by another floor face of this area + for ( k = 0; k < area->numFaces; k++ ) { + if ( k == i ) { + continue; + } + face2Num = file->GetFaceIndex( area->firstFace + k ); + face2 = &file->GetFace( abs(face2Num) ); + + if ( !(face2->flags & FACE_FLOOR ) ) { + continue; + } + + for ( l = 0; l < face2->numEdges; l++ ) { + edge2Num = abs( file->GetEdgeIndex( face2->firstEdge + l ) ); + if ( edge2Num == absEdge1Num ) { + break; + } + } + if ( l < face2->numEdges ) { + break; + } + } + if ( k < area->numFaces ) { + continue; + } + + // test if the edge is used by a reachability + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + if ( reach->edgeNum == absEdge1Num ) { + break; + } + } + } + if ( reach ) { + continue; + } + + // test if the edge is already in the list + for ( k = 0; k < numEdges; k++ ) { + if ( edge1Num == edges[k] ) { + break; + } + } + if ( k < numEdges ) { + continue; + } + + // add the edge to the list + edges[numEdges++] = edge1Num; + if ( numEdges >= maxEdges ) { + return numEdges; + } + } + } + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + return numEdges; +} diff --git a/neo/d3xp/ai/AAS_routing.cpp b/neo/d3xp/ai/AAS_routing.cpp new file mode 100644 index 00000000..162ac35a --- /dev/null +++ b/neo/d3xp/ai/AAS_routing.cpp @@ -0,0 +1,1350 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "AAS_local.h" +#include "../Game_local.h" // for print and error + +#define CACHETYPE_AREA 1 +#define CACHETYPE_PORTAL 2 + +#define MAX_ROUTING_CACHE_MEMORY (2*1024*1024) + +#define LEDGE_TRAVELTIME_PANALTY 250 + +/* +============ +idRoutingCache::idRoutingCache +============ +*/ +idRoutingCache::idRoutingCache( int size ) { + areaNum = 0; + cluster = 0; + next = prev = NULL; + time_next = time_prev = NULL; + travelFlags = 0; + startTravelTime = 0; + type = 0; + this->size = size; + reachabilities = new (TAG_AAS) byte[size]; + memset( reachabilities, 0, size * sizeof( reachabilities[0] ) ); + travelTimes = new (TAG_AAS) unsigned short[size]; + memset( travelTimes, 0, size * sizeof( travelTimes[0] ) ); +} + +/* +============ +idRoutingCache::~idRoutingCache +============ +*/ +idRoutingCache::~idRoutingCache() { + delete [] reachabilities; + delete [] travelTimes; +} + +/* +============ +idRoutingCache::Size +============ +*/ +int idRoutingCache::Size() const { + return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] ); +} + +/* +============ +idAASLocal::AreaTravelTime +============ +*/ +unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const { + float dist; + + dist = ( end - start ).Length(); + + if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) { + dist *= 100.0f / 100.0f; + } else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) { + dist *= 100.0f / 150.0f; + } else { + dist *= 100.0f / 300.0f; + } + if ( dist < 1.0f ) { + return 1; + } + return (unsigned short) idMath::Ftoi( dist ); +} + +/* +============ +idAASLocal::CalculateAreaTravelTimes +============ +*/ +void idAASLocal::CalculateAreaTravelTimes() { + int n, i, j, numReach, numRevReach, t, maxt; + byte *bytePtr; + idReachability *reach, *rev_reach; + + // get total memory for all area travel times + numAreaTravelTimes = 0; + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + numReach = 0; + for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) { + numReach++; + } + + numRevReach = 0; + for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + numRevReach++; + } + numAreaTravelTimes += numReach * numRevReach; + } + + areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ), TAG_AAS ); + bytePtr = (byte *) areaTravelTimes; + + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + // for each reachability that starts in this area calculate the travel time + // towards all the reachabilities that lead towards this area + for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) { + assert( i < MAX_REACH_PER_AREA ); + if ( i >= MAX_REACH_PER_AREA ) { + gameLocal.Error( "i >= MAX_REACH_PER_AREA" ); + } + reach->number = i; + reach->disableCount = 0; + reach->areaTravelTimes = (unsigned short *) bytePtr; + for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) { + t = AreaTravelTime( n, reach->start, rev_reach->end ); + reach->areaTravelTimes[j] = t; + if ( t > maxt ) { + maxt = t; + } + } + bytePtr += j * sizeof( unsigned short ); + } + + // if this area is a portal + if ( file->GetArea( n ).cluster < 0 ) { + // set the maximum travel time through this portal + file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt ); + } + } + + assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) ); +} + +/* +============ +idAASLocal::DeleteAreaTravelTimes +============ +*/ +void idAASLocal::DeleteAreaTravelTimes() { + Mem_Free( areaTravelTimes ); + areaTravelTimes = NULL; + numAreaTravelTimes = 0; +} + +/* +============ +idAASLocal::SetupRoutingCache +============ +*/ +void idAASLocal::SetupRoutingCache() { + int i; + byte *bytePtr; + + areaCacheIndexSize = 0; + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndexSize += file->GetCluster( i ).numReachableAreas; + } + areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) + + areaCacheIndexSize * sizeof( idRoutingCache *), TAG_AAS ); + bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** ); + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr; + bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * ); + } + + portalCacheIndexSize = file->GetNumAreas(); + portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ), TAG_AAS ); + + areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ), TAG_AAS ); + portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ), TAG_AAS ); + + goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ), TAG_AAS ); + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::DeleteClusterCache +============ +*/ +void idAASLocal::DeleteClusterCache( int clusterNum ) { + int i; + idRoutingCache *cache; + + for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) { + for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) { + areaCacheIndex[clusterNum][i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::DeletePortalCache +============ +*/ +void idAASLocal::DeletePortalCache() { + int i; + idRoutingCache *cache; + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) { + portalCacheIndex[i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::ShutdownRoutingCache +============ +*/ +void idAASLocal::ShutdownRoutingCache() { + int i; + + for ( i = 0; i < file->GetNumClusters(); i++ ) { + DeleteClusterCache( i ); + } + + DeletePortalCache(); + + Mem_Free( areaCacheIndex ); + areaCacheIndex = NULL; + areaCacheIndexSize = 0; + Mem_Free( portalCacheIndex ); + portalCacheIndex = NULL; + portalCacheIndexSize = 0; + Mem_Free( areaUpdate ); + areaUpdate = NULL; + Mem_Free( portalUpdate ); + portalUpdate = NULL; + Mem_Free( goalAreaTravelTimes ); + goalAreaTravelTimes = NULL; + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::SetupRouting +============ +*/ +bool idAASLocal::SetupRouting() { + CalculateAreaTravelTimes(); + SetupRoutingCache(); + return true; +} + +/* +============ +idAASLocal::ShutdownRouting +============ +*/ +void idAASLocal::ShutdownRouting() { + DeleteAreaTravelTimes(); + ShutdownRoutingCache(); +} + +/* +============ +idAASLocal::RoutingStats +============ +*/ +void idAASLocal::RoutingStats() const { + idRoutingCache *cache; + int numAreaCache, numPortalCache; + int totalAreaCacheMemory, totalPortalCacheMemory; + + numAreaCache = numPortalCache = 0; + totalAreaCacheMemory = totalPortalCacheMemory = 0; + for ( cache = cacheListStart; cache; cache = cache->time_next ) { + if ( cache->type == CACHETYPE_AREA ) { + numAreaCache++; + totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } else { + numPortalCache++; + totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } + } + + gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 ); + gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 ); + gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 ); + gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 ); + gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); + gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); +} + +/* +============ +idAASLocal::RemoveRoutingCacheUsingArea +============ +*/ +void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) { + int clusterNum; + + clusterNum = file->GetArea( areaNum ).cluster; + if ( clusterNum > 0 ) { + // remove all the cache in the cluster the area is in + DeleteClusterCache( clusterNum ); + } + else { + // if this is a portal remove all cache in both the front and back cluster + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] ); + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] ); + } + DeletePortalCache(); +} + +/* +============ +idAASLocal::DisableArea +============ +*/ +void idAASLocal::DisableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) { + return; + } + + file->SetAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::EnableArea +============ +*/ +void idAASLocal::EnableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) { + return; + } + + file->RemoveAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::SetAreaState_r +============ +*/ +bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) { + int res; + const aasNode_t *node; + bool foundClusterPortal = false; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + // if this area is a cluster portal + if ( file->GetArea( -nodeNum ).contents & areaContents ) { + if ( disabled ) { + DisableArea( -nodeNum ); + } else { + EnableArea( -nodeNum ); + } + foundClusterPortal |= true; + } + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled ); + nodeNum = node->children[0]; + } + } + + return foundClusterPortal; +} + +/* +============ +idAASLocal::SetAreaState +============ +*/ +bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) { + idBounds expBounds; + + if ( !file ) { + return false; + } + + expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + + // find all areas within or touching the bounds with the given contents and disable/enable them for routing + return SetAreaState_r( 1, expBounds, areaContents, disabled ); +} + +/* +============ +idAASLocal::GetBoundsAreas_r +============ +*/ +void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const { + int res; + const aasNode_t *node; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + areas.Append( -nodeNum ); + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + GetBoundsAreas_r( node->children[1], bounds, areas ); + nodeNum = node->children[0]; + } + } +} + +/* +============ +idAASLocal::SetObstacleState +============ +*/ +void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) { + int i; + const aasArea_t *area; + idReachability *reach, *rev_reach; + bool inside; + + for ( i = 0; i < obstacle->areas.Num(); i++ ) { + + RemoveRoutingCacheUsingArea( obstacle->areas[i] ); + + area = &file->GetArea( obstacle->areas[i] ); + + for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + + if ( rev_reach->travelType & TFL_INVALID ) { + continue; + } + + inside = false; + + if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) { + inside = true; + } + else { + for ( reach = area->reach; reach; reach = reach->next ) { + if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) { + inside = true; + break; + } + } + } + + if ( inside ) { + if ( enable ) { + rev_reach->disableCount--; + if ( rev_reach->disableCount <= 0 ) { + rev_reach->travelType &= ~TFL_INVALID; + rev_reach->disableCount = 0; + } + } + else { + rev_reach->travelType |= TFL_INVALID; + rev_reach->disableCount++; + } + } + } + } +} + +/* +============ +idAASLocal::AddObstacle +============ +*/ +aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) { + idRoutingObstacle *obstacle; + + if ( !file ) { + return -1; + } + + obstacle = new (TAG_AAS) idRoutingObstacle; + obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas ); + SetObstacleState( obstacle, true ); + + obstacleList.Append( obstacle ); + return obstacleList.Num() - 1; +} + +/* +============ +idAASLocal::RemoveObstacle +============ +*/ +void idAASLocal::RemoveObstacle( const aasHandle_t handle ) { + if ( !file ) { + return; + } + if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) { + SetObstacleState( obstacleList[handle], false ); + + delete obstacleList[handle]; + obstacleList.RemoveIndex( handle ); + } +} + +/* +============ +idAASLocal::RemoveAllObstacles +============ +*/ +void idAASLocal::RemoveAllObstacles() { + int i; + + if ( !file ) { + return; + } + + for ( i = 0; i < obstacleList.Num(); i++ ) { + SetObstacleState( obstacleList[i], false ); + delete obstacleList[i]; + } + obstacleList.Clear(); +} + +/* +============ +idAASLocal::LinkCache + + link the cache in the cache list sorted from oldest to newest cache +============ +*/ +void idAASLocal::LinkCache( idRoutingCache *cache ) const { + + // if the cache is already linked + if ( cache->time_next || cache->time_prev || cacheListStart == cache ) { + UnlinkCache( cache ); + } + + totalCacheMemory += cache->Size(); + + // add cache to the end of the list + cache->time_next = NULL; + cache->time_prev = cacheListEnd; + if ( cacheListEnd ) { + cacheListEnd->time_next = cache; + } + cacheListEnd = cache; + if ( !cacheListStart ) { + cacheListStart = cache; + } +} + +/* +============ +idAASLocal::UnlinkCache +============ +*/ +void idAASLocal::UnlinkCache( idRoutingCache *cache ) const { + + totalCacheMemory -= cache->Size(); + + // unlink the cache + if ( cache->time_next ) { + cache->time_next->time_prev = cache->time_prev; + } else { + cacheListEnd = cache->time_prev; + } + if ( cache->time_prev ) { + cache->time_prev->time_next = cache->time_next; + } else { + cacheListStart = cache->time_next; + } + cache->time_next = cache->time_prev = NULL; +} + +/* +============ +idAASLocal::DeleteOldestCache +============ +*/ +void idAASLocal::DeleteOldestCache() const { + idRoutingCache *cache; + + assert( cacheListStart ); + + // unlink the oldest cache + cache = cacheListStart; + UnlinkCache( cache ); + + // unlink the oldest cache from the area or portal cache index + if ( cache->next ) { + cache->next->prev = cache->prev; + } + if ( cache->prev ) { + cache->prev->next = cache->next; + } + else if ( cache->type == CACHETYPE_AREA ) { + areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next; + } + else if ( cache->type == CACHETYPE_PORTAL ) { + portalCacheIndex[cache->areaNum] = cache->next; + } + + delete cache; +} + +/* +============ +idAASLocal::GetAreaReachability +============ +*/ +idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const { + idReachability *reach; + + for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) { + if ( --reachabilityNum < 0 ) { + return reach; + } + } + return NULL; +} + +/* +============ +idAASLocal::ClusterAreaNum +============ +*/ +ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const { + int side, areaCluster; + + areaCluster = file->GetArea( areaNum ).cluster; + if ( areaCluster > 0 ) { + return file->GetArea( areaNum ).clusterAreaNum; + } + else { + side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum; + return file->GetPortal( -areaCluster ).clusterAreaNum[side]; + } +} + +/* +============ +idAASLocal::UpdateAreaRoutingCache +============ +*/ +void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const { + int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas; + unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA]; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + + // number of reachability areas within this cluster + numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas; + + // number of the start area within the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + return; + } + + areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime; + badTravelFlags = ~areaCache->travelFlags; + memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) ); + + // initialize first update + curUpdate = &areaUpdate[clusterAreaNum]; + curUpdate->areaNum = areaCache->areaNum; + curUpdate->areaTravelTimes = startAreaTravelTimes; + curUpdate->tmpTravelTime = areaCache->startTravelTime; + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the list + while( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->fromAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + // get the cluster number of the area + cluster = nextArea->cluster; + // don't leave the cluster, however do flood into cluster portals + if ( cluster > 0 && cluster != areaCache->cluster ) { + continue; + } + + // get the number of the area in the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + continue; // should never happen + } + + assert( clusterAreaNum < areaCache->size ); + + // time already travelled plus the traveltime through the current area + // plus the travel time of the reachability towards the next area + t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime; + + if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) { + + areaCache->travelTimes[clusterAreaNum] = t; + areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area + nextUpdate = &areaUpdate[clusterAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->areaTravelTimes = reach->areaTravelTimes; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetAreaRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + int clusterAreaNum; + idRoutingCache *cache, *clusterCache; + + // number of the area in the cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // pointer to the cache for the area in the cluster + clusterCache = areaCacheIndex[clusterNum][clusterAreaNum]; + // check if cache without undesired travel flags already exists + for ( cache = clusterCache; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new (TAG_AAS) idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas ); + cache->type = CACHETYPE_AREA; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = clusterCache; + if ( clusterCache ) { + clusterCache->prev = cache; + } + areaCacheIndex[clusterNum][clusterAreaNum] = cache; + UpdateAreaRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::UpdatePortalRoutingCache +============ +*/ +void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const { + int i, portalNum, clusterAreaNum; + unsigned short t; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *cache; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + + curUpdate = &portalUpdate[ file->GetNumPortals() ]; + curUpdate->cluster = portalCache->cluster; + curUpdate->areaNum = portalCache->areaNum; + curUpdate->tmpTravelTime = portalCache->startTravelTime; + + //put the area to start with in the current read list + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the current list + while( updateListStart ) { + + curUpdate = updateListStart; + // remove the current update from the list + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + // current update is removed from the list + curUpdate->isInList = false; + + cluster = &file->GetCluster( curUpdate->cluster ); + cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags ); + + // take all portals of the cluster + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + assert( portalNum < portalCache->size ); + portal = &file->GetPortal( portalNum ); + + clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum ); + if ( clusterAreaNum >= cluster->numReachableAreas ) { + continue; + } + + t = cache->travelTimes[clusterAreaNum]; + if ( t == 0 ) { + continue; + } + t += curUpdate->tmpTravelTime; + + if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) { + + portalCache->travelTimes[portalNum] = t; + portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum]; + nextUpdate = &portalUpdate[portalNum]; + if ( portal->clusters[0] == curUpdate->cluster ) { + nextUpdate->cluster = portal->clusters[1]; + } + else { + nextUpdate->cluster = portal->clusters[0]; + } + nextUpdate->areaNum = portal->areaNum; + // add travel time through the actual portal area for the next update + nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime; + + if ( !nextUpdate->isInList ) { + + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetPortalRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + idRoutingCache *cache; + + // check if cache without undesired travel flags already exists + for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new (TAG_AAS) idRoutingCache( file->GetNumPortals() ); + cache->type = CACHETYPE_PORTAL; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = portalCacheIndex[areaNum]; + if ( portalCacheIndex[areaNum] ) { + portalCacheIndex[areaNum]->prev = cache; + } + portalCacheIndex[areaNum] = cache; + UpdatePortalRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::RouteToGoalArea +============ +*/ +bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const { + int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum; + unsigned short int t, bestTime; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *areaCache, *portalCache, *clusterCache; + idReachability *bestReach, *r, *nextr; + + travelTime = 0; + *reach = NULL; + + if ( !file ) { + return false; + } + + if ( areaNum == goalAreaNum ) { + return true; + } + + if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) { + gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum ); + return false; + } + if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) { + gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum ); + return false; + } + + while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) { + DeleteOldestCache(); + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the source area is a cluster portal, read directly from the portal cache + if ( clusterNum < 0 ) { + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + *reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] ); + travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start ); + return true; + } + + bestTime = 0; + bestReach = NULL; + + // check if the goal area is a portal of the source area cluster + if ( goalClusterNum < 0 ) { + portal = &file->GetPortal( -goalClusterNum ); + if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) { + goalClusterNum = clusterNum; + } + } + + // if both areas are in the same cluster + if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) { + clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags ); + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + if ( clusterCache->travelTimes[clusterAreaNum] ) { + bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] ); + bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start ); + } + else { + clusterCache = NULL; + } + } + else { + clusterCache = NULL; + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + + // the cluster the area is in + cluster = &file->GetCluster( clusterNum ); + // current area inside the current cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // if the area is not a reachable area + if ( clusterAreaNum >= cluster->numReachableAreas) { + return false; + } + + // find the portal of the source area cluster leading towards the goal area + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + + // if the goal area isn't reachable from the portal + if ( !portalCache->travelTimes[portalNum] ) { + continue; + } + + portal = &file->GetPortal( portalNum ); + // get the cache of the portal area + areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags ); + // if the portal is not reachable from this area + if ( !areaCache->travelTimes[clusterAreaNum] ) { + continue; + } + + r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] ); + + if ( clusterCache ) { + // if the next reachability from the portal leads back into the cluster + nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] ); + if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) { + continue; + } + } + + // the total travel time is the travel time from the portal area to the goal area + // plus the travel time from the source area towards the portal area + t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum]; + // NOTE: Should add the exact travel time through the portal area. + // However we add the largest travel time through the portal area. + // We cannot directly calculate the exact travel time through the portal area + // because the reachability used to travel into the portal area is not known. + t += portal->maxAreaTravelTime; + + // if the time is better than the one already found + if ( !bestTime || t < bestTime ) { + bestReach = r; + bestTime = t; + } + } + + if ( !bestReach ) { + return false; + } + + *reach = bestReach; + travelTime = bestTime; + + return true; +} + +/* +============ +idAASLocal::TravelTimeToGoalArea +============ +*/ +int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { + int travelTime; + idReachability *reach; + + if ( !file ) { + return 0; + } + + if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) { + return 0; + } + return travelTime; +} + +/* +============ +idAASLocal::FindNearestGoal +============ +*/ +bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const { + int i, j, k, badTravelFlags, nextAreaNum, bestAreaNum; + unsigned short t, bestTravelTime; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + idVec3 v1, v2, p; + float targetDist, dist; + + if ( file == NULL || areaNum <= 0 ) { + goal.areaNum = areaNum; + goal.origin = origin; + return false; + } + + // if the first area is valid goal, just return the origin + if ( callback.TestArea( this, areaNum ) ) { + goal.areaNum = areaNum; + goal.origin = origin; + return true; + } + + // setup obstacles + for ( k = 0; k < numObstacles; k++ ) { + obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0]; + } + + badTravelFlags = ~travelFlags; + SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) ); + + targetDist = (target - origin).Length(); + + // initialize first update + curUpdate = &areaUpdate[areaNum]; + curUpdate->areaNum = areaNum; + curUpdate->tmpTravelTime = 0; + curUpdate->start = origin; + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + bestTravelTime = 0; + bestAreaNum = 0; + + // while there are updates in the list + while ( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + // if we already found a closer location + if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) { + continue; + } + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->toAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + t = curUpdate->tmpTravelTime + + AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) + + reach->travelTime; + + // project target origin onto movement vector through the area + v1 = reach->end - curUpdate->start; + v1.Normalize(); + v2 = target - curUpdate->start; + p = curUpdate->start + (v2 * v1) * v1; + + // get the point on the path closest to the target + for ( j = 0; j < 3; j++ ) { + if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) || + (p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) { + break; + } + } + if ( j >= 3 ) { + dist = (target - p).Length(); + } else { + dist = (target - reach->end).Length(); + } + + // avoid moving closer to the target + if ( dist < targetDist ) { + t += ( targetDist - dist ) * 10; + } + + // if we already found a closer location + if ( bestTravelTime && t >= bestTravelTime ) { + continue; + } + + // if this is not the best path towards the next area + if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) { + continue; + } + + // path may not go through any obstacles + for ( k = 0; k < numObstacles; k++ ) { + // if the movement vector intersects the expanded obstacle bounds + if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + } + if ( k < numObstacles ) { + continue; + } + + goalAreaTravelTimes[nextAreaNum] = t; + nextUpdate = &areaUpdate[nextAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->start = reach->end; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + + // don't put goal near a ledge + if ( !( nextArea->flags & AREA_LEDGE ) ) { + + // add travel time through the area + t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center ); + + if ( !bestTravelTime || t < bestTravelTime ) { + // if the area is not visible to the target + if ( callback.TestArea( this, reach->toAreaNum ) ) { + bestTravelTime = t; + bestAreaNum = reach->toAreaNum; + } + } + } + } + } + + if ( bestAreaNum ) { + goal.areaNum = bestAreaNum; + goal.origin = AreaCenter( bestAreaNum ); + return true; + } + + return false; +} diff --git a/neo/d3xp/ai/AI.cpp b/neo/d3xp/ai/AI.cpp new file mode 100644 index 00000000..5f9c8b9e --- /dev/null +++ b/neo/d3xp/ai/AI.cpp @@ -0,0 +1,5337 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +static const char *moveCommandString[ NUM_MOVE_COMMANDS ] = { + "MOVE_NONE", + "MOVE_FACE_ENEMY", + "MOVE_FACE_ENTITY", + "MOVE_TO_ENEMY", + "MOVE_TO_ENEMYHEIGHT", + "MOVE_TO_ENTITY", + "MOVE_OUT_OF_RANGE", + "MOVE_TO_ATTACK_POSITION", + "MOVE_TO_COVER", + "MOVE_TO_POSITION", + "MOVE_TO_POSITION_DIRECT", + "MOVE_SLIDE_TO_POSITION", + "MOVE_WANDER" +}; + +/* +===================== +idMoveState::idMoveState +===================== +*/ +idMoveState::idMoveState() { + moveType = MOVETYPE_ANIM; + moveCommand = MOVE_NONE; + moveStatus = MOVE_STATUS_DONE; + moveDest.Zero(); + moveDir.Set( 1.0f, 0.0f, 0.0f ); + goalEntity = NULL; + goalEntityOrigin.Zero(); + toAreaNum = 0; + startTime = 0; + duration = 0; + speed = 0.0f; + range = 0.0f; + wanderYaw = 0; + nextWanderTime = 0; + blockTime = 0; + obstacle = NULL; + lastMoveOrigin = vec3_origin; + lastMoveTime = 0; + anim = 0; +} + +/* +===================== +idMoveState::Save +===================== +*/ +void idMoveState::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)moveType ); + savefile->WriteInt( (int)moveCommand ); + savefile->WriteInt( (int)moveStatus ); + savefile->WriteVec3( moveDest ); + savefile->WriteVec3( moveDir ); + goalEntity.Save( savefile ); + savefile->WriteVec3( goalEntityOrigin ); + savefile->WriteInt( toAreaNum ); + savefile->WriteInt( startTime ); + savefile->WriteInt( duration ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( range ); + savefile->WriteFloat( wanderYaw ); + savefile->WriteInt( nextWanderTime ); + savefile->WriteInt( blockTime ); + obstacle.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( lastMoveTime ); + savefile->WriteInt( anim ); +} + +/* +===================== +idMoveState::Restore +===================== +*/ +void idMoveState::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)moveType ); + savefile->ReadInt( (int &)moveCommand ); + savefile->ReadInt( (int &)moveStatus ); + savefile->ReadVec3( moveDest ); + savefile->ReadVec3( moveDir ); + goalEntity.Restore( savefile ); + savefile->ReadVec3( goalEntityOrigin ); + savefile->ReadInt( toAreaNum ); + savefile->ReadInt( startTime ); + savefile->ReadInt( duration ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( range ); + savefile->ReadFloat( wanderYaw ); + savefile->ReadInt( nextWanderTime ); + savefile->ReadInt( blockTime ); + obstacle.Restore( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( lastMoveTime ); + savefile->ReadInt( anim ); +} + +/* +============ +idAASFindCover::idAASFindCover +============ +*/ +idAASFindCover::idAASFindCover( const idVec3 &hideFromPos ) { + int numPVSAreas; + idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) ); + + // setup PVS + numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS ); + hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); +} + +/* +============ +idAASFindCover::~idAASFindCover +============ +*/ +idAASFindCover::~idAASFindCover() { + gameLocal.pvs.FreeCurrentPVS( hidePVS ); +} + +/* +============ +idAASFindCover::TestArea +============ +*/ +bool idAASFindCover::TestArea( const idAAS *aas, int areaNum ) { + idVec3 areaCenter; + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + areaCenter = aas->AreaCenter( areaNum ); + areaCenter[ 2 ] += 1.0f; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS ); + if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) { + return true; + } + + return false; +} + +/* +============ +idAASFindAreaOutOfRange::idAASFindAreaOutOfRange +============ +*/ +idAASFindAreaOutOfRange::idAASFindAreaOutOfRange( const idVec3 &targetPos, float maxDist ) { + this->targetPos = targetPos; + this->maxDistSqr = maxDist * maxDist; +} + +/* +============ +idAASFindAreaOutOfRange::TestArea +============ +*/ +bool idAASFindAreaOutOfRange::TestArea( const idAAS *aas, int areaNum ) { + const idVec3 &areaCenter = aas->AreaCenter( areaNum ); + trace_t trace; + float dist; + + dist = ( targetPos.ToVec2() - areaCenter.ToVec2() ).LengthSqr(); + + if ( ( maxDistSqr > 0.0f ) && ( dist < maxDistSqr ) ) { + return false; + } + + gameLocal.clip.TracePoint( trace, targetPos, areaCenter + idVec3( 0.0f, 0.0f, 1.0f ), MASK_OPAQUE, NULL ); + if ( trace.fraction < 1.0f ) { + return false; + } + + return true; +} + +/* +============ +idAASFindAttackPosition::idAASFindAttackPosition +============ +*/ +idAASFindAttackPosition::idAASFindAttackPosition( const idAI *self, const idMat3 &gravityAxis, idEntity *target, const idVec3 &targetPos, const idVec3 &fireOffset ) { + int numPVSAreas; + + this->target = target; + this->targetPos = targetPos; + this->fireOffset = fireOffset; + this->self = self; + this->gravityAxis = gravityAxis; + + excludeBounds = idBounds( idVec3( -64.0, -64.0f, -8.0f ), idVec3( 64.0, 64.0f, 64.0f ) ); + excludeBounds.TranslateSelf( self->GetPhysics()->GetOrigin() ); + + // setup PVS + idBounds bounds( targetPos - idVec3( 16, 16, 0 ), targetPos + idVec3( 16, 16, 64 ) ); + numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS ); + targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); +} + +/* +============ +idAASFindAttackPosition::~idAASFindAttackPosition +============ +*/ +idAASFindAttackPosition::~idAASFindAttackPosition() { + gameLocal.pvs.FreeCurrentPVS( targetPVS ); +} + +/* +============ +idAASFindAttackPosition::TestArea +============ +*/ +bool idAASFindAttackPosition::TestArea( const idAAS *aas, int areaNum ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 areaCenter; + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + areaCenter = aas->AreaCenter( areaNum ); + areaCenter[ 2 ] += 1.0f; + + if ( excludeBounds.ContainsPoint( areaCenter ) ) { + // too close to where we already are + return false; + } + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS ); + if ( !gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ) ) { + return false; + } + + // calculate the world transform of the launch position + dir = targetPos - areaCenter; + gravityAxis.ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + fromPos = areaCenter + fireOffset * axis; + + return self->GetAimDir( fromPos, target, self, dir ); +} + +/* +===================== +idAI::idAI +===================== +*/ +idAI::idAI() { + aas = NULL; + travelFlags = TFL_WALK|TFL_AIR; + + kickForce = 2048.0f; + ignore_obstacles = false; + blockedRadius = 0.0f; + blockedMoveTime = 750; + blockedAttackTime = 750; + turnRate = 360.0f; + turnVel = 0.0f; + anim_turn_yaw = 0.0f; + anim_turn_amount = 0.0f; + anim_turn_angles = 0.0f; + fly_offset = 0; + fly_seek_scale = 1.0f; + fly_roll_scale = 0.0f; + fly_roll_max = 0.0f; + fly_roll = 0.0f; + fly_pitch_scale = 0.0f; + fly_pitch_max = 0.0f; + fly_pitch = 0.0f; + allowMove = false; + allowHiddenMovement = false; + fly_speed = 0.0f; + fly_bob_strength = 0.0f; + fly_bob_vert = 0.0f; + fly_bob_horz = 0.0f; + lastHitCheckResult = false; + lastHitCheckTime = 0; + lastAttackTime = 0; + melee_range = 0.0f; + projectile_height_to_distance_ratio = 1.0f; + projectileDef = NULL; + projectile = NULL; + projectileClipModel = NULL; + projectileRadius = 0.0f; + projectileVelocity = vec3_origin; + projectileGravity = vec3_origin; + projectileSpeed = 0.0f; + chat_snd = NULL; + chat_min = 0; + chat_max = 0; + chat_time = 0; + talk_state = TALK_NEVER; + talkTarget = NULL; + + particles.Clear(); + restartParticles = true; + useBoneAxis = false; + + wakeOnFlashlight = false; + memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) ); + worldMuzzleFlashHandle = -1; + + enemy = NULL; + lastVisibleEnemyPos.Zero(); + lastVisibleEnemyEyeOffset.Zero(); + lastVisibleReachableEnemyPos.Zero(); + lastReachableEnemyPos.Zero(); + fl.neverDormant = false; // AI's can go dormant + current_yaw = 0.0f; + ideal_yaw = 0.0f; + + spawnClearMoveables = false; + harvestEnt = NULL; + + num_cinematics = 0; + current_cinematic = 0; + + allowEyeFocus = true; + allowPain = true; + allowJointMod = true; + focusEntity = NULL; + focusTime = 0; + alignHeadTime = 0; + forceAlignHeadTime = 0; + + currentFocusPos.Zero(); + eyeAng.Zero(); + lookAng.Zero(); + destLookAng.Zero(); + lookMin.Zero(); + lookMax.Zero(); + + eyeMin.Zero(); + eyeMax.Zero(); + muzzleFlashEnd = 0; + flashTime = 0; + flashJointWorld = INVALID_JOINT; + + focusJoint = INVALID_JOINT; + orientationJoint = INVALID_JOINT; + flyTiltJoint = INVALID_JOINT; + + eyeVerticalOffset = 0.0f; + eyeHorizontalOffset = 0.0f; + eyeFocusRate = 0.0f; + headFocusRate = 0.0f; + focusAlignTime = 0; +} + +/* +===================== +idAI::~idAI +===================== +*/ +idAI::~idAI() { + delete projectileClipModel; + DeconstructScriptObject(); + scriptObject.Free(); + if ( worldMuzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle ); + worldMuzzleFlashHandle = -1; + } + + if ( harvestEnt.GetEntity() ) { + harvestEnt.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +===================== +idAI::Save +===================== +*/ +void idAI::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( travelFlags ); + move.Save( savefile ); + savedMove.Save( savefile ); + savefile->WriteFloat( kickForce ); + savefile->WriteBool( ignore_obstacles ); + savefile->WriteFloat( blockedRadius ); + savefile->WriteInt( blockedMoveTime ); + savefile->WriteInt( blockedAttackTime ); + + savefile->WriteFloat( ideal_yaw ); + savefile->WriteFloat( current_yaw ); + savefile->WriteFloat( turnRate ); + savefile->WriteFloat( turnVel ); + savefile->WriteFloat( anim_turn_yaw ); + savefile->WriteFloat( anim_turn_amount ); + savefile->WriteFloat( anim_turn_angles ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteFloat( fly_speed ); + savefile->WriteFloat( fly_bob_strength ); + savefile->WriteFloat( fly_bob_vert ); + savefile->WriteFloat( fly_bob_horz ); + savefile->WriteInt( fly_offset ); + savefile->WriteFloat( fly_seek_scale ); + savefile->WriteFloat( fly_roll_scale ); + savefile->WriteFloat( fly_roll_max ); + savefile->WriteFloat( fly_roll ); + savefile->WriteFloat( fly_pitch_scale ); + savefile->WriteFloat( fly_pitch_max ); + savefile->WriteFloat( fly_pitch ); + + savefile->WriteBool( allowMove ); + savefile->WriteBool( allowHiddenMovement ); + savefile->WriteBool( disableGravity ); + savefile->WriteBool( af_push_moveables ); + + savefile->WriteBool( lastHitCheckResult ); + savefile->WriteInt( lastHitCheckTime ); + savefile->WriteInt( lastAttackTime ); + savefile->WriteFloat( melee_range ); + savefile->WriteFloat( projectile_height_to_distance_ratio ); + + savefile->WriteInt( missileLaunchOffset.Num() ); + for( i = 0; i < missileLaunchOffset.Num(); i++ ) { + savefile->WriteVec3( missileLaunchOffset[ i ] ); + } + + idStr projectileName; + spawnArgs.GetString( "def_projectile", "", projectileName ); + savefile->WriteString( projectileName ); + savefile->WriteFloat( projectileRadius ); + savefile->WriteFloat( projectileSpeed ); + savefile->WriteVec3( projectileVelocity ); + savefile->WriteVec3( projectileGravity ); + projectile.Save( savefile ); + savefile->WriteString( attack ); + + savefile->WriteSoundShader( chat_snd ); + savefile->WriteInt( chat_min ); + savefile->WriteInt( chat_max ); + savefile->WriteInt( chat_time ); + savefile->WriteInt( talk_state ); + talkTarget.Save( savefile ); + + savefile->WriteInt( num_cinematics ); + savefile->WriteInt( current_cinematic ); + + savefile->WriteBool( allowJointMod ); + focusEntity.Save( savefile ); + savefile->WriteVec3( currentFocusPos ); + savefile->WriteInt( focusTime ); + savefile->WriteInt( alignHeadTime ); + savefile->WriteInt( forceAlignHeadTime ); + savefile->WriteAngles( eyeAng ); + savefile->WriteAngles( lookAng ); + savefile->WriteAngles( destLookAng ); + savefile->WriteAngles( lookMin ); + savefile->WriteAngles( lookMax ); + + savefile->WriteInt( lookJoints.Num() ); + for( i = 0; i < lookJoints.Num(); i++ ) { + savefile->WriteJoint( lookJoints[ i ] ); + savefile->WriteAngles( lookJointAngles[ i ] ); + } + + savefile->WriteInt( particles.Num() ); + for ( i = 0; i < particles.Num(); i++ ) { + savefile->WriteParticle( particles[i].particle ); + savefile->WriteInt( particles[i].time ); + savefile->WriteJoint( particles[i].joint ); + } + savefile->WriteBool( restartParticles ); + savefile->WriteBool( useBoneAxis ); + + enemy.Save( savefile ); + savefile->WriteVec3( lastVisibleEnemyPos ); + savefile->WriteVec3( lastVisibleEnemyEyeOffset ); + savefile->WriteVec3( lastVisibleReachableEnemyPos ); + savefile->WriteVec3( lastReachableEnemyPos ); + savefile->WriteBool( wakeOnFlashlight ); + + savefile->WriteAngles( eyeMin ); + savefile->WriteAngles( eyeMax ); + + savefile->WriteFloat( eyeVerticalOffset ); + savefile->WriteFloat( eyeHorizontalOffset ); + savefile->WriteFloat( eyeFocusRate ); + savefile->WriteFloat( headFocusRate ); + savefile->WriteInt( focusAlignTime ); + + savefile->WriteJoint( flashJointWorld ); + savefile->WriteInt( muzzleFlashEnd ); + + savefile->WriteJoint( focusJoint ); + savefile->WriteJoint( orientationJoint ); + savefile->WriteJoint( flyTiltJoint ); + + savefile->WriteBool( GetPhysics() == static_cast(&physicsObj) ); + + savefile->WriteInt(funcEmitters.Num()); + for(int i = 0; i < funcEmitters.Num(); i++) { + funcEmitter_t* emitter = funcEmitters.GetIndex(i); + savefile->WriteString(emitter->name); + savefile->WriteJoint(emitter->joint); + savefile->WriteObject(emitter->particle); + } + + harvestEnt.Save( savefile); +} + +/* +===================== +idAI::Restore +===================== +*/ +void idAI::Restore( idRestoreGame *savefile ) { + bool restorePhysics; + int i; + int num; + idBounds bounds; + + savefile->ReadInt( travelFlags ); + move.Restore( savefile ); + savedMove.Restore( savefile ); + savefile->ReadFloat( kickForce ); + savefile->ReadBool( ignore_obstacles ); + savefile->ReadFloat( blockedRadius ); + savefile->ReadInt( blockedMoveTime ); + savefile->ReadInt( blockedAttackTime ); + + savefile->ReadFloat( ideal_yaw ); + savefile->ReadFloat( current_yaw ); + savefile->ReadFloat( turnRate ); + savefile->ReadFloat( turnVel ); + savefile->ReadFloat( anim_turn_yaw ); + savefile->ReadFloat( anim_turn_amount ); + savefile->ReadFloat( anim_turn_angles ); + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadFloat( fly_speed ); + savefile->ReadFloat( fly_bob_strength ); + savefile->ReadFloat( fly_bob_vert ); + savefile->ReadFloat( fly_bob_horz ); + savefile->ReadInt( fly_offset ); + savefile->ReadFloat( fly_seek_scale ); + savefile->ReadFloat( fly_roll_scale ); + savefile->ReadFloat( fly_roll_max ); + savefile->ReadFloat( fly_roll ); + savefile->ReadFloat( fly_pitch_scale ); + savefile->ReadFloat( fly_pitch_max ); + savefile->ReadFloat( fly_pitch ); + + savefile->ReadBool( allowMove ); + savefile->ReadBool( allowHiddenMovement ); + savefile->ReadBool( disableGravity ); + savefile->ReadBool( af_push_moveables ); + + savefile->ReadBool( lastHitCheckResult ); + savefile->ReadInt( lastHitCheckTime ); + savefile->ReadInt( lastAttackTime ); + savefile->ReadFloat( melee_range ); + savefile->ReadFloat( projectile_height_to_distance_ratio ); + + savefile->ReadInt( num ); + missileLaunchOffset.SetGranularity( 1 ); + missileLaunchOffset.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( missileLaunchOffset[ i ] ); + } + + idStr projectileName; + savefile->ReadString( projectileName ); + if ( projectileName.Length() ) { + projectileDef = gameLocal.FindEntityDefDict( projectileName ); + } else { + projectileDef = NULL; + } + savefile->ReadFloat( projectileRadius ); + savefile->ReadFloat( projectileSpeed ); + savefile->ReadVec3( projectileVelocity ); + savefile->ReadVec3( projectileGravity ); + projectile.Restore( savefile ); + savefile->ReadString( attack ); + + savefile->ReadSoundShader( chat_snd ); + savefile->ReadInt( chat_min ); + savefile->ReadInt( chat_max ); + savefile->ReadInt( chat_time ); + savefile->ReadInt( i ); + talk_state = static_cast( i ); + talkTarget.Restore( savefile ); + + savefile->ReadInt( num_cinematics ); + savefile->ReadInt( current_cinematic ); + + savefile->ReadBool( allowJointMod ); + focusEntity.Restore( savefile ); + savefile->ReadVec3( currentFocusPos ); + savefile->ReadInt( focusTime ); + savefile->ReadInt( alignHeadTime ); + savefile->ReadInt( forceAlignHeadTime ); + savefile->ReadAngles( eyeAng ); + savefile->ReadAngles( lookAng ); + savefile->ReadAngles( destLookAng ); + savefile->ReadAngles( lookMin ); + savefile->ReadAngles( lookMax ); + + savefile->ReadInt( num ); + lookJoints.SetGranularity( 1 ); + lookJoints.SetNum( num ); + lookJointAngles.SetGranularity( 1 ); + lookJointAngles.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadJoint( lookJoints[ i ] ); + savefile->ReadAngles( lookJointAngles[ i ] ); + } + + savefile->ReadInt( num ); + particles.SetNum( num ); + for ( i = 0; i < particles.Num(); i++ ) { + savefile->ReadParticle( particles[i].particle ); + savefile->ReadInt( particles[i].time ); + savefile->ReadJoint( particles[i].joint ); + } + savefile->ReadBool( restartParticles ); + savefile->ReadBool( useBoneAxis ); + + enemy.Restore( savefile ); + savefile->ReadVec3( lastVisibleEnemyPos ); + savefile->ReadVec3( lastVisibleEnemyEyeOffset ); + savefile->ReadVec3( lastVisibleReachableEnemyPos ); + savefile->ReadVec3( lastReachableEnemyPos ); + + savefile->ReadBool( wakeOnFlashlight ); + + savefile->ReadAngles( eyeMin ); + savefile->ReadAngles( eyeMax ); + + savefile->ReadFloat( eyeVerticalOffset ); + savefile->ReadFloat( eyeHorizontalOffset ); + savefile->ReadFloat( eyeFocusRate ); + savefile->ReadFloat( headFocusRate ); + savefile->ReadInt( focusAlignTime ); + + savefile->ReadJoint( flashJointWorld ); + savefile->ReadInt( muzzleFlashEnd ); + + savefile->ReadJoint( focusJoint ); + savefile->ReadJoint( orientationJoint ); + savefile->ReadJoint( flyTiltJoint ); + + savefile->ReadBool( restorePhysics ); + + // Set the AAS if the character has the correct gravity vector + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + if ( gravity == gameLocal.GetGravity() ) { + SetAAS(); + } + + SetCombatModel(); + LinkCombat(); + + InitMuzzleFlash(); + + // Link the script variables back to the scriptobject + LinkScriptVariables(); + + if ( restorePhysics ) { + RestorePhysics( &physicsObj ); + } + + + //Clean up the emitters + for(int i = 0; i < funcEmitters.Num(); i++) { + funcEmitter_t* emitter = funcEmitters.GetIndex(i); + if(emitter->particle) { + //Destroy the emitters + emitter->particle->PostEventMS(&EV_Remove, 0 ); + } + } + funcEmitters.Clear(); + + int emitterCount; + savefile->ReadInt( emitterCount ); + for(int i = 0; i < emitterCount; i++) { + funcEmitter_t newEmitter; + memset(&newEmitter, 0, sizeof(newEmitter)); + + idStr name; + savefile->ReadString( name ); + + strcpy( newEmitter.name, name.c_str() ); + + savefile->ReadJoint( newEmitter.joint ); + savefile->ReadObject(reinterpret_cast(newEmitter.particle)); + + funcEmitters.Set(newEmitter.name, newEmitter); + } + + harvestEnt.Restore(savefile); + //if(harvestEnt.GetEntity()) { + // harvestEnt.GetEntity()->SetParent(this); + //} + +} + +/* +===================== +idAI::Spawn +===================== +*/ +void idAI::Spawn() { + const char *jointname; + const idKeyValue *kv; + idStr jointName; + idAngles jointScale; + jointHandle_t joint; + idVec3 local_dir; + bool talks; + + if ( !g_monsters.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + spawnArgs.GetInt( "team", "1", team ); + spawnArgs.GetInt( "rank", "0", rank ); + spawnArgs.GetInt( "fly_offset", "0", fly_offset ); + spawnArgs.GetFloat( "fly_speed", "100", fly_speed ); + spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength ); + spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz ); + spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert ); + spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale ); + spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale ); + spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max ); + spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale ); + spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max ); + + spawnArgs.GetFloat( "melee_range", "64", melee_range ); + spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio ); + + spawnArgs.GetFloat( "turn_rate", "360", turnRate ); + + spawnArgs.GetBool( "talks", "0", talks ); + if ( spawnArgs.GetString( "npc_name", NULL ) != NULL ) { + if ( talks ) { + talk_state = TALK_OK; + } else { + talk_state = TALK_BUSY; + } + } else { + talk_state = TALK_NEVER; + } + + spawnArgs.GetBool( "animate_z", "0", disableGravity ); + spawnArgs.GetBool( "af_push_moveables", "0", af_push_moveables ); + spawnArgs.GetFloat( "kick_force", "4096", kickForce ); + spawnArgs.GetBool( "ignore_obstacles", "0", ignore_obstacles ); + spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius ); + spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime ); + spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime ); + + spawnArgs.GetInt( "num_cinematics", "0", num_cinematics ); + current_cinematic = 0; + + LinkScriptVariables(); + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + enemy = NULL; + allowMove = true; + allowHiddenMovement = false; + + animator.RemoveOriginOffset( true ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" ); + lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" ); + + lookJoints.SetGranularity( 1 ); + lookJointAngles.SetGranularity( 1 ); + kv = spawnArgs.MatchPrefix( "look_joint", NULL ); + while( kv ) { + jointName = kv->GetKey(); + jointName.StripLeadingOnce( "look_joint " ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() ); + } else { + jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" ); + jointScale.roll = 0.0f; + + // if no scale on any component, then don't bother adding it. this may be done to + // zero out rotation from an inherited entitydef. + if ( jointScale != ang_zero ) { + lookJoints.Append( joint ); + lookJointAngles.Append( jointScale ); + } + } + kv = spawnArgs.MatchPrefix( "look_joint", kv ); + } + + // calculate joint positions on attack frames so we can do proper "can hit" tests + CalculateAttackOffsets(); + + eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" ); + eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" ); + eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" ); + eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" ); + eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" ); + headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.1" ); + focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) ); + + flashJointWorld = animator.GetJointHandle( "flash" ); + + if ( head.GetEntity() ) { + idAnimator *headAnimator = head.GetEntity()->GetAnimator(); + + jointname = spawnArgs.GetString( "bone_focus" ); + if ( *jointname ) { + focusJoint = headAnimator->GetJointHandle( jointname ); + if ( focusJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on head on '%s'", jointname, name.c_str() ); + } + } + } else { + jointname = spawnArgs.GetString( "bone_focus" ); + if ( *jointname ) { + focusJoint = animator.GetJointHandle( jointname ); + if ( focusJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + } + + jointname = spawnArgs.GetString( "bone_orientation" ); + if ( *jointname ) { + orientationJoint = animator.GetJointHandle( jointname ); + if ( orientationJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + jointname = spawnArgs.GetString( "bone_flytilt" ); + if ( *jointname ) { + flyTiltJoint = animator.GetJointHandle( jointname ); + if ( flyTiltJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + InitMuzzleFlash(); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY ); + } else { + if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + } + + // move up to make sure the monster is at least an epsilon above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + if ( num_cinematics ) { + physicsObj.SetGravity( vec3_origin ); + } else { + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + physicsObj.SetGravity( gravity ); + } + + SetPhysics( &physicsObj ); + + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + current_yaw = local_dir.ToYaw(); + ideal_yaw = idMath::AngleNormalize180( current_yaw ); + + move.blockTime = 0; + + SetAAS(); + + projectile = NULL; + projectileDef = NULL; + projectileClipModel = NULL; + idStr projectileName; + if ( spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) { + projectileDef = gameLocal.FindEntityDefDict( projectileName ); + CreateProjectile( vec3_origin, viewAxis[ 0 ] ); + projectileRadius = projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius(); + projectileVelocity = idProjectile::GetVelocity( projectileDef ); + projectileGravity = idProjectile::GetGravity( projectileDef ); + projectileSpeed = projectileVelocity.Length(); + delete projectile.GetEntity(); + projectile = NULL; + } + + particles.Clear(); + restartParticles = true; + useBoneAxis = spawnArgs.GetBool( "useBoneAxis" ); + SpawnParticles( "smokeParticleSystem" ); + + if ( num_cinematics || spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "teleport" ) || spawnArgs.GetBool( "trigger_anim" ) ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + Hide(); + } else { + // play a looping ambient sound if we have one + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); + } + + if ( health <= 0 ) { + gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() ); + health = 1; + } + + // set up monster chatter + SetChatSound(); + + BecomeActive( TH_THINK ); + + if ( af_push_moveables ) { + af.SetupPose( this, gameLocal.time ); + af.GetPhysics()->EnableClip(); + } + + // init the move variables + StopMove( MOVE_STATUS_DONE ); + + + spawnArgs.GetBool( "spawnClearMoveables", "0", spawnClearMoveables ); +} + + +void idAI::Gib( const idVec3 &dir, const char *damageDefName ) { + if(harvestEnt.GetEntity()) { + //Let the harvest ent know that we gibbed + harvestEnt.GetEntity()->Gib(); + } + idActor::Gib(dir, damageDefName); +} + +/* +=================== +idAI::InitMuzzleFlash +=================== +*/ +void idAI::InitMuzzleFlash() { + const char *shader; + idVec3 flashColor; + + spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader ); + spawnArgs.GetVector( "flashColor", "0 0 0", flashColor ); + float flashRadius = spawnArgs.GetFloat( "flashRadius" ); + flashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) ); + + memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) ); + + worldMuzzleFlash.pointLight = true; + worldMuzzleFlash.shader = declManager->FindMaterial( shader, false ); + worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; + worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; + worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; + worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + worldMuzzleFlash.lightRadius[0] = flashRadius; + worldMuzzleFlash.lightRadius[1] = flashRadius; + worldMuzzleFlash.lightRadius[2] = flashRadius; + + worldMuzzleFlashHandle = -1; +} + +/* +=================== +idAI::List_f +=================== +*/ +void idAI::List_f( const idCmdArgs &args ) { + int e; + idAI *check; + int count; + const char *statename; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" ); + gameLocal.Printf( "------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = static_cast(gameLocal.entities[ e ]); + if ( !check || !check->IsType( idAI::Type ) ) { + continue; + } + + if ( check->state ) { + statename = check->state->Name(); + } else { + statename = "NULL state"; + } + + gameLocal.Printf( "%4i: %-20s %-20s %s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), statename, check->allowMove ); + count++; + } + + gameLocal.Printf( "...%d monsters\n", count ); +} + +/* +================ +idAI::DormantBegin + +called when entity becomes dormant +================ +*/ +void idAI::DormantBegin() { + // since dormant happens on a timer, we wont get to update particles to + // hidden through the think loop, but we need to hide them though. + if ( particles.Num() ) { + for ( int i = 0; i < particles.Num(); i++ ) { + particles[i].time = 0; + } + } + + if ( enemyNode.InList() ) { + // remove ourselves from the enemy's enemylist + enemyNode.Remove(); + } + idActor::DormantBegin(); +} + +/* +================ +idAI::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idAI::DormantEnd() { + if ( enemy.GetEntity() && !enemyNode.InList() ) { + // let our enemy know we're back on the trail + enemyNode.AddToEnd( enemy.GetEntity()->enemyList ); + } + + if ( particles.Num() ) { + for ( int i = 0; i < particles.Num(); i++ ) { + particles[i].time = gameLocal.time; + } + } + + idActor::DormantEnd(); +} + +/* +===================== +idAI::Think +===================== +*/ +idCVar ai_think( "ai_think", "1", CVAR_BOOL, "for testing.." ); +void idAI::Think() { + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + if ( !ai_think.GetBool() ) { + return; + } + + if ( thinkFlags & TH_THINK ) { + // clear out the enemy when he dies or is hidden + idActor *enemyEnt = enemy.GetEntity(); + if ( enemyEnt ) { + if ( enemyEnt->health <= 0 ) { + EnemyDead(); + } + } + + current_yaw += deltaViewAngles.yaw; + ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); + + if ( num_cinematics ) { + if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) { + PlayCinematic(); + } + RunPhysics(); + } else if ( !allowHiddenMovement && IsHidden() ) { + // hidden monsters + UpdateAIScript(); + } else { + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + switch( move.moveType ) { + case MOVETYPE_DEAD : + // dead monsters + UpdateAIScript(); + DeadMove(); + break; + + case MOVETYPE_FLY : + // flying monsters + UpdateEnemyPosition(); + UpdateAIScript(); + FlyMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_STATIC : + // static monsters + UpdateEnemyPosition(); + UpdateAIScript(); + StaticMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_ANIM : + // animation based movement + UpdateEnemyPosition(); + UpdateAIScript(); + AnimMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_SLIDE : + // velocity based movement + UpdateEnemyPosition(); + UpdateAIScript(); + SlideMove(); + PlayChatter(); + CheckBlink(); + break; + } + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + AI_PAIN = false; + AI_SPECIAL_DAMAGE = 0; + AI_PUSHED = false; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + if ( af_push_moveables ) { + PushWithAF(); + } + + if ( fl.hidden && allowHiddenMovement ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } +/* this still draws in retail builds.. not sure why.. don't care at this point. + if ( !aas && developer.GetBool() && !fl.hidden && !num_cinematics ) { + gameRenderWorld->DrawText( "No AAS", physicsObj.GetAbsBounds().GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 1 ); + } +*/ + + UpdateMuzzleFlash(); + UpdateAnimation(); + UpdateParticles(); + Present(); + UpdateDamageEffects(); + LinkCombat(); + + if(ai_showHealth.GetBool()) { + idVec3 aboveHead(0,0,20); + gameRenderWorld->DrawText( va( "%d", ( int )health), this->GetEyePosition()+aboveHead, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } +} + +/*********************************************************************** + + AI script state management + +***********************************************************************/ + +/* +===================== +idAI::LinkScriptVariables +===================== +*/ +void idAI::LinkScriptVariables() { + AI_TALK.LinkTo( scriptObject, "AI_TALK" ); + AI_DAMAGE.LinkTo( scriptObject, "AI_DAMAGE" ); + AI_PAIN.LinkTo( scriptObject, "AI_PAIN" ); + AI_SPECIAL_DAMAGE.LinkTo( scriptObject, "AI_SPECIAL_DAMAGE" ); + AI_DEAD.LinkTo( scriptObject, "AI_DEAD" ); + AI_ENEMY_VISIBLE.LinkTo( scriptObject, "AI_ENEMY_VISIBLE" ); + AI_ENEMY_IN_FOV.LinkTo( scriptObject, "AI_ENEMY_IN_FOV" ); + AI_ENEMY_DEAD.LinkTo( scriptObject, "AI_ENEMY_DEAD" ); + AI_MOVE_DONE.LinkTo( scriptObject, "AI_MOVE_DONE" ); + AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" ); + AI_ACTIVATED.LinkTo( scriptObject, "AI_ACTIVATED" ); + AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" ); + AI_JUMP.LinkTo( scriptObject, "AI_JUMP" ); + AI_BLOCKED.LinkTo( scriptObject, "AI_BLOCKED" ); + AI_DEST_UNREACHABLE.LinkTo( scriptObject, "AI_DEST_UNREACHABLE" ); + AI_HIT_ENEMY.LinkTo( scriptObject, "AI_HIT_ENEMY" ); + AI_OBSTACLE_IN_PATH.LinkTo( scriptObject, "AI_OBSTACLE_IN_PATH" ); + AI_PUSHED.LinkTo( scriptObject, "AI_PUSHED" ); +} + +/* +===================== +idAI::UpdateAIScript +===================== +*/ +void idAI::UpdateAIScript() { + UpdateScript(); + + // clear the hit enemy flag so we catch the next time we hit someone + AI_HIT_ENEMY = false; + + if ( allowHiddenMovement || !IsHidden() ) { + // update the animstate if we're not hidden + UpdateAnimState(); + } +} + +/*********************************************************************** + + navigation + +***********************************************************************/ + +/* +============ +idAI::KickObstacles +============ +*/ +void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) { + int i, numListedClipModels; + idBounds clipBounds; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + int clipmask; + idVec3 org; + idVec3 forceVec; + idVec3 delta; + idVec2 perpendicular; + + org = physicsObj.GetOrigin(); + + // find all possible obstacles + clipBounds = physicsObj.GetAbsBounds(); + clipBounds.TranslateSelf( dir * 32.0f ); + clipBounds.ExpandSelf( 8.0f ); + clipBounds.AddPoint( org ); + clipmask = physicsObj.GetClipMask(); + numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipmask, clipModelList, MAX_GENTITIES ); + for ( i = 0; i < numListedClipModels; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + if ( obEnt == alwaysKick ) { + // we'll kick this one outside the loop + continue; + } + + if ( !clipModel->IsTraceModel() ) { + continue; + } + + if ( obEnt->IsType( idMoveable::Type ) && obEnt->GetPhysics()->IsPushable() ) { + delta = obEnt->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * obEnt->GetPhysics()->GetMass(); + obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec ); + } + } + + if ( alwaysKick ) { + delta = alwaysKick->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * alwaysKick->GetPhysics()->GetMass(); + alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec ); + } +} + +/* +============ +ValidForBounds +============ +*/ +bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +===================== +idAI::SetAAS +===================== +*/ +void idAI::SetAAS() { + idStr use_aas; + + spawnArgs.GetString( "use_aas", NULL, use_aas ); + aas = gameLocal.GetAAS( use_aas ); + if ( aas ) { + const idAASSettings *settings = aas->GetSettings(); + if ( settings ) { + if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) { + gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() ); + } + float height = settings->maxStepHeight; + physicsObj.SetMaxStepHeight( height ); + return; + } else { + aas = NULL; + } + } + gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() ); +} + +/* +===================== +idAI::DrawRoute +===================== +*/ +void idAI::DrawRoute() const { + if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY && move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } else { + aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } + } +} + +/* +===================== +idAI::ReachedPos +===================== +*/ +bool idAI::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const { + if ( move.moveType == MOVETYPE_SLIDE ) { + idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } else { + if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) { + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + return true; + } + } else { + idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } + } + return false; +} + +/* +===================== +idAI::PointReachableAreaNum +===================== +*/ +int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const { + int areaNum; + idVec3 size; + idBounds bounds; + + if ( !aas ) { + return 0; + } + + size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + if ( move.moveType == MOVETYPE_FLY ) { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ); + } else { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + } + + return areaNum; +} + +/* +===================== +idAI::PathToGoal +===================== +*/ +bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + idVec3 org; + idVec3 goal; + + if ( !aas ) { + return false; + } + + org = origin; + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !areaNum ) { + return false; + } + + goal = goalOrigin; + aas->PushPointIntoAreaNum( goalAreaNum, goal ); + if ( !goalAreaNum ) { + return false; + } + + if ( move.moveType == MOVETYPE_FLY ) { + return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags ); + } else { + return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags ); + } +} + +/* +===================== +idAI::TravelDistance + +Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance. + +This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you +are from the goal, so try to break the goals up into shorter distances. +===================== +*/ +float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const { + int fromArea; + int toArea; + float dist; + idVec2 delta; + aasPath_t path; + + if ( !aas ) { + // no aas, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorBlue, start, end, 1, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + fromArea = PointReachableAreaNum( start ); + toArea = PointReachableAreaNum( end ); + + if ( !fromArea || !toArea ) { + // can't seem to get there + return -1; + } + + if ( fromArea == toArea ) { + // same area, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorBlue, start, end, 1, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + idReachability *reach; + int travelTime; + if ( !aas->RouteToGoalArea( fromArea, start, toArea, travelFlags, travelTime, &reach ) ) { + return -1; + } + + if ( ai_debugMove.GetBool() ) { + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( start, toArea, end ); + } else { + aas->ShowWalkPath( start, toArea, end ); + } + } + + return travelTime; +} + +/* +===================== +idAI::StopMove +===================== +*/ +void idAI::StopMove( moveStatus_t status ) { + AI_MOVE_DONE = true; + AI_FORWARD = false; + move.moveCommand = MOVE_NONE; + move.moveStatus = status; + move.toAreaNum = 0; + move.goalEntity = NULL; + move.moveDest = physicsObj.GetOrigin(); + AI_DEST_UNREACHABLE = false; + AI_OBSTACLE_IN_PATH = false; + AI_BLOCKED = false; + move.startTime = gameLocal.time; + move.duration = 0; + move.range = 0.0f; + move.speed = 0.0f; + move.anim = 0; + move.moveDir.Zero(); + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; +} + +/* +===================== +idAI::FaceEnemy + +Continually face the enemy's last known position. MoveDone is always true in this case. +===================== +*/ +bool idAI::FaceEnemy() { + idActor *enemyEnt = enemy.GetEntity(); + if ( !enemyEnt ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + TurnToward( lastVisibleEnemyPos ); + move.goalEntity = enemyEnt; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENEMY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + AI_MOVE_DONE = true; + AI_FORWARD = false; + AI_DEST_UNREACHABLE = false; + + return true; +} + +/* +===================== +idAI::FaceEntity + +Continually face the entity position. MoveDone is always true in this case. +===================== +*/ +bool idAI::FaceEntity( idEntity *ent ) { + if ( !ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + idVec3 entityOrg = ent->GetPhysics()->GetOrigin(); + TurnToward( entityOrg ); + move.goalEntity = ent; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENTITY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + AI_MOVE_DONE = true; + AI_FORWARD = false; + AI_DEST_UNREACHABLE = false; + + return true; +} + +/* +===================== +idAI::DirectMoveToPosition +===================== +*/ +bool idAI::DirectMoveToPosition( const idVec3 &pos ) { + if ( ReachedPos( pos, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.moveDest = pos; + move.goalEntity = NULL; + move.moveCommand = MOVE_TO_POSITION_DIRECT; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.speed = fly_speed; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + if ( move.moveType == MOVETYPE_FLY ) { + idVec3 dir = pos - physicsObj.GetOrigin(); + dir.Normalize(); + dir *= fly_speed; + physicsObj.SetLinearVelocity( dir ); + } + + return true; +} + +/* +===================== +idAI::MoveToEnemyHeight +===================== +*/ +bool idAI::MoveToEnemyHeight() { + idActor *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt || ( move.moveType != MOVETYPE_FLY ) ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + move.moveDest.z = lastVisibleEnemyPos.z + enemyEnt->EyeOffset().z + fly_offset; + move.goalEntity = enemyEnt; + move.moveCommand = MOVE_TO_ENEMYHEIGHT; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = false; + + return true; +} + +/* +===================== +idAI::MoveToEnemy +===================== +*/ +bool idAI::MoveToEnemy() { + int areaNum; + aasPath_t path; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + if ( ReachedPos( lastVisibleReachableEnemyPos, MOVE_TO_ENEMY ) ) { + if ( !ReachedPos( lastVisibleEnemyPos, MOVE_TO_ENEMY ) || !AI_ENEMY_VISIBLE ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + StopMove( MOVE_STATUS_DONE ); + return true; + } + + idVec3 pos = lastVisibleReachableEnemyPos; + + move.toAreaNum = 0; + if ( aas ) { + move.toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( move.toAreaNum, pos ); + + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) { + AI_DEST_UNREACHABLE = true; + return false; + } + } + + if ( !move.toAreaNum ) { + // if only trying to update the enemy position + if ( move.moveCommand == MOVE_TO_ENEMY ) { + if ( !aas ) { + // keep the move destination up to date for wandering + move.moveDest = pos; + } + return false; + } + + if ( !NewWanderDir( pos ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + } + + if ( move.moveCommand != MOVE_TO_ENEMY ) { + move.moveCommand = MOVE_TO_ENEMY; + move.startTime = gameLocal.time; + } + + move.moveDest = pos; + move.goalEntity = enemyEnt; + move.speed = fly_speed; + move.moveStatus = MOVE_STATUS_MOVING; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::MoveToEntity +===================== +*/ +bool idAI::MoveToEntity( idEntity *ent ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + pos = ent->GetPhysics()->GetOrigin(); + if ( ( move.moveType != MOVETYPE_FLY ) && ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntityOrigin != pos ) ) ) { + ent->GetFloorPos( 64.0f, pos ); + } + + if ( ReachedPos( pos, MOVE_TO_ENTITY ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.toAreaNum = 0; + if ( aas ) { + move.toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( move.toAreaNum, pos ); + + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) { + AI_DEST_UNREACHABLE = true; + return false; + } + } + + if ( !move.toAreaNum ) { + // if only trying to update the entity position + if ( move.moveCommand == MOVE_TO_ENTITY ) { + if ( !aas ) { + // keep the move destination up to date for wandering + move.moveDest = pos; + } + return false; + } + + if ( !NewWanderDir( pos ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + } + + if ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntity.GetEntity() != ent ) ) { + move.startTime = gameLocal.time; + move.goalEntity = ent; + move.moveCommand = MOVE_TO_ENTITY; + } + + move.moveDest = pos; + move.goalEntityOrigin = ent->GetPhysics()->GetOrigin(); + move.moveStatus = MOVE_STATUS_MOVING; + move.speed = fly_speed; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::MoveOutOfRange +===================== +*/ +bool idAI::MoveOutOfRange( idEntity *ent, float range ) { + int areaNum; + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + if ( !aas || !ent ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + + // consider the entity the monster is getting close to as an obstacle + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + + if ( ent == enemy.GetEntity() ) { + pos = lastVisibleEnemyPos; + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + idAASFindAreaOutOfRange findGoal( pos, range ); + if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + if ( ReachedPos( goal.origin, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.moveDest = goal.origin; + move.toAreaNum = goal.areaNum; + move.goalEntity = ent; + move.moveCommand = MOVE_OUT_OF_RANGE; + move.moveStatus = MOVE_STATUS_MOVING; + move.range = range; + move.speed = fly_speed; + move.startTime = gameLocal.time; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::MoveToAttackPosition +===================== +*/ +bool idAI::MoveToAttackPosition( idEntity *ent, int attack_anim ) { + int areaNum; + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + if ( !aas || !ent ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + + // consider the entity the monster is getting close to as an obstacle + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + + if ( ent == enemy.GetEntity() ) { + pos = lastVisibleEnemyPos; + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + idAASFindAttackPosition findGoal( this, physicsObj.GetGravityAxis(), ent, pos, missileLaunchOffset[ attack_anim ] ); + if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + move.moveDest = goal.origin; + move.toAreaNum = goal.areaNum; + move.goalEntity = ent; + move.moveCommand = MOVE_TO_ATTACK_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.speed = fly_speed; + move.startTime = gameLocal.time; + move.anim = attack_anim; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::MoveToPosition +===================== +*/ +bool idAI::MoveToPosition( const idVec3 &pos ) { + idVec3 org; + int areaNum; + aasPath_t path; + + if ( ReachedPos( pos, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + org = pos; + move.toAreaNum = 0; + if ( aas ) { + move.toAreaNum = PointReachableAreaNum( org ); + aas->PushPointIntoAreaNum( move.toAreaNum, org ); + + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, org ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + } + + if ( !move.toAreaNum && !NewWanderDir( org ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + move.moveDest = org; + move.goalEntity = NULL; + move.moveCommand = MOVE_TO_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.speed = fly_speed; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::MoveToCover +===================== +*/ +bool idAI::MoveToCover( idEntity *entity, const idVec3 &hideFromPos ) { + int areaNum; + aasObstacle_t obstacle; + aasGoal_t hideGoal; + idBounds bounds; + + if ( !aas || !entity ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + + // consider the entity the monster tries to hide from as an obstacle + obstacle.absBounds = entity->GetPhysics()->GetAbsBounds(); + + idAASFindCover findCover( hideFromPos ); + if ( !aas->FindNearestGoal( hideGoal, areaNum, org, hideFromPos, travelFlags, &obstacle, 1, findCover ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + if ( ReachedPos( hideGoal.origin, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.moveDest = hideGoal.origin; + move.toAreaNum = hideGoal.areaNum; + move.goalEntity = entity; + move.moveCommand = MOVE_TO_COVER; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.speed = fly_speed; + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::SlideToPosition +===================== +*/ +bool idAI::SlideToPosition( const idVec3 &pos, float time ) { + StopMove( MOVE_STATUS_DONE ); + + move.moveDest = pos; + move.goalEntity = NULL; + move.moveCommand = MOVE_SLIDE_TO_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + AI_MOVE_DONE = false; + AI_DEST_UNREACHABLE = false; + AI_FORWARD = false; + + if ( move.duration > 0 ) { + move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration ); + if ( move.moveType != MOVETYPE_FLY ) { + move.moveDir.z = 0.0f; + } + move.speed = move.moveDir.LengthFast(); + } + + return true; +} + +/* +===================== +idAI::WanderAround +===================== +*/ +bool idAI::WanderAround() { + StopMove( MOVE_STATUS_DONE ); + + move.moveDest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + if ( !NewWanderDir( move.moveDest ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + return false; + } + + move.moveCommand = MOVE_WANDER; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.speed = fly_speed; + AI_MOVE_DONE = false; + AI_FORWARD = true; + + return true; +} + +/* +===================== +idAI::MoveDone +===================== +*/ +bool idAI::MoveDone() const { + return ( move.moveCommand == MOVE_NONE ); +} + +/* +================ +idAI::StepDirection +================ +*/ +bool idAI::StepDirection( float dir ) { + predictedPath_t path; + idVec3 org; + + move.wanderYaw = dir; + move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward(); + + org = physicsObj.GetOrigin(); + + idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) { + // don't report being blocked if we ran into our goal entity + return true; + } + + if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) { + float z; + + move.moveDir = path.endVelocity * 1.0f / 48.0f; + + // trace down to the floor and see if we can go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 floorPos = path.endPos; + idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = -1.0f; + return true; + } + + // trace up to see if we can go over something and go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 ceilingPos = path.endPos; + + for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) { + idVec3 start; + if ( z <= ceilingPos.z ) { + start.x = org.x; + start.y = org.y; + start.z = z; + } else { + start = ceilingPos; + } + idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = 1.0f; + return true; + } + } + return false; + } + + return ( path.endEvent == 0 ); +} + +/* +================ +idAI::NewWanderDir +================ +*/ +bool idAI::NewWanderDir( const idVec3 &dest ) { + float deltax, deltay; + float d[ 3 ]; + float tdir, olddir, turnaround; + + move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 ); + + olddir = idMath::AngleNormalize360( ( int )( current_yaw / 45 ) * 45 ); + turnaround = idMath::AngleNormalize360( olddir - 180 ); + + idVec3 org = physicsObj.GetOrigin(); + deltax = dest.x - org.x; + deltay = dest.y - org.y; + if ( deltax > 10 ) { + d[ 1 ]= 0; + } else if ( deltax < -10 ) { + d[ 1 ] = 180; + } else { + d[ 1 ] = DI_NODIR; + } + + if ( deltay < -10 ) { + d[ 2 ] = 270; + } else if ( deltay > 10 ) { + d[ 2 ] = 90; + } else { + d[ 2 ] = DI_NODIR; + } + + // try direct route + if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) { + if ( d[ 1 ] == 0 ) { + tdir = d[ 2 ] == 90 ? 45 : 315; + } else { + tdir = d[ 2 ] == 90 ? 135 : 215; + } + + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + + // try other directions + if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) { + tdir = d[ 1 ]; + d[ 1 ] = d[ 2 ]; + d[ 2 ] = tdir; + } + + if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) { + return true; + } + + if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) { + return true; + } + + // there is no direct path to the player, so pick another direction + if ( olddir != DI_NODIR && StepDirection( olddir ) ) { + return true; + } + + // randomly determine direction of search + if ( gameLocal.random.RandomInt() & 1 ) { + for( tdir = 0; tdir <= 315; tdir += 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } else { + for ( tdir = 315; tdir >= 0; tdir -= 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } + + if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) { + return true; + } + + // can't move + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; +} + +/* +===================== +idAI::GetMovePos +===================== +*/ +bool idAI::GetMovePos( idVec3 &seekPos ) { + int areaNum; + aasPath_t path; + bool result; + idVec3 org; + + org = physicsObj.GetOrigin(); + seekPos = org; + + switch( move.moveCommand ) { + case MOVE_NONE : + seekPos = move.moveDest; + return false; + break; + + case MOVE_FACE_ENEMY : + case MOVE_FACE_ENTITY : + seekPos = move.moveDest; + return false; + break; + + case MOVE_TO_POSITION_DIRECT : + seekPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + return false; + break; + + case MOVE_SLIDE_TO_POSITION : + seekPos = org; + return false; + break; + } + + if ( move.moveCommand == MOVE_TO_ENTITY ) { + MoveToEntity( move.goalEntity.GetEntity() ); + } + + move.moveStatus = MOVE_STATUS_MOVING; + result = false; + if ( gameLocal.time > move.blockTime ) { + if ( move.moveCommand == MOVE_WANDER ) { + move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + } else { + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + seekPos = org; + return false; + } + } + + if ( aas && move.toAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) { + seekPos = path.moveGoal; + result = true; + move.nextWanderTime = 0; + } else { + AI_DEST_UNREACHABLE = true; + } + } + } + + if ( !result ) { + // wander around + if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) { + result = NewWanderDir( move.moveDest ); + if ( !result ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + AI_DEST_UNREACHABLE = true; + seekPos = org; + return false; + } + } else { + result = true; + } + + seekPos = org + move.moveDir * 2048.0f; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, org, seekPos, 1, true ); + } + } else { + AI_DEST_UNREACHABLE = false; + } + + if ( result && ( ai_debugMove.GetBool() ) ) { + gameRenderWorld->DebugLine( colorCyan, physicsObj.GetOrigin(), seekPos ); + } + + return result; +} + +/* +===================== +idAI::EntityCanSeePos +===================== +*/ +bool idAI::EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ) { + idVec3 eye, point; + trace_t results; + pvsHandle_t handle; + + handle = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() ); + + if ( !gameLocal.pvs.InCurrentPVS( handle, GetPVSAreas(), GetNumPVSAreas() ) ) { + gameLocal.pvs.FreeCurrentPVS( handle ); + return false; + } + + gameLocal.pvs.FreeCurrentPVS( handle ); + + eye = actorOrigin + actor->EyeOffset(); + + point = pos; + point[2] += 1.0f; + + physicsObj.DisableClip(); + + gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor ); + if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) { + physicsObj.EnableClip(); + return true; + } + + const idBounds &bounds = physicsObj.GetBounds(); + point[2] += bounds[1][2] - bounds[0][2]; + + gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor ); + physicsObj.EnableClip(); + if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) { + return true; + } + return false; +} + +/* +===================== +idAI::BlockedFailSafe +===================== +*/ +void idAI::BlockedFailSafe() { + if ( !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f ) { + return; + } + if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL || + ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( blockedRadius ) ) { + move.lastMoveOrigin = physicsObj.GetOrigin(); + move.lastMoveTime = gameLocal.time; + } + if ( move.lastMoveTime < gameLocal.time - blockedMoveTime ) { + if ( lastAttackTime < gameLocal.time - blockedAttackTime ) { + AI_BLOCKED = true; + move.lastMoveTime = gameLocal.time; + } + } +} + +/*********************************************************************** + + turning + +***********************************************************************/ + +/* +===================== +idAI::Turn +===================== +*/ +void idAI::Turn() { + float diff; + float diff2; + float turnAmount; + animFlags_t animflags; + + if ( !turnRate ) { + return; + } + + // check if the animator has marker this anim as non-turning + if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) { + animflags = legsAnim.GetAnimFlags(); + } else { + animflags = torsoAnim.GetAnimFlags(); + } + if ( animflags.ai_no_turn ) { + return; + } + + if ( anim_turn_angles && animflags.anim_turn ) { + idMat3 rotateAxis; + + // set the blend between no turn and full turn + float frac = anim_turn_amount / anim_turn_angles; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac ); + + // get the total rotation from the start of the anim + animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis ); + current_yaw = idMath::AngleNormalize180( anim_turn_yaw + rotateAxis[ 0 ].ToYaw() ); + } else { + diff = idMath::AngleNormalize180( ideal_yaw - current_yaw ); + turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.time - gameLocal.previousTime ); + if ( turnVel > turnRate ) { + turnVel = turnRate; + } else if ( turnVel < -turnRate ) { + turnVel = -turnRate; + } + turnAmount = turnVel * MS2SEC( gameLocal.time - gameLocal.previousTime ); + if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) { + turnVel = diff / MS2SEC( gameLocal.time - gameLocal.previousTime ); + turnAmount = diff; + } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) { + turnVel = diff / MS2SEC( gameLocal.time - gameLocal.previousTime ); + turnAmount = diff; + } + current_yaw += turnAmount; + current_yaw = idMath::AngleNormalize180( current_yaw ); + diff2 = idMath::AngleNormalize180( ideal_yaw - current_yaw ); + if ( idMath::Fabs( diff2 ) < 0.1f ) { + current_yaw = ideal_yaw; + } + } + + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); + + if ( ai_debugMove.GetBool() ) { + const idVec3 &org = physicsObj.GetOrigin(); + gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, ideal_yaw, 0 ).ToForward() * 64, 1 ); + gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, current_yaw, 0 ).ToForward() * 48, 1 ); + gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, current_yaw + turnVel, 0 ).ToForward() * 32, 1 ); + } +} + +/* +===================== +idAI::FacingIdeal +===================== +*/ +bool idAI::FacingIdeal() { + float diff; + + if ( !turnRate ) { + return true; + } + + diff = idMath::AngleNormalize180( current_yaw - ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + current_yaw = ideal_yaw; + return true; + } + + return false; +} + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( float yaw ) { + ideal_yaw = idMath::AngleNormalize180( yaw ); + bool result = FacingIdeal(); + return result; +} + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + dir = pos - physicsObj.GetOrigin(); + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) { + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + + bool result = FacingIdeal(); + return result; +} + +/*********************************************************************** + + Movement + +***********************************************************************/ + +/* +================ +idAI::ApplyImpulse +================ +*/ +void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + // FIXME: Jim take a look at this and see if this is a reasonable thing to do + // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 ) + // and we don't want him taking physics impulses as it can knock him off the path + if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) { + idActor::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +===================== +idAI::GetMoveDelta +===================== +*/ +void idAI::GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) { + idVec3 oldModelOrigin; + idVec3 modelOrigin; + + animator.GetDelta( gameLocal.previousTime, gameLocal.time, delta ); + delta = axis * delta; + + if ( modelOffset != vec3_zero ) { + // the pivot of the monster's model is around its origin, and not around the bounding + // box's origin, so we have to compensate for this when the model is offset so that + // the monster still appears to rotate around it's origin. + oldModelOrigin = modelOffset * oldaxis; + modelOrigin = modelOffset * axis; + delta += oldModelOrigin - modelOrigin; + } + + delta *= physicsObj.GetGravityAxis(); +} + +/* +===================== +idAI::CheckObstacleAvoidance +===================== +*/ +void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ) { + idEntity *obstacle; + obstaclePath_t path; + idVec3 dir; + float dist; + bool foundPath; + + if ( ignore_obstacles ) { + newPos = goalPos; + move.obstacle = NULL; + return; + } + + const idVec3 &origin = physicsObj.GetOrigin(); + + obstacle = NULL; + AI_OBSTACLE_IN_PATH = false; + foundPath = FindPathAroundObstacles( &physicsObj, aas, enemy.GetEntity(), origin, goalPos, path ); + if ( ai_showObstacleAvoidance.GetBool() ) { + gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), 1 ); + gameRenderWorld->DebugLine( foundPath ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), 1 ); + } + + if ( !foundPath ) { + // couldn't get around obstacles + if ( path.firstObstacle ) { + AI_OBSTACLE_IN_PATH = true; + if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.firstObstacle->GetPhysics()->GetAbsBounds() ) ) { + obstacle = path.firstObstacle; + } + } else if ( path.startPosObstacle ) { + AI_OBSTACLE_IN_PATH = true; + if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.startPosObstacle->GetPhysics()->GetAbsBounds() ) ) { + obstacle = path.startPosObstacle; + } + } else { + // Blocked by wall + move.moveStatus = MOVE_STATUS_BLOCKED_BY_WALL; + } +#if 0 + } else if ( path.startPosObstacle ) { + // check if we're past where the our origin was pushed out of the obstacle + dir = goalPos - origin; + dir.Normalize(); + dist = ( path.seekPos - origin ) * dir; + if ( dist < 1.0f ) { + AI_OBSTACLE_IN_PATH = true; + obstacle = path.startPosObstacle; + } +#endif + } else if ( path.seekPosObstacle ) { + // if the AI is very close to the path.seekPos already and path.seekPosObstacle != NULL + // then we want to push the path.seekPosObstacle entity out of the way + AI_OBSTACLE_IN_PATH = true; + + // check if we're past where the goalPos was pushed out of the obstacle + dir = goalPos - origin; + dir.Normalize(); + dist = ( path.seekPos - origin ) * dir; + if ( dist < 1.0f ) { + obstacle = path.seekPosObstacle; + } + } + + // if we had an obstacle, set our move status based on the type, and kick it out of the way if it's a moveable + if ( obstacle ) { + if ( obstacle->IsType( idActor::Type ) ) { + // monsters aren't kickable + if ( obstacle == enemy.GetEntity() ) { + move.moveStatus = MOVE_STATUS_BLOCKED_BY_ENEMY; + } else { + move.moveStatus = MOVE_STATUS_BLOCKED_BY_MONSTER; + } + } else { + // try kicking the object out of the way + move.moveStatus = MOVE_STATUS_BLOCKED_BY_OBJECT; + } + newPos = obstacle->GetPhysics()->GetOrigin(); + //newPos = path.seekPos; + move.obstacle = obstacle; + } else { + newPos = path.seekPos; + move.obstacle = NULL; + } +} + +/* +===================== +idAI::DeadMove +===================== +*/ +void idAI::DeadMove() { + idVec3 delta; + monsterMoveResult_t moveResult; + + idVec3 org = physicsObj.GetOrigin(); + + GetMoveDelta( viewAxis, viewAxis, delta ); + physicsObj.SetDelta( delta ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + AI_ONGROUND = physicsObj.OnGround(); +} + +/* +===================== +idAI::AnimMove +===================== +*/ +void idAI::AnimMove() { + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { + GetMoveDelta( oldaxis, viewAxis, delta ); + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.UseFlyMove( false ); + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt != NULL && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, 1 ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, 1 ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, 1, true ); + DrawRoute(); + } +} + +/* +===================== +Seek +===================== +*/ +idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) { + idVec3 predictedPos; + idVec3 goalDelta; + idVec3 seekVel; + + // predict our position + predictedPos = org + vel * prediction; + goalDelta = goal - predictedPos; + seekVel = goalDelta * MS2SEC( gameLocal.time - gameLocal.previousTime ); + + return seekVel; +} + +/* +===================== +idAI::SlideMove +===================== +*/ +void idAI::SlideMove() { + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = move.moveDest; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = move.moveDest; + } else if ( GetMovePos( goalPos ) ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + goalPos = newDest; + } + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + } else { + goalPos = move.moveDest; + StopMove( MOVE_STATUS_DONE ); + } + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + float z = vel.z; + idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION; + + // seek the goal position + goalDelta = goalPos - predictedPos; + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.time - gameLocal.previousTime ); + vel += goalDelta * MS2SEC( gameLocal.time - gameLocal.previousTime ); + + // cap our speed + vel = vel.Truncate( fly_speed ); + vel.z = z; + physicsObj.SetLinearVelocity( vel ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); + + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.moveCommand != MOVE_NONE ) { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt != NULL && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, 1 ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, 1 ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, 1, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::AdjustFlyingAngles +===================== +*/ +void idAI::AdjustFlyingAngles() { + idVec3 vel; + float speed; + float roll; + float pitch; + + vel = physicsObj.GetLinearVelocity(); + + speed = vel.Length(); + if ( speed < 5.0f ) { + roll = 0.0f; + pitch = 0.0f; + } else { + roll = vel * viewAxis[ 1 ] * -fly_roll_scale / fly_speed; + if ( roll > fly_roll_max ) { + roll = fly_roll_max; + } else if ( roll < -fly_roll_max ) { + roll = -fly_roll_max; + } + + pitch = vel * viewAxis[ 2 ] * -fly_pitch_scale / fly_speed; + if ( pitch > fly_pitch_max ) { + pitch = fly_pitch_max; + } else if ( pitch < -fly_pitch_max ) { + pitch = -fly_pitch_max; + } + } + + fly_roll = fly_roll * 0.95f + roll * 0.05f; + fly_pitch = fly_pitch * 0.95f + pitch * 0.05f; + + if ( flyTiltJoint != INVALID_JOINT ) { + animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() ); + } else { + viewAxis = idAngles( fly_pitch, current_yaw, fly_roll ).ToMat3(); + } +} + +/* +===================== +idAI::AddFlyBob +===================== +*/ +void idAI::AddFlyBob( idVec3 &vel ) { + idVec3 fly_bob_add; + float t; + + if ( fly_bob_strength ) { + t = MS2SEC( gameLocal.time + entityNumber * 497 ); + fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * fly_bob_vert ) ) * fly_bob_strength; + vel += fly_bob_add * MS2SEC( gameLocal.time - gameLocal.previousTime ); + if ( ai_debugMove.GetBool() ) { + const idVec3 &origin = physicsObj.GetOrigin(); + gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 ); + } + } +} + +/* +===================== +idAI::AdjustFlyHeight +===================== +*/ +void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) { + const idVec3 &origin = physicsObj.GetOrigin(); + predictedPath_t path; + idVec3 end; + idVec3 dest; + trace_t trace; + idActor *enemyEnt; + bool goLower; + + // make sure we're not flying too high to get through doors + goLower = false; + if ( origin.z > goalPos.z ) { + dest = goalPos; + dest.z = origin.z + 128.0f; + idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path ); + if ( path.endPos.z < origin.z ) { + idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION ); + vel.z += addVel.z; + goLower = true; + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, 1 ); + } + } + + if ( !goLower ) { + // make sure we don't fly too low + end = origin; + + enemyEnt = enemy.GetEntity(); + if ( enemyEnt ) { + end.z = lastVisibleEnemyPos.z + lastVisibleEnemyEyeOffset.z + fly_offset; + } else { + // just use the default eye height for the player + end.z = goalPos.z + DEFAULT_FLY_OFFSET + fly_offset; + } + + gameLocal.clip.Translation( trace, origin, end, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this ); + vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION ); + } +} + +/* +===================== +idAI::FlySeekGoal +===================== +*/ +void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) { + idVec3 seekVel; + + // seek the goal position + seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION ); + seekVel *= fly_seek_scale; + vel += seekVel; +} + +/* +===================== +idAI::AdjustFlySpeed +===================== +*/ +void idAI::AdjustFlySpeed( idVec3 &vel ) { + float speed; + + // apply dampening + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.time - gameLocal.previousTime ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( move.speed - speed ) * MS2SEC( gameLocal.time - gameLocal.previousTime ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if ( move.speed && ( speed > move.speed ) ) { + speed = move.speed; + } + + vel *= speed; +} + +/* +===================== +idAI::FlyTurn +===================== +*/ +void idAI::FlyTurn() { + if ( move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); +} + +/* +===================== +idAI::FlyMove +===================== +*/ +void idAI::FlyMove() { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( ai_debugMove.GetBool() ) { + gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), moveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, fly_speed ); + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( goalPos ) ) { + CheckObstacleAvoidance( goalPos, newDest ); + goalPos = newDest; + } + + if ( move.speed ) { + FlySeekGoal( vel, goalPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, goalPos ); + } + + AdjustFlySpeed( vel ); + + physicsObj.SetLinearVelocity( vel ); + } + + // turn + FlyTurn(); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt != NULL && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 ); + gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, 1 ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, 1 ); + gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), 1, true ); + gameRenderWorld->DebugLine( colorBlue, org, goalPos, 1, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, 1, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::StaticMove +===================== +*/ +void idAI::StaticMove() { + idActor *enemyEnt = enemy.GetEntity(); + + if ( AI_DEAD ) { + return; + } + + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemyEnt ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.moveCommand != MOVE_NONE ) { + TurnToward( move.moveDest ); + } + Turn(); + + physicsObj.ForceDeltaMove( true ); // disable gravity + RunPhysics(); + + AI_ONGROUND = false; + + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemyEnt ); + } + + if ( ai_debugMove.GetBool() ) { + const idVec3 &org = physicsObj.GetOrigin(); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, 1 ); + gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, 1, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, 1, true ); + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +===================== +idAI::ReactionTo +===================== +*/ +int idAI::ReactionTo( const idEntity *ent ) { + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::Type ) ) { + return ATTACK_IGNORE; + } + + const idActor *actor = static_cast( ent ); + if ( actor->IsType( idPlayer::Type ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + // actors on different teams will always fight each other + if ( actor->team != team ) { + if ( actor->fl.notarget ) { + // don't attack on sight when attacker is notargeted + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } + + // monsters will fight when attacked by lower ranked monsters. rank 0 never fights back. + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + + +/* +===================== +idAI::Pain +===================== +*/ +bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idActor *actor; + + AI_PAIN = idActor::Pain( inflictor, attacker, damage, dir, location ); + AI_DAMAGE = true; + + // force a blink + blink_time = 0; + + // ignore damage from self + if ( attacker != this ) { + if ( inflictor ) { + AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" ); + } else { + AI_SPECIAL_DAMAGE = 0; + } + + if ( enemy.GetEntity() != attacker && attacker->IsType( idActor::Type ) ) { + actor = ( idActor * )attacker; + if ( ReactionTo( actor ) & ATTACK_ON_DAMAGE ) { + gameLocal.AlertAI( actor ); + SetEnemy( actor ); + } + } + } + + return ( AI_PAIN != 0 ); +} + + +/* +===================== +idAI::SpawnParticles +===================== +*/ +void idAI::SpawnParticles( const char *keyName ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( keyName, NULL ); + while ( kv ) { + particleEmitter_t pe; + + idStr particleName = kv->GetValue(); + + if ( particleName.Length() ) { + + idStr jointName = kv->GetValue(); + int dash = jointName.Find('-'); + if ( dash > 0 ) { + particleName = particleName.Left( dash ); + jointName = jointName.Right( jointName.Length() - dash - 1 ); + } + + SpawnParticlesOnJoint( pe, particleName, jointName ); + particles.Append( pe ); + } + + kv = spawnArgs.MatchPrefix( keyName, kv ); + } +} + +/* +===================== +idAI::SpawnParticlesOnJoint +===================== +*/ +const idDeclParticle *idAI::SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ) { + idVec3 origin; + idMat3 axis; + + if ( *particleName == '\0' ) { + memset( &pe, 0, sizeof( pe ) ); + return pe.particle; + } + + pe.joint = animator.GetJointHandle( jointName ); + if ( pe.joint == INVALID_JOINT ) { + gameLocal.Warning( "Unknown particleJoint '%s' on '%s'", jointName, name.c_str() ); + pe.time = 0; + pe.particle = NULL; + } else { + animator.GetJointTransform( pe.joint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + + BecomeActive( TH_UPDATEPARTICLES ); + if ( !gameLocal.time ) { + // particles with time of 0 don't show, so set the time differently on the first frame + pe.time = 1; + } else { + pe.time = gameLocal.time; + } + pe.particle = static_cast( declManager->FindType( DECL_PARTICLE, particleName ) ); + gameLocal.smokeParticles->EmitSmoke( pe.particle, pe.time, gameLocal.random.CRandomFloat(), origin, axis, timeGroup /*_D3XP*/ ); + } + + return pe.particle; +} + +/* +===================== +idAI::Killed +===================== +*/ +void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idAngles ang; + const char *modelDeath; + + // Guardian died? grats, you get an achievement + if ( idStr::Icmp( name, "guardian_spawn" ) == 0 ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL && player->GetExpansionType() == GAME_BASE ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DEFEAT_GUARDIAN_BOSS ); + } + } + + // make sure the monster is activated + EndAttack(); + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( inflictor ) { + AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" ); + } else { + AI_SPECIAL_DAMAGE = 0; + } + + if ( AI_DEAD ) { + AI_PAIN = true; + AI_DAMAGE = true; + return; + } + + // stop all voice sounds + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + + disableGravity = false; + move.moveType = MOVETYPE_DEAD; + af_push_moveables = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::Type ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( attacker ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + ClearEnemy(); + AI_DEAD = true; + + // make monster nonsolid + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + + Unbind(); + + if ( StartRagdoll() ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) { + // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + SetModel( modelDeath ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + // No grabbing if "model_death" + noGrab = true; + } + + restartParticles = false; + + state = GetScriptFunction( "state_Killed" ); + SetState( state ); + SetWaitState( "" ); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while( kv ) { + idDict args; + + args.Set( "classname", kv->GetValue() ); + args.Set( "origin", physicsObj.GetOrigin().ToString() ); + gameLocal.SpawnEntityDef( args ); + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } + + if ( ( attacker && attacker->IsType( idPlayer::Type ) ) && ( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ) ) { + static_cast< idPlayer* >( attacker )->AddAIKill(); + } + + if(spawnArgs.GetBool("harvest_on_death")) { + const idDict *harvestDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_harvest_type"), false ); + if ( harvestDef ) { + idEntity *temp; + gameLocal.SpawnEntityDef( *harvestDef, &temp, false ); + harvestEnt = static_cast(temp); + + } + + if(harvestEnt.GetEntity()) { + //Let the harvest entity set itself up + harvestEnt.GetEntity()->Init(this); + harvestEnt.GetEntity()->BecomeActive( TH_THINK ); + } + } +} + +/*********************************************************************** + + Targeting/Combat + +***********************************************************************/ + +/* +===================== +idAI::PlayCinematic +===================== +*/ +void idAI::PlayCinematic() { + const char *animname; + + if ( current_cinematic >= num_cinematics ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + if ( !spawnArgs.GetBool( "cinematic_no_hide" ) ) { + Hide(); + } + current_cinematic = 0; + ActivateTargets( gameLocal.GetLocalPlayer() ); + fl.neverDormant = false; + return; + } + + Show(); + current_cinematic++; + + allowJointMod = false; + allowEyeFocus = false; + + spawnArgs.GetString( va( "anim%d", current_cinematic ), NULL, &animname ); + if ( !animname ) { + gameLocal.Warning( "missing 'anim%d' key on %s", current_cinematic, name.c_str() ); + return; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start '%s'\n", gameLocal.framenum, GetName(), animname ); + } + + headAnim.animBlendFrames = 0; + headAnim.lastAnimBlendFrames = 0; + headAnim.BecomeIdle(); + + legsAnim.animBlendFrames = 0; + legsAnim.lastAnimBlendFrames = 0; + legsAnim.BecomeIdle(); + + torsoAnim.animBlendFrames = 0; + torsoAnim.lastAnimBlendFrames = 0; + ProcessEvent( &AI_PlayAnim, ANIMCHANNEL_TORSO, animname ); + + // make sure our model gets updated + animator.ForceUpdate(); + + // update the anim bounds + UpdateAnimation(); + UpdateVisuals(); + Present(); + + if ( head.GetEntity() ) { + // since the body anim was updated, we need to run physics to update the position of the head + RunPhysics(); + + // make sure our model gets updated + head.GetEntity()->GetAnimator()->ForceUpdate(); + + // update the anim bounds + head.GetEntity()->UpdateAnimation(); + head.GetEntity()->UpdateVisuals(); + head.GetEntity()->Present(); + } + + fl.neverDormant = true; +} + +/* +===================== +idAI::Activate + +Notifies the script that a monster has been activated by a trigger or flashlight +===================== +*/ +void idAI::Activate( idEntity *activator ) { + idPlayer *player; + + if ( AI_DEAD ) { + // ignore it when they're dead + return; + } + + // make sure he's not dormant + dormantStart = 0; + + if ( num_cinematics ) { + PlayCinematic(); + } else { + AI_ACTIVATED = true; + if ( !activator || !activator->IsType( idPlayer::Type ) ) { + player = gameLocal.GetLocalPlayer(); + } else { + player = static_cast( activator ); + } + + if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) { + SetEnemy( player ); + } + + // update the script in cinematics so that entities don't start anims or show themselves a frame late. + if ( cinematic ) { + UpdateAIScript(); + + // make sure our model gets updated + animator.ForceUpdate(); + + // update the anim bounds + UpdateAnimation(); + UpdateVisuals(); + Present(); + + if ( head.GetEntity() ) { + // since the body anim was updated, we need to run physics to update the position of the head + RunPhysics(); + + // make sure our model gets updated + head.GetEntity()->GetAnimator()->ForceUpdate(); + + // update the anim bounds + head.GetEntity()->UpdateAnimation(); + head.GetEntity()->UpdateVisuals(); + head.GetEntity()->Present(); + } + } + } +} + +/* +===================== +idAI::EnemyDead +===================== +*/ +void idAI::EnemyDead() { + ClearEnemy(); + AI_ENEMY_DEAD = true; +} + +/* +===================== +idAI::TalkTo +===================== +*/ +void idAI::TalkTo( idActor *actor ) { + if ( talk_state != TALK_OK ) { + return; + } + + // Wake up monsters that are pretending to be NPC's + if ( team == 1 && actor && actor->team != team ) { + ProcessEvent( &EV_Activate, actor ); + } + + talkTarget = actor; + if ( actor ) { + AI_TALK = true; + } else { + AI_TALK = false; + } +} + +/* +===================== +idAI::GetEnemy +===================== +*/ +idActor *idAI::GetEnemy() const { + return enemy.GetEntity(); +} + +/* +===================== +idAI::GetTalkState +===================== +*/ +talkState_t idAI::GetTalkState() const { + if ( ( talk_state != TALK_NEVER ) && AI_DEAD ) { + return TALK_DEAD; + } + if ( IsHidden() ) { + return TALK_NEVER; + } + return talk_state; +} + +/* +===================== +idAI::TouchedByFlashlight +===================== +*/ +void idAI::TouchedByFlashlight( idActor *flashlight_owner ) { + if ( wakeOnFlashlight ) { + Activate( flashlight_owner ); + } +} + +/* +===================== +idAI::ClearEnemy +===================== +*/ +void idAI::ClearEnemy() { + if ( move.moveCommand == MOVE_TO_ENEMY ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + } + + enemyNode.Remove(); + enemy = NULL; + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + AI_ENEMY_DEAD = true; + + SetChatSound(); +} + +/* +===================== +idAI::EnemyPositionValid +===================== +*/ +bool idAI::EnemyPositionValid() const { + trace_t tr; + idVec3 muzzle; + idMat3 axis; + + if ( !enemy.GetEntity() ) { + return false; + } + + if ( AI_ENEMY_VISIBLE ) { + return true; + } + + gameLocal.clip.TracePoint( tr, GetEyePosition(), lastVisibleEnemyPos + lastVisibleEnemyEyeOffset, MASK_OPAQUE, this ); + if ( tr.fraction < 1.0f ) { + // can't see the area yet, so don't know if he's there or not + return true; + } + + return false; +} + +/* +===================== +idAI::SetEnemyPosition +===================== +*/ +void idAI::SetEnemyPosition() { + idActor *enemyEnt = enemy.GetEntity(); + int enemyAreaNum; + int areaNum; + int lastVisibleReachableEnemyAreaNum = 0; + aasPath_t path; + idVec3 pos; + bool onGround; + + if ( !enemyEnt ) { + return; + } + + lastVisibleReachableEnemyPos = lastReachableEnemyPos; + lastVisibleEnemyEyeOffset = enemyEnt->EyeOffset(); + lastVisibleEnemyPos = enemyEnt->GetPhysics()->GetOrigin(); + if ( move.moveType == MOVETYPE_FLY ) { + pos = lastVisibleEnemyPos; + onGround = true; + } else { + onGround = enemyEnt->GetFloorPos( 64.0f, pos ); + if ( enemyEnt->OnLadder() ) { + onGround = false; + } + } + + if ( !onGround ) { + if ( move.moveCommand == MOVE_TO_ENEMY ) { + AI_DEST_UNREACHABLE = true; + } + return; + } + + // when we don't have an AAS, we can't tell if an enemy is reachable or not, + // so just assume that he is. + if ( !aas ) { + lastVisibleReachableEnemyPos = lastVisibleEnemyPos; + if ( move.moveCommand == MOVE_TO_ENEMY ) { + AI_DEST_UNREACHABLE = false; + } + enemyAreaNum = 0; + areaNum = 0; + } else { + lastVisibleReachableEnemyAreaNum = move.toAreaNum; + enemyAreaNum = PointReachableAreaNum( lastVisibleEnemyPos, 1.0f ); + if ( !enemyAreaNum ) { + enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f ); + pos = lastReachableEnemyPos; + } + if ( !enemyAreaNum ) { + if ( move.moveCommand == MOVE_TO_ENEMY ) { + AI_DEST_UNREACHABLE = true; + } + areaNum = 0; + } else { + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, enemyAreaNum, pos ) ) { + lastVisibleReachableEnemyPos = pos; + lastVisibleReachableEnemyAreaNum = enemyAreaNum; + if ( move.moveCommand == MOVE_TO_ENEMY ) { + AI_DEST_UNREACHABLE = false; + } + } else if ( move.moveCommand == MOVE_TO_ENEMY ) { + AI_DEST_UNREACHABLE = true; + } + } + } + + if ( move.moveCommand == MOVE_TO_ENEMY ) { + if ( !aas ) { + // keep the move destination up to date for wandering + move.moveDest = lastVisibleReachableEnemyPos; + } else if ( enemyAreaNum ) { + move.toAreaNum = lastVisibleReachableEnemyAreaNum; + move.moveDest = lastVisibleReachableEnemyPos; + } + + if ( move.moveType == MOVETYPE_FLY ) { + predictedPath_t path; + idVec3 end = move.moveDest; + end.z += enemyEnt->EyeOffset().z + fly_offset; + idAI::PredictPath( this, aas, move.moveDest, end - move.moveDest, 1000, 1000, SE_BLOCKED, path ); + move.moveDest = path.endPos; + move.toAreaNum = PointReachableAreaNum( move.moveDest, 1.0f ); + } + } +} + +/* +===================== +idAI::UpdateEnemyPosition +===================== +*/ +void idAI::UpdateEnemyPosition() { + idActor *enemyEnt = enemy.GetEntity(); + int enemyAreaNum; + int areaNum; + aasPath_t path; + predictedPath_t predictedPath; + idVec3 enemyPos; + bool onGround; + + if ( !enemyEnt ) { + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + if ( move.moveType == MOVETYPE_FLY ) { + enemyPos = enemyEnt->GetPhysics()->GetOrigin(); + onGround = true; + } else { + onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos ); + if ( enemyEnt->OnLadder() ) { + onGround = false; + } + } + + if ( onGround ) { + // when we don't have an AAS, we can't tell if an enemy is reachable or not, + // so just assume that he is. + if ( !aas ) { + enemyAreaNum = 0; + lastReachableEnemyPos = enemyPos; + } else { + enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f ); + if ( enemyAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) { + lastReachableEnemyPos = enemyPos; + } + } + } + } + + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + + if ( CanSee( enemyEnt, false ) ) { + AI_ENEMY_VISIBLE = true; + if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) { + AI_ENEMY_IN_FOV = true; + } + + SetEnemyPosition(); + } else { + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertEntity() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr(); + if ( dist < Square( AI_HEARING_RANGE ) ) { + SetEnemyPosition(); + } + } + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, 1 ); + gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, 1 ); + } +} + +/* +===================== +idAI::SetEnemy +===================== +*/ +void idAI::SetEnemy( idActor *newEnemy ) { + int enemyAreaNum; + + if ( AI_DEAD ) { + ClearEnemy(); + return; + } + + AI_ENEMY_DEAD = false; + if ( !newEnemy ) { + ClearEnemy(); + } else if ( enemy.GetEntity() != newEnemy ) { + + // Check to see if we should unlock the 'Turncloak' achievement + const idActor * enemyEnt = enemy.GetEntity(); + if ( enemyEnt != NULL && enemyEnt->IsType( idPlayer::Type ) && newEnemy->IsType( idAI::Type ) && newEnemy->team == this->team && ( idStr::Icmp( newEnemy->GetName(), "hazmat_dummy") != 0 ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_TWO_DEMONS_FIGHT_EACH_OTHER ); + } + } + + enemy = newEnemy; + enemyNode.AddToEnd( newEnemy->enemyList ); + if ( newEnemy->health <= 0 ) { + EnemyDead(); + return; + } + // let the monster know where the enemy is + newEnemy->GetAASLocation( aas, lastReachableEnemyPos, enemyAreaNum ); + SetEnemyPosition(); + SetChatSound(); + + lastReachableEnemyPos = lastVisibleEnemyPos; + lastVisibleReachableEnemyPos = lastReachableEnemyPos; + enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f ); + if ( aas && enemyAreaNum ) { + aas->PushPointIntoAreaNum( enemyAreaNum, lastReachableEnemyPos ); + lastVisibleReachableEnemyPos = lastReachableEnemyPos; + } + } +} + +/* +============ +idAI::FirstVisiblePointOnPath +============ +*/ +idVec3 idAI::FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const { + int i, areaNum, targetAreaNum, curAreaNum, travelTime; + idVec3 curOrigin; + idReachability *reach; + + if ( !aas ) { + return origin; + } + + areaNum = PointReachableAreaNum( origin ); + targetAreaNum = PointReachableAreaNum( target ); + + if ( !areaNum || !targetAreaNum ) { + return origin; + } + + if ( ( areaNum == targetAreaNum ) || PointVisible( origin ) ) { + return origin; + } + + curAreaNum = areaNum; + curOrigin = origin; + + for( i = 0; i < 10; i++ ) { + + if ( !aas->RouteToGoalArea( curAreaNum, curOrigin, targetAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return target; + } + + curAreaNum = reach->toAreaNum; + curOrigin = reach->end; + + if ( PointVisible( curOrigin ) ) { + return curOrigin; + } + } + + return origin; +} + +/* +=================== +idAI::CalculateAttackOffsets + +calculate joint positions on attack frames so we can do proper "can hit" tests +=================== +*/ +void idAI::CalculateAttackOffsets() { + const idDeclModelDef *modelDef; + int num; + int i; + int frame; + const frameCommand_t *command; + idMat3 axis; + const idAnim *anim; + jointHandle_t joint; + + modelDef = animator.ModelDef(); + if ( !modelDef ) { + return; + } + num = modelDef->NumAnims(); + + // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim + animator.RemoveOriginOffset( false ); + + // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for + // launch offsets so that anim number can be used without subtracting 1. + missileLaunchOffset.SetGranularity( 1 ); + missileLaunchOffset.SetNum( num + 1 ); + missileLaunchOffset[ 0 ].Zero(); + + for( i = 1; i <= num; i++ ) { + missileLaunchOffset[ i ].Zero(); + anim = modelDef->GetAnim( i ); + if ( anim ) { + frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command ); + if ( frame >= 0 ) { + joint = animator.GetJointHandle( command->string->c_str() ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() ); + } + GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis ); + } + } + } + + animator.RemoveOriginOffset( true ); +} + +/* +===================== +idAI::CreateProjectileClipModel +===================== +*/ +void idAI::CreateProjectileClipModel() const { + if ( projectileClipModel == NULL ) { + idBounds projectileBounds( vec3_origin ); + projectileBounds.ExpandSelf( projectileRadius ); + projectileClipModel = new (TAG_MODEL) idClipModel( idTraceModel( projectileBounds ) ); + } +} + +/* +===================== +idAI::GetAimDir +===================== +*/ +bool idAI::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const { + idVec3 targetPos1; + idVec3 targetPos2; + idVec3 delta; + float max_height; + bool result; + + // if no aimAtEnt or projectile set + if ( !aimAtEnt || !projectileDef ) { + aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + return false; + } + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + if ( aimAtEnt == enemy.GetEntity() ) { + static_cast( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 ); + } else if ( aimAtEnt->IsType( idActor::Type ) ) { + static_cast( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 ); + } else { + targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter(); + targetPos2 = targetPos1; + } + + if ( this->team == 0 && !idStr::Cmp( aimAtEnt->GetEntityDefName(), "monster_demon_vulgar" ) ) { + targetPos1.z -= 28.f; + targetPos2.z -= 12.f; + } + + // try aiming for chest + delta = firePos - targetPos1; + max_height = delta.LengthFast() * projectile_height_to_distance_ratio; + result = PredictTrajectory( firePos, targetPos1, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + if ( result || !aimAtEnt->IsType( idActor::Type ) ) { + return result; + } + + // try aiming for head + delta = firePos - targetPos2; + max_height = delta.LengthFast() * projectile_height_to_distance_ratio; + result = PredictTrajectory( firePos, targetPos2, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + + return result; +} + +/* +===================== +idAI::BeginAttack +===================== +*/ +void idAI::BeginAttack( const char *name ) { + attack = name; + lastAttackTime = gameLocal.time; +} + +/* +===================== +idAI::EndAttack +===================== +*/ +void idAI::EndAttack() { + attack = ""; +} + +/* +===================== +idAI::CreateProjectile +===================== +*/ +idProjectile *idAI::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) { + idEntity *ent; + const char *clsname; + + if ( !projectile.GetEntity() ) { + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); + if ( ent == NULL ) { + clsname = projectileDef->GetString( "classname" ); + gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); + return NULL; + } + + if ( !ent->IsType( idProjectile::Type ) ) { + clsname = ent->GetClassname(); + gameLocal.Error( "'%s' is not an idProjectile", clsname ); + } + projectile = ( idProjectile * )ent; + } + + projectile.GetEntity()->Create( this, pos, dir ); + + return projectile.GetEntity(); +} + +/* +===================== +idAI::RemoveProjectile +===================== +*/ +void idAI::RemoveProjectile() { + if ( projectile.GetEntity() ) { + projectile.GetEntity()->PostEventMS( &EV_Remove, 0 ); + projectile = NULL; + } +} + +/* +===================== +idAI::LaunchProjectile +===================== +*/ +idProjectile *idAI::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) { + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idMat3 proj_axis; + bool forceMuzzle; + idVec3 tmp; + idProjectile *lastProjectile; + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + forceMuzzle = spawnArgs.GetBool( "forceMuzzle", "0" ); + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + proj_axis = axis; + + if ( !forceMuzzle ) { // _D3XP + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + } + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // clamp the attack direction to be within monster's attack cone so he doesn't do + // things like throw the missile backwards if you're behind him + diff = idMath::AngleDelta( ang.yaw, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle ); + + lastAttackTime = gameLocal.time; + + return lastProjectile; +} + +/* +================ +idAI::DamageFeedback + +callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller. + +FIXME: This gets called when we call idPlayer::CalcDamagePoints from idAI::AttackMelee, which then checks for a saving throw, +possibly forcing a miss. This is harmless behavior ATM, but is not intuitive. +================ +*/ +void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( ( victim == this ) && inflictor->IsType( idProjectile::Type ) ) { + // monsters only get half damage from their own projectiles + damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage + + } else if ( victim == enemy.GetEntity() ) { + AI_HIT_ENEMY = true; + } +} + +/* +===================== +idAI::DirectDamage + +Causes direct damage to an entity + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( meleeDef == NULL ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + return; + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p != NULL && *p != NULL ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + + ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + + // end the attack if we're a multiframe attack + EndAttack(); +} + +/* +===================== +idAI::TestMelee +===================== +*/ +bool idAI::TestMelee() const { + trace_t trace; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt || !melee_range ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -melee_range; + bounds[0][1] = -melee_range; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = melee_range; + bounds[1][1] = melee_range; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, 1 ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +/* +===================== +idAI::AttackMelee + +jointname allows the endpoint to be exactly specified in the model, +as for the commando tentacle. If not specified, it will be set to +the facing direction + melee_range. + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +bool idAI::AttackMelee( const char *meleeDefName ) { + const idDict *meleeDef; + idActor *enemyEnt = enemy.GetEntity(); + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( meleeDef == NULL ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + return false; + } + + if ( enemyEnt == NULL ) { + p = meleeDef->GetString( "snd_miss" ); + if ( p != NULL && *p != NULL ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // check for the "saving throw" automatic melee miss on lethal blow + // stupid place for this. + bool forceMiss = false; + if ( enemyEnt->IsType( idPlayer::Type ) && g_skill.GetInteger() < 2 ) { + int damage, armor; + idPlayer *player = static_cast( enemyEnt ); + player->CalcDamagePoints( this, this, meleeDef, 1.0f, INVALID_JOINT, &damage, &armor ); + + if ( enemyEnt->health <= damage ) { + int t = gameLocal.time - player->lastSavingThrowTime; + if ( t > SAVING_THROW_TIME ) { + player->lastSavingThrowTime = gameLocal.time; + t = 0; + } + if ( t < 1000 ) { + gameLocal.Printf( "Saving throw.\n" ); + forceMiss = true; + } + } + } + + // make sure the trace can actually hit the enemy + if ( forceMiss || !TestMelee() ) { + // missed + p = meleeDef->GetString( "snd_miss" ); + if ( p != NULL && *p != NULL ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p != NULL && *p != NULL ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + + enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + + lastAttackTime = gameLocal.time; + + return true; +} + +/* +================ +idAI::PushWithAF +================ +*/ +void idAI::PushWithAF() { + int i, j; + afTouch_t touchList[ MAX_GENTITIES ]; + idEntity *pushed_ents[ MAX_GENTITIES ]; + idEntity *ent; + idVec3 vel; + int num_pushed; + + num_pushed = 0; + af.ChangePose( this, gameLocal.time ); + int num = af.EntitiesTouchingAF( touchList ); + for( i = 0; i < num; i++ ) { + if ( touchList[ i ].touchedEnt->IsType( idProjectile::Type ) ) { + // skip projectiles + continue; + } + + // make sure we havent pushed this entity already. this avoids causing double damage + for( j = 0; j < num_pushed; j++ ) { + if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) { + break; + } + } + if ( j >= num_pushed ) { + ent = touchList[ i ].touchedEnt; + pushed_ents[num_pushed++] = ent; + vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin(); + vel.Normalize(); + if ( attack.Length() && ent->IsType( idActor::Type ) ) { + ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT ); + } else { + ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() ); + } + } + } +} + +/*********************************************************************** + + Misc + +***********************************************************************/ + +/* +================ +idAI::GetMuzzle +================ +*/ +void idAI::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) { + jointHandle_t joint; + + if ( !jointname || !jointname[ 0 ] ) { + muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14; + muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f; + } else { + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); + } +} + +/* +================ +idAI::TriggerWeaponEffects +================ +*/ +void idAI::TriggerWeaponEffects( const idVec3 &muzzle ) { + idVec3 org; + idMat3 axis; + + if ( !g_muzzleFlash.GetBool() ) { + return; + } + + // muzzle flash + // offset the shader parms so muzzle flashes show up + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat(); + + if ( flashJointWorld != INVALID_JOINT ) { + GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis ); + + if ( worldMuzzleFlash.lightRadius.x > 0.0f ) { + worldMuzzleFlash.axis = axis; + worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + if ( worldMuzzleFlashHandle != - 1 ) { + gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); + } else { + worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash ); + } + muzzleFlashEnd = gameLocal.time + flashTime; + UpdateVisuals(); + } + } +} + +/* +================ +idAI::UpdateMuzzleFlash +================ +*/ +void idAI::UpdateMuzzleFlash() { + if ( worldMuzzleFlashHandle != -1 ) { + if ( gameLocal.time >= muzzleFlashEnd ) { + gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle ); + worldMuzzleFlashHandle = -1; + } else { + idVec3 muzzle; + animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis ); + animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis ); + muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); + worldMuzzleFlash.origin = muzzle; + gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); + } + } +} + +/* +================ +idAI::Hide +================ +*/ +void idAI::Hide() { + idActor::Hide(); + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + StopSound( SND_CHANNEL_AMBIENT, false ); + SetChatSound(); + + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::Show +================ +*/ +void idAI::Show() { + idActor::Show(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.GetClipModel()->Link( gameLocal.clip ); + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + SetChatSound(); + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); +} + +/* +===================== +idAI::SetChatSound +===================== +*/ +void idAI::SetChatSound() { + const char *snd; + + if ( IsHidden() ) { + snd = NULL; + } else if ( enemy.GetEntity() ) { + snd = spawnArgs.GetString( "snd_chatter_combat", NULL ); + chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_combat_min", "5" ) ); + chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_combat_max", "10" ) ); + } else if ( !spawnArgs.GetBool( "no_idle_chatter" ) ) { + snd = spawnArgs.GetString( "snd_chatter", NULL ); + chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_min", "5" ) ); + chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_max", "10" ) ); + } else { + snd = NULL; + } + + if ( snd != NULL && *snd != NULL ) { + chat_snd = declManager->FindSound( snd ); + + // set the next chat time + chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min ); + } else { + chat_snd = NULL; + } +} + +/* +================ +idAI::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idAI::CanPlayChatterSounds() const { + if ( AI_DEAD ) { + return false; + } + + if ( IsHidden() ) { + return false; + } + + if ( enemy.GetEntity() ) { + return true; + } + + if ( spawnArgs.GetBool( "no_idle_chatter" ) ) { + return false; + } + + return true; +} + +/* +===================== +idAI::PlayChatter +===================== +*/ +void idAI::PlayChatter() { + // check if it's time to play a chat sound + if ( AI_DEAD || !chat_snd || ( chat_time > gameLocal.time ) ) { + return; + } + + StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, NULL ); + + // set the next chat time + chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min ); +} + +/* +===================== +idAI::UpdateParticles +===================== +*/ +void idAI::UpdateParticles() { + if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) { + idVec3 realVector; + idMat3 realAxis; + + int particlesAlive = 0; + for ( int i = 0; i < particles.Num(); i++ ) { + // Smoke particles on AI characters will always be "slow", even when held by grabber + SetTimeState ts(TIME_GROUP1); + if ( particles[i].particle && particles[i].time ) { + particlesAlive++; + if (af.IsActive()) { + realAxis = mat3_identity; + realVector = GetPhysics()->GetOrigin(); + } else { + animator.GetJointTransform( particles[i].joint, gameLocal.time, realVector, realAxis ); + realAxis *= renderEntity.axis; + realVector = physicsObj.GetOrigin() + ( realVector + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ); + } + + if ( !gameLocal.smokeParticles->EmitSmoke( particles[i].particle, particles[i].time, gameLocal.random.CRandomFloat(), realVector, realAxis, timeGroup /*_D3XP*/ )) { + if ( restartParticles ) { + particles[i].time = gameLocal.time; + } else { + particles[i].time = 0; + particlesAlive--; + } + } + } + } + if ( particlesAlive == 0 ) { + BecomeInactive( TH_UPDATEPARTICLES ); + } + } +} + +/* +===================== +idAI::TriggerParticles +===================== +*/ +void idAI::TriggerParticles( const char *jointName ) { + jointHandle_t jointNum; + + jointNum = animator.GetJointHandle( jointName ); + for ( int i = 0; i < particles.Num(); i++ ) { + if ( particles[i].joint == jointNum ) { + particles[i].time = gameLocal.time; + BecomeActive( TH_UPDATEPARTICLES ); + } + } +} + +void idAI::TriggerFX( const char* joint, const char* fx ) { + + if( !strcmp(joint, "origin") ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } else { + idVec3 joint_origin; + idMat3 joint_axis; + jointHandle_t jointNum; + jointNum = animator.GetJointHandle( joint ); + + if ( jointNum == INVALID_JOINT ) { + gameLocal.Warning( "Unknown fx joint '%s' on entity %s", joint, name.c_str() ); + return; + } + + GetJointWorldTransform( jointNum, gameLocal.time, joint_origin, joint_axis ); + idEntityFx::StartFx( fx, &joint_origin, &joint_axis, this, true ); + } +} + +idEntity* idAI::StartEmitter( const char* name, const char* joint, const char* particle ) { + + idEntity* existing = GetEmitter(name); + if(existing) { + return existing; + } + + jointHandle_t jointNum; + jointNum = animator.GetJointHandle( joint ); + + idVec3 offset; + idMat3 axis; + + GetJointWorldTransform( jointNum, gameLocal.time, offset, axis ); + + /*animator.GetJointTransform( jointNum, gameLocal.time, offset, axis ); + offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis(); + axis = axis * GetPhysics()->GetAxis();*/ + + + + idDict args; + + const idDeclEntityDef *emitterDef = gameLocal.FindEntityDef( "func_emitter", false ); + args = emitterDef->dict; + args.Set("model", particle); + args.Set( "origin", offset.ToString() ); + args.SetBool("start_off", true); + + idEntity* ent; + gameLocal.SpawnEntityDef(args, &ent, false); + + ent->GetPhysics()->SetOrigin(offset); + //ent->GetPhysics()->SetAxis(axis); + + // align z-axis of model with the direction + /*idVec3 tmp; + axis = (viewAxis[ 0 ] * physicsObj.GetGravityAxis()).ToMat3(); + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + ent->GetPhysics()->SetAxis(axis);*/ + + axis = physicsObj.GetGravityAxis(); + ent->GetPhysics()->SetAxis(axis); + + + ent->GetPhysics()->GetClipModel()->SetOwner( this ); + + + //Keep a reference to the emitter so we can track it + funcEmitter_t newEmitter; + strcpy(newEmitter.name, name); + newEmitter.particle = (idFuncEmitter*)ent; + newEmitter.joint = jointNum; + funcEmitters.Set(newEmitter.name, newEmitter); + + //Bind it to the joint and make it active + newEmitter.particle->BindToJoint(this, jointNum, true); + newEmitter.particle->BecomeActive(TH_THINK); + newEmitter.particle->Show(); + newEmitter.particle->PostEventMS(&EV_Activate, 0, this); + return newEmitter.particle; +} + +idEntity* idAI::GetEmitter( const char* name ) { + funcEmitter_t* emitter; + funcEmitters.Get(name, &emitter); + if(emitter) { + return emitter->particle; + } + return NULL; +} + +void idAI::StopEmitter( const char* name ) { + funcEmitter_t* emitter; + funcEmitters.Get(name, &emitter); + if(emitter) { + emitter->particle->Unbind(); + emitter->particle->PostEventMS( &EV_Remove, 0 ); + funcEmitters.Remove(name); + } +} + + + +/*********************************************************************** + + Head & torso aiming + +***********************************************************************/ + +/* +================ +idAI::UpdateAnimationControllers +================ +*/ +bool idAI::UpdateAnimationControllers() { + idVec3 local; + idVec3 focusPos; + idQuat jawQuat; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, eyepos, eyepos + orientationJointAxis[ 0 ] * 32.0f, 1 ); + } + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions. + // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which + // are already up to date because of getting the joints in this function) and then sets their positions, which + // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled, + // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no + // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number + // in order to see how many times an entity transforms the joints per frame. + idActor::UpdateAnimationControllers(); + + idEntity *focusEnt = focusEntity.GetEntity(); + if ( !allowJointMod || !allowEyeFocus || ( gameLocal.time >= focusTime ) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + +#if 0 + gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, 1 ); + gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, 1 ); + gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, 1 ); +#endif + + // determine pitch from joint position + dir = focusPos - eyepos; + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + newLookAng.roll = 0.0f; + + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + return true; +} + +/*********************************************************************** + +idCombatNode + +***********************************************************************/ + +const idEventDef EV_CombatNode_MarkUsed( "markUsed" ); + +CLASS_DECLARATION( idEntity, idCombatNode ) + EVENT( EV_CombatNode_MarkUsed, idCombatNode::Event_MarkUsed ) + EVENT( EV_Activate, idCombatNode::Event_Activate ) +END_CLASS + +/* +===================== +idCombatNode::idCombatNode +===================== +*/ +idCombatNode::idCombatNode() { + min_dist = 0.0f; + max_dist = 0.0f; + cone_dist = 0.0f; + min_height = 0.0f; + max_height = 0.0f; + cone_left.Zero(); + cone_right.Zero(); + offset.Zero(); + disabled = false; +} + +/* +===================== +idCombatNode::Save +===================== +*/ +void idCombatNode::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( min_dist ); + savefile->WriteFloat( max_dist ); + savefile->WriteFloat( cone_dist ); + savefile->WriteFloat( min_height ); + savefile->WriteFloat( max_height ); + savefile->WriteVec3( cone_left ); + savefile->WriteVec3( cone_right ); + savefile->WriteVec3( offset ); + savefile->WriteBool( disabled ); +} + +/* +===================== +idCombatNode::Restore +===================== +*/ +void idCombatNode::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( min_dist ); + savefile->ReadFloat( max_dist ); + savefile->ReadFloat( cone_dist ); + savefile->ReadFloat( min_height ); + savefile->ReadFloat( max_height ); + savefile->ReadVec3( cone_left ); + savefile->ReadVec3( cone_right ); + savefile->ReadVec3( offset ); + savefile->ReadBool( disabled ); +} + +/* +===================== +idCombatNode::Spawn +===================== +*/ +void idCombatNode::Spawn() { + float fov; + float yaw; + float height; + + min_dist = spawnArgs.GetFloat( "min" ); + max_dist = spawnArgs.GetFloat( "max" ); + height = spawnArgs.GetFloat( "height" ); + fov = spawnArgs.GetFloat( "fov", "60" ); + offset = spawnArgs.GetVector( "offset" ); + + const idVec3 &org = GetPhysics()->GetOrigin() + offset; + min_height = org.z - height * 0.5f; + max_height = min_height + height; + + const idMat3 &axis = GetPhysics()->GetAxis(); + yaw = axis[ 0 ].ToYaw(); + + idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f ); + cone_left = leftang.ToForward(); + + idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f ); + cone_right = rightang.ToForward(); + + disabled = spawnArgs.GetBool( "start_off" ); +} + +/* +===================== +idCombatNode::IsDisabled +===================== +*/ +bool idCombatNode::IsDisabled() const { + return disabled; +} + +/* +===================== +idCombatNode::DrawDebugInfo +===================== +*/ +void idCombatNode::DrawDebugInfo() { + idEntity *ent; + idCombatNode *node; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec4 color; + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 0 ) ); + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idCombatNode::Type ) ) { + continue; + } + + node = static_cast( ent ); + if ( node->disabled ) { + color = colorMdGrey; + } else if ( player != NULL && node->EntityInView( player, player->GetPhysics()->GetOrigin() ) ) { + color = colorYellow; + } else { + color = colorRed; + } + + idVec3 leftDir( -node->cone_left.y, node->cone_left.x, 0.0f ); + idVec3 rightDir( node->cone_right.y, -node->cone_right.x, 0.0f ); + idVec3 org = node->GetPhysics()->GetOrigin() + node->offset; + + bounds[ 1 ].z = node->max_height; + + leftDir.NormalizeFast(); + rightDir.NormalizeFast(); + + const idMat3 &axis = node->GetPhysics()->GetAxis(); + float cone_dot = node->cone_right * axis[ 1 ]; + if ( idMath::Fabs( cone_dot ) > 0.1 ) { + float cone_dist = node->max_dist / cone_dot; + idVec3 pos1 = org + leftDir * node->min_dist; + idVec3 pos2 = org + leftDir * cone_dist; + idVec3 pos3 = org + rightDir * node->min_dist; + idVec3 pos4 = org + rightDir * cone_dist; + + gameRenderWorld->DebugLine( color, node->GetPhysics()->GetOrigin(), ( pos1 + pos3 ) * 0.5f, 1 ); + gameRenderWorld->DebugLine( color, pos1, pos2, 1 ); + gameRenderWorld->DebugLine( color, pos1, pos3, 1 ); + gameRenderWorld->DebugLine( color, pos3, pos4, 1 ); + gameRenderWorld->DebugLine( color, pos2, pos4, 1 ); + gameRenderWorld->DebugBounds( color, bounds, org, 1 ); + } + } +} + +/* +===================== +idCombatNode::EntityInView +===================== +*/ +bool idCombatNode::EntityInView( idActor *actor, const idVec3 &pos ) { + if ( !actor || ( actor->health <= 0 ) ) { + return false; + } + + const idBounds &bounds = actor->GetPhysics()->GetBounds(); + if ( ( pos.z + bounds[ 1 ].z < min_height ) || ( pos.z + bounds[ 0 ].z >= max_height ) ) { + return false; + } + + const idVec3 &org = GetPhysics()->GetOrigin() + offset; + const idMat3 &axis = GetPhysics()->GetAxis(); + idVec3 dir = pos - org; + float dist = dir * axis[ 0 ]; + + if ( ( dist < min_dist ) || ( dist > max_dist ) ) { + return false; + } + + float left_dot = dir * cone_left; + if ( left_dot < 0.0f ) { + return false; + } + + float right_dot = dir * cone_right; + if ( right_dot < 0.0f ) { + return false; + } + + return true; +} + +/* +===================== +idCombatNode::Event_Activate +===================== +*/ +void idCombatNode::Event_Activate( idEntity *activator ) { + disabled = !disabled; +} + +/* +===================== +idCombatNode::Event_MarkUsed +===================== +*/ +void idCombatNode::Event_MarkUsed() { + if ( spawnArgs.GetBool( "use_once" ) ) { + disabled = true; + } +} diff --git a/neo/d3xp/ai/AI.h b/neo/d3xp/ai/AI.h new file mode 100644 index 00000000..a7f2a3d4 --- /dev/null +++ b/neo/d3xp/ai/AI.h @@ -0,0 +1,731 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __AI_H__ +#define __AI_H__ + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +const float SQUARE_ROOT_OF_2 = 1.414213562f; +const float AI_TURN_PREDICTION = 0.2f; +const float AI_TURN_SCALE = 60.0f; +const float AI_SEEK_PREDICTION = 0.3f; +const float AI_FLY_DAMPENING = 0.15f; +const float AI_HEARING_RANGE = 2048.0f; +const int DEFAULT_FLY_OFFSET = 68; + +#define ATTACK_IGNORE 0 +#define ATTACK_ON_DAMAGE 1 +#define ATTACK_ON_ACTIVATE 2 +#define ATTACK_ON_SIGHT 4 + +typedef struct ballistics_s { + float angle; // angle in degrees in the range [-180, 180] + float time; // time it takes before the projectile arrives +} ballistics_t; + +extern int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ); + +// defined in script/ai_base.script. please keep them up to date. +typedef enum { + MOVETYPE_DEAD, + MOVETYPE_ANIM, + MOVETYPE_SLIDE, + MOVETYPE_FLY, + MOVETYPE_STATIC, + NUM_MOVETYPES +} moveType_t; + +typedef enum { + MOVE_NONE, + MOVE_FACE_ENEMY, + MOVE_FACE_ENTITY, + + // commands < NUM_NONMOVING_COMMANDS don't cause a change in position + NUM_NONMOVING_COMMANDS, + + MOVE_TO_ENEMY = NUM_NONMOVING_COMMANDS, + MOVE_TO_ENEMYHEIGHT, + MOVE_TO_ENTITY, + MOVE_OUT_OF_RANGE, + MOVE_TO_ATTACK_POSITION, + MOVE_TO_COVER, + MOVE_TO_POSITION, + MOVE_TO_POSITION_DIRECT, + MOVE_SLIDE_TO_POSITION, + MOVE_WANDER, + NUM_MOVE_COMMANDS +} moveCommand_t; + +typedef enum { + TALK_NEVER, + TALK_DEAD, + TALK_OK, + TALK_BUSY, + NUM_TALK_STATES +} talkState_t; + +// +// status results from move commands +// make sure to change script/doom_defs.script if you add any, or change their order +// +typedef enum { + MOVE_STATUS_DONE, + MOVE_STATUS_MOVING, + MOVE_STATUS_WAITING, + MOVE_STATUS_DEST_NOT_FOUND, + MOVE_STATUS_DEST_UNREACHABLE, + MOVE_STATUS_BLOCKED_BY_WALL, + MOVE_STATUS_BLOCKED_BY_OBJECT, + MOVE_STATUS_BLOCKED_BY_ENEMY, + MOVE_STATUS_BLOCKED_BY_MONSTER +} moveStatus_t; + +#define DI_NODIR -1 + +// obstacle avoidance +typedef struct obstaclePath_s { + idVec3 seekPos; // seek position avoiding obstacles + idEntity * firstObstacle; // if != NULL the first obstacle along the path + idVec3 startPosOutsideObstacles; // start position outside obstacles + idEntity * startPosObstacle; // if != NULL the obstacle containing the start position + idVec3 seekPosOutsideObstacles; // seek position outside obstacles + idEntity * seekPosObstacle; // if != NULL the obstacle containing the seek position +} obstaclePath_t; + +// path prediction +typedef enum { + SE_BLOCKED = BIT(0), + SE_ENTER_LEDGE_AREA = BIT(1), + SE_ENTER_OBSTACLE = BIT(2), + SE_FALL = BIT(3), + SE_LAND = BIT(4) +} stopEvent_t; + +typedef struct predictedPath_s { + idVec3 endPos; // final position + idVec3 endVelocity; // velocity at end position + idVec3 endNormal; // normal of blocking surface + int endTime; // time predicted + int endEvent; // event that stopped the prediction + const idEntity * blockingEntity; // entity that blocks the movement +} predictedPath_t; + +// +// events +// +extern const idEventDef AI_BeginAttack; +extern const idEventDef AI_EndAttack; +extern const idEventDef AI_MuzzleFlash; +extern const idEventDef AI_CreateMissile; +extern const idEventDef AI_AttackMissile; +extern const idEventDef AI_FireMissileAtTarget; +extern const idEventDef AI_LaunchProjectile; +extern const idEventDef AI_TriggerFX; +extern const idEventDef AI_StartEmitter; +extern const idEventDef AI_StopEmitter; +extern const idEventDef AI_AttackMelee; +extern const idEventDef AI_DirectDamage; +extern const idEventDef AI_JumpFrame; +extern const idEventDef AI_EnableClip; +extern const idEventDef AI_DisableClip; +extern const idEventDef AI_EnableGravity; +extern const idEventDef AI_DisableGravity; +extern const idEventDef AI_TriggerParticles; +extern const idEventDef AI_RandomPath; + +class idPathCorner; + +typedef struct particleEmitter_s { + particleEmitter_s() { + particle = NULL; + time = 0; + joint = INVALID_JOINT; + }; + const idDeclParticle *particle; + int time; + jointHandle_t joint; +} particleEmitter_t; + +typedef struct funcEmitter_s { + char name[64]; + idFuncEmitter* particle; + jointHandle_t joint; +} funcEmitter_t; + +class idMoveState { +public: + idMoveState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + moveType_t moveType; + moveCommand_t moveCommand; + moveStatus_t moveStatus; + idVec3 moveDest; + idVec3 moveDir; // used for wandering and slide moves + idEntityPtr goalEntity; + idVec3 goalEntityOrigin; // move to entity uses this to avoid checking the floor position every frame + int toAreaNum; + int startTime; + int duration; + float speed; // only used by flying creatures + float range; + float wanderYaw; + int nextWanderTime; + int blockTime; + idEntityPtr obstacle; + idVec3 lastMoveOrigin; + int lastMoveTime; + int anim; +}; + +class idAASFindCover : public idAASCallback { +public: + idAASFindCover( const idVec3 &hideFromPos ); + ~idAASFindCover(); + + virtual bool TestArea( const idAAS *aas, int areaNum ); + +private: + pvsHandle_t hidePVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; +}; + +class idAASFindAreaOutOfRange : public idAASCallback { +public: + idAASFindAreaOutOfRange( const idVec3 &targetPos, float maxDist ); + + virtual bool TestArea( const idAAS *aas, int areaNum ); + +private: + idVec3 targetPos; + float maxDistSqr; +}; + +class idAASFindAttackPosition : public idAASCallback { +public: + idAASFindAttackPosition( const idAI *self, const idMat3 &gravityAxis, idEntity *target, const idVec3 &targetPos, const idVec3 &fireOffset ); + ~idAASFindAttackPosition(); + + virtual bool TestArea( const idAAS *aas, int areaNum ); + +private: + const idAI *self; + idEntity *target; + idBounds excludeBounds; + idVec3 targetPos; + idVec3 fireOffset; + idMat3 gravityAxis; + pvsHandle_t targetPVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; +}; + +class idAI : public idActor { +public: + CLASS_PROTOTYPE( idAI ); + + idAI(); + ~idAI(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + void HeardSound( idEntity *ent, const char *action ); + idActor *GetEnemy() const; + void TalkTo( idActor *actor ); + talkState_t GetTalkState() const; + + bool GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const; + + void TouchedByFlashlight( idActor *flashlight_owner ); + + // Outputs a list of all monsters to the console. + static void List_f( const idCmdArgs &args ); + + // Finds a path around dynamic obstacles. + static bool FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ); + // Frees any nodes used for the dynamic obstacle avoidance. + static void FreeObstacleAvoidanceNodes(); + // Predicts movement, returns true if a stop event was triggered. + static bool PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path ); + // Return true if the trajectory of the clip model is collision free. + static bool TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ); + // Finds the best collision free trajectory for a clip model. + static bool PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ); + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + +protected: + // navigation + idAAS * aas; + int travelFlags; + + idMoveState move; + idMoveState savedMove; + + float kickForce; + bool ignore_obstacles; + float blockedRadius; + int blockedMoveTime; + int blockedAttackTime; + + // turning + float ideal_yaw; + float current_yaw; + float turnRate; + float turnVel; + float anim_turn_yaw; + float anim_turn_amount; + float anim_turn_angles; + + // physics + idPhysics_Monster physicsObj; + + // flying + jointHandle_t flyTiltJoint; + float fly_speed; + float fly_bob_strength; + float fly_bob_vert; + float fly_bob_horz; + int fly_offset; // prefered offset from player's view + float fly_seek_scale; + float fly_roll_scale; + float fly_roll_max; + float fly_roll; + float fly_pitch_scale; + float fly_pitch_max; + float fly_pitch; + + bool allowMove; // disables any animation movement + bool allowHiddenMovement; // allows character to still move around while hidden + bool disableGravity; // disables gravity and allows vertical movement by the animation + bool af_push_moveables; // allow the articulated figure to push moveable objects + + // weapon/attack vars + bool lastHitCheckResult; + int lastHitCheckTime; + int lastAttackTime; + float melee_range; + float projectile_height_to_distance_ratio; // calculates the maximum height a projectile can be thrown + idList missileLaunchOffset; + + const idDict * projectileDef; + mutable idClipModel *projectileClipModel; + float projectileRadius; + float projectileSpeed; + idVec3 projectileVelocity; + idVec3 projectileGravity; + idEntityPtr projectile; + idStr attack; + idVec3 homingMissileGoal; + + // chatter/talking + const idSoundShader *chat_snd; + int chat_min; + int chat_max; + int chat_time; + talkState_t talk_state; + idEntityPtr talkTarget; + + // cinematics + int num_cinematics; + int current_cinematic; + + bool allowJointMod; + idEntityPtr focusEntity; + idVec3 currentFocusPos; + int focusTime; + int alignHeadTime; + int forceAlignHeadTime; + idAngles eyeAng; + idAngles lookAng; + idAngles destLookAng; + idAngles lookMin; + idAngles lookMax; + idList lookJoints; + idList lookJointAngles; + float eyeVerticalOffset; + float eyeHorizontalOffset; + float eyeFocusRate; + float headFocusRate; + int focusAlignTime; + + // special fx + bool restartParticles; // should smoke emissions restart + bool useBoneAxis; // use the bone vs the model axis + idList particles; // particle data + + renderLight_t worldMuzzleFlash; // positioned on world weapon bone + int worldMuzzleFlashHandle; + jointHandle_t flashJointWorld; + int muzzleFlashEnd; + int flashTime; + + // joint controllers + idAngles eyeMin; + idAngles eyeMax; + jointHandle_t focusJoint; + jointHandle_t orientationJoint; + + // enemy variables + idEntityPtr enemy; + idVec3 lastVisibleEnemyPos; + idVec3 lastVisibleEnemyEyeOffset; + idVec3 lastVisibleReachableEnemyPos; + idVec3 lastReachableEnemyPos; + bool wakeOnFlashlight; + + bool spawnClearMoveables; + + idHashTable funcEmitters; + + idEntityPtr harvestEnt; + + // script variables + idScriptBool AI_TALK; + idScriptBool AI_DAMAGE; + idScriptBool AI_PAIN; + idScriptFloat AI_SPECIAL_DAMAGE; + idScriptBool AI_DEAD; + idScriptBool AI_ENEMY_VISIBLE; + idScriptBool AI_ENEMY_IN_FOV; + idScriptBool AI_ENEMY_DEAD; + idScriptBool AI_MOVE_DONE; + idScriptBool AI_ONGROUND; + idScriptBool AI_ACTIVATED; + idScriptBool AI_FORWARD; + idScriptBool AI_JUMP; + idScriptBool AI_ENEMY_REACHABLE; + idScriptBool AI_BLOCKED; + idScriptBool AI_OBSTACLE_IN_PATH; + idScriptBool AI_DEST_UNREACHABLE; + idScriptBool AI_HIT_ENEMY; + idScriptBool AI_PUSHED; + + // + // ai/ai.cpp + // + void SetAAS(); + virtual void DormantBegin(); // called when entity becomes dormant + virtual void DormantEnd(); // called when entity wakes from being dormant + void Think(); + void Activate( idEntity *activator ); +public: + int ReactionTo( const idEntity *ent ); +protected: + bool CheckForEnemy(); + void EnemyDead(); + virtual bool CanPlayChatterSounds() const; + void SetChatSound(); + void PlayChatter(); + virtual void Hide(); + virtual void Show(); + idVec3 FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const; + void CalculateAttackOffsets(); + void PlayCinematic(); + + // movement + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ); + void CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ); + void DeadMove(); + void AnimMove(); + void SlideMove(); + void AdjustFlyingAngles(); + void AddFlyBob( idVec3 &vel ); + void AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ); + void FlySeekGoal( idVec3 &vel, idVec3 &goalPos ); + void AdjustFlySpeed( idVec3 &vel ); + void FlyTurn(); + void FlyMove(); + void StaticMove(); + + // damage + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // navigation + void KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ); + bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const; + float TravelDistance( const idVec3 &start, const idVec3 &end ) const; + int PointReachableAreaNum( const idVec3 &pos, const float boundsScale = 2.0f ) const; + bool PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + void DrawRoute() const; + bool GetMovePos( idVec3 &seekPos ); + bool MoveDone() const; + bool EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ); + void BlockedFailSafe(); + + // movement control + void StopMove( moveStatus_t status ); + bool FaceEnemy(); + bool FaceEntity( idEntity *ent ); + bool DirectMoveToPosition( const idVec3 &pos ); + bool MoveToEnemyHeight(); + bool MoveOutOfRange( idEntity *entity, float range ); + bool MoveToAttackPosition( idEntity *ent, int attack_anim ); + bool MoveToEnemy(); + bool MoveToEntity( idEntity *ent ); + bool MoveToPosition( const idVec3 &pos ); + bool MoveToCover( idEntity *entity, const idVec3 &pos ); + bool SlideToPosition( const idVec3 &pos, float time ); + bool WanderAround(); + bool StepDirection( float dir ); + bool NewWanderDir( const idVec3 &dest ); + + // effects + const idDeclParticle *SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ); + void SpawnParticles( const char *keyName ); + bool ParticlesActive(); + + // turning + bool FacingIdeal(); + void Turn(); + bool TurnToward( float yaw ); + bool TurnToward( const idVec3 &pos ); + + // enemy management + void ClearEnemy(); + bool EnemyPositionValid() const; + void SetEnemyPosition(); + void UpdateEnemyPosition(); + void SetEnemy( idActor *newEnemy ); + + // attacks + void CreateProjectileClipModel() const; + idProjectile *CreateProjectile( const idVec3 &pos, const idVec3 &dir ); + void RemoveProjectile(); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ); + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + void DirectDamage( const char *meleeDefName, idEntity *ent ); + bool TestMelee() const; + bool AttackMelee( const char *meleeDefName ); + void BeginAttack( const char *name ); + void EndAttack(); + void PushWithAF(); + + // special effects + void GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ); + void InitMuzzleFlash(); + void TriggerWeaponEffects( const idVec3 &muzzle ); + void UpdateMuzzleFlash(); + virtual bool UpdateAnimationControllers(); + void UpdateParticles(); + void TriggerParticles( const char *jointName ); + + void TriggerFX( const char* joint, const char* fx ); + idEntity* StartEmitter( const char* name, const char* joint, const char* particle ); + idEntity* GetEmitter( const char* name ); + void StopEmitter( const char* name ); + + // AI script state management + void LinkScriptVariables(); + void UpdateAIScript(); + + // + // ai/ai_events.cpp + // + void Event_Activate( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_FindEnemy( int useFOV ); + void Event_FindEnemyAI( int useFOV ); + void Event_FindEnemyInCombatNodes(); + void Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ); + void Event_HeardSound( int ignore_team ); + void Event_SetEnemy( idEntity *ent ); + void Event_ClearEnemy(); + void Event_MuzzleFlash( const char *jointname ); + void Event_CreateMissile( const char *jointname ); + void Event_AttackMissile( const char *jointname ); + void Event_FireMissileAtTarget( const char *jointname, const char *targetname ); + void Event_LaunchMissile( const idVec3 &muzzle, const idAngles &ang ); + void Event_LaunchHomingMissile(); + void Event_SetHomingMissileGoal(); + void Event_LaunchProjectile( const char *entityDefName ); + void Event_AttackMelee( const char *meleeDefName ); + void Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ); + void Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ); + void Event_BeginAttack( const char *name ); + void Event_EndAttack(); + void Event_MeleeAttackToJoint( const char *jointname, const char *meleeDefName ); + void Event_RandomPath(); + void Event_CanBecomeSolid(); + void Event_BecomeSolid(); + void Event_BecomeNonSolid(); + void Event_BecomeRagdoll(); + void Event_StopRagdoll(); + void Event_SetHealth( float newHealth ); + void Event_GetHealth(); + void Event_AllowDamage(); + void Event_IgnoreDamage(); + void Event_GetCurrentYaw(); + void Event_TurnTo( float angle ); + void Event_TurnToPos( const idVec3 &pos ); + void Event_TurnToEntity( idEntity *ent ); + void Event_MoveStatus(); + void Event_StopMove(); + void Event_MoveToCover(); + void Event_MoveToEnemy(); + void Event_MoveToEnemyHeight(); + void Event_MoveOutOfRange( idEntity *entity, float range ); + void Event_MoveToAttackPosition( idEntity *entity, const char *attack_anim ); + void Event_MoveToEntity( idEntity *ent ); + void Event_MoveToPosition( const idVec3 &pos ); + void Event_SlideTo( const idVec3 &pos, float time ); + void Event_Wander(); + void Event_FacingIdeal(); + void Event_FaceEnemy(); + void Event_FaceEntity( idEntity *ent ); + void Event_WaitAction( const char *waitForState ); + void Event_GetCombatNode(); + void Event_EnemyInCombatCone( idEntity *ent, int use_current_enemy_location ); + void Event_WaitMove(); + void Event_GetJumpVelocity( const idVec3 &pos, float speed, float max_height ); + void Event_EntityInAttackCone( idEntity *ent ); + void Event_CanSeeEntity( idEntity *ent ); + void Event_SetTalkTarget( idEntity *target ); + void Event_GetTalkTarget(); + void Event_SetTalkState( int state ); + void Event_EnemyRange(); + void Event_EnemyRange2D(); + void Event_GetEnemy(); + void Event_GetEnemyPos(); + void Event_GetEnemyEyePos(); + void Event_PredictEnemyPos( float time ); + void Event_CanHitEnemy(); + void Event_CanHitEnemyFromAnim( const char *animname ); + void Event_CanHitEnemyFromJoint( const char *jointname ); + void Event_EnemyPositionValid(); + void Event_ChargeAttack( const char *damageDef ); + void Event_TestChargeAttack(); + void Event_TestAnimMoveTowardEnemy( const char *animname ); + void Event_TestAnimMove( const char *animname ); + void Event_TestMoveToPosition( const idVec3 &position ); + void Event_TestMeleeAttack(); + void Event_TestAnimAttack( const char *animname ); + void Event_Burn(); + void Event_PreBurn(); + void Event_ClearBurn(); + void Event_SetSmokeVisibility( int num, int on ); + void Event_NumSmokeEmitters(); + void Event_StopThinking(); + void Event_GetTurnDelta(); + void Event_GetMoveType(); + void Event_SetMoveType( int moveType ); + void Event_SaveMove(); + void Event_RestoreMove(); + void Event_AllowMovement( float flag ); + void Event_JumpFrame(); + void Event_EnableClip(); + void Event_DisableClip(); + void Event_EnableGravity(); + void Event_DisableGravity(); + void Event_EnableAFPush(); + void Event_DisableAFPush(); + void Event_SetFlySpeed( float speed ); + void Event_SetFlyOffset( int offset ); + void Event_ClearFlyOffset(); + void Event_GetClosestHiddenTarget( const char *type ); + void Event_GetRandomTarget( const char *type ); + void Event_TravelDistanceToPoint( const idVec3 &pos ); + void Event_TravelDistanceToEntity( idEntity *ent ); + void Event_TravelDistanceBetweenPoints( const idVec3 &source, const idVec3 &dest ); + void Event_TravelDistanceBetweenEntities( idEntity *source, idEntity *dest ); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_LookAtEnemy( float duration ); + void Event_SetJointMod( int allowJointMod ); + void Event_ThrowMoveable(); + void Event_ThrowAF(); + void Event_SetAngles( idAngles const &ang ); + void Event_GetAngles(); + void Event_GetTrajectoryToPlayer(); + void Event_RealKill(); + void Event_Kill(); + void Event_WakeOnFlashlight( int enable ); + void Event_LocateEnemy(); + void Event_KickObstacles( idEntity *kickEnt, float force ); + void Event_GetObstacle(); + void Event_PushPointIntoAAS( const idVec3 &pos ); + void Event_GetTurnRate(); + void Event_SetTurnRate( float rate ); + void Event_AnimTurn( float angles ); + void Event_AllowHiddenMovement( int enable ); + void Event_TriggerParticles( const char *jointName ); + void Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ); + void Event_CanReachPosition( const idVec3 &pos ); + void Event_CanReachEntity( idEntity *ent ); + void Event_CanReachEnemy(); + void Event_GetReachableEntityPosition( idEntity *ent ); + void Event_MoveToPositionDirect( const idVec3 &pos ); + void Event_AvoidObstacles( int ignore); + void Event_TriggerFX( const char* joint, const char* fx ); + + void Event_StartEmitter( const char* name, const char* joint, const char* particle ); + void Event_GetEmitter( const char* name ); + void Event_StopEmitter( const char* name ); +}; + +class idCombatNode : public idEntity { +public: + CLASS_PROTOTYPE( idCombatNode ); + + idCombatNode(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + bool IsDisabled() const; + bool EntityInView( idActor *actor, const idVec3 &pos ); + static void DrawDebugInfo(); + +private: + float min_dist; + float max_dist; + float cone_dist; + float min_height; + float max_height; + idVec3 cone_left; + idVec3 cone_right; + idVec3 offset; + bool disabled; + + void Event_Activate( idEntity *activator ); + void Event_MarkUsed(); +}; + +#endif /* !__AI_H__ */ diff --git a/neo/d3xp/ai/AI_Vagary.cpp b/neo/d3xp/ai/AI_Vagary.cpp new file mode 100644 index 00000000..72cd634f --- /dev/null +++ b/neo/d3xp/ai/AI_Vagary.cpp @@ -0,0 +1,149 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/*********************************************************************** + +game/ai/AI_Vagary.cpp + +Vagary specific AI code + +***********************************************************************/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +class idAI_Vagary : public idAI { +public: + CLASS_PROTOTYPE( idAI_Vagary ); + +private: + void Event_ChooseObjectToThrow( const idVec3 &mins, const idVec3 &maxs, float speed, float minDist, float offset ); + void Event_ThrowObjectAtEnemy( idEntity *ent, float speed ); +}; + +const idEventDef AI_Vagary_ChooseObjectToThrow( "vagary_ChooseObjectToThrow", "vvfff", 'e' ); +const idEventDef AI_Vagary_ThrowObjectAtEnemy( "vagary_ThrowObjectAtEnemy", "ef" ); + +CLASS_DECLARATION( idAI, idAI_Vagary ) + EVENT( AI_Vagary_ChooseObjectToThrow, idAI_Vagary::Event_ChooseObjectToThrow ) + EVENT( AI_Vagary_ThrowObjectAtEnemy, idAI_Vagary::Event_ThrowObjectAtEnemy ) +END_CLASS + +/* +================ +idAI_Vagary::Event_ChooseObjectToThrow +================ +*/ +void idAI_Vagary::Event_ChooseObjectToThrow( const idVec3 &mins, const idVec3 &maxs, float speed, float minDist, float offset ) { + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + int i, index; + float dist; + idVec3 vel; + idVec3 offsetVec( 0, 0, offset ); + idEntity *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt ) { + idThread::ReturnEntity( NULL ); + return; + } + + idVec3 enemyEyePos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset; + const idBounds &myBounds = physicsObj.GetAbsBounds(); + idBounds checkBounds( mins, maxs ); + checkBounds.TranslateSelf( physicsObj.GetOrigin() ); + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( checkBounds, -1, entityList, MAX_GENTITIES ); + + index = gameLocal.random.RandomInt( numListedEntities ); + for ( i = 0; i < numListedEntities; i++, index++ ) { + if ( index >= numListedEntities ) { + index = 0; + } + ent = entityList[ index ]; + if ( !ent->IsType( idMoveable::Type ) ) { + continue; + } + + if ( ent->fl.hidden ) { + // don't throw hidden objects + continue; + } + + idPhysics *entPhys = ent->GetPhysics(); + const idVec3 &entOrg = entPhys->GetOrigin(); + dist = ( entOrg - enemyEyePos ).LengthFast(); + if ( dist < minDist ) { + continue; + } + + idBounds expandedBounds = myBounds.Expand( entPhys->GetBounds().GetRadius() ); + if ( expandedBounds.LineIntersection( entOrg, enemyEyePos ) ) { + // ignore objects that are behind us + continue; + } + + if ( PredictTrajectory( entPhys->GetOrigin() + offsetVec, enemyEyePos, speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), MAX_WORLD_SIZE, NULL, enemyEnt, ai_debugTrajectory.GetBool() ? 4000 : 0, vel ) ) { + idThread::ReturnEntity( ent ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idAI_Vagary::Event_ThrowObjectAtEnemy +================ +*/ +void idAI_Vagary::Event_ThrowObjectAtEnemy( idEntity *ent, float speed ) { + idVec3 vel; + idEntity *enemyEnt; + idPhysics *entPhys; + + entPhys = ent->GetPhysics(); + enemyEnt = enemy.GetEntity(); + if ( !enemyEnt ) { + vel = ( viewAxis[ 0 ] * physicsObj.GetGravityAxis() ) * speed; + } else { + PredictTrajectory( entPhys->GetOrigin(), lastVisibleEnemyPos + lastVisibleEnemyEyeOffset, speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), MAX_WORLD_SIZE, NULL, enemyEnt, ai_debugTrajectory.GetBool() ? 4000 : 0, vel ); + vel *= speed; + } + + entPhys->SetLinearVelocity( vel ); + + if ( ent->IsType( idMoveable::Type ) ) { + idMoveable *ment = static_cast( ent ); + ment->EnableDamage( true, 2.5f ); + } +} diff --git a/neo/d3xp/ai/AI_events.cpp b/neo/d3xp/ai/AI_events.cpp new file mode 100644 index 00000000..c9e941a4 --- /dev/null +++ b/neo/d3xp/ai/AI_events.cpp @@ -0,0 +1,2986 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +/*********************************************************************** + + AI Events + +***********************************************************************/ + +const idEventDef AI_FindEnemy( "findEnemy", "d", 'e' ); +const idEventDef AI_FindEnemyAI( "findEnemyAI", "d", 'e' ); +const idEventDef AI_FindEnemyInCombatNodes( "findEnemyInCombatNodes", NULL, 'e' ); +const idEventDef AI_ClosestReachableEnemyOfEntity( "closestReachableEnemyOfEntity", "E", 'e' ); +const idEventDef AI_HeardSound( "heardSound", "d", 'e' ); +const idEventDef AI_SetEnemy( "setEnemy", "E" ); +const idEventDef AI_ClearEnemy( "clearEnemy" ); +const idEventDef AI_MuzzleFlash( "muzzleFlash", "s" ); +const idEventDef AI_CreateMissile( "createMissile", "s", 'e' ); +const idEventDef AI_AttackMissile( "attackMissile", "s", 'e' ); +const idEventDef AI_FireMissileAtTarget( "fireMissileAtTarget", "ss", 'e' ); +const idEventDef AI_LaunchMissile( "launchMissile", "vv", 'e' ); +const idEventDef AI_LaunchHomingMissile( "launchHomingMissile" ); +const idEventDef AI_SetHomingMissileGoal( "setHomingMissileGoal" ); +const idEventDef AI_LaunchProjectile( "launchProjectile", "s" ); +const idEventDef AI_AttackMelee( "attackMelee", "s", 'd' ); +const idEventDef AI_DirectDamage( "directDamage", "es" ); +const idEventDef AI_RadiusDamageFromJoint( "radiusDamageFromJoint", "ss" ); +const idEventDef AI_BeginAttack( "attackBegin", "s" ); +const idEventDef AI_EndAttack( "attackEnd" ); +const idEventDef AI_MeleeAttackToJoint( "meleeAttackToJoint", "ss", 'd' ); +const idEventDef AI_RandomPath( "randomPath", NULL, 'e' ); +const idEventDef AI_CanBecomeSolid( "canBecomeSolid", NULL, 'f' ); +const idEventDef AI_BecomeSolid( "becomeSolid" ); +const idEventDef AI_BecomeRagdoll( "becomeRagdoll", NULL, 'd' ); +const idEventDef AI_StopRagdoll( "stopRagdoll" ); +const idEventDef AI_SetHealth( "setHealth", "f" ); +const idEventDef AI_GetHealth( "getHealth", NULL, 'f' ); +const idEventDef AI_AllowDamage( "allowDamage" ); +const idEventDef AI_IgnoreDamage( "ignoreDamage" ); +const idEventDef AI_GetCurrentYaw( "getCurrentYaw", NULL, 'f' ); +const idEventDef AI_TurnTo( "turnTo", "f" ); +const idEventDef AI_TurnToPos( "turnToPos", "v" ); +const idEventDef AI_TurnToEntity( "turnToEntity", "E" ); +const idEventDef AI_MoveStatus( "moveStatus", NULL, 'd' ); +const idEventDef AI_StopMove( "stopMove" ); +const idEventDef AI_MoveToCover( "moveToCover" ); +const idEventDef AI_MoveToEnemy( "moveToEnemy" ); +const idEventDef AI_MoveToEnemyHeight( "moveToEnemyHeight" ); +const idEventDef AI_MoveOutOfRange( "moveOutOfRange", "ef" ); +const idEventDef AI_MoveToAttackPosition( "moveToAttackPosition", "es" ); +const idEventDef AI_Wander( "wander" ); +const idEventDef AI_MoveToEntity( "moveToEntity", "e" ); +const idEventDef AI_MoveToPosition( "moveToPosition", "v" ); +const idEventDef AI_SlideTo( "slideTo", "vf" ); +const idEventDef AI_FacingIdeal( "facingIdeal", NULL, 'd' ); +const idEventDef AI_FaceEnemy( "faceEnemy" ); +const idEventDef AI_FaceEntity( "faceEntity", "E" ); +const idEventDef AI_GetCombatNode( "getCombatNode", NULL, 'e' ); +const idEventDef AI_EnemyInCombatCone( "enemyInCombatCone", "Ed", 'd' ); +const idEventDef AI_WaitMove( "waitMove" ); +const idEventDef AI_GetJumpVelocity( "getJumpVelocity", "vff", 'v' ); +const idEventDef AI_GetTrajectoryToPlayer( "getTrajectoryToPlayer", NULL, 'v' ); +const idEventDef AI_EntityInAttackCone( "entityInAttackCone", "E", 'd' ); +const idEventDef AI_CanSeeEntity( "canSee", "E", 'd' ); +const idEventDef AI_SetTalkTarget( "setTalkTarget", "E" ); +const idEventDef AI_GetTalkTarget( "getTalkTarget", NULL, 'e' ); +const idEventDef AI_SetTalkState( "setTalkState", "d" ); +const idEventDef AI_EnemyRange( "enemyRange", NULL, 'f' ); +const idEventDef AI_EnemyRange2D( "enemyRange2D", NULL, 'f' ); +const idEventDef AI_GetEnemy( "getEnemy", NULL, 'e' ); +const idEventDef AI_GetEnemyPos( "getEnemyPos", NULL, 'v' ); +const idEventDef AI_GetEnemyEyePos( "getEnemyEyePos", NULL, 'v' ); +const idEventDef AI_PredictEnemyPos( "predictEnemyPos", "f", 'v' ); +const idEventDef AI_CanHitEnemy( "canHitEnemy", NULL, 'd' ); +const idEventDef AI_CanHitEnemyFromAnim( "canHitEnemyFromAnim", "s", 'd' ); +const idEventDef AI_CanHitEnemyFromJoint( "canHitEnemyFromJoint", "s", 'd' ); +const idEventDef AI_EnemyPositionValid( "enemyPositionValid", NULL, 'd' ); +const idEventDef AI_ChargeAttack( "chargeAttack", "s" ); +const idEventDef AI_TestChargeAttack( "testChargeAttack", NULL, 'f' ); +const idEventDef AI_TestMoveToPosition( "testMoveToPosition", "v", 'd' ); +const idEventDef AI_TestAnimMoveTowardEnemy( "testAnimMoveTowardEnemy", "s", 'd' ); +const idEventDef AI_TestAnimMove( "testAnimMove", "s", 'd' ); +const idEventDef AI_TestMeleeAttack( "testMeleeAttack", NULL, 'd' ); +const idEventDef AI_TestAnimAttack( "testAnimAttack", "s", 'd' ); +const idEventDef AI_Burn( "burn" ); +const idEventDef AI_ClearBurn( "clearBurn" ); +const idEventDef AI_PreBurn( "preBurn" ); +const idEventDef AI_SetSmokeVisibility( "setSmokeVisibility", "dd" ); +const idEventDef AI_NumSmokeEmitters( "numSmokeEmitters", NULL, 'd' ); +const idEventDef AI_WaitAction( "waitAction", "s" ); +const idEventDef AI_StopThinking( "stopThinking" ); +const idEventDef AI_GetTurnDelta( "getTurnDelta", NULL, 'f' ); +const idEventDef AI_GetMoveType( "getMoveType", NULL, 'd' ); +const idEventDef AI_SetMoveType( "setMoveType", "d" ); +const idEventDef AI_SaveMove( "saveMove" ); +const idEventDef AI_RestoreMove( "restoreMove" ); +const idEventDef AI_AllowMovement( "allowMovement", "f" ); +const idEventDef AI_JumpFrame( "" ); +const idEventDef AI_EnableClip( "enableClip" ); +const idEventDef AI_DisableClip( "disableClip" ); +const idEventDef AI_EnableGravity( "enableGravity" ); +const idEventDef AI_DisableGravity( "disableGravity" ); +const idEventDef AI_EnableAFPush( "enableAFPush" ); +const idEventDef AI_DisableAFPush( "disableAFPush" ); +const idEventDef AI_SetFlySpeed( "setFlySpeed", "f" ); +const idEventDef AI_SetFlyOffset( "setFlyOffset", "d" ); +const idEventDef AI_ClearFlyOffset( "clearFlyOffset" ); +const idEventDef AI_GetClosestHiddenTarget( "getClosestHiddenTarget", "s", 'e' ); +const idEventDef AI_GetRandomTarget( "getRandomTarget", "s", 'e' ); +const idEventDef AI_TravelDistanceToPoint( "travelDistanceToPoint", "v", 'f' ); +const idEventDef AI_TravelDistanceToEntity( "travelDistanceToEntity", "e", 'f' ); +const idEventDef AI_TravelDistanceBetweenPoints( "travelDistanceBetweenPoints", "vv", 'f' ); +const idEventDef AI_TravelDistanceBetweenEntities( "travelDistanceBetweenEntities", "ee", 'f' ); +const idEventDef AI_LookAtEntity( "lookAt", "Ef" ); +const idEventDef AI_LookAtEnemy( "lookAtEnemy", "f" ); +const idEventDef AI_SetJointMod( "setBoneMod", "d" ); +const idEventDef AI_ThrowMoveable( "throwMoveable" ); +const idEventDef AI_ThrowAF( "throwAF" ); +const idEventDef AI_RealKill( "" ); +const idEventDef AI_Kill( "kill" ); +const idEventDef AI_WakeOnFlashlight( "wakeOnFlashlight", "d" ); +const idEventDef AI_LocateEnemy( "locateEnemy" ); +const idEventDef AI_KickObstacles( "kickObstacles", "Ef" ); +const idEventDef AI_GetObstacle( "getObstacle", NULL, 'e' ); +const idEventDef AI_PushPointIntoAAS( "pushPointIntoAAS", "v", 'v' ); +const idEventDef AI_GetTurnRate( "getTurnRate", NULL, 'f' ); +const idEventDef AI_SetTurnRate( "setTurnRate", "f" ); +const idEventDef AI_AnimTurn( "animTurn", "f" ); +const idEventDef AI_AllowHiddenMovement( "allowHiddenMovement", "d" ); +const idEventDef AI_TriggerParticles( "triggerParticles", "s" ); +const idEventDef AI_FindActorsInBounds( "findActorsInBounds", "vv", 'e' ); +const idEventDef AI_CanReachPosition( "canReachPosition", "v", 'd' ); +const idEventDef AI_CanReachEntity( "canReachEntity", "E", 'd' ); +const idEventDef AI_CanReachEnemy( "canReachEnemy", NULL, 'd' ); +const idEventDef AI_GetReachableEntityPosition( "getReachableEntityPosition", "e", 'v' ); +const idEventDef AI_MoveToPositionDirect( "moveToPositionDirect", "v" ); +const idEventDef AI_AvoidObstacles( "avoidObstacles", "d" ); +const idEventDef AI_TriggerFX( "triggerFX", "ss" ); +const idEventDef AI_StartEmitter( "startEmitter", "sss", 'e' ); +const idEventDef AI_GetEmitter( "getEmitter", "s", 'e' ); +const idEventDef AI_StopEmitter( "stopEmitter", "s" ); + + + +CLASS_DECLARATION( idActor, idAI ) + EVENT( EV_Activate, idAI::Event_Activate ) + EVENT( EV_Touch, idAI::Event_Touch ) + EVENT( AI_FindEnemy, idAI::Event_FindEnemy ) + EVENT( AI_FindEnemyAI, idAI::Event_FindEnemyAI ) + EVENT( AI_FindEnemyInCombatNodes, idAI::Event_FindEnemyInCombatNodes ) + EVENT( AI_ClosestReachableEnemyOfEntity, idAI::Event_ClosestReachableEnemyOfEntity ) + EVENT( AI_HeardSound, idAI::Event_HeardSound ) + EVENT( AI_SetEnemy, idAI::Event_SetEnemy ) + EVENT( AI_ClearEnemy, idAI::Event_ClearEnemy ) + EVENT( AI_MuzzleFlash, idAI::Event_MuzzleFlash ) + EVENT( AI_CreateMissile, idAI::Event_CreateMissile ) + EVENT( AI_AttackMissile, idAI::Event_AttackMissile ) + EVENT( AI_FireMissileAtTarget, idAI::Event_FireMissileAtTarget ) + EVENT( AI_LaunchMissile, idAI::Event_LaunchMissile ) + EVENT( AI_LaunchHomingMissile, idAI::Event_LaunchHomingMissile ) + EVENT( AI_SetHomingMissileGoal, idAI::Event_SetHomingMissileGoal ) + EVENT( AI_LaunchProjectile, idAI::Event_LaunchProjectile ) + EVENT( AI_AttackMelee, idAI::Event_AttackMelee ) + EVENT( AI_DirectDamage, idAI::Event_DirectDamage ) + EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint ) + EVENT( AI_BeginAttack, idAI::Event_BeginAttack ) + EVENT( AI_EndAttack, idAI::Event_EndAttack ) + EVENT( AI_MeleeAttackToJoint, idAI::Event_MeleeAttackToJoint ) + EVENT( AI_RandomPath, idAI::Event_RandomPath ) + EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid ) + EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid ) + EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid ) + EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll ) + EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll ) + EVENT( AI_SetHealth, idAI::Event_SetHealth ) + EVENT( AI_GetHealth, idAI::Event_GetHealth ) + EVENT( AI_AllowDamage, idAI::Event_AllowDamage ) + EVENT( AI_IgnoreDamage, idAI::Event_IgnoreDamage ) + EVENT( AI_GetCurrentYaw, idAI::Event_GetCurrentYaw ) + EVENT( AI_TurnTo, idAI::Event_TurnTo ) + EVENT( AI_TurnToPos, idAI::Event_TurnToPos ) + EVENT( AI_TurnToEntity, idAI::Event_TurnToEntity ) + EVENT( AI_MoveStatus, idAI::Event_MoveStatus ) + EVENT( AI_StopMove, idAI::Event_StopMove ) + EVENT( AI_MoveToCover, idAI::Event_MoveToCover ) + EVENT( AI_MoveToEnemy, idAI::Event_MoveToEnemy ) + EVENT( AI_MoveToEnemyHeight, idAI::Event_MoveToEnemyHeight ) + EVENT( AI_MoveOutOfRange, idAI::Event_MoveOutOfRange ) + EVENT( AI_MoveToAttackPosition, idAI::Event_MoveToAttackPosition ) + EVENT( AI_Wander, idAI::Event_Wander ) + EVENT( AI_MoveToEntity, idAI::Event_MoveToEntity ) + EVENT( AI_MoveToPosition, idAI::Event_MoveToPosition ) + EVENT( AI_SlideTo, idAI::Event_SlideTo ) + EVENT( AI_FacingIdeal, idAI::Event_FacingIdeal ) + EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy ) + EVENT( AI_FaceEntity, idAI::Event_FaceEntity ) + EVENT( AI_WaitAction, idAI::Event_WaitAction ) + EVENT( AI_GetCombatNode, idAI::Event_GetCombatNode ) + EVENT( AI_EnemyInCombatCone, idAI::Event_EnemyInCombatCone ) + EVENT( AI_WaitMove, idAI::Event_WaitMove ) + EVENT( AI_GetJumpVelocity, idAI::Event_GetJumpVelocity ) + EVENT( AI_GetTrajectoryToPlayer, idAI::Event_GetTrajectoryToPlayer ) + EVENT( AI_EntityInAttackCone, idAI::Event_EntityInAttackCone ) + EVENT( AI_CanSeeEntity, idAI::Event_CanSeeEntity ) + EVENT( AI_SetTalkTarget, idAI::Event_SetTalkTarget ) + EVENT( AI_GetTalkTarget, idAI::Event_GetTalkTarget ) + EVENT( AI_SetTalkState, idAI::Event_SetTalkState ) + EVENT( AI_EnemyRange, idAI::Event_EnemyRange ) + EVENT( AI_EnemyRange2D, idAI::Event_EnemyRange2D ) + EVENT( AI_GetEnemy, idAI::Event_GetEnemy ) + EVENT( AI_GetEnemyPos, idAI::Event_GetEnemyPos ) + EVENT( AI_GetEnemyEyePos, idAI::Event_GetEnemyEyePos ) + EVENT( AI_PredictEnemyPos, idAI::Event_PredictEnemyPos ) + EVENT( AI_CanHitEnemy, idAI::Event_CanHitEnemy ) + EVENT( AI_CanHitEnemyFromAnim, idAI::Event_CanHitEnemyFromAnim ) + EVENT( AI_CanHitEnemyFromJoint, idAI::Event_CanHitEnemyFromJoint ) + EVENT( AI_EnemyPositionValid, idAI::Event_EnemyPositionValid ) + EVENT( AI_ChargeAttack, idAI::Event_ChargeAttack ) + EVENT( AI_TestChargeAttack, idAI::Event_TestChargeAttack ) + EVENT( AI_TestAnimMoveTowardEnemy, idAI::Event_TestAnimMoveTowardEnemy ) + EVENT( AI_TestAnimMove, idAI::Event_TestAnimMove ) + EVENT( AI_TestMoveToPosition, idAI::Event_TestMoveToPosition ) + EVENT( AI_TestMeleeAttack, idAI::Event_TestMeleeAttack ) + EVENT( AI_TestAnimAttack, idAI::Event_TestAnimAttack ) + EVENT( AI_Burn, idAI::Event_Burn ) + EVENT( AI_PreBurn, idAI::Event_PreBurn ) + EVENT( AI_SetSmokeVisibility, idAI::Event_SetSmokeVisibility ) + EVENT( AI_NumSmokeEmitters, idAI::Event_NumSmokeEmitters ) + EVENT( AI_ClearBurn, idAI::Event_ClearBurn ) + EVENT( AI_StopThinking, idAI::Event_StopThinking ) + EVENT( AI_GetTurnDelta, idAI::Event_GetTurnDelta ) + EVENT( AI_GetMoveType, idAI::Event_GetMoveType ) + EVENT( AI_SetMoveType, idAI::Event_SetMoveType ) + EVENT( AI_SaveMove, idAI::Event_SaveMove ) + EVENT( AI_RestoreMove, idAI::Event_RestoreMove ) + EVENT( AI_AllowMovement, idAI::Event_AllowMovement ) + EVENT( AI_JumpFrame, idAI::Event_JumpFrame ) + EVENT( AI_EnableClip, idAI::Event_EnableClip ) + EVENT( AI_DisableClip, idAI::Event_DisableClip ) + EVENT( AI_EnableGravity, idAI::Event_EnableGravity ) + EVENT( AI_DisableGravity, idAI::Event_DisableGravity ) + EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush ) + EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush ) + EVENT( AI_SetFlySpeed, idAI::Event_SetFlySpeed ) + EVENT( AI_SetFlyOffset, idAI::Event_SetFlyOffset ) + EVENT( AI_ClearFlyOffset, idAI::Event_ClearFlyOffset ) + EVENT( AI_GetClosestHiddenTarget, idAI::Event_GetClosestHiddenTarget ) + EVENT( AI_GetRandomTarget, idAI::Event_GetRandomTarget ) + EVENT( AI_TravelDistanceToPoint, idAI::Event_TravelDistanceToPoint ) + EVENT( AI_TravelDistanceToEntity, idAI::Event_TravelDistanceToEntity ) + EVENT( AI_TravelDistanceBetweenPoints, idAI::Event_TravelDistanceBetweenPoints ) + EVENT( AI_TravelDistanceBetweenEntities, idAI::Event_TravelDistanceBetweenEntities ) + EVENT( AI_LookAtEntity, idAI::Event_LookAtEntity ) + EVENT( AI_LookAtEnemy, idAI::Event_LookAtEnemy ) + EVENT( AI_SetJointMod, idAI::Event_SetJointMod ) + EVENT( AI_ThrowMoveable, idAI::Event_ThrowMoveable ) + EVENT( AI_ThrowAF, idAI::Event_ThrowAF ) + EVENT( EV_GetAngles, idAI::Event_GetAngles ) + EVENT( EV_SetAngles, idAI::Event_SetAngles ) + EVENT( AI_RealKill, idAI::Event_RealKill ) + EVENT( AI_Kill, idAI::Event_Kill ) + EVENT( AI_WakeOnFlashlight, idAI::Event_WakeOnFlashlight ) + EVENT( AI_LocateEnemy, idAI::Event_LocateEnemy ) + EVENT( AI_KickObstacles, idAI::Event_KickObstacles ) + EVENT( AI_GetObstacle, idAI::Event_GetObstacle ) + EVENT( AI_PushPointIntoAAS, idAI::Event_PushPointIntoAAS ) + EVENT( AI_GetTurnRate, idAI::Event_GetTurnRate ) + EVENT( AI_SetTurnRate, idAI::Event_SetTurnRate ) + EVENT( AI_AnimTurn, idAI::Event_AnimTurn ) + EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement ) + EVENT( AI_TriggerParticles, idAI::Event_TriggerParticles ) + EVENT( AI_FindActorsInBounds, idAI::Event_FindActorsInBounds ) + EVENT( AI_CanReachPosition, idAI::Event_CanReachPosition ) + EVENT( AI_CanReachEntity, idAI::Event_CanReachEntity ) + EVENT( AI_CanReachEnemy, idAI::Event_CanReachEnemy ) + EVENT( AI_GetReachableEntityPosition, idAI::Event_GetReachableEntityPosition ) + EVENT( AI_MoveToPositionDirect, idAI::Event_MoveToPositionDirect ) + EVENT( AI_AvoidObstacles, idAI::Event_AvoidObstacles ) + EVENT( AI_TriggerFX, idAI::Event_TriggerFX ) + EVENT( AI_StartEmitter, idAI::Event_StartEmitter ) + EVENT( AI_GetEmitter, idAI::Event_GetEmitter ) + EVENT( AI_StopEmitter, idAI::Event_StopEmitter ) +END_CLASS + +/* +===================== +idAI::Event_Activate +===================== +*/ +void idAI::Event_Activate( idEntity *activator ) { + Activate( activator ); +} + +/* +===================== +idAI::Event_Touch +===================== +*/ +void idAI::Event_Touch( idEntity *other, trace_t *trace ) { + if ( !enemy.GetEntity() && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + } + AI_PUSHED = true; +} + +/* +===================== +idAI::Event_FindEnemy +===================== +*/ +void idAI::Event_FindEnemy( int useFOV ) { + int i; + idEntity *ent; + idActor *actor; + + if ( gameLocal.InPlayerPVS( this ) ) { + for ( i = 0; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idActor::Type ) ) { + continue; + } + + actor = static_cast( ent ); + if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { + continue; + } + + if ( CanSee( actor, useFOV != 0 ) ) { + idThread::ReturnEntity( actor ); + return; + } + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_FindEnemyAI +===================== +*/ +void idAI::Event_FindEnemyAI( int useFOV ) { + idEntity *ent; + idActor *actor; + idActor *bestEnemy; + float bestDist; + float dist; + idVec3 delta; + pvsHandle_t pvs; + + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + + bestDist = idMath::INFINITY; + bestEnemy = NULL; + for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( idActor::Type ) ) { + continue; + } + + actor = static_cast( ent ); + if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { + continue; + } + + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); + dist = delta.LengthSqr(); + if ( ( dist < bestDist ) && CanSee( actor, useFOV != 0 ) ) { + bestDist = dist; + bestEnemy = actor; + } + } + + gameLocal.pvs.FreeCurrentPVS( pvs ); + idThread::ReturnEntity( bestEnemy ); +} + +/* +===================== +idAI::Event_FindEnemyInCombatNodes +===================== +*/ +void idAI::Event_FindEnemyInCombatNodes() { + int i, j; + idCombatNode *node; + idEntity *ent; + idEntity *targetEnt; + idActor *actor; + + if ( !gameLocal.InPlayerPVS( this ) ) { + // don't locate the player when we're not in his PVS + idThread::ReturnEntity( NULL ); + return; + } + + for ( i = 0; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idActor::Type ) ) { + continue; + } + + actor = static_cast( ent ); + if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { + continue; + } + + for( j = 0; j < targets.Num(); j++ ) { + targetEnt = targets[ j ].GetEntity(); + if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { + continue; + } + + node = static_cast( targetEnt ); + if ( !node->IsDisabled() && node->EntityInView( actor, actor->GetPhysics()->GetOrigin() ) ) { + idThread::ReturnEntity( actor ); + return; + } + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_ClosestReachableEnemyOfEntity +===================== +*/ +void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) { + idActor *actor; + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + int areaNum; + int enemyAreaNum; + aasPath_t path; + + if ( !team_mate->IsType( idActor::Type ) ) { + gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() ); + } + + actor = static_cast( team_mate ); + + const idVec3 &origin = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( origin ); + + bestDistSquared = idMath::INFINITY; + bestEnt = NULL; + for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - origin; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin(); + enemyAreaNum = PointReachableAreaNum( enemyPos ); + if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) { + bestEnt = ent; + bestDistSquared = distSquared; + } + } + } + + idThread::ReturnEntity( bestEnt ); +} + +/* +===================== +idAI::Event_HeardSound +===================== +*/ +void idAI::Event_HeardSound( int ignore_team ) { + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertEntity(); + if ( actor != NULL && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) { + idVec3 pos = actor->GetPhysics()->GetOrigin(); + idVec3 org = physicsObj.GetOrigin(); + float dist = ( pos - org ).LengthSqr(); + if ( dist < Square( AI_HEARING_RANGE ) ) { + idThread::ReturnEntity( actor ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_SetEnemy +===================== +*/ +void idAI::Event_SetEnemy( idEntity *ent ) { + if ( !ent ) { + ClearEnemy(); + } else if ( !ent->IsType( idActor::Type ) ) { + gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", ent->name.c_str() ); + } else { + SetEnemy( static_cast( ent ) ); + } +} + +/* +===================== +idAI::Event_ClearEnemy +===================== +*/ +void idAI::Event_ClearEnemy() { + ClearEnemy(); +} + +/* +===================== +idAI::Event_MuzzleFlash +===================== +*/ +void idAI::Event_MuzzleFlash( const char *jointname ) { + idVec3 muzzle; + idMat3 axis; + + GetMuzzle( jointname, muzzle, axis ); + TriggerWeaponEffects( muzzle ); +} + +/* +===================== +idAI::Event_CreateMissile +===================== +*/ +void idAI::Event_CreateMissile( const char *jointname ) { + idVec3 muzzle; + idMat3 axis; + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return idThread::ReturnEntity( NULL ); + } + + GetMuzzle( jointname, muzzle, axis ); + CreateProjectile( muzzle, viewAxis[ 0 ] * physicsObj.GetGravityAxis() ); + if ( projectile.GetEntity() ) { + if ( !jointname || !jointname[ 0 ] ) { + projectile.GetEntity()->Bind( this, true ); + } else { + projectile.GetEntity()->BindToJoint( this, jointname, true ); + } + } + idThread::ReturnEntity( projectile.GetEntity() ); +} + +/* +===================== +idAI::Event_AttackMissile +===================== +*/ +void idAI::Event_AttackMissile( const char *jointname ) { + idProjectile *proj; + + proj = LaunchProjectile( jointname, enemy.GetEntity(), true ); + idThread::ReturnEntity( proj ); +} + +/* +===================== +idAI::Event_FireMissileAtTarget +===================== +*/ +void idAI::Event_FireMissileAtTarget( const char *jointname, const char *targetname ) { + idEntity *aent; + idProjectile *proj; + + aent = gameLocal.FindEntity( targetname ); + if ( !aent ) { + gameLocal.Warning( "Entity '%s' not found for 'fireMissileAtTarget'", targetname ); + } + + proj = LaunchProjectile( jointname, aent, false ); + idThread::ReturnEntity( proj ); +} + +/* +===================== +idAI::Event_LaunchMissile +===================== +*/ +void idAI::Event_LaunchMissile( const idVec3 &org, const idAngles &ang ) { + idVec3 start; + trace_t tr; + idBounds projBounds; + const idClipModel *projClip; + idMat3 axis; + float distance; + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + idThread::ReturnEntity( NULL ); + return; + } + + axis = ang.ToMat3(); + if ( !projectile.GetEntity() ) { + CreateProjectile( org, axis[ 0 ] ); + } + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + + gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); + + // launch the projectile + idThread::ReturnEntity( projectile.GetEntity() ); + projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); + projectile = NULL; + + TriggerWeaponEffects( tr.endpos ); + + lastAttackTime = gameLocal.time; +} + + +/* +===================== +idAI::Event_LaunchProjectile +===================== +*/ +void idAI::Event_LaunchProjectile( const char *entityDefName ) { + idVec3 muzzle, start, dir; + const idDict *projDef; + idMat3 axis; + const idClipModel *projClip; + idBounds projBounds; + trace_t tr; + idEntity *ent; + const char *clsname; + float distance; + idProjectile *proj = NULL; + + projDef = gameLocal.FindEntityDefDict( entityDefName ); + + gameLocal.SpawnEntityDef( *projDef, &ent, false ); + if ( ent == NULL ) { + clsname = projectileDef->GetString( "classname" ); + gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); + return; + } + + if ( !ent->IsType( idProjectile::Type ) ) { + clsname = ent->GetClassname(); + gameLocal.Error( "'%s' is not an idProjectile", clsname ); + } + proj = ( idProjectile * )ent; + + GetMuzzle( "pistol", muzzle, axis ); + proj->Create( this, muzzle, axis[0] ); + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = proj->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + GetAimDir( muzzle, enemy.GetEntity(), this, dir ); + + proj->Launch( muzzle, dir, vec3_origin ); + + TriggerWeaponEffects( muzzle ); +} + + + +/* +===================== +idAI::Event_AttackMelee +===================== +*/ +void idAI::Event_AttackMelee( const char *meleeDefName ) { + bool hit; + + hit = AttackMelee( meleeDefName ); + idThread::ReturnInt( hit ); +} + +/* +===================== +idAI::Event_DirectDamage +===================== +*/ +void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { + DirectDamage( damageDefName, damageTarget ); +} + +/* +===================== +idAI::Event_RadiusDamageFromJoint +===================== +*/ +void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { + jointHandle_t joint; + idVec3 org; + idMat3 axis; + + if ( !jointname || !jointname[ 0 ] ) { + org = physicsObj.GetOrigin(); + } else { + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + GetJointWorldTransform( joint, gameLocal.time, org, axis ); + } + + gameLocal.RadiusDamage( org, this, this, this, this, damageDefName ); +} + +/* +===================== +idAI::Event_RandomPath +===================== +*/ +void idAI::Event_RandomPath() { + idPathCorner *path; + + path = idPathCorner::RandomPath( this, NULL ); + idThread::ReturnEntity( path ); +} + +/* +===================== +idAI::Event_BeginAttack +===================== +*/ +void idAI::Event_BeginAttack( const char *name ) { + BeginAttack( name ); +} + +/* +===================== +idAI::Event_EndAttack +===================== +*/ +void idAI::Event_EndAttack() { + EndAttack(); +} + +/* +===================== +idAI::Event_MeleeAttackToJoint +===================== +*/ +void idAI::Event_MeleeAttackToJoint( const char *jointname, const char *meleeDefName ) { + jointHandle_t joint; + idVec3 start; + idVec3 end; + idMat3 axis; + trace_t trace; + idEntity *hitEnt; + + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + animator.GetJointTransform( joint, gameLocal.time, end, axis ); + end = physicsObj.GetOrigin() + ( end + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); + start = GetEyePosition(); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, start, end, 1 ); + } + + gameLocal.clip.TranslationEntities( trace, start, end, NULL, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); + if ( trace.fraction < 1.0f ) { + hitEnt = gameLocal.GetTraceEntity( trace ); + if ( hitEnt != NULL && hitEnt->IsType( idActor::Type ) ) { + DirectDamage( meleeDefName, hitEnt ); + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +/* +===================== +idAI::Event_CanBecomeSolid +===================== +*/ +void idAI::Event_CanBecomeSolid() { + int i; + int num; + bool returnValue = true; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + + num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + + if ( spawnClearMoveables && hit->IsType( idMoveable::Type ) || hit->IsType( idBarrel::Type ) || hit->IsType( idExplodingBarrel::Type ) ) { + idVec3 push; + push = hit->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + push.z = 30.f; + push.NormalizeFast(); + if ( (idMath::Fabs(push.x) < 0.15f) && (idMath::Fabs(push.y) < 0.15f) ) { + push.x = 10.f; push.y = 10.f; push.z = 15.f; + push.NormalizeFast(); + } + push *= 300.f; + hit->GetPhysics()->SetLinearVelocity( push ); + } + + if ( physicsObj.ClipContents( cm ) ) { + returnValue = false; + } + } + + idThread::ReturnFloat( returnValue ); +} + +/* +===================== +idAI::Event_BecomeSolid +===================== +*/ +void idAI::Event_BecomeSolid() { + physicsObj.EnableClip(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.GetClipModel()->Link( gameLocal.clip ); + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); +} + +/* +===================== +idAI::Event_BecomeNonSolid +===================== +*/ +void idAI::Event_BecomeNonSolid() { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); +} + +/* +===================== +idAI::Event_BecomeRagdoll +===================== +*/ +void idAI::Event_BecomeRagdoll() { + bool result; + + result = StartRagdoll(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_StopRagdoll +===================== +*/ +void idAI::Event_StopRagdoll() { + StopRagdoll(); + + // set back the monster physics + SetPhysics( &physicsObj ); +} + +/* +===================== +idAI::Event_SetHealth +===================== +*/ +void idAI::Event_SetHealth( float newHealth ) { + health = newHealth; + fl.takedamage = true; + if ( health > 0 ) { + AI_DEAD = false; + } else { + AI_DEAD = true; + } +} + +/* +===================== +idAI::Event_GetHealth +===================== +*/ +void idAI::Event_GetHealth() { + idThread::ReturnFloat( health ); +} + +/* +===================== +idAI::Event_AllowDamage +===================== +*/ +void idAI::Event_AllowDamage() { + fl.takedamage = true; +} + +/* +===================== +idAI::Event_IgnoreDamage +===================== +*/ +void idAI::Event_IgnoreDamage() { + fl.takedamage = false; +} + +/* +===================== +idAI::Event_GetCurrentYaw +===================== +*/ +void idAI::Event_GetCurrentYaw() { + idThread::ReturnFloat( current_yaw ); +} + +/* +===================== +idAI::Event_TurnTo +===================== +*/ +void idAI::Event_TurnTo( float angle ) { + TurnToward( angle ); +} + +/* +===================== +idAI::Event_TurnToPos +===================== +*/ +void idAI::Event_TurnToPos( const idVec3 &pos ) { + TurnToward( pos ); +} + +/* +===================== +idAI::Event_TurnToEntity +===================== +*/ +void idAI::Event_TurnToEntity( idEntity *ent ) { + if ( ent ) { + TurnToward( ent->GetPhysics()->GetOrigin() ); + } +} + +/* +===================== +idAI::Event_MoveStatus +===================== +*/ +void idAI::Event_MoveStatus() { + idThread::ReturnInt( move.moveStatus ); +} + +/* +===================== +idAI::Event_StopMove +===================== +*/ +void idAI::Event_StopMove() { + StopMove( MOVE_STATUS_DONE ); +} + +/* +===================== +idAI::Event_MoveToCover +===================== +*/ +void idAI::Event_MoveToCover() { + idActor *enemyEnt = enemy.GetEntity(); + + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + if ( !enemyEnt || !MoveToCover( enemyEnt, lastVisibleEnemyPos ) ) { + return; + } +} + +/* +===================== +idAI::Event_MoveToEnemy +===================== +*/ +void idAI::Event_MoveToEnemy() { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + if ( !enemy.GetEntity() || !MoveToEnemy() ) { + return; + } +} + +/* +===================== +idAI::Event_MoveToEnemyHeight +===================== +*/ +void idAI::Event_MoveToEnemyHeight() { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + MoveToEnemyHeight(); +} + +/* +===================== +idAI::Event_MoveOutOfRange +===================== +*/ +void idAI::Event_MoveOutOfRange( idEntity *entity, float range ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + MoveOutOfRange( entity, range ); +} + +/* +===================== +idAI::Event_MoveToAttackPosition +===================== +*/ +void idAI::Event_MoveToAttackPosition( idEntity *entity, const char *attack_anim ) { + int anim; + + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + + anim = GetAnim( ANIMCHANNEL_LEGS, attack_anim ); + if ( !anim ) { + gameLocal.Error( "Unknown anim '%s'", attack_anim ); + } + + MoveToAttackPosition( entity, anim ); +} + +/* +===================== +idAI::Event_MoveToEntity +===================== +*/ +void idAI::Event_MoveToEntity( idEntity *ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + if ( ent ) { + MoveToEntity( ent ); + } +} + +/* +===================== +idAI::Event_MoveToPosition +===================== +*/ +void idAI::Event_MoveToPosition( const idVec3 &pos ) { + StopMove( MOVE_STATUS_DONE ); + MoveToPosition( pos ); +} + +/* +===================== +idAI::Event_SlideTo +===================== +*/ +void idAI::Event_SlideTo( const idVec3 &pos, float time ) { + SlideToPosition( pos, time ); +} +/* +===================== +idAI::Event_Wander +===================== +*/ +void idAI::Event_Wander() { + WanderAround(); +} + +/* +===================== +idAI::Event_FacingIdeal +===================== +*/ +void idAI::Event_FacingIdeal() { + bool facing = FacingIdeal(); + idThread::ReturnInt( facing ); +} + +/* +===================== +idAI::Event_FaceEnemy +===================== +*/ +void idAI::Event_FaceEnemy() { + FaceEnemy(); +} + +/* +===================== +idAI::Event_FaceEntity +===================== +*/ +void idAI::Event_FaceEntity( idEntity *ent ) { + FaceEntity( ent ); +} + +/* +===================== +idAI::Event_WaitAction +===================== +*/ +void idAI::Event_WaitAction( const char *waitForState ) { + if ( idThread::BeginMultiFrameEvent( this, &AI_WaitAction ) ) { + SetWaitState( waitForState ); + } + + if ( !WaitState() ) { + idThread::EndMultiFrameEvent( this, &AI_WaitAction ); + } +} + +/* +===================== +idAI::Event_GetCombatNode +===================== +*/ +void idAI::Event_GetCombatNode() { + int i; + float dist; + idEntity *targetEnt; + idCombatNode *node; + float bestDist; + idCombatNode *bestNode; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !targets.Num() ) { + // no combat nodes + idThread::ReturnEntity( NULL ); + return; + } + + if ( !enemyEnt || !EnemyPositionValid() ) { + // don't return a combat node if we don't have an enemy or + // if we can see he's not in the last place we saw him + + if ( team == 0 ) { + // find the closest attack node to the player + bestNode = NULL; + const idVec3 &myPos = physicsObj.GetOrigin(); + const idVec3 &playerPos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + + bestDist = ( myPos - playerPos ).LengthSqr(); + + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { + continue; + } + + node = static_cast( targetEnt ); + if ( !node->IsDisabled() ) { + idVec3 org = node->GetPhysics()->GetOrigin(); + dist = ( playerPos - org ).LengthSqr(); + if ( dist < bestDist ) { + bestNode = node; + bestDist = dist; + } + } + } + + idThread::ReturnEntity( bestNode ); + return; + } + + idThread::ReturnEntity( NULL ); + return; + } + + // find the closest attack node that can see our enemy and is closer than our enemy + bestNode = NULL; + const idVec3 &myPos = physicsObj.GetOrigin(); + bestDist = ( myPos - lastVisibleEnemyPos ).LengthSqr(); + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { + continue; + } + + node = static_cast( targetEnt ); + if ( !node->IsDisabled() && node->EntityInView( enemyEnt, lastVisibleEnemyPos ) ) { + idVec3 org = node->GetPhysics()->GetOrigin(); + dist = ( myPos - org ).LengthSqr(); + if ( dist < bestDist ) { + bestNode = node; + bestDist = dist; + } + } + } + + idThread::ReturnEntity( bestNode ); +} + +/* +===================== +idAI::Event_EnemyInCombatCone +===================== +*/ +void idAI::Event_EnemyInCombatCone( idEntity *ent, int use_current_enemy_location ) { + idCombatNode *node; + bool result; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !targets.Num() ) { + // no combat nodes + idThread::ReturnInt( false ); + return; + } + + if ( !enemyEnt ) { + // have to have an enemy + idThread::ReturnInt( false ); + return; + } + + if ( !ent || !ent->IsType( idCombatNode::Type ) ) { + // not a combat node + idThread::ReturnInt( false ); + return; + } + + //Allow the level designers define attack nodes that the enemy should never leave. + //This is different that the turrent type combat nodes because they can play an animation + if(ent->spawnArgs.GetBool("neverLeave", "0")) { + idThread::ReturnInt( true ); + return; + } + + node = static_cast( ent ); + if ( use_current_enemy_location ) { + const idVec3 &pos = enemyEnt->GetPhysics()->GetOrigin(); + result = node->EntityInView( enemyEnt, pos ); + } else { + result = node->EntityInView( enemyEnt, lastVisibleEnemyPos ); + } + + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_WaitMove +===================== +*/ +void idAI::Event_WaitMove() { + idThread::BeginMultiFrameEvent( this, &AI_WaitMove ); + + if ( MoveDone() ) { + idThread::EndMultiFrameEvent( this, &AI_WaitMove ); + } +} + +/* +===================== +idAI::Event_GetJumpVelocity +===================== +*/ +void idAI::Event_GetJumpVelocity( const idVec3 &pos, float speed, float max_height ) { + idVec3 start; + idVec3 end; + idVec3 dir; + float dist; + bool result; + idEntity *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + start = physicsObj.GetOrigin(); + end = pos; + dir = end - start; + dist = dir.Normalize(); + if ( dist > 16.0f ) { + dist -= 16.0f; + end -= dir * 16.0f; + } + + result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, max_height, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir ); + if ( result ) { + idThread::ReturnVector( dir * speed ); + } else { + idThread::ReturnVector( vec3_zero ); + } +} + +/* +===================== +idAI::Event_EntityInAttackCone +===================== +*/ +void idAI::Event_EntityInAttackCone( idEntity *ent ) { + float attack_cone; + idVec3 delta; + float yaw; + float relYaw; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + delta = ent->GetPhysics()->GetOrigin() - GetEyePosition(); + + // get our gravity normal + const idVec3 &gravityDir = GetPhysics()->GetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + yaw = delta.ToYaw(); + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + relYaw = idMath::AngleNormalize180( ideal_yaw - yaw ); + if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idAI::Event_CanSeeEntity +===================== +*/ +void idAI::Event_CanSeeEntity( idEntity *ent ) { + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + bool cansee = CanSee( ent, false ); + idThread::ReturnInt( cansee ); +} + +/* +===================== +idAI::Event_SetTalkTarget +===================== +*/ +void idAI::Event_SetTalkTarget( idEntity *target ) { + if ( target && !target->IsType( idActor::Type ) ) { + gameLocal.Error( "Cannot set talk target to '%s'. Not a character or player.", target->GetName() ); + } + talkTarget = static_cast( target ); + if ( target ) { + AI_TALK = true; + } else { + AI_TALK = false; + } +} + +/* +===================== +idAI::Event_GetTalkTarget +===================== +*/ +void idAI::Event_GetTalkTarget() { + idThread::ReturnEntity( talkTarget.GetEntity() ); +} + +/* +================ +idAI::Event_SetTalkState +================ +*/ +void idAI::Event_SetTalkState( int state ) { + if ( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) { + gameLocal.Error( "Invalid talk state (%d)", state ); + } + + talk_state = static_cast( state ); +} + +/* +===================== +idAI::Event_EnemyRange +===================== +*/ +void idAI::Event_EnemyRange() { + float dist; + idActor *enemyEnt = enemy.GetEntity(); + + if ( enemyEnt ) { + dist = ( enemyEnt->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + } else { + // Just some really high number + dist = idMath::INFINITY; + } + + idThread::ReturnFloat( dist ); +} + +/* +===================== +idAI::Event_EnemyRange2D +===================== +*/ +void idAI::Event_EnemyRange2D() { + float dist; + idActor *enemyEnt = enemy.GetEntity(); + + if ( enemyEnt ) { + dist = ( enemyEnt->GetPhysics()->GetOrigin().ToVec2() - GetPhysics()->GetOrigin().ToVec2() ).Length(); + } else { + // Just some really high number + dist = idMath::INFINITY; + } + + idThread::ReturnFloat( dist ); +} + +/* +===================== +idAI::Event_GetEnemy +===================== +*/ +void idAI::Event_GetEnemy() { + idThread::ReturnEntity( enemy.GetEntity() ); +} + +/* +===================== +idAI::Event_GetEnemyPos +===================== +*/ +void idAI::Event_GetEnemyPos() { + idThread::ReturnVector( lastVisibleEnemyPos ); +} + +/* +===================== +idAI::Event_GetEnemyEyePos +===================== +*/ +void idAI::Event_GetEnemyEyePos() { + idThread::ReturnVector( lastVisibleEnemyPos + lastVisibleEnemyEyeOffset ); +} + +/* +===================== +idAI::Event_PredictEnemyPos +===================== +*/ +void idAI::Event_PredictEnemyPos( float time ) { + predictedPath_t path; + idActor *enemyEnt = enemy.GetEntity(); + + // if no enemy set + if ( !enemyEnt ) { + idThread::ReturnVector( physicsObj.GetOrigin() ); + return; + } + + // predict the enemy movement + idAI::PredictPath( enemyEnt, aas, lastVisibleEnemyPos, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnVector( path.endPos ); +} + +/* +===================== +idAI::Event_CanHitEnemy +===================== +*/ +void idAI::Event_CanHitEnemy() { + trace_t tr; + idEntity *hit; + + idActor *enemyEnt = enemy.GetEntity(); + if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + idThread::ReturnInt( lastHitCheckResult ); + return; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + idVec3 eye = GetEyePosition(); + idVec3 dir; + + // expand the ray out as far as possible so we can detect anything behind the enemy + dir = toPos - eye; + dir.Normalize(); + toPos = eye + dir * MAX_WORLD_SIZE; + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + hit = gameLocal.GetTraceEntity( tr ); + if ( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) { + lastHitCheckResult = true; + } else if ( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) && + ( static_cast( hit )->team != team ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + idThread::ReturnInt( lastHitCheckResult ); +} + +/* +===================== +idAI::Event_CanHitEnemyFromAnim +===================== +*/ +void idAI::Event_CanHitEnemyFromAnim( const char *animname ) { + int anim; + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + float distance; + + idActor *enemyEnt = enemy.GetEntity(); + if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + idThread::ReturnInt( false ); + return; + } + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + Event_CanHitEnemy(); + return; + } + + // calculate the world transform of the launch position + const idVec3 &org = physicsObj.GetOrigin(); + dir = lastVisibleEnemyPos - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis; + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + // check if the owner bounds is bigger than the projectile bounds + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + const idBounds &projBounds = projectileClipModel->GetBounds(); + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + + gameLocal.clip.Translation( tr, start, fromPos, projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this ); + fromPos = tr.endpos; + + if ( GetAimDir( fromPos, enemy.GetEntity(), this, dir ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idAI::Event_CanHitEnemyFromJoint +===================== +*/ +void idAI::Event_CanHitEnemyFromJoint( const char *jointname ) { + trace_t tr; + idVec3 muzzle; + idMat3 axis; + idVec3 start; + float distance; + + idActor *enemyEnt = enemy.GetEntity(); + if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + idThread::ReturnInt( lastHitCheckResult ); + return; + } + + lastHitCheckTime = gameLocal.time; + + const idVec3 &org = physicsObj.GetOrigin(); + idVec3 toPos = enemyEnt->GetEyePosition(); + jointHandle_t joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); + muzzle = org + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + // check if the owner bounds is bigger than the projectile bounds + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + const idBounds &projBounds = projectileClipModel->GetBounds(); + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); + muzzle = tr.endpos; + + gameLocal.clip.Translation( tr, muzzle, toPos, projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + idThread::ReturnInt( lastHitCheckResult ); +} + +/* +===================== +idAI::Event_EnemyPositionValid +===================== +*/ +void idAI::Event_EnemyPositionValid() { + bool result; + + result = EnemyPositionValid(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_ChargeAttack +===================== +*/ +void idAI::Event_ChargeAttack( const char *damageDef ) { + idActor *enemyEnt = enemy.GetEntity(); + + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + if ( enemyEnt ) { + idVec3 enemyOrg; + + if ( move.moveType == MOVETYPE_FLY ) { + // position destination so that we're in the enemy's view + enemyOrg = enemyEnt->GetEyePosition(); + enemyOrg -= enemyEnt->GetPhysics()->GetGravityNormal() * fly_offset; + } else { + enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); + } + + BeginAttack( damageDef ); + DirectMoveToPosition( enemyOrg ); + TurnToward( enemyOrg ); + } +} + +/* +===================== +idAI::Event_TestChargeAttack +===================== +*/ +void idAI::Event_TestChargeAttack() { + trace_t trace; + idActor *enemyEnt = enemy.GetEntity(); + predictedPath_t path; + idVec3 end; + + if ( !enemyEnt ) { + idThread::ReturnFloat( 0.0f ); + return; + } + + if ( move.moveType == MOVETYPE_FLY ) { + // position destination so that we're in the enemy's view + end = enemyEnt->GetEyePosition(); + end -= enemyEnt->GetPhysics()->GetGravityNormal() * fly_offset; + } else { + end = enemyEnt->GetPhysics()->GetOrigin(); + } + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), end - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), end, 1 ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), end, 1 ); + } + + if ( ( path.endEvent == 0 ) || ( path.blockingEntity == enemyEnt ) ) { + idVec3 delta = end - physicsObj.GetOrigin(); + float time = delta.LengthFast(); + idThread::ReturnFloat( time ); + } else { + idThread::ReturnFloat( 0.0f ); + } +} + +/* +===================== +idAI::Event_TestAnimMoveTowardEnemy +===================== +*/ +void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + float yaw; + idVec3 delta; + idActor *enemyEnt; + + enemyEnt = enemy.GetEntity(); + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); + yaw = delta.ToYaw(); + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, 1 ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, 1 ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestAnimMove +===================== +*/ +void idAI::Event_TestAnimMove( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, 1 ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, 1 ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMoveToPosition +===================== +*/ +void idAI::Event_TestMoveToPosition( const idVec3 &position ) { + predictedPath_t path; + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, 1 ); + gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, 1 ); + if ( path.endEvent ) { + gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, 1 ); + } + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMeleeAttack +===================== +*/ +void idAI::Event_TestMeleeAttack() { + bool result = TestMelee(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_TestAnimAttack +===================== +*/ +void idAI::Event_TestAnimAttack( const char *animname ) { + int anim; + predictedPath_t path; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.GetEntity() ) ); +} + +/* +===================== +idAI::Event_PreBurn +===================== +*/ +void idAI::Event_PreBurn() { + // No grabbing after the burn has started! + noGrab = true; + + // for now this just turns shadows off + renderEntity.noShadow = true; +} + +/* +===================== +idAI::Event_Burn +===================== +*/ +void idAI::Event_Burn() { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + SpawnParticles( "smoke_burnParticleSystem" ); + UpdateVisuals(); +} + +/* +===================== +idAI::Event_ClearBurn +===================== +*/ +void idAI::Event_ClearBurn() { + renderEntity.noShadow = spawnArgs.GetBool( "noshadows" ); + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + UpdateVisuals(); +} + +/* +===================== +idAI::Event_SetSmokeVisibility +===================== +*/ +void idAI::Event_SetSmokeVisibility( int num, int on ) { + int i; + int time; + + if ( num >= particles.Num() ) { + gameLocal.Warning( "Particle #%d out of range (%d particles) on entity '%s'", num, particles.Num(), name.c_str() ); + return; + } + + if ( on != 0 ) { + time = gameLocal.time; + BecomeActive( TH_UPDATEPARTICLES ); + } else { + time = 0; + } + + if ( num >= 0 ) { + particles[ num ].time = time; + } else { + for ( i = 0; i < particles.Num(); i++ ) { + particles[ i ].time = time; + } + } + + UpdateVisuals(); +} + +/* +===================== +idAI::Event_NumSmokeEmitters +===================== +*/ +void idAI::Event_NumSmokeEmitters() { + idThread::ReturnInt( particles.Num() ); +} + +/* +===================== +idAI::Event_StopThinking +===================== +*/ +void idAI::Event_StopThinking() { + BecomeInactive( TH_THINK ); + idThread *thread = idThread::CurrentThread(); + if ( thread ) { + thread->DoneProcessing(); + } +} + +/* +===================== +idAI::Event_GetTurnDelta +===================== +*/ +void idAI::Event_GetTurnDelta() { + float amount; + + if ( turnRate ) { + amount = idMath::AngleNormalize180( ideal_yaw - current_yaw ); + idThread::ReturnFloat( amount ); + } else { + idThread::ReturnFloat( 0.0f ); + } +} + +/* +===================== +idAI::Event_GetMoveType +===================== +*/ +void idAI::Event_GetMoveType() { + idThread::ReturnInt( move.moveType ); +} + +/* +===================== +idAI::Event_SetMoveTypes +===================== +*/ +void idAI::Event_SetMoveType( int moveType ) { + if ( ( moveType < 0 ) || ( moveType >= NUM_MOVETYPES ) ) { + gameLocal.Error( "Invalid movetype %d", moveType ); + } + + move.moveType = static_cast( moveType ); + if ( move.moveType == MOVETYPE_FLY ) { + travelFlags = TFL_WALK|TFL_AIR|TFL_FLY; + } else { + travelFlags = TFL_WALK|TFL_AIR; + } +} + +/* +===================== +idAI::Event_SaveMove +===================== +*/ +void idAI::Event_SaveMove() { + savedMove = move; +} + +/* +===================== +idAI::Event_RestoreMove +===================== +*/ +void idAI::Event_RestoreMove() { + idVec3 goalPos; + idVec3 dest; + + switch( savedMove.moveCommand ) { + case MOVE_NONE : + StopMove( savedMove.moveStatus ); + break; + + case MOVE_FACE_ENEMY : + FaceEnemy(); + break; + + case MOVE_FACE_ENTITY : + FaceEntity( savedMove.goalEntity.GetEntity() ); + break; + + case MOVE_TO_ENEMY : + MoveToEnemy(); + break; + + case MOVE_TO_ENEMYHEIGHT : + MoveToEnemyHeight(); + break; + + case MOVE_TO_ENTITY : + MoveToEntity( savedMove.goalEntity.GetEntity() ); + break; + + case MOVE_OUT_OF_RANGE : + MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_TO_ATTACK_POSITION : + MoveToAttackPosition( savedMove.goalEntity.GetEntity(), savedMove.anim ); + break; + + case MOVE_TO_COVER : + MoveToCover( savedMove.goalEntity.GetEntity(), lastVisibleEnemyPos ); + break; + + case MOVE_TO_POSITION : + MoveToPosition( savedMove.moveDest ); + break; + + case MOVE_TO_POSITION_DIRECT : + DirectMoveToPosition( savedMove.moveDest ); + break; + + case MOVE_SLIDE_TO_POSITION : + SlideToPosition( savedMove.moveDest, savedMove.duration ); + break; + + case MOVE_WANDER : + WanderAround(); + break; + } + + if ( GetMovePos( goalPos ) ) { + CheckObstacleAvoidance( goalPos, dest ); + } +} + +/* +===================== +idAI::Event_AllowMovement +===================== +*/ +void idAI::Event_AllowMovement( float flag ) { + allowMove = ( flag != 0.0f ); +} + +/* +===================== +idAI::Event_JumpFrame +===================== +*/ +void idAI::Event_JumpFrame() { + AI_JUMP = true; +} + +/* +===================== +idAI::Event_EnableClip +===================== +*/ +void idAI::Event_EnableClip() { + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + disableGravity = false; +} + +/* +===================== +idAI::Event_DisableClip +===================== +*/ +void idAI::Event_DisableClip() { + physicsObj.SetClipMask( 0 ); + disableGravity = true; +} + +/* +===================== +idAI::Event_EnableGravity +===================== +*/ +void idAI::Event_EnableGravity() { + disableGravity = false; +} + +/* +===================== +idAI::Event_DisableGravity +===================== +*/ +void idAI::Event_DisableGravity() { + disableGravity = true; +} + +/* +===================== +idAI::Event_EnableAFPush +===================== +*/ +void idAI::Event_EnableAFPush() { + af_push_moveables = true; +} + +/* +===================== +idAI::Event_DisableAFPush +===================== +*/ +void idAI::Event_DisableAFPush() { + af_push_moveables = false; +} + +/* +===================== +idAI::Event_SetFlySpeed +===================== +*/ +void idAI::Event_SetFlySpeed( float speed ) { + if ( move.speed == fly_speed ) { + move.speed = speed; + } + fly_speed = speed; +} + +/* +================ +idAI::Event_SetFlyOffset +================ +*/ +void idAI::Event_SetFlyOffset( int offset ) { + fly_offset = offset; +} + +/* +================ +idAI::Event_ClearFlyOffset +================ +*/ +void idAI::Event_ClearFlyOffset() { + spawnArgs.GetInt( "fly_offset", "0", fly_offset ); +} + +/* +===================== +idAI::Event_GetClosestHiddenTarget +===================== +*/ +void idAI::Event_GetClosestHiddenTarget( const char *type ) { + int i; + idEntity *ent; + idEntity *bestEnt; + float time; + float bestTime; + const idVec3 &org = physicsObj.GetOrigin(); + idActor *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt ) { + // no enemy to hide from + idThread::ReturnEntity( NULL ); + return; + } + + if ( targets.Num() == 1 ) { + ent = targets[ 0 ].GetEntity(); + if ( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) { + if ( !EntityCanSeePos( enemyEnt, lastVisibleEnemyPos, ent->GetPhysics()->GetOrigin() ) ) { + idThread::ReturnEntity( ent ); + return; + } + } + idThread::ReturnEntity( NULL ); + return; + } + + bestEnt = NULL; + bestTime = idMath::INFINITY; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) { + const idVec3 &destOrg = ent->GetPhysics()->GetOrigin(); + time = TravelDistance( org, destOrg ); + if ( ( time >= 0.0f ) && ( time < bestTime ) ) { + if ( !EntityCanSeePos( enemyEnt, lastVisibleEnemyPos, destOrg ) ) { + bestEnt = ent; + bestTime = time; + } + } + } + } + idThread::ReturnEntity( bestEnt ); +} + +/* +===================== +idAI::Event_GetRandomTarget +===================== +*/ +void idAI::Event_GetRandomTarget( const char *type ) { + int i; + int num; + int which; + idEntity *ent; + idEntity *ents[ MAX_GENTITIES ]; + + num = 0; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) { + ents[ num++ ] = ent; + if ( num >= MAX_GENTITIES ) { + break; + } + } + } + + if ( !num ) { + idThread::ReturnEntity( NULL ); + return; + } + + which = gameLocal.random.RandomInt( num ); + idThread::ReturnEntity( ents[ which ] ); +} + +/* +================ +idAI::Event_TravelDistanceToPoint +================ +*/ +void idAI::Event_TravelDistanceToPoint( const idVec3 &pos ) { + float time; + + time = TravelDistance( physicsObj.GetOrigin(), pos ); + idThread::ReturnFloat( time ); +} + +/* +================ +idAI::Event_TravelDistanceToEntity +================ +*/ +void idAI::Event_TravelDistanceToEntity( idEntity *ent ) { + float time; + + time = TravelDistance( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() ); + idThread::ReturnFloat( time ); +} + +/* +================ +idAI::Event_TravelDistanceBetweenPoints +================ +*/ +void idAI::Event_TravelDistanceBetweenPoints( const idVec3 &source, const idVec3 &dest ) { + float time; + + time = TravelDistance( source, dest ); + idThread::ReturnFloat( time ); +} + +/* +================ +idAI::Event_TravelDistanceBetweenEntities +================ +*/ +void idAI::Event_TravelDistanceBetweenEntities( idEntity *source, idEntity *dest ) { + float time; + + assert( source ); + assert( dest ); + time = TravelDistance( source->GetPhysics()->GetOrigin(), dest->GetPhysics()->GetOrigin() ); + idThread::ReturnFloat( time ); +} + +/* +===================== +idAI::Event_LookAtEntity +===================== +*/ +void idAI::Event_LookAtEntity( idEntity *ent, float duration ) { + if ( ent == this ) { + ent = NULL; + } + + if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = ent; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + focusTime = gameLocal.time + SEC2MS( duration ); +} + +/* +===================== +idAI::Event_LookAtEnemy +===================== +*/ +void idAI::Event_LookAtEnemy( float duration ) { + idActor *enemyEnt; + + enemyEnt = enemy.GetEntity(); + if ( ( enemyEnt != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = enemyEnt; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + focusTime = gameLocal.time + SEC2MS( duration ); +} + +/* +=============== +idAI::Event_SetJointMod +=============== +*/ +void idAI::Event_SetJointMod( int allow ) { + allowJointMod = ( allow != 0 ); +} + +/* +================ +idAI::Event_ThrowMoveable +================ +*/ +void idAI::Event_ThrowMoveable() { + idEntity *ent; + idEntity *moveable = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { + if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::Type ) ) { + moveable = ent; + break; + } + } + if ( moveable ) { + moveable->Unbind(); + moveable->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_ThrowAF +================ +*/ +void idAI::Event_ThrowAF() { + idEntity *ent; + idEntity *af = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { + if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::Type ) ) { + af = ent; + break; + } + } + if ( af ) { + af->Unbind(); + af->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_SetAngles +================ +*/ +void idAI::Event_SetAngles( idAngles const &ang ) { + current_yaw = ang.yaw; + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); +} + +/* +================ +idAI::Event_GetAngles +================ +*/ +void idAI::Event_GetAngles() { + idThread::ReturnVector( idVec3( 0.0f, current_yaw, 0.0f ) ); +} + +/* +================ +idAI::Event_GetTrajectoryToPlayer +================ +*/ +void idAI::Event_GetTrajectoryToPlayer() { + + idVec3 start; + idVec3 end; + idVec3 dir; + float dist; +// bool result; + idEntity *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + end = enemyEnt->GetPhysics()->GetOrigin(); + float speed = 400.0f; + + if ( speed <= 0.0f ) { + gameLocal.Error( "Invalid speed. speed must be > 0." ); + } + + start = physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 50.0f ); + dir = end - start; + dist = dir.Normalize(); + if ( dist > 16.0f ) { + dist -= 16.0f; + end -= dir * 16.0f; + } + + + + idVec3 gravity; + ballistics_t ballistics[2]; + Ballistics( start, end, speed, 0.0f, ballistics ); + dir.y = ballistics[0].angle; + + + + + +// result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, 1000.0f, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir ); +// if ( result ) { + idThread::ReturnVector( dir * speed ); +// } else { +// idThread::ReturnVector( vec3_zero ); +// } +} + +/* +================ +idAI::Event_RealKill +================ +*/ +void idAI::Event_RealKill() { + health = 0; + + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + Killed( this, this, 0, vec3_zero, INVALID_JOINT ); +} + +/* +================ +idAI::Event_Kill +================ +*/ +void idAI::Event_Kill() { + PostEventMS( &AI_RealKill, 0 ); +} + +/* +================ +idAI::Event_WakeOnFlashlight +================ +*/ +void idAI::Event_WakeOnFlashlight( int enable ) { + wakeOnFlashlight = ( enable != 0 ); +} + +/* +================ +idAI::Event_LocateEnemy +================ +*/ +void idAI::Event_LocateEnemy() { + idActor *enemyEnt; + int areaNum; + + enemyEnt = enemy.GetEntity(); + if ( !enemyEnt ) { + return; + } + + enemyEnt->GetAASLocation( aas, lastReachableEnemyPos, areaNum ); + SetEnemyPosition(); + UpdateEnemyPosition(); +} + +/* +================ +idAI::Event_KickObstacles +================ +*/ +void idAI::Event_KickObstacles( idEntity *kickEnt, float force ) { + idVec3 dir; + idEntity *obEnt; + + if ( kickEnt ) { + obEnt = kickEnt; + } else { + obEnt = move.obstacle.GetEntity(); + } + + if ( obEnt ) { + dir = obEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); + dir.Normalize(); + } else { + dir = viewAxis[ 0 ]; + } + KickObstacles( dir, force, obEnt ); +} + +/* +================ +idAI::Event_GetObstacle +================ +*/ +void idAI::Event_GetObstacle() { + idThread::ReturnEntity( move.obstacle.GetEntity() ); +} + +/* +================ +idAI::Event_PushPointIntoAAS +================ +*/ +void idAI::Event_PushPointIntoAAS( const idVec3 &pos ) { + int areaNum; + idVec3 newPos; + + areaNum = PointReachableAreaNum( pos ); + if ( areaNum ) { + newPos = pos; + aas->PushPointIntoAreaNum( areaNum, newPos ); + idThread::ReturnVector( newPos ); + } else { + idThread::ReturnVector( pos ); + } +} + + +/* +================ +idAI::Event_GetTurnRate +================ +*/ +void idAI::Event_GetTurnRate() { + idThread::ReturnFloat( turnRate ); +} + +/* +================ +idAI::Event_SetTurnRate +================ +*/ +void idAI::Event_SetTurnRate( float rate ) { + turnRate = rate; +} + +/* +================ +idAI::Event_AnimTurn +================ +*/ +void idAI::Event_AnimTurn( float angles ) { + turnVel = 0.0f; + anim_turn_angles = angles; + if ( angles ) { + anim_turn_yaw = current_yaw; + anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( current_yaw - ideal_yaw ) ); + if ( anim_turn_amount > anim_turn_angles ) { + anim_turn_amount = anim_turn_angles; + } + } else { + anim_turn_amount = 0.0f; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f ); + } +} + +/* +================ +idAI::Event_AllowHiddenMovement +================ +*/ +void idAI::Event_AllowHiddenMovement( int enable ) { + allowHiddenMovement = ( enable != 0 ); +} + +/* +================ +idAI::Event_TriggerParticles +================ +*/ +void idAI::Event_TriggerParticles( const char *jointName ) { + TriggerParticles( jointName ); +} + +/* +===================== +idAI::Event_FindActorsInBounds +===================== +*/ +void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) { + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + int i; + + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES ); + for( i = 0; i < numListedEntities; i++ ) { + ent = entityList[ i ]; + if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::Type ) ) { + idThread::ReturnEntity( ent ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idAI::Event_CanReachPosition +================ +*/ +void idAI::Event_CanReachPosition( const idVec3 &pos ) { + aasPath_t path; + int toAreaNum; + int areaNum; + + toAreaNum = PointReachableAreaNum( pos ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEntity +================ +*/ +void idAI::Event_CanReachEntity( idEntity *ent ) { + aasPath_t path; + int toAreaNum; + int areaNum; + idVec3 pos; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } + if ( ent->IsType( idActor::Type ) && static_cast( ent )->OnLadder() ) { + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + toAreaNum = PointReachableAreaNum( pos ); + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEnemy +================ +*/ +void idAI::Event_CanReachEnemy() { + aasPath_t path; + int toAreaNum; + int areaNum; + idVec3 pos; + idActor *enemyEnt; + + enemyEnt = enemy.GetEntity(); + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if ( enemyEnt->OnLadder() ) { + idThread::ReturnInt( false ); + return; + } + enemyEnt->GetAASLocation( aas, pos, toAreaNum ); + } else { + pos = enemyEnt->GetPhysics()->GetOrigin(); + toAreaNum = PointReachableAreaNum( pos ); + } + + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_GetReachableEntityPosition +================ +*/ +void idAI::Event_GetReachableEntityPosition( idEntity *ent ) { + int toAreaNum; + idVec3 pos; + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + // NOTE: not a good way to return 'false' + return idThread::ReturnVector( vec3_zero ); + } + if ( ent->IsType( idActor::Type ) && static_cast( ent )->OnLadder() ) { + // NOTE: not a good way to return 'false' + return idThread::ReturnVector( vec3_zero ); + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + if ( aas ) { + toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( toAreaNum, pos ); + } + + idThread::ReturnVector( pos ); +} + +/* +================ +idAI::Event_MoveToPositionDirect +================ +*/ +void idAI::Event_MoveToPositionDirect( const idVec3 &pos ) { + StopMove( MOVE_STATUS_DONE ); + DirectMoveToPosition( pos ); +} + +/* +================ +idAI::Event_AvoidObstacles +================ +*/ +void idAI::Event_AvoidObstacles( int ignore) { + ignore_obstacles = (ignore == 1) ? false : true; +} + +/* +================ +idAI::Event_TriggerFX +================ +*/ +void idAI::Event_TriggerFX( const char* joint, const char* fx ) { + TriggerFX(joint, fx); +} + +void idAI::Event_StartEmitter( const char* name, const char* joint, const char* particle ) { + idEntity *ent = StartEmitter(name, joint, particle); + idThread::ReturnEntity(ent); +} + +void idAI::Event_GetEmitter( const char* name ) { + idThread::ReturnEntity(GetEmitter(name)); +} + +void idAI::Event_StopEmitter( const char* name ) { + StopEmitter(name); +} + + +/* +===================== +idAI::Event_LaunchHomingMissile +===================== +*/ +void idAI::Event_LaunchHomingMissile() { + idVec3 start; + trace_t tr; + idBounds projBounds; + const idClipModel *projClip; + idMat3 axis; + float distance; + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + idThread::ReturnEntity( NULL ); + return; + } + + idActor *enemy = GetEnemy(); + if ( enemy == NULL ) { + idThread::ReturnEntity( NULL ); + return; + } + + idVec3 org = GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 250.0f ); + idVec3 goal = enemy->GetPhysics()->GetOrigin(); + homingMissileGoal = goal; + +// axis = ( goal - org ).ToMat3(); +// axis.Identity(); + if ( !projectile.GetEntity() ) { + idHomingProjectile *homing = ( idHomingProjectile * ) CreateProjectile( org, idVec3( 0.0f, 0.0f, 1.0f ) ); + if ( homing != NULL ) { + homing->SetEnemy( enemy ); + homing->SetSeekPos( homingMissileGoal ); + } + } + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + + gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); + + // launch the projectile + idThread::ReturnEntity( projectile.GetEntity() ); + idVec3 dir = homingMissileGoal - org; + idAngles ang = dir.ToAngles(); + ang.pitch = -45.0f; + projectile.GetEntity()->Launch( org, ang.ToForward(), vec3_origin ); + projectile = NULL; + + TriggerWeaponEffects( tr.endpos ); + + lastAttackTime = gameLocal.time; +} + +/* +===================== +idAI::Event_SetHomingMissileGoal +===================== +*/ +void idAI::Event_SetHomingMissileGoal() { + idActor *enemy = GetEnemy(); + if ( enemy == NULL ) { + idThread::ReturnEntity( NULL ); + return; + } + + homingMissileGoal = enemy->GetPhysics()->GetOrigin(); +} \ No newline at end of file diff --git a/neo/d3xp/ai/AI_pathing.cpp b/neo/d3xp/ai/AI_pathing.cpp new file mode 100644 index 00000000..b320161b --- /dev/null +++ b/neo/d3xp/ai/AI_pathing.cpp @@ -0,0 +1,1535 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +/* +=============================================================================== + + Dynamic Obstacle Avoidance + + - assumes the AI lives inside a bounding box aligned with the gravity direction + - obstacles in proximity of the AI are gathered + - if obstacles are found the AAS walls are also considered as obstacles + - every obstacle is represented by an oriented bounding box (OBB) + - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction + - the 2D windings of the projections are expanded for the AI bbox + - a path tree is build using clockwise and counter clockwise edge walks along the winding edges + - the path tree is pruned and optimized + - the shortest path is chosen for navigation + +=============================================================================== +*/ + +const float MAX_OBSTACLE_RADIUS = 256.0f; +const float PUSH_OUTSIDE_OBSTACLES = 0.5f; +const float CLIP_BOUNDS_EPSILON = 10.0f; +const int MAX_AAS_WALL_EDGES = 256; +const int MAX_OBSTACLES = 256; +const int MAX_PATH_NODES = 256; +const int MAX_OBSTACLE_PATH = 64; + +typedef struct obstacle_s { + idVec2 bounds[2]; + idWinding2D winding; + idEntity * entity; +} obstacle_t; + +typedef struct pathNode_s { + int dir; + idVec2 pos; + idVec2 delta; + float dist; + int obstacle; + int edgeNum; + int numNodes; + struct pathNode_s * parent; + struct pathNode_s * children[2]; + struct pathNode_s * next; + void Init(); +} pathNode_t; + +void pathNode_s::Init() { + dir = 0; + pos.Zero(); + delta.Zero(); + obstacle = -1; + edgeNum = -1; + numNodes = 0; + parent = children[0] = children[1] = next = NULL; +} + +idBlockAlloc pathNodeAllocator; + + +/* +============ +LineIntersectsPath +============ +*/ +bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) { + float d0, d1, d2, d3; + idVec3 plane1, plane2; + + plane1 = idWinding2D::Plane2DFromPoints( start, end ); + d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z; + while( node->parent ) { + d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z; + if ( IEEE_FLT_SIGNBITSET( d0 ) ^ IEEE_FLT_SIGNBITSET( d1 ) ) { + plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos ); + d2 = plane2.x * start.x + plane2.y * start.y + plane2.z; + d3 = plane2.x * end.x + plane2.y * end.y + plane2.z; + if ( IEEE_FLT_SIGNBITSET( d2 ) ^ IEEE_FLT_SIGNBITSET( d3 ) ) { + return true; + } + } + d0 = d1; + node = node->parent; + } + return false; +} + +/* +============ +PointInsideObstacle +============ +*/ +int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point ) { + int i; + + for ( i = 0; i < numObstacles; i++ ) { + + const idVec2 *bounds = obstacles[i].bounds; + if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) { + continue; + } + + if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) { + continue; + } + + return i; + } + + return -1; +} + +/* +============ +GetPointOutsideObstacles +============ +*/ +void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) { + int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2]; + float d, bestd, scale[2]; + idVec3 plane, bestPlane; + idVec2 newPoint, dir, bestPoint; + int *queue; + bool *obstacleVisited; + idWinding2D w1, w2; + + if ( obstacle ) { + *obstacle = -1; + } + if ( edgeNum ) { + *edgeNum = -1; + } + + bestObstacle = PointInsideObstacle( obstacles, numObstacles, point ); + if ( bestObstacle == -1 ) { + return; + } + + const idWinding2D &w = obstacles[bestObstacle].winding; + bestd = idMath::INFINITY; + bestEdgeNum = 0; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d < bestd ) { + bestd = d; + bestPlane = plane; + bestEdgeNum = i; + } + // if this is a wall always try to pop out at the first edge + if ( obstacles[bestObstacle].entity == NULL ) { + break; + } + } + + newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2(); + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + point = newPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + + queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) ); + obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) ); + + queueStart = 0; + queueEnd = 1; + queue[0] = bestObstacle; + + memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) ); + assert( bestObstacle < numObstacles ); + obstacleVisited[bestObstacle] = true; + + bestd = idMath::INFINITY; + for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) { + w1 = obstacles[i].winding; + w1.Expand( PUSH_OUTSIDE_OBSTACLES ); + + for ( j = 0; j < numObstacles; j++ ) { + // if the obstacle has been visited already + if ( obstacleVisited[j] ) { + continue; + } + // if the bounds do not intersect + if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y || + obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + + assert( queueEnd < numObstacles ); + queue[queueEnd++] = j; + obstacleVisited[j] = true; + + w2 = obstacles[j].winding; + w2.Expand( 0.2f ); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + dir = w1[(k+1)%w1.GetNumPoints()] - w1[k]; + if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) { + continue; + } + for ( n = 0; n < 2; n++ ) { + newPoint = w1[k] + scale[n] * dir; + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + d = ( newPoint - point ).LengthSqr(); + if ( d < bestd ) { + bestd = d; + bestPoint = newPoint; + bestEdgeNum = edgeNums[n]; + bestObstacle = j; + } + } + } + } + } + + if ( bestd < idMath::INFINITY ) { + point = bestPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + } + gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" ); +} + +/* +============ +GetFirstBlockingObstacle +============ +*/ +bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) { + int i, edgeNums[2]; + float dist, scale1, scale2; + idVec2 bounds[2]; + + // get bounds for the current movement delta + bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[IEEE_FLT_SIGNBITNOTSET(delta.x)].x += delta.x; + bounds[IEEE_FLT_SIGNBITNOTSET(delta.y)].y += delta.y; + + // test for obstacles blocking the path + blockingScale = idMath::INFINITY; + dist = delta.Length(); + for ( i = 0; i < numObstacles; i++ ) { + if ( i == skipObstacle ) { + continue; + } + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) { + if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) { + blockingScale = scale1; + blockingObstacle = i; + blockingEdgeNum = edgeNums[0]; + } + } + } + return ( blockingScale < 1.0f ); +} + +/* +============ +GetObstacles +============ +*/ +int GetObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) { + int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum; + int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2]; + float stepHeight, headHeight, blockingScale, min, max; + idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd; + idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal; + idVec2 obDelta; + idPhysics *obPhys; + idBox box; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + + numObstacles = 0; + + seekDelta = seekPos - startPos; + expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + + physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight ); + stepHeight += aas->GetSettings()->maxStepHeight; + + // clip bounds for the obstacle search space + clipBounds[0] = clipBounds[1] = startPos; + clipBounds.AddPoint( seekPos ); + clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS ); + clipMask = physics->GetClipMask(); + + // find all obstacles touching the clip bounds + numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipMask, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + + if ( !clipModel->IsTraceModel() ) { + continue; + } + + if ( obEnt->IsType( idActor::Type ) ) { + obPhys = obEnt->GetPhysics(); + // ignore myself, my enemy, and dead bodies + if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) { + continue; + } + // if the actor is moving + idVec3 v1 = obPhys->GetLinearVelocity(); + if ( v1.LengthSqr() > Square( 10.0f ) ) { + idVec3 v2 = physics->GetLinearVelocity(); + if ( v2.LengthSqr() > Square( 10.0f ) ) { + // if moving in about the same direction + if ( v1 * v2 > 0.0f ) { + continue; + } + } + } + } else if ( obEnt->IsType( idMoveable::Type ) ) { + // moveables are considered obstacles + } else { + // ignore everything else + continue; + } + + // check if we can step over the object + clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max ); + if ( max < stepHeight || min > headHeight ) { + // can step over this one + continue; + } + + // project a box containing the obstacle onto the floor plane + box = idBox( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); + numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts ); + + // create a 2D winding for the obstacle; + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + for ( j = 0; j < numVerts; j++ ) { + obstacle.winding.AddPoint( silVerts[j].ToVec2() ); + } + + if ( ai_showObstacleAvoidance.GetBool() ) { + for ( j = 0; j < numVerts; j++ ) { + silVerts[j].z = startPos.z; + } + for ( j = 0; j < numVerts; j++ ) { + gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 ); + } + } + + // expand the 2D winding for collision with a 2D box + obstacle.winding.ExpandForAxialBox( expBounds ); + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = obEnt; + } + + // if there are no dynamic obstacles the path should be through valid AAS space + if ( numObstacles == 0 ) { + return 0; + } + + // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space + if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2() ) == -1 ) { + if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) { + return 0; + } + } + + // create obstacles for AAS walls + if ( aas ) { + float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f; + + numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES ); + aas->SortWallEdges( wallEdges, numWallEdges ); + + lastVerts[0] = lastVerts[1] = 0; + lastEdgeNormal.Zero(); + nextVerts[0] = nextVerts[1] = 0; + for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) { + aas->GetEdge( wallEdges[i], start, end ); + aas->GetEdgeVertexNumbers( wallEdges[i], verts ); + edgeDir = end.ToVec2() - start.ToVec2(); + edgeDir.Normalize(); + edgeNormal.x = edgeDir.y; + edgeNormal.y = -edgeDir.x; + if ( i < numWallEdges-1 ) { + aas->GetEdge( wallEdges[i+1], nextStart, nextEnd ); + aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts ); + nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2(); + nextEdgeDir.Normalize(); + nextEdgeNormal.x = nextEdgeDir.y; + nextEdgeNormal.y = -nextEdgeDir.x; + } + + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + obstacle.winding.AddPoint( end.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize ); + obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize ); + if ( lastVerts[1] == verts[0] ) { + obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[1] -= edgeDir; + } + if ( verts[1] == nextVerts[0] ) { + obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[0] += edgeDir; + } + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = NULL; + + memcpy( lastVerts, verts, sizeof( lastVerts ) ); + lastEdgeNormal = edgeNormal; + } + } + + // show obstacles + if ( ai_showObstacleAvoidance.GetBool() ) { + for ( i = 0; i < numObstacles; i++ ) { + obstacle_t &obstacle = obstacles[i]; + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + silVerts[j].ToVec2() = obstacle.winding[j]; + silVerts[j].z = startPos.z; + } + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 ); + } + } + } + + return numObstacles; +} + +/* +============ +FreePathTree_r +============ +*/ +void FreePathTree_r( pathNode_t *node ) { + if ( node->children[0] ) { + FreePathTree_r( node->children[0] ); + } + if ( node->children[1] ) { + FreePathTree_r( node->children[1] ); + } + pathNodeAllocator.Free( node ); +} + +/* +============ +DrawPathTree +============ +*/ +void DrawPathTree( const pathNode_t *root, const float height ) { + int i; + idVec3 start, end; + const pathNode_t *node; + + for ( node = root; node; node = node->next ) { + for ( i = 0; i < 2; i++ ) { + if ( node->children[i] ) { + start.ToVec2() = node->pos; + start.z = height; + end.ToVec2() = node->children[i]->pos; + end.z = height; + gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 ); + break; + } + } + } +} + +/* +============ +GetPathNodeDelta +============ +*/ +bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) { + int numPoints, edgeNum; + bool facing; + idVec2 seekDelta, dir; + pathNode_t *n; + + numPoints = obstacles[node->obstacle].winding.GetNumPoints(); + + // get delta along the current edge + while( 1 ) { + edgeNum = ( node->edgeNum + node->dir ) % numPoints; + node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos; + if ( node->delta.LengthSqr() > 0.01f ) { + break; + } + node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints; + } + + // if not blocked + if ( !blocked ) { + + // test if the current edge faces the goal + seekDelta = seekPos - node->pos; + facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f; + + // if the current edge faces goal and the line from the current + // position to the goal does not intersect the current path + if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) { + node->delta = seekPos - node->pos; + node->edgeNum = -1; + } + } + + // if the delta is along the obstacle edge + if ( node->edgeNum != -1 ) { + // if the edge is found going from this node to the root node + for ( n = node->parent; n; n = n->parent ) { + + if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) { + continue; + } + + // test whether or not the edge segments actually overlap + if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) { + continue; + } + if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) { + continue; + } + + break; + } + if ( n ) { + return false; + } + } + return true; +} + +/* +============ +BuildPathTree +============ +*/ +pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) { + int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH; + float blockingScale; + pathNode_t *root, *node, *child; + // gcc 4.0 + idQueueTemplate pathNodeQueue, treeQueue; + + root = pathNodeAllocator.Alloc(); + root->Init(); + root->pos = startPos; + + root->delta = seekPos - root->pos; + root->numNodes = 0; + pathNodeQueue.Add( root ); + + for ( node = pathNodeQueue.Get(); node != NULL && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) { + + treeQueue.Add( node ); + + // if this path has more than twice the number of nodes than the best path so far + if ( node->numNodes > bestNumNodes * 2 ) { + continue; + } + + // don't move outside of the clip bounds + idVec2 endPos = node->pos + node->delta; + if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x || + endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) { + continue; + } + + // if an obstacle is blocking the path + if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) { + + if ( path.firstObstacle == NULL ) { + path.firstObstacle = obstacles[blockingObstacle].entity; + } + + node->delta *= blockingScale; + + if ( node->edgeNum == -1 ) { + node->children[0] = pathNodeAllocator.Alloc(); + node->children[0]->Init(); + node->children[1] = pathNodeAllocator.Alloc(); + node->children[1]->Init(); + node->children[0]->dir = 0; + node->children[1]->dir = 1; + node->children[0]->parent = node->children[1]->parent = node; + node->children[0]->pos = node->children[1]->pos = node->pos + node->delta; + node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle; + node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum; + node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[0] ); + } + if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[1] ); + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->obstacle = blockingObstacle; + child->edgeNum = blockingEdgeNum; + child->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) { + pathNodeQueue.Add( child ); + } + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->numNodes = node->numNodes + 1; + + // there is a free path towards goal + if ( node->edgeNum == -1 ) { + if ( node->numNodes < bestNumNodes ) { + bestNumNodes = node->numNodes; + } + continue; + } + + child->obstacle = node->obstacle; + obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints(); + child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints; + + if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) { + pathNodeQueue.Add( child ); + } + } + } + + return root; +} + +/* +============ +PrunePathTree +============ +*/ +void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) { + int i; + float bestDist; + pathNode_t *node, *lastNode, *n, *bestNode; + + node = root; + while( node ) { + + node->dist = ( seekPos - node->pos ).LengthSqr(); + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + + // find the node closest to the goal along this path + bestDist = idMath::INFINITY; + bestNode = node; + for ( n = node; n; n = n->parent ) { + if ( n->children[0] && n->children[1] ) { + break; + } + if ( n->dist < bestDist ) { + bestDist = n->dist; + bestNode = n; + } + } + + // free tree down from the best node + for ( i = 0; i < 2; i++ ) { + if ( bestNode->children[i] ) { + FreePathTree_r( bestNode->children[i] ); + bestNode->children[i] = NULL; + } + } + + for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && ( node->children[1] != lastNode ) ) { + node = node->children[1]; + break; + } + } + } + } +} + +/* +============ +OptimizePath +============ +*/ +int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) { + int i, numPathPoints, edgeNums[2]; + const pathNode_t *curNode, *nextNode; + idVec2 curPos, curDelta, bounds[2]; + float scale1, scale2, curLength; + + optimizedPath[0] = root->pos; + numPathPoints = 1; + + for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) { + + for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) { + + // can only take shortcuts when going from one object to another + if ( nextNode->obstacle == curNode->obstacle ) { + continue; + } + + curPos = curNode->pos; + curDelta = nextNode->pos - curPos; + curLength = curDelta.Length(); + + // get bounds for the current movement delta + bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[IEEE_FLT_SIGNBITNOTSET(curDelta.x)].x += curDelta.x; + bounds[IEEE_FLT_SIGNBITNOTSET(curDelta.y)].y += curDelta.y; + + // test if the shortcut intersects with any obstacles + for ( i = 0; i < numObstacles; i++ ) { + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) { + if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) { + break; + } + if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) { + break; + } + } + } + if ( i >= numObstacles ) { + break; + } + } + + // store the next position along the optimized path + optimizedPath[numPathPoints++] = nextNode->pos; + } + + return numPathPoints; +} + +/* +============ +PathLength +============ +*/ +float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) { + int i; + float pathLength; + + // calculate the path length + pathLength = 0.0f; + for ( i = 0; i < numPathPoints-1; i++ ) { + pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast(); + } + + // add penalty if this path does not go in the current direction + if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) { + pathLength += 100.0f; + } + return pathLength; +} + +/* +============ +FindOptimalPath + + Returns true if there is a path all the way to the goal. +============ +*/ +bool FindOptimalPath( const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) { + int i, numPathPoints, bestNumPathPoints; + const pathNode_t *node, *lastNode, *bestNode; + idVec2 optimizedPath[MAX_OBSTACLE_PATH]; + float pathLength, bestPathLength; + bool pathToGoalExists, optimizedPathCalculated; + + seekPos.Zero(); + seekPos.z = height; + + pathToGoalExists = false; + optimizedPathCalculated = false; + + bestNode = root; + bestNumPathPoints = 0; + bestPathLength = idMath::INFINITY; + + node = root; + while( node ) { + + pathToGoalExists |= ( node->dist < 0.1f ); + + if ( node->dist <= bestNode->dist ) { + + if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) { + + if ( !optimizedPathCalculated ) { + bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() ); + seekPos.ToVec2() = optimizedPath[1]; + } + + numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath ); + pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() ); + + if ( pathLength < bestPathLength ) { + bestNode = node; + bestNumPathPoints = numPathPoints; + bestPathLength = pathLength; + seekPos.ToVec2() = optimizedPath[1]; + } + optimizedPathCalculated = true; + + } else { + + bestNode = node; + optimizedPathCalculated = false; + } + } + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && node->children[1] != lastNode ) { + node = node->children[1]; + break; + } + } + } + } + + if ( root != NULL ) { + if ( !pathToGoalExists ) { + if ( root->children[0] != NULL ) { + seekPos.ToVec2() = root->children[0]->pos; + } else { + seekPos.ToVec2() = root->pos; + } + } else if ( !optimizedPathCalculated ) { + OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + seekPos.ToVec2() = optimizedPath[1]; + } + + if ( ai_showObstacleAvoidance.GetBool() ) { + idVec3 start, end; + start.z = end.z = height + 4.0f; + numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + for ( i = 0; i < numPathPoints-1; i++ ) { + start.ToVec2() = optimizedPath[i]; + end.ToVec2() = optimizedPath[i+1]; + gameRenderWorld->DebugArrow( colorCyan, start, end, 1 ); + } + } + } + + return pathToGoalExists; +} + +/* +============ +idAI::FindPathAroundObstacles + + Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks. +============ +*/ +bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) { + int numObstacles, areaNum, insideObstacle; + obstacle_t obstacles[MAX_OBSTACLES]; + idBounds clipBounds; + idBounds bounds; + pathNode_t *root; + bool pathToGoalExists; + + path.seekPos = seekPos; + path.firstObstacle = NULL; + path.startPosOutsideObstacles = startPos; + path.startPosObstacle = NULL; + path.seekPosOutsideObstacles = seekPos; + path.seekPosObstacle = NULL; + + if ( !aas ) { + return true; + } + + bounds[1] = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -bounds[1]; + bounds[1].z = 32.0f; + + // get the AAS area number and a valid point inside that area + areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles ); + + // get all the nearby obstacles + numObstacles = GetObstacles( physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds ); + + // get a source position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.startPosObstacle = obstacles[insideObstacle].entity; + } + + // get a goal position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.seekPosObstacle = obstacles[insideObstacle].entity; + } + + // if start and destination are pushed to the same point, we don't have a path around the obstacle + if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) { + if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) { + return false; + } + } + + // build a path tree + root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path ); + + // draw the path tree + if ( ai_showObstacleAvoidance.GetBool() ) { + DrawPathTree( root, physics->GetOrigin().z ); + } + + // prune the tree + PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() ); + + // find the optimal path + pathToGoalExists = FindOptimalPath( root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos ); + + // free the tree + FreePathTree_r( root ); + + return pathToGoalExists; +} + +/* +============ +idAI::FreeObstacleAvoidanceNodes +============ +*/ +void idAI::FreeObstacleAvoidanceNodes() { + pathNodeAllocator.Shutdown(); +} + + +/* +=============================================================================== + + Path Prediction + + Uses the AAS to quickly and accurately predict a path for a certain + period of time based on an initial position and velocity. + +=============================================================================== +*/ + +const float OVERCLIP = 1.001f; +const int MAX_FRAME_SLIDE = 5; + +typedef struct pathTrace_s { + float fraction; + idVec3 endPos; + idVec3 normal; + const idEntity * blockingEntity; +} pathTrace_t; + +/* +============ +PathTrace + + Returns true if a stop event was triggered. +============ +*/ +bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path ) { + trace_t clipTrace; + aasTrace_t aasTrace; + + memset( &trace, 0, sizeof( trace ) ); + + if ( !aas || !aas->GetSettings() ) { + + gameLocal.clip.Translation( clipTrace, start, end, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent ); + + // NOTE: could do (expensive) ledge detection here for when there is no AAS file + + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } else { + aasTrace.getOutOfSolid = true; + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + aasTrace.flags |= AREA_LEDGE; + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + aasTrace.travelFlags |= TFL_INVALID; + } + + aas->Trace( aasTrace, start, end ); + + gameLocal.clip.TranslationEntities( clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent ); + + if ( clipTrace.fraction >= 1.0f ) { + + trace.fraction = aasTrace.fraction; + trace.endPos = aasTrace.endpos; + trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal(); + trace.blockingEntity = gameLocal.world; + + if ( aasTrace.fraction < 1.0f ) { + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_LEDGE_AREA; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_OBSTACLE; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + } + } else { + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } + } + + if ( trace.fraction >= 1.0f ) { + trace.blockingEntity = NULL; + } + + return false; +} + +/* +============ +idAI::PredictPath + + Can also be used when there is no AAS file available however ledges are not detected. +============ +*/ +bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path ) { + int i, j, step, numFrames, curFrameTime; + idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart; + idVec3 gravity, gravityDir, invGravityDir; + float maxStepHeight, minFloorCos; + pathTrace_t trace; + + if ( aas && aas->GetSettings() ) { + gravity = aas->GetSettings()->gravity; + gravityDir = aas->GetSettings()->gravityDir; + invGravityDir = aas->GetSettings()->invGravityDir; + maxStepHeight = aas->GetSettings()->maxStepHeight; + minFloorCos = aas->GetSettings()->minFloorCos; + } else { + gravity = DEFAULT_GRAVITY_VEC3; + gravityDir = idVec3( 0, 0, -1 ); + invGravityDir = idVec3( 0, 0, 1 ); + maxStepHeight = 14.0f; + minFloorCos = 0.7f; + } + + path.endPos = start; + path.endVelocity = velocity; + path.endNormal.Zero(); + path.endEvent = 0; + path.endTime = 0; + path.blockingEntity = NULL; + + curStart = start; + curVelocity = velocity; + + numFrames = ( totalTime + frameTime - 1 ) / frameTime; + curFrameTime = frameTime; + for ( i = 0; i < numFrames; i++ ) { + + if ( i == numFrames-1 ) { + curFrameTime = totalTime - i * curFrameTime; + } + + delta = curVelocity * curFrameTime * 0.001f; + + path.endVelocity = curVelocity; + path.endTime = i * frameTime; + + // allow sliding along a few surfaces per frame + for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) { + + idVec3 lineStart = curStart; + + // allow stepping up three times per frame + for ( step = 0; step < 3; step++ ) { + + curEnd = curStart + delta; + if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path ) ) { + return true; + } + + if ( step ) { + + // step down at end point + tmpStart = trace.endPos; + curEnd = tmpStart - stepUp; + if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path ) ) { + return true; + } + + // if not moved any further than without stepping up, or if not on a floor surface + if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f || + ( trace.normal * invGravityDir ) < minFloorCos ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = lastEnd; + path.endEvent = SE_BLOCKED; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd ); + } + + return true; + } + + curStart = lastEnd; + break; + } + } + + path.endNormal = trace.normal; + path.blockingEntity = trace.blockingEntity; + + // if the trace is not blocked or blocked by a floor surface + if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) { + curStart = trace.endPos; + break; + } + + // save last result + lastEnd = trace.endPos; + + // step up + stepUp = invGravityDir * maxStepHeight; + if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path ) ) { + return true; + } + stepUp *= trace.fraction; + curStart = trace.endPos; + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, lineStart, curStart ); + } + + if ( trace.fraction >= 1.0f ) { + break; + } + + delta.ProjectOntoPlane( trace.normal, OVERCLIP ); + curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP ); + + if ( stopEvent & SE_BLOCKED ) { + // if going backwards + if ( (curVelocity - gravityDir * curVelocity * gravityDir ) * + (velocity - gravityDir * velocity * gravityDir) < 0.0f ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + + return true; + } + } + } + + if ( j >= MAX_FRAME_SLIDE ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + return true; + } + } + + // add gravity + curVelocity += gravity * frameTime * 0.001f; + } + + path.endTime = totalTime; + path.endVelocity = curVelocity; + path.endPos = curStart; + path.endEvent = 0; + + return false; +} + + +/* +=============================================================================== + + Trajectory Prediction + + Finds the best collision free trajectory for a clip model based on an + initial position, target position and speed. + +=============================================================================== +*/ + +/* +===================== +Ballistics + + get the ideal aim pitch angle in order to hit the target + also get the time it takes for the projectile to arrive at the target +===================== +*/ + +int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) { + int n, i; + float x, y, a, b, c, d, sqrtd, inva, p[2]; + + x = ( end.ToVec2() - start.ToVec2() ).Length(); + y = end[2] - start[2]; + + a = 4.0f * y * y + 4.0f * x * x; + b = -4.0f * speed * speed - 4.0f * y * gravity; + c = gravity * gravity; + + d = b * b - 4.0f * a * c; + if ( d <= 0.0f || a == 0.0f ) { + return 0; + } + sqrtd = idMath::Sqrt( d ); + inva = 0.5f / a; + p[0] = ( - b + sqrtd ) * inva; + p[1] = ( - b - sqrtd ) * inva; + n = 0; + for ( i = 0; i < 2; i++ ) { + if ( p[i] <= 0.0f ) { + continue; + } + d = idMath::Sqrt( p[i] ); + bal[n].angle = atan2( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x ); + bal[n].time = x / ( cos( bal[n].angle ) * speed ); + bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) ); + n++; + } + + return n; +} + +#if 0 +// not used +/* +===================== +HeightForTrajectory + +Returns the maximum hieght of a given trajectory +===================== +*/ +static float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) { + float maxHeight, t; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + + return maxHeight; +} +#endif + +/* +===================== +idAI::TestTrajectory +===================== +*/ +bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) { + int i, numSegments; + float maxHeight, t, t2; + idVec3 points[5]; + trace_t trace; + bool result; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + // time it takes to fall from the top to the end height + t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) ); + + // start of parabolic + points[0] = start; + + if ( t < time ) { + numSegments = 4; + // point in the middle between top and start + t2 = ( time - t ) * 0.5f; + points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // top of parabolic + t2 = time - t; + points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // point in the middel between top and end + t2 = time - t * 0.5f; + points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } else { + numSegments = 2; + // point halfway through + t2 = time * 0.5f; + points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f; + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } + + // end of parabolic + points[numSegments] = end; + + if ( drawtime ) { + for ( i = 0; i < numSegments; i++ ) { + gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime ); + } + } + + // make sure projectile doesn't go higher than we want it to go + for ( i = 0; i < numSegments; i++ ) { + if ( points[i].z > max_height ) { + // goes higher than we want to allow + return false; + } + } + + result = true; + for ( i = 0; i < numSegments; i++ ) { + gameLocal.clip.Translation( trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore ); + if ( trace.fraction < 1.0f ) { + if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) { + result = true; + } else { + result = false; + } + break; + } + } + + if ( drawtime ) { + if ( clip ) { + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime ); + } else { + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + } + + return result; +} + +/* +===================== +idAI::PredictTrajectory + + returns true if there is a collision free trajectory for the clip model + aimDir is set to the ideal aim direction in order to hit the target +===================== +*/ +bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) { + int n, i, j; + float zVel, a, t, pitch, s, c; + trace_t trace; + ballistics_t ballistics[2]; + idVec3 dir[2]; + idVec3 velocity; + idVec3 lastPos, pos; + + if ( targetEntity == NULL ) { + return false; + } + + // check if the projectile starts inside the target + if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) { + aimDir = target - firePos; + aimDir.Normalize(); + return true; + } + + // if no velocity or the projectile is not affected by gravity + if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) { + + aimDir = target - firePos; + aimDir.Normalize(); + + gameLocal.clip.Translation( trace, firePos, target, clip, mat3_identity, clipmask, ignore ); + + if ( drawtime ) { + gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime ); + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + + return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ); + } + + n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics ); + if ( n == 0 ) { + // there is no valid trajectory + aimDir = target - firePos; + aimDir.Normalize(); + return false; + } + + // make sure the first angle is the smallest + if ( n == 2 ) { + if ( ballistics[1].angle < ballistics[0].angle ) { + a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a; + t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t; + } + } + + // test if there is a collision free trajectory + for ( i = 0; i < n; i++ ) { + pitch = DEG2RAD( ballistics[i].angle ); + idMath::SinCos( pitch, s, c ); + dir[i] = target - firePos; + dir[i].z = 0.0f; + dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() ); + dir[i].z = s; + + zVel = projectileSpeed * dir[i].z; + + if ( ai_debugTrajectory.GetBool() ) { + t = ballistics[i].time / 100.0f; + velocity = dir[i] * projectileSpeed; + lastPos = firePos; + pos = firePos; + for ( j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; + gameRenderWorld->DebugLine( colorCyan, lastPos, pos ); + lastPos = pos; + } + } + + if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) { + aimDir = dir[i]; + return true; + } + } + + aimDir = dir[0]; + + // there is no collision free trajectory + return false; +} diff --git a/neo/d3xp/anim/Anim.cpp b/neo/d3xp/anim/Anim.cpp new file mode 100644 index 00000000..f6551310 --- /dev/null +++ b/neo/d3xp/anim/Anim.cpp @@ -0,0 +1,1177 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +idCVar binaryLoadAnim( "binaryLoadAnim", "1", 0, "enable binary load/write of idMD5Anim" ); + +static const byte B_ANIM_MD5_VERSION = 101; +static const unsigned int B_ANIM_MD5_MAGIC = ( 'B' << 24 ) | ( 'M' << 16 ) | ( 'D' << 8 ) | B_ANIM_MD5_VERSION; + +static const int JOINT_FRAME_PAD = 1; // one extra to be able to read one more float than is necessary + +bool idAnimManager::forceExport = false; + +/*********************************************************************** + + idMD5Anim + +***********************************************************************/ + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::idMD5Anim() { + ref_count = 0; + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + numAnimatedComponents = 0; + totaldelta.Zero(); +} + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::~idMD5Anim() { + Free(); +} + +/* +==================== +idMD5Anim::Free +==================== +*/ +void idMD5Anim::Free() { + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + numAnimatedComponents = 0; + //name = ""; + + totaldelta.Zero(); + + jointInfo.Clear(); + bounds.Clear(); + componentFrames.Clear(); +} + +/* +==================== +idMD5Anim::NumFrames +==================== +*/ +int idMD5Anim::NumFrames() const { + return numFrames; +} + +/* +==================== +idMD5Anim::NumJoints +==================== +*/ +int idMD5Anim::NumJoints() const { + return numJoints; +} + +/* +==================== +idMD5Anim::Length +==================== +*/ +int idMD5Anim::Length() const { + return animLength; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const idVec3 &idMD5Anim::TotalMovementDelta() const { + return totaldelta; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const char *idMD5Anim::Name() const { + return name; +} + +/* +==================== +idMD5Anim::Reload +==================== +*/ +bool idMD5Anim::Reload() { + idStr filename; + + filename = name; + Free(); + + return LoadAnim( filename ); +} + +/* +==================== +idMD5Anim::Allocated +==================== +*/ +size_t idMD5Anim::Allocated() const { + size_t size = bounds.Allocated() + jointInfo.Allocated() + componentFrames.Allocated() + name.Allocated(); + return size; +} + +/* +==================== +idMD5Anim::LoadAnim +==================== +*/ +bool idMD5Anim::LoadAnim( const char * filename ) { + + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + + idStr generatedFileName = "generated/anim/"; + generatedFileName.AppendPath( filename ); + generatedFileName.SetFileExtension( ".bMD5anim" ); + + // Get the timestamp on the original file, if it's newer than what is stored in binary model, regenerate it + ID_TIME_T sourceTimeStamp = fileSystem->GetTimestamp( filename ); + + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + if ( binaryLoadAnim.GetBool() && LoadBinary( file, sourceTimeStamp ) ) { + name = filename; + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + // for resource gathering write this anim to the preload file for this map + fileSystem->AddAnimPreload( name ); + } + return true; + } + + if ( !parser.LoadFile( filename ) ) { + return false; + } + + name = filename; + + Free(); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + int version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + numJoints = parser.ParseInt(); + if ( numJoints <= 0 ) { + parser.Error( "Invalid number of joints: %d", numJoints ); + } + + // parse frame rate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate < 0 ) { + parser.Error( "Invalid frame rate: %d", frameRate ); + } + + // parse number of animated components + parser.ExpectTokenString( "numAnimatedComponents" ); + numAnimatedComponents = parser.ParseInt(); + if ( ( numAnimatedComponents < 0 ) || ( numAnimatedComponents > numJoints * 6 ) ) { + parser.Error( "Invalid number of animated components: %d", numAnimatedComponents ); + } + + // parse the hierarchy + jointInfo.SetGranularity( 1 ); + jointInfo.SetNum( numJoints ); + parser.ExpectTokenString( "hierarchy" ); + parser.ExpectTokenString( "{" ); + for ( int i = 0; i < numJoints; i++ ) { + parser.ReadToken( &token ); + jointInfo[ i ].nameIndex = animationLib.JointIndex( token ); + + // parse parent num + jointInfo[ i ].parentNum = parser.ParseInt(); + if ( jointInfo[ i ].parentNum >= i ) { + parser.Error( "Invalid parent num: %d", jointInfo[ i ].parentNum ); + } + + if ( ( i != 0 ) && ( jointInfo[ i ].parentNum < 0 ) ) { + parser.Error( "Animations may have only one root joint" ); + } + + // parse anim bits + jointInfo[ i ].animBits = parser.ParseInt(); + if ( jointInfo[ i ].animBits & ~63 ) { + parser.Error( "Invalid anim bits: %d", jointInfo[ i ].animBits ); + } + + // parse first component + jointInfo[ i ].firstComponent = parser.ParseInt(); + if ( ( numAnimatedComponents > 0 ) && ( ( jointInfo[ i ].firstComponent < 0 ) || ( jointInfo[ i ].firstComponent >= numAnimatedComponents ) ) ) { + parser.Error( "Invalid first component: %d", jointInfo[ i ].firstComponent ); + } + } + + parser.ExpectTokenString( "}" ); + + // parse bounds + parser.ExpectTokenString( "bounds" ); + parser.ExpectTokenString( "{" ); + bounds.SetGranularity( 1 ); + bounds.SetNum( numFrames ); + for ( int i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, bounds[ i ][ 0 ].ToFloatPtr() ); + parser.Parse1DMatrix( 3, bounds[ i ][ 1 ].ToFloatPtr() ); + } + parser.ExpectTokenString( "}" ); + + // parse base frame + baseFrame.SetGranularity( 1 ); + baseFrame.SetNum( numJoints ); + parser.ExpectTokenString( "baseframe" ); + parser.ExpectTokenString( "{" ); + for ( int i = 0; i < numJoints; i++ ) { + idCQuat q; + parser.Parse1DMatrix( 3, baseFrame[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, q.ToFloatPtr() );//baseFrame[ i ].q.ToFloatPtr() ); + baseFrame[ i ].q = q.ToQuat();//.w = baseFrame[ i ].q.CalcW(); + baseFrame[ i ].w = 0.0f; + } + parser.ExpectTokenString( "}" ); + + // parse frames + componentFrames.SetGranularity( 1 ); + componentFrames.SetNum( numAnimatedComponents * numFrames + JOINT_FRAME_PAD ); + componentFrames[numAnimatedComponents * numFrames + JOINT_FRAME_PAD - 1] = 0.0f; + + float *componentPtr = componentFrames.Ptr(); + for ( int i = 0; i < numFrames; i++ ) { + parser.ExpectTokenString( "frame" ); + int num = parser.ParseInt(); + if ( num != i ) { + parser.Error( "Expected frame number %d", i ); + } + parser.ExpectTokenString( "{" ); + + for ( int j = 0; j < numAnimatedComponents; j++, componentPtr++ ) { + *componentPtr = parser.ParseFloat(); + } + + parser.ExpectTokenString( "}" ); + } + + // get total move delta + if ( !numAnimatedComponents ) { + totaldelta.Zero(); + } else { + componentPtr = &componentFrames[ jointInfo[ 0 ].firstComponent ]; + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + for ( int i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.x; + } + totaldelta.x = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.x = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + for ( int i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.y; + } + totaldelta.y = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.y = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + for ( int i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.z; + } + totaldelta.z = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + } else { + totaldelta.z = 0.0f; + } + } + baseFrame[ 0 ].t.Zero(); + + // we don't count last frame because it would cause a 1 frame pause at the end + animLength = ( ( numFrames - 1 ) * 1000 + frameRate - 1 ) / frameRate; + + if ( binaryLoadAnim.GetBool() ) { + idLib::Printf( "Writing %s\n", generatedFileName.c_str() ); + idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); + WriteBinary( outputFile, sourceTimeStamp ); + } + + // done + return true; +} + +/* +======================== +idMD5Anim::LoadBinary +======================== +*/ +bool idMD5Anim::LoadBinary( idFile * file, ID_TIME_T sourceTimeStamp ) { + + if ( file == NULL ) { + return false; + } + + unsigned int magic = 0; + file->ReadBig( magic ); + if ( magic != B_ANIM_MD5_MAGIC ) { + return false; + } + + ID_TIME_T loadedTimeStamp; + file->ReadBig( loadedTimeStamp ); + if ( !fileSystem->InProductionMode() && sourceTimeStamp != loadedTimeStamp ) { + return false; + } + + file->ReadBig( numFrames ); + file->ReadBig( frameRate ); + file->ReadBig( animLength ); + file->ReadBig( numJoints ); + file->ReadBig( numAnimatedComponents ); + + int num; + file->ReadBig( num ); + bounds.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + idBounds & b = bounds[i]; + file->ReadBig( b[0] ); + file->ReadBig( b[1] ); + } + + file->ReadBig( num ); + jointInfo.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + jointAnimInfo_t & j = jointInfo[i]; + + idStr jointName; + file->ReadString( jointName ); + if ( jointName.IsEmpty() ) { + j.nameIndex = -1; + } else { + j.nameIndex = animationLib.JointIndex( jointName.c_str() ); + } + + file->ReadBig( j.parentNum ); + file->ReadBig( j.animBits ); + file->ReadBig( j.firstComponent ); + } + + file->ReadBig( num ); + baseFrame.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + idJointQuat & j = baseFrame[i]; + file->ReadBig( j.q.x ); + file->ReadBig( j.q.y ); + file->ReadBig( j.q.z ); + file->ReadBig( j.q.w ); + file->ReadVec3( j.t ); + j.w = 0.0f; + } + + file->ReadBig( num ); + componentFrames.SetNum( num + JOINT_FRAME_PAD ); + for ( int i = 0; i < componentFrames.Num(); i++ ) { + file->ReadFloat( componentFrames[i] ); + } + + //file->ReadString( name ); + file->ReadVec3( totaldelta ); + //file->ReadBig( ref_count ); + + return true; +} + +/* +======================== +idMD5Anim::WriteBinary +======================== +*/ +void idMD5Anim::WriteBinary( idFile * file, ID_TIME_T sourceTimeStamp ) { + + if ( file == NULL ) { + return; + } + + file->WriteBig( B_ANIM_MD5_MAGIC ); + file->WriteBig( sourceTimeStamp ); + + file->WriteBig( numFrames ); + file->WriteBig( frameRate ); + file->WriteBig( animLength ); + file->WriteBig( numJoints ); + file->WriteBig( numAnimatedComponents ); + + file->WriteBig( bounds.Num() ); + for ( int i = 0; i < bounds.Num(); i++ ) { + idBounds & b = bounds[i]; + file->WriteBig( b[0] ); + file->WriteBig( b[1] ); + } + + file->WriteBig( jointInfo.Num() ); + for ( int i = 0; i < jointInfo.Num(); i++ ) { + jointAnimInfo_t & j = jointInfo[i]; + idStr jointName = animationLib.JointName( j.nameIndex ); + file->WriteString( jointName ); + file->WriteBig( j.parentNum ); + file->WriteBig( j.animBits ); + file->WriteBig( j.firstComponent ); + } + + file->WriteBig( baseFrame.Num() ); + for ( int i = 0; i < baseFrame.Num(); i++ ) { + idJointQuat & j = baseFrame[i]; + file->WriteBig( j.q.x ); + file->WriteBig( j.q.y ); + file->WriteBig( j.q.z ); + file->WriteBig( j.q.w ); + file->WriteVec3( j.t ); + } + + file->WriteBig( componentFrames.Num() - JOINT_FRAME_PAD ); + for ( int i = 0; i < componentFrames.Num(); i++ ) { + file->WriteFloat( componentFrames[i] ); + } + + //file->WriteString( name ); + file->WriteVec3( totaldelta ); + //file->WriteBig( ref_count ); +} + +/* +==================== +idMD5Anim::IncreaseRefs +==================== +*/ +void idMD5Anim::IncreaseRefs() const { + ref_count++; +} + +/* +==================== +idMD5Anim::DecreaseRefs +==================== +*/ +void idMD5Anim::DecreaseRefs() const { + ref_count--; +} + +/* +==================== +idMD5Anim::NumRefs +==================== +*/ +int idMD5Anim::NumRefs() const { + return ref_count; +} + +/* +==================== +idMD5Anim::GetFrameBlend +==================== +*/ +void idMD5Anim::GetFrameBlend( int framenum, frameBlend_t &frame ) const { + frame.cycleCount = 0; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + + // frame 1 is first frame + framenum--; + if ( framenum < 0 ) { + framenum = 0; + } else if ( framenum >= numFrames ) { + framenum = numFrames - 1; + } + + frame.frame1 = framenum; + frame.frame2 = framenum; +} + +/* +==================== +idMD5Anim::ConvertTimeToFrame +==================== +*/ +void idMD5Anim::ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const { + int frameTime; + int frameNum; + + if ( numFrames <= 1 ) { + frame.frame1 = 0; + frame.frame2 = 0; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + if ( time <= 0 ) { + frame.frame1 = 0; + frame.frame2 = 1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + frameTime = time * frameRate; + frameNum = frameTime / 1000; + frame.cycleCount = frameNum / ( numFrames - 1 ); + + if ( ( cyclecount > 0 ) && ( frame.cycleCount >= cyclecount ) ) { + frame.cycleCount = cyclecount - 1; + frame.frame1 = numFrames - 1; + frame.frame2 = frame.frame1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + return; + } + + frame.frame1 = frameNum % ( numFrames - 1 ); + frame.frame2 = frame.frame1 + 1; + if ( frame.frame2 >= numFrames ) { + frame.frame2 = 0; + } + + frame.backlerp = ( frameTime % 1000 ) * 0.001f; + frame.frontlerp = 1.0f - frame.backlerp; +} + +/* +==================== +idMD5Anim::GetOrigin +==================== +*/ +void idMD5Anim::GetOrigin( idVec3 &offset, int time, int cyclecount ) const { + offset = baseFrame[ 0 ].t; + if ( !( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) ) { + // just use the baseframe + return; + } + + frameBlend_t frame; + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + + if ( frame.cycleCount ) { + offset += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetOriginRotation +==================== +*/ +void idMD5Anim::GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const { + int animBits = jointInfo[ 0 ].animBits; + if ( !( animBits & ( ANIM_QX | ANIM_QY | ANIM_QZ ) ) ) { + // just use the baseframe + rotation = baseFrame[ 0 ].q; + return; + } + + frameBlend_t frame; + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *jointframe1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *jointframe2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( animBits & ANIM_TX ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TY ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TZ ) { + jointframe1++; + jointframe2++; + } + + idQuat q1; + idQuat q2; + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case ANIM_QX: + q1.x = jointframe1[0]; + q2.x = jointframe2[0]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY: + q1.y = jointframe1[0]; + q2.y = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QZ: + q1.z = jointframe1[0]; + q2.z = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + q1.x = jointframe1[0]; + q1.z = jointframe1[1]; + q2.x = jointframe2[0]; + q2.z = jointframe2[1]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + q1.y = jointframe1[0]; + q1.z = jointframe1[1]; + q2.y = jointframe2[0]; + q2.z = jointframe2[1]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q1.z = jointframe1[2]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q2.z = jointframe2[2]; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + } + + rotation.Slerp( q1, q2, frame.backlerp ); +} + +/* +==================== +idMD5Anim::GetBounds +==================== +*/ +void idMD5Anim::GetBounds( idBounds &bnds, int time, int cyclecount ) const { + frameBlend_t frame; + ConvertTimeToFrame( time, cyclecount, frame ); + + bnds = bounds[ frame.frame1 ]; + bnds.AddBounds( bounds[ frame.frame2 ] ); + + // origin position + idVec3 offset = baseFrame[ 0 ].t; + if ( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) { + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + } + + bnds[ 0 ] -= offset; + bnds[ 1 ] -= offset; +} + +/* +==================== +DecodeInterpolatedFrames + +==================== +*/ +int DecodeInterpolatedFrames( idJointQuat * joints, idJointQuat * blendJoints, int * lerpIndex, const float * frame1, const float * frame2, + const jointAnimInfo_t * jointInfo, const int * index, const int numIndexes ) { + int numLerpJoints = 0; + for ( int i = 0; i < numIndexes; i++ ) { + const int j = index[i]; + const jointAnimInfo_t * infoPtr = &jointInfo[j]; + + const int animBits = infoPtr->animBits; + if ( animBits != 0 ) { + + lerpIndex[numLerpJoints++] = j; + + idJointQuat * jointPtr = &joints[j]; + idJointQuat * blendPtr = &blendJoints[j]; + + *blendPtr = *jointPtr; + + const float * jointframe1 = frame1 + infoPtr->firstComponent; + const float * jointframe2 = frame2 + infoPtr->firstComponent; + + if ( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + if ( animBits & ANIM_TX ) { + jointPtr->t.x = *jointframe1++; + blendPtr->t.x = *jointframe2++; + } + if ( animBits & ANIM_TY ) { + jointPtr->t.y = *jointframe1++; + blendPtr->t.y = *jointframe2++; + } + if ( animBits & ANIM_TZ ) { + jointPtr->t.z = *jointframe1++; + blendPtr->t.z = *jointframe2++; + } + } + + if ( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + if ( animBits & ANIM_QX ) { + jointPtr->q.x = *jointframe1++; + blendPtr->q.x = *jointframe2++; + } + if ( animBits & ANIM_QY ) { + jointPtr->q.y = *jointframe1++; + blendPtr->q.y = *jointframe2++; + } + if ( animBits & ANIM_QZ ) { + jointPtr->q.z = *jointframe1++; + blendPtr->q.z = *jointframe2++; + } + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + } + } + } + return numLerpJoints; +} + +/* +==================== +idMD5Anim::GetInterpolatedFrame +==================== +*/ +void idMD5Anim::GetInterpolatedFrame( frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const { + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + + if ( numAnimatedComponents == 0 ) { + // just use the base frame + return; + } + + idJointQuat * blendJoints = (idJointQuat *)_alloca16( baseFrame.Num() * sizeof( blendJoints[ 0 ] ) ); + int * lerpIndex = (int *)_alloca16( baseFrame.Num() * sizeof( lerpIndex[ 0 ] ) ); + + const float * frame1 = &componentFrames[frame.frame1 * numAnimatedComponents]; + const float * frame2 = &componentFrames[frame.frame2 * numAnimatedComponents]; + + int numLerpJoints = DecodeInterpolatedFrames( joints, blendJoints, lerpIndex, frame1, frame2, jointInfo.Ptr(), index, numIndexes ); + + SIMDProcessor->BlendJoints( joints, blendJoints, frame.backlerp, lerpIndex, numLerpJoints ); + + if ( frame.cycleCount ) { + joints[ 0 ].t += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +DecodeSingleFrame + +==================== +*/ +void DecodeSingleFrame( idJointQuat * joints, const float * frame, + const jointAnimInfo_t * jointInfo, const int * index, const int numIndexes ) { + for ( int i = 0; i < numIndexes; i++ ) { + const int j = index[i]; + const jointAnimInfo_t * infoPtr = &jointInfo[j]; + + const int animBits = infoPtr->animBits; + if ( animBits != 0 ) { + + idJointQuat * jointPtr = &joints[j]; + + const float * jointframe = frame + infoPtr->firstComponent; + + if ( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + if ( animBits & ANIM_TX ) { + jointPtr->t.x = *jointframe++; + } + if ( animBits & ANIM_TY ) { + jointPtr->t.y = *jointframe++; + } + if ( animBits & ANIM_TZ ) { + jointPtr->t.z = *jointframe++; + } + } + + if ( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + if ( animBits & ANIM_QX ) { + jointPtr->q.x = *jointframe++; + } + if ( animBits & ANIM_QY ) { + jointPtr->q.y = *jointframe++; + } + if ( animBits & ANIM_QZ ) { + jointPtr->q.z = *jointframe++; + } + jointPtr->q.w = jointPtr->q.CalcW(); + } + } + } +} + +/* +==================== +idMD5Anim::GetSingleFrame +==================== +*/ +void idMD5Anim::GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const { + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + + if ( framenum == 0 || numAnimatedComponents == 0 ) { + // just use the base frame + return; + } + + const float * frame = &componentFrames[framenum * numAnimatedComponents]; + + DecodeSingleFrame( joints, frame, jointInfo.Ptr(), index, numIndexes ); +} + +/* +==================== +idMD5Anim::CheckModelHierarchy +==================== +*/ +void idMD5Anim::CheckModelHierarchy( const idRenderModel *model ) const { + if ( jointInfo.Num() != model->NumJoints() ) { + if ( !fileSystem->InProductionMode() ) { + gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", model->Name(), name.c_str() ); + } else { + //gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", model->Name(), name.c_str() ); + return; + } + } + + const idMD5Joint *modelJoints = model->GetJoints(); + for( int i = 0; i < jointInfo.Num(); i++ ) { + int jointNum = jointInfo[ i ].nameIndex; + if ( modelJoints[ i ].name != animationLib.JointName( jointNum ) ) { + gameLocal.Error( "Model '%s''s joint names don't match anim '%s''s", model->Name(), name.c_str() ); + } + int parent; + if ( modelJoints[ i ].parent ) { + parent = modelJoints[ i ].parent - modelJoints; + } else { + parent = -1; + } + if ( parent != jointInfo[ i ].parentNum ) { + gameLocal.Error( "Model '%s' has different joint hierarchy than anim '%s'", model->Name(), name.c_str() ); + } + } +} + +/*********************************************************************** + + idAnimManager + +***********************************************************************/ + +/* +==================== +idAnimManager::idAnimManager +==================== +*/ +idAnimManager::idAnimManager() { +} + +/* +==================== +idAnimManager::~idAnimManager +==================== +*/ +idAnimManager::~idAnimManager() { + Shutdown(); +} + +/* +==================== +idAnimManager::Shutdown +==================== +*/ +void idAnimManager::Shutdown() { + animations.DeleteContents(); + jointnames.Clear(); + jointnamesHash.Free(); +} + +/* +==================== +idAnimManager::GetAnim +==================== +*/ +idMD5Anim *idAnimManager::GetAnim( const char *name ) { + idMD5Anim **animptrptr; + idMD5Anim *anim; + + // see if it has been asked for before + animptrptr = NULL; + if ( animations.Get( name, &animptrptr ) ) { + anim = *animptrptr; + } else { + idStr extension; + idStr filename = name; + + filename.ExtractFileExtension( extension ); + if ( extension != MD5_ANIM_EXT ) { + return NULL; + } + + anim = new (TAG_ANIM) idMD5Anim(); + if ( !anim->LoadAnim( filename ) ) { + gameLocal.Warning( "Couldn't load anim: '%s'", filename.c_str() ); + delete anim; + anim = NULL; + } + animations.Set( filename, anim ); + } + + return anim; +} + +/* +================ +idAnimManager::Preload +================ +*/ +void idAnimManager::Preload( const idPreloadManifest &manifest ) { + if ( manifest.NumResources() >= 0 ) { + common->Printf( "Preloading anims...\n" ); + int start = Sys_Milliseconds(); + int numLoaded = 0; + for ( int i = 0; i < manifest.NumResources(); i++ ) { + const preloadEntry_s & p = manifest.GetPreloadByIndex( i ); + if ( p.resType == PRELOAD_ANIM ) { + GetAnim( p.resourceName ); + numLoaded++; + } + } + int end = Sys_Milliseconds(); + common->Printf( "%05d anims preloaded ( or were already loaded ) in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 ); + common->Printf( "----------------------------------------\n" ); + } +} + +/* +================ +idAnimManager::ReloadAnims +================ +*/ +void idAnimManager::ReloadAnims() { + int i; + idMD5Anim **animptr; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr != NULL && *animptr != NULL ) { + ( *animptr )->Reload(); + } + } +} + +/* +================ +idAnimManager::JointIndex +================ +*/ +int idAnimManager::JointIndex( const char *name ) { + int i, hash; + + hash = jointnamesHash.GenerateKey( name ); + for ( i = jointnamesHash.First( hash ); i != -1; i = jointnamesHash.Next( i ) ) { + if ( jointnames[i].Cmp( name ) == 0 ) { + return i; + } + } + + i = jointnames.Append( name ); + jointnamesHash.Add( hash, i ); + return i; +} + +/* +================ +idAnimManager::JointName +================ +*/ +const char *idAnimManager::JointName( int index ) const { + return jointnames[ index ]; +} + +/* +================ +idAnimManager::ListAnims +================ +*/ +void idAnimManager::ListAnims() const { + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t s; + size_t namesize; + int num; + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr != NULL && *animptr != NULL ) { + anim = *animptr; + s = anim->Size(); + gameLocal.Printf( "%8d bytes : %2d refs : %s\n", s, anim->NumRefs(), anim->Name() ); + size += s; + num++; + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + gameLocal.Printf( "\n%d memory used in %d anims\n", size, num ); + gameLocal.Printf( "%d memory used in %d joint names\n", namesize, jointnames.Num() ); +} + +/* +================ +idAnimManager::FlushUnusedAnims +================ +*/ +void idAnimManager::FlushUnusedAnims() { + int i; + idMD5Anim **animptr; + idList removeAnims; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr != NULL && *animptr != NULL ) { + if ( ( *animptr )->NumRefs() <= 0 ) { + removeAnims.Append( *animptr ); + } + } + } + + for( i = 0; i < removeAnims.Num(); i++ ) { + animations.Remove( removeAnims[ i ]->Name() ); + delete removeAnims[ i ]; + } +} diff --git a/neo/d3xp/anim/Anim.h b/neo/d3xp/anim/Anim.h new file mode 100644 index 00000000..275c53ff --- /dev/null +++ b/neo/d3xp/anim/Anim.h @@ -0,0 +1,598 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __ANIM_H__ +#define __ANIM_H__ + +// +// animation channels +// these can be changed by modmakers and licensees to be whatever they need. +const int ANIM_NumAnimChannels = 5; +const int ANIM_MaxAnimsPerChannel = 3; +const int ANIM_MaxSyncedAnims = 3; + +// +// animation channels. make sure to change script/doom_defs.script if you add any channels, or change their order +// +const int ANIMCHANNEL_ALL = 0; +const int ANIMCHANNEL_TORSO = 1; +const int ANIMCHANNEL_LEGS = 2; +const int ANIMCHANNEL_HEAD = 3; +const int ANIMCHANNEL_EYELIDS = 4; + +// for converting from 24 frames per second to milliseconds +ID_INLINE int FRAME2MS( int framenum ) { + return ( framenum * 1000 ) / 24; +} + +class idRenderModel; +class idAnimator; +class idAnimBlend; +class function_t; +class idEntity; +class idSaveGame; +class idRestoreGame; + +typedef struct { + int cycleCount; // how many times the anim has wrapped to the begining (0 for clamped anims) + int frame1; + int frame2; + float frontlerp; + float backlerp; +} frameBlend_t; + +typedef struct { + int nameIndex; + int parentNum; + int animBits; + int firstComponent; +} jointAnimInfo_t; + +typedef struct { + jointHandle_t num; + jointHandle_t parentNum; + int channel; +} jointInfo_t; + +// +// joint modifier modes. make sure to change script/doom_defs.script if you add any, or change their order. +// +typedef enum { + JOINTMOD_NONE, // no modification + JOINTMOD_LOCAL, // modifies the joint's position or orientation in joint local space + JOINTMOD_LOCAL_OVERRIDE, // sets the joint's position or orientation in joint local space + JOINTMOD_WORLD, // modifies joint's position or orientation in model space + JOINTMOD_WORLD_OVERRIDE // sets the joint's position or orientation in model space +} jointModTransform_t; + +typedef struct { + jointHandle_t jointnum; + idMat3 mat; + idVec3 pos; + jointModTransform_t transform_pos; + jointModTransform_t transform_axis; +} jointMod_t; + +#define ANIM_BIT_TX 0 +#define ANIM_BIT_TY 1 +#define ANIM_BIT_TZ 2 +#define ANIM_BIT_QX 3 +#define ANIM_BIT_QY 4 +#define ANIM_BIT_QZ 5 + +#define ANIM_TX BIT( ANIM_BIT_TX ) +#define ANIM_TY BIT( ANIM_BIT_TY ) +#define ANIM_TZ BIT( ANIM_BIT_TZ ) +#define ANIM_QX BIT( ANIM_BIT_QX ) +#define ANIM_QY BIT( ANIM_BIT_QY ) +#define ANIM_QZ BIT( ANIM_BIT_QZ ) + +typedef enum { + FC_SCRIPTFUNCTION, + FC_SCRIPTFUNCTIONOBJECT, + FC_EVENTFUNCTION, + FC_SOUND, + FC_SOUND_VOICE, + FC_SOUND_VOICE2, + FC_SOUND_BODY, + FC_SOUND_BODY2, + FC_SOUND_BODY3, + FC_SOUND_WEAPON, + FC_SOUND_ITEM, + FC_SOUND_GLOBAL, + FC_SOUND_CHATTER, + FC_SKIN, + FC_TRIGGER, + FC_TRIGGER_SMOKE_PARTICLE, + FC_MELEE, + FC_DIRECTDAMAGE, + FC_BEGINATTACK, + FC_ENDATTACK, + FC_MUZZLEFLASH, + FC_CREATEMISSILE, + FC_LAUNCHMISSILE, + FC_FIREMISSILEATTARGET, + FC_FOOTSTEP, + FC_LEFTFOOT, + FC_RIGHTFOOT, + FC_ENABLE_EYE_FOCUS, + FC_DISABLE_EYE_FOCUS, + FC_FX, + FC_DISABLE_GRAVITY, + FC_ENABLE_GRAVITY, + FC_JUMP, + FC_ENABLE_CLIP, + FC_DISABLE_CLIP, + FC_ENABLE_WALK_IK, + FC_DISABLE_WALK_IK, + FC_ENABLE_LEG_IK, + FC_DISABLE_LEG_IK, + FC_RECORDDEMO, + FC_AVIGAME + , FC_LAUNCH_PROJECTILE, + FC_TRIGGER_FX, + FC_START_EMITTER, + FC_STOP_EMITTER, +} frameCommandType_t; + +typedef struct { + int num; + int firstCommand; +} frameLookup_t; + +typedef struct { + frameCommandType_t type; + idStr *string; + + union { + const idSoundShader *soundShader; + const function_t *function; + const idDeclSkin *skin; + int index; + }; +} frameCommand_t; + +typedef struct { + bool prevent_idle_override : 1; + bool random_cycle_start : 1; + bool ai_no_turn : 1; + bool anim_turn : 1; +} animFlags_t; + +/* +============================================================================================== + + idMD5Anim + +============================================================================================== +*/ + +class idMD5Anim { +private: + int numFrames; + int frameRate; + int animLength; + int numJoints; + int numAnimatedComponents; + idList bounds; + idList jointInfo; + idList baseFrame; + idList componentFrames; + idStr name; + idVec3 totaldelta; + mutable int ref_count; + +public: + idMD5Anim(); + ~idMD5Anim(); + + void Free(); + bool Reload(); + size_t Allocated() const; + size_t Size() const { return sizeof( *this ) + Allocated(); }; + bool LoadAnim( const char *filename ); + bool LoadBinary( idFile * file, ID_TIME_T sourceTimeStamp ); + void WriteBinary( idFile * file, ID_TIME_T sourceTimeStamp ); + + void IncreaseRefs() const; + void DecreaseRefs() const; + int NumRefs() const; + + void CheckModelHierarchy( const idRenderModel *model ) const; + void GetInterpolatedFrame( frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const; + void GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const; + int Length() const; + int NumFrames() const; + int NumJoints() const; + const idVec3 &TotalMovementDelta() const; + const char *Name() const; + + void GetFrameBlend( int framenum, frameBlend_t &frame ) const; // frame 1 is first frame + void ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const; + + void GetOrigin( idVec3 &offset, int currentTime, int cyclecount ) const; + void GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const; + void GetBounds( idBounds &bounds, int currentTime, int cyclecount ) const; +}; + +/* +============================================================================================== + + idAnim + +============================================================================================== +*/ + +class idAnim { +private: + const class idDeclModelDef *modelDef; + const idMD5Anim *anims[ ANIM_MaxSyncedAnims ]; + int numAnims; + idStr name; + idStr realname; + idList frameLookup; + idList frameCommands; + animFlags_t flags; + +public: + idAnim(); + idAnim( const idDeclModelDef *modelDef, const idAnim *anim ); + ~idAnim(); + + void SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ); + const char *Name() const; + const char *FullName() const; + const idMD5Anim *MD5Anim( int num ) const; + const idDeclModelDef *ModelDef() const; + int Length() const; + int NumFrames() const; + int NumAnims() const; + const idVec3 &TotalMovementDelta() const; + bool GetOrigin( idVec3 &offset, int animNum, int time, int cyclecount ) const; + bool GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const; + bool GetBounds( idBounds &bounds, int animNum, int time, int cyclecount ) const; + const char *AddFrameCommand( const class idDeclModelDef *modelDef, int framenum, idLexer &src, const idDict *def ); + void CallFrameCommands( idEntity *ent, int from, int to ) const; + bool HasFrameCommands() const; + + // returns first frame (zero based) that command occurs. returns -1 if not found. + int FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const; + void SetAnimFlags( const animFlags_t &animflags ); + const animFlags_t &GetAnimFlags() const; +}; + +/* +============================================================================================== + + idDeclModelDef + +============================================================================================== +*/ + +class idDeclModelDef : public idDecl { +public: + idDeclModelDef(); + ~idDeclModelDef(); + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + + void Touch() const; + + const idDeclSkin * GetDefaultSkin() const; + const idJointQuat * GetDefaultPose() const; + void SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const; + idRenderModel * ModelHandle() const; + void GetJointList( const char *jointnames, idList &jointList ) const; + const jointInfo_t * FindJoint( const char *name ) const; + + int NumAnims() const; + const idAnim * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + const idDeclSkin * GetSkin() const; + const char * GetModelName() const; + const idList & Joints() const; + const int * JointParents() const; + int NumJoints() const; + const jointInfo_t * GetJoint( int jointHandle ) const; + const char * GetJointName( int jointHandle ) const; + int NumJointsOnChannel( int channel ) const; + const int * GetChannelJoints( int channel ) const; + + const idVec3 & GetVisualOffset() const; + +private: + void CopyDecl( const idDeclModelDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idVec3 offset; + idList joints; + idList jointParents; + idList channelJoints[ ANIM_NumAnimChannels ]; + idRenderModel * modelHandle; + idList anims; + const idDeclSkin * skin; +}; + +/* +============================================================================================== + + idAnimBlend + +============================================================================================== +*/ + +class idAnimBlend { +private: + const class idDeclModelDef *modelDef; + int starttime; + int endtime; + int timeOffset; + float rate; + + int blendStartTime; + int blendDuration; + float blendStartValue; + float blendEndValue; + + float animWeights[ ANIM_MaxSyncedAnims ]; + short cycle; + short frame; + short animNum; + bool allowMove; + bool allowFrameCommands; + + friend class idAnimator; + + void Reset( const idDeclModelDef *_modelDef ); + void CallFrameCommands( idEntity *ent, int fromtime, int totime ) const; + void SetFrame( const idDeclModelDef *modelDef, int animnum, int frame, int currenttime, int blendtime ); + void CycleAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime ); + void PlayAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime ); + bool BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOrigin, bool overrideBlend, bool printInfo ) const; + void BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const; + void BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const; + void BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const; + bool AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const; + +public: + idAnimBlend(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ); + const char *AnimName() const; + const char *AnimFullName() const; + float GetWeight( int currenttime ) const; + float GetFinalWeight() const; + void SetWeight( float newweight, int currenttime, int blendtime ); + int NumSyncedAnims() const; + bool SetSyncedAnimWeight( int num, float weight ); + void Clear( int currentTime, int clearTime ); + bool IsDone( int currentTime ) const; + bool FrameHasChanged( int currentTime ) const; + int GetCycleCount() const; + void SetCycleCount( int count ); + void SetPlaybackRate( int currentTime, float newRate ); + float GetPlaybackRate() const; + void SetStartTime( int startTime ); + int GetStartTime() const; + int GetEndTime() const; + int GetFrameNumber( int currenttime ) const; + int AnimTime( int currenttime ) const; + int NumFrames() const; + int Length() const; + int PlayLength() const; + void AllowMovement( bool allow ); + void AllowFrameCommands( bool allow ); + const idAnim *Anim() const; + int AnimNum() const; +}; + +/* +============================================================================================== + + idAFPoseJointMod + +============================================================================================== +*/ + +typedef enum { + AF_JOINTMOD_AXIS, + AF_JOINTMOD_ORIGIN, + AF_JOINTMOD_BOTH +} AFJointModType_t; + +class idAFPoseJointMod { +public: + idAFPoseJointMod(); + + AFJointModType_t mod; + idMat3 axis; + idVec3 origin; +}; + +ID_INLINE idAFPoseJointMod::idAFPoseJointMod() { + mod = AF_JOINTMOD_AXIS; + axis.Identity(); + origin.Zero(); +} + +/* +============================================================================================== + + idAnimator + +============================================================================================== +*/ + +class idAnimator { +public: + idAnimator(); + ~idAnimator(); + + size_t Allocated() const; + size_t Size() const; + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetEntity( idEntity *ent ); + idEntity *GetEntity() const ; + void RemoveOriginOffset( bool remove ); + bool RemoveOrigin() const; + + void GetJointList( const char *jointnames, idList &jointList ) const; + + int NumAnims() const; + const idAnim *GetAnim( int index ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + + void ServiceAnims( int fromtime, int totime ); + bool IsAnimating( int currentTime ) const; + + void GetJoints( int *numJoints, idJointMat **jointsPtr ); + int NumJoints() const; + jointHandle_t GetFirstChild( jointHandle_t jointnum ) const; + jointHandle_t GetFirstChild( const char *name ) const; + + idRenderModel *SetModel( const char *modelname ); + idRenderModel *ModelHandle() const; + const idDeclModelDef *ModelDef() const; + + void ForceUpdate(); + void ClearForceUpdate(); + bool CreateFrame( int animtime, bool force ); + bool FrameHasChanged( int animtime ) const; + void GetDelta( int fromtime, int totime, idVec3 &delta ) const; + bool GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const; + void GetOrigin( int currentTime, idVec3 &pos ) const; + bool GetBounds( int currentTime, idBounds &bounds ); + + idAnimBlend *CurrentAnim( int channelNum ); + void Clear( int channelNum, int currentTime, int cleartime ); + void SetFrame( int channelNum, int animnum, int frame, int currenttime, int blendtime ); + void CycleAnim( int channelNum, int animnum, int currenttime, int blendtime ); + void PlayAnim( int channelNum, int animnum, int currenttime, int blendTime ); + + // copies the current anim from fromChannelNum to channelNum. + // the copied anim will have frame commands disabled to avoid executing them twice. + void SyncAnimChannels( int channelNum, int fromChannelNum, int currenttime, int blendTime ); + + void SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ); + void ClearJoint( jointHandle_t jointnum ); + void ClearAllJoints(); + + void InitAFPose(); + void SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ); + void FinishAFPose( int animnum, const idBounds &bounds, const int time ); + void SetAFPoseBlendWeight( float blendWeight ); + bool BlendAFPose( idJointQuat *blendFrame ) const; + void ClearAFPose(); + + void ClearAllAnims( int currentTime, int cleartime ); + + jointHandle_t GetJointHandle( const char *name ) const; + const char * GetJointName( jointHandle_t handle ) const; + int GetChannelForJoint( jointHandle_t joint ) const; + bool GetJointTransform( jointHandle_t jointHandle, int currenttime, idVec3 &offset, idMat3 &axis ); + bool GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + + const animFlags_t GetAnimFlags( int animnum ) const; + int NumFrames( int animnum ) const; + int NumSyncedAnims( int animnum ) const; + const char *AnimName( int animnum ) const; + const char *AnimFullName( int animnum ) const; + int AnimLength( int animnum ) const; + const idVec3 &TotalMovementDelta( int animnum ) const; + +private: + void FreeData(); + void PushAnims( int channel, int currentTime, int blendTime ); + +private: + const idDeclModelDef * modelDef; + idEntity * entity; + + idAnimBlend channels[ ANIM_NumAnimChannels ][ ANIM_MaxAnimsPerChannel ]; + idList jointMods; + int numJoints; + idJointMat * joints; + + mutable int lastTransformTime; // mutable because the value is updated in CreateFrame + mutable bool stoppedAnimatingUpdate; + bool removeOriginOffset; + bool forceUpdate; + + idBounds frameBounds; + + float AFPoseBlendWeight; + idList AFPoseJoints; + idList AFPoseJointMods; + idList AFPoseJointFrame; + idBounds AFPoseBounds; + int AFPoseTime; +}; + +/* +============================================================================================== + + idAnimManager + +============================================================================================== +*/ + +class idAnimManager { +public: + idAnimManager(); + ~idAnimManager(); + + static bool forceExport; + + void Shutdown(); + idMD5Anim * GetAnim( const char *name ); + void Preload( const idPreloadManifest &manifest ); + void ReloadAnims(); + void ListAnims() const; + int JointIndex( const char *name ); + const char * JointName( int index ) const; + + void ClearAnimsInUse(); + void FlushUnusedAnims(); + +private: + idHashTable animations; + idStrList jointnames; + idHashIndex jointnamesHash; +}; + +#endif /* !__ANIM_H__ */ diff --git a/neo/d3xp/anim/Anim_Blend.cpp b/neo/d3xp/anim/Anim_Blend.cpp new file mode 100644 index 00000000..b3bbf952 --- /dev/null +++ b/neo/d3xp/anim/Anim_Blend.cpp @@ -0,0 +1,5114 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +static const char *channelNames[ ANIM_NumAnimChannels ] = { + "all", "torso", "legs", "head", "eyelids" +}; + +/*********************************************************************** + + idAnim + +***********************************************************************/ + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim() { + modelDef = NULL; + numAnims = 0; + memset( anims, 0, sizeof( anims ) ); + memset( &flags, 0, sizeof( flags ) ); +} + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim( const idDeclModelDef *modelDef, const idAnim *anim ) { + int i; + + this->modelDef = modelDef; + numAnims = anim->numAnims; + name = anim->name; + realname = anim->realname; + flags = anim->flags; + + memset( anims, 0, sizeof( anims ) ); + for( i = 0; i < numAnims; i++ ) { + anims[ i ] = anim->anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + frameLookup.SetNum( anim->frameLookup.Num() ); + memcpy( frameLookup.Ptr(), anim->frameLookup.Ptr(), frameLookup.MemoryUsed() ); + + frameCommands.SetNum( anim->frameCommands.Num() ); + for( i = 0; i < frameCommands.Num(); i++ ) { + frameCommands[ i ] = anim->frameCommands[ i ]; + if ( anim->frameCommands[ i ].string ) { + frameCommands[ i ].string = new (TAG_ANIM) idStr( *anim->frameCommands[ i ].string ); + } + } +} + +/* +===================== +idAnim::~idAnim +===================== +*/ +idAnim::~idAnim() { + int i; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + } + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + } +} + +/* +===================== +idAnim::SetAnim +===================== +*/ +void idAnim::SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ) { + int i; + + this->modelDef = modelDef; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + anims[ i ] = NULL; + } + + assert( ( num > 0 ) && ( num <= ANIM_MaxSyncedAnims ) ); + numAnims = num; + realname = sourcename; + name = animname; + + for( i = 0; i < num; i++ ) { + anims[ i ] = md5anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + memset( &flags, 0, sizeof( flags ) ); + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + } + + frameLookup.Clear(); + frameCommands.Clear(); +} + +/* +===================== +idAnim::Name +===================== +*/ +const char *idAnim::Name() const { + return name; +} + +/* +===================== +idAnim::FullName +===================== +*/ +const char *idAnim::FullName() const { + return realname; +} + +/* +===================== +idAnim::MD5Anim + +index 0 will never be NULL. Any anim >= NumAnims will return NULL. +===================== +*/ +const idMD5Anim *idAnim::MD5Anim( int num ) const { + if ( anims[0] == NULL ) { + return NULL; + } + return anims[ num ]; +} + +/* +===================== +idAnim::ModelDef +===================== +*/ +const idDeclModelDef *idAnim::ModelDef() const { + return modelDef; +} + +/* +===================== +idAnim::Length +===================== +*/ +int idAnim::Length() const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->Length(); +} + +/* +===================== +idAnim::NumFrames +===================== +*/ +int idAnim::NumFrames() const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->NumFrames(); +} + +/* +===================== +idAnim::NumAnims +===================== +*/ +int idAnim::NumAnims() const { + return numAnims; +} + +/* +===================== +idAnim::TotalMovementDelta +===================== +*/ +const idVec3 &idAnim::TotalMovementDelta() const { + if ( !anims[ 0 ] ) { + return vec3_zero; + } + + return anims[ 0 ]->TotalMovementDelta(); +} + +/* +===================== +idAnim::GetOrigin +===================== +*/ +bool idAnim::GetOrigin( idVec3 &offset, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + offset.Zero(); + return false; + } + + anims[ animNum ]->GetOrigin( offset, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetOriginRotation +===================== +*/ +bool idAnim::GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + rotation.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + return false; + } + + anims[ animNum ]->GetOriginRotation( rotation, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetBounds +===================== +*/ +ID_INLINE bool idAnim::GetBounds( idBounds &bounds, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + return false; + } + + anims[ animNum ]->GetBounds( bounds, currentTime, cyclecount ); + return true; +} + + +/* +===================== +idAnim::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *idAnim::AddFrameCommand( const idDeclModelDef *modelDef, int framenum, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + const jointInfo_t *jointInfo; + + // make sure we're within bounds + if ( ( framenum < 1 ) || ( framenum > anims[ 0 ]->NumFrames() ) ) { + return va( "Frame %d out of range", framenum ); + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_FX; + if ( !declManager->FindType( DECL_FX, token.c_str() ) ) { + return va( "fx '%s' not found", token.c_str() ); + } + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + if ( !declManager->FindType( DECL_PARTICLE, token.c_str() ) ) { + return va( "Particle '%s' not found", token.c_str() ); + } + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_MELEE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "attack_begin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_BEGINATTACK; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "attack_end" ) { + fc.type = FC_ENDATTACK; + } else if ( token == "muzzle_flash" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "create_missile" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_CREATEMISSILE; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "launch_missile" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_LAUNCHMISSILE; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "fire_missile_at_target" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + jointInfo = modelDef->FindJoint( token ); + if ( !jointInfo ) { + return va( "Joint '%s' not found", token.c_str() ); + } + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_FIREMISSILEATTARGET; + fc.string = new (TAG_ANIM) idStr( token ); + fc.index = jointInfo->num; + } else if ( token == "launch_projectile" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( !declManager->FindDeclWithoutParsing( DECL_ENTITYDEF, token, false ) ) { + return "Unknown projectile def"; + } + fc.type = FC_LAUNCH_PROJECTILE; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "trigger_fx" ) { + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + jointInfo = modelDef->FindJoint( token ); + if ( !jointInfo ) { + return va( "Joint '%s' not found", token.c_str() ); + } + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( !declManager->FindType( DECL_FX, token, false ) ) { + return "Unknown FX def"; + } + + fc.type = FC_TRIGGER_FX; + fc.string = new (TAG_ANIM) idStr( token ); + fc.index = jointInfo->num; + + } else if ( token == "start_emitter" ) { + + idStr str; + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + str = token + " "; + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + jointInfo = modelDef->FindJoint( token ); + if ( !jointInfo ) { + return va( "Joint '%s' not found", token.c_str() ); + } + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( !declManager->FindType( DECL_PARTICLE, token.c_str() ) ) { + return va( "Particle '%s' not found", token.c_str() ); + } + str += token; + fc.type = FC_START_EMITTER; + fc.string = new (TAG_ANIM) idStr( str ); + fc.index = jointInfo->num; + + } else if ( token == "stop_emitter" ) { + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_STOP_EMITTER; + fc.string = new (TAG_ANIM) idStr( token ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new (TAG_ANIM) idStr( token ); + } + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( anims[ 0 ]->NumFrames() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + + // return with no error + return NULL; +} + +/* +===================== +idAnim::CallFrameCommands +===================== +*/ +void idAnim::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + numframes = anims[ 0 ]->NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } + case FC_SCRIPTFUNCTIONOBJECT: { + gameLocal.CallObjectFrameCommand( ent, command.string->c_str() ); + break; + } + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } + case FC_SOUND: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_ANY, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_ANY, 0, false, NULL ); + } + break; + } + case FC_SOUND_VOICE: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_VOICE, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_voice' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + break; + } + case FC_SOUND_VOICE2: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_VOICE2, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_voice2' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_VOICE2, 0, false, NULL ); + } + break; + } + case FC_SOUND_BODY: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_BODY, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_body' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case FC_SOUND_BODY2: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_BODY2, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_body2' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_BODY2, 0, false, NULL ); + } + break; + } + case FC_SOUND_BODY3: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_BODY3, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_body3' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_BODY3, 0, false, NULL ); + } + break; + } + case FC_SOUND_WEAPON: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_WEAPON, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_weapon' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_WEAPON, 0, false, NULL ); + } + break; + } + case FC_SOUND_GLOBAL: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_global' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + break; + } + case FC_SOUND_ITEM: { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_ITEM, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_item' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_ITEM, 0, false, NULL ); + } + break; + } + case FC_SOUND_CHATTER: { + if ( ent->CanPlayChatterSounds() ) { + if ( !command.soundShader ) { + if ( !ent->StartSound( command.string->c_str(), SND_CHANNEL_VOICE, 0, false, NULL ) ) { + gameLocal.Warning( "Framecommand 'sound_chatter' on entity '%s', anim '%s', frame %d: Could not find sound '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + } else { + ent->StartSoundShader( command.soundShader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + break; + } + case FC_FX: { + idEntityFx::StartFx( command.string->c_str(), NULL, NULL, ent, true ); + break; + } + case FC_SKIN: { + ent->SetSkin( command.skin ); + break; + } + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + SetTimeState ts(target->timeGroup); + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + case FC_TRIGGER_SMOKE_PARTICLE: { + ent->ProcessEvent( &AI_TriggerParticles, command.string->c_str() ); + break; + } + case FC_MELEE: { + ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_BEGINATTACK: { + ent->ProcessEvent( &AI_BeginAttack, command.string->c_str() ); + break; + } + case FC_ENDATTACK: { + ent->ProcessEvent( &AI_EndAttack ); + break; + } + case FC_MUZZLEFLASH: { + ent->ProcessEvent( &AI_MuzzleFlash, command.string->c_str() ); + break; + } + case FC_CREATEMISSILE: { + ent->ProcessEvent( &AI_CreateMissile, command.string->c_str() ); + break; + } + case FC_LAUNCHMISSILE: { + ent->ProcessEvent( &AI_AttackMissile, command.string->c_str() ); + break; + } + case FC_FIREMISSILEATTARGET: { + ent->ProcessEvent( &AI_FireMissileAtTarget, modelDef->GetJointName( command.index ), command.string->c_str() ); + break; + } + case FC_LAUNCH_PROJECTILE: { + ent->ProcessEvent( &AI_LaunchProjectile, command.string->c_str() ); + break; + } + case FC_TRIGGER_FX: { + ent->ProcessEvent( &AI_TriggerFX, modelDef->GetJointName( command.index ), command.string->c_str() ); + break; + } + case FC_START_EMITTER: { + int index = command.string->Find(" "); + if(index >= 0) { + idStr name = command.string->Left(index); + idStr particle = command.string->Right(command.string->Length() - index - 1); + ent->ProcessEvent( &AI_StartEmitter, name.c_str(), modelDef->GetJointName( command.index ), particle.c_str() ); + } + } + + case FC_STOP_EMITTER: { + ent->ProcessEvent( &AI_StopEmitter, command.string->c_str() ); + } + case FC_FOOTSTEP : { + ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { + ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { + ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { + ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { + ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { + ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { + ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + } + } + } +} + +/* +===================== +idAnim::FindFrameForFrameCommand +===================== +*/ +int idAnim::FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const { + int frame; + int index; + int numframes; + int end; + + if ( !frameCommands.Num() ) { + return -1; + } + + numframes = anims[ 0 ]->NumFrames(); + for( frame = 0; frame < numframes; frame++ ) { + end = frameLookup[ frame ].firstCommand + frameLookup[ frame ].num; + for( index = frameLookup[ frame ].firstCommand; index < end; index++ ) { + if ( frameCommands[ index ].type == framecommand ) { + if ( command ) { + *command = &frameCommands[ index ]; + } + return frame; + } + } + } + + if ( command ) { + *command = NULL; + } + + return -1; +} + +/* +===================== +idAnim::HasFrameCommands +===================== +*/ +bool idAnim::HasFrameCommands() const { + if ( !frameCommands.Num() ) { + return false; + } + return true; +} + +/* +===================== +idAnim::SetAnimFlags +===================== +*/ +void idAnim::SetAnimFlags( const animFlags_t &animflags ) { + flags = animflags; +} + +/* +===================== +idAnim::GetAnimFlags +===================== +*/ +const animFlags_t &idAnim::GetAnimFlags() const { + return flags; +} + +/*********************************************************************** + + idAnimBlend + +***********************************************************************/ + +/* +===================== +idAnimBlend::idAnimBlend +===================== +*/ +idAnimBlend::idAnimBlend() { + Reset( NULL ); +} + +/* +===================== +idAnimBlend::Save + +archives object for save game file +===================== +*/ +void idAnimBlend::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( starttime ); + savefile->WriteInt( endtime ); + savefile->WriteInt( timeOffset ); + savefile->WriteFloat( rate ); + + savefile->WriteInt( blendStartTime ); + savefile->WriteInt( blendDuration ); + savefile->WriteFloat( blendStartValue ); + savefile->WriteFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->WriteFloat( animWeights[ i ] ); + } + savefile->WriteShort( cycle ); + savefile->WriteShort( frame ); + savefile->WriteShort( animNum ); + savefile->WriteBool( allowMove ); + savefile->WriteBool( allowFrameCommands ); +} + +/* +===================== +idAnimBlend::Restore + +unarchives object from save game file +===================== +*/ +void idAnimBlend::Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ) { + int i; + + this->modelDef = modelDef; + + savefile->ReadInt( starttime ); + savefile->ReadInt( endtime ); + savefile->ReadInt( timeOffset ); + savefile->ReadFloat( rate ); + + savefile->ReadInt( blendStartTime ); + savefile->ReadInt( blendDuration ); + savefile->ReadFloat( blendStartValue ); + savefile->ReadFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->ReadFloat( animWeights[ i ] ); + } + savefile->ReadShort( cycle ); + savefile->ReadShort( frame ); + savefile->ReadShort( animNum ); + if ( !modelDef ) { + animNum = 0; + } else if ( ( animNum < 0 ) || ( animNum > modelDef->NumAnims() ) ) { + gameLocal.Warning( "Anim number %d out of range for model '%s' during save game", animNum, modelDef->GetModelName() ); + animNum = 0; + } + savefile->ReadBool( allowMove ); + savefile->ReadBool( allowFrameCommands ); +} + +/* +===================== +idAnimBlend::Reset +===================== +*/ +void idAnimBlend::Reset( const idDeclModelDef *_modelDef ) { + modelDef = _modelDef; + cycle = 1; + starttime = 0; + endtime = 0; + timeOffset = 0; + rate = 1.0f; + frame = 0; + allowMove = true; + allowFrameCommands = true; + animNum = 0; + + memset( animWeights, 0, sizeof( animWeights ) ); + + blendStartValue = 0.0f; + blendEndValue = 0.0f; + blendStartTime = 0; + blendDuration = 0; +} + +/* +===================== +idAnimBlend::FullName +===================== +*/ +const char *idAnimBlend::AnimFullName() const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} + +/* +===================== +idAnimBlend::AnimName +===================== +*/ +const char *idAnimBlend::AnimName() const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->Name(); +} + +/* +===================== +idAnimBlend::NumFrames +===================== +*/ +int idAnimBlend::NumFrames() const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumFrames(); +} + +/* +===================== +idAnimBlend::Length +===================== +*/ +int idAnimBlend::Length() const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->Length(); +} + +/* +===================== +idAnimBlend::GetWeight +===================== +*/ +float idAnimBlend::GetWeight( int currentTime ) const { + int timeDelta; + float frac; + float w; + + timeDelta = currentTime - blendStartTime; + if ( timeDelta <= 0 ) { + w = blendStartValue; + } else if ( timeDelta >= blendDuration ) { + w = blendEndValue; + } else { + frac = ( float )timeDelta / ( float )blendDuration; + w = blendStartValue + ( blendEndValue - blendStartValue ) * frac; + } + + return w; +} + +/* +===================== +idAnimBlend::GetFinalWeight +===================== +*/ +float idAnimBlend::GetFinalWeight() const { + return blendEndValue; +} + +/* +===================== +idAnimBlend::SetWeight +===================== +*/ +void idAnimBlend::SetWeight( float newweight, int currentTime, int blendTime ) { + blendStartValue = GetWeight( currentTime ); + blendEndValue = newweight; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + + if ( !newweight ) { + endtime = currentTime + blendTime; + } +} + +/* +===================== +idAnimBlend::NumSyncedAnims +===================== +*/ +int idAnimBlend::NumSyncedAnims() const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumAnims(); +} + +/* +===================== +idAnimBlend::SetSyncedAnimWeight +===================== +*/ +bool idAnimBlend::SetSyncedAnimWeight( int num, float weight ) { + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + if ( ( num < 0 ) || ( num > anim->NumAnims() ) ) { + return false; + } + + animWeights[ num ] = weight; + return true; +} + +/* +===================== +idAnimBlend::SetFrame +===================== +*/ +void idAnimBlend::SetFrame( const idDeclModelDef *modelDef, int _animNum, int _frame, int currentTime, int blendTime ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { + gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", modelDef->GetModelName(), md5anim->Name() ); + return; + } + + animNum = _animNum; + starttime = currentTime; + endtime = -1; + cycle = -1; + animWeights[ 0 ] = 1.0f; + frame = _frame; + + // a frame of 0 means it's not a single frame blend, so we set it to frame + 1 + if ( frame <= 0 ) { + frame = 1; + } else if ( frame > _anim->NumFrames() ) { + frame = _anim->NumFrames(); + } + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; +} + +/* +===================== +idAnimBlend::CycleAnim +===================== +*/ +void idAnimBlend::CycleAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { + gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", modelDef->GetModelName(), md5anim->Name() ); + return; + } + + animNum = _animNum; + animWeights[ 0 ] = 1.0f; + endtime = -1; + cycle = -1; + if ( _anim->GetAnimFlags().random_cycle_start ) { + // start the animation at a random time so that characters don't walk in sync + starttime = currentTime - gameLocal.random.RandomFloat() * _anim->Length(); + } else { + starttime = currentTime; + } + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; +} + +/* +===================== +idAnimBlend::PlayAnim +===================== +*/ +void idAnimBlend::PlayAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { + gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", modelDef->GetModelName(), md5anim->Name() ); + return; + } + + animNum = _animNum; + starttime = currentTime; + endtime = starttime + _anim->Length(); + cycle = 1; + animWeights[ 0 ] = 1.0f; + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; +} + +/* +===================== +idAnimBlend::Clear +===================== +*/ +void idAnimBlend::Clear( int currentTime, int clearTime ) { + if ( !clearTime ) { + Reset( modelDef ); + } else { + SetWeight( 0.0f, currentTime, clearTime ); + } +} + +/* +===================== +idAnimBlend::IsDone +===================== +*/ +bool idAnimBlend::IsDone( int currentTime ) const { + if ( !frame && ( endtime > 0 ) && ( currentTime >= endtime ) ) { + return true; + } + + if ( ( blendEndValue <= 0.0f ) && ( currentTime >= ( blendStartTime + blendDuration ) ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimBlend::FrameHasChanged +===================== +*/ +bool idAnimBlend::FrameHasChanged( int currentTime ) const { + // if we don't have an anim, no change + if ( !animNum ) { + return false; + } + + // if anim is done playing, no change + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + // if our blend weight changes, we need to update + if ( ( currentTime < ( blendStartTime + blendDuration ) && ( blendStartValue != blendEndValue ) ) ) { + return true; + } + + // if we're a single frame anim and this isn't the frame we started on, we don't need to update + if ( ( frame || ( NumFrames() == 1 ) ) && ( currentTime != starttime ) ) { + return false; + } + + return true; +} + +/* +===================== +idAnimBlend::GetCycleCount +===================== +*/ +int idAnimBlend::GetCycleCount() const { + return cycle; +} + +/* +===================== +idAnimBlend::SetCycleCount +===================== +*/ +void idAnimBlend::SetCycleCount( int count ) { + const idAnim *anim = Anim(); + + if ( !anim ) { + cycle = -1; + endtime = 0; + } else { + cycle = count; + if ( cycle < 0 ) { + cycle = -1; + endtime = -1; + } else if ( cycle == 0 ) { + cycle = 1; + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length(); + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + anim->Length() / rate; + } else { + endtime = -1; + } + } else { + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length() * cycle; + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + ( anim->Length() * cycle ) / rate; + } else { + endtime = -1; + } + } + } +} + +/* +===================== +idAnimBlend::SetPlaybackRate +===================== +*/ +void idAnimBlend::SetPlaybackRate( int currentTime, float newRate ) { + int animTime; + + if ( rate == newRate ) { + return; + } + + animTime = AnimTime( currentTime ); + if ( newRate == 1.0f ) { + timeOffset = animTime - ( currentTime - starttime ); + } else { + timeOffset = animTime - ( currentTime - starttime ) * newRate; + } + + rate = newRate; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetPlaybackRate +===================== +*/ +float idAnimBlend::GetPlaybackRate() const { + return rate; +} + +/* +===================== +idAnimBlend::SetStartTime +===================== +*/ +void idAnimBlend::SetStartTime( int _startTime ) { + starttime = _startTime; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetStartTime +===================== +*/ +int idAnimBlend::GetStartTime() const { + if ( !animNum ) { + return 0; + } + + return starttime; +} + +/* +===================== +idAnimBlend::GetEndTime +===================== +*/ +int idAnimBlend::GetEndTime() const { + if ( !animNum ) { + return 0; + } + + return endtime; +} + +/* +===================== +idAnimBlend::PlayLength +===================== +*/ +int idAnimBlend::PlayLength() const { + if ( !animNum ) { + return 0; + } + + if ( endtime < 0 ) { + return -1; + } + + return endtime - starttime + timeOffset; +} + +/* +===================== +idAnimBlend::AllowMovement +===================== +*/ +void idAnimBlend::AllowMovement( bool allow ) { + allowMove = allow; +} + +/* +===================== +idAnimBlend::AllowFrameCommands +===================== +*/ +void idAnimBlend::AllowFrameCommands( bool allow ) { + allowFrameCommands = allow; +} + + +/* +===================== +idAnimBlend::Anim +===================== +*/ +const idAnim *idAnimBlend::Anim() const { + if ( !modelDef ) { + return NULL; + } + + const idAnim *anim = modelDef->GetAnim( animNum ); + return anim; +} + +/* +===================== +idAnimBlend::AnimNum +===================== +*/ +int idAnimBlend::AnimNum() const { + return animNum; +} + +/* +===================== +idAnimBlend::AnimTime +===================== +*/ +int idAnimBlend::AnimTime( int currentTime ) const { + int time; + int length; + const idAnim *anim = Anim(); + + if ( anim ) { + if ( frame ) { + return FRAME2MS( frame - 1 ); + } + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + time = currentTime - starttime + timeOffset; + } else { + time = static_cast( ( currentTime - starttime ) * rate ) + timeOffset; + } + + // given enough time, we can easily wrap time around in our frame calculations, so + // keep cycling animations' time within the length of the anim. + length = anim->Length(); + if ( ( cycle < 0 ) && ( length > 0 ) ) { + time %= length; + + // time will wrap after 24 days (oh no!), resulting in negative results for the %. + // adding the length gives us the proper result. + if ( time < 0 ) { + time += length; + } + } + return time; + } else { + return 0; + } +} + +/* +===================== +idAnimBlend::GetFrameNumber +===================== +*/ +int idAnimBlend::GetFrameNumber( int currentTime ) const { + const idMD5Anim *md5anim; + frameBlend_t frameinfo; + int animTime; + + const idAnim *anim = Anim(); + if ( !anim ) { + return 1; + } + + if ( frame ) { + return frame; + } + + md5anim = anim->MD5Anim( 0 ); + animTime = AnimTime( currentTime ); + md5anim->ConvertTimeToFrame( animTime, cycle, frameinfo ); + + return frameinfo.frame1 + 1; +} + +/* +===================== +idAnimBlend::CallFrameCommands +===================== +*/ +void idAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + const idMD5Anim *md5anim; + frameBlend_t frame1; + frameBlend_t frame2; + int fromFrameTime; + int toFrameTime; + + if ( !allowFrameCommands || !ent || frame || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->HasFrameCommands() ) { + return; + } + + if ( totime <= starttime ) { + // don't play until next frame or we'll play commands twice. + // this happens on the player sometimes. + return; + } + + fromFrameTime = AnimTime( fromtime ); + toFrameTime = AnimTime( totime ); + if ( toFrameTime < fromFrameTime ) { + toFrameTime += anim->Length(); + } + + md5anim = anim->MD5Anim( 0 ); + md5anim->ConvertTimeToFrame( fromFrameTime, cycle, frame1 ); + md5anim->ConvertTimeToFrame( toFrameTime, cycle, frame2 ); + + if ( fromFrameTime <= 0 ) { + // make sure first frame is called + anim->CallFrameCommands( ent, -1, frame2.frame1 ); + } else { + anim->CallFrameCommands( ent, frame1.frame1, frame2.frame1 ); + } +} + +/* +===================== +idAnimBlend::BlendAnim +===================== +*/ +bool idAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + int i; + float lerp; + float mixWeight; + const idMD5Anim *md5anim; + idJointQuat *ptr; + frameBlend_t frametime = { 0 }; + idJointQuat *jointFrame; + idJointQuat *mixFrame; + int numAnims; + int time; + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( blendWeight > 0.0f ) { + if ( ( endtime >= 0 ) && ( currentTime >= endtime ) ) { + return false; + } + if ( !weight ) { + return false; + } + if ( overrideBlend ) { + blendWeight = 1.0f - weight; + } + } + + if ( ( channel == ANIMCHANNEL_ALL ) && !blendWeight ) { + // we don't need a temporary buffer, so just store it directly in the blend frame + jointFrame = blendFrame; + } else { + // allocate a temporary buffer to copy the joints from + jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + } + + time = AnimTime( currentTime ); + + numAnims = anim->NumAnims(); + if ( numAnims == 1 ) { + md5anim = anim->MD5Anim( 0 ); + if ( frame ) { + md5anim->GetSingleFrame( frame - 1, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->ConvertTimeToFrame( time, cycle, frametime ); + md5anim->GetInterpolatedFrame( frametime, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + } else { + // + // need to mix the multipoint anim together first + // + // allocate a temporary buffer to copy the joints to + mixFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + + if ( !frame ) { + anim->MD5Anim( 0 )->ConvertTimeToFrame( time, cycle, frametime ); + } + + ptr = jointFrame; + mixWeight = 0.0f; + for( i = 0; i < numAnims; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + lerp = animWeights[ i ] / mixWeight; + md5anim = anim->MD5Anim( i ); + if ( frame ) { + md5anim->GetSingleFrame( frame - 1, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->GetInterpolatedFrame( frametime, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + // only blend after the first anim is mixed in + if ( ptr != jointFrame ) { + SIMDProcessor->BlendJoints( jointFrame, ptr, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + ptr = mixFrame; + } + } + + if ( !mixWeight ) { + return false; + } + } + + if ( removeOriginOffset ) { + if ( allowMove ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + if ( anim->GetAnimFlags().anim_turn ) { + jointFrame[ 0 ].q.Set( -0.70710677f, 0.0f, 0.0f, 0.70710677f ); + } + } + + if ( !blendWeight ) { + blendWeight = weight; + if ( channel != ANIMCHANNEL_ALL ) { + const int *index = modelDef->GetChannelJoints( channel ); + const int num = modelDef->NumJointsOnChannel( channel ); + for( i = 0; i < num; i++ ) { + int j = index[i]; + blendFrame[j].t = jointFrame[j].t; + blendFrame[j].q = jointFrame[j].q; + } + } + } else { + blendWeight += weight; + lerp = weight / blendWeight; + SIMDProcessor->BlendJoints( blendFrame, jointFrame, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + if ( printInfo ) { + if ( frame ) { + gameLocal.Printf( " %s: '%s', %d, %.2f%%\n", channelNames[ channel ], anim->FullName(), frame, weight * 100.0f ); + } else { + gameLocal.Printf( " %s: '%s', %.3f, %.2f%%\n", channelNames[ channel ], anim->FullName(), ( float )frametime.frame1 + frametime.backlerp, weight * 100.0f ); + } + } + + return true; +} + +/* +===================== +idAnimBlend::BlendOrigin +===================== +*/ +void idAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + float lerp; + idVec3 animpos; + idVec3 pos; + int time; + int num; + int i; + + if ( frame || ( ( endtime > 0 ) && ( currentTime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + if ( allowMove && removeOriginOffset ) { + return; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return; + } + + time = AnimTime( currentTime ); + + pos.Zero(); + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time, cycle ); + pos += animpos * animWeights[ i ]; + } + + if ( !blendWeight ) { + blendPos = pos; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendPos += lerp * ( pos - blendPos ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDelta +===================== +*/ +void idAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + idVec3 pos1; + idVec3 pos2; + idVec3 animpos; + idVec3 delta; + int time1; + int time2; + float lerp; + int num; + int i; + + if ( frame || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + num = anim->NumAnims(); + + pos1.Zero(); + pos2.Zero(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time1, cycle ); + pos1 += animpos * animWeights[ i ]; + + anim->GetOrigin( animpos, i, time2, cycle ); + pos2 += animpos * animWeights[ i ]; + } + + delta = pos2 - pos1; + if ( !blendWeight ) { + blendDelta = delta; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta += lerp * ( delta - blendDelta ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDeltaRotation +===================== +*/ +void idAnimBlend::BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const { + idQuat q1; + idQuat q2; + idQuat q3; + int time1; + int time2; + float lerp; + float mixWeight; + int num; + int i; + + if ( frame || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->GetAnimFlags().anim_turn ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + q1.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + q2.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + + mixWeight = 0.0f; + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + if ( animWeights[ i ] == mixWeight ) { + anim->GetOriginRotation( q1, i, time1, cycle ); + anim->GetOriginRotation( q2, i, time2, cycle ); + } else { + lerp = animWeights[ i ] / mixWeight; + anim->GetOriginRotation( q3, i, time1, cycle ); + q1.Slerp( q1, q3, lerp ); + + anim->GetOriginRotation( q3, i, time2, cycle ); + q2.Slerp( q1, q3, lerp ); + } + } + } + + q3 = q1.Inverse() * q2; + if ( !blendWeight ) { + blendDelta = q3; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta.Slerp( blendDelta, q3, lerp ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::AddBounds +===================== +*/ +bool idAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + int i; + int num; + idBounds b; + int time; + idVec3 pos; + bool addorigin; + + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return false; + } + + time = AnimTime( currentTime ); + num = anim->NumAnims(); + + addorigin = !allowMove || !removeOriginOffset; + for( i = 0; i < num; i++ ) { + if ( anim->GetBounds( b, i, time, cycle ) ) { + if ( addorigin ) { + anim->GetOrigin( pos, i, time, cycle ); + b.TranslateSelf( pos ); + } + bounds.AddBounds( b ); + } + } + + return true; +} + +/*********************************************************************** + + idDeclModelDef + +***********************************************************************/ + +/* +===================== +idDeclModelDef::idDeclModelDef +===================== +*/ +idDeclModelDef::idDeclModelDef() { + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +} + +/* +===================== +idDeclModelDef::~idDeclModelDef +===================== +*/ +idDeclModelDef::~idDeclModelDef() { + FreeData(); +} + +/* +================= +idDeclModelDef::Size +================= +*/ +size_t idDeclModelDef::Size() const { + return sizeof( idDeclModelDef ); +} + +/* +===================== +idDeclModelDef::CopyDecl +===================== +*/ +void idDeclModelDef::CopyDecl( const idDeclModelDef *decl ) { + int i; + + FreeData(); + + offset = decl->offset; + modelHandle = decl->modelHandle; + skin = decl->skin; + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new (TAG_ANIM) idAnim( this, decl->anims[ i ] ); + } + + joints.SetNum( decl->joints.Num() ); + memcpy( joints.Ptr(), decl->joints.Ptr(), decl->joints.Num() * sizeof( joints[0] ) ); + jointParents.SetNum( decl->jointParents.Num() ); + memcpy( jointParents.Ptr(), decl->jointParents.Ptr(), decl->jointParents.Num() * sizeof( jointParents[0] ) ); + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i] = decl->channelJoints[i]; + } +} + +/* +===================== +idDeclModelDef::FreeData +===================== +*/ +void idDeclModelDef::FreeData() { + anims.DeleteContents( true ); + joints.Clear(); + jointParents.Clear(); + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +} + +/* +================ +idDeclModelDef::DefaultDefinition +================ +*/ +const char *idDeclModelDef::DefaultDefinition() const { + return "{ }"; +} + +/* +==================== +idDeclModelDef::FindJoint +==================== +*/ +const jointInfo_t *idDeclModelDef::FindJoint( const char *name ) const { + int i; + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + joint = modelHandle->GetJoints(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( !joint->name.Icmp( name ) ) { + return &joints[ i ]; + } + } + + return NULL; +} + +/* +===================== +idDeclModelDef::ModelHandle +===================== +*/ +idRenderModel *idDeclModelDef::ModelHandle() const { + return ( idRenderModel * )modelHandle; +} + +/* +===================== +idDeclModelDef::GetJointList +===================== +*/ +void idDeclModelDef::GetJointList( const char *jointnames, idList &jointList ) const { + const char *pos; + idStr jointname; + const jointInfo_t *joint; + const jointInfo_t *child; + int i; + int num; + bool getChildren; + bool subtract; + + if ( !modelHandle ) { + return; + } + + jointList.Clear(); + + num = modelHandle->NumJoints(); + + // scan through list of joints and add each to the joint list + pos = jointnames; + while( *pos ) { + // skip over whitespace + while( ( *pos != 0 ) && isspace( (unsigned char)*pos ) ) { + pos++; + } + + if ( !*pos ) { + // no more names + break; + } + + // copy joint name + jointname = ""; + + if ( *pos == '-' ) { + subtract = true; + pos++; + } else { + subtract = false; + } + + if ( *pos == '*' ) { + getChildren = true; + pos++; + } else { + getChildren = false; + } + + while( ( *pos != 0 ) && !isspace( (unsigned char)*pos ) ) { + jointname += *pos; + pos++; + } + + joint = FindJoint( jointname ); + if ( !joint ) { + gameLocal.Warning( "Unknown joint '%s' in '%s' for model '%s'", jointname.c_str(), jointnames, GetName() ); + continue; + } + + if ( !subtract ) { + jointList.AddUnique( joint->num ); + } else { + jointList.Remove( joint->num ); + } + + if ( getChildren ) { + // include all joint's children + child = joint + 1; + for( i = joint->num + 1; i < num; i++, child++ ) { + // all children of the joint should follow it in the list. + // once we reach a joint without a parent or with a parent + // who is earlier in the list than the specified joint, then + // we've gone through all it's children. + if ( child->parentNum < joint->num ) { + break; + } + + if ( !subtract ) { + jointList.AddUnique( child->num ); + } else { + jointList.Remove( child->num ); + } + } + } + } +} + +/* +===================== +idDeclModelDef::Touch +===================== +*/ +void idDeclModelDef::Touch() const { + if ( modelHandle ) { + renderModelManager->FindModel( modelHandle->Name() ); + } +} + +/* +===================== +idDeclModelDef::GetDefaultSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetDefaultSkin() const { + return skin; +} + +/* +===================== +idDeclModelDef::GetDefaultPose +===================== +*/ +const idJointQuat *idDeclModelDef::GetDefaultPose() const { + return modelHandle->GetDefaultPose(); +} + +/* +===================== +idDeclModelDef::SetupJoints +===================== +*/ +void idDeclModelDef::SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const { + int num; + const idJointQuat *pose; + idJointMat *list; + + if ( !modelHandle || modelHandle->IsDefaultModel() ) { + Mem_Free16( (*jointList) ); + (*jointList) = NULL; + frameBounds.Clear(); + return; + } + + // get the number of joints + num = modelHandle->NumJoints(); + + if ( !num ) { + gameLocal.Error( "model '%s' has no joints", modelHandle->Name() ); + } + + // set up initial pose for model (with no pose, model is just a jumbled mess) + list = (idJointMat *) Mem_Alloc16( SIMD_ROUND_JOINTS( num ) * sizeof( list[0] ), TAG_JOINTMAT ); + pose = GetDefaultPose(); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( list, pose, joints.Num() ); + + // check if we offset the model by the origin joint + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + list[ 0 ].SetTranslation( idVec3( offset.x, offset.y + pose[0].t.y, offset.z + pose[0].t.z ) ); +#else + list[ 0 ].SetTranslation( offset ); +#endif + } else { + list[ 0 ].SetTranslation( pose[0].t + offset ); + } + + // transform the joint hierarchy + SIMDProcessor->TransformJoints( list, jointParents.Ptr(), 1, joints.Num() - 1 ); + + SIMD_INIT_LAST_JOINT( list, num ); + + *numJoints = num; + *jointList = list; + + // get the bounds of the default pose + frameBounds = modelHandle->Bounds( NULL ); +} + +/* +===================== +idDeclModelDef::ParseAnim +===================== +*/ +bool idDeclModelDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + idAnim *anim; + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + const idMD5Anim *md5anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + animFlags_t flags; + + numAnims = 0; + memset( md5anims, 0, sizeof( md5anims ) ); + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !strcmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new (TAG_ANIM) idAnim(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( (unsigned char)alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + do { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + // lookup the animation + md5anim = animationLib.GetAnim( token ); + if ( !md5anim ) { + src.Warning( "Couldn't load anim '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + md5anim->CheckModelHierarchy( modelHandle ); + + if ( numAnims > 0 ) { + // make sure it's the same length as the other anims + if ( md5anim->Length() != md5anims[ 0 ]->Length() ) { + src.Warning( "Anim '%s' does not match length of anim '%s'", md5anim->Name(), md5anims[ 0 ]->Name() ); + MakeDefault(); + return false; + } + } + + if ( numAnims >= ANIM_MaxSyncedAnims ) { + src.Warning( "Exceeded max synced anims (%d)", ANIM_MaxSyncedAnims ); + MakeDefault(); + return false; + } + + // add it to our list + md5anims[ numAnims ] = md5anim; + numAnims++; + } while ( src.CheckTokenString( "," ) ); + + if ( !numAnims ) { + src.Warning( "No animation specified" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, numAnims, md5anims ); + memset( &flags, 0, sizeof( flags ) ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + }else if ( token == "prevent_idle_override" ) { + flags.prevent_idle_override = true; + } else if ( token == "random_cycle_start" ) { + flags.random_cycle_start = true; + } else if ( token == "ai_no_turn" ) { + flags.ai_no_turn = true; + } else if ( token == "anim_turn" ) { + flags.anim_turn = true; + } else if ( token == "frame" ) { + // create a frame command + int framenum; + const char *err; + + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } + + // get the frame number + framenum = token.GetIntValue(); + + // put the command on the specified frame of the animation + err = anim->AddFrameCommand( this, framenum, src, NULL ); + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + // set the flags + anim->SetAnimFlags( flags ); + return true; +} + +/* +================ +idDeclModelDef::Parse +================ +*/ +bool idDeclModelDef::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + int i; + int num; + idStr filename; + idStr extension; + const idMD5Joint *md5joint; + const idMD5Joint *md5joints; + idLexer src; + idToken token; + idToken token2; + idStr jointnames; + int channel; + jointHandle_t jointnum; + idList jointList; + int numDefaultAnims; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( token == "inherit" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + const idDeclModelDef *copy = static_cast( declManager->FindType( DECL_MODELDEF, token2, false ) ); + if ( !copy ) { + common->Warning( "Unknown model definition '%s'", token2.c_str() ); + } else if ( copy->GetState() == DS_DEFAULTED ) { + common->Warning( "inherited model definition '%s' defaulted", token2.c_str() ); + MakeDefault(); + return false; + } else { + CopyDecl( copy ); + numDefaultAnims = anims.Num(); + } + } else if ( token == "skin" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + skin = declManager->FindSkin( token2 ); + if ( !skin ) { + src.Warning( "Skin '%s' not found", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "mesh" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + filename = token2; + filename.ExtractFileExtension( extension ); + if ( extension != MD5_MESH_EXT ) { + src.Warning( "Invalid model for MD5 mesh" ); + MakeDefault(); + return false; + } + modelHandle = renderModelManager->FindModel( filename ); + if ( !modelHandle ) { + src.Warning( "Model '%s' not found", filename.c_str() ); + MakeDefault(); + return false; + } + + if ( modelHandle->IsDefaultModel() ) { + src.Warning( "Model '%s' defaulted", filename.c_str() ); + MakeDefault(); + return false; + } + + // get the number of joints + num = modelHandle->NumJoints(); + if ( !num ) { + src.Warning( "Model '%s' has no joints", filename.c_str() ); + } + + // set up the joint hierarchy + joints.SetGranularity( 1 ); + joints.SetNum( num ); + jointParents.SetNum( num ); + channelJoints[0].SetNum( num ); + md5joints = modelHandle->GetJoints(); + md5joint = md5joints; + for( i = 0; i < num; i++, md5joint++ ) { + joints[i].channel = ANIMCHANNEL_ALL; + joints[i].num = static_cast( i ); + if ( md5joint->parent ) { + joints[i].parentNum = static_cast( md5joint->parent - md5joints ); + } else { + joints[i].parentNum = INVALID_JOINT; + } + jointParents[i] = joints[i].parentNum; + channelJoints[0][i] = i; + } + } else if ( token == "remove" ) { + // removes any anims whos name matches + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + num = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( ( token2 == anims[ i ]->Name() ) || ( token2 == anims[ i ]->FullName() ) ) { + delete anims[ i ]; + anims.RemoveIndex( i ); + if ( i >= numDefaultAnims ) { + src.Warning( "Anim '%s' was not inherited. Anim should be removed from the model def.", token2.c_str() ); + MakeDefault(); + return false; + } + i--; + numDefaultAnims--; + num++; + continue; + } + } + if ( !num ) { + src.Warning( "Couldn't find anim '%s' to remove", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "anim" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining anims" ); + MakeDefault(); + return false; + } + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else if ( token == "offset" ) { + if ( !src.Parse1DMatrix( 3, offset.ToFloatPtr() ) ) { + src.Warning( "Expected vector following 'offset'" ); + MakeDefault(); + return false; + } + } else if ( token == "channel" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining channels" ); + MakeDefault(); + return false; + } + + // set the channel for a group of joints + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( !src.CheckTokenString( "(" ) ) { + src.Warning( "Expected { after '%s'\n", token2.c_str() ); + MakeDefault(); + return false; + } + + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !idStr::Icmp( channelNames[ i ], token2 ) ) { + break; + } + } + + if ( i >= ANIM_NumAnimChannels ) { + src.Warning( "Unknown channel '%s'", token2.c_str() ); + MakeDefault(); + return false; + } + + channel = i; + jointnames = ""; + + while( !src.CheckTokenString( ")" ) ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + jointnames += token2; + if ( ( token2 != "*" ) && ( token2 != "-" ) ) { + jointnames += " "; + } + } + + GetJointList( jointnames, jointList ); + + channelJoints[ channel ].SetNum( jointList.Num() ); + for( num = i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + if ( joints[ jointnum ].channel != ANIMCHANNEL_ALL ) { + src.Warning( "Joint '%s' assigned to multiple channels", modelHandle->GetJointName( jointnum ) ); + continue; + } + joints[ jointnum ].channel = channel; + channelJoints[ channel ][ num++ ] = jointnum; + } + channelJoints[ channel ].SetNum( num ); + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclModelDef::HasAnim +===================== +*/ +bool idDeclModelDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !strcmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclModelDef::NumAnims +===================== +*/ +int idDeclModelDef::NumAnims() const { + return anims.Num() + 1; +} + +/* +===================== +idDeclModelDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclModelDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !strcmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclModelDef::GetAnim +===================== +*/ +const idAnim *idDeclModelDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + + return anims[ index - 1 ]; +} + +/* +===================== +idDeclModelDef::GetAnim +===================== +*/ +int idDeclModelDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !strcmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + +/* +===================== +idDeclModelDef::GetSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetSkin() const { + return skin; +} + +/* +===================== +idDeclModelDef::GetModelName +===================== +*/ +const char *idDeclModelDef::GetModelName() const { + if ( modelHandle ) { + return modelHandle->Name(); + } else { + return ""; + } +} + +/* +===================== +idDeclModelDef::Joints +===================== +*/ +const idList &idDeclModelDef::Joints() const { + return joints; +} + +/* +===================== +idDeclModelDef::JointParents +===================== +*/ +const int * idDeclModelDef::JointParents() const { + return jointParents.Ptr(); +} + +/* +===================== +idDeclModelDef::NumJoints +===================== +*/ +int idDeclModelDef::NumJoints() const { + return joints.Num(); +} + +/* +===================== +idDeclModelDef::GetJoint +===================== +*/ +const jointInfo_t *idDeclModelDef::GetJoint( int jointHandle ) const { + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJoint : joint handle out of range" ); + } + return &joints[ jointHandle ]; +} + +/* +==================== +idDeclModelDef::GetJointName +==================== +*/ +const char *idDeclModelDef::GetJointName( int jointHandle ) const { + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJointName : joint handle out of range" ); + } + + joint = modelHandle->GetJoints(); + return joint[ jointHandle ].name.c_str(); +} + +/* +===================== +idDeclModelDef::NumJointsOnChannel +===================== +*/ +int idDeclModelDef::NumJointsOnChannel( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::NumJointsOnChannel : channel out of range" ); + return 0; + } + return channelJoints[ channel ].Num(); +} + +/* +===================== +idDeclModelDef::GetChannelJoints +===================== +*/ +const int * idDeclModelDef::GetChannelJoints( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::GetChannelJoints : channel out of range" ); + return NULL; + } + return channelJoints[ channel ].Ptr(); +} + +/* +===================== +idDeclModelDef::GetVisualOffset +===================== +*/ +const idVec3 &idDeclModelDef::GetVisualOffset() const { + return offset; +} + +/*********************************************************************** + + idAnimator + +***********************************************************************/ + +/* +===================== +idAnimator::idAnimator +===================== +*/ +idAnimator::idAnimator() { + int i, j; + + modelDef = NULL; + entity = NULL; + numJoints = 0; + joints = NULL; + lastTransformTime = -1; + stoppedAnimatingUpdate = false; + removeOriginOffset = false; + forceUpdate = false; + + frameBounds.Clear(); + + AFPoseJoints.SetGranularity( 1 ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointFrame.SetGranularity( 1 ); + + ClearAFPose(); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } +} + +/* +===================== +idAnimator::~idAnimator +===================== +*/ +idAnimator::~idAnimator() { + FreeData(); +} + +/* +===================== +idAnimator::Allocated +===================== +*/ +size_t idAnimator::Allocated() const { + size_t size; + + size = jointMods.Allocated() + numJoints * sizeof( joints[0] ) + jointMods.Num() * sizeof( jointMods[ 0 ] ) + AFPoseJointMods.Allocated() + AFPoseJointFrame.Allocated() + AFPoseJoints.Allocated(); + + return size; +} + +/* +===================== +idAnimator::Save + +archives object for save game file +===================== +*/ +void idAnimator::Save( idSaveGame *savefile ) const { + int i; + int j; + + savefile->WriteModelDef( modelDef ); + savefile->WriteObject( entity ); + + savefile->WriteInt( jointMods.Num() ); + for( i = 0; i < jointMods.Num(); i++ ) { + savefile->WriteInt( jointMods[ i ]->jointnum ); + savefile->WriteMat3( jointMods[ i ]->mat ); + savefile->WriteVec3( jointMods[ i ]->pos ); + savefile->WriteInt( (int&)jointMods[ i ]->transform_pos ); + savefile->WriteInt( (int&)jointMods[ i ]->transform_axis ); + } + + savefile->WriteInt( numJoints ); + for ( i = 0; i < numJoints; i++ ) { + float *data = joints[i].ToFloatPtr(); + for ( j = 0; j < 12; j++ ) { + savefile->WriteFloat( data[j] ); + } + } + + savefile->WriteInt( lastTransformTime ); + savefile->WriteBool( stoppedAnimatingUpdate ); + savefile->WriteBool( forceUpdate ); + savefile->WriteBounds( frameBounds ); + + savefile->WriteFloat( AFPoseBlendWeight ); + + savefile->WriteInt( AFPoseJoints.Num() ); + for ( i = 0; i < AFPoseJoints.Num(); i++ ) { + savefile->WriteInt( AFPoseJoints[i] ); + } + + savefile->WriteInt( AFPoseJointMods.Num() ); + for ( i = 0; i < AFPoseJointMods.Num(); i++ ) { + savefile->WriteInt( (int&)AFPoseJointMods[i].mod ); + savefile->WriteMat3( AFPoseJointMods[i].axis ); + savefile->WriteVec3( AFPoseJointMods[i].origin ); + } + + savefile->WriteInt( AFPoseJointFrame.Num() ); + for ( i = 0; i < AFPoseJointFrame.Num(); i++ ) { + savefile->WriteFloat( AFPoseJointFrame[i].q.x ); + savefile->WriteFloat( AFPoseJointFrame[i].q.y ); + savefile->WriteFloat( AFPoseJointFrame[i].q.z ); + savefile->WriteFloat( AFPoseJointFrame[i].q.w ); + savefile->WriteVec3( AFPoseJointFrame[i].t ); + } + + savefile->WriteBounds( AFPoseBounds ); + savefile->WriteInt( AFPoseTime ); + + savefile->WriteBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Save( savefile ); + } + } +} + +/* +===================== +idAnimator::Restore + +unarchives object from save game file +===================== +*/ +void idAnimator::Restore( idRestoreGame *savefile ) { + int i; + int j; + int num; + + savefile->ReadModelDef( modelDef ); + savefile->ReadObject( reinterpret_cast( entity ) ); + + savefile->ReadInt( num ); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new (TAG_ANIM) jointMod_t; + savefile->ReadInt( (int&)jointMods[ i ]->jointnum ); + savefile->ReadMat3( jointMods[ i ]->mat ); + savefile->ReadVec3( jointMods[ i ]->pos ); + savefile->ReadInt( (int&)jointMods[ i ]->transform_pos ); + savefile->ReadInt( (int&)jointMods[ i ]->transform_axis ); + } + + savefile->ReadInt( numJoints ); + joints = (idJointMat *) Mem_Alloc16( SIMD_ROUND_JOINTS( numJoints ) * sizeof( joints[0] ), TAG_JOINTMAT ); + for ( i = 0; i < numJoints; i++ ) { + float *data = joints[i].ToFloatPtr(); + for ( j = 0; j < 12; j++ ) { + savefile->ReadFloat( data[j] ); + } + } + SIMD_INIT_LAST_JOINT( joints, numJoints ); + + savefile->ReadInt( lastTransformTime ); + savefile->ReadBool( stoppedAnimatingUpdate ); + savefile->ReadBool( forceUpdate ); + savefile->ReadBounds( frameBounds ); + + savefile->ReadFloat( AFPoseBlendWeight ); + + savefile->ReadInt( num ); + AFPoseJoints.SetGranularity( 1 ); + AFPoseJoints.SetNum( num ); + for ( i = 0; i < AFPoseJoints.Num(); i++ ) { + savefile->ReadInt( AFPoseJoints[i] ); + } + + savefile->ReadInt( num ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointMods.SetNum( num ); + for ( i = 0; i < AFPoseJointMods.Num(); i++ ) { + savefile->ReadInt( (int&)AFPoseJointMods[i].mod ); + savefile->ReadMat3( AFPoseJointMods[i].axis ); + savefile->ReadVec3( AFPoseJointMods[i].origin ); + } + + savefile->ReadInt( num ); + AFPoseJointFrame.SetGranularity( 1 ); + AFPoseJointFrame.SetNum( num ); + for ( i = 0; i < AFPoseJointFrame.Num(); i++ ) { + savefile->ReadFloat( AFPoseJointFrame[i].q.x ); + savefile->ReadFloat( AFPoseJointFrame[i].q.y ); + savefile->ReadFloat( AFPoseJointFrame[i].q.z ); + savefile->ReadFloat( AFPoseJointFrame[i].q.w ); + savefile->ReadVec3( AFPoseJointFrame[i].t ); + } + + savefile->ReadBounds( AFPoseBounds ); + savefile->ReadInt( AFPoseTime ); + + savefile->ReadBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Restore( savefile, modelDef ); + } + } +} + +/* +===================== +idAnimator::FreeData +===================== +*/ +void idAnimator::FreeData() { + int i, j; + + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + } + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } + + jointMods.DeleteContents( true ); + + Mem_Free16( joints ); + joints = NULL; + numJoints = 0; + + modelDef = NULL; + + ForceUpdate(); +} + +/* +===================== +idAnimator::PushAnims +===================== +*/ +void idAnimator::PushAnims( int channelNum, int currentTime, int blendTime ) { + int i; + idAnimBlend *channel; + + channel = channels[ channelNum ]; + if ( !channel[ 0 ].GetWeight( currentTime ) || ( channel[ 0 ].starttime == currentTime ) ) { + return; + } + + for( i = ANIM_MaxAnimsPerChannel - 1; i > 0; i-- ) { + channel[ i ] = channel[ i - 1 ]; + } + + channel[ 0 ].Reset( modelDef ); + channel[ 1 ].Clear( currentTime, blendTime ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetModel +===================== +*/ +idRenderModel *idAnimator::SetModel( const char *modelname ) { + int i, j; + + FreeData(); + + // check if we're just clearing the model + if ( !modelname || !*modelname ) { + return NULL; + } + + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( !modelDef ) { + return NULL; + } + + idRenderModel *renderModel = modelDef->ModelHandle(); + if ( !renderModel ) { + modelDef = NULL; + return NULL; + } + + // make sure model hasn't been purged + modelDef->Touch(); + + modelDef->SetupJoints( &numJoints, &joints, frameBounds, removeOriginOffset ); + modelDef->ModelHandle()->Reset(); + + // set the modelDef on all channels + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( modelDef ); + } + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::Size +===================== +*/ +size_t idAnimator::Size() const { + return sizeof( *this ) + Allocated(); +} + +/* +===================== +idAnimator::SetEntity +===================== +*/ +void idAnimator::SetEntity( idEntity *ent ) { + entity = ent; +} + +/* +===================== +idAnimator::GetEntity +===================== +*/ +idEntity *idAnimator::GetEntity() const { + return entity; +} + +/* +===================== +idAnimator::RemoveOriginOffset +===================== +*/ +void idAnimator::RemoveOriginOffset( bool remove ) { + removeOriginOffset = remove; +} + +/* +===================== +idAnimator::RemoveOrigin +===================== +*/ +bool idAnimator::RemoveOrigin() const { + return removeOriginOffset; +} + +/* +===================== +idAnimator::GetJointList +===================== +*/ +void idAnimator::GetJointList( const char *jointnames, idList &jointList ) const { + if ( modelDef ) { + modelDef->GetJointList( jointnames, jointList ); + } +} + +/* +===================== +idAnimator::NumAnims +===================== +*/ +int idAnimator::NumAnims() const { + if ( !modelDef ) { + return 0; + } + + return modelDef->NumAnims(); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +const idAnim *idAnimator::GetAnim( int index ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->GetAnim( index ); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +int idAnimator::GetAnim( const char *name ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->GetAnim( name ); +} + +/* +===================== +idAnimator::HasAnim +===================== +*/ +bool idAnimator::HasAnim( const char *name ) const { + if ( !modelDef ) { + return false; + } + + return modelDef->HasAnim( name ); +} + +/* +===================== +idAnimator::NumJoints +===================== +*/ +int idAnimator::NumJoints() const { + return numJoints; +} + +/* +===================== +idAnimator::ModelHandle +===================== +*/ +idRenderModel *idAnimator::ModelHandle() const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::ModelDef +===================== +*/ +const idDeclModelDef *idAnimator::ModelDef() const { + return modelDef; +} + +/* +===================== +idAnimator::CurrentAnim +===================== +*/ +idAnimBlend *idAnimator::CurrentAnim( int channelNum ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CurrentAnim : channel out of range" ); + return NULL; + } + + return &channels[ channelNum ][ 0 ]; +} + +/* +===================== +idAnimator::Clear +===================== +*/ +void idAnimator::Clear( int channelNum, int currentTime, int cleartime ) { + int i; + idAnimBlend *blend; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::Clear : channel out of range" ); + return; + } + + blend = channels[ channelNum ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->Clear( currentTime, cleartime ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetFrame +===================== +*/ +void idAnimator::SetFrame( int channelNum, int animNum, int frame, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SetFrame : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].SetFrame( modelDef, animNum, frame, currentTime, blendTime ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::CycleAnim +===================== +*/ +void idAnimator::CycleAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CycleAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].CycleAnim( modelDef, animNum, currentTime, blendTime ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::PlayAnim +===================== +*/ +void idAnimator::PlayAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::PlayAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].PlayAnim( modelDef, animNum, currentTime, blendTime ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::SyncAnimChannels +===================== +*/ +void idAnimator::SyncAnimChannels( int channelNum, int fromChannelNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) || ( fromChannelNum < 0 ) || ( fromChannelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SyncToChannel : channel out of range" ); + return; + } + + idAnimBlend &fromBlend = channels[ fromChannelNum ][ 0 ]; + idAnimBlend &toBlend = channels[ channelNum ][ 0 ]; + + float weight = fromBlend.blendEndValue; + if ( ( fromBlend.Anim() != toBlend.Anim() ) || ( fromBlend.GetStartTime() != toBlend.GetStartTime() ) || ( fromBlend.GetEndTime() != toBlend.GetEndTime() ) ) { + PushAnims( channelNum, currentTime, blendTime ); + toBlend = fromBlend; + toBlend.blendStartValue = 0.0f; + toBlend.blendEndValue = 0.0f; + } + toBlend.SetWeight( weight, currentTime - 1, blendTime ); + + // disable framecommands on the current channel so that commands aren't called twice + toBlend.AllowFrameCommands( false ); + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::SetJointPos +===================== +*/ +void idAnimator::SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { + int i; + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new (TAG_ANIM) jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } + + jointMod->pos = pos; + jointMod->transform_pos = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAxis +===================== +*/ +void idAnimator::SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ) { + int i; + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new (TAG_ANIM) jointMod_t; + jointMod->jointnum = jointnum; + jointMod->pos.Zero(); + jointMod->transform_pos = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } + + jointMod->mat = mat; + jointMod->transform_axis = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::ClearJoint +===================== +*/ +void idAnimator::ClearJoint( jointHandle_t jointnum ) { + int i; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + delete jointMods[ i ]; + jointMods.RemoveIndex( i ); + ForceUpdate(); + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } +} + +/* +===================== +idAnimator::ClearAllJoints +===================== +*/ +void idAnimator::ClearAllJoints() { + if ( jointMods.Num() ) { + ForceUpdate(); + } + jointMods.DeleteContents( true ); +} + +/* +===================== +idAnimator::ClearAllAnims +===================== +*/ +void idAnimator::ClearAllAnims( int currentTime, int cleartime ) { + int i; + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + Clear( i, currentTime, cleartime ); + } + + ClearAFPose(); + ForceUpdate(); +} + +/* +==================== +idAnimator::GetDelta +==================== +*/ +void idAnimator::GetDelta( int fromtime, int totime, idVec3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Zero(); + return; + } + + delta.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + } +} + +/* +==================== +idAnimator::GetDeltaRotation +==================== +*/ +bool idAnimator::GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + idQuat q; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Identity(); + return false; + } + + q.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( fromtime, totime, q, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( fromtime, totime, q, blendWeight ); + } + } + + if ( blendWeight > 0.0f ) { + delta = q.ToMat3(); + return true; + } else { + delta.Identity(); + return false; + } +} + +/* +==================== +idAnimator::GetOrigin +==================== +*/ +void idAnimator::GetOrigin( int currentTime, idVec3 &pos ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() ) { + pos.Zero(); + return; + } + + pos.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + } + + pos += modelDef->GetVisualOffset(); +} + +/* +==================== +idAnimator::GetBounds +==================== +*/ +bool idAnimator::GetBounds( int currentTime, idBounds &bounds ) { + int i, j; + const idAnimBlend *blend; + int count; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + if ( AFPoseJoints.Num() ) { + bounds = AFPoseBounds; + count = 1; + } else { + bounds.Clear(); + count = 0; + } + + blend = channels[ 0 ]; + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->AddBounds( currentTime, bounds, removeOriginOffset ) ) { + count++; + } + } + } + + if ( !count ) { + if ( !frameBounds.IsCleared() ) { + bounds = frameBounds; + return true; + } else { + bounds.Zero(); + return false; + } + } + + bounds.TranslateSelf( modelDef->GetVisualOffset() ); + + if ( g_debugBounds.GetBool() ) { + if ( bounds[1][0] - bounds[0][0] > 2048 || bounds[1][1] - bounds[0][1] > 2048 ) { + if ( entity ) { + gameLocal.Warning( "big frameBounds on entity '%s' with model '%s': %f,%f", entity->name.c_str(), modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } else { + gameLocal.Warning( "big frameBounds on model '%s': %f,%f", modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } + } + } + + frameBounds = bounds; + + return true; +} + +/* +===================== +idAnimator::InitAFPose +===================== +*/ +void idAnimator::InitAFPose() { + + if ( !modelDef ) { + return; + } + + AFPoseJoints.SetNum( modelDef->Joints().Num() ); + AFPoseJoints.SetNum( 0 ); + AFPoseJointMods.SetNum( modelDef->Joints().Num() ); + AFPoseJointFrame.SetNum( modelDef->Joints().Num() ); +} + +/* +===================== +idAnimator::SetAFPoseJointMod +===================== +*/ +void idAnimator::SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ) { + AFPoseJointMods[jointNum].mod = mod; + AFPoseJointMods[jointNum].axis = axis; + AFPoseJointMods[jointNum].origin = origin; + + int index = idBinSearch_GreaterEqual( AFPoseJoints.Ptr(), AFPoseJoints.Num(), jointNum ); + if ( index >= AFPoseJoints.Num() || jointNum != AFPoseJoints[index] ) { + AFPoseJoints.Insert( jointNum, index ); + } +} + +/* +===================== +idAnimator::FinishAFPose +===================== +*/ +void idAnimator::FinishAFPose( int animNum, const idBounds &bounds, const int time ) { + int i, j; + int numJoints; + int parentNum; + int jointMod; + int jointNum; + const int * jointParent; + + if ( !modelDef ) { + return; + } + + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return; + } + + numJoints = modelDef->Joints().Num(); + if ( !numJoints ) { + return; + } + + idRenderModel *md5 = modelDef->ModelHandle(); + const idMD5Anim *md5anim = anim->MD5Anim( 0 ); + + if ( numJoints != md5anim->NumJoints() ) { + gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", md5->Name(), md5anim->Name() ); + return; + } + + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + md5anim->GetSingleFrame( 0, jointFrame, modelDef->GetChannelJoints( ANIMCHANNEL_ALL ), modelDef->NumJointsOnChannel( ANIMCHANNEL_ALL ) ); + + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( *joints ) ); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( AFPoseJoints.Num() && AFPoseJoints[0] == 0 ) { + switch( AFPoseJointMods[0].mod ) { + case AF_JOINTMOD_AXIS: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + } + j = 1; + } else { + j = 0; + } + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // transform the child joints + for( i = 1; j < AFPoseJoints.Num(); j++, i++ ) { + jointMod = AFPoseJoints[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod - 1 ); + i = jointMod; + + parentNum = jointParent[i]; + + switch( AFPoseJointMods[jointMod].mod ) { + case AF_JOINTMOD_AXIS: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[i].SetRotation( joints[i].ToMat3() * joints[parentNum].ToMat3() ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + } + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + // untransform hierarchy + SIMDProcessor->UntransformJoints( joints, jointParent, 1, numJoints - 1 ); + + // convert joint matrices back to joint quaternions + SIMDProcessor->ConvertJointMatsToJointQuats( AFPoseJointFrame.Ptr(), joints, numJoints ); + + // find all modified joints and their parents + bool *blendJoints = (bool *) _alloca16( numJoints * sizeof( bool ) ); + memset( blendJoints, 0, numJoints * sizeof( bool ) ); + + // mark all modified joints and their parents + for( i = 0; i < AFPoseJoints.Num(); i++ ) { + for( jointNum = AFPoseJoints[i]; jointNum != INVALID_JOINT; jointNum = jointParent[jointNum] ) { + blendJoints[jointNum] = true; + } + } + + // lock all parents of modified joints + AFPoseJoints.SetNum( 0 ); + for ( i = 0; i < numJoints; i++ ) { + if ( blendJoints[i] ) { + AFPoseJoints.Append( i ); + } + } + + AFPoseBounds = bounds; + AFPoseTime = time; + + ForceUpdate(); +} + +/* +===================== +idAnimator::SetAFPoseBlendWeight +===================== +*/ +void idAnimator::SetAFPoseBlendWeight( float blendWeight ) { + AFPoseBlendWeight = blendWeight; +} + +/* +===================== +idAnimator::BlendAFPose +===================== +*/ +bool idAnimator::BlendAFPose( idJointQuat *blendFrame ) const { + + if ( !AFPoseJoints.Num() ) { + return false; + } + + SIMDProcessor->BlendJoints( blendFrame, AFPoseJointFrame.Ptr(), AFPoseBlendWeight, AFPoseJoints.Ptr(), AFPoseJoints.Num() ); + + return true; +} + +/* +===================== +idAnimator::ClearAFPose +===================== +*/ +void idAnimator::ClearAFPose() { + if ( AFPoseJoints.Num() ) { + ForceUpdate(); + } + AFPoseBlendWeight = 1.0f; + AFPoseJoints.SetNum( 0 ); + AFPoseBounds.Clear(); + AFPoseTime = 0; +} + +/* +===================== +idAnimator::ServiceAnims +===================== +*/ +void idAnimator::ServiceAnims( int fromtime, int totime ) { + int i, j; + idAnimBlend *blend; + + if ( !modelDef ) { + return; + } + + if ( modelDef->ModelHandle() ) { + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + blend->CallFrameCommands( entity, fromtime, totime ); + } + } + } + + if ( !IsAnimating( totime ) ) { + stoppedAnimatingUpdate = true; + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + + // present one more time with stopped animations so the renderer can properly recreate interactions + entity->BecomeActive( TH_UPDATEVISUALS ); + } + } +} + +/* +===================== +idAnimator::IsAnimating +===================== +*/ +bool idAnimator::IsAnimating( int currentTime ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( !blend->IsDone( currentTime ) ) { + return true; + } + } + } + + return false; +} + +/* +===================== +idAnimator::FrameHasChanged +===================== +*/ +bool idAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->FrameHasChanged( currentTime ) ) { + return true; + } + } + } + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimator::CreateFrame +===================== +*/ +bool idAnimator::CreateFrame( int currentTime, bool force ) { + int i, j; + int numJoints; + int parentNum; + bool hasAnim; + bool debugInfo; + float baseBlend; + float blendWeight; + const idAnimBlend * blend; + const int * jointParent; + const jointMod_t * jointMod; + const idJointQuat * defaultPose; + + static idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + if ( !force && !r_showSkel.GetInteger() ) { + if ( lastTransformTime == currentTime ) { + return false; + } + if ( lastTransformTime != -1 && !stoppedAnimatingUpdate && !IsAnimating( currentTime ) ) { + return false; + } + } + + lastTransformTime = currentTime; + stoppedAnimatingUpdate = false; + + if ( entity && ( ( g_debugAnim.GetInteger() == entity->entityNumber ) || ( g_debugAnim.GetInteger() == -2 ) ) ) { + debugInfo = true; + gameLocal.Printf( "---------------\n%d: entity '%s':\n", gameLocal.time, entity->GetName() ); + gameLocal.Printf( "model '%s':\n", modelDef->GetModelName() ); + } else { + debugInfo = false; + } + + // init the joint buffer + if ( AFPoseJoints.Num() ) { + // initialize with AF pose anim for the case where there are no other animations and no AF pose joint modifications + defaultPose = AFPoseJointFrame.Ptr(); + } else { + defaultPose = modelDef->GetDefaultPose(); + } + + if ( !defaultPose ) { + //gameLocal.Warning( "idAnimator::CreateFrame: no defaultPose on '%s'", modelDef->Name() ); + return false; + } + + numJoints = modelDef->Joints().Num(); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( jointFrame[0] ) ); + SIMDProcessor->Memcpy( jointFrame, defaultPose, numJoints * sizeof( jointFrame[0] ) ); + + hasAnim = false; + + // blend the all channel + baseBlend = 0.0f; + blend = channels[ ANIMCHANNEL_ALL ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_ALL, numJoints, jointFrame, baseBlend, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( baseBlend >= 1.0f ) { + break; + } + } + } + + // only blend other channels if there's enough space to blend into + if ( baseBlend < 1.0f ) { + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !modelDef->NumJointsOnChannel( i ) ) { + continue; + } + if ( i == ANIMCHANNEL_EYELIDS ) { + // eyelids blend over any previous anims, so skip it and blend it later + continue; + } + blendWeight = baseBlend; + blend = channels[ i ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, i, numJoints, jointFrame, blendWeight, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + + if ( debugInfo && !AFPoseJoints.Num() && !blendWeight ) { + gameLocal.Printf( "%d: %s using default pose in model '%s'\n", gameLocal.time, channelNames[ i ], modelDef->GetModelName() ); + } + } + } + + // blend in the eyelids + if ( modelDef->NumJointsOnChannel( ANIMCHANNEL_EYELIDS ) ) { + blend = channels[ ANIMCHANNEL_EYELIDS ]; + blendWeight = baseBlend; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_EYELIDS, numJoints, jointFrame, blendWeight, removeOriginOffset, true, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + } + + // blend the articulated figure pose + if ( BlendAFPose( jointFrame ) ) { + hasAnim = true; + } + + if ( !hasAnim && !jointMods.Num() ) { + // no animations were updated + return false; + } + + // convert the joint quaternions to rotation matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // check if we need to modify the origin + if ( jointMods.Num() && ( jointMods[0]->jointnum == 0 ) ) { + jointMod = jointMods[0]; + + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetRotation( jointMod->mat * joints[0].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[0].SetRotation( joints[0].ToMat3() * jointMod->mat ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetRotation( jointMod->mat ); + break; + } + + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetTranslation( joints[0].ToVec3() + jointMod->pos ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetTranslation( jointMod->pos ); + break; + } + j = 1; + } else { + j = 0; + } + + // add in the model offset + joints[0].SetTranslation( joints[0].ToVec3() + modelDef->GetVisualOffset() ); + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // add in any joint modifications + for( i = 1; j < jointMods.Num(); j++, i++ ) { + jointMod = jointMods[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod->jointnum - 1 ); + i = jointMod->jointnum; + + parentNum = jointParent[i]; + + // modify the axis + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + joints[i].SetRotation( joints[i].ToMat3() * joints[ parentNum ].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetRotation( jointMod->mat * ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetRotation( jointMod->mat * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetRotation( ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) * jointMod->mat ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetRotation( jointMod->mat ); + break; + } + + // modify the position + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetTranslation( joints[parentNum].ToVec3() + ( joints[i].ToVec3() + jointMod->pos ) * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + jointMod->pos * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() + jointMod->pos ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetTranslation( jointMod->pos ); + break; + } + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + return true; +} + +/* +===================== +idAnimator::ForceUpdate +===================== +*/ +void idAnimator::ForceUpdate() { + lastTransformTime = -1; + forceUpdate = true; +} + +/* +===================== +idAnimator::ClearForceUpdate +===================== +*/ +void idAnimator::ClearForceUpdate() { + forceUpdate = false; +} + +/* +===================== +idAnimator::GetJointTransform> gamex86.dll!idAnimator::ForceUpdate() Line 4268 C++ + +===================== +*/ +bool idAnimator::GetJointTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef || ( jointHandle < 0 ) || ( jointHandle >= modelDef->NumJoints() ) ) { + return false; + } + + CreateFrame( currentTime, false ); + + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + + return true; +} + +/* +===================== +idAnimator::GetJointLocalTransform +===================== +*/ +bool idAnimator::GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef ) { + return false; + } + + const idList &modelJoints = modelDef->Joints(); + + if ( ( jointHandle < 0 ) || ( jointHandle >= modelJoints.Num() ) ) { + return false; + } + + // FIXME: overkill + CreateFrame( currentTime, false ); + + if ( jointHandle > 0 ) { + idJointMat m = joints[ jointHandle ]; + m /= joints[ modelJoints[ jointHandle ].parentNum ]; + offset = m.ToVec3(); + axis = m.ToMat3(); + } else { + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + } + + return true; +} + +/* +===================== +idAnimator::GetJointHandle +===================== +*/ +jointHandle_t idAnimator::GetJointHandle( const char *name ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return INVALID_JOINT; + } + + return modelDef->ModelHandle()->GetJointHandle( name ); +} + +/* +===================== +idAnimator::GetJointName +===================== +*/ +const char *idAnimator::GetJointName( jointHandle_t handle ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return ""; + } + + return modelDef->ModelHandle()->GetJointName( handle ); +} + +/* +===================== +idAnimator::GetChannelForJoint +===================== +*/ +int idAnimator::GetChannelForJoint( jointHandle_t joint ) const { + if ( !modelDef ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: NULL model" ); + return -1; + } + + if ( ( joint < 0 ) || ( joint >= numJoints ) ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: invalid joint num (%d)", joint ); + return -1; + } + + return modelDef->GetJoint( joint )->channel; +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( const char *name ) const { + return GetFirstChild( GetJointHandle( name ) ); +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( jointHandle_t jointnum ) const { + int i; + int num; + const jointInfo_t *joint; + + if ( !modelDef ) { + return INVALID_JOINT; + } + + num = modelDef->NumJoints(); + if ( !num ) { + return jointnum; + } + joint = modelDef->GetJoint( 0 ); + for( i = 0; i < num; i++, joint++ ) { + if ( joint->parentNum == jointnum ) { + return ( jointHandle_t )joint->num; + } + } + return jointnum; +} + +/* +===================== +idAnimator::GetJoints +===================== +*/ +void idAnimator::GetJoints( int *numJoints, idJointMat **jointsPtr ) { + *numJoints = this->numJoints; + *jointsPtr = this->joints; +} + +/* +===================== +idAnimator::GetAnimFlags +===================== +*/ +const animFlags_t idAnimator::GetAnimFlags( int animNum ) const { + animFlags_t result; + + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->GetAnimFlags(); + } + + memset( &result, 0, sizeof( result ) ); + return result; +} + +/* +===================== +idAnimator::NumFrames +===================== +*/ +int idAnimator::NumFrames( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumFrames(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::NumSyncedAnims +===================== +*/ +int idAnimator::NumSyncedAnims( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumAnims(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::AnimName +===================== +*/ +const char *idAnimator::AnimName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Name(); + } else { + return ""; + } +} + +/* +===================== +idAnimator::AnimFullName +===================== +*/ +const char *idAnimator::AnimFullName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } else { + return ""; + } +} + +/* +===================== +idAnimator::AnimLength +===================== +*/ +int idAnimator::AnimLength( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Length(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::TotalMovementDelta +===================== +*/ +const idVec3 &idAnimator::TotalMovementDelta( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->TotalMovementDelta(); + } else { + return vec3_origin; + } +} + +/*********************************************************************** + + Util functions + +***********************************************************************/ + +/* +===================== +ANIM_GetModelDefFromEntityDef +===================== +*/ +const idDeclModelDef *ANIM_GetModelDefFromEntityDef( const idDict *args ) { + const idDeclModelDef *modelDef; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef != NULL && modelDef->ModelHandle() ) { + return modelDef; + } + + return NULL; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const idDict *args ) { + idRenderModel *model; + const idDeclModelDef *modelDef; + + model = NULL; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef != NULL ) { + model = modelDef->ModelHandle(); + } + + if ( model == NULL ) { + model = renderModelManager->FindModel( name ); + } + + if ( model != NULL && model->IsDefaultModel() ) { + return NULL; + } + + return model; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const char *classname ) { + const idDict *args; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + return ANIM_GetModelFromEntityDef( args ); +} + +/* +===================== +idGameEdit::ANIM_GetModelOffsetFromEntityDef +===================== +*/ +const idVec3 &idGameEdit::ANIM_GetModelOffsetFromEntityDef( const char *classname ) { + const idDict *args; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return vec3_origin; + } + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( !modelDef ) { + return vec3_origin; + } + + return modelDef->GetVisualOffset(); +} + +/* +===================== +idGameEdit::ANIM_GetModelFromName +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromName( const char *modelName ) { + const idDeclModelDef *modelDef; + idRenderModel *model; + + model = NULL; + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + if ( !model ) { + model = renderModelManager->FindModel( modelName ); + } + return model; +} + +/* +===================== +idGameEdit::ANIM_GetAnimFromEntityDef +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ) { + const idDict *args; + const idMD5Anim *md5anim; + const idAnim *anim; + int animNum; + const char *modelname; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + md5anim = NULL; + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( animNum ) { + anim = modelDef->GetAnim( animNum ); + if ( anim ) { + md5anim = anim->MD5Anim( 0 ); + } + } + } + return md5anim; +} + +/* +===================== +idGameEdit::ANIM_GetNumAnimsFromEntityDef +===================== +*/ +int idGameEdit::ANIM_GetNumAnimsFromEntityDef( const idDict *args ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + return modelDef->NumAnims(); + } + return 0; +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntityDef +===================== +*/ +const char *idGameEdit::ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + const idAnim* anim = modelDef->GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } + } + return ""; +} + +/* +===================== +idGameEdit::ANIM_GetAnim +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnim( const char *fileName ) { + return animationLib.GetAnim( fileName ); +} + +/* +===================== +idGameEdit::ANIM_GetLength +===================== +*/ +int idGameEdit::ANIM_GetLength( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->Length(); +} + +/* +===================== +idGameEdit::ANIM_GetNumFrames +===================== +*/ +int idGameEdit::ANIM_GetNumFrames( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->NumFrames(); +} + +/* +===================== +idGameEdit::ANIM_CreateAnimFrame +===================== +*/ +void idGameEdit::ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *joints, int time, const idVec3 &offset, bool remove_origin_offset ) { + int i; + frameBlend_t frame; + const idMD5Joint *md5joints; + int *index; + + if ( !model || model->IsDefaultModel() || !anim ) { + return; + } + + if ( numJoints != model->NumJoints() ) { + gameLocal.Error( "ANIM_CreateAnimFrame: different # of joints in renderEntity_t than in model (%s)", model->Name() ); + } + + if ( !model->NumJoints() ) { + // FIXME: Print out a warning? + return; + } + + if ( !joints ) { + gameLocal.Error( "ANIM_CreateAnimFrame: NULL joint frame pointer on model (%s)", model->Name() ); + } + + if ( numJoints != anim->NumJoints() ) { + gameLocal.Warning( "Model '%s' has different # of joints than anim '%s'", model->Name(), anim->Name() ); + for( i = 0; i < numJoints; i++ ) { + joints[i].SetRotation( mat3_identity ); + joints[i].SetTranslation( offset ); + } + return; + } + + // create index for all joints + index = ( int * )_alloca16( numJoints * sizeof( int ) ); + for ( i = 0; i < numJoints; i++ ) { + index[i] = i; + } + + // create the frame + anim->ConvertTimeToFrame( time, 1, frame ); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + anim->GetInterpolatedFrame( frame, jointFrame, index, numJoints ); + + // convert joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( remove_origin_offset ) { + joints[0].SetTranslation( offset ); + } else { + joints[0].SetTranslation( joints[0].ToVec3() + offset ); + } + + // transform the children + md5joints = model->GetJoints(); + for( i = 1; i < numJoints; i++ ) { + joints[i] *= joints[ md5joints[i].parent - md5joints ]; + } +} + +/* +===================== +idGameEdit::ANIM_CreateMeshForAnim +===================== +*/ +idRenderModel *idGameEdit::ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ) { + renderEntity_t ent; + const idDict *args; + const char *temp; + idRenderModel *newmodel; + const idMD5Anim *md5anim; + idStr filename; + idStr extension; + const idAnim *anim; + int animNum; + idVec3 offset; + const idDeclModelDef *modelDef; + + if ( !model || model->IsDefaultModel() ) { + return NULL; + } + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + memset( &ent, 0, sizeof( ent ) ); + + ent.bounds.Clear(); + ent.suppressSurfaceInViewID = 0; + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( !animNum ) { + return NULL; + } + anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + md5anim = anim->MD5Anim( 0 ); + ent.customSkin = modelDef->GetDefaultSkin(); + offset = modelDef->GetVisualOffset(); + } else { + filename = animname; + filename.ExtractFileExtension( extension ); + if ( !extension.Length() ) { + animname = args->GetString( va( "anim %s", animname ) ); + } + + md5anim = animationLib.GetAnim( animname ); + offset.Zero(); + } + + if ( !md5anim ) { + return NULL; + } + + temp = args->GetString( "skin", "" ); + if ( temp[ 0 ] ) { + ent.customSkin = declManager->FindSkin( temp ); + } + + ent.numJoints = model->NumJoints(); + ent.joints = ( idJointMat * )Mem_Alloc16( SIMD_ROUND_JOINTS( ent.numJoints ) * sizeof( *ent.joints ), TAG_JOINTMAT ); + + ANIM_CreateAnimFrame( model, md5anim, ent.numJoints, ent.joints, FRAME2MS( frame ), offset, remove_origin_offset ); + + SIMD_INIT_LAST_JOINT( ent.joints, ent.numJoints ); + + newmodel = model->InstantiateDynamicModel( &ent, NULL, NULL ); + + Mem_Free16( ent.joints ); + ent.joints = NULL; + + return newmodel; +} diff --git a/neo/d3xp/anim/Anim_Testmodel.cpp b/neo/d3xp/anim/Anim_Testmodel.cpp new file mode 100644 index 00000000..acee1e90 --- /dev/null +++ b/neo/d3xp/anim/Anim_Testmodel.cpp @@ -0,0 +1,901 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +============================================================================= + + MODEL TESTING + +Model viewing can begin with either "testmodel " + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Extension will default to ".ase" if not specified. + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + + g_testModelRotate + g_testModelAnimate + g_testModelBlend + +============================================================================= +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idAnimatedEntity, idTestModel ) + EVENT( EV_FootstepLeft, idTestModel::Event_Footstep ) + EVENT( EV_FootstepRight, idTestModel::Event_Footstep ) +END_CLASS + +/* +================ +idTestModel::idTestModel +================ +*/ +idTestModel::idTestModel() { + head = NULL; + headAnimator = NULL; + anim = 0; + headAnim = 0; + starttime = 0; + animtime = 0; + mode = 0; + frame = 0; +} + +/* +================ +idTestModel::Save +================ +*/ +void idTestModel::Save( idSaveGame *savefile ) { +} + +/* +================ +idTestModel::Restore +================ +*/ +void idTestModel::Restore( idRestoreGame *savefile ) { + // FIXME: one day we may actually want to save/restore test models, but for now we'll just delete them + delete this; +} + +/* +================ +idTestModel::Spawn +================ +*/ +void idTestModel::Spawn() { + idVec3 size; + idBounds bounds; + const char *headModel; + jointHandle_t joint; + idStr jointName; + idVec3 origin, modelOffset; + idMat3 axis; + const idKeyValue *kv; + copyJoints_t copyJoint; + + if ( renderEntity.hModel && renderEntity.hModel->IsDefaultModel() && !animator.ModelDef() ) { + gameLocal.Warning( "Unable to create testmodel for '%s' : model defaulted", spawnArgs.GetString( "model" ) ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + mode = g_testModelAnimate.GetInteger(); + animator.RemoveOriginOffset( g_testModelAnimate.GetInteger() == 1 ); + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) ) { + spawnArgs.GetVector( "maxs", NULL, bounds[1] ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } + + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + // add the head model if it has one + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { + jointName = spawnArgs.GetString( "head_joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found for 'head_joint'", jointName.c_str() ); + } else { + // copy any sounds in case we have frame commands on the head + idDict args; + const idKeyValue *sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + head = gameLocal.SpawnEntityType( idAnimatedEntity::Type, &args ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + origin = GetPhysics()->GetOrigin() + ( origin + modelOffset ) * GetPhysics()->GetAxis(); + head.GetEntity()->SetModel( headModel ); + head.GetEntity()->SetOrigin( origin ); + head.GetEntity()->SetAxis( GetPhysics()->GetAxis() ); + head.GetEntity()->BindToJoint( this, animator.GetJointName( joint ), true ); + + headAnimator = head.GetEntity()->GetAnimator(); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + jointName = kv->GetKey(); + + if ( jointName.StripLeadingOnce( "copy_joint_world " ) ) { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } else { + jointName.StripLeadingOnce( "copy_joint " ); + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + } + + copyJoint.from = animator.GetJointHandle( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s'", jointName.c_str() ); + continue; + } + + copyJoint.to = headAnimator->GetJointHandle( jointName ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head", jointName.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } + } + } + + // start any shader effects based off of the spawn time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + SetPhysics( &physicsObj ); + + gameLocal.Printf( "Added testmodel at origin = '%s', angles = '%s'\n", GetPhysics()->GetOrigin().ToString(), GetPhysics()->GetAxis().ToAngles().ToString() ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTestModel::~idTestModel +================ +*/ +idTestModel::~idTestModel() { + StopSound( SND_CHANNEL_ANY, false ); + if ( renderEntity.hModel ) { + gameLocal.Printf( "Removing testmodel %s\n", renderEntity.hModel->Name() ); + } else { + gameLocal.Printf( "Removing testmodel\n" ); + } + if ( gameLocal.testmodel == this ) { + gameLocal.testmodel = NULL; + } + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +=============== +idTestModel::Event_Footstep +=============== +*/ +void idTestModel::Event_Footstep() { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idTestModel::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idTestModel::ShouldConstructScriptObjectAtSpawn() const { + return false; +} + +/* +================ +idTestModel::Think +================ +*/ +void idTestModel::Think() { + idVec3 pos; + idMat3 axis; + idAngles ang; + int i; + + if ( thinkFlags & TH_THINK ) { + if ( anim && ( gameLocal.testmodel == this ) && ( mode != g_testModelAnimate.GetInteger() ) ) { + StopSound( SND_CHANNEL_ANY, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + } + switch( g_testModelAnimate.GetInteger() ) { + default: + case 0: + // cycle anim with origin reset + if ( animator.NumFrames( anim ) <= 1 ) { + // single frame animations end immediately, so just cycle it since it's the same result + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + } else { + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + animator.RemoveOriginOffset( false ); + break; + + case 1: + // cycle anim with fixed origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 2: + // cycle anim with continuous origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 3: + // frame by frame with continuous origin + animator.SetFrame( ANIMCHANNEL_ALL, anim, frame, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frame, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 4: + // play anim once + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 5: + // frame by frame with fixed origin + animator.SetFrame( ANIMCHANNEL_ALL, anim, frame, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frame, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + } + + mode = g_testModelAnimate.GetInteger(); + } + + if ( ( mode == 0 ) && ( gameLocal.time >= starttime + animtime ) ) { + starttime = gameLocal.time; + StopSound( SND_CHANNEL_ANY, false ); + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + + if ( headAnimator ) { + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + idMat3 mat = head.GetEntity()->GetPhysics()->GetAxis().Transpose(); + GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= head.GetEntity()->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + animator.GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } + } + + // update rotation + RunPhysics(); + + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, idAngles( 0, g_testModelRotate.GetFloat() * 360.0f / 60.0f, 0 ), ang_zero ); + + idClipModel *clip = physicsObj.GetClipModel(); + if ( clip != NULL && animator.ModelDef() ) { + idVec3 neworigin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle( "origin" ); + animator.GetJointTransform( joint, gameLocal.time, neworigin, axis ); + neworigin = ( ( neworigin - animator.ModelDef()->GetVisualOffset() ) * physicsObj.GetAxis() ) + GetPhysics()->GetOrigin(); + clip->Link( gameLocal.clip, this, 0, neworigin, clip->GetAxis() ); + } + } + + UpdateAnimation(); + Present(); + + if ( ( gameLocal.testmodel == this ) && g_showTestModelFrame.GetInteger() && anim ) { + gameLocal.Printf( "^5 Anim: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n", animator.AnimFullName( anim ), animator.CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + animator.CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - animator.CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + if ( headAnim ) { + gameLocal.Printf( "^5 Head: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n\n", headAnimator->AnimFullName( headAnim ), headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + } else { + gameLocal.Printf( "\n\n" ); + } + } +} + +/* +================ +idTestModel::NextAnim +================ +*/ +void idTestModel::NextAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + anim++; + if ( anim >= animator.NumAnims() ) { + // anim 0 is no anim + anim = 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::PrevAnim +================ +*/ +void idTestModel::PrevAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + anim--; + if ( anim < 0 ) { + anim = animator.NumAnims() - 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::NextFrame +================ +*/ +void idTestModel::NextFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame++; + if ( frame > animator.NumFrames( anim ) ) { + frame = 1; + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::PrevFrame +================ +*/ +void idTestModel::PrevFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame--; + if ( frame < 1 ) { + frame = animator.NumFrames( anim ); + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::TestAnim +================ +*/ +void idTestModel::TestAnim( const idCmdArgs &args ) { + idStr name; + int animNum; + const idAnim *newanim; + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: testanim \n" ); + return; + } + + newanim = NULL; + + name = args.Argv( 1 ); + animNum = animator.GetAnim( name ); + + if ( !animNum ) { + gameLocal.Printf( "Animation '%s' not found.\n", name.c_str() ); + return; + } + + anim = animNum; + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + if ( !headAnim ) { + gameLocal.Printf( "Missing 'idle' anim for head.\n" ); + } + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + animname = name; + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +===================== +idTestModel::BlendAnim +===================== +*/ +void idTestModel::BlendAnim( const idCmdArgs &args ) { + int anim1; + int anim2; + + if ( args.Argc() < 4 ) { + gameLocal.Printf( "usage: testblend \n" ); + return; + } + + anim1 = gameLocal.testmodel->animator.GetAnim( args.Argv( 1 ) ); + if ( !anim1 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 1 ) ); + return; + } + + anim2 = gameLocal.testmodel->animator.GetAnim( args.Argv( 2 ) ); + if ( !anim2 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 2 ) ); + return; + } + + animname = args.Argv( 2 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim1, gameLocal.time, 0 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, FRAME2MS( atoi( args.Argv( 3 ) ) ) ); + + anim = anim2; + headAnim = 0; +} + +/*********************************************************************** + + Testmodel console commands + +***********************************************************************/ + +/* +================= +idTestModel::KeepTestModel_f + +Makes the current test model permanent, allowing you to place +multiple test models +================= +*/ +void idTestModel::KeepTestModel_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel.\n" ); + return; + } + + gameLocal.Printf( "modelDef %p kept\n", gameLocal.testmodel->renderEntity.hModel ); + + gameLocal.testmodel = NULL; +} + +/* +================= +idTestModel::TestSkin_f + +Sets a skin on an existing testModel +================= +*/ +void idTestModel::TestSkin_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + common->Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() < 2 ) { + common->Printf( "removing testSkin.\n" ); + gameLocal.testmodel->SetSkin( NULL ); + return; + } + + name = args.Argv( 1 ); + gameLocal.testmodel->SetSkin( declManager->FindSkin( name ) ); +} + +/* +================= +idTestModel::TestShaderParm_f + +Sets a shaderParm on an existing testModel +================= +*/ +void idTestModel::TestShaderParm_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + common->Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() != 3 ) { + common->Printf( "USAGE: testShaderParm \n" ); + return; + } + + int parm = atoi( args.Argv( 1 ) ); + if ( parm < 0 || parm >= MAX_ENTITY_SHADER_PARMS ) { + common->Printf( "parmNum %i out of range\n", parm ); + return; + } + + float value; + if ( !idStr::Icmp( args.Argv( 2 ), "time" ) ) { + value = gameLocal.time * -0.001; + } else { + value = atof( args.Argv( 2 ) ); + } + + gameLocal.testmodel->SetShaderParm( parm, value ); +} + +/* +================= +idTestModel::TestModel_f + +Creates a static modelDef in front of the current position, which +can then be moved around +================= +*/ +void idTestModel::TestModel_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + const idDict * entityDef; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testmodel ) { + delete gameLocal.testmodel; + gameLocal.testmodel = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + entityDef = gameLocal.FindEntityDefDict( name, false ); + if ( entityDef ) { + dict = *entityDef; + } else { + if ( declManager->FindType( DECL_MODELDEF, name, false ) ) { + dict.Set( "model", name ); + } else { + // allow map models with underscore prefixes to be tested during development + // without appending an ase + if ( name[ 0 ] != '_' ) { + name.DefaultFileExtension( ".ase" ); + } + if ( !renderModelManager->CheckModel( name ) ) { + gameLocal.Printf( "Can't register model\n" ); + return; + } + dict.Set( "model", name ); + } + } + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "angle", va( "%f", player->viewAngles.yaw + 180.0f ) ); + gameLocal.testmodel = ( idTestModel * )gameLocal.SpawnEntityType( idTestModel::Type, &dict ); + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); +} + +/* +===================== +idTestModel::ArgCompletion_TestModel +===================== +*/ +void idTestModel::ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i, num; + + num = declManager->GetNumDecls( DECL_ENTITYDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_ENTITYDEF, i , false )->GetName() ); + } + num = declManager->GetNumDecls( DECL_MODELDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_MODELDEF, i , false )->GetName() ); + } + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", ".mb", NULL ); +} + +/* +===================== +idTestModel::TestParticleStopTime_f +===================== +*/ +void idTestModel::TestParticleStopTime_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + gameLocal.testmodel->UpdateVisuals(); +} + +/* +===================== +idTestModel::TestAnim_f +===================== +*/ +void idTestModel::TestAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->TestAnim( args ); +} + + +/* +===================== +idTestModel::ArgCompletion_TestAnim +===================== +*/ +void idTestModel::ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ) { + if ( gameLocal.testmodel ) { + idAnimator *animator = gameLocal.testmodel->GetAnimator(); + for( int i = 0; i < animator->NumAnims(); i++ ) { + callback( va( "%s %s", args.Argv( 0 ), animator->AnimFullName( i ) ) ); + } + } +} + +/* +===================== +idTestModel::TestBlend_f +===================== +*/ +void idTestModel::TestBlend_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->BlendAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextAnim_f +===================== +*/ +void idTestModel::TestModelNextAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextAnim( args ); +} + +/* +===================== +idTestModel::TestModelPrevAnim_f +===================== +*/ +void idTestModel::TestModelPrevAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextFrame_f +===================== +*/ +void idTestModel::TestModelNextFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextFrame( args ); +} + +/* +===================== +idTestModel::TestModelPrevFrame_f +===================== +*/ +void idTestModel::TestModelPrevFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevFrame( args ); +} diff --git a/neo/d3xp/anim/Anim_Testmodel.h b/neo/d3xp/anim/Anim_Testmodel.h new file mode 100644 index 00000000..ef78886b --- /dev/null +++ b/neo/d3xp/anim/Anim_Testmodel.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __ANIM_TESTMODEL_H__ +#define __ANIM_TESTMODEL_H__ + +/* +============================================================================================== + + idTestModel + +============================================================================================== +*/ + +class idTestModel : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idTestModel ); + + idTestModel(); + ~idTestModel(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + void Spawn(); + + virtual bool ShouldConstructScriptObjectAtSpawn() const; + + void NextAnim( const idCmdArgs &args ); + void PrevAnim( const idCmdArgs &args ); + void NextFrame( const idCmdArgs &args ); + void PrevFrame( const idCmdArgs &args ); + void TestAnim( const idCmdArgs &args ); + void BlendAnim( const idCmdArgs &args ); + + static void KeepTestModel_f( const idCmdArgs &args ); + static void TestModel_f( const idCmdArgs &args ); + static void ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestSkin_f( const idCmdArgs &args ); + static void TestShaderParm_f( const idCmdArgs &args ); + static void TestParticleStopTime_f( const idCmdArgs &args ); + static void TestAnim_f( const idCmdArgs &args ); + static void ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestBlend_f( const idCmdArgs &args ); + static void TestModelNextAnim_f( const idCmdArgs &args ); + static void TestModelPrevAnim_f( const idCmdArgs &args ); + static void TestModelNextFrame_f( const idCmdArgs &args ); + static void TestModelPrevFrame_f( const idCmdArgs &args ); + +private: + idEntityPtr head; + idAnimator *headAnimator; + idAnim customAnim; + idPhysics_Parametric physicsObj; + idStr animname; + int anim; + int headAnim; + int mode; + int frame; + int starttime; + int animtime; + + idList copyJoints; + + virtual void Think(); + + void Event_Footstep(); +}; + +#endif /* !__ANIM_TESTMODEL_H__*/ diff --git a/neo/d3xp/gamesys/Callbacks.cpp b/neo/d3xp/gamesys/Callbacks.cpp new file mode 100644 index 00000000..3876c4bb --- /dev/null +++ b/neo/d3xp/gamesys/Callbacks.cpp @@ -0,0 +1,2626 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + /******************************************************* + + 1 args + + *******************************************************/ + + case 512 : + typedef void ( idClass::*eventCallback_i_t )( void * ); + ( this->*( eventCallback_i_t )callback )( (void *)data[ 0 ] ); + break; + + case 513 : + typedef void ( idClass::*eventCallback_f_t )( const float ); + ( this->*( eventCallback_f_t )callback )( *( float * )&data[ 0 ] ); + break; + + /******************************************************* + + 2 args + + *******************************************************/ + + case 1024 : + typedef void ( idClass::*eventCallback_ii_t )( void *, void * ); + ( this->*( eventCallback_ii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ] ); + break; + + case 1025 : + typedef void ( idClass::*eventCallback_fi_t )( const float, void * ); + ( this->*( eventCallback_fi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ] ); + break; + + case 1026 : + typedef void ( idClass::*eventCallback_if_t )( void *, const float ); + ( this->*( eventCallback_if_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ] ); + break; + + case 1027 : + typedef void ( idClass::*eventCallback_ff_t )( const float, const float ); + ( this->*( eventCallback_ff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ] ); + break; + + /******************************************************* + + 3 args + + *******************************************************/ + + case 2048 : + typedef void ( idClass::*eventCallback_iii_t )( void *, void *, void * ); + ( this->*( eventCallback_iii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ] ); + break; + + case 2049 : + typedef void ( idClass::*eventCallback_fii_t )( const float, void *, void * ); + ( this->*( eventCallback_fii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ] ); + break; + + case 2050 : + typedef void ( idClass::*eventCallback_ifi_t )( void *, const float, void * ); + ( this->*( eventCallback_ifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ] ); + break; + + case 2051 : + typedef void ( idClass::*eventCallback_ffi_t )( const float, const float, void * ); + ( this->*( eventCallback_ffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ] ); + break; + + case 2052 : + typedef void ( idClass::*eventCallback_iif_t )( void *, void *, const float ); + ( this->*( eventCallback_iif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2053 : + typedef void ( idClass::*eventCallback_fif_t )( const float, void *, const float ); + ( this->*( eventCallback_fif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2054 : + typedef void ( idClass::*eventCallback_iff_t )( void *, const float, const float ); + ( this->*( eventCallback_iff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2055 : + typedef void ( idClass::*eventCallback_fff_t )( const float, const float, const float ); + ( this->*( eventCallback_fff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + /******************************************************* + + 4 args + + *******************************************************/ + + case 4096 : + typedef void ( idClass::*eventCallback_iiii_t )( void *, void *, void *, void * ); + ( this->*( eventCallback_iiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4097 : + typedef void ( idClass::*eventCallback_fiii_t )( const float, void *, void *, void * ); + ( this->*( eventCallback_fiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4098 : + typedef void ( idClass::*eventCallback_ifii_t )( void *, const float, void *, void * ); + ( this->*( eventCallback_ifii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4099 : + typedef void ( idClass::*eventCallback_ffii_t )( const float, const float, void *, void * ); + ( this->*( eventCallback_ffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4100 : + typedef void ( idClass::*eventCallback_iifi_t )( void *, void *, const float, void * ); + ( this->*( eventCallback_iifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4101 : + typedef void ( idClass::*eventCallback_fifi_t )( const float, void *, const float, void * ); + ( this->*( eventCallback_fifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4102 : + typedef void ( idClass::*eventCallback_iffi_t )( void *, const float, const float, void * ); + ( this->*( eventCallback_iffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4103 : + typedef void ( idClass::*eventCallback_fffi_t )( const float, const float, const float, void * ); + ( this->*( eventCallback_fffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ] ); + break; + + case 4104 : + typedef void ( idClass::*eventCallback_iiif_t )( void *, void *, void *, const float ); + ( this->*( eventCallback_iiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4105 : + typedef void ( idClass::*eventCallback_fiif_t )( const float, void *, void *, const float ); + ( this->*( eventCallback_fiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4106 : + typedef void ( idClass::*eventCallback_ifif_t )( void *, const float, void *, const float ); + ( this->*( eventCallback_ifif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4107 : + typedef void ( idClass::*eventCallback_ffif_t )( const float, const float, void *, const float ); + ( this->*( eventCallback_ffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4108 : + typedef void ( idClass::*eventCallback_iiff_t )( void *, void *, const float, const float ); + ( this->*( eventCallback_iiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4109 : + typedef void ( idClass::*eventCallback_fiff_t )( const float, void *, const float, const float ); + ( this->*( eventCallback_fiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4110 : + typedef void ( idClass::*eventCallback_ifff_t )( void *, const float, const float, const float ); + ( this->*( eventCallback_ifff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4111 : + typedef void ( idClass::*eventCallback_ffff_t )( const float, const float, const float, const float ); + ( this->*( eventCallback_ffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + /******************************************************* + + 5 args + + *******************************************************/ + + case 8192 : + typedef void ( idClass::*eventCallback_iiiii_t )( void *, void *, void *, void *, void * ); + ( this->*( eventCallback_iiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8193 : + typedef void ( idClass::*eventCallback_fiiii_t )( const float, void *, void *, void *, void * ); + ( this->*( eventCallback_fiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8194 : + typedef void ( idClass::*eventCallback_ifiii_t )( void *, const float, void *, void *, void * ); + ( this->*( eventCallback_ifiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8195 : + typedef void ( idClass::*eventCallback_ffiii_t )( const float, const float, void *, void *, void * ); + ( this->*( eventCallback_ffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8196 : + typedef void ( idClass::*eventCallback_iifii_t )( void *, void *, const float, void *, void * ); + ( this->*( eventCallback_iifii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8197 : + typedef void ( idClass::*eventCallback_fifii_t )( const float, void *, const float, void *, void * ); + ( this->*( eventCallback_fifii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8198 : + typedef void ( idClass::*eventCallback_iffii_t )( void *, const float, const float, void *, void * ); + ( this->*( eventCallback_iffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8199 : + typedef void ( idClass::*eventCallback_fffii_t )( const float, const float, const float, void *, void * ); + ( this->*( eventCallback_fffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8200 : + typedef void ( idClass::*eventCallback_iiifi_t )( void *, void *, void *, const float, void * ); + ( this->*( eventCallback_iiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8201 : + typedef void ( idClass::*eventCallback_fiifi_t )( const float, void *, void *, const float, void * ); + ( this->*( eventCallback_fiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8202 : + typedef void ( idClass::*eventCallback_ififi_t )( void *, const float, void *, const float, void * ); + ( this->*( eventCallback_ififi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8203 : + typedef void ( idClass::*eventCallback_ffifi_t )( const float, const float, void *, const float, void * ); + ( this->*( eventCallback_ffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8204 : + typedef void ( idClass::*eventCallback_iiffi_t )( void *, void *, const float, const float, void * ); + ( this->*( eventCallback_iiffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8205 : + typedef void ( idClass::*eventCallback_fiffi_t )( const float, void *, const float, const float, void * ); + ( this->*( eventCallback_fiffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8206 : + typedef void ( idClass::*eventCallback_ifffi_t )( void *, const float, const float, const float, void * ); + ( this->*( eventCallback_ifffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8207 : + typedef void ( idClass::*eventCallback_ffffi_t )( const float, const float, const float, const float, void * ); + ( this->*( eventCallback_ffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ] ); + break; + + case 8208 : + typedef void ( idClass::*eventCallback_iiiif_t )( void *, void *, void *, void *, const float ); + ( this->*( eventCallback_iiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8209 : + typedef void ( idClass::*eventCallback_fiiif_t )( const float, void *, void *, void *, const float ); + ( this->*( eventCallback_fiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8210 : + typedef void ( idClass::*eventCallback_ifiif_t )( void *, const float, void *, void *, const float ); + ( this->*( eventCallback_ifiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8211 : + typedef void ( idClass::*eventCallback_ffiif_t )( const float, const float, void *, void *, const float ); + ( this->*( eventCallback_ffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8212 : + typedef void ( idClass::*eventCallback_iifif_t )( void *, void *, const float, void *, const float ); + ( this->*( eventCallback_iifif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8213 : + typedef void ( idClass::*eventCallback_fifif_t )( const float, void *, const float, void *, const float ); + ( this->*( eventCallback_fifif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8214 : + typedef void ( idClass::*eventCallback_iffif_t )( void *, const float, const float, void *, const float ); + ( this->*( eventCallback_iffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8215 : + typedef void ( idClass::*eventCallback_fffif_t )( const float, const float, const float, void *, const float ); + ( this->*( eventCallback_fffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8216 : + typedef void ( idClass::*eventCallback_iiiff_t )( void *, void *, void *, const float, const float ); + ( this->*( eventCallback_iiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8217 : + typedef void ( idClass::*eventCallback_fiiff_t )( const float, void *, void *, const float, const float ); + ( this->*( eventCallback_fiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8218 : + typedef void ( idClass::*eventCallback_ififf_t )( void *, const float, void *, const float, const float ); + ( this->*( eventCallback_ififf_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8219 : + typedef void ( idClass::*eventCallback_ffiff_t )( const float, const float, void *, const float, const float ); + ( this->*( eventCallback_ffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8220 : + typedef void ( idClass::*eventCallback_iifff_t )( void *, void *, const float, const float, const float ); + ( this->*( eventCallback_iifff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8221 : + typedef void ( idClass::*eventCallback_fifff_t )( const float, void *, const float, const float, const float ); + ( this->*( eventCallback_fifff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8222 : + typedef void ( idClass::*eventCallback_iffff_t )( void *, const float, const float, const float, const float ); + ( this->*( eventCallback_iffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8223 : + typedef void ( idClass::*eventCallback_fffff_t )( const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + /******************************************************* + + 6 args + + *******************************************************/ + + case 16384 : + typedef void ( idClass::*eventCallback_iiiiii_t )( void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_iiiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16385 : + typedef void ( idClass::*eventCallback_fiiiii_t )( const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_fiiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16386 : + typedef void ( idClass::*eventCallback_ifiiii_t )( void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_ifiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16387 : + typedef void ( idClass::*eventCallback_ffiiii_t )( const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_ffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16388 : + typedef void ( idClass::*eventCallback_iifiii_t )( void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_iifiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16389 : + typedef void ( idClass::*eventCallback_fifiii_t )( const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_fifiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16390 : + typedef void ( idClass::*eventCallback_iffiii_t )( void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_iffiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16391 : + typedef void ( idClass::*eventCallback_fffiii_t )( const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_fffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16392 : + typedef void ( idClass::*eventCallback_iiifii_t )( void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_iiifii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16393 : + typedef void ( idClass::*eventCallback_fiifii_t )( const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_fiifii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16394 : + typedef void ( idClass::*eventCallback_ififii_t )( void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_ififii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16395 : + typedef void ( idClass::*eventCallback_ffifii_t )( const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_ffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16396 : + typedef void ( idClass::*eventCallback_iiffii_t )( void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_iiffii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16397 : + typedef void ( idClass::*eventCallback_fiffii_t )( const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_fiffii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16398 : + typedef void ( idClass::*eventCallback_ifffii_t )( void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_ifffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16399 : + typedef void ( idClass::*eventCallback_ffffii_t )( const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_ffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16400 : + typedef void ( idClass::*eventCallback_iiiifi_t )( void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_iiiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16401 : + typedef void ( idClass::*eventCallback_fiiifi_t )( const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_fiiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16402 : + typedef void ( idClass::*eventCallback_ifiifi_t )( void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_ifiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16403 : + typedef void ( idClass::*eventCallback_ffiifi_t )( const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_ffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16404 : + typedef void ( idClass::*eventCallback_iififi_t )( void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_iififi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16405 : + typedef void ( idClass::*eventCallback_fififi_t )( const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_fififi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16406 : + typedef void ( idClass::*eventCallback_iffifi_t )( void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_iffifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16407 : + typedef void ( idClass::*eventCallback_fffifi_t )( const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_fffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16408 : + typedef void ( idClass::*eventCallback_iiiffi_t )( void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_iiiffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16409 : + typedef void ( idClass::*eventCallback_fiiffi_t )( const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_fiiffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16410 : + typedef void ( idClass::*eventCallback_ififfi_t )( void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_ififfi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16411 : + typedef void ( idClass::*eventCallback_ffiffi_t )( const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_ffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16412 : + typedef void ( idClass::*eventCallback_iifffi_t )( void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_iifffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16413 : + typedef void ( idClass::*eventCallback_fifffi_t )( const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_fifffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16414 : + typedef void ( idClass::*eventCallback_iffffi_t )( void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_iffffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16415 : + typedef void ( idClass::*eventCallback_fffffi_t )( const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_fffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ] ); + break; + + case 16416 : + typedef void ( idClass::*eventCallback_iiiiif_t )( void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_iiiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16417 : + typedef void ( idClass::*eventCallback_fiiiif_t )( const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_fiiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16418 : + typedef void ( idClass::*eventCallback_ifiiif_t )( void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_ifiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16419 : + typedef void ( idClass::*eventCallback_ffiiif_t )( const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_ffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16420 : + typedef void ( idClass::*eventCallback_iifiif_t )( void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_iifiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16421 : + typedef void ( idClass::*eventCallback_fifiif_t )( const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_fifiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16422 : + typedef void ( idClass::*eventCallback_iffiif_t )( void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_iffiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16423 : + typedef void ( idClass::*eventCallback_fffiif_t )( const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_fffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16424 : + typedef void ( idClass::*eventCallback_iiifif_t )( void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_iiifif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16425 : + typedef void ( idClass::*eventCallback_fiifif_t )( const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_fiifif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16426 : + typedef void ( idClass::*eventCallback_ififif_t )( void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_ififif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16427 : + typedef void ( idClass::*eventCallback_ffifif_t )( const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_ffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16428 : + typedef void ( idClass::*eventCallback_iiffif_t )( void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_iiffif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16429 : + typedef void ( idClass::*eventCallback_fiffif_t )( const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_fiffif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16430 : + typedef void ( idClass::*eventCallback_ifffif_t )( void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_ifffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16431 : + typedef void ( idClass::*eventCallback_ffffif_t )( const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_ffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16432 : + typedef void ( idClass::*eventCallback_iiiiff_t )( void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_iiiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16433 : + typedef void ( idClass::*eventCallback_fiiiff_t )( const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_fiiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16434 : + typedef void ( idClass::*eventCallback_ifiiff_t )( void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_ifiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16435 : + typedef void ( idClass::*eventCallback_ffiiff_t )( const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_ffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16436 : + typedef void ( idClass::*eventCallback_iififf_t )( void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_iififf_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16437 : + typedef void ( idClass::*eventCallback_fififf_t )( const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_fififf_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16438 : + typedef void ( idClass::*eventCallback_iffiff_t )( void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_iffiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16439 : + typedef void ( idClass::*eventCallback_fffiff_t )( const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_fffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16440 : + typedef void ( idClass::*eventCallback_iiifff_t )( void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_iiifff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16441 : + typedef void ( idClass::*eventCallback_fiifff_t )( const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_fiifff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16442 : + typedef void ( idClass::*eventCallback_ififff_t )( void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_ififff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16443 : + typedef void ( idClass::*eventCallback_ffifff_t )( const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_ffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16444 : + typedef void ( idClass::*eventCallback_iiffff_t )( void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16445 : + typedef void ( idClass::*eventCallback_fiffff_t )( const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16446 : + typedef void ( idClass::*eventCallback_ifffff_t )( void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16447 : + typedef void ( idClass::*eventCallback_ffffff_t )( const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + /******************************************************* + + 7 args + + *******************************************************/ + + case 32768 : + typedef void ( idClass::*eventCallback_iiiiiii_t )( void *, void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_iiiiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32769 : + typedef void ( idClass::*eventCallback_fiiiiii_t )( const float, void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_fiiiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32770 : + typedef void ( idClass::*eventCallback_ifiiiii_t )( void *, const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_ifiiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32771 : + typedef void ( idClass::*eventCallback_ffiiiii_t )( const float, const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_ffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32772 : + typedef void ( idClass::*eventCallback_iifiiii_t )( void *, void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_iifiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32773 : + typedef void ( idClass::*eventCallback_fifiiii_t )( const float, void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_fifiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32774 : + typedef void ( idClass::*eventCallback_iffiiii_t )( void *, const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_iffiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32775 : + typedef void ( idClass::*eventCallback_fffiiii_t )( const float, const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_fffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32776 : + typedef void ( idClass::*eventCallback_iiifiii_t )( void *, void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_iiifiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32777 : + typedef void ( idClass::*eventCallback_fiifiii_t )( const float, void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_fiifiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32778 : + typedef void ( idClass::*eventCallback_ififiii_t )( void *, const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_ififiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32779 : + typedef void ( idClass::*eventCallback_ffifiii_t )( const float, const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_ffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32780 : + typedef void ( idClass::*eventCallback_iiffiii_t )( void *, void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_iiffiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32781 : + typedef void ( idClass::*eventCallback_fiffiii_t )( const float, void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_fiffiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32782 : + typedef void ( idClass::*eventCallback_ifffiii_t )( void *, const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_ifffiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32783 : + typedef void ( idClass::*eventCallback_ffffiii_t )( const float, const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_ffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32784 : + typedef void ( idClass::*eventCallback_iiiifii_t )( void *, void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_iiiifii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32785 : + typedef void ( idClass::*eventCallback_fiiifii_t )( const float, void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_fiiifii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32786 : + typedef void ( idClass::*eventCallback_ifiifii_t )( void *, const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_ifiifii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32787 : + typedef void ( idClass::*eventCallback_ffiifii_t )( const float, const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_ffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32788 : + typedef void ( idClass::*eventCallback_iififii_t )( void *, void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_iififii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32789 : + typedef void ( idClass::*eventCallback_fififii_t )( const float, void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_fififii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32790 : + typedef void ( idClass::*eventCallback_iffifii_t )( void *, const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_iffifii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32791 : + typedef void ( idClass::*eventCallback_fffifii_t )( const float, const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_fffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32792 : + typedef void ( idClass::*eventCallback_iiiffii_t )( void *, void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_iiiffii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32793 : + typedef void ( idClass::*eventCallback_fiiffii_t )( const float, void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_fiiffii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32794 : + typedef void ( idClass::*eventCallback_ififfii_t )( void *, const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_ififfii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32795 : + typedef void ( idClass::*eventCallback_ffiffii_t )( const float, const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_ffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32796 : + typedef void ( idClass::*eventCallback_iifffii_t )( void *, void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_iifffii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32797 : + typedef void ( idClass::*eventCallback_fifffii_t )( const float, void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_fifffii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32798 : + typedef void ( idClass::*eventCallback_iffffii_t )( void *, const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_iffffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32799 : + typedef void ( idClass::*eventCallback_fffffii_t )( const float, const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_fffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32800 : + typedef void ( idClass::*eventCallback_iiiiifi_t )( void *, void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_iiiiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32801 : + typedef void ( idClass::*eventCallback_fiiiifi_t )( const float, void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_fiiiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32802 : + typedef void ( idClass::*eventCallback_ifiiifi_t )( void *, const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_ifiiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32803 : + typedef void ( idClass::*eventCallback_ffiiifi_t )( const float, const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_ffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32804 : + typedef void ( idClass::*eventCallback_iifiifi_t )( void *, void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_iifiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32805 : + typedef void ( idClass::*eventCallback_fifiifi_t )( const float, void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_fifiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32806 : + typedef void ( idClass::*eventCallback_iffiifi_t )( void *, const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_iffiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32807 : + typedef void ( idClass::*eventCallback_fffiifi_t )( const float, const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_fffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32808 : + typedef void ( idClass::*eventCallback_iiififi_t )( void *, void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_iiififi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32809 : + typedef void ( idClass::*eventCallback_fiififi_t )( const float, void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_fiififi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32810 : + typedef void ( idClass::*eventCallback_ifififi_t )( void *, const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_ifififi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32811 : + typedef void ( idClass::*eventCallback_ffififi_t )( const float, const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_ffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32812 : + typedef void ( idClass::*eventCallback_iiffifi_t )( void *, void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_iiffifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32813 : + typedef void ( idClass::*eventCallback_fiffifi_t )( const float, void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_fiffifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32814 : + typedef void ( idClass::*eventCallback_ifffifi_t )( void *, const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_ifffifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32815 : + typedef void ( idClass::*eventCallback_ffffifi_t )( const float, const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_ffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32816 : + typedef void ( idClass::*eventCallback_iiiiffi_t )( void *, void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_iiiiffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32817 : + typedef void ( idClass::*eventCallback_fiiiffi_t )( const float, void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_fiiiffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32818 : + typedef void ( idClass::*eventCallback_ifiiffi_t )( void *, const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_ifiiffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32819 : + typedef void ( idClass::*eventCallback_ffiiffi_t )( const float, const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_ffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32820 : + typedef void ( idClass::*eventCallback_iififfi_t )( void *, void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_iififfi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32821 : + typedef void ( idClass::*eventCallback_fififfi_t )( const float, void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_fififfi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32822 : + typedef void ( idClass::*eventCallback_iffiffi_t )( void *, const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_iffiffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32823 : + typedef void ( idClass::*eventCallback_fffiffi_t )( const float, const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_fffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32824 : + typedef void ( idClass::*eventCallback_iiifffi_t )( void *, void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_iiifffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32825 : + typedef void ( idClass::*eventCallback_fiifffi_t )( const float, void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_fiifffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32826 : + typedef void ( idClass::*eventCallback_ififffi_t )( void *, const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_ififffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32827 : + typedef void ( idClass::*eventCallback_ffifffi_t )( const float, const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_ffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32828 : + typedef void ( idClass::*eventCallback_iiffffi_t )( void *, void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_iiffffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32829 : + typedef void ( idClass::*eventCallback_fiffffi_t )( const float, void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_fiffffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32830 : + typedef void ( idClass::*eventCallback_ifffffi_t )( void *, const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_ifffffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32831 : + typedef void ( idClass::*eventCallback_ffffffi_t )( const float, const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_ffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ] ); + break; + + case 32832 : + typedef void ( idClass::*eventCallback_iiiiiif_t )( void *, void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_iiiiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32833 : + typedef void ( idClass::*eventCallback_fiiiiif_t )( const float, void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_fiiiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32834 : + typedef void ( idClass::*eventCallback_ifiiiif_t )( void *, const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_ifiiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32835 : + typedef void ( idClass::*eventCallback_ffiiiif_t )( const float, const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_ffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32836 : + typedef void ( idClass::*eventCallback_iifiiif_t )( void *, void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_iifiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32837 : + typedef void ( idClass::*eventCallback_fifiiif_t )( const float, void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_fifiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32838 : + typedef void ( idClass::*eventCallback_iffiiif_t )( void *, const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_iffiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32839 : + typedef void ( idClass::*eventCallback_fffiiif_t )( const float, const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_fffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32840 : + typedef void ( idClass::*eventCallback_iiifiif_t )( void *, void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_iiifiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32841 : + typedef void ( idClass::*eventCallback_fiifiif_t )( const float, void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_fiifiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32842 : + typedef void ( idClass::*eventCallback_ififiif_t )( void *, const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_ififiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32843 : + typedef void ( idClass::*eventCallback_ffifiif_t )( const float, const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_ffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32844 : + typedef void ( idClass::*eventCallback_iiffiif_t )( void *, void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_iiffiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32845 : + typedef void ( idClass::*eventCallback_fiffiif_t )( const float, void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_fiffiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32846 : + typedef void ( idClass::*eventCallback_ifffiif_t )( void *, const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_ifffiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32847 : + typedef void ( idClass::*eventCallback_ffffiif_t )( const float, const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_ffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32848 : + typedef void ( idClass::*eventCallback_iiiifif_t )( void *, void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_iiiifif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32849 : + typedef void ( idClass::*eventCallback_fiiifif_t )( const float, void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_fiiifif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32850 : + typedef void ( idClass::*eventCallback_ifiifif_t )( void *, const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_ifiifif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32851 : + typedef void ( idClass::*eventCallback_ffiifif_t )( const float, const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_ffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32852 : + typedef void ( idClass::*eventCallback_iififif_t )( void *, void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_iififif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32853 : + typedef void ( idClass::*eventCallback_fififif_t )( const float, void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_fififif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32854 : + typedef void ( idClass::*eventCallback_iffifif_t )( void *, const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_iffifif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32855 : + typedef void ( idClass::*eventCallback_fffifif_t )( const float, const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_fffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32856 : + typedef void ( idClass::*eventCallback_iiiffif_t )( void *, void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_iiiffif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32857 : + typedef void ( idClass::*eventCallback_fiiffif_t )( const float, void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_fiiffif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32858 : + typedef void ( idClass::*eventCallback_ififfif_t )( void *, const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_ififfif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32859 : + typedef void ( idClass::*eventCallback_ffiffif_t )( const float, const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_ffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32860 : + typedef void ( idClass::*eventCallback_iifffif_t )( void *, void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_iifffif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32861 : + typedef void ( idClass::*eventCallback_fifffif_t )( const float, void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_fifffif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32862 : + typedef void ( idClass::*eventCallback_iffffif_t )( void *, const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_iffffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32863 : + typedef void ( idClass::*eventCallback_fffffif_t )( const float, const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_fffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32864 : + typedef void ( idClass::*eventCallback_iiiiiff_t )( void *, void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_iiiiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32865 : + typedef void ( idClass::*eventCallback_fiiiiff_t )( const float, void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_fiiiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32866 : + typedef void ( idClass::*eventCallback_ifiiiff_t )( void *, const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_ifiiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32867 : + typedef void ( idClass::*eventCallback_ffiiiff_t )( const float, const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_ffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32868 : + typedef void ( idClass::*eventCallback_iifiiff_t )( void *, void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_iifiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32869 : + typedef void ( idClass::*eventCallback_fifiiff_t )( const float, void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_fifiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32870 : + typedef void ( idClass::*eventCallback_iffiiff_t )( void *, const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_iffiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32871 : + typedef void ( idClass::*eventCallback_fffiiff_t )( const float, const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_fffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32872 : + typedef void ( idClass::*eventCallback_iiififf_t )( void *, void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_iiififf_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32873 : + typedef void ( idClass::*eventCallback_fiififf_t )( const float, void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_fiififf_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32874 : + typedef void ( idClass::*eventCallback_ifififf_t )( void *, const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_ifififf_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32875 : + typedef void ( idClass::*eventCallback_ffififf_t )( const float, const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_ffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32876 : + typedef void ( idClass::*eventCallback_iiffiff_t )( void *, void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_iiffiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32877 : + typedef void ( idClass::*eventCallback_fiffiff_t )( const float, void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_fiffiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32878 : + typedef void ( idClass::*eventCallback_ifffiff_t )( void *, const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_ifffiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32879 : + typedef void ( idClass::*eventCallback_ffffiff_t )( const float, const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_ffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32880 : + typedef void ( idClass::*eventCallback_iiiifff_t )( void *, void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_iiiifff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32881 : + typedef void ( idClass::*eventCallback_fiiifff_t )( const float, void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_fiiifff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32882 : + typedef void ( idClass::*eventCallback_ifiifff_t )( void *, const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_ifiifff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32883 : + typedef void ( idClass::*eventCallback_ffiifff_t )( const float, const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_ffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32884 : + typedef void ( idClass::*eventCallback_iififff_t )( void *, void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_iififff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32885 : + typedef void ( idClass::*eventCallback_fififff_t )( const float, void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_fififff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32886 : + typedef void ( idClass::*eventCallback_iffifff_t )( void *, const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_iffifff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32887 : + typedef void ( idClass::*eventCallback_fffifff_t )( const float, const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_fffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32888 : + typedef void ( idClass::*eventCallback_iiiffff_t )( void *, void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiffff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32889 : + typedef void ( idClass::*eventCallback_fiiffff_t )( const float, void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiffff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32890 : + typedef void ( idClass::*eventCallback_ififfff_t )( void *, const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_ififfff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32891 : + typedef void ( idClass::*eventCallback_ffiffff_t )( const float, const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32892 : + typedef void ( idClass::*eventCallback_iifffff_t )( void *, void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iifffff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32893 : + typedef void ( idClass::*eventCallback_fifffff_t )( const float, void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fifffff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32894 : + typedef void ( idClass::*eventCallback_iffffff_t )( void *, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iffffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32895 : + typedef void ( idClass::*eventCallback_fffffff_t )( const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + /******************************************************* + + 8 args + + *******************************************************/ + + case 65536 : + typedef void ( idClass::*eventCallback_iiiiiiii_t )( void *, void *, void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_iiiiiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65537 : + typedef void ( idClass::*eventCallback_fiiiiiii_t )( const float, void *, void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_fiiiiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65538 : + typedef void ( idClass::*eventCallback_ifiiiiii_t )( void *, const float, void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_ifiiiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65539 : + typedef void ( idClass::*eventCallback_ffiiiiii_t )( const float, const float, void *, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_ffiiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65540 : + typedef void ( idClass::*eventCallback_iifiiiii_t )( void *, void *, const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_iifiiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65541 : + typedef void ( idClass::*eventCallback_fifiiiii_t )( const float, void *, const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_fifiiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65542 : + typedef void ( idClass::*eventCallback_iffiiiii_t )( void *, const float, const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_iffiiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65543 : + typedef void ( idClass::*eventCallback_fffiiiii_t )( const float, const float, const float, void *, void *, void *, void *, void * ); + ( this->*( eventCallback_fffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65544 : + typedef void ( idClass::*eventCallback_iiifiiii_t )( void *, void *, void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_iiifiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65545 : + typedef void ( idClass::*eventCallback_fiifiiii_t )( const float, void *, void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_fiifiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65546 : + typedef void ( idClass::*eventCallback_ififiiii_t )( void *, const float, void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_ififiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65547 : + typedef void ( idClass::*eventCallback_ffifiiii_t )( const float, const float, void *, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_ffifiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65548 : + typedef void ( idClass::*eventCallback_iiffiiii_t )( void *, void *, const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_iiffiiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65549 : + typedef void ( idClass::*eventCallback_fiffiiii_t )( const float, void *, const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_fiffiiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65550 : + typedef void ( idClass::*eventCallback_ifffiiii_t )( void *, const float, const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_ifffiiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65551 : + typedef void ( idClass::*eventCallback_ffffiiii_t )( const float, const float, const float, const float, void *, void *, void *, void * ); + ( this->*( eventCallback_ffffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65552 : + typedef void ( idClass::*eventCallback_iiiifiii_t )( void *, void *, void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_iiiifiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65553 : + typedef void ( idClass::*eventCallback_fiiifiii_t )( const float, void *, void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_fiiifiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65554 : + typedef void ( idClass::*eventCallback_ifiifiii_t )( void *, const float, void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_ifiifiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65555 : + typedef void ( idClass::*eventCallback_ffiifiii_t )( const float, const float, void *, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_ffiifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65556 : + typedef void ( idClass::*eventCallback_iififiii_t )( void *, void *, const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_iififiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65557 : + typedef void ( idClass::*eventCallback_fififiii_t )( const float, void *, const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_fififiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65558 : + typedef void ( idClass::*eventCallback_iffifiii_t )( void *, const float, const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_iffifiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65559 : + typedef void ( idClass::*eventCallback_fffifiii_t )( const float, const float, const float, void *, const float, void *, void *, void * ); + ( this->*( eventCallback_fffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65560 : + typedef void ( idClass::*eventCallback_iiiffiii_t )( void *, void *, void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_iiiffiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65561 : + typedef void ( idClass::*eventCallback_fiiffiii_t )( const float, void *, void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_fiiffiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65562 : + typedef void ( idClass::*eventCallback_ififfiii_t )( void *, const float, void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_ififfiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65563 : + typedef void ( idClass::*eventCallback_ffiffiii_t )( const float, const float, void *, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_ffiffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65564 : + typedef void ( idClass::*eventCallback_iifffiii_t )( void *, void *, const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_iifffiii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65565 : + typedef void ( idClass::*eventCallback_fifffiii_t )( const float, void *, const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_fifffiii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65566 : + typedef void ( idClass::*eventCallback_iffffiii_t )( void *, const float, const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_iffffiii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65567 : + typedef void ( idClass::*eventCallback_fffffiii_t )( const float, const float, const float, const float, const float, void *, void *, void * ); + ( this->*( eventCallback_fffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65568 : + typedef void ( idClass::*eventCallback_iiiiifii_t )( void *, void *, void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_iiiiifii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65569 : + typedef void ( idClass::*eventCallback_fiiiifii_t )( const float, void *, void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_fiiiifii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65570 : + typedef void ( idClass::*eventCallback_ifiiifii_t )( void *, const float, void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_ifiiifii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65571 : + typedef void ( idClass::*eventCallback_ffiiifii_t )( const float, const float, void *, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_ffiiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65572 : + typedef void ( idClass::*eventCallback_iifiifii_t )( void *, void *, const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_iifiifii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65573 : + typedef void ( idClass::*eventCallback_fifiifii_t )( const float, void *, const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_fifiifii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65574 : + typedef void ( idClass::*eventCallback_iffiifii_t )( void *, const float, const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_iffiifii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65575 : + typedef void ( idClass::*eventCallback_fffiifii_t )( const float, const float, const float, void *, void *, const float, void *, void * ); + ( this->*( eventCallback_fffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65576 : + typedef void ( idClass::*eventCallback_iiififii_t )( void *, void *, void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_iiififii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65577 : + typedef void ( idClass::*eventCallback_fiififii_t )( const float, void *, void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_fiififii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65578 : + typedef void ( idClass::*eventCallback_ifififii_t )( void *, const float, void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_ifififii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65579 : + typedef void ( idClass::*eventCallback_ffififii_t )( const float, const float, void *, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_ffififii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65580 : + typedef void ( idClass::*eventCallback_iiffifii_t )( void *, void *, const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_iiffifii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65581 : + typedef void ( idClass::*eventCallback_fiffifii_t )( const float, void *, const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_fiffifii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65582 : + typedef void ( idClass::*eventCallback_ifffifii_t )( void *, const float, const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_ifffifii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65583 : + typedef void ( idClass::*eventCallback_ffffifii_t )( const float, const float, const float, const float, void *, const float, void *, void * ); + ( this->*( eventCallback_ffffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65584 : + typedef void ( idClass::*eventCallback_iiiiffii_t )( void *, void *, void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_iiiiffii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65585 : + typedef void ( idClass::*eventCallback_fiiiffii_t )( const float, void *, void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_fiiiffii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65586 : + typedef void ( idClass::*eventCallback_ifiiffii_t )( void *, const float, void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_ifiiffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65587 : + typedef void ( idClass::*eventCallback_ffiiffii_t )( const float, const float, void *, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_ffiiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65588 : + typedef void ( idClass::*eventCallback_iififfii_t )( void *, void *, const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_iififfii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65589 : + typedef void ( idClass::*eventCallback_fififfii_t )( const float, void *, const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_fififfii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65590 : + typedef void ( idClass::*eventCallback_iffiffii_t )( void *, const float, const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_iffiffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65591 : + typedef void ( idClass::*eventCallback_fffiffii_t )( const float, const float, const float, void *, const float, const float, void *, void * ); + ( this->*( eventCallback_fffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65592 : + typedef void ( idClass::*eventCallback_iiifffii_t )( void *, void *, void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_iiifffii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65593 : + typedef void ( idClass::*eventCallback_fiifffii_t )( const float, void *, void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_fiifffii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65594 : + typedef void ( idClass::*eventCallback_ififffii_t )( void *, const float, void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_ififffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65595 : + typedef void ( idClass::*eventCallback_ffifffii_t )( const float, const float, void *, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_ffifffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65596 : + typedef void ( idClass::*eventCallback_iiffffii_t )( void *, void *, const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_iiffffii_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65597 : + typedef void ( idClass::*eventCallback_fiffffii_t )( const float, void *, const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_fiffffii_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65598 : + typedef void ( idClass::*eventCallback_ifffffii_t )( void *, const float, const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_ifffffii_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65599 : + typedef void ( idClass::*eventCallback_ffffffii_t )( const float, const float, const float, const float, const float, const float, void *, void * ); + ( this->*( eventCallback_ffffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65600 : + typedef void ( idClass::*eventCallback_iiiiiifi_t )( void *, void *, void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_iiiiiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65601 : + typedef void ( idClass::*eventCallback_fiiiiifi_t )( const float, void *, void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_fiiiiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65602 : + typedef void ( idClass::*eventCallback_ifiiiifi_t )( void *, const float, void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_ifiiiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65603 : + typedef void ( idClass::*eventCallback_ffiiiifi_t )( const float, const float, void *, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_ffiiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65604 : + typedef void ( idClass::*eventCallback_iifiiifi_t )( void *, void *, const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_iifiiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65605 : + typedef void ( idClass::*eventCallback_fifiiifi_t )( const float, void *, const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_fifiiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65606 : + typedef void ( idClass::*eventCallback_iffiiifi_t )( void *, const float, const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_iffiiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65607 : + typedef void ( idClass::*eventCallback_fffiiifi_t )( const float, const float, const float, void *, void *, void *, const float, void * ); + ( this->*( eventCallback_fffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65608 : + typedef void ( idClass::*eventCallback_iiifiifi_t )( void *, void *, void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_iiifiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65609 : + typedef void ( idClass::*eventCallback_fiifiifi_t )( const float, void *, void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_fiifiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65610 : + typedef void ( idClass::*eventCallback_ififiifi_t )( void *, const float, void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_ififiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65611 : + typedef void ( idClass::*eventCallback_ffifiifi_t )( const float, const float, void *, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_ffifiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65612 : + typedef void ( idClass::*eventCallback_iiffiifi_t )( void *, void *, const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_iiffiifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65613 : + typedef void ( idClass::*eventCallback_fiffiifi_t )( const float, void *, const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_fiffiifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65614 : + typedef void ( idClass::*eventCallback_ifffiifi_t )( void *, const float, const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_ifffiifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65615 : + typedef void ( idClass::*eventCallback_ffffiifi_t )( const float, const float, const float, const float, void *, void *, const float, void * ); + ( this->*( eventCallback_ffffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65616 : + typedef void ( idClass::*eventCallback_iiiififi_t )( void *, void *, void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_iiiififi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65617 : + typedef void ( idClass::*eventCallback_fiiififi_t )( const float, void *, void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_fiiififi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65618 : + typedef void ( idClass::*eventCallback_ifiififi_t )( void *, const float, void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_ifiififi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65619 : + typedef void ( idClass::*eventCallback_ffiififi_t )( const float, const float, void *, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_ffiififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65620 : + typedef void ( idClass::*eventCallback_iifififi_t )( void *, void *, const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_iifififi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65621 : + typedef void ( idClass::*eventCallback_fifififi_t )( const float, void *, const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_fifififi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65622 : + typedef void ( idClass::*eventCallback_iffififi_t )( void *, const float, const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_iffififi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65623 : + typedef void ( idClass::*eventCallback_fffififi_t )( const float, const float, const float, void *, const float, void *, const float, void * ); + ( this->*( eventCallback_fffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65624 : + typedef void ( idClass::*eventCallback_iiiffifi_t )( void *, void *, void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_iiiffifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65625 : + typedef void ( idClass::*eventCallback_fiiffifi_t )( const float, void *, void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_fiiffifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65626 : + typedef void ( idClass::*eventCallback_ififfifi_t )( void *, const float, void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_ififfifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65627 : + typedef void ( idClass::*eventCallback_ffiffifi_t )( const float, const float, void *, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_ffiffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65628 : + typedef void ( idClass::*eventCallback_iifffifi_t )( void *, void *, const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_iifffifi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65629 : + typedef void ( idClass::*eventCallback_fifffifi_t )( const float, void *, const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_fifffifi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65630 : + typedef void ( idClass::*eventCallback_iffffifi_t )( void *, const float, const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_iffffifi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65631 : + typedef void ( idClass::*eventCallback_fffffifi_t )( const float, const float, const float, const float, const float, void *, const float, void * ); + ( this->*( eventCallback_fffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65632 : + typedef void ( idClass::*eventCallback_iiiiiffi_t )( void *, void *, void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_iiiiiffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65633 : + typedef void ( idClass::*eventCallback_fiiiiffi_t )( const float, void *, void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_fiiiiffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65634 : + typedef void ( idClass::*eventCallback_ifiiiffi_t )( void *, const float, void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_ifiiiffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65635 : + typedef void ( idClass::*eventCallback_ffiiiffi_t )( const float, const float, void *, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_ffiiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65636 : + typedef void ( idClass::*eventCallback_iifiiffi_t )( void *, void *, const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_iifiiffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65637 : + typedef void ( idClass::*eventCallback_fifiiffi_t )( const float, void *, const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_fifiiffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65638 : + typedef void ( idClass::*eventCallback_iffiiffi_t )( void *, const float, const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_iffiiffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65639 : + typedef void ( idClass::*eventCallback_fffiiffi_t )( const float, const float, const float, void *, void *, const float, const float, void * ); + ( this->*( eventCallback_fffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65640 : + typedef void ( idClass::*eventCallback_iiififfi_t )( void *, void *, void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_iiififfi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65641 : + typedef void ( idClass::*eventCallback_fiififfi_t )( const float, void *, void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_fiififfi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65642 : + typedef void ( idClass::*eventCallback_ifififfi_t )( void *, const float, void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_ifififfi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65643 : + typedef void ( idClass::*eventCallback_ffififfi_t )( const float, const float, void *, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_ffififfi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65644 : + typedef void ( idClass::*eventCallback_iiffiffi_t )( void *, void *, const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_iiffiffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65645 : + typedef void ( idClass::*eventCallback_fiffiffi_t )( const float, void *, const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_fiffiffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65646 : + typedef void ( idClass::*eventCallback_ifffiffi_t )( void *, const float, const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_ifffiffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65647 : + typedef void ( idClass::*eventCallback_ffffiffi_t )( const float, const float, const float, const float, void *, const float, const float, void * ); + ( this->*( eventCallback_ffffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65648 : + typedef void ( idClass::*eventCallback_iiiifffi_t )( void *, void *, void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_iiiifffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65649 : + typedef void ( idClass::*eventCallback_fiiifffi_t )( const float, void *, void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_fiiifffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65650 : + typedef void ( idClass::*eventCallback_ifiifffi_t )( void *, const float, void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_ifiifffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65651 : + typedef void ( idClass::*eventCallback_ffiifffi_t )( const float, const float, void *, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_ffiifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65652 : + typedef void ( idClass::*eventCallback_iififffi_t )( void *, void *, const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_iififffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65653 : + typedef void ( idClass::*eventCallback_fififffi_t )( const float, void *, const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_fififffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65654 : + typedef void ( idClass::*eventCallback_iffifffi_t )( void *, const float, const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_iffifffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65655 : + typedef void ( idClass::*eventCallback_fffifffi_t )( const float, const float, const float, void *, const float, const float, const float, void * ); + ( this->*( eventCallback_fffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65656 : + typedef void ( idClass::*eventCallback_iiiffffi_t )( void *, void *, void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_iiiffffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65657 : + typedef void ( idClass::*eventCallback_fiiffffi_t )( const float, void *, void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_fiiffffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65658 : + typedef void ( idClass::*eventCallback_ififfffi_t )( void *, const float, void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_ififfffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65659 : + typedef void ( idClass::*eventCallback_ffiffffi_t )( const float, const float, void *, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_ffiffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65660 : + typedef void ( idClass::*eventCallback_iifffffi_t )( void *, void *, const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_iifffffi_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65661 : + typedef void ( idClass::*eventCallback_fifffffi_t )( const float, void *, const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_fifffffi_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65662 : + typedef void ( idClass::*eventCallback_iffffffi_t )( void *, const float, const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_iffffffi_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65663 : + typedef void ( idClass::*eventCallback_fffffffi_t )( const float, const float, const float, const float, const float, const float, const float, void * ); + ( this->*( eventCallback_fffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], (void *)data[ 7 ] ); + break; + + case 65664 : + typedef void ( idClass::*eventCallback_iiiiiiif_t )( void *, void *, void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_iiiiiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65665 : + typedef void ( idClass::*eventCallback_fiiiiiif_t )( const float, void *, void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_fiiiiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65666 : + typedef void ( idClass::*eventCallback_ifiiiiif_t )( void *, const float, void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_ifiiiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65667 : + typedef void ( idClass::*eventCallback_ffiiiiif_t )( const float, const float, void *, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_ffiiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65668 : + typedef void ( idClass::*eventCallback_iifiiiif_t )( void *, void *, const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_iifiiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65669 : + typedef void ( idClass::*eventCallback_fifiiiif_t )( const float, void *, const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_fifiiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65670 : + typedef void ( idClass::*eventCallback_iffiiiif_t )( void *, const float, const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_iffiiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65671 : + typedef void ( idClass::*eventCallback_fffiiiif_t )( const float, const float, const float, void *, void *, void *, void *, const float ); + ( this->*( eventCallback_fffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65672 : + typedef void ( idClass::*eventCallback_iiifiiif_t )( void *, void *, void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_iiifiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65673 : + typedef void ( idClass::*eventCallback_fiifiiif_t )( const float, void *, void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_fiifiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65674 : + typedef void ( idClass::*eventCallback_ififiiif_t )( void *, const float, void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_ififiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65675 : + typedef void ( idClass::*eventCallback_ffifiiif_t )( const float, const float, void *, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_ffifiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65676 : + typedef void ( idClass::*eventCallback_iiffiiif_t )( void *, void *, const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_iiffiiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65677 : + typedef void ( idClass::*eventCallback_fiffiiif_t )( const float, void *, const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_fiffiiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65678 : + typedef void ( idClass::*eventCallback_ifffiiif_t )( void *, const float, const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_ifffiiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65679 : + typedef void ( idClass::*eventCallback_ffffiiif_t )( const float, const float, const float, const float, void *, void *, void *, const float ); + ( this->*( eventCallback_ffffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65680 : + typedef void ( idClass::*eventCallback_iiiifiif_t )( void *, void *, void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_iiiifiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65681 : + typedef void ( idClass::*eventCallback_fiiifiif_t )( const float, void *, void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_fiiifiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65682 : + typedef void ( idClass::*eventCallback_ifiifiif_t )( void *, const float, void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_ifiifiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65683 : + typedef void ( idClass::*eventCallback_ffiifiif_t )( const float, const float, void *, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_ffiifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65684 : + typedef void ( idClass::*eventCallback_iififiif_t )( void *, void *, const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_iififiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65685 : + typedef void ( idClass::*eventCallback_fififiif_t )( const float, void *, const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_fififiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65686 : + typedef void ( idClass::*eventCallback_iffifiif_t )( void *, const float, const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_iffifiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65687 : + typedef void ( idClass::*eventCallback_fffifiif_t )( const float, const float, const float, void *, const float, void *, void *, const float ); + ( this->*( eventCallback_fffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65688 : + typedef void ( idClass::*eventCallback_iiiffiif_t )( void *, void *, void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_iiiffiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65689 : + typedef void ( idClass::*eventCallback_fiiffiif_t )( const float, void *, void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_fiiffiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65690 : + typedef void ( idClass::*eventCallback_ififfiif_t )( void *, const float, void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_ififfiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65691 : + typedef void ( idClass::*eventCallback_ffiffiif_t )( const float, const float, void *, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_ffiffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65692 : + typedef void ( idClass::*eventCallback_iifffiif_t )( void *, void *, const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_iifffiif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65693 : + typedef void ( idClass::*eventCallback_fifffiif_t )( const float, void *, const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_fifffiif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65694 : + typedef void ( idClass::*eventCallback_iffffiif_t )( void *, const float, const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_iffffiif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65695 : + typedef void ( idClass::*eventCallback_fffffiif_t )( const float, const float, const float, const float, const float, void *, void *, const float ); + ( this->*( eventCallback_fffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65696 : + typedef void ( idClass::*eventCallback_iiiiifif_t )( void *, void *, void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_iiiiifif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65697 : + typedef void ( idClass::*eventCallback_fiiiifif_t )( const float, void *, void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_fiiiifif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65698 : + typedef void ( idClass::*eventCallback_ifiiifif_t )( void *, const float, void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_ifiiifif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65699 : + typedef void ( idClass::*eventCallback_ffiiifif_t )( const float, const float, void *, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_ffiiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65700 : + typedef void ( idClass::*eventCallback_iifiifif_t )( void *, void *, const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_iifiifif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65701 : + typedef void ( idClass::*eventCallback_fifiifif_t )( const float, void *, const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_fifiifif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65702 : + typedef void ( idClass::*eventCallback_iffiifif_t )( void *, const float, const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_iffiifif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65703 : + typedef void ( idClass::*eventCallback_fffiifif_t )( const float, const float, const float, void *, void *, const float, void *, const float ); + ( this->*( eventCallback_fffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65704 : + typedef void ( idClass::*eventCallback_iiififif_t )( void *, void *, void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_iiififif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65705 : + typedef void ( idClass::*eventCallback_fiififif_t )( const float, void *, void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_fiififif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65706 : + typedef void ( idClass::*eventCallback_ifififif_t )( void *, const float, void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_ifififif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65707 : + typedef void ( idClass::*eventCallback_ffififif_t )( const float, const float, void *, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_ffififif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65708 : + typedef void ( idClass::*eventCallback_iiffifif_t )( void *, void *, const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_iiffifif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65709 : + typedef void ( idClass::*eventCallback_fiffifif_t )( const float, void *, const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_fiffifif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65710 : + typedef void ( idClass::*eventCallback_ifffifif_t )( void *, const float, const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_ifffifif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65711 : + typedef void ( idClass::*eventCallback_ffffifif_t )( const float, const float, const float, const float, void *, const float, void *, const float ); + ( this->*( eventCallback_ffffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65712 : + typedef void ( idClass::*eventCallback_iiiiffif_t )( void *, void *, void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_iiiiffif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65713 : + typedef void ( idClass::*eventCallback_fiiiffif_t )( const float, void *, void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_fiiiffif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65714 : + typedef void ( idClass::*eventCallback_ifiiffif_t )( void *, const float, void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_ifiiffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65715 : + typedef void ( idClass::*eventCallback_ffiiffif_t )( const float, const float, void *, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_ffiiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65716 : + typedef void ( idClass::*eventCallback_iififfif_t )( void *, void *, const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_iififfif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65717 : + typedef void ( idClass::*eventCallback_fififfif_t )( const float, void *, const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_fififfif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65718 : + typedef void ( idClass::*eventCallback_iffiffif_t )( void *, const float, const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_iffiffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65719 : + typedef void ( idClass::*eventCallback_fffiffif_t )( const float, const float, const float, void *, const float, const float, void *, const float ); + ( this->*( eventCallback_fffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65720 : + typedef void ( idClass::*eventCallback_iiifffif_t )( void *, void *, void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_iiifffif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65721 : + typedef void ( idClass::*eventCallback_fiifffif_t )( const float, void *, void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_fiifffif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65722 : + typedef void ( idClass::*eventCallback_ififffif_t )( void *, const float, void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_ififffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65723 : + typedef void ( idClass::*eventCallback_ffifffif_t )( const float, const float, void *, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_ffifffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65724 : + typedef void ( idClass::*eventCallback_iiffffif_t )( void *, void *, const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_iiffffif_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65725 : + typedef void ( idClass::*eventCallback_fiffffif_t )( const float, void *, const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_fiffffif_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65726 : + typedef void ( idClass::*eventCallback_ifffffif_t )( void *, const float, const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_ifffffif_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65727 : + typedef void ( idClass::*eventCallback_ffffffif_t )( const float, const float, const float, const float, const float, const float, void *, const float ); + ( this->*( eventCallback_ffffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], (void *)data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65728 : + typedef void ( idClass::*eventCallback_iiiiiiff_t )( void *, void *, void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_iiiiiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65729 : + typedef void ( idClass::*eventCallback_fiiiiiff_t )( const float, void *, void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_fiiiiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65730 : + typedef void ( idClass::*eventCallback_ifiiiiff_t )( void *, const float, void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_ifiiiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65731 : + typedef void ( idClass::*eventCallback_ffiiiiff_t )( const float, const float, void *, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_ffiiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65732 : + typedef void ( idClass::*eventCallback_iifiiiff_t )( void *, void *, const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_iifiiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65733 : + typedef void ( idClass::*eventCallback_fifiiiff_t )( const float, void *, const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_fifiiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65734 : + typedef void ( idClass::*eventCallback_iffiiiff_t )( void *, const float, const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_iffiiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65735 : + typedef void ( idClass::*eventCallback_fffiiiff_t )( const float, const float, const float, void *, void *, void *, const float, const float ); + ( this->*( eventCallback_fffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65736 : + typedef void ( idClass::*eventCallback_iiifiiff_t )( void *, void *, void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_iiifiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65737 : + typedef void ( idClass::*eventCallback_fiifiiff_t )( const float, void *, void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_fiifiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65738 : + typedef void ( idClass::*eventCallback_ififiiff_t )( void *, const float, void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_ififiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65739 : + typedef void ( idClass::*eventCallback_ffifiiff_t )( const float, const float, void *, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_ffifiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65740 : + typedef void ( idClass::*eventCallback_iiffiiff_t )( void *, void *, const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_iiffiiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65741 : + typedef void ( idClass::*eventCallback_fiffiiff_t )( const float, void *, const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_fiffiiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65742 : + typedef void ( idClass::*eventCallback_ifffiiff_t )( void *, const float, const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_ifffiiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65743 : + typedef void ( idClass::*eventCallback_ffffiiff_t )( const float, const float, const float, const float, void *, void *, const float, const float ); + ( this->*( eventCallback_ffffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65744 : + typedef void ( idClass::*eventCallback_iiiififf_t )( void *, void *, void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_iiiififf_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65745 : + typedef void ( idClass::*eventCallback_fiiififf_t )( const float, void *, void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_fiiififf_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65746 : + typedef void ( idClass::*eventCallback_ifiififf_t )( void *, const float, void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_ifiififf_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65747 : + typedef void ( idClass::*eventCallback_ffiififf_t )( const float, const float, void *, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_ffiififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65748 : + typedef void ( idClass::*eventCallback_iifififf_t )( void *, void *, const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_iifififf_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65749 : + typedef void ( idClass::*eventCallback_fifififf_t )( const float, void *, const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_fifififf_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65750 : + typedef void ( idClass::*eventCallback_iffififf_t )( void *, const float, const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_iffififf_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65751 : + typedef void ( idClass::*eventCallback_fffififf_t )( const float, const float, const float, void *, const float, void *, const float, const float ); + ( this->*( eventCallback_fffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65752 : + typedef void ( idClass::*eventCallback_iiiffiff_t )( void *, void *, void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_iiiffiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65753 : + typedef void ( idClass::*eventCallback_fiiffiff_t )( const float, void *, void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_fiiffiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65754 : + typedef void ( idClass::*eventCallback_ififfiff_t )( void *, const float, void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_ififfiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65755 : + typedef void ( idClass::*eventCallback_ffiffiff_t )( const float, const float, void *, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_ffiffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65756 : + typedef void ( idClass::*eventCallback_iifffiff_t )( void *, void *, const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_iifffiff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65757 : + typedef void ( idClass::*eventCallback_fifffiff_t )( const float, void *, const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_fifffiff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65758 : + typedef void ( idClass::*eventCallback_iffffiff_t )( void *, const float, const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_iffffiff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65759 : + typedef void ( idClass::*eventCallback_fffffiff_t )( const float, const float, const float, const float, const float, void *, const float, const float ); + ( this->*( eventCallback_fffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], (void *)data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65760 : + typedef void ( idClass::*eventCallback_iiiiifff_t )( void *, void *, void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_iiiiifff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65761 : + typedef void ( idClass::*eventCallback_fiiiifff_t )( const float, void *, void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_fiiiifff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65762 : + typedef void ( idClass::*eventCallback_ifiiifff_t )( void *, const float, void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_ifiiifff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65763 : + typedef void ( idClass::*eventCallback_ffiiifff_t )( const float, const float, void *, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_ffiiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65764 : + typedef void ( idClass::*eventCallback_iifiifff_t )( void *, void *, const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_iifiifff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65765 : + typedef void ( idClass::*eventCallback_fifiifff_t )( const float, void *, const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_fifiifff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65766 : + typedef void ( idClass::*eventCallback_iffiifff_t )( void *, const float, const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_iffiifff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65767 : + typedef void ( idClass::*eventCallback_fffiifff_t )( const float, const float, const float, void *, void *, const float, const float, const float ); + ( this->*( eventCallback_fffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65768 : + typedef void ( idClass::*eventCallback_iiififff_t )( void *, void *, void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_iiififff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65769 : + typedef void ( idClass::*eventCallback_fiififff_t )( const float, void *, void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_fiififff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65770 : + typedef void ( idClass::*eventCallback_ifififff_t )( void *, const float, void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_ifififff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65771 : + typedef void ( idClass::*eventCallback_ffififff_t )( const float, const float, void *, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_ffififff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65772 : + typedef void ( idClass::*eventCallback_iiffifff_t )( void *, void *, const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_iiffifff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65773 : + typedef void ( idClass::*eventCallback_fiffifff_t )( const float, void *, const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_fiffifff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65774 : + typedef void ( idClass::*eventCallback_ifffifff_t )( void *, const float, const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_ifffifff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65775 : + typedef void ( idClass::*eventCallback_ffffifff_t )( const float, const float, const float, const float, void *, const float, const float, const float ); + ( this->*( eventCallback_ffffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], (void *)data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65776 : + typedef void ( idClass::*eventCallback_iiiiffff_t )( void *, void *, void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiiffff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65777 : + typedef void ( idClass::*eventCallback_fiiiffff_t )( const float, void *, void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiiffff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65778 : + typedef void ( idClass::*eventCallback_ifiiffff_t )( void *, const float, void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_ifiiffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65779 : + typedef void ( idClass::*eventCallback_ffiiffff_t )( const float, const float, void *, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65780 : + typedef void ( idClass::*eventCallback_iififfff_t )( void *, void *, const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_iififfff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65781 : + typedef void ( idClass::*eventCallback_fififfff_t )( const float, void *, const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_fififfff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65782 : + typedef void ( idClass::*eventCallback_iffiffff_t )( void *, const float, const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_iffiffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65783 : + typedef void ( idClass::*eventCallback_fffiffff_t )( const float, const float, const float, void *, const float, const float, const float, const float ); + ( this->*( eventCallback_fffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], (void *)data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65784 : + typedef void ( idClass::*eventCallback_iiifffff_t )( void *, void *, void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiifffff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65785 : + typedef void ( idClass::*eventCallback_fiifffff_t )( const float, void *, void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiifffff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65786 : + typedef void ( idClass::*eventCallback_ififffff_t )( void *, const float, void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ififffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65787 : + typedef void ( idClass::*eventCallback_ffifffff_t )( const float, const float, void *, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffifffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], (void *)data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65788 : + typedef void ( idClass::*eventCallback_iiffffff_t )( void *, void *, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffffff_t )callback )( (void *)data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65789 : + typedef void ( idClass::*eventCallback_fiffffff_t )( const float, void *, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffffff_t )callback )( *( float * )&data[ 0 ], (void *)data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65790 : + typedef void ( idClass::*eventCallback_ifffffff_t )( void *, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffffff_t )callback )( (void *)data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65791 : + typedef void ( idClass::*eventCallback_ffffffff_t )( const float, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + diff --git a/neo/d3xp/gamesys/Class.cpp b/neo/d3xp/gamesys/Class.cpp new file mode 100644 index 00000000..d8979297 --- /dev/null +++ b/neo/d3xp/gamesys/Class.cpp @@ -0,0 +1,1025 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* + +Base class for all C++ objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#include "TypeInfo.h" + + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +// this is the head of a singly linked list of all the idTypes +static idTypeInfo *typelist = NULL; +static idHierarchy classHierarchy; +static int eventCallbackMemory = 0; + +/* +================ +idTypeInfo::idClassType() + +Constructor for class. Should only be called from CLASS_DECLARATION macro. +Handles linking class definition into class hierarchy. This should only happen +at startup as idTypeInfos are statically defined. Since static variables can be +initialized in any order, the constructor must handle the case that subclasses +are initialized before superclasses. +================ +*/ +idTypeInfo::idTypeInfo( const char *classname, const char *superclass, idEventFunc *eventCallbacks, idClass *( *CreateInstance )(), + void ( idClass::*Spawn )(), void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ) { + + idTypeInfo *type; + idTypeInfo **insert; + + this->classname = classname; + this->superclass = superclass; + this->eventCallbacks = eventCallbacks; + this->eventMap = NULL; + this->Spawn = Spawn; + this->Save = Save; + this->Restore = Restore; + this->CreateInstance = CreateInstance; + this->super = idClass::GetClass( superclass ); + this->freeEventMap = false; + typeNum = 0; + lastChild = 0; + + // Check if any subclasses were initialized before their superclass + for( type = typelist; type != NULL; type = type->next ) { + if ( ( type->super == NULL ) && !idStr::Cmp( type->superclass, this->classname ) && + idStr::Cmp( type->classname, "idClass" ) ) { + type->super = this; + } + } + + // Insert sorted + for ( insert = &typelist; *insert; insert = &(*insert)->next ) { + assert( idStr::Cmp( classname, (*insert)->classname ) ); + if ( idStr::Cmp( classname, (*insert)->classname ) < 0 ) { + next = *insert; + *insert = this; + break; + } + } + if ( !*insert ) { + *insert = this; + next = NULL; + } +} + +/* +================ +idTypeInfo::~idTypeInfo +================ +*/ +idTypeInfo::~idTypeInfo() { + Shutdown(); +} + +/* +================ +idTypeInfo::Init + +Initializes the event callback table for the class. Creates a +table for fast lookups of event functions. Should only be called once. +================ +*/ +void idTypeInfo::Init() { + idTypeInfo *c; + idEventFunc *def; + int ev; + int i; + bool *set; + int num; + + if ( eventMap ) { + // we've already been initialized by a subclass + return; + } + + // make sure our superclass is initialized first + if ( super && !super->eventMap ) { + super->Init(); + } + + // add to our node hierarchy + if ( super ) { + node.ParentTo( super->node ); + } else { + node.ParentTo( classHierarchy ); + } + node.SetOwner( this ); + + // keep track of the number of children below each class + for( c = super; c != NULL; c = c->super ) { + c->lastChild++; + } + + // if we're not adding any new event callbacks, we can just use our superclass's table + if ( ( !eventCallbacks || !eventCallbacks->event ) && super ) { + eventMap = super->eventMap; + return; + } + + // set a flag so we know to delete the eventMap table + freeEventMap = true; + + // Allocate our new table. It has to have as many entries as there + // are events. NOTE: could save some space by keeping track of the maximum + // event that the class responds to and doing range checking. + num = idEventDef::NumEventCommands(); + eventMap = new (TAG_SYSTEM) eventCallback_t[ num ]; + memset( eventMap, 0, sizeof( eventCallback_t ) * num ); + eventCallbackMemory += sizeof( eventCallback_t ) * num; + + // allocate temporary memory for flags so that the subclass's event callbacks + // override the superclass's event callback + set = new (TAG_SYSTEM) bool[ num ]; + memset( set, 0, sizeof( bool ) * num ); + + // go through the inheritence order and copies the event callback function into + // a list indexed by the event number. This allows fast lookups of + // event functions. + for( c = this; c != NULL; c = c->super ) { + def = c->eventCallbacks; + if ( !def ) { + continue; + } + + // go through each entry until we hit the NULL terminator + for( i = 0; def[ i ].event != NULL; i++ ) { + ev = def[ i ].event->GetEventNum(); + + if ( set[ ev ] ) { + continue; + } + set[ ev ] = true; + eventMap[ ev ] = def[ i ].function; + } + } + + delete[] set; +} + +/* +================ +idTypeInfo::Shutdown + +Should only be called when DLL or EXE is being shutdown. +Although it cleans up any allocated memory, it doesn't bother to remove itself +from the class list since the program is shutting down. +================ +*/ +void idTypeInfo::Shutdown() { + // free up the memory used for event lookups + if ( eventMap ) { + if ( freeEventMap ) { + delete[] eventMap; + } + eventMap = NULL; + } + typeNum = 0; + lastChild = 0; +} + + +/*********************************************************************** + + idClass + +***********************************************************************/ + +const idEventDef EV_Remove( "", NULL ); +const idEventDef EV_SafeRemove( "remove", NULL ); + +ABSTRACT_DECLARATION( NULL, idClass ) + EVENT( EV_Remove, idClass::Event_Remove ) + EVENT( EV_SafeRemove, idClass::Event_SafeRemove ) +END_CLASS + +// alphabetical order +idList idClass::types; +// typenum order +idList idClass::typenums; + +bool idClass::initialized = false; +int idClass::typeNumBits = 0; +int idClass::memused = 0; +int idClass::numobjects = 0; + +/* +================ +idClass::CallSpawn +================ +*/ +void idClass::CallSpawn() { + idTypeInfo *type; + + type = GetType(); + CallSpawnFunc( type ); +} + +/* +================ +idClass::CallSpawnFunc +================ +*/ +classSpawnFunc_t idClass::CallSpawnFunc( idTypeInfo *cls ) { + classSpawnFunc_t func; + + if ( cls->super ) { + func = CallSpawnFunc( cls->super ); + if ( func == cls->Spawn ) { + // don't call the same function twice in a row. + // this can happen when subclasses don't have their own spawn function. + return func; + } + } + + ( this->*cls->Spawn )(); + + return cls->Spawn; +} + +/* +================ +idClass::FindUninitializedMemory +================ +*/ +void idClass::FindUninitializedMemory() { +#ifdef ID_DEBUG_UNINITIALIZED_MEMORY + unsigned long *ptr = ( ( unsigned long * )this ) - 1; + int size = *ptr; + assert( ( size & 3 ) == 0 ); + size >>= 2; + for ( int i = 0; i < size; i++ ) { + if ( ptr[i] == 0xcdcdcdcd ) { + const char *varName = GetTypeVariableName( GetClassname(), i << 2 ); + gameLocal.Warning( "type '%s' has uninitialized variable %s (offset %d)", GetClassname(), varName, i << 2 ); + } + } +#endif +} + +/* +================ +idClass::Spawn +================ +*/ +void idClass::Spawn() { +} + +/* +================ +idClass::~idClass + +Destructor for object. Cancels any events that depend on this object. +================ +*/ +idClass::~idClass() { + idEvent::CancelEvents( this ); +} + +/* +================ +idClass::DisplayInfo_f +================ +*/ +void idClass::DisplayInfo_f( const idCmdArgs &args ) { + gameLocal.Printf( "Class memory status: %i bytes allocated in %i objects\n", memused, numobjects ); +} + +/* +================ +idClass::ListClasses_f +================ +*/ +void idClass::ListClasses_f( const idCmdArgs &args ) { + int i; + idTypeInfo *type; + + gameLocal.Printf( "%-24s %-24s %-6s %-6s\n", "Classname", "Superclass", "Type", "Subclasses" ); + gameLocal.Printf( "----------------------------------------------------------------------\n" ); + + for( i = 0; i < types.Num(); i++ ) { + type = types[ i ]; + gameLocal.Printf( "%-24s %-24s %6d %6d\n", type->classname, type->superclass, type->typeNum, type->lastChild - type->typeNum ); + } + + gameLocal.Printf( "...%d classes", types.Num() ); +} + +/* +================ +idClass::CreateInstance +================ +*/ +idClass *idClass::CreateInstance( const char *name ) { + const idTypeInfo *type; + idClass *obj; + + type = idClass::GetClass( name ); + if ( !type ) { + return NULL; + } + + obj = type->CreateInstance(); + return obj; +} + +/* +================ +idClass::Init + +Should be called after all idTypeInfos are initialized, so must be called +manually upon game code initialization. Tells all the idTypeInfos to initialize +their event callback table for the associated class. This should only be called +once during the execution of the program or DLL. +================ +*/ +void idClass::Init() { + idTypeInfo *c; + int num; + + gameLocal.Printf( "Initializing class hierarchy\n" ); + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + return; + } + + // init the event callback tables for all the classes + for( c = typelist; c != NULL; c = c->next ) { + c->Init(); + } + + // number the types according to the class hierarchy so we can quickly determine if a class + // is a subclass of another + num = 0; + for( c = classHierarchy.GetNext(); c != NULL; c = c->node.GetNext(), num++ ) { + c->typeNum = num; + c->lastChild += num; + } + + // number of bits needed to send types over network + typeNumBits = idMath::BitsForInteger( num ); + + // create a list of the types so we can do quick lookups + // one list in alphabetical order, one in typenum order + types.SetGranularity( 1 ); + types.SetNum( num ); + typenums.SetGranularity( 1 ); + typenums.SetNum( num ); + num = 0; + for( c = typelist; c != NULL; c = c->next, num++ ) { + types[ num ] = c; + typenums[ c->typeNum ] = c; + } + + initialized = true; + + gameLocal.Printf( "...%i classes, %i bytes for event callbacks\n", types.Num(), eventCallbackMemory ); +} + +/* +================ +idClass::Shutdown +================ +*/ +void idClass::Shutdown() { + idTypeInfo *c; + + for( c = typelist; c != NULL; c = c->next ) { + c->Shutdown(); + } + types.Clear(); + typenums.Clear(); + + initialized = false; +} + +/* +================ +idClass::new +================ +*/ +void * idClass::operator new( size_t s ) { + int *p; + + s += sizeof( int ); + p = (int *)Mem_Alloc( s, TAG_IDCLASS ); + *p = s; + memused += s; + numobjects++; + + return p + 1; +} + +/* +================ +idClass::delete +================ +*/ +void idClass::operator delete( void *ptr ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +/* +================ +idClass::GetClass + +Returns the idTypeInfo for the name of the class passed in. This is a static function +so it must be called as idClass::GetClass( classname ) +================ +*/ +idTypeInfo *idClass::GetClass( const char *name ) { + idTypeInfo *c; + int order; + int mid; + int min; + int max; + + if ( !initialized ) { + // idClass::Init hasn't been called yet, so do a slow lookup + for( c = typelist; c != NULL; c = c->next ) { + if ( !idStr::Cmp( c->classname, name ) ) { + return c; + } + } + } else { + // do a binary search through the list of types + min = 0; + max = types.Num() - 1; + while( min <= max ) { + mid = ( min + max ) / 2; + c = types[ mid ]; + order = idStr::Cmp( c->classname, name ); + if ( !order ) { + return c; + } else if ( order > 0 ) { + max = mid - 1; + } else { + min = mid + 1; + } + } + } + + return NULL; +} + +/* +================ +idClass::GetType +================ +*/ +idTypeInfo *idClass::GetType( const int typeNum ) { + idTypeInfo *c; + + if ( !initialized ) { + for( c = typelist; c != NULL; c = c->next ) { + if ( c->typeNum == typeNum ) { + return c; + } + } + } else if ( ( typeNum >= 0 ) && ( typeNum < types.Num() ) ) { + return typenums[ typeNum ]; + } + + return NULL; +} + +/* +================ +idClass::GetClassname + +Returns the text classname of the object. +================ +*/ +const char *idClass::GetClassname() const { + idTypeInfo *type; + + type = GetType(); + return type->classname; +} + +/* +================ +idClass::GetSuperclass + +Returns the text classname of the superclass. +================ +*/ +const char *idClass::GetSuperclass() const { + idTypeInfo *cls; + + cls = GetType(); + return cls->superclass; +} + +/* +================ +idClass::CancelEvents +================ +*/ +void idClass::CancelEvents( const idEventDef *ev ) { + idEvent::CancelEvents( this, ev ); +} + +/* +================ +idClass::PostEventArgs +================ +*/ +bool idClass::PostEventArgs( const idEventDef *ev, int time, int numargs, ... ) { + idTypeInfo *c; + idEvent *event; + va_list args; + + assert( ev ); + + if ( !idEvent::initialized ) { + return false; + } + + c = GetType(); + if ( !c->eventMap[ ev->GetEventNum() ] ) { + // we don't respond to this event, so ignore it + return false; + } + + bool isReplicated = true; + // If this is an entity with skipReplication, we want to process the event normally even on clients. + if ( IsType( idEntity::Type ) ) { + idEntity * thisEnt = static_cast< idEntity * >( this ); + if ( thisEnt->fl.skipReplication ) { + isReplicated = false; + } + } + + // we service events on the client to avoid any bad code filling up the event pool + // we don't want them processed usually, unless when the map is (re)loading. + // we allow threads to run fine, though. + if ( common->IsClient() && isReplicated && ( gameLocal.GameState() != GAMESTATE_STARTUP ) && !IsType( idThread::Type ) ) { + return true; + } + + va_start( args, numargs ); + event = idEvent::Alloc( ev, numargs, args ); + va_end( args ); + + event->Schedule( this, c, time ); + + return true; +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time ) { + return PostEventArgs( ev, time, 0 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ) { + return PostEventArgs( ev, time, 1, &arg1 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, time, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, time, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, time, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, time, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, time, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, time, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, time, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time ) { + return PostEventArgs( ev, SEC2MS( time ), 0 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ) { + return PostEventArgs( ev, SEC2MS( time ), 1, &arg1 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, SEC2MS( time ), 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, SEC2MS( time ), 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, SEC2MS( time ), 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, SEC2MS( time ), 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, SEC2MS( time ), 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, SEC2MS( time ), 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, SEC2MS( time ), 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgs +================ +*/ +bool idClass::ProcessEventArgs( const idEventDef *ev, int numargs, ... ) { + idTypeInfo *c; + int num; + int data[ D_EVENT_MAXARGS ]; + va_list args; + + assert( ev ); + assert( idEvent::initialized ); + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + va_start( args, numargs ); + idEvent::CopyArgs( ev, numargs, args, data ); + va_end( args ); + + ProcessEventArgPtr( ev, data ); + + return true; +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev ) { + return ProcessEventArgs( ev, 0 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1 ) { + return ProcessEventArgs( ev, 1, &arg1 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ) { + return ProcessEventArgs( ev, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return ProcessEventArgs( ev, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return ProcessEventArgs( ev, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return ProcessEventArgs( ev, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return ProcessEventArgs( ev, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return ProcessEventArgs( ev, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return ProcessEventArgs( ev, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgPtr +================ +*/ +bool idClass::ProcessEventArgPtr( const idEventDef *ev, int *data ) { + idTypeInfo *c; + int num; + eventCallback_t callback; + + assert( ev ); + assert( idEvent::initialized ); + + SetTimeState ts; + + if ( IsType( idEntity::Type ) ) { + idEntity *ent = (idEntity*)this; + ts.PushState( ent->timeGroup ); + } + + if ( g_debugTriggers.GetBool() && ( ev == &EV_Activate ) && IsType( idEntity::Type ) ) { + const idEntity *ent = *reinterpret_cast( data ); + gameLocal.Printf( "%d: '%s' activated by '%s'\n", gameLocal.framenum, static_cast( this )->GetName(), ent ? ent->GetName() : "NULL" ); + } + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + callback = c->eventMap[ num ]; + +#if !CPU_EASYARGS + +/* +on ppc architecture, floats are passed in a seperate set of registers +the function prototypes must have matching float declaration + +http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/2rt_powerpc_abi/chapter_9_section_5.html +*/ + + switch( ev->GetFormatspecIndex() ) { + case 1 << D_EVENT_MAXARGS : + ( this->*callback )(); + break; + +// generated file - see CREATE_EVENT_CODE +#include "Callbacks.cpp" + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#else + + assert( D_EVENT_MAXARGS == 8 ); + + switch( ev->GetNumArgs() ) { + case 0 : + ( this->*callback )(); + break; + + case 1 : + typedef void ( idClass::*eventCallback_1_t )( const int ); + ( this->*( eventCallback_1_t )callback )( data[ 0 ] ); + break; + + case 2 : + typedef void ( idClass::*eventCallback_2_t )( const int, const int ); + ( this->*( eventCallback_2_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 3 : + typedef void ( idClass::*eventCallback_3_t )( const int, const int, const int ); + ( this->*( eventCallback_3_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 4 : + typedef void ( idClass::*eventCallback_4_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_4_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 5 : + typedef void ( idClass::*eventCallback_5_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_5_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 6 : + typedef void ( idClass::*eventCallback_6_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_6_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 7 : + typedef void ( idClass::*eventCallback_7_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_7_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 8 : + typedef void ( idClass::*eventCallback_8_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_8_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#endif + + return true; +} + +/* +================ +idClass::Event_Remove +================ +*/ +void idClass::Event_Remove() { + delete this; +} + +/* +================ +idClass::Event_SafeRemove +================ +*/ +void idClass::Event_SafeRemove() { + // Forces the remove to be done at a safe time + PostEventMS( &EV_Remove, 0 ); +} diff --git a/neo/d3xp/gamesys/Class.h b/neo/d3xp/gamesys/Class.h new file mode 100644 index 00000000..2f830bca --- /dev/null +++ b/neo/d3xp/gamesys/Class.h @@ -0,0 +1,349 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* + +Base class for all game objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#ifndef __SYS_CLASS_H__ +#define __SYS_CLASS_H__ + +class idClass; +class idTypeInfo; + +extern const idEventDef EV_Remove; +extern const idEventDef EV_SafeRemove; + +typedef void ( idClass::*eventCallback_t )(); + +template< class Type > +struct idEventFunc { + const idEventDef *event; + eventCallback_t function; +}; + +// added & so gcc could compile this +#define EVENT( event, function ) { &( event ), ( void ( idClass::* )() )( &function ) }, +#define END_CLASS { NULL, NULL } }; + + +class idEventArg { +public: + int type; + int value; + + idEventArg() { type = D_EVENT_INTEGER; value = 0; }; + idEventArg( int data ) { type = D_EVENT_INTEGER; value = data; }; + idEventArg( float data ) { type = D_EVENT_FLOAT; value = *reinterpret_cast( &data ); }; + idEventArg( idVec3 &data ) { type = D_EVENT_VECTOR; value = reinterpret_cast( &data ); }; + idEventArg( const idStr &data ) { type = D_EVENT_STRING; value = reinterpret_cast( data.c_str() ); }; + idEventArg( const char *data ) { type = D_EVENT_STRING; value = reinterpret_cast( data ); }; + idEventArg( const class idEntity *data ) { type = D_EVENT_ENTITY; value = reinterpret_cast( data ); }; + idEventArg( const struct trace_s *data ) { type = D_EVENT_TRACE; value = reinterpret_cast( data ); }; +}; + +class idAllocError : public idException { +public: + idAllocError( const char *text = "" ) : idException( text ) {} +}; + +/*********************************************************************** + + idClass + +***********************************************************************/ + +/* +================ +CLASS_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance concrete classes only. +================ +*/ +#define CLASS_PROTOTYPE( nameofclass ) \ +public: \ + static idTypeInfo Type; \ + static idClass *CreateInstance(); \ + virtual idTypeInfo *GetType() const; \ + static idEventFunc eventCallbacks[] + +/* +================ +CLASS_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking and run-time instanciation. It also defines the list +of events that the class responds to. Take special care to ensure that the +proper superclass is indicated or the run-time type information will be +incorrect. Use this on concrete classes only. +================ +*/ +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )() )&nameofclass::Spawn, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + idClass *nameofclass::CreateInstance() { \ + try { \ + nameofclass *ptr = new nameofclass; \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo *nameofclass::GetType() const { \ + return &( nameofclass::Type ); \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { + +/* +================ +ABSTRACT_PROTOTYPE + +This macro must be included in the definition of any abstract subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance abstract classes only. +================ +*/ +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +public: \ + static idTypeInfo Type; \ + static idClass *CreateInstance(); \ + virtual idTypeInfo *GetType() const; \ + static idEventFunc eventCallbacks[] + +/* +================ +ABSTRACT_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking. It also defines the list of events that the class +responds to. Take special care to ensure that the proper superclass is +indicated or the run-time tyep information will be incorrect. Use this +on abstract classes only. +================ +*/ +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )() )&nameofclass::Spawn, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + idClass *nameofclass::CreateInstance() { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo *nameofclass::GetType() const { \ + return &( nameofclass::Type ); \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +typedef void ( idClass::*classSpawnFunc_t )(); + +class idSaveGame; +class idRestoreGame; + +class idClass { +public: + ABSTRACT_PROTOTYPE( idClass ); + + void * operator new( size_t ); + void operator delete( void * ); + + virtual ~idClass(); + + void Spawn(); + void CallSpawn(); + bool IsType( const idTypeInfo &c ) const; + const char * GetClassname() const; + const char * GetSuperclass() const; + void FindUninitializedMemory(); + + void Save( idSaveGame *savefile ) const {}; + void Restore( idRestoreGame *savefile ) {}; + + bool RespondsTo( const idEventDef &ev ) const; + + bool PostEventMS( const idEventDef *ev, int time ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool PostEventSec( const idEventDef *ev, float time ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEvent( const idEventDef *ev ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEventArgPtr( const idEventDef *ev, int *data ); + void CancelEvents( const idEventDef *ev ); + + void Event_Remove(); + + // Static functions + static void Init(); + static void Shutdown(); + static idTypeInfo * GetClass( const char *name ); + static void DisplayInfo_f( const idCmdArgs &args ); + static void ListClasses_f( const idCmdArgs &args ); + static idClass * CreateInstance( const char *name ); + static int GetNumTypes() { return types.Num(); } + static int GetTypeNumBits() { return typeNumBits; } + static idTypeInfo * GetType( int num ); + +private: + classSpawnFunc_t CallSpawnFunc( idTypeInfo *cls ); + + bool PostEventArgs( const idEventDef *ev, int time, int numargs, ... ); + bool ProcessEventArgs( const idEventDef *ev, int numargs, ... ); + + void Event_SafeRemove(); + + static bool initialized; + static idList types; + static idList typenums; + static int typeNumBits; + static int memused; + static int numobjects; +}; + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +class idTypeInfo { +public: + const char * classname; + const char * superclass; + idClass * ( *CreateInstance )(); + void ( idClass::*Spawn )(); + void ( idClass::*Save )( idSaveGame *savefile ) const; + void ( idClass::*Restore )( idRestoreGame *savefile ); + + idEventFunc * eventCallbacks; + eventCallback_t * eventMap; + idTypeInfo * super; + idTypeInfo * next; + bool freeEventMap; + int typeNum; + int lastChild; + + idHierarchy node; + + idTypeInfo( const char *classname, const char *superclass, + idEventFunc *eventCallbacks, idClass *( *CreateInstance )(), void ( idClass::*Spawn )(), + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ); + ~idTypeInfo(); + + void Init(); + void Shutdown(); + + bool IsType( const idTypeInfo &superclass ) const; + bool RespondsTo( const idEventDef &ev ) const; +}; + +/* +================ +idTypeInfo::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idTypeInfo::IsType( const idTypeInfo &type ) const { + return ( ( typeNum >= type.typeNum ) && ( typeNum <= type.lastChild ) ); +} + +/* +================ +idTypeInfo::RespondsTo +================ +*/ +ID_INLINE bool idTypeInfo::RespondsTo( const idEventDef &ev ) const { + assert( idEvent::initialized ); + if ( !eventMap[ ev.GetEventNum() ] ) { + // we don't respond to this event + return false; + } + + return true; +} + +/* +================ +idClass::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idClass::IsType( const idTypeInfo &superclass ) const { + idTypeInfo *subclass; + + subclass = GetType(); + return subclass->IsType( superclass ); +} + +/* +================ +idClass::RespondsTo +================ +*/ +ID_INLINE bool idClass::RespondsTo( const idEventDef &ev ) const { + const idTypeInfo *c; + + assert( idEvent::initialized ); + c = GetType(); + return c->RespondsTo( ev ); +} + +#endif /* !__SYS_CLASS_H__ */ diff --git a/neo/d3xp/gamesys/Event.cpp b/neo/d3xp/gamesys/Event.cpp new file mode 100644 index 00000000..54d6052d --- /dev/null +++ b/neo/d3xp/gamesys/Event.cpp @@ -0,0 +1,1062 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +sys_event.cpp + +Event are used for scheduling tasks and for linking script commands. + +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#define MAX_EVENTSPERFRAME 4096 +//#define CREATE_EVENT_CODE + +/*********************************************************************** + + idEventDef + +***********************************************************************/ + +idEventDef *idEventDef::eventDefList[MAX_EVENTS]; +int idEventDef::numEventDefs = 0; + +static bool eventError = false; +static char eventErrorMsg[ 128 ]; + +/* +================ +idEventDef::idEventDef +================ +*/ +idEventDef::idEventDef( const char *command, const char *formatspec, char returnType ) { + idEventDef *ev; + int i; + unsigned int bits; + + assert( command ); + assert( !idEvent::initialized ); + + // Allow NULL to indicate no args, but always store it as "" + // so we don't have to check for it. + if ( !formatspec ) { + formatspec = ""; + } + + this->name = command; + this->formatspec = formatspec; + this->returnType = returnType; + + numargs = strlen( formatspec ); + assert( numargs <= D_EVENT_MAXARGS ); + if ( numargs > D_EVENT_MAXARGS ) { + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Too many args for '%s' event.", name ); + return; + } + + // make sure the format for the args is valid, calculate the formatspecindex, and the offsets for each arg + bits = 0; + argsize = 0; + memset( argOffset, 0, sizeof( argOffset ) ); + for( i = 0; i < numargs;i++ ) { + argOffset[ i ] = argsize; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + bits |= 1 << i; + argsize += sizeof( float ); + break; + + case D_EVENT_INTEGER : + argsize += sizeof( int ); + break; + + case D_EVENT_VECTOR : + argsize += sizeof( idVec3 ); + break; + + case D_EVENT_STRING : + argsize += MAX_STRING_LEN; + break; + + case D_EVENT_ENTITY : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_ENTITY_NULL : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_TRACE : + argsize += sizeof( trace_t ) + MAX_STRING_LEN + sizeof( bool ); + break; + + default : + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Invalid arg format '%s' string for '%s' event.", formatspec, name ); + return; + break; + } + } + + // calculate the formatspecindex + formatspecIndex = ( 1 << ( numargs + D_EVENT_MAXARGS ) ) | bits; + + // go through the list of defined events and check for duplicates + // and mismatched format strings + eventnum = numEventDefs; + for( i = 0; i < eventnum; i++ ) { + ev = eventDefList[ i ]; + if ( strcmp( command, ev->name ) == 0 ) { + if ( strcmp( formatspec, ev->formatspec ) != 0 ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing format strings ('%s'!='%s').", + command, formatspec, ev->formatspec ); + return; + } + + if ( ev->returnType != returnType ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing return types ('%c'!='%c').", + command, returnType, ev->returnType ); + return; + } + // Don't bother putting the duplicate event in list. + eventnum = ev->eventnum; + return; + } + } + + ev = this; + + if ( numEventDefs >= MAX_EVENTS ) { + eventError = true; + sprintf( eventErrorMsg, "numEventDefs >= MAX_EVENTS" ); + return; + } + eventDefList[numEventDefs] = ev; + numEventDefs++; +} + +/* +================ +idEventDef::NumEventCommands +================ +*/ +int idEventDef::NumEventCommands() { + return numEventDefs; +} + +/* +================ +idEventDef::GetEventCommand +================ +*/ +const idEventDef *idEventDef::GetEventCommand( int eventnum ) { + return eventDefList[ eventnum ]; +} + +/* +================ +idEventDef::FindEvent +================ +*/ +const idEventDef *idEventDef::FindEvent( const char *name ) { + idEventDef *ev; + int num; + int i; + + assert( name ); + + num = numEventDefs; + for( i = 0; i < num; i++ ) { + ev = eventDefList[ i ]; + if ( strcmp( name, ev->name ) == 0 ) { + return ev; + } + } + + return NULL; +} + +/*********************************************************************** + + idEvent + +***********************************************************************/ + +static idLinkList FreeEvents; +static idLinkList EventQueue; +static idLinkList FastEventQueue; +static idEvent EventPool[ MAX_EVENTS ]; + +bool idEvent::initialized = false; + +idDynamicBlockAlloc idEvent::eventDataAllocator; + +/* +================ +idEvent::~idEvent() +================ +*/ +idEvent::~idEvent() { + Free(); +} + +/* +================ +idEvent::Alloc +================ +*/ +idEvent *idEvent::Alloc( const idEventDef *evdef, int numargs, va_list args ) { + idEvent *ev; + size_t size; + const char *format; + idEventArg *arg; + byte *dataPtr; + int i; + const char *materialName; + + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Alloc : No more free events" ); + } + + ev = FreeEvents.Next(); + ev->eventNode.Remove(); + + ev->eventdef = evdef; + + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::Alloc : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + size = evdef->GetArgSize(); + if ( size ) { + ev->data = eventDataAllocator.Alloc( size ); + memset( ev->data, 0, size ); + } else { + ev->data = NULL; + return ev; + } + + format = evdef->GetArgFormat(); + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { + // when NULL is passed in for an entity, it gets cast as an integer 0, so don't give an error when it happens + if ( !( ( ( format[ i ] == D_EVENT_TRACE ) || ( format[ i ] == D_EVENT_ENTITY ) ) && ( arg->type == 'd' ) && ( arg->value == 0 ) ) ) { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } + + dataPtr = &ev->data[ evdef->GetArgOffset( i ) ]; + + switch( format[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + *reinterpret_cast( dataPtr ) = arg->value; + break; + + case D_EVENT_VECTOR : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = *reinterpret_cast( arg->value ); + } + break; + + case D_EVENT_STRING : + if ( arg->value ) { + idStr::Copynz( reinterpret_cast( dataPtr ), reinterpret_cast( arg->value ), MAX_STRING_LEN ); + } + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; + + case D_EVENT_TRACE : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = true; + *reinterpret_cast( dataPtr + sizeof( bool ) ) = *reinterpret_cast( arg->value ); + + // save off the material as a string since the pointer won't be valid in save games. + // since we save off the entire trace_t structure, if the material is NULL here, + // it will be NULL when we process it, so we don't need to save off anything in that case. + if ( reinterpret_cast( arg->value )->c.material ) { + materialName = reinterpret_cast( arg->value )->c.material->GetName(); + idStr::Copynz( reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ), materialName, MAX_STRING_LEN ); + } + } else { + *reinterpret_cast( dataPtr ) = false; + } + break; + + default : + gameLocal.Error( "idEvent::Alloc : Invalid arg format '%s' string for '%s' event.", format, evdef->GetName() ); + break; + } + } + + return ev; +} + +/* +================ +idEvent::CopyArgs +================ +*/ +void idEvent::CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ) { + int i; + const char *format; + idEventArg *arg; + + format = evdef->GetArgFormat(); + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::CopyArgs : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { + // when NULL is passed in for an entity, it gets cast as an integer 0, so don't give an error when it happens + if ( !( ( ( format[ i ] == D_EVENT_TRACE ) || ( format[ i ] == D_EVENT_ENTITY ) ) && ( arg->type == 'd' ) && ( arg->value == 0 ) ) ) { + gameLocal.Error( "idEvent::CopyArgs : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } + + data[ i ] = arg->value; + } +} + +/* +================ +idEvent::Free +================ +*/ +void idEvent::Free() { + if ( data ) { + eventDataAllocator.Free( data ); + data = NULL; + } + + eventdef = NULL; + time = 0; + object = NULL; + typeinfo = NULL; + + eventNode.SetOwner( this ); + eventNode.AddToEnd( FreeEvents ); +} + +/* +================ +idEvent::Schedule +================ +*/ +void idEvent::Schedule( idClass *obj, const idTypeInfo *type, int time ) { + idEvent *event; + + assert( initialized ); + if ( !initialized ) { + return; + } + + object = obj; + typeinfo = type; + + // wraps after 24 days...like I care. ;) + this->time = gameLocal.time + time; + + eventNode.Remove(); + + if ( obj->IsType( idEntity::Type ) && ( ( (idEntity*)(obj) )->timeGroup == TIME_GROUP2 ) ) { + event = FastEventQueue.Next(); + while( ( event != NULL ) && ( this->time >= event->time ) ) { + event = event->eventNode.Next(); + } + + if ( event ) { + eventNode.InsertBefore( event->eventNode ); + } else { + eventNode.AddToEnd( FastEventQueue ); + } + + return; + } else { + this->time = gameLocal.slow.time + time; + } + + event = EventQueue.Next(); + while( ( event != NULL ) && ( this->time >= event->time ) ) { + event = event->eventNode.Next(); + } + + if ( event ) { + eventNode.InsertBefore( event->eventNode ); + } else { + eventNode.AddToEnd( EventQueue ); + } +} + +/* +================ +idEvent::CancelEvents +================ +*/ +void idEvent::CancelEvents( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if ( event->object == obj ) { + if ( !evdef || ( evdef == event->eventdef ) ) { + event->Free(); + } + } + } + + for( event = FastEventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if ( event->object == obj ) { + if ( !evdef || ( evdef == event->eventdef ) ) { + event->Free(); + } + } + } +} + +/* +================ +idEvent::ClearEventList +================ +*/ +void idEvent::ClearEventList() { + int i; + + // + // initialize lists + // + FreeEvents.Clear(); + EventQueue.Clear(); + + // + // add the events to the free list + // + for( i = 0; i < MAX_EVENTS; i++ ) { + EventPool[ i ].Free(); + } +} + +/* +================ +idEvent::ServiceEvents +================ +*/ +void idEvent::ServiceEvents() { + idEvent *event; + int num; + int args[ D_EVENT_MAXARGS ]; + int offset; + int i; + int numargs; + const char *formatspec; + trace_t **tracePtr; + const idEventDef *ev; + byte *data; + const char *materialName; + + num = 0; + while( !EventQueue.IsListEmpty() ) { + event = EventQueue.Next(); + assert( event ); + + if ( event->time > gameLocal.time ) { + break; + } + + common->UpdateLevelLoadPacifier(); + + // copy the data into the local args array and set up pointers + ev = event->eventdef; + formatspec = ev->GetArgFormat(); + numargs = ev->GetNumArgs(); + for( i = 0; i < numargs; i++ ) { + offset = ev->GetArgOffset( i ); + data = event->data; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + args[ i ] = *reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_VECTOR : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_STRING : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + break; + + case D_EVENT_TRACE : + tracePtr = reinterpret_cast( &args[ i ] ); + if ( *reinterpret_cast( &data[ offset ] ) ) { + *tracePtr = reinterpret_cast( &data[ offset + sizeof( bool ) ] ); + + if ( ( *tracePtr )->c.material != NULL ) { + // look up the material name to get the material pointer + materialName = reinterpret_cast( &data[ offset + sizeof( bool ) + sizeof( trace_t ) ] ); + ( *tracePtr )->c.material = declManager->FindMaterial( materialName, true ); + } + } else { + *tracePtr = NULL; + } + break; + + default: + gameLocal.Error( "idEvent::ServiceEvents : Invalid arg format '%s' string for '%s' event.", formatspec, ev->GetName() ); + } + } + + // the event is removed from its list so that if then object + // is deleted, the event won't be freed twice + event->eventNode.Remove(); + assert( event->object ); + event->object->ProcessEventArgPtr( ev, args ); + +#if 0 + // event functions may never leave return values on the FPU stack + // enable this code to check if any event call left values on the FPU stack + if ( !sys->FPU_StackIsEmpty() ) { + gameLocal.Error( "idEvent::ServiceEvents %d: %s left a value on the FPU stack\n", num, ev->GetName() ); + } +#endif + + // return the event to the free list + event->Free(); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > MAX_EVENTSPERFRAME ) { + gameLocal.Error( "Event overflow. Possible infinite loop in script." ); + } + } +} + +/* +================ +idEvent::ServiceFastEvents +================ +*/ +void idEvent::ServiceFastEvents() { + idEvent *event; + int num; + int args[ D_EVENT_MAXARGS ]; + int offset; + int i; + int numargs; + const char *formatspec; + trace_t **tracePtr; + const idEventDef *ev; + byte *data; + const char *materialName; + + num = 0; + while( !FastEventQueue.IsListEmpty() ) { + event = FastEventQueue.Next(); + assert( event ); + + if ( event->time > gameLocal.fast.time ) { + break; + } + + // copy the data into the local args array and set up pointers + ev = event->eventdef; + formatspec = ev->GetArgFormat(); + numargs = ev->GetNumArgs(); + for( i = 0; i < numargs; i++ ) { + offset = ev->GetArgOffset( i ); + data = event->data; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + args[ i ] = *reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_VECTOR : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_STRING : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + break; + + case D_EVENT_TRACE : + tracePtr = reinterpret_cast( &args[ i ] ); + if ( *reinterpret_cast( &data[ offset ] ) ) { + *tracePtr = reinterpret_cast( &data[ offset + sizeof( bool ) ] ); + + if ( ( *tracePtr )->c.material != NULL ) { + // look up the material name to get the material pointer + materialName = reinterpret_cast( &data[ offset + sizeof( bool ) + sizeof( trace_t ) ] ); + ( *tracePtr )->c.material = declManager->FindMaterial( materialName, true ); + } + } else { + *tracePtr = NULL; + } + break; + + default: + gameLocal.Error( "idEvent::ServiceFastEvents : Invalid arg format '%s' string for '%s' event.", formatspec, ev->GetName() ); + } + } + + // the event is removed from its list so that if then object + // is deleted, the event won't be freed twice + event->eventNode.Remove(); + assert( event->object ); + event->object->ProcessEventArgPtr( ev, args ); + +#if 0 + // event functions may never leave return values on the FPU stack + // enable this code to check if any event call left values on the FPU stack + if ( !sys->FPU_StackIsEmpty() ) { + gameLocal.Error( "idEvent::ServiceEvents %d: %s left a value on the FPU stack\n", num, event->eventdef->GetName() ); + } +#endif + + // return the event to the free list + event->Free(); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > MAX_EVENTSPERFRAME ) { + gameLocal.Error( "Event overflow. Possible infinite loop in script." ); + } + } +} + +/* +================ +idEvent::Init +================ +*/ +void idEvent::Init() { + gameLocal.Printf( "Initializing event system\n" ); + + if ( eventError ) { + gameLocal.Error( "%s", eventErrorMsg ); + } + +#ifdef CREATE_EVENT_CODE + void CreateEventCallbackHandler(); + CreateEventCallbackHandler(); + gameLocal.Error( "Wrote event callback handler" ); +#endif + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + ClearEventList(); + return; + } + + ClearEventList(); + + eventDataAllocator.Init(); + + gameLocal.Printf( "...%i event definitions\n", idEventDef::NumEventCommands() ); + + // the event system has started + initialized = true; +} + +/* +================ +idEvent::Shutdown +================ +*/ +void idEvent::Shutdown() { + gameLocal.Printf( "Shutdown event system\n" ); + + if ( !initialized ) { + gameLocal.Printf( "...not started\n" ); + return; + } + + ClearEventList(); + + eventDataAllocator.Shutdown(); + + // say it is now shutdown + initialized = false; +} + +/* +================ +idEvent::Save +================ +*/ +void idEvent::Save( idSaveGame *savefile ) { + char *str; + int i, size; + idEvent *event; + byte *dataPtr; + bool validTrace; + const char *format; + + savefile->WriteInt( EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + savefile->WriteInt( event->time ); + savefile->WriteString( event->eventdef->GetName() ); + savefile->WriteString( event->typeinfo->classname ); + savefile->WriteObject( event->object ); + savefile->WriteInt( event->eventdef->GetArgSize() ); + format = event->eventdef->GetArgFormat(); + for ( i = 0, size = 0; i < event->eventdef->GetNumArgs(); ++i) { + dataPtr = &event->data[ event->eventdef->GetArgOffset( i ) ]; + switch( format[ i ] ) { + case D_EVENT_FLOAT : + savefile->WriteFloat( *reinterpret_cast( dataPtr ) ); + size += sizeof( float ); + break; + case D_EVENT_INTEGER : + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + savefile->WriteInt( *reinterpret_cast( dataPtr ) ); + size += sizeof( int ); + break; + case D_EVENT_VECTOR : + savefile->WriteVec3( *reinterpret_cast( dataPtr ) ); + size += sizeof( idVec3 ); + break; + case D_EVENT_TRACE : + validTrace = *reinterpret_cast( dataPtr ); + savefile->WriteBool( validTrace ); + size += sizeof( bool ); + if ( validTrace ) { + size += sizeof( trace_t ); + const trace_t &t = *reinterpret_cast( dataPtr + sizeof( bool ) ); + SaveTrace( savefile, t ); + if ( t.c.material ) { + size += MAX_STRING_LEN; + str = reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ); + savefile->Write( str, MAX_STRING_LEN ); + } + } + break; + default: + break; + } + } + assert( size == (int)event->eventdef->GetArgSize() ); + event = event->eventNode.Next(); + } + + // Save the Fast EventQueue + savefile->WriteInt( FastEventQueue.Num() ); + + event = FastEventQueue.Next(); + while( event != NULL ) { + savefile->WriteInt( event->time ); + savefile->WriteString( event->eventdef->GetName() ); + savefile->WriteString( event->typeinfo->classname ); + savefile->WriteObject( event->object ); + savefile->WriteInt( event->eventdef->GetArgSize() ); + savefile->Write( event->data, event->eventdef->GetArgSize() ); + + event = event->eventNode.Next(); + } +} + +/* +================ +idEvent::Restore +================ +*/ +void idEvent::Restore( idRestoreGame *savefile ) { + char *str; + int num, argsize, i, j, size; + idStr name; + byte *dataPtr; + idEvent *event; + const char *format; + + savefile->ReadInt( num ); + + for ( i = 0; i < num; i++ ) { + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Restore : No more free events" ); + } + + event = FreeEvents.Next(); + event->eventNode.Remove(); + event->eventNode.AddToEnd( EventQueue ); + + savefile->ReadInt( event->time ); + + // read the event name + savefile->ReadString( name ); + event->eventdef = idEventDef::FindEvent( name ); + if ( event->eventdef == NULL ) { + savefile->Error( "idEvent::Restore: unknown event '%s'", name.c_str() ); + return; + } + + // read the classtype + savefile->ReadString( name ); + event->typeinfo = idClass::GetClass( name ); + if ( event->typeinfo == NULL ) { + savefile->Error( "idEvent::Restore: unknown class '%s' on event '%s'", name.c_str(), event->eventdef->GetName() ); + return; + } + + savefile->ReadObject( event->object ); + + // read the args + savefile->ReadInt( argsize ); + if ( argsize != (int)event->eventdef->GetArgSize() ) { + savefile->Error( "idEvent::Restore: arg size (%d) doesn't match saved arg size(%d) on event '%s'", event->eventdef->GetArgSize(), argsize, event->eventdef->GetName() ); + } + if ( argsize ) { + event->data = eventDataAllocator.Alloc( argsize ); + format = event->eventdef->GetArgFormat(); + assert( format ); + for ( j = 0, size = 0; j < event->eventdef->GetNumArgs(); ++j) { + dataPtr = &event->data[ event->eventdef->GetArgOffset( j ) ]; + switch( format[ j ] ) { + case D_EVENT_FLOAT : + savefile->ReadFloat( *reinterpret_cast( dataPtr ) ); + size += sizeof( float ); + break; + case D_EVENT_INTEGER : + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + savefile->ReadInt( *reinterpret_cast( dataPtr ) ); + size += sizeof( int ); + break; + case D_EVENT_VECTOR : + savefile->ReadVec3( *reinterpret_cast( dataPtr ) ); + size += sizeof( idVec3 ); + break; + case D_EVENT_TRACE : + savefile->ReadBool( *reinterpret_cast( dataPtr ) ); + size += sizeof( bool ); + if ( *reinterpret_cast( dataPtr ) ) { + size += sizeof( trace_t ); + trace_t &t = *reinterpret_cast( dataPtr + sizeof( bool ) ); + RestoreTrace( savefile, t) ; + if ( t.c.material ) { + size += MAX_STRING_LEN; + str = reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ); + savefile->Read( str, MAX_STRING_LEN ); + } + } + break; + default: + break; + } + } + assert( size == (int)event->eventdef->GetArgSize() ); + } else { + event->data = NULL; + } + } + + // Restore the Fast EventQueue + savefile->ReadInt( num ); + + for ( i = 0; i < num; i++ ) { + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Restore : No more free events" ); + } + + event = FreeEvents.Next(); + event->eventNode.Remove(); + event->eventNode.AddToEnd( FastEventQueue ); + + savefile->ReadInt( event->time ); + + // read the event name + savefile->ReadString( name ); + event->eventdef = idEventDef::FindEvent( name ); + if ( event->eventdef == NULL ) { + savefile->Error( "idEvent::Restore: unknown event '%s'", name.c_str() ); + return; + } + + // read the classtype + savefile->ReadString( name ); + event->typeinfo = idClass::GetClass( name ); + if ( event->typeinfo == NULL ) { + savefile->Error( "idEvent::Restore: unknown class '%s' on event '%s'", name.c_str(), event->eventdef->GetName() ); + return; + } + + savefile->ReadObject( event->object ); + + // read the args + savefile->ReadInt( argsize ); + if ( argsize != (int)event->eventdef->GetArgSize() ) { + savefile->Error( "idEvent::Restore: arg size (%d) doesn't match saved arg size(%d) on event '%s'", event->eventdef->GetArgSize(), argsize, event->eventdef->GetName() ); + } + if ( argsize ) { + event->data = eventDataAllocator.Alloc( argsize ); + savefile->Read( event->data, argsize ); + } else { + event->data = NULL; + } + } +} + +/* + ================ + idEvent::ReadTrace + + idRestoreGame has a ReadTrace procedure, but unfortunately idEvent wants the material + string name at the of the data structure rather than in the middle + ================ + */ +void idEvent::RestoreTrace( idRestoreGame *savefile, trace_t &trace ) { + savefile->ReadFloat( trace.fraction ); + savefile->ReadVec3( trace.endpos ); + savefile->ReadMat3( trace.endAxis ); + savefile->ReadInt( (int&)trace.c.type ); + savefile->ReadVec3( trace.c.point ); + savefile->ReadVec3( trace.c.normal ); + savefile->ReadFloat( trace.c.dist ); + savefile->ReadInt( trace.c.contents ); + savefile->ReadInt( (int&)trace.c.material ); + savefile->ReadInt( trace.c.contents ); + savefile->ReadInt( trace.c.modelFeature ); + savefile->ReadInt( trace.c.trmFeature ); + savefile->ReadInt( trace.c.id ); +} + +/* + ================ + idEvent::WriteTrace + + idSaveGame has a WriteTrace procedure, but unfortunately idEvent wants the material + string name at the of the data structure rather than in the middle +================ + */ +void idEvent::SaveTrace( idSaveGame *savefile, const trace_t &trace ) { + savefile->WriteFloat( trace.fraction ); + savefile->WriteVec3( trace.endpos ); + savefile->WriteMat3( trace.endAxis ); + savefile->WriteInt( trace.c.type ); + savefile->WriteVec3( trace.c.point ); + savefile->WriteVec3( trace.c.normal ); + savefile->WriteFloat( trace.c.dist ); + savefile->WriteInt( trace.c.contents ); + savefile->WriteInt( (int&)trace.c.material ); + savefile->WriteInt( trace.c.contents ); + savefile->WriteInt( trace.c.modelFeature ); + savefile->WriteInt( trace.c.trmFeature ); + savefile->WriteInt( trace.c.id ); +} + + + +#ifdef CREATE_EVENT_CODE +/* +================ +CreateEventCallbackHandler +================ +*/ +void CreateEventCallbackHandler() { + int i, j, k; + char argString[ D_EVENT_MAXARGS + 1 ]; + idStr string1; + idStr string2; + idFile *file; + + file = fileSystem->OpenFileWrite( "Callbacks.cpp" ); + + file->Printf( "/*\n================================================================================================\nCONFIDENTIAL AND PROPRIETARY INFORMATION/NOT FOR DISCLOSURE WITHOUT WRITTEN PERMISSION \nCopyright 1999-2012 id Software LLC, a ZeniMax Media company. All Rights Reserved. \n================================================================================================\n*/\n\n" ); + + for( i = 1; i <= D_EVENT_MAXARGS; i++ ) { + + file->Printf( "\t/*******************************************************\n\n\t\t%d args\n\n\t*******************************************************/\n\n", i ); + + for ( j = 0; j < ( 1 << i ); j++ ) { + for( k = 0; k < i; k++ ) { + argString[ k ] = j & ( 1 << k ) ? 'f' : 'i'; + } + argString[ i ] = '\0'; + + string1.Empty(); + string2.Empty(); + + for( k = 0; k < i; k++ ) { + if ( j & ( 1 << k ) ) { + string1 += "const float"; + string2 += va( "*( float * )&data[ %d ]", k ); + } else { + string1 += "void *"; + string2 += va( "(void *)data[ %d ]", k ); + } + + if ( k < i - 1 ) { + string1 += ", "; + string2 += ", "; + } + } + + file->Printf( "\tcase %d :\n\t\ttypedef void ( idClass::*eventCallback_%s_t )( %s );\n", ( 1 << ( i + D_EVENT_MAXARGS ) ) + j, argString, string1.c_str() ); + file->Printf( "\t\t( this->*( eventCallback_%s_t )callback )( %s );\n\t\tbreak;\n\n", argString, string2.c_str() ); + + } + } + + fileSystem->CloseFile( file ); +} + +#endif diff --git a/neo/d3xp/gamesys/Event.h b/neo/d3xp/gamesys/Event.h new file mode 100644 index 00000000..f192ddd4 --- /dev/null +++ b/neo/d3xp/gamesys/Event.h @@ -0,0 +1,210 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +sys_event.h + +Event are used for scheduling tasks and for linking script commands. +*/ +#ifndef __SYS_EVENT_H__ +#define __SYS_EVENT_H__ + +#define D_EVENT_MAXARGS 8 // if changed, enable the CREATE_EVENT_CODE define in Event.cpp to generate switch statement for idClass::ProcessEventArgPtr. + // running the game will then generate c:\doom\base\events.txt, the contents of which should be copied into the switch statement. + +#define D_EVENT_VOID ( ( char )0 ) +#define D_EVENT_INTEGER 'd' +#define D_EVENT_FLOAT 'f' +#define D_EVENT_VECTOR 'v' +#define D_EVENT_STRING 's' +#define D_EVENT_ENTITY 'e' +#define D_EVENT_ENTITY_NULL 'E' // event can handle NULL entity pointers +#define D_EVENT_TRACE 't' + +#define MAX_EVENTS 4096 + +class idClass; +class idTypeInfo; + +class idEventDef { +private: + const char *name; + const char *formatspec; + unsigned int formatspecIndex; + int returnType; + int numargs; + size_t argsize; + int argOffset[ D_EVENT_MAXARGS ]; + int eventnum; + const idEventDef * next; + + static idEventDef * eventDefList[MAX_EVENTS]; + static int numEventDefs; + +public: + idEventDef( const char *command, const char *formatspec = NULL, char returnType = 0 ); + + const char *GetName() const; + const char *GetArgFormat() const; + unsigned int GetFormatspecIndex() const; + char GetReturnType() const; + int GetEventNum() const; + int GetNumArgs() const; + size_t GetArgSize() const; + int GetArgOffset( int arg ) const; + + static int NumEventCommands(); + static const idEventDef *GetEventCommand( int eventnum ); + static const idEventDef *FindEvent( const char *name ); +}; + +class idSaveGame; +class idRestoreGame; + +class idEvent { +private: + const idEventDef *eventdef; + byte *data; + int time; + idClass *object; + const idTypeInfo *typeinfo; + + idLinkList eventNode; + + static idDynamicBlockAlloc eventDataAllocator; + + +public: + static bool initialized; + + ~idEvent(); + + static idEvent *Alloc( const idEventDef *evdef, int numargs, va_list args ); + static void CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ); + + void Free(); + void Schedule( idClass *object, const idTypeInfo *cls, int time ); + byte *GetData(); + + static void CancelEvents( const idClass *obj, const idEventDef *evdef = NULL ); + static void ClearEventList(); + static void ServiceEvents(); + static void ServiceFastEvents(); + static void Init(); + static void Shutdown(); + + // save games + static void Save( idSaveGame *savefile ); // archives object for save game file + static void Restore( idRestoreGame *savefile ); // unarchives object from save game file + static void SaveTrace( idSaveGame *savefile, const trace_t &trace ); + static void RestoreTrace( idRestoreGame *savefile, trace_t &trace ); + +}; + +/* +================ +idEvent::GetData +================ +*/ +ID_INLINE byte *idEvent::GetData() { + return data; +} + +/* +================ +idEventDef::GetName +================ +*/ +ID_INLINE const char *idEventDef::GetName() const { + return name; +} + +/* +================ +idEventDef::GetArgFormat +================ +*/ +ID_INLINE const char *idEventDef::GetArgFormat() const { + return formatspec; +} + +/* +================ +idEventDef::GetFormatspecIndex +================ +*/ +ID_INLINE unsigned int idEventDef::GetFormatspecIndex() const { + return formatspecIndex; +} + +/* +================ +idEventDef::GetReturnType +================ +*/ +ID_INLINE char idEventDef::GetReturnType() const { + return returnType; +} + +/* +================ +idEventDef::GetNumArgs +================ +*/ +ID_INLINE int idEventDef::GetNumArgs() const { + return numargs; +} + +/* +================ +idEventDef::GetArgSize +================ +*/ +ID_INLINE size_t idEventDef::GetArgSize() const { + return argsize; +} + +/* +================ +idEventDef::GetArgOffset +================ +*/ +ID_INLINE int idEventDef::GetArgOffset( int arg ) const { + assert( ( arg >= 0 ) && ( arg < D_EVENT_MAXARGS ) ); + return argOffset[ arg ]; +} + +/* +================ +idEventDef::GetEventNum +================ +*/ +ID_INLINE int idEventDef::GetEventNum() const { + return eventnum; +} + +#endif /* !__SYS_EVENT_H__ */ diff --git a/neo/d3xp/gamesys/SaveGame.cpp b/neo/d3xp/gamesys/SaveGame.cpp new file mode 100644 index 00000000..d54304a9 --- /dev/null +++ b/neo/d3xp/gamesys/SaveGame.cpp @@ -0,0 +1,1601 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#include "TypeInfo.h" + +/* +Save game related helper classes. + +Save games are implemented in two classes, idSaveGame and idRestoreGame, that implement write/read functions for +common types. They're passed in to each entity and object for them to archive themselves. Each class +implements save/restore functions for it's own data. When restoring, all the objects are instantiated, +then the restore function is called on each, superclass first, then subclasses. + +Pointers are restored by saving out an object index for each unique object pointer and adding them to a list of +objects that are to be saved. Restore instantiates all the objects in the list before calling the Restore function +on each object so that the pointers returned are valid. No object's restore function should rely on any other objects +being fully instantiated until after the restore process is complete. Post restore fixup should be done by posting +events with 0 delay. + +The savegame header will have the Game Name, Version, Map Name, and Player Persistent Info. + +Changes in version make savegames incompatible, and the game will start from the beginning of the level with +the player's persistent info. + +Changes to classes that don't need to break compatibilty can use the build number as the savegame version. +Later versions are responsible for restoring from previous versions by ignoring any unused data and initializing +variables that weren't in previous versions with safe information. + +At the head of the save game is enough information to restore the player to the beginning of the level should the +file be unloadable in some way (for example, due to script changes). +*/ + +/* +================ +idSaveGame::idSaveGame() +================ +*/ +idSaveGame::idSaveGame( idFile *savefile, idFile *stringTableFile, int saveVersion ) { + //compressor = idCompressor::AllocLZW(); + //compressor->Init( savefile, true, 8 ); + //file = compressor; + file = savefile; + stringFile = stringTableFile; + version = saveVersion; + + // Put NULL at the start of the list so we can skip over it. + objects.Clear(); + objects.Append( NULL ); + + curStringTableOffset = 0; +} + +/* +================ +idSaveGame::~idSaveGame() +================ +*/ +idSaveGame::~idSaveGame() { + //compressor->FinishCompress(); + //delete compressor; + + if ( objects.Num() ) { + Close(); + } +} + +/* +================ +idSaveGame::Close +================ +*/ +void idSaveGame::Close() { + WriteSoundCommands(); + + // read trace models + idClipModel::SaveTraceModels( this ); + + for( int i = 1; i < objects.Num(); i++ ) { + CallSave_r( objects[ i ]->GetType(), objects[ i ] ); + } + + objects.Clear(); + + // Save out the string table at the end of the file + for ( int i = 0; i < stringTable.Num(); ++i ) { + stringFile->WriteString( stringTable[i].string ); + } + + stringHash.Free(); + stringTable.Clear(); + + if ( file->Length() > MIN_SAVEGAME_SIZE_BYTES || stringFile->Length() > MAX_SAVEGAME_STRING_TABLE_SIZE ) { + idLib::FatalError( "OVERFLOWED SAVE GAME FILE BUFFER" ); + } + +#ifdef ID_DEBUG_MEMORY + idStr gameState = file->GetName(); + gameState.StripFileExtension(); + WriteGameState_f( idCmdArgs( va( "test %s_save", gameState.c_str() ), false ) ); +#endif +} + +/* +================ +idSaveGame::WriteDecls +================ +*/ +void idSaveGame::WriteDecls() { + // Write out all loaded decls + for ( int t = 0; t < declManager->GetNumDeclTypes(); t++ ) { + for ( int d = 0; d < declManager->GetNumDecls( (declType_t)t ); d++ ) { + const idDecl * decl = declManager->DeclByIndex( (declType_t)t, d, false ); + if ( decl == NULL || decl->GetState() == DS_UNPARSED ) { + continue; + } + const char * declName = decl->GetName(); + if ( declName[0] == 0 ) { + continue; + } + WriteString( declName ); + } + WriteString( 0 ); + } +} + +/* +================ +idSaveGame::WriteObjectList +================ +*/ +void idSaveGame::WriteObjectList() { + WriteInt( objects.Num() - 1 ); + for ( int i = 1; i < objects.Num(); i++ ) { + WriteString( objects[ i ]->GetClassname() ); + } +} + +/* +================ +idSaveGame::CallSave_r +================ +*/ +void idSaveGame::CallSave_r( const idTypeInfo *cls, const idClass *obj ) { + if ( cls->super ) { + CallSave_r( cls->super, obj ); + if ( cls->super->Save == cls->Save ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + ( obj->*cls->Save )( this ); +} + +/* +================ +idSaveGame::AddObject +================ +*/ +void idSaveGame::AddObject( const idClass *obj ) { + objects.AddUnique( obj ); +} + +/* +================ +idSaveGame::Write +================ +*/ +void idSaveGame::Write( const void *buffer, int len ) { + file->Write( buffer, len ); +} + +/* +================ +idSaveGame::WriteInt +================ +*/ +void idSaveGame::WriteInt( const int value ) { + file->WriteBig( value ); +} + +/* +================ +idSaveGame::WriteJoint +================ +*/ +void idSaveGame::WriteJoint( const jointHandle_t value ) { + file->WriteBig( (int&)value ); +} + +/* +================ +idSaveGame::WriteShort +================ +*/ +void idSaveGame::WriteShort( const short value ) { + file->WriteBig( value ); +} + +/* +================ +idSaveGame::WriteByte +================ +*/ +void idSaveGame::WriteByte( const byte value ) { + file->Write( &value, sizeof( value ) ); +} + +/* +================ +idSaveGame::WriteSignedChar +================ +*/ +void idSaveGame::WriteSignedChar( const signed char value ) { + file->Write( &value, sizeof( value ) ); +} + +/* +================ +idSaveGame::WriteFloat +================ +*/ +void idSaveGame::WriteFloat( const float value ) { + file->WriteBig( value ); +} + +/* +================ +idSaveGame::WriteBool +================ +*/ +void idSaveGame::WriteBool( const bool value ) { + file->WriteBool( value ); +} + +/* +================ +idSaveGame::WriteString +================ +*/ +void idSaveGame::WriteString( const char *string ) { + if ( string == NULL || *string == 0 ) { + WriteInt( -1 ); + return; + } + + // If we already have this string in our hash, write out of the offset in the table and return + int hash = stringHash.GenerateKey( string ); + for ( int i = stringHash.First( hash); i != -1; i = stringHash.Next( i ) ) { + if ( stringTable[i].string.Cmp( string ) == 0 ) { + WriteInt( stringTable[i].offset ); + return; + } + } + + // Add the string to our hash, generate the index, and update our current table offset + stringTableIndex_s & tableIndex = stringTable.Alloc(); + tableIndex.offset = curStringTableOffset; + tableIndex.string = string; + stringHash.Add( hash, stringTable.Num() - 1 ); + + WriteInt( curStringTableOffset ); + curStringTableOffset += ( strlen( string ) + 4 ); +} + +/* +================ +idSaveGame::WriteVec2 +================ +*/ +void idSaveGame::WriteVec2( const idVec2 &vec ) { + file->WriteBig( vec ); +} + +/* +================ +idSaveGame::WriteVec3 +================ +*/ +void idSaveGame::WriteVec3( const idVec3 &vec ) { + file->WriteBig( vec ); +} + +/* +================ +idSaveGame::WriteVec4 +================ +*/ +void idSaveGame::WriteVec4( const idVec4 &vec ) { + file->WriteBig( vec ); +} + +/* +================ +idSaveGame::WriteVec6 +================ +*/ +void idSaveGame::WriteVec6( const idVec6 &vec ) { + file->WriteBig( vec ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteBounds( const idBounds &bounds ) { + file->WriteBig( bounds ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteWinding( const idWinding &w ) +{ + int i, num; + num = w.GetNumPoints(); + file->WriteBig( num ); + for ( i = 0; i < num; i++ ) { + idVec5 v = w[i]; + file->WriteBig( v ); + } +} + + +/* +================ +idSaveGame::WriteMat3 +================ +*/ +void idSaveGame::WriteMat3( const idMat3 &mat ) { + file->WriteBig( mat ); +} + +/* +================ +idSaveGame::WriteAngles +================ +*/ +void idSaveGame::WriteAngles( const idAngles &angles ) { + file->WriteBig( angles ); +} + +/* +================ +idSaveGame::WriteObject +================ +*/ +void idSaveGame::WriteObject( const idClass *obj ) { + int index; + + index = objects.FindIndex( obj ); + if ( index < 0 ) { + gameLocal.DPrintf( "idSaveGame::WriteObject - WriteObject FindIndex failed\n" ); + + // Use the NULL index + index = 0; + } + + WriteInt( index ); +} + +/* +================ +idSaveGame::WriteStaticObject +================ +*/ +void idSaveGame::WriteStaticObject( const idClass &obj ) { + CallSave_r( obj.GetType(), &obj ); +} + +/* +================ +idSaveGame::WriteDict +================ +*/ +void idSaveGame::WriteDict( const idDict *dict ) { + int num; + int i; + const idKeyValue *kv; + + if ( !dict ) { + WriteInt( -1 ); + } else { + num = dict->GetNumKeyVals(); + WriteInt( num ); + for( i = 0; i < num; i++ ) { + kv = dict->GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + } + } +} + +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterial( const idMaterial *material ) { + if ( !material ) { + WriteString( "" ); + } else { + WriteString( material->GetName() ); + } +} + +/* +================ +idSaveGame::WriteSkin +================ +*/ +void idSaveGame::WriteSkin( const idDeclSkin *skin ) { + if ( !skin ) { + WriteString( "" ); + } else { + WriteString( skin->GetName() ); + } +} + +/* +================ +idSaveGame::WriteParticle +================ +*/ +void idSaveGame::WriteParticle( const idDeclParticle *particle ) { + if ( !particle ) { + WriteString( "" ); + } else { + WriteString( particle->GetName() ); + } +} + +/* +================ +idSaveGame::WriteFX +================ +*/ +void idSaveGame::WriteFX( const idDeclFX *fx ) { + if ( !fx ) { + WriteString( "" ); + } else { + WriteString( fx->GetName() ); + } +} + +/* +================ +idSaveGame::WriteModelDef +================ +*/ +void idSaveGame::WriteModelDef( const idDeclModelDef *modelDef ) { + if ( !modelDef ) { + WriteString( "" ); + } else { + WriteString( modelDef->GetName() ); + } +} + +/* +================ +idSaveGame::WriteSoundShader +================ +*/ +void idSaveGame::WriteSoundShader( const idSoundShader *shader ) { + const char *name; + + if ( !shader ) { + WriteString( "" ); + } else { + name = shader->GetName(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteModel +================ +*/ +void idSaveGame::WriteModel( const idRenderModel *model ) { + const char *name; + + if ( !model ) { + WriteString( "" ); + } else { + name = model->Name(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteUserInterface +================ +*/ +void idSaveGame::WriteUserInterface( const idUserInterface *ui, bool unique ) { + const char *name; + + if ( !ui ) { + WriteString( "" ); + } else { + name = ui->Name(); + WriteString( name ); + WriteBool( unique ); + if ( ui->WriteToSaveGame( file ) == false ) { + gameLocal.Error( "idSaveGame::WriteUserInterface: ui failed to write properly\n" ); + } + } +} + +/* +================ +idSaveGame::WriteRenderEntity +================ +*/ +void idSaveGame::WriteRenderEntity( const renderEntity_t &renderEntity ) { + int i; + + WriteModel( renderEntity.hModel ); + + WriteInt( renderEntity.entityNum ); + WriteInt( renderEntity.bodyId ); + + WriteBounds( renderEntity.bounds ); + + // callback is set by class's Restore function + + WriteInt( renderEntity.suppressSurfaceInViewID ); + WriteInt( renderEntity.suppressShadowInViewID ); + WriteInt( renderEntity.suppressShadowInLightID ); + WriteInt( renderEntity.allowSurfaceInViewID ); + + WriteVec3( renderEntity.origin ); + WriteMat3( renderEntity.axis ); + + WriteMaterial( renderEntity.customShader ); + WriteMaterial( renderEntity.referenceShader ); + WriteSkin( renderEntity.customSkin ); + + if ( renderEntity.referenceSound != NULL ) { + WriteInt( renderEntity.referenceSound->Index() ); + } else { + WriteInt( 0 ); + } + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + WriteUserInterface( renderEntity.gui[ i ], renderEntity.gui[ i ] ? renderEntity.gui[ i ]->IsUniqued() : false ); + } + + WriteFloat( renderEntity.modelDepthHack ); + + WriteBool( renderEntity.noSelfShadow ); + WriteBool( renderEntity.noShadow ); + WriteBool( renderEntity.noDynamicInteractions ); + WriteBool( renderEntity.weaponDepthHack ); + + WriteInt( renderEntity.forceUpdate ); + + WriteInt( renderEntity.timeGroup ); + WriteInt( renderEntity.xrayIndex ); +} + +/* +================ +idSaveGame::WriteRenderLight +================ +*/ +void idSaveGame::WriteRenderLight( const renderLight_t &renderLight ) { + int i; + + WriteMat3( renderLight.axis ); + WriteVec3( renderLight.origin ); + + WriteInt( renderLight.suppressLightInViewID ); + WriteInt( renderLight.allowLightInViewID ); + WriteBool( renderLight.noShadows ); + WriteBool( renderLight.noSpecular ); + WriteBool( renderLight.pointLight ); + WriteBool( renderLight.parallel ); + + WriteVec3( renderLight.lightRadius ); + WriteVec3( renderLight.lightCenter ); + + WriteVec3( renderLight.target ); + WriteVec3( renderLight.right ); + WriteVec3( renderLight.up ); + WriteVec3( renderLight.start ); + WriteVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // WriteModel( renderLight.prelightModel ); + + WriteInt( renderLight.lightId ); + + WriteMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderLight.shaderParms[ i ] ); + } + + if ( renderLight.referenceSound != NULL ) { + WriteInt( renderLight.referenceSound->Index() ); + } else { + WriteInt( 0 ); + } +} + +/* +================ +idSaveGame::WriteRefSound +================ +*/ +void idSaveGame::WriteRefSound( const refSound_t &refSound ) { + if ( refSound.referenceSound ) { + WriteInt( refSound.referenceSound->Index() ); + } else { + WriteInt( 0 ); + } + WriteVec3( refSound.origin ); + WriteInt( refSound.listenerId ); + WriteSoundShader( refSound.shader ); + WriteFloat( refSound.diversity ); + WriteBool( refSound.waitfortrigger ); + + WriteFloat( refSound.parms.minDistance ); + WriteFloat( refSound.parms.maxDistance ); + WriteFloat( refSound.parms.volume ); + WriteFloat( refSound.parms.shakes ); + WriteInt( refSound.parms.soundShaderFlags ); + WriteInt( refSound.parms.soundClass ); +} + +/* +================ +idSaveGame::WriteRenderView +================ +*/ +void idSaveGame::WriteRenderView( const renderView_t &view ) { + int i; + + WriteInt( view.viewID ); + WriteInt( 0 /* view.x */ ); + WriteInt( 0 /* view.y */ ); + WriteInt( 0 /* view.width */ ); + WriteInt( 0 /* view.height */ ); + + WriteFloat( view.fov_x ); + WriteFloat( view.fov_y ); + WriteVec3( view.vieworg ); + WriteMat3( view.viewaxis ); + + WriteBool( view.cramZNear ); + + WriteInt( view.time[0] ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + WriteFloat( view.shaderParms[ i ] ); + } +} + +/* +=================== +idSaveGame::WriteUsercmd +=================== +*/ +void idSaveGame::WriteUsercmd( const usercmd_t &usercmd ) { + WriteByte( usercmd.buttons ); + WriteSignedChar( usercmd.forwardmove ); + WriteSignedChar( usercmd.rightmove ); + WriteShort( usercmd.angles[0] ); + WriteShort( usercmd.angles[1] ); + WriteShort( usercmd.angles[2] ); + WriteShort( usercmd.mx ); + WriteShort( usercmd.my ); + WriteByte( usercmd.impulse ); + WriteByte( usercmd.impulseSequence ); +} + +/* +=================== +idSaveGame::WriteContactInfo +=================== +*/ +void idSaveGame::WriteContactInfo( const contactInfo_t &contactInfo ) { + WriteInt( (int)contactInfo.type ); + WriteVec3( contactInfo.point ); + WriteVec3( contactInfo.normal ); + WriteFloat( contactInfo.dist ); + WriteInt( contactInfo.contents ); + WriteMaterial( contactInfo.material ); + WriteInt( contactInfo.modelFeature ); + WriteInt( contactInfo.trmFeature ); + WriteInt( contactInfo.entityNum ); + WriteInt( contactInfo.id ); +} + +/* +=================== +idSaveGame::WriteTrace +=================== +*/ +void idSaveGame::WriteTrace( const trace_t &trace ) { + WriteFloat( trace.fraction ); + WriteVec3( trace.endpos ); + WriteMat3( trace.endAxis ); + WriteContactInfo( trace.c ); +} + +/* + =================== + idRestoreGame::WriteTraceModel + =================== + */ +void idSaveGame::WriteTraceModel( const idTraceModel &trace ) { + int j, k; + + WriteInt( (int&)trace.type ); + WriteInt( trace.numVerts ); + for ( j = 0; j < MAX_TRACEMODEL_VERTS; j++ ) { + WriteVec3( trace.verts[j] ); + } + WriteInt( trace.numEdges ); + for ( j = 0; j < (MAX_TRACEMODEL_EDGES+1); j++ ) { + WriteInt( trace.edges[j].v[0] ); + WriteInt( trace.edges[j].v[1] ); + WriteVec3( trace.edges[j].normal ); + } + WriteInt( trace.numPolys ); + for ( j = 0; j < MAX_TRACEMODEL_POLYS; j++ ) { + WriteVec3( trace.polys[j].normal ); + WriteFloat( trace.polys[j].dist ); + WriteBounds( trace.polys[j].bounds ); + WriteInt( trace.polys[j].numEdges ); + for ( k = 0; k < MAX_TRACEMODEL_POLYEDGES; k++ ) { + WriteInt( trace.polys[j].edges[k] ); + } + } + WriteVec3( trace.offset ); + WriteBounds( trace.bounds ); + WriteBool( trace.isConvex ); + // padding win32 native structs + char tmp[3]; + memset( tmp, 0, sizeof( tmp ) ); + file->Write( tmp, 3 ); +} + +/* +=================== +idSaveGame::WriteClipModel +=================== +*/ +void idSaveGame::WriteClipModel( const idClipModel *clipModel ) { + if ( clipModel != NULL ) { + WriteBool( true ); + clipModel->Save( this ); + } else { + WriteBool( false ); + } +} + +/* +=================== +idSaveGame::WriteSoundCommands +=================== +*/ +void idSaveGame::WriteSoundCommands() { + gameSoundWorld->WriteToSaveGame( file ); +} + +/* +====================== +idSaveGame::WriteBuildNumber +====================== +*/ +void idSaveGame::WriteBuildNumber( const int value ) { + WriteInt( BUILD_NUMBER ); +} + +/*********************************************************************** + + idRestoreGame + +***********************************************************************/ + +/* +================ +idRestoreGame::RestoreGame +================ +*/ +idRestoreGame::idRestoreGame( idFile * savefile, idFile * stringTableFile, int saveVersion ) { + file = savefile; + stringFile = stringTableFile; + version = saveVersion; +} + +/* +================ +idRestoreGame::~idRestoreGame() +================ +*/ +idRestoreGame::~idRestoreGame() { +} + +/* +================ +idRestoreGame::ReadDecls +================ +*/ +void idRestoreGame::ReadDecls() { + idStr declName; + for ( int t = 0; t < declManager->GetNumDeclTypes(); t++ ) { + while ( true ) { + ReadString( declName ); + if ( declName.IsEmpty() ) { + break; + } + declManager->FindType( (declType_t)t, declName ); + } + } +} + +/* +================ +void idRestoreGame::CreateObjects +================ +*/ +void idRestoreGame::CreateObjects() { + int i, num; + idStr classname; + idTypeInfo *type; + + ReadInt( num ); + + // create all the objects + objects.SetNum( num + 1 ); + memset( objects.Ptr(), 0, sizeof( objects[ 0 ] ) * objects.Num() ); + + for( i = 1; i < objects.Num(); i++ ) { + ReadString( classname ); + type = idClass::GetClass( classname ); + if ( type == NULL ) { + Error( "idRestoreGame::CreateObjects: Unknown class '%s'", classname.c_str() ); + return; + } + objects[ i ] = type->CreateInstance(); + +#ifdef ID_DEBUG_MEMORY + InitTypeVariables( objects[i], type->classname, 0xce ); +#endif + } +} + +/* +================ +void idRestoreGame::RestoreObjects +================ +*/ +void idRestoreGame::RestoreObjects() { + int i; + + ReadSoundCommands(); + + // read trace models + idClipModel::RestoreTraceModels( this ); + + // restore all the objects + for( i = 1; i < objects.Num(); i++ ) { + CallRestore_r( objects[ i ]->GetType(), objects[ i ] ); + } + + // regenerate render entities and render lights because are not saved + for( i = 1; i < objects.Num(); i++ ) { + if ( objects[ i ]->IsType( idEntity::Type ) ) { + idEntity *ent = static_cast( objects[ i ] ); + ent->UpdateVisuals(); + ent->Present(); + } + } + +#ifdef ID_DEBUG_MEMORY + idStr gameState = file->GetName(); + gameState.StripFileExtension(); + WriteGameState_f( idCmdArgs( va( "test %s_restore", gameState.c_str() ), false ) ); + //CompareGameState_f( idCmdArgs( va( "test %s_save", gameState.c_str() ) ) ); + gameLocal.Error( "dumped game states" ); +#endif +} + +/* +==================== +void idRestoreGame::DeleteObjects +==================== +*/ +void idRestoreGame::DeleteObjects() { + + // Remove the NULL object before deleting + objects.RemoveIndex( 0 ); + + objects.DeleteContents( true ); +} + +/* +================ +idRestoreGame::Error +================ +*/ +void idRestoreGame::Error( const char *fmt, ... ) { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + objects.DeleteContents( true ); + + gameLocal.Error( "%s", text ); +} + +/* +================ +idRestoreGame::CallRestore_r +================ +*/ +void idRestoreGame::CallRestore_r( const idTypeInfo *cls, idClass *obj ) { + if ( cls->super ) { + CallRestore_r( cls->super, obj ); + if ( cls->super->Restore == cls->Restore ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + ( obj->*cls->Restore )( this ); +} + +/* +================ +idRestoreGame::Read +================ +*/ +void idRestoreGame::Read( void *buffer, int len ) { + file->Read( buffer, len ); +} + +/* +================ +idRestoreGame::ReadInt +================ +*/ +void idRestoreGame::ReadInt( int &value ) { + file->ReadBig( value ); +} + +/* +================ +idRestoreGame::ReadJoint +================ +*/ +void idRestoreGame::ReadJoint( jointHandle_t &value ) { + file->ReadBig( (int&)value ); +} + +/* +================ +idRestoreGame::ReadShort +================ +*/ +void idRestoreGame::ReadShort( short &value ) { + file->ReadBig( value ); +} + +/* +================ +idRestoreGame::ReadByte +================ +*/ +void idRestoreGame::ReadByte( byte &value ) { + file->Read( &value, sizeof( value ) ); +} + +/* +================ +idRestoreGame::ReadSignedChar +================ +*/ +void idRestoreGame::ReadSignedChar( signed char &value ) { + file->Read( &value, sizeof( value ) ); +} + +/* +================ +idRestoreGame::ReadFloat +================ +*/ +void idRestoreGame::ReadFloat( float &value ) { + file->ReadBig( value ); +} + +/* +================ +idRestoreGame::ReadBool +================ +*/ +void idRestoreGame::ReadBool( bool &value ) { + file->ReadBig( value ); +} + +/* +================ +idRestoreGame::ReadString +================ +*/ +void idRestoreGame::ReadString( idStr &string ) { + string.Empty(); + + int offset = -1; + ReadInt( offset ); + + if ( offset < 0 ) { + return; + } + + stringFile->Seek( offset, FS_SEEK_SET ); + stringFile->ReadString( string ); + + return; +} + +/* +================ +idRestoreGame::ReadVec2 +================ +*/ +void idRestoreGame::ReadVec2( idVec2 &vec ) { + file->ReadBig( vec ); +} + +/* +================ +idRestoreGame::ReadVec3 +================ +*/ +void idRestoreGame::ReadVec3( idVec3 &vec ) { + file->ReadBig( vec ); +} + +/* +================ +idRestoreGame::ReadVec4 +================ +*/ +void idRestoreGame::ReadVec4( idVec4 &vec ) { + file->ReadBig( vec ); +} + +/* +================ +idRestoreGame::ReadVec6 +================ +*/ +void idRestoreGame::ReadVec6( idVec6 &vec ) { + file->ReadBig( vec ); +} + +/* +================ +idRestoreGame::ReadBounds +================ +*/ +void idRestoreGame::ReadBounds( idBounds &bounds ) { + file->ReadBig( bounds ); +} + +/* +================ +idRestoreGame::ReadWinding +================ +*/ +void idRestoreGame::ReadWinding( idWinding &w ) +{ + int i, num; + ReadInt( num ); + w.SetNumPoints( num ); + for ( i = 0; i < num; i++ ) { + idVec5 & v = w[i]; + file->ReadBig( v ); + } +} + +/* +================ +idRestoreGame::ReadMat3 +================ +*/ +void idRestoreGame::ReadMat3( idMat3 &mat ) { + file->ReadBig( mat ); +} + +/* +================ +idRestoreGame::ReadAngles +================ +*/ +void idRestoreGame::ReadAngles( idAngles &angles ) { + file->ReadBig( angles ); +} + +/* +================ +idRestoreGame::ReadObject +================ +*/ +void idRestoreGame::ReadObject( idClass *&obj ) { + int index; + + ReadInt( index ); + if ( ( index < 0 ) || ( index >= objects.Num() ) ) { + Error( "idRestoreGame::ReadObject: invalid object index" ); + } + obj = objects[ index ]; +} + +/* +================ +idRestoreGame::ReadStaticObject +================ +*/ +void idRestoreGame::ReadStaticObject( idClass &obj ) { + CallRestore_r( obj.GetType(), &obj ); +} + +/* +================ +idRestoreGame::ReadDict +================ +*/ +void idRestoreGame::ReadDict( idDict *dict ) { + int num; + int i; + idStr key; + idStr value; + + ReadInt( num ); + + if ( num < 0 ) { + dict = NULL; + } else { + dict->Clear(); + for( i = 0; i < num; i++ ) { + ReadString( key ); + ReadString( value ); + dict->Set( key, value ); + } + } +} + +/* +================ +idRestoreGame::ReadMaterial +================ +*/ +void idRestoreGame::ReadMaterial( const idMaterial *&material ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + material = NULL; + } else { + material = declManager->FindMaterial( name ); + } +} + +/* +================ +idRestoreGame::ReadSkin +================ +*/ +void idRestoreGame::ReadSkin( const idDeclSkin *&skin ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( name ); + } +} + +/* +================ +idRestoreGame::ReadParticle +================ +*/ +void idRestoreGame::ReadParticle( const idDeclParticle *&particle ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + particle = NULL; + } else { + particle = static_cast( declManager->FindType( DECL_PARTICLE, name ) ); + } +} + +/* +================ +idRestoreGame::ReadFX +================ +*/ +void idRestoreGame::ReadFX( const idDeclFX *&fx ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + fx = NULL; + } else { + fx = static_cast( declManager->FindType( DECL_FX, name ) ); + } +} + +/* +================ +idRestoreGame::ReadSoundShader +================ +*/ +void idRestoreGame::ReadSoundShader( const idSoundShader *&shader ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + shader = NULL; + } else { + shader = declManager->FindSound( name ); + } +} + +/* +================ +idRestoreGame::ReadModelDef +================ +*/ +void idRestoreGame::ReadModelDef( const idDeclModelDef *&modelDef ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + modelDef = NULL; + } else { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + } +} + +/* +================ +idRestoreGame::ReadModel +================ +*/ +void idRestoreGame::ReadModel( idRenderModel *&model ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + model = NULL; + } else { + model = renderModelManager->FindModel( name ); + } +} + +/* +================ +idRestoreGame::ReadUserInterface +================ +*/ +void idRestoreGame::ReadUserInterface( idUserInterface *&ui ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + ui = NULL; + } else { + bool unique; + ReadBool( unique ); + ui = uiManager->FindGui( name, true, unique ); + if ( ui ) { + if ( ui->ReadFromSaveGame( file ) == false ) { + Error( "idSaveGame::ReadUserInterface: ui failed to read properly\n" ); + } else { + ui->StateChanged( gameLocal.time ); + } + } + } +} + +/* +================ +idRestoreGame::ReadRenderEntity +================ +*/ +void idRestoreGame::ReadRenderEntity( renderEntity_t &renderEntity ) { + int i; + int index; + + ReadModel( renderEntity.hModel ); + + ReadInt( renderEntity.entityNum ); + ReadInt( renderEntity.bodyId ); + + ReadBounds( renderEntity.bounds ); + + // callback is set by class's Restore function + renderEntity.callback = NULL; + renderEntity.callbackData = NULL; + + ReadInt( renderEntity.suppressSurfaceInViewID ); + ReadInt( renderEntity.suppressShadowInViewID ); + ReadInt( renderEntity.suppressShadowInLightID ); + ReadInt( renderEntity.allowSurfaceInViewID ); + + ReadVec3( renderEntity.origin ); + ReadMat3( renderEntity.axis ); + + ReadMaterial( renderEntity.customShader ); + ReadMaterial( renderEntity.referenceShader ); + ReadSkin( renderEntity.customSkin ); + + ReadInt( index ); + renderEntity.referenceSound = gameSoundWorld->EmitterForIndex( index ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + ReadUserInterface( renderEntity.gui[ i ] ); + } + + // idEntity will restore "cameraTarget", which will be used in idEntity::Present to restore the remoteRenderView + renderEntity.remoteRenderView = NULL; + + renderEntity.joints = NULL; + renderEntity.numJoints = 0; + + ReadFloat( renderEntity.modelDepthHack ); + + ReadBool( renderEntity.noSelfShadow ); + ReadBool( renderEntity.noShadow ); + ReadBool( renderEntity.noDynamicInteractions ); + ReadBool( renderEntity.weaponDepthHack ); + + ReadInt( renderEntity.forceUpdate ); + + ReadInt( renderEntity.timeGroup ); + ReadInt( renderEntity.xrayIndex ); +} + +/* +================ +idRestoreGame::ReadRenderLight +================ +*/ +void idRestoreGame::ReadRenderLight( renderLight_t &renderLight ) { + int index; + int i; + + ReadMat3( renderLight.axis ); + ReadVec3( renderLight.origin ); + + ReadInt( renderLight.suppressLightInViewID ); + ReadInt( renderLight.allowLightInViewID ); + ReadBool( renderLight.noShadows ); + ReadBool( renderLight.noSpecular ); + ReadBool( renderLight.pointLight ); + ReadBool( renderLight.parallel ); + + ReadVec3( renderLight.lightRadius ); + ReadVec3( renderLight.lightCenter ); + + ReadVec3( renderLight.target ); + ReadVec3( renderLight.right ); + ReadVec3( renderLight.up ); + ReadVec3( renderLight.start ); + ReadVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // ReadModel( renderLight.prelightModel ); + renderLight.prelightModel = NULL; + + ReadInt( renderLight.lightId ); + + ReadMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderLight.shaderParms[ i ] ); + } + + ReadInt( index ); + renderLight.referenceSound = gameSoundWorld->EmitterForIndex( index ); +} + +/* +================ +idRestoreGame::ReadRefSound +================ +*/ +void idRestoreGame::ReadRefSound( refSound_t &refSound ) { + int index; + ReadInt( index ); + + refSound.referenceSound = gameSoundWorld->EmitterForIndex( index ); + ReadVec3( refSound.origin ); + ReadInt( refSound.listenerId ); + ReadSoundShader( refSound.shader ); + ReadFloat( refSound.diversity ); + ReadBool( refSound.waitfortrigger ); + + ReadFloat( refSound.parms.minDistance ); + ReadFloat( refSound.parms.maxDistance ); + ReadFloat( refSound.parms.volume ); + ReadFloat( refSound.parms.shakes ); + ReadInt( refSound.parms.soundShaderFlags ); + ReadInt( refSound.parms.soundClass ); +} + +/* +================ +idRestoreGame::ReadRenderView +================ +*/ +void idRestoreGame::ReadRenderView( renderView_t &view ) { + int i; + + ReadInt( view.viewID ); + ReadInt( i /* view.x */ ); + ReadInt( i /* view.y */ ); + ReadInt( i /* view.width */ ); + ReadInt( i /* view.height */ ); + + ReadFloat( view.fov_x ); + ReadFloat( view.fov_y ); + ReadVec3( view.vieworg ); + ReadMat3( view.viewaxis ); + + ReadBool( view.cramZNear ); + + ReadInt( view.time[0] ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + ReadFloat( view.shaderParms[ i ] ); + } +} + +/* +================= +idRestoreGame::ReadUsercmd +================= +*/ +void idRestoreGame::ReadUsercmd( usercmd_t &usercmd ) { + ReadByte( usercmd.buttons ); + ReadSignedChar( usercmd.forwardmove ); + ReadSignedChar( usercmd.rightmove ); + ReadShort( usercmd.angles[0] ); + ReadShort( usercmd.angles[1] ); + ReadShort( usercmd.angles[2] ); + ReadShort( usercmd.mx ); + ReadShort( usercmd.my ); + ReadByte( usercmd.impulse ); + ReadByte( usercmd.impulseSequence ); +} + +/* +=================== +idRestoreGame::ReadContactInfo +=================== +*/ +void idRestoreGame::ReadContactInfo( contactInfo_t &contactInfo ) { + ReadInt( (int &)contactInfo.type ); + ReadVec3( contactInfo.point ); + ReadVec3( contactInfo.normal ); + ReadFloat( contactInfo.dist ); + ReadInt( contactInfo.contents ); + ReadMaterial( contactInfo.material ); + ReadInt( contactInfo.modelFeature ); + ReadInt( contactInfo.trmFeature ); + ReadInt( contactInfo.entityNum ); + ReadInt( contactInfo.id ); +} + +/* +=================== +idRestoreGame::ReadTrace +=================== +*/ +void idRestoreGame::ReadTrace( trace_t &trace ) { + ReadFloat( trace.fraction ); + ReadVec3( trace.endpos ); + ReadMat3( trace.endAxis ); + ReadContactInfo( trace.c ); +} + +/* + =================== + idRestoreGame::ReadTraceModel + =================== + */ +void idRestoreGame::ReadTraceModel( idTraceModel &trace ) { + int j, k; + + ReadInt( (int&)trace.type ); + ReadInt( trace.numVerts ); + for ( j = 0; j < MAX_TRACEMODEL_VERTS; j++ ) { + ReadVec3( trace.verts[j] ); + } + ReadInt( trace.numEdges ); + for ( j = 0; j < (MAX_TRACEMODEL_EDGES+1); j++ ) { + ReadInt( trace.edges[j].v[0] ); + ReadInt( trace.edges[j].v[1] ); + ReadVec3( trace.edges[j].normal ); + } + ReadInt( trace.numPolys ); + for ( j = 0; j < MAX_TRACEMODEL_POLYS; j++ ) { + ReadVec3( trace.polys[j].normal ); + ReadFloat( trace.polys[j].dist ); + ReadBounds( trace.polys[j].bounds ); + ReadInt( trace.polys[j].numEdges ); + for ( k = 0; k < MAX_TRACEMODEL_POLYEDGES; k++ ) { + ReadInt( trace.polys[j].edges[k] ); + } + } + ReadVec3( trace.offset ); + ReadBounds( trace.bounds ); + ReadBool( trace.isConvex ); + // padding win32 native structs + char tmp[3]; + file->Read( tmp, 3 ); +} + +/* +===================== +idRestoreGame::ReadClipModel +===================== +*/ +void idRestoreGame::ReadClipModel( idClipModel *&clipModel ) { + bool restoreClipModel; + + ReadBool( restoreClipModel ); + if ( restoreClipModel ) { + clipModel = new (TAG_SAVEGAMES) idClipModel(); + clipModel->Restore( this ); + } else { + clipModel = NULL; + } +} + +/* +===================== +idRestoreGame::ReadSoundCommands +===================== +*/ +void idRestoreGame::ReadSoundCommands() { + gameSoundWorld->StopAllSounds(); + gameSoundWorld->ReadFromSaveGame( file ); +} diff --git a/neo/d3xp/gamesys/SaveGame.h b/neo/d3xp/gamesys/SaveGame.h new file mode 100644 index 00000000..9c2c15df --- /dev/null +++ b/neo/d3xp/gamesys/SaveGame.h @@ -0,0 +1,182 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SAVEGAME_H__ +#define __SAVEGAME_H__ + +/* + +Save game related helper classes. + +*/ + +class idSaveGame { +public: + idSaveGame( idFile *savefile, idFile *stringFile, int inVersion ); + ~idSaveGame(); + + void Close(); + + void WriteDecls(); + + void AddObject( const idClass *obj ); + void Resize( const int count ) { objects.Resize( count ); } + void WriteObjectList(); + + void Write( const void *buffer, int len ); + void WriteInt( const int value ); + void WriteJoint( const jointHandle_t value ); + void WriteShort( const short value ); + void WriteByte( const byte value ); + void WriteSignedChar( const signed char value ); + void WriteFloat( const float value ); + void WriteBool( const bool value ); + void WriteString( const char *string ); + void WriteVec2( const idVec2 &vec ); + void WriteVec3( const idVec3 &vec ); + void WriteVec4( const idVec4 &vec ); + void WriteVec6( const idVec6 &vec ); + void WriteWinding( const idWinding &winding ); + void WriteBounds( const idBounds &bounds ); + void WriteMat3( const idMat3 &mat ); + void WriteAngles( const idAngles &angles ); + void WriteObject( const idClass *obj ); + void WriteStaticObject( const idClass &obj ); + void WriteDict( const idDict *dict ); + void WriteMaterial( const idMaterial *material ); + void WriteSkin( const idDeclSkin *skin ); + void WriteParticle( const idDeclParticle *particle ); + void WriteFX( const idDeclFX *fx ); + void WriteSoundShader( const idSoundShader *shader ); + void WriteModelDef( const class idDeclModelDef *modelDef ); + void WriteModel( const idRenderModel *model ); + void WriteUserInterface( const idUserInterface *ui, bool unique ); + void WriteRenderEntity( const renderEntity_t &renderEntity ); + void WriteRenderLight( const renderLight_t &renderLight ); + void WriteRefSound( const refSound_t &refSound ); + void WriteRenderView( const renderView_t &view ); + void WriteUsercmd( const usercmd_t &usercmd ); + void WriteContactInfo( const contactInfo_t &contactInfo ); + void WriteTrace( const trace_t &trace ); + void WriteTraceModel( const idTraceModel &trace ); + void WriteClipModel( const class idClipModel *clipModel ); + void WriteSoundCommands(); + + void WriteBuildNumber( const int value ); + + int GetBuildNumber() const { return version; } + + int GetCurrentSaveSize() const { return file->Length(); } + +private: + idFile * file; + idFile * stringFile; + idCompressor * compressor; + + idList objects; + int version; + + void CallSave_r( const idTypeInfo *cls, const idClass *obj ); + + struct stringTableIndex_s { + idStr string; + int offset; + }; + + idHashIndex stringHash; + idList< stringTableIndex_s > stringTable; + int curStringTableOffset; + +}; + +class idRestoreGame { +public: + idRestoreGame( idFile * savefile, idFile * stringTableFile, int saveVersion ); + ~idRestoreGame(); + + void ReadDecls(); + + void CreateObjects(); + void RestoreObjects(); + void DeleteObjects(); + + void Error( VERIFY_FORMAT_STRING const char *fmt, ... ); + + void Read( void *buffer, int len ); + void ReadInt( int &value ); + void ReadJoint( jointHandle_t &value ); + void ReadShort( short &value ); + void ReadByte( byte &value ); + void ReadSignedChar( signed char &value ); + void ReadFloat( float &value ); + void ReadBool( bool &value ); + void ReadString( idStr &string ); + void ReadVec2( idVec2 &vec ); + void ReadVec3( idVec3 &vec ); + void ReadVec4( idVec4 &vec ); + void ReadVec6( idVec6 &vec ); + void ReadWinding( idWinding &winding ); + void ReadBounds( idBounds &bounds ); + void ReadMat3( idMat3 &mat ); + void ReadAngles( idAngles &angles ); + void ReadObject( idClass *&obj ); + void ReadStaticObject( idClass &obj ); + void ReadDict( idDict *dict ); + void ReadMaterial( const idMaterial *&material ); + void ReadSkin( const idDeclSkin *&skin ); + void ReadParticle( const idDeclParticle *&particle ); + void ReadFX( const idDeclFX *&fx ); + void ReadSoundShader( const idSoundShader *&shader ); + void ReadModelDef( const idDeclModelDef *&modelDef ); + void ReadModel( idRenderModel *&model ); + void ReadUserInterface( idUserInterface *&ui ); + void ReadRenderEntity( renderEntity_t &renderEntity ); + void ReadRenderLight( renderLight_t &renderLight ); + void ReadRefSound( refSound_t &refSound ); + void ReadRenderView( renderView_t &view ); + void ReadUsercmd( usercmd_t &usercmd ); + void ReadContactInfo( contactInfo_t &contactInfo ); + void ReadTrace( trace_t &trace ); + void ReadTraceModel( idTraceModel &trace ); + void ReadClipModel( idClipModel *&clipModel ); + void ReadSoundCommands(); + + // Used to retrieve the saved game buildNumber from within class Restore methods + int GetBuildNumber() const { return version; } + +private: + idFile * file; + idFile * stringFile; + idList objects; + int version; + int stringTableOffset; + + void CallRestore_r( const idTypeInfo *cls, idClass *obj ); +}; + +#endif /* !__SAVEGAME_H__*/ diff --git a/neo/d3xp/gamesys/SysCmds.cpp b/neo/d3xp/gamesys/SysCmds.cpp new file mode 100644 index 00000000..a8f2f86e --- /dev/null +++ b/neo/d3xp/gamesys/SysCmds.cpp @@ -0,0 +1,2397 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#include "TypeInfo.h" + +/* +================== +Cmd_GetFloatArg +================== +*/ +float Cmd_GetFloatArg( const idCmdArgs &args, int &argNum ) { + const char *value; + + value = args.Argv( argNum++ ); + return atof( value ); +} + +/* +=================== +Cmd_EntityList_f +=================== +*/ +void Cmd_EntityList_f( const idCmdArgs &args ) { + int e; + idEntity *check; + int count; + size_t size; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + size = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + + if ( !check ) { + continue; + } + + if ( !check->name.Filter( match, true ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s %-20s %s\n", e, + check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + + count++; + size += check->spawnArgs.Allocated(); + } + + gameLocal.Printf( "...%d entities\n...%d bytes of spawnargs\n", count, size ); +} + +/* +=================== +Cmd_ActiveEntityList_f +=================== +*/ +void Cmd_ActiveEntityList_f( const idCmdArgs &args ) { + idEntity *check; + int count; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( check = gameLocal.activeEntities.Next(); check != NULL; check = check->activeNode.Next() ) { + char dormant = check->fl.isDormant ? '-' : ' '; + gameLocal.Printf( "%4i:%c%-20s %-20s %s\n", check->entityNumber, dormant, check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + count++; + } + + gameLocal.Printf( "...%d active entities\n", count ); +} + +/* +=================== +Cmd_ListSpawnArgs_f +=================== +*/ +void Cmd_ListSpawnArgs_f( const idCmdArgs &args ) { + int i; + idEntity *ent; + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + for ( i = 0; i < ent->spawnArgs.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = ent->spawnArgs.GetKeyVal( i ); + gameLocal.Printf( "\"%s\" "S_COLOR_WHITE"\"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } +} + +/* +=================== +Cmd_ReloadScript_f +=================== +*/ +void Cmd_ReloadScript_f( const idCmdArgs &args ) { + // shutdown the map because entities may point to script objects + gameLocal.MapShutdown(); + + // recompile the scripts + gameLocal.program.Startup( SCRIPT_DEFAULT ); + + if ( fileSystem->ReadFile("doom_main.script", NULL) > 0 ) { + gameLocal.program.CompileFile( "doom_main.script" ); + gameLocal.program.FinishCompilation(); + } + + // error out so that the user can rerun the scripts + gameLocal.Error( "Exiting map to reload scripts" ); +} + +CONSOLE_COMMAND( reloadScript2, "Doesn't thow an error... Use this when switching game modes", 0 ) { + // shutdown the map because entities may point to script objects + gameLocal.MapShutdown(); + + // recompile the scripts + gameLocal.program.Startup( SCRIPT_DEFAULT ); + + if ( fileSystem->ReadFile("doom_main.script", NULL) > 0 ) { + gameLocal.program.CompileFile( "doom_main.script" ); + gameLocal.program.FinishCompilation(); + } +} + +/* +=================== +Cmd_Script_f +=================== +*/ +void Cmd_Script_f( const idCmdArgs &args ) { + const char * script; + idStr text; + idStr funcname; + static int funccount = 0; + idThread * thread; + const function_t *func; + idEntity *ent; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + sprintf( funcname, "ConsoleFunction_%d", funccount++ ); + + script = args.Args(); + sprintf( text, "void %s() {%s;}\n", funcname.c_str(), script ); + if ( gameLocal.program.CompileText( "console", text, true ) ) { + func = gameLocal.program.FindFunction( funcname ); + if ( func ) { + // set all the entity names in case the user named one in the script that wasn't referenced in the default script + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + gameLocal.program.SetEntity( ent->name, ent ); + } + + thread = new idThread( func ); + thread->Start(); + } + } +} + +/* +================== +KillEntities + +Kills all the entities of the given class in a level. +================== +*/ +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ) { + idEntity *ent; + idStrList ignore; + const char *name; + int i; + + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for( i = 1; i < args.Argc(); i++ ) { + name = args.Argv( i ); + ignore.Append( name ); + } + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( superClass ) ) { + for( i = 0; i < ignore.Num(); i++ ) { + if ( ignore[ i ] == ent->name ) { + break; + } + } + + if ( i >= ignore.Num() ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + } +} + +/* +================== +Cmd_KillMonsters_f + +Kills all the monsters in a level. +================== +*/ +void Cmd_KillMonsters_f( const idCmdArgs &args ) { + KillEntities( args, idAI::Type ); + + // kill any projectiles as well since they have pointers to the monster that created them + KillEntities( args, idProjectile::Type ); +} + +/* +================== +Cmd_KillMovables_f + +Kills all the moveables in a level. +================== +*/ +void Cmd_KillMovables_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + KillEntities( args, idMoveable::Type ); +} + +/* +================== +Cmd_KillRagdolls_f + +Kills all the ragdolls in a level. +================== +*/ +void Cmd_KillRagdolls_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + KillEntities( args, idAFEntity_Generic::Type ); + KillEntities( args, idAFEntity_WithAttachedHead::Type ); +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( const idCmdArgs &args ) { + const char *name; + int i; + bool give_all; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + name = args.Argv( 1 ); + + if ( idStr::Icmp( name, "all" ) == 0 ) { + give_all = true; + } else { + give_all = false; + } + + if ( give_all || ( idStr::Cmpn( name, "weapon", 6 ) == 0 ) ) { + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", false ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, gameLocal.entities[ i ]->spawnArgs.GetString( "def_weapon1" ) ); + } + } + } + } + + if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) ) { + player->GiveItem( name ); + return; + } + + if ( give_all || idStr::Icmp( name, "health" ) == 0 ) { + player->health = player->inventory.maxHealth; + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "weapons" ) == 0 ) { + player->inventory.weapons = (int)( BIT( MAX_WEAPONS ) - 1 ); + player->CacheWeapons(); + + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "ammo" ) == 0 ) { + for ( i = 0 ; i < AMMO_NUMTYPES; i++ ) { + player->inventory.SetInventoryAmmoForType( i, player->inventory.MaxAmmoForAmmoClass( player, idWeapon::GetAmmoNameForNum( ( ammo_t )i ) ) ); + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "armor" ) == 0 ) { + player->inventory.armor = player->inventory.maxarmor; + if ( !give_all ) { + return; + } + } + + if ( idStr::Icmp( name, "berserk" ) == 0 ) { + player->GivePowerUp( BERSERK, SEC2MS( 30.0f ), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + return; + } + + if ( idStr::Icmp( name, "invis" ) == 0 ) { + player->GivePowerUp( INVISIBILITY, SEC2MS( 30.0f ), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + return; + } + + if ( idStr::Icmp( name, "invulnerability" ) == 0 ) { + if ( args.Argc() > 2 ) { + player->GivePowerUp( INVULNERABILITY, atoi( args.Argv( 2 ) ), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + else { + player->GivePowerUp( INVULNERABILITY, 30000, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + return; + } + + if ( idStr::Icmp( name, "helltime" ) == 0 ) { + if ( args.Argc() > 2 ) { + player->GivePowerUp( HELLTIME, atoi( args.Argv( 2 ) ), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + else { + player->GivePowerUp( HELLTIME, 30000, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + return; + } + + if ( idStr::Icmp( name, "envirosuit" ) == 0 ) { + if ( args.Argc() > 2 ) { + player->GivePowerUp( ENVIROSUIT, atoi( args.Argv( 2 ) ), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + else { + player->GivePowerUp( ENVIROSUIT, 30000, ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ); + } + return; + } + if ( idStr::Icmp( name, "pda" ) == 0 ) { + if ( args.Argc() == 2 ) { + player->GivePDA( NULL, NULL ); + } else if ( idStr::Icmp( args.Argv(2), "all" ) == 0 ) { + // Give the personal PDA first + player->GivePDA( NULL, NULL ); + for ( int i = 0; i < declManager->GetNumDecls( DECL_PDA ); i++ ) { + player->GivePDA( static_cast( declManager->DeclByIndex( DECL_PDA, i ) ), NULL ); + } + } else { + const idDeclPDA * pda = static_cast( declManager->FindType( DECL_PDA, args.Argv(2), false ) ); + if ( pda == NULL ) { + gameLocal.Printf( "Unknown PDA %s\n", args.Argv(2) ); + } else { + player->GivePDA( pda, NULL ); + } + } + return; + } + + if ( idStr::Icmp( name, "video" ) == 0 ) { + const idDeclVideo * video = static_cast( declManager->FindType( DECL_VIDEO, args.Argv(2), false ) ); + if ( video == NULL ) { + gameLocal.Printf( "Unknown video %s\n", args.Argv(2) ); + } else { + player->GiveVideo( video, NULL ); + } + return; + } + + if ( !give_all && !player->Give( args.Argv(1), args.Argv(2), ITEM_GIVE_FEEDBACK | ITEM_GIVE_UPDATE_STATE ) ) { + gameLocal.Printf( "unknown item\n" ); + } +} + +/* +================== +Cmd_CenterView_f + +Centers the players pitch +================== +*/ +void Cmd_CenterView_f( const idCmdArgs &args ) { + idPlayer *player; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + ang = player->viewAngles; + ang.pitch = 0.0f; + player->SetViewAngles( ang ); +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->godmode ) { + player->godmode = false; + msg = "godmode OFF\n"; + } else { + player->godmode = true; + msg = "godmode ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->fl.notarget ) { + player->fl.notarget = false; + msg = "notarget OFF\n"; + } else { + player->fl.notarget = true; + msg = "notarget ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + player->noclip = !player->noclip; + + gameLocal.Printf( "%s", msg ); +} + +/* +================= +Cmd_PlayerModel_f +================= +*/ +void Cmd_PlayerModel_f( const idCmdArgs &args ) { + idPlayer *player; + const char *name; + idVec3 pos; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: playerModel \n" ); + return; + } + + name = args.Argv( 1 ); + player->spawnArgs.Set( "model", name ); + + pos = player->GetPhysics()->GetOrigin(); + ang = player->viewAngles; + player->SpawnToPoint( pos, ang ); +} + +/* +================== +Cmd_Say +================== +*/ +static void Cmd_Say( bool team, const idCmdArgs &args ) { + const char *cmd = team ? "sayTeam" : "say" ; + + if ( !common->IsMultiplayer() ) { + gameLocal.Printf( "%s can only be used in a multiplayer game\n", cmd ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + idStr text = args.Args(); + if ( text.Length() == 0 ) { + return; + } + + if ( text[ text.Length() - 1 ] == '\n' ) { + text[ text.Length() - 1 ] = '\0'; + } + const char * name = "player"; + + // "server" will only appear on a dedicated server + if ( common->IsServer() && gameLocal.GetLocalClientNum() == -1 ) { + name = "server"; + } else { + name = session->GetActingGameStateLobbyBase().GetLobbyUserName( gameLocal.lobbyUserIDs[ gameLocal.GetLocalClientNum() ] ); + + // Append the player's location to team chat messages in CTF + idPlayer * player = static_cast( gameLocal.entities[ gameLocal.GetLocalClientNum() ] ); + if ( gameLocal.mpGame.IsGametypeFlagBased() && team && player ) { + idLocationEntity *locationEntity = gameLocal.LocationForPoint( player->GetEyePosition() ); + + if ( locationEntity ) { + idStr temp = "["; + temp += locationEntity->GetLocation(); + temp += "] "; + temp += text; + text = temp; + } + + } + } + + if ( common->IsClient() ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + outMsg.InitWrite( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteString( name ); + outMsg.WriteString( text, -1, false ); + session->GetActingGameStateLobbyBase().SendReliableToHost( team ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT, outMsg ); + } else { + gameLocal.mpGame.ProcessChatMessage( gameLocal.GetLocalClientNum(), team, name, text, NULL ); + } +} + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( const idCmdArgs &args ) { + Cmd_Say( false, args ); +} + +/* +================== +Cmd_SayTeam_f +================== +*/ +static void Cmd_SayTeam_f( const idCmdArgs &args ) { + Cmd_Say( true, args ); +} + +/* +================== +Cmd_AddChatLine_f +================== +*/ +static void Cmd_AddChatLine_f( const idCmdArgs &args ) { + gameLocal.mpGame.AddChatLine( args.Argv( 1 ) ); +} + +/* +================== +Cmd_GetViewpos_f +================== +*/ +void Cmd_GetViewpos_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( view ) { + gameLocal.Printf( "(%s) %.1f\n", view->vieworg.ToString(), view->viewaxis[0].ToYaw() ); + } else { + player->GetViewPos( origin, axis ); + gameLocal.Printf( "(%s) %.1f\n", origin.ToString(), axis[0].ToYaw() ); + } +} + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + int i; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( ( args.Argc() != 4 ) && ( args.Argc() != 5 ) ) { + gameLocal.Printf( "usage: setviewpos \n" ); + return; + } + + angles.Zero(); + if ( args.Argc() == 5 ) { + angles.yaw = atof( args.Argv( 4 ) ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + origin[i] = atof( args.Argv( i + 1 ) ); + } + origin.z -= pm_normalviewheight.GetFloat() - 0.25f; + + player->Teleport( origin, angles, NULL ); +} + +/* +================= +Cmd_Teleport_f +================= +*/ +void Cmd_Teleport_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: teleport \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + angles.Zero(); + angles.yaw = ent->GetPhysics()->GetAxis()[ 0 ].ToYaw(); + origin = ent->GetPhysics()->GetOrigin(); + + player->Teleport( origin, angles, ent ); +} + +/* +================= +Cmd_Trigger_f +================= +*/ +void Cmd_Trigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); +} + +/* +=================== +Cmd_Spawn_f +=================== +*/ +void Cmd_Spawn_f( const idCmdArgs &args ) { + const char *key, *value; + int i; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname [key/value pairs]\n" ); + return; + } + + yaw = player->viewAngles.yaw; + + value = args.Argv( 1 ); + dict.Set( "classname", value ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + gameLocal.SpawnEntityDef( dict ); +} + +/* +================== +Cmd_Damage_f + +Damages the specified entity +================== +*/ +void Cmd_Damage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: damage \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT ); +} + + +/* +================== +Cmd_Remove_f + +Removes the specified entity +================== +*/ +void Cmd_Remove_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: remove \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + delete ent; +} + +/* +=================== +Cmd_TestLight_f +=================== +*/ +void Cmd_TestLight_f( const idCmdArgs &args ) { + int i; + idStr filename; + const char *key = NULL, *value = NULL, *name = NULL; + idPlayer * player = NULL; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + renderView_t *rv = player->GetRenderView(); + + float fov = tan( idMath::M_DEG2RAD * rv->fov_x / 2 ); + + + dict.SetMatrix( "rotation", mat3_default ); + dict.SetVector( "origin", rv->vieworg ); + dict.SetVector( "light_target", rv->viewaxis[0] ); + dict.SetVector( "light_right", rv->viewaxis[1] * -fov ); + dict.SetVector( "light_up", rv->viewaxis[2] * fov ); + dict.SetVector( "light_start", rv->viewaxis[0] * 16 ); + dict.SetVector( "light_end", rv->viewaxis[0] * 1000 ); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + filename = args.Argv(1); + filename.DefaultFileExtension( ".tga" ); + dict.Set( "texture", filename ); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "spawned_light_%d", i ); // not just light_, or it might pick up a prelight shadow + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new light\n"); +} + +/* +=================== +Cmd_TestPointLight_f +=================== +*/ +void Cmd_TestPointLight_f( const idCmdArgs &args ) { + const char *key = NULL, *value = NULL, *name = NULL; + int i; + idPlayer *player = NULL; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + dict.SetVector("origin", player->GetRenderView()->vieworg); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + dict.Set("light", value); + } else { + dict.Set("light", "300"); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "light_%d", i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new point light\n"); +} + +/* +================== +Cmd_PopLight_f +================== +*/ +void Cmd_PopLight_f( const idCmdArgs &args ) { + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idLight *lastLight; + int last; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + bool removeFromMap = ( args.Argc() > 1 ); + + lastLight = NULL; + last = -1; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idLight::Type ) ) { + continue; + } + + if ( gameLocal.spawnIds[ ent->entityNumber ] > last ) { + last = gameLocal.spawnIds[ ent->entityNumber ]; + lastLight = static_cast( ent ); + } + } + + if ( lastLight ) { + // find map file entity + mapEnt = mapFile->FindEntity( lastLight->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + gameLocal.Printf( "Removing light %i\n", lastLight->GetLightDefHandle() ); + delete lastLight; + } else { + gameLocal.Printf( "No lights to clear.\n" ); + } + +} + +/* +==================== +Cmd_ClearLights_f +==================== +*/ +void Cmd_ClearLights_f( const idCmdArgs &args ) { + idEntity *ent; + idEntity *next; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + + bool removeFromMap = ( args.Argc() > 1 ); + + gameLocal.Printf( "Clearing all lights.\n" ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); + if ( !ent->IsType( idLight::Type ) ) { + continue; + } + + light = static_cast( ent ); + mapEnt = mapFile->FindEntity( light->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + + delete light; + } +} + +/* +================== +Cmd_TestFx_f +================== +*/ +void Cmd_TestFx_f( const idCmdArgs &args ) { + idVec3 offset; + const char *name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testFx ) { + delete gameLocal.testFx; + gameLocal.testFx = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "test", "1"); + dict.Set( "fx", name ); + gameLocal.testFx = ( idEntityFx * )gameLocal.SpawnEntityType( idEntityFx::Type, &dict ); +} + +#define MAX_DEBUGLINES 128 + +typedef struct { + bool used; + idVec3 start, end; + int color; + bool blink; + bool arrow; +} gameDebugLine_t; + +gameDebugLine_t debugLines[MAX_DEBUGLINES]; + +/* +================== +Cmd_AddDebugLine_f +================== +*/ +static void Cmd_AddDebugLine_f( const idCmdArgs &args ) { + int i, argNum; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 7 ) { + gameLocal.Printf( "usage: addline \n" ); + return; + } + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( !debugLines[i].used ) { + break; + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "no free debug lines\n" ); + return; + } + value = args.Argv( 0 ); + if ( !idStr::Icmp( value, "addarrow" ) ) { + debugLines[i].arrow = true; + } else { + debugLines[i].arrow = false; + } + debugLines[i].used = true; + debugLines[i].blink = false; + argNum = 1; + debugLines[i].start.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].color = Cmd_GetFloatArg( args, argNum ); +} + +/* +================== +Cmd_RemoveDebugLine_f +================== +*/ +static void Cmd_RemoveDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: removeline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi(value); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].used = false; +} + +/* +================== +Cmd_BlinkDebugLine_f +================== +*/ +static void Cmd_BlinkDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: blinkline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi( value ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].blink = !debugLines[i].blink; +} + +/* +================== +PrintFloat +================== +*/ +static void PrintFloat( float f ) { + char buf[128], i; + + for ( i = sprintf( buf, "%3.2f", f ); i < 7; i++ ) { + buf[i] = ' '; + } + buf[i] = '\0'; + gameLocal.Printf( buf ); +} + +/* +================== +Cmd_ListDebugLines_f +================== +*/ +static void Cmd_ListDebugLines_f( const idCmdArgs &args ) { + int i, num; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + num = 0; + gameLocal.Printf( "line num: x1 y1 z1 x2 y2 z2 c b a\n" ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + gameLocal.Printf( "line %3d: ", num ); + PrintFloat( debugLines[i].start.x ); + PrintFloat( debugLines[i].start.y ); + PrintFloat( debugLines[i].start.z ); + PrintFloat( debugLines[i].end.x ); + PrintFloat( debugLines[i].end.y ); + PrintFloat( debugLines[i].end.z ); + gameLocal.Printf( "%d %d %d\n", debugLines[i].color, debugLines[i].blink, debugLines[i].arrow ); + num++; + } + } + if ( !num ) { + gameLocal.Printf( "no debug lines\n" ); + } +} + +/* +================== +D_DrawDebugLines +================== +*/ +void D_DrawDebugLines() { + int i; + idVec3 forward, right, up, p1, p2; + idVec4 color; + float l; + + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( !debugLines[i].blink || (gameLocal.time & (1<<9)) ) { + color = idVec4( debugLines[i].color&1, (debugLines[i].color>>1)&1, (debugLines[i].color>>2)&1, 1 ); + gameRenderWorld->DebugLine( color, debugLines[i].start, debugLines[i].end ); + // + if ( debugLines[i].arrow ) { + // draw a nice arrow + forward = debugLines[i].end - debugLines[i].start; + l = forward.Normalize() * 0.2f; + forward.NormalVectors( right, up); + + if ( l > 3.0f ) { + l = 3.0f; + } + p1 = debugLines[i].end - l * forward + (l * 0.4f) * right; + p2 = debugLines[i].end - l * forward - (l * 0.4f) * right; + gameRenderWorld->DebugLine( color, debugLines[i].end, p1 ); + gameRenderWorld->DebugLine( color, debugLines[i].end, p2 ); + gameRenderWorld->DebugLine( color, p1, p2 ); + } + } + } + } +} + +/* +================== +Cmd_ListCollisionModels_f +================== +*/ +static void Cmd_ListCollisionModels_f( const idCmdArgs &args ) { + if ( !gameLocal.CheatsOk() ) { + return; + } + + collisionModelManager->ListModels(); +} + +/* +================== +Cmd_CollisionModelInfo_f +================== +*/ +static void Cmd_CollisionModelInfo_f( const idCmdArgs &args ) { + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: collisionModelInfo \n" + "use 'all' instead of the model number for accumulated info\n" ); + return; + } + + value = args.Argv( 1 ); + if ( !idStr::Icmp( value, "all" ) ) { + collisionModelManager->ModelInfo( -1 ); + } else { + collisionModelManager->ModelInfo( atoi(value) ); + } +} + +/* +================== +Cmd_ReloadAnims_f +================== +*/ +static void Cmd_ReloadAnims_f( const idCmdArgs &args ) { + // don't allow reloading anims when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + animationLib.ReloadAnims(); +} + +/* +================== +Cmd_ListAnims_f +================== +*/ +static void Cmd_ListAnims_f( const idCmdArgs &args ) { + idEntity * ent; + int num; + size_t size; + size_t alloced; + idAnimator * animator; + const char * classname; + const idDict * dict; + int i; + + if ( args.Argc() > 1 ) { + idAnimator animator; + + classname = args.Argv( 1 ); + + dict = gameLocal.FindEntityDefDict( classname, false ); + if ( !dict ) { + gameLocal.Printf( "Entitydef '%s' not found\n", classname ); + return; + } + animator.SetModel( dict->GetString( "model" ) ); + + gameLocal.Printf( "----------------\n" ); + num = animator.NumAnims(); + for( i = 0; i < num; i++ ) { + gameLocal.Printf( "%s\n", animator.AnimFullName( i ) ); + } + gameLocal.Printf( "%d anims\n", num ); + } else { + animationLib.ListAnims(); + + size = 0; + num = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + animator = ent->GetAnimator(); + if ( animator ) { + alloced = animator->Allocated(); + size += alloced; + num++; + } + } + + gameLocal.Printf( "%d memory used in %d entity animators\n", size, num ); + } +} + +/* +================== +Cmd_AASStats_f +================== +*/ +static void Cmd_AASStats_f( const idCmdArgs &args ) { + int aasNum; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + aasNum = aas_test.GetInteger(); + idAAS *aas = gameLocal.GetAAS( aasNum ); + if ( !aas ) { + gameLocal.Printf( "No aas #%d loaded\n", aasNum ); + } else { + aas->Stats(); + } +} + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDamage_f( const idCmdArgs &args ) { + idPlayer *player; + const char *damageDefName; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + gameLocal.Printf( "usage: testDamage [angle]\n" ); + return; + } + + damageDefName = args.Argv( 1 ); + + idVec3 dir; + if ( args.Argc() == 3 ) { + float angle = atof( args.Argv( 2 ) ); + + idMath::SinCos( DEG2RAD( angle ), dir[1], dir[0] ); + dir[2] = 0; + } else { + dir.Zero(); + } + + // give the player full health before and after + // running the damage + player->health = player->inventory.maxHealth; + player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT ); + player->health = player->inventory.maxHealth; +} + +/* +================== +Cmd_TestBoneFx_f +================== +*/ +static void Cmd_TestBoneFx_f( const idCmdArgs &args ) { + idPlayer *player; + const char *bone, *fx; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 3 || args.Argc() > 4 ) { + gameLocal.Printf( "usage: testBoneFx \n" ); + return; + } + + fx = args.Argv( 1 ); + bone = args.Argv( 2 ); + + player->StartFxOnBone( fx, bone ); +} + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDeath_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + idVec3 dir; + idMath::SinCos( DEG2RAD( 45.0f ), dir[1], dir[0] ); + dir[2] = 0; + + g_testDeath.SetBool( 1 ); + player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT ); + if ( args.Argc() >= 2) { + player->SpawnGibs( dir, "damage_triggerhurt_1000" ); + } + +} + +/* +================== +Cmd_WeaponSplat_f +================== +*/ +static void Cmd_WeaponSplat_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->weapon.GetEntity()->BloodSplat( 2.0f ); +} + +/* +================== +Cmd_SaveSelected_f +================== +*/ +static void Cmd_SaveSelected_f( const idCmdArgs &args ) { + int i; + idPlayer *player = NULL; + idEntity *s = NULL; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + s = player->dragEntity.GetSelected(); + if ( !s ) { + gameLocal.Printf( "no entity selected, set g_dragShowSelection 1 to show the current selection\n" ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + // find map file entity + mapEnt = mapFile->FindEntity( s->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new (TAG_SYSTEM) idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", s->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + s->name = name; + mapEnt->epairs.Set( "classname", s->GetEntityDefName() ); + mapEnt->epairs.Set( "name", s->name ); + } + + if ( s->IsType( idMoveable::Type ) ) { + // save the moveable state + mapEnt->epairs.Set( "origin", s->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", s->GetPhysics()->GetAxis().ToString( 8 ) ); + } + else if ( s->IsType( idAFEntity_Generic::Type ) || s->IsType( idAFEntity_WithAttachedHead::Type ) ) { + // save the articulated figure state + dict.Clear(); + static_cast(s)->SaveState( dict ); + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_DeleteSelected_f +================== +*/ +static void Cmd_DeleteSelected_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.DeleteSelected(); + } +} + +/* +================== +Cmd_SaveMoveables_f +================== +*/ +static void Cmd_SaveMoveables_f( const idCmdArgs &args ) { + int e, i; + idMoveable *m = NULL; + idMapEntity *mapEnt = NULL; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + + if ( !m || !m->IsType( idMoveable::Type ) ) { + continue; + } + + if ( m->IsBound() ) { + continue; + } + + if ( !m->IsAtRest() ) { + break; + } + } + + if ( e < MAX_GENTITIES ) { + gameLocal.Warning( "map not saved because the moveable entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + + if ( !m || !m->IsType( idMoveable::Type ) ) { + continue; + } + + if ( m->IsBound() ) { + continue; + } + + // find map file entity + mapEnt = mapFile->FindEntity( m->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new (TAG_SYSTEM) idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", m->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + m->name = name; + mapEnt->epairs.Set( "classname", m->GetEntityDefName() ); + mapEnt->epairs.Set( "name", m->name ); + } + // save the moveable state + mapEnt->epairs.Set( "origin", m->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", m->GetPhysics()->GetAxis().ToString( 8 ) ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_SaveRagdolls_f +================== +*/ +static void Cmd_SaveRagdolls_f( const idCmdArgs &args ) { + int e, i; + idAFEntity_Base *af = NULL; + idMapEntity *mapEnt = NULL; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + af = static_cast(gameLocal.entities[ e ]); + + if ( !af ) { + continue; + } + + if ( !af->IsType( idAFEntity_WithAttachedHead::Type ) && !af->IsType( idAFEntity_Generic::Type ) ) { + continue; + } + + if ( af->IsBound() ) { + continue; + } + + if ( !af->IsAtRest() ) { + gameLocal.Warning( "the articulated figure for entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + } + + dict.Clear(); + af->SaveState( dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( af->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new (TAG_SYSTEM) idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", af->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + af->name = name; + mapEnt->epairs.Set( "classname", af->GetEntityDefName() ); + mapEnt->epairs.Set( "name", af->name ); + } + // save the articulated figure state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_BindRagdoll_f +================== +*/ +static void Cmd_BindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.BindSelected(); + } +} + +/* +================== +Cmd_UnbindRagdoll_f +================== +*/ +static void Cmd_UnbindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.UnbindSelected(); + } +} + +/* +================== +Cmd_GameError_f +================== +*/ +static void Cmd_GameError_f( const idCmdArgs &args ) { + gameLocal.Error( "game error" ); +} + +/* +================== +Cmd_SaveLights_f +================== +*/ +static void Cmd_SaveLights_f( const idCmdArgs &args ) { + int e, i; + idLight *light = NULL; + idMapEntity *mapEnt = NULL; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + light = static_cast(gameLocal.entities[ e ]); + + if ( !light || !light->IsType( idLight::Type ) ) { + continue; + } + + dict.Clear(); + light->SaveState( &dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( light->name ); + // create new map file entity if there isn't one for this light + if ( !mapEnt ) { + mapEnt = new (TAG_SYSTEM) idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", light->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + light->name = name; + mapEnt->epairs.Set( "classname", light->GetEntityDefName() ); + mapEnt->epairs.Set( "name", light->name ); + } + // save the light state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_SaveParticles_f +================== +*/ +static void Cmd_SaveParticles_f( const idCmdArgs &args ) { + int e; + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName, strModel; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + + ent = static_cast ( gameLocal.entities[ e ] ); + + if ( !ent ) { + continue; + } + + strModel = ent->spawnArgs.GetString( "model" ); + if ( strModel.Length() && strModel.Find( ".prt") > 0 ) { + dict.Clear(); + dict.Set( "model", ent->spawnArgs.GetString( "model" ) ); + dict.SetVector( "origin", ent->GetPhysics()->GetOrigin() ); + + // find map file entity + mapEnt = mapFile->FindEntity( ent->name ); + // create new map file entity if there isn't one for this entity + if ( !mapEnt ) { + continue; + } + // save the particle state + mapEnt->epairs.Copy( dict ); + } + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_DisasmScript_f +================== +*/ +static void Cmd_DisasmScript_f( const idCmdArgs &args ) { + gameLocal.program.Disassemble(); +} + +/* +================== +Cmd_TestSave_f +================== +*/ +static void Cmd_TestSave_f( const idCmdArgs &args ) { + idFile *f, *strings; + + f = fileSystem->OpenFileWrite( "test.sav" ); + strings = NULL; + gameLocal.SaveGame( f, strings ); + fileSystem->CloseFile( f ); +} + +/* +================== +Cmd_RecordViewNotes_f +================== +*/ +static void Cmd_RecordViewNotes_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + if ( args.Argc() <= 3 ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + player->GetViewPos( origin, axis ); + + // Argv(1) = filename for map (viewnotes/mapname/person) + // Argv(2) = note number (person0001) + // Argv(3) = comments + + idStr str = args.Argv(1); + str.SetFileExtension( ".txt" ); + + idFile *file = fileSystem->OpenFileAppend( str ); + + if ( file ) { + file->WriteFloatString( "\"view\"\t( %s )\t( %s )\r\n", origin.ToString(), axis.ToString() ); + file->WriteFloatString( "\"comments\"\t\"%s: %s\"\r\n\r\n", args.Argv(2), args.Argv(3) ); + fileSystem->CloseFile( file ); + } + + idStr viewComments = args.Argv(1); + viewComments.StripLeading("viewnotes/"); + viewComments += " -- Loc: "; + viewComments += origin.ToString(); + viewComments += "\n"; + viewComments += args.Argv(3); + + // TODO_SPARTY: removed old hud need to find a way of doing this with the new hud + //player->hud->SetStateString( "viewcomments", viewComments ); + //player->hud->HandleNamedEvent( "showViewComments" ); +} + +/* +================== +Cmd_CloseViewNotes_f +================== +*/ +static void Cmd_CloseViewNotes_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + // TODO_SPARTY: removed old hud need to find a way of doing this with the new hud + //player->hud->SetStateString( "viewcomments", "" ); + //player->hud->HandleNamedEvent( "hideViewComments" ); +} + +/* +================== +Cmd_ShowViewNotes_f +================== +*/ +static void Cmd_ShowViewNotes_f( const idCmdArgs &args ) { + static idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_NOFATALERRORS ); + idToken token; + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( !parser.IsLoaded() ) { + idStr str = "viewnotes/"; + str += gameLocal.GetMapName(); + str.StripFileExtension(); + str += "/"; + if ( args.Argc() > 1 ) { + str += args.Argv( 1 ); + } else { + str += "comments"; + } + str.SetFileExtension( ".txt" ); + if ( !parser.LoadFile( str ) ) { + gameLocal.Printf( "No view notes for %s\n", gameLocal.GetMapName() ); + return; + } + } + + if ( parser.ExpectTokenString( "view" ) && parser.Parse1DMatrix( 3, origin.ToFloatPtr() ) && + parser.Parse1DMatrix( 9, axis.ToFloatPtr() ) && parser.ExpectTokenString( "comments" ) && parser.ReadToken( &token ) ) { + + // TODO_SPARTY: removed old hud need to find a way of doing this with the new hud + //player->hud->SetStateString( "viewcomments", token ); + //player->hud->HandleNamedEvent( "showViewComments" ); + player->Teleport( origin, axis.ToAngles(), NULL ); + } else { + parser.FreeSource(); + // TODO_SPARTY: removed old hud need to find a way of doing this with the new hud + //player->hud->HandleNamedEvent( "hideViewComments" ); + return; + } +} + +/* +================= +FindEntityGUIs + +helper function for Cmd_NextGUI_f. Checks the passed entity to determine if it +has any valid gui surfaces. +================= +*/ +bool FindEntityGUIs( idEntity *ent, const modelSurface_t ** surfaces, int maxSurfs, int &guiSurfaces ) { + renderEntity_t *renderEnt; + idRenderModel *renderModel; + const modelSurface_t *surf; + const idMaterial *shader; + int i; + + assert( surfaces != NULL ); + assert( ent != NULL ); + + memset( surfaces, 0x00, sizeof( modelSurface_t *) * maxSurfs ); + guiSurfaces = 0; + + renderEnt = ent->GetRenderEntity(); + renderModel = renderEnt->hModel; + if ( renderModel == NULL ) { + return false; + } + + for( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf == NULL ) { + continue; + } + shader = surf->shader; + if ( shader == NULL ) { + continue; + } + if ( shader->GetEntityGui() > 0 ) { + surfaces[ guiSurfaces++ ] = surf; + } + } + + return ( guiSurfaces != 0 ); +} + +/* +================= +Cmd_NextGUI_f +================= +*/ +void Cmd_NextGUI_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + int guiSurfaces; + bool newEnt; + renderEntity_t *renderEnt; + int surfIndex; + srfTriangles_t *geom; + idVec3 normal; + idVec3 center; + const modelSurface_t *surfaces[ MAX_RENDERENTITY_GUI ]; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 1 ) { + gameLocal.Printf( "usage: nextgui\n" ); + return; + } + + // start at the last entity + ent = gameLocal.lastGUIEnt.GetEntity(); + + // see if we have any gui surfaces left to go to on the current entity. + guiSurfaces = 0; + newEnt = false; + if ( ent == NULL ) { + newEnt = true; + } else if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == true ) { + if ( gameLocal.lastGUI >= guiSurfaces ) { + newEnt = true; + } + } else { + // no actual gui surfaces on this ent, so skip it + newEnt = true; + } + + if ( newEnt == true ) { + // go ahead and skip to the next entity with a gui... + if ( ent == NULL ) { + ent = gameLocal.spawnedEntities.Next(); + } else { + ent = ent->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->spawnArgs.GetString( "gui", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui2", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui3", NULL ) != NULL ) { + break; + } + + // try the next entity + gameLocal.lastGUIEnt = ent; + } + + gameLocal.lastGUIEnt = ent; + gameLocal.lastGUI = 0; + + if ( !ent ) { + gameLocal.Printf( "No more gui entities. Starting over...\n" ); + return; + } + } + + if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == false ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces.\n", ent->name.c_str() ); + } + + if ( guiSurfaces == 0 ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces!\n", ent->name.c_str() ); + return; + } + + gameLocal.Printf( "Teleporting to gui entity \"%s\", gui #%d.\n" , ent->name.c_str (), gameLocal.lastGUI ); + + renderEnt = ent->GetRenderEntity(); + surfIndex = gameLocal.lastGUI++; + geom = surfaces[ surfIndex ]->geometry; + if ( geom == NULL ) { + gameLocal.Printf( "Entity \"%s\" has gui surface %d without geometry!\n", ent->name.c_str(), surfIndex ); + return; + } + + const idVec3 & v0 = geom->verts[geom->indexes[0]].xyz; + const idVec3 & v1 = geom->verts[geom->indexes[1]].xyz; + const idVec3 & v2 = geom->verts[geom->indexes[2]].xyz; + + const idPlane plane( v0, v1, v2 ); + + normal = plane.Normal() * renderEnt->axis; + center = geom->bounds.GetCenter() * renderEnt->axis + renderEnt->origin; + + origin = center + (normal * 32.0f); + origin.z -= player->EyeHeight(); + normal *= -1.0f; + angles = normal.ToAngles (); + + // make sure the player is in noclip + player->noclip = true; + player->Teleport( origin, angles, NULL ); +} + +void Cmd_SetActorState_f( const idCmdArgs &args ) { + + if ( args.Argc() != 3 ) { + common->Printf( "usage: setActorState \n" ); + return; + } + + idEntity* ent; + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + + if(!ent->IsType(idActor::Type)) { + gameLocal.Printf( "entity not an actor\n" ); + return; + } + + idActor* actor = (idActor*)ent; + actor->PostEventMS(&AI_SetState, 0, args.Argv(2)); +} + +#if 0 +// not used +static void ArgCompletion_DefFile( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "def/", true, ".def", NULL ); +} +#endif + +/* +=============== +Cmd_TestId_f +outputs a string from the string table for the specified id +=============== +*/ +void Cmd_TestId_f( const idCmdArgs &args ) { + idStr id; + int i; + if ( args.Argc() == 1 ) { + common->Printf( "usage: testid \n" ); + return; + } + + for ( i = 1; i < args.Argc(); i++ ) { + id += args.Argv( i ); + } + if ( idStr::Cmpn( id, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + id = STRTABLE_ID + id; + } + gameLocal.mpGame.AddChatLine( idLocalization::GetString( id ), "", "", "" ); +} + + +/* +================= +idGameLocal::InitConsoleCommands + +Let the system know about all of our commands +so it can perform tab completion +================= +*/ +void idGameLocal::InitConsoleCommands() { + cmdSystem->AddCommand( "game_memory", idClass::DisplayInfo_f, CMD_FL_GAME, "displays game class info" ); + cmdSystem->AddCommand( "listClasses", idClass::ListClasses_f, CMD_FL_GAME, "lists game classes" ); + cmdSystem->AddCommand( "listThreads", idThread::ListThreads_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists script threads" ); + cmdSystem->AddCommand( "listEntities", Cmd_EntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists game entities" ); + cmdSystem->AddCommand( "listActiveEntities", Cmd_ActiveEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists active game entities" ); + cmdSystem->AddCommand( "listMonsters", idAI::List_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists monsters" ); + cmdSystem->AddCommand( "listSpawnArgs", Cmd_ListSpawnArgs_f, CMD_FL_GAME|CMD_FL_CHEAT, "list the spawn args of an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "say", Cmd_Say_f, CMD_FL_GAME, "text chat" ); + cmdSystem->AddCommand( "sayTeam", Cmd_SayTeam_f, CMD_FL_GAME, "team text chat" ); + cmdSystem->AddCommand( "addChatLine", Cmd_AddChatLine_f, CMD_FL_GAME, "internal use - core to game chat lines" ); + cmdSystem->AddCommand( "give", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); + cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); + cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + cmdSystem->AddCommand( "noclip", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "where", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "getviewpos", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "setviewpos", Cmd_SetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the current view position" ); + cmdSystem->AddCommand( "teleport", Cmd_Teleport_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleports the player to an entity location", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "trigger", Cmd_Trigger_f, CMD_FL_GAME|CMD_FL_CHEAT, "triggers an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "spawn", Cmd_Spawn_f, CMD_FL_GAME|CMD_FL_CHEAT, "spawns a game entity", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "damage", Cmd_Damage_f, CMD_FL_GAME|CMD_FL_CHEAT, "apply damage to an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "remove", Cmd_Remove_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "killMonsters", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all monsters" ); + cmdSystem->AddCommand( "killMoveables", Cmd_KillMovables_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all moveables" ); + cmdSystem->AddCommand( "killRagdolls", Cmd_KillRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all ragdolls" ); + cmdSystem->AddCommand( "addline", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug line" ); + cmdSystem->AddCommand( "addarrow", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug arrow" ); + cmdSystem->AddCommand( "removeline", Cmd_RemoveDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes a debug line" ); + cmdSystem->AddCommand( "blinkline", Cmd_BlinkDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "blinks a debug line" ); + cmdSystem->AddCommand( "listLines", Cmd_ListDebugLines_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists all debug lines" ); + cmdSystem->AddCommand( "playerModel", Cmd_PlayerModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the given model on the player", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testFx", Cmd_TestFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testBoneFx", Cmd_TestBoneFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system bound to a joint", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testLight", Cmd_TestLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a light" ); + cmdSystem->AddCommand( "testPointLight", Cmd_TestPointLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a point light" ); + cmdSystem->AddCommand( "popLight", Cmd_PopLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes the last created light" ); + cmdSystem->AddCommand( "testDeath", Cmd_TestDeath_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests death" ); + cmdSystem->AddCommand( "testSave", Cmd_TestSave_f, CMD_FL_GAME|CMD_FL_CHEAT, "writes out a test savegame" ); + cmdSystem->AddCommand( "testModel", idTestModel::TestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a model", idTestModel::ArgCompletion_TestModel ); + cmdSystem->AddCommand( "testSkin", idTestModel::TestSkin_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a skin on an existing testModel", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testShaderParm", idTestModel::TestShaderParm_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets a shaderParm on an existing testModel" ); + cmdSystem->AddCommand( "keepTestModel", idTestModel::KeepTestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "keeps the last test model in the game" ); + cmdSystem->AddCommand( "testAnim", idTestModel::TestAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an animation", idTestModel::ArgCompletion_TestAnim ); + cmdSystem->AddCommand( "testParticleStopTime", idTestModel::TestParticleStopTime_f,CMD_FL_GAME|CMD_FL_CHEAT, "tests particle stop time on a test model" ); + cmdSystem->AddCommand( "nextAnim", idTestModel::TestModelNextAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation on test model" ); + cmdSystem->AddCommand( "prevAnim", idTestModel::TestModelPrevAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation on test model" ); + cmdSystem->AddCommand( "nextFrame", idTestModel::TestModelNextFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation frame on test model" ); + cmdSystem->AddCommand( "prevFrame", idTestModel::TestModelPrevFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation frame on test model" ); + cmdSystem->AddCommand( "testBlend", idTestModel::TestBlend_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests animation blending" ); + cmdSystem->AddCommand( "reloadScript", Cmd_ReloadScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads scripts" ); + cmdSystem->AddCommand( "script", Cmd_Script_f, CMD_FL_GAME|CMD_FL_CHEAT, "executes a line of script" ); + cmdSystem->AddCommand( "listCollisionModels", Cmd_ListCollisionModels_f, CMD_FL_GAME, "lists collision models" ); + cmdSystem->AddCommand( "collisionModelInfo", Cmd_CollisionModelInfo_f, CMD_FL_GAME, "shows collision model info" ); + cmdSystem->AddCommand( "reloadanims", Cmd_ReloadAnims_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads animations" ); + cmdSystem->AddCommand( "listAnims", Cmd_ListAnims_f, CMD_FL_GAME, "lists all animations" ); + cmdSystem->AddCommand( "aasStats", Cmd_AASStats_f, CMD_FL_GAME, "shows AAS stats" ); + cmdSystem->AddCommand( "testDamage", Cmd_TestDamage_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a damage def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "weaponSplat", Cmd_WeaponSplat_f, CMD_FL_GAME|CMD_FL_CHEAT, "projects a blood splat on the player weapon" ); + cmdSystem->AddCommand( "saveSelected", Cmd_SaveSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves the selected entity to the .map file" ); + cmdSystem->AddCommand( "deleteSelected", Cmd_DeleteSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "deletes selected entity" ); + cmdSystem->AddCommand( "saveMoveables", Cmd_SaveMoveables_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all moveables to the .map file" ); + cmdSystem->AddCommand( "saveRagdolls", Cmd_SaveRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all ragdoll poses to the .map file" ); + cmdSystem->AddCommand( "bindRagdoll", Cmd_BindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "binds ragdoll at the current drag position" ); + cmdSystem->AddCommand( "unbindRagdoll", Cmd_UnbindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "unbinds the selected ragdoll" ); + cmdSystem->AddCommand( "saveLights", Cmd_SaveLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "saveParticles", Cmd_SaveParticles_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "clearLights", Cmd_ClearLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "clears all lights" ); + cmdSystem->AddCommand( "gameError", Cmd_GameError_f, CMD_FL_GAME|CMD_FL_CHEAT, "causes a game error" ); + + cmdSystem->AddCommand( "disasmScript", Cmd_DisasmScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "disassembles script" ); + cmdSystem->AddCommand( "recordViewNotes", Cmd_RecordViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "record the current view position with notes" ); + cmdSystem->AddCommand( "showViewNotes", Cmd_ShowViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "show any view notes for the current map, successive calls will cycle to the next note" ); + cmdSystem->AddCommand( "closeViewNotes", Cmd_CloseViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "close the view showing any notes for this map" ); + + // multiplayer client commands ( replaces old impulses stuff ) + //cmdSystem->AddCommand( "clientDropWeapon", idMultiplayerGame::DropWeapon_f, CMD_FL_GAME, "drop current weapon" ); + cmdSystem->AddCommand( "clientMessageMode", idMultiplayerGame::MessageMode_f, CMD_FL_GAME, "ingame gui message mode" ); + // FIXME: implement +// cmdSystem->AddCommand( "clientVote", idMultiplayerGame::Vote_f, CMD_FL_GAME, "cast your vote: clientVote yes | no" ); +// cmdSystem->AddCommand( "clientCallVote", idMultiplayerGame::CallVote_f, CMD_FL_GAME, "call a vote: clientCallVote si_.. proposed_value" ); + cmdSystem->AddCommand( "clientVoiceChat", idMultiplayerGame::VoiceChat_f, CMD_FL_GAME, "voice chats: clientVoiceChat " ); + cmdSystem->AddCommand( "clientVoiceChatTeam", idMultiplayerGame::VoiceChatTeam_f, CMD_FL_GAME, "team voice chats: clientVoiceChat " ); + + // multiplayer server commands + cmdSystem->AddCommand( "serverMapRestart", idGameLocal::MapRestart_f, CMD_FL_GAME, "restart the current game" ); + + // localization help commands + cmdSystem->AddCommand( "nextGUI", Cmd_NextGUI_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleport the player to the next func_static with a gui" ); + cmdSystem->AddCommand( "testid", Cmd_TestId_f, CMD_FL_GAME|CMD_FL_CHEAT, "output the string for the specified id." ); + + cmdSystem->AddCommand( "setActorState", Cmd_SetActorState_f, CMD_FL_GAME|CMD_FL_CHEAT, "Manually sets an actors script state", idGameLocal::ArgCompletion_EntityName ); +} + +/* +================= +idGameLocal::ShutdownConsoleCommands +================= +*/ +void idGameLocal::ShutdownConsoleCommands() { + cmdSystem->RemoveFlaggedCommands( CMD_FL_GAME ); +} diff --git a/neo/d3xp/gamesys/SysCmds.h b/neo/d3xp/gamesys/SysCmds.h new file mode 100644 index 00000000..f7144ae6 --- /dev/null +++ b/neo/d3xp/gamesys/SysCmds.h @@ -0,0 +1,34 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SYS_CMDS_H__ +#define __SYS_CMDS_H__ + +void D_DrawDebugLines(); + +#endif /* !__SYS_CMDS_H__ */ diff --git a/neo/d3xp/gamesys/SysCvar.cpp b/neo/d3xp/gamesys/SysCvar.cpp new file mode 100644 index 00000000..1694713a --- /dev/null +++ b/neo/d3xp/gamesys/SysCvar.cpp @@ -0,0 +1,323 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "-release" +#endif + +/* +All game cvars should be defined here. +*/ + +struct gameVersion_s { + gameVersion_s() { sprintf( string, "%s.%d%s %s %s %s", ENGINE_VERSION, BUILD_NUMBER, BUILD_DEBUG, BUILD_STRING, __DATE__, __TIME__ ); } + char string[256]; +} gameVersion; + + +// noset vars +idCVar gamename( "gamename", GAME_VERSION, CVAR_GAME | CVAR_ROM, "" ); +idCVar gamedate( "gamedate", __DATE__, CVAR_GAME | CVAR_ROM, "" ); + +idCVar si_map( "si_map", "-1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "default map choice for profile" ); +idCVar si_mode( "si_mode", "-1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "default mode choice for profile", -1, GAME_COUNT - 1 ); +idCVar si_fragLimit( "si_fragLimit", "10", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "frag limit", 1, MP_PLAYER_MAXFRAGS ); +idCVar si_timeLimit( "si_timeLimit", "10", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "time limit in minutes", 0, 60 ); +idCVar si_teamDamage( "si_teamDamage", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable team damage" ); +idCVar si_spectators( "si_spectators", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "allow spectators or require all clients to play" ); + +//idCVar si_pointLimit( "si_pointlimit", "8", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "team points limit to win in CTF" ); +idCVar si_flagDropTimeLimit( "si_flagDropTimeLimit", "30", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "seconds before a dropped CTF flag is returned" ); +idCVar si_midnight( "si_midnight", "0", CVAR_GAME | CVAR_INTEGER | CVAR_SERVERINFO, "Start the game up in midnight CTF (completely dark)" ); + +// change anytime vars +idCVar developer( "developer", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_cinematic( "g_cinematic", "1", CVAR_GAME | CVAR_BOOL, "skips updating entities that aren't marked 'cinematic' '1' during cinematics" ); +idCVar g_cinematicMaxSkipTime( "g_cinematicMaxSkipTime", "600", CVAR_GAME | CVAR_FLOAT, "# of seconds to allow game to run when skipping cinematic. prevents lock-up when cinematic doesn't end.", 0, 3600 ); + +idCVar g_muzzleFlash( "g_muzzleFlash", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show muzzle flashes" ); +idCVar g_projectileLights( "g_projectileLights", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show dynamic lights on projectiles" ); +idCVar g_bloodEffects( "g_bloodEffects", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show blood splats, sprays and gibs" ); +idCVar g_monsters( "g_monsters", "1", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_decals( "g_decals", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show decals such as bullet holes" ); +idCVar g_knockback( "g_knockback", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar g_skill( "g_skill", "1", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar g_nightmare( "g_nightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed" ); +idCVar g_roeNightmare( "g_roeNightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed for roe" ); +idCVar g_leNightmare( "g_leNightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed for le" ); +idCVar g_gravity( "g_gravity", DEFAULT_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_skipFX( "g_skipFX", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_disasm( "g_disasm", "0", CVAR_GAME | CVAR_BOOL, "disassemble script into base/script/disasm.txt on the local drive when script is compiled" ); +idCVar g_debugBounds( "g_debugBounds", "0", CVAR_GAME | CVAR_BOOL, "checks for models with bounds > 2048" ); +idCVar g_debugAnim( "g_debugAnim", "-1", CVAR_GAME | CVAR_INTEGER, "displays information on which animations are playing on the specified entity number. set to -1 to disable." ); +idCVar g_debugMove( "g_debugMove", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugDamage( "g_debugDamage", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugWeapon( "g_debugWeapon", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugScript( "g_debugScript", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugMover( "g_debugMover", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugTriggers( "g_debugTriggers", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugCinematic( "g_debugCinematic", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_stopTime( "g_stopTime", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_damageScale( "g_damageScale", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale final damage on player by this factor" ); +idCVar g_armorProtection( "g_armorProtection", "0.3", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "armor takes this percentage of damage" ); +idCVar g_armorProtectionMP( "g_armorProtectionMP", "0.6", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); +idCVar g_useDynamicProtection( "g_useDynamicProtection", "1", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "scale damage and armor dynamically to keep the player alive more often" ); +idCVar g_healthTakeTime( "g_healthTakeTime", "5", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "how often to take health in nightmare mode" ); +idCVar g_healthTakeAmt( "g_healthTakeAmt", "5", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "how much health to take in nightmare mode" ); +idCVar g_healthTakeLimit( "g_healthTakeLimit", "25", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "how low can health get taken in nightmare mode" ); + + + +idCVar g_showPVS( "g_showPVS", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +idCVar g_showTargets( "g_showTargets", "0", CVAR_GAME | CVAR_BOOL, "draws entities and thier targets. hidden entities are drawn grey." ); +idCVar g_showTriggers( "g_showTriggers", "0", CVAR_GAME | CVAR_BOOL, "draws trigger entities (orange) and thier targets (green). disabled triggers are drawn grey." ); +idCVar g_showCollisionWorld( "g_showCollisionWorld", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showCollisionModels( "g_showCollisionModels", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showCollisionTraces( "g_showCollisionTraces", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_maxShowDistance( "g_maxShowDistance", "128", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_showEntityInfo( "g_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showviewpos( "g_showviewpos", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showcamerainfo( "g_showcamerainfo", "0", CVAR_GAME | CVAR_ARCHIVE, "displays the current frame # for the camera when playing cinematics" ); +idCVar g_showTestModelFrame( "g_showTestModelFrame", "0", CVAR_GAME | CVAR_BOOL, "displays the current animation and frame # for testmodels" ); +idCVar g_showActiveEntities( "g_showActiveEntities", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around thinking entities. dormant entities (outside of pvs) are drawn yellow. non-dormant are green." ); +idCVar g_showEnemies( "g_showEnemies", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around monsters that have targeted the the player" ); + +idCVar g_frametime( "g_frametime", "0", CVAR_GAME | CVAR_BOOL, "displays timing information for each game frame" ); +idCVar g_timeentities( "g_timeEntities", "0", CVAR_GAME | CVAR_FLOAT, "when non-zero, shows entities whose think functions exceeded the # of milliseconds specified" ); + +idCVar g_debugShockwave( "g_debugShockwave", "0", CVAR_GAME | CVAR_BOOL, "Debug the shockwave" ); + +idCVar g_enableSlowmo( "g_enableSlowmo", "0", CVAR_GAME | CVAR_BOOL, "for testing purposes only" ); +idCVar g_slowmoStepRate( "g_slowmoStepRate", "0.02", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_enablePortalSky( "g_enablePortalSky", "1", CVAR_GAME | CVAR_BOOL, "enables the portal sky" ); +idCVar g_testFullscreenFX( "g_testFullscreenFX", "-1", CVAR_GAME | CVAR_INTEGER, "index will activate specific fx, -2 is for all on, -1 is off" ); +idCVar g_testHelltimeFX( "g_testHelltimeFX", "-1", CVAR_GAME | CVAR_INTEGER, "set to 0, 1, 2 to test helltime, -1 is off" ); +idCVar g_testMultiplayerFX( "g_testMultiplayerFX", "-1", CVAR_GAME | CVAR_INTEGER, "set to 0, 1, 2 to test multiplayer, -1 is off" ); + +idCVar g_moveableDamageScale( "g_moveableDamageScale", "0.1", CVAR_GAME | CVAR_FLOAT, "scales damage wrt mass of object in multiplayer" ); + +idCVar g_testBloomIntensity( "g_testBloomIntensity", "-0.01", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_testBloomNumPasses( "g_testBloomNumPasses", "30", CVAR_GAME | CVAR_INTEGER, "" ); + +idCVar ai_debugScript( "ai_debugScript", "-1", CVAR_GAME | CVAR_INTEGER, "displays script calls for the specified monster entity number" ); +idCVar ai_debugMove( "ai_debugMove", "0", CVAR_GAME | CVAR_BOOL, "draws movement information for monsters" ); +idCVar ai_debugTrajectory( "ai_debugTrajectory", "0", CVAR_GAME | CVAR_BOOL, "draws trajectory tests for monsters" ); +idCVar ai_testPredictPath( "ai_testPredictPath", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCombatNodes( "ai_showCombatNodes", "0", CVAR_GAME | CVAR_BOOL, "draws attack cones for monsters" ); +idCVar ai_showPaths( "ai_showPaths", "0", CVAR_GAME | CVAR_BOOL, "draws path_* entities" ); +idCVar ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "0", CVAR_GAME | CVAR_INTEGER, "draws obstacle avoidance information for monsters. if 2, draws obstacles for player, as well", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar ai_blockedFailSafe( "ai_blockedFailSafe", "1", CVAR_GAME | CVAR_BOOL, "enable blocked fail safe handling" ); + +idCVar ai_showHealth( "ai_showHealth", "0", CVAR_GAME | CVAR_BOOL, "Draws the AI's health above its head" ); + +idCVar g_dvTime( "g_dvTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvAmplitude( "g_dvAmplitude", "0.001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvFrequency( "g_dvFrequency", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_kickTime( "g_kickTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_kickAmplitude( "g_kickAmplitude", "0.0001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobTime( "g_blobTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobSize( "g_blobSize", "1", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_testHealthVision( "g_testHealthVision", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_editEntityMode( "g_editEntityMode", "0", CVAR_GAME | CVAR_INTEGER, "0 = off\n" + "1 = lights\n" + "2 = sounds\n" + "3 = articulated figures\n" + "4 = particle systems\n" + "5 = monsters\n" + "6 = entity names\n" + "7 = entity models", 0, 7, idCmdSystem::ArgCompletion_Integer<0,7> ); +idCVar g_dragEntity( "g_dragEntity", "0", CVAR_GAME | CVAR_BOOL, "allows dragging physics objects around by placing the crosshair over them and holding the fire button" ); +idCVar g_dragDamping( "g_dragDamping", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dragShowSelection( "g_dragShowSelection", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_dropItemRotation( "g_dropItemRotation", "", CVAR_GAME, "" ); + +// Note: These cvars do not necessarily need to be in the shipping game. +idCVar g_flagAttachJoint( "g_flagAttachJoint", "Chest", CVAR_GAME | CVAR_CHEAT, "player joint to attach CTF flag to" ); +idCVar g_flagAttachOffsetX( "g_flagAttachOffsetX", "8", CVAR_GAME | CVAR_CHEAT, "X offset of CTF flag when carried" ); +idCVar g_flagAttachOffsetY( "g_flagAttachOffsetY", "4", CVAR_GAME | CVAR_CHEAT, "Y offset of CTF flag when carried" ); +idCVar g_flagAttachOffsetZ( "g_flagAttachOffsetZ", "-12", CVAR_GAME | CVAR_CHEAT, "Z offset of CTF flag when carried" ); +idCVar g_flagAttachAngleX( "g_flagAttachAngleX", "90", CVAR_GAME | CVAR_CHEAT, "X angle of CTF flag when carried" ); +idCVar g_flagAttachAngleY( "g_flagAttachAngleY", "25", CVAR_GAME | CVAR_CHEAT, "Y angle of CTF flag when carried" ); +idCVar g_flagAttachAngleZ( "g_flagAttachAngleZ", "-90", CVAR_GAME | CVAR_CHEAT, "Z angle of CTF flag when carried" ); + + +idCVar g_vehicleVelocity( "g_vehicleVelocity", "1000", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleForce( "g_vehicleForce", "50000", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleSuspensionUp( "g_vehicleSuspensionUp", "32", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleSuspensionDown( "g_vehicleSuspensionDown", "20", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleSuspensionKCompress("g_vehicleSuspensionKCompress","200", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleSuspensionDamping( "g_vehicleSuspensionDamping","400", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleTireFriction( "g_vehicleTireFriction", "0.8", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleDebug( "g_vehicleDebug", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar ik_enable( "ik_enable", "1", CVAR_GAME | CVAR_BOOL, "enable IK" ); +idCVar ik_debug( "ik_debug", "0", CVAR_GAME | CVAR_BOOL, "show IK debug lines" ); + +idCVar af_useLinearTime( "af_useLinearTime", "1", CVAR_GAME | CVAR_BOOL, "use linear time algorithm for tree-like structures" ); +idCVar af_useImpulseFriction( "af_useImpulseFriction", "0", CVAR_GAME | CVAR_BOOL, "use impulse based contact friction" ); +idCVar af_useJointImpulseFriction( "af_useJointImpulseFriction","0", CVAR_GAME | CVAR_BOOL, "use impulse based joint friction" ); +idCVar af_useSymmetry( "af_useSymmetry", "1", CVAR_GAME | CVAR_BOOL, "use constraint matrix symmetry" ); +idCVar af_skipSelfCollision( "af_skipSelfCollision", "0", CVAR_GAME | CVAR_BOOL, "skip self collision detection" ); +idCVar af_skipLimits( "af_skipLimits", "0", CVAR_GAME | CVAR_BOOL, "skip joint limits" ); +idCVar af_skipFriction( "af_skipFriction", "0", CVAR_GAME | CVAR_BOOL, "skip friction" ); +idCVar af_forceFriction( "af_forceFriction", "-1", CVAR_GAME | CVAR_FLOAT, "force the given friction value" ); +idCVar af_maxLinearVelocity( "af_maxLinearVelocity", "128", CVAR_GAME | CVAR_FLOAT, "maximum linear velocity" ); +idCVar af_maxAngularVelocity( "af_maxAngularVelocity", "1.57", CVAR_GAME | CVAR_FLOAT, "maximum angular velocity" ); +idCVar af_timeScale( "af_timeScale", "1", CVAR_GAME | CVAR_FLOAT, "scales the time" ); +idCVar af_jointFrictionScale( "af_jointFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the joint friction" ); +idCVar af_contactFrictionScale( "af_contactFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the contact friction" ); +idCVar af_highlightBody( "af_highlightBody", "", CVAR_GAME, "name of the body to highlight" ); +idCVar af_highlightConstraint( "af_highlightConstraint", "", CVAR_GAME, "name of the constraint to highlight" ); +idCVar af_showTimings( "af_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show articulated figure cpu usage" ); +idCVar af_showConstraints( "af_showConstraints", "0", CVAR_GAME | CVAR_BOOL, "show constraints" ); +idCVar af_showConstraintNames( "af_showConstraintNames", "0", CVAR_GAME | CVAR_BOOL, "show constraint names" ); +idCVar af_showConstrainedBodies( "af_showConstrainedBodies", "0", CVAR_GAME | CVAR_BOOL, "show the two bodies contrained by the highlighted constraint" ); +idCVar af_showPrimaryOnly( "af_showPrimaryOnly", "0", CVAR_GAME | CVAR_BOOL, "show primary constraints only" ); +idCVar af_showTrees( "af_showTrees", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures" ); +idCVar af_showLimits( "af_showLimits", "0", CVAR_GAME | CVAR_BOOL, "show joint limits" ); +idCVar af_showBodies( "af_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show bodies" ); +idCVar af_showBodyNames( "af_showBodyNames", "0", CVAR_GAME | CVAR_BOOL, "show body names" ); +idCVar af_showMass( "af_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each body" ); +idCVar af_showTotalMass( "af_showTotalMass", "0", CVAR_GAME | CVAR_BOOL, "show the total mass of each articulated figure" ); +idCVar af_showInertia( "af_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each body" ); +idCVar af_showVelocity( "af_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each body" ); +idCVar af_showActive( "af_showActive", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures of articulated figures not at rest" ); +idCVar af_testSolid( "af_testSolid", "1", CVAR_GAME | CVAR_BOOL, "test for bodies initially stuck in solid" ); + +idCVar rb_showTimings( "rb_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show rigid body cpu usage" ); +idCVar rb_showBodies( "rb_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies" ); +idCVar rb_showMass( "rb_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each rigid body" ); +idCVar rb_showInertia( "rb_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each rigid body" ); +idCVar rb_showVelocity( "rb_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each rigid body" ); +idCVar rb_showActive( "rb_showActive", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies that are not at rest" ); + +// The default values for player movement cvars are set in def/player.def +idCVar pm_jumpheight( "pm_jumpheight", "48", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "approximate hieght the player can jump" ); +idCVar pm_stepsize( "pm_stepsize", "16", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "maximum height the player can step up without jumping" ); +idCVar pm_crouchspeed( "pm_crouchspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "speed the player can move while crouched" ); +idCVar pm_walkspeed( "pm_walkspeed", "140", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "speed the player can move while walking" ); +idCVar pm_runspeed( "pm_runspeed", "220", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "speed the player can move while running" ); +idCVar pm_noclipspeed( "pm_noclipspeed", "200", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "speed the player can move while in noclip" ); +idCVar pm_spectatespeed( "pm_spectatespeed", "450", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "speed the player can move while spectating" ); +idCVar pm_spectatebbox( "pm_spectatebbox", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "size of the spectator bounding box" ); +idCVar pm_usecylinder( "pm_usecylinder", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL, "use a cylinder approximation instead of a bounding box for player collision detection" ); +idCVar pm_minviewpitch( "pm_minviewpitch", "-89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "amount player's view can look up (negative values are up)" ); +idCVar pm_maxviewpitch( "pm_maxviewpitch", "89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "amount player's view can look down" ); +idCVar pm_stamina( "pm_stamina", "24", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "length of time player can run" ); +idCVar pm_staminathreshold( "pm_staminathreshold", "45", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "when stamina drops below this value, player gradually slows to a walk" ); +idCVar pm_staminarate( "pm_staminarate", "0.75", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "rate that player regains stamina. divide pm_stamina by this value to determine how long it takes to fully recharge." ); +idCVar pm_crouchheight( "pm_crouchheight", "38", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of player's bounding box while crouched" ); +idCVar pm_crouchviewheight( "pm_crouchviewheight", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of player's view while crouched" ); +idCVar pm_normalheight( "pm_normalheight", "74", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of player's bounding box while standing" ); +idCVar pm_normalviewheight( "pm_normalviewheight", "68", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of player's view while standing" ); +idCVar pm_deadheight( "pm_deadheight", "20", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of player's bounding box while dead" ); +idCVar pm_deadviewheight( "pm_deadviewheight", "10", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of player's view while dead" ); +idCVar pm_crouchrate( "pm_crouchrate", "0.87", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "time it takes for player's view to change from standing to crouching" ); +idCVar pm_bboxwidth( "pm_bboxwidth", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "x/y size of player's bounding box" ); +idCVar pm_crouchbob( "pm_crouchbob", "0.5", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "bob much faster when crouched" ); +idCVar pm_walkbob( "pm_walkbob", "0.3", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "bob slowly when walking" ); +idCVar pm_runbob( "pm_runbob", "0.4", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "bob faster when running" ); +idCVar pm_runpitch( "pm_runpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "" ); +idCVar pm_runroll( "pm_runroll", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "" ); +idCVar pm_bobup( "pm_bobup", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "" ); +idCVar pm_bobpitch( "pm_bobpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "" ); +idCVar pm_bobroll( "pm_bobroll", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "" ); +idCVar pm_thirdPersonRange( "pm_thirdPersonRange", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "camera distance from player in 3rd person" ); +idCVar pm_thirdPersonHeight( "pm_thirdPersonHeight", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "height of camera from normal view height in 3rd person" ); +idCVar pm_thirdPersonAngle( "pm_thirdPersonAngle", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT, "direction of camera from player in 3rd person in degrees (0 = behind player, 180 = in front)" ); +idCVar pm_thirdPersonClip( "pm_thirdPersonClip", "1", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL, "clip third person view into world space" ); +idCVar pm_thirdPerson( "pm_thirdPerson", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL, "enables third person view" ); +idCVar pm_thirdPersonDeath( "pm_thirdPersonDeath", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL, "enables third person view when player dies" ); +idCVar pm_modelView( "pm_modelView", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "draws camera from POV of player model (1 = always, 2 = when dead)", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar pm_airMsec( "pm_air", "30000", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "how long in milliseconds the player can go without air before he starts taking damage" ); + +idCVar g_showPlayerShadow( "g_showPlayerShadow", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "enables shadow of player model" ); +idCVar g_showHud( "g_showHud", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar g_showProjectilePct( "g_showProjectilePct", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "enables display of player hit percentage" ); +idCVar g_showBrass( "g_showBrass", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "enables ejected shells from weapon" ); +idCVar g_gun_x( "g_gunX", "3", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gun_y( "g_gunY", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gun_z( "g_gunZ", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gunScale( "g_gunScale", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_viewNodalX( "g_viewNodalX", "3", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalZ( "g_viewNodalZ", "6", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_fov( "g_fov", "80", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "" ); +idCVar g_skipViewEffects( "g_skipViewEffects", "0", CVAR_GAME | CVAR_BOOL, "skip damage and other view effects" ); +idCVar g_mpWeaponAngleScale( "g_mpWeaponAngleScale", "0", CVAR_GAME | CVAR_FLOAT, "Control the weapon sway in MP" ); + +idCVar g_testParticle( "g_testParticle", "0", CVAR_GAME | CVAR_INTEGER, "test particle visualation, set by the particle editor" ); +idCVar g_testParticleName( "g_testParticleName", "", CVAR_GAME, "name of the particle being tested by the particle editor" ); +idCVar g_testModelRotate( "g_testModelRotate", "0", CVAR_GAME, "test model rotation speed" ); +idCVar g_testPostProcess( "g_testPostProcess", "", CVAR_GAME, "name of material to draw over screen" ); +idCVar g_testModelAnimate( "g_testModelAnimate", "0", CVAR_GAME | CVAR_INTEGER, "test model animation,\n" + "0 = cycle anim with origin reset\n" + "1 = cycle anim with fixed origin\n" + "2 = cycle anim with continuous origin\n" + "3 = frame by frame with continuous origin\n" + "4 = play anim once", 0, 4, idCmdSystem::ArgCompletion_Integer<0,4> ); +idCVar g_testModelBlend( "g_testModelBlend", "0", CVAR_GAME | CVAR_INTEGER, "number of frames to blend" ); +idCVar g_testDeath( "g_testDeath", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_flushSave( "g_flushSave", "0", CVAR_GAME | CVAR_BOOL, "1 = don't buffer file writing for save games." ); + +idCVar aas_test( "aas_test", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreas( "aas_showAreas", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_showPath( "aas_showPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showFlyPath( "aas_showFlyPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showWallEdges( "aas_showWallEdges", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_showHideArea( "aas_showHideArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_pullPlayer( "aas_pullPlayer", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_randomPullPlayer( "aas_randomPullPlayer", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_goalArea( "aas_goalArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showPushIntoArea( "aas_showPushIntoArea", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_countDown( "g_countDown", "15", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "pregame countdown in seconds", 4, 3600 ); +idCVar g_gameReviewPause( "g_gameReviewPause", "10", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | CVAR_ARCHIVE, "scores review time in seconds (at end game)", 2, 3600 ); +idCVar g_CTFArrows( "g_CTFArrows", "1", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL, "draw arrows over teammates in CTF" ); + +idCVar net_clientPredictGUI( "net_clientPredictGUI", "1", CVAR_GAME | CVAR_BOOL, "test guis in networking without prediction" ); + +idCVar g_grabberHoldSeconds( "g_grabberHoldSeconds", "3", CVAR_GAME | CVAR_FLOAT | CVAR_CHEAT, "number of seconds to hold object" ); +idCVar g_grabberEnableShake( "g_grabberEnableShake", "1", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "enable the grabber shake" ); +idCVar g_grabberRandomMotion( "g_grabberRandomMotion", "1", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "enable random motion on the grabbed object" ); +idCVar g_grabberHardStop( "g_grabberHardStop", "1", CVAR_GAME | CVAR_BOOL | CVAR_CHEAT, "hard stops object if too fast" ); +idCVar g_grabberDamping( "g_grabberDamping", "0.5", CVAR_GAME | CVAR_FLOAT | CVAR_CHEAT, "damping of grabber" ); + +idCVar g_xp_bind_run_once( "g_xp_bind_run_once", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "Rebind all controls once for D3XP." ); diff --git a/neo/d3xp/gamesys/SysCvar.h b/neo/d3xp/gamesys/SysCvar.h new file mode 100644 index 00000000..8b127a22 --- /dev/null +++ b/neo/d3xp/gamesys/SysCvar.h @@ -0,0 +1,274 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SYS_CVAR_H__ +#define __SYS_CVAR_H__ + +extern idCVar developer; + +extern idCVar g_cinematic; +extern idCVar g_cinematicMaxSkipTime; + +extern idCVar g_monsters; +extern idCVar g_decals; +extern idCVar g_knockback; +extern idCVar g_skill; +extern idCVar g_gravity; +extern idCVar g_skipFX; +extern idCVar g_bloodEffects; +extern idCVar g_projectileLights; +extern idCVar g_muzzleFlash; + +extern idCVar g_disasm; +extern idCVar g_debugBounds; +extern idCVar g_debugAnim; +extern idCVar g_debugMove; +extern idCVar g_debugDamage; +extern idCVar g_debugWeapon; +extern idCVar g_debugScript; +extern idCVar g_debugMover; +extern idCVar g_debugTriggers; +extern idCVar g_debugCinematic; +extern idCVar g_stopTime; +extern idCVar g_armorProtection; +extern idCVar g_armorProtectionMP; +extern idCVar g_damageScale; +extern idCVar g_useDynamicProtection; +extern idCVar g_healthTakeTime; +extern idCVar g_healthTakeAmt; +extern idCVar g_healthTakeLimit; + +extern idCVar g_showPVS; +extern idCVar g_showTargets; +extern idCVar g_showTriggers; +extern idCVar g_showCollisionWorld; +extern idCVar g_showCollisionModels; +extern idCVar g_showCollisionTraces; +extern idCVar g_maxShowDistance; +extern idCVar g_showEntityInfo; +extern idCVar g_showviewpos; +extern idCVar g_showcamerainfo; +extern idCVar g_showTestModelFrame; +extern idCVar g_showActiveEntities; +extern idCVar g_showEnemies; + +extern idCVar g_frametime; +extern idCVar g_timeentities; + +extern idCVar ai_debugScript; +extern idCVar ai_debugMove; +extern idCVar ai_debugTrajectory; +extern idCVar ai_testPredictPath; +extern idCVar ai_showCombatNodes; +extern idCVar ai_showPaths; +extern idCVar ai_showObstacleAvoidance; +extern idCVar ai_blockedFailSafe; +extern idCVar ai_showHealth; + +extern idCVar g_dvTime; +extern idCVar g_dvAmplitude; +extern idCVar g_dvFrequency; + +extern idCVar g_kickTime; +extern idCVar g_kickAmplitude; +extern idCVar g_blobTime; +extern idCVar g_blobSize; + +extern idCVar g_testHealthVision; +extern idCVar g_editEntityMode; +extern idCVar g_dragEntity; +extern idCVar g_dragDamping; +extern idCVar g_dragShowSelection; +extern idCVar g_dropItemRotation; + +extern idCVar g_vehicleVelocity; +extern idCVar g_vehicleForce; +extern idCVar g_vehicleSuspensionUp; +extern idCVar g_vehicleSuspensionDown; +extern idCVar g_vehicleSuspensionKCompress; +extern idCVar g_vehicleSuspensionDamping; +extern idCVar g_vehicleTireFriction; +extern idCVar g_vehicleDebug; +extern idCVar g_debugShockwave; +extern idCVar g_enablePortalSky; + +extern idCVar ik_enable; +extern idCVar ik_debug; + +extern idCVar af_useLinearTime; +extern idCVar af_useImpulseFriction; +extern idCVar af_useJointImpulseFriction; +extern idCVar af_useSymmetry; +extern idCVar af_skipSelfCollision; +extern idCVar af_skipLimits; +extern idCVar af_skipFriction; +extern idCVar af_forceFriction; +extern idCVar af_maxLinearVelocity; +extern idCVar af_maxAngularVelocity; +extern idCVar af_timeScale; +extern idCVar af_jointFrictionScale; +extern idCVar af_contactFrictionScale; +extern idCVar af_highlightBody; +extern idCVar af_highlightConstraint; +extern idCVar af_showTimings; +extern idCVar af_showConstraints; +extern idCVar af_showConstraintNames; +extern idCVar af_showConstrainedBodies; +extern idCVar af_showPrimaryOnly; +extern idCVar af_showTrees; +extern idCVar af_showLimits; +extern idCVar af_showBodies; +extern idCVar af_showBodyNames; +extern idCVar af_showMass; +extern idCVar af_showTotalMass; +extern idCVar af_showInertia; +extern idCVar af_showVelocity; +extern idCVar af_showActive; +extern idCVar af_testSolid; + +extern idCVar rb_showTimings; +extern idCVar rb_showBodies; +extern idCVar rb_showMass; +extern idCVar rb_showInertia; +extern idCVar rb_showVelocity; +extern idCVar rb_showActive; + +extern idCVar pm_jumpheight; +extern idCVar pm_stepsize; +extern idCVar pm_crouchspeed; +extern idCVar pm_walkspeed; +extern idCVar pm_runspeed; +extern idCVar pm_noclipspeed; +extern idCVar pm_spectatespeed; +extern idCVar pm_spectatebbox; +extern idCVar pm_usecylinder; +extern idCVar pm_minviewpitch; +extern idCVar pm_maxviewpitch; +extern idCVar pm_stamina; +extern idCVar pm_staminathreshold; +extern idCVar pm_staminarate; +extern idCVar pm_crouchheight; +extern idCVar pm_crouchviewheight; +extern idCVar pm_normalheight; +extern idCVar pm_normalviewheight; +extern idCVar pm_deadheight; +extern idCVar pm_deadviewheight; +extern idCVar pm_crouchrate; +extern idCVar pm_bboxwidth; +extern idCVar pm_crouchbob; +extern idCVar pm_walkbob; +extern idCVar pm_runbob; +extern idCVar pm_runpitch; +extern idCVar pm_runroll; +extern idCVar pm_bobup; +extern idCVar pm_bobpitch; +extern idCVar pm_bobroll; +extern idCVar pm_thirdPersonRange; +extern idCVar pm_thirdPersonHeight; +extern idCVar pm_thirdPersonAngle; +extern idCVar pm_thirdPersonClip; +extern idCVar pm_thirdPerson; +extern idCVar pm_thirdPersonDeath; +extern idCVar pm_modelView; +extern idCVar pm_airMsec; + +extern idCVar g_showPlayerShadow; +extern idCVar g_showHud; +extern idCVar g_showProjectilePct; +extern idCVar g_showBrass; +extern idCVar g_gun_x; +extern idCVar g_gun_y; +extern idCVar g_gun_z; +extern idCVar g_gunScale; +extern idCVar g_viewNodalX; +extern idCVar g_viewNodalZ; +extern idCVar g_fov; +extern idCVar g_testDeath; +extern idCVar g_skipViewEffects; +extern idCVar g_mpWeaponAngleScale; + +extern idCVar g_testParticle; +extern idCVar g_testParticleName; + +extern idCVar g_testPostProcess; + +extern idCVar g_testModelRotate; +extern idCVar g_testModelAnimate; +extern idCVar g_testModelBlend; +extern idCVar g_flushSave; + +extern idCVar g_enableSlowmo; +extern idCVar g_slowmoStepRate; +extern idCVar g_testFullscreenFX; +extern idCVar g_testHelltimeFX; +extern idCVar g_testMultiplayerFX; +extern idCVar g_moveableDamageScale; +extern idCVar g_testBloomIntensity; +extern idCVar g_testBloomNumPasses; + +extern idCVar g_grabberHoldSeconds; +extern idCVar g_grabberEnableShake; +extern idCVar g_grabberRandomMotion; +extern idCVar g_grabberHardStop; +extern idCVar g_grabberDamping; + +extern idCVar g_xp_bind_run_once; + +extern idCVar aas_test; +extern idCVar aas_showAreas; +extern idCVar aas_showPath; +extern idCVar aas_showFlyPath; +extern idCVar aas_showWallEdges; +extern idCVar aas_showHideArea; +extern idCVar aas_pullPlayer; +extern idCVar aas_randomPullPlayer; +extern idCVar aas_goalArea; +extern idCVar aas_showPushIntoArea; + +extern idCVar net_clientPredictGUI; + +extern idCVar si_timeLimit; +extern idCVar si_fragLimit; +extern idCVar si_spectators; + +extern idCVar si_flagDropTimeLimit; +extern idCVar si_midnight; + +extern idCVar g_flagAttachJoint; +extern idCVar g_flagAttachOffsetX; +extern idCVar g_flagAttachOffsetY; +extern idCVar g_flagAttachOffsetZ; +extern idCVar g_flagAttachAngleX; +extern idCVar g_flagAttachAngleY; +extern idCVar g_flagAttachAngleZ; + +extern idCVar g_CTFArrows; + +extern idCVar net_clientSelfSmoothing; + +#endif /* !__SYS_CVAR_H__ */ diff --git a/neo/d3xp/menus/MenuHandler.cpp b/neo/d3xp/menus/MenuHandler.cpp new file mode 100644 index 00000000..955c12cd --- /dev/null +++ b/neo/d3xp/menus/MenuHandler.cpp @@ -0,0 +1,449 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +extern idCVar in_useJoystick; + +/* +================================================ +idMenuHandler::~idMenuHandler +================================================ +*/ +idMenuHandler::idMenuHandler() { + scrollingMenu = false; + scrollCounter = 0; + activeScreen = -1; + nextScreen = -1; + transition = -1; + platform = 0; + gui = NULL; + cmdBar = NULL; + + for ( int index = 0; index < MAX_SCREEN_AREAS; ++index ) { + menuScreens[ index ] = NULL; + } + + sounds.SetNum( NUM_GUI_SOUNDS ); + +} + +/* +================================================ +idMenuHandler::~idMenuHandler +================================================ +*/ +idMenuHandler::~idMenuHandler() { + Cleanup(); +} + +/* +================================================ +idMenuHandler::Initialize +================================================ +*/ +void idMenuHandler::Initialize( const char * swfFile, idSoundWorld * sw ) { + Cleanup(); + gui = new idSWF( swfFile, sw ); + + platform = 2; + +} + +/* +================================================ +idMenuHandler::AddChild +================================================ +*/ +void idMenuHandler::AddChild( idMenuWidget * widget ) { + widget->SetSWFObj( gui ); + widget->SetHandlerIsParent( true ); + children.Append( widget ); + widget->AddRef(); +} + +/* +================================================ +idMenuHandler::GetChildFromIndex +================================================ +*/ +idMenuWidget * idMenuHandler::GetChildFromIndex( int index ) { + + if ( children.Num() == 0 ) { + return NULL; + } + + if ( index > children.Num() ) { + return NULL; + } + + return children[ index ]; +} + +/* +================================================ +idMenuHandler::GetPlatform +================================================ +*/ +int idMenuHandler::GetPlatform( bool realPlatform ) { + + if ( platform == 2 && in_useJoystick.GetBool() && !realPlatform ) { + return 0; + } + + return platform; +} + +/* +================================================ +idMenuHandler::GetPlatform +================================================ +*/ +void idMenuHandler::PlaySound( menuSounds_t type, int channel ) { + + if ( gui == NULL ) { + return; + } + + if ( type >= sounds.Num() ) { + return; + } + + if ( sounds[ type ].IsEmpty() ) { + return; + } + + int c = SCHANNEL_ANY; + if ( channel != -1 ) { + c = channel; + } + + gui->PlaySound( sounds[ type ], c ); + +} + +/* +================================================ +idMenuHandler::StopSound +================================================ +*/ +void idMenuHandler::StopSound( int channel ) { + gui->StopSound(); +} + +/* +================================================ +idMenuHandler::Cleanup +================================================ +*/ +void idMenuHandler::Cleanup() { + for ( int index = 0; index < children.Num(); ++index ) { + assert( children[ index ]->GetRefCount() > 0 ); + children[ index ]->Release(); + } + children.Clear(); + + for ( int index = 0; index < MAX_SCREEN_AREAS; ++index ) { + if ( menuScreens[ index ] != NULL ) { + menuScreens[ index ]->Release(); + } + } + + delete gui; + gui = NULL; +} + +/* +================================================ +idMenuHandler::TriggerMenu +================================================ +*/ +void idMenuHandler::TriggerMenu() { + +} + +/* +================================================ +idMenuHandler::IsActive +================================================ +*/ +bool idMenuHandler::IsActive() { + if ( gui == NULL ) { + return false; + } + + return gui->IsActive(); +} + +/* +================================================ +idMenuHandler::ActivateMenu +================================================ +*/ +void idMenuHandler::ActivateMenu( bool show ) { + + if ( gui == NULL ) { + return; + } + + if ( !show ) { + gui->Activate( show ); + return; + } + + + class idSWFScriptFunction_updateMenuDisplay : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_updateMenuDisplay( idSWF * _gui, idMenuHandler * _handler ) { + gui = _gui; + handler = _handler; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( handler != NULL ) { + int screen = parms[0].ToInteger(); + handler->UpdateMenuDisplay( screen ); + } + + return idSWFScriptVar(); + } + private: + idSWF * gui; + idMenuHandler * handler; + }; + + class idSWFScriptFunction_activateMenu : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_activateMenu( idMenuHandler * _handler ) { + handler = _handler; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( handler != NULL ) { + handler->TriggerMenu(); + } + + return idSWFScriptVar(); + } + private: + idMenuHandler * handler; + }; + + gui->SetGlobal( "updateMenuDisplay", new (TAG_SWF) idSWFScriptFunction_updateMenuDisplay( gui, this ) ); + gui->SetGlobal( "activateMenus", new (TAG_SWF) idSWFScriptFunction_activateMenu( this ) ); + + gui->Activate( show ); +} + +/* +================================================ +idMenuHandler::Update +================================================ +*/ +void idMenuHandler::Update() { + + PumpWidgetActionRepeater(); + + if ( gui != NULL && gui->IsActive() ) { + gui->Render( renderSystem, Sys_Milliseconds() ); + } +} + +/* +================================================ +idMenuHandler::UpdateChildren +================================================ +*/ +void idMenuHandler::UpdateChildren() { + for ( int index = 0; index < children.Num(); ++index ) { + if ( children[ index ] != NULL ) { + children[index]->Update(); + } + } +} + +/* +================================================ +idMenuHandler::UpdateMenuDisplay +================================================ +*/ +void idMenuHandler::UpdateMenuDisplay( int menu ) { + + if ( menuScreens[ menu ] != NULL ) { + menuScreens[ menu ]->Update(); + } + + UpdateChildren(); + +} + +/* +================================================ +idMenuHandler::Update +================================================ +*/ +bool idMenuHandler::HandleGuiEvent( const sysEvent_t * sev ) { + + if ( gui != NULL && activeScreen != -1 ) { + return gui->HandleEvent( sev ); + } + + return false; +} + +/* +================================================ +idMenuHandler::Update +================================================ +*/ +bool idMenuHandler::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_ADJUST_FIELD: { + if ( widget != NULL && widget->GetDataSource() != NULL ) { + widget->GetDataSource()->AdjustField( widget->GetDataSourceFieldIndex(), parms[ 0 ].ToInteger() ); + widget->Update(); + } + return true; + } + case WIDGET_ACTION_FUNCTION: { + if ( verify( action.GetScriptFunction() != NULL ) ) { + action.GetScriptFunction()->Call( event.thisObject, event.parms ); + } + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + idMenuScreen * const screen = menuScreens[ activeScreen ]; + if ( screen != NULL ) { + idWidgetEvent pressEvent( WIDGET_EVENT_PRESS, 0, event.thisObject, idSWFParmList() ); + screen->ReceiveEvent( pressEvent ); + } + return true; + } + case WIDGET_ACTION_START_REPEATER: { + idWidgetAction repeatAction; + widgetAction_t repeatActionType = static_cast< widgetAction_t >( parms[ 0 ].ToInteger() ); + assert( parms.Num() >= 2 ); + int repeatDelay = DEFAULT_REPEAT_TIME; + if ( parms.Num() >= 3 ) { + repeatDelay = parms[2].ToInteger(); + } + repeatAction.Set( repeatActionType, parms[ 1 ], repeatDelay ); + StartWidgetActionRepeater( widget, repeatAction, event ); + return true; + } + case WIDGET_ACTION_STOP_REPEATER: { + ClearWidgetActionRepeater(); + return true; + } + } + + if ( !widget->GetHandlerIsParent() ) { + for ( int index = 0; index < children.Num(); ++index ) { + if ( children[index] != NULL ) { + if ( children[index]->HandleAction( action, event, widget, forceHandled ) ) { + return true; + } + } + } + } + + return false; +} + +/* +======================== +idMenuHandler::StartWidgetActionRepeater +======================== +*/ +void idMenuHandler::StartWidgetActionRepeater( idMenuWidget * widget, const idWidgetAction & action, const idWidgetEvent & event ) { + if ( actionRepeater.isActive && actionRepeater.action == action ) { + return; // don't attempt to reactivate an already active repeater + } + + actionRepeater.isActive = true; + actionRepeater.action = action; + actionRepeater.widget = widget; + actionRepeater.event = event; + actionRepeater.numRepetitions = 0; + actionRepeater.nextRepeatTime = 0; + actionRepeater.screenIndex = activeScreen; // repeaters are cleared between screens + + if ( action.GetParms().Num() == 2 ) { + actionRepeater.repeatDelay = action.GetParms()[ 1 ].ToInteger(); + } else { + actionRepeater.repeatDelay = DEFAULT_REPEAT_TIME; + } + + // do the first event immediately + PumpWidgetActionRepeater(); +} + +/* +======================== +idMenuHandler::PumpWidgetActionRepeater +======================== +*/ +void idMenuHandler::PumpWidgetActionRepeater() { + if ( !actionRepeater.isActive ) { + return; + } + + if ( activeScreen != actionRepeater.screenIndex || nextScreen != activeScreen ) { // || common->IsDialogActive() ) { + actionRepeater.isActive = false; + return; + } + + if ( actionRepeater.nextRepeatTime > Sys_Milliseconds() ) { + return; + } + + // need to hold down longer on the first iteration before we continue to scroll + if ( actionRepeater.numRepetitions == 0 ) { + actionRepeater.nextRepeatTime = Sys_Milliseconds() + 400; + } else { + actionRepeater.nextRepeatTime = Sys_Milliseconds() + actionRepeater.repeatDelay; + } + + if ( verify( actionRepeater.widget != NULL ) ) { + actionRepeater.widget->HandleAction( actionRepeater.action, actionRepeater.event, actionRepeater.widget ); + actionRepeater.numRepetitions++; + } +} + +/* +======================== +idMenuHandler::ClearWidgetActionRepeater +======================== +*/ +void idMenuHandler::ClearWidgetActionRepeater() { + actionRepeater.isActive = false; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuHandler.h b/neo/d3xp/menus/MenuHandler.h new file mode 100644 index 00000000..37a7f9d6 --- /dev/null +++ b/neo/d3xp/menus/MenuHandler.h @@ -0,0 +1,516 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __MENUDATA_H__ +#define __MENUDATA_H__ + +enum shellAreas_t { + SHELL_AREA_INVALID = -1, + SHELL_AREA_START, + SHELL_AREA_ROOT, + SHELL_AREA_DEV, + SHELL_AREA_CAMPAIGN, + SHELL_AREA_LOAD, + SHELL_AREA_SAVE, + SHELL_AREA_NEW_GAME, + SHELL_AREA_GAME_OPTIONS, + SHELL_AREA_SYSTEM_OPTIONS, + SHELL_AREA_MULTIPLAYER, + SHELL_AREA_GAME_LOBBY, + SHELL_AREA_STEREOSCOPICS, + SHELL_AREA_PARTY_LOBBY, + SHELL_AREA_SETTINGS, + SHELL_AREA_AUDIO, + SHELL_AREA_VIDEO, + SHELL_AREA_KEYBOARD, + SHELL_AREA_CONTROLS, + SHELL_AREA_CONTROLLER_LAYOUT, + SHELL_AREA_GAMEPAD, + SHELL_AREA_PAUSE, + SHELL_AREA_LEADERBOARDS, + SHELL_AREA_PLAYSTATION, + SHELL_AREA_DIFFICULTY, + SHELL_AREA_RESOLUTION, + SHELL_AREA_MATCH_SETTINGS, + SHELL_AREA_MODE_SELECT, + SHELL_AREA_BROWSER, + SHELL_AREA_CREDITS, + SHELL_NUM_AREAS +}; + +enum shellState_t { + SHELL_STATE_INVALID = -1, + SHELL_STATE_PRESS_START, + SHELL_STATE_IDLE, + SHELL_STATE_PARTY_LOBBY, + SHELL_STATE_GAME_LOBBY, + SHELL_STATE_PAUSED, + SHELL_STATE_CONNECTING, + SHELL_STATE_SEARCHING, + SHELL_STATE_LOADING, + SHELL_STATE_BUSY, + SHELL_STATE_IN_GAME +}; + +enum pdaAreas_t { + PDA_AREA_INVALID = -1, + PDA_AREA_USER_DATA, + PDA_AREA_USER_EMAIL, + PDA_AREA_VIDEO_DISKS, + PDA_AREA_INVENTORY, + PDA_NUM_AREAS +}; + +enum hudArea_t { + HUD_AREA_INVALID = -1, + HUD_AREA_PLAYING, + HUD_NUM_AREAS +}; + +enum scoreboardArea_t { + SCOREBOARD_AREA_INVALID = -1, + SCOREBOARD_AREA_DEFAULT, + SCOREBOARD_AREA_TEAM, + SCOREBOARD_AREA_CTF, + SCOREBOARD_NUM_AREAS +}; + +enum pdaHandlerWidgets_t { + PDA_WIDGET_NAV_BAR, + PDA_WIDGET_PDA_LIST, + PDA_WIDGET_PDA_LIST_SCROLLBAR, + PDA_WIDGET_CMD_BAR +}; + +enum scoreboardHandlerWidgets_t { + SCOREBOARD_WIDGET_CMD_BAR, +}; + +enum menuSounds_t { + GUI_SOUND_MUSIC, + GUI_SOUND_SCROLL, + GUI_SOUND_ADVANCE, + GUI_SOUND_BACK, + GUI_SOUND_BUILD_ON, + GUI_SOUND_BUILD_OFF, + GUI_SOUND_FOCUS, + GUI_SOUND_ROLL_OVER, + GUI_SOUND_ROLL_OUT, + NUM_GUI_SOUNDS, +}; + +static const int MAX_SCREEN_AREAS = 32; +static const int DEFAULT_REPEAT_TIME = 150; +static const int WAIT_START_TIME_LONG = 30000; +static const int WAIT_START_TIME_SHORT = 5000; + +struct actionRepeater_t { + actionRepeater_t() : + widget( NULL ), + numRepetitions( 0 ), + nextRepeatTime( 0 ), + screenIndex( -1 ), + repeatDelay( DEFAULT_REPEAT_TIME ), + isActive( false ) { + } + + idMenuWidget * widget; + idWidgetEvent event; + idWidgetAction action; + int numRepetitions; + int nextRepeatTime; + int repeatDelay; + int screenIndex; + bool isActive; +}; + +class mpScoreboardInfo{ +public: + + mpScoreboardInfo() : + voiceState( VOICECHAT_DISPLAY_NONE ), + score( 0 ), + wins( 0 ), + ping( 0 ), + team( -1 ), + playerNum( 0 ) { + } + + mpScoreboardInfo( const mpScoreboardInfo & src ) { + voiceState = src.voiceState; + score = src.score; + wins = src.wins; + ping = src.ping; + spectateData = src.spectateData; + name = src.name; + team = src.team; + playerNum = src.playerNum; + } + + void operator=( const mpScoreboardInfo & src ) { + voiceState = src.voiceState; + score = src.score; + wins = src.wins; + ping = src.ping; + spectateData = src.spectateData; + name = src.name; + team = src.team; + playerNum = src.playerNum; + } + + bool operator!=( const mpScoreboardInfo & otherInfo ) const { + + if ( otherInfo.score != score || otherInfo.wins != wins || otherInfo.ping != ping || + otherInfo.spectateData != spectateData || otherInfo.name != name || otherInfo.team != team || + otherInfo.playerNum != playerNum || otherInfo.voiceState != voiceState ) { + return true; + } + + return false; + } + + bool operator==( const mpScoreboardInfo & otherInfo ) const { + + if ( otherInfo.score != score || otherInfo.wins != wins || otherInfo.ping != ping || + otherInfo.spectateData != spectateData || otherInfo.name != name || otherInfo.team != team || + otherInfo.playerNum != playerNum || otherInfo.voiceState != voiceState ) { + return false; + } + + return true; + } + + voiceStateDisplay_t voiceState; + int score; + int wins; + int ping; + int team; + int playerNum; + idStr spectateData; + idStr name; + +}; + +/* +================================================ +idMenuHandler +================================================ +*/ +class idMenuHandler { +public: + idMenuHandler(); + virtual ~idMenuHandler(); + virtual void Initialize( const char * swfFile, idSoundWorld * sw ); + virtual void Cleanup(); + virtual void Update(); + virtual void UpdateChildren(); + virtual void UpdateMenuDisplay( int menu ); + virtual bool HandleGuiEvent( const sysEvent_t * sev ); + virtual bool IsActive(); + virtual void ActivateMenu( bool show ); + virtual void TriggerMenu(); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual int ActiveScreen() { return activeScreen; } + virtual int NextScreen() { return nextScreen; } + virtual int MenuTransition() { return transition; } + virtual idMenuScreen * GetMenuScreen( int index ) { return NULL; } + virtual void SetNextScreen( int screen, int trans ) { nextScreen = screen; transition = trans; } + + virtual void StartWidgetActionRepeater( idMenuWidget * widget, const idWidgetAction & action, const idWidgetEvent & event ); + virtual void PumpWidgetActionRepeater(); + virtual void ClearWidgetActionRepeater(); + virtual idSWF * GetGUI() { return gui; } + virtual void AddChild( idMenuWidget * widget ); + virtual idMenuWidget * GetChildFromIndex( int index ); + virtual int GetPlatform( bool realPlatform = false ); + virtual void PlaySound( menuSounds_t type, int channel = -1 ); + virtual void StopSound( int channel = SCHANNEL_ANY ); + + idMenuWidget_CommandBar * GetCmdBar() { return cmdBar; } + +protected: + + bool scrollingMenu; + int scrollCounter; + int activeScreen; + int nextScreen; + int transition; + int platform; + idSWF * gui; + actionRepeater_t actionRepeater; + idMenuScreen * menuScreens[MAX_SCREEN_AREAS]; + idList< idMenuWidget *, TAG_IDLIB_LIST_MENU> children; + + idStaticList< idStr, NUM_GUI_SOUNDS > sounds; + + idMenuWidget_CommandBar * cmdBar; +}; + +/* +================================================ +lobbyPlayerInfo_t +================================================ +*/ +struct lobbyPlayerInfo_t { + lobbyPlayerInfo_t() : + partyToken( 0 ), + voiceState( VOICECHAT_DISPLAY_NONE ) { + } + + idStr name; + int partyToken; + voiceStateDisplay_t voiceState; +}; + +/* +================================================ +idMenuHandler_Shell +================================================ +*/ +class idMenuHandler_Shell : public idMenuHandler { +public: + idMenuHandler_Shell() : + state( SHELL_STATE_INVALID ), + nextState( SHELL_STATE_INVALID ), + smallFrameShowing( false ), + largeFrameShowing( false ), + bgShowing( true ), + nextPeerUpdateMs( 0 ), + inGame( false ), + waitForBinding( false ), + waitBind( NULL ), + newGameType( 0 ), + menuBar( NULL ), + pacifier( NULL ), + showingIntro( false ), + introGui( NULL ), + doom3Intro( NULL ), + roeIntro( NULL ), + lmIntro( NULL ), + typeSoundShader( NULL ), + continueWaitForEnumerate( false ), + gameComplete( false ), + marsRotation( NULL ) { + } + virtual void Update(); + virtual void ActivateMenu( bool show ); + virtual void Initialize( const char * swfFile, idSoundWorld * sw ); + virtual void Cleanup(); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual idMenuScreen * GetMenuScreen( int index ); + virtual bool HandleGuiEvent( const sysEvent_t * sev ); + + void UpdateSavedGames(); + void ShowSmallFrame( bool show ); + void ShowMPFrame( bool show ); + void ShowLogo( bool show ); + void SetShellState( shellState_t s ) { nextState = s; } + bool IsSmallFrameShowing() { return smallFrameShowing; } + void UpdateBGState(); + void GetMapName( int index, idStr & name ); + void GetModeName( int index, idStr & name ); + + idMenuWidget * GetPacifier() { return pacifier; } + idMenuWidget_MenuBar * GetMenuBar() { return menuBar; } + bool IsPacifierVisible() const { return ( pacifier != NULL && pacifier->GetSprite() != NULL ) ? pacifier->GetSprite()->IsVisible() : false; } + void ShowPacifier( const idStr & msg ); + void HidePacifier(); + + void SetTimeRemaining( int time ) { timeRemaining = time; } + int GetTimeRemaining() { return timeRemaining; } + void SetNewGameType( int type ) { newGameType = type; } + int GetNewGameType() { return newGameType; } + void SetInGame( bool val ) { inGame = val; } + bool GetInGame() { return inGame; } + void HandleExitGameBtn(); + void SetupPCOptions(); + void SetWaitForBinding( const char * bind ) { waitForBinding = true; waitBind = bind; } + void ClearWaitForBinding() { waitForBinding = false; } + void UpdateLeaderboard( const idLeaderboardCallback * callback ); + void UpdateLobby( idMenuWidget_LobbyList * lobbyList ); + void ShowDoomIntro(); + void ShowROEIntro(); + void ShowLEIntro(); + void StartGame( int index ); + void SetContinueWaitForEnumerate( bool wait ) { continueWaitForEnumerate = wait; } + void SetCanContinue( bool valid ); + void SetGameComplete() { gameComplete = true; } + bool GetGameComplete() { return gameComplete; } + +private: + + shellState_t state; + shellState_t nextState; + bool smallFrameShowing; + bool largeFrameShowing; + bool bgShowing; + bool waitForBinding; + const char * waitBind; + //idSysSignal deviceRequestedSignal; + + idList mpGameModes; + idList mpGameMaps; + idMenuWidget_MenuBar * menuBar; + idMenuWidget * pacifier; + int timeRemaining; + int nextPeerUpdateMs; + int newGameType; + bool inGame; + bool showingIntro; + bool continueWaitForEnumerate; + bool gameComplete; + idSWF * introGui; + const idSoundShader * typeSoundShader; + const idMaterial * doom3Intro; + const idMaterial * roeIntro; + const idMaterial * lmIntro; + const idMaterial * marsRotation; + idList< idStr, TAG_IDLIB_LIST_MENU> navOptions; + +}; + +/* +================================================ +idMenuHandler_PDA +================================================ +*/ +class idMenuHandler_PDA : public idMenuHandler { +public: + idMenuHandler_PDA() : + audioLogPlaying( false ), + videoPlaying( false ), + audioFile( NULL ) { + } + virtual ~idMenuHandler_PDA(); + + virtual void Update(); + virtual void ActivateMenu( bool show ); + virtual void TriggerMenu(); + virtual void Initialize( const char * swfFile, idSoundWorld * sw ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual idMenuScreen * GetMenuScreen( int index ); + void UpdateAudioLogPlaying( bool playing ); + void UdpateVideoPlaying( bool playing ); + void ClearVideoPlaying() { videoPlaying = false; } + + bool PlayPDAAudioLog( int pdaIndex, int audioIndex ); + virtual void Cleanup(); + +protected: + + bool audioLogPlaying; + bool videoPlaying; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > pdaNames; + idList< idStr, TAG_IDLIB_LIST_MENU > navOptions; + const idDeclAudio * audioFile; + idMenuWidget_ScrollBar pdaScrollBar; + idMenuWidget_DynamicList pdaList; + idMenuWidget_NavBar navBar; + idMenuWidget_CommandBar commandBarWidget; +}; + +/* +================================================ +idMenuHandler_PDA +================================================ +*/ +class idMenuHandler_HUD : public idMenuHandler { +public: + + idMenuHandler_HUD() : + autoHideTip( true ), + tipStartTime( 0 ), + hiding( false ), + radioMessage( false ) { + } + + virtual void Update(); + virtual void ActivateMenu( bool show ); + virtual void Initialize( const char * swfFile, idSoundWorld * sw ); + virtual idMenuScreen * GetMenuScreen( int index ); + + idMenuScreen_HUD * GetHud(); + void ShowTip( const char * title, const char * tip, bool autoHide ); + void HideTip(); + void SetRadioMessage( bool show ) { radioMessage = show; } + +protected: + + bool autoHideTip; + int tipStartTime; + bool hiding; + bool radioMessage; + +}; + +/* +================================================ +idMenuHandler_Scoreboard +================================================ +*/ +class idMenuHandler_Scoreboard : public idMenuHandler { +public: + + idMenuHandler_Scoreboard() : + redScore( 0 ), + blueScore( 0 ), + activationScreen( SCOREBOARD_AREA_INVALID ) { + } + + virtual void Update(); + virtual void TriggerMenu(); + virtual void ActivateMenu( bool show ); + virtual void Initialize( const char * swfFile, idSoundWorld * sw ); + virtual idMenuScreen * GetMenuScreen( int index ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void AddPlayerInfo( int index, voiceStateDisplay_t voiceState, int team, idStr name, int score, int wins, int ping, idStr spectateData ); + void UpdateScoreboard( idList< mpScoreboardInfo > & data, idStr gameInfo ); + void UpdateVoiceStates(); + void UpdateSpectating( idStr spectate, idStr follow ); + void SetTeamScores( int r, int b ); + int GetNumPlayers( int team ); + void SetActivationScreen( int screen, int trans ); + void ViewPlayerProfile( int slot ); + void MutePlayer( int slot ); + void GetUserID( int slot, lobbyUserID_t & luid ); + void UpdateScoreboardSelection(); + +protected: + + int redScore; + int blueScore; + int activationScreen; + + idList< mpScoreboardInfo > scoreboardInfo; + idList< scoreboardInfo_t, TAG_IDLIB_LIST_MENU > redInfo; + idList< scoreboardInfo_t, TAG_IDLIB_LIST_MENU> blueInfo; + +}; + + +#endif //__MENUDATA_H__ \ No newline at end of file diff --git a/neo/d3xp/menus/MenuHandler_HUD.cpp b/neo/d3xp/menus/MenuHandler_HUD.cpp new file mode 100644 index 00000000..49307c85 --- /dev/null +++ b/neo/d3xp/menus/MenuHandler_HUD.cpp @@ -0,0 +1,182 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int TIP_DISPLAY_TIME = 5000; + +/* +======================== +idMenuHandler_HUD::Update +======================== +*/ +void idMenuHandler_HUD::Update() { + + if ( gui == NULL || !gui->IsActive() ) { + return; + } + + if ( nextScreen != activeScreen ) { + + if ( activeScreen > HUD_AREA_INVALID && activeScreen < HUD_NUM_AREAS && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + } + + if ( nextScreen > HUD_AREA_INVALID && nextScreen < HUD_NUM_AREAS && menuScreens[ nextScreen ] != NULL ) { + menuScreens[ nextScreen ]->ShowScreen( static_cast(transition) ); + } + + transition = MENU_TRANSITION_INVALID; + activeScreen = nextScreen; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + if ( player->IsTipVisible() && autoHideTip && !hiding ) { + if ( gameLocal.time >= tipStartTime + TIP_DISPLAY_TIME ) { + player->HideTip(); + } + } + + if ( player->IsSoundChannelPlaying( SND_CHANNEL_PDA_AUDIO ) && GetHud() != NULL ) { + GetHud()->UpdateAudioLog( true ); + } else { + GetHud()->UpdateAudioLog( false ); + } + + if ( radioMessage ) { + GetHud()->UpdateCommunication( true, player ); + } else { + GetHud()->UpdateCommunication( false, player ); + } + + } + + idMenuHandler::Update(); +} + +/* +======================== +idMenuHandler_HUD::ActivateMenu +======================== +*/ +void idMenuHandler_HUD::ActivateMenu( bool show ) { + + idMenuHandler::ActivateMenu( show ); + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + if ( show ) { + activeScreen = HUD_AREA_INVALID; + nextScreen = HUD_AREA_PLAYING; + } else { + activeScreen = HUD_AREA_INVALID; + nextScreen = HUD_AREA_INVALID; + } + +} + +/* +======================== +idMenuHandler_HUD::Initialize +======================== +*/ +void idMenuHandler_HUD::Initialize( const char * swfFile, idSoundWorld * sw ) { + idMenuHandler::Initialize( swfFile, sw ); + + //--------------------- + // Initialize the menus + //--------------------- +#define BIND_HUD_SCREEN( screenId, className, menuHandler ) \ + menuScreens[ (screenId) ] = new className(); \ + menuScreens[ (screenId) ]->Initialize( menuHandler ); \ + menuScreens[ (screenId) ]->AddRef(); + + for ( int i = 0; i < HUD_NUM_AREAS; ++i ) { + menuScreens[ i ] = NULL; + } + + BIND_HUD_SCREEN( HUD_AREA_PLAYING, idMenuScreen_HUD, this ); +} + +/* +======================== +idMenuHandler_HUD::GetMenuScreen +======================== +*/ +idMenuScreen * idMenuHandler_HUD::GetMenuScreen( int index ) { + + if ( index < 0 || index >= HUD_NUM_AREAS ) { + return NULL; + } + + return menuScreens[ index ]; + +} + +/* +======================== +idMenuHandler_HUD::GetHud +======================== +*/ +idMenuScreen_HUD * idMenuHandler_HUD::GetHud() { + idMenuScreen_HUD * screen = dynamic_cast< idMenuScreen_HUD * >( menuScreens[ HUD_AREA_PLAYING ] ); + return screen; +} + +/* +======================== +idMenuHandler_HUD::ShowTip +======================== +*/ +void idMenuHandler_HUD::ShowTip( const char * title, const char * tip, bool autoHide ) { + autoHideTip = autoHideTip; + tipStartTime = gameLocal.time; + hiding = false; + idMenuScreen_HUD * screen = GetHud(); + if ( screen != NULL ) { + screen->ShowTip( title, tip ); + } +} + +/* +======================== +idMenuHandler_HUD::HideTip +======================== +*/ +void idMenuHandler_HUD::HideTip() { + idMenuScreen_HUD * screen = GetHud(); + if ( screen != NULL && !hiding ) { + screen->HideTip(); + } + hiding = true; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuHandler_PDA.cpp b/neo/d3xp/menus/MenuHandler_PDA.cpp new file mode 100644 index 00000000..acadfb07 --- /dev/null +++ b/neo/d3xp/menus/MenuHandler_PDA.cpp @@ -0,0 +1,599 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int MAX_PDA_ITEMS = 15; +static const int MAX_NAV_OPTIONS = 4; + +/* +======================== +idMenuHandler_PDA::Update +======================== +*/ +void idMenuHandler_PDA::Update() { + + if ( gui == NULL || !gui->IsActive() ) { + return; + } + + if ( activeScreen != nextScreen ) { + + if ( nextScreen == PDA_AREA_INVALID ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * >( GetChildFromIndex( PDA_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + cmdBar->Update(); + } + + idSWFSpriteInstance * menu = gui->GetRootObject().GetNestedSprite( "navBar" ); + idSWFSpriteInstance * bg = gui->GetRootObject().GetNestedSprite( "background" ); + idSWFSpriteInstance * edging = gui->GetRootObject().GetNestedSprite( "_fullScreen" ); + + if ( menu != NULL ) { + menu->PlayFrame( "rollOff" ); + } + + if ( bg != NULL ) { + bg->PlayFrame( "rollOff" ); + } + + if ( edging != NULL ) { + edging->PlayFrame( "rollOff" ); + } + + } else { + if ( activeScreen > PDA_AREA_INVALID && activeScreen < PDA_NUM_AREAS && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + } + + if ( nextScreen > PDA_AREA_INVALID && nextScreen < PDA_NUM_AREAS && menuScreens[ nextScreen ] != NULL ) { + menuScreens[ nextScreen ]->UpdateCmds(); + menuScreens[ nextScreen ]->ShowScreen( static_cast(transition) ); + } + } + + transition = MENU_TRANSITION_INVALID; + activeScreen = nextScreen; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + if ( activeScreen == PDA_AREA_USER_DATA ) { + bool isPlaying = player->IsSoundChannelPlaying( SND_CHANNEL_PDA_AUDIO ); + UpdateAudioLogPlaying( isPlaying ); + } + + if ( activeScreen == PDA_AREA_VIDEO_DISKS ) { + bool isPlaying = player->IsSoundChannelPlaying( SND_CHANNEL_PDA_VIDEO ); + UdpateVideoPlaying( isPlaying ); + } + } + + idMenuHandler::Update(); +} + +/* +================================================ +idMenuHandler::TriggerMenu +================================================ +*/ +void idMenuHandler_PDA::TriggerMenu() { + nextScreen = PDA_AREA_USER_DATA; + transition = MENU_TRANSITION_FORCE; +} + +/* +======================== +idMenuHandler_PDA::ActivateMenu +======================== +*/ +void idMenuHandler_PDA::ActivateMenu( bool show ) { + idMenuHandler::ActivateMenu( show ); + + if ( show ) { + // Add names to pda + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + pdaNames.Clear(); + for ( int j = 0; j < player->GetInventory().pdas.Num(); j++ ) { + const idDeclPDA * pda = player->GetInventory().pdas[ j ]; + idList< idStr > names; + names.Append( pda->GetPdaName() ); + pdaNames.Append( names ); + } + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + pdaList->SetListData( pdaNames ); + } + + navOptions.Clear(); + navOptions.Append( idLocalization::GetString( "#str_04190" ) ); + navOptions.Append( idLocalization::GetString( "#str_01442" ) ); + navOptions.Append( idLocalization::GetString( "#str_01440" ) ); + navOptions.Append( idLocalization::GetString( "#str_01414" ) ); + idMenuWidget_NavBar * navBar = dynamic_cast< idMenuWidget_NavBar * >( GetChildFromIndex( PDA_WIDGET_NAV_BAR ) ); + if ( navBar != NULL ) { + navBar->SetListHeadings( navOptions ); + navBar->SetFocusIndex( 0 ); + navBar->Update(); + } + + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * >( GetChildFromIndex( PDA_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + cmdBar->Update(); + } + + } else { + nextScreen = PDA_AREA_INVALID; + activeScreen = PDA_AREA_INVALID; + } + +} + +/* +======================== +idMenuHandler_PDA::Initialize +======================== +*/ +void idMenuHandler_PDA::Initialize( const char * swfFile, idSoundWorld * sw ) { + idMenuHandler::Initialize( swfFile, sw ); + + //--------------------- + // Initialize the menus + //--------------------- +#define BIND_PDA_SCREEN( screenId, className, menuHandler ) \ + menuScreens[ (screenId) ] = new (TAG_SWF) className(); \ + menuScreens[ (screenId) ]->Initialize( menuHandler ); \ + menuScreens[ (screenId) ]->AddRef(); \ + menuScreens[ (screenId) ]->SetNoAutoFree( true ); + + for ( int i = 0; i < PDA_NUM_AREAS; ++i ) { + menuScreens[ i ] = NULL; + } + + BIND_PDA_SCREEN( PDA_AREA_USER_DATA, idMenuScreen_PDA_UserData, this ); + BIND_PDA_SCREEN( PDA_AREA_USER_EMAIL, idMenuScreen_PDA_UserEmails, this ); + BIND_PDA_SCREEN( PDA_AREA_VIDEO_DISKS, idMenuScreen_PDA_VideoDisks, this ); + BIND_PDA_SCREEN( PDA_AREA_INVENTORY, idMenuScreen_PDA_Inventory, this ); + + + pdaScrollBar.SetSpritePath( "pda_persons", "info", "scrollbar" ); + pdaScrollBar.Initialize( this ); + pdaScrollBar.SetNoAutoFree( true ); + + pdaList.SetSpritePath( "pda_persons", "info", "list" ); + pdaList.SetNumVisibleOptions( MAX_PDA_ITEMS ); + pdaList.SetWrappingAllowed( true ); + pdaList.SetNoAutoFree( true ); + + while ( pdaList.GetChildren().Num() < MAX_PDA_ITEMS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PDA_SELECT_USER, pdaList.GetChildren().Num() ); + buttonWidget->Initialize( this ); + if ( menuScreens[ PDA_AREA_USER_DATA ] != NULL ) { + idMenuScreen_PDA_UserData * userDataScreen = dynamic_cast< idMenuScreen_PDA_UserData * >( menuScreens[ PDA_AREA_USER_DATA ] ); + if ( userDataScreen != NULL ) { + buttonWidget->RegisterEventObserver( userDataScreen->GetUserData() ); + buttonWidget->RegisterEventObserver( userDataScreen->GetObjective() ); + buttonWidget->RegisterEventObserver( userDataScreen->GetAudioFiles() ); + } + } + if ( menuScreens[ PDA_AREA_USER_EMAIL ] != NULL ) { + idMenuScreen_PDA_UserEmails * userEmailScreen = dynamic_cast< idMenuScreen_PDA_UserEmails * >( menuScreens[ PDA_AREA_USER_EMAIL ] ); + if ( userEmailScreen != NULL ) { + buttonWidget->RegisterEventObserver( &userEmailScreen->GetInbox() ); + buttonWidget->RegisterEventObserver( userEmailScreen ); + } + } + buttonWidget->RegisterEventObserver( &pdaScrollBar ); + pdaList.AddChild( buttonWidget ); + } + pdaList.AddChild( &pdaScrollBar ); + pdaList.Initialize( this ); + + navBar.SetSpritePath( "navBar", "options" ); + navBar.Initialize( this ); + navBar.SetNumVisibleOptions( MAX_NAV_OPTIONS ); + navBar.SetWrappingAllowed( true ); + navBar.SetButtonSpacing( 20.0f, 25.0f, 75.0f ); + navBar.SetInitialXPos( 40.0f ); + navBar.SetNoAutoFree( true ); + for ( int count = 0; count < ( MAX_NAV_OPTIONS * 2 - 1 ); ++count ) { + idMenuWidget_NavButton * const navButton = new (TAG_SWF) idMenuWidget_NavButton(); + + if ( count < MAX_NAV_OPTIONS - 1 ) { + navButton->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PDA_SELECT_NAV, count ); + } else if ( count < ( ( MAX_NAV_OPTIONS - 1 ) * 2 ) ) { + int index = ( count - ( MAX_NAV_OPTIONS - 1 ) ) + 1; + navButton->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PDA_SELECT_NAV, index ); + } else { + navButton->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PDA_SELECT_NAV, -1 ); + } + + navBar.AddChild( navButton ); + } + + // + // command bar + // + commandBarWidget.SetAlignment( idMenuWidget_CommandBar::LEFT ); + commandBarWidget.SetSpritePath( "prompts" ); + commandBarWidget.Initialize( this ); + commandBarWidget.SetNoAutoFree( true ); + + AddChild( &navBar ); + AddChild( &pdaList ); + AddChild( &pdaScrollBar ); + AddChild( &commandBarWidget ); + + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + pdaList.AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + + menuScreens[ PDA_AREA_USER_DATA ]->RegisterEventObserver( &pdaList ); + menuScreens[ PDA_AREA_USER_EMAIL ]->RegisterEventObserver( &pdaList ); + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + + for ( int j = 0; j < MAX_WEAPONS; j++ ) { + const char * weaponDefName = va( "def_weapon%d", j ); + const char *weap = player->spawnArgs.GetString( weaponDefName ); + if ( weap != NULL && *weap != NULL ) { + const idDeclEntityDef * weaponDef = gameLocal.FindEntityDef( weap, false ); + if ( weaponDef != NULL ) { + declManager->FindMaterial( weaponDef->dict.GetString( "pdaIcon" ) ); + declManager->FindMaterial( weaponDef->dict.GetString( "hudIcon" ) ); + } + } + } + + } + + class idPDAGGUIClose : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + player->TogglePDA(); + } + return idSWFScriptVar(); + } + }; + + if ( gui != NULL ) { + gui->SetGlobal( "closePDA", new idPDAGGUIClose() ); + } + + // precache sounds + // don't load gui music for the pause menu to save some memory + const idSoundShader * soundShader = NULL; + soundShader = declManager->FindSound( "gui/list_scroll", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_SCROLL ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_PDA_advance", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ADVANCE ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_PDA_back", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BACK ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/pda_next_tab", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BUILD_ON ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/pda_prev_tab", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BUILD_OFF ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_set_focus", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_FOCUS ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_roll_over", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ROLL_OVER ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_roll_out", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ROLL_OUT ] = soundShader->GetName(); + } +} + +/* +======================== +idMenuHandler_PDA::HandleAction +======================== +*/ +bool idMenuHandler_PDA::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( activeScreen == PDA_AREA_INVALID ) { + return true; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + if ( event.type == WIDGET_EVENT_COMMAND ) { + if ( menuScreens[ activeScreen ] != NULL && !forceHandled ) { + if ( menuScreens[ activeScreen ]->HandleAction( action, event, widget, true ) ) { + if ( actionType == WIDGET_ACTION_GO_BACK ) { + PlaySound( GUI_SOUND_BACK ); + } else { + PlaySound( GUI_SOUND_ADVANCE ); + } + return true; + } + } + } + + switch ( actionType ) { + case WIDGET_ACTION_PDA_SELECT_USER: { + int index = parms[0].ToInteger(); + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + pdaList->SetViewIndex( pdaList->GetViewOffset() + index ); + pdaList->SetFocusIndex( index ); + } + return true; + } + case WIDGET_ACTION_SCROLL_TAB: { + + if ( transition != MENU_TRANSITION_INVALID ) { + return true; + } + int delta = parms[0].ToInteger(); + idMenuWidget_NavBar * navBar = dynamic_cast< idMenuWidget_NavBar * >( GetChildFromIndex( PDA_WIDGET_NAV_BAR ) ); + if ( navBar != NULL ) { + int focused = navBar->GetFocusIndex(); + focused += delta; + if ( focused < 0 ) { + focused = navBar->GetNumVisibleOptions() - 1; + } else if ( focused >= navBar->GetNumVisibleOptions() ) { + focused = 0; + } + + navBar->SetViewIndex( focused ); + navBar->SetFocusIndex( focused, true ); + navBar->Update(); + + nextScreen = activeScreen + delta; + if ( nextScreen < 0 ) { + nextScreen = PDA_NUM_AREAS - 1; + } else if ( nextScreen == PDA_NUM_AREAS ) { + nextScreen = 0; + } + + if ( delta < 0 ) { + transition = MENU_TRANSITION_BACK; + } else { + transition = MENU_TRANSITION_ADVANCE; + } + + } + return true; + } + case WIDGET_ACTION_PDA_SELECT_NAV: { + int index = parms[0].ToInteger(); + + if ( index == -1 && activeScreen == PDA_AREA_USER_EMAIL ) { + idMenuScreen_PDA_UserEmails * screen = dynamic_cast< idMenuScreen_PDA_UserEmails * const >( menuScreens[ PDA_AREA_USER_EMAIL ] ); + if ( screen ) { + screen->ShowEmail( false ); + } + return true; + } + + // click on the current nav tab + if ( index == -1 ) { + return true; + } + + idMenuWidget_NavBar * navBar = dynamic_cast< idMenuWidget_NavBar * >( GetChildFromIndex( PDA_WIDGET_NAV_BAR ) ); + if ( navBar != NULL ) { + navBar->SetViewIndex( navBar->GetViewOffset() + index ); + navBar->SetFocusIndex( index, true ); + navBar->Update(); + + if ( index < activeScreen ) { + nextScreen = index; + transition = MENU_TRANSITION_BACK; + } else if ( index > activeScreen ) { + nextScreen = index; + transition = MENU_TRANSITION_ADVANCE; + } + } + return true; + } + case WIDGET_ACTION_SELECT_PDA_AUDIO: { + if ( activeScreen == PDA_AREA_USER_DATA ) { + int index = parms[0].ToInteger(); + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + + bool change = false; + if ( pdaList != NULL ) { + int pdaIndex = pdaList->GetViewIndex(); + change = PlayPDAAudioLog( pdaIndex, index ); + } + + if ( change ) { + if ( widget->GetParent() != NULL ) { + idMenuWidget_DynamicList * audioList = dynamic_cast< idMenuWidget_DynamicList * >( widget->GetParent() ); + int index = parms[0].ToInteger(); + if ( audioList != NULL ) { + audioList->SetFocusIndex( index ); + } + } + } + } + + return true; + } + case WIDGET_ACTION_SELECT_PDA_VIDEO: { + if ( activeScreen == PDA_AREA_VIDEO_DISKS ) { + int index = parms[0].ToInteger(); + if ( menuScreens[ PDA_AREA_VIDEO_DISKS ] != NULL ) { + idMenuScreen_PDA_VideoDisks * screen = dynamic_cast< idMenuScreen_PDA_VideoDisks * const >( menuScreens[ PDA_AREA_VIDEO_DISKS ] ); + if ( screen != NULL ) { + screen->SelectedVideoToPlay( index ); + } + } + } + return true; + } + } + + return idMenuHandler::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuHandler_PDA::PlayPDAAudioLog +======================== +*/ +bool idMenuHandler_PDA::PlayPDAAudioLog( int pdaIndex, int audioIndex ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + if ( pda != NULL && pda->GetNumAudios() > audioIndex ) { + const idDeclAudio *aud = pda->GetAudioByIndex( audioIndex ); + + if ( audioFile == aud ) { + player->EndAudioLog(); + return true; + } else if ( aud != NULL ) { + audioFile = aud; + player->EndAudioLog(); + player->PlayAudioLog( aud->GetWave() ); + return true; + } + } + } + return false; +} + +/* +======================== +idMenuHandler_PDA::GetMenuScreen +======================== +*/ +idMenuScreen * idMenuHandler_PDA::GetMenuScreen( int index ) { + + if ( index < 0 || index >= PDA_NUM_AREAS ) { + return NULL; + } + + return menuScreens[ index ]; + +} + +/* +======================== +idMenuHandler_PDA::GetMenuScreen +======================== +*/ +void idMenuHandler_PDA::UpdateAudioLogPlaying( bool playing ) { + + if ( playing != audioLogPlaying && activeScreen == PDA_AREA_USER_DATA && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->Update(); + } + + audioLogPlaying = playing; + if ( !playing ) { + audioFile = NULL; + } +} + +/* +======================== +idMenuHandler_PDA::GetMenuScreen +======================== +*/ +void idMenuHandler_PDA::UdpateVideoPlaying( bool playing ) { + + if ( playing != videoPlaying ) { + if ( activeScreen == PDA_AREA_VIDEO_DISKS && menuScreens[ activeScreen ] != NULL ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( !playing ) { + player->EndVideoDisk(); + } + + idMenuScreen_PDA_VideoDisks * screen = dynamic_cast< idMenuScreen_PDA_VideoDisks * const >( menuScreens[ PDA_AREA_VIDEO_DISKS ] ); + if ( screen != NULL ) { + if ( !playing ) { + screen->ClearActiveVideo(); + } + screen->Update(); + } + } + + videoPlaying = playing; + } +} + + /* + ================================================ + idMenuHandler_PDA::Cleanup + ================================================ + */ + void idMenuHandler_PDA::Cleanup() { + idMenuHandler::Cleanup(); + for ( int index = 0; index < MAX_SCREEN_AREAS; ++index ) { + delete menuScreens[ index ]; + menuScreens[ index ] = NULL; + } + } + + + /* + ================================================ + idMenuHandler_PDA::~idMenuHandler_PDA + ================================================ + */ + idMenuHandler_PDA::~idMenuHandler_PDA() { + pdaScrollBar.Cleanup(); + pdaList.Cleanup(); + navBar.Cleanup(); + commandBarWidget.Cleanup(); + Cleanup(); + } \ No newline at end of file diff --git a/neo/d3xp/menus/MenuHandler_Scoreboard.cpp b/neo/d3xp/menus/MenuHandler_Scoreboard.cpp new file mode 100644 index 00000000..611380a5 --- /dev/null +++ b/neo/d3xp/menus/MenuHandler_Scoreboard.cpp @@ -0,0 +1,510 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuHandler_Scoreboard::Update +======================== +*/ +void idMenuHandler_Scoreboard::Update() { + + if ( gui == NULL || !gui->IsActive() ) { + return; + } + + if ( nextScreen != activeScreen ) { + + if ( nextScreen == SCOREBOARD_AREA_INVALID ) { + + if ( activeScreen > SCOREBOARD_AREA_INVALID && activeScreen < SCOREBOARD_NUM_AREAS && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + } + + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * >( GetChildFromIndex( SCOREBOARD_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + cmdBar->Update(); + } + + idSWFSpriteInstance * bg = gui->GetRootObject().GetNestedSprite( "background" ); + + if ( bg != NULL ) { + bg->PlayFrame( "rollOff" ); + } + + } else { + + if ( activeScreen > SCOREBOARD_AREA_INVALID && activeScreen < SCOREBOARD_NUM_AREAS && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + } + + if ( nextScreen > SCOREBOARD_AREA_INVALID && nextScreen < SCOREBOARD_NUM_AREAS && menuScreens[ nextScreen ] != NULL ) { + menuScreens[ nextScreen ]->UpdateCmds(); + menuScreens[ nextScreen ]->ShowScreen( static_cast(transition) ); + } + } + + transition = MENU_TRANSITION_INVALID; + activeScreen = nextScreen; + } + + idMenuHandler::Update(); +} + +/* +======================== +idMenuHandler_Scoreboard::ActivateMenu +======================== +*/ +void idMenuHandler_Scoreboard::TriggerMenu() { + nextScreen = activationScreen; +} + +/* +======================== +idMenuHandler_Scoreboard::ActivateMenu +======================== +*/ +void idMenuHandler_Scoreboard::ActivateMenu( bool show ) { + + idMenuHandler::ActivateMenu( show ); + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + if ( show ) { + + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * >( GetChildFromIndex( SCOREBOARD_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + cmdBar->Update(); + } + + nextScreen = SCOREBOARD_AREA_INVALID; + activeScreen = SCOREBOARD_AREA_INVALID; + } else { + activeScreen = SCOREBOARD_AREA_INVALID; + nextScreen = SCOREBOARD_AREA_INVALID; + } + + + class idSWFScriptFunction_activateMenu : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_activateMenu( idMenuHandler * _handler ) { + handler = _handler; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( handler != NULL ) { + handler->TriggerMenu(); + } + + return idSWFScriptVar(); + } + private: + idMenuHandler * handler; + }; + + gui->SetGlobal( "activateMenus", new (TAG_SWF) idSWFScriptFunction_activateMenu( this ) ); + +} + +/* +======================== +idMenuHandler_Scoreboard::Initialize +======================== +*/ +void idMenuHandler_Scoreboard::Initialize( const char * swfFile, idSoundWorld * sw ) { + idMenuHandler::Initialize( swfFile, sw ); + + //--------------------- + // Initialize the menus + //--------------------- +#define BIND_SCOREBOARD_SCREEN( screenId, className, menuHandler ) \ + menuScreens[ (screenId) ] = new className(); \ + menuScreens[ (screenId) ]->Initialize( menuHandler ); \ + menuScreens[ (screenId) ]->AddRef(); + + for ( int i = 0; i < SCOREBOARD_NUM_AREAS; ++i ) { + menuScreens[ i ] = NULL; + } + + BIND_SCOREBOARD_SCREEN( SCOREBOARD_AREA_DEFAULT, idMenuScreen_Scoreboard, this ); + BIND_SCOREBOARD_SCREEN( SCOREBOARD_AREA_TEAM, idMenuScreen_Scoreboard_Team, this ); + + // + // command bar + // + idMenuWidget_CommandBar * const commandBarWidget = new (TAG_SWF) idMenuWidget_CommandBar(); + commandBarWidget->SetAlignment( idMenuWidget_CommandBar::LEFT ); + commandBarWidget->SetSpritePath( "prompts" ); + commandBarWidget->Initialize( this ); + + AddChild( commandBarWidget ); + + class idScoreboardGUIClose : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + gameLocal.mpGame.SetScoreboardActive( false ); + return idSWFScriptVar(); + } + }; + + if ( gui != NULL ) { + gui->SetGlobal( "closeScoreboard", new idScoreboardGUIClose() ); + } + + // precache sounds + // don't load gui music for the pause menu to save some memory + const idSoundShader * soundShader = NULL; + soundShader = declManager->FindSound( "gui/list_scroll", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_SCROLL ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_PDA_advance", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ADVANCE ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_PDA_back", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BACK ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/pda_next_tab", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BUILD_ON ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/pda_prev_tab", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BUILD_OFF ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_set_focus", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_FOCUS ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_roll_over", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ROLL_OVER ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_roll_out", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ROLL_OUT ] = soundShader->GetName(); + } +} + +/* +======================== +idMenuHandler_Scoreboard::GetMenuScreen +======================== +*/ +idMenuScreen * idMenuHandler_Scoreboard::GetMenuScreen( int index ) { + + if ( index < 0 || index >= SCOREBOARD_NUM_AREAS ) { + return NULL; + } + + return menuScreens[ index ]; + +} + +/* +======================== +idMenuHandler_Scoreboard::HandleAction +======================== +*/ +bool idMenuHandler_Scoreboard::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( activeScreen == SCOREBOARD_AREA_INVALID ) { + return true; + } + + widgetAction_t actionType = action.GetType(); + //const idSWFParmList & parms = action.GetParms(); + + if ( event.type == WIDGET_EVENT_COMMAND ) { + if ( menuScreens[ activeScreen ] != NULL && !forceHandled ) { + if ( menuScreens[ activeScreen ]->HandleAction( action, event, widget, true ) ) { + if ( actionType == WIDGET_ACTION_GO_BACK ) { + PlaySound( GUI_SOUND_BACK ); + } else { + PlaySound( GUI_SOUND_ADVANCE ); + } + return true; + } + } + } + + return idMenuHandler::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuHandler_Scoreboard::AddPlayerInfo +======================== +*/ +void idMenuHandler_Scoreboard::AddPlayerInfo( int index, voiceStateDisplay_t voiceState, int team, idStr name, int score, int wins, int ping, idStr spectateData ) { + + scoreboardInfo_t info; + idList< idStr > values; + values.Append( name ); + + if ( spectateData.IsEmpty() || gameLocal.mpGame.GetGameState() == idMultiplayerGame::GAMEREVIEW ) { + values.Append( va( "%i", score ) ); + } else { + values.Append( spectateData ); + } + + values.Append( va( "%i", wins ) ); + values.Append( va( "%i", ping ) ); + + info.index = index; + info.voiceState = voiceState; + info.values = values; + + if ( team == 1 ) { + blueInfo.Append( info ); + } else { + redInfo.Append( info ); + } +} + +/* +======================== +idMenuHandler_Scoreboard::UpdateScoreboard +======================== +*/ +void idMenuHandler_Scoreboard::UpdateSpectating( idStr spectate, idStr follow ) { + + if ( nextScreen == SCOREBOARD_AREA_DEFAULT ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_DEFAULT ] ); + if ( screen ) { + screen->UpdateSpectating( spectate, follow ); + } + } else if ( nextScreen == SCOREBOARD_AREA_TEAM ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_TEAM ] ); + if ( screen ) { + screen->UpdateSpectating( spectate, follow ); + } + } +} + +/* +======================== +idMenuHandler_Scoreboard::UpdateScoreboard +======================== +*/ +void idMenuHandler_Scoreboard::UpdateScoreboard( idList< mpScoreboardInfo > & data, idStr gameInfo ) { + + bool changed = false; + if ( data.Num() != scoreboardInfo.Num() ) { + changed = true; + } else { + for ( int i = 0; i < data.Num(); ++i ) { + if ( data[i] != scoreboardInfo[i] ) { + changed = true; + break; + } + } + } + + if ( nextScreen == SCOREBOARD_AREA_DEFAULT ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_DEFAULT ] ); + if ( screen ) { + screen->UpdateGameInfo( gameInfo ); + screen->UpdateTeamScores( redScore, blueScore ); + } + } else if ( nextScreen == SCOREBOARD_AREA_TEAM ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_TEAM ] ); + if ( screen ) { + screen->UpdateGameInfo( gameInfo ); + screen->UpdateTeamScores( redScore, blueScore ); + } + } + + redInfo.Clear(); + blueInfo.Clear(); + for ( int i = 0; i < data.Num(); ++i ) { + AddPlayerInfo( data[i].playerNum, data[i].voiceState, data[i].team, data[i].name, data[i].score, data[i].wins, data[i].ping, data[i].spectateData ); + } + + idList< scoreboardInfo_t, TAG_IDLIB_LIST_MENU > listItemInfo; + for ( int i = 0; i < redInfo.Num(); ++i ) { + listItemInfo.Append( redInfo[i] ); + } + + // add empty items to list + if ( blueInfo.Num() > 0 ) { + while ( listItemInfo.Num() < 4 ) { + scoreboardInfo_t info; + listItemInfo.Append( info ); + } + } + + for ( int i = 0; i < blueInfo.Num(); ++i ) { + listItemInfo.Append( blueInfo[i] ); + } + + while ( listItemInfo.Num() < 8 ) { + scoreboardInfo_t info; + listItemInfo.Append( info ); + } + + if ( nextScreen == SCOREBOARD_AREA_DEFAULT || activationScreen == SCOREBOARD_AREA_DEFAULT ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_DEFAULT ] ); + if ( screen ) { + screen->SetPlayerData( listItemInfo ); + } + } else if ( nextScreen == SCOREBOARD_AREA_TEAM || activationScreen == SCOREBOARD_AREA_TEAM ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_TEAM ] ); + if ( screen ) { + screen->SetPlayerData( listItemInfo ); + } + } + + scoreboardInfo = data; +} + +/* +======================== +idMenuHandler_Scoreboard::SetTeamScore +======================== +*/ +void idMenuHandler_Scoreboard::SetTeamScores( int r, int b ) { + redScore = r; + blueScore = b; +} + +/* +======================== +idMenuHandler_Scoreboard::GetNumPlayers +======================== +*/ +int idMenuHandler_Scoreboard::GetNumPlayers( int team ) { + + if ( team == 1 ) { + return blueInfo.Num(); + } else { + return redInfo.Num(); + } + +} + +/* +======================== +idMenuHandler_Scoreboard::SetActivationScreen +======================== +*/ +void idMenuHandler_Scoreboard::SetActivationScreen( int screen, int trans ) { + activationScreen = screen; + transition = trans; +} + +/* +======================== +idMenuHandler_Scoreboard::GetUserID +======================== +*/ +void idMenuHandler_Scoreboard::GetUserID( int slot, lobbyUserID_t & luid ) { + idList< int > redList; + idList< int > blueList; + + for ( int i = 0; i < scoreboardInfo.Num(); ++i ) { + if ( scoreboardInfo[i].team == 1 ) { + blueList.Append( scoreboardInfo[i].playerNum ); + } else { + redList.Append( scoreboardInfo[i].playerNum ); + } + } + + idList< int > displayList; + + for ( int i = 0; i < redList.Num(); ++i ) { + displayList.Append( redList[ i ] ); + } + + for ( int i = 0; i < blueList.Num(); ++i ) { + displayList.Append( blueList[ i ] ); + } + + if ( slot >= displayList.Num() ) { + return; + } + + luid = gameLocal.lobbyUserIDs[ displayList[ slot ] ]; +} + +/* +======================== +idMenuHandler_Scoreboard::ViewPlayerProfile +======================== +*/ +void idMenuHandler_Scoreboard::ViewPlayerProfile( int slot ) { + + lobbyUserID_t luid; + GetUserID( slot, luid ); + if ( luid.IsValid() ) { + session->ShowLobbyUserGamerCardUI( luid ); + } +} + +/* +======================== +idMenuHandler_Scoreboard::MutePlayer +======================== +*/ +void idMenuHandler_Scoreboard::MutePlayer( int slot ) { + + lobbyUserID_t luid; + GetUserID( slot, luid ); + if ( luid.IsValid() ) { + session->ToggleLobbyUserVoiceMute( luid ); + } +} + +/* +======================== +idMenuHandler_Scoreboard::UpdateScoreboardSelection +======================== +*/ +void idMenuHandler_Scoreboard::UpdateScoreboardSelection() { + + if ( nextScreen == SCOREBOARD_AREA_DEFAULT || activationScreen == SCOREBOARD_AREA_DEFAULT ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_DEFAULT ] ); + if ( screen ) { + screen->UpdateHighlight(); + } + } else if ( nextScreen == SCOREBOARD_AREA_TEAM || activationScreen == SCOREBOARD_AREA_TEAM ) { + idMenuScreen_Scoreboard * screen = dynamic_cast< idMenuScreen_Scoreboard * >( menuScreens[ SCOREBOARD_AREA_TEAM ] ); + if ( screen ) { + screen->UpdateHighlight(); + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuHandler_Shell.cpp b/neo/d3xp/menus/MenuHandler_Shell.cpp new file mode 100644 index 00000000..b1be842a --- /dev/null +++ b/neo/d3xp/menus/MenuHandler_Shell.cpp @@ -0,0 +1,1568 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + + +extern idCVar g_demoMode; + +static const int PEER_UPDATE_INTERVAL = 500; +static const int MAX_MENU_OPTIONS = 6; + +void idMenuHandler_Shell::Update() { + +//#if defined ( ID_360 ) +// if ( deviceRequestedSignal.Wait( 0 ) ) { +// // This clears the delete save dialog to catch the case of a delete confirmation for an old device after we've changed the device. +// common->Dialog().ClearDialog( GDM_DELETE_SAVE ); +// common->Dialog().ClearDialog( GDM_DELETE_CORRUPT_SAVEGAME ); +// common->Dialog().ClearDialog( GDM_RESTORE_CORRUPT_SAVEGAME ); +// common->Dialog().ClearDialog( GDM_LOAD_DAMAGED_FILE ); +// common->Dialog().ClearDialog( GDM_OVERWRITE_SAVE ); +// +// } +//#endif + + if ( gui == NULL || !gui->IsActive() ) { + return; + } + + if ( ( IsPacifierVisible() || common->Dialog().IsDialogActive() ) && actionRepeater.isActive ) { + ClearWidgetActionRepeater(); + } + + if ( nextState != state ) { + + if ( introGui != NULL && introGui->IsActive() ) { + gui->StopSound(); + showingIntro = false; + introGui->Activate( false ); + PlaySound( GUI_SOUND_MUSIC ); + } + + if ( nextState == SHELL_STATE_PRESS_START ) { + HidePacifier(); + nextScreen = SHELL_AREA_START; + transition = MENU_TRANSITION_SIMPLE; + state = nextState; + if ( menuBar != NULL && gui != NULL ) { + menuBar->ClearSprite(); + } + } else if ( nextState == SHELL_STATE_IDLE ) { + HidePacifier(); + if ( nextScreen == SHELL_AREA_START || nextScreen == SHELL_AREA_PARTY_LOBBY || nextScreen == SHELL_AREA_GAME_LOBBY || nextScreen == SHELL_AREA_INVALID ) { + nextScreen = SHELL_AREA_ROOT; + } + + if ( menuBar != NULL && gui != NULL ) { + idSWFScriptObject & root = gui->GetRootObject(); + menuBar->BindSprite( root ); + SetupPCOptions(); + } + transition = MENU_TRANSITION_SIMPLE; + state = nextState; + } else if ( nextState == SHELL_STATE_PARTY_LOBBY ) { + HidePacifier(); + nextScreen = SHELL_AREA_PARTY_LOBBY; + transition = MENU_TRANSITION_SIMPLE; + state = nextState; + } else if ( nextState == SHELL_STATE_GAME_LOBBY ) { + HidePacifier(); + if ( state != SHELL_STATE_IN_GAME ) { + timeRemaining = WAIT_START_TIME_LONG; + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + /*if ( MatchTypeIsPrivate( matchParameters.matchFlags ) && ActiveScreen() == SHELL_AREA_PARTY_LOBBY ) { + timeRemaining = 0; + session->StartMatch(); + state = SHELL_STATE_IN_GAME; + } else {*/ + nextScreen = SHELL_AREA_GAME_LOBBY; + transition = MENU_TRANSITION_SIMPLE; + //} + + state = nextState; + } + } else if ( nextState == SHELL_STATE_PAUSED ) { + HidePacifier(); + transition = MENU_TRANSITION_SIMPLE; + + if ( gameComplete ) { + nextScreen = SHELL_AREA_CREDITS; + } else { + nextScreen = SHELL_AREA_ROOT; + } + + state = nextState; + } else if ( nextState == SHELL_STATE_CONNECTING ) { + ShowPacifier( "#str_dlg_connecting" ); + state = nextState; + } else if ( nextState == SHELL_STATE_SEARCHING ) { + ShowPacifier( "#str_online_mpstatus_searching" ); + state = nextState; + } + } + + if ( activeScreen != nextScreen ) { + + ClearWidgetActionRepeater(); + UpdateBGState(); + + if ( nextScreen == SHELL_AREA_INVALID ) { + + if ( activeScreen > SHELL_AREA_INVALID && activeScreen < SHELL_NUM_AREAS && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + } + + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + cmdBar->Update(); + } + + idSWFSpriteInstance * bg = gui->GetRootObject().GetNestedSprite( "pause_bg" ); + idSWFSpriteInstance * edging = gui->GetRootObject().GetNestedSprite( "_fullscreen" ); + + if ( bg != NULL ) { + bg->PlayFrame( "rollOff" ); + } + + if ( edging != NULL ) { + edging->PlayFrame( "rollOff" ); + } + + } else { + + if ( activeScreen > SHELL_AREA_INVALID && activeScreen < SHELL_NUM_AREAS && menuScreens[ activeScreen ] != NULL ) { + menuScreens[ activeScreen ]->HideScreen( static_cast(transition) ); + } + + if ( nextScreen > SHELL_AREA_INVALID && nextScreen < SHELL_NUM_AREAS && menuScreens[ nextScreen ] != NULL ) { + menuScreens[ nextScreen ]->UpdateCmds(); + menuScreens[ nextScreen ]->ShowScreen( static_cast(transition) ); + } + } + + transition = MENU_TRANSITION_INVALID; + activeScreen = nextScreen; + } + + if ( cmdBar != NULL && cmdBar->GetSprite() ) { + if ( common->Dialog().IsDialogActive() ) { + cmdBar->GetSprite()->SetVisible( false ); + } else { + cmdBar->GetSprite()->SetVisible( true ); + } + } + + idMenuHandler::Update(); + + if ( activeScreen == nextScreen && activeScreen == SHELL_AREA_LEADERBOARDS ) { + idMenuScreen_Shell_Leaderboards * screen = dynamic_cast< idMenuScreen_Shell_Leaderboards * >( menuScreens[ SHELL_AREA_LEADERBOARDS ] ); + if ( screen != NULL ) { + screen->PumpLBCache(); + screen->RefreshLeaderboard(); + } + } else if ( activeScreen == nextScreen && activeScreen == SHELL_AREA_PARTY_LOBBY ) { + idMenuScreen_Shell_PartyLobby * screen = dynamic_cast< idMenuScreen_Shell_PartyLobby * >( menuScreens[ SHELL_AREA_PARTY_LOBBY ] ); + if ( screen != NULL ) { + screen->UpdateLobby(); + } + } else if ( activeScreen == nextScreen && activeScreen == SHELL_AREA_GAME_LOBBY ) { + if ( session->GetActingGameStateLobbyBase().IsHost() ) { + + if ( timeRemaining <= 0 && state != SHELL_STATE_IN_GAME ) { + session->StartMatch(); + state = SHELL_STATE_IN_GAME; + } + + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + if ( !MatchTypeIsPrivate( matchParameters.matchFlags ) ) { + if ( Sys_Milliseconds() >= nextPeerUpdateMs ) { + nextPeerUpdateMs = Sys_Milliseconds() + PEER_UPDATE_INTERVAL; + byte buffer[ 128 ]; + idBitMsg msg; + msg.InitWrite( buffer, sizeof( buffer ) ); + msg.WriteLong( timeRemaining ); + session->GetActingGameStateLobbyBase().SendReliable( GAME_RELIABLE_MESSAGE_LOBBY_COUNTDOWN, msg, false ); + } + } + } + + idMenuScreen_Shell_GameLobby * screen = dynamic_cast< idMenuScreen_Shell_GameLobby * >( menuScreens[ SHELL_AREA_GAME_LOBBY ] ); + if ( screen != NULL ) { + screen->UpdateLobby(); + } + } + + if ( introGui != NULL && introGui->IsActive() ) { + introGui->Render( renderSystem, Sys_Milliseconds() ); + } + + if ( continueWaitForEnumerate ) { + if ( !session->GetSaveGameManager().IsWorking() ) { + continueWaitForEnumerate = false; + common->Dialog().ClearDialog( GDM_REFRESHING ); + idMenuScreen_Shell_Singleplayer * screen = dynamic_cast< idMenuScreen_Shell_Singleplayer * >( menuScreens[ SHELL_AREA_CAMPAIGN ] ); + if ( screen != NULL ) { + screen->ContinueGame(); + } + } + } +} + +/* +======================== +idMenuHandler_Shell::SetCanContinue +======================== +*/ +void idMenuHandler_Shell::SetCanContinue( bool valid ) { + + idMenuScreen_Shell_Singleplayer * screen = dynamic_cast< idMenuScreen_Shell_Singleplayer * >( menuScreens[ SHELL_AREA_CAMPAIGN ] ); + if ( screen != NULL ) { + screen->SetCanContinue( valid ); + } + +} + +/* +======================== +idMenuHandler_Shell::HandleGuiEvent +======================== +*/ +bool idMenuHandler_Shell::HandleGuiEvent( const sysEvent_t * sev ) { + + if ( IsPacifierVisible() ) { + return true; + } + + if ( showingIntro ) { + return true; + } + + if ( waitForBinding ) { + + if ( sev->evType == SE_KEY && sev->evValue2 == 1 ) { + + if ( sev->evValue >= K_JOY_STICK1_UP && sev->evValue <= K_JOY_STICK2_RIGHT ) { + return true; + } + + if ( sev->evValue == K_ESCAPE ) { + waitForBinding = false; + + idMenuScreen_Shell_Bindings * bindScreen = dynamic_cast< idMenuScreen_Shell_Bindings * >( menuScreens[ SHELL_AREA_KEYBOARD ] ); + if ( bindScreen != NULL ) { + bindScreen->ToggleWait( false ); + bindScreen->Update(); + } + + } else { + + if ( idStr::Icmp( idKeyInput::GetBinding( sev->evValue ), "" ) == 0 ) { // no existing binding found + + idKeyInput::SetBinding( sev->evValue, waitBind ); + + idMenuScreen_Shell_Bindings * bindScreen = dynamic_cast< idMenuScreen_Shell_Bindings * >( menuScreens[ SHELL_AREA_KEYBOARD ] ); + if ( bindScreen != NULL ) { + bindScreen->SetBindingChanged( true ); + bindScreen->UpdateBindingDisplay(); + bindScreen->ToggleWait( false ); + bindScreen->Update(); + } + + waitForBinding = false; + + } else { // binding found prompt to change + + const char * curBind = idKeyInput::GetBinding( sev->evValue ); + + if ( idStr::Icmp( waitBind, curBind ) == 0 ) { + idKeyInput::SetBinding( sev->evValue, "" ); + idMenuScreen_Shell_Bindings * bindScreen = dynamic_cast< idMenuScreen_Shell_Bindings * >( menuScreens[ SHELL_AREA_KEYBOARD ] ); + if ( bindScreen != NULL ) { + bindScreen->SetBindingChanged( true ); + bindScreen->UpdateBindingDisplay(); + bindScreen->ToggleWait( false ); + bindScreen->Update(); + waitForBinding = false; + } + } else { + + idMenuScreen_Shell_Bindings * bindScreen = dynamic_cast< idMenuScreen_Shell_Bindings * >( menuScreens[ SHELL_AREA_KEYBOARD ] ); + if ( bindScreen != NULL ) { + class idSWFScriptFunction_RebindKey : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_RebindKey( idMenuScreen_Shell_Bindings * _menu, gameDialogMessages_t _msg, bool _accept, idMenuHandler_Shell * _mgr, int _key, const char * _bind ) { + menu = _menu; + msg = _msg; + accept = _accept; + mgr = _mgr; + key = _key; + bind = _bind; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + mgr->ClearWaitForBinding(); + menu->ToggleWait( false ); + if ( accept ) { + idKeyInput::SetBinding( key, bind ); + menu->SetBindingChanged( true ); + menu->UpdateBindingDisplay(); + menu->Update(); + } + return idSWFScriptVar(); + } + private: + idMenuScreen_Shell_Bindings * menu; + gameDialogMessages_t msg; + bool accept; + idMenuHandler_Shell * mgr; + int key; + const char * bind; + }; + + common->Dialog().AddDialog( GDM_BINDING_ALREDY_SET, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_RebindKey( bindScreen, GDM_BINDING_ALREDY_SET, true, this, sev->evValue, waitBind ), new idSWFScriptFunction_RebindKey( bindScreen, GDM_BINDING_ALREDY_SET, false, this, sev->evValue, waitBind ), false ); + } + + } + } + } + } + + return true; + } + + return idMenuHandler::HandleGuiEvent( sev ); + +} + +/* +======================== +idMenuHandler_Shell::Initialize +======================== +*/ +void idMenuHandler_Shell::Initialize( const char * swfFile, idSoundWorld * sw ) { + idMenuHandler::Initialize( swfFile, sw ); + + //--------------------- + // Initialize the menus + //--------------------- +#define BIND_SHELL_SCREEN( screenId, className, menuHandler ) \ + menuScreens[ (screenId) ] = new (TAG_SWF) className(); \ + menuScreens[ (screenId) ]->Initialize( menuHandler ); \ + menuScreens[ (screenId) ]->AddRef(); + + for ( int i = 0; i < SHELL_NUM_AREAS; ++i ) { + menuScreens[ i ] = NULL; + } + + // done for build game purposes so these get touched + delete new idSWF( "doomIntro", NULL ); + delete new idSWF( "roeIntro", NULL ); + delete new idSWF( "leIntro", NULL ); + + if ( inGame ) { + BIND_SHELL_SCREEN( SHELL_AREA_ROOT, idMenuScreen_Shell_Pause, this ); + BIND_SHELL_SCREEN( SHELL_AREA_SETTINGS, idMenuScreen_Shell_Settings, this ); + BIND_SHELL_SCREEN( SHELL_AREA_LOAD, idMenuScreen_Shell_Load, this ); + BIND_SHELL_SCREEN( SHELL_AREA_SYSTEM_OPTIONS, idMenuScreen_Shell_SystemOptions, this ); + BIND_SHELL_SCREEN( SHELL_AREA_GAME_OPTIONS, idMenuScreen_Shell_GameOptions, this ); + BIND_SHELL_SCREEN( SHELL_AREA_SAVE, idMenuScreen_Shell_Save, this ); + BIND_SHELL_SCREEN( SHELL_AREA_STEREOSCOPICS, idMenuScreen_Shell_Stereoscopics, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CONTROLS, idMenuScreen_Shell_Controls, this ); + BIND_SHELL_SCREEN( SHELL_AREA_KEYBOARD, idMenuScreen_Shell_Bindings, this ); + BIND_SHELL_SCREEN( SHELL_AREA_RESOLUTION, idMenuScreen_Shell_Resolution, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CONTROLLER_LAYOUT, idMenuScreen_Shell_ControllerLayout, this ); + + BIND_SHELL_SCREEN( SHELL_AREA_GAMEPAD, idMenuScreen_Shell_Gamepad, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CREDITS, idMenuScreen_Shell_Credits, this ); + + } else { + BIND_SHELL_SCREEN( SHELL_AREA_START, idMenuScreen_Shell_PressStart, this ); + BIND_SHELL_SCREEN( SHELL_AREA_ROOT, idMenuScreen_Shell_Root, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CAMPAIGN, idMenuScreen_Shell_Singleplayer, this ); + BIND_SHELL_SCREEN( SHELL_AREA_SETTINGS, idMenuScreen_Shell_Settings, this ); + BIND_SHELL_SCREEN( SHELL_AREA_LOAD, idMenuScreen_Shell_Load, this ); + BIND_SHELL_SCREEN( SHELL_AREA_NEW_GAME, idMenuScreen_Shell_NewGame, this ); + BIND_SHELL_SCREEN( SHELL_AREA_SYSTEM_OPTIONS, idMenuScreen_Shell_SystemOptions, this ); + BIND_SHELL_SCREEN( SHELL_AREA_GAME_OPTIONS, idMenuScreen_Shell_GameOptions, this ); + BIND_SHELL_SCREEN( SHELL_AREA_PARTY_LOBBY, idMenuScreen_Shell_PartyLobby, this ); + BIND_SHELL_SCREEN( SHELL_AREA_GAME_LOBBY, idMenuScreen_Shell_GameLobby, this ); + BIND_SHELL_SCREEN( SHELL_AREA_STEREOSCOPICS, idMenuScreen_Shell_Stereoscopics, this ); + BIND_SHELL_SCREEN( SHELL_AREA_DIFFICULTY, idMenuScreen_Shell_Difficulty, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CONTROLS, idMenuScreen_Shell_Controls, this ); + BIND_SHELL_SCREEN( SHELL_AREA_KEYBOARD, idMenuScreen_Shell_Bindings, this ); + BIND_SHELL_SCREEN( SHELL_AREA_RESOLUTION, idMenuScreen_Shell_Resolution, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CONTROLLER_LAYOUT, idMenuScreen_Shell_ControllerLayout, this ); + BIND_SHELL_SCREEN( SHELL_AREA_DEV, idMenuScreen_Shell_Dev, this ); + BIND_SHELL_SCREEN( SHELL_AREA_LEADERBOARDS, idMenuScreen_Shell_Leaderboards, this ); + BIND_SHELL_SCREEN( SHELL_AREA_GAMEPAD, idMenuScreen_Shell_Gamepad, this ); + BIND_SHELL_SCREEN( SHELL_AREA_MATCH_SETTINGS, idMenuScreen_Shell_MatchSettings, this ); + BIND_SHELL_SCREEN( SHELL_AREA_MODE_SELECT, idMenuScreen_Shell_ModeSelect, this ); + BIND_SHELL_SCREEN( SHELL_AREA_BROWSER, idMenuScreen_Shell_GameBrowser, this ); + BIND_SHELL_SCREEN( SHELL_AREA_CREDITS, idMenuScreen_Shell_Credits, this ); + + doom3Intro = declManager->FindMaterial( "gui/intro/introloop" ); + roeIntro = declManager->FindMaterial( "gui/intro/marsflyby" ); + + //typeSoundShader = declManager->FindSound( "gui/teletype/print_text", true ); + typeSoundShader = declManager->FindSound( "gui/teletype/print_text", true ); + declManager->FindSound( "gui/doomintro", true ); + + marsRotation = declManager->FindMaterial( "gui/shell/mars_rotation" ); + } + + menuBar = new (TAG_SWF) idMenuWidget_MenuBar(); + menuBar->SetSpritePath( "pcBar" ); + menuBar->Initialize( this ); + menuBar->SetNumVisibleOptions( MAX_MENU_OPTIONS ); + menuBar->SetWrappingAllowed( true ); + menuBar->SetButtonSpacing( 45.0f ); + while ( menuBar->GetChildren().Num() < MAX_MENU_OPTIONS ) { + idMenuWidget_MenuButton * const navButton = new (TAG_SWF) idMenuWidget_MenuButton(); + idMenuScreen_Shell_Root * rootScreen = dynamic_cast< idMenuScreen_Shell_Root * >( menuScreens[ SHELL_AREA_ROOT ] ); + if ( rootScreen != NULL ) { + navButton->RegisterEventObserver( rootScreen->GetHelpWidget() ); + } + menuBar->AddChild( navButton ); + } + AddChild( menuBar ); + + // + // command bar + // + cmdBar = new (TAG_SWF) idMenuWidget_CommandBar(); + cmdBar->SetAlignment( idMenuWidget_CommandBar::LEFT ); + cmdBar->SetSpritePath( "prompts" ); + cmdBar->Initialize( this ); + AddChild( cmdBar ); + + pacifier = new ( TAG_SWF ) idMenuWidget(); + pacifier->SetSpritePath( "pacifier" ); + AddChild( pacifier ); + + // precache sounds + // don't load gui music for the pause menu to save some memory + const idSoundShader * soundShader = NULL; + if ( !inGame ) { + soundShader = declManager->FindSound( "gui/menu_music", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_MUSIC ] = soundShader->GetName(); + } + } else { + idStrStatic< MAX_OSPATH > shortMapName = gameLocal.GetMapFileName(); + shortMapName.StripFileExtension(); + shortMapName.StripLeading( "maps/" ); + shortMapName.StripLeading( "game/" ); + if ( ( shortMapName.Icmp( "le_hell_post" ) == 0 ) || ( shortMapName.Icmp( "hellhole" ) == 0 ) || ( shortMapName.Icmp( "hell" ) == 0 ) ) { + soundShader = declManager->FindSound( "hell_music_credits", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_MUSIC ] = soundShader->GetName(); + } + } + } + + soundShader = declManager->FindSound( "gui/list_scroll", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_SCROLL ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_PDA_advance", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ADVANCE ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_PDA_back", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BACK ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/menu_build_on", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BUILD_ON ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/pda_next_tab", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_BUILD_ON ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_set_focus", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_FOCUS ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_roll_over", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ROLL_OVER ] = soundShader->GetName(); + } + soundShader = declManager->FindSound( "gui/btn_roll_out", true ); + if ( soundShader != NULL ) { + sounds[ GUI_SOUND_ROLL_OUT ] = soundShader->GetName(); + } + + class idPauseGUIClose : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + gameLocal.Shell_Show( false ); + return idSWFScriptVar(); + } + }; + + if ( gui != NULL ) { + gui->SetGlobal( "closeMenu", new idPauseGUIClose() ); + } +} + +/* +======================== +idMenuHandler_Shell::Cleanup +======================== +*/ +void idMenuHandler_Shell::Cleanup() { + idMenuHandler::Cleanup(); + + delete introGui; + introGui = NULL; +} + +/* +======================== +idMenuHandler_Shell::ActivateMenu +======================== +*/ +void idMenuHandler_Shell::ActivateMenu( bool show ) { + + if ( show && gui != NULL && gui->IsActive() ) { + return; + } else if ( !show && gui != NULL && !gui->IsActive() ) { + return; + } + + + if ( inGame ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + if ( !show ) { + bool isDead = false; + if ( player->health <= 0 ) { + isDead = true; + } + + if ( isDead && !common->IsMultiplayer() ) { + return; + } + } + } + } + + idMenuHandler::ActivateMenu( show ); + if ( show ) { + + if ( !inGame ) { + PlaySound( GUI_SOUND_MUSIC ); + + if ( gui != NULL ) { + + idSWFSpriteInstance * mars = gui->GetRootObject().GetNestedSprite( "mars" ); + if ( mars ) { + mars->stereoDepth = STEREO_DEPTH_TYPE_FAR; + + idSWFSpriteInstance * planet = mars->GetScriptObject()->GetNestedSprite( "planet" ); + + if ( marsRotation != NULL && planet != NULL ) { + const idMaterial * mat = marsRotation; + if ( mat != NULL ) { + int c = mat->GetNumStages(); + for ( int i = 0; i < c; i++ ) { + const shaderStage_t *stage = mat->GetStage( i ); + if ( stage != NULL && stage->texture.cinematic ) { + stage->texture.cinematic->ResetTime( Sys_Milliseconds() ); + } + } + } + + planet->SetMaterial( mat ); + } + } + } + } + + SetupPCOptions(); + + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + cmdBar->Update(); + } + + } else { + ClearWidgetActionRepeater(); + nextScreen = SHELL_AREA_INVALID; + activeScreen = SHELL_AREA_INVALID; + nextState = SHELL_STATE_INVALID; + state = SHELL_STATE_INVALID; + smallFrameShowing = false; + largeFrameShowing = false; + bgShowing = true; + + common->Dialog().ClearDialog( GDM_LEAVE_LOBBY_RET_NEW_PARTY ); + } +} + +enum shellCommandsPC_t { + SHELL_CMD_DEMO0, + SHELL_CMD_DEMO1, + SHELL_CMD_DEV, + SHELL_CMD_CAMPAIGN, + SHELL_CMD_MULTIPLAYER, + SHELL_CMD_SETTINGS, + SHELL_CMD_CREDITS, + SHELL_CMD_QUIT +}; + +/* +======================== +idMenuHandler_Shell::SetPCOptionsVisible +======================== +*/ +void idMenuHandler_Shell::SetupPCOptions() { + + if ( inGame ) { + return; + } + + navOptions.Clear(); + + if ( GetPlatform() == 2 && menuBar != NULL ) { + if ( g_demoMode.GetBool() ) { + navOptions.Append( "START DEMO" ); // START DEMO + if ( g_demoMode.GetInteger() == 2 ) { + navOptions.Append( "START PRESS DEMO" ); // START DEMO + } + navOptions.Append( "#str_swf_settings" ); // settings + navOptions.Append( "#str_swf_quit" ); // quit + + idMenuWidget_MenuButton * buttonWidget = NULL; + int index = 0; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_DEMO0, index ); + buttonWidget->SetDescription( "Launch the demo" ); + } + if ( g_demoMode.GetInteger() == 2 ) { + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_DEMO1, index ); + buttonWidget->SetDescription( "Launch the press Demo" ); + } + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_SETTINGS, index ); + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_QUIT, index ); + buttonWidget->SetDescription( "#str_01976" ); + } + } else { +#if !defined ( ID_RETAIL ) + navOptions.Append( "DEV" ); // DEV +#endif + navOptions.Append( "#str_swf_campaign" ); // singleplayer + navOptions.Append( "#str_swf_multiplayer" ); // multiplayer + navOptions.Append( "#str_swf_settings" ); // settings + navOptions.Append( "#str_swf_credits" ); // credits + navOptions.Append( "#str_swf_quit" ); // quit + + + idMenuWidget_MenuButton * buttonWidget = NULL; + int index = 0; +#if !defined ( ID_RETAIL ) + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_DEV, index ); + buttonWidget->SetDescription( "View a list of maps available for play" ); + } + index++; +#endif + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_CAMPAIGN, index ); + buttonWidget->SetDescription( "#str_swf_campaign_desc" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_MULTIPLAYER, index ); + buttonWidget->SetDescription( "#str_02215" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_SETTINGS, index ); + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_CREDITS, index ); + buttonWidget->SetDescription( "#str_02219" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->ClearEventActions(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, SHELL_CMD_QUIT, index ); + buttonWidget->SetDescription( "#str_01976" ); + } + } + } + + if ( menuBar != NULL && gui != NULL ) { + idSWFScriptObject & root = gui->GetRootObject(); + if ( menuBar->BindSprite( root ) ) { + menuBar->GetSprite()->SetVisible( true ); + menuBar->SetListHeadings( navOptions ); + menuBar->Update(); + + idMenuScreen_Shell_Root * menu = dynamic_cast< idMenuScreen_Shell_Root * >( menuScreens[ SHELL_AREA_ROOT ] ); + if ( menu != NULL ) { + const int activeIndex = menu->GetRootIndex(); + menuBar->SetViewIndex( activeIndex ); + menuBar->SetFocusIndex( activeIndex ); + } + + } + } +} + +/* +======================== +idMenuHandler_Shell::HandleExitGameBtn +======================== +*/ +void idMenuHandler_Shell::HandleExitGameBtn() { + class idSWFScriptFunction_QuitDialog : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_QuitDialog( gameDialogMessages_t _msg, int _accept ) { + msg = _msg; + accept = _accept; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept == 1 ) { + common->Quit(); + } else if ( accept == -1 ) { + session->MoveToPressStart(); + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + int accept; + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_QuitDialog( GDM_QUIT_GAME, 1 ) ); + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_QuitDialog( GDM_QUIT_GAME, 0 ) ); + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_QuitDialog( GDM_QUIT_GAME, -1 ) ); + optionText.Append( idStrId( "#STR_SWF_ACCEPT" ) ); + optionText.Append( idStrId( "#STR_SWF_CANCEL" ) ); + optionText.Append( idStrId( "#str_swf_change_game" ) ); + + common->Dialog().AddDynamicDialog( GDM_QUIT_GAME, callbacks, optionText, true, "" ); +} + +/* +======================== +idMenuHandler_Shell::HandleAction +======================== +*/ +bool idMenuHandler_Shell::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( activeScreen == SHELL_AREA_INVALID ) { + return true; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + if ( event.type == WIDGET_EVENT_COMMAND ) { + + /*if ( activeScreen == SHELL_AREA_ROOT && navOptions.Num() > 0 ) { + return true; + }*/ + + if ( menuScreens[ activeScreen ] != NULL && !forceHandled ) { + if ( menuScreens[ activeScreen ]->HandleAction( action, event, widget, true ) ) { + if ( actionType == WIDGET_ACTION_GO_BACK ) { + PlaySound( GUI_SOUND_BACK ); + } else { + PlaySound( GUI_SOUND_ADVANCE ); + } + return true; + } + } + } + + switch ( actionType ) { + case WIDGET_ACTION_COMMAND: { + + if ( parms.Num() < 2 ) { + return true; + } + + int cmd = parms[0].ToInteger(); + + if ( ( activeScreen == SHELL_AREA_GAME_LOBBY || activeScreen == SHELL_AREA_MATCH_SETTINGS ) && cmd != SHELL_CMD_QUIT && cmd != SHELL_CMD_MULTIPLAYER ) { + session->Cancel(); + session->Cancel(); + } else if ( ( activeScreen == SHELL_AREA_PARTY_LOBBY || activeScreen == SHELL_AREA_LEADERBOARDS || activeScreen == SHELL_AREA_BROWSER || activeScreen == SHELL_AREA_MODE_SELECT ) && cmd != SHELL_CMD_QUIT && cmd != SHELL_CMD_MULTIPLAYER ) { + session->Cancel(); + } + + if ( cmd != SHELL_CMD_QUIT && ( nextScreen == SHELL_AREA_STEREOSCOPICS || nextScreen == SHELL_AREA_SYSTEM_OPTIONS || nextScreen == SHELL_AREA_GAME_OPTIONS || + nextScreen == SHELL_AREA_GAMEPAD || nextScreen == SHELL_AREA_MATCH_SETTINGS ) ) { + + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + } + + const int index = parms[1].ToInteger(); + menuBar->SetFocusIndex( index ); + menuBar->SetViewIndex( index ); + + idMenuScreen_Shell_Root * menu = dynamic_cast< idMenuScreen_Shell_Root * >( menuScreens[ SHELL_AREA_ROOT ] ); + if ( menu != NULL ) { + menu->SetRootIndex( index ); + } + + switch ( cmd ) { + case SHELL_CMD_DEMO0: { + cmdSystem->AppendCommandText( va( "devmap %s %d\n", "demo/enpro_e3_2012", 1 ) ); + break; + } + case SHELL_CMD_DEMO1: { + cmdSystem->AppendCommandText( va( "devmap %s %d\n", "game/le_hell", 2 ) ); + break; + } + case SHELL_CMD_DEV: { + nextScreen = SHELL_AREA_DEV; + transition = MENU_TRANSITION_SIMPLE; + break; + } + case SHELL_CMD_CAMPAIGN: { + nextScreen = SHELL_AREA_CAMPAIGN; + transition = MENU_TRANSITION_SIMPLE; + break; + } + case SHELL_CMD_MULTIPLAYER: { + idMatchParameters matchParameters; + matchParameters.matchFlags = DefaultPartyFlags; + session->CreatePartyLobby( matchParameters ); + break; + } + case SHELL_CMD_SETTINGS: { + nextScreen = SHELL_AREA_SETTINGS; + transition = MENU_TRANSITION_SIMPLE; + break; + } + case SHELL_CMD_CREDITS: { + nextScreen = SHELL_AREA_CREDITS; + transition = MENU_TRANSITION_SIMPLE; + break; + } + case SHELL_CMD_QUIT: { + HandleExitGameBtn(); + break; + } + } + + return true; + } + } + + return idMenuHandler::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuHandler_Shell::GetMenuScreen +======================== +*/ +idMenuScreen * idMenuHandler_Shell::GetMenuScreen( int index ) { + + if ( index < 0 || index >= SHELL_NUM_AREAS ) { + return NULL; + } + + return menuScreens[ index ]; +} + +/* +======================== +idMenuHandler_Shell::ShowSmallFrame +======================== +*/ +void idMenuHandler_Shell::ShowSmallFrame( bool show ) { + + if ( gui == NULL ) { + return; + } + + idSWFSpriteInstance * smallFrame = gui->GetRootObject().GetNestedSprite( "smallFrame" ); + if ( smallFrame == NULL ) { + return; + } + + smallFrame->stereoDepth = STEREO_DEPTH_TYPE_MID; + + if ( show && !smallFrameShowing ) { + smallFrame->PlayFrame( "rollOn" ); + } else if ( !show && smallFrameShowing ) { + smallFrame->PlayFrame( "rollOff" ); + } + + smallFrameShowing = show; + +} + +/* +======================== +idMenuHandler_Shell::ShowMPFrame +======================== +*/ +void idMenuHandler_Shell::ShowMPFrame( bool show ) { + + if ( gui == NULL ) { + return; + } + + idSWFSpriteInstance * smallFrame = gui->GetRootObject().GetNestedSprite( "smallFrameMP" ); + if ( smallFrame == NULL ) { + return; + } + + smallFrame->stereoDepth = STEREO_DEPTH_TYPE_MID; + + if ( show && !largeFrameShowing ) { + smallFrame->PlayFrame( "rollOn" ); + } else if ( !show && largeFrameShowing ) { + smallFrame->PlayFrame( "rollOff" ); + } + + largeFrameShowing = show; + +} + +/* +======================== +idMenuHandler_Shell::ShowSmallFrame +======================== +*/ +void idMenuHandler_Shell::ShowLogo( bool show ) { + + if ( gui == NULL ) { + return; + } + + if ( show == bgShowing ) { + return; + } + + idSWFSpriteInstance * logo = gui->GetRootObject().GetNestedSprite( "logoInfo" ); + idSWFSpriteInstance * bg = gui->GetRootObject().GetNestedSprite( "background" ); + if ( logo != NULL && bg != NULL ) { + + bg->stereoDepth = STEREO_DEPTH_TYPE_MID; + + if ( show && !bgShowing ) { + logo->PlayFrame( "rollOn" ); + bg->PlayFrame( "rollOff" ); + } else if ( !show && bgShowing ) { + logo->PlayFrame( "rollOff" ); + bg->PlayFrame( "rollOn" ); + } + } + + bgShowing = show; + +} + +/* +======================== +idMenuHandler_Shell::UpdateSavedGames +======================== +*/ +void idMenuHandler_Shell::UpdateSavedGames() { + if ( activeScreen == SHELL_AREA_LOAD ) { + idMenuScreen_Shell_Load * screen = dynamic_cast< idMenuScreen_Shell_Load * >( menuScreens[ SHELL_AREA_LOAD ] ); + if ( screen != NULL ) { + screen->UpdateSaveEnumerations(); + } + } else if ( activeScreen == SHELL_AREA_SAVE ) { + idMenuScreen_Shell_Save * screen = dynamic_cast< idMenuScreen_Shell_Save * >( menuScreens[ SHELL_AREA_SAVE ] ); + if ( screen != NULL ) { + screen->UpdateSaveEnumerations(); + } + } +} + +/* +======================== +idMenuHandler_Shell::UpdateBGState +======================== +*/ +void idMenuHandler_Shell::UpdateBGState() { + + if ( smallFrameShowing ) { + if ( nextScreen != SHELL_AREA_PLAYSTATION && nextScreen != SHELL_AREA_SETTINGS && nextScreen != SHELL_AREA_CAMPAIGN && nextScreen != SHELL_AREA_DEV ) { + if ( nextScreen != SHELL_AREA_RESOLUTION && nextScreen != SHELL_AREA_GAMEPAD && nextScreen != SHELL_AREA_DIFFICULTY && nextScreen != SHELL_AREA_SYSTEM_OPTIONS && nextScreen != SHELL_AREA_GAME_OPTIONS && nextScreen != SHELL_AREA_NEW_GAME && nextScreen != SHELL_AREA_STEREOSCOPICS && + nextScreen != SHELL_AREA_CONTROLS ) { + ShowSmallFrame( false ); + } + } + } else { + if ( nextScreen == SHELL_AREA_RESOLUTION || nextScreen == SHELL_AREA_GAMEPAD || nextScreen == SHELL_AREA_PLAYSTATION || nextScreen == SHELL_AREA_SETTINGS || nextScreen == SHELL_AREA_CAMPAIGN || nextScreen == SHELL_AREA_CONTROLS || nextScreen == SHELL_AREA_DEV || nextScreen == SHELL_AREA_DIFFICULTY ) { + ShowSmallFrame( true ); + } + } + + if ( largeFrameShowing ) { + if ( nextScreen != SHELL_AREA_PARTY_LOBBY && nextScreen != SHELL_AREA_GAME_LOBBY && nextScreen != SHELL_AREA_CONTROLLER_LAYOUT && nextScreen != SHELL_AREA_KEYBOARD && nextScreen != SHELL_AREA_LEADERBOARDS && nextScreen != SHELL_AREA_MATCH_SETTINGS && nextScreen != SHELL_AREA_MODE_SELECT && + nextScreen != SHELL_AREA_BROWSER && nextScreen != SHELL_AREA_LOAD && nextScreen != SHELL_AREA_SAVE && nextScreen != SHELL_AREA_CREDITS ) { + ShowMPFrame( false ); + } + } else { + if ( nextScreen == SHELL_AREA_PARTY_LOBBY || nextScreen == SHELL_AREA_CONTROLLER_LAYOUT || nextScreen == SHELL_AREA_GAME_LOBBY || nextScreen == SHELL_AREA_KEYBOARD || nextScreen == SHELL_AREA_LEADERBOARDS || nextScreen == SHELL_AREA_MATCH_SETTINGS || nextScreen == SHELL_AREA_MODE_SELECT || + nextScreen == SHELL_AREA_BROWSER || nextScreen == SHELL_AREA_LOAD || nextScreen == SHELL_AREA_SAVE || nextScreen == SHELL_AREA_CREDITS ) { + ShowMPFrame( true ); + } + } + + if ( smallFrameShowing || largeFrameShowing || nextScreen == SHELL_AREA_START ) { + ShowLogo( false ); + } else { + ShowLogo( true ); + } + +} + +/* +======================== +idMenuHandler_Shell::UpdateLeaderboard +======================== +*/ +void idMenuHandler_Shell::UpdateLeaderboard( const idLeaderboardCallback * callback ) { + idMenuScreen_Shell_Leaderboards * screen = dynamic_cast< idMenuScreen_Shell_Leaderboards * >( menuScreens[ SHELL_AREA_LEADERBOARDS ] ); + if ( screen != NULL ) { + screen->UpdateLeaderboard( callback ); + } +} + +/* +======================== +idMenuManager_Shell::ShowPacifier +======================== +*/ +void idMenuHandler_Shell::ShowPacifier( const idStr & msg ) { + if ( GetPacifier() != NULL && gui != NULL ) { + gui->SetGlobal( "paciferMessage", msg ); + GetPacifier()->Show(); + } +} + +/* +======================== +idMenuManager_Shell::HidePacifier +======================== +*/ +void idMenuHandler_Shell::HidePacifier() { + if ( GetPacifier() != NULL ) { + GetPacifier()->Hide(); + } +} + +/* +======================== +idMenuHandler_Shell::CopySettingsFromSession +======================== +*/ +void idMenuHandler_Shell::UpdateLobby( idMenuWidget_LobbyList * lobbyList ) { + + if ( lobbyList == NULL ) { + return; + } + + idLobbyBase & lobby = session->GetActivePlatformLobbyBase(); + const int numLobbyPlayers = lobby.GetNumLobbyUsers(); + int maxPlayers = session->GetTitleStorageInt("MAX_PLAYERS_ALLOWED", 4 ); + + idStaticList< lobbyPlayerInfo_t, MAX_PLAYERS > lobbyPlayers; + for ( int i = 0; i < numLobbyPlayers; ++i ) { + lobbyPlayerInfo_t * lobbyPlayer = lobbyPlayers.Alloc(); + + lobbyUserID_t lobbyUserID = lobby.GetLobbyUserIdByOrdinal( i ); + + if ( !lobbyUserID.IsValid() ) { + continue; + } + + lobbyPlayer->name = lobby.GetLobbyUserName( lobbyUserID ); + // Voice + lobbyPlayer->voiceState = session->GetDisplayStateFromVoiceState( session->GetLobbyUserVoiceState( lobbyUserID ) ); + } + + + for ( int i = 0; i < maxPlayers; ++i ) { + if ( i >= lobbyPlayers.Num() ) { + lobbyList->SetEntryData( i, "", VOICECHAT_DISPLAY_NONE ); + } else { + lobbyPlayerInfo_t & lobbyPlayer = lobbyPlayers[ i ]; + lobbyList->SetEntryData( i, lobbyPlayer.name, lobbyPlayer.voiceState ); + } + } + + lobbyList->SetNumEntries( lobbyPlayers.Num() ); + +} + +/* +======================== +idMenuHandler_Shell::StartGame +======================== +*/ +void idMenuHandler_Shell::StartGame( int index ) { + if ( index == 0 ) { + cmdSystem->AppendCommandText( va( "map %s %d\n", "game/mars_city1", 0 ) ); + } else if ( index == 1 ) { + cmdSystem->AppendCommandText( va( "map %s %d\n", "game/erebus1", 1 ) ); + } else if ( index == 2 ) { + cmdSystem->AppendCommandText( va( "map %s %d\n", "game/le_enpro1", 2 ) ); + } +} + +static const int NUM_DOOM_INTRO_LINES = 7; +/* +======================== +idMenuHandler_Shell::ShowIntroVideo +======================== +*/ + void idMenuHandler_Shell::ShowDoomIntro() { + + StopSound(); + + showingIntro = true; + + delete introGui; + introGui = new idSWF( "doomIntro", common->MenuSW() ); + + if ( introGui != NULL ) { + const idMaterial * mat = doom3Intro; + if ( mat != NULL ) { + int c = mat->GetNumStages(); + for ( int i = 0; i < c; i++ ) { + const shaderStage_t *stage = mat->GetStage( i ); + if ( stage != NULL && stage->texture.cinematic ) { + stage->texture.cinematic->ResetTime( Sys_Milliseconds() ); + } + } + } + + introGui->Activate( true ); + + int numTextFields = NUM_DOOM_INTRO_LINES; + idStr textEntries[NUM_DOOM_INTRO_LINES] = { va( "%s %s", idLocalization::GetString("#str_04052"), idLocalization::GetString( "#str_04053" ) ), + va( "%s %s", idLocalization::GetString("#str_04054"), idLocalization::GetString( "#str_04055" ) ), + idLocalization::GetString( "#str_03012" ), + idLocalization::GetString( "#str_04056" ), + idLocalization::GetString( "#str_04057" ), + va( "%s %s", idLocalization::GetString("#str_04058"), idLocalization::GetString( "#str_04059" ) ), + va( "%s %s", idLocalization::GetString("#str_04060"), idLocalization::GetString( "#str_04061" ) ) }; + + for ( int i = 0; i < numTextFields; ++i ) { + + idSWFTextInstance * txtVal = introGui->GetRootObject().GetNestedText( va( "info%d", i ), "txtInfo", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( textEntries[i] ); + txtVal->SetStrokeInfo( true ); + txtVal->renderMode = SWF_TEXT_RENDER_PARAGRAPH; + txtVal->rndSpotsVisible = -1; + txtVal->renderDelay = 50; + txtVal->generatingText = false; + if ( typeSoundShader != NULL ) { + txtVal->soundClip = typeSoundShader->GetName(); + } + } + + idSWFSpriteInstance * infoSprite = introGui->GetRootObject().GetNestedSprite( va( "info%d", i ) ); + if ( infoSprite != NULL && txtVal != NULL ) { + class idIntroTextUpdate : public idSWFScriptFunction_RefCounted { + public: + idIntroTextUpdate( idSWFTextInstance * _txtVal, int _numLines, int _nextIndex, idMenuHandler_Shell * _shell, idSWF * _gui ) { + txtVal = _txtVal; + generating = false; + numLines = _numLines; + nextIndex = _nextIndex; + shell = _shell; + gui = _gui; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( thisObject->GetSprite() == NULL ) { + return idSWFScriptVar(); + } + + if ( thisObject->GetSprite()->currentFrame == 1 ) { + return idSWFScriptVar(); + } + + if ( txtVal == NULL ) { + return idSWFScriptVar(); + } + + if ( !generating ) { + generating = true; + txtVal->triggerGenerate = true; + } else if ( generating ) { + if ( !txtVal->generatingText ) { + float newYPos = thisObject->GetSprite()->GetYPos() - 1.5f; + if ( newYPos <= 350.0f - ( numLines * 36.0f ) ) { + if ( thisObject->GetSprite()->IsVisible() ) { + thisObject->GetSprite()->SetVisible( false ); + if ( nextIndex >= NUM_DOOM_INTRO_LINES ) { + shell->StartGame( 0 ); + } + } + } else if ( newYPos <= 665.0f - ( numLines * 36.0f ) ) { + if ( nextIndex < NUM_DOOM_INTRO_LINES ) { + idSWFSpriteInstance * nextInfo = gui->GetRootObject().GetNestedSprite( va( "info%d", nextIndex ) ); + if ( nextInfo != NULL && nextInfo->GetCurrentFrame() != nextInfo->FindFrame( "active" ) ) { + nextInfo->StopFrame( "active" ); + } + } + + float alpha = 1.0f; + if ( newYPos <= 450 ) { + alpha = ( newYPos - 350.0f ) / 100.0f; + } + thisObject->GetSprite()->SetAlpha( alpha ); + thisObject->GetSprite()->SetYPos( newYPos ); + } else { + thisObject->GetSprite()->SetYPos( newYPos ); + thisObject->GetSprite()->SetAlpha( 1.0f ); + } + } + } + + return idSWFScriptVar(); + } + private: + idSWFTextInstance * txtVal; + idMenuHandler_Shell * shell; + int numLines; + int nextIndex; + bool generating; + idSWF * gui; + }; + + infoSprite->GetScriptObject()->Set( "onEnterFrame", new idIntroTextUpdate( txtVal, txtVal->CalcNumLines(), i + 1, this, introGui ) ); + } + } + + class idIntroVOStart : public idSWFScriptFunction_RefCounted { + public: + idIntroVOStart( idSWF * gui ) { + introGui = gui; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( introGui != NULL ) { + introGui->PlaySound( "gui/doomintro" ); + } + return idSWFScriptVar(); + } + private: + idSWF * introGui; + }; + + if ( introGui != NULL ) { + introGui->SetGlobal( "playVo", new idIntroVOStart( introGui ) ); + } + + idSWFSpriteInstance * img = introGui->GetRootObject().GetNestedSprite( "intro", "img" ); + if ( img != NULL ) { + if ( mat != NULL ) { + img->SetMaterial( mat ); + } + } + } +} + +static const int NUM_ROE_INTRO_LINES = 6; +/* +======================== +idMenuHandler_Shell::ShowROEIntro +======================== +*/ +void idMenuHandler_Shell::ShowROEIntro() { + + StopSound(); + + showingIntro = true; + + delete introGui; + introGui = new idSWF( "roeIntro", common->MenuSW() ); + + if ( introGui != NULL ) { + const idMaterial * mat = roeIntro; + if ( mat != NULL ) { + int c = mat->GetNumStages(); + for ( int i = 0; i < c; i++ ) { + const shaderStage_t *stage = mat->GetStage( i ); + if ( stage != NULL && stage->texture.cinematic ) { + stage->texture.cinematic->ResetTime( Sys_Milliseconds() ); + } + } + } + + introGui->Activate( true ); + + int numTextFields = NUM_ROE_INTRO_LINES; + idStr textEntries[NUM_ROE_INTRO_LINES] = { + idLocalization::GetString( "#str_00100870" ), + idLocalization::GetString( "#str_00100854" ), + idLocalization::GetString( "#str_00100879" ), + idLocalization::GetString( "#str_00100855" ), + idLocalization::GetString( "#str_00100890" ), + idLocalization::GetString( "#str_00100856" ), + }; + + for ( int i = 0; i < numTextFields; ++i ) { + + idSWFTextInstance * txtVal = introGui->GetRootObject().GetNestedText( va( "info%d", i ), "txtInfo", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( textEntries[i] ); + txtVal->SetStrokeInfo( true ); + txtVal->renderMode = SWF_TEXT_RENDER_PARAGRAPH; + txtVal->rndSpotsVisible = -1; + txtVal->renderDelay = 40; + txtVal->generatingText = false; + if ( typeSoundShader != NULL ) { + txtVal->soundClip = typeSoundShader->GetName(); + } + } + + idSWFSpriteInstance * infoSprite = introGui->GetRootObject().GetNestedSprite( va( "info%d", i ) ); + if ( infoSprite != NULL && txtVal != NULL ) { + class idIntroTextUpdate : public idSWFScriptFunction_RefCounted { + public: + idIntroTextUpdate( idSWFTextInstance * _txtVal, int _numLines, int _nextIndex, idMenuHandler_Shell * _shell, idSWF * _gui ) { + txtVal = _txtVal; + generating = false; + numLines = _numLines; + nextIndex = _nextIndex; + shell = _shell; + gui = _gui; + startFade = 0; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( thisObject->GetSprite() == NULL ) { + return idSWFScriptVar(); + } + + if ( thisObject->GetSprite()->currentFrame == 1 ) { + return idSWFScriptVar(); + } + + if ( txtVal == NULL ) { + return idSWFScriptVar(); + } + + if ( !generating ) { + generating = true; + txtVal->triggerGenerate = true; + } else if ( generating ) { + if ( !txtVal->generatingText && thisObject->GetSprite()->IsVisible() ) { + if ( nextIndex % 2 != 0 ) { + if ( nextIndex < NUM_ROE_INTRO_LINES ) { + idSWFSpriteInstance * nextInfo = gui->GetRootObject().GetNestedSprite( va( "info%d", nextIndex ) ); + if ( nextInfo != NULL && nextInfo->GetCurrentFrame() != nextInfo->FindFrame( "active" ) ) { + nextInfo->StopFrame( "active" ); + } else if ( nextInfo != NULL && nextInfo->IsVisible() ) { + idSWFTextInstance * txtData = nextInfo->GetScriptObject()->GetNestedText( "txtInfo", "txtVal" ); + if ( txtData != NULL && !txtData->generatingText ) { + if ( startFade == 0 ) { + startFade = Sys_Milliseconds(); + } else { + if ( Sys_Milliseconds() - startFade >= 3000 ) { + nextInfo->SetVisible( false ); + thisObject->GetSprite()->SetVisible( false ); + + int nextDateIndex = ( nextIndex + 1 ); + if ( nextDateIndex < NUM_ROE_INTRO_LINES ) { + idSWFSpriteInstance * nextInfo = gui->GetRootObject().GetNestedSprite( va( "info%d", nextDateIndex ) ); + if ( nextInfo != NULL && nextInfo->GetCurrentFrame() != nextInfo->FindFrame( "active" ) ) { + nextInfo->StopFrame( "active" ); + return idSWFScriptVar(); + } + } else { + shell->StartGame( 1 ); + return idSWFScriptVar(); + } + } else { + float alpha = 1.0f - ( (float)( Sys_Milliseconds() - startFade ) / 3000.0f ); + nextInfo->SetAlpha( alpha ); + thisObject->GetSprite()->SetAlpha( alpha ); + } + } + } + } + } + } + } + } + + return idSWFScriptVar(); + } + private: + idSWFTextInstance * txtVal; + idMenuHandler_Shell * shell; + int numLines; + int nextIndex; + bool generating; + idSWF * gui; + int startFade; + }; + + infoSprite->GetScriptObject()->Set( "onEnterFrame", new idIntroTextUpdate( txtVal, txtVal->CalcNumLines(), i + 1, this, introGui ) ); + } + } + + idSWFSpriteInstance * img = introGui->GetRootObject().GetNestedSprite( "intro", "img" ); + if ( img != NULL ) { + if ( mat != NULL ) { + img->SetMaterial( mat ); + } + } + } +} + +static const int NUM_LE_INTRO_LINES = 1; +/* +======================== +idMenuHandler_Shell::ShowLEIntro +======================== +*/ +void idMenuHandler_Shell::ShowLEIntro() { + + StopSound(); + + showingIntro = true; + + delete introGui; + introGui = new idSWF( "leIntro", common->MenuSW() ); + + if ( introGui != NULL ) { + introGui->Activate( true ); + + idStr textEntry = va( "%s\n%s\n%s", idLocalization::GetString( "#str_00200071" ), idLocalization::GetString( "#str_00200072" ), idLocalization::GetString( "#str_00200073" ) ); + idSWFTextInstance * txtVal = introGui->GetRootObject().GetNestedText( "info0", "txtInfo", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( textEntry ); + txtVal->SetStrokeInfo( true ); + txtVal->renderMode = SWF_TEXT_RENDER_PARAGRAPH; + txtVal->rndSpotsVisible = -1; + txtVal->renderDelay = 60; + txtVal->generatingText = false; + if ( typeSoundShader != NULL ) { + txtVal->soundClip = typeSoundShader->GetName(); + } + } + + idSWFSpriteInstance * infoSprite = introGui->GetRootObject().GetNestedSprite( "info0" ); + if ( infoSprite != NULL ) { + class idIntroTextUpdate : public idSWFScriptFunction_RefCounted { + public: + idIntroTextUpdate( idSWFTextInstance * _txtVal, idMenuHandler_Shell * _shell ) { + txtVal = _txtVal; + generating = false; + shell = _shell; + startFade = 0; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( thisObject->GetSprite() == NULL ) { + return idSWFScriptVar(); + } + + if ( thisObject->GetSprite()->currentFrame == 1 ) { + return idSWFScriptVar(); + } + + if ( txtVal == NULL ) { + return idSWFScriptVar(); + } + + if ( !generating ) { + generating = true; + txtVal->triggerGenerate = true; + } else if ( generating ) { + if ( !txtVal->generatingText ) { + if ( startFade == 0 ) { + startFade = Sys_Milliseconds(); + } else { + float alpha = 1.0f - ( (float)( Sys_Milliseconds() - startFade ) / 3000.0f ); + if ( alpha <= 0.0f ) { + thisObject->GetSprite()->SetVisible( false ); + shell->StartGame( 2 ); + return idSWFScriptVar(); + } + thisObject->GetSprite()->SetAlpha( alpha ); + } + } + } + + return idSWFScriptVar(); + } + private: + idSWFTextInstance * txtVal; + idMenuHandler_Shell * shell; + bool generating; + int startFade; + }; + + infoSprite->GetScriptObject()->Set( "onEnterFrame", new idIntroTextUpdate( txtVal, this ) ); + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen.cpp b/neo/d3xp/menus/MenuScreen.cpp new file mode 100644 index 00000000..f2eebe65 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen.cpp @@ -0,0 +1,330 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +idMenuScreen::idMenuScreen() { + menuGUI = NULL; + transition = MENU_TRANSITION_INVALID; +} + +idMenuScreen::~idMenuScreen() { + +} + +/* +======================== +idMenuScreen::ObserveEvent +======================== +*/ +void idMenuScreen::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + if ( event.type == WIDGET_EVENT_COMMAND ) { + switch ( event.arg ) { + case idMenuWidget_CommandBar::BUTTON_JOY1: { + ReceiveEvent( idWidgetEvent( WIDGET_EVENT_PRESS, 0, event.thisObject, event.parms ) ); + break; + } + case idMenuWidget_CommandBar::BUTTON_JOY2: { + ReceiveEvent( idWidgetEvent( WIDGET_EVENT_BACK, 0, event.thisObject, event.parms ) ); + break; + } + } + } +} + +/* +======================== +idMenuScreen::Update +======================== +*/ +void idMenuScreen::Update() { + + if ( menuGUI == NULL ) { + return; + } + + // + // Display + // + for ( int childIndex = 0; childIndex < GetChildren().Num(); ++childIndex ) { + GetChildByIndex( childIndex ).Update(); + } + + if ( menuData != NULL ) { + menuData->UpdateChildren(); + } +} + +/* +======================== +idMenuScreen::UpdateCmds +======================== +*/ +void idMenuScreen::UpdateCmds() { + idSWF * const gui = menuGUI; + + idSWFScriptObject * const shortcutKeys = gui->GetGlobal( "shortcutKeys" ).GetObject(); + if ( !verify( shortcutKeys != NULL ) ) { + return; + } + + idSWFScriptVar clearFunc = shortcutKeys->Get( "clear" ); + if ( clearFunc.IsFunction() ) { + clearFunc.GetFunction()->Call( NULL, idSWFParmList() ); + } + + // NAVIGATION: UP/DOWN, etc. + idSWFScriptObject * const buttons = gui->GetRootObject().GetObject( "buttons" ); + if ( buttons != NULL ) { + + idSWFScriptObject * const btnUp = buttons->GetObject( "btnUp" ); + if ( btnUp != NULL ) { + btnUp->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP, SCROLL_SINGLE ) ); + btnUp->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP_RELEASE, 0 ) ); + shortcutKeys->Set( "UP", btnUp ); + shortcutKeys->Set( "MWHEEL_UP", btnUp ); + } + + idSWFScriptObject * const btnDown = buttons->GetObject( "btnDown" ); + if ( btnDown != NULL ) { + btnDown->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN, SCROLL_SINGLE ) ); + btnDown->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN_RELEASE, 0 ) ); + shortcutKeys->Set( "DOWN", btnDown ); + shortcutKeys->Set( "MWHEEL_DOWN", btnDown ); + } + + idSWFScriptObject * const btnUp_LStick = buttons->GetObject( "btnUp_LStick" ); + if ( btnUp_LStick != NULL ) { + btnUp_LStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP_LSTICK, SCROLL_SINGLE ) ); + btnUp_LStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK1_UP", btnUp_LStick ); + } + + idSWFScriptObject * const btnDown_LStick = buttons->GetObject( "btnDown_LStick" ); + if ( btnDown_LStick != NULL ) { + btnDown_LStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN_LSTICK, SCROLL_SINGLE ) ); + btnDown_LStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK1_DOWN", btnDown_LStick ); + } + + idSWFScriptObject * const btnUp_RStick = buttons->GetObject( "btnUp_RStick" ); + if ( btnUp_RStick != NULL ) { + btnUp_RStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP_RSTICK, SCROLL_SINGLE ) ); + btnUp_RStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK2_UP", btnUp_RStick ); + } + + idSWFScriptObject * const btnDown_RStick = buttons->GetObject( "btnDown_RStick" ); + if ( btnDown_RStick != NULL ) { + btnDown_RStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN_RSTICK, SCROLL_SINGLE ) ); + btnDown_RStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK2_DOWN", btnDown_RStick ); + } + + idSWFScriptObject * const btnPageUp = buttons->GetObject( "btnPageUp" ); + if ( btnPageUp != NULL ) { + btnPageUp->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_PAGEUP, SCROLL_PAGE ) ); + btnPageUp->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_PAGEUP_RELEASE, 0 ) ); + shortcutKeys->Set( "PGUP", btnPageUp ); + } + + idSWFScriptObject * const btnPageDown = buttons->GetObject( "btnPageDown" ); + if ( btnPageDown != NULL ) { + btnPageDown->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_PAGEDWN, SCROLL_PAGE ) ); + btnPageDown->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_PAGEDWN_RELEASE, 0 ) ); + shortcutKeys->Set( "PGDN", btnPageDown ); + } + + idSWFScriptObject * const btnHome = buttons->GetObject( "btnHome" ); + if ( btnHome != NULL ) { + btnHome->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP, SCROLL_FULL ) ); + btnHome->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_UP_RELEASE, 0 ) ); + shortcutKeys->Set( "HOME", btnHome ); + } + + idSWFScriptObject * const btnEnd = buttons->GetObject( "btnEnd" ); + if ( btnEnd != NULL ) { + btnEnd->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN, SCROLL_FULL ) ); + btnEnd->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_DOWN_RELEASE, 0 ) ); + shortcutKeys->Set( "END", btnEnd ); + } + + idSWFScriptObject * const btnLeft = buttons->GetObject( "btnLeft" ); + if ( btnLeft != NULL ) { + btnLeft->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT, 0 ) ); + btnLeft->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT_RELEASE, 0 ) ); + shortcutKeys->Set( "LEFT", btnLeft ); + } + + idSWFScriptObject * const btnRight = buttons->GetObject( "btnRight" ); + if ( btnRight != NULL ) { + btnRight->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT, 0 ) ); + btnRight->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT_RELEASE, 0 ) ); + shortcutKeys->Set( "RIGHT", btnRight ); + } + + idSWFScriptObject * const btnLeft_LStick = buttons->GetObject( "btnLeft_LStick" ); + if ( btnLeft_LStick != NULL ) { + btnLeft_LStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT_LSTICK, 0 ) ); + btnLeft_LStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK1_LEFT", btnLeft_LStick ); + } + + idSWFScriptObject * const btnRight_LStick = buttons->GetObject( "btnRight_LStick" ); + if ( btnRight_LStick != NULL ) { + btnRight_LStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT_LSTICK, 0 ) ); + btnRight_LStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK1_RIGHT", btnRight_LStick ); + } + + idSWFScriptObject * const btnLeft_RStick = buttons->GetObject( "btnLeft_RStick" ); + if ( btnLeft_RStick != NULL ) { + btnLeft_RStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT_RSTICK, 0 ) ); + btnLeft_RStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT_RSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK2_LEFT", btnLeft_RStick ); + } + + idSWFScriptObject * const btnRight_RStick = buttons->GetObject( "btnRight_RStick" ); + if ( btnRight_RStick != NULL ) { + btnRight_RStick->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT_RSTICK, 0 ) ); + btnRight_RStick->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT_RSTICK_RELEASE, 0 ) ); + shortcutKeys->Set( "STICK2_RIGHT", btnRight_RStick ); + } + } + + idSWFScriptObject * const navigation = gui->GetRootObject().GetObject( "navBar" ); + if ( navigation != NULL ) { + // TAB NEXT + idSWFScriptObject * const btnTabNext = navigation->GetNestedObj( "options", "btnTabNext" ); + if ( btnTabNext != NULL ) { + btnTabNext->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_TAB_NEXT, 0 ) ); + shortcutKeys->Set( "JOY6", btnTabNext ); + + if ( btnTabNext->GetSprite() != NULL && menuData != NULL ) { + btnTabNext->GetSprite()->StopFrame( menuData->GetPlatform() + 1 ); + } + + } + + // TAB PREV + idSWFScriptObject * const btnTabPrev = navigation->GetNestedObj( "options", "btnTabPrev" ); + if ( btnTabPrev != NULL ) { + btnTabPrev->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_TAB_PREV, 0 ) ); + shortcutKeys->Set( "JOY5", btnTabPrev ); + + if ( btnTabPrev->GetSprite() != NULL && menuData != NULL ) { + btnTabPrev->GetSprite()->StopFrame( menuData->GetPlatform() + 1 ); + } + } + } +} + +/* +======================== +idMenuScreen::HideScreen +======================== +*/ +void idMenuScreen::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( menuGUI == NULL ) { + return; + } + + if ( !BindSprite( menuGUI->GetRootObject() ) ) { + return; + } + + if ( transitionType == MENU_TRANSITION_SIMPLE ) { + GetSprite()->PlayFrame( "rollOff" ); + } else if ( transitionType == MENU_TRANSITION_ADVANCE ) { + GetSprite()->PlayFrame( "rollOffBack" ); + } else { + GetSprite()->PlayFrame( "rollOffFront" ); + } + + Update(); + +} + +/* +======================== +idMenuScreen::ShowScreen +======================== +*/ +void idMenuScreen::ShowScreen( const mainMenuTransition_t transitionType ) { + if ( menuGUI == NULL ) { + return; + } + + if ( !BindSprite( menuGUI->GetRootObject() ) ) { + return; + } + + GetSprite()->SetVisible( true ); + if ( transitionType == MENU_TRANSITION_SIMPLE ) { + if ( menuData != NULL && menuData->ActiveScreen() != -1 ) { + menuData->PlaySound( GUI_SOUND_BUILD_ON ); + } + GetSprite()->PlayFrame( "rollOn" ); + } else if ( transitionType == MENU_TRANSITION_ADVANCE ) { + if ( menuData != NULL && menuData->ActiveScreen() != -1 ) { + menuData->PlaySound( GUI_SOUND_BUILD_ON ); + } + GetSprite()->PlayFrame( "rollOnFront" ); + } else { + if ( menuData != NULL ) { + menuData->PlaySound( GUI_SOUND_BUILD_OFF ); + } + GetSprite()->PlayFrame( "rollOnBack" ); + } + + Update(); + + SetFocusIndex( GetFocusIndex(), true ); +} + +/* +======================== +idMenuScreen::HandleMenu + +NOTE: This is holdover from the way the menu system was setup before. It should be able to +be removed when the old way is fully replaced, and instead events will just be sent directly +to the screen. +======================== +*/ +void idMenuScreen::HandleMenu( const mainMenuTransition_t type ) { + if ( type == MENU_TRANSITION_ADVANCE ) { + ReceiveEvent( idWidgetEvent( WIDGET_EVENT_PRESS, 0, NULL, idSWFParmList() ) ); + } else if ( type == MENU_TRANSITION_BACK ) { + ReceiveEvent( idWidgetEvent( WIDGET_EVENT_BACK, 0, NULL, idSWFParmList() ) ); + } + + transition = type; +} + diff --git a/neo/d3xp/menus/MenuScreen.h b/neo/d3xp/menus/MenuScreen.h new file mode 100644 index 00000000..a3778401 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen.h @@ -0,0 +1,1642 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __MENUSCREEN_H__ +#define __MENUSCREEN_H__ + +#include "../../renderer/tr_local.h" + +enum mainMenuTransition_t { + MENU_TRANSITION_INVALID = -1, + MENU_TRANSITION_SIMPLE, + MENU_TRANSITION_ADVANCE, + MENU_TRANSITION_BACK, + MENU_TRANSITION_FORCE +}; + +enum cursorState_t { + CURSOR_NONE, + CURSOR_IN_COMBAT, + CURSOR_TALK, + CURSOR_GRABBER, + CURSOR_ITEM, +}; + +/* +================================================ +idLBRowBlock +================================================ +*/ +class idLBRowBlock { +public: + idLBRowBlock() : lastTime( 0 ), startIndex( 0 ) {} + + int lastTime; + int startIndex; + idList< idLeaderboardCallback::row_t > rows; +}; + +enum leaderboardFilterMode_t { + LEADERBOARD_FILTER_OVERALL = 0, + LEADERBOARD_FILTER_MYSCORE = 1, + LEADERBOARD_FILTER_FRIENDS = 2 +}; + +/* +================================================ +idLBCache +================================================ +*/ +class idLBCache { +public: + static const int NUM_ROW_BLOCKS = 5; + static const leaderboardFilterMode_t DEFAULT_LEADERBOARD_FILTER = LEADERBOARD_FILTER_OVERALL; + + idLBCache() : + def( NULL ), + filter( DEFAULT_LEADERBOARD_FILTER ), + pendingDef( NULL ), + pendingFilter( DEFAULT_LEADERBOARD_FILTER ), + requestingRows( false ), + loadingNewLeaderboard( false ), + numRowsInLeaderboard( 0 ), + entryIndex( 0 ), + rowOffset( 0 ), + localIndex( -1 ), + errorCode( LEADERBOARD_DISPLAY_ERROR_NONE ) {} + + void Pump(); + void Reset(); + void SetLeaderboard( const leaderboardDefinition_t * def_, leaderboardFilterMode_t filter_ = DEFAULT_LEADERBOARD_FILTER ); + void CycleFilter(); + leaderboardFilterMode_t GetFilter() const { return filter; } + idStr GetFilterStrType(); + bool Scroll( int amount ); + bool ScrollOffset( int amount ); + idLBRowBlock * FindFreeRowBlock(); + void Update( const idLeaderboardCallback * callback ); + const idLeaderboardCallback::row_t * GetLeaderboardRow( int row ); + + const leaderboardDefinition_t * GetLeaderboard() const { return def; } + int GetNumRowsInLeaderboard() const { return numRowsInLeaderboard; } + + int GetEntryIndex() const { return entryIndex; } + int GetRowOffset() const { return rowOffset; } + int GetLocalIndex() const { return localIndex; } + leaderboardDisplayError_t GetErrorCode() const { return errorCode; } + + bool IsRequestingRows() const { return requestingRows; } + bool IsLoadingNewLeaderboard() const { return loadingNewLeaderboard; } + + void SetEntryIndex( int value ) { entryIndex = value; } + void SetRowOffset( int value ) { rowOffset = value; } + + void DisplayGamerCardUI( const idLeaderboardCallback::row_t * row ); + +private: + leaderboardDisplayError_t CallbackErrorToDisplayError( leaderboardError_t errorCode ); + + idLBRowBlock rowBlocks[NUM_ROW_BLOCKS]; + + const leaderboardDefinition_t * def; + leaderboardFilterMode_t filter; + + // Pending def and filter are simply used to queue up SetLeaderboard calls when the system is currently + // busy waiting on results from a previous SetLeaderboard/GetLeaderboardRow call. + // This is so we only have ONE request in-flight at any given time. + const leaderboardDefinition_t * pendingDef; + leaderboardFilterMode_t pendingFilter; + + bool requestingRows; // True while requested rows are in flight + bool loadingNewLeaderboard; // True when changing to a new leaderboard (or filter type) + + int numRowsInLeaderboard; // Total rows in this leaderboard (they won't all be cached though) + int entryIndex; // Relative row offset (from top of viewing window) + int rowOffset; // Absolute row offset + int localIndex; // Row for the master local user (-1 means not on leaderboard) + + leaderboardDisplayError_t errorCode; // Error state of the leaderboard +}; + +/* +================================================ +idMenuScreen +================================================ +*/ +class idMenuScreen : public idMenuWidget { +public: + + idMenuScreen(); + virtual ~idMenuScreen(); + + virtual void Update(); + virtual void UpdateCmds(); + virtual void HandleMenu( const mainMenuTransition_t type ); + + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + virtual void SetScreenGui( idSWF * gui ) { menuGUI = gui; } + +protected: + + idSWF * menuGUI; + mainMenuTransition_t transition; + +}; + +/* +================================================ +idMenuScreen_PDA_UserData +================================================ +*/ +class idMenuScreen_PDA_UserData : public idMenuScreen { +public: + + idMenuScreen_PDA_UserData() {} + + virtual ~idMenuScreen_PDA_UserData() {} + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + idMenuWidget_PDA_UserData * GetUserData() { return &pdaUserData; } + idMenuWidget_PDA_Objective * GetObjective() { return &pdaObjectiveSimple; } + idMenuWidget_PDA_AudioFiles * GetAudioFiles() { return &pdaAudioFiles; } + +private: + idMenuWidget_PDA_UserData pdaUserData; + idMenuWidget_PDA_Objective pdaObjectiveSimple; + idMenuWidget_PDA_AudioFiles pdaAudioFiles; +}; + +/* +================================================ +idMenuScreen_PDA_UserEmails +================================================ +*/ +class idMenuScreen_PDA_UserEmails : public idMenuScreen { +public: + idMenuScreen_PDA_UserEmails() : + readingEmails( false ), + scrollEmailInfo( false ) { + } + + virtual ~idMenuScreen_PDA_UserEmails() { + } + + virtual void Update(); + virtual void Initialize( idMenuHandler * data ); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + idMenuWidget_PDA_EmailInbox & GetInbox() { return pdaInbox; } + + bool ScrollCorrectList( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget ); + void ShowEmail( bool show ); + void UpdateEmail(); +private: + idMenuWidget_PDA_EmailInbox pdaInbox; + idMenuWidget_InfoBox emailInfo; + idMenuWidget_ScrollBar emailScrollbar; + bool readingEmails; + bool scrollEmailInfo; +}; + +/* +================================================ +idMenuScreen_PDA_UserData +================================================ +*/ +class idMenuScreen_PDA_VideoDisks : public idMenuScreen { +public: + idMenuScreen_PDA_VideoDisks() : + activeVideo( NULL ) { + } + + virtual ~idMenuScreen_PDA_VideoDisks() { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void ToggleVideoDiskPlay(); + void UpdateVideoDetails(); + void SelectedVideoToPlay( int index ); + void ClearActiveVideo() { activeVideo = NULL; } + const idDeclVideo * GetActiveVideo() { return activeVideo; } +private: + idMenuWidget_ScrollBar scrollbar; + idMenuWidget_DynamicList pdaVideoList; + idMenuWidget_PDA_VideoInfo videoDetails; + idList< idList< idStr, TAG_IDLIB_LIST_MENU>, TAG_IDLIB_LIST_MENU > videoItems; + const idDeclVideo * activeVideo; +}; + +/* +================================================ +idMenuScreen_PDA_UserData +================================================ +*/ +class idMenuScreen_PDA_Inventory : public idMenuScreen { +public: + idMenuScreen_PDA_Inventory() { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void EquipWeapon(); + const char * GetWeaponName( int index ); + bool IsVisibleWeapon( int index ); + +private: + idMenuWidget_Carousel itemList; + idMenuWidget_InfoBox infoBox; +}; + +//* +//================================================ +//idMenuScreen_Shell_Root +//================================================ +//*/ +class idMenuScreen_Shell_Root : public idMenuScreen { +public: + idMenuScreen_Shell_Root() : + options( NULL ), + helpWidget( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void HandleExitGameBtn(); + int GetRootIndex(); + void SetRootIndex( int index ); + idMenuWidget_Help * GetHelpWidget() { return helpWidget; } + +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Help * helpWidget; +}; + +//* +//================================================ +//idMenuScreen_Shell_Pause +//================================================ +//*/ +class idMenuScreen_Shell_Pause : public idMenuScreen { +public: + idMenuScreen_Shell_Pause() : + options( NULL ), + isMpPause( false ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void HandleExitGameBtn(); + void HandleRestartBtn(); + +private: + idMenuWidget_DynamicList * options; + bool isMpPause; +}; + +//* +//================================================ +//idMenuScreen_Shell_PressStart +//================================================ +//*/ +class idMenuScreen_Shell_PressStart : public idMenuScreen { +public: + idMenuScreen_Shell_PressStart() : + startButton( NULL ), + options( NULL ), + itemList( NULL ), + doomCover( NULL ), + doom2Cover( NULL ), + doom3Cover( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_Button * startButton; + idMenuWidget_DynamicList * options; + idMenuWidget_Carousel * itemList; + const idMaterial * doomCover; + const idMaterial * doom2Cover; + const idMaterial * doom3Cover; +}; + +//* +//================================================ +//idMenuScreen_Shell_PressStart +//================================================ +//*/ +class idMenuScreen_Shell_GameSelect : public idMenuScreen { +public: + idMenuScreen_Shell_GameSelect() : + startButton( NULL ), + options( NULL ), + itemList( NULL ), + doomCover( NULL ), + doom2Cover( NULL ), + doom3Cover( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_Button * startButton; + idMenuWidget_DynamicList * options; + idMenuWidget_Carousel * itemList; + const idMaterial * doomCover; + const idMaterial * doom2Cover; + const idMaterial * doom3Cover; +}; + +//* +//================================================ +//idMenuScreen_Shell_Singleplayer +//================================================ +//*/ +class idMenuScreen_Shell_Singleplayer : public idMenuScreen { +public: + idMenuScreen_Shell_Singleplayer() : + options( NULL ), + btnBack( NULL ), + canContinue( false ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void SetCanContinue( bool valid ) { canContinue = valid; } + void ContinueGame(); +private: + bool canContinue; + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Settings +//================================================ +//*/ +class idMenuScreen_Shell_Settings : public idMenuScreen { +public: + idMenuScreen_Shell_Settings() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +struct creditInfo_t { + + creditInfo_t() { + type = -1; + entry = ""; + } + + creditInfo_t( int t, const char * val ) { + type = t; + entry = val; + } + + int type; + idStr entry; +}; + +//* +//================================================ +//idMenuScreen_Shell_Credits +//================================================ +//*/ +class idMenuScreen_Shell_Credits : public idMenuScreen { +public: + idMenuScreen_Shell_Credits() : + btnBack( NULL ), + creditIndex( 0 ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void SetupCreditList(); + void UpdateCredits(); + +private: + idMenuWidget_Button * btnBack; + idList< creditInfo_t > creditList; + int creditIndex; +}; + +//* +//================================================ +//idMenuScreen_Shell_Resolution +//================================================ +//*/ +class idMenuScreen_Shell_Resolution : public idMenuScreen { +public: + idMenuScreen_Shell_Resolution() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + +private: + struct optionData_t { + optionData_t() { + fullscreen = -1; + vidmode = -1; + } + optionData_t( int f, int v ) { + fullscreen = f; + vidmode = v; + } + optionData_t( const optionData_t & other ) { + fullscreen = other.fullscreen; + vidmode = other.vidmode; + } + const optionData_t & operator=( const optionData_t & other ) { + fullscreen = other.fullscreen; + vidmode = other.vidmode; + return *this; + } + bool operator==( const optionData_t & other ) const { + return ( fullscreen == other.fullscreen ) && ( ( vidmode == other.vidmode ) || ( fullscreen == 0 ) ); + } + int fullscreen; + int vidmode; + }; + idList optionData; + + optionData_t originalOption; + + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Difficulty +//================================================ +//*/ +class idMenuScreen_Shell_Difficulty : public idMenuScreen { +public: + idMenuScreen_Shell_Difficulty() : + options( NULL ), + btnBack( NULL ), + nightmareUnlocked( false ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + bool nightmareUnlocked; + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Playstation +//================================================ +//*/ +class idMenuScreen_Shell_Playstation : public idMenuScreen { +public: + idMenuScreen_Shell_Playstation() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_ModeSelect +//================================================ +//*/ +class idMenuScreen_Shell_ModeSelect : public idMenuScreen { +public: + idMenuScreen_Shell_ModeSelect() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_GameBrowser +//================================================ +//*/ +class idMenuScreen_Shell_GameBrowser : public idMenuScreen { +public: + idMenuScreen_Shell_GameBrowser() : + listWidget( NULL ), + btnBack( NULL ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandle = false ); + + void UpdateServerList(); + void OnServerListReady(); + void DescribeServer( const serverInfo_t & server, const int index ); + +private: + idMenuWidget_GameBrowserList * listWidget; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Leaderboards +//================================================ +//*/ +class idMenuScreen_Shell_Leaderboards : public idMenuScreen { +public: + idMenuScreen_Shell_Leaderboards() : + options( NULL ), + btnBack( NULL ), + refreshLeaderboard( false ), + refreshWhenMasterIsOnline( false ), + lbIndex( 0 ), + btnPrev( NULL ), + btnNext( NULL ), + lbHeading( NULL ), + btnPageDwn( NULL ), + btnPageUp( NULL ) { + } + + virtual ~idMenuScreen_Shell_Leaderboards(); + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void UpdateLeaderboard( const idLeaderboardCallback * callback ); + void PumpLBCache(); + void RefreshLeaderboard(); + void ShowMessage( bool show, idStr message, bool spinner ); + void ClearLeaderboard(); + void SetLeaderboardIndex(); + +protected: + + struct doomLeaderboard_t { + doomLeaderboard_t() : lb(NULL) { } + doomLeaderboard_t( const leaderboardDefinition_t * _lb, idStr _name ) { lb=_lb; name=_name; } + const leaderboardDefinition_t * lb; + idStr name; + }; + + idList< doomLeaderboard_t > leaderboards; + + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; + idMenuWidget_Button * btnPrev; + idMenuWidget_Button * btnNext; + idMenuWidget_Button * btnPageDwn; + idMenuWidget_Button * btnPageUp; + idLBCache * lbCache; + idSWFTextInstance * lbHeading; + int lbIndex; + bool refreshLeaderboard; + bool refreshWhenMasterIsOnline; +}; + +//* +//================================================ +//idMenuScreen_Shell_Bindings +//================================================ +//*/ +class idMenuScreen_Shell_Bindings : public idMenuScreen { +public: + idMenuScreen_Shell_Bindings() : + options( NULL ), + btnBack( NULL ), + blinder( NULL ), + restoreDefault( NULL ), + txtBlinder( NULL ), + bindingsChanged( false ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void SetBinding( int keyNum ); + void UpdateBindingDisplay(); + void ToggleWait( bool wait ); + void SetBindingChanged( bool changed ) { bindingsChanged = changed; } + +protected: + void HandleRestoreDefaults(); + + idMenuWidget_DynamicList * options; + idMenuWidget_Button * restoreDefault; + idSWFSpriteInstance * blinder; + idSWFSpriteInstance * txtBlinder; + idMenuWidget_Button * btnBack; + bool bindingsChanged; +}; + +//* +//================================================ +//idMenuScreen_Shell_Dev +//================================================ +//*/ +class idMenuScreen_Shell_Dev : public idMenuScreen { +public: + + struct devOption_t { + devOption_t() { + map = ""; + name = ""; + }; + + devOption_t( const char * m, const char * n ) { + map = m; + name = n; + } + + const char * map; + const char * name; + }; + + idMenuScreen_Shell_Dev() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void SetupDevOptions(); + +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; + idList< devOption_t, TAG_IDLIB_LIST_MENU > devOptions; +}; + +//* +//================================================ +//idMenuScreen_Shell_NewGame +//================================================ +//*/ +class idMenuScreen_Shell_NewGame : public idMenuScreen { +public: + idMenuScreen_Shell_NewGame() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Load +//================================================ +//*/ +class idMenuScreen_Shell_Load : public idMenuScreen { +public: + idMenuScreen_Shell_Load() : + options( NULL ), + btnBack( NULL ), + btnDelete( NULL ), + saveInfo( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void UpdateSaveEnumerations(); + void LoadDamagedGame( int index ); + void LoadGame( int index ); + void DeleteGame( int index ); + saveGameDetailsList_t GetSortedSaves() const { return sortedSaves; } + +private: + idMenuWidget_DynamicList * options; + idMenuWidget_Shell_SaveInfo * saveInfo; + idMenuWidget_Button * btnBack; + idMenuWidget_Button * btnDelete; + saveGameDetailsList_t sortedSaves; +}; + +//* +//================================================ +//idMenuScreen_Shell_Save +//================================================ +//*/ +class idMenuScreen_Shell_Save : public idMenuScreen { +public: + idMenuScreen_Shell_Save() : + options( NULL ), + btnBack( NULL ), + btnDelete( NULL ), + saveInfo( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + saveGameDetailsList_t GetSortedSaves() const { return sortedSaves; } + + void UpdateSaveEnumerations(); + void SaveGame( int index ); + void DeleteGame( int index ); + +private: + idMenuWidget_Button * btnBack; + idMenuWidget_DynamicList * options; + idMenuWidget_Shell_SaveInfo * saveInfo; + idMenuWidget_Button * btnDelete; + saveGameDetailsList_t sortedSaves; +}; + +//* +//================================================ +//idMenuScreen_Shell_GameOptions +//================================================ +//*/ +class idMenuScreen_Shell_GameOptions : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_GameSettings + ================================================ + */ + class idMenuDataSource_GameSettings : public idMenuDataSource { + public: + enum gameSettingFields_t { + GAME_FIELD_FOV, + GAME_FIELD_CHECKPOINTS, + GAME_FIELD_AUTO_SWITCH, + GAME_FIELD_AUTO_RELOAD, + GAME_FIELD_AIM_ASSIST, + GAME_FIELD_ALWAYS_SPRINT, + GAME_FIELD_FLASHLIGHT_SHADOWS, + MAX_GAME_FIELDS + }; + + idMenuDataSource_GameSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading or updating + virtual idSWFScriptVar GetField( const int fieldIndex ) const { return fields[ fieldIndex ]; } + + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + private: + idStaticList< idSWFScriptVar, MAX_GAME_FIELDS > fields; + idStaticList< idSWFScriptVar, MAX_GAME_FIELDS > originalFields; + }; + + idMenuScreen_Shell_GameOptions() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuDataSource_GameSettings systemData; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_GameOptions +//================================================ +//*/ +class idMenuScreen_Shell_MatchSettings : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_MatchSettings + ================================================ + */ + class idMenuDataSource_MatchSettings : public idMenuDataSource { + public: + enum matchSettingFields_t { + MATCH_FIELD_MODE, + MATCH_FIELD_MAP, + MATCH_FIELD_TIME, + MATCH_FIELD_SCORE, + MAX_MATCH_FIELDS + }; + + idMenuDataSource_MatchSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading or updating + virtual idSWFScriptVar GetField( const int fieldIndex ) const { return fields[ fieldIndex ]; } + + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + bool MapChanged() { return updateMap; } + void ClearMapChanged() { updateMap = false; } + + private: + + void GetModeName( int index, idStr & name ); + void GetMapName( int index, idStr & name ); + + idStaticList< idSWFScriptVar, MAX_MATCH_FIELDS > fields; + idStaticList< idSWFScriptVar, MAX_MATCH_FIELDS > originalFields; + bool updateMap; + }; + + idMenuScreen_Shell_MatchSettings() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuDataSource_MatchSettings matchData; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Controls +//================================================ +//*/ +class idMenuScreen_Shell_Controls : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_ControlSettings + ================================================ + */ + class idMenuDataSource_ControlSettings : public idMenuDataSource { + public: + enum controlSettingFields_t { + CONTROLS_FIELD_INVERT_MOUSE, + CONTROLS_FIELD_GAMEPAD_ENABLED, + CONTROLS_FIELD_MOUSE_SENS, + MAX_CONTROL_FIELDS + }; + + idMenuDataSource_ControlSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading or updating + virtual idSWFScriptVar GetField( const int fieldIndex ) const { return fields[ fieldIndex ]; } + + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + private: + idStaticList< idSWFScriptVar, MAX_CONTROL_FIELDS > fields; + idStaticList< idSWFScriptVar, MAX_CONTROL_FIELDS > originalFields; + }; + + idMenuScreen_Shell_Controls() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuDataSource_ControlSettings controlData; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_Controls +//================================================ +//*/ +class idMenuScreen_Shell_Gamepad : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_ControlSettings + ================================================ + */ + class idMenuDataSource_GamepadSettings : public idMenuDataSource { + public: + enum controlSettingFields_t { + GAMEPAD_FIELD_LEFTY, + GAMEPAD_FIELD_INVERT, + GAMEPAD_FIELD_VIBRATE, + GAMEPAD_FIELD_HOR_SENS, + GAMEPAD_FIELD_VERT_SENS, + GAMEPAD_FIELD_ACCELERATION, + GAMEPAD_FIELD_THRESHOLD, + MAX_GAMEPAD_FIELDS + }; + + idMenuDataSource_GamepadSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading or updating + virtual idSWFScriptVar GetField( const int fieldIndex ) const { return fields[ fieldIndex ]; } + + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + private: + idStaticList< idSWFScriptVar, MAX_GAMEPAD_FIELDS > fields; + idStaticList< idSWFScriptVar, MAX_GAMEPAD_FIELDS > originalFields; + }; + + idMenuScreen_Shell_Gamepad() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuDataSource_GamepadSettings gamepadData; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_ControllerLayout +//================================================ +//*/ +class idMenuScreen_Shell_ControllerLayout : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_ControlSettings + ================================================ + */ + class idMenuDataSource_LayoutSettings : public idMenuDataSource { + public: + enum controlSettingFields_t { + LAYOUT_FIELD_LAYOUT, + MAX_LAYOUT_FIELDS, + }; + + idMenuDataSource_LayoutSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading or updating + virtual idSWFScriptVar GetField( const int fieldIndex ) const { return fields[ fieldIndex ]; } + + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + private: + idStaticList< idSWFScriptVar, MAX_LAYOUT_FIELDS > fields; + idStaticList< idSWFScriptVar, MAX_LAYOUT_FIELDS > originalFields; + }; + + idMenuScreen_Shell_ControllerLayout() : + btnBack( NULL ), + options( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void UpdateBindingInfo(); +private: + + idMenuDataSource_LayoutSettings layoutData; + idMenuWidget_DynamicList * options; + idMenuWidget_Button * btnBack; +}; + +//* +//================================================ +//idMenuScreen_Shell_SystemOptions +//================================================ +//*/ +class idMenuScreen_Shell_SystemOptions : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_SystemSettings + ================================================ + */ + class idMenuDataSource_SystemSettings : public idMenuDataSource { + public: + enum systemSettingFields_t { + SYSTEM_FIELD_FULLSCREEN, + SYSTEM_FIELD_FRAMERATE, + SYSTEM_FIELD_VSYNC, + SYSTEM_FIELD_ANTIALIASING, + SYSTEM_FIELD_MOTIONBLUR, + SYSTEM_FIELD_LODBIAS, + SYSTEM_FIELD_BRIGHTNESS, + SYSTEM_FIELD_VOLUME, + MAX_SYSTEM_FIELDS + }; + + idMenuDataSource_SystemSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading + virtual idSWFScriptVar GetField( const int fieldIndex ) const; + + // updates a particular field value + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + bool IsRestartRequired() const; + + private: + int originalFramerate; + int originalAntialias; + int originalMotionBlur; + int originalVsync; + float originalBrightness; + float originalVolume; + + idList modeList; + }; + + idMenuScreen_Shell_SystemOptions() : + options( NULL ), + btnBack( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + +private: + idMenuWidget_DynamicList * options; + idMenuDataSource_SystemSettings systemData; + idMenuWidget_Button * btnBack; + +}; + +//* +//================================================ +//idMenuScreen_Shell_Stereoscopics +//================================================ +//*/ +class idMenuScreen_Shell_Stereoscopics : public idMenuScreen { +public: + + /* + ================================================ + idMenuDataSource_StereoSettings + ================================================ + */ + class idMenuDataSource_StereoSettings : public idMenuDataSource { + public: + enum stereoSettingFields_t { + STEREO_FIELD_ENABLE, + STEREO_FIELD_SEPERATION, + STEREO_FIELD_SWAP_EYES, + MAX_STEREO_FIELDS + }; + + idMenuDataSource_StereoSettings(); + + // loads data + virtual void LoadData(); + + // submits data + virtual void CommitData(); + + // says whether something changed with the data + virtual bool IsDataChanged() const; + + // retrieves a particular field for reading or updating + virtual idSWFScriptVar GetField( const int fieldIndex ) const; + + virtual void AdjustField( const int fieldIndex, const int adjustAmount ); + + bool IsRestartRequired() const; + + private: + idStaticList< idSWFScriptVar, MAX_STEREO_FIELDS > fields; + idStaticList< idSWFScriptVar, MAX_STEREO_FIELDS > originalFields; + }; + + idMenuScreen_Shell_Stereoscopics() : + options( NULL ), + btnBack( NULL ), + leftEyeMat( NULL ), + rightEyeMat( NULL ) { + } + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); +private: + idMenuWidget_DynamicList * options; + idMenuDataSource_StereoSettings stereoData; + idMenuWidget_Button * btnBack; + const idMaterial * leftEyeMat; + const idMaterial * rightEyeMat; +}; + +//* +//================================================ +//idMenuScreen_Shell_PartyLobby +//================================================ +//*/ +class idMenuScreen_Shell_PartyLobby : public idMenuScreen { +public: + idMenuScreen_Shell_PartyLobby() : + options( NULL ), + lobby( NULL ), + isHost( false ), + isPeer( false ), + btnBack( NULL ), + inParty( false ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + void UpdateOptions(); + void UpdateLobby(); + bool CanKickSelectedPlayer( lobbyUserID_t & luid ); + void ShowLeaderboards(); + +private: + + bool isHost; + bool isPeer; + bool inParty; + idMenuWidget_DynamicList * options; + idMenuWidget_LobbyList * lobby; + idMenuWidget_Button * btnBack; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; +}; + +//* +//================================================ +//idMenuScreen_Shell_GameLobby +//================================================ +//*/ +class idMenuScreen_Shell_GameLobby : public idMenuScreen { +public: + idMenuScreen_Shell_GameLobby() : + options( NULL ), + lobby( NULL ), + longCountdown( 0 ), + shortCountdown( 0 ), + longCountRemaining( 0 ), + isPeer( false ), + isHost( false ), + privateGameLobby( true ), + btnBack( NULL ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + void UpdateLobby(); + bool CanKickSelectedPlayer( lobbyUserID_t & luid ); + +private: + + int longCountdown; + int longCountRemaining; + int shortCountdown; + + bool isHost; + bool isPeer; + bool privateGameLobby; + + idMenuWidget_DynamicList * options; + idMenuWidget_LobbyList * lobby; + idMenuWidget_Button * btnBack; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; +}; + +//* +//================================================ +//idMenuScreen_HUD +//================================================ +//*/ +class idMenuScreen_HUD : public idMenuScreen { +public: + + idMenuScreen_HUD() : + weaponInfo( NULL ), + playerInfo( NULL ), + audioLog( NULL ), + communication( NULL ), + oxygen( NULL ), + stamina( NULL ), + weaponName( NULL ), + weaponImg( NULL ), + weaponPills( NULL ), + locationName( NULL ), + downloadPda( NULL ), + downloadVideo( NULL ), + newPDA( NULL ), + newVideo( NULL ), + objective( NULL ), + objectiveComplete( NULL ), + tipInfo( NULL ), + healthBorder( NULL ), + healthPulse( NULL ), + armorFrame( NULL ), + security( NULL ), + securityText( NULL ), + newPDADownload( NULL ), + newPDAHeading( NULL ), + newPDAName( NULL ), + newVideoDownload( NULL ), + newVideoHeading( NULL ), + audioLogPrevTime( 0 ), + commPrevTime( 0 ), + oxygenComm( false ), + inVaccuum( false ), + ammoInfo( NULL ), + newWeapon( NULL ), + pickupInfo( NULL ), + cursorState( CURSOR_NONE ), + cursorInCombat( 0 ), + cursorTalking( 0 ), + cursorItem( 0 ), + cursorGrabber( 0 ), + cursorNone( 0 ), + talkCursor( NULL ), + combatCursor( NULL ), + grabberCursor( NULL ), + bsInfo( NULL ), + soulcubeInfo( NULL ), + mpInfo( NULL ), + mpHitInfo( NULL ), + mpTime( NULL ), + mpMessage( NULL ), + mpChat( NULL ), + mpWeapons( NULL ), + newItem( NULL ), + respawnMessage( NULL ), + flashlight( NULL ), + mpChatObject( NULL ), + showSoulCubeInfoOnLoad( false ), + mpConnection( NULL ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual void HideScreen( const mainMenuTransition_t transitionType ); + + void UpdateHealthArmor( idPlayer * player ); + void UpdateStamina( idPlayer * player ); + void UpdateLocation( idPlayer * player ); + void UpdateWeaponInfo( idPlayer * player ); + void UpdateWeaponStates( idPlayer * player, bool weaponChanged ); + void ShowTip( const char * title, const char * tip ); + void HideTip(); + void DownloadVideo(); + void DownloadPDA( const idDeclPDA * pda, bool newSecurity ); + void UpdatedSecurity(); + void ToggleNewVideo( bool show ); + void ClearNewPDAInfo(); + void ToggleNewPDA( bool show ); + void UpdateAudioLog( bool show ); + void UpdateCommunication( bool show, idPlayer * player ); + void UpdateOxygen( bool show, int val = 0 ); + void SetupObjective( const idStr & title, const idStr & desc, const idMaterial * screenshot ); + void SetupObjectiveComplete( const idStr & title ); + void ShowObjective( bool complete ); + void HideObjective( bool complete ); + void GiveWeapon( idPlayer * player, int weaponIndex ); + void UpdatePickupInfo( int index, const idStr & name ); + bool IsPickupListReady(); + void ShowPickups(); + void SetCursorState( idPlayer * player, cursorState_t state, int set ); + void SetCursorText( const idStr & action, const idStr & focus ); + void UpdateCursorState(); + void CombatCursorFlash(); + void UpdateSoulCube( bool ready ); + void ShowRespawnMessage( bool show ); + void SetShowSoulCubeOnLoad( bool show ) { showSoulCubeInfoOnLoad = show; } + + // MULTIPLAYER + + void ToggleMPInfo( bool show, bool showTeams, bool isCTF = false ); + void SetFlagState( int team, int state ); + void SetTeamScore( int team, int score ); + void SetTeam( int team ); + void TriggerHitTarget( bool show, const idStr & target, int color = 0 ); + void ToggleLagged( bool show ); + void UpdateGameTime( const char * time ); + void UpdateMessage( bool show, const idStr & message ); + void ShowNewItem( const char * name, const char * icon ); + void UpdateFlashlight( idPlayer * player ); + void UpdateChattingHud( idPlayer * player ); + +private: + + idSWFScriptObject * weaponInfo; + idSWFScriptObject * playerInfo; + idSWFScriptObject * stamina; + idSWFScriptObject * weaponName; + idSWFScriptObject * weaponPills; + idSWFScriptObject * downloadPda; + idSWFScriptObject * downloadVideo; + idSWFScriptObject * tipInfo; + idSWFScriptObject * mpChat; + idSWFScriptObject * mpWeapons; + + idSWFSpriteInstance * healthBorder; + idSWFSpriteInstance * healthPulse; + idSWFSpriteInstance * armorFrame; + idSWFSpriteInstance * security; + idSWFSpriteInstance * newPDADownload; + idSWFSpriteInstance * newVideoDownload; + idSWFSpriteInstance * newPDA; + idSWFSpriteInstance * newVideo; + idSWFSpriteInstance * audioLog; + idSWFSpriteInstance * communication; + idSWFSpriteInstance * oxygen; + idSWFSpriteInstance * objective; + idSWFSpriteInstance * objectiveComplete; + idSWFSpriteInstance * ammoInfo; + idSWFSpriteInstance * weaponImg; + idSWFSpriteInstance * newWeapon; + idSWFSpriteInstance * pickupInfo; + idSWFSpriteInstance * talkCursor; + idSWFSpriteInstance * combatCursor; + idSWFSpriteInstance * grabberCursor; + idSWFSpriteInstance * bsInfo; + idSWFSpriteInstance * soulcubeInfo; + idSWFSpriteInstance * newItem; + idSWFSpriteInstance * respawnMessage; + idSWFSpriteInstance * flashlight; + idSWFSpriteInstance * mpChatObject; + idSWFSpriteInstance * mpConnection; + + idSWFSpriteInstance * mpInfo; + idSWFSpriteInstance * mpHitInfo; + + idSWFTextInstance * locationName; + idSWFTextInstance * securityText; + idSWFTextInstance * newPDAName; + idSWFTextInstance * newPDAHeading; + idSWFTextInstance * newVideoHeading; + + idSWFTextInstance * mpMessage; + idSWFTextInstance * mpTime; + + int audioLogPrevTime; + int commPrevTime; + + bool oxygenComm; + bool inVaccuum; + + idStr objTitle; + idStr objDesc; + const idMaterial * objScreenshot; + idStr objCompleteTitle; + + cursorState_t cursorState; + int cursorInCombat; + int cursorTalking; + int cursorItem; + int cursorGrabber; + int cursorNone; + idStr cursorAction; + idStr cursorFocus; + + bool showSoulCubeInfoOnLoad; +}; + +//* +//================================================ +//idMenuScreen_Scoreboard +//================================================ +//*/ +class idMenuScreen_Scoreboard : public idMenuScreen { +public: + + idMenuScreen_Scoreboard() : + playerList( NULL ) { + + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual void ShowScreen( const mainMenuTransition_t transitionType ); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + + virtual void SetPlayerData( idList< scoreboardInfo_t, TAG_IDLIB_LIST_MENU > data ); + virtual void UpdateTeamScores( int r, int b ); + virtual void UpdateGameInfo( idStr gameInfo ); + virtual void UpdateSpectating( idStr spectating, idStr follow ); + virtual void UpdateHighlight(); + +protected: + idMenuWidget_ScoreboardList * playerList; + +}; + +//* +//================================================ +//idMenuScreen_Scoreboard_CTF +//================================================ +//*/ +class idMenuScreen_Scoreboard_CTF : public idMenuScreen_Scoreboard { +public: + virtual void Initialize( idMenuHandler * data ); +}; + +//* +//================================================ +//idMenuScreen_Scoreboard_Team +//================================================ +//*/ +class idMenuScreen_Scoreboard_Team : public idMenuScreen_Scoreboard { +public: + virtual void Initialize( idMenuHandler * data ); +}; + + +/* +======================== +InvitePartyOrFriends + +Invites the master local user's party, if he's in one and the party isn't in the lobby already. +Otherwise brings up the invite friends system menu. +======================== +*/ +inline void InvitePartyOrFriends() { + const idLocalUser * const user = session->GetSignInManager().GetMasterLocalUser(); + if ( user != NULL && user->IsInParty() && user->GetPartyCount() > 1 && !session->IsPlatformPartyInLobby() ) { + session->InviteParty(); + } else { + session->InviteFriends(); + } +} + +#endif diff --git a/neo/d3xp/menus/MenuScreen_HUD.cpp b/neo/d3xp/menus/MenuScreen_HUD.cpp new file mode 100644 index 00000000..ec0c50fa --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_HUD.cpp @@ -0,0 +1,1967 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +extern idCVar pm_stamina; +extern idCVar in_useJoystick; +extern idCVar flashlight_batteryDrainTimeMS; + +/* +======================== +idMenuScreen_HUD::Initialize +======================== +*/ +void idMenuScreen_HUD::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); +} + +/* +======================== +idMenuScreen_HUD::ShowScreen +======================== +*/ +void idMenuScreen_HUD::ShowScreen( const mainMenuTransition_t transitionType ) { + if ( menuData != NULL ) { + menuGUI = menuData->GetGUI(); + } + + if ( menuGUI == NULL ) { + return; + } + + idSWFScriptObject & root = menuGUI->GetRootObject(); + playerInfo = root.GetNestedObj( "_bottomLeft", "playerInfo", "info" ); + stamina = root.GetNestedObj( "_bottomLeft", "stamina" ); + locationName = root.GetNestedText( "_bottomLeft", "location", "txtVal" ); + tipInfo = root.GetNestedObj( "_left", "tip" ); + + if ( playerInfo ) { + healthBorder = playerInfo->GetNestedSprite( "healthBorder", "damage" ); + healthPulse = playerInfo->GetNestedSprite( "healthBorder", "pulse" ); + armorFrame = playerInfo->GetNestedSprite( "armorFrame" ); + } + + // Security Update + security = root.GetNestedSprite( "_center", "security" ); + securityText = root.GetNestedText( "_center", "security", "info", "txtVal" ); + + // PDA Download + newPDADownload = root.GetNestedSprite( "_center", "pdaDownload" ); + newPDAName = root.GetNestedText( "_center", "pdaDownload", "info", "txtName" ); + newPDAHeading = root.GetNestedText( "_center", "pdaDownload", "info", "txtHeading" ); + newPDA = root.GetNestedSprite( "_bottomLeft", "newPDA" ); + + // Video Download + newVideoDownload = root.GetNestedSprite( "_center", "videoDownload" ); + newVideoHeading = root.GetNestedText( "_center", "videoDownload", "info", "txtHeading" ); + newVideo = root.GetNestedSprite( "_bottomLeft", "newVideo" ); + + // Audio Log + audioLog = root.GetNestedSprite( "_bottomLeft", "audioLog" ); + + // Radio Communication + communication = root.GetNestedSprite( "_bottomLeft", "communication" ); + + // Oxygen + oxygen = root.GetNestedSprite( "_bottomLeft", "oxygen" ); + flashlight = root.GetNestedSprite( "_bottomLeft", "flashlight" ); + + // Objective + objective = root.GetNestedSprite( "_right", "objective" ); + objectiveComplete = root.GetNestedSprite( "_right", "objectiveComplete" ); + + // Ammo Info + ammoInfo = root.GetNestedSprite( "_bottomRight", "ammoInfo" ); + bsInfo = root.GetNestedSprite( "_bottomRight", "bsInfo" ); + soulcubeInfo = root.GetNestedSprite( "_bottomRight", "soulcube" ); + + // If the player loaded a save with enough souls to use the cube, the icon wouldn't show. We're setting this flag in idPlayer::Restore so we can show the cube after loading a game + if ( showSoulCubeInfoOnLoad == true ) { + showSoulCubeInfoOnLoad = false; + UpdateSoulCube( true ); + } + + // Weapon pills + weaponPills = root.GetNestedObj( "_bottomRight", "weaponState" ); + weaponImg = root.GetNestedSprite( "_bottomRight", "weaponIcon" ); + weaponName = root.GetNestedObj( "_bottomRight", "weaponName" ); + + // Pickup Info + newWeapon = root.GetNestedSprite( "_center", "newWeapon" ); + pickupInfo = root.GetNestedSprite( "_bottomLeft", "pickupInfo" ); + newItem = root.GetNestedSprite( "_left", "newItem" ); + + // Cursors + talkCursor = root.GetNestedSprite( "_center", "crosshairTalk" ); + combatCursor = root.GetNestedSprite( "_center", "crosshairCombat" ); + grabberCursor = root.GetNestedSprite( "_center", "crosshairGrabber" ); + respawnMessage = root.GetNestedSprite( "_center", "respawnMessage" ); + + // MP OBJECTS + mpInfo = root.GetNestedSprite( "_top", "mp_info" ); + mpHitInfo = root.GetNestedSprite( "_bottom", "hitInfo" ); + mpTime = root.GetNestedText( "_top", "mp_info", "txtTime" ); + mpMessage = root.GetNestedText( "_top", "mp_info", "txtInfo" ); + mpWeapons = root.GetNestedObj( "_bottom", "mpWeapons" ); + mpChatObject = root.GetNestedSprite( "_left", "mpChat" ); + mpConnection = root.GetNestedSprite( "_center", "connectionMsg" ); + + + // Functions + + class idTriggerNewPDAOrVideo : public idSWFScriptFunction_RefCounted { + public: + idTriggerNewPDAOrVideo( idMenuScreen_HUD * _screen ) : + screen( _screen ) { + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + if ( screen == NULL ) { + return idSWFScriptVar(); + } + + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + + bool pdaDownload = parms[0].ToBool(); + if ( pdaDownload ) { + screen->ToggleNewPDA( true ); + } else { + screen->ToggleNewVideo( true ); + } + + return idSWFScriptVar(); + } + private: + idMenuScreen_HUD * screen; + }; + + menuGUI->SetGlobal( "toggleNewNotification", new idTriggerNewPDAOrVideo( this ) ); + +} + +/* +======================== +idMenuScreen_HUD::HideScreen +======================== +*/ +void idMenuScreen_HUD::HideScreen( const mainMenuTransition_t transitionType ) { + +} + +/* +======================== +idMenuScreen_HUD::Update +======================== +*/ +void idMenuScreen_HUD::Update() { + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_HUD::UpdateHealth +======================== +*/ +void idMenuScreen_HUD::UpdateHealthArmor( idPlayer * player ) { + + if ( !playerInfo || !player ) { + return; + } + + if ( common->IsMultiplayer() ) { + playerInfo->GetSprite()->SetYPos( 20.0f ); + } else { + playerInfo->GetSprite()->SetYPos( 0.0f ); + } + + idSWFTextInstance * txtVal = playerInfo->GetNestedText( "health", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( va( "%d", player->health ) ); + txtVal->SetStrokeInfo( true, 0.75f, 1.5f ); + + // Set the damage color + swfColorRGBA_t color; + color.r = 255; + color.a = 255; + uint8 gbColor; + if ( player->health > 60 ) { + gbColor = 255; + } else if ( player->health > 30 ) { + gbColor = 156; + } else { + gbColor = 0; + } + color.g = gbColor; + color.b = gbColor; + txtVal->color = color; + } + + txtVal = playerInfo->GetNestedText( "armor", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( va( "%d", player->inventory.armor ) ); + txtVal->SetStrokeInfo( true, 0.75f, 1.5f ); + + if ( armorFrame != NULL ) { + if ( player->inventory.armor == 0 ) { + armorFrame->StopFrame( 2 ); + } else { + armorFrame->StopFrame( 1 ); + } + } + } + + if ( healthBorder != NULL ) { + healthBorder->StopFrame( 100 - player->health + 1 ); + } + + if ( healthPulse != NULL ) { + if ( player->healthPulse ) { + player->StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL ); + player->healthPulse = false; + healthPulse->SetVisible( true ); + healthPulse->PlayFrame( "rollOn" ); + } + + if ( player->healthTake ) { + player->StartSound( "snd_healthtake", SND_CHANNEL_ITEM, 0, false, NULL ); + player->healthTake = false; + healthPulse->SetVisible( true ); + healthPulse->PlayFrame( "rollOn" ); + } + } +} + +/* +======================== +idMenuScreen_HUD::UpdateStamina +======================== +*/ +void idMenuScreen_HUD::UpdateStamina( idPlayer * player ) { + + if ( !stamina || !player ) { + return; + } + + idSWFSpriteInstance * stamSprite = stamina->GetSprite(); + if ( stamSprite != NULL ) { + + if ( common->IsMultiplayer() ) { + stamSprite->SetVisible( false ); + } else { + float max_stamina = pm_stamina.GetFloat(); + if ( !max_stamina ) { + stamSprite->SetVisible( false ); + } else { + stamSprite->SetVisible( true ); + float staminaPercent = idMath::Ftoi( 100.0f * player->stamina / max_stamina ); + stamSprite->StopFrame( staminaPercent + 1 ); + } + } + } +} + +/* +======================== +idMenuScreen_HUD::UpdateLocation +======================== +*/ +void idMenuScreen_HUD::UpdateWeaponInfo( idPlayer * player ) { + + if ( !player || !ammoInfo ) { + return; + } + + idEntityPtr weapon = player->weapon; + + assert( weapon.GetEntity() ); + + int inClip = weapon.GetEntity()->AmmoInClip(); + int ammoAmount = weapon.GetEntity()->AmmoAvailable(); + + //Make sure the hud always knows how many bloodstone charges there are + int ammoRequired; + int bloodstoneAmmo = 0; + if ( player->weapon_bloodstone >= 0 ) { + ammo_t ammo_i = player->inventory.AmmoIndexForWeaponClass( "weapon_bloodstone_passive", &ammoRequired ); + bloodstoneAmmo = player->inventory.HasAmmo( ammo_i, ammoRequired ); + } + if ( bsInfo ) { + if ( bloodstoneAmmo > 0 ) { + bsInfo->SetVisible( true ); + bsInfo->StopFrame( bloodstoneAmmo + 1 ); + } else { + bsInfo->SetVisible( false ); + } + } + + if ( ammoAmount == -1 || player->GetCurrentWeaponSlot() == player->weapon_bloodstone || player->GetCurrentWeaponSlot() == player->weapon_soulcube ) { + + ammoInfo->SetVisible( false ); + + } else { + + idStr totalAmmo; + idStr playerAmmo; + idStr playerClip; + + bool showClip = true; + + //Hack to stop the bloodstone ammo to display when it is being activated + if ( !weapon.GetEntity()->IsReady() ) { + // show infinite ammo + playerAmmo = ""; + totalAmmo = ""; + } else { + // show remaining ammo + totalAmmo = va( "%i", ammoAmount ); + playerAmmo = weapon.GetEntity()->ClipSize() ? va( "%i", inClip ) : "--"; // how much in the current clip + playerClip = weapon.GetEntity()->ClipSize() ? va( "%i", ammoAmount / weapon.GetEntity()->ClipSize() ) : "--"; + //allAmmo = va( "%i/%i", inClip, ammoAmount ); + } + + if ( !weapon.GetEntity()->ClipSize() ) { + showClip = false; + } + + bool ammoEmpty = ( ammoAmount == 0 ); + bool clipEmpty = ( weapon.GetEntity()->ClipSize() ? inClip == 0 : false ); + bool clipLow = ( weapon.GetEntity()->ClipSize() ? inClip <= weapon.GetEntity()->LowAmmo() : false ); + + //Hack to stop the bloodstone ammo to display when it is being activated + if ( player->GetCurrentWeaponSlot() == player->weapon_bloodstone ) { + ammoEmpty = false; + clipEmpty = false; + clipLow = false; + } + + if ( showClip ) { + + ammoInfo->SetVisible( true ); + ammoInfo->StopFrame( 1 ); + if ( common->IsMultiplayer() ) { + ammoInfo->SetYPos( 20.0f ); + } else { + ammoInfo->SetYPos( 0.0f ); + } + idSWFSpriteInstance * txtClipSprite = ammoInfo->GetScriptObject()->GetNestedSprite( "info", "clip" ); + idSWFSpriteInstance * clipLowSprite = ammoInfo->GetScriptObject()->GetNestedSprite( "info", "lowAmmo" ); + idSWFSpriteInstance * clipEmptySprite = ammoInfo->GetScriptObject()->GetNestedSprite( "info", "clipEmpty" ); + idSWFSpriteInstance * ammoEmptySprite = ammoInfo->GetScriptObject()->GetNestedSprite( "info", "noAmmo" ); + idSWFSpriteInstance * txtAmmoSprite = ammoInfo->GetScriptObject()->GetNestedSprite( "info", "ammoCount" ); + + idSWFTextInstance * txtClip = ammoInfo->GetScriptObject()->GetNestedText( "info", "clip", "clipCount", "txtVal" ); + idSWFTextInstance * txtAmmo = ammoInfo->GetScriptObject()->GetNestedText( "info", "ammoCount", "txtVal" ); + + if ( txtClipSprite && clipLowSprite && clipEmptySprite ) { + + if ( clipEmpty ) { + clipLowSprite->SetVisible( false ); + clipEmptySprite->SetVisible( true ); + txtClipSprite->StopFrame( 3 ); + } else if ( clipLow ) { + clipLowSprite->SetVisible( true ); + clipEmptySprite->SetVisible( false ); + txtClipSprite->StopFrame( 2 ); + } else { + clipLowSprite->SetVisible( false ); + clipEmptySprite->SetVisible( false ); + txtClipSprite->StopFrame( 1 ); + } + + if ( txtClip != NULL ) { + txtClip->SetText( playerAmmo ); + txtClip->SetStrokeInfo( true, 0.75f, 1.5f ); + } + } + + if ( txtAmmo != NULL ) { + + if ( ammoEmptySprite && txtAmmoSprite ) { + if ( ammoEmpty ) { + ammoEmptySprite->SetVisible( true ); + txtAmmoSprite->StopFrame( 2 ); + } else { + ammoEmptySprite->SetVisible( false ); + txtAmmoSprite->StopFrame( 1 ); + } + } + + txtAmmo->SetText( totalAmmo ); + txtAmmo->SetStrokeInfo( true, 0.75f, 1.5f ); + } + } else { + + ammoInfo->SetVisible( true ); + ammoInfo->StopFrame( 2 ); + + if ( common->IsMultiplayer() ) { + ammoInfo->SetYPos( 20.0f ); + } else { + ammoInfo->SetYPos( 0.0f ); + } + + idSWFTextInstance * txtAmmo = ammoInfo->GetScriptObject()->GetNestedText( "info", "txtVal" ); + + if ( txtAmmo != NULL ) { + txtAmmo->SetText( totalAmmo ); + txtAmmo->SetStrokeInfo( true, 0.75f, 1.5f ); + } + + } + } +} + +/* +======================== +idMenuScreen_HUD::GiveWeapon +======================== +*/ +void idMenuScreen_HUD::GiveWeapon( idPlayer * player, int weaponIndex ) { + + if ( common->IsMultiplayer() ) { + return; + } + + const char *weapnum = va( "def_weapon%d", weaponIndex ); + const char *weap = player->spawnArgs.GetString( weapnum ); + if ( weap != NULL && *weap != NULL ) { + const idDeclEntityDef * weaponDef = gameLocal.FindEntityDef( weap, false ); + if ( weaponDef != NULL ) { + const char * hudIconName = weaponDef->dict.GetString( "hudIcon" ); + if ( hudIconName[ 0 ] == '\0' ) { + idLib::Warning( "idMenuScreen_HUD: Missing hudIcon for weapon %s", weap ); + return; + } + + const idMaterial * hudIcon = declManager->FindMaterial( hudIconName, false ); + if ( newWeapon != NULL ) { + newWeapon->SetVisible( true ); + newWeapon->PlayFrame( 2 ); + + idSWFSpriteInstance * topImg = newWeapon->GetScriptObject()->GetNestedSprite( "topImg" ); + idSWFSpriteInstance * botImg = newWeapon->GetScriptObject()->GetNestedSprite( "botImg" ); + + if ( topImg && botImg ) { + topImg->SetMaterial( hudIcon ); + botImg->SetMaterial( hudIcon ); + } + } + } + } +} + +/* +======================== +idMenuScreen_HUD::UpdateWeaponStates +======================== +*/ +void idMenuScreen_HUD::UpdatePickupInfo( int index, const idStr & name ) { + + if ( !pickupInfo ) { + return; + } + + idSWFTextInstance * txtItem = pickupInfo->GetScriptObject()->GetNestedText( va( "item%d", index ), "txtVal" ); + if ( txtItem != NULL ) { + txtItem->SetText( name ); + txtItem->SetStrokeInfo( true, 0.6f, 2.0f ); + } + +} + +/* +======================== +idMenuScreen_HUD::IsPickupListReady +======================== +*/ +bool idMenuScreen_HUD::IsPickupListReady() { + + if ( !pickupInfo ) { + return false; + } + + if ( pickupInfo->GetCurrentFrame() == 1 ) { + return true; + } + + return false; +} + +/* +======================== +idMenuScreen_HUD::UpdateWeaponStates +======================== +*/ +void idMenuScreen_HUD::ShowPickups() { + + if ( !pickupInfo ) { + return; + } + + pickupInfo->SetVisible( true ); + pickupInfo->PlayFrame( "rollOn" ); +} + +/* +======================== +idMenuScreen_HUD::SetCursorState +======================== +*/ +void idMenuScreen_HUD::SetCursorState( idPlayer * player, cursorState_t state, int set ) { + + switch ( state ) { + case CURSOR_TALK: { + cursorNone = 0; + cursorTalking = set; + break; + } + case CURSOR_IN_COMBAT: { + cursorNone = 0; + cursorInCombat = set; + break; + } + case CURSOR_ITEM: { + cursorNone = 0; + cursorItem = set; + break; + } + case CURSOR_GRABBER: { + cursorNone = 0; + cursorGrabber = set; + break; + } + case CURSOR_NONE: { + // so that talk button still appears for 3D view + if ( cursorState != CURSOR_TALK || cursorTalking != 1 ) { + cursorTalking = 0; + cursorGrabber = 0; + cursorInCombat = 0; + cursorItem = 0; + cursorNone = 1; + } + break; + } + } + +} + +/* +======================== +idMenuScreen_HUD::SetCursorText +======================== +*/ +void idMenuScreen_HUD::SetCursorText( const idStr & action, const idStr & focus ) { + cursorAction = action; + cursorFocus = focus; +} + +/* +======================== +idMenuScreen_HUD::CombatCursorFlash +======================== +*/ +void idMenuScreen_HUD::CombatCursorFlash() { + + if ( cursorInCombat ) { + if ( cursorState == CURSOR_IN_COMBAT ) { + if ( combatCursor ) { + combatCursor->PlayFrame( "hit" ); + } + } + } + +} + +/* +======================== +idMenuScreen_HUD::UpdateCursorState +======================== +*/ +void idMenuScreen_HUD::UpdateCursorState() { + + if ( !cursorTalking && !cursorInCombat && !cursorGrabber && !cursorItem ) { + + cursorNone = true; + cursorState = CURSOR_NONE; + + // hide all cursors + if ( combatCursor ) { + combatCursor->StopFrame( 1 ); + combatCursor->SetVisible( false ); + } + + if ( talkCursor ) { + talkCursor->StopFrame( 1 ); + talkCursor->SetVisible( false ); + } + + if ( grabberCursor ) { + grabberCursor->StopFrame( 1 ); + grabberCursor->SetVisible( false ); + } + + } else { + + if ( cursorTalking ) { + + if ( cursorTalking == 1 ) { // ready to talk + + } else if ( cursorTalking == 2 ) { // already talking / busy + + } + + if ( cursorState != CURSOR_TALK ) { + + if ( combatCursor ) { + combatCursor->StopFrame( 1 ); + combatCursor->SetVisible( false ); + } + + if ( grabberCursor ) { + grabberCursor->StopFrame( 1 ); + grabberCursor->SetVisible( false ); + } + + // play roll on + if ( talkCursor ) { + talkCursor->SetVisible( true ); + talkCursor->PlayFrame( 2 ); + + idSWFSpriteInstance * topBacking = talkCursor->GetScriptObject()->GetNestedSprite( "backing", "topBar" ); + idSWFSpriteInstance * bottomBacking = talkCursor->GetScriptObject()->GetNestedSprite( "backing", "botBar" ); + + idSWFTextInstance * txtAction = talkCursor->GetScriptObject()->GetNestedText( "info", "txtAction" ); + idSWFTextInstance * txtFocus = talkCursor->GetScriptObject()->GetNestedText( "info", "txtFocus" ); + + idSWFTextInstance * txtPrompt = talkCursor->GetScriptObject()->GetNestedText( "talkPrompt", "txtPrompt" ); + + if ( txtAction ) { + + if ( !in_useJoystick.GetBool() ) { + txtAction->tooltip = true; + keyBindings_t bind = idKeyInput::KeyBindingsFromBinding( "_use", true ); + idStr actionText = idLocalization::GetString( cursorAction ); + if ( !bind.mouse.IsEmpty() ) { + actionText.Append( " [" ); + actionText.Append( bind.mouse ); + actionText.Append( "]" ); + } else if ( !bind.keyboard.IsEmpty() ) { + actionText.Append( " [" ); + actionText.Append( bind.keyboard ); + actionText.Append( "]" ); + } + + txtAction->SetText( actionText ); + } else { + txtAction->tooltip = false; + txtAction->SetText( cursorAction ); + } + txtAction->SetStrokeInfo( true, 0.75f, 1.5f ); + float actionLength = txtAction->GetTextLength(); + + if ( topBacking ) { + if ( !cursorAction.IsEmpty() ) { + topBacking->SetXPos( actionLength ); + } else { + topBacking->SetXPos( -75.0f ); + } + } + } + + if ( txtFocus ) { + txtFocus->SetText( cursorFocus ); + txtFocus->SetStrokeInfo( true, 0.75f, 1.5f ); + float focusLength = txtFocus->GetTextLength(); + + if ( bottomBacking ) { + if ( !cursorFocus.IsEmpty() ) { + bottomBacking->SetXPos( focusLength ); + } else { + bottomBacking->SetXPos( -75.0f ); + } + } + } + + if ( txtPrompt ) { + if ( in_useJoystick.GetBool() ) { + txtPrompt->tooltip = true; + txtPrompt->SetText( "_use" ); + } else { + txtPrompt->tooltip = false; + txtPrompt->SetText( "" ); + } + } + } + cursorState = CURSOR_TALK; + } + + } else if ( cursorGrabber ) { + + if ( talkCursor ) { + talkCursor->StopFrame( 1 ); + talkCursor->SetVisible( false ); + } + + if ( combatCursor ) { + combatCursor->StopFrame( 1 ); + combatCursor->SetVisible( false ); + } + + if ( cursorState != CURSOR_GRABBER ) { + if ( grabberCursor ) { + grabberCursor->SetVisible( true ); + grabberCursor->PlayFrame( "loop" ); + } + } + + cursorState = CURSOR_GRABBER; + + } else if ( cursorItem ) { + + cursorState = CURSOR_ITEM; + + } else if ( cursorInCombat ) { + + if ( cursorState == CURSOR_TALK ) { + if ( talkCursor ) { + talkCursor->StopFrame( 1 ); + talkCursor->SetVisible( false ); + } + + if ( combatCursor ) { + combatCursor->SetVisible( true ); + combatCursor->PlayFrame( "rollOn" ); + } + + // play cursor roll on + } else if ( cursorState != CURSOR_IN_COMBAT ) { + + if ( grabberCursor ) { + grabberCursor->StopFrame( 1 ); + grabberCursor->SetVisible( false ); + } + + // set cursor visible + if ( combatCursor ) { + combatCursor->SetVisible( true ); + combatCursor->StopFrame( 2 ); + } + + } + + cursorState = CURSOR_IN_COMBAT; + + } + } +} + +/* +======================== +idMenuScreen_HUD::UpdateSoulCube +======================== +*/ +void idMenuScreen_HUD::UpdateSoulCube( bool ready ) { + + if ( !soulcubeInfo ) { + return; + } + + if ( ready && !soulcubeInfo->IsVisible() ) { + soulcubeInfo->SetVisible( true ); + soulcubeInfo->PlayFrame( "rollOn" ); + } else if ( !ready ) { + soulcubeInfo->PlayFrame( "rollOff" ); + } + +} + +/* +======================== +idMenuScreen_HUD::ShowRespawnMessage +======================== +*/ +void idMenuScreen_HUD::ShowRespawnMessage( bool show ) { + + if ( !respawnMessage ) { + return; + } + + if ( show ) { + respawnMessage->SetVisible( true ); + respawnMessage->PlayFrame( "rollOn" ); + + idSWFTextInstance * message = respawnMessage->GetScriptObject()->GetNestedText( "info", "txtMessage" ); + if ( message != NULL ) { + message->tooltip = true; + message->SetText( "#str_respawn_message" ); + message->SetStrokeInfo( true ); + } + + } else { + if ( respawnMessage->IsVisible() ) { + respawnMessage->PlayFrame( "rollOff" ); + } + } +} + +/* +======================== +idMenuScreen_HUD::UpdateWeaponStates +======================== +*/ +void idMenuScreen_HUD::UpdateWeaponStates( idPlayer * player, bool weaponChanged ) { + + if ( !weaponPills ) { + return; + } + + if ( player == NULL ) { + return; + } + + idStr displayName; + if ( common->IsMultiplayer() ) { + + if ( !mpWeapons || player->GetIdealWeapon() == 0 ) { + return; + } + + weaponPills->GetSprite()->SetVisible( false ); + + if ( weaponChanged ) { + mpWeapons->GetSprite()->SetVisible( true ); + mpWeapons->GetSprite()->PlayFrame( "rollOn" ); + + int weaponDefIndex = -1; + idList< idStr > weaponDefNames; + // start at 1 so we skip the fists + for ( int i = 1; i < MAX_WEAPONS; ++i ) { + if ( player->inventory.weapons & ( 1 << i ) ) { + if ( i == player->GetIdealWeapon() ) { + weaponDefIndex = weaponDefNames.Num(); + } + weaponDefNames.Append( va( "def_weapon%d", i ) ); + } + } + + int numRightWeapons = 0; + int numLeftWeapons = 0; + + if ( weaponDefNames.Num() == 2 ) { + numRightWeapons = 1 - weaponDefIndex; + numLeftWeapons = weaponDefIndex; + } else if ( weaponDefNames.Num() == 3 ) { + numRightWeapons = 1; + numLeftWeapons = 1; + } else if ( weaponDefNames.Num() > 3 ) { + numRightWeapons = 2; + numLeftWeapons = 2; + } + + for ( int i = -2; i < 3; ++i ) { + + bool hide = false; + + if ( i < 0 && idMath::Abs( i ) > numLeftWeapons ) { + hide = true; + } else if ( i > numRightWeapons ) { + hide = true; + } else if ( weaponDefNames.Num() == 0 ) { + hide = true; + } + + int index = i; + if ( i < 0 ) { + index = 2 + idMath::Abs( i ); + } + + idSWFSpriteInstance * topValid = mpWeapons->GetNestedSprite( "list", va( "weapon%i", index ), "topValid" ); + idSWFSpriteInstance * botValid = mpWeapons->GetNestedSprite( "list", va( "weapon%i", index ), "botValid" ); + idSWFSpriteInstance * topInvalid = mpWeapons->GetNestedSprite( "list", va( "weapon%i", index ), "topInvalid" ); + idSWFSpriteInstance * botInvalid = mpWeapons->GetNestedSprite( "list", va( "weapon%i", index ), "botInvalid" ); + + if ( !topValid || !botValid || !topInvalid || !botInvalid ) { + mpWeapons->GetSprite()->SetVisible( false ); + break; + } + + if ( hide ) { + topValid->SetVisible( false ); + botValid->SetVisible( false ); + topInvalid->SetVisible( false ); + botInvalid->SetVisible( false ); + continue; + } + + int weaponIndex = weaponDefIndex + i; + if ( weaponIndex < 0 ) { + weaponIndex = weaponDefNames.Num() + weaponIndex; + } else if ( weaponIndex >= weaponDefNames.Num() ) { + weaponIndex = ( weaponIndex - weaponDefNames.Num() ); + } + + int weapState = 1; + const idMaterial * hudIcon = NULL; + const char * weapNum = weaponDefNames[ weaponIndex ]; + const char * weap = player->spawnArgs.GetString( weapNum ); + if ( weap != NULL && *weap != NULL ) { + const idDeclEntityDef * weaponDef = gameLocal.FindEntityDef( weap, false ); + if ( weaponDef != NULL ) { + hudIcon = declManager->FindMaterial( weaponDef->dict.GetString( "hudIcon" ), false ); + if ( i == 0 ) { + displayName = weaponDef->dict.GetString( "display_name" ); + weapState++; + } + } + + if ( !player->inventory.HasAmmo( weap, true, player ) ) { + weapState = 0; + } + } + + topValid->SetVisible( false ); + botValid->SetVisible( false ); + topInvalid->SetVisible( false ); + botInvalid->SetVisible( false ); + + topValid->SetMaterial( hudIcon ); + botValid->SetMaterial( hudIcon ); + topInvalid->SetMaterial( hudIcon ); + botInvalid->SetMaterial( hudIcon ); + + if ( weapState == 0 ) { + botInvalid->SetVisible( true ); + if ( i == 0 ) { + topInvalid->SetVisible( true ); + } + } else if ( weapState == 2 ) { + topValid->SetVisible( true ); + botValid->SetVisible( true ); + } else { + botValid->SetVisible( true ); + } + } + } + + } else { + + bool hasWeapons = false; + const idMaterial * hudIcon = NULL; + + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + const char *weapnum = va( "def_weapon%d", i ); + int weapstate = 0; + if ( player->inventory.weapons & ( 1 << i ) ) { + hasWeapons = true; + const char *weap = player->spawnArgs.GetString( weapnum ); + if ( weap != NULL && *weap != NULL ) { + weapstate++; + } + if ( player->GetIdealWeapon() == i ) { + + const idDeclEntityDef * weaponDef = gameLocal.FindEntityDef( weap, false ); + if ( weaponDef != NULL ) { + hudIcon = declManager->FindMaterial( weaponDef->dict.GetString( "hudIcon" ), false ); + displayName = weaponDef->dict.GetString( "display_name" ); + } + + weapstate++; + } + } + + idSWFSpriteInstance * pill = weaponPills->GetNestedSprite( va( "pill%d", i ) ); + if ( pill ) { + pill->StopFrame( weapstate + 1 ); + } + } + + if ( !hasWeapons ) { + weaponPills->GetSprite()->SetVisible( false ); + } else { + weaponPills->GetSprite()->SetVisible( true ); + } + + if ( weaponImg ) { + if ( weaponChanged && hudIcon != NULL ) { + weaponImg->SetVisible( true ); + weaponImg->PlayFrame( 2 ); + + idSWFSpriteInstance * topImg = weaponImg->GetScriptObject()->GetNestedSprite( "topImg" ); + idSWFSpriteInstance * botImg = weaponImg->GetScriptObject()->GetNestedSprite( "botImg" ); + + if ( topImg != NULL && botImg != NULL ) { + topImg->SetMaterial( hudIcon ); + botImg->SetMaterial( hudIcon ); + } + + /*if ( weaponName && weaponName->GetSprite() ) { + weaponName->GetSprite()->SetVisible( true ); + weaponName->GetSprite()->PlayFrame( 2 ); + + idSWFTextInstance * txtVal = weaponName->GetNestedText( "info", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( displayName ); + txtVal->SetStrokeInfo( true, 0.6f, 2.0f ); + } + }*/ + } + } + } + +} + +/* +======================== +idMenuScreen_HUD::UpdateLocation +======================== +*/ +void idMenuScreen_HUD::UpdateLocation( idPlayer * player ) { + + if ( !locationName || !player ) { + return; + } + + idPlayer * playertoLoc = player; + if( player->spectating && player->spectator != player->entityNumber ) { + playertoLoc = static_cast< idPlayer* >( gameLocal.entities[ player->spectator ] ); + if( playertoLoc == NULL ) { + playertoLoc = player; + } + } + + idLocationEntity *locationEntity = gameLocal.LocationForPoint( playertoLoc->GetEyePosition() ); + if ( locationEntity ) { + locationName->SetText( locationEntity->GetLocation() ); + } else { + locationName->SetText( idLocalization::GetString( "#str_02911" ) ); + } + locationName->SetStrokeInfo( true, 0.6f, 2.0f ); + +} + +/* +======================== +idMenuScreen_HUD::ShowTip +======================== +*/ +void idMenuScreen_HUD::ShowTip( const char * title, const char * tip ) { + + if ( !tipInfo ) { + return; + } + + idSWFSpriteInstance * tipSprite = tipInfo->GetSprite(); + + if ( !tipSprite ) { + return; + } + + tipSprite->SetVisible( true ); + tipSprite->PlayFrame( "rollOn" ); + + idSWFTextInstance * txtTitle = tipInfo->GetNestedText( "info", "txtTitle" ); + idSWFTextInstance * txtTip = tipInfo->GetNestedText( "info", "txtTip" ); + + if ( txtTitle != NULL ) { + txtTitle->SetText( title ); + txtTitle->SetStrokeInfo( true, 0.75f, 1.5f ); + } + + if ( txtTip != NULL ) { + txtTip->SetText( tip ); + txtTip->tooltip = true; + txtTip->SetStrokeInfo( true, 0.75f, 1.5f ); + int numLines = txtTip->CalcNumLines(); + if ( numLines == 0 ) { + numLines = 1; + } + idSWFSpriteInstance * backing = tipInfo->GetNestedSprite( "info", "backing" ); + if ( backing != NULL ) { + backing->StopFrame( numLines ); + } + } +} + +/* +======================== +idMenuScreen_HUD::HideTip +======================== +*/ +void idMenuScreen_HUD::HideTip() { + + if ( !tipInfo ) { + return; + } + + idSWFSpriteInstance * tipSprite = tipInfo->GetSprite(); + + if ( !tipSprite ) { + return; + } + + tipSprite->SetVisible( true ); + tipSprite->PlayFrame( "rollOff" ); + +} + +/* +======================== +idMenuScreen_HUD::DownloadPDA +======================== +*/ +void idMenuScreen_HUD::DownloadPDA( const idDeclPDA * pda, bool newSecurity ) { + + if ( newPDADownload ) { + newPDADownload->SetVisible( true ); + newPDADownload->PlayFrame( "rollOn" ); + + newPDAName = newPDADownload->GetScriptObject()->GetNestedText( "info", "txtName" ); + newPDAHeading = newPDADownload->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + + if ( newPDAName && GetSWFObject() != NULL ) { + idStr pdaName = pda->GetPdaName(); + pdaName.RemoveColors(); + GetSWFObject()->SetGlobal( "pdaNameDownload", pdaName ); + newPDAName->SetStrokeInfo( true, 0.9f, 2.0f ); + } + + if ( newPDAHeading && GetSWFObject() != NULL ) { + GetSWFObject()->SetGlobal( "pdaDownloadHeading", "#str_02031" ); + newPDAHeading->SetStrokeInfo( true, 0.9f, 2.0f ); + } + } + + if ( newSecurity ) { + UpdatedSecurity(); + } +} + +/* +======================== +idMenuScreen_HUD::DownloadVideo +======================== +*/ +void idMenuScreen_HUD::DownloadVideo() { + + if ( newVideoDownload ) { + newVideoDownload->SetVisible( true ); + newVideoDownload->PlayFrame( "rollOn" ); + + newVideoHeading = newVideoDownload->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + + if ( newVideoHeading ) { + newVideoHeading->SetText( "#str_02033" ); + newVideoHeading->SetStrokeInfo( true, 0.9f, 2.0f ); + } + } +} + +/* +======================== +idMenuScreen_HUD::UpdatedSecurity +======================== +*/ +void idMenuScreen_HUD::UpdatedSecurity() { + if ( security != NULL && securityText != NULL ) { + security->SetVisible( true ); + security->PlayFrame( "rollOn" ); + securityText->SetText( "#str_02032" ); + securityText->SetStrokeInfo( true, 0.9f, 2.0f ); + } +} + +/* +======================== +idMenuScreen_HUD::ClearNewPDAInfo +======================== +*/ +void idMenuScreen_HUD::ClearNewPDAInfo() { + + ToggleNewVideo( false ); + ToggleNewPDA( false ); + + if ( security ) { + security->StopFrame( 1 ); + } + + if ( newPDADownload ) { + newPDADownload->StopFrame( 1 ); + } + + if ( newVideoDownload ) { + newVideoDownload->StopFrame( 1 ); + } + +} + +/* +======================== +idMenuScreen_HUD::UpdatedSecurity +======================== +*/ +void idMenuScreen_HUD::ToggleNewVideo( bool show ) { + + if ( !newVideo ) { + return; + } + + if ( show && !newVideo->IsVisible() ) { + newVideo->SetVisible( true ); + newVideo->PlayFrame( "rollOn" ); + } else if ( !show ) { + newVideo->StopFrame( 1 ); + } + +} + +/* +======================== +idMenuScreen_HUD::UpdatedSecurity +======================== +*/ +void idMenuScreen_HUD::ToggleNewPDA( bool show ) { + + if ( !newPDA ) { + return; + } + + if ( show && !newPDA->IsVisible() ) { + newPDA->SetVisible( true ); + newPDA->PlayFrame( "rollOn" ); + } else if ( !show ) { + newPDA->StopFrame( 1 ); + } + +} + +/* +======================== +idMenuScreen_HUD::UpdatedSecurity +======================== +*/ +void idMenuScreen_HUD::UpdateAudioLog( bool show ) { + + if ( !audioLog ) { + return; + } + + if ( show && !audioLog->IsVisible() ) { + audioLog->SetVisible( true ); + audioLog->StopFrame( "2" ); + + for ( int index = 0; index < 13; ++index ) { + idSWFSpriteInstance * node = audioLog->GetScriptObject()->GetNestedSprite( "bar", va( "node%d", index ) ); + if ( node != NULL ) { + int frame = gameLocal.random.RandomInt( 100 ); + node->SetScale( 100.0f, frame ); + float toFrame = gameLocal.random.RandomFloat(); + node->SetMoveToScale( -1.0f, toFrame ); + } + } + + } else if ( !show ) { + + audioLog->StopFrame( 1 ); + + } else if ( show ) { + + if ( audioLogPrevTime == 0 ) { + audioLogPrevTime = gameLocal.time; + } + + for ( int index = 0; index < 13; ++index ) { + idSWFSpriteInstance * node = audioLog->GetScriptObject()->GetNestedSprite( "bar", va( "node%d", index ) ); + if ( node != NULL ) { + float diff = gameLocal.time - audioLogPrevTime; + float speed = ( diff / 350.0f ) * 100.0f; + if ( !node->UpdateMoveToScale( speed ) ) { + int frame = gameLocal.random.RandomInt( 100 ); + float scale = frame / 100.0f; + node->SetMoveToScale( -1.0f, scale ); + } + } + } + audioLogPrevTime = gameLocal.time; + } +} + +/* +======================== +idMenuScreen_HUD::UpdatedSecurity +======================== +*/ +void idMenuScreen_HUD::UpdateCommunication( bool show, idPlayer * player ) { + + if ( !communication || !player ) { + return; + } + + bool oxygenChanged = false; + if ( inVaccuum != oxygenComm ) { + oxygenChanged = true; + } + + if ( show && !communication->IsVisible() ) { + communication->SetVisible( true ); + if ( inVaccuum ) { + communication->StopFrame( "oxygen" ); + } else { + communication->StopFrame( "2" ); + } + + for ( int index = 0; index < 16; ++index ) { + idSWFSpriteInstance * node = communication->GetScriptObject()->GetNestedSprite( "info", "bar", va( "node%d", index ) ); + if ( node != NULL ) { + int frame = gameLocal.random.RandomInt( 100 ); + node->SetScale( 100.0f, frame ); + float toFrame = gameLocal.random.RandomFloat(); + node->SetMoveToScale( -1.0f, toFrame ); + } + } + } else if ( !show ) { + communication->StopFrame( 1 ); + } else if ( show ) { + + if ( oxygenChanged ) { + if ( inVaccuum ) { + communication->PlayFrame( "rollUp" ); + } else { + communication->PlayFrame( "rollDown" ); + } + } + + if ( commPrevTime == 0 ) { + commPrevTime = gameLocal.time; + } + + for ( int index = 0; index < 16; ++index ) { + idSWFSpriteInstance * node = communication->GetScriptObject()->GetNestedSprite( "info", "bar", va( "node%d", index ) ); + if ( node != NULL ) { + float diff = gameLocal.time - commPrevTime; + float speed = ( diff / 350.0f ) * 100.0f; + if ( !node->UpdateMoveToScale( speed ) ) { + int frame = gameLocal.random.RandomInt( 100 ); + float scale = frame / 100.0f; + node->SetMoveToScale( -1.0f, scale ); + } + } + } + + commPrevTime = gameLocal.time; + } + + oxygenComm = inVaccuum; +} + +/* +======================== +idMenuScreen_HUD::UpdateOxygen +======================== +*/ +void idMenuScreen_HUD::UpdateOxygen( bool show, int val ) { + + if ( !oxygen ) { + return; + } + + if ( show ) { + if ( !oxygen->IsVisible() ) { + inVaccuum = true; + oxygen->SetVisible( true ); + oxygen->PlayFrame( "rollOn" ); + } + + idSWFSpriteInstance * info = oxygen->GetScriptObject()->GetNestedSprite( "info" ); + if ( info != NULL ) { + info->StopFrame( val + 1 ); + } + + idSWFSpriteInstance * goodFrame = oxygen->GetScriptObject()->GetNestedSprite( "goodFrame" ); + idSWFSpriteInstance * badFrame = oxygen->GetScriptObject()->GetNestedSprite( "badFrame" ); + + if ( goodFrame != NULL && badFrame != NULL ) { + if ( val + 1 >= 36 ) { + goodFrame->SetVisible( true ); + badFrame->SetVisible( false ); + } else { + goodFrame->SetVisible( false ); + badFrame->SetVisible( true ); + } + } + + idSWFTextInstance * txtVal = oxygen->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_00100922" ); + txtVal->SetStrokeInfo( true, 0.9f, 2.0f ); + } + + txtVal = oxygen->GetScriptObject()->GetNestedText( "info", "txtVal" ); + if ( txtVal != NULL ) { + txtVal->SetText( va( "%d", val ) ); + txtVal->SetStrokeInfo( true, 0.9f, 2.0f ); + } + + } else if ( !show ) { + inVaccuum = false; + oxygen->StopFrame( 1 ); + } +} + +/* +======================== +idMenuScreen_HUD::SetupObjective +======================== +*/ +void idMenuScreen_HUD::SetupObjective( const idStr & title, const idStr & desc, const idMaterial * screenshot ) { + objTitle = title; + objDesc = desc; + objScreenshot = screenshot; +} + +/* +======================== +idMenuScreen_HUD::SetupObjective +======================== +*/ +void idMenuScreen_HUD::SetupObjectiveComplete( const idStr & title ) { + + objCompleteTitle = title; + +} + +/* +======================== +idMenuScreen_HUD::ShowObjective +======================== +*/ +void idMenuScreen_HUD::ShowObjective( bool complete ) { + + if ( complete ) { + + if ( !objectiveComplete ) { + return; + } + + objectiveComplete->SetVisible( true ); + objectiveComplete->PlayFrame( "rollOn" ); + + idSWFTextInstance * txtComplete = objectiveComplete->GetScriptObject()->GetNestedText( "info", "txtComplete" ); + idSWFTextInstance * txtTitle = objectiveComplete->GetScriptObject()->GetNestedText( "info", "txtTitle" ); + idSWFSpriteInstance * rightArrow = objectiveComplete->GetScriptObject()->GetNestedSprite( "info", "right_arrows" ); + + if ( txtComplete != NULL ) { + txtComplete->SetStrokeInfo( true, 0.9f, 2.0f ); + + if ( rightArrow != NULL ) { + rightArrow->SetXPos( txtComplete->GetTextLength() + 30.0f ); + } + } + + if ( txtTitle != NULL ) { + txtTitle->SetText( objCompleteTitle ); + txtTitle->SetStrokeInfo( true, 0.9f, 2.0f ); + } + + } else { + + if ( !objective ) { + return; + } + + objective->SetVisible( true ); + objective->PlayFrame( "rollOn" ); + + idSWFTextInstance * txtNew = objective->GetScriptObject()->GetNestedText( "info", "txtComplete" ); + idSWFTextInstance * txtTitle = objective->GetScriptObject()->GetNestedText( "info", "txtTitle" ); + idSWFTextInstance * txtDesc = objective->GetScriptObject()->GetNestedText( "info", "txtDesc" ); + idSWFSpriteInstance * img = objective->GetScriptObject()->GetNestedSprite( "info", "img" ); + idSWFSpriteInstance * rightArrow = objective->GetScriptObject()->GetNestedSprite( "info", "right_arrows" ); + + if ( txtNew != NULL ) { + txtNew->SetStrokeInfo( true, 0.9f, 2.0f ); + + if ( rightArrow != NULL ) { + rightArrow->SetXPos( txtNew->GetTextLength() + 55.0f ); + } + } + + if ( txtTitle != NULL ) { + txtTitle->SetText( objTitle ); + txtTitle->SetStrokeInfo( true, 0.9f, 2.0f ); + } + + if ( txtDesc ) { + txtDesc->SetText( objDesc ); + } + + if ( img != NULL ) { + img->SetMaterial( objScreenshot ); + } + + } + +} + +/* +======================== +idMenuScreen_HUD::HideObjective +======================== +*/ +void idMenuScreen_HUD::HideObjective( bool complete ) { + + if ( complete ) { + + if ( !objectiveComplete ) { + return; + } + + objectiveComplete->PlayFrame( "rollOff" ); + + } else { + + if ( !objective ) { + return; + } + + objective->PlayFrame( "rollOff" ); + + } + +} + + +//****************************************************************************************** +// MULTIPLAYER FUNCITONS +//****************************************************************************************** + +/* +======================== +idMenuScreen_HUD::ToggleMPInfo +======================== +*/ +void idMenuScreen_HUD::ToggleMPInfo( bool show, bool showTeams, bool isCTF ) { + + if ( !mpInfo ) { + return; + } + + if ( show ) { + + mpInfo->SetVisible( true ); + + idSWFSpriteInstance * redTeam = mpInfo->GetScriptObject()->GetNestedSprite( "redTeam" ); + idSWFSpriteInstance * blueTeam = mpInfo->GetScriptObject()->GetNestedSprite( "blueTeam" ); + idSWFSpriteInstance * redFlag = mpInfo->GetScriptObject()->GetNestedSprite( "redFlag" ); + idSWFSpriteInstance * blueFlag = mpInfo->GetScriptObject()->GetNestedSprite( "blueFlag" ); + + if ( redFlag ) { + redFlag->SetVisible( isCTF ); + } + + if ( blueFlag ) { + blueFlag->SetVisible( isCTF ); + } + + if ( !showTeams ) { + if ( redTeam ) { + redTeam->SetVisible( false ); + } + + if ( blueTeam ) { + blueTeam->SetVisible( false ); + } + } else { + if ( redTeam ) { + redTeam->SetVisible( true ); + } + + if ( blueTeam ) { + blueTeam->SetVisible( true ); + } + } + + } else { + mpInfo->SetVisible( false ); + } + +} + +/* +======================== +idMenuScreen_HUD::SetFlagState +======================== +*/ +void idMenuScreen_HUD::SetFlagState( int team, int state ) { + + if ( !mpInfo ) { + return; + } + + + idSWFSpriteInstance * flag = NULL; + if ( team == 0 ) { + flag = mpInfo->GetScriptObject()->GetNestedSprite( "redFlag" ); + } else if ( team == 1 ) { + flag = mpInfo->GetScriptObject()->GetNestedSprite( "blueFlag" ); + } + + if ( flag ) { + if ( state == 3 ) { //FLAGSTATUS_NONE + flag->StopFrame( 1 ); + } else { + flag->SetVisible( true ); + flag->StopFrame( state + 2 ); + } + } + +} + +/* +======================== +idMenuScreen_HUD::SetTeamScore +======================== +*/ +void idMenuScreen_HUD::SetTeamScore( int team, int score ) { + + if ( !mpInfo ) { + return; + } + + idSWFTextInstance * txtScore = NULL; + + if ( team == 0 ) { + txtScore = mpInfo->GetScriptObject()->GetNestedText( "redTeam", "txtRedScore" ); + } else if ( team == 1 ) { + txtScore = mpInfo->GetScriptObject()->GetNestedText( "blueTeam", "txtBlueScore" ); + } + + if ( txtScore ) { + txtScore->SetText( va( "%i", score ) ); + txtScore->SetStrokeInfo( true, 0.75f, 1.5f ); + } + +} + +/* +======================== +idMenuScreen_HUD::SetTeam +======================== +*/ +void idMenuScreen_HUD::SetTeam( int team ) { + + if ( !mpInfo ) { + return; + } + + idSWFSpriteInstance * teamBacking = mpInfo->GetScriptObject()->GetNestedSprite( "teamBack" ); + + if ( teamBacking ) { + if ( team < 0 ) { + teamBacking->StopFrame( 3 ); + } else { + teamBacking->StopFrame( team + 1 ); + } + } + +} + +/* +======================== +idMenuScreen_HUD::TriggerHitTarget +======================== +*/ +void idMenuScreen_HUD::TriggerHitTarget( bool show, const idStr & target, int color ) { + + if ( !mpHitInfo ) { + return; + } + + if ( show ) { + + mpHitInfo->SetVisible( true ); + mpHitInfo->PlayFrame( "rollOn" ); + + if ( menuGUI ) { + menuGUI->SetGlobal( "hitTargetName", target.c_str() ); + } + + idSWFSpriteInstance * backing = mpHitInfo->GetScriptObject()->GetNestedSprite( "bgColor" ); + if ( backing ) { + if ( color <= 0 || !gameLocal.mpGame.IsGametypeTeamBased() ) { + color = 1; + } + backing->StopFrame( color ); + } + + } else { + mpHitInfo->PlayFrame( "rollOff" ); + } + +} + +/* +======================== +idMenuScreen_HUD::ToggleLagged +======================== +*/ +void idMenuScreen_HUD::ToggleLagged( bool show ) { + + if ( !mpConnection ) { + return; + } + + mpConnection->SetVisible( show ); +} + +/* +======================== +idMenuScreen_HUD::UpdateGameTime +======================== +*/ +void idMenuScreen_HUD::UpdateGameTime( const char * time ) { + + if ( !mpTime ) { + return; + } + + UpdateMessage( false, "" ); + + mpTime->SetText( time ); + mpTime->SetStrokeInfo( true, 0.75f, 1.5f ); + +} + +/* +======================== +idMenuScreen_HUD::UpdateMessage +======================== +*/ +void idMenuScreen_HUD::UpdateMessage( bool show, const idStr & message ) { + + if ( !mpMessage ) { + return; + } + + if ( show ) { + if ( mpTime ) { + mpTime->SetText( "" ); + } + + mpMessage->SetText( message ); + mpMessage->SetStrokeInfo( true, 0.75f, 1.5f ); + } else { + mpMessage->SetText( "" ); + } + +} + +/* +======================== +idMenuScreen_HUD::ShowNewItem +======================== +*/ +void idMenuScreen_HUD::ShowNewItem( const char * name, const char * icon ) { + + if ( !newItem ) { + return; + } + + newItem->SetVisible( true ); + newItem->PlayFrame( "rollOn" ); + + idSWFSpriteInstance * topImg = newItem->GetScriptObject()->GetNestedSprite( "info", "topImg" ); + idSWFSpriteInstance * botImg = newItem->GetScriptObject()->GetNestedSprite( "info", "botImg" ); + idSWFTextInstance * heading = newItem->GetScriptObject()->GetNestedText( "info", "txtTitle" ); + idSWFTextInstance * itemName = newItem->GetScriptObject()->GetNestedText( "info", "txtItem" ); + + const idMaterial * mat = declManager->FindMaterial( icon, false ); + if ( topImg != NULL && botImg != NULL && mat != NULL ) { + topImg->SetMaterial( mat ); + botImg->SetMaterial( mat ); + } + + if ( heading != NULL ) { + heading->SetText( "#str_02027" ); + heading->SetStrokeInfo( true, 0.75f, 1.5f ); + } + + if ( itemName != NULL ) { + itemName->SetText( name ); + itemName->SetStrokeInfo( true, 0.75f, 1.5f ); + } + +} + +/* +======================== +idMenuScreen_HUD::UpdateFlashlight +======================== +*/ +void idMenuScreen_HUD::UpdateFlashlight( idPlayer * player ) { + + if ( !player || !flashlight ) { + return; + } + + if ( player->flashlightBattery != flashlight_batteryDrainTimeMS.GetInteger() ) { + flashlight->StopFrame( 2 ); + flashlight->SetVisible( true ); + idSWFSpriteInstance * batteryLife = flashlight->GetScriptObject()->GetNestedSprite( "info" ); + if ( batteryLife ) { + float power = ( (float)player->flashlightBattery / (float)flashlight_batteryDrainTimeMS.GetInteger() ) * 100.0f; + batteryLife->StopFrame( power ); + } + } else { + flashlight->StopFrame( 1 ); + } + +} + +/* +======================== +idMenuScreen_HUD::UpdateChattingHud +======================== +*/ +void idMenuScreen_HUD::UpdateChattingHud( idPlayer * player ) { + + if ( !mpChatObject || !GetSWFObject() ) { + return; + } + + idSWF * gui = GetSWFObject(); + + if ( player->isChatting == 0 ) { + if ( mpChatObject->GetCurrentFrame() != 1 ) { + mpChatObject->StopFrame( 1 ); + gui->ForceInhibitControl( false ); + gui->SetGlobal( "focusWindow", NULL ); + } + } else { + if ( !mpChatObject->IsVisible() ) { + mpChatObject->SetVisible( true ); + mpChatObject->PlayFrame( "rollOn" ); + gui->ForceInhibitControl( true ); + + idSWFTextInstance * txtType = mpChatObject->GetScriptObject()->GetNestedText( "info", "saybox" ); + int length = 0; + if ( txtType ) { + if ( player->isChatting == 1 ) { + txtType->SetText( "#str_swf_talk_all" ); + } else if ( player->isChatting == 2 ) { + txtType->SetText( "#str_swf_talk_team" ); + } + txtType->SetStrokeInfo( true ); + length = txtType->GetTextLength(); + } + + idSWFSpriteInstance * sayBox = mpChatObject->GetScriptObject()->GetNestedSprite( "info", "textEntry" ); + if ( sayBox ) { + sayBox->SetXPos( length + 10 ); + } + + idSWFTextInstance * say = mpChatObject->GetScriptObject()->GetNestedText( "info", "textEntry", "txtVal" ); + if ( say != NULL ) { + say->SetIgnoreColor( false ); + say->SetText( "" ); + say->SetStrokeInfo( true ); + say->renderMode = SWF_TEXT_RENDER_AUTOSCROLL; + } + + idSWFScriptObject * const sayObj = mpChatObject->GetScriptObject()->GetNestedObj( "info", "textEntry", "txtVal" ); + if ( sayObj != NULL ) { + + gui->SetGlobal( "focusWindow", sayObj ); + + class idPostTextChat : public idSWFScriptFunction_RefCounted { + public: + idPostTextChat( idPlayer * _player, idSWFTextInstance * _text ) { + player = _player; + text = _text; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( !player || !text ) { + return idSWFScriptVar(); + } + + idStr val = text->text; + val.Replace( "\'", "" ); + val.Replace( "\"", "" ); + idStr command; + if ( player->isChatting == 2 ) { + command = va( "sayTeam %s\n", val.c_str() ); + } else { + command = va( "say %s\n", val.c_str() ); + } + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, command.c_str() ); + + player->isChatting = 0; + return idSWFScriptVar(); + } + idPlayer * player; + idSWFTextInstance * text; + }; + + class idCancelTextChat : public idSWFScriptFunction_RefCounted { + public: + idCancelTextChat( idPlayer * _player ) { + player = _player; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( !player ) { + return idSWFScriptVar(); + } + + player->isChatting = 0; + return idSWFScriptVar(); + } + idPlayer * player; + }; + + sayObj->Set( "onPress", new ( TAG_SWF ) idPostTextChat( player, say ) ); + + idSWFScriptObject * const shortcutKeys = gui->GetGlobal( "shortcutKeys" ).GetObject(); + if ( verify( shortcutKeys != NULL ) ) { + shortcutKeys->Set( "ENTER", sayObj ); + shortcutKeys->Set( "ESCAPE", new ( TAG_SWF ) idCancelTextChat( player ) ); + } + } + } + } +} diff --git a/neo/d3xp/menus/MenuScreen_PDA_Inventory.cpp b/neo/d3xp/menus/MenuScreen_PDA_Inventory.cpp new file mode 100644 index 00000000..d8dcd5b1 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_PDA_Inventory.cpp @@ -0,0 +1,384 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int NUM_INVENTORY_ITEMS_VISIBLE = 9; + +/* +======================== +idMenuScreen_PDA_Inventory::Initialize +======================== +*/ +void idMenuScreen_PDA_Inventory::Initialize( idMenuHandler * data ) { + + AddEventAction( WIDGET_EVENT_TAB_NEXT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_NEXT, WIDGET_EVENT_TAB_NEXT ) ); + AddEventAction( WIDGET_EVENT_TAB_PREV ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_PREV, WIDGET_EVENT_TAB_PREV ) ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + SetSpritePath( "menuItems" ); + + if ( menuGUI != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + BindSprite( root ); + } + + infoBox.SetSpritePath( GetSpritePath(), "info", "details" ); + infoBox.Initialize( data ); + infoBox.SetNoAutoFree( true ); + + itemList.SetSpritePath( GetSpritePath(), "info", "options" ); + itemList.SetNumVisibleOptions( NUM_INVENTORY_ITEMS_VISIBLE ); + itemList.SetNoAutoFree( true ); + while ( itemList.GetChildren().Num() < NUM_INVENTORY_ITEMS_VISIBLE ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_SELECT_PDA_ITEM, itemList.GetChildren().Num() ); + itemList.AddChild( buttonWidget ); + } + itemList.Initialize( data ); + + AddChild( &itemList ); + AddChild( &infoBox ); + //AddChild( assignment ); + + AddEventAction( WIDGET_EVENT_SCROLL_LEFT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER, WIDGET_EVENT_SCROLL_LEFT ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER, WIDGET_EVENT_SCROLL_RIGHT ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_LEFT_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER, WIDGET_EVENT_SCROLL_LEFT_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE ) ); + + + idMenuScreen::Initialize( data ); +} + +/* +======================== +idMenuScreen_PDA_Inventory::ShowScreen +======================== +*/ +void idMenuScreen_PDA_Inventory::ShowScreen( const mainMenuTransition_t transitionType ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + + int numItems = player->GetInventory().items.Num(); + for ( int j = 0; j < numItems; j++ ) { + idDict *item = player->GetInventory().items[j]; + if ( !item->GetBool( "inv_pda" ) ) { + const char *iname = item->GetString( "inv_name" ); + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + iname = iname; + iicon = iicon; + itext = itext; + const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL ); + if ( kv ) { + //objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + } + } + + idList weaponIcons; + for ( int j = 0; j < MAX_WEAPONS; j++ ) { + + const char * weap = GetWeaponName( j ); + if ( weap == NULL || *weap == NULL ){ + continue; + } + + if ( !IsVisibleWeapon( j ) ) { + continue; + } + + const idDeclEntityDef * weaponDef = gameLocal.FindEntityDef( weap, false ); + if ( weaponDef != NULL ) { + weaponIcons.Append( declManager->FindMaterial( weaponDef->dict.GetString( "hudIcon" ), false ) ); + } + } + + itemList.SetListImages( weaponIcons ); + itemList.SetViewIndex( 0 ); + itemList.SetMoveToIndex( 0 ); + itemList.SetMoveDiff( 0 ); + + } + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_Inventory::HideScreen +======================== +*/ +void idMenuScreen_PDA_Inventory::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_Inventory::GetWeaponName +======================== +*/ +const char * idMenuScreen_PDA_Inventory::GetWeaponName( int index ) { + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return NULL; + } + + const char * weaponDefName = va( "def_weapon%d", index ); + if ( player->GetInventory().weapons & ( 1 << index ) ) { + return player->spawnArgs.GetString( weaponDefName ); + } + + return NULL; +} + +/* +======================== +idMenuScreen_PDA_Inventory::GetWeaponName +======================== +*/ +bool idMenuScreen_PDA_Inventory::IsVisibleWeapon( int index ) { + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return false; + } + + if ( player->GetInventory().weapons & ( 1 << index ) ) { + return player->spawnArgs.GetBool( va( "weapon%d_visible", index ) ); + } + + return false; + +} + +/* +======================== +idMenuScreen_PDA_Inventory::Update +======================== +*/ +void idMenuScreen_PDA_Inventory::Update() { + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + idMenuScreen::Update(); + return; + } + + int validIndex = 0; + for ( int j = 0; j < MAX_WEAPONS; j++ ) { + + const char * weap = GetWeaponName( j ); + if ( weap == NULL || *weap == NULL ){ + continue; + } + + if ( !IsVisibleWeapon( j ) ) { + return; + } + + const idDeclEntityDef * weaponDef = gameLocal.FindEntityDef( weap, false ); + if ( weaponDef == NULL ) { + continue; + } + + if ( validIndex == itemList.GetMoveToIndex() ) { + idStr itemName = weaponDef->dict.GetString( "display_name" ); + idStr itemDesc = weaponDef->dict.GetString( "inv_desc" ); + infoBox.SetHeading( idLocalization::GetString( itemName.c_str() ) ); + infoBox.SetBody( idLocalization::GetString( itemDesc.c_str() ) ); + break; + } + validIndex++; + } + + if ( GetSprite() != NULL ) { + idSWFSpriteInstance * dpad = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "dpad" ); + if ( dpad != NULL ) { + dpad->SetVisible( false ); + } + } + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * const >( menuData->GetChildFromIndex( PDA_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_01345"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + buttonInfo->label = "#str_SWF_EQUIP"; + buttonInfo->action.Set( WIDGET_ACTION_JOY3_ON_PRESS ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_TAB ); + buttonInfo->label = ""; + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + } + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_PDA_Inventory::EquipWeapon +======================== +*/ +void idMenuScreen_PDA_Inventory::EquipWeapon() { + + if ( itemList.GetViewIndex() != itemList.GetMoveToIndex() ) { + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + int validIndex = 0; + for ( int j = 0; j < MAX_WEAPONS; j++ ) { + + const char * weap = GetWeaponName( j ); + if ( weap == NULL || *weap == NULL ){ + continue; + } + + if ( !IsVisibleWeapon( j ) ) { + continue; + } + + if ( validIndex == itemList.GetMoveToIndex() ) { + int slot = player->SlotForWeapon( weap ); + player->SetPreviousWeapon( slot ); + break; + } + validIndex++; + } + + player->TogglePDA(); + +} + +/* +======================== +idMenuScreen_PDA_Inventory::HandleAction +======================== +*/ +bool idMenuScreen_PDA_Inventory::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != PDA_AREA_INVENTORY ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_JOY3_ON_PRESS: { + EquipWeapon(); + return true; + } + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( PDA_AREA_INVALID, MENU_TRANSITION_ADVANCE ); + return true; + } + case WIDGET_ACTION_START_REPEATER: { + idWidgetAction repeatAction; + widgetAction_t repeatActionType = static_cast< widgetAction_t >( parms[ 0 ].ToInteger() ); + assert( parms.Num() == 2 ); + repeatAction.Set( repeatActionType, parms[ 1 ] ); + menuData->StartWidgetActionRepeater( widget, repeatAction, event ); + return true; + } + case WIDGET_ACTION_SELECT_PDA_ITEM: { + + if ( itemList.GetMoveDiff() > 0 ) { + itemList.MoveToIndex( itemList.GetMoveToIndex(), true ); + } + + int index = parms[0].ToInteger(); + if ( index != 0 ) { + itemList.MoveToIndex( index ); + Update(); + } + + return true; + } + case WIDGET_ACTION_STOP_REPEATER: { + menuData->ClearWidgetActionRepeater(); + return true; + } + case WIDGET_ACTION_SCROLL_HORIZONTAL: { + + if ( itemList.GetTotalNumberOfOptions() <= 1 ) { + return true; + } + + if ( itemList.GetMoveDiff() > 0 ) { + itemList.MoveToIndex( itemList.GetMoveToIndex(), true ); + } + + int direction = parms[0].ToInteger(); + if ( direction == 1 ) { + if ( itemList.GetViewIndex() == itemList.GetTotalNumberOfOptions() - 1 ) { + return true; + } else { + itemList.MoveToIndex( 1 ); + } + } else { + if ( itemList.GetViewIndex() == 0 ) { + return true; + } else { + itemList.MoveToIndex( ( itemList.GetNumVisibleOptions() / 2 ) + 1 ); + } + } + Update(); + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_PDA_UserData.cpp b/neo/d3xp/menus/MenuScreen_PDA_UserData.cpp new file mode 100644 index 00000000..f4730bc4 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_PDA_UserData.cpp @@ -0,0 +1,247 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + + + +/* +======================== +idMenuScreen_PDA_UserData::Initialize +======================== +*/ +void idMenuScreen_PDA_UserData::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + SetSpritePath( "menuData" ); + + pdaUserData.SetSpritePath( GetSpritePath(), "info", "pdaData" ); + pdaUserData.Initialize( data ); + pdaUserData.SetNoAutoFree( true ); + + AddChild( &pdaUserData ); + + pdaObjectiveSimple.SetSpritePath( GetSpritePath(), "info", "missionInfo" ); + pdaObjectiveSimple.Initialize( data ); + pdaObjectiveSimple.SetNoAutoFree( true ); + + AddChild( &pdaObjectiveSimple ); + + pdaAudioFiles.SetSpritePath( GetSpritePath(), "info", "audioFiles" ); + pdaAudioFiles.Initialize( data ); + pdaAudioFiles.SetNoAutoFree( true ); + + AddChild( &pdaAudioFiles ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaAudioFiles.GetChildByIndex( 0 ), WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaAudioFiles.GetChildByIndex( 0 ), WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaAudioFiles.GetChildByIndex( 0 ), WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaAudioFiles.GetChildByIndex( 0 ), WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ) ); + + AddEventAction( WIDGET_EVENT_TAB_NEXT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_NEXT, WIDGET_EVENT_TAB_NEXT ) ); + AddEventAction( WIDGET_EVENT_TAB_PREV ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_PREV, WIDGET_EVENT_TAB_PREV ) ); + +} + +/* +======================== +idMenuScreen_PDA_UserData::Update +======================== +*/ +void idMenuScreen_PDA_UserData::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * const >( menuData->GetChildFromIndex( PDA_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_01345"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_TAB ); + buttonInfo->label = ""; + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + idPlayer * player = gameLocal.GetLocalPlayer(); + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL && player != NULL ) { + int pdaIndex = pdaList->GetViewIndex(); + if ( pdaIndex < player->GetInventory().pdas.Num() ) { + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + if ( pda != NULL && pdaIndex != 0 ) { + if ( player->IsSoundChannelPlaying( SND_CHANNEL_PDA_AUDIO ) ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_stop"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } else if ( pda->GetNumAudios() > 0 ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_play"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + } + } + + } + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_PDA_UserData::ShowScreen +======================== +*/ +void idMenuScreen_PDA_UserData::ShowScreen( const mainMenuTransition_t transitionType ) { + + if ( menuGUI != NULL && menuData != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + idSWFSpriteInstance * pdaSprite = root.GetNestedSprite( "pda_persons" ); + if ( pdaSprite != NULL && menuData != NULL && menuData->ActiveScreen() != PDA_AREA_USER_EMAIL ) { + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOn" ); + } + + idSWFSpriteInstance * navBar = root.GetNestedSprite( "navBar" ); + if ( navBar != NULL && menuData != NULL && menuData->ActiveScreen() == PDA_AREA_INVALID ) { + navBar->PlayFrame( "rollOn" ); + } + } + + idMenuScreen::ShowScreen( transitionType ); + + if ( menuData != NULL) { + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + pdaList->SetFocusIndex( pdaList->GetFocusIndex() ); + } + } +} + +/* +======================== +idMenuScreen_PDA_UserData::HideScreen +======================== +*/ +void idMenuScreen_PDA_UserData::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( menuGUI != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + idSWFSpriteInstance * pdaSprite = root.GetNestedSprite( "pda_persons" ); + if ( pdaSprite != NULL && menuData != NULL && menuData->NextScreen() != PDA_AREA_USER_EMAIL ) { + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOff" ); + } + } + + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_UserData::HandleAction +======================== +*/ +bool idMenuScreen_PDA_UserData::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != PDA_AREA_USER_DATA ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_PRESS_FOCUSED: { + + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList == NULL ) { + return true; + } + + int pdaIndex = pdaList->GetViewIndex(); + if ( pdaIndex == 0 ) { + return true; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL && player->IsSoundChannelPlaying( SND_CHANNEL_PDA_AUDIO ) ) { + player->EndAudioLog(); + } else { + if ( menuData != NULL && pdaAudioFiles.GetChildren().Num() > 0 ) { + int index = pdaAudioFiles.GetChildByIndex( 0 ).GetFocusIndex(); + idMenuHandler_PDA * pdaHandler = dynamic_cast< idMenuHandler_PDA * const >( menuData ); + if ( pdaHandler != NULL ) { + pdaHandler->PlayPDAAudioLog( pdaIndex, index ); + } + } + } + return true; + } + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( PDA_AREA_INVALID, MENU_TRANSITION_ADVANCE ); + return true; + } + case WIDGET_ACTION_START_REPEATER: { + idWidgetAction repeatAction; + widgetAction_t repeatActionType = static_cast< widgetAction_t >( parms[ 0 ].ToInteger() ); + assert( parms.Num() == 2 ); + repeatAction.Set( repeatActionType, parms[ 1 ] ); + if ( menuData != NULL ) { + menuData->StartWidgetActionRepeater( widget, repeatAction, event ); + } + return true; + } + case WIDGET_ACTION_STOP_REPEATER: { + if ( menuData != NULL ) { + menuData->ClearWidgetActionRepeater(); + } + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} diff --git a/neo/d3xp/menus/MenuScreen_PDA_UserEmails.cpp b/neo/d3xp/menus/MenuScreen_PDA_UserEmails.cpp new file mode 100644 index 00000000..e22bb18f --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_PDA_UserEmails.cpp @@ -0,0 +1,478 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuScreen_PDA_UserEmails::Initialize +======================== +*/ +void idMenuScreen_PDA_UserEmails::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + SetSpritePath( "menuEmail" ); + + pdaInbox.SetSpritePath( GetSpritePath(), "info", "inbox" ); + pdaInbox.Initialize( data ); + pdaInbox.SetNoAutoFree( true ); + + emailScrollbar.SetSpritePath( GetSpritePath(), "info", "email", "info", "scrollbar" ); + emailScrollbar.Initialize( data ); + emailScrollbar.SetNoAutoFree( true ); + + emailInfo.SetSpritePath( GetSpritePath(), "info", "email" ); + emailInfo.Initialize( data ); + emailInfo.SetScrollbar( &emailScrollbar ); + emailInfo.AddChild( &emailScrollbar ); + emailInfo.RegisterEventObserver( this ); + emailInfo.AddEventAction( WIDGET_EVENT_ROLL_OVER ).Set( WIDGET_ACTION_EMAIL_HOVER, 1 ); + emailInfo.AddEventAction( WIDGET_EVENT_ROLL_OUT ).Set( WIDGET_ACTION_EMAIL_HOVER, 0 ); + emailInfo.SetNoAutoFree( true ); + + AddChild( &pdaInbox ); + AddChild( &emailInfo ); + + if ( pdaInbox.GetEmailList() != NULL ) { + pdaInbox.GetEmailList()->RegisterEventObserver( &emailInfo ); + pdaInbox.GetEmailList()->RegisterEventObserver( &emailScrollbar ); + + for ( int i = 0; i < pdaInbox.GetEmailList()->GetChildren().Num(); ++i ) { + idMenuWidget & child = pdaInbox.GetEmailList()->GetChildByIndex( i ); + idMenuWidget_Button * const button = dynamic_cast< idMenuWidget_Button * >( &child ); + if ( button != NULL ) { + button->RegisterEventObserver( &emailInfo ); + } + } + + } + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ) ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + + AddEventAction( WIDGET_EVENT_TAB_NEXT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_NEXT, WIDGET_EVENT_TAB_NEXT ) ); + AddEventAction( WIDGET_EVENT_TAB_PREV ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_PREV, WIDGET_EVENT_TAB_PREV ) ); + + class idInfoBoxRefresh : public idSWFScriptFunction_RefCounted { + public: + idInfoBoxRefresh( idMenuWidget_InfoBox * _widget ) : + widget( _widget ) { + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + if ( widget == NULL ) { + return idSWFScriptVar(); + } + + widget->Update(); + return idSWFScriptVar(); + } + private: + idMenuWidget_InfoBox * widget; + }; + + if ( GetSWFObject() != NULL ) { + GetSWFObject()->SetGlobal( "refreshInfoBox", new (TAG_SWF) idInfoBoxRefresh( &emailInfo ) ); + } +} + +/* +======================== +idMenuScreen_PDA_UserEmails::ShowScreen +======================== +*/ +void idMenuScreen_PDA_UserEmails::ShowScreen( const mainMenuTransition_t transitionType ) { + + if ( menuGUI != NULL ) { + readingEmails = false; + + idSWFScriptObject & root = menuGUI->GetRootObject(); + idSWFSpriteInstance * pdaSprite = root.GetNestedSprite( "pda_persons" ); + if ( pdaSprite != NULL && menuData != NULL && menuData->ActiveScreen() != PDA_AREA_USER_DATA ) { + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOn" ); + } + + menuGUI->SetGlobal( "emailRollback", false ); + if ( pdaInbox.BindSprite( root ) && pdaInbox.GetSprite() ) { + pdaInbox.GetSprite()->StopFrame( 1 ); + } + if ( emailInfo.BindSprite( root ) && emailInfo.GetSprite() ) { + emailInfo.GetSprite()->StopFrame( 1 ); + } + } + + scrollEmailInfo = false; + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_UserEmails::Update +======================== +*/ +void idMenuScreen_PDA_UserEmails::Update() { + + if ( menuData != NULL ) { + + if ( menuData->NextScreen() != PDA_AREA_USER_EMAIL ) { + return; + } + + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * const >( menuData->GetChildFromIndex( PDA_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + if ( readingEmails ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + } else { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_01345"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + int pdaIndex = pdaList->GetViewIndex(); + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + if ( pdaIndex < player->GetInventory().pdas.Num() ) { + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + if ( pda != NULL && pdaInbox.GetEmailList() != NULL ) { + idStr pdaFullName = pda->GetFullName(); + int emailIndex = pdaInbox.GetEmailList()->GetViewIndex(); + if ( emailIndex < pda->GetNumEmails() ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_01102"; + } + buttonInfo->action.Set( WIDGET_ACTION_PDA_SELECT_EMAIL ); + } + } + } + } + } + } + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_TAB ); + buttonInfo->label = ""; + buttonInfo->action.Set( WIDGET_ACTION_PDA_CLOSE ); + } + } + + UpdateEmail(); + idMenuScreen::Update(); + +} + +/* +======================== +idMenuScreen_PDA_UserEmails::HideScreen +======================== +*/ +void idMenuScreen_PDA_UserEmails::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( menuGUI != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + idSWFSpriteInstance * pdaSprite = root.GetNestedSprite( "pda_persons" ); + if ( pdaSprite != NULL && menuData != NULL ) { + if ( menuData->NextScreen() != PDA_AREA_USER_DATA ) { + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOff" ); + readingEmails = false; + } else { + if ( readingEmails ) { + readingEmails = false; + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOn" ); + } + } + } + } + + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_UserEmails::UpdateEmail +======================== +*/ +void idMenuScreen_PDA_UserEmails::UpdateEmail() { + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + + int pdaIndex = pdaList->GetViewIndex(); + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + if ( pdaIndex > player->GetInventory().pdas.Num() ) { + return; + } + + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + if ( pda != NULL && pdaInbox.GetEmailList() != NULL ) { + idStr pdaFullName = pda->GetFullName(); + int emailIndex = pdaInbox.GetEmailList()->GetViewIndex(); + if ( emailIndex < pda->GetNumEmails() ) { + const idDeclEmail * email = pda->GetEmailByIndex( emailIndex ); + if ( email != NULL ) { + emailInfo.SetHeading( email->GetSubject() ); + emailInfo.SetBody( email->GetBody() ); + emailInfo.Update(); + } + } + } + } +} + +/* +======================== +idMenuScreen_PDA_UserEmails::HandleAction +======================== +*/ +bool idMenuScreen_PDA_UserEmails::ScrollCorrectList( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget ) { + + bool handled = false; + bool leftScroll = false; + if ( event.type == WIDGET_EVENT_SCROLL_UP_LSTICK || event.type == WIDGET_EVENT_SCROLL_DOWN_LSTICK || + event.type == WIDGET_EVENT_SCROLL_UP || event.type == WIDGET_EVENT_SCROLL_DOWN ) { + leftScroll = true; + } + + if ( readingEmails ) { + if ( leftScroll && !scrollEmailInfo ) { + idMenuWidget_DynamicList * inbox = pdaInbox.GetEmailList(); + if ( inbox != NULL ){ + inbox->HandleAction( action, event, inbox ); + UpdateEmail(); + handled = true; + } + } else { + emailInfo.HandleAction( action, event, &emailInfo ); + handled = true; + } + } else if ( !leftScroll ) { + idMenuWidget_DynamicList * inbox = pdaInbox.GetEmailList(); + if ( inbox != NULL ){ + inbox->HandleAction( action, event, inbox ); + UpdateEmail(); + handled = true; + } + } else if ( menuData != NULL ) { + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * const >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + pdaList->HandleAction( action, event, pdaList ); + handled = true; + } + } + + return handled; +} + +/* +======================== +idMenuScreen_PDA_UserEmails::HandleAction +======================== +*/ +void idMenuScreen_PDA_UserEmails::ShowEmail( bool show ) { + + idSWFSpriteInstance * pdaSprite = NULL; + + if ( menuGUI != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + pdaSprite = root.GetNestedSprite( "pda_persons" ); + + if ( show && !readingEmails ) { + + scrollEmailInfo = false; + + if ( pdaSprite != NULL ) { + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOff" ); + } + + if ( emailInfo.BindSprite( root ) && emailInfo.GetSprite() != NULL ) { + emailInfo.GetSprite()->PlayFrame( "rollOn" ); + emailInfo.Update(); + } + + if ( pdaInbox.BindSprite( root ) && pdaInbox.GetSprite() != NULL ) { + pdaInbox.GetSprite()->PlayFrame( "rollOff" ); + } + } else if ( !show && readingEmails ) { + + if ( emailInfo.BindSprite( root ) && emailInfo.GetSprite() != NULL ) { + emailInfo.GetSprite()->PlayFrame( "rollOff" ); + } + + if ( pdaInbox.BindSprite( root ) && pdaInbox.GetSprite() != NULL ) { + pdaInbox.GetSprite()->PlayFrame( "rollOn" ); + } + + if ( pdaSprite != NULL ) { + pdaSprite->SetVisible( true ); + pdaSprite->PlayFrame( "rollOn" ); + + if ( menuData != NULL ) { + idMenuWidget_DynamicList * pdaList = dynamic_cast< idMenuWidget_DynamicList * const >( menuData->GetChildFromIndex( PDA_WIDGET_PDA_LIST ) ); + if ( pdaList != NULL ) { + pdaList->SetFocusIndex( pdaList->GetFocusIndex() ); + } + } + } + } + } + + + readingEmails = show; + Update(); +} + +/* +======================== +idMenuScreen_PDA_UserEmails::HandleAction +======================== +*/ +bool idMenuScreen_PDA_UserEmails::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != PDA_AREA_USER_EMAIL ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_PDA_CLOSE: { + menuData->SetNextScreen( PDA_AREA_INVALID, MENU_TRANSITION_ADVANCE ); + return true; + } + case WIDGET_ACTION_GO_BACK: { + if ( readingEmails ) { + ShowEmail( false ); + } else { + menuData->SetNextScreen( PDA_AREA_INVALID, MENU_TRANSITION_ADVANCE ); + } + return true; + } + case WIDGET_ACTION_REFRESH: { + UpdateEmail(); + return true; + } + case WIDGET_ACTION_PDA_SELECT_EMAIL: { + + if ( widget->GetParent() != NULL ) { + idMenuWidget_DynamicList * emailList = dynamic_cast< idMenuWidget_DynamicList * >( widget->GetParent() ); + int index = parms[0].ToInteger(); + if ( emailList != NULL ) { + emailList->SetViewIndex( emailList->GetViewOffset() + index ); + emailList->SetFocusIndex( index ); + } + } + + ShowEmail( true ); + + return true; + } + case WIDGET_ACTION_EMAIL_HOVER: { + scrollEmailInfo = parms[0].ToBool(); + return true; + } + case WIDGET_ACTION_SCROLL_VERTICAL: { + if ( ScrollCorrectList( action, event, widget ) ) { + return true; + } + UpdateEmail(); + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuScreen_PDA_UserEmails::ObserveEvent +======================== +*/ +void idMenuScreen_PDA_UserEmails::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + + if ( menuData != NULL && menuData->ActiveScreen() != PDA_AREA_USER_EMAIL ) { + return; + } + + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + Update(); + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_PDA_VideoDisks.cpp b/neo/d3xp/menus/MenuScreen_PDA_VideoDisks.cpp new file mode 100644 index 00000000..4a46b4db --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_PDA_VideoDisks.cpp @@ -0,0 +1,322 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int MAX_VIDEO_ITEMS = 5; + +/* +======================== +idMenuScreen_PDA_VideoDisks::Initialize +======================== +*/ +void idMenuScreen_PDA_VideoDisks::Initialize( idMenuHandler * data ) { + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + SetSpritePath( "menuVideo" ); + + + videoDetails.SetSpritePath( GetSpritePath(), "info", "details" ); + videoDetails.Initialize( data ); + videoDetails.SetNoAutoFree( true ); + + scrollbar.SetSpritePath( GetSpritePath(), "info", "videoList", "scrollbar" ); + scrollbar.Initialize( data ); + scrollbar.SetNoAutoFree( true ); + + pdaVideoList.SetSpritePath( GetSpritePath(), "info", "videoList", "options" ); + pdaVideoList.SetNumVisibleOptions( MAX_VIDEO_ITEMS ); + pdaVideoList.SetWrappingAllowed( true ); + pdaVideoList.SetNoAutoFree( true ); + while ( pdaVideoList.GetChildren().Num() < MAX_VIDEO_ITEMS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_SELECT_PDA_VIDEO, pdaVideoList.GetChildren().Num() ); + buttonWidget->RegisterEventObserver( &videoDetails ); + buttonWidget->RegisterEventObserver( &scrollbar ); + buttonWidget->Initialize( data ); + pdaVideoList.AddChild( buttonWidget ); + } + pdaVideoList.Initialize( data ); + pdaVideoList.AddChild( &scrollbar ); + + AddChild( &pdaVideoList ); + AddChild( &videoDetails ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( &pdaVideoList, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + AddEventAction( WIDGET_EVENT_TAB_NEXT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_NEXT, WIDGET_EVENT_TAB_NEXT ) ); + AddEventAction( WIDGET_EVENT_TAB_PREV ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_PREV, WIDGET_EVENT_TAB_PREV ) ); + + idMenuScreen::Initialize( data ); +} + +/* +======================== +idMenuScreen_PDA_VideoDisks::Update +======================== +*/ +void idMenuScreen_PDA_VideoDisks::Update() { + + idPlayer * player = gameLocal.GetLocalPlayer(); + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * const >( menuData->GetChildFromIndex( PDA_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_01345"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_TAB ); + buttonInfo->label = ""; + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + if ( player != NULL && player->GetInventory().videos.Num() > 0 ) { + if ( player->GetVideoMaterial() == NULL ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_play"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } else { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_stop"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + } + } + + if ( player != NULL ) { + //if ( pdaVideoList == NULL ) { + // idMenuScreen::Update(); + // return; + //} + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + int index = pdaVideoList.GetViewIndex(); + const idDeclVideo * video = player->GetVideo( index ); + + if ( video == NULL ) { + idMenuScreen::Update(); + return; + } + + if ( player->GetVideoMaterial() != NULL ) { + // update video material + if ( BindSprite( root ) && GetSprite() != NULL ) { + idSWFSpriteInstance * videoSprite = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "details", "video", "img" ); + const idMaterial * mat = player->GetVideoMaterial(); + + if ( videoSprite != NULL && mat != NULL ) { + videoSprite->SetMaterial( mat ); + } + } + } else { + idSWFSpriteInstance * videoSprite = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "details", "video", "img" ); + if ( videoSprite != NULL ) { + videoSprite->SetMaterial( video->GetPreview() ); + } + } + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_PDA_VideoDisks::ShowScreen +======================== +*/ +void idMenuScreen_PDA_VideoDisks::ShowScreen( const mainMenuTransition_t transitionType ) { + + videoItems.Clear(); + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + int numVideos = player->GetInventory().videos.Num(); + for ( int i = 0; i < numVideos; ++i ) { + const idDeclVideo * video = player->GetVideo( i ); + if( video != NULL ) { + idList< idStr > item; + item.Append( video->GetVideoName() ); + videoItems.Append( item ); + } + } + } + + pdaVideoList.SetListData( videoItems ); + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_VideoDisks::ToggleVideoDiskPlay +======================== +*/ +void idMenuScreen_PDA_VideoDisks::ToggleVideoDiskPlay() { + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + + int index = pdaVideoList.GetViewIndex(); + const idDeclVideo * video = player->GetVideo( index ); + + if ( video == NULL ) { + return; + } + + if ( video == activeVideo ) { + player->EndVideoDisk(); + activeVideo = NULL; + return; + } + + activeVideo = video; + + if ( player->GetVideoMaterial() == NULL ) { + player->PlayVideoDisk( video ); + } else { + player->EndVideoDisk(); + } +} + +/* +======================== +idMenuScreen_PDA_VideoDisks::SelectedVideoToPlay +======================== +*/ +void idMenuScreen_PDA_VideoDisks::SelectedVideoToPlay( int index ) { + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + player->EndVideoDisk(); + if ( menuData != NULL ) { + idMenuHandler_PDA * pdaHandler = dynamic_cast< idMenuHandler_PDA * const >( menuData ); + pdaHandler->ClearVideoPlaying(); + } + + + pdaVideoList.SetViewIndex( pdaVideoList.GetViewOffset() + index ); + pdaVideoList.SetFocusIndex( index ); + const idDeclVideo * video = player->GetVideo( pdaVideoList.GetViewOffset() + index ); + + if ( video == NULL ) { + return; + } + + if ( video == activeVideo ) { + activeVideo = NULL; + return; + } + + activeVideo = video; + + player->PlayVideoDisk( video ); + + +} + +/* +======================== +idMenuScreen_PDA_VideoDisks::HideScreen +======================== +*/ +void idMenuScreen_PDA_VideoDisks::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_PDA_VideoDisks::HandleAction +======================== +*/ +bool idMenuScreen_PDA_VideoDisks::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != PDA_AREA_VIDEO_DISKS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( PDA_AREA_INVALID, MENU_TRANSITION_ADVANCE ); + return true; + } + case WIDGET_ACTION_START_REPEATER: { + idWidgetAction repeatAction; + widgetAction_t repeatActionType = static_cast< widgetAction_t >( parms[ 0 ].ToInteger() ); + assert( parms.Num() == 2 ); + repeatAction.Set( repeatActionType, parms[ 1 ] ); + if ( menuData != NULL ) { + menuData->StartWidgetActionRepeater( widget, repeatAction, event ); + } + return true; + } + case WIDGET_ACTION_STOP_REPEATER: { + if ( menuData != NULL ) { + menuData->ClearWidgetActionRepeater(); + } + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + ToggleVideoDiskPlay(); + Update(); + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Scoreboard.cpp b/neo/d3xp/menus/MenuScreen_Scoreboard.cpp new file mode 100644 index 00000000..3bbb4cb2 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Scoreboard.cpp @@ -0,0 +1,570 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +//*************************************************************** +// DEFAULT SCOREBOARD +//*************************************************************** +static const int MAX_SCOREBOARD_SLOTS = 8; + +/* +======================== +idMenuScreen_Scoreboard::Initialize +======================== +*/ +void idMenuScreen_Scoreboard::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "sbDefault" ); + + playerList = new (TAG_SWF) idMenuWidget_ScoreboardList(); + playerList->SetSpritePath( GetSpritePath(), "info", "playerList" ); + playerList->SetNumVisibleOptions( MAX_SCOREBOARD_SLOTS ); + playerList->SetWrappingAllowed( true ); + while ( playerList->GetChildren().Num() < MAX_SCOREBOARD_SLOTS ) { + idMenuWidget_ScoreboardButton * const buttonWidget = new (TAG_SWF) idMenuWidget_ScoreboardButton(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, playerList->GetChildren().Num() ); + buttonWidget->AddEventAction( WIDGET_EVENT_COMMAND ).Set( WIDGET_ACTION_MUTE_PLAYER, playerList->GetChildren().Num() ); + buttonWidget->Initialize( data ); + playerList->AddChild( buttonWidget ); + } + playerList->Initialize( data ); + AddChild( playerList ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Scoreboard::Update +======================== +*/ +void idMenuScreen_Scoreboard::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = dynamic_cast< idMenuWidget_CommandBar * const >( menuData->GetChildFromIndex( SCOREBOARD_WIDGET_CMD_BAR ) ); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + + if ( gameLocal.mpGame.GetGameState() != idMultiplayerGame::GAMEREVIEW ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_01345"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_TAB ); + buttonInfo->label = ""; + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + } + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_view_profile"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Scoreboard::ShowScreen +======================== +*/ +void idMenuScreen_Scoreboard::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); + + if ( GetSWFObject() ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + + idSWFTextInstance * txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtScoreboard" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02618" ); + txtVal->SetStrokeInfo( true, 0.9f, 2.0f ); + } + + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtGameType" ); + if ( txtVal != NULL ) { + idStr mode = idLocalization::GetString( "#str_02376" ); + mode.Append( ": " ); + const idStrList & modes = common->GetModeDisplayList(); + idStr modeName = idLocalization::GetString( modes[ idMath::ClampInt( 0, modes.Num() - 1, gameLocal.gameType ) ] ); + mode.Append( idLocalization::GetString( idLocalization::GetString( modeName ) ) ); + txtVal->SetText( mode ); + txtVal->SetStrokeInfo( true, 0.9f, 1.8f ); + } + + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtNameHeading" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02181" ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtScore" ); + if ( txtVal != NULL ) { + if ( gameLocal.gameType == GAME_LASTMAN ) { + txtVal->SetText( idLocalization::GetString( "#str_04242" ) ); + } else if ( gameLocal.gameType == GAME_CTF ) { + txtVal->SetText( idLocalization::GetString( "#str_11112" ) ); + } else { + txtVal->SetText( idLocalization::GetString( "#str_04243" ) ); + } + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtWins" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02619" ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtPing" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02048" ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtNameHeading2" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02181" ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtScore2" ); + if ( txtVal != NULL ) { + if ( gameLocal.gameType == GAME_LASTMAN ) { + txtVal->SetText( idLocalization::GetString( "#str_04242" ) ); + } else if ( gameLocal.gameType == GAME_CTF ) { + txtVal->SetText( idLocalization::GetString( "#str_11112" ) ); + } else { + txtVal->SetText( idLocalization::GetString( "#str_04243" ) ); + } + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtWins2" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02619" ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtPing2" ); + if ( txtVal != NULL ) { + txtVal->SetText( "#str_02048" ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + } + } + +} + +/* +======================== +idMenuScreen_Scoreboard::SetPlayerData +======================== +*/ +void idMenuScreen_Scoreboard::SetPlayerData( idList< scoreboardInfo_t, TAG_IDLIB_LIST_MENU > data ) { + if ( playerList != NULL ) { + for ( int i = 0; i < data.Num(); ++i ) { + if ( i < playerList->GetChildren().Num() ) { + idMenuWidget_ScoreboardButton * button = dynamic_cast< idMenuWidget_ScoreboardButton * >( &playerList->GetChildByIndex( i ) ); + if ( button != NULL ) { + button->SetButtonInfo( data[i].index, data[i].values, data[i].voiceState ); + } + } + playerList->Update(); + } + } +} + +/* +======================== +idMenuScreen_Scoreboard::UpdateGameInfo +======================== +*/ +void idMenuScreen_Scoreboard::UpdateGameInfo( idStr gameInfo ) { + + if ( GetSWFObject() ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + + idSWFTextInstance * txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtGameInfo" ); + if ( txtVal != NULL ) { + txtVal->SetText( gameInfo ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + } + } +} + +/* +======================== +idMenuScreen_Scoreboard::UpdateSpectating +======================== +*/ +void idMenuScreen_Scoreboard::UpdateSpectating( idStr spectating, idStr follow ) { + + if ( GetSWFObject() ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + + idSWFTextInstance * txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtSpectating" ); + if ( txtVal != NULL ) { + txtVal->tooltip = true; + txtVal->SetText( spectating ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtFollow" ); + if ( txtVal != NULL ) { + txtVal->SetText( follow ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + } + } +} + +/* +======================== +idMenuScreen_Scoreboard::UpdateTeamScores +======================== +*/ +void idMenuScreen_Scoreboard::UpdateTeamScores( int r, int b ) { + + if ( GetSWFObject() ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + + idSWFTextInstance * txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtRedScore" ); + if ( txtVal != NULL ) { + txtVal->SetText( va( "%i", r ) ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBlueScore" ); + if ( txtVal != NULL ) { + txtVal->SetText( va( "%i", b ) ); + txtVal->SetStrokeInfo( true, 0.75f, 1.75f ); + } + } + } +} + +/* +======================== +idMenuScreen_Scoreboard::UpdateHighlight +======================== +*/ +void idMenuScreen_Scoreboard::UpdateHighlight() { + + if ( playerList == NULL || menuData == NULL ) { + return; + } + + idMenuHandler_Scoreboard * data = dynamic_cast< idMenuHandler_Scoreboard * >( menuData ); + + int curIndex = playerList->GetViewIndex(); + int newIndex = playerList->GetViewIndex(); + int numRed = data->GetNumPlayers( 0 ); + int numBlue = data->GetNumPlayers( 1 ); + + if ( numBlue == 0 ) { + if ( curIndex >= numRed ) { + newIndex = numRed - 1; + } + } else { + if ( curIndex > 3 + numBlue ) { + newIndex = 3 + numBlue; + } else if ( curIndex <= 3 ) { + if ( numRed == 0 ) { + newIndex = 4; + } else { + if ( curIndex >= numRed ) { + newIndex = numRed - 1; + } + } + } + } + + // newIndex can be -1 if all players are spectating ( no rankedplayers ) + if ( newIndex != curIndex && newIndex != -1 ) { + playerList->SetViewIndex( newIndex ); + playerList->SetFocusIndex( newIndex ); + } +} + +/* +======================== +idMenuScreen_Scoreboard::HandleAction +======================== +*/ +bool idMenuScreen_Scoreboard::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SCOREBOARD_AREA_INVALID, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_MUTE_PLAYER: { + + if ( parms.Num() != 1 ) { + return true; + } + + idMenuHandler_Scoreboard * data = dynamic_cast< idMenuHandler_Scoreboard * >( menuData ); + + if ( !data ) { + return true; + } + int index = parms[0].ToInteger(); + data->MutePlayer( index ); + + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + + if ( playerList == NULL ) { + return true; + } + + int selectionIndex = playerList->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != playerList->GetFocusIndex() ) { + playerList->SetViewIndex( playerList->GetViewOffset() + selectionIndex ); + playerList->SetFocusIndex( selectionIndex ); + } + + idMenuHandler_Scoreboard * data = dynamic_cast< idMenuHandler_Scoreboard * >( menuData ); + + if ( !data ) { + return true; + } + + int numRed = data->GetNumPlayers( 0 ); + int numBlue = data->GetNumPlayers( 1 ); + + if ( selectionIndex >= 4 && numBlue != 0 ) { + int index = numRed + ( selectionIndex - 4 ); + data->ViewPlayerProfile( index ); + } else { + data->ViewPlayerProfile( selectionIndex ); + } + + + return true; + } + case WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE: { + + if ( parms.Num() == 0 ) { + return true; + } + + if ( playerList ) { + + int dir = parms[ 0 ].ToInteger(); + int scroll = 0; + int curIndex = playerList->GetFocusIndex(); + + idMenuHandler_Scoreboard * data = dynamic_cast< idMenuHandler_Scoreboard * >( menuData ); + + if ( !data ) { + return true; + } + + int numRed = data->GetNumPlayers( 0 ); + int numBlue = data->GetNumPlayers( 1 ); + + if ( numRed + numBlue <= 1 ) { + return true; + } + + if ( dir > 0 ) { + if ( numBlue == 0 ) { + if ( curIndex + 1 >= numRed ) { + scroll = MAX_SCOREBOARD_SLOTS - curIndex; + } else { + scroll = dir; + } + } else { + if ( curIndex < 4 ) { + if ( curIndex + 1 >= numRed ) { + scroll = ( MAX_SCOREBOARD_SLOTS * 0.5f ) - curIndex; + } else { + scroll = dir; + } + } else { + if ( curIndex - 3 >= numBlue ) { + scroll = MAX_SCOREBOARD_SLOTS - curIndex; + } else { + scroll = dir; + } + } + } + } else if ( dir < 0 ) { + if ( numBlue == 0 ) { + if ( curIndex - 1 < 0 ) { + scroll = numRed - 1; + } else { + scroll = dir; + } + } else { + if ( curIndex < 4 ) { + if ( curIndex - 1 < 0 ) { + scroll = ( ( MAX_SCOREBOARD_SLOTS * 0.5f ) + numBlue ) - 1; + } else { + scroll = dir; + } + } else { + if ( curIndex - 1 < 4 ) { + scroll = -( ( MAX_SCOREBOARD_SLOTS * 0.5f ) - ( numRed - 1 ) ); + } else { + scroll = dir; + } + } + } + } + + if ( scroll != 0 ) { + playerList->Scroll( scroll ); + } + } + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +//*************************************************************** +// CTF SCOREBOARD +//*************************************************************** + +/* +======================== +idMenuScreen_Scoreboard_CTF::Initialize +======================== +*/ +void idMenuScreen_Scoreboard_CTF::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "sbCTF" ); + + playerList = new (TAG_SWF) idMenuWidget_ScoreboardList(); + playerList->SetSpritePath( GetSpritePath(), "info", "playerList" ); + playerList->SetNumVisibleOptions( MAX_SCOREBOARD_SLOTS ); + playerList->SetWrappingAllowed( true ); + while ( playerList->GetChildren().Num() < MAX_SCOREBOARD_SLOTS ) { + idMenuWidget_ScoreboardButton * const buttonWidget = new (TAG_SWF) idMenuWidget_ScoreboardButton(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, playerList->GetChildren().Num() ); + buttonWidget->AddEventAction( WIDGET_EVENT_COMMAND ).Set( WIDGET_ACTION_MUTE_PLAYER, playerList->GetChildren().Num() ); + buttonWidget->Initialize( data ); + playerList->AddChild( buttonWidget ); + } + playerList->Initialize( data ); + AddChild( playerList ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + +} + +//*************************************************************** +// TEAM SCOREBOARD +//*************************************************************** + +/* +======================== +idMenuScreen_Scoreboard_Team::Initialize +======================== +*/ +void idMenuScreen_Scoreboard_Team::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "sbTeam" ); + + playerList = new (TAG_SWF) idMenuWidget_ScoreboardList(); + playerList->SetSpritePath( GetSpritePath(), "info", "playerList" ); + playerList->SetNumVisibleOptions( MAX_SCOREBOARD_SLOTS ); + playerList->SetWrappingAllowed( true ); + while ( playerList->GetChildren().Num() < MAX_SCOREBOARD_SLOTS ) { + idMenuWidget_ScoreboardButton * const buttonWidget = new (TAG_SWF) idMenuWidget_ScoreboardButton(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, playerList->GetChildren().Num() ); + buttonWidget->AddEventAction( WIDGET_EVENT_COMMAND ).Set( WIDGET_ACTION_MUTE_PLAYER, playerList->GetChildren().Num() ); + buttonWidget->Initialize( data ); + playerList->AddChild( buttonWidget ); + } + playerList->Initialize( data ); + AddChild( playerList ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Bindings.cpp b/neo/d3xp/menus/MenuScreen_Shell_Bindings.cpp new file mode 100644 index 00000000..6d0b48a3 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Bindings.cpp @@ -0,0 +1,527 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +typedef struct { + const char * display; + const char * bind; +} bindInfo_t; + +static bindInfo_t keyboardBinds[] = { + { "#str_02090", "" }, // HEADING + { "#str_02100", "_forward" }, // FORWARD + { "#str_02101", "_back" }, // BACKPEDAL + { "#str_02102", "_moveLeft" }, // MOVE LEFT + { "#str_02103", "_moveRight" }, // MOVE RIGHT + { "#str_02104", "_moveUp" }, // JUMP + { "#str_02105", "_moveDown" }, // CROUCH + { "#str_02106", "_left" }, // TURN LEFT + { "#str_02107", "_right" }, // TURN RIGHT + { "#str_02109", "_speed" }, // SPRINT + + { "#str_02095", "" }, // HEADING + { "#str_02112", "_attack" }, // ATTACK + { "#str_02114", "_impulse14" }, // PREV. WEAPON + { "#str_02113", "_impulse15" }, // NEXT WEAPON + { "#str_02115", "_impulse13" }, // RELOAD + { "#str_swf_action_use", "_use" }, // USE + { "#str_02116", "_lookUp" }, // LOOK UP + { "#str_02117", "_lookDown" }, // LOOK DOWN + { "#str_02121", "_impulse19" }, // PDA / SCOREBOARD + + { "#str_02093", "" }, // HEADING + { "#str_00100177", "_impulse0" }, // FISTS / GRABBER + { "#str_00100178", "_impulse2" }, // PISTOL + { "#str_00100179", "_impulse3" }, // SHOTGUN / DOUBLE + { "#str_00100180", "_impulse5" }, // MACHINEGUN + { "#str_00100181", "_impulse6" }, // CHAINGUN + { "#str_00100182", "_impulse7" }, // GRENADES + { "#str_00100183", "_impulse8" }, // PLASMA GUN + { "#str_00100184", "_impulse9" }, // ROCKETS + { "#str_00100185", "_impulse10" }, // BFG + { "#str_swf_soulcube_artifact", "_impulse12" }, // SOULCUBE / ARTIFACT + { "#str_00100187", "_impulse16" }, // FLASHLIGHT + + { "#str_04065", "" }, // HEADING + { "#str_04067", "savegame quick" }, // QUICK SAVE + { "#str_04068", "loadgame quick" }, // QUICK LOAD + { "#str_04069", "screenshot" }, // SCREENSHOT + { "#str_02068", "clientMessageMode" }, // SCREENSHOT + { "#str_02122", "clientMessageMode 1" }, // SCREENSHOT + //{ "#str_04071", "clientDropWeapon" } // DROP WEAPON +}; + +static const int numBinds = sizeof( keyboardBinds ) / sizeof( keyboardBinds[0] ); + +static const int NUM_BIND_LISTINGS = 14; +/* +======================== +idMenuScreen_Shell_Bindings::Initialize +======================== +*/ +void idMenuScreen_Shell_Bindings::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuBindings" ); + + restoreDefault = new idMenuWidget_Button(); + restoreDefault->Initialize( data ); + restoreDefault->SetLabel( "" ); + restoreDefault->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_JOY3_ON_PRESS ); + restoreDefault->SetSpritePath( GetSpritePath(), "info", "btnRestore" ); + + AddChild( restoreDefault ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + idStr controls( idLocalization::GetString( "#str_04158" ) ); + controls.ToUpper(); + btnBack->SetLabel( controls ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options = new idMenuWidget_DynamicList(); + options->SetIgnoreColor( true ); + options->SetNumVisibleOptions( NUM_BIND_LISTINGS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + + UpdateBindingDisplay(); + + while ( options->GetChildren().Num() < NUM_BIND_LISTINGS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + +} + +/* +======================== +idMenuScreen_Shell_Bindings::Update +======================== +*/ +void idMenuScreen_Shell_Bindings::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_controls_keyboard" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Bindings::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Bindings::ShowScreen( const mainMenuTransition_t transitionType ) { + if ( options != NULL ) { + options->SetViewOffset( 0 ); + options->SetViewIndex( 1 ); + options->SetFocusIndex( 1 ); + } + + if ( menuData != NULL ) { + menuGUI = menuData->GetGUI(); + if ( menuGUI != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + txtBlinder = root.GetNestedSprite( "menuBindings", "info", "rebind" ); + blinder = root.GetNestedSprite( "menuBindings", "info", "blinder" ); + if ( restoreDefault != NULL ) { + restoreDefault->BindSprite( root ); + } + } + } + + ToggleWait( false ); + UpdateBindingDisplay(); + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Bindings::HideScreen +======================== +*/ +void idMenuScreen_Shell_Bindings::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( bindingsChanged ) { + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + bindingsChanged = false; + } + + idMenuScreen::HideScreen( transitionType ); +} + +extern idCVar in_useJoystick; + +/* +======================== +idMenuScreen_Shell_Bindings::UpdateBindingDisplay +======================== +*/ +void idMenuScreen_Shell_Bindings::UpdateBindingDisplay() { + + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > bindList; + + for ( int i = 0; i < numBinds; ++i ) { + idList< idStr > option; + + option.Append( keyboardBinds[i].display ); + + if ( ( idStr::Icmp( keyboardBinds[i].bind, "" ) != 0 ) ) { + keyBindings_t bind = idKeyInput::KeyBindingsFromBinding( keyboardBinds[i].bind, false, true ); + + idStr bindings; + + if ( !bind.gamepad.IsEmpty() && in_useJoystick.GetBool() ) { + idStrList joyBinds; + int start = 0; + while ( start < bind.gamepad.Length() ) { + int end = bind.gamepad.Find( ", ", true, start ); + if ( end < 0 ) { + end = bind.gamepad.Length(); + } + joyBinds.Alloc().CopyRange( bind.gamepad, start, end ); + start = end + 2; + } + const char * buttonsWithImages[] = { + "JOY1", "JOY2", "JOY3", "JOY4", "JOY5", "JOY6", + "JOY_TRIGGER1", "JOY_TRIGGER2", 0 + }; + for ( int i = 0; i < joyBinds.Num(); i++ ) { + if ( joyBinds[i].Icmpn( "JOY_STICK", 9 ) == 0 ) { + continue; // Can't rebind the sticks, so don't even show them + } + bool hasImage = false; + for ( const char ** b = buttonsWithImages; *b != 0; b++ ) { + if ( joyBinds[i].Icmp( *b ) == 0 ) { + hasImage = true; + break; + } + } + if ( !bindings.IsEmpty() ) { + bindings.Append( ", " ); + } + if ( hasImage ) { + bindings.Append( '<' ); + bindings.Append( joyBinds[i] ); + bindings.Append( '>' ); + } else { + bindings.Append( joyBinds[i] ); + } + } + bindings.Replace( "JOY_DPAD", "DPAD" ); + } + + if ( !bind.keyboard.IsEmpty() ) { + if ( !bindings.IsEmpty() ) { + bindings.Append( ", " ); + } + bindings.Append( bind.keyboard ); + } + + if ( !bind.mouse.IsEmpty() ) { + if ( !bindings.IsEmpty() ) { + bindings.Append( ", " ); + } + bindings.Append( bind.mouse ); + } + + bindings.ToUpper(); + option.Append( bindings ); + + } else { + option.Append( "" ); + } + + bindList.Append( option ); + } + + options->SetListData( bindList ); + +} + +/* +======================== +idMenuScreen_Shell_Bindings::ToggleWait +======================== +*/ +void idMenuScreen_Shell_Bindings::ToggleWait( bool wait ) { + + if ( wait ) { + + if ( blinder != NULL ) { + blinder->SetVisible( true ); + if ( options != NULL ) { + blinder->StopFrame( options->GetFocusIndex() + 1 ); + } + } + + if ( txtBlinder != NULL ) { + txtBlinder->SetVisible( true ); + } + + if ( restoreDefault != NULL ) { + restoreDefault->SetLabel( "" ); + } + + } else { + + if ( blinder != NULL ) { + blinder->SetVisible( false ); + } + + if ( txtBlinder != NULL ) { + txtBlinder->SetVisible( false ); + } + + if ( restoreDefault != NULL ) { + if ( menuData != NULL ) { + menuGUI = menuData->GetGUI(); + if ( menuGUI != NULL ) { + idSWFScriptObject & root = menuGUI->GetRootObject(); + restoreDefault->SetSpritePath( GetSpritePath(), "info", "btnRestore" ); + restoreDefault->BindSprite( root ); + } + } + if ( restoreDefault->GetSprite() ) { + restoreDefault->GetSprite()->SetVisible( true ); + } + restoreDefault->SetLabel( "#str_swf_restore_defaults" ); + } + + } +} + +/* +======================== +idMenuScreen_Shell_Bindings::SetBinding +======================== +*/ +void idMenuScreen_Shell_Bindings::SetBinding( int keyNum ) { + + int listIndex = options->GetViewIndex(); + idKeyInput::SetBinding( keyNum, keyboardBinds[ listIndex ].bind ); + UpdateBindingDisplay(); + ToggleWait( false ); + Update(); + +} + +/* +======================== +idMenuScreen_Shell_Bindings::HandleRestoreDefaults +======================== +*/ +void idMenuScreen_Shell_Bindings::HandleRestoreDefaults() { + + + class idSWFScriptFunction_Restore : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Restore( gameDialogMessages_t _msg, bool _accept, idMenuScreen_Shell_Bindings * _menu ) { + msg = _msg; + accept = _accept; + menu = _menu; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept ) { + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user != NULL ) { + idPlayerProfile * profile = user->GetProfile(); + if ( profile != NULL ) { + profile->RestoreDefault(); + if ( menu != NULL ) { + menu->UpdateBindingDisplay(); + menu->Update(); + } + } + } + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + bool accept; + idMenuScreen_Shell_Bindings * menu; + }; + + common->Dialog().AddDialog( GDM_BINDINGS_RESTORE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_Restore( GDM_BINDINGS_RESTORE, true, this ), new idSWFScriptFunction_Restore( GDM_BINDINGS_RESTORE, false, this ), false ); + +} + +/* +======================== +idMenuScreen_Shell_Bindings::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Bindings::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_KEYBOARD ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_CONTROLS, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_JOY3_ON_PRESS: { + HandleRestoreDefaults(); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + + int listIndex = 0; + if ( parms.Num() > 0 ) { + listIndex = options->GetViewOffset() + parms[ 0 ].ToInteger(); + } else { + listIndex = options->GetViewIndex(); + } + + if ( listIndex < 0 || listIndex >= numBinds ) { + return true; + } + + if ( options->GetViewIndex() != listIndex ) { + + if ( idStr::Icmp( keyboardBinds[ listIndex ].bind, "" ) == 0 ) { + return true; + } + + options->SetViewIndex( listIndex ); + options->SetFocusIndex( listIndex - options->GetViewOffset() ); + } else { + + idMenuHandler_Shell * data = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( data != NULL ) { + ToggleWait( true ); + Update(); + data->SetWaitForBinding( keyboardBinds[ listIndex ].bind ); + } + + } + + return true; + } + case WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE: { + + if ( parms.Num() == 0 ) { + return true; + } + + if ( options != NULL ) { + + int dir = parms[ 0 ].ToInteger(); + int scroll = 0; + int curIndex = options->GetViewIndex(); + + if ( dir != 0 ) { + if ( curIndex + dir >= numBinds ) { + scroll = dir * 2; + } else if ( curIndex + dir < 1 ) { + scroll = dir * 2; + } else { + if ( idStr::Icmp( keyboardBinds[curIndex + dir].bind, "" ) == 0 ) { + scroll = dir * 2; + } else { + scroll = dir; + } + } + } + + options->Scroll( scroll, true ); + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} diff --git a/neo/d3xp/menus/MenuScreen_Shell_Browser.cpp b/neo/d3xp/menus/MenuScreen_Shell_Browser.cpp new file mode 100644 index 00000000..8a77d659 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Browser.cpp @@ -0,0 +1,403 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +enum browserCommand_t { + BROWSER_COMMAND_REFRESH_SERVERS, + BROWSER_COMMAND_SHOW_GAMERTAG, +}; + +static const int NUM_SERVER_LIST_ITEMS = 10; + +/* +================================================ +idPair is is a template class Container composed of two objects, which can be of +any type, and provides accessors to these objects as well as Pair equality operators. The main +uses of Pairs in the engine are for the Tools and for callbacks. +================================================ +*/ +template +class idPair { +public: + idPair() { } + idPair( const T& f, const U& s ) : first( f ), second( s ) { } + + const bool operator==( const idPair& rhs ) const { + return ( rhs.first == first ) && ( rhs.second == second ); + } + + const bool operator!=( const idPair& rhs ) const { + return !(*this == rhs); + } + + T first; + U second; +}; + +/* +================================================ +idSort_PlayerGamesList +================================================ +*/ +class idSort_PlayerGamesList : public idSort_Quick< idPair< serverInfo_t, int >, idSort_PlayerGamesList > { +public: + int Compare( const idPair< serverInfo_t, int > & a, const idPair< serverInfo_t, int > & b ) const { + if ( a.first.joinable == b.first.joinable ) { + return a.first.gameMode - b.first.gameMode; + } else if ( a.first.joinable ) { + return 1; + } else { + return -1; + } + } +}; + +/* +======================== +idMenuScreen_Shell_GameBrowser::Initialize +======================== +*/ +void idMenuScreen_Shell_GameBrowser::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuPWF" ); + + listWidget = new idMenuWidget_GameBrowserList(); + listWidget->SetSpritePath( GetSpritePath(), "info", "options" ); + listWidget->SetNumVisibleOptions( NUM_SERVER_LIST_ITEMS ); + listWidget->SetWrappingAllowed( true ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, 1 ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, -1 ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, 1 ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, -1 ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + listWidget->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + AddChild( listWidget ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + while ( listWidget->GetChildren().Num() < NUM_SERVER_LIST_ITEMS ) { + idMenuWidget_ServerButton * buttonWidget = new idMenuWidget_ServerButton; + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, listWidget->GetChildren().Num() ); + buttonWidget->SetState( WIDGET_STATE_HIDDEN ); + buttonWidget->RegisterEventObserver( helpWidget ); + listWidget->AddChild( buttonWidget ); + } + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_multiplayer" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); +} + +/* +======================== +idMenuScreen_Shell_GameBrowser::ShowScreen +======================== +*/ +void idMenuScreen_Shell_GameBrowser::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuHandler_Shell * const mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( mgr == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_pwf_heading" ); // MULTIPLAYER + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + listWidget->ClearGames(); + + if ( mgr->GetCmdBar() != NULL ) { + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + + mgr->GetCmdBar()->ClearAllButtons(); + + buttonInfo = mgr->GetCmdBar()->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = mgr->GetCmdBar()->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + buttonInfo->label = "#str_02276"; + buttonInfo->action.Set( WIDGET_ACTION_COMMAND, BROWSER_COMMAND_REFRESH_SERVERS ); + } + + mgr->HidePacifier(); + + idMenuScreen::ShowScreen( transitionType ); + UpdateServerList(); +} + +/* +======================== +idMenuScreen_Shell_GameBrowser::HideScreen +======================== +*/ +void idMenuScreen_Shell_GameBrowser::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuHandler_Shell * const mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( mgr == NULL ) { + return; + } + + mgr->HidePacifier(); + + session->CancelListServers(); + + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_GameBrowser::UpdateServerList +======================== +*/ +void idMenuScreen_Shell_GameBrowser::UpdateServerList() { + idMenuHandler_Shell * const mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + + if ( mgr == NULL ) { + return; + } + + for ( int i = 0; i < listWidget->GetChildren().Num(); ++i ) { + idMenuWidget & child = listWidget->GetChildByIndex( i ); + child.SetState( WIDGET_STATE_HIDDEN ); + } + + // need to show the pacifier before actually making the ListServers call, because it can fail + // immediately and send back an error result to SWF. Things get confused if the showLoadingPacifier + // then gets called after that. + mgr->ShowPacifier( "#str_online_mpstatus_searching" ); + + session->ListServers( MakeCallback( this, &idMenuScreen_Shell_GameBrowser::OnServerListReady ) ); +} + +/* +======================== +idMenuScreen_Shell_GameBrowser::OnServerListReady +======================== +*/ +void idMenuScreen_Shell_GameBrowser::OnServerListReady() { + idMenuHandler_Shell * const mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + + if ( mgr == NULL ) { + return; + } + + mgr->HidePacifier(); + + idList< idPair< serverInfo_t, int > > servers; + for ( int i = 0; i < session->NumServers(); ++i ) { + const serverInfo_t * const server = session->ServerInfo( i ); + if ( server != NULL && server->joinable ) { + idPair< serverInfo_t, int > & serverPair = servers.Alloc(); + serverPair.first = *server; + serverPair.second = i; + } + } + + servers.SortWithTemplate( idSort_PlayerGamesList() ); + + listWidget->ClearGames(); + for ( int i = 0; i < servers.Num(); ++i ) { + idPair< serverInfo_t, int > & serverPair = servers[ i ]; + DescribeServer( serverPair.first, serverPair.second ); + } + + if ( servers.Num() > 0 ) { + listWidget->Update(); + listWidget->SetViewOffset( 0 ); + listWidget->SetViewIndex( 0 ); + listWidget->SetFocusIndex( 0 ); + } else { + listWidget->AddGame( "#str_swf_no_servers_found", idStrId(), idStr(), -1, 0, 0, false, false ); + listWidget->Update(); + listWidget->SetViewOffset( 0 ); + listWidget->SetViewIndex( 0 ); + listWidget->SetFocusIndex( 0 ); + } + + if ( mgr->GetCmdBar() != NULL ) { + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + + mgr->GetCmdBar()->ClearAllButtons(); + + if ( servers.Num() > 0 ) { + buttonInfo = mgr->GetCmdBar()->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#STR_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + + buttonInfo = mgr->GetCmdBar()->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = mgr->GetCmdBar()->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + buttonInfo->label = "#str_02276"; + buttonInfo->action.Set( WIDGET_ACTION_COMMAND, BROWSER_COMMAND_REFRESH_SERVERS ); + + if ( servers.Num() > 0 ) { + buttonInfo = mgr->GetCmdBar()->GetButton( idMenuWidget_CommandBar::BUTTON_JOY4 ); + buttonInfo->label = "#str_swf_view_profile"; + buttonInfo->action.Set( WIDGET_ACTION_COMMAND, BROWSER_COMMAND_SHOW_GAMERTAG ); + } + + mgr->GetCmdBar()->Update(); + } +} + +/* +======================== +idMenuScreen_Shell_GameBrowser::DescribeServers +======================== +*/ +void idMenuScreen_Shell_GameBrowser::DescribeServer( const serverInfo_t & server, const int index ) { + + idStr serverName; + int serverIndex = index; + bool joinable = false; + bool validMap = false; + int players = 0; + int maxPlayers = 0; + idStrId mapName; + idStr modeName; + + const idList< mpMap_t > maps = common->GetMapList(); + const bool isMapValid = ( server.gameMap >= 0 ) && ( server.gameMap < maps.Num() ); + if ( !isMapValid ) { + validMap = false; + serverName = server.serverName; + mapName = "#str_online_in_lobby"; + modeName = ""; + players = server.numPlayers; + maxPlayers = server.maxPlayers; + joinable = server.joinable; + } else { + mapName = common->GetMapList()[ server.gameMap ].mapName; + + const idStrList & modes = common->GetModeDisplayList(); + idStr mode = idLocalization::GetString( modes[ idMath::ClampInt( 0, modes.Num() - 1, server.gameMode ) ] ); + validMap = true; + serverName = server.serverName; + modeName = mode; + players = server.numPlayers; + maxPlayers = server.maxPlayers; + joinable = server.joinable; + } + + listWidget->AddGame( serverName, mapName, modeName, serverIndex, players, maxPlayers, joinable, validMap ); +} + +/* +======================== +idMenuScreen_Shell_GameBrowser::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_GameBrowser::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandle ) { + idMenuHandler_Shell * const mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + + if ( mgr == NULL ) { + return false; + } + + if ( mgr->ActiveScreen() != SHELL_AREA_BROWSER ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_PARTY_LOBBY, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_COMMAND: { + switch ( parms[ 0 ].ToInteger() ) { + case BROWSER_COMMAND_REFRESH_SERVERS: { + UpdateServerList(); + break; + } + case BROWSER_COMMAND_SHOW_GAMERTAG: { + int index = listWidget->GetServerIndex(); + if ( index != -1 ) { + session->ShowServerGamerCardUI( index ); + } + break; + } + } + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + int selectionIndex = listWidget->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != listWidget->GetFocusIndex() ) { + listWidget->SetViewIndex( listWidget->GetViewOffset() + selectionIndex ); + listWidget->SetFocusIndex( selectionIndex ); + return true; + } + + int index = listWidget->GetServerIndex(); + if ( index != -1 ) { + session->ConnectToServer( index ); + } + return true; + } + } + + return idMenuScreen::HandleAction( action, event, widget, forceHandle ); +} diff --git a/neo/d3xp/menus/MenuScreen_Shell_ControllerLayout.cpp b/neo/d3xp/menus/MenuScreen_Shell_ControllerLayout.cpp new file mode 100644 index 00000000..0ee7ed0d --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_ControllerLayout.cpp @@ -0,0 +1,389 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_LAYOUT_OPTIONS = 1; + +const static int MAX_CONTROLLER_CONFIGS = 2; + +typedef struct { + const char * textField; + int keyNum; +} gamepadBindInfo_t; + +static gamepadBindInfo_t gamepadBinds[] = { + { "txtJoy1", K_JOY1 }, + { "txtJoy2", K_JOY2 }, + { "txtJoy3", K_JOY3 }, + { "txtJoy4", K_JOY4 }, + { "txtDpad", K_JOY_DPAD_UP }, + { "txtStart", K_JOY9 }, + { "txtBack", K_JOY10 }, + { "txtLClick", K_JOY7 }, + { "txtRClick", K_JOY8 }, + { "txtLBumper", K_JOY5 }, + { "txtRBumper", K_JOY6 }, + { "txtLStick", K_JOY_STICK1_UP }, + { "txtRStick", K_JOY_STICK2_UP }, + { "txtLTrigger", K_JOY_TRIGGER1 }, + { "txtRTrigger", K_JOY_TRIGGER2 } +}; + +static const int numGamepadBinds = sizeof( gamepadBinds ) / sizeof( gamepadBinds[0] ); + +/* +======================== +idMenuScreen_Shell_ControllerLayout::Initialize +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuControllerLayout" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_LAYOUT_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "controlInfo", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_gamepad_heading" ); // CONTROLS + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_BUTTON_FULL_TEXT_SLIDER ); + control->SetLabel( "CONTROL LAYOUT" ); // Auto Weapon Reload + control->SetDataSource( &layoutData, idMenuDataSource_LayoutSettings::LAYOUT_FIELD_LAYOUT ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::Update +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_controller_layout" ); // CONTROLLER LAYOUT + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + + if ( menuData != NULL ) { + idSWFSpriteInstance * layout = NULL; + + layout = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "controlInfo", "layout360" ); + + if ( layout != NULL ) { + if ( menuData->GetPlatform( true ) == 2 ) { + layout->StopFrame( 1 ); + } else { + layout->StopFrame( menuData->GetPlatform( true ) + 1 ); + } + } + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::ShowScreen +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::ShowScreen( const mainMenuTransition_t transitionType ) { + layoutData.LoadData(); + idMenuScreen::ShowScreen( transitionType ); + + if ( GetSprite() != NULL ) { + + idSWFSpriteInstance * layout360 = NULL; + idSWFSpriteInstance * layoutPS3 = NULL; + + layout360 = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "controlInfo", "layout360" ); + layoutPS3 = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "controlInfo", "layoutPS3" ); + + if ( layout360 != NULL && layoutPS3 != NULL ) { + layout360->SetVisible( true ); + layoutPS3->SetVisible( false ); + } + } + + UpdateBindingInfo(); +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::HideScreen +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::HideScreen( const mainMenuTransition_t transitionType ) { + layoutData.CommitData(); + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::UpdateBindingInfo +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::UpdateBindingInfo() { + + if ( !GetSprite() ) { + return; + } + + for ( int i = 0; i < numGamepadBinds; ++i ) { + + const char * txtField = gamepadBinds[i].textField; + int keyNum = gamepadBinds[i].keyNum; + + idSWFTextInstance * txtVal = NULL; + + txtVal = GetSprite()->GetScriptObject()->GetNestedText( "info", "controlInfo", "layout360", txtField ); + + if ( txtVal != NULL ) { + const char * binding = idKeyInput::GetBinding( keyNum ); + if ( binding == NULL || binding[0] == 0 ) { + txtVal->SetText( "" ); + } else if ( keyNum == K_JOY7 ) { + idStr action = idLocalization::GetString( va( "#str_swf_action%s", binding ) ); + txtVal->SetText( action.c_str() ); + } else if ( keyNum == K_JOY8 ) { + idStr action = idLocalization::GetString( va( "#str_swf_action%s", binding ) ); + txtVal->SetText( action.c_str() ); + } else { + txtVal->SetText( va( "#str_swf_action%s", binding ) ); + } + } + } +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_ControllerLayout::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_CONTROLLER_LAYOUT ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_GAMEPAD, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( parms.Num() != 1 ) { + return true; + } + + if ( options == NULL ) { + return true; + } + + int selectionIndex = parms[0].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + layoutData.AdjustField( selectionIndex, 1 ); + options->Update(); + UpdateBindingInfo(); + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + case WIDGET_ACTION_ADJUST_FIELD: { + if ( widget != NULL && widget->GetDataSource() != NULL ) { + widget->GetDataSource()->AdjustField( widget->GetDataSourceFieldIndex(), parms[ 0 ].ToInteger() ); + widget->Update(); + } + UpdateBindingInfo(); + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::idMenuDataSource_AudioSettings::idMenuDataSource_AudioSettings +======================== +*/ +idMenuScreen_Shell_ControllerLayout::idMenuDataSource_LayoutSettings::idMenuDataSource_LayoutSettings() { + fields.SetNum( MAX_LAYOUT_FIELDS ); + originalFields.SetNum( MAX_LAYOUT_FIELDS ); +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::idMenuDataSource_AudioSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::idMenuDataSource_LayoutSettings::LoadData() { + + idPlayerProfile * profile = session->GetProfileFromMasterLocalUser(); + if ( profile == NULL ) { + return; + } + + int configSet = profile->GetConfig(); + + fields[ LAYOUT_FIELD_LAYOUT ].SetString( idLocalization::GetString( va( "#str_swf_config_360_%i", configSet ) ) ); + + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::idMenuDataSource_AudioSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::idMenuDataSource_LayoutSettings::CommitData() { + + if ( IsDataChanged() ) { + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + } + + // make the committed fields into the backup fields + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::idMenuDataSource_AudioSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_ControllerLayout::idMenuDataSource_LayoutSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + + idPlayerProfile * profile = session->GetProfileFromMasterLocalUser(); + if ( profile == NULL ) { + return; + } + + int configSet = profile->GetConfig(); + + if ( fieldIndex == LAYOUT_FIELD_LAYOUT ) { + configSet += adjustAmount; + if ( configSet < 0 ) { + configSet = MAX_CONTROLLER_CONFIGS - 1; + } else if ( configSet >= MAX_CONTROLLER_CONFIGS ) { + configSet = 0; + } + } + + fields[ LAYOUT_FIELD_LAYOUT ].SetString( idLocalization::GetString( va( "#str_swf_config_360_%i", configSet ) ) ); + + profile->SetConfig( configSet, false ); + +} + +/* +======================== +idMenuScreen_Shell_ControllerLayout::idMenuDataSource_AudioSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_ControllerLayout::idMenuDataSource_LayoutSettings::IsDataChanged() const { + bool hasLocalChanges = false; + + if ( fields[ LAYOUT_FIELD_LAYOUT ].ToString() != originalFields[ LAYOUT_FIELD_LAYOUT ].ToString() ) { + return true; + } + + return hasLocalChanges; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Controls.cpp b/neo/d3xp/menus/MenuScreen_Shell_Controls.cpp new file mode 100644 index 00000000..bc502498 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Controls.cpp @@ -0,0 +1,387 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_CONTROLS_OPTIONS = 8; + +enum contorlsMenuCmds_t { + CONTROLS_CMD_BINDINGS, + CONTROLS_CMD_GAMEPAD, + CONTROLS_CMD_GAMEPAD_ENABLED, + CONTROLS_CMD_INVERT, + CONTROLS_CMD_MOUSE_SENS +}; + +/* +======================== +idMenuScreen_Shell_Controls::Initialize +======================== +*/ +void idMenuScreen_Shell_Controls::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuControls" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_CONTROLS_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_settings" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control; + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_BUTTON_TEXT ); + control->SetLabel( "#str_swf_keyboard" ); // KEY BINDINGS + control->SetDescription( "#str_swf_binding_desc" ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_BINDINGS ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_BUTTON_TEXT ); + control->SetLabel( "#str_swf_gamepad" ); // Gamepad + control->SetDescription( "#str_swf_gamepad_desc" ); + control->RegisterEventObserver( helpWidget ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_GAMEPAD ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_gamepad_enabled" ); // Gamepad Enabled + control->SetDataSource( &controlData, idMenuDataSource_ControlSettings::CONTROLS_FIELD_GAMEPAD_ENABLED ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_GAMEPAD_ENABLED ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_invert_mouse" ); // Invert Mouse + control->SetDataSource( &controlData, idMenuDataSource_ControlSettings::CONTROLS_FIELD_INVERT_MOUSE ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_INVERT ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_swf_mouse_sens" ); // Mouse Sensitivity + control->SetDataSource( &controlData, idMenuDataSource_ControlSettings::CONTROLS_FIELD_MOUSE_SENS ); + control->SetupEvents( 2, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, CONTROLS_CMD_MOUSE_SENS ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Controls::Update +======================== +*/ +void idMenuScreen_Shell_Controls::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + idStr controls( idLocalization::GetString( "#str_04158" ) ); + controls.ToUpper(); + heading->SetText( controls ); // CONTROLS + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Controls::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Controls::ShowScreen( const mainMenuTransition_t transitionType ) { + controlData.LoadData(); + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Controls::HideScreen +======================== +*/ +void idMenuScreen_Shell_Controls::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( controlData.IsDataChanged() ) { + controlData.CommitData(); + } + + if ( menuData != NULL ) { + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( handler != NULL ) { + handler->SetupPCOptions(); + } + } + + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Controls::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Controls::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_CONTROLS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE ); + return true; + } + + case WIDGET_ACTION_COMMAND: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + switch ( parms[0].ToInteger() ) { + case CONTROLS_CMD_BINDINGS: { + menuData->SetNextScreen( SHELL_AREA_KEYBOARD, MENU_TRANSITION_SIMPLE ); + break; + } + case CONTROLS_CMD_GAMEPAD: { + menuData->SetNextScreen( SHELL_AREA_GAMEPAD, MENU_TRANSITION_SIMPLE ); + break; + } + case CONTROLS_CMD_INVERT: { + controlData.AdjustField( idMenuDataSource_ControlSettings::CONTROLS_FIELD_INVERT_MOUSE, 1 ); + if ( options != NULL ) { + options->Update(); + } + break; + } + case CONTROLS_CMD_MOUSE_SENS: { + controlData.AdjustField( idMenuDataSource_ControlSettings::CONTROLS_FIELD_MOUSE_SENS, 1 ); + if ( options != NULL ) { + options->Update(); + } + break; + } + case CONTROLS_CMD_GAMEPAD_ENABLED: { + controlData.AdjustField( idMenuDataSource_ControlSettings::CONTROLS_FIELD_GAMEPAD_ENABLED, 1 ); + if ( options != NULL ) { + options->Update(); + } + break; + } + } + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +///////////////////////////////// +// SCREEN SETTINGS +///////////////////////////////// + +extern idCVar in_mouseInvertLook; +extern idCVar in_mouseSpeed; +extern idCVar in_useJoystick; + +/* +======================== +idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::idMenuDataSource_AudioSettings +======================== +*/ +idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::idMenuDataSource_ControlSettings() { + fields.SetNum( MAX_CONTROL_FIELDS ); + originalFields.SetNum( MAX_CONTROL_FIELDS ); +} + +/* +======================== +idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::LoadData() { + fields[ CONTROLS_FIELD_INVERT_MOUSE ].SetBool( in_mouseInvertLook.GetBool() ); + float mouseSpeed = ( ( in_mouseSpeed.GetFloat() - 0.25f ) / ( 4.0f - 0.25 ) ) * 100.0f; + fields[ CONTROLS_FIELD_MOUSE_SENS ].SetFloat( mouseSpeed ); + fields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].SetBool( in_useJoystick.GetBool() ); + + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::CommitData() { + + in_mouseInvertLook.SetBool( fields[ CONTROLS_FIELD_INVERT_MOUSE ].ToBool() ); + float mouseSpeed = 0.25f + ( ( 4.0f - 0.25 ) * ( fields[ CONTROLS_FIELD_MOUSE_SENS ].ToFloat() / 100.0f ) ); + in_mouseSpeed.SetFloat( mouseSpeed ); + in_useJoystick.SetBool( fields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].ToBool() ); + + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + + // make the committed fields into the backup fields + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + if ( fieldIndex == CONTROLS_FIELD_INVERT_MOUSE || fieldIndex == CONTROLS_FIELD_GAMEPAD_ENABLED ) { + fields[ fieldIndex ].SetBool( !fields[ fieldIndex ].ToBool() ); + } else if ( fieldIndex == CONTROLS_FIELD_MOUSE_SENS ) { + float newValue = idMath::ClampFloat( 0.0f, 100.0f, fields[ fieldIndex ].ToFloat() + adjustAmount ); + fields[ fieldIndex ].SetFloat( newValue ); + } +} + +/* +======================== +idMenuScreen_Shell_Controls::idMenuDataSource_AudioSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_Controls::idMenuDataSource_ControlSettings::IsDataChanged() const { + + if ( fields[ CONTROLS_FIELD_INVERT_MOUSE ].ToBool() != originalFields[ CONTROLS_FIELD_INVERT_MOUSE ].ToBool() ) { + return true; + } + + if ( fields[ CONTROLS_FIELD_MOUSE_SENS ].ToFloat() != originalFields[ CONTROLS_FIELD_MOUSE_SENS ].ToFloat() ) { + return true; + } + + if ( fields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].ToFloat() != originalFields[ CONTROLS_FIELD_GAMEPAD_ENABLED ].ToFloat() ) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Credits.cpp b/neo/d3xp/menus/MenuScreen_Shell_Credits.cpp new file mode 100644 index 00000000..b2c434b4 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Credits.cpp @@ -0,0 +1,934 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int NUM_CREDIT_LINES = 16; + +void idMenuScreen_Shell_Credits::SetupCreditList() { + + class idRefreshCredits : public idSWFScriptFunction_RefCounted { + public: + idRefreshCredits( idMenuScreen_Shell_Credits * _screen ) : + screen( _screen ) { + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + if ( screen == NULL ) { + return idSWFScriptVar(); + } + + screen->UpdateCredits(); + return idSWFScriptVar(); + } + private: + idMenuScreen_Shell_Credits * screen; + }; + + if ( GetSWFObject() ) { + GetSWFObject()->SetGlobal( "updateCredits", new ( TAG_SWF ) idRefreshCredits( this ) ); + } + + + creditList.Clear(); + creditList.Append( creditInfo_t( 3, "DOOM 3 BFG EDITION" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 2, "DEVELOPMENT TEAM" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Executive Producer" ) ); + creditList.Append( creditInfo_t( 0, "Eric Webb" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Lead Programmer" ) ); + creditList.Append( creditInfo_t( 0, "Brian Harris" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Lead Designer" ) ); + creditList.Append( creditInfo_t( 0, "Jerry Keehan" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Programming" ) ); + creditList.Append( creditInfo_t( 0, "Curtis Arink" ) ); + creditList.Append( creditInfo_t( 0, "Robert Duffy" ) ); + creditList.Append( creditInfo_t( 0, "Jeff Farrand" ) ); + creditList.Append( creditInfo_t( 0, "Ryan Gerleve" ) ); + creditList.Append( creditInfo_t( 0, "Billy Khan" ) ); + creditList.Append( creditInfo_t( 0, "Gloria Kennickell" ) ); + creditList.Append( creditInfo_t( 0, "Mike Maynard" ) ); + creditList.Append( creditInfo_t( 0, "John Roberts" ) ); + creditList.Append( creditInfo_t( 0, "Steven Serafin" ) ); + creditList.Append( creditInfo_t( 0, "Jan Paul Van Waveren" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Design" ) ); + creditList.Append( creditInfo_t( 0, "Steve Rescoe" ) ); + creditList.Append( creditInfo_t( 0, "Chris Voss" ) ); + creditList.Append( creditInfo_t( 0, "David Vargo" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Art" ) ); + creditList.Append( creditInfo_t( 0, "Kevin Cloud" ) ); + creditList.Append( creditInfo_t( 0, "Andy Chang" ) ); + creditList.Append( creditInfo_t( 0, "Pat Duffy" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Audio" ) ); + creditList.Append( creditInfo_t( 0, "Christian Antkow" ) ); + creditList.Append( creditInfo_t( 0, "Chris Hite" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional Support" ) ); + creditList.Append( creditInfo_t( 0, "Roger Berrones" ) ); + creditList.Append( creditInfo_t( 0, "Brad Bramlett" ) ); + creditList.Append( creditInfo_t( 0, "John Casey" ) ); + creditList.Append( creditInfo_t( 0, "Jeremy Cook" ) ); + creditList.Append( creditInfo_t( 0, "Chris Hays" ) ); + creditList.Append( creditInfo_t( 0, "Matt Hooper" ) ); + creditList.Append( creditInfo_t( 0, "Danny Keys" ) ); + creditList.Append( creditInfo_t( 0, "Jason Kim" ) ); + creditList.Append( creditInfo_t( 0, "Brian Kowalczyk" ) ); + creditList.Append( creditInfo_t( 0, "Dustin Land" ) ); + creditList.Append( creditInfo_t( 0, "Stephane Lebrun" ) ); + creditList.Append( creditInfo_t( 0, "Matt Nelson" ) ); + creditList.Append( creditInfo_t( 0, "John Pollard" ) ); + creditList.Append( creditInfo_t( 0, "Alan Rogers" ) ); + creditList.Append( creditInfo_t( 0, "Jah Raphael" ) ); + creditList.Append( creditInfo_t( 0, "Jarrod Showers" ) ); + creditList.Append( creditInfo_t( 0, "Shale Williams" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Studio Director" ) ); + creditList.Append( creditInfo_t( 0, "Tim Willits" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Studio President" ) ); + creditList.Append( creditInfo_t( 0, "Todd Hollenshead" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Technical Director" ) ); + creditList.Append( creditInfo_t( 0, "John Carmack" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Human Resources" ) ); + creditList.Append( creditInfo_t( 0, "Carrie Barcroft" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "id Mom" ) ); + creditList.Append( creditInfo_t( 0, "Donna Jackson" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "id Software IT" ) ); + creditList.Append( creditInfo_t( 0, "Duncan Welch" ) ); + creditList.Append( creditInfo_t( 0, "Michael Cave" ) ); + creditList.Append( creditInfo_t( 0, "Josh Shoemate" ) ); + creditList.Append( creditInfo_t( 0, "Michael Musick" ) ); + creditList.Append( creditInfo_t( 0, "Alex Brandt" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "DOOM 3 DEVELOPED BY" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 2, "id Software" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Technical Director " ) ); + creditList.Append( creditInfo_t( 0, "John Carmack" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Lead Programmer" ) ); + creditList.Append( creditInfo_t( 0, "Robert A. Duffy" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Lead Designer" ) ); + creditList.Append( creditInfo_t( 0, "Tim Willits" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Lead Artist" ) ); + creditList.Append( creditInfo_t( 0, "Kenneth Scott" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Animation" ) ); + creditList.Append( creditInfo_t( 0, "James Houska" ) ); + creditList.Append( creditInfo_t( 0, "Fredrik Nilsson" ) ); + creditList.Append( creditInfo_t( 0, "Eric Webb" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Art" ) ); + creditList.Append( creditInfo_t( 0, "Adrian Carmack" ) ); + creditList.Append( creditInfo_t( 0, "Andy Chang" ) ); + creditList.Append( creditInfo_t( 0, "Kevin Cloud" ) ); + creditList.Append( creditInfo_t( 0, "Seneca Menard" ) ); + creditList.Append( creditInfo_t( 0, "Patrick Thomas" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Design" ) ); + creditList.Append( creditInfo_t( 0, "Mal Blackwell" ) ); + creditList.Append( creditInfo_t( 0, "Matt Hooper" ) ); + creditList.Append( creditInfo_t( 0, "Jerry Keehan" ) ); + creditList.Append( creditInfo_t( 0, "Steve Rescoe" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "UI Artist" ) ); + creditList.Append( creditInfo_t( 0, "Pat Duffy" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Programming" ) ); + creditList.Append( creditInfo_t( 0, "Timothee Besset" ) ); + creditList.Append( creditInfo_t( 0, "Jim Dose" ) ); + creditList.Append( creditInfo_t( 0, "Jan Paul van Waveren" ) ); + creditList.Append( creditInfo_t( 0, "Jonathan Wright" ) ); + creditList.Append( creditInfo_t( 0, "Brian Harris" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sound Design " ) ); + creditList.Append( creditInfo_t( 0, "Christian Antkow" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Dir. Business Development" ) ); + creditList.Append( creditInfo_t( 0, "Marty Stratton" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Development Assistant" ) ); + creditList.Append( creditInfo_t( 0, "Eric Webb" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 2, "3rd PARTY" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "DOOM 3: Ressurection of Evil Developed by" ) ); + creditList.Append( creditInfo_t( 0, "Nerve Software" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional Sound Design" ) ); + creditList.Append( creditInfo_t( 0, "Ed Lima" ) ); + creditList.Append( creditInfo_t( 0, "Danetracks, Inc." ) ); + creditList.Append( creditInfo_t( 0, "Defacto Sound" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Theme for DOOM 3 Produced by" ) ); + creditList.Append( creditInfo_t( 0, "Chris Vrenna" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Theme for DOOM 3 Composed by" ) ); + creditList.Append( creditInfo_t( 0, "Clint Walsh" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional Story and Dialog" ) ); + creditList.Append( creditInfo_t( 0, "Matthew J. Costello" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Casting and Voice Direction" ) ); + creditList.Append( creditInfo_t( 0, "Margaret Tang - Womb Music" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Voice Editorial and Post" ) ); + creditList.Append( creditInfo_t( 0, "Rik Schaffer - Womb Music" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Voice Recording" ) ); + creditList.Append( creditInfo_t( 0, "Womb Music" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Voice Performers" ) ); + creditList.Append( creditInfo_t( 0, "Paul Eiding" ) ); + creditList.Append( creditInfo_t( 0, "Jim Cummings" ) ); + creditList.Append( creditInfo_t( 0, "Liam O'Brien" ) ); + creditList.Append( creditInfo_t( 0, "Nicholas Guest" ) ); + creditList.Append( creditInfo_t( 0, "Vanessa Marshall" ) ); + creditList.Append( creditInfo_t( 0, "Neill Ross" ) ); + creditList.Append( creditInfo_t( 0, "Phillip Clarke" ) ); + creditList.Append( creditInfo_t( 0, "Andy Chanley" ) ); + creditList.Append( creditInfo_t( 0, "Charles Dennis" ) ); + creditList.Append( creditInfo_t( 0, "Grey Delisle" ) ); + creditList.Append( creditInfo_t( 0, "Jennifer Hale" ) ); + creditList.Append( creditInfo_t( 0, "Grant Albrecht" ) ); + creditList.Append( creditInfo_t( 0, "Dee Baker" ) ); + creditList.Append( creditInfo_t( 0, "Michael Bell" ) ); + creditList.Append( creditInfo_t( 0, "Steve Blum" ) ); + creditList.Append( creditInfo_t( 0, "S. Scott Bullock" ) ); + creditList.Append( creditInfo_t( 0, "Cam Clarke" ) ); + creditList.Append( creditInfo_t( 0, "Robin Atkin Downes" ) ); + creditList.Append( creditInfo_t( 0, "Keith Ferguson" ) ); + creditList.Append( creditInfo_t( 0, "Jay Gordon" ) ); + creditList.Append( creditInfo_t( 0, "Michael Gough" ) ); + creditList.Append( creditInfo_t( 0, "Bill Harper" ) ); + creditList.Append( creditInfo_t( 0, "Nick Jameson" ) ); + creditList.Append( creditInfo_t( 0, "David Kaye" ) ); + creditList.Append( creditInfo_t( 0, "Phil La Marr" ) ); + creditList.Append( creditInfo_t( 0, "Scott Menville" ) ); + creditList.Append( creditInfo_t( 0, "Jim Meskimen" ) ); + creditList.Append( creditInfo_t( 0, "Matt Morton" ) ); + creditList.Append( creditInfo_t( 0, "Daran Norris" ) ); + creditList.Append( creditInfo_t( 0, "Rob Paulsen" ) ); + creditList.Append( creditInfo_t( 0, "Phil Proctor" ) ); + creditList.Append( creditInfo_t( 0, "Rino Romoano" ) ); + creditList.Append( creditInfo_t( 0, "Andre Sogliuzzo" ) ); + creditList.Append( creditInfo_t( 0, "Jim Ward" ) ); + creditList.Append( creditInfo_t( 0, "Wally Wingert" ) ); + creditList.Append( creditInfo_t( 0, "Edward Yin" ) ); + creditList.Append( creditInfo_t( 0, "Keone Young" ) ); + creditList.Append( creditInfo_t( 0, "Ryun Yu" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional User Interface" ) ); + creditList.Append( creditInfo_t( 0, "Double-Action Design" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Motion Capture Services" ) ); + creditList.Append( creditInfo_t( 0, "Janimation" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "UAC Promotional Videos" ) ); + creditList.Append( creditInfo_t( 0, "Six Foot Studios" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional Multiplayer Design" ) ); + creditList.Append( creditInfo_t( 0, "Splash Damage, Ltd." ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional Programming" ) ); + creditList.Append( creditInfo_t( 0, "Graeme Devine" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Localization Services" ) ); + creditList.Append( creditInfo_t( 0, "Synthesis" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "BETHESDA SOFTWORKS" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Senior Producer" ) ); + creditList.Append( creditInfo_t( 0, "Laffy Taylor" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sr. Producer, Submissions" ) ); + creditList.Append( creditInfo_t( 0, "Timothy Beggs" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "VP, Product Development" ) ); + creditList.Append( creditInfo_t( 0, "Todd Vaughn" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "External Technical Director" ) ); + creditList.Append( creditInfo_t( 0, "Jonathan Williams" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "President" ) ); + creditList.Append( creditInfo_t( 0, "Vlatko Andonov" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "VP, Public Relations and Marketing" ) ); + creditList.Append( creditInfo_t( 0, "Pete Hines" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Director of Global Marketing" ) ); + creditList.Append( creditInfo_t( 0, "Steve Perkins" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Director of Global PR" ) ); + creditList.Append( creditInfo_t( 0, "Tracey Thompson" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Senior Brand Manager" ) ); + creditList.Append( creditInfo_t( 0, "Rakhi Gupta" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Associate Brand Manager" ) ); + creditList.Append( creditInfo_t( 0, "David Clayman" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Event and Trade Show Director" ) ); + creditList.Append( creditInfo_t( 0, "Henry Mobley" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Senior Community Manager" ) ); + creditList.Append( creditInfo_t( 0, "Matthew Grandstaff" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Community Manager" ) ); + creditList.Append( creditInfo_t( 0, "Nick Breckon" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Senior PR Coordinator" ) ); + creditList.Append( creditInfo_t( 0, "Angela Ramsey-Chapman" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Marketing Assistant" ) ); + creditList.Append( creditInfo_t( 0, "Jenny McWhorter" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Video Production" ) ); + creditList.Append( creditInfo_t( 0, "Matt Killmon" ) ); + creditList.Append( creditInfo_t( 0, "Sal Goldenburg" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Graphic Design" ) ); + creditList.Append( creditInfo_t( 0, "Michael Wagner" ) ); + creditList.Append( creditInfo_t( 0, "Lindsay Westcott" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "VP, Sales" ) ); + creditList.Append( creditInfo_t( 0, "Ron Seger" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Operations Director" ) ); + creditList.Append( creditInfo_t( 0, "Todd Curtis" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales and Operations Manager" ) ); + creditList.Append( creditInfo_t( 0, "Jill Bralove" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Account Manager" ) ); + creditList.Append( creditInfo_t( 0, "Michelle Ferrara" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Regional Sales Manager" ) ); + creditList.Append( creditInfo_t( 0, "Michael Donnellan" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Channel Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Michelle Burgess" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Assistants" ) ); + creditList.Append( creditInfo_t( 0, "Sara Simpson" ) ); + creditList.Append( creditInfo_t( 0, "Jason Snead" ) ); + creditList.Append( creditInfo_t( 0, "Jessica Williams" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Operations Assistant" ) ); + creditList.Append( creditInfo_t( 0, "Scott Mills" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Director of Quality Assurance" ) ); + creditList.Append( creditInfo_t( 0, "Darren Manes" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Quality Assurance Manager" ) ); + creditList.Append( creditInfo_t( 0, "Rob Gray" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Quality Assurance Leads" ) ); + creditList.Append( creditInfo_t( 0, "Terry Dunn" ) ); + creditList.Append( creditInfo_t( 0, "Matt Weil" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Quality Assurance" ) ); + creditList.Append( creditInfo_t( 0, "Dan Silva" ) ); + creditList.Append( creditInfo_t( 0, "Alan Webb" ) ); + creditList.Append( creditInfo_t( 0, "Cory Andrews" ) ); + creditList.Append( creditInfo_t( 0, "George Churchill" ) ); + creditList.Append( creditInfo_t( 0, "Jonathan DeVriendt" ) ); + creditList.Append( creditInfo_t( 0, "Spencer Gottlieb" ) ); + creditList.Append( creditInfo_t( 0, "Gary Powell" ) ); + creditList.Append( creditInfo_t( 0, "Samuel Papke" ) ); + creditList.Append( creditInfo_t( 0, "Amanda Sheehan" ) ); + creditList.Append( creditInfo_t( 0, "Philip Spangrud" ) ); + creditList.Append( creditInfo_t( 0, "Larry Waldman" ) ); + creditList.Append( creditInfo_t( 0, "Donald Anderson" ) ); + creditList.Append( creditInfo_t( 0, "James Audet" ) ); + creditList.Append( creditInfo_t( 0, "Hunter Calvert" ) ); + creditList.Append( creditInfo_t( 0, "Max Cameron" ) ); + creditList.Append( creditInfo_t( 0, "Donald Harris" ) ); + creditList.Append( creditInfo_t( 0, "Peter Garcia" ) ); + creditList.Append( creditInfo_t( 0, "Joseph Lopatta" ) ); + creditList.Append( creditInfo_t( 0, "Scott LoPresti" ) ); + creditList.Append( creditInfo_t( 0, "Collin Mackett" ) ); + creditList.Append( creditInfo_t( 0, "Gerard Nagy" ) ); + creditList.Append( creditInfo_t( 0, "William Pegus" ) ); + creditList.Append( creditInfo_t( 0, "Angela Rupinen" ) ); + creditList.Append( creditInfo_t( 0, "Gabriel Scaringello" ) ); + creditList.Append( creditInfo_t( 0, "Drew Slotkin" ) ); + creditList.Append( creditInfo_t( 0, "Kyle Wallace" ) ); + creditList.Append( creditInfo_t( 0, "Patrick Walsh" ) ); + creditList.Append( creditInfo_t( 0, "John Welkner" ) ); + creditList.Append( creditInfo_t( 0, "Jason Wilkin" ) ); + creditList.Append( creditInfo_t( 0, "John Benoit" ) ); + creditList.Append( creditInfo_t( 0, "Jacob Clayman" ) ); + creditList.Append( creditInfo_t( 0, "Colin McInerney" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Additional QA" ) ); + creditList.Append( creditInfo_t( 0, "James Costantino" ) ); + creditList.Append( creditInfo_t( 0, "Chris Krietz" ) ); + creditList.Append( creditInfo_t( 0, "Joseph Mueller" ) ); + creditList.Append( creditInfo_t( 0, "Andrew Scharf" ) ); + creditList.Append( creditInfo_t( 0, "Jen Tonon" ) ); + creditList.Append( creditInfo_t( 0, "Justin McSweeney" ) ); + creditList.Append( creditInfo_t( 0, "Sean Palomino" ) ); + creditList.Append( creditInfo_t( 0, "Erica Stead" ) ); + creditList.Append( creditInfo_t( 0, "Wil Cookman" ) ); + creditList.Append( creditInfo_t( 0, "Garrett Hohl" ) ); + creditList.Append( creditInfo_t( 0, "Tim Hartgrave" ) ); + creditList.Append( creditInfo_t( 0, "Heath Hollenshead" ) ); + creditList.Append( creditInfo_t( 0, "Brandon Korbel" ) ); + creditList.Append( creditInfo_t( 0, "Daniel Korecki" ) ); + creditList.Append( creditInfo_t( 0, "Max Morrison" ) ); + creditList.Append( creditInfo_t( 0, "Doug Ransley" ) ); + creditList.Append( creditInfo_t( 0, "Mauricio Rivera" ) ); + creditList.Append( creditInfo_t( 0, "Aaron Walsh" ) ); + creditList.Append( creditInfo_t( 0, "Daniel Wathen" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Special Thanks" ) ); + creditList.Append( creditInfo_t( 0, "Jason Bergman" ) ); + creditList.Append( creditInfo_t( 0, "Darren Chukitus" ) ); + creditList.Append( creditInfo_t( 0, "Matt Dickenson" ) ); + creditList.Append( creditInfo_t( 0, "Will Noble" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "ZENIMAX MEDIA" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Chairman & CEO" ) ); + creditList.Append( creditInfo_t( 0, "Robert Altman" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "President" ) ); + creditList.Append( creditInfo_t( 0, "Ernie Del" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "EVP & COO" ) ); + creditList.Append( creditInfo_t( 0, "Jamie Leder" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "EVP & CFO" ) ); + creditList.Append( creditInfo_t( 0, "Cindy Tallent" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "EVP Legal & Secretary" ) ); + creditList.Append( creditInfo_t( 0, "Grif Lesher" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "SVP Finance & Controller" ) ); + creditList.Append( creditInfo_t( 0, "Denise Kidd" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Legal Lead" ) ); + creditList.Append( creditInfo_t( 0, "Joshua Gillespie" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Legal" ) ); + creditList.Append( creditInfo_t( 0, "Diana Bender" ) ); + creditList.Append( creditInfo_t( 0, "Adam Carter" ) ); + creditList.Append( creditInfo_t( 0, "Candice Garner-Groves" ) ); + creditList.Append( creditInfo_t( 0, "Marcia Mitnick" ) ); + creditList.Append( creditInfo_t( 0, "Amy Yeung" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "VP, Information Technology" ) ); + creditList.Append( creditInfo_t( 0, "Steve Bloom" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Information Technology" ) ); + creditList.Append( creditInfo_t( 0, "Rob Havlovick" ) ); + creditList.Append( creditInfo_t( 0, "Nicholas Lea" ) ); + creditList.Append( creditInfo_t( 0, "Drew McCartney" ) ); + creditList.Append( creditInfo_t( 0, "Josh Mosby" ) ); + creditList.Append( creditInfo_t( 0, "Joseph Owens" ) ); + creditList.Append( creditInfo_t( 0, "Paul Tuttle" ) ); + creditList.Append( creditInfo_t( 0, "Keelian Wardle" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Director of Global HR" ) ); + creditList.Append( creditInfo_t( 0, "Tammy Boyd-Shumway" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Human Resources" ) ); + creditList.Append( creditInfo_t( 0, "Michelle Cool" ) ); + creditList.Append( creditInfo_t( 0, "Andrea Glinski" ) ); + creditList.Append( creditInfo_t( 0, "Katrina Lang" ) ); + creditList.Append( creditInfo_t( 0, "Valery Ridore" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Administration" ) ); + creditList.Append( creditInfo_t( 0, "Melissa Ayala" ) ); + creditList.Append( creditInfo_t( 0, "Brittany Bezawada-Joseph" ) ); + creditList.Append( creditInfo_t( 0, "Rosanna Campanile " ) ); + creditList.Append( creditInfo_t( 0, "Katherine Edwards" ) ); + creditList.Append( creditInfo_t( 0, "Douglas Fredrick " ) ); + creditList.Append( creditInfo_t( 0, "Jon Freund" ) ); + creditList.Append( creditInfo_t( 0, "Ken Garcia " ) ); + creditList.Append( creditInfo_t( 0, "Gerard Garnica" ) ); + creditList.Append( creditInfo_t( 0, "Betty Kouatelay" ) ); + creditList.Append( creditInfo_t( 0, "Ho Joong Lee" ) ); + creditList.Append( creditInfo_t( 0, "Barb Manning" ) ); + creditList.Append( creditInfo_t( 0, "Stephane Marquis" ) ); + creditList.Append( creditInfo_t( 0, "Michael Masciola" ) ); + creditList.Append( creditInfo_t( 0, "Tanuja Mistry" ) ); + creditList.Append( creditInfo_t( 0, "Rissa Monzano" ) ); + creditList.Append( creditInfo_t( 0, "Patrick Nolan " ) ); + creditList.Append( creditInfo_t( 0, "Patti Pulupa" ) ); + creditList.Append( creditInfo_t( 0, "Dave Rasmussen " ) ); + creditList.Append( creditInfo_t( 0, "Heather Spurrier" ) ); + creditList.Append( creditInfo_t( 0, "Claudia Umana" ) ); + creditList.Append( creditInfo_t( 0, "Melissa Washabaugh" ) ); + creditList.Append( creditInfo_t( 0, "Eric Weis" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Administrative Assistants" ) ); + creditList.Append( creditInfo_t( 0, "Bernice Guice" ) ); + creditList.Append( creditInfo_t( 0, "Paula Kasey" ) ); + creditList.Append( creditInfo_t( 0, "Shana Reed" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Executive Chef" ) ); + creditList.Append( creditInfo_t( 0, "Kenny McDonald" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "ZENIMAX EUROPE" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European Managing Director" ) ); + creditList.Append( creditInfo_t( 0, "Sean Brennan" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European Marketing & PR Director" ) ); + creditList.Append( creditInfo_t( 0, "Sarah Seaby" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European Sales Director" ) ); + creditList.Append( creditInfo_t( 0, "Paul Oughton" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Director" ) ); + creditList.Append( creditInfo_t( 0, "Greg Baverstock" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Finance Director" ) ); + creditList.Append( creditInfo_t( 0, "Robert Ford" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European Brand Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Thach Quach" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European Brand Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Alex Price" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European PR Manager" ) ); + creditList.Append( creditInfo_t( 0, "Alistair Hatch" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "European Assistant PR Manager" ) ); + creditList.Append( creditInfo_t( 0, "Nicholas Heller" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "International Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Rosemarie Dalton" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Manager Trade Shows/Events" ) ); + creditList.Append( creditInfo_t( 0, "Gareth Swann" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "UK Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Gregory Weller" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "UK Sales Manager" ) ); + creditList.Append( creditInfo_t( 0, "Gethyn Deakins" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Creative Services Artist" ) ); + creditList.Append( creditInfo_t( 0, "Morgan Gibbons" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Legal Counsel" ) ); + creditList.Append( creditInfo_t( 0, "Adam Carter" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Localisation Director" ) ); + creditList.Append( creditInfo_t( 0, "Harald Simon" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Localisation Project Manager" ) ); + creditList.Append( creditInfo_t( 0, "Ruth Granados Garcia" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Administrator" ) ); + creditList.Append( creditInfo_t( 0, "Heather Clarke" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Operations Manager" ) ); + creditList.Append( creditInfo_t( 0, "Isabelle Midrouillet" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Operations Coordinator" ) ); + creditList.Append( creditInfo_t( 0, "David Gordon" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Financial Controller" ) ); + creditList.Append( creditInfo_t( 0, "Paul New" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Accounts Assistant" ) ); + creditList.Append( creditInfo_t( 0, "Charlotte Ovens" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Contracts Administrator/Paralegal" ) ); + creditList.Append( creditInfo_t( 0, "Katie Brooks" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "IT Manager" ) ); + creditList.Append( creditInfo_t( 0, "Joseph Owens" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Office Manager" ) ); + creditList.Append( creditInfo_t( 0, "Angela Clement" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "ZENIMAX FRANCE" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "General Manager" ) ); + creditList.Append( creditInfo_t( 0, "Julie Chalmette" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Manager" ) ); + creditList.Append( creditInfo_t( 0, "Yvan Rault" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Key Account Manager" ) ); + creditList.Append( creditInfo_t( 0, "Gaelle Gombert" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Trade Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Laurent Chatain" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Geraldine Mazot" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "PR Manager" ) ); + creditList.Append( creditInfo_t( 0, "Jerome Firon" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Finance Manager" ) ); + creditList.Append( creditInfo_t( 0, "Cecile De Freitas" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Finance Assistant" ) ); + creditList.Append( creditInfo_t( 0, "Adeline Nonis" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "ZENIMAX GERMANY" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Managing Director" ) ); + creditList.Append( creditInfo_t( 0, "Frank Matzke" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Director" ) ); + creditList.Append( creditInfo_t( 0, "Thomas Huber" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Head of Marketing & PR" ) ); + creditList.Append( creditInfo_t( 0, "Marcel Jung" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Product Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Stefan Dettmering" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "PR Manager" ) ); + creditList.Append( creditInfo_t( 0, "Peter Langhofer" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Trade Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Andrea Reuth" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Key Account Manager" ) ); + creditList.Append( creditInfo_t( 0, "Juergen Pahl" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Finance Manager" ) ); + creditList.Append( creditInfo_t( 0, "Joern Hoehling" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales and Office Administrator" ) ); + creditList.Append( creditInfo_t( 0, "Christiane Jauss" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "ZENIMAX BENELUX" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "General Manager" ) ); + creditList.Append( creditInfo_t( 0, "Menno Eijck" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Manager" ) ); + creditList.Append( creditInfo_t( 0, "Stefan Koppers" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Marketing Manager" ) ); + creditList.Append( creditInfo_t( 0, "Jurgen Stirnweis" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "PR Manager" ) ); + creditList.Append( creditInfo_t( 0, "Maikel van Dijk" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales and Marketing Administrator" ) ); + creditList.Append( creditInfo_t( 0, "Pam van Griethuysen" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 3, "ZENIMAX ASIA" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "General Manager" ) ); + creditList.Append( creditInfo_t( 0, "Tetsu Takahashi" ) ); + creditList.Append( creditInfo_t()); + creditList.Append( creditInfo_t( 1, "Localization Producer " ) ); + creditList.Append( creditInfo_t( 0, "Kei Iwamoto" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Associate Producer " ) ); + creditList.Append( creditInfo_t( 0, "Takayuki Tanaka" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Product Manager " ) ); + creditList.Append( creditInfo_t( 0, "Seigen Ko" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Localization Programmer" ) ); + creditList.Append( creditInfo_t( 0, "Masayuki Nagahashi" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Marketing and PR Manager " ) ); + creditList.Append( creditInfo_t( 0, "Eiichi Yaji" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Sales Manager" ) ); + creditList.Append( creditInfo_t( 0, "Hiroaki Yanagiguchi" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Office Manager" ) ); + creditList.Append( creditInfo_t( 0, "Myongsuk Rim" ) ); + creditList.Append( creditInfo_t() ); + creditList.Append( creditInfo_t( 1, "Web Director" ) ); + creditList.Append( creditInfo_t( 0, "Tanaka Keisuke" ) ); + creditList.Append( creditInfo_t( 0, "Kosuke Fujita" ) ); +}; + +/* +======================== +idMenuScreen_Shell_Credits::Initialize +======================== +*/ +void idMenuScreen_Shell_Credits::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuCredits" ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_02305" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + SetupCreditList(); +} + +/* +======================== +idMenuScreen_Shell_Credits::Update +======================== +*/ +void idMenuScreen_Shell_Credits::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + bool complete = false; + if ( shell != NULL ) { + complete = shell->GetGameComplete(); + } + + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + if ( !complete ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + } else { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_continue"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + } + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_02218" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Credits::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Credits::ShowScreen( const mainMenuTransition_t transitionType ) { + + if ( menuData != NULL ) { + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + bool complete = false; + if ( shell != NULL ) { + complete = shell->GetGameComplete(); + } + + if ( complete ) { + menuData->PlaySound( GUI_SOUND_MUSIC ); + } + } + + idMenuScreen::ShowScreen( transitionType ); + creditIndex = 0; + UpdateCredits(); +} + +/* +======================== +idMenuScreen_Shell_Credits::HideScreen +======================== +*/ +void idMenuScreen_Shell_Credits::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Credits::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Credits::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_CREDITS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + bool complete = false; + if ( shell != NULL ) { + complete = shell->GetGameComplete(); + } + + if ( complete ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + } else { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuScreen_Shell_Credits::UpdateCredits +======================== +*/ +void idMenuScreen_Shell_Credits::UpdateCredits() { + + if ( menuData == NULL || GetSWFObject() == NULL ) { + return; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_CREDITS && menuData->NextScreen() != SHELL_AREA_CREDITS ) { + return; + } + + if ( creditIndex >= creditList.Num() + NUM_CREDIT_LINES ) { + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + bool complete = false; + if ( shell != NULL ) { + complete = shell->GetGameComplete(); + } + + if ( complete ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + } else { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + } + return; + } + + idSWFScriptObject * options = GetSWFObject()->GetRootObject().GetNestedObj( "menuCredits", "info", "options" ); + if ( options != NULL ) { + for ( int i = 15; i >= 0; --i ) { + int curIndex = creditIndex - i; + idSWFTextInstance * heading = options->GetNestedText( va( "item%d", 15 - i ), "heading" ); + idSWFTextInstance * subHeading = options->GetNestedText( va( "item%d", 15 - i ), "subHeading" ); + idSWFTextInstance * title = options->GetNestedText( va( "item%d", 15 - i ), "title" ); + idSWFTextInstance * txtEntry = options->GetNestedText( va( "item%d", 15 - i ), "entry" ); + + if ( curIndex >= 0 && curIndex < creditList.Num() ) { + + int type = creditList[ curIndex ].type; + idStr entry = creditList[ curIndex ].entry; + + if ( heading ) { + heading->SetText( type == 3 ? entry : "" ); + heading->SetStrokeInfo( true ); + } + + if ( subHeading ) { + subHeading->SetText( type == 2 ? entry : "" ); + subHeading->SetStrokeInfo( true ); + } + + if ( title ) { + title->SetText( type == 1 ? entry : "" ); + title->SetStrokeInfo( true ); + } + + if ( txtEntry ) { + txtEntry->SetText( type == 0 ? entry : "" ); + txtEntry->SetStrokeInfo( true ); + } + + } else { + + if ( heading ) { + heading->SetText( "" ); + } + + if ( subHeading ) { + subHeading->SetText( "" ); + } + + if ( txtEntry ) { + txtEntry->SetText( "" ); + } + + if ( title ) { + title->SetText( "" ); + } + + } + } + if ( options->GetSprite() ) { + options->GetSprite()->PlayFrame( "roll" ); + } + } + + creditIndex++; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Dev.cpp b/neo/d3xp/menus/MenuScreen_Shell_Dev.cpp new file mode 100644 index 00000000..40f731cd --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Dev.cpp @@ -0,0 +1,268 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_DEV_OPTIONS = 8; + +/* +======================== +idMenuScreen_Shell_Dev::Initialize +======================== +*/ +void idMenuScreen_Shell_Dev::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuSettings" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_DEV_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + + while ( options->GetChildren().Num() < NUM_DEV_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "MAIN MENU" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + SetupDevOptions(); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Dev::SetupDevOptions +======================== +*/ +void idMenuScreen_Shell_Dev::SetupDevOptions() { + + devOptions.Clear(); + + devOptions.Append( devOption_t( "game/mars_city1", "Mars City 1" ) ); + devOptions.Append( devOption_t( "game/mc_underground", "MC Underground" ) ); + devOptions.Append( devOption_t( "game/mars_city2", "Mars City 2" ) ); + devOptions.Append( devOption_t( "game/admin", "Admin" ) ); + devOptions.Append( devOption_t( "game/alphalabs1", "Alpha Labs 1" ) ); + devOptions.Append( devOption_t( "game/alphalabs2", "Alpha Labs 2" ) ); + devOptions.Append( devOption_t( "game/alphalabs3", "Alpha Labs 3" ) ); + devOptions.Append( devOption_t( "game/alphalabs4", "Alpha Labs 4" ) ); + devOptions.Append( devOption_t( "game/enpro", "Enpro" ) ); + devOptions.Append( devOption_t( "game/commoutside", "Comm outside" ) ); + devOptions.Append( devOption_t( "game/comm1", "Comm 1" ) ); + devOptions.Append( devOption_t( "game/recycling1", "Recycling 1" ) ); + devOptions.Append( devOption_t( "game/recycling2", "Recycling 2" ) ); + devOptions.Append( devOption_t( "game/monorail", "Monorail" ) ); + devOptions.Append( devOption_t( "game/delta1", "Delta Labs 1" ) ); + devOptions.Append( devOption_t( "game/delta2a", "Delta Labs 2a" ) ); + devOptions.Append( devOption_t( "game/delta2b", "Delta Labs 2b" ) ); + devOptions.Append( devOption_t( "game/delta3", "Delta Labs 3" ) ); + devOptions.Append( devOption_t( "game/delta4", "Delta Labs 4" ) ); + devOptions.Append( devOption_t( "game/hell1", "Hell 1" ) ); + devOptions.Append( devOption_t( "game/delta5", "Delta Labs 5" ) ); + devOptions.Append( devOption_t( "game/cpu", "CPU" ) ); + devOptions.Append( devOption_t( "game/cpuboss", "CPU Boss" ) ); + devOptions.Append( devOption_t( "game/site3", "Site 3" ) ); + devOptions.Append( devOption_t( "game/caverns1", "Caverns 1" ) ); + devOptions.Append( devOption_t( "game/caverns2", "Caverns 2" ) ); + devOptions.Append( devOption_t( "game/hellhole", "Hell Hole" ) ); + devOptions.Append( devOption_t( NULL, "-DOOM 3 Expansion-" ) ); + devOptions.Append( devOption_t( "game/erebus1", "Erebus 1" ) ); + devOptions.Append( devOption_t( "game/erebus2", "Erebus 2" ) ); + devOptions.Append( devOption_t( "game/erebus3", "Erebus 3" ) ); + devOptions.Append( devOption_t( "game/erebus4", "Erebus 4" ) ); + devOptions.Append( devOption_t( "game/erebus5", "Erebus 5" ) ); + devOptions.Append( devOption_t( "game/erebus6", "Erebus 6" ) ); + devOptions.Append( devOption_t( "game/phobos1", "Phobos 1" ) ); + devOptions.Append( devOption_t( "game/phobos2", "Phobos 2" ) ); + devOptions.Append( devOption_t( "game/phobos3", "Phobos 3" ) ); + devOptions.Append( devOption_t( "game/phobos4", "Phobos 4" ) ); + devOptions.Append( devOption_t( "game/deltax", "Delta X" ) ); + devOptions.Append( devOption_t( "game/hell", "Hell" ) ); + devOptions.Append( devOption_t( NULL, "-Lost Missions-" ) ); + devOptions.Append( devOption_t( "game/le_enpro1", "Enpro 1" ) ); + devOptions.Append( devOption_t( "game/le_enpro2", "Enpro 2" ) ); + devOptions.Append( devOption_t( "game/le_underground", "Undeground" ) ); + devOptions.Append( devOption_t( "game/le_underground2", "Undeground 2" ) ); + devOptions.Append( devOption_t( "game/le_exis1", "Exis 1" ) ); + devOptions.Append( devOption_t( "game/le_exis2", "Exis 2" ) ); + devOptions.Append( devOption_t( "game/le_hell", "Hell" ) ); + devOptions.Append( devOption_t( "game/le_hell_post", "Hell Post" ) ); + devOptions.Append( devOption_t( NULL, "-Test Maps-" ) ); + devOptions.Append( devOption_t( "game/pdas", "PDAs" ) ); + devOptions.Append( devOption_t( "testmaps/test_box", "Box" ) ); + + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + + for ( int i = 0; i < devOptions.Num(); ++i ) { + idList< idStr > option; + option.Append( devOptions[ i ].name ); + menuOptions.Append( option ); + } + + options->SetListData( menuOptions ); +} + +/* +======================== +idMenuScreen_Shell_Dev::Update +======================== +*/ +void idMenuScreen_Shell_Dev::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "DEV" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Dev::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Dev::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Dev::HideScreen +======================== +*/ +void idMenuScreen_Shell_Dev::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Dev::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Dev::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_DEV ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( options->GetFocusIndex() != selectionIndex - options->GetViewOffset() ) { + options->SetFocusIndex( selectionIndex ); + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + } + + int mapIndex = options->GetViewIndex(); + if ( ( mapIndex < devOptions.Num() ) && ( devOptions[ mapIndex ].map != NULL ) ) { + cmdSystem->AppendCommandText( va( "devmap %s\n", devOptions[ mapIndex ].map ) ); + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Difficulty.cpp b/neo/d3xp/menus/MenuScreen_Shell_Difficulty.cpp new file mode 100644 index 00000000..ac28aee2 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Difficulty.cpp @@ -0,0 +1,293 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SETTING_OPTIONS = 8; +extern idCVar g_nightmare; +extern idCVar g_roeNightmare; +extern idCVar g_leNightmare; +extern idCVar g_skill; +/* +======================== +idMenuScreen_Shell_Difficulty::Initialize +======================== +*/ +void idMenuScreen_Shell_Difficulty::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuDifficulty" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + option.Append( "#str_04089" ); // Easy + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_04091" ); // Medium + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_04093" ); // Hard + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_02357" ); // Nightmare + menuOptions.Append( option ); + + options->SetListData( menuOptions ); + options->SetNumVisibleOptions( NUM_SETTING_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + const char * tips[] = { "#str_02358", "#str_02360", "#str_02362", "#str_02364" }; + + while ( options->GetChildren().Num() < NUM_SETTING_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + + if ( options->GetChildren().Num() < menuOptions.Num() ) { + buttonWidget->SetDescription( tips[options->GetChildren().Num()] ); + } + + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_02207" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Difficulty::Update +======================== +*/ +void idMenuScreen_Shell_Difficulty::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_04088" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Difficulty::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Difficulty::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); + + nightmareUnlocked = false; + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + int type = 0; + if ( shell != NULL ) { + type = shell->GetNewGameType(); + } + + if ( type == 0 ) { + nightmareUnlocked = g_nightmare.GetBool(); + } else if ( type == 1 ) { + nightmareUnlocked = g_roeNightmare.GetBool(); + } else if ( type == 2 ) { + nightmareUnlocked = g_leNightmare.GetBool(); + } + + int skill = Max( 0, g_skill.GetInteger() ); + if ( !nightmareUnlocked ) { + options->GetChildByIndex( 3 ).SetState( WIDGET_STATE_DISABLED ); + skill = Min( skill, 2 ); + } else { + options->GetChildByIndex( 3 ).SetState( WIDGET_STATE_NORMAL ); + skill = Min( skill, 3 ); + } + options->SetFocusIndex( skill ); + options->SetViewIndex( options->GetViewOffset() + skill ); +} + +/* +======================== +idMenuScreen_Shell_Difficulty::HideScreen +======================== +*/ +void idMenuScreen_Shell_Difficulty::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Difficulty::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Difficulty::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_DIFFICULTY ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE: { + + if ( parms.Num() == 0 ) { + return true; + } + + if ( options == NULL ) { + return true; + } + + int dir = parms[ 0 ].ToInteger(); + int scroll = dir; + + bool nightmareEnabled = nightmareUnlocked; + int curIndex = options->GetFocusIndex(); + + if ( ( curIndex + scroll < 0 && !nightmareEnabled ) || ( curIndex + scroll == 3 && !nightmareEnabled ) ) { + scroll *= 2; + options->Scroll( scroll, true ); + } else { + options->Scroll( scroll ); + } + + return true; + } + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_NEW_GAME, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex == 3 && !nightmareUnlocked ) { + return true; + } + + if ( options->GetFocusIndex() != selectionIndex ) { + options->SetFocusIndex( selectionIndex ); + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + } + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + int type = 0; + if ( shell != NULL ) { + type = shell->GetNewGameType(); + } + + g_skill.SetInteger( selectionIndex ); + + idMenuHandler_Shell * shellMgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( shellMgr ) { + if ( type == 0 ) { + shellMgr->ShowDoomIntro(); + } else if ( type == 1 ) { + shellMgr->ShowROEIntro(); + } else if ( type == 2 ) { + shellMgr->ShowLEIntro(); + } + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_GameLobby.cpp b/neo/d3xp/menus/MenuScreen_Shell_GameLobby.cpp new file mode 100644 index 00000000..408780f0 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_GameLobby.cpp @@ -0,0 +1,706 @@ + +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_LOBBY_OPTIONS = 8; + +extern idCVar net_inviteOnly; + +enum gameLobbyCmd_t { + GAME_CMD_START, + GAME_CMD_INVITE, + GAME_CMD_SETTINGS, + GAME_CMD_TOGGLE_PRIVACY, +}; + +/* +======================== +idMenuScreen_Shell_GameLobby::Initialize +======================== +*/ +void idMenuScreen_Shell_GameLobby::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuGameLobby" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_LOBBY_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + while ( options->GetChildren().Num() < NUM_LOBBY_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + lobby = new (TAG_SWF) idMenuWidget_LobbyList(); + lobby->SetNumVisibleOptions( 8 ); + lobby->SetSpritePath( GetSpritePath(), "options" ); + lobby->SetWrappingAllowed( true ); + lobby->Initialize( data ); + while ( lobby->GetChildren().Num() < 8 ) { + idMenuWidget_LobbyButton * const buttonWidget = new (TAG_SWF) idMenuWidget_LobbyButton(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_SELECT_GAMERTAG, lobby->GetChildren().Num() ); + buttonWidget->AddEventAction( WIDGET_EVENT_COMMAND ).Set( WIDGET_ACTION_MUTE_PLAYER, lobby->GetChildren().Num() ); + buttonWidget->Initialize( data ); + lobby->AddChild( buttonWidget ); + } + AddChild( lobby ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_multiplayer" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_GameLobby::Update +======================== +*/ +void idMenuScreen_Shell_GameLobby::Update() { + + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + if ( lobby != NULL ) { + + if ( activeLobby.GetNumActiveLobbyUsers() != 0 ) { + if ( lobby->GetFocusIndex() >= activeLobby.GetNumActiveLobbyUsers() ) { + lobby->SetFocusIndex( activeLobby.GetNumActiveLobbyUsers() - 1 ); + lobby->SetViewIndex( lobby->GetViewOffset() + lobby->GetFocusIndex() ); + } + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_multiplayer" ); // MULTIPLAYER + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( privateGameLobby && options != NULL ) { + + if ( session->GetActivePlatformLobbyBase().IsHost() && !isHost ) { + + menuOptions.Clear(); + idList< idStr > option; + + isHost = true; + isPeer = false; + + option.Append( "#str_swf_start_match" ); // Start match + menuOptions.Append( option ); + option.Clear(); + + option.Append( "#str_swf_match_settings" ); // Match Settings + menuOptions.Append( option ); + option.Clear(); + + option.Append( "#str_swf_invite_only" ); // Toggle privacy + menuOptions.Append( option ); + option.Clear(); + + option.Append( "#str_swf_invite_friends" ); // Invite Friends + menuOptions.Append( option ); + option.Clear(); + + idMenuWidget_Button * buttonWidget = NULL; + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAME_CMD_START, 0 ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_quick_start_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAME_CMD_SETTINGS, 1 ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_match_setting_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAME_CMD_TOGGLE_PRIVACY, 2 ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_toggle_privacy_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAME_CMD_INVITE, 3 ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_invite_desc" ); + } + index++; + + options->SetListData( menuOptions ); + + } else if ( session->GetActivePlatformLobbyBase().IsPeer() ) { + + if ( !isPeer ) { + + menuOptions.Clear(); + idList< idStr > option; + + option.Append( "#str_swf_invite_friends" ); // Invite Friends + menuOptions.Append( option ); + option.Clear(); + + idMenuWidget_Button * buttonWidget = NULL; + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAME_CMD_INVITE, 0 ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_invite_desc" ); + } + + options->SetListData( menuOptions ); + } + + isPeer = true; + isHost = false; + } + } + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_view_profile"; + } + buttonInfo->action.Set( WIDGET_ACTION_SELECT_GAMERTAG ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + + lobbyUserID_t luid; + if ( isHost && CanKickSelectedPlayer( luid ) ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY4 ); + buttonInfo->label = "#str_swf_kick"; + buttonInfo->action.Set( WIDGET_ACTION_JOY4_ON_PRESS ); + } + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_GameLobby::ShowScreen +======================== +*/ +void idMenuScreen_Shell_GameLobby::ShowScreen( const mainMenuTransition_t transitionType ) { + + if ( options != NULL ) { + options->SetFocusIndex( 0 ); + options->SetViewIndex( 0 ); + } + + isHost = false; + isPeer = false; + + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + + // Make sure map name is up to date. + if ( matchParameters.gameMap >= 0 ) { + matchParameters.mapName = common->GetMapList()[ matchParameters.gameMap ].mapFile; + } + + privateGameLobby = MatchTypeIsPrivate( matchParameters.matchFlags ); + + if ( !privateGameLobby ) { // Public Game Lobby + menuOptions.Clear(); + idList< idStr > option; + + if ( options != NULL ) { + option.Append( "#str_swf_invite_friends" ); // Invite Friends + menuOptions.Append( option ); + option.Clear(); + + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAME_CMD_INVITE, 0 ); + idMenuWidget_Button * buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_invite_desc" ); + } + + options->SetListData( menuOptions ); + } + + longCountdown = Sys_Milliseconds() + WAIT_START_TIME_LONG; + longCountRemaining = longCountdown; + shortCountdown = Sys_Milliseconds() + WAIT_START_TIME_SHORT; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFSpriteInstance * waitTime = GetSprite()->GetScriptObject()->GetNestedSprite( "waitTime" ); + if ( waitTime != NULL ) { + waitTime->SetVisible( !privateGameLobby ); + } + } + + idMenuScreen::ShowScreen( transitionType ); + + if ( lobby != NULL ) { + lobby->SetFocusIndex( 0 ); + } + + session->UpdateMatchParms( matchParameters ); +} + +/* +======================== +idMenuScreen_Shell_GameLobby::HideScreen +======================== +*/ +void idMenuScreen_Shell_GameLobby::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_GameLobby::CanKickSelectedPlayer +======================== +*/ +bool idMenuScreen_Shell_GameLobby::CanKickSelectedPlayer( lobbyUserID_t & luid ) { + + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + const int playerId = lobby->GetFocusIndex(); + + // can't kick yourself + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + luid = activeLobby.GetLobbyUserIdByOrdinal( playerId ); + if ( session->GetSignInManager().GetMasterLocalUser() == activeLobby.GetLocalUserFromLobbyUser( luid ) ) { + return false; + } + + return true; +} + +/* +======================== +idMenuScreen_Shell_GameLobby::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_GameLobby::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_GAME_LOBBY ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_JOY4_ON_PRESS: { + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + lobbyUserID_t luid; + if ( CanKickSelectedPlayer( luid ) ) { + activeLobby.KickLobbyUser( luid ); + } + return true; + } + case WIDGET_ACTION_GO_BACK: { + class idSWFScriptFunction_Accept : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Accept() { } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_LEAVE_LOBBY_RET_NEW_PARTY ); + session->Cancel(); + + return idSWFScriptVar(); + } + }; + class idSWFScriptFunction_Cancel : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Cancel() { } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_LEAVE_LOBBY_RET_NEW_PARTY ); + return idSWFScriptVar(); + } + }; + + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + + if( activeLobby.GetNumActiveLobbyUsers() > 1 ) { + common->Dialog().AddDialog( GDM_LEAVE_LOBBY_RET_NEW_PARTY, DIALOG_ACCEPT_CANCEL, new (TAG_SWF) idSWFScriptFunction_Accept(), new (TAG_SWF) idSWFScriptFunction_Cancel(), false ); + } else { + session->Cancel(); + } + + return true; + } + case WIDGET_ACTION_MUTE_PLAYER: { + + if ( parms.Num() != 1 ) { + return true; + } + + int index = parms[0].ToInteger(); + + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + lobbyUserID_t luid = activeLobby.GetLobbyUserIdByOrdinal( index ); + if ( luid.IsValid() ) { + session->ToggleLobbyUserVoiceMute( luid ); + } + + return true; + } + case WIDGET_ACTION_COMMAND: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 1 ) { + selectionIndex = parms[1].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + switch ( parms[0].ToInteger() ) { + case GAME_CMD_START: { + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * const >( menuData ); + if ( handler != NULL ) { + handler->SetTimeRemaining( 0 ); + } + break; + } + case GAME_CMD_SETTINGS: { + menuData->SetNextScreen( SHELL_AREA_MATCH_SETTINGS, MENU_TRANSITION_SIMPLE ); + break; + } + case GAME_CMD_TOGGLE_PRIVACY: { + idMatchParameters matchParameters = idMatchParameters( session->GetActivePlatformLobbyBase().GetMatchParms() ); + matchParameters.matchFlags ^= MATCH_INVITE_ONLY; + session->UpdateMatchParms( matchParameters ); + int bitSet = ( matchParameters.matchFlags & MATCH_INVITE_ONLY ); + net_inviteOnly.SetBool( bitSet != 0 ? true : false ); + + // Must update the party parameters too for Xbox JSIP to work in game lobbies. + idMatchParameters partyParms = session->GetPartyLobbyBase().GetMatchParms(); + if ( MatchTypeInviteOnly( matchParameters.matchFlags ) ) { + partyParms.matchFlags |= MATCH_INVITE_ONLY; + } else { + partyParms.matchFlags &= ~MATCH_INVITE_ONLY; + } + session->UpdatePartyParms( partyParms ); + + break; + } + case GAME_CMD_INVITE: { + if ( session->GetActivePlatformLobbyBase().IsLobbyFull() ) { + common->Dialog().AddDialog( GDM_CANNOT_INVITE_LOBBY_FULL, DIALOG_CONTINUE, NULL, NULL, true, __FUNCTION__, __LINE__, false ); + return true; + } + + InvitePartyOrFriends(); + break; + } + } + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + case WIDGET_ACTION_SELECT_GAMERTAG: { + int selectionIndex = lobby->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != lobby->GetFocusIndex() ) { + lobby->SetViewIndex( lobby->GetViewOffset() + selectionIndex ); + lobby->SetFocusIndex( selectionIndex ); + return true; + } + + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + lobbyUserID_t luid = activeLobby.GetLobbyUserIdByOrdinal( selectionIndex ); + if ( luid.IsValid() ) { + session->ShowLobbyUserGamerCardUI( luid ); + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuScreen_Shell_GameLobby::UpdateLobby +======================== +*/ +void idMenuScreen_Shell_GameLobby::UpdateLobby() { + + if ( menuData != NULL && menuData->ActiveScreen() != SHELL_AREA_GAME_LOBBY ) { + return; + } + + // Keep this menu in sync with the session host/peer status. + if ( session->GetActivePlatformLobbyBase().IsHost() && !isHost ) { + Update(); + } + + if ( session->GetActivePlatformLobbyBase().IsPeer() && !isPeer ) { + Update(); + } + + if ( !privateGameLobby ) { + int ms = 0; + if ( session->GetActivePlatformLobbyBase().IsHost() ) { + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * const >( menuData ); + if ( handler != NULL ) { + if ( session->GetActivePlatformLobbyBase().IsLobbyFull() ) { + longCountdown = Sys_Milliseconds() + longCountRemaining; + int timeRemaining = shortCountdown - Sys_Milliseconds(); + if ( timeRemaining < 0 ) { + timeRemaining = 0; + } + ms = (int) ceilf( timeRemaining / 1000.0f ); + handler->SetTimeRemaining( timeRemaining ); + } else if ( session->GetActivePlatformLobbyBase().GetNumLobbyUsers() > 1 ) { + int timeRemaining = longCountdown - Sys_Milliseconds(); + if ( timeRemaining > WAIT_START_TIME_SHORT ) { + shortCountdown = Sys_Milliseconds() + WAIT_START_TIME_SHORT; + } else { + shortCountdown = timeRemaining; + } + longCountRemaining = timeRemaining; + if ( timeRemaining < 0 ) { + timeRemaining = 0; + } + ms = (int) ceilf( timeRemaining / 1000.0f ); + handler->SetTimeRemaining( timeRemaining ); + } else { + ms = 0; + longCountdown = Sys_Milliseconds() + WAIT_START_TIME_LONG; + longCountRemaining = longCountdown; + shortCountdown = Sys_Milliseconds() + WAIT_START_TIME_SHORT; + handler->SetTimeRemaining( longCountRemaining ); + } + } + } else { + if ( menuData != NULL ) { + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * const >( menuData ); + if ( handler != NULL ) { + ms = (int) ceilf( handler->GetTimeRemaining() / 1000.0f ); + } + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * waitTime = GetSprite()->GetScriptObject()->GetNestedText( "waitTime", "txtVal" ); + if ( waitTime != NULL ) { + idStr status; + if ( ms == 1 ) { + status = idLocalization::GetString( "#str_online_game_starts_in_second" ); + status.Replace( "", idStr( ms ) ); + waitTime->SetText( status ); + } else if ( ms > 0 && ms < 30 ) { + status = idLocalization::GetString( "#str_online_game_starts_in_seconds" ); + status.Replace( "", idStr( ms ) ); + waitTime->SetText( status ); + } else { + waitTime->SetText( "" ); + } + waitTime->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } + Update(); + + } else { + + if ( isPeer ) { + Update(); + } + + } + + if ( session->GetState() == idSession::GAME_LOBBY ) { + + if ( options != NULL ) { + if ( options->GetFocusIndex() >= options->GetTotalNumberOfOptions() && options->GetTotalNumberOfOptions() > 0 ) { + options->SetViewIndex( options->GetTotalNumberOfOptions() - 1 ); + options->SetFocusIndex( options->GetTotalNumberOfOptions() - 1 ); + } + } + + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + + idSWFTextInstance * mapName = GetSprite()->GetScriptObject()->GetNestedText( "matchInfo", "txtMapName" ); + idSWFTextInstance * modeName = GetSprite()->GetScriptObject()->GetNestedText( "matchInfo", "txtModeName" ); + + if ( mapName != NULL ){ + const idList< mpMap_t > maps = common->GetMapList(); + idStr name = idLocalization::GetString( maps[ idMath::ClampInt( 0, maps.Num() - 1, matchParameters.gameMap ) ].mapName ); + mapName->SetText( name ); + mapName->SetStrokeInfo( true ); + } + + if ( modeName != NULL ) { + const idStrList & modes = common->GetModeDisplayList(); + idStr mode = idLocalization::GetString( modes[ idMath::ClampInt( 0, modes.Num() - 1, matchParameters.gameMode ) ] ); + modeName->SetText( mode ); + modeName->SetStrokeInfo( true ); + } + + idSWFTextInstance * privacy = GetSprite()->GetScriptObject()->GetNestedText( "matchInfo", "txtPrivacy" ); + if ( privacy != NULL ) { + if ( isPeer || !privateGameLobby ) { + privacy->SetText( "" ); + } else { + int bitSet = ( matchParameters.matchFlags & MATCH_INVITE_ONLY ); + bool privacySet = ( bitSet != 0 ? true : false ); + if ( privacySet ) { + privacy->SetText( "#str_swf_privacy_closed" ); + privacy->SetStrokeInfo( true ); + } else if ( !privacySet ) { + privacy->SetText( "#str_swf_privacy_open" ); + privacy->SetStrokeInfo( true ); + } + } + } + + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user != NULL && options != NULL ) { + if ( user->IsInParty() && user->GetPartyCount() > 1 && !session->IsPlatformPartyInLobby() && menuOptions.Num() > 0 ) { + if ( menuOptions[ menuOptions.Num() - 1 ][0] != "#str_swf_invite_xbox_live_party" ) { + menuOptions[ menuOptions.Num() - 1 ][0] = "#str_swf_invite_xbox_live_party"; // invite Xbox LIVE party + options->SetListData( menuOptions ); + options->Update(); + } + } else if ( menuOptions.Num() > 0 ) { + if ( menuOptions[ menuOptions.Num() - 1 ][0] != "#str_swf_invite_friends" ) { + menuOptions[ menuOptions.Num() - 1 ][0] = "#str_swf_invite_friends"; // invite Xbox LIVE party + options->SetListData( menuOptions ); + options->Update(); + } + } + } + } + + // setup names for lobby; + if ( lobby != NULL ) { + idMenuHandler_Shell * mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( mgr != NULL ) { + mgr->UpdateLobby( lobby ); + lobby->Update(); + } + + if ( lobby->GetNumEntries() > 0 && lobby->GetFocusIndex() >= lobby->GetNumEntries() ) { + lobby->SetFocusIndex( lobby->GetNumEntries() - 1 ); + lobby->SetViewIndex( lobby->GetNumEntries() - 1 ); + } + } +} + + + diff --git a/neo/d3xp/menus/MenuScreen_Shell_GameOptions.cpp b/neo/d3xp/menus/MenuScreen_Shell_GameOptions.cpp new file mode 100644 index 00000000..521a05ed --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_GameOptions.cpp @@ -0,0 +1,371 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_GAME_OPTIONS_OPTIONS = 8; + +const float MIN_FOV = 80.0f; +const float MAX_FOV = 100.0f; + +const float MIN_FOV_GUN = 3.0f; +const float MAX_FOV_GUN = 0.0f; + +/* +======================== +idMenuScreen_Shell_GameOptions::Initialize +======================== +*/ +void idMenuScreen_Shell_GameOptions::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuGameOptions" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_GAME_OPTIONS_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_settings" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control; + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_swf_fov" ); + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_FOV ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_checkpoints" ); + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_CHECKPOINTS ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_02135" ); // Auto Weapon Switch + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_AUTO_SWITCH ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_02134" ); // Auto Weapon Reload + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_AUTO_RELOAD ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_aim_assist" ); // Aim Assist + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_AIM_ASSIST ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_04102" ); // Always Run + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_ALWAYS_SPRINT ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_flashlight_shadows" ); + control->SetDataSource( &systemData, idMenuDataSource_GameSettings::GAME_FIELD_FLASHLIGHT_SHADOWS ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_GameOptions::Update +======================== +*/ +void idMenuScreen_Shell_GameOptions::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_02129" ); // SYSTEM SETTINGS + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_GameOptions::ShowScreen +======================== +*/ +void idMenuScreen_Shell_GameOptions::ShowScreen( const mainMenuTransition_t transitionType ) { + systemData.LoadData(); + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_GameOptions::HideScreen +======================== +*/ +void idMenuScreen_Shell_GameOptions::HideScreen( const mainMenuTransition_t transitionType ) { + if ( systemData.IsDataChanged() ) { + systemData.CommitData(); + } + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_GameOptions::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_GameOptions::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_GAME_OPTIONS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + systemData.AdjustField( selectionIndex, 1 ); + options->Update(); + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +///////////////////////////////// +// SCREEN SETTINGS +///////////////////////////////// + +extern idCVar ui_autoSwitch; +extern idCVar ui_autoReload; +extern idCVar aa_targetAimAssistEnable; +extern idCVar in_alwaysRun; +extern idCVar g_checkpoints; +extern idCVar g_weaponShadows; + +/* +======================== +idMenuScreen_Shell_GameOptions::idMenuDataSource_AudioSettings::idMenuDataSource_AudioSettings +======================== +*/ +idMenuScreen_Shell_GameOptions::idMenuDataSource_GameSettings::idMenuDataSource_GameSettings() { + fields.SetNum( MAX_GAME_FIELDS ); + originalFields.SetNum( MAX_GAME_FIELDS ); +} + +/* +======================== +idMenuScreen_Shell_GameOptions::idMenuDataSource_AudioSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_GameOptions::idMenuDataSource_GameSettings::LoadData() { + fields[ GAME_FIELD_FOV ].SetInteger( g_fov.GetFloat() ); + fields[ GAME_FIELD_CHECKPOINTS ].SetBool( g_checkpoints.GetBool() ); + fields[ GAME_FIELD_AUTO_SWITCH ].SetBool( ui_autoSwitch.GetBool() ); + fields[ GAME_FIELD_AUTO_RELOAD ].SetBool( ui_autoReload.GetBool() ); + fields[ GAME_FIELD_AIM_ASSIST ].SetBool( aa_targetAimAssistEnable.GetBool() ); + fields[ GAME_FIELD_ALWAYS_SPRINT ].SetBool( in_alwaysRun.GetBool() ); + fields[ GAME_FIELD_FLASHLIGHT_SHADOWS ].SetBool( g_weaponShadows.GetBool() ); + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_GameOptions::idMenuDataSource_AudioSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_GameOptions::idMenuDataSource_GameSettings::CommitData() { + + g_fov.SetFloat( fields[ GAME_FIELD_FOV ].ToFloat() ); + g_gun_x.SetFloat( Lerp( MIN_FOV_GUN, MAX_FOV_GUN, ( fields[ GAME_FIELD_FOV ].ToFloat() - MIN_FOV ) / ( MAX_FOV - MIN_FOV ) ) ); + + g_checkpoints.SetBool( fields[ GAME_FIELD_CHECKPOINTS ].ToBool() ); + ui_autoSwitch.SetBool( fields[ GAME_FIELD_AUTO_SWITCH ].ToBool() ); + ui_autoReload.SetBool( fields[ GAME_FIELD_AUTO_RELOAD ].ToBool() ); + aa_targetAimAssistEnable.SetBool( fields[ GAME_FIELD_AIM_ASSIST ].ToBool() ); + in_alwaysRun.SetBool( fields[ GAME_FIELD_ALWAYS_SPRINT ].ToBool() ); + g_weaponShadows.SetBool( fields[ GAME_FIELD_FLASHLIGHT_SHADOWS ].ToBool() ); + + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + + // make the committed fields into the backup fields + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_GameOptions::idMenuDataSource_AudioSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_GameOptions::idMenuDataSource_GameSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + if ( fieldIndex == GAME_FIELD_FOV ) { + fields[ fieldIndex ].SetInteger( idMath::ClampInt( MIN_FOV, MAX_FOV, fields[ fieldIndex ].ToInteger() + adjustAmount * 5 ) ); + } else { + fields[ fieldIndex ].SetBool( !fields[ fieldIndex ].ToBool() ); + } +} + +/* +======================== +idMenuScreen_Shell_GameOptions::idMenuDataSource_AudioSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_GameOptions::idMenuDataSource_GameSettings::IsDataChanged() const { + + if ( fields[ GAME_FIELD_FOV ].ToInteger() != originalFields[ GAME_FIELD_FOV ].ToInteger() ) { + return true; + } + + if ( fields[ GAME_FIELD_CHECKPOINTS ].ToBool() != originalFields[ GAME_FIELD_CHECKPOINTS ].ToBool() ) { + return true; + } + + if ( fields[ GAME_FIELD_AUTO_SWITCH ].ToBool() != originalFields[ GAME_FIELD_AUTO_SWITCH ].ToBool() ) { + return true; + } + + if ( fields[ GAME_FIELD_AUTO_RELOAD ].ToBool() != originalFields[ GAME_FIELD_AUTO_RELOAD ].ToBool() ) { + return true; + } + + if ( fields[ GAME_FIELD_AIM_ASSIST ].ToBool() != originalFields[ GAME_FIELD_AIM_ASSIST ].ToBool() ) { + return true; + } + + if ( fields[ GAME_FIELD_ALWAYS_SPRINT ].ToBool() != originalFields[ GAME_FIELD_ALWAYS_SPRINT ].ToBool() ) { + return true; + } + + if ( fields[ GAME_FIELD_FLASHLIGHT_SHADOWS ].ToBool() != originalFields[ GAME_FIELD_FLASHLIGHT_SHADOWS ].ToBool() ) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Gamepad.cpp b/neo/d3xp/menus/MenuScreen_Shell_Gamepad.cpp new file mode 100644 index 00000000..b559aabe --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Gamepad.cpp @@ -0,0 +1,464 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_CONTROLS_OPTIONS = 8; + +enum gamepadMenuCmds_t { +#ifndef ID_PC + GAMEPAD_CMD_CONFIG, +#endif + GAMEPAD_CMD_LEFTY, + GAMEPAD_CMD_INVERT, + GAMEPAD_CMD_VIBRATE, + GAMEPAD_CMD_HOR_SENS, + GAMEPAD_CMD_VERT_SENS, + GAMEPAD_CMD_ACCELERATION, + GAMEPAD_CMD_THRESHOLD, +}; + +/* +======================== +idMenuScreen_Shell_Gamepad::Initialize +======================== +*/ +void idMenuScreen_Shell_Gamepad::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuGamepad" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_CONTROLS_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + idStr controls( idLocalization::GetString( "#str_04158" ) ); + controls.ToUpper(); + btnBack->SetLabel( controls ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control; +#ifndef ID_PC + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_BUTTON_TEXT ); + control->SetLabel( "#str_swf_gamepad_config" ); // Gamepad Configuration + control->SetDescription( "#str_swf_config_desc" ); + control->RegisterEventObserver( helpWidget ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_CONFIG ); + options->AddChild( control ); +#endif + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_lefty_flip" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_LEFTY ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_LEFTY ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_invert_gamepad" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_INVERT ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_INVERT ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_vibration" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_VIBRATE ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_VIBRATE ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_swf_hor_sens" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_HOR_SENS ); + control->SetupEvents( 2, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_HOR_SENS ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_swf_vert_sens" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_VERT_SENS ); + control->SetupEvents( 2, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_VERT_SENS ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_joy_gammaLook" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_ACCELERATION ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_ACCELERATION ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_joy_mergedThreshold" ); + control->SetDataSource( &gamepadData, idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_THRESHOLD ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, GAMEPAD_CMD_THRESHOLD ); + control->RegisterEventObserver( helpWidget ); + options->AddChild( control ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Gamepad::Update +======================== +*/ +void idMenuScreen_Shell_Gamepad::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_gamepad_heading" ); // CONTROLS + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Gamepad::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Gamepad::ShowScreen( const mainMenuTransition_t transitionType ) { + gamepadData.LoadData(); + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Gamepad::HideScreen +======================== +*/ +void idMenuScreen_Shell_Gamepad::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( gamepadData.IsDataChanged() ) { + gamepadData.CommitData(); + } + + if ( menuData != NULL ) { + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( handler != NULL ) { + handler->SetupPCOptions(); + } + } + + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Gamepad::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Gamepad::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_GAMEPAD ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_CONTROLS, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_COMMAND: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + switch ( parms[0].ToInteger() ) { +#ifndef ID_PC + case GAMEPAD_CMD_CONFIG: { + menuData->SetNextScreen( SHELL_AREA_CONTROLLER_LAYOUT, MENU_TRANSITION_SIMPLE ); + break; + } +#endif + case GAMEPAD_CMD_INVERT: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_INVERT, 1 ); + options->Update(); + break; + } + case GAMEPAD_CMD_LEFTY: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_LEFTY, 1 ); + options->Update(); + break; + } + case GAMEPAD_CMD_VIBRATE: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_VIBRATE, 1 ); + options->Update(); + break; + } + case GAMEPAD_CMD_HOR_SENS: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_HOR_SENS, 1 ); + options->Update(); + break; + } + case GAMEPAD_CMD_VERT_SENS: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_VERT_SENS, 1 ); + options->Update(); + break; + } + case GAMEPAD_CMD_ACCELERATION: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_ACCELERATION, 1 ); + options->Update(); + break; + } + case GAMEPAD_CMD_THRESHOLD: { + gamepadData.AdjustField( idMenuDataSource_GamepadSettings::GAMEPAD_FIELD_THRESHOLD, 1 ); + options->Update(); + break; + } + } + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +///////////////////////////////// +// SCREEN SETTINGS +///////////////////////////////// + +extern idCVar in_invertLook; +extern idCVar in_joystickRumble; +extern idCVar joy_pitchSpeed; +extern idCVar joy_yawSpeed; +extern idCVar joy_gammaLook; +extern idCVar joy_mergedThreshold; + +/* +======================== +idMenuScreen_Shell_Gamepad::idMenuDataSource_AudioSettings::idMenuDataSource_AudioSettings +======================== +*/ +idMenuScreen_Shell_Gamepad::idMenuDataSource_GamepadSettings::idMenuDataSource_GamepadSettings() { + fields.SetNum( MAX_GAMEPAD_FIELDS ); + originalFields.SetNum( MAX_GAMEPAD_FIELDS ); +} + +/* +======================== +idMenuScreen_Shell_Gamepad::idMenuDataSource_AudioSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_Gamepad::idMenuDataSource_GamepadSettings::LoadData() { + idPlayerProfile * profile = session->GetProfileFromMasterLocalUser(); + + fields[ GAMEPAD_FIELD_INVERT ].SetBool( in_invertLook.GetBool() ); + fields[ GAMEPAD_FIELD_LEFTY ].SetBool( profile ? profile->GetLeftyFlip() : false ); + fields[ GAMEPAD_FIELD_VIBRATE ].SetBool( in_joystickRumble.GetBool() ); + fields[ GAMEPAD_FIELD_HOR_SENS ].SetFloat( 100.0f * ( ( joy_yawSpeed.GetFloat() - 100.0f ) / 300.0f ) ); + fields[ GAMEPAD_FIELD_VERT_SENS ].SetFloat( 100.0f * ( ( joy_pitchSpeed.GetFloat() - 60.0f ) / 200.0f ) ); + fields[ GAMEPAD_FIELD_ACCELERATION ].SetBool( joy_gammaLook.GetBool() ); + fields[ GAMEPAD_FIELD_THRESHOLD ].SetBool( joy_mergedThreshold.GetBool() ); + + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_Gamepad::idMenuDataSource_AudioSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_Gamepad::idMenuDataSource_GamepadSettings::CommitData() { + + in_invertLook.SetBool( fields[ GAMEPAD_FIELD_INVERT ].ToBool() ); + in_joystickRumble.SetBool( fields[ GAMEPAD_FIELD_VIBRATE ].ToBool() ); + joy_yawSpeed.SetFloat( ( ( fields[ GAMEPAD_FIELD_HOR_SENS ].ToFloat() / 100.0f ) * 300.0f ) + 100.0f ); + joy_pitchSpeed.SetFloat( ( ( fields[ GAMEPAD_FIELD_VERT_SENS ].ToFloat() / 100.0f ) * 200.0f ) + 60.0f ); + joy_gammaLook.SetBool( fields[ GAMEPAD_FIELD_ACCELERATION ].ToBool() ); + joy_mergedThreshold.SetBool( fields[ GAMEPAD_FIELD_THRESHOLD ].ToBool() ); + + idPlayerProfile * profile = session->GetProfileFromMasterLocalUser(); + if ( profile != NULL ) { + profile->SetLeftyFlip( fields[ GAMEPAD_FIELD_LEFTY ].ToBool() ); + } + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + + // make the committed fields into the backup fields + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_Gamepad::idMenuDataSource_AudioSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_Gamepad::idMenuDataSource_GamepadSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + + if ( fieldIndex == GAMEPAD_FIELD_INVERT || fieldIndex == GAMEPAD_FIELD_LEFTY || fieldIndex == GAMEPAD_FIELD_VIBRATE || fieldIndex == GAMEPAD_FIELD_ACCELERATION || fieldIndex == GAMEPAD_FIELD_THRESHOLD ) { + fields[ fieldIndex ].SetBool( !fields[ fieldIndex ].ToBool() ); + } else { + float newValue = idMath::ClampFloat( 0.0f, 100.0f, fields[ fieldIndex ].ToFloat() + adjustAmount ); + fields[ fieldIndex ].SetFloat( newValue ); + } +} + +/* +======================== +idMenuScreen_Shell_Gamepad::idMenuDataSource_AudioSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_Gamepad::idMenuDataSource_GamepadSettings::IsDataChanged() const { + + if ( fields[ GAMEPAD_FIELD_INVERT ].ToBool() != originalFields[ GAMEPAD_FIELD_INVERT ].ToBool() ) { + return true; + } + + if ( fields[ GAMEPAD_FIELD_LEFTY ].ToBool() != originalFields[ GAMEPAD_FIELD_LEFTY ].ToBool() ) { + return true; + } + + if ( fields[ GAMEPAD_FIELD_VIBRATE ].ToBool() != originalFields[ GAMEPAD_FIELD_VIBRATE ].ToBool() ) { + return true; + } + + if ( fields[ GAMEPAD_FIELD_HOR_SENS ].ToFloat() != originalFields[ GAMEPAD_FIELD_HOR_SENS ].ToFloat() ) { + return true; + } + + if ( fields[ GAMEPAD_FIELD_VERT_SENS ].ToFloat() != originalFields[ GAMEPAD_FIELD_VERT_SENS ].ToFloat() ) { + return true; + } + + if ( fields[ GAMEPAD_FIELD_ACCELERATION ].ToBool() != originalFields[ GAMEPAD_FIELD_ACCELERATION ].ToBool() ) { + return true; + } + + if ( fields[ GAMEPAD_FIELD_THRESHOLD ].ToBool() != originalFields[ GAMEPAD_FIELD_THRESHOLD ].ToBool() ) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Leaderboards.cpp b/neo/d3xp/menus/MenuScreen_Shell_Leaderboards.cpp new file mode 100644 index 00000000..15fb64a0 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Leaderboards.cpp @@ -0,0 +1,1037 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_LEADERBOARD_ITEMS = 16; +const int MAX_STAT_LISTINGS = 16; +static const int MAX_ROWS_PER_BLOCK = 50; + +idMenuScreen_Shell_Leaderboards::~idMenuScreen_Shell_Leaderboards() { + if ( lbCache != NULL ) { + delete lbCache; + lbCache = NULL; + } +} + +// Helper functions for formatting leaderboard columns +static idStr FormatTime( int64 time ) { + int minutes = time / ( 1000 * 60 ); + int seconds = ( time - ( minutes * 1000 * 60 ) ) / 1000; + int mseconds = time - ( ( minutes * 1000 * 60 ) + ( seconds * 1000 ) ); + return idStr( va( "%02d:%02d.%03d", minutes, seconds, mseconds ) ); +} + +static idStr FormatCash( int64 cash ) { return idStr::FormatCash( static_cast( cash ) ); } +static int32 FormatNumber( int64 number ) { return static_cast( number ); } + +static idSWFScriptVar FormatColumn( const columnDef_t * columnDef, int64 score ) { + switch( columnDef->displayType ) { + case STATS_COLUMN_DISPLAY_TIME_MILLISECONDS: return idSWFScriptVar( FormatTime( score ) ); + case STATS_COLUMN_DISPLAY_CASH: return idSWFScriptVar( FormatCash( score ) ); + default: return idSWFScriptVar( FormatNumber( score ) ); + } +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::Initialize +======================== +*/ +void idMenuScreen_Shell_Leaderboards::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + lbCache = new idLBCache(); + lbCache->Reset(); + + SetSpritePath( "menuLeaderboards" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_LEADERBOARD_ITEMS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + + while ( options->GetChildren().Num() < NUM_LEADERBOARD_ITEMS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_party_lobby" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + btnNext = new (TAG_SWF) idMenuWidget_Button(); + btnNext->Initialize( data ); + btnNext->SetLabel( "#str_swf_next" ); + btnNext->SetSpritePath( GetSpritePath(), "info", "btnNext" ); + btnNext->AddEventAction( WIDGET_EVENT_PRESS ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_NEXT, WIDGET_EVENT_TAB_NEXT ) ); + AddChild( btnNext ); + + btnPrev = new (TAG_SWF) idMenuWidget_Button(); + btnPrev->Initialize( data ); + btnPrev->SetLabel( "#str_swf_prev" ); + btnPrev->SetSpritePath( GetSpritePath(), "info", "btnPrevious" ); + btnPrev->AddEventAction( WIDGET_EVENT_PRESS ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_PREV, WIDGET_EVENT_TAB_PREV ) ); + AddChild( btnPrev ); + + btnPageDwn = new (TAG_SWF) idMenuWidget_Button(); + btnPageDwn->Initialize( data ); + btnPageDwn->SetLabel( "#str_swf_next_page" ); + btnPageDwn->SetSpritePath( GetSpritePath(), "info", "btnPageDwn" ); + idSWFParmList parms; + parms.Append( MAX_STAT_LISTINGS - 1 ); + btnPageDwn->AddEventAction( WIDGET_EVENT_PRESS ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_PAGE_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_PAGEDWN ) ); + btnPageDwn->AddEventAction( WIDGET_EVENT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_PAGEDWN_RELEASE ) ); + AddChild( btnPageDwn ); + + btnPageUp = new (TAG_SWF) idMenuWidget_Button(); + btnPageUp->Initialize( data ); + btnPageUp->SetLabel( "#str_swf_prev_page" ); + btnPageUp->SetSpritePath( GetSpritePath(), "info", "btnPageUp" ); + parms.Clear(); + parms.Append( MAX_STAT_LISTINGS - 1 ); + btnPageUp->AddEventAction( WIDGET_EVENT_PRESS ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_PAGE_UP_START_REPEATER, WIDGET_EVENT_SCROLL_PAGEUP ) ); + btnPageUp->AddEventAction( WIDGET_EVENT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_PAGEUP_RELEASE ) ); + AddChild( btnPageUp ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_PAGEDWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_PAGE_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_PAGEDWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_PAGEUP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_PAGE_UP_START_REPEATER, WIDGET_EVENT_SCROLL_PAGEUP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_PAGEDWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_PAGEDWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_PAGEUP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_PAGEUP_RELEASE ) ); + AddEventAction( WIDGET_EVENT_TAB_NEXT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_NEXT, WIDGET_EVENT_TAB_NEXT ) ); + AddEventAction( WIDGET_EVENT_TAB_PREV ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_TAB_PREV, WIDGET_EVENT_TAB_PREV ) ); + + leaderboards.Clear(); + + const idList< mpMap_t > maps = common->GetMapList(); + const char ** gameModes = NULL; + const char ** gameModesDisplay = NULL; + int numModes = game->GetMPGameModes( &gameModes, &gameModesDisplay ); + + for ( int mapIndex = 0; mapIndex < maps.Num(); ++mapIndex ) { + for ( int modeIndex = 0; modeIndex < numModes; ++modeIndex ) { + // Check the supported modes on the map. + if( maps[ mapIndex ].supportedModes & BIT( modeIndex ) ) { + int boardID = LeaderboardLocal_GetID( mapIndex, modeIndex ); + const leaderboardDefinition_t * lbDef = Sys_FindLeaderboardDef( boardID ); + if ( lbDef != NULL ) { + doomLeaderboard_t lb = doomLeaderboard_t( lbDef, lbDef->boardName ); + leaderboards.Append( lb ); + } + } + } + } + +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::PumpLBCache +======================== +*/ +void idMenuScreen_Shell_Leaderboards::PumpLBCache() { + + if ( lbCache == NULL ) { + return; + } + + lbCache->Pump(); + +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::ClearLeaderboard +======================== +*/ +void idMenuScreen_Shell_Leaderboards::ClearLeaderboard() { + + if ( lbCache == NULL ) { + return; + } + + lbCache->Reset(); + +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::Update +======================== +*/ +void idMenuScreen_Shell_Leaderboards::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + buttonInfo->label = "#str_online_leaderboards_toggle_filter"; + buttonInfo->action.Set( WIDGET_ACTION_JOY3_ON_PRESS ); + + if ( !lbCache->IsLoadingNewLeaderboard() && !lbCache->IsRequestingRows() && options != NULL && options->GetTotalNumberOfOptions() > 0 ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_view_profile"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( lbCache->GetFilterStrType() ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Leaderboards::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); + + if ( GetSprite() != NULL ) { + lbHeading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtLbType" ); + if ( menuData != NULL && menuData->GetGUI() != NULL ) { + idSWFScriptObject * const shortcutKeys = menuData->GetGUI()->GetGlobal( "shortcutKeys" ).GetObject(); + if ( verify( shortcutKeys != NULL ) ) { + + // TAB NEXT + idSWFScriptObject * const btnTabNext = GetSprite()->GetScriptObject()->GetNestedObj( "info", "btnNext" ); + if ( btnTabNext != NULL ) { + btnTabNext->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_TAB_NEXT, 0 ) ); + shortcutKeys->Set( "JOY6", btnTabNext ); + + if ( btnTabNext->GetSprite() != NULL && menuData != NULL ) { + btnTabNext->GetSprite()->StopFrame( menuData->GetPlatform() + 1 ); + } + + } + + // TAB PREV + idSWFScriptObject * const btnTabPrev = GetSprite()->GetScriptObject()->GetNestedObj( "info", "btnPrevious" ); + if ( btnTabPrev != NULL ) { + btnTabPrev->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_TAB_PREV, 0 ) ); + shortcutKeys->Set( "JOY5", btnTabPrev ); + + if ( btnTabPrev->GetSprite() != NULL && menuData != NULL ) { + btnTabPrev->GetSprite()->StopFrame( menuData->GetPlatform() + 1 ); + } + } + + // TAB NEXT + idSWFScriptObject * const btnDwn = GetSprite()->GetScriptObject()->GetNestedObj( "info", "btnPageDwn" ); + if ( btnDwn != NULL ) { + btnDwn->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_PAGEDWN, 0 ) ); + shortcutKeys->Set( "JOY_TRIGGER2", btnDwn ); + + if ( btnDwn->GetSprite() != NULL && menuData != NULL ) { + btnDwn->GetSprite()->StopFrame( menuData->GetPlatform() + 1 ); + } + + } + + // TAB PREV + idSWFScriptObject * const btnUp = GetSprite()->GetScriptObject()->GetNestedObj( "info", "btnPageUp" ); + if ( btnUp != NULL ) { + btnUp->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_PAGEUP, 0 ) ); + shortcutKeys->Set( "JOY_TRIGGER1", btnUp ); + + if ( btnUp->GetSprite() != NULL && menuData != NULL ) { + btnUp->GetSprite()->StopFrame( menuData->GetPlatform() + 1 ); + } + } + + } + } + } + + SetLeaderboardIndex(); + + if ( menuData == NULL ) { + return; + } + + int platform = menuData->GetPlatform(); + if ( btnNext != NULL && btnNext->GetSprite() != NULL ) { + idSWFSpriteInstance * btnImg = btnNext->GetSprite()->GetScriptObject()->GetNestedSprite( "btnImg" ); + + if ( btnImg != NULL ) { + if ( platform == 2 ) { + btnImg->SetVisible( false ); + } else { + btnImg->SetVisible( true ); + btnImg->StopFrame( platform + 1 ); + } + } + } + + if ( btnPrev != NULL && btnPrev->GetSprite() != NULL ) { + idSWFSpriteInstance * btnImg = btnPrev->GetSprite()->GetScriptObject()->GetNestedSprite( "btnImg" ); + + if ( btnImg != NULL ) { + if ( platform == 2 ) { + btnImg->SetVisible( false ); + } else { + btnImg->SetVisible( true ); + btnImg->StopFrame( platform + 1 ); + } + } + } + + if ( btnPageDwn != NULL && btnPageDwn->GetSprite() != NULL ) { + idSWFSpriteInstance * btnImg = btnPageDwn->GetSprite()->GetScriptObject()->GetNestedSprite( "btnImg" ); + + if ( btnImg != NULL ) { + if ( platform == 2 ) { + btnImg->SetVisible( false ); + } else { + btnImg->SetVisible( true ); + btnImg->StopFrame( platform + 1 ); + } + } + } + + if ( btnPageUp != NULL && btnPageUp->GetSprite() != NULL ) { + idSWFSpriteInstance * btnImg = btnPageUp->GetSprite()->GetScriptObject()->GetNestedSprite( "btnImg" ); + + if ( btnImg != NULL ) { + if ( platform == 2 ) { + btnImg->SetVisible( false ); + } else { + btnImg->SetVisible( true ); + btnImg->StopFrame( platform + 1 ); + } + } + } + + +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::HideScreen +======================== +*/ +void idMenuScreen_Shell_Leaderboards::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Leaderboards::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_LEADERBOARDS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_PARTY_LOBBY, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_JOY3_ON_PRESS: { + lbCache->CycleFilter(); + refreshLeaderboard = true; + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + + if ( options == NULL ) { + return true; + } + + int index = options->GetFocusIndex(); + if ( parms.Num() != 0 ) { + index = parms[0].ToInteger(); + } + + if ( lbCache->GetEntryIndex() != index ) { + lbCache->SetEntryIndex( index ); + refreshLeaderboard = true; + return true; + } + + const idLeaderboardCallback::row_t * row = lbCache->GetLeaderboardRow( lbCache->GetRowOffset() + lbCache->GetEntryIndex() ); + if ( row != NULL ) { + lbCache->DisplayGamerCardUI( row ); + } + + return true; + } + case WIDGET_ACTION_SCROLL_TAB: { + int delta = parms[0].ToInteger(); + lbIndex += delta; + SetLeaderboardIndex(); + return true; + } + case WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE: { + if ( parms.Num() == 0 ) { + return true; + } + + if ( options == NULL ) { + return true; + } + + int dir = parms[ 0 ].ToInteger(); + if ( lbCache->Scroll( dir ) ) { + // TODO_SPARTY: play scroll sound + refreshLeaderboard = true; + } + + return true; + } + case WIDGET_ACTION_SCROLL_PAGE: { + if ( parms.Num() == 0 ) { + return true; + } + + if ( options == NULL ) { + return true; + } + + int dir = parms[ 0 ].ToInteger(); + if ( lbCache->ScrollOffset( dir ) ) { + refreshLeaderboard = true; + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::UpdateLeaderboard +======================== +*/ +void idMenuScreen_Shell_Leaderboards::UpdateLeaderboard( const idLeaderboardCallback * callback ) { + + lbCache->Update( callback ); + + if ( callback->GetErrorCode() != LEADERBOARD_ERROR_NONE ) { + if ( !session->GetSignInManager().IsMasterLocalUserOnline() ) { + refreshWhenMasterIsOnline = true; + } + } + + refreshLeaderboard = true; +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::SetLeaderboardIndex +======================== +*/ +void idMenuScreen_Shell_Leaderboards::SetLeaderboardIndex() { + + if ( lbIndex >= leaderboards.Num() ) { + lbIndex = 0; + } else if ( lbIndex < 0 ) { + lbIndex = leaderboards.Num() - 1; + } + + const leaderboardDefinition_t * leaderboardDef = leaderboards[ lbIndex ].lb; + for ( int i = 0; i < leaderboardDef->numColumns; i++ ) { + /*if ( leaderboardDef->columnDefs[i].displayType != STATS_COLUMN_NEVER_DISPLAY ) { + gameLocal->GetMainMenu()->mainMenu->SetGlobal( va("columnname%d",i), leaderboardDef->columnDefs[i].locDisplayName ); + }*/ + } + + lbCache->SetLeaderboard( leaderboardDef, lbCache->GetFilter() ); + + refreshLeaderboard = true; + +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::RefreshLeaderboard +======================== +*/ +void idMenuScreen_Shell_Leaderboards::RefreshLeaderboard() { + + if ( refreshWhenMasterIsOnline ) { + SetLeaderboardIndex(); + refreshWhenMasterIsOnline = false; + } + + if ( !refreshLeaderboard ) { + return; + } + + refreshLeaderboard = false; + bool upArrow = false; + bool downArrow = false; + + int focusIndex = -1; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > lbListings; + + if ( !lbCache->IsLoadingNewLeaderboard() && lbCache->GetErrorCode() == LEADERBOARD_DISPLAY_ERROR_NONE ) { + for ( int addIndex = 0; addIndex < MAX_STAT_LISTINGS; ++addIndex ) { + + idList< idStr > values; + + int index = lbCache->GetRowOffset() + addIndex; + + const idLeaderboardCallback::row_t * row = lbCache->GetLeaderboardRow( index ); // If this row is not in the cache, this will kick off a request + if ( row != NULL ) { + values.Append( va( "%i", (int)row->rank ) ); + values.Append( row->name ); + values.Append( FormatColumn( &lbCache->GetLeaderboard()->columnDefs[0], row->columns[0] ).ToString() ); + } + + if ( lbCache->GetEntryIndex() == addIndex ) { + focusIndex = addIndex; + } + + lbListings.Append( values ); + } + + if ( lbCache->GetRowOffset() != 0 ) { + upArrow = true; + } + + if ( ( lbCache->GetRowOffset() + MAX_STAT_LISTINGS ) < lbCache->GetNumRowsInLeaderboard() ) { + downArrow = true; + } + } + + if ( lbHeading != NULL ) { + lbHeading->SetText( leaderboards[ lbIndex ].name ); + lbHeading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + if ( focusIndex >= 0 ) { + options->SetFocusIndex( focusIndex ); + } + + if ( btnPageDwn != NULL && btnPageDwn->GetSprite() != NULL ) { + btnPageDwn->GetSprite()->SetVisible( downArrow ); + } + + if ( btnPageUp != NULL && btnPageUp->GetSprite() != NULL ) { + btnPageUp->GetSprite()->SetVisible( upArrow ); + } + + options->SetListData( lbListings ); + Update(); + + const char * leaderboardErrorStrings[] = { + "", + "#str_online_leaderboards_error_failed", // failed + "", // not online - players are just taken back to multiplayer menu + "#str_online_leaderboards_error_not_ranked", // not ranked + }; + + compile_time_assert( sizeof( leaderboardErrorStrings ) / sizeof( leaderboardErrorStrings[0] ) == LEADERBOARD_DISPLAY_ERROR_MAX ); + + bool isLoading = lbCache->IsLoadingNewLeaderboard(); + idStr error = leaderboardErrorStrings[ lbCache->GetErrorCode() ]; + + if ( isLoading ) { + ShowMessage( true, "#str_online_loading", true ); + } else { + if ( !error.IsEmpty() ) { + ShowMessage( true, error, false ); + } else { + if ( lbCache->GetNumRowsInLeaderboard() > 0 ) { + ShowMessage( false, "", false ); + } else { + ShowMessage( true, "#str_online_leaderboards_no_data", false ); + } + } + } +} + +/* +======================== +idMenuScreen_Shell_Leaderboards::ShowMessage +======================== +*/ +void idMenuScreen_Shell_Leaderboards::ShowMessage( bool show, idStr message, bool spinner ) { + + if ( !menuData || !menuData->GetGUI() ) { + return; + } + + idSWFSpriteInstance * pacifier = menuData->GetGUI()->GetRootObject().GetNestedSprite( "menuLeaderboards", "info", "pacifier" ); + + if ( !pacifier ) { + return; + } + + if ( show ) { + + if ( spinner && options != NULL && options->GetSprite() != NULL ) { + options->GetSprite()->SetAlpha( 0.35f ); + } else if ( options != NULL && options->GetSprite() != NULL ) { + options->GetSprite()->SetVisible( false ); + } + + pacifier->SetVisible( true ); + idSWFTextInstance * txtMsg = pacifier->GetScriptObject()->GetNestedText( "message" ); + if ( txtMsg != NULL ) { + txtMsg->SetText( message ); + txtMsg->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * spriteSpinner = pacifier->GetScriptObject()->GetNestedSprite( "graphic" ); + if ( spriteSpinner != NULL ) { + spriteSpinner->StopFrame( spinner ? 1 : 2 ); + } + + } else { + + if ( options != NULL && options->GetSprite() != NULL ) { + options->GetSprite()->SetVisible( true ); + options->GetSprite()->SetAlpha( 1.0f ); + } + + pacifier->SetVisible( false ); + } + +} + + +//************************************************************************************************************************* +// LBCACHE +//************************************************************************************************************************* + +class LBCallback : public idLeaderboardCallback +{ +public: + LBCallback() {} + + void Call() { + gameLocal.Shell_UpdateLeaderboard( this ); + } + + LBCallback * Clone() const { + return new LBCallback( *this ); + } +}; + +/* +======================== +idLBCache::Pump +======================== +*/ +void idLBCache::Pump() { + if ( loadingNewLeaderboard || requestingRows ) { + return; + } + + if ( pendingDef != NULL ) { + SetLeaderboard( pendingDef, pendingFilter ); + } +} + +/* +======================== +idLBCache::Reset +======================== +*/ +void idLBCache::Reset() { + for ( int i = 0; i < NUM_ROW_BLOCKS; i++ ) { + rowBlocks[i].startIndex = 0; + rowBlocks[i].rows.Clear(); + } + + def = NULL; + filter = DEFAULT_LEADERBOARD_FILTER; + pendingDef = NULL; + pendingFilter = DEFAULT_LEADERBOARD_FILTER; + rowOffset = 0; + requestingRows = false; + numRowsInLeaderboard = 0; + entryIndex = 0; + loadingNewLeaderboard = false; +} + +/* +======================== +idLBCache::SetLeaderboard +======================== +*/ +void idLBCache::SetLeaderboard( const leaderboardDefinition_t * def_, leaderboardFilterMode_t filter_ ) { + + // If we are busy waiting on results from a previous request, queue up this request + if ( loadingNewLeaderboard || requestingRows ) { + pendingDef = def_; + pendingFilter = filter_; + return; + } + + //idLib::Printf( "SetLeaderboard 0x%p.\n", def_ ); + + // Reset all + Reset(); + + // Set leaderboard and filter + def = def_; + filter = filter_; + + loadingNewLeaderboard = true; // This means we are waiting on the first set of results for this new leaderboard + + localIndex = -1; // don't know where the user is in the rows yet + + // Kick off initial stats request (which is initially based on the filter type) + if ( filter == LEADERBOARD_FILTER_MYSCORE ) { + LBCallback cb; + session->LeaderboardDownload( 0, def, 0, MAX_ROWS_PER_BLOCK, cb ); + } else if ( filter == LEADERBOARD_FILTER_FRIENDS ) { + LBCallback cb; + session->LeaderboardDownload( 0, def, -1, 100, cb ); // Request up to 100 friends + } else { + LBCallback cb; + session->LeaderboardDownload( 0, def, rowOffset + 1, MAX_ROWS_PER_BLOCK, cb ); + //session->LeaderboardDownload( 0, def, rowOffset + 1, 10, cb ); // For testing + } +} + +/* +======================== +idLBCache::CycleFilter +======================== +*/ +void idLBCache::CycleFilter() { + // Set the proper filter + if ( filter == LEADERBOARD_FILTER_OVERALL ) { + filter = LEADERBOARD_FILTER_MYSCORE; + } else if ( filter == LEADERBOARD_FILTER_MYSCORE ) { + filter = LEADERBOARD_FILTER_FRIENDS; + } else { + filter = LEADERBOARD_FILTER_OVERALL; + } + + // Reset the leaderboard with the new filter + SetLeaderboard( def, filter ); +} + +/* +======================== +idLBCache::GetFilterStrType +======================== +*/ +idStr idLBCache::GetFilterStrType() { + if ( filter == LEADERBOARD_FILTER_FRIENDS ) { + return idLocalization::GetString( "#str_swf_leaderboards_friends_heading" ); + } else if ( filter == LEADERBOARD_FILTER_MYSCORE ) { + return idLocalization::GetString( "#str_swf_leaderboards_global_self_heading" ); + } + + return idLocalization::GetString( "#str_swf_leaderboards_global_heading" ); +} + +/* +======================== +idLBCache::Scroll +======================== +*/ +bool idLBCache::Scroll( int amount ) { + if ( GetErrorCode() != LEADERBOARD_DISPLAY_ERROR_NONE ) { + return false; // don't allow scrolling on errors + } + + // Remember old offsets so we know if anything moved + int oldEntryIndex = entryIndex; + int oldRowOffset = rowOffset; + + // Move cursor index by scroll amount + entryIndex += amount; + + // Clamp cursor index (scrolling row offset if we can) + if ( entryIndex < 0 ) { + rowOffset += entryIndex; + entryIndex = 0; + } else if ( entryIndex >= numRowsInLeaderboard ) { + entryIndex = numRowsInLeaderboard - 1; + rowOffset = entryIndex - ( MAX_STAT_LISTINGS - 1 ); + } else if ( entryIndex >= MAX_STAT_LISTINGS ) { + rowOffset += entryIndex - ( MAX_STAT_LISTINGS - 1 ); + entryIndex = MAX_STAT_LISTINGS - 1; + } + + // Clamp row offset + rowOffset = idMath::ClampInt( 0, Max( numRowsInLeaderboard - MAX_STAT_LISTINGS, 0 ), rowOffset ); + + // Let caller know if anything actually changed + return ( oldEntryIndex != entryIndex || oldRowOffset != rowOffset ); +} + +/* +======================== +idLBCache::ScrollOffset +======================== +*/ +bool idLBCache::ScrollOffset( int amount ) { + if ( GetErrorCode() != LEADERBOARD_DISPLAY_ERROR_NONE ) { + return false; // don't allow scrolling on errors + } + + // Remember old offsets so we know if anything moved + int oldEntryIndex = entryIndex; + int oldRowOffset = rowOffset; + + rowOffset += amount; + + // Clamp row offset + rowOffset = idMath::ClampInt( 0, Max( numRowsInLeaderboard - MAX_STAT_LISTINGS, 0 ), rowOffset ); + + if ( rowOffset != oldRowOffset ) { + entryIndex -= amount; // adjust in opposite direction so same item stays selected + entryIndex = idMath::ClampInt( 0, rowOffset + ( MAX_STAT_LISTINGS - 1 ), entryIndex ); + } else { + entryIndex += amount; + entryIndex = idMath::ClampInt( 0, numRowsInLeaderboard - 1, entryIndex ); + } + + // Let caller know if anything actually changed + return ( oldEntryIndex != entryIndex || oldRowOffset != rowOffset ); +} + +/* +======================== +idLBCache::FindFreeRowBlock +======================== +*/ +idLBRowBlock * idLBCache::FindFreeRowBlock() { + int bestTime = 0; + int bestBlockIndex = 0; + + for ( int i = 0; i < NUM_ROW_BLOCKS; i++ ) { + if ( rowBlocks[i].rows.Num() == 0 ) { + return &rowBlocks[i]; // Prefer completely empty blocks + } + + // Search for oldest block in the mean time + if ( i == 0 || rowBlocks[i].lastTime < bestTime ) { + bestBlockIndex = i; + bestTime = rowBlocks[i].lastTime; + } + } + + return &rowBlocks[bestBlockIndex]; +} + +/* +======================== +idLBCache::CallbackErrorToDisplayError +======================== +*/ +leaderboardDisplayError_t idLBCache::CallbackErrorToDisplayError( leaderboardError_t errorCode ) { + switch ( errorCode ) { + case LEADERBOARD_ERROR_NONE: + return LEADERBOARD_DISPLAY_ERROR_NONE; + default: + return LEADERBOARD_DISPLAY_ERROR_FAILED; + } +} + +/* +======================== +idLBCache::Update +======================== +*/ +void idLBCache::Update( const idLeaderboardCallback * callback ) { + requestingRows = false; + + //idLib::Printf( "Stats returned.\n" ); + + errorCode = CallbackErrorToDisplayError( callback->GetErrorCode() ); + + // check if trying to view "My Score" leaderboard when you aren't ranked yet + if ( loadingNewLeaderboard && filter == LEADERBOARD_FILTER_MYSCORE && callback->GetLocalIndex() == -1 && errorCode == LEADERBOARD_DISPLAY_ERROR_NONE ) { + errorCode = LEADERBOARD_DISPLAY_ERROR_NOT_RANKED; + } + + if ( errorCode != LEADERBOARD_DISPLAY_ERROR_NONE ) { + numRowsInLeaderboard = 0; + loadingNewLeaderboard = false; + + switch ( errorCode ) { + case LEADERBOARD_DISPLAY_ERROR_NOT_ONLINE: + /*idMenuHandler_Shell * shell = gameLocal.Shell_GetHandler(); + if ( shell != NULL ) { + shell->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + }*/ + common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, true, "", 0, true ); + break; + default: + break; + } + + return; + } + + if ( callback->GetDef() != def ) { + // Not the leaderboard we are looking for (This should no longer be possible) + idLib::Printf( "Wrong leaderboard.\n" ); + numRowsInLeaderboard = 0; + loadingNewLeaderboard = false; + return; + } + + // Get total rows in this leaderboard + numRowsInLeaderboard = callback->GetNumRowsInLeaderboard(); + + // Store the index that the master user is in, if we haven't already found the index + if ( callback->GetLocalIndex() != -1 ) { + localIndex = callback->GetStartIndex() + callback->GetLocalIndex(); + } + + if ( loadingNewLeaderboard == true ) { + // Default to viewing the local user (if the right filter mode is set) + if ( callback->GetLocalIndex() != -1 && ( filter == LEADERBOARD_FILTER_MYSCORE || filter == LEADERBOARD_FILTER_FRIENDS ) ) { + // Put their name and cursor at the top + rowOffset = callback->GetLocalIndex() + callback->GetStartIndex(); + entryIndex = 0; + + // Scroll the cursor up to center their name as much as possible + Scroll( -MAX_STAT_LISTINGS / 2 ); + + // Set the cursor to their name + entryIndex = ( callback->GetLocalIndex() + callback->GetStartIndex() ) - rowOffset; + } + loadingNewLeaderboard = false; + } + + // Find a a row block to store these new rows + idLBRowBlock * rowBlock = FindFreeRowBlock(); + + rowBlock->lastTime = Sys_Milliseconds(); // Freshen row + rowBlock->startIndex = callback->GetStartIndex(); + rowBlock->rows = callback->GetRows(); +} + +/* +======================== +idLBCache::GetLeaderboardRow +======================== +*/ +const idLeaderboardCallback::row_t * idLBCache::GetLeaderboardRow( int row ) { + if ( loadingNewLeaderboard ) { + return NULL; // If we are refreshing (seeing this leaderboard for the first time), force NULL till we get first set of results + } + + if ( row >= numRowsInLeaderboard ) { + return NULL; + } + + // Find it in the cache + for ( int i = 0; i < NUM_ROW_BLOCKS; i++ ) { + int startIndex = rowBlocks[i].startIndex; + int lastIndex = startIndex + rowBlocks[i].rows.Num() - 1; + if ( row >= startIndex && row <= lastIndex ) { + rowBlocks[i].lastTime = Sys_Milliseconds(); // Freshen row + return &rowBlocks[i].rows[row - startIndex]; + } + } + + // Not found, kick off a request to download it + // (this will not allow more than one request at a time) + if ( !requestingRows ) { + // If we don't have this row, kick off a request to get it + LBCallback cb; + requestingRows = true; + session->LeaderboardDownload( 0, def, row + 1, MAX_ROWS_PER_BLOCK, cb ); + //session->LeaderboardDownload( 0, def, row + 1, 10, cb ); + //idLib::Printf( "Stat request\n" ); + } + + return NULL; +} + + +/* +======================== +idMainMenu::SetSPLeaderboardFromMenuSettings +======================== +*/ +void idLBCache::DisplayGamerCardUI( const idLeaderboardCallback::row_t * row ) { +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Load.cpp b/neo/d3xp/menus/MenuScreen_Shell_Load.cpp new file mode 100644 index 00000000..4a0444bc --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Load.cpp @@ -0,0 +1,465 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + + +const static int NUM_LOAD_OPTIONS = 10; + +/* +======================== +idMenuScreen_Shell_Load::Initialize +======================== +*/ +void idMenuScreen_Shell_Load::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuLoad" ); + + saveInfo = new (TAG_SWF) idMenuWidget_Shell_SaveInfo(); + saveInfo->SetSpritePath( GetSpritePath(), "info", "details" ); + saveInfo->Initialize( data ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_LOAD_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + while ( options->GetChildren().Num() < NUM_LOAD_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->RegisterEventObserver( saveInfo ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + AddChild( saveInfo ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * >( data ); + if ( handler != NULL && handler->GetInGame() ) { + btnBack->SetLabel( "#str_swf_pause_menu" ); + } else { + btnBack->SetLabel( "#str_swf_campaign" ); + } + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + btnDelete = new idMenuWidget_Button(); + btnDelete->Initialize( data ); + btnDelete->SetLabel( "" ); + btnDelete->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_JOY3_ON_PRESS ); + btnDelete->SetSpritePath( GetSpritePath(), "info", "btnDelete" ); + + AddChild( btnDelete ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Load::Update +======================== +*/ +void idMenuScreen_Shell_Load::Update() { + + UpdateSaveEnumerations(); + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_02187" ); // LOAD GAME + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Load::UpdateSaveEnumerations +======================== +*/ +void idMenuScreen_Shell_Load::UpdateSaveEnumerations() { + + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + sortedSaves = saveGameInfo; + sortedSaves.Sort( idSort_SavesByDate() ); + + if ( options != NULL ) { + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > saveList; + if ( session->GetSaveGameManager().IsWorking() ) { + idList< idStr > saveName; + saveName.Append( "#str_dlg_refreshing" ); + saveList.Append( saveName ); + } else if ( sortedSaves.Num() == 0 ) { + idList< idStr > saveName; + saveName.Append( "#str_no_saves_found" ); + saveList.Append( saveName ); + } else { + + saveList.SetNum( sortedSaves.Num() ); + for ( int slot = 0; slot < sortedSaves.Num(); ++slot ) { + idStr & slotSaveName = saveList[slot].Alloc(); + const idSaveGameDetails & details = sortedSaves[slot]; + if ( details.damaged ) { + slotSaveName = va( S_COLOR_RED "%s", idLocalization::GetString( "#str_swf_corrupt_file" ) ); + } else if ( details.GetSaveVersion() > BUILD_NUMBER ) { + slotSaveName = va( S_COLOR_RED "%s", idLocalization::GetString( "#str_swf_wrong_version" ) ); + } else { + if ( details.slotName.Icmp( "autosave" ) == 0 ) { + slotSaveName.Append( S_COLOR_YELLOW ); + } else if ( details.slotName.Icmp( "quick" ) == 0 ) { + slotSaveName.Append( S_COLOR_ORANGE ); + } + slotSaveName.Append( details.GetMapName() ); + } + } + } + options->SetListData( saveList ); + options->Update(); + } + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; // BACK + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + + + if ( sortedSaves.Num() > 0 && !session->GetSaveGameManager().IsWorking() ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_02187"; // LOAD GAME + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_02315"; // DELETE GAME + } + buttonInfo->action.Set( WIDGET_ACTION_JOY3_ON_PRESS ); + + if ( btnDelete != NULL ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( btnDelete->BindSprite( root ) ) { + if ( menuData->GetPlatform() != 2 ) { + btnDelete->SetLabel( "" ); + } else { + btnDelete->GetSprite()->SetVisible( true ); + btnDelete->SetLabel( "#str_02315" ); + } + } + btnDelete->Update(); + } + } else { + if ( btnDelete != NULL ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( btnDelete->BindSprite( root ) ) { + btnDelete->SetLabel( "" ); + btnDelete->Update(); + } + } + } + cmdBar->Update(); + } + } + + if ( saveInfo != NULL ) { + saveInfo->Update(); + } + + if ( options != NULL && options->GetTotalNumberOfOptions() > 0 && options->GetViewIndex() >= options->GetTotalNumberOfOptions() ) { + options->SetViewIndex( options->GetTotalNumberOfOptions() - 1 ); + if ( options->GetViewOffset() > options->GetViewIndex() ) { + options->SetViewOffset( options->GetViewIndex() ); + } + options->SetFocusIndex( options->GetViewIndex() - options->GetViewOffset() ); + } +} + +/* +======================== +idMenuScreen_Shell_Load::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Load::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Load::HideScreen +======================== +*/ +void idMenuScreen_Shell_Load::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + + +/* +======================== +idMenuScreen_Shell_Load::LoadDamagedGame +======================== +*/ +void idMenuScreen_Shell_Load::LoadDamagedGame( int index ) { + + if ( index >= sortedSaves.Num() ) { + return; + } + + class idSWFScriptFunction_LoadDamaged : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_LoadDamaged( gameDialogMessages_t _msg, bool _accept, int _index, idMenuScreen_Shell_Load * _screen ) { + msg = _msg; + accept = _accept; + index = _index; + screen = _screen; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept ) { + screen->DeleteGame( index ); + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + int index; + bool accept; + idMenuScreen_Shell_Load * screen; + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_LoadDamaged( GDM_LOAD_DAMAGED_FILE, true, index, this ) ); + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_LoadDamaged( GDM_LOAD_DAMAGED_FILE, false, index, this ) ); + idStaticList< idStrId, 4 > optionText; + optionText.Append( idStrId( "#str_02315" ) ); // DELETE + optionText.Append( idStrId( "#STR_SWF_CANCEL" ) ); + + common->Dialog().AddDynamicDialog( GDM_LOAD_DAMAGED_FILE, callbacks, optionText, false, "" ); +} + +/* +======================== +idMenuScreen_Shell_Load::LoadGame +======================== +*/ +void idMenuScreen_Shell_Load::LoadGame( int index ) { + + if ( menuData == NULL ) { + return; + } + + if ( index < GetSortedSaves().Num() && GetSortedSaves()[index].damaged ) { + LoadDamagedGame( index ); + return; + } + + bool isDead = false; + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL && player->health <= 0 ) { + isDead = true; + } + + idMenuHandler_Shell * mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( mgr != NULL && mgr->GetInGame() && !isDead ) { + + class idSWFScriptFunction_LoadDialog : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_LoadDialog( gameDialogMessages_t _msg, bool _accept, const char * _name ) { + msg = _msg; + accept = _accept; + name = _name; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept && name != NULL ) { + + cmdSystem->AppendCommandText( va( "loadgame %s\n", name ) ); + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + bool accept; + const char * name; + }; + + if ( index < sortedSaves.Num() ) { + const idStr & name = sortedSaves[ index ].slotName; + common->Dialog().AddDialog( GDM_SP_LOAD_SAVE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_LoadDialog( GDM_SP_LOAD_SAVE, true, name.c_str() ), new idSWFScriptFunction_LoadDialog( GDM_SP_LOAD_SAVE, false, name.c_str() ), false ); + } + + } else { + if ( index < sortedSaves.Num() ) { + const idStr & name = sortedSaves[ index ].slotName; + + cmdSystem->AppendCommandText( va( "loadgame %s\n", name.c_str() ) ); + } + } +} + +/* +======================== +idMenuScreen_Shell_Save::DeleteGame +======================== +*/ +void idMenuScreen_Shell_Load::DeleteGame( int index ) { + + class idSWFScriptFunction_DeleteGame : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_DeleteGame( gameDialogMessages_t _msg, bool _accept, int _index, idMenuScreen_Shell_Load * _screen ) { + msg = _msg; + accept = _accept; + index = _index; + screen = _screen; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept && screen != NULL ) { + if ( index < screen->GetSortedSaves().Num() ) { + session->DeleteSaveGameSync( screen->GetSortedSaves()[ index ].slotName ); + } + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + int index; + bool accept; + idMenuScreen_Shell_Load * screen; + }; + + common->Dialog().AddDialog( GDM_DELETE_SAVE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_DeleteGame( GDM_DELETE_SAVE, true, index, this ), new idSWFScriptFunction_DeleteGame( GDM_DELETE_SAVE, false, index, this ), false ); + +} + +/* +======================== +idMenuScreen_Shell_Load::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Load::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData != NULL ) { + if ( menuData->ActiveScreen() != SHELL_AREA_LOAD ) { + return false; + } + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + switch ( actionType ) { + case WIDGET_ACTION_JOY4_ON_PRESS: { + return true; + } + case WIDGET_ACTION_JOY3_ON_PRESS: { + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + DeleteGame( selectionIndex ); + return true; + } + case WIDGET_ACTION_GO_BACK: { + if ( menuData != NULL ) { + if ( game->IsInGame() ) { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + } else { + menuData->SetNextScreen( SHELL_AREA_CAMPAIGN, MENU_TRANSITION_SIMPLE ); + } + } + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + if ( sortedSaves.Num() == 0 ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } else { + LoadGame( options->GetViewOffset() + selectionIndex ); + } + } else { + LoadGame( options->GetViewIndex() ); + } + + return true; + } + case WIDGET_ACTION_SCROLL_VERTICAL: { + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_MatchSettings.cpp b/neo/d3xp/menus/MenuScreen_Shell_MatchSettings.cpp new file mode 100644 index 00000000..5ecfba63 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_MatchSettings.cpp @@ -0,0 +1,465 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_GAME_OPTIONS_OPTIONS = 8; +/* +======================== +idMenuScreen_Shell_MatchSettings::Initialize +======================== +*/ +void idMenuScreen_Shell_MatchSettings::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuMatchSettings" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_GAME_OPTIONS_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_multiplayer" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control; + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_swf_mode" ); // Mode + control->SetDataSource( &matchData, idMenuDataSource_MatchSettings::MATCH_FIELD_MODE ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_02049" ); // Map + control->SetDataSource( &matchData, idMenuDataSource_MatchSettings::MATCH_FIELD_MAP ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_02183" ); // Time + control->SetDataSource( &matchData, idMenuDataSource_MatchSettings::MATCH_FIELD_TIME ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_00100917" ); // Score + control->SetDataSource( &matchData, idMenuDataSource_MatchSettings::MATCH_FIELD_SCORE ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::Update +======================== +*/ +void idMenuScreen_Shell_MatchSettings::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_match_settings_heading" ); // SYSTEM SETTINGS + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::ShowScreen +======================== +*/ +void idMenuScreen_Shell_MatchSettings::ShowScreen( const mainMenuTransition_t transitionType ) { + matchData.LoadData(); + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::HideScreen +======================== +*/ +void idMenuScreen_Shell_MatchSettings::HideScreen( const mainMenuTransition_t transitionType ) { + if ( matchData.IsDataChanged() ) { + matchData.CommitData(); + } + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_MatchSettings::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_MATCH_SETTINGS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_ADJUST_FIELD: { + if ( widget != NULL && widget->GetDataSource() != NULL && options != NULL ) { + widget->GetDataSource()->AdjustField( widget->GetDataSourceFieldIndex(), parms[ 0 ].ToInteger() ); + widget->Update(); + + if ( matchData.MapChanged() ) { + idMenuWidget_ControlButton * button = dynamic_cast< idMenuWidget_ControlButton * >( &options->GetChildByIndex( 1 ) ); + if ( button != NULL ) { + button->Update(); + } + matchData.ClearMapChanged(); + } + } + return true; + } + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_GAME_LOBBY, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + matchData.AdjustField( selectionIndex, 1 ); + options->Update(); + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +///////////////////////////////// +// SCREEN SETTINGS +///////////////////////////////// + +extern idCVar si_timeLimit; +extern idCVar si_fragLimit; +extern idCVar si_map; +extern idCVar si_mode; + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::idMenuDataSource_MatchSettings +======================== +*/ +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::idMenuDataSource_MatchSettings() { + fields.SetNum( MAX_MATCH_FIELDS ); + originalFields.SetNum( MAX_MATCH_FIELDS ); + updateMap = false; +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::LoadData() { + updateMap = false; + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + idStr val; + GetMapName( matchParameters.gameMap, val ); + fields[ MATCH_FIELD_MAP ].SetString( val ); + GetModeName( matchParameters.gameMode, val ); + fields[ MATCH_FIELD_MODE ].SetString( val ); + + int time = matchParameters.serverInfo.GetInt( "si_timeLimit" ); + if ( time == 0 ) { + fields[ MATCH_FIELD_TIME ].SetString( "#str_02844" ); // none + } else { + fields[ MATCH_FIELD_TIME ].SetString( va( "%i", time ) ); + } + + int fragLimit = matchParameters.serverInfo.GetInt( "si_fragLimit" ); + fields[ MATCH_FIELD_SCORE ].SetInteger( fragLimit ); + + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::CommitData() { + + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + + // make the committed fields into the backup fields + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::GetMapName +======================== +*/ +void idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::GetMapName( int index, idStr & name ) { + idLobbyBase & lobby = session->GetActivePlatformLobbyBase(); + const idMatchParameters & matchParameters = lobby.GetMatchParms(); + name = "#str_swf_filter_random"; + if ( matchParameters.gameMap >= 0 ) { + const idList< mpMap_t > maps = common->GetMapList(); + name = idLocalization::GetString( maps[ idMath::ClampInt( 0, maps.Num() - 1, matchParameters.gameMap ) ].mapName ); + } +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::GetModeName +======================== +*/ +void idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::GetModeName( int index, idStr & name ) { + idLobbyBase & lobby = session->GetActivePlatformLobbyBase(); + const idMatchParameters & matchParameters = lobby.GetMatchParms(); + name = "#str_swf_filter_random"; + if ( matchParameters.gameMode >= 0 ) { + const idStrList & modes = common->GetModeDisplayList(); + name = idLocalization::GetString( modes[ idMath::ClampInt( 0, modes.Num() - 1, matchParameters.gameMode ) ] ); + } +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + + const idStrList & modes = common->GetModeList(); + const idList< mpMap_t > maps = common->GetMapList(); + + idMatchParameters matchParameters = session->GetActivePlatformLobbyBase().GetMatchParms(); + if ( fieldIndex == MATCH_FIELD_MAP ) { + for ( int i = 0; i < maps.Num(); i++ ) { + // Don't allow random maps in the game lobby + matchParameters.gameMap += adjustAmount; + if ( matchParameters.gameMap < 0 ) { + matchParameters.gameMap = maps.Num() - 1; + } + matchParameters.gameMap %= maps.Num(); + matchParameters.mapName = maps[ matchParameters.gameMap ].mapFile; + if ( ( maps[matchParameters.gameMap].supportedModes & BIT(matchParameters.gameMode) ) != 0 ) { + // This map supports this mode + break; + } + } + session->UpdateMatchParms( matchParameters ); + + idStr val; + GetMapName( matchParameters.gameMap, val ); + si_map.SetInteger( matchParameters.gameMap ); + fields[ MATCH_FIELD_MAP ].SetString( val ); + + } else if ( fieldIndex == MATCH_FIELD_MODE ) { + // Don't allow random modes in the game lobby + matchParameters.gameMode += adjustAmount; + + if ( matchParameters.gameMode < 0 ) { + matchParameters.gameMode = modes.Num() - 1; + } + matchParameters.gameMode %= modes.Num(); + updateMap = false; + if ( ( maps[matchParameters.gameMap].supportedModes & BIT(matchParameters.gameMode) ) == 0 ) { + for ( int i = 0; i < maps.Num(); ++i ) { + if ( ( maps[i].supportedModes & BIT(matchParameters.gameMode) ) != 0 ) { + matchParameters.gameMap = i; + updateMap = true; + break; + } + } + } + + session->UpdateMatchParms( matchParameters ); + idStr val; + + GetModeName( matchParameters.gameMode, val ); + si_mode.SetInteger( matchParameters.gameMode ); + fields[ MATCH_FIELD_MODE ].SetString( val ); + + if ( updateMap ) { + GetMapName( matchParameters.gameMap, val ); + si_map.SetInteger( matchParameters.gameMap ); + fields[ MATCH_FIELD_MAP ].SetString( val ); + } + + } else if ( fieldIndex == MATCH_FIELD_TIME ) { + int time = si_timeLimit.GetInteger() + ( adjustAmount * 5 ); + if ( time < 0 ) { + time = 60; + } else if ( time > 60 ) { + time = 0; + } + + if ( time == 0 ) { + fields[ MATCH_FIELD_TIME ].SetString( "#str_02844" ); // none + } else { + fields[ MATCH_FIELD_TIME ].SetString( va( "%i", time ) ); + } + + si_timeLimit.SetInteger( time ); + + matchParameters.serverInfo.SetInt( "si_timeLimit", si_timeLimit.GetInteger() ); + session->UpdateMatchParms( matchParameters ); + + } else if ( fieldIndex == MATCH_FIELD_SCORE ) { + + int val = fields[ fieldIndex ].ToInteger() + ( adjustAmount * 5 ); + if ( val < 5 ) { + val = MP_PLAYER_MAXFRAGS; + } else if ( val > MP_PLAYER_MAXFRAGS ) { + val = 5; + } + + fields[ fieldIndex ].SetInteger( val ); + si_fragLimit.SetInteger( val ); + + matchParameters.serverInfo.SetInt( "si_fragLimit", si_fragLimit.GetInteger() ); + session->UpdateMatchParms( matchParameters ); + } + + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); +} + +/* +======================== +idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_MatchSettings::idMenuDataSource_MatchSettings::IsDataChanged() const { + + if ( fields[ MATCH_FIELD_TIME ].ToString() != originalFields[ MATCH_FIELD_TIME ].ToString() ) { + return true; + } + + if ( fields[ MATCH_FIELD_MAP ].ToString() != originalFields[ MATCH_FIELD_MAP ].ToString() ) { + return true; + } + + if ( fields[ MATCH_FIELD_MODE ].ToString() != originalFields[ MATCH_FIELD_MODE ].ToString() ) { + return true; + } + + if ( fields[ MATCH_FIELD_SCORE ].ToInteger() != originalFields[ MATCH_FIELD_SCORE ].ToInteger() ) { + return true; + } + + return false; +} diff --git a/neo/d3xp/menus/MenuScreen_Shell_ModeSelect.cpp b/neo/d3xp/menus/MenuScreen_Shell_ModeSelect.cpp new file mode 100644 index 00000000..c24ff7b2 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_ModeSelect.cpp @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SETTING_OPTIONS = 8; + +/* +======================== +idMenuScreen_Shell_ModeSelect::Initialize +======================== +*/ +void idMenuScreen_Shell_ModeSelect::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuModeSelect" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_SETTING_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + const idStrList & modes = common->GetModeDisplayList(); + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + for ( int i = 0; i < modes.Num(); ++i ) { + idList< idStr > option; + option.Append( modes[i] ); + menuOptions.Append( option ); + } + options->SetListData( menuOptions ); + + const char * tips[] = { "#str_swf_deathmatch_desc", "#str_swf_tourney_desc", "#str_swf_team_deathmatch_desc", "#str_swf_lastman_desc", "#str_swf_ctf_desc" }; + + while ( options->GetChildren().Num() < NUM_SETTING_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + + if ( options->GetChildren().Num() < menuOptions.Num() ) { + buttonWidget->SetDescription( tips[options->GetChildren().Num()] ); + } + + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_multiplayer" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_ModeSelect::Update +======================== +*/ +void idMenuScreen_Shell_ModeSelect::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_find_match_heading" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_ModeSelect::ShowScreen +======================== +*/ +void idMenuScreen_Shell_ModeSelect::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_ModeSelect::HideScreen +======================== +*/ +void idMenuScreen_Shell_ModeSelect::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_ModeSelect::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_ModeSelect::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_MODE_SELECT ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_PARTY_LOBBY, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( options->GetFocusIndex() != selectionIndex ) { + options->SetFocusIndex( selectionIndex ); + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + } + + idMatchParameters matchParameters = idMatchParameters( session->GetPartyLobbyBase().GetMatchParms() ); + matchParameters.gameMap = GAME_MAP_RANDOM; + matchParameters.gameMode = selectionIndex; + + // Always a public match. + matchParameters.matchFlags &= ~MATCH_INVITE_ONLY; + + session->UpdatePartyParms( matchParameters ); + + // Update flags for game lobby. + matchParameters.matchFlags = DefaultPartyFlags | DefaultPublicGameFlags; + + cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); + + // Force a default value for the si_timelimit and si_fraglimit for quickmatch + matchParameters.serverInfo.SetInt( "si_timelimit", 15 ); + matchParameters.serverInfo.SetInt( "si_fraglimit", 10 ); + + session->FindOrCreateMatch( matchParameters ); + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_NewGame.cpp b/neo/d3xp/menus/MenuScreen_Shell_NewGame.cpp new file mode 100644 index 00000000..17bebe76 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_NewGame.cpp @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_NEW_GAME_OPTIONS = 8; +/* +======================== +idMenuScreen_Shell_NewGame::Initialize +======================== +*/ +void idMenuScreen_Shell_NewGame::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuNewGame" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + option.Append( "#str_swf_doom3" ); // doom 3 + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_resurrection" ); // resurrection of evil + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_lost_episodes" ); // lost episodes + menuOptions.Append( option ); + + options->SetListData( menuOptions ); + options->SetNumVisibleOptions( NUM_NEW_GAME_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + + while ( options->GetChildren().Num() < NUM_NEW_GAME_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_campaign" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_NewGame::Update +======================== +*/ +void idMenuScreen_Shell_NewGame::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_02207" ); // NEW GAME + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_NewGame::ShowScreen +======================== +*/ +void idMenuScreen_Shell_NewGame::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_NewGame::HideScreen +======================== +*/ +void idMenuScreen_Shell_NewGame::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_NewGame::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_NewGame::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData != NULL ) { + if ( menuData->ActiveScreen() != SHELL_AREA_NEW_GAME ) { + return false; + } + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + if ( menuData != NULL ) { + menuData->SetNextScreen( SHELL_AREA_CAMPAIGN, MENU_TRANSITION_SIMPLE ); + } + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( shell != NULL ) { + shell->SetNewGameType( selectionIndex ); + menuData->SetNextScreen( SHELL_AREA_DIFFICULTY, MENU_TRANSITION_SIMPLE ); + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_PartyLobby.cpp b/neo/d3xp/menus/MenuScreen_Shell_PartyLobby.cpp new file mode 100644 index 00000000..7d1281ec --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_PartyLobby.cpp @@ -0,0 +1,697 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_LOBBY_OPTIONS = 8; + +extern idCVar net_inviteOnly; +extern idCVar si_map; +extern idCVar si_mode; + +enum partyLobbyCmds_t { + PARTY_CMD_QUICK, + PARTY_CMD_FIND, + PARTY_CMD_CREATE, + PARTY_CMD_PWF, + PARTY_CMD_INVITE, + PARTY_CMD_LEADERBOARDS, + PARTY_CMD_TOGGLE_PRIVACY, + PARTY_CMD_SHOW_PARTY_GAMES, +}; + +/* +======================== +idMenuScreen_Shell_PartyLobby::Initialize +======================== +*/ +void idMenuScreen_Shell_PartyLobby::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuPartyLobby" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_LOBBY_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + while ( options->GetChildren().Num() < NUM_LOBBY_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_02305" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + lobby = new (TAG_SWF) idMenuWidget_LobbyList(); + lobby->SetNumVisibleOptions( 8 ); + lobby->SetSpritePath( GetSpritePath(), "options" ); + lobby->SetWrappingAllowed( true ); + lobby->Initialize( data ); + while ( lobby->GetChildren().Num() < 8 ) { + idMenuWidget_LobbyButton * const buttonWidget = new idMenuWidget_LobbyButton(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_SELECT_GAMERTAG, lobby->GetChildren().Num() ); + buttonWidget->AddEventAction( WIDGET_EVENT_COMMAND ).Set( WIDGET_ACTION_MUTE_PLAYER, lobby->GetChildren().Num() ); + buttonWidget->Initialize( data ); + lobby->AddChild( buttonWidget ); + } + AddChild( lobby ); + + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( lobby, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::Update +======================== +*/ +void idMenuScreen_Shell_PartyLobby::Update() { + + idLobbyBase & activeLobby = session->GetPartyLobbyBase(); + if ( lobby != NULL ) { + if ( activeLobby.GetNumActiveLobbyUsers() != 0 ) { + if ( lobby->GetFocusIndex() >= activeLobby.GetNumActiveLobbyUsers() ) { + lobby->SetFocusIndex( activeLobby.GetNumActiveLobbyUsers() - 1 ); + lobby->SetViewIndex( lobby->GetViewOffset() + lobby->GetFocusIndex() ); + } + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_multiplayer" ); // MULTIPLAYER + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + UpdateOptions(); + + if ( menuData != NULL && menuData->NextScreen() == SHELL_AREA_PARTY_LOBBY ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + + lobbyUserID_t luid; + if ( isHost && CanKickSelectedPlayer( luid ) ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY4 ); + buttonInfo->label = "#str_swf_kick"; + buttonInfo->action.Set( WIDGET_ACTION_JOY4_ON_PRESS ); + } + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_swf_view_profile"; + } + buttonInfo->action.Set( WIDGET_ACTION_SELECT_GAMERTAG ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +void idMenuScreen_Shell_PartyLobby::UpdateOptions() { + + bool forceUpdate = false; + + + if ( ( session->GetPartyLobbyBase().IsHost() && ( !isHost || forceUpdate ) ) && options != NULL ) { + + menuOptions.Clear(); + idList< idStr > option; + + isHost = true; + isPeer = false; + + option.Append( "#str_swf_join_public" ); // Quick Match + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_find_match" ); // Find Match + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_create_private" ); // Create Match + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_pwf" ); // Play With Friends + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_leaderboards" ); // Play With Friends + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_invite_only" ); // Toggle privacy + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_invite_friends" ); // Invite Friends + menuOptions.Append( option ); + + idMenuWidget_Button * buttonWidget = NULL; + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_QUICK, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_quick_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_FIND, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_find_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_CREATE, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_create_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_PWF, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_pwf_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_LEADERBOARDS, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_leaderboards_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_TOGGLE_PRIVACY, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_toggle_privacy_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_INVITE, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_invite_desc" ); + } + + options->SetListData( menuOptions ); + + } else if ( session->GetPartyLobbyBase().IsPeer() && options != NULL ) { + if ( !isPeer || forceUpdate ) { + + menuOptions.Clear(); + idList< idStr > option; + + idMenuWidget_Button * buttonWidget = NULL; + option.Append( "#str_swf_leaderboards" ); // Play With Friends + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_invite_friends" ); // Play With Friends + menuOptions.Append( option ); + + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_LEADERBOARDS, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_leaderboards_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PARTY_CMD_INVITE, index ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_invite_desc" ); + } + + options->SetListData( menuOptions ); + + } + + isPeer = true; + isHost = false; + } + + if ( forceUpdate ) { + options->Update(); + } + +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::ShowScreen +======================== +*/ +void idMenuScreen_Shell_PartyLobby::ShowScreen( const mainMenuTransition_t transitionType ) { + + isPeer = false; + isHost = false; + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFSpriteInstance * waitTime = GetSprite()->GetScriptObject()->GetNestedSprite( "waitTime" ); + if ( waitTime != NULL ) { + waitTime->SetVisible( false ); + } + } + + if ( session->GetPartyLobbyBase().IsHost() ) { + idMatchParameters matchParameters = session->GetPartyLobbyBase().GetMatchParms(); + if ( net_inviteOnly.GetBool() ) { + matchParameters.matchFlags |= MATCH_INVITE_ONLY; + } else { + matchParameters.matchFlags &= ~MATCH_INVITE_ONLY; + } + + matchParameters.numSlots = session->GetTitleStorageInt("MAX_PLAYERS_ALLOWED", 4 ); + + session->UpdatePartyParms( matchParameters ); + } + + idMenuScreen::ShowScreen( transitionType ); + if ( lobby != NULL ) { + lobby->SetFocusIndex( 0 ); + } +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::HideScreen +======================== +*/ +void idMenuScreen_Shell_PartyLobby::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::CanKickSelectedPlayer +======================== +*/ +bool idMenuScreen_Shell_PartyLobby::CanKickSelectedPlayer( lobbyUserID_t & luid ) { + + idMatchParameters matchParameters = session->GetPartyLobbyBase().GetMatchParms(); + const int playerId = lobby->GetFocusIndex(); + + // can't kick yourself + idLobbyBase & activeLobby = session->GetPartyLobbyBase(); + luid = activeLobby.GetLobbyUserIdByOrdinal( playerId ); + if ( session->GetSignInManager().GetMasterLocalUser() == activeLobby.GetLocalUserFromLobbyUser( luid ) ) { + return false; + } + + return true; +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::ShowLeaderboards +======================== +*/ +void idMenuScreen_Shell_PartyLobby::ShowLeaderboards() { + + const bool canPlayOnline = session->GetSignInManager().GetMasterLocalUser() != NULL && session->GetSignInManager().GetMasterLocalUser()->CanPlayOnline(); + + if ( !canPlayOnline ) { + common->Dialog().AddDialog( GDM_LEADERBOARD_ONLINE_NO_PROFILE, DIALOG_CONTINUE, NULL, NULL, true, __FUNCTION__, __LINE__, false ); + } else if ( menuData != NULL ) { + menuData->SetNextScreen( SHELL_AREA_LEADERBOARDS, MENU_TRANSITION_SIMPLE ); + } +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_PartyLobby::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_PARTY_LOBBY ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_JOY4_ON_PRESS: { + + idLobbyBase & activeLobby = session->GetPartyLobbyBase(); + lobbyUserID_t luid; + if ( CanKickSelectedPlayer( luid ) ) { + activeLobby.KickLobbyUser( luid ); + } + return true; + } + case WIDGET_ACTION_GO_BACK: { + class idSWFScriptFunction_Accept : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Accept() { } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_LEAVE_LOBBY_RET_MAIN ); + session->Cancel(); + + return idSWFScriptVar(); + } + }; + class idSWFScriptFunction_Cancel : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Cancel() { } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_LEAVE_LOBBY_RET_MAIN ); + return idSWFScriptVar(); + } + }; + + idLobbyBase & activeLobby = session->GetActivePlatformLobbyBase(); + + if( activeLobby.GetNumActiveLobbyUsers() > 1 ) { + common->Dialog().AddDialog( GDM_LEAVE_LOBBY_RET_MAIN, DIALOG_ACCEPT_CANCEL, new (TAG_SWF) idSWFScriptFunction_Accept(), new (TAG_SWF) idSWFScriptFunction_Cancel(), false ); + } else { + session->Cancel(); + } + + return true; + } + case WIDGET_ACTION_MUTE_PLAYER: { + + if ( parms.Num() != 1 ) { + return true; + } + + int index = parms[0].ToInteger(); + + idLobbyBase & activeLobby = session->GetPartyLobbyBase(); + lobbyUserID_t luid = activeLobby.GetLobbyUserIdByOrdinal( index ); + if ( luid.IsValid() ) { + session->ToggleLobbyUserVoiceMute( luid ); + } + + return true; + } + case WIDGET_ACTION_COMMAND: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 1 ) { + selectionIndex = parms[1].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + switch ( parms[0].ToInteger() ) { + case PARTY_CMD_QUICK: { + idMatchParameters matchParameters = idMatchParameters( session->GetPartyLobbyBase().GetMatchParms() ); + + // Reset these to random for quick match. + matchParameters.gameMap = GAME_MAP_RANDOM; + matchParameters.gameMode = GAME_MODE_RANDOM; + + // Always a public match. + matchParameters.matchFlags &= ~MATCH_INVITE_ONLY; + + session->UpdatePartyParms( matchParameters ); + + // Update flags for game lobby. + matchParameters.matchFlags = DefaultPartyFlags | DefaultPublicGameFlags; + cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); + + // Force a default value for the si_timelimit and si_fraglimit for quickmatch + matchParameters.serverInfo.SetInt( "si_timelimit", 15 ); + matchParameters.serverInfo.SetInt( "si_fraglimit", 10 ); + + session->FindOrCreateMatch( matchParameters ); + break; + } + case PARTY_CMD_FIND: { + menuData->SetNextScreen( SHELL_AREA_MODE_SELECT, MENU_TRANSITION_SIMPLE ); + break; + } + case PARTY_CMD_CREATE: { + idMatchParameters matchParameters = idMatchParameters( session->GetPartyLobbyBase().GetMatchParms() ); + + const bool isInviteOnly = MatchTypeInviteOnly( matchParameters.matchFlags ); + + matchParameters.matchFlags = DefaultPartyFlags | DefaultPrivateGameFlags; + + if ( isInviteOnly ) { + matchParameters.matchFlags |= MATCH_INVITE_ONLY; + } + + int mode = idMath::ClampInt( -1, GAME_COUNT - 1, si_mode.GetInteger() ); + const idList< mpMap_t > maps = common->GetMapList(); + int map = idMath::ClampInt( -1, maps.Num() - 1, si_map.GetInteger() ); + + matchParameters.gameMap = map; + matchParameters.gameMode = mode; + cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); + session->CreateMatch( matchParameters ); + break; + } + case PARTY_CMD_PWF: { + menuData->SetNextScreen( SHELL_AREA_BROWSER, MENU_TRANSITION_SIMPLE ); + break; + } + case PARTY_CMD_LEADERBOARDS: { + ShowLeaderboards(); + break; + } + case PARTY_CMD_TOGGLE_PRIVACY: { + idMatchParameters matchParameters = idMatchParameters( session->GetPartyLobbyBase().GetMatchParms() ); + matchParameters.matchFlags ^= MATCH_INVITE_ONLY; + session->UpdatePartyParms( matchParameters ); + int bitSet = ( matchParameters.matchFlags & MATCH_INVITE_ONLY ); + net_inviteOnly.SetBool( bitSet != 0 ? true : false ); + break; + } + case PARTY_CMD_SHOW_PARTY_GAMES: { + session->ShowPartySessions(); + break; + } + case PARTY_CMD_INVITE: { + if ( session->GetPartyLobbyBase().IsLobbyFull() ) { + common->Dialog().AddDialog( GDM_CANNOT_INVITE_LOBBY_FULL, DIALOG_CONTINUE, NULL, NULL, true, __FUNCTION__, __LINE__, false ); + return true; + } + + InvitePartyOrFriends(); + break; + } + } + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + case WIDGET_ACTION_SELECT_GAMERTAG: { + int selectionIndex = lobby->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != lobby->GetFocusIndex() ) { + lobby->SetViewIndex( lobby->GetViewOffset() + selectionIndex ); + lobby->SetFocusIndex( selectionIndex ); + return true; + } + + idLobbyBase & activeLobby = session->GetPartyLobbyBase(); + lobbyUserID_t luid = activeLobby.GetLobbyUserIdByOrdinal( selectionIndex ); + if ( luid.IsValid() ) { + session->ShowLobbyUserGamerCardUI( luid ); + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuScreen_Shell_PartyLobby::UpdateLobby +======================== +*/ +void idMenuScreen_Shell_PartyLobby::UpdateLobby() { + + if ( menuData != NULL && menuData->ActiveScreen() != SHELL_AREA_PARTY_LOBBY ) { + return; + } + + // Keep this menu in sync with the session host/peer status. + if ( session->GetPartyLobbyBase().IsHost() && !isHost ) { + Update(); + } + + if ( session->GetPartyLobbyBase().IsPeer() && !isPeer ) { + Update(); + } + + if ( isPeer ) { + Update(); + } + + UpdateOptions(); + + // setup names for lobby; + if ( lobby != NULL ) { + idMenuHandler_Shell * mgr = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( mgr != NULL ) { + mgr->UpdateLobby( lobby ); + lobby->Update(); + } + + if ( lobby->GetNumEntries() > 0 && lobby->GetFocusIndex() >= lobby->GetNumEntries() ) { + lobby->SetFocusIndex( lobby->GetNumEntries() - 1 ); + lobby->SetViewIndex( lobby->GetNumEntries() - 1 ); + } + } + + if ( session->GetState() == idSession::PARTY_LOBBY ) { + + if ( options != NULL ) { + if ( options->GetFocusIndex() >= options->GetTotalNumberOfOptions() && options->GetTotalNumberOfOptions() > 0 ) { + options->SetViewIndex( options->GetTotalNumberOfOptions() - 1 ); + options->SetFocusIndex( options->GetTotalNumberOfOptions() - 1 ); + } + } + + idSWFTextInstance * privacy = GetSprite()->GetScriptObject()->GetNestedText( "matchInfo", "txtPrivacy" ); + if ( privacy != NULL ) { + if ( isPeer ) { + privacy->SetText( "" ); + } else { + + idMatchParameters matchParameters = session->GetPartyLobbyBase().GetMatchParms(); + int bitSet = ( matchParameters.matchFlags & MATCH_INVITE_ONLY ); + bool privacySet = ( bitSet != 0 ? true : false ); + if ( privacySet ) { + privacy->SetText( "#str_swf_privacy_closed" ); + privacy->SetStrokeInfo( true ); + } else if ( !privacySet ) { + privacy->SetText( "#str_swf_privacy_open" ); + privacy->SetStrokeInfo( true ); + } + } + } + + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user != NULL && options != NULL ) { + if ( user->IsInParty() && user->GetPartyCount() > 1 && !session->IsPlatformPartyInLobby() && menuOptions.Num() > 0 ) { + if ( menuOptions[ menuOptions.Num() - 1 ][0] != "#str_swf_invite_xbox_live_party" ) { + menuOptions[ menuOptions.Num() - 1 ][0] = "#str_swf_invite_xbox_live_party"; // invite Xbox LIVE party + options->SetListData( menuOptions ); + options->Update(); + } + } else if ( menuOptions.Num() > 0 ) { + if ( menuOptions[ menuOptions.Num() - 1 ][0] != "#str_swf_invite_friends" ) { + menuOptions[ menuOptions.Num() - 1 ][0] = "#str_swf_invite_friends"; // invite Xbox LIVE party + options->SetListData( menuOptions ); + options->Update(); + } + } + } + } +} diff --git a/neo/d3xp/menus/MenuScreen_Shell_Pause.cpp b/neo/d3xp/menus/MenuScreen_Shell_Pause.cpp new file mode 100644 index 00000000..5c94792a --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Pause.cpp @@ -0,0 +1,481 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +extern idCVar g_demoMode; +const static int NUM_PAUSE_OPTIONS = 6; + +enum pauseMenuCmds_t { + PAUSE_CMD_RESTART, + PAUSE_CMD_DEAD_RESTART, + PAUSE_CMD_SETTINGS, + PAUSE_CMD_EXIT, + PAUSE_CMD_LEAVE, + PAUSE_CMD_RETURN, + PAUSE_CMD_LOAD, + PAUSE_CMD_SAVE, + PAUSE_CMD_PS3, + PAUSE_CMD_INVITE_FRIENDS +}; + +/* +======================== +idMenuScreen_Shell_Pause::Initialize +======================== +*/ +void idMenuScreen_Shell_Pause::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuPause" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_PAUSE_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + while ( options->GetChildren().Num() < NUM_PAUSE_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Pause::Update +======================== +*/ +void idMenuScreen_Shell_Pause::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + + bool isDead = false; + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + if ( player->health <= 0 ) { + isDead = true; + } + } + + if ( !isDead ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_RETURN ); + } + } + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Pause::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Pause::ShowScreen( const mainMenuTransition_t transitionType ) { + + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + + bool isDead = false; + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + if ( player->health <= 0 ) { + isDead = true; + } + } + + if ( g_demoMode.GetBool() ) { + isMpPause = false; + if ( isDead ) { + option.Append( "#str_swf_restart_map" ); // retart map + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_exit_game" ); // exit game + menuOptions.Append( option ); + + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_DEAD_RESTART ); + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_SETTINGS ); + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_LEAVE ); + } else { + option.Append( "#str_04106" ); // return to game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_restart_map" ); // retart map + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_exit_game" ); // exit game + menuOptions.Append( option ); + + int index = 0; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_RETURN ); + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_RESTART ); + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_SETTINGS ); + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_EXIT ); + } + } else { + if ( common->IsMultiplayer() ) { + isMpPause = true; + option.Append( "#str_04106" ); // return to game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_invite_friends_upper" ); // settings + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_leave_game" ); // leave game + menuOptions.Append( option ); + + int index = 0; + idMenuWidget_Button * buttonWidget = NULL; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_RETURN ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_resume_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_SETTINGS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_INVITE_FRIENDS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_invite_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_LEAVE ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_exit_game_desc" ); + } + + } else { + isMpPause = false; + if ( isDead ) { + option.Append( "#str_02187" ); // load game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_exit_game" ); // exit game + menuOptions.Append( option ); + + int index = 0; + idMenuWidget_Button * buttonWidget = NULL; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_LOAD ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02213" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_SETTINGS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_EXIT ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_exit_game_desc" ); + } + + } else { + option.Append( "#str_04106" ); // return to game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_02179" ); // save game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_02187" ); // load game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_exit_game" ); // exit game + menuOptions.Append( option ); + + int index = 0; + idMenuWidget_Button * buttonWidget = NULL; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_RETURN ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_resume_desc" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_SAVE ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02211" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_LOAD ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02213" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_SETTINGS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, PAUSE_CMD_EXIT ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_exit_game_desc" ); + } + } + } + } + + options->SetListData( menuOptions ); + idMenuScreen::ShowScreen( transitionType ); + + if ( options->GetFocusIndex() >= menuOptions.Num() ) { + options->SetViewIndex( 0 ); + options->SetFocusIndex( 0 ); + } + +} + +/* +======================== +idMenuScreen_Shell_Pause::HideScreen +======================== +*/ +void idMenuScreen_Shell_Pause::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Pause::HandleExitGameBtn +======================== +*/ +void idMenuScreen_Shell_Pause::HandleExitGameBtn() { + class idSWFScriptFunction_QuitDialog : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_QuitDialog( idMenuScreen_Shell_Pause * _menu, gameDialogMessages_t _msg, bool _accept ) { + menu = _menu; + msg = _msg; + accept = _accept; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + } + return idSWFScriptVar(); + } + private: + idMenuScreen_Shell_Pause * menu; + gameDialogMessages_t msg; + bool accept; + }; + + gameDialogMessages_t msg = GDM_SP_QUIT_SAVE; + + if ( common->IsMultiplayer() ) { + if ( ( session->GetGameLobbyBase().GetNumLobbyUsers() > 1 ) && MatchTypeHasStats( session->GetGameLobbyBase().GetMatchParms().matchFlags ) ) { + msg = GDM_MULTI_VDM_QUIT_LOSE_LEADERBOARDS; + } else { + msg = GDM_MULTI_VDM_QUIT; + } + } + + common->Dialog().AddDialog( msg, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_QuitDialog( this, msg, true ), new idSWFScriptFunction_QuitDialog( this, msg, false ), false ); +} + +/* +======================== +idMenuScreen_Shell_Pause::HandleRestartBtn +======================== +*/ +void idMenuScreen_Shell_Pause::HandleRestartBtn() { + class idSWFScriptFunction_RestartDialog : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_RestartDialog( idMenuScreen_Shell_Pause * _menu, gameDialogMessages_t _msg, bool _accept ) { + menu = _menu; + msg = _msg; + accept = _accept; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept ) { + cmdSystem->AppendCommandText( "restartMap\n" ); + } + return idSWFScriptVar(); + } + private: + idMenuScreen_Shell_Pause * menu; + gameDialogMessages_t msg; + bool accept; + }; + + common->Dialog().AddDialog( GDM_SP_RESTART_SAVE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_RestartDialog( this, GDM_SP_RESTART_SAVE, true ), new idSWFScriptFunction_RestartDialog( this, GDM_SP_RESTART_SAVE, false ), false ); +} + +/* +======================== +idMenuScreen_Shell_Pause::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Pause::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_ROOT ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_COMMAND: { + switch ( parms[0].ToInteger() ) { + case PAUSE_CMD_RESTART: { + HandleRestartBtn(); + break; + } + case PAUSE_CMD_DEAD_RESTART: { + cmdSystem->AppendCommandText( "restartMap\n" ); + break; + } + case PAUSE_CMD_SETTINGS: { + menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE ); + break; + } + case PAUSE_CMD_LEAVE: + case PAUSE_CMD_EXIT: { + HandleExitGameBtn(); + break; + } + case PAUSE_CMD_RETURN: { + menuData->SetNextScreen( SHELL_AREA_INVALID, MENU_TRANSITION_SIMPLE ); + break; + } + case PAUSE_CMD_LOAD: { + menuData->SetNextScreen( SHELL_AREA_LOAD, MENU_TRANSITION_SIMPLE ); + break; + } + case PAUSE_CMD_SAVE: { + menuData->SetNextScreen( SHELL_AREA_SAVE, MENU_TRANSITION_SIMPLE ); + break; + } + case PAUSE_CMD_PS3: { + menuData->SetNextScreen( SHELL_AREA_PLAYSTATION, MENU_TRANSITION_SIMPLE ); + break; + } + case PAUSE_CMD_INVITE_FRIENDS: { + session->InviteFriends(); + break; + } + } + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Playstation.cpp b/neo/d3xp/menus/MenuScreen_Shell_Playstation.cpp new file mode 100644 index 00000000..af7f1eea --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Playstation.cpp @@ -0,0 +1,207 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SETTING_OPTIONS = 8; +/* +======================== +idMenuScreen_Shell_Playstation::Initialize +======================== +*/ +void idMenuScreen_Shell_Playstation::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuPlaystation" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + option.Append( "#str_swf_friends" ); // FRIENDS + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_swf_check_for_invites" ); // CHECK FOR INVITES + menuOptions.Append( option ); + options->SetListData( menuOptions ); + options->SetNumVisibleOptions( NUM_SETTING_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + + while ( options->GetChildren().Num() < NUM_SETTING_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * >( data ); + if ( handler != NULL && handler->GetInGame() ) { + btnBack->SetLabel( "#str_swf_pause_menu" ); + } else { + btnBack->SetLabel( "#str_02305" ); + } + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Playstation::Update +======================== +*/ +void idMenuScreen_Shell_Playstation::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_playstation" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Playstation::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Playstation::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Playstation::HideScreen +======================== +*/ +void idMenuScreen_Shell_Playstation::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Playstation::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Playstation::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_PLAYSTATION ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( options->GetFocusIndex() != selectionIndex ) { + options->SetFocusIndex( selectionIndex ); + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + } + + if ( selectionIndex == 0 ) { + + + } else if ( selectionIndex == 1 ) { + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_PressStart.cpp b/neo/d3xp/menus/MenuScreen_Shell_PressStart.cpp new file mode 100644 index 00000000..2f3b726d --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_PressStart.cpp @@ -0,0 +1,302 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" +#include "../../framework/Common_local.h" + +static const int NUM_GAME_SELECTIONS_VISIBLE = 5; +extern idCVar g_demoMode; + +namespace { + /* + ================================================ + UICmd_RegisterUser + ================================================ + */ + class UICmd_RegisterUser : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + idLib::Warning( "No device specified when registering mouse user" ); + return idSWFScriptVar(); + } + + const int device = parms[ 0 ].ToInteger(); + session->GetSignInManager().RegisterLocalUser( device ); + return idSWFScriptVar(); + } + }; +} + +/* +======================== +idMenuScreen_Shell_PressStart::Initialize +======================== +*/ +void idMenuScreen_Shell_PressStart::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuStart" ); + + itemList = new (TAG_SWF) idMenuWidget_Carousel(); + itemList->SetSpritePath( GetSpritePath(), "info", "options" ); + itemList->SetNumVisibleOptions( NUM_GAME_SELECTIONS_VISIBLE ); + while ( itemList->GetChildren().Num() < NUM_GAME_SELECTIONS_VISIBLE ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, itemList->GetChildren().Num() ); + buttonWidget->Initialize( data ); + itemList->AddChild( buttonWidget ); + } + itemList->Initialize( data ); + + AddChild( itemList ); + + AddEventAction( WIDGET_EVENT_SCROLL_LEFT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER, WIDGET_EVENT_SCROLL_LEFT ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER, WIDGET_EVENT_SCROLL_RIGHT ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_LEFT_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_RELEASE ) ); + + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER, WIDGET_EVENT_SCROLL_LEFT_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_LSTICK ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE ) ); + + doomCover = declManager->FindMaterial( "guis/assets/mainmenu/doom_cover.tga" ); + doom2Cover = declManager->FindMaterial( "guis/assets/mainmenu/doom2_cover.tga" ); + doom3Cover = declManager->FindMaterial( "guis/assets/mainmenu/doom3_cover.tga" ); + + startButton = new idMenuWidget_Button(); + startButton->SetSpritePath( GetSpritePath(), "info", "btnStart" ); + AddChild( startButton ); +} + +/* +======================== +idMenuScreen_Shell_Root::Update +======================== +*/ +void idMenuScreen_Shell_PressStart::Update() { + + if ( !g_demoMode.GetBool() ) { + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_PressStart::ShowScreen +======================== +*/ +void idMenuScreen_Shell_PressStart::ShowScreen( const mainMenuTransition_t transitionType ) { + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + if ( g_demoMode.GetBool() ) { + + idList coverIcons; + if ( itemList != NULL ) { + itemList->SetListImages( coverIcons ); + } + + if ( startButton != NULL ) { + startButton->BindSprite( root ); + startButton->SetLabel( idLocalization::GetString( "#str_swf_press_start" ) ); + } + + idSWFSpriteInstance * backing = GetSprite()->GetScriptObject()->GetNestedSprite( "backing" ); + if ( backing != NULL ) { + backing->SetVisible( false ); + } + + } else { + + idList coverIcons; + + coverIcons.Append( doomCover ); + coverIcons.Append( doom3Cover ); + coverIcons.Append( doom2Cover ); + + if ( itemList != NULL ) { + itemList->SetListImages( coverIcons ); + itemList->SetFocusIndex( 1, true ); + itemList->SetViewIndex( 1 ); + itemList->SetMoveToIndex( 1 ); + } + + if ( startButton != NULL ) { + startButton->BindSprite( root ); + startButton->SetLabel( "" ); + } + + idSWFSpriteInstance * backing = GetSprite()->GetScriptObject()->GetNestedSprite( "backing" ); + if ( backing != NULL ) { + backing->SetVisible( true ); + } + + } + } + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_PressStart::HideScreen +======================== +*/ +void idMenuScreen_Shell_PressStart::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_PressStart::HandleAction +======================== +*/ +bool idMenuScreen_Shell_PressStart::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_START ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( itemList == NULL ) { + return true; + } + + if ( event.parms.Num() != 1 ) { + return true; + } + + if ( itemList->GetMoveToIndex() != itemList->GetViewIndex() ) { + return true; + } + + if ( parms.Num() > 0 ) { + const int index = parms[0].ToInteger(); + if ( index != 0 ) { + itemList->MoveToIndex( index ); + Update(); + } + } + + if ( itemList->GetMoveToIndex() == 0 ) { + common->SwitchToGame( DOOM_CLASSIC ); + } else if ( itemList->GetMoveToIndex() == 1 ) { + if ( session->GetSignInManager().GetMasterLocalUser() == NULL ) { + const int device = event.parms[ 0 ].ToInteger(); + session->GetSignInManager().RegisterLocalUser( device ); + } else { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + } + } else if ( itemList->GetMoveToIndex() == 2 ) { + common->SwitchToGame( DOOM2_CLASSIC ); + } + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + idWidgetAction repeatAction; + widgetAction_t repeatActionType = static_cast< widgetAction_t >( parms[ 0 ].ToInteger() ); + assert( parms.Num() == 2 ); + repeatAction.Set( repeatActionType, parms[ 1 ] ); + menuData->StartWidgetActionRepeater( widget, repeatAction, event ); + return true; + } + case WIDGET_ACTION_STOP_REPEATER: { + menuData->ClearWidgetActionRepeater(); + return true; + } + case WIDGET_ACTION_SCROLL_HORIZONTAL: { + + if ( itemList == NULL ) { + return true; + } + + if ( itemList->GetTotalNumberOfOptions() <= 1 ) { + return true; + } + + idLib::Printf( "scroll \n" ); + + if ( itemList->GetMoveDiff() > 0 ) { + itemList->MoveToIndex( itemList->GetMoveToIndex(), true ); + } + + int direction = parms[0].ToInteger(); + if ( direction == 1 ) { + if ( itemList->GetViewIndex() == itemList->GetTotalNumberOfOptions() - 1 ) { + return true; + } else { + itemList->MoveToIndex( 1 ); + } + } else { + if ( itemList->GetViewIndex() == 0 ) { + return true; + } else { + itemList->MoveToIndex( ( itemList->GetNumVisibleOptions() / 2 ) + 1 ); + } + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Resolution.cpp b/neo/d3xp/menus/MenuScreen_Shell_Resolution.cpp new file mode 100644 index 00000000..8da907a4 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Resolution.cpp @@ -0,0 +1,295 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" +#include "../../renderer/tr_local.h" + +const static int NUM_SETTING_OPTIONS = 7; + +enum settingMenuCmds_t { + SETTING_CMD_CONTROLS, + SETTING_CMD_GAMEPLAY, + SETTING_CMD_SYSTEM, + SETTING_CMD_3D, +}; + +/* +======================== +idMenuScreen_Shell_Resolution::Initialize +======================== +*/ +void idMenuScreen_Shell_Resolution::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuResolution" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_SETTING_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + + while ( options->GetChildren().Num() < NUM_SETTING_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_00183" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Resolution::Update +======================== +*/ +void idMenuScreen_Shell_Resolution::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_02154" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Resolution::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Resolution::ShowScreen( const mainMenuTransition_t transitionType ) { + + + originalOption.fullscreen = r_fullscreen.GetInteger(); + originalOption.vidmode = r_vidMode.GetInteger(); + + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + menuOptions.Alloc().Alloc() = "#str_swf_disabled"; + optionData.Append( optionData_t( 0, 0 ) ); + + int viewIndex = 0; + idList< idList > displays; + for ( int displayNum = 0 ; ; displayNum++ ) { + idList & modeList = displays.Alloc(); + if ( !R_GetModeListForDisplay( displayNum, modeList ) ) { + displays.RemoveIndex( displays.Num() - 1 ); + break; + } + } + for ( int displayNum = 0 ; displayNum < displays.Num(); displayNum++ ) { + idList & modeList = displays[displayNum]; + for ( int i = 0; i < modeList.Num(); i++ ) { + const optionData_t thisOption( displayNum + 1, i ); + if ( originalOption == thisOption ) { + viewIndex = menuOptions.Num(); + } + idStr str; + if ( displays.Num() > 1 ) { + str.Append( va( "%s %i: ", idLocalization::GetString( "#str_swf_monitor" ), displayNum+1 ) ); + } + str.Append( va( "%4i x %4i", modeList[i].width, modeList[i].height ) ); + if ( modeList[i].displayHz != 60 ) { + str.Append( va( " @ %dhz", modeList[i].displayHz ) ); + } + menuOptions.Alloc().Alloc() = str; + optionData.Append( thisOption ); + } + } + + options->SetListData( menuOptions ); + options->SetViewIndex( viewIndex ); + const int topOfLastPage = menuOptions.Num() - NUM_SETTING_OPTIONS; + if ( viewIndex < NUM_SETTING_OPTIONS ) { + options->SetViewOffset( 0 ); + options->SetFocusIndex( viewIndex ); + } else if ( viewIndex >= topOfLastPage ) { + options->SetViewOffset( topOfLastPage ); + options->SetFocusIndex( viewIndex - topOfLastPage ); + } else { + options->SetViewOffset( viewIndex ); + options->SetFocusIndex( 0 ); + } + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Resolution::HideScreen +======================== +*/ +void idMenuScreen_Shell_Resolution::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Resolution::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Resolution::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_RESOLUTION ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_SYSTEM_OPTIONS, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options != NULL ) { + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( options->GetFocusIndex() != selectionIndex ) { + options->SetFocusIndex( selectionIndex ); + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + } + const optionData_t & currentOption = optionData[options->GetViewIndex()]; + + if ( currentOption == originalOption ) { + // No change + menuData->SetNextScreen( SHELL_AREA_SYSTEM_OPTIONS, MENU_TRANSITION_SIMPLE ); + } else if ( currentOption.fullscreen == 0 ) { + // Changing to windowed mode + r_fullscreen.SetInteger( 0 ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + menuData->SetNextScreen( SHELL_AREA_SYSTEM_OPTIONS, MENU_TRANSITION_SIMPLE ); + } else { + // Changing to fullscreen mode + r_fullscreen.SetInteger( currentOption.fullscreen ); + r_vidMode.SetInteger( currentOption.vidmode ); + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + + class idSWFFuncAcceptVideoChanges : public idSWFScriptFunction_RefCounted { + public: + idSWFFuncAcceptVideoChanges( idMenuHandler * _menu, gameDialogMessages_t _msg, const optionData_t & _optionData, bool _accept ) { + menuHandler = _menu; + msg = _msg; + optionData = _optionData; + accept = _accept; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept ) { + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + if ( menuHandler != NULL ) { + menuHandler->SetNextScreen( SHELL_AREA_SYSTEM_OPTIONS, MENU_TRANSITION_SIMPLE ); + } + } else { + r_fullscreen.SetInteger( optionData.fullscreen ); + r_vidMode.SetInteger( optionData.vidmode ); + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + } + return idSWFScriptVar(); + } + private: + idMenuHandler * menuHandler; + gameDialogMessages_t msg; + optionData_t optionData; + bool accept; + }; + common->Dialog().AddDialog( GDM_CONFIRM_VIDEO_CHANGES, DIALOG_TIMER_ACCEPT_REVERT, new ( TAG_SWF ) idSWFFuncAcceptVideoChanges( menuData, GDM_CONFIRM_VIDEO_CHANGES, currentOption, true ), new ( TAG_SWF ) idSWFFuncAcceptVideoChanges( menuData, GDM_CONFIRM_VIDEO_CHANGES, originalOption, false ), false ); + } + return true; + } + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Root.cpp b/neo/d3xp/menus/MenuScreen_Shell_Root.cpp new file mode 100644 index 00000000..60e2300b --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Root.cpp @@ -0,0 +1,514 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +extern idCVar g_demoMode; +const static int NUM_MAIN_OPTIONS = 6; +/* +======================== +idMenuScreen_Shell_Root::Initialize +======================== +*/ +void idMenuScreen_Shell_Root::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuMain" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_MAIN_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->Initialize( data ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + while ( options->GetChildren().Num() < NUM_MAIN_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->Initialize( data ); + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + + + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER, WIDGET_EVENT_SCROLL_RIGHT ) ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_RIGHT_RELEASE ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER, WIDGET_EVENT_SCROLL_LEFT ) ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_LEFT_RELEASE ) ); + AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, 0 ); + +} + +/* +======================== +idMenuScreen_Shell_Root::Update +======================== +*/ +void idMenuScreen_Shell_Root::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + + if ( !g_demoMode.GetBool() ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + } + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idMenuScreen::Update(); +} + +enum rootMenuCmds_t { + ROOT_CMD_START_DEMO, + ROOT_CMD_START_DEMO2, + ROOT_CMD_SETTINGS, + ROOT_CMD_QUIT, + ROOT_CMD_DEV, + ROOT_CMD_CAMPAIGN, + ROOT_CMD_MULTIPLAYER, + ROOT_CMD_PLAYSTATION, + ROOT_CMD_CREDITS +}; + +/* +======================== +idMenuScreen_Shell_Root::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Root::ShowScreen( const mainMenuTransition_t transitionType ) { + + if ( menuData != NULL && menuData->GetPlatform() != 2 ) { + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + + int index = 0; + + if ( g_demoMode.GetBool() ) { + idMenuWidget_Button * buttonWidget = NULL; + + option.Append( "START DEMO" ); // START DEMO + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_START_DEMO ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "Launch the demo" ); + } + index++; + + if ( g_demoMode.GetInteger() == 2 ) { + option.Clear(); + option.Append( "START PRESS DEMO" ); // START DEMO + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_START_DEMO2 ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "Launch the press demo" ); + } + index++; + } + + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_SETTINGS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + + option.Clear(); + option.Append( "#str_swf_quit" ); // quit + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_QUIT ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_01976" ); + } + index++; + + } else { + + idMenuWidget_Button * buttonWidget = NULL; + +#if !defined ( ID_RETAIL ) + option.Append( "DEV" ); // DEV + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_DEV ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "View a list of maps available for play" ); + } + index++; +#endif + + option.Clear(); + option.Append( "#str_swf_campaign" ); // singleplayer + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_CAMPAIGN ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_campaign_desc" ); + } + index++; + + option.Clear(); + option.Append( "#str_swf_multiplayer" ); // multiplayer + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_MULTIPLAYER ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02215" ); + } + index++; + + option.Clear(); + option.Append( "#str_swf_settings" ); // settings + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_SETTINGS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02206" ); + } + index++; + + + option.Clear(); + option.Append( "#str_swf_credits" ); // credits + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_CREDITS ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02219" ); + } + index++; + + // only add quit option for PC + option.Clear(); + option.Append( "#str_swf_quit" ); // quit + menuOptions.Append( option ); + options->GetChildByIndex( index ).ClearEventActions(); + options->GetChildByIndex( index ).AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, ROOT_CMD_QUIT ); + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_01976" ); + } + index++; + } + options->SetListData( menuOptions ); + } else { + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + options->SetListData( menuOptions ); + } + + idMenuScreen::ShowScreen( transitionType ); + + if ( menuData != NULL && menuData->GetPlatform() == 2 ) { + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( shell != NULL ) { + idMenuWidget_MenuBar * menuBar = shell->GetMenuBar(); + if ( menuBar != NULL ) { + menuBar->SetFocusIndex( GetRootIndex() ); + } + } + } +} + +/* +======================== +idMenuScreen_Shell_Root::HideScreen +======================== +*/ +void idMenuScreen_Shell_Root::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Root::HandleExitGameBtn +======================== +*/ +void idMenuScreen_Shell_Root::HandleExitGameBtn() { + class idSWFScriptFunction_QuitDialog : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_QuitDialog( gameDialogMessages_t _msg, int _accept ) { + msg = _msg; + accept = _accept; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept == 1 ) { + common->Quit(); + } else if ( accept == -1 ) { + session->MoveToPressStart(); + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + int accept; + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_QuitDialog( GDM_QUIT_GAME, 1 ) ); + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_QuitDialog( GDM_QUIT_GAME, 0 ) ); + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_QuitDialog( GDM_QUIT_GAME, -1 ) ); + optionText.Append( idStrId( "#STR_SWF_ACCEPT" ) ); + optionText.Append( idStrId( "#STR_SWF_CANCEL" ) ); + optionText.Append( idStrId( "#str_swf_change_game" ) ); + + common->Dialog().AddDynamicDialog( GDM_QUIT_GAME, callbacks, optionText, true, "" ); +} + +/* +======================== +idMenuScreen_Shell_Root::GetRootIndex +======================== +*/ +int idMenuScreen_Shell_Root::GetRootIndex() { + if ( options != NULL ) { + return options->GetFocusIndex(); + } + + return 0; +} + +/* +======================== +idMenuScreen_Shell_Root::SetRootIndex +======================== +*/ +void idMenuScreen_Shell_Root::SetRootIndex( int index ) { + if ( options != NULL ) { + options->SetFocusIndex( index ); + } +} + +/* +======================== +idMenuScreen_Shell_Root::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Root::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_ROOT ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + session->MoveToPressStart(); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( menuData->GetPlatform() == 2 ) { + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( !shell ) { + return true; + } + + idMenuWidget_MenuBar * menuBar = shell->GetMenuBar(); + + if ( !menuBar ) { + return true; + } + + const idMenuWidget_MenuButton * buttonWidget = dynamic_cast< idMenuWidget_MenuButton * >( &menuBar->GetChildByIndex( menuBar->GetFocusIndex() ) ); + if ( !buttonWidget ) { + return true; + } + + idWidgetEvent pressEvent( WIDGET_EVENT_PRESS, 0, NULL, idSWFParmList() ); + menuBar->ReceiveEvent( pressEvent ); + return true; + } + break; + } + case WIDGET_ACTION_SCROLL_HORIZONTAL: { + + if ( menuData->GetPlatform() != 2 ) { + return true; + } + + idMenuHandler_Shell * shell = dynamic_cast< idMenuHandler_Shell * >( menuData ); + if ( !shell ) { + return true; + } + + idMenuWidget_MenuBar * menuBar = shell->GetMenuBar(); + + if ( !menuBar ) { + return true; + } + + int index = menuBar->GetViewIndex(); + const int dir = parms[0].ToInteger(); +#ifdef ID_RETAIL + const int totalCount = menuBar->GetTotalNumberOfOptions() - 1; +#else + const int totalCount = menuBar->GetTotalNumberOfOptions(); +#endif + index += dir; + if ( index < 0 ) { + index = totalCount - 1; + } else if ( index >= totalCount ) { + index = 0; + } + + SetRootIndex( index ); + menuBar->SetViewIndex( index ); + menuBar->SetFocusIndex( index ); + + return true; + } + case WIDGET_ACTION_COMMAND: { + switch ( parms[0].ToInteger() ) { + case ROOT_CMD_START_DEMO: { + cmdSystem->AppendCommandText( va( "devmap %s %d\n", "demo/enpro_e3_2012", 1 ) ); + break; + } + case ROOT_CMD_START_DEMO2: { + cmdSystem->AppendCommandText( va( "devmap %s %d\n", "game/le_hell", 2 ) ); + break; + } + case ROOT_CMD_SETTINGS: { + menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE ); + break; + } + case ROOT_CMD_QUIT: { + HandleExitGameBtn(); + break; + } + case ROOT_CMD_DEV: { + menuData->SetNextScreen( SHELL_AREA_DEV, MENU_TRANSITION_SIMPLE ); + break; + } + case ROOT_CMD_CAMPAIGN: { + menuData->SetNextScreen( SHELL_AREA_CAMPAIGN, MENU_TRANSITION_SIMPLE ); + break; + } + case ROOT_CMD_MULTIPLAYER: { + const idLocalUser * masterUser = session->GetSignInManager().GetMasterLocalUser(); + + if ( masterUser == NULL ) { + break; + } + + if ( masterUser->GetOnlineCaps() & CAP_BLOCKED_PERMISSION ) { + common->Dialog().AddDialog( GDM_ONLINE_INCORRECT_PERMISSIONS, DIALOG_CONTINUE, NULL, NULL, true, __FUNCTION__, __LINE__, false ); + } else if ( !masterUser->CanPlayOnline() ) { + class idSWFScriptFunction_Accept : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Accept() { } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_PLAY_ONLINE_NO_PROFILE ); + session->ShowOnlineSignin(); + return idSWFScriptVar(); + } + }; + class idSWFScriptFunction_Cancel : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Cancel() { } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_PLAY_ONLINE_NO_PROFILE ); + return idSWFScriptVar(); + } + }; + + common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT_CANCEL, new (TAG_SWF) idSWFScriptFunction_Accept(), new (TAG_SWF) idSWFScriptFunction_Cancel(), false ); + } else { + idMatchParameters matchParameters; + matchParameters.matchFlags = DefaultPartyFlags; + session->CreatePartyLobby( matchParameters ); + } + break; + } + case ROOT_CMD_PLAYSTATION: { + menuData->SetNextScreen( SHELL_AREA_PLAYSTATION, MENU_TRANSITION_SIMPLE ); + break; + } + case ROOT_CMD_CREDITS: { + menuData->SetNextScreen( SHELL_AREA_CREDITS, MENU_TRANSITION_SIMPLE ); + break; + } + } + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Save.cpp b/neo/d3xp/menus/MenuScreen_Shell_Save.cpp new file mode 100644 index 00000000..7eaf2cf3 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Save.cpp @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + + +const static int NUM_SAVE_OPTIONS = 10; + +/* +======================== +idMenuScreen_Shell_Save::Initialize +======================== +*/ +void idMenuScreen_Shell_Save::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuSave" ); + + saveInfo = new (TAG_SWF) idMenuWidget_Shell_SaveInfo(); + saveInfo->SetSpritePath( GetSpritePath(), "info", "details" ); + saveInfo->Initialize( data ); + saveInfo->SetForSaveScreen( true ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_SAVE_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + while ( options->GetChildren().Num() < NUM_SAVE_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->RegisterEventObserver( saveInfo ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + AddChild( options ); + AddChild( saveInfo ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_pause_menu" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + btnDelete = new idMenuWidget_Button(); + btnDelete->Initialize( data ); + btnDelete->SetLabel( "" ); + btnDelete->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_JOY3_ON_PRESS ); + btnDelete->SetSpritePath( GetSpritePath(), "info", "btnDelete" ); + + AddChild( btnDelete ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Save::Update +======================== +*/ +void idMenuScreen_Shell_Save::Update() { + + UpdateSaveEnumerations(); + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_02179" ); // SAVE GAME + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Save::UpdateSaveEnumerations +======================== +*/ +void idMenuScreen_Shell_Save::UpdateSaveEnumerations() { + + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + sortedSaves = saveGameInfo; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > saveList; + int newSaveOffset = 1; + bool hasAutosave = false; + + if ( session->GetSaveGameManager().IsWorking() ) { + idList< idStr > saveName; + saveName.Append( "#str_dlg_refreshing" ); + saveList.Append( saveName ); + + if ( options != NULL ) { + options->SetListData( saveList ); + options->Update(); + } + } else { + + for ( int i = 0; i < saveGameInfo.Num(); ++i ) { + if ( saveGameInfo[i].slotName.Icmp( "autosave" ) == 0 ) { + hasAutosave = true; + break; + } + } + + if ( saveGameInfo.Num() == MAX_SAVEGAMES || ( !hasAutosave && saveGameInfo.Num() == MAX_SAVEGAMES - 1 ) ) { + newSaveOffset = 0; + } + + if ( newSaveOffset != 0 ) { + idList< idStr > newSave; + newSave.Append( "#str_swf_new_save_game" ); + saveList.Append( newSave ); + } + + if ( options != NULL ) { + sortedSaves.Sort( idSort_SavesByDate() ); + + for ( int slot = 0; slot < sortedSaves.Num(); ++slot ) { + const idSaveGameDetails & details = sortedSaves[slot]; + if ( details.slotName.Icmp( "autosave" ) == 0 ) { + sortedSaves.RemoveIndex( slot ); + slot--; + } + } + // +1 because the first item is "New Save" + saveList.SetNum( sortedSaves.Num() + newSaveOffset ); + for ( int slot = 0; slot < sortedSaves.Num(); ++slot ) { + idStr & slotSaveName = saveList[ slot + newSaveOffset ].Alloc(); + const idSaveGameDetails & details = sortedSaves[slot]; + if ( details.damaged ) { + slotSaveName = va( S_COLOR_RED "%s", idLocalization::GetString( "#str_swf_corrupt_file" ) ); + } else if ( details.GetSaveVersion() > BUILD_NUMBER ) { + slotSaveName = va( S_COLOR_RED "%s", idLocalization::GetString( "#str_swf_wrong_version" ) ); + } else { + if ( details.slotName.Icmp( "autosave" ) == 0 ) { + slotSaveName.Append( S_COLOR_YELLOW ); + } else if ( details.slotName.Icmp( "quick" ) == 0 ) { + slotSaveName.Append( S_COLOR_ORANGE ); + } + slotSaveName.Append( details.GetMapName() ); + } + } + + options->SetListData( saveList ); + options->Update(); + } + } + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; // BACK + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + + if ( !session->GetSaveGameManager().IsWorking() ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_02179"; // SAVE GAME + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + + if ( options != NULL ) { + if ( options->GetViewIndex() != 0 || ( options->GetViewIndex() == 0 && newSaveOffset == 0 ) ) { + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY3 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_02315"; // DELETE + } + buttonInfo->action.Set( WIDGET_ACTION_JOY3_ON_PRESS ); + + if ( btnDelete != NULL ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( btnDelete->BindSprite( root ) ) { + if ( menuData->GetPlatform() != 2 ) { + btnDelete->SetLabel( "" ); + } else { + btnDelete->GetSprite()->SetVisible( true ); + btnDelete->SetLabel( "#str_02315" ); + } + } + btnDelete->Update(); + } + } else { + if ( btnDelete != NULL ) { + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( btnDelete->BindSprite( root ) ) { + btnDelete->SetLabel( "" ); + btnDelete->Update(); + } + } + } + } + } + cmdBar->Update(); + } + } + + if ( saveInfo != NULL ) { + saveInfo->Update(); + } + + if ( options != NULL && options->GetTotalNumberOfOptions() > 0 && options->GetViewIndex() >= options->GetTotalNumberOfOptions() ) { + options->SetViewIndex( options->GetTotalNumberOfOptions() - newSaveOffset ); + if ( options->GetViewOffset() > options->GetViewIndex() ) { + options->SetViewOffset( options->GetViewIndex() ); + } + options->SetFocusIndex( options->GetViewIndex() - options->GetViewOffset() ); + } +} + +/* +======================== +idMenuScreen_Shell_Save::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Save::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Save::HideScreen +======================== +*/ +void idMenuScreen_Shell_Save::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Save::SaveGame +======================== +*/ +void idMenuScreen_Shell_Save::SaveGame( int index ) { + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + int newSaveOffset = 1; + + bool hasAutosave = false; + for ( int i = 0; i < saveGameInfo.Num(); ++i ) { + if ( saveGameInfo[i].slotName.Icmp( "autosave" ) == 0 ) { + hasAutosave = true; + break; + } + } + + if ( saveGameInfo.Num() == MAX_SAVEGAMES || ( ( saveGameInfo.Num() == MAX_SAVEGAMES - 1 ) && !hasAutosave ) ) { + newSaveOffset = 0; + } + + if ( index == 0 && newSaveOffset == 1 ) { + // New save... + + // Scan all the savegames for the first doom3_xxx slot. + const idStr savePrefix = "doom3_"; + uint64 slotMask = 0; + for ( int slot = 0; slot < saveGameInfo.Num(); ++slot ) { + const idSaveGameDetails & details = saveGameInfo[slot]; + + if ( details.slotName.Icmp( "autosave" ) == 0 ) { + continue; + } + + idStr name = details.slotName; + + name.ToLower(); // PS3 saves are uppercase ... we need to lower case-ify them for comparison here + name.StripLeading( savePrefix.c_str() ); + if ( name.IsNumeric() ) { + int slotNumber = atoi( name.c_str() ); + slotMask |= ( 1ULL << slotNumber ); + } + } + + int slotNumber = 0; + for ( slotNumber = 0; slotNumber < ( sizeof( slotMask ) * 8 ); slotNumber++ ) { + // If the slot isn't used, grab it + if ( !( slotMask & ( 1ULL << slotNumber ) ) ) { + break; + } + } + + assert( slotNumber < ( sizeof( slotMask ) * 8 ) ); + idStr name = va( "%s%d", savePrefix.c_str(), slotNumber ); + cmdSystem->AppendCommandText( va( "savegame %s\n", name.c_str() ) ); + + // Throw up the saving message... + common->Dialog().ShowSaveIndicator( true ); + } else { + + class idSWFScriptFunction_OverwriteSave : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_OverwriteSave( gameDialogMessages_t _msg, bool _accept, int _index, idMenuScreen_Shell_Save * _screen ) { + msg = _msg; + accept = _accept; + index = _index; + screen = _screen; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept && screen != NULL ) { + // Replace the save + if ( index < screen->GetSortedSaves().Num() ) { + idStr name = screen->GetSortedSaves()[ index ].slotName; + cmdSystem->AppendCommandText( va( "savegame %s\n", name.c_str() ) ); + + // Throw up the saving message... + common->Dialog().ShowSaveIndicator( true ); + } + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + int index; + bool accept; + idMenuScreen_Shell_Save * screen; + }; + + if ( newSaveOffset == 1 ) { + index--; + } + + common->Dialog().AddDialog( GDM_OVERWRITE_SAVE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_OverwriteSave( GDM_OVERWRITE_SAVE, true, index, this ), new idSWFScriptFunction_OverwriteSave( GDM_OVERWRITE_SAVE, false, index, this ), false ); + } + +} + +/* +======================== +idMenuScreen_Shell_Save::DeleteGame +======================== +*/ +void idMenuScreen_Shell_Save::DeleteGame( int index ) { + + class idSWFScriptFunction_DeleteGame : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_DeleteGame( gameDialogMessages_t _msg, bool _accept, int _index, idMenuScreen_Shell_Save * _screen ) { + msg = _msg; + accept = _accept; + index = _index; + screen = _screen; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( accept && screen != NULL ) { + if ( index < screen->GetSortedSaves().Num() ) { + session->DeleteSaveGameSync( screen->GetSortedSaves()[ index ].slotName ); + } + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + int index; + bool accept; + idMenuScreen_Shell_Save * screen; + }; + + bool hasAutosave = false; + const saveGameDetailsList_t & saveInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + for ( int i = 0; i < saveInfo.Num(); ++i ) { + if ( saveInfo[i].slotName.Icmp( "autosave" ) == 0 ) { + hasAutosave = true; + break; + } + } + + if ( ( saveInfo.Num() < MAX_SAVEGAMES - 1 ) || ( ( saveInfo.Num() == MAX_SAVEGAMES - 1 ) && hasAutosave ) ) { + index--; // Subtract 1 from the index for 'New Game' + } + + common->Dialog().AddDialog( GDM_DELETE_SAVE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_DeleteGame( GDM_DELETE_SAVE, true, index, this ), new idSWFScriptFunction_DeleteGame( GDM_DELETE_SAVE, false, index, this ), false ); + +} + +/* +======================== +idMenuScreen_Shell_Save::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Save::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_SAVE ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_JOY4_ON_PRESS: { + return true; + } + case WIDGET_ACTION_JOY3_ON_PRESS: { + + if ( options == NULL ) { + return true; + } + + DeleteGame( options->GetViewIndex() ); + return true; + } + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + if ( session->GetSaveGameManager().IsWorking() ) { + return true; + } + + if ( parms.Num() == 1 ) { + int selectionIndex = parms[0].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + return true; + } + } + + SaveGame( options->GetViewIndex() ); + return true; + } + case WIDGET_ACTION_SCROLL_VERTICAL: { + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} diff --git a/neo/d3xp/menus/MenuScreen_Shell_Settings.cpp b/neo/d3xp/menus/MenuScreen_Shell_Settings.cpp new file mode 100644 index 00000000..23dd18a4 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Settings.cpp @@ -0,0 +1,248 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SETTING_OPTIONS = 8; + +enum settingMenuCmds_t { + SETTING_CMD_CONTROLS, + SETTING_CMD_GAMEPLAY, + SETTING_CMD_SYSTEM, + SETTING_CMD_3D, +}; + +/* +======================== +idMenuScreen_Shell_Settings::Initialize +======================== +*/ +void idMenuScreen_Shell_Settings::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuSettings" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + + option.Append( "#str_04158" ); // controls + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_02401" ); // game options + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_04160" ); // system + menuOptions.Append( option ); + option.Clear(); + + if ( renderSystem->IsStereoScopicRenderingSupported() ) { + option.Append( "#str_swf_stereoscopics" ); // Stereoscopic Rendering + menuOptions.Append( option ); + } + + options->SetListData( menuOptions ); + options->SetNumVisibleOptions( NUM_SETTING_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + const char * tips[] = { "#str_02166", "#str_02168", "#str_02170", "#str_swf_customize_3d" }; + + while ( options->GetChildren().Num() < NUM_SETTING_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->Initialize( data ); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, options->GetChildren().Num() ); + + if ( options->GetChildren().Num() < menuOptions.Num() ) { + buttonWidget->SetDescription( tips[options->GetChildren().Num()] ); + } + + buttonWidget->RegisterEventObserver( helpWidget ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + idMenuHandler_Shell * handler = dynamic_cast< idMenuHandler_Shell * >( data ); + if ( handler != NULL && handler->GetInGame() ) { + btnBack->SetLabel( "#str_swf_pause_menu" ); + } else { + btnBack->SetLabel( "#str_02305" ); + } + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Settings::Update +======================== +*/ +void idMenuScreen_Shell_Settings::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_settings" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Settings::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Settings::ShowScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Settings::HideScreen +======================== +*/ +void idMenuScreen_Shell_Settings::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Settings::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Settings::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_SETTINGS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_COMMAND: { + switch ( parms[0].ToInteger() ) { + case SETTING_CMD_CONTROLS: { + menuData->SetNextScreen( SHELL_AREA_CONTROLS, MENU_TRANSITION_SIMPLE ); + break; + } + case SETTING_CMD_GAMEPLAY: { + menuData->SetNextScreen( SHELL_AREA_GAME_OPTIONS, MENU_TRANSITION_SIMPLE ); + break; + } + case SETTING_CMD_SYSTEM: { + menuData->SetNextScreen( SHELL_AREA_SYSTEM_OPTIONS, MENU_TRANSITION_SIMPLE ); + break; + } + case SETTING_CMD_3D: { + menuData->SetNextScreen( SHELL_AREA_STEREOSCOPICS, MENU_TRANSITION_SIMPLE ); + break; + } + } + + if ( options != NULL ) { + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( options->GetFocusIndex() != selectionIndex ) { + options->SetFocusIndex( selectionIndex ); + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + } + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Singleplayer.cpp b/neo/d3xp/menus/MenuScreen_Shell_Singleplayer.cpp new file mode 100644 index 00000000..9e969b8e --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Singleplayer.cpp @@ -0,0 +1,314 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SINGLEPLAYER_OPTIONS = 8; +/* +======================== +idMenuScreen_Shell_Singleplayer::Initialize +======================== +*/ +void idMenuScreen_Shell_Singleplayer::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuCampaign" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_SINGLEPLAYER_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + AddChild( options ); + + idMenuWidget_Help * const helpWidget = new ( TAG_SWF ) idMenuWidget_Help(); + helpWidget->SetSpritePath( GetSpritePath(), "info", "helpTooltip" ); + AddChild( helpWidget ); + + while ( options->GetChildren().Num() < NUM_SINGLEPLAYER_OPTIONS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + buttonWidget->RegisterEventObserver( helpWidget ); + buttonWidget->Initialize( data ); + options->AddChild( buttonWidget ); + } + options->Initialize( data ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_02305" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( btnBack ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_Singleplayer::Update +======================== +*/ +void idMenuScreen_Shell_Singleplayer::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_SWF_SELECT"; + } + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_campaign" ); + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Singleplayer::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Singleplayer::ShowScreen( const mainMenuTransition_t transitionType ) { + + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > menuOptions; + idList< idStr > option; + + canContinue = false; + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + canContinue = ( saveGameInfo.Num() > 0 ); + if ( canContinue ) { + option.Append( "#str_swf_continue_game" ); // continue game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_01866" ); // new game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_01867" ); // load game + menuOptions.Append( option ); + + int index = 0; + idMenuWidget_Button * buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_swf_continue_desc" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02209" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02213" ); + } + index++; + + } else { + option.Append( "#str_01866" ); // new game + menuOptions.Append( option ); + option.Clear(); + option.Append( "#str_01867" ); // load game + menuOptions.Append( option ); + + if ( options != NULL ) { + int index = 0; + idMenuWidget_Button * buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02209" ); + } + index++; + buttonWidget = dynamic_cast< idMenuWidget_Button * >( &options->GetChildByIndex( index ) ); + if ( buttonWidget != NULL ) { + buttonWidget->SetDescription( "#str_02213" ); + } + } + } + + if ( options != NULL ) { + options->SetListData( menuOptions ); + } + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Singleplayer::HideScreen +======================== +*/ +void idMenuScreen_Shell_Singleplayer::HideScreen( const mainMenuTransition_t transitionType ) { + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Singleplayer::ContinueGame +======================== +*/ +void idMenuScreen_Shell_Singleplayer::ContinueGame() { + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + saveGameDetailsList_t sortedSaves = saveGameInfo; + sortedSaves.Sort( idSort_SavesByDate() ); + if ( sortedSaves.Num() > 0 ) { + if ( sortedSaves[0].damaged ) { + class idSWFScriptFunction_ContinueDamaged : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_CORRUPT_CONTINUE ); + return idSWFScriptVar(); + } + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_ContinueDamaged() ); + idStaticList< idStrId, 4 > optionText; + optionText.Append( idStrId( "#str_04339" ) ); // OK + common->Dialog().AddDynamicDialog( GDM_CORRUPT_CONTINUE, callbacks, optionText, false, "" ); + } else { + const idStr & name = sortedSaves[ 0 ].slotName; + cmdSystem->AppendCommandText( va( "loadgame %s\n", name.c_str() ) ); + } + } +} + +/* +======================== +idMenuScreen_Shell_Singleplayer::HandleAction +======================== +*/ +bool idMenuScreen_Shell_Singleplayer::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_CAMPAIGN ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + menuData->SetNextScreen( SHELL_AREA_ROOT, MENU_TRANSITION_SIMPLE ); + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetViewIndex(); + if ( parms.Num() == 1 ) { + selectionIndex = parms[0].ToInteger(); + } + + canContinue = false; + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + canContinue = ( saveGameInfo.Num() > 0 ); + if ( canContinue ) { + if ( selectionIndex == 0 ) { + ContinueGame(); + + } else if ( selectionIndex == 1 ) { + class idSWFScriptFunction_NewGame : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_NewGame( idMenuHandler * _menuData, bool _accept ) { + menuData = _menuData; + accept = _accept; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_DELETE_AUTOSAVE ); + if ( accept ) { + menuData->SetNextScreen( SHELL_AREA_NEW_GAME, MENU_TRANSITION_SIMPLE ); + } + return idSWFScriptVar(); + } + private: + idMenuHandler * menuData; + bool accept; + }; + common->Dialog().AddDialog( GDM_DELETE_AUTOSAVE, DIALOG_ACCEPT_CANCEL, new idSWFScriptFunction_NewGame( menuData, true ), new idSWFScriptFunction_NewGame( menuData, false ), true ); + } else if ( selectionIndex == 2 ) { + menuData->SetNextScreen( SHELL_AREA_LOAD, MENU_TRANSITION_SIMPLE ); + } + } else { + if ( selectionIndex == 0 ) { + menuData->SetNextScreen( SHELL_AREA_NEW_GAME, MENU_TRANSITION_SIMPLE ); + } else if ( selectionIndex == 1 ) { + menuData->SetNextScreen( SHELL_AREA_LOAD, MENU_TRANSITION_SIMPLE ); + } + } + + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuScreen_Shell_Stereoscopics.cpp b/neo/d3xp/menus/MenuScreen_Shell_Stereoscopics.cpp new file mode 100644 index 00000000..b20a290b --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_Stereoscopics.cpp @@ -0,0 +1,422 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SYSTEM_OPTIONS_OPTIONS = 4; + +// TRC requires a maximum interoccular distance of 6.5cm even though human adults can easily have an interoccular distance of over 7.5cm +const static float MAX_INTEROCCULAR_DISTANCE = 6.5f; + +// This should line up with stereo3DMode_t +static const char * stereoRender_enable_text[] = { + "#str_00641", + "#str_swf_stereo_side_by_side", + "#str_swf_stereo_top_and_bottom", + "#str_swf_stereo_side_by_side_full", + "#str_swf_stereo_interlaced", + "#str_swf_stereo_quad" +}; +static const int NUM_STEREO_ENABLE = sizeof( stereoRender_enable_text ) / sizeof( stereoRender_enable_text[0] ); + +/* +======================== +idMenuScreen_Shell_Stereoscopics::Initialize +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuStereoscopics" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_SYSTEM_OPTIONS_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + AddChild( options ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_settings" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control; + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_swf_stereoscopic_rendering" ); // Stereoscopics + control->SetDataSource( &stereoData, idMenuDataSource_StereoSettings::STEREO_FIELD_ENABLE ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_swf_stereo_seperation" ); // View Offset + control->SetDataSource( &stereoData, idMenuDataSource_StereoSettings::STEREO_FIELD_SEPERATION ); + control->SetupEvents( 2, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TOGGLE ); + control->SetLabel( "#str_swf_stereo_eye_swap" ); // Swap Eyes + control->SetDataSource( &stereoData, idMenuDataSource_StereoSettings::STEREO_FIELD_SWAP_EYES ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PRESS_FOCUSED, options->GetChildren().Num() ); + options->AddChild( control ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); + + leftEyeMat = declManager->FindMaterial( "doomLeftEye" ); + rightEyeMat = declManager->FindMaterial( "doomRightEye" ); +} + + + +/* +======================== +idMenuScreen_Shell_Stereoscopics::Update +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_swf_stereoscopics_heading" ); // STEREOSCOPIC RENDERING + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::ShowScreen +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::ShowScreen( const mainMenuTransition_t transitionType ) { + stereoData.LoadData(); + idMenuScreen::ShowScreen( transitionType ); + + if ( GetSprite() != NULL ) { + idSWFSpriteInstance * leftEye = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "leftEye" ); + idSWFSpriteInstance * rightEye = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "rightEye" ); + + if ( leftEye != NULL && leftEyeMat != NULL ) { + leftEye->SetMaterial( leftEyeMat ); + } + + if ( rightEye != NULL && rightEyeMat != NULL ) { + rightEye->SetMaterial( rightEyeMat ); + } + } +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::HideScreen +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::HideScreen( const mainMenuTransition_t transitionType ) { + if ( stereoData.IsRestartRequired() ) { + class idSWFScriptFunction_Restart : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Restart( gameDialogMessages_t _msg, bool _restart ) { + msg = _msg; + restart = _restart; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( restart ) { + idStr cmdLine = Sys_GetCmdLine(); + if ( cmdLine.Find( "com_skipIntroVideos" ) < 0 ) { + cmdLine.Append( " +set com_skipIntroVideos 1" ); + } + Sys_ReLaunch( (void*)cmdLine.c_str(), cmdLine.Length() ); + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + bool restart; + }; + idStaticList callbacks; + idStaticList optionText; + callbacks.Append( new idSWFScriptFunction_Restart( GDM_GAME_RESTART_REQUIRED, false ) ); + callbacks.Append( new idSWFScriptFunction_Restart( GDM_GAME_RESTART_REQUIRED, true ) ); + optionText.Append( idStrId( "#str_00100113" ) ); // Continue + optionText.Append( idStrId( "#str_02487" ) ); // Restart Now + common->Dialog().AddDynamicDialog( GDM_GAME_RESTART_REQUIRED, callbacks, optionText, true, idStr() ); + } + if ( stereoData.IsDataChanged() ) { + stereoData.CommitData(); + } + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_Stereoscopics::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData != NULL ) { + if ( menuData->ActiveScreen() != SHELL_AREA_STEREOSCOPICS ) { + return false; + } + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + if ( menuData != NULL ) { + menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE ); + } + return true; + } + case WIDGET_ACTION_PRESS_FOCUSED: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + stereoData.AdjustField( selectionIndex, 1 ); + options->Update(); + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +///////////////////////////////// +// SCREEN SETTINGS +///////////////////////////////// + +extern idCVar stereoRender_interOccularCentimeters; +extern idCVar stereoRender_swapEyes; + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::idMenuDataSource_StereoSettings +======================== +*/ +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::idMenuDataSource_StereoSettings() { + fields.SetNum( MAX_STEREO_FIELDS ); + originalFields.SetNum( MAX_STEREO_FIELDS ); +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::LoadData() { + + fields[ STEREO_FIELD_ENABLE ].SetInteger( renderSystem->GetStereoScopicRenderingMode() ); + + fields[ STEREO_FIELD_SEPERATION ].SetFloat( 100.0f * ( stereoRender_interOccularCentimeters.GetFloat() / MAX_INTEROCCULAR_DISTANCE ) ); + + fields[ STEREO_FIELD_SWAP_EYES ].SetBool( stereoRender_swapEyes.GetBool() ); + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::CommitData() { + + if ( IsDataChanged() ) { + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); + } + + // make the committed fields into the backup fields + originalFields = fields; +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + + if ( fieldIndex == STEREO_FIELD_ENABLE ) { + int numOptions = NUM_STEREO_ENABLE; + if ( !renderSystem->HasQuadBufferSupport() ) { + numOptions--; + } + + int adjusted = fields[ fieldIndex ].ToInteger() + adjustAmount; + adjusted += numOptions; + adjusted %= numOptions; + fields[fieldIndex].SetInteger( adjusted ); + renderSystem->EnableStereoScopicRendering( (stereo3DMode_t)adjusted ); + + gameLocal.Shell_ClearRepeater(); + + } else if ( fieldIndex == STEREO_FIELD_SWAP_EYES ) { + + fields[ fieldIndex ].SetBool( !fields[ fieldIndex ].ToBool() ); + stereoRender_swapEyes.SetBool( fields[ fieldIndex ].ToBool() ); + + } else if ( fieldIndex == STEREO_FIELD_SEPERATION ) { + + float newValue = idMath::ClampFloat( 0.0f, 100.0f, fields[ fieldIndex ].ToFloat() + adjustAmount ); + fields[ fieldIndex ].SetFloat( newValue ); + + stereoRender_interOccularCentimeters.SetFloat( ( fields[ STEREO_FIELD_SEPERATION ].ToFloat() / 100.0f ) * MAX_INTEROCCULAR_DISTANCE ); + + } + + // do this so we don't save every time we modify a setting. Only save once when we leave the screen + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::IsDataChanged +======================== +*/ +idSWFScriptVar idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::GetField( const int fieldIndex ) const { + if ( fieldIndex == STEREO_FIELD_ENABLE ) { + return idSWFScriptVar( stereoRender_enable_text[fields[fieldIndex].ToInteger()] ); + } + return fields[ fieldIndex ]; +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::IsDataChanged() const { + if ( fields[ STEREO_FIELD_SWAP_EYES ].ToBool() != originalFields[ STEREO_FIELD_SWAP_EYES ].ToBool() ) { + return true; + } + + if ( fields[ STEREO_FIELD_ENABLE ].ToInteger() != originalFields[ STEREO_FIELD_ENABLE ].ToInteger() ) { + return true; + } + + if ( fields[ STEREO_FIELD_SEPERATION ].ToFloat() != originalFields[ STEREO_FIELD_SEPERATION ].ToFloat() ) { + return true; + } + + return false; +} + +/* +======================== +idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::IsRestartRequired +======================== +*/ +bool idMenuScreen_Shell_Stereoscopics::idMenuDataSource_StereoSettings::IsRestartRequired() const { + if ( fields[ STEREO_FIELD_ENABLE ].ToInteger() != originalFields[ STEREO_FIELD_ENABLE ].ToInteger() ) { + if ( fields[ STEREO_FIELD_ENABLE ].ToInteger() == STEREO3D_QUAD_BUFFER || originalFields[ STEREO_FIELD_ENABLE ].ToInteger() == STEREO3D_QUAD_BUFFER ) { + return true; + } + } + return false; +} diff --git a/neo/d3xp/menus/MenuScreen_Shell_SystemOptions.cpp b/neo/d3xp/menus/MenuScreen_Shell_SystemOptions.cpp new file mode 100644 index 00000000..54a820a3 --- /dev/null +++ b/neo/d3xp/menus/MenuScreen_Shell_SystemOptions.cpp @@ -0,0 +1,549 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +const static int NUM_SYSTEM_OPTIONS_OPTIONS = 8; + +extern idCVar r_multiSamples; +extern idCVar r_motionBlur; +extern idCVar r_swapInterval; +extern idCVar s_volume_dB; +extern idCVar r_lightScale; + +/* +======================== +idMenuScreen_Shell_SystemOptions::Initialize +======================== +*/ +void idMenuScreen_Shell_SystemOptions::Initialize( idMenuHandler * data ) { + idMenuScreen::Initialize( data ); + + if ( data != NULL ) { + menuGUI = data->GetGUI(); + } + + SetSpritePath( "menuSystemOptions" ); + + options = new (TAG_SWF) idMenuWidget_DynamicList(); + options->SetNumVisibleOptions( NUM_SYSTEM_OPTIONS_OPTIONS ); + options->SetSpritePath( GetSpritePath(), "info", "options" ); + options->SetWrappingAllowed( true ); + options->SetControlList( true ); + options->Initialize( data ); + + btnBack = new (TAG_SWF) idMenuWidget_Button(); + btnBack->Initialize( data ); + btnBack->SetLabel( "#str_swf_settings" ); + btnBack->SetSpritePath( GetSpritePath(), "info", "btnBack" ); + btnBack->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_GO_BACK ); + + AddChild( options ); + AddChild( btnBack ); + + idMenuWidget_ControlButton * control; + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_02154" ); + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_FULLSCREEN ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_FULLSCREEN ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_swf_framerate" ); + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_FRAMERATE ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_FRAMERATE ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_04126" ); + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_VSYNC ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_VSYNC ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_04128" ); + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_ANTIALIASING ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_ANTIALIASING ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_TEXT ); + control->SetLabel( "#str_swf_motionblur" ); + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_MOTIONBLUR ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_MOTIONBLUR ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_swf_lodbias" ); + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_LODBIAS ); + control->SetupEvents( DEFAULT_REPEAT_TIME, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_LODBIAS ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_02155" ); // Brightness + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_BRIGHTNESS ); + control->SetupEvents( 2, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_BRIGHTNESS ); + options->AddChild( control ); + + control = new (TAG_SWF) idMenuWidget_ControlButton(); + control->SetOptionType( OPTION_SLIDER_BAR ); + control->SetLabel( "#str_02163" ); // Volume + control->SetDataSource( &systemData, idMenuDataSource_SystemSettings::SYSTEM_FIELD_VOLUME ); + control->SetupEvents( 2, options->GetChildren().Num() ); + control->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_COMMAND, idMenuDataSource_SystemSettings::SYSTEM_FIELD_VOLUME ); + options->AddChild( control ); + + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE ) ); + options->AddEventAction( WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ).Set( new (TAG_SWF) idWidgetActionHandler( options, WIDGET_ACTION_EVENT_STOP_REPEATER, WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE ) ); +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::Update +======================== +*/ +void idMenuScreen_Shell_SystemOptions::Update() { + + if ( menuData != NULL ) { + idMenuWidget_CommandBar * cmdBar = menuData->GetCmdBar(); + if ( cmdBar != NULL ) { + cmdBar->ClearAllButtons(); + idMenuWidget_CommandBar::buttonInfo_t * buttonInfo; + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY2 ); + if ( menuData->GetPlatform() != 2 ) { + buttonInfo->label = "#str_00395"; + } + buttonInfo->action.Set( WIDGET_ACTION_GO_BACK ); + + buttonInfo = cmdBar->GetButton( idMenuWidget_CommandBar::BUTTON_JOY1 ); + buttonInfo->action.Set( WIDGET_ACTION_PRESS_FOCUSED ); + } + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + idSWFTextInstance * heading = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtHeading" ); + if ( heading != NULL ) { + heading->SetText( "#str_00183" ); // FULLSCREEN + heading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + idSWFSpriteInstance * gradient = GetSprite()->GetScriptObject()->GetNestedSprite( "info", "gradient" ); + if ( gradient != NULL && heading != NULL ) { + gradient->SetXPos( heading->GetTextLength() ); + } + } + + if ( btnBack != NULL ) { + btnBack->BindSprite( root ); + } + + idMenuScreen::Update(); +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::ShowScreen +======================== +*/ +void idMenuScreen_Shell_SystemOptions::ShowScreen( const mainMenuTransition_t transitionType ) { + + systemData.LoadData(); + + idMenuScreen::ShowScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::HideScreen +======================== +*/ +void idMenuScreen_Shell_SystemOptions::HideScreen( const mainMenuTransition_t transitionType ) { + + if ( systemData.IsRestartRequired() ) { + class idSWFScriptFunction_Restart : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Restart( gameDialogMessages_t _msg, bool _restart ) { + msg = _msg; + restart = _restart; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + if ( restart ) { + idStr cmdLine = Sys_GetCmdLine(); + if ( cmdLine.Find( "com_skipIntroVideos" ) < 0 ) { + cmdLine.Append( " +set com_skipIntroVideos 1" ); + } + Sys_ReLaunch( (void*)cmdLine.c_str(), cmdLine.Length() ); + } + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + bool restart; + }; + idStaticList callbacks; + idStaticList optionText; + callbacks.Append( new idSWFScriptFunction_Restart( GDM_GAME_RESTART_REQUIRED, false ) ); + callbacks.Append( new idSWFScriptFunction_Restart( GDM_GAME_RESTART_REQUIRED, true ) ); + optionText.Append( idStrId( "#str_00100113" ) ); // Continue + optionText.Append( idStrId( "#str_02487" ) ); // Restart Now + common->Dialog().AddDynamicDialog( GDM_GAME_RESTART_REQUIRED, callbacks, optionText, true, idStr() ); + } + + if ( systemData.IsDataChanged() ) { + systemData.CommitData(); + } + + idMenuScreen::HideScreen( transitionType ); +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::HandleAction h +======================== +*/ +bool idMenuScreen_Shell_SystemOptions::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + if ( menuData == NULL ) { + return true; + } + + if ( menuData->ActiveScreen() != SHELL_AREA_SYSTEM_OPTIONS ) { + return false; + } + + widgetAction_t actionType = action.GetType(); + const idSWFParmList & parms = action.GetParms(); + + switch ( actionType ) { + case WIDGET_ACTION_GO_BACK: { + if ( menuData != NULL ) { + menuData->SetNextScreen( SHELL_AREA_SETTINGS, MENU_TRANSITION_SIMPLE ); + } + return true; + } + case WIDGET_ACTION_ADJUST_FIELD: + if ( widget->GetDataSourceFieldIndex() == idMenuDataSource_SystemSettings::SYSTEM_FIELD_FULLSCREEN ) { + menuData->SetNextScreen( SHELL_AREA_RESOLUTION, MENU_TRANSITION_SIMPLE ); + return true; + } + break; + case WIDGET_ACTION_COMMAND: { + + if ( options == NULL ) { + return true; + } + + int selectionIndex = options->GetFocusIndex(); + if ( parms.Num() > 0 ) { + selectionIndex = parms[0].ToInteger(); + } + + if ( options && selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + + switch ( parms[0].ToInteger() ) { + case idMenuDataSource_SystemSettings::SYSTEM_FIELD_FULLSCREEN: { + menuData->SetNextScreen( SHELL_AREA_RESOLUTION, MENU_TRANSITION_SIMPLE ); + return true; + } + default: { + systemData.AdjustField( parms[0].ToInteger(), 1 ); + options->Update(); + } + } + + return true; + } + case WIDGET_ACTION_START_REPEATER: { + + if ( options == NULL ) { + return true; + } + + if ( parms.Num() == 4 ) { + int selectionIndex = parms[3].ToInteger(); + if ( selectionIndex != options->GetFocusIndex() ) { + options->SetViewIndex( options->GetViewOffset() + selectionIndex ); + options->SetFocusIndex( selectionIndex ); + } + } + break; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +///////////////////////////////// +// SCREEN SETTINGS +///////////////////////////////// + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::idMenuDataSource_SystemSettings +======================== +*/ +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::idMenuDataSource_SystemSettings() { +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::LoadData +======================== +*/ +void idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::LoadData() { + originalFramerate = com_engineHz.GetInteger(); + originalAntialias = r_multiSamples.GetInteger(); + originalMotionBlur = r_motionBlur.GetInteger(); + originalVsync = r_swapInterval.GetInteger(); + originalBrightness = r_lightScale.GetFloat(); + originalVolume = s_volume_dB.GetFloat(); + + const int fullscreen = r_fullscreen.GetInteger(); + if ( fullscreen > 0 ) { + R_GetModeListForDisplay( fullscreen - 1, modeList ); + } else { + modeList.Clear(); + } +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::IsRestartRequired +======================== +*/ +bool idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::IsRestartRequired() const { + if ( originalAntialias != r_multiSamples.GetInteger() ) { + return true; + } + if ( originalFramerate != com_engineHz.GetInteger() ) { + return true; + } + return false; +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::CommitData +======================== +*/ +void idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::CommitData() { + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); +} + +/* +======================== +AdjustOption +Given a current value in an array of possible values, returns the next n value +======================== +*/ +int AdjustOption( int currentValue, const int values[], int numValues, int adjustment ) { + int index = 0; + for ( int i = 0; i < numValues; i++ ) { + if ( currentValue == values[i] ) { + index = i; + break; + } + } + index += adjustment; + while ( index < 0 ) { + index += numValues; + } + index %= numValues; + return values[index]; +} + +/* +======================== +LinearAdjust +Linearly converts a float from one scale to another +======================== +*/ +float LinearAdjust( float input, float currentMin, float currentMax, float desiredMin, float desiredMax ) { + return ( ( input - currentMin ) / ( currentMax - currentMin ) ) * ( desiredMax - desiredMin ) + desiredMin; +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::AdjustField +======================== +*/ +void idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::AdjustField( const int fieldIndex, const int adjustAmount ) { + switch ( fieldIndex ) { + case SYSTEM_FIELD_FRAMERATE: { + static const int numValues = 2; + static const int values[numValues] = { 60, 120 }; + com_engineHz.SetInteger( AdjustOption( com_engineHz.GetInteger(), values, numValues, adjustAmount ) ); + break; + } + case SYSTEM_FIELD_VSYNC: { + static const int numValues = 3; + static const int values[numValues] = { 0, 1, 2 }; + r_swapInterval.SetInteger( AdjustOption( r_swapInterval.GetInteger(), values, numValues, adjustAmount ) ); + break; + } + case SYSTEM_FIELD_ANTIALIASING: { + static const int numValues = 5; + static const int values[numValues] = { 0, 2, 4, 8, 16 }; + r_multiSamples.SetInteger( AdjustOption( r_multiSamples.GetInteger(), values, numValues, adjustAmount ) ); + break; + } + case SYSTEM_FIELD_MOTIONBLUR: { + static const int numValues = 5; + static const int values[numValues] = { 0, 2, 3, 4, 5 }; + r_motionBlur.SetInteger( AdjustOption( r_motionBlur.GetInteger(), values, numValues, adjustAmount ) ); + break; + } + case SYSTEM_FIELD_LODBIAS: { + const float percent = LinearAdjust( r_lodBias.GetFloat(), -1.0f, 1.0f, 0.0f, 100.0f ); + const float adjusted = percent + (float)adjustAmount * 5.0f; + const float clamped = idMath::ClampFloat( 0.0f, 100.0f, adjusted ); + r_lodBias.SetFloat( LinearAdjust( clamped, 0.0f, 100.0f, -1.0f, 1.0f ) ); + break; + } + case SYSTEM_FIELD_BRIGHTNESS: { + const float percent = LinearAdjust( r_lightScale.GetFloat(), 2.0f, 4.0f, 0.0f, 100.0f ); + const float adjusted = percent + (float)adjustAmount; + const float clamped = idMath::ClampFloat( 0.0f, 100.0f, adjusted ); + r_lightScale.SetFloat( LinearAdjust( clamped, 0.0f, 100.0f, 2.0f, 4.0f ) ); + break; + } + case SYSTEM_FIELD_VOLUME: { + const float percent = 100.0f * Square( 1.0f - ( s_volume_dB.GetFloat() / DB_SILENCE ) ); + const float adjusted = percent + (float)adjustAmount; + const float clamped = idMath::ClampFloat( 0.0f, 100.0f, adjusted ); + s_volume_dB.SetFloat( DB_SILENCE - ( idMath::Sqrt( clamped / 100.0f ) * DB_SILENCE ) ); + break; + } + } + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::GetField +======================== +*/ +idSWFScriptVar idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::GetField( const int fieldIndex ) const { + switch ( fieldIndex ) { + case SYSTEM_FIELD_FULLSCREEN: { + const int fullscreen = r_fullscreen.GetInteger(); + const int vidmode = r_vidMode.GetInteger(); + if ( fullscreen == 0 ) { + return "#str_swf_disabled"; + } + if ( fullscreen < 0 || vidmode < 0 || vidmode >= modeList.Num() ) { + return "???"; + } + if ( modeList[vidmode].displayHz == 60 ) { + return va( "%4i x %4i", modeList[vidmode].width, modeList[vidmode].height ); + } else { + return va( "%4i x %4i @ %dhz", modeList[vidmode].width, modeList[vidmode].height, modeList[vidmode].displayHz ); + } + } + case SYSTEM_FIELD_FRAMERATE: + return va( "%d FPS", com_engineHz.GetInteger() ); + case SYSTEM_FIELD_VSYNC: + if ( r_swapInterval.GetInteger() == 1 ) { + return "#str_swf_smart"; + } else if ( r_swapInterval.GetInteger() == 2 ) { + return "#str_swf_enabled"; + } else { + return "#str_swf_disabled"; + } + case SYSTEM_FIELD_ANTIALIASING: + if ( r_multiSamples.GetInteger() == 0 ) { + return "#str_swf_disabled"; + } + return va( "%dx", r_multiSamples.GetInteger() ); + case SYSTEM_FIELD_MOTIONBLUR: + if ( r_motionBlur.GetInteger() == 0 ) { + return "#str_swf_disabled"; + } + return va( "%dx", idMath::IPow( 2, r_motionBlur.GetInteger() ) ); + case SYSTEM_FIELD_LODBIAS: + return LinearAdjust( r_lodBias.GetFloat(), -1.0f, 1.0f, 0.0f, 100.0f ); + case SYSTEM_FIELD_BRIGHTNESS: + return LinearAdjust( r_lightScale.GetFloat(), 2.0f, 4.0f, 0.0f, 100.0f ); + case SYSTEM_FIELD_VOLUME: { + return 100.0f * Square( 1.0f - ( s_volume_dB.GetFloat() / DB_SILENCE ) ); + } + } + return false; +} + +/* +======================== +idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::IsDataChanged +======================== +*/ +bool idMenuScreen_Shell_SystemOptions::idMenuDataSource_SystemSettings::IsDataChanged() const { + if ( originalFramerate != com_engineHz.GetInteger() ) { + return true; + } + if ( originalAntialias != r_multiSamples.GetInteger() ) { + return true; + } + if ( originalMotionBlur != r_motionBlur.GetInteger() ) { + return true; + } + if ( originalVsync != r_swapInterval.GetInteger() ) { + return true; + } + if ( originalBrightness != r_lightScale.GetFloat() ) { + return true; + } + if ( originalVolume != s_volume_dB.GetFloat() ) { + return true; + } + return false; +} diff --git a/neo/d3xp/menus/MenuWidget.cpp b/neo/d3xp/menus/MenuWidget.cpp new file mode 100644 index 00000000..af8d7eef --- /dev/null +++ b/neo/d3xp/menus/MenuWidget.cpp @@ -0,0 +1,545 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget::idMenuWidget +======================== +*/ +idMenuWidget::idMenuWidget() : + boundSprite( NULL ), + parent( NULL ), + dataSource( NULL ), + dataSourceFieldIndex( 0 ), + focusIndex( 0 ), + widgetState( WIDGET_STATE_NORMAL ), + menuData( NULL ), + swfObj( NULL ), + handlerIsParent( false ), + refCount( 0 ), + noAutoFree( false ) { + + eventActionLookup.SetNum( eventActionLookup.Max() ); + for ( int i = 0; i < eventActionLookup.Num(); ++i ) { + eventActionLookup[ i ] = INVALID_ACTION_INDEX; + } +} + +/* +======================== +idMenuWidget::~idMenuWidget +======================== +*/ +idMenuWidget::~idMenuWidget() { + Cleanup(); +} + +void idMenuWidget::Cleanup() { + for ( int j = 0; j < observers.Num(); ++j ) { + assert( observers[j]->refCount > 0 ); + observers[ j ]->Release(); + } + + observers.Clear(); + + // free all children + for ( int i = 0; i < children.Num(); ++i ) { + assert( children[i]->refCount > 0 ); + children[ i ]->Release(); + } + + children.Clear(); +} + +/* +======================== +idMenuWidget::AddChild +======================== +*/ +void idMenuWidget::AddChild( idMenuWidget * widget ) { + if ( !verify( children.Find( widget ) == NULL ) ) { + return; // attempt to add a widget that was already in the list + } + + if ( widget->GetParent() != NULL ) { + // take out of previous parent + widget->GetParent()->RemoveChild( widget ); + } + + widget->AddRef(); + widget->SetParent( this ); + children.Append( widget ); +} + +/* +======================== +idMenuWidget::RemoveAllChildren +======================== +*/ +void idMenuWidget::RemoveAllChildren() { + + for ( int i = 0; i < children.Num(); ++ i ) { + + assert( children[ i ]->GetParent() == this ); + + children[ i ]->SetParent( NULL ); + children[ i ]->Release(); + } + + children.Clear(); +} + +/* +======================== +idMenuWidget::RemoveChild +======================== +*/ +void idMenuWidget::RemoveChild( idMenuWidget * widget ) { + assert( widget->GetParent() == this ); + + children.Remove( widget ); + widget->SetParent( NULL ); + widget->Release(); +} + +/* +======================== +idMenuWidget::RemoveChild +======================== +*/ +bool idMenuWidget::HasChild( idMenuWidget * widget ) { + for ( int i = 0; i < children.Num(); ++ i ) { + if ( children[ i ] == widget ) { + return true; + } + } + return false; +} + +/* +======================== +idMenuWidget::ReceiveEvent + +Events received through this function are passed to the innermost focused widget first, and then +propagates back through each widget within the focus chain. The first widget that handles the +event will stop propagation. + +Each widget along the way will fire off an event to its observers, whether or not it actually +handles the event. + +Note: How the focus chain is calculated: +Descend through GetFocus() calls until you reach a NULL focus. The terminating widget is the +innermost widget, while *this* widget is the outermost widget. +======================== +*/ +void idMenuWidget::ReceiveEvent( const idWidgetEvent & event ) { + idStaticList< idMenuWidget *, 16 > focusChain; + + int focusRunawayCounter = focusChain.Max(); + idMenuWidget * focusedWidget = this; + while ( focusedWidget != NULL && --focusRunawayCounter != 0 ) { + focusChain.Append( focusedWidget ); + focusedWidget = focusedWidget->GetFocus(); + } + + // If hitting this then more than likely you have a self-referential chain. If that's not + // the case, then you may need to increase the size of the focusChain list. + assert( focusRunawayCounter != 0 ); + for ( int focusIndex = focusChain.Num() - 1; focusIndex >= 0; --focusIndex ) { + idMenuWidget * const focusedWidget = focusChain[ focusIndex ]; + + if ( focusedWidget->ExecuteEvent( event ) ) { + break; // this widget has handled the event, so stop propagation + } + } +} + + +/* +======================== +idMenuWidget::ExecuteEvent + +Handles the event directly, and doesn't pass it through the focus chain. + +This should only be used in very specific circumstances! Most events should go to the focus. +======================== +*/ +bool idMenuWidget::ExecuteEvent( const idWidgetEvent & event ) { + idList< idWidgetAction, TAG_IDLIB_LIST_MENU > * const actions = GetEventActions( event.type ); + + if ( actions != NULL ) { + for ( int actionIndex = 0; actionIndex < actions->Num(); ++actionIndex ) { + HandleAction( ( *actions )[ actionIndex ], event, this ); + } + } + + SendEventToObservers( event ); + + return actions != NULL && actions->Num() > 0; +} + +/* +======================== +idMenuWidget::SendEventToObservers + +Sends an event to all the observers +======================== +*/ +void idMenuWidget::SendEventToObservers( const idWidgetEvent & event ) { + for ( int i = 0; i < observers.Num(); ++i ) { + observers[ i ]->ObserveEvent( *this, event ); + } +} + +/* +======================== +idMenuWidget::RegisterEventObserver + +Adds an observer to our observers list +======================== +*/ +void idMenuWidget::RegisterEventObserver( idMenuWidget * observer ) { + if ( !verify( observers.Find( observer ) == NULL ) ) { + return; + } + + observer->AddRef(); + observers.Append( observer ); +} + +/* +======================== +idMenuWidget::SetSpritePath +======================== +*/ +void idMenuWidget::SetSpritePath( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5 ) { + const char * args[] = { arg1, arg2, arg3, arg4, arg5 }; + const int numArgs = sizeof( args ) / sizeof( args[ 0 ] ); + spritePath.Clear(); + for ( int i = 0; i < numArgs; ++i ) { + if ( args[ i ] == NULL ) { + break; + } + spritePath.Append( args[ i ] ); + } +} + +/* +======================== +idMenuWidget::SetSpritePath +======================== +*/ +void idMenuWidget::SetSpritePath( const idList< idStr > & spritePath_, const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5 ) { + const char * args[] = { arg1, arg2, arg3, arg4, arg5 }; + const int numArgs = sizeof( args ) / sizeof( args[ 0 ] ); + spritePath = spritePath_; + for ( int i = 0; i < numArgs; ++i ) { + if ( args[ i ] == NULL ) { + break; + } + spritePath.Append( args[ i ] ); + } +} + +/* +======================== +idMenuWidget::ClearSprite +======================== +*/ +void idMenuWidget::ClearSprite() { + if ( GetSprite() == NULL ) { + return; + } + GetSprite()->SetVisible( false ); + boundSprite = NULL; +} + +/* +======================== +idMenuWidget::GetSWFObject +======================== +*/ +idSWF * idMenuWidget::GetSWFObject() { + + if ( swfObj != NULL ) { + return swfObj; + } + + if ( parent != NULL ) { + return parent->GetSWFObject(); + } + + if ( menuData != NULL ) { + return menuData->GetGUI(); + } + + return NULL; +} + +/* +======================== +idMenuWidget::GetMenuData +======================== +*/ +idMenuHandler * idMenuWidget::GetMenuData() { + if ( parent != NULL ) { + return parent->GetMenuData(); + } + + return menuData; +} + +/* +======================== +idMenuWidget::BindSprite + +Takes the sprite path strings and resolves it to an actual sprite relative to a given root. + +This is setup in this manner, because we can't resolve from path -> sprite immediately since +SWFs aren't necessarily loaded at the time widgets are instantiated. +======================== +*/ +bool idMenuWidget::BindSprite( idSWFScriptObject & root ) { + + const char * args[ 6 ] = { NULL }; + assert( GetSpritePath().Num() > 0 ); + for ( int i = 0; i < GetSpritePath().Num(); ++i ) { + args[ i ] = GetSpritePath()[ i ].c_str(); + } + boundSprite = root.GetNestedSprite( args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], args[ 4 ], args[ 5 ] ); + return boundSprite != NULL; +} + +/* +======================== +idMenuWidget::Show +======================== +*/ +void idMenuWidget::Show() { + if ( GetSWFObject() == NULL ) { + return; + } + + if ( !BindSprite( GetSWFObject()->GetRootObject() ) ) { + return; + } + + GetSprite()->SetVisible( true ); + int currentFrame = GetSprite()->GetCurrentFrame(); + int findFrame = GetSprite()->FindFrame( "rollOn" ); + int idleFrame = GetSprite()->FindFrame( "idle" ); + if ( currentFrame == findFrame || ( currentFrame > 1 && currentFrame <= idleFrame ) ) { + return; + } + + GetSprite()->PlayFrame( findFrame ); +} + +/* +======================== +idMenuWidget::Hide +======================== +*/ +void idMenuWidget::Hide() { + if ( GetSWFObject() == NULL ) { + return; + } + + if ( !BindSprite( GetSWFObject()->GetRootObject() ) ) { + return; + } + + int currentFrame = GetSprite()->GetCurrentFrame(); + int findFrame = GetSprite()->FindFrame( "rollOff" ); + if ( currentFrame >= findFrame || currentFrame == 1 ) { + return; + } + + GetSprite()->PlayFrame( findFrame ); +} + +/* +======================== +idMenuWidget::SetDataSource +======================== +*/ +void idMenuWidget::SetDataSource( idMenuDataSource * dataSource_, const int fieldIndex ) { + dataSource = dataSource_; + dataSourceFieldIndex = fieldIndex; +} + +/* +======================== +idMenuWidget::SetFocusIndex +======================== +*/ +void idMenuWidget::SetFocusIndex( const int index, bool skipSound ) { + + if ( GetChildren().Num() == 0 ) { + return; + } + + const int oldIndex = focusIndex; + + assert( index >= 0 && index < GetChildren().Num() ); //&& oldIndex >= 0 && oldIndex < GetChildren().Num() ); + + focusIndex = index; + + if ( oldIndex != focusIndex && !skipSound ) { + if ( menuData != NULL ) { + menuData->PlaySound( GUI_SOUND_FOCUS ); + } + } + + idSWFParmList parms; + parms.Append( oldIndex ); + parms.Append( index ); + + // need to mark the widget as having lost focus + if ( oldIndex != index && oldIndex >= 0 && oldIndex < GetChildren().Num() && GetChildByIndex( oldIndex ).GetState() != WIDGET_STATE_HIDDEN ) { + GetChildByIndex( oldIndex ).ReceiveEvent( idWidgetEvent( WIDGET_EVENT_FOCUS_OFF, 0, NULL, parms ) ); + } + + //assert( GetChildByIndex( index ).GetState() != WIDGET_STATE_HIDDEN ); + GetChildByIndex( index ).ReceiveEvent( idWidgetEvent( WIDGET_EVENT_FOCUS_ON, 0, NULL, parms ) ); +} + +/* +======================== +idMenuWidget_Button::SetState + +Transitioning from the current button state to the new button state +======================== +*/ +void idMenuWidget::SetState( const widgetState_t state ) { + if ( GetSprite() != NULL ) { + // FIXME: will need some more intelligence in the transitions to go from, say, + // selected_up -> up ... but this should work fine for now. + if ( state == WIDGET_STATE_HIDDEN ) { + GetSprite()->SetVisible( false ); + } else { + GetSprite()->SetVisible( true ); + if ( state == WIDGET_STATE_DISABLED ) { + GetSprite()->PlayFrame( "disabled" ); + } else if ( state == WIDGET_STATE_SELECTING ) { + if ( widgetState == WIDGET_STATE_NORMAL ) { + GetSprite()->PlayFrame( "selecting" ); // transition from unselected to selected + } else { + GetSprite()->PlayFrame( "sel_up" ); + } + } else if ( state == WIDGET_STATE_SELECTED ) { + GetSprite()->PlayFrame( "sel_up" ); + } else if ( state == WIDGET_STATE_NORMAL ) { + if ( widgetState == WIDGET_STATE_SELECTING ) { + GetSprite()->PlayFrame( "unselecting" ); // transition from selected to unselected + } else if ( widgetState != WIDGET_STATE_HIDDEN && widgetState != WIDGET_STATE_NORMAL ) { + GetSprite()->PlayFrame( "out" ); + } else { + GetSprite()->PlayFrame( "up" ); + } + } + } + + Update(); + } + + widgetState = state; +} + +/* +======================== +idMenuWidget::HandleAction +======================== +*/ +bool idMenuWidget::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + bool handled = false; + if ( GetParent() != NULL ) { + handled = GetParent()->HandleAction( action, event, widget ); + } else { + + if ( forceHandled ) { + return false; + } + + idMenuHandler * data = GetMenuData(); + if ( data != NULL ) { + return data->HandleAction( action, event, widget, false ); + } + } + + return handled; +} + +/* +======================== +idMenuWidget::GetEventActions +======================== +*/ +idList< idWidgetAction, TAG_IDLIB_LIST_MENU > * idMenuWidget::GetEventActions( const widgetEvent_t eventType ) { + if ( eventActionLookup[ eventType ] == INVALID_ACTION_INDEX ) { + return NULL; + } + return &eventActions[ eventActionLookup[ eventType ] ]; +} + +/* +======================== +idMenuWidget::AddEventAction +======================== +*/ +idWidgetAction & idMenuWidget::AddEventAction( const widgetEvent_t eventType ) { + if ( eventActionLookup[ eventType ] == INVALID_ACTION_INDEX ) { + eventActionLookup[ eventType ] = eventActions.Num(); + eventActions.Alloc(); + } + return eventActions[ eventActionLookup[ eventType ] ].Alloc(); +} + +/* +======================== +idMenuWidget::ClearEventActions +======================== +*/ +void idMenuWidget::ClearEventActions() { + eventActions.Clear(); + eventActionLookup.Clear(); + eventActionLookup.SetNum( eventActionLookup.Max() ); + for ( int i = 0; i < eventActionLookup.Num(); ++i ) { + eventActionLookup[ i ] = INVALID_ACTION_INDEX; + } +} + + + + + diff --git a/neo/d3xp/menus/MenuWidget.h b/neo/d3xp/menus/MenuWidget.h new file mode 100644 index 00000000..384df93c --- /dev/null +++ b/neo/d3xp/menus/MenuWidget.h @@ -0,0 +1,1341 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __MENU_WIDGET_H__ +#define __MENU_WIDGET_H__ + +class idMenuHandler; +class idMenuWidget; + +enum menuOption_t { + OPTION_INVALID = -1, + OPTION_BUTTON_TEXT, + OPTION_SLIDER_BAR, + OPTION_SLIDER_TEXT, + OPTION_SLIDER_TOGGLE, + OPTION_BUTTON_INFO, + OPTION_BUTTON_FULL_TEXT_SLIDER, + MAX_MENU_OPTION_TYPES +}; + +enum widgetEvent_t { + WIDGET_EVENT_PRESS, + WIDGET_EVENT_RELEASE, + WIDGET_EVENT_ROLL_OVER, + WIDGET_EVENT_ROLL_OUT, + WIDGET_EVENT_FOCUS_ON, + WIDGET_EVENT_FOCUS_OFF, + + WIDGET_EVENT_SCROLL_UP_LSTICK, + WIDGET_EVENT_SCROLL_UP_LSTICK_RELEASE, + WIDGET_EVENT_SCROLL_DOWN_LSTICK, + WIDGET_EVENT_SCROLL_DOWN_LSTICK_RELEASE, + WIDGET_EVENT_SCROLL_LEFT_LSTICK, + WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE, + WIDGET_EVENT_SCROLL_RIGHT_LSTICK, + WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE, + + WIDGET_EVENT_SCROLL_UP_RSTICK, + WIDGET_EVENT_SCROLL_UP_RSTICK_RELEASE, + WIDGET_EVENT_SCROLL_DOWN_RSTICK, + WIDGET_EVENT_SCROLL_DOWN_RSTICK_RELEASE, + WIDGET_EVENT_SCROLL_LEFT_RSTICK, + WIDGET_EVENT_SCROLL_LEFT_RSTICK_RELEASE, + WIDGET_EVENT_SCROLL_RIGHT_RSTICK, + WIDGET_EVENT_SCROLL_RIGHT_RSTICK_RELEASE, + + WIDGET_EVENT_SCROLL_UP, + WIDGET_EVENT_SCROLL_UP_RELEASE, + WIDGET_EVENT_SCROLL_DOWN, + WIDGET_EVENT_SCROLL_DOWN_RELEASE, + WIDGET_EVENT_SCROLL_LEFT, + WIDGET_EVENT_SCROLL_LEFT_RELEASE, + WIDGET_EVENT_SCROLL_RIGHT, + WIDGET_EVENT_SCROLL_RIGHT_RELEASE, + + WIDGET_EVENT_DRAG_START, + WIDGET_EVENT_DRAG_STOP, + + WIDGET_EVENT_SCROLL_PAGEDWN, + WIDGET_EVENT_SCROLL_PAGEDWN_RELEASE, + WIDGET_EVENT_SCROLL_PAGEUP, + WIDGET_EVENT_SCROLL_PAGEUP_RELEASE, + + WIDGET_EVENT_SCROLL, + WIDGET_EVENT_SCROLL_RELEASE, + WIDGET_EVENT_BACK, + WIDGET_EVENT_COMMAND, + WIDGET_EVENT_TAB_NEXT, + WIDGET_EVENT_TAB_PREV, + MAX_WIDGET_EVENT +}; + +enum scrollType_t { + SCROLL_SINGLE, // scroll a single unit + SCROLL_PAGE, // scroll a page + SCROLL_FULL, // scroll all the way to the end + SCROLL_TOP, // scroll to the first selection + SCROLL_END, // scroll to the last selection +}; + +enum widgetAction_t { + WIDGET_ACTION_NONE, + WIDGET_ACTION_COMMAND, + WIDGET_ACTION_FUNCTION, // call the SWF function + WIDGET_ACTION_SCROLL_VERTICAL, // scroll something. takes one param = amount to scroll (can be negative) + WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE, + WIDGET_ACTION_SCROLL_PAGE, + WIDGET_ACTION_SCROLL_HORIZONTAL, // scroll something. takes one param = amount to scroll (can be negative) + WIDGET_ACTION_SCROLL_TAB, + WIDGET_ACTION_START_REPEATER, + WIDGET_ACTION_STOP_REPEATER, + WIDGET_ACTION_ADJUST_FIELD, + WIDGET_ACTION_PRESS_FOCUSED, + WIDGET_ACTION_JOY3_ON_PRESS, + WIDGET_ACTION_JOY4_ON_PRESS, + // + WIDGET_ACTION_GOTO_MENU, + WIDGET_ACTION_GO_BACK, + WIDGET_ACTION_EXIT_GAME, + WIDGET_ACTION_LAUNCH_MULTIPLAYER, + WIDGET_ACTION_MENU_BAR_SELECT, + WIDGET_ACTION_EMAIL_HOVER, + // PDA USER DATA ACTIONS + WIDGET_ACTION_PDA_SELECT_USER, + WIDGET_ACTION_SELECT_GAMERTAG, + WIDGET_ACTION_PDA_SELECT_NAV, + WIDGET_ACTION_SELECT_PDA_AUDIO, + WIDGET_ACTION_SELECT_PDA_VIDEO, + WIDGET_ACTION_SELECT_PDA_ITEM, + WIDGET_ACTION_SCROLL_DRAG, + // PDA EMAIL ACTIONS + WIDGET_ACTION_PDA_SELECT_EMAIL, + WIDGET_ACTION_PDA_CLOSE, + WIDGET_ACTION_REFRESH, + WIDGET_ACTION_MUTE_PLAYER, + MAX_WIDGET_ACTION +}; + +enum actionHandler_t { + WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER, + WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE, + WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER, + WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE, + WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER, + WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER, + WIDGET_ACTION_EVENT_SCROLL_PAGE_DOWN_START_REPEATER, + WIDGET_ACTION_EVENT_SCROLL_PAGE_UP_START_REPEATER, + WIDGET_ACTION_EVENT_STOP_REPEATER, + WIDGET_ACTION_EVENT_TAB_NEXT, + WIDGET_ACTION_EVENT_TAB_PREV, + WIDGET_ACTION_EVENT_DRAG_START, + WIDGET_ACTION_EVENT_DRAG_STOP, + WIDGET_ACTION_EVENT_JOY3_ON_PRESS, +}; + +struct widgetTransition_t { + widgetTransition_t() : + animationName( NULL ) { + + } + + const char * animationName; // name of the animation to run + idStaticList< const char *, 4 > prefixes; // prefixes to try to use for animation +}; + + +/* +================================================ +scoreboardInfo_t +================================================ +*/ +struct scoreboardInfo_t { + scoreboardInfo_t() : + index( -1 ), + voiceState( VOICECHAT_DISPLAY_NONE ) { + } + + idList< idStr, TAG_IDLIB_LIST_MENU> values; + int index; + voiceStateDisplay_t voiceState; +}; + +/* +================================================ +idSort_SavesByDate +================================================ +*/ +class idSort_SavesByDate : public idSort_Quick< idSaveGameDetails, idSort_SavesByDate > { +public: + int Compare( const idSaveGameDetails & a, const idSaveGameDetails & b ) const { + return b.date - a.date; + } +}; + +/* +================================================ +idMenuDataSource +================================================ +*/ +class idMenuDataSource { +public: + virtual ~idMenuDataSource() { } + + virtual void LoadData() = 0; + virtual void CommitData() = 0; + virtual bool IsDataChanged() const = 0; + virtual idSWFScriptVar GetField( const int fieldIndex ) const = 0; + virtual void AdjustField( const int fieldIndex, const int adjustAmount ) = 0; +}; + +/* +================================================ +idWidgetEvent +================================================ +*/ +class idWidgetEvent { +public: + idWidgetEvent() : + type( WIDGET_EVENT_PRESS ), + arg( 0 ), + thisObject( NULL ) { + + } + + idWidgetEvent( const widgetEvent_t type_, const int arg_, idSWFScriptObject * thisObject_, const idSWFParmList & parms_ ) : + type( type_ ), + arg( arg_ ), + thisObject( thisObject_ ), + parms( parms_ ) { + } + + widgetEvent_t type; + int arg; + idSWFScriptObject * thisObject; + idSWFParmList parms; +}; + +/* +================================================ +idWidgetAction +================================================ +*/ +class idWidgetAction { +public: + idWidgetAction() : + action( WIDGET_ACTION_NONE ), + scriptFunction( NULL ) { + } + + idWidgetAction( const idWidgetAction & src ) { + action = src.action; + parms = src.parms; + scriptFunction = src.scriptFunction; + if ( scriptFunction != NULL ) { + scriptFunction->AddRef(); + } + } + + ~idWidgetAction() { + if ( scriptFunction != NULL ) { + scriptFunction->Release(); + } + } + + void operator=( const idWidgetAction & src ) { + action = src.action; + parms = src.parms; + scriptFunction = src.scriptFunction; + if ( scriptFunction != NULL ) { + scriptFunction->AddRef(); + } + } + + bool operator==( const idWidgetAction & otherAction ) const { + if ( GetType() != otherAction.GetType() + || GetParms().Num() != otherAction.GetParms().Num() ) { + return false; + } + + // everything else is equal, so check all parms. NOTE: this assumes we are only sending + // integral types. + for ( int i = 0; i < GetParms().Num(); ++i ) { + if ( GetParms()[ i ].GetType() != otherAction.GetParms()[ i ].GetType() + || GetParms()[ i ].ToInteger() != otherAction.GetParms()[ i ].ToInteger() ) { + return false; + } + } + + return true; + } + + void Set( idSWFScriptFunction * function ) { + action = WIDGET_ACTION_FUNCTION; + if ( scriptFunction != NULL ) { + scriptFunction->Release(); + } + scriptFunction = function; + scriptFunction->AddRef(); + } + + void Set( widgetAction_t action_ ) { + action = action_; + parms.Clear(); + } + + void Set( widgetAction_t action_, const idSWFScriptVar & var1 ) { + action = action_; + parms.Clear(); + parms.Append( var1 ); + } + + void Set( widgetAction_t action_, const idSWFScriptVar & var1, const idSWFScriptVar & var2 ) { + action = action_; + parms.Clear(); + parms.Append( var1 ); + parms.Append( var2 ); + } + + void Set( widgetAction_t action_, const idSWFScriptVar & var1, const idSWFScriptVar & var2, const idSWFScriptVar & var3 ) { + action = action_; + parms.Clear(); + parms.Append( var1 ); + parms.Append( var2 ); + parms.Append( var3 ); + } + + void Set( widgetAction_t action_, const idSWFScriptVar & var1, const idSWFScriptVar & var2, const idSWFScriptVar & var3, const idSWFScriptVar & var4 ) { + action = action_; + parms.Clear(); + parms.Append( var1 ); + parms.Append( var2 ); + parms.Append( var3 ); + parms.Append( var4 ); + } + + idSWFScriptFunction * GetScriptFunction() { return scriptFunction; } + const widgetAction_t GetType() const { return action; } + const idSWFParmList & GetParms() const { return parms; } + +private: + widgetAction_t action; + idSWFParmList parms; + idSWFScriptFunction * scriptFunction; +}; + +typedef idList< idMenuWidget *, TAG_IDLIB_LIST_MENU > idMenuWidgetList; + +/* +================================================ +idMenuWidget + +We're using a model/view architecture, so this is the combination of both model and view. The +other part of the view is the SWF itself. +================================================ +*/ +class idMenuWidget { +public: + + /* + ================================================ + WrapWidgetSWFEvent + ================================================ + */ + class WrapWidgetSWFEvent : public idSWFScriptFunction_RefCounted { + public: + WrapWidgetSWFEvent( idMenuWidget * widget, const widgetEvent_t event, const int eventArg ) : + targetWidget( widget ), + targetEvent( event ), + targetEventArg( eventArg ) { + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + targetWidget->ReceiveEvent( idWidgetEvent( targetEvent, targetEventArg, thisObject, parms ) ); + return idSWFScriptVar(); + } + + private: + idMenuWidget * targetWidget; + widgetEvent_t targetEvent; + int targetEventArg; + }; + + + enum widgetState_t { + WIDGET_STATE_HIDDEN, // hidden + WIDGET_STATE_NORMAL, // normal + WIDGET_STATE_SELECTING, // going into the selected state + WIDGET_STATE_SELECTED, // fully selected + WIDGET_STATE_DISABLED, // disabled + WIDGET_STATE_MAX + }; + + idMenuWidget(); + + virtual ~idMenuWidget(); + + void Cleanup(); + // typically this is where the allocations for a widget will occur: sub widgets, etc. + // Note that SWF sprite objects may not be accessible at this point. + virtual void Initialize( idMenuHandler * data ) { menuData = data; } + + // takes the information described in this widget and applies it to a given script object. + // the script object should point to the root that you want to run from. Running this will + // also create the sprite binding, if any. + virtual void Update() {} + virtual void Show(); + virtual void Hide(); + + widgetState_t GetState() const { return widgetState; } + void SetState( const widgetState_t state ); + + // actually binds the sprite. this must be called after setting sprite path! + idSWFSpriteInstance * GetSprite() { return boundSprite; } + idSWF * GetSWFObject(); + idMenuHandler * GetMenuData(); + bool BindSprite( idSWFScriptObject & root ); + void ClearSprite(); + + void SetSpritePath( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL ); + void SetSpritePath( const idList< idStr > & spritePath_, const char * arg1 = NULL, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL ); + idList< idStr, TAG_IDLIB_LIST_MENU > & GetSpritePath() { return spritePath; } + int GetRefCount() const { return refCount; } + void AddRef() { refCount++; } + void Release() { assert( refCount > 0 ); if ( --refCount == 0 && !noAutoFree ) { delete this; } } + + //------------------------ + // Event Handling + //------------------------ + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { } + void SendEventToObservers( const idWidgetEvent & event ); + void RegisterEventObserver( idMenuWidget * observer ); + void ReceiveEvent( const idWidgetEvent & event ); + + // Executes an event in the context of this widget. Only rarely should this be called + // directly. Instead calls should go through ReceiveEvent which will propagate the event + // through the standard focus order. + virtual bool ExecuteEvent( const idWidgetEvent & event ); + + // returns the list of actions for a given event, or NULL if no actions are registered for + // that event. Events should not be directly added to the returned list. Instead use + // AddEventAction for adding new events. + idList< idWidgetAction, TAG_IDLIB_LIST_MENU > * GetEventActions( const widgetEvent_t eventType ); + + // allocates an action for the given event + idWidgetAction & AddEventAction( const widgetEvent_t eventType ); + void ClearEventActions(); + + //------------------------ + // Data modeling + //------------------------ + void SetDataSource( idMenuDataSource * dataSource, const int fieldIndex ); + idMenuDataSource * GetDataSource() { return dataSource; } + void SetDataSourceFieldIndex( const int dataSourceFieldIndex_ ) { dataSourceFieldIndex = dataSourceFieldIndex_; } + int GetDataSourceFieldIndex() const { return dataSourceFieldIndex; } + + idMenuWidget * GetFocus() { return ( focusIndex >= 0 && focusIndex < children.Num() ) ? children[ focusIndex ] : NULL; } + int GetFocusIndex() const { return focusIndex; } + void SetFocusIndex( const int index, bool skipSound = false ); + + //------------------------ + // Hierarchy + //------------------------ + idMenuWidgetList & GetChildren() { return children; } + const idMenuWidgetList & GetChildren() const { return children; } + idMenuWidget & GetChildByIndex( const int index ) const { return *children[ index ]; } + + void AddChild( idMenuWidget * widget ); + void RemoveChild( idMenuWidget * widget ); + bool HasChild( idMenuWidget * widget ); + void RemoveAllChildren(); + + idMenuWidget * GetParent() { return parent; } + const idMenuWidget * GetParent() const { return parent; } + void SetParent( idMenuWidget * parent_ ) { parent = parent_; } + void SetSWFObj( idSWF * obj ) { swfObj = obj; } + bool GetHandlerIsParent() { return handlerIsParent; } + void SetHandlerIsParent( bool val ) { handlerIsParent = val; } + void SetNoAutoFree( bool b ) { noAutoFree = b; } + +protected: + void ForceFocusIndex( const int index ) { focusIndex = index; } + +protected: + bool handlerIsParent; + idMenuHandler * menuData; + idSWF * swfObj; + idSWFSpriteInstance * boundSprite; + idMenuWidget * parent; + idList< idStr, TAG_IDLIB_LIST_MENU > spritePath; + idMenuWidgetList children; + idMenuWidgetList observers; + + static const int INVALID_ACTION_INDEX = -1; + idList< idList< idWidgetAction, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > eventActions; + idStaticList< int, MAX_WIDGET_EVENT > eventActionLookup; + + idMenuDataSource * dataSource; + int dataSourceFieldIndex; + + int focusIndex; + + widgetState_t widgetState; + int refCount; + bool noAutoFree; +}; + +/* +================================================ +idMenuWidget_Button + +This might be better named "ComboButton", as it contains behavior for several controls along +with standard button behavior. +================================================ +*/ +class idMenuWidget_Button : public idMenuWidget { +public: + + enum animState_t { + ANIM_STATE_UP, // standard + ANIM_STATE_DOWN, // pressed down + ANIM_STATE_OVER, // hovered over this + ANIM_STATE_MAX + }; + + idMenuWidget_Button() : + animState( ANIM_STATE_UP ), + img( NULL ), + ignoreColor( false ) { + } + + virtual ~idMenuWidget_Button() {} + + virtual bool ExecuteEvent( const idWidgetEvent & event ); + virtual void Update(); + + //--------------- + // Model + //--------------- + void SetLabel( const idStr & label ) { btnLabel = label; } + const idStr & GetLabel() const { return btnLabel; } + void SetValues( idList< idStr > & list ); + const idStr & GetValue( int index ) const; + void SetImg( const idMaterial * val ) { img = val; } + const idMaterial * GetImg() { return img; } + void SetDescription( const char * desc_ ) { description = desc_; } + const idStr & GetDescription() const { return description; } + + void SetIgnoreColor( const bool b ) { ignoreColor = b; } + + animState_t GetAnimState() const { return animState; } + void SetAnimState( const animState_t state ) { animState = state; } + void SetOnPressFunction( idSWFScriptFunction * func ) { scriptFunction = func; } + +protected: + void SetupTransitionInfo( widgetTransition_t & trans, const widgetState_t buttonState, const animState_t sourceAnimState, const animState_t destAnimState ) const; + void AnimateToState( const animState_t targetState, const bool force = false ); + + idList< idStr, TAG_IDLIB_LIST_MENU > values; + idStr btnLabel; + idStr description; + animState_t animState; + const idMaterial * img; + idSWFScriptFunction * scriptFunction; + bool ignoreColor; +}; + +/* +================================================ +idMenuWidget_LobbyButton +================================================ +*/ +class idMenuWidget_LobbyButton : public idMenuWidget_Button { +public: + idMenuWidget_LobbyButton() : + voiceState( VOICECHAT_DISPLAY_NONE ) { + } + + virtual void Update(); + void SetButtonInfo( idStr name_, voiceStateDisplay_t voiceState_ ); + bool IsValid() { return !name.IsEmpty(); } + +protected: + idStr name; + voiceStateDisplay_t voiceState; +}; + +/* +================================================ +idMenuWidget_ScoreboardButton +================================================ +*/ +class idMenuWidget_ScoreboardButton : public idMenuWidget_Button { +public: + idMenuWidget_ScoreboardButton() : + voiceState( VOICECHAT_DISPLAY_NONE ), + index( -1 ) { + } + + virtual void Update(); + void SetButtonInfo( int index_, idList< idStr > & list, voiceStateDisplay_t voiceState_ ); + +protected: + voiceStateDisplay_t voiceState; + int index; +}; + +/* +================================================ +idMenuWidget_ControlButton +================================================ +*/ +class idMenuWidget_ControlButton : public idMenuWidget_Button { +public: + idMenuWidget_ControlButton() : + optionType( OPTION_BUTTON_TEXT ), + disabled( false ) { + } + + virtual void Update(); + void SetOptionType( const menuOption_t type ) { optionType = type; } + menuOption_t GetOptionType() const { return optionType; } + void SetupEvents( int delay, int index ); + void SetDisabled( bool disable ) { disabled = disable; } + +protected: + menuOption_t optionType; + bool disabled; +}; + +/* +================================================ +idMenuWidget_ServerButton +================================================ +*/ +class idMenuWidget_ServerButton : public idMenuWidget_Button { +public: + + idMenuWidget_ServerButton() : + players( 0 ), + index( 0 ), + maxPlayers( 0 ), + joinable( false ), + validMap( false ) { + } + + virtual void Update(); + void SetButtonInfo( idStr name_, idStrId mapName_, idStr modeName_, int index_ = 0, int players_ = 0, int maxPlayers_ = 0, bool joinable_ = false, bool validMap_ = false ); + bool IsValid() { return !serverName.IsEmpty(); } + bool CanJoin() { return ( joinable && validMap ); } + +protected: + idStr serverName; + int index; + int players; + int maxPlayers; + bool joinable; + bool validMap; + idStrId mapName; + idStr modeName; +}; + +/* +================================================ +idMenuWidget_NavButton +================================================ +*/ +class idMenuWidget_NavButton : public idMenuWidget_Button { +public: + + enum navWidgetState_t { + NAV_WIDGET_LEFT, // option on left side + NAV_WIDGET_RIGHT, // option on right side + NAV_WIDGET_SELECTED // option is selected + }; + + idMenuWidget_NavButton() : + navIndex( 0 ), + xPos( 0 ) { + } + + virtual bool ExecuteEvent( const idWidgetEvent & event ); + virtual void Update(); + + void SetNavIndex( int i, const navWidgetState_t type ) { navIndex = i; navState = type; } + void SetPosition( float pos ) { xPos = pos; } + +private: + + int navIndex; + float xPos; + navWidgetState_t navState; + +}; + +/* +================================================ +idMenuWidget_NavButton +================================================ +*/ +class idMenuWidget_MenuButton : public idMenuWidget_Button { +public: + + idMenuWidget_MenuButton() : + xPos( 0 ) { + } + + virtual void Update(); + void SetPosition( float pos ) { xPos = pos; } + +private: + + float xPos; + +}; + +/* +================================================ +idMenuWidget_List + +Stores a list of widgets but displays only a segment of them at a time. +================================================ +*/ +class idMenuWidget_List : public idMenuWidget { +public: + idMenuWidget_List() : + numVisibleOptions( 0 ), + viewOffset( 0 ), + viewIndex( 0 ), + allowWrapping( false ) { + + } + + virtual void Update(); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + virtual void Scroll( const int scrollIndexAmount, const bool wrapAround = false ); + virtual void ScrollOffset( const int scrollIndexAmount ); + virtual int GetTotalNumberOfOptions() const { return GetChildren().Num(); } + virtual bool PrepareListElement( idMenuWidget & widget, const int childIndex ) { return true; } + + bool IsWrappingAllowed() const { return allowWrapping; } + void SetWrappingAllowed( const bool allow ) { allowWrapping = allow; } + + void SetNumVisibleOptions( const int numVisibleOptions_ ) { numVisibleOptions = numVisibleOptions_; } + int GetNumVisibleOptions() const { return numVisibleOptions; } + + int GetViewOffset() const { return viewOffset; } + void SetViewOffset( const int offset ) { viewOffset = offset; } + + int GetViewIndex() const { return viewIndex; } + void SetViewIndex( const int index ) { viewIndex = index; } + + void CalculatePositionFromIndexDelta( int & outIndex, int & outOffset, const int currentIndex, const int currentOffset, const int windowSize, const int maxSize, const int indexDelta, const bool allowWrapping, const bool wrapAround = false ) const; + void CalculatePositionFromOffsetDelta( int & outIndex, int & outOffset, const int currentIndex, const int currentOffset, const int windowSize, const int maxSize, const int offsetDelta ) const; + +private: + int numVisibleOptions; + int viewOffset; + int viewIndex; + bool allowWrapping; +}; + +class idBrowserEntry_t { +public: + idStr serverName; + int index; + int players; + int maxPlayers; + bool joinable; + bool validMap; + idStrId mapName; + idStr modeName; +}; + +/* +================================================ +idMenuWidget_GameBrowserList +================================================ +*/ +class idMenuWidget_GameBrowserList : public idMenuWidget_List { +public: + virtual void Update(); + virtual bool PrepareListElement( idMenuWidget & widget, const int childIndex ); + virtual int GetTotalNumberOfOptions() const; + void ClearGames(); + void AddGame( idStr name_, idStrId mapName_, idStr modeName_, int index_ = 0, int players_ = 0, int maxPlayers_ = 0, bool joinable_ = false, bool validMap_ = false ); + int GetServerIndex(); +private: + idList< idBrowserEntry_t > games; +}; + +/* +================================================ +idMenuWidget_Carousel +Displays a list of items in a looping carousel pattern +================================================ +*/ +class idMenuWidget_Carousel : public idMenuWidget { +public: + + idMenuWidget_Carousel() : + numVisibleOptions( 0 ), + viewIndex( 0 ), + moveToIndex( 0 ), + scrollLeft( false ), + fastScroll( false ), + moveDiff( 0 ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual int GetTotalNumberOfOptions() const { return imgList.Num(); } + virtual bool PrepareListElement( idMenuWidget & widget, const int childIndex ) { return true; } + + void SetNumVisibleOptions( const int numVisibleOptions_ ) { numVisibleOptions = numVisibleOptions_; } + int GetNumVisibleOptions() const { return numVisibleOptions; } + + void MoveToIndex( int index, bool instant = false ); + void MoveToFirstItem( bool instant = true ); + void MoveToLastItem( bool instant = true ); + int GetMoveToIndex() { return moveToIndex; } + void SetMoveToIndex( int index ) { moveToIndex = index; } + void SetViewIndex( int index ) { viewIndex = index; } + int GetViewIndex() const { return viewIndex; } + void SetListImages( idList & list ); + void SetMoveDiff( int val ) { moveDiff = val; } + int GetMoveDiff() { return moveDiff; } + bool GetScrollLeft() { return scrollLeft; } + +private: + + int numVisibleOptions; + int viewIndex; + int moveToIndex; + int moveDiff; + bool fastScroll; + bool scrollLeft; + idList imgList; + +}; + +/* +================================================ +idMenuWidget_Help + +Knows how to display help tooltips by observing events from other widgets +================================================ +*/ +class idMenuWidget_Help : public idMenuWidget { +public: + virtual void Update(); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + +private: + idStr lastFocusedMessage; // message from last widget that had focus + idStr lastHoveredMessage; // message from last widget that was hovered over + bool hideMessage; +}; + +/* +================================================ +idMenuWidget_CommandBar +================================================ +*/ +class idMenuWidget_CommandBar : public idMenuWidget { +public: + enum button_t { + BUTTON_JOY1, + BUTTON_JOY2, + BUTTON_JOY3, + BUTTON_JOY4, + BUTTON_JOY10, + BUTTON_TAB, + MAX_BUTTONS + }; + + enum alignment_t { + LEFT, + RIGHT + }; + + struct buttonInfo_t { + idStr label; // empty labels are treated as hidden buttons + idWidgetAction action; + }; + + idMenuWidget_CommandBar() : + alignment( LEFT ) { + + buttons.SetNum( MAX_BUTTONS ); + } + + virtual void Update(); + virtual bool ExecuteEvent( const idWidgetEvent & event ); + + buttonInfo_t * GetButton( const button_t button ) { return &buttons[ button ]; } + void ClearAllButtons(); + + alignment_t GetAlignment() const { return alignment; } + void SetAlignment( const alignment_t alignment_ ) { alignment = alignment_; } + +private: + idStaticList< buttonInfo_t, MAX_BUTTONS > buttons; + alignment_t alignment; +}; + +/* +================================================ + idMenuWidget_DynamicList +================================================ +*/ +class idMenuWidget_LobbyList : public idMenuWidget_List { +public: + idMenuWidget_LobbyList() : + numEntries( 0 ) { + } + + virtual void Update(); + virtual bool PrepareListElement( idMenuWidget & widget, const int childIndex ); + virtual int GetTotalNumberOfOptions() const { return numEntries; } + void SetEntryData( int index, idStr name, voiceStateDisplay_t voiceState ); + void SetHeadingInfo( idList< idStr > & list ); + void SetNumEntries( int num ) { numEntries = num; } + int GetNumEntries() { return numEntries; } + void SetRefreshFunction( const char* func ); +private: + idList< idStr, TAG_IDLIB_LIST_MENU > headings; + int numEntries; +}; + +/* +================================================ +idMenuWidget_DynamicList +================================================ +*/ +class idMenuWidget_DynamicList : public idMenuWidget_List { +public: + + idMenuWidget_DynamicList() : + controlList( false ), + ignoreColor( false ) { + } + + virtual void Update(); + virtual void Initialize( idMenuHandler * data ); + virtual int GetTotalNumberOfOptions() const; + virtual bool PrepareListElement( idMenuWidget & widget, const int childIndex ); + + virtual void Recalculate(); + virtual void SetListData( idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > & list ); + + void SetControlList( bool val ) { controlList = val; } + void SetIgnoreColor( bool val ) { ignoreColor = val; } + +protected: + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > listItemInfo; + bool controlList; + bool ignoreColor; +}; + +/* +================================================ +idMenuWidget_ScoreboardList +================================================ +*/ +class idMenuWidget_ScoreboardList : public idMenuWidget_DynamicList { +public: + virtual void Update(); + virtual int GetTotalNumberOfOptions() const; +}; + +/* +================================================ +idMenuWidget_NavBar + +The nav bar is set up with the main option being at the safe frame line. +================================================ +*/ +class idMenuWidget_NavBar : public idMenuWidget_DynamicList { +public: + + idMenuWidget_NavBar() : + initialPos( 0.0f ), + buttonPos( 0.0f ), + leftSpacer( 0.0f ), + rightSpacer( 0.0f ), + selectedSpacer( 0.0f ) { + } + + virtual void Update(); + virtual void Initialize( idMenuHandler * data ); + virtual void SetInitialXPos( float pos ) { initialPos = pos; } + virtual void SetButtonSpacing( float lSpace, float rSpace, float sSpace ) { leftSpacer = lSpace; rightSpacer = rSpace; selectedSpacer = sSpace; } + virtual bool PrepareListElement( idMenuWidget & widget, const int navIndex ); + virtual void SetListHeadings( idList< idStr > & list ); + virtual int GetTotalNumberOfOptions() const; + +private: + + idList< idStr, TAG_IDLIB_LIST_MENU > headings; + float initialPos; + float buttonPos; + float leftSpacer; + float rightSpacer; + float selectedSpacer; + +}; + +/* +================================================ +idMenuWidget_NavBar + +The nav bar is set up with the main option being at the safe frame line. +================================================ +*/ +class idMenuWidget_MenuBar : public idMenuWidget_DynamicList { +public: + + idMenuWidget_MenuBar() : + totalWidth( 0.0f ), + buttonPos( 0.0f ), + rightSpacer( 0.0f ) { + } + + virtual void Update(); + virtual void Initialize( idMenuHandler * data ); + virtual void SetButtonSpacing( float rSpace ) { rightSpacer = rSpace; } + virtual bool PrepareListElement( idMenuWidget & widget, const int navIndex ); + virtual void SetListHeadings( idList< idStr > & list ); + virtual int GetTotalNumberOfOptions() const; + +private: + + idList< idStr, TAG_IDLIB_LIST_MENU > headings; + float totalWidth; + float buttonPos; + float rightSpacer; + +}; + +/* +================================================ +idMenuWidget_PDA_UserData +================================================ +*/ +class idMenuWidget_PDA_UserData : public idMenuWidget { +public: + idMenuWidget_PDA_UserData() : + pdaIndex( 0 ) { + } +virtual ~idMenuWidget_PDA_UserData() {} + virtual void Update(); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + +private: + int pdaIndex; +}; + +/* +================================================ +idMenuWidget_DynamicList +================================================ +*/ +class idMenuWidget_ScrollBar : public idMenuWidget { +public: + idMenuWidget_ScrollBar() : + yTop( 0.0f ), + yBot( 0.0f ), + dragging( false ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + + void CalcTopAndBottom(); + void CalculatePosition( float x, float y ); + + float yTop; + float yBot; + bool dragging; +}; + +/* +================================================ +idMenuWidget_InfoBox +================================================ +*/ +class idMenuWidget_InfoBox: public idMenuWidget { +public: + idMenuWidget_InfoBox() : + scrollbar( NULL ) { + } + + virtual void Initialize( idMenuHandler * data ); + virtual void Update(); + virtual bool HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled = false ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + void SetHeading( idStr val ) { heading = val; } + void SetBody( idStr val ) { info = val; } + void ResetInfoScroll(); + void Scroll( int d ); + int GetScroll(); + int GetMaxScroll(); + void SetScroll( int scroll ); + void SetScrollbar( idMenuWidget_ScrollBar * bar ); +private: + idMenuWidget_ScrollBar * scrollbar; + idStr heading; + idStr info; +}; + +/* +================================================ +idMenuWidget_PDA_Objective +================================================ +*/ +class idMenuWidget_PDA_Objective: public idMenuWidget { +public: + idMenuWidget_PDA_Objective() : + pdaIndex( 0 ) { + } + virtual void Update(); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); +private: + int pdaIndex; +}; + +/* +================================================ +idMenuWidget_Shell_SaveInfo +================================================ +*/ +class idMenuWidget_Shell_SaveInfo: public idMenuWidget { +public: + idMenuWidget_Shell_SaveInfo() : + loadIndex( 0 ), + forSaveScreen( false ) { + } + virtual void Update(); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + + void SetForSaveScreen( bool val ) { forSaveScreen = val; } +private: + int loadIndex; + bool forSaveScreen; +}; + +/* +================================================ +idMenuWidget_PDA_AudioFiles +================================================ +*/ +class idMenuWidget_PDA_AudioFiles: public idMenuWidget { +public: + idMenuWidget_PDA_AudioFiles() : + pdaIndex( 0 ) { + } + virtual ~idMenuWidget_PDA_AudioFiles(); + virtual void Update(); + virtual void Initialize( idMenuHandler * data ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); +private: + int pdaIndex; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > audioFileNames; +}; + +/* +================================================ +idMenuWidget_PDA_AudioFiles +================================================ +*/ +class idMenuWidget_PDA_EmailInbox: public idMenuWidget { +public: + idMenuWidget_PDA_EmailInbox() : + pdaIndex( 0 ), + emailList( NULL ), + scrollbar( NULL ) { + } + virtual void Update(); + virtual void Initialize( idMenuHandler * data ); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); + + idMenuWidget_DynamicList * GetEmailList() { return emailList; } + idMenuWidget_ScrollBar * GetScrollbar() { return scrollbar; } +private: + idMenuWidget_DynamicList * emailList; + idMenuWidget_ScrollBar * scrollbar; + int pdaIndex; + idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > emailInfo; +}; + +/* +================================================ + idMenuWidget_PDA_AudioFiles +================================================ +*/ +class idMenuWidget_ItemAssignment: public idMenuWidget { +public: + idMenuWidget_ItemAssignment() : + slotIndex( 0 ) { + } + + virtual void Update(); + void SetIcon( int index, const idMaterial * icon ); + void FindFreeSpot(); + int GetSlotIndex() { return slotIndex; } + void SetSlotIndex( int num ) { slotIndex = num; } +private: + const idMaterial * images[ NUM_QUICK_SLOTS ]; + int slotIndex; + +}; + + +/* +================================================ +idMenuWidget_PDA_AudioFiles +================================================ +*/ +class idMenuWidget_PDA_VideoInfo: public idMenuWidget { +public: + virtual void Update(); + virtual void ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ); +private: + int videoIndex; +}; + + +/* +================================================ +idWidgetActionHandler +================================================ +*/ +class idWidgetActionHandler : public idSWFScriptFunction_RefCounted { +public: + idWidgetActionHandler( idMenuWidget * widget, actionHandler_t actionEventType, widgetEvent_t _event ) : + targetWidget( widget ), + type( actionEventType ), + targetEvent( _event ) { + + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + idWidgetAction action; + bool handled = false; + switch ( type ) { + case WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, 1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, -1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_DOWN_START_REPEATER_VARIABLE: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE, 1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_UP_START_REPEATER_VARIABLE: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL_VARIABLE, -1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_PAGE_DOWN_START_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_PAGE, 1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_PAGE_UP_START_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_PAGE, -1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_STOP_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_STOP_REPEATER ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_TAB_NEXT: { + action.Set( (widgetAction_t)WIDGET_ACTION_SCROLL_TAB, 1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_TAB_PREV: { + action.Set( (widgetAction_t)WIDGET_ACTION_SCROLL_TAB, -1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_JOY3_ON_PRESS: { + action.Set( (widgetAction_t)WIDGET_ACTION_JOY3_ON_PRESS ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_LEFT_START_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_HORIZONTAL, -1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_SCROLL_RIGHT_START_REPEATER: { + action.Set( (widgetAction_t)WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_HORIZONTAL, 1 ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_DRAG_START: { + action.Set( (widgetAction_t)WIDGET_ACTION_SCROLL_DRAG ); + handled = true; + break; + } + case WIDGET_ACTION_EVENT_DRAG_STOP: { + action.Set( (widgetAction_t)WIDGET_ACTION_EVENT_DRAG_STOP ); + handled = true; + break; + } + } + + if ( handled ) { + targetWidget->HandleAction( action, idWidgetEvent( targetEvent, 0, thisObject, parms ), targetWidget ); + } + + return idSWFScriptVar(); + } + +private: + idMenuWidget * targetWidget; + actionHandler_t type; + widgetEvent_t targetEvent; +}; + +#endif diff --git a/neo/d3xp/menus/MenuWidget_Button.cpp b/neo/d3xp/menus/MenuWidget_Button.cpp new file mode 100644 index 00000000..75cc58e9 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_Button.cpp @@ -0,0 +1,600 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +================================================================================================ +idMenuWidget_Button + +SWF object structure +-------------------- +BUTTON (Frames: up, over, out, down, release, disabled, sel_up, sel_over, sel_out, sel_down, sel_release, selecting, unselecting) + txtOption + txtValue (Text) + optionType (Frames: One per mainMenuOption_t enum) + sliderBar + bar (Frames: 1-100 for percentage filled) + btnLess + btnMore + sliderText + txtVal (Text) + btnLess + btnMore + +Future work: +- Perhaps this should be called "MultiButton", since it merges additional controls with a standard button? +================================================================================================ +*/ + +//--------------------------------- +// Animation State Transitions +// +// Maps animations that should be called when transitioning states: +// +// X-axis = state transitioning FROM +// Y-axis = state transitioning TO +// +// An empty string indicates remain at current animation. +//--------------------------------- +static const char * ANIM_STATE_TRANSITIONS[ idMenuWidget_Button::ANIM_STATE_MAX * idMenuWidget_Button::ANIM_STATE_MAX ] = { + // UP DOWN OVER + "", "release", "out", // UP + "down", "", "down", // DOWN + "over", "over", "", // OVER +}; + +// script name for the control object for a given type of button +static const char * const CONTROL_SPRITE_NAMES[ MAX_MENU_OPTION_TYPES ] = { + NULL, + "sliderBar", + "sliderText", + "sliderText", + NULL, + "sliderText", +}; +compile_time_assert( sizeof( CONTROL_SPRITE_NAMES ) / sizeof( CONTROL_SPRITE_NAMES[ 0 ] ) == MAX_MENU_OPTION_TYPES ); + +/* +======================== +idMenuWidget_Button::Update +======================== +*/ +void idMenuWidget_Button::Update() { + + if ( menuData != NULL && menuData->GetGUI() != NULL ) { + BindSprite( menuData->GetGUI()->GetRootObject() ); + } + + if ( GetSprite() == NULL ) { + return; + } + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject(); + + if ( btnLabel.IsEmpty() ) { + if ( values.Num() > 0 ) { + for ( int val = 0; val < values.Num(); ++val ) { + idSWFScriptObject * const textObject = spriteObject->GetNestedObj( va( "label%d", val ), "txtVal" ); + if ( textObject != NULL ) { + idSWFTextInstance * const text = textObject->GetText(); + text->SetIgnoreColor( ignoreColor ); + text->tooltip = ignoreColor; // ignoreColor does double duty as "allow tooltips" + text->SetText( values[ val ].c_str() ); + text->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } + } else if ( img != NULL ) { + idSWFSpriteInstance * btnImg = spriteObject->GetNestedSprite( "img" ); + if ( btnImg != NULL ) { + btnImg->SetMaterial( img ); + } + + btnImg = spriteObject->GetNestedSprite( "imgTop" ); + if ( btnImg != NULL ) { + btnImg->SetMaterial( img ); + } + } else { + ClearSprite(); + } + } else { + idSWFScriptObject * const textObject = spriteObject->GetNestedObj( "label0", "txtVal" ); + if ( textObject != NULL ) { + idSWFTextInstance * const text = textObject->GetText(); + text->SetIgnoreColor( ignoreColor ); + text->tooltip = ignoreColor; // ignoreColor does double duty as "allow tooltips" + text->SetText( btnLabel.c_str() ); + text->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } + + // events + spriteObject->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + spriteObject->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = spriteObject->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = spriteObject; + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); +} + +/* +======================== +idMenuWidget_Button::ExecuteEvent +======================== +*/ +bool idMenuWidget_Button::ExecuteEvent( const idWidgetEvent & event ) { + bool handled = false; + + // do nothing at all if it's disabled + if ( GetState() != WIDGET_STATE_DISABLED ) { + switch ( event.type ) { + case WIDGET_EVENT_PRESS: { + if ( GetMenuData() != NULL ) { + GetMenuData()->PlaySound( GUI_SOUND_ADVANCE ); + } + AnimateToState( ANIM_STATE_DOWN ); + handled = true; + break; + } + case WIDGET_EVENT_RELEASE: { + AnimateToState( ANIM_STATE_UP ); + GetMenuData()->ClearWidgetActionRepeater(); + handled = true; + break; + } + case WIDGET_EVENT_ROLL_OVER: { + if ( GetMenuData() != NULL ) { + GetMenuData()->PlaySound( GUI_SOUND_ROLL_OVER ); + } + AnimateToState( ANIM_STATE_OVER ); + handled = true; + break; + } + case WIDGET_EVENT_ROLL_OUT: { + AnimateToState( ANIM_STATE_UP ); + GetMenuData()->ClearWidgetActionRepeater(); + handled = true; + break; + } + case WIDGET_EVENT_FOCUS_OFF: { + SetState( WIDGET_STATE_NORMAL ); + handled = true; + break; + } + case WIDGET_EVENT_FOCUS_ON: { + SetState( WIDGET_STATE_SELECTING ); + handled = true; + break; + } + case WIDGET_EVENT_SCROLL_LEFT_RELEASE: { + GetMenuData()->ClearWidgetActionRepeater(); + break; + } + case WIDGET_EVENT_SCROLL_RIGHT_RELEASE: { + GetMenuData()->ClearWidgetActionRepeater(); + break; + } + } + } + + idMenuWidget::ExecuteEvent( event ); + + return handled; +} + +/* +======================== +idMenuWidget_Button::AddValue +======================== +*/ +void idMenuWidget_Button::SetValues( idList< idStr > & list ) { + values.Clear(); + for ( int i = 0; i < list.Num(); ++ i ) { + values.Append( list[ i ] ); + } +} + +/* +======================== +idMenuWidget_Button::GetValue +======================== +*/ +const idStr & idMenuWidget_Button::GetValue( int index ) const { + + return values[ index ]; + +} + +/* +======================== +idMenuWidget_Button::SetupTransitionInfo +======================== +*/ +void idMenuWidget_Button::SetupTransitionInfo( widgetTransition_t & trans, const widgetState_t buttonState, const animState_t sourceAnimState, const animState_t destAnimState ) const { + trans.prefixes.Clear(); + if ( buttonState == WIDGET_STATE_DISABLED ) { + trans.animationName = "disabled"; + } else { + const int animIndex = (int)destAnimState * ANIM_STATE_MAX + (int)sourceAnimState; + trans.animationName = ANIM_STATE_TRANSITIONS[ animIndex ]; + if ( buttonState == WIDGET_STATE_SELECTING ) { + trans.prefixes.Append( "sel_" ); + } + } + trans.prefixes.Append( "" ); +} + +/* +======================== +idMenuWidget_Button::AnimateToState + +Plays an animation from the current state to the target state. +======================== +*/ +void idMenuWidget_Button::AnimateToState( const animState_t targetAnimState, const bool force ) { + if ( !force && targetAnimState == GetAnimState() ) { + return; + } + + if ( GetSprite() != NULL ) { + widgetTransition_t trans; + SetupTransitionInfo( trans, GetState(), GetAnimState(), targetAnimState ); + if ( trans.animationName[0] != '\0' ) { + for ( int i = 0; i < trans.prefixes.Num(); ++i ) { + const char * const frameLabel = va( "%s%s", trans.prefixes[ i ], trans.animationName ); + if ( GetSprite()->FrameExists( frameLabel ) ) { + GetSprite()->PlayFrame( frameLabel ); + Update(); + break; + } + } + } + + idSWFSpriteInstance * const focusSprite = GetSprite()->GetScriptObject()->GetSprite( "focusIndicator" ); + if ( focusSprite != NULL ) { + if ( targetAnimState == ANIM_STATE_OVER ) { + focusSprite->PlayFrame( "show" ); + } else { + focusSprite->PlayFrame( "hide" ); + } + } + } + + SetAnimState( targetAnimState ); +} + +//***************************************************************************************************************** +// CONTROL BUTTON + +/* +======================== +idMenuWidget_ControlButton::Update +======================== +*/ +void idMenuWidget_ControlButton::Update() { + + if ( GetSprite() == NULL ) { + return; + } + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject()->GetNestedObj( "type" ); + if ( spriteObject == NULL ) { + return; + } + idSWFSpriteInstance * type = spriteObject->GetSprite(); + + if ( type == NULL ) { + return; + } + + if ( GetOptionType() != OPTION_BUTTON_FULL_TEXT_SLIDER ) { + type->StopFrame( GetOptionType() + 1 ); + } + + idSWFTextInstance * text = spriteObject->GetNestedText( "label0", "txtVal" ); + if ( text != NULL ) { + text->SetText( btnLabel ); + text->SetStrokeInfo( true, 0.75f, 2.0f ); + } + + if ( CONTROL_SPRITE_NAMES[ GetOptionType() ] != NULL ) { + idSWFSpriteInstance * controlSprite = NULL; + if ( CONTROL_SPRITE_NAMES[ GetOptionType() ] != NULL ) { + controlSprite = type->GetScriptObject()->GetSprite( CONTROL_SPRITE_NAMES[ GetOptionType() ] ); + if ( verify( controlSprite != NULL ) ) { + if ( verify( GetDataSource() != NULL ) ) { + idSWFScriptVar fieldValue = GetDataSource()->GetField( GetDataSourceFieldIndex() ); + if ( GetOptionType() == OPTION_SLIDER_BAR ) { + controlSprite->StopFrame( 1 + fieldValue.ToInteger() ); + } else if ( GetOptionType() == OPTION_SLIDER_TOGGLE ) { + idSWFTextInstance * const txtInfo = controlSprite->GetScriptObject()->GetNestedText( "txtVal" ); + if ( verify( txtInfo != NULL ) ) { + txtInfo->SetText( fieldValue.ToBool() ? "#str_swf_enabled" : "#str_swf_disabled" ); + txtInfo->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } else if ( GetOptionType() == OPTION_SLIDER_TEXT || GetOptionType() == OPTION_BUTTON_FULL_TEXT_SLIDER ) { + idSWFTextInstance * const txtInfo = controlSprite->GetScriptObject()->GetNestedText( "txtVal" ); + if ( verify( txtInfo != NULL ) ) { + txtInfo->SetText( fieldValue.ToString() ); + txtInfo->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } + } + + idSWFScriptObject * const btnLess = GetSprite()->GetScriptObject()->GetObject( "btnLess" ); + idSWFScriptObject * const btnMore = GetSprite()->GetScriptObject()->GetObject( "btnMore" ); + + if ( btnLess != NULL && btnMore != NULL ) { + if ( disabled ) { + btnLess->GetSprite()->SetVisible( false ); + btnMore->GetSprite()->SetVisible( false ); + } else { + btnLess->GetSprite()->SetVisible( true ); + btnMore->GetSprite()->SetVisible( true ); + + btnLess->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT, 0 ) ); + btnLess->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_LEFT_RELEASE, 0 ) ); + + btnMore->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT, 0 ) ); + btnMore->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_SCROLL_RIGHT_RELEASE, 0 ) ); + + btnLess->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + btnLess->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + + btnMore->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + btnMore->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + } + } + } + } + } else { + idSWFScriptObject * const btnLess = GetSprite()->GetScriptObject()->GetObject( "btnLess" ); + idSWFScriptObject * const btnMore = GetSprite()->GetScriptObject()->GetObject( "btnMore" ); + + if ( btnLess != NULL && btnMore != NULL ) { + btnLess->GetSprite()->SetVisible( false ); + btnMore->GetSprite()->SetVisible( false ); + } + } + + // events + GetSprite()->GetScriptObject()->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + GetSprite()->GetScriptObject()->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = GetSprite()->GetScriptObject()->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = GetSprite()->GetScriptObject(); + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + +} + +/* +======================== +idMenuWidget_ControlButton::Update +======================== +*/ +void idMenuWidget_ControlButton::SetupEvents( int delay, int index ) { + AddEventAction( WIDGET_EVENT_SCROLL_LEFT ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_ADJUST_FIELD, -1, delay, index ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_ADJUST_FIELD, 1, delay, index ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_LSTICK ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_ADJUST_FIELD, -1, delay, index ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_LSTICK ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_ADJUST_FIELD, 1, delay, index ); + AddEventAction( WIDGET_EVENT_SCROLL_LEFT_LSTICK_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + AddEventAction( WIDGET_EVENT_SCROLL_RIGHT_LSTICK_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); +} + +//**************************************************************** +// SERVER BUTTON +//**************************************************************** + +/* +======================== +idMenuWidget_ServerButton::Update +======================== +*/ +void idMenuWidget_ServerButton::Update() { + + if ( GetSprite() == NULL ) { + return; + } + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject(); + idSWFTextInstance * const txtName = spriteObject->GetNestedText( "label0", "txtVal" ); + + if ( txtName != NULL ) { + txtName->SetText( serverName ); + txtName->SetStrokeInfo( true, 0.75f, 1.75f ); + } + + // events + spriteObject->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + spriteObject->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = spriteObject->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = spriteObject; + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + +} + +/* +======================== +idMenuWidget_ServerButton::SetButtonInfo +======================== +*/ +void idMenuWidget_ServerButton::SetButtonInfo( idStr name_, idStrId mapName_, idStr modeName_, int index_, int players_, int maxPlayers_, bool joinable_, bool validMap_ ) { + serverName = name_; + index = index_; + players = players_; + maxPlayers = maxPlayers_; + joinable = joinable_; + validMap = validMap_; + mapName = mapName_; + modeName = modeName_; + + idStr desc; + if ( index >= 0 ) { + idStr playerVal = va( "%s %d/%d", idLocalization::GetString( "#str_02396" ), players, maxPlayers ); + idStr lobbyMapName = va( "%s %s", idLocalization::GetString( "#str_02045" ), mapName.GetLocalizedString() ); + idStr lobbyMode; + if ( !modeName.IsEmpty() ) { + lobbyMode = va( "%s %s", idLocalization::GetString( "#str_02044" ), modeName.c_str() ); + } + + desc = va( "%s %s %s", playerVal.c_str(), lobbyMapName.c_str(), lobbyMode.c_str() ); + } + SetDescription( desc ); + +} + +//**************************************************************** +// LOBBY BUTTON +//**************************************************************** + +/* +======================== +idMenuWidget_LobbyButton::Update +======================== +*/ +void idMenuWidget_LobbyButton::Update() { + + if ( GetSprite() == NULL ) { + return; + } + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject(); + idSWFTextInstance * const txtName = spriteObject->GetNestedText( "itemName", "txtVal" ); + idSWFSpriteInstance * talkIcon = spriteObject->GetNestedSprite( "chaticon" ); + + if ( txtName != NULL ) { + txtName->SetText( name ); + } + + if ( talkIcon != NULL ) { + talkIcon->StopFrame( voiceState + 1 ); + talkIcon->GetScriptObject()->Set( "onPress", new (TAG_SWF) WrapWidgetSWFEvent( this, WIDGET_EVENT_COMMAND, WIDGET_ACTION_MUTE_PLAYER ) ); + } + + // events + spriteObject->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + spriteObject->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = spriteObject->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = spriteObject; + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); +} + +/* +======================== +idMenuWidget_LobbyButton::SetButtonInfo +======================== +*/ +void idMenuWidget_LobbyButton::SetButtonInfo( idStr name_, voiceStateDisplay_t voiceState_ ) { + name = name_; + voiceState = voiceState_; +} + +//**************************************************************** +// SCOREBOARD BUTTON +//**************************************************************** + +/* +======================== +idMenuWidget_ScoreboardButton::Update +======================== +*/ +void idMenuWidget_ScoreboardButton::Update() { + + if ( GetSprite() == NULL ) { + return; + } + + if ( index == -1 ) { + GetSprite()->SetVisible( false ); + return; + } + + GetSprite()->SetVisible( true ); + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject(); + for ( int val = 0; val < values.Num(); ++val ) { + idSWFScriptObject * const textObject = spriteObject->GetNestedObj( va( "label%d", val ), "txtVal" ); + if ( textObject != NULL ) { + idSWFTextInstance * const text = textObject->GetText(); + text->SetIgnoreColor( ignoreColor ); + text->tooltip = ignoreColor; // ignoreColor does double duty as "allow tooltips" + text->SetText( values[ val ].c_str() ); + text->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } + + idSWFSpriteInstance * talkIcon = spriteObject->GetNestedSprite( "chaticon" ); + if ( talkIcon != NULL ) { + talkIcon->StopFrame( voiceState + 1 ); + talkIcon->GetScriptObject()->Set( "onPress", new (TAG_SWF) WrapWidgetSWFEvent( this, WIDGET_EVENT_COMMAND, WIDGET_ACTION_MUTE_PLAYER ) ); + } + + // events + spriteObject->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + spriteObject->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = spriteObject->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = spriteObject; + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); +} + +/* +======================== +idMenuWidget_ScoreboardButton::SetButtonInfo +======================== +*/ +void idMenuWidget_ScoreboardButton::SetButtonInfo( int index_, idList< idStr > & list, voiceStateDisplay_t voiceState_ ) { + index = index_; + voiceState = voiceState_; + SetValues( list ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_Carousel.cpp b/neo/d3xp/menus/MenuWidget_Carousel.cpp new file mode 100644 index 00000000..b0462922 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_Carousel.cpp @@ -0,0 +1,265 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +void idMenuWidget_Carousel::Initialize( idMenuHandler * data ) { + idMenuWidget::Initialize( data ); + + class idCarouselRefresh : public idSWFScriptFunction_RefCounted { + public: + idCarouselRefresh( idMenuWidget_Carousel * _widget ) : + widget( _widget ) { + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + if ( widget == NULL ) { + return idSWFScriptVar(); + } + + if ( widget->GetMoveDiff() != 0 ) { + int diff = widget->GetMoveDiff(); + diff--; + + if ( widget->GetScrollLeft() ) { + widget->SetViewIndex( widget->GetViewIndex() - 1 ); + } else { + widget->SetViewIndex( widget->GetViewIndex() + 1 ); + } + + if ( diff > 0 ) { + if ( widget->GetScrollLeft() ) { + widget->MoveToIndex( ( widget->GetNumVisibleOptions() / 2 ) + diff ); + } else { + widget->MoveToIndex( diff ); + } + } else { + widget->SetMoveDiff( 0 ); + } + } + + widget->Update(); + return idSWFScriptVar(); + } + private: + idMenuWidget_Carousel * widget; + }; + + if ( GetSWFObject() != NULL ) { + GetSWFObject()->SetGlobal( "refreshCarousel", new idCarouselRefresh( this ) ); + } +} + +/* +======================== +idMenuWidget_Carousel::Update +======================== +*/ +void idMenuWidget_Carousel::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + int midPoint = GetNumVisibleOptions() / 2 + 1; + for ( int optionIndex = 0; optionIndex < GetNumVisibleOptions(); ++optionIndex ) { + + int listIndex = viewIndex + optionIndex; + if ( optionIndex >= midPoint ) { + listIndex = viewIndex - ( optionIndex - ( midPoint - 1 ) ); + } + + idMenuWidget & child = GetChildByIndex( optionIndex ); + child.SetSpritePath( GetSpritePath(), va( "item%d", optionIndex ) ); + if ( child.BindSprite( root ) ) { + if ( listIndex < 0 || listIndex >= GetTotalNumberOfOptions() ) { + child.SetState( WIDGET_STATE_HIDDEN ); + } else { + idMenuWidget_Button * const button = dynamic_cast< idMenuWidget_Button * >( &child ); + button->SetImg( imgList[ listIndex ] ); + child.Update(); + if ( optionIndex == focusIndex ) { + child.SetState( WIDGET_STATE_SELECTING ); + } else { + child.SetState( WIDGET_STATE_NORMAL ); + } + } + } + } +} + +/* +======================== +idMenuWidget_Carousel::SetListImages +======================== +*/ +void idMenuWidget_Carousel::SetListImages( idList & list ) { + imgList.Clear(); + for ( int i = 0; i < list.Num(); ++i ) { + imgList.Append( list[ i ] ); + } +} + +/* +======================== +idMenuWidget_Carousel::Update +======================== +*/ +bool idMenuWidget_Carousel::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuWidget_Carousel::MoveToFirstItem +======================== +*/ +void idMenuWidget_Carousel::MoveToFirstItem( bool instant ) { + if ( instant ) { + moveDiff = 0; + viewIndex = 0; + moveToIndex = 0; + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + GetSprite()->StopFrame( 1 ); + } + + Update(); + } +} + +/* +======================== +idMenuWidget_Carousel::MoveToLastItem +======================== +*/ +void idMenuWidget_Carousel::MoveToLastItem( bool instant ) { + if ( instant ) { + moveDiff = 0; + viewIndex = GetTotalNumberOfOptions() - 1; + moveToIndex = GetTotalNumberOfOptions() - 1; + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + GetSprite()->StopFrame( 1 ); + } + Update(); + } +} + +/* +======================== +idMenuWidget_Carousel::Update +======================== +*/ +void idMenuWidget_Carousel::MoveToIndex( int index, bool instant ) { + + idLib::Printf( "moveToIndex %i\n", index ); + + if ( instant ) { + viewIndex = index; + moveDiff = 0; + moveToIndex = viewIndex; + + idLib::Printf( "moveDiff = %i\n", moveDiff ); + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( BindSprite( root ) ) { + GetSprite()->StopFrame( 1 ); + } + + Update(); + return; + } + + if ( index == 0 ) { + fastScroll = false; + moveDiff = 0; + + idLib::Printf( "moveDiff = %i\n", moveDiff ); + + viewIndex = moveToIndex; + return; + } + + int midPoint = GetNumVisibleOptions() / 2; + scrollLeft = false; + if ( index > midPoint ) { + moveDiff = index - midPoint; + scrollLeft = true; + } else { + moveDiff = index; + } + + idLib::Printf( "moveDiff = %i\n", moveDiff ); + + if ( scrollLeft ) { + moveToIndex = viewIndex - moveDiff; + if ( moveToIndex < 0 ) { + moveToIndex = 0; + int diff = 0 - moveToIndex; + moveDiff -= diff; + } + } else { + moveToIndex = viewIndex + moveDiff; + if ( moveToIndex >= GetTotalNumberOfOptions() ) { + moveDiff = GetTotalNumberOfOptions() - GetViewIndex() - 1; + moveToIndex = GetTotalNumberOfOptions() - 1; + } + } + + idLib::Printf( "moveDiff = %i\n", moveDiff ); + + if ( moveDiff != 0 ) { + if ( moveDiff > 1 ) { + if ( scrollLeft ) { + GetSprite()->PlayFrame( "leftFast" ); + } else { + GetSprite()->PlayFrame( "rightFast" ); + } + } else { + if ( scrollLeft ) { + GetSprite()->PlayFrame( "left" ); + } else { + GetSprite()->PlayFrame( "right" ); + } + } + } + + idLib::Printf( "moveDiff = %i\n", moveDiff ); +} + diff --git a/neo/d3xp/menus/MenuWidget_CommandBar.cpp b/neo/d3xp/menus/MenuWidget_CommandBar.cpp new file mode 100644 index 00000000..adab9f60 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_CommandBar.cpp @@ -0,0 +1,198 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +================================================================================================ +idMenuWidget_CommandBar + +Provides a paged view of this widgets children. Each child is expected to take on the following +naming scheme. Children outside of the given window size (NumVisibleOptions) are not rendered, +and will affect which type of arrow indicators are shown. + +This transparently supports the "UseCircleForAccept" behavior that we need for Japanese PS3 SKU. + +SWF object structure +-------------------- +COMMANDBAR + joy# + img (Frames: platform) + txt_info (Text) +================================================================================================ +*/ + +static const char * const BUTTON_NAMES[] = { + "joy1", + "joy2", + "joy3", + "joy4", + "joy10", + "tab" +}; +compile_time_assert( sizeof( BUTTON_NAMES ) / sizeof( BUTTON_NAMES[ 0 ] ) == idMenuWidget_CommandBar::MAX_BUTTONS ); + +/* +======================== +idMenuWidget_CommandBar::ClearAllButtons +======================== +*/ +void idMenuWidget_CommandBar::ClearAllButtons() { + for ( int index = 0; index < MAX_BUTTONS; ++index ) { + buttons[index].label.Clear(); + buttons[index].action.Set( WIDGET_ACTION_NONE ); + } +} + +/* +======================== +idMenuWidget_CommandBar::Update +======================== +*/ +void idMenuWidget_CommandBar::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + const int BASE_PADDING = 35; + const int PER_BUTTON_PADDING = 65; + const int ALIGNMENT_SCALE = ( GetAlignment() == LEFT ) ? 1 : -1; + + int xPos = ALIGNMENT_SCALE * BASE_PADDING; + + // Setup the button order. + idStaticList< button_t, MAX_BUTTONS > buttonOrder; + for ( int i = 0; i < buttonOrder.Max(); ++i ) { + buttonOrder.Append( static_cast< button_t >( i ) ); + } + + // NOTE: Special consideration is done for JPN PS3 where the standard accept button is + // swapped with the standard back button. i.e. In US: X = Accept, O = Back, but in JPN + // X = Back, O = Accept. + if ( GetSWFObject()->UseCircleForAccept() ) { + buttonOrder[ BUTTON_JOY2 ] = BUTTON_JOY1; + buttonOrder[ BUTTON_JOY1 ] = BUTTON_JOY2; + } + + // FIXME: handle animating in of the button bar? + GetSprite()->SetVisible( true ); + + idStr shortcutName; + for ( int i = 0; i < buttonOrder.Num(); ++i ) { + const char * const buttonName = BUTTON_NAMES[ buttonOrder[ i ] ]; + + idSWFSpriteInstance * const buttonSprite = GetSprite()->GetScriptObject()->GetSprite( buttonName ); + if ( buttonSprite == NULL ) { + continue; + } + idSWFTextInstance * const buttonText = buttonSprite->GetScriptObject()->GetText( "txt_info" ); + if ( buttonText == NULL ) { + continue; + } + idSWFSpriteInstance * const imageSprite = buttonSprite->GetScriptObject()->GetSprite( "img" ); + if ( imageSprite == NULL ) { + continue; + } + + if ( buttons[ i ].action.GetType() != WIDGET_ACTION_NONE ) { + idSWFScriptObject * const shortcutKeys = GetSWFObject()->GetGlobal( "shortcutKeys" ).GetObject(); + if ( verify( shortcutKeys != NULL ) ) { + buttonSprite->GetScriptObject()->Set( "onPress", new WrapWidgetSWFEvent( this, WIDGET_EVENT_COMMAND, i ) ); + + // bind the main action - need to use all caps here because shortcuts are stored that way + shortcutName = buttonName; + shortcutName.ToUpper(); + shortcutKeys->Set( shortcutName, buttonSprite->GetScriptObject() ); + + // Some other keys have additional bindings. Remember that the button here is + // actually the virtual button, and the physical button could be swapped based + // on the UseCircleForAccept business on JPN PS3. + switch ( i ) { + case BUTTON_JOY1: { + shortcutKeys->Set( "ENTER", buttonSprite->GetScriptObject() ); + break; + } + case BUTTON_JOY2: { + shortcutKeys->Set( "ESCAPE", buttonSprite->GetScriptObject() ); + shortcutKeys->Set( "BACKSPACE", buttonSprite->GetScriptObject() ); + break; + } + case BUTTON_TAB: { + shortcutKeys->Set( "K_TAB", buttonSprite->GetScriptObject() ); + break; + } + } + } + + if ( buttons[ i ].label.IsEmpty() ) { + buttonSprite->SetVisible( false ); + } else { + imageSprite->SetVisible( true ); + imageSprite->StopFrame( menuData->GetPlatform() + 1 ); + buttonSprite->SetVisible( true ); + buttonSprite->SetXPos( xPos ); + buttonText->SetText( buttons[ i ].label ); + xPos += ALIGNMENT_SCALE * ( buttonText->GetTextLength() + PER_BUTTON_PADDING ); + } + } else { + buttonSprite->SetVisible( false ); + idSWFScriptObject * const shortcutKeys = GetSWFObject()->GetGlobal( "shortcutKeys" ).GetObject(); + if ( verify( shortcutKeys != NULL ) ) { + buttonSprite->GetScriptObject()->Set( "onPress", NULL ); + // bind the main action - need to use all caps here because shortcuts are stored that way + shortcutName = buttonName; + shortcutName.ToUpper(); + shortcutKeys->Set( shortcutName, buttonSprite->GetScriptObject() ); + } + } + } +} + +/* +======================== +idMenuWidget_CommandBar::ReceiveEvent +======================== +*/ +bool idMenuWidget_CommandBar::ExecuteEvent( const idWidgetEvent & event ) { + if ( event.type == WIDGET_EVENT_COMMAND ) { + if ( verify( event.arg >= 0 && event.arg < buttons.Num() ) ) { + HandleAction( buttons[ event.arg ].action, event, this ); + } + return true; + } else { + return idMenuWidget::ExecuteEvent( event ); + } +} diff --git a/neo/d3xp/menus/MenuWidget_DevList.cpp b/neo/d3xp/menus/MenuWidget_DevList.cpp new file mode 100644 index 00000000..406afcff --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_DevList.cpp @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../precompiled.h" +#include "../MainMenuLocal.h" + +/* +================================================================================================ +idMenuWidget_DevList + +Extends the standard list widget to support the developer menu. +================================================================================================ +*/ + +namespace { + /* + ================================================ + DevList_NavigateForward + ================================================ + */ + class DevList_NavigateForward : public idSWFScriptFunction_RefCounted { + public: + DevList_NavigateForward( idMenuWidget_DevList * devList_, const int optionIndex_ ) : + devList( devList_ ), + optionIndex( optionIndex_ ) { + + } + + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + devList->NavigateForward( optionIndex ); + return idSWFScriptVar(); + } + + private: + idMenuWidget_DevList * devList; + int optionIndex; + }; +} + +/* +======================== +idMenuWidget_DevList::Initialize +======================== +*/ +void idMenuWidget_DevList::Initialize() { + AddEventAction( WIDGET_EVENT_SCROLL_DOWN ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, 1 ); + AddEventAction( WIDGET_EVENT_SCROLL_UP ).Set( WIDGET_ACTION_START_REPEATER, WIDGET_ACTION_SCROLL_VERTICAL, -1 ); + AddEventAction( WIDGET_EVENT_SCROLL_DOWN_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + AddEventAction( WIDGET_EVENT_SCROLL_UP_RELEASE ).Set( WIDGET_ACTION_STOP_REPEATER ); + + int optionIndex = 0; + while ( GetChildren().Num() < GetNumVisibleOptions() ) { + idMenuWidget_Button * const buttonWidget = new ( TAG_MENU ) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( new ( TAG_SWF ) DevList_NavigateForward( this, optionIndex++ ) ); + AddChild( buttonWidget ); + } +} + +/* +======================== +idMenuWidget_DevList::GetTotalNumberOfOptions +======================== +*/ +int idMenuWidget_DevList::GetTotalNumberOfOptions() const { + if ( devMenuList == NULL ) { + return 0; + } + + return devMenuList->devMenuList.Num(); +} + +/* +======================== +idMenuWidget_DevList::GoToFirstMenu +======================== +*/ +void idMenuWidget_DevList::GoToFirstMenu() { + devMapListInfos.Clear(); + indexInfo_t & info = devMapListInfos.Alloc(); + info.name = "devmenuoption/main"; + + devMenuList = NULL; + + RecalculateDevMenu(); +} + +/* +======================== +idMenuWidget_DevList::NavigateForward +======================== +*/ +void idMenuWidget_DevList::NavigateForward( const int optionIndex ) { + if ( devMenuList == NULL ) { + return; + } + + const int focusedIndex = GetViewOffset() + optionIndex; + + const idDeclDevMenuList::idDevMenuOption & devOption = devMenuList->devMenuList[ focusedIndex ]; + if ( ( devOption.devMenuDisplayName.Length() == 0 ) || ( devOption.devMenuDisplayName.Cmp( "..." ) == 0 ) ) { + return; + } + + if ( devOption.devMenuSubList != NULL ) { + indexInfo_t & indexes = devMapListInfos.Alloc(); + indexes.name = devOption.devMenuSubList->GetName(); + indexes.focusIndex = GetFocusIndex(); + indexes.viewIndex = GetViewIndex(); + indexes.viewOffset = GetViewOffset(); + + RecalculateDevMenu(); + + SetViewIndex( 0 ); + SetViewOffset( 0 ); + + Update(); + + // NOTE: This must be done after the Update() because it MAY change the sprites that + // children refer to + GetChildByIndex( 0 ).SetState( WIDGET_STATE_SELECTED ); + ForceFocusIndex( 0 ); + SetFocusIndex( 0 ); + + gameLocal->GetMainMenu()->ClearWidgetActionRepeater(); + } else { + cmdSystem->AppendCommandText( va( "loadDevMenuOption %s %d\n", devMapListInfos[ devMapListInfos.Num() - 1 ].name, focusedIndex ) ); + } +} + +/* +======================== +idMenuWidget_DevList::NavigateBack +======================== +*/ +void idMenuWidget_DevList::NavigateBack() { + assert( devMapListInfos.Num() != 0 ); + if ( devMapListInfos.Num() == 1 ) { + // Important that this goes through as a DIRECT event, since more than likely the list + // widget will have the parent's focus, so a standard ReceiveEvent() here would turn + // into an infinite recursion. + idWidgetEvent event( WIDGET_EVENT_BACK, 0, NULL, idSWFParmList() ); + + idWidgetAction action; + action.Set( WIDGET_ACTION_GO_BACK, MENU_ROOT ); + HandleAction( action, event ); + + return; + } + + // NOTE: we need a copy here, since it's about to be removed from the list + const indexInfo_t indexes = devMapListInfos[ devMapListInfos.Num() - 1 ]; + assert( indexes.focusIndex < GetChildren().Num() ); + assert( ( indexes.viewIndex - indexes.viewOffset ) < GetNumVisibleOptions() ); + devMapListInfos.RemoveIndex( devMapListInfos.Num() - 1 ); + + RecalculateDevMenu(); + + SetViewIndex( indexes.viewIndex ); + SetViewOffset( indexes.viewOffset ); + + Update(); + + // NOTE: This must be done AFTER Update() because so that it is sure to refer to the proper sprite + GetChildByIndex( indexes.focusIndex ).SetState( WIDGET_STATE_SELECTED ); + ForceFocusIndex( indexes.focusIndex ); + SetFocusIndex( indexes.focusIndex ); + + gameLocal->GetMainMenu()->ClearWidgetActionRepeater(); +} + + +/* +======================== +idMenuWidget_DevList::RecalculateDevMenu +======================== +*/ +void idMenuWidget_DevList::RecalculateDevMenu() { + if ( devMapListInfos.Num() > 0 ) { + const idDeclDevMenuList * const devMenuListDecl = idDeclDevMenuList::resourceList.Load( devMapListInfos[ devMapListInfos.Num() - 1 ].name, false ); + if ( devMenuListDecl != NULL ) { + devMenuList = devMenuListDecl; + } + } + + idSWFScriptObject & root = gameLocal->GetMainMenu()->GetSWF()->GetRootObject(); + for ( int i = 0; i < GetChildren().Num(); ++i ) { + idMenuWidget & child = GetChildByIndex( i ); + child.SetSpritePath( GetSpritePath(), va( "option%d", i ) ); + if ( child.BindSprite( root ) ) { + child.SetState( WIDGET_STATE_NORMAL ); + child.GetSprite()->StopFrame( 1 ); + } + } +} diff --git a/neo/d3xp/menus/MenuWidget_DynamicList.cpp b/neo/d3xp/menus/MenuWidget_DynamicList.cpp new file mode 100644 index 00000000..91b6a84a --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_DynamicList.cpp @@ -0,0 +1,236 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_DynamicList::Initialize +======================== +*/ +void idMenuWidget_DynamicList::Initialize( idMenuHandler * data ) { + idMenuWidget::Initialize( data ); +} + +/* +======================== +idMenuWidget_DynamicList::Update +======================== +*/ +void idMenuWidget_DynamicList::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + for ( int optionIndex = 0; optionIndex < GetNumVisibleOptions(); ++optionIndex ) { + + if ( optionIndex >= children.Num() ) { + idSWFSpriteInstance * item = GetSprite()->GetScriptObject()->GetNestedSprite( va( "item%d", optionIndex ) ); + if ( item != NULL ) { + item->SetVisible( false ); + continue; + } + } + + idMenuWidget & child = GetChildByIndex( optionIndex ); + const int childIndex = GetViewOffset() + optionIndex; + bool shown = false; + child.SetSpritePath( GetSpritePath(), va( "item%d", optionIndex ) ); + if ( child.BindSprite( root ) ) { + + if ( optionIndex >= GetTotalNumberOfOptions() ) { + child.ClearSprite(); + continue; + } else { + //const int controlIndex = GetNumVisibleOptions() - Min( GetNumVisibleOptions(), GetTotalNumberOfOptions() ) + optionIndex; + shown = PrepareListElement( child, childIndex ); + child.Update(); + } + + if ( !shown ) { + child.SetState( WIDGET_STATE_HIDDEN ); + } else { + if ( optionIndex == focusIndex ) { + child.SetState( WIDGET_STATE_SELECTING ); + } else { + child.SetState( WIDGET_STATE_NORMAL ); + } + } + } + } + + idSWFSpriteInstance * const upSprite = GetSprite()->GetScriptObject()->GetSprite( "upIndicator" ); + if ( upSprite != NULL ) { + upSprite->SetVisible( GetViewOffset() > 0 ); + } + + idSWFSpriteInstance * const downSprite = GetSprite()->GetScriptObject()->GetSprite( "downIndicator" ); + if ( downSprite != NULL ) { + downSprite->SetVisible( GetViewOffset() + GetNumVisibleOptions() < GetTotalNumberOfOptions() ); + } + +} + +/* +======================== +idMenuWidget_DynamicList::GetTotalNumberOfOptions +======================== +*/ +int idMenuWidget_DynamicList::GetTotalNumberOfOptions() const { + + if ( controlList ) { + return GetChildren().Num(); + } + + return listItemInfo.Num(); +} + +/* +======================== +idMenuWidget_DynamicList::PrepareListElement +======================== +*/ +bool idMenuWidget_DynamicList::PrepareListElement( idMenuWidget & widget, const int childIndex ) { + + idMenuWidget_ScoreboardButton * const sbButton = dynamic_cast< idMenuWidget_ScoreboardButton * >( &widget ); + if ( sbButton != NULL ) { + return true; + } + + if ( listItemInfo.Num() == 0 ) { + return true; + } + + if ( childIndex > listItemInfo.Num() ) { + return false; + } + + idMenuWidget_Button * const button = dynamic_cast< idMenuWidget_Button * >( &widget ); + if ( button != NULL ) { + button->SetIgnoreColor( ignoreColor ); + button->SetValues( listItemInfo[ childIndex ] ); + if ( listItemInfo[ childIndex ].Num() > 0 ) { + return true; + } + } + + return false; +} + +/* +======================== +idMenuWidget_DynamicList::SetListData +======================== +*/ +void idMenuWidget_DynamicList::SetListData( idList< idList< idStr, TAG_IDLIB_LIST_MENU >, TAG_IDLIB_LIST_MENU > & list ) { + listItemInfo.Clear(); + for ( int i = 0; i < list.Num(); ++i ) { + idList< idStr > values; + for ( int j = 0; j < list[i].Num(); ++j ) { + values.Append( list[i][j] ); + } + listItemInfo.Append( values ); + } +} + +/* +======================== +idMenuWidget_DynamicList::Recalculate +======================== +*/ +void idMenuWidget_DynamicList::Recalculate() { + + idSWF * swf = GetSWFObject(); + + if ( swf == NULL ) { + return; + } + + idSWFScriptObject & root = swf->GetRootObject(); + for ( int i = 0; i < GetChildren().Num(); ++i ) { + idMenuWidget & child = GetChildByIndex( i ); + child.SetSpritePath( GetSpritePath(), "info", "list", va( "item%d", i ) ); + if ( child.BindSprite( root ) ) { + child.SetState( WIDGET_STATE_NORMAL ); + child.GetSprite()->StopFrame( 1 ); + } + } +} + +/* +======================== +idMenuWidget_ScoreboardList::Update +======================== +*/ +void idMenuWidget_ScoreboardList::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + for ( int optionIndex = 0; optionIndex < GetNumVisibleOptions(); ++optionIndex ) { + idMenuWidget & child = GetChildByIndex( optionIndex ); + const int childIndex = GetViewOffset() + optionIndex; + bool shown = false; + child.SetSpritePath( GetSpritePath(), va( "item%d", optionIndex ) ); + if ( child.BindSprite( root ) ) { + shown = PrepareListElement( child, childIndex ); + + if ( optionIndex == focusIndex && child.GetState() != WIDGET_STATE_SELECTED && child.GetState() != WIDGET_STATE_SELECTING ) { + child.SetState( WIDGET_STATE_SELECTING ); + } else if ( optionIndex != focusIndex && child.GetState() != WIDGET_STATE_NORMAL ) { + child.SetState( WIDGET_STATE_NORMAL ); + } + + child.Update(); + } + } +} + +/* +======================== +idMenuWidget_ScoreboardList::GetTotalNumberOfOptions +======================== +*/ +int idMenuWidget_ScoreboardList::GetTotalNumberOfOptions() const { + return GetChildren().Num(); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_Help.cpp b/neo/d3xp/menus/MenuWidget_Help.cpp new file mode 100644 index 00000000..be031df3 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_Help.cpp @@ -0,0 +1,139 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +================================================================================================ +idMenuWidget_Help + +Shows a help tooltip message based on observed events. It's expected that the widgets being +observed are all buttons, and therefore have a GetDescription() call to get the help message. + +SWF object structure +-------------------- +HELPTOOLTIP (Frames: shown, shown, hide, hidden) + txtOption + txtValue (Text) +Note: Frame 1 should, effectively, be a "hidden" state. + +Future work: +- Make this act more like a help tooltip when on PC? +================================================================================================ +*/ + +/* +======================== +idMenuWidget_Help::Update +======================== +*/ +void idMenuWidget_Help::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + const idStr & msg = ( lastHoveredMessage.Length() > 0 ) ? lastHoveredMessage : lastFocusedMessage; + if ( msg.Length() > 0 && !hideMessage ) { + // try to show it if... + // - we are on the first frame + // - we aren't still animating while being between the "show" and "shown" frames + // + if ( GetSprite()->GetCurrentFrame() != GetSprite()->FindFrame( "shown" ) + && ( GetSprite()->GetCurrentFrame() == 1 || !( GetSprite()->IsPlaying() && GetSprite()->IsBetweenFrames( "show", "shown" ) ) ) ) { + GetSprite()->PlayFrame( "show" ); + } + + idSWFScriptObject * const textObject = GetSprite()->GetScriptObject()->GetNestedObj( "txtOption", "txtValue" ); + if ( textObject != NULL ) { + idSWFTextInstance * const text = textObject->GetText(); + text->SetText( msg ); + text->SetStrokeInfo( true, 0.75f, 2.0f ); + } + } else { + // try to hide it + if ( GetSprite()->GetCurrentFrame() != 1 + && GetSprite()->GetCurrentFrame() != GetSprite()->FindFrame( "hidden" ) + && !GetSprite()->IsBetweenFrames( "hide", "hidden" ) ) { + GetSprite()->PlayFrame( "hide" ); + } + } +} + +/* +======================== +idMenuWidget_Help::ObserveEvent +======================== +*/ +void idMenuWidget_Help::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + hideMessage = false; + lastFocusedMessage = button->GetDescription(); + lastHoveredMessage.Clear(); + Update(); + break; + } + case WIDGET_EVENT_FOCUS_OFF: { + // Don't do anything when losing focus. Focus updates come in pairs, so we can + // skip doing anything on the "lost focus" event, and instead do updates on the + // "got focus" event. + break; + } + case WIDGET_EVENT_ROLL_OVER: { + idStr desc = button->GetDescription(); + if ( desc.IsEmpty() ) { + hideMessage = true; + } else { + hideMessage = false; + lastHoveredMessage = button->GetDescription(); + } + Update(); + break; + } + case WIDGET_EVENT_ROLL_OUT: { + hideMessage = false; + lastHoveredMessage.Clear(); + Update(); + break; + } + } +} + diff --git a/neo/d3xp/menus/MenuWidget_InfoBox.cpp b/neo/d3xp/menus/MenuWidget_InfoBox.cpp new file mode 100644 index 00000000..4a91b06a --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_InfoBox.cpp @@ -0,0 +1,208 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_InfoBox::Update +======================== +*/ +void idMenuWidget_InfoBox::Initialize( idMenuHandler * data) { + idMenuWidget::Initialize( data ); +} + +/* +======================== +idMenuWidget_InfoBox::Update +======================== +*/ +void idMenuWidget_InfoBox::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + idSWFTextInstance * txtHeading = GetSprite()->GetScriptObject()->GetNestedText( "info", "heading", "txtVal" ); + idSWFTextInstance * txtBody = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBody" ); + + if ( txtHeading != NULL ) { + txtHeading->SetText( heading ); + } + + if ( txtBody != NULL ) { + txtBody->SetText( info ); + } + + if ( scrollbar != NULL && txtBody != NULL ) { + txtBody->CalcMaxScroll(); + scrollbar->Update(); + } + + idSWFScriptObject * info = GetSprite()->GetScriptObject()->GetNestedObj( "info" ); + if ( info != NULL ) { + info->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + info->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + } + +} + +/* +======================== +idMenuWidget_InfoBox::ObserveEvent +======================== +*/ +void idMenuWidget_InfoBox::ResetInfoScroll() { + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ){ + return; + } + + idSWFTextInstance * txtBody = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBody" ); + if ( txtBody != NULL ) { + txtBody->scroll = 0; + } + + if ( scrollbar != NULL ) { + scrollbar->Update(); + } +} + +/* +======================== +idMenuWidget_InfoBox::Scroll +======================== +*/ +void idMenuWidget_InfoBox::Scroll( int d ) { + + idSWFTextInstance * txtBody = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBody" ); + + if ( txtBody != NULL && txtBody->scroll + d >= 0 && txtBody->scroll + d <= txtBody->maxscroll ) { + txtBody->scroll += d; + } + + if ( scrollbar != NULL ) { + scrollbar->Update(); + } + +} + +/* +======================== +idMenuWidget_InfoBox::GetScroll +======================== +*/ +int idMenuWidget_InfoBox::GetScroll() { + + idSWFTextInstance * txtBody = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBody" ); + if ( txtBody != NULL ) { + return txtBody->scroll; + } + + return 0; +} + +/* +======================== +idMenuWidget_InfoBox::GetMaxScroll +======================== +*/ +int idMenuWidget_InfoBox::GetMaxScroll() { + + idSWFTextInstance * txtBody = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBody" ); + if ( txtBody != NULL ) { + return txtBody->maxscroll; + } + + return 0; +} + +/* +======================== +idMenuWidget_InfoBox::SetScroll +======================== +*/ +void idMenuWidget_InfoBox::SetScroll( int scroll ) { + + idSWFTextInstance * txtBody = GetSprite()->GetScriptObject()->GetNestedText( "info", "txtBody" ); + + if ( txtBody != NULL && scroll <= txtBody->maxscroll ) { + txtBody->scroll = scroll; + } + +} + +/* +======================== +idMenuWidget_InfoBox::SetScrollbar +======================== +*/ +void idMenuWidget_InfoBox::SetScrollbar( idMenuWidget_ScrollBar * bar ) { + scrollbar = bar; +} + +/* +======================== +idMenuWidget_InfoBox::ObserveEvent +======================== +*/ +bool idMenuWidget_InfoBox::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + const idSWFParmList & parms = action.GetParms(); + + if ( action.GetType() == WIDGET_ACTION_SCROLL_VERTICAL ) { + const scrollType_t scrollType = static_cast< scrollType_t >( event.arg ); + if ( scrollType == SCROLL_SINGLE ) { + Scroll( parms[ 0 ].ToInteger() ); + } + return true; + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuWidget_InfoBox::ObserveEvent +======================== +*/ +void idMenuWidget_InfoBox::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + ResetInfoScroll(); + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_ItemAssignment.cpp b/neo/d3xp/menus/MenuWidget_ItemAssignment.cpp new file mode 100644 index 00000000..a2eb71b5 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_ItemAssignment.cpp @@ -0,0 +1,104 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +void idMenuWidget_ItemAssignment::SetIcon( int index, const idMaterial * icon ) { + + if ( index < 0 || index >= NUM_QUICK_SLOTS ) { + return; + } + + images[ index ] = icon; +} + +void idMenuWidget_ItemAssignment::FindFreeSpot() { + slotIndex = 0; + for ( int i = 0; i < NUM_QUICK_SLOTS; ++i ) { + if ( images[ i ] == NULL ) { + slotIndex = i; + break; + } + } +} + +/* +======================== +idMenuWidget_ItemAssignment::Update +======================== +*/ +void idMenuWidget_ItemAssignment::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + idSWFSpriteInstance * dpad = GetSprite()->GetScriptObject()->GetNestedSprite( "dpad" ); + + if ( dpad != NULL ) { + dpad->StopFrame( slotIndex + 2 ); + } + + for ( int i = 0; i < NUM_QUICK_SLOTS; ++i ) { + idSWFSpriteInstance * item = GetSprite()->GetScriptObject()->GetNestedSprite( va( "item%d", i ) ); + if ( item != NULL ) { + if ( i == slotIndex ) { + item->StopFrame( 2 ); + } else { + item->StopFrame( 1 ); + } + } + + idSWFSpriteInstance * itemIcon = GetSprite()->GetScriptObject()->GetNestedSprite( va( "item%d", i ), "img" ); + if ( itemIcon != NULL ) { + if ( images[ i ] != NULL ) { + itemIcon->SetVisible( true ); + itemIcon->SetMaterial( images[ i ] ); + } else { + itemIcon->SetVisible( false ); + } + } + + itemIcon = GetSprite()->GetScriptObject()->GetNestedSprite( va( "item%d", i ), "imgTop" ); + if ( itemIcon != NULL ) { + if ( images[ i ] != NULL ) { + itemIcon->SetVisible( true ); + itemIcon->SetMaterial( images[ i ] ); + } else { + itemIcon->SetVisible( false ); + } + } + } +} diff --git a/neo/d3xp/menus/MenuWidget_List.cpp b/neo/d3xp/menus/MenuWidget_List.cpp new file mode 100644 index 00000000..a9a36970 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_List.cpp @@ -0,0 +1,408 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +================================================================================================ +idMenuWidget_List + +Provides a paged view of this widgets children. Each child is expected to take on the following +naming scheme. Children outside of the given window size (NumVisibleOptions) are not rendered, +and will affect which type of arrow indicators are shown. + +Future work: +- Make upIndicator another kind of widget (Image widget?) +================================================================================================ +*/ + +/* +======================== +idMenuWidget_List::Update +======================== +*/ +void idMenuWidget_List::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + for ( int optionIndex = 0; optionIndex < GetNumVisibleOptions(); ++optionIndex ) { + const int childIndex = GetViewOffset() + optionIndex; + bool shown = false; + + if ( optionIndex < GetChildren().Num() ) { + idMenuWidget & child = GetChildByIndex( optionIndex ); + const int controlIndex = GetNumVisibleOptions() - Min( GetNumVisibleOptions(), GetTotalNumberOfOptions() ) + optionIndex; + child.SetSpritePath( GetSpritePath(), va( "item%d", controlIndex ) ); + if ( child.BindSprite( root ) ) { + PrepareListElement( child, childIndex ); + child.Update(); + shown = true; + } + } + + if ( !shown ) { + // hide the item + idSWFSpriteInstance * const sprite = GetSprite()->GetScriptObject()->GetSprite( va( "item%d", optionIndex - GetTotalNumberOfOptions() ) ); + if ( sprite != NULL ) { + sprite->SetVisible( false ); + } + } + } + + idSWFSpriteInstance * const upSprite = GetSprite()->GetScriptObject()->GetSprite( "upIndicator" ); + if ( upSprite != NULL ) { + upSprite->SetVisible( GetViewOffset() > 0 ); + } + + idSWFSpriteInstance * const downSprite = GetSprite()->GetScriptObject()->GetSprite( "downIndicator" ); + if ( downSprite != NULL ) { + downSprite->SetVisible( GetViewOffset() + GetNumVisibleOptions() < GetTotalNumberOfOptions() ); + } +} + +/* +======================== +idMenuWidget_List::HandleAction +======================== +*/ +bool idMenuWidget_List::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + const idSWFParmList & parms = action.GetParms(); + + if ( action.GetType() == WIDGET_ACTION_SCROLL_VERTICAL ) { + const scrollType_t scrollType = static_cast< scrollType_t >( event.arg ); + if ( scrollType == SCROLL_SINGLE ) { + Scroll( parms[ 0 ].ToInteger() ); + } else if ( scrollType == SCROLL_PAGE ) { + ScrollOffset( parms[ 0 ].ToInteger() * ( GetNumVisibleOptions() - 1 ) ); + } else if ( scrollType == SCROLL_FULL ) { + ScrollOffset( parms[ 0 ].ToInteger() * 999 ); + } + return true; + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuWidget_List::ObserveEvent +======================== +*/ +void idMenuWidget_List::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + ExecuteEvent( event ); +} + +/* +======================== +idMenuWidget_List::CalculatePositionFromIndexDelta + +Pure functional encapsulation of how to calculate a new index and offset based on how the user +chose to move through the list. +======================== +*/ +void idMenuWidget_List::CalculatePositionFromIndexDelta( int & outIndex, int & outOffset, const int currentIndex, const int currentOffset, const int windowSize, const int maxSize, const int indexDelta, const bool allowWrapping, const bool wrapAround ) const { + assert( indexDelta != 0 ); + + int newIndex = currentIndex + indexDelta; + bool wrapped = false; + + if ( indexDelta > 0 ) { + // moving down the list + if ( newIndex > maxSize - 1 ) { + if ( allowWrapping ) { + if ( wrapAround ) { + wrapped = true; + newIndex = 0 + ( newIndex - maxSize ); + } else { + newIndex = 0; + } + } else { + newIndex = maxSize - 1; + } + } + } else { + // moving up the list + if ( newIndex < 0 ) { + if ( allowWrapping ) { + if ( wrapAround ) { + newIndex = maxSize + newIndex; + } else { + newIndex = maxSize - 1; + } + } else { + newIndex = 0; + } + } + } + + // calculate the offset + if ( newIndex - currentOffset >= windowSize ) { + outOffset = newIndex - windowSize + 1; + } else if ( currentOffset > newIndex ) { + if ( wrapped ) { + outOffset = 0; + } else { + outOffset = newIndex; + } + } else { + outOffset = currentOffset; + } + + outIndex = newIndex; + + // the intended behavior is that outOffset and outIndex are always within maxSize of each + // other, as they are meant to model a window of items that should be visible in the list. + assert( outIndex - outOffset < windowSize ); + assert( outIndex >= outOffset && outIndex >= 0 && outOffset >= 0 ); +} + +/* +======================== +idMenuWidget_List::CalculatePositionFromOffsetDelta +======================== +*/ +void idMenuWidget_List::CalculatePositionFromOffsetDelta( int & outIndex, int & outOffset, const int currentIndex, const int currentOffset, const int windowSize, const int maxSize, const int offsetDelta ) const { + // shouldn't be setting both indexDelta AND offsetDelta + // FIXME: make this simpler code - just pass a boolean to control it? + assert( offsetDelta != 0 ); + + const int newOffset = Max( currentIndex + offsetDelta, 0 ); + + if ( newOffset >= maxSize ) { + // scrolling past the end - just scroll all the way to the end + outIndex = maxSize - 1; + outOffset = Max( maxSize - windowSize, 0 ); + } else if ( newOffset >= maxSize - windowSize ) { + // scrolled to the last window + outIndex = newOffset; + outOffset = Max( maxSize - windowSize, 0 ); + } else { + outIndex = outOffset = newOffset; + } + + // the intended behavior is that outOffset and outIndex are always within maxSize of each + // other, as they are meant to model a window of items that should be visible in the list. + assert( outIndex - outOffset < windowSize ); + assert( outIndex >= outOffset && outIndex >= 0 && outOffset >= 0 ); +} + +/* +======================== +idMenuWidget_List::Scroll +======================== +*/ +void idMenuWidget_List::Scroll( const int scrollAmount, const bool wrapAround ) { + + if ( GetTotalNumberOfOptions() == 0 ) { + return; + } + + int newIndex, newOffset; + + CalculatePositionFromIndexDelta( newIndex, newOffset, GetViewIndex(), GetViewOffset(), GetNumVisibleOptions(), GetTotalNumberOfOptions(), scrollAmount, IsWrappingAllowed(), wrapAround ); + if ( newOffset != GetViewOffset() ) { + SetViewOffset( newOffset ); + if ( menuData != NULL ) { + menuData->PlaySound( GUI_SOUND_FOCUS ); + } + Update(); + } + + if ( newIndex != GetViewIndex() ) { + SetViewIndex( newIndex ); + SetFocusIndex( newIndex - newOffset ); + } +} + +/* +======================== +idMenuWidget_List::ScrollOffset +======================== +*/ +void idMenuWidget_List::ScrollOffset( const int scrollAmount ) { + + if ( GetTotalNumberOfOptions() == 0 ) { + return; + } + + int newIndex, newOffset; + + CalculatePositionFromOffsetDelta( newIndex, newOffset, GetViewIndex(), GetViewOffset(), GetNumVisibleOptions(), GetTotalNumberOfOptions(), scrollAmount ); + if ( newOffset != GetViewOffset() ) { + SetViewOffset( newOffset ); + Update(); + } + + if ( newIndex != GetViewIndex() ) { + SetViewIndex( newIndex ); + SetFocusIndex( newIndex - newOffset ); + } +} + +//********************************************************************************* +// GAME BROWSER LIST +//******************************************************************************** + +/* +======================== +idMenuWidget_GameBrowserList::Update +======================== +*/ +void idMenuWidget_GameBrowserList::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + for ( int optionIndex = 0; optionIndex < GetNumVisibleOptions(); ++optionIndex ) { + const int childIndex = GetViewOffset() + optionIndex; + bool shown = false; + if ( optionIndex < GetChildren().Num() ) { + idMenuWidget & child = GetChildByIndex( optionIndex ); + child.SetSpritePath( GetSpritePath(), va( "item%d", optionIndex ) ); + if ( child.BindSprite( root ) ) { + shown = PrepareListElement( child, childIndex ); + if ( shown ) { + child.SetState( WIDGET_STATE_NORMAL ); + child.GetSprite()->SetVisible( true ); + child.Update(); + } else { + child.GetSprite()->SetVisible( false ); + } + } + } + } + + idSWFSpriteInstance * const upSprite = GetSprite()->GetScriptObject()->GetSprite( "upIndicator" ); + if ( upSprite != NULL ) { + upSprite->SetVisible( GetViewOffset() > 0 ); + } + + idSWFSpriteInstance * const downSprite = GetSprite()->GetScriptObject()->GetSprite( "downIndicator" ); + if ( downSprite != NULL ) { + downSprite->SetVisible( GetViewOffset() + GetNumVisibleOptions() < GetTotalNumberOfOptions() ); + } +} + +/* +======================== +idMenuWidget_GameBrowserList::PrepareListElement +======================== +*/ +bool idMenuWidget_GameBrowserList::PrepareListElement( idMenuWidget & widget, const int childIndex ) { + + if ( childIndex >= games.Num() ) { + return false; + } + + idMenuWidget_ServerButton * const button = dynamic_cast< idMenuWidget_ServerButton * >( &widget ); + if ( button == NULL ) { + return false; + } + + if ( games[childIndex].serverName.IsEmpty() ) { + return false; + } + + const idBrowserEntry_t entry = games[childIndex]; + + button->SetButtonInfo( entry.serverName, entry.mapName, entry.modeName, entry.index, entry.players, entry.maxPlayers, entry.joinable, entry.validMap ); + + return true; + +} + +/* +======================== +idMenuWidget_GameBrowserList::PrepareListElement +======================== +*/ +void idMenuWidget_GameBrowserList::ClearGames() { + games.Clear(); +} + +/* +======================== +idMenuWidget_GameBrowserList::PrepareListElement +======================== +*/ +void idMenuWidget_GameBrowserList::AddGame( idStr name_, idStrId mapName_, idStr modeName_, int index_, int players_, int maxPlayers_, bool joinable_, bool validMap_ ) { + + idBrowserEntry_t entry; + + entry.serverName = name_; + entry.index = index_; + entry.players = players_; + entry.maxPlayers = maxPlayers_; + entry.joinable = joinable_; + entry.validMap = validMap_; + entry.mapName = mapName_; + entry.modeName = modeName_; + + games.Append( entry ); +} + +/* +======================== +idMenuWidget_GameBrowserList::GetTotalNumberOfOptions +======================== +*/ +int idMenuWidget_GameBrowserList::GetTotalNumberOfOptions() const { + return games.Num(); +} + +/* +======================== +idMenuWidget_GameBrowserList::PrepareListElement +======================== +*/ +int idMenuWidget_GameBrowserList::GetServerIndex() { + + if ( GetViewIndex() < games.Num() ) { + return games[ GetViewIndex() ].index; + } + + return -1; + +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_LobbyList.cpp b/neo/d3xp/menus/MenuWidget_LobbyList.cpp new file mode 100644 index 00000000..1baf92b8 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_LobbyList.cpp @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_LobbyList::Update +======================== +*/ +void idMenuWidget_LobbyList::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + for ( int i = 0; i < headings.Num(); ++i ) { + idSWFTextInstance * txtHeading = GetSprite()->GetScriptObject()->GetNestedText( va( "heading%d", i ) ); + if ( txtHeading != NULL ) { + txtHeading->SetText( headings[i] ); + txtHeading->SetStrokeInfo( true, 0.75f, 1.75f ); + } + } + + for ( int optionIndex = 0; optionIndex < GetNumVisibleOptions(); ++optionIndex ) { + bool shown = false; + if ( optionIndex < GetChildren().Num() ) { + idMenuWidget & child = GetChildByIndex( optionIndex ); + child.SetSpritePath( GetSpritePath(), va( "item%d", optionIndex ) ); + if ( child.BindSprite( root ) ) { + shown = PrepareListElement( child, optionIndex ); + if ( shown ) { + child.GetSprite()->SetVisible( true ); + child.Update(); + } else { + child.GetSprite()->SetVisible( false ); + } + } + } + } +} + +/* +======================== +idMenuWidget_LobbyList::PrepareListElement +======================== +*/ +bool idMenuWidget_LobbyList::PrepareListElement( idMenuWidget & widget, const int childIndex ) { + + idMenuWidget_LobbyButton * const button = dynamic_cast< idMenuWidget_LobbyButton * >( &widget ); + if ( button == NULL ) { + return false; + } + + if ( !button->IsValid() ) { + return false; + } + + return true; + +} + +/* +======================== +idMenuWidget_LobbyList::SetHeadingInfo +======================== +*/ +void idMenuWidget_LobbyList::SetHeadingInfo( idList< idStr > & list ) { + headings.Clear(); + for ( int index = 0; index < list.Num(); ++index ) { + headings.Append( list[ index ] ); + } +} + +/* +======================== +idMenuWidget_LobbyList::SetEntryData +======================== +*/ +void idMenuWidget_LobbyList::SetEntryData( int index, idStr name, voiceStateDisplay_t voiceState ) { + + if ( GetChildren().Num() == 0 || index >= GetChildren().Num() ) { + return; + } + + idMenuWidget & child = GetChildByIndex( index ); + idMenuWidget_LobbyButton * const button = dynamic_cast< idMenuWidget_LobbyButton * >( &child ); + + if ( button == NULL ) { + return; + } + + button->SetButtonInfo( name, voiceState ); +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_MenuBar.cpp b/neo/d3xp/menus/MenuWidget_MenuBar.cpp new file mode 100644 index 00000000..1ac9a61a --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_MenuBar.cpp @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_MenuBar::Initialize +======================== +*/ +void idMenuWidget_MenuBar::Initialize( idMenuHandler * data ) { + idMenuWidget::Initialize( data ); +} + +/* +======================== +idMenuWidget_MenuBar::Update +======================== +*/ +void idMenuWidget_MenuBar::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + totalWidth = 0.0f; + buttonPos = 0.0f; + + for ( int index = 0; index < GetNumVisibleOptions(); ++index ) { + + if ( index >= children.Num() ) { + break; + } + + if ( index != 0 ) { + totalWidth += rightSpacer; + } + + idMenuWidget & child = GetChildByIndex( index ); + child.SetSpritePath( GetSpritePath(), va( "btn%d", index ) ); + if ( child.BindSprite( root ) ) { + PrepareListElement( child, index ); + child.Update(); + } + } + + // 640 is half the size of our flash files width + float xPos = 640.0f - ( totalWidth / 2.0f ); + GetSprite()->SetXPos( xPos ); + + idSWFSpriteInstance * backing = GetSprite()->GetScriptObject()->GetNestedSprite( "backing" ); + if ( backing != NULL ) { + if ( menuData != NULL && menuData->GetPlatform() != 2 ) { + backing->SetVisible( false ); + } else { + backing->SetVisible( true ); + backing->SetXPos( totalWidth / 2.0f ); + } + } + +} + +/* +======================== +idMenuWidget_MenuBar::SetListHeadings +======================== +*/ +void idMenuWidget_MenuBar::SetListHeadings( idList< idStr > & list ) { + headings.Clear(); + for ( int index = 0; index < list.Num(); ++index ) { + headings.Append( list[ index ] ); + } +} + +/* +======================== +idMenuWidget_MenuBar::GetTotalNumberOfOptions +======================== +*/ +int idMenuWidget_MenuBar::GetTotalNumberOfOptions() const { + return GetChildren().Num(); +} + +/* +======================== +idMenuWidget_MenuBar::PrepareListElement +======================== +*/ +bool idMenuWidget_MenuBar::PrepareListElement( idMenuWidget & widget, const int navIndex ) { + + if ( navIndex >= GetNumVisibleOptions() ) { + return false; + } + + idMenuWidget_MenuButton * const button = dynamic_cast< idMenuWidget_MenuButton * >( &widget ); + if ( button == NULL || button->GetSprite() == NULL ) { + return false; + } + + if ( navIndex >= headings.Num() ) { + button->SetLabel( "" ); + } else { + button->SetLabel( headings[navIndex] ); + idSWFTextInstance * ti = button->GetSprite()->GetScriptObject()->GetNestedText( "txtVal" ); + if ( ti != NULL ) { + ti->SetStrokeInfo( true, 0.7f, 1.25f ); + ti->SetText( headings[ navIndex ] ); + button->SetPosition( buttonPos ); + totalWidth += ti->GetTextLength(); + buttonPos += rightSpacer + ti->GetTextLength(); + } + } + + return true; +} diff --git a/neo/d3xp/menus/MenuWidget_NavBar.cpp b/neo/d3xp/menus/MenuWidget_NavBar.cpp new file mode 100644 index 00000000..5512299f --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_NavBar.cpp @@ -0,0 +1,175 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_NavBar::Initialize +======================== +*/ +void idMenuWidget_NavBar::Initialize( idMenuHandler * data ) { + idMenuWidget::Initialize( data ); +} + +/* +======================== +idMenuWidget_NavBar::PrepareListElement +======================== +*/ +void idMenuWidget_NavBar::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + + if ( !BindSprite( root ) ) { + return; + } + + int rightIndex = 0; + + buttonPos = initialPos; + + for ( int index = 0; index < GetNumVisibleOptions() - 1; ++index ) { + idSWFSpriteInstance * const rightOption = GetSprite()->GetScriptObject()->GetSprite( va( "optionRight%d", index ) ); + rightOption->SetVisible( false ); + idSWFSpriteInstance * const leftOption = GetSprite()->GetScriptObject()->GetSprite( va( "optionLeft%d", index ) ); + leftOption->SetVisible( false ); + } + + for ( int index = 0; index < GetTotalNumberOfOptions(); ++index ) { + idMenuWidget & child = GetChildByIndex( index ); + idMenuWidget_NavButton * const button = dynamic_cast< idMenuWidget_NavButton * >( &child ); + button->SetLabel( "" ); + } + + for ( int index = 0; index < GetNumVisibleOptions(); ++index ) { + if ( index < GetFocusIndex() ) { + idMenuWidget & child = GetChildByIndex( index ); + child.SetSpritePath( GetSpritePath(), va( "optionLeft%d", index ) ); + + if ( child.BindSprite( root ) ) { + PrepareListElement( child, index ); + child.Update(); + } + + } else if ( index > GetFocusIndex() ) { + int rightChildIndex = ( GetNumVisibleOptions() - 1 ) + ( index - 1 ); + idMenuWidget & child = GetChildByIndex( rightChildIndex ); + child.SetSpritePath( GetSpritePath(), va( "optionRight%d", rightIndex ) ); + rightIndex++; + + if ( child.BindSprite( root ) ) { + PrepareListElement( child, index ); + child.Update(); + } + + } else { + int mainIndex = GetTotalNumberOfOptions() - 1; + idMenuWidget & child = GetChildByIndex( mainIndex ); + child.SetSpritePath( GetSpritePath(), va( "option" ) ); + + if ( child.BindSprite( root ) ) { + PrepareListElement( child, index ); + child.Update(); + } + } + } +} + +/* +======================== +idMenuWidget_NavBar::SetListHeadings +======================== +*/ +void idMenuWidget_NavBar::SetListHeadings( idList< idStr > & list ) { + headings.Clear(); + for ( int index = 0; index < list.Num(); ++index ) { + headings.Append( list[ index ] ); + } +} + +/* +======================== +idMenuWidget_NavBar::GetTotalNumberOfOptions +======================== +*/ +int idMenuWidget_NavBar::GetTotalNumberOfOptions() const { + return GetChildren().Num(); +} + +/* +======================== +idMenuWidget_NavBar::PrepareListElement +======================== +*/ +bool idMenuWidget_NavBar::PrepareListElement( idMenuWidget & widget, const int navIndex ) { + + if ( navIndex >= GetNumVisibleOptions() || navIndex >= headings.Num() ) { + return false; + } + + idMenuWidget_NavButton * const button = dynamic_cast< idMenuWidget_NavButton * >( &widget ); + if ( button == NULL || button->GetSprite() == NULL ) { + return false; + } + + button->SetLabel( headings[navIndex] ); + idSWFTextInstance * ti = button->GetSprite()->GetScriptObject()->GetNestedText( "txtVal" ); + if ( ti != NULL ) { + ti->SetStrokeInfo( true, 0.7f, 1.25f ); + if ( navIndex < GetFocusIndex() ) { + ti->SetText( headings[ navIndex ] ); + buttonPos = buttonPos + ti->GetTextLength(); + button->SetPosition( buttonPos ); + button->SetNavIndex( navIndex, idMenuWidget_NavButton::NAV_WIDGET_LEFT ); + buttonPos += leftSpacer; + } else if ( navIndex > GetFocusIndex() ) { + ti->SetText( headings[ navIndex ] ); + ti->SetStrokeInfo( true, 0.7f, 1.25f ); + button->GetSprite()->SetXPos( buttonPos ); + button->SetPosition( buttonPos ); + button->SetNavIndex( navIndex, idMenuWidget_NavButton::NAV_WIDGET_RIGHT ); + buttonPos = buttonPos + ti->GetTextLength() + rightSpacer; + } else { + ti->SetText( headings[ navIndex ] ); + ti->SetStrokeInfo( true, 0.7f, 1.25f ); + button->GetSprite()->SetXPos( buttonPos ); + button->SetPosition( buttonPos ); + button->SetNavIndex( navIndex, idMenuWidget_NavButton::NAV_WIDGET_SELECTED ); + buttonPos = buttonPos + ti->GetTextLength() + selectedSpacer; + } + } + + return true; + +} diff --git a/neo/d3xp/menus/MenuWidget_NavButton.cpp b/neo/d3xp/menus/MenuWidget_NavButton.cpp new file mode 100644 index 00000000..b4f62409 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_NavButton.cpp @@ -0,0 +1,183 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_NavButton::Update +======================== +*/ +void idMenuWidget_NavButton::Update() { + + if ( GetSprite() == NULL ) { + return; + } + + if ( btnLabel.IsEmpty() ) { + GetSprite()->SetVisible( false ); + return; + } + + GetSprite()->SetVisible( true ); + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject(); + idSWFTextInstance * const text = spriteObject->GetNestedText( "txtVal" ); + if ( text != NULL ) { + text->SetText( btnLabel.c_str() ); + text->SetStrokeInfo( true, 0.7f, 1.25f ); + } + + GetSprite()->SetXPos( xPos ); + + if ( navState == NAV_WIDGET_SELECTED ) { + idSWFSpriteInstance * backing = GetSprite()->GetScriptObject()->GetNestedSprite( "backing" ); + if ( backing != NULL && text != NULL ) { + backing->SetXPos( text->GetTextLength() + 53.0f ); + } + } + + // + // events + // + + idSWFScriptObject * textObj = spriteObject->GetNestedObj( "txtVal" ); + + if ( textObj != NULL ) { + + textObj->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + textObj->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = spriteObject->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = textObj; + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + } +} + +/* +======================== +idMenuWidget_NavButton::ExecuteEvent +======================== +*/ +bool idMenuWidget_NavButton::ExecuteEvent( const idWidgetEvent & event ) { + + bool handled = false; + + //// do nothing at all if it's disabled + if ( GetState() != WIDGET_STATE_DISABLED ) { + switch ( event.type ) { + case WIDGET_EVENT_PRESS: { + //AnimateToState( ANIM_STATE_DOWN ); + handled = true; + break; + } + case WIDGET_EVENT_ROLL_OVER: { + if ( GetMenuData() ) { + GetMenuData()->PlaySound( GUI_SOUND_ROLL_OVER ); + } + handled = true; + break; + } + } + } + + idMenuWidget::ExecuteEvent( event ); + + return handled; +} + +//********************************************************************************************************* +// idMenuWidget_MenuButton + + +/* +======================== +idMenuWidget_NavButton::Update +======================== +*/ +void idMenuWidget_MenuButton::Update() { + + if ( GetSprite() == NULL ) { + return; + } + + if ( btnLabel.IsEmpty() ) { + GetSprite()->SetVisible( false ); + return; + } + + GetSprite()->SetVisible( true ); + + idSWFScriptObject * const spriteObject = GetSprite()->GetScriptObject(); + idSWFTextInstance * const text = spriteObject->GetNestedText( "txtVal" ); + if ( text != NULL ) { + text->SetText( btnLabel.c_str() ); + text->SetStrokeInfo( true, 0.7f, 1.25f ); + + idSWFSpriteInstance * selBar = spriteObject->GetNestedSprite( "sel", "bar" ); + idSWFSpriteInstance * hoverBar =spriteObject->GetNestedSprite( "hover", "bar" ); + + if ( selBar != NULL ) { + selBar->SetXPos( text->GetTextLength() / 2.0f ); + selBar->SetScale( 100.0f * ( text->GetTextLength() / 300.0f ), 100.0f ); + } + + if ( hoverBar != NULL ) { + hoverBar->SetXPos( text->GetTextLength() / 2.0f ); + hoverBar->SetScale( 100.0f * ( text->GetTextLength() / 352.0f ), 100.0f ); + } + + idSWFSpriteInstance * hitBox =spriteObject->GetNestedSprite( "hitBox" ); + if ( hitBox != NULL ) { + hitBox->SetScale( 100.0f * ( text->GetTextLength() / 235 ), 100.0f ); + } + } + + GetSprite()->SetXPos( xPos ); + + idSWFScriptObject * textObj = spriteObject->GetNestedObj( "txtVal" ); + + if ( textObj != NULL ) { + + textObj->Set( "onPress", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_PRESS, 0 ) ); + textObj->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_RELEASE, 0 ) ); + + idSWFScriptObject * hitBox = spriteObject->GetObject( "hitBox" ); + if ( hitBox == NULL ) { + hitBox = textObj; + } + + hitBox->Set( "onRollOver", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OVER, 0 ) ); + hitBox->Set( "onRollOut", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_ROLL_OUT, 0 ) ); + } +} diff --git a/neo/d3xp/menus/MenuWidget_PDA_AudioFiles.cpp b/neo/d3xp/menus/MenuWidget_PDA_AudioFiles.cpp new file mode 100644 index 00000000..cd8ffd3e --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_PDA_AudioFiles.cpp @@ -0,0 +1,188 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int MAX_AUDIO_ITEMS = 3; + + +/* +======================== +idMenuWidget_PDA_AudioFiles::~idMenuWidget_PDA_AudioFiles +======================== +*/ +idMenuWidget_PDA_AudioFiles::~idMenuWidget_PDA_AudioFiles() { +} + +/* +======================== +idMenuWidget_PDA_AudioFiles::Initialize +======================== +*/ +void idMenuWidget_PDA_AudioFiles::Initialize( idMenuHandler * data ) { + + idMenuWidget_DynamicList * pdaAudioList = new (TAG_SWF) idMenuWidget_DynamicList(); + + pdaAudioList->SetSpritePath( GetSpritePath(), "info", "options" ); + pdaAudioList->SetNumVisibleOptions( MAX_AUDIO_ITEMS ); + pdaAudioList->SetWrappingAllowed( true ); + + while ( pdaAudioList->GetChildren().Num() < MAX_AUDIO_ITEMS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_SELECT_PDA_AUDIO, pdaAudioList->GetChildren().Num() ); + buttonWidget->Initialize( data ); + pdaAudioList->AddChild( buttonWidget ); + } + pdaAudioList->Initialize( data ); + + AddChild( pdaAudioList ); +} + +/* +======================== +idMenuWidget_PDA_AudioFiles::Update +======================== +*/ +void idMenuWidget_PDA_AudioFiles::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + if ( pdaIndex > player->GetInventory().pdas.Num() ) { + return; + } + + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + + idSWFScriptObject * dataObj = GetSprite()->GetScriptObject()->GetNestedObj( "info" ); + if ( dataObj != NULL && pda != NULL ) { + + idSWFTextInstance * txtOwner = dataObj->GetNestedText( "txtOwner" ); + if ( txtOwner != NULL ) { + idStr ownerText = idLocalization::GetString( "#str_04203" ); + ownerText.Append( ": "); + ownerText.Append( pda->GetFullName() ); + txtOwner->SetText( ownerText.c_str() ); + } + + idMenuWidget_DynamicList * const audioList = dynamic_cast< idMenuWidget_DynamicList * >( &GetChildByIndex( 0 ) ); + if ( audioList != NULL ) { + audioFileNames.Clear(); + if ( pda->GetNumAudios() == 0 ) { + idList< idStr > audioName; + audioName.Append( idLocalization::GetString( "#str_04168" ) ); + audioList->GetChildByIndex( 0 ).SetState( WIDGET_STATE_DISABLED ); + audioFileNames.Append( audioName ); + } else { + audioList->GetChildByIndex( 0 ).SetState( WIDGET_STATE_NORMAL ); + const idDeclAudio *aud = NULL; + for ( int index = 0; index < pda->GetNumAudios(); ++index ) { + idList< idStr > audioName; + aud = pda->GetAudioByIndex( index ); + if ( aud != NULL ) { + audioName.Append( aud->GetAudioName() ); + } else { + audioName.Append( "" ); + } + audioFileNames.Append( audioName ); + } + } + + audioList->SetListData( audioFileNames ); + if ( audioList->BindSprite( root ) ) { + audioList->Update(); + } + } + } + + //idSWFSpriteInstance * dataSprite = dataObj->GetSprite(); +} + +/* +======================== +idMenuWidget_PDA_AudioFiles::ObserveEvent +======================== +*/ +void idMenuWidget_PDA_AudioFiles::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + if ( GetSprite() != NULL ) { + if ( list->GetViewIndex() == 0 ) { + GetSprite()->PlayFrame( "rollOff" ); + } else if ( ( pdaIndex == 0 || GetSprite()->GetCurrentFrame() <= 2 || GetSprite()->GetCurrentFrame() > GetSprite()->FindFrame( "idle" ) ) && list->GetViewIndex() != 0 ) { + GetSprite()->PlayFrame( "rollOn" ); + } + } + pdaIndex = list->GetViewIndex(); + if ( GetParent() != NULL && menuData != NULL && menuData->ActiveScreen() == PDA_AREA_USER_DATA ) { + GetParent()->Update(); + } else { + Update(); + } + idMenuWidget_DynamicList * const audioList = dynamic_cast< idMenuWidget_DynamicList * >( &GetChildByIndex( 0 ) ); + if ( audioList != NULL ) { + audioList->SetFocusIndex( 0 ); + audioList->SetViewIndex( 0 ); + } + break; + } + case WIDGET_EVENT_FOCUS_OFF: { + idMenuWidget_DynamicList * const audioList = dynamic_cast< idMenuWidget_DynamicList * >( &GetChildByIndex( 0 ) ); + if ( audioList != NULL ) { + audioList->SetFocusIndex( 0 ); + audioList->SetViewIndex( 0 ); + } + Update(); + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_PDA_EmailInbox.cpp b/neo/d3xp/menus/MenuWidget_PDA_EmailInbox.cpp new file mode 100644 index 00000000..fe78cb7b --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_PDA_EmailInbox.cpp @@ -0,0 +1,178 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +static const int MAX_EMAIL_ITEMS = 7; + +/* +======================== +idMenuWidget_PDA_EmailInbox::Initialize +======================== +*/ +void idMenuWidget_PDA_EmailInbox::Initialize( idMenuHandler * data ) { + + idMenuWidget_ScrollBar * scrollbar = new (TAG_SWF) idMenuWidget_ScrollBar(); + scrollbar->SetSpritePath( GetSpritePath(), "info", "scrollbar" ); + scrollbar->Initialize( data ); + + emailList = new (TAG_SWF) idMenuWidget_DynamicList(); + emailList->SetSpritePath( GetSpritePath(), "info", "options" ); + emailList->SetNumVisibleOptions( MAX_EMAIL_ITEMS ); + emailList->SetWrappingAllowed( true ); + while ( emailList->GetChildren().Num() < MAX_EMAIL_ITEMS ) { + idMenuWidget_Button * const buttonWidget = new (TAG_SWF) idMenuWidget_Button(); + buttonWidget->AddEventAction( WIDGET_EVENT_PRESS ).Set( WIDGET_ACTION_PDA_SELECT_EMAIL, emailList->GetChildren().Num() ); + buttonWidget->AddEventAction( WIDGET_EVENT_FOCUS_ON ).Set( WIDGET_ACTION_REFRESH ); + buttonWidget->Initialize( data ); + buttonWidget->RegisterEventObserver( scrollbar ); + emailList->AddChild( buttonWidget ); + } + emailList->Initialize( data ); + emailList->AddChild( scrollbar ); + + AddChild( emailList ); +} + +/* +======================== +idMenuWidget_PDA_EmailInbox::Update +======================== +*/ +void idMenuWidget_PDA_EmailInbox::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + if ( pdaIndex > player->GetInventory().pdas.Num() ) { + return; + } + + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + + idSWFScriptObject * dataObj = GetSprite()->GetScriptObject()->GetNestedObj( "info" ); + if ( dataObj != NULL && pda != NULL ) { + + idSWFTextInstance * txtOwner = dataObj->GetNestedText( "heading", "txtOwner" ); + if ( txtOwner != NULL ) { + idStr ownerText = idLocalization::GetString( "#str_01474" ); + ownerText.Append( ": "); + + if ( pdaIndex == 0 ) { + ownerText.Append( session->GetLocalUserName(0) ); + } else { + ownerText.Append( pda->GetFullName() ); + } + txtOwner->SetText( ownerText.c_str() ); + + if ( pdaIndex == 0 ) { + txtOwner->SetIgnoreColor( false ); + } else { + txtOwner->SetIgnoreColor( false ); + txtOwner->SetText( pda->GetFullName() ); + } + + } + + if ( emailList != NULL ) { + const idDeclEmail *email = NULL; + emailInfo.Clear(); + for ( int index = 0; index < pda->GetNumEmails(); ++index ) { + idList< idStr > emailData; + email = pda->GetEmailByIndex( index ); + if ( email != NULL ) { + emailData.Append( email->GetFrom() ); + emailData.Append( email->GetSubject() ); + emailData.Append( email->GetDate() ); + } + emailInfo.Append( emailData ); + } + + emailList->SetListData( emailInfo ); + if ( emailList->BindSprite( root ) ) { + emailList->Update(); + } + } + } +} + +/* +======================== +idMenuWidget_PDA_EmailInbox::ObserveEvent +======================== +*/ +void idMenuWidget_PDA_EmailInbox::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + int oldIndex = pdaIndex; + if ( list != NULL ) { + pdaIndex = list->GetViewIndex(); + Update(); + } + if ( emailList != NULL && oldIndex != pdaIndex ) { + emailList->SetFocusIndex( 0 ); + emailList->SetViewIndex( 0 ); + emailList->SetViewOffset( 0 ); + } + break; + } + case WIDGET_EVENT_FOCUS_OFF: { + Update(); + if ( emailList != NULL ) { + emailList->SetFocusIndex( 0 ); + emailList->SetViewIndex( 0 ); + emailList->SetViewOffset( 0 ); + } + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_PDA_Objective.cpp b/neo/d3xp/menus/MenuWidget_PDA_Objective.cpp new file mode 100644 index 00000000..24127bb5 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_PDA_Objective.cpp @@ -0,0 +1,156 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_PDA_Objective::Update +======================== +*/ +void idMenuWidget_PDA_Objective::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + idSWFScriptObject * dataObj = GetSprite()->GetScriptObject()->GetNestedObj( "info" ); + idSWFSpriteInstance * dataSprite = dataObj->GetSprite(); + + if ( dataObj != NULL && dataSprite != NULL ) { + + idSWFSpriteInstance * img = dataObj->GetNestedSprite( "objImg", "img" ); + + if ( player->GetInventory().objectiveNames.Num() == 0 ) { + dataSprite->StopFrame( 1 ); + } else { + int numObjectives = player->GetInventory().objectiveNames.Num(); + + int objStartIndex = 0; + if ( numObjectives == 1 ) { + dataSprite->StopFrame( 2 ); + objStartIndex = 0; + } else { + dataSprite->StopFrame( 3 ); + objStartIndex = 1; + } + + idSWFTextInstance * txtDesc = dataObj->GetNestedText( "txtDesc" ); + + int displayCount = 0; + for ( int index = numObjectives - 1; displayCount < 2 && index >= 0; --index ) { + + if ( img != NULL ) { + if ( player->GetInventory().objectiveNames[index].screenshot == NULL ) { + img->SetVisible( false ); + } else { + img->SetVisible( true ); + img->SetMaterial( player->GetInventory().objectiveNames[index].screenshot ); + } + } + + idSWFSpriteInstance * objSel = dataObj->GetNestedSprite( va( "obj%d", objStartIndex - displayCount ), "sel" ); + idSWFTextInstance * txtNote = dataObj->GetNestedText( va( "obj%d", objStartIndex - displayCount ), "txtVal" ); + + if ( objSel != NULL ) { + if ( displayCount == 0 ) { + objSel->SetVisible( true ); + } else { + objSel->SetVisible( false ); + } + } + + if ( txtNote != NULL ) { + txtNote->SetText( player->GetInventory().objectiveNames[index].title.c_str() ); + } + + if ( displayCount == 0 ) { + txtDesc->SetText( player->GetInventory().objectiveNames[index].text.c_str() ); + } + + displayCount++; + } + } + + // Set the main objective text + idTarget_SetPrimaryObjective * mainObj = player->GetPrimaryObjective(); + idSWFTextInstance * txtMainObj = dataObj->GetNestedText( "txtObj" ); + if ( txtMainObj != NULL ) { + if ( mainObj != NULL ) { + txtMainObj->SetText( mainObj->spawnArgs.GetString( "text", idLocalization::GetString( "#str_04253" ) ) ); + } else { + txtMainObj->SetText( idLocalization::GetString( "#str_02526" ) ); + } + } + } +} + +/* +======================== +idMenuWidget_Help::ObserveEvent +======================== +*/ +void idMenuWidget_PDA_Objective::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + if ( GetSprite() != NULL ) { + if ( list->GetViewIndex() == 0 ) { + GetSprite()->PlayFrame( "rollOn" ); + } else if ( pdaIndex == 0 && list->GetViewIndex() != 0 ) { + GetSprite()->PlayFrame( "rollOff" ); + } + } + pdaIndex = list->GetViewIndex(); + Update(); + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_PDA_UserData.cpp b/neo/d3xp/menus/MenuWidget_PDA_UserData.cpp new file mode 100644 index 00000000..f7e496bc --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_PDA_UserData.cpp @@ -0,0 +1,168 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_PDA_UserData::Update +======================== +*/ +void idMenuWidget_PDA_UserData::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + if ( pdaIndex > player->GetInventory().pdas.Num() ) { + return; + } + + const idDeclPDA * pda = player->GetInventory().pdas[ pdaIndex ]; + + idSWFScriptObject * dataObj = GetSprite()->GetScriptObject(); + + if ( dataObj != NULL && pda != NULL ) { + + idSWFTextInstance * txtName = dataObj->GetNestedText( "txtName" ); + idSWFTextInstance * txtId = dataObj->GetNestedText( "txtId" ); + idSWFTextInstance * txtLocation = dataObj->GetNestedText( "txtLocation" ); + idSWFTextInstance * txtRank = dataObj->GetNestedText( "txtRank" ); + idSWFTextInstance * txtClearance = dataObj->GetNestedText( "txtClearance" ); + idSWFTextInstance * txtLocHeading = dataObj->GetNestedText( "txtLocHeading" ); + + if ( txtName != NULL ) { + + if ( pdaIndex == 0 ) { + txtName->SetIgnoreColor( false ); + txtName->SetText( session->GetLocalUserName(0) ); + } else { + txtName->SetIgnoreColor( false ); + txtName->SetText( pda->GetFullName() ); + } + } + + if ( txtLocHeading != NULL ) { + if ( pdaIndex == 0 ) { + txtLocHeading->SetText( idLocalization::GetString( "#str_02516" ) ); // location + } else { + txtLocHeading->SetText( idLocalization::GetString( "#str_02515" ) ); // post + } + } + + if ( txtId != NULL ) { + txtId->SetText( pda->GetID() ); + } + + if ( txtLocation != NULL ) { + if ( pdaIndex == 0 ) { + idLocationEntity *locationEntity = gameLocal.LocationForPoint( player->GetEyePosition() ); + if ( locationEntity ) { + txtLocation->SetText( locationEntity->GetLocation() ); + } else { + txtLocation->SetText( idLocalization::GetString( "#str_02911" ) ); + } + } else { + txtLocation->SetText( pda->GetPost() ); + } + } + + if ( txtRank != NULL ) { + txtRank->SetText( pda->GetTitle() ); + } + + if ( txtClearance != NULL ) { + const char *security = pda->GetSecurity(); + if ( *security == NULL ) { + txtClearance->SetText( idLocalization::GetString( "#str_00066" ) ); + } else { + txtClearance->SetText( security ); + } + } + } +} + +/* +======================== +idMenuWidget_Help::ObserveEvent +======================== +*/ +void idMenuWidget_PDA_UserData::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + pdaIndex = list->GetViewIndex(); + if ( GetParent() != NULL && menuData != NULL && menuData->ActiveScreen() == PDA_AREA_USER_DATA ) { + GetParent()->Update(); + } else { + Update(); + } + break; + } + case WIDGET_EVENT_FOCUS_OFF: { + // Don't do anything when losing focus. Focus updates come in pairs, so we can + // skip doing anything on the "lost focus" event, and instead do updates on the + // "got focus" event. + break; + } + case WIDGET_EVENT_ROLL_OVER: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + pdaIndex = list->GetViewIndex(); + Update(); + break; + } + case WIDGET_EVENT_ROLL_OUT: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + pdaIndex = list->GetViewIndex(); + Update(); + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/menus/MenuWidget_PDA_VideoInfo.cpp b/neo/d3xp/menus/MenuWidget_PDA_VideoInfo.cpp new file mode 100644 index 00000000..d46a3132 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_PDA_VideoInfo.cpp @@ -0,0 +1,116 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +void idMenuWidget_PDA_VideoInfo::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + idSWFTextInstance * txtHeading = GetSprite()->GetScriptObject()->GetNestedText( "txtName" ); + idSWFTextInstance * txtInfo = GetSprite()->GetScriptObject()->GetNestedText( "txtInfo" ); + + int numVideos = player->GetInventory().videos.Num(); + if ( numVideos != 0 ) { + const idDeclVideo * video = player->GetVideo( videoIndex ); + if( video != NULL ) { + if ( txtHeading != NULL ) { + txtHeading->SetText( video->GetVideoName() ); + } + + if ( txtInfo != NULL ) { + txtInfo->SetText( video->GetInfo() ); + } + } + } else { + if ( txtHeading != NULL ) { + txtHeading->SetText( "" ); + } + + if ( txtInfo != NULL ) { + txtInfo->SetText( "" ); + } + } +} + +void idMenuWidget_PDA_VideoInfo::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + videoIndex = list->GetViewIndex(); + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + player->EndVideoDisk(); + + const idDeclVideo * video = player->GetVideo( videoIndex ); + + if ( video != NULL ) { + idSWFSpriteInstance * videoSprite = GetSprite()->GetScriptObject()->GetNestedSprite( "video", "img" ); + if ( videoSprite != NULL ) { + videoSprite->SetMaterial( video->GetPreview() ); + } + } + } + + if ( GetParent() != NULL ) { + idMenuScreen_PDA_VideoDisks * screen = dynamic_cast< idMenuScreen_PDA_VideoDisks * const >( GetParent() ); + if ( screen != NULL ) { + screen->Update(); + } + } + + break; + } + } +} + diff --git a/neo/d3xp/menus/MenuWidget_Scrollbar.cpp b/neo/d3xp/menus/MenuWidget_Scrollbar.cpp new file mode 100644 index 00000000..4e3a67d6 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_Scrollbar.cpp @@ -0,0 +1,261 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +void idMenuWidget_ScrollBar::Initialize( idMenuHandler * data ) { + idMenuWidget::Initialize( data ); + + AddEventAction( WIDGET_EVENT_DRAG_START ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_DRAG_START, WIDGET_EVENT_DRAG_START ) ); + AddEventAction( WIDGET_EVENT_DRAG_STOP ).Set( new (TAG_SWF) idWidgetActionHandler( this, WIDGET_ACTION_EVENT_DRAG_STOP, WIDGET_EVENT_DRAG_STOP ) ); +} + +/* +======================== +idMenuWidget_ScrollBar::Update +======================== +*/ +void idMenuWidget_ScrollBar::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + if ( GetParent() == NULL ) { + return; + } + + CalcTopAndBottom(); + + idSWFScriptObject * node = GetSprite()->GetScriptObject()->GetNestedObj( "node" ); + idSWFSpriteInstance * nodeSprite = GetSprite()->GetScriptObject()->GetNestedSprite( "node" ); + if ( node != NULL && nodeSprite != NULL ) { + node->Set( "onDrag", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_DRAG_START, 0 ) ); + node->Set( "onRelease", new ( TAG_SWF ) WrapWidgetSWFEvent( this, WIDGET_EVENT_DRAG_STOP, 0 ) ); + + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( GetParent() ); + if ( list != NULL ) { + float percent = 0.0f; + if ( ( list->GetTotalNumberOfOptions() - list->GetNumVisibleOptions() ) > 0 ) { + percent = (float)list->GetViewOffset() / ( (float)list->GetTotalNumberOfOptions() - list->GetNumVisibleOptions() ); + float range = yBot - yTop; + nodeSprite->SetVisible( true ); + nodeSprite->SetYPos( percent * range ); + //GetSprite()->StopFrame( int( ( percent * 100.0f ) + 2.0f ) ); + } else { + nodeSprite->SetVisible( 0 ); + } + } + + idMenuWidget_InfoBox * const infoBox = dynamic_cast< idMenuWidget_InfoBox * const >( GetParent() ); + if ( infoBox != NULL ) { + float percent = 0.0f; + if ( infoBox->GetMaxScroll() == 0 ) { + nodeSprite->SetVisible( 0 ); + } else { + percent = (float)infoBox->GetScroll() / (float)infoBox->GetMaxScroll(); + float range = yBot - yTop; + nodeSprite->SetVisible( true ); + nodeSprite->SetYPos( percent * range ); + //GetSprite()->StopFrame( int( ( percent * 100.0f ) + 2.0f ) ); + } + } + } +} + +/* +======================== +idMenuWidget_ScrollBar::CalcTopAndBottom +======================== +*/ +void idMenuWidget_ScrollBar::CalcTopAndBottom() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + int tempPos = 0.0f; + idSWFSpriteInstance * curMC = GetSprite()->GetScriptObject()->GetNestedSprite( "top" ); + if ( curMC != NULL ) { + tempPos = curMC->GetYPos(); + while ( curMC->parent != NULL ) { + curMC = curMC->parent; + tempPos += curMC->GetYPos(); + } + } + yTop = tempPos; + + tempPos = 0.0f; + curMC = GetSprite()->GetScriptObject()->GetNestedSprite( "bottom" ); + if ( curMC != NULL ) { + tempPos = curMC->GetYPos(); + while ( curMC->parent != NULL ) { + curMC = curMC->parent; + tempPos += curMC->GetYPos(); + } + } + yBot = tempPos; +} + +/* +======================== +idMenuWidget_ScrollBar::CalculatePosition +======================== +*/ +void idMenuWidget_ScrollBar::CalculatePosition( float x, float y ) { + if ( GetSprite() == NULL ) { + return; + } + + if ( y >= yTop && y <= yBot ) { + float range = yBot - yTop; + float val = y - yTop; + + float percent = val / range; + idSWFSpriteInstance * node = GetSprite()->GetScriptObject()->GetNestedSprite( "node" ); + if ( node != NULL ) { + node->SetYPos( percent * range ); + } + + idMenuWidget_DynamicList * const list = dynamic_cast< idMenuWidget_DynamicList * const >( GetParent() ); + if ( list != NULL ) { + float maxScroll = list->GetTotalNumberOfOptions() - list->GetNumVisibleOptions(); + int offset = list->GetViewOffset(); + + float segment = ( maxScroll + 0.5f ) / 100.0f; + int newOffset = ( int )( ( ( percent * segment ) * 100.0f ) ); + + if ( newOffset >= maxScroll ) { + int i = 1; + i = i; + } + + if ( newOffset != offset ) { + + int viewIndex = list->GetViewIndex(); + list->SetViewOffset( newOffset ); + idLib::Printf( "newOffset = %d\n", newOffset ); + if ( viewIndex < newOffset ) { + viewIndex = newOffset; + list->SetViewIndex( viewIndex ); + } else if ( viewIndex > ( newOffset + list->GetNumVisibleOptions() - 1 ) ) { + viewIndex = ( newOffset + list->GetNumVisibleOptions() - 1 ); + list->SetViewIndex( viewIndex ); + } + idLib::Printf( "newView = %d\n", list->GetViewIndex() ); + + int newFocus = viewIndex - newOffset; + if ( newFocus >= 0 ) { + list->SetFocusIndex( newFocus ); + } + list->Update(); + } + } + + idMenuWidget_InfoBox * const infoBox = dynamic_cast< idMenuWidget_InfoBox * const >( GetParent() ); + if ( infoBox != NULL ) { + float maxScroll = infoBox->GetMaxScroll(); + int scroll = infoBox->GetScroll(); + float segment = ( maxScroll + 1.0f ) / 100.0f; + int newScroll = (int)( ( ( percent * segment ) * 100.0f ) ); + if ( newScroll != scroll ) { + infoBox->SetScroll( newScroll ); + } + } + } +} + +/* +======================== +idMenuWidget_ScrollBar::HandleAction +======================== +*/ +bool idMenuWidget_ScrollBar::HandleAction( idWidgetAction & action, const idWidgetEvent & event, idMenuWidget * widget, bool forceHandled ) { + + widgetAction_t actionType = action.GetType(); + + switch ( actionType ) { + case WIDGET_ACTION_SCROLL_DRAG: { + + if ( event.parms.Num() != 3 ) { + return true; + } + + dragging = true; + + float x = event.parms[0].ToFloat(); + float y = event.parms[1].ToFloat(); + bool initial = event.parms[2].ToBool(); + if ( initial ) { + CalcTopAndBottom(); + } + + CalculatePosition( x, y ); + return true; + } + case WIDGET_ACTION_EVENT_DRAG_STOP: { + dragging = false; + return true; + } + } + + return idMenuWidget::HandleAction( action, event, widget, forceHandled ); +} + +/* +======================== +idMenuWidget_Help::ObserveEvent +======================== +*/ +void idMenuWidget_ScrollBar::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + switch ( event.type ) { + case WIDGET_EVENT_SCROLL_UP: + case WIDGET_EVENT_SCROLL_DOWN: + case WIDGET_EVENT_SCROLL_UP_LSTICK: + case WIDGET_EVENT_SCROLL_UP_RSTICK: + case WIDGET_EVENT_SCROLL_DOWN_LSTICK: + case WIDGET_EVENT_SCROLL_DOWN_RSTICK: + case WIDGET_EVENT_FOCUS_ON: { + if ( !dragging ) { + Update(); + } + break; + } + } +} diff --git a/neo/d3xp/menus/MenuWidget_Shell_SaveInfo.cpp b/neo/d3xp/menus/MenuWidget_Shell_SaveInfo.cpp new file mode 100644 index 00000000..ed232014 --- /dev/null +++ b/neo/d3xp/menus/MenuWidget_Shell_SaveInfo.cpp @@ -0,0 +1,161 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idLib/precompiled.h" +#include "../Game_local.h" + +/* +======================== +idMenuWidget_Shell_SaveInfo::Update +======================== +*/ +void idMenuWidget_Shell_SaveInfo::Update() { + + if ( GetSWFObject() == NULL ) { + return; + } + + idSWFScriptObject & root = GetSWFObject()->GetRootObject(); + if ( !BindSprite( root ) || GetSprite() == NULL ) { + return; + } + + const saveGameDetailsList_t & saveGameInfo = session->GetSaveGameManager().GetEnumeratedSavegames(); + + saveGameDetailsList_t sortedSaves = saveGameInfo; + sortedSaves.Sort( idSort_SavesByDate() ); + + for ( int slot = 0; slot < sortedSaves.Num(); ++slot ) { + const idSaveGameDetails & details = sortedSaves[slot]; + if ( forSaveScreen && details.slotName.Icmp( "autosave" ) == 0 ) { + sortedSaves.RemoveIndex( slot ); + slot--; + } + } + + idStr info; + if ( loadIndex >= 0 && sortedSaves.Num() != 0 && loadIndex < sortedSaves.Num() ) { + const idSaveGameDetails & details = sortedSaves[ loadIndex ]; + + info.Append( Sys_TimeStampToStr( details.date ) ); + info.Append( "\n" ); + + // PS3 only strings that use the dict just set + const char * expansionStr = ""; + switch ( details.GetExpansion() ) { + case GAME_D3XP: expansionStr = idLocalization::GetString( "#str_swf_resurrection" ); break; + case GAME_D3LE: expansionStr = idLocalization::GetString( "#str_swf_lost_episodes" ); break; + case GAME_BASE: expansionStr = idLocalization::GetString( "#str_swf_doom3" ); break; + default: expansionStr = idLocalization::GetString( "#str_savegame_title" ); break; + } + + const char * difficultyStr = ""; + switch ( details.GetDifficulty() ) { + case 0: difficultyStr = idLocalization::GetString( "#str_04089" ); break; + case 1: difficultyStr = idLocalization::GetString( "#str_04091" ); break; + case 2: difficultyStr = idLocalization::GetString( "#str_04093" ); break; + case 3: difficultyStr = idLocalization::GetString( "#str_02357" ); break; + } + + idStr summary; + summary.Format( idLocalization::GetString( "#str_swf_save_info_format" ), difficultyStr, Sys_SecToStr( details.GetPlaytime() ), expansionStr ); + + info.Append( summary ); + + if ( details.damaged ) { + info.Append( "\n" ); + info.Append( va( "^1%s^0", idLocalization::GetString( "#str_swf_damaged" ) ) ); + } else if ( details.GetSaveVersion() > BUILD_NUMBER ) { + info.Append( "\n" ); + info.Append( va( "^1%s^0", idLocalization::GetString( "#str_swf_wrong_version" ) ) ); + } + } + + idSWFTextInstance * infoSprite = GetSprite()->GetScriptObject()->GetNestedText( "txtDesc" ); + if ( infoSprite != NULL ) { + infoSprite->SetText( info ); + } + + idSWFSpriteInstance * img = GetSprite()->GetScriptObject()->GetNestedSprite( "img" ); + if ( img != NULL ) { + // TODO_SPARTY: until we have a thumbnail hide the image + img->SetVisible( false ); + } +} + +/* +======================== +idMenuWidget_Help::ObserveEvent +======================== +*/ +void idMenuWidget_Shell_SaveInfo::ObserveEvent( const idMenuWidget & widget, const idWidgetEvent & event ) { + const idMenuWidget_Button * const button = dynamic_cast< const idMenuWidget_Button * >( &widget ); + if ( button == NULL ) { + return; + } + + const idMenuWidget * const listWidget = button->GetParent(); + + if ( listWidget == NULL ) { + return; + } + + switch ( event.type ) { + case WIDGET_EVENT_FOCUS_ON: { + const idMenuWidget_DynamicList * const list = dynamic_cast< const idMenuWidget_DynamicList * const >( listWidget ); + loadIndex = list->GetViewIndex(); + + const saveGameDetailsList_t & detailList = session->GetSaveGameManager().GetEnumeratedSavegames(); + bool hasAutoSave = false; + for ( int i = 0; i < detailList.Num(); ++i ) { + if ( detailList[i].slotName.Icmp( "autosave" ) == 0 ) { + hasAutoSave = true; + } + } + + + if ( forSaveScreen && ( ( detailList.Num() < MAX_SAVEGAMES - 1 ) || ( ( detailList.Num() == MAX_SAVEGAMES - 1 ) && hasAutoSave ) ) ) { + loadIndex -= 1; + } + + Update(); + + idMenuScreen_Shell_Load * loadScreen = dynamic_cast< idMenuScreen_Shell_Load * >( GetParent() ); + if ( loadScreen ) { + loadScreen->UpdateSaveEnumerations(); + } + + idMenuScreen_Shell_Save * saveScreen = dynamic_cast< idMenuScreen_Shell_Save * >( GetParent() ); + if ( saveScreen ) { + saveScreen->UpdateSaveEnumerations(); + } + + break; + } + } +} \ No newline at end of file diff --git a/neo/d3xp/physics/Clip.cpp b/neo/d3xp/physics/Clip.cpp new file mode 100644 index 00000000..b34a4934 --- /dev/null +++ b/neo/d3xp/physics/Clip.cpp @@ -0,0 +1,1756 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#define MAX_SECTOR_DEPTH 12 +#define MAX_SECTORS ((1<<(MAX_SECTOR_DEPTH+1))-1) + +typedef struct clipSector_s { + int axis; // -1 = leaf node + float dist; + struct clipSector_s * children[2]; + struct clipLink_s * clipLinks; +} clipSector_t; + +typedef struct clipLink_s { + idClipModel * clipModel; + struct clipSector_s * sector; + struct clipLink_s * prevInSector; + struct clipLink_s * nextInSector; + struct clipLink_s * nextLink; +} clipLink_t; + +typedef struct trmCache_s { + idTraceModel trm; + int refCount; + float volume; + idVec3 centerOfMass; + idMat3 inertiaTensor; +} trmCache_t; + +idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON ); + +idBlockAlloc clipLinkAllocator; + + +/* +=============================================================== + + idClipModel trace model cache + +=============================================================== +*/ + +static idList traceModelCache; +static idList traceModelCache_Unsaved; +static idHashIndex traceModelHash; +static idHashIndex traceModelHash_Unsaved; +const static int TRACE_MODEL_SAVED = BIT( 16 ); + + +/* +=============== +idClipModel::ClearTraceModelCache +=============== +*/ +void idClipModel::ClearTraceModelCache() { + traceModelCache.DeleteContents( true ); + traceModelCache_Unsaved.DeleteContents( true ); + traceModelHash.Free(); + traceModelHash_Unsaved.Free(); +} + +/* +=============== +idClipModel::TraceModelCacheSize +=============== +*/ +int idClipModel::TraceModelCacheSize() { + return traceModelCache.Num() * sizeof( idTraceModel ); +} + +/* +=============== +idClipModel::AllocTraceModel +=============== +*/ +int idClipModel::AllocTraceModel( const idTraceModel &trm, bool persistantThroughSaves ) { + int i, hashKey, traceModelIndex; + trmCache_t *entry; + + hashKey = GetTraceModelHashKey( trm ); + + if( persistantThroughSaves ) { + // Look Inside the saved list. + for ( i = traceModelHash.First( hashKey ); i >= 0; i = traceModelHash.Next( i ) ) { + if ( traceModelCache[i]->trm == trm ) { + traceModelCache[i]->refCount++; + int flagged_index = i | TRACE_MODEL_SAVED; + return flagged_index; + } + } + } else { + + // Look inside the unsaved list. + for ( i = traceModelHash_Unsaved.First( hashKey ); i >= 0; i = traceModelHash_Unsaved.Next( i ) ) { + if ( traceModelCache_Unsaved[i]->trm == trm ) { + traceModelCache_Unsaved[i]->refCount++; + return i; + } + } + } + + + entry = new (TAG_PHYSICS_CLIP) trmCache_t; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + + if( persistantThroughSaves ) { + traceModelIndex = traceModelCache.Append( entry ); + traceModelHash.Add( hashKey, traceModelIndex ); + + // Set the saved bit. + traceModelIndex |= TRACE_MODEL_SAVED; + + } else { + traceModelIndex = traceModelCache_Unsaved.Append( entry ); + traceModelHash_Unsaved.Add( hashKey, traceModelIndex ); + + // remove the saved bit + traceModelIndex &= ~TRACE_MODEL_SAVED; + + } + + return traceModelIndex; +} + +/* +=============== +idClipModel::FreeTraceModel +=============== +*/ +void idClipModel::FreeTraceModel( int traceModelIndex ) { + + int realTraceModelIndex = traceModelIndex & ~TRACE_MODEL_SAVED; + + // Check which cache we are using. + if( traceModelIndex & TRACE_MODEL_SAVED ) { + + if ( realTraceModelIndex < 0 || realTraceModelIndex >= traceModelCache.Num() || traceModelCache[realTraceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" ); + return; + } + traceModelCache[realTraceModelIndex]->refCount--; + + } else { + + if ( realTraceModelIndex < 0 || realTraceModelIndex >= traceModelCache_Unsaved.Num() || traceModelCache_Unsaved[realTraceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" ); + return; + } + traceModelCache_Unsaved[realTraceModelIndex]->refCount--; + + } +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idTraceModel *idClipModel::GetCachedTraceModel( int traceModelIndex ) { + int realTraceModelIndex = traceModelIndex & ~TRACE_MODEL_SAVED; + + if( traceModelIndex & TRACE_MODEL_SAVED ) { + return &traceModelCache[realTraceModelIndex]->trm; + } else { + return &traceModelCache_Unsaved[realTraceModelIndex]->trm; + } +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +trmCache_t * idClipModel::GetTraceModelEntry( int traceModelIndex ) { + + int realTraceModelIndex = traceModelIndex & ~TRACE_MODEL_SAVED; + + if( traceModelIndex & TRACE_MODEL_SAVED ) { + return traceModelCache[realTraceModelIndex]; + } else { + return traceModelCache_Unsaved[realTraceModelIndex]; + } +} + +/* +=============== +idClipModel::GetTraceModelHashKey +=============== +*/ +int idClipModel::GetTraceModelHashKey( const idTraceModel &trm ) { + const idVec3 &v = trm.bounds[0]; + return ( trm.type << 8 ) ^ ( trm.numVerts << 4 ) ^ ( trm.numEdges << 2 ) ^ ( trm.numPolys << 0 ) ^ idMath::FloatHash( v.ToFloatPtr(), v.GetDimension() ); +} + +/* +=============== +idClipModel::SaveTraceModels +=============== +*/ +void idClipModel::SaveTraceModels( idSaveGame *savefile ) { + int i; + + savefile->WriteInt( traceModelCache.Num() ); + for ( i = 0; i < traceModelCache.Num(); i++ ) { + trmCache_t *entry = traceModelCache[i]; + + savefile->WriteTraceModel( entry->trm ); + savefile->WriteFloat( entry->volume ); + savefile->WriteVec3( entry->centerOfMass ); + savefile->WriteMat3( entry->inertiaTensor ); + } +} + +/* +=============== +idClipModel::RestoreTraceModels +=============== +*/ +void idClipModel::RestoreTraceModels( idRestoreGame *savefile ) { + int i, num; + + ClearTraceModelCache(); + + savefile->ReadInt( num ); + traceModelCache.SetNum( num ); + + for ( i = 0; i < num; i++ ) { + trmCache_t *entry = new (TAG_PHYSICS_CLIP) trmCache_t; + + savefile->ReadTraceModel( entry->trm ); + + savefile->ReadFloat( entry->volume ); + savefile->ReadVec3( entry->centerOfMass ); + savefile->ReadMat3( entry->inertiaTensor ); + entry->refCount = 0; + + traceModelCache[i] = entry; + traceModelHash.Add( GetTraceModelHashKey( entry->trm ), i ); + } +} + + +/* +=============================================================== + + idClipModel + +=============================================================== +*/ + +/* +================ +idClipModel::LoadModel +================ +*/ +bool idClipModel::LoadModel( const char *name ) { + renderModelHandle = -1; + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + traceModelIndex = -1; + } + collisionModelHandle = collisionModelManager->LoadModel( name ); + if ( collisionModelHandle ) { + collisionModelManager->GetModelBounds( collisionModelHandle, bounds ); + collisionModelManager->GetModelContents( collisionModelHandle, contents ); + return true; + } else { + bounds.Zero(); + return false; + } +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const idTraceModel &trm, bool persistantThroughSave ) { + collisionModelHandle = 0; + renderModelHandle = -1; + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + } + traceModelIndex = AllocTraceModel( trm, persistantThroughSave ); + bounds = trm.bounds; +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const int renderModelHandle ) { + collisionModelHandle = 0; + this->renderModelHandle = renderModelHandle; + if ( renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + bounds = renderEntity->bounds; + } + } + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + traceModelIndex = -1; + } +} + +/* +================ +idClipModel::Init +================ +*/ +void idClipModel::Init() { + enabled = true; + entity = NULL; + id = 0; + owner = NULL; + origin.Zero(); + axis.Identity(); + bounds.Zero(); + absBounds.Zero(); + material = NULL; + contents = CONTENTS_BODY; + collisionModelHandle = 0; + renderModelHandle = -1; + traceModelIndex = -1; + clipLinks = NULL; + touchCount = -1; +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel() { + Init(); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const char *name ) { + Init(); + LoadModel( name ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idTraceModel &trm ) { + Init(); + LoadModel( trm, true ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idTraceModel &trm, bool persistantThroughSave ) { + Init(); + LoadModel( trm, persistantThroughSave ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const int renderModelHandle ) { + Init(); + contents = CONTENTS_RENDERMODEL; + LoadModel( renderModelHandle ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idClipModel *model ) { + enabled = model->enabled; + entity = model->entity; + id = model->id; + owner = model->owner; + origin = model->origin; + axis = model->axis; + bounds = model->bounds; + absBounds = model->absBounds; + material = model->material; + contents = model->contents; + collisionModelHandle = model->collisionModelHandle; + traceModelIndex = -1; + if ( model->traceModelIndex != -1 ) { + LoadModel( *GetCachedTraceModel( model->traceModelIndex ) ); + } + renderModelHandle = model->renderModelHandle; + clipLinks = NULL; + touchCount = -1; +} + +/* +================ +idClipModel::~idClipModel +================ +*/ +idClipModel::~idClipModel() { + // make sure the clip model is no longer linked + Unlink(); + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + } +} + +/* +================ +idClipModel::Save +================ +*/ +void idClipModel::Save( idSaveGame *savefile ) const { + savefile->WriteBool( enabled ); + savefile->WriteObject( entity ); + savefile->WriteInt( id ); + savefile->WriteObject( owner ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); + savefile->WriteBounds( bounds ); + savefile->WriteBounds( absBounds ); + savefile->WriteMaterial( material ); + savefile->WriteInt( contents ); + if ( collisionModelHandle >= 0 ) { + savefile->WriteString( collisionModelManager->GetModelName( collisionModelHandle ) ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteInt( traceModelIndex ); + savefile->WriteInt( renderModelHandle ); + savefile->WriteBool( clipLinks != NULL ); + savefile->WriteInt( touchCount ); +} + +/* +================ +idClipModel::Restore +================ +*/ +void idClipModel::Restore( idRestoreGame *savefile ) { + idStr collisionModelName; + bool linked; + + savefile->ReadBool( enabled ); + savefile->ReadObject( reinterpret_cast( entity ) ); + savefile->ReadInt( id ); + savefile->ReadObject( reinterpret_cast( owner ) ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); + savefile->ReadBounds( bounds ); + savefile->ReadBounds( absBounds ); + savefile->ReadMaterial( material ); + savefile->ReadInt( contents ); + savefile->ReadString( collisionModelName ); + if ( collisionModelName.Length() ) { + collisionModelHandle = collisionModelManager->LoadModel( collisionModelName ); + } else { + collisionModelHandle = -1; + } + savefile->ReadInt( traceModelIndex ); + if ( traceModelIndex >= 0 ) { + int realIndex = traceModelIndex & ~TRACE_MODEL_SAVED; + traceModelCache[realIndex]->refCount++; + } + savefile->ReadInt( renderModelHandle ); + savefile->ReadBool( linked ); + savefile->ReadInt( touchCount ); + + // the render model will be set when the clip model is linked + renderModelHandle = -1; + clipLinks = NULL; + touchCount = -1; + + if ( linked ) { + Link( gameLocal.clip, entity, id, origin, axis, renderModelHandle ); + } +} + +/* +================ +idClipModel::SetPosition +================ +*/ +void idClipModel::SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ) { + if ( clipLinks ) { + Unlink(); // unlink from old position + } + origin = newOrigin; + axis = newAxis; +} + +/* +================ +idClipModel::Handle +================ +*/ +cmHandle_t idClipModel::Handle() const { + assert( renderModelHandle == -1 ); + if ( collisionModelHandle ) { + return collisionModelHandle; + } else if ( traceModelIndex != -1 ) { + return collisionModelManager->SetupTrmModel( *GetCachedTraceModel( traceModelIndex ), material ); + } else { + // this happens in multiplayer on the combat models + gameLocal.Warning( "idClipModel::Handle: clip model %d on '%s' (%x) is not a collision or trace model", id, entity->name.c_str(), entity->entityNumber ); + return 0; + } +} + +/* +================ +idClipModel::GetMassProperties +================ +*/ +void idClipModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + if ( traceModelIndex == -1 ) { + gameLocal.Error( "idClipModel::GetMassProperties: clip model %d on '%s' is not a trace model\n", id, entity->name.c_str() ); + } + + trmCache_t *entry = GetTraceModelEntry( traceModelIndex ); //traceModelCache[traceModelIndex]; + mass = entry->volume * density; + centerOfMass = entry->centerOfMass; + inertiaTensor = density * entry->inertiaTensor; +} + +/* +=============== +idClipModel::Unlink +=============== +*/ +void idClipModel::Unlink() { + clipLink_t *link; + + for ( link = clipLinks; link; link = clipLinks ) { + clipLinks = link->nextLink; + if ( link->prevInSector ) { + link->prevInSector->nextInSector = link->nextInSector; + } else { + link->sector->clipLinks = link->nextInSector; + } + if ( link->nextInSector ) { + link->nextInSector->prevInSector = link->prevInSector; + } + clipLinkAllocator.Free( link ); + } +} + +/* +=============== +idClipModel::Link_r +=============== +*/ +void idClipModel::Link_r( struct clipSector_s *node ) { + clipLink_t *link; + + while( node->axis != -1 ) { + if ( absBounds[0][node->axis] > node->dist ) { + node = node->children[0]; + } else if ( absBounds[1][node->axis] < node->dist ) { + node = node->children[1]; + } else { + Link_r( node->children[0] ); + node = node->children[1]; + } + } + + link = clipLinkAllocator.Alloc(); + link->clipModel = this; + link->sector = node; + link->nextInSector = node->clipLinks; + link->prevInSector = NULL; + if ( node->clipLinks ) { + node->clipLinks->prevInSector = link; + } + node->clipLinks = link; + link->nextLink = clipLinks; + clipLinks = link; +} + +/* +=============== +idClipModel::Link +=============== +*/ +void idClipModel::Link( idClip &clp ) { + + assert( idClipModel::entity ); + if ( !idClipModel::entity ) { + return; + } + + if ( clipLinks ) { + Unlink(); // unlink from old position + } + + if ( bounds.IsCleared() ) { + return; + } + + // set the abs box + if ( axis.IsRotated() ) { + // expand for rotation + absBounds.FromTransformedBounds( bounds, origin, axis ); + } else { + // normal + absBounds[0] = bounds[0] + origin; + absBounds[1] = bounds[1] + origin; + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + absBounds[0] -= vec3_boxEpsilon; + absBounds[1] += vec3_boxEpsilon; + + Link_r( clp.clipSectors ); +} + +/* +=============== +idClipModel::Link +=============== +*/ +void idClipModel::Link( idClip &clp, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle ) { + + this->entity = ent; + this->id = newId; + this->origin = newOrigin; + this->axis = newAxis; + if ( renderModelHandle != -1 ) { + this->renderModelHandle = renderModelHandle; + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + this->bounds = renderEntity->bounds; + } + } + this->Link( clp ); +} + +/* +============ +idClipModel::CheckModel +============ +*/ +cmHandle_t idClipModel::CheckModel( const char *name ) { + return collisionModelManager->LoadModel( name ); +} + + +/* +=============================================================== + + idClip + +=============================================================== +*/ + +/* +=============== +idClip::idClip +=============== +*/ +idClip::idClip() { + numClipSectors = 0; + clipSectors = NULL; + worldBounds.Zero(); + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::CreateClipSectors_r + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +clipSector_t *idClip::CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ) { + int i; + clipSector_t *anode; + idVec3 size; + idBounds front, back; + + anode = &clipSectors[idClip::numClipSectors]; + idClip::numClipSectors++; + + if ( depth == MAX_SECTOR_DEPTH ) { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[1][i] - bounds[0][i] > maxSector[i] ) { + maxSector[i] = bounds[1][i] - bounds[0][i]; + } + } + return anode; + } + + size = bounds[1] - bounds[0]; + if ( size[0] >= size[1] && size[0] >= size[2] ) { + anode->axis = 0; + } else if ( size[1] >= size[0] && size[1] >= size[2] ) { + anode->axis = 1; + } else { + anode->axis = 2; + } + + anode->dist = 0.5f * ( bounds[1][anode->axis] + bounds[0][anode->axis] ); + + front = bounds; + back = bounds; + + front[0][anode->axis] = back[1][anode->axis] = anode->dist; + + anode->children[0] = CreateClipSectors_r( depth+1, front, maxSector ); + anode->children[1] = CreateClipSectors_r( depth+1, back, maxSector ); + + return anode; +} + +/* +=============== +idClip::Init +=============== +*/ +void idClip::Init() { + cmHandle_t h; + idVec3 size, maxSector = vec3_origin; + + // clear clip sectors + clipSectors = new (TAG_PHYSICS_CLIP) clipSector_t[MAX_SECTORS]; + memset( clipSectors, 0, MAX_SECTORS * sizeof( clipSector_t ) ); + numClipSectors = 0; + touchCount = -1; + // get world map bounds + h = collisionModelManager->LoadModel( "worldMap" ); + collisionModelManager->GetModelBounds( h, worldBounds ); + // create world sectors + CreateClipSectors_r( 0, worldBounds, maxSector ); + + size = worldBounds[1] - worldBounds[0]; + gameLocal.Printf( "map bounds are (%1.1f, %1.1f, %1.1f)\n", size[0], size[1], size[2] ); + gameLocal.Printf( "max clip sector is (%1.1f, %1.1f, %1.1f)\n", maxSector[0], maxSector[1], maxSector[2] ); + + // initialize a default clip model + defaultClipModel.LoadModel( idTraceModel( idBounds( idVec3( 0, 0, 0 ) ).Expand( 8 ) ) ); + + // set counters to zero + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::Shutdown +=============== +*/ +void idClip::Shutdown() { + delete[] clipSectors; + clipSectors = NULL; + + // free the trace model used for the temporaryClipModel + if ( temporaryClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( temporaryClipModel.traceModelIndex ); + temporaryClipModel.traceModelIndex = -1; + } + + // free the trace model used for the defaultClipModel + if ( defaultClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( defaultClipModel.traceModelIndex ); + defaultClipModel.traceModelIndex = -1; + } + + clipLinkAllocator.Shutdown(); +} + +/* +==================== +idClip::ClipModelsTouchingBounds_r +==================== +*/ +typedef struct listParms_s { + idBounds bounds; + int contentMask; + idClipModel ** list; + int count; + int maxCount; +} listParms_t; + +void idClip::ClipModelsTouchingBounds_r( const struct clipSector_s *node, listParms_t &parms ) const { + + while( node->axis != -1 ) { + if ( parms.bounds[0][node->axis] > node->dist ) { + node = node->children[0]; + } else if ( parms.bounds[1][node->axis] < node->dist ) { + node = node->children[1]; + } else { + ClipModelsTouchingBounds_r( node->children[0], parms ); + node = node->children[1]; + } + } + + for ( clipLink_t *link = node->clipLinks; link; link = link->nextInSector ) { + idClipModel *check = link->clipModel; + + // if the clip model is enabled + if ( !check->enabled ) { + continue; + } + + // avoid duplicates in the list + if ( check->touchCount == touchCount ) { + continue; + } + + // if the clip model does not have any contents we are looking for + if ( !( check->contents & parms.contentMask ) ) { + continue; + } + + // if the bounds really do overlap + if ( check->absBounds[0][0] > parms.bounds[1][0] || + check->absBounds[1][0] < parms.bounds[0][0] || + check->absBounds[0][1] > parms.bounds[1][1] || + check->absBounds[1][1] < parms.bounds[0][1] || + check->absBounds[0][2] > parms.bounds[1][2] || + check->absBounds[1][2] < parms.bounds[0][2] ) { + continue; + } + + if ( parms.count >= parms.maxCount ) { + gameLocal.Warning( "idClip::ClipModelsTouchingBounds_r: max count" ); + return; + } + + check->touchCount = touchCount; + parms.list[parms.count] = check; + parms.count++; + } +} + +/* +================ +idClip::ClipModelsTouchingBounds +================ +*/ +int idClip::ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + listParms_t parms; + + if ( bounds[0][0] > bounds[1][0] || + bounds[0][1] > bounds[1][1] || + bounds[0][2] > bounds[1][2] ) { + // we should not go through the tree for degenerate or backwards bounds + assert( false ); + return 0; + } + + parms.bounds[0] = bounds[0] - vec3_boxEpsilon; + parms.bounds[1] = bounds[1] + vec3_boxEpsilon; + parms.contentMask = contentMask; + parms.list = clipModelList; + parms.count = 0; + parms.maxCount = maxCount; + + touchCount++; + ClipModelsTouchingBounds_r( clipSectors, parms ); + + return parms.count; +} + +/* +================ +idClip::EntitiesTouchingBounds +================ +*/ +int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, entCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + entCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < entCount; j++ ) { + if ( entityList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= entCount ) { + if ( entCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return entCount; + } + entityList[entCount] = clipModelList[i]->entity; + entCount++; + } + } + + return entCount; +} + +/* +==================== +idClip::GetTraceClipModels + + an ent will be excluded from testing if: + cm->entity == passEntity ( don't clip against the pass entity ) + cm->entity == passOwner ( missiles don't clip with owner ) + cm->owner == passEntity ( don't interact with your own missiles ) + cm->owner == passOwner ( don't interact with other missiles from same owner ) +==================== +*/ +int idClip::GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList ) const { + int i, num; + idClipModel *cm; + idEntity *passOwner; + + num = ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + + if ( !passEntity ) { + return num; + } + + if ( passEntity->GetPhysics()->GetNumClipModels() > 0 ) { + passOwner = passEntity->GetPhysics()->GetClipModel()->GetOwner(); + } else { + passOwner = NULL; + } + + for ( i = 0; i < num; i++ ) { + + cm = clipModelList[i]; + + // check if we should ignore this entity + if ( cm->entity == passEntity ) { + clipModelList[i] = NULL; // don't clip against the pass entity + } else if ( cm->entity == passOwner ) { + clipModelList[i] = NULL; // missiles don't clip with their owner + } else if ( cm->owner ) { + if ( cm->owner == passEntity ) { + clipModelList[i] = NULL; // don't clip against own missiles + } else if ( cm->owner == passOwner ) { + clipModelList[i] = NULL; // don't clip against other missiles from same owner + } + } + } + + return num; +} + +/* +============ +idClip::TraceRenderModel +============ +*/ +void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const { + trace.fraction = 1.0f; + + // if the trace is passing through the bounds + if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) { + modelTrace_t modelTrace; + + // test with exact render model and modify trace_t structure accordingly + if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) { + trace.fraction = modelTrace.fraction; + trace.endAxis = axis; + trace.endpos = modelTrace.point; + trace.c.normal = modelTrace.normal; + trace.c.dist = modelTrace.point * modelTrace.normal; + trace.c.point = modelTrace.point; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.modelFeature = 0; + trace.c.trmFeature = 0; + trace.c.contents = modelTrace.material->GetContentFlags(); + trace.c.material = modelTrace.material; + // NOTE: trace.c.id will be the joint number + touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber ); + } + } +} + +/* +============ +idClip::TraceModelForClipModel +============ +*/ +const idTraceModel *idClip::TraceModelForClipModel( const idClipModel *mdl ) const { + if ( !mdl ) { + return NULL; + } else { + if ( !mdl->IsTraceModel() ) { + if ( mdl->GetEntity() ) { + gameLocal.Error( "TraceModelForClipModel: clip model %d on '%s' is not a trace model\n", mdl->GetId(), mdl->GetEntity()->name.c_str() ); + } else { + gameLocal.Error( "TraceModelForClipModel: clip model %d is not a trace model\n", mdl->GetId() ); + } + } + return idClipModel::GetCachedTraceModel( mdl->traceModelIndex ); + } +} + +/* +============ +idClip::TestHugeTranslation +============ +*/ +ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { + if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { +#ifndef CTF + // May be important: This occurs in CTF when a player connects and spawns + // in the PVS of a player that has a flag that is spawning the idMoveableItem + // "nuggets". The error seems benign and the assert was getting in the way + // of testing. + assert( 0 ); +#endif + + results.fraction = 0.0f; + results.endpos = start; + results.endAxis = trmAxis; + memset( &results.c, 0, sizeof( results.c ) ); + results.c.point = start; + + if ( mdl->GetEntity() ) { + gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + } else { + gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + } + return true; + } + return false; +} + +/* +============ +idClip::TranslationEntities +============ +*/ +void idClip::TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return; + } + + trm = TraceModelForClipModel( mdl ); + + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + + if ( !trm ) { + traceBounds.FromPointTranslation( start, end - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, end - start ); + radius = trm->bounds.GetRadius(); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->Handle(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } +} + +/* +============ +idClip::Translation +============ +*/ +bool idClip::Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return true; + } + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + } + + if ( !trm ) { + traceBounds.FromPointTranslation( start, results.endpos - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, results.endpos - start ); + radius = trm->bounds.GetRadius(); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->Handle(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Rotation +============ +*/ +bool idClip::Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + trace_t trace; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( !trm ) { + traceBounds.FromPointRotation( start, rotation ); + } else { + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask, + touch->Handle(), touch->origin, touch->axis ); + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Motion +============ +*/ +bool idClip::Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idVec3 dir, endPosition; + idBounds traceBounds; + float radius; + trace_t translationalTrace, rotationalTrace, trace; + idRotation endRotation; + const idTraceModel *trm; + + assert( rotation.GetOrigin() == start ); + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return true; + } + + if ( mdl != NULL && rotation.GetAngle() != 0.0f && rotation.GetVec() != vec3_origin ) { + // if no translation + if ( start == end ) { + // pure rotation + return Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + } else if ( start != end ) { + // pure translation + return Translation( results, start, end, mdl, trmAxis, contentMask, passEntity ); + } else { + // no motion + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis; + return false; + } + + trm = TraceModelForClipModel( mdl ); + + radius = trm->bounds.GetRadius(); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // translational collision with world + idClip::numTranslations++; + collisionModelManager->Translation( &translationalTrace, start, end, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default ); + translationalTrace.c.entityNum = translationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &translationalTrace, 0, sizeof( translationalTrace ) ); + translationalTrace.fraction = 1.0f; + translationalTrace.endpos = end; + translationalTrace.endAxis = trmAxis; + } + + if ( translationalTrace.fraction != 0.0f ) { + + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + dir = translationalTrace.endpos - start; + for ( i = 0; i < 3; i++ ) { + if ( dir[i] < 0.0f ) { + traceBounds[0][i] += dir[i]; + } + else { + traceBounds[1][i] += dir[i]; + } + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->Handle(), touch->origin, touch->axis ); + } + + if ( trace.fraction < translationalTrace.fraction ) { + translationalTrace = trace; + translationalTrace.c.entityNum = touch->entity->entityNumber; + translationalTrace.c.id = touch->id; + if ( translationalTrace.fraction == 0.0f ) { + break; + } + } + } + } else { + num = -1; + } + + endPosition = translationalTrace.endpos; + endRotation = rotation; + endRotation.SetOrigin( endPosition ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // rotational collision with world + idClip::numRotations++; + collisionModelManager->Rotation( &rotationalTrace, endPosition, endRotation, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default ); + rotationalTrace.c.entityNum = rotationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &rotationalTrace, 0, sizeof( rotationalTrace ) ); + rotationalTrace.fraction = 1.0f; + rotationalTrace.endpos = endPosition; + rotationalTrace.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( rotationalTrace.fraction != 0.0f ) { + + if ( num == -1 ) { + traceBounds.FromBoundsRotation( trm->bounds, endPosition, trmAxis, endRotation ); + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + } + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision detection with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, endPosition, endRotation, trm, trmAxis, contentMask, + touch->Handle(), touch->origin, touch->axis ); + + if ( trace.fraction < rotationalTrace.fraction ) { + rotationalTrace = trace; + rotationalTrace.c.entityNum = touch->entity->entityNumber; + rotationalTrace.c.id = touch->id; + if ( rotationalTrace.fraction == 0.0f ) { + break; + } + } + } + } + + if ( rotationalTrace.fraction < 1.0f ) { + results = rotationalTrace; + } else { + results = translationalTrace; + results.endAxis = rotationalTrace.endAxis; + } + + results.fraction = Max( translationalTrace.fraction, rotationalTrace.fraction ); + + return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f ); +} + +/* +============ +idClip::Contacts +============ +*/ +int idClip::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, j, num, n, numContacts; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContacts++; + numContacts = collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default ); + } else { + numContacts = 0; + } + + for ( i = 0; i < numContacts; i++ ) { + contacts[i].entityNum = ENTITYNUM_WORLD; + contacts[i].id = 0; + } + + if ( numContacts >= maxContacts ) { + return numContacts; + } + + if ( !trm ) { + traceBounds = idBounds( start ).Expand( depth ); + } else { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + traceBounds.ExpandSelf( depth ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contacts with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numContacts++; + n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts, + start, dir, depth, trm, trmAxis, contentMask, + touch->Handle(), touch->origin, touch->axis ); + + for ( j = 0; j < n; j++ ) { + contacts[numContacts].entityNum = touch->entity->entityNumber; + contacts[numContacts].id = touch->id; + numContacts++; + } + + if ( numContacts >= maxContacts ) { + break; + } + } + + return numContacts; +} + +/* +============ +idClip::Contents +============ +*/ +int idClip::Contents( const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num, contents; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContents++; + contents = collisionModelManager->Contents( start, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default ); + } else { + contents = 0; + } + + if ( !trm ) { + traceBounds[0] = start; + traceBounds[1] = start; + } else if ( trmAxis.IsRotated() ) { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + } else { + traceBounds[0] = trm->bounds[0] + start; + traceBounds[1] = trm->bounds[1] + start; + } + + num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contents test with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + // if the entity does not have any contents we are looking for + if ( ( touch->contents & contentMask ) == 0 ) { + continue; + } + + // if the entity has no new contents flags + if ( ( touch->contents & contents ) == touch->contents ) { + continue; + } + + idClip::numContents++; + if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, touch->Handle(), touch->origin, touch->axis ) ) { + contents |= ( touch->contents & contentMask ); + } + } + + return contents; +} + +/* +============ +idClip::TranslationModel +============ +*/ +void idClip::TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::RotationModel +============ +*/ +void idClip::RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContactsModel +============ +*/ +int idClip::ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContacts++; + return collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContentsModel +============ +*/ +int idClip::ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContents++; + return collisionModelManager->Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::GetModelContactFeature +============ +*/ +bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + int i; + cmHandle_t handle; + idVec3 start, end; + + handle = -1; + winding.Clear(); + + if ( clipModel == NULL ) { + handle = 0; + } else { + if ( clipModel->renderModelHandle != -1 ) { + winding += contact.point; + return true; + } else if ( clipModel->traceModelIndex != -1 ) { + handle = collisionModelManager->SetupTrmModel( *idClipModel::GetCachedTraceModel( clipModel->traceModelIndex ), clipModel->material ); + } else { + handle = clipModel->collisionModelHandle; + } + } + + // if contact with a collision model + if ( handle != -1 ) { + switch( contact.type ) { + case CONTACT_EDGE: { + // the model contact feature is a collision model edge + collisionModelManager->GetModelEdge( handle, contact.modelFeature, start, end ); + winding += start; + winding += end; + break; + } + case CONTACT_MODELVERTEX: { + // the model contact feature is a collision model vertex + collisionModelManager->GetModelVertex( handle, contact.modelFeature, start ); + winding += start; + break; + } + case CONTACT_TRMVERTEX: { + // the model contact feature is a collision model polygon + collisionModelManager->GetModelPolygon( handle, contact.modelFeature, winding ); + break; + } + } + } + + // transform the winding to world space + if ( clipModel ) { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() *= clipModel->axis; + winding[i].ToVec3() += clipModel->origin; + } + } + + return true; +} + +/* +============ +idClip::PrintStatistics +============ +*/ +void idClip::PrintStatistics() { + gameLocal.Printf( "t = %-3d, r = %-3d, m = %-3d, render = %-3d, contents = %-3d, contacts = %-3d\n", + numTranslations, numRotations, numMotions, numRenderModelTraces, numContents, numContacts ); + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +============ +idClip::DrawClipModels +============ +*/ +void idClip::DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity ) { + int i, num; + idBounds bounds; + idClipModel *clipModelList[MAX_GENTITIES]; + idClipModel *clipModel; + + bounds = idBounds( eye ).Expand( radius ); + + num = idClip::ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < num; i++ ) { + clipModel = clipModelList[i]; + if ( clipModel->GetEntity() == passEntity ) { + continue; + } + if ( clipModel->renderModelHandle != -1 ) { + gameRenderWorld->DebugBounds( colorCyan, clipModel->GetAbsBounds() ); + } else { + collisionModelManager->DrawModel( clipModel->Handle(), clipModel->GetOrigin(), clipModel->GetAxis(), eye, radius ); + } + } +} + +/* +============ +idClip::DrawModelContactFeature +============ +*/ +bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const { + int i; + idMat3 axis; + idFixedWinding winding; + + if ( !GetModelContactFeature( contact, clipModel, winding ) ) { + return false; + } + + axis = contact.normal.ToMat3(); + + if ( winding.GetNumPoints() == 1 ) { + gameRenderWorld->DebugLine( colorCyan, winding[0].ToVec3(), winding[0].ToVec3() + 2.0f * axis[0], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[1], winding[0].ToVec3() + 1.0f * axis[1], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[2], winding[0].ToVec3() + 1.0f * axis[2], lifetime ); + } else { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + gameRenderWorld->DebugLine( colorCyan, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3(), lifetime ); + } + } + + axis[0] = -axis[0]; + axis[2] = -axis[2]; + gameRenderWorld->DrawText( contact.material->GetName(), winding.GetCenter() - 4.0f * axis[2], 0.1f, colorWhite, axis, 1, 5000 ); + + return true; +} diff --git a/neo/d3xp/physics/Clip.h b/neo/d3xp/physics/Clip.h new file mode 100644 index 00000000..9098885e --- /dev/null +++ b/neo/d3xp/physics/Clip.h @@ -0,0 +1,353 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __CLIP_H__ +#define __CLIP_H__ + +/* +=============================================================================== + + Handles collision detection with the world and between physics objects. + +=============================================================================== +*/ + +#define CLIPMODEL_ID_TO_JOINT_HANDLE( id ) ( ( id ) >= 0 ? INVALID_JOINT : ((jointHandle_t) ( -1 - id )) ) +#define JOINT_HANDLE_TO_CLIPMODEL_ID( id ) ( -1 - id ) + +class idClip; +class idClipModel; +class idEntity; + +//=============================================================== +// +// idClipModel +// +//=============================================================== + +class idClipModel { + + friend class idClip; + +public: + idClipModel(); + explicit idClipModel( const char *name ); + explicit idClipModel( const idTraceModel &trm ); + explicit idClipModel( const idTraceModel &trm, bool persistantThroughSave ); + explicit idClipModel( const int renderModelHandle ); + explicit idClipModel( const idClipModel *model ); + ~idClipModel(); + + bool LoadModel( const char *name ); + void LoadModel( const idTraceModel &trm, bool persistantThroughSave = true ); + void LoadModel( const int renderModelHandle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Link( idClip &clp ); // must have been linked with an entity and id before + void Link( idClip &clp, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); + void Unlink(); // unlink from sectors + void SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ); // unlinks the clip model + void Translate( const idVec3 &translation ); // unlinks the clip model + void Rotate( const idRotation &rotation ); // unlinks the clip model + void Enable(); // enable for clipping + void Disable(); // keep linked but disable for clipping + void SetMaterial( const idMaterial *m ); + const idMaterial * GetMaterial() const; + void SetContents( int newContents ); // override contents + int GetContents() const; + void SetEntity( idEntity *newEntity ); + idEntity * GetEntity() const; + void SetId( int newId ); + int GetId() const; + void SetOwner( idEntity *newOwner ); + idEntity * GetOwner() const; + const idBounds & GetBounds() const; + const idBounds & GetAbsBounds() const; + const idVec3 & GetOrigin() const; + const idMat3 & GetAxis() const; + bool IsTraceModel() const; // returns true if this is a trace model + bool IsRenderModel() const; // returns true if this is a render model + bool IsLinked() const; // returns true if the clip model is linked + bool IsEnabled() const; // returns true if enabled for collision detection + bool IsEqual( const idTraceModel &trm ) const; + cmHandle_t Handle() const; // returns handle used to collide vs this model + const idTraceModel * GetTraceModel() const; + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + + static cmHandle_t CheckModel( const char *name ); + static void ClearTraceModelCache(); + static int TraceModelCacheSize(); + + static void SaveTraceModels( idSaveGame *savefile ); + static void RestoreTraceModels( idRestoreGame *savefile ); + +private: + bool enabled; // true if this clip model is used for clipping + idEntity * entity; // entity using this clip model + int id; // id for entities that use multiple clip models + idEntity * owner; // owner of the entity that owns this clip model + idVec3 origin; // origin of clip model + idMat3 axis; // orientation of clip model + idBounds bounds; // bounds + idBounds absBounds; // absolute bounds + const idMaterial * material; // material for trace models + int contents; // all contents ored together + cmHandle_t collisionModelHandle; // handle to collision model + int traceModelIndex; // trace model used for collision detection + int renderModelHandle; // render model def handle + + struct clipLink_s * clipLinks; // links into sectors + int touchCount; + + void Init(); // initialize + void Link_r( struct clipSector_s *node ); + + static int AllocTraceModel( const idTraceModel &trm, bool persistantThroughSaves = true ); + static void FreeTraceModel( int traceModelIndex ); + static idTraceModel * GetCachedTraceModel( int traceModelIndex ); + static int GetTraceModelHashKey( const idTraceModel &trm ); + static struct trmCache_s * GetTraceModelEntry( int traceModelIndex ); +}; + + +ID_INLINE void idClipModel::Translate( const idVec3 &translation ) { + Unlink(); + origin += translation; +} + +ID_INLINE void idClipModel::Rotate( const idRotation &rotation ) { + Unlink(); + origin *= rotation; + axis *= rotation.ToMat3(); +} + +ID_INLINE void idClipModel::Enable() { + enabled = true; +} + +ID_INLINE void idClipModel::Disable() { + enabled = false; +} + +ID_INLINE void idClipModel::SetMaterial( const idMaterial *m ) { + material = m; +} + +ID_INLINE const idMaterial * idClipModel::GetMaterial() const { + return material; +} + +ID_INLINE void idClipModel::SetContents( int newContents ) { + contents = newContents; +} + +ID_INLINE int idClipModel::GetContents() const { + return contents; +} + +ID_INLINE void idClipModel::SetEntity( idEntity *newEntity ) { + entity = newEntity; +} + +ID_INLINE idEntity *idClipModel::GetEntity() const { + return entity; +} + +ID_INLINE void idClipModel::SetId( int newId ) { + id = newId; +} + +ID_INLINE int idClipModel::GetId() const { + return id; +} + +ID_INLINE void idClipModel::SetOwner( idEntity *newOwner ) { + owner = newOwner; +} + +ID_INLINE idEntity *idClipModel::GetOwner() const { + return owner; +} + +ID_INLINE const idBounds &idClipModel::GetBounds() const { + return bounds; +} + +ID_INLINE const idBounds &idClipModel::GetAbsBounds() const { + return absBounds; +} + +ID_INLINE const idVec3 &idClipModel::GetOrigin() const { + return origin; +} + +ID_INLINE const idMat3 &idClipModel::GetAxis() const { + return axis; +} + +ID_INLINE bool idClipModel::IsRenderModel() const { + return ( renderModelHandle != -1 ); +} + +ID_INLINE bool idClipModel::IsTraceModel() const { + return ( traceModelIndex != -1 ); +} + +ID_INLINE bool idClipModel::IsLinked() const { + return ( clipLinks != NULL ); +} + +ID_INLINE bool idClipModel::IsEnabled() const { + return enabled; +} + +ID_INLINE bool idClipModel::IsEqual( const idTraceModel &trm ) const { + return ( traceModelIndex != -1 && *GetCachedTraceModel( traceModelIndex ) == trm ); +} + +ID_INLINE const idTraceModel *idClipModel::GetTraceModel() const { + if ( !IsTraceModel() ) { + return NULL; + } + return idClipModel::GetCachedTraceModel( traceModelIndex ); +} + + +//=============================================================== +// +// idClip +// +//=============================================================== + +class idClip { + + friend class idClipModel; + +public: + idClip(); + + void Init(); + void Shutdown(); + + // clip versus the rest of the world + bool Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contents( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + + // special case translations versus the rest of the world + bool TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, + int contentMask, const idEntity *passEntity ); + bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, + int contentMask, const idEntity *passEntity ); + + // clip versus a specific model + void TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + + // clip versus all entities but not the world + void TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + + // get a contact feature + bool GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + + const idBounds & GetWorldBounds() const; + idClipModel * DefaultClipModel(); + + // stats and debug drawing + void PrintStatistics(); + void DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity ); + bool DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const; + +private: + int numClipSectors; + struct clipSector_s * clipSectors; + idBounds worldBounds; + idClipModel temporaryClipModel; + idClipModel defaultClipModel; + mutable int touchCount; + // statistics + int numTranslations; + int numRotations; + int numMotions; + int numRenderModelTraces; + int numContents; + int numContacts; + +private: + struct clipSector_s * CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ); + void ClipModelsTouchingBounds_r( const struct clipSector_s *node, struct listParms_s &parms ) const; + const idTraceModel * TraceModelForClipModel( const idClipModel *mdl ) const; + int GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList ) const; + void TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const; +}; + + +ID_INLINE bool idClip::TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + Translation( results, start, end, NULL, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE bool idClip::TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + temporaryClipModel.LoadModel( idTraceModel( bounds ) ); + Translation( results, start, end, &temporaryClipModel, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE const idBounds & idClip::GetWorldBounds() const { + return worldBounds; +} + +ID_INLINE idClipModel *idClip::DefaultClipModel() { + return &defaultClipModel; +} + +#endif /* !__CLIP_H__ */ diff --git a/neo/d3xp/physics/Force.cpp b/neo/d3xp/physics/Force.cpp new file mode 100644 index 00000000..d3809be6 --- /dev/null +++ b/neo/d3xp/physics/Force.cpp @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idClass, idForce ) +END_CLASS + +idList idForce::forceList; + +/* +================ +idForce::idForce +================ +*/ +idForce::idForce() { + forceList.Append( this ); +} + +/* +================ +idForce::~idForce +================ +*/ +idForce::~idForce() { + forceList.Remove( this ); +} + +/* +================ +idForce::DeletePhysics +================ +*/ +void idForce::DeletePhysics( const idPhysics *phys ) { + int i; + + for ( i = 0; i < forceList.Num(); i++ ) { + forceList[i]->RemovePhysics( phys ); + } +} + +/* +================ +idForce::ClearForceList +================ +*/ +void idForce::ClearForceList() { + forceList.Clear(); +} + +/* +================ +idForce::Evaluate +================ +*/ +void idForce::Evaluate( int time ) { +} + +/* +================ +idForce::RemovePhysics +================ +*/ +void idForce::RemovePhysics( const idPhysics *phys ) { +} diff --git a/neo/d3xp/physics/Force.h b/neo/d3xp/physics/Force.h new file mode 100644 index 00000000..3d6d2d43 --- /dev/null +++ b/neo/d3xp/physics/Force.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FORCE_H__ +#define __FORCE_H__ + +/* +=============================================================================== + + Force base class + + A force object applies a force to a physics object. + +=============================================================================== +*/ + +class idEntity; +class idPhysics; + +class idForce : public idClass { + +public: + CLASS_PROTOTYPE( idForce ); + + idForce(); + virtual ~idForce(); + static void DeletePhysics( const idPhysics *phys ); + static void ClearForceList(); + +public: // common force interface + // evalulate the force up to the given time + virtual void Evaluate( int time ); + // removes any pointers to the physics object + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + static idList forceList; +}; + +#endif /* !__FORCE_H__ */ diff --git a/neo/d3xp/physics/Force_Constant.cpp b/neo/d3xp/physics/Force_Constant.cpp new file mode 100644 index 00000000..9de391d7 --- /dev/null +++ b/neo/d3xp/physics/Force_Constant.cpp @@ -0,0 +1,135 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Constant ) +END_CLASS + +/* +================ +idForce_Constant::idForce_Constant +================ +*/ +idForce_Constant::idForce_Constant() { + force = vec3_zero; + physics = NULL; + id = 0; + point = vec3_zero; +} + +/* +================ +idForce_Constant::~idForce_Constant +================ +*/ +idForce_Constant::~idForce_Constant() { +} + +/* +================ +idForce_Constant::Save +================ +*/ +void idForce_Constant::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( force ); + savefile->WriteInt( id ); + savefile->WriteVec3( point ); +} + +/* +================ +idForce_Constant::Restore +================ +*/ +void idForce_Constant::Restore( idRestoreGame *savefile ) { + // Owner needs to call SetPhysics!! + savefile->ReadVec3( force ); + savefile->ReadInt( id ); + savefile->ReadVec3( point ); +} + +/* +================ +idForce_Constant::SetPosition +================ +*/ +void idForce_Constant::SetPosition( idPhysics *physics, int id, const idVec3 &point ) { + this->physics = physics; + this->id = id; + this->point = point; +} + +/* +================ +idForce_Constant::SetForce +================ +*/ +void idForce_Constant::SetForce( const idVec3 &force ) { + this->force = force; +} + +/* +================ +idForce_Constant::SetPhysics +================ +*/ +void idForce_Constant::SetPhysics( idPhysics *physics ) { + this->physics = physics; +} + +/* +================ +idForce_Constant::Evaluate +================ +*/ +void idForce_Constant::Evaluate( int time ) { + idVec3 p; + + if ( !physics ) { + return; + } + + p = physics->GetOrigin( id ) + point * physics->GetAxis( id ); + + physics->AddForce( id, p, force ); +} + +/* +================ +idForce_Constant::RemovePhysics +================ +*/ +void idForce_Constant::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/neo/d3xp/physics/Force_Constant.h b/neo/d3xp/physics/Force_Constant.h new file mode 100644 index 00000000..f5929f8d --- /dev/null +++ b/neo/d3xp/physics/Force_Constant.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FORCE_CONSTANT_H__ +#define __FORCE_CONSTANT_H__ + +/* +=============================================================================== + + Constant force + +=============================================================================== +*/ + +class idForce_Constant : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Constant ); + + idForce_Constant(); + virtual ~idForce_Constant(); + + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // constant force + void SetForce( const idVec3 &force ); + // set force position + void SetPosition( idPhysics *physics, int id, const idVec3 &point ); + + void SetPhysics( idPhysics *physics ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + // force properties + idVec3 force; + idPhysics * physics; + int id; + idVec3 point; +}; + +#endif /* !__FORCE_CONSTANT_H__ */ diff --git a/neo/d3xp/physics/Force_Drag.cpp b/neo/d3xp/physics/Force_Drag.cpp new file mode 100644 index 00000000..22c23c22 --- /dev/null +++ b/neo/d3xp/physics/Force_Drag.cpp @@ -0,0 +1,155 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Drag ) +END_CLASS + +/* +================ +idForce_Drag::idForce_Drag +================ +*/ +idForce_Drag::idForce_Drag() { + damping = 0.5f; + physics = NULL; + id = 0; + p = vec3_zero; + dragPosition = vec3_zero; +} + +/* +================ +idForce_Drag::~idForce_Drag +================ +*/ +idForce_Drag::~idForce_Drag() { +} + +/* +================ +idForce_Drag::Init +================ +*/ +void idForce_Drag::Init( float damping ) { + if ( damping >= 0.0f && damping < 1.0f ) { + this->damping = damping; + } +} + +/* +================ +idForce_Drag::SetPhysics +================ +*/ +void idForce_Drag::SetPhysics( idPhysics *phys, int id, const idVec3 &p ) { + this->physics = phys; + this->id = id; + this->p = p; +} + +/* +================ +idForce_Drag::SetDragPosition +================ +*/ +void idForce_Drag::SetDragPosition( const idVec3 &pos ) { + this->dragPosition = pos; +} + +/* +================ +idForce_Drag::GetDragPosition +================ +*/ +const idVec3 &idForce_Drag::GetDragPosition() const { + return this->dragPosition; +} + +/* +================ +idForce_Drag::GetDraggedPosition +================ +*/ +const idVec3 idForce_Drag::GetDraggedPosition() const { + return ( physics->GetOrigin( id ) + p * physics->GetAxis( id ) ); +} + +/* +================ +idForce_Drag::Evaluate +================ +*/ +void idForce_Drag::Evaluate( int time ) { + float l1, l2, mass; + idVec3 dragOrigin, dir1, dir2, velocity, centerOfMass; + idMat3 inertiaTensor; + idRotation rotation; + idClipModel *clipModel; + + if ( !physics ) { + return; + } + + clipModel = physics->GetClipModel( id ); + if ( clipModel != NULL && clipModel->IsTraceModel() ) { + clipModel->GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + } else { + centerOfMass.Zero(); + } + + centerOfMass = physics->GetOrigin( id ) + centerOfMass * physics->GetAxis( id ); + dragOrigin = physics->GetOrigin( id ) + p * physics->GetAxis( id ); + + dir1 = dragPosition - centerOfMass; + dir2 = dragOrigin - centerOfMass; + l1 = dir1.Normalize(); + l2 = dir2.Normalize(); + + rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( gameLocal.time - gameLocal.previousTime ), id ); + + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.time - gameLocal.previousTime ) ); + physics->SetLinearVelocity( velocity, id ); +} + +/* +================ +idForce_Drag::RemovePhysics +================ +*/ +void idForce_Drag::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/neo/d3xp/physics/Force_Drag.h b/neo/d3xp/physics/Force_Drag.h new file mode 100644 index 00000000..b3a25d25 --- /dev/null +++ b/neo/d3xp/physics/Force_Drag.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FORCE_DRAG_H__ +#define __FORCE_DRAG_H__ + +/* +=============================================================================== + + Drag force + +=============================================================================== +*/ + +class idForce_Drag : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Drag ); + + idForce_Drag(); + virtual ~idForce_Drag(); + // initialize the drag force + void Init( float damping ); + // set physics object being dragged + void SetPhysics( idPhysics *physics, int id, const idVec3 &p ); + // set position to drag towards + void SetDragPosition( const idVec3 &pos ); + // get the position dragged towards + const idVec3 & GetDragPosition() const; + // get the position on the dragged physics object + const idVec3 GetDraggedPosition() const; + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // properties + float damping; + + // positioning + idPhysics * physics; // physics object + int id; // clip model id of physics object + idVec3 p; // position on clip model + idVec3 dragPosition; // drag towards this position +}; + +#endif /* !__FORCE_DRAG_H__ */ diff --git a/neo/d3xp/physics/Force_Field.cpp b/neo/d3xp/physics/Force_Field.cpp new file mode 100644 index 00000000..337f2c69 --- /dev/null +++ b/neo/d3xp/physics/Force_Field.cpp @@ -0,0 +1,258 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Field ) +END_CLASS + +/* +================ +idForce_Field::idForce_Field +================ +*/ +idForce_Field::idForce_Field() { + type = FORCEFIELD_UNIFORM; + applyType = FORCEFIELD_APPLY_FORCE; + magnitude = 0.0f; + dir.Set( 0, 0, 1 ); + randomTorque = 0.0f; + playerOnly = false; + monsterOnly = false; + clipModel = NULL; +} + +/* +================ +idForce_Field::~idForce_Field +================ +*/ +idForce_Field::~idForce_Field() { + if ( this->clipModel ) { + delete this->clipModel; + } +} + +/* +================ +idForce_Field::Save +================ +*/ +void idForce_Field::Save( idSaveGame *savefile ) const { + savefile->WriteInt( type ); + savefile->WriteInt( applyType); + savefile->WriteFloat( magnitude ); + savefile->WriteVec3( dir ); + savefile->WriteFloat( randomTorque ); + savefile->WriteBool( playerOnly ); + savefile->WriteBool( monsterOnly ); + savefile->WriteClipModel( clipModel ); +} + +/* +================ +idForce_Field::Restore +================ +*/ +void idForce_Field::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)type ); + savefile->ReadInt( (int &)applyType); + savefile->ReadFloat( magnitude ); + savefile->ReadVec3( dir ); + savefile->ReadFloat( randomTorque ); + savefile->ReadBool( playerOnly ); + savefile->ReadBool( monsterOnly ); + savefile->ReadClipModel( clipModel ); +} + +/* +================ +idForce_Field::SetClipModel +================ +*/ +void idForce_Field::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && clipModel != this->clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idForce_Field::Uniform +================ +*/ +void idForce_Field::Uniform( const idVec3 &force ) { + dir = force; + magnitude = dir.Normalize(); + type = FORCEFIELD_UNIFORM; +} + +/* +================ +idForce_Field::Explosion +================ +*/ +void idForce_Field::Explosion( float force ) { + magnitude = force; + type = FORCEFIELD_EXPLOSION; +} + +/* +================ +idForce_Field::Implosion +================ +*/ +void idForce_Field::Implosion( float force ) { + magnitude = force; + type = FORCEFIELD_IMPLOSION; +} + +/* +================ +idForce_Field::RandomTorque +================ +*/ +void idForce_Field::RandomTorque( float force ) { + randomTorque = force; +} + +/* +================ +idForce_Field::Evaluate +================ +*/ +void idForce_Field::Evaluate( int time ) { + int numClipModels, i; + idBounds bounds; + idVec3 force, torque, angularVelocity; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + + assert( clipModel ); + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + + idPhysics *physics = entity->GetPhysics(); + + if ( playerOnly ) { + if ( !physics->IsType( idPhysics_Player::Type ) ) { + continue; + } + } else if ( monsterOnly ) { + if ( !physics->IsType( idPhysics_Monster::Type ) ) { + continue; + } + } + + if ( !gameLocal.clip.ContentsModel( cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->Handle(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { + continue; + } + + switch( type ) { + case FORCEFIELD_UNIFORM: { + force = dir; + break; + } + case FORCEFIELD_EXPLOSION: { + force = cm->GetOrigin() - clipModel->GetOrigin(); + force.Normalize(); + break; + } + case FORCEFIELD_IMPLOSION: { + force = clipModel->GetOrigin() - cm->GetOrigin(); + force.Normalize(); + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid type" ); + break; + } + } + + if ( randomTorque != 0.0f ) { + torque[0] = gameLocal.random.CRandomFloat(); + torque[1] = gameLocal.random.CRandomFloat(); + torque[2] = gameLocal.random.CRandomFloat(); + if ( torque.Normalize() == 0.0f ) { + torque[2] = 1.0f; + } + } + + switch( applyType ) { + case FORCEFIELD_APPLY_FORCE: { + if ( randomTorque != 0.0f ) { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + case FORCEFIELD_APPLY_VELOCITY: { + physics->SetLinearVelocity( force * magnitude, cm->GetId() ); + if ( randomTorque != 0.0f ) { + angularVelocity = physics->GetAngularVelocity( cm->GetId() ); + physics->SetAngularVelocity( 0.5f * (angularVelocity + torque * randomTorque), cm->GetId() ); + } + break; + } + case FORCEFIELD_APPLY_IMPULSE: { + if ( randomTorque != 0.0f ) { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid apply type" ); + break; + } + } + } +} diff --git a/neo/d3xp/physics/Force_Field.h b/neo/d3xp/physics/Force_Field.h new file mode 100644 index 00000000..5c86c08e --- /dev/null +++ b/neo/d3xp/physics/Force_Field.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FORCE_FIELD_H__ +#define __FORCE_FIELD_H__ + +/* +=============================================================================== + + Force field + +=============================================================================== +*/ + +enum forceFieldType { + FORCEFIELD_UNIFORM, + FORCEFIELD_EXPLOSION, + FORCEFIELD_IMPLOSION +}; + +enum forceFieldApplyType { + FORCEFIELD_APPLY_FORCE, + FORCEFIELD_APPLY_VELOCITY, + FORCEFIELD_APPLY_IMPULSE +}; + +class idForce_Field : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Field ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idForce_Field(); + virtual ~idForce_Field(); + // uniform constant force + void Uniform( const idVec3 &force ); + // explosion from clip model origin + void Explosion( float force ); + // implosion towards clip model origin + void Implosion( float force ); + // add random torque + void RandomTorque( float force ); + // should the force field apply a force, velocity or impulse + void SetApplyType( const forceFieldApplyType type ) { applyType = type; } + // make the force field only push players + void SetPlayerOnly( bool set ) { playerOnly = set; } + // make the force field only push monsters + void SetMonsterOnly( bool set ) { monsterOnly = set; } + // clip model describing the extents of the force field + void SetClipModel( idClipModel *clipModel ); + +public: // common force interface + virtual void Evaluate( int time ); + +private: + // force properties + forceFieldType type; + forceFieldApplyType applyType; + float magnitude; + idVec3 dir; + float randomTorque; + bool playerOnly; + bool monsterOnly; + idClipModel * clipModel; +}; + +#endif /* !__FORCE_FIELD_H__ */ diff --git a/neo/d3xp/physics/Force_Grab.cpp b/neo/d3xp/physics/Force_Grab.cpp new file mode 100644 index 00000000..3ea1ac64 --- /dev/null +++ b/neo/d3xp/physics/Force_Grab.cpp @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Grab ) +END_CLASS + + +/* +================ +idForce_Grab::Save +================ +*/ +void idForce_Grab::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( damping ); + savefile->WriteVec3( goalPosition ); + savefile->WriteFloat( distanceToGoal ); + savefile->WriteInt( id ); +} + +/* +================ +idForce_Grab::Restore +================ +*/ +void idForce_Grab::Restore( idRestoreGame *savefile ) { + + //Note: Owner needs to call set physics + savefile->ReadFloat( damping ); + savefile->ReadVec3( goalPosition ); + savefile->ReadFloat( distanceToGoal ); + savefile->ReadInt( id ); +} + +/* +================ +idForce_Grab::idForce_Grab +================ +*/ +idForce_Grab::idForce_Grab() { + damping = 0.5f; + physics = NULL; + id = 0; +} + +/* +================ +idForce_Grab::~idForce_Grab +================ +*/ +idForce_Grab::~idForce_Grab() { +} + +/* +================ +idForce_Grab::Init +================ +*/ +void idForce_Grab::Init( float damping ) { + if ( damping >= 0.0f && damping < 1.0f ) { + this->damping = damping; + } +} + +/* +================ +idForce_Grab::SetPhysics +================ +*/ +void idForce_Grab::SetPhysics( idPhysics *phys, int id, const idVec3 &goal ) { + this->physics = phys; + this->id = id; + this->goalPosition = goal; +} + +/* +================ +idForce_Grab::SetGoalPosition +================ +*/ +void idForce_Grab::SetGoalPosition( const idVec3 &goal ) { + this->goalPosition = goal; +} + +/* +================= +idForce_Grab::GetDistanceToGoal +================= +*/ +float idForce_Grab::GetDistanceToGoal() { + return distanceToGoal; +} + +/* +================ +idForce_Grab::Evaluate +================ +*/ +void idForce_Grab::Evaluate( int time ) { + if ( !physics ) { + return; + } + idVec3 forceDir, v, objectCenter; + float forceAmt; + float mass = physics->GetMass(id); + + objectCenter = physics->GetAbsBounds(id).GetCenter(); + + if ( g_grabberRandomMotion.GetBool() && !common->IsMultiplayer() ) { + // Jitter the objectCenter around so it doesn't remain stationary + float SinOffset = idMath::Sin( (float)(gameLocal.time)/66.f ); + float randScale1 = gameLocal.random.RandomFloat(); + float randScale2 = gameLocal.random.CRandomFloat(); + objectCenter.x += ( SinOffset * 3.5f * randScale1 ) + ( randScale2 * 1.2f ); + objectCenter.y += ( SinOffset * -3.5f * randScale1 ) + ( randScale2 * 1.4f ); + objectCenter.z += ( SinOffset * 2.4f * randScale1 ) + ( randScale2 * 1.6f ); + } + + forceDir = goalPosition - objectCenter; + distanceToGoal = forceDir.Normalize(); + + float temp = distanceToGoal; + if ( temp > 12.f && temp < 32.f ) { + temp = 32.f; + } + forceAmt = (1000.f * mass) + (500.f * temp * mass); + + if ( forceAmt/mass > 120000.f ) { + forceAmt = 120000.f * mass; + } + physics->AddForce( id, objectCenter, forceDir * forceAmt ); + + if ( distanceToGoal < 196.f ) { + v = physics->GetLinearVelocity( id ); + physics->SetLinearVelocity( v * damping, id ); + } + if ( distanceToGoal < 16.f ) { + v = physics->GetAngularVelocity(id); + if ( v.LengthSqr() > Square(8) ) { + physics->SetAngularVelocity( v * 0.99999f, id ); + } + } +} + +/* +================ +idForce_Grab::RemovePhysics +================ +*/ +void idForce_Grab::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} + diff --git a/neo/d3xp/physics/Force_Grab.h b/neo/d3xp/physics/Force_Grab.h new file mode 100644 index 00000000..f7724ad6 --- /dev/null +++ b/neo/d3xp/physics/Force_Grab.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FORCE_GRAB_H__ +#define __FORCE_GRAB_H__ + + +/* +=============================================================================== + + Drag force + +=============================================================================== +*/ + +class idForce_Grab : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Grab ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idForce_Grab(); + virtual ~idForce_Grab(); + // initialize the drag force + void Init( float damping ); + // set physics object being dragged + void SetPhysics( idPhysics *physics, int id, const idVec3 &goal ); + // update the goal position + void SetGoalPosition( const idVec3 &goal ); + + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + + // Get the distance from object to goal position + float GetDistanceToGoal(); + +private: + + // properties + float damping; + idVec3 goalPosition; + + float distanceToGoal; + + // positioning + idPhysics * physics; // physics object + int id; // clip model id of physics object +}; + +#endif /* !__FORCE_GRAB_H__ */ diff --git a/neo/d3xp/physics/Force_Spring.cpp b/neo/d3xp/physics/Force_Spring.cpp new file mode 100644 index 00000000..d41b209d --- /dev/null +++ b/neo/d3xp/physics/Force_Spring.cpp @@ -0,0 +1,165 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Spring ) +END_CLASS + +/* +================ +idForce_Spring::idForce_Spring +================ +*/ +idForce_Spring::idForce_Spring() { + Kstretch = 100.0f; + Kcompress = 100.0f; + damping = 0.0f; + restLength = 0.0f; + physics1 = NULL; + id1 = 0; + p1 = vec3_zero; + physics2 = NULL; + id2 = 0; + p2 = vec3_zero; +} + +/* +================ +idForce_Spring::~idForce_Spring +================ +*/ +idForce_Spring::~idForce_Spring() { +} + +/* +================ +idForce_Spring::InitSpring +================ +*/ +void idForce_Spring::InitSpring( float Kstretch, float Kcompress, float damping, float restLength ) { + this->Kstretch = Kstretch; + this->Kcompress = Kcompress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idForce_Spring::SetPosition +================ +*/ +void idForce_Spring::SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, idPhysics *physics2, int id2, const idVec3 &p2 ) { + this->physics1 = physics1; + this->id1 = id1; + this->p1 = p1; + this->physics2 = physics2; + this->id2 = id2; + this->p2 = p2; +} + +/* +================ +idForce_Spring::Evaluate +================ +*/ +void idForce_Spring::Evaluate( int time ) { + float length; + idMat3 axis; + idVec3 pos1, pos2, velocity1, velocity2, force, dampingForce; + impactInfo_t info; + + pos1 = p1; + pos2 = p2; + velocity1 = velocity2 = vec3_origin; + + if ( physics1 ) { + axis = physics1->GetAxis( id1 ); + pos1 = physics1->GetOrigin( id1 ); + pos1 += p1 * axis; + if ( damping > 0.0f ) { + physics1->GetImpactInfo( id1, pos1, &info ); + velocity1 = info.velocity; + } + } + + if ( physics2 ) { + axis = physics2->GetAxis( id2 ); + pos2 = physics2->GetOrigin( id2 ); + pos2 += p2 * axis; + if ( damping > 0.0f ) { + physics2->GetImpactInfo( id2, pos2, &info ); + velocity2 = info.velocity; + } + } + + force = pos2 - pos1; + dampingForce = ( damping * ( ((velocity2 - velocity1) * force) / (force * force) ) ) * force; + length = force.Normalize(); + + // if the spring is stretched + if ( length > restLength ) { + if ( Kstretch > 0.0f ) { + force = ( Square( length - restLength ) * Kstretch ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, -force ); + } + } + } + else { + if ( Kcompress > 0.0f ) { + force = ( Square( length - restLength ) * Kcompress ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, -force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, force ); + } + } + } +} + +/* +================ +idForce_Spring::RemovePhysics +================ +*/ +void idForce_Spring::RemovePhysics( const idPhysics *phys ) { + if ( physics1 == phys ) { + physics1 = NULL; + } + if ( physics2 == phys ) { + physics2 = NULL; + } +} diff --git a/neo/d3xp/physics/Force_Spring.h b/neo/d3xp/physics/Force_Spring.h new file mode 100644 index 00000000..f8c8eabf --- /dev/null +++ b/neo/d3xp/physics/Force_Spring.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FORCE_SPRING_H__ +#define __FORCE_SPRING_H__ + +/* +=============================================================================== + + Spring force + +=============================================================================== +*/ + +class idForce_Spring : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Spring ); + + idForce_Spring(); + virtual ~idForce_Spring(); + // initialize the spring + void InitSpring( float Kstretch, float Kcompress, float damping, float restLength ); + // set the entities and positions on these entities the spring is attached to + void SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, + idPhysics *physics2, int id2, const idVec3 &p2 ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // spring properties + float Kstretch; + float Kcompress; + float damping; + float restLength; + + // positioning + idPhysics * physics1; // first physics object + int id1; // clip model id of first physics object + idVec3 p1; // position on clip model + idPhysics * physics2; // second physics object + int id2; // clip model id of second physics object + idVec3 p2; // position on clip model + +}; + +#endif /* !__FORCE_SPRING_H__ */ diff --git a/neo/d3xp/physics/Physics.cpp b/neo/d3xp/physics/Physics.cpp new file mode 100644 index 00000000..2c941f34 --- /dev/null +++ b/neo/d3xp/physics/Physics.cpp @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, idPhysics ) +END_CLASS + + +/* +================ +idPhysics::~idPhysics +================ +*/ +idPhysics::~idPhysics() { +} + +/* +================ +idPhysics::Save +================ +*/ +void idPhysics::Save( idSaveGame *savefile ) const { +} + +/* +================ +idPhysics::Restore +================ +*/ +void idPhysics::Restore( idRestoreGame *savefile ) { +} + +/* +================ +idPhysics::SetClipBox +================ +*/ +void idPhysics::SetClipBox( const idBounds &bounds, float density ) { + SetClipModel( new (TAG_PHYSICS_CLIP) idClipModel( idTraceModel( bounds ) ), density ); +} + +/* +================ +idPhysics::SnapTimeToPhysicsFrame +================ +*/ +int idPhysics::SnapTimeToPhysicsFrame( int t ) { + return MSEC_ALIGN_TO_FRAME( t ); +} diff --git a/neo/d3xp/physics/Physics.h b/neo/d3xp/physics/Physics.h new file mode 100644 index 00000000..1f3b31fc --- /dev/null +++ b/neo/d3xp/physics/Physics.h @@ -0,0 +1,188 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_H__ +#define __PHYSICS_H__ + +/* +=============================================================================== + + Physics abstract class + + A physics object is a tool to manipulate the position and orientation of + an entity. The physics object is a container for idClipModels used for + collision detection. The physics deals with moving these collision models + through the world according to the laws of physics or other rules. + + The mass of a clip model is the volume of the clip model times the density. + An arbitrary mass can however be set for specific clip models or the + whole physics object. The contents of a clip model is a set of bit flags + that define the contents. The clip mask defines the contents a clip model + collides with. + + The linear velocity of a physics object is a vector that defines the + translation of the center of mass in units per second. The angular velocity + of a physics object is a vector that passes through the center of mass. The + direction of this vector defines the axis of rotation and the magnitude + defines the rate of rotation about the axis in radians per second. + The gravity is the change in velocity per second due to gravitational force. + + Entities update their visual position and orientation from the physics + using GetOrigin() and GetAxis(). Direct origin and axis changes of + entities should go through the physics. In other words the physics origin + and axis are updated first and the entity updates it's visual position + from the physics. + +=============================================================================== +*/ + +#define CONTACT_EPSILON 0.25f // maximum contact seperation distance + +class idEntity; + +typedef struct impactInfo_s { + float invMass; // inverse mass + idMat3 invInertiaTensor; // inverse inertia tensor + idVec3 position; // impact position relative to center of mass + idVec3 velocity; // velocity at the impact position +} impactInfo_t; + + +class idPhysics : public idClass { + +public: + ABSTRACT_PROTOTYPE( idPhysics ); + + virtual ~idPhysics(); + static int SnapTimeToPhysicsFrame( int t ); + + // Must not be virtual + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + // set pointer to entity using physics + virtual void SetSelf( idEntity *e ) = 0; + // clip models + virtual void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ) = 0; + virtual void SetClipBox( const idBounds &bounds, float density ); + virtual idClipModel * GetClipModel( int id = 0 ) const = 0; + virtual int GetNumClipModels() const = 0; + // get/set the mass of a specific clip model or the whole physics object + virtual void SetMass( float mass, int id = -1 ) = 0; + virtual float GetMass( int id = -1 ) const = 0; + // get/set the contents of a specific clip model or the whole physics object + virtual void SetContents( int contents, int id = -1 ) = 0; + virtual int GetContents( int id = -1 ) const = 0; + // get/set the contents a specific clip model or the whole physics object collides with + virtual void SetClipMask( int mask, int id = -1 ) = 0; + virtual int GetClipMask( int id = -1 ) const = 0; + // get the bounds of a specific clip model or the whole physics object + virtual const idBounds & GetBounds( int id = -1 ) const = 0; + virtual const idBounds & GetAbsBounds( int id = -1 ) const = 0; + // evaluate the physics with the given time step, returns true if the object moved + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ) = 0; + // Interpolate between the two known snapshots with the given fraction, used for MP clients. + // returns true if the object moved. + virtual bool Interpolate( const float fraction ) = 0; + // resets the prev and next states to the parameters. + virtual void ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ) = 0; + // update the time without moving + virtual void UpdateTime( int endTimeMSec ) = 0; + // get the last physics update time + virtual int GetTime() const = 0; + // collision interaction between different physics objects + virtual void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const = 0; + virtual void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) = 0; + virtual void AddForce( const int id, const idVec3 &point, const idVec3 &force ) = 0; + virtual void Activate() = 0; + virtual void PutToRest() = 0; + virtual bool IsAtRest() const = 0; + virtual int GetRestStartTime() const = 0; + virtual bool IsPushable() const = 0; + // save and restore the physics state + virtual void SaveState() = 0; + virtual void RestoreState() = 0; + // set the position and orientation in master space or world space if no master set + virtual void SetOrigin( const idVec3 &newOrigin, int id = -1 ) = 0; + virtual void SetAxis( const idMat3 &newAxis, int id = -1 ) = 0; + // translate or rotate the physics object in world space + virtual void Translate( const idVec3 &translation, int id = -1 ) = 0; + virtual void Rotate( const idRotation &rotation, int id = -1 ) = 0; + // get the position and orientation in world space + virtual const idVec3 & GetOrigin( int id = 0 ) const = 0; + virtual const idMat3 & GetAxis( int id = 0 ) const = 0; + // set linear and angular velocity + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ) = 0; + virtual void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ) = 0; + // get linear and angular velocity + virtual const idVec3 & GetLinearVelocity( int id = 0 ) const = 0; + virtual const idVec3 & GetAngularVelocity( int id = 0 ) const = 0; + // gravity + virtual void SetGravity( const idVec3 &newGravity ) = 0; + virtual const idVec3 & GetGravity() const = 0; + virtual const idVec3 & GetGravityNormal() const = 0; + // get first collision when translating or rotating this physics object + virtual void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const = 0; + virtual void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const = 0; + virtual int ClipContents( const idClipModel *model ) const = 0; + // disable/enable the clip models contained by this physics object + virtual void DisableClip() = 0; + virtual void EnableClip() = 0; + // link/unlink the clip models contained by this physics object + virtual void UnlinkClip() = 0; + virtual void LinkClip() = 0; + // contacts + virtual bool EvaluateContacts() = 0; + virtual int GetNumContacts() const = 0; + virtual const contactInfo_t &GetContact( int num ) const = 0; + virtual void ClearContacts() = 0; + virtual void AddContactEntity( idEntity *e ) = 0; + virtual void RemoveContactEntity( idEntity *e ) = 0; + // ground contacts + virtual bool HasGroundContacts() const = 0; + virtual bool IsGroundEntity( int entityNum ) const = 0; + virtual bool IsGroundClipModel( int entityNum, int id ) const = 0; + // set the master entity for objects bound to a master + virtual void SetMaster( idEntity *master, const bool orientated = true ) = 0; + // set pushed state + virtual void SetPushed( int deltaTime ) = 0; + virtual const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const = 0; + virtual const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const = 0; + // get blocking info, returns NULL if the object is not blocked + virtual const trace_t * GetBlockingInfo() const = 0; + virtual idEntity * GetBlockingEntity() const = 0; + // movement end times in msec for reached events at the end of predefined motion + virtual int GetLinearEndTime() const = 0; + virtual int GetAngularEndTime() const = 0; + // networking + virtual void WriteToSnapshot( idBitMsg &msg ) const = 0; + virtual void ReadFromSnapshot( const idBitMsg &msg ) = 0; +}; + +#endif /* !__PHYSICS_H__ */ diff --git a/neo/d3xp/physics/Physics_AF.cpp b/neo/d3xp/physics/Physics_AF.cpp new file mode 100644 index 00000000..c974f5a2 --- /dev/null +++ b/neo/d3xp/physics/Physics_AF.cpp @@ -0,0 +1,8013 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_AF ) +END_CLASS + +const float ERROR_REDUCTION = 0.5f; +const float ERROR_REDUCTION_MAX = 256.0f; +const float LIMIT_ERROR_REDUCTION = 0.3f; +const float LCP_EPSILON = 1e-7f; +const float LIMIT_LCP_EPSILON = 1e-4f; +const float CONTACT_LCP_EPSILON = 1e-6f; +const float CENTER_OF_MASS_EPSILON = 1e-4f; +const float NO_MOVE_TIME = 1.0f; +const float NO_MOVE_TRANSLATION_TOLERANCE = 10.0f; +const float NO_MOVE_ROTATION_TOLERANCE = 10.0f; +const float MIN_MOVE_TIME = -1.0f; +const float MAX_MOVE_TIME = -1.0f; +const float IMPULSE_THRESHOLD = 500.0f; +const float SUSPEND_LINEAR_VELOCITY = 10.0f; +const float SUSPEND_ANGULAR_VELOCITY = 15.0f; +const float SUSPEND_LINEAR_ACCELERATION = 20.0f; +const float SUSPEND_ANGULAR_ACCELERATION = 30.0f; +const idVec6 vec6_lcp_epsilon = idVec6( LCP_EPSILON, LCP_EPSILON, LCP_EPSILON, + LCP_EPSILON, LCP_EPSILON, LCP_EPSILON ); + +#define AF_TIMINGS + +#ifdef AF_TIMINGS +static int lastTimerReset = 0; +static int numArticulatedFigures = 0; +static idTimer timer_total, timer_pc, timer_ac, timer_collision, timer_lcp; +#endif + + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +/* +================ +idAFConstraint::idAFConstraint +================ +*/ +idAFConstraint::idAFConstraint() { + type = CONSTRAINT_INVALID; + name = "noname"; + body1 = NULL; + body2 = NULL; + physics = NULL; + + lo.Zero( 6 ); + lo.SubVec6(0) = -vec6_infinity; + hi.Zero( 6 ); + hi.SubVec6(0) = vec6_infinity; + e.SetSize( 6 ); + e.SubVec6(0) = vec6_lcp_epsilon; + + boxConstraint = NULL; + boxIndex[0] = -1; + boxIndex[1] = -1; + boxIndex[2] = -1; + boxIndex[3] = -1; + boxIndex[4] = -1; + boxIndex[5] = -1; + + firstIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); +} + +/* +================ +idAFConstraint::~idAFConstraint +================ +*/ +idAFConstraint::~idAFConstraint() { +} + +/* +================ +idAFConstraint::SetBody1 +================ +*/ +void idAFConstraint::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::SetBody2 +================ +*/ +void idAFConstraint::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::GetMultiplier +================ +*/ +const idVecX &idAFConstraint::GetMultiplier() { + return lm; +} + +/* +================ +idAFConstraint::Evaluate +================ +*/ +void idAFConstraint::Evaluate( float invTimeStep ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::ApplyFriction +================ +*/ +void idAFConstraint::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint::GetForce +================ +*/ +void idAFConstraint::GetForce( idAFBody *body, idVec6 &force ) { + idVecX v; + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + if ( body == body1 ) { + J1.TransposeMultiply( v, lm ); + } + else if ( body == body2 ) { + J2.TransposeMultiply( v, lm ); + } + else { + v.Zero(); + } + force[0] = v[0]; force[1] = v[1]; force[2] = v[2]; force[3] = v[3]; force[4] = v[4]; force[5] = v[5]; +} + +/* +================ +idAFConstraint::Translate +================ +*/ +void idAFConstraint::Translate( const idVec3 &translation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::Rotate +================ +*/ +void idAFConstraint::Rotate( const idRotation &rotation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::GetCenter +================ +*/ +void idAFConstraint::GetCenter( idVec3 ¢er ) { + center.Zero(); +} + +/* +================ +idAFConstraint::DebugDraw +================ +*/ +void idAFConstraint::DebugDraw() { +} + +/* +================ +idAFConstraint::InitSize +================ +*/ +void idAFConstraint::InitSize( int size ) { + J1.Zero( size, 6 ); + J2.Zero( size, 6 ); + c1.Zero( size ); + c2.Zero( size ); + s.Zero( size ); + lm.Zero( size ); +} + +/* +================ +idAFConstraint::Save +================ +*/ +void idAFConstraint::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( type ); +} + +/* +================ +idAFConstraint::Restore +================ +*/ +void idAFConstraint::Restore( idRestoreGame *saveFile ) { + constraintType_t t; + saveFile->ReadInt( (int &)t ); + assert( t == type ); +} + + +//=============================================================== +// +// idAFConstraint_Fixed +// +//=============================================================== + +/* +================ +idAFConstraint_Fixed::idAFConstraint_Fixed +================ +*/ +idAFConstraint_Fixed::idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_FIXED; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 6 ); + fl.allowPrimary = true; + fl.noCollision = true; + + InitOffset(); +} + +/* +================ +idAFConstraint_Fixed::InitOffset +================ +*/ +void idAFConstraint_Fixed::InitOffset() { + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Fixed::SetBody1 +================ +*/ +void idAFConstraint_Fixed::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::SetBody2 +================ +*/ +void idAFConstraint_Fixed::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::Evaluate +================ +*/ +void idAFConstraint_Fixed::Evaluate( float invTimeStep ) { + idVec3 ofs, a2; + idMat3 ax; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + a2 = offset * master->GetWorldAxis(); + ofs = a2 + master->GetWorldOrigin(); + ax = relAxis * master->GetWorldAxis(); + } + else { + a2.Zero(); + ofs = offset; + ax = relAxis; + } + + J1.Set( mat3_identity, mat3_zero, + mat3_zero, mat3_identity ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, -mat3_identity ); + } + else { + J2.Zero( 6, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( ofs - body1->GetWorldOrigin() ); + r = ( body1->GetWorldAxis().Transpose() * ax ).ToRotation(); + c1.SubVec3(1) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * -(float) DEG2RAD( r.GetAngle() ) ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Fixed::ApplyFriction +================ +*/ +void idAFConstraint_Fixed::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Fixed::Translate +================ +*/ +void idAFConstraint_Fixed::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Fixed::Rotate +================ +*/ +void idAFConstraint_Fixed::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + relAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Fixed::GetCenter +================ +*/ +void idAFConstraint_Fixed::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin(); +} + +/* +================ +idAFConstraint_Fixed::DebugDraw +================ +*/ +void idAFConstraint_Fixed::DebugDraw() { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), master->GetWorldOrigin() ); + } + else { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), vec3_origin ); + } +} + +/* +================ +idAFConstraint_Fixed::Save +================ +*/ +void idAFConstraint_Fixed::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Fixed::Restore +================ +*/ +void idAFConstraint_Fixed::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJoint +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_BALLANDSOCKETJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 3 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint() { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetAnchor +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_BallAndSocketJoint::GetAnchor() const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetNoLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetNoLimit() { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetConeLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new (TAG_PHYSICS_AF) idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new (TAG_PHYSICS_AF) idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetFriction +================ +*/ +float idAFConstraint_BallAndSocketJoint::GetFriction() const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( anchor2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + J1.Set( mat3_identity, -SkewSymmetric( a1 ) ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ) ); + } + else { + J2.Zero( 3, 6 ); + } + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new (TAG_PHYSICS_AF) idAFConstraint_BallAndSocketJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetForce +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Translate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetCenter +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::DebugDraw +================ +*/ +void idAFConstraint_BallAndSocketJoint::DebugDraw() { + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 5, 0, 0 ), a1 + idVec3( 5, 0, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 5, 0 ), a1 + idVec3( 0, 5, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 0, 5 ), a1 + idVec3( 0, 0, 5 ) ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Save +================ +*/ +void idAFConstraint_BallAndSocketJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + coneLimit->Save( saveFile ); + } + if ( pyramidLimit ) { + pyramidLimit->Save( saveFile ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Restore +================ +*/ +void idAFConstraint_BallAndSocketJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( friction ); + if ( coneLimit ) { + coneLimit->Restore( saveFile ); + } + if ( pyramidLimit ) { + pyramidLimit->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction +================ +*/ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction() { + type = CONSTRAINT_FRICTION; + name = "ballAndSocketJointFriction"; + InitSize( 3 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Setup +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Setup( idAFConstraint_BallAndSocketJoint *bsj ) { + this->joint = bsj; + body1 = bsj->GetBody1(); + body2 = bsj->GetBody2(); +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Add +================ +*/ +bool idAFConstraint_BallAndSocketJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = lo[2] = -f; + hi[0] = hi[1] = hi[2] = f; + + J1.Zero( 3, 6 ); + J1[0][3] = J1[1][4] = J1[2][5] = 1.0f; + + if ( body2 ) { + + J2.Zero( 3, 6 ); + J2[0][3] = J2[1][4] = J2[2][5] = 1.0f; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Translate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_UniversalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_UNIVERSALJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 4 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint() { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetAnchor +================ +*/ +void idAFConstraint_UniversalJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_UniversalJoint::GetAnchor() const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_UniversalJoint::SetShafts +================ +*/ +void idAFConstraint_UniversalJoint::SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ) { + idVec3 cardanAxis; + float l; + + shaft1 = cardanShaft1; + l = shaft1.Normalize(); + assert( l != 0.0f ); + shaft2 = cardanShaft2; + l = shaft2.Normalize(); + assert( l != 0.0f ); + + // the cardan axis is a vector orthogonal to both cardan shafts + cardanAxis = shaft1.Cross( shaft2 ); + if ( cardanAxis.Normalize() == 0.0f ) { + idVec3 vecY; + shaft1.OrthogonalBasis( cardanAxis, vecY ); + cardanAxis.Normalize(); + } + + shaft1 *= body1->GetWorldAxis().Transpose(); + axis1 = cardanAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + shaft2 *= body2->GetWorldAxis().Transpose(); + axis2 = cardanAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = cardanAxis; + } + + if ( coneLimit ) { + coneLimit->SetBody1Axis( shaft1 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetBody1Axis( shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetNoLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetNoLimit() { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetConeLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new (TAG_PHYSICS_AF) idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, shaft1 ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new (TAG_PHYSICS_AF) idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, shaft1 ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_UniversalJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetFriction +================ +*/ +float idAFConstraint_UniversalJoint::GetFriction() const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_UniversalJoint::Evaluate + + NOTE: this joint is homokinetic +================ +*/ +void idAFConstraint_UniversalJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = s1.Cross( axis1 * body1->GetWorldAxis() ); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( s1[0], s1[1], s1[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 4, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( s2[0], s2[1], s2[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 4, 6 ); + } + else { + J2.Zero( 4, 6 ); + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( d1 * d2 ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new (TAG_PHYSICS_AF) idAFConstraint_UniversalJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetForce +================ +*/ +void idAFConstraint_UniversalJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_UniversalJoint::Translate +================ +*/ +void idAFConstraint_UniversalJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::Rotate +================ +*/ +void idAFConstraint_UniversalJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + shaft2 *= rotation.ToMat3(); + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetCenter +================ +*/ +void idAFConstraint_UniversalJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_UniversalJoint::DebugDraw +================ +*/ +void idAFConstraint_UniversalJoint::DebugDraw() { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = axis1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + s1 * 5.0f, 1.0f ); + gameRenderWorld->DebugArrow( colorBlue, a2, a2 + s2 * 5.0f, 1.0f ); + gameRenderWorld->DebugLine( colorGreen, a1, a1 + d1 * 5.0f ); + gameRenderWorld->DebugLine( colorGreen, a2, a2 + d2 * 5.0f ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_UniversalJoint::Save +================ +*/ +void idAFConstraint_UniversalJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( shaft1 ); + saveFile->WriteVec3( shaft2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + coneLimit->Save( saveFile ); + } + if ( pyramidLimit ) { + pyramidLimit->Save( saveFile ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::Restore +================ +*/ +void idAFConstraint_UniversalJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( shaft1 ); + saveFile->ReadVec3( shaft2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadFloat( friction ); + if ( coneLimit ) { + coneLimit->Restore( saveFile ); + } + if ( pyramidLimit ) { + pyramidLimit->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_UniversalJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction +================ +*/ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction() { + type = CONSTRAINT_FRICTION; + name = "universalJointFriction"; + InitSize( 2 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Setup +================ +*/ +void idAFConstraint_UniversalJointFriction::Setup( idAFConstraint_UniversalJoint *uj ) { + this->joint = uj; + body1 = uj->GetBody1(); + body2 = uj->GetBody2(); +} + +/* +================ +idAFConstraint_UniversalJointFriction::Evaluate +================ +*/ +void idAFConstraint_UniversalJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::Add +================ +*/ +bool idAFConstraint_UniversalJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 s1, s2, dir1, dir2; + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = -f; + hi[0] = hi[1] = f; + + joint->GetShafts( s1, s2 ); + + s1 *= body1->GetWorldAxis(); + s1.NormalVectors( dir1, dir2 ); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = dir1; + J1.SubVec6(1).SubVec3(0).Zero(); + J1.SubVec6(1).SubVec3(1) = dir2; + + if ( body2 ) { + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -dir1; + J2.SubVec6(1).SubVec3(0).Zero(); + J2.SubVec6(1).SubVec3(1) = -dir2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Translate +================ +*/ +void idAFConstraint_UniversalJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_UniversalJointFriction::Rotate +================ +*/ +void idAFConstraint_UniversalJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_CylindricalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint +================ +*/ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Evaluate +================ +*/ +void idAFConstraint_CylindricalJoint::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::ApplyFriction +================ +*/ +void idAFConstraint_CylindricalJoint::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Translate +================ +*/ +void idAFConstraint_CylindricalJoint::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Rotate +================ +*/ +void idAFConstraint_CylindricalJoint::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::DebugDraw +================ +*/ +void idAFConstraint_CylindricalJoint::DebugDraw() { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Hinge +// +//=============================================================== + +/* +================ +idAFConstraint_Hinge::idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_HINGE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + coneLimit = NULL; + steering = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; + initialAxis = body1->GetWorldAxis(); + if ( body2 ) { + initialAxis *= body2->GetWorldAxis().Transpose(); + } +} + +/* +================ +idAFConstraint_Hinge::~idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::~idAFConstraint_Hinge() { + if ( coneLimit ) { + delete coneLimit; + } + if ( fc ) { + delete fc; + } + if ( steering ) { + delete steering; + } +} + +/* +================ +idAFConstraint_Hinge::SetAnchor +================ +*/ +void idAFConstraint_Hinge::SetAnchor( const idVec3 &worldPosition ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_Hinge::GetAnchor +================ +*/ +idVec3 idAFConstraint_Hinge::GetAnchor() const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_Hinge::SetAxis +================ +*/ +void idAFConstraint_Hinge::SetAxis( const idVec3 &axis ) { + idVec3 normAxis; + + normAxis = axis; + normAxis.Normalize(); + + // get axis relative to body1 + axis1 = normAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get axis relative to body2 + axis2 = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = normAxis; + } +} + +/* +================ +idAFConstraint_Hinge::GetAxis +================ +*/ +idVec3 idAFConstraint_Hinge::GetAxis() const { + if ( body2 ) { + return axis2 * body2->GetWorldAxis(); + } + return axis2; +} + +/* +================ +idAFConstraint_Hinge::SetNoLimit +================ +*/ +void idAFConstraint_Hinge::SetNoLimit() { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } +} + +/* +================ +idAFConstraint_Hinge::SetLimit +================ +*/ +void idAFConstraint_Hinge::SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ) { + if ( !coneLimit ) { + coneLimit = new (TAG_PHYSICS_AF) idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, axis * body2->GetWorldAxis().Transpose(), angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, axis, angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_Hinge::SetLimitEpsilon +================ +*/ +void idAFConstraint_Hinge::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_Hinge::GetFriction +================ +*/ +float idAFConstraint_Hinge::GetFriction() const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_Hinge::GetAngle +================ +*/ +float idAFConstraint_Hinge::GetAngle() const { + idMat3 axis; + idRotation rotation; + float angle; + + axis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose() * initialAxis.Transpose(); + rotation = axis.ToRotation(); + angle = rotation.GetAngle(); + if ( rotation.GetVec() * axis1 < 0.0f ) { + return -angle; + } + return angle; +} + +/* +================ +idAFConstraint_Hinge::SetSteerAngle +================ +*/ +void idAFConstraint_Hinge::SetSteerAngle( const float degrees ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !steering ) { + steering = new (TAG_PHYSICS_AF) idAFConstraint_HingeSteering(); + steering->Setup( this ); + } + steering->SetSteerAngle( degrees ); +} + +/* +================ +idAFConstraint_Hinge::SetSteerSpeed +================ +*/ +void idAFConstraint_Hinge::SetSteerSpeed( const float speed ) { + if ( steering ) { + steering->SetSteerSpeed( speed ); + } +} + +/* +================ +idAFConstraint_Hinge::Evaluate +================ +*/ +void idAFConstraint_Hinge::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idVec3 x1, x2, cross; + idVec3 vecX, vecY; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + x1 = axis1 * body1->GetWorldAxis(); // axis in body1 space + x1.OrthogonalBasis( vecX, vecY ); // basis for axis in body1 space + + a1 = anchor1 * body1->GetWorldAxis(); // anchor in body1 space + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); // anchor in master space + x2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + x2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( vecX[0], vecX[1], vecX[2], + vecY[0], vecY[1], vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( -vecX[0], -vecX[1], -vecX[2], + -vecY[0], -vecY[1], -vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + cross = x1.Cross( x2 ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecX ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecY ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( steering ) { + steering->Add( physics, invTimeStep ); + } + else if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::ApplyFriction +================ +*/ +void idAFConstraint_Hinge::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new (TAG_PHYSICS_AF) idAFConstraint_HingeFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::GetForce +================ +*/ +void idAFConstraint_Hinge::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_Hinge::Translate +================ +*/ +void idAFConstraint_Hinge::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_Hinge::Rotate +================ +*/ +void idAFConstraint_Hinge::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_Hinge::GetCenter +================ +*/ +void idAFConstraint_Hinge::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_Hinge::DebugDraw +================ +*/ +void idAFConstraint_Hinge::DebugDraw() { + idVec3 vecX, vecY; + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + idVec3 x1 = axis1 * body1->GetWorldAxis(); + x1.OrthogonalBasis( vecX, vecY ); + + gameRenderWorld->DebugArrow( colorBlue, a1 - 4.0f * x1, a1 + 4.0f * x1, 1 ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecX, a1 + 2.0f * vecX ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecY, a1 + 2.0f * vecY ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_Hinge::Save +================ +*/ +void idAFConstraint_Hinge::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteMat3( initialAxis ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + saveFile->WriteBool( true ); + coneLimit->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( steering ) { + saveFile->WriteBool( true ); + steering->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( fc ) { + saveFile->WriteBool( true ); + fc->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } +} + +/* +================ +idAFConstraint_Hinge::Restore +================ +*/ +void idAFConstraint_Hinge::Restore( idRestoreGame *saveFile ) { + bool b; + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadMat3( initialAxis ); + saveFile->ReadFloat( friction ); + + saveFile->ReadBool( b ); + if ( b ) { + if ( !coneLimit ) { + coneLimit = new (TAG_PHYSICS_AF) idAFConstraint_ConeLimit; + } + coneLimit->SetPhysics( physics ); + coneLimit->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !steering ) { + steering = new (TAG_PHYSICS_AF) idAFConstraint_HingeSteering; + } + steering->Setup( this ); + steering->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !fc ) { + fc = new (TAG_PHYSICS_AF) idAFConstraint_HingeFriction; + } + fc->Setup( this ); + fc->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_HingeFriction +// +//=============================================================== + +/* +================ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction +================ +*/ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction() { + type = CONSTRAINT_FRICTION; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_HingeFriction::Setup +================ +*/ +void idAFConstraint_HingeFriction::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeFriction::Evaluate +================ +*/ +void idAFConstraint_HingeFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::ApplyFriction +================ +*/ +void idAFConstraint_HingeFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::Add +================ +*/ +bool idAFConstraint_HingeFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 a1, a2; + float f; + + physics = phys; + + f = hinge->GetFriction() * hinge->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = -f; + hi[0] = f; + + hinge->GetAxis( a1, a2 ); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeFriction::Translate +================ +*/ +void idAFConstraint_HingeFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeFriction::Rotate +================ +*/ +void idAFConstraint_HingeFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_HingeSteering +// +//=============================================================== + +/* +================ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering +================ +*/ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering() { + type = CONSTRAINT_HINGESTEERING; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; + steerSpeed = 0.0f; + epsilon = LCP_EPSILON; +} + +/* +================ +idAFConstraint_HingeSteering::Save +================ +*/ +void idAFConstraint_HingeSteering::Save( idSaveGame *saveFile ) const { + saveFile->WriteFloat(steerAngle); + saveFile->WriteFloat(steerSpeed); + saveFile->WriteFloat(epsilon); +} + +/* +================ +idAFConstraint_HingeSteering::Restore +================ +*/ +void idAFConstraint_HingeSteering::Restore( idRestoreGame *saveFile ) { + saveFile->ReadFloat(steerAngle); + saveFile->ReadFloat(steerSpeed); + saveFile->ReadFloat(epsilon); +} + +/* +================ +idAFConstraint_HingeSteering::Setup +================ +*/ +void idAFConstraint_HingeSteering::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeSteering::Evaluate +================ +*/ +void idAFConstraint_HingeSteering::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::ApplyFriction +================ +*/ +void idAFConstraint_HingeSteering::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::Add +================ +*/ +bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep ) { + float angle, speed; + idVec3 a1, a2; + + physics = phys; + + hinge->GetAxis( a1, a2 ); + angle = hinge->GetAngle(); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + speed = steerAngle - angle; + if ( steerSpeed != 0.0f ) { + if ( speed > steerSpeed ) { + speed = steerSpeed; + } + else if ( speed < -steerSpeed ) { + speed = -steerSpeed; + } + } + + c1[0] = DEG2RAD( speed ) * invTimeStep; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeSteering::Translate +================ +*/ +void idAFConstraint_HingeSteering::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeSteering::Rotate +================ +*/ +void idAFConstraint_HingeSteering::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_Slider +// +//=============================================================== + +/* +================ +idAFConstraint_Slider::idAFConstraint_Slider +================ +*/ +idAFConstraint_Slider::idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SLIDER; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + fl.allowPrimary = true; + fl.noCollision = true; + + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Slider::SetAxis +================ +*/ +void idAFConstraint_Slider::SetAxis( const idVec3 &ax ) { + idVec3 normAxis; + + // get normalized axis relative to body1 + normAxis = ax; + normAxis.Normalize(); + if ( body2 ) { + axis = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis = normAxis; + } +} + +/* +================ +idAFConstraint_Slider::Evaluate +================ +*/ +void idAFConstraint_Slider::Evaluate( float invTimeStep ) { + idVec3 vecX, vecY, ofs; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + (axis * master->GetWorldAxis()).OrthogonalBasis( vecX, vecY ); + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * (relAxis * master->GetWorldAxis()) ).ToRotation(); + } + else { + axis.OrthogonalBasis( vecX, vecY ); + ofs = offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * relAxis ).ToRotation(); + } + + J1.Set( mat3_zero, mat3_identity, + idMat3( vecX, vecY, vec3_origin ), mat3_zero ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + + J2.Set( mat3_zero, -mat3_identity, + idMat3( -vecX, -vecY, vec3_origin ), mat3_zero ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * - (float) DEG2RAD( r.GetAngle() ) ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( vecX * ofs ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( vecY * ofs ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Slider::ApplyFriction +================ +*/ +void idAFConstraint_Slider::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Slider::Translate +================ +*/ +void idAFConstraint_Slider::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Slider::Rotate +================ +*/ +void idAFConstraint_Slider::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + } +} + +/* +================ +idAFConstraint_Slider::GetCenter +================ +*/ +void idAFConstraint_Slider::GetCenter( idVec3 ¢er ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + center = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + center = offset - body1->GetWorldOrigin(); + } +} + +/* +================ +idAFConstraint_Slider::DebugDraw +================ +*/ +void idAFConstraint_Slider::DebugDraw() { + idVec3 ofs; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + ofs = offset - body1->GetWorldOrigin(); + } + gameRenderWorld->DebugLine( colorGreen, ofs, ofs + axis * body1->GetWorldAxis() ); +} + +/* +================ +idAFConstraint_Slider::Save +================ +*/ +void idAFConstraint_Slider::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( axis ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Slider::Restore +================ +*/ +void idAFConstraint_Slider::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( axis ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_Line +// +//=============================================================== + +/* +================ +idAFConstraint_Line::idAFConstraint_Line +================ +*/ +idAFConstraint_Line::idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Evaluate +================ +*/ +void idAFConstraint_Line::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::ApplyFriction +================ +*/ +void idAFConstraint_Line::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Translate +================ +*/ +void idAFConstraint_Line::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Rotate +================ +*/ +void idAFConstraint_Line::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::DebugDraw +================ +*/ +void idAFConstraint_Line::DebugDraw() { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Plane +// +//=============================================================== + +/* +================ +idAFConstraint_Plane::idAFConstraint_Plane +================ +*/ +idAFConstraint_Plane::idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_PLANE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_Plane::SetPlane +================ +*/ +void idAFConstraint_Plane::SetPlane( const idVec3 &normal, const idVec3 &anchor ) { + // get anchor relative to center of mass of body1 + anchor1 = ( anchor - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( anchor - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + planeNormal = normal * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = anchor; + planeNormal = normal; + } +} + +/* +================ +idAFConstraint_Plane::Evaluate +================ +*/ +void idAFConstraint_Plane::Evaluate( float invTimeStep ) { + idVec3 a1, a2, normal, p; + idVec6 v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + normal = planeNormal * master->GetWorldAxis(); + } + else { + a2 = anchor2; + normal = planeNormal; + } + + p = a1 - body1->GetWorldOrigin(); + v.SubVec3(0) = normal; + v.SubVec3(1) = p.Cross( normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + + if ( body2 ) { + p = a1 - body2->GetWorldOrigin(); + v.SubVec3(0) = -normal; + v.SubVec3(1) = p.Cross( -normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + } + + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * (a1 * normal - a2 * normal); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Plane::ApplyFriction +================ +*/ +void idAFConstraint_Plane::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Plane::Translate +================ +*/ +void idAFConstraint_Plane::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Plane::Rotate +================ +*/ +void idAFConstraint_Plane::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + planeNormal *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Plane::DebugDraw +================ +*/ +void idAFConstraint_Plane::DebugDraw() { + idVec3 a1, normal, right, up; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + normal = planeNormal * master->GetWorldAxis(); + } + else { + normal = planeNormal; + } + normal.NormalVectors( right, up ); + normal *= 4.0f; + right *= 4.0f; + up *= 4.0f; + + gameRenderWorld->DebugLine( colorCyan, a1 - right, a1 + right ); + gameRenderWorld->DebugLine( colorCyan, a1 - up, a1 + up ); + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + normal, 1 ); +} + +/* +================ +idAFConstraint_Plane::Save +================ +*/ +void idAFConstraint_Plane::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( planeNormal ); +} + +/* +================ +idAFConstraint_Plane::Restore +================ +*/ +void idAFConstraint_Plane::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( planeNormal ); +} + + +//=============================================================== +// +// idAFConstraint_Spring +// +//=============================================================== + +/* +================ +idAFConstraint_Spring::idAFConstraint_Spring +================ +*/ +idAFConstraint_Spring::idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SPRING; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = false; + kstretch = kcompress = damping = 1.0f; + minLength = maxLength = restLength = 0.0f; +} + +/* +================ +idAFConstraint_Spring::SetAnchor +================ +*/ +void idAFConstraint_Spring::SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldAnchor1 - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldAnchor2 - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldAnchor2; + } +} + +/* +================ +idAFConstraint_Spring::SetSpring +================ +*/ +void idAFConstraint_Spring::SetSpring( const float stretch, const float compress, const float damping, const float restLength ) { + assert( stretch >= 0.0f && compress >= 0.0f && restLength >= 0.0f ); + this->kstretch = stretch; + this->kcompress = compress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idAFConstraint_Spring::SetLimit +================ +*/ +void idAFConstraint_Spring::SetLimit( const float minLength, const float maxLength ) { + assert( minLength >= 0.0f && maxLength >= 0.0f && maxLength >= minLength ); + this->minLength = minLength; + this->maxLength = maxLength; +} + +/* +================ +idAFConstraint_Spring::Evaluate +================ +*/ +void idAFConstraint_Spring::Evaluate( float invTimeStep ) { + idVec3 a1, a2, velocity1, velocity2, force; + idVec6 v1, v2; + float d, dampingForce, length, error; + bool limit; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + velocity1 = body1->GetPointVelocity( a1 ); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + velocity2 = master->GetPointVelocity( a2 ); + } + else { + a2 = anchor2; + velocity2.Zero(); + } + + force = a2 - a1; + d = force * force; + if ( d != 0.0f ) { + dampingForce = damping * idMath::Fabs( (velocity2 - velocity1) * force ) / d; + } + else { + dampingForce = 0.0f; + } + length = force.Normalize(); + + if ( length > restLength ) { + if ( kstretch > 0.0f ) { + idVec3 springForce = force * ( Square( length - restLength ) * kstretch - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + else { + if ( kcompress > 0.0f ) { + idVec3 springForce = force * -( Square( restLength - length ) * kcompress - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + + // check for spring limits + if ( length < minLength ) { + force = -force; + error = minLength - length; + limit = true; + } + else if ( maxLength > 0.0f && length > maxLength ) { + error = length - maxLength; + limit = true; + } + else { + error = 0.0f; + limit = false; + } + + if ( limit ) { + a1 -= body1->GetWorldOrigin(); + v1.SubVec3(0) = force; + v1.SubVec3(1) = a1.Cross( force ); + J1.Set( 1, 6, v1.ToFloatPtr() ); + if ( body2 ) { + a2 -= body2->GetWorldOrigin(); + v2.SubVec3(0) = -force; + v2.SubVec3(1) = a2.Cross( -force ); + J2.Set( 1, 6, v2.ToFloatPtr() ); + } + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * error; + lo[0] = 0.0f; + } + else { + J1.Zero( 0, 0 ); + J2.Zero( 0, 0 ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Spring::ApplyFriction +================ +*/ +void idAFConstraint_Spring::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Spring::Translate +================ +*/ +void idAFConstraint_Spring::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Spring::Rotate +================ +*/ +void idAFConstraint_Spring::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } +} + +/* +================ +idAFConstraint_Spring::GetCenter +================ +*/ +void idAFConstraint_Spring::GetCenter( idVec3 ¢er ) { + idAFBody *master; + idVec3 a1, a2; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + center = ( a1 + a2 ) * 0.5f; +} + +/* +================ +idAFConstraint_Spring::DebugDraw +================ +*/ +void idAFConstraint_Spring::DebugDraw() { + idAFBody *master; + float length; + idVec3 a1, a2, dir, mid, p; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + dir = a2 - a1; + mid = a1 + 0.5f * dir; + length = dir.Normalize(); + + // draw spring + gameRenderWorld->DebugLine( colorGreen, a1, a2 ); + + // draw rest length + p = restLength * 0.5f * dir; + gameRenderWorld->DebugCircle( colorWhite, mid + p, dir, 1.0f, 10 ); + gameRenderWorld->DebugCircle( colorWhite, mid - p, dir, 1.0f, 10 ); + if ( restLength > length ) { + gameRenderWorld->DebugLine( colorWhite, a2, mid + p ); + gameRenderWorld->DebugLine( colorWhite, a1, mid - p ); + } + + if ( minLength > 0.0f ) { + // draw min length + gameRenderWorld->DebugCircle( colorBlue, mid + minLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorBlue, mid - minLength * 0.5f * dir, dir, 2.0f, 10 ); + } + + if ( maxLength > 0.0f ) { + // draw max length + gameRenderWorld->DebugCircle( colorRed, mid + maxLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorRed, mid - maxLength * 0.5f * dir, dir, 2.0f, 10 ); + } +} + +/* +================ +idAFConstraint_Spring::Save +================ +*/ +void idAFConstraint_Spring::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( kstretch ); + saveFile->WriteFloat( kcompress ); + saveFile->WriteFloat( damping ); + saveFile->WriteFloat( restLength ); + saveFile->WriteFloat( minLength ); + saveFile->WriteFloat( maxLength ); +} + +/* +================ +idAFConstraint_Spring::Restore +================ +*/ +void idAFConstraint_Spring::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( kstretch ); + saveFile->ReadFloat( kcompress ); + saveFile->ReadFloat( damping ); + saveFile->ReadFloat( restLength ); + saveFile->ReadFloat( minLength ); + saveFile->ReadFloat( maxLength ); +} + + +//=============================================================== +// +// idAFConstraint_Contact +// +//=============================================================== + +/* +================ +idAFConstraint_Contact::idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::idAFConstraint_Contact() { + name = "contact"; + type = CONSTRAINT_CONTACT; + InitSize( 1 ); + fc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_Contact::~idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::~idAFConstraint_Contact() { + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_Contact::Setup +================ +*/ +void idAFConstraint_Contact::Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ) { + idVec3 p; + idVec6 v; + float vel; + float minBounceVelocity = 2.0f; + + assert( b1 ); + + body1 = b1; + body2 = b2; + contact = c; + + p = c.point - body1->GetWorldOrigin(); + v.SubVec3(0) = c.normal; + v.SubVec3(1) = p.Cross( c.normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + vel = v.SubVec3(0) * body1->GetLinearVelocity() + v.SubVec3(1) * body1->GetAngularVelocity(); + + if ( body2 ) { + p = c.point - body2->GetWorldOrigin(); + v.SubVec3(0) = -c.normal; + v.SubVec3(1) = p.Cross( -c.normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + vel += v.SubVec3(0) * body2->GetLinearVelocity() + v.SubVec3(1) * body2->GetAngularVelocity(); + c2[0] = 0.0f; + } + + if ( body1->GetBouncyness() > 0.0f && -vel > minBounceVelocity ) { + c1[0] = body1->GetBouncyness() * vel; + } + else { + c1[0] = 0.0f; + } + + e[0] = CONTACT_LCP_EPSILON; + lo[0] = 0.0f; + hi[0] = idMath::INFINITY; + boxConstraint = NULL; + boxIndex[0] = -1; +} + +/* +================ +idAFConstraint_Contact::Evaluate +================ +*/ +void idAFConstraint_Contact::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_Contact::ApplyFriction +================ +*/ +void idAFConstraint_Contact::ApplyFriction( float invTimeStep ) { + idVec3 r, velocity, normal, dir1, dir2; + float friction, magnitude, forceNumerator, forceDenominator; + idVecX impulse, dv; + + if ( !body1 ) { + return; + } + friction = body1->GetContactFriction(); + if ( body2 && body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + + friction *= physics->GetContactFrictionScale(); + + if ( friction <= 0.0f ) { + return; + } + + // seperate friction per contact is silly but it's fast and often looks close enough + if ( af_useImpulseFriction.GetBool() ) { + + impulse.SetData( 6, VECX_ALLOCA( 6 ) ); + dv.SetData( 6, VECX_ALLOCA( 6 ) ); + + // calculate velocity in the contact plane + r = contact.point - body1->GetWorldOrigin(); + velocity = body1->GetLinearVelocity() + body1->GetAngularVelocity().Cross( r ); + velocity -= contact.normal * velocity * contact.normal; + + // get normalized direction of friction and magnitude of velocity + normal = -velocity; + magnitude = normal.Normalize(); + + forceNumerator = friction * magnitude; + forceDenominator = body1->GetInverseMass() + ( ( body1->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse.SubVec3(0) = (forceNumerator / forceDenominator) * normal; + impulse.SubVec3(1) = r.Cross( impulse.SubVec3(0) ); + body1->InverseWorldSpatialInertiaMultiply( dv, impulse.ToFloatPtr() ); + + // modify velocity with friction force + body1->SetLinearVelocity( body1->GetLinearVelocity() + dv.SubVec3(0) ); + body1->SetAngularVelocity( body1->GetAngularVelocity() + dv.SubVec3(1) ); + } + else { + + if ( !fc ) { + fc = new (TAG_PHYSICS_AF) idAFConstraint_ContactFriction; + } + // call setup each frame because contact constraints are re-used for different bodies + fc->Setup( this ); + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Contact::Translate +================ +*/ +void idAFConstraint_Contact::Translate( const idVec3 &translation ) { + assert( 0 ); // contact should never be translated +} + +/* +================ +idAFConstraint_Contact::Rotate +================ +*/ +void idAFConstraint_Contact::Rotate( const idRotation &rotation ) { + assert( 0 ); // contact should never be rotated +} + +/* +================ +idAFConstraint_Contact::GetCenter +================ +*/ +void idAFConstraint_Contact::GetCenter( idVec3 ¢er ) { + center = contact.point; +} + +/* +================ +idAFConstraint_Contact::DebugDraw +================ +*/ +void idAFConstraint_Contact::DebugDraw() { + idVec3 x, y; + contact.normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contact.point, contact.point + 6.0f * contact.normal ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * x, contact.point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * y, contact.point + 2.0f * y ); +} + + +//=============================================================== +// +// idAFConstraint_ContactFriction +// +//=============================================================== + +/* +================ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction +================ +*/ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction() { + type = CONSTRAINT_FRICTION; + name = "contactFriction"; + InitSize( 2 ); + cc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ContactFriction::Setup +================ +*/ +void idAFConstraint_ContactFriction::Setup( idAFConstraint_Contact *cc ) { + this->cc = cc; + body1 = cc->GetBody1(); + body2 = cc->GetBody2(); +} + +/* +================ +idAFConstraint_ContactFriction::Evaluate +================ +*/ +void idAFConstraint_ContactFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::ApplyFriction +================ +*/ +void idAFConstraint_ContactFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::Add +================ +*/ +bool idAFConstraint_ContactFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 r, dir1, dir2; + float friction; + int newRow; + + physics = phys; + + friction = body1->GetContactFriction() * physics->GetContactFrictionScale(); + + // if the body only has friction in one direction + if ( body1->GetFrictionDirection( dir1 ) ) { + // project the friction direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + c1.SetSize( 1 ); + c1[0] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + c2.SetSize( 1 ); + c2[0] = 0.0f; + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + } + else { + // get two friction directions orthogonal to contact normal + cc->GetContact().normal.NormalVectors( dir1, dir2 ); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + J1.SubVec6(1).SubVec3(0) = dir2; + J1.SubVec6(1).SubVec3(1) = r.Cross( dir2 ); + c1.SetSize( 2 ); + c1[0] = c1[1] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + J2.SubVec6(1).SubVec3(0) = -dir2; + J2.SubVec6(1).SubVec3(1) = r.Cross( -dir2 ); + c2.SetSize( 2 ); + c2[0] = c2[1] = 0.0f; + + if ( body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + lo[1] = -friction; + hi[1] = friction; + boxIndex[1] = 0; + } + + if ( body1->GetContactMotorDirection( dir1 ) && body1->GetContactMotorForce() > 0.0f ) { + // project the motor force direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + newRow = J1.GetNumRows(); + J1.ChangeSize( newRow+1, J1.GetNumColumns() ); + J1.SubVec6(newRow).SubVec3(0) = -dir1; + J1.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c1.ChangeSize( newRow+1 ); + c1[newRow] = body1->GetContactMotorVelocity(); + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.ChangeSize( newRow+1, J2.GetNumColumns() ); + J2.SubVec6(newRow).SubVec3(0) = -dir1; + J2.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c2.ChangeSize( newRow+1 ); + c2[newRow] = 0.0f; + } + + lo[newRow] = -body1->GetContactMotorForce(); + hi[newRow] = body1->GetContactMotorForce(); + boxIndex[newRow] = -1; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ContactFriction::Translate +================ +*/ +void idAFConstraint_ContactFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_ContactFriction::Rotate +================ +*/ +void idAFConstraint_ContactFriction::Rotate( const idRotation &rotation ) { +} + +/* +================ +idAFConstraint_ContactFriction::DebugDraw +================ +*/ +void idAFConstraint_ContactFriction::DebugDraw() { +} + + +//=============================================================== +// +// idAFConstraint_ConeLimit +// +//=============================================================== + +/* +================ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit +================ +*/ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit() { + type = CONSTRAINT_CONELIMIT; + name = "coneLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ConeLimit::Setup + + the coneAnchor is the top of the cone in body2 space + the coneAxis is the axis of the cone in body2 space + the coneAngle is the angle the cone hull makes at the top + the body1Axis is the axis in body1 space that should stay within the cone +================ +*/ +void idAFConstraint_ConeLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + this->body1 = b1; + this->body2 = b2; + this->coneAxis = coneAxis; + this->coneAxis.Normalize(); + this->coneAnchor = coneAnchor; + this->body1Axis = body1Axis; + this->body1Axis.Normalize(); + this->cosAngle = (float) cos( DEG2RAD( coneAngle * 0.5f ) ); + this->sinHalfAngle = (float) sin( DEG2RAD( coneAngle * 0.25f ) ); + this->cosHalfAngle = (float) cos( DEG2RAD( coneAngle * 0.25f ) ); +} + +/* +================ +idAFConstraint_ConeLimit::SetAnchor +================ +*/ +void idAFConstraint_ConeLimit::SetAnchor( const idVec3 &coneAnchor ) { + this->coneAnchor = coneAnchor; +} + +/* +================ +idAFConstraint_ConeLimit::SetBody1Axis +================ +*/ +void idAFConstraint_ConeLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_ConeLimit::Evaluate +================ +*/ +void idAFConstraint_ConeLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ConeLimit::ApplyFriction +================ +*/ +void idAFConstraint_ConeLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_ConeLimit::Add +================ +*/ +bool idAFConstraint_ConeLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + float a; + idVec6 J1row, J2row; + idVec3 ax, anchor, body1ax, normal, coneVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + a = ax * body1ax; + + // if the body1 axis is inside the cone + if ( a > cosAngle ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward cone normal for the position the body1 axis went outside the cone + normal = body1ax.Cross( ax ); + normal.Normalize(); + q.x = normal.x * sinHalfAngle; + q.y = normal.y * sinHalfAngle; + q.z = normal.z * sinHalfAngle; + q.w = cosHalfAngle; + coneVector = ax * q.ToMat3(); + normal = coneVector.Cross( ax ).Cross( coneVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * coneVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * coneVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ConeLimit::Translate +================ +*/ +void idAFConstraint_ConeLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + coneAnchor += translation; + } +} + +/* +================ +idAFConstraint_ConeLimit::Rotate +================ +*/ +void idAFConstraint_ConeLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + coneAnchor *= rotation; + coneAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_ConeLimit::DebugDraw +================ +*/ +void idAFConstraint_ConeLimit::DebugDraw() { + idVec3 ax, anchor, x, y, z, start, end; + float sinAngle, a, size = 10.0f; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw cone + ax.NormalVectors( x, y ); + sinAngle = idMath::Sqrt( 1.0f - cosAngle * cosAngle ); + x *= size * sinAngle; + y *= size * sinAngle; + z = anchor + ax * size * cosAngle; + start = x + z; + for ( a = 0.0f; a < 360.0f; a += 45.0f ) { + end = x * (float) cos( DEG2RAD(a + 45.0f) ) + y * (float) sin( DEG2RAD(a + 45.0f) ) + z; + gameRenderWorld->DebugLine( colorMagenta, anchor, start ); + gameRenderWorld->DebugLine( colorMagenta, start, end ); + start = end; + } +} + +/* +================ +idAFConstraint_ConeLimit::Save +================ +*/ +void idAFConstraint_ConeLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( coneAnchor ); + saveFile->WriteVec3( coneAxis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle ); + saveFile->WriteFloat( sinHalfAngle ); + saveFile->WriteFloat( cosHalfAngle ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_ConeLimit::Restore +================ +*/ +void idAFConstraint_ConeLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( coneAnchor ); + saveFile->ReadVec3( coneAxis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle ); + saveFile->ReadFloat( sinHalfAngle ); + saveFile->ReadFloat( cosHalfAngle ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFConstraint_PyramidLimit +// +//=============================================================== + +/* +================ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit +================ +*/ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit() { + type = CONSTRAINT_PYRAMIDLIMIT; + name = "pyramidLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_PyramidLimit::Setup +================ +*/ +void idAFConstraint_PyramidLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ) { + body1 = b1; + body2 = b2; + // setup the base and make sure the basis is orthonormal + pyramidBasis[2] = pyramidAxis; + pyramidBasis[2].Normalize(); + pyramidBasis[0] = baseAxis; + pyramidBasis[0] -= pyramidBasis[2] * baseAxis * pyramidBasis[2]; + pyramidBasis[0].Normalize(); + pyramidBasis[1] = pyramidBasis[0].Cross( pyramidBasis[2] ); + // pyramid top + this->pyramidAnchor = pyramidAnchor; + // angles + cosAngle[0] = (float) cos( DEG2RAD( pyramidAngle1 * 0.5f ) ); + cosAngle[1] = (float) cos( DEG2RAD( pyramidAngle2 * 0.5f ) ); + sinHalfAngle[0] = (float) sin( DEG2RAD( pyramidAngle1 * 0.25f ) ); + sinHalfAngle[1] = (float) sin( DEG2RAD( pyramidAngle2 * 0.25f ) ); + cosHalfAngle[0] = (float) cos( DEG2RAD( pyramidAngle1 * 0.25f ) ); + cosHalfAngle[1] = (float) cos( DEG2RAD( pyramidAngle2 * 0.25f ) ); + + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::SetAnchor +================ +*/ +void idAFConstraint_PyramidLimit::SetAnchor( const idVec3 &pyramidAnchor ) { + this->pyramidAnchor = pyramidAnchor; +} + +/* +================ +idAFConstraint_PyramidLimit::SetBody1Axis +================ +*/ +void idAFConstraint_PyramidLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::Evaluate +================ +*/ +void idAFConstraint_PyramidLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_PyramidLimit::ApplyFriction +================ +*/ +void idAFConstraint_PyramidLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_PyramidLimit::Add +================ +*/ +bool idAFConstraint_PyramidLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + int i; + float a[2]; + idVec6 J1row, J2row; + idMat3 worldBase; + idVec3 anchor, body1ax, ax[2], v, normal, pyramidVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + for ( i = 0; i < 2; i++ ) { + ax[i] = body1ax - worldBase[!i] * body1ax * worldBase[!i]; + ax[i].Normalize(); + a[i] = worldBase[2] * ax[i]; + } + + // if the body1 axis is inside the pyramid + if ( a[0] > cosAngle[0] && a[1] > cosAngle[1] ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward pyramid normal for the position the body1 axis went outside the pyramid + pyramidVector = worldBase[2]; + for ( i = 0; i < 2; i++ ) { + if ( a[i] <= cosAngle[i] ) { + v = ax[i].Cross( worldBase[2] ); + v.Normalize(); + q.x = v.x * sinHalfAngle[i]; + q.y = v.y * sinHalfAngle[i]; + q.z = v.z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + pyramidVector *= q.ToMat3(); + } + } + normal = pyramidVector.Cross( worldBase[2] ).Cross( pyramidVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * pyramidVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * pyramidVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_PyramidLimit::Translate +================ +*/ +void idAFConstraint_PyramidLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + pyramidAnchor += translation; + } +} + +/* +================ +idAFConstraint_PyramidLimit::Rotate +================ +*/ +void idAFConstraint_PyramidLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + pyramidAnchor *= rotation; + pyramidBasis[0] *= rotation.ToMat3(); + pyramidBasis[1] *= rotation.ToMat3(); + pyramidBasis[2] *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_PyramidLimit::DebugDraw +================ +*/ +void idAFConstraint_PyramidLimit::DebugDraw() { + int i; + float size = 10.0f; + idVec3 anchor, dir, p[4]; + idMat3 worldBase, m[2]; + idQuat q; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw the pyramid + for ( i = 0; i < 2; i++ ) { + q.x = worldBase[!i].x * sinHalfAngle[i]; + q.y = worldBase[!i].y * sinHalfAngle[i]; + q.z = worldBase[!i].z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + m[i] = q.ToMat3(); + } + + dir = worldBase[2] * size; + p[0] = anchor + m[0] * (m[1] * dir); + p[1] = anchor + m[0] * (m[1].Transpose() * dir); + p[2] = anchor + m[0].Transpose() * (m[1].Transpose() * dir); + p[3] = anchor + m[0].Transpose() * (m[1] * dir); + + for ( i = 0; i < 4; i++ ) { + gameRenderWorld->DebugLine( colorMagenta, anchor, p[i] ); + gameRenderWorld->DebugLine( colorMagenta, p[i], p[(i+1)&3] ); + } +} + +/* +================ +idAFConstraint_PyramidLimit::Save +================ +*/ +void idAFConstraint_PyramidLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( pyramidAnchor ); + saveFile->WriteMat3( pyramidBasis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle[0] ); + saveFile->WriteFloat( cosAngle[1] ); + saveFile->WriteFloat( sinHalfAngle[0] ); + saveFile->WriteFloat( sinHalfAngle[1] ); + saveFile->WriteFloat( cosHalfAngle[0] ); + saveFile->WriteFloat( cosHalfAngle[1] ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_PyramidLimit::Restore +================ +*/ +void idAFConstraint_PyramidLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( pyramidAnchor ); + saveFile->ReadMat3( pyramidBasis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle[0] ); + saveFile->ReadFloat( cosAngle[1] ); + saveFile->ReadFloat( sinHalfAngle[0] ); + saveFile->ReadFloat( sinHalfAngle[1] ); + saveFile->ReadFloat( cosHalfAngle[0] ); + saveFile->ReadFloat( cosHalfAngle[1] ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFConstraint_Suspension +// +//=============================================================== + +/* +================ +idAFConstraint_Suspension::idAFConstraint_Suspension +================ +*/ +idAFConstraint_Suspension::idAFConstraint_Suspension() { + type = CONSTRAINT_SUSPENSION; + name = "suspension"; + InitSize( 3 ); + fl.allowPrimary = false; + fl.frameConstraint = true; + + localOrigin.Zero(); + localAxis.Identity(); + suspensionUp = 0.0f; + suspensionDown = 0.0f; + suspensionKCompress = 0.0f; + suspensionDamping = 0.0f; + steerAngle = 0.0f; + friction = 2.0f; + motorEnabled = false; + motorForce = 0.0f; + motorVelocity = 0.0f; + wheelModel = NULL; + memset( &trace, 0, sizeof( trace ) ); + epsilon = LCP_EPSILON; +} + +/* +================ +idAFConstraint_Suspension::Setup +================ +*/ +void idAFConstraint_Suspension::Setup( const char *name, idAFBody *body, const idVec3 &origin, const idMat3 &axis, idClipModel *clipModel ) { + this->name = name; + body1 = body; + body2 = NULL; + localOrigin = ( origin - body->GetWorldOrigin() ) * body->GetWorldAxis().Transpose(); + localAxis = axis * body->GetWorldAxis().Transpose(); + wheelModel = clipModel; +} + +/* +================ +idAFConstraint_Suspension::SetSuspension +================ +*/ +void idAFConstraint_Suspension::SetSuspension( const float up, const float down, const float k, const float d, const float f ) { + suspensionUp = up; + suspensionDown = down; + suspensionKCompress = k; + suspensionDamping = d; + friction = f; +} + +/* +================ +idAFConstraint_Suspension::GetWheelOrigin +================ +*/ +const idVec3 idAFConstraint_Suspension::GetWheelOrigin() const { + return body1->GetWorldOrigin() + wheelOffset * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_Suspension::Evaluate +================ +*/ +void idAFConstraint_Suspension::Evaluate( float invTimeStep ) { + float velocity, suspensionLength, springLength, compression, dampingForce, springForce; + idVec3 origin, start, end, vel1, vel2, springDir, r, frictionDir, motorDir; + idMat3 axis; + idRotation rotation; + + axis = localAxis * body1->GetWorldAxis(); + origin = body1->GetWorldOrigin() + localOrigin * body1->GetWorldAxis(); + start = origin + suspensionUp * axis[2]; + end = origin - suspensionDown * axis[2]; + + rotation.SetVec( axis[2] ); + rotation.SetAngle( steerAngle ); + + axis *= rotation.ToMat3(); + + gameLocal.clip.Translation( trace, start, end, wheelModel, axis, MASK_SOLID, NULL ); + + wheelOffset = ( trace.endpos - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + + if ( trace.fraction >= 1.0f ) { + J1.SetSize( 0, 6 ); + if ( body2 ) { + J2.SetSize( 0, 6 ); + } + return; + } + + // calculate and add spring force + vel1 = body1->GetPointVelocity( start ); + if ( body2 ) { + vel2 = body2->GetPointVelocity( trace.c.point ); + } else { + vel2.Zero(); + } + + suspensionLength = suspensionUp + suspensionDown; + springDir = trace.endpos - start; + springLength = trace.fraction * suspensionLength; + dampingForce = suspensionDamping * idMath::Fabs( ( vel2 - vel1 ) * springDir ) / ( 1.0f + springLength * springLength ); + compression = suspensionLength - springLength; + springForce = compression * compression * suspensionKCompress - dampingForce; + + r = trace.c.point - body1->GetWorldOrigin(); + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0) = trace.c.normal; + J1.SubVec6(0).SubVec3(1) = r.Cross( trace.c.normal ); + c1.SetSize( 2 ); + c1[0] = 0.0f; + velocity = J1.SubVec6(0).SubVec3(0) * body1->GetLinearVelocity() + J1.SubVec6(0).SubVec3(1) * body1->GetAngularVelocity(); + + if ( body2 ) { + r = trace.c.point - body2->GetWorldOrigin(); + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0) = -trace.c.normal; + J2.SubVec6(0).SubVec3(1) = r.Cross( -trace.c.normal ); + c2.SetSize( 2 ); + c2[0] = 0.0f; + velocity += J2.SubVec6(0).SubVec3(0) * body2->GetLinearVelocity() + J2.SubVec6(0).SubVec3(1) * body2->GetAngularVelocity(); + } + + c1[0] = -compression; // + 0.5f * -velocity; + + e[0] = 1e-4f; + lo[0] = 0.0f; + hi[0] = springForce; + boxConstraint = NULL; + boxIndex[0] = -1; + + // project the friction direction into the contact plane + frictionDir = axis[1] - axis[1] * trace.c.normal * axis[1]; + frictionDir.Normalize(); + + r = trace.c.point - body1->GetWorldOrigin(); + + J1.SubVec6(1).SubVec3(0) = frictionDir; + J1.SubVec6(1).SubVec3(1) = r.Cross( frictionDir ); + c1[1] = 0.0f; + + if ( body2 ) { + r = trace.c.point - body2->GetWorldOrigin(); + + J2.SubVec6(1).SubVec3(0) = -frictionDir; + J2.SubVec6(1).SubVec3(1) = r.Cross( -frictionDir ); + c2[1] = 0.0f; + } + + lo[1] = -friction * physics->GetContactFrictionScale(); + hi[1] = friction * physics->GetContactFrictionScale(); + + boxConstraint = this; + boxIndex[1] = 0; + + + if ( motorEnabled ) { + // project the motor force direction into the contact plane + motorDir = axis[0] - axis[0] * trace.c.normal * axis[0]; + motorDir.Normalize(); + + r = trace.c.point - body1->GetWorldOrigin(); + + J1.ChangeSize( 3, J1.GetNumColumns() ); + J1.SubVec6(2).SubVec3(0) = -motorDir; + J1.SubVec6(2).SubVec3(1) = r.Cross( -motorDir ); + c1.ChangeSize( 3 ); + c1[2] = motorVelocity; + + if ( body2 ) { + r = trace.c.point - body2->GetWorldOrigin(); + + J2.ChangeSize( 3, J2.GetNumColumns() ); + J2.SubVec6(2).SubVec3(0) = -motorDir; + J2.SubVec6(2).SubVec3(1) = r.Cross( -motorDir ); + c2.ChangeSize( 3 ); + c2[2] = 0.0f; + } + + lo[2] = -motorForce; + hi[2] = motorForce; + boxIndex[2] = -1; + } +} + +/* +================ +idAFConstraint_Suspension::ApplyFriction +================ +*/ +void idAFConstraint_Suspension::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_Suspension::Translate +================ +*/ +void idAFConstraint_Suspension::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_Suspension::Rotate +================ +*/ +void idAFConstraint_Suspension::Rotate( const idRotation &rotation ) { +} + +/* +================ +idAFConstraint_Suspension::DebugDraw +================ +*/ +void idAFConstraint_Suspension::DebugDraw() { + idVec3 origin; + idMat3 axis; + idRotation rotation; + + axis = localAxis * body1->GetWorldAxis(); + + rotation.SetVec( axis[2] ); + rotation.SetAngle( steerAngle ); + + axis *= rotation.ToMat3(); + + if ( trace.fraction < 1.0f ) { + origin = trace.c.point; + + gameRenderWorld->DebugLine( colorWhite, origin, origin + 6.0f * axis[2] ); + gameRenderWorld->DebugLine( colorWhite, origin - 4.0f * axis[0], origin + 4.0f * axis[0] ); + gameRenderWorld->DebugLine( colorWhite, origin - 2.0f * axis[1], origin + 2.0f * axis[1] ); + } +} + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody() { + Init(); +} + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( const idStr &name, idClipModel *clipModel, float density ) { + + assert( clipModel ); + assert( clipModel->IsTraceModel() ); + + Init(); + + this->name = name; + this->clipModel = NULL; + + SetClipModel( clipModel ); + SetDensity( density ); + + current->worldOrigin = clipModel->GetOrigin(); + current->worldAxis = clipModel->GetAxis(); + *next = *current; + +} + +/* +================ +idAFBody::~idAFBody +================ +*/ +idAFBody::~idAFBody() { + delete clipModel; +} + +/* +================ +idAFBody::Init +================ +*/ +void idAFBody::Init() { + name = "noname"; + parent = NULL; + clipModel = NULL; + primaryConstraint = NULL; + tree = NULL; + + linearFriction = -1.0f; + angularFriction = -1.0f; + contactFriction = -1.0f; + bouncyness = -1.0f; + clipMask = 0; + + frictionDir = vec3_zero; + contactMotorDir = vec3_zero; + contactMotorVelocity = 0.0f; + contactMotorForce = 0.0f; + + mass = 1.0f; + invMass = 1.0f; + centerOfMass = vec3_zero; + inertiaTensor = mat3_identity; + inverseInertiaTensor = mat3_identity; + + current = &state[0]; + next = &state[1]; + current->worldOrigin = vec3_zero; + current->worldAxis = mat3_identity; + current->spatialVelocity = vec6_zero; + current->externalForce = vec6_zero; + *next = *current; + saved = *current; + atRestOrigin = vec3_zero; + atRestAxis = mat3_identity; + + s.Zero( 6 ); + totalForce.Zero( 6 ); + auxForce.Zero( 6 ); + acceleration.Zero( 6 ); + + response = NULL; + responseIndex = NULL; + numResponses = 0; + maxAuxiliaryIndex = 0; + maxSubTreeAuxiliaryIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); + + fl.selfCollision = true; + fl.isZero = true; +} + +/* +================ +idAFBody::SetClipModel +================ +*/ +void idAFBody::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && this->clipModel != clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idAFBody::SetFriction +================ +*/ +void idAFBody::SetFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f ) { + gameLocal.Warning( "idAFBody::SetFriction: friction out of range, linear = %.1f, angular = %.1f, contact = %.1f", linear, angular, contact ); + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idAFBody::SetBouncyness +================ +*/ +void idAFBody::SetBouncyness( float bounce ) { + if ( bounce < 0.0f || bounce > 1.0f ) { + gameLocal.Warning( "idAFBody::SetBouncyness: bouncyness out of range, bounce = %.1f", bounce ); + return; + } + bouncyness = bounce; +} + +/* +================ +idAFBody::SetDensity +================ +*/ +void idAFBody::SetDensity( float density, const idMat3 &inertiaScale ) { + + // get the body mass properties + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // make sure we have a valid mass + if ( mass <= 0.0f || IEEE_FLT_IS_NAN( mass ) ) { + gameLocal.Warning( "idAFBody::SetDensity: invalid mass for body '%s'", name.c_str() ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // make sure the center of mass is at the body origin + if ( !centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + gameLocal.Warning( "idAFBody::SetDentity: center of mass not at origin for body '%s'", name.c_str() ); + } + centerOfMass.Zero(); + + // calculate the inverse mass and inverse inertia tensor + invMass = 1.0f / mass; + if ( inertiaScale != mat3_identity ) { + inertiaTensor *= inertiaScale; + } + if ( inertiaTensor.IsDiagonal( 1e-3f ) ) { + inertiaTensor[0][1] = inertiaTensor[0][2] = 0.0f; + inertiaTensor[1][0] = inertiaTensor[1][2] = 0.0f; + inertiaTensor[2][0] = inertiaTensor[2][1] = 0.0f; + inverseInertiaTensor.Identity(); + inverseInertiaTensor[0][0] = 1.0f / inertiaTensor[0][0]; + inverseInertiaTensor[1][1] = 1.0f / inertiaTensor[1][1]; + inverseInertiaTensor[2][2] = 1.0f / inertiaTensor[2][2]; + } + else { + inverseInertiaTensor = inertiaTensor.Inverse(); + } +} + +/* +================ +idAFBody::SetFrictionDirection +================ +*/ +void idAFBody::SetFrictionDirection( const idVec3 &dir ) { + frictionDir = dir * current->worldAxis.Transpose(); + fl.useFrictionDir = true; +} + +/* +================ +idAFBody::GetFrictionDirection +================ +*/ +bool idAFBody::GetFrictionDirection( idVec3 &dir ) const { + if ( fl.useFrictionDir ) { + dir = frictionDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::SetContactMotorDirection +================ +*/ +void idAFBody::SetContactMotorDirection( const idVec3 &dir ) { + contactMotorDir = dir * current->worldAxis.Transpose(); + fl.useContactMotorDir = true; +} + +/* +================ +idAFBody::GetContactMotorDirection +================ +*/ +bool idAFBody::GetContactMotorDirection( idVec3 &dir ) const { + if ( fl.useContactMotorDir ) { + dir = contactMotorDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::GetPointVelocity +================ +*/ +idVec3 idAFBody::GetPointVelocity( const idVec3 &point ) const { + idVec3 r = point - current->worldOrigin; + return current->spatialVelocity.SubVec3(0) + current->spatialVelocity.SubVec3(1).Cross( r ); +} + +/* +================ +idAFBody::AddForce +================ +*/ +void idAFBody::AddForce( const idVec3 &point, const idVec3 &force ) { + current->externalForce.SubVec3(0) += force; + current->externalForce.SubVec3(1) += (point - current->worldOrigin).Cross( force ); +} + +/* +================ +idAFBody::InverseWorldSpatialInertiaMultiply + + dst = this->inverseWorldSpatialInertia * v; +================ +*/ +ID_INLINE void idAFBody::InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const { + const float *mPtr = inverseWorldSpatialInertia.ToFloatPtr(); + const float *vPtr = v; + float *dstPtr = dst.ToFloatPtr(); + + if ( fl.spatialInertiaSparse ) { + dstPtr[0] = mPtr[0*6+0] * vPtr[0]; + dstPtr[1] = mPtr[1*6+1] * vPtr[1]; + dstPtr[2] = mPtr[2*6+2] * vPtr[2]; + dstPtr[3] = mPtr[3*6+3] * vPtr[3] + mPtr[3*6+4] * vPtr[4] + mPtr[3*6+5] * vPtr[5]; + dstPtr[4] = mPtr[4*6+3] * vPtr[3] + mPtr[4*6+4] * vPtr[4] + mPtr[4*6+5] * vPtr[5]; + dstPtr[5] = mPtr[5*6+3] * vPtr[3] + mPtr[5*6+4] * vPtr[4] + mPtr[5*6+5] * vPtr[5]; + } else { + gameLocal.Warning( "spatial inertia is not sparse for body %s", name.c_str() ); + } +} + +/* +================ +idAFBody::Save +================ +*/ +void idAFBody::Save( idSaveGame *saveFile ) { + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteInt( clipMask ); + saveFile->WriteVec3( frictionDir ); + saveFile->WriteVec3( contactMotorDir ); + saveFile->WriteFloat( contactMotorVelocity ); + saveFile->WriteFloat( contactMotorForce ); + + saveFile->WriteFloat( mass ); + saveFile->WriteFloat( invMass ); + saveFile->WriteVec3( centerOfMass ); + saveFile->WriteMat3( inertiaTensor ); + saveFile->WriteMat3( inverseInertiaTensor ); + + saveFile->WriteVec3( current->worldOrigin ); + saveFile->WriteMat3( current->worldAxis ); + saveFile->WriteVec6( current->spatialVelocity ); + saveFile->WriteVec6( current->externalForce ); + saveFile->WriteVec3( atRestOrigin ); + saveFile->WriteMat3( atRestAxis ); +} + +/* +================ +idAFBody::Restore +================ +*/ +void idAFBody::Restore( idRestoreGame *saveFile ) { + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadInt( clipMask ); + saveFile->ReadVec3( frictionDir ); + saveFile->ReadVec3( contactMotorDir ); + saveFile->ReadFloat( contactMotorVelocity ); + saveFile->ReadFloat( contactMotorForce ); + + saveFile->ReadFloat( mass ); + saveFile->ReadFloat( invMass ); + saveFile->ReadVec3( centerOfMass ); + saveFile->ReadMat3( inertiaTensor ); + saveFile->ReadMat3( inverseInertiaTensor ); + + saveFile->ReadVec3( current->worldOrigin ); + saveFile->ReadMat3( current->worldAxis ); + saveFile->ReadVec6( current->spatialVelocity ); + saveFile->ReadVec6( current->externalForce ); + saveFile->ReadVec3( atRestOrigin ); + saveFile->ReadMat3( atRestAxis ); +} + + + +//=============================================================== +// M +// idAFTree MrE +// E +//=============================================================== + +/* +================ +idAFTree::Factor + + factor matrix for the primary constraints in the tree +================ +*/ +void idAFTree::Factor() const { + int i, j; + idAFBody *body; + idAFConstraint *child = NULL; + idMatX childI; + + childI.SetData( 6, 6, MATX_ALLOCA( 6 * 6 ) ); + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + if ( body->children.Num() ) { + + for ( j = 0; j < body->children.Num(); j++ ) { + + child = body->children[j]->primaryConstraint; + + // child->I = - child->body1->J.Transpose() * child->body1->I * child->body1->J; + childI.SetSize( child->J1.GetNumRows(), child->J1.GetNumRows() ); + child->body1->J.TransposeMultiply( child->body1->I ).Multiply( childI, child->body1->J ); + childI.Negate(); + + child->invI = childI; + if ( !child->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for constraint '%s'", + child->invI.GetNumRows(), child->invI.GetNumColumns(), child->GetName().c_str() ); + } + child->J = child->invI * child->J; + + body->I -= child->J.TransposeMultiply( childI ) * child->J; + } + + body->invI = body->I; + if ( !body->invI.InverseFastSelf() && child != NULL ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for body %s", + child->invI.GetNumRows(), child->invI.GetNumColumns(), body->GetName().c_str() ); + } + if ( body->primaryConstraint ) { + body->J = body->invI * body->J; + } + } + else if ( body->primaryConstraint ) { + body->J = body->inverseWorldSpatialInertia * body->J; + } + } +} + +/* +================ +idAFTree::Solve + + solve for primary constraints in the tree +================ +*/ +void idAFTree::Solve( int auxiliaryIndex ) const { + int i, j; + idAFBody *body, *child; + idAFConstraint *primaryConstraint; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + primaryConstraint = child->primaryConstraint; + + if ( !child->fl.isZero ) { + child->J.TransposeMultiplySub( primaryConstraint->s, child->s ); + primaryConstraint->fl.isZero = false; + } + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->J.TransposeMultiplySub( body->s, primaryConstraint->s ); + body->fl.isZero = false; + } + } + } + + bool useSymmetry = af_useSymmetry.GetBool(); + + // from the root down towards the leaves + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + + if ( primaryConstraint ) { + + if ( useSymmetry && body->parent->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->s = primaryConstraint->invI * primaryConstraint->s; + } + primaryConstraint->J.MultiplySub( primaryConstraint->s, primaryConstraint->body2->s ); + + primaryConstraint->lm = primaryConstraint->s; + + if ( useSymmetry && body->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( body->children.Num() ) { + if ( !body->fl.isZero ) { + body->s = body->invI * body->s; + } + body->J.MultiplySub( body->s, primaryConstraint->s ); + } + } else if ( body->children.Num() ) { + body->s = body->invI * body->s; + } + } +} + +/* +================ +idAFTree::Response + + calculate body forces in the tree in response to a constraint force +================ +*/ +void idAFTree::Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const { + int i, j; + idAFBody *body; + idAFConstraint *child, *primaryConstraint; + idVecX v; + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + body = constraint->body1; + if ( body->tree == this ) { + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + else { + body = constraint->body2; + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + return; + } + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + + // initialize right hand side to zero + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->s.Zero(); + primaryConstraint->fl.isZero = true; + } + body->s.Zero(); + body->fl.isZero = true; + body->GetResponseForce( body->numResponses ).Zero(); + } + + // set right hand side for first constrained body + body = constraint->body1; + if ( body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J1[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.Multiply( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.Multiply( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + } + + // set right hand side for second constrained body + body = constraint->body2; + if ( body && body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J2[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.MultiplyAdd( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.MultiplyAdd( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + } + + + // solve for primary constraints + Solve( auxiliaryIndex ); + + bool useSymmetry = af_useSymmetry.GetBool(); + + // store body forces in response to the constraint force + idVecX force; + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + if ( useSymmetry && body->maxAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + force.SetData( 6, body->response + body->numResponses * 8 ); + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( force, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( force, child->lm ); + } + + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } +} + +/* +================ +idAFTree::CalculateForces + + calculate forces on the bodies in the tree +================ +*/ +void idAFTree::CalculateForces( float timeStep ) const { + int i, j; + float invStep; + idAFBody *body; + idAFConstraint *child, *c, *primaryConstraint; + + // forces on bodies + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->totalForce.SubVec6(0) = body->current->externalForce + body->auxForce.SubVec6(0); + } + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + return; + } + + invStep = 1.0f / timeStep; + + // initialize right hand side + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + // b = ( J * acc + c ) + c = primaryConstraint; + c->s = c->J1 * c->body1->acceleration + c->J2 * c->body2->acceleration + invStep * ( c->c1 + c->c2 ); + c->fl.isZero = false; + } + body->s.Zero(); + body->fl.isZero = true; + } + + // solve for primary constraints + Solve(); + + // calculate forces on bodies after applying primary constraints + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( body->totalForce, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( body->totalForce, child->lm ); + } + } +} + +/* +================ +idAFTree::SetMaxSubTreeAuxiliaryIndex +================ +*/ +void idAFTree::SetMaxSubTreeAuxiliaryIndex() { + int i, j; + idAFBody *body, *child; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex; + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + if ( child->maxSubTreeAuxiliaryIndex > body->maxSubTreeAuxiliaryIndex ) { + body->maxSubTreeAuxiliaryIndex = child->maxSubTreeAuxiliaryIndex; + } + } + } +} + +/* +================ +idAFTree::SortBodies_r +================ +*/ +void idAFTree::SortBodies_r( idList&sortedList, idAFBody *body ) { + int i; + + for ( i = 0; i < body->children.Num(); i++ ) { + sortedList.Append( body->children[i] ); + } + for ( i = 0; i < body->children.Num(); i++ ) { + SortBodies_r( sortedList, body->children[i] ); + } +} + +/* +================ +idAFTree::SortBodies + + sort body list to make sure parents come first +================ +*/ +void idAFTree::SortBodies() { + int i; + idAFBody *body; + + // find the root + for ( i = 0; i < sortedBodies.Num(); i++ ) { + if ( !sortedBodies[i]->parent ) { + break; + } + } + + if ( i >= sortedBodies.Num() ) { + gameLocal.Error( "Articulated figure tree has no root." ); + } + + body = sortedBodies[i]; + sortedBodies.Clear(); + sortedBodies.Append( body ); + SortBodies_r( sortedBodies, body ); +} + +/* +================ +idAFTree::DebugDraw +================ +*/ +void idAFTree::DebugDraw( const idVec4 &color ) const { + int i; + idAFBody *body; + + for ( i = 1; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + gameRenderWorld->DebugArrow( color, body->parent->current->worldOrigin, body->current->worldOrigin, 1 ); + } +} + + +//=============================================================== +// M +// idPhysics_AF MrE +// E +//=============================================================== + +/* +================ +idPhysics_AF::EvaluateConstraints +================ +*/ +void idPhysics_AF::EvaluateConstraints( float timeStep ) { + int i; + float invTimeStep; + idAFBody *body; + idAFConstraint *c; + + invTimeStep = 1.0f / timeStep; + + // setup the constraint equations for the current position and orientation of the bodies + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + c = primaryConstraints[i]; + c->Evaluate( invTimeStep ); + c->J = c->J2; + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->Evaluate( invTimeStep ); + } + + // add contact constraints to the list with frame constraints + for ( i = 0; i < contactConstraints.Num(); i++ ) { + AddFrameConstraint( contactConstraints[i] ); + } + + // setup body primary constraint matrix + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->primaryConstraint ) { + body->J = body->primaryConstraint->J1.Transpose(); + } + } +} + +/* +================ +idPhysics_AF::EvaluateBodies +================ +*/ +void idPhysics_AF::EvaluateBodies( float timeStep ) { + int i; + idAFBody *body; + idMat3 axis; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // we transpose the axis before using it because idMat3 is column-major + axis = body->current->worldAxis.Transpose(); + + // if the center of mass is at the body point of reference + if ( body->centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia.Set( body->invMass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inverseInertiaTensor * axis.Transpose() ); + + body->fl.spatialInertiaSparse = true; + } + else { + idMat3 massMoment = body->mass * SkewSymmetric( body->centerOfMass ); + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, massMoment, + massMoment.Transpose(), axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia = body->I.InverseFast(); + + body->fl.spatialInertiaSparse = false; + } + + // initialize auxiliary constraint force to zero + body->auxForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddFrameConstraints +================ +*/ +void idPhysics_AF::AddFrameConstraints() { + int i; + + // add frame constraints to auxiliary constraints + for ( i = 0; i < frameConstraints.Num(); i++ ) { + auxiliaryConstraints.Append( frameConstraints[i] ); + } +} + +/* +================ +idPhysics_AF::RemoveFrameConstraints +================ +*/ +void idPhysics_AF::RemoveFrameConstraints() { + // remove all the frame constraints from the auxiliary constraints + auxiliaryConstraints.SetNum( auxiliaryConstraints.Num() - frameConstraints.Num() ); + frameConstraints.SetNum( 0 ); +} + +/* +================ +idPhysics_AF::ApplyFriction +================ +*/ +void idPhysics_AF::ApplyFriction( float timeStep, float endTimeMSec ) { + int i; + float invTimeStep; + + if ( af_skipFriction.GetBool() ) { + return; + } + + if ( jointFrictionDentStart < MS2SEC( endTimeMSec ) && jointFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( jointFrictionDentEnd - jointFrictionDentStart ) * 0.5f; + if ( jointFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + jointFrictionDentScale = 1.0f - ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart ) / halfTime; + } else { + jointFrictionDentScale = jointFrictionDent + ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart - halfTime ) / halfTime; + } + } else { + jointFrictionDentScale = 0.0f; + } + + if ( contactFrictionDentStart < MS2SEC( endTimeMSec ) && contactFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( contactFrictionDentEnd - contactFrictionDentStart ) * 0.5f; + if ( contactFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + contactFrictionDentScale = 1.0f - ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart ) / halfTime; + } else { + contactFrictionDentScale = contactFrictionDent + ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart - halfTime ) / halfTime; + } + } else { + contactFrictionDentScale = 0.0f; + } + + invTimeStep = 1.0f / timeStep; + + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + primaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < frameConstraints.Num(); i++ ) { + frameConstraints[i]->ApplyFriction( invTimeStep ); + } +} + +/* +================ +idPhysics_AF::PrimaryFactor +================ +*/ +void idPhysics_AF::PrimaryFactor() { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->Factor(); + } +} + +/* +================ +idPhysics_AF::PrimaryForces +================ +*/ +void idPhysics_AF::PrimaryForces( float timeStep ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->CalculateForces( timeStep ); + } +} + +/* +================ +idPhysics_AF::AuxiliaryForces +================ +*/ +void idPhysics_AF::AuxiliaryForces( float timeStep ) { + int i, j, k, l, n, m, s, numAuxConstraints, *index, *boxIndex; + float *ptr, *j1, *j2, *dstPtr, *forcePtr; + float invStep, u; + idAFBody *body; + idAFConstraint *constraint; + idVecX tmp; + idMatX jmk; + idVecX rhs, w, lm, lo, hi; + + // get the number of one dimensional auxiliary constraints + for ( numAuxConstraints = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxConstraints += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + if ( numAuxConstraints == 0 ) { + return; + } + + // allocate memory to store the body response to auxiliary constraint forces + forcePtr = (float *) _alloca16( bodies.Num() * numAuxConstraints * 8 * sizeof( float ) ); + index = (int *) _alloca16( bodies.Num() * numAuxConstraints * sizeof( int ) ); + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = forcePtr; + body->responseIndex = index; + body->numResponses = 0; + body->maxAuxiliaryIndex = 0; + forcePtr += numAuxConstraints * 8; + index += numAuxConstraints; + } + + // set on each body the largest index of an auxiliary constraint constraining the body + if ( af_useSymmetry.GetBool() ) { + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + if ( k > constraint->body1->maxAuxiliaryIndex ) { + constraint->body1->maxAuxiliaryIndex = k; + } + if ( constraint->body2 && k > constraint->body2->maxAuxiliaryIndex ) { + constraint->body2->maxAuxiliaryIndex = k; + } + } + } + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SetMaxSubTreeAuxiliaryIndex(); + } + } + + // calculate forces of primary constraints in response to the auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + // calculate body forces in the tree in response to the constraint force + constraint->body1->tree->Response( constraint, j, k ); + // if there is a second body which is part of a different tree + if ( constraint->body2 && constraint->body2->tree != constraint->body1->tree ) { + // calculate body forces in the second tree in response to the constraint force + constraint->body2->tree->Response( constraint, j, k ); + } + } + } + + // NOTE: the rows are 16 byte padded + jmk.SetData( numAuxConstraints, ((numAuxConstraints+3)&~3), MATX_ALLOCA( numAuxConstraints * ((numAuxConstraints+3)&~3) ) ); + tmp.SetData( 6, VECX_ALLOCA( 6 ) ); + + // create constraint matrix for auxiliary constraints using a mass matrix adjusted for the primary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->body1->InverseWorldSpatialInertiaMultiply( tmp, constraint->J1[j] ); + j1 = tmp.ToFloatPtr(); + ptr = constraint->body1->response; + index = constraint->body1->responseIndex; + dstPtr = jmk[k]; + s = af_useSymmetry.GetBool() ? k + 1 : numAuxConstraints; + for ( l = n = 0, m = index[n]; n < constraint->body1->numResponses && m < s; n++, m = index[n] ) { + while( l < m ) { + dstPtr[l++] = 0.0f; + } + dstPtr[l++] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + ptr += 8; + } + + while( l < s ) { + dstPtr[l++] = 0.0f; + } + + if ( constraint->body2 ) { + constraint->body2->InverseWorldSpatialInertiaMultiply( tmp, constraint->J2[j] ); + j2 = tmp.ToFloatPtr(); + ptr = constraint->body2->response; + index = constraint->body2->responseIndex; + for ( n = 0, m = index[n]; n < constraint->body2->numResponses && m < s; n++, m = index[n] ) { + dstPtr[m] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + ptr += 8; + } + } + } + } + + if ( af_useSymmetry.GetBool() ) { + n = jmk.GetNumColumns(); + for ( i = 0; i < numAuxConstraints; i++ ) { + ptr = jmk.ToFloatPtr() + ( i + 1 ) * n + i; + dstPtr = jmk.ToFloatPtr() + i * n + i + 1; + for ( j = i+1; j < numAuxConstraints; j++ ) { + *dstPtr++ = *ptr; + ptr += n; + } + } + } + + invStep = 1.0f / timeStep; + + // calculate body acceleration + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + } + + rhs.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lo.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + hi.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lm.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + boxIndex = (int *) _alloca16( numAuxConstraints * sizeof( int ) ); + + // set first index for special box constrained variables + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->firstIndex = k; + k += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + // initialize right hand side and low and high bounds for auxiliary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + n = k; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + j1 = constraint->J1[j]; + ptr = constraint->body1->acceleration.ToFloatPtr(); + rhs[k] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + rhs[k] += constraint->c1[j] * invStep; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->acceleration.ToFloatPtr(); + rhs[k] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + rhs[k] += constraint->c2[j] * invStep; + } + + rhs[k] = -rhs[k]; + lo[k] = constraint->lo[j]; + hi[k] = constraint->hi[j]; + + if ( constraint->boxIndex[j] >= 0 ) { + if ( constraint->boxConstraint->fl.isPrimary ) { + gameLocal.Error( "cannot reference primary constraints for the box index" ); + } + boxIndex[k] = constraint->boxConstraint->firstIndex + constraint->boxIndex[j]; + } + else { + boxIndex[k] = -1; + } + jmk[k][k] += constraint->e[j] * invStep; + } + } + +#ifdef AF_TIMINGS + timer_lcp.Start(); +#endif + + // calculate lagrange multipliers for auxiliary constraints + if ( !lcp->Solve( jmk, lm, rhs, lo, hi, boxIndex ) ) { + return; // bad monkey! + } + +#ifdef AF_TIMINGS + timer_lcp.Stop(); +#endif + + // calculate auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->lm[j] = u = lm[k]; + + j1 = constraint->J1[j]; + ptr = constraint->body1->auxForce.ToFloatPtr(); + ptr[0] += j1[0] * u; ptr[1] += j1[1] * u; ptr[2] += j1[2] * u; + ptr[3] += j1[3] * u; ptr[4] += j1[4] * u; ptr[5] += j1[5] * u; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->auxForce.ToFloatPtr(); + ptr[0] += j2[0] * u; ptr[1] += j2[1] * u; ptr[2] += j2[2] * u; + ptr[3] += j2[3] * u; ptr[4] += j2[4] * u; ptr[5] += j2[5] * u; + } + } + } + + // recalculate primary constraint forces in response to auxiliary constraint forces + PrimaryForces( timeStep ); + + // clear pointers pointing to stack space so tools don't get confused + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = NULL; + body->responseIndex = NULL; + } +} + +/* +================ +idPhysics_AF::VerifyContactConstraints +================ +*/ +void idPhysics_AF::VerifyContactConstraints() { +#if 0 + int i; + float impulseNumerator, impulseDenominator; + idVec3 r, velocity, normalVelocity, normal, impulse; + idAFBody *body; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + + r = contact.point - body->GetCenterOfMass(); + + // calculate velocity at contact point + velocity = body->GetLinearVelocity() + body->GetAngularVelocity().Cross( r ); + + // velocity along normal vector + normalVelocity = ( velocity * contact.normal ) * contact.normal; + + // if moving towards the surface at the contact point + if ( normalVelocity * contact.normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = body->GetInverseMass() + ( ( body->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal * 1.0001f; + + // apply impulse + body->SetLinearVelocity( body->GetLinearVelocity() + impulse ); + body->SetAngularVelocity( body->GetAngularVelocity() + r.Cross( impulse ) ); + } + } +#else + int i; + idAFBody *body; + idVec3 normal; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + normal = contactConstraints[i]->GetContact().normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + body = contactConstraints[i]->body2; + if ( !body ) { + continue; + } + normal = -normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + } +#endif +} + +/* +================ +idPhysics_AF::Evolve +================ +*/ +void idPhysics_AF::Evolve( float timeStep ) { + int i; + float angle; + idVec3 vec; + idAFBody *body; + idVec6 force; + idRotation rotation; + float vSqr, maxLinearVelocity, maxAngularVelocity; + + maxLinearVelocity = af_maxLinearVelocity.GetFloat() / timeStep; + maxAngularVelocity = af_maxAngularVelocity.GetFloat() / timeStep; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // calculate the spatial velocity for the next physics state + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->next->spatialVelocity = body->current->spatialVelocity + timeStep * body->acceleration.SubVec6(0); + + if ( maxLinearVelocity > 0.0f ) { + // cap the linear velocity + vSqr = body->next->spatialVelocity.SubVec3(0).LengthSqr(); + if ( vSqr > Square( maxLinearVelocity ) ) { + body->next->spatialVelocity.SubVec3(0) *= idMath::InvSqrt( vSqr ) * maxLinearVelocity; + } + } + + if ( maxAngularVelocity > 0.0f ) { + // cap the angular velocity + vSqr = body->next->spatialVelocity.SubVec3(1).LengthSqr(); + if ( vSqr > Square( maxAngularVelocity ) ) { + body->next->spatialVelocity.SubVec3(1) *= idMath::InvSqrt( vSqr ) * maxAngularVelocity; + } + } + } + + // make absolutely sure all contact constraints are satisfied + VerifyContactConstraints(); + + // calculate the position of the bodies for the next physics state + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // translate world origin + body->next->worldOrigin = body->current->worldOrigin + timeStep * body->next->spatialVelocity.SubVec3( 0 ); + + // convert angular velocity to a rotation matrix + vec = body->next->spatialVelocity.SubVec3( 1 ); + angle = -timeStep * (float) RAD2DEG( vec.Normalize() ); + rotation = idRotation( vec3_origin, vec, angle ); + rotation.Normalize180(); + + // rotate world axis + body->next->worldAxis = body->current->worldAxis * rotation.ToMat3(); + body->next->worldAxis.OrthoNormalizeSelf(); + + // linear and angular friction + body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0); + body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1); + } +} + +/* +================ +idPhysics_AF::CollisionImpulse + + apply impulse to the colliding bodies + the current state of the body should be set to the moment of impact + this is silly as it doesn't take the AF structure into account +================ +*/ +bool idPhysics_AF::CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ) { + idVec3 r, velocity, impulse; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator; + impactInfo_t info; + idEntity *ent; + + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent == self ) { + return false; + } + + // get info from other entity involved + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + // collision point relative to the body center of mass + r = collision.c.point - (body->current->worldOrigin + body->centerOfMass * body->current->worldAxis); + // the velocity at the collision point + velocity = body->current->spatialVelocity.SubVec3(0) + body->current->spatialVelocity.SubVec3(1).Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + // never stick + if ( velocity * collision.c.normal > 0.0f ) { + velocity = collision.c.normal; + } + inverseWorldInertiaTensor = body->current->worldAxis.Transpose() * body->inverseInertiaTensor * body->current->worldAxis; + impulseNumerator = -( 1.0f + body->bouncyness ) * ( velocity * collision.c.normal ); + impulseDenominator = body->invMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + + // callback to self to let the entity know about the impact + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_AF::ApplyCollisions +================ +*/ +bool idPhysics_AF::ApplyCollisions( float timeStep ) { + int i; + + for ( i = 0; i < collisions.Num(); i++ ) { + if ( CollisionImpulse( timeStep, collisions[i].body, collisions[i].trace ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_AF::SetupCollisionForBody +================ +*/ +idEntity *idPhysics_AF::SetupCollisionForBody( idAFBody *body ) const { + int i; + idAFBody *b; + idEntity *passEntity; + + passEntity = NULL; + + if ( !selfCollision || !body->fl.selfCollision || af_skipSelfCollision.GetBool() ) { + + // disable all bodies + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } + + // don't collide with world collision model if attached to the world + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } + } + + } else { + + // enable all bodies that have self collision + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->fl.selfCollision ) { + bodies[i]->clipModel->Enable(); + } else { + bodies[i]->clipModel->Disable(); + } + } + + // don't let the body collide with itself + body->clipModel->Disable(); + + // disable any bodies attached with constraints + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } else { + if ( body->constraints[i]->body1 == body ) { + b = body->constraints[i]->body2; + } else if ( body->constraints[i]->body2 == body ) { + b = body->constraints[i]->body1; + } else { + continue; + } + // don't collide with this body + b->clipModel->Disable(); + } + } + } + + return passEntity; +} + +/* +================ +idPhysics_AF::CheckForCollisions + + check for collisions between the current and next state + if there is a collision the next state is set to the state at the moment of impact + assumes all bodies are linked for collision detection and relinks all bodies after moving them +================ +*/ +void idPhysics_AF::CheckForCollisions( float timeStep ) { +// #define TEST_COLLISION_DETECTION + int i, index; + idAFBody *body; + idMat3 axis; + idRotation rotation; + trace_t collision; + idEntity *passEntity; + + // clear list with collisions + collisions.SetNum( 0 ); + + if ( !enableCollision ) { + return; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipMask != 0 ) { + + passEntity = SetupCollisionForBody( body ); + +#ifdef TEST_COLLISION_DETECTION + bool startsolid = false; + if ( gameLocal.clip.Contents( body->current->worldOrigin, body->clipModel, + body->current->worldAxis, body->clipMask, passEntity ) ) { + startsolid = true; + } +#endif + + TransposeMultiply( body->current->worldAxis, body->next->worldAxis, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( body->current->worldOrigin ); + + // if there was a collision + if ( gameLocal.clip.Motion( collision, body->current->worldOrigin, body->next->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ) ) { + + // set the next state to the state at the moment of impact + body->next->worldOrigin = collision.endpos; + body->next->worldAxis = collision.endAxis; + + // add collision to the list + index = collisions.Num(); + collisions.SetNum( index + 1 ); + collisions[index].trace = collision; + collisions[index].body = body; + } + +#ifdef TEST_COLLISION_DETECTION + if ( gameLocal.clip.Contents( body->next->worldOrigin, body->clipModel, + body->next->worldAxis, body->clipMask, passEntity ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + } + + body->clipModel->Link( gameLocal.clip, self, body->clipModel->GetId(), body->next->worldOrigin, body->next->worldAxis ); + } +} + +/* +================ +idPhysics_AF::EvaluateContacts +================ +*/ +bool idPhysics_AF::EvaluateContacts() { + int i, j, k, numContacts, numBodyContacts; + idAFBody *body; + contactInfo_t contactInfo[10]; + idEntity *passEntity; + idVecX dir( 6, VECX_ALLOCA( 6 ) ); + + // evaluate bodies + EvaluateBodies( current.lastTimeStep ); + + // remove all existing contacts + ClearContacts(); + + contactBodies.SetNum( 0 ); + + if ( !enableCollision ) { + return false; + } + + // find all the contacts + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipMask == 0 ) { + continue; + } + + passEntity = SetupCollisionForBody( body ); + + body->InverseWorldSpatialInertiaMultiply( dir, body->current->externalForce.ToFloatPtr() ); + dir.SubVec6(0) = body->current->spatialVelocity + current.lastTimeStep * dir.SubVec6(0); + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); + + numContacts = gameLocal.clip.Contacts( contactInfo, 10, body->current->worldOrigin, dir.SubVec6(0), 2.0f, //CONTACT_EPSILON, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ); + +#if 1 + // merge nearby contacts between the same bodies + // and assure there are at most three planar contacts between any pair of bodies + for ( j = 0; j < numContacts; j++ ) { + + numBodyContacts = 0; + for ( k = 0; k < contacts.Num(); k++ ) { + if ( contacts[k].entityNum == contactInfo[j].entityNum ) { + if ( ( contacts[k].id == i && contactInfo[j].id == contactBodies[k] ) || + ( contactBodies[k] == i && contacts[k].id == contactInfo[j].id ) ) { + + if ( ( contacts[k].point - contactInfo[j].point ).LengthSqr() < Square( 2.0f ) ) { + break; + } + if ( idMath::Fabs( contacts[k].normal * contactInfo[j].normal ) > 0.9f ) { + numBodyContacts++; + } + } + } + } + + if ( k >= contacts.Num() && numBodyContacts < 3 ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } + } + +#else + + for ( j = 0; j < numContacts; j++ ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } +#endif + + } + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_AF::SetupContactConstraints +================ +*/ +void idPhysics_AF::SetupContactConstraints() { + int i; + + // make sure enough contact constraints are allocated + contactConstraints.AssureSizeAlloc( contacts.Num(), idListNewElement ); + contactConstraints.SetNum( contacts.Num() ); + + // setup contact constraints + for ( i = 0; i < contacts.Num(); i++ ) { + // add contact constraint + contactConstraints[i]->physics = this; + if ( contacts[i].entityNum == self->entityNumber ) { + contactConstraints[i]->Setup( bodies[contactBodies[i]], bodies[ contacts[i].id ], contacts[i] ); + } + else { + contactConstraints[i]->Setup( bodies[contactBodies[i]], NULL, contacts[i] ); + } + } +} + +/* +================ +idPhysics_AF::ApplyContactForces +================ +*/ +void idPhysics_AF::ApplyContactForces() { +#if 0 + int i; + idEntity *ent; + idVec3 force; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + if ( contactConstraints[i]->body2 != NULL ) { + continue; + } + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + ent = gameLocal.entities[contact.entityNum]; + if ( !ent ) { + continue; + } + force.Zero(); + ent->AddForce( self, contact.id, contact.point, force ); + } +#endif +} + +/* +================ +idPhysics_AF::ClearExternalForce +================ +*/ +void idPhysics_AF::ClearExternalForce() { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // clear external force + body->current->externalForce.Zero(); + body->next->externalForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddGravity +================ +*/ +void idPhysics_AF::AddGravity() { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + // add gravitational force + body->current->externalForce.SubVec3( 0 ) += body->mass * gravityVector; + } +} + +/* +================ +idPhysics_AF::SwapStates +================ +*/ +void idPhysics_AF::SwapStates() { + int i; + idAFBody *body; + AFBodyPState_t *swap; + + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + + // swap the current and next state for next simulation step + swap = body->current; + body->current = body->next; + body->next = swap; + } +} + +/* +================ +idPhysics_AF::UpdateClipModels +================ +*/ +void idPhysics_AF::UpdateClipModels() { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->clipModel->Link( gameLocal.clip, self, body->clipModel->GetId(), body->current->worldOrigin, body->current->worldAxis ); + } +} + +/* +================ +idPhysics_AF::SetSuspendSpeed +================ +*/ +void idPhysics_AF::SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ) { + this->suspendVelocity = velocity; + this->suspendAcceleration = acceleration; +} + +/* +================ +idPhysics_AF::SetSuspendTime +================ +*/ +void idPhysics_AF::SetSuspendTime( const float minTime, const float maxTime ) { + this->minMoveTime = minTime; + this->maxMoveTime = maxTime; +} + +/* +================ +idPhysics_AF::SetSuspendTolerance +================ +*/ +void idPhysics_AF::SetSuspendTolerance( const float noMoveTime, const float noMoveTranslation, const float noMoveRotation ) { + this->noMoveTime = noMoveTime; + this->noMoveTranslation = noMoveTranslation; + this->noMoveRotation = noMoveRotation; +} + +/* +================ +idPhysics_AF::SetTimeScaleRamp +================ +*/ +void idPhysics_AF::SetTimeScaleRamp( const float start, const float end ) { + timeScaleRampStart = start; + timeScaleRampEnd = end; +} + +/* +================ +idPhysics_AF::SetJointFrictionDent +================ +*/ +void idPhysics_AF::SetJointFrictionDent( const float dent, const float start, const float end ) { + jointFrictionDent = dent; + jointFrictionDentStart = start; + jointFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetJointFrictionScale +================ +*/ +float idPhysics_AF::GetJointFrictionScale() const { + if ( jointFrictionDentScale > 0.0f ) { + return jointFrictionDentScale; + } else if ( jointFrictionScale > 0.0f ) { + return jointFrictionScale; + } else if ( af_jointFrictionScale.GetFloat() > 0.0f ) { + return af_jointFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::SetContactFrictionDent +================ +*/ +void idPhysics_AF::SetContactFrictionDent( const float dent, const float start, const float end ) { + contactFrictionDent = dent; + contactFrictionDentStart = start; + contactFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetContactFrictionScale +================ +*/ +float idPhysics_AF::GetContactFrictionScale() const { + if ( contactFrictionDentScale > 0.0f ) { + return contactFrictionDentScale; + } else if ( contactFrictionScale > 0.0f ) { + return contactFrictionScale; + } else if ( af_contactFrictionScale.GetFloat() > 0.0f ) { + return af_contactFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::TestIfAtRest +================ +*/ +bool idPhysics_AF::TestIfAtRest( float timeStep ) { + int i; + float translationSqr, maxTranslationSqr, rotation, maxRotation; + idAFBody *body; + + if ( current.atRest >= 0 ) { + return true; + } + + current.activateTime += timeStep; + + // if the simulation should never be suspended before a certaint amount of time passed + if ( minMoveTime > 0.0f && current.activateTime < minMoveTime ) { + return false; + } + + // if the simulation should always be suspended after a certain amount time passed + if ( maxMoveTime > 0.0f && current.activateTime > maxMoveTime ) { + return true; + } + + // test if all bodies hardly moved over a period of time + if ( current.noMoveTime == 0.0f ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->atRestOrigin = body->current->worldOrigin; + body->atRestAxis = body->current->worldAxis; + } + current.noMoveTime += timeStep; + } + else if ( current.noMoveTime > noMoveTime ) { + current.noMoveTime = 0.0f; + maxTranslationSqr = 0.0f; + maxRotation = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + translationSqr = ( body->current->worldOrigin - body->atRestOrigin ).LengthSqr(); + if ( translationSqr > maxTranslationSqr ) { + maxTranslationSqr = translationSqr; + } + rotation = ( body->atRestAxis.Transpose() * body->current->worldAxis ).ToRotation().GetAngle(); + if ( rotation > maxRotation ) { + maxRotation = rotation; + } + } + + if ( maxTranslationSqr < Square( noMoveTranslation ) && maxRotation < noMoveRotation ) { + // hardly moved over a period of time so the articulated figure may come to rest + return true; + } + } else { + current.noMoveTime += timeStep; + } + + // test if the velocity or acceleration of any body is still too large to come to rest + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->current->spatialVelocity.SubVec3(0).LengthSqr() > Square( suspendVelocity[0] ) ) { + return false; + } + if ( body->current->spatialVelocity.SubVec3(1).LengthSqr() > Square( suspendVelocity[1] ) ) { + return false; + } + if ( body->acceleration.SubVec3(0).LengthSqr() > Square( suspendAcceleration[0] ) ) { + return false; + } + if ( body->acceleration.SubVec3(1).LengthSqr() > Square( suspendAcceleration[1] ) ) { + return false; + } + } + + // all bodies have a velocity and acceleration small enough to come to rest + return true; +} + +/* +================ +idPhysics_AF::Rest +================ +*/ +void idPhysics_AF::Rest() { + int i; + + current.atRest = gameLocal.time; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity.Zero(); + bodies[i]->current->externalForce.Zero(); + } + + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::Activate +================ +*/ +void idPhysics_AF::Activate() { + // if the articulated figure was at rest + if ( current.atRest >= 0 ) { + // normally gravity is added at the end of a simulation frame + // if the figure was at rest add gravity here so it is applied this simulation frame + AddGravity(); + // reset the active time for the max move time + current.activateTime = 0.0f; + } + current.atRest = -1; + current.noMoveTime = 0.0f; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_AF::PutToRest() { + Rest(); +} + +/* +================ +idPhysics_AF::EnableImpact +================ +*/ +void idPhysics_AF::EnableImpact() { + noImpact = false; +} + +/* +================ +idPhysics_AF::DisableImpact +================ +*/ +void idPhysics_AF::DisableImpact() { + noImpact = true; +} + +/* +================ +idPhysics_AF::AddPushVelocity +================ +*/ +void idPhysics_AF::AddPushVelocity( const idVec6 &pushVelocity ) { + int i; + + if ( pushVelocity != vec6_origin ) { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity += pushVelocity; + } + } +} + +/* +================ +idPhysics_AF::SetClipModel +================ +*/ +void idPhysics_AF::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_AF::GetClipModel +================ +*/ +idClipModel *idPhysics_AF::GetClipModel( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel(); + } + return NULL; +} + +/* +================ +idPhysics_AF::GetNumClipModels +================ +*/ +int idPhysics_AF::GetNumClipModels() const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::SetMass +================ +*/ +void idPhysics_AF::SetMass( float mass, int id ) { + if ( id >= 0 && id < bodies.Num() ) { + } + else { + forceTotalMass = mass; + } + SetChanged(); +} + +/* +================ +idPhysics_AF::GetMass +================ +*/ +float idPhysics_AF::GetMass( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->mass; + } + return totalMass; +} + +/* +================ +idPhysics_AF::SetContents +================ +*/ +void idPhysics_AF::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < bodies.Num() ) { + bodies[id]->GetClipModel()->SetContents( contents ); + } + else { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->GetClipModel()->SetContents( contents ); + } + } +} + +/* +================ +idPhysics_AF::GetContents +================ +*/ +int idPhysics_AF::GetContents( int id ) const { + int i, contents; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetContents(); + } + else { + contents = 0; + for ( i = 0; i < bodies.Num(); i++ ) { + contents |= bodies[i]->GetClipModel()->GetContents(); + } + return contents; + } +} + +/* +================ +idPhysics_AF::GetBounds +================ +*/ +const idBounds &idPhysics_AF::GetBounds( int id ) const { + int i; + static idBounds relBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetBounds(); + } + else if ( !bodies.Num() ) { + relBounds.Zero(); + return relBounds; + } + else { + relBounds = bodies[0]->GetClipModel()->GetBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + idBounds bounds; + idVec3 origin = ( bodies[i]->GetWorldOrigin() - bodies[0]->GetWorldOrigin() ) * bodies[0]->GetWorldAxis().Transpose(); + idMat3 axis = bodies[i]->GetWorldAxis() * bodies[0]->GetWorldAxis().Transpose(); + bounds.FromTransformedBounds( bodies[i]->GetClipModel()->GetBounds(), origin, axis ); + relBounds += bounds; + } + return relBounds; + } +} + +/* +================ +idPhysics_AF::GetAbsBounds +================ +*/ +const idBounds &idPhysics_AF::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetAbsBounds(); + } + else if ( !bodies.Num() ) { + absBounds.Zero(); + return absBounds; + } + else { + absBounds = bodies[0]->GetClipModel()->GetAbsBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + absBounds += bodies[i]->GetClipModel()->GetAbsBounds(); + } + return absBounds; + } +} + +/* +================ +idPhysics_AF::Evaluate +================ +*/ +bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) { + float timeStep; + + if ( timeScaleRampStart < MS2SEC( endTimeMSec ) && timeScaleRampEnd > MS2SEC( endTimeMSec ) ) { + timeStep = MS2SEC( timeStepMSec ) * ( MS2SEC( endTimeMSec ) - timeScaleRampStart ) / ( timeScaleRampEnd - timeScaleRampStart ); + } else if ( af_timeScale.GetFloat() != 1.0f ) { + timeStep = MS2SEC( timeStepMSec ) * af_timeScale.GetFloat(); + } else { + timeStep = MS2SEC( timeStepMSec ) * timeScale; + } + current.lastTimeStep = timeStep; + + + // if the articulated figure changed + if ( changedAF || ( linearTime != af_useLinearTime.GetBool() ) ) { + BuildTrees(); + changedAF = false; + linearTime = af_useLinearTime.GetBool(); + } + + // get the new master position + if ( masterBody ) { + idVec3 masterOrigin; + idMat3 masterAxis; + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( current.atRest >= 0 && ( masterBody->current->worldOrigin != masterOrigin || masterBody->current->worldAxis != masterAxis ) ) { + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + + // if the simulation is suspended because the figure is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // move the af velocity into the frame of a pusher + AddPushVelocity( -current.pushVelocity ); + +#ifdef AF_TIMINGS + timer_total.Start(); +#endif + +#ifdef AF_TIMINGS + timer_collision.Start(); +#endif + + // evaluate contacts + EvaluateContacts(); + + // setup contact constraints + SetupContactConstraints(); + +#ifdef AF_TIMINGS + timer_collision.Stop(); +#endif + + // evaluate constraint equations + EvaluateConstraints( timeStep ); + + // apply friction + ApplyFriction( timeStep, endTimeMSec ); + + // add frame constraints + AddFrameConstraints(); + +#ifdef AF_TIMINGS + int i, numPrimary = 0, numAuxiliary = 0; + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + numPrimary += primaryConstraints[i]->J1.GetNumRows(); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxiliary += auxiliaryConstraints[i]->J1.GetNumRows(); + } + timer_pc.Start(); +#endif + + // factor matrices for primary constraints + PrimaryFactor(); + + // calculate forces on bodies after applying primary constraints + PrimaryForces( timeStep ); + +#ifdef AF_TIMINGS + timer_pc.Stop(); + timer_ac.Start(); +#endif + + // calculate and apply auxiliary constraint forces + AuxiliaryForces( timeStep ); + +#ifdef AF_TIMINGS + timer_ac.Stop(); +#endif + + // evolve current state to next state + Evolve( timeStep ); + + // debug graphics + DebugDraw(); + + // clear external forces on all bodies + ClearExternalForce(); + + // apply contact force to other entities + ApplyContactForces(); + + // remove all frame constraints + RemoveFrameConstraints(); + +#ifdef AF_TIMINGS + timer_collision.Start(); +#endif + + // check for collisions between current and next state + CheckForCollisions( timeStep ); + +#ifdef AF_TIMINGS + timer_collision.Stop(); +#endif + + // swap the current and next state + SwapStates(); + + // make sure all clip models are disabled in case they were enabled for self collision + if ( selfCollision && !af_skipSelfCollision.GetBool() ) { + DisableClip(); + } + + // apply collision impulses + if ( ApplyCollisions( timeStep ) ) { + current.atRest = gameLocal.time; + comeToRest = true; + } + + // test if the simulation can be suspended because the whole figure is at rest + if ( comeToRest && TestIfAtRest( timeStep ) ) { + Rest(); + } else { + ActivateContactEntities(); + } + + // add gravitational force + AddGravity(); + + // move the af velocity back into the world frame + AddPushVelocity( current.pushVelocity ); + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); + Rest(); + } + +#ifdef AF_TIMINGS + timer_total.Stop(); + + if ( af_showTimings.GetInteger() == 1 ) { + gameLocal.Printf( "%12s: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + else if ( af_showTimings.GetInteger() == 2 ) { + numArticulatedFigures++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "af %d: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + numArticulatedFigures, + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + } + + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numArticulatedFigures = 0; + timer_total.Clear(); + timer_pc.Clear(); + timer_ac.Clear(); + timer_collision.Clear(); + timer_lcp.Clear(); + } +#endif + + return true; +} + +/* +================ +idPhysics_AF::UpdateTime +================ +*/ +void idPhysics_AF::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_AF::GetTime +================ +*/ +int idPhysics_AF::GetTime() const { + return gameLocal.time; +} + +/* +================ +DrawTraceModelSilhouette +================ +*/ +void DrawTraceModelSilhouette( const idVec3 &projectionOrigin, const idClipModel *clipModel ) { + int i, numSilEdges; + int silEdges[MAX_TRACEMODEL_EDGES]; + idVec3 v1, v2; + const idTraceModel *trm = clipModel->GetTraceModel(); + const idVec3 &origin = clipModel->GetOrigin(); + const idMat3 &axis = clipModel->GetAxis(); + + numSilEdges = trm->GetProjectionSilhouetteEdges( ( projectionOrigin - origin ) * axis.Transpose(), silEdges ); + for ( i = 0; i < numSilEdges; i++ ) { + v1 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INT32_SIGNBITSET( silEdges[i] ) ] ]; + v2 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INT32_SIGNBITNOTSET( silEdges[i] ) ] ]; + gameRenderWorld->DebugArrow( colorRed, origin + v1 * axis, origin + v2 * axis, 1 ); + } +} + +/* +================ +idPhysics_AF::DebugDraw +================ +*/ +void idPhysics_AF::DebugDraw() { + int i; + idAFBody *body, *highlightBody = NULL, *constrainedBody1 = NULL, *constrainedBody2 = NULL; + idAFConstraint *constraint; + idVec3 center; + idMat3 axis; + + if ( af_highlightConstraint.GetString()[0] ) { + constraint = GetConstraint( af_highlightConstraint.GetString() ); + if ( constraint ) { + constraint->GetCenter( center ); + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DebugCone( colorYellow, center, (axis[2] - axis[1]) * 4.0f, 0.0f, 1.0f, 0 ); + + if ( af_showConstrainedBodies.GetBool() ) { + cvarSystem->SetCVarString( "cm_drawColor", colorCyan.ToString( 0 ) ); + constrainedBody1 = constraint->body1; + if ( constrainedBody1 ) { + collisionModelManager->DrawModel( constrainedBody1->clipModel->Handle(), constrainedBody1->clipModel->GetOrigin(), + constrainedBody1->clipModel->GetAxis(), vec3_origin, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorBlue.ToString( 0 ) ); + constrainedBody2 = constraint->body2; + if ( constrainedBody2 ) { + collisionModelManager->DrawModel( constrainedBody2->clipModel->Handle(), constrainedBody2->clipModel->GetOrigin(), + constrainedBody2->clipModel->GetAxis(), vec3_origin, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + } + + if ( af_highlightBody.GetString()[0] ) { + highlightBody = GetBody( af_highlightBody.GetString() ); + if ( highlightBody ) { + cvarSystem->SetCVarString( "cm_drawColor", colorYellow.ToString( 0 ) ); + collisionModelManager->DrawModel( highlightBody->clipModel->Handle(), highlightBody->clipModel->GetOrigin(), + highlightBody->clipModel->GetAxis(), vec3_origin, 0.0f ); + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + + if ( af_showBodies.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + if ( body == constrainedBody1 || body == constrainedBody2 ) { + continue; + } + if ( body == highlightBody ) { + continue; + } + collisionModelManager->DrawModel( body->clipModel->Handle(), body->clipModel->GetOrigin(), + body->clipModel->GetAxis(), vec3_origin, 0.0f ); + //DrawTraceModelSilhouette( gameLocal.GetLocalPlayer()->GetEyePosition(), body->clipModel ); + } + } + + if ( af_showBodyNames.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( body->GetName().c_str(), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showMass.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( va( "\n%1.2f", 1.0f / body->GetInverseMass() ), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showTotalMass.GetBool() ) { + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DrawText( va( "\n%1.2f", totalMass ), bodies[0]->GetWorldOrigin() + axis[2] * 8.0f, 0.15f, colorCyan, axis, 1 ); + } + + if ( af_showInertia.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + idMat3 &I = body->inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + body->GetWorldOrigin(), 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showVelocity.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + DrawVelocity( bodies[i]->clipModel->GetId(), 0.1f, 4.0f ); + } + } + + if ( af_showConstraints.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->DebugDraw(); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->DebugDraw(); + } + } + } + + if ( af_showConstraintNames.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + } + + if ( af_showTrees.GetBool() || ( af_showActive.GetBool() && current.atRest < 0 ) ) { + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->DebugDraw( idStr::ColorForIndex( i+3 ) ); + } + } +} + +/* +================ +idPhysics_AF::idPhysics_AF +================ +*/ +idPhysics_AF::idPhysics_AF() { + trees.Clear(); + bodies.Clear(); + constraints.Clear(); + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + frameConstraints.Clear(); + contacts.Clear(); + collisions.Clear(); + changedAF = true; + masterBody = NULL; + + lcp = idLCP::AllocSymmetric(); + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + current.lastTimeStep = 0.0f; + saved = current; + + linearFriction = 0.005f; + angularFriction = 0.005f; + contactFriction = 0.8f; + bouncyness = 0.4f; + totalMass = 0.0f; + forceTotalMass = -1.0f; + + suspendVelocity.Set( SUSPEND_LINEAR_VELOCITY, SUSPEND_ANGULAR_VELOCITY ); + suspendAcceleration.Set( SUSPEND_LINEAR_ACCELERATION, SUSPEND_LINEAR_ACCELERATION ); + noMoveTime = NO_MOVE_TIME; + noMoveTranslation = NO_MOVE_TRANSLATION_TOLERANCE; + noMoveRotation = NO_MOVE_ROTATION_TOLERANCE; + minMoveTime = MIN_MOVE_TIME; + maxMoveTime = MAX_MOVE_TIME; + impulseThreshold = IMPULSE_THRESHOLD; + + timeScale = 1.0f; + timeScaleRampStart = 0.0f; + timeScaleRampEnd = 0.0f; + + jointFrictionScale = 0.0f; + jointFrictionDent = 0.0f; + jointFrictionDentStart = 0.0f; + jointFrictionDentEnd = 0.0f; + jointFrictionDentScale = 0.0f; + + contactFrictionScale = 0.0f; + contactFrictionDent = 0.0f; + contactFrictionDentStart = 0.0f; + contactFrictionDentEnd = 0.0f; + contactFrictionDentScale = 0.0f; + + enableCollision = true; + selfCollision = true; + comeToRest = true; + linearTime = true; + noImpact = false; + worldConstraintsLocked = false; + forcePushable = false; + +#ifdef AF_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_AF::~idPhysics_AF +================ +*/ +idPhysics_AF::~idPhysics_AF() { + int i; + + trees.DeleteContents( true ); + + for ( i = 0; i < bodies.Num(); i++ ) { + delete bodies[i]; + } + + for ( i = 0; i < constraints.Num(); i++ ) { + delete constraints[i]; + } + + contactConstraints.SetNum( contactConstraints.NumAllocated() ); + for ( i = 0; i < contactConstraints.Num(); i++ ) { + delete contactConstraints[i]; + } + + delete lcp; + + if ( masterBody ) { + delete masterBody; + } +} + +/* +================ +idPhysics_AF_SavePState +================ +*/ +void idPhysics_AF_SavePState( idSaveGame *saveFile, const AFPState_t &state ) { + saveFile->WriteInt( state.atRest ); + saveFile->WriteFloat( state.noMoveTime ); + saveFile->WriteFloat( state.activateTime ); + saveFile->WriteFloat( state.lastTimeStep ); + saveFile->WriteVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF_RestorePState +================ +*/ +void idPhysics_AF_RestorePState( idRestoreGame *saveFile, AFPState_t &state ) { + saveFile->ReadInt( state.atRest ); + saveFile->ReadFloat( state.noMoveTime ); + saveFile->ReadFloat( state.activateTime ); + saveFile->ReadFloat( state.lastTimeStep ); + saveFile->ReadVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF::Save +================ +*/ +void idPhysics_AF::Save( idSaveGame *saveFile ) const { + int i; + + // the articulated figure structure is handled by the owner + + idPhysics_AF_SavePState( saveFile, current ); + idPhysics_AF_SavePState( saveFile, saved ); + + saveFile->WriteInt( bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Save( saveFile ); + } + if ( masterBody ) { + saveFile->WriteBool( true ); + masterBody->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + + saveFile->WriteInt( constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Save( saveFile ); + } + + saveFile->WriteBool( changedAF ); + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteFloat( totalMass ); + saveFile->WriteFloat( forceTotalMass ); + + saveFile->WriteVec2( suspendVelocity ); + saveFile->WriteVec2( suspendAcceleration ); + saveFile->WriteFloat( noMoveTime ); + saveFile->WriteFloat( noMoveTranslation ); + saveFile->WriteFloat( noMoveRotation ); + saveFile->WriteFloat( minMoveTime ); + saveFile->WriteFloat( maxMoveTime ); + saveFile->WriteFloat( impulseThreshold ); + + saveFile->WriteFloat( timeScale ); + saveFile->WriteFloat( timeScaleRampStart ); + saveFile->WriteFloat( timeScaleRampEnd ); + + saveFile->WriteFloat( jointFrictionScale ); + saveFile->WriteFloat( jointFrictionDent ); + saveFile->WriteFloat( jointFrictionDentStart ); + saveFile->WriteFloat( jointFrictionDentEnd ); + saveFile->WriteFloat( jointFrictionDentScale ); + + saveFile->WriteFloat( contactFrictionScale ); + saveFile->WriteFloat( contactFrictionDent ); + saveFile->WriteFloat( contactFrictionDentStart ); + saveFile->WriteFloat( contactFrictionDentEnd ); + saveFile->WriteFloat( contactFrictionDentScale ); + + saveFile->WriteBool( enableCollision ); + saveFile->WriteBool( selfCollision ); + saveFile->WriteBool( comeToRest ); + saveFile->WriteBool( linearTime ); + saveFile->WriteBool( noImpact ); + saveFile->WriteBool( worldConstraintsLocked ); + saveFile->WriteBool( forcePushable ); +} + +/* +================ +idPhysics_AF::Restore +================ +*/ +void idPhysics_AF::Restore( idRestoreGame *saveFile ) { + int i, num; + bool hasMaster; + + // the articulated figure structure should have already been restored + + idPhysics_AF_RestorePState( saveFile, current ); + idPhysics_AF_RestorePState( saveFile, saved ); + + saveFile->ReadInt( num ); + assert( num == bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Restore( saveFile ); + } + saveFile->ReadBool( hasMaster ); + if ( hasMaster ) { + masterBody = new (TAG_PHYSICS_AF) idAFBody(); + masterBody->Restore( saveFile ); + } + + saveFile->ReadInt( num ); + assert( num == constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Restore( saveFile ); + } + + saveFile->ReadBool( changedAF ); + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadFloat( totalMass ); + saveFile->ReadFloat( forceTotalMass ); + + saveFile->ReadVec2( suspendVelocity ); + saveFile->ReadVec2( suspendAcceleration ); + saveFile->ReadFloat( noMoveTime ); + saveFile->ReadFloat( noMoveTranslation ); + saveFile->ReadFloat( noMoveRotation ); + saveFile->ReadFloat( minMoveTime ); + saveFile->ReadFloat( maxMoveTime ); + saveFile->ReadFloat( impulseThreshold ); + + saveFile->ReadFloat( timeScale ); + saveFile->ReadFloat( timeScaleRampStart ); + saveFile->ReadFloat( timeScaleRampEnd ); + + saveFile->ReadFloat( jointFrictionScale ); + saveFile->ReadFloat( jointFrictionDent ); + saveFile->ReadFloat( jointFrictionDentStart ); + saveFile->ReadFloat( jointFrictionDentEnd ); + saveFile->ReadFloat( jointFrictionDentScale ); + + saveFile->ReadFloat( contactFrictionScale ); + saveFile->ReadFloat( contactFrictionDent ); + saveFile->ReadFloat( contactFrictionDentStart ); + saveFile->ReadFloat( contactFrictionDentEnd ); + saveFile->ReadFloat( contactFrictionDentScale ); + + saveFile->ReadBool( enableCollision ); + saveFile->ReadBool( selfCollision ); + saveFile->ReadBool( comeToRest ); + saveFile->ReadBool( linearTime ); + saveFile->ReadBool( noImpact ); + saveFile->ReadBool( worldConstraintsLocked ); + saveFile->ReadBool( forcePushable ); + + changedAF = true; + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::IsClosedLoop +================ +*/ +bool idPhysics_AF::IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const { + const idAFBody *b1, *b2; + + for ( b1 = body1; b1->parent; b1 = b1->parent ) { + } + for ( b2 = body2; b2->parent; b2 = b2->parent ) { + } + return ( b1 == b2 ); +} + +/* +================ +idPhysics_AF::BuildTrees +================ +*/ +void idPhysics_AF::BuildTrees() { + int i; + float scale; + idAFBody *b; + idAFConstraint *c; + idAFTree *tree; + + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + trees.DeleteContents( true ); + + totalMass = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->parent = NULL; + b->primaryConstraint = NULL; + b->constraints.SetNum( 0 ); + b->children.Clear(); + b->tree = NULL; + totalMass += b->mass; + } + + if ( forceTotalMass > 0.0f ) { + scale = forceTotalMass / totalMass; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->mass *= scale; + b->invMass = 1.0f / b->mass; + b->inertiaTensor *= scale; + b->inverseInertiaTensor = b->inertiaTensor.Inverse(); + } + totalMass = forceTotalMass; + } + + if ( af_useLinearTime.GetBool() ) { + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + // only bilateral constraints between two non-world bodies that do not + // create loops can be used as primary constraints + if ( !c->body1->primaryConstraint && c->fl.allowPrimary && c->body2 != NULL && !IsClosedLoop( c->body1, c->body2 ) ) { + c->body1->primaryConstraint = c; + c->body1->parent = c->body2; + c->body2->children.Append( c->body1 ); + c->fl.isPrimary = true; + c->firstIndex = 0; + primaryConstraints.Append( c ); + } else { + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } + + // create trees for all parent bodies + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->parent ) { + tree = new (TAG_PHYSICS_AF) idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + } + + // add each child body to the appropriate tree + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->parent ) { + for ( b = bodies[i]->parent; !b->tree; b = b->parent ) { + } + b->tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = b->tree; + } + } + + if ( trees.Num() > 1 ) { + gameLocal.Warning( "Articulated figure has multiple seperate tree structures for entity '%s' type '%s'.", + self->name.c_str(), self->GetType()->classname ); + } + + // sort bodies in each tree to make sure parents come first + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SortBodies(); + } + + } else { + + // create a tree for each body + for ( i = 0; i < bodies.Num(); i++ ) { + tree = new (TAG_PHYSICS_AF) idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } +} + +/* +================ +idPhysics_AF::AddBody + + bodies get an id in the order they are added starting at zero + as such the first body added will get id zero +================ +*/ +int idPhysics_AF::AddBody( idAFBody *body ) { + int id = 0; + + if ( body->clipModel == NULL ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' has no clip model.", body->name.c_str() ); + return 0; + } + + if ( bodies.Find( body ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' added twice.", body->name.c_str() ); + } + + if ( GetBody( body->name ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: a body with the name '%s' already exists.", body->name.c_str() ); + } + + id = bodies.Num(); + body->clipModel->SetId( id ); + if ( body->linearFriction < 0.0f ) { + body->linearFriction = linearFriction; + body->angularFriction = angularFriction; + body->contactFriction = contactFriction; + } + if ( body->bouncyness < 0.0f ) { + body->bouncyness = bouncyness; + } + if ( !body->fl.clipMaskSet ) { + body->clipMask = clipMask; + } + + bodies.Append( body ); + + changedAF = true; + + return id; +} + +/* +================ +idPhysics_AF::AddConstraint +================ +*/ +void idPhysics_AF::AddConstraint( idAFConstraint *constraint ) { + + if ( constraints.Find( constraint ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: constraint '%s' added twice.", constraint->name.c_str() ); + } + if ( GetConstraint( constraint->name ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: a constraint with the name '%s' already exists.", constraint->name.c_str() ); + } + if ( !constraint->body1 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 == NULL on constraint '%s'.", constraint->name.c_str() ); + } + if ( !bodies.Find( constraint->body1 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body2 && !bodies.Find( constraint->body2 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body2 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body1 == constraint->body2 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 and body2 of constraint '%s' are the same.", constraint->name.c_str() ); + } + + constraints.Append( constraint ); + constraint->physics = this; + + changedAF = true; +} + +/* +================ +idPhysics_AF::AddFrameConstraint +================ +*/ +void idPhysics_AF::AddFrameConstraint( idAFConstraint *constraint ) { + frameConstraints.Append( constraint ); + constraint->physics = this; +} + +/* +================ +idPhysics_AF::ForceBodyId +================ +*/ +void idPhysics_AF::ForceBodyId( idAFBody *body, int newId ) { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 ) { + gameLocal.Error( "ForceBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + if ( id != newId ) { + idAFBody *b = bodies[newId]; + bodies[newId] = bodies[id]; + bodies[id] = b; + changedAF = true; + } +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( idAFBody *body ) const { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 && body ) { + gameLocal.Error( "GetBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return i; + } + } + gameLocal.Error( "GetBodyId: no body with the name '%s' is not part of the articulated figure.\n", bodyName ); + return 0; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( idAFConstraint *constraint ) const { + int id; + + id = constraints.FindIndex( constraint ); + if ( id == -1 && constraint ) { + gameLocal.Error( "GetConstraintId: constraint '%s' is not part of the articulated figure.\n", constraint->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return i; + } + } + gameLocal.Error( "GetConstraintId: no constraint with the name '%s' is not part of the articulated figure.\n", constraintName ); + return 0; +} + +/* +================ +idPhysics_AF::GetNumBodies +================ +*/ +int idPhysics_AF::GetNumBodies() const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::GetNumConstraints +================ +*/ +int idPhysics_AF::GetNumConstraints() const { + return constraints.Num(); +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return bodies[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + gameLocal.Error( "GetBody: no body with id %d exists\n", id ); + return NULL; + } + return bodies[id]; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return constraints[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const int id ) const { + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "GetConstraint: no constraint with id %d exists\n", id ); + return NULL; + } + return constraints[id]; +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const char *bodyName ) { + int i; + + // find the body with the given name + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + break; + } + } + + if ( i >= bodies.Num() ) { + gameLocal.Warning( "DeleteBody: no body found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + bodyName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteBody( i ); +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const int id ) { + int j; + + if ( id < 0 || id > bodies.Num() ) { + gameLocal.Error( "DeleteBody: no body with id %d.", id ); + return; + } + + // remove any constraints attached to this body + for ( j = 0; j < constraints.Num(); j++ ) { + if ( constraints[j]->body1 == bodies[id] || constraints[j]->body2 == bodies[id] ) { + delete constraints[j]; + constraints.RemoveIndex( j ); + j--; + } + } + + // remove the body + delete bodies[id]; + bodies.RemoveIndex( id ); + + // set new body ids + for ( j = 0; j < bodies.Num(); j++ ) { + bodies[j]->clipModel->SetId( j ); + } + + changedAF = true; +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const char *constraintName ) { + int i; + + // find the constraint with the given name + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !constraints[i]->name.Icmp( constraintName ) ) { + break; + } + } + + if ( i >= constraints.Num() ) { + gameLocal.Warning( "DeleteConstraint: no constriant found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + constraintName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteConstraint( i ); +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const int id ) { + + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "DeleteConstraint: no constraint with id %d.", id ); + return; + } + + // remove the constraint + delete constraints[id]; + constraints.RemoveIndex( id ); + + changedAF = true; +} + +/* +================ +idPhysics_AF::GetBodyContactConstraints +================ +*/ +int idPhysics_AF::GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const { + int i, numContacts; + idAFBody *body; + idAFConstraint_Contact *contact; + + if ( id < 0 || id >= bodies.Num() || maxContacts <= 0 ) { + return 0; + } + + numContacts = 0; + body = bodies[id]; + for ( i = 0; i < contactConstraints.Num(); i++ ) { + contact = contactConstraints[i]; + if ( contact->body1 == body || contact->body2 == body ) { + contacts[numContacts++] = contact; + if ( numContacts >= maxContacts ) { + return numContacts; + } + } + } + return numContacts; +} + +/* +================ +idPhysics_AF::SetDefaultFriction +================ +*/ +void idPhysics_AF::SetDefaultFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_AF::GetImpactInfo +================ +*/ +void idPhysics_AF::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + if ( id < 0 || id >= bodies.Num() ) { + memset( info, 0, sizeof( *info ) ); + return; + } + info->invMass = 1.0f / bodies[id]->mass; + info->invInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + info->position = point - bodies[id]->current->worldOrigin; + info->velocity = bodies[id]->current->spatialVelocity.SubVec3(0) + bodies[id]->current->spatialVelocity.SubVec3(1).Cross( info->position ); +} + +/* +================ +idPhysics_AF::ApplyImpulse +================ +*/ +void idPhysics_AF::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + if ( noImpact || impulse.LengthSqr() < Square( impulseThreshold ) ) { + return; + } + const float maxImpulse = 100000.0f; + const float maxRotation = 100000.0f; + idMat3 invWorldInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + bodies[id]->current->spatialVelocity.SubVec3(0) += bodies[id]->invMass * impulse.Truncate( maxImpulse ); + bodies[id]->current->spatialVelocity.SubVec3(1) += invWorldInertiaTensor * (point - bodies[id]->current->worldOrigin).Cross( impulse ).Truncate( maxRotation ); + Activate(); +} + +/* +================ +idPhysics_AF::AddForce +================ +*/ +void idPhysics_AF::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->externalForce.SubVec3( 0 ) += force; + bodies[id]->current->externalForce.SubVec3( 1 ) += (point - bodies[id]->current->worldOrigin).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_AF::IsAtRest +================ +*/ +bool idPhysics_AF::IsAtRest() const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_AF::GetRestStartTime +================ +*/ +int idPhysics_AF::GetRestStartTime() const { + return current.atRest; +} + +/* +================ +idPhysics_AF::IsPushable +================ +*/ +bool idPhysics_AF::IsPushable() const { + return ( !noImpact && ( masterBody == NULL || forcePushable ) ); +} + +/* +================ +idPhysics_AF::SaveState +================ +*/ +void idPhysics_AF::SaveState() { + int i; + + saved = current; + + for ( i = 0; i < bodies.Num(); i++ ) { + memcpy( &bodies[i]->saved, bodies[i]->current, sizeof( AFBodyPState_t ) ); + } +} + +/* +================ +idPhysics_AF::RestoreState +================ +*/ +void idPhysics_AF::RestoreState() { + int i; + + current = saved; + + for ( i = 0; i < bodies.Num(); i++ ) { + *(bodies[i]->current) = bodies[i]->saved; + } + + EvaluateContacts(); +} + +/* +================ +idPhysics_AF::SetOrigin +================ +*/ +void idPhysics_AF::SetOrigin( const idVec3 &newOrigin, int id ) { + if ( masterBody ) { + Translate( masterBody->current->worldOrigin + masterBody->current->worldAxis * newOrigin - bodies[0]->current->worldOrigin ); + } else { + Translate( newOrigin - bodies[0]->current->worldOrigin ); + } +} + +/* +================ +idPhysics_AF::SetAxis +================ +*/ +void idPhysics_AF::SetAxis( const idMat3 &newAxis, int id ) { + idMat3 axis; + idRotation rotation; + + if ( masterBody ) { + axis = bodies[0]->current->worldAxis.Transpose() * ( newAxis * masterBody->current->worldAxis ); + } else { + axis = bodies[0]->current->worldAxis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( bodies[0]->current->worldOrigin ); + + Rotate( rotation ); +} + +/* +================ +idPhysics_AF::Translate +================ +*/ +void idPhysics_AF::Translate( const idVec3 &translation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // translate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Translate( translation ); + } + } + + // translate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + body->current->worldOrigin += translation; + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::Rotate +================ +*/ +void idPhysics_AF::Rotate( const idRotation &rotation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // rotate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Rotate( rotation ); + } + } + + // rotate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + body->current->worldOrigin *= rotation; + body->current->worldAxis *= rotation.ToMat3(); + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::GetOrigin +================ +*/ +const idVec3 &idPhysics_AF::GetOrigin( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->worldOrigin; + } +} + +/* +================ +idPhysics_AF::GetAxis +================ +*/ +const idMat3 &idPhysics_AF::GetAxis( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return mat3_identity; + } + else { + return bodies[id]->current->worldAxis; + } +} + +/* +================ +idPhysics_AF::SetLinearVelocity +================ +*/ +void idPhysics_AF::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 0 ) = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::SetAngularVelocity +================ +*/ +void idPhysics_AF::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 1 ) = newAngularVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetLinearVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 0 ); + } +} + +/* +================ +idPhysics_AF::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetAngularVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 1 ); + } +} + +/* +================ +idPhysics_AF::ClipTranslation +================ +*/ +void idPhysics_AF::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { + gameLocal.clip.TranslationModel( bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.clip.Translation( bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + results.endpos = bodies[0]->current->worldOrigin + results.fraction * translation; + results.endAxis = bodies[0]->current->worldAxis; +} + +/* +================ +idPhysics_AF::ClipRotation +================ +*/ +void idPhysics_AF::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + idRotation partialRotation; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { + gameLocal.clip.RotationModel( bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.clip.Rotation( bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + partialRotation = rotation * results.fraction; + results.endpos = bodies[0]->current->worldOrigin * partialRotation; + results.endAxis = bodies[0]->current->worldAxis * partialRotation.ToMat3(); +} + +/* +================ +idPhysics_AF::ClipContents +================ +*/ +int idPhysics_AF::ClipContents( const idClipModel *model ) const { + int i, contents; + idAFBody *body; + + contents = 0; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { + contents |= gameLocal.clip.ContentsModel( body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + contents |= gameLocal.clip.Contents( body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, NULL ); + } + } + } + + return contents; +} + +/* +================ +idPhysics_AF::DisableClip +================ +*/ +void idPhysics_AF::DisableClip() { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } +} + +/* +================ +idPhysics_AF::EnableClip +================ +*/ +void idPhysics_AF::EnableClip() { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Enable(); + } +} + +/* +================ +idPhysics_AF::UnlinkClip +================ +*/ +void idPhysics_AF::UnlinkClip() { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Unlink(); + } +} + +/* +================ +idPhysics_AF::LinkClip +================ +*/ +void idPhysics_AF::LinkClip() { + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::SetPushed +================ +*/ +void idPhysics_AF::SetPushed( int deltaTime ) { + idAFBody *body; + idRotation rotation; + + if ( bodies.Num() ) { + body = bodies[0]; + rotation = ( body->saved.worldAxis.Transpose() * body->current->worldAxis ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( body->current->worldOrigin - body->saved.worldOrigin ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); + } +} + +/* +================ +idPhysics_AF::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_AF::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_AF::SetMaster + + the binding is orientated based on the constraints being used +================ +*/ +void idPhysics_AF::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + idRotation rotation; + + if ( master ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( !masterBody ) { + masterBody = new (TAG_PHYSICS_AF) idAFBody(); + // translate and rotate all the constraints with body2 == NULL from world space to master space + rotation = masterAxis.Transpose().ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Translate( -masterOrigin ); + constraints[i]->Rotate( rotation ); + } + } + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + else { + if ( masterBody ) { + // translate and rotate all the constraints with body2 == NULL from master space to world space + rotation = masterBody->current->worldAxis.ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Rotate( rotation ); + constraints[i]->Translate( masterBody->current->worldOrigin ); + } + } + delete masterBody; + masterBody = NULL; + Activate(); + } + } +} + + +const float AF_VELOCITY_MAX = 16000; +const int AF_VELOCITY_TOTAL_BITS = 16; +const int AF_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_VELOCITY_MAX ) ) + 1; +const int AF_VELOCITY_MANTISSA_BITS = AF_VELOCITY_TOTAL_BITS - 1 - AF_VELOCITY_EXPONENT_BITS; +const float AF_FORCE_MAX = 1e20f; +const int AF_FORCE_TOTAL_BITS = 16; +const int AF_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_FORCE_MAX ) ) + 1; +const int AF_FORCE_MANTISSA_BITS = AF_FORCE_TOTAL_BITS - 1 - AF_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_AF::WriteToSnapshot +================ +*/ +void idPhysics_AF::WriteToSnapshot( idBitMsg &msg ) const { + int i; + idCQuat quat; + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.noMoveTime ); + msg.WriteFloat( current.activateTime ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + msg.WriteByte( bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + quat = state->worldAxis.ToCQuat(); + + msg.WriteFloat( state->worldOrigin[0] ); + msg.WriteFloat( state->worldOrigin[1] ); + msg.WriteFloat( state->worldOrigin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* msg.WriteDeltaFloat( 0.0f, state->externalForce[0], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[1], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[2], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[3], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[4], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[5], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + } +} + +/* +================ +idPhysics_AF::ReadFromSnapshot +================ +*/ +void idPhysics_AF::ReadFromSnapshot( const idBitMsg &msg ) { + int i, num; + idCQuat quat; + + current.atRest = msg.ReadLong(); + current.noMoveTime = msg.ReadFloat(); + current.activateTime = msg.ReadFloat(); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + num = msg.ReadByte(); + assert( num == bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + + state->worldOrigin[0] = msg.ReadFloat(); + state->worldOrigin[1] = msg.ReadFloat(); + state->worldOrigin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + state->spatialVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* state->externalForce[0] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[1] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[2] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[3] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[4] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[5] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + state->worldAxis = quat.ToMat3(); + } + + UpdateClipModels(); +} diff --git a/neo/d3xp/physics/Physics_AF.h b/neo/d3xp/physics/Physics_AF.h new file mode 100644 index 00000000..51ee619a --- /dev/null +++ b/neo/d3xp/physics/Physics_AF.h @@ -0,0 +1,1047 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_AF_H__ +#define __PHYSICS_AF_H__ + +/* +=================================================================================== + + Articulated Figure physics + + Employs a constraint force based dynamic simulation using a lagrangian + multiplier method to solve for the constraint forces. + +=================================================================================== +*/ + +class idAFConstraint; +class idAFConstraint_Fixed; +class idAFConstraint_BallAndSocketJoint; +class idAFConstraint_BallAndSocketJointFriction; +class idAFConstraint_UniversalJoint; +class idAFConstraint_UniversalJointFriction; +class idAFConstraint_CylindricalJoint; +class idAFConstraint_Hinge; +class idAFConstraint_HingeFriction; +class idAFConstraint_HingeSteering; +class idAFConstraint_Slider; +class idAFConstraint_Line; +class idAFConstraint_Plane; +class idAFConstraint_Spring; +class idAFConstraint_Contact; +class idAFConstraint_ContactFriction; +class idAFConstraint_ConeLimit; +class idAFConstraint_PyramidLimit; +class idAFConstraint_Suspension; +class idAFBody; +class idAFTree; +class idPhysics_AF; + +typedef enum { + CONSTRAINT_INVALID, + CONSTRAINT_FIXED, + CONSTRAINT_BALLANDSOCKETJOINT, + CONSTRAINT_UNIVERSALJOINT, + CONSTRAINT_HINGE, + CONSTRAINT_HINGESTEERING, + CONSTRAINT_SLIDER, + CONSTRAINT_CYLINDRICALJOINT, + CONSTRAINT_LINE, + CONSTRAINT_PLANE, + CONSTRAINT_SPRING, + CONSTRAINT_CONTACT, + CONSTRAINT_FRICTION, + CONSTRAINT_CONELIMIT, + CONSTRAINT_PYRAMIDLIMIT, + CONSTRAINT_SUSPENSION +} constraintType_t; + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +// base class for all constraints +class idAFConstraint { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFConstraint(); + virtual ~idAFConstraint(); + constraintType_t GetType() const { return type; } + const idStr & GetName() const { return name; } + idAFBody * GetBody1() const { return body1; } + idAFBody * GetBody2() const { return body2; } + void SetPhysics( idPhysics_AF *p ) { physics = p; } + const idVecX & GetMultiplier(); + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw(); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + constraintType_t type; // constraint type + idStr name; // name of constraint + idAFBody * body1; // first constrained body + idAFBody * body2; // second constrained body, NULL for world + idPhysics_AF * physics; // for adding additional constraints like limits + + // simulation variables set by Evaluate + idMatX J1, J2; // matrix with left hand side of constraint equations + idVecX c1, c2; // right hand side of constraint equations + idVecX lo, hi, e; // low and high bounds and lcp epsilon + idAFConstraint * boxConstraint; // constraint the boxIndex refers to + int boxIndex[6]; // indexes for special box constrained variables + + // simulation variables used during calculations + idMatX invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX lm; // lagrange multipliers + int firstIndex; // index of the first constraint row in the lcp matrix + + struct constraintFlags_s { + bool allowPrimary : 1; // true if the constraint can be used as a primary constraint + bool frameConstraint : 1; // true if this constraint is added to the frame constraints + bool noCollision : 1; // true if body1 and body2 never collide with each other + bool isPrimary : 1; // true if this is a primary constraint + bool isZero : 1; // true if 's' is zero during calculations + } fl; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitSize( int size ); +}; + +// fixed or rigid joint which allows zero degrees of freedom +// constrains body1 to have a fixed position and orientation relative to body2 +class idAFConstraint_Fixed : public idAFConstraint { + +public: + idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetRelativeOrigin( const idVec3 &origin ) { this->offset = origin; } + void SetRelativeAxis( const idMat3 &axis ) { this->relAxis = axis; } + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 offset; // offset of body1 relative to body2 in body2 space + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitOffset(); +}; + +// ball and socket or spherical joint which allows 3 degrees of freedom +// constrains body1 relative to body2 with a ball and socket joint +class idAFConstraint_BallAndSocketJoint : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_BallAndSocketJoint(); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor() const; + void SetNoLimit(); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction() const; + virtual void DebugDraw(); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_BallAndSocketJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// ball and socket joint friction +class idAFConstraint_BallAndSocketJointFriction : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJointFriction(); + void Setup( idAFConstraint_BallAndSocketJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_BallAndSocketJoint *joint; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal, Cardan or Hooke joint which allows 2 degrees of freedom +// like a ball and socket joint but also constrains the rotation about the cardan shafts +class idAFConstraint_UniversalJoint : public idAFConstraint { + +public: + idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_UniversalJoint(); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor() const; + void SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ); + void GetShafts( idVec3 &cardanShaft1, idVec3 &cardanShaft2 ) { cardanShaft1 = shaft1; cardanShaft2 = shaft2; } + void SetNoLimit(); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction() const; + virtual void DebugDraw(); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 shaft1; // body1 cardan shaft in body1 space + idVec3 shaft2; // body2 cardan shaft in body2 space + idVec3 axis1; // cardan axis in body1 space + idVec3 axis2; // cardan axis in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_UniversalJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal joint friction +class idAFConstraint_UniversalJointFriction : public idAFConstraint { + +public: + idAFConstraint_UniversalJointFriction(); + void Setup( idAFConstraint_UniversalJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_UniversalJoint *joint; // universal joint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// cylindrical joint which allows 2 degrees of freedom +// constrains body1 to lie on a line relative to body2 and allows only translation along and rotation about the line +class idAFConstraint_CylindricalJoint : public idAFConstraint { + +public: + idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge, revolute or pin joint which allows 1 degree of freedom +// constrains all motion of body1 relative to body2 except the rotation about the hinge axis +class idAFConstraint_Hinge : public idAFConstraint { + +public: + idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_Hinge(); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor() const; + void SetAxis( const idVec3 &axis ); + void GetAxis( idVec3 &a1, idVec3 &a2 ) const { a1 = axis1; a2 = axis2; } + idVec3 GetAxis() const; + void SetNoLimit(); + void SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + float GetAngle() const; + void SetSteerAngle( const float degrees ); + void SetSteerSpeed( const float speed ); + void SetFriction( const float f ) { friction = f; } + float GetFriction() const; + virtual void DebugDraw(); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 axis1; // axis in body1 space + idVec3 axis2; // axis in body2 space + idMat3 initialAxis; // initial axis of body1 relative to body2 + float friction; // hinge friction + idAFConstraint_ConeLimit *coneLimit; // cone limit + idAFConstraint_HingeSteering *steering; // steering + idAFConstraint_HingeFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge joint friction +class idAFConstraint_HingeFriction : public idAFConstraint { + +public: + idAFConstraint_HingeFriction(); + void Setup( idAFConstraint_Hinge *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains two bodies attached to each other with a hinge to get a specified relative orientation +class idAFConstraint_HingeSteering : public idAFConstraint { + +public: + idAFConstraint_HingeSteering(); + void Setup( idAFConstraint_Hinge *cc ); + void SetSteerAngle( const float degrees ) { steerAngle = degrees; } + void SetSteerSpeed( const float speed ) { steerSpeed = speed; } + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + float steerAngle; // desired steer angle in degrees + float steerSpeed; // steer speed + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// slider, prismatic or translational constraint which allows 1 degree of freedom +// constrains body1 to lie on a line relative to body2, the orientation is also fixed relative to body2 +class idAFConstraint_Slider : public idAFConstraint { + +public: + idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAxis( const idVec3 &ax ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 axis; // axis along which body1 slides in body2 space + idVec3 offset; // offset of body1 relative to body2 + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// line constraint which allows 4 degrees of freedom +// constrains body1 to lie on a line relative to body2, does not constrain the orientation. +class idAFConstraint_Line : public idAFConstraint { + +public: + idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// plane constraint which allows 5 degrees of freedom +// constrains body1 to lie in a plane relative to body2, does not constrain the orientation. +class idAFConstraint_Plane : public idAFConstraint { + +public: + idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetPlane( const idVec3 &normal, const idVec3 &anchor ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 planeNormal; // plane normal in body2 space + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// spring constraint which allows 6 or 5 degrees of freedom based on the spring limits +// constrains body1 relative to body2 with a spring +class idAFConstraint_Spring : public idAFConstraint { + +public: + idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ); + void SetSpring( const float stretch, const float compress, const float damping, const float restLength ); + void SetLimit( const float minLength, const float maxLength ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float kstretch; // spring constant when stretched + float kcompress; // spring constant when compressed + float damping; // spring damping + float restLength; // rest length of spring + float minLength; // minimum spring length + float maxLength; // maximum spring length + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains body1 to either be in contact with or move away from body2 +class idAFConstraint_Contact : public idAFConstraint { + +public: + idAFConstraint_Contact(); + ~idAFConstraint_Contact(); + void Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ); + const contactInfo_t & GetContact() const { return contact; } + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + +protected: + contactInfo_t contact; // contact information + idAFConstraint_ContactFriction *fc; // contact friction + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// contact friction +class idAFConstraint_ContactFriction : public idAFConstraint { + +public: + idAFConstraint_ContactFriction(); + void Setup( idAFConstraint_Contact *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Contact *cc; // contact constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a cone relative to body2 +class idAFConstraint_ConeLimit : public idAFConstraint { + +public: + idAFConstraint_ConeLimit(); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, + const float coneAngle, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &coneAnchor ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 coneAnchor; // top of the cone in body2 space + idVec3 coneAxis; // cone axis in body2 space + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle; // cos( coneAngle / 2 ) + float sinHalfAngle; // sin( coneAngle / 4 ) + float cosHalfAngle; // cos( coneAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a pyramid relative to body2 +class idAFConstraint_PyramidLimit : public idAFConstraint { + +public: + idAFConstraint_PyramidLimit(); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &pyramidAxis ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 pyramidAnchor; // top of the pyramid in body2 space + idMat3 pyramidBasis; // pyramid basis in body2 space with base[2] being the pyramid axis + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle[2]; // cos( pyramidAngle / 2 ) + float sinHalfAngle[2]; // sin( pyramidAngle / 4 ) + float cosHalfAngle[2]; // cos( pyramidAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// vehicle suspension +class idAFConstraint_Suspension : public idAFConstraint { + +public: + idAFConstraint_Suspension(); + + void Setup( const char *name, idAFBody *body, const idVec3 &origin, const idMat3 &axis, idClipModel *clipModel ); + void SetSuspension( const float up, const float down, const float k, const float d, const float f ); + + void SetSteerAngle( const float degrees ) { steerAngle = degrees; } + void EnableMotor( const bool enable ) { motorEnabled = enable; } + void SetMotorForce( const float force ) { motorForce = force; } + void SetMotorVelocity( const float vel ) { motorVelocity = vel; } + void SetEpsilon( const float e ) { epsilon = e; } + const idVec3 GetWheelOrigin() const; + + virtual void DebugDraw(); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idVec3 localOrigin; // position of suspension relative to body1 + idMat3 localAxis; // orientation of suspension relative to body1 + float suspensionUp; // suspension up movement + float suspensionDown; // suspension down movement + float suspensionKCompress; // spring compress constant + float suspensionDamping; // spring damping + float steerAngle; // desired steer angle in degrees + float friction; // friction + bool motorEnabled; // whether the motor is enabled or not + float motorForce; // motor force + float motorVelocity; // desired velocity + idClipModel * wheelModel; // wheel model + idVec3 wheelOffset; // wheel position relative to body1 + trace_t trace; // contact point with the ground + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +typedef struct AFBodyPState_s { + idVec3 worldOrigin; // position in world space + idMat3 worldAxis; // axis at worldOrigin + idVec6 spatialVelocity; // linear and rotational velocity of body + idVec6 externalForce; // external force and torque applied to body +} AFBodyPState_t; + + +class idAFBody { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFBody(); + idAFBody( const idStr &name, idClipModel *clipModel, float density ); + ~idAFBody(); + + void Init(); + const idStr & GetName() const { return name; } + const idVec3 & GetWorldOrigin() const { return current->worldOrigin; } + const idMat3 & GetWorldAxis() const { return current->worldAxis; } + const idVec3 & GetLinearVelocity() const { return current->spatialVelocity.SubVec3(0); } + const idVec3 & GetAngularVelocity() const { return current->spatialVelocity.SubVec3(1); } + idVec3 GetPointVelocity( const idVec3 &point ) const; + const idVec3 & GetCenterOfMass() const { return centerOfMass; } + void SetClipModel( idClipModel *clipModel ); + idClipModel * GetClipModel() const { return clipModel; } + void SetClipMask( const int mask ) { clipMask = mask; fl.clipMaskSet = true; } + int GetClipMask() const { return clipMask; } + void SetSelfCollision( const bool enable ) { fl.selfCollision = enable; } + void SetWorldOrigin( const idVec3 &origin ) { current->worldOrigin = origin; } + void SetWorldAxis( const idMat3 &axis ) { current->worldAxis = axis; } + void SetLinearVelocity( const idVec3 &linear ) const { current->spatialVelocity.SubVec3(0) = linear; } + void SetAngularVelocity( const idVec3 &angular ) const { current->spatialVelocity.SubVec3(1) = angular; } + void SetFriction( float linear, float angular, float contact ); + float GetContactFriction() const { return contactFriction; } + void SetBouncyness( float bounce ); + float GetBouncyness() const { return bouncyness; } + void SetDensity( float density, const idMat3 &inertiaScale = mat3_identity ); + float GetInverseMass() const { return invMass; } + idMat3 GetInverseWorldInertia() const { return current->worldAxis.Transpose() * inverseInertiaTensor * current->worldAxis; } + + void SetFrictionDirection( const idVec3 &dir ); + bool GetFrictionDirection( idVec3 &dir ) const; + + void SetContactMotorDirection( const idVec3 &dir ); + bool GetContactMotorDirection( idVec3 &dir ) const; + void SetContactMotorVelocity( float vel ) { contactMotorVelocity = vel; } + float GetContactMotorVelocity() const { return contactMotorVelocity; } + void SetContactMotorForce( float force ) { contactMotorForce = force; } + float GetContactMotorForce() const { return contactMotorForce; } + + void AddForce( const idVec3 &point, const idVec3 &force ); + void InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const; + idVec6 & GetResponseForce( int index ) { return reinterpret_cast(response[ index * 8 ]); } + + void Save( idSaveGame *saveFile ); + void Restore( idRestoreGame *saveFile ); + +private: + // properties + idStr name; // name of body + idAFBody * parent; // parent of this body + idList children; // children of this body + idClipModel * clipModel; // model used for collision detection + idAFConstraint * primaryConstraint; // primary constraint (this->constraint->body1 = this) + idListconstraints; // all constraints attached to this body + idAFTree * tree; // tree structure this body is part of + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bounce + int clipMask; // contents this body collides with + idVec3 frictionDir; // specifies a single direction of friction in body space + idVec3 contactMotorDir; // contact motor direction + float contactMotorVelocity; // contact motor velocity + float contactMotorForce; // maximum force applied to reach the motor velocity + + // derived properties + float mass; // mass of body + float invMass; // inverse mass + idVec3 centerOfMass; // center of mass of body + idMat3 inertiaTensor; // inertia tensor + idMat3 inverseInertiaTensor; // inverse inertia tensor + + // physics state + AFBodyPState_t state[2]; + AFBodyPState_t * current; // current physics state + AFBodyPState_t * next; // next physics state + AFBodyPState_t saved; // saved physics state + idVec3 atRestOrigin; // origin at rest + idMat3 atRestAxis; // axis at rest + + // simulation variables used during calculations + idMatX inverseWorldSpatialInertia; // inverse spatial inertia in world space + idMatX I, invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX totalForce; // total force acting on body + idVecX auxForce; // force from auxiliary constraints + idVecX acceleration; // acceleration + float * response; // forces on body in response to auxiliary constraint forces + int * responseIndex; // index to response forces + int numResponses; // number of response forces + int maxAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body + int maxSubTreeAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body or one of it's children + + struct bodyFlags_s { + bool clipMaskSet : 1; // true if this body has a clip mask set + bool selfCollision : 1; // true if this body can collide with other bodies of this AF + bool spatialInertiaSparse: 1; // true if the spatial inertia matrix is sparse + bool useFrictionDir : 1; // true if a single friction direction should be used + bool useContactMotorDir : 1; // true if a contact motor should be used + bool isZero : 1; // true if 's' is zero during calculations + } fl; +}; + + +//=============================================================== +// +// idAFTree +// +//=============================================================== + +class idAFTree { + friend class idPhysics_AF; + +public: + void Factor() const; + void Solve( int auxiliaryIndex = 0 ) const; + void Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const; + void CalculateForces( float timeStep ) const; + void SetMaxSubTreeAuxiliaryIndex(); + void SortBodies(); + void SortBodies_r( idList&sortedList, idAFBody *body ); + void DebugDraw( const idVec4 &color ) const; + +private: + idList sortedBodies; +}; + + +//=============================================================== +// +// idPhysics_AF +// +//=============================================================== + +typedef struct AFPState_s { + int atRest; // >= 0 if articulated figure is at rest + float noMoveTime; // time the articulated figure is hardly moving + float activateTime; // time since last activation + float lastTimeStep; // last time step + idVec6 pushVelocity; // velocity with which the af is pushed +} AFPState_t; + +typedef struct AFCollision_s { + trace_t trace; + idAFBody * body; +} AFCollision_t; + + +class idPhysics_AF : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_AF ); + + idPhysics_AF(); + ~idPhysics_AF(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + int AddBody( idAFBody *body ); // returns body id + void AddConstraint( idAFConstraint *constraint ); + void AddFrameConstraint( idAFConstraint *constraint ); + // force a body to have a certain id + void ForceBodyId( idAFBody *body, int newId ); + // get body or constraint id + int GetBodyId( idAFBody *body ) const; + int GetBodyId( const char *bodyName ) const; + int GetConstraintId( idAFConstraint *constraint ) const; + int GetConstraintId( const char *constraintName ) const; + // number of bodies and constraints + int GetNumBodies() const; + int GetNumConstraints() const; + // retrieve body or constraint + idAFBody * GetBody( const char *bodyName ) const; + idAFBody * GetBody( const int id ) const; + idAFBody * GetMasterBody() const { return masterBody; } + idAFConstraint * GetConstraint( const char *constraintName ) const; + idAFConstraint * GetConstraint( const int id ) const; + // delete body or constraint + void DeleteBody( const char *bodyName ); + void DeleteBody( const int id ); + void DeleteConstraint( const char *constraintName ); + void DeleteConstraint( const int id ); + // get all the contact constraints acting on the body + int GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const; + // set the default friction for bodies + void SetDefaultFriction( float linear, float angular, float contact ); + // suspend settings + void SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ); + // set the time and tolerances used to determine if the simulation can be suspended when the figure hardly moves for a while + void SetSuspendTolerance( const float noMoveTime, const float translationTolerance, const float rotationTolerance ); + // set minimum and maximum simulation time in seconds + void SetSuspendTime( const float minTime, const float maxTime ); + // set the time scale value + void SetTimeScale( const float ts ) { timeScale = ts; } + // set time scale ramp + void SetTimeScaleRamp( const float start, const float end ); + // set the joint friction scale + void SetJointFrictionScale( const float scale ) { jointFrictionScale = scale; } + // set joint friction dent + void SetJointFrictionDent( const float dent, const float start, const float end ); + // get the current joint friction scale + float GetJointFrictionScale() const; + // set the contact friction scale + void SetContactFrictionScale( const float scale ) { contactFrictionScale = scale; } + // set contact friction dent + void SetContactFrictionDent( const float dent, const float start, const float end ); + // get the current contact friction scale + float GetContactFrictionScale() const; + // enable or disable collision detection + void SetCollision( const bool enable ) { enableCollision = enable; } + // enable or disable self collision + void SetSelfCollision( const bool enable ) { selfCollision = enable; } + // enable or disable coming to a dead stop + void SetComeToRest( bool enable ) { comeToRest = enable; } + // call when structure of articulated figure changes + void SetChanged() { changedAF = true; } + // enable/disable activation by impact + void EnableImpact(); + void DisableImpact(); + // lock of unlock the world constraints + void LockWorldConstraints( const bool lock ) { worldConstraintsLocked = lock; } + // set force pushable + void SetForcePushable( const bool enable ) { forcePushable = enable; } + // update the clip model positions + void UpdateClipModels(); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + bool IsAtRest() const; + int GetRestStartTime() const; + void Activate(); + void PutToRest(); + bool IsPushable() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + bool EvaluateContacts(); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +private: + // articulated figure + idList trees; // tree structures + idList bodies; // all bodies + idListconstraints; // all frame independent constraints + idListprimaryConstraints; // list with primary constraints + idListauxiliaryConstraints; // list with auxiliary constraints + idListframeConstraints; // constraints that only live one frame + idListcontactConstraints; // contact constraints + idList contactBodies; // body id for each contact + idList collisions; // collisions + bool changedAF; // true when the articulated figure just changed + + // properties + float linearFriction; // default translational friction + float angularFriction; // default rotational friction + float contactFriction; // default friction with contact surfaces + float bouncyness; // default bouncyness + float totalMass; // total mass of articulated figure + float forceTotalMass; // force this total mass + + idVec2 suspendVelocity; // simulation may not be suspended if a body has more velocity + idVec2 suspendAcceleration; // simulation may not be suspended if a body has more acceleration + float noMoveTime; // suspend simulation if hardly any movement for this many seconds + float noMoveTranslation; // maximum translation considered no movement + float noMoveRotation; // maximum rotation considered no movement + float minMoveTime; // if > 0 the simulation is never suspended before running this many seconds + float maxMoveTime; // if > 0 the simulation is always suspeded after running this many seconds + float impulseThreshold; // threshold below which impulses are ignored to avoid continuous activation + + float timeScale; // the time is scaled with this value for slow motion effects + float timeScaleRampStart; // start of time scale change + float timeScaleRampEnd; // end of time scale change + + float jointFrictionScale; // joint friction scale + float jointFrictionDent; // joint friction dives from 1 to this value and goes up again + float jointFrictionDentStart; // start time of joint friction dent + float jointFrictionDentEnd; // end time of joint friction dent + float jointFrictionDentScale; // dent scale + + float contactFrictionScale; // contact friction scale + float contactFrictionDent; // contact friction dives from 1 to this value and goes up again + float contactFrictionDentStart; // start time of contact friction dent + float contactFrictionDentEnd; // end time of contact friction dent + float contactFrictionDentScale; // dent scale + + bool enableCollision; // if true collision detection is enabled + bool selfCollision; // if true the self collision is allowed + bool comeToRest; // if true the figure can come to rest + bool linearTime; // if true use the linear time algorithm + bool noImpact; // if true do not activate when another object collides + bool worldConstraintsLocked; // if true world constraints cannot be moved + bool forcePushable; // if true can be pushed even when bound to a master + + // physics state + AFPState_t current; + AFPState_t saved; + + idAFBody * masterBody; // master body + idLCP * lcp; // linear complementarity problem solver + +private: + void BuildTrees(); + bool IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const; + void PrimaryFactor(); + void EvaluateBodies( float timeStep ); + void EvaluateConstraints( float timeStep ); + void AddFrameConstraints(); + void RemoveFrameConstraints(); + void ApplyFriction( float timeStep, float endTimeMSec ); + void PrimaryForces( float timeStep ); + void AuxiliaryForces( float timeStep ); + void VerifyContactConstraints(); + void SetupContactConstraints(); + void ApplyContactForces(); + void Evolve( float timeStep ); + idEntity * SetupCollisionForBody( idAFBody *body ) const; + bool CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ); + bool ApplyCollisions( float timeStep ); + void CheckForCollisions( float timeStep ); + void ClearExternalForce(); + void AddGravity(); + void SwapStates(); + bool TestIfAtRest( float timeStep ); + void Rest(); + void AddPushVelocity( const idVec6 &pushVelocity ); + void DebugDraw(); +}; + +#endif /* !__PHYSICS_AF_H__ */ diff --git a/neo/d3xp/physics/Physics_Actor.cpp b/neo/d3xp/physics/Physics_Actor.cpp new file mode 100644 index 00000000..e593c898 --- /dev/null +++ b/neo/d3xp/physics/Physics_Actor.cpp @@ -0,0 +1,383 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Actor ) +END_CLASS + +/* +================ +idPhysics_Actor::idPhysics_Actor +================ +*/ +idPhysics_Actor::idPhysics_Actor() { + clipModel = NULL; + SetClipModelAxis(); + mass = 100.0f; + invMass = 1.0f / mass; + masterEntity = NULL; + masterYaw = 0.0f; + masterDeltaYaw = 0.0f; + groundEntityPtr = NULL; +} + +/* +================ +idPhysics_Actor::~idPhysics_Actor +================ +*/ +idPhysics_Actor::~idPhysics_Actor() { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +idPhysics_Actor::Save +================ +*/ +void idPhysics_Actor::Save( idSaveGame *savefile ) const { + + savefile->WriteClipModel( clipModel ); + savefile->WriteMat3( clipModelAxis ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( invMass ); + + savefile->WriteObject( masterEntity ); + savefile->WriteFloat( masterYaw ); + savefile->WriteFloat( masterDeltaYaw ); + + groundEntityPtr.Save( savefile ); +} + +/* +================ +idPhysics_Actor::Restore +================ +*/ +void idPhysics_Actor::Restore( idRestoreGame *savefile ) { + + savefile->ReadClipModel( clipModel ); + savefile->ReadMat3( clipModelAxis ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( invMass ); + + savefile->ReadObject( reinterpret_cast( masterEntity ) ); + savefile->ReadFloat( masterYaw ); + savefile->ReadFloat( masterDeltaYaw ); + + groundEntityPtr.Restore( savefile ); +} + +/* +================ +idPhysics_Actor::SetClipModelAxis +================ +*/ +void idPhysics_Actor::SetClipModelAxis() { + // align clip model to gravity direction + if ( ( gravityNormal[2] == -1.0f ) || ( gravityNormal == vec3_zero ) ) { + clipModelAxis.Identity(); + } + else { + clipModelAxis[2] = -gravityNormal; + clipModelAxis[2].NormalVectors( clipModelAxis[0], clipModelAxis[1] ); + clipModelAxis[1] = -clipModelAxis[1]; + } + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), clipModelAxis ); + } +} + +/* +================ +idPhysics_Actor::GetGravityAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetGravityAxis() const { + return clipModelAxis; +} + +/* +================ +idPhysics_Actor::GetMasterDeltaYaw +================ +*/ +float idPhysics_Actor::GetMasterDeltaYaw() const { + return masterDeltaYaw; +} + +/* +================ +idPhysics_Actor::GetGroundEntity +================ +*/ +idEntity *idPhysics_Actor::GetGroundEntity() const { + return groundEntityPtr.GetEntity(); +} + +/* +================ +idPhysics_Actor::SetClipModel +================ +*/ +void idPhysics_Actor::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + assert( self ); + assert( model ); // a clip model is required + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), clipModelAxis ); +} + +/* +================ +idPhysics_Actor::GetClipModel +================ +*/ +idClipModel *idPhysics_Actor::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Actor::GetNumClipModels +================ +*/ +int idPhysics_Actor::GetNumClipModels() const { + return 1; +} + +/* +================ +idPhysics_Actor::SetMass +================ +*/ +void idPhysics_Actor::SetMass( float _mass, int id ) { + assert( _mass > 0.0f ); + mass = _mass; + invMass = 1.0f / _mass; +} + +/* +================ +idPhysics_Actor::GetMass +================ +*/ +float idPhysics_Actor::GetMass( int id ) const { + return mass; +} + +/* +================ +idPhysics_Actor::SetClipMask +================ +*/ +void idPhysics_Actor::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_Actor::SetClipMask +================ +*/ +int idPhysics_Actor::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_Actor::GetBounds +================ +*/ +const idBounds &idPhysics_Actor::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_Actor::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Actor::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_Actor::IsPushable +================ +*/ +bool idPhysics_Actor::IsPushable() const { + return ( masterEntity == NULL ); +} + +/* +================ +idPhysics_Actor::GetOrigin +================ +*/ +const idVec3 &idPhysics_Actor::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +idPhysics_Player::GetAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetAxis( int id ) const { + return clipModel->GetAxis(); +} + +/* +================ +idPhysics_Actor::SetGravity +================ +*/ +void idPhysics_Actor::SetGravity( const idVec3 &newGravity ) { + if ( newGravity != gravityVector ) { + idPhysics_Base::SetGravity( newGravity ); + SetClipModelAxis(); + } +} + +/* +================ +idPhysics_Actor::ClipTranslation +================ +*/ +void idPhysics_Actor::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.clip.TranslationModel( results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.clip.Translation( results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_Actor::ClipRotation +================ +*/ +void idPhysics_Actor::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.clip.RotationModel( results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.clip.Rotation( results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_Actor::ClipContents +================ +*/ +int idPhysics_Actor::ClipContents( const idClipModel *model ) const { + if ( model ) { + return gameLocal.clip.ContentsModel( clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.clip.Contents( clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } +} + +/* +================ +idPhysics_Actor::DisableClip +================ +*/ +void idPhysics_Actor::DisableClip() { + clipModel->Disable(); +} + +/* +================ +idPhysics_Actor::EnableClip +================ +*/ +void idPhysics_Actor::EnableClip() { + clipModel->Enable(); +} + +/* +================ +idPhysics_Actor::UnlinkClip +================ +*/ +void idPhysics_Actor::UnlinkClip() { + clipModel->Unlink(); +} + +/* +================ +idPhysics_Actor::LinkClip +================ +*/ +void idPhysics_Actor::LinkClip() { + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), clipModel->GetAxis() ); +} + +/* +================ +idPhysics_Actor::EvaluateContacts +================ +*/ +bool idPhysics_Actor::EvaluateContacts() { + + // get all the ground contacts + ClearContacts(); + AddGroundContacts( clipModel ); + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} diff --git a/neo/d3xp/physics/Physics_Actor.h b/neo/d3xp/physics/Physics_Actor.h new file mode 100644 index 00000000..977e39dd --- /dev/null +++ b/neo/d3xp/physics/Physics_Actor.h @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_ACTOR_H__ +#define __PHYSICS_ACTOR_H__ + +/* +=================================================================================== + + Actor physics base class + + An actor typically uses one collision model which is aligned with the gravity + direction. The collision model is usually a simple box with the origin at the + bottom center. + +=================================================================================== +*/ + +class idPhysics_Actor : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Actor ); + + idPhysics_Actor(); + ~idPhysics_Actor(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // get delta yaw of master + float GetMasterDeltaYaw() const; + // returns the ground entity + idEntity * GetGroundEntity() const; + // align the clip model with the gravity direction + void SetClipModelAxis(); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool IsPushable() const; + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idMat3 & GetGravityAxis() const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + bool EvaluateContacts(); + +protected: + idClipModel * clipModel; // clip model used for collision detection + idMat3 clipModelAxis; // axis of clip model aligned with gravity direction + + // derived properties + float mass; + float invMass; + + // master + idEntity * masterEntity; + float masterYaw; + float masterDeltaYaw; + + // results of last evaluate + idEntityPtr groundEntityPtr; +}; + +#endif /* !__PHYSICS_ACTOR_H__ */ diff --git a/neo/d3xp/physics/Physics_Base.cpp b/neo/d3xp/physics/Physics_Base.cpp new file mode 100644 index 00000000..97bfcd7c --- /dev/null +++ b/neo/d3xp/physics/Physics_Base.cpp @@ -0,0 +1,855 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Base ) +END_CLASS + +/* +================ +idPhysics_Base::idPhysics_Base +================ +*/ +idPhysics_Base::idPhysics_Base() { + self = NULL; + clipMask = 0; + SetGravity( gameLocal.GetGravity() ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::~idPhysics_Base +================ +*/ +idPhysics_Base::~idPhysics_Base() { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::Save +================ +*/ +void idPhysics_Base::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + savefile->WriteInt( clipMask ); + savefile->WriteVec3( gravityVector ); + savefile->WriteVec3( gravityNormal ); + + savefile->WriteInt( contacts.Num() ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->WriteContactInfo( contacts[i] ); + } + + savefile->WriteInt( contactEntities.Num() ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Save( savefile ); + } +} + +/* +================ +idPhysics_Base::Restore +================ +*/ +void idPhysics_Base::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadInt( clipMask ); + savefile->ReadVec3( gravityVector ); + savefile->ReadVec3( gravityNormal ); + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Restore( savefile ); + } +} + +/* +================ +idPhysics_Base::SetSelf +================ +*/ +void idPhysics_Base::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Base::SetClipModel +================ +*/ +void idPhysics_Base::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_Base::GetClipModel +================ +*/ +idClipModel *idPhysics_Base::GetClipModel( int id ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetNumClipModels +================ +*/ +int idPhysics_Base::GetNumClipModels() const { + return 0; +} + +/* +================ +idPhysics_Base::SetMass +================ +*/ +void idPhysics_Base::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Base::GetMass +================ +*/ +float idPhysics_Base::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Base::SetContents +================ +*/ +void idPhysics_Base::SetContents( int contents, int id ) { +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +int idPhysics_Base::GetContents( int id ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +void idPhysics_Base::SetClipMask( int mask, int id ) { + clipMask = mask; +} + +/* +================ +idPhysics_Base::GetClipMask +================ +*/ +int idPhysics_Base::GetClipMask( int id ) const { + return clipMask; +} + +/* +================ +idPhysics_Base::GetBounds +================ +*/ +const idBounds &idPhysics_Base::GetBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Base::GetAbsBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::Evaluate +================ +*/ +bool idPhysics_Base::Evaluate( int timeStepMSec, int endTimeMSec ) { + return false; +} + +/* +================ +idPhysics_Base::Interpolate +================ +*/ +bool idPhysics_Base::Interpolate( const float fraction ) { + return false; +} + +/* +================ +idPhysics_Base::ResetInterpolationState +================ +*/ +void idPhysics_Base::ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ) { + +} + +/* +================ +idPhysics_Base::UpdateTime +================ +*/ +void idPhysics_Base::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Base::GetTime +================ +*/ +int idPhysics_Base::GetTime() const { + return 0; +} + +/* +================ +idPhysics_Base::GetImpactInfo +================ +*/ +void idPhysics_Base::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Base::ApplyImpulse +================ +*/ +void idPhysics_Base::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Base::AddForce +================ +*/ +void idPhysics_Base::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Base::Activate +================ +*/ +void idPhysics_Base::Activate() { +} + +/* +================ +idPhysics_Base::PutToRest +================ +*/ +void idPhysics_Base::PutToRest() { +} + +/* +================ +idPhysics_Base::IsAtRest +================ +*/ +bool idPhysics_Base::IsAtRest() const { + return true; +} + +/* +================ +idPhysics_Base::GetRestStartTime +================ +*/ +int idPhysics_Base::GetRestStartTime() const { + return 0; +} + +/* +================ +idPhysics_Base::IsPushable +================ +*/ +bool idPhysics_Base::IsPushable() const { + return true; +} + +/* +================ +idPhysics_Base::SaveState +================ +*/ +void idPhysics_Base::SaveState() { +} + +/* +================ +idPhysics_Base::RestoreState +================ +*/ +void idPhysics_Base::RestoreState() { +} + +/* +================ +idPhysics_Base::SetOrigin +================ +*/ +void idPhysics_Base::SetOrigin( const idVec3 &newOrigin, int id ) { +} + +/* +================ +idPhysics_Base::SetAxis +================ +*/ +void idPhysics_Base::SetAxis( const idMat3 &newAxis, int id ) { +} + +/* +================ +idPhysics_Base::Translate +================ +*/ +void idPhysics_Base::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Base::Rotate +================ +*/ +void idPhysics_Base::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Base::GetOrigin +================ +*/ +const idVec3 &idPhysics_Base::GetOrigin( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAxis +================ +*/ +const idMat3 &idPhysics_Base::GetAxis( int id ) const { + return mat3_identity; +} + +/* +================ +idPhysics_Base::SetLinearVelocity +================ +*/ +void idPhysics_Base::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Base::SetAngularVelocity +================ +*/ +void idPhysics_Base::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Base::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetGravity +================ +*/ +void idPhysics_Base::SetGravity( const idVec3 &newGravity ) { + gravityVector = newGravity; + gravityNormal = newGravity; + gravityNormal.Normalize(); +} + +/* +================ +idPhysics_Base::GetGravity +================ +*/ +const idVec3 &idPhysics_Base::GetGravity() const { + return gravityVector; +} + +/* +================ +idPhysics_Base::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Base::GetGravityNormal() const { + return gravityNormal; +} + +/* +================ +idPhysics_Base::ClipTranslation +================ +*/ +void idPhysics_Base::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipRotation +================ +*/ +void idPhysics_Base::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipContents +================ +*/ +int idPhysics_Base::ClipContents( const idClipModel *model ) const { + return 0; +} + +/* +================ +idPhysics_Base::DisableClip +================ +*/ +void idPhysics_Base::DisableClip() { +} + +/* +================ +idPhysics_Base::EnableClip +================ +*/ +void idPhysics_Base::EnableClip() { +} + +/* +================ +idPhysics_Base::UnlinkClip +================ +*/ +void idPhysics_Base::UnlinkClip() { +} + +/* +================ +idPhysics_Base::LinkClip +================ +*/ +void idPhysics_Base::LinkClip() { +} + +/* +================ +idPhysics_Base::EvaluateContacts +================ +*/ +bool idPhysics_Base::EvaluateContacts() { + return false; +} + +/* +================ +idPhysics_Base::GetNumContacts +================ +*/ +int idPhysics_Base::GetNumContacts() const { + return contacts.Num(); +} + +/* +================ +idPhysics_Base::GetContact +================ +*/ +const contactInfo_t &idPhysics_Base::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +idPhysics_Base::ClearContacts +================ +*/ +void idPhysics_Base::ClearContacts() { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent ) { + ent->RemoveContactEntity( self ); + } + } + contacts.SetNum( 0 ); +} + +/* +================ +idPhysics_Base::AddContactEntity +================ +*/ +void idPhysics_Base::AddContactEntity( idEntity *e ) { + int i; + idEntity *ent; + bool found = false; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent == NULL ) { + contactEntities.RemoveIndex( i-- ); + } + if ( ent == e ) { + found = true; + } + } + if ( !found ) { + contactEntities.Alloc() = e; + } +} + +/* +================ +idPhysics_Base::RemoveContactEntity +================ +*/ +void idPhysics_Base::RemoveContactEntity( idEntity *e ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( !ent ) { + contactEntities.RemoveIndex( i-- ); + continue; + } + if ( ent == e ) { + contactEntities.RemoveIndex( i-- ); + return; + } + } +} + +/* +================ +idPhysics_Base::HasGroundContacts +================ +*/ +bool idPhysics_Base::HasGroundContacts() const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundEntity +================ +*/ +bool idPhysics_Base::IsGroundEntity( int entityNum ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundClipModel +================ +*/ +bool idPhysics_Base::IsGroundClipModel( int entityNum, int id ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && contacts[i].id == id && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::SetPushed +================ +*/ +void idPhysics_Base::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Base::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetMaster +================ +*/ +void idPhysics_Base::SetMaster( idEntity *master, const bool orientated ) { +} + +/* +================ +idPhysics_Base::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Base::GetBlockingInfo() const { + return NULL; +} + +/* +================ +idPhysics_Base::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Base::GetBlockingEntity() const { + return NULL; +} + +/* +================ +idPhysics_Base::GetLinearEndTime +================ +*/ +int idPhysics_Base::GetLinearEndTime() const { + return 0; +} + +/* +================ +idPhysics_Base::GetAngularEndTime +================ +*/ +int idPhysics_Base::GetAngularEndTime() const { + return 0; +} + +/* +================ +idPhysics_Base::AddGroundContacts +================ +*/ +void idPhysics_Base::AddGroundContacts( const idClipModel *clipModel ) { + idVec6 dir; + int index, num; + + index = contacts.Num(); + contacts.SetNum( index + 10 ); + + dir.SubVec3(0) = gravityNormal; + dir.SubVec3(1) = vec3_origin; + num = gameLocal.clip.Contacts( &contacts[index], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); + contacts.SetNum( index + num ); +} + +/* +================ +idPhysics_Base::AddContactEntitiesForContacts +================ +*/ +void idPhysics_Base::AddContactEntitiesForContacts() { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent && ent != self ) { + ent->AddContactEntity( self ); + } + } +} + +/* +================ +idPhysics_Base::ActivateContactEntities +================ +*/ +void idPhysics_Base::ActivateContactEntities() { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent ) { + ent->ActivatePhysics( self ); + } else { + contactEntities.RemoveIndex( i-- ); + } + } +} + +/* +================ +idPhysics_Base::IsOutsideWorld +================ +*/ +bool idPhysics_Base::IsOutsideWorld() const { + if ( !gameLocal.clip.GetWorldBounds().Expand( 128.0f ).IntersectsBounds( GetAbsBounds() ) ) { + return true; + } + return false; +} + +/* +================ +idPhysics_Base::DrawVelocity +================ +*/ +void idPhysics_Base::DrawVelocity( int id, float linearScale, float angularScale ) const { + idVec3 dir, org, vec, start, end; + idMat3 axis; + float length, a; + + dir = GetLinearVelocity( id ); + dir *= linearScale; + if ( dir.LengthSqr() > Square( 0.1f ) ) { + dir = dir.Truncate( 10.0f ); + org = GetOrigin( id ); + gameRenderWorld->DebugArrow( colorRed, org, org + dir, 1 ); + } + + dir = GetAngularVelocity( id ); + length = dir.Normalize(); + length *= angularScale; + if ( length > 0.1f ) { + if ( length < 60.0f ) { + length = 60.0f; + } + else if ( length > 360.0f ) { + length = 360.0f; + } + axis = GetAxis( id ); + vec = axis[2]; + if ( idMath::Fabs( dir * vec ) > 0.99f ) { + vec = axis[0]; + } + vec -= vec * dir * vec; + vec.Normalize(); + vec *= 4.0f; + start = org + vec; + for ( a = 20.0f; a < length; a += 20.0f ) { + end = org + idRotation( vec3_origin, dir, -a ).ToMat3() * vec; + gameRenderWorld->DebugLine( colorBlue, start, end, 1 ); + start = end; + } + end = org + idRotation( vec3_origin, dir, -length ).ToMat3() * vec; + gameRenderWorld->DebugArrow( colorBlue, start, end, 1 ); + } +} + +/* +================ +idPhysics_Base::WriteToSnapshot +================ +*/ +void idPhysics_Base::WriteToSnapshot( idBitMsg &msg ) const { +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Base::ReadFromSnapshot( const idBitMsg &msg ) { +} diff --git a/neo/d3xp/physics/Physics_Base.h b/neo/d3xp/physics/Physics_Base.h new file mode 100644 index 00000000..caaee902 --- /dev/null +++ b/neo/d3xp/physics/Physics_Base.h @@ -0,0 +1,204 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_BASE_H__ +#define __PHYSICS_BASE_H__ + +/* +=============================================================================== + + Physics base for a moving object using one or more collision models. + +=============================================================================== +*/ + +#define contactEntity_t idEntityPtr + +class idPhysics_Base : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Base ); + + idPhysics_Base(); + ~idPhysics_Base(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + + void SetSelf( idEntity *e ); + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool Interpolate( const float fraction ); + void ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ); + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate(); + void PutToRest(); + bool IsAtRest() const; + int GetRestStartTime() const; + bool IsPushable() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity() const; + const idVec3 & GetGravityNormal() const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + bool EvaluateContacts(); + int GetNumContacts() const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts(); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts() const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo() const; + idEntity * GetBlockingEntity() const; + + int GetLinearEndTime() const; + int GetAngularEndTime() const; + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +protected: + idEntity * self; // entity using this physics object + int clipMask; // contents the physics object collides with + idVec3 gravityVector; // direction and magnitude of gravity + idVec3 gravityNormal; // normalized direction of gravity + idList contacts; // contacts with other physics objects + idList contactEntities; // entities touching this physics object + +protected: + // add ground contacts for the clip model + void AddGroundContacts( const idClipModel *clipModel ); + // add contact entity links to contact entities + void AddContactEntitiesForContacts(); + // active all contact entities + void ActivateContactEntities(); + // returns true if the whole physics object is outside the world bounds + bool IsOutsideWorld() const; + // draw linear and angular velocity + void DrawVelocity( int id, float linearScale, float angularScale ) const; +}; + +/* +=============================================================================== +Physics interpolation state +=============================================================================== +*/ +struct physicsInterpolationState_t { + physicsInterpolationState_t(): + origin( 0.0f, 0.0f, 0.0f ), + axis( 0.0f, 0.0f, 0.0f, 1.0f ) { + } + + idVec3 origin; + idQuat axis; +}; + +/* +================ +Sets the origin and axis members of stateToUpdate based on previous, next, and the fraction. +Return true if the origin or axis changed, false if they haven't. +================ +*/ +template< class _stateType_ > +bool InterpolatePhysicsState( _stateType_ & stateToUpdate, + const physicsInterpolationState_t & previous, + const physicsInterpolationState_t & next, + const float fraction ) { + const idVec3 oldOrigin = stateToUpdate.origin; + const idMat3 oldAxis = stateToUpdate.axis; + + stateToUpdate.origin = Lerp( previous.origin, next.origin, fraction ); + + const idQuat currentQuat = Slerp( previous.axis, next.axis, fraction ); + stateToUpdate.axis = currentQuat.ToMat3(); + + return ( stateToUpdate.origin != oldOrigin || stateToUpdate.axis != oldAxis ); +} + +#endif /* !__PHYSICS_BASE_H__ */ diff --git a/neo/d3xp/physics/Physics_Monster.cpp b/neo/d3xp/physics/Physics_Monster.cpp new file mode 100644 index 00000000..907e74f5 --- /dev/null +++ b/neo/d3xp/physics/Physics_Monster.cpp @@ -0,0 +1,807 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Monster ) +END_CLASS + +const float OVERCLIP = 1.001f; + +/* +===================== +idPhysics_Monster::CheckGround +===================== +*/ +void idPhysics_Monster::CheckGround( monsterPState_t &state ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + down = state.origin + gravityNormal * CONTACT_EPSILON; + gameLocal.clip.Translation( groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( groundTrace.fraction == 1.0f ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + if ( ( groundTrace.c.normal * -gravityNormal ) < minFloorCosine ) { + state.onGround = false; + return; + } + + state.onGround = true; + + // let the entity know about the collision + self->Collide( groundTrace, state.velocity ); + + // apply impact to a non world floor entity + if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, 0, groundTrace.c.point, state.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +===================== +idPhysics_Monster::SlideMove +===================== +*/ +monsterMoveResult_t idPhysics_Monster::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { + gameLocal.clip.Translation( tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + + if ( tr.c.entityNum != ENTITYNUM_NONE ) { + assert( tr.c.entityNum < MAX_GENTITIES ); + blockingEntity = gameLocal.entities[ tr.c.entityNum ]; + } + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + + return MM_BLOCKED; +} + +/* +===================== +idPhysics_Monster::StepMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel; + monsterMoveResult_t result1, result2; + float stepdist; + float nostepdist; + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta ); + if ( result1 == MM_OK ) { + velocity = noStepVel; + if ( gravityNormal == vec3_zero ) { + start = noStepPos; + return MM_OK; + } + + // try to step down so that we walk down slopes and stairs at a normal rate + down = noStepPos + gravityNormal * maxStepHeight; + gameLocal.clip.Translation( tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( tr.fraction < 1.0f ) { + start = tr.endpos; + return MM_STEPPED; + } else { + start = noStepPos; + return MM_OK; + } + } + + if ( blockingEntity && blockingEntity->IsType( idActor::Type ) ) { + // try to step down in case walking into an actor while going down steps + down = noStepPos + gravityNormal * maxStepHeight; + gameLocal.clip.Translation( tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); + start = tr.endpos; + velocity = noStepVel; + return MM_BLOCKED; + } + + if ( gravityNormal == vec3_zero ) { + return result1; + } + + // try to step up + up = start - gravityNormal * maxStepHeight; + gameLocal.clip.Translation( tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( tr.fraction == 0.0f ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta ); + if ( result2 == MM_BLOCKED ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // step down again + down = stepPos + gravityNormal * maxStepHeight; + gameLocal.clip.Translation( tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); + stepPos = tr.endpos; + + // if the move is further without stepping up, or the slope is too steap, don't step up + nostepdist = ( noStepPos - start ).LengthSqr(); + stepdist = ( stepPos - start ).LengthSqr(); + if ( ( nostepdist >= stepdist ) || ( ( tr.c.normal * -gravityNormal ) < minFloorCosine ) ) { + start = noStepPos; + velocity = noStepVel; + return MM_SLIDING; + } + + start = stepPos; + velocity = stepVel; + + return MM_STEPPED; +} + +/* +================ +idPhysics_Monster::Activate +================ +*/ +void idPhysics_Monster::Activate() { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::Rest +================ +*/ +void idPhysics_Monster::Rest() { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::PutToRest +================ +*/ +void idPhysics_Monster::PutToRest() { + Rest(); +} + +/* +================ +idPhysics_Monster::idPhysics_Monster +================ +*/ +idPhysics_Monster::idPhysics_Monster() { + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + saved = current; + + delta.Zero(); + maxStepHeight = 18.0f; + minFloorCosine = 0.7f; + moveResult = MM_OK; + forceDeltaMove = false; + fly = false; + useVelocityMove = false; + noImpact = false; + blockingEntity = NULL; +} + +/* +================ +idPhysics_Monster_SavePState +================ +*/ +void idPhysics_Monster_SavePState( idSaveGame *savefile, const monsterPState_t &state ) { + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + savefile->WriteBool( state.onGround ); + savefile->WriteInt( state.atRest ); +} + +/* +================ +idPhysics_Monster_RestorePState +================ +*/ +void idPhysics_Monster_RestorePState( idRestoreGame *savefile, monsterPState_t &state ) { + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + savefile->ReadBool( state.onGround ); + savefile->ReadInt( state.atRest ); +} + +/* +================ +idPhysics_Monster::Save +================ +*/ +void idPhysics_Monster::Save( idSaveGame *savefile ) const { + + idPhysics_Monster_SavePState( savefile, current ); + idPhysics_Monster_SavePState( savefile, saved ); + + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( minFloorCosine ); + savefile->WriteVec3( delta ); + + savefile->WriteBool( forceDeltaMove ); + savefile->WriteBool( fly ); + savefile->WriteBool( useVelocityMove ); + savefile->WriteBool( noImpact ); + + savefile->WriteInt( (int)moveResult ); + savefile->WriteObject( blockingEntity ); +} + +/* +================ +idPhysics_Monster::Restore +================ +*/ +void idPhysics_Monster::Restore( idRestoreGame *savefile ) { + + idPhysics_Monster_RestorePState( savefile, current ); + idPhysics_Monster_RestorePState( savefile, saved ); + + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( minFloorCosine ); + savefile->ReadVec3( delta ); + + savefile->ReadBool( forceDeltaMove ); + savefile->ReadBool( fly ); + savefile->ReadBool( useVelocityMove ); + savefile->ReadBool( noImpact ); + + savefile->ReadInt( (int &)moveResult ); + savefile->ReadObject( reinterpret_cast( blockingEntity ) ); +} + +/* +================ +idPhysics_Monster::SetDelta +================ +*/ +void idPhysics_Monster::SetDelta( const idVec3 &d ) { + delta = d; + if ( delta != vec3_origin ) { + Activate(); + } +} + +/* +================ +idPhysics_Monster::SetMaxStepHeight +================ +*/ +void idPhysics_Monster::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Monster::GetMaxStepHeight +================ +*/ +float idPhysics_Monster::GetMaxStepHeight() const { + return maxStepHeight; +} + +/* +================ +idPhysics_Monster::OnGround +================ +*/ +bool idPhysics_Monster::OnGround() const { + return current.onGround; +} + +/* +================ +idPhysics_Monster::GetSlideMoveEntity +================ +*/ +idEntity *idPhysics_Monster::GetSlideMoveEntity() const { + return blockingEntity; +} + +/* +================ +idPhysics_Monster::GetMoveResult +================ +*/ +monsterMoveResult_t idPhysics_Monster::GetMoveResult() const { + return moveResult; +} + +/* +================ +idPhysics_Monster::ForceDeltaMove +================ +*/ +void idPhysics_Monster::ForceDeltaMove( bool force ) { + forceDeltaMove = force; +} + +/* +================ +idPhysics_Monster::UseFlyMove +================ +*/ +void idPhysics_Monster::UseFlyMove( bool force ) { + fly = force; +} + +/* +================ +idPhysics_Monster::UseVelocityMove +================ +*/ +void idPhysics_Monster::UseVelocityMove( bool force ) { + useVelocityMove = force; +} + +/* +================ +idPhysics_Monster::EnableImpact +================ +*/ +void idPhysics_Monster::EnableImpact() { + noImpact = false; +} + +/* +================ +idPhysics_Monster::DisableImpact +================ +*/ +void idPhysics_Monster::DisableImpact() { + noImpact = true; +} + +/* +================ +idPhysics_Monster::Evaluate +================ +*/ +bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + current.velocity = ( current.origin - oldOrigin ) / timeStep; + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return false; + } + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + idPhysics_Monster::CheckGround( current ); + + // if not on the ground or moving upwards + float upspeed; + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + if ( fly || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) { + if ( upspeed < 0.0f ) { + moveResult = MM_FALLING; + } + else { + current.onGround = false; + moveResult = MM_OK; + } + delta = current.velocity * timeStep; + if ( delta != vec3_origin ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly ) { + current.velocity += gravityVector * timeStep; + } + } else { + if ( useVelocityMove ) { + delta = current.velocity * timeStep; + } else { + current.velocity = delta / timeStep; + } + + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + + if ( delta == vec3_origin ) { + Rest(); + } else { + // try moving into the desired direction + moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + } + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + Rest(); + } + + return ( current.origin != oldOrigin ); +} + +/* +================ +idPhysics_Monster::UpdateTime +================ +*/ +void idPhysics_Monster::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Monster::GetTime +================ +*/ +int idPhysics_Monster::GetTime() const { + return gameLocal.time; +} + +/* +================ +idPhysics_Monster::GetImpactInfo +================ +*/ +void idPhysics_Monster::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Monster::ApplyImpulse +================ +*/ +void idPhysics_Monster::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.velocity += impulse * invMass; + Activate(); +} + +/* +================ +idPhysics_Monster::IsAtRest +================ +*/ +bool idPhysics_Monster::IsAtRest() const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Monster::GetRestStartTime +================ +*/ +int idPhysics_Monster::GetRestStartTime() const { + return current.atRest; +} + +/* +================ +idPhysics_Monster::SaveState +================ +*/ +void idPhysics_Monster::SaveState() { + saved = current; +} + +/* +================ +idPhysics_Monster::RestoreState +================ +*/ +void idPhysics_Monster::RestoreState() { + current = saved; + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Monster::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } + clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() ); + Activate(); +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Monster::SetAxis( const idMat3 &newAxis, int id ) { + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis ); + Activate(); +} + +/* +================ +idPhysics_Monster::Translate +================ +*/ +void idPhysics_Monster::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + Activate(); +} + +/* +================ +idPhysics_Monster::Rotate +================ +*/ +void idPhysics_Monster::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); + Activate(); +} + +/* +================ +idPhysics_Monster::SetLinearVelocity +================ +*/ +void idPhysics_Monster::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_Monster::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Monster::SetPushed +================ +*/ +void idPhysics_Monster::SetPushed( int deltaTime ) { + // velocity with which the monster is pushed + current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_Monster::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity; +} + +/* +================ +idPhysics_Monster::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Monster::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +const float MONSTER_VELOCITY_MAX = 4000; +const int MONSTER_VELOCITY_TOTAL_BITS = 16; +const int MONSTER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( MONSTER_VELOCITY_MAX ) ) + 1; +const int MONSTER_VELOCITY_MANTISSA_BITS = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS; + +/* +================ +idPhysics_Monster::WriteToSnapshot +================ +*/ +void idPhysics_Monster::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteLong( current.atRest ); + msg.WriteBits( current.onGround, 1 ); +} + +/* +================ +idPhysics_Monster::ReadFromSnapshot +================ +*/ +void idPhysics_Monster::ReadFromSnapshot( const idBitMsg &msg ) { + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.velocity[0] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[1] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[2] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.atRest = msg.ReadLong(); + current.onGround = msg.ReadBits( 1 ) != 0; +} diff --git a/neo/d3xp/physics/Physics_Monster.h b/neo/d3xp/physics/Physics_Monster.h new file mode 100644 index 00000000..2233ca39 --- /dev/null +++ b/neo/d3xp/physics/Physics_Monster.h @@ -0,0 +1,156 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_MONSTER_H__ +#define __PHYSICS_MONSTER_H__ + +/* +=================================================================================== + + Monster physics + + Simulates the motion of a monster through the environment. The monster motion + is typically driven by animations. + +=================================================================================== +*/ + +typedef enum { + MM_OK, + MM_SLIDING, + MM_BLOCKED, + MM_STEPPED, + MM_FALLING +} monsterMoveResult_t; + +typedef struct monsterPState_s { + int atRest; + bool onGround; + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +} monsterPState_t; + +class idPhysics_Monster : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Monster ); + + idPhysics_Monster(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // maximum step up the monster can take, default 18 units + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight() const; + // minimum cosine of floor angle to be able to stand on the floor + void SetMinFloorCosine( const float newMinFloorCosine ); + // set delta for next move + void SetDelta( const idVec3 &d ); + // returns true if monster is standing on the ground + bool OnGround() const; + // returns the movement result + monsterMoveResult_t GetMoveResult() const; + // overrides any velocity for pure delta movement + void ForceDeltaMove( bool force ); + // whether velocity should be affected by gravity + void UseFlyMove( bool force ); + // don't use delta movement + void UseVelocityMove( bool force ); + // get entity blocking the move + idEntity * GetSlideMoveEntity() const; + // enable/disable activation by impact + void EnableImpact(); + void DisableImpact(); + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void Activate(); + void PutToRest(); + bool IsAtRest() const; + int GetRestStartTime() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +private: + // monster physics state + monsterPState_t current; + monsterPState_t saved; + + // client interpolation state + monsterPState_t previous; + monsterPState_t next; + + // properties + float maxStepHeight; // maximum step height + float minFloorCosine; // minimum cosine of floor angle + idVec3 delta; // delta for next move + + bool forceDeltaMove; + bool fly; + bool useVelocityMove; + bool noImpact; // if true do not activate when another object collides + + // results of last evaluate + monsterMoveResult_t moveResult; + idEntity * blockingEntity; + +private: + void CheckGround( monsterPState_t &state ); + monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + monsterMoveResult_t StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + void Rest(); +}; + +#endif /* !__PHYSICS_MONSTER_H__ */ diff --git a/neo/d3xp/physics/Physics_Parametric.cpp b/neo/d3xp/physics/Physics_Parametric.cpp new file mode 100644 index 00000000..fe583862 --- /dev/null +++ b/neo/d3xp/physics/Physics_Parametric.cpp @@ -0,0 +1,1086 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Parametric ) +END_CLASS + + +/* +================ +idPhysics_Parametric::Activate +================ +*/ +void idPhysics_Parametric::Activate() { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::TestIfAtRest +================ +*/ +bool idPhysics_Parametric::TestIfAtRest() const { + + if ( ( current.linearExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + ( current.angularExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + current.linearInterpolation.GetDuration() == 0 && + current.angularInterpolation.GetDuration() == 0 && + current.spline == NULL ) { + return true; + } + + if ( !current.linearExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.linearInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( current.spline != NULL && !current.spline->IsDone( current.time ) ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_Parametric::Rest +================ +*/ +void idPhysics_Parametric::Rest() { + current.atRest = gameLocal.time; + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::idPhysics_Parametric +================ +*/ +idPhysics_Parametric::idPhysics_Parametric() { + + current.time = gameLocal.time; + current.atRest = -1; + current.useSplineAngles = false; + current.origin.Zero(); + current.angles.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAngles.Zero(); + current.linearExtrapolation.Init( 0, 0, vec3_zero, vec3_zero, vec3_zero, EXTRAPOLATION_NONE ); + current.angularExtrapolation.Init( 0, 0, ang_zero, ang_zero, ang_zero, EXTRAPOLATION_NONE ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + current.spline = NULL; + current.splineInterpolate.Init( 0, 1, 1, 2, 0, 0 ); + + saved = current; + + isPusher = false; + pushFlags = 0; + clipModel = NULL; + isBlocked = false; + memset( &pushResults, 0, sizeof( pushResults ) ); + + hasMaster = false; + isOrientated = false; +} + +/* +================ +idPhysics_Parametric::~idPhysics_Parametric +================ +*/ +idPhysics_Parametric::~idPhysics_Parametric() { + if ( clipModel != NULL ) { + delete clipModel; + clipModel = NULL; + } + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } +} + +/* +================ +idPhysics_Parametric_SavePState +================ +*/ +void idPhysics_Parametric_SavePState( idSaveGame *savefile, const parametricPState_t &state ) { + savefile->WriteInt( state.time ); + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.useSplineAngles ); + savefile->WriteVec3( state.origin ); + savefile->WriteAngles( state.angles ); + savefile->WriteMat3( state.axis ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteAngles( state.localAngles ); + + savefile->WriteInt( (int)state.linearExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.linearExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.linearExtrapolation.GetDuration() ); + savefile->WriteVec3( state.linearExtrapolation.GetStartValue() ); + savefile->WriteVec3( state.linearExtrapolation.GetBaseSpeed() ); + savefile->WriteVec3( state.linearExtrapolation.GetSpeed() ); + + savefile->WriteInt( (int)state.angularExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.angularExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.angularExtrapolation.GetDuration() ); + savefile->WriteAngles( state.angularExtrapolation.GetStartValue() ); + savefile->WriteAngles( state.angularExtrapolation.GetBaseSpeed() ); + savefile->WriteAngles( state.angularExtrapolation.GetSpeed() ); + + savefile->WriteFloat( state.linearInterpolation.GetStartTime() ); + savefile->WriteFloat( state.linearInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDuration() ); + savefile->WriteVec3( state.linearInterpolation.GetStartValue() ); + savefile->WriteVec3( state.linearInterpolation.GetEndValue() ); + + savefile->WriteFloat( state.angularInterpolation.GetStartTime() ); + savefile->WriteFloat( state.angularInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDuration() ); + savefile->WriteAngles( state.angularInterpolation.GetStartValue() ); + savefile->WriteAngles( state.angularInterpolation.GetEndValue() ); + + // spline is handled by owner + + savefile->WriteFloat( state.splineInterpolate.GetStartTime() ); + savefile->WriteFloat( state.splineInterpolate.GetAcceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetDuration() ); + savefile->WriteFloat( state.splineInterpolate.GetDeceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetStartValue() ); + savefile->WriteFloat( state.splineInterpolate.GetEndValue() ); +} + +/* +================ +idPhysics_Parametric_RestorePState +================ +*/ +void idPhysics_Parametric_RestorePState( idRestoreGame *savefile, parametricPState_t &state ) { + extrapolation_t etype; + float startTime, duration, accelTime, decelTime, startValue, endValue; + idVec3 linearStartValue, linearBaseSpeed, linearSpeed, startPos, endPos; + idAngles angularStartValue, angularBaseSpeed, angularSpeed, startAng, endAng; + + savefile->ReadInt( state.time ); + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.useSplineAngles ); + savefile->ReadVec3( state.origin ); + savefile->ReadAngles( state.angles ); + savefile->ReadMat3( state.axis ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadAngles( state.localAngles ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( linearStartValue ); + savefile->ReadVec3( linearBaseSpeed ); + savefile->ReadVec3( linearSpeed ); + + state.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, etype ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( angularStartValue ); + savefile->ReadAngles( angularBaseSpeed ); + savefile->ReadAngles( angularSpeed ); + + state.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, etype ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( startPos ); + savefile->ReadVec3( endPos ); + + state.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( startAng ); + savefile->ReadAngles( endAng ); + + state.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + // spline is handled by owner + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( startValue ); + savefile->ReadFloat( endValue ); + + state.splineInterpolate.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idPhysics_Parametric::Save +================ +*/ +void idPhysics_Parametric::Save( idSaveGame *savefile ) const { + + idPhysics_Parametric_SavePState( savefile, current ); + idPhysics_Parametric_SavePState( savefile, saved ); + + savefile->WriteBool( isPusher ); + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( pushFlags ); + + savefile->WriteTrace( pushResults ); + savefile->WriteBool( isBlocked ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_Parametric::Restore +================ +*/ +void idPhysics_Parametric::Restore( idRestoreGame *savefile ) { + + idPhysics_Parametric_RestorePState( savefile, current ); + idPhysics_Parametric_RestorePState( savefile, saved ); + + savefile->ReadBool( isPusher ); + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( pushFlags ); + + savefile->ReadTrace( pushResults ); + savefile->ReadBool( isBlocked ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_Parametric::SetPusher +================ +*/ +void idPhysics_Parametric::SetPusher( int flags ) { + assert( clipModel ); + isPusher = true; + pushFlags = flags; +} + +/* +================ +idPhysics_Parametric::IsPusher +================ +*/ +bool idPhysics_Parametric::IsPusher() const { + return isPusher; +} + +/* +================ +idPhysics_Parametric::SetLinearExtrapolation +================ +*/ +void idPhysics_Parametric::SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ) { + current.time = gameLocal.time; + current.linearExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localOrigin = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularExtrapolation +================ +*/ +void idPhysics_Parametric::SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ) { + current.time = gameLocal.time; + current.angularExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localAngles = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetLinearExtrapolationType() const { + return current.linearExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::GetAngularExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetAngularExtrapolationType() const { + return current.angularExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::SetLinearInterpolation +================ +*/ +void idPhysics_Parametric::SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ) { + current.time = gameLocal.time; + current.linearInterpolation.Init( time, accelTime, decelTime, duration, startPos, endPos ); + current.localOrigin = startPos; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularInterpolation +================ +*/ +void idPhysics_Parametric::SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ) { + current.time = gameLocal.time; + current.angularInterpolation.Init( time, accelTime, decelTime, duration, startAng, endAng ); + current.localAngles = startAng; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetSpline +================ +*/ +void idPhysics_Parametric::SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ) { + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } + current.spline = spline; + if ( current.spline != NULL ) { + float startTime = current.spline->GetTime( 0 ); + float endTime = current.spline->GetTime( current.spline->GetNumValues() - 1 ); + float length = current.spline->GetLengthForTime( endTime ); + current.splineInterpolate.Init( startTime, accelTime, decelTime, endTime - startTime, 0.0f, length ); + } + current.useSplineAngles = useSplineAngles; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetSpline +================ +*/ +idCurve_Spline *idPhysics_Parametric::GetSpline() const { + return current.spline; +} + +/* +================ +idPhysics_Parametric::GetSplineAcceleration +================ +*/ +int idPhysics_Parametric::GetSplineAcceleration() const { + return current.splineInterpolate.GetAcceleration(); +} + +/* +================ +idPhysics_Parametric::GetSplineDeceleration +================ +*/ +int idPhysics_Parametric::GetSplineDeceleration() const { + return current.splineInterpolate.GetDeceleration(); +} + +/* +================ +idPhysics_Parametric::UsingSplineAngles +================ +*/ +bool idPhysics_Parametric::UsingSplineAngles() const { + return current.useSplineAngles; +} + +/* +================ +idPhysics_Parametric::GetLocalOrigin +================ +*/ +void idPhysics_Parametric::GetLocalOrigin( idVec3 &curOrigin ) const { + curOrigin = current.localOrigin; +} + +/* +================ +idPhysics_Parametric::GetLocalAngles +================ +*/ +void idPhysics_Parametric::GetLocalAngles( idAngles &curAngles ) const { + curAngles = current.localAngles; +} + +/* +================ +idPhysics_Parametric::SetClipModel +================ +*/ +void idPhysics_Parametric::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); +} + +/* +================ +idPhysics_Parametric::GetClipModel +================ +*/ +idClipModel *idPhysics_Parametric::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Parametric::GetNumClipModels +================ +*/ +int idPhysics_Parametric::GetNumClipModels() const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Parametric::SetMass +================ +*/ +void idPhysics_Parametric::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Parametric::GetMass +================ +*/ +float idPhysics_Parametric::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +void idPhysics_Parametric::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +int idPhysics_Parametric::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Parametric::GetBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return idPhysics_Base::GetBounds(); +} + +/* +================ +idPhysics_Parametric::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetAbsBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + return idPhysics_Base::GetAbsBounds(); +} + +/* +================ +idPhysics_Parametric::Evaluate +================ +*/ +bool idPhysics_Parametric::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 oldLocalOrigin, oldOrigin, masterOrigin; + idAngles oldLocalAngles, oldAngles; + idMat3 oldAxis, masterAxis; + + isBlocked = false; + oldLocalOrigin = current.localOrigin; + oldOrigin = current.origin; + oldLocalAngles = current.localAngles; + oldAngles = current.angles; + oldAxis = current.axis; + + current.localOrigin.Zero(); + current.localAngles.Zero(); + + if ( current.spline != NULL ) { + float length = current.splineInterpolate.GetCurrentValue( endTimeMSec ); + float t = current.spline->GetTimeForLength( length, 0.01f ); + current.localOrigin = current.spline->GetCurrentValue( t ); + if ( current.useSplineAngles ) { + current.localAngles = current.spline->GetCurrentFirstDerivative( t ).ToAngles(); + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + current.localOrigin += current.linearInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localOrigin += current.linearExtrapolation.GetCurrentValue( endTimeMSec ); + } + + if ( current.angularInterpolation.GetDuration() != 0 ) { + current.localAngles += current.angularInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localAngles += current.angularExtrapolation.GetCurrentValue( endTimeMSec ); + } + + current.localAngles.Normalize360(); + current.origin = current.localOrigin; + current.angles = current.localAngles; + current.axis = current.localAngles.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( masterAxis.IsRotated() ) { + current.origin = current.origin * masterAxis + masterOrigin; + if ( isOrientated ) { + current.axis *= masterAxis; + current.angles = current.axis.ToAngles(); + } + } + else { + current.origin += masterOrigin; + } + } + + if ( isPusher ) { + + gameLocal.push.ClipPush( pushResults, self, pushFlags, oldOrigin, oldAxis, current.origin, current.axis ); + if ( pushResults.fraction < 1.0f ) { + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, oldOrigin, oldAxis ); + } + current.localOrigin = oldLocalOrigin; + current.origin = oldOrigin; + current.localAngles = oldLocalAngles; + current.angles = oldAngles; + current.axis = oldAxis; + isBlocked = true; + return false; + } + + current.angles = current.axis.ToAngles(); + } + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + + current.time = endTimeMSec; + + if ( TestIfAtRest() ) { + Rest(); + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); +} + +/* +================ +Sets the currentInterpolated state based on previous, next, and the fraction. +================ +*/ +bool idPhysics_Parametric::Interpolate( const float fraction ) { + + if( self->GetNumSnapshotsReceived() <= 1 ) { + return false; + } + + idVec3 oldOrigin = current.origin; + idMat3 oldAxis = current.axis; + + const bool hasChanged = InterpolatePhysicsState( current, previous, next, fraction ); + + gameLocal.push.ClipPush( pushResults, self, pushFlags, oldOrigin, oldAxis, current.origin, current.axis ); + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + + return hasChanged; +} + +/* +================ +idPhysics_Parametric::UpdateTime +================ +*/ +void idPhysics_Parametric::UpdateTime( int endTimeMSec ) { + int timeLeap = endTimeMSec - current.time; + + current.time = endTimeMSec; + // move the trajectory start times to sync the trajectory with the current endTime + current.linearExtrapolation.SetStartTime( current.linearExtrapolation.GetStartTime() + timeLeap ); + current.angularExtrapolation.SetStartTime( current.angularExtrapolation.GetStartTime() + timeLeap ); + current.linearInterpolation.SetStartTime( current.linearInterpolation.GetStartTime() + timeLeap ); + current.angularInterpolation.SetStartTime( current.angularInterpolation.GetStartTime() + timeLeap ); + if ( current.spline != NULL ) { + current.spline->ShiftTime( timeLeap ); + current.splineInterpolate.SetStartTime( current.splineInterpolate.GetStartTime() + timeLeap ); + } +} + +/* +================ +idPhysics_Parametric::GetTime +================ +*/ +int idPhysics_Parametric::GetTime() const { + return current.time; +} + +/* +================ +idPhysics_Parametric::IsAtRest +================ +*/ +bool idPhysics_Parametric::IsAtRest() const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Parametric::GetRestStartTime +================ +*/ +int idPhysics_Parametric::GetRestStartTime() const { + return current.atRest; +} + +/* +================ +idPhysics_Parametric::IsPushable +================ +*/ +bool idPhysics_Parametric::IsPushable() const { + return false; +} + +/* +================ +idPhysics_Parametric::SaveState +================ +*/ +void idPhysics_Parametric::SaveState() { + saved = current; +} + +/* +================ +idPhysics_Parametric::RestoreState +================ +*/ +void idPhysics_Parametric::RestoreState() { + + current = saved; + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } +} + +/* +================ +idPhysics_Parametric::SetOrigin +================ +*/ +void idPhysics_Parametric::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.linearExtrapolation.SetStartValue( newOrigin ); + current.linearInterpolation.SetStartValue( newOrigin ); + + current.localOrigin = current.linearExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAxis +================ +*/ +void idPhysics_Parametric::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAngles = newAxis.ToAngles(); + + current.angularExtrapolation.SetStartValue( current.localAngles ); + current.angularInterpolation.SetStartValue( current.localAngles ); + + current.localAngles = current.angularExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAngles.ToMat3() * masterAxis; + current.angles = current.axis.ToAngles(); + } + else { + current.axis = current.localAngles.ToMat3(); + current.angles = current.localAngles; + } + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + Activate(); +} + +/* +================ +idPhysics_Parametric::Move +================ +*/ +void idPhysics_Parametric::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Parametric::Rotate +================ +*/ +void idPhysics_Parametric::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Parametric::GetOrigin +================ +*/ +const idVec3 &idPhysics_Parametric::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Parametric::GetAxis +================ +*/ +const idMat3 &idPhysics_Parametric::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Parametric::GetAngles +================ +*/ +void idPhysics_Parametric::GetAngles( idAngles &curAngles ) const { + curAngles = current.angles; +} + +/* +================ +idPhysics_Parametric::SetLinearVelocity +================ +*/ +void idPhysics_Parametric::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.origin, newLinearVelocity, vec3_origin ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularVelocity +================ +*/ +void idPhysics_Parametric::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + idRotation rotation; + idVec3 vec; + float angle; + + vec = newAngularVelocity; + angle = vec.Normalize(); + rotation.Set( vec3_origin, vec, (float) RAD2DEG( angle ) ); + + SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.angles, rotation.ToAngles(), ang_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + + curLinearVelocity = current.linearExtrapolation.GetCurrentSpeed( gameLocal.time ); + return curLinearVelocity; +} + +/* +================ +idPhysics_Parametric::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idAngles angles; + + angles = current.angularExtrapolation.GetCurrentSpeed( gameLocal.time ); + curAngularVelocity = angles.ToAngularVelocity(); + return curAngularVelocity; +} + +/* +================ +idPhysics_Parametric::DisableClip +================ +*/ +void idPhysics_Parametric::DisableClip() { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Parametric::EnableClip +================ +*/ +void idPhysics_Parametric::EnableClip() { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Parametric::UnlinkClip +================ +*/ +void idPhysics_Parametric::UnlinkClip() { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Parametric::LinkClip +================ +*/ +void idPhysics_Parametric::LinkClip() { + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } +} + +/* +================ +idPhysics_Parametric::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Parametric::GetBlockingInfo() const { + return ( isBlocked ? &pushResults : NULL ); +} + +/* +================ +idPhysics_Parametric::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Parametric::GetBlockingEntity() const { + if ( isBlocked ) { + return gameLocal.entities[ pushResults.c.entityNum ]; + } + return NULL; +} + +/* +================ +idPhysics_Parametric::SetMaster +================ +*/ +void idPhysics_Parametric::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAngles = ( current.axis * masterAxis.Transpose() ).ToAngles(); + } + else { + current.localAngles = current.axis.ToAngles(); + } + + current.linearExtrapolation.SetStartValue( current.localOrigin ); + current.angularExtrapolation.SetStartValue( current.localAngles ); + hasMaster = true; + isOrientated = orientated; + } + } + else { + if ( hasMaster ) { + // transform from master space to world space + current.localOrigin = current.origin; + current.localAngles = current.angles; + SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.origin, vec3_origin, vec3_origin ); + SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.angles, ang_zero, ang_zero ); + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Parametric::GetLinearEndTime +================ +*/ +int idPhysics_Parametric::GetLinearEndTime() const { + if ( current.spline != NULL ) { + if ( current.spline->GetBoundaryType() != idCurve_Spline::BT_CLOSED ) { + return current.spline->GetTime( current.spline->GetNumValues() - 1 ); + } else { + return 0; + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + return current.linearInterpolation.GetEndTime(); + } else { + return current.linearExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::GetAngularEndTime +================ +*/ +int idPhysics_Parametric::GetAngularEndTime() const { + if ( current.angularInterpolation.GetDuration() != 0 ) { + return current.angularInterpolation.GetEndTime(); + } else { + return current.angularExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::WriteToSnapshot +================ +*/ +void idPhysics_Parametric::WriteToSnapshot( idBitMsg &msg ) const { + + const idQuat currentQuat = current.axis.ToQuat(); + + WriteFloatArray( msg, current.origin ); + WriteFloatArray( msg, currentQuat ); +} + +/* +================ +idPhysics_Parametric::ReadFromSnapshot +================ +*/ +void idPhysics_Parametric::ReadFromSnapshot( const idBitMsg &msg ) { + + previous = next; + + next.origin = ReadFloatArray< idVec3 >( msg ); + next.axis = ReadFloatArray< idQuat >( msg ); + + if( self->GetNumSnapshotsReceived() <= 1 ) { + current.origin = next.origin; + previous.origin = next.origin; + current.axis = next.axis.ToMat3(); + previous.axis = next.axis; + } +} diff --git a/neo/d3xp/physics/Physics_Parametric.h b/neo/d3xp/physics/Physics_Parametric.h new file mode 100644 index 00000000..c8f806bf --- /dev/null +++ b/neo/d3xp/physics/Physics_Parametric.h @@ -0,0 +1,180 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_PARAMETRIC_H__ +#define __PHYSICS_PARAMETRIC_H__ + +/* +=================================================================================== + + Parametric physics + + Used for predefined or scripted motion. The motion of an object is completely + parametrized. By adjusting the parameters an object is forced to follow a + predefined path. The parametric physics is typically used for doors, bridges, + rotating fans etc. + +=================================================================================== +*/ + +typedef struct parametricPState_s { + int time; // physics time + int atRest; // set when simulation is suspended + idVec3 origin; // world origin + idAngles angles; // world angles + idMat3 axis; // world axis + idVec3 localOrigin; // local origin + idAngles localAngles; // local angles + idExtrapolate linearExtrapolation; // extrapolation based description of the position over time + idExtrapolate angularExtrapolation; // extrapolation based description of the orientation over time + idInterpolateAccelDecelLinear linearInterpolation; // interpolation based description of the position over time + idInterpolateAccelDecelLinear angularInterpolation; // interpolation based description of the orientation over time + idCurve_Spline * spline; // spline based description of the position over time + idInterpolateAccelDecelLinear splineInterpolate; // position along the spline over time + bool useSplineAngles; // set the orientation using the spline +} parametricPState_t; + +class idPhysics_Parametric : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Parametric ); + + idPhysics_Parametric(); + ~idPhysics_Parametric(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPusher( int flags ); + bool IsPusher() const; + + void SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ); + void SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ); + extrapolation_t GetLinearExtrapolationType() const; + extrapolation_t GetAngularExtrapolationType() const; + + void SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ); + void SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ); + + void SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ); + idCurve_Spline *GetSpline() const; + int GetSplineAcceleration() const; + int GetSplineDeceleration() const; + bool UsingSplineAngles() const; + + void GetLocalOrigin( idVec3 &curOrigin ) const; + void GetLocalAngles( idAngles &curAngles ) const; + + void GetAngles( idAngles &curAngles ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool Interpolate( const float fraction ); + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void Activate(); + bool IsAtRest() const; + int GetRestStartTime() const; + bool IsPushable() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo() const; + idEntity * GetBlockingEntity() const; + + int GetLinearEndTime() const; + int GetAngularEndTime() const; + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +private: + // parametric physics state + parametricPState_t current; + parametricPState_t saved; + + physicsInterpolationState_t previous; + physicsInterpolationState_t next; + + // pusher + bool isPusher; + idClipModel * clipModel; + int pushFlags; + + // results of last evaluate + trace_t pushResults; + bool isBlocked; + + // master + bool hasMaster; + bool isOrientated; + +private: + bool TestIfAtRest() const; + void Rest(); +}; + +#endif /* !__PHYSICS_PARAMETRIC_H__ */ diff --git a/neo/d3xp/physics/Physics_Player.cpp b/neo/d3xp/physics/Physics_Player.cpp new file mode 100644 index 00000000..d89aa7cd --- /dev/null +++ b/neo/d3xp/physics/Physics_Player.cpp @@ -0,0 +1,2178 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player ) +END_CLASS + +// movement parameters +const float PM_STOPSPEED = 100.0f; +const float PM_SWIMSCALE = 0.5f; +const float PM_LADDERSPEED = 100.0f; +const float PM_STEPSCALE = 1.0f; + +const float PM_ACCELERATE = 10.0f; +const float PM_AIRACCELERATE = 1.0f; +const float PM_WATERACCELERATE = 4.0f; +const float PM_FLYACCELERATE = 8.0f; + +const float PM_FRICTION = 6.0f; +const float PM_AIRFRICTION = 0.0f; +const float PM_WATERFRICTION = 1.0f; +const float PM_FLYFRICTION = 3.0f; +const float PM_NOCLIPFRICTION = 12.0f; + +const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes +const float OVERCLIP = 1.001f; + +// movementFlags +const int PMF_DUCKED = 1; // set when ducking +const int PMF_JUMPED = 2; // set when the player jumped this frame +const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame +const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame +const int PMF_JUMP_HELD = 16; // set when jump button is held down +const int PMF_TIME_LAND = 32; // movementTime is time before rejump +const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time +const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump +const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK); + +int c_pmove = 0; + +extern idCVar pm_clientInterpolation_Divergence; + +/* +============ +idPhysics_Player::CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const { + int max; + float total; + float scale; + + int forwardmove = cmd.forwardmove; + int rightmove = cmd.rightmove; + int upmove = 0; + + // since the crouch key doubles as downward movement, ignore downward movement when we're on the ground + // otherwise crouch speed will be lower than specified + if ( !walking ) { + upmove = ( ( cmd.buttons & BUTTON_JUMP ) ? 127 : 0 ) - ( ( cmd.buttons & BUTTON_CROUCH ) ? 127 : 0 ); + } + + max = abs( forwardmove ); + if ( abs( rightmove ) > max ) { + max = abs( rightmove ); + } + if ( abs( upmove ) > max ) { + max = abs( upmove ); + } + + if ( !max ) { + return 0.0f; + } + + total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove ); + scale = (float) playerSpeed * max / ( 127.0f * total ); + + return scale; +} + +/* +============== +idPhysics_Player::Accelerate + +Handles user intended acceleration +============== +*/ +void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) { +#if 1 + // q2 style + float addspeed, accelspeed, currentspeed; + + currentspeed = current.velocity * wishdir; + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel * frametime * wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + current.velocity += accelspeed * wishdir; +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + idVec3 wishVelocity; + idVec3 pushDir; + float pushLen; + float canPush; + + wishVelocity = wishdir * wishspeed; + pushDir = wishVelocity - current.velocity; + pushLen = pushDir.Normalize(); + + canPush = accel * frametime * wishspeed; + if (canPush > pushLen) { + canPush = pushLen; + } + + current.velocity += canPush * pushDir; +#endif +} + +/* +================== +idPhysics_Player::SlideMove + +Returns true if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 + +bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) { + int i, j, k, pushFlags; + int bumpcount, numbumps, numplanes; + float d, time_left, into, totalMass; + idVec3 dir, planes[MAX_CLIP_PLANES]; + idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity; + trace_t trace, stepTrace, downTrace; + bool nearGround, stepped, pushed; + + numbumps = 4; + + primal_velocity = current.velocity; + + if ( gravity ) { + endVelocity = current.velocity + gravityVector * frametime; + current.velocity = ( current.velocity + endVelocity ) * 0.5f; + primal_velocity = endVelocity; + if ( groundPlane ) { + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + } + else { + endVelocity = current.velocity; + } + + time_left = frametime; + + // never turn against the ground plane + if ( groundPlane ) { + numplanes = 1; + planes[0] = groundTrace.c.normal; + } else { + numplanes = 0; + } + + // never turn against original velocity + planes[numplanes] = current.velocity; + planes[numplanes].Normalize(); + numplanes++; + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { + + // calculate position we are trying to move to + end = current.origin + time_left * current.velocity; + + // see if we can make it there + gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + + time_left -= time_left * trace.fraction; + current.origin = trace.endpos; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + + stepped = pushed = false; + + // if we are allowed to step up + if ( stepUp ) { + + nearGround = groundPlane | ladder; + + if ( !nearGround ) { + // trace down to see if the player is near the ground + // step checking when near the ground allows the player to move up stairs smoothly while jumping + stepEnd = current.origin + maxStepHeight * gravityNormal; + gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ); + } + + // may only step up if near the ground or on a ladder + if ( nearGround ) { + + // step up + stepEnd = current.origin - maxStepHeight * gravityNormal; + gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + // trace along velocity + stepEnd = downTrace.endpos + time_left * current.velocity; + gameLocal.clip.Translation( stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + // step down + stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal; + gameLocal.clip.Translation( downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ) { + + // if moved the entire distance + if ( stepTrace.fraction >= 1.0f ) { + time_left = 0; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + current.velocity *= PM_STEPSCALE; + break; + } + + // if the move is further when stepping up + if ( stepTrace.fraction > trace.fraction ) { + time_left -= time_left * stepTrace.fraction; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + current.velocity *= PM_STEPSCALE; + trace = stepTrace; + stepped = true; + } + } + } + } + + // if we can push other entities and not blocked by the world + if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) { + + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + // clip movement, only push idMoveables, don't push entities the player is standing on + // apply impact to pushed objects + pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f ); + pushed = true; + } + + current.origin = trace.endpos; + time_left -= time_left * trace.fraction; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + } + + if ( !stepped ) { + // let the entity know about the collision + self->Collide( trace, current.velocity ); + } + + if ( numplanes >= MAX_CLIP_PLANES ) { + // MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris + // where it may hit the max clip planes + current.velocity = vec3_origin; + return true; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0; i < numplanes; i++ ) { + if ( ( trace.c.normal * planes[i] ) > 0.999f ) { + current.velocity += trace.c.normal; + break; + } + } + if ( i < numplanes ) { + continue; + } + planes[numplanes] = trace.c.normal; + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0; i < numplanes; i++ ) { + into = current.velocity * planes[i]; + if ( into >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // slide along the plane + clipVelocity = current.velocity; + clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // slide along the plane + endClipVelocity = endVelocity; + endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0; j < numplanes; j++ ) { + if ( j == i ) { + continue; + } + if ( ( clipVelocity * planes[j] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + + // see if it goes back into the first clip plane + if ( ( clipVelocity * planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * current.velocity; + clipVelocity = d * dir; + + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * endVelocity; + endClipVelocity = d * dir; + + // see if there is a third plane the the new move enters + for ( k = 0; k < numplanes; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( ( clipVelocity * planes[k] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + current.velocity = vec3_origin; + return true; + } + } + + // if we have fixed all interactions, try another move + current.velocity = clipVelocity; + endVelocity = endClipVelocity; + break; + } + } + + // step down + if ( stepDown && groundPlane ) { + stepEnd = current.origin + gravityNormal * maxStepHeight; + gameLocal.clip.Translation( downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) { + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_DOWN; + current.velocity *= PM_STEPSCALE; + } + } + + if ( gravity ) { + current.velocity = endVelocity; + } + + // come to a dead stop when the velocity orthogonal to the gravity flipped + clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal; + endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal; + if ( clipVelocity * endClipVelocity < 0.0f ) { + current.velocity = gravityNormal * current.velocity * gravityNormal; + } + + return (bool)( bumpcount == 0 ); +} + +/* +================== +idPhysics_Player::Friction + +Handles both ground friction and water friction +================== +*/ +void idPhysics_Player::Friction() { + idVec3 vel; + float speed, newspeed, control; + float drop; + + vel = current.velocity; + if ( walking ) { + // ignore slope movement, remove all velocity in gravity direction + vel += (vel * gravityNormal) * gravityNormal; + } + + speed = vel.Length(); + if ( speed < 1.0f ) { + // remove all movement orthogonal to gravity, allows for sinking underwater + if ( fabs( current.velocity * gravityNormal ) < 1e-5f ) { + current.velocity.Zero(); + } else { + current.velocity = (current.velocity * gravityNormal) * gravityNormal; + } + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // spectator friction + if ( current.movementType == PM_SPECTATOR ) { + drop += speed * PM_FLYFRICTION * frametime; + } + // apply ground friction + else if ( walking && waterLevel <= WATERLEVEL_FEET ) { + // no friction on slick surfaces + if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) { + control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed; + drop += control * PM_FRICTION * frametime; + } + } + } + // apply water friction even if just wading + else if ( waterLevel ) { + drop += speed * PM_WATERFRICTION * waterLevel * frametime; + } + // apply air friction + else { + drop += speed * PM_AIRFRICTION * frametime; + } + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + current.velocity *= ( newspeed / speed ); +} + +/* +=================== +idPhysics_Player::WaterJumpMove + +Flying out of the water +=================== +*/ +void idPhysics_Player::WaterJumpMove() { + + // waterjump has no control, but falls + idPhysics_Player::SlideMove( true, true, false, false ); + + // add gravity + current.velocity += gravityNormal * frametime; + // if falling down + if ( current.velocity * gravityNormal > 0.0f ) { + // cancel as soon as we are falling down again + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } +} + +/* +=================== +idPhysics_Player::WaterMove +=================== +*/ +void idPhysics_Player::WaterMove() { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + float vel; + + if ( idPhysics_Player::CheckWaterJump() ) { + idPhysics_Player::WaterJumpMove(); + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // user intentions + if ( !scale ) { + wishvel = gravityNormal * 60; // sink towards bottom + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * ( ( ( command.buttons & BUTTON_JUMP ) ? 127 : 0 ) - ( ( command.buttons & BUTTON_CROUCH ) ? 127 : 0 ) ); + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + if ( wishspeed > playerSpeed * PM_SWIMSCALE ) { + wishspeed = playerSpeed * PM_SWIMSCALE; + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE ); + + // make sure we can go up slopes easily under water + if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) { + vel = current.velocity.Length(); + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + current.velocity.Normalize(); + current.velocity *= vel; + } + + idPhysics_Player::SlideMove( false, true, false, false ); +} + +/* +=================== +idPhysics_Player::FlyMove +=================== +*/ +void idPhysics_Player::FlyMove() { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + // normal slowdown + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * ( ( ( command.buttons & BUTTON_JUMP ) ? 127 : 0 ) - ( ( command.buttons & BUTTON_CROUCH ) ? 127 : 0 ) ); + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +=================== +idPhysics_Player::AirMove +=================== +*/ +void idPhysics_Player::AirMove() { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishvel -= (wishvel * gravityNormal) * gravityNormal; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // not on ground, so little effect on velocity + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_AIRACCELERATE ); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( groundPlane ) { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + + idPhysics_Player::SlideMove( true, false, false, false ); +} + +/* +=================== +idPhysics_Player::WalkMove +=================== +*/ +void idPhysics_Player::WalkMove() { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + float accelerate; + idVec3 oldVelocity, vel; + float oldVel, newVel; + + if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) { + // begin swimming + idPhysics_Player::WaterMove(); + return; + } + + if ( idPhysics_Player::CheckJump() ) { + // jumped away + if ( waterLevel > WATERLEVEL_FEET ) { + idPhysics_Player::WaterMove(); + } + else { + idPhysics_Player::AirMove(); + } + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + + // project the forward and right directions onto the ground plane + viewForward.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + viewRight.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + // + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // clamp the speed lower if wading or walking on the bottom + if ( waterLevel ) { + float waterScale; + + waterScale = waterLevel / 3.0f; + waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale; + if ( wishspeed > playerSpeed * waterScale ) { + wishspeed = playerSpeed * waterScale; + } + } + + // when a player gets hit, they temporarily lose full control, which allows them to be moved a bit + if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) { + accelerate = PM_AIRACCELERATE; + } + else { + accelerate = PM_ACCELERATE; + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate ); + + if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) { + current.velocity += gravityVector * frametime; + } + + oldVelocity = current.velocity; + + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + // if not clipped into the opposite direction + if ( oldVelocity * current.velocity > 0.0f ) { + newVel = current.velocity.LengthSqr(); + if ( newVel > 1.0f ) { + oldVel = oldVelocity.LengthSqr(); + if ( oldVel > 1.0f ) { + // don't decrease velocity when going up or down a slope + current.velocity *= idMath::Sqrt( oldVel / newVel ); + } + } + } + + // don't do anything if standing still + vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal; + if ( !vel.LengthSqr() ) { + return; + } + + gameLocal.push.InitSavingPushedEntityPositions(); + + idPhysics_Player::SlideMove( false, true, true, true ); +} + +/* +============== +idPhysics_Player::DeadMove +============== +*/ +void idPhysics_Player::DeadMove() { + float forward; + + if ( !walking ) { + return; + } + + // extra friction + forward = current.velocity.Length(); + forward -= 20; + if ( forward <= 0 ) { + current.velocity = vec3_origin; + } + else { + current.velocity.Normalize(); + current.velocity *= forward; + } +} + +/* +=============== +idPhysics_Player::NoclipMove +=============== +*/ +void idPhysics_Player::NoclipMove() { + float speed, drop, friction, newspeed, stopspeed; + float scale, wishspeed; + idVec3 wishdir; + + // friction + speed = current.velocity.Length(); + if ( speed < 20.0f ) { + current.velocity = vec3_origin; + } + else { + stopspeed = playerSpeed * 0.3f; + if ( speed < stopspeed ) { + speed = stopspeed; + } + friction = PM_NOCLIPFRICTION; + drop = speed * friction * frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= newspeed / speed; + } + + // accelerate + scale = idPhysics_Player::CmdScale( command ); + + wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishdir -= scale * gravityNormal * ( ( ( command.buttons & BUTTON_JUMP ) ? 127 : 0 ) - ( ( command.buttons & BUTTON_CROUCH ) ? 127 : 0 ) ); + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_ACCELERATE ); + + // move + current.origin += frametime * current.velocity; +} + +/* +=============== +idPhysics_Player::SpectatorMove +=============== +*/ +void idPhysics_Player::SpectatorMove() { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + trace_t trace; + idVec3 end; + + // fly movement + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +============ +idPhysics_Player::LadderMove +============ +*/ +void idPhysics_Player::LadderMove() { + idVec3 wishdir, wishvel, right; + float wishspeed, scale; + float upscale; + + // stick to the ladder + wishvel = -100.0f * ladderNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + + upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f; + if ( upscale > 1.0f ) { + upscale = 1.0f; + } + else if ( upscale < -1.0f ) { + upscale = -1.0f; + } + + scale = idPhysics_Player::CmdScale( command ); + wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove; + + // strafe + if ( command.rightmove ) { + // right vector orthogonal to gravity + right = viewRight - (gravityNormal * viewRight) * gravityNormal; + // project right vector into ladder plane + right = right - (ladderNormal * right) * ladderNormal; + right.Normalize(); + + // if we are looking away from the ladder, reverse the right vector + if ( ladderNormal * viewForward > 0.0f ) { + right = -right; + } + wishvel += 2.0f * right * scale * (float) command.rightmove; + } + + // up down movement + if ( command.buttons & (BUTTON_JUMP|BUTTON_CROUCH) ) { + wishvel += -0.5f * gravityNormal * scale * (float)( ( ( command.buttons & BUTTON_JUMP ) ? 127 : 0 ) - ( ( command.buttons & BUTTON_CROUCH ) ? 127 : 0 ) ); + } + + // do strafe friction + idPhysics_Player::Friction(); + + // accelerate + wishspeed = wishvel.Normalize(); + idPhysics_Player::Accelerate( wishvel, wishspeed, PM_ACCELERATE ); + + // cap the vertical velocity + upscale = current.velocity * -gravityNormal; + if ( upscale < -PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale + PM_LADDERSPEED); + } + else if ( upscale > PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale - PM_LADDERSPEED); + } + + if ( (wishvel * gravityNormal) == 0.0f ) { + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity += gravityVector * frametime; + if ( current.velocity * gravityNormal > 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + else { + current.velocity -= gravityVector * frametime; + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + } + + idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); +} + +/* +============= +idPhysics_Player::CorrectAllSolid +============= +*/ +void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:allsolid\n", c_pmove ); + } + + // FIXME: jitter around to find a free spot ? + + if ( trace.fraction >= 1.0f ) { + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = current.origin; + trace.endAxis = clipModelAxis; + trace.fraction = 0.0f; + trace.c.dist = current.origin.z; + trace.c.normal.Set( 0, 0, 1 ); + trace.c.point = current.origin; + trace.c.entityNum = ENTITYNUM_WORLD; + trace.c.id = 0; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.material = NULL; + trace.c.contents = contents; + } +} + +/* +============= +idPhysics_Player::CheckGround +============= +*/ +void idPhysics_Player::CheckGround() { + int i, contents; + idVec3 point; + bool hadGroundContacts; + + hadGroundContacts = HasGroundContacts(); + + // set the clip model origin before getting the contacts + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); + + // setup a ground trace from the contacts + groundTrace.endpos = current.origin; + groundTrace.endAxis = clipModel->GetAxis(); + if ( contacts.Num() ) { + groundTrace.fraction = 0.0f; + groundTrace.c = contacts[0]; + for ( i = 1; i < contacts.Num(); i++ ) { + groundTrace.c.normal += contacts[i].normal; + } + groundTrace.c.normal.Normalize(); + } else { + groundTrace.fraction = 1.0f; + } + + contents = gameLocal.clip.Contents( current.origin, clipModel, clipModel->GetAxis(), -1, self ); + if ( contents & MASK_SOLID ) { + // do something corrective if stuck in solid + idPhysics_Player::CorrectAllSolid( groundTrace, contents ); + } + + // if the trace didn't hit anything, we are in free fall + if ( groundTrace.fraction == 1.0f ) { + groundPlane = false; + walking = false; + groundEntityPtr = NULL; + return; + } + + groundMaterial = groundTrace.c.material; + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + // check if getting thrown off the ground + if ( (current.velocity * -gravityNormal) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 10.0f ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:kickoff\n", c_pmove ); + } + + groundPlane = false; + walking = false; + return; + } + + // slopes that are too steep will not be considered onground + if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:steep\n", c_pmove ); + } + + // FIXME: if they can't slide down the slope, let them walk (sharp crevices) + + // make sure we don't die from sliding down a steep slope + if ( current.velocity * gravityNormal > 150.0f ) { + current.velocity -= ( current.velocity * gravityNormal - 150.0f ) * gravityNormal; + } + + groundPlane = true; + walking = false; + return; + } + + groundPlane = true; + walking = true; + + // hitting solid ground will end a waterjump + if ( current.movementFlags & PMF_TIME_WATERJUMP ) { + current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + current.movementTime = 0; + } + + // if the player didn't have ground contacts the previous frame + if ( !hadGroundContacts ) { + + // don't do landing time if we were just going down a slope + if ( (current.velocity * -gravityNormal) < -200.0f ) { + // don't allow another jump for a little while + current.movementFlags |= PMF_TIME_LAND; + current.movementTime = 250; + } + } + + // let the entity know about the collision + self->Collide( groundTrace, current.velocity ); + + if ( groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +============== +idPhysics_Player::CheckDuck + +Sets clip model size +============== +*/ +void idPhysics_Player::CheckDuck() { + trace_t trace; + idVec3 end; + idBounds bounds; + float maxZ; + + if ( current.movementType == PM_DEAD ) { + maxZ = pm_deadheight.GetFloat(); + } else { + // stand up when up against a ladder + if ( ( command.buttons & BUTTON_CROUCH ) && !ladder ) { + // duck + current.movementFlags |= PMF_DUCKED; + } else { + // stand up if possible + if ( current.movementFlags & PMF_DUCKED ) { + // try to stand up + end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal; + gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( trace.fraction >= 1.0f ) { + current.movementFlags &= ~PMF_DUCKED; + } + } + } + + if ( current.movementFlags & PMF_DUCKED ) { + playerSpeed = crouchSpeed; + maxZ = pm_crouchheight.GetFloat(); + } else { + maxZ = pm_normalheight.GetFloat(); + } + } + // if the clipModel height should change + if ( clipModel->GetBounds()[1][2] != maxZ ) { + + bounds = clipModel->GetBounds(); + bounds[1][2] = maxZ; + if ( pm_usecylinder.GetBool() ) { + clipModel->LoadModel( idTraceModel( bounds, 8 ) ); + } else { + clipModel->LoadModel( idTraceModel( bounds ) ); + } + } +} + +/* +================ +idPhysics_Player::CheckLadder +================ +*/ +void idPhysics_Player::CheckLadder() { + idVec3 forward, start, end; + trace_t trace; + float tracedist; + + if ( current.movementTime ) { + return; + } + + // if on the ground moving backwards + if ( walking && command.forwardmove <= 0 ) { + return; + } + + // forward vector orthogonal to gravity + forward = viewForward - (gravityNormal * viewForward) * gravityNormal; + forward.Normalize(); + + if ( walking ) { + // don't want to get sucked towards the ladder when still walking + tracedist = 1.0f; + } else { + tracedist = 48.0f; + } + + end = current.origin + tracedist * forward; + gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + + // if near a surface + if ( trace.fraction < 1.0f ) { + + // if a ladder surface + if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) { + + // check a step height higher + end = current.origin - gravityNormal * ( maxStepHeight * 0.75f ); + gameLocal.clip.Translation( trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + start = trace.endpos; + end = start + tracedist * forward; + gameLocal.clip.Translation( trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self ); + + // if also near a surface a step height higher + if ( trace.fraction < 1.0f ) { + + // if it also is a ladder surface + if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) { + ladder = true; + ladderNormal = trace.c.normal; + } + } + } + } +} + +/* +============= +idPhysics_Player::CheckJump +============= +*/ +bool idPhysics_Player::CheckJump() { + idVec3 addVelocity; + + if ( ( command.buttons & BUTTON_JUMP ) == 0 ) { + // not holding jump + return false; + } + + // must wait for jump to be released + if ( current.movementFlags & PMF_JUMP_HELD ) { + return false; + } + + // don't jump if we can't stand up + if ( current.movementFlags & PMF_DUCKED ) { + return false; + } + + groundPlane = false; // jumping away + walking = false; + current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED; + + addVelocity = 2.0f * maxJumpHeight * -gravityVector; + addVelocity *= idMath::Sqrt( addVelocity.Normalize() ); + current.velocity += addVelocity; + + return true; +} + +/* +============= +idPhysics_Player::CheckWaterJump +============= +*/ +bool idPhysics_Player::CheckWaterJump() { + idVec3 spot; + int cont; + idVec3 flatforward; + + if ( current.movementTime ) { + return false; + } + + // check for water jump + if ( waterLevel != WATERLEVEL_WAIST ) { + return false; + } + + flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal; + flatforward.Normalize(); + + spot = current.origin + 30.0f * flatforward; + spot -= 4.0f * gravityNormal; + cont = gameLocal.clip.Contents( spot, NULL, mat3_identity, -1, self ); + if ( !(cont & CONTENTS_SOLID) ) { + return false; + } + + spot -= 16.0f * gravityNormal; + cont = gameLocal.clip.Contents( spot, NULL, mat3_identity, -1, self ); + if ( cont ) { + return false; + } + + // jump out of water + current.velocity = 200.0f * viewForward - 350.0f * gravityNormal; + current.movementFlags |= PMF_TIME_WATERJUMP; + current.movementTime = 2000; + + return true; +} + +/* +============= +idPhysics_Player::SetWaterLevel +============= +*/ +void idPhysics_Player::SetWaterLevel() { + idVec3 point; + idBounds bounds; + int contents; + + // + // get waterlevel, accounting for ducking + // + waterLevel = WATERLEVEL_NONE; + waterType = 0; + + bounds = clipModel->GetBounds(); + + // check at feet level + point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal; + contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + + waterType = contents; + waterLevel = WATERLEVEL_FEET; + + // check at waist level + point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal; + contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + + waterLevel = WATERLEVEL_WAIST; + + // check at head level + point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal; + contents = gameLocal.clip.Contents( point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + waterLevel = WATERLEVEL_HEAD; + } + } + } +} + +/* +================ +idPhysics_Player::DropTimers +================ +*/ +void idPhysics_Player::DropTimers() { + // drop misc timing counter + if ( current.movementTime ) { + if ( framemsec >= current.movementTime ) { + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } + else { + current.movementTime -= framemsec; + } + } +} + +/* +================ +idPhysics_Player::MovePlayer +================ +*/ +void idPhysics_Player::MovePlayer( int msec ) { + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint for the previous frame + c_pmove++; + + walking = false; + groundPlane = false; + ladder = false; + + // determine the time + framemsec = msec; + frametime = framemsec * 0.001f; + + // default speed + playerSpeed = walkSpeed; + + // remove jumped and stepped up flag + current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN); + current.stepUp = 0.0f; + + if ( ( command.buttons & BUTTON_JUMP ) == 0 ) { + // not holding jump + current.movementFlags &= ~PMF_JUMP_HELD; + } + + // if no movement at all + if ( current.movementType == PM_FREEZE ) { + return; + } + + // move the player velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + // view vectors + viewForward = commandForward * clipModelAxis; + viewRight = gravityNormal.Cross( viewForward ); + viewRight.Normalize(); + + // fly in spectator mode + if ( current.movementType == PM_SPECTATOR ) { + SpectatorMove(); + idPhysics_Player::DropTimers(); + return; + } + + // special no clip mode + if ( current.movementType == PM_NOCLIP ) { + idPhysics_Player::NoclipMove(); + idPhysics_Player::DropTimers(); + return; + } + + // no control when dead + if ( current.movementType == PM_DEAD ) { + command.forwardmove = 0; + command.rightmove = 0; + command.buttons &= ~(BUTTON_JUMP|BUTTON_CROUCH); + } + + // set watertype and waterlevel + idPhysics_Player::SetWaterLevel(); + + // check for ground + idPhysics_Player::CheckGround(); + + // check if up against a ladder + idPhysics_Player::CheckLadder(); + + // set clip model size + idPhysics_Player::CheckDuck(); + + // handle timers + idPhysics_Player::DropTimers(); + + // move + if ( current.movementType == PM_DEAD ) { + // dead + idPhysics_Player::DeadMove(); + } + else if ( ladder ) { + // going up or down a ladder + idPhysics_Player::LadderMove(); + } + else if ( current.movementFlags & PMF_TIME_WATERJUMP ) { + // jumping out of water + idPhysics_Player::WaterJumpMove(); + } + else if ( waterLevel > 1 ) { + // swimming + idPhysics_Player::WaterMove(); + } + else if ( walking ) { + // walking on ground + idPhysics_Player::WalkMove(); + } + else { + // airborne + idPhysics_Player::AirMove(); + } + + // set watertype, waterlevel and groundentity + idPhysics_Player::SetWaterLevel(); + idPhysics_Player::CheckGround(); + + // move the player velocity back into the world frame + current.velocity += current.pushVelocity; + current.pushVelocity.Zero(); +} + +/* +================ +idPhysics_Player::GetWaterLevel +================ +*/ +waterLevel_t idPhysics_Player::GetWaterLevel() const { + return waterLevel; +} + +/* +================ +idPhysics_Player::GetWaterType +================ +*/ +int idPhysics_Player::GetWaterType() const { + return waterType; +} + +/* +================ +idPhysics_Player::HasJumped +================ +*/ +bool idPhysics_Player::HasJumped() const { + return ( ( current.movementFlags & PMF_JUMPED ) != 0 ); +} + +/* +================ +idPhysics_Player::HasSteppedUp +================ +*/ +bool idPhysics_Player::HasSteppedUp() const { + return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 ); +} + +/* +================ +idPhysics_Player::GetStepUp +================ +*/ +float idPhysics_Player::GetStepUp() const { + return current.stepUp; +} + +/* +================ +idPhysics_Player::IsCrouching +================ +*/ +bool idPhysics_Player::IsCrouching() const { + return ( ( current.movementFlags & PMF_DUCKED ) != 0 ); +} + +/* +================ +idPhysics_Player::OnLadder +================ +*/ +bool idPhysics_Player::OnLadder() const { + return ladder; +} + +/* +================ +idPhysics_Player::idPhysics_Player +================ +*/ +idPhysics_Player::idPhysics_Player() { + debugLevel = false; + clipModel = NULL; + clipMask = 0; + memset( ¤t, 0, sizeof( current ) ); + saved = current; + walkSpeed = 0; + crouchSpeed = 0; + maxStepHeight = 0; + maxJumpHeight = 0; + memset( &command, 0, sizeof( command ) ); + commandForward = idVec3( 1, 0, 0 ); + framemsec = 0; + frametime = 0; + playerSpeed = 0; + viewForward.Zero(); + viewRight.Zero(); + walking = false; + groundPlane = false; + memset( &groundTrace, 0, sizeof( groundTrace ) ); + groundMaterial = NULL; + ladder = false; + ladderNormal.Zero(); + waterLevel = WATERLEVEL_NONE; + waterType = 0; +} + +/* +================ +idPhysics_Player_SavePState +================ +*/ +void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) { + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + savefile->WriteFloat( state.stepUp ); + savefile->WriteInt( state.movementType ); + savefile->WriteInt( state.movementFlags ); + savefile->WriteInt( state.movementTime ); +} + +/* +================ +idPhysics_Player_RestorePState +================ +*/ +void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) { + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + savefile->ReadFloat( state.stepUp ); + savefile->ReadInt( state.movementType ); + savefile->ReadInt( state.movementFlags ); + savefile->ReadInt( state.movementTime ); +} + +/* +================ +idPhysics_Player::Save +================ +*/ +void idPhysics_Player::Save( idSaveGame *savefile ) const { + + idPhysics_Player_SavePState( savefile, current ); + idPhysics_Player_SavePState( savefile, saved ); + + savefile->WriteFloat( walkSpeed ); + savefile->WriteFloat( crouchSpeed ); + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( maxJumpHeight ); + savefile->WriteInt( debugLevel ); + + savefile->WriteUsercmd( command ); + savefile->WriteVec3( commandForward ); + + savefile->WriteInt( framemsec ); + savefile->WriteFloat( frametime ); + savefile->WriteFloat( playerSpeed ); + savefile->WriteVec3( viewForward ); + savefile->WriteVec3( viewRight ); + + savefile->WriteBool( walking ); + savefile->WriteBool( groundPlane ); + savefile->WriteTrace( groundTrace ); + savefile->WriteMaterial( groundMaterial ); + + savefile->WriteBool( ladder ); + savefile->WriteVec3( ladderNormal ); + + savefile->WriteInt( (int)waterLevel ); + savefile->WriteInt( waterType ); +} + +/* +================ +idPhysics_Player::Restore +================ +*/ +void idPhysics_Player::Restore( idRestoreGame *savefile ) { + + idPhysics_Player_RestorePState( savefile, current ); + idPhysics_Player_RestorePState( savefile, saved ); + + savefile->ReadFloat( walkSpeed ); + savefile->ReadFloat( crouchSpeed ); + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( maxJumpHeight ); + savefile->ReadInt( debugLevel ); + + savefile->ReadUsercmd( command ); + savefile->ReadVec3( commandForward ); + + savefile->ReadInt( framemsec ); + savefile->ReadFloat( frametime ); + savefile->ReadFloat( playerSpeed ); + savefile->ReadVec3( viewForward ); + savefile->ReadVec3( viewRight ); + + savefile->ReadBool( walking ); + savefile->ReadBool( groundPlane ); + savefile->ReadTrace( groundTrace ); + savefile->ReadMaterial( groundMaterial ); + + savefile->ReadBool( ladder ); + savefile->ReadVec3( ladderNormal ); + + savefile->ReadInt( (int &)waterLevel ); + savefile->ReadInt( waterType ); +} + +/* +================ +idPhysics_Player::SetPlayerInput +================ +*/ +void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idVec3 &forwardVector ) { + command = cmd; + commandForward = forwardVector; // can't use cmd.angles cause of the delta_angles +} + +/* +================ +idPhysics_Player::SetSpeed +================ +*/ +void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) { + walkSpeed = newWalkSpeed; + crouchSpeed = newCrouchSpeed; +} + +/* +================ +idPhysics_Player::SetMaxStepHeight +================ +*/ +void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Player::GetMaxStepHeight +================ +*/ +float idPhysics_Player::GetMaxStepHeight() const { + return maxStepHeight; +} + +/* +================ +idPhysics_Player::SetMaxJumpHeight +================ +*/ +void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) { + maxJumpHeight = newMaxJumpHeight; +} + +/* +================ +idPhysics_Player::SetMovementType +================ +*/ +void idPhysics_Player::SetMovementType( const pmtype_t type ) { + current.movementType = type; +} + +/* +================ +idPhysics_Player::SetKnockBack +================ +*/ +void idPhysics_Player::SetKnockBack( const int knockBackTime ) { + if ( current.movementTime ) { + return; + } + current.movementFlags |= PMF_TIME_KNOCKBACK; + current.movementTime = knockBackTime; +} + +/* +================ +idPhysics_Player::SetDebugLevel +================ +*/ +void idPhysics_Player::SetDebugLevel( bool set ) { + debugLevel = set; +} + +/* +================ +idPhysics_Player::Evaluate +================ +*/ +bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + + waterLevel = WATERLEVEL_NONE; + waterType = 0; + oldOrigin = current.origin; + + clipModel->Unlink(); + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f ); + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + ActivateContactEntities(); + + idPhysics_Player::MovePlayer( timeStepMSec ); + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + } + + return true; //( current.origin != oldOrigin ); +} + +/* +================ +idPhysics_Player::Interpolate +================ +*/ +bool idPhysics_Player::Interpolate( const float fraction ) { + + /* + // Client is on a pusher... ignore him so he doesn't lag behind + bool becameUnlocked = false; + if ( ClientPusherLocked( becameUnlocked ) ) { + return true; + } + */ + + // Test to see how far we are interolating to, if it's a large jump + // in positions, then dont interpolate just do a straight set. + + idVec3 deltaVec = previous.origin - next.origin; + float deltaLengthSq = idMath::Fabs( deltaVec.LengthSqr() ); + + if( deltaLengthSq > pm_clientInterpolation_Divergence.GetFloat() ) { + idLib::Printf( "Client Interpolation Divergence exceeded, snapping client to next position\n" ); + current.origin = next.origin; + previous.origin = next.origin; + } else { + current.origin = Lerp( previous.origin, next.origin, fraction ); + } + + //current.localOrigin = Lerp( previous.localOrigin, next.localOrigin, fraction ); + if ( self != NULL && ( self->entityNumber != gameLocal.GetLocalClientNum() ) ) { + current.velocity = Lerp( previous.velocity, next.velocity, fraction ); + } + //current.pushVelocity = Lerp( previous.pushVelocity, next.pushVelocity, fraction ); + + //current.movementTime = Lerp( previous.movementTime, next.movementTime, fraction ); + //current.stepUp = Lerp( previous.stepUp, next.stepUp, fraction ); + + // Since we can't lerp between flag-type variables, use the previous flags if + // fraction is < 0.5 and the next flags if fraction is > 0.5. + //const playerPState_t & flagStateToUse = ( fraction < 0.5f ) ? previous : next; + + //current.movementFlags = flagStateToUse.movementFlags; + //current.movementType = flagStateToUse.movementType; + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, next.origin, clipModel->GetAxis() ); + } + + return true; +} + +/* +================ +idPhysics_Player::UpdateTime +================ +*/ +void idPhysics_Player::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Player::GetTime +================ +*/ +int idPhysics_Player::GetTime() const { + return gameLocal.time; +} + +/* +================ +idPhysics_Player::GetImpactInfo +================ +*/ +void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Player::ApplyImpulse +================ +*/ +void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( current.movementType != PM_NOCLIP ) { + current.velocity += impulse * invMass; + } +} + +/* +================ +idPhysics_Player::IsAtRest +================ +*/ +bool idPhysics_Player::IsAtRest() const { + return false; +} + +/* +================ +idPhysics_Player::GetRestStartTime +================ +*/ +int idPhysics_Player::GetRestStartTime() const { + return -1; +} + +/* +================ +idPhysics_Player::SaveState +================ +*/ +void idPhysics_Player::SaveState() { + saved = current; +} + +/* +================ +idPhysics_Player::RestoreState +================ +*/ +void idPhysics_Player::RestoreState() { + current = saved; + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } + + clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() ); + + previous = next = current; +} + +/* +================ +idPhysics_Player::GetOrigin +================ +*/ +const idVec3 & idPhysics_Player::PlayerGetOrigin() const { + return current.origin; +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) { + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis ); + + previous = next = current; +} + +/* +================ +idPhysics_Player::Translate +================ +*/ +void idPhysics_Player::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + previous = next = current; +} + +/* +================ +idPhysics_Player::Rotate +================ +*/ +void idPhysics_Player::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +} + +/* +================ +idPhysics_Player::SetLinearVelocity +================ +*/ +void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; +} + +/* +================ +idPhysics_Player::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Player::SetPushed +================ +*/ +void idPhysics_Player::SetPushed( int deltaTime ) { + idVec3 velocity; + float d; + + // Dont push non Local clients on clients. + if( self->entityNumber != gameLocal.GetLocalClientNum() && common->IsClient() ) { return; } + + // velocity with which the player is pushed + velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); + + // remove any downward push velocity + d = velocity * gravityNormal; + if ( d > 0.0f ) { + velocity -= d * gravityNormal; + } + + current.pushVelocity += velocity; +} + +/* +================ +idPhysics_Player::SetPushedWithAbnormalVelocityHack + +NOTE: Aside from the velocity hack, this MUST be identical to idPhysics_Player::SetPushed +================ +*/ +void idPhysics_Player::SetPushedWithAbnormalVelocityHack( int deltaTime ) { + idVec3 velocity; + float d; + + // Dont push non Local clients on clients. + if( self->entityNumber != gameLocal.GetLocalClientNum() && common->IsClient() ) { return; } + + // velocity with which the player is pushed + velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); + + // START ABNORMAL VELOCITY HACK + // There is a bug where on the first 1 to 2 frames after a load, the player on the boat + // in le_hell_post will be pushed an abnormal amount by the boat mover, causing them to + // be thrown off of the boat. + // + // We're resolving this by just watching for the abnormal velocities and ignoring the push + // in those cases. Since it is literally only 1 or 2 frames, the remaining updates should + // continue to push the player by sane values. + // + const float ABNORMAL_VELOCITY = 600.0f; // anything with a magnitude of this or higher will be ignored + const float len = velocity.LengthSqr(); + if ( len >= Square( ABNORMAL_VELOCITY ) ) { + velocity.Zero(); // just ignore the large velocity change completely + } + // END ABNORMAL VELOCITY HACK + + // remove any downward push velocity + d = velocity * gravityNormal; + if ( d > 0.0f ) { + velocity -= d * gravityNormal; + } + + current.pushVelocity += velocity; +} + +/* +================ +idPhysics_Player::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity; +} + +/* +================ +idPhysics_Player::ClearPushedVelocity +================ +*/ +void idPhysics_Player::ClearPushedVelocity() { + current.pushVelocity.Zero(); +} + + +/* +======================== +idPhysics_Player::ClientPusherLocked +======================== +*/ +bool idPhysics_Player::ClientPusherLocked( bool & justBecameUnlocked ) { + + bool hasPhysicsContact = false; + bool hasGroundContact = false; + for ( int i = 0; i < contacts.Num(); i++ ) { + + idEntity * ent = gameLocal.entities[ contacts[i].entityNum ]; + if( ent ) { + idPhysics * p = ent->GetPhysics(); + if ( p != NULL ) { + // Testing IsAtRest seems cleaner but there are edge cases of clients jumping right before a mover starts to move + if ( p->IsType( idPhysics_Static::Type ) == false && p->IsType( idPhysics_StaticMulti::Type ) == false ) { + hasPhysicsContact = true; + + clientPusherLocked = true; // locked until you have a ground contact that isn't a non static phys obj + + // HACK - Tomiko Reactor rotating disks screw up if server locks the pushed clients, but elevators need clients to be locked ( otherwise clients will clip through elevators ) + if( strcmp( ent->GetName(), "cylinder_disk1" ) == 0 || strcmp( ent->GetName(), "cylinder_disk2" ) == 0 || strcmp( ent->GetName(), "cylinder_disk3" ) == 0 ) { + clientPusherLocked = false; + } + } + } + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + hasGroundContact = true; + } + } + } + + justBecameUnlocked = false; + if ( hasGroundContact && !hasPhysicsContact ) { + if ( clientPusherLocked ) { + justBecameUnlocked = true; + } + clientPusherLocked = false; + } + + return clientPusherLocked; +} + +/* +================ +idPhysics_Player::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + } + } +} + +const float PLAYER_VELOCITY_MAX = 4000; +const int PLAYER_VELOCITY_TOTAL_BITS = 16; +const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1; +const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS; +const int PLAYER_MOVEMENT_TYPE_BITS = 3; +const int PLAYER_MOVEMENT_FLAGS_BITS = 8; + +/* +================ +idPhysics_Player::WriteToSnapshot +================ +*/ +void idPhysics_Player::WriteToSnapshot( idBitMsg &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.velocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + //idLib::Printf("Writing Velocity: x %2f, y %2f, z %2f \n", current.velocity[0], current.velocity[1], current.velocity[2] ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); +} + +/* +================ +idPhysics_Player::ReadFromSnapshot +================ +*/ +void idPhysics_Player::ReadFromSnapshot( const idBitMsg &msg ) { + + previous = next; + + next.origin = ReadFloatArray< idVec3 >( msg ); + next.velocity[0] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + next.velocity[1] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + next.velocity[2] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + //idLib::Printf("Reading Velocity: x %2f, y %2f, z %2f \n", next.velocity[0], next.velocity[1], next.velocity[2] ); + next.localOrigin = ReadDeltaFloatArray( msg, next.origin ); + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, next.origin, clipModel->GetAxis() ); + } + +} + diff --git a/neo/d3xp/physics/Physics_Player.h b/neo/d3xp/physics/Physics_Player.h new file mode 100644 index 00000000..a7eaebce --- /dev/null +++ b/neo/d3xp/physics/Physics_Player.h @@ -0,0 +1,217 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_PLAYER_H__ +#define __PHYSICS_PLAYER_H__ + +/* +=================================================================================== + + Player physics + + Simulates the motion of a player through the environment. Input from the + player is used to allow a certain degree of control over the motion. + +=================================================================================== +*/ + +// movementType +typedef enum { + PM_NORMAL, // normal physics + PM_DEAD, // no acceleration or turning, but free falling + PM_SPECTATOR, // flying without gravity but with collision detection + PM_FREEZE, // stuck in place without control + PM_NOCLIP // flying without collision detection nor gravity +} pmtype_t; + +typedef enum { + WATERLEVEL_NONE, + WATERLEVEL_FEET, + WATERLEVEL_WAIST, + WATERLEVEL_HEAD +} waterLevel_t; + +#define MAXTOUCH 32 + +typedef struct playerPState_s { + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; + float stepUp; + int movementType; + int movementFlags; + int movementTime; + + playerPState_s() : + origin( vec3_zero ), + velocity( vec3_zero ), + localOrigin( vec3_zero ), + pushVelocity( vec3_zero ), + stepUp( 0.0f ), + movementType( 0 ), + movementFlags( 0 ), + movementTime( 0 ) { + } +} playerPState_t; + +class idPhysics_Player : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Player ); + + idPhysics_Player(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ); + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight() const; + void SetMaxJumpHeight( const float newMaxJumpHeight ); + void SetMovementType( const pmtype_t type ); + void SetPlayerInput( const usercmd_t &cmd, const idVec3 &forwardVector ); + void SetKnockBack( const int knockBackTime ); + void SetDebugLevel( bool set ); + // feed back from last physics frame + waterLevel_t GetWaterLevel() const; + int GetWaterType() const; + bool HasJumped() const; + bool HasSteppedUp() const; + float GetStepUp() const; + bool IsCrouching() const; + bool OnLadder() const; + const idVec3 & PlayerGetOrigin() const; // != GetOrigin + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool Interpolate( const float fraction ); + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + bool IsAtRest() const; + int GetRestStartTime() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + bool ClientPusherLocked( bool & justBecameUnlocked ); + void SetPushed( int deltaTime ); + void SetPushedWithAbnormalVelocityHack( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + void ClearPushedVelocity(); + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + + void SnapToNextState() { current = next; previous = current; } + +private: + // player physics state + playerPState_t current; + playerPState_t saved; + + // physics state for client interpolation + playerPState_t previous; + playerPState_t next; + + // properties + float walkSpeed; + float crouchSpeed; + float maxStepHeight; + float maxJumpHeight; + int debugLevel; // if set, diagnostic output will be printed + + // player input + usercmd_t command; + idVec3 commandForward; // can't use cmd.angles cause of the delta_angles and head tracking + + // run-time variables + int framemsec; + float frametime; + float playerSpeed; + idVec3 viewForward; + idVec3 viewRight; + + // walk movement + bool walking; + bool groundPlane; + trace_t groundTrace; + const idMaterial * groundMaterial; + + // ladder movement + bool ladder; + idVec3 ladderNormal; + + // results of last evaluate + waterLevel_t waterLevel; + int waterType; + + bool clientPusherLocked; + +private: + float CmdScale( const usercmd_t &cmd ) const; + void Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ); + bool SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ); + void Friction(); + void WaterJumpMove(); + void WaterMove(); + void FlyMove(); + void AirMove(); + void WalkMove(); + void DeadMove(); + void NoclipMove(); + void SpectatorMove(); + void LadderMove(); + void CorrectAllSolid( trace_t &trace, int contents ); + void CheckGround(); + void CheckDuck(); + void CheckLadder(); + bool CheckJump(); + bool CheckWaterJump(); + void SetWaterLevel(); + void DropTimers(); + void MovePlayer( int msec ); +}; + +#endif /* !__PHYSICS_PLAYER_H__ */ diff --git a/neo/d3xp/physics/Physics_RigidBody.cpp b/neo/d3xp/physics/Physics_RigidBody.cpp new file mode 100644 index 00000000..41e19109 --- /dev/null +++ b/neo/d3xp/physics/Physics_RigidBody.cpp @@ -0,0 +1,1539 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_RigidBody ) +END_CLASS + +const float STOP_SPEED = 10.0f; + + +#undef RB_TIMINGS + +#ifdef RB_TIMINGS +static int lastTimerReset = 0; +static int numRigidBodies = 0; +static idTimer timer_total, timer_collision; +#endif + + +/* +================ +RigidBodyDerivatives +================ +*/ +void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const idPhysics_RigidBody *p = (idPhysics_RigidBody *) clientData; + rigidBodyIState_t *s = (rigidBodyIState_t *) state; + // NOTE: this struct should be build conform rigidBodyIState_t + struct rigidBodyDerivatives_s { + idVec3 linearVelocity; + idMat3 angularMatrix; + idVec3 force; + idVec3 torque; + } *d = (struct rigidBodyDerivatives_s *) derivatives; + idVec3 angularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = s->orientation * p->inverseInertiaTensor * s->orientation.Transpose(); + angularVelocity = inverseWorldInertiaTensor * s->angularMomentum; + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix = SkewSymmetric( angularVelocity ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque = - p->angularFriction * s->angularMomentum + p->current.externalTorque; +} + +/* +================ +idPhysics_RigidBody::Integrate + + Calculate next state from the current state using an integrator. +================ +*/ +void idPhysics_RigidBody::Integrate( float deltaTime, rigidBodyPState_t &next_ ) { + idVec3 position; + + position = current.i.position; + current.i.position += centerOfMass * current.i.orientation; + + current.i.orientation.TransposeSelf(); + + integrator->Evaluate( (float *) ¤t.i, (float *) &next_.i, 0, deltaTime ); + next_.i.orientation.OrthoNormalizeSelf(); + + // apply gravity + next_.i.linearMomentum += deltaTime * gravityVector * mass; + + current.i.orientation.TransposeSelf(); + next_.i.orientation.TransposeSelf(); + + current.i.position = position; + next_.i.position -= centerOfMass * next_.i.orientation; + + next_.atRest = current.atRest; +} + +/* +================ +idPhysics_RigidBody::CollisionImpulse + + Calculates the collision impulse using the velocity relative to the collision object. + The current state should be set to the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CollisionImpulse( const trace_t &collision, idVec3 &impulse ) { + idVec3 r, linearVelocity, angularVelocity, velocity; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator, vel; + impactInfo_t info; + idEntity *ent; + + // get info from other entity involved + ent = gameLocal.entities[collision.c.entityNum]; + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + + // collision point relative to the body center of mass + r = collision.c.point - ( current.i.position + centerOfMass * current.i.orientation ); + // the velocity at the collision point + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + + // velocity in normal direction + vel = velocity * collision.c.normal; + + if ( vel > -STOP_SPEED ) { + impulseNumerator = STOP_SPEED; + } + else { + impulseNumerator = -( 1.0f + bouncyness ) * vel; + } + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // update linear and angular momentum with impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if no movement at all don't blow up + if ( collision.fraction < 0.0001f ) { + current.i.linearMomentum *= 0.5f; + current.i.angularMomentum *= 0.5f; + } + + // callback to self to let the entity know about the collision + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_RigidBody::CheckForCollisions + + Check for collisions between the current and next state. + If there is a collision the next state is set to the state at the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next_, trace_t &collision ) { +//#define TEST_COLLISION_DETECTION + idMat3 axis; + idRotation rotation; + bool collided = false; + +#ifdef TEST_COLLISION_DETECTION + bool startsolid; + if ( gameLocal.clip.Contents( current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { + startsolid = true; + } +#endif + + TransposeMultiply( current.i.orientation, next_.i.orientation, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( current.i.position ); + + // if there was a collision + if ( gameLocal.clip.Motion( collision, current.i.position, next_.i.position, rotation, clipModel, current.i.orientation, clipMask, self ) ) { + // set the next state to the state at the moment of impact + next_.i.position = collision.endpos; + next_.i.orientation = collision.endAxis; + next_.i.linearMomentum = current.i.linearMomentum; + next_.i.angularMomentum = current.i.angularMomentum; + collided = true; + } + +#ifdef TEST_COLLISION_DETECTION + if ( gameLocal.clip.Contents( next.i.position, clipModel, next_.i.orientation, clipMask, self ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return collided; +} + +/* +================ +idPhysics_RigidBody::ContactFriction + + Does not solve friction for multiple simultaneous contacts but applies contact friction in isolation. + Uses absolute velocity at the contact points instead of the velocity relative to the contact object. +================ +*/ +void idPhysics_RigidBody::ContactFriction( float deltaTime ) { + int i; + float magnitude, impulseNumerator, impulseDenominator; + idMat3 inverseWorldInertiaTensor; + idVec3 linearVelocity, angularVelocity; + idVec3 massCenter, r, velocity, normal, impulse, normalVelocity; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + + massCenter = current.i.position + centerOfMass * current.i.orientation; + + for ( i = 0; i < contacts.Num(); i++ ) { + + r = contacts[i].point - massCenter; + + // calculate velocity at contact point + linearVelocity = inverseMass * current.i.linearMomentum; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + + // velocity along normal vector + normalVelocity = ( velocity * contacts[i].normal ) * contacts[i].normal; + + // calculate friction impulse + normal = -( velocity - normalVelocity ); + magnitude = normal.Normalize(); + impulseNumerator = contactFriction * magnitude; + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply friction impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if moving towards the surface at the contact point + if ( normalVelocity * contacts[i].normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross( impulse ); + } + } +} + +/* +================ +idPhysics_RigidBody::TestIfAtRest + + Returns true if the body is considered at rest. + Does not catch all cases where the body is at rest but is generally good enough. +================ +*/ +bool idPhysics_RigidBody::TestIfAtRest() const { + int i; + float gv; + idVec3 v, av, normal, point; + idMat3 inverseWorldInertiaTensor; + idFixedWinding contactWinding; + + if ( current.atRest >= 0 ) { + return true; + } + + // need at least 3 contact points to come to rest + if ( contacts.Num() < 3 ) { + return false; + } + + // get average contact plane normal + normal.Zero(); + for ( i = 0; i < contacts.Num(); i++ ) { + normal += contacts[i].normal; + } + normal /= (float) contacts.Num(); + normal.Normalize(); + + // if on a too steep surface + if ( (normal * gravityNormal) > -0.7f ) { + return false; + } + + // create bounds for contact points + contactWinding.Clear(); + for ( i = 0; i < contacts.Num(); i++ ) { + // project point onto plane through origin orthogonal to the gravity + point = contacts[i].point - (contacts[i].point * gravityNormal) * gravityNormal; + contactWinding.AddToConvexHull( point, gravityNormal ); + } + + // need at least 3 contact points to come to rest + if ( contactWinding.GetNumPoints() < 3 ) { + return false; + } + + // center of mass in world space + point = current.i.position + centerOfMass * current.i.orientation; + point -= (point * gravityNormal) * gravityNormal; + + // if the point is not inside the winding + if ( !contactWinding.PointInside( gravityNormal, point, 0 ) ) { + return false; + } + + // linear velocity of body + v = inverseMass * current.i.linearMomentum; + // linear velocity in gravity direction + gv = v * gravityNormal; + // linear velocity orthogonal to gravity direction + v -= gv * gravityNormal; + + // if too much velocity orthogonal to gravity direction + if ( v.Length() > STOP_SPEED ) { + return false; + } + // if too much velocity in gravity direction + if ( gv > 2.0f * STOP_SPEED || gv < -2.0f * STOP_SPEED ) { + return false; + } + + // calculate rotational velocity + inverseWorldInertiaTensor = current.i.orientation * inverseInertiaTensor * current.i.orientation.Transpose(); + av = inverseWorldInertiaTensor * current.i.angularMomentum; + + // if too much rotational velocity + if ( av.LengthSqr() > STOP_SPEED ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_RigidBody::DropToFloorAndRest + + Drops the object straight down to the floor and verifies if the object is at rest on the floor. +================ +*/ +void idPhysics_RigidBody::DropToFloorAndRest() { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + + testSolid = false; + + if ( gameLocal.clip.Contents( current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { + gameLocal.DWarning( "rigid body in solid for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.i.position + gravityNormal * 128.0f; + gameLocal.clip.Translation( tr, current.i.position, down, clipModel, current.i.orientation, clipMask, self ); + current.i.position = tr.endpos; + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), tr.endpos, current.i.orientation ); + + // if on the floor already + if ( tr.fraction == 0.0f ) { + // test if we are really at rest + EvaluateContacts(); + if ( !TestIfAtRest() ) { + gameLocal.DWarning( "rigid body not at rest for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + } + Rest(); + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + } +} + +/* +================ +idPhysics_RigidBody::DebugDraw +================ +*/ +void idPhysics_RigidBody::DebugDraw() { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->Handle(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, 0.0f ); + } + + if ( rb_showMass.GetBool() ) { + gameRenderWorld->DrawText( va( "\n%1.2f", mass ), current.i.position, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + + if ( rb_showInertia.GetBool() ) { + idMat3 &I = inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + current.i.position, 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + + if ( rb_showVelocity.GetBool() ) { + DrawVelocity( clipModel->GetId(), 0.1f, 4.0f ); + } +} + +/* +================ +idPhysics_RigidBody::idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::idPhysics_RigidBody() { + + // set default rigid body properties + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f ); + SetFriction( 0.6f, 0.6f, 0.0f ); + clipModel = NULL; + + current.atRest = -1; + current.lastTimeStep = 0.0f; + + current.i.position.Zero(); + current.i.orientation.Identity(); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + + saved = current; + + mass = 1.0f; + inverseMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + inverseInertiaTensor.Identity(); + + // use the least expensive euler integrator + integrator = new (TAG_PHYSICS) idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), RigidBodyDerivatives, this ); + + dropToFloor = false; + noImpact = false; + noContact = false; + + hasMaster = false; + isOrientated = false; + +#ifdef RB_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_RigidBody::~idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::~idPhysics_RigidBody() { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } + delete integrator; +} + +/* +================ +idPhysics_RigidBody_SavePState +================ +*/ +void idPhysics_RigidBody_SavePState( idSaveGame *savefile, const rigidBodyPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteFloat( state.lastTimeStep ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteMat3( state.localAxis ); + savefile->WriteVec6( state.pushVelocity ); + savefile->WriteVec3( state.externalForce ); + savefile->WriteVec3( state.externalTorque ); + + savefile->WriteVec3( state.i.position ); + savefile->WriteMat3( state.i.orientation ); + savefile->WriteVec3( state.i.linearMomentum ); + savefile->WriteVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody_RestorePState +================ +*/ +void idPhysics_RigidBody_RestorePState( idRestoreGame *savefile, rigidBodyPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadFloat( state.lastTimeStep ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadMat3( state.localAxis ); + savefile->ReadVec6( state.pushVelocity ); + savefile->ReadVec3( state.externalForce ); + savefile->ReadVec3( state.externalTorque ); + + savefile->ReadVec3( state.i.position ); + savefile->ReadMat3( state.i.orientation ); + savefile->ReadVec3( state.i.linearMomentum ); + savefile->ReadVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody::Save +================ +*/ +void idPhysics_RigidBody::Save( idSaveGame *savefile ) const { + + idPhysics_RigidBody_SavePState( savefile, current ); + idPhysics_RigidBody_SavePState( savefile, saved ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( inverseMass ); + savefile->WriteVec3( centerOfMass ); + savefile->WriteMat3( inertiaTensor ); + savefile->WriteMat3( inverseInertiaTensor ); + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + savefile->WriteBool( noImpact ); + savefile->WriteBool( noContact ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::Restore +================ +*/ +void idPhysics_RigidBody::Restore( idRestoreGame *savefile ) { + + idPhysics_RigidBody_RestorePState( savefile, current ); + idPhysics_RigidBody_RestorePState( savefile, saved ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( inverseMass ); + savefile->ReadVec3( centerOfMass ); + savefile->ReadMat3( inertiaTensor ); + savefile->ReadMat3( inverseInertiaTensor ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + savefile->ReadBool( noImpact ); + savefile->ReadBool( noContact ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::SetClipModel +================ +*/ +#define MAX_INERTIA_SCALE 10.0f + +void idPhysics_RigidBody::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + int minIndex; + idMat3 inertiaScale; + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + clipModel->Link( gameLocal.clip, self, 0, current.i.position, current.i.orientation ); + + // get mass properties from the trace model + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // check whether or not the clip model has valid mass properties + if ( mass <= 0.0f || IEEE_FLT_IS_NAN( mass ) ) { + gameLocal.Warning( "idPhysics_RigidBody::SetClipModel: invalid mass for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // check whether or not the inertia tensor is balanced + minIndex = Min3Index( inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] ); + inertiaScale.Identity(); + inertiaScale[0][0] = inertiaTensor[0][0] / inertiaTensor[minIndex][minIndex]; + inertiaScale[1][1] = inertiaTensor[1][1] / inertiaTensor[minIndex][minIndex]; + inertiaScale[2][2] = inertiaTensor[2][2] / inertiaTensor[minIndex][minIndex]; + + if ( inertiaScale[0][0] > MAX_INERTIA_SCALE || inertiaScale[1][1] > MAX_INERTIA_SCALE || inertiaScale[2][2] > MAX_INERTIA_SCALE ) { + gameLocal.DWarning( "idPhysics_RigidBody::SetClipModel: unbalanced inertia tensor for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + float min = inertiaTensor[minIndex][minIndex] * MAX_INERTIA_SCALE; + inertiaScale[(minIndex+1)%3][(minIndex+1)%3] = min / inertiaTensor[(minIndex+1)%3][(minIndex+1)%3]; + inertiaScale[(minIndex+2)%3][(minIndex+2)%3] = min / inertiaTensor[(minIndex+2)%3][(minIndex+2)%3]; + inertiaTensor *= inertiaScale; + } + + inverseMass = 1.0f / mass; + inverseInertiaTensor = inertiaTensor.Inverse() * ( 1.0f / 6.0f ); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); +} + +/* +================ +idPhysics_RigidBody::GetClipModel +================ +*/ +idClipModel *idPhysics_RigidBody::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_RigidBody::GetNumClipModels +================ +*/ +int idPhysics_RigidBody::GetNumClipModels() const { + return 1; +} + +/* +================ +idPhysics_RigidBody::SetMass +================ +*/ +void idPhysics_RigidBody::SetMass( float mass, int id ) { + assert( mass > 0.0f ); + inertiaTensor *= mass / this->mass; + inverseInertiaTensor = inertiaTensor.Inverse() * (1.0f / 6.0f); + this->mass = mass; + inverseMass = 1.0f / mass; +} + +/* +================ +idPhysics_RigidBody::GetMass +================ +*/ +float idPhysics_RigidBody::GetMass( int id ) const { + return mass; +} + +/* +================ +idPhysics_RigidBody::SetFriction +================ +*/ +void idPhysics_RigidBody::SetFriction( const float linear, const float angular, const float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_RigidBody::SetBouncyness +================ +*/ +void idPhysics_RigidBody::SetBouncyness( const float b ) { + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +idPhysics_RigidBody::Rest +================ +*/ +void idPhysics_RigidBody::Rest() { + current.atRest = gameLocal.time; + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::DropToFloor +================ +*/ +void idPhysics_RigidBody::DropToFloor() { + dropToFloor = true; + testSolid = true; +} + +/* +================ +idPhysics_RigidBody::NoContact +================ +*/ +void idPhysics_RigidBody::NoContact() { + noContact = true; +} + +/* +================ +idPhysics_RigidBody::Activate +================ +*/ +void idPhysics_RigidBody::Activate() { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_RigidBody::PutToRest() { + Rest(); +} + +/* +================ +idPhysics_RigidBody::EnableImpact +================ +*/ +void idPhysics_RigidBody::EnableImpact() { + noImpact = false; +} + +/* +================ +idPhysics_RigidBody::DisableImpact +================ +*/ +void idPhysics_RigidBody::DisableImpact() { + noImpact = true; +} + +/* +================ +idPhysics_RigidBody::SetContents +================ +*/ +void idPhysics_RigidBody::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_RigidBody::GetContents +================ +*/ +int idPhysics_RigidBody::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_RigidBody::GetBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_RigidBody::GetAbsBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_RigidBody::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool idPhysics_RigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) { + rigidBodyPState_t next_step; + idAngles angles; + trace_t collision; + idVec3 impulse; + idEntity *ent; + idVec3 oldOrigin, masterOrigin; + idMat3 oldAxis, masterAxis; + float timeStep; + bool collided, cameToRest = false; + + timeStep = MS2SEC( timeStepMSec ); + current.lastTimeStep = timeStep; + + if ( hasMaster ) { + oldOrigin = current.i.position; + oldAxis = current.i.orientation; + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.i.orientation = current.localAxis * masterAxis; + } + else { + current.i.orientation = current.localAxis; + } + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); + current.i.linearMomentum = mass * ( ( current.i.position - oldOrigin ) / timeStep ); + current.i.angularMomentum = inertiaTensor * ( ( current.i.orientation * oldAxis.Transpose() ).ToAngularVelocity() / timeStep ); + current.externalForce.Zero(); + current.externalTorque.Zero(); + + return ( current.i.position != oldOrigin || current.i.orientation != oldAxis ); + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + current.externalForce.Zero(); + current.externalTorque.Zero(); + return true; + } + +#ifdef RB_TIMINGS + timer_total.Start(); +#endif + + // move the rigid body velocity into the frame of a pusher +// current.i.linearMomentum -= current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum -= current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + + clipModel->Unlink(); + + next_step = current; + + // calculate next position and orientation + Integrate( timeStep, next_step ); + +#ifdef RB_TIMINGS + timer_collision.Start(); +#endif + + // check for collisions from the current to the next state + collided = CheckForCollisions( timeStep, next_step, collision ); + +#ifdef RB_TIMINGS + timer_collision.Stop(); +#endif + + // set the new state + current = next_step; + + if ( collided ) { + // apply collision impulse + if ( CollisionImpulse( collision, impulse ) ) { + current.atRest = gameLocal.time; + } + } + + // update the position of the clip model + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); + + DebugDraw(); + + if ( !noContact ) { + +#ifdef RB_TIMINGS + timer_collision.Start(); +#endif + // get contacts + EvaluateContacts(); + +#ifdef RB_TIMINGS + timer_collision.Stop(); +#endif + + // check if the body has come to rest + if ( TestIfAtRest() ) { + // put to rest + Rest(); + cameToRest = true; + } else { + // apply contact friction + ContactFriction( timeStep ); + } + } + + if ( current.atRest < 0 ) { + ActivateContactEntities(); + } + + if ( collided ) { + // if the rigid body didn't come to rest or the other entity is not at rest + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ( !cameToRest || !ent->IsAtRest() ) ) { + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + } + } + + // move the rigid body velocity back into the world frame +// current.i.linearMomentum += current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum += current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + current.pushVelocity.Zero(); + + current.lastTimeStep = timeStep; + current.externalForce.Zero(); + current.externalTorque.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + } + +#ifdef RB_TIMINGS + timer_total.Stop(); + + if ( rb_showTimings->integer == 1 ) { + gameLocal.Printf( "%12s: t %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + lastTimerReset = 0; + } + else if ( rb_showTimings->integer == 2 ) { + numRigidBodies++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "rb %d: t %1.4f cd %1.4f\n", + numRigidBodies, + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + } + } + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numRigidBodies = 0; + timer_total.Clear(); + timer_collision.Clear(); + } +#endif + + return true; +} + +/* +================ +idPhysics_RigidBody::Interpolate + + Simply interpolate between snapshots of the state of the rigid body + for MP clients. +================ +*/ +bool idPhysics_RigidBody::Interpolate( const float fraction ) { + if ( !self ) { + return false; + } + + if ( self->GetInterpolationBehavior() == idEntity::USE_LATEST_SNAP_ONLY ) { + current = next; + return true; + } else if ( self->GetInterpolationBehavior() == idEntity::USE_INTERPOLATION ) { + current.i.position = Lerp( previous.i.position, next.i.position, fraction ); + current.i.orientation = idQuat().Slerp( previous.i.orientation.ToQuat(), next.i.orientation.ToQuat(), fraction ).ToMat3(); + current.i.linearMomentum = Lerp( previous.i.linearMomentum, next.i.linearMomentum, fraction ); + return true; + } + + return false; +} + +/* +================ +idPhysics_RigidBody::ResetInterpolationState +================ +*/ +void idPhysics_RigidBody::ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ) { + previous = current; + next = current; +} + +/* +================ +idPhysics_RigidBody::UpdateTime +================ +*/ +void idPhysics_RigidBody::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_RigidBody::GetTime +================ +*/ +int idPhysics_RigidBody::GetTime() const { + return gameLocal.time; +} + +/* +================ +idPhysics_RigidBody::GetImpactInfo +================ +*/ +void idPhysics_RigidBody::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + idVec3 linearVelocity, angularVelocity; + idMat3 inverseWorldInertiaTensor; + + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + + info->invMass = inverseMass; + info->invInertiaTensor = inverseWorldInertiaTensor; + info->position = point - ( current.i.position + centerOfMass * current.i.orientation ); + info->velocity = linearVelocity + angularVelocity.Cross( info->position ); +} + +/* +================ +idPhysics_RigidBody::ApplyImpulse +================ +*/ +void idPhysics_RigidBody::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.i.linearMomentum += impulse; + current.i.angularMomentum += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::AddForce +================ +*/ +void idPhysics_RigidBody::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + current.externalForce += force; + current.externalTorque += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::IsAtRest +================ +*/ +bool idPhysics_RigidBody::IsAtRest() const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_RigidBody::GetRestStartTime +================ +*/ +int idPhysics_RigidBody::GetRestStartTime() const { + return current.atRest; +} + +/* +================ +idPhysics_RigidBody::IsPushable +================ +*/ +bool idPhysics_RigidBody::IsPushable() const { + return ( !noImpact && !hasMaster ); +} + +/* +================ +idPhysics_RigidBody::SaveState +================ +*/ +void idPhysics_RigidBody::SaveState() { + saved = current; +} + +/* +================ +idPhysics_RigidBody::RestoreState +================ +*/ +void idPhysics_RigidBody::RestoreState() { + current = saved; + + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); + + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void idPhysics_RigidBody::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + newOrigin * masterAxis; + } + else { + current.i.position = newOrigin; + } + + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void idPhysics_RigidBody::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.orientation = newAxis * masterAxis; + } + else { + current.i.orientation = newAxis; + } + + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), clipModel->GetOrigin(), current.i.orientation ); + + Activate(); +} + +/* +================ +idPhysics::Move +================ +*/ +void idPhysics_RigidBody::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.i.position += translation; + + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); + + Activate(); +} + +/* +================ +idPhysics::Rotate +================ +*/ +void idPhysics_RigidBody::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.i.orientation *= rotation.ToMat3(); + current.i.position *= rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + current.localOrigin = current.i.position; + } + + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); + + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetOrigin +================ +*/ +const idVec3 &idPhysics_RigidBody::GetOrigin( int id ) const { + return current.i.position; +} + +/* +================ +idPhysics_RigidBody::GetAxis +================ +*/ +const idMat3 &idPhysics_RigidBody::GetAxis( int id ) const { + return current.i.orientation; +} + +/* +================ +idPhysics_RigidBody::SetLinearVelocity +================ +*/ +void idPhysics_RigidBody::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.i.linearMomentum = newLinearVelocity * mass; + Activate(); +} + +/* +================ +idPhysics_RigidBody::SetAngularVelocity +================ +*/ +void idPhysics_RigidBody::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + current.i.angularMomentum = newAngularVelocity * inertiaTensor; + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + curLinearVelocity = current.i.linearMomentum * inverseMass; + return curLinearVelocity; +} + +/* +================ +idPhysics_RigidBody::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + curAngularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + return curAngularVelocity; +} + +/* +================ +idPhysics_RigidBody::ClipTranslation +================ +*/ +void idPhysics_RigidBody::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.clip.TranslationModel( results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.clip.Translation( results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_RigidBody::ClipRotation +================ +*/ +void idPhysics_RigidBody::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.clip.RotationModel( results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.clip.Rotation( results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_RigidBody::ClipContents +================ +*/ +int idPhysics_RigidBody::ClipContents( const idClipModel *model ) const { + if ( model ) { + return gameLocal.clip.ContentsModel( clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.clip.Contents( clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } +} + +/* +================ +idPhysics_RigidBody::DisableClip +================ +*/ +void idPhysics_RigidBody::DisableClip() { + clipModel->Disable(); +} + +/* +================ +idPhysics_RigidBody::EnableClip +================ +*/ +void idPhysics_RigidBody::EnableClip() { + clipModel->Enable(); +} + +/* +================ +idPhysics_RigidBody::UnlinkClip +================ +*/ +void idPhysics_RigidBody::UnlinkClip() { + clipModel->Unlink(); +} + +/* +================ +idPhysics_RigidBody::LinkClip +================ +*/ +void idPhysics_RigidBody::LinkClip() { + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); +} + +/* +================ +idPhysics_RigidBody::EvaluateContacts +================ +*/ +bool idPhysics_RigidBody::EvaluateContacts() { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10 ); + + dir.SubVec3(0) = current.i.linearMomentum + current.lastTimeStep * gravityVector * mass; + dir.SubVec3(1) = current.i.angularMomentum; + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); + num = gameLocal.clip.Contacts( &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); + contacts.SetNum( num ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_RigidBody::SetPushed +================ +*/ +void idPhysics_RigidBody::SetPushed( int deltaTime ) { + idRotation rotation; + + rotation = ( saved.i.orientation * current.i.orientation ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( current.i.position - saved.i.position ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_RigidBody::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_RigidBody::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_RigidBody::SetMaster +================ +*/ +void idPhysics_RigidBody::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.i.orientation * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + } + hasMaster = true; + isOrientated = orientated; + ClearContacts(); + } + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +const float RB_VELOCITY_MAX = 16000; +const int RB_VELOCITY_TOTAL_BITS = 16; +const int RB_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_VELOCITY_MAX ) ) + 1; +const int RB_VELOCITY_MANTISSA_BITS = RB_VELOCITY_TOTAL_BITS - 1 - RB_VELOCITY_EXPONENT_BITS; +const float RB_MOMENTUM_MAX = 1e20f; +const int RB_MOMENTUM_TOTAL_BITS = 16; +const int RB_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_MOMENTUM_MAX ) ) + 1; +const int RB_MOMENTUM_MANTISSA_BITS = RB_MOMENTUM_TOTAL_BITS - 1 - RB_MOMENTUM_EXPONENT_BITS; +const float RB_FORCE_MAX = 1e20f; +const int RB_FORCE_TOTAL_BITS = 16; +const int RB_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_FORCE_MAX ) ) + 1; +const int RB_FORCE_MANTISSA_BITS = RB_FORCE_TOTAL_BITS - 1 - RB_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_RigidBody::WriteToSnapshot +================ +*/ +void idPhysics_RigidBody::WriteToSnapshot( idBitMsg &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + + msg.WriteFloat( current.i.position[0] ); + msg.WriteFloat( current.i.position[1] ); + msg.WriteFloat( current.i.position[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( current.i.linearMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); +} + +/* +================ +idPhysics_RigidBody::ReadFromSnapshot +================ +*/ +void idPhysics_RigidBody::ReadFromSnapshot( const idBitMsg &msg ) { + idCQuat quat, localQuat; + + previous = next; + + next.i.position[0] = msg.ReadFloat(); + next.i.position[1] = msg.ReadFloat(); + next.i.position[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + next.i.linearMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + next.i.linearMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + next.i.linearMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + + next.i.orientation = quat.ToMat3(); + + // Make sure to initially set them up. Dont try to interpolate yet. + if( self->GetNumSnapshotsReceived() <= 1 ) { + current = next; + } + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), next.i.position, next.i.orientation ); + } +} diff --git a/neo/d3xp/physics/Physics_RigidBody.h b/neo/d3xp/physics/Physics_RigidBody.h new file mode 100644 index 00000000..fc37a831 --- /dev/null +++ b/neo/d3xp/physics/Physics_RigidBody.h @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_RIGIDBODY_H__ +#define __PHYSICS_RIGIDBODY_H__ + +/* +=================================================================================== + + Rigid body physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +extern const float RB_VELOCITY_MAX; +extern const int RB_VELOCITY_TOTAL_BITS; +extern const int RB_VELOCITY_EXPONENT_BITS; +extern const int RB_VELOCITY_MANTISSA_BITS; + +typedef struct rididBodyIState_s { + idVec3 position; // position of trace model + idMat3 orientation; // orientation of trace model + idVec3 linearMomentum; // translational momentum relative to center of mass + idVec3 angularMomentum; // rotational momentum relative to center of mass + + rididBodyIState_s() : + position( vec3_zero ), + orientation( mat3_identity ), + linearMomentum( vec3_zero ), + angularMomentum( vec3_zero ) { + } +} rigidBodyIState_t; + +typedef struct rigidBodyPState_s { + int atRest; // set when simulation is suspended + float lastTimeStep; // length of last time step + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec6 pushVelocity; // push velocity + idVec3 externalForce; // external force relative to center of mass + idVec3 externalTorque; // external torque relative to center of mass + rigidBodyIState_t i; // state used for integration + + rigidBodyPState_s() : + atRest( true ), + lastTimeStep( 0 ), + localOrigin( vec3_zero ), + localAxis( mat3_identity ), + pushVelocity( vec6_zero ), + externalForce( vec3_zero ), + externalTorque( vec3_zero ) { + } +} rigidBodyPState_t; + +class idPhysics_RigidBody : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( idPhysics_RigidBody ); + + idPhysics_RigidBody(); + ~idPhysics_RigidBody(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b ); + // same as above but drop to the floor first + void DropToFloor(); + // no contact determination and contact friction + void NoContact(); + // enable/disable activation by impact + void EnableImpact(); + void DisableImpact(); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool Interpolate( const float fraction ); + void ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ); + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate(); + void PutToRest(); + bool IsAtRest() const; + int GetRestStartTime() const; + bool IsPushable() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + bool EvaluateContacts(); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +private: + // state of the rigid body + rigidBodyPState_t current; + rigidBodyPState_t saved; + + // states for client interpolation + rigidBodyPState_t previous; + rigidBodyPState_t next; + + // rigid body properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + idClipModel * clipModel; // clip model used for collision detection + + // derived properties + float mass; // mass of body + float inverseMass; // 1 / mass + idVec3 centerOfMass; // center of mass of trace model + idMat3 inertiaTensor; // mass distribution + idMat3 inverseInertiaTensor; // inverse inertia tensor + + idODE * integrator; // integrator + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for solid when dropping to the floor + bool noImpact; // if true do not activate when another object collides + bool noContact; // if true do not determine contacts and no contact friction + + // master + bool hasMaster; + bool isOrientated; + +private: + friend void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); + void Integrate( const float deltaTime, rigidBodyPState_t &next ); + bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + bool CollisionImpulse( const trace_t &collision, idVec3 &impulse ); + void ContactFriction( float deltaTime ); + void DropToFloorAndRest(); + bool TestIfAtRest() const; + void Rest(); + void DebugDraw(); +}; + +#endif /* !__PHYSICS_RIGIDBODY_H__ */ diff --git a/neo/d3xp/physics/Physics_Static.cpp b/neo/d3xp/physics/Physics_Static.cpp new file mode 100644 index 00000000..26b05e83 --- /dev/null +++ b/neo/d3xp/physics/Physics_Static.cpp @@ -0,0 +1,924 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Static ) +END_CLASS + +/* +================ +idPhysics_Static::idPhysics_Static +================ +*/ +idPhysics_Static::idPhysics_Static() { + self = NULL; + clipModel = NULL; + current.origin.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAxis.Identity(); + + next = ConvertPStateToInterpolateState( current ); + previous = next; + + hasMaster = false; + isOrientated = false; +} + +/* +================ +idPhysics_Static::~idPhysics_Static +================ +*/ +idPhysics_Static::~idPhysics_Static() { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + if ( clipModel ) { + delete clipModel; + } +} + +/* +================ +idPhysics_Static::Save +================ +*/ +void idPhysics_Static::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + + savefile->WriteVec3( current.origin ); + savefile->WriteMat3( current.axis ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_Static::Restore +================ +*/ +void idPhysics_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadVec3( current.origin ); + savefile->ReadMat3( current.axis ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_Static::SetSelf +================ +*/ +void idPhysics_Static::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Static::SetClipModel +================ +*/ +void idPhysics_Static::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + assert( self ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } +} + +/* +================ +idPhysics_Static::GetClipModel +================ +*/ +idClipModel *idPhysics_Static::GetClipModel( int id ) const { + if ( clipModel ) { + return clipModel; + } + return gameLocal.clip.DefaultClipModel(); +} + +/* +================ +idPhysics_Static::GetNumClipModels +================ +*/ +int idPhysics_Static::GetNumClipModels() const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Static::SetMass +================ +*/ +void idPhysics_Static::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Static::GetMass +================ +*/ +float idPhysics_Static::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Static::SetContents +================ +*/ +void idPhysics_Static::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Static::GetContents +================ +*/ +int idPhysics_Static::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Static::SetClipMask +================ +*/ +void idPhysics_Static::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_Static::GetClipMask +================ +*/ +int idPhysics_Static::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetBounds +================ +*/ +const idBounds &idPhysics_Static::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return bounds_zero; +} + +/* +================ +idPhysics_Static::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Static::GetAbsBounds( int id ) const { + static idBounds absBounds; + + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + absBounds[0] = absBounds[1] = current.origin; + return absBounds; +} + +/* +================ +idPhysics_Static::Evaluate +================ +*/ +bool idPhysics_Static::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis, oldAxis; + + + if ( hasMaster ) { + oldOrigin = current.origin; + oldAxis = current.axis; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.axis = current.localAxis * masterAxis; + } else { + current.axis = current.localAxis; + } + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); + } + return false; +} + +/* +================ +idPhysics_Static::Interpolate +================ +*/ +bool idPhysics_Static::Interpolate( const float fraction ) { + + // We only interpolate if we actually get snapshots. + if( self->GetNumSnapshotsReceived() >= 1 ) { + current = InterpolateStaticPState( previous, next, fraction ); + } + + return true; +} + +/* +================ +idPhysics_Static::UpdateTime +================ +*/ +void idPhysics_Static::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Static::GetTime +================ +*/ +int idPhysics_Static::GetTime() const { + return 0; +} + +/* +================ +idPhysics_Static::GetImpactInfo +================ +*/ +void idPhysics_Static::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Static::ApplyImpulse +================ +*/ +void idPhysics_Static::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Static::AddForce +================ +*/ +void idPhysics_Static::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Static::Activate +================ +*/ +void idPhysics_Static::Activate() { +} + +/* +================ +idPhysics_Static::PutToRest +================ +*/ +void idPhysics_Static::PutToRest() { +} + +/* +================ +idPhysics_Static::IsAtRest +================ +*/ +bool idPhysics_Static::IsAtRest() const { + return true; +} + +/* +================ +idPhysics_Static::GetRestStartTime +================ +*/ +int idPhysics_Static::GetRestStartTime() const { + return 0; +} + +/* +================ +idPhysics_Static::IsPushable +================ +*/ +bool idPhysics_Static::IsPushable() const { + return false; +} + +/* +================ +idPhysics_Static::SaveState +================ +*/ +void idPhysics_Static::SaveState() { +} + +/* +================ +idPhysics_Static::RestoreState +================ +*/ +void idPhysics_Static::RestoreState() { +} + +/* +================ +idPhysics_Static::SetOrigin +================ +*/ +void idPhysics_Static::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } else { + current.origin = newOrigin; + } + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + + next = ConvertPStateToInterpolateState( current ); + previous = next; +} + +/* +================ +idPhysics_Static::SetAxis +================ +*/ +void idPhysics_Static::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } else { + current.axis = newAxis; + } + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } + + next = ConvertPStateToInterpolateState( current ); + previous = next; +} + + + +/* +================ +idPhysics_Static::Translate +================ +*/ +void idPhysics_Static::Translate( const idVec3 &translation, int id ) { + current.localOrigin += translation; + current.origin += translation; + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } +} + +/* +================ +idPhysics_Static::Rotate +================ +*/ +void idPhysics_Static::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + current.axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + current.localOrigin = current.origin; + } + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } +} + +/* +================ +idPhysics_Static::GetOrigin +================ +*/ +const idVec3 &idPhysics_Static::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Static::GetAxis +================ +*/ +const idMat3 &idPhysics_Static::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Static::SetLinearVelocity +================ +*/ +void idPhysics_Static::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Static::SetAngularVelocity +================ +*/ +void idPhysics_Static::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Static::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetGravity +================ +*/ +void idPhysics_Static::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_Static::GetGravity +================ +*/ +const idVec3 &idPhysics_Static::GetGravity() const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + return gravity; +} + +/* +================ +idPhysics_Static::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Static::GetGravityNormal() const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_Static::ClipTranslation +================ +*/ +void idPhysics_Static::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.clip.TranslationModel( results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, model->Handle(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.clip.Translation( results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, self ); + } +} + +/* +================ +idPhysics_Static::ClipRotation +================ +*/ +void idPhysics_Static::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.clip.RotationModel( results, current.origin, rotation, + clipModel, current.axis, MASK_SOLID, model->Handle(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.clip.Rotation( results, current.origin, rotation, clipModel, current.axis, MASK_SOLID, self ); + } +} + +/* +================ +idPhysics_Static::ClipContents +================ +*/ +int idPhysics_Static::ClipContents( const idClipModel *model ) const { + if ( clipModel ) { + if ( model ) { + return gameLocal.clip.ContentsModel( clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } else { + return gameLocal.clip.Contents( clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } + } + return 0; +} + +/* +================ +idPhysics_Static::DisableClip +================ +*/ +void idPhysics_Static::DisableClip() { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Static::EnableClip +================ +*/ +void idPhysics_Static::EnableClip() { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Static::UnlinkClip +================ +*/ +void idPhysics_Static::UnlinkClip() { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Static::LinkClip +================ +*/ +void idPhysics_Static::LinkClip() { + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, 0, current.origin, current.axis ); + } +} + +/* +================ +idPhysics_Static::EvaluateContacts +================ +*/ +bool idPhysics_Static::EvaluateContacts() { + return false; +} + +/* +================ +idPhysics_Static::GetNumContacts +================ +*/ +int idPhysics_Static::GetNumContacts() const { + return 0; +} + +/* +================ +idPhysics_Static::GetContact +================ +*/ +const contactInfo_t &idPhysics_Static::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_Static::ClearContacts +================ +*/ +void idPhysics_Static::ClearContacts() { +} + +/* +================ +idPhysics_Static::AddContactEntity +================ +*/ +void idPhysics_Static::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::RemoveContactEntity +================ +*/ +void idPhysics_Static::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::HasGroundContacts +================ +*/ +bool idPhysics_Static::HasGroundContacts() const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundEntity +================ +*/ +bool idPhysics_Static::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundClipModel +================ +*/ +bool idPhysics_Static::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_Static::SetPushed +================ +*/ +void idPhysics_Static::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Static::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetMaster +================ +*/ +void idPhysics_Static::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.axis * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Static::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Static::GetBlockingInfo() const { + return NULL; +} + +/* +================ +idPhysics_Static::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Static::GetBlockingEntity() const { + return NULL; +} + +/* +================ +idPhysics_Static::GetLinearEndTime +================ +*/ +int idPhysics_Static::GetLinearEndTime() const { + return 0; +} + +/* +================ +idPhysics_Static::GetAngularEndTime +================ +*/ +int idPhysics_Static::GetAngularEndTime() const { + return 0; +} + +/* +================ +idPhysics_Static::WriteToSnapshot +================ +*/ +void idPhysics_Static::WriteToSnapshot( idBitMsg &msg ) const { + idCQuat quat, localQuat; + + quat = current.axis.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Static::ReadFromSnapshot( const idBitMsg &msg ) { + idCQuat quat, localQuat; + + previous = next; + + next = ReadStaticInterpolatePStateFromSnapshot( msg ); +} + +/* +================ +ConvertInterpolateStateToPState +================ +*/ +staticPState_s ConvertInterpolateStateToPState( const staticInterpolatePState_t & interpolateState ) { + + staticPState_s state; + state.origin = interpolateState.origin; + state.localOrigin = interpolateState.localOrigin; + state.localAxis = interpolateState.localAxis.ToMat3(); + state.axis = interpolateState.axis.ToMat3(); + + return state; +} + +/* +================ +ConvertPStateToInterpolateState +================ +*/ +staticInterpolatePState_t ConvertPStateToInterpolateState( const staticPState_t & state ) { + + staticInterpolatePState_t interpolateState; + interpolateState.origin = state.origin; + interpolateState.localOrigin = state.localOrigin; + interpolateState.localAxis = state.localAxis.ToQuat(); + interpolateState.axis = state.axis.ToQuat(); + + return interpolateState; +} + +/* +================ +ReadStaticInterpolatePStateFromSnapshot +================ +*/ +staticInterpolatePState_t ReadStaticInterpolatePStateFromSnapshot( const idBitMsg & msg ) { + staticInterpolatePState_t state; + + state.origin = ReadFloatArray< idVec3 >( msg ); + const idCQuat cAxis = ReadFloatArray< idCQuat >( msg ); + state.localOrigin = ReadDeltaFloatArray( msg, state.origin ); + state.localAxis = ReadDeltaFloatArray( msg, cAxis ).ToQuat(); + + state.axis = cAxis.ToQuat(); + + return state; +} + +/* +================ +InterpolateStaticPState +================ +*/ +staticPState_t InterpolateStaticPState( const staticInterpolatePState_t & previous, const staticInterpolatePState_t & next, float fraction ) { + staticPState_t result; + + result.origin = Lerp( previous.origin, next.origin, fraction ); + result.axis = idQuat().Slerp( previous.axis, next.axis, fraction ).ToMat3(); + + result.localOrigin = Lerp( previous.localOrigin, next.localOrigin, fraction ); + result.localAxis = idQuat().Slerp( previous.localAxis, next.localAxis, fraction ).ToMat3(); + + return result; +} diff --git a/neo/d3xp/physics/Physics_Static.h b/neo/d3xp/physics/Physics_Static.h new file mode 100644 index 00000000..931648e3 --- /dev/null +++ b/neo/d3xp/physics/Physics_Static.h @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_STATIC_H__ +#define __PHYSICS_STATIC_H__ + +/* +=============================================================================== + + Physics for a non moving object using at most one collision model. + +=============================================================================== +*/ + +class idBitMsg; + +typedef struct staticPState_s { + idVec3 origin; + idMat3 axis; + idVec3 localOrigin; + idMat3 localAxis; +} staticPState_t; + +// Storing the state used for interpolation with quaternions +// means I don't have to do a bunch of conversions between +// idMat3s and idQuats every frame. +struct staticInterpolatePState_t { + idVec3 origin; + idQuat axis; + idVec3 localOrigin; + idQuat localAxis; +}; + +/* +================ +ReadStaticInterpolatePStateFromSnapshot +================ +*/ +staticInterpolatePState_t ReadStaticInterpolatePStateFromSnapshot( const idBitMsg & msg ); +staticPState_s ConvertInterpolateStateToPState( const staticInterpolatePState_t & interpolateState ); +staticInterpolatePState_t ConvertPStateToInterpolateState( const staticPState_t & state ); + +class idPhysics_Static : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Static ); + + idPhysics_Static(); + ~idPhysics_Static(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + void SetSelf( idEntity *e ); + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool Interpolate( const float fraction ); + void ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ) {} + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate(); + void PutToRest(); + bool IsAtRest() const; + int GetRestStartTime() const; + bool IsPushable() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity() const; + const idVec3 & GetGravityNormal() const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + bool EvaluateContacts(); + int GetNumContacts() const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts(); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts() const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo() const; + idEntity * GetBlockingEntity() const; + + int GetLinearEndTime() const; + int GetAngularEndTime() const; + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +protected: + idEntity * self; // entity using this physics object + staticPState_t current; // physics state + idClipModel * clipModel; // collision model + + // Used for client-side interpolation + staticInterpolatePState_t previous; + staticInterpolatePState_t next; + + // master + bool hasMaster; + bool isOrientated; +}; + +staticPState_t InterpolateStaticPState( const staticInterpolatePState_t & previous, + const staticInterpolatePState_t & next, + float fraction ); + +#endif /* !__PHYSICS_STATIC_H__ */ diff --git a/neo/d3xp/physics/Physics_StaticMulti.cpp b/neo/d3xp/physics/Physics_StaticMulti.cpp new file mode 100644 index 00000000..d5c7331a --- /dev/null +++ b/neo/d3xp/physics/Physics_StaticMulti.cpp @@ -0,0 +1,1082 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_StaticMulti ) +END_CLASS + +staticPState_t defaultState; +staticInterpolatePState_t defaultInterpolateState; + +/* +================ +idPhysics_StaticMulti::idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::idPhysics_StaticMulti() { + self = NULL; + hasMaster = false; + isOrientated = false; + + defaultState.origin.Zero(); + defaultState.axis.Identity(); + defaultState.localOrigin.Zero(); + defaultState.localAxis.Identity(); + + defaultInterpolateState.origin.Zero(); + defaultInterpolateState.axis = defaultState.axis.ToQuat(); + defaultInterpolateState.localAxis = defaultInterpolateState.axis; + defaultInterpolateState.localOrigin.Zero(); + + current.SetNum( 1 ); + current[0] = defaultState; + previous.SetNum( 1 ); + previous[0] = defaultInterpolateState; + next.SetNum( 1 ); + next[0] = defaultInterpolateState; + clipModels.SetNum( 1 ); + clipModels[0] = NULL; +} + +/* +================ +idPhysics_StaticMulti::~idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::~idPhysics_StaticMulti() { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + for ( int i = 0; i < clipModels.Num(); i++ ) { + delete clipModels[i]; + } +} + +/* +================ +idPhysics_StaticMulti::Save +================ +*/ +void idPhysics_StaticMulti::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + + savefile->WriteInt(current.Num()); + for ( i = 0; i < current.Num(); i++ ) { + savefile->WriteVec3( current[i].origin ); + savefile->WriteMat3( current[i].axis ); + savefile->WriteVec3( current[i].localOrigin ); + savefile->WriteMat3( current[i].localAxis ); + } + + savefile->WriteInt( clipModels.Num() ); + for ( i = 0; i < clipModels.Num(); i++ ) { + savefile->WriteClipModel( clipModels[i] ); + } + + savefile->WriteBool(hasMaster); + savefile->WriteBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::Restore +================ +*/ +void idPhysics_StaticMulti::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadInt(num); + current.AssureSize( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadVec3( current[i].origin ); + savefile->ReadMat3( current[i].axis ); + savefile->ReadVec3( current[i].localOrigin ); + savefile->ReadMat3( current[i].localAxis ); + } + + savefile->ReadInt(num); + clipModels.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadClipModel( clipModels[i] ); + } + + savefile->ReadBool(hasMaster); + savefile->ReadBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::SetSelf +================ +*/ +void idPhysics_StaticMulti::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_StaticMulti::RemoveIndex +================ +*/ +void idPhysics_StaticMulti::RemoveIndex( int id, bool freeClipModel ) { + if ( id < 0 || id >= clipModels.Num() ) { + return; + } + if ( clipModels[id] && freeClipModel ) { + delete clipModels[id]; + clipModels[id] = NULL; + } + clipModels.RemoveIndex( id ); + current.RemoveIndex( id ); +} + +/* +================ +idPhysics_StaticMulti::SetClipModel +================ +*/ +void idPhysics_StaticMulti::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + int i; + + assert( self ); + + if ( id >= clipModels.Num() ) { + current.AssureSize( id+1, defaultState ); + clipModels.AssureSize( id+1, NULL ); + } + + if ( clipModels[id] && clipModels[id] != model && freeOld ) { + delete clipModels[id]; + } + clipModels[id] = model; + if ( clipModels[id] ) { + clipModels[id]->Link( gameLocal.clip, self, id, current[id].origin, current[id].axis ); + + } + + for ( i = clipModels.Num() - 1; i >= 1; i-- ) { + if ( clipModels[i] ) { + break; + } + } + current.SetNum( i+1 ); + clipModels.SetNum( i+1 ); + + // Assure that on first setup, our next/previous is the same as current. + previous.SetNum( current.Num() ); + next.SetNum( previous.Num() ); + for( int curIdx = 0; curIdx < current.Num(); curIdx++ ) { + previous[curIdx] = ConvertPStateToInterpolateState( current[curIdx] ); + previous[curIdx] = next[curIdx]; + } +} + +/* +================ +idPhysics_StaticMulti::GetClipModel +================ +*/ +idClipModel *idPhysics_StaticMulti::GetClipModel( int id ) const { + if ( id >= 0 && id < clipModels.Num() && clipModels[id] ) { + return clipModels[id]; + } + return gameLocal.clip.DefaultClipModel(); +} + +/* +================ +idPhysics_StaticMulti::GetNumClipModels +================ +*/ +int idPhysics_StaticMulti::GetNumClipModels() const { + return clipModels.Num(); +} + +/* +================ +idPhysics_StaticMulti::SetMass +================ +*/ +void idPhysics_StaticMulti::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetMass +================ +*/ +float idPhysics_StaticMulti::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_StaticMulti::SetContents +================ +*/ +void idPhysics_StaticMulti::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + clipModels[id]->SetContents( contents ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->SetContents( contents ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetContents +================ +*/ +int idPhysics_StaticMulti::GetContents( int id ) const { + int i, contents = 0; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + contents = clipModels[id]->GetContents(); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + contents |= clipModels[i]->GetContents(); + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::SetClipMask +================ +*/ +void idPhysics_StaticMulti::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetClipMask +================ +*/ +int idPhysics_StaticMulti::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetBounds( int id ) const { + int i; + static idBounds bounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetBounds(); + } + } + if ( id == -1 ) { + bounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds[0] -= clipModels[i]->GetOrigin(); + bounds[1] -= clipModels[i]->GetOrigin(); + break; + } + } + return bounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::GetAbsBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetAbsBounds(); + } + } + if ( id == -1 ) { + absBounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + absBounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + return absBounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::Evaluate +================ +*/ +bool idPhysics_StaticMulti::Evaluate( int timeStepMSec, int endTimeMSec ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin = masterOrigin + current[i].localOrigin * masterAxis; + if ( isOrientated ) { + current[i].axis = current[i].localAxis * masterAxis; + } else { + current[i].axis = current[i].localAxis; + } + if ( clipModels[i] ) { + clipModels[i]->Link( gameLocal.clip, self, i, current[i].origin, current[i].axis ); + } + } + + // FIXME: return false if master did not move + return true; + } + return false; +} + +/* +================ +idPhysics_StaticMulti::Interpolate +================ +*/ +bool idPhysics_StaticMulti::Interpolate( const float fraction ) { + // If the sizes don't match, just use the latest version. + // TODO: This might cause visual snapping, is there a better solution? + if ( current.Num() != previous.Num() || + current.Num() != next.Num() ) { + current.SetNum( next.Num() ); + for ( int i = 0; i < next.Num(); ++i ) { + current[i] = InterpolateStaticPState( next[i], next[i], 1.0f ); + } + return true; + } + + for ( int i = 0; i < current.Num(); ++i ) { + current[i] = InterpolateStaticPState( previous[i], next[i], fraction ); + } + + return true; +} + +/* +================ +idPhysics_StaticMulti::UpdateTime +================ +*/ +void idPhysics_StaticMulti::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_StaticMulti::GetTime +================ +*/ +int idPhysics_StaticMulti::GetTime() const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetImpactInfo +================ +*/ +void idPhysics_StaticMulti::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_StaticMulti::ApplyImpulse +================ +*/ +void idPhysics_StaticMulti::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_StaticMulti::AddForce +================ +*/ +void idPhysics_StaticMulti::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_StaticMulti::Activate +================ +*/ +void idPhysics_StaticMulti::Activate() { +} + +/* +================ +idPhysics_StaticMulti::PutToRest +================ +*/ +void idPhysics_StaticMulti::PutToRest() { +} + +/* +================ +idPhysics_StaticMulti::IsAtRest +================ +*/ +bool idPhysics_StaticMulti::IsAtRest() const { + return true; +} + +/* +================ +idPhysics_StaticMulti::GetRestStartTime +================ +*/ +int idPhysics_StaticMulti::GetRestStartTime() const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::IsPushable +================ +*/ +bool idPhysics_StaticMulti::IsPushable() const { + return false; +} + +/* +================ +idPhysics_StaticMulti::SaveState +================ +*/ +void idPhysics_StaticMulti::SaveState() { +} + +/* +================ +idPhysics_StaticMulti::RestoreState +================ +*/ +void idPhysics_StaticMulti::RestoreState() { +} + +/* +================ +idPhysics_StaticMulti::SetOrigin +================ +*/ +void idPhysics_StaticMulti::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].origin = masterOrigin + newOrigin * masterAxis; + } else { + current[id].origin = newOrigin; + } + if ( clipModels[id] ) { + clipModels[id]->Link( gameLocal.clip, self, id, current[id].origin, current[id].axis ); + } + } else if ( id == -1 ) { + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + Translate( masterOrigin + masterAxis * newOrigin - current[0].origin ); + } else { + Translate( newOrigin - current[0].origin ); + } + } +} + +/* +================ +idPhysics_StaticMulti::SetAxis +================ +*/ +void idPhysics_StaticMulti::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].axis = newAxis * masterAxis; + } else { + current[id].axis = newAxis; + } + if ( clipModels[id] ) { + clipModels[id]->Link( gameLocal.clip, self, id, current[id].origin, current[id].axis ); + } + } else if ( id == -1 ) { + idMat3 axis; + idRotation rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + axis = current[0].axis.Transpose() * ( newAxis * masterAxis ); + } else { + axis = current[0].axis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( current[0].origin ); + + Rotate( rotation ); + } +} + +/* +================ +idPhysics_StaticMulti::Translate +================ +*/ +void idPhysics_StaticMulti::Translate( const idVec3 &translation, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin += translation; + current[id].origin += translation; + + if ( clipModels[id] ) { + clipModels[id]->Link( gameLocal.clip, self, id, current[id].origin, current[id].axis ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin += translation; + current[i].origin += translation; + + if ( clipModels[i] ) { + clipModels[i]->Link( gameLocal.clip, self, i, current[i].origin, current[i].axis ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::Rotate +================ +*/ +void idPhysics_StaticMulti::Rotate( const idRotation &rotation, int id ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].origin *= rotation; + current[id].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].localAxis *= rotation.ToMat3(); + current[id].localOrigin = ( current[id].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[id].localAxis = current[id].axis; + current[id].localOrigin = current[id].origin; + } + + if ( clipModels[id] ) { + clipModels[id]->Link( gameLocal.clip, self, id, current[id].origin, current[id].axis ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin *= rotation; + current[i].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[i].localAxis *= rotation.ToMat3(); + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + current[i].localOrigin = current[i].origin; + } + + if ( clipModels[i] ) { + clipModels[i]->Link( gameLocal.clip, self, i, current[i].origin, current[i].axis ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetOrigin +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetOrigin( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].origin; + } + if ( clipModels.Num() ) { + return current[0].origin; + } else { + return vec3_origin; + } +} + +/* +================ +idPhysics_StaticMulti::GetAxis +================ +*/ +const idMat3 &idPhysics_StaticMulti::GetAxis( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].axis; + } + if ( clipModels.Num() ) { + return current[0].axis; + } else { + return mat3_identity; + } +} + +/* +================ +idPhysics_StaticMulti::SetLinearVelocity +================ +*/ +void idPhysics_StaticMulti::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::SetAngularVelocity +================ +*/ +void idPhysics_StaticMulti::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetGravity +================ +*/ +void idPhysics_StaticMulti::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_StaticMulti::GetGravity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravity() const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + return gravity; +} + +/* +================ +idPhysics_StaticMulti::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravityNormal() const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_StaticMulti::ClipTranslation +================ +*/ +void idPhysics_StaticMulti::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipTranslation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipRotation +================ +*/ +void idPhysics_StaticMulti::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipRotation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipContents +================ +*/ +int idPhysics_StaticMulti::ClipContents( const idClipModel *model ) const { + int i, contents; + + contents = 0; + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + if ( model ) { + contents |= gameLocal.clip.ContentsModel( clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, + model->Handle(), model->GetOrigin(), model->GetAxis() ); + } else { + contents |= gameLocal.clip.Contents( clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, NULL ); + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::DisableClip +================ +*/ +void idPhysics_StaticMulti::DisableClip() { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Disable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::EnableClip +================ +*/ +void idPhysics_StaticMulti::EnableClip() { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Enable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::UnlinkClip +================ +*/ +void idPhysics_StaticMulti::UnlinkClip() { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Unlink(); + } + } +} + +/* +================ +idPhysics_StaticMulti::LinkClip +================ +*/ +void idPhysics_StaticMulti::LinkClip() { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Link( gameLocal.clip, self, i, current[i].origin, current[i].axis ); + } + } +} + +/* +================ +idPhysics_StaticMulti::EvaluateContacts +================ +*/ +bool idPhysics_StaticMulti::EvaluateContacts() { + return false; +} + +/* +================ +idPhysics_StaticMulti::GetNumContacts +================ +*/ +int idPhysics_StaticMulti::GetNumContacts() const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetContact +================ +*/ +const contactInfo_t &idPhysics_StaticMulti::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_StaticMulti::ClearContacts +================ +*/ +void idPhysics_StaticMulti::ClearContacts() { +} + +/* +================ +idPhysics_StaticMulti::AddContactEntity +================ +*/ +void idPhysics_StaticMulti::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::RemoveContactEntity +================ +*/ +void idPhysics_StaticMulti::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::HasGroundContacts +================ +*/ +bool idPhysics_StaticMulti::HasGroundContacts() const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundEntity +================ +*/ +bool idPhysics_StaticMulti::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundClipModel +================ +*/ +bool idPhysics_StaticMulti::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::SetPushed +================ +*/ +void idPhysics_StaticMulti::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_StaticMulti::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetMaster +================ +*/ +void idPhysics_StaticMulti::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current[i].localAxis = current[i].axis * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + } + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_StaticMulti::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_StaticMulti::GetBlockingInfo() const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetBlockingEntity +================ +*/ +idEntity *idPhysics_StaticMulti::GetBlockingEntity() const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetLinearEndTime +================ +*/ +int idPhysics_StaticMulti::GetLinearEndTime() const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetAngularEndTime +================ +*/ +int idPhysics_StaticMulti::GetAngularEndTime() const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::WriteToSnapshot +================ +*/ +void idPhysics_StaticMulti::WriteToSnapshot( idBitMsg &msg ) const { + int i; + idCQuat quat, localQuat; + + msg.WriteByte( current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + quat = current[i].axis.ToCQuat(); + localQuat = current[i].localAxis.ToCQuat(); + + msg.WriteFloat( current[i].origin[0] ); + msg.WriteFloat( current[i].origin[1] ); + msg.WriteFloat( current[i].origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current[i].origin[0], current[i].localOrigin[0] ); + msg.WriteDeltaFloat( current[i].origin[1], current[i].localOrigin[1] ); + msg.WriteDeltaFloat( current[i].origin[2], current[i].localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + } +} + +/* +================ +idPhysics_StaticMulti::ReadFromSnapshot +================ +*/ +void idPhysics_StaticMulti::ReadFromSnapshot( const idBitMsg &msg ) { + int i, num; + idCQuat quat, localQuat; + + num = msg.ReadByte(); + assert( num == current.Num() ); + + previous = next; + + next.SetNum( num ); + + for ( i = 0; i < current.Num(); i++ ) { + next[i] = ReadStaticInterpolatePStateFromSnapshot( msg ); + } +} diff --git a/neo/d3xp/physics/Physics_StaticMulti.h b/neo/d3xp/physics/Physics_StaticMulti.h new file mode 100644 index 00000000..39ca7116 --- /dev/null +++ b/neo/d3xp/physics/Physics_StaticMulti.h @@ -0,0 +1,160 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PHYSICS_STATICMULTI_H__ +#define __PHYSICS_STATICMULTI_H__ + +/* +=============================================================================== + + Physics for a non moving object using no or multiple collision models. + +=============================================================================== +*/ + +class idPhysics_StaticMulti : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_StaticMulti ); + + idPhysics_StaticMulti(); + ~idPhysics_StaticMulti(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void RemoveIndex( int id = 0, bool freeClipModel = true ); + +public: // common physics interface + + void SetSelf( idEntity *e ); + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels() const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool Interpolate( const float fraction ); + void ResetInterpolationState( const idVec3 & origin, const idMat3 & axis ) {} + void UpdateTime( int endTimeMSec ); + int GetTime() const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate(); + void PutToRest(); + bool IsAtRest() const; + int GetRestStartTime() const; + bool IsPushable() const; + + void SaveState(); + void RestoreState(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity() const; + const idVec3 & GetGravityNormal() const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip(); + void EnableClip(); + + void UnlinkClip(); + void LinkClip(); + + bool EvaluateContacts(); + int GetNumContacts() const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts(); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts() const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo() const; + idEntity * GetBlockingEntity() const; + + int GetLinearEndTime() const; + int GetAngularEndTime() const; + + void WriteToSnapshot( idBitMsg &msg ) const; + void ReadFromSnapshot( const idBitMsg &msg ); + +protected: + idEntity * self; // entity using this physics object + idList current; // physics state + idList clipModels; // collision model + + // States used in client-side interpolation + idList previous; + idList next; + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATICMULTI_H__ */ diff --git a/neo/d3xp/physics/Push.cpp b/neo/d3xp/physics/Push.cpp new file mode 100644 index 00000000..ce1214db --- /dev/null +++ b/neo/d3xp/physics/Push.cpp @@ -0,0 +1,1448 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../Game_local.h" + + +/* +============ +idPush::InitSavingPushedEntityPositions +============ +*/ +void idPush::InitSavingPushedEntityPositions() { + numPushed = 0; +} + +/* +============ +idPush::SaveEntityPosition +============ +*/ +void idPush::SaveEntityPosition( idEntity *ent ) { + int i; + + // if already saved the physics state for this entity + for ( i = 0; i < numPushed; i++ ) { + if ( pushed[i].ent == ent ) { + return; + } + } + + // don't overflow + if ( numPushed >= MAX_GENTITIES ) { + gameLocal.Error( "more than MAX_GENTITIES pushed entities" ); + return; + } + + pushed[numPushed].ent = ent; + + // if the entity is an actor + if ( ent->IsType( idActor::Type ) ) { + // save the delta view angles + pushed[numPushed].deltaViewAngles = static_cast(ent)->GetDeltaViewAngles(); + } + + // save the physics state + ent->GetPhysics()->SaveState(); + + numPushed++; +} + +/* +============ +idPush::RestorePushedEntityPositions +============ +*/ +void idPush::RestorePushedEntityPositions() { + int i; + + for ( i = 0; i < numPushed; i++ ) { + + // if the entity is an actor + if ( pushed[i].ent->IsType( idActor::Type ) ) { + // set back the delta view angles + static_cast(pushed[i].ent)->SetDeltaViewAngles( pushed[i].deltaViewAngles ); + } + + // restore the physics state + pushed[i].ent->GetPhysics()->RestoreState(); + } +} + +/* +============ +idPush::RotateEntityToAxial +============ +*/ +bool idPush::RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ) { + int i; + trace_t trace; + idRotation rotation; + idMat3 axis; + idPhysics *physics; + + physics = ent->GetPhysics(); + axis = physics->GetAxis(); + if ( !axis.IsRotated() ) { + return true; + } + // try to rotate the bbox back to axial with at most four rotations + for ( i = 0; i < 4; i++ ) { + axis = physics->GetAxis(); + rotation = axis.ToRotation(); + rotation.Scale( -1 ); + rotation.SetOrigin( rotationPoint ); + // tiny float numbers in the clip axis, this can get the entity stuck + if ( rotation.GetAngle() == 0.0f ) { + physics->SetAxis( mat3_identity ); + return true; + } + // + ent->GetPhysics()->ClipRotation( trace, rotation, NULL ); + // if the full rotation is possible + if ( trace.fraction >= 1.0f ) { + // set bbox in final axial position + physics->SetOrigin( trace.endpos ); + physics->SetAxis( mat3_identity ); + return true; + } + // if partial rotation was possible + else if ( trace.fraction > 0.0f ) { + // partial rotation + physics->SetOrigin( trace.endpos ); + physics->SetAxis( trace.endAxis ); + } + // next rotate around collision point + rotationPoint = trace.c.point; + } + return false; +} + +#ifdef NEW_PUSH + +/* +============ +idPush::CanPushEntity +============ +*/ +bool idPush::CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ) { + + // if the physics object is not pushable + if ( !ent->GetPhysics()->IsPushable() ) { + return false; + } + + // if the entity doesn't clip with this pusher + if ( !( ent->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + return false; + } + + // don't push players in noclip mode + if ( ent->client && ent->client->noclip ) { + return false; + } + + // if we should only push idMoveable entities + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !ent->IsType( idMoveable::Type ) ) { + return false; + } + + // if we shouldn't push entities the original pusher rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( initialPusher->GetPhysics()->IsGroundEntity( ent->entityNumber ) ) { + return false; + } + } + + return true; +} + +/* +============ +idPush::AddEntityToPushedGroup +============ +*/ +void idPush::AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ) { + int i, j; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + if ( fraction > pushedGroup[i].fraction ) { + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact &= groundContact; + pushedGroup[i].test = true; + } + return; + } + else if ( fraction > pushedGroup[i].fraction ) { + for ( j = pushedGroupSize; j > i; j-- ) { + pushedGroup[j] = pushedGroup[j-1]; + } + break; + } + } + + // put the entity in the group + pushedGroupSize++; + pushedGroup[i].ent = ent; + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact = groundContact; + pushedGroup[i].test = true; + + // remove any further occurances of the same entity in the group + for ( i++; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + for ( j = i+1; j < pushedGroupSize; j++ ) { + pushedGroup[j-1] = pushedGroup[j]; + } + pushedGroupSize--; + break; + } + } +} + +/* +============ +idPush::IsFullyPushed +============ +*/ +bool idPush::IsFullyPushed( idEntity *ent ) { + int i; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( pushedGroup[i].fraction < 1.0f ) { + return false; + } + if ( ent == pushedGroup[i].ent ) { + return true; + } + } + return false; +} + +/* +============ +idPush::ClipTranslationAgainstPusher +============ +*/ +bool idPush::ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipTranslation( t, translation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForTranslation +============ +*/ +int idPush::GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsTranslation( bounds, physics->GetOrigin(), physics->GetAxis(), translation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds + n = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES ); + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by translating the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idVec3 realTranslation, partialTranslation; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = pusher->GetPhysics()->GetAxis(); + memset( results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForTranslation( pusher, pusher, flags, translation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip translation + pusher->GetPhysics()->ClipTranslation( results, translation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realTranslation = results.fraction * translation; + } + else { + realTranslation = translation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForTranslation( curPusher, pusher, flags, realTranslation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( !CanPushEntity( ent, curPusher, pusher, flags ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipTranslationAgainstPusher( trace, ent, curPusher, -fraction * realTranslation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 0; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipTranslation( trace, partialTranslation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipTranslation( results, realTranslation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // translate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Translate( partialTranslation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // translate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Translate( results.fraction * realTranslation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + } + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationAgainstPusher +============ +*/ +bool idPush::ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipRotation( t, rotation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForRotation +============ +*/ +int idPush::GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsRotation( bounds, physics->GetOrigin(), physics->GetAxis(), rotation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds + n = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES ); + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by rotating the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idRotation realRotation, partialRotation; + idMat3 oldAxis; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = newAxis; + memset( results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForRotation( pusher, pusher, flags, rotation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip rotation + pusher->GetPhysics()->ClipRotation( results, rotation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realRotation = results.fraction * rotation; + } + else { + realRotation = rotation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForRotation( curPusher, pusher, flags, realRotation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipRotationAgainstPusher( trace, ent, curPusher, -fraction * realRotation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipRotation( trace, partialRotation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipRotation( results, realRotation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // rotate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Rotate( partialRotation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // rotate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Rotate( realRotation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + pusher->GetPhysics()->GetClipModel(i)->Enable(); + } + // rotate any actors back to axial + for ( i = 1; i < pushedGroupSize; i++ ) { + // if the entity is using actor physics + if ( pushedGroup[i].ent->GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( pushedGroup[i].ent, pushedGroup[i].ent->GetPhysics()->GetOrigin() ) ) { + // don't allow rotation if the bbox is no longer axial + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + } + } + + return totalMass; +} + +#else /* !NEW_PUSH */ + +enum { + PUSH_NO, // not pushed + PUSH_OK, // pushed ok + PUSH_BLOCKED // blocked +}; + +/* +============ +idPush::ClipEntityRotation +============ +*/ +void idPush::ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idRotation &rotation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipRotation( trace, rotation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::ClipEntityTranslation +============ +*/ +void idPush::ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idVec3 &translation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipTranslation( trace, translation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::TryRotatePushEntity +============ +*/ +#ifdef _DEBUG +// #define ROTATIONAL_PUSH_DEBUG +#endif + +int idPush::TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + trace_t trace; + idVec3 rotationPoint; + idRotation newRotation; + float checkAngle; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef ROTATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate the entity colliding with all other entities except the pusher itself + ClipEntityRotation( trace, check, NULL, clipModel, rotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle() * trace.fraction; + // test if the entity can stay at it's partly pushed position by rotating + // the entity in reverse only colliding with pusher + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), -(rotation.GetAngle() - checkAngle) ); + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle(); + } + // point to rotate entity bbox around back to axial + rotationPoint = physics->GetOrigin(); + } + else { + // rotate entity in reverse only colliding with pusher + newRotation = rotation; + newRotation.Scale( -1 ); + // + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { +#ifdef ROTATIONAL_PUSH_DEBUG + // set pusher into final position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return PUSH_NO; + } + // get point to rotate bbox around back to axial + rotationPoint = results.c.point; + // angle along which the entity will be pushed + checkAngle = rotation.GetAngle() * (1.0f - results.fraction); + // rotate the entity colliding with all other entities except the pusher itself + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + ClipEntityRotation( trace, check, NULL, clipModel, newRotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + + SaveEntityPosition( check ); + + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + // NOTE: this code prevents msvc 6.0 & 7.0 from screwing up the above code in + // release builds moving less floats than it should + static float shit = checkAngle; + + newRotation.RotatePoint( rotationPoint ); + + // rotate the entity + physics->Rotate( newRotation ); + + // set pusher into final position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity uses actor physics + if ( physics->IsType( idPhysics_Actor::Type ) ) { + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( check, rotationPoint ) ) { + // don't allow rotation if the bbox is no longer axial + return PUSH_BLOCKED; + } + } + +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity is an actor using actor physics + if ( check->IsType( idActor::Type ) && physics->IsType( idPhysics_Actor::Type ) ) { + + // if the entity is standing ontop of the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate actor view + idActor *actor = static_cast(check); + idAngles delta = actor->GetDeltaViewAngles(); + delta.yaw += newRotation.ToMat3()[0].ToYaw(); + actor->SetDeltaViewAngles( delta ); + } + } + + return PUSH_OK; +} + +/* +============ +idPush::TryTranslatePushEntity +============ +*/ +#ifdef _DEBUG +// #define TRANSLATIONAL_PUSH_DEBUG +#endif + +int idPush::TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ) { + trace_t trace; + idVec3 checkMove; + idVec3 oldOrigin; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, move ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // vector along which the entity is pushed + checkMove = move * trace.fraction; + // test if the entity can stay at it's partly pushed position by moving the entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -(move - checkMove) ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // vector along which the entity is pushed + checkMove = move; + } + } + else { + // move entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -move ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { + return PUSH_NO; + } + // vector along which the entity is pushed + checkMove = move * (1.0f - results.fraction); + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, checkMove ); + // if there is a collisions + if ( trace.fraction < 1.0f ) { + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // FIXME: try to push the blocking entity as well ? + // FIXME: handle sliding along more than one collision plane ? + // FIXME: this code has issues, player pushing box into corner in "maps/mre/aaron/test.map" + +/* + oldOrigin = physics->GetOrigin(); + + // movement still remaining + checkMove *= (1.0f - trace.fraction); + + // project the movement along the collision plane + if ( !checkMove.ProjectAlongPlane( trace.c.normal, 0.1f, 1.001f ) ) { + return PUSH_BLOCKED; + } + checkMove *= 1.001f; + + // move entity from collision point along the collision plane + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, NULL, NULL, checkMove ); + + if ( trace.fraction < 1.0f ) { + physics->SetOrigin( oldOrigin ); + return PUSH_BLOCKED; + } + + checkMove = trace.endpos - oldOrigin; + + // move entity in reverse only colliding with pusher + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, clipModel, NULL, -move ); + + physics->SetOrigin( oldOrigin ); +*/ + if ( trace.fraction < 1.0f ) { + return PUSH_BLOCKED; + } + } + } + + SaveEntityPosition( check ); + + // translate the entity + physics->Translate( checkMove ); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + // set the pusher in the translated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + return PUSH_OK; +} + +/* +============ +idPush::DiscardEntities +============ +*/ +int idPush::DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ) { + int i, num; + idEntity *check; + + // remove all entities we cannot or should not push from the list + for ( num = i = 0; i < numEntities; i++ ) { + check = entityList[ i ]; + + // if the physics object is not pushable + if ( !check->GetPhysics()->IsPushable() ) { + continue; + } + + // if the entity doesn't clip with this pusher + if ( !( check->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + continue; + } + + // don't push players in noclip mode + if ( check->IsType( idPlayer::Type ) && static_cast(check)->noclip ) { + continue; + } + + // if we should only push idMoveable entities + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !check->IsType( idMoveable::Type ) ) { + continue; + } + + // if we shouldn't push entities the clip model rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( pusher->GetPhysics()->IsGroundEntity( check->entityNumber ) ) { + continue; + } + } + + // keep entity in list + entityList[ num++ ] = entityList[i]; + } + + return num; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idVec3 clipMove, clipOrigin, oldOrigin, dir, impulse; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + dir = translation; + dir.Normalize(); + dir.z += 1.0f; + dir *= 10.0f; + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsTranslation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), translation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + + listedEntities = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES ); + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + + gameLocal.clip.Translation( results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); + + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipMove = results.endpos - clipModel->GetOrigin(); + clipOrigin = results.endpos; + + } + else { + + clipMove = translation; + clipOrigin = newOrigin; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldOrigin = clipModel->GetOrigin(); + + // try to push the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryTranslatePushEntity( pushResults, check, clipModel, flags, clipOrigin, clipMove ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the translated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), oldOrigin, clipModel->GetAxis() ); + + // wake up this object + if ( flags & PUSHFL_APPLYIMPULSE ) { + impulse = physics->GetMass() * dir; + } else { + impulse.Zero(); + } + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), impulse ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile + if ( check->IsType( idProjectile::Type ) ) { + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs + if ( check->IsType( idAFEntity_Base::Type ) && check->spawnArgs.GetBool( "gib" ) ) { + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // if the entity is a moveable item and gibs + if ( check->IsType( idMoveableItem::Type ) && check->spawnArgs.GetBool( "gib" ) ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idRotation clipRotation; + idMat3 clipAxis, oldAxis; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsRotation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), rotation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + + listedEntities = gameLocal.clip.EntitiesTouchingBounds( pushBounds, -1, entityList, MAX_GENTITIES ); + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + + gameLocal.clip.Rotation( results, clipModel->GetOrigin(), rotation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); + + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipRotation = rotation * results.fraction; + clipAxis = results.endAxis; + } + else { + + clipRotation = rotation; + clipAxis = newAxis; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldAxis = clipModel->GetAxis(); + + // try to push all the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryRotatePushEntity( pushResults, check, clipModel, flags, clipAxis, clipRotation ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the rotated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), oldAxis ); + + // wake up this object + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), vec3_origin ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile + if ( check->IsType( idProjectile::Type ) ) { + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs + if ( check->IsType( idAFEntity_Base::Type ) && check->spawnArgs.GetBool( "gib" ) ) { + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +#endif /* !NEW_PUSH */ + + +/* +============ +idPush::ClipPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ) { + idVec3 translation; + idRotation rotation; + float mass; + + mass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // translational push + translation = newOrigin - oldOrigin; + + // if the pusher translates + if ( translation != vec3_origin ) { + + mass += ClipTranslationalPush( results, pusher, flags, newOrigin, translation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newOrigin = oldOrigin; + } + + // rotational push + rotation = ( oldAxis.Transpose() * newAxis ).ToRotation(); + rotation.SetOrigin( newOrigin ); + rotation.Normalize180(); + rotation.ReCalculateMatrix(); // recalculate the rotation matrix to avoid accumulating rounding errors + + // if the pusher rotates + if ( rotation.GetAngle() != 0.0f ) { + + // recalculate new axis to avoid floating point rounding problems + newAxis = oldAxis * rotation.ToMat3(); + newAxis.OrthoNormalizeSelf(); + newAxis.FixDenormals(); + newAxis.FixDegeneracies(); + + pusher->GetPhysics()->GetClipModel()->SetPosition( newOrigin, oldAxis ); + + mass += ClipRotationalPush( results, pusher, flags, newAxis, rotation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newAxis = oldAxis; + } + + return mass; +} diff --git a/neo/d3xp/physics/Push.h b/neo/d3xp/physics/Push.h new file mode 100644 index 00000000..66937592 --- /dev/null +++ b/neo/d3xp/physics/Push.h @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PUSH_H__ +#define __PUSH_H__ + +/* +=============================================================================== + + Allows physics objects to be pushed geometrically. + +=============================================================================== +*/ + +#define PUSHFL_ONLYMOVEABLE 1 // only push moveable entities +#define PUSHFL_NOGROUNDENTITIES 2 // don't push entities the clip model rests upon +#define PUSHFL_CLIP 4 // also clip against all non-moveable entities +#define PUSHFL_CRUSH 8 // kill blocking entities +#define PUSHFL_APPLYIMPULSE 16 // apply impulse to pushed entities + +//#define NEW_PUSH + +class idPush { +public: + // Try to push other entities by moving the given entity. + // If results.fraction < 1.0 the move was blocked by results.c.entityNum + // Returns total mass of all pushed entities. + float ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + + float ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + + float ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ); + + // initialize saving the positions of entities being pushed + void InitSavingPushedEntityPositions(); + // move all pushed entities back to their previous position + void RestorePushedEntityPositions(); + // returns the number of pushed entities + int GetNumPushedEntities() const { return numPushed; } + // get the ith pushed entity + idEntity * GetPushedEntity( int i ) const { assert( i >= 0 && i < numPushed ); return pushed[i].ent; } + +private: + struct pushed_s { + idEntity * ent; // pushed entity + idAngles deltaViewAngles; // actor delta view angles + } pushed[MAX_GENTITIES]; // pushed entities + int numPushed; // number of pushed entities + + struct pushedGroup_s { + idEntity * ent; + float fraction; + bool groundContact; + bool test; + } pushedGroup[MAX_GENTITIES]; + int pushedGroupSize; + +private: + void SaveEntityPosition( idEntity *ent ); + bool RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ); +#ifdef NEW_PUSH + bool CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ); + void AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ); + bool IsFullyPushed( idEntity *ent ); + bool ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ); + int GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ); + bool ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ); + int GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ); +#else + void ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idRotation &rotation ); + void ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idVec3 &translation ); + int TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + int TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + int DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ); +#endif +}; + +#endif /* !__PUSH_H__ */ diff --git a/neo/d3xp/precompiled.cpp b/neo/d3xp/precompiled.cpp new file mode 100644 index 00000000..ef6e84e3 --- /dev/null +++ b/neo/d3xp/precompiled.cpp @@ -0,0 +1,29 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" diff --git a/neo/d3xp/script/Script_Compiler.cpp b/neo/d3xp/script/Script_Compiler.cpp new file mode 100644 index 00000000..1f5c4441 --- /dev/null +++ b/neo/d3xp/script/Script_Compiler.cpp @@ -0,0 +1,2648 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +#define FUNCTION_PRIORITY 2 +#define INT_PRIORITY 2 +#define NOT_PRIORITY 5 +#define TILDE_PRIORITY 5 +#define TOP_PRIORITY 7 + +bool idCompiler::punctuationValid[ 256 ]; +char *idCompiler::punctuation[] = { + "+=", "-=", "*=", "/=", "%=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", "::", ";", ",", + "~", "!", "*", "/", "%", "(", ")", "-", "+", + "=", "[", "]", ".", "<", ">" , "&", "|", ":", NULL +}; + +opcode_t idCompiler::opcodes[] = { + { "", "RETURN", -1, false, &def_void, &def_void, &def_void }, + + { "++", "UINC_F", 1, true, &def_float, &def_void, &def_void }, + { "++", "UINCP_F", 1, true, &def_object, &def_field, &def_float }, + { "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void }, + { "--", "UDECP_F", 1, true, &def_object, &def_field, &def_float }, + + { "~", "COMP_F", -1, false, &def_float, &def_void, &def_float }, + + { "*", "MUL_F", 3, false, &def_float, &def_float, &def_float }, + { "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float }, + { "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector }, + { "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector }, + + { "/", "DIV", 3, false, &def_float, &def_float, &def_float }, + { "%", "MOD_F", 3, false, &def_float, &def_float, &def_float }, + + { "+", "ADD_F", 4, false, &def_float, &def_float, &def_float }, + { "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector }, + { "+", "ADD_S", 4, false, &def_string, &def_string, &def_string }, + { "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string }, + { "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string }, + { "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string }, + { "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string }, + + { "-", "SUB_F", 4, false, &def_float, &def_float, &def_float }, + { "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector }, + + { "==", "EQ_F", 5, false, &def_float, &def_float, &def_float }, + { "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float }, + { "==", "EQ_S", 5, false, &def_string, &def_string, &def_float }, + { "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float }, + { "==", "EQ_EO", 5, false, &def_entity, &def_object, &def_float }, + { "==", "EQ_OE", 5, false, &def_object, &def_entity, &def_float }, + { "==", "EQ_OO", 5, false, &def_object, &def_object, &def_float }, + + { "!=", "NE_F", 5, false, &def_float, &def_float, &def_float }, + { "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float }, + { "!=", "NE_S", 5, false, &def_string, &def_string, &def_float }, + { "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float }, + { "!=", "NE_EO", 5, false, &def_entity, &def_object, &def_float }, + { "!=", "NE_OE", 5, false, &def_object, &def_entity, &def_float }, + { "!=", "NE_OO", 5, false, &def_object, &def_object, &def_float }, + + { "<=", "LE", 5, false, &def_float, &def_float, &def_float }, + { ">=", "GE", 5, false, &def_float, &def_float, &def_float }, + { "<", "LT", 5, false, &def_float, &def_float, &def_float }, + { ">", "GT", 5, false, &def_float, &def_float, &def_float }, + + { ".", "INDIRECT_F", 1, false, &def_object, &def_field, &def_float }, + { ".", "INDIRECT_V", 1, false, &def_object, &def_field, &def_vector }, + { ".", "INDIRECT_S", 1, false, &def_object, &def_field, &def_string }, + { ".", "INDIRECT_E", 1, false, &def_object, &def_field, &def_entity }, + { ".", "INDIRECT_BOOL", 1, false, &def_object, &def_field, &def_boolean }, + { ".", "INDIRECT_OBJ", 1, false, &def_object, &def_field, &def_object }, + + { ".", "ADDRESS", 1, false, &def_entity, &def_field, &def_pointer }, + + { ".", "EVENTCALL", 2, false, &def_entity, &def_function, &def_void }, + { ".", "OBJECTCALL", 2, false, &def_object, &def_function, &def_void }, + { ".", "SYSCALL", 2, false, &def_void, &def_function, &def_void }, + + { "=", "STORE_F", 6, true, &def_float, &def_float, &def_float }, + { "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector }, + { "=", "STORE_S", 6, true, &def_string, &def_string, &def_string }, + { "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity }, + { "=", "STORE_BOOL", 6, true, &def_boolean, &def_boolean, &def_boolean }, + { "=", "STORE_OBJENT", 6, true, &def_object, &def_entity, &def_object }, + { "=", "STORE_OBJ", 6, true, &def_object, &def_object, &def_object }, + { "=", "STORE_OBJENT", 6, true, &def_entity, &def_object, &def_object }, + + { "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string }, + { "=", "STORE_BTOS", 6, true, &def_string, &def_boolean, &def_string }, + { "=", "STORE_VTOS", 6, true, &def_string, &def_vector, &def_string }, + { "=", "STORE_FTOBOOL", 6, true, &def_boolean, &def_float, &def_boolean }, + { "=", "STORE_BOOLTOF", 6, true, &def_float, &def_boolean, &def_float }, + + { "=", "STOREP_F", 6, true, &def_pointer, &def_float, &def_float }, + { "=", "STOREP_V", 6, true, &def_pointer, &def_vector, &def_vector }, + { "=", "STOREP_S", 6, true, &def_pointer, &def_string, &def_string }, + { "=", "STOREP_ENT", 6, true, &def_pointer, &def_entity, &def_entity }, + { "=", "STOREP_FLD", 6, true, &def_pointer, &def_field, &def_field }, + { "=", "STOREP_BOOL", 6, true, &def_pointer, &def_boolean, &def_boolean }, + { "=", "STOREP_OBJ", 6, true, &def_pointer, &def_object, &def_object }, + { "=", "STOREP_OBJENT", 6, true, &def_pointer, &def_object, &def_object }, + + { "<=>", "STOREP_FTOS", 6, true, &def_pointer, &def_float, &def_string }, + { "<=>", "STOREP_BTOS", 6, true, &def_pointer, &def_boolean, &def_string }, + { "<=>", "STOREP_VTOS", 6, true, &def_pointer, &def_vector, &def_string }, + { "<=>", "STOREP_FTOBOOL", 6, true, &def_pointer, &def_float, &def_boolean }, + { "<=>", "STOREP_BOOLTOF", 6, true, &def_pointer, &def_boolean, &def_float }, + + { "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void }, + { "*=", "UMUL_V", 6, true, &def_vector, &def_float, &def_void }, + { "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void }, + { "/=", "UDIV_V", 6, true, &def_vector, &def_float, &def_void }, + { "%=", "UMOD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_V", 6, true, &def_vector, &def_vector, &def_void }, + { "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void }, + { "-=", "USUB_V", 6, true, &def_vector, &def_vector, &def_void }, + { "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void }, + { "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void }, + + { "!", "NOT_BOOL", -1, false, &def_boolean, &def_void, &def_float }, + { "!", "NOT_F", -1, false, &def_float, &def_void, &def_float }, + { "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float }, + + { "", "NEG_F", -1, false, &def_float, &def_void, &def_float }, + { "", "NEG_V", -1, false, &def_vector, &def_void, &def_vector }, + + { "int", "INT_F", -1, false, &def_float, &def_void, &def_float }, + + { "", "IF", -1, false, &def_float, &def_jumpoffset, &def_void }, + { "", "IFNOT", -1, false, &def_float, &def_jumpoffset, &def_void }, + + // calls returns REG_RETURN + { "", "CALL", -1, false, &def_function, &def_argsize, &def_void }, + { "", "THREAD", -1, false, &def_function, &def_argsize, &def_void }, + { "", "OBJTHREAD", -1, false, &def_function, &def_argsize, &def_void }, + + { "", "PUSH_F", -1, false, &def_float, &def_float, &def_void }, + { "", "PUSH_V", -1, false, &def_vector, &def_vector, &def_void }, + { "", "PUSH_S", -1, false, &def_string, &def_string, &def_void }, + { "", "PUSH_ENT", -1, false, &def_entity, &def_entity, &def_void }, + { "", "PUSH_OBJ", -1, false, &def_object, &def_object, &def_void }, + { "", "PUSH_OBJENT", -1, false, &def_entity, &def_object, &def_void }, + { "", "PUSH_FTOS", -1, false, &def_string, &def_float, &def_void }, + { "", "PUSH_BTOF", -1, false, &def_float, &def_boolean, &def_void }, + { "", "PUSH_FTOB", -1, false, &def_boolean, &def_float, &def_void }, + { "", "PUSH_VTOS", -1, false, &def_string, &def_vector, &def_void }, + { "", "PUSH_BTOS", -1, false, &def_string, &def_boolean, &def_void }, + + { "", "GOTO", -1, false, &def_jumpoffset, &def_void, &def_void }, + + { "&&", "AND", 7, false, &def_float, &def_float, &def_float }, + { "&&", "AND_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "&&", "AND_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "&&", "AND_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + { "||", "OR", 7, false, &def_float, &def_float, &def_float }, + { "||", "OR_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "||", "OR_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "||", "OR_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + + { "&", "BITAND", 3, false, &def_float, &def_float, &def_float }, + { "|", "BITOR", 3, false, &def_float, &def_float, &def_float }, + + { "", "BREAK", -1, false, &def_float, &def_void, &def_void }, + { "", "CONTINUE", -1, false, &def_float, &def_void, &def_void }, + + { NULL } +}; + +/* +================ +idCompiler::idCompiler() +================ +*/ +idCompiler::idCompiler() { + char **ptr; + int id; + + // make sure we have the right # of opcodes in the table + assert( ( sizeof( opcodes ) / sizeof( opcodes[ 0 ] ) ) == ( NUM_OPCODES + 1 ) ); + + parserPtr = &parser; + + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + basetype = NULL; + currentLineNumber = 0; + currentFileNumber = 0; + errorCount = 0; + console = false; + scope = &def_namespace; + + memset( &immediate, 0, sizeof( immediate ) ); + memset( punctuationValid, 0, sizeof( punctuationValid ) ); + for( ptr = punctuation; *ptr != NULL; ptr++ ) { + id = parserPtr->GetPunctuationId( *ptr ); + if ( ( id >= 0 ) && ( id < 256 ) ) { + punctuationValid[ id ] = true; + } + } +} + +/* +============ +idCompiler::Error + +Aborts the current file load +============ +*/ +void idCompiler::Error( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + throw idCompileError( string ); +} + +/* +============ +idCompiler::Warning + +Prints a warning about the current line +============ +*/ +void idCompiler::Warning( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + parserPtr->Warning( "%s", string ); +} + +/* +============ +idCompiler::VirtualFunctionConstant + +Creates a def for an index into a virtual function table +============ +*/ +ID_INLINE idVarDef *idCompiler::VirtualFunctionConstant( idVarDef *func ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = func->scope->TypeDef()->GetFunctionNumber( func->value.functionPtr ); + if ( eval._int < 0 ) { + Error( "Function '%s' not found in scope '%s'", func->Name(), func->scope->Name() ); + } + + return GetImmediate( &type_virtualfunction, &eval, "" ); +} + +/* +============ +idCompiler::SizeConstant + +Creates a def for a size constant +============ +*/ +ID_INLINE idVarDef *idCompiler::SizeConstant( int size ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = size; + return GetImmediate( &type_argsize, &eval, "" ); +} + +/* +============ +idCompiler::JumpConstant + +Creates a def for a jump constant +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpConstant( int value ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = value; + return GetImmediate( &type_jumpoffset, &eval, "" ); +} + +/* +============ +idCompiler::JumpDef + +Creates a def for a relative jump from one code location to another +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpDef( int jumpfrom, int jumpto ) { + return JumpConstant( jumpto - jumpfrom ); +} + +/* +============ +idCompiler::JumpTo + +Creates a def for a relative jump from current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpTo( int jumpto ) { + return JumpDef( gameLocal.program.NumStatements(), jumpto ); +} + +/* +============ +idCompiler::JumpFrom + +Creates a def for a relative jump from code location to current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpFrom( int jumpfrom ) { + return JumpDef( jumpfrom, gameLocal.program.NumStatements() ); +} + +/* +============ +idCompiler::Divide +============ +*/ +ID_INLINE float idCompiler::Divide( float numerator, float denominator ) { + if ( denominator == 0 ) { + Error( "Divide by zero" ); + } + + return numerator / denominator; +} + +/* +============ +idCompiler::FindImmediate + +tries to find an existing immediate with the same value +============ +*/ +idVarDef *idCompiler::FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const { + idVarDef *def; + etype_t etype; + + etype = type->Type(); + + // check for a constant with the same value + for( def = gameLocal.program.GetDefList( "" ); def != NULL; def = def->Next() ) { + if ( def->TypeDef() != type ) { + continue; + } + + switch( etype ) { + case ev_field : + if ( *def->value.intPtr == eval->_int ) { + return def; + } + break; + + case ev_argsize : + if ( def->value.argSize == eval->_int ) { + return def; + } + break; + + case ev_jumpoffset : + if ( def->value.jumpOffset == eval->_int ) { + return def; + } + break; + + case ev_entity : + if ( *def->value.intPtr == eval->entity ) { + return def; + } + break; + + case ev_string : + if ( idStr::Cmp( def->value.stringPtr, string ) == 0 ) { + return def; + } + break; + + case ev_float : + if ( *def->value.floatPtr == eval->_float ) { + return def; + } + break; + + case ev_virtualfunction : + if ( def->value.virtualFunction == eval->_int ) { + return def; + } + break; + + + case ev_vector : + if ( ( def->value.vectorPtr->x == eval->vector[ 0 ] ) && + ( def->value.vectorPtr->y == eval->vector[ 1 ] ) && + ( def->value.vectorPtr->z == eval->vector[ 2 ] ) ) { + return def; + } + break; + + default : + Error( "weird immediate type" ); + break; + } + } + + return NULL; +} + +/* +============ +idCompiler::GetImmediate + +returns an existing immediate with the same value, or allocates a new one +============ +*/ +idVarDef *idCompiler::GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ) { + idVarDef *def; + + def = FindImmediate( type, eval, string ); + if ( def ) { + def->numUsers++; + } else { + // allocate a new def + def = gameLocal.program.AllocDef( type, "", &def_namespace, true ); + if ( type->Type() == ev_string ) { + def->SetString( string, true ); + } else { + def->SetValue( *eval, true ); + } + } + + return def; +} + +/* +============ +idCompiler::OptimizeOpcode + +try to optimize when the operator works on constants only +============ +*/ +idVarDef *idCompiler::OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + eval_t c; + idTypeDef *type; + + if ( var_a == NULL || var_a->initialized != idVarDef::initializedConstant ) { + return NULL; + } + if ( var_b == NULL || var_b->initialized != idVarDef::initializedConstant ) { + return NULL; + } + + idVec3 &vec_c = *reinterpret_cast( &c.vector[ 0 ] ); + + memset( &c, 0, sizeof( c ) ); + switch( op - opcodes ) { + case OP_ADD_F: c._float = *var_a->value.floatPtr + *var_b->value.floatPtr; type = &type_float; break; + case OP_ADD_V: vec_c = *var_a->value.vectorPtr + *var_b->value.vectorPtr; type = &type_vector; break; + case OP_SUB_F: c._float = *var_a->value.floatPtr - *var_b->value.floatPtr; type = &type_float; break; + case OP_SUB_V: vec_c = *var_a->value.vectorPtr - *var_b->value.vectorPtr; type = &type_vector; break; + case OP_MUL_F: c._float = *var_a->value.floatPtr * *var_b->value.floatPtr; type = &type_float; break; + case OP_MUL_V: c._float = *var_a->value.vectorPtr * *var_b->value.vectorPtr; type = &type_float; break; + case OP_MUL_FV: vec_c = *var_b->value.vectorPtr * *var_a->value.floatPtr; type = &type_vector; break; + case OP_MUL_VF: vec_c = *var_a->value.vectorPtr * *var_b->value.floatPtr; type = &type_vector; break; + case OP_DIV_F: c._float = Divide( *var_a->value.floatPtr, *var_b->value.floatPtr ); type = &type_float; break; + case OP_MOD_F: c._float = (int)*var_a->value.floatPtr % (int)*var_b->value.floatPtr; type = &type_float; break; + case OP_BITAND: c._float = ( int )*var_a->value.floatPtr & ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_BITOR: c._float = ( int )*var_a->value.floatPtr | ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_GE: c._float = *var_a->value.floatPtr >= *var_b->value.floatPtr; type = &type_float; break; + case OP_LE: c._float = *var_a->value.floatPtr <= *var_b->value.floatPtr; type = &type_float; break; + case OP_GT: c._float = *var_a->value.floatPtr > *var_b->value.floatPtr; type = &type_float; break; + case OP_LT: c._float = *var_a->value.floatPtr < *var_b->value.floatPtr; type = &type_float; break; + case OP_AND: c._float = *var_a->value.floatPtr && *var_b->value.floatPtr; type = &type_float; break; + case OP_OR: c._float = *var_a->value.floatPtr || *var_b->value.floatPtr; type = &type_float; break; + case OP_NOT_BOOL: c._int = !*var_a->value.intPtr; type = &type_boolean; break; + case OP_NOT_F: c._float = !*var_a->value.floatPtr; type = &type_float; break; + case OP_NOT_V: c._float = !var_a->value.vectorPtr->x && !var_a->value.vectorPtr->y && !var_a->value.vectorPtr->z; type = &type_float; break; + case OP_NEG_F: c._float = -*var_a->value.floatPtr; type = &type_float; break; + case OP_NEG_V: vec_c = -*var_a->value.vectorPtr; type = &type_vector; break; + case OP_INT_F: c._float = ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_EQ_F: c._float = ( *var_a->value.floatPtr == *var_b->value.floatPtr ); type = &type_float; break; + case OP_EQ_V: c._float = var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_EQ_E: c._float = ( *var_a->value.intPtr == *var_b->value.intPtr ); type = &type_float; break; + case OP_NE_F: c._float = ( *var_a->value.floatPtr != *var_b->value.floatPtr ); type = &type_float; break; + case OP_NE_V: c._float = !var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_NE_E: c._float = ( *var_a->value.intPtr != *var_b->value.intPtr ); type = &type_float; break; + case OP_UADD_F: c._float = *var_b->value.floatPtr + *var_a->value.floatPtr; type = &type_float; break; + case OP_USUB_F: c._float = *var_b->value.floatPtr - *var_a->value.floatPtr; type = &type_float; break; + case OP_UMUL_F: c._float = *var_b->value.floatPtr * *var_a->value.floatPtr; type = &type_float; break; + case OP_UDIV_F: c._float = Divide( *var_b->value.floatPtr, *var_a->value.floatPtr ); type = &type_float; break; + case OP_UMOD_F: c._float = ( int ) *var_b->value.floatPtr % ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UOR_F: c._float = ( int )*var_b->value.floatPtr | ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UAND_F: c._float = ( int )*var_b->value.floatPtr & ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UINC_F: c._float = *var_a->value.floatPtr + 1; type = &type_float; break; + case OP_UDEC_F: c._float = *var_a->value.floatPtr - 1; type = &type_float; break; + case OP_COMP_F: c._float = ( float )~( int )*var_a->value.floatPtr; type = &type_float; break; + default: type = NULL; break; + } + + if ( !type ) { + return NULL; + } + + if ( var_a ) { + var_a->numUsers--; + if ( var_a->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_a, NULL ); + } + } + if ( var_b ) { + var_b->numUsers--; + if ( var_b->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_b, NULL ); + } + } + + return GetImmediate( type, &c, "" ); +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +idVarDef *idCompiler::EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + statement_t *statement; + idVarDef *var_c; + + var_c = OptimizeOpcode( op, var_a, var_b ); + if ( var_c ) { + return var_c; + } + + if ( var_a && !strcmp( var_a->Name(), RESULT_STRING ) ) { + var_a->numUsers++; + } + if ( var_b && !strcmp( var_b->Name(), RESULT_STRING ) ) { + var_b->numUsers++; + } + + statement = gameLocal.program.AllocStatement(); + statement->linenumber = currentLineNumber; + statement->file = currentFileNumber; + + if ( ( op->type_c == &def_void ) || op->rightAssociative ) { + // ifs, gotos, and assignments don't need vars allocated + var_c = NULL; + } else { + // allocate result space + // try to reuse result defs as much as possible + var_c = gameLocal.program.FindFreeResultDef( op->type_c->TypeDef(), RESULT_STRING, scope, var_a, var_b ); + // set user count back to 1, a result def needs to be used twice before it can be reused + var_c->numUsers = 1; + } + + statement->op = op - opcodes; + statement->a = var_a; + statement->b = var_b; + statement->c = var_c; + + if ( op->rightAssociative ) { + return var_a; + } + + return var_c; +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +ID_INLINE idVarDef *idCompiler::EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ) { + return EmitOpcode( &opcodes[ op ], var_a, var_b ); +} + +/* +============ +idCompiler::EmitPush + +Emits an opcode to push the variable onto the stack. +============ +*/ +bool idCompiler::EmitPush( idVarDef *expression, const idTypeDef *funcArg ) { + opcode_t *op; + opcode_t *out; + + out = NULL; + for( op = &opcodes[ OP_PUSH_F ]; op->name && !strcmp( op->name, "" ); op++ ) { + if ( ( funcArg->Type() == op->type_a->Type() ) && ( expression->Type() == op->type_b->Type() ) ) { + out = op; + break; + } + } + + if ( !out ) { + if ( ( expression->TypeDef() != funcArg ) && !expression->TypeDef()->Inherits( funcArg ) ) { + return false; + } + + out = &opcodes[ OP_PUSH_ENT ]; + } + + EmitOpcode( out, expression, 0 ); + + return true; +} + +/* +============== +idCompiler::NextToken + +Sets token, immediateType, and possibly immediate +============== +*/ +void idCompiler::NextToken() { + int i; + + // reset our type + immediateType = NULL; + memset( &immediate, 0, sizeof( immediate ) ); + + // Save the token's line number and filename since when we emit opcodes the current + // token is always the next one to be read + currentLineNumber = token.line; + currentFileNumber = gameLocal.program.GetFilenum( parserPtr->GetFileName() ); + + if ( !parserPtr->ReadToken( &token ) ) { + eof = true; + return; + } + + if ( currentFileNumber != gameLocal.program.GetFilenum( parserPtr->GetFileName() ) ) { + if ( ( braceDepth > 0 ) && ( token != "}" ) ) { + // missing a closing brace. try to give as much info as possible. + if ( scope->Type() == ev_function ) { + Error( "Unexpected end of file inside function '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_object ) { + Error( "Unexpected end of file inside object '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_namespace ) { + Error( "Unexpected end of file inside namespace '%s'. Missing closing braces.", scope->Name() ); + } else { + Error( "Unexpected end of file inside braced section" ); + } + } + } + + switch( token.type ) { + case TT_STRING: + // handle quoted strings as a unit + immediateType = &type_string; + return; + + case TT_LITERAL: { + // handle quoted vectors as a unit + immediateType = &type_vector; + idLexer lex( token, token.Length(), parserPtr->GetFileName(), LEXFL_NOERRORS ); + idToken token2; + for( i = 0; i < 3; i++ ) { + if ( !lex.ReadToken( &token2 ) ) { + Error( "Couldn't read vector. '%s' is not in the form of 'x y z'", token.c_str() ); + } + if ( token2.type == TT_PUNCTUATION && token2 == "-" ) { + if ( !lex.CheckTokenType( TT_NUMBER, 0, &token2 ) ) { + Error( "expected a number following '-' but found '%s' in vector '%s'", token2.c_str(), token.c_str() ); + } + immediate.vector[ i ] = -token2.GetFloatValue(); + } else if ( token2.type == TT_NUMBER ) { + immediate.vector[ i ] = token2.GetFloatValue(); + } else { + Error( "vector '%s' is not in the form of 'x y z'. expected float value, found '%s'", token.c_str(), token2.c_str() ); + } + } + return; + } + + case TT_NUMBER: + immediateType = &type_float; + immediate._float = token.GetFloatValue(); + return; + + case TT_PUNCTUATION: + // entity names + if ( token == "$" ) { + immediateType = &type_entity; + parserPtr->ReadToken( &token ); + return; + } + + if ( token == "{" ) { + braceDepth++; + return; + } + + if ( token == "}" ) { + braceDepth--; + return; + } + + if ( punctuationValid[ token.subtype ] ) { + return; + } + + Error( "Unknown punctuation '%s'", token.c_str() ); + break; + + case TT_NAME: + return; + + default: + Error( "Unknown token '%s'", token.c_str() ); + } +} + +/* +============= +idCompiler::ExpectToken + +Issues an Error if the current token isn't equal to string +Gets the next token +============= +*/ +void idCompiler::ExpectToken( const char *string ) { + if ( token != string ) { + Error( "expected '%s', found '%s'", string, token.c_str() ); + } + + NextToken(); +} + +/* +============= +idCompiler::CheckToken + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool idCompiler::CheckToken( const char *string ) { + if ( token != string ) { + return false; + } + + NextToken(); + + return true; +} + +/* +============ +idCompiler::ParseName + +Checks to see if the current token is a valid name +============ +*/ +void idCompiler::ParseName( idStr &name ) { + if ( token.type != TT_NAME ) { + Error( "'%s' is not a name", token.c_str() ); + } + + name = token; + NextToken(); +} + +/* +============ +idCompiler::SkipOutOfFunction + +For error recovery, pops out of nested braces +============ +*/ +void idCompiler::SkipOutOfFunction() { + while( braceDepth ) { + parserPtr->SkipBracedSection( false ); + braceDepth--; + } + NextToken(); +} + +/* +============ +idCompiler::SkipToSemicolon + +For error recovery +============ +*/ +void idCompiler::SkipToSemicolon() { + do { + if ( CheckToken( ";" ) ) { + return; + } + + NextToken(); + } while( !eof ); +} + +/* +============ +idCompiler::CheckType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::CheckType() { + idTypeDef *type; + + if ( token == "float" ) { + type = &type_float; + } else if ( token == "vector" ) { + type = &type_vector; + } else if ( token == "entity" ) { + type = &type_entity; + } else if ( token == "string" ) { + type = &type_string; + } else if ( token == "void" ) { + type = &type_void; + } else if ( token == "object" ) { + type = &type_object; + } else if ( token == "boolean" ) { + type = &type_boolean; + } else if ( token == "namespace" ) { + type = &type_namespace; + } else if ( token == "scriptEvent" ) { + type = &type_scriptevent; + } else { + type = gameLocal.program.FindType( token.c_str() ); + if ( type != NULL && !type->Inherits( &type_object ) ) { + type = NULL; + } + } + + return type; +} + +/* +============ +idCompiler::ParseType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::ParseType() { + idTypeDef *type; + + type = CheckType(); + if ( !type ) { + Error( "\"%s\" is not a type", token.c_str() ); + } + + if ( ( type == &type_scriptevent ) && ( scope != &def_namespace ) ) { + Error( "scriptEvents can only defined in the global namespace" ); + } + + if ( ( type == &type_namespace ) && ( scope->Type() != ev_namespace ) ) { + Error( "A namespace may only be defined globally, or within another namespace" ); + } + + NextToken(); + + return type; +} + +/* +============ +idCompiler::ParseImmediate + +Looks for a preexisting constant +============ +*/ +idVarDef *idCompiler::ParseImmediate() { + idVarDef *def; + + def = GetImmediate( immediateType, &immediate, token.c_str() ); + NextToken(); + + return def; +} + +/* +============ +idCompiler::EmitFunctionParms +============ +*/ +idVarDef *idCompiler::EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ) { + idVarDef *e; + const idTypeDef *type; + const idTypeDef *funcArg; + idVarDef *returnDef; + idTypeDef *returnType; + int arg; + int size; + int resultOp; + + type = func->TypeDef(); + if ( func->Type() != ev_function ) { + Error( "'%s' is not a function", func->Name() ); + } + + // copy the parameters to the global parameter variables + arg = startarg; + size = startsize; + if ( !CheckToken( ")" ) ) { + do { + if ( arg >= type->NumParameters() ) { + Error( "too many parameters" ); + } + + e = GetExpression( TOP_PRIORITY ); + + funcArg = type->GetParmType( arg ); + if ( !EmitPush( e, funcArg ) ) { + Error( "type mismatch on parm %i of call to '%s'", arg + 1, func->Name() ); + } + + if ( funcArg->Type() == ev_object ) { + size += type_object.Size(); + } else { + size += funcArg->Size(); + } + + arg++; + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + if ( arg < type->NumParameters() ) { + Error( "too few parameters for function '%s'", func->Name() ); + } + + if ( op == OP_CALL ) { + EmitOpcode( op, func, 0 ); + } else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) { + EmitOpcode( op, object, VirtualFunctionConstant( func ) ); + + // need arg size seperate since script object may be NULL + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = SizeConstant( func->value.functionPtr->parmTotal ); + } else { + EmitOpcode( op, func, SizeConstant( size ) ); + } + + // we need to copy off the result into a temporary result location, so figure out the opcode + returnType = type->ReturnType(); + if ( returnType->Type() == ev_string ) { + resultOp = OP_STORE_S; + returnDef = gameLocal.program.returnStringDef; + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + returnDef = gameLocal.program.returnDef; + + switch( returnType->Type() ) { + case ev_void : + resultOp = OP_STORE_F; + break; + + case ev_boolean : + resultOp = OP_STORE_BOOL; + break; + + case ev_float : + resultOp = OP_STORE_F; + break; + + case ev_vector : + resultOp = OP_STORE_V; + break; + + case ev_entity : + resultOp = OP_STORE_ENT; + break; + + case ev_object : + resultOp = OP_STORE_OBJ; + break; + + default : + // shut up compiler + resultOp = OP_STORE_OBJ; + Error( "Invalid return type for function '%s'", func->Name() ); + } + } + + if ( returnType->Type() == ev_void ) { + // don't need result space since there's no result, so just return the normal result def. + return returnDef; + } + + // allocate result space + // try to reuse result defs as much as possible + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + idVarDef *resultDef = gameLocal.program.FindFreeResultDef( returnType, RESULT_STRING, scope, statement.a, statement.b ); + // set user count back to 0, a result def needs to be used twice before it can be reused + resultDef->numUsers = 0; + + EmitOpcode( resultOp, returnDef, resultDef ); + + return resultDef; +} + +/* +============ +idCompiler::ParseFunctionCall +============ +*/ +idVarDef *idCompiler::ParseFunctionCall( idVarDef *funcDef ) { + assert( funcDef ); + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( funcDef->initialized == idVarDef::uninitialized ) { + Error( "Function '%s' has not been defined yet", funcDef->GlobalName() ); + } + + assert( funcDef->value.functionPtr ); + if ( callthread ) { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + Error( "Built-in functions cannot be called as threads" ); + } + callthread = false; + return EmitFunctionParms( OP_THREAD, funcDef, 0, 0, NULL ); + } else { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + if ( !thisdef ) { + Error( "No 'self' within scope" ); + } + + return ParseEventCall( thisdef, funcDef ); + } else { + Error( "Built-in functions cannot be called without an object" ); + } + } + + return EmitFunctionParms( OP_CALL, funcDef, 0, 0, NULL ); + } +} + +/* +============ +idCompiler::ParseObjectCall +============ +*/ +idVarDef *idCompiler::ParseObjectCall( idVarDef *object, idVarDef *func ) { + EmitPush( object, object->TypeDef() ); + if ( callthread ) { + callthread = false; + return EmitFunctionParms( OP_OBJTHREAD, func, 1, type_object.Size(), object ); + } else { + return EmitFunctionParms( OP_OBJECTCALL, func, 1, 0, object ); + } +} + +/* +============ +idCompiler::ParseEventCall +============ +*/ +idVarDef *idCompiler::ParseEventCall( idVarDef *object, idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + + if ( object->Type() == ev_object ) { + EmitPush( object, &type_entity ); + } else { + EmitPush( object, object->TypeDef() ); + } + + return EmitFunctionParms( OP_EVENTCALL, funcDef, 0, type_object.Size(), NULL ); +} + +/* +============ +idCompiler::ParseSysObjectCall +============ +*/ +idVarDef *idCompiler::ParseSysObjectCall( idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( funcDef->value.functionPtr->eventdef == NULL ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + + assert( funcDef->value.functionPtr->eventdef != NULL ); // to remove stupid analyze warning + if ( !idThread::Type.RespondsTo( *funcDef->value.functionPtr->eventdef ) ) { + Error( "\"%s\" is not callable as a 'sys' function", funcDef->Name() ); + } + + return EmitFunctionParms( OP_SYSCALL, funcDef, 0, 0, NULL ); +} + +/* +============ +idCompiler::LookupDef +============ +*/ +idVarDef *idCompiler::LookupDef( const char *name, const idVarDef *baseobj ) { + idVarDef *def; + idVarDef *field; + etype_t type_b; + etype_t type_c; + opcode_t *op; + + // check if we're accessing a field + if ( baseobj && ( baseobj->Type() == ev_object ) ) { + const idVarDef *tdef; + + def = NULL; + for( tdef = baseobj; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = gameLocal.program.GetDef( NULL, name, tdef ); + if ( def ) { + break; + } + } + } else { + // first look through the defs in our scope + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + // if we're in a member function, check types local to the object + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + + field = LookupDef( name, scope->scope->TypeDef()->def ); + if ( !field ) { + Error( "Unknown value \"%s\"", name ); + } + + // type check + type_b = field->Type(); + if ( field->Type() == ev_function ) { + type_c = field->TypeDef()->ReturnType()->Type(); + } else { + type_c = field->TypeDef()->FieldType()->Type(); // field access gets type from field + if ( CheckToken( "++" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for ++" ); + } + def = EmitOpcode( OP_UINCP_F, thisdef, field ); + return def; + } else if ( CheckToken( "--" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for --" ); + } + def = EmitOpcode( OP_UDECP_F, thisdef, field ); + return def; + } + } + + op = &opcodes[ OP_INDIRECT_F ]; + while( ( op->type_a->Type() != ev_object ) + || ( type_b != op->type_b->Type() ) || ( type_c != op->type_c->Type() ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && ( op->type_a->Type() == ev_object ) && ( op->type_c->Type() == ev_void ) && + ( type_c != op->type_c->Type() ) ) { + // catches object calls that return a value + break; + } + op++; + if ( !op->name || strcmp( op->name, "." ) ) { + Error( "no valid opcode to access type '%s'", field->TypeDef()->SuperClass()->Name() ); + } + } + + if ( ( op - opcodes ) == OP_OBJECTCALL ) { + ExpectToken( "(" ); + def = ParseObjectCall( thisdef, field ); + } else { + // emit the conversion opcode + def = EmitOpcode( op, thisdef, field ); + + // field access gets type from field + def->SetTypeDef( field->TypeDef()->FieldType() ); + } + } + } + } + + return def; +} + +/* +============ +idCompiler::ParseValue + +Returns the def for the current token +============ +*/ +idVarDef *idCompiler::ParseValue() { + idVarDef *def; + idVarDef *namespaceDef; + idStr name; + + if ( immediateType == &type_entity ) { + // if an immediate entity ($-prefaced name) then create or lookup a def for it. + // when entities are spawned, they'll lookup the def and point it to them. + def = gameLocal.program.GetDef( &type_entity, "$" + token, &def_namespace ); + if ( !def ) { + def = gameLocal.program.AllocDef( &type_entity, "$" + token, &def_namespace, true ); + } + NextToken(); + return def; + } else if ( immediateType ) { + // if the token is an immediate, allocate a constant for it + return ParseImmediate(); + } + + ParseName( name ); + def = LookupDef( name, basetype ); + if ( def == NULL ) { + if ( basetype ) { + Error( "%s is not a member of %s", name.c_str(), basetype->TypeDef()->Name() ); + } else { + Error( "Unknown value \"%s\"", name.c_str() ); + } + // if namespace, then look up the variable in that namespace + } else if ( def->Type() == ev_namespace ) { + while( def->Type() == ev_namespace ) { + ExpectToken( "::" ); + ParseName( name ); + namespaceDef = def; + def = gameLocal.program.GetDef( NULL, name, namespaceDef ); + if ( def == NULL ) { + if ( namespaceDef != NULL ) { + Error( "Unknown value \"%s::%s\"", namespaceDef->GlobalName(), name.c_str() ); + } else { + Error( "Unknown value \"%s\"", name.c_str() ); + } + break; + } + } + //def = LookupDef( name, basetype ); + } + + return def; +} + +/* +============ +idCompiler::GetTerm +============ +*/ +idVarDef *idCompiler::GetTerm() { + idVarDef *e; + int op; + + if ( !immediateType && CheckToken( "~" ) ) { + e = GetExpression( TILDE_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_COMP_F; + break; + + default : + // shut up compiler + op = OP_COMP_F; + Error( "type mismatch for ~" ); + } + + return EmitOpcode( op, e, 0 ); + } + + if ( !immediateType && CheckToken( "!" ) ) { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_boolean : + op = OP_NOT_BOOL; + break; + + case ev_float : + op = OP_NOT_F; + break; + + case ev_string : + op = OP_NOT_S; + break; + + case ev_vector : + op = OP_NOT_V; + break; + + case ev_entity : + op = OP_NOT_ENT; + break; + + case ev_function : + // shut up compiler + op = OP_NOT_F; + Error( "Invalid type for !" ); + break; + case ev_object : + op = OP_NOT_ENT; + break; + + default : + // shut up compiler + op = OP_NOT_F; + Error( "type mismatch for !" ); + } + + return EmitOpcode( op, e, 0 ); + } + + // check for negation operator + if ( !immediateType && CheckToken( "-" ) ) { + // constants are directly negated without an instruction + if ( immediateType == &type_float ) { + immediate._float = -immediate._float; + return ParseImmediate(); + } else if ( immediateType == &type_vector ) { + immediate.vector[0] = -immediate.vector[0]; + immediate.vector[1] = -immediate.vector[1]; + immediate.vector[2] = -immediate.vector[2]; + return ParseImmediate(); + } else { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_NEG_F; + break; + + case ev_vector : + op = OP_NEG_V; + break; + default : + // shut up compiler + op = OP_NEG_F; + Error( "type mismatch for -" ); + } + return EmitOpcode( &opcodes[ op ], e, 0 ); + } + } + + if ( CheckToken( "int" ) ) { + ExpectToken( "(" ); + + e = GetExpression( INT_PRIORITY ); + if ( e->Type() != ev_float ) { + Error( "type mismatch for int()" ); + } + + ExpectToken( ")" ); + + return EmitOpcode( OP_INT_F, e, 0 ); + } + + if ( CheckToken( "thread" ) ) { + callthread = true; + e = GetExpression( FUNCTION_PRIORITY ); + + if ( callthread ) { + Error( "Invalid thread call" ); + } + + // threads return the thread number + gameLocal.program.returnDef->SetTypeDef( &type_float ); + return gameLocal.program.returnDef; + } + + if ( !immediateType && CheckToken( "(" ) ) { + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + return e; + } + + return ParseValue(); +} + +/* +============== +idCompiler::TypeMatches +============== +*/ +bool idCompiler::TypeMatches( etype_t type1, etype_t type2 ) const { + if ( type1 == type2 ) { + return true; + } + + //if ( ( type1 == ev_entity ) && ( type2 == ev_object ) ) { + // return true; + //} + + //if ( ( type2 == ev_entity ) && ( type1 == ev_object ) ) { + // return true; + //} + + return false; +} + +/* +============== +idCompiler::GetExpression +============== +*/ +idVarDef *idCompiler::GetExpression( int priority ) { + opcode_t *op; + opcode_t *oldop; + idVarDef *e; + idVarDef *e2; + const idVarDef *oldtype; + etype_t type_a; + etype_t type_b; + etype_t type_c; + + if ( priority == 0 ) { + return GetTerm(); + } + + e = GetExpression( priority - 1 ); + if ( token == ";" ) { + // save us from searching through the opcodes unneccesarily + return e; + } + + while( 1 ) { + if ( ( priority == FUNCTION_PRIORITY ) && CheckToken( "(" ) ) { + return ParseFunctionCall( e ); + } + + // has to be a punctuation + if ( immediateType ) { + break; + } + + for( op = opcodes; op->name; op++ ) { + if ( ( op->priority == priority ) && CheckToken( op->name ) ) { + break; + } + } + + if ( !op->name ) { + // next token isn't at this priority level + break; + } + + // unary operators act only on the left operand + if ( op->type_b == &def_void ) { + e = EmitOpcode( op, e, 0 ); + return e; + } + + // preserve our base type + oldtype = basetype; + + // field access needs scope from object + if ( ( op->name[ 0 ] == '.' ) && e->TypeDef()->Inherits( &type_object ) ) { + // save off what type this field is part of + basetype = e->TypeDef()->def; + } + + if ( op->rightAssociative ) { + // if last statement is an indirect, change it to an address of + if ( gameLocal.program.NumStatements() > 0 ) { + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + if ( ( statement.op >= OP_INDIRECT_F ) && ( statement.op < OP_ADDRESS ) ) { + statement.op = OP_ADDRESS; + type_pointer.SetPointerType( e->TypeDef() ); + e->SetTypeDef( &type_pointer ); + } + } + + e2 = GetExpression( priority ); + } else { + e2 = GetExpression( priority - 1 ); + } + + // restore type + basetype = oldtype; + + // type check + type_a = e->Type(); + type_b = e2->Type(); + + // field access gets type from field + if ( op->name[ 0 ] == '.' ) { + if ( ( e2->Type() == ev_function ) && e2->TypeDef()->ReturnType() ) { + type_c = e2->TypeDef()->ReturnType()->Type(); + } else if ( e2->TypeDef()->FieldType() ) { + type_c = e2->TypeDef()->FieldType()->Type(); + } else { + // not a field + type_c = ev_error; + } + } else { + type_c = ev_void; + } + + oldop = op; + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) || + ( ( type_c != ev_void ) && !TypeMatches( type_c, op->type_c->Type() ) ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && TypeMatches( type_a, op->type_a->Type() ) && TypeMatches( type_b, op->type_b->Type() ) ) { + break; + } + + op++; + if ( !op->name || strcmp( op->name, oldop->name ) ) { + Error( "type mismatch for '%s'", oldop->name ); + } + } + + switch( op - opcodes ) { + case OP_SYSCALL : + ExpectToken( "(" ); + e = ParseSysObjectCall( e2 ); + break; + + case OP_OBJECTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + case OP_EVENTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + default: + if ( callthread ) { + Error( "Expecting function call after 'thread'" ); + } + + if ( ( type_a == ev_pointer ) && ( type_b != e->TypeDef()->PointerType()->Type() ) ) { + // FIXME: need to make a general case for this + if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_boolean ) ) { + // copy from float to boolean pointer + op = &opcodes[ OP_STOREP_FTOBOOL ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_float ) ) { + // copy from boolean to float pointer + op = &opcodes[ OP_STOREP_BOOLTOF ]; + } else if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from float to string pointer + op = &opcodes[ OP_STOREP_FTOS ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from boolean to string pointer + op = &opcodes[ OP_STOREP_BTOS ]; + } else if ( ( op - opcodes == OP_STOREP_V ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from vector to string pointer + op = &opcodes[ OP_STOREP_VTOS ]; + } else if ( ( op - opcodes == OP_STOREP_ENT ) && ( e->TypeDef()->PointerType()->Type() == ev_object ) ) { + // store an entity into an object pointer + op = &opcodes[ OP_STOREP_OBJENT ]; + } else { + Error( "type mismatch for '%s'", op->name ); + } + } + + if ( op->rightAssociative ) { + e = EmitOpcode( op, e2, e ); + } else { + e = EmitOpcode( op, e, e2 ); + } + + if ( op - opcodes == OP_STOREP_OBJENT ) { + // statement.b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in statement.c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = type_pointer.PointerType()->def; + } + + // field access gets type from field + if ( type_c != ev_void ) { + e->SetTypeDef( e2->TypeDef()->FieldType() ); + } + break; + } + } + + return e; +} + +/* +================ +idCompiler::PatchLoop +================ +*/ +void idCompiler::PatchLoop( int start, int continuePos ) { + int i; + statement_t *pos; + + pos = &gameLocal.program.GetStatement( start ); + for( i = start; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_BREAK ) { + pos->op = OP_GOTO; + pos->a = JumpFrom( i ); + } else if ( pos->op == OP_CONTINUE ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, continuePos ); + } + } +} + +/* +================ +idCompiler::ParseReturnStatement +================ +*/ +void idCompiler::ParseReturnStatement() { + idVarDef *e; + etype_t type_a; + etype_t type_b; + opcode_t *op; + + if ( CheckToken( ";" ) ) { + if ( scope->TypeDef()->ReturnType()->Type() != ev_void ) { + Error( "expecting return value" ); + } + + EmitOpcode( OP_RETURN, 0, 0 ); + return; + } + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + type_a = e->Type(); + type_b = scope->TypeDef()->ReturnType()->Type(); + + if ( TypeMatches( type_a, type_b ) ) { + EmitOpcode( OP_RETURN, e, 0 ); + return; + } + + for( op = opcodes; op->name; op++ ) { + if ( !strcmp( op->name, "=" ) ) { + break; + } + } + + assert( op->name ); + + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ) { + op++; + if ( !op->name || strcmp( op->name, "=" ) ) { + Error( "type mismatch for return value" ); + } + } + + idTypeDef *returnType = scope->TypeDef()->ReturnType(); + if ( returnType->Type() == ev_string ) { + EmitOpcode( op, e, gameLocal.program.returnStringDef ); + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + EmitOpcode( op, e, gameLocal.program.returnDef ); + } + EmitOpcode( OP_RETURN, 0, 0 ); +} + +/* +================ +idCompiler::ParseWhileStatement +================ +*/ +void idCompiler::ParseWhileStatement() { + idVarDef *e; + int patch1; + int patch2; + + loopDepth++; + + ExpectToken( "(" ); + + patch2 = gameLocal.program.NumStatements(); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + if ( ( e->initialized == idVarDef::initializedConstant ) && ( *e->value.intPtr != 0 ) ) { + //FIXME: we can completely skip generation of this code in the opposite case + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + } else { + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } + + // fixup breaks and continues + PatchLoop( patch2, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseForStatement + +Form of for statement with a counter: + + a = 0; +start: << patch4 + if ( !( a < 10 ) ) { + goto end; << patch1 + } else { + goto process; << patch3 + } + +increment: << patch2 + a = a + 1; + goto start; << goto patch4 + +process: + statements; + goto increment; << goto patch2 + +end: + +Form of for statement without a counter: + + a = 0; +start: << patch2 + if ( !( a < 10 ) ) { + goto end; << patch1 + } + +process: + statements; + goto start; << goto patch2 + +end: +================ +*/ +void idCompiler::ParseForStatement() { + idVarDef *e; + int start; + int patch1; + int patch2; + int patch3; + int patch4; + + loopDepth++; + + start = gameLocal.program.NumStatements(); + + ExpectToken( "(" ); + + // init + if ( !CheckToken( ";" ) ) { + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ";" ); + } + + // condition + patch2 = gameLocal.program.NumStatements(); + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + // counter + if ( !CheckToken( ")" ) ) { + patch3 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IF, e, 0 ); + + patch4 = patch2; + patch2 = gameLocal.program.NumStatements(); + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + + // goto patch4 + EmitOpcode( OP_GOTO, JumpTo( patch4 ), 0 ); + + // fixup patch3 + gameLocal.program.GetStatement( patch3 ).b = JumpFrom( patch3 ); + } + + ParseStatement(); + + // goto patch2 + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + + // fixup patch1 + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + + // fixup breaks and continues + PatchLoop( start, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseDoWhileStatement +================ +*/ +void idCompiler::ParseDoWhileStatement() { + idVarDef *e; + int patch1; + + loopDepth++; + + patch1 = gameLocal.program.NumStatements(); + ParseStatement(); + ExpectToken( "while" ); + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + ExpectToken( ";" ); + + EmitOpcode( OP_IF, e, JumpTo( patch1 ) ); + + // fixup breaks and continues + PatchLoop( patch1, patch1 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseIfStatement +================ +*/ +void idCompiler::ParseIfStatement() { + idVarDef *e; + int patch1; + int patch2; + + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + ParseStatement(); + + if ( CheckToken( "else" ) ) { + patch2 = gameLocal.program.NumStatements(); + EmitOpcode( OP_GOTO, 0, 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + ParseStatement(); + gameLocal.program.GetStatement( patch2 ).a = JumpFrom( patch2 ); + } else { + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } +} + +/* +============ +idCompiler::ParseStatement +============ +*/ +void idCompiler::ParseStatement() { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + if ( CheckToken( "{" ) ) { + do { + ParseStatement(); + } while( !CheckToken( "}" ) ); + + return; + } + + if ( CheckToken( "return" ) ) { + ParseReturnStatement(); + return; + } + + if ( CheckToken( "while" ) ) { + ParseWhileStatement(); + return; + } + + if ( CheckToken( "for" ) ) { + ParseForStatement(); + return; + } + + if ( CheckToken( "do" ) ) { + ParseDoWhileStatement(); + return; + } + + if ( CheckToken( "break" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot break outside of a loop" ); + } + EmitOpcode( OP_BREAK, 0, 0 ); + return; + } + + if ( CheckToken( "continue" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot contine outside of a loop" ); + } + EmitOpcode( OP_CONTINUE, 0, 0 ); + return; + } + + if ( CheckType() != NULL ) { + ParseDefs(); + return; + } + + if ( CheckToken( "if" ) ) { + ParseIfStatement(); + return; + } + + GetExpression( TOP_PRIORITY ); + ExpectToken(";"); +} + +/* +================ +idCompiler::ParseObjectDef +================ +*/ +void idCompiler::ParseObjectDef( const char *objname ) { + idTypeDef *objtype; + idTypeDef *type; + idTypeDef *parentType; + idTypeDef *fieldtype; + idStr name; + const char *fieldname; + idTypeDef newtype( ev_field, NULL, "", 0, NULL ); + idVarDef *oldscope; + int num; + int i; + + oldscope = scope; + if ( scope->Type() != ev_namespace ) { + Error( "Objects cannot be defined within functions or other objects" ); + } + + // make sure it doesn't exist before we create it + if ( gameLocal.program.FindType( objname ) != NULL ) { + Error( "'%s' : redefinition; different basic types", objname ); + } + + // base type + if ( !CheckToken( ":" ) ) { + parentType = &type_object; + } else { + parentType = ParseType(); + if ( !parentType->Inherits( &type_object ) ) { + Error( "Objects may only inherit from objects." ); + } + } + + objtype = gameLocal.program.AllocType( ev_object, NULL, objname, parentType == &type_object ? 0 : parentType->Size(), parentType ); + objtype->def = gameLocal.program.AllocDef( objtype, objname, scope, true ); + scope = objtype->def; + + // inherit all the functions + num = parentType->NumFunctions(); + for( i = 0; i < parentType->NumFunctions(); i++ ) { + const function_t *func = parentType->GetFunction( i ); + objtype->AddFunction( func ); + } + + ExpectToken( "{" ); + + do { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + continue; + } + + fieldtype = ParseType(); + newtype.SetFieldType( fieldtype ); + + fieldname = va( "%s field", fieldtype->Name() ); + newtype.SetName( fieldname ); + + ParseName( name ); + + // check for a function prototype or declaraction + if ( CheckToken( "(" ) ) { + ParseFunctionDef( newtype.FieldType(), name ); + } else { + type = gameLocal.program.GetType( newtype, true ); + assert( !type->def ); + gameLocal.program.AllocDef( type, name, scope, true ); + objtype->AddField( type, name ); + ExpectToken( ";" ); + } + } while( !CheckToken( "}" ) ); + + scope = oldscope; + + ExpectToken( ";" ); +} + +/* +============ +idCompiler::ParseFunction + +parse a function type +============ +*/ +idTypeDef *idCompiler::ParseFunction( idTypeDef *returnType, const char *name ) { + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + idTypeDef *type; + + if ( scope->Type() != ev_namespace ) { + // create self pointer + newtype.AddFunctionParm( scope->TypeDef(), "self" ); + } + + if ( !CheckToken( ")" ) ) { + idStr parmName; + do { + type = ParseType(); + ParseName( parmName ); + newtype.AddFunctionParm( type, parmName ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + return gameLocal.program.GetType( newtype, true ); +} + +/* +================ +idCompiler::ParseFunctionDef +================ +*/ +void idCompiler::ParseFunctionDef( idTypeDef *returnType, const char *name ) { + idTypeDef *type; + idVarDef *def; + const idVarDef *parm; + idVarDef *oldscope; + int i; + int numParms; + const idTypeDef *parmType; + function_t *func; + statement_t *pos; + + if ( ( scope->Type() != ev_namespace ) && !scope->TypeDef()->Inherits( &type_object ) ) { + Error( "Functions may not be defined within other functions" ); + } + + type = ParseFunction( returnType, name ); + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + type->def = def; + + func = &gameLocal.program.AllocFunction( def ); + if ( scope->TypeDef()->Inherits( &type_object ) ) { + scope->TypeDef()->AddFunction( func ); + } + } else { + func = def->value.functionPtr; + assert( func ); + if ( func->firstStatement ) { + Error( "%s redeclared", def->GlobalName() ); + } + } + + // check if this is a prototype or declaration + if ( !CheckToken( "{" ) ) { + // it's just a prototype, so get the ; and move on + ExpectToken( ";" ); + return; + } + + // calculate stack space used by parms + numParms = type->NumParameters(); + func->parmSize.SetNum( numParms ); + for( i = 0; i < numParms; i++ ) { + parmType = type->GetParmType( i ); + if ( parmType->Inherits( &type_object ) ) { + func->parmSize[ i ] = type_object.Size(); + } else { + func->parmSize[ i ] = parmType->Size(); + } + func->parmTotal += func->parmSize[ i ]; + } + + // define the parms + for( i = 0; i < numParms; i++ ) { + if ( gameLocal.program.GetDef( type->GetParmType( i ), type->GetParmName( i ), def ) ) { + Error( "'%s' defined more than once in function parameters", type->GetParmName( i ) ); + } + parm = gameLocal.program.AllocDef( type->GetParmType( i ), type->GetParmName( i ), def, false ); + } + + oldscope = scope; + scope = def; + + func->firstStatement = gameLocal.program.NumStatements(); + + // check if we should call the super class constructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !idStr::Icmp( name, "init" ) ) { + idTypeDef *superClass; + function_t *constructorFunc = NULL; + + // find the superclass constructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + constructorFunc = gameLocal.program.FindFunction( va( "%s::init", superClass->Name() ) ); + if ( constructorFunc ) { + break; + } + } + + // emit the call to the constructor + if ( constructorFunc ) { + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], constructorFunc->def, 0 ); + } + } + + // parse regular statements + while( !CheckToken( "}" ) ) { + ParseStatement(); + } + + // check if we should call the super class destructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !idStr::Icmp( name, "destroy" ) ) { + idTypeDef *superClass; + function_t *destructorFunc = NULL; + + // find the superclass destructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + destructorFunc = gameLocal.program.FindFunction( va( "%s::destroy", superClass->Name() ) ); + if ( destructorFunc ) { + break; + } + } + + if ( destructorFunc ) { + if ( func->firstStatement < gameLocal.program.NumStatements() ) { + // change all returns to point to the call to the destructor + pos = &gameLocal.program.GetStatement( func->firstStatement ); + for( i = func->firstStatement; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_RETURN ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, gameLocal.program.NumStatements() ); + } + } + } + + // emit the call to the destructor + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], destructorFunc->def, 0 ); + } + } + +// Disabled code since it caused a function to fall through to the next function when last statement is in the form "if ( x ) { return; }" +#if 0 + // don't bother adding a return opcode if the "return" statement was used. + if ( ( func->firstStatement == gameLocal.program.NumStatements() ) || ( gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ).op != OP_RETURN ) ) { + // emit an end of statements opcode + EmitOpcode( OP_RETURN, 0, 0 ); + } +#else + // always emit the return opcode + EmitOpcode( OP_RETURN, 0, 0 ); +#endif + + // record the number of statements in the function + func->numStatements = gameLocal.program.NumStatements() - func->firstStatement; + + scope = oldscope; +} + +/* +================ +idCompiler::ParseVariableDef +================ +*/ +void idCompiler::ParseVariableDef( idTypeDef *type, const char *name ) { + idVarDef *def, *def2; + bool negate; + + def = gameLocal.program.GetDef( type, name, scope ); + if ( def ) { + Error( "%s redeclared", name ); + } + + def = gameLocal.program.AllocDef( type, name, scope, false ); + + // check for an initialization + if ( CheckToken( "=" ) ) { + // if a local variable in a function then write out interpreter code to initialize variable + if ( scope->Type() == ev_function ) { + def2 = GetExpression( TOP_PRIORITY ); + if ( ( type == &type_float ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_F, def2, def ); + } else if ( ( type == &type_vector ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_V, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_string ) ) { + EmitOpcode( OP_STORE_S, def2, def ); + } else if ( ( type == &type_entity ) && ( ( def2->TypeDef() == &type_entity ) || ( def2->TypeDef()->Inherits( &type_object ) ) ) ) { + EmitOpcode( OP_STORE_ENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef() == &type_entity ) ) { + EmitOpcode( OP_STORE_OBJENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef()->Inherits( type ) ) ) { + EmitOpcode( OP_STORE_OBJ, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOL, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_VTOS, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOBOOL, def2, def ); + } else if ( ( type == &type_float ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOLTOF, def2, def ); + } else { + Error( "bad initialization for '%s'", name ); + } + } else { + // global variables can only be initialized with immediate values + negate = false; + if ( token.type == TT_PUNCTUATION && token == "-" ) { + negate = true; + NextToken(); + if ( immediateType != &type_float ) { + Error( "wrong immediate type for '-' on variable '%s'", name ); + } + } + + if ( immediateType != type ) { + Error( "wrong immediate type for '%s'", name ); + } + + // global variables are initialized at start up + if ( type == &type_string ) { + def->SetString( token, false ); + } else { + if ( negate ) { + immediate._float = -immediate._float; + } + def->SetValue( immediate, false ); + } + NextToken(); + } + } else if ( type == &type_string ) { + // local strings on the stack are initialized in the interpreter + if ( scope->Type() != ev_function ) { + def->SetString( "", false ); + } + } else if ( type->Inherits( &type_object ) ) { + if ( scope->Type() != ev_function ) { + def->SetObject( NULL ); + } + } +} + +/* +================ +idCompiler::GetTypeForEventArg +================ +*/ +idTypeDef *idCompiler::GetTypeForEventArg( char argType ) { + idTypeDef *type; + + switch( argType ) { + case D_EVENT_INTEGER : + // this will get converted to int by the interpreter + type = &type_float; + break; + + case D_EVENT_FLOAT : + type = &type_float; + break; + + case D_EVENT_VECTOR : + type = &type_vector; + break; + + case D_EVENT_STRING : + type = &type_string; + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + type = &type_entity; + break; + + case D_EVENT_VOID : + type = &type_void; + break; + + case D_EVENT_TRACE : + // This data type isn't available from script + type = NULL; + break; + + default: + // probably a typo + type = NULL; + break; + } + + return type; +} + +/* +================ +idCompiler::ParseEventDef +================ +*/ +void idCompiler::ParseEventDef( idTypeDef *returnType, const char *name ) { + const idTypeDef *expectedType; + idTypeDef *argType; + idTypeDef *type; + int i; + int num; + const char *format; + const idEventDef *ev; + idStr parmName; + + ev = idEventDef::FindEvent( name ); + if ( ev == NULL ) { + Error( "Unknown event '%s'", name ); + return; + } + + // set the return type + expectedType = GetTypeForEventArg( ev->GetReturnType() ); + if ( expectedType == NULL ) { + Error( "Invalid return type '%c' in definition of '%s' event.", ev->GetReturnType(), name ); + return; + } + if ( returnType != expectedType ) { + Error( "Return type doesn't match internal return type '%s'", expectedType->Name() ); + } + + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + + ExpectToken( "(" ); + + format = ev->GetArgFormat(); + num = strlen( format ); + for( i = 0; i < num; i++ ) { + expectedType = GetTypeForEventArg( format[ i ] ); + if ( expectedType == NULL || ( expectedType == &type_void ) ) { + Error( "Invalid parameter '%c' in definition of '%s' event.", format[ i ], name ); + return; + } + + argType = ParseType(); + ParseName( parmName ); + if ( argType != expectedType ) { + Error( "The type of parm %d ('%s') does not match the internal type '%s' in definition of '%s' event.", + i + 1, parmName.c_str(), expectedType->Name(), name ); + } + + newtype.AddFunctionParm( argType, "" ); + + if ( i < num - 1 ) { + if ( CheckToken( ")" ) ) { + Error( "Too few parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( "," ); + } + } + if ( !CheckToken( ")" ) ) { + Error( "Too many parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( ";" ); + + type = gameLocal.program.FindType( name ); + if ( type ) { + if ( !newtype.MatchesType( *type ) || ( type->def->value.functionPtr->eventdef != ev ) ) { + Error( "Type mismatch on redefinition of '%s'", name ); + } + } else { + type = gameLocal.program.AllocType( newtype ); + type->def = gameLocal.program.AllocDef( type, name, &def_namespace, true ); + + function_t &func = gameLocal.program.AllocFunction( type->def ); + func.eventdef = ev; + func.parmSize.SetNum( num ); + for( i = 0; i < num; i++ ) { + argType = newtype.GetParmType( i ); + func.parmTotal += argType->Size(); + func.parmSize[ i ] = argType->Size(); + } + + // mark the parms as local + func.locals = func.parmTotal; + } +} + +/* +================ +idCompiler::ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void idCompiler::ParseDefs() { + idStr name; + idTypeDef *type; + idVarDef *def; + idVarDef *oldscope; + + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + type = ParseType(); + if ( type == &type_scriptevent ) { + type = ParseType(); + ParseName( name ); + ParseEventDef( type, name ); + return; + } + + ParseName( name ); + + if ( type == &type_namespace ) { + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + } + ParseNamespace( def ); + } else if ( CheckToken( "::" ) ) { + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + Error( "Unknown object name '%s'", name.c_str() ); + } + ParseName( name ); + oldscope = scope; + scope = def; + + ExpectToken( "(" ); + ParseFunctionDef( type, name.c_str() ); + scope = oldscope; + } else if ( type == &type_object ) { + ParseObjectDef( name.c_str() ); + } else if ( CheckToken( "(" ) ) { // check for a function prototype or declaraction + ParseFunctionDef( type, name.c_str() ); + } else { + ParseVariableDef( type, name.c_str() ); + while( CheckToken( "," ) ) { + ParseName( name ); + ParseVariableDef( type, name.c_str() ); + } + ExpectToken( ";" ); + } +} + +/* +================ +idCompiler::ParseNamespace + +Parses anything within a namespace definition +================ +*/ +void idCompiler::ParseNamespace( idVarDef *newScope ) { + idVarDef *oldscope; + + oldscope = scope; + if ( newScope != &def_namespace ) { + ExpectToken( "{" ); + } + + while( !eof ) { + scope = newScope; + callthread = false; + + if ( ( newScope != &def_namespace ) && CheckToken( "}" ) ) { + break; + } + + ParseDefs(); + } + + scope = oldscope; +} + +/* +============ +idCompiler::CompileFile + +compiles the 0 terminated text, adding definitions to the program structure +============ +*/ +void idCompiler::CompileFile( const char *text, const char *filename, bool toConsole ) { + idTimer compile_time; + bool error; + + compile_time.Start(); + + scope = &def_namespace; + basetype = NULL; + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + currentLineNumber = 0; + console = toConsole; + + memset( &immediate, 0, sizeof( immediate ) ); + + parser.SetFlags( LEXFL_ALLOWMULTICHARLITERALS ); + parser.LoadMemory( text, strlen( text ), filename ); + parserPtr = &parser; + + // unread tokens to include script defines + token = SCRIPT_DEFAULTDEFS; + token.type = TT_STRING; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "include"; + token.type = TT_NAME; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "#"; + token.type = TT_PUNCTUATION; + token.subtype = P_PRECOMP; + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + // init the current token line to be the first line so that currentLineNumber is set correctly in NextToken + token.line = 1; + + error = false; + try { + // read first token + NextToken(); + while( !eof && !error ) { + // parse from global namespace + ParseNamespace( &def_namespace ); + } + } + + catch( idCompileError &err ) { + idStr error; + + if ( console ) { + // don't print line number of an error if were calling script from the console using the "script" command + sprintf( error, "Error: %s\n", err.GetError() ); + } else { + sprintf( error, "Error: file %s, line %d: %s\n", gameLocal.program.GetFilename( currentFileNumber ), currentLineNumber, err.GetError() ); + } + + parser.FreeSource(); + + throw idCompileError( error ); + } + + parser.FreeSource(); + + compile_time.Stop(); + if ( !toConsole ) { + gameLocal.Printf( "Compiled '%s': %.1f ms\n", filename, compile_time.Milliseconds() ); + } +} diff --git a/neo/d3xp/script/Script_Compiler.h b/neo/d3xp/script/Script_Compiler.h new file mode 100644 index 00000000..743f6371 --- /dev/null +++ b/neo/d3xp/script/Script_Compiler.h @@ -0,0 +1,278 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SCRIPT_COMPILER_H__ +#define __SCRIPT_COMPILER_H__ + +const char * const RESULT_STRING = ""; + +typedef struct opcode_s { + char *name; + char *opname; + int priority; + bool rightAssociative; + idVarDef *type_a; + idVarDef *type_b; + idVarDef *type_c; +} opcode_t; + +// These opcodes are no longer necessary: +// OP_PUSH_OBJ: +// OP_PUSH_OBJENT: + +enum { + OP_RETURN, + + OP_UINC_F, + OP_UINCP_F, + OP_UDEC_F, + OP_UDECP_F, + OP_COMP_F, + + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_MOD_F, + OP_ADD_F, + OP_ADD_V, + OP_ADD_S, + OP_ADD_FS, + OP_ADD_SF, + OP_ADD_VS, + OP_ADD_SV, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_EO, + OP_EQ_OE, + OP_EQ_OO, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_EO, + OP_NE_OE, + OP_NE_OO, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_INDIRECT_F, + OP_INDIRECT_V, + OP_INDIRECT_S, + OP_INDIRECT_ENT, + OP_INDIRECT_BOOL, + OP_INDIRECT_OBJ, + + OP_ADDRESS, + + OP_EVENTCALL, + OP_OBJECTCALL, + OP_SYSCALL, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_BOOL, + OP_STORE_OBJENT, + OP_STORE_OBJ, + OP_STORE_ENTOBJ, + + OP_STORE_FTOS, + OP_STORE_BTOS, + OP_STORE_VTOS, + OP_STORE_FTOBOOL, + OP_STORE_BOOLTOF, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_BOOL, + OP_STOREP_OBJ, + OP_STOREP_OBJENT, + + OP_STOREP_FTOS, + OP_STOREP_BTOS, + OP_STOREP_VTOS, + OP_STOREP_FTOBOOL, + OP_STOREP_BOOLTOF, + + OP_UMUL_F, + OP_UMUL_V, + OP_UDIV_F, + OP_UDIV_V, + OP_UMOD_F, + OP_UADD_F, + OP_UADD_V, + OP_USUB_F, + OP_USUB_V, + OP_UAND_F, + OP_UOR_F, + + OP_NOT_BOOL, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + + OP_NEG_F, + OP_NEG_V, + + OP_INT_F, + OP_IF, + OP_IFNOT, + + OP_CALL, + OP_THREAD, + OP_OBJTHREAD, + + OP_PUSH_F, + OP_PUSH_V, + OP_PUSH_S, + OP_PUSH_ENT, + OP_PUSH_OBJ, + OP_PUSH_OBJENT, + OP_PUSH_FTOS, + OP_PUSH_BTOF, + OP_PUSH_FTOB, + OP_PUSH_VTOS, + OP_PUSH_BTOS, + + OP_GOTO, + + OP_AND, + OP_AND_BOOLF, + OP_AND_FBOOL, + OP_AND_BOOLBOOL, + OP_OR, + OP_OR_BOOLF, + OP_OR_FBOOL, + OP_OR_BOOLBOOL, + + OP_BITAND, + OP_BITOR, + + OP_BREAK, // placeholder op. not used in final code + OP_CONTINUE, // placeholder op. not used in final code + + NUM_OPCODES +}; + +class idCompiler { +private: + static bool punctuationValid[ 256 ]; + static char *punctuation[]; + + idParser parser; + idParser *parserPtr; + idToken token; + + idTypeDef *immediateType; + eval_t immediate; + + bool eof; + bool console; + bool callthread; + int braceDepth; + int loopDepth; + int currentLineNumber; + int currentFileNumber; + int errorCount; + + idVarDef *scope; // the function being parsed, or NULL + const idVarDef *basetype; // for accessing fields + + float Divide( float numerator, float denominator ); + void Error( VERIFY_FORMAT_STRING const char *error, ... ) const; + void Warning( VERIFY_FORMAT_STRING const char *message, ... ) const; + idVarDef *OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ); + bool EmitPush( idVarDef *expression, const idTypeDef *funcArg ); + void NextToken(); + void ExpectToken( const char *string ); + bool CheckToken( const char *string ); + void ParseName( idStr &name ); + void SkipOutOfFunction(); + void SkipToSemicolon(); + idTypeDef *CheckType(); + idTypeDef *ParseType(); + idVarDef *FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const; + idVarDef *GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ); + idVarDef *VirtualFunctionConstant( idVarDef *func ); + idVarDef *SizeConstant( int size ); + idVarDef *JumpConstant( int value ); + idVarDef *JumpDef( int jumpfrom, int jumpto ); + idVarDef *JumpTo( int jumpto ); + idVarDef *JumpFrom( int jumpfrom ); + idVarDef *ParseImmediate(); + idVarDef *EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ); + idVarDef *ParseFunctionCall( idVarDef *func ); + idVarDef *ParseObjectCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseEventCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseSysObjectCall( idVarDef *func ); + idVarDef *LookupDef( const char *name, const idVarDef *baseobj ); + idVarDef *ParseValue(); + idVarDef *GetTerm(); + bool TypeMatches( etype_t type1, etype_t type2 ) const; + idVarDef *GetExpression( int priority ); + idTypeDef *GetTypeForEventArg( char argType ); + void PatchLoop( int start, int continuePos ); + void ParseReturnStatement(); + void ParseWhileStatement(); + void ParseForStatement(); + void ParseDoWhileStatement(); + void ParseIfStatement(); + void ParseStatement(); + void ParseObjectDef( const char *objname ); + idTypeDef *ParseFunction( idTypeDef *returnType, const char *name ); + void ParseFunctionDef( idTypeDef *returnType, const char *name ); + void ParseVariableDef( idTypeDef *type, const char *name ); + void ParseEventDef( idTypeDef *type, const char *name ); + void ParseDefs(); + void ParseNamespace( idVarDef *newScope ); + +public : + static opcode_t opcodes[]; + + idCompiler(); + void CompileFile( const char *text, const char *filename, bool console ); +}; + +#endif /* !__SCRIPT_COMPILER_H__ */ diff --git a/neo/d3xp/script/Script_Interpreter.cpp b/neo/d3xp/script/Script_Interpreter.cpp new file mode 100644 index 00000000..519d0b55 --- /dev/null +++ b/neo/d3xp/script/Script_Interpreter.cpp @@ -0,0 +1,1842 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +/* +================ +idInterpreter::idInterpreter() +================ +*/ +idInterpreter::idInterpreter() { + localstackUsed = 0; + terminateOnExit = true; + debug = 0; + memset( localstack, 0, sizeof( localstack ) ); + memset( callStack, 0, sizeof( callStack ) ); + Reset(); +} + +/* +================ +idInterpreter::Save +================ +*/ +void idInterpreter::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->WriteInt( callStack[i].s ); + if ( callStack[i].f ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( callStack[i].f ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( callStack[i].stackbase ); + } + savefile->WriteInt( maxStackDepth ); + + savefile->WriteInt( localstackUsed ); + savefile->Write( &localstack, localstackUsed ); + + savefile->WriteInt( localstackBase ); + savefile->WriteInt( maxLocalstackUsed ); + + if ( currentFunction ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( currentFunction ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( instructionPointer ); + + savefile->WriteInt( popParms ); + + if ( multiFrameEvent ) { + savefile->WriteString( multiFrameEvent->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteObject( eventEntity ); + + savefile->WriteObject( thread ); + + savefile->WriteBool( doneProcessing ); + savefile->WriteBool( threadDying ); + savefile->WriteBool( terminateOnExit ); + savefile->WriteBool( debug ); +} + +/* +================ +idInterpreter::Restore +================ +*/ +void idInterpreter::Restore( idRestoreGame *savefile ) { + int i; + idStr funcname; + int func_index; + + savefile->ReadInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->ReadInt( callStack[i].s ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + callStack[i].f = gameLocal.program.GetFunction( func_index ); + } else { + callStack[i].f = NULL; + } + + savefile->ReadInt( callStack[i].stackbase ); + } + savefile->ReadInt( maxStackDepth ); + + savefile->ReadInt( localstackUsed ); + savefile->Read( &localstack, localstackUsed ); + + savefile->ReadInt( localstackBase ); + savefile->ReadInt( maxLocalstackUsed ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + currentFunction = gameLocal.program.GetFunction( func_index ); + } else { + currentFunction = NULL; + } + savefile->ReadInt( instructionPointer ); + + savefile->ReadInt( popParms ); + + savefile->ReadString( funcname ); + if ( funcname.Length() ) { + multiFrameEvent = idEventDef::FindEvent( funcname ); + } + + savefile->ReadObject( reinterpret_cast( eventEntity ) ); + savefile->ReadObject( reinterpret_cast( thread ) ); + + savefile->ReadBool( doneProcessing ); + savefile->ReadBool( threadDying ); + savefile->ReadBool( terminateOnExit ); + savefile->ReadBool( debug ); +} + +/* +================ +idInterpreter::Reset +================ +*/ +void idInterpreter::Reset() { + callStackDepth = 0; + localstackUsed = 0; + localstackBase = 0; + + maxLocalstackUsed = 0; + maxStackDepth = 0; + + popParms = 0; + multiFrameEvent = NULL; + eventEntity = NULL; + + currentFunction = 0; + NextInstruction( 0 ); + + threadDying = false; + doneProcessing = true; +} + +/* +================ +idInterpreter::GetRegisterValue + +Returns a string representation of the value of the register. This is +used primarily for the debugger and debugging + +//FIXME: This is pretty much wrong. won't access data in most situations. +================ +*/ +bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { + varEval_t reg; + idVarDef *d; + char funcObject[ 1024 ]; + char *funcName; + const idVarDef *scope; + const idTypeDef *field; + const idScriptObject *obj; + const function_t *func; + + out.Empty(); + + if ( scopeDepth == -1 ) { + scopeDepth = callStackDepth; + } + + if ( scopeDepth == callStackDepth ) { + func = currentFunction; + } else { + func = callStack[ scopeDepth ].f; + } + if ( !func ) { + return false; + } + + idStr::Copynz( funcObject, func->Name(), sizeof( funcObject ) ); + funcName = strstr( funcObject, "::" ); + if ( funcName ) { + *funcName = '\0'; + scope = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + funcName += 2; + } else { + funcName = funcObject; + scope = &def_namespace; + } + + // Get the function from the object + d = gameLocal.program.GetDef( NULL, funcName, scope ); + if ( !d ) { + return false; + } + + // Get the variable itself and check various namespaces + d = gameLocal.program.GetDef( NULL, name, d ); + if ( !d ) { + if ( scope == &def_namespace ) { + return false; + } + + d = gameLocal.program.GetDef( NULL, name, scope ); + if ( !d ) { + d = gameLocal.program.GetDef( NULL, name, &def_namespace ); + if ( !d ) { + return false; + } + } + } + + reg = GetVariable( d ); + switch( d->Type() ) { + case ev_float: + if ( reg.floatPtr ) { + out = va("%g", *reg.floatPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_vector: + if ( reg.vectorPtr ) { + out = va( "%g,%g,%g", reg.vectorPtr->x, reg.vectorPtr->y, reg.vectorPtr->z ); + } else { + out = "0,0,0"; + } + return true; + break; + + case ev_boolean: + if ( reg.intPtr ) { + out = va( "%d", *reg.intPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_field: + if ( scope == &def_namespace ) { + // should never happen, but handle it safely anyway + return false; + } + + field = scope->TypeDef()->GetParmType( reg.ptrOffset )->FieldType(); + obj = *reinterpret_cast( &localstack[ callStack[ callStackDepth ].stackbase ] ); + if ( !field || !obj ) { + return false; + } + + switch ( field->Type() ) { + case ev_boolean: + out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_float: + out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + default: + return false; + } + break; + + case ev_string: + if ( reg.stringPtr ) { + out = "\""; + out += reg.stringPtr; + out += "\""; + } else { + out = "\"\""; + } + return true; + + default: + return false; + } +} + +/* +================ +idInterpreter::GetCallstackDepth +================ +*/ +int idInterpreter::GetCallstackDepth() const { + return callStackDepth; +} + +/* +================ +idInterpreter::GetCallstack +================ +*/ +const prstack_t *idInterpreter::GetCallstack() const { + return &callStack[ 0 ]; +} + +/* +================ +idInterpreter::GetCurrentFunction +================ +*/ +const function_t *idInterpreter::GetCurrentFunction() const { + return currentFunction; +} + +/* +================ +idInterpreter::GetThread +================ +*/ +idThread *idInterpreter::GetThread() const { + return thread; +} + + +/* +================ +idInterpreter::SetThread +================ +*/ +void idInterpreter::SetThread( idThread *pThread ) { + thread = pThread; +} + +/* +================ +idInterpreter::CurrentLine +================ +*/ +int idInterpreter::CurrentLine() const { + if ( instructionPointer < 0 ) { + return 0; + } + return gameLocal.program.GetLineNumberForStatement( instructionPointer ); +} + +/* +================ +idInterpreter::CurrentFile +================ +*/ +const char *idInterpreter::CurrentFile() const { + if ( instructionPointer < 0 ) { + return ""; + } + return gameLocal.program.GetFilenameForStatement( instructionPointer ); +} + +/* +============ +idInterpreter::StackTrace +============ +*/ +void idInterpreter::StackTrace() const { + const function_t *f; + int i; + int top; + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + return; + } + + top = callStackDepth; + if ( top >= MAX_STACK_DEPTH ) { + top = MAX_STACK_DEPTH - 1; + } + + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = top; i >= 0; i-- ) { + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } +} + +/* +============ +idInterpreter::Error + +Aborts the currently executing function +============ +*/ +void idInterpreter::Error( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + StackTrace(); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Error( "%s(%d): Thread '%s': %s\n", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Error( "Thread '%s': %s\n", thread->GetThreadName(), text ); + } +} + +/* +============ +idInterpreter::Warning + +Prints file and line number information with warning. +============ +*/ +void idInterpreter::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Warning( "%s(%d): Thread '%s': %s", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Warning( "Thread '%s' : %s", thread->GetThreadName(), text ); + } +} + +/* +================ +idInterpreter::DisplayInfo +================ +*/ +void idInterpreter::DisplayInfo() const { + const function_t *f; + int i; + + gameLocal.Printf( " Stack depth: %d bytes, %d max\n", localstackUsed, maxLocalstackUsed ); + gameLocal.Printf( " Call depth: %d, %d max\n", callStackDepth, maxStackDepth ); + gameLocal.Printf( " Call Stack: " ); + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + } else { + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = callStackDepth; i > 0; i-- ) { + gameLocal.Printf( " " ); + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } + } +} + +/* +==================== +idInterpreter::ThreadCall + +Copys the args from the calling thread's stack +==================== +*/ +void idInterpreter::ThreadCall( idInterpreter *source, const function_t *func, int args ) { + Reset(); + + if ( args > LOCALSTACK_SIZE ) { + args = LOCALSTACK_SIZE; + } + memcpy( localstack, &source->localstack[ source->localstackUsed - args ], args ); + + localstackUsed = args; + localstackBase = 0; + + maxLocalstackUsed = localstackUsed; + EnterFunction( func, false ); + + thread->SetThreadName( currentFunction->Name() ); +} + +/* +================ +idInterpreter::EnterObjectFunction + +Calls a function on a script object. + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +================ +*/ +void idInterpreter::EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ) { + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + Push( self->entityNumber + 1 ); + EnterFunction( func, false ); +} + +/* +==================== +idInterpreter::EnterFunction + +Returns the new program statement counter + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +==================== +*/ +void idInterpreter::EnterFunction( const function_t *func, bool clearStack ) { + int c; + prstack_t *stack; + + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + + if ( callStackDepth >= MAX_STACK_DEPTH ) { + Error( "call stack overflow" ); + } + + stack = &callStack[ callStackDepth ]; + + stack->s = instructionPointer + 1; // point to the next instruction to execute + stack->f = currentFunction; + stack->stackbase = localstackBase; + + callStackDepth++; + if ( callStackDepth > maxStackDepth ) { + maxStackDepth = callStackDepth; + } + + if ( func == NULL ) { + Error( "NULL function" ); + return; + } + + if ( debug ) { + if ( currentFunction ) { + gameLocal.Printf( "%d: call '%s' from '%s'(line %d)%s\n", gameLocal.time, func->Name(), currentFunction->Name(), + gameLocal.program.GetStatement( instructionPointer ).linenumber, clearStack ? " clear stack" : "" ); + } else { + gameLocal.Printf( "%d: call '%s'%s\n", gameLocal.time, func->Name(), clearStack ? " clear stack" : "" ); + } + } + + currentFunction = func; + assert( !func->eventdef ); + NextInstruction( func->firstStatement ); + + // allocate space on the stack for locals + // parms are already on stack + c = func->locals - func->parmTotal; + assert( c >= 0 ); + + if ( localstackUsed + c > LOCALSTACK_SIZE ) { + Error( "EnterFuncton: locals stack overflow\n" ); + } + + // initialize local stack variables to zero + memset( &localstack[ localstackUsed ], 0, c ); + + localstackUsed += c; + localstackBase = localstackUsed - func->locals; + + if ( localstackUsed > maxLocalstackUsed ) { + maxLocalstackUsed = localstackUsed ; + } +} + +/* +==================== +idInterpreter::LeaveFunction +==================== +*/ +void idInterpreter::LeaveFunction( idVarDef *returnDef ) { + prstack_t *stack; + varEval_t ret; + + if ( callStackDepth <= 0 ) { + Error( "prog stack underflow" ); + } + + // return value + if ( returnDef ) { + switch( returnDef->Type() ) { + case ev_string : + gameLocal.program.ReturnString( GetString( returnDef ) ); + break; + + case ev_vector : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnVector( *ret.vectorPtr ); + break; + + default : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnInteger( *ret.intPtr ); + } + } + + // remove locals from the stack + PopParms( currentFunction->locals ); + assert( localstackUsed == localstackBase ); + + if ( debug ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + gameLocal.Printf( "%d: %s(%d): exit %s", gameLocal.time, gameLocal.program.GetFilename( line.file ), line.linenumber, currentFunction->Name() ); + if ( callStackDepth > 1 ) { + gameLocal.Printf( " return to %s(line %d)\n", callStack[ callStackDepth - 1 ].f->Name(), gameLocal.program.GetStatement( callStack[ callStackDepth - 1 ].s ).linenumber ); + } else { + gameLocal.Printf( " done\n" ); + } + } + + // up stack + callStackDepth--; + stack = &callStack[ callStackDepth ]; + currentFunction = stack->f; + localstackBase = stack->stackbase; + NextInstruction( stack->s ); + + if ( !callStackDepth ) { + // all done + doneProcessing = true; + threadDying = true; + currentFunction = 0; + } +} + +/* +================ +idInterpreter::CallEvent +================ +*/ +void idInterpreter::CallEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t var; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( func == NULL ) { + Error( "NULL function" ); + return; + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + var.intPtr = ( int * )&localstack[ start ]; + eventEntity = GetEntity( *var.entityNumberPtr ); + + if ( eventEntity == NULL || !eventEntity->RespondsTo( *evdef ) ) { + if ( eventEntity != NULL && developer.GetBool() ) { + // give a warning in developer mode + Warning( "Function '%s' not supported on entity '%s'", evdef->GetName(), eventEntity->name.c_str() ); + } + // always return a safe value when an object doesn't exist + switch( evdef->GetReturnType() ) { + case D_EVENT_INTEGER : + gameLocal.program.ReturnInteger( 0 ); + break; + + case D_EVENT_FLOAT : + gameLocal.program.ReturnFloat( 0 ); + break; + + case D_EVENT_VECTOR : + gameLocal.program.ReturnVector( vec3_zero ); + break; + + case D_EVENT_STRING : + gameLocal.program.ReturnString( "" ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + gameLocal.program.ReturnEntity( ( idEntity * )NULL ); + break; + + case D_EVENT_TRACE : + default: + // unsupported data type + break; + } + + PopParms( argsize ); + eventEntity = NULL; + return; + } + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = type_object.Size(); ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + var.intPtr = ( int * )&localstack[ start + pos ]; + data[ i ] = int( *var.floatPtr ); + break; + + case D_EVENT_FLOAT : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( float * )&data[ i ] ) = *var.floatPtr; + break; + + case D_EVENT_VECTOR : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idVec3 ** )&data[ i ] ) = var.vectorPtr; + break; + + case D_EVENT_STRING : + ( *( const char ** )&data[ i ] ) = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + if ( !( *( idEntity ** )&data[ i ] ) ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + eventEntity->ProcessEventArgPtr( evdef, data ); + + if ( !multiFrameEvent ) { + if ( popParms ) { + PopParms( popParms ); + } + eventEntity = NULL; + } else { + doneProcessing = true; + } + popParms = 0; +} + +/* +================ +idInterpreter::BeginMultiFrameEvent +================ +*/ +bool idInterpreter::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( eventEntity != ent ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong entity" ); + } + if ( multiFrameEvent ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong event" ); + } + return false; + } + + multiFrameEvent = event; + return true; +} + +/* +================ +idInterpreter::EndMultiFrameEvent +================ +*/ +void idInterpreter::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::EndMultiFrameEvent called with wrong event" ); + } + + multiFrameEvent = NULL; +} + +/* +================ +idInterpreter::MultiFrameEventInProgress +================ +*/ +bool idInterpreter::MultiFrameEventInProgress() const { + return multiFrameEvent != NULL; +} + +/* +================ +idInterpreter::CallSysEvent +================ +*/ +void idInterpreter::CallSysEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t source; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( func == NULL ) { + Error( "NULL function" ); + return; + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = 0; ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( int * )&data[ i ] = int( *source.floatPtr ); + break; + + case D_EVENT_FLOAT : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( float * )&data[ i ] = *source.floatPtr; + break; + + case D_EVENT_VECTOR : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idVec3 ** )&data[ i ] = source.vectorPtr; + break; + + case D_EVENT_STRING : + *( const char ** )&data[ i ] = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + if ( !*( idEntity ** )&data[ i ] ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + thread->ProcessEventArgPtr( evdef, data ); + if ( popParms ) { + PopParms( popParms ); + } + popParms = 0; +} + +/* +==================== +idInterpreter::Execute +==================== +*/ +bool idInterpreter::Execute() { + varEval_t var_a; + varEval_t var_b; + varEval_t var_c; + varEval_t var; + statement_t *st; + int runaway; + idThread *newThread; + float floatVal; + idScriptObject *obj; + const function_t *func; + + if ( threadDying || !currentFunction ) { + return true; + } + + if ( multiFrameEvent ) { + // move to previous instruction and call it again + instructionPointer--; + } + + runaway = 5000000; + + doneProcessing = false; + while( !doneProcessing && !threadDying ) { + instructionPointer++; + + if ( !--runaway ) { + Error( "runaway loop error" ); + } + + // next statement + st = &gameLocal.program.GetStatement( instructionPointer ); + + switch( st->op ) { + case OP_RETURN: + LeaveFunction( st->a ); + break; + + case OP_THREAD: + newThread = new idThread( this, st->a->value.functionPtr, st->b->value.argSize ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + PopParms( st->b->value.argSize ); + break; + + case OP_OBJTHREAD: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + assert( st->c->value.argSize == func->parmTotal ); + newThread = new idThread( this, GetEntity( *var_a.entityNumberPtr ), func, func->parmTotal ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + } else { + // return a null thread to the script + gameLocal.program.ReturnFloat( 0.0f ); + } + PopParms( st->c->value.argSize ); + break; + + case OP_CALL: + EnterFunction( st->a->value.functionPtr, false ); + break; + + case OP_EVENTCALL: + CallEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_OBJECTCALL: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + EnterFunction( func, false ); + } else { + // return a 'safe' value + gameLocal.program.ReturnVector( vec3_zero ); + gameLocal.program.ReturnString( "" ); + PopParms( st->c->value.argSize ); + } + break; + + case OP_SYSCALL: + CallSysEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_IFNOT: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr == 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_IF: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr != 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_GOTO: + NextInstruction( instructionPointer + st->a->value.jumpOffset ); + break; + + case OP_ADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr + *var_b.floatPtr; + break; + + case OP_ADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr + *var_b.vectorPtr; + break; + + case OP_ADD_S: + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_FS: + var_a = GetVariable( st->a ); + SetString( st->c, FloatToString( *var_a.floatPtr ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SF: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, FloatToString( *var_b.floatPtr ) ); + break; + + case OP_ADD_VS: + var_a = GetVariable( st->a ); + SetString( st->c, var_a.vectorPtr->ToString() ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SV: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, var_b.vectorPtr->ToString() ); + break; + + case OP_SUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr - *var_b.floatPtr; + break; + + case OP_SUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr - *var_b.vectorPtr; + break; + + case OP_MUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr * *var_b.floatPtr; + break; + + case OP_MUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.vectorPtr * *var_b.vectorPtr; + break; + + case OP_MUL_FV: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.floatPtr * *var_b.vectorPtr; + break; + + case OP_MUL_VF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr * *var_b.floatPtr; + break; + + case OP_DIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = idMath::INFINITY; + } else { + *var_c.floatPtr = *var_a.floatPtr / *var_b.floatPtr; + } + break; + + case OP_MOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable ( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = *var_a.floatPtr; + } else { + *var_c.floatPtr = static_cast( *var_a.floatPtr ) % static_cast( *var_b.floatPtr ); + } + break; + + case OP_BITAND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) & static_cast( *var_b.floatPtr ); + break; + + case OP_BITOR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) | static_cast( *var_b.floatPtr ); + break; + + case OP_GE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr >= *var_b.floatPtr ); + break; + + case OP_LE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr <= *var_b.floatPtr ); + break; + + case OP_GT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr > *var_b.floatPtr ); + break; + + case OP_LT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr < *var_b.floatPtr ); + break; + + case OP_AND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.intPtr != 0 ); + break; + + case OP_AND_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.intPtr != 0 ); + break; + + case OP_OR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.intPtr != 0 ); + break; + + case OP_OR_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.intPtr != 0 ); + break; + + case OP_NOT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr == 0 ); + break; + + case OP_NOT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == 0.0f ); + break; + + case OP_NOT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == vec3_zero ); + break; + + case OP_NOT_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( strlen( GetString( st->a ) ) == 0 ); + break; + + case OP_NOT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( GetEntity( *var_a.entityNumberPtr ) == NULL ); + break; + + case OP_NEG_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = -*var_a.floatPtr; + break; + + case OP_NEG_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = -*var_a.vectorPtr; + break; + + case OP_INT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ); + break; + + case OP_EQ_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == *var_b.floatPtr ); + break; + + case OP_EQ_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == *var_b.vectorPtr ); + break; + + case OP_EQ_S: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) == 0 ); + break; + + case OP_EQ_E: + case OP_EQ_EO: + case OP_EQ_OE: + case OP_EQ_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr == *var_b.entityNumberPtr ); + break; + + case OP_NE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != *var_b.floatPtr ); + break; + + case OP_NE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr != *var_b.vectorPtr ); + break; + + case OP_NE_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) != 0 ); + break; + + case OP_NE_E: + case OP_NE_EO: + case OP_NE_OE: + case OP_NE_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr != *var_b.entityNumberPtr ); + break; + + case OP_UADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr += *var_a.floatPtr; + break; + + case OP_UADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr += *var_a.vectorPtr; + break; + + case OP_USUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr -= *var_a.floatPtr; + break; + + case OP_USUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr -= *var_a.vectorPtr; + break; + + case OP_UMUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr *= *var_a.floatPtr; + break; + + case OP_UMUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr *= *var_a.floatPtr; + break; + + case OP_UDIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = idMath::INFINITY; + } else { + *var_b.floatPtr = *var_b.floatPtr / *var_a.floatPtr; + } + break; + + case OP_UDIV_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + var_b.vectorPtr->Set( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + } else { + *var_b.vectorPtr = *var_b.vectorPtr / *var_a.floatPtr; + } + break; + + case OP_UMOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = *var_a.floatPtr; + } else { + *var_b.floatPtr = static_cast( *var_b.floatPtr ) % static_cast( *var_a.floatPtr ); + } + break; + + case OP_UOR_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) | static_cast( *var_a.floatPtr ); + break; + + case OP_UAND_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) & static_cast( *var_a.floatPtr ); + break; + + case OP_UINC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )++; + break; + + case OP_UINCP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )++; + } + break; + + case OP_UDEC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )--; + break; + + case OP_UDECP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )--; + } + break; + + case OP_COMP_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ~static_cast( *var_a.floatPtr ); + break; + + case OP_STORE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = *var_a.floatPtr; + break; + + case OP_STORE_ENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_BOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.intPtr = *var_a.intPtr; + break; + + case OP_STORE_OBJENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.entityNumberPtr = 0; + } else if ( !obj->GetTypeDef()->Inherits( st->b->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->b->TypeDef()->Name() ); + *var_b.entityNumberPtr = 0; + } else { + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STORE_OBJ: + case OP_STORE_ENTOBJ: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_S: + SetString( st->b, GetString( st->a ) ); + break; + + case OP_STORE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr = *var_a.vectorPtr; + break; + + case OP_STORE_FTOS: + var_a = GetVariable( st->a ); + SetString( st->b, FloatToString( *var_a.floatPtr ) ); + break; + + case OP_STORE_BTOS: + var_a = GetVariable( st->a ); + SetString( st->b, *var_a.intPtr ? "true" : "false" ); + break; + + case OP_STORE_VTOS: + var_a = GetVariable( st->a ); + SetString( st->b, var_a.vectorPtr->ToString() ); + break; + + case OP_STORE_FTOBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.intPtr = 1; + } else { + *var_b.intPtr = 0; + } + break; + + case OP_STORE_BOOLTOF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_a.intPtr ); + break; + + case OP_STOREP_F: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = *var_a.floatPtr; + } + break; + + case OP_STOREP_ENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_FLD: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_BOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_S: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + idStr::Copynz( var_b.evalPtr->stringPtr, GetString( st->a ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_V: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->vectorPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->vectorPtr = *var_a.vectorPtr; + } + break; + + case OP_STOREP_FTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, FloatToString( *var_a.floatPtr ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_BTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + idStr::Copynz( var_b.evalPtr->stringPtr, "true", MAX_STRING_LEN ); + } else { + idStr::Copynz( var_b.evalPtr->stringPtr, "false", MAX_STRING_LEN ); + } + } + break; + + case OP_STOREP_VTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, var_a.vectorPtr->ToString(), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_FTOBOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.evalPtr->intPtr = 1; + } else { + *var_b.evalPtr->intPtr = 0; + } + } + break; + + case OP_STOREP_BOOLTOF: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = static_cast( *var_a.intPtr ); + } + break; + + case OP_STOREP_OBJ: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_OBJENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.evalPtr->entityNumberPtr = 0; + + // st->b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in st->c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + } else if ( !obj->GetTypeDef()->Inherits( st->c->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->c->TypeDef()->Name() ); + *var_b.evalPtr->entityNumberPtr = 0; + } else { + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + } + break; + + case OP_ADDRESS: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var_c.evalPtr->bytePtr = &obj->data[ st->b->value.ptrOffset ]; + } else { + var_c.evalPtr->bytePtr = NULL; + } + break; + + case OP_INDIRECT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.floatPtr = *var.floatPtr; + } else { + *var_c.floatPtr = 0.0f; + } + break; + + case OP_INDIRECT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } else { + *var_c.entityNumberPtr = 0; + } + break; + + case OP_INDIRECT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.intPtr = *var.intPtr; + } else { + *var_c.intPtr = 0; + } + break; + + case OP_INDIRECT_S: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + SetString( st->c, var.stringPtr ); + } else { + SetString( st->c, "" ); + } + break; + + case OP_INDIRECT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.vectorPtr = *var.vectorPtr; + } else { + var_c.vectorPtr->Zero(); + } + break; + + case OP_INDIRECT_OBJ: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_c.entityNumberPtr = 0; + } else { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } + break; + + case OP_PUSH_F: + var_a = GetVariable( st->a ); + Push( *var_a.intPtr ); + break; + + case OP_PUSH_FTOS: + var_a = GetVariable( st->a ); + PushString( FloatToString( *var_a.floatPtr ) ); + break; + + case OP_PUSH_BTOF: + var_a = GetVariable( st->a ); + floatVal = *var_a.intPtr; + Push( *reinterpret_cast( &floatVal ) ); + break; + + case OP_PUSH_FTOB: + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + Push( 1 ); + } else { + Push( 0 ); + } + break; + + case OP_PUSH_VTOS: + var_a = GetVariable( st->a ); + PushString( var_a.vectorPtr->ToString() ); + break; + + case OP_PUSH_BTOS: + var_a = GetVariable( st->a ); + PushString( *var_a.intPtr ? "true" : "false" ); + break; + + case OP_PUSH_ENT: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_S: + PushString( GetString( st->a ) ); + break; + + case OP_PUSH_V: + var_a = GetVariable( st->a ); + Push( *reinterpret_cast( &var_a.vectorPtr->x ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->y ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->z ) ); + break; + + case OP_PUSH_OBJ: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_OBJENT: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_BREAK: + case OP_CONTINUE: + default: + Error( "Bad opcode %i", st->op ); + break; + } + } + + return threadDying; +} diff --git a/neo/d3xp/script/Script_Interpreter.h b/neo/d3xp/script/Script_Interpreter.h new file mode 100644 index 00000000..287894af --- /dev/null +++ b/neo/d3xp/script/Script_Interpreter.h @@ -0,0 +1,271 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SCRIPT_INTERPRETER_H__ +#define __SCRIPT_INTERPRETER_H__ + +#define MAX_STACK_DEPTH 64 +#define LOCALSTACK_SIZE 6144 + +typedef struct prstack_s { + int s; + const function_t *f; + int stackbase; +} prstack_t; + +class idInterpreter { +private: + prstack_t callStack[ MAX_STACK_DEPTH ]; + int callStackDepth; + int maxStackDepth; + + byte localstack[ LOCALSTACK_SIZE ]; + int localstackUsed; + int localstackBase; + int maxLocalstackUsed; + + const function_t *currentFunction; + int instructionPointer; + + int popParms; + const idEventDef *multiFrameEvent; + idEntity *eventEntity; + + idThread *thread; + + void PopParms( int numParms ); + void PushString( const char *string ); + void Push( int value ); + const char *FloatToString( float value ); + void AppendString( idVarDef *def, const char *from ); + void SetString( idVarDef *def, const char *from ); + const char *GetString( idVarDef *def ); + varEval_t GetVariable( idVarDef *def ); + idEntity *GetEntity( int entnum ) const; + idScriptObject *GetScriptObject( int entnum ) const; + void NextInstruction( int position ); + + void LeaveFunction( idVarDef *returnDef ); + void CallEvent( const function_t *func, int argsize ); + void CallSysEvent( const function_t *func, int argsize ); + +public: + bool doneProcessing; + bool threadDying; + bool terminateOnExit; + bool debug; + + idInterpreter(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetThread( idThread *pThread ); + + void StackTrace() const; + + int CurrentLine() const; + const char *CurrentFile() const; + + void Error( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void Warning( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void DisplayInfo() const; + + bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + bool MultiFrameEventInProgress() const; + + void ThreadCall( idInterpreter *source, const function_t *func, int args ); + void EnterFunction( const function_t *func, bool clearStack ); + void EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ); + + bool Execute(); + void Reset(); + + bool GetRegisterValue( const char *name, idStr &out, int scopeDepth ); + int GetCallstackDepth() const; + const prstack_t *GetCallstack() const; + const function_t *GetCurrentFunction() const; + idThread *GetThread() const; + +}; + +/* +==================== +idInterpreter::PopParms +==================== +*/ +ID_INLINE void idInterpreter::PopParms( int numParms ) { + // pop our parms off the stack + if ( localstackUsed < numParms ) { + Error( "locals stack underflow\n" ); + } + + localstackUsed -= numParms; +} + +/* +==================== +idInterpreter::Push +==================== +*/ +ID_INLINE void idInterpreter::Push( int value ) { + if ( localstackUsed + sizeof( int ) > LOCALSTACK_SIZE ) { + Error( "Push: locals stack overflow\n" ); + } + *( int * )&localstack[ localstackUsed ] = value; + localstackUsed += sizeof( int ); +} + +/* +==================== +idInterpreter::PushString +==================== +*/ +ID_INLINE void idInterpreter::PushString( const char *string ) { + if ( localstackUsed + MAX_STRING_LEN > LOCALSTACK_SIZE ) { + Error( "PushString: locals stack overflow\n" ); + } + idStr::Copynz( ( char * )&localstack[ localstackUsed ], string, MAX_STRING_LEN ); + localstackUsed += MAX_STRING_LEN; +} + +/* +==================== +idInterpreter::FloatToString +==================== +*/ +ID_INLINE const char *idInterpreter::FloatToString( float value ) { + static char text[ 32 ]; + + if ( value == ( float )( int )value ) { + sprintf( text, "%d", ( int )value ); + } else { + sprintf( text, "%f", value ); + } + return text; +} + +/* +==================== +idInterpreter::AppendString +==================== +*/ +ID_INLINE void idInterpreter::AppendString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Append( ( char * )&localstack[ localstackBase + def->value.stackOffset ], MAX_STRING_LEN, from ); + } else { + idStr::Append( def->value.stringPtr, MAX_STRING_LEN, from ); + } +} + +/* +==================== +idInterpreter::SetString +==================== +*/ +ID_INLINE void idInterpreter::SetString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Copynz( ( char * )&localstack[ localstackBase + def->value.stackOffset ], from, MAX_STRING_LEN ); + } else { + idStr::Copynz( def->value.stringPtr, from, MAX_STRING_LEN ); + } +} + +/* +==================== +idInterpreter::GetString +==================== +*/ +ID_INLINE const char *idInterpreter::GetString( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + return ( char * )&localstack[ localstackBase + def->value.stackOffset ]; + } else { + return def->value.stringPtr; + } +} + +/* +==================== +idInterpreter::GetVariable +==================== +*/ +ID_INLINE varEval_t idInterpreter::GetVariable( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + varEval_t val; + val.intPtr = ( int * )&localstack[ localstackBase + def->value.stackOffset ]; + return val; + } else { + return def->value; + } +} + +/* +================ +idInterpreter::GetEntity +================ +*/ +ID_INLINE idEntity *idInterpreter::GetEntity( int entnum ) const{ + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + return gameLocal.entities[ entnum - 1 ]; + } + return NULL; +} + +/* +================ +idInterpreter::GetScriptObject +================ +*/ +ID_INLINE idScriptObject *idInterpreter::GetScriptObject( int entnum ) const { + idEntity *ent; + + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + ent = gameLocal.entities[ entnum - 1 ]; + if ( ent && ent->scriptObject.data ) { + return &ent->scriptObject; + } + } + return NULL; +} + +/* +==================== +idInterpreter::NextInstruction +==================== +*/ +ID_INLINE void idInterpreter::NextInstruction( int position ) { + // Before we execute an instruction, we increment instructionPointer, + // therefore we need to compensate for that here. + instructionPointer = position - 1; +} + +#endif /* !__SCRIPT_INTERPRETER_H__ */ diff --git a/neo/d3xp/script/Script_Program.cpp b/neo/d3xp/script/Script_Program.cpp new file mode 100644 index 00000000..de197aa3 --- /dev/null +++ b/neo/d3xp/script/Script_Program.cpp @@ -0,0 +1,2145 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +// simple types. function types are dynamically allocated +idTypeDef type_void( ev_void, &def_void, "void", 0, NULL ); +idTypeDef type_scriptevent( ev_scriptevent, &def_scriptevent, "scriptevent", sizeof( void * ), NULL ); +idTypeDef type_namespace( ev_namespace, &def_namespace, "namespace", sizeof( void * ), NULL ); +idTypeDef type_string( ev_string, &def_string, "string", MAX_STRING_LEN, NULL ); +idTypeDef type_float( ev_float, &def_float, "float", sizeof( float ), NULL ); +idTypeDef type_vector( ev_vector, &def_vector, "vector", sizeof( idVec3 ), NULL ); +idTypeDef type_entity( ev_entity, &def_entity, "entity", sizeof( int * ), NULL ); // stored as entity number pointer +idTypeDef type_field( ev_field, &def_field, "field", sizeof( void * ), NULL ); +idTypeDef type_function( ev_function, &def_function, "function", sizeof( void * ), &type_void ); +idTypeDef type_virtualfunction( ev_virtualfunction, &def_virtualfunction, "virtual function", sizeof( int ), NULL ); +idTypeDef type_pointer( ev_pointer, &def_pointer, "pointer", sizeof( void * ), NULL ); +idTypeDef type_object( ev_object, &def_object, "object", sizeof( int * ), NULL ); // stored as entity number pointer +idTypeDef type_jumpoffset( ev_jumpoffset, &def_jumpoffset, "", sizeof( int ), NULL ); // only used for jump opcodes +idTypeDef type_argsize( ev_argsize, &def_argsize, "", sizeof( int ), NULL ); // only used for function call and thread opcodes +idTypeDef type_boolean( ev_boolean, &def_boolean, "boolean", sizeof( int ), NULL ); + +idVarDef def_void( &type_void ); +idVarDef def_scriptevent( &type_scriptevent ); +idVarDef def_namespace( &type_namespace ); +idVarDef def_string( &type_string ); +idVarDef def_float( &type_float ); +idVarDef def_vector( &type_vector ); +idVarDef def_entity( &type_entity ); +idVarDef def_field( &type_field ); +idVarDef def_function( &type_function ); +idVarDef def_virtualfunction( &type_virtualfunction ); +idVarDef def_pointer( &type_pointer ); +idVarDef def_object( &type_object ); +idVarDef def_jumpoffset( &type_jumpoffset ); // only used for jump opcodes +idVarDef def_argsize( &type_argsize ); +idVarDef def_boolean( &type_boolean ); + +/*********************************************************************** + + function_t + +***********************************************************************/ + +/* +================ +function_t::function_t +================ +*/ +function_t::function_t() { + Clear(); +} + +/* +================ +function_t::Allocated +================ +*/ +size_t function_t::Allocated() const { + return name.Allocated() + parmSize.Allocated(); +} + +/* +================ +function_t::SetName +================ +*/ +void function_t::SetName( const char *name ) { + this->name = name; +} + +/* +================ +function_t::Name +================ +*/ +const char *function_t::Name() const { + return name; +} + +/* +================ +function_t::Clear +================ +*/ +void function_t::Clear() { + eventdef = NULL; + def = NULL; + type = NULL; + firstStatement = 0; + numStatements = 0; + parmTotal = 0; + locals = 0; + filenum = 0; + name.Clear(); + parmSize.Clear(); +} + +/*********************************************************************** + + idTypeDef + +***********************************************************************/ + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + name = ename; + type = etype; + def = edef; + size = esize; + auxType = aux; + + parmTypes.SetGranularity( 1 ); + parmNames.SetGranularity( 1 ); + functions.SetGranularity( 1 ); +} + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( const idTypeDef &other ) { + *this = other; +} + +/* +================ +idTypeDef::operator= +================ +*/ +void idTypeDef::operator=( const idTypeDef& other ) { + type = other.type; + def = other.def; + name = other.name; + size = other.size; + auxType = other.auxType; + parmTypes = other.parmTypes; + parmNames = other.parmNames; + functions = other.functions; +} + +/* +================ +idTypeDef::Allocated +================ +*/ +size_t idTypeDef::Allocated() const { + size_t memsize; + int i; + + memsize = name.Allocated() + parmTypes.Allocated() + parmNames.Allocated() + functions.Allocated(); + for( i = 0; i < parmTypes.Num(); i++ ) { + memsize += parmNames[ i ].Allocated(); + } + + return memsize; +} + +/* +================ +idTypeDef::Inherits + +Returns true if basetype is an ancestor of this type. +================ +*/ +bool idTypeDef::Inherits( const idTypeDef *basetype ) const { + idTypeDef *superType; + + if ( type != ev_object ) { + return false; + } + + if ( this == basetype ) { + return true; + } + for( superType = auxType; superType != NULL; superType = superType->auxType ) { + if ( superType == basetype ) { + return true; + } + } + + return false; +} + +/* +================ +idTypeDef::MatchesType + +Returns true if both types' base types and parameters match +================ +*/ +bool idTypeDef::MatchesType( const idTypeDef &matchtype ) const { + int i; + + if ( this == &matchtype ) { + return true; + } + + if ( ( type != matchtype.type ) || ( auxType != matchtype.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchtype.parmTypes.Num() ) { + return false; + } + + for( i = 0; i < matchtype.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchtype.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::MatchesVirtualFunction + +Returns true if both functions' base types and parameters match +================ +*/ +bool idTypeDef::MatchesVirtualFunction( const idTypeDef &matchfunc ) const { + int i; + + if ( this == &matchfunc ) { + return true; + } + + if ( ( type != matchfunc.type ) || ( auxType != matchfunc.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchfunc.parmTypes.Num() ) { + return false; + } + + if ( parmTypes.Num() > 0 ) { + if ( !parmTypes[ 0 ]->Inherits( matchfunc.parmTypes[ 0 ] ) ) { + return false; + } + } + + for( i = 1; i < matchfunc.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchfunc.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::AddFunctionParm + +Adds a new parameter for a function type. +================ +*/ +void idTypeDef::AddFunctionParm( idTypeDef *parmtype, const char *name ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); + } + + parmTypes.Append( parmtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; +} + +/* +================ +idTypeDef::AddField + +Adds a new field to an object type. +================ +*/ +void idTypeDef::AddField( idTypeDef *fieldtype, const char *name ) { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::AddField : tried to add field to non-object type" ); + } + + parmTypes.Append( fieldtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; + + if ( fieldtype->FieldType()->Inherits( &type_object ) ) { + size += type_object.Size(); + } else { + size += fieldtype->FieldType()->Size(); + } +} + +/* +================ +idTypeDef::SetName +================ +*/ +void idTypeDef::SetName( const char *newname ) { + name = newname; +} + +/* +================ +idTypeDef::Name +================ +*/ +const char *idTypeDef::Name() const { + return name; +} + +/* +================ +idTypeDef::Type +================ +*/ +etype_t idTypeDef::Type() const { + return type; +} + +/* +================ +idTypeDef::Size +================ +*/ +int idTypeDef::Size() const { + return size; +} + +/* +================ +idTypeDef::SuperClass + +If type is an object, then returns the object's superclass +================ +*/ +idTypeDef *idTypeDef::SuperClass() const { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::ReturnType + +If type is a function, then returns the function's return type +================ +*/ +idTypeDef *idTypeDef::ReturnType() const { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::ReturnType: tried to get return type on non-function type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetReturnType + +If type is a function, then sets the function's return type +================ +*/ +void idTypeDef::SetReturnType( idTypeDef *returntype ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); + } + + auxType = returntype; +} + +/* +================ +idTypeDef::FieldType + +If type is a field, then returns it's type +================ +*/ +idTypeDef *idTypeDef::FieldType() const { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::FieldType: tried to get field type on non-field type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetFieldType + +If type is a field, then sets the function's return type +================ +*/ +void idTypeDef::SetFieldType( idTypeDef *fieldtype ) { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); + } + + auxType = fieldtype; +} + +/* +================ +idTypeDef::PointerType + +If type is a pointer, then returns the type it points to +================ +*/ +idTypeDef *idTypeDef::PointerType() const { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetPointerType + +If type is a pointer, then sets the pointer's type +================ +*/ +void idTypeDef::SetPointerType( idTypeDef *pointertype ) { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); + } + + auxType = pointertype; +} + +/* +================ +idTypeDef::NumParameters +================ +*/ +int idTypeDef::NumParameters() const { + return parmTypes.Num(); +} + +/* +================ +idTypeDef::GetParmType +================ +*/ +idTypeDef *idTypeDef::GetParmType( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmTypes[ parmNumber ]; +} + +/* +================ +idTypeDef::GetParmName +================ +*/ +const char *idTypeDef::GetParmName( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmNames[ parmNumber ]; +} + +/* +================ +idTypeDef::NumFunctions +================ +*/ +int idTypeDef::NumFunctions() const { + return functions.Num(); +} + +/* +================ +idTypeDef::GetFunctionNumber +================ +*/ +int idTypeDef::GetFunctionNumber( const function_t *func ) const { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( functions[ i ] == func ) { + return i; + } + } + return -1; +} + +/* +================ +idTypeDef::GetFunction +================ +*/ +const function_t *idTypeDef::GetFunction( int funcNumber ) const { + assert( funcNumber >= 0 ); + assert( funcNumber < functions.Num() ); + return functions[ funcNumber ]; +} + +/* +================ +idTypeDef::AddFunction +================ +*/ +void idTypeDef::AddFunction( const function_t *func ) { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( !strcmp( functions[ i ]->def->Name(), func->def->Name() ) ) { + if ( func->def->TypeDef()->MatchesVirtualFunction( *functions[ i ]->def->TypeDef() ) ) { + functions[ i ] = func; + return; + } + } + } + functions.Append( func ); +} + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +================ +idVarDef::idVarDef() +================ +*/ +idVarDef::idVarDef( idTypeDef *typeptr ) { + typeDef = typeptr; + num = 0; + scope = NULL; + numUsers = 0; + initialized = idVarDef::uninitialized; + memset( &value, 0, sizeof( value ) ); + name = NULL; + next = NULL; +} + +/* +============ +idVarDef::~idVarDef +============ +*/ +idVarDef::~idVarDef() { + if ( name ) { + name->RemoveDef( this ); + } +} + +/* +============ +idVarDef::Name +============ +*/ +const char *idVarDef::Name() const { + return name->Name(); +} + +/* +============ +idVarDef::GlobalName +============ +*/ +const char *idVarDef::GlobalName() const { + if ( scope != &def_namespace ) { + return va( "%s::%s", scope->GlobalName(), name->Name() ); + } else { + return name->Name(); + } +} + +/* +============ +idVarDef::DepthOfScope +============ +*/ +int idVarDef::DepthOfScope( const idVarDef *otherScope ) const { + const idVarDef *def; + int depth; + + depth = 1; + for( def = otherScope; def != NULL; def = def->scope ) { + if ( def == scope ) { + return depth; + } + depth++; + } + + return 0; +} + +/* +============ +idVarDef::SetFunction +============ +*/ +void idVarDef::SetFunction( function_t *func ) { + assert( typeDef ); + initialized = initializedConstant; + assert( typeDef->Type() == ev_function ); + value.functionPtr = func; +} + +/* +============ +idVarDef::SetObject +============ +*/ +void idVarDef::SetObject( idScriptObject *object ) { + assert( typeDef ); + initialized = initialized; + assert( typeDef->Inherits( &type_object ) ); + *value.objectPtrPtr = object; +} + +/* +============ +idVarDef::SetValue +============ +*/ +void idVarDef::SetValue( const eval_t &_value, bool constant ) { + assert( typeDef ); + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + switch( typeDef->Type() ) { + case ev_pointer : + case ev_boolean : + case ev_field : + *value.intPtr = _value._int; + break; + + case ev_jumpoffset : + value.jumpOffset = _value._int; + break; + + case ev_argsize : + value.argSize = _value._int; + break; + + case ev_entity : + *value.entityNumberPtr = _value.entity; + break; + + case ev_string : + idStr::Copynz( value.stringPtr, _value.stringPtr, MAX_STRING_LEN ); + break; + + case ev_float : + *value.floatPtr = _value._float; + break; + + case ev_vector : + value.vectorPtr->x = _value.vector[ 0 ]; + value.vectorPtr->y = _value.vector[ 1 ]; + value.vectorPtr->z = _value.vector[ 2 ]; + break; + + case ev_function : + value.functionPtr = _value.function; + break; + + case ev_virtualfunction : + value.virtualFunction = _value._int; + break; + + case ev_object : + *value.entityNumberPtr = _value.entity; + break; + + default : + throw idCompileError( va( "weird type on '%s'", Name() ) ); + break; + } +} + +/* +============ +idVarDef::SetString +============ +*/ +void idVarDef::SetString( const char *string, bool constant ) { + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + assert( typeDef && ( typeDef->Type() == ev_string ) ); + idStr::Copynz( value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +============ +idVarDef::PrintInfo +============ +*/ +void idVarDef::PrintInfo( idFile *file, int instructionPointer ) const { + statement_t *jumpst; + int jumpto; + etype_t etype; + int i; + int len; + const char *ch; + + if ( initialized == initializedConstant ) { + file->Printf( "const " ); + } + + etype = typeDef->Type(); + switch( etype ) { + case ev_jumpoffset : + jumpto = instructionPointer + value.jumpOffset; + jumpst = &gameLocal.program.GetStatement( jumpto ); + file->Printf( "address %d [%s(%d)]", jumpto, gameLocal.program.GetFilename( jumpst->file ), jumpst->linenumber ); + break; + + case ev_function : + if ( value.functionPtr->eventdef ) { + file->Printf( "event %s", GlobalName() ); + } else { + file->Printf( "function %s", GlobalName() ); + } + break; + + case ev_field : + file->Printf( "field %d", value.ptrOffset ); + break; + + case ev_argsize: + file->Printf( "args %d", value.argSize ); + break; + + default: + file->Printf( "%s ", typeDef->Name() ); + if ( initialized == initializedConstant ) { + switch( etype ) { + case ev_string : + file->Printf( "\"" ); + len = strlen( value.stringPtr ); + ch = value.stringPtr; + for( i = 0; i < len; i++, ch++ ) { + if ( idStr::CharIsPrintable( *ch ) ) { + file->Printf( "%c", *ch ); + } else if ( *ch == '\n' ) { + file->Printf( "\\n" ); + } else { + file->Printf( "\\x%.2x", static_cast( *ch ) ); + } + } + file->Printf( "\"" ); + break; + + case ev_vector : + file->Printf( "'%s'", value.vectorPtr->ToString() ); + break; + + case ev_float : + file->Printf( "%f", *value.floatPtr ); + break; + + case ev_virtualfunction : + file->Printf( "vtable[ %d ]", value.virtualFunction ); + break; + + default : + file->Printf( "%d", *value.intPtr ); + break; + } + } else if ( initialized == stackVariable ) { + file->Printf( "stack[%d]", value.stackOffset ); + } else { + file->Printf( "global[%d]", num ); + } + break; + } +} + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +============ +idVarDefName::AddDef +============ +*/ +void idVarDefName::AddDef( idVarDef *def ) { + assert( def->next == NULL ); + def->name = this; + def->next = defs; + defs = def; +} + +/* +============ +idVarDefName::RemoveDef +============ +*/ +void idVarDefName::RemoveDef( idVarDef *def ) { + if ( defs == def ) { + defs = def->next; + } else { + for ( idVarDef *d = defs; d->next != NULL; d = d->next ) { + if ( d->next == def ) { + d->next = def->next; + break; + } + } + } + def->next = NULL; + def->name = NULL; +} + +/*********************************************************************** + + idScriptObject + +***********************************************************************/ + +/* +============ +idScriptObject::idScriptObject +============ +*/ +idScriptObject::idScriptObject() { + data = NULL; + type = &type_object; +} + +/* +============ +idScriptObject::~idScriptObject +============ +*/ +idScriptObject::~idScriptObject() { + Free(); +} + +/* +============ +idScriptObject::Free +============ +*/ +void idScriptObject::Free() { + if ( data ) { + Mem_Free( data ); + } + + data = NULL; + type = &type_object; +} + +/* +================ +idScriptObject::Save +================ +*/ +void idScriptObject::Save( idSaveGame *savefile ) const { + size_t size; + + if ( type == &type_object && data == NULL ) { + // Write empty string for uninitialized object + savefile->WriteString( "" ); + } else { + savefile->WriteString( type->Name() ); + size = type->Size(); + savefile->WriteInt( size ); + savefile->Write( data, size ); + } +} + +/* +================ +idScriptObject::Restore +================ +*/ +void idScriptObject::Restore( idRestoreGame *savefile ) { + idStr typeName; + int size; + + savefile->ReadString( typeName ); + + // Empty string signals uninitialized object + if ( typeName.Length() == 0 ) { + return; + } + + if ( !SetType( typeName ) ) { + savefile->Error( "idScriptObject::Restore: failed to restore object of type '%s'.", typeName.c_str() ); + } + + savefile->ReadInt( size ); + if ( size != type->Size() ) { + savefile->Error( "idScriptObject::Restore: size of object '%s' doesn't match size in save game.", typeName.c_str() ); + } + + savefile->Read( data, size ); +} + +/* +============ +idScriptObject::SetType + +Allocates an object and initializes memory. +============ +*/ +bool idScriptObject::SetType( const char *typeName ) { + size_t size; + idTypeDef *newtype; + + // lookup the type + newtype = gameLocal.program.FindType( typeName ); + + // only allocate memory if the object type changes + if ( newtype != type ) { + Free(); + if ( !newtype ) { + gameLocal.Warning( "idScriptObject::SetType: Unknown type '%s'", typeName ); + return false; + } + + if ( !newtype->Inherits( &type_object ) ) { + gameLocal.Warning( "idScriptObject::SetType: Can't create object of type '%s'. Must be an object type.", newtype->Name() ); + return false; + } + + // set the type + type = newtype; + + // allocate the memory + size = type->Size(); + data = ( byte * )Mem_Alloc( size, TAG_SCRIPT ); + } + + // init object memory + ClearObject(); + + return true; +} + +/* +============ +idScriptObject::ClearObject + +Resets the memory for the script object without changing its type. +============ +*/ +void idScriptObject::ClearObject() { + size_t size; + + if ( type != &type_object ) { + // init object memory + size = type->Size(); + memset( data, 0, size ); + } +} + +/* +============ +idScriptObject::HasObject +============ +*/ +bool idScriptObject::HasObject() const { + return ( type != &type_object ); +} + +/* +============ +idScriptObject::GetTypeDef +============ +*/ +idTypeDef *idScriptObject::GetTypeDef() const { + return type; +} + +/* +============ +idScriptObject::GetTypeName +============ +*/ +const char *idScriptObject::GetTypeName() const { + return type->Name(); +} + +/* +============ +idScriptObject::GetConstructor +============ +*/ +const function_t *idScriptObject::GetConstructor() const { + const function_t *func; + + func = GetFunction( "init" ); + return func; +} + +/* +============ +idScriptObject::GetDestructor +============ +*/ +const function_t *idScriptObject::GetDestructor() const { + const function_t *func; + + func = GetFunction( "destroy" ); + return func; +} + +/* +============ +idScriptObject::GetFunction +============ +*/ +const function_t *idScriptObject::GetFunction( const char *name ) const { + const function_t *func; + + if ( type == &type_object ) { + return NULL; + } + + func = gameLocal.program.FindFunction( name, type ); + return func; +} + +/* +============ +idScriptObject::GetVariable +============ +*/ +byte *idScriptObject::GetVariable( const char *name, etype_t etype ) const { + int i; + int pos; + const idTypeDef *t = type; + const idTypeDef *parm; + + if ( t == &type_object || t == NULL ) { + return NULL; + } + + do { + if ( t->SuperClass() != &type_object ) { + pos = t->SuperClass()->Size(); + } else { + pos = 0; + } + for( i = 0; i < t->NumParameters(); i++ ) { + parm = t->GetParmType( i ); + if ( !strcmp( t->GetParmName( i ), name ) ) { + if ( etype != parm->FieldType()->Type() ) { + return NULL; + } + return &data[ pos ]; + } + + if ( parm->FieldType()->Inherits( &type_object ) ) { + pos += type_object.Size(); + } else { + pos += parm->FieldType()->Size(); + } + } + t = t->SuperClass(); + } while( t != NULL && ( t != &type_object ) ); + + return NULL; +} + +/*********************************************************************** + + idProgram + +***********************************************************************/ + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( idTypeDef &type ) { + idTypeDef * newtype = new (TAG_SCRIPT) idTypeDef( type ); + typesHash.Add( idStr::Hash( type.Name() ), types.Append( newtype ) ); + return newtype; +} + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + idTypeDef * newtype = new (TAG_SCRIPT) idTypeDef( etype, edef, ename, esize, aux ); + typesHash.Add( idStr::Hash( ename ), types.Append( newtype ) ); + return newtype; +} + +/* +============ +idProgram::GetType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +idTypeDef *idProgram::GetType( idTypeDef &type, bool allocate ) { + + for ( int i = typesHash.First( idStr::Hash( type.Name() ) ); i != -1; i = typesHash.Next( i ) ) { + if ( types[ i ]->MatchesType( type ) && !strcmp( types[ i ]->Name(), type.Name() ) ) { + return types[ i ]; + } + } + + if ( !allocate ) { + return NULL; + } + + // allocate a new one + return AllocType( type ); +} + +/* +============ +idProgram::FindType + +Returns a preexisting complex type that matches the name, or returns NULL if not found +============ +*/ +idTypeDef *idProgram::FindType( const char *name ) { + + for ( int i = typesHash.First( idStr::Hash( name ) ); i != -1; i = typesHash.Next( i ) ) { + idTypeDef * check = types[ i ]; + if ( !strcmp( check->Name(), name ) ) { + return check; + } + } + + return NULL; +} + +/* +============ +idProgram::GetDefList +============ +*/ +idVarDef *idProgram::GetDefList( const char *name ) const { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + return varDefNames[i]->GetDefs(); + } + } + return NULL; +} + +/* +============ +idProgram::AddDefToNameList +============ +*/ +void idProgram::AddDefToNameList( idVarDef *def, const char *name ) { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + break; + } + } + if ( i == -1 ) { + i = varDefNames.Append( new (TAG_SCRIPT) idVarDefName( name ) ); + varDefNameHash.Add( hash, i ); + } + varDefNames[i]->AddDef( def ); +} + +/* +============ +idProgram::AllocDef +============ +*/ +idVarDef *idProgram::AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ) { + idVarDef *def; + idStr element; + idVarDef *def_x; + idVarDef *def_y; + idVarDef *def_z; + + // allocate a new def + def = new (TAG_SCRIPT) idVarDef( type ); + def->scope = scope; + def->numUsers = 1; + def->num = varDefs.Append( def ); + + // add the def to the list with defs with this name and set the name pointer + AddDefToNameList( def, name ); + + if ( ( type->Type() == ev_vector ) || ( ( type->Type() == ev_field ) && ( type->FieldType()->Type() == ev_vector ) ) ) { + // + // vector + // + if ( !strcmp( name, RESULT_STRING ) ) { + // vector defs don't need the _x, _y and _z components + assert( scope->Type() == ev_function ); + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + scope->value.functionPtr->locals += type->Size(); + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + idTypeDef newtype( ev_field, NULL, "float field", 0, &type_float ); + idTypeDef *type = GetType( newtype, true ); + + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( type, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( type, element, scope, constant ); + def_y->value.ptrOffset = def_x->value.ptrOffset + type_float.Size(); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( type, element, scope, constant ); + def_z->value.ptrOffset = def_y->value.ptrOffset + type_float.Size(); + } else { + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( &type_float, element, scope, constant ); + + // point the vector def to the x coordinate + def->value = def_x->value; + def->initialized = def_x->initialized; + } + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + // + // object variable + // + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + } else if ( scope->Type() == ev_function ) { + // + // stack variable + // + // since we don't know how many local variables there are, + // we have to have them go backwards on the stack + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + + if ( type->Inherits( &type_object ) ) { + // objects only have their entity number on the stack, not the entire object + scope->value.functionPtr->locals += type_object.Size(); + } else { + scope->value.functionPtr->locals += type->Size(); + } + } else { + // + // global variable + // + def->value.bytePtr = &variables[ numVariables ]; + numVariables += def->TypeDef()->Size(); + if ( numVariables > sizeof( variables ) ) { + throw idCompileError( va( "Exceeded global memory size (%d bytes)", sizeof( variables ) ) ); + } + + memset( def->value.bytePtr, 0, def->TypeDef()->Size() ); + } + + return def; +} + +/* +============ +idProgram::GetDef + +If type is NULL, it will match any type +============ +*/ +idVarDef *idProgram::GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const { + idVarDef *def; + idVarDef *bestDef; + int bestDepth; + int depth; + + bestDepth = 0; + bestDef = NULL; + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def->scope->Type() == ev_namespace ) { + depth = def->DepthOfScope( scope ); + if ( !depth ) { + // not in the same namespace + continue; + } + } else if ( def->scope != scope ) { + // in a different function + continue; + } else { + depth = 1; + } + + if ( !bestDef || ( depth < bestDepth ) ) { + bestDepth = depth; + bestDef = def; + } + } + + // see if the name is already in use for another type + if ( bestDef && type && ( bestDef->TypeDef() != type ) ) { + throw idCompileError( va( "Type mismatch on redeclaration of %s", name ) ); + } + + return bestDef; +} + +/* +============ +idProgram::FreeDef +============ +*/ +void idProgram::FreeDef( idVarDef *def, const idVarDef *scope ) { + idVarDef *e; + int i; + + if ( def->Type() == ev_vector ) { + idStr name; + + sprintf( name, "%s_x", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_y", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_z", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + } + + varDefs.RemoveIndex( def->num ); + for( i = def->num; i < varDefs.Num(); i++ ) { + varDefs[ i ]->num = i; + } + + delete def; +} + +/* +============ +idProgram::FindFreeResultDef +============ +*/ +idVarDef *idProgram::FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ) { + idVarDef *def; + + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def == a || def == b ) { + continue; + } + if ( def->TypeDef() != type ) { + continue; + } + if ( def->scope != scope ) { + continue; + } + if ( def->numUsers <= 1 ) { + continue; + } + return def; + } + + return AllocDef( type, name, scope, false ); +} + +/* +================ +idProgram::FindFunction + +Searches for the specified function in the currently loaded script. A full namespace should be +specified if not in the global namespace. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name ) const { + int start; + int pos; + idVarDef *namespaceDef; + idVarDef *def; + + assert( name ); + + idStr fullname = name; + start = 0; + namespaceDef = &def_namespace; + do { + pos = fullname.Find( "::", true, start ); + if ( pos < 0 ) { + break; + } + + idStr namespaceName = fullname.Mid( start, pos - start ); + def = GetDef( NULL, namespaceName, namespaceDef ); + if ( !def ) { + // couldn't find namespace + return NULL; + } + namespaceDef = def; + + // skip past the :: + start = pos + 2; + } while( def->Type() == ev_namespace ); + + idStr funcName = fullname.Right( fullname.Length() - start ); + def = GetDef( NULL, funcName, namespaceDef ); + if ( !def ) { + // couldn't find function + return NULL; + } + + if ( ( def->Type() == ev_function ) && ( def->value.functionPtr->eventdef == NULL ) ) { + return def->value.functionPtr; + } + + // is not a function, or is an eventdef + return NULL; +} + +/* +================ +idProgram::FindFunction + +Searches for the specified object function in the currently loaded script. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name, const idTypeDef *type ) const { + const idVarDef *tdef; + const idVarDef *def; + + // look for the function + def = NULL; + for( tdef = type->def; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = GetDef( NULL, name, tdef ); + if ( def ) { + return def->value.functionPtr; + } + } + + return NULL; +} + +/* +================ +idProgram::AllocFunction +================ +*/ +function_t &idProgram::AllocFunction( idVarDef *def ) { + if ( functions.Num() >= functions.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of functions (%d)", functions.Max() ) ); + } + + // fill in the dfunction + function_t &func = *functions.Alloc(); + func.eventdef = NULL; + func.def = def; + func.type = def->TypeDef(); + func.firstStatement = 0; + func.numStatements = 0; + func.parmTotal = 0; + func.locals = 0; + func.filenum = filenum; + func.parmSize.SetGranularity( 1 ); + func.SetName( def->GlobalName() ); + + def->SetFunction( &func ); + + return func; +} + +/* +================ +idProgram::SetEntity +================ +*/ +void idProgram::SetEntity( const char *name, idEntity *ent ) { + idVarDef *def; + idStr defName( "$" ); + + defName += name; + + def = GetDef( &type_entity, defName, &def_namespace ); + if ( def != NULL && ( def->initialized != idVarDef::stackVariable ) ) { + // 0 is reserved for NULL entity + if ( !ent ) { + *def->value.entityNumberPtr = 0; + } else { + *def->value.entityNumberPtr = ent->entityNumber + 1; + } + } +} + +/* +================ +idProgram::AllocStatement +================ +*/ +statement_t *idProgram::AllocStatement() { + if ( statements.Num() >= statements.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of statements (%d)", statements.Max() ) ); + } + return statements.Alloc(); +} + +/* +============== +idProgram::BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void idProgram::BeginCompilation() { + statement_t *statement; + + FreeData(); + + try { + // make the first statement a return for a "NULL" function + statement = AllocStatement(); + statement->linenumber = 0; + statement->file = 0; + statement->op = OP_RETURN; + statement->a = NULL; + statement->b = NULL; + statement->c = NULL; + + // define NULL + //AllocDef( &type_void, "", &def_namespace, true ); + + // define the return def + returnDef = AllocDef( &type_vector, "", &def_namespace, false ); + + // define the return def for strings + returnStringDef = AllocDef( &type_string, "", &def_namespace, false ); + + // define the sys object + sysDef = AllocDef( &type_void, "sys", &def_namespace, true ); + } + + catch( idCompileError &err ) { + gameLocal.Error( "%s", err.GetError() ); + } +} + +/* +============== +idProgram::DisassembleStatement +============== +*/ +void idProgram::DisassembleStatement( idFile *file, int instructionPointer ) const { + opcode_t *op; + const statement_t *statement; + + statement = &statements[ instructionPointer ]; + op = &idCompiler::opcodes[ statement->op ]; + file->Printf( "%20s(%d):\t%6d: %15s\t", fileList[ statement->file ].c_str(), statement->linenumber, instructionPointer, op->opname ); + + if ( statement->a ) { + file->Printf( "\ta: " ); + statement->a->PrintInfo( file, instructionPointer ); + } + + if ( statement->b ) { + file->Printf( "\tb: " ); + statement->b->PrintInfo( file, instructionPointer ); + } + + if ( statement->c ) { + file->Printf( "\tc: " ); + statement->c->PrintInfo( file, instructionPointer ); + } + + file->Printf( "\n" ); +} + +/* +============== +idProgram::Disassemble +============== +*/ +void idProgram::Disassemble() const { + int i; + int instructionPointer; + const function_t *func; + idFile *file; + + file = fileSystem->OpenFileByMode( "script/disasm.txt", FS_WRITE ); + + for( i = 0; i < functions.Num(); i++ ) { + func = &functions[ i ]; + if ( func->eventdef ) { + // skip eventdefs + continue; + } + + file->Printf( "\nfunction %s() %d stack used, %d parms, %d locals {\n", func->Name(), func->locals, func->parmTotal, func->locals - func->parmTotal ); + + for( instructionPointer = 0; instructionPointer < func->numStatements; instructionPointer++ ) { + DisassembleStatement( file, func->firstStatement + instructionPointer ); + } + + file->Printf( "}\n" ); + } + + fileSystem->CloseFile( file ); +} + +/* +============== +idProgram::FinishCompilation + +Called after all files are compiled to check for errors +============== +*/ +void idProgram::FinishCompilation() { + int i; + + top_functions = functions.Num(); + top_statements = statements.Num(); + top_types = types.Num(); + top_defs = varDefs.Num(); + top_files = fileList.Num(); + + variableDefaults.Clear(); + variableDefaults.SetNum( numVariables ); + + for( i = 0; i < numVariables; i++ ) { + variableDefaults[ i ] = variables[ i ]; + } +} + +/* +============== +idProgram::CompileStats + +called after all files are compiled to report memory usage. +============== +*/ +void idProgram::CompileStats() { + int memused; + int memallocated; + int numdefs; + int stringspace; + int funcMem; + int i; + + gameLocal.Printf( "---------- Compile stats ----------\n" ); + gameLocal.DPrintf( "Files loaded:\n" ); + + stringspace = 0; + for( i = 0; i < fileList.Num(); i++ ) { + gameLocal.DPrintf( " %s\n", fileList[ i ].c_str() ); + stringspace += fileList[ i ].Allocated(); + } + stringspace += fileList.Size(); + + numdefs = varDefs.Num(); + memused = varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + memused += stringspace; + + for( i = 0; i < types.Num(); i++ ) { + memused += types[ i ]->Allocated(); + } + + funcMem = functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + funcMem += functions[ i ].Allocated(); + } + + memallocated = funcMem + memused + sizeof( idProgram ); + + memused += statements.MemoryUsed(); + memused += functions.MemoryUsed(); // name and filename of functions are shared, so no need to include them + memused += sizeof( variables ); + + gameLocal.Printf( "\nMemory usage:\n" ); + gameLocal.Printf( " Strings: %d, %d bytes\n", fileList.Num(), stringspace ); + gameLocal.Printf( " Statements: %d, %d bytes\n", statements.Num(), statements.MemoryUsed() ); + gameLocal.Printf( " Functions: %d, %d bytes\n", functions.Num(), funcMem ); + gameLocal.Printf( " Variables: %d bytes\n", numVariables ); + gameLocal.Printf( " Mem used: %d bytes\n", memused ); + gameLocal.Printf( " Static data: %d bytes\n", sizeof( idProgram ) ); + gameLocal.Printf( " Allocated: %d bytes\n", memallocated ); + gameLocal.Printf( " Thread size: %d bytes\n\n", sizeof( idThread ) ); +} + +/* +================ +idProgram::CompileText +================ +*/ +bool idProgram::CompileText( const char *source, const char *text, bool console ) { + idCompiler compiler; + int i; + idVarDef *def; + idStr ospath; + + // use a full os path for GetFilenum since it calls OSPathToRelativePath to convert filenames from the parser + ospath = fileSystem->RelativePathToOSPath( source ); + filenum = GetFilenum( ospath ); + + try { + compiler.CompileFile( text, filename, console ); + + // check to make sure all functions prototyped have code + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + if ( ( def->Type() == ev_function ) && ( ( def->scope->Type() == ev_namespace ) || def->scope->TypeDef()->Inherits( &type_object ) ) ) { + if ( !def->value.functionPtr->eventdef && !def->value.functionPtr->firstStatement ) { + throw idCompileError( va( "function %s was not defined\n", def->GlobalName() ) ); + } + } + } + } + + catch( idCompileError &err ) { + if ( console ) { + gameLocal.Printf( "%s\n", err.GetError() ); + return false; + } else { + gameLocal.Error( "%s\n", err.GetError() ); + } + }; + + if ( !console ) { + CompileStats(); + } + + return true; +} + +/* +================ +idProgram::CompileFunction +================ +*/ +const function_t *idProgram::CompileFunction( const char *functionName, const char *text ) { + bool result; + + result = CompileText( functionName, text, false ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed." ); + } + + return FindFunction( functionName ); +} + +/* +================ +idProgram::CompileFile +================ +*/ +void idProgram::CompileFile( const char *filename ) { + char *src; + bool result; + + if ( fileSystem->ReadFile( filename, ( void ** )&src, NULL ) < 0 ) { + gameLocal.Error( "Couldn't load %s\n", filename ); + } + + result = CompileText( filename, src, false ); + + fileSystem->FreeFile( src ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed in file %s.", filename ); + } +} + +/* +================ +idProgram::FreeData +================ +*/ +void idProgram::FreeData() { + int i; + + // free the defs + varDefs.DeleteContents( true ); + varDefNames.DeleteContents( true ); + varDefNameHash.Free(); + + returnDef = NULL; + returnStringDef = NULL; + sysDef = NULL; + + // free any special types we've created + types.DeleteContents( true ); + typesHash.Free(); + + filenum = 0; + + numVariables = 0; + memset( variables, 0, sizeof( variables ) ); + + // clear all the strings in the functions so that it doesn't look like we're leaking memory. + for( i = 0; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + + filename.Clear(); + fileList.Clear(); + statements.Clear(); + functions.Clear(); + + top_functions = 0; + top_statements = 0; + top_types = 0; + top_defs = 0; + top_files = 0; + + filename = ""; +} + +/* +================ +idProgram::Startup +================ +*/ +void idProgram::Startup( const char *defaultScript ) { + gameLocal.Printf( "Initializing scripts\n" ); + + // make sure all data is freed up + idThread::Restart(); + + // get ready for loading scripts + BeginCompilation(); + + // load the default script + if ( defaultScript && *defaultScript ) { + CompileFile( defaultScript ); + } + + FinishCompilation(); +} + +/* +================ +idProgram::Save +================ +*/ +void idProgram::Save( idSaveGame *savefile ) const { + int i; + int currentFileNum = top_files; + + savefile->WriteInt( (fileList.Num() - currentFileNum) ); + while ( currentFileNum < fileList.Num() ) { + savefile->WriteString( fileList[ currentFileNum ] ); + currentFileNum++; + } + + for ( i = 0; i < variableDefaults.Num(); i++ ) { + if ( variables[i] != variableDefaults[i] ) { + savefile->WriteInt( i ); + savefile->WriteByte( variables[i] ); + } + } + // Mark the end of the diff with default variables with -1 + savefile->WriteInt( -1 ); + + savefile->WriteInt( numVariables ); + for ( i = variableDefaults.Num(); i < numVariables; i++ ) { + savefile->WriteByte( variables[i] ); + } + + int checksum = CalculateChecksum(); + savefile->WriteInt( checksum ); +} + +/* +================ +idProgram::Restore +================ +*/ +bool idProgram::Restore( idRestoreGame *savefile ) { + int i, num, index; + bool result = true; + idStr scriptname; + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( scriptname ); + CompileFile( scriptname ); + } + + savefile->ReadInt( index ); + while( index >= 0 ) { + savefile->ReadByte( variables[index] ); + savefile->ReadInt( index ); + } + + savefile->ReadInt( num ); + for ( i = variableDefaults.Num(); i < num; i++ ) { + savefile->ReadByte( variables[i] ); + } + + int saved_checksum, checksum; + + savefile->ReadInt( saved_checksum ); + checksum = CalculateChecksum(); + + if ( saved_checksum != checksum ) { + result = false; + } + + return result; +} + +/* +================ +idProgram::CalculateChecksum +================ +*/ +int idProgram::CalculateChecksum() const { + int i, result; + + typedef struct { + unsigned short op; + int a; + int b; + int c; + unsigned short linenumber; + unsigned short file; + } statementBlock_t; + + statementBlock_t *statementList = new (TAG_SCRIPT) statementBlock_t[ statements.Num() ]; + + memset( statementList, 0, ( sizeof(statementBlock_t) * statements.Num() ) ); + + // Copy info into new list, using the variable numbers instead of a pointer to the variable + for( i = 0; i < statements.Num(); i++ ) { + statementList[i].op = statements[i].op; + + if ( statements[i].a ) { + statementList[i].a = statements[i].a->num; + } else { + statementList[i].a = -1; + } + if ( statements[i].b ) { + statementList[i].b = statements[i].b->num; + } else { + statementList[i].b = -1; + } + if ( statements[i].c ) { + statementList[i].c = statements[i].c->num; + } else { + statementList[i].c = -1; + } + + statementList[i].linenumber = statements[i].linenumber; + statementList[i].file = statements[i].file; + } + + result = MD4_BlockChecksum( statementList, ( sizeof(statementBlock_t) * statements.Num() ) ); + + delete [] statementList; + + return result; +} + +/* +============== +idProgram::Restart + +Restores all variables to their initial value +============== +*/ +void idProgram::Restart() { + int i; + + idThread::Restart(); + + // + // since there may have been a script loaded by the map or the user may + // have typed "script" from the console, free up any types and vardefs that + // have been allocated after the initial startup + // + for( i = top_types; i < types.Num(); i++ ) { + delete types[ i ]; + } + types.SetNum( top_types ); + + typesHash.Free(); + for( i = 0; i < types.Num(); i++ ) { + typesHash.Add( idStr::Hash( types[i]->Name() ), i ); + } + + for( i = top_defs; i < varDefs.Num(); i++ ) { + delete varDefs[ i ]; + } + varDefs.SetNum( top_defs ); + + for( i = top_functions; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + functions.SetNum( top_functions ); + + statements.SetNum( top_statements ); + fileList.SetNum( top_files ); + filename.Clear(); + + // reset the variables to their default values + numVariables = variableDefaults.Num(); + for( i = 0; i < numVariables; i++ ) { + variables[ i ] = variableDefaults[ i ]; + } +} + +/* +================ +idProgram::GetFilenum +================ +*/ +int idProgram::GetFilenum( const char *name ) { + if ( filename == name ) { + return filenum; + } + + idStr strippedName; + strippedName = fileSystem->OSPathToRelativePath( name ); + if ( !strippedName.Length() ) { + // not off the base path so just use the full path + filenum = fileList.AddUnique( name ); + } else { + filenum = fileList.AddUnique( strippedName ); + } + + // save the unstripped name so that we don't have to strip the incoming name every time we call GetFilenum + filename = name; + + return filenum; +} + +/* +================ +idProgram::idProgram +================ +*/ +idProgram::idProgram() { + varDefs.SetGranularity( 256 ); + varDefNames.SetGranularity( 256 ); + + FreeData(); +} + +/* +================ +idProgram::~idProgram +================ +*/ +idProgram::~idProgram() { + FreeData(); +} + +/* +================ +idProgram::ReturnEntity +================ +*/ +void idProgram::ReturnEntity( idEntity *ent ) { + if ( ent ) { + *returnDef->value.entityNumberPtr = ent->entityNumber + 1; + } else { + *returnDef->value.entityNumberPtr = 0; + } +} diff --git a/neo/d3xp/script/Script_Program.h b/neo/d3xp/script/Script_Program.h new file mode 100644 index 00000000..75625dab --- /dev/null +++ b/neo/d3xp/script/Script_Program.h @@ -0,0 +1,626 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SCRIPT_PROGRAM_H__ +#define __SCRIPT_PROGRAM_H__ + +class idScriptObject; +class idEventDef; +class idVarDef; +class idTypeDef; +class idEntity; +class idThread; +class idSaveGame; +class idRestoreGame; + +#define MAX_STRING_LEN 128 +#define MAX_GLOBALS 296608 // in bytes +#define MAX_STRINGS 1024 + +#define MAX_FUNCS 3584 + +#define MAX_STATEMENTS 131072 // statement_t - 18 bytes last I checked + +typedef enum { + ev_error = -1, ev_void, ev_scriptevent, ev_namespace, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_virtualfunction, ev_pointer, ev_object, ev_jumpoffset, ev_argsize, ev_boolean +} etype_t; + +class function_t { +public: + function_t(); + + size_t Allocated() const; + void SetName( const char *name ); + const char *Name() const; + void Clear(); + +private: + idStr name; +public: + const idEventDef *eventdef; + idVarDef *def; + const idTypeDef *type; + int firstStatement; + int numStatements; + int parmTotal; + int locals; // total ints of parms + locals + int filenum; // source file defined in + idList parmSize; +}; + +typedef union eval_s { + const char *stringPtr; + float _float; + float vector[ 3 ]; + function_t *function; + int _int; + int entity; +} eval_t; + +/*********************************************************************** + +idTypeDef + +Contains type information for variables and functions. + +***********************************************************************/ + +class idTypeDef { +private: + etype_t type; + idStr name; + int size; + + // function types are more complex + idTypeDef *auxType; // return type + idList parmTypes; + idStrList parmNames; + idList functions; + +public: + idVarDef *def; // a def that points to this type + + idTypeDef( const idTypeDef &other ); + idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + void operator=( const idTypeDef& other ); + size_t Allocated() const; + + bool Inherits( const idTypeDef *basetype ) const; + bool MatchesType( const idTypeDef &matchtype ) const; + bool MatchesVirtualFunction( const idTypeDef &matchfunc ) const; + void AddFunctionParm( idTypeDef *parmtype, const char *name ); + void AddField( idTypeDef *fieldtype, const char *name ); + + void SetName( const char *newname ); + const char *Name() const; + + etype_t Type() const; + int Size() const; + + idTypeDef *SuperClass() const; + + idTypeDef *ReturnType() const; + void SetReturnType( idTypeDef *type ); + + idTypeDef *FieldType() const; + void SetFieldType( idTypeDef *type ); + + idTypeDef *PointerType() const; + void SetPointerType( idTypeDef *type ); + + int NumParameters() const; + idTypeDef *GetParmType( int parmNumber ) const; + const char *GetParmName( int parmNumber ) const; + + int NumFunctions() const; + int GetFunctionNumber( const function_t *func ) const; + const function_t *GetFunction( int funcNumber ) const; + void AddFunction( const function_t *func ); +}; + +/*********************************************************************** + +idScriptObject + +In-game representation of objects in scripts. Use the idScriptVariable template +(below) to access variables. + +***********************************************************************/ + +class idScriptObject { +private: + idTypeDef *type; + +public: + byte *data; + + idScriptObject(); + ~idScriptObject(); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Free(); + bool SetType( const char *typeName ); + void ClearObject(); + bool HasObject() const; + idTypeDef *GetTypeDef() const; + const char *GetTypeName() const; + const function_t *GetConstructor() const; + const function_t *GetDestructor() const; + const function_t *GetFunction( const char *name ) const; + + byte *GetVariable( const char *name, etype_t etype ) const; +}; + +/*********************************************************************** + +idScriptVariable + +Helper template that handles looking up script variables stored in objects. +If the specified variable doesn't exist, or is the wrong data type, idScriptVariable +will cause an error. + +***********************************************************************/ + +template +class idScriptVariable { +private: + type *data; + +public: + idScriptVariable(); + bool IsLinked() const; + void Unlink(); + void LinkTo( idScriptObject &obj, const char *name ); + idScriptVariable &operator=( const returnType &value ); + operator returnType() const; +}; + +template +ID_INLINE idScriptVariable::idScriptVariable() { + data = NULL; +} + +template +ID_INLINE bool idScriptVariable::IsLinked() const { + return ( data != NULL ); +} + +template +ID_INLINE void idScriptVariable::Unlink() { + data = NULL; +} + +template +ID_INLINE void idScriptVariable::LinkTo( idScriptObject &obj, const char *name ) { + data = ( type * )obj.GetVariable( name, etype ); + if ( !data ) { + gameError( "Missing '%s' field in script object '%s'", name, obj.GetTypeName() ); + } +} + +template +ID_INLINE idScriptVariable &idScriptVariable::operator=( const returnType &value ) { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + *data = ( type )value; + } + return *this; +} + +template +ID_INLINE idScriptVariable::operator returnType() const { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + return ( const returnType )*data; + } else { + // reasonably safe value + return ( const returnType )0; + } +} + +/*********************************************************************** + +Script object variable access template instantiations + +These objects will automatically handle looking up of the current value +of a variable in a script object. They can be stored as part of a class +for up-to-date values of the variable, or can be used in functions to +sample the data for non-dynamic values. + +***********************************************************************/ + +typedef idScriptVariable idScriptBool; +typedef idScriptVariable idScriptFloat; +typedef idScriptVariable idScriptInt; +typedef idScriptVariable idScriptVector; +typedef idScriptVariable idScriptString; + +/*********************************************************************** + +idCompileError + +Causes the compiler to exit out of compiling the current function and +display an error message with line and file info. + +***********************************************************************/ + +class idCompileError : public idException { +public: + idCompileError( const char *text ) : idException( text ) {} +}; + +/*********************************************************************** + +idVarDef + +Define the name, type, and location of variables, functions, and objects +defined in script. + +***********************************************************************/ + +typedef union varEval_s { + idScriptObject **objectPtrPtr; + char *stringPtr; + float *floatPtr; + idVec3 *vectorPtr; + function_t *functionPtr; + int *intPtr; + byte *bytePtr; + int *entityNumberPtr; + int virtualFunction; + int jumpOffset; + int stackOffset; // offset in stack for local variables + int argSize; + varEval_s *evalPtr; + int ptrOffset; +} varEval_t; + +class idVarDefName; + +class idVarDef { + friend class idVarDefName; + +public: + int num; + varEval_t value; + idVarDef * scope; // function, namespace, or object the var was defined in + int numUsers; // number of users if this is a constant + + typedef enum { + uninitialized, initializedVariable, initializedConstant, stackVariable + } initialized_t; + + initialized_t initialized; + +public: + idVarDef( idTypeDef *typeptr = NULL ); + ~idVarDef(); + + const char * Name() const; + const char * GlobalName() const; + + void SetTypeDef( idTypeDef *_type ) { typeDef = _type; } + idTypeDef * TypeDef() const { return typeDef; } + etype_t Type() const { return ( typeDef != NULL ) ? typeDef->Type() : ev_void; } + + int DepthOfScope( const idVarDef *otherScope ) const; + + void SetFunction( function_t *func ); + void SetObject( idScriptObject *object ); + void SetValue( const eval_t &value, bool constant ); + void SetString( const char *string, bool constant ); + + idVarDef * Next() const { return next; } // next var def with same name + + void PrintInfo( idFile *file, int instructionPointer ) const; + +private: + idTypeDef * typeDef; + idVarDefName * name; // name of this var + idVarDef * next; // next var with the same name +}; + +/*********************************************************************** + + idVarDefName + +***********************************************************************/ + +class idVarDefName { +public: + idVarDefName() { defs = NULL; } + idVarDefName( const char *n ) { name = n; defs = NULL; } + + const char * Name() const { return name; } + idVarDef * GetDefs() const { return defs; } + + void AddDef( idVarDef *def ); + void RemoveDef( idVarDef *def ); + +private: + idStr name; + idVarDef * defs; +}; + +/*********************************************************************** + + Variable and type defintions + +***********************************************************************/ + +extern idTypeDef type_void; +extern idTypeDef type_scriptevent; +extern idTypeDef type_namespace; +extern idTypeDef type_string; +extern idTypeDef type_float; +extern idTypeDef type_vector; +extern idTypeDef type_entity; +extern idTypeDef type_field; +extern idTypeDef type_function; +extern idTypeDef type_virtualfunction; +extern idTypeDef type_pointer; +extern idTypeDef type_object; +extern idTypeDef type_jumpoffset; // only used for jump opcodes +extern idTypeDef type_argsize; // only used for function call and thread opcodes +extern idTypeDef type_boolean; + +extern idVarDef def_void; +extern idVarDef def_scriptevent; +extern idVarDef def_namespace; +extern idVarDef def_string; +extern idVarDef def_float; +extern idVarDef def_vector; +extern idVarDef def_entity; +extern idVarDef def_field; +extern idVarDef def_function; +extern idVarDef def_virtualfunction; +extern idVarDef def_pointer; +extern idVarDef def_object; +extern idVarDef def_jumpoffset; // only used for jump opcodes +extern idVarDef def_argsize; // only used for function call and thread opcodes +extern idVarDef def_boolean; + +typedef struct statement_s { + unsigned short op; + idVarDef *a; + idVarDef *b; + idVarDef *c; + unsigned short linenumber; + unsigned short file; +} statement_t; + +/*********************************************************************** + +idProgram + +Handles compiling and storage of script data. Multiple idProgram objects +would represent seperate programs with no knowledge of each other. Scripts +meant to access shared data and functions should all be compiled by a +single idProgram. + +***********************************************************************/ + +class idProgram { +private: + idStrList fileList; + idStr filename; + int filenum; + + int numVariables; + byte variables[ MAX_GLOBALS ]; + idStaticList variableDefaults; + idStaticList functions; + idStaticList statements; + idList types; + idHashIndex typesHash; + idList varDefNames; + idHashIndex varDefNameHash; + idList varDefs; + + idVarDef *sysDef; + + int top_functions; + int top_statements; + int top_types; + int top_defs; + int top_files; + + void CompileStats(); + +public: + idVarDef *returnDef; + idVarDef *returnStringDef; + + idProgram(); + ~idProgram(); + + // save games + void Save( idSaveGame *savefile ) const; + bool Restore( idRestoreGame *savefile ); + int CalculateChecksum() const; // Used to insure program code has not + // changed between savegames + + void Startup( const char *defaultScript ); + void Restart(); + bool CompileText( const char *source, const char *text, bool console ); + const function_t *CompileFunction( const char *functionName, const char *text ); + void CompileFile( const char *filename ); + void BeginCompilation(); + void FinishCompilation(); + void DisassembleStatement( idFile *file, int instructionPointer ) const; + void Disassemble() const; + void FreeData(); + + const char *GetFilename( int num ); + int GetFilenum( const char *name ); + int GetLineNumberForStatement( int index ); + const char *GetFilenameForStatement( int index ); + + idTypeDef *AllocType( idTypeDef &type ); + idTypeDef *AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + idTypeDef *GetType( idTypeDef &type, bool allocate ); + idTypeDef *FindType( const char *name ); + + idVarDef *AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ); + idVarDef *GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const; + void FreeDef( idVarDef *d, const idVarDef *scope ); + idVarDef *FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ); + idVarDef *GetDefList( const char *name ) const; + void AddDefToNameList( idVarDef *def, const char *name ); + + function_t *FindFunction( const char *name ) const; // returns NULL if function not found + function_t *FindFunction( const char *name, const idTypeDef *type ) const; // returns NULL if function not found + function_t &AllocFunction( idVarDef *def ); + function_t *GetFunction( int index ); + int GetFunctionIndex( const function_t *func ); + + void SetEntity( const char *name, idEntity *ent ); + + statement_t *AllocStatement(); + statement_t &GetStatement( int index ); + int NumStatements() { return statements.Num(); } + + int GetReturnedInteger(); + + void ReturnFloat( float value ); + void ReturnInteger( int value ); + void ReturnVector( idVec3 const &vec ); + void ReturnString( const char *string ); + void ReturnEntity( idEntity *ent ); + + int NumFilenames() { return fileList.Num( ); } +}; + +/* +================ +idProgram::GetStatement +================ +*/ +ID_INLINE statement_t &idProgram::GetStatement( int index ) { + return statements[ index ]; +} + +/* +================ +idProgram::GetFunction +================ +*/ +ID_INLINE function_t *idProgram::GetFunction( int index ) { + return &functions[ index ]; +} + +/* +================ +idProgram::GetFunctionIndex +================ +*/ +ID_INLINE int idProgram::GetFunctionIndex( const function_t *func ) { + return func - &functions[0]; +} + +/* +================ +idProgram::GetReturnedInteger +================ +*/ +ID_INLINE int idProgram::GetReturnedInteger() { + return *returnDef->value.intPtr; +} + +/* +================ +idProgram::ReturnFloat +================ +*/ +ID_INLINE void idProgram::ReturnFloat( float value ) { + *returnDef->value.floatPtr = value; +} + +/* +================ +idProgram::ReturnInteger +================ +*/ +ID_INLINE void idProgram::ReturnInteger( int value ) { + *returnDef->value.intPtr = value; +} + +/* +================ +idProgram::ReturnVector +================ +*/ +ID_INLINE void idProgram::ReturnVector( idVec3 const &vec ) { + *returnDef->value.vectorPtr = vec; +} + +/* +================ +idProgram::ReturnString +================ +*/ +ID_INLINE void idProgram::ReturnString( const char *string ) { + idStr::Copynz( returnStringDef->value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +================ +idProgram::GetFilename +================ +*/ +ID_INLINE const char *idProgram::GetFilename( int num ) { + return fileList[ num ]; +} + +/* +================ +idProgram::GetLineNumberForStatement +================ +*/ +ID_INLINE int idProgram::GetLineNumberForStatement( int index ) { + return statements[ index ].linenumber; +} + +/* +================ +idProgram::GetFilenameForStatement +================ +*/ +ID_INLINE const char *idProgram::GetFilenameForStatement( int index ) { + return GetFilename( statements[ index ].file ); +} + +#endif /* !__SCRIPT_PROGRAM_H__ */ diff --git a/neo/d3xp/script/Script_Thread.cpp b/neo/d3xp/script/Script_Thread.cpp new file mode 100644 index 00000000..bfc2bf64 --- /dev/null +++ b/neo/d3xp/script/Script_Thread.cpp @@ -0,0 +1,1921 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + + +#include "../Game_local.h" + +const idEventDef EV_Thread_Execute( "", NULL ); +const idEventDef EV_Thread_SetCallback( "", NULL ); + +// script callable events +const idEventDef EV_Thread_TerminateThread( "terminate", "d" ); +const idEventDef EV_Thread_Pause( "pause", NULL ); +const idEventDef EV_Thread_Wait( "wait", "f" ); +const idEventDef EV_Thread_WaitFrame( "waitFrame" ); +const idEventDef EV_Thread_WaitFor( "waitFor", "e" ); +const idEventDef EV_Thread_WaitForThread( "waitForThread", "d" ); +const idEventDef EV_Thread_Print( "print", "s" ); +const idEventDef EV_Thread_PrintLn( "println", "s" ); +const idEventDef EV_Thread_Say( "say", "s" ); +const idEventDef EV_Thread_Assert( "assert", "f" ); +const idEventDef EV_Thread_Trigger( "trigger", "e" ); +const idEventDef EV_Thread_SetCvar( "setcvar", "ss" ); +const idEventDef EV_Thread_GetCvar( "getcvar", "s", 's' ); +const idEventDef EV_Thread_Random( "random", "f", 'f' ); +const idEventDef EV_Thread_RandomInt( "randomInt", "d", 'd' ); +const idEventDef EV_Thread_GetTime( "getTime", NULL, 'f' ); +const idEventDef EV_Thread_KillThread( "killthread", "s" ); +const idEventDef EV_Thread_SetThreadName( "threadname", "s" ); +const idEventDef EV_Thread_GetEntity( "getEntity", "s", 'e' ); +const idEventDef EV_Thread_Spawn( "spawn", "s", 'e' ); +const idEventDef EV_Thread_CopySpawnArgs( "copySpawnArgs", "e" ); +const idEventDef EV_Thread_SetSpawnArg( "setSpawnArg", "ss" ); +const idEventDef EV_Thread_SpawnString( "SpawnString", "ss", 's' ); +const idEventDef EV_Thread_SpawnFloat( "SpawnFloat", "sf", 'f' ); +const idEventDef EV_Thread_SpawnVector( "SpawnVector", "sv", 'v' ); +const idEventDef EV_Thread_ClearPersistantArgs( "clearPersistantArgs" ); +const idEventDef EV_Thread_SetPersistantArg( "setPersistantArg", "ss" ); +const idEventDef EV_Thread_GetPersistantString( "getPersistantString", "s", 's' ); +const idEventDef EV_Thread_GetPersistantFloat( "getPersistantFloat", "s", 'f' ); +const idEventDef EV_Thread_GetPersistantVector( "getPersistantVector", "s", 'v' ); +const idEventDef EV_Thread_AngToForward( "angToForward", "v", 'v' ); +const idEventDef EV_Thread_AngToRight( "angToRight", "v", 'v' ); +const idEventDef EV_Thread_AngToUp( "angToUp", "v", 'v' ); +const idEventDef EV_Thread_Sine( "sin", "f", 'f' ); +const idEventDef EV_Thread_Cosine( "cos", "f", 'f' ); +const idEventDef EV_Thread_ArcSine( "asin", "f", 'f' ); +const idEventDef EV_Thread_ArcCosine( "acos", "f", 'f' ); +const idEventDef EV_Thread_SquareRoot( "sqrt", "f", 'f' ); +const idEventDef EV_Thread_Normalize( "vecNormalize", "v", 'v' ); +const idEventDef EV_Thread_VecLength( "vecLength", "v", 'f' ); +const idEventDef EV_Thread_VecDotProduct( "DotProduct", "vv", 'f' ); +const idEventDef EV_Thread_VecCrossProduct( "CrossProduct", "vv", 'v' ); +const idEventDef EV_Thread_VecToAngles( "VecToAngles", "v", 'v' ); +const idEventDef EV_Thread_VecToOrthoBasisAngles( "VecToOrthoBasisAngles", "v", 'v' ); +const idEventDef EV_Thread_RotateVector("rotateVector", "vv", 'v'); +const idEventDef EV_Thread_OnSignal( "onSignal", "des" ); +const idEventDef EV_Thread_ClearSignal( "clearSignalThread", "de" ); +const idEventDef EV_Thread_SetCamera( "setCamera", "e" ); +const idEventDef EV_Thread_FirstPerson( "firstPerson", NULL ); +const idEventDef EV_Thread_Trace( "trace", "vvvvde", 'f' ); +const idEventDef EV_Thread_TracePoint( "tracePoint", "vvde", 'f' ); +const idEventDef EV_Thread_GetTraceFraction( "getTraceFraction", NULL, 'f' ); +const idEventDef EV_Thread_GetTraceEndPos( "getTraceEndPos", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceNormal( "getTraceNormal", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceEntity( "getTraceEntity", NULL, 'e' ); +const idEventDef EV_Thread_GetTraceJoint( "getTraceJoint", NULL, 's' ); +const idEventDef EV_Thread_GetTraceBody( "getTraceBody", NULL, 's' ); +const idEventDef EV_Thread_FadeIn( "fadeIn", "vf" ); +const idEventDef EV_Thread_FadeOut( "fadeOut", "vf" ); +const idEventDef EV_Thread_FadeTo( "fadeTo", "vff" ); +const idEventDef EV_Thread_StartMusic( "music", "s" ); +const idEventDef EV_Thread_Error( "error", "s" ); +const idEventDef EV_Thread_Warning( "warning", "s" ); +const idEventDef EV_Thread_StrLen( "strLength", "s", 'd' ); +const idEventDef EV_Thread_StrLeft( "strLeft", "sd", 's' ); +const idEventDef EV_Thread_StrRight( "strRight", "sd", 's' ); +const idEventDef EV_Thread_StrSkip( "strSkip", "sd", 's' ); +const idEventDef EV_Thread_StrMid( "strMid", "sdd", 's' ); +const idEventDef EV_Thread_StrToFloat( "strToFloat", "s", 'f' ); +const idEventDef EV_Thread_RadiusDamage( "radiusDamage", "vEEEsf" ); +const idEventDef EV_Thread_IsClient( "isClient", NULL, 'f' ); +const idEventDef EV_Thread_IsMultiplayer( "isMultiplayer", NULL, 'f' ); +const idEventDef EV_Thread_GetFrameTime( "getFrameTime", NULL, 'f' ); +const idEventDef EV_Thread_GetTicsPerSecond( "getTicsPerSecond", NULL, 'f' ); +const idEventDef EV_Thread_DebugLine( "debugLine", "vvvf" ); +const idEventDef EV_Thread_DebugArrow( "debugArrow", "vvvdf" ); +const idEventDef EV_Thread_DebugCircle( "debugCircle", "vvvfdf" ); +const idEventDef EV_Thread_DebugBounds( "debugBounds", "vvvf" ); +const idEventDef EV_Thread_DrawText( "drawText", "svfvdf" ); +const idEventDef EV_Thread_InfluenceActive( "influenceActive", NULL, 'd' ); + +CLASS_DECLARATION( idClass, idThread ) + EVENT( EV_Thread_Execute, idThread::Event_Execute ) + EVENT( EV_Thread_TerminateThread, idThread::Event_TerminateThread ) + EVENT( EV_Thread_Pause, idThread::Event_Pause ) + EVENT( EV_Thread_Wait, idThread::Event_Wait ) + EVENT( EV_Thread_WaitFrame, idThread::Event_WaitFrame ) + EVENT( EV_Thread_WaitFor, idThread::Event_WaitFor ) + EVENT( EV_Thread_WaitForThread, idThread::Event_WaitForThread ) + EVENT( EV_Thread_Print, idThread::Event_Print ) + EVENT( EV_Thread_PrintLn, idThread::Event_PrintLn ) + EVENT( EV_Thread_Say, idThread::Event_Say ) + EVENT( EV_Thread_Assert, idThread::Event_Assert ) + EVENT( EV_Thread_Trigger, idThread::Event_Trigger ) + EVENT( EV_Thread_SetCvar, idThread::Event_SetCvar ) + EVENT( EV_Thread_GetCvar, idThread::Event_GetCvar ) + EVENT( EV_Thread_Random, idThread::Event_Random ) + EVENT( EV_Thread_RandomInt, idThread::Event_RandomInt ) + EVENT( EV_Thread_GetTime, idThread::Event_GetTime ) + EVENT( EV_Thread_KillThread, idThread::Event_KillThread ) + EVENT( EV_Thread_SetThreadName, idThread::Event_SetThreadName ) + EVENT( EV_Thread_GetEntity, idThread::Event_GetEntity ) + EVENT( EV_Thread_Spawn, idThread::Event_Spawn ) + EVENT( EV_Thread_CopySpawnArgs, idThread::Event_CopySpawnArgs ) + EVENT( EV_Thread_SetSpawnArg, idThread::Event_SetSpawnArg ) + EVENT( EV_Thread_SpawnString, idThread::Event_SpawnString ) + EVENT( EV_Thread_SpawnFloat, idThread::Event_SpawnFloat ) + EVENT( EV_Thread_SpawnVector, idThread::Event_SpawnVector ) + EVENT( EV_Thread_ClearPersistantArgs, idThread::Event_ClearPersistantArgs ) + EVENT( EV_Thread_SetPersistantArg, idThread::Event_SetPersistantArg ) + EVENT( EV_Thread_GetPersistantString, idThread::Event_GetPersistantString ) + EVENT( EV_Thread_GetPersistantFloat, idThread::Event_GetPersistantFloat ) + EVENT( EV_Thread_GetPersistantVector, idThread::Event_GetPersistantVector ) + EVENT( EV_Thread_AngToForward, idThread::Event_AngToForward ) + EVENT( EV_Thread_AngToRight, idThread::Event_AngToRight ) + EVENT( EV_Thread_AngToUp, idThread::Event_AngToUp ) + EVENT( EV_Thread_Sine, idThread::Event_GetSine ) + EVENT( EV_Thread_Cosine, idThread::Event_GetCosine ) + EVENT( EV_Thread_ArcSine, idThread::Event_GetArcSine ) + EVENT( EV_Thread_ArcCosine, idThread::Event_GetArcCosine ) + EVENT( EV_Thread_SquareRoot, idThread::Event_GetSquareRoot ) + EVENT( EV_Thread_Normalize, idThread::Event_VecNormalize ) + EVENT( EV_Thread_VecLength, idThread::Event_VecLength ) + EVENT( EV_Thread_VecDotProduct, idThread::Event_VecDotProduct ) + EVENT( EV_Thread_VecCrossProduct, idThread::Event_VecCrossProduct ) + EVENT( EV_Thread_VecToAngles, idThread::Event_VecToAngles ) + EVENT( EV_Thread_VecToOrthoBasisAngles, idThread::Event_VecToOrthoBasisAngles ) + EVENT( EV_Thread_RotateVector, idThread::Event_RotateVector ) + EVENT( EV_Thread_OnSignal, idThread::Event_OnSignal ) + EVENT( EV_Thread_ClearSignal, idThread::Event_ClearSignalThread ) + EVENT( EV_Thread_SetCamera, idThread::Event_SetCamera ) + EVENT( EV_Thread_FirstPerson, idThread::Event_FirstPerson ) + EVENT( EV_Thread_Trace, idThread::Event_Trace ) + EVENT( EV_Thread_TracePoint, idThread::Event_TracePoint ) + EVENT( EV_Thread_GetTraceFraction, idThread::Event_GetTraceFraction ) + EVENT( EV_Thread_GetTraceEndPos, idThread::Event_GetTraceEndPos ) + EVENT( EV_Thread_GetTraceNormal, idThread::Event_GetTraceNormal ) + EVENT( EV_Thread_GetTraceEntity, idThread::Event_GetTraceEntity ) + EVENT( EV_Thread_GetTraceJoint, idThread::Event_GetTraceJoint ) + EVENT( EV_Thread_GetTraceBody, idThread::Event_GetTraceBody ) + EVENT( EV_Thread_FadeIn, idThread::Event_FadeIn ) + EVENT( EV_Thread_FadeOut, idThread::Event_FadeOut ) + EVENT( EV_Thread_FadeTo, idThread::Event_FadeTo ) + EVENT( EV_SetShaderParm, idThread::Event_SetShaderParm ) + EVENT( EV_Thread_StartMusic, idThread::Event_StartMusic ) + EVENT( EV_Thread_Warning, idThread::Event_Warning ) + EVENT( EV_Thread_Error, idThread::Event_Error ) + EVENT( EV_Thread_StrLen, idThread::Event_StrLen ) + EVENT( EV_Thread_StrLeft, idThread::Event_StrLeft ) + EVENT( EV_Thread_StrRight, idThread::Event_StrRight ) + EVENT( EV_Thread_StrSkip, idThread::Event_StrSkip ) + EVENT( EV_Thread_StrMid, idThread::Event_StrMid ) + EVENT( EV_Thread_StrToFloat, idThread::Event_StrToFloat ) + EVENT( EV_Thread_RadiusDamage, idThread::Event_RadiusDamage ) + EVENT( EV_Thread_IsClient, idThread::Event_IsClient ) + EVENT( EV_Thread_IsMultiplayer, idThread::Event_IsMultiplayer ) + EVENT( EV_Thread_GetFrameTime, idThread::Event_GetFrameTime ) + EVENT( EV_Thread_GetTicsPerSecond, idThread::Event_GetTicsPerSecond ) + EVENT( EV_CacheSoundShader, idThread::Event_CacheSoundShader ) + EVENT( EV_Thread_DebugLine, idThread::Event_DebugLine ) + EVENT( EV_Thread_DebugArrow, idThread::Event_DebugArrow ) + EVENT( EV_Thread_DebugCircle, idThread::Event_DebugCircle ) + EVENT( EV_Thread_DebugBounds, idThread::Event_DebugBounds ) + EVENT( EV_Thread_DrawText, idThread::Event_DrawText ) + EVENT( EV_Thread_InfluenceActive, idThread::Event_InfluenceActive ) +END_CLASS + +idThread *idThread::currentThread = NULL; +int idThread::threadIndex = 0; +idList idThread::threadList; +trace_t idThread::trace; + +/* +================ +idThread::CurrentThread +================ +*/ +idThread *idThread::CurrentThread() { + return currentThread; +} + +/* +================ +idThread::CurrentThreadNum +================ +*/ +int idThread::CurrentThreadNum() { + if ( currentThread ) { + return currentThread->GetThreadNum(); + } else { + return 0; + } +} + +/* +================ +idThread::BeginMultiFrameEvent +================ +*/ +bool idThread::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( currentThread == NULL ) { + gameLocal.Error( "idThread::BeginMultiFrameEvent called without a current thread" ); + return false; + } + return currentThread->interpreter.BeginMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::EndMultiFrameEvent +================ +*/ +void idThread::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( currentThread == NULL ) { + gameLocal.Error( "idThread::EndMultiFrameEvent called without a current thread" ); + return; + } + currentThread->interpreter.EndMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread() { + Init(); + SetThreadName( va( "thread_%d", threadIndex ) ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idEntity *self, const function_t *func ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.EnterObjectFunction( self, func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( const function_t *func ) { + assert( func ); + + Init(); + SetThreadName( func->Name() ); + interpreter.EnterFunction( func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, const function_t *func, int args ) { + Init(); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::~idThread +================ +*/ +idThread::~idThread() { + idThread *thread; + int i; + int n; + + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: end thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } + threadList.Remove( this ); + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->WaitingOnThread() == this ) { + thread->ThreadCallback( this ); + } + } + + if ( currentThread == this ) { + currentThread = NULL; + } +} + +/* +================ +idThread::ManualDelete +================ +*/ +void idThread::ManualDelete() { + interpreter.terminateOnExit = false; +} + +/* +================ +idThread::Save +================ +*/ +void idThread::Save( idSaveGame *savefile ) const { + + // We will check on restore that threadNum is still the same, + // threads should have been restored in the same order. + savefile->WriteInt( threadNum ); + + savefile->WriteObject( waitingForThread ); + savefile->WriteInt( waitingFor ); + savefile->WriteInt( waitingUntil ); + + interpreter.Save( savefile ); + + savefile->WriteDict( &spawnArgs ); + savefile->WriteString( threadName ); + + savefile->WriteInt( lastExecuteTime ); + savefile->WriteInt( creationTime ); + + savefile->WriteBool( manualControl ); +} + +/* +================ +idThread::Restore +================ +*/ +void idThread::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( threadNum ); + + savefile->ReadObject( reinterpret_cast( waitingForThread ) ); + savefile->ReadInt( waitingFor ); + savefile->ReadInt( waitingUntil ); + + interpreter.Restore( savefile ); + + savefile->ReadDict( &spawnArgs ); + savefile->ReadString( threadName ); + + savefile->ReadInt( lastExecuteTime ); + savefile->ReadInt( creationTime ); + + savefile->ReadBool( manualControl ); +} + +/* +================ +idThread::Init +================ +*/ +void idThread::Init() { + // create a unique threadNum + do { + threadIndex++; + if ( threadIndex == 0 ) { + threadIndex = 1; + } + } while( GetThread( threadIndex ) ); + + threadNum = threadIndex; + threadList.Append( this ); + + creationTime = gameLocal.time; + lastExecuteTime = 0; + manualControl = false; + + ClearWaitFor(); + + interpreter.SetThread( this ); +} + +/* +================ +idThread::GetThread +================ +*/ +idThread *idThread::GetThread( int num ) { + int i; + int n; + idThread *thread; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->GetThreadNum() == num ) { + return thread; + } + } + + return NULL; +} + +/* +================ +idThread::DisplayInfo +================ +*/ +void idThread::DisplayInfo() { + gameLocal.Printf( + "%12i: '%s'\n" + " File: %s(%d)\n" + " Created: %d (%d ms ago)\n" + " Status: ", + threadNum, threadName.c_str(), + interpreter.CurrentFile(), interpreter.CurrentLine(), + creationTime, gameLocal.time - creationTime ); + + if ( interpreter.threadDying ) { + gameLocal.Printf( "Dying\n" ); + } else if ( interpreter.doneProcessing ) { + gameLocal.Printf( + "Paused since %d (%d ms)\n" + " Reason: ", lastExecuteTime, gameLocal.time - lastExecuteTime ); + if ( waitingForThread ) { + gameLocal.Printf( "Waiting for thread #%3i '%s'\n", waitingForThread->GetThreadNum(), waitingForThread->GetThreadName() ); + } else if ( ( waitingFor != ENTITYNUM_NONE ) && ( waitingFor < MAX_GENTITIES ) && ( gameLocal.entities[ waitingFor ] ) ) { + gameLocal.Printf( "Waiting for entity #%3i '%s'\n", waitingFor, gameLocal.entities[ waitingFor ]->name.c_str() ); + } else if ( waitingUntil ) { + gameLocal.Printf( "Waiting until %d (%d ms total wait time)\n", waitingUntil, waitingUntil - lastExecuteTime ); + } else { + gameLocal.Printf( "None\n" ); + } + } else { + gameLocal.Printf( "Processing\n" ); + } + + interpreter.DisplayInfo(); + + gameLocal.Printf( "\n" ); +} + +/* +================ +idThread::ListThreads_f +================ +*/ +void idThread::ListThreads_f( const idCmdArgs &args ) { + int i; + int n; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + //threadList[ i ]->DisplayInfo(); + gameLocal.Printf( "%3i: %-20s : %s(%d)\n", threadList[ i ]->threadNum, threadList[ i ]->threadName.c_str(), threadList[ i ]->interpreter.CurrentFile(), threadList[ i ]->interpreter.CurrentLine() ); + } + gameLocal.Printf( "%d active threads\n\n", n ); +} + +/* +================ +idThread::Restart +================ +*/ +void idThread::Restart() { + int i; + int n; + + // reset the threadIndex + threadIndex = 0; + + currentThread = NULL; + n = threadList.Num(); + for( i = n - 1; i >= 0; i-- ) { + delete threadList[ i ]; + } + threadList.Clear(); + + memset( &trace, 0, sizeof( trace ) ); + trace.c.entityNum = ENTITYNUM_NONE; +} + +/* +================ +idThread::DelayedStart +================ +*/ +void idThread::DelayedStart( int delay ) { + CancelEvents( &EV_Thread_Execute ); + if ( gameLocal.time <= 0 ) { + delay++; + } + PostEventMS( &EV_Thread_Execute, delay ); +} + +/* +================ +idThread::Start +================ +*/ +bool idThread::Start() { + bool result; + + CancelEvents( &EV_Thread_Execute ); + result = Execute(); + + return result; +} + +/* +================ +idThread::SetThreadName +================ +*/ +void idThread::SetThreadName( const char *name ) { + threadName = name; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( int threadnum, idEntity *obj ) { + idThread *thread; + + if ( !threadnum ) { + return; + } + + thread = GetThread( threadnum ); + if ( thread ) { + thread->ObjectMoveDone( obj ); + } +} + +/* +================ +idThread::End +================ +*/ +void idThread::End() { + // Tell thread to die. It will exit on its own. + Pause(); + interpreter.threadDying = true; +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( const char *name ) { + int i; + int num; + int len; + const char *ptr; + idThread *thread; + + // see if the name uses a wild card + ptr = strchr( name, '*' ); + if ( ptr ) { + len = ptr - name; + } else { + len = strlen( name ); + } + + // kill only those threads whose name matches name + num = threadList.Num(); + for( i = 0; i < num; i++ ) { + thread = threadList[ i ]; + if ( !idStr::Cmpn( thread->GetThreadName(), name, len ) ) { + thread->End(); + } + } +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( thread ) { + // Tell thread to die. It will delete itself on it's own. + thread->End(); + } +} + +/* +================ +idThread::Execute +================ +*/ +bool idThread::Execute() { + idThread *oldThread; + bool done; + + if ( manualControl && ( waitingUntil > gameLocal.time ) ) { + return false; + } + + oldThread = currentThread; + currentThread = this; + + lastExecuteTime = gameLocal.time; + ClearWaitFor(); + done = interpreter.Execute(); + if ( done ) { + End(); + if ( interpreter.terminateOnExit ) { + PostEventMS( &EV_Remove, 0 ); + } + } else if ( !manualControl ) { + if ( waitingUntil > lastExecuteTime ) { + PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); + } else if ( interpreter.MultiFrameEventInProgress() ) { + PostEventMS( &EV_Thread_Execute, 1 ); + } + } + + currentThread = oldThread; + + return done; +} + +/* +================ +idThread::IsWaiting + +Checks if thread is still waiting for some event to occur. +================ +*/ +bool idThread::IsWaiting() { + if ( waitingForThread || ( waitingFor != ENTITYNUM_NONE ) ) { + return true; + } + + if ( waitingUntil && ( waitingUntil > gameLocal.time ) ) { + return true; + } + + return false; +} + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( const function_t *func, bool clearStack ) { + ClearWaitFor(); + interpreter.EnterFunction( func, clearStack ); +} + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( idEntity *self, const function_t *func, bool clearStack ) { + assert( self ); + ClearWaitFor(); + interpreter.EnterObjectFunction( self, func, clearStack ); +} + +/* +================ +idThread::ClearWaitFor +================ +*/ +void idThread::ClearWaitFor() { + waitingFor = ENTITYNUM_NONE; + waitingForThread = NULL; + waitingUntil = 0; +} + +/* +================ +idThread::IsWaitingFor +================ +*/ +bool idThread::IsWaitingFor( idEntity *obj ) { + assert( obj ); + return waitingFor == obj->entityNumber; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( idEntity *obj ) { + assert( obj ); + + if ( IsWaitingFor( obj ) ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::ThreadCallback +================ +*/ +void idThread::ThreadCallback( idThread *thread ) { + if ( interpreter.threadDying ) { + return; + } + + if ( thread == waitingForThread ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::Event_SetThreadName +================ +*/ +void idThread::Event_SetThreadName( const char *name ) { + SetThreadName( name ); +} + +/* +================ +idThread::Error +================ +*/ +void idThread::Error( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Error( text ); +} + +/* +================ +idThread::Warning +================ +*/ +void idThread::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Warning( text ); +} + +/* +================ +idThread::ReturnString +================ +*/ +void idThread::ReturnString( const char *text ) { + gameLocal.program.ReturnString( text ); +} + +/* +================ +idThread::ReturnFloat +================ +*/ +void idThread::ReturnFloat( float value ) { + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnInt +================ +*/ +void idThread::ReturnInt( int value ) { + // true integers aren't supported in the compiler, + // so int values are stored as floats + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnVector +================ +*/ +void idThread::ReturnVector( idVec3 const &vec ) { + gameLocal.program.ReturnVector( vec ); +} + +/* +================ +idThread::ReturnEntity +================ +*/ +void idThread::ReturnEntity( idEntity *ent ) { + gameLocal.program.ReturnEntity( ent ); +} + +/* +================ +idThread::Event_Execute +================ +*/ +void idThread::Event_Execute() { + Execute(); +} + +/* +================ +idThread::Pause +================ +*/ +void idThread::Pause() { + ClearWaitFor(); + interpreter.doneProcessing = true; +} + +/* +================ +idThread::WaitMS +================ +*/ +void idThread::WaitMS( int time ) { + Pause(); + waitingUntil = gameLocal.time + time; +} + +/* +================ +idThread::WaitSec +================ +*/ +void idThread::WaitSec( float time ) { + WaitMS( SEC2MS( time ) ); +} + +/* +================ +idThread::WaitFrame +================ +*/ +void idThread::WaitFrame() { + Pause(); + + // manual control threads don't set waitingUntil so that they can be run again + // that frame if necessary. + if ( !manualControl ) { + waitingUntil = gameLocal.time + 1; + } +} + +/*********************************************************************** + + Script callable events + +***********************************************************************/ + +/* +================ +idThread::Event_TerminateThread +================ +*/ +void idThread::Event_TerminateThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + KillThread( num ); +} + +/* +================ +idThread::Event_Pause +================ +*/ +void idThread::Event_Pause() { + Pause(); +} + +/* +================ +idThread::Event_Wait +================ +*/ +void idThread::Event_Wait( float time ) { + WaitSec( time ); +} + +/* +================ +idThread::Event_WaitFrame +================ +*/ +void idThread::Event_WaitFrame() { + WaitFrame(); +} + +/* +================ +idThread::Event_WaitFor +================ +*/ +void idThread::Event_WaitFor( idEntity *ent ) { + if ( ent && ent->RespondsTo( EV_Thread_SetCallback ) ) { + ent->ProcessEvent( &EV_Thread_SetCallback ); + if ( gameLocal.program.GetReturnedInteger() ) { + Pause(); + waitingFor = ent->entityNumber; + } + } +} + +/* +================ +idThread::Event_WaitForThread +================ +*/ +void idThread::Event_WaitForThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( !thread ) { + if ( g_debugScript.GetBool() ) { + // just print a warning and continue executing + Warning( "Thread %d not running", num ); + } + } else { + Pause(); + waitingForThread = thread; + } +} + +/* +================ +idThread::Event_Print +================ +*/ +void idThread::Event_Print( const char *text ) { + gameLocal.Printf( "%s", text ); +} + +/* +================ +idThread::Event_PrintLn +================ +*/ +void idThread::Event_PrintLn( const char *text ) { + gameLocal.Printf( "%s\n", text ); +} + +/* +================ +idThread::Event_Say +================ +*/ +void idThread::Event_Say( const char *text ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); +} + +/* +================ +idThread::Event_Assert +================ +*/ +void idThread::Event_Assert( float value ) { + assert( value ); +} + +/* +================ +idThread::Event_Trigger +================ +*/ +void idThread::Event_Trigger( idEntity *ent ) { + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + ent->TriggerGuis(); + } +} + +/* +================ +idThread::Event_SetCvar +================ +*/ +void idThread::Event_SetCvar( const char *name, const char *value ) const { + cvarSystem->SetCVarString( name, value ); +} + +/* +================ +idThread::Event_GetCvar +================ +*/ +void idThread::Event_GetCvar( const char *name ) const { + ReturnString( cvarSystem->GetCVarString( name ) ); +} + +/* +================ +idThread::Event_Random +================ +*/ +void idThread::Event_Random( float range ) const { + float result; + + result = gameLocal.random.RandomFloat(); + ReturnFloat( range * result ); +} + + +void idThread::Event_RandomInt( int range ) const { + int result; + result = gameLocal.random.RandomInt(range); + ReturnFloat(result); +} + + +/* +================ +idThread::Event_GetTime +================ +*/ +void idThread::Event_GetTime() { + + ReturnFloat( MS2SEC( gameLocal.realClientTime ) ); + + /* Script always uses realClient time to determine scripty stuff. ( This Fixes Weapon Animation timing bugs ) + if ( common->IsMultiplayer() ) { + ReturnFloat( MS2SEC( gameLocal.GetServerGameTimeMs() ) ); + } else { + ReturnFloat( MS2SEC( gameLocal.realClientTime ) ); + } + */ +} + +/* +================ +idThread::Event_KillThread +================ +*/ +void idThread::Event_KillThread( const char *name ) { + KillThread( name ); +} + +/* +================ +idThread::Event_GetEntity +================ +*/ +void idThread::Event_GetEntity( const char *name ) { + int entnum; + idEntity *ent; + + assert( name ); + + if ( name[ 0 ] == '*' ) { + entnum = atoi( &name[ 1 ] ); + if ( ( entnum < 0 ) || ( entnum >= MAX_GENTITIES ) ) { + Error( "Entity number in string out of range." ); + return; + } + ReturnEntity( gameLocal.entities[ entnum ] ); + } else { + ent = gameLocal.FindEntity( name ); + ReturnEntity( ent ); + } +} + +/* +================ +idThread::Event_Spawn +================ +*/ +void idThread::Event_Spawn( const char *classname ) { + idEntity *ent; + + spawnArgs.Set( "classname", classname ); + gameLocal.SpawnEntityDef( spawnArgs, &ent ); + ReturnEntity( ent ); + spawnArgs.Clear(); +} + +/* +================ +idThread::Event_CopySpawnArgs +================ +*/ +void idThread::Event_CopySpawnArgs( idEntity *ent ) { + spawnArgs.Copy( ent->spawnArgs ); +} + +/* +================ +idThread::Event_SetSpawnArg +================ +*/ +void idThread::Event_SetSpawnArg( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +/* +================ +idThread::Event_SpawnString +================ +*/ +void idThread::Event_SpawnString( const char *key, const char *defaultvalue ) { + const char *result; + + spawnArgs.GetString( key, defaultvalue, &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_SpawnFloat +================ +*/ +void idThread::Event_SpawnFloat( const char *key, float defaultvalue ) { + float result; + + spawnArgs.GetFloat( key, va( "%f", defaultvalue ), result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_SpawnVector +================ +*/ +void idThread::Event_SpawnVector( const char *key, idVec3 &defaultvalue ) { + idVec3 result; + + spawnArgs.GetVector( key, va( "%f %f %f", defaultvalue.x, defaultvalue.y, defaultvalue.z ), result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_ClearPersistantArgs +================ +*/ +void idThread::Event_ClearPersistantArgs() { + gameLocal.persistentLevelInfo.Clear(); +} + + +/* +================ +idThread::Event_SetPersistantArg +================ +*/ +void idThread::Event_SetPersistantArg( const char *key, const char *value ) { + gameLocal.persistentLevelInfo.Set( key, value ); +} + +/* +================ +idThread::Event_GetPersistantString +================ +*/ +void idThread::Event_GetPersistantString( const char *key ) { + const char *result; + + gameLocal.persistentLevelInfo.GetString( key, "", &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_GetPersistantFloat +================ +*/ +void idThread::Event_GetPersistantFloat( const char *key ) { + float result; + + gameLocal.persistentLevelInfo.GetFloat( key, "0", result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_GetPersistantVector +================ +*/ +void idThread::Event_GetPersistantVector( const char *key ) { + idVec3 result; + + gameLocal.persistentLevelInfo.GetVector( key, "0 0 0", result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_AngToForward +================ +*/ +void idThread::Event_AngToForward( idAngles &ang ) { + ReturnVector( ang.ToForward() ); +} + +/* +================ +idThread::Event_AngToRight +================ +*/ +void idThread::Event_AngToRight( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, &vec ); + ReturnVector( vec ); +} + +/* +================ +idThread::Event_AngToUp +================ +*/ +void idThread::Event_AngToUp( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, NULL, &vec ); + ReturnVector( vec ); +} + +/* +================ +idThread::Event_GetSine +================ +*/ +void idThread::Event_GetSine( float angle ) { + ReturnFloat( idMath::Sin( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetCosine +================ +*/ +void idThread::Event_GetCosine( float angle ) { + ReturnFloat( idMath::Cos( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetArcSine +================ +*/ +void idThread::Event_GetArcSine( float a ) { + ReturnFloat(RAD2DEG(idMath::ASin(a))); +} + +/* +================ +idThread::Event_GetArcCosine +================ +*/ +void idThread::Event_GetArcCosine( float a ) { + ReturnFloat(RAD2DEG(idMath::ACos(a))); +} + +/* +================ +idThread::Event_GetSquareRoot +================ +*/ +void idThread::Event_GetSquareRoot( float theSquare ) { + ReturnFloat( idMath::Sqrt( theSquare ) ); +} + +/* +================ +idThread::Event_VecNormalize +================ +*/ +void idThread::Event_VecNormalize( idVec3 &vec ) { + idVec3 n; + + n = vec; + n.Normalize(); + ReturnVector( n ); +} + +/* +================ +idThread::Event_VecLength +================ +*/ +void idThread::Event_VecLength( idVec3 &vec ) { + ReturnFloat( vec.Length() ); +} + +/* +================ +idThread::Event_VecDotProduct +================ +*/ +void idThread::Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnFloat( vec1 * vec2 ); +} + +/* +================ +idThread::Event_VecCrossProduct +================ +*/ +void idThread::Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnVector( vec1.Cross( vec2 ) ); +} + +/* +================ +idThread::Event_VecToAngles +================ +*/ +void idThread::Event_VecToAngles( idVec3 &vec ) { + idAngles ang = vec.ToAngles(); + ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +/* +================ +idThread::Event_VecToOrthoBasisAngles +================ +*/ +void idThread::Event_VecToOrthoBasisAngles( idVec3 &vec ) { + idVec3 left, up; + idAngles ang; + + vec.OrthogonalBasis( left, up ); + idMat3 axis( left, up, vec ); + + ang = axis.ToAngles(); + + ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +void idThread::Event_RotateVector( idVec3 &vec, idVec3 &ang ) { + + idAngles tempAng(ang); + idMat3 axis = tempAng.ToMat3(); + idVec3 ret = vec * axis; + ReturnVector(ret); + +} + +/* +================ +idThread::Event_OnSignal +================ +*/ +void idThread::Event_OnSignal( int signal, idEntity *ent, const char *func ) { + const function_t *function; + + assert( func ); + + if ( ent == NULL ) { + Error( "Entity not found" ); + return; + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + function = gameLocal.program.FindFunction( func ); + if ( !function ) { + Error( "Function '%s' not found", func ); + } + + ent->SetSignal( ( signalNum_t )signal, this, function ); +} + +/* +================ +idThread::Event_ClearSignalThread +================ +*/ +void idThread::Event_ClearSignalThread( int signal, idEntity *ent ) { + if ( ent == NULL ) { + Error( "Entity not found" ); + return; + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignalThread( ( signalNum_t )signal, this ); +} + +/* +================ +idThread::Event_SetCamera +================ +*/ +void idThread::Event_SetCamera( idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + return; + } + + if ( !ent->IsType( idCamera::Type ) ) { + Error( "Entity is not a camera" ); + return; + } + + gameLocal.SetCamera( ( idCamera * )ent ); +} + +/* +================ +idThread::Event_FirstPerson +================ +*/ +void idThread::Event_FirstPerson() { + gameLocal.SetCamera( NULL ); +} + +/* +================ +idThread::Event_Trace +================ +*/ +void idThread::Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ) { + if ( mins == vec3_origin && maxs == vec3_origin ) { + gameLocal.clip.TracePoint( trace, start, end, contents_mask, passEntity ); + } else { + gameLocal.clip.TraceBounds( trace, start, end, idBounds( mins, maxs ), contents_mask, passEntity ); + } + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_TracePoint +================ +*/ +void idThread::Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ) { + gameLocal.clip.TracePoint( trace, start, end, contents_mask, passEntity ); + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceFraction +================ +*/ +void idThread::Event_GetTraceFraction() { + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceEndPos +================ +*/ +void idThread::Event_GetTraceEndPos() { + ReturnVector( trace.endpos ); +} + +/* +================ +idThread::Event_GetTraceNormal +================ +*/ +void idThread::Event_GetTraceNormal() { + if ( trace.fraction < 1.0f ) { + ReturnVector( trace.c.normal ); + } else { + ReturnVector( vec3_origin ); + } +} + +/* +================ +idThread::Event_GetTraceEntity +================ +*/ +void idThread::Event_GetTraceEntity() { + if ( trace.fraction < 1.0f ) { + ReturnEntity( gameLocal.entities[ trace.c.entityNum ] ); + } else { + ReturnEntity( ( idEntity * )NULL ); + } +} + +/* +================ +idThread::Event_GetTraceJoint +================ +*/ +void idThread::Event_GetTraceJoint() { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); + if ( af && af->IsType( idAFEntity_Base::Type ) && af->IsActiveAF() ) { + ReturnString( af->GetAnimator()->GetJointName( CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ) ) ); + return; + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_GetTraceBody +================ +*/ +void idThread::Event_GetTraceBody() { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); + if ( af && af->IsType( idAFEntity_Base::Type ) && af->IsActiveAF() ) { + int bodyId = af->BodyForClipModelId( trace.c.id ); + idAFBody *body = af->GetAFPhysics()->GetBody( bodyId ); + if ( body ) { + ReturnString( body->GetName() ); + return; + } + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_FadeIn +================ +*/ +void idThread::Event_FadeIn( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 0.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeOut +================ +*/ +void idThread::Event_FadeOut( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 1.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeTo +================ +*/ +void idThread::Event_FadeTo( idVec3 &color, float alpha, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], alpha ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_SetShaderParm +================ +*/ +void idThread::Event_SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_GLOBAL_SHADER_PARMS ) ) { + Error( "shader parm index (%d) out of range", parmnum ); + return; + } + + gameLocal.globalShaderParms[ parmnum ] = value; +} + +/* +================ +idThread::Event_StartMusic +================ +*/ +void idThread::Event_StartMusic( const char *text ) { + gameSoundWorld->PlayShaderDirectly( text ); +} + +/* +================ +idThread::Event_Warning +================ +*/ +void idThread::Event_Warning( const char *text ) { + Warning( "%s", text ); +} + +/* +================ +idThread::Event_Error +================ +*/ +void idThread::Event_Error( const char *text ) { + Error( "%s", text ); +} + +/* +================ +idThread::Event_StrLen +================ +*/ +void idThread::Event_StrLen( const char *string ) { + int len; + + len = strlen( string ); + idThread::ReturnInt( len ); +} + +/* +================ +idThread::Event_StrLeft +================ +*/ +void idThread::Event_StrLeft( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idStr result( string, 0, num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrRight +================ +*/ +void idThread::Event_StrRight( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idThread::ReturnString( string + len - num ); +} + +/* +================ +idThread::Event_StrSkip +================ +*/ +void idThread::Event_StrSkip( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( string ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( string + num ); +} + +/* +================ +idThread::Event_StrMid +================ +*/ +void idThread::Event_StrMid( const char *string, int start, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + if ( start < 0 ) { + start = 0; + } + len = strlen( string ); + if ( start > len ) { + start = len; + } + + if ( start + num > len ) { + num = len - start; + } + + idStr result( string, start, start + num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrToFloat( const char *string ) +================ +*/ +void idThread::Event_StrToFloat( const char *string ) { + float result; + + result = atof( string ); + idThread::ReturnFloat( result ); +} + +/* +================ +idThread::Event_RadiusDamage +================ +*/ +void idThread::Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ) { + gameLocal.RadiusDamage( origin, inflictor, attacker, ignore, ignore, damageDefName, dmgPower ); +} + +/* +================ +idThread::Event_IsClient +================ +*/ +void idThread::Event_IsClient() { + idThread::ReturnFloat( common->IsClient() ); +} + +/* +================ +idThread::Event_IsMultiplayer +================ +*/ +void idThread::Event_IsMultiplayer() { + idThread::ReturnFloat( common->IsMultiplayer() ); +} + +/* +================ +idThread::Event_GetFrameTime +================ +*/ +void idThread::Event_GetFrameTime() { + idThread::ReturnFloat( MS2SEC( gameLocal.time - gameLocal.previousTime ) ); +} + +/* +================ +idThread::Event_GetTicsPerSecond +================ +*/ +void idThread::Event_GetTicsPerSecond() { + idThread::ReturnFloat( com_engineHz_latched ); +} + +/* +================ +idThread::Event_CacheSoundShader +================ +*/ +void idThread::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idThread::Event_DebugLine +================ +*/ +void idThread::Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ) { + gameRenderWorld->DebugLine( idVec4( color.x, color.y, color.z, 0.0f ), start, end, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugArrow +================ +*/ +void idThread::Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ) { + gameRenderWorld->DebugArrow( idVec4( color.x, color.y, color.z, 0.0f ), start, end, size, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugCircle +================ +*/ +void idThread::Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ) { + gameRenderWorld->DebugCircle( idVec4( color.x, color.y, color.z, 0.0f ), origin, dir, radius, numSteps, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugBounds +================ +*/ +void idThread::Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ) { + gameRenderWorld->DebugBounds( idVec4( color.x, color.y, color.z, 0.0f ), idBounds( mins, maxs ), vec3_origin, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DrawText +================ +*/ +void idThread::Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ) { + gameRenderWorld->DrawText( text, origin, scale, idVec4( color.x, color.y, color.z, 0.0f ), gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), align, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_InfluenceActive +================ +*/ +void idThread::Event_InfluenceActive() { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player != NULL && player->GetInfluenceLevel() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} diff --git a/neo/d3xp/script/Script_Thread.h b/neo/d3xp/script/Script_Thread.h new file mode 100644 index 00000000..b2c2c34f --- /dev/null +++ b/neo/d3xp/script/Script_Thread.h @@ -0,0 +1,334 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SCRIPT_THREAD_H__ +#define __SCRIPT_THREAD_H__ + +extern const idEventDef EV_Thread_Execute; +extern const idEventDef EV_Thread_SetCallback; +extern const idEventDef EV_Thread_TerminateThread; +extern const idEventDef EV_Thread_Pause; +extern const idEventDef EV_Thread_Wait; +extern const idEventDef EV_Thread_WaitFrame; +extern const idEventDef EV_Thread_WaitFor; +extern const idEventDef EV_Thread_WaitForThread; +extern const idEventDef EV_Thread_Print; +extern const idEventDef EV_Thread_PrintLn; +extern const idEventDef EV_Thread_Say; +extern const idEventDef EV_Thread_Assert; +extern const idEventDef EV_Thread_Trigger; +extern const idEventDef EV_Thread_SetCvar; +extern const idEventDef EV_Thread_GetCvar; +extern const idEventDef EV_Thread_Random; +extern const idEventDef EV_Thread_GetTime; +extern const idEventDef EV_Thread_KillThread; +extern const idEventDef EV_Thread_SetThreadName; +extern const idEventDef EV_Thread_GetEntity; +extern const idEventDef EV_Thread_Spawn; +extern const idEventDef EV_Thread_SetSpawnArg; +extern const idEventDef EV_Thread_SpawnString; +extern const idEventDef EV_Thread_SpawnFloat; +extern const idEventDef EV_Thread_SpawnVector; +extern const idEventDef EV_Thread_AngToForward; +extern const idEventDef EV_Thread_AngToRight; +extern const idEventDef EV_Thread_AngToUp; +extern const idEventDef EV_Thread_Sine; +extern const idEventDef EV_Thread_Cosine; +extern const idEventDef EV_Thread_Normalize; +extern const idEventDef EV_Thread_VecLength; +extern const idEventDef EV_Thread_VecDotProduct; +extern const idEventDef EV_Thread_VecCrossProduct; +extern const idEventDef EV_Thread_OnSignal; +extern const idEventDef EV_Thread_ClearSignal; +extern const idEventDef EV_Thread_SetCamera; +extern const idEventDef EV_Thread_FirstPerson; +extern const idEventDef EV_Thread_TraceFraction; +extern const idEventDef EV_Thread_TracePos; +extern const idEventDef EV_Thread_FadeIn; +extern const idEventDef EV_Thread_FadeOut; +extern const idEventDef EV_Thread_FadeTo; +extern const idEventDef EV_Thread_Restart; + +class idThread : public idClass { +private: + static idThread *currentThread; + + idThread *waitingForThread; + int waitingFor; + int waitingUntil; + idInterpreter interpreter; + + idDict spawnArgs; + + int threadNum; + idStr threadName; + + int lastExecuteTime; + int creationTime; + + bool manualControl; + + static int threadIndex; + static idList threadList; + + static trace_t trace; + + void Init(); + void Pause(); + + void Event_Execute(); + void Event_SetThreadName( const char *name ); + + // + // script callable Events + // + void Event_TerminateThread( int num ); + void Event_Pause(); + void Event_Wait( float time ); + void Event_WaitFrame(); + void Event_WaitFor( idEntity *ent ); + void Event_WaitForThread( int num ); + void Event_Print( const char *text ); + void Event_PrintLn( const char *text ); + void Event_Say( const char *text ); + void Event_Assert( float value ); + void Event_Trigger( idEntity *ent ); + void Event_SetCvar( const char *name, const char *value ) const; + void Event_GetCvar( const char *name ) const; + void Event_Random( float range ) const; + void Event_RandomInt( int range ) const; + void Event_GetTime(); + void Event_KillThread( const char *name ); + void Event_GetEntity( const char *name ); + void Event_Spawn( const char *classname ); + void Event_CopySpawnArgs( idEntity *ent ); + void Event_SetSpawnArg( const char *key, const char *value ); + void Event_SpawnString( const char *key, const char *defaultvalue ); + void Event_SpawnFloat( const char *key, float defaultvalue ); + void Event_SpawnVector( const char *key, idVec3 &defaultvalue ); + void Event_ClearPersistantArgs(); + void Event_SetPersistantArg( const char *key, const char *value ); + void Event_GetPersistantString( const char *key ); + void Event_GetPersistantFloat( const char *key ); + void Event_GetPersistantVector( const char *key ); + void Event_AngToForward( idAngles &ang ); + void Event_AngToRight( idAngles &ang ); + void Event_AngToUp( idAngles &ang ); + void Event_GetSine( float angle ); + void Event_GetCosine( float angle ); + void Event_GetArcSine( float a ); + void Event_GetArcCosine( float a ); + void Event_GetSquareRoot( float theSquare ); + void Event_VecNormalize( idVec3 &vec ); + void Event_VecLength( idVec3 &vec ); + void Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecToAngles( idVec3 &vec ); + void Event_VecToOrthoBasisAngles( idVec3 &vec ); + void Event_RotateVector( idVec3 &vec, idVec3 &ang ); + void Event_OnSignal( int signal, idEntity *ent, const char *func ); + void Event_ClearSignalThread( int signal, idEntity *ent ); + void Event_SetCamera( idEntity *ent ); + void Event_FirstPerson(); + void Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ); + void Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ); + void Event_GetTraceFraction(); + void Event_GetTraceEndPos(); + void Event_GetTraceNormal(); + void Event_GetTraceEntity(); + void Event_GetTraceJoint(); + void Event_GetTraceBody(); + void Event_FadeIn( idVec3 &color, float time ); + void Event_FadeOut( idVec3 &color, float time ); + void Event_FadeTo( idVec3 &color, float alpha, float time ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_StartMusic( const char *name ); + void Event_Warning( const char *text ); + void Event_Error( const char *text ); + void Event_StrLen( const char *string ); + void Event_StrLeft( const char *string, int num ); + void Event_StrRight( const char *string, int num ); + void Event_StrSkip( const char *string, int num ); + void Event_StrMid( const char *string, int start, int num ); + void Event_StrToFloat( const char *string ); + void Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ); + void Event_IsClient(); + void Event_IsMultiplayer(); + void Event_GetFrameTime(); + void Event_GetTicsPerSecond(); + void Event_CacheSoundShader( const char *soundName ); + void Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ); + void Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ); + void Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ); + void Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ); + void Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ); + void Event_InfluenceActive(); + +public: + CLASS_PROTOTYPE( idThread ); + + idThread(); + idThread( idEntity *self, const function_t *func ); + idThread( const function_t *func ); + idThread( idInterpreter *source, const function_t *func, int args ); + idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ); + + virtual ~idThread(); + + // tells the thread manager not to delete this thread when it ends + void ManualDelete(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void EnableDebugInfo() { interpreter.debug = true; }; + void DisableDebugInfo() { interpreter.debug = false; }; + + void WaitMS( int time ); + void WaitSec( float time ); + void WaitFrame(); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( const function_t *func, bool clearStack ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( idEntity *obj, const function_t *func, bool clearStack ); + + void DisplayInfo(); + static idThread *GetThread( int num ); + static void ListThreads_f( const idCmdArgs &args ); + static void Restart(); + static void ObjectMoveDone( int threadnum, idEntity *obj ); + + static idList& GetThreads (); + + bool IsDoneProcessing (); + bool IsDying (); + + void End(); + static void KillThread( const char *name ); + static void KillThread( int num ); + bool Execute(); + void ManualControl() { manualControl = true; CancelEvents( &EV_Thread_Execute ); }; + void DoneProcessing() { interpreter.doneProcessing = true; }; + void ContinueProcessing() { interpreter.doneProcessing = false; }; + bool ThreadDying() { return interpreter.threadDying; }; + void EndThread() { interpreter.threadDying = true; }; + bool IsWaiting(); + void ClearWaitFor(); + bool IsWaitingFor( idEntity *obj ); + void ObjectMoveDone( idEntity *obj ); + void ThreadCallback( idThread *thread ); + void DelayedStart( int delay ); + bool Start(); + idThread *WaitingOnThread(); + void SetThreadNum( int num ); + int GetThreadNum(); + void SetThreadName( const char *name ); + const char *GetThreadName(); + + void Error( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + void Warning( VERIFY_FORMAT_STRING const char *fmt, ... ) const; + + static idThread *CurrentThread(); + static int CurrentThreadNum(); + static bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + static void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + + static void ReturnString( const char *text ); + static void ReturnFloat( float value ); + static void ReturnInt( int value ); + static void ReturnVector( idVec3 const &vec ); + static void ReturnEntity( idEntity *ent ); +}; + +/* +================ +idThread::WaitingOnThread +================ +*/ +ID_INLINE idThread *idThread::WaitingOnThread() { + return waitingForThread; +} + +/* +================ +idThread::SetThreadNum +================ +*/ +ID_INLINE void idThread::SetThreadNum( int num ) { + threadNum = num; +} + +/* +================ +idThread::GetThreadNum +================ +*/ +ID_INLINE int idThread::GetThreadNum() { + return threadNum; +} + +/* +================ +idThread::GetThreadName +================ +*/ +ID_INLINE const char *idThread::GetThreadName() { + return threadName.c_str(); +} + +/* +================ +idThread::GetThreads +================ +*/ +ID_INLINE idList& idThread::GetThreads () { + return threadList; +} + +/* +================ +idThread::IsDoneProcessing +================ +*/ +ID_INLINE bool idThread::IsDoneProcessing () { + return interpreter.doneProcessing; +} + +/* +================ +idThread::IsDying +================ +*/ +ID_INLINE bool idThread::IsDying () { + return interpreter.threadDying; +} + +#endif /* !__SCRIPT_THREAD_H__ */ diff --git a/neo/doom3.sln b/neo/doom3.sln new file mode 100644 index 00000000..c7c5d1d1 --- /dev/null +++ b/neo/doom3.sln @@ -0,0 +1,73 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idLib", "idlib.vcxproj", "{49BEC5C6-B964-417A-851E-808886B57400}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Doom3BFG", "doomexe.vcxproj", "{49BEC5C6-B964-417A-851E-808886B57420}" + ProjectSection(ProjectDependencies) = postProject + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F} = {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game-d3xp", "game-d3xp.vcxproj", "{0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "amplitude", "amplitude\amplitude.vcxproj", "{57DBA8C7-2BBF-44CA-8189-600F90DC29DD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "external", "external.vcxproj", "{B679FDA2-CB76-4348-906B-4CAA294195FD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "doomclassic", "..\doomclassic\doomclassic.vcxproj", "{D87ADC61-3968-4A89-824F-01365AB6BD88}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "timidity", "..\doomclassic\timidity\timidity.vcxproj", "{3267F0ED-FE57-4348-91D7-AA8A4976750F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + Retail|Win32 = Retail|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {49BEC5C6-B964-417A-851E-808886B57400}.Debug|Win32.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Debug|Win32.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Release|Win32.ActiveCfg = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Release|Win32.Build.0 = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Retail|Win32.ActiveCfg = Retail|Win32 + {49BEC5C6-B964-417A-851E-808886B57400}.Retail|Win32.Build.0 = Retail|Win32 + {49BEC5C6-B964-417A-851E-808886B57420}.Debug|Win32.ActiveCfg = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57420}.Debug|Win32.Build.0 = Debug|Win32 + {49BEC5C6-B964-417A-851E-808886B57420}.Release|Win32.ActiveCfg = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57420}.Release|Win32.Build.0 = Release|Win32 + {49BEC5C6-B964-417A-851E-808886B57420}.Retail|Win32.ActiveCfg = Retail|Win32 + {49BEC5C6-B964-417A-851E-808886B57420}.Retail|Win32.Build.0 = Retail|Win32 + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}.Debug|Win32.ActiveCfg = Debug|Win32 + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}.Debug|Win32.Build.0 = Debug|Win32 + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}.Release|Win32.ActiveCfg = Release|Win32 + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}.Release|Win32.Build.0 = Release|Win32 + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}.Retail|Win32.ActiveCfg = Retail|Win32 + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F}.Retail|Win32.Build.0 = Retail|Win32 + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD}.Debug|Win32.ActiveCfg = Debug|Win32 + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD}.Debug|Win32.Build.0 = Debug|Win32 + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD}.Release|Win32.ActiveCfg = Release|Win32 + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD}.Release|Win32.Build.0 = Release|Win32 + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD}.Retail|Win32.ActiveCfg = Retail|Win32 + {57DBA8C7-2BBF-44CA-8189-600F90DC29DD}.Retail|Win32.Build.0 = Retail|Win32 + {B679FDA2-CB76-4348-906B-4CAA294195FD}.Debug|Win32.ActiveCfg = Debug|Win32 + {B679FDA2-CB76-4348-906B-4CAA294195FD}.Debug|Win32.Build.0 = Debug|Win32 + {B679FDA2-CB76-4348-906B-4CAA294195FD}.Release|Win32.ActiveCfg = Release|Win32 + {B679FDA2-CB76-4348-906B-4CAA294195FD}.Release|Win32.Build.0 = Release|Win32 + {B679FDA2-CB76-4348-906B-4CAA294195FD}.Retail|Win32.ActiveCfg = Retail|Win32 + {B679FDA2-CB76-4348-906B-4CAA294195FD}.Retail|Win32.Build.0 = Retail|Win32 + {D87ADC61-3968-4A89-824F-01365AB6BD88}.Debug|Win32.ActiveCfg = Debug|Win32 + {D87ADC61-3968-4A89-824F-01365AB6BD88}.Debug|Win32.Build.0 = Debug|Win32 + {D87ADC61-3968-4A89-824F-01365AB6BD88}.Release|Win32.ActiveCfg = Release|Win32 + {D87ADC61-3968-4A89-824F-01365AB6BD88}.Release|Win32.Build.0 = Release|Win32 + {D87ADC61-3968-4A89-824F-01365AB6BD88}.Retail|Win32.ActiveCfg = Release|Win32 + {D87ADC61-3968-4A89-824F-01365AB6BD88}.Retail|Win32.Build.0 = Release|Win32 + {3267F0ED-FE57-4348-91D7-AA8A4976750F}.Debug|Win32.ActiveCfg = Debug|Win32 + {3267F0ED-FE57-4348-91D7-AA8A4976750F}.Debug|Win32.Build.0 = Debug|Win32 + {3267F0ED-FE57-4348-91D7-AA8A4976750F}.Release|Win32.ActiveCfg = Release|Win32 + {3267F0ED-FE57-4348-91D7-AA8A4976750F}.Release|Win32.Build.0 = Release|Win32 + {3267F0ED-FE57-4348-91D7-AA8A4976750F}.Retail|Win32.ActiveCfg = Release|Win32 + {3267F0ED-FE57-4348-91D7-AA8A4976750F}.Retail|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/neo/doomexe.vcxproj b/neo/doomexe.vcxproj new file mode 100644 index 00000000..6a7bcb4b --- /dev/null +++ b/neo/doomexe.vcxproj @@ -0,0 +1,550 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Retail + Win32 + + + + Doom3BFG + {49BEC5C6-B964-417A-851E-808886B57420} + DoomEXE + + + + + + + Win32Proj + + + + + + Application + MultiByte + + + Application + MultiByte + false + + + Application + MultiByte + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + AllRules.ruleset + + + AllRules.ruleset + AllRules.ruleset + + + + + $(VCInstallDir)PlatformSDK\include;$(ProjectDir)dxsdk_June2010\include;$(IncludePath) + $(VCInstallDir)PlatformSDK\lib;$(ProjectDir)dxsdk_June2010\lib\x86;$(LibraryPath) + $(VCInstallDir)PlatformSDK\lib;$(ProjectDir)dxsdk_June2010\lib\x86;$(LibraryPath) + $(VCInstallDir)PlatformSDK\include;$(ProjectDir)dxsdk_June2010\include;$(IncludePath) + $(VCInstallDir)PlatformSDK\lib;$(ProjectDir)dxsdk_June2010\lib\x86;$(LibraryPath) + $(VCInstallDir)PlatformSDK\include;$(ProjectDir)dxsdk_June2010\include;$(IncludePath) + + + + true + + + true + true + + + + + + + + true + $(DXSDK_DIR)\Include\;%(AdditionalIncludeDirectories) + + + true + true + $(DXSDK_DIR)\Include\;%(AdditionalIncludeDirectories) + $(DXSDK_DIR)\Include\;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions);ID_RETAIL + + + + + + + + + + true + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {d87adc61-3968-4a89-824f-01365ab6bd88} + false + true + false + true + false + + + {3267f0ed-fe57-4348-91d7-aa8a4976750f} + false + true + false + true + false + + + {b679fda2-cb76-4348-906b-4caa294195fd} + + + {0bc6fcc9-c65e-4b1f-9a58-0b9399987c9f} + + + {49bec5c6-b964-417a-851e-808886b57400} + false + + + + + + \ No newline at end of file diff --git a/neo/doomexe.vcxproj.filters b/neo/doomexe.vcxproj.filters new file mode 100644 index 00000000..5da08872 --- /dev/null +++ b/neo/doomexe.vcxproj.filters @@ -0,0 +1,1153 @@ + + + + + {0b350a6a-58d3-441e-bdcf-1115ff7edc1d} + + + {f5d345cb-9f26-4da2-9ef1-335e871dcce5} + + + {19b363f5-76a7-4256-b7f0-7a6ec012d09d} + + + {2b6b512d-2388-41b9-8880-402e156ec20b} + + + {8672dd57-2df1-454a-be5e-1ea2d6647cef} + + + {c4397186-0c42-4057-82b4-17dea14c173f} + + + {900f9550-2a58-4b22-82a9-298f01f6ac51} + + + {b3166d25-0562-49da-bc8d-961006fe1785} + + + {8d78c2c9-7073-4938-a19d-e04208bf7a18} + + + {9bc463bc-e3cf-4cce-9be3-bd2239fc67c5} + + + {a2d7a7d4-8b15-4874-b56b-e0ec38cef758} + + + {0266e842-a651-4b82-bc6f-4785aed69c9f} + + + {7bfc05c1-c366-44d9-8667-0cbbc1efff2f} + + + {35b72578-715a-4274-9370-56d30f5dea8e} + + + {d47aaec5-ecad-482e-9eab-f1d8ae41abbd} + + + {951ee3a5-56f9-4e38-b79e-e2cf75ae1056} + + + {36bf36af-8243-4a18-b2f8-f86e2c8b048d} + + + {b58e1849-e427-46ad-8671-cbd2170fd35b} + + + {88d7c0be-162a-494a-9b71-4cf4a3f1a568} + + + {de864e31-e19e-41ac-8186-9b27d6c29c03} + + + + + CM + + + CM + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Sound + + + Sound + + + Sys + + + Sys + + + Sys\RC + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + AAS + + + AAS + + + AAS + + + Renderer\OpenGL + + + Renderer\OpenGL + + + Renderer\OpenGL + + + Renderer\OpenGL + + + Sys\Win32 + + + Sys + + + Framework + + + Framework + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Sys + + + Sys + + + Sys + + + Sound\XAudio2 + + + Sound\XAudio2 + + + Sound\XAudio2 + + + Sound + + + Sound + + + Sys\Win32 + + + Sys\Win32 + + + Sys + + + Renderer + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Sys + + + Sys\Win32 + + + Sys\Win32 + + + Renderer + + + Renderer + + + Renderer + + + Renderer\DXT + + + Renderer\DXT + + + Renderer\Color + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + Renderer + + + Renderer + + + Framework + + + Renderer + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Framework + + + Framework + + + Renderer + + + Framework + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Framework + + + Renderer + + + Framework + + + Renderer + + + Renderer\Jobs\DynamicShadowVolume + + + Renderer\Jobs\DynamicShadowVolume + + + Framework + + + Framework + + + Framework + + + Renderer\Jobs\StaticShadowVolume + + + Renderer\Jobs\StaticShadowVolume + + + Renderer\Jobs + + + Renderer\Jobs\PreLightShadowVolume + + + Renderer\Jobs\PreLightShadowVolume + + + Renderer + + + Renderer + + + + + CM + + + CM + + + CM + + + CM + + + CM + + + CM + + + CM + + + CM + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Sound + + + Sound + + + Sound + + + Sound + + + Sys + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + Ui + + + AAS + + + AAS + + + AAS + + + AAS + + + Renderer\OpenGL + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys\Win32 + + + Sys + + + Framework + + + Sys\Win32 + + + Sys + + + Sys + + + Sys + + + Sound\XAudio2 + + + Sound\XAudio2 + + + Sound\XAudio2 + + + Sound + + + Sound + + + Sys\Win32 + + + Sys\Win32 + + + Sys + + + Renderer + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Framework\Decls + + + Sys\Win32 + + + Renderer + + + Renderer + + + Renderer + + + Renderer\OpenGL + + + Renderer + + + Renderer\DXT + + + Renderer\DXT + + + Renderer\DXT + + + Renderer\Color + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + SWF + + + Renderer + + + Framework + + + Renderer + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Framework + + + Framework + + + Renderer + + + Framework + + + Framework + + + Framework + + + Framework + + + Framework + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Sys + + + Framework + + + Framework + + + Framework + + + Renderer + + + Framework + + + Framework + + + Renderer + + + Renderer\Jobs\DynamicShadowVolume + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Renderer + + + Framework + + + Framework + + + Framework + + + Renderer\Jobs\StaticShadowVolume + + + Renderer\Jobs + + + Renderer\Jobs\PreLightShadowVolume + + + Renderer + + + Renderer + + + Renderer\OpenGL + + + Renderer + + + Renderer + + + Sys\Win32 + + + + + Sys\RC\res + + + Sys\RC\res + + + + + Sys\RC + + + \ No newline at end of file diff --git a/neo/external.vcxproj b/neo/external.vcxproj new file mode 100644 index 00000000..163b0e37 --- /dev/null +++ b/neo/external.vcxproj @@ -0,0 +1,192 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Retail + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {B679FDA2-CB76-4348-906B-4CAA294195FD} + Win32Proj + external + + + + + + + + + + + + StaticLibrary + true + Unicode + + + StaticLibrary + false + true + Unicode + + + StaticLibrary + false + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + \ No newline at end of file diff --git a/neo/external.vcxproj.filters b/neo/external.vcxproj.filters new file mode 100644 index 00000000..25dc3eae --- /dev/null +++ b/neo/external.vcxproj.filters @@ -0,0 +1,216 @@ + + + + + {be59fb10-0a86-4e8d-a0e3-f6029f24da5d} + + + {2d5b407e-8075-4eda-b4d1-1327db0580c6} + + + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + zlib + + + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + jpeg-6 + + + \ No newline at end of file diff --git a/neo/framework/BuildVersion.h b/neo/framework/BuildVersion.h new file mode 100644 index 00000000..fc5eae8e --- /dev/null +++ b/neo/framework/BuildVersion.h @@ -0,0 +1,32 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +const int BUILD_NUMBER_SAVE_VERSION_CHANGE = 1400; // Altering saves so that the version goes in the Details file that we read in during the enumeration phase + +const int BUILD_NUMBER = BUILD_NUMBER_SAVE_VERSION_CHANGE; +const int BUILD_NUMBER_MINOR = 0; \ No newline at end of file diff --git a/neo/framework/CVarSystem.cpp b/neo/framework/CVarSystem.cpp new file mode 100644 index 00000000..23efeb9e --- /dev/null +++ b/neo/framework/CVarSystem.cpp @@ -0,0 +1,1206 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar * idCVar::staticVars = NULL; + +extern idCVar net_allowCheats; + +/* +=============================================================================== + + idInternalCVar + +=============================================================================== +*/ + +class idInternalCVar : public idCVar { + friend class idCVarSystemLocal; +public: + idInternalCVar(); + idInternalCVar( const char *newName, const char *newValue, int newFlags ); + idInternalCVar( const idCVar *cvar ); + virtual ~idInternalCVar(); + + const char ** CopyValueStrings( const char **strings ); + void Update( const idCVar *cvar ); + void UpdateValue(); + void UpdateCheat(); + void Set( const char *newValue, bool force, bool fromServer ); + void Reset(); + +private: + idStr nameString; // name + idStr resetString; // resetting will change to this value + idStr valueString; // value + idStr descriptionString; // description + + virtual const char * InternalGetResetString() const; + + virtual void InternalSetString( const char *newValue ); + virtual void InternalServerSetString( const char *newValue ); + virtual void InternalSetBool( const bool newValue ); + virtual void InternalSetInteger( const int newValue ); + virtual void InternalSetFloat( const float newValue ); +}; + +/* +============ +idInternalCVar::idInternalCVar +============ +*/ +idInternalCVar::idInternalCVar() { +} + +/* +============ +idInternalCVar::idInternalCVar +============ +*/ +idInternalCVar::idInternalCVar( const char *newName, const char *newValue, int newFlags ) { + nameString = newName; + name = nameString.c_str(); + valueString = newValue; + value = valueString.c_str(); + resetString = newValue; + descriptionString = ""; + description = descriptionString.c_str(); + flags = ( newFlags & ~CVAR_STATIC ) | CVAR_MODIFIED; + valueMin = 1; + valueMax = -1; + valueStrings = NULL; + valueCompletion = 0; + UpdateValue(); + UpdateCheat(); + internalVar = this; +} + +/* +============ +idInternalCVar::idInternalCVar +============ +*/ +idInternalCVar::idInternalCVar( const idCVar *cvar ) { + nameString = cvar->GetName(); + name = nameString.c_str(); + valueString = cvar->GetString(); + value = valueString.c_str(); + resetString = cvar->GetString(); + descriptionString = cvar->GetDescription(); + description = descriptionString.c_str(); + flags = cvar->GetFlags() | CVAR_MODIFIED; + valueMin = cvar->GetMinValue(); + valueMax = cvar->GetMaxValue(); + valueStrings = CopyValueStrings( cvar->GetValueStrings() ); + valueCompletion = cvar->GetValueCompletion(); + UpdateValue(); + UpdateCheat(); + internalVar = this; +} + +/* +============ +idInternalCVar::~idInternalCVar +============ +*/ +idInternalCVar::~idInternalCVar() { + Mem_Free( valueStrings ); + valueStrings = NULL; +} + + +/* +============ +idInternalCVar::CopyValueStrings +============ +*/ +const char **idInternalCVar::CopyValueStrings( const char **strings ) { + int i, totalLength; + const char **ptr; + char *str; + + if ( !strings ) { + return NULL; + } + + totalLength = 0; + for ( i = 0; strings[i] != NULL; i++ ) { + totalLength += idStr::Length( strings[i] ) + 1; + } + + ptr = (const char **) Mem_Alloc( ( i + 1 ) * sizeof( char * ) + totalLength, TAG_CVAR ); + str = (char *) (((byte *)ptr) + ( i + 1 ) * sizeof( char * ) ); + + for ( i = 0; strings[i] != NULL; i++ ) { + ptr[i] = str; + strcpy( str, strings[i] ); + str += idStr::Length( strings[i] ) + 1; + } + ptr[i] = NULL; + + return ptr; +} + +/* +============ +idInternalCVar::Update +============ +*/ +void idInternalCVar::Update( const idCVar *cvar ) { + + // if this is a statically declared variable + if ( cvar->GetFlags() & CVAR_STATIC ) { + + if ( flags & CVAR_STATIC ) { + + // the code has more than one static declaration of the same variable, make sure they have the same properties + if ( resetString.Icmp( cvar->GetString() ) != 0 ) { + common->Warning( "CVar '%s' declared multiple times with different initial value", nameString.c_str() ); + } + if ( ( flags & (CVAR_BOOL|CVAR_INTEGER|CVAR_FLOAT) ) != ( cvar->GetFlags() & (CVAR_BOOL|CVAR_INTEGER|CVAR_FLOAT) ) ) { + common->Warning( "CVar '%s' declared multiple times with different type", nameString.c_str() ); + } + if ( valueMin != cvar->GetMinValue() || valueMax != cvar->GetMaxValue() ) { + common->Warning( "CVar '%s' declared multiple times with different minimum/maximum", nameString.c_str() ); + } + + } + + // the code is now specifying a variable that the user already set a value for, take the new value as the reset value + resetString = cvar->GetString(); + descriptionString = cvar->GetDescription(); + description = descriptionString.c_str(); + valueMin = cvar->GetMinValue(); + valueMax = cvar->GetMaxValue(); + Mem_Free( valueStrings ); + valueStrings = CopyValueStrings( cvar->GetValueStrings() ); + valueCompletion = cvar->GetValueCompletion(); + UpdateValue(); + cvarSystem->SetModifiedFlags( cvar->GetFlags() ); + } + + flags |= cvar->GetFlags(); + + UpdateCheat(); + + // only allow one non-empty reset string without a warning + if ( resetString.Length() == 0 ) { + resetString = cvar->GetString(); + } else if ( cvar->GetString()[0] && resetString.Cmp( cvar->GetString() ) != 0 ) { + common->Warning( "cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", nameString.c_str(), resetString.c_str(), cvar->GetString() ); + } +} + +/* +============ +idInternalCVar::UpdateValue +============ +*/ +void idInternalCVar::UpdateValue() { + bool clamped = false; + + if ( flags & CVAR_BOOL ) { + integerValue = ( atoi( value ) != 0 ); + floatValue = integerValue; + if ( idStr::Icmp( value, "0" ) != 0 && idStr::Icmp( value, "1" ) != 0 ) { + valueString = idStr( (bool)( integerValue != 0 ) ); + value = valueString.c_str(); + } + } else if ( flags & CVAR_INTEGER ) { + integerValue = (int)atoi( value ); + if ( valueMin < valueMax ) { + if ( integerValue < valueMin ) { + integerValue = (int)valueMin; + clamped = true; + } else if ( integerValue > valueMax ) { + integerValue = (int)valueMax; + clamped = true; + } + } + if ( clamped || !idStr::IsNumeric( value ) || idStr::FindChar( value, '.' ) ) { + valueString = idStr( integerValue ); + value = valueString.c_str(); + } + floatValue = (float)integerValue; + } else if ( flags & CVAR_FLOAT ) { + floatValue = (float)atof( value ); + if ( valueMin < valueMax ) { + if ( floatValue < valueMin ) { + floatValue = valueMin; + clamped = true; + } else if ( floatValue > valueMax ) { + floatValue = valueMax; + clamped = true; + } + } + if ( clamped || !idStr::IsNumeric( value ) ) { + valueString = idStr( floatValue ); + value = valueString.c_str(); + } + integerValue = (int)floatValue; + } else { + if ( valueStrings && valueStrings[0] ) { + integerValue = 0; + for ( int i = 0; valueStrings[i]; i++ ) { + if ( valueString.Icmp( valueStrings[i] ) == 0 ) { + integerValue = i; + break; + } + } + valueString = valueStrings[integerValue]; + value = valueString.c_str(); + floatValue = (float)integerValue; + } else if ( valueString.Length() < 32 ) { + floatValue = (float)atof( value ); + integerValue = (int)floatValue; + } else { + floatValue = 0.0f; + integerValue = 0; + } + } +} + +/* +============ +idInternalCVar::UpdateCheat +============ +*/ +void idInternalCVar::UpdateCheat() { + // all variables are considered cheats except for a few types + if ( flags & ( CVAR_NOCHEAT | CVAR_INIT | CVAR_ROM | CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_NETWORKSYNC ) ) { + flags &= ~CVAR_CHEAT; + } else { + flags |= CVAR_CHEAT; + } +} + +/* +============ +idInternalCVar::Set +============ +*/ +void idInternalCVar::Set( const char *newValue, bool force, bool fromServer ) { + if ( common->IsMultiplayer() && !fromServer ) { +#ifndef ID_TYPEINFO + if ( ( flags & CVAR_NETWORKSYNC ) && common->IsClient() ) { + common->Printf( "%s is a synced over the network and cannot be changed on a multiplayer client.\n", nameString.c_str() ); + return; + } +#endif + if ( ( flags & CVAR_CHEAT ) && !net_allowCheats.GetBool() ) { + common->Printf( "%s cannot be changed in multiplayer.\n", nameString.c_str() ); + return; + } + } + + if ( !newValue ) { + newValue = resetString.c_str(); + } + + if ( !force ) { + if ( flags & CVAR_ROM ) { + common->Printf( "%s is read only.\n", nameString.c_str() ); + return; + } + + if ( flags & CVAR_INIT ) { + common->Printf( "%s is write protected.\n", nameString.c_str() ); + return; + } + } + + if ( valueString.Icmp( newValue ) == 0 ) { + return; + } + + valueString = newValue; + value = valueString.c_str(); + UpdateValue(); + + SetModified(); + cvarSystem->SetModifiedFlags( flags ); +} + +/* +============ +idInternalCVar::Reset +============ +*/ +void idInternalCVar::Reset() { + valueString = resetString; + value = valueString.c_str(); + UpdateValue(); +} + +/* +============ +idInternalCVar::InternalGetResetString +============ +*/ +const char * idInternalCVar::InternalGetResetString() const { + return resetString; +} + +/* +============ +idInternalCVar::InternalSetString +============ +*/ +void idInternalCVar::InternalSetString( const char *newValue ) { + Set( newValue, true, false ); +} + +/* +=============== +idInternalCVar::InternalServerSetString +=============== +*/ +void idInternalCVar::InternalServerSetString( const char *newValue ) { + Set( newValue, true, true ); +} + +/* +============ +idInternalCVar::InternalSetBool +============ +*/ +void idInternalCVar::InternalSetBool( const bool newValue ) { + Set( idStr( newValue ), true, false ); +} + +/* +============ +idInternalCVar::InternalSetInteger +============ +*/ +void idInternalCVar::InternalSetInteger( const int newValue ) { + Set( idStr( newValue ), true, false ); +} + +/* +============ +idInternalCVar::InternalSetFloat +============ +*/ +void idInternalCVar::InternalSetFloat( const float newValue ) { + Set( idStr( newValue ), true, false ); +} + + +/* +=============================================================================== + + idCVarSystemLocal + +=============================================================================== +*/ + +class idCVarSystemLocal : public idCVarSystem { +public: + idCVarSystemLocal(); + + virtual ~idCVarSystemLocal() {} + + virtual void Init(); + virtual void Shutdown(); + virtual bool IsInitialized() const; + + virtual void Register( idCVar *cvar ); + + virtual idCVar * Find( const char *name ); + + virtual void SetCVarString( const char *name, const char *value, int flags = 0 ); + virtual void SetCVarBool( const char *name, const bool value, int flags = 0 ); + virtual void SetCVarInteger( const char *name, const int value, int flags = 0 ); + virtual void SetCVarFloat( const char *name, const float value, int flags = 0 ); + + virtual const char * GetCVarString( const char *name ) const; + virtual bool GetCVarBool( const char *name ) const; + virtual int GetCVarInteger( const char *name ) const; + virtual float GetCVarFloat( const char *name ) const; + + virtual bool Command( const idCmdArgs &args ); + + virtual void CommandCompletion( void(*callback)( const char *s ) ); + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ); + + virtual void SetModifiedFlags( int flags ); + virtual int GetModifiedFlags() const; + virtual void ClearModifiedFlags( int flags ); + + virtual void ResetFlaggedVariables( int flags ); + virtual void RemoveFlaggedAutoCompletion( int flags ); + virtual void WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const; + + virtual void MoveCVarsToDict( int flags, idDict & dict, bool onlyModified ) const; + virtual void SetCVarsFromDict( const idDict &dict ); + + void RegisterInternal( idCVar *cvar ); + idInternalCVar * FindInternal( const char *name ) const; + void SetInternal( const char *name, const char *value, int flags ); + +private: + bool initialized; + idList cvars; + idHashIndex cvarHash; + int modifiedFlags; + +private: + static void Toggle_f( const idCmdArgs &args ); + static void Set_f( const idCmdArgs &args ); + static void Reset_f( const idCmdArgs &args ); + static void ListByFlags( const idCmdArgs &args, cvarFlags_t flags ); + static void List_f( const idCmdArgs &args ); + static void Restart_f( const idCmdArgs &args ); + static void CvarAdd_f( const idCmdArgs &args ); +}; + +idCVarSystemLocal localCVarSystem; +idCVarSystem * cvarSystem = &localCVarSystem; + +#define NUM_COLUMNS 77 // 78 - 1 +#define NUM_NAME_CHARS 33 +#define NUM_DESCRIPTION_CHARS ( NUM_COLUMNS - NUM_NAME_CHARS ) +#define FORMAT_STRING "%-32s " + +const char *CreateColumn( const char *text, int columnWidth, const char *indent, idStr &string ) { + int i, lastLine; + + string.Clear(); + for ( lastLine = i = 0; text[i] != '\0'; i++ ) { + if ( i - lastLine >= columnWidth || text[i] == '\n' ) { + while( i > 0 && text[i] > ' ' && text[i] != '/' && text[i] != ',' && text[i] != '\\' ) { + i--; + } + while( lastLine < i ) { + string.Append( text[lastLine++] ); + } + string.Append( indent ); + lastLine++; + } + } + while( lastLine < i ) { + string.Append( text[lastLine++] ); + } + return string.c_str(); +} + +/* +============ +idCVarSystemLocal::FindInternal +============ +*/ +idInternalCVar *idCVarSystemLocal::FindInternal( const char *name ) const { + int hash = cvarHash.GenerateKey( name, false ); + for ( int i = cvarHash.First( hash ); i != -1; i = cvarHash.Next( i ) ) { + if ( cvars[i]->nameString.Icmp( name ) == 0 ) { + return cvars[i]; + } + } + return NULL; +} + +/* +============ +idCVarSystemLocal::SetInternal +============ +*/ +void idCVarSystemLocal::SetInternal( const char *name, const char *value, int flags ) { + int hash; + idInternalCVar *internal; + + internal = FindInternal( name ); + + if ( internal ) { + internal->InternalSetString( value ); + internal->flags |= flags & ~CVAR_STATIC; + internal->UpdateCheat(); + } else { + internal = new (TAG_SYSTEM) idInternalCVar( name, value, flags ); + hash = cvarHash.GenerateKey( internal->nameString.c_str(), false ); + cvarHash.Add( hash, cvars.Append( internal ) ); + } +} + +/* +============ +idCVarSystemLocal::idCVarSystemLocal +============ +*/ +idCVarSystemLocal::idCVarSystemLocal() { + initialized = false; + modifiedFlags = 0; +} + +/* +============ +idCVarSystemLocal::Init +============ +*/ +void idCVarSystemLocal::Init() { + + modifiedFlags = 0; + + cmdSystem->AddCommand( "toggle", Toggle_f, CMD_FL_SYSTEM, "toggles a cvar" ); + cmdSystem->AddCommand( "set", Set_f, CMD_FL_SYSTEM, "sets a cvar" ); + cmdSystem->AddCommand( "seta", Set_f, CMD_FL_SYSTEM, "sets a cvar" ); + cmdSystem->AddCommand( "sets", Set_f, CMD_FL_SYSTEM, "sets a cvar" ); + cmdSystem->AddCommand( "sett", Set_f, CMD_FL_SYSTEM, "sets a cvar" ); + cmdSystem->AddCommand( "setu", Set_f, CMD_FL_SYSTEM, "sets a cvar" ); + cmdSystem->AddCommand( "reset", Reset_f, CMD_FL_SYSTEM, "resets a cvar" ); + cmdSystem->AddCommand( "listCvars", List_f, CMD_FL_SYSTEM, "lists cvars" ); + cmdSystem->AddCommand( "cvar_restart", Restart_f, CMD_FL_SYSTEM, "restart the cvar system" ); + cmdSystem->AddCommand( "cvarAdd", CvarAdd_f, CMD_FL_SYSTEM, "adds a value to a numeric cvar" ); + + initialized = true; +} + +/* +============ +idCVarSystemLocal::Shutdown +============ +*/ +void idCVarSystemLocal::Shutdown() { + cvars.DeleteContents( true ); + cvarHash.Free(); + initialized = false; +} + +/* +============ +idCVarSystemLocal::IsInitialized +============ +*/ +bool idCVarSystemLocal::IsInitialized() const { + return initialized; +} + +/* +============ +idCVarSystemLocal::Register +============ +*/ +void idCVarSystemLocal::Register( idCVar *cvar ) { + int hash; + idInternalCVar *internal; + + cvar->SetInternalVar( cvar ); + + internal = FindInternal( cvar->GetName() ); + + if ( internal ) { + internal->Update( cvar ); + } else { + internal = new (TAG_SYSTEM) idInternalCVar( cvar ); + hash = cvarHash.GenerateKey( internal->nameString.c_str(), false ); + cvarHash.Add( hash, cvars.Append( internal ) ); + } + + cvar->SetInternalVar( internal ); +} + +/* +============ +idCVarSystemLocal::Find +============ +*/ +idCVar *idCVarSystemLocal::Find( const char *name ) { + return FindInternal( name ); +} + +/* +============ +idCVarSystemLocal::SetCVarString +============ +*/ +void idCVarSystemLocal::SetCVarString( const char *name, const char *value, int flags ) { + SetInternal( name, value, flags ); +} + +/* +============ +idCVarSystemLocal::SetCVarBool +============ +*/ +void idCVarSystemLocal::SetCVarBool( const char *name, const bool value, int flags ) { + SetInternal( name, idStr( value ), flags ); +} + +/* +============ +idCVarSystemLocal::SetCVarInteger +============ +*/ +void idCVarSystemLocal::SetCVarInteger( const char *name, const int value, int flags ) { + SetInternal( name, idStr( value ), flags ); +} + +/* +============ +idCVarSystemLocal::SetCVarFloat +============ +*/ +void idCVarSystemLocal::SetCVarFloat( const char *name, const float value, int flags ) { + SetInternal( name, idStr( value ), flags ); +} + +/* +============ +idCVarSystemLocal::GetCVarString +============ +*/ +const char *idCVarSystemLocal::GetCVarString( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetString(); + } + return ""; +} + +/* +============ +idCVarSystemLocal::GetCVarBool +============ +*/ +bool idCVarSystemLocal::GetCVarBool( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetBool(); + } + return false; +} + +/* +============ +idCVarSystemLocal::GetCVarInteger +============ +*/ +int idCVarSystemLocal::GetCVarInteger( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetInteger(); + } + return 0; +} + +/* +============ +idCVarSystemLocal::GetCVarFloat +============ +*/ +float idCVarSystemLocal::GetCVarFloat( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetFloat(); + } + return 0.0f; +} + +/* +============ +idCVarSystemLocal::Command +============ +*/ +bool idCVarSystemLocal::Command( const idCmdArgs &args ) { + idInternalCVar *internal; + + internal = FindInternal( args.Argv( 0 ) ); + + if ( internal == NULL ) { + return false; + } + + if ( args.Argc() == 1 ) { + // print the variable + common->Printf( "\"%s\" is:\"%s\"" S_COLOR_WHITE " default:\"%s\"\n", + internal->nameString.c_str(), internal->valueString.c_str(), internal->resetString.c_str() ); + if ( idStr::Length( internal->GetDescription() ) > 0 ) { + common->Printf( S_COLOR_WHITE "%s\n", internal->GetDescription() ); + } + } else { + // set the value + internal->Set( args.Args(), false, false ); + } + return true; +} + +/* +============ +idCVarSystemLocal::CommandCompletion +============ +*/ +void idCVarSystemLocal::CommandCompletion( void(*callback)( const char *s ) ) { + for( int i = 0; i < cvars.Num(); i++ ) { + callback( cvars[i]->GetName() ); + } +} + +/* +============ +idCVarSystemLocal::ArgCompletion +============ +*/ +void idCVarSystemLocal::ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) { + idCmdArgs args; + + args.TokenizeString( cmdString, false ); + + for( int i = 0; i < cvars.Num(); i++ ) { + if ( !cvars[i]->valueCompletion ) { + continue; + } + if ( idStr::Icmp( args.Argv( 0 ), cvars[i]->nameString.c_str() ) == 0 ) { + cvars[i]->valueCompletion( args, callback ); + break; + } + } +} + +/* +============ +idCVarSystemLocal::SetModifiedFlags +============ +*/ +void idCVarSystemLocal::SetModifiedFlags( int flags ) { + modifiedFlags |= flags; +} + +/* +============ +idCVarSystemLocal::GetModifiedFlags +============ +*/ +int idCVarSystemLocal::GetModifiedFlags() const { + return modifiedFlags; +} + +/* +============ +idCVarSystemLocal::ClearModifiedFlags +============ +*/ +void idCVarSystemLocal::ClearModifiedFlags( int flags ) { + modifiedFlags &= ~flags; +} + +/* +============ +idCVarSystemLocal::ResetFlaggedVariables +============ +*/ +void idCVarSystemLocal::ResetFlaggedVariables( int flags ) { + for( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + cvar->Set( NULL, true, true ); + } + } +} + +/* +============ +idCVarSystemLocal::RemoveFlaggedAutoCompletion +============ +*/ +void idCVarSystemLocal::RemoveFlaggedAutoCompletion( int flags ) { + for( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + cvar->valueCompletion = NULL; + } + } +} + +/* +============ +idCVarSystemLocal::WriteFlaggedVariables + +Appends lines containing "set variable value" for all variables +with the "flags" flag set to true. +============ +*/ +void idCVarSystemLocal::WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const { + for( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + f->Printf( "%s %s \"%s\"\n", setCmd, cvar->GetName(), cvar->GetString() ); + } + } +} + +/* +============ +idCVarSystemLocal::MoveCVarsToDict +============ +*/ +void idCVarSystemLocal::MoveCVarsToDict( int flags, idDict & dict, bool onlyModified ) const { + dict.Clear(); + for( int i = 0; i < cvars.Num(); i++ ) { + idCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + if ( onlyModified && idStr::Icmp( cvar->GetString(), cvar->GetDefaultString() ) == 0 ) { + continue; + } + dict.Set( cvar->GetName(), cvar->GetString() ); + } + } +} + +/* +============ +idCVarSystemLocal::SetCVarsFromDict +============ +*/ +void idCVarSystemLocal::SetCVarsFromDict( const idDict &dict ) { + idInternalCVar *internal; + + for( int i = 0; i < dict.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict.GetKeyVal( i ); + internal = FindInternal( kv->GetKey() ); + if ( internal ) { + internal->InternalServerSetString( kv->GetValue() ); + } + } +} + +/* +============ +idCVarSystemLocal::Toggle_f +============ +*/ +void idCVarSystemLocal::Toggle_f( const idCmdArgs &args ) { + int argc, i; + float current, set; + const char *text; + + argc = args.Argc(); + if ( argc < 2 ) { + common->Printf ("usage:\n" + " toggle - toggles between 0 and 1\n" + " toggle - toggles between 0 and \n" + " toggle [string 1] [string 2]...[string n] - cycles through all strings\n"); + return; + } + + idInternalCVar *cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + + if ( cvar == NULL ) { + common->Warning( "Toggle_f: cvar \"%s\" not found", args.Argv( 1 ) ); + return; + } + + if ( argc > 3 ) { + // cycle through multiple values + text = cvar->GetString(); + for( i = 2; i < argc; i++ ) { + if ( !idStr::Icmp( text, args.Argv( i ) ) ) { + // point to next value + i++; + break; + } + } + if ( i >= argc ) { + i = 2; + } + + common->Printf( "set %s = %s\n", args.Argv(1), args.Argv( i ) ); + cvar->Set( va("%s", args.Argv( i ) ), false, false ); + } else { + // toggle between 0 and 1 + current = cvar->GetFloat(); + if ( argc == 3 ) { + set = atof( args.Argv( 2 ) ); + } else { + set = 1.0f; + } + if ( current == 0.0f ) { + current = set; + } else { + current = 0.0f; + } + common->Printf( "set %s = %f\n", args.Argv(1), current ); + cvar->Set( idStr( current ), false, false ); + } +} + +/* +============ +idCVarSystemLocal::Set_f +============ +*/ +void idCVarSystemLocal::Set_f( const idCmdArgs &args ) { + const char *str; + + str = args.Args( 2, args.Argc() - 1 ); + localCVarSystem.SetCVarString( args.Argv(1), str ); +} + +/* +============ +idCVarSystemLocal::Reset_f +============ +*/ +void idCVarSystemLocal::Reset_f( const idCmdArgs &args ) { + idInternalCVar *cvar; + + if ( args.Argc() != 2 ) { + common->Printf ("usage: reset \n"); + return; + } + cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + + cvar->Reset(); +} + +/* +============ +idCVarSystemLocal::CvarAdd_f +============ +*/ +void idCVarSystemLocal::CvarAdd_f( const idCmdArgs &args ) { + if ( args.Argc() != 3 ) { + common->Printf ("usage: cvarAdd \n"); + } + + idInternalCVar *cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + + const float newValue = cvar->GetFloat() + atof( args.Argv( 2 ) ); + cvar->SetFloat( newValue ); + common->Printf( "%s = %f\n", cvar->GetName(), newValue ); +} +// +///* +//================================================ +//idSort_CommandDef +//================================================ +//*/ +//class idSort_InternalCVar : public idSort_Quick< const idInternalCVar *, idSort_InternalCVar > { +//public: +// int Compare( const idInternalCVar * & a, const idInternalCVar * & b ) const { return idStr::Icmp( a.GetName(), b.GetName() ); } +//}; + +void idCVarSystemLocal::ListByFlags( const idCmdArgs &args, cvarFlags_t flags ) { + int i, argNum; + idStr match, indent, string; + const idInternalCVar *cvar; + idListcvarList; + + enum { + SHOW_VALUE, + SHOW_DESCRIPTION, + SHOW_TYPE, + SHOW_FLAGS + } show; + + argNum = 1; + show = SHOW_VALUE; + + if ( idStr::Icmp( args.Argv( argNum ), "-" ) == 0 || idStr::Icmp( args.Argv( argNum ), "/" ) == 0 ) { + if ( idStr::Icmp( args.Argv( argNum + 1 ), "help" ) == 0 || idStr::Icmp( args.Argv( argNum + 1 ), "?" ) == 0 ) { + argNum = 3; + show = SHOW_DESCRIPTION; + } else if ( idStr::Icmp( args.Argv( argNum + 1 ), "type" ) == 0 || idStr::Icmp( args.Argv( argNum + 1 ), "range" ) == 0 ) { + argNum = 3; + show = SHOW_TYPE; + } else if ( idStr::Icmp( args.Argv( argNum + 1 ), "flags" ) == 0 ) { + argNum = 3; + show = SHOW_FLAGS; + } + } + + if ( args.Argc() > argNum ) { + match = args.Args( argNum, -1 ); + match.Replace( " ", "" ); + } else { + match = ""; + } + + for ( i = 0; i < localCVarSystem.cvars.Num(); i++ ) { + cvar = localCVarSystem.cvars[i]; + + if ( !( cvar->GetFlags() & flags ) ) { + continue; + } + + if ( match.Length() && !cvar->nameString.Filter( match, false ) ) { + continue; + } + + cvarList.Append( cvar ); + } + + //cvarList.SortWithTemplate( idSort_InternalCVar() ); + + switch( show ) { + case SHOW_VALUE: { + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + common->Printf( FORMAT_STRING S_COLOR_WHITE "\"%s\"\n", cvar->nameString.c_str(), cvar->valueString.c_str() ); + } + break; + } + case SHOW_DESCRIPTION: { + indent.Fill( ' ', NUM_NAME_CHARS ); + indent.Insert( "\n", 0 ); + + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + common->Printf( FORMAT_STRING S_COLOR_WHITE "%s\n", cvar->nameString.c_str(), CreateColumn( cvar->GetDescription(), NUM_DESCRIPTION_CHARS, indent, string ) ); + } + break; + } + case SHOW_TYPE: { + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + if ( cvar->GetFlags() & CVAR_BOOL ) { + common->Printf( FORMAT_STRING S_COLOR_CYAN "bool\n", cvar->GetName() ); + } else if ( cvar->GetFlags() & CVAR_INTEGER ) { + if ( cvar->GetMinValue() < cvar->GetMaxValue() ) { + common->Printf( FORMAT_STRING S_COLOR_GREEN "int " S_COLOR_WHITE "[%d, %d]\n", cvar->GetName(), (int) cvar->GetMinValue(), (int) cvar->GetMaxValue() ); + } else { + common->Printf( FORMAT_STRING S_COLOR_GREEN "int\n", cvar->GetName() ); + } + } else if ( cvar->GetFlags() & CVAR_FLOAT ) { + if ( cvar->GetMinValue() < cvar->GetMaxValue() ) { + common->Printf( FORMAT_STRING S_COLOR_RED "float " S_COLOR_WHITE "[%s, %s]\n", cvar->GetName(), idStr( cvar->GetMinValue() ).c_str(), idStr( cvar->GetMaxValue() ).c_str() ); + } else { + common->Printf( FORMAT_STRING S_COLOR_RED "float\n", cvar->GetName() ); + } + } else if ( cvar->GetValueStrings() ) { + common->Printf( FORMAT_STRING S_COLOR_WHITE "string " S_COLOR_WHITE "[", cvar->GetName() ); + for ( int j = 0; cvar->GetValueStrings()[j] != NULL; j++ ) { + if ( j ) { + common->Printf( S_COLOR_WHITE ", %s", cvar->GetValueStrings()[j] ); + } else { + common->Printf( S_COLOR_WHITE "%s", cvar->GetValueStrings()[j] ); + } + } + common->Printf( S_COLOR_WHITE "]\n" ); + } else { + common->Printf( FORMAT_STRING S_COLOR_WHITE "string\n", cvar->GetName() ); + } + } + break; + } + case SHOW_FLAGS: { + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + common->Printf( FORMAT_STRING, cvar->GetName() ); + string = ""; + if ( cvar->GetFlags() & CVAR_BOOL ) { + string += S_COLOR_CYAN "B "; + } else if ( cvar->GetFlags() & CVAR_INTEGER ) { + string += S_COLOR_GREEN "I "; + } else if ( cvar->GetFlags() & CVAR_FLOAT ) { + string += S_COLOR_RED "F "; + } else { + string += S_COLOR_WHITE "S "; + } + if ( cvar->GetFlags() & CVAR_SYSTEM ) { + string += S_COLOR_WHITE "SYS "; + } else if ( cvar->GetFlags() & CVAR_RENDERER ) { + string += S_COLOR_WHITE "RNDR "; + } else if ( cvar->GetFlags() & CVAR_SOUND ) { + string += S_COLOR_WHITE "SND "; + } else if ( cvar->GetFlags() & CVAR_GUI ) { + string += S_COLOR_WHITE "GUI "; + } else if ( cvar->GetFlags() & CVAR_GAME ) { + string += S_COLOR_WHITE "GAME "; + } else if ( cvar->GetFlags() & CVAR_TOOL ) { + string += S_COLOR_WHITE "TOOL "; + } else { + string += S_COLOR_WHITE " "; + } + string += ( cvar->GetFlags() & CVAR_SERVERINFO ) ? "SI " : " "; + string += ( cvar->GetFlags() & CVAR_STATIC ) ? "ST " : " "; + string += ( cvar->GetFlags() & CVAR_CHEAT ) ? "CH " : " "; + string += ( cvar->GetFlags() & CVAR_INIT ) ? "IN " : " "; + string += ( cvar->GetFlags() & CVAR_ROM ) ? "RO " : " "; + string += ( cvar->GetFlags() & CVAR_ARCHIVE ) ? "AR " : " "; + string += ( cvar->GetFlags() & CVAR_MODIFIED ) ? "MO " : " "; + string += "\n"; + common->Printf( string ); + } + break; + } + } + + common->Printf( "\n%i cvars listed\n\n", cvarList.Num() ); + common->Printf( "listCvar [search string] = list cvar values\n" + "listCvar -help [search string] = list cvar descriptions\n" + "listCvar -type [search string] = list cvar types\n" + "listCvar -flags [search string] = list cvar flags\n" ); +} + +/* +============ +idCVarSystemLocal::List_f +============ +*/ +void idCVarSystemLocal::List_f( const idCmdArgs &args ) { + ListByFlags( args, CVAR_ALL ); +} + +/* +============ +idCVarSystemLocal::Restart_f +============ +*/ +void idCVarSystemLocal::Restart_f( const idCmdArgs &args ) { + int i, hash; + idInternalCVar *cvar; + + for ( i = 0; i < localCVarSystem.cvars.Num(); i++ ) { + cvar = localCVarSystem.cvars[i]; + + // don't mess with rom values + if ( cvar->flags & ( CVAR_ROM | CVAR_INIT ) ) { + continue; + } + + // throw out any variables the user created + if ( !( cvar->flags & CVAR_STATIC ) ) { + hash = localCVarSystem.cvarHash.GenerateKey( cvar->nameString, false ); + delete cvar; + localCVarSystem.cvars.RemoveIndex( i ); + localCVarSystem.cvarHash.RemoveIndex( hash, i ); + i--; + continue; + } + + cvar->Reset(); + } +} diff --git a/neo/framework/CVarSystem.h b/neo/framework/CVarSystem.h new file mode 100644 index 00000000..e0dee0bc --- /dev/null +++ b/neo/framework/CVarSystem.h @@ -0,0 +1,311 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __CVARSYSTEM_H__ +#define __CVARSYSTEM_H__ + +/* +=============================================================================== + + Console Variables (CVars) are used to hold scalar or string variables + that can be changed or displayed at the console as well as accessed + directly in code. + + CVars are mostly used to hold settings that can be changed from the + console or saved to and loaded from configuration files. CVars are also + occasionally used to communicate information between different modules + of the program. + + CVars are restricted from having the same names as console commands to + keep the console interface from being ambiguous. + + CVars can be accessed from the console in three ways: + cvarName prints the current value + cvarName X sets the value to X if the variable exists + set cvarName X as above, but creates the CVar if not present + + CVars may be declared in the global namespace, in classes and in functions. + However declarations in classes and functions should always be static to + save space and time. Making CVars static does not change their + functionality due to their global nature. + + CVars should be contructed only through one of the constructors with name, + value, flags and description. The name, value and description parameters + to the constructor have to be static strings, do not use va() or the like + functions returning a string. + + CVars may be declared multiple times using the same name string. However, + they will all reference the same value and changing the value of one CVar + changes the value of all CVars with the same name. + + CVars should always be declared with the correct type flag: CVAR_BOOL, + CVAR_INTEGER or CVAR_FLOAT. If no such flag is specified the CVar + defaults to type string. If the CVAR_BOOL flag is used there is no need + to specify an argument auto-completion function because the CVar gets + one assigned automatically. + + CVars are automatically range checked based on their type and any min/max + or valid string set specified in the constructor. + + CVars are always considered cheats except when CVAR_NOCHEAT, CVAR_INIT, + CVAR_ROM, CVAR_ARCHIVE, CVAR_SERVERINFO, CVAR_NETWORKSYNC + is set. + +=============================================================================== +*/ + +typedef enum { + CVAR_ALL = -1, // all flags + CVAR_BOOL = BIT(0), // variable is a boolean + CVAR_INTEGER = BIT(1), // variable is an integer + CVAR_FLOAT = BIT(2), // variable is a float + CVAR_SYSTEM = BIT(3), // system variable + CVAR_RENDERER = BIT(4), // renderer variable + CVAR_SOUND = BIT(5), // sound variable + CVAR_GUI = BIT(6), // gui variable + CVAR_GAME = BIT(7), // game variable + CVAR_TOOL = BIT(8), // tool variable + CVAR_SERVERINFO = BIT(10), // sent from servers, available to menu + CVAR_NETWORKSYNC = BIT(11), // cvar is synced from the server to clients + CVAR_STATIC = BIT(12), // statically declared, not user created + CVAR_CHEAT = BIT(13), // variable is considered a cheat + CVAR_NOCHEAT = BIT(14), // variable is not considered a cheat + CVAR_INIT = BIT(15), // can only be set from the command-line + CVAR_ROM = BIT(16), // display only, cannot be set by user at all + CVAR_ARCHIVE = BIT(17), // set to cause it to be saved to a config file + CVAR_MODIFIED = BIT(18) // set when the variable is modified +} cvarFlags_t; + + +/* +=============================================================================== + + idCVar + +=============================================================================== +*/ + +class idCVar { +public: + // Never use the default constructor. + idCVar() { assert( typeid( this ) != typeid( idCVar ) ); } + + // Always use one of the following constructors. + idCVar( const char *name, const char *value, int flags, const char *description, + argCompletion_t valueCompletion = NULL ); + idCVar( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, argCompletion_t valueCompletion = NULL ); + idCVar( const char *name, const char *value, int flags, const char *description, + const char **valueStrings, argCompletion_t valueCompletion = NULL ); + + virtual ~idCVar() {} + + const char * GetName() const { return internalVar->name; } + int GetFlags() const { return internalVar->flags; } + const char * GetDescription() const { return internalVar->description; } + float GetMinValue() const { return internalVar->valueMin; } + float GetMaxValue() const { return internalVar->valueMax; } + const char ** GetValueStrings() const { return valueStrings; } + argCompletion_t GetValueCompletion() const { return valueCompletion; } + + bool IsModified() const { return ( internalVar->flags & CVAR_MODIFIED ) != 0; } + void SetModified() { internalVar->flags |= CVAR_MODIFIED; } + void ClearModified() { internalVar->flags &= ~CVAR_MODIFIED; } + + const char * GetDefaultString() const { return internalVar->InternalGetResetString(); } + const char * GetString() const { return internalVar->value; } + bool GetBool() const { return ( internalVar->integerValue != 0 ); } + int GetInteger() const { return internalVar->integerValue; } + float GetFloat() const { return internalVar->floatValue; } + + void SetString( const char *value ) { internalVar->InternalSetString( value ); } + void SetBool( const bool value ) { internalVar->InternalSetBool( value ); } + void SetInteger( const int value ) { internalVar->InternalSetInteger( value ); } + void SetFloat( const float value ) { internalVar->InternalSetFloat( value ); } + + void SetInternalVar( idCVar *cvar ) { internalVar = cvar; } + + static void RegisterStaticVars(); + +protected: + const char * name; // name + const char * value; // value + const char * description; // description + int flags; // CVAR_? flags + float valueMin; // minimum value + float valueMax; // maximum value + const char ** valueStrings; // valid value strings + argCompletion_t valueCompletion; // value auto-completion function + int integerValue; // atoi( string ) + float floatValue; // atof( value ) + idCVar * internalVar; // internal cvar + idCVar * next; // next statically declared cvar + +private: + void Init( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, const char **valueStrings, argCompletion_t valueCompletion ); + + virtual void InternalSetString( const char *newValue ) {} + virtual void InternalSetBool( const bool newValue ) {} + virtual void InternalSetInteger( const int newValue ) {} + virtual void InternalSetFloat( const float newValue ) {} + + virtual const char * InternalGetResetString() const { return value; } + + static idCVar * staticVars; +}; + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + argCompletion_t valueCompletion ) { + if ( !valueCompletion && ( flags & CVAR_BOOL ) ) { + valueCompletion = idCmdSystem::ArgCompletion_Boolean; + } + Init( name, value, flags, description, 1, -1, NULL, valueCompletion ); +} + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, argCompletion_t valueCompletion ) { + Init( name, value, flags, description, valueMin, valueMax, NULL, valueCompletion ); +} + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + const char **valueStrings, argCompletion_t valueCompletion ) { + Init( name, value, flags, description, 1, -1, valueStrings, valueCompletion ); +} + + +/* +=============================================================================== + + idCVarSystem + +=============================================================================== +*/ + +class idCVarSystem { +public: + virtual ~idCVarSystem() {} + + virtual void Init() = 0; + virtual void Shutdown() = 0; + virtual bool IsInitialized() const = 0; + + // Registers a CVar. + virtual void Register( idCVar *cvar ) = 0; + + // Finds the CVar with the given name. + // Returns NULL if there is no CVar with the given name. + virtual idCVar * Find( const char *name ) = 0; + + // Sets the value of a CVar by name. + virtual void SetCVarString( const char *name, const char *value, int flags = 0 ) = 0; + virtual void SetCVarBool( const char *name, const bool value, int flags = 0 ) = 0; + virtual void SetCVarInteger( const char *name, const int value, int flags = 0 ) = 0; + virtual void SetCVarFloat( const char *name, const float value, int flags = 0 ) = 0; + + // Gets the value of a CVar by name. + virtual const char * GetCVarString( const char *name ) const = 0; + virtual bool GetCVarBool( const char *name ) const = 0; + virtual int GetCVarInteger( const char *name ) const = 0; + virtual float GetCVarFloat( const char *name ) const = 0; + + // Called by the command system when argv(0) doesn't match a known command. + // Returns true if argv(0) is a variable reference and prints or changes the CVar. + virtual bool Command( const idCmdArgs &args ) = 0; + + // Command and argument completion using callback for each valid string. + virtual void CommandCompletion( void(*callback)( const char *s ) ) = 0; + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) = 0; + + // Sets/gets/clears modified flags that tell what kind of CVars have changed. + virtual void SetModifiedFlags( int flags ) = 0; + virtual int GetModifiedFlags() const = 0; + virtual void ClearModifiedFlags( int flags ) = 0; + + // Resets variables with one of the given flags set. + virtual void ResetFlaggedVariables( int flags ) = 0; + + // Removes auto-completion from the flagged variables. + virtual void RemoveFlaggedAutoCompletion( int flags ) = 0; + + // Writes variables with one of the given flags set to the given file. + virtual void WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const = 0; + + // Moves CVars to and from dictionaries. + virtual void MoveCVarsToDict( int flags, idDict & dict, bool onlyModified = false ) const = 0; + virtual void SetCVarsFromDict( const idDict &dict ) = 0; +}; + +extern idCVarSystem * cvarSystem; + + +/* +=============================================================================== + + CVar Registration + + Each DLL using CVars has to declare a private copy of the static variable + idCVar::staticVars like this: idCVar * idCVar::staticVars = NULL; + Furthermore idCVar::RegisterStaticVars() has to be called after the + cvarSystem pointer is set when the DLL is first initialized. + +=============================================================================== +*/ + +ID_INLINE void idCVar::Init( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, const char **valueStrings, argCompletion_t valueCompletion ) { + this->name = name; + this->value = value; + this->flags = flags; + this->description = description; + this->flags = flags | CVAR_STATIC; + this->valueMin = valueMin; + this->valueMax = valueMax; + this->valueStrings = valueStrings; + this->valueCompletion = valueCompletion; + this->integerValue = 0; + this->floatValue = 0.0f; + this->internalVar = this; + if ( staticVars != (idCVar *)0xFFFFFFFF ) { + this->next = staticVars; + staticVars = this; + } else { + cvarSystem->Register( this ); + } +} + +ID_INLINE void idCVar::RegisterStaticVars() { + if ( staticVars != (idCVar *)0xFFFFFFFF ) { + for ( idCVar *cvar = staticVars; cvar; cvar = cvar->next ) { + cvarSystem->Register( cvar ); + } + staticVars = (idCVar *)0xFFFFFFFF; + } +} + +#endif /* !__CVARSYSTEM_H__ */ diff --git a/neo/framework/CmdSystem.cpp b/neo/framework/CmdSystem.cpp new file mode 100644 index 00000000..561341e1 --- /dev/null +++ b/neo/framework/CmdSystem.cpp @@ -0,0 +1,801 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef ID_RETAIL +idCVar net_allowCheats( "net_allowCheats", "0", CVAR_BOOL | CVAR_ROM, "Allow cheats in multiplayer" ); +#else +idCVar net_allowCheats( "net_allowCheats", "0", CVAR_BOOL | CVAR_NOCHEAT, "Allow cheats in multiplayer" ); +#endif + +/* +=============================================================================== + + idCmdSystemLocal + +=============================================================================== +*/ + +typedef struct commandDef_s { + struct commandDef_s * next; + char * name; + cmdFunction_t function; + argCompletion_t argCompletion; + int flags; + char * description; +} commandDef_t; + +/* +================================================ +idCmdSystemLocal +================================================ +*/ +class idCmdSystemLocal : public idCmdSystem { +public: + virtual void Init(); + virtual void Shutdown(); + + virtual void AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion = NULL ); + virtual void RemoveCommand( const char *cmdName ); + virtual void RemoveFlaggedCommands( int flags ); + + virtual void CommandCompletion( void(*callback)( const char *s ) ); + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ); + virtual void ExecuteCommandText( const char * text ); + virtual void AppendCommandText( const char * text ); + + virtual void BufferCommandText( cmdExecution_t exec, const char *text ); + virtual void ExecuteCommandBuffer(); + + virtual void ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ); + virtual void ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ); + + virtual void BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ); + + virtual void SetupReloadEngine( const idCmdArgs &args ); + virtual bool PostReloadEngine(); + + void SetWait( int numFrames ) { wait = numFrames; } + commandDef_t * GetCommands() const { return commands; } + +private: + static const int MAX_CMD_BUFFER = 0x10000; + + commandDef_t * commands; + + int wait; + int textLength; + byte textBuf[MAX_CMD_BUFFER]; + + idStr completionString; + idStrList completionParms; + + // piggybacks on the text buffer, avoids tokenize again and screwing it up + idList tokenizedCmds; + + // a command stored to be executed after a reloadEngine and all associated commands have been processed + idCmdArgs postReload; + +private: + void ExecuteTokenizedString( const idCmdArgs &args ); + void InsertCommandText( const char *text ); + + static void ListByFlags( const idCmdArgs &args, cmdFlags_t flags ); + static void List_f( const idCmdArgs &args ); + static void SystemList_f( const idCmdArgs &args ); + static void RendererList_f( const idCmdArgs &args ); + static void SoundList_f( const idCmdArgs &args ); + static void GameList_f( const idCmdArgs &args ); + static void ToolList_f( const idCmdArgs &args ); + static void Exec_f( const idCmdArgs &args ); + static void Vstr_f( const idCmdArgs &args ); + static void Echo_f( const idCmdArgs &args ); + static void Parse_f( const idCmdArgs &args ); + static void Wait_f( const idCmdArgs &args ); + static void PrintMemInfo_f( const idCmdArgs &args ); +}; + +idCmdSystemLocal cmdSystemLocal; +idCmdSystem * cmdSystem = &cmdSystemLocal; + +/* +================================================ +idSort_CommandDef +================================================ +*/ +class idSort_CommandDef : public idSort_Quick< commandDef_t, idSort_CommandDef > { +public: + int Compare( const commandDef_t & a, const commandDef_t & b ) const { return idStr::Icmp( a.name, b.name ); } +}; + +/* +============ +idCmdSystemLocal::ListByFlags +============ +*/ +void idCmdSystemLocal::ListByFlags( const idCmdArgs &args, cmdFlags_t flags ) { + int i; + idStr match; + const commandDef_t *cmd; + idList cmdList; + + if ( args.Argc() > 1 ) { + match = args.Args( 1, -1 ); + match.Replace( " ", "" ); + } else { + match = ""; + } + + for ( cmd = cmdSystemLocal.GetCommands(); cmd; cmd = cmd->next ) { + if ( !( cmd->flags & flags ) ) { + continue; + } + if ( match.Length() && idStr( cmd->name ).Filter( match, false ) == 0 ) { + continue; + } + + cmdList.Append( cmd ); + } + + //cmdList.SortWithTemplate( idSort_CommandDef() ); + + for ( i = 0; i < cmdList.Num(); i++ ) { + cmd = cmdList[i]; + + common->Printf( " %-21s %s\n", cmd->name, cmd->description ); + } + + common->Printf( "%i commands\n", cmdList.Num() ); +} + +/* +============ +idCmdSystemLocal::List_f +============ +*/ +void idCmdSystemLocal::List_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_ALL ); +} + +/* +============ +idCmdSystemLocal::SystemList_f +============ +*/ +void idCmdSystemLocal::SystemList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_SYSTEM ); +} + +/* +============ +idCmdSystemLocal::RendererList_f +============ +*/ +void idCmdSystemLocal::RendererList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_RENDERER ); +} + +/* +============ +idCmdSystemLocal::SoundList_f +============ +*/ +void idCmdSystemLocal::SoundList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_SOUND ); +} + +/* +============ +idCmdSystemLocal::GameList_f +============ +*/ +void idCmdSystemLocal::GameList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_GAME ); +} + +/* +============ +idCmdSystemLocal::ToolList_f +============ +*/ +void idCmdSystemLocal::ToolList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_TOOL ); +} + +/* +=============== +idCmdSystemLocal::Exec_f +=============== +*/ +void idCmdSystemLocal::Exec_f( const idCmdArgs &args ) { + char * f; + int len; + idStr filename; + + if ( args.Argc () != 2 ) { + common->Printf( "exec : execute a script file\n" ); + return; + } + + filename = args.Argv(1); + filename.DefaultFileExtension( ".cfg" ); + len = fileSystem->ReadFile( filename, reinterpret_cast(&f), NULL ); + if ( !f ) { + common->Printf( "couldn't exec %s\n", args.Argv(1) ); + return; + } + common->Printf( "execing %s\n", args.Argv(1) ); + + cmdSystemLocal.BufferCommandText( CMD_EXEC_INSERT, f ); + + fileSystem->FreeFile( f ); +} + +/* +=============== +idCmdSystemLocal::Vstr_f + +Inserts the current value of a cvar as command text +=============== +*/ +void idCmdSystemLocal::Vstr_f( const idCmdArgs &args ) { + const char *v; + + if ( args.Argc () != 2 ) { + common->Printf( "vstr : execute a variable command\n" ); + return; + } + + v = cvarSystem->GetCVarString( args.Argv( 1 ) ); + + cmdSystemLocal.BufferCommandText( CMD_EXEC_APPEND, va( "%s\n", v ) ); +} + +/* +=============== +idCmdSystemLocal::Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void idCmdSystemLocal::Echo_f( const idCmdArgs &args ) { + int i; + + for ( i = 1; i < args.Argc(); i++ ) { + common->Printf( "%s ", args.Argv( i ) ); + } + common->Printf( "\n" ); +} + +/* +============ +idCmdSystemLocal::Wait_f + +Causes execution of the remainder of the command buffer to be delayed until next frame. +============ +*/ +void idCmdSystemLocal::Wait_f( const idCmdArgs &args ) { + if ( args.Argc() == 2 ) { + cmdSystemLocal.SetWait( atoi( args.Argv( 1 ) ) ); + } else { + cmdSystemLocal.SetWait( 1 ); + } +} + +/* +============ +idCmdSystemLocal::Parse_f + +This just prints out how the rest of the line was parsed, as a debugging tool. +============ +*/ +void idCmdSystemLocal::Parse_f( const idCmdArgs &args ) { + int i; + + for ( i = 0; i < args.Argc(); i++ ) { + common->Printf( "%i: %s\n", i, args.Argv(i) ); + } +} + +/* +============ +idCmdSystemLocal::Init +============ +*/ +void idCmdSystemLocal::Init() { + + AddCommand( "listCmds", List_f, CMD_FL_SYSTEM, "lists commands" ); + AddCommand( "listSystemCmds", SystemList_f, CMD_FL_SYSTEM, "lists system commands" ); + AddCommand( "listRendererCmds", RendererList_f, CMD_FL_SYSTEM, "lists renderer commands" ); + AddCommand( "listSoundCmds", SoundList_f, CMD_FL_SYSTEM, "lists sound commands" ); + AddCommand( "listGameCmds", GameList_f, CMD_FL_SYSTEM, "lists game commands" ); + AddCommand( "listToolCmds", ToolList_f, CMD_FL_SYSTEM, "lists tool commands" ); + AddCommand( "exec", Exec_f, CMD_FL_SYSTEM, "executes a config file", ArgCompletion_ConfigName ); + AddCommand( "vstr", Vstr_f, CMD_FL_SYSTEM, "inserts the current value of a cvar as command text" ); + AddCommand( "echo", Echo_f, CMD_FL_SYSTEM, "prints text" ); + AddCommand( "parse", Parse_f, CMD_FL_SYSTEM, "prints tokenized string" ); + AddCommand( "wait", Wait_f, CMD_FL_SYSTEM, "delays remaining buffered commands one or more frames" ); + + // link in all the commands declared with static idCommandLink variables or CONSOLE_COMMAND macros + for ( idCommandLink * link = CommandLinks(); link != NULL; link = link->next ) { + AddCommand( link->cmdName_, link->function_, CMD_FL_SYSTEM, link->description_, link->argCompletion_ ); + } + + completionString = "*"; + + textLength = 0; +} + +/* +============ +idCmdSystemLocal::Shutdown +============ +*/ +void idCmdSystemLocal::Shutdown() { + commandDef_t *cmd; + + for ( cmd = commands; cmd; cmd = commands ) { + commands = commands->next; + Mem_Free( cmd->name ); + Mem_Free( cmd->description ); + delete cmd; + } + + completionString.Clear(); + completionParms.Clear(); + tokenizedCmds.Clear(); + postReload.Clear(); +} + +/* +============ +idCmdSystemLocal::AddCommand +============ +*/ +void idCmdSystemLocal::AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion ) { + commandDef_t *cmd; + + // fail if the command already exists + for ( cmd = commands; cmd; cmd = cmd->next ) { + if ( idStr::Cmp( cmdName, cmd->name ) == 0 ) { + if ( function != cmd->function ) { + common->Printf( "idCmdSystemLocal::AddCommand: %s already defined\n", cmdName ); + } + return; + } + } + + cmd = new (TAG_SYSTEM) commandDef_t; + cmd->name = Mem_CopyString( cmdName ); + cmd->function = function; + cmd->argCompletion = argCompletion; + cmd->flags = flags; + cmd->description = Mem_CopyString( description ); + cmd->next = commands; + commands = cmd; +} + +/* +============ +idCmdSystemLocal::RemoveCommand +============ +*/ +void idCmdSystemLocal::RemoveCommand( const char *cmdName ) { + commandDef_t *cmd, **last; + + for ( last = &commands, cmd = *last; cmd; cmd = *last ) { + if ( idStr::Cmp( cmdName, cmd->name ) == 0 ) { + *last = cmd->next; + Mem_Free( cmd->name ); + Mem_Free( cmd->description ); + delete cmd; + return; + } + last = &cmd->next; + } +} + +/* +============ +idCmdSystemLocal::RemoveFlaggedCommands +============ +*/ +void idCmdSystemLocal::RemoveFlaggedCommands( int flags ) { + commandDef_t *cmd, **last; + + for ( last = &commands, cmd = *last; cmd; cmd = *last ) { + if ( cmd->flags & flags ) { + *last = cmd->next; + Mem_Free( cmd->name ); + Mem_Free( cmd->description ); + delete cmd; + continue; + } + last = &cmd->next; + } +} + +/* +============ +idCmdSystemLocal::CommandCompletion +============ +*/ +void idCmdSystemLocal::CommandCompletion( void(*callback)( const char *s ) ) { + commandDef_t *cmd; + + for ( cmd = commands; cmd; cmd = cmd->next ) { + callback( cmd->name ); + } +} + +/* +============ +idCmdSystemLocal::ArgCompletion +============ +*/ +void idCmdSystemLocal::ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) { + commandDef_t *cmd; + idCmdArgs args; + + args.TokenizeString( cmdString, false ); + + for ( cmd = commands; cmd; cmd = cmd->next ) { + if ( !cmd->argCompletion ) { + continue; + } + if ( idStr::Icmp( args.Argv( 0 ), cmd->name ) == 0 ) { + cmd->argCompletion( args, callback ); + break; + } + } +} + +/* +============ +idCmdSystemLocal::ExecuteTokenizedString +============ +*/ +void idCmdSystemLocal::ExecuteTokenizedString( const idCmdArgs &args ) { + commandDef_t *cmd, **prev; + + // execute the command line + if ( !args.Argc() ) { + return; // no tokens + } + + // check registered command functions + for ( prev = &commands; *prev; prev = &cmd->next ) { + cmd = *prev; + if ( idStr::Icmp( args.Argv( 0 ), cmd->name ) == 0 ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + *prev = cmd->next; + cmd->next = commands; + commands = cmd; + + if ( ( cmd->flags & (CMD_FL_CHEAT|CMD_FL_TOOL) ) && common->IsMultiplayer() && !net_allowCheats.GetBool() ) { + common->Printf( "Command '%s' not valid in multiplayer mode.\n", cmd->name ); + return; + } + // perform the action + if ( !cmd->function ) { + break; + } else { + cmd->function( args ); + } + return; + } + } + + // check cvars + if ( cvarSystem->Command( args ) ) { + return; + } + + common->Printf( "Unknown command '%s'\n", args.Argv( 0 ) ); +} + +/* +============ +idCmdSystemLocal::ExecuteCommandText + +Tokenizes, then executes. +============ +*/ +void idCmdSystemLocal::ExecuteCommandText( const char *text ) { + ExecuteTokenizedString( idCmdArgs( text, false ) ); +} + +/* +============ +idCmdSystemLocal::InsertCommandText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void idCmdSystemLocal::InsertCommandText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + if ( len + textLength > (int)sizeof( textBuf ) ) { + common->Printf( "idCmdSystemLocal::InsertText: buffer overflow\n" ); + return; + } + + // move the existing command text + for ( i = textLength - 1; i >= 0; i-- ) { + textBuf[ i + len ] = textBuf[ i ]; + } + + // copy the new text in + memcpy( textBuf, text, len - 1 ); + + // add a \n + textBuf[ len - 1 ] = '\n'; + + textLength += len; +} + +/* +============ +idCmdSystemLocal::AppendCommandText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void idCmdSystemLocal::AppendCommandText( const char *text ) { + int l; + + l = strlen( text ); + + if ( textLength + l >= (int)sizeof( textBuf ) ) { + common->Printf( "idCmdSystemLocal::AppendText: buffer overflow\n" ); + return; + } + memcpy( textBuf + textLength, text, l ); + textLength += l; +} + +/* +============ +idCmdSystemLocal::BufferCommandText +============ +*/ +void idCmdSystemLocal::BufferCommandText( cmdExecution_t exec, const char *text ) { + switch( exec ) { + case CMD_EXEC_NOW: { + ExecuteCommandText( text ); + break; + } + case CMD_EXEC_INSERT: { + InsertCommandText( text ); + break; + } + case CMD_EXEC_APPEND: { + AppendCommandText( text ); + break; + } + default: { + common->FatalError( "idCmdSystemLocal::BufferCommandText: bad exec type" ); + } + } +} + +/* +============ +idCmdSystemLocal::BufferCommandArgs +============ +*/ +void idCmdSystemLocal::BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ) { + switch ( exec ) { + case CMD_EXEC_NOW: { + ExecuteTokenizedString( args ); + break; + } + case CMD_EXEC_APPEND: { + AppendCommandText( "_execTokenized\n" ); + tokenizedCmds.Append( args ); + break; + } + default: { + common->FatalError( "idCmdSystemLocal::BufferCommandArgs: bad exec type" ); + } + } +} + +/* +============ +idCmdSystemLocal::ExecuteCommandBuffer +============ +*/ +void idCmdSystemLocal::ExecuteCommandBuffer() { + int i; + char * text; + int quotes; + idCmdArgs args; + + while( textLength ) { + + if ( wait ) { + // skip out while text still remains in buffer, leaving it for next frame + wait--; + break; + } + + // find a \n or ; line break + text = (char *)textBuf; + + quotes = 0; + for ( i = 0; i < textLength; i++ ) { + if ( text[i] == '"' ) { + quotes++; + } + if ( !( quotes & 1 ) && text[i] == ';' ) { + break; // don't break if inside a quoted string + } + if ( text[i] == '\n' || text[i] == '\r' ) { + break; + } + } + + text[i] = 0; + + if ( !idStr::Cmp( text, "_execTokenized" ) ) { + args = tokenizedCmds[ 0 ]; + tokenizedCmds.RemoveIndex( 0 ); + } else { + args.TokenizeString( text, false ); + } + + // delete the text from the command buffer and move remaining commands down + // this is necessary because commands (exec) can insert data at the + // beginning of the text buffer + + if ( i == textLength ) { + textLength = 0; + } else { + i++; + textLength -= i; + memmove( text, text+i, textLength ); + } + + // execute the command line that we have already tokenized + ExecuteTokenizedString( args ); + } +} + +/* +============ +idCmdSystemLocal::ArgCompletion_FolderExtension +============ +*/ +void idCmdSystemLocal::ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ) { + int i; + idStr string; + const char *extension; + va_list argPtr; + + string = args.Argv( 0 ); + string += " "; + string += args.Argv( 1 ); + + if ( string.Icmp( completionString ) != 0 ) { + idStr parm, path; + idFileList *names; + + completionString = string; + completionParms.Clear(); + + parm = args.Argv( 1 ); + parm.ExtractFilePath( path ); + if ( stripFolder || path.Length() == 0 ) { + path = folder + path; + } + path.StripTrailing( '/' ); + + // list folders + names = fileSystem->ListFiles( path, "/", true, true ); + for ( i = 0; i < names->GetNumFiles(); i++ ) { + idStr name = names->GetFile( i ); + if ( stripFolder ) { + name.Strip( folder ); + } else { + name.Strip( "/" ); + } + name = args.Argv( 0 ) + ( " " + name ) + "/"; + completionParms.Append( name ); + } + fileSystem->FreeFileList( names ); + + // list files + va_start( argPtr, stripFolder ); + for ( extension = va_arg( argPtr, const char * ); extension; extension = va_arg( argPtr, const char * ) ) { + names = fileSystem->ListFiles( path, extension, true, true ); + for ( i = 0; i < names->GetNumFiles(); i++ ) { + idStr name = names->GetFile( i ); + if ( stripFolder ) { + name.Strip( folder ); + } else { + name.Strip( "/" ); + } + name = args.Argv( 0 ) + ( " " + name ); + completionParms.Append( name ); + } + fileSystem->FreeFileList( names ); + } + va_end( argPtr ); + } + for ( i = 0; i < completionParms.Num(); i++ ) { + callback( completionParms[i] ); + } +} + +/* +============ +idCmdSystemLocal::ArgCompletion_DeclName +============ +*/ +void idCmdSystemLocal::ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ) { + int i, num; + + if ( declManager == NULL ) { + return; + } + num = declManager->GetNumDecls( (declType_t)type ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( (declType_t)type, i , false )->GetName() ); + } +} + +/* +============ +idCmdSystemLocal::SetupReloadEngine +============ +*/ +void idCmdSystemLocal::SetupReloadEngine( const idCmdArgs &args ) { + BufferCommandText( CMD_EXEC_APPEND, "reloadEngine\n" ); + postReload = args; +} + +/* +============ +idCmdSystemLocal::PostReloadEngine +============ +*/ +bool idCmdSystemLocal::PostReloadEngine() { + if ( !postReload.Argc() ) { + return false; + } + BufferCommandArgs( CMD_EXEC_APPEND, postReload ); + postReload.Clear(); + return true; +} diff --git a/neo/framework/CmdSystem.h b/neo/framework/CmdSystem.h new file mode 100644 index 00000000..0b1ab307 --- /dev/null +++ b/neo/framework/CmdSystem.h @@ -0,0 +1,259 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __CMDSYSTEM_H__ +#define __CMDSYSTEM_H__ + +/* +=============================================================================== + + Console command execution and command text buffering. + + Any number of commands can be added in a frame from several different + sources. Most commands come from either key bindings or console line input, + but entire text files can be execed. + + Command execution takes a null terminated string, breaks it into tokens, + then searches for a command or variable that matches the first token. + +=============================================================================== +*/ + +// command flags +typedef enum { + CMD_FL_ALL = -1, + CMD_FL_CHEAT = BIT(0), // command is considered a cheat + CMD_FL_SYSTEM = BIT(1), // system command + CMD_FL_RENDERER = BIT(2), // renderer command + CMD_FL_SOUND = BIT(3), // sound command + CMD_FL_GAME = BIT(4), // game command + CMD_FL_TOOL = BIT(5) // tool command +} cmdFlags_t; + +// parameters for command buffer stuffing +typedef enum { + CMD_EXEC_NOW, // don't return until completed + CMD_EXEC_INSERT, // insert at current position, but don't run yet + CMD_EXEC_APPEND // add to end of the command buffer (normal case) +} cmdExecution_t; + +// command function +typedef void (*cmdFunction_t)( const idCmdArgs &args ); + +// argument completion function +typedef void (*argCompletion_t)( const idCmdArgs &args, void(*callback)( const char *s ) ); + +/* +================================================ +idCommandLink is a convenient way to get a function registered as a +ConsoleCommand without having to add an explicit call to idCmdSystem->AddCommand() in a startup +function somewhere. Simply declare a static variable with the parameters and it will get +executed before main(). For example: + +static idCommandLink sys_dumpMemory( "sys_dumpMemory", Sys_DumpMemory_f, "Walks the heap and reports stats" ); +================================================ +*/ + +class idCommandLink { +public: + idCommandLink( const char *cmdName, cmdFunction_t function, + const char *description, argCompletion_t argCompletion = NULL ); + idCommandLink * next; + const char * cmdName_; + cmdFunction_t function_; + const char * description_; + argCompletion_t argCompletion_; +}; + +// The command system will create commands for all the static definitions +// when it initializes. +idCommandLink *CommandLinks( idCommandLink *cl = NULL ); + +/* +================================================ +The CONSOLE_COMMAND macro is an even easier way to create a console command by +automatically generating the idCommandLink variable, and it also allows all the +command code to be stripped from a build with a single define. For example: + +CONSOLE_COMMAND( Sys_DumpMemory, "Walks the heap and reports stats" ) { + // do stuff +} + +NOTE: All CONSOLE_COMMANDs will be stripped with the shipping build unless it's +created using the CONSOLE_COMMAND_SHIP macro. +================================================ +*/ + +#if defined ( ID_RETAIL ) && !defined( ID_RETAIL_INTERNAL ) +#define CONSOLE_COMMAND_SHIP CONSOLE_COMMAND_COMPILE +#define CONSOLE_COMMAND CONSOLE_COMMAND_NO_COMPILE +// We need to disable this warning to get commands that were made friends +// of classes to compile as inline. +// warning C4211: nonstandard extension used : redefined extern to static +#pragma warning( disable : 4211 ) +// warning C4505: 'xxx' : unreferenced local function has been removed +#pragma warning( disable : 4505 ) +#else +#define CONSOLE_COMMAND_SHIP CONSOLE_COMMAND_COMPILE +#define CONSOLE_COMMAND CONSOLE_COMMAND_COMPILE +#endif + +// Turn console commands into static inline code, which will cause them to be +// removed from the build. +#define CONSOLE_COMMAND_NO_COMPILE( name, comment, completion ) \ + static inline void name ## _f( const idCmdArgs &args ) + +// lint incorrectly gives this for all console commands: Issue 1568: (Warning -- Variable 'TestAtomicString_v' accesses variable 'atomicStringManager' before the latter is initialized through calls: 'TestAtomicString_f() => idAtomicString::FreeDynamic()') +// I can't figure out how to disable this just around CONSOLE_COMMAND, so it must stay disabled everywhere, +// which is a shame. +//lint -e1568 +#define CONSOLE_COMMAND_COMPILE( name, comment, completion ) \ + void name ## _f( const idCmdArgs &args ); \ + idCommandLink name ## _v( #name, name ## _f, comment, completion ); \ + void name ## _f( const idCmdArgs &args ) + +class idCmdSystem { +public: + virtual ~idCmdSystem() {} + + virtual void Init() = 0; + virtual void Shutdown() = 0; + + // Registers a command and the function to call for it. + virtual void AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion = NULL ) = 0; + // Removes a command. + virtual void RemoveCommand( const char *cmdName ) = 0; + // Remove all commands with one of the flags set. + virtual void RemoveFlaggedCommands( int flags ) = 0; + + // Command and argument completion using callback for each valid string. + virtual void CommandCompletion( void(*callback)( const char *s ) ) = 0; + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) = 0; + + virtual void ExecuteCommandText( const char * text ) = 0; + virtual void AppendCommandText( const char * text ) = 0; + + // Adds command text to the command buffer, does not add a final \n + virtual void BufferCommandText( cmdExecution_t exec, const char *text ) = 0; + // Pulls off \n \r or ; terminated lines of text from the command buffer and + // executes the commands. Stops when the buffer is empty. + // Normally called once per frame, but may be explicitly invoked. + virtual void ExecuteCommandBuffer() = 0; + + // Base for path/file auto-completion. + virtual void ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ) = 0; + // Base for decl name auto-completion. + virtual void ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ) = 0; + + // Adds to the command buffer in tokenized form ( CMD_EXEC_NOW or CMD_EXEC_APPEND only ) + virtual void BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ) = 0; + + // Setup a reloadEngine to happen on next command run, and give a command to execute after reload + virtual void SetupReloadEngine( const idCmdArgs &args ) = 0; + virtual bool PostReloadEngine() = 0; + + // Default argument completion functions. + static void ArgCompletion_Boolean( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_Integer( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_String( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_Decl( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_FileName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_MapName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ModelName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_SoundName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ImageName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_VideoName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ConfigName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_SaveGame( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_DemoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +}; + +extern idCmdSystem * cmdSystem; + + +ID_INLINE void idCmdSystem::ArgCompletion_Boolean( const idCmdArgs &args, void(*callback)( const char *s ) ) { + callback( va( "%s 0", args.Argv( 0 ) ) ); + callback( va( "%s 1", args.Argv( 0 ) ) ); +} + +template ID_INLINE void idCmdSystem::ArgCompletion_Integer( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( int i = min; i <= max; i++ ) { + callback( va( "%s %d", args.Argv( 0 ), i ) ); + } +} + +template ID_INLINE void idCmdSystem::ArgCompletion_String( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( int i = 0; strings[i]; i++ ) { + callback( va( "%s %s", args.Argv( 0 ), strings[i] ) ); + } +} + +template ID_INLINE void idCmdSystem::ArgCompletion_Decl( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_DeclName( args, callback, type ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_FileName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", true, "", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_MapName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "maps/", true, ".map", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ModelName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_SoundName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "sound/", false, ".wav", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ImageName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", false, ".tga", ".dds", ".jpg", ".pcx", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_VideoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", false, ".bik", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ConfigName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", true, ".cfg", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_SaveGame( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "SaveGames/", true, ".save", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_DemoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "demos/", true, ".demo", NULL ); +} + +#endif /* !__CMDSYSTEM_H__ */ diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp new file mode 100644 index 00000000..03014113 --- /dev/null +++ b/neo/framework/Common.cpp @@ -0,0 +1,1709 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" + +#include "ConsoleHistory.h" +#include "../renderer/AutoRenderBink.h" + +#include "../sound/sound.h" + +#include "../../doomclassic/doom/doomlib.h" +#include "../../doomclassic/doom/d_event.h" +#include "../../doomclassic/doom/d_main.h" + + + +#include "../sys/sys_savegame.h" + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "" +#endif + +struct version_s { + version_s() { sprintf( string, "%s.%d%s %s %s %s", ENGINE_VERSION, BUILD_NUMBER, BUILD_DEBUG, BUILD_STRING, __DATE__, __TIME__ ); } + char string[256]; +} version; + +idCVar com_version( "si_version", version.string, CVAR_SYSTEM|CVAR_ROM|CVAR_SERVERINFO, "engine version" ); +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "force generic platform independent SIMD" ); + +#ifdef ID_RETAIL +idCVar com_allowConsole( "com_allowConsole", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_INIT, "allow toggling console with the tilde key" ); +#else +idCVar com_allowConsole( "com_allowConsole", "1", CVAR_BOOL | CVAR_SYSTEM | CVAR_INIT, "allow toggling console with the tilde key" ); +#endif + +idCVar com_developer( "developer", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "developer mode" ); +idCVar com_speeds( "com_speeds", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "show engine timings" ); +idCVar com_showFPS( "com_showFPS", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_ARCHIVE|CVAR_NOCHEAT, "show frames rendered per second" ); +idCVar com_showMemoryUsage( "com_showMemoryUsage", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "show total and per frame memory usage" ); +idCVar com_updateLoadSize( "com_updateLoadSize", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "update the load size after loading a map" ); + +idCVar com_productionMode( "com_productionMode", "0", CVAR_SYSTEM | CVAR_BOOL, "0 - no special behavior, 1 - building a production build, 2 - running a production build" ); + +idCVar com_japaneseCensorship( "com_japaneseCensorship", "0", CVAR_NOCHEAT, "Enable Japanese censorship" ); + +idCVar preload_CommonAssets( "preload_CommonAssets", "1", CVAR_SYSTEM | CVAR_BOOL, "preload common assets" ); + +idCVar net_inviteOnly( "net_inviteOnly", "1", CVAR_BOOL | CVAR_ARCHIVE, "whether or not the private server you create allows friends to join or invite only" ); + +extern idCVar g_demoMode; + +idCVar com_engineHz( "com_engineHz", "60", CVAR_FLOAT | CVAR_ARCHIVE, "Frames per second the engine runs at", 10.0f, 1024.0f ); +float com_engineHz_latched = 60.0f; // Latched version of cvar, updated between map loads +int64 com_engineHz_numerator = 100LL * 1000LL; +int64 com_engineHz_denominator = 100LL * 60LL; + +HWND com_hwndMsg = NULL; + +#ifdef __DOOM_DLL__ +idGame * game = NULL; +idGameEdit * gameEdit = NULL; +#endif + +idCommonLocal commonLocal; +idCommon * common = &commonLocal; + + +idCVar com_skipIntroVideos( "com_skipIntroVideos", "0", CVAR_BOOL , "skips intro videos" ); + +// For doom classic +struct Globals; + +/* +================== +idCommonLocal::idCommonLocal +================== +*/ +idCommonLocal::idCommonLocal() : + readSnapshotIndex( 0 ), + writeSnapshotIndex( 0 ), + optimalTimeBuffered( 0.0f ), + optimalTimeBufferedWindow( 0.0f ), + optimalPCTBuffer( 0.5f ), + lastPacifierSessionTime( 0 ), + lastPacifierGuiTime( 0 ), + lastPacifierDialogState( false ), + showShellRequested( false ), + currentGame( DOOM3_BFG ), + idealCurrentGame( DOOM3_BFG ), + doomClassicMaterial( NULL ) + { + + snapCurrent.localTime = -1; + snapPrevious.localTime = -1; + snapCurrent.serverTime = -1; + snapPrevious.serverTime = -1; + snapTimeBuffered = 0.0f; + effectiveSnapRate = 0.0f; + totalBufferedTime = 0; + totalRecvTime = 0; + + com_fullyInitialized = false; + com_refreshOnPrint = false; + com_errorEntered = ERP_NONE; + com_shuttingDown = false; + com_isJapaneseSKU = false; + + logFile = NULL; + + strcpy( errorMessage, "" ); + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; + + gameDLL = 0; + + loadGUI = NULL; + nextLoadTip = 0; + isHellMap = false; + wipeForced = false; + defaultLoadscreen = false; + + menuSoundWorld = NULL; + + insideUpdateScreen = false; + insideExecuteMapChange = false; + + mapSpawnData.savegameFile = NULL; + + currentMapName.Clear(); + aviDemoShortName.Clear(); + + renderWorld = NULL; + soundWorld = NULL; + menuSoundWorld = NULL; + readDemo = NULL; + writeDemo = NULL; + + gameFrame = 0; + gameTimeResidual = 0; + syncNextGameFrame = true; + mapSpawned = false; + aviCaptureMode = false; + timeDemo = TD_NO; + + nextSnapshotSendTime = 0; + nextUsercmdSendTime = 0; + + clientPrediction = 0; + + saveFile = NULL; + stringsFile = NULL; + + ClearWipe(); +} + +/* +================== +idCommonLocal::Quit +================== +*/ +void idCommonLocal::Quit() { + + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) { + Shutdown(); + } + Sys_Quit(); +} + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters separate the commandLine string into multiple console +command lines. + +All of these are valid: + +doom +set test blah +map test +doom set test blah+map test +doom set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +idCmdArgs com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +idCommonLocal::ParseCommandLine +================== +*/ +void idCommonLocal::ParseCommandLine( int argc, const char * const * argv ) { + int i, current_count; + + com_numConsoleLines = 0; + current_count = 0; + // API says no program path + for ( i = 0; i < argc; i++ ) { + if ( idStr::Icmp( argv[ i ], "+connect_lobby" ) == 0 ) { + // Handle Steam bootable invites. + session->HandleBootableInvite( _atoi64( argv[ i + 1 ] ) ); + } else if ( argv[ i ][ 0 ] == '+' ) { + com_numConsoleLines++; + com_consoleLines[ com_numConsoleLines-1 ].AppendArg( argv[ i ] + 1 ); + } else { + if ( !com_numConsoleLines ) { + com_numConsoleLines++; + } + com_consoleLines[ com_numConsoleLines-1 ].AppendArg( argv[ i ] ); + } + } +} + +/* +================== +idCommonLocal::SafeMode + +Check for "safe" on the command line, which will +skip loading of config file (DoomConfig.cfg) +================== +*/ +bool idCommonLocal::SafeMode() { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "safe" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "cvar_restart" ) ) { + com_consoleLines[ i ].Clear(); + return true; + } + } + return false; +} + +/* +================== +idCommonLocal::StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +================== +*/ +void idCommonLocal::StartupVariable( const char *match ) { + int i = 0; + while ( i < com_numConsoleLines ) { + if ( strcmp( com_consoleLines[ i ].Argv( 0 ), "set" ) != 0 ) { + i++; + continue; + } + const char * s = com_consoleLines[ i ].Argv(1); + + if ( !match || !idStr::Icmp( s, match ) ) { + cvarSystem->SetCVarString( s, com_consoleLines[ i ].Argv( 2 ) ); + } + i++; + } +} + +/* +================== +idCommonLocal::AddStartupCommands + +Adds command line parameters as script statements +Commands are separated by + signs + +Returns true if any late commands were added, which +will keep the demoloop from immediately starting +================== +*/ +void idCommonLocal::AddStartupCommands() { + // quote every token, so args with semicolons can work + for ( int i = 0; i < com_numConsoleLines; i++ ) { + if ( !com_consoleLines[i].Argc() ) { + continue; + } + // directly as tokenized so nothing gets screwed + cmdSystem->BufferCommandArgs( CMD_EXEC_APPEND, com_consoleLines[i] ); + } +} + +/* +================== +idCommonLocal::WriteConfigToFile +================== +*/ +void idCommonLocal::WriteConfigToFile( const char *filename ) { + idFile * f = fileSystem->OpenFileWrite( filename ); + if ( !f ) { + Printf ("Couldn't write %s.\n", filename ); + return; + } + + idKeyInput::WriteBindings( f ); + cvarSystem->WriteFlaggedVariables( CVAR_ARCHIVE, "set", f ); + fileSystem->CloseFile( f ); +} + +/* +=============== +idCommonLocal::WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void idCommonLocal::WriteConfiguration() { + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) { + return; + } + + if ( !( cvarSystem->GetModifiedFlags() & CVAR_ARCHIVE ) ) { + return; + } + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + + // save to the profile + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + if ( user != NULL ) { + user->SaveProfileSettings(); + } + +#ifdef CONFIG_FILE + // disable printing out the "Writing to:" message + bool developer = com_developer.GetBool(); + com_developer.SetBool( false ); + + WriteConfigToFile( CONFIG_FILE ); + + // restore the developer cvar + com_developer.SetBool( developer ); +#endif +} + +/* +=============== +KeysFromBinding() +Returns the key bound to the command +=============== +*/ +const char* idCommonLocal::KeysFromBinding( const char *bind ) { + return idKeyInput::KeysFromBinding( bind ); +} + +/* +=============== +BindingFromKey() +Returns the binding bound to key +=============== +*/ +const char* idCommonLocal::BindingFromKey( const char *key ) { + return idKeyInput::BindingFromKey( key ); +} + +/* +=============== +ButtonState() +Returns the state of the button +=============== +*/ +int idCommonLocal::ButtonState( int key ) { + return usercmdGen->ButtonState(key); +} + +/* +=============== +ButtonState() +Returns the state of the key +=============== +*/ +int idCommonLocal::KeyState( int key ) { + return usercmdGen->KeyState(key); +} + +/* +============ +idCmdSystemLocal::PrintMemInfo_f + +This prints out memory debugging data +============ +*/ +CONSOLE_COMMAND( printMemInfo, "prints memory debugging data", NULL ) { + MemInfo_t mi; + memset( &mi, 0, sizeof( mi ) ); + mi.filebase = commonLocal.GetCurrentMapName(); + + renderSystem->PrintMemInfo( &mi ); // textures and models + soundSystem->PrintMemInfo( &mi ); // sounds + + common->Printf( " Used image memory: %s bytes\n", idStr::FormatNumber( mi.imageAssetsTotal ).c_str() ); + mi.assetTotals += mi.imageAssetsTotal; + + common->Printf( " Used model memory: %s bytes\n", idStr::FormatNumber( mi.modelAssetsTotal ).c_str() ); + mi.assetTotals += mi.modelAssetsTotal; + + common->Printf( " Used sound memory: %s bytes\n", idStr::FormatNumber( mi.soundAssetsTotal ).c_str() ); + mi.assetTotals += mi.soundAssetsTotal; + + common->Printf( " Used asset memory: %s bytes\n", idStr::FormatNumber( mi.assetTotals ).c_str() ); + + // write overview file + idFile *f; + + f = fileSystem->OpenFileAppend( "maps/printmeminfo.txt" ); + if ( !f ) { + return; + } + + f->Printf( "total(%s ) image(%s ) model(%s ) sound(%s ): %s\n", idStr::FormatNumber( mi.assetTotals ).c_str(), idStr::FormatNumber( mi.imageAssetsTotal ).c_str(), + idStr::FormatNumber( mi.modelAssetsTotal ).c_str(), idStr::FormatNumber( mi.soundAssetsTotal ).c_str(), mi.filebase.c_str() ); + + fileSystem->CloseFile( f ); +} + +/* +================== +Com_Error_f + +Just throw a fatal error to test error shutdown procedures. +================== +*/ +CONSOLE_COMMAND( error, "causes an error", NULL ) { + if ( !com_developer.GetBool() ) { + commonLocal.Printf( "error may only be used in developer mode\n" ); + return; + } + + if ( args.Argc() > 1 ) { + commonLocal.FatalError( "Testing fatal error" ); + } else { + commonLocal.Error( "Testing drop error" ); + } +} + +/* +================== +Com_Freeze_f + +Just freeze in place for a given number of seconds to test error recovery. +================== +*/ +CONSOLE_COMMAND( freeze, "freezes the game for a number of seconds", NULL ) { + float s; + int start, now; + + if ( args.Argc() != 2 ) { + commonLocal.Printf( "freeze \n" ); + return; + } + + if ( !com_developer.GetBool() ) { + commonLocal.Printf( "freeze may only be used in developer mode\n" ); + return; + } + + s = atof( args.Argv(1) ); + + start = eventLoop->Milliseconds(); + + while ( 1 ) { + now = eventLoop->Milliseconds(); + if ( ( now - start ) * 0.001f > s ) { + break; + } + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +CONSOLE_COMMAND( crash, "causes a crash", NULL ) { + if ( !com_developer.GetBool() ) { + commonLocal.Printf( "crash may only be used in developer mode\n" ); + return; + } + + * ( int * ) 0 = 0x12345678; +} + +/* +================= +Com_Quit_f +================= +*/ +CONSOLE_COMMAND_SHIP( quit, "quits the game", NULL ) { + commonLocal.Quit(); +} +CONSOLE_COMMAND_SHIP( exit, "exits the game", NULL ) { + commonLocal.Quit(); +} + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +CONSOLE_COMMAND( writeConfig, "writes a config file", NULL ) { + idStr filename; + + if ( args.Argc() != 2 ) { + commonLocal.Printf( "Usage: writeconfig \n" ); + return; + } + + filename = args.Argv(1); + filename.DefaultFileExtension( ".cfg" ); + commonLocal.Printf( "Writing %s.\n", filename.c_str() ); + commonLocal.WriteConfigToFile( filename ); +} + +/* +======================== +idCommonLocal::CheckStartupStorageRequirements +======================== +*/ +void idCommonLocal::CheckStartupStorageRequirements() { + int64 availableSpace = 0; + // ------------------------------------------------------------------------ + // Savegame and Profile required storage + // ------------------------------------------------------------------------ + { + // Make sure the save path exists in case it was deleted. + // If the path cannot be created we can safely assume there is no + // free space because in that case nothing can be saved anyway. + const char * savepath = cvarSystem->GetCVarString( "fs_savepath" ); + idStr directory = savepath; + //idStr directory = fs_savepath.GetString(); + directory += "\\"; // so it doesn't think the last part is a file and ignores in the directory creation + fileSystem->CreateOSPath( directory ); + + // Get the free space on the save path. + availableSpace = Sys_GetDriveFreeSpaceInBytes( savepath ); + + // If free space fails then get space on drive as a fall back + // (the directory will be created later anyway) + if ( availableSpace <= 1 ) { + idStr savePath( savepath ); + if ( savePath.Length() >= 3 ) { + if ( savePath[ 1 ] == ':' && savePath[ 2 ] == '\\' && + ( ( savePath[ 0 ] >= 'A' && savePath[ 0 ] <= 'Z' ) || + ( savePath[ 0 ] >= 'a' && savePath[ 0 ] <= 'z' ) ) ) { + savePath = savePath.Left( 3 ); + availableSpace = Sys_GetDriveFreeSpaceInBytes( savePath ); + } + } + } + } + + const int MIN_SAVE_STORAGE_PROFILE = 1024 * 1024; + const int MIN_SAVE_STORAGE_SAVEGAME = MIN_SAVEGAME_SIZE_BYTES; + + uint64 requiredSizeBytes = MIN_SAVE_STORAGE_SAVEGAME + MIN_SAVE_STORAGE_PROFILE; + + idLib::Printf( "requiredSizeBytes: %lld\n", requiredSizeBytes ); + + if ( (int64)( requiredSizeBytes - availableSpace ) > 0 ) { + class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted { + public: + virtual ~idSWFScriptFunction_Continue() {} + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE ); + common->Quit(); + return idSWFScriptVar(); + } + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_Continue() ); + optionText.Append( idStrId( "#STR_SWF_ACCEPT" ) ); + + // build custom space required string + // #str_dlg_space_required ~= "There is insufficient storage available. Please free %s and try again." + idStr format = idStrId( "#str_dlg_startup_insufficient_storage" ).GetLocalizedString(); + idStr size; + if ( requiredSizeBytes > ( 1024 * 1024 ) ) { + size = va( "%.1f MB", (float)requiredSizeBytes / ( 1024.0f * 1024.0f ) + 0.1f ); // +0.1 to avoid truncation + } else { + size = va( "%.1f KB", (float)requiredSizeBytes / 1024.0f + 0.1f ); + } + idStr msg = va( format.c_str(), size.c_str() ); + + common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg ); + } + + + session->GetAchievementSystem().Start(); +} + +/* +=============== +idCommonLocal::JapaneseCensorship +=============== +*/ +bool idCommonLocal::JapaneseCensorship() const { + return com_japaneseCensorship.GetBool() || com_isJapaneseSKU; +} + +/* +=============== +idCommonLocal::FilterLangList +=============== +*/ +void idCommonLocal::FilterLangList( idStrList* list, idStr lang ) { + + idStr temp; + for( int i = 0; i < list->Num(); i++ ) { + temp = (*list)[i]; + temp = temp.Right(temp.Length()-strlen("strings/")); + temp = temp.Left(lang.Length()); + if(idStr::Icmp(temp, lang) != 0) { + list->RemoveIndex(i); + i--; + } + } +} + +/* +=============== +idCommonLocal::InitLanguageDict +=============== +*/ +extern idCVar sys_lang; +void idCommonLocal::InitLanguageDict() { + idStr fileName; + + //D3XP: Instead of just loading a single lang file for each language + //we are going to load all files that begin with the language name + //similar to the way pak files work. So you can place english001.lang + //to add new strings to the english language dictionary + idFileList* langFiles; + langFiles = fileSystem->ListFilesTree( "strings", ".lang", true ); + + idStrList langList = langFiles->GetList(); + + // Loop through the list and filter + idStrList currentLangList = langList; + FilterLangList( ¤tLangList, sys_lang.GetString() ); + + if ( currentLangList.Num() == 0 ) { + // reset to english and try to load again + sys_lang.SetString( ID_LANG_ENGLISH ); + currentLangList = langList; + FilterLangList( ¤tLangList, sys_lang.GetString() ); + } + + idLocalization::ClearDictionary(); + for( int i = 0; i < currentLangList.Num(); i++ ) { + //common->Printf("%s\n", currentLangList[i].c_str()); + const byte * buffer = NULL; + int len = fileSystem->ReadFile( currentLangList[i], (void**)&buffer ); + if ( len <= 0 ) { + assert( false && "couldn't read the language dict file" ); + break; + } + idLocalization::LoadDictionary( buffer, len, currentLangList[i] ); + fileSystem->FreeFile( (void *)buffer ); + } + + fileSystem->FreeFileList(langFiles); +} + +/* +================= +ReloadLanguage_f +================= +*/ +CONSOLE_COMMAND( reloadLanguage, "reload language dict", NULL ) { + commonLocal.InitLanguageDict(); +} + +#include "../renderer/Image.h" + +/* +================= +Com_StartBuild_f +================= +*/ +CONSOLE_COMMAND( startBuild, "prepares to make a build", NULL ) { + globalImages->StartBuild(); +} + +/* +================= +Com_FinishBuild_f +================= +*/ +CONSOLE_COMMAND( finishBuild, "finishes the build process", NULL ) { + if ( game ) { + game->CacheDictionaryMedia( NULL ); + } + globalImages->FinishBuild( ( args.Argc() > 1 ) ); +} + +/* +================= +idCommonLocal::RenderSplash +================= +*/ +void idCommonLocal::RenderSplash() { + const float sysWidth = renderSystem->GetWidth() * renderSystem->GetPixelAspect(); + const float sysHeight = renderSystem->GetHeight(); + const float sysAspect = sysWidth / sysHeight; + const float splashAspect = 16.0f / 9.0f; + const float adjustment = sysAspect / splashAspect; + const float barHeight = ( adjustment >= 1.0f ) ? 0.0f : ( 1.0f - adjustment ) * (float)SCREEN_HEIGHT * 0.25f; + const float barWidth = ( adjustment <= 1.0f ) ? 0.0f : ( adjustment - 1.0f ) * (float)SCREEN_WIDTH * 0.25f; + if ( barHeight > 0.0f ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial ); + renderSystem->DrawStretchPic( 0, SCREEN_HEIGHT - barHeight, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial ); + } + if ( barWidth > 0.0f ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + renderSystem->DrawStretchPic( SCREEN_WIDTH - barWidth, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + } + renderSystem->SetColor4( 1, 1, 1, 1 ); + renderSystem->DrawStretchPic( barWidth, barHeight, SCREEN_WIDTH - barWidth * 2.0f, SCREEN_HEIGHT - barHeight * 2.0f, 0, 0, 1, 1, splashScreen ); + + const emptyCommand_t * cmd = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu ); + renderSystem->RenderCommandBuffers( cmd ); +} + +/* +================= +idCommonLocal::RenderBink +================= +*/ +void idCommonLocal::RenderBink( const char * path ) { + const float sysWidth = renderSystem->GetWidth() * renderSystem->GetPixelAspect(); + const float sysHeight = renderSystem->GetHeight(); + const float sysAspect = sysWidth / sysHeight; + const float movieAspect = ( 16.0f / 9.0f ); + const float imageWidth = SCREEN_WIDTH * movieAspect / sysAspect; + const float chop = 0.5f * ( SCREEN_WIDTH - imageWidth ); + + idStr materialText; + materialText.Format( "{ translucent { videoMap %s } }", path ); + + idMaterial * material = const_cast( declManager->FindMaterial( "splashbink" ) ); + material->Parse( materialText.c_str(), materialText.Length(), false ); + material->ResetCinematicTime( Sys_Milliseconds() ); + + while ( Sys_Milliseconds() <= material->GetCinematicStartTime() + material->CinematicLength() ) { + renderSystem->DrawStretchPic( chop, 0, imageWidth, SCREEN_HEIGHT, 0, 0, 1, 1, material ); + const emptyCommand_t * cmd = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu ); + renderSystem->RenderCommandBuffers( cmd ); + Sys_GenerateEvents(); + Sys_Sleep( 10 ); + } + + material->MakeDefault(); +} + +/* +================= +idCommonLocal::InitSIMD +================= +*/ +void idCommonLocal::InitSIMD() { + idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() ); + com_forceGenericSIMD.ClearModified(); +} + + +/* +================= +idCommonLocal::LoadGameDLL +================= +*/ +void idCommonLocal::LoadGameDLL() { +#ifdef __DOOM_DLL__ + char dllPath[ MAX_OSPATH ]; + + gameImport_t gameImport; + gameExport_t gameExport; + GetGameAPI_t GetGameAPI; + + fileSystem->FindDLL( "game", dllPath, true ); + + if ( !dllPath[ 0 ] ) { + common->FatalError( "couldn't find game dynamic library" ); + return; + } + common->DPrintf( "Loading game DLL: '%s'\n", dllPath ); + gameDLL = sys->DLL_Load( dllPath ); + if ( !gameDLL ) { + common->FatalError( "couldn't load game dynamic library" ); + return; + } + + const char * functionName = "GetGameAPI"; + GetGameAPI = (GetGameAPI_t) Sys_DLL_GetProcAddress( gameDLL, functionName ); + if ( !GetGameAPI ) { + Sys_DLL_Unload( gameDLL ); + gameDLL = NULL; + common->FatalError( "couldn't find game DLL API" ); + return; + } + + gameImport.version = GAME_API_VERSION; + gameImport.sys = ::sys; + gameImport.common = ::common; + gameImport.cmdSystem = ::cmdSystem; + gameImport.cvarSystem = ::cvarSystem; + gameImport.fileSystem = ::fileSystem; + gameImport.renderSystem = ::renderSystem; + gameImport.soundSystem = ::soundSystem; + gameImport.renderModelManager = ::renderModelManager; + gameImport.uiManager = ::uiManager; + gameImport.declManager = ::declManager; + gameImport.AASFileManager = ::AASFileManager; + gameImport.collisionModelManager = ::collisionModelManager; + + gameExport = *GetGameAPI( &gameImport ); + + if ( gameExport.version != GAME_API_VERSION ) { + Sys_DLL_Unload( gameDLL ); + gameDLL = NULL; + common->FatalError( "wrong game DLL API version" ); + return; + } + + game = gameExport.game; + gameEdit = gameExport.gameEdit; + +#endif + + // initialize the game object + if ( game != NULL ) { + game->Init(); + } +} + +/* +================= +idCommonLocal::UnloadGameDLL +================= +*/ +void idCommonLocal::CleanupShell() { + if ( game != NULL ) { + game->Shell_Cleanup(); + } +} + +/* +================= +idCommonLocal::UnloadGameDLL +================= +*/ +void idCommonLocal::UnloadGameDLL() { + + // shut down the game object + if ( game != NULL ) { + game->Shutdown(); + } + +#ifdef __DOOM_DLL__ + + if ( gameDLL ) { + Sys_DLL_Unload( gameDLL ); + gameDLL = NULL; + } + game = NULL; + gameEdit = NULL; + +#endif +} + +/* +================= +idCommonLocal::IsInitialized +================= +*/ +bool idCommonLocal::IsInitialized() const { + return com_fullyInitialized; +} + + +//====================================================================================== + + +/* +================= +idCommonLocal::Init +================= +*/ +void idCommonLocal::Init( int argc, const char * const * argv, const char *cmdline ) { + try { + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // initialize idLib + idLib::Init(); + + // clear warning buffer + ClearWarnings( GAME_NAME " initialization" ); + + idLib::Printf( va( "Command line: %s\n", cmdline ) ); + //::MessageBox( NULL, cmdline, "blah", MB_OK ); + // parse command line options + idCmdArgs args; + if ( cmdline ) { + // tokenize if the OS doesn't do it for us + args.TokenizeString( cmdline, true ); + argv = args.GetArgs( &argc ); + } + ParseCommandLine( argc, argv ); + + // init console command system + cmdSystem->Init(); + + // init CVar system + cvarSystem->Init(); + + // register all static CVars + idCVar::RegisterStaticVars(); + + idLib::Printf( "QA Timing INIT: %06dms\n", Sys_Milliseconds() ); + + // print engine version + Printf( "%s\n", version.string ); + + // initialize key input/binding, done early so bind command exists + idKeyInput::Init(); + + // init the console so we can take prints + console->Init(); + + // get architecture info + Sys_Init(); + + // initialize networking + Sys_InitNetworking(); + + // override cvars from command line + StartupVariable( NULL ); + + consoleUsed = com_allowConsole.GetBool(); + + if ( Sys_AlreadyRunning() ) { + Sys_Quit(); + } + + // initialize processor specific SIMD implementation + InitSIMD(); + + // initialize the file system + fileSystem->Init(); + + const char * defaultLang = Sys_DefaultLanguage(); + com_isJapaneseSKU = ( idStr::Icmp( defaultLang, ID_LANG_JAPANESE ) == 0 ); + + // Allow the system to set a default lanugage + Sys_SetLanguageFromSystem(); + + // Pre-allocate our 20 MB save buffer here on time, instead of on-demand for each save.... + + saveFile.SetNameAndType( SAVEGAME_CHECKPOINT_FILENAME, SAVEGAMEFILE_BINARY ); + saveFile.PreAllocate( MIN_SAVEGAME_SIZE_BYTES ); + + stringsFile.SetNameAndType( SAVEGAME_STRINGS_FILENAME, SAVEGAMEFILE_BINARY ); + stringsFile.PreAllocate( MAX_SAVEGAME_STRING_TABLE_SIZE ); + + fileSystem->BeginLevelLoad( "_startup", saveFile.GetDataPtr(), saveFile.GetAllocated() ); + + // initialize the declaration manager + declManager->Init(); + + // init journalling, etc + eventLoop->Init(); + + // init the parallel job manager + parallelJobManager->Init(); + + // exec the startup scripts + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec default.cfg\n" ); + +#ifdef CONFIG_FILE + // skip the config file if "safe" is on the command line + if ( !SafeMode() && !g_demoMode.GetBool() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec " CONFIG_FILE "\n" ); + } +#endif + + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec autoexec.cfg\n" ); + + // run cfg execution + cmdSystem->ExecuteCommandBuffer(); + + // re-override anything from the config files with command line args + StartupVariable( NULL ); + + // if any archived cvars are modified after this, we will trigger a writing of the config file + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + + // init OpenGL, which will open a window and connect sound and input hardware + renderSystem->InitOpenGL(); + + // Support up to 2 digits after the decimal point + com_engineHz_denominator = 100LL * com_engineHz.GetFloat(); + com_engineHz_latched = com_engineHz.GetFloat(); + + // start the sound system, but don't do any hardware operations yet + soundSystem->Init(); + + // initialize the renderSystem data structures + renderSystem->Init(); + + whiteMaterial = declManager->FindMaterial( "_white" ); + + if ( idStr::Icmp( sys_lang.GetString(), ID_LANG_FRENCH ) == 0 ) { + // If the user specified french, we show french no matter what SKU + splashScreen = declManager->FindMaterial( "guis/assets/splash/legal_french" ); + } else if ( idStr::Icmp( defaultLang, ID_LANG_FRENCH ) == 0 ) { + // If the lead sku is french (ie: europe), display figs + splashScreen = declManager->FindMaterial( "guis/assets/splash/legal_figs" ); + } else { + // Otherwise show it in english + splashScreen = declManager->FindMaterial( "guis/assets/splash/legal_english" ); + } + + const int legalMinTime = 4000; + const bool showVideo = ( !com_skipIntroVideos.GetBool () && fileSystem->UsingResourceFiles() ); + if ( showVideo ) { + RenderBink( "video\\loadvideo.bik" ); + RenderSplash(); + RenderSplash(); + } else { + idLib::Printf( "Skipping Intro Videos!\n" ); + // display the legal splash screen + // No clue why we have to render this twice to show up... + RenderSplash(); + RenderSplash(); + } + + + int legalStartTime = Sys_Milliseconds(); + declManager->Init2(); + + // initialize string database so we can use it for loading messages + InitLanguageDict(); + + // spawn the game thread, even if we are going to run without SMP + // one meg stack, because it can parse decls from gui surfaces (unfortunately) + // use a lower priority so job threads can run on the same core + gameThread.StartWorkerThread( "Game/Draw", CORE_1B, THREAD_BELOW_NORMAL, 0x100000 ); + // boost this thread's priority, so it will prevent job threads from running while + // the render back end still has work to do + + // init the user command input code + usercmdGen->Init(); + + Sys_SetRumble( 0, 0, 0 ); + + // initialize the user interfaces + uiManager->Init(); + + // startup the script debugger + // DebuggerServerInit(); + + // load the game dll + LoadGameDLL(); + + // On the PC touch them all so they get included in the resource build + if ( !fileSystem->UsingResourceFiles() ) { + declManager->FindMaterial( "guis/assets/splash/legal_english" ); + declManager->FindMaterial( "guis/assets/splash/legal_french" ); + declManager->FindMaterial( "guis/assets/splash/legal_figs" ); + // register the japanese font so it gets included + renderSystem->RegisterFont( "DFPHeiseiGothicW7" ); + // Make sure all videos get touched because you can bring videos from one map to another, they need to be included in all maps + for ( int i = 0; i < declManager->GetNumDecls( DECL_VIDEO ); i++ ) { + declManager->DeclByIndex( DECL_VIDEO, i ); + } + } + + fileSystem->UnloadResourceContainer( "_ordered" ); + + // the same idRenderWorld will be used for all games + // and demos, insuring that level specific models + // will be freed + renderWorld = renderSystem->AllocRenderWorld(); + soundWorld = soundSystem->AllocSoundWorld( renderWorld ); + + menuSoundWorld = soundSystem->AllocSoundWorld( NULL ); + menuSoundWorld->PlaceListener( vec3_origin, mat3_identity, 0 ); + + // init the session + session->Initialize(); + session->InitializeSoundRelatedSystems(); + + InitializeMPMapsModes(); + + // leaderboards need to be initialized after InitializeMPMapsModes, which populates the MP Map list. + if( game != NULL ) { + game->Leaderboards_Init(); + } + + CreateMainMenu(); + + commonDialog.Init(); + + // load the console history file + consoleHistory.LoadHistoryFile(); + + AddStartupCommands(); + + StartMenu( true ); + + while ( Sys_Milliseconds() - legalStartTime < legalMinTime ) { + RenderSplash(); + Sys_GenerateEvents(); + Sys_Sleep( 10 ); + }; + + // print all warnings queued during initialization + PrintWarnings(); + + // remove any prints from the notify lines + console->ClearNotifyLines(); + + CheckStartupStorageRequirements(); + + + if ( preload_CommonAssets.GetBool() && fileSystem->UsingResourceFiles() ) { + idPreloadManifest manifest; + manifest.LoadManifest( "_common.preload" ); + globalImages->Preload( manifest, false ); + soundSystem->Preload( manifest ); + } + + fileSystem->EndLevelLoad(); + + // Initialize support for Doom classic. + doomClassicMaterial = declManager->FindMaterial( "_doomClassic" ); + idImage *image = globalImages->GetImage( "_doomClassic" ); + if ( image != NULL ) { + idImageOpts opts; + opts.format = FMT_RGBA8; + opts.colorFormat = CFM_DEFAULT; + opts.width = DOOMCLASSIC_RENDERWIDTH; + opts.height = DOOMCLASSIC_RENDERHEIGHT; + opts.numLevels = 1; + image->AllocImage( opts, TF_LINEAR, TR_REPEAT ); + } + + com_fullyInitialized = true; + + + // No longer need the splash screen + if ( splashScreen != NULL ) { + for ( int i = 0; i < splashScreen->GetNumStages(); i++ ) { + idImage * image = splashScreen->GetStage( i )->texture.image; + if ( image != NULL ) { + image->PurgeImage(); + } + } + } + + Printf( "--- Common Initialization Complete ---\n" ); + + idLib::Printf( "QA Timing IIS: %06dms\n", Sys_Milliseconds() ); + } catch( idException & ) { + Sys_Error( "Error during initialization" ); + } +} + +/* +================= +idCommonLocal::Shutdown +================= +*/ +void idCommonLocal::Shutdown() { + + if ( com_shuttingDown ) { + return; + } + com_shuttingDown = true; + + + // Kill any pending saves... + printf( "session->GetSaveGameManager().CancelToTerminate();\n" ); + session->GetSaveGameManager().CancelToTerminate(); + + // kill sound first + printf( "soundSystem->StopAllSounds();\n" ); + soundSystem->StopAllSounds(); + + // shutdown the script debugger + // DebuggerServerShutdown(); + + if ( aviCaptureMode ) { + printf( "EndAVICapture();\n" ); + EndAVICapture(); + } + + printf( "Stop();\n" ); + Stop(); + + printf( "CleanupShell();\n" ); + CleanupShell(); + + printf( "delete loadGUI;\n" ); + delete loadGUI; + loadGUI = NULL; + + printf( "delete renderWorld;\n" ); + delete renderWorld; + renderWorld = NULL; + + printf( "delete soundWorld;\n" ); + delete soundWorld; + soundWorld = NULL; + + printf( "delete menuSoundWorld;\n" ); + delete menuSoundWorld; + menuSoundWorld = NULL; + + // shut down the session + printf( "session->ShutdownSoundRelatedSystems();\n" ); + session->ShutdownSoundRelatedSystems(); + printf( "session->Shutdown();\n" ); + session->Shutdown(); + + // shutdown, deallocate leaderboard definitions. + if( game != NULL ) { + printf( "game->Leaderboards_Shutdown();\n" ); + game->Leaderboards_Shutdown(); + } + + // shut down the user interfaces + printf( "uiManager->Shutdown();\n" ); + uiManager->Shutdown(); + + // shut down the sound system + printf( "soundSystem->Shutdown();\n" ); + soundSystem->Shutdown(); + + // shut down the user command input code + printf( "usercmdGen->Shutdown();\n" ); + usercmdGen->Shutdown(); + + // shut down the event loop + printf( "eventLoop->Shutdown();\n" ); + eventLoop->Shutdown(); + + // shutdown the decl manager + printf( "declManager->Shutdown();\n" ); + declManager->Shutdown(); + + // shut down the renderSystem + printf( "renderSystem->Shutdown();\n" ); + renderSystem->Shutdown(); + + printf( "commonDialog.Shutdown();\n" ); + commonDialog.Shutdown(); + + // unload the game dll + printf( "UnloadGameDLL();\n" ); + UnloadGameDLL(); + + printf( "saveFile.Clear( true );\n" ); + saveFile.Clear( true ); + printf( "stringsFile.Clear( true );\n" ); + stringsFile.Clear( true ); + + // only shut down the log file after all output is done + printf( "CloseLogFile();\n" ); + CloseLogFile(); + + // shut down the file system + printf( "fileSystem->Shutdown( false );\n" ); + fileSystem->Shutdown( false ); + + // shut down non-portable system services + printf( "Sys_Shutdown();\n" ); + Sys_Shutdown(); + + // shut down the console + printf( "console->Shutdown();\n" ); + console->Shutdown(); + + // shut down the key system + printf( "idKeyInput::Shutdown();\n" ); + idKeyInput::Shutdown(); + + // shut down the cvar system + printf( "cvarSystem->Shutdown();\n" ); + cvarSystem->Shutdown(); + + // shut down the console command system + printf( "cmdSystem->Shutdown();\n" ); + cmdSystem->Shutdown(); + + // free any buffered warning messages + printf( "ClearWarnings( GAME_NAME \" shutdown\" );\n" ); + ClearWarnings( GAME_NAME " shutdown" ); + printf( "warningCaption.Clear();\n" ); + warningCaption.Clear(); + printf( "errorList.Clear();\n" ); + errorList.Clear(); + + // shutdown idLib + printf( "idLib::ShutDown();\n" ); + idLib::ShutDown(); +} + +/* +======================== +idCommonLocal::CreateMainMenu +======================== +*/ +void idCommonLocal::CreateMainMenu() { + if ( game != NULL ) { + // note which media we are going to need to load + declManager->BeginLevelLoad(); + renderSystem->BeginLevelLoad(); + soundSystem->BeginLevelLoad(); + uiManager->BeginLevelLoad(); + + // create main inside an "empty" game level load - so assets get + // purged automagically when we transition to a "real" map + game->Shell_CreateMenu( false ); + game->Shell_Show( true ); + game->Shell_SyncWithSession(); + + // load + renderSystem->EndLevelLoad(); + soundSystem->EndLevelLoad(); + declManager->EndLevelLoad(); + uiManager->EndLevelLoad( "" ); + } +} + +/* +=============== +idCommonLocal::Stop + +called on errors and game exits +=============== +*/ +void idCommonLocal::Stop( bool resetSession ) { + ClearWipe(); + + // clear mapSpawned and demo playing flags + UnloadMap(); + + soundSystem->StopAllSounds(); + + insideUpdateScreen = false; + insideExecuteMapChange = false; + + // drop all guis + ExitMenu(); + + if ( resetSession ) { + session->QuitMatchToTitle(); + } +} + +/* +=============== +idCommonLocal::BusyWait +=============== +*/ +void idCommonLocal::BusyWait() { + Sys_GenerateEvents(); + + const bool captureToImage = false; + UpdateScreen( captureToImage ); + + session->UpdateSignInManager(); + session->Pump(); +} + +/* +=============== +idCommonLocal::WaitForSessionState +=============== +*/ +bool idCommonLocal::WaitForSessionState( idSession::sessionState_t desiredState ) { + if ( session->GetState() == desiredState ) { + return true; + } + + while ( true ) { + BusyWait(); + + idSession::sessionState_t sessionState = session->GetState(); + if ( sessionState == desiredState ) { + return true; + } + if ( sessionState != idSession::LOADING && + sessionState != idSession::SEARCHING && + sessionState != idSession::CONNECTING && + sessionState != idSession::BUSY && + sessionState != desiredState ) { + return false; + } + + Sys_Sleep( 10 ); + } +} + +/* +======================== +idCommonLocal::LeaveGame +======================== +*/ +void idCommonLocal::LeaveGame() { + + const bool captureToImage = false; + UpdateScreen( captureToImage ); + + ResetNetworkingState(); + + + Stop( false ); + + CreateMainMenu(); + + StartMenu(); + + +} + +/* +=============== +idCommonLocal::ProcessEvent +=============== +*/ +bool idCommonLocal::ProcessEvent( const sysEvent_t *event ) { + // hitting escape anywhere brings up the menu + if ( game && game->IsInGame() ) { + if ( event->evType == SE_KEY && event->evValue2 == 1 && ( event->evValue == K_ESCAPE || event->evValue == K_JOY9 ) ) { + if ( !game->Shell_IsActive() ) { + + // menus / etc + if ( MenuEvent( event ) ) { + return true; + } + + console->Close(); + + StartMenu(); + return true; + } else { + console->Close(); + + // menus / etc + if ( MenuEvent( event ) ) { + return true; + } + + game->Shell_ClosePause(); + } + } + } + + // let the pull-down console take it if desired + if ( console->ProcessEvent( event, false ) ) { + return true; + } + if ( session->ProcessInputEvent( event ) ) { + return true; + } + + if ( Dialog().IsDialogActive() ) { + Dialog().HandleDialogEvent( event ); + return true; + } + + // Let Doom classic run events. + if ( IsPlayingDoomClassic() ) { + // Translate the event to Doom classic format. + event_t classicEvent; + if ( event->evType == SE_KEY ) { + + if( event->evValue2 == 1 ) { + classicEvent.type = ev_keydown; + } else if( event->evValue2 == 0 ) { + classicEvent.type = ev_keyup; + } + + DoomLib::SetPlayer( 0 ); + + extern Globals * g; + if ( g != NULL ) { + classicEvent.data1 = DoomLib::RemapControl( event->GetKey() ); + + D_PostEvent( &classicEvent ); + } + DoomLib::SetPlayer( -1 ); + } + + // Let the classics eat all events. + return true; + } + + // menus / etc + if ( MenuEvent( event ) ) { + return true; + } + + // if we aren't in a game, force the console to take it + if ( !mapSpawned ) { + console->ProcessEvent( event, true ); + return true; + } + + // in game, exec bindings for all key downs + if ( event->evType == SE_KEY && event->evValue2 == 1 ) { + idKeyInput::ExecKeyBinding( event->evValue ); + return true; + } + + return false; +} + +/* +======================== +idCommonLocal::ResetPlayerInput +======================== +*/ +void idCommonLocal::ResetPlayerInput( int playerIndex ) { + userCmdMgr.ResetPlayer( playerIndex ); +} + +/* +======================== +idCommonLocal::SwitchToGame +======================== +*/ +void idCommonLocal::SwitchToGame( currentGame_t newGame ) { + idealCurrentGame = newGame; +} + +/* +======================== +idCommonLocal::PerformGameSwitch +======================== +*/ +void idCommonLocal::PerformGameSwitch() { + // If the session state is past the menu, we should be in Doom 3. + // This will happen if, for example, we accept an invite while playing + // Doom or Doom 2. + if ( session->GetState() > idSession::IDLE ) { + idealCurrentGame = DOOM3_BFG; + } + + if ( currentGame == idealCurrentGame ) { + return; + } + + const int DOOM_CLASSIC_HZ = 35; + + if ( idealCurrentGame == DOOM_CLASSIC || idealCurrentGame == DOOM2_CLASSIC ) { + // Pause Doom 3 sound. + if ( menuSoundWorld != NULL ) { + menuSoundWorld->Pause(); + } + + DoomLib::skipToNew = false; + DoomLib::skipToLoad = false; + + // Reset match parameters for the classics. + DoomLib::matchParms = idMatchParameters(); + + // The classics use the usercmd manager too, clear it. + userCmdMgr.SetDefaults(); + + // Classics need a local user too. + session->UpdateSignInManager(); + session->GetSignInManager().RegisterLocalUser( 0 ); + + com_engineHz_denominator = 100LL * DOOM_CLASSIC_HZ; + com_engineHz_latched = DOOM_CLASSIC_HZ; + + DoomLib::SetCurrentExpansion( idealCurrentGame ); + + } else if ( idealCurrentGame == DOOM3_BFG ) { + DoomLib::Interface.Shutdown(); + com_engineHz_denominator = 100LL * com_engineHz.GetFloat(); + com_engineHz_latched = com_engineHz.GetFloat(); + + // Don't MoveToPressStart if we have an invite, we need to go + // directly to the lobby. + if ( session->GetState() <= idSession::IDLE ) { + session->MoveToPressStart(); + } + + // Unpause Doom 3 sound. + if ( menuSoundWorld != NULL ) { + menuSoundWorld->UnPause(); + } + } + + currentGame = idealCurrentGame; +} + +/* +================== +Common_WritePrecache_f +================== +*/ +CONSOLE_COMMAND( writePrecache, "writes precache commands", NULL ) { + if ( args.Argc() != 2 ) { + common->Printf( "USAGE: writePrecache \n" ); + return; + } + idStr str = args.Argv(1); + str.DefaultFileExtension( ".cfg" ); + idFile *f = fileSystem->OpenFileWrite( str ); + declManager->WritePrecacheCommands( f ); + renderModelManager->WritePrecacheCommands( f ); + uiManager->WritePrecacheCommands( f ); + + fileSystem->CloseFile( f ); +} + +/* +================ +Common_Disconnect_f +================ +*/ +CONSOLE_COMMAND_SHIP( disconnect, "disconnects from a game", NULL ) { + session->QuitMatch(); +} + +/* +=============== +Common_Hitch_f +=============== +*/ +CONSOLE_COMMAND( hitch, "hitches the game", NULL ) { + if ( args.Argc() == 2 ) { + Sys_Sleep( atoi(args.Argv(1)) ); + } else { + Sys_Sleep( 100 ); + } +} + +CONSOLE_COMMAND( showStringMemory, "shows memory used by strings", NULL ) { + idStr::ShowMemoryUsage_f( args ); +} +CONSOLE_COMMAND( showDictMemory, "shows memory used by dictionaries", NULL ) { + idDict::ShowMemoryUsage_f( args ); +} +CONSOLE_COMMAND( listDictKeys, "lists all keys used by dictionaries", NULL ) { + idDict::ListKeys_f( args ); +} +CONSOLE_COMMAND( listDictValues, "lists all values used by dictionaries", NULL ) { + idDict::ListValues_f( args ); +} +CONSOLE_COMMAND( testSIMD, "test SIMD code", NULL ) { + idSIMD::Test_f( args ); +} diff --git a/neo/framework/Common.h b/neo/framework/Common.h new file mode 100644 index 00000000..eb12c4b0 --- /dev/null +++ b/neo/framework/Common.h @@ -0,0 +1,316 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +/* +============================================================== + + Common + +============================================================== +*/ + +extern idCVar com_engineHz; +extern float com_engineHz_latched; +extern int64 com_engineHz_numerator; +extern int64 com_engineHz_denominator; + +// Returns the msec the frame starts on +ID_INLINE int FRAME_TO_MSEC( int64 frame ) { + return (int)( ( frame * com_engineHz_numerator ) / com_engineHz_denominator ); +} +// Rounds DOWN to the nearest frame +ID_INLINE int MSEC_TO_FRAME_FLOOR( int msec ) { + return (int)( ( ( (int64)msec * com_engineHz_denominator ) + ( com_engineHz_denominator - 1 ) ) / com_engineHz_numerator ); +} +// Rounds UP to the nearest frame +ID_INLINE int MSEC_TO_FRAME_CEIL( int msec ) { + return (int)( ( ( (int64)msec * com_engineHz_denominator ) + ( com_engineHz_numerator - 1 ) ) / com_engineHz_numerator ); +} +// Aligns msec so it starts on a frame bondary +ID_INLINE int MSEC_ALIGN_TO_FRAME( int msec ) { + return FRAME_TO_MSEC( MSEC_TO_FRAME_CEIL( msec ) ); +} + +class idGame; +class idRenderWorld; +class idSoundWorld; +class idSession; +class idCommonDialog; +class idDemoFile; +class idUserInterface; +class idSaveLoadParms; +class idMatchParameters; + +struct lobbyConnectInfo_t; + +ID_INLINE void BeginProfileNamedEventColor( uint32 color, VERIFY_FORMAT_STRING const char * szName ) { +} +ID_INLINE void EndProfileNamedEvent() { +} + +ID_INLINE void BeginProfileNamedEvent( VERIFY_FORMAT_STRING const char * szName ) { + BeginProfileNamedEventColor( (uint32) 0xFF00FF00, szName ); +} + +class idScopedProfileEvent { +public: + idScopedProfileEvent( const char * name ) { BeginProfileNamedEvent( name ); } + ~idScopedProfileEvent() { EndProfileNamedEvent(); } +}; + +#define SCOPED_PROFILE_EVENT( x ) idScopedProfileEvent scopedProfileEvent_##__LINE__( x ) + +ID_INLINE bool BeginTraceRecording( char * szName ) { + return false; +} + +ID_INLINE bool EndTraceRecording() { + return false; +} + +typedef enum { + EDITOR_NONE = 0, + EDITOR_RADIANT = BIT(1), + EDITOR_GUI = BIT(2), + EDITOR_DEBUGGER = BIT(3), + EDITOR_SCRIPT = BIT(4), + EDITOR_LIGHT = BIT(5), + EDITOR_SOUND = BIT(6), + EDITOR_DECL = BIT(7), + EDITOR_AF = BIT(8), + EDITOR_PARTICLE = BIT(9), + EDITOR_PDA = BIT(10), + EDITOR_AAS = BIT(11), + EDITOR_MATERIAL = BIT(12) +} toolFlag_t; + +#define STRTABLE_ID "#str_" +#define STRTABLE_ID_LENGTH 5 + +extern idCVar com_version; +extern idCVar com_developer; +extern idCVar com_allowConsole; +extern idCVar com_speeds; +extern idCVar com_showFPS; +extern idCVar com_showMemoryUsage; +extern idCVar com_updateLoadSize; +extern idCVar com_productionMode; + +struct MemInfo_t { + idStr filebase; + + int total; + int assetTotals; + + // memory manager totals + int memoryManagerTotal; + + // subsystem totals + int gameSubsystemTotal; + int renderSubsystemTotal; + + // asset totals + int imageAssetsTotal; + int modelAssetsTotal; + int soundAssetsTotal; +}; + +struct mpMap_t { + + void operator=( const mpMap_t & src ) { + mapFile = src.mapFile; + mapName = src.mapName; + supportedModes = src.supportedModes; + } + + idStr mapFile; + idStr mapName; + uint32 supportedModes; +}; + +static const int MAX_LOGGED_STATS = 60 * 120; // log every half second + +enum currentGame_t { + DOOM_CLASSIC, + DOOM2_CLASSIC, + DOOM3_BFG +}; + +class idCommon { +public: + virtual ~idCommon() {} + + // Initialize everything. + // if the OS allows, pass argc/argv directly (without executable name) + // otherwise pass the command line in a single string (without executable name) + virtual void Init( int argc, const char * const * argv, const char *cmdline ) = 0; + + // Shuts down everything. + virtual void Shutdown() = 0; + virtual bool IsShuttingDown() const = 0; + + virtual void CreateMainMenu() = 0; + + // Shuts down everything. + virtual void Quit() = 0; + + // Returns true if common initialization is complete. + virtual bool IsInitialized() const = 0; + + // Called repeatedly as the foreground thread for rendering and game logic. + virtual void Frame() = 0; + + // Redraws the screen, handling games, guis, console, etc + // in a modal manner outside the normal frame loop + virtual void UpdateScreen( bool captureToImage ) = 0; + + virtual void UpdateLevelLoadPacifier() = 0; + + + // Checks for and removes command line "+set var arg" constructs. + // If match is NULL, all set commands will be executed, otherwise + // only a set with the exact name. + virtual void StartupVariable( const char * match ) = 0; + + // Begins redirection of console output to the given buffer. + virtual void BeginRedirect( char *buffer, int buffersize, void (*flush)( const char * ) ) = 0; + + // Stops redirection of console output. + virtual void EndRedirect() = 0; + + // Update the screen with every message printed. + virtual void SetRefreshOnPrint( bool set ) = 0; + + // Prints message to the console, which may cause a screen update if com_refreshOnPrint is set. + virtual void Printf( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + + // Same as Printf, with a more usable API - Printf pipes to this. + virtual void VPrintf( const char *fmt, va_list arg ) = 0; + + // Prints message that only shows up if the "developer" cvar is set, + // and NEVER forces a screen update, which could cause reentrancy problems. + virtual void DPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + + // Prints WARNING %s message and adds the warning message to a queue for printing later on. + virtual void Warning( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + + // Prints WARNING %s message in yellow that only shows up if the "developer" cvar is set. + virtual void DWarning( VERIFY_FORMAT_STRING const char *fmt, ...) = 0; + + // Prints all queued warnings. + virtual void PrintWarnings() = 0; + + // Removes all queued warnings. + virtual void ClearWarnings( const char *reason ) = 0; + + // Issues a C++ throw. Normal errors just abort to the game loop, + // which is appropriate for media or dynamic logic errors. + virtual void Error( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + + // Fatal errors quit all the way to a system dialog box, which is appropriate for + // static internal errors or cases where the system may be corrupted. + virtual void FatalError( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + + // Returns key bound to the command + virtual const char * KeysFromBinding( const char *bind ) = 0; + + // Returns the binding bound to the key + virtual const char * BindingFromKey( const char *key ) = 0; + + // Directly sample a button. + virtual int ButtonState( int key ) = 0; + + // Directly sample a keystate. + virtual int KeyState( int key ) = 0; + + // Returns true if a multiplayer game is running. + // CVars and commands are checked differently in multiplayer mode. + virtual bool IsMultiplayer() = 0; + virtual bool IsServer() = 0; + virtual bool IsClient() = 0; + + // Returns true if the player has ever enabled the console + virtual bool GetConsoleUsed() = 0; + + // Returns the rate (in ms between snaps) that we want to generate snapshots + virtual int GetSnapRate() = 0; + + virtual void NetReceiveReliable( int peer, int type, idBitMsg & msg ) = 0; + virtual void NetReceiveSnapshot( class idSnapShot & ss ) = 0; + virtual void NetReceiveUsercmds( int peer, idBitMsg & msg ) = 0; + + // Processes the given event. + virtual bool ProcessEvent( const sysEvent_t * event ) = 0; + + virtual bool LoadGame( const char * saveName ) = 0; + virtual bool SaveGame( const char * saveName ) = 0; + + virtual idDemoFile * ReadDemo() = 0; + virtual idDemoFile * WriteDemo() = 0; + + virtual idGame * Game() = 0; + virtual idRenderWorld * RW() = 0; + virtual idSoundWorld * SW() = 0; + virtual idSoundWorld * MenuSW() = 0; + virtual idSession * Session() = 0; + virtual idCommonDialog & Dialog() = 0; + + virtual void OnSaveCompleted( idSaveLoadParms & parms ) = 0; + virtual void OnLoadCompleted( idSaveLoadParms & parms ) = 0; + virtual void OnLoadFilesCompleted( idSaveLoadParms & parms ) = 0; + virtual void OnEnumerationCompleted( idSaveLoadParms & parms ) = 0; + virtual void OnDeleteCompleted( idSaveLoadParms & parms ) = 0; + virtual void TriggerScreenWipe( const char * _wipeMaterial, bool hold ) = 0; + + virtual void OnStartHosting( idMatchParameters & parms ) = 0; + + virtual int GetGameFrame() = 0; + + virtual void LaunchExternalTitle( int titleIndex, int device, const lobbyConnectInfo_t * const connectInfo ) = 0; + + virtual void InitializeMPMapsModes() = 0; + virtual const idStrList & GetModeList() const = 0; + virtual const idStrList & GetModeDisplayList() const = 0; + virtual const idList & GetMapList() const = 0; + + virtual void ResetPlayerInput( int playerIndex ) = 0; + + virtual bool JapaneseCensorship() const = 0; + + virtual void QueueShowShell() = 0; // Will activate the shell on the next frame. + + virtual currentGame_t GetCurrentGame() const = 0; + virtual void SwitchToGame( currentGame_t newGame ) = 0; +}; + +extern idCommon * common; + +#endif /* !__COMMON_H__ */ diff --git a/neo/framework/Common_demos.cpp b/neo/framework/Common_demos.cpp new file mode 100644 index 00000000..0ba9716a --- /dev/null +++ b/neo/framework/Common_demos.cpp @@ -0,0 +1,518 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" + +/* +================ +FindUnusedFileName +================ +*/ +static idStr FindUnusedFileName( const char *format ) { + idStr filename; + + for ( int i = 0 ; i < 999 ; i++ ) { + filename.Format( format, i ); + int len = fileSystem->ReadFile( filename, NULL, NULL ); + if ( len <= 0 ) { + return filename; // file doesn't exist + } + } + + return filename; +} + +/* +================ +idCommonLocal::StartRecordingRenderDemo +================ +*/ +void idCommonLocal::StartRecordingRenderDemo( const char *demoName ) { + if ( writeDemo ) { + // allow it to act like a toggle + StopRecordingRenderDemo(); + return; + } + + if ( !demoName[0] ) { + common->Printf( "idCommonLocal::StartRecordingRenderDemo: no name specified\n" ); + return; + } + + console->Close(); + + writeDemo = new (TAG_SYSTEM) idDemoFile; + if ( !writeDemo->OpenForWriting( demoName ) ) { + common->Printf( "error opening %s\n", demoName ); + delete writeDemo; + writeDemo = NULL; + return; + } + + common->Printf( "recording to %s\n", writeDemo->GetName() ); + + writeDemo->WriteInt( DS_VERSION ); + writeDemo->WriteInt( RENDERDEMO_VERSION ); + + // if we are in a map already, dump the current state + soundWorld->StartWritingDemo( writeDemo ); + renderWorld->StartWritingDemo( writeDemo ); +} + +/* +================ +idCommonLocal::StopRecordingRenderDemo +================ +*/ +void idCommonLocal::StopRecordingRenderDemo() { + if ( !writeDemo ) { + common->Printf( "idCommonLocal::StopRecordingRenderDemo: not recording\n" ); + return; + } + soundWorld->StopWritingDemo(); + renderWorld->StopWritingDemo(); + + writeDemo->Close(); + common->Printf( "stopped recording %s.\n", writeDemo->GetName() ); + delete writeDemo; + writeDemo = NULL; +} + +/* +================ +idCommonLocal::StopPlayingRenderDemo + +Reports timeDemo numbers and finishes any avi recording +================ +*/ +void idCommonLocal::StopPlayingRenderDemo() { + if ( !readDemo ) { + timeDemo = TD_NO; + return; + } + + // Record the stop time before doing anything that could be time consuming + int timeDemoStopTime = Sys_Milliseconds(); + + EndAVICapture(); + + readDemo->Close(); + + soundWorld->StopAllSounds(); + soundSystem->SetPlayingSoundWorld( menuSoundWorld ); + + common->Printf( "stopped playing %s.\n", readDemo->GetName() ); + delete readDemo; + readDemo = NULL; + + if ( timeDemo ) { + // report the stats + float demoSeconds = ( timeDemoStopTime - timeDemoStartTime ) * 0.001f; + float demoFPS = numDemoFrames / demoSeconds; + idStr message = va( "%i frames rendered in %3.1f seconds = %3.1f fps\n", numDemoFrames, demoSeconds, demoFPS ); + + common->Printf( message ); + if ( timeDemo == TD_YES_THEN_QUIT ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + } + timeDemo = TD_NO; + } +} + +/* +================ +idCommonLocal::DemoShot + +A demoShot is a single frame demo +================ +*/ +void idCommonLocal::DemoShot( const char *demoName ) { + StartRecordingRenderDemo( demoName ); + + // force draw one frame + const bool captureToImage = false; + UpdateScreen( captureToImage ); + + StopRecordingRenderDemo(); +} + +/* +================ +idCommonLocal::StartPlayingRenderDemo +================ +*/ +void idCommonLocal::StartPlayingRenderDemo( idStr demoName ) { + if ( !demoName[0] ) { + common->Printf( "idCommonLocal::StartPlayingRenderDemo: no name specified\n" ); + return; + } + + // make sure localSound / GUI intro music shuts up + soundWorld->StopAllSounds(); + soundWorld->PlayShaderDirectly( "", 0 ); + menuSoundWorld->StopAllSounds(); + menuSoundWorld->PlayShaderDirectly( "", 0 ); + + // exit any current game + Stop(); + + // automatically put the console away + console->Close(); + + readDemo = new (TAG_SYSTEM) idDemoFile; + demoName.DefaultFileExtension( ".demo" ); + if ( !readDemo->OpenForReading( demoName ) ) { + common->Printf( "couldn't open %s\n", demoName.c_str() ); + delete readDemo; + readDemo = NULL; + Stop(); + StartMenu(); + return; + } + + const bool captureToImage = false; + UpdateScreen( captureToImage ); + + AdvanceRenderDemo( true ); + + numDemoFrames = 1; + + timeDemoStartTime = Sys_Milliseconds(); +} + +/* +================ +idCommonLocal::TimeRenderDemo +================ +*/ +void idCommonLocal::TimeRenderDemo( const char *demoName, bool twice, bool quit ) { + idStr demo = demoName; + + StartPlayingRenderDemo( demo ); + + if ( twice && readDemo ) { + while ( readDemo ) { + const bool captureToImage = false; + UpdateScreen( captureToImage ); + AdvanceRenderDemo( true ); + } + + StartPlayingRenderDemo( demo ); + } + + + if ( !readDemo ) { + return; + } + + if ( quit ) { + // this allows hardware vendors to automate some testing + timeDemo = TD_YES_THEN_QUIT; + } else { + timeDemo = TD_YES; + } +} + + +/* +================ +idCommonLocal::BeginAVICapture +================ +*/ +void idCommonLocal::BeginAVICapture( const char *demoName ) { + idStr name = demoName; + name.ExtractFileBase( aviDemoShortName ); + aviCaptureMode = true; + aviDemoFrameCount = 0; + soundWorld->AVIOpen( va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() ); +} + +/* +================ +idCommonLocal::EndAVICapture +================ +*/ +void idCommonLocal::EndAVICapture() { + if ( !aviCaptureMode ) { + return; + } + + soundWorld->AVIClose(); + + // write a .roqParam file so the demo can be converted to a roq file + idFile *f = fileSystem->OpenFileWrite( va( "demos/%s/%s.roqParam", + aviDemoShortName.c_str(), aviDemoShortName.c_str() ) ); + f->Printf( "INPUT_DIR demos/%s\n", aviDemoShortName.c_str() ); + f->Printf( "FILENAME demos/%s/%s.RoQ\n", aviDemoShortName.c_str(), aviDemoShortName.c_str() ); + f->Printf( "\nINPUT\n" ); + f->Printf( "%s_*.tga [00000-%05i]\n", aviDemoShortName.c_str(), (int)( aviDemoFrameCount-1 ) ); + f->Printf( "END_INPUT\n" ); + delete f; + + common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() ); + + aviCaptureMode = false; +} + + +/* +================ +idCommonLocal::AVIRenderDemo +================ +*/ +void idCommonLocal::AVIRenderDemo( const char *_demoName ) { + idStr demoName = _demoName; // copy off from va() buffer + + StartPlayingRenderDemo( demoName ); + if ( !readDemo ) { + return; + } + + BeginAVICapture( demoName.c_str() ) ; + + // I don't understand why I need to do this twice, something + // strange with the nvidia swapbuffers? + const bool captureToImage = false; + UpdateScreen( captureToImage ); +} + +/* +================ +idCommonLocal::AVIGame + +Start AVI recording the current game session +================ +*/ +void idCommonLocal::AVIGame( const char *demoName ) { + if ( aviCaptureMode ) { + EndAVICapture(); + return; + } + + if ( !mapSpawned ) { + common->Printf( "No map spawned.\n" ); + } + + if ( !demoName || !demoName[0] ) { + idStr filename = FindUnusedFileName( "demos/game%03i.game" ); + demoName = filename.c_str(); + + // write a one byte stub .game file just so the FindUnusedFileName works, + fileSystem->WriteFile( demoName, demoName, 1 ); + } + + BeginAVICapture( demoName ) ; +} + +/* +================ +idCommonLocal::CompressDemoFile +================ +*/ +void idCommonLocal::CompressDemoFile( const char *scheme, const char *demoName ) { + idStr fullDemoName = "demos/"; + fullDemoName += demoName; + fullDemoName.DefaultFileExtension( ".demo" ); + idStr compressedName = fullDemoName; + compressedName.StripFileExtension(); + compressedName.Append( "_compressed.demo" ); + + int savedCompression = cvarSystem->GetCVarInteger("com_compressDemos"); + bool savedPreload = cvarSystem->GetCVarBool("com_preloadDemos"); + cvarSystem->SetCVarBool( "com_preloadDemos", false ); + cvarSystem->SetCVarInteger("com_compressDemos", atoi(scheme) ); + + idDemoFile demoread, demowrite; + if ( !demoread.OpenForReading( fullDemoName ) ) { + common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() ); + return; + } + if ( !demowrite.OpenForWriting( compressedName ) ) { + common->Printf( "Could not open %s for writing\n", compressedName.c_str() ); + demoread.Close(); + cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); + cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); + return; + } + common->SetRefreshOnPrint( true ); + common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() ); + + static const int bufferSize = 65535; + char buffer[bufferSize]; + int bytesRead; + while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) { + demowrite.Write( buffer, bytesRead ); + common->Printf( "." ); + } + + demoread.Close(); + demowrite.Close(); + + cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); + cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); + + common->Printf( "Done\n" ); + common->SetRefreshOnPrint( false ); + +} + +/* +=============== +idCommonLocal::AdvanceRenderDemo +=============== +*/ +void idCommonLocal::AdvanceRenderDemo( bool singleFrameOnly ) { + int ds = DS_FINISHED; + readDemo->ReadInt( ds ); + + switch ( ds ) { + case DS_FINISHED: + if ( numDemoFrames != 1 ) { + // if the demo has a single frame (a demoShot), continuously replay + // the renderView that has already been read + Stop(); + StartMenu(); + } + return; + case DS_RENDER: + if ( renderWorld->ProcessDemoCommand( readDemo, ¤tDemoRenderView, &demoTimeOffset ) ) { + // a view is ready to render + numDemoFrames++; + } + break; + case DS_SOUND: + soundWorld->ProcessDemoCommand( readDemo ); + break; + default: + common->Error( "Bad render demo token" ); + } +} + +/* +================ +Common_DemoShot_f +================ +*/ +CONSOLE_COMMAND( demoShot, "writes a screenshot as a demo", NULL ) { + if ( args.Argc() != 2 ) { + idStr filename = FindUnusedFileName( "demos/shot%03i.demo" ); + commonLocal.DemoShot( filename ); + } else { + commonLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) ); + } +} + +/* +================ +Common_RecordDemo_f +================ +*/ +CONSOLE_COMMAND( recordDemo, "records a demo", NULL ) { + if ( args.Argc() != 2 ) { + idStr filename = FindUnusedFileName( "demos/demo%03i.demo" ); + commonLocal.StartRecordingRenderDemo( filename ); + } else { + commonLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) ); + } +} + +/* +================ +Common_CompressDemo_f +================ +*/ +CONSOLE_COMMAND( compressDemo, "compresses a demo file", idCmdSystem::ArgCompletion_DemoName ) { + if ( args.Argc() == 2 ) { + commonLocal.CompressDemoFile( "2", args.Argv(1) ); + } else if ( args.Argc() == 3 ) { + commonLocal.CompressDemoFile( args.Argv(2), args.Argv(1) ); + } else { + common->Printf("use: CompressDemo [scheme]\nscheme is the same as com_compressDemo, defaults to 2" ); + } +} + +/* +================ +Common_StopRecordingDemo_f +================ +*/ +CONSOLE_COMMAND( stopRecording, "stops demo recording", NULL ) { + commonLocal.StopRecordingRenderDemo(); +} + +/* +================ +Common_PlayDemo_f +================ +*/ +CONSOLE_COMMAND( playDemo, "plays back a demo", idCmdSystem::ArgCompletion_DemoName ) { + if ( args.Argc() >= 2 ) { + commonLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) ); + } +} + +/* +================ +Common_TimeDemo_f +================ +*/ +CONSOLE_COMMAND( timeDemo, "times a demo", idCmdSystem::ArgCompletion_DemoName ) { + if ( args.Argc() >= 2 ) { + commonLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), ( args.Argc() > 2 ), false ); + } +} + +/* +================ +Common_TimeDemoQuit_f +================ +*/ +CONSOLE_COMMAND( timeDemoQuit, "times a demo and quits", idCmdSystem::ArgCompletion_DemoName ) { + commonLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), true ); +} + +/* +================ +Common_AVIDemo_f +================ +*/ +CONSOLE_COMMAND( aviDemo, "writes AVIs for a demo", idCmdSystem::ArgCompletion_DemoName ) { + commonLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) ); +} + +/* +================ +Common_AVIGame_f +================ +*/ +CONSOLE_COMMAND( aviGame, "writes AVIs for the current game", NULL ) { + commonLocal.AVIGame( args.Argv(1) ); +} diff --git a/neo/framework/Common_dialog.cpp b/neo/framework/Common_dialog.cpp new file mode 100644 index 00000000..a7562d5e --- /dev/null +++ b/neo/framework/Common_dialog.cpp @@ -0,0 +1,1400 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_dialog.h" + +idCVar popupDialog_debug( "popupDialog_debug", "0", CVAR_BOOL | CVAR_ARCHIVE, "display debug spam" ); + +extern idCVar g_demoMode; + +static const char * dialogStateToString[ GDM_MAX + 1 ] = { + ASSERT_ENUM_STRING( GDM_INVALID, 0 ), + ASSERT_ENUM_STRING( GDM_SWAP_DISKS_TO1, 1 ), + ASSERT_ENUM_STRING( GDM_SWAP_DISKS_TO2, 2 ), + ASSERT_ENUM_STRING( GDM_SWAP_DISKS_TO3, 3 ), + ASSERT_ENUM_STRING( GDM_NO_GAMER_PROFILE, 4 ), + ASSERT_ENUM_STRING( GDM_PLAY_ONLINE_NO_PROFILE, 5 ), + ASSERT_ENUM_STRING( GDM_LEADERBOARD_ONLINE_NO_PROFILE, 6 ), + ASSERT_ENUM_STRING( GDM_NO_STORAGE_SELECTED, 7 ), + ASSERT_ENUM_STRING( GDM_ONLINE_INCORRECT_PERMISSIONS, 8 ), + ASSERT_ENUM_STRING( GDM_SP_QUIT_SAVE, 9 ), + ASSERT_ENUM_STRING( GDM_SP_RESTART_SAVE, 10 ), + ASSERT_ENUM_STRING( GDM_SP_SIGNIN_CHANGE, 11 ), + ASSERT_ENUM_STRING( GDM_SERVER_NOT_AVAILABLE, 12 ), + ASSERT_ENUM_STRING( GDM_CONNECTION_LOST_HOST, 13 ), + ASSERT_ENUM_STRING( GDM_CONNECTION_LOST, 14 ), + ASSERT_ENUM_STRING( GDM_OPPONENT_CONNECTION_LOST, 15 ), + ASSERT_ENUM_STRING( GDM_HOST_CONNECTION_LOST, 16 ), + ASSERT_ENUM_STRING( GDM_HOST_CONNECTION_LOST_STATS, 17 ), + ASSERT_ENUM_STRING( GDM_FAILED_TO_LOAD_RANKINGS, 18 ), + ASSERT_ENUM_STRING( GDM_HOST_QUIT, 19 ), + ASSERT_ENUM_STRING( GDM_BECAME_HOST_PARTY, 20 ), + ASSERT_ENUM_STRING( GDM_NEW_HOST_PARTY, 21 ), + ASSERT_ENUM_STRING( GDM_LOBBY_BECAME_HOST_GAME, 22 ), + ASSERT_ENUM_STRING( GDM_LOBBY_NEW_HOST_GAME, 23 ), + ASSERT_ENUM_STRING( GDM_NEW_HOST_GAME, 24 ), + ASSERT_ENUM_STRING( GDM_NEW_HOST_GAME_STATS_DROPPED, 25 ), + ASSERT_ENUM_STRING( GDM_BECAME_HOST_GAME, 26 ), + ASSERT_ENUM_STRING( GDM_BECAME_HOST_GAME_STATS_DROPPED, 27 ), + ASSERT_ENUM_STRING( GDM_LOBBY_DISBANDED, 28 ), + ASSERT_ENUM_STRING( GDM_LEAVE_WITH_PARTY, 29 ), + ASSERT_ENUM_STRING( GDM_LEAVE_LOBBY_RET_MAIN, 30 ), + ASSERT_ENUM_STRING( GDM_LEAVE_LOBBY_RET_NEW_PARTY, 31 ), + ASSERT_ENUM_STRING( GDM_MIGRATING, 32 ), + ASSERT_ENUM_STRING( GDM_OPPONENT_LEFT, 33 ), + ASSERT_ENUM_STRING( GDM_NO_MATCHES_FOUND, 34 ), + ASSERT_ENUM_STRING( GDM_INVALID_INVITE, 35 ), + ASSERT_ENUM_STRING( GDM_KICKED, 36 ), + ASSERT_ENUM_STRING( GDM_BANNED, 37 ), + ASSERT_ENUM_STRING( GDM_SAVING, 38 ), + ASSERT_ENUM_STRING( GDM_OVERWRITE_SAVE, 39 ), + ASSERT_ENUM_STRING( GDM_LOAD_REQUEST, 40 ), + ASSERT_ENUM_STRING( GDM_AUTOSAVE_DISABLED_STORAGE_REMOVED, 41 ), + ASSERT_ENUM_STRING( GDM_STORAGE_INVALID, 42 ), + ASSERT_ENUM_STRING( GDM_STORAGE_REMOVED, 43 ), + ASSERT_ENUM_STRING( GDM_CONNECTING, 44 ), + ASSERT_ENUM_STRING( GDM_REFRESHING, 45 ), + ASSERT_ENUM_STRING( GDM_DELETE_SAVE, 46 ), + ASSERT_ENUM_STRING( GDM_DELETING, 47 ), + ASSERT_ENUM_STRING( GDM_BINDING_ALREDY_SET, 48 ), + ASSERT_ENUM_STRING( GDM_CANNOT_BIND, 49 ), + ASSERT_ENUM_STRING( GDM_OVERLAY_DISABLED, 50 ), + ASSERT_ENUM_STRING( GDM_DIRECT_MAP_CHANGE, 51 ), + ASSERT_ENUM_STRING( GDM_DELETE_AUTOSAVE, 52 ), + ASSERT_ENUM_STRING( GDM_QUICK_SAVE, 53 ), + ASSERT_ENUM_STRING( GDM_MULTI_RETRY, 54 ), + ASSERT_ENUM_STRING( GDM_MULTI_SELF_DESTRUCT, 55 ), + ASSERT_ENUM_STRING( GDM_MULTI_VDM_QUIT, 56 ), + ASSERT_ENUM_STRING( GDM_MULTI_COOP_QUIT, 57 ), + ASSERT_ENUM_STRING( GDM_LOADING_PROFILE, 58 ), + ASSERT_ENUM_STRING( GDM_STORAGE_REQUIRED, 59 ), + ASSERT_ENUM_STRING( GDM_INSUFFICENT_STORAGE_SPACE, 60 ), + ASSERT_ENUM_STRING( GDM_PARTNER_LEFT, 61 ), + ASSERT_ENUM_STRING( GDM_RESTORE_CORRUPT_SAVEGAME, 62 ), + ASSERT_ENUM_STRING( GDM_UNRECOVERABLE_SAVEGAME, 63 ), + ASSERT_ENUM_STRING( GDM_PROFILE_SAVE_ERROR, 64 ), + ASSERT_ENUM_STRING( GDM_LOBBY_FULL, 65 ), + ASSERT_ENUM_STRING( GDM_QUIT_GAME, 66 ), + ASSERT_ENUM_STRING( GDM_CONNECTION_PROBLEMS, 67 ), + ASSERT_ENUM_STRING( GDM_VOICE_RESTRICTED, 68 ), + ASSERT_ENUM_STRING( GDM_LOAD_DAMAGED_FILE, 69 ), + ASSERT_ENUM_STRING( GDM_MUST_SIGNIN, 70 ), + ASSERT_ENUM_STRING( GDM_CONNECTION_LOST_NO_LEADERBOARD, 71 ), + ASSERT_ENUM_STRING( GDM_SP_SIGNIN_CHANGE_POST, 72 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_WAITING, 73 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_RELAUNCHING, 74 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_FAILED_CONNECTION, 75 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_FAILED_CONNECTION_STATS, 76 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_FAILED_DISBANDED, 77 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_FAILED_DISBANDED_STATS, 78 ), + ASSERT_ENUM_STRING( GDM_MIGRATING_FAILED_PARTNER_LEFT, 79 ), + ASSERT_ENUM_STRING( GDM_HOST_RETURNED_TO_LOBBY, 80 ), + ASSERT_ENUM_STRING( GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED, 81 ), + ASSERT_ENUM_STRING( GDM_FAILED_JOIN_LOCAL_SESSION, 82 ), + ASSERT_ENUM_STRING( GDM_DELETE_CORRUPT_SAVEGAME, 83 ), + ASSERT_ENUM_STRING( GDM_LEAVE_INCOMPLETE_INSTANCE, 84 ), + ASSERT_ENUM_STRING( GDM_UNBIND_CONFIRM, 85 ), + ASSERT_ENUM_STRING( GDM_BINDINGS_RESTORE, 86 ), + ASSERT_ENUM_STRING( GDM_NEW_HOST, 87 ), + ASSERT_ENUM_STRING( GDM_CONFIRM_VIDEO_CHANGES, 88 ), + ASSERT_ENUM_STRING( GDM_UNABLE_TO_USE_SELECTED_STORAGE_DEVICE, 89 ), + ASSERT_ENUM_STRING( GDM_ERROR_LOADING_SAVEGAME, 90 ), + ASSERT_ENUM_STRING( GDM_ERROR_SAVING_SAVEGAME, 91 ), + ASSERT_ENUM_STRING( GDM_DISCARD_CHANGES, 92 ), + ASSERT_ENUM_STRING( GDM_LEAVE_LOBBY, 93 ), + ASSERT_ENUM_STRING( GDM_LEAVE_LOBBY_AND_TEAM, 94 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_0, 95 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_1, 96 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_2, 97 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_3, 98 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_4, 99 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_5, 100 ), + ASSERT_ENUM_STRING( GDM_CONTROLLER_DISCONNECTED_6, 101 ), + ASSERT_ENUM_STRING( GDM_DLC_ERROR_REMOVED, 102 ), + ASSERT_ENUM_STRING( GDM_DLC_ERROR_CORRUPT, 103 ), + ASSERT_ENUM_STRING( GDM_DLC_ERROR_MISSING, 104 ), + ASSERT_ENUM_STRING( GDM_DLC_ERROR_MISSING_GENERIC, 105 ), + ASSERT_ENUM_STRING( GDM_DISC_SWAP, 106 ), + ASSERT_ENUM_STRING( GDM_NEEDS_INSTALL, 107 ), + ASSERT_ENUM_STRING( GDM_NO_SAVEGAMES_AVAILABLE, 108 ), + ASSERT_ENUM_STRING( GDM_ERROR_JOIN_TWO_PROFILES_ONE_BOX, 109 ), + ASSERT_ENUM_STRING( GDM_WARNING_PLAYING_COOP_SOLO, 110 ), + ASSERT_ENUM_STRING( GDM_MULTI_COOP_QUIT_LOSE_LEADERBOARDS, 111 ), + ASSERT_ENUM_STRING( GDM_CORRUPT_CONTINUE, 112 ), + ASSERT_ENUM_STRING( GDM_MULTI_VDM_QUIT_LOSE_LEADERBOARDS, 113 ), + ASSERT_ENUM_STRING( GDM_WARNING_PLAYING_VDM_SOLO, 114 ), + ASSERT_ENUM_STRING( GDM_NO_GUEST_SUPPORT, 115 ), + ASSERT_ENUM_STRING( GDM_DISC_SWAP_CONFIRMATION, 116 ), + ASSERT_ENUM_STRING( GDM_ERROR_LOADING_PROFILE, 117 ), + ASSERT_ENUM_STRING( GDM_CANNOT_INVITE_LOBBY_FULL, 118 ), + ASSERT_ENUM_STRING( GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, 119 ), + ASSERT_ENUM_STRING( GDM_DISCONNECTED, 120 ), + ASSERT_ENUM_STRING( GDM_INCOMPATIBLE_NEWER_SAVE, 121 ), + ASSERT_ENUM_STRING( GDM_ACHIEVEMENTS_DISABLED_DUE_TO_CHEATING, 122 ), + ASSERT_ENUM_STRING( GDM_INCOMPATIBLE_POINTER_SIZE, 123 ), + ASSERT_ENUM_STRING( GDM_TEXTUREDETAIL_RESTARTREQUIRED, 124 ), + ASSERT_ENUM_STRING( GDM_TEXTUREDETAIL_INSUFFICIENT_CPU, 125 ), + ASSERT_ENUM_STRING( GDM_CHECKPOINT_SAVE, 126 ), + ASSERT_ENUM_STRING( GDM_CALCULATING_BENCHMARK, 127 ), + ASSERT_ENUM_STRING( GDM_DISPLAY_BENCHMARK, 128 ), + ASSERT_ENUM_STRING( GDM_DISPLAY_CHANGE_FAILED, 129 ), + ASSERT_ENUM_STRING( GDM_GPU_TRANSCODE_FAILED, 130 ), + ASSERT_ENUM_STRING( GDM_OUT_OF_MEMORY, 131 ), + ASSERT_ENUM_STRING( GDM_CORRUPT_PROFILE, 132 ), + ASSERT_ENUM_STRING( GDM_PROFILE_TOO_OUT_OF_DATE_DEVELOPMENT_ONLY, 133 ), + ASSERT_ENUM_STRING( GDM_SP_LOAD_SAVE, 134 ), + ASSERT_ENUM_STRING( GDM_INSTALLING_TROPHIES, 135 ), + ASSERT_ENUM_STRING( GDM_XBOX_DEPLOYMENT_TYPE_FAIL, 136 ), + ASSERT_ENUM_STRING( GDM_SAVEGAME_WRONG_LANGUAGE, 137 ), + ASSERT_ENUM_STRING( GDM_GAME_RESTART_REQUIRED, 138 ), + ASSERT_ENUM_STRING( GDM_MAX, 139 ) +}; + +idCVar dialog_saveClearLevel( "dialog_saveClearLevel", "1000", CVAR_INTEGER, "Time required to show long message" ); + +/* +======================== +bool DialogMsgShouldWait + +There are a few dialog types that should pause so the user has the ability to read what's going on +======================== +*/ +bool DialogMsgShouldWait( gameDialogMessages_t msg ) { + switch ( msg ) { + case GDM_SAVING: + case GDM_QUICK_SAVE: + case GDM_LOADING_PROFILE: + case GDM_INSTALLING_TROPHIES: + case GDM_REFRESHING: + return true; + default: + return false; + } +} + +/* +================================================ +idCommonDialog::ClearDialogs +================================================ +*/ +void idCommonDialog::ClearDialogs( bool forceClear ) { + bool topMessageCleared = false; + for ( int index = 0; index < messageList.Num(); ++index ) { + if ( !messageList[index].leaveOnClear || forceClear ) { + ReleaseCallBacks( index ); + messageList.RemoveIndex( index ); + + if ( index == 0 ) { + topMessageCleared = true; + } + index--; + } + } + + if ( topMessageCleared ) { + ActivateDialog( false ); + } +} + +/* +================================================ +idCommonDialog::AddDialogIntVal +================================================ +*/ +void idCommonDialog::AddDialogIntVal( const char * name, int val ) { + if ( dialog != NULL ) { + dialog->SetGlobal( name, val ); + } +} + +/* +================================================ +idCommonDialog::AddDialog +================================================ +*/ +void idCommonDialog::AddDialog( gameDialogMessages_t msg, dialogType_t type, idSWFScriptFunction * acceptCallback, + idSWFScriptFunction * cancelCallback, bool pause, const char * location, int lineNumber, + bool leaveOnMapHeapReset, bool waitOnAtlas, bool renderDuringLoad ) { + + idKeyInput::ClearStates(); + + // TODO_D3_PORT: + //sys->ClearEvents(); + + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s] msg: %s, pause: %d from: %s:%d\n", __FUNCTION__, dialogStateToString[msg], pause, location == NULL ? "NULL" : location, lineNumber ); + + if ( dialog == NULL ) { + return; + } + + idDialogInfo info; + info.msg = msg; + info.type = type; + info.acceptCB = acceptCallback; + info.cancelCB = cancelCallback; + info.clear = false; + info.pause = pause; + info.startTime = Sys_Milliseconds(); + info.killTime = 0; + info.leaveOnClear = leaveOnMapHeapReset; + info.renderDuringLoad = renderDuringLoad; + + AddDialogInternal( info ); +} + +/* +======================== +idCommonDialog::AddDynamicDialog +======================== +*/ +void idCommonDialog::AddDynamicDialog( gameDialogMessages_t msg, const idStaticList< idSWFScriptFunction *, 4 > & callbacks, + const idStaticList< idStrId, 4 > & optionText, bool pause, idStrStatic< 256 > overrideMsg, + bool leaveOnMapHeapReset, bool waitOnAtlas, bool renderDuringLoad ) { + + if ( dialog == NULL ) { + return; + } + + idDialogInfo info; + info.msg = msg; + info.overrideMsg = overrideMsg; + info.type = DIALOG_DYNAMIC; + info.pause = pause; + info.leaveOnClear = leaveOnMapHeapReset; + info.acceptCB = 0 < callbacks.Num() ? callbacks[0] : NULL; + info.cancelCB = 1 < callbacks.Num() ? callbacks[1] : NULL; + info.altCBOne = 2 < callbacks.Num() ? callbacks[2] : NULL; + info.altCBTwo = 3 < callbacks.Num() ? callbacks[3] : NULL; + info.txt1 = 0 < optionText.Num() ? optionText[0] : idStrId(); + info.txt2 = 1 < optionText.Num() ? optionText[1] : idStrId(); + info.txt3 = 2 < optionText.Num() ? optionText[2] : idStrId(); + info.txt4 = 3 < optionText.Num() ? optionText[3] : idStrId(); + info.renderDuringLoad = renderDuringLoad; + + info.clear = false; + info.startTime = Sys_Milliseconds(); + info.killTime = 0; + + AddDialogInternal( info ); +} + +/* +======================== +idCommonDialog::AddDialogInternal +======================== +*/ +void idCommonDialog::AddDialogInternal( idDialogInfo & info ) { + + // don't add the dialog if it's already in the list, we never want to show a duplicate dialog + if ( HasDialogMsg( info.msg, NULL ) ) { + return; + } + + // Remove the delete confirmation if we remove the device and ask for a storage confirmation + if ( info.msg == GDM_STORAGE_REQUIRED ) { + if ( HasDialogMsg( GDM_DELETE_SAVE, NULL ) ) { + ClearDialog( GDM_DELETE_SAVE, NULL, 0 ); + } + if ( HasDialogMsg( GDM_DELETE_AUTOSAVE, NULL ) ) { + ClearDialog( GDM_DELETE_AUTOSAVE, NULL, 0 ); + } + if ( HasDialogMsg( GDM_LOAD_DAMAGED_FILE, NULL ) ) { + ClearDialog( GDM_LOAD_DAMAGED_FILE, NULL, 0 ); + } + } + + if ( info.acceptCB != NULL ) { + info.acceptCB->AddRef(); + } + + if ( info.cancelCB != NULL ) { + info.cancelCB->AddRef(); + } + + if ( info.altCBOne != NULL ) { + info.altCBOne->AddRef(); + } + + if ( info.altCBTwo != NULL ) { + info.altCBTwo->AddRef(); + } + + if ( messageList.Num() == 0 ) { + messageList.Append( info ); + } else { + + // attempting to add another one beyond our set max. take off the oldest + // one from the list in order to not crash, but this really isn't a good + // thing to be happening... + if ( !verify( messageList.Num() < MAX_DIALOGS ) ) { + messageList.RemoveIndex( MAX_DIALOGS - 1 ); + } + + if ( messageList.Num() > 0 ) { + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s] msg: %s new dialog added over old\n", __FUNCTION__, dialogStateToString[info.msg] ); + + dialog->Activate( false ); + messageList.Insert( info, 0 ); + } + } + + if ( info.type == DIALOG_QUICK_SAVE || info.type == DIALOG_CRAWL_SAVE || messageList[0].msg == GDM_CALCULATING_BENCHMARK ) { + ShowNextDialog(); + } +} + +/* +======================== +idCommonDialog::ActivateDialog +======================== +*/ +void idCommonDialog::ActivateDialog( bool activate ) { + dialogInUse = activate; + if ( dialog != NULL ) { + dialog->Activate( activate ); + } +} + +/* +================================================ +idCommonDialog::ShowDialog +================================================ +*/ +void idCommonDialog::ShowDialog( const idDialogInfo & info ) { + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s] msg: %s, m.clear = %d, m.waitClear = %d, m.killTime = %d\n", + __FUNCTION__, dialogStateToString[info.msg], info.clear, info.waitClear, info.killTime ); + + // here instead of add dialog to make sure we meet the TCR, otherwise it has a chance to be visible for less than 1 second + if ( DialogMsgShouldWait( info.msg ) && !dialogInUse ) { + startSaveTime = Sys_Milliseconds(); + stopSaveTime = 0; + } + + if ( IsDialogActive() ) { + dialog->Activate( false ); + } + + idStr message, title; + GetDialogMsg( info.msg, message, title ); + + dialog->SetGlobal( "titleVal", title ); + if ( info.overrideMsg.IsEmpty() ) { + dialog->SetGlobal( "messageInfo", message ); + } else { + dialog->SetGlobal( "messageInfo", info.overrideMsg ); + } + dialog->SetGlobal( "Infotype", info.type ); + + if ( info.acceptCB == NULL && ( info.type != DIALOG_WAIT && info.type != DIALOG_WAIT_BLACKOUT ) ) { + class idSWFScriptFunction_Accept : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_Accept( gameDialogMessages_t _msg ) { + msg = _msg; + } + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( msg ); + return idSWFScriptVar(); + } + private: + gameDialogMessages_t msg; + }; + + dialog->SetGlobal( "acceptCallBack", new (TAG_SWF) idSWFScriptFunction_Accept( info.msg ) ); + + } else { + dialog->SetGlobal( "acceptCallBack", info.acceptCB ); + } + + dialog->SetGlobal( "cancelCallBack", info.cancelCB ); + dialog->SetGlobal( "altCBOne", info.altCBOne ); + dialog->SetGlobal( "altCBTwo", info.altCBTwo ); + dialog->SetGlobal( "opt1Txt", info.txt1.GetLocalizedString() ); + dialog->SetGlobal( "opt2Txt", info.txt2.GetLocalizedString() ); + dialog->SetGlobal( "opt3Txt", info.txt3.GetLocalizedString() ); + dialog->SetGlobal( "opt4Txt", info.txt4.GetLocalizedString() ); + + ActivateDialog( true ); +} + +/* +================================================ +idCommonDialog::ShowNextDialog +================================================ +*/ +void idCommonDialog::ShowNextDialog() { + for ( int index = 0; index < messageList.Num(); ++index ) { + if ( !messageList[index].clear ) { + idDialogInfo info = messageList[index]; + ShowDialog( info ); + break; + } + } +} + +/* +================================================ +idCommonDialog::ShowSaveIndicator +================================================ +*/ +void idCommonDialog::ShowSaveIndicator( bool show ) { + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s]\n", __FUNCTION__ ); + + if ( show ) { + idStr msg = idStrId( "#str_dlg_pc_saving" ).GetLocalizedString(); + + common->Dialog().AddDialog( GDM_SAVING, DIALOG_WAIT, NULL, NULL, true, "", 0, false, true, true ); + } else { + common->Dialog().ClearDialog( GDM_SAVING ); + } +} + +/* +======================== +idCommonDialog::RemoveSaveDialog + +From TCR# 047 +Games must display a message during storage writes for the following conditions and the respective amount of time: +- Writes longer than one second require the standard message be displayed for three seconds. +- Writes longer than three seconds require the standard message be displayed for the length of the write. +- Writes that last one second or less require the shorter message be displayed for one second or the standard message for three seconds. + +======================== +*/ +void idCommonDialog::RemoveWaitDialogs() { + bool topMessageCleared = false; + for ( int index = 0; index < messageList.Num(); ++index ) { + if ( DialogMsgShouldWait( messageList[index].msg ) ) { + if ( Sys_Milliseconds() >= messageList[index].killTime && messageList[index].waitClear ) { + messageList[index].clear = true; + messageList[index].waitClear = false; + if ( index == 0 ) { + topMessageCleared = true; + } + } + } + } + + if ( topMessageCleared && messageList.Num() > 0 ) { + ActivateDialog( false ); + } +} + +/* +================================================ +idCommonDialog::ClearAllDialogHack +================================================ +*/ +void idCommonDialog::ClearAllDialogHack() { + for ( int index = 0; index < messageList.Num(); ++index ) { + messageList[index].clear = true; + messageList[index].waitClear = false; + } +} + +/* +================================================ +idCommonDialog::HasDialogMsg +================================================ +*/ +bool idCommonDialog::HasDialogMsg( gameDialogMessages_t msg, bool * isNowActive ) { + for ( int index = 0; index < messageList.Num(); ++index ) { + idDialogInfo & info = messageList[index]; + + if ( info.msg == msg && !info.clear ) { + if ( isNowActive != NULL ) { + *isNowActive = ( index == 0 ); + } + return true; + } + } + + if ( isNowActive != NULL ) { + *isNowActive = false; + } + + return false; +} + +/* +================================================ +idCommonDialog::ClearDialog +================================================ +*/ +void idCommonDialog::ClearDialog( gameDialogMessages_t msg, const char * location, int lineNumber ) { + bool topMessageCleared = false; + + for ( int index = 0; index < messageList.Num(); ++index ) { + idDialogInfo & info = messageList[index]; + + if ( info.msg == msg && !info.clear ) { + if ( DialogMsgShouldWait( info.msg ) ) { + + // you can have 2 saving dialogs simultaneously, if you clear back-to-back, we need to let the 2nd dialog + // get the clear message + if ( messageList[index].waitClear ) { + continue; + } + + int timeShown = Sys_Milliseconds() - messageList[index].startTime; + + // for the time being always use the long saves + if ( timeShown < dialog_saveClearLevel.GetInteger() ) { + messageList[index].killTime = Sys_Milliseconds() + ( dialog_saveClearLevel.GetInteger() - timeShown ); + messageList[index].waitClear = true; + } else { + messageList[index].clear = true; + if ( index == 0 ) { + topMessageCleared = true; + } + } + } else { + messageList[index].clear = true; + if ( index == 0 ) { + topMessageCleared = true; + } + } + assert( info.msg >= GDM_INVALID && info.msg < GDM_MAX ); // not sure why /analyze complains about this + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s] msg: %s, from: %s:%d, topMessageCleared = %d, m.clear = %d, m.waitClear = %d, m.killTime = %d\n", + __FUNCTION__, dialogStateToString[info.msg], location == NULL ? "NULL" : location, lineNumber, + topMessageCleared, messageList[index].clear, + messageList[index].waitClear, messageList[index].killTime ); + break; + } + } + + if ( topMessageCleared && messageList.Num() > 0 ) { + ActivateDialog( false ); + } +} + +/* +================================================ +idCommonDialog::ReleaseCallBacks +================================================ +*/ +void idCommonDialog::ReleaseCallBacks( int index ) { + + if ( index < messageList.Num() ) { + if ( messageList[index].acceptCB != NULL ) { + messageList[index].acceptCB->Release(); + messageList[index].acceptCB = NULL; + } + + if ( messageList[index].cancelCB != NULL ) { + messageList[index].cancelCB->Release(); + messageList[index].cancelCB = NULL; + } + + if ( messageList[index].altCBOne != NULL ) { + messageList[index].altCBOne->Release(); + messageList[index].altCBOne = NULL; + } + + if ( messageList[index].altCBTwo != NULL ) { + messageList[index].altCBTwo->Release(); + messageList[index].altCBTwo = NULL; + } + } +} + +/* +================================================ +idCommonDialog::Render +================================================ +*/ +void idCommonDialog::Render( bool loading ) { + + dialogPause = false; + + if ( dialog == NULL ) { + return; + } + + RemoveWaitDialogs(); + + bool pauseCheck = false; + for ( int index = 0; index < messageList.Num(); ++index ) { + if ( messageList[index].clear ) { + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s] removing %s\n", __FUNCTION__, dialogStateToString[messageList[index].msg] ); + ReleaseCallBacks( index ); + messageList.RemoveIndex( index ); + index--; + } else { + if ( messageList[index].pause && !pauseCheck ) { + pauseCheck = true; + } + } + } + + dialogPause = pauseCheck; + + if ( messageList.Num() > 0 && !dialog->IsActive() ) { + ShowNextDialog(); + } + + if ( messageList.Num() == 0 && dialog->IsActive() ) { + dialog->Activate( false ); + } + + // Decrement the time remaining on the save indicator or turn it off + if ( !dialogShowingSaveIndicatorRequested && saveIndicator->IsActive() ) { + ShowSaveIndicator( false ); + } + + if ( messageList.Num() > 0 && messageList[0].type == DIALOG_TIMER_ACCEPT_REVERT ) { + int startTime = messageList[0].startTime; + int endTime = startTime + PC_KEYBOARD_WAIT; + int timeRemaining = ( endTime - Sys_Milliseconds() ) / 1000; + + if ( timeRemaining <= 0 ) { + if ( messageList[0].cancelCB != NULL ) { + idSWFParmList parms; + messageList[0].cancelCB->Call( NULL, parms ); + } + messageList[0].clear = true; + } else { + idStrId txtTime = idStrId( "#str_time_remaining" ); + dialog->SetGlobal( "countdownInfo", va( txtTime.GetLocalizedString(), timeRemaining ) ); + } + } + + if ( messageList.Num() > 0 && loading && ( messageList[0].renderDuringLoad == false ) ) { + return; + } + + if ( dialog->IsActive() ) { + dialog->Render( renderSystem, Sys_Microseconds() ); + } + + if ( saveIndicator != NULL && saveIndicator->IsActive() ) { + saveIndicator->Render( renderSystem, Sys_Microseconds() ); + } +} + +/* +================================================ +idCommonDialog::Init +================================================ +*/ +void idCommonDialog::Init() { + + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s]\n", __FUNCTION__ ); + + Shutdown(); + + dialog = new (TAG_SWF) idSWF( "dialog" ); + saveIndicator = new (TAG_SWF) idSWF( "save_indicator" ); + +#define BIND_DIALOG_CONSTANT( x ) dialog->SetGlobal( #x, x ) + if ( dialog != NULL ) { + BIND_DIALOG_CONSTANT( DIALOG_ACCEPT ); + BIND_DIALOG_CONSTANT( DIALOG_CONTINUE ); + BIND_DIALOG_CONSTANT( DIALOG_ACCEPT_CANCEL ); + BIND_DIALOG_CONSTANT( DIALOG_YES_NO ); + BIND_DIALOG_CONSTANT( DIALOG_CANCEL ); + BIND_DIALOG_CONSTANT( DIALOG_WAIT ); + BIND_DIALOG_CONSTANT( DIALOG_WAIT_BLACKOUT ); + BIND_DIALOG_CONSTANT( DIALOG_WAIT_CANCEL ); + BIND_DIALOG_CONSTANT( DIALOG_DYNAMIC ); + BIND_DIALOG_CONSTANT( DIALOG_QUICK_SAVE ); + BIND_DIALOG_CONSTANT( DIALOG_TIMER_ACCEPT_REVERT ); + BIND_DIALOG_CONSTANT( DIALOG_CRAWL_SAVE ); + BIND_DIALOG_CONSTANT( DIALOG_CONTINUE_LARGE ); + BIND_DIALOG_CONSTANT( DIALOG_BENCHMARK ); + } +} + +/* +================================================ +idCommonDialog::Shutdown +================================================ +*/ +void idCommonDialog::Shutdown() { + idLib::PrintfIf( popupDialog_debug.GetBool(), "[%s]\n", __FUNCTION__ ); + + ClearDialogs(); + + delete dialog; + dialog = NULL; + + delete saveIndicator; + saveIndicator = NULL; +} + +/* +======================== +idCommonDialog::Restart +======================== +*/ +void idCommonDialog::Restart() { + Shutdown(); + Init(); +} + +/* +================================================ +idCommonDialog::GetDialogMsg +================================================ +*/ +idStr idCommonDialog::GetDialogMsg( gameDialogMessages_t msg, idStr & message, idStr & title ) { + + message = "#str_dlg_pc_"; + + switch ( msg ) { + case GDM_SWAP_DISKS_TO1: { + message.Append( "switch_disc_to_1" ); + break; + } + case GDM_SWAP_DISKS_TO2: { + message.Append( "switch_disc_to_2" ); + break; + } + case GDM_SWAP_DISKS_TO3: { + message.Append( "switch_disc_to_3" ); + break; + } + case GDM_NO_GAMER_PROFILE: { + message.Append( "signin_request" ); + break; + } + case GDM_PLAY_ONLINE_NO_PROFILE: { + message.Append( "online_signin_request" ); + break; + } + case GDM_LEADERBOARD_ONLINE_NO_PROFILE: { + message.Append( "online_signing_request_leaderboards" ); + break; + } + case GDM_NO_STORAGE_SELECTED: { + message.Append( "storage_device_selection_request" ); + break; + } + case GDM_ONLINE_INCORRECT_PERMISSIONS: { + message.Append( "incorrect_online_permissions" ); + break; + } + case GDM_SP_QUIT_SAVE: { + title = idLocalization::GetString( "#str_04215" ); + title.ToUpper(); + if ( g_demoMode.GetBool() ) { + message = "#str_04145"; + } else { + message = "#str_dlg_quit_progress_lost"; + } + break; + } + case GDM_SP_LOAD_SAVE: { + title = idLocalization::GetString( "#str_02187" ); + title.ToUpper(); + message = "#str_dlg_360_load_request"; + break; + } + case GDM_SP_RESTART_SAVE: { + title = idLocalization::GetString( "#str_04271" ); + title.ToUpper(); + message = "#str_dlg_restart_progress_lost"; + break; + } + case GDM_SP_SIGNIN_CHANGE: { + message = "#str_dlg_signin_changed"; + break; + } + case GDM_SERVER_NOT_AVAILABLE: { + message.Append( "game_server_unavailable" ); + break; + } + case GDM_CONNECTION_LOST_HOST: { + message = "#str_dlg_opponent_connection_lost_ranking_not_counted"; + break; + } + case GDM_CONNECTION_LOST: { + message.Append( "online_connection_lost_main_menu_return" ); + break; + } + case GDM_OPPONENT_CONNECTION_LOST: { + message = "#str_dlg_opponent_connection_lost"; + break; + } + case GDM_HOST_CONNECTION_LOST: { + message = "#str_dlg_host_connection_lost"; + break; + } + case GDM_HOST_CONNECTION_LOST_STATS: { + message = "#str_dlg_host_connection_lost_ranking_not_counted"; + break; + } + case GDM_FAILED_TO_LOAD_RANKINGS: { + message = "#str_dlg_ranking_load_failed"; + break; + } + case GDM_HOST_QUIT: { + message = "#str_dlg_host_quit"; + break; + } + case GDM_OPPONENT_LEFT: { + message = "#str_dlg_opponent_left"; + break; + } + case GDM_PARTNER_LEFT: { + message = "#str_dlg_partner_left"; + break; + } + case GDM_NO_MATCHES_FOUND: { + message = "#str_dlg_matches_not_found"; + break; + } + case GDM_INVALID_INVITE: { + message = "#str_dlg_invalid_game"; + break; + } + case GDM_KICKED: { + message = "#str_dlg_kicked"; + break; + } + case GDM_BANNED: { + message = "#str_dlg_banned"; + break; + } + case GDM_SAVING: { + title = "#str_save_dialog_heading"; + message.Append( "saving" ); + break; + } + case GDM_QUICK_SAVE: { + title = "#str_save_dialog_heading"; + message.Append( "saving" ); + //message = "#STR_SWF_SAVING"; + break; + } + case GDM_OVERWRITE_SAVE: { + title = "#str_02306"; + message = "#str_dlg_overwrite_save"; + break; + } + case GDM_LOAD_REQUEST: { + message.Append( "load_request" ); + break; + } + case GDM_AUTOSAVE_DISABLED_STORAGE_REMOVED: { + message.Append( "storage_removed_autosave_disabled" ); + break; + } + case GDM_STORAGE_INVALID: { + message.Append( "storage_not_available" ); + break; + } + case GDM_CONNECTING: { + message = "#str_dlg_connecting"; + break; + } + case GDM_REFRESHING: { + title = "#str_01694"; + message = "#str_dlg_refreshing"; + break; + } + case GDM_DELETE_SAVE: { + title = "#str_02313"; + message.Append( "delete_save" ); + break; + } + case GDM_DELETING: { + message.Append( "deleting" ); + break; + } + case GDM_BINDING_ALREDY_SET: { + message.Append( "bind_exists" ); + break; + } + case GDM_CANNOT_BIND: { + message.Append( "cannont_bind" ); + break; + } + case GDM_OVERLAY_DISABLED: { + message.Append( "overlay_disabled" ); + break; + } + case GDM_BECAME_HOST_PARTY: { + message = "#str_dlg_became_host_party"; + break; + } + case GDM_NEW_HOST_PARTY: { + message = "#str_dlg_new_host_party"; + break; + } + case GDM_LOBBY_BECAME_HOST_GAME: { + message = "#str_dlg_lobby_became_host_game"; + break; + } + case GDM_LOBBY_NEW_HOST_GAME: { + message.Append( "lobby_new_host_game" ); + break; + } + case GDM_NEW_HOST_GAME: { + message = "#str_dlg_new_host_game"; + break; + } + case GDM_NEW_HOST_GAME_STATS_DROPPED: { + message = "#str_dlg_new_host_game_stats_dropped"; + break; + } + case GDM_BECAME_HOST_GAME: { + message.Append( "became_host_game" ); + break; + } + case GDM_BECAME_HOST_GAME_STATS_DROPPED: { + message = "#str_dlg_became_host_game_stats_dropped"; + break; + } + case GDM_LOBBY_DISBANDED: { + message.Append( "lobby_disbanded" ); + break; + } + case GDM_LEAVE_WITH_PARTY: { + message = "#str_dlg_leave_with_party"; + break; + } + case GDM_LEAVE_LOBBY_RET_MAIN: { + message.Append( "leave_lobby_ret_main" ); + break; + } + case GDM_LEAVE_LOBBY_RET_NEW_PARTY: { + message.Append( "leave_lobby_ret_new_party" ); + break; + } + case GDM_MIGRATING: { + message = "#str_online_host_migration"; + break; + } + case GDM_MIGRATING_WAITING: { + message = "#str_online_host_migration_waiting"; + break; + } + case GDM_MIGRATING_RELAUNCHING: { + message = "#str_online_host_migration_relaunching"; + break; + } + case GDM_DIRECT_MAP_CHANGE: { + message = "#str_dlg_direct_map_change"; + break; + } + case GDM_DELETE_AUTOSAVE: { + message.Append( "delete_autosave" ); + break; + } + case GDM_MULTI_RETRY: { + message = "#str_online_confirm_retry"; + break; + } + case GDM_MULTI_SELF_DESTRUCT: { + message = "#str_online_confirm_suicide"; + break; + } + case GDM_MULTI_VDM_QUIT: { + message = "#str_online_confirm_quit_generic"; + break; + } + case GDM_MULTI_COOP_QUIT: { + message = "#str_online_confirm_coop_quit_game_generic"; + break; + } + case GDM_LOADING_PROFILE: { + message = "#str_dlg_loading_profile"; + title = "#str_dlg_updating_profile"; + break; + } + case GDM_STORAGE_REQUIRED: { + message.Append( "storage_required" ); + break; + } + case GDM_INSUFFICENT_STORAGE_SPACE: { + message = "#str_dlg_insufficient_space"; + break; + } + case GDM_RESTORE_CORRUPT_SAVEGAME: { + message = "#str_dlg_restore_corrupt_savegame"; + break; + } + case GDM_UNRECOVERABLE_SAVEGAME: { + message = "#str_dlg_unrecoverable_savegame"; + break; + } + case GDM_PROFILE_SAVE_ERROR: { + message.Append( "profile_save_error" ); + break; + } + case GDM_LOBBY_FULL: { + message.Append( "lobby_full" ); + break; + } + case GDM_QUIT_GAME: { + title = "#str_01975"; // EXIT GAME + message = "#str_dlg_confirm_quit"; + break; + } + case GDM_CONNECTION_PROBLEMS: { + message = "#str_online_connection_problems"; + break; + } + case GDM_VOICE_RESTRICTED: { + message.Append( "voice_restricted" ); + break; + } + case GDM_MUST_SIGNIN: { + message.Append( "must_signin" ); + break; + } + case GDM_LOAD_DAMAGED_FILE: { + message = "#str_dlg_corrupt_save_file"; + break; + } + case GDM_DLC_ERROR_REMOVED: { + message.Append( "dlc_error_content_removed" ); + break; + } + case GDM_DLC_ERROR_CORRUPT: { + message.Append( "dlc_error_content_corrupt" ); + break; + } + case GDM_DLC_ERROR_MISSING: { + message.Append( "dlc_error_content_missing" ); + break; + } + case GDM_DLC_ERROR_MISSING_GENERIC: { + message.Append( "dlc_error_content_missing_generic" ); + break; + } + case GDM_CONNECTION_LOST_NO_LEADERBOARD: { + message.Append( "online_connection_lost_no_leaderboard" ); + break; + } + case GDM_SP_SIGNIN_CHANGE_POST: { + message.Append( "signin_changed_post" ); + break; + } + case GDM_MIGRATING_FAILED_CONNECTION: { + message = "#str_online_host_migration_failed"; + break; + } + case GDM_MIGRATING_FAILED_CONNECTION_STATS: { + message = "#str_online_host_migration_failed_stats"; + break; + } + case GDM_MIGRATING_FAILED_DISBANDED: { + message = "#str_online_host_migration_failed_disbanded"; + break; + } + case GDM_MIGRATING_FAILED_DISBANDED_STATS: { + message = "#str_online_host_migration_failed_disbanded_stats"; + break; + } + case GDM_MIGRATING_FAILED_PARTNER_LEFT: { + message = "#str_online_host_migration_failed_partner_left"; + break; + } + case GDM_FAILED_JOIN_LOCAL_SESSION: { + message = "#str_dlg_failed_join_local_session"; + break; + } + case GDM_DELETE_CORRUPT_SAVEGAME: + message = "#str_dlg_delete_corrupt_savegame"; + break; + case GDM_LEAVE_INCOMPLETE_INSTANCE: + message = "#str_dlg_leave_incomplete_instance"; + break; + case GDM_UNBIND_CONFIRM: + message = "#str_dlg_bind_unbind"; + break; + case GDM_BINDINGS_RESTORE: + message = "#str_dlg_bind_restore"; + break; + case GDM_HOST_RETURNED_TO_LOBBY: { + message.Append( "host_quit_to_lobby" ); + break; + } + case GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED: { + message.Append( "host_quit_to_lobby_stats_dropped" ); + break; + } + case GDM_NEW_HOST: { + message.Append( "new_host" ); + break; + } + case GDM_DISC_SWAP: { + message = "#str_dlg_disc_swap"; + break; + } + case GDM_NO_SAVEGAMES_AVAILABLE: { + message = "#str_dlg_no_savegames_available"; + break; + } + case GDM_CONFIRM_VIDEO_CHANGES: { + message = "#str_dlg_confirm_display_changes"; + break; + } + case GDM_UNABLE_TO_USE_SELECTED_STORAGE_DEVICE: { + message.Append( "unable_to_use_selected_storage_device" ); + break; + } + case GDM_ERROR_LOADING_SAVEGAME: { + message = "#str_dlg_error_loading_savegame"; + break; + } + case GDM_ERROR_SAVING_SAVEGAME: { + message = "#str_dlg_error_saving_savegame"; + break; + } + case GDM_DISCARD_CHANGES: { + message = "#str_dlg_confirm_discard"; + break; + } + case GDM_LEAVE_LOBBY: { + message = "#str_online_leave_game_lobby_alt_02"; + break; + } + case GDM_LEAVE_LOBBY_AND_TEAM: { + message = "#str_online_party_leave_game"; + break; + } + case GDM_CONTROLLER_DISCONNECTED_0: + case GDM_CONTROLLER_DISCONNECTED_1: + case GDM_CONTROLLER_DISCONNECTED_2: + case GDM_CONTROLLER_DISCONNECTED_3: + case GDM_CONTROLLER_DISCONNECTED_4: + case GDM_CONTROLLER_DISCONNECTED_5: + case GDM_CONTROLLER_DISCONNECTED_6:{ + message = "#str_dlg_reconnect_controller"; + break; + } + case GDM_NEEDS_INSTALL: { + message = "#str_dlg_game_install_message"; + break; + } + case GDM_ERROR_JOIN_TWO_PROFILES_ONE_BOX: { + message.Append( "online_join_error_two_profiles_one_box" ); + break; + } + case GDM_WARNING_PLAYING_COOP_SOLO: { + message = "#str_online_lotw_solo_warning_alt_05"; + break; + } + case GDM_MULTI_COOP_QUIT_LOSE_LEADERBOARDS: { + message = "#str_online_confirm_coop_quit_game"; + break; + } + case GDM_CORRUPT_CONTINUE: { + message = "#str_corrupt_save_load"; + break; + } + case GDM_MULTI_VDM_QUIT_LOSE_LEADERBOARDS: { + message = "#str_online_confirm_quit_game"; + break; + } + case GDM_WARNING_PLAYING_VDM_SOLO: { + message = "#str_online_cr_custom_game_no_stats"; + break; + } + case GDM_NO_GUEST_SUPPORT: { + message = "#str_dlg_ps3_incorrect_online_permissions"; + break; + } + case GDM_DISC_SWAP_CONFIRMATION: { + message = "#str_dlg_disc_swap_confirmation"; + break; + } + case GDM_ERROR_LOADING_PROFILE: { + message.Append( "error_loading_profile" ); + break; + } + case GDM_CANNOT_INVITE_LOBBY_FULL: { + message = "#str_online_join_error_full"; + break; + } + case GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS: { + message = "#str_dlg_360_new_device_selected"; + break; + } + case GDM_DISCONNECTED: { + message = "#str_online_connection_error_03"; + break; + } + case GDM_INCOMPATIBLE_NEWER_SAVE: { + message = "#str_dlg_newer_incompatible_savegame"; + break; + } + case GDM_ACHIEVEMENTS_DISABLED_DUE_TO_CHEATING: { + message = "#str_dlg_achievements_disabled_due_to_cheating"; + break; + } + case GDM_INCOMPATIBLE_POINTER_SIZE: { + message = "#str_dlg_pointer_size_mismatch"; + break; + } + case GDM_TEXTUREDETAIL_RESTARTREQUIRED: { + message = "#str_swf_texture_restart"; + break; + } + case GDM_TEXTUREDETAIL_INSUFFICIENT_CPU: { + message = "#str_swf_insufficient_cores"; + break; + } + case GDM_CALCULATING_BENCHMARK: { + message = "#str_swf_calc_benchmark"; + break; + } + case GDM_DISPLAY_BENCHMARK: { + message = "BENCHMARK SCORE = "; + break; + } + case GDM_DISPLAY_CHANGE_FAILED: { + message = "#str_swf_display_changes_failed"; + break; + } + case GDM_GPU_TRANSCODE_FAILED: { + message = "#str_swf_gpu_transcode_failed"; + break; + } + case GDM_OUT_OF_MEMORY: { + message = "#str_swf_failed_level_load"; + break; + } + case GDM_CORRUPT_PROFILE: { + message = "#str_dlg_corrupt_profile"; + break; + } + case GDM_PROFILE_TOO_OUT_OF_DATE_DEVELOPMENT_ONLY: { + message = "#str_dlg_profile_too_out_of_date_development_only"; + break; + } + case GDM_INSTALLING_TROPHIES: { + title = "#str_dlg_ps3_trophy_install_heading"; + message = "#str_dlg_ps3_trophy_installing"; + break; + } + case GDM_XBOX_DEPLOYMENT_TYPE_FAIL: { + message = "#str_dlg_360_installed_continue"; + break; + } + case GDM_GAME_RESTART_REQUIRED: { + message = "#str_dlg_game_restart_required"; + break; + } + default: { + message = "MESSAGE TYPE NOT DEFINED"; + break; + } + } + + return message; +} + +/* +================================================ +idCommonDialog::HandleDialogEvent +================================================ +*/ +bool idCommonDialog::HandleDialogEvent( const sysEvent_t * sev ) { + + if ( dialog != NULL && dialog->IsLoaded() && dialog->IsActive() ) { + if ( saveIndicator->IsActive() ) { + return false; + } else { + if ( dialog->HandleEvent( sev ) ) { + idKeyInput::ClearStates(); + // TODO_D3_PORT + //sys->ClearEvents(); + } + } + + return true; + } + + return false; +} + +/* +================================================ +idCommonDialog::IsDialogActive +================================================ +*/ +bool idCommonDialog::IsDialogActive() { + if ( dialog != NULL ) { + return dialog->IsActive(); + } + + return false; +} + +CONSOLE_COMMAND( commonDialogClear, "clears all dialogs that may be hung", 0 ) { + common->Dialog().ClearAllDialogHack(); +} + +CONSOLE_COMMAND( testShowDialog, "show a dialog", 0 ) { + int dialogId = atoi( args.Argv( 1 ) ); + common->Dialog().AddDialog( (gameDialogMessages_t)dialogId, DIALOG_ACCEPT, NULL, NULL, false ); +} + +CONSOLE_COMMAND( testShowDynamicDialog, "show a dynamic dialog", 0 ) { + class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE ); + return idSWFScriptVar(); + } + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_Continue() ); + optionText.Append( idStrId( "#str_swf_continue" ) ); + + // build custom space required string + // #str_dlg_space_required ~= "There is insufficient storage available. Please free %s and try again." + idStr format = idStrId( "#str_dlg_space_required" ).GetLocalizedString(); + idStr size; + int requiredSpaceInBytes = 150000; + if ( requiredSpaceInBytes > ( 1024 * 1024 ) ) { + size = va( "%.1f MB", (float) requiredSpaceInBytes / ( 1024.0f * 1024.0f ) ); + } else { + size = va( "%.0f KB", (float) requiredSpaceInBytes / 1024.0f ); + } + idStr msg = va( format.c_str(), size.c_str() ); + + common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg ); +} + +CONSOLE_COMMAND( testShowDialogBug, "show a dynamic dialog", 0 ) { + common->Dialog().ShowSaveIndicator( true ); + common->Dialog().ShowSaveIndicator( false ); + + // This locks the game because it thinks it's paused because we're passing in pause = true but the + // dialog isn't ever added because of the abuse of dialog->isActive when the save indicator is shown. + int dialogId = atoi( args.Argv( 1 ) ); + common->Dialog().AddDialog( (gameDialogMessages_t)dialogId, DIALOG_ACCEPT, NULL, NULL, true ); +} diff --git a/neo/framework/Common_dialog.h b/neo/framework/Common_dialog.h new file mode 100644 index 00000000..a484715f --- /dev/null +++ b/neo/framework/Common_dialog.h @@ -0,0 +1,316 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __COMMON_DIALOG_H__ +#define __COMMON_DIALOG_H__ + +static const int MAX_DIALOGS = 4; // maximum dialogs that can be open at one time +static const int PC_KEYBOARD_WAIT = 20000; + +/* +================================================ +Dialog box message types +================================================ +*/ +enum gameDialogMessages_t { + GDM_INVALID, + GDM_SWAP_DISKS_TO1, + GDM_SWAP_DISKS_TO2, + GDM_SWAP_DISKS_TO3, + GDM_NO_GAMER_PROFILE, + GDM_PLAY_ONLINE_NO_PROFILE, + GDM_LEADERBOARD_ONLINE_NO_PROFILE, + GDM_NO_STORAGE_SELECTED, + GDM_ONLINE_INCORRECT_PERMISSIONS, + GDM_SP_QUIT_SAVE, + GDM_SP_RESTART_SAVE, + GDM_SP_SIGNIN_CHANGE, + GDM_SERVER_NOT_AVAILABLE, + GDM_CONNECTION_LOST_HOST, + GDM_CONNECTION_LOST, + GDM_OPPONENT_CONNECTION_LOST, + GDM_HOST_CONNECTION_LOST, + GDM_HOST_CONNECTION_LOST_STATS, + GDM_FAILED_TO_LOAD_RANKINGS, + GDM_HOST_QUIT, + GDM_BECAME_HOST_PARTY, // Became host of party + GDM_NEW_HOST_PARTY, // Someone else became host of party + GDM_LOBBY_BECAME_HOST_GAME, // In lobby, you became game host + GDM_LOBBY_NEW_HOST_GAME, // In lobby, new game host was chosen (not you) + GDM_NEW_HOST_GAME, // Host left/DC'd, someone else is new host, unranked game + GDM_NEW_HOST_GAME_STATS_DROPPED,// Host left/DC'd, someone else is new host, ranked game so stats were dropped + GDM_BECAME_HOST_GAME, // Host left/DC'd, you became host, unranked game + GDM_BECAME_HOST_GAME_STATS_DROPPED, // Host left/DC'd, you became host, ranked game so stats were dropped + GDM_LOBBY_DISBANDED, + GDM_LEAVE_WITH_PARTY, + GDM_LEAVE_LOBBY_RET_MAIN, + GDM_LEAVE_LOBBY_RET_NEW_PARTY, + GDM_MIGRATING, + GDM_OPPONENT_LEFT, + GDM_NO_MATCHES_FOUND, + GDM_INVALID_INVITE, + GDM_KICKED, + GDM_BANNED, + GDM_SAVING, + GDM_OVERWRITE_SAVE, + GDM_LOAD_REQUEST, + GDM_AUTOSAVE_DISABLED_STORAGE_REMOVED, + GDM_STORAGE_INVALID, + GDM_STORAGE_REMOVED, + GDM_CONNECTING, + GDM_REFRESHING, + GDM_DELETE_SAVE, + GDM_DELETING, + GDM_BINDING_ALREDY_SET, + GDM_CANNOT_BIND, + GDM_OVERLAY_DISABLED, + GDM_DIRECT_MAP_CHANGE, + GDM_DELETE_AUTOSAVE, + GDM_QUICK_SAVE, + GDM_MULTI_RETRY, + GDM_MULTI_SELF_DESTRUCT, + GDM_MULTI_VDM_QUIT, + GDM_MULTI_COOP_QUIT, + GDM_LOADING_PROFILE, + GDM_STORAGE_REQUIRED, + GDM_INSUFFICENT_STORAGE_SPACE, + GDM_PARTNER_LEFT, + GDM_RESTORE_CORRUPT_SAVEGAME, + GDM_UNRECOVERABLE_SAVEGAME, + GDM_PROFILE_SAVE_ERROR, + GDM_LOBBY_FULL, + GDM_QUIT_GAME, + GDM_CONNECTION_PROBLEMS, + GDM_VOICE_RESTRICTED, + GDM_LOAD_DAMAGED_FILE, + GDM_MUST_SIGNIN, + GDM_CONNECTION_LOST_NO_LEADERBOARD, + GDM_SP_SIGNIN_CHANGE_POST, + GDM_MIGRATING_WAITING, + GDM_MIGRATING_RELAUNCHING, + GDM_MIGRATING_FAILED_CONNECTION, + GDM_MIGRATING_FAILED_CONNECTION_STATS, + GDM_MIGRATING_FAILED_DISBANDED, + GDM_MIGRATING_FAILED_DISBANDED_STATS, + GDM_MIGRATING_FAILED_PARTNER_LEFT, + GDM_HOST_RETURNED_TO_LOBBY, + GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED, + GDM_FAILED_JOIN_LOCAL_SESSION, + GDM_DELETE_CORRUPT_SAVEGAME, + GDM_LEAVE_INCOMPLETE_INSTANCE, + GDM_UNBIND_CONFIRM, + GDM_BINDINGS_RESTORE, + GDM_NEW_HOST, + GDM_CONFIRM_VIDEO_CHANGES, + GDM_UNABLE_TO_USE_SELECTED_STORAGE_DEVICE, + GDM_ERROR_LOADING_SAVEGAME, + GDM_ERROR_SAVING_SAVEGAME, + GDM_DISCARD_CHANGES, + GDM_LEAVE_LOBBY, + GDM_LEAVE_LOBBY_AND_TEAM, + GDM_CONTROLLER_DISCONNECTED_0, + GDM_CONTROLLER_DISCONNECTED_1, + GDM_CONTROLLER_DISCONNECTED_2, + GDM_CONTROLLER_DISCONNECTED_3, + GDM_CONTROLLER_DISCONNECTED_4, + GDM_CONTROLLER_DISCONNECTED_5, + GDM_CONTROLLER_DISCONNECTED_6, + GDM_DLC_ERROR_REMOVED, + GDM_DLC_ERROR_CORRUPT, + GDM_DLC_ERROR_MISSING, + GDM_DLC_ERROR_MISSING_GENERIC, + GDM_DISC_SWAP, + GDM_NEEDS_INSTALL, + GDM_NO_SAVEGAMES_AVAILABLE, + GDM_ERROR_JOIN_TWO_PROFILES_ONE_BOX, + GDM_WARNING_PLAYING_COOP_SOLO, + GDM_MULTI_COOP_QUIT_LOSE_LEADERBOARDS, + GDM_CORRUPT_CONTINUE, + GDM_MULTI_VDM_QUIT_LOSE_LEADERBOARDS, + GDM_WARNING_PLAYING_VDM_SOLO, + GDM_NO_GUEST_SUPPORT, + GDM_DISC_SWAP_CONFIRMATION, + GDM_ERROR_LOADING_PROFILE, + GDM_CANNOT_INVITE_LOBBY_FULL, + GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, + GDM_DISCONNECTED, + GDM_INCOMPATIBLE_NEWER_SAVE, + GDM_ACHIEVEMENTS_DISABLED_DUE_TO_CHEATING, + GDM_INCOMPATIBLE_POINTER_SIZE, + GDM_TEXTUREDETAIL_RESTARTREQUIRED, + GDM_TEXTUREDETAIL_INSUFFICIENT_CPU, + GDM_CHECKPOINT_SAVE, + GDM_CALCULATING_BENCHMARK, + GDM_DISPLAY_BENCHMARK, + GDM_DISPLAY_CHANGE_FAILED, + GDM_GPU_TRANSCODE_FAILED, + GDM_OUT_OF_MEMORY, + GDM_CORRUPT_PROFILE, + GDM_PROFILE_TOO_OUT_OF_DATE_DEVELOPMENT_ONLY, + GDM_SP_LOAD_SAVE, + GDM_INSTALLING_TROPHIES, + GDM_XBOX_DEPLOYMENT_TYPE_FAIL, + GDM_SAVEGAME_WRONG_LANGUAGE, + GDM_GAME_RESTART_REQUIRED, + GDM_MAX +}; + +/* +================================================ +Dialog box types +================================================ +*/ +enum dialogType_t { + DIALOG_INVALID = -1, + DIALOG_ACCEPT, + DIALOG_CONTINUE, + DIALOG_ACCEPT_CANCEL, + DIALOG_YES_NO, + DIALOG_CANCEL, + DIALOG_WAIT, + DIALOG_WAIT_BLACKOUT, + DIALOG_WAIT_CANCEL, + DIALOG_DYNAMIC, + DIALOG_QUICK_SAVE, + DIALOG_TIMER_ACCEPT_REVERT, + DIALOG_CRAWL_SAVE, + DIALOG_CONTINUE_LARGE, + DIALOG_BENCHMARK, +}; + +/* +================================================ +idDialogInfo +================================================ +*/ +class idDialogInfo { +public: + idDialogInfo() { + msg = GDM_INVALID; + type = DIALOG_ACCEPT; + acceptCB = NULL; + cancelCB = NULL; + altCBOne = NULL; + altCBTwo = NULL; + showing = false; + clear = false; + waitClear = false; + pause = false; + startTime = 0; + killTime = 0; + leaveOnClear = false; + renderDuringLoad = false; + } + gameDialogMessages_t msg; + dialogType_t type; + idSWFScriptFunction * acceptCB; + idSWFScriptFunction * cancelCB; + idSWFScriptFunction * altCBOne; + idSWFScriptFunction * altCBTwo; + bool showing; + bool clear; + bool waitClear; + bool pause; + bool forcePause; + bool leaveOnClear; + bool renderDuringLoad; + int startTime; + int killTime; + idStrStatic< 256 > overrideMsg; + + idStrId txt1; + idStrId txt2; + idStrId txt3; + idStrId txt4; +}; + +/* +================================================ +idLoadScreenInfo +================================================ +*/ +class idLoadScreenInfo { +public: + idStr varName; + idStr value; +}; + +/* +============================================================== + + Common Dialog + +============================================================== +*/ + +class idCommonDialog { +public: + void Init(); + void Render( bool loading ); + void Shutdown(); + void Restart(); + + bool IsDialogPausing() { return dialogPause; } + void ClearDialogs( bool forceClear = false ); + bool HasDialogMsg( gameDialogMessages_t msg, bool * isNowActive ); + void AddDialog( gameDialogMessages_t msg, dialogType_t type, idSWFScriptFunction * acceptCallback, idSWFScriptFunction * cancelCallback, bool pause, const char * location = NULL, int lineNumber = 0, bool leaveOnMapHeapReset = false, bool waitOnAtlas = false, bool renderDuringLoad = false ); + void AddDynamicDialog( gameDialogMessages_t msg, const idStaticList< idSWFScriptFunction *, 4 > & callbacks, const idStaticList< idStrId, 4 > & optionText, bool pause, idStrStatic< 256 > overrideMsg, bool leaveOnMapHeapReset = false, bool waitOnAtlas = false, bool renderDuringLoad = false ); + void AddDialogIntVal( const char * name, int val ); + bool IsDialogActive(); + void ClearDialog( gameDialogMessages_t msg, const char * location = NULL, int lineNumber = 0 ); + void ShowSaveIndicator( bool show ); + bool HasAnyActiveDialog() const { return ( messageList.Num() > 0 ) && ( !messageList[0].clear ); } + + void ClearAllDialogHack(); + idStr GetDialogMsg( gameDialogMessages_t msg, idStr & message, idStr & title ); + bool HandleDialogEvent( const sysEvent_t * sev ); + +protected: + void RemoveWaitDialogs(); + void ShowDialog( const idDialogInfo & info ); + void ShowNextDialog(); + void ActivateDialog( bool activate ); + void AddDialogInternal( idDialogInfo & info ); + void ReleaseCallBacks( int index ); + +private: + bool dialogPause; + idSWF * dialog; + idSWF * saveIndicator; + bool dialogShowingSaveIndicatorRequested; + int dialogShowingSaveIndicatorTimeRemaining; + + idStaticList< idDialogInfo, MAX_DIALOGS > messageList; + idStaticList< idLoadScreenInfo, 16 > loadScreenInfo; + + int startSaveTime; // with stopSaveTime, useful to pass 360 TCR# 047. Need to keep the dialog on the screen for a minimum amount of time + int stopSaveTime; + bool dialogInUse; // this is to prevent an active msg getting lost during a map heap reset +}; + +#endif diff --git a/neo/framework/Common_load.cpp b/neo/framework/Common_load.cpp new file mode 100644 index 00000000..e83991ef --- /dev/null +++ b/neo/framework/Common_load.cpp @@ -0,0 +1,1208 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" +#include "../sys/sys_lobby_backend.h" + + +#define LAUNCH_TITLE_DOOM_EXECUTABLE "doom1.exe" +#define LAUNCH_TITLE_DOOM2_EXECUTABLE "doom2.exe" + +idCVar com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" ); +idCVar com_disableAutoSaves( "com_disableAutoSaves", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar com_disableAllSaves( "com_disableAllSaves", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); + + +extern idCVar sys_lang; + +extern idCVar g_demoMode; + +// This is for the dirty hack to get a dialog to show up before we capture the screen for autorender. +const int NumScreenUpdatesToShowDialog = 25; + +/* +================ +idCommonLocal::LaunchExternalTitle + +Launches an external title ( Doom 1, or 2 ) based on title index. +for PS3, a device number is sent in, for the game to register as a local +user by default, when title initializes. +================ +*/ +void idCommonLocal::LaunchExternalTitle( int titleIndex, int device, const lobbyConnectInfo_t * const connectInfo ) { + + idStr deviceString( device ); + + // We want to pass in the current executable, so that the launching title knows which title to return to. + // as of right now, this feature is TBD. + const char * currentExecutablePath = "ImNotSureYet"; + idStr launchingExecutablePath; + + idCmdArgs cmdArgs; + cmdArgs.AppendArg( currentExecutablePath ); + + if ( titleIndex == LAUNCH_TITLE_DOOM ) { + launchingExecutablePath.Format("%s%s", Sys_DefaultBasePath(), LAUNCH_TITLE_DOOM_EXECUTABLE ); + cmdArgs.AppendArg( "d1bfg" ); + } else if ( titleIndex == LAUNCH_TITLE_DOOM2 ) { + launchingExecutablePath.Format("%s%s", Sys_DefaultBasePath(), LAUNCH_TITLE_DOOM2_EXECUTABLE ); + cmdArgs.AppendArg( "d2bfg" ); + + } else { + + idLib::Warning("Unhandled Launch Title %d \n", titleIndex ); + } + + cmdArgs.AppendArg( deviceString.c_str() ); + + // Add an argument so that the new process knows whether or not to read exitspawn data. + if ( connectInfo != NULL ) { + cmdArgs.AppendArg( "exitspawnInvite" ); + } + + // Add arguments so that the new process will know which command line to invoke to relaunch this process + // if necessary. + + const int launchDataSize = ( connectInfo == NULL ) ? 0 : sizeof( *connectInfo ); + + Sys_Launch( launchingExecutablePath.c_str() , cmdArgs, const_cast< lobbyConnectInfo_t * const >( connectInfo ), launchDataSize ); +} + +/* +================ +idCommonLocal::StartWipe + +Draws and captures the current state, then starts a wipe with that image +================ +*/ +void idCommonLocal::StartWipe( const char *_wipeMaterial, bool hold ) { + console->Close(); + + Draw(); + + renderSystem->CaptureRenderToImage( "_currentRender" ); + + wipeMaterial = declManager->FindMaterial( _wipeMaterial, false ); + + wipeStartTime = Sys_Milliseconds(); + wipeStopTime = wipeStartTime + SEC2MS( com_wipeSeconds.GetFloat() ); + wipeHold = hold; +} + +/* +================ +idCommonLocal::CompleteWipe +================ +*/ +void idCommonLocal::CompleteWipe() { + while ( Sys_Milliseconds() < wipeStopTime ) { + BusyWait(); + Sys_Sleep( 10 ); + } + + // ensure it is completely faded out + wipeStopTime = Sys_Milliseconds(); + BusyWait(); +} + +/* +================ +idCommonLocal::ClearWipe +================ +*/ +void idCommonLocal::ClearWipe() { + wipeHold = false; + wipeStopTime = 0; + wipeStartTime = 0; + wipeForced = false; +} + +/* +=============== +idCommonLocal::StartNewGame +=============== +*/ +void idCommonLocal::StartNewGame( const char * mapName, bool devmap, int gameMode ) { + if ( session->GetSignInManager().GetMasterLocalUser() == NULL ) { + // For development make sure a controller is registered + // Can't just register the local user because it will be removed because of it's persistent state + session->GetSignInManager().SetDesiredLocalUsers( 1, 1 ); + session->GetSignInManager().Pump(); + } + + idStr mapNameClean = mapName; + mapNameClean.StripFileExtension(); + mapNameClean.BackSlashesToSlashes(); + + idMatchParameters matchParameters; + matchParameters.mapName = mapNameClean; + if ( gameMode == GAME_MODE_SINGLEPLAYER ) { + matchParameters.numSlots = 1; + matchParameters.gameMode = GAME_MODE_SINGLEPLAYER; + matchParameters.gameMap = GAME_MAP_SINGLEPLAYER; + } else { + matchParameters.gameMap = mpGameMaps.Num(); // If this map isn't found in mpGameMaps, then set it to some undefined value (this happens when, for example, we load a box map with netmap) + matchParameters.gameMode = gameMode; + matchParameters.matchFlags = DefaultPartyFlags; + for ( int i = 0; i < mpGameMaps.Num(); i++ ) { + if ( idStr::Icmp( mpGameMaps[i].mapFile, mapNameClean ) == 0 ) { + matchParameters.gameMap = i; + break; + } + } + matchParameters.numSlots = session->GetTitleStorageInt("MAX_PLAYERS_ALLOWED", 4 ); + } + + cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); + if ( devmap ) { + matchParameters.serverInfo.Set( "devmap", "1" ); + } else { + matchParameters.serverInfo.Delete( "devmap" ); + } + + session->QuitMatchToTitle(); + if ( WaitForSessionState( idSession::IDLE ) ) { + session->CreatePartyLobby( matchParameters ); + if ( WaitForSessionState( idSession::PARTY_LOBBY ) ) { + session->CreateMatch( matchParameters ); + if ( WaitForSessionState( idSession::GAME_LOBBY ) ) { + cvarSystem->SetCVarBool( "developer", devmap ); + session->StartMatch(); + } + } + } +} + +/* +=============== +idCommonLocal::MoveToNewMap +Single player transition from one map to another +=============== +*/ +void idCommonLocal::MoveToNewMap( const char *mapName, bool devmap ) { + idMatchParameters matchParameters; + matchParameters.numSlots = 1; + matchParameters.gameMode = GAME_MODE_SINGLEPLAYER; + matchParameters.gameMap = GAME_MAP_SINGLEPLAYER; + matchParameters.mapName = mapName; + cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO, matchParameters.serverInfo ); + if ( devmap ) { + matchParameters.serverInfo.Set( "devmap", "1" ); + mapSpawnData.persistentPlayerInfo.Clear(); + } else { + matchParameters.serverInfo.Delete( "devmap" ); + mapSpawnData.persistentPlayerInfo = game->GetPersistentPlayerInfo( 0 ); + } + session->QuitMatchToTitle(); + if ( WaitForSessionState( idSession::IDLE ) ) { + session->CreatePartyLobby( matchParameters ); + if ( WaitForSessionState( idSession::PARTY_LOBBY ) ) { + session->CreateMatch( matchParameters ); + if ( WaitForSessionState( idSession::GAME_LOBBY ) ) { + session->StartMatch(); + } + } + } +} + +/* +=============== +idCommonLocal::UnloadMap + +Performs cleanup that needs to happen between maps, or when a +game is exited. +Exits with mapSpawned = false +=============== +*/ +void idCommonLocal::UnloadMap() { + StopPlayingRenderDemo(); + + // end the current map in the game + if ( game ) { + game->MapShutdown(); + } + + if ( writeDemo ) { + StopRecordingRenderDemo(); + } + + mapSpawned = false; +} + +/* +=============== +idCommonLocal::LoadLoadingGui +=============== +*/ +void idCommonLocal::LoadLoadingGui( const char *mapName, bool & hellMap ) { + + defaultLoadscreen = false; + loadGUI = new idSWF( "loading/default", NULL ); + + if ( g_demoMode.GetBool() ) { + hellMap = false; + if ( loadGUI != NULL ) { + const idMaterial * defaultMat = declManager->FindMaterial( "guis/assets/loadscreens/default" ); + renderSystem->LoadLevelImages(); + + loadGUI->Activate( true ); + idSWFSpriteInstance * bgImg = loadGUI->GetRootObject().GetSprite( "bgImage" ); + if ( bgImg != NULL ) { + bgImg->SetMaterial( defaultMat ); + } + } + defaultLoadscreen = true; + return; + } + + // load / program a gui to stay up on the screen while loading + idStrStatic< MAX_OSPATH > stripped = mapName; + stripped.StripFileExtension(); + stripped.StripPath(); + + // use default load screen for demo + idStrStatic< MAX_OSPATH > matName = "guis/assets/loadscreens/"; + matName.Append( stripped ); + const idMaterial * mat = declManager->FindMaterial( matName ); + + renderSystem->LoadLevelImages(); + + if ( mat->GetImageWidth() < 32 ) { + mat = declManager->FindMaterial( "guis/assets/loadscreens/default" ); + renderSystem->LoadLevelImages(); + } + + loadTipList.SetNum( loadTipList.Max() ); + for ( int i = 0; i < loadTipList.Max(); ++i ) { + loadTipList[i] = i; + } + + if ( loadGUI != NULL ) { + loadGUI->Activate( true ); + nextLoadTip = Sys_Milliseconds() + LOAD_TIP_CHANGE_INTERVAL; + + idSWFSpriteInstance * bgImg = loadGUI->GetRootObject().GetSprite( "bgImage" ); + if ( bgImg != NULL ) { + bgImg->SetMaterial( mat ); + } + + idSWFSpriteInstance * overlay = loadGUI->GetRootObject().GetSprite( "overlay" ); + + const idDeclEntityDef * mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef != NULL ) { + isHellMap = mapDef->dict.GetBool( "hellMap", false ); + + if ( isHellMap && overlay != NULL ) { + overlay->SetVisible( false ); + } + + idStr desc; + idStr subTitle; + idStr displayName; + idSWFTextInstance * txtVal = NULL; + + txtVal = loadGUI->GetRootObject().GetNestedText( "txtRegLoad" ); + displayName = idLocalization::GetString( mapDef->dict.GetString( "name", mapName ) ); + + if ( txtVal != NULL ) { + txtVal->SetText( "#str_00408" ); + txtVal->SetStrokeInfo( true, 2.0f, 1.0f ); + } + + const idMatchParameters & matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms(); + if ( matchParameters.gameMode == GAME_MODE_SINGLEPLAYER ) { + desc = idLocalization::GetString( mapDef->dict.GetString( "desc", "" ) ); + subTitle = idLocalization::GetString( mapDef->dict.GetString( "subTitle", "" ) ); + } else { + const idStrList & modes = common->GetModeDisplayList(); + subTitle = modes[ idMath::ClampInt( 0, modes.Num() - 1, matchParameters.gameMode ) ]; + + const char * modeDescs[] = { "#str_swf_deathmatch_desc", "#str_swf_tourney_desc", "#str_swf_team_deathmatch_desc", "#str_swf_lastman_desc", "#str_swf_ctf_desc" }; + desc = idLocalization::GetString( modeDescs[matchParameters.gameMode] ); + } + + if ( !isHellMap ) { + txtVal = loadGUI->GetRootObject().GetNestedText( "txtName" ); + } else { + txtVal = loadGUI->GetRootObject().GetNestedText( "txtHellName" ); + } + if ( txtVal != NULL ) { + txtVal->SetText( displayName ); + txtVal->SetStrokeInfo( true, 2.0f, 1.0f ); + } + + txtVal = loadGUI->GetRootObject().GetNestedText( "txtSub" ); + if ( txtVal != NULL && !isHellMap ) { + txtVal->SetText( subTitle ); + txtVal->SetStrokeInfo( true, 1.75f, 0.75f ); + } + + txtVal = loadGUI->GetRootObject().GetNestedText( "txtDesc" ); + if ( txtVal != NULL ) { + if ( isHellMap ) { + txtVal->SetText( va( "\n%s", desc.c_str() ) ); + } else { + txtVal->SetText( desc ); + } + txtVal->SetStrokeInfo( true, 1.75f, 0.75f ); + } + } + } +} + +/* +=============== +idCommonLocal::ExecuteMapChange + +Performs the initialization of a game based on session match parameters, used for both single +player and multiplayer, but not for renderDemos, which don't create a game at all. +Exits with mapSpawned = true +=============== +*/ +void idCommonLocal::ExecuteMapChange() { + if ( session->GetState() != idSession::LOADING ) { + idLib::Warning( "Session state is not LOADING in ExecuteMapChange" ); + return; + } + + // Clear all dialogs before beginning the load + common->Dialog().ClearDialogs( true ); + + // Remember the current load ID. + // This is so we can tell if we had a new loadmap request from within an existing loadmap call + const int cachedLoadingID = session->GetLoadingID(); + + const idMatchParameters & matchParameters = session->GetActingGameStateLobbyBase().GetMatchParms(); + + if ( matchParameters.numSlots <= 0 ) { + idLib::Warning( "numSlots <= 0 in ExecuteMapChange" ); + return; + } + + insideExecuteMapChange = true; + + common->Printf( "--------- Execute Map Change ---------\n" ); + common->Printf( "Map: %s\n", matchParameters.mapName.c_str() ); + + // ensure that r_znear is reset to the default value + // this fixes issues with the projection matrix getting messed up when switching maps or loading a saved game + // while an in-game cinematic is playing. + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + + // reset all cheat cvars for a multiplayer game + if ( IsMultiplayer() ) { + cvarSystem->ResetFlaggedVariables( CVAR_CHEAT ); + } + + int start = Sys_Milliseconds(); + + for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { + Sys_SetRumble( i, 0, 0 ); + } + + // close console and remove any prints from the notify lines + console->Close(); + + // clear all menu sounds + soundWorld->Pause(); + menuSoundWorld->ClearAllSoundEmitters(); + soundSystem->SetPlayingSoundWorld( menuSoundWorld ); + soundSystem->Render(); + + // extract the map name from serverinfo + currentMapName = matchParameters.mapName; + currentMapName.StripFileExtension(); + + idStrStatic< MAX_OSPATH > fullMapName = "maps/"; + fullMapName += currentMapName; + fullMapName.SetFileExtension( "map" ); + + if ( mapSpawnData.savegameFile ) { + fileSystem->BeginLevelLoad( currentMapName, NULL, 0 ); + } else { + fileSystem->BeginLevelLoad( currentMapName, saveFile.GetDataPtr(), saveFile.GetAllocated() ); + } + + // capture the current screen and start a wipe + // immediately complete the wipe to fade out the level transition + // run the wipe to completion + StartWipe( "wipeMaterial", true ); + CompleteWipe(); + + int sm = Sys_Milliseconds(); + // shut down the existing game if it is running + UnloadMap(); + int ms = Sys_Milliseconds() - sm; + common->Printf( "%6d msec to unload map\n", ms ); + + // Free media from previous level and + // note which media we are going to need to load + sm = Sys_Milliseconds(); + renderSystem->BeginLevelLoad(); + soundSystem->BeginLevelLoad(); + declManager->BeginLevelLoad(); + uiManager->BeginLevelLoad(); + ms = Sys_Milliseconds() - sm; + common->Printf( "%6d msec to free assets\n", ms ); + + //Sys_DumpMemory( true ); + + // load / program a gui to stay up on the screen while loading + // set the loading gui that we will wipe to + bool hellMap = false; + LoadLoadingGui( currentMapName, hellMap ); + + // Stop rendering the wipe + ClearWipe(); + + + if ( fileSystem->UsingResourceFiles() ) { + idStrStatic< MAX_OSPATH > manifestName = currentMapName; + manifestName.Replace( "game/", "maps/" ); + manifestName.Replace( "/mp/", "/" ); + manifestName += ".preload"; + idPreloadManifest manifest; + manifest.LoadManifest( manifestName ); + renderSystem->Preload( manifest, currentMapName ); + soundSystem->Preload( manifest ); + game->Preload( manifest ); + } + + if ( common->IsMultiplayer() ) { + // In multiplayer, make sure the player is either 60Hz or 120Hz + // to avoid potential issues. + const float mpEngineHz = ( com_engineHz.GetFloat() < 90.0f ) ? 60.0f : 120.0f; + com_engineHz_denominator = 100LL * mpEngineHz; + com_engineHz_latched = mpEngineHz; + } else { + // allow com_engineHz to be changed between map loads + com_engineHz_denominator = 100LL * com_engineHz.GetFloat(); + com_engineHz_latched = com_engineHz.GetFloat(); + } + + // note any warning prints that happen during the load process + common->ClearWarnings( currentMapName ); + + // release the mouse cursor + // before we do this potentially long operation + Sys_GrabMouseCursor( false ); + + // let the renderSystem load all the geometry + if ( !renderWorld->InitFromMap( fullMapName ) ) { + common->Error( "couldn't load %s", fullMapName.c_str() ); + } + + // for the synchronous networking we needed to roll the angles over from + // level to level, but now we can just clear everything + usercmdGen->InitForNewMap(); + + // load and spawn all other entities ( from a savegame possibly ) + if ( mapSpawnData.savegameFile ) { + if ( !game->InitFromSaveGame( fullMapName, renderWorld, soundWorld, mapSpawnData.savegameFile, mapSpawnData.stringTableFile, mapSpawnData.savegameVersion ) ) { + // If the loadgame failed, end the session, which will force us to go back to the main menu + session->QuitMatchToTitle(); + } + } else { + if ( !IsMultiplayer() ) { + assert( game->GetLocalClientNum() == 0 ); + assert( matchParameters.gameMode == GAME_MODE_SINGLEPLAYER ); + assert( matchParameters.gameMap == GAME_MAP_SINGLEPLAYER ); + game->SetPersistentPlayerInfo( 0, mapSpawnData.persistentPlayerInfo ); + } + game->SetServerInfo( matchParameters.serverInfo ); + game->InitFromNewMap( fullMapName, renderWorld, soundWorld, matchParameters.gameMode, Sys_Milliseconds() ); + } + + game->Shell_CreateMenu( true ); + + // Reset some values important to multiplayer + ResetNetworkingState(); + + // If the session state is not loading here, something went wrong. + if ( session->GetState() == idSession::LOADING && session->GetLoadingID() == cachedLoadingID ) { + // Notify session we are done loading + session->LoadingFinished(); + + while ( session->GetState() == idSession::LOADING ) { + Sys_GenerateEvents(); + session->UpdateSignInManager(); + session->Pump(); + Sys_Sleep( 10 ); + } + } + + if ( !mapSpawnData.savegameFile ) { + // run a single frame to catch any resources that are referenced by events posted in spawn + idUserCmdMgr emptyCommandManager; + gameReturn_t emptyGameReturn; + for ( int playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex ) { + emptyCommandManager.PutUserCmdForPlayer( playerIndex, usercmd_t() ); + } + if ( IsClient() ) { + game->ClientRunFrame( emptyCommandManager, false, emptyGameReturn ); + } else { + game->RunFrame( emptyCommandManager, emptyGameReturn ); + } + } + + renderSystem->EndLevelLoad(); + soundSystem->EndLevelLoad(); + declManager->EndLevelLoad(); + uiManager->EndLevelLoad( currentMapName ); + fileSystem->EndLevelLoad(); + + if ( !mapSpawnData.savegameFile && !IsMultiplayer() ) { + common->Printf( "----- Running initial game frames -----\n" ); + + // In single player, run a bunch of frames to make sure ragdolls are settled + idUserCmdMgr emptyCommandManager; + gameReturn_t emptyGameReturn; + for ( int i = 0; i < 100; i++ ) { + for ( int playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex ) { + emptyCommandManager.PutUserCmdForPlayer( playerIndex, usercmd_t() ); + } + game->RunFrame( emptyCommandManager, emptyGameReturn ); + } + + // kick off an auto-save of the game (so we can always continue in this map if we die before hitting an autosave) + common->Printf( "----- Saving Game -----\n" ); + SaveGame( "autosave" ); + } + + common->Printf( "----- Generating Interactions -----\n" ); + + // let the renderSystem generate interactions now that everything is spawned + renderWorld->GenerateAllInteractions(); + + { + int vertexMemUsedKB = vertexCache.staticData.vertexMemUsed.GetValue() / 1024; + int indexMemUsedKB = vertexCache.staticData.indexMemUsed.GetValue() / 1024; + idLib::Printf( "Used %dkb of static vertex memory (%d%%)\n", vertexMemUsedKB, vertexMemUsedKB * 100 / ( STATIC_VERTEX_MEMORY / 1024 ) ); + idLib::Printf( "Used %dkb of static index memory (%d%%)\n", indexMemUsedKB, indexMemUsedKB * 100 / ( STATIC_INDEX_MEMORY / 1024 ) ); + } + + if ( common->JapaneseCensorship() ) { + if ( currentMapName.Icmp( "game/mp/d3xpdm3" ) == 0 ) { + const idMaterial * gizpool2 = declManager->FindMaterial( "textures/hell/gizpool2" ); + idMaterial * chiglass1bluex = const_cast( declManager->FindMaterial( "textures/sfx/chiglass1bluex" ) ); + idTempArray text( gizpool2->GetTextLength() ); + gizpool2->GetText( text.Ptr() ); + chiglass1bluex->Parse( text.Ptr(), text.Num(), false ); + } + } + + common->PrintWarnings(); + + session->Pump(); + + if ( session->GetState() != idSession::INGAME ) { + // Something went wrong, don't process stale reliables that have been queued up. + reliableQueue.Clear(); + } + + usercmdGen->Clear(); + + // remove any prints from the notify lines + console->ClearNotifyLines(); + + Sys_SetPhysicalWorkMemory( -1, -1 ); + + // at this point we should be done with the loading gui so we kill it + delete loadGUI; + loadGUI = NULL; + + + // capture the current screen and start a wipe + StartWipe( "wipe2Material" ); + + // we are valid for game draws now + insideExecuteMapChange = false; + mapSpawned = true; + Sys_ClearEvents(); + + + int msec = Sys_Milliseconds() - start; + common->Printf( "%6d msec to load %s\n", msec, currentMapName.c_str() ); + //Sys_DumpMemory( false ); + + // Issue a render at the very end of the load process to update soundTime before the first frame + soundSystem->Render(); +} + +/* +=============== +idCommonLocal::UpdateLevelLoadPacifier + +Pumps the session and if multiplayer, displays dialogs during the loading process. +=============== +*/ +void idCommonLocal::UpdateLevelLoadPacifier() { + autoRenderIconType_t icon = AUTORENDER_DEFAULTICON; + bool autoswapsRunning = renderSystem->AreAutomaticBackgroundSwapsRunning( &icon ); + if ( !insideExecuteMapChange && !autoswapsRunning ) { + return; + } + + const int sessionUpdateTime = common->IsMultiplayer() ? 16 : 100; + + const int time = Sys_Milliseconds(); + + // Throttle session pumps. + if ( time - lastPacifierSessionTime >= sessionUpdateTime ) { + lastPacifierSessionTime = time; + + Sys_GenerateEvents(); + + session->UpdateSignInManager(); + session->Pump(); + session->ProcessSnapAckQueue(); + } + + if ( autoswapsRunning ) { + // If autoswaps are running, only update if a Dialog is shown/dismissed + bool dialogState = Dialog().HasAnyActiveDialog(); + if ( lastPacifierDialogState != dialogState ) { + lastPacifierDialogState = dialogState; + renderSystem->EndAutomaticBackgroundSwaps(); + if ( dialogState ) { + icon = AUTORENDER_DIALOGICON; // Done this way to handle the rare case of a tip changing at the same time a dialog comes up + for ( int i = 0; i < NumScreenUpdatesToShowDialog; ++i ) { + UpdateScreen( false ); + } + } + renderSystem->BeginAutomaticBackgroundSwaps( icon ); + } + } else { + // On the PC just update at a constant rate for the Steam overlay + if ( time - lastPacifierGuiTime >= 50 ) { + lastPacifierGuiTime = time; + UpdateScreen( false ); + } + } + + if ( time >= nextLoadTip && loadGUI != NULL && loadTipList.Num() > 0 && !defaultLoadscreen ) { + if ( autoswapsRunning ) { + renderSystem->EndAutomaticBackgroundSwaps(); + } + nextLoadTip = time + LOAD_TIP_CHANGE_INTERVAL; + const int rnd = time % loadTipList.Num(); + idStrStatic<20> tipId; + tipId.Format( "#str_loadtip_%d", loadTipList[ rnd ] ); + loadTipList.RemoveIndex( rnd ); + + idSWFTextInstance * txtVal = loadGUI->GetRootObject().GetNestedText( "txtDesc" ); + if ( txtVal != NULL ) { + if ( isHellMap ) { + txtVal->SetText( va( "\n%s", idLocalization::GetString( tipId ) ) ); + } else { + txtVal->SetText( idLocalization::GetString( tipId ) ); + } + txtVal->SetStrokeInfo( true, 1.75f, 0.75f ); + } + UpdateScreen( false ); + if ( autoswapsRunning ) { + renderSystem->BeginAutomaticBackgroundSwaps( icon ); + } + } +} + +/* +=============== +idCommonLocal::ScrubSaveGameFileName + +Turns a bad file name into a good one or your money back +=============== +*/ +void idCommonLocal::ScrubSaveGameFileName( idStr &saveFileName ) const { + int i; + idStr inFileName; + + inFileName = saveFileName; + inFileName.RemoveColors(); + inFileName.StripFileExtension(); + + saveFileName.Clear(); + + int len = inFileName.Length(); + for ( i = 0; i < len; i++ ) { + if ( strchr( "',.~!@#$%^&*()[]{}<>\\|/=?+;:-\'\"", inFileName[i] ) ) { + // random junk + saveFileName += '_'; + } else if ( (const unsigned char)inFileName[i] >= 128 ) { + // high ascii chars + saveFileName += '_'; + } else if ( inFileName[i] == ' ' ) { + saveFileName += '_'; + } else { + saveFileName += inFileName[i]; + } + } +} + +/* +=============== +idCommonLocal::SaveGame +=============== +*/ +bool idCommonLocal::SaveGame( const char * saveName ) { + if ( pipelineFile != NULL ) { + // We're already in the middle of a save. Leave us alone. + return false; + } + + if ( com_disableAllSaves.GetBool() || ( com_disableAutoSaves.GetBool() && ( idStr::Icmp( saveName, "autosave" ) == 0 ) ) ) { + return false; + } + + if ( IsMultiplayer() ) { + common->Printf( "Can't save during net play.\n" ); + return false; + } + + if (mapSpawnData.savegameFile != NULL ) { + return false; + } + + const idDict & persistentPlayerInfo = game->GetPersistentPlayerInfo( 0 ); + if ( persistentPlayerInfo.GetInt( "health" ) <= 0 ) { + common->Printf( "You must be alive to save the game\n" ); + return false; + } + + soundWorld->Pause(); + soundSystem->SetPlayingSoundWorld( menuSoundWorld ); + soundSystem->Render(); + + Dialog().ShowSaveIndicator( true ); + if ( insideExecuteMapChange ) { + UpdateLevelLoadPacifier(); + } else { + // Heremake sure we pump the gui enough times to show the 'saving' dialog + const bool captureToImage = false; + for ( int i = 0; i < NumScreenUpdatesToShowDialog; ++i ) { + UpdateScreen( captureToImage ); + } + renderSystem->BeginAutomaticBackgroundSwaps( AUTORENDER_DIALOGICON ); + } + + // Make sure the file is writable and the contents are cleared out (Set to write from the start of file) + saveFile.MakeWritable(); + saveFile.Clear( false ); + stringsFile.MakeWritable(); + stringsFile.Clear( false ); + + // Setup the save pipeline + pipelineFile = new (TAG_SAVEGAMES) idFile_SaveGamePipelined(); + pipelineFile->OpenForWriting( &saveFile ); + + // Write SaveGame Header: + // Game Name / Version / Map Name / Persistant Player Info + + // game + const char *gamename = GAME_NAME; + saveFile.WriteString( gamename ); + + // map + saveFile.WriteString( currentMapName ); + + saveFile.WriteBool( consoleUsed ); + + game->GetServerInfo().WriteToFileHandle( &saveFile ); + + // let the game save its state + game->SaveGame( pipelineFile, &stringsFile ); + + pipelineFile->Finish(); + + idSaveGameDetails gameDetails; + game->GetSaveGameDetails( gameDetails ); + + gameDetails.descriptors.Set( SAVEGAME_DETAIL_FIELD_LANGUAGE, sys_lang.GetString() ); + gameDetails.descriptors.SetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, (int)gameDetails.descriptors.Checksum() ); + + gameDetails.slotName = saveName; + ScrubSaveGameFileName( gameDetails.slotName ); + + saveFileEntryList_t files; + files.Append( &stringsFile ); + files.Append( &saveFile ); + + session->SaveGameSync( gameDetails.slotName, files, gameDetails ); + + if ( !insideExecuteMapChange ) { + renderSystem->EndAutomaticBackgroundSwaps(); + } + + syncNextGameFrame = true; + + return true; +} + +/* +=============== +idCommonLocal::LoadGame +=============== +*/ +bool idCommonLocal::LoadGame( const char * saveName ) { + if ( IsMultiplayer() ) { + common->Printf( "Can't load during net play.\n" ); + if ( wipeForced ) { + ClearWipe(); + } + return false; + } + + if ( GetCurrentGame() != DOOM3_BFG ) { + return false; + } + + if ( session->GetSignInManager().GetMasterLocalUser() == NULL ) { + return false; + } + if (mapSpawnData.savegameFile != NULL ) { + return false; + } + + bool found = false; + const saveGameDetailsList_t & sgdl = session->GetSaveGameManager().GetEnumeratedSavegames(); + for ( int i = 0; i < sgdl.Num(); i++ ) { + if ( sgdl[i].slotName == saveName ) { + if ( sgdl[i].GetLanguage() != sys_lang.GetString() ) { + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + optionText.Append( idStrId( "#str_swf_continue" ) ); + idStrStatic<256> langName = "#str_lang_" + sgdl[i].GetLanguage(); + idStrStatic<256> msg; + msg.Format( idLocalization::GetString( "#str_dlg_wrong_language" ), idLocalization::GetString( langName ) ); + Dialog().AddDynamicDialog( GDM_SAVEGAME_WRONG_LANGUAGE, callbacks, optionText, true, msg, false, true ); + if ( wipeForced ) { + ClearWipe(); + } + return false; + } + found = true; + break; + } + } + if ( !found ) { + common->Printf( "Could not find save '%s'\n", saveName ); + if ( wipeForced ) { + ClearWipe(); + } + return false; + } + + mapSpawnData.savegameFile = &saveFile; + mapSpawnData.stringTableFile = &stringsFile; + + saveFileEntryList_t files; + files.Append( mapSpawnData.stringTableFile ); + files.Append( mapSpawnData.savegameFile ); + + idStr slotName = saveName; + ScrubSaveGameFileName( slotName ); + saveFile.Clear( false ); + stringsFile.Clear( false ); + + saveGameHandle_t loadGameHandle = session->LoadGameSync( slotName, files ); + if ( loadGameHandle != 0 ) { + return true; + } + mapSpawnData.savegameFile = NULL; + if ( wipeForced ) { + ClearWipe(); + } + return false; +} + +/* +======================== +HandleInsufficientStorage +======================== +*/ +void HandleInsufficientStorage( const idSaveLoadParms & parms ) { + session->GetSaveGameManager().ShowRetySaveDialog( parms.directory, parms.requiredSpaceInBytes ); +} + +/* +======================== +HandleCommonErrors +======================== +*/ +bool HandleCommonErrors( const idSaveLoadParms & parms ) { + if ( parms.GetError() == SAVEGAME_E_NONE ) { + return true; + } + + common->Dialog().ShowSaveIndicator( false ); + + if ( parms.GetError() & SAVEGAME_E_CORRUPTED ) { + // This one might need to be handled by the game + common->Dialog().AddDialog( GDM_CORRUPT_CONTINUE, DIALOG_CONTINUE, NULL, NULL, false ); + + // Find the game in the enumerated details, mark as corrupt so the menus can show as corrupt + saveGameDetailsList_t & list = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst(); + for ( int i = 0; i < list.Num(); i++ ) { + if ( idStr::Icmp( list[i].slotName, parms.description.slotName ) == 0 ) { + list[i].damaged = true; + } + } + return true; + } else if ( parms.GetError() & SAVEGAME_E_INSUFFICIENT_ROOM ) { + HandleInsufficientStorage( parms ); + return true; + } else if ( parms.GetError() & SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE && saveGame_enable.GetBool() ) { + common->Dialog().AddDialog( GDM_UNABLE_TO_USE_SELECTED_STORAGE_DEVICE, DIALOG_CONTINUE, NULL, NULL, false ); + return true; + } else if ( parms.GetError() & SAVEGAME_E_INVALID_FILENAME ) { + idLib::Warning( va( "Invalid savegame filename [%s]!", parms.directory.c_str() ) ); + return true; + } else if ( parms.GetError() & SAVEGAME_E_DLC_NOT_FOUND ) { + common->Dialog().AddDialog( GDM_DLC_ERROR_MISSING_GENERIC, DIALOG_CONTINUE, NULL, NULL, false ); + return true; + } else if ( parms.GetError() & SAVEGAME_E_DISC_SWAP ) { + common->Dialog().AddDialog( GDM_DISC_SWAP, DIALOG_CONTINUE, NULL, NULL, false ); + return true; + } else if ( parms.GetError() & SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION ) { + common->Dialog().AddDialog( GDM_INCOMPATIBLE_NEWER_SAVE, DIALOG_CONTINUE, NULL, NULL, false ); + return true; + } + + return false; +} + +/* +======================== +idCommonLocal::OnSaveCompleted +======================== +*/ +void idCommonLocal::OnSaveCompleted( idSaveLoadParms & parms ) { + assert( pipelineFile != NULL ); + delete pipelineFile; + pipelineFile = NULL; + + if ( parms.GetError() == SAVEGAME_E_NONE ) { + game->Shell_UpdateSavedGames(); + } + + if ( !HandleCommonErrors( parms ) ) { + common->Dialog().AddDialog( GDM_ERROR_SAVING_SAVEGAME, DIALOG_CONTINUE, NULL, NULL, false ); + } +} + +/* +======================== +idCommonLocal::OnLoadCompleted +======================== +*/ +void idCommonLocal::OnLoadCompleted( idSaveLoadParms & parms ) { + if ( !HandleCommonErrors( parms ) ) { + common->Dialog().AddDialog( GDM_ERROR_LOADING_SAVEGAME, DIALOG_CONTINUE, NULL, NULL, false ); + } +} + +/* +======================== +idCommonLocal::OnLoadFilesCompleted +======================== +*/ +void idCommonLocal::OnLoadFilesCompleted( idSaveLoadParms & parms ) { + if ( ( mapSpawnData.savegameFile != NULL ) && ( parms.GetError() == SAVEGAME_E_NONE ) ) { + // just need to make the file readable + ((idFile_Memory *)mapSpawnData.savegameFile)->MakeReadOnly(); + ((idFile_Memory *)mapSpawnData.stringTableFile)->MakeReadOnly(); + + idStr gamename; + idStr mapname; + + mapSpawnData.savegameVersion = parms.description.GetSaveVersion(); + mapSpawnData.savegameFile->ReadString( gamename ); + mapSpawnData.savegameFile->ReadString( mapname ); + + if ( ( gamename != GAME_NAME ) || ( mapname.IsEmpty() ) || ( parms.description.GetSaveVersion() > BUILD_NUMBER ) ) { + // if this isn't a savegame for the correct game, abort loadgame + common->Warning( "Attempted to load an invalid savegame" ); + } else { + common->DPrintf( "loading savegame\n" ); + + mapSpawnData.savegameFile->ReadBool( consoleUsed ); + consoleUsed = consoleUsed || com_allowConsole.GetBool(); + + idMatchParameters matchParameters; + matchParameters.numSlots = 1; + matchParameters.gameMode = GAME_MODE_SINGLEPLAYER; + matchParameters.gameMap = GAME_MAP_SINGLEPLAYER; + matchParameters.mapName = mapname; + matchParameters.serverInfo.ReadFromFileHandle( mapSpawnData.savegameFile ); + + session->QuitMatchToTitle(); + if ( WaitForSessionState( idSession::IDLE ) ) { + session->CreatePartyLobby( matchParameters ); + if ( WaitForSessionState( idSession::PARTY_LOBBY ) ) { + session->CreateMatch( matchParameters ); + if ( WaitForSessionState( idSession::GAME_LOBBY ) ) { + session->StartMatch(); + return; + } + } + } + } + } + // If we got here then we didn't actually load the save game for some reason + mapSpawnData.savegameFile = NULL; +} + +/* +======================== +idCommonLocal::TriggerScreenWipe +======================== +*/ +void idCommonLocal::TriggerScreenWipe( const char * _wipeMaterial, bool hold ) { + StartWipe( _wipeMaterial, hold ); + CompleteWipe(); + wipeForced = true; + renderSystem->BeginAutomaticBackgroundSwaps( AUTORENDER_DEFAULTICON ); +} + +/* +======================== +idCommonLocal::OnEnumerationCompleted +======================== +*/ +void idCommonLocal::OnEnumerationCompleted( idSaveLoadParms & parms ) { + if ( parms.GetError() == SAVEGAME_E_NONE ) { + game->Shell_UpdateSavedGames(); + } +} + +/* +======================== +idCommonLocal::OnDeleteCompleted +======================== +*/ +void idCommonLocal::OnDeleteCompleted( idSaveLoadParms & parms ) { + if ( parms.GetError() == SAVEGAME_E_NONE ) { + game->Shell_UpdateSavedGames(); + } +} + +/* +=============== +LoadGame_f +=============== +*/ +CONSOLE_COMMAND_SHIP( loadGame, "loads a game", idCmdSystem::ArgCompletion_SaveGame ) { + console->Close(); + commonLocal.LoadGame( ( args.Argc() > 1 ) ? args.Argv(1) : "quick" ); +} + +/* +=============== +SaveGame_f +=============== +*/ +CONSOLE_COMMAND_SHIP( saveGame, "saves a game", NULL ) { + const char * savename = ( args.Argc() > 1 ) ? args.Argv(1) : "quick"; + if ( commonLocal.SaveGame( savename ) ) { + common->Printf( "Saved: %s\n", savename ); + } +} + +/* +================== +Common_Map_f + +Restart the server on a different map +================== +*/ +CONSOLE_COMMAND_SHIP( map, "loads a map", idCmdSystem::ArgCompletion_MapName ) { + commonLocal.StartNewGame( args.Argv(1), false, GAME_MODE_SINGLEPLAYER ); +} + +/* +================== +Common_RestartMap_f +================== +*/ +CONSOLE_COMMAND_SHIP( restartMap, "restarts the current map", NULL ) { + if ( g_demoMode.GetBool() ) { + cmdSystem->AppendCommandText( va( "devmap %s %d\n", commonLocal.GetCurrentMapName(), 0 ) ); + } +} + +/* +================== +Common_DevMap_f + +Restart the server on a different map in developer mode +================== +*/ +CONSOLE_COMMAND_SHIP( devmap, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName ) { + commonLocal.StartNewGame( args.Argv(1), true, GAME_MODE_SINGLEPLAYER ); +} + +/* +================== +Common_NetMap_f + +Restart the server on a different map in multiplayer mode +================== +*/ +CONSOLE_COMMAND_SHIP( netmap, "loads a map in multiplayer mode", idCmdSystem::ArgCompletion_MapName ) { + int gameMode = 0; // Default to deathmatch + if ( args.Argc() > 2 ) { + gameMode = atoi( args.Argv(2) ); + } + commonLocal.StartNewGame( args.Argv(1), true, gameMode ); +} + +/* +================== +Common_TestMap_f +================== +*/ +CONSOLE_COMMAND( testmap, "tests a map", idCmdSystem::ArgCompletion_MapName ) { + idStr map, string; + + map = args.Argv(1); + if ( !map.Length() ) { + return; + } + map.StripFileExtension(); + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + + sprintf( string, "dmap maps/%s.map", map.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); + + sprintf( string, "devmap %s", map.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); +} diff --git a/neo/framework/Common_local.h b/neo/framework/Common_local.h new file mode 100644 index 00000000..5ff1702c --- /dev/null +++ b/neo/framework/Common_local.h @@ -0,0 +1,504 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +static const int MAX_USERCMD_BACKUP = 256; +static const int NUM_USERCMD_RELAY = 10; +static const int NUM_USERCMD_SEND = 8; + +static const int initialHz = 60; +static const int initialBaseTicks = 1000 / initialHz; +static const int initialBaseTicksPerSec = initialHz * initialBaseTicks; + +static const int LOAD_TIP_CHANGE_INTERVAL = 12000; +static const int LOAD_TIP_COUNT = 26; + +class idGameThread : public idSysThread { +public: + idGameThread() : + gameTime(), + drawTime(), + threadTime(), + threadGameTime(), + threadRenderTime(), + userCmdMgr( NULL ), + ret(), + numGameFrames(), + isClient() + {} + + // the gameReturn_t is from the previous frame, the + // new frame will be running in parallel on exit + gameReturn_t RunGameAndDraw( int numGameFrames, idUserCmdMgr & userCmdMgr_, bool isClient_, int startGameFrame ); + + // Accessors to the stored frame/thread time information + void SetThreadTotalTime( const int inTime ) { threadTime = inTime; } + int GetThreadTotalTime() const { return threadTime; } + + void SetThreadGameTime( const int time ) { threadGameTime = time; } + int GetThreadGameTime() const { return threadGameTime; } + + void SetThreadRenderTime( const int time ) { threadRenderTime = time; } + int GetThreadRenderTime() const { return threadRenderTime; } + +private: + virtual int Run(); + + int gameTime; + int drawTime; + int threadTime; // total time : game time + foreground render time + int threadGameTime; // game time only + int threadRenderTime; // render fg time only + idUserCmdMgr * userCmdMgr; + gameReturn_t ret; + int numGameFrames; + bool isClient; +}; + +enum errorParm_t { + ERP_NONE, + ERP_FATAL, // exit the entire game with a popup window + ERP_DROP, // print to console and disconnect from game + ERP_DISCONNECT // don't kill server +}; + +enum gameLaunch_t { + LAUNCH_TITLE_DOOM = 0, + LAUNCH_TITLE_DOOM2, +}; + +struct netTimes_t { + int localTime; + int serverTime; +}; + +struct frameTiming_t { + uint64 startSyncTime; + uint64 finishSyncTime; + uint64 startGameTime; + uint64 finishGameTime; + uint64 finishDrawTime; + uint64 startRenderTime; + uint64 finishRenderTime; +}; + +#define MAX_PRINT_MSG_SIZE 4096 +#define MAX_WARNING_LIST 256 + +#define SAVEGAME_CHECKPOINT_FILENAME "gamedata.save" +#define SAVEGAME_DESCRIPTION_FILENAME "gamedata.txt" +#define SAVEGAME_STRINGS_FILENAME "gamedata.strings" + +class idCommonLocal : public idCommon { +public: + idCommonLocal(); + + virtual void Init( int argc, const char * const * argv, const char *cmdline ); + virtual void Shutdown(); + virtual void CreateMainMenu(); + virtual void Quit(); + virtual bool IsInitialized() const; + virtual void Frame(); + virtual void UpdateScreen( bool captureToImage ); + virtual void UpdateLevelLoadPacifier(); + virtual void StartupVariable( const char * match ); + virtual void WriteConfigToFile( const char *filename ); + virtual void BeginRedirect( char *buffer, int buffersize, void (*flush)( const char * ) ); + virtual void EndRedirect(); + virtual void SetRefreshOnPrint( bool set ); + virtual void Printf( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual void VPrintf( const char *fmt, va_list arg ); + virtual void DPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual void Warning( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual void DWarning( VERIFY_FORMAT_STRING const char *fmt, ...); + virtual void PrintWarnings(); + virtual void ClearWarnings( const char *reason ); + virtual void Error( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual void FatalError( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual bool IsShuttingDown() const { return com_shuttingDown; } + + virtual const char * KeysFromBinding( const char *bind ); + virtual const char * BindingFromKey( const char *key ); + + virtual bool IsMultiplayer(); + virtual bool IsServer(); + virtual bool IsClient(); + + virtual bool GetConsoleUsed() { return consoleUsed; } + + virtual int GetSnapRate(); + + virtual void NetReceiveReliable( int peer, int type, idBitMsg & msg ); + virtual void NetReceiveSnapshot( class idSnapShot & ss ); + virtual void NetReceiveUsercmds( int peer, idBitMsg & msg ); + void NetReadUsercmds( int clientNum, idBitMsg & msg ); + + virtual bool ProcessEvent( const sysEvent_t *event ); + + virtual bool LoadGame( const char * saveName ); + virtual bool SaveGame( const char * saveName ); + + virtual int ButtonState( int key ); + virtual int KeyState( int key ); + + virtual idDemoFile * ReadDemo() { return readDemo; } + virtual idDemoFile * WriteDemo() { return writeDemo; } + + virtual idGame * Game() { return game; } + virtual idRenderWorld * RW() { return renderWorld; } + virtual idSoundWorld * SW() { return soundWorld; } + virtual idSoundWorld * MenuSW() { return menuSoundWorld; } + virtual idSession * Session() { return session; } + virtual idCommonDialog & Dialog() { return commonDialog; } + + virtual void OnSaveCompleted( idSaveLoadParms & parms ); + virtual void OnLoadCompleted( idSaveLoadParms & parms ); + virtual void OnLoadFilesCompleted( idSaveLoadParms & parms ); + virtual void OnEnumerationCompleted( idSaveLoadParms & parms ); + virtual void OnDeleteCompleted( idSaveLoadParms & parms ); + virtual void TriggerScreenWipe( const char * _wipeMaterial, bool hold ); + + virtual void OnStartHosting( idMatchParameters & parms ); + + virtual int GetGameFrame() { return gameFrame; } + + virtual void LaunchExternalTitle( int titleIndex, + int device, + const lobbyConnectInfo_t * const connectInfo ); // For handling invitations. NULL if no invitation used. + + virtual void InitializeMPMapsModes(); + virtual const idStrList & GetModeList() const { return mpGameModes; } + virtual const idStrList & GetModeDisplayList() const { return mpDisplayGameModes; } + virtual const idList & GetMapList() const { return mpGameMaps; } + + virtual void ResetPlayerInput( int playerIndex ); + + virtual bool JapaneseCensorship() const; + + virtual void QueueShowShell() { showShellRequested = true; } + + virtual currentGame_t GetCurrentGame() const { return currentGame; } + virtual void SwitchToGame( currentGame_t newGame ); + +public: + void Draw(); // called by gameThread + + int GetGameThreadTotalTime() const { return gameThread.GetThreadTotalTime(); } + int GetGameThreadGameTime() const { return gameThread.GetThreadGameTime(); } + int GetGameThreadRenderTime() const { return gameThread.GetThreadRenderTime(); } + int GetRendererBackEndMicroseconds() const { return time_backend; } + int GetRendererShadowsMicroseconds() const { return time_shadows; } + int GetRendererIdleMicroseconds() const { return mainFrameTiming.startRenderTime - mainFrameTiming.finishSyncTime; } + int GetRendererGPUMicroseconds() const { return time_gpu; } + + frameTiming_t frameTiming; + frameTiming_t mainFrameTiming; + +public: // These are public because they are called directly by static functions in this file + + const char * GetCurrentMapName() { return currentMapName.c_str(); } + + // loads a map and starts a new game on it + void StartNewGame( const char * mapName, bool devmap, int gameMode ); + void LeaveGame(); + + void DemoShot( const char *name ); + void StartRecordingRenderDemo( const char *name ); + void StopRecordingRenderDemo(); + void StartPlayingRenderDemo( idStr name ); + void StopPlayingRenderDemo(); + void CompressDemoFile( const char *scheme, const char *name ); + void TimeRenderDemo( const char *name, bool twice = false, bool quit = false ); + void AVIRenderDemo( const char *name ); + void AVIGame( const char *name ); + + // localization + void InitLanguageDict(); + void LocalizeGui( const char *fileName, idLangDict &langDict ); + void LocalizeMapData( const char *fileName, idLangDict &langDict ); + void LocalizeSpecificMapData( const char *fileName, idLangDict &langDict, const idLangDict &replaceArgs ); + + idUserCmdMgr & GetUCmdMgr() { return userCmdMgr; } + +private: + bool com_fullyInitialized; + bool com_refreshOnPrint; // update the screen every print for dmap + errorParm_t com_errorEntered; + bool com_shuttingDown; + bool com_isJapaneseSKU; + + idFile * logFile; + + char errorMessage[MAX_PRINT_MSG_SIZE]; + + char * rd_buffer; + int rd_buffersize; + void (*rd_flush)( const char *buffer ); + + idStr warningCaption; + idStrList warningList; + idStrList errorList; + + int gameDLL; + + idCommonDialog commonDialog; + + idFile_SaveGame saveFile; + idFile_SaveGame stringsFile; + idFile_SaveGamePipelined *pipelineFile; + + // The main render world and sound world + idRenderWorld * renderWorld; + idSoundWorld * soundWorld; + + // The renderer and sound system will write changes to writeDemo. + // Demos can be recorded and played at the same time when splicing. + idDemoFile * readDemo; + idDemoFile * writeDemo; + + bool menuActive; + idSoundWorld * menuSoundWorld; // so the game soundWorld can be muted + + bool insideExecuteMapChange; // Enable Pacifier Updates + + // This is set if the player enables the console, which disables achievements + bool consoleUsed; + + // This additional information is required for ExecuteMapChange for SP games ONLY + // This data is cleared after ExecuteMapChange + struct mapSpawnData_t { + idFile_SaveGame * savegameFile; // Used for loading a save game + idFile_SaveGame * stringTableFile; // String table read from save game loaded + idFile_SaveGamePipelined *pipelineFile; + int savegameVersion; // Version of the save game we're loading + idDict persistentPlayerInfo; // Used for transitioning from map to map + }; + mapSpawnData_t mapSpawnData; + idStr currentMapName; // for checking reload on same level + bool mapSpawned; // cleared on Stop() + + bool insideUpdateScreen; // true while inside ::UpdateScreen() + + idUserCmdMgr userCmdMgr; + + int nextUsercmdSendTime; // Next time to send usercmds + int nextSnapshotSendTime; // Next time to send a snapshot + + idSnapShot lastSnapShot; // last snapshot we received from the server + struct reliableMsg_t { + int client; + int type; + int dataSize; + byte * data; + }; + idList reliableQueue; + + + // Snapshot interpolation + idSnapShot oldss; // last local snapshot + // (ie on server this is the last "master" snapshot we created) + // (on clients this is the last received snapshot) + // used for comparisons with the new snapshot for com_drawSnapshot + + // This is ultimately controlled by net_maxBufferedSnapshots by running double speed, but this is the hard max before seeing visual popping + static const int RECEIVE_SNAPSHOT_BUFFER_SIZE = 16; + + int readSnapshotIndex; + int writeSnapshotIndex; + idArray receivedSnaps; + + float optimalPCTBuffer; + float optimalTimeBuffered; + float optimalTimeBufferedWindow; + + uint64 snapRate; + uint64 actualRate; + + uint64 snapTime; // time we got the most recent snapshot + uint64 snapTimeDelta; // time interval that current ss was sent in + + uint64 snapTimeWrite; + uint64 snapCurrentTime; // realtime playback time + netTimes_t snapCurrent; // current snapshot + netTimes_t snapPrevious; // previous snapshot + float snapCurrentResidual; + + float snapTimeBuffered; + float effectiveSnapRate; + int totalBufferedTime; + int totalRecvTime; + + + + int clientPrediction; + + int gameFrame; // Frame number of the local game + double gameTimeResidual; // left over msec from the last game frame + bool syncNextGameFrame; + + bool aviCaptureMode; // if true, screenshots will be taken and sound captured + idStr aviDemoShortName; // + int aviDemoFrameCount; + + enum timeDemo_t { + TD_NO, + TD_YES, + TD_YES_THEN_QUIT + }; + timeDemo_t timeDemo; + int timeDemoStartTime; + int numDemoFrames; // for timeDemo and demoShot + int demoTimeOffset; + renderView_t currentDemoRenderView; + + idStrList mpGameModes; + idStrList mpDisplayGameModes; + idList mpGameMaps; + + idSWF * loadGUI; + int nextLoadTip; + bool isHellMap; + bool defaultLoadscreen; + idStaticList loadTipList; + + const idMaterial * splashScreen; + + const idMaterial * whiteMaterial; + + const idMaterial * wipeMaterial; + int wipeStartTime; + int wipeStopTime; + bool wipeHold; + bool wipeForced; // used for the PS3 to start an early wipe while we are accessing saved game data + + idGameThread gameThread; // the game and draw code can be run in parallel + + // com_speeds times + int count_numGameFrames; // total number of game frames that were run + int time_gameFrame; // game logic time + int time_maxGameFrame; // maximum single frame game logic time + int time_gameDraw; // game present time + uint64 time_frontend; // renderer frontend time + uint64 time_backend; // renderer backend time + uint64 time_shadows; // renderer backend waiting for shadow volumes to be created + uint64 time_gpu; // total gpu time, at least for PC + + // Used during loading screens + int lastPacifierSessionTime; + int lastPacifierGuiTime; + bool lastPacifierDialogState; + + bool showShellRequested; + + currentGame_t currentGame; + currentGame_t idealCurrentGame; // Defer game switching so that bad things don't happen in the middle of the frame. + const idMaterial * doomClassicMaterial; + + static const int DOOMCLASSIC_RENDERWIDTH = 320 * 3; + static const int DOOMCLASSIC_RENDERHEIGHT = 200 * 3; + static const int DOOMCLASSIC_BYTES_PER_PIXEL = 4; + static const int DOOMCLASSIC_IMAGE_SIZE_IN_BYTES = DOOMCLASSIC_RENDERWIDTH * DOOMCLASSIC_RENDERHEIGHT * DOOMCLASSIC_BYTES_PER_PIXEL; + + idArray< byte, DOOMCLASSIC_IMAGE_SIZE_IN_BYTES > doomClassicImageData; + +private: + void InitCommands(); + void InitSIMD(); + void AddStartupCommands(); + void ParseCommandLine( int argc, const char * const * argv ); + bool SafeMode(); + void CloseLogFile(); + void WriteConfiguration(); + void DumpWarnings(); + void LoadGameDLL(); + void UnloadGameDLL(); + void CleanupShell(); + void RenderBink( const char * path ); + void RenderSplash(); + void FilterLangList( idStrList* list, idStr lang ); + void CheckStartupStorageRequirements(); + + void ExitMenu(); + bool MenuEvent( const sysEvent_t * event ); + + void StartMenu( bool playIntro = false ); + void GuiFrameEvents(); + + void BeginAVICapture( const char *name ); + void EndAVICapture(); + + void AdvanceRenderDemo( bool singleFrameOnly ); + + void ProcessGameReturn( const gameReturn_t & ret ); + + void RunNetworkSnapshotFrame(); + void ExecuteReliableMessages(); + + + // Snapshot interpolation + void ProcessSnapshot( idSnapShot & ss ); + int CalcSnapTimeBuffered( int & totalBufferedTime, int & totalRecvTime ); + void ProcessNextSnapshot(); + void InterpolateSnapshot( netTimes_t & prev, netTimes_t & next, float fraction, bool predict ); + void ResetNetworkingState(); + + int NetworkFrame(); + void SendSnapshots(); + void SendUsercmds( int localClientNum ); + + void LoadLoadingGui(const char *mapName, bool & hellMap ); + + // Meant to be used like: + // while ( waiting ) { BusyWait(); } + void BusyWait(); + bool WaitForSessionState( idSession::sessionState_t desiredState ); + + void ExecuteMapChange(); + void UnloadMap(); + + void Stop( bool resetSession = true ); + + // called by Draw when the scene to scene wipe is still running + void DrawWipeModel(); + void StartWipe( const char *materialName, bool hold = false); + void CompleteWipe(); + void ClearWipe(); + + void MoveToNewMap( const char * mapName, bool devmap ); + + void PlayIntroGui(); + + void ScrubSaveGameFileName( idStr &saveFileName ) const; + + // Doom classic support + void RunDoomClassicFrame(); + void RenderDoomClassic(); + bool IsPlayingDoomClassic() const { return GetCurrentGame() != DOOM3_BFG; } + void PerformGameSwitch(); +}; + +extern idCommonLocal commonLocal; \ No newline at end of file diff --git a/neo/framework/Common_localize.cpp b/neo/framework/Common_localize.cpp new file mode 100644 index 00000000..1bdb4165 --- /dev/null +++ b/neo/framework/Common_localize.cpp @@ -0,0 +1,674 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" + +idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "Extension to use when creating language files." ); + +/* +================= +LoadMapLocalizeData +================= +*/ +typedef idHashTable ListHash; +void LoadMapLocalizeData(ListHash& listHash) { + + idStr fileName = "map_localize.cfg"; + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + idStr classname; + idToken token; + + + + while ( src.ReadToken( &token ) ) { + classname = token; + src.ExpectTokenString( "{" ); + + idStrList list; + while ( src.ReadToken( &token) ) { + if ( token == "}" ) { + break; + } + list.Append(token); + } + + listHash.Set(classname, list); + } + } + fileSystem->FreeFile( (void*)buffer ); + } + +} + +void LoadGuiParmExcludeList(idStrList& list) { + + idStr fileName = "guiparm_exclude.cfg"; + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + idStr classname; + idToken token; + + + + while ( src.ReadToken( &token ) ) { + list.Append(token); + } + } + fileSystem->FreeFile( (void*)buffer ); + } +} + +bool TestMapVal(idStr& str) { + //Already Localized? + if(str.Find("#str_") != -1) { + return false; + } + + return true; +} + +bool TestGuiParm(const char* parm, const char* value, idStrList& excludeList) { + + idStr testVal = value; + + //Already Localized? + if(testVal.Find("#str_") != -1) { + return false; + } + + //Numeric + if(testVal.IsNumeric()) { + return false; + } + + //Contains :: + if(testVal.Find("::") != -1) { + return false; + } + + //Contains / + if(testVal.Find("/") != -1) { + return false; + } + + if(excludeList.Find(testVal)) { + return false; + } + + return true; +} + +void GetFileList(const char* dir, const char* ext, idStrList& list) { + + //Recurse Subdirectories + idStrList dirList; + Sys_ListFiles(dir, "/", dirList); + for(int i = 0; i < dirList.Num(); i++) { + if(dirList[i] == "." || dirList[i] == "..") { + continue; + } + idStr fullName = va("%s/%s", dir, dirList[i].c_str()); + GetFileList(fullName, ext, list); + } + + idStrList fileList; + Sys_ListFiles(dir, ext, fileList); + for(int i = 0; i < fileList.Num(); i++) { + idStr fullName = va("%s/%s", dir, fileList[i].c_str()); + list.Append(fullName); + } +} + +int LocalizeMap(const char* mapName, idLangDict &langDict, ListHash& listHash, idStrList& excludeList, bool writeFile) { + + common->Printf("Localizing Map '%s'\n", mapName); + + int strCount = 0; + + idMapFile map; + if ( map.Parse(mapName, false, false ) ) { + int count = map.GetNumEntities(); + for ( int j = 0; j < count; j++ ) { + idMapEntity *ent = map.GetEntity( j ); + if ( ent ) { + + idStr classname = ent->epairs.GetString("classname"); + + //Hack: for info_location + bool hasLocation = false; + + idStrList* list; + listHash.Get(classname, &list); + if(list) { + + for(int k = 0; k < list->Num(); k++) { + + idStr val = ent->epairs.GetString((*list)[k], ""); + + if(val.Length() && classname == "info_location" && (*list)[k] == "location") { + hasLocation = true; + } + + if(val.Length() && TestMapVal(val)) { + + if(!hasLocation || (*list)[k] == "location") { + //Localize it!!! + strCount++; + ent->epairs.Set( (*list)[k], langDict.AddString( val ) ); + } + } + } + } + + listHash.Get("all", &list); + if(list) { + for(int k = 0; k < list->Num(); k++) { + idStr val = ent->epairs.GetString((*list)[k], ""); + if(val.Length() && TestMapVal(val)) { + //Localize it!!! + strCount++; + ent->epairs.Set( (*list)[k], langDict.AddString( val ) ); + } + } + } + + //Localize the gui_parms + const idKeyValue* kv = ent->epairs.MatchPrefix("gui_parm"); + while( kv ) { + if(TestGuiParm(kv->GetKey(), kv->GetValue(), excludeList)) { + //Localize It! + strCount++; + ent->epairs.Set( kv->GetKey(), langDict.AddString( kv->GetValue() ) ); + } + kv = ent->epairs.MatchPrefix( "gui_parm", kv ); + } + } + } + if(writeFile && strCount > 0) { + //Before we write the map file lets make a backup of the original + idStr file = fileSystem->RelativePathToOSPath(mapName); + idStr bak = file.Left(file.Length() - 4); + bak.Append(".bak_loc"); + fileSystem->CopyFile( file, bak ); + + map.Write( mapName, ".map" ); + } + } + + common->Printf("Count: %d\n", strCount); + return strCount; +} + +/* +================= +LocalizeMaps_f +================= +*/ +CONSOLE_COMMAND( localizeMaps, "localize maps", NULL ) { + if ( args.Argc() < 2 ) { + common->Printf( "Usage: localizeMaps \n" ); + return; + } + + int strCount = 0; + + bool count = false; + bool dictUpdate = false; + bool write = false; + + if ( idStr::Icmp( args.Argv(1), "count" ) == 0 ) { + count = true; + } else if ( idStr::Icmp( args.Argv(1), "dictupdate" ) == 0 ) { + count = true; + dictUpdate = true; + } else if ( idStr::Icmp( args.Argv(1), "all" ) == 0 ) { + count = true; + dictUpdate = true; + write = true; + } else { + common->Printf( "Invalid Command\n" ); + common->Printf( "Usage: localizeMaps \n" ); + return; + + } + + idLangDict strTable; + idStr filename = va("strings/english%.3i.lang", com_product_lang_ext.GetInteger()); + + { + // I think this is equivalent... + const byte * buffer = NULL; + int len = fileSystem->ReadFile( filename, (void**)&buffer ); + if ( verify( len > 0 ) ) { + strTable.Load( buffer, len, filename ); + } + fileSystem->FreeFile( (void *)buffer ); + + // ... to this + //if ( strTable.Load( filename ) == false) { + // //This is a new file so set the base index + // strTable.SetBaseID(com_product_lang_ext.GetInteger()*100000); + //} + } + + common->SetRefreshOnPrint( true ); + + ListHash listHash; + LoadMapLocalizeData(listHash); + + idStrList excludeList; + LoadGuiParmExcludeList(excludeList); + + if(args.Argc() == 3) { + strCount += LocalizeMap(args.Argv(2), strTable, listHash, excludeList, write); + } else { + idStrList files; + GetFileList("z:/d3xp/d3xp/maps/game", "*.map", files); + for ( int i = 0; i < files.Num(); i++ ) { + idStr file = fileSystem->OSPathToRelativePath(files[i]); + strCount += LocalizeMap(file, strTable, listHash, excludeList, write); + } + } + + if(count) { + common->Printf("Localize String Count: %d\n", strCount); + } + + common->SetRefreshOnPrint( false ); + + if(dictUpdate) { + strTable.Save( filename ); + } +} + +/* +================= +LocalizeGuis_f +================= +*/ +CONSOLE_COMMAND( localizeGuis, "localize guis", NULL ) { + + if ( args.Argc() != 2 ) { + common->Printf( "Usage: localizeGuis \n" ); + return; + } + + idLangDict strTable; + + idStr filename = va("strings/english%.3i.lang", com_product_lang_ext.GetInteger()); + + { + // I think this is equivalent... + const byte * buffer = NULL; + int len = fileSystem->ReadFile( filename, (void**)&buffer ); + if ( verify( len > 0 ) ) { + strTable.Load( buffer, len, filename ); + } + fileSystem->FreeFile( (void *)buffer ); + + // ... to this + //if(strTable.Load( filename ) == false) { + // //This is a new file so set the base index + // strTable.SetBaseID(com_product_lang_ext.GetInteger()*100000); + //} + } + + idFileList *files; + if ( idStr::Icmp( args.Argv(1), "all" ) == 0 ) { + idStr game = cvarSystem->GetCVarString( "game_expansion" ); + if(game.Length()) { + files = fileSystem->ListFilesTree( "guis", "*.gui", true, game ); + } else { + files = fileSystem->ListFilesTree( "guis", "*.gui", true ); + } + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + commonLocal.LocalizeGui( files->GetFile( i ), strTable ); + } + fileSystem->FreeFileList( files ); + + if(game.Length()) { + files = fileSystem->ListFilesTree( "guis", "*.pd", true, game ); + } else { + files = fileSystem->ListFilesTree( "guis", "*.pd", true, "d3xp" ); + } + + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + commonLocal.LocalizeGui( files->GetFile( i ), strTable ); + } + fileSystem->FreeFileList( files ); + + } else { + commonLocal.LocalizeGui( args.Argv(1), strTable ); + } + strTable.Save( filename ); +} + +CONSOLE_COMMAND( localizeGuiParmsTest, "Create test files that show gui parms localized and ignored.", NULL ) { + + common->SetRefreshOnPrint( true ); + + idFile *localizeFile = fileSystem->OpenFileWrite( "gui_parm_localize.csv" ); + idFile *noLocalizeFile = fileSystem->OpenFileWrite( "gui_parm_nolocalize.csv" ); + + idStrList excludeList; + LoadGuiParmExcludeList(excludeList); + + idStrList files; + GetFileList("z:/d3xp/d3xp/maps/game", "*.map", files); + + for ( int i = 0; i < files.Num(); i++ ) { + + common->Printf("Testing Map '%s'\n", files[i].c_str()); + idMapFile map; + + idStr file = fileSystem->OSPathToRelativePath(files[i]); + if ( map.Parse(file, false, false ) ) { + int count = map.GetNumEntities(); + for ( int j = 0; j < count; j++ ) { + idMapEntity *ent = map.GetEntity( j ); + if ( ent ) { + const idKeyValue* kv = ent->epairs.MatchPrefix("gui_parm"); + while( kv ) { + if(TestGuiParm(kv->GetKey(), kv->GetValue(), excludeList)) { + idStr out = va("%s,%s,%s\r\n", kv->GetValue().c_str(), kv->GetKey().c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + } else { + idStr out = va("%s,%s,%s\r\n", kv->GetValue().c_str(), kv->GetKey().c_str(), file.c_str()); + noLocalizeFile->Write( out.c_str(), out.Length() ); + } + kv = ent->epairs.MatchPrefix( "gui_parm", kv ); + } + } + } + } + } + + fileSystem->CloseFile( localizeFile ); + fileSystem->CloseFile( noLocalizeFile ); + + common->SetRefreshOnPrint( false ); +} + + +CONSOLE_COMMAND( localizeMapsTest, "Create test files that shows which strings will be localized.", NULL ) { + + ListHash listHash; + LoadMapLocalizeData(listHash); + + + common->SetRefreshOnPrint( true ); + + idFile *localizeFile = fileSystem->OpenFileWrite( "map_localize.csv" ); + + idStrList files; + GetFileList("z:/d3xp/d3xp/maps/game", "*.map", files); + + for ( int i = 0; i < files.Num(); i++ ) { + + common->Printf("Testing Map '%s'\n", files[i].c_str()); + idMapFile map; + + idStr file = fileSystem->OSPathToRelativePath(files[i]); + if ( map.Parse(file, false, false ) ) { + int count = map.GetNumEntities(); + for ( int j = 0; j < count; j++ ) { + idMapEntity *ent = map.GetEntity( j ); + if ( ent ) { + + //Temp code to get a list of all entity key value pairs + /*idStr classname = ent->epairs.GetString("classname"); + if(classname == "worldspawn" || classname == "func_static" || classname == "light" || classname == "speaker" || classname.Left(8) == "trigger_") { + continue; + } + for( int i = 0; i < ent->epairs.GetNumKeyVals(); i++) { + const idKeyValue* kv = ent->epairs.GetKeyVal(i); + idStr out = va("%s,%s,%s,%s\r\n", classname.c_str(), kv->GetKey().c_str(), kv->GetValue().c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + }*/ + + idStr classname = ent->epairs.GetString("classname"); + + //Hack: for info_location + bool hasLocation = false; + + idStrList* list; + listHash.Get(classname, &list); + if(list) { + + for(int k = 0; k < list->Num(); k++) { + + idStr val = ent->epairs.GetString((*list)[k], ""); + + if(classname == "info_location" && (*list)[k] == "location") { + hasLocation = true; + } + + if(val.Length() && TestMapVal(val)) { + + if(!hasLocation || (*list)[k] == "location") { + idStr out = va("%s,%s,%s\r\n", val.c_str(), (*list)[k].c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + } + } + } + } + + listHash.Get("all", &list); + if(list) { + for(int k = 0; k < list->Num(); k++) { + idStr val = ent->epairs.GetString((*list)[k], ""); + if(val.Length() && TestMapVal(val)) { + idStr out = va("%s,%s,%s\r\n", val.c_str(), (*list)[k].c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + } + } + } + } + } + } + } + + fileSystem->CloseFile( localizeFile ); + + common->SetRefreshOnPrint( false ); +} + +/* +=============== +idCommonLocal::LocalizeSpecificMapData +=============== +*/ +void idCommonLocal::LocalizeSpecificMapData( const char *fileName, idLangDict &langDict, const idLangDict &replaceArgs ) { + idStr out, ws, work; + + idMapFile map; + if ( map.Parse( fileName, false, false ) ) { + int count = map.GetNumEntities(); + for ( int i = 0; i < count; i++ ) { + idMapEntity *ent = map.GetEntity( i ); + if ( ent ) { + for ( int j = 0; j < replaceArgs.GetNumKeyVals(); j++ ) { + const idLangKeyValue *kv = replaceArgs.GetKeyVal( j ); + const char *temp = ent->epairs.GetString( kv->key ); + if ( ( temp != NULL ) && *temp ) { + idStr val = kv->value; + if ( val == temp ) { + ent->epairs.Set( kv->key, langDict.AddString( temp ) ); + } + } + } + } + } + map.Write( fileName, ".map" ); + } +} + +/* +=============== +idCommonLocal::LocalizeMapData +=============== +*/ +void idCommonLocal::LocalizeMapData( const char *fileName, idLangDict &langDict ) { + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + common->SetRefreshOnPrint( true ); + + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + common->Printf( "Processing %s\n", fileName ); + idStr mapFileName; + idToken token, token2; + idLangDict replaceArgs; + while ( src.ReadToken( &token ) ) { + mapFileName = token; + replaceArgs.Clear(); + src.ExpectTokenString( "{" ); + while ( src.ReadToken( &token) ) { + if ( token == "}" ) { + break; + } + if ( src.ReadToken( &token2 ) ) { + if ( token2 == "}" ) { + break; + } + replaceArgs.AddKeyVal( token, token2 ); + } + } + common->Printf( " localizing map %s...\n", mapFileName.c_str() ); + LocalizeSpecificMapData( mapFileName, langDict, replaceArgs ); + } + } + fileSystem->FreeFile( (void*)buffer ); + } + + common->SetRefreshOnPrint( false ); +} + +/* +=============== +idCommonLocal::LocalizeGui +=============== +*/ +void idCommonLocal::LocalizeGui( const char *fileName, idLangDict &langDict ) { + idStr out, ws, work; + const char *buffer = NULL; + out.Empty(); + int k; + char ch; + char slash = '\\'; + char tab = 't'; + char nl = 'n'; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + idFile *outFile = fileSystem->OpenFileWrite( fileName ); + common->Printf( "Processing %s\n", fileName ); + + const bool captureToImage = false; + UpdateScreen( captureToImage ); + idToken token; + while( src.ReadToken( &token ) ) { + src.GetLastWhiteSpace( ws ); + out += ws; + if ( token.type == TT_STRING ) { + out += va( "\"%s\"", token.c_str() ); + } else { + out += token; + } + if ( out.Length() > 200000 ) { + outFile->Write( out.c_str(), out.Length() ); + out = ""; + } + work = token.Right( 6 ); + if ( token.Icmp( "text" ) == 0 || work.Icmp( "::text" ) == 0 || token.Icmp( "choices" ) == 0 ) { + if ( src.ReadToken( &token ) ) { + // see if already exists, if so save that id to this position in this file + // otherwise add this to the list and save the id to this position in this file + src.GetLastWhiteSpace( ws ); + out += ws; + token = langDict.AddString( token ); + out += "\""; + for ( k = 0; k < token.Length(); k++ ) { + ch = token[k]; + if ( ch == '\t' ) { + out += slash; + out += tab; + } else if ( ch == '\n' || ch == '\r' ) { + out += slash; + out += nl; + } else { + out += ch; + } + } + out += "\""; + } + } else if ( token.Icmp( "comment" ) == 0 ) { + if ( src.ReadToken( &token ) ) { + // need to write these out by hand to preserve any \n's + // see if already exists, if so save that id to this position in this file + // otherwise add this to the list and save the id to this position in this file + src.GetLastWhiteSpace( ws ); + out += ws; + out += "\""; + for ( k = 0; k < token.Length(); k++ ) { + ch = token[k]; + if ( ch == '\t' ) { + out += slash; + out += tab; + } else if ( ch == '\n' || ch == '\r' ) { + out += slash; + out += nl; + } else { + out += ch; + } + } + out += "\""; + } + } + } + outFile->Write( out.c_str(), out.Length() ); + fileSystem->CloseFile( outFile ); + } + fileSystem->FreeFile( (void*)buffer ); + } +} diff --git a/neo/framework/Common_menu.cpp b/neo/framework/Common_menu.cpp new file mode 100644 index 00000000..c7d348c6 --- /dev/null +++ b/neo/framework/Common_menu.cpp @@ -0,0 +1,188 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" + +/* +============== +idCommonLocal::InitializeMPMapsModes +============== +*/ +void idCommonLocal::InitializeMPMapsModes() { + + const char ** gameModes = NULL; + const char ** gameModesDisplay = NULL; + int numModes = game->GetMPGameModes( &gameModes, &gameModesDisplay ); + mpGameModes.SetNum( numModes ); + for ( int i = 0; i < numModes; i++ ) { + mpGameModes[i] = gameModes[i]; + } + mpDisplayGameModes.SetNum( numModes ); + for ( int i = 0; i < numModes; i++ ) { + mpDisplayGameModes[i] = gameModesDisplay[i]; + } + int numMaps = declManager->GetNumDecls( DECL_MAPDEF ); + mpGameMaps.Clear(); + for ( int i = 0; i < numMaps; i++ ) { + const idDeclEntityDef * mapDef = static_cast( declManager->DeclByIndex( DECL_MAPDEF, i ) ); + uint32 supportedModes = 0; + for ( int j = 0; j < numModes; j++ ) { + if ( mapDef->dict.GetBool( gameModes[j], false ) ) { + supportedModes |= BIT(j); + } + } + if ( supportedModes != 0 ) { + mpMap_t & mpMap = mpGameMaps.Alloc(); + mpMap.mapFile = mapDef->GetName(); + mpMap.mapName = mapDef->dict.GetString( "name", mpMap.mapFile ); + mpMap.supportedModes = supportedModes; + } + } +} + +/* +============== +idCommonLocal::OnStartHosting +============== +*/ +void idCommonLocal::OnStartHosting( idMatchParameters & parms ) { + if ( ( parms.matchFlags & MATCH_REQUIRE_PARTY_LOBBY ) == 0 ) { + return; // This is the party lobby or a SP match + } + + // If we were searching for a random match but didn't find one, we'll need to select parameters now + if ( parms.gameMap < 0 ) { + if ( parms.gameMode < 0 ) { + // Random mode means any map will do + parms.gameMap = idLib::frameNumber % mpGameMaps.Num(); + } else { + // Select a map which supports the chosen mode + idList supportedMaps; + uint32 supportedMode = BIT( parms.gameMode ); + for ( int i = 0; i < mpGameMaps.Num(); i++ ) { + if ( mpGameMaps[i].supportedModes & supportedMode ) { + supportedMaps.Append( i ); + } + } + if ( supportedMaps.Num() == 0 ) { + // We don't have any maps which support the chosen mode... + parms.gameMap = idLib::frameNumber % mpGameMaps.Num(); + parms.gameMode = -1; + } else { + parms.gameMap = supportedMaps[ idLib::frameNumber % supportedMaps.Num() ]; + } + } + } + if ( parms.gameMode < 0 ) { + uint32 supportedModes = mpGameMaps[parms.gameMap].supportedModes; + int8 supportedModeList[32] = {}; + int numSupportedModes = 0; + for ( int i = 0; i < 32; i++ ) { + if ( supportedModes & BIT(i) ) { + supportedModeList[numSupportedModes] = i; + numSupportedModes++; + } + } + parms.gameMode = supportedModeList[ ( idLib::frameNumber / mpGameMaps.Num() ) % numSupportedModes ]; + } + parms.mapName = mpGameMaps[parms.gameMap].mapFile; + parms.numSlots = session->GetTitleStorageInt("MAX_PLAYERS_ALLOWED", 4 ); +} + +/* +============== +idCommonLocal::StartMainMenu +============== +*/ +void idCommonLocal::StartMenu( bool playIntro ) { + if ( game && game->Shell_IsActive() ) { + return; + } + + if ( readDemo ) { + // if we're playing a demo, esc kills it + UnloadMap(); + } + + if ( game ) { + game->Shell_Show( true ); + game->Shell_SyncWithSession(); + } + + console->Close(); + +} + +/* +=============== +idCommonLocal::ExitMenu +=============== +*/ +void idCommonLocal::ExitMenu() { + if ( game ) { + game->Shell_Show( false ); + } +} + +/* +============== +idCommonLocal::MenuEvent + +Executes any commands returned by the gui +============== +*/ +bool idCommonLocal::MenuEvent( const sysEvent_t * event ) { + + if ( session->GetSignInManager().ProcessInputEvent( event ) ) { + return true; + } + + if ( game && game->Shell_IsActive() ) { + return game->Shell_HandleGuiEvent( event ); + } + + if ( game ) { + return game->HandlePlayerGuiEvent( event ); + } + + return false; +} + +/* +================= +idCommonLocal::GuiFrameEvents +================= +*/ +void idCommonLocal::GuiFrameEvents() { + if ( game ) { + game->Shell_SyncWithSession(); + } +} diff --git a/neo/framework/Common_network.cpp b/neo/framework/Common_network.cpp new file mode 100644 index 00000000..f3318d6c --- /dev/null +++ b/neo/framework/Common_network.cpp @@ -0,0 +1,639 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" + +idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "5000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); +idCVar net_snapRate( "net_snapRate", "100", CVAR_SYSTEM | CVAR_INTEGER, "How many milliseconds between sending snapshots" ); +idCVar net_ucmdRate( "net_ucmdRate", "40", CVAR_SYSTEM | CVAR_INTEGER, "How many milliseconds between sending usercmds" ); + +idCVar net_debug_snapShotTime( "net_debug_snapShotTime", "0", CVAR_BOOL | CVAR_ARCHIVE, "" ); +idCVar com_forceLatestSnap( "com_forceLatestSnap", "0", CVAR_BOOL, "" ); + +// Enables effective snap rate: dynamically adjust the client snap rate based on: +// -client FPS +// -server FPS (interpolated game time received / interval it was received over) +// -local buffered time (leave a cushion to absorb spikes, slow down when infront of it, speed up when behind it) ie: net_minBufferedSnapPCT_Static +idCVar net_effectiveSnapRateEnable( "net_effectiveSnapRateEnable", "1", CVAR_BOOL, "Dynamically adjust client snaprate"); +idCVar net_effectiveSnapRateDebug( "net_effectiveSnapRateDebug", "0", CVAR_BOOL, "Debug"); + +// Min buffered snapshot time to keep as a percentage of the effective snaprate +// -ie we want to keep 50% of the amount of time difference between last two snaps. +// -we need to scale this because we may get throttled at the snaprate may change +// -Acts as a buffer to absorb spikes +idCVar net_minBufferedSnapPCT_Static( "net_minBufferedSnapPCT_Static", "1.0", CVAR_FLOAT, "Min amount of snapshot buffer time we want need to buffer"); +idCVar net_maxBufferedSnapMS( "net_maxBufferedSnapMS", "336", CVAR_INTEGER, "Max time to allow for interpolation cushion"); +idCVar net_minBufferedSnapWinPCT_Static( "net_minBufferedSnapWinPCT_Static", "1.0", CVAR_FLOAT, "Min amount of snapshot buffer time we want need to buffer"); + +// Factor at which we catch speed up interpolation if we fall behind our optimal interpolation window +// -This is a static factor. We may experiment with a dynamic one that would be faster the farther you are from the ideal window +idCVar net_interpolationCatchupRate( "net_interpolationCatchupRate", "1.3", CVAR_FLOAT, "Scale interpolationg rate when we fall behind"); +idCVar net_interpolationFallbackRate( "net_interpolationFallbackRate", "0.95", CVAR_FLOAT, "Scale interpolationg rate when we fall behind"); +idCVar net_interpolationBaseRate( "net_interpolationBaseRate", "1.0", CVAR_FLOAT, "Scale interpolationg rate when we fall behind"); + +// Enabled a dynamic ideal snap buffer window: we will scale the distance and size +idCVar net_optimalDynamic( "net_optimalDynamic", "1", CVAR_BOOL, "How fast to add to our optimal time buffer when we are playing snapshots faster than server is feeding them to us"); + +// These values are used instead if net_optimalDynamic is 0 (don't scale by actual snap rate/interval) +idCVar net_optimalSnapWindow( "net_optimalSnapWindow", "112", CVAR_FLOAT, ""); +idCVar net_optimalSnapTime( "net_optimalSnapTime", "112", CVAR_FLOAT, "How fast to add to our optimal time buffer when we are playing snapshots faster than server is feeding them to us"); + +// this is at what percentage of being ahead of the interpolation buffer that we start slowing down (we ramp down from 1.0 to 0.0 starting here) +// this is a percentage of the total cushion time. +idCVar net_interpolationSlowdownStart( "net_interpolationSlowdownStart", "0.5", CVAR_FLOAT, "Scale interpolation rate when we fall behind"); + + +// Extrapolation is now disabled +idCVar net_maxExtrapolationInMS( "net_maxExtrapolationInMS", "0", CVAR_INTEGER, "Max time in MS that extrapolation is allowed to occur."); + +static const int SNAP_USERCMDS = 8192; + +/* +=============== +idCommonLocal::IsMultiplayer +=============== +*/ +bool idCommonLocal::IsMultiplayer() { + idLobbyBase & lobby = session->GetPartyLobbyBase(); + return ( ( ( lobby.GetMatchParms().matchFlags & MATCH_ONLINE ) != 0 ) && ( session->GetState() > idSession::IDLE ) ); +} + +/* +=============== +idCommonLocal::IsServer +=============== +*/ +bool idCommonLocal::IsServer() { + return IsMultiplayer() && session->GetActingGameStateLobbyBase().IsHost(); +} + +/* +=============== +idCommonLocal::IsClient +=============== +*/ +bool idCommonLocal::IsClient() { + return IsMultiplayer() && session->GetActingGameStateLobbyBase().IsPeer(); +} + +/* +=============== +idCommonLocal::SendSnapshots +=============== +*/ +int idCommonLocal::GetSnapRate() { + return net_snapRate.GetInteger(); +} + +/* +=============== +idCommonLocal::SendSnapshots +=============== +*/ +void idCommonLocal::SendSnapshots() { + if ( !mapSpawned ) { + return; + } + int currentTime = Sys_Milliseconds(); + if ( currentTime < nextSnapshotSendTime ) { + return; + } + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + if ( !lobby.IsHost() ) { + return; + } + if ( !lobby.HasActivePeers() ) { + return; + } + idSnapShot ss; + game->ServerWriteSnapshot( ss ); + + session->SendSnapshot( ss ); + nextSnapshotSendTime = MSEC_ALIGN_TO_FRAME( currentTime + net_snapRate.GetInteger() ); +} + +/* +=============== +idCommonLocal::NetReceiveSnapshot +=============== +*/ +void idCommonLocal::NetReceiveSnapshot( class idSnapShot & ss ) { + ss.SetRecvTime( Sys_Milliseconds() ); + // If we are about to overwrite the oldest snap, then force a read, which will cause a pop on screen, but we have to do this. + if ( writeSnapshotIndex - readSnapshotIndex >= RECEIVE_SNAPSHOT_BUFFER_SIZE ) { + idLib::Printf( "Overwritting oldest snapshot %d with new snapshot %d\n", readSnapshotIndex, writeSnapshotIndex ); + assert( writeSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE == readSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE ); + ProcessNextSnapshot(); + } + + receivedSnaps[ writeSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE ] = ss; + writeSnapshotIndex++; + + // Force read the very first 2 snapshots + if ( readSnapshotIndex < 2 ) { + ProcessNextSnapshot(); + } +} + +/* +=============== +idCommonLocal::SendUsercmd +=============== +*/ +void idCommonLocal::SendUsercmds( int localClientNum ) { + if ( !mapSpawned ) { + return; + } + int currentTime = Sys_Milliseconds(); + if ( currentTime < nextUsercmdSendTime ) { + return; + } + idLobbyBase & lobby = session->GetActingGameStateLobbyBase(); + if ( lobby.IsHost() ) { + return; + } + // We always send the last NUM_USERCMD_SEND usercmds + // Which may result in duplicate usercmds being sent in the case of a low net_ucmdRate + // But the LZW compressor means the extra usercmds are not large and the redundancy can smooth packet loss + byte buffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE]; + idBitMsg msg( buffer, sizeof( buffer ) ); + idSerializer ser( msg, true ); + usercmd_t empty; + usercmd_t * last = ∅ + + usercmd_t * cmdBuffer[NUM_USERCMD_SEND]; + const int numCmds = userCmdMgr.GetPlayerCmds( localClientNum, cmdBuffer, NUM_USERCMD_SEND ); + msg.WriteByte( numCmds ); + for ( int i = 0; i < numCmds; i++ ) { + cmdBuffer[i]->Serialize( ser, *last ); + + last = cmdBuffer[i]; + } + session->SendUsercmds( msg ); + + nextUsercmdSendTime = MSEC_ALIGN_TO_FRAME( currentTime + net_ucmdRate.GetInteger() ); +} + +/* +=============== +idCommonLocal::NetReceiveUsercmds +=============== +*/ +void idCommonLocal::NetReceiveUsercmds( int peer, idBitMsg & msg ) { + int clientNum = Game()->MapPeerToClient( peer ); + if ( clientNum == -1 ) { + idLib::Warning( "NetReceiveUsercmds: Could not find client for peer %d", peer ); + return; + } + + NetReadUsercmds( clientNum, msg ); +} + +/* +=============== +idCommonLocal::NetReceiveReliable +=============== +*/ +void idCommonLocal::NetReceiveReliable( int peer, int type, idBitMsg & msg ) { + int clientNum = Game()->MapPeerToClient( peer ); + // Only servers care about the client num. Band-aid for problems related to the host's peerIndex being -1 on clients. + if ( common->IsServer() && clientNum == -1 ) { + idLib::Warning( "NetReceiveReliable: Could not find client for peer %d", peer ); + return; + } + + const byte * msgData = msg.GetReadData() + msg.GetReadCount(); + int msgSize = msg.GetRemainingData(); + reliableMsg_t & reliable = reliableQueue.Alloc(); + reliable.client = clientNum; + reliable.type = type; + reliable.dataSize = msgSize; + reliable.data = (byte *)Mem_Alloc( msgSize, TAG_NETWORKING ); + memcpy( reliable.data, msgData, msgSize ); +} + +/* +======================== +idCommonLocal::ProcessSnapshot +======================== +*/ +void idCommonLocal::ProcessSnapshot( idSnapShot & ss ) { + int time = Sys_Milliseconds(); + + snapTime = time; + snapPrevious = snapCurrent; + snapCurrent.serverTime = ss.GetTime(); + snapRate = snapCurrent.serverTime - snapPrevious.serverTime; + + + static int lastReceivedLocalTime = 0; + int timeSinceLastSnap = ( time - lastReceivedLocalTime ); + if ( net_debug_snapShotTime.GetBool() ) { + idLib::Printf( "^2ProcessSnapshot. delta serverTime: %d delta localTime: %d \n", ( snapCurrent.serverTime-snapPrevious.serverTime ), timeSinceLastSnap ); + } + lastReceivedLocalTime = time; + + /* JAF ? + for ( int i = 0; i < MAX_PLAYERS; i++ ) { + idBitMsg msg; + if ( ss.GetObjectMsgByID( idSession::SS_PLAYER + i, msg ) ) { + if ( msg.GetSize() == 0 ) { + snapCurrent.players[ i ].valid = false; + continue; + } + + idSerializer ser( msg, false ); + SerializePlayer( ser, snapCurrent.players[ i ] ); + snapCurrent.players[ i ].valid = true; + + extern idCVar com_drawSnapshots; + if ( com_drawSnapshots.GetInteger() == 3 ) { + console->AddSnapObject( "players", msg.GetSize(), ss.CompareObject( &oldss, idSession::SS_PLAYER + i ) ); + } + } + } + */ + + // Read usercmds from other players + for ( int p = 0; p < MAX_PLAYERS; p++ ) { + if ( p == game->GetLocalClientNum() ) { + continue; + } + idBitMsg msg; + if ( ss.GetObjectMsgByID( SNAP_USERCMDS + p, msg ) ) { + NetReadUsercmds( p, msg ); + } + } + + + + + // Set server game time here so that it accurately reflects the time when this frame was saved out, in case any serialize function needs it. + int oldTime = Game()->GetServerGameTimeMs(); + Game()->SetServerGameTimeMs( snapCurrent.serverTime ); + + Game()->ClientReadSnapshot( ss ); //, &oldss ); + + // Restore server game time + Game()->SetServerGameTimeMs( oldTime ); + + snapTimeDelta = ss.GetRecvTime() - oldss.GetRecvTime(); + oldss = ss; +} + +/* +======================== +idCommonLocal::NetReadUsercmds +======================== +*/ +void idCommonLocal::NetReadUsercmds( int clientNum, idBitMsg & msg ) { + if ( clientNum == -1 ) { + idLib::Warning( "NetReadUsercmds: Trying to read commands from invalid clientNum %d", clientNum ); + return; + } + + // TODO: This shouldn't actually happen. Figure out why it does. + // Seen on clients when another client leaves a match. + if ( msg.GetReadData() == NULL ) { + return; + } + + idSerializer ser( msg, false ); + + usercmd_t fakeCmd; + usercmd_t * base = &fakeCmd; + + usercmd_t lastCmd; + + bool gotNewCmd = false; + idStaticList< usercmd_t, NUM_USERCMD_RELAY > newCmdBuffer; + + usercmd_t baseCmd = userCmdMgr.NewestUserCmdForPlayer( clientNum ); + int curMilliseconds = baseCmd.clientGameMilliseconds; + + const int numCmds = msg.ReadByte(); + + for ( int i = 0; i < numCmds; i++ ) { + usercmd_t newCmd; + newCmd.Serialize( ser, *base ); + + lastCmd = newCmd; + base = &lastCmd; + + int newMilliseconds = newCmd.clientGameMilliseconds; + + if ( newMilliseconds > curMilliseconds ) { + if ( verify( i < NUM_USERCMD_RELAY ) ) { + newCmdBuffer.Append( newCmd ); + gotNewCmd = true; + curMilliseconds = newMilliseconds; + } + } + } + + // Push the commands into the buffer. + for ( int i = 0; i < newCmdBuffer.Num(); ++i ) { + userCmdMgr.PutUserCmdForPlayer( clientNum, newCmdBuffer[i] ); + } +} + +/* +======================== +idCommonLocal::ProcessNextSnapshot +======================== +*/ +void idCommonLocal::ProcessNextSnapshot() { + if ( readSnapshotIndex == writeSnapshotIndex ) { + idLib::Printf("No snapshots to process.\n"); + return; // No snaps to process + } + ProcessSnapshot( receivedSnaps[ readSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE ] ); + readSnapshotIndex++; +} + +/* +======================== +idCommonLocal::CalcSnapTimeBuffered +Return the amount of game time left of buffered snapshots +totalBufferedTime - total amount of snapshot time (includng what we've already past in current interpolate) +totalRecvTime - total real time (sys_milliseconds) all of totalBufferedTime was received over +======================== +*/ +int idCommonLocal::CalcSnapTimeBuffered( int & totalBufferedTime, int & totalRecvTime ) { + + totalBufferedTime = snapRate; + totalRecvTime = snapTimeDelta; + + // oldSS = last ss we deserialized + int lastBuffTime = oldss.GetTime(); + int lastRecvTime = oldss.GetRecvTime(); + + // receivedSnaps[readSnapshotIndex % RECEIVE_SNAPSHOT_BUFFER_SIZE] = next buffered snapshot we haven't processed yet (might not exist) + for ( int i = readSnapshotIndex; i < writeSnapshotIndex; i++ ) { + int buffTime = receivedSnaps[i % RECEIVE_SNAPSHOT_BUFFER_SIZE].GetTime(); + int recvTime = receivedSnaps[i % RECEIVE_SNAPSHOT_BUFFER_SIZE].GetRecvTime(); + + totalBufferedTime += buffTime - lastBuffTime; + totalRecvTime += recvTime - lastRecvTime; + + lastRecvTime = recvTime; + lastBuffTime = buffTime; + } + + totalRecvTime = Max( 1, totalRecvTime ); + totalRecvTime = static_cast( initialBaseTicksPerSec ) * static_cast( totalRecvTime / 1000.0f ); // convert realMS to gameMS + + // remove time we've already interpolated over + int timeLeft = totalBufferedTime - Min< int >( snapRate, snapCurrentTime ); + + //idLib::Printf( "CalcSnapTimeBuffered. timeLeft: %d totalRecvTime: %d, totalTimeBuffered: %d\n", timeLeft, totalRecvTime, totalBufferedTime ); + return timeLeft; +} + +/* +======================== +idCommonLocal::InterpolateSnapshot +======================== +*/ +void idCommonLocal::InterpolateSnapshot( netTimes_t & prev, netTimes_t & next, float fraction, bool predict ) { + + int serverTime = Lerp( prev.serverTime, next.serverTime, fraction ); + + Game()->SetServerGameTimeMs( serverTime ); // Set the global server time to the interpolated time of the server + Game()->SetInterpolation( fraction, serverTime, prev.serverTime, next.serverTime ); + + //Game()->RunFrame( &userCmdMgr, &ret, true ); + +} + +/* +======================== +idCommonLocal::RunNetworkSnapshotFrame +======================== +*/ +void idCommonLocal::RunNetworkSnapshotFrame() { + + // Process any reliable messages we've received + for ( int i = 0; i < reliableQueue.Num(); i++ ) { + game->ProcessReliableMessage( reliableQueue[i].client, reliableQueue[i].type, idBitMsg( (const byte *)reliableQueue[i].data, reliableQueue[i].dataSize ) ); + Mem_Free( reliableQueue[i].data ); + } + reliableQueue.Clear(); + + // abuse the game timing to time presentable thinking on clients + time_gameFrame = Sys_Microseconds(); + time_maxGameFrame = 0; + count_numGameFrames = 0; + + if ( snapPrevious.serverTime >= 0 ) { + + int msec_interval = 1 + idMath::Ftoi( (float)initialBaseTicksPerSec ); + + static int clientTimeResidual = 0; + static int lastTime = Sys_Milliseconds(); + int currentTime = Sys_Milliseconds(); + int deltaFrameTime = idMath::ClampInt( 1, 33, currentTime - lastTime ); + + clientTimeResidual += idMath::ClampInt( 0, 50, currentTime - lastTime ); + lastTime = currentTime; + + extern idCVar com_fixedTic; + if ( com_fixedTic.GetBool() ) { + clientTimeResidual = 0; + } + + do { + // If we are extrapolating and have fresher snapshots, then use the freshest one + while ( ( snapCurrentTime >= snapRate || com_forceLatestSnap.GetBool() ) && readSnapshotIndex < writeSnapshotIndex ) { + snapCurrentTime -= snapRate; + ProcessNextSnapshot(); + } + + // this only matters when running < 60 fps + // JAF Game()->GetRenderWorld()->UpdateDeferredPositions(); + + // Clamp the current time so that it doesn't fall outside of our extrapolation bounds + snapCurrentTime = idMath::ClampInt( 0, snapRate + Min( (int)snapRate, (int)net_maxExtrapolationInMS.GetInteger() ), snapCurrentTime ); + + if ( snapRate <= 0 ) { + idLib::Warning("snapRate <= 0. Resetting to 100"); + snapRate = 100; + } + + float fraction = (float)snapCurrentTime / (float)snapRate; + if ( !IsValid( fraction ) ) { + idLib::Warning("Interpolation Fraction invalid: snapCurrentTime %d / snapRate %d", (int)snapCurrentTime, (int)snapRate ); + fraction = 0.0f; + } + + InterpolateSnapshot( snapPrevious, snapCurrent, fraction, true ); + + // Default to a snap scale of 1 + float snapRateScale = net_interpolationBaseRate.GetFloat(); + + snapTimeBuffered = CalcSnapTimeBuffered( totalBufferedTime, totalRecvTime ); + effectiveSnapRate = static_cast< float > ( totalBufferedTime ) / static_cast< float > ( totalRecvTime ); + + if ( net_minBufferedSnapPCT_Static.GetFloat() > 0.0f ) { + optimalPCTBuffer = session->GetTitleStorageFloat( "net_minBufferedSnapPCT_Static", net_minBufferedSnapPCT_Static.GetFloat() ); + } + + // Calculate optimal amount of buffered time we want + if ( net_optimalDynamic.GetBool() ) { + optimalTimeBuffered = idMath::ClampInt( 0, net_maxBufferedSnapMS.GetInteger(), snapRate * optimalPCTBuffer ); + optimalTimeBufferedWindow = snapRate * net_minBufferedSnapWinPCT_Static.GetFloat(); + } else { + optimalTimeBuffered = net_optimalSnapTime.GetFloat(); + optimalTimeBufferedWindow = net_optimalSnapWindow.GetFloat(); + } + + // Scale snapRate based on where we are in the buffer + if ( snapTimeBuffered <= optimalTimeBuffered ) { + if ( snapTimeBuffered <= idMath::FLT_SMALLEST_NON_DENORMAL ) { + snapRateScale = 0; + } else { + snapRateScale = net_interpolationFallbackRate.GetFloat(); + // When we interpolate past our cushion of buffered snapshot, we want to slow smoothly slow the + // rate of interpolation. frac will go from 1.0 to 0.0 (if snapshots stop coming in). + float startSlowdown = ( net_interpolationSlowdownStart.GetFloat() * optimalTimeBuffered ); + if ( startSlowdown > 0 && snapTimeBuffered < startSlowdown ) { + float frac = idMath::ClampFloat( 0.0f, 1.0f, snapTimeBuffered / startSlowdown ); + if ( !IsValid( frac ) ) { + frac = 0.0f; + } + snapRateScale = Square( frac ) * snapRateScale; + if ( !IsValid( snapRateScale ) ) { + snapRateScale = 0.0f; + } + } + } + + + } else if ( snapTimeBuffered > optimalTimeBuffered + optimalTimeBufferedWindow ) { + // Go faster + snapRateScale = net_interpolationCatchupRate.GetFloat(); + + } + + float delta_interpolate = (float)initialBaseTicksPerSec * snapRateScale; + if ( net_effectiveSnapRateEnable.GetBool() ) { + + float deltaFrameGameMS = static_cast( initialBaseTicksPerSec ) * static_cast( deltaFrameTime / 1000.0f ); + delta_interpolate = ( deltaFrameGameMS * snapRateScale * effectiveSnapRate ) + snapCurrentResidual; + if ( !IsValid( delta_interpolate ) ) { + delta_interpolate = 0.0f; + } + + snapCurrentResidual = idMath::Frac( delta_interpolate ); // fixme: snapCurrentTime should just be a float, but would require changes in d4 too + if ( !IsValid( snapCurrentResidual ) ) { + snapCurrentResidual = 0.0f; + } + + if ( net_effectiveSnapRateDebug.GetBool() ) { + idLib::Printf("%d/%.2f snapRateScale: %.2f effectiveSR: %.2f d.interp: %.2f snapTimeBuffered: %.2f res: %.2f\n", deltaFrameTime, deltaFrameGameMS, snapRateScale, effectiveSnapRate, delta_interpolate, snapTimeBuffered, snapCurrentResidual ); + } + } + + assert( IsValid( delta_interpolate ) ); + int interpolate_interval = idMath::Ftoi( delta_interpolate ); + + snapCurrentTime += interpolate_interval; // advance interpolation time by the scaled interpolate_interval + clientTimeResidual -= msec_interval; // advance local client residual time (fixed step) + + } while ( clientTimeResidual >= msec_interval ); + + if ( clientTimeResidual < 0 ) { + clientTimeResidual = 0; + } + } + + time_gameFrame = Sys_Microseconds() - time_gameFrame; +} + +/* +======================== +idCommonLocal::ExecuteReliableMessages +======================== +*/ +void idCommonLocal::ExecuteReliableMessages() { + + // Process any reliable messages we've received + for ( int i = 0; i < reliableQueue.Num(); i++ ) { + reliableMsg_t & reliable = reliableQueue[i]; + game->ProcessReliableMessage( reliable.client, reliable.type, idBitMsg( (const byte *)reliable.data, reliable.dataSize ) ); + Mem_Free( reliable.data ); + } + reliableQueue.Clear(); + +} + +/* +======================== +idCommonLocal::ResetNetworkingState +======================== +*/ +void idCommonLocal::ResetNetworkingState() { + snapTime = 0; + snapTimeWrite = 0; + snapCurrentTime = 0; + snapCurrentResidual = 0.0f; + + snapTimeBuffered = 0.0f; + effectiveSnapRate = 0.0f; + totalBufferedTime = 0; + totalRecvTime = 0; + + readSnapshotIndex = 0; + writeSnapshotIndex = 0; + snapRate = 100000; + optimalTimeBuffered = 0.0f; + optimalPCTBuffer = 0.5f; + optimalTimeBufferedWindow = 0.0; + + // Clear snapshot queue + for ( int i = 0; i < RECEIVE_SNAPSHOT_BUFFER_SIZE; i++ ) { + receivedSnaps[i].Clear(); + } + + userCmdMgr.SetDefaults(); + + snapCurrent.localTime = -1; + snapPrevious.localTime = -1; + snapCurrent.serverTime = -1; + snapPrevious.serverTime = -1; + + // Make sure our current snap state is cleared so state from last game doesn't carry over into new game + oldss.Clear(); + + gameFrame = 0; + clientPrediction = 0; + nextUsercmdSendTime = 0; + nextSnapshotSendTime = 0; +} diff --git a/neo/framework/Common_printf.cpp b/neo/framework/Common_printf.cpp new file mode 100644 index 00000000..b79c4df6 --- /dev/null +++ b/neo/framework/Common_printf.cpp @@ -0,0 +1,543 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" + +idCVar com_logFile( "logFile", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "1 = buffer log, 2 = flush after each print", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar com_logFileName( "logFileName", "qconsole.log", CVAR_SYSTEM | CVAR_NOCHEAT, "name of log file, if empty, qconsole.log will be used" ); +idCVar com_timestampPrints( "com_timestampPrints", "0", CVAR_SYSTEM, "print time with each console print, 1 = msec, 2 = sec", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + +#ifndef ID_RETAIL +idCVar com_printFilter( "com_printFilter", "", CVAR_SYSTEM, "only print lines that contain this, add multiple filters with a ; delimeter"); +#endif + +/* +================== +idCommonLocal::BeginRedirect +================== +*/ +void idCommonLocal::BeginRedirect( char *buffer, int buffersize, void (*flush)( const char *) ) { + if ( !buffer || !buffersize || !flush ) { + return; + } + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +/* +================== +idCommonLocal::EndRedirect +================== +*/ +void idCommonLocal::EndRedirect() { + if ( rd_flush && rd_buffer[ 0 ] ) { + rd_flush( rd_buffer ); + } + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} + +/* +================== +idCommonLocal::CloseLogFile +================== +*/ +void idCommonLocal::CloseLogFile() { + if ( logFile ) { + com_logFile.SetBool( false ); // make sure no further VPrintf attempts to open the log file again + fileSystem->CloseFile( logFile ); + logFile = NULL; + } +} + +/* +================== +idCommonLocal::SetRefreshOnPrint +================== +*/ +void idCommonLocal::SetRefreshOnPrint( bool set ) { + com_refreshOnPrint = set; +} + +/* +================== +idCommonLocal::VPrintf + +A raw string should NEVER be passed as fmt, because of "%f" type crashes. +================== +*/ +void idCommonLocal::VPrintf( const char *fmt, va_list args ) { + static bool logFileFailed = false; + + // if the cvar system is not initialized + if ( !cvarSystem->IsInitialized() ) { + return; + } + // optionally put a timestamp at the beginning of each print, + // so we can see how long different init sections are taking + int timeLength = 0; + char msg[MAX_PRINT_MSG_SIZE]; + msg[ 0 ] = '\0'; + if ( com_timestampPrints.GetInteger() ) { + int t = Sys_Milliseconds(); + if ( com_timestampPrints.GetInteger() == 1 ) { + sprintf( msg, "[%5.2f]", t * 0.001f ); + } else { + sprintf( msg, "[%i]", t ); + } + } + timeLength = strlen( msg ); + // don't overflow + if ( idStr::vsnPrintf( msg+timeLength, MAX_PRINT_MSG_SIZE-timeLength-1, fmt, args ) < 0 ) { + msg[sizeof(msg)-2] = '\n'; msg[sizeof(msg)-1] = '\0'; // avoid output garbling + Sys_Printf( "idCommon::VPrintf: truncated to %d characters\n", strlen(msg)-1 ); + } + + if ( rd_buffer ) { + if ( (int)( strlen( msg ) + strlen( rd_buffer ) ) > ( rd_buffersize - 1 ) ) { + rd_flush( rd_buffer ); + *rd_buffer = 0; + } + strcat( rd_buffer, msg ); + return; + } +#ifndef ID_RETAIL + if ( com_printFilter.GetString() != NULL && com_printFilter.GetString()[ 0 ] != '\0' ) { + idStrStatic< 4096 > filterBuf = com_printFilter.GetString(); + idStrStatic< 4096 > msgBuf = msg; + filterBuf.ToLower(); + msgBuf.ToLower(); + char *sp = strtok( &filterBuf[ 0 ], ";" ); + bool p = false; + for( ; sp != NULL ; ) { + if ( strstr( msgBuf, sp ) != NULL ) { + p = true; + break; + } + sp = strtok( NULL, ";" ); + } + if ( !p ) { + return; + } + } +#endif + if ( !idLib::IsMainThread() ) { + OutputDebugString( msg ); + return; + } + + // echo to console buffer + console->Print( msg ); + + // remove any color codes + idStr::RemoveColors( msg ); + + // echo to dedicated console and early console + Sys_Printf( "%s", msg ); + + // print to script debugger server + // DebuggerServerPrint( msg ); + +#if 0 // !@# +#if defined(_DEBUG) && defined(WIN32) + if ( strlen( msg ) < 512 ) { + TRACE( msg ); + } +#endif +#endif + + // logFile + if ( com_logFile.GetInteger() && !logFileFailed && fileSystem->IsInitialized() ) { + static bool recursing; + + if ( !logFile && !recursing ) { + const char *fileName = com_logFileName.GetString()[0] ? com_logFileName.GetString() : "qconsole.log"; + + // fileSystem->OpenFileWrite can cause recursive prints into here + recursing = true; + + logFile = fileSystem->OpenFileWrite( fileName ); + if ( !logFile ) { + logFileFailed = true; + FatalError( "failed to open log file '%s'\n", fileName ); + } + + recursing = false; + + if ( com_logFile.GetInteger() > 1 ) { + // force it to not buffer so we get valid + // data even if we are crashing + logFile->ForceFlush(); + } + + time_t aclock; + time( &aclock ); + struct tm * newtime = localtime( &aclock ); + Printf( "log file '%s' opened on %s\n", fileName, asctime( newtime ) ); + } + if ( logFile ) { + logFile->Write( msg, strlen( msg ) ); + logFile->Flush(); // ForceFlush doesn't help a whole lot + } + } + + // don't trigger any updates if we are in the process of doing a fatal error + if ( com_errorEntered != ERP_FATAL ) { + // update the console if we are in a long-running command, like dmap + if ( com_refreshOnPrint ) { + const bool captureToImage = false; + UpdateScreen( captureToImage ); + } + } +} + +/* +================== +idCommonLocal::Printf + +Both client and server can use this, and it will output to the appropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +================== +*/ +void idCommonLocal::Printf( const char *fmt, ... ) { + va_list argptr; + va_start( argptr, fmt ); + VPrintf( fmt, argptr ); + va_end( argptr ); +} + +/* +================== +idCommonLocal::DPrintf + +prints message that only shows up if the "developer" cvar is set +================== +*/ +void idCommonLocal::DPrintf( const char *fmt, ... ) { + va_list argptr; + char msg[MAX_PRINT_MSG_SIZE]; + + if ( !cvarSystem->IsInitialized() || !com_developer.GetBool() ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + msg[sizeof(msg)-1] = '\0'; + + // never refresh the screen, which could cause reentrency problems + bool temp = com_refreshOnPrint; + com_refreshOnPrint = false; + + Printf( S_COLOR_RED"%s", msg ); + + com_refreshOnPrint = temp; +} + +/* +================== +idCommonLocal::DWarning + +prints warning message in yellow that only shows up if the "developer" cvar is set +================== +*/ +void idCommonLocal::DWarning( const char *fmt, ... ) { + va_list argptr; + char msg[MAX_PRINT_MSG_SIZE]; + + if ( !com_developer.GetBool() ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + msg[sizeof(msg)-1] = '\0'; + + Printf( S_COLOR_YELLOW"WARNING: %s\n", msg ); +} + +/* +================== +idCommonLocal::Warning + +prints WARNING %s and adds the warning message to a queue to be printed later on +================== +*/ +void idCommonLocal::Warning( const char *fmt, ... ) { + va_list argptr; + char msg[MAX_PRINT_MSG_SIZE]; + + if ( !idLib::IsMainThread() ) { + return; // not thread safe! + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + msg[sizeof(msg)-1] = 0; + + Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", msg ); + + if ( warningList.Num() < MAX_WARNING_LIST ) { + warningList.AddUnique( msg ); + } +} + +/* +================== +idCommonLocal::PrintWarnings +================== +*/ +void idCommonLocal::PrintWarnings() { + int i; + + if ( !warningList.Num() ) { + return; + } + + Printf( "------------- Warnings ---------------\n" ); + Printf( "during %s...\n", warningCaption.c_str() ); + + for ( i = 0; i < warningList.Num(); i++ ) { + Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", warningList[i].c_str() ); + } + if ( warningList.Num() ) { + if ( warningList.Num() >= MAX_WARNING_LIST ) { + Printf( "more than %d warnings\n", MAX_WARNING_LIST ); + } else { + Printf( "%d warnings\n", warningList.Num() ); + } + } +} + +/* +================== +idCommonLocal::ClearWarnings +================== +*/ +void idCommonLocal::ClearWarnings( const char *reason ) { + warningCaption = reason; + warningList.Clear(); +} + +/* +================== +idCommonLocal::DumpWarnings +================== +*/ +void idCommonLocal::DumpWarnings() { + int i; + idFile *warningFile; + + if ( !warningList.Num() ) { + return; + } + + warningFile = fileSystem->OpenFileWrite( "warnings.txt", "fs_savepath" ); + if ( warningFile ) { + + warningFile->Printf( "------------- Warnings ---------------\n\n" ); + warningFile->Printf( "during %s...\n", warningCaption.c_str() ); + for ( i = 0; i < warningList.Num(); i++ ) { + warningList[i].RemoveColors(); + warningFile->Printf( "WARNING: %s\n", warningList[i].c_str() ); + } + if ( warningList.Num() >= MAX_WARNING_LIST ) { + warningFile->Printf( "\nmore than %d warnings!\n", MAX_WARNING_LIST ); + } else { + warningFile->Printf( "\n%d warnings.\n", warningList.Num() ); + } + + warningFile->Printf( "\n\n-------------- Errors ---------------\n\n" ); + for ( i = 0; i < errorList.Num(); i++ ) { + errorList[i].RemoveColors(); + warningFile->Printf( "ERROR: %s", errorList[i].c_str() ); + } + + warningFile->ForceFlush(); + + fileSystem->CloseFile( warningFile ); + +#ifndef ID_DEBUG + idStr osPath; + osPath = fileSystem->RelativePathToOSPath( "warnings.txt", "fs_savepath" ); + WinExec( va( "Notepad.exe %s", osPath.c_str() ), SW_SHOW ); +#endif + } +} + +/* +================== +idCommonLocal::Error +================== +*/ +void idCommonLocal::Error( const char *fmt, ... ) { + va_list argptr; + static int lastErrorTime; + static int errorCount; + int currentTime; + + errorParm_t code = ERP_DROP; + + // always turn this off after an error + com_refreshOnPrint = false; + + if ( com_productionMode.GetInteger() == 3 ) { + Sys_Quit(); + } + + // when we are running automated scripts, make sure we + // know if anything failed + if ( cvarSystem->GetCVarInteger( "fs_copyfiles" ) ) { + code = ERP_FATAL; + } + + // if we don't have GL running, make it a fatal error + if ( !renderSystem->IsOpenGLRunning() ) { + code = ERP_FATAL; + } + + // if we got a recursive error, make it fatal + if ( com_errorEntered ) { + // if we are recursively erroring while exiting + // from a fatal error, just kill the entire + // process immediately, which will prevent a + // full screen rendering window covering the + // error dialog + if ( com_errorEntered == ERP_FATAL ) { + Sys_Quit(); + } + code = ERP_FATAL; + } + + // if we are getting a solid stream of ERP_DROP, do an ERP_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) { + if ( ++errorCount > 3 ) { + code = ERP_FATAL; + } + } else { + errorCount = 0; + } + lastErrorTime = currentTime; + + com_errorEntered = code; + + va_start (argptr,fmt); + idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); + va_end (argptr); + errorMessage[sizeof(errorMessage)-1] = '\0'; + + + // copy the error message to the clip board + Sys_SetClipboardData( errorMessage ); + + // add the message to the error list + errorList.AddUnique( errorMessage ); + + Stop(); + + if ( code == ERP_DISCONNECT ) { + com_errorEntered = ERP_NONE; + throw idException( errorMessage ); + } else if ( code == ERP_DROP ) { + Printf( "********************\nERROR: %s\n********************\n", errorMessage ); + com_errorEntered = ERP_NONE; + throw idException( errorMessage ); + } else { + Printf( "********************\nERROR: %s\n********************\n", errorMessage ); + } + + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); + } + + Sys_Error( "%s", errorMessage ); + +} + +/* +================== +idCommonLocal::FatalError + +Dump out of the game to a system dialog +================== +*/ +void idCommonLocal::FatalError( const char *fmt, ... ) { + va_list argptr; + + if ( com_productionMode.GetInteger() == 3 ) { + Sys_Quit(); + } + // if we got a recursive error, make it fatal + if ( com_errorEntered ) { + // if we are recursively erroring while exiting + // from a fatal error, just kill the entire + // process immediately, which will prevent a + // full screen rendering window covering the + // error dialog + + Sys_Printf( "FATAL: recursed fatal error:\n%s\n", errorMessage ); + + va_start( argptr, fmt ); + idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); + va_end( argptr ); + errorMessage[sizeof(errorMessage)-1] = '\0'; + + Sys_Printf( "%s\n", errorMessage ); + + // write the console to a log file? + Sys_Quit(); + } + com_errorEntered = ERP_FATAL; + + va_start( argptr, fmt ); + idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); + va_end( argptr ); + errorMessage[sizeof(errorMessage)-1] = '\0'; + + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); + } + + Sys_SetFatalError( errorMessage ); + + Sys_Error( "%s", errorMessage ); + +} diff --git a/neo/framework/Compressor.cpp b/neo/framework/Compressor.cpp new file mode 100644 index 00000000..f71785b1 --- /dev/null +++ b/neo/framework/Compressor.cpp @@ -0,0 +1,2575 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================================================================================= + + idCompressor_None + +================================================================================= +*/ + +class idCompressor_None : public idCompressor { +public: + idCompressor_None(); + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress(); + float GetCompressionRatio() const; + + const char * GetName(); + const char * GetFullPath(); + int Read( void *outData, int outLength ); + int Write( const void *inData, int inLength ); + int Length(); + ID_TIME_T Timestamp(); + int Tell(); + void ForceFlush(); + void Flush(); + int Seek( long offset, fsOrigin_t origin ); + +protected: + idFile * file; + bool compress; +}; + +/* +================ +idCompressor_None::idCompressor_None +================ +*/ +idCompressor_None::idCompressor_None() { + file = NULL; + compress = true; +} + +/* +================ +idCompressor_None::Init +================ +*/ +void idCompressor_None::Init( idFile *f, bool compress, int wordLength ) { + this->file = f; + this->compress = compress; +} + +/* +================ +idCompressor_None::FinishCompress +================ +*/ +void idCompressor_None::FinishCompress() { +} + +/* +================ +idCompressor_None::GetCompressionRatio +================ +*/ +float idCompressor_None::GetCompressionRatio() const { + return 0.0f; +} + +/* +================ +idCompressor_None::GetName +================ +*/ +const char *idCompressor_None::GetName() { + if ( file ) { + return file->GetName(); + } else { + return ""; + } +} + +/* +================ +idCompressor_None::GetFullPath +================ +*/ +const char *idCompressor_None::GetFullPath() { + if ( file ) { + return file->GetFullPath(); + } else { + return ""; + } +} + +/* +================ +idCompressor_None::Write +================ +*/ +int idCompressor_None::Write( const void *inData, int inLength ) { + if ( compress == false || inLength <= 0 ) { + return 0; + } + return file->Write( inData, inLength ); +} + +/* +================ +idCompressor_None::Read +================ +*/ +int idCompressor_None::Read( void *outData, int outLength ) { + if ( compress == true || outLength <= 0 ) { + return 0; + } + return file->Read( outData, outLength ); +} + +/* +================ +idCompressor_None::Length +================ +*/ +int idCompressor_None::Length() { + if ( file ) { + return file->Length(); + } else { + return 0; + } +} + +/* +================ +idCompressor_None::Timestamp +================ +*/ +ID_TIME_T idCompressor_None::Timestamp() { + if ( file ) { + return file->Timestamp(); + } else { + return 0; + } +} + +/* +================ +idCompressor_None::Tell +================ +*/ +int idCompressor_None::Tell() { + if ( file ) { + return file->Tell(); + } else { + return 0; + } +} + +/* +================ +idCompressor_None::ForceFlush +================ +*/ +void idCompressor_None::ForceFlush() { + if ( file ) { + file->ForceFlush(); + } +} + +/* +================ +idCompressor_None::Flush +================ +*/ +void idCompressor_None::Flush() { + if ( file ) { + file->ForceFlush(); + } +} + +/* +================ +idCompressor_None::Seek +================ +*/ +int idCompressor_None::Seek( long offset, fsOrigin_t origin ) { + common->Error( "cannot seek on idCompressor" ); + return -1; +} + + +/* +================================================================================= + + idCompressor_BitStream + + Base class for bit stream compression. + +================================================================================= +*/ + +class idCompressor_BitStream : public idCompressor_None { +public: + idCompressor_BitStream() {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress(); + float GetCompressionRatio() const; + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +protected: + byte buffer[65536]; + int wordLength; + + int readTotalBytes; + int readLength; + int readByte; + int readBit; + const byte * readData; + + int writeTotalBytes; + int writeLength; + int writeByte; + int writeBit; + byte * writeData; + +protected: + void InitCompress( const void *inData, const int inLength ); + void InitDecompress( void *outData, int outLength ); + void WriteBits( int value, int numBits ); + int ReadBits( int numBits ); + void UnreadBits( int numBits ); + int Compare( const byte *src1, int bitPtr1, const byte *src2, int bitPtr2, int maxBits ) const; +}; + +/* +================ +idCompressor_BitStream::Init +================ +*/ +void idCompressor_BitStream::Init( idFile *f, bool compress, int wordLength ) { + + assert( wordLength >= 1 && wordLength <= 32 ); + + this->file = f; + this->compress = compress; + this->wordLength = wordLength; + + readTotalBytes = 0; + readLength = 0; + readByte = 0; + readBit = 0; + readData = NULL; + + writeTotalBytes = 0; + writeLength = 0; + writeByte = 0; + writeBit = 0; + writeData = NULL; +} + +/* +================ +idCompressor_BitStream::InitCompress +================ +*/ +ID_INLINE void idCompressor_BitStream::InitCompress( const void *inData, const int inLength ) { + + readLength = inLength; + readByte = 0; + readBit = 0; + readData = (const byte *) inData; + + if ( !writeLength ) { + writeLength = sizeof( buffer ); + writeByte = 0; + writeBit = 0; + writeData = buffer; + } +} + +/* +================ +idCompressor_BitStream::InitDecompress +================ +*/ +ID_INLINE void idCompressor_BitStream::InitDecompress( void *outData, int outLength ) { + + if ( !readLength ) { + readLength = file->Read( buffer, sizeof( buffer ) ); + readByte = 0; + readBit = 0; + readData = buffer; + } + + writeLength = outLength; + writeByte = 0; + writeBit = 0; + writeData = (byte *) outData; +} + +/* +================ +idCompressor_BitStream::WriteBits +================ +*/ +void idCompressor_BitStream::WriteBits( int value, int numBits ) { + int put, fraction; + + // Short circuit for writing single bytes at a time + if ( writeBit == 0 && numBits == 8 && writeByte < writeLength ) { + writeByte++; + writeTotalBytes++; + writeData[writeByte - 1] = value; + return; + } + + + while( numBits ) { + if ( writeBit == 0 ) { + if ( writeByte >= writeLength ) { + if ( writeData == buffer ) { + file->Write( buffer, writeByte ); + writeByte = 0; + } else { + put = numBits; + writeBit = put & 7; + writeByte += ( put >> 3 ) + ( writeBit != 0 ); + writeTotalBytes += ( put >> 3 ) + ( writeBit != 0 ); + return; + } + } + writeData[writeByte] = 0; + writeByte++; + writeTotalBytes++; + } + put = 8 - writeBit; + if ( put > numBits ) { + put = numBits; + } + fraction = value & ( ( 1 << put ) - 1 ); + writeData[writeByte - 1] |= fraction << writeBit; + numBits -= put; + value >>= put; + writeBit = ( writeBit + put ) & 7; + } +} + +/* +================ +idCompressor_BitStream::ReadBits +================ +*/ +int idCompressor_BitStream::ReadBits( int numBits ) { + int value, valueBits, get, fraction; + + value = 0; + valueBits = 0; + + // Short circuit for reading single bytes at a time + if ( readBit == 0 && numBits == 8 && readByte < readLength ) { + readByte++; + readTotalBytes++; + return readData[readByte - 1]; + } + + while ( valueBits < numBits ) { + if ( readBit == 0 ) { + if ( readByte >= readLength ) { + if ( readData == buffer ) { + readLength = file->Read( buffer, sizeof( buffer ) ); + readByte = 0; + } else { + get = numBits - valueBits; + readBit = get & 7; + readByte += ( get >> 3 ) + ( readBit != 0 ); + readTotalBytes += ( get >> 3 ) + ( readBit != 0 ); + return value; + } + } + readByte++; + readTotalBytes++; + } + get = 8 - readBit; + if ( get > (numBits - valueBits) ) { + get = (numBits - valueBits); + } + fraction = readData[readByte - 1]; + fraction >>= readBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + valueBits += get; + readBit = ( readBit + get ) & 7; + } + + return value; +} + +/* +================ +idCompressor_BitStream::UnreadBits +================ +*/ +void idCompressor_BitStream::UnreadBits( int numBits ) { + readByte -= ( numBits >> 3 ); + readTotalBytes -= ( numBits >> 3 ); + if ( readBit == 0 ) { + readBit = 8 - ( numBits & 7 ); + } else { + readBit -= numBits & 7; + if ( readBit <= 0 ) { + readByte--; + readTotalBytes--; + readBit = ( readBit + 8 ) & 7; + } + } + if ( readByte < 0 ) { + readByte = 0; + readBit = 0; + } +} + +/* +================ +idCompressor_BitStream::Compare +================ +*/ +int idCompressor_BitStream::Compare( const byte *src1, int bitPtr1, const byte *src2, int bitPtr2, int maxBits ) const { + int i; + + // If the two bit pointers are aligned then we can use a faster comparison + if ( ( bitPtr1 & 7 ) == (bitPtr2 & 7 ) && maxBits > 16 ) { + const byte *p1 = &src1[bitPtr1 >> 3]; + const byte *p2 = &src2[bitPtr2 >> 3]; + + int bits = 0; + + int bitsRemain = maxBits; + + // Compare the first couple bits (if any) + if ( bitPtr1 & 7 ) { + for ( i = (bitPtr1 & 7); i < 8; i++, bits++ ) { + if ( ( ( *p1 >> i ) ^ ( *p2 >> i ) ) & 1 ) { + return bits; + } + bitsRemain--; + } + p1++; + p2++; + } + + int remain = bitsRemain >> 3; + + // Compare the middle bytes as ints + while ( remain >= 4 && (*(const int *)p1 == *(const int *)p2) ) { + p1 += 4; + p2 += 4; + remain -= 4; + bits += 32; + } + + // Compare the remaining bytes + while ( remain > 0 && (*p1 == *p2) ) { + p1++; + p2++; + remain--; + bits += 8; + } + + // Compare the last couple of bits (if any) + int finalBits = 8; + if ( remain == 0 ) { + finalBits = ( bitsRemain & 7 ); + } + for ( i = 0; i < finalBits; i++, bits++ ) { + if ( ( ( *p1 >> i ) ^ ( *p2 >> i ) ) & 1 ) { + return bits; + } + } + + assert(bits == maxBits); + return bits; + } else { + for ( i = 0; i < maxBits; i++ ) { + if ( ( ( src1[bitPtr1 >> 3] >> ( bitPtr1 & 7 ) ) ^ ( src2[bitPtr2 >> 3] >> ( bitPtr2 & 7 ) ) ) & 1 ) { + break; + } + bitPtr1++; + bitPtr2++; + } + return i; +} +} + +/* +================ +idCompressor_BitStream::Write +================ +*/ +int idCompressor_BitStream::Write( const void *inData, int inLength ) { + int i; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + for ( i = 0; i < inLength; i++ ) { + WriteBits( ReadBits( 8 ), 8 ); + } + return i; +} + +/* +================ +idCompressor_BitStream::FinishCompress +================ +*/ +void idCompressor_BitStream::FinishCompress() { + if ( compress == false ) { + return; + } + + if ( writeByte ) { + file->Write( buffer, writeByte ); + } + writeLength = 0; + writeByte = 0; + writeBit = 0; +} + +/* +================ +idCompressor_BitStream::Read +================ +*/ +int idCompressor_BitStream::Read( void *outData, int outLength ) { + int i; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + for ( i = 0; i < outLength && readLength >= 0; i++ ) { + WriteBits( ReadBits( 8 ), 8 ); + } + return i; +} + +/* +================ +idCompressor_BitStream::GetCompressionRatio +================ +*/ +float idCompressor_BitStream::GetCompressionRatio() const { + if ( compress ) { + return ( readTotalBytes - writeTotalBytes ) * 100.0f / readTotalBytes; + } else { + return ( writeTotalBytes - readTotalBytes ) * 100.0f / writeTotalBytes; + } +} + + +/* +================================================================================= + + idCompressor_RunLength + + The following algorithm implements run length compression with an arbitrary + word size. + +================================================================================= +*/ + +class idCompressor_RunLength : public idCompressor_BitStream { +public: + idCompressor_RunLength() {} + + void Init( idFile *f, bool compress, int wordLength ); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: + int runLengthCode; +}; + +/* +================ +idCompressor_RunLength::Init +================ +*/ +void idCompressor_RunLength::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_BitStream::Init( f, compress, wordLength ); + runLengthCode = ( 1 << wordLength ) - 1; +} + +/* +================ +idCompressor_RunLength::Write +================ +*/ +int idCompressor_RunLength::Write( const void *inData, int inLength ) { + int bits, nextBits, count; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + while( readByte <= readLength ) { + count = 1; + bits = ReadBits( wordLength ); + for ( nextBits = ReadBits( wordLength ); nextBits == bits; nextBits = ReadBits( wordLength ) ) { + count++; + if ( count >= ( 1 << wordLength ) ) { + if ( count >= ( 1 << wordLength ) + 3 || bits == runLengthCode ) { + break; + } + } + } + if ( nextBits != bits ) { + UnreadBits( wordLength ); + } + if ( count > 3 || bits == runLengthCode ) { + WriteBits( runLengthCode, wordLength ); + WriteBits( bits, wordLength ); + if ( bits != runLengthCode ) { + count -= 3; + } + WriteBits( count - 1, wordLength ); + } else { + while( count-- ) { + WriteBits( bits, wordLength ); + } + } + } + + return inLength; +} + +/* +================ +idCompressor_RunLength::Read +================ +*/ +int idCompressor_RunLength::Read( void *outData, int outLength ) { + int bits, count; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + while( writeByte <= writeLength && readLength >= 0 ) { + bits = ReadBits( wordLength ); + if ( bits == runLengthCode ) { + bits = ReadBits( wordLength ); + count = ReadBits( wordLength ) + 1; + if ( bits != runLengthCode ) { + count += 3; + } + while( count-- ) { + WriteBits( bits, wordLength ); + } + } else { + WriteBits( bits, wordLength ); + } + } + + return writeByte; +} + + +/* +================================================================================= + + idCompressor_RunLength_ZeroBased + + The following algorithm implements run length compression with an arbitrary + word size for data with a lot of zero bits. + +================================================================================= +*/ + +class idCompressor_RunLength_ZeroBased : public idCompressor_BitStream { +public: + idCompressor_RunLength_ZeroBased() {} + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: +}; + +/* +================ +idCompressor_RunLength_ZeroBased::Write +================ +*/ +int idCompressor_RunLength_ZeroBased::Write( const void *inData, int inLength ) { + int bits, count; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + while( readByte <= readLength ) { + count = 0; + for ( bits = ReadBits( wordLength ); bits == 0 && count < ( 1 << wordLength ); bits = ReadBits( wordLength ) ) { + count++; + } + if ( count ) { + WriteBits( 0, wordLength ); + WriteBits( count - 1, wordLength ); + UnreadBits( wordLength ); + } else { + WriteBits( bits, wordLength ); + } + } + + return inLength; +} + +/* +================ +idCompressor_RunLength_ZeroBased::Read +================ +*/ +int idCompressor_RunLength_ZeroBased::Read( void *outData, int outLength ) { + int bits, count; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + while( writeByte <= writeLength && readLength >= 0 ) { + bits = ReadBits( wordLength ); + if ( bits == 0 ) { + count = ReadBits( wordLength ) + 1; + while( count-- > 0 ) { + WriteBits( 0, wordLength ); + } + } else { + WriteBits( bits, wordLength ); + } + } + + return writeByte; +} + + +/* +================================================================================= + + idCompressor_Huffman + + The following algorithm is based on the adaptive Huffman algorithm described + in Sayood's Data Compression book. The ranks are not actually stored, but + implicitly defined by the location of a node within a doubly-linked list + +================================================================================= +*/ + +const int HMAX = 256; // Maximum symbol +const int NYT = HMAX; // NYT = Not Yet Transmitted +const int INTERNAL_NODE = HMAX + 1; // internal node + +typedef struct nodetype { + struct nodetype *left, *right, *parent; // tree structure + struct nodetype *next, *prev; // doubly-linked list + struct nodetype **head; // highest ranked node in block + int weight; + int symbol; +} huffmanNode_t; + +class idCompressor_Huffman : public idCompressor_None { +public: + idCompressor_Huffman() {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress(); + float GetCompressionRatio() const; + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: + byte seq[65536]; + int bloc; + int blocMax; + int blocIn; + int blocNode; + int blocPtrs; + + int compressedSize; + int unCompressedSize; + + huffmanNode_t * tree; + huffmanNode_t * lhead; + huffmanNode_t * ltail; + huffmanNode_t * loc[HMAX+1]; + huffmanNode_t **freelist; + + huffmanNode_t nodeList[768]; + huffmanNode_t * nodePtrs[768]; + +private: + void AddRef( byte ch ); + int Receive( huffmanNode_t *node, int *ch ); + void Transmit( int ch, byte *fout ); + void PutBit( int bit, byte *fout, int *offset ); + int GetBit( byte *fout, int *offset ); + + void Add_bit( char bit, byte *fout ); + int Get_bit(); + huffmanNode_t **Get_ppnode(); + void Free_ppnode( huffmanNode_t **ppnode ); + void Swap( huffmanNode_t *node1, huffmanNode_t *node2 ); + void Swaplist( huffmanNode_t *node1, huffmanNode_t *node2 ); + void Increment( huffmanNode_t *node ); + void Send( huffmanNode_t *node, huffmanNode_t *child, byte *fout ); +}; + +/* +================ +idCompressor_Huffman::Init +================ +*/ +void idCompressor_Huffman::Init( idFile *f, bool compress, int wordLength ) { + int i; + + this->file = f; + this->compress = compress; + bloc = 0; + blocMax = 0; + blocIn = 0; + blocNode = 0; + blocPtrs = 0; + compressedSize = 0; + unCompressedSize = 0; + + tree = NULL; + lhead = NULL; + ltail = NULL; + for( i = 0; i < (HMAX+1); i++ ) { + loc[i] = NULL; + } + freelist = NULL; + + for( i = 0; i < 768; i++ ) { + memset( &nodeList[i], 0, sizeof(huffmanNode_t) ); + nodePtrs[i] = NULL; + } + + if ( compress ) { + // Add the NYT (not yet transmitted) node into the tree/list + tree = lhead = loc[NYT] = &nodeList[blocNode++]; + tree->symbol = NYT; + tree->weight = 0; + lhead->next = lhead->prev = NULL; + tree->parent = tree->left = tree->right = NULL; + } else { + // Initialize the tree & list with the NYT node + tree = lhead = ltail = loc[NYT] = &nodeList[blocNode++]; + tree->symbol = NYT; + tree->weight = 0; + lhead->next = lhead->prev = NULL; + tree->parent = tree->left = tree->right = NULL; + } +} + +/* +================ +idCompressor_Huffman::PutBit +================ +*/ +void idCompressor_Huffman::PutBit( int bit, byte *fout, int *offset) { + bloc = *offset; + if ( (bloc&7) == 0 ) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; + *offset = bloc; +} + +/* +================ +idCompressor_Huffman::GetBit +================ +*/ +int idCompressor_Huffman::GetBit( byte *fin, int *offset) { + int t; + bloc = *offset; + t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; + bloc++; + *offset = bloc; + return t; +} + +/* +================ +idCompressor_Huffman::Add_bit + + Add a bit to the output file (buffered) +================ +*/ +void idCompressor_Huffman::Add_bit( char bit, byte *fout ) { + if ( (bloc&7) == 0 ) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; +} + +/* +================ +idCompressor_Huffman::Get_bit + + Get one bit from the input file (buffered) +================ +*/ +int idCompressor_Huffman::Get_bit() { + int t; + int wh = bloc >> 3; + int whb = wh>> 16; + if ( whb != blocIn ) { + blocMax += file->Read( seq, sizeof( seq ) ); + blocIn++; + } + wh &= 0xffff; + t = ( seq[wh] >> ( bloc & 7 ) ) & 0x1; + bloc++; + return t; +} + +/* +================ +idCompressor_Huffman::Get_ppnode +================ +*/ +huffmanNode_t **idCompressor_Huffman::Get_ppnode() { + huffmanNode_t **tppnode; + if ( !freelist ) { + return &nodePtrs[blocPtrs++]; + } else { + tppnode = freelist; + freelist = (huffmanNode_t **)*tppnode; + return tppnode; + } +} + +/* +================ +idCompressor_Huffman::Free_ppnode +================ +*/ +void idCompressor_Huffman::Free_ppnode( huffmanNode_t **ppnode ) { + *ppnode = (huffmanNode_t *)freelist; + freelist = ppnode; +} + +/* +================ +idCompressor_Huffman::Swap + + Swap the location of the given two nodes in the tree. +================ +*/ +void idCompressor_Huffman::Swap( huffmanNode_t *node1, huffmanNode_t *node2 ) { + huffmanNode_t *par1, *par2; + + par1 = node1->parent; + par2 = node2->parent; + + if ( par1 ) { + if ( par1->left == node1 ) { + par1->left = node2; + } else { + par1->right = node2; + } + } else { + tree = node2; + } + + if ( par2 ) { + if ( par2->left == node2 ) { + par2->left = node1; + } else { + par2->right = node1; + } + } else { + tree = node1; + } + + node1->parent = par2; + node2->parent = par1; +} + +/* +================ +idCompressor_Huffman::Swaplist + + Swap the given two nodes in the linked list (update ranks) +================ +*/ +void idCompressor_Huffman::Swaplist( huffmanNode_t *node1, huffmanNode_t *node2 ) { + huffmanNode_t *par1; + + par1 = node1->next; + node1->next = node2->next; + node2->next = par1; + + par1 = node1->prev; + node1->prev = node2->prev; + node2->prev = par1; + + if ( node1->next == node1 ) { + node1->next = node2; + } + if ( node2->next == node2 ) { + node2->next = node1; + } + if ( node1->next ) { + node1->next->prev = node1; + } + if ( node2->next ) { + node2->next->prev = node2; + } + if ( node1->prev ) { + node1->prev->next = node1; + } + if ( node2->prev ) { + node2->prev->next = node2; + } +} + +/* +================ +idCompressor_Huffman::Increment +================ +*/ +void idCompressor_Huffman::Increment( huffmanNode_t *node ) { + huffmanNode_t *lnode; + + if ( !node ) { + return; + } + + if ( node->next != NULL && node->next->weight == node->weight ) { + lnode = *node->head; + if ( lnode != node->parent ) { + Swap( lnode, node ); + } + Swaplist( lnode, node ); + } + if ( node->prev && node->prev->weight == node->weight ) { + *node->head = node->prev; + } else { + *node->head = NULL; + Free_ppnode( node->head ); + } + node->weight++; + if ( node->next && node->next->weight == node->weight ) { + node->head = node->next->head; + } else { + node->head = Get_ppnode(); + *node->head = node; + } + if ( node->parent ) { + Increment( node->parent ); + if ( node->prev == node->parent ) { + Swaplist( node, node->parent ); + if ( *node->head == node ) { + *node->head = node->parent; + } + } + } +} + +/* +================ +idCompressor_Huffman::AddRef +================ +*/ +void idCompressor_Huffman::AddRef( byte ch ) { + huffmanNode_t *tnode, *tnode2; + if ( loc[ch] == NULL ) { /* if this is the first transmission of this node */ + tnode = &nodeList[blocNode++]; + tnode2 = &nodeList[blocNode++]; + + tnode2->symbol = INTERNAL_NODE; + tnode2->weight = 1; + tnode2->next = lhead->next; + if ( lhead->next ) { + lhead->next->prev = tnode2; + if ( lhead->next->weight == 1 ) { + tnode2->head = lhead->next->head; + } else { + tnode2->head = Get_ppnode(); + *tnode2->head = tnode2; + } + } else { + tnode2->head = Get_ppnode(); + *tnode2->head = tnode2; + } + lhead->next = tnode2; + tnode2->prev = lhead; + + tnode->symbol = ch; + tnode->weight = 1; + tnode->next = lhead->next; + if ( lhead->next ) { + lhead->next->prev = tnode; + if ( lhead->next->weight == 1 ) { + tnode->head = lhead->next->head; + } else { + /* this should never happen */ + tnode->head = Get_ppnode(); + *tnode->head = tnode2; + } + } else { + /* this should never happen */ + tnode->head = Get_ppnode(); + *tnode->head = tnode; + } + lhead->next = tnode; + tnode->prev = lhead; + tnode->left = tnode->right = NULL; + + if ( lhead->parent ) { + if ( lhead->parent->left == lhead ) { /* lhead is guaranteed to by the NYT */ + lhead->parent->left = tnode2; + } else { + lhead->parent->right = tnode2; + } + } else { + tree = tnode2; + } + + tnode2->right = tnode; + tnode2->left = lhead; + + tnode2->parent = lhead->parent; + lhead->parent = tnode->parent = tnode2; + + loc[ch] = tnode; + + Increment( tnode2->parent ); + } else { + Increment( loc[ch] ); + } +} + +/* +================ +idCompressor_Huffman::Receive + + Get a symbol. +================ +*/ +int idCompressor_Huffman::Receive( huffmanNode_t *node, int *ch ) { + while ( node && node->symbol == INTERNAL_NODE ) { + if ( Get_bit() ) { + node = node->right; + } else { + node = node->left; + } + } + if ( !node ) { + return 0; + } + return (*ch = node->symbol); +} + +/* +================ +idCompressor_Huffman::Send + + Send the prefix code for this node. +================ +*/ +void idCompressor_Huffman::Send( huffmanNode_t *node, huffmanNode_t *child, byte *fout ) { + if ( node->parent ) { + Send( node->parent, node, fout ); + } + if ( child ) { + if ( node->right == child ) { + Add_bit( 1, fout ); + } else { + Add_bit( 0, fout ); + } + } +} + +/* +================ +idCompressor_Huffman::Transmit + + Send a symbol. +================ +*/ +void idCompressor_Huffman::Transmit( int ch, byte *fout ) { + int i; + if ( loc[ch] == NULL ) { + /* huffmanNode_t hasn't been transmitted, send a NYT, then the symbol */ + Transmit( NYT, fout ); + for ( i = 7; i >= 0; i-- ) { + Add_bit( (char)((ch >> i) & 0x1), fout ); + } + } else { + Send( loc[ch], NULL, fout ); + } +} + +/* +================ +idCompressor_Huffman::Write +================ +*/ +int idCompressor_Huffman::Write( const void *inData, int inLength ) { + int i, ch; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + for ( i = 0; i < inLength; i++ ) { + ch = ((const byte *)inData)[i]; + Transmit( ch, seq ); /* Transmit symbol */ + AddRef( (byte)ch ); /* Do update */ + int b = (bloc>>3); + if ( b > 32768 ) { + file->Write( seq, b ); + seq[0] = seq[b]; + bloc &= 7; + compressedSize += b; + } + } + + unCompressedSize += i; + return i; +} + +/* +================ +idCompressor_Huffman::FinishCompress +================ +*/ +void idCompressor_Huffman::FinishCompress() { + + if ( compress == false ) { + return; + } + + bloc += 7; + int str = (bloc>>3); + if ( str ) { + file->Write( seq, str ); + compressedSize += str; + } +} + +/* +================ +idCompressor_Huffman::Read +================ +*/ +int idCompressor_Huffman::Read( void *outData, int outLength ) { + int i, j, ch; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + if ( bloc == 0 ) { + blocMax = file->Read( seq, sizeof( seq ) ); + blocIn = 0; + } + + for ( i = 0; i < outLength; i++ ) { + ch = 0; + // don't overflow reading from the file + if ( ( bloc >> 3 ) > blocMax ) { + break; + } + Receive( tree, &ch ); /* Get a character */ + if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */ + ch = 0; + for ( j = 0; j < 8; j++ ) { + ch = ( ch << 1 ) + Get_bit(); + } + } + + ((byte *)outData)[i] = ch; /* Write symbol */ + AddRef( (byte) ch ); /* Increment node */ + } + + compressedSize = bloc >> 3; + unCompressedSize += i; + return i; +} + +/* +================ +idCompressor_Huffman::GetCompressionRatio +================ +*/ +float idCompressor_Huffman::GetCompressionRatio() const { + return ( unCompressedSize - compressedSize ) * 100.0f / unCompressedSize; +} + + +/* +================================================================================= + + idCompressor_Arithmetic + + The following algorithm is based on the Arithmetic Coding methods described + by Mark Nelson. The probability table is implicitly stored. + +================================================================================= +*/ + +const int AC_WORD_LENGTH = 8; +const int AC_NUM_BITS = 16; +const int AC_MSB_SHIFT = 15; +const int AC_MSB2_SHIFT = 14; +const int AC_MSB_MASK = 0x8000; +const int AC_MSB2_MASK = 0x4000; +const int AC_HIGH_INIT = 0xffff; +const int AC_LOW_INIT = 0x0000; + +class idCompressor_Arithmetic : public idCompressor_BitStream { +public: + idCompressor_Arithmetic() {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress(); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: + typedef struct acProbs_s { + unsigned int low; + unsigned int high; + } acProbs_t; + + typedef struct acSymbol_s { + unsigned int low; + unsigned int high; + int position; + } acSymbol_t; + + acProbs_t probabilities[1<position; + + probabilities[ x ].high++; + + for( i = x + 1; i < (1< 0 ) { + mid = len >> 1; + if ( count >= probabilities[offset+mid].high ) { + offset += mid; + len -= mid; + res = 1; + } + else if ( count < probabilities[offset+mid].low ) { + len -= mid; + res = 0; + } else { + return offset+mid; + } + } + return offset+res; + +#else + + int j; + + for( j = 0; j < (1<= probabilities[ j ].low && count < probabilities[ j ].high ) { + return j; + } + } + + assert( false ); + + return 0; + +#endif +} + +/* +================ +idCompressor_Arithmetic::SymbolFromCount +================ +*/ +int idCompressor_Arithmetic::SymbolFromCount( unsigned int count, acSymbol_t* symbol ) { + int p = ProbabilityForCount( count ); + symbol->low = probabilities[ p ].low; + symbol->high = probabilities[ p ].high; + symbol->position = p; + return p; +} + +/* +================ +idCompressor_Arithmetic::RemoveSymbolFromStream +================ +*/ +void idCompressor_Arithmetic::RemoveSymbolFromStream( acSymbol_t* symbol ) { + long range; + + range = ( long )( high - low ) + 1; + high = low + ( unsigned short )( ( range * symbol->high ) / scale - 1 ); + low = low + ( unsigned short )( ( range * symbol->low ) / scale ); + + while( true ) { + + if ( ( high & AC_MSB_MASK ) == ( low & AC_MSB_MASK ) ) { + + } else if( ( low & AC_MSB2_MASK ) == AC_MSB2_MASK && ( high & AC_MSB2_MASK ) == 0 ) { + code ^= AC_MSB2_MASK; + low &= AC_MSB2_MASK - 1; + high |= AC_MSB2_MASK; + } else { + UpdateProbabilities( symbol ); + return; + } + + low <<= 1; + high <<= 1; + high |= 1; + code <<= 1; + code |= ReadBits( 1 ); + } +} + +/* +================ +idCompressor_Arithmetic::GetBit +================ +*/ +int idCompressor_Arithmetic::GetBit() { + int getbit; + + if( symbolBit <= 0 ) { + // read a new symbol out + acSymbol_t symbol; + symbolBuffer = SymbolFromCount( GetCurrentCount(), &symbol ); + RemoveSymbolFromStream( &symbol ); + symbolBit = AC_WORD_LENGTH; + } + + getbit = ( symbolBuffer >> ( AC_WORD_LENGTH - symbolBit ) ) & 1; + symbolBit--; + + return getbit; +} + +/* +================ +idCompressor_Arithmetic::EncodeSymbol +================ +*/ +void idCompressor_Arithmetic::EncodeSymbol( acSymbol_t* symbol ) { + unsigned int range; + + // rescale high and low for the new symbol. + range = ( high - low ) + 1; + high = low + ( unsigned short )(( range * symbol->high ) / scale - 1 ); + low = low + ( unsigned short )(( range * symbol->low ) / scale ); + + while( true ) { + if ( ( high & AC_MSB_MASK ) == ( low & AC_MSB_MASK ) ) { + // the high digits of low and high have converged, and can be written to the stream + WriteBits( high >> AC_MSB_SHIFT, 1 ); + + while( underflowBits > 0 ) { + + WriteBits( ~high >> AC_MSB_SHIFT, 1 ); + + underflowBits--; + } + } else if ( ( low & AC_MSB2_MASK ) && !( high & AC_MSB2_MASK ) ) { + // underflow is in danger of happening, 2nd digits are converging but 1st digits don't match + underflowBits += 1; + low &= AC_MSB2_MASK - 1; + high |= AC_MSB2_MASK; + } else { + UpdateProbabilities( symbol ); + return; + } + + low <<= 1; + high <<= 1; + high |= 1; + } +} + +/* +================ +idCompressor_Arithmetic::CharToSymbol +================ +*/ +void idCompressor_Arithmetic::CharToSymbol( byte c, acSymbol_t* symbol ) { + symbol->low = probabilities[ c ].low; + symbol->high = probabilities[ c ].high; + symbol->position = c; +} + +/* +================ +idCompressor_Arithmetic::PutBit +================ +*/ +void idCompressor_Arithmetic::PutBit( int putbit ) { + symbolBuffer |= ( putbit & 1 ) << symbolBit; + symbolBit++; + + if( symbolBit >= AC_WORD_LENGTH ) { + acSymbol_t symbol; + + CharToSymbol( symbolBuffer, &symbol ); + EncodeSymbol( &symbol ); + + symbolBit = 0; + symbolBuffer = 0; + } +} + +/* +================ +idCompressor_Arithmetic::WriteOverflowBits +================ +*/ +void idCompressor_Arithmetic::WriteOverflowBits() { + + WriteBits( low >> AC_MSB2_SHIFT, 1 ); + + underflowBits++; + while( underflowBits-- > 0 ) { + WriteBits( ~low >> AC_MSB2_SHIFT, 1 ); + } +} + +/* +================ +idCompressor_Arithmetic::Write +================ +*/ +int idCompressor_Arithmetic::Write( const void *inData, int inLength ) { + int i, j; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + for( i = 0; i < inLength; i++ ) { + if ( ( readTotalBytes & ( ( 1 << 14 ) - 1 ) ) == 0 ) { + if ( readTotalBytes ) { + WriteOverflowBits(); + WriteBits( 0, 15 ); + while( writeBit ) { + WriteBits( 0, 1 ); + } + WriteBits( 255, 8 ); + } + InitProbabilities(); + } + for ( j = 0; j < 8; j++ ) { + PutBit( ReadBits( 1 ) ); + } + } + + return inLength; +} + +/* +================ +idCompressor_Arithmetic::FinishCompress +================ +*/ +void idCompressor_Arithmetic::FinishCompress() { + if ( compress == false ) { + return; + } + + WriteOverflowBits(); + + idCompressor_BitStream::FinishCompress(); +} + +/* +================ +idCompressor_Arithmetic::Read +================ +*/ +int idCompressor_Arithmetic::Read( void *outData, int outLength ) { + int i, j; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + for( i = 0; i < outLength && readLength >= 0; i++ ) { + if ( ( writeTotalBytes & ( ( 1 << 14 ) - 1 ) ) == 0 ) { + if ( writeTotalBytes ) { + while( readBit ) { + ReadBits( 1 ); + } + while( ReadBits( 8 ) == 0 && readLength > 0 ) { + } + } + InitProbabilities(); + for ( j = 0; j < AC_NUM_BITS; j++ ) { + code <<= 1; + code |= ReadBits( 1 ); + } + } + for ( j = 0; j < 8; j++ ) { + WriteBits( GetBit(), 1 ); + } + } + + return i; +} + + +/* +================================================================================= + + idCompressor_LZSS + + In 1977 Abraham Lempel and Jacob Ziv presented a dictionary based scheme for + text compression called LZ77. For any new text LZ77 outputs an offset/length + pair to previously seen text and the next new byte after the previously seen + text. + + In 1982 James Storer and Thomas Szymanski presented a modification on the work + of Lempel and Ziv called LZSS. LZ77 always outputs an offset/length pair, even + if a match is only one byte long. An offset/length pair usually takes more than + a single byte to store and the compression is not optimal for small match sizes. + LZSS uses a bit flag which tells whether the following data is a literal (byte) + or an offset/length pair. + + The following algorithm is an implementation of LZSS with arbitrary word size. + +================================================================================= +*/ + +const int LZSS_BLOCK_SIZE = 65535; +const int LZSS_HASH_BITS = 10; +const int LZSS_HASH_SIZE = ( 1 << LZSS_HASH_BITS ); +const int LZSS_HASH_MASK = ( 1 << LZSS_HASH_BITS ) - 1; +const int LZSS_OFFSET_BITS = 11; +const int LZSS_LENGTH_BITS = 5; + +class idCompressor_LZSS : public idCompressor_BitStream { +public: + idCompressor_LZSS() {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress(); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +protected: + int offsetBits; + int lengthBits; + int minMatchWords; + + byte block[LZSS_BLOCK_SIZE]; + int blockSize; + int blockIndex; + + int hashTable[LZSS_HASH_SIZE]; + int hashNext[LZSS_BLOCK_SIZE * 8]; + +protected: + bool FindMatch( int startWord, int startValue, int &wordOffset, int &numWords ); + void AddToHash( int index, int hash ); + int GetWordFromBlock( int wordOffset ) const; + virtual void CompressBlock(); + virtual void DecompressBlock(); +}; + +/* +================ +idCompressor_LZSS::Init +================ +*/ +void idCompressor_LZSS::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_BitStream::Init( f, compress, wordLength ); + + offsetBits = LZSS_OFFSET_BITS; + lengthBits = LZSS_LENGTH_BITS; + + minMatchWords = ( offsetBits + lengthBits + wordLength ) / wordLength; + blockSize = 0; + blockIndex = 0; +} + +/* +================ +idCompressor_LZSS::FindMatch +================ +*/ +bool idCompressor_LZSS::FindMatch( int startWord, int startValue, int &wordOffset, int &numWords ) { + int i, n, hash, bottom, maxBits; + + wordOffset = startWord; + numWords = minMatchWords - 1; + + bottom = Max( 0, startWord - ( ( 1 << offsetBits ) - 1 ) ); + maxBits = ( blockSize << 3 ) - startWord * wordLength; + + hash = startValue & LZSS_HASH_MASK; + for ( i = hashTable[hash]; i >= bottom; i = hashNext[i] ) { + n = Compare( block, i * wordLength, block, startWord * wordLength, Min( maxBits, ( startWord - i ) * wordLength ) ); + if ( n > numWords * wordLength ) { + numWords = n / wordLength; + wordOffset = i; + if ( numWords > ( ( 1 << lengthBits ) - 1 + minMatchWords ) - 1 ) { + numWords = ( ( 1 << lengthBits ) - 1 + minMatchWords ) - 1; + break; + } + } + } + + return ( numWords >= minMatchWords ); +} + +/* +================ +idCompressor_LZSS::AddToHash +================ +*/ +void idCompressor_LZSS::AddToHash( int index, int hash ) { + hashNext[index] = hashTable[hash]; + hashTable[hash] = index; +} + +/* +================ +idCompressor_LZSS::GetWordFromBlock +================ +*/ +int idCompressor_LZSS::GetWordFromBlock( int wordOffset ) const { + int blockBit, blockByte, value, valueBits, get, fraction; + + blockBit = ( wordOffset * wordLength ) & 7; + blockByte = ( wordOffset * wordLength ) >> 3; + if ( blockBit != 0 ) { + blockByte++; + } + + value = 0; + valueBits = 0; + + while ( valueBits < wordLength ) { + if ( blockBit == 0 ) { + if ( blockByte >= LZSS_BLOCK_SIZE ) { + return value; + } + blockByte++; + } + get = 8 - blockBit; + if ( get > (wordLength - valueBits) ) { + get = (wordLength - valueBits); + } + fraction = block[blockByte - 1]; + fraction >>= blockBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + valueBits += get; + blockBit = ( blockBit + get ) & 7; + } + + return value; +} + +/* +================ +idCompressor_LZSS::CompressBlock +================ +*/ +void idCompressor_LZSS::CompressBlock() { + int i, startWord, startValue, wordOffset, numWords; + + InitCompress( block, blockSize ); + + memset( hashTable, -1, sizeof( hashTable ) ); + memset( hashNext, -1, sizeof( hashNext ) ); + + startWord = 0; + while( readByte < readLength ) { + startValue = ReadBits( wordLength ); + if ( FindMatch( startWord, startValue, wordOffset, numWords ) ) { + WriteBits( 1, 1 ); + WriteBits( startWord - wordOffset, offsetBits ); + WriteBits( numWords - minMatchWords, lengthBits ); + UnreadBits( wordLength ); + for ( i = 0; i < numWords; i++ ) { + startValue = ReadBits( wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } else { + WriteBits( 0, 1 ); + WriteBits( startValue, wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } + + blockSize = 0; +} + +/* +================ +idCompressor_LZSS::DecompressBlock +================ +*/ +void idCompressor_LZSS::DecompressBlock() { + int i, offset, startWord, numWords; + + InitDecompress( block, LZSS_BLOCK_SIZE ); + + startWord = 0; + while( writeByte < writeLength && readLength >= 0 ) { + if ( ReadBits( 1 ) ) { + offset = startWord - ReadBits( offsetBits ); + numWords = ReadBits( lengthBits ) + minMatchWords; + for ( i = 0; i < numWords; i++ ) { + WriteBits( GetWordFromBlock( offset + i ), wordLength ); + startWord++; + } + } else { + WriteBits( ReadBits( wordLength ), wordLength ); + startWord++; + } + } + + blockSize = Min( writeByte, LZSS_BLOCK_SIZE ); +} + +/* +================ +idCompressor_LZSS::Write +================ +*/ +int idCompressor_LZSS::Write( const void *inData, int inLength ) { + int i, n; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + for ( n = i = 0; i < inLength; i += n ) { + n = LZSS_BLOCK_SIZE - blockSize; + if ( inLength - i >= n ) { + memcpy( block + blockSize, ((const byte *)inData) + i, n ); + blockSize = LZSS_BLOCK_SIZE; + CompressBlock(); + blockSize = 0; + } else { + memcpy( block + blockSize, ((const byte *)inData) + i, inLength - i ); + n = inLength - i; + blockSize += n; + } + } + + return inLength; +} + +/* +================ +idCompressor_LZSS::FinishCompress +================ +*/ +void idCompressor_LZSS::FinishCompress() { + if ( compress == false ) { + return; + } + if ( blockSize ) { + CompressBlock(); + } + idCompressor_BitStream::FinishCompress(); +} + +/* +================ +idCompressor_LZSS::Read +================ +*/ +int idCompressor_LZSS::Read( void *outData, int outLength ) { + int i, n; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + if ( !blockSize ) { + DecompressBlock(); + } + + for ( n = i = 0; i < outLength; i += n ) { + if ( !blockSize ) { + return i; + } + n = blockSize - blockIndex; + if ( outLength - i >= n ) { + memcpy( ((byte *)outData) + i, block + blockIndex, n ); + DecompressBlock(); + blockIndex = 0; + } else { + memcpy( ((byte *)outData) + i, block + blockIndex, outLength - i ); + n = outLength - i; + blockIndex += n; + } + } + + return outLength; +} + +/* +================================================================================= + + idCompressor_LZSS_WordAligned + + Outputs word aligned compressed data. + +================================================================================= +*/ + +class idCompressor_LZSS_WordAligned : public idCompressor_LZSS { +public: + idCompressor_LZSS_WordAligned() {} + + void Init( idFile *f, bool compress, int wordLength ); +private: + virtual void CompressBlock(); + virtual void DecompressBlock(); +}; + +/* +================ +idCompressor_LZSS_WordAligned::Init +================ +*/ +void idCompressor_LZSS_WordAligned::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_LZSS::Init( f, compress, wordLength ); + + offsetBits = 2 * wordLength; + lengthBits = wordLength; + + minMatchWords = ( offsetBits + lengthBits + wordLength ) / wordLength; + blockSize = 0; + blockIndex = 0; +} + +/* +================ +idCompressor_LZSS_WordAligned::CompressBlock +================ +*/ +void idCompressor_LZSS_WordAligned::CompressBlock() { + int i, startWord, startValue, wordOffset, numWords; + + InitCompress( block, blockSize ); + + memset( hashTable, -1, sizeof( hashTable ) ); + memset( hashNext, -1, sizeof( hashNext ) ); + + startWord = 0; + while( readByte < readLength ) { + startValue = ReadBits( wordLength ); + if ( FindMatch( startWord, startValue, wordOffset, numWords ) ) { + WriteBits( numWords - ( minMatchWords - 1 ), lengthBits ); + WriteBits( startWord - wordOffset, offsetBits ); + UnreadBits( wordLength ); + for ( i = 0; i < numWords; i++ ) { + startValue = ReadBits( wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } else { + WriteBits( 0, lengthBits ); + WriteBits( startValue, wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } + + blockSize = 0; +} + +/* +================ +idCompressor_LZSS_WordAligned::DecompressBlock +================ +*/ +void idCompressor_LZSS_WordAligned::DecompressBlock() { + int i, offset, startWord, numWords; + + InitDecompress( block, LZSS_BLOCK_SIZE ); + + startWord = 0; + while( writeByte < writeLength && readLength >= 0 ) { + numWords = ReadBits( lengthBits ); + if ( numWords ) { + numWords += ( minMatchWords - 1 ); + offset = startWord - ReadBits( offsetBits ); + for ( i = 0; i < numWords; i++ ) { + WriteBits( GetWordFromBlock( offset + i ), wordLength ); + startWord++; + } + } else { + WriteBits( ReadBits( wordLength ), wordLength ); + startWord++; + } + } + + blockSize = Min( writeByte, LZSS_BLOCK_SIZE ); +} + +/* +================================================================================= + + idCompressor_LZW + + http://www.unisys.com/about__unisys/lzw + http://www.dogma.net/markn/articles/lzw/lzw.htm + http://www.cs.cf.ac.uk/Dave/Multimedia/node214.html + http://www.cs.duke.edu/csed/curious/compression/lzw.html + http://oldwww.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html + + This is the same compression scheme used by GIF with the exception that + the EOI and clear codes are not explicitly stored. Instead EOI happens + when the input stream runs dry and CC happens when the table gets to big. + + This is a derivation of LZ78, but the dictionary starts with all single + character values so only code words are output. It is similar in theory + to LZ77, but instead of using the previous X bytes as a lookup table, a table + is built as the stream is read. The compressor and decompressor use the + same formula, so the tables should be exactly alike. The only catch is the + decompressor is always one step behind the compressor and may get a code not + yet in the table. In this case, it is easy to determine what the next code + is going to be (it will be the previous string plus the first byte of the + previous string). + + The dictionary can be any size, but 12 bits seems to produce best results for + most sample data. The code size is variable. It starts with the minimum + number of bits required to store the dictionary and automatically increases + as the dictionary gets bigger (it starts at 9 bits and grows to 10 bits when + item 512 is added, 11 bits when 1024 is added, etc...) once the the dictionary + is filled (4096 items for a 12 bit dictionary), the whole thing is cleared and + the process starts over again. + + The compressor increases the bit size after it adds the item, while the + decompressor does before it adds the item. The difference is subtle, but + it's because decompressor being one step behind. Otherwise, the decompressor + would read 512 with only 9 bits. + + If "Hello" is in the dictionary, then "Hell", "Hel", "He" and "H" will be too. + We use this to our advantage by storing the index of the previous code, and + the value of the last character. This means when we traverse through the + dictionary, we get the characters in reverse. + + Dictionary entries 0-255 are always going to have the values 0-255 + +================================================================================= +*/ + +class idCompressor_LZW : public idCompressor_BitStream { +public: + idCompressor_LZW() {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress(); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +protected: + int AddToDict( int w, int k ); + int Lookup( int w, int k ); + + bool BumpBits(); + + int WriteChain( int code ); + void DecompressBlock(); + + static const int LZW_BLOCK_SIZE = 32767; + static const int LZW_START_BITS = 9; + static const int LZW_FIRST_CODE = (1 << (LZW_START_BITS-1)); + static const int LZW_DICT_BITS = 12; + static const int LZW_DICT_SIZE = 1 << LZW_DICT_BITS; + + // Dictionary data + struct { + int k; + int w; + } dictionary[LZW_DICT_SIZE]; + idHashIndex index; + + int nextCode; + int codeBits; + + // Block data + byte block[LZW_BLOCK_SIZE]; + int blockSize; + int blockIndex; + + // Used by the compressor + int w; + + // Used by the decompressor + int oldCode; +}; + +/* +================ +idCompressor_LZW::Init +================ +*/ +void idCompressor_LZW::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_BitStream::Init( f, compress, wordLength ); + + for ( int i=0; i= n ) { + memcpy( ((byte *)outData) + i, block + blockIndex, n ); + DecompressBlock(); + blockIndex = 0; + } else { + memcpy( ((byte *)outData) + i, block + blockIndex, outLength - i ); + n = outLength - i; + blockIndex += n; + } + } + + return outLength; +} + +/* +================ +idCompressor_LZW::Lookup +================ +*/ +int idCompressor_LZW::Lookup( int w, int k ) { + int j; + + if ( w == -1 ) { + return k; + } else { + for ( j = index.First( w ^ k ); j >= 0 ; j = index.Next( j ) ) { + if ( dictionary[ j ].k == k && dictionary[ j ].w == w ) { + return j; + } + } + } + + return -1; +} + +/* +================ +idCompressor_LZW::AddToDict +================ +*/ +int idCompressor_LZW::AddToDict( int w, int k ) { + dictionary[ nextCode ].k = k; + dictionary[ nextCode ].w = w; + index.Add( w ^ k, nextCode ); + return nextCode++; +} + +/* +================ +idCompressor_LZW::BumpBits + +Possibly increments codeBits +Returns true if the dictionary was cleared +================ +*/ +bool idCompressor_LZW::BumpBits() { + if ( nextCode == ( 1 << codeBits ) ) { + codeBits ++; + if ( codeBits > LZW_DICT_BITS ) { + nextCode = LZW_FIRST_CODE; + codeBits = LZW_START_BITS; + index.Clear(); + return true; + } + } + return false; +} + +/* +================ +idCompressor_LZW::FinishCompress +================ +*/ +void idCompressor_LZW::FinishCompress() { + WriteBits( w, codeBits ); + idCompressor_BitStream::FinishCompress(); +} + +/* +================ +idCompressor_LZW::Write +================ +*/ +int idCompressor_LZW::Write( const void *inData, int inLength ) { + int i; + + InitCompress( inData, inLength ); + + for ( i = 0; i < inLength; i++ ) { + int k = ReadBits( 8 ); + + int code = Lookup(w, k); + if ( code >= 0 ) { + w = code; + } else { + WriteBits( w, codeBits ); + if ( !BumpBits() ) { + AddToDict( w, k ); + } + w = k; + } + } + + return inLength; +} + + +/* +================ +idCompressor_LZW::WriteChain +The chain is stored backwards, so we have to write it to a buffer then output the buffer in reverse +================ +*/ +int idCompressor_LZW::WriteChain( int code ) { + byte chain[LZW_DICT_SIZE]; + int firstChar = 0; + int i = 0; + do { + assert( i < LZW_DICT_SIZE-1 && code >= 0 ); + chain[i++] = dictionary[code].k; + code = dictionary[code].w; + } while ( code >= 0 ); + firstChar = chain[--i]; + for ( ; i >= 0; i-- ) { + WriteBits( chain[i], 8 ); + } + return firstChar; +} + +/* +================ +idCompressor_LZW::DecompressBlock +================ +*/ +void idCompressor_LZW::DecompressBlock() { + int code, firstChar; + + InitDecompress( block, LZW_BLOCK_SIZE ); + + while( writeByte < writeLength - LZW_DICT_SIZE && readLength > 0 ) { + assert( codeBits <= LZW_DICT_BITS ); + + code = ReadBits( codeBits ); + if ( readLength == 0 ) { + break; + } + + if ( oldCode == -1 ) { + assert( code < 256 ); + WriteBits( code, 8 ); + oldCode = code; + firstChar = code; + continue; + } + + if ( code >= nextCode ) { + assert( code == nextCode ); + firstChar = WriteChain( oldCode ); + WriteBits( firstChar, 8 ); + } else { + firstChar = WriteChain( code ); + } + AddToDict( oldCode, firstChar ); + if ( BumpBits() ) { + oldCode = -1; + } else { + oldCode = code; + } + } + + blockSize = Min( writeByte, LZW_BLOCK_SIZE ); +} + +/* +================================================================================= + + idCompressor + +================================================================================= +*/ + +/* +================ +idCompressor::AllocNoCompression +================ +*/ +idCompressor * idCompressor::AllocNoCompression() { + return new (TAG_IDFILE) idCompressor_None(); +} + +/* +================ +idCompressor::AllocBitStream +================ +*/ +idCompressor * idCompressor::AllocBitStream() { + return new (TAG_IDFILE) idCompressor_BitStream(); +} + +/* +================ +idCompressor::AllocRunLength +================ +*/ +idCompressor * idCompressor::AllocRunLength() { + return new (TAG_IDFILE) idCompressor_RunLength(); +} + +/* +================ +idCompressor::AllocRunLength_ZeroBased +================ +*/ +idCompressor * idCompressor::AllocRunLength_ZeroBased() { + return new (TAG_IDFILE) idCompressor_RunLength_ZeroBased(); +} + +/* +================ +idCompressor::AllocHuffman +================ +*/ +idCompressor * idCompressor::AllocHuffman() { + return new (TAG_IDFILE) idCompressor_Huffman(); +} + +/* +================ +idCompressor::AllocArithmetic +================ +*/ +idCompressor * idCompressor::AllocArithmetic() { + return new (TAG_IDFILE) idCompressor_Arithmetic(); +} + +/* +================ +idCompressor::AllocLZSS +================ +*/ +idCompressor * idCompressor::AllocLZSS() { + return new (TAG_IDFILE) idCompressor_LZSS(); +} + +/* +================ +idCompressor::AllocLZSS_WordAligned +================ +*/ +idCompressor * idCompressor::AllocLZSS_WordAligned() { + return new (TAG_IDFILE) idCompressor_LZSS_WordAligned(); +} + +/* +================ +idCompressor::AllocLZW +================ +*/ +idCompressor * idCompressor::AllocLZW() { + return new (TAG_IDFILE) idCompressor_LZW(); +} diff --git a/neo/framework/Compressor.h b/neo/framework/Compressor.h new file mode 100644 index 00000000..705f52a0 --- /dev/null +++ b/neo/framework/Compressor.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __COMPRESSOR_H__ +#define __COMPRESSOR_H__ + +/* +=============================================================================== + + idCompressor is a layer ontop of idFile which provides lossless data + compression. The compressor can be used as a regular file and multiple + compressors can be stacked ontop of each other. + +=============================================================================== +*/ + +class idCompressor : public idFile { +public: + // compressor allocation + static idCompressor * AllocNoCompression(); + static idCompressor * AllocBitStream(); + static idCompressor * AllocRunLength(); + static idCompressor * AllocRunLength_ZeroBased(); + static idCompressor * AllocHuffman(); + static idCompressor * AllocArithmetic(); + static idCompressor * AllocLZSS(); + static idCompressor * AllocLZSS_WordAligned(); + static idCompressor * AllocLZW(); + + // initialization + virtual void Init( idFile *f, bool compress, int wordLength ) = 0; + virtual void FinishCompress() = 0; + virtual float GetCompressionRatio() const = 0; + + // common idFile interface + virtual const char * GetName() = 0; + virtual const char * GetFullPath() = 0; + virtual int Read( void *outData, int outLength ) = 0; + virtual int Write( const void *inData, int inLength ) = 0; + virtual int Length() = 0; + virtual ID_TIME_T Timestamp() = 0; + virtual int Tell() = 0; + virtual void ForceFlush() = 0; + virtual void Flush() = 0; + virtual int Seek( long offset, fsOrigin_t origin ) = 0; +}; + +#endif /* !__COMPRESSOR_H__ */ diff --git a/neo/framework/Console.cpp b/neo/framework/Console.cpp new file mode 100644 index 00000000..4daf8822 --- /dev/null +++ b/neo/framework/Console.cpp @@ -0,0 +1,1249 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "ConsoleHistory.h" +#include "../renderer/ResolutionScale.h" +#include "Common_local.h" + +#define CON_TEXTSIZE 0x30000 +#define NUM_CON_TIMES 4 +#define CONSOLE_FIRSTREPEAT 200 +#define CONSOLE_REPEAT 100 + +#define COMMAND_HISTORY 64 + +struct overlayText_t { + idStr text; + justify_t justify; + int time; +}; + +// the console will query the cvar and command systems for +// command completion information + +class idConsoleLocal : public idConsole { +public: + virtual void Init(); + virtual void Shutdown(); + virtual bool ProcessEvent( const sysEvent_t * event, bool forceAccept ); + virtual bool Active(); + virtual void ClearNotifyLines(); + virtual void Close(); + virtual void Print( const char *text ); + virtual void Draw( bool forceFullScreen ); + + virtual void PrintOverlay( idOverlayHandle &handle, justify_t justify, const char *text, ... ); + + virtual idDebugGraph * CreateGraph( int numItems ); + virtual void DestroyGraph( idDebugGraph * graph ); + + void Dump( const char *toFile ); + void Clear(); + +private: + void KeyDownEvent( int key ); + + void Linefeed(); + + void PageUp(); + void PageDown(); + void Top(); + void Bottom(); + + void DrawInput(); + void DrawNotify(); + void DrawSolidConsole( float frac ); + + void Scroll(); + void SetDisplayFraction( float frac ); + void UpdateDisplayFraction(); + + void DrawTextLeftAlign( float x, float &y, const char *text, ... ); + void DrawTextRightAlign( float x, float &y, const char *text, ... ); + + float DrawFPS( float y ); + float DrawMemoryUsage( float y ); + + void DrawOverlayText( float & leftY, float & rightY, float & centerY ); + void DrawDebugGraphs(); + + //============================ + + // allow these constants to be adjusted for HMD + int LOCALSAFE_LEFT; + int LOCALSAFE_RIGHT; + int LOCALSAFE_TOP; + int LOCALSAFE_BOTTOM; + int LOCALSAFE_WIDTH; + int LOCALSAFE_HEIGHT; + int LINE_WIDTH; + int TOTAL_LINES; + + bool keyCatching; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + int lastKeyEvent; // time of last key event for scroll delay + int nextKeyEvent; // keyboard repeat rate + + float displayFrac; // approaches finalFrac at con_speed + float finalFrac; // 0.0 to 1.0 lines of console to display + int fracTime; // time of last displayFrac update + + int vislines; // in scanlines + + int times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines + idVec4 color; + + idEditField historyEditLines[COMMAND_HISTORY]; + + int nextHistoryLine;// the last line in the history buffer, not masked + int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + + idEditField consoleField; + + idList< overlayText_t > overlayText; + idList< idDebugGraph *> debugGraphs; + + static idCVar con_speed; + static idCVar con_notifyTime; + static idCVar con_noPrint; +}; + +static idConsoleLocal localConsole; +idConsole * console = &localConsole; + +idCVar idConsoleLocal::con_speed( "con_speed", "3", CVAR_SYSTEM, "speed at which the console moves up and down" ); +idCVar idConsoleLocal::con_notifyTime( "con_notifyTime", "3", CVAR_SYSTEM, "time messages are displayed onscreen when console is pulled up" ); +#ifdef DEBUG +idCVar idConsoleLocal::con_noPrint( "con_noPrint", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" ); +#else +idCVar idConsoleLocal::con_noPrint( "con_noPrint", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" ); +#endif + +/* +============================================================================= + + Misc stats + +============================================================================= +*/ + +/* +================== +idConsoleLocal::DrawTextLeftAlign +================== +*/ +void idConsoleLocal::DrawTextLeftAlign( float x, float &y, const char *text, ... ) { + char string[MAX_STRING_CHARS]; + va_list argptr; + va_start( argptr, text ); + idStr::vsnPrintf( string, sizeof( string ), text, argptr ); + va_end( argptr ); + renderSystem->DrawSmallStringExt( x, y + 2, string, colorWhite, true ); + y += SMALLCHAR_HEIGHT + 4; +} + +/* +================== +idConsoleLocal::DrawTextRightAlign +================== +*/ +void idConsoleLocal::DrawTextRightAlign( float x, float &y, const char *text, ... ) { + char string[MAX_STRING_CHARS]; + va_list argptr; + va_start( argptr, text ); + int i = idStr::vsnPrintf( string, sizeof( string ), text, argptr ); + va_end( argptr ); + renderSystem->DrawSmallStringExt( x - i * SMALLCHAR_WIDTH, y + 2, string, colorWhite, true ); + y += SMALLCHAR_HEIGHT + 4; +} + + + + +/* +================== +idConsoleLocal::DrawFPS +================== +*/ +#define FPS_FRAMES 6 +float idConsoleLocal::DrawFPS( float y ) { + static int previousTimes[FPS_FRAMES]; + static int index; + static int previous; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + int t = Sys_Milliseconds(); + int frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + int total = 0; + for ( int i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + int fps = 1000000 * FPS_FRAMES / total; + fps = ( fps + 500 ) / 1000; + + const char * s = va( "%ifps", fps ); + int w = strlen( s ) * BIGCHAR_WIDTH; + + renderSystem->DrawBigStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, s, colorWhite, true ); + } + + y += BIGCHAR_HEIGHT + 4; + + // print the resolution scale so we can tell when we are at reduced resolution + idStr resolutionText; + resolutionScale.GetConsoleText( resolutionText ); + int w = resolutionText.Length() * BIGCHAR_WIDTH; + renderSystem->DrawBigStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, resolutionText.c_str(), colorWhite, true ); + + const int gameThreadTotalTime = commonLocal.GetGameThreadTotalTime(); + const int gameThreadGameTime = commonLocal.GetGameThreadGameTime(); + const int gameThreadRenderTime = commonLocal.GetGameThreadRenderTime(); + const int rendererBackEndTime = commonLocal.GetRendererBackEndMicroseconds(); + const int rendererShadowsTime = commonLocal.GetRendererShadowsMicroseconds(); + const int rendererGPUIdleTime = commonLocal.GetRendererIdleMicroseconds(); + const int rendererGPUTime = commonLocal.GetRendererGPUMicroseconds(); + const int maxTime = 16; + + y += SMALLCHAR_HEIGHT + 4; + idStr timeStr; + timeStr.Format( "%sG+RF: %4d", gameThreadTotalTime > maxTime ? S_COLOR_RED : "", gameThreadTotalTime ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + y += SMALLCHAR_HEIGHT + 4; + + timeStr.Format( "%sG: %4d", gameThreadGameTime > maxTime ? S_COLOR_RED : "", gameThreadGameTime ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + y += SMALLCHAR_HEIGHT + 4; + + timeStr.Format( "%sRF: %4d", gameThreadRenderTime > maxTime ? S_COLOR_RED : "", gameThreadRenderTime ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + y += SMALLCHAR_HEIGHT + 4; + + timeStr.Format( "%sRB: %4.1f", rendererBackEndTime > maxTime * 1000 ? S_COLOR_RED : "", rendererBackEndTime / 1000.0f ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + y += SMALLCHAR_HEIGHT + 4; + + timeStr.Format( "%sSV: %4.1f", rendererShadowsTime > maxTime * 1000 ? S_COLOR_RED : "", rendererShadowsTime / 1000.0f ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + y += SMALLCHAR_HEIGHT + 4; + + timeStr.Format( "%sIDLE: %4.1f", rendererGPUIdleTime > maxTime * 1000 ? S_COLOR_RED : "", rendererGPUIdleTime / 1000.0f ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + y += SMALLCHAR_HEIGHT + 4; + + timeStr.Format( "%sGPU: %4.1f", rendererGPUTime > maxTime * 1000 ? S_COLOR_RED : "", rendererGPUTime / 1000.0f ); + w = timeStr.LengthWithoutColors() * SMALLCHAR_WIDTH; + renderSystem->DrawSmallStringExt( LOCALSAFE_RIGHT - w, idMath::Ftoi( y ) + 2, timeStr.c_str(), colorWhite, false ); + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +idConsoleLocal::DrawMemoryUsage +================== +*/ +float idConsoleLocal::DrawMemoryUsage( float y ) { + return y; +} + +//========================================================================= + +/* +============== +Con_Clear_f +============== +*/ +static void Con_Clear_f( const idCmdArgs &args ) { + localConsole.Clear(); +} + +/* +============== +Con_Dump_f +============== +*/ +static void Con_Dump_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "usage: conDump \n" ); + return; + } + + idStr fileName = args.Argv(1); + fileName.DefaultFileExtension(".txt"); + + common->Printf( "Dumped console text to %s.\n", fileName.c_str() ); + + localConsole.Dump( fileName.c_str() ); +} + +/* +============== +idConsoleLocal::Init +============== +*/ +void idConsoleLocal::Init() { + int i; + + keyCatching = false; + + LOCALSAFE_LEFT = 32; + LOCALSAFE_RIGHT = 608; + LOCALSAFE_TOP = 24; + LOCALSAFE_BOTTOM = 456; + LOCALSAFE_WIDTH = LOCALSAFE_RIGHT - LOCALSAFE_LEFT; + LOCALSAFE_HEIGHT = LOCALSAFE_BOTTOM - LOCALSAFE_TOP; + + LINE_WIDTH = ( ( LOCALSAFE_WIDTH / SMALLCHAR_WIDTH ) - 2 ); + TOTAL_LINES = (CON_TEXTSIZE / LINE_WIDTH); + + lastKeyEvent = -1; + nextKeyEvent = CONSOLE_FIRSTREPEAT; + + consoleField.Clear(); + consoleField.SetWidthInChars( LINE_WIDTH ); + + for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { + historyEditLines[i].Clear(); + historyEditLines[i].SetWidthInChars( LINE_WIDTH ); + } + + cmdSystem->AddCommand( "clear", Con_Clear_f, CMD_FL_SYSTEM, "clears the console" ); + cmdSystem->AddCommand( "conDump", Con_Dump_f, CMD_FL_SYSTEM, "dumps the console text to a file" ); +} + +/* +============== +idConsoleLocal::Shutdown +============== +*/ +void idConsoleLocal::Shutdown() { + cmdSystem->RemoveCommand( "clear" ); + cmdSystem->RemoveCommand( "conDump" ); + + debugGraphs.DeleteContents( true ); +} + +/* +================ +idConsoleLocal::Active +================ +*/ +bool idConsoleLocal::Active() { + return keyCatching; +} + +/* +================ +idConsoleLocal::ClearNotifyLines +================ +*/ +void idConsoleLocal::ClearNotifyLines() { + int i; + + for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { + times[i] = 0; + } +} + +/* +================ +idConsoleLocal::Close +================ +*/ +void idConsoleLocal::Close() { + keyCatching = false; + SetDisplayFraction( 0 ); + displayFrac = 0; // don't scroll to that point, go immediately + ClearNotifyLines(); +} + +/* +================ +idConsoleLocal::Clear +================ +*/ +void idConsoleLocal::Clear() { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + text[i] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' '; + } + + Bottom(); // go to end +} + +/* +================ +idConsoleLocal::Dump + +Save the console contents out to a file +================ +*/ +void idConsoleLocal::Dump( const char *fileName ) { + int l, x, i; + short * line; + idFile *f; + char * buffer = (char *)alloca( LINE_WIDTH + 3 ); + + f = fileSystem->OpenFileWrite( fileName ); + if ( !f ) { + common->Warning( "couldn't open %s", fileName ); + return; + } + + // skip empty lines + l = current - TOTAL_LINES + 1; + if ( l < 0 ) { + l = 0; + } + for ( ; l <= current ; l++ ) + { + line = text + ( l % TOTAL_LINES ) * LINE_WIDTH; + for ( x = 0; x < LINE_WIDTH; x++ ) + if ( ( line[x] & 0xff ) > ' ' ) + break; + if ( x != LINE_WIDTH ) + break; + } + + // write the remaining lines + for ( ; l <= current; l++ ) { + line = text + ( l % TOTAL_LINES ) * LINE_WIDTH; + for( i = 0; i < LINE_WIDTH; i++ ) { + buffer[i] = line[i] & 0xff; + } + for ( x = LINE_WIDTH-1; x >= 0; x-- ) { + if ( buffer[x] <= ' ' ) { + buffer[x] = 0; + } else { + break; + } + } + buffer[x+1] = '\r'; + buffer[x+2] = '\n'; + buffer[x+3] = 0; + f->Write( buffer, strlen( buffer ) ); + } + + fileSystem->CloseFile( f ); +} + +/* +================ +idConsoleLocal::PageUp +================ +*/ +void idConsoleLocal::PageUp() { + display -= 2; + if ( current - display >= TOTAL_LINES ) { + display = current - TOTAL_LINES + 1; + } +} + +/* +================ +idConsoleLocal::PageDown +================ +*/ +void idConsoleLocal::PageDown() { + display += 2; + if ( display > current ) { + display = current; + } +} + +/* +================ +idConsoleLocal::Top +================ +*/ +void idConsoleLocal::Top() { + display = 0; +} + +/* +================ +idConsoleLocal::Bottom +================ +*/ +void idConsoleLocal::Bottom() { + display = current; +} + + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +/* +==================== +KeyDownEvent + +Handles history and console scrollback +==================== +*/ +void idConsoleLocal::KeyDownEvent( int key ) { + + // Execute F key bindings + if ( key >= K_F1 && key <= K_F12 ) { + idKeyInput::ExecKeyBinding( key ); + return; + } + + // ctrl-L clears screen + if ( key == K_L && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + Clear(); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + + common->Printf ( "]%s\n", consoleField.GetBuffer() ); + + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, consoleField.GetBuffer() ); // valid command + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); + + // copy line to history buffer + + if ( consoleField.GetBuffer()[ 0 ] != '\n' && consoleField.GetBuffer()[ 0 ] != '\0' ) { + consoleHistory.AddToHistory( consoleField.GetBuffer() ); + } + + consoleField.Clear(); + consoleField.SetWidthInChars( LINE_WIDTH ); + + const bool captureToImage = false; + common->UpdateScreen( captureToImage );// force an update, because the command + // may take some time + return; + } + + // command completion + + if ( key == K_TAB ) { + consoleField.AutoComplete(); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( ( key == K_UPARROW ) || + ( key == K_P && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) { + idStr hist = consoleHistory.RetrieveFromHistory( true ); + if ( !hist.IsEmpty() ) { + consoleField.SetBuffer( hist ); + } + return; + } + + if ( ( key == K_DOWNARROW ) || + ( key == K_N && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) { + idStr hist = consoleHistory.RetrieveFromHistory( false ); + if ( !hist.IsEmpty() ) { + consoleField.SetBuffer( hist ); + } + return; + } + + // console scrolling + if ( key == K_PGUP ) { + PageUp(); + lastKeyEvent = eventLoop->Milliseconds(); + nextKeyEvent = CONSOLE_FIRSTREPEAT; + return; + } + + if ( key == K_PGDN ) { + PageDown(); + lastKeyEvent = eventLoop->Milliseconds(); + nextKeyEvent = CONSOLE_FIRSTREPEAT; + return; + } + + if ( key == K_MWHEELUP ) { + PageUp(); + return; + } + + if ( key == K_MWHEELDOWN ) { + PageDown(); + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + Bottom(); + return; + } + + // pass to the normal editline routine + consoleField.KeyDownEvent( key ); +} + +/* +============== +Scroll +deals with scrolling text because we don't have key repeat +============== +*/ +void idConsoleLocal::Scroll( ) { + if (lastKeyEvent == -1 || (lastKeyEvent+200) > eventLoop->Milliseconds()) { + return; + } + // console scrolling + if ( idKeyInput::IsDown( K_PGUP ) ) { + PageUp(); + nextKeyEvent = CONSOLE_REPEAT; + return; + } + + if ( idKeyInput::IsDown( K_PGDN ) ) { + PageDown(); + nextKeyEvent = CONSOLE_REPEAT; + return; + } +} + +/* +============== +SetDisplayFraction + +Causes the console to start opening the desired amount. +============== +*/ +void idConsoleLocal::SetDisplayFraction( float frac ) { + finalFrac = frac; + fracTime = Sys_Milliseconds(); +} + +/* +============== +UpdateDisplayFraction + +Scrolls the console up or down based on conspeed +============== +*/ +void idConsoleLocal::UpdateDisplayFraction() { + if ( con_speed.GetFloat() <= 0.1f ) { + fracTime = Sys_Milliseconds(); + displayFrac = finalFrac; + return; + } + + // scroll towards the destination height + if ( finalFrac < displayFrac ) { + displayFrac -= con_speed.GetFloat() * ( Sys_Milliseconds() - fracTime ) * 0.001f; + if ( finalFrac > displayFrac ) { + displayFrac = finalFrac; + } + fracTime = Sys_Milliseconds(); + } else if ( finalFrac > displayFrac ) { + displayFrac += con_speed.GetFloat() * ( Sys_Milliseconds() - fracTime ) * 0.001f; + if ( finalFrac < displayFrac ) { + displayFrac = finalFrac; + } + fracTime = Sys_Milliseconds(); + } +} + +/* +============== +ProcessEvent +============== +*/ +bool idConsoleLocal::ProcessEvent( const sysEvent_t *event, bool forceAccept ) { + const bool consoleKey = event->evType == SE_KEY && event->evValue == K_GRAVE && com_allowConsole.GetBool(); + + // we always catch the console key event + if ( !forceAccept && consoleKey ) { + // ignore up events + if ( event->evValue2 == 0 ) { + return true; + } + + consoleField.ClearAutoComplete(); + + // a down event will toggle the destination lines + if ( keyCatching ) { + Close(); + Sys_GrabMouseCursor( true ); + } else { + consoleField.Clear(); + keyCatching = true; + if ( idKeyInput::IsDown( K_LSHIFT ) || idKeyInput::IsDown( K_RSHIFT ) ) { + // if the shift key is down, don't open the console as much + SetDisplayFraction( 0.2f ); + } else { + SetDisplayFraction( 0.5f ); + } + } + return true; + } + + // if we aren't key catching, dump all the other events + if ( !forceAccept && !keyCatching ) { + return false; + } + + // handle key and character events + if ( event->evType == SE_CHAR ) { + // never send the console key as a character + if ( event->evValue != '`' && event->evValue != '~' ) { + consoleField.CharEvent( event->evValue ); + } + return true; + } + + if ( event->evType == SE_KEY ) { + // ignore up key events + if ( event->evValue2 == 0 ) { + return true; + } + + KeyDownEvent( event->evValue ); + return true; + } + + // we don't handle things like mouse, joystick, and network packets + return false; +} + +/* +============================================================================== + +PRINTING + +============================================================================== +*/ + +/* +=============== +Linefeed +=============== +*/ +void idConsoleLocal::Linefeed() { + int i; + + // mark time for transparent overlay + if ( current >= 0 ) { + times[current % NUM_CON_TIMES] = Sys_Milliseconds(); + } + + x = 0; + if ( display == current ) { + display++; + } + current++; + for ( i = 0; i < LINE_WIDTH; i++ ) { + int offset = ( (unsigned int)current % TOTAL_LINES ) * LINE_WIDTH + i; + text[offset] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' '; + } +} + + +/* +================ +Print + +Handles cursor positioning, line wrapping, etc +================ +*/ +void idConsoleLocal::Print( const char *txt ) { + int y; + int c, l; + int color; + + if ( TOTAL_LINES == 0 ) { + // not yet initialized + return; + } + + color = idStr::ColorIndex( C_COLOR_CYAN ); + + while ( (c = *(const unsigned char*)txt) != 0 ) { + if ( idStr::IsColor( txt ) ) { + if ( *(txt+1) == C_COLOR_DEFAULT ) { + color = idStr::ColorIndex( C_COLOR_CYAN ); + } else { + color = idStr::ColorIndex( *(txt+1) ); + } + txt += 2; + continue; + } + + y = current % TOTAL_LINES; + + // if we are about to print a new word, check to see + // if we should wrap to the new line + if ( c > ' ' && ( x == 0 || text[y*LINE_WIDTH+x-1] <= ' ' ) ) { + // count word length + for (l=0 ; l< LINE_WIDTH ; l++) { + if ( txt[l] <= ' ') { + break; + } + } + + // word wrap + if (l != LINE_WIDTH && (x + l >= LINE_WIDTH) ) { + Linefeed(); + } + } + + txt++; + + switch( c ) { + case '\n': + Linefeed (); + break; + case '\t': + do { + text[y*LINE_WIDTH+x] = (color << 8) | ' '; + x++; + if ( x >= LINE_WIDTH ) { + Linefeed(); + x = 0; + } + } while ( x & 3 ); + break; + case '\r': + x = 0; + break; + default: // display character and advance + text[y*LINE_WIDTH+x] = (color << 8) | c; + x++; + if ( x >= LINE_WIDTH ) { + Linefeed(); + x = 0; + } + break; + } + } + + + // mark time for transparent overlay + if ( current >= 0 ) { + times[current % NUM_CON_TIMES] = Sys_Milliseconds(); + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +DrawInput + +Draw the editline after a ] prompt +================ +*/ +void idConsoleLocal::DrawInput() { + int y, autoCompleteLength; + + y = vislines - ( SMALLCHAR_HEIGHT * 2 ); + + if ( consoleField.GetAutoCompleteLength() != 0 ) { + autoCompleteLength = strlen( consoleField.GetBuffer() ) - consoleField.GetAutoCompleteLength(); + + if ( autoCompleteLength > 0 ) { + renderSystem->DrawFilled( idVec4( 0.8f, 0.2f, 0.2f, 0.45f ), + LOCALSAFE_LEFT + 2 * SMALLCHAR_WIDTH + consoleField.GetAutoCompleteLength() * SMALLCHAR_WIDTH, + y + 2, autoCompleteLength * SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT - 2 ); + } + } + + renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); + + renderSystem->DrawSmallChar( LOCALSAFE_LEFT + 1 * SMALLCHAR_WIDTH, y, ']' ); + + consoleField.Draw( LOCALSAFE_LEFT + 2 * SMALLCHAR_WIDTH, y, SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, true ); +} + + +/* +================ +DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void idConsoleLocal::DrawNotify() { + int x, v; + short *text_p; + int i; + int time; + int currentColor; + + if ( con_noPrint.GetBool() ) { + return; + } + + currentColor = idStr::ColorIndex( C_COLOR_WHITE ); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + + v = 0; + for ( i = current-NUM_CON_TIMES+1; i <= current; i++ ) { + if ( i < 0 ) { + continue; + } + time = times[i % NUM_CON_TIMES]; + if ( time == 0 ) { + continue; + } + time = Sys_Milliseconds() - time; + if ( time > con_notifyTime.GetFloat() * 1000 ) { + continue; + } + text_p = text + (i % TOTAL_LINES)*LINE_WIDTH; + + for ( x = 0; x < LINE_WIDTH; x++ ) { + if ( ( text_p[x] & 0xff ) == ' ' ) { + continue; + } + if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) { + currentColor = idStr::ColorIndex(text_p[x]>>8); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + } + renderSystem->DrawSmallChar( LOCALSAFE_LEFT + (x+1)*SMALLCHAR_WIDTH, v, text_p[x] & 0xff ); + } + + v += SMALLCHAR_HEIGHT; + } + + renderSystem->SetColor( colorCyan ); +} + +/* +================ +DrawSolidConsole + +Draws the console with the solid background +================ +*/ +void idConsoleLocal::DrawSolidConsole( float frac ) { + int i, x; + float y; + int rows; + short *text_p; + int row; + int lines; + int currentColor; + + lines = idMath::Ftoi( SCREEN_HEIGHT * frac ); + if ( lines <= 0 ) { + return; + } + + if ( lines > SCREEN_HEIGHT ) { + lines = SCREEN_HEIGHT; + } + + // draw the background + y = frac * SCREEN_HEIGHT - 2; + if ( y < 1.0f ) { + y = 0.0f; + } else { + renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.75f ), 0, 0, SCREEN_WIDTH, y ); + } + + renderSystem->DrawFilled( colorCyan, 0, y, SCREEN_WIDTH, 2 ); + + // draw the version number + + renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); + + idStr version = va( "%s.%i.%i", ENGINE_VERSION, BUILD_NUMBER, BUILD_NUMBER_MINOR ); + i = version.Length(); + + for ( x = 0; x < i; x++ ) { + renderSystem->DrawSmallChar( LOCALSAFE_WIDTH - ( i - x ) * SMALLCHAR_WIDTH, + (lines-(SMALLCHAR_HEIGHT+SMALLCHAR_HEIGHT/4)), version[x] ); + + } + + + // draw the text + vislines = lines; + rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH; // rows of text to draw + + y = lines - (SMALLCHAR_HEIGHT*3); + + // draw from the bottom up + if ( display != current ) { + // draw arrows to show the buffer is backscrolled + renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); + for ( x = 0; x < LINE_WIDTH; x += 4 ) { + renderSystem->DrawSmallChar( LOCALSAFE_LEFT + (x+1)*SMALLCHAR_WIDTH, idMath::Ftoi( y ), '^' ); + } + y -= SMALLCHAR_HEIGHT; + rows--; + } + + row = display; + + if ( x == 0 ) { + row--; + } + + currentColor = idStr::ColorIndex( C_COLOR_WHITE ); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + + for ( i = 0; i < rows; i++, y -= SMALLCHAR_HEIGHT, row-- ) { + if ( row < 0 ) { + break; + } + if ( current - row >= TOTAL_LINES ) { + // past scrollback wrap point + continue; + } + + text_p = text + (row % TOTAL_LINES)*LINE_WIDTH; + + for ( x = 0; x < LINE_WIDTH; x++ ) { + if ( ( text_p[x] & 0xff ) == ' ' ) { + continue; + } + + if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) { + currentColor = idStr::ColorIndex(text_p[x]>>8); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + } + renderSystem->DrawSmallChar( LOCALSAFE_LEFT + (x+1)*SMALLCHAR_WIDTH, idMath::Ftoi( y ), text_p[x] & 0xff ); + } + } + + // draw the input prompt, user text, and cursor if desired + DrawInput(); + + renderSystem->SetColor( colorCyan ); +} + + +/* +============== +Draw + +ForceFullScreen is used by the editor +============== +*/ +void idConsoleLocal::Draw( bool forceFullScreen ) { + if ( forceFullScreen ) { + // if we are forced full screen because of a disconnect, + // we want the console closed when we go back to a session state + Close(); + // we are however catching keyboard input + keyCatching = true; + } + + Scroll(); + + UpdateDisplayFraction(); + + if ( forceFullScreen ) { + DrawSolidConsole( 1.0f ); + } else if ( displayFrac ) { + DrawSolidConsole( displayFrac ); + } else { + // only draw the notify lines if the developer cvar is set, + // or we are a debug build + if ( !con_noPrint.GetBool() ) { + DrawNotify(); + } + } + + float lefty = LOCALSAFE_TOP; + float righty = LOCALSAFE_TOP; + float centery = LOCALSAFE_TOP; + if ( com_showFPS.GetBool() ) { + righty = DrawFPS( righty ); + } + if ( com_showMemoryUsage.GetBool() ) { + righty = DrawMemoryUsage( righty ); + } + DrawOverlayText( lefty, righty, centery ); + DrawDebugGraphs(); +} + +/* +======================== +idConsoleLocal::PrintOverlay +======================== +*/ +void idConsoleLocal::PrintOverlay( idOverlayHandle &handle, justify_t justify, const char *text, ... ) { + if ( handle.index >= 0 && handle.index < overlayText.Num() ) { + if ( overlayText[handle.index].time == handle.time ) { + return; + } + } + + char string[MAX_PRINT_MSG]; + va_list argptr; + va_start( argptr, text ); + idStr::vsnPrintf( string, sizeof( string ), text, argptr ); + va_end( argptr ); + + overlayText_t &overlay = overlayText.Alloc(); + overlay.text = string; + overlay.justify = justify; + overlay.time = Sys_Milliseconds(); + + handle.index = overlayText.Num() - 1; + handle.time = overlay.time; +} + +/* +======================== +idConsoleLocal::DrawOverlayText +======================== +*/ +void idConsoleLocal::DrawOverlayText( float & leftY, float & rightY, float & centerY ) { + for ( int i = 0; i < overlayText.Num(); i++ ) { + const idStr & text = overlayText[i].text; + + int maxWidth = 0; + int numLines = 0; + for ( int j = 0; j < text.Length(); j++ ) { + int width = 1; + for (; j < text.Length() && text[j] != '\n'; j++ ) { + width++; + } + numLines++; + if ( width > maxWidth ) { + maxWidth = width; + } + } + + idVec4 bgColor( 0.0f, 0.0f, 0.0f, 0.75f ); + + const float width = maxWidth * SMALLCHAR_WIDTH; + const float height = numLines * ( SMALLCHAR_HEIGHT + 4 ); + const float bgAdjust = - 0.5f * SMALLCHAR_WIDTH; + if ( overlayText[i].justify == JUSTIFY_LEFT ) { + renderSystem->DrawFilled( bgColor, LOCALSAFE_LEFT + bgAdjust, leftY, width, height ); + } else if ( overlayText[i].justify == JUSTIFY_RIGHT ) { + renderSystem->DrawFilled( bgColor, LOCALSAFE_RIGHT - width + bgAdjust, rightY, width, height ); + } else if ( overlayText[i].justify == JUSTIFY_CENTER_LEFT || overlayText[i].justify == JUSTIFY_CENTER_RIGHT ) { + renderSystem->DrawFilled( bgColor, LOCALSAFE_LEFT + ( LOCALSAFE_WIDTH - width + bgAdjust ) * 0.5f, centerY, width, height ); + } else { + assert( false ); + } + + idStr singleLine; + for ( int j = 0; j < text.Length(); j += singleLine.Length() + 1 ) { + singleLine = ""; + for ( int k = j; k < text.Length() && text[k] != '\n'; k++ ) { + singleLine.Append( text[k] ); + } + if ( overlayText[i].justify == JUSTIFY_LEFT ) { + DrawTextLeftAlign( LOCALSAFE_LEFT, leftY, "%s", singleLine.c_str() ); + } else if ( overlayText[i].justify == JUSTIFY_RIGHT ) { + DrawTextRightAlign( LOCALSAFE_RIGHT, rightY, "%s", singleLine.c_str() ); + } else if ( overlayText[i].justify == JUSTIFY_CENTER_LEFT ) { + DrawTextLeftAlign( LOCALSAFE_LEFT + ( LOCALSAFE_WIDTH - width ) * 0.5f, centerY, "%s", singleLine.c_str() ); + } else if ( overlayText[i].justify == JUSTIFY_CENTER_RIGHT ) { + DrawTextRightAlign( LOCALSAFE_LEFT + ( LOCALSAFE_WIDTH + width ) * 0.5f, centerY, "%s", singleLine.c_str() ); + } else { + assert( false ); + } + } + } + overlayText.SetNum( 0 ); +} + +/* +======================== +idConsoleLocal::CreateGraph +======================== +*/ +idDebugGraph * idConsoleLocal::CreateGraph( int numItems ) { + idDebugGraph * graph = new (TAG_SYSTEM) idDebugGraph( numItems ); + debugGraphs.Append( graph ); + return graph; +} + +/* +======================== +idConsoleLocal::DestroyGraph +======================== +*/ +void idConsoleLocal::DestroyGraph( idDebugGraph * graph ) { + debugGraphs.Remove( graph ); + delete graph; +} + +/* +======================== +idConsoleLocal::DrawDebugGraphs +======================== +*/ +void idConsoleLocal::DrawDebugGraphs() { + for ( int i = 0; i < debugGraphs.Num(); i++ ) { + debugGraphs[i]->Render( renderSystem ); + } +} \ No newline at end of file diff --git a/neo/framework/Console.h b/neo/framework/Console.h new file mode 100644 index 00000000..06ff292c --- /dev/null +++ b/neo/framework/Console.h @@ -0,0 +1,90 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __CONSOLE_H__ +#define __CONSOLE_H__ + +enum justify_t { + JUSTIFY_LEFT, + JUSTIFY_RIGHT, + JUSTIFY_CENTER_LEFT, + JUSTIFY_CENTER_RIGHT +}; + +class idOverlayHandle { +friend class idConsoleLocal; +public: + idOverlayHandle() : index( -1 ), time( 0 ) {} +private: + int index; + int time; +}; + +/* +=============================================================================== + + The console is strictly for development and advanced users. It should + never be used to convey actual game information to the user, which should + always be done through a GUI. + + The force options are for the editor console display window, which + doesn't respond to pull up / pull down + +=============================================================================== +*/ + +class idConsole { +public: + virtual ~idConsole() {} + + virtual void Init() = 0; + virtual void Shutdown() = 0; + + virtual bool ProcessEvent( const sysEvent_t * event, bool forceAccept ) = 0; + + // the system code can release the mouse pointer when the console is active + virtual bool Active() = 0; + + // clear the timers on any recent prints that are displayed in the notify lines + virtual void ClearNotifyLines() = 0; + + // some console commands, like timeDemo, will force the console closed before they start + virtual void Close() = 0; + + virtual void Draw( bool forceFullScreen ) = 0; + virtual void Print( const char *text ) = 0; + + virtual void PrintOverlay( idOverlayHandle & handle, justify_t justify, VERIFY_FORMAT_STRING const char * text, ... ) = 0; + + virtual idDebugGraph * CreateGraph( int numItems ) = 0; + virtual void DestroyGraph( idDebugGraph * graph ) = 0; +}; + +extern idConsole * console; // statically initialized to an idConsoleLocal + +#endif /* !__CONSOLE_H__ */ diff --git a/neo/framework/ConsoleHistory.cpp b/neo/framework/ConsoleHistory.cpp new file mode 100644 index 00000000..1db59aa5 --- /dev/null +++ b/neo/framework/ConsoleHistory.cpp @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "ConsoleHistory.h" + +idConsoleHistory consoleHistory; + +const char * HISTORY_FILE_NAME = "consoleHistory.txt"; + +/* +======================== +idConsoleHistory::AddToHistory +======================== +*/ +void idConsoleHistory::AddToHistory( const char *line, bool writeHistoryFile ) { + // empty lines never modify history + if ( line == NULL ) { + return; + } + const char *s; + for ( s = line; *s != '\0'; s++ ) { + if ( *s > ' ' ) { + break; + } + } + if ( *s == '\0' ) { + return; + } + + // repeating the last command doesn't add to the list + if ( historyLines[( numHistory - 1 ) & ( COMMAND_HISTORY - 1 )].Icmp( line ) == 0 ) { + if ( historyLines[returnLine & ( COMMAND_HISTORY - 1 )].Icmp( line ) == 0 ) { + // the command was retrieved from the history, so + // move the up point down so that up arrow will retrieve the same + // command again. + upPoint = returnLine; + } else { + // the command was typed again, so leave the up/down points alone + } + return; + } + // If we had previously retrieved older history commands with + // up arrow, the up/down point will be reset to the end where + // this command is added. + upPoint = numHistory; + returnLine = numHistory; + downPoint = numHistory + 1; +// //mem.PushHeap(); // go to the system heap, not the current map heap + historyLines[numHistory & ( COMMAND_HISTORY - 1 )] = line; +// //mem.PopHeap(); + numHistory++; + + // write the history file to disk + if ( writeHistoryFile ) { + idFile *f = fileSystem->OpenFileWrite( HISTORY_FILE_NAME ); + if ( f != NULL ) { + for ( int i = numHistory - COMMAND_HISTORY; i < numHistory; i++ ) { + if ( i < 0 ) { + continue; + } + f->Printf( "%s\n", historyLines[i & ( COMMAND_HISTORY - 1 )].c_str() ); + } + delete f; + } + } +} + +/* +======================== +idConsoleHistory::RetrieveFromHistory +======================== +*/ +idStr idConsoleHistory::RetrieveFromHistory( bool backward ) { + // if there are no commands in the history + if ( numHistory == 0 ) { + return idStr( "" ); + } + // move the history point + if ( backward ) { + if ( upPoint < numHistory - COMMAND_HISTORY || upPoint < 0 ) { + return idStr( "" ); + } + returnLine = upPoint; + downPoint = upPoint + 1; + upPoint--; + } else { + if ( downPoint >= numHistory ) { + return idStr( "" ); + } + returnLine = downPoint; + upPoint = downPoint - 1; + downPoint++; + } + return historyLines[returnLine & ( COMMAND_HISTORY - 1 )]; +} + +/* +======================== +idConsoleHistory::LoadHistoryFile +======================== +*/ +void idConsoleHistory::LoadHistoryFile() { + idLexer lex; + if ( lex.LoadFile( HISTORY_FILE_NAME, false ) ) { + while( 1 ) { + idStr line; + lex.ParseCompleteLine( line ); + if ( line.IsEmpty() ) { + break; + } + line.StripTrailingWhitespace(); // remove the \n + AddToHistory( line, false ); + } + } +} + +/* +======================== +idConsoleHistory::PrintHistory +======================== +*/ +void idConsoleHistory::PrintHistory() { + for ( int i = numHistory - COMMAND_HISTORY; i < numHistory; i++ ) { + if ( i < 0 ) { + continue; + } + idLib::Printf( "%c%c%c%4i: %s\n", + i == upPoint ? 'U' : ' ', + i == downPoint ? 'D' : ' ', + i == returnLine ? 'R' : ' ', + i, historyLines[i & ( COMMAND_HISTORY - 1 )].c_str() ); + } +} + +/* +======================== +idConsoleHistory::ClearHistory +======================== +*/ +void idConsoleHistory::ClearHistory() { + upPoint = 0; + downPoint = 0; + returnLine = 0; + numHistory = 0; +} + +/* +======================== +history +======================== +*/ +CONSOLE_COMMAND_SHIP( history, "Displays the console command history", 0 ) { + consoleHistory.PrintHistory(); +} + +/* +======================== +clearHistory +======================== +*/ +CONSOLE_COMMAND_SHIP( clearHistory, "Clears the console history", 0 ) { + consoleHistory.ClearHistory(); +} diff --git a/neo/framework/ConsoleHistory.h b/neo/framework/ConsoleHistory.h new file mode 100644 index 00000000..58897516 --- /dev/null +++ b/neo/framework/ConsoleHistory.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __CONSOLEHISTORY_H__ +#define __CONSOLEHISTORY_H__ + +/* + +This should behave like the windows command prompt, with the addition +of a persistant history file. + +Note that commands bound to keys do not go through the console history. + +*/ + +class idConsoleHistory { +public: + idConsoleHistory() : + upPoint( 0 ), + downPoint( 0 ), + returnLine( 0 ), + numHistory( 0 ) + { ClearHistory(); } + + void LoadHistoryFile(); + + // the line should not have a \n + // Empty lines are never added to the history. + // If the command is the same as the last returned history line, nothing is changed. + void AddToHistory( const char *line, bool writeHistoryFile = true ); + + // the string will not have a \n + // Returns an empty string if there is nothing to retrieve in that + // direction. + idStr RetrieveFromHistory( bool backward ); + + // console commands + void PrintHistory(); + void ClearHistory(); + +private: + int upPoint; // command to be retrieved with next up-arrow + int downPoint; // command to be retrieved with next down-arrow + int returnLine; // last returned by RetrieveFromHistory() + int numHistory; + + static const int COMMAND_HISTORY = 64; + idArray historyLines; + + compile_time_assert( CONST_ISPOWEROFTWO( COMMAND_HISTORY ) ); // we use the binary 'and' operator for wrapping +}; + +extern idConsoleHistory consoleHistory; + +#endif // !__CONSOLEHISTORY_H__ diff --git a/neo/framework/DebugGraph.cpp b/neo/framework/DebugGraph.cpp new file mode 100644 index 00000000..2cfddc92 --- /dev/null +++ b/neo/framework/DebugGraph.cpp @@ -0,0 +1,191 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +================================================================================================ +Contains the DebugGraph implementation. +================================================================================================ +*/ + +/* +======================== +idDebugGraph::idDebugGraph +======================== +*/ +idDebugGraph::idDebugGraph( int numItems ) : + bgColor( 0.0f, 0.0f, 0.0f, 0.5f ), + fontColor( 1.0f, 1.0f, 1.0f, 1.0f ), + enable( true ), + mode( GRAPH_FILL ), + sideways( false ), + border( 0.0f ), + position( 100.0f, 100.0f, 100.0f, 100.0f ) { + + Init( numItems ); +} + +/* +======================== +idDebugGraph::Init +======================== +*/ +void idDebugGraph::Init( int numBars ) { + bars.SetNum( numBars ); + labels.Clear(); + + for ( int i = 0; i < numBars; i++ ) { + bars[i].value = 0.0f; + } +} + +/* +======================== +idDebugGraph::AddGridLine +======================== +*/ +void idDebugGraph::AddGridLine( float value, const idVec4 & color ) { + graphPlot_t & line = grid.Alloc(); + line.value = value; + line.color = color; +} + +/* +======================== +idDebugGraph::SetValue +======================== +*/ +void idDebugGraph::SetValue( int b, float value, const idVec4 & color ) { + if ( !enable ) { + return; + } + if ( b < 0 ) { + bars.RemoveIndex( 0 ); + graphPlot_t & graph = bars.Alloc(); + graph.value = value; + graph.color = color; + } else { + bars[b].value = value; + bars[b].color = color; + } +} + +/* +======================== +idDebugGraph::SetLabel +======================== +*/ +void idDebugGraph::SetLabel( int b, const char * text ) { + if ( labels.Num() != bars.Num() ) { + labels.SetNum( bars.Num() ); + } + labels[b] = text; +} + +/* +======================== +idDebugGraph::Render +======================== +*/ +void idDebugGraph::Render( idRenderSystem * gui ) { + if ( !enable ) { + return; + } + + gui->DrawFilled( bgColor, position.x, position.y, position.z, position.w ); + + if ( bars.Num() == 0 ) { + return; + } + + if ( sideways ) { + float barWidth = position.z - border * 2.0f; + float barHeight = ( ( position.w - border ) / (float)bars.Num() ); + float barLeft = position.x + border; + float barTop = position.y + border; + + for ( int i = 0; i < bars.Num(); i++ ) { + idVec4 rect( vec4_zero ); + if ( mode == GRAPH_LINE ) { + rect.Set( barLeft + barWidth * bars[i].value, barTop + i * barHeight, 1.0f, barHeight - border ); + } else if ( mode == GRAPH_FILL ) { + rect.Set( barLeft, barTop + i * barHeight, barWidth * bars[i].value, barHeight - border ); + } else if ( mode == GRAPH_FILL_REVERSE ) { + rect.Set( barLeft + barWidth, barTop + i * barHeight, barWidth - barWidth * bars[i].value, barHeight - border ); + } + gui->DrawFilled( bars[i].color, rect.x, rect.y, rect.z, rect.w ); + } + if ( labels.Num() > 0 ) { + int maxLen = 0; + for ( int i = 0; i < labels.Num(); i++ ) { + maxLen = Max( maxLen, labels[i].Length() ); + } + idVec4 rect( position ); + rect.x -= SMALLCHAR_WIDTH * maxLen; + rect.z = SMALLCHAR_WIDTH * maxLen; + gui->DrawFilled( bgColor, rect.x, rect.y, rect.z, rect.w ); + for ( int i = 0; i < labels.Num(); i++ ) { + idVec2 pos( barLeft - SMALLCHAR_WIDTH * maxLen, barTop + i * barHeight ); + gui->DrawSmallStringExt( idMath::Ftoi( pos.x ), idMath::Ftoi( pos.y ), labels[i], fontColor, true ); + } + } + } else { + float barWidth = ( ( position.z - border ) / (float)bars.Num() ); + float barHeight = position.w - border * 2.0f; + float barLeft = position.x + border; + float barTop = position.y + border; + float barBottom = barTop + barHeight; + + for ( int i = 0; i < grid.Num(); i++ ) { + idVec4 rect( position.x, barBottom - barHeight * grid[i].value, position.z, 1.0f ); + gui->DrawFilled( grid[i].color, rect.x, rect.y, rect.z, rect.w ); + } + for ( int i = 0; i < bars.Num(); i++ ) { + idVec4 rect; + if ( mode == GRAPH_LINE ) { + rect.Set( barLeft + i * barWidth, barBottom - barHeight * bars[i].value, barWidth - border, 1.0f ); + } else if ( mode == GRAPH_FILL ) { + rect.Set( barLeft + i * barWidth, barBottom - barHeight * bars[i].value, barWidth - border, barHeight * bars[i].value ); + } else if ( mode == GRAPH_FILL_REVERSE ) { + rect.Set( barLeft + i * barWidth, barTop, barWidth - border, barHeight * bars[i].value ); + } + gui->DrawFilled( bars[i].color, rect.x, rect.y, rect.z, rect.w ); + } + if ( labels.Num() > 0 ) { + idVec4 rect( position ); + rect.y += barHeight; + rect.w = SMALLCHAR_HEIGHT; + gui->DrawFilled( bgColor, rect.x, rect.y, rect.z, rect.w ); + for ( int i = 0; i < labels.Num(); i++ ) { + idVec2 pos( barLeft + i * barWidth, barBottom + border ); + gui->DrawSmallStringExt( idMath::Ftoi( pos.x ), idMath::Ftoi( pos.y ), labels[i], fontColor, true ); + } + } + } +} diff --git a/neo/framework/DebugGraph.h b/neo/framework/DebugGraph.h new file mode 100644 index 00000000..c22ab770 --- /dev/null +++ b/neo/framework/DebugGraph.h @@ -0,0 +1,103 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __DEBUGGRAPH_H__ +#define __DEBUGGRAPH_H__ + +/* +================================================================================================ +Contains the DebugGraph declaration. +================================================================================================ +*/ + +/* +================================================ +The *Debug Graph, idDebugGraph, contains graphing functionality common to many debug tools. +================================================ +*/ +class idDebugGraph { +public: + idDebugGraph( int numItems = 0 ); + + void Enable( bool b ) { enable = b; } + + // create a graph with the specified number of bars + void Init( int numBars ); + + void AddGridLine( float value, const idVec4 & color ); + + // sets a bar value, pass -1 to append an element + void SetValue( int b, float value, const idVec4 & color ); + float GetValue( int b ) { return bars[b].value; } + + // sets a bar label + void SetLabel( int b, const char * text ); + + enum fillMode_t { + GRAPH_LINE, // only draw a single top line for each bar + GRAPH_FILL, // fill the entire bar from the bottom (or left) + GRAPH_FILL_REVERSE, // fill the entire bar from the top (or right) + }; + void SetFillMode( fillMode_t m ) { mode = m; } + + // render the graph sideways? + void SetSideways( bool s ) { sideways = s; } + + // the background color is what's drawn between bars and in the empty space + void SetBackgroundColor( const idVec4 & color ) { bgColor = color; } + void SetLabelColor( const idVec4 & color ) { fontColor = color; } + + // the border specifies the amount of space between bars as well as the amount of space around the entire graph + void SetBorder( float b ) { border = b; } + + // set the screen position for the graph + void SetPosition( float x, float y, float w, float h ) { position.Set( x, y, w, h ); } + + void Render( idRenderSystem * gui ); + +private: + const class idMaterial * white; + const class idMaterial * font; + + idVec4 bgColor; + idVec4 fontColor; + fillMode_t mode; + bool sideways; + float border; + idVec4 position; + bool enable; + + struct graphPlot_t { + float value; + idVec4 color; + }; + idList< graphPlot_t > bars; + idList< graphPlot_t > grid; + idList< idStr > labels; +}; + +#endif diff --git a/neo/framework/DeclAF.cpp b/neo/framework/DeclAF.cpp new file mode 100644 index 00000000..0417ebea --- /dev/null +++ b/neo/framework/DeclAF.cpp @@ -0,0 +1,1747 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +=============================================================================== + + idDeclAF + +=============================================================================== +*/ + +/* +================ +idAFVector::idAFVector +================ +*/ +idAFVector::idAFVector() { + type = VEC_COORDS; + vec.Zero(); + negate = false; +} + +/* +================ +idAFVector::Parse +================ +*/ +bool idAFVector::Parse( idLexer &src ) { + idToken token; + + if ( !src.ReadToken( &token ) ) { + return false; + } + + if ( token == "-" ) { + negate = true; + if ( !src.ReadToken( &token ) ) { + return false; + } + } + else { + negate = false; + } + + if ( token == "(" ) { + type = idAFVector::VEC_COORDS; + vec.x = src.ParseFloat(); + src.ExpectTokenString( "," ); + vec.y = src.ParseFloat(); + src.ExpectTokenString( "," ); + vec.z = src.ParseFloat(); + src.ExpectTokenString( ")" ); + } + else if ( token == "joint" ) { + type = idAFVector::VEC_JOINT; + src.ExpectTokenString( "(" ); + src.ReadToken( &token ); + joint1 = token; + src.ExpectTokenString( ")" ); + } + else if ( token == "bonecenter" ) { + type = idAFVector::VEC_BONECENTER; + src.ExpectTokenString( "(" ); + src.ReadToken( &token ); + joint1 = token; + src.ExpectTokenString( "," ); + src.ReadToken( &token ); + joint2 = token; + src.ExpectTokenString( ")" ); + } + else if ( token == "bonedir" ) { + type = idAFVector::VEC_BONEDIR; + src.ExpectTokenString( "(" ); + src.ReadToken( &token ); + joint1 = token; + src.ExpectTokenString( "," ); + src.ReadToken( &token ); + joint2 = token; + src.ExpectTokenString( ")" ); + } + else { + src.Error( "unknown token %s in vector", token.c_str() ); + return false; + } + + return true; +} + +/* +================ +idAFVector::Finish +================ +*/ +bool idAFVector::Finish( const char *fileName, const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const { + idMat3 axis; + idVec3 start, end; + + switch( type ) { + case idAFVector::VEC_COORDS: { + break; + } + case idAFVector::VEC_JOINT: { + if ( !GetJointTransform( model, frame, joint1, vec, axis ) ) { + common->Warning( "invalid joint %s in joint() in '%s'", joint1.c_str(), fileName ); + vec.Zero(); + } + break; + } + case idAFVector::VEC_BONECENTER: { + if ( !GetJointTransform( model, frame, joint1, start, axis ) ) { + common->Warning( "invalid joint %s in bonecenter() in '%s'", joint1.c_str(), fileName ); + start.Zero(); + } + if ( !GetJointTransform( model, frame, joint2, end, axis ) ) { + common->Warning( "invalid joint %s in bonecenter() in '%s'", joint2.c_str(), fileName ); + end.Zero(); + } + vec = ( start + end ) * 0.5f; + break; + } + case idAFVector::VEC_BONEDIR: { + if ( !GetJointTransform( model, frame, joint1, start, axis ) ) { + common->Warning( "invalid joint %s in bonedir() in '%s'", joint1.c_str(), fileName ); + start.Zero(); + } + if ( !GetJointTransform( model, frame, joint2, end, axis ) ) { + common->Warning( "invalid joint %s in bonedir() in '%s'", joint2.c_str(), fileName ); + end.Zero(); + } + vec = ( end - start ); + break; + } + default: { + vec.Zero(); + break; + } + } + + if ( negate ) { + vec = -vec; + } + + return true; +} + +/* +================ +idAFVector::Write +================ +*/ +bool idAFVector::Write( idFile *f ) const { + + if ( negate ) { + f->WriteFloatString( "-" ); + } + switch( type ) { + case idAFVector::VEC_COORDS: { + f->WriteFloatString( "( %f, %f, %f )", vec.x, vec.y, vec.z ); + break; + } + case idAFVector::VEC_JOINT: { + f->WriteFloatString( "joint( \"%s\" )", joint1.c_str() ); + break; + } + case idAFVector::VEC_BONECENTER: { + f->WriteFloatString( "bonecenter( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + case idAFVector::VEC_BONEDIR: { + f->WriteFloatString( "bonedir( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + default: { + break; + } + } + return true; +} + +/* +================ +idAFVector::ToString +================ +*/ +const char *idAFVector::ToString( idStr &str, const int precision ) { + + switch( type ) { + case idAFVector::VEC_COORDS: { + char format[128]; + sprintf( format, "( %%.%df, %%.%df, %%.%df )", precision, precision, precision ); + sprintf( str, format, vec.x, vec.y, vec.z ); + break; + } + case idAFVector::VEC_JOINT: { + sprintf( str, "joint( \"%s\" )", joint1.c_str() ); + break; + } + case idAFVector::VEC_BONECENTER: { + sprintf( str, "bonecenter( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + case idAFVector::VEC_BONEDIR: { + sprintf( str, "bonedir( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + default: { + break; + } + } + if ( negate ) { + str = "-" + str; + } + return str.c_str(); +} + +/* +================ +idDeclAF_Body::SetDefault +================ +*/ +void idDeclAF_Body::SetDefault( const idDeclAF *file ) { + name = "noname"; + modelType = TRM_BOX; + v1.type = idAFVector::VEC_COORDS; + v1.ToVec3().x = v1.ToVec3().y = v1.ToVec3().z = -10.0f; + v2.type = idAFVector::VEC_COORDS; + v2.ToVec3().x = v2.ToVec3().y = v2.ToVec3().z = 10.0f; + numSides = 3; + origin.ToVec3().Zero(); + angles.Zero(); + density = 0.2f; + inertiaScale.Identity(); + linearFriction = file->defaultLinearFriction; + angularFriction = file->defaultAngularFriction; + contactFriction = file->defaultContactFriction; + contents = file->contents; + clipMask = file->clipMask; + selfCollision = file->selfCollision; + frictionDirection.ToVec3().Zero(); + contactMotorDirection.ToVec3().Zero(); + jointName = "origin"; + jointMod = DECLAF_JOINTMOD_AXIS; + containedJoints = "*origin"; +} + +/* +================ +idDeclAF_Constraint::SetDefault +================ +*/ +void idDeclAF_Constraint::SetDefault( const idDeclAF *file ) { + name = "noname"; + type = DECLAF_CONSTRAINT_UNIVERSALJOINT; + if ( file->bodies.Num() ) { + body1 = file->bodies[0]->name; + } + else { + body1 = "world"; + } + body2 = "world"; + friction = file->defaultConstraintFriction; + anchor.ToVec3().Zero(); + anchor2.ToVec3().Zero(); + axis.ToVec3().Set( 1.0f, 0.0f, 0.0f ); + shaft[0].ToVec3().Set( 0.0f, 0.0f, -1.0f ); + shaft[1].ToVec3().Set( 0.0f, 0.0f, 1.0f ); + limit = idDeclAF_Constraint::LIMIT_NONE; + limitAngles[0] = + limitAngles[1] = + limitAngles[2] = 0.0f; + limitAxis.ToVec3().Set( 0.0f, 0.0f, -1.0f ); +} + +/* +================ +idDeclAF::WriteBody +================ +*/ +bool idDeclAF::WriteBody( idFile *f, const idDeclAF_Body &body ) const { + idStr str; + + f->WriteFloatString( "\nbody \"%s\" {\n", body.name.c_str() ); + f->WriteFloatString( "\tjoint \"%s\"\n", body.jointName.c_str() ); + f->WriteFloatString( "\tmod %s\n", JointModToString( body.jointMod ) ); + switch( body.modelType ) { + case TRM_BOX: { + f->WriteFloatString( "\tmodel box( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( " )\n" ); + break; + } + case TRM_OCTAHEDRON: { + f->WriteFloatString( "\tmodel octahedron( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( " )\n" ); + break; + } + case TRM_DODECAHEDRON: { + f->WriteFloatString( "\tmodel dodecahedron( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( " )\n" ); + break; + } + case TRM_CYLINDER: { + f->WriteFloatString( "\tmodel cylinder( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( ", %d )\n", body.numSides ); + break; + } + case TRM_CONE: { + f->WriteFloatString( "\tmodel cone( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( ", %d )\n", body.numSides ); + break; + } + case TRM_BONE: { + f->WriteFloatString( "\tmodel bone( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( ", %f )\n", body.width ); + break; + } + default: + assert( 0 ); + break; + } + f->WriteFloatString( "\torigin " ); + body.origin.Write( f ); + f->WriteFloatString( "\n" ); + if ( body.angles != ang_zero ) { + f->WriteFloatString( "\tangles ( %f, %f, %f )\n", body.angles.pitch, body.angles.yaw, body.angles.roll ); + } + f->WriteFloatString( "\tdensity %f\n", body.density ); + if ( body.inertiaScale != mat3_identity ) { + const idMat3 &ic = body.inertiaScale; + f->WriteFloatString( "\tinertiaScale (%f %f %f %f %f %f %f %f %f)\n", + ic[0][0], ic[0][1], ic[0][2], + ic[1][0], ic[1][1], ic[1][2], + ic[2][0], ic[2][1], ic[2][2] ); + } + if ( body.linearFriction != -1 ) { + f->WriteFloatString( "\tfriction %f, %f, %f\n", body.linearFriction, body.angularFriction, body.contactFriction ); + } + f->WriteFloatString( "\tcontents %s\n", ContentsToString( body.contents, str ) ); + f->WriteFloatString( "\tclipMask %s\n", ContentsToString( body.clipMask, str ) ); + f->WriteFloatString( "\tselfCollision %d\n", body.selfCollision ); + if ( body.frictionDirection.ToVec3() != vec3_origin ) { + f->WriteFloatString( "\tfrictionDirection " ); + body.frictionDirection.Write( f ); + f->WriteFloatString( "\n" ); + } + if ( body.contactMotorDirection.ToVec3() != vec3_origin ) { + f->WriteFloatString( "\tcontactMotorDirection " ); + body.contactMotorDirection.Write( f ); + f->WriteFloatString( "\n" ); + } + f->WriteFloatString( "\tcontainedJoints \"%s\"\n", body.containedJoints.c_str() ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteFixed +================ +*/ +bool idDeclAF::WriteFixed( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nfixed \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteBallAndSocketJoint +================ +*/ +bool idDeclAF::WriteBallAndSocketJoint( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nballAndSocketJoint \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) { + f->WriteFloatString( "\tconeLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f, ", c.limitAngles[0] ); + c.shaft[0].Write( f ); + f->WriteFloatString( "\n" ); + } + else if ( c.limit == idDeclAF_Constraint::LIMIT_PYRAMID ) { + f->WriteFloatString( "\tpyramidLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f, %f, %f, ", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] ); + c.shaft[0].Write( f ); + f->WriteFloatString( "\n" ); + } + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteUniversalJoint +================ +*/ +bool idDeclAF::WriteUniversalJoint( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nuniversalJoint \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tshafts " ); + c.shaft[0].Write( f ); + f->WriteFloatString( ", " ); + c.shaft[1].Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) { + f->WriteFloatString( "\tconeLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f\n", c.limitAngles[0] ); + } + else if ( c.limit == idDeclAF_Constraint::LIMIT_PYRAMID ) { + f->WriteFloatString( "\tpyramidLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f, %f, %f\n", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] ); + } + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteHinge +================ +*/ +bool idDeclAF::WriteHinge( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nhinge \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\taxis " ); + c.axis.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) { + f->WriteFloatString( "\tlimit " ); + f->WriteFloatString( "%f, %f, %f", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] ); + f->WriteFloatString( "\n" ); + } + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteSlider +================ +*/ +bool idDeclAF::WriteSlider( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nslider \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\taxis " ); + c.axis.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteSpring +================ +*/ +bool idDeclAF::WriteSpring( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nspring \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor1 " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tanchor2 " ); + c.anchor2.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + f->WriteFloatString( "\tstretch %f\n", c.stretch ); + f->WriteFloatString( "\tcompress %f\n", c.compress ); + f->WriteFloatString( "\tdamping %f\n", c.damping ); + f->WriteFloatString( "\trestLength %f\n", c.restLength ); + f->WriteFloatString( "\tminLength %f\n", c.minLength ); + f->WriteFloatString( "\tmaxLength %f\n", c.maxLength ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteConstraint +================ +*/ +bool idDeclAF::WriteConstraint( idFile *f, const idDeclAF_Constraint &c ) const { + switch( c.type ) { + case DECLAF_CONSTRAINT_FIXED: + return WriteFixed( f, c ); + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: + return WriteBallAndSocketJoint( f, c ); + case DECLAF_CONSTRAINT_UNIVERSALJOINT: + return WriteUniversalJoint( f, c ); + case DECLAF_CONSTRAINT_HINGE: + return WriteHinge( f, c ); + case DECLAF_CONSTRAINT_SLIDER: + return WriteSlider( f, c ); + case DECLAF_CONSTRAINT_SPRING: + return WriteSpring( f, c ); + default: + break; + } + return false; +} + +/* +================ +idDeclAF::WriteSettings +================ +*/ +bool idDeclAF::WriteSettings( idFile *f ) const { + idStr str; + + f->WriteFloatString( "\nsettings {\n" ); + f->WriteFloatString( "\tmodel \"%s\"\n", model.c_str() ); + f->WriteFloatString( "\tskin \"%s\"\n", skin.c_str() ); + f->WriteFloatString( "\tfriction %f, %f, %f, %f\n", defaultLinearFriction, defaultAngularFriction, defaultContactFriction, defaultConstraintFriction ); + f->WriteFloatString( "\tsuspendSpeed %f, %f, %f, %f\n", suspendVelocity[0], suspendVelocity[1], suspendAcceleration[0], suspendAcceleration[1] ); + f->WriteFloatString( "\tnoMoveTime %f\n", noMoveTime ); + f->WriteFloatString( "\tnoMoveTranslation %f\n", noMoveTranslation ); + f->WriteFloatString( "\tnoMoveRotation %f\n", noMoveRotation ); + f->WriteFloatString( "\tminMoveTime %f\n", minMoveTime ); + f->WriteFloatString( "\tmaxMoveTime %f\n", maxMoveTime ); + f->WriteFloatString( "\ttotalMass %f\n", totalMass ); + f->WriteFloatString( "\tcontents %s\n", ContentsToString( contents, str ) ); + f->WriteFloatString( "\tclipMask %s\n", ContentsToString( clipMask, str ) ); + f->WriteFloatString( "\tselfCollision %d\n", selfCollision ); + f->WriteFloatString( "}\n" ); + return true; +} + + +/* +================ +idDeclAF::RebuildTextSource +================ +*/ +bool idDeclAF::RebuildTextSource() { + int i; + idFile_Memory f; + + f.WriteFloatString("\n\n/*\n" + "\tGenerated by the Articulated Figure Editor.\n" + "\tDo not edit directly but launch the game and type 'editAFs' on the console.\n" + "*/\n" ); + + f.WriteFloatString( "\narticulatedFigure %s {\n", GetName() ); + + if ( !WriteSettings( &f ) ) { + return false; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !WriteBody( &f, *bodies[i] ) ) { + return false; + } + } + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !WriteConstraint( &f, *constraints[i] ) ) { + return false; + } + } + + f.WriteFloatString( "\n}" ); + + SetText( f.GetDataPtr() ); + + return true; +} + +/* +================ +idDeclAF::Save +================ +*/ +bool idDeclAF::Save() { + RebuildTextSource(); + ReplaceSourceFileText(); + modified = false; + return true; +} + +/* +================ +idDeclAF::ContentsFromString +================ +*/ +int idDeclAF::ContentsFromString( const char *str ) { + int c; + idToken token; + idLexer src( str, idStr::Length( str ), "idDeclAF::ContentsFromString" ); + + c = 0; + while( src.ReadToken( &token ) ) { + if ( token.Icmp( "none" ) == 0 ) { + c = 0; + } + else if ( token.Icmp( "solid" ) == 0 ) { + c |= CONTENTS_SOLID; + } + else if ( token.Icmp( "body" ) == 0 ) { + c |= CONTENTS_BODY; + } + else if ( token.Icmp( "corpse" ) == 0 ) { + c |= CONTENTS_CORPSE; + } + else if ( token.Icmp( "playerclip" ) == 0 ) { + c |= CONTENTS_PLAYERCLIP; + } + else if ( token.Icmp( "monsterclip" ) == 0 ) { + c |= CONTENTS_MONSTERCLIP; + } + else if ( token == "," ) { + continue; + } + else { + return c; + } + } + return c; +} + +/* +================ +idDeclAF::ContentsToString +================ +*/ +const char *idDeclAF::ContentsToString( const int contents, idStr &str ) { + str = ""; + if ( contents & CONTENTS_SOLID ) { + if ( str.Length() ) str += ", "; + str += "solid"; + } + if ( contents & CONTENTS_BODY ) { + if ( str.Length() ) str += ", "; + str += "body"; + } + if ( contents & CONTENTS_CORPSE ) { + if ( str.Length() ) str += ", "; + str += "corpse"; + } + if ( contents & CONTENTS_PLAYERCLIP ) { + if ( str.Length() ) str += ", "; + str += "playerclip"; + } + if ( contents & CONTENTS_MONSTERCLIP ) { + if ( str.Length() ) str += ", "; + str += "monsterclip"; + } + if ( str[0] == '\0' ) { + str = "none"; + } + return str.c_str(); +} + +/* +================ +idDeclAF::JointModFromString +================ +*/ +declAFJointMod_t idDeclAF::JointModFromString( const char *str ) { + if ( idStr::Icmp( str, "orientation" ) == 0 ) { + return DECLAF_JOINTMOD_AXIS; + } + if ( idStr::Icmp( str, "position" ) == 0 ) { + return DECLAF_JOINTMOD_ORIGIN; + } + if ( idStr::Icmp( str, "both" ) == 0 ) { + return DECLAF_JOINTMOD_BOTH; + } + return DECLAF_JOINTMOD_AXIS; +} + +/* +================ +idDeclAF::JointModToString +================ +*/ +const char * idDeclAF::JointModToString( declAFJointMod_t jointMod ) { + switch( jointMod ) { + case DECLAF_JOINTMOD_AXIS: { + return "orientation"; + } + case DECLAF_JOINTMOD_ORIGIN: { + return "position"; + } + case DECLAF_JOINTMOD_BOTH: { + return "both"; + } + } + return "orientation"; +} + +/* +================= +idDeclAF::Size +================= +*/ +size_t idDeclAF::Size() const { + return sizeof( idDeclAF ); +} + +/* +================ +idDeclAF::ParseContents +================ +*/ +bool idDeclAF::ParseContents( idLexer &src, int &c ) const { + idToken token; + idStr str; + + while( src.ReadToken( &token ) ) { + str += token; + if ( !src.CheckTokenString( "," ) ) { + break; + } + str += ","; + } + c = ContentsFromString( str ); + return true; +} + +/* +================ +idDeclAF::ParseBody +================ +*/ +bool idDeclAF::ParseBody( idLexer &src ) { + bool hasJoint = false; + idToken token; + idAFVector angles; + idDeclAF_Body *body = new (TAG_DECL) idDeclAF_Body; + + bodies.Alloc() = body; + + body->SetDefault( this ); + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + body->name = token; + if ( !body->name.Icmp( "origin" ) || !body->name.Icmp( "world" ) ) { + src.Error( "a body may not be named \"origin\" or \"world\"" ); + return false; + } + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "model" ) ) { + if ( !src.ExpectTokenType( TT_NAME, 0, &token ) ) { + return false; + } + if ( !token.Icmp( "box" ) ) { + body->modelType = TRM_BOX; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "octahedron" ) ) { + body->modelType = TRM_OCTAHEDRON; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "dodecahedron" ) ) { + body->modelType = TRM_DODECAHEDRON; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "cylinder" ) ) { + body->modelType = TRM_CYLINDER; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + body->numSides = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "cone" ) ) { + body->modelType = TRM_CONE; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + body->numSides = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "bone" ) ) { + body->modelType = TRM_BONE; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + body->width = src.ParseFloat(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "custom" ) ) { + src.Error( "custom models not yet implemented" ); + return false; + } else { + src.Error( "unkown model type %s", token.c_str() ); + return false; + } + } else if ( !token.Icmp( "origin" ) ) { + if ( !body->origin.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "angles" ) ) { + if ( !angles.Parse( src ) ) { + return false; + } + body->angles = idAngles( angles.ToVec3().x, angles.ToVec3().y, angles.ToVec3().z ); + } else if ( !token.Icmp( "joint" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + body->jointName = token; + hasJoint = true; + } else if ( !token.Icmp( "mod" ) ) { + if ( !src.ExpectAnyToken( &token ) ) { + return false; + } + body->jointMod = JointModFromString( token.c_str() ); + } else if ( !token.Icmp( "density" ) ) { + body->density = src.ParseFloat(); + } else if ( !token.Icmp( "inertiaScale" ) ) { + src.Parse1DMatrix( 9, body->inertiaScale[0].ToFloatPtr() ); + } else if ( !token.Icmp( "friction" ) ) { + body->linearFriction = src.ParseFloat(); + src.ExpectTokenString( "," ); + body->angularFriction = src.ParseFloat(); + src.ExpectTokenString( "," ); + body->contactFriction = src.ParseFloat(); + } else if ( !token.Icmp( "contents" ) ) { + ParseContents( src, body->contents ); + } else if ( !token.Icmp( "clipMask" ) ) { + ParseContents( src, body->clipMask ); + body->clipMask &= ~CONTENTS_CORPSE; // never allow collisions against corpses + } else if ( !token.Icmp( "selfCollision" ) ) { + body->selfCollision = src.ParseBool(); + } else if ( !token.Icmp( "containedjoints" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + body->containedJoints = token; + } else if ( !token.Icmp( "frictionDirection" ) ) { + if ( !body->frictionDirection.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "contactMotorDirection" ) ) { + if ( !body->contactMotorDirection.Parse( src ) ) { + return false; + } + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in body", token.c_str() ); + return false; + } + } + + if ( body->modelType == TRM_INVALID ) { + src.Error( "no model set for body" ); + return false; + } + + if ( !hasJoint ) { + src.Error( "no joint set for body" ); + return false; + } + + body->clipMask |= CONTENTS_MOVEABLECLIP; + + return true; +} + +/* +================ +idDeclAF::ParseFixed +================ +*/ +bool idDeclAF::ParseFixed( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new (TAG_DECL) idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_FIXED; + constraint->name = token; + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in ball and socket joint", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseBallAndSocketJoint +================ +*/ +bool idDeclAF::ParseBallAndSocketJoint( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new (TAG_DECL) idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_BALLANDSOCKETJOINT; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + constraint->anchor.ToVec3().Zero(); + constraint->shaft[0].ToVec3().Zero(); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "conelimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) || + !constraint->shaft[0].Parse( src ) ) { + return false; + } + constraint->limit = idDeclAF_Constraint::LIMIT_CONE; + } else if ( !token.Icmp( "pyramidlimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[2] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) || + !constraint->shaft[0].Parse( src ) ) { + return false; + } + constraint->limit = idDeclAF_Constraint::LIMIT_PYRAMID; + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in ball and socket joint", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseUniversalJoint +================ +*/ +bool idDeclAF::ParseUniversalJoint( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new (TAG_DECL) idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_UNIVERSALJOINT; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + constraint->anchor.ToVec3().Zero(); + constraint->shaft[0].ToVec3().Zero(); + constraint->shaft[1].ToVec3().Zero(); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "shafts" ) ) { + if ( !constraint->shaft[0].Parse( src ) || + !src.ExpectTokenString( "," ) || + !constraint->shaft[1].Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "conelimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + constraint->limit = idDeclAF_Constraint::LIMIT_CONE; + } else if ( !token.Icmp( "pyramidlimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[2] = src.ParseFloat(); + constraint->limit = idDeclAF_Constraint::LIMIT_PYRAMID; + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in universal joint", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseHinge +================ +*/ +bool idDeclAF::ParseHinge( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new (TAG_DECL) idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_HINGE; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + constraint->anchor.ToVec3().Zero(); + constraint->axis.ToVec3().Zero(); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "axis" ) ) { + if ( !constraint->axis.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "limit" ) ) { + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[2] = src.ParseFloat(); + constraint->limit = idDeclAF_Constraint::LIMIT_CONE; + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in hinge", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseSlider +================ +*/ +bool idDeclAF::ParseSlider( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new (TAG_DECL) idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_SLIDER; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "axis" ) ) { + if ( !constraint->axis.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in slider", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseSpring +================ +*/ +bool idDeclAF::ParseSpring( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new (TAG_DECL) idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_SPRING; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor1" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "anchor2" ) ) { + if ( !constraint->anchor2.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( !token.Icmp( "stretch" ) ) { + constraint->stretch = src.ParseFloat(); + } else if ( !token.Icmp( "compress" ) ) { + constraint->compress = src.ParseFloat(); + } else if ( !token.Icmp( "damping" ) ) { + constraint->damping = src.ParseFloat(); + } else if ( !token.Icmp( "restLength" ) ) { + constraint->restLength = src.ParseFloat(); + } else if ( !token.Icmp( "minLength" ) ) { + constraint->minLength = src.ParseFloat(); + } else if ( !token.Icmp( "maxLength" ) ) { + constraint->maxLength = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in spring", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseSettings +================ +*/ +bool idDeclAF::ParseSettings( idLexer &src ) { + idToken token; + + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "mesh" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + } else if ( !token.Icmp( "anim" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + } else if ( !token.Icmp( "model" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + model = token; + } else if ( !token.Icmp( "skin" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + skin = token; + } else if ( !token.Icmp( "friction" ) ) { + + defaultLinearFriction = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + defaultAngularFriction = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + defaultContactFriction = src.ParseFloat(); + if ( src.CheckTokenString( "," ) ) { + defaultConstraintFriction = src.ParseFloat(); + } + } else if ( !token.Icmp( "totalMass" ) ) { + totalMass = src.ParseFloat(); + } else if ( !token.Icmp( "suspendSpeed" ) ) { + + suspendVelocity[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + suspendVelocity[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + suspendAcceleration[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + suspendAcceleration[1] = src.ParseFloat(); + } else if ( !token.Icmp( "noMoveTime" ) ) { + noMoveTime = src.ParseFloat(); + } else if ( !token.Icmp( "noMoveTranslation" ) ) { + noMoveTranslation = src.ParseFloat(); + } else if ( !token.Icmp( "noMoveRotation" ) ) { + noMoveRotation = src.ParseFloat(); + } else if ( !token.Icmp( "minMoveTime" ) ) { + minMoveTime = src.ParseFloat(); + } else if ( !token.Icmp( "maxMoveTime" ) ) { + maxMoveTime = src.ParseFloat(); + } else if ( !token.Icmp( "contents" ) ) { + ParseContents( src, contents ); + } else if ( !token.Icmp( "clipMask" ) ) { + ParseContents( src, clipMask ); + clipMask &= ~CONTENTS_CORPSE; // never allow collisions against corpses + } else if ( !token.Icmp( "selfCollision" ) ) { + selfCollision = src.ParseBool(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in settings", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::Parse +================ +*/ +bool idDeclAF::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + int i, j; + idLexer src; + idToken token; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "settings" ) ) { + if ( !ParseSettings( src ) ) { + return false; + } + } else if ( !token.Icmp( "body" ) ) { + if ( !ParseBody( src ) ) { + return false; + } + } else if ( !token.Icmp( "fixed" ) ) { + if ( !ParseFixed( src ) ) { + return false; + } + } else if ( !token.Icmp( "ballAndSocketJoint" ) ) { + if ( !ParseBallAndSocketJoint( src ) ) { + return false; + } + } else if ( !token.Icmp( "universalJoint" ) ) { + if ( !ParseUniversalJoint( src ) ) { + return false; + } + } else if ( !token.Icmp( "hinge" ) ) { + if ( !ParseHinge( src ) ) { + return false; + } + } else if ( !token.Icmp( "slider" ) ) { + if ( !ParseSlider( src ) ) { + return false; + } + } else if ( !token.Icmp( "spring" ) ) { + if ( !ParseSpring( src ) ) { + return false; + } + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown keyword %s", token.c_str() ); + return false; + } + } + + for ( i = 0; i < bodies.Num(); i++ ) { + // check for multiple bodies with the same name + for ( j = i+1; j < bodies.Num(); j++ ) { + if ( bodies[i]->name == bodies[j]->name ) { + src.Error( "two bodies with the same name \"%s\"", bodies[i]->name.c_str() ); + } + } + } + + for ( i = 0; i < constraints.Num(); i++ ) { + // check for multiple constraints with the same name + for ( j = i+1; j < constraints.Num(); j++ ) { + if ( constraints[i]->name == constraints[j]->name ) { + src.Error( "two constraints with the same name \"%s\"", constraints[i]->name.c_str() ); + } + } + // check if there are two valid bodies set + if ( constraints[i]->body1 == "" ) { + src.Error( "no valid body1 specified for constraint '%s'", constraints[i]->name.c_str() ); + } + if ( constraints[i]->body2 == "" ) { + src.Error( "no valid body2 specified for constraint '%s'", constraints[i]->name.c_str() ); + } + } + + // make sure the body which modifies the origin comes first + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->jointName == "origin" ) { + if ( i != 0 ) { + idDeclAF_Body *b = bodies[0]; + bodies[0] = bodies[i]; + bodies[i] = b; + } + break; + } + } + + return true; +} + +/* +================ +idDeclAF::DefaultDefinition +================ +*/ +const char *idDeclAF::DefaultDefinition() const { + return + "{\n" + "\t" "settings {\n" + "\t\t" "model \"\"\n" + "\t\t" "skin \"\"\n" + "\t\t" "friction 0.01, 0.01, 0.8, 0.5\n" + "\t\t" "suspendSpeed 20, 30, 40, 60\n" + "\t\t" "noMoveTime 1\n" + "\t\t" "noMoveTranslation 10\n" + "\t\t" "noMoveRotation 10\n" + "\t\t" "minMoveTime -1\n" + "\t\t" "maxMoveTime -1\n" + "\t\t" "totalMass -1\n" + "\t\t" "contents corpse\n" + "\t\t" "clipMask solid, corpse\n" + "\t\t" "selfCollision 1\n" + "\t" "}\n" + "\t" "body \"body\" {\n" + "\t\t" "joint \"origin\"\n" + "\t\t" "mod orientation\n" + "\t\t" "model box( ( -10, -10, -10 ), ( 10, 10, 10 ) )\n" + "\t\t" "origin ( 0, 0, 0 )\n" + "\t\t" "density 0.2\n" + "\t\t" "friction 0.01, 0.01, 0.8\n" + "\t\t" "contents corpse\n" + "\t\t" "clipMask solid, corpse\n" + "\t\t" "selfCollision 1\n" + "\t\t" "containedJoints \"*origin\"\n" + "\t" "}\n" + "}\n"; +} + +/* +================ +idDeclAF::FreeData +================ +*/ +void idDeclAF::FreeData() { + modified = false; + defaultLinearFriction = 0.01f; + defaultAngularFriction = 0.01f; + defaultContactFriction = 0.8f; + defaultConstraintFriction = 0.5f; + totalMass = -1; + suspendVelocity.Set( 20.0f, 30.0f ); + suspendAcceleration.Set( 40.0f, 60.0f ); + noMoveTime = 1.0f; + noMoveTranslation = 10.0f; + noMoveRotation = 10.0f; + minMoveTime = -1.0f; + maxMoveTime = -1.0f; + selfCollision = true; + contents = CONTENTS_CORPSE; + clipMask = CONTENTS_SOLID; + bodies.DeleteContents( true ); + constraints.DeleteContents( true ); +} + +/* +================ +idDeclAF::Finish +================ +*/ +void idDeclAF::Finish( const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const { + int i; + + const char *name = GetName(); + for ( i = 0; i < bodies.Num(); i++ ) { + idDeclAF_Body *body = bodies[i]; + body->v1.Finish( name, GetJointTransform, frame, model ); + body->v2.Finish( name, GetJointTransform, frame, model ); + body->origin.Finish( name, GetJointTransform, frame, model ); + body->frictionDirection.Finish( name, GetJointTransform, frame, model ); + body->contactMotorDirection.Finish( name, GetJointTransform, frame, model ); + } + for ( i = 0; i < constraints.Num(); i++ ) { + idDeclAF_Constraint *constraint = constraints[i]; + constraint->anchor.Finish( name, GetJointTransform, frame, model ); + constraint->anchor2.Finish( name, GetJointTransform, frame, model ); + constraint->shaft[0].Finish( name, GetJointTransform, frame, model ); + constraint->shaft[1].Finish( name, GetJointTransform, frame, model ); + constraint->axis.Finish( name, GetJointTransform, frame, model ); + constraint->limitAxis.Finish( name, GetJointTransform, frame, model ); + } +} + +/* +================ +idDeclAF::NewBody +================ +*/ +void idDeclAF::NewBody( const char *name ) { + idDeclAF_Body *body; + + body = new (TAG_DECL) idDeclAF_Body(); + body->SetDefault( this ); + body->name = name; + bodies.Append( body ); +} + +/* +================ +idDeclAF::RenameBody + + rename the body with the given name and rename + all constraint body references +================ +*/ +void idDeclAF::RenameBody( const char *oldName, const char *newName ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->name.Icmp( oldName ) == 0 ) { + bodies[i]->name = newName; + break; + } + } + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->body1.Icmp( oldName ) == 0 ) { + constraints[i]->body1 = newName; + } else if ( constraints[i]->body2.Icmp( oldName ) == 0 ) { + constraints[i]->body2 = newName; + } + } +} + +/* +================ +idDeclAF::DeleteBody + + delete the body with the given name and delete + all constraints that reference the body +================ +*/ +void idDeclAF::DeleteBody( const char *name ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->name.Icmp( name ) == 0 ) { + delete bodies[i]; + bodies.RemoveIndex( i ); + break; + } + } + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->body1.Icmp( name ) == 0 || + constraints[i]->body2.Icmp( name ) == 0 ) { + delete constraints[i]; + constraints.RemoveIndex( i ); + i--; + } + } +} + +/* +================ +idDeclAF::NewConstraint +================ +*/ +void idDeclAF::NewConstraint( const char *name ) { + idDeclAF_Constraint *constraint; + + constraint = new (TAG_DECL) idDeclAF_Constraint; + constraint->SetDefault( this ); + constraint->name = name; + constraints.Append( constraint ); +} + +/* +================ +idDeclAF::RenameConstraint +================ +*/ +void idDeclAF::RenameConstraint( const char *oldName, const char *newName ) { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( oldName ) == 0 ) { + constraints[i]->name = newName; + return; + } + } +} + +/* +================ +idDeclAF::DeleteConstraint +================ +*/ +void idDeclAF::DeleteConstraint( const char *name ) { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( name ) == 0 ) { + delete constraints[i]; + constraints.RemoveIndex( i ); + return; + } + } +} + +/* +================ +idDeclAF::idDeclAF +================ +*/ +idDeclAF::idDeclAF() { + FreeData(); +} + +/* +================ +idDeclAF::~idDeclAF +================ +*/ +idDeclAF::~idDeclAF() { + bodies.DeleteContents( true ); + constraints.DeleteContents( true ); +} diff --git a/neo/framework/DeclAF.h b/neo/framework/DeclAF.h new file mode 100644 index 00000000..ed3d6418 --- /dev/null +++ b/neo/framework/DeclAF.h @@ -0,0 +1,216 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLAF_H__ +#define __DECLAF_H__ + +/* +=============================================================================== + + Articulated Figure + +=============================================================================== +*/ + +class idDeclAF; + +typedef enum { + DECLAF_CONSTRAINT_INVALID, + DECLAF_CONSTRAINT_FIXED, + DECLAF_CONSTRAINT_BALLANDSOCKETJOINT, + DECLAF_CONSTRAINT_UNIVERSALJOINT, + DECLAF_CONSTRAINT_HINGE, + DECLAF_CONSTRAINT_SLIDER, + DECLAF_CONSTRAINT_SPRING +} declAFConstraintType_t; + +typedef enum { + DECLAF_JOINTMOD_AXIS, + DECLAF_JOINTMOD_ORIGIN, + DECLAF_JOINTMOD_BOTH +} declAFJointMod_t; + +typedef bool (*getJointTransform_t)( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ); + +class idAFVector { +public: + enum { + VEC_COORDS = 0, + VEC_JOINT, + VEC_BONECENTER, + VEC_BONEDIR + } type; + idStr joint1; + idStr joint2; + +public: + idAFVector(); + + bool Parse( idLexer &src ); + bool Finish( const char *fileName, const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const; + bool Write( idFile *f ) const; + const char * ToString( idStr &str, const int precision = 8 ); + const idVec3 & ToVec3() const { return vec; } + idVec3 & ToVec3() { return vec; } + +private: + mutable idVec3 vec; + bool negate; +}; + +class idDeclAF_Body { +public: + idStr name; + idStr jointName; + declAFJointMod_t jointMod; + int modelType; + idAFVector v1, v2; + int numSides; + float width; + float density; + idAFVector origin; + idAngles angles; + int contents; + int clipMask; + bool selfCollision; + idMat3 inertiaScale; + float linearFriction; + float angularFriction; + float contactFriction; + idStr containedJoints; + idAFVector frictionDirection; + idAFVector contactMotorDirection; +public: + void SetDefault( const idDeclAF *file ); +}; + +class idDeclAF_Constraint { +public: + idStr name; + idStr body1; + idStr body2; + declAFConstraintType_t type; + float friction; + float stretch; + float compress; + float damping; + float restLength; + float minLength; + float maxLength; + idAFVector anchor; + idAFVector anchor2; + idAFVector shaft[2]; + idAFVector axis; + enum { + LIMIT_NONE = -1, + LIMIT_CONE, + LIMIT_PYRAMID + } limit; + idAFVector limitAxis; + float limitAngles[3]; + +public: + void SetDefault( const idDeclAF *file ); +}; + +class idDeclAF : public idDecl { + friend class idAFFileManager; +public: + idDeclAF(); + virtual ~idDeclAF(); + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + + virtual void Finish( const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const; + + bool Save(); + + void NewBody( const char *name ); + void RenameBody( const char *oldName, const char *newName ); + void DeleteBody( const char *name ); + + void NewConstraint( const char *name ); + void RenameConstraint( const char *oldName, const char *newName ); + void DeleteConstraint( const char *name ); + + static int ContentsFromString( const char *str ); + static const char * ContentsToString( const int contents, idStr &str ); + + static declAFJointMod_t JointModFromString( const char *str ); + static const char * JointModToString( declAFJointMod_t jointMod ); + +public: + bool modified; + idStr model; + idStr skin; + float defaultLinearFriction; + float defaultAngularFriction; + float defaultContactFriction; + float defaultConstraintFriction; + float totalMass; + idVec2 suspendVelocity; + idVec2 suspendAcceleration; + float noMoveTime; + float noMoveTranslation; + float noMoveRotation; + float minMoveTime; + float maxMoveTime; + int contents; + int clipMask; + bool selfCollision; + idList bodies; + idList constraints; + +private: + bool ParseContents( idLexer &src, int &c ) const; + bool ParseBody( idLexer &src ); + bool ParseFixed( idLexer &src ); + bool ParseBallAndSocketJoint( idLexer &src ); + bool ParseUniversalJoint( idLexer &src ); + bool ParseHinge( idLexer &src ); + bool ParseSlider( idLexer &src ); + bool ParseSpring( idLexer &src ); + bool ParseSettings( idLexer &src ); + + bool WriteBody( idFile *f, const idDeclAF_Body &body ) const; + bool WriteFixed( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteBallAndSocketJoint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteUniversalJoint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteHinge( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSlider( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSpring( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteConstraint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSettings( idFile *f ) const; + + bool RebuildTextSource(); +}; + +#endif /* !__DECLAF_H__ */ diff --git a/neo/framework/DeclEntityDef.cpp b/neo/framework/DeclEntityDef.cpp new file mode 100644 index 00000000..357bc8bb --- /dev/null +++ b/neo/framework/DeclEntityDef.cpp @@ -0,0 +1,149 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================= +idDeclEntityDef::Size +================= +*/ +size_t idDeclEntityDef::Size() const { + return sizeof( idDeclEntityDef ) + dict.Allocated(); +} + +/* +================ +idDeclEntityDef::FreeData +================ +*/ +void idDeclEntityDef::FreeData() { + dict.Clear(); +} + +/* +================ +idDeclEntityDef::Parse +================ +*/ +bool idDeclEntityDef::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token, token2; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + while (1) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + if ( token.type != TT_STRING ) { + src.Warning( "Expected quoted string, but found '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + if ( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + if ( dict.FindKey( token ) ) { + src.Warning( "'%s' already defined", token.c_str() ); + } + dict.Set( token, token2 ); + } + + // we always automatically set a "classname" key to our name + dict.Set( "classname", GetName() ); + + // "inherit" keys will cause all values from another entityDef to be copied into this one + // if they don't conflict. We can't have circular recursions, because each entityDef will + // never be parsed mroe than once + + // find all of the dicts first, because copying inherited values will modify the dict + idList defList; + + while ( 1 ) { + const idKeyValue *kv; + kv = dict.MatchPrefix( "inherit", NULL ); + if ( !kv ) { + break; + } + + const idDeclEntityDef *copy = static_cast( declManager->FindType( DECL_ENTITYDEF, kv->GetValue(), false ) ); + if ( !copy ) { + src.Warning( "Unknown entityDef '%s' inherited by '%s'", kv->GetValue().c_str(), GetName() ); + } else { + defList.Append( copy ); + } + + // delete this key/value pair + dict.Delete( kv->GetKey() ); + } + + // now copy over the inherited key / value pairs + for ( int i = 0 ; i < defList.Num() ; i++ ) { + dict.SetDefaults( &defList[ i ]->dict ); + } + + game->CacheDictionaryMedia( &dict ); + + return true; +} + +/* +================ +idDeclEntityDef::DefaultDefinition +================ +*/ +const char *idDeclEntityDef::DefaultDefinition() const { + return + "{\n" + "\t" "\"DEFAULTED\"\t\"1\"\n" + "}"; +} + +/* +================ +idDeclEntityDef::Print + +Dumps all key/value pairs, including inherited ones +================ +*/ +void idDeclEntityDef::Print() { + dict.Print(); +} diff --git a/neo/framework/DeclEntityDef.h b/neo/framework/DeclEntityDef.h new file mode 100644 index 00000000..425d3330 --- /dev/null +++ b/neo/framework/DeclEntityDef.h @@ -0,0 +1,51 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLENTITYDEF_H__ +#define __DECLENTITYDEF_H__ + +/* +=============================================================================== + + idDeclEntityDef + +=============================================================================== +*/ + +class idDeclEntityDef : public idDecl { +public: + idDict dict; + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print(); +}; + +#endif /* !__DECLENTITYDEF_H__ */ diff --git a/neo/framework/DeclFX.cpp b/neo/framework/DeclFX.cpp new file mode 100644 index 00000000..f937b895 --- /dev/null +++ b/neo/framework/DeclFX.cpp @@ -0,0 +1,473 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================= +idDeclFX::Size +================= +*/ +size_t idDeclFX::Size() const { + return sizeof( idDeclFX ); +} + +/* +=============== +idDeclFX::Print +=============== +*/ +void idDeclFX::Print() const { + const idDeclFX *list = this; + + common->Printf("%d events\n", list->events.Num() ); + for( int i = 0; i < list->events.Num(); i++ ) { + switch( list->events[i].type ) { + case FX_LIGHT: + common->Printf("FX_LIGHT %s\n", list->events[i].data.c_str()); + break; + case FX_PARTICLE: + common->Printf("FX_PARTICLE %s\n", list->events[i].data.c_str()); + break; + case FX_MODEL: + common->Printf("FX_MODEL %s\n", list->events[i].data.c_str()); + break; + case FX_SOUND: + common->Printf("FX_SOUND %s\n", list->events[i].data.c_str()); + break; + case FX_DECAL: + common->Printf("FX_DECAL %s\n", list->events[i].data.c_str()); + break; + case FX_SHAKE: + common->Printf("FX_SHAKE %s\n", list->events[i].data.c_str()); + break; + case FX_ATTACHLIGHT: + common->Printf("FX_ATTACHLIGHT %s\n", list->events[i].data.c_str()); + break; + case FX_ATTACHENTITY: + common->Printf("FX_ATTACHENTITY %s\n", list->events[i].data.c_str()); + break; + case FX_LAUNCH: + common->Printf("FX_LAUNCH %s\n", list->events[i].data.c_str()); + break; + case FX_SHOCKWAVE: + common->Printf("FX_SHOCKWAVE %s\n", list->events[i].data.c_str()); + break; + } + } +} + +/* +=============== +idDeclFX::List +=============== +*/ +void idDeclFX::List() const { + common->Printf("%s, %d stages\n", GetName(), events.Num() ); +} + +/* +================ +idDeclFX::ParseSingleFXAction +================ +*/ +void idDeclFX::ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction ) { + idToken token; + + FXAction.type = -1; + FXAction.sibling = -1; + + FXAction.data = ""; + FXAction.name = ""; + FXAction.fire = ""; + + FXAction.delay = 0.0f; + FXAction.duration = 0.0f; + FXAction.restart = 0.0f; + FXAction.size = 0.0f; + FXAction.fadeInTime = 0.0f; + FXAction.fadeOutTime = 0.0f; + FXAction.shakeTime = 0.0f; + FXAction.shakeAmplitude = 0.0f; + FXAction.shakeDistance = 0.0f; + FXAction.shakeFalloff = false; + FXAction.shakeImpulse = 0.0f; + FXAction.shakeIgnoreMaster = false; + FXAction.lightRadius = 0.0f; + FXAction.rotate = 0.0f; + FXAction.random1 = 0.0f; + FXAction.random2 = 0.0f; + + FXAction.lightColor = vec3_origin; + FXAction.offset = vec3_origin; + FXAction.axis = mat3_identity; + + FXAction.bindParticles = false; + FXAction.explicitAxis = false; + FXAction.noshadows = false; + FXAction.particleTrackVelocity = false; + FXAction.trackOrigin = false; + FXAction.soundStarted = false; + + while (1) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( !token.Icmp( "shake" ) ) { + FXAction.type = FX_SHAKE; + FXAction.shakeTime = src.ParseFloat(); + src.ExpectTokenString(","); + FXAction.shakeAmplitude = src.ParseFloat(); + src.ExpectTokenString(","); + FXAction.shakeDistance = src.ParseFloat(); + src.ExpectTokenString(","); + FXAction.shakeFalloff = src.ParseBool(); + src.ExpectTokenString(","); + FXAction.shakeImpulse = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "noshadows" ) ) { + FXAction.noshadows = true; + continue; + } + + if ( !token.Icmp( "name" ) ) { + src.ReadToken( &token ); + FXAction.name = token; + continue; + } + + if ( !token.Icmp( "fire") ) { + src.ReadToken( &token ); + FXAction.fire = token; + continue; + } + + if ( !token.Icmp( "random" ) ) { + FXAction.random1 = src.ParseFloat(); + src.ExpectTokenString( "," ); + FXAction.random2 = src.ParseFloat(); + FXAction.delay = 0.0f; // check random + continue; + } + + if ( !token.Icmp( "delay" ) ) { + FXAction.delay = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "rotate" ) ) { + FXAction.rotate = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "duration" ) ) { + FXAction.duration = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "trackorigin" ) ) { + FXAction.trackOrigin = src.ParseBool(); + continue; + } + + if (!token.Icmp("restart")) { + FXAction.restart = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "fadeIn" ) ) { + FXAction.fadeInTime = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "fadeOut" ) ) { + FXAction.fadeOutTime = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "size" ) ) { + FXAction.size = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "offset" ) ) { + FXAction.offset.x = src.ParseFloat(); + src.ExpectTokenString( "," ); + FXAction.offset.y = src.ParseFloat(); + src.ExpectTokenString( "," ); + FXAction.offset.z = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "axis" ) ) { + idVec3 v; + v.x = src.ParseFloat(); + src.ExpectTokenString( "," ); + v.y = src.ParseFloat(); + src.ExpectTokenString( "," ); + v.z = src.ParseFloat(); + v.Normalize(); + FXAction.axis = v.ToMat3(); + FXAction.explicitAxis = true; + continue; + } + + if ( !token.Icmp( "angle" ) ) { + idAngles a; + a[0] = src.ParseFloat(); + src.ExpectTokenString( "," ); + a[1] = src.ParseFloat(); + src.ExpectTokenString( "," ); + a[2] = src.ParseFloat(); + FXAction.axis = a.ToMat3(); + FXAction.explicitAxis = true; + continue; + } + + if ( !token.Icmp( "uselight" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + for( int i = 0; i < events.Num(); i++ ) { + if ( events[i].name.Icmp( FXAction.data ) == 0 ) { + FXAction.sibling = i; + FXAction.lightColor = events[i].lightColor; + FXAction.lightRadius = events[i].lightRadius; + } + } + FXAction.type = FX_LIGHT; + + // precache the light material + declManager->FindMaterial( FXAction.data ); + continue; + } + + if ( !token.Icmp( "attachlight" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_ATTACHLIGHT; + + // precache it + declManager->FindMaterial( FXAction.data ); + continue; + } + + if ( !token.Icmp( "attachentity" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_ATTACHENTITY; + + // precache the model + renderModelManager->FindModel( FXAction.data ); + continue; + } + + if ( !token.Icmp( "launch" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_LAUNCH; + + // precache the entity def + declManager->FindType( DECL_ENTITYDEF, FXAction.data ); + continue; + } + + if ( !token.Icmp( "useModel" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + for( int i = 0; i < events.Num(); i++ ) { + if ( events[i].name.Icmp( FXAction.data ) == 0 ) { + FXAction.sibling = i; + } + } + FXAction.type = FX_MODEL; + + // precache the model + renderModelManager->FindModel( FXAction.data ); + continue; + } + + if ( !token.Icmp( "light" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + src.ExpectTokenString( "," ); + FXAction.lightColor[0] = src.ParseFloat(); + src.ExpectTokenString( "," ); + FXAction.lightColor[1] = src.ParseFloat(); + src.ExpectTokenString( "," ); + FXAction.lightColor[2] = src.ParseFloat(); + src.ExpectTokenString( "," ); + FXAction.lightRadius = src.ParseFloat(); + FXAction.type = FX_LIGHT; + + // precache the light material + declManager->FindMaterial( FXAction.data ); + continue; + } + + if ( !token.Icmp( "model" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_MODEL; + + // precache it + renderModelManager->FindModel( FXAction.data ); + continue; + } + + if ( !token.Icmp( "particle" ) ) { // FIXME: now the same as model + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_PARTICLE; + + // precache it + renderModelManager->FindModel( FXAction.data ); + continue; + } + + if ( !token.Icmp( "decal" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_DECAL; + + // precache it + declManager->FindMaterial( FXAction.data ); + continue; + } + + if ( !token.Icmp( "particleTrackVelocity" ) ) { + FXAction.particleTrackVelocity = true; + continue; + } + + if ( !token.Icmp( "sound" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_SOUND; + + // precache it + declManager->FindSound( FXAction.data ); + continue; + } + + if ( !token.Icmp( "ignoreMaster" ) ) { + FXAction.shakeIgnoreMaster = true; + continue; + } + + if ( !token.Icmp( "shockwave" ) ) { + src.ReadToken( &token ); + FXAction.data = token; + FXAction.type = FX_SHOCKWAVE; + + // precache the entity def + declManager->FindType( DECL_ENTITYDEF, FXAction.data ); + continue; + } + + src.Warning( "FX File: bad token" ); + continue; + } +} + +/* +================ +idDeclFX::Parse +================ +*/ +bool idDeclFX::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "bindto" ) ) { + src.ReadToken( &token ); + joint = token; + continue; + } + + if ( !token.Icmp( "{" ) ) { + idFXSingleAction action; + ParseSingleFXAction( src, action ); + events.Append( action ); + continue; + } + } + + if ( src.HadError() ) { + src.Warning( "FX decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclFX::DefaultDefinition +=================== +*/ +const char *idDeclFX::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "duration\t5\n" + "\t\t" "model\t\t_default\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclFX::FreeData +=================== +*/ +void idDeclFX::FreeData() { + events.Clear(); +} diff --git a/neo/framework/DeclFX.h b/neo/framework/DeclFX.h new file mode 100644 index 00000000..6a466488 --- /dev/null +++ b/neo/framework/DeclFX.h @@ -0,0 +1,113 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLFX_H__ +#define __DECLFX_H__ + +/* +=============================================================================== + + idDeclFX + +=============================================================================== +*/ + +enum { + FX_LIGHT, + FX_PARTICLE, + FX_DECAL, + FX_MODEL, + FX_SOUND, + FX_SHAKE, + FX_ATTACHLIGHT, + FX_ATTACHENTITY, + FX_LAUNCH, + FX_SHOCKWAVE +}; + +// +// single fx structure +// +typedef struct { + int type; + int sibling; + + idStr data; + idStr name; + idStr fire; + + float delay; + float duration; + float restart; + float size; + float fadeInTime; + float fadeOutTime; + float shakeTime; + float shakeAmplitude; + float shakeDistance; + float shakeImpulse; + float lightRadius; + float rotate; + float random1; + float random2; + + idVec3 lightColor; + idVec3 offset; + idMat3 axis; + + bool soundStarted; + bool shakeStarted; + bool shakeFalloff; + bool shakeIgnoreMaster; + bool bindParticles; + bool explicitAxis; + bool noshadows; + bool particleTrackVelocity; + bool trackOrigin; +} idFXSingleAction; + +// +// grouped fx structures +// +class idDeclFX : public idDecl { +public: + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print() const; + virtual void List() const; + + idListevents; + idStr joint; + +private: + void ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction ); +}; + +#endif /* !__DECLFX_H__ */ diff --git a/neo/framework/DeclManager.cpp b/neo/framework/DeclManager.cpp new file mode 100644 index 00000000..fbb47860 --- /dev/null +++ b/neo/framework/DeclManager.cpp @@ -0,0 +1,2433 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* + +GUIs and script remain separately parsed + +Following a parse, all referenced media (and other decls) will have been touched. + +sinTable and cosTable are required for the rotate material keyword to function + +A new FindType on a purged decl will cause it to be reloaded, but a stale pointer to a purged +decl will look like a defaulted decl. + +Moving a decl from one file to another will not be handled correctly by a reload, the material +will be defaulted. + +NULL or empty decl names will always return NULL + Should probably make a default decl for this + +Decls are initially created without a textSource +A parse without textSource set should always just call MakeDefault() +A parse that has an error should internally call MakeDefault() +A purge does nothing to a defaulted decl + +Should we have a "purged" media state separate from the "defaulted" media state? + +reloading over a decl name that was defaulted + +reloading over a decl name that was valid + +missing reload over a previously explicit definition + +*/ + +#define USE_COMPRESSED_DECLS +//#define GET_HUFFMAN_FREQUENCIES + +class idDeclType { +public: + idStr typeName; + declType_t type; + idDecl * (*allocator)(); +}; + +class idDeclFolder { +public: + idStr folder; + idStr extension; + declType_t defaultType; +}; + +class idDeclFile; + +class idDeclLocal : public idDeclBase { + friend class idDeclFile; + friend class idDeclManagerLocal; + +public: + idDeclLocal(); + virtual ~idDeclLocal() {}; + virtual const char * GetName() const; + virtual declType_t GetType() const; + virtual declState_t GetState() const; + virtual bool IsImplicit() const; + virtual bool IsValid() const; + virtual void Invalidate(); + virtual void Reload(); + virtual void EnsureNotPurged(); + virtual int Index() const; + virtual int GetLineNum() const; + virtual const char * GetFileName() const; + virtual size_t Size() const; + virtual void GetText( char *text ) const; + virtual int GetTextLength() const; + virtual void SetText( const char *text ); + virtual bool ReplaceSourceFileText(); + virtual bool SourceFileChanged() const; + virtual void MakeDefault(); + virtual bool EverReferenced() const; + +protected: + virtual bool SetDefaultText(); + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void List() const; + virtual void Print() const; + +protected: + void AllocateSelf(); + + // Parses the decl definition. + // After calling parse, a decl will be guaranteed usable. + void ParseLocal(); + + // Does a MakeDefualt, but flags the decl so that it + // will Parse() the next time the decl is found. + void Purge(); + + // Set textSource possible with compression. + void SetTextLocal( const char *text, const int length ); + +private: + idDecl * self; + + idStr name; // name of the decl + char * textSource; // decl text definition + int textLength; // length of textSource + int compressedLength; // compressed length + idDeclFile * sourceFile; // source file in which the decl was defined + int sourceTextOffset; // offset in source file to decl text + int sourceTextLength; // length of decl text in source file + int sourceLine; // this is where the actual declaration token starts + int checksum; // checksum of the decl text + declType_t type; // decl type + declState_t declState; // decl state + int index; // index in the per-type list + + bool parsedOutsideLevelLoad; // these decls will never be purged + bool everReferenced; // set to true if the decl was ever used + bool referencedThisLevel; // set to true when the decl is used for the current level + bool redefinedInReload; // used during file reloading to make sure a decl that has + // its source removed will be defaulted + idDeclLocal * nextInFile; // next decl in the decl file +}; + +class idDeclFile { +public: + idDeclFile(); + idDeclFile( const char *fileName, declType_t defaultType ); + + void Reload( bool force ); + int LoadAndParse(); + +public: + idStr fileName; + declType_t defaultType; + + ID_TIME_T timestamp; + int checksum; + int fileSize; + int numLines; + + idDeclLocal * decls; +}; + +class idDeclManagerLocal : public idDeclManager { + friend class idDeclLocal; + +public: + virtual void Init(); + virtual void Init2(); + virtual void Shutdown(); + virtual void Reload( bool force ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad(); + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)() ); + virtual void RegisterDeclFolder( const char *folder, const char *extension, declType_t defaultType ); + virtual int GetChecksum() const; + virtual int GetNumDeclTypes() const; + virtual int GetNumDecls( declType_t type ); + virtual const char * GetDeclNameFromType( declType_t type ) const; + virtual declType_t GetDeclTypeFromName( const char *typeName ) const; + virtual const idDecl * FindType( declType_t type, const char *name, bool makeDefault = true ); + virtual const idDecl * DeclByIndex( declType_t type, int index, bool forceParse = true ); + + virtual const idDecl* FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault = true ); + virtual void ReloadFile( const char* filename, bool force ); + + virtual void ListType( const idCmdArgs &args, declType_t type ); + virtual void PrintType( const idCmdArgs &args, declType_t type ); + + virtual idDecl * CreateNewDecl( declType_t type, const char *name, const char *fileName ); + + //BSM Added for the material editors rename capabilities + virtual bool RenameDecl( declType_t type, const char* oldName, const char* newName ); + + virtual void MediaPrint( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual void WritePrecacheCommands( idFile *f ); + + virtual const idMaterial * FindMaterial( const char *name, bool makeDefault = true ); + virtual const idDeclSkin * FindSkin( const char *name, bool makeDefault = true ); + virtual const idSoundShader * FindSound( const char *name, bool makeDefault = true ); + + virtual const idMaterial * MaterialByIndex( int index, bool forceParse = true ); + virtual const idDeclSkin * SkinByIndex( int index, bool forceParse = true ); + virtual const idSoundShader * SoundByIndex( int index, bool forceParse = true ); + + virtual void Touch( const idDecl * decl ); + +public: + static void MakeNameCanonical( const char *name, char *result, int maxLength ); + idDeclLocal * FindTypeWithoutParsing( declType_t type, const char *name, bool makeDefault = true ); + + idDeclType * GetDeclType( int type ) const { return declTypes[type]; } + const idDeclFile * GetImplicitDeclFile() const { return &implicitDecls; } + + void ConvertPDAsToStrings( const idCmdArgs &args ); + +private: + idSysMutex mutex; + + idList declTypes; + idList declFolders; + + idList loadedFiles; + idHashIndex hashTables[DECL_MAX_TYPES]; + idList linearLists[DECL_MAX_TYPES]; + idDeclFile implicitDecls; // this holds all the decls that were created because explicit + // text definitions were not found. Decls that became default + // because of a parse error are not in this list. + int checksum; // checksum of all loaded decl text + int indent; // for MediaPrint + bool insideLevelLoad; + + static idCVar decl_show; + +private: + static void ListDecls_f( const idCmdArgs &args ); + static void ReloadDecls_f( const idCmdArgs &args ); + static void TouchDecl_f( const idCmdArgs &args ); +}; + +idCVar idDeclManagerLocal::decl_show( "decl_show", "0", CVAR_SYSTEM, "set to 1 to print parses, 2 to also print references", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + +idDeclManagerLocal declManagerLocal; +idDeclManager * declManager = &declManagerLocal; + +/* +==================================================================================== + + decl text huffman compression + +==================================================================================== +*/ + +const int MAX_HUFFMAN_SYMBOLS = 256; + +typedef struct huffmanNode_s { + int symbol; + int frequency; + struct huffmanNode_s * next; + struct huffmanNode_s * children[2]; +} huffmanNode_t; + +typedef struct huffmanCode_s { + unsigned long bits[8]; + int numBits; +} huffmanCode_t; + +// compression ratio = 64% +static int huffmanFrequencies[] = { + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00078fb6, 0x000352a7, 0x00000002, 0x00000001, 0x0002795e, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00049600, 0x000000dd, 0x00018732, 0x0000005a, 0x00000007, 0x00000092, 0x0000000a, 0x00000919, + 0x00002dcf, 0x00002dda, 0x00004dfc, 0x0000039a, 0x000058be, 0x00002d13, 0x00014d8c, 0x00023c60, + 0x0002ddb0, 0x0000d1fc, 0x000078c4, 0x00003ec7, 0x00003113, 0x00006b59, 0x00002499, 0x0000184a, + 0x0000250b, 0x00004e38, 0x000001ca, 0x00000011, 0x00000020, 0x000023da, 0x00000012, 0x00000091, + 0x0000000b, 0x00000b14, 0x0000035d, 0x0000137e, 0x000020c9, 0x00000e11, 0x000004b4, 0x00000737, + 0x000006b8, 0x00001110, 0x000006b3, 0x000000fe, 0x00000f02, 0x00000d73, 0x000005f6, 0x00000be4, + 0x00000d86, 0x0000014d, 0x00000d89, 0x0000129b, 0x00000db3, 0x0000015a, 0x00000167, 0x00000375, + 0x00000028, 0x00000112, 0x00000018, 0x00000678, 0x0000081a, 0x00000677, 0x00000003, 0x00018112, + 0x00000001, 0x000441ee, 0x000124b0, 0x0001fa3f, 0x00026125, 0x0005a411, 0x0000e50f, 0x00011820, + 0x00010f13, 0x0002e723, 0x00003518, 0x00005738, 0x0002cc26, 0x0002a9b7, 0x0002db81, 0x0003b5fa, + 0x000185d2, 0x00001299, 0x00030773, 0x0003920d, 0x000411cd, 0x00018751, 0x00005fbd, 0x000099b0, + 0x00009242, 0x00007cf2, 0x00002809, 0x00005a1d, 0x00000001, 0x00005a1d, 0x00000001, 0x00000001, + + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, +}; + +static huffmanCode_t huffmanCodes[MAX_HUFFMAN_SYMBOLS]; +static huffmanNode_t *huffmanTree = NULL; +static int totalUncompressedLength = 0; +static int totalCompressedLength = 0; +static int maxHuffmanBits = 0; + + +/* +================ +ClearHuffmanFrequencies +================ +*/ +void ClearHuffmanFrequencies() { + int i; + + for( i = 0; i < MAX_HUFFMAN_SYMBOLS; i++ ) { + huffmanFrequencies[i] = 1; + } +} + +/* +================ +InsertHuffmanNode +================ +*/ +huffmanNode_t *InsertHuffmanNode( huffmanNode_t *firstNode, huffmanNode_t *node ) { + huffmanNode_t *n, *lastNode; + + lastNode = NULL; + for ( n = firstNode; n; n = n->next ) { + if ( node->frequency <= n->frequency ) { + break; + } + lastNode = n; + } + if ( lastNode ) { + node->next = lastNode->next; + lastNode->next = node; + } else { + node->next = firstNode; + firstNode = node; + } + return firstNode; +} + +/* +================ +BuildHuffmanCode_r +================ +*/ +void BuildHuffmanCode_r( huffmanNode_t *node, huffmanCode_t code, huffmanCode_t codes[MAX_HUFFMAN_SYMBOLS] ) { + if ( node->symbol == -1 ) { + huffmanCode_t newCode = code; + assert( code.numBits < sizeof( codes[0].bits ) * 8 ); + newCode.numBits++; + if ( code.numBits > maxHuffmanBits ) { + maxHuffmanBits = newCode.numBits; + } + BuildHuffmanCode_r( node->children[0], newCode, codes ); + newCode.bits[code.numBits >> 5] |= 1 << ( code.numBits & 31 ); + BuildHuffmanCode_r( node->children[1], newCode, codes ); + } else { + assert( code.numBits <= sizeof( codes[0].bits ) * 8 ); + codes[node->symbol] = code; + } +} + +/* +================ +FreeHuffmanTree_r +================ +*/ +void FreeHuffmanTree_r( huffmanNode_t *node ) { + if ( node->symbol == -1 ) { + FreeHuffmanTree_r( node->children[0] ); + FreeHuffmanTree_r( node->children[1] ); + } + delete node; +} + +/* +================ +HuffmanHeight_r +================ +*/ +int HuffmanHeight_r( huffmanNode_t *node ) { + if ( node == NULL ) { + return -1; + } + int left = HuffmanHeight_r( node->children[0] ); + int right = HuffmanHeight_r( node->children[1] ); + if ( left > right ) { + return left + 1; + } + return right + 1; +} + +/* +================ +SetupHuffman +================ +*/ +void SetupHuffman() { + int i, height; + huffmanNode_t *firstNode, *node; + huffmanCode_t code; + + firstNode = NULL; + for( i = 0; i < MAX_HUFFMAN_SYMBOLS; i++ ) { + node = new (TAG_DECL) huffmanNode_t; + node->symbol = i; + node->frequency = huffmanFrequencies[i]; + node->next = NULL; + node->children[0] = NULL; + node->children[1] = NULL; + firstNode = InsertHuffmanNode( firstNode, node ); + } + + for( i = 1; i < MAX_HUFFMAN_SYMBOLS; i++ ) { + node = new (TAG_DECL) huffmanNode_t; + node->symbol = -1; + node->frequency = firstNode->frequency + firstNode->next->frequency; + node->next = NULL; + node->children[0] = firstNode; + node->children[1] = firstNode->next; + firstNode = InsertHuffmanNode( firstNode->next->next, node ); + } + + maxHuffmanBits = 0; + memset( &code, 0, sizeof( code ) ); + BuildHuffmanCode_r( firstNode, code, huffmanCodes ); + + huffmanTree = firstNode; + + height = HuffmanHeight_r( firstNode ); + assert( maxHuffmanBits == height ); +} + +/* +================ +ShutdownHuffman +================ +*/ +void ShutdownHuffman() { + if ( huffmanTree ) { + FreeHuffmanTree_r( huffmanTree ); + } +} + +/* +================ +HuffmanCompressText +================ +*/ +int HuffmanCompressText( const char *text, int textLength, byte *compressed, int maxCompressedSize ) { + int i, j; + idBitMsg msg; + + totalUncompressedLength += textLength; + + msg.InitWrite( compressed, maxCompressedSize ); + msg.BeginWriting(); + for ( i = 0; i < textLength; i++ ) { + const huffmanCode_t &code = huffmanCodes[(unsigned char)text[i]]; + for ( j = 0; j < ( code.numBits >> 5 ); j++ ) { + msg.WriteBits( code.bits[j], 32 ); + } + if ( code.numBits & 31 ) { + msg.WriteBits( code.bits[j], code.numBits & 31 ); + } + } + + totalCompressedLength += msg.GetSize(); + + return msg.GetSize(); +} + +/* +================ +HuffmanDecompressText +================ +*/ +int HuffmanDecompressText( char *text, int textLength, const byte *compressed, int compressedSize ) { + int i, bit; + idBitMsg msg; + huffmanNode_t *node; + + msg.InitRead( compressed, compressedSize ); + msg.SetSize( compressedSize ); + msg.BeginReading(); + for ( i = 0; i < textLength; i++ ) { + node = huffmanTree; + do { + bit = msg.ReadBits( 1 ); + node = node->children[bit]; + } while( node->symbol == -1 ); + text[i] = node->symbol; + } + text[i] = '\0'; + return msg.GetReadCount(); +} + +/* +================ +ListHuffmanFrequencies_f +================ +*/ +void ListHuffmanFrequencies_f( const idCmdArgs &args ) { + int i; + float compression; + compression = !totalUncompressedLength ? 100 : 100 * totalCompressedLength / totalUncompressedLength; + common->Printf( "// compression ratio = %d%%\n", (int)compression ); + common->Printf( "static int huffmanFrequencies[] = {\n" ); + for( i = 0; i < MAX_HUFFMAN_SYMBOLS; i += 8 ) { + common->Printf( "\t0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x,\n", + huffmanFrequencies[i+0], huffmanFrequencies[i+1], + huffmanFrequencies[i+2], huffmanFrequencies[i+3], + huffmanFrequencies[i+4], huffmanFrequencies[i+5], + huffmanFrequencies[i+6], huffmanFrequencies[i+7]); + } + common->Printf( "}\n" ); +} + +void ConvertPDAsToStrings_f( const idCmdArgs &args ) { + declManagerLocal.ConvertPDAsToStrings( args ); +} + +/* +==================================================================================== + + idDeclFile + +==================================================================================== +*/ + +/* +================ +idDeclFile::idDeclFile +================ +*/ +idDeclFile::idDeclFile( const char *fileName, declType_t defaultType ) { + this->fileName = fileName; + this->defaultType = defaultType; + this->timestamp = 0; + this->checksum = 0; + this->fileSize = 0; + this->numLines = 0; + this->decls = NULL; +} + +/* +================ +idDeclFile::idDeclFile +================ +*/ +idDeclFile::idDeclFile() { + this->fileName = ""; + this->defaultType = DECL_MAX_TYPES; + this->timestamp = 0; + this->checksum = 0; + this->fileSize = 0; + this->numLines = 0; + this->decls = NULL; +} + +/* +================ +idDeclFile::Reload + +ForceReload will cause it to reload even if the timestamp hasn't changed +================ +*/ +void idDeclFile::Reload( bool force ) { + // check for an unchanged timestamp + if ( !force && timestamp != 0 ) { + ID_TIME_T testTimeStamp; + fileSystem->ReadFile( fileName, NULL, &testTimeStamp ); + + if ( testTimeStamp == timestamp ) { + return; + } + } + + // parse the text + LoadAndParse(); +} + +/* +================ +idDeclFile::LoadAndParse + +This is used during both the initial load, and any reloads +================ +*/ +int c_savedMemory = 0; + +int idDeclFile::LoadAndParse() { + int i, numTypes; + idLexer src; + idToken token; + int startMarker; + char * buffer; + int length, size; + int sourceLine; + idStr name; + idDeclLocal *newDecl; + bool reparse; + + // load the text + common->DPrintf( "...loading '%s'\n", fileName.c_str() ); + length = fileSystem->ReadFile( fileName, (void **)&buffer, ×tamp ); + if ( length == -1 ) { + common->FatalError( "couldn't load %s", fileName.c_str() ); + return 0; + } + + if ( !src.LoadMemory( buffer, length, fileName ) ) { + common->Error( "Couldn't parse %s", fileName.c_str() ); + Mem_Free( buffer ); + return 0; + } + + // mark all the defs that were from the last reload of this file + for ( idDeclLocal *decl = decls; decl; decl = decl->nextInFile ) { + decl->redefinedInReload = false; + } + + src.SetFlags( DECL_LEXER_FLAGS ); + + checksum = MD5_BlockChecksum( buffer, length ); + + fileSize = length; + + // scan through, identifying each individual declaration + while( 1 ) { + + startMarker = src.GetFileOffset(); + sourceLine = src.GetLineNum(); + + // parse the decl type name + if ( !src.ReadToken( &token ) ) { + break; + } + + declType_t identifiedType = DECL_MAX_TYPES; + + // get the decl type from the type name + numTypes = declManagerLocal.GetNumDeclTypes(); + for ( i = 0; i < numTypes; i++ ) { + idDeclType *typeInfo = declManagerLocal.GetDeclType( i ); + if ( typeInfo != NULL && typeInfo->typeName.Icmp( token ) == 0 ) { + identifiedType = (declType_t) typeInfo->type; + break; + } + } + + if ( i >= numTypes ) { + + if ( token.Icmp( "{" ) == 0 ) { + + // if we ever see an open brace, we somehow missed the [type] prefix + src.Warning( "Missing decl name" ); + src.SkipBracedSection( false ); + continue; + + } else { + + if ( defaultType == DECL_MAX_TYPES ) { + src.Warning( "No type" ); + continue; + } + src.UnreadToken( &token ); + // use the default type + identifiedType = defaultType; + } + } + + // now parse the name + if ( !src.ReadToken( &token ) ) { + src.Warning( "Type without definition at end of file" ); + break; + } + + if ( !token.Icmp( "{" ) ) { + // if we ever see an open brace, we somehow missed the [type] prefix + src.Warning( "Missing decl name" ); + src.SkipBracedSection( false ); + continue; + } + + // FIXME: export decls are only used by the model exporter, they are skipped here for now + if ( identifiedType == DECL_MODELEXPORT ) { + src.SkipBracedSection(); + continue; + } + + name = token; + + // make sure there's a '{' + if ( !src.ReadToken( &token ) ) { + src.Warning( "Type without definition at end of file" ); + break; + } + if ( token != "{" ) { + src.Warning( "Expecting '{' but found '%s'", token.c_str() ); + continue; + } + src.UnreadToken( &token ); + + // now take everything until a matched closing brace + src.SkipBracedSection(); + size = src.GetFileOffset() - startMarker; + + // look it up, possibly getting a newly created default decl + reparse = false; + newDecl = declManagerLocal.FindTypeWithoutParsing( identifiedType, name, false ); + if ( newDecl ) { + // update the existing copy + if ( newDecl->sourceFile != this || newDecl->redefinedInReload ) { + src.Warning( "%s '%s' previously defined at %s:%i", declManagerLocal.GetDeclNameFromType( identifiedType ), + name.c_str(), newDecl->sourceFile->fileName.c_str(), newDecl->sourceLine ); + continue; + } + if ( newDecl->declState != DS_UNPARSED ) { + reparse = true; + } + } else { + // allow it to be created as a default, then add it to the per-file list + newDecl = declManagerLocal.FindTypeWithoutParsing( identifiedType, name, true ); + newDecl->nextInFile = this->decls; + this->decls = newDecl; + } + + newDecl->redefinedInReload = true; + + if ( newDecl->textSource ) { + Mem_Free( newDecl->textSource ); + newDecl->textSource = NULL; + } + + newDecl->SetTextLocal( buffer + startMarker, size ); + newDecl->sourceFile = this; + newDecl->sourceTextOffset = startMarker; + newDecl->sourceTextLength = size; + newDecl->sourceLine = sourceLine; + newDecl->declState = DS_UNPARSED; + + // if it is currently in use, reparse it immedaitely + if ( reparse ) { + newDecl->ParseLocal(); + } + } + + numLines = src.GetLineNum(); + + Mem_Free( buffer ); + + // any defs that weren't redefinedInReload should now be defaulted + for ( idDeclLocal *decl = decls ; decl ; decl = decl->nextInFile ) { + if ( decl->redefinedInReload == false ) { + decl->MakeDefault(); + decl->sourceTextOffset = decl->sourceFile->fileSize; + decl->sourceTextLength = 0; + decl->sourceLine = decl->sourceFile->numLines; + } + } + + return checksum; +} + +/* +==================================================================================== + + idDeclManagerLocal + +==================================================================================== +*/ + +const char *listDeclStrings[] = { "current", "all", "ever", NULL }; + +/* +=================== +idDeclManagerLocal::Init +=================== +*/ +void idDeclManagerLocal::Init() { + + common->Printf( "----- Initializing Decls -----\n" ); + + checksum = 0; + +#ifdef USE_COMPRESSED_DECLS + SetupHuffman(); +#endif + +#ifdef GET_HUFFMAN_FREQUENCIES + ClearHuffmanFrequencies(); +#endif + + // decls used throughout the engine + RegisterDeclType( "table", DECL_TABLE, idDeclAllocator ); + RegisterDeclType( "material", DECL_MATERIAL, idDeclAllocator ); + RegisterDeclType( "skin", DECL_SKIN, idDeclAllocator ); + RegisterDeclType( "sound", DECL_SOUND, idDeclAllocator ); + + RegisterDeclType( "entityDef", DECL_ENTITYDEF, idDeclAllocator ); + RegisterDeclType( "mapDef", DECL_MAPDEF, idDeclAllocator ); + RegisterDeclType( "fx", DECL_FX, idDeclAllocator ); + RegisterDeclType( "particle", DECL_PARTICLE, idDeclAllocator ); + RegisterDeclType( "articulatedFigure", DECL_AF, idDeclAllocator ); + RegisterDeclType( "pda", DECL_PDA, idDeclAllocator ); + RegisterDeclType( "email", DECL_EMAIL, idDeclAllocator ); + RegisterDeclType( "video", DECL_VIDEO, idDeclAllocator ); + RegisterDeclType( "audio", DECL_AUDIO, idDeclAllocator ); + + RegisterDeclFolder( "materials", ".mtr", DECL_MATERIAL ); + + // add console commands + cmdSystem->AddCommand( "listDecls", ListDecls_f, CMD_FL_SYSTEM, "lists all decls" ); + + cmdSystem->AddCommand( "reloadDecls", ReloadDecls_f, CMD_FL_SYSTEM, "reloads decls" ); + cmdSystem->AddCommand( "touch", TouchDecl_f, CMD_FL_SYSTEM, "touches a decl" ); + + cmdSystem->AddCommand( "listTables", idListDecls_f, CMD_FL_SYSTEM, "lists tables", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listMaterials", idListDecls_f, CMD_FL_SYSTEM, "lists materials", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listSkins", idListDecls_f, CMD_FL_SYSTEM, "lists skins", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listSoundShaders", idListDecls_f, CMD_FL_SYSTEM, "lists sound shaders", idCmdSystem::ArgCompletion_String ); + + cmdSystem->AddCommand( "listEntityDefs", idListDecls_f, CMD_FL_SYSTEM, "lists entity defs", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listFX", idListDecls_f, CMD_FL_SYSTEM, "lists FX systems", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listParticles", idListDecls_f, CMD_FL_SYSTEM, "lists particle systems", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listAF", idListDecls_f, CMD_FL_SYSTEM, "lists articulated figures", idCmdSystem::ArgCompletion_String); + + cmdSystem->AddCommand( "listPDAs", idListDecls_f, CMD_FL_SYSTEM, "lists PDAs", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listEmails", idListDecls_f, CMD_FL_SYSTEM, "lists Emails", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listVideos", idListDecls_f, CMD_FL_SYSTEM, "lists Videos", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listAudios", idListDecls_f, CMD_FL_SYSTEM, "lists Audios", idCmdSystem::ArgCompletion_String ); + + cmdSystem->AddCommand( "printTable", idPrintDecls_f, CMD_FL_SYSTEM, "prints a table", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printMaterial", idPrintDecls_f, CMD_FL_SYSTEM, "prints a material", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printSkin", idPrintDecls_f, CMD_FL_SYSTEM, "prints a skin", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printSoundShader", idPrintDecls_f, CMD_FL_SYSTEM, "prints a sound shader", idCmdSystem::ArgCompletion_Decl ); + + cmdSystem->AddCommand( "printEntityDef", idPrintDecls_f, CMD_FL_SYSTEM, "prints an entity def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printFX", idPrintDecls_f, CMD_FL_SYSTEM, "prints an FX system", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printParticle", idPrintDecls_f, CMD_FL_SYSTEM, "prints a particle system", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printAF", idPrintDecls_f, CMD_FL_SYSTEM, "prints an articulated figure", idCmdSystem::ArgCompletion_Decl ); + + cmdSystem->AddCommand( "printPDA", idPrintDecls_f, CMD_FL_SYSTEM, "prints an PDA", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printEmail", idPrintDecls_f, CMD_FL_SYSTEM, "prints an Email", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printVideo", idPrintDecls_f, CMD_FL_SYSTEM, "prints an Audio", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printAudio", idPrintDecls_f, CMD_FL_SYSTEM, "prints a Video", idCmdSystem::ArgCompletion_Decl ); + + cmdSystem->AddCommand( "listHuffmanFrequencies", ListHuffmanFrequencies_f, CMD_FL_SYSTEM, "lists decl text character frequencies" ); + + cmdSystem->AddCommand( "convertPDAsToStrings", ConvertPDAsToStrings_f, CMD_FL_SYSTEM, "Converts *.pda files to text which can be plugged into *.lang files." ); + + common->Printf( "------------------------------\n" ); +} + +void idDeclManagerLocal::Init2() { + RegisterDeclFolder( "skins", ".skin", DECL_SKIN ); + RegisterDeclFolder( "sound", ".sndshd", DECL_SOUND ); +} + +/* +=================== +idDeclManagerLocal::Shutdown +=================== +*/ +void idDeclManagerLocal::Shutdown() { + int i, j; + idDeclLocal *decl; + + // free decls + for ( i = 0; i < DECL_MAX_TYPES; i++ ) { + for ( j = 0; j < linearLists[i].Num(); j++ ) { + decl = linearLists[i][j]; + if ( decl->self != NULL ) { + decl->self->FreeData(); + delete decl->self; + } + if ( decl->textSource ) { + Mem_Free( decl->textSource ); + decl->textSource = NULL; + } + delete decl; + } + linearLists[i].Clear(); + hashTables[i].Free(); + } + + // free decl files + loadedFiles.DeleteContents( true ); + + // free the decl types and folders + declTypes.DeleteContents( true ); + declFolders.DeleteContents( true ); + +#ifdef USE_COMPRESSED_DECLS + ShutdownHuffman(); +#endif +} + +/* +=================== +idDeclManagerLocal::Reload +=================== +*/ +void idDeclManagerLocal::Reload( bool force ) { + for ( int i = 0; i < loadedFiles.Num(); i++ ) { + loadedFiles[i]->Reload( force ); + } +} + +/* +=================== +idDeclManagerLocal::BeginLevelLoad +=================== +*/ +void idDeclManagerLocal::BeginLevelLoad() { + insideLevelLoad = true; + + // clear all the referencedThisLevel flags and purge all the data + // so the next reference will cause a reparse + for ( int i = 0; i < DECL_MAX_TYPES; i++ ) { + int num = linearLists[i].Num(); + for ( int j = 0 ; j < num ; j++ ) { + idDeclLocal *decl = linearLists[i][j]; + decl->Purge(); + } + } +} + +/* +=================== +idDeclManagerLocal::EndLevelLoad +=================== +*/ +void idDeclManagerLocal::EndLevelLoad() { + insideLevelLoad = false; + + // we don't need to do anything here, but the image manager, model manager, + // and sound sample manager will need to free media that was not referenced +} + +/* +=================== +idDeclManagerLocal::RegisterDeclType +=================== +*/ +void idDeclManagerLocal::RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)() ) { + idDeclType *declType; + + if ( type < declTypes.Num() && declTypes[(int)type] ) { + common->Warning( "idDeclManager::RegisterDeclType: type '%s' already exists", typeName ); + return; + } + + declType = new (TAG_DECL) idDeclType; + declType->typeName = typeName; + declType->type = type; + declType->allocator = allocator; + + if ( (int)type + 1 > declTypes.Num() ) { + declTypes.AssureSize( (int)type + 1, NULL ); + } + declTypes[type] = declType; +} + +/* +=================== +idDeclManagerLocal::RegisterDeclFolder +=================== +*/ +void idDeclManagerLocal::RegisterDeclFolder( const char *folder, const char *extension, declType_t defaultType ) { + int i, j; + idStr fileName; + idDeclFolder *declFolder; + idFileList *fileList; + idDeclFile *df; + + // check whether this folder / extension combination already exists + for ( i = 0; i < declFolders.Num(); i++ ) { + if ( declFolders[i]->folder.Icmp( folder ) == 0 && declFolders[i]->extension.Icmp( extension ) == 0 ) { + break; + } + } + if ( i < declFolders.Num() ) { + declFolder = declFolders[i]; + } else { + declFolder = new (TAG_DECL) idDeclFolder; + declFolder->folder = folder; + declFolder->extension = extension; + declFolder->defaultType = defaultType; + declFolders.Append( declFolder ); + } + + // scan for decl files + fileList = fileSystem->ListFiles( declFolder->folder, declFolder->extension, true ); + + // load and parse decl files + for ( i = 0; i < fileList->GetNumFiles(); i++ ) { + fileName = declFolder->folder + "/" + fileList->GetFile( i ); + + // check whether this file has already been loaded + for ( j = 0; j < loadedFiles.Num(); j++ ) { + if ( fileName.Icmp( loadedFiles[j]->fileName ) == 0 ) { + break; + } + } + if ( j < loadedFiles.Num() ) { + df = loadedFiles[j]; + } else { + df = new (TAG_DECL) idDeclFile( fileName, defaultType ); + loadedFiles.Append( df ); + } + df->LoadAndParse(); + } + + fileSystem->FreeFileList( fileList ); +} + +/* +=================== +idDeclManagerLocal::GetChecksum +=================== +*/ +int idDeclManagerLocal::GetChecksum() const { + int i, j, total, num; + int *checksumData; + + // get the total number of decls + total = 0; + for ( i = 0; i < DECL_MAX_TYPES; i++ ) { + total += linearLists[i].Num(); + } + + checksumData = (int *) _alloca16( total * 2 * sizeof( int ) ); + + total = 0; + for ( i = 0; i < DECL_MAX_TYPES; i++ ) { + declType_t type = (declType_t) i; + + // FIXME: not particularly pretty but PDAs and associated decls are localized and should not be checksummed + if ( type == DECL_PDA || type == DECL_VIDEO || type == DECL_AUDIO || type == DECL_EMAIL ) { + continue; + } + + num = linearLists[i].Num(); + for ( j = 0; j < num; j++ ) { + idDeclLocal *decl = linearLists[i][j]; + + if ( decl->sourceFile == &implicitDecls ) { + continue; + } + + checksumData[total*2+0] = total; + checksumData[total*2+1] = decl->checksum; + total++; + } + } + + LittleRevBytes( checksumData, sizeof(int), total * 2 ); + return MD5_BlockChecksum( checksumData, total * 2 * sizeof( int ) ); +} + +/* +=================== +idDeclManagerLocal::GetNumDeclTypes +=================== +*/ +int idDeclManagerLocal::GetNumDeclTypes() const { + return declTypes.Num(); +} + +/* +=================== +idDeclManagerLocal::GetDeclNameFromType +=================== +*/ +const char * idDeclManagerLocal::GetDeclNameFromType( declType_t type ) const { + int typeIndex = (int)type; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::GetDeclNameFromType: bad type: %i", typeIndex ); + } + return declTypes[typeIndex]->typeName; +} + +/* +=================== +idDeclManagerLocal::GetDeclTypeFromName +=================== +*/ +declType_t idDeclManagerLocal::GetDeclTypeFromName( const char *typeName ) const { + int i; + + for ( i = 0; i < declTypes.Num(); i++ ) { + if ( declTypes[i] && declTypes[i]->typeName.Icmp( typeName ) == 0 ) { + return (declType_t)declTypes[i]->type; + } + } + return DECL_MAX_TYPES; +} + +/* +================= +idDeclManagerLocal::FindType + +External users will always cause the decl to be parsed before returning +================= +*/ +const idDecl *idDeclManagerLocal::FindType( declType_t type, const char *name, bool makeDefault ) { + idDeclLocal *decl; + + idScopedCriticalSection cs( mutex ); + + if ( !name || !name[0] ) { + name = "_emptyName"; + //common->Warning( "idDeclManager::FindType: empty %s name", GetDeclType( (int)type )->typeName.c_str() ); + } + + decl = FindTypeWithoutParsing( type, name, makeDefault ); + if ( !decl ) { + return NULL; + } + + decl->AllocateSelf(); + + // if it hasn't been parsed yet, parse it now + if ( decl->declState == DS_UNPARSED ) { + if ( !idLib::IsMainThread() ) { + // we can't load images from a background thread on OpenGL, + // the renderer on the main thread should parse it if needed + idLib::Error( "Attempted to load %s decl '%s' from game thread!", GetDeclNameFromType( type ), name ); + } + decl->ParseLocal(); + } + + // mark it as referenced + decl->referencedThisLevel = true; + decl->everReferenced = true; + if ( insideLevelLoad ) { + decl->parsedOutsideLevelLoad = false; + } + + return decl->self; +} + +/* +=============== +idDeclManagerLocal::FindDeclWithoutParsing +=============== +*/ +const idDecl* idDeclManagerLocal::FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault) { + idDeclLocal* decl; + decl = FindTypeWithoutParsing(type, name, makeDefault); + if(decl) { + return decl->self; + } + return NULL; +} + +/* +=============== +idDeclManagerLocal::ReloadFile +=============== +*/ +void idDeclManagerLocal::ReloadFile( const char* filename, bool force ) { + for ( int i = 0; i < loadedFiles.Num(); i++ ) { + if(!loadedFiles[i]->fileName.Icmp(filename)) { + checksum ^= loadedFiles[i]->checksum; + loadedFiles[i]->Reload( force ); + checksum ^= loadedFiles[i]->checksum; + } + } +} + +/* +=================== +idDeclManagerLocal::GetNumDecls +=================== +*/ +int idDeclManagerLocal::GetNumDecls( declType_t type ) { + int typeIndex = (int)type; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::GetNumDecls: bad type: %i", typeIndex ); + return 0; + } + return linearLists[ typeIndex ].Num(); +} + +/* +=================== +idDeclManagerLocal::DeclByIndex +=================== +*/ +const idDecl *idDeclManagerLocal::DeclByIndex( declType_t type, int index, bool forceParse ) { + int typeIndex = (int)type; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::DeclByIndex: bad type: %i", typeIndex ); + return NULL; + } + if ( index < 0 || index >= linearLists[ typeIndex ].Num() ) { + common->Error( "idDeclManager::DeclByIndex: out of range" ); + } + idDeclLocal *decl = linearLists[ typeIndex ][ index ]; + + decl->AllocateSelf(); + + if ( forceParse && decl->declState == DS_UNPARSED ) { + decl->ParseLocal(); + } + + return decl->self; +} + +/* +=================== +idDeclManagerLocal::ListType + +list* +Lists decls currently referenced + +list* ever +Lists decls that have been referenced at least once since app launched + +list* all +Lists every decl declared, even if it hasn't been referenced or parsed + +FIXME: alphabetized, wildcards? +=================== +*/ +void idDeclManagerLocal::ListType( const idCmdArgs &args, declType_t type ) { + bool all, ever; + + if ( !idStr::Icmp( args.Argv( 1 ), "all" ) ) { + all = true; + } else { + all = false; + } + if ( !idStr::Icmp( args.Argv( 1 ), "ever" ) ) { + ever = true; + } else { + ever = false; + } + + common->Printf( "--------------------\n" ); + int printed = 0; + int count = linearLists[ (int)type ].Num(); + for ( int i = 0 ; i < count ; i++ ) { + idDeclLocal *decl = linearLists[ (int)type ][ i ]; + + if ( !all && decl->declState == DS_UNPARSED ) { + continue; + } + + if ( !all && !ever && !decl->referencedThisLevel ) { + continue; + } + + if ( decl->referencedThisLevel ) { + common->Printf( "*" ); + } else if ( decl->everReferenced ) { + common->Printf( "." ); + } else { + common->Printf( " " ); + } + if ( decl->declState == DS_DEFAULTED ) { + common->Printf( "D" ); + } else { + common->Printf( " " ); + } + common->Printf( "%4i: ", decl->index ); + printed++; + if ( decl->declState == DS_UNPARSED ) { + // doesn't have any type specific data yet + common->Printf( "%s\n", decl->GetName() ); + } else { + decl->self->List(); + } + } + + common->Printf( "--------------------\n" ); + common->Printf( "%i of %i %s\n", printed, count, declTypes[type]->typeName.c_str() ); +} + +/* +=================== +idDeclManagerLocal::PrintType +=================== +*/ +void idDeclManagerLocal::PrintType( const idCmdArgs &args, declType_t type ) { + // individual decl types may use additional command parameters + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: Print [type specific parms]\n" ); + return; + } + + // look it up, skipping the public path so it won't parse or reference + idDeclLocal *decl = FindTypeWithoutParsing( type, args.Argv( 1 ), false ); + if ( !decl ) { + common->Printf( "%s '%s' not found.\n", declTypes[ type ]->typeName.c_str(), args.Argv( 1 ) ); + return; + } + + // print information common to all decls + common->Printf( "%s %s:\n", declTypes[ type ]->typeName.c_str(), decl->name.c_str() ); + common->Printf( "source: %s:%i\n", decl->sourceFile->fileName.c_str(), decl->sourceLine ); + common->Printf( "----------\n" ); + if ( decl->textSource != NULL ) { + char *declText = (char *)_alloca( decl->textLength + 1 ); + decl->GetText( declText ); + common->Printf( "%s\n", declText ); + } else { + common->Printf( "NO SOURCE\n" ); + } + common->Printf( "----------\n" ); + switch( decl->declState ) { + case DS_UNPARSED: + common->Printf( "Unparsed.\n" ); + break; + case DS_DEFAULTED: + common->Printf( "\n" ); + break; + case DS_PARSED: + common->Printf( "Parsed.\n" ); + break; + } + + if ( decl->referencedThisLevel ) { + common->Printf( "Currently referenced this level.\n" ); + } else if ( decl->everReferenced ) { + common->Printf( "Referenced in a previous level.\n" ); + } else { + common->Printf( "Never referenced.\n" ); + } + + // allow type-specific data to be printed + if ( decl->self != NULL ) { + decl->self->Print(); + } +} + +/* +=================== +idDeclManagerLocal::CreateNewDecl +=================== +*/ +idDecl *idDeclManagerLocal::CreateNewDecl( declType_t type, const char *name, const char *_fileName ) { + int typeIndex = (int)type; + int i, hash; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL || typeIndex >= DECL_MAX_TYPES ) { + common->FatalError( "idDeclManager::CreateNewDecl: bad type: %i", typeIndex ); + return NULL; + } + + char canonicalName[MAX_STRING_CHARS]; + + MakeNameCanonical( name, canonicalName, sizeof( canonicalName ) ); + + idStr fileName = _fileName; + fileName.BackSlashesToSlashes(); + + // see if it already exists + hash = hashTables[typeIndex].GenerateKey( canonicalName, false ); + for ( i = hashTables[typeIndex].First( hash ); i >= 0; i = hashTables[typeIndex].Next( i ) ) { + if ( linearLists[typeIndex][i]->name.Icmp( canonicalName ) == 0 ) { + linearLists[typeIndex][i]->AllocateSelf(); + return linearLists[typeIndex][i]->self; + } + } + + idDeclFile *sourceFile; + + // find existing source file or create a new one + for ( i = 0; i < loadedFiles.Num(); i++ ) { + if ( loadedFiles[i]->fileName.Icmp( fileName ) == 0 ) { + break; + } + } + if ( i < loadedFiles.Num() ) { + sourceFile = loadedFiles[i]; + } else { + sourceFile = new (TAG_DECL) idDeclFile( fileName, type ); + loadedFiles.Append( sourceFile ); + } + + idDeclLocal *decl = new (TAG_DECL) idDeclLocal; + decl->name = canonicalName; + decl->type = type; + decl->declState = DS_UNPARSED; + decl->AllocateSelf(); + idStr header = declTypes[typeIndex]->typeName; + idStr defaultText = decl->self->DefaultDefinition(); + + + int size = header.Length() + 1 + idStr::Length( canonicalName ) + 1 + defaultText.Length(); + char *declText = ( char * ) _alloca( size + 1 ); + + memcpy( declText, header, header.Length() ); + declText[header.Length()] = ' '; + memcpy( declText + header.Length() + 1, canonicalName, idStr::Length( canonicalName ) ); + declText[header.Length() + 1 + idStr::Length( canonicalName )] = ' '; + memcpy( declText + header.Length() + 1 + idStr::Length( canonicalName ) + 1, defaultText, defaultText.Length() + 1 ); + + decl->SetTextLocal( declText, size ); + decl->sourceFile = sourceFile; + decl->sourceTextOffset = sourceFile->fileSize; + decl->sourceTextLength = 0; + decl->sourceLine = sourceFile->numLines; + + decl->ParseLocal(); + + // add this decl to the source file list + decl->nextInFile = sourceFile->decls; + sourceFile->decls = decl; + + // add it to the hash table and linear list + decl->index = linearLists[typeIndex].Num(); + hashTables[typeIndex].Add( hash, linearLists[typeIndex].Append( decl ) ); + + return decl->self; +} + +/* +=============== +idDeclManagerLocal::RenameDecl +=============== +*/ +bool idDeclManagerLocal::RenameDecl( declType_t type, const char* oldName, const char* newName ) { + + char canonicalOldName[MAX_STRING_CHARS]; + MakeNameCanonical( oldName, canonicalOldName, sizeof( canonicalOldName )); + + char canonicalNewName[MAX_STRING_CHARS]; + MakeNameCanonical( newName, canonicalNewName, sizeof( canonicalNewName ) ); + + idDeclLocal *decl = NULL; + + // make sure it already exists + int typeIndex = (int)type; + int i, hash; + hash = hashTables[typeIndex].GenerateKey( canonicalOldName, false ); + for ( i = hashTables[typeIndex].First( hash ); i >= 0; i = hashTables[typeIndex].Next( i ) ) { + if ( linearLists[typeIndex][i]->name.Icmp( canonicalOldName ) == 0 ) { + decl = linearLists[typeIndex][i]; + break; + } + } + if(!decl) + return false; + + //if ( !hashTables[(int)type].Get( canonicalOldName, &declPtr ) ) + // return false; + + //decl = *declPtr; + + //Change the name + decl->name = canonicalNewName; + + + // add it to the hash table + //hashTables[(int)decl->type].Set( decl->name, decl ); + int newhash = hashTables[typeIndex].GenerateKey( canonicalNewName, false ); + hashTables[typeIndex].Add( newhash, decl->index ); + + //Remove the old hash item + hashTables[typeIndex].Remove(hash, decl->index); + + return true; +} + +/* +=================== +idDeclManagerLocal::MediaPrint + +This is just used to nicely indent media caching prints +=================== +*/ +void idDeclManagerLocal::MediaPrint( const char *fmt, ... ) { + if ( !decl_show.GetInteger() ) { + return; + } + for ( int i = 0 ; i < indent ; i++ ) { + common->Printf( " " ); + } + va_list argptr; + char buffer[1024]; + va_start (argptr,fmt); + idStr::vsnPrintf( buffer, sizeof(buffer), fmt, argptr ); + va_end (argptr); + buffer[sizeof(buffer)-1] = '\0'; + + common->Printf( "%s", buffer ); +} + +/* +=================== +idDeclManagerLocal::WritePrecacheCommands +=================== +*/ +void idDeclManagerLocal::WritePrecacheCommands( idFile *f ) { + for ( int i = 0; i < declTypes.Num(); i++ ) { + int num; + + if ( declTypes[i] == NULL ) { + continue; + } + + num = linearLists[i].Num(); + + for ( int j = 0 ; j < num ; j++ ) { + idDeclLocal *decl = linearLists[i][j]; + + if ( !decl->referencedThisLevel ) { + continue; + } + + char str[1024]; + sprintf( str, "touch %s %s\n", declTypes[i]->typeName.c_str(), decl->GetName() ); + common->Printf( "%s", str ); + f->Printf( "%s", str ); + } + } +} + +/********************************************************************/ + +const idMaterial *idDeclManagerLocal::FindMaterial( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_MATERIAL, name, makeDefault ) ); +} + +const idMaterial *idDeclManagerLocal::MaterialByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_MATERIAL, index, forceParse ) ); +} + +/********************************************************************/ + +const idDeclSkin *idDeclManagerLocal::FindSkin( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_SKIN, name, makeDefault ) ); +} + +const idDeclSkin *idDeclManagerLocal::SkinByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_SKIN, index, forceParse ) ); +} + +/********************************************************************/ + +const idSoundShader *idDeclManagerLocal::FindSound( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_SOUND, name, makeDefault ) ); +} + +const idSoundShader *idDeclManagerLocal::SoundByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_SOUND, index, forceParse ) ); +} + +/* +=================== +idDeclManagerLocal::Touch +=================== +*/ +void idDeclManagerLocal::Touch( const idDecl * decl ) { + + if( decl->base->GetState() == DS_UNPARSED ) { + // This should parse the decl as well. + FindType( decl->GetType(), decl->GetName() ); + } +} + +/* +=================== +idDeclManagerLocal::MakeNameCanonical +=================== +*/ +void idDeclManagerLocal::MakeNameCanonical( const char *name, char *result, int maxLength ) { + int i, lastDot; + + lastDot = -1; + for ( i = 0; i < maxLength && name[i] != '\0'; i++ ) { + int c = name[i]; + if ( c == '\\' ) { + result[i] = '/'; + } else if ( c == '.' ) { + lastDot = i; + result[i] = c; + } else { + result[i] = idStr::ToLower( c ); + } + } + if ( lastDot != -1 ) { + result[lastDot] = '\0'; + } else { + result[i] = '\0'; + } +} + +/* +================ +idDeclManagerLocal::ListDecls_f +================ +*/ +void idDeclManagerLocal::ListDecls_f( const idCmdArgs &args ) { + int i, j; + int totalDecls = 0; + int totalText = 0; + int totalStructs = 0; + + for ( i = 0; i < declManagerLocal.declTypes.Num(); i++ ) { + int size, num; + + if ( declManagerLocal.declTypes[i] == NULL ) { + continue; + } + + num = declManagerLocal.linearLists[i].Num(); + totalDecls += num; + + size = 0; + for ( j = 0; j < num; j++ ) { + size += declManagerLocal.linearLists[i][j]->Size(); + if ( declManagerLocal.linearLists[i][j]->self != NULL ) { + size += declManagerLocal.linearLists[i][j]->self->Size(); + } + } + totalStructs += size; + + common->Printf( "%4ik %4i %s\n", size >> 10, num, declManagerLocal.declTypes[i]->typeName.c_str() ); + } + + for ( i = 0 ; i < declManagerLocal.loadedFiles.Num() ; i++ ) { + idDeclFile *df = declManagerLocal.loadedFiles[i]; + totalText += df->fileSize; + } + + common->Printf( "%i total decls is %i decl files\n", totalDecls, declManagerLocal.loadedFiles.Num() ); + common->Printf( "%iKB in text, %iKB in structures\n", totalText >> 10, totalStructs >> 10 ); +} + +/* +=================== +idDeclManagerLocal::ReloadDecls_f + +Reload will not find any new files created in the directories, it +will only reload existing files. + +A reload will never cause anything to be purged. +=================== +*/ +void idDeclManagerLocal::ReloadDecls_f( const idCmdArgs &args ) { + bool force; + + if ( !idStr::Icmp( args.Argv( 1 ), "all" ) ) { + force = true; + common->Printf( "reloading all decl files:\n" ); + } else { + force = false; + common->Printf( "reloading changed decl files:\n" ); + } + + declManagerLocal.Reload( force ); +} + +/* +=================== +idDeclManagerLocal::TouchDecl_f +=================== +*/ +void idDeclManagerLocal::TouchDecl_f( const idCmdArgs &args ) { + int i; + + if ( args.Argc() != 3 ) { + common->Printf( "usage: touch \n" ); + common->Printf( "valid types: " ); + for ( int i = 0 ; i < declManagerLocal.declTypes.Num() ; i++ ) { + if ( declManagerLocal.declTypes[i] ) { + common->Printf( "%s ", declManagerLocal.declTypes[i]->typeName.c_str() ); + } + } + common->Printf( "\n" ); + return; + } + + for ( i = 0; i < declManagerLocal.declTypes.Num(); i++ ) { + if ( declManagerLocal.declTypes[i] && declManagerLocal.declTypes[i]->typeName.Icmp( args.Argv( 1 ) ) == 0 ) { + break; + } + } + if ( i >= declManagerLocal.declTypes.Num() ) { + common->Printf( "unknown decl type '%s'\n", args.Argv( 1 ) ); + return; + } + + const idDecl *decl = declManagerLocal.FindType( (declType_t)i, args.Argv( 2 ), false ); + if ( !decl ) { + common->Printf( "%s '%s' not found\n", declManagerLocal.declTypes[i]->typeName.c_str(), args.Argv( 2 ) ); + } +} + +/* +=================== +idDeclManagerLocal::FindTypeWithoutParsing + +This finds or creats the decl, but does not cause a parse. This is only used internally. +=================== +*/ +idDeclLocal *idDeclManagerLocal::FindTypeWithoutParsing( declType_t type, const char *name, bool makeDefault ) { + int typeIndex = (int)type; + int i, hash; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL || typeIndex >= DECL_MAX_TYPES ) { + common->FatalError( "idDeclManager::FindTypeWithoutParsing: bad type: %i", typeIndex ); + return NULL; + } + + char canonicalName[MAX_STRING_CHARS]; + + MakeNameCanonical( name, canonicalName, sizeof( canonicalName ) ); + + // see if it already exists + hash = hashTables[typeIndex].GenerateKey( canonicalName, false ); + for ( i = hashTables[typeIndex].First( hash ); i >= 0; i = hashTables[typeIndex].Next( i ) ) { + if ( linearLists[typeIndex][i]->name.Icmp( canonicalName ) == 0 ) { + // only print these when decl_show is set to 2, because it can be a lot of clutter + if ( decl_show.GetInteger() > 1 ) { + MediaPrint( "referencing %s %s\n", declTypes[ type ]->typeName.c_str(), name ); + } + return linearLists[typeIndex][i]; + } + } + + if ( !makeDefault ) { + return NULL; + } + + idDeclLocal *decl = new (TAG_DECL) idDeclLocal; + decl->self = NULL; + decl->name = canonicalName; + decl->type = type; + decl->declState = DS_UNPARSED; + decl->textSource = NULL; + decl->textLength = 0; + decl->sourceFile = &implicitDecls; + decl->referencedThisLevel = false; + decl->everReferenced = false; + decl->parsedOutsideLevelLoad = !insideLevelLoad; + + // add it to the linear list and hash table + decl->index = linearLists[typeIndex].Num(); + hashTables[typeIndex].Add( hash, linearLists[typeIndex].Append( decl ) ); + + return decl; +} + +/* +================= +idDeclManagerLocal::ConvertPDAsToStrings +================= +*/ +void idDeclManagerLocal::ConvertPDAsToStrings( const idCmdArgs &args ) { + + idStr pdaStringsFileName = "temppdas/pdas.lang"; + idFileLocal file( fileSystem->OpenFileWrite( pdaStringsFileName ) ); + + if ( file == NULL ) { + idLib::Printf( "Failed to Convert PDA data to Strings.\n" ); + } + + int totalEmailCount = 0; + int totalAudioCount = 0; + int totalVideoCount = 0; + idStr headEnd = "\t\"#str_%s_"; + idStr tailEnd = "\"\t\"%s\"\n"; + idStr temp; + + int count = linearLists[ DECL_PDA ].Num(); + for ( int i = 0; i < count; i++ ) { + const idDeclPDA *decl = static_cast< const idDeclPDA * >( FindType( DECL_PDA, linearLists[ DECL_PDA ][ i ]->GetName(), false ) ); + + idStr pdaBaseStrId = va( headEnd.c_str(), decl->GetName() ); + + temp = va( "\n\n//////// %s PDA ////////////\n", decl->GetName() ); + file->Write( temp, temp.Length() ); + + idStr pdaBase = pdaBaseStrId + "pda_%s" + tailEnd; + // Pda Name + temp = va( pdaBase.c_str(), "name", decl->GetPdaName() ); + file->Write( temp, temp.Length() ); + // Full Name + temp = va( pdaBase.c_str(), "fullname", decl->GetFullName() ); + file->Write( temp, temp.Length() ); + // ID + temp = va( pdaBase.c_str(), "id", decl->GetID() ); + file->Write( temp, temp.Length() ); + // Post + temp = va( pdaBase.c_str(), "post", decl->GetPost() ); + file->Write( temp, temp.Length() ); + // Title + temp = va( pdaBase.c_str(), "title", decl->GetTitle() ); + file->Write( temp, temp.Length() ); + // Security + temp = va( pdaBase.c_str(), "security", decl->GetSecurity() ); + file->Write( temp, temp.Length() ); + + int emailCount = decl->GetNumEmails(); + for ( int emailIter = 0; emailIter < emailCount; emailIter++ ) { + const idDeclEmail * email = decl->GetEmailByIndex( emailIter ); + + idStr emailBaseStrId = va( headEnd.c_str(), email->GetName() ); + idStr emailBase = emailBaseStrId + "email_%s" + tailEnd; + + file->Write( "\t//Email\n", 9 ); + // Date + temp = va( emailBase, "date", email->GetDate() ); + file->Write( temp, temp.Length() ); + // To + temp = va( emailBase, "to", email->GetTo() ); + file->Write( temp, temp.Length() ); + // From + temp = va( emailBase, "from", email->GetFrom() ); + file->Write( temp, temp.Length() ); + // Subject + temp = va( emailBase, "subject", email->GetSubject() ); + file->Write( temp, temp.Length() ); + // Body + idStr body = email->GetBody(); + body.Replace( "\n", "\\n" ); + temp = va( emailBase, "text", body.c_str() ); + file->Write( temp, temp.Length() ); + + totalEmailCount++; + } + + int audioCount = decl->GetNumAudios(); + for ( int audioIter = 0; audioIter < audioCount; audioIter++ ) { + const idDeclAudio * audio = decl->GetAudioByIndex( audioIter ); + + idStr audioBaseStrId = va( headEnd.c_str(), audio->GetName() ); + idStr audioBase = audioBaseStrId + "audio_%s" + tailEnd; + + file->Write( "\t//Audio\n", 9 ); + // Name + temp = va( audioBase, "name", audio->GetAudioName() ); + file->Write( temp, temp.Length() ); + // Info + idStr info = audio->GetInfo(); + info.Replace( "\n", "\\n" ); + temp = va( audioBase, "info", info.c_str() ); + file->Write( temp, temp.Length() ); + + totalAudioCount++; + } + } + + int infoEmailCount = linearLists[ DECL_EMAIL ].Num(); + if ( infoEmailCount > 0 ) { + temp = "\n\n//////// PDA Info Emails ////////////\n"; + file->Write( temp, temp.Length() ); + } + for ( int i = 0; i < infoEmailCount; i++ ) { + const idDeclEmail * email = static_cast< const idDeclEmail * >( FindType( DECL_EMAIL, linearLists[ DECL_EMAIL ][ i ]->GetName(), false ) ); + + idStr filename = email->base->GetFileName(); + if ( filename.Icmp( "newpdas/info_emails.pda" ) != 0 ) { + continue; + } + + idStr emailBaseStrId = va( "\t\"#str_%s_", email->GetName() ); + idStr emailBase = emailBaseStrId + "email_%s" + tailEnd; + + file->Write( "\t//Email\n", 9 ); + + // Date + temp = va( emailBase, "date", email->GetDate() ); + file->Write( temp, temp.Length() ); + // To + temp = va( emailBase, "to", email->GetTo() ); + file->Write( temp, temp.Length() ); + // From + temp = va( emailBase, "from", email->GetFrom() ); + file->Write( temp, temp.Length() ); + // Subject + temp = va( emailBase, "subject", email->GetSubject() ); + file->Write( temp, temp.Length() ); + // Body + idStr body = email->GetBody(); + body.Replace( "\n", "\\n" ); + temp = va( emailBase, "text", body.c_str() ); + file->Write( temp, temp.Length() ); + + totalEmailCount++; + } + + int videoCount = linearLists[ DECL_VIDEO ].Num(); + if ( videoCount > 0 ) { + temp = "\n\n//////// PDA Videos ////////////\n"; + file->Write( temp, temp.Length() ); + } + for ( int i = 0; i < videoCount; i++ ) { + const idDeclVideo * video = static_cast< const idDeclVideo * >( FindType( DECL_VIDEO, linearLists[ DECL_VIDEO ][ i ]->GetName(), false ) ); + + idStr videoBaseStrId = va( "\t\"#str_%s_", video->GetName() ); + idStr videoBase = videoBaseStrId + "video_%s" + tailEnd; + + file->Write( "\t//Video\n", 9 ); + + // Name + temp = va( videoBase, "name", video->GetVideoName() ); + file->Write( temp, temp.Length() ); + // Info + idStr info = video->GetInfo(); + info.Replace( "\n", "\\n" ); + temp = va( videoBase, "info", info.c_str() ); + file->Write( temp, temp.Length() ); + + totalVideoCount++; + } + + file->Flush(); + + idLib::Printf( "\nData written to %s\n", pdaStringsFileName.c_str() ); + idLib::Printf( "----------------------------\n" ); + idLib::Printf( "Wrote %d PDAs.\n", count ); + idLib::Printf( "Wrote %d Emails.\n", totalEmailCount ); + idLib::Printf( "Wrote %d Audio Records.\n", totalAudioCount ); + idLib::Printf( "Wrote %d Video Records.\n", totalVideoCount ); + idLib::Printf( "Please copy the results into the appropriate .lang file.\n" ); +} + +/* +==================================================================================== + + idDeclLocal + +==================================================================================== +*/ + +/* +================= +idDeclLocal::idDeclLocal +================= +*/ +idDeclLocal::idDeclLocal() { + name = "unnamed"; + textSource = NULL; + textLength = 0; + compressedLength = 0; + sourceFile = NULL; + sourceTextOffset = 0; + sourceTextLength = 0; + sourceLine = 0; + checksum = 0; + type = DECL_ENTITYDEF; + index = 0; + declState = DS_UNPARSED; + parsedOutsideLevelLoad = false; + referencedThisLevel = false; + everReferenced = false; + redefinedInReload = false; + nextInFile = NULL; +} + +/* +================= +idDeclLocal::GetName +================= +*/ +const char *idDeclLocal::GetName() const { + return name.c_str(); +} + +/* +================= +idDeclLocal::GetType +================= +*/ +declType_t idDeclLocal::GetType() const { + return type; +} + +/* +================= +idDeclLocal::GetState +================= +*/ +declState_t idDeclLocal::GetState() const { + return declState; +} + +/* +================= +idDeclLocal::IsImplicit +================= +*/ +bool idDeclLocal::IsImplicit() const { + return ( sourceFile == declManagerLocal.GetImplicitDeclFile() ); +} + +/* +================= +idDeclLocal::IsValid +================= +*/ +bool idDeclLocal::IsValid() const { + return ( declState != DS_UNPARSED ); +} + +/* +================= +idDeclLocal::Invalidate +================= +*/ +void idDeclLocal::Invalidate() { + declState = DS_UNPARSED; +} + +/* +================= +idDeclLocal::EnsureNotPurged +================= +*/ +void idDeclLocal::EnsureNotPurged() { + if ( declState == DS_UNPARSED ) { + ParseLocal(); + } +} + +/* +================= +idDeclLocal::Index +================= +*/ +int idDeclLocal::Index() const { + return index; +} + +/* +================= +idDeclLocal::GetLineNum +================= +*/ +int idDeclLocal::GetLineNum() const { + return sourceLine; +} + +/* +================= +idDeclLocal::GetFileName +================= +*/ +const char *idDeclLocal::GetFileName() const { + return ( sourceFile ) ? sourceFile->fileName.c_str() : "*invalid*"; +} + +/* +================= +idDeclLocal::Size +================= +*/ +size_t idDeclLocal::Size() const { + return sizeof( idDecl ) + name.Allocated(); +} + +/* +================= +idDeclLocal::GetText +================= +*/ +void idDeclLocal::GetText( char *text ) const { +#ifdef USE_COMPRESSED_DECLS + HuffmanDecompressText( text, textLength, (byte *)textSource, compressedLength ); +#else + memcpy( text, textSource, textLength+1 ); +#endif +} + +/* +================= +idDeclLocal::GetTextLength +================= +*/ +int idDeclLocal::GetTextLength() const { + return textLength; +} + +/* +================= +idDeclLocal::SetText +================= +*/ +void idDeclLocal::SetText( const char *text ) { + SetTextLocal( text, idStr::Length( text ) ); +} + +/* +================= +idDeclLocal::SetTextLocal +================= +*/ +void idDeclLocal::SetTextLocal( const char *text, const int length ) { + + Mem_Free( textSource ); + + checksum = MD5_BlockChecksum( text, length ); + +#ifdef GET_HUFFMAN_FREQUENCIES + for( int i = 0; i < length; i++ ) { + huffmanFrequencies[((const unsigned char *)text)[i]]++; + } +#endif + +#ifdef USE_COMPRESSED_DECLS + int maxBytesPerCode = ( maxHuffmanBits + 7 ) >> 3; + byte *compressed = (byte *)_alloca( length * maxBytesPerCode ); + compressedLength = HuffmanCompressText( text, length, compressed, length * maxBytesPerCode ); + textSource = (char *)Mem_Alloc( compressedLength, TAG_DECLTEXT ); + memcpy( textSource, compressed, compressedLength ); +#else + compressedLength = length; + textSource = (char *) Mem_Alloc( length + 1, TAG_DECLTEXT ); + memcpy( textSource, text, length ); + textSource[length] = '\0'; +#endif + textLength = length; +} + +/* +================= +idDeclLocal::ReplaceSourceFileText +================= +*/ +bool idDeclLocal::ReplaceSourceFileText() { + int oldFileLength, newFileLength; + idFile *file; + + common->Printf( "Writing \'%s\' to \'%s\'...\n", GetName(), GetFileName() ); + + if ( sourceFile == &declManagerLocal.implicitDecls ) { + common->Warning( "Can't save implicit declaration %s.", GetName() ); + return false; + } + + // get length and allocate buffer to hold the file + oldFileLength = sourceFile->fileSize; + newFileLength = oldFileLength - sourceTextLength + textLength; + idTempArray buffer( Max( newFileLength, oldFileLength ) ); + memset( buffer.Ptr(), 0, buffer.Size() ); + + // read original file + if ( sourceFile->fileSize ) { + + file = fileSystem->OpenFileRead( GetFileName() ); + if ( !file ) { + common->Warning( "Couldn't open %s for reading.", GetFileName() ); + return false; + } + + if ( file->Length() != sourceFile->fileSize || file->Timestamp() != sourceFile->timestamp ) { + common->Warning( "The file %s has been modified outside of the engine.", GetFileName() ); + return false; + } + + file->Read( buffer.Ptr(), oldFileLength ); + fileSystem->CloseFile( file ); + + if ( MD5_BlockChecksum( buffer.Ptr(), oldFileLength ) != (unsigned int)sourceFile->checksum ) { + common->Warning( "The file %s has been modified outside of the engine.", GetFileName() ); + return false; + } + } + + // insert new text + char *declText = (char *) _alloca( textLength + 1 ); + GetText( declText ); + memmove( buffer.Ptr() + sourceTextOffset + textLength, buffer.Ptr() + sourceTextOffset + sourceTextLength, oldFileLength - sourceTextOffset - sourceTextLength ); + memcpy( buffer.Ptr() + sourceTextOffset, declText, textLength ); + + // write out new file + file = fileSystem->OpenFileWrite( GetFileName(), "fs_basepath" ); + if ( !file ) { + common->Warning( "Couldn't open %s for writing.", GetFileName() ); + return false; + } + file->Write( buffer.Ptr(), newFileLength ); + fileSystem->CloseFile( file ); + + // set new file size, checksum and timestamp + sourceFile->fileSize = newFileLength; + sourceFile->checksum = MD5_BlockChecksum( buffer.Ptr(), newFileLength ); + fileSystem->ReadFile( GetFileName(), NULL, &sourceFile->timestamp ); + + // move all decls in the same file + for ( idDeclLocal *decl = sourceFile->decls; decl; decl = decl->nextInFile ) { + if (decl->sourceTextOffset > sourceTextOffset) { + decl->sourceTextOffset += textLength - sourceTextLength; + } + } + + // set new size of text in source file + sourceTextLength = textLength; + + return true; +} + +/* +================= +idDeclLocal::SourceFileChanged +================= +*/ +bool idDeclLocal::SourceFileChanged() const { + int newLength; + ID_TIME_T newTimestamp; + + if ( sourceFile->fileSize <= 0 ) { + return false; + } + + newLength = fileSystem->ReadFile( GetFileName(), NULL, &newTimestamp ); + + if ( newLength != sourceFile->fileSize || newTimestamp != sourceFile->timestamp ) { + return true; + } + + return false; +} + +/* +================= +idDeclLocal::MakeDefault +================= +*/ +void idDeclLocal::MakeDefault() { + static int recursionLevel; + const char *defaultText; + + declManagerLocal.MediaPrint( "DEFAULTED\n" ); + declState = DS_DEFAULTED; + + AllocateSelf(); + + defaultText = self->DefaultDefinition(); + + // a parse error inside a DefaultDefinition() string could + // cause an infinite loop, but normal default definitions could + // still reference other default definitions, so we can't + // just dump out on the first recursion + if ( ++recursionLevel > 100 ) { + common->FatalError( "idDecl::MakeDefault: bad DefaultDefinition(): %s", defaultText ); + } + + // always free data before parsing + self->FreeData(); + + // parse + self->Parse( defaultText, strlen( defaultText ), false ); + + // we could still eventually hit the recursion if we have enough Error() calls inside Parse... + --recursionLevel; +} + +/* +================= +idDeclLocal::SetDefaultText +================= +*/ +bool idDeclLocal::SetDefaultText() { + return false; +} + +/* +================= +idDeclLocal::DefaultDefinition +================= +*/ +const char *idDeclLocal::DefaultDefinition() const { + return "{ }"; +} + +/* +================= +idDeclLocal::Parse +================= +*/ +bool idDeclLocal::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + src.SkipBracedSection( false ); + return true; +} + +/* +================= +idDeclLocal::FreeData +================= +*/ +void idDeclLocal::FreeData() { +} + +/* +================= +idDeclLocal::List +================= +*/ +void idDeclLocal::List() const { + common->Printf( "%s\n", GetName() ); +} + +/* +================= +idDeclLocal::Print +================= +*/ +void idDeclLocal::Print() const { +} + +/* +================= +idDeclLocal::Reload +================= +*/ +void idDeclLocal::Reload() { + this->sourceFile->Reload( false ); +} + +/* +================= +idDeclLocal::AllocateSelf +================= +*/ +void idDeclLocal::AllocateSelf() { + if ( self == NULL ) { + self = declManagerLocal.GetDeclType( (int)type )->allocator(); + self->base = this; + } +} + +/* +================= +idDeclLocal::ParseLocal +================= +*/ +void idDeclLocal::ParseLocal() { + bool generatedDefaultText = false; + + AllocateSelf(); + + // always free data before parsing + self->FreeData(); + + declManagerLocal.MediaPrint( "parsing %s %s\n", declManagerLocal.declTypes[type]->typeName.c_str(), name.c_str() ); + + // if no text source try to generate default text + if ( textSource == NULL ) { + generatedDefaultText = self->SetDefaultText(); + } + + // indent for DEFAULTED or media file references + declManagerLocal.indent++; + + // no text immediately causes a MakeDefault() + if ( textSource == NULL ) { + MakeDefault(); + declManagerLocal.indent--; + return; + } + + declState = DS_PARSED; + + // parse + char *declText = (char *) _alloca( ( GetTextLength() + 1 ) * sizeof( char ) ); + GetText( declText ); + self->Parse( declText, GetTextLength(), true ); + + // free generated text + if ( generatedDefaultText ) { + Mem_Free( textSource ); + textSource = NULL; + textLength = 0; + } + + declManagerLocal.indent--; +} + +/* +================= +idDeclLocal::Purge +================= +*/ +void idDeclLocal::Purge() { + // never purge things that were referenced outside level load, + // like the console and menu graphics + if ( parsedOutsideLevelLoad ) { + return; + } + + referencedThisLevel = false; + MakeDefault(); + + // the next Find() for this will re-parse the real data + declState = DS_UNPARSED; +} + +/* +================= +idDeclLocal::EverReferenced +================= +*/ +bool idDeclLocal::EverReferenced() const { + return everReferenced; +} \ No newline at end of file diff --git a/neo/framework/DeclManager.h b/neo/framework/DeclManager.h new file mode 100644 index 00000000..1ebdba87 --- /dev/null +++ b/neo/framework/DeclManager.h @@ -0,0 +1,339 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLMANAGER_H__ +#define __DECLMANAGER_H__ + +/* +=============================================================================== + + Declaration Manager + + All "small text" data types, like materials, sound shaders, fx files, + entity defs, etc. are managed uniformly, allowing reloading, purging, + listing, printing, etc. All "large text" data types that never have more + than one declaration in a given file, like maps, models, AAS files, etc. + are not handled here. + + A decl will never, ever go away once it is created. The manager is + garranteed to always return the same decl pointer for a decl type/name + combination. The index of a decl in the per type list also stays the + same throughout the lifetime of the engine. Although the pointer to + a decl always stays the same, one should never maintain pointers to + data inside decls. The data stored in a decl is not garranteed to stay + the same for more than one engine frame. + + The decl indexes of explicitely defined decls are garrenteed to be + consistent based on the parsed decl files. However, the indexes of + implicit decls may be different based on the order in which levels + are loaded. + + The decl namespaces are separate for each type. Comments for decls go + above the text definition to keep them associated with the proper decl. + + During decl parsing, errors should never be issued, only warnings + followed by a call to MakeDefault(). + +=============================================================================== +*/ + +typedef enum { + DECL_TABLE = 0, + DECL_MATERIAL, + DECL_SKIN, + DECL_SOUND, + DECL_ENTITYDEF, + DECL_MODELDEF, + DECL_FX, + DECL_PARTICLE, + DECL_AF, + DECL_PDA, + DECL_VIDEO, + DECL_AUDIO, + DECL_EMAIL, + DECL_MODELEXPORT, + DECL_MAPDEF, + + // new decl types can be added here + + DECL_MAX_TYPES = 32 +} declType_t; + +typedef enum { + DS_UNPARSED, + DS_DEFAULTED, // set if a parse failed due to an error, or the lack of any source + DS_PARSED +} declState_t; + +const int DECL_LEXER_FLAGS = LEXFL_NOSTRINGCONCAT | // multiple strings seperated by whitespaces are not concatenated + LEXFL_NOSTRINGESCAPECHARS | // no escape characters inside strings + LEXFL_ALLOWPATHNAMES | // allow path seperators in names + LEXFL_ALLOWMULTICHARLITERALS | // allow multi character literals + LEXFL_ALLOWBACKSLASHSTRINGCONCAT | // allow multiple strings seperated by '\' to be concatenated + LEXFL_NOFATALERRORS; // just set a flag instead of fatal erroring + + +class idDeclBase { +public: + virtual ~idDeclBase() {}; + virtual const char * GetName() const = 0; + virtual declType_t GetType() const = 0; + virtual declState_t GetState() const = 0; + virtual bool IsImplicit() const = 0; + virtual bool IsValid() const = 0; + virtual void Invalidate() = 0; + virtual void Reload() = 0; + virtual void EnsureNotPurged() = 0; + virtual int Index() const = 0; + virtual int GetLineNum() const = 0; + virtual const char * GetFileName() const = 0; + virtual void GetText( char *text ) const = 0; + virtual int GetTextLength() const = 0; + virtual void SetText( const char *text ) = 0; + virtual bool ReplaceSourceFileText() = 0; + virtual bool SourceFileChanged() const = 0; + virtual void MakeDefault() = 0; + virtual bool EverReferenced() const = 0; + virtual bool SetDefaultText() = 0; + virtual const char * DefaultDefinition() const = 0; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ) = 0; + virtual void FreeData() = 0; + virtual size_t Size() const = 0; + virtual void List() const = 0; + virtual void Print() const = 0; +}; + + +class idDecl { +public: + // The constructor should initialize variables such that + // an immediate call to FreeData() does no harm. + idDecl() { base = NULL; } + virtual ~idDecl() {}; + + // Returns the name of the decl. + const char * GetName() const { return base->GetName(); } + + // Returns the decl type. + declType_t GetType() const { return base->GetType(); } + + // Returns the decl state which is usefull for finding out if a decl defaulted. + declState_t GetState() const { return base->GetState(); } + + // Returns true if the decl was defaulted or the text was created with a call to SetDefaultText. + bool IsImplicit() const { return base->IsImplicit(); } + + // The only way non-manager code can have an invalid decl is if the *ByIndex() + // call was used with forceParse = false to walk the lists to look at names + // without touching the media. + bool IsValid() const { return base->IsValid(); } + + // Sets state back to unparsed. + // Used by decl editors to undo any changes to the decl. + void Invalidate() { base->Invalidate(); } + + // if a pointer might possible be stale from a previous level, + // call this to have it re-parsed + void EnsureNotPurged() { base->EnsureNotPurged(); } + + // Returns the index in the per-type list. + int Index() const { return base->Index(); } + + // Returns the line number the decl starts. + int GetLineNum() const { return base->GetLineNum(); } + + // Returns the name of the file in which the decl is defined. + const char * GetFileName() const { return base->GetFileName(); } + + // Returns the decl text. + void GetText( char *text ) const { base->GetText( text ); } + + // Returns the length of the decl text. + int GetTextLength() const { return base->GetTextLength(); } + + // Sets new decl text. + void SetText( const char *text ) { base->SetText( text ); } + + // Saves out new text for the decl. + // Used by decl editors to replace the decl text in the source file. + bool ReplaceSourceFileText() { return base->ReplaceSourceFileText(); } + + // Returns true if the source file changed since it was loaded and parsed. + bool SourceFileChanged() const { return base->SourceFileChanged(); } + + // Frees data and makes the decl a default. + void MakeDefault() { base->MakeDefault(); } + + // Returns true if the decl was ever referenced. + bool EverReferenced() const { return base->EverReferenced(); } + +public: + // Sets textSource to a default text if necessary. + // This may be overridden to provide a default definition based on the + // decl name. For instance materials may default to an implicit definition + // using a texture with the same name as the decl. + virtual bool SetDefaultText() { return base->SetDefaultText(); } + + // Each declaration type must have a default string that it is guaranteed + // to parse acceptably. When a decl is not explicitly found, is purged, or + // has an error while parsing, MakeDefault() will do a FreeData(), then a + // Parse() with DefaultDefinition(). The defaultDefintion should start with + // an open brace and end with a close brace. + virtual const char * DefaultDefinition() const { return base->DefaultDefinition(); } + + // The manager will have already parsed past the type, name and opening brace. + // All necessary media will be touched before return. + // The manager will have called FreeData() before issuing a Parse(). + // The subclass can call MakeDefault() internally at any point if + // there are parse errors. + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion = false ) { return base->Parse( text, textLength, allowBinaryVersion ); } + + // Frees any pointers held by the subclass. This may be called before + // any Parse(), so the constructor must have set sane values. The decl will be + // invalid after issuing this call, but it will always be immediately followed + // by a Parse() + virtual void FreeData() { base->FreeData(); } + + // Returns the size of the decl in memory. + virtual size_t Size() const { return base->Size(); } + + // If this isn't overridden, it will just print the decl name. + // The manager will have printed 7 characters on the line already, + // containing the reference state and index number. + virtual void List() const { base->List(); } + + // The print function will already have dumped the text source + // and common data, subclasses can override this to dump more + // explicit data. + virtual void Print() const { base->Print(); } + +public: + idDeclBase * base; +}; + + +template< class type > +ID_INLINE idDecl *idDeclAllocator() { + return new (TAG_DECL) type; +} + + +class idMaterial; +class idDeclSkin; +class idSoundShader; + +class idDeclManager { +public: + virtual ~idDeclManager() {} + + virtual void Init() = 0; + virtual void Init2() = 0; + virtual void Shutdown() = 0; + virtual void Reload( bool force ) = 0; + + virtual void BeginLevelLoad() = 0; + virtual void EndLevelLoad() = 0; + + // Registers a new decl type. + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)() ) = 0; + + // Registers a new folder with decl files. + virtual void RegisterDeclFolder( const char *folder, const char *extension, declType_t defaultType ) = 0; + + // Returns a checksum for all loaded decl text. + virtual int GetChecksum() const = 0; + + // Returns the number of decl types. + virtual int GetNumDeclTypes() const = 0; + + // Returns the type name for a decl type. + virtual const char * GetDeclNameFromType( declType_t type ) const = 0; + + // Returns the decl type for a type name. + virtual declType_t GetDeclTypeFromName( const char *typeName ) const = 0; + + // If makeDefault is true, a default decl of appropriate type will be created + // if an explicit one isn't found. If makeDefault is false, NULL will be returned + // if the decl wasn't explcitly defined. + virtual const idDecl * FindType( declType_t type, const char *name, bool makeDefault = true ) = 0; + + virtual const idDecl* FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault = true ) = 0; + + virtual void ReloadFile( const char* filename, bool force ) = 0; + + // Returns the number of decls of the given type. + virtual int GetNumDecls( declType_t type ) = 0; + + // The complete lists of decls can be walked to populate editor browsers. + // If forceParse is set false, you can get the decl to check name / filename / etc. + // without causing it to parse the source and load media. + virtual const idDecl * DeclByIndex( declType_t type, int index, bool forceParse = true ) = 0; + + // List and print decls. + virtual void ListType( const idCmdArgs &args, declType_t type ) = 0; + virtual void PrintType( const idCmdArgs &args, declType_t type ) = 0; + + // Creates a new default decl of the given type with the given name in + // the given file used by editors to create a new decls. + virtual idDecl * CreateNewDecl( declType_t type, const char *name, const char *fileName ) = 0; + + // BSM - Added for the material editors rename capabilities + virtual bool RenameDecl( declType_t type, const char* oldName, const char* newName ) = 0; + + // When media files are loaded, a reference line can be printed at a + // proper indentation if decl_show is set + virtual void MediaPrint( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // Convenience functions for specific types. + virtual const idMaterial * FindMaterial( const char *name, bool makeDefault = true ) = 0; + virtual const idDeclSkin * FindSkin( const char *name, bool makeDefault = true ) = 0; + virtual const idSoundShader * FindSound( const char *name, bool makeDefault = true ) = 0; + + virtual const idMaterial * MaterialByIndex( int index, bool forceParse = true ) = 0; + virtual const idDeclSkin * SkinByIndex( int index, bool forceParse = true ) = 0; + virtual const idSoundShader * SoundByIndex( int index, bool forceParse = true ) = 0; + + virtual void Touch( const idDecl * decl ) = 0; +}; + +extern idDeclManager * declManager; + + +template< declType_t type > +ID_INLINE void idListDecls_f( const idCmdArgs &args ) { + declManager->ListType( args, type ); +} + +template< declType_t type > +ID_INLINE void idPrintDecls_f( const idCmdArgs &args ) { + declManager->PrintType( args, type ); +} + +#endif /* !__DECLMANAGER_H__ */ diff --git a/neo/framework/DeclPDA.cpp b/neo/framework/DeclPDA.cpp new file mode 100644 index 00000000..7150f1b6 --- /dev/null +++ b/neo/framework/DeclPDA.cpp @@ -0,0 +1,609 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar g_useOldPDAStrings( "g_useOldPDAStrings", "0", CVAR_BOOL, "Read strings from the .pda files rather than from the .lang file" ); + +/* +================= +idDeclPDA::Size +================= +*/ +size_t idDeclPDA::Size() const { + return sizeof( idDeclPDA ); +} + +/* +=============== +idDeclPDA::Print +=============== +*/ +void idDeclPDA::Print() const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclPDA::List +=============== +*/ +void idDeclPDA::List() const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclPDA::Parse +================ +*/ +bool idDeclPDA::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + + idStr baseStrId = va( "#str_%s_pda_", GetName() ); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "name") ) { + src.ReadToken( &token ); + + if ( g_useOldPDAStrings.GetBool() ) { + pdaName = token; + } else { + pdaName = idLocalization::GetString( baseStrId + "name" ); + } + continue; + } + + if ( !token.Icmp( "fullname") ) { + src.ReadToken( &token ); + + if ( g_useOldPDAStrings.GetBool() ) { + fullName = token; + } else { + fullName = idLocalization::GetString( baseStrId + "fullname" ); + } + continue; + } + + if ( !token.Icmp( "icon") ) { + src.ReadToken( &token ); + icon = token; + continue; + } + + if ( !token.Icmp( "id") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + id = token; + } else { + id = idLocalization::GetString( baseStrId + "id" ); + } + continue; + } + + if ( !token.Icmp( "post") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + post = token; + } else { + post = idLocalization::GetString( baseStrId + "post" ); + } + continue; + } + + if ( !token.Icmp( "title") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + title = token; + } else { + title = idLocalization::GetString( baseStrId + "title" ); + } + continue; + } + + if ( !token.Icmp( "security") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + security = token; + } else { + security = idLocalization::GetString( baseStrId + "security" ); + } + continue; + } + + if ( !token.Icmp( "pda_email") ) { + src.ReadToken( &token ); + emails.Append( static_cast( declManager->FindType( DECL_EMAIL, token ) ) ); + continue; + } + + if ( !token.Icmp( "pda_audio") ) { + src.ReadToken( &token ); + audios.Append( static_cast( declManager->FindType( DECL_AUDIO, token ) ) ); + continue; + } + + if ( !token.Icmp( "pda_video") ) { + src.ReadToken( &token ); + videos.Append( static_cast( declManager->FindType( DECL_VIDEO, token ) ) ); + continue; + } + + } + + if ( src.HadError() ) { + src.Warning( "PDA decl '%s' had a parse error", GetName() ); + return false; + } + + originalVideos = videos.Num(); + originalEmails = emails.Num(); + return true; +} + +/* +=================== +idDeclPDA::DefaultDefinition +=================== +*/ +const char *idDeclPDA::DefaultDefinition() const { + return + "{\n" + "\t" "name \"default pda\"\n" + "}"; +} + +/* +=================== +idDeclPDA::FreeData +=================== +*/ +void idDeclPDA::FreeData() { + videos.Clear(); + audios.Clear(); + emails.Clear(); + originalEmails = 0; + originalVideos = 0; +} + +/* +================= +idDeclPDA::RemoveAddedEmailsAndVideos +================= +*/ +void idDeclPDA::RemoveAddedEmailsAndVideos() const { + int num = emails.Num(); + if ( originalEmails < num ) { + while ( num && num > originalEmails ) { + emails.RemoveIndex( --num ); + } + } + num = videos.Num(); + if ( originalVideos < num ) { + while ( num && num > originalVideos ) { + videos.RemoveIndex( --num ); + } + } +} + +/* +================= +idDeclPDA::SetSecurity +================= +*/ +void idDeclPDA::SetSecurity( const char *sec ) const { + security = sec; +} + +/* +================= +idDeclEmail::Size +================= +*/ +size_t idDeclEmail::Size() const { + return sizeof( idDeclEmail ); +} + +/* +=============== +idDeclEmail::Print +=============== +*/ +void idDeclEmail::Print() const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclEmail::List +=============== +*/ +void idDeclEmail::List() const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclEmail::Parse +================ +*/ +bool idDeclEmail::Parse( const char *_text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + + idStr baseStrId = va( "#str_%s_email_", GetName() ); + + src.LoadMemory( _text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT | LEXFL_NOFATALERRORS ); + src.SkipUntilString( "{" ); + + text = ""; + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "subject") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + subject = token; + } else { + subject = idLocalization::GetString( baseStrId + "subject" ); + } + continue; + } + + if ( !token.Icmp( "to") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + to = token; + } else { + to = idLocalization::GetString( baseStrId + "to" ); + } + continue; + } + + if ( !token.Icmp( "from") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + from = token; + } else { + from = idLocalization::GetString( baseStrId + "from" ); + } + continue; + } + + if ( !token.Icmp( "date") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + date = token; + } else { + date = idLocalization::GetString( baseStrId + "date" ); + } + continue; + } + + if ( !token.Icmp( "text") ) { + src.ReadToken( &token ); + if ( token != "{" ) { + src.Warning( "Email decl '%s' had a parse error", GetName() ); + return false; + } + while ( src.ReadToken( &token ) && token != "}" ) { + text += token; + } + if ( !g_useOldPDAStrings.GetBool() ) { + text = idLocalization::GetString( baseStrId + "text" ); + } + continue; + } + } + + if ( src.HadError() ) { + src.Warning( "Email decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclEmail::DefaultDefinition +=================== +*/ +const char *idDeclEmail::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "to\t5Mail recipient\n" + "\t\t" "subject\t5Nothing\n" + "\t\t" "from\t5No one\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclEmail::FreeData +=================== +*/ +void idDeclEmail::FreeData() { +} + +/* +================= +idDeclVideo::Size +================= +*/ +size_t idDeclVideo::Size() const { + return sizeof( idDeclVideo ); +} + +/* +=============== +idDeclVideo::Print +=============== +*/ +void idDeclVideo::Print() const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclVideo::List +=============== +*/ +void idDeclVideo::List() const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclVideo::Parse +================ +*/ +bool idDeclVideo::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + + idStr baseStrId = va( "#str_%s_video_", GetName() ); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT | LEXFL_NOFATALERRORS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "name") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + videoName = token; + } else { + videoName = idLocalization::GetString( baseStrId + "name" ); + } + continue; + } + + if ( !token.Icmp( "preview") ) { + src.ReadToken( &token ); + preview = declManager->FindMaterial( token ); + continue; + } + + if ( !token.Icmp( "video") ) { + src.ReadToken( &token ); + video = declManager->FindMaterial( token ); + continue; + } + + if ( !token.Icmp( "info") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + info = token; + } else { + info = idLocalization::GetString( baseStrId + "info" ); + } + continue; + } + + if ( !token.Icmp( "audio") ) { + src.ReadToken( &token ); + audio = declManager->FindSound( token ); + continue; + } + + } + + if ( src.HadError() ) { + src.Warning( "Video decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclVideo::DefaultDefinition +=================== +*/ +const char *idDeclVideo::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "name\t5Default Video\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclVideo::FreeData +=================== +*/ +void idDeclVideo::FreeData() { +} + +/* +================= +idDeclAudio::Size +================= +*/ +size_t idDeclAudio::Size() const { + return sizeof( idDeclAudio ); +} + +/* +=============== +idDeclAudio::Print +=============== +*/ +void idDeclAudio::Print() const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclAudio::List +=============== +*/ +void idDeclAudio::List() const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclAudio::Parse +================ +*/ +bool idDeclAudio::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + + idStr baseStrId = va( "#str_%s_audio_", GetName() ); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT | LEXFL_NOFATALERRORS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "name") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + audioName = token; + } else { + audioName = idLocalization::GetString( baseStrId + "name" ); + } + continue; + } + + if ( !token.Icmp( "audio") ) { + src.ReadToken( &token ); + audio = declManager->FindSound( token ); + continue; + } + + if ( !token.Icmp( "info") ) { + src.ReadToken( &token ); + if ( g_useOldPDAStrings.GetBool() ) { + info = token; + } else { + info = idLocalization::GetString( baseStrId + "info" ); + } + continue; + } + } + + if ( src.HadError() ) { + src.Warning( "Audio decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclAudio::DefaultDefinition +=================== +*/ +const char *idDeclAudio::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "name\t5Default Audio\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclAudio::FreeData +=================== +*/ +void idDeclAudio::FreeData() { +} diff --git a/neo/framework/DeclPDA.h b/neo/framework/DeclPDA.h new file mode 100644 index 00000000..1cbb5606 --- /dev/null +++ b/neo/framework/DeclPDA.h @@ -0,0 +1,162 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLPDA_H__ +#define __DECLPDA_H__ + +/* +=============================================================================== + + idDeclPDA + +=============================================================================== +*/ + + +class idDeclEmail : public idDecl { +public: + idDeclEmail() {} + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print() const; + virtual void List() const; + + const char * GetFrom() const { return from; } + const char * GetBody() const { return text; } + const char * GetSubject() const { return subject; } + const char * GetDate() const { return date; } + const char * GetTo() const { return to; } + +private: + idStr text; + idStr subject; + idStr date; + idStr to; + idStr from; +}; + + +class idDeclVideo : public idDecl { +public: + idDeclVideo() : preview( NULL ), video( NULL ), audio( NULL ) {}; + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print() const; + virtual void List() const; + + const idMaterial * GetRoq() const { return video; } + const idSoundShader * GetWave() const { return audio; } + const char * GetVideoName() const { return videoName; } + const char * GetInfo() const { return info; } + const idMaterial * GetPreview() const { return preview; } + +private: + const idMaterial * preview; + const idMaterial * video; + idStr videoName; + idStr info; + const idSoundShader * audio; +}; + + +class idDeclAudio : public idDecl { +public: + idDeclAudio() : audio( NULL ) {}; + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print() const; + virtual void List() const; + + const char * GetAudioName() const { return audioName; } + const idSoundShader * GetWave() const { return audio; } + const char * GetInfo() const { return info; } + +private: + const idSoundShader * audio; + idStr audioName; + idStr info; +}; + +class idDeclPDA : public idDecl { +public: + idDeclPDA() { originalEmails = originalVideos = 0; }; + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print() const; + virtual void List() const; + + virtual void AddVideo( const idDeclVideo * video, bool unique = true ) const { if ( unique ) { videos.AddUnique( video ); } else { videos.Append( video ); } } + virtual void AddAudio( const idDeclAudio * audio, bool unique = true ) const { if ( unique ) { audios.AddUnique( audio ); } else { audios.Append( audio ); } } + virtual void AddEmail( const idDeclEmail * email, bool unique = true ) const { if ( unique ) { emails.AddUnique( email ); } else { emails.Append( email ); } } + virtual void RemoveAddedEmailsAndVideos() const; + + virtual const int GetNumVideos() const { return videos.Num(); } + virtual const int GetNumAudios() const { return audios.Num(); } + virtual const int GetNumEmails() const { return emails.Num(); } + virtual const idDeclVideo *GetVideoByIndex( int index ) const { return ( index < 0 || index > videos.Num() ? NULL : videos[index] ); } + virtual const idDeclAudio *GetAudioByIndex( int index ) const { return ( index < 0 || index > audios.Num() ? NULL : audios[index] ); } + virtual const idDeclEmail *GetEmailByIndex( int index ) const { return ( index < 0 || index > emails.Num() ? NULL : emails[index] ); } + + virtual void SetSecurity( const char *sec ) const; + + const char * GetPdaName() const { return pdaName; } + const char * GetSecurity() const {return security; } + const char * GetFullName() const { return fullName; } + const char * GetIcon() const { return icon; } + const char * GetPost() const { return post; } + const char * GetID() const { return id; } + const char * GetTitle() const { return title; } + +private: + mutable idList videos; + mutable idList audios; + mutable idList emails; + idStr pdaName; + idStr fullName; + idStr icon; + idStr id; + idStr post; + idStr title; + mutable idStr security; + mutable int originalEmails; + mutable int originalVideos; +}; + +#endif /* !__DECLPDA_H__ */ diff --git a/neo/framework/DeclParticle.cpp b/neo/framework/DeclParticle.cpp new file mode 100644 index 00000000..2baa16c7 --- /dev/null +++ b/neo/framework/DeclParticle.cpp @@ -0,0 +1,1642 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar binaryLoadParticles( "binaryLoadParticles", "1", 0, "enable binary load/write of particle decls" ); + +static const byte BPRT_VERSION = 101; +static const unsigned int BPRT_MAGIC = ( 'B' << 24 ) | ( 'P' << 16 ) | ( 'R' << 8 ) | BPRT_VERSION; + +struct ParticleParmDesc { + const char *name; + int count; + const char *desc; +}; + +const ParticleParmDesc ParticleDistributionDesc[] = { + { "rect", 3, "" }, + { "cylinder", 4, "" }, + { "sphere", 3, "" } +}; + +const ParticleParmDesc ParticleDirectionDesc[] = { + { "cone", 1, "" }, + { "outward", 1, "" }, +}; + +const ParticleParmDesc ParticleOrientationDesc[] = { + { "view", 0, "" }, + { "aimed", 2, "" }, + { "x", 0, "" }, + { "y", 0, "" }, + { "z", 0, "" } +}; + +const ParticleParmDesc ParticleCustomDesc[] = { + { "standard", 0, "Standard" }, + { "helix", 5, "sizeX Y Z radialSpeed axialSpeed" }, + { "flies", 3, "radialSpeed axialSpeed size" }, + { "orbit", 2, "radius speed"}, + { "drip", 2, "something something" } +}; + +const int CustomParticleCount = sizeof( ParticleCustomDesc ) / sizeof( const ParticleParmDesc ); + +/* +================= +idDeclParticle::Size +================= +*/ +size_t idDeclParticle::Size() const { + return sizeof( idDeclParticle ); +} + +/* +===================== +idDeclParticle::GetStageBounds +===================== +*/ +void idDeclParticle::GetStageBounds( idParticleStage *stage ) { + + stage->bounds.Clear(); + + // this isn't absolutely guaranteed, but it should be close + + particleGen_t g; + + renderEntity_t renderEntity; + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.axis = mat3_identity; + + renderView_t renderView; + memset( &renderView, 0, sizeof( renderView ) ); + renderView.viewaxis = mat3_identity; + + g.renderEnt = &renderEntity; + g.renderView = &renderView; + g.origin.Zero(); + g.axis = mat3_identity; + + idRandom steppingRandom; + steppingRandom.SetSeed( 0 ); + + // just step through a lot of possible particles as a representative sampling + for ( int i = 0 ; i < 1000 ; i++ ) { + g.random = g.originalRandom = steppingRandom; + + int maxMsec = stage->particleLife * 1000; + for ( int inCycleTime = 0 ; inCycleTime < maxMsec ; inCycleTime += 16 ) { + + // make sure we get the very last tic, which may make up an extreme edge + if ( inCycleTime + 16 > maxMsec ) { + inCycleTime = maxMsec - 1; + } + + g.frac = (float)inCycleTime / ( stage->particleLife * 1000 ); + g.age = inCycleTime * 0.001f; + + // if the particle doesn't get drawn because it is faded out or beyond a kill region, + // don't increment the verts + + idVec3 origin; + stage->ParticleOrigin( &g, origin ); + stage->bounds.AddPoint( origin ); + } + } + + // find the max size + float maxSize = 0; + + for ( float f = 0; f <= 1.0f; f += 1.0f / 64 ) { + float size = stage->size.Eval( f, steppingRandom ); + float aspect = stage->aspect.Eval( f, steppingRandom ); + if ( aspect > 1 ) { + size *= aspect; + } + if ( size > maxSize ) { + maxSize = size; + } + } + + maxSize += 8; // just for good measure + // users can specify a per-stage bounds expansion to handle odd cases + stage->bounds.ExpandSelf( maxSize + stage->boundsExpansion ); +} + +/* +================ +idDeclParticle::ParseParms + +Parses a variable length list of parms on one line +================ +*/ +void idDeclParticle::ParseParms( idLexer &src, float *parms, int maxParms ) { + idToken token; + + memset( parms, 0, maxParms * sizeof( *parms ) ); + int count = 0; + while( 1 ) { + if ( !src.ReadTokenOnLine( &token ) ) { + return; + } + if ( count == maxParms ) { + src.Error( "too many parms on line" ); + return; + } + token.StripQuotes(); + parms[count] = atof( token ); + count++; + } +} + +/* +================ +idDeclParticle::ParseParametric +================ +*/ +void idDeclParticle::ParseParametric( idLexer &src, idParticleParm *parm ) { + idToken token; + + parm->table = NULL; + parm->from = parm->to = 0.0f; + + if ( !src.ReadToken( &token ) ) { + src.Error( "not enough parameters" ); + return; + } + + if ( token.IsNumeric() ) { + // can have a to + 2nd parm + parm->from = parm->to = atof( token ); + if ( src.ReadToken( &token ) ) { + if ( !token.Icmp( "to" ) ) { + if ( !src.ReadToken( &token ) ) { + src.Error( "missing second parameter" ); + return; + } + parm->to = atof( token ); + } else { + src.UnreadToken( &token ); + } + } + } else { + // table + parm->table = static_cast( declManager->FindType( DECL_TABLE, token, false ) ); + } + +} + +/* +================ +idDeclParticle::ParseParticleStage +================ +*/ +idParticleStage *idDeclParticle::ParseParticleStage( idLexer &src ) { + idToken token; + + idParticleStage *stage = new (TAG_DECL) idParticleStage; + stage->Default(); + + while (1) { + if ( src.HadError() ) { + break; + } + if ( !src.ReadToken( &token ) ) { + break; + } + if ( !token.Icmp( "}" ) ) { + break; + } + if ( !token.Icmp( "material" ) ) { + src.ReadToken( &token ); + stage->material = declManager->FindMaterial( token.c_str() ); + continue; + } + if ( !token.Icmp( "count" ) ) { + stage->totalParticles = src.ParseInt(); + continue; + } + if ( !token.Icmp( "time" ) ) { + stage->particleLife = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "cycles" ) ) { + stage->cycles = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "timeOffset" ) ) { + stage->timeOffset = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "deadTime" ) ) { + stage->deadTime = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "randomDistribution" ) ) { + stage->randomDistribution = src.ParseBool(); + continue; + } + if ( !token.Icmp( "bunching" ) ) { + stage->spawnBunching = src.ParseFloat(); + continue; + } + + if ( !token.Icmp( "distribution" ) ) { + src.ReadToken( &token ); + if ( !token.Icmp( "rect" ) ) { + stage->distributionType = PDIST_RECT; + } else if ( !token.Icmp( "cylinder" ) ) { + stage->distributionType = PDIST_CYLINDER; + } else if ( !token.Icmp( "sphere" ) ) { + stage->distributionType = PDIST_SPHERE; + } else { + src.Error( "bad distribution type: %s\n", token.c_str() ); + } + ParseParms( src, stage->distributionParms, sizeof( stage->distributionParms ) / sizeof( stage->distributionParms[0] ) ); + continue; + } + + if ( !token.Icmp( "direction" ) ) { + src.ReadToken( &token ); + if ( !token.Icmp( "cone" ) ) { + stage->directionType = PDIR_CONE; + } else if ( !token.Icmp( "outward" ) ) { + stage->directionType = PDIR_OUTWARD; + } else { + src.Error( "bad direction type: %s\n", token.c_str() ); + } + ParseParms( src, stage->directionParms, sizeof( stage->directionParms ) / sizeof( stage->directionParms[0] ) ); + continue; + } + + if ( !token.Icmp( "orientation" ) ) { + src.ReadToken( &token ); + if ( !token.Icmp( "view" ) ) { + stage->orientation = POR_VIEW; + } else if ( !token.Icmp( "aimed" ) ) { + stage->orientation = POR_AIMED; + } else if ( !token.Icmp( "x" ) ) { + stage->orientation = POR_X; + } else if ( !token.Icmp( "y" ) ) { + stage->orientation = POR_Y; + } else if ( !token.Icmp( "z" ) ) { + stage->orientation = POR_Z; + } else { + src.Error( "bad orientation type: %s\n", token.c_str() ); + } + ParseParms( src, stage->orientationParms, sizeof( stage->orientationParms ) / sizeof( stage->orientationParms[0] ) ); + continue; + } + + if ( !token.Icmp( "customPath" ) ) { + src.ReadToken( &token ); + if ( !token.Icmp( "standard" ) ) { + stage->customPathType = PPATH_STANDARD; + } else if ( !token.Icmp( "helix" ) ) { + stage->customPathType = PPATH_HELIX; + } else if ( !token.Icmp( "flies" ) ) { + stage->customPathType = PPATH_FLIES; + } else if ( !token.Icmp( "spherical" ) ) { + stage->customPathType = PPATH_ORBIT; + } else { + src.Error( "bad path type: %s\n", token.c_str() ); + } + ParseParms( src, stage->customPathParms, sizeof( stage->customPathParms ) / sizeof( stage->customPathParms[0] ) ); + continue; + } + + if ( !token.Icmp( "speed" ) ) { + ParseParametric( src, &stage->speed ); + continue; + } + if ( !token.Icmp( "rotation" ) ) { + ParseParametric( src, &stage->rotationSpeed ); + continue; + } + if ( !token.Icmp( "angle" ) ) { + stage->initialAngle = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "entityColor" ) ) { + stage->entityColor = src.ParseBool(); + continue; + } + if ( !token.Icmp( "size" ) ) { + ParseParametric( src, &stage->size ); + continue; + } + if ( !token.Icmp( "aspect" ) ) { + ParseParametric( src, &stage->aspect ); + continue; + } + if ( !token.Icmp( "fadeIn" ) ) { + stage->fadeInFraction = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "fadeOut" ) ) { + stage->fadeOutFraction = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "fadeIndex" ) ) { + stage->fadeIndexFraction = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "color" ) ) { + stage->color[0] = src.ParseFloat(); + stage->color[1] = src.ParseFloat(); + stage->color[2] = src.ParseFloat(); + stage->color[3] = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "fadeColor" ) ) { + stage->fadeColor[0] = src.ParseFloat(); + stage->fadeColor[1] = src.ParseFloat(); + stage->fadeColor[2] = src.ParseFloat(); + stage->fadeColor[3] = src.ParseFloat(); + continue; + } + if ( !token.Icmp("offset" ) ) { + stage->offset[0] = src.ParseFloat(); + stage->offset[1] = src.ParseFloat(); + stage->offset[2] = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "animationFrames" ) ) { + stage->animationFrames = src.ParseInt(); + continue; + } + if ( !token.Icmp( "animationRate" ) ) { + stage->animationRate = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "boundsExpansion" ) ) { + stage->boundsExpansion = src.ParseFloat(); + continue; + } + if ( !token.Icmp( "gravity" ) ) { + src.ReadToken( &token ); + if ( !token.Icmp( "world" ) ) { + stage->worldGravity = true; + } else { + src.UnreadToken( &token ); + } + stage->gravity = src.ParseFloat(); + continue; + } + + src.Error( "unknown token %s\n", token.c_str() ); + } + + // derive values + stage->cycleMsec = ( stage->particleLife + stage->deadTime ) * 1000; + + return stage; +} + +/* +================ +idDeclParticle::Parse +================ +*/ +bool idDeclParticle::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + fileSystem->AddParticlePreload( GetName() ); + } + + idLexer src; + idToken token; + + unsigned int sourceChecksum = 0; + idStrStatic< MAX_OSPATH > generatedFileName; + if ( allowBinaryVersion ) { + // Try to load the generated version of it + // If successful, + // - Create an MD5 of the hash of the source + // - Load the MD5 of the generated, if they differ, create a new generated + generatedFileName = "generated/particles/"; + generatedFileName.AppendPath( GetName() ); + generatedFileName.SetFileExtension( ".bprt" ); + + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + sourceChecksum = MD5_BlockChecksum( text, textLength ); + + if ( binaryLoadParticles.GetBool() && LoadBinary( file, sourceChecksum ) ) { + return true; + } + } + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + depthHack = 0.0f; + + while (1) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( !token.Icmp( "{" ) ) { + if ( stages.Num() >= MAX_PARTICLE_STAGES ) { + src.Error( "Too many particle stages" ); + MakeDefault(); + return false; + } + idParticleStage *stage = ParseParticleStage( src ); + if ( !stage ) { + src.Warning( "Particle stage parse failed" ); + MakeDefault(); + return false; + } + stages.Append( stage ); + continue; + } + + if ( !token.Icmp( "depthHack" ) ) { + depthHack = src.ParseFloat(); + continue; + } + + src.Warning( "bad token %s", token.c_str() ); + MakeDefault(); + return false; + } + + // don't calculate bounds or write binary files for defaulted ( non-existent ) particles in resource builds + if ( fileSystem->UsingResourceFiles() ) { + bounds = idBounds( vec3_origin ).Expand( 8.0f ); + return true; + } + // + // calculate the bounds + // + bounds.Clear(); + for( int i = 0; i < stages.Num(); i++ ) { + GetStageBounds( stages[i] ); + bounds.AddBounds( stages[i]->bounds ); + } + + if ( bounds.GetVolume() <= 0.1f ) { + bounds = idBounds( vec3_origin ).Expand( 8.0f ); + } + + if ( allowBinaryVersion && binaryLoadParticles.GetBool() ) { + idLib::Printf( "Writing %s\n", generatedFileName.c_str() ); + idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); + WriteBinary( outputFile, sourceChecksum ); + } + + return true; +} + +/* +======================== +idDeclParticle::LoadBinary +======================== +*/ +bool idDeclParticle::LoadBinary( idFile * file, unsigned int checksum ) { + + if ( file == NULL ) { + return false; + } + + struct local { + static void LoadParticleParm( idFile * file, idParticleParm & parm ) { + idStr name; + file->ReadString( name ); + if ( name.IsEmpty() ) { + parm.table = NULL; + } else { + parm.table = (idDeclTable *)declManager->FindType( DECL_TABLE, name, false ); + } + + file->ReadFloat( parm.from ); + file->ReadFloat( parm.to ); + } + }; + + unsigned int magic = 0; + file->ReadBig( magic ); + if ( magic != BPRT_MAGIC ) { + return false; + } + + unsigned int loadedChecksum; + file->ReadBig( loadedChecksum ); + if ( checksum != loadedChecksum && !fileSystem->InProductionMode() ) { + return false; + } + + int numStages; + file->ReadBig( numStages ); + + for ( int i = 0; i < numStages; i++ ) { + idParticleStage * s = new (TAG_DECL) idParticleStage; + stages.Append( s ); + assert( stages.Num() <= MAX_PARTICLE_STAGES ); + + idStr name; + file->ReadString( name ); + if ( name.IsEmpty() ) { + s->material = NULL; + } else { + s->material = declManager->FindMaterial( name ); + } + + file->ReadBig( s->totalParticles ); + file->ReadFloat( s->cycles ); + file->ReadBig( s->cycleMsec ); + file->ReadFloat( s->spawnBunching ); + file->ReadFloat( s->particleLife ); + file->ReadFloat( s->timeOffset ); + file->ReadFloat( s->deadTime ); + file->ReadBig( s->distributionType ); + file->ReadBigArray( s->distributionParms, sizeof( s->distributionParms ) / sizeof ( s->distributionParms[0] ) ); + file->ReadBig( s->directionType ); + file->ReadBigArray( s->directionParms, sizeof( s->directionParms ) / sizeof ( s->directionParms[0] ) ); + local::LoadParticleParm( file, s->speed ); + file->ReadFloat( s->gravity ); + file->ReadBig( s->worldGravity ); + file->ReadBig( s->randomDistribution ); + file->ReadBig( s->entityColor ); + file->ReadBig( s->customPathType ); + file->ReadBigArray( s->customPathParms, sizeof( s->customPathParms ) / sizeof ( s->customPathParms[0] ) ); + file->ReadVec3( s->offset ); + file->ReadBig( s->animationFrames ); + file->ReadFloat( s->animationRate ); + file->ReadFloat( s->initialAngle ); + local::LoadParticleParm( file, s->rotationSpeed ); + file->ReadBig( s->orientation ); + file->ReadBigArray( s->orientationParms, sizeof( s->orientationParms ) / sizeof ( s->orientationParms[0] ) ); + local::LoadParticleParm( file, s->size ); + local::LoadParticleParm( file, s->aspect ); + file->ReadVec4( s->color ); + file->ReadVec4( s->fadeColor ); + file->ReadFloat( s->fadeInFraction ); + file->ReadFloat( s->fadeOutFraction ); + file->ReadFloat( s->fadeIndexFraction ); + file->ReadBig( s->hidden ); + file->ReadFloat( s->boundsExpansion ); + file->ReadVec3( s->bounds[0] ); + file->ReadVec3( s->bounds[1] ); + } + + file->ReadVec3( bounds[0] ); + file->ReadVec3( bounds[1] ); + file->ReadFloat( depthHack ); + + return true; +} + +/* +======================== +idDeclParticle::WriteBinary +======================== +*/ +void idDeclParticle::WriteBinary( idFile * file, unsigned int checksum ) { + + if ( file == NULL ) { + return; + } + + struct local { + static void WriteParticleParm( idFile * file, idParticleParm & parm ) { + if ( parm.table != NULL && parm.table->GetName() != NULL ) { + file->WriteString( parm.table->GetName() ); + } else { + file->WriteString( "" ); + } + file->WriteFloat( parm.from ); + file->WriteFloat( parm.to ); + } + }; + + file->WriteBig( BPRT_MAGIC ); + file->WriteBig( checksum ); + file->WriteBig( stages.Num() ); + + for ( int i = 0; i < stages.Num(); i++ ) { + idParticleStage * s = stages[i]; + + if ( s->material != NULL && s->material->GetName() != NULL ) { + file->WriteString( s->material->GetName() ); + } else { + file->WriteString( "" ); + } + file->WriteBig( s->totalParticles ); + file->WriteFloat( s->cycles ); + file->WriteBig( s->cycleMsec ); + file->WriteFloat( s->spawnBunching ); + file->WriteFloat( s->particleLife ); + file->WriteFloat( s->timeOffset ); + file->WriteFloat( s->deadTime ); + file->WriteBig( s->distributionType ); + file->WriteBigArray( s->distributionParms, sizeof( s->distributionParms ) / sizeof ( s->distributionParms[0] ) ); + file->WriteBig( s->directionType ); + file->WriteBigArray( s->directionParms, sizeof( s->directionParms ) / sizeof ( s->directionParms[0] ) ); + local::WriteParticleParm( file, s->speed ); + file->WriteFloat( s->gravity ); + file->WriteBig( s->worldGravity ); + file->WriteBig( s->randomDistribution ); + file->WriteBig( s->entityColor ); + file->WriteBig( s->customPathType ); + file->WriteBigArray( s->customPathParms, sizeof( s->customPathParms ) / sizeof ( s->customPathParms[0] ) ); + file->WriteVec3( s->offset ); + file->WriteBig( s->animationFrames ); + file->WriteFloat( s->animationRate ); + file->WriteFloat( s->initialAngle ); + local::WriteParticleParm( file, s->rotationSpeed ); + file->WriteBig( s->orientation ); + file->WriteBigArray( s->orientationParms, sizeof( s->orientationParms ) / sizeof ( s->orientationParms[0] ) ); + local::WriteParticleParm( file, s->size ); + local::WriteParticleParm( file, s->aspect ); + file->WriteVec4( s->color ); + file->WriteVec4( s->fadeColor ); + file->WriteFloat( s->fadeInFraction ); + file->WriteFloat( s->fadeOutFraction ); + file->WriteFloat( s->fadeIndexFraction ); + file->WriteBig( s->hidden ); + file->WriteFloat( s->boundsExpansion ); + file->WriteVec3( s->bounds[0] ); + file->WriteVec3( s->bounds[1] ); + } + + file->WriteVec3( bounds[0] ); + file->WriteVec3( bounds[1] ); + file->WriteFloat( depthHack ); +} + +/* +================ +idDeclParticle::FreeData +================ +*/ +void idDeclParticle::FreeData() { + stages.DeleteContents( true ); +} + +/* +================ +idDeclParticle::DefaultDefinition +================ +*/ +const char *idDeclParticle::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "material\t_default\n" + "\t\t" "count\t20\n" + "\t\t" "time\t\t1.0\n" + "\t" "}\n" + "}"; +} + +/* +================ +idDeclParticle::WriteParticleParm +================ +*/ +void idDeclParticle::WriteParticleParm( idFile *f, idParticleParm *parm, const char *name ) { + + f->WriteFloatString( "\t\t%s\t\t\t\t ", name ); + if ( parm->table ) { + f->WriteFloatString( "%s\n", parm->table->GetName() ); + } else { + f->WriteFloatString( "\"%.3f\" ", parm->from ); + if ( parm->from == parm->to ) { + f->WriteFloatString( "\n" ); + } else { + f->WriteFloatString( " to \"%.3f\"\n", parm->to ); + } + } +} + +/* +================ +idDeclParticle::WriteStage +================ +*/ +void idDeclParticle::WriteStage( idFile *f, idParticleStage *stage ) { + + int i; + + f->WriteFloatString( "\t{\n" ); + f->WriteFloatString( "\t\tcount\t\t\t\t%i\n", stage->totalParticles ); + f->WriteFloatString( "\t\tmaterial\t\t\t%s\n", stage->material->GetName() ); + if ( stage->animationFrames ) { + f->WriteFloatString( "\t\tanimationFrames \t%i\n", stage->animationFrames ); + } + if ( stage->animationRate ) { + f->WriteFloatString( "\t\tanimationRate \t\t%.3f\n", stage->animationRate ); + } + f->WriteFloatString( "\t\ttime\t\t\t\t%.3f\n", stage->particleLife ); + f->WriteFloatString( "\t\tcycles\t\t\t\t%.3f\n", stage->cycles ); + if ( stage->timeOffset ) { + f->WriteFloatString( "\t\ttimeOffset\t\t\t%.3f\n", stage->timeOffset ); + } + if ( stage->deadTime ) { + f->WriteFloatString( "\t\tdeadTime\t\t\t%.3f\n", stage->deadTime ); + } + f->WriteFloatString( "\t\tbunching\t\t\t%.3f\n", stage->spawnBunching ); + + f->WriteFloatString( "\t\tdistribution\t\t%s ", ParticleDistributionDesc[stage->distributionType].name ); + for ( i = 0; i < ParticleDistributionDesc[stage->distributionType].count; i++ ) { + f->WriteFloatString( "%.3f ", stage->distributionParms[i] ); + } + f->WriteFloatString( "\n" ); + + f->WriteFloatString( "\t\tdirection\t\t\t%s ", ParticleDirectionDesc[stage->directionType].name ); + for ( i = 0; i < ParticleDirectionDesc[stage->directionType].count; i++ ) { + f->WriteFloatString( "\"%.3f\" ", stage->directionParms[i] ); + } + f->WriteFloatString( "\n" ); + + f->WriteFloatString( "\t\torientation\t\t\t%s ", ParticleOrientationDesc[stage->orientation].name ); + for ( i = 0; i < ParticleOrientationDesc[stage->orientation].count; i++ ) { + f->WriteFloatString( "%.3f ", stage->orientationParms[i] ); + } + f->WriteFloatString( "\n" ); + + if ( stage->customPathType != PPATH_STANDARD ) { + f->WriteFloatString( "\t\tcustomPath %s ", ParticleCustomDesc[stage->customPathType].name ); + for ( i = 0; i < ParticleCustomDesc[stage->customPathType].count; i++ ) { + f->WriteFloatString( "%.3f ", stage->customPathParms[i] ); + } + f->WriteFloatString( "\n" ); + } + + if ( stage->entityColor ) { + f->WriteFloatString( "\t\tentityColor\t\t\t1\n" ); + } + + WriteParticleParm( f, &stage->speed, "speed" ); + WriteParticleParm( f, &stage->size, "size" ); + WriteParticleParm( f, &stage->aspect, "aspect" ); + + if ( stage->rotationSpeed.from ) { + WriteParticleParm( f, &stage->rotationSpeed, "rotation" ); + } + + if ( stage->initialAngle ) { + f->WriteFloatString( "\t\tangle\t\t\t\t%.3f\n", stage->initialAngle ); + } + + f->WriteFloatString( "\t\trandomDistribution\t\t\t\t%i\n", static_cast( stage->randomDistribution ) ); + f->WriteFloatString( "\t\tboundsExpansion\t\t\t\t%.3f\n", stage->boundsExpansion ); + + + f->WriteFloatString( "\t\tfadeIn\t\t\t\t%.3f\n", stage->fadeInFraction ); + f->WriteFloatString( "\t\tfadeOut\t\t\t\t%.3f\n", stage->fadeOutFraction ); + f->WriteFloatString( "\t\tfadeIndex\t\t\t\t%.3f\n", stage->fadeIndexFraction ); + + f->WriteFloatString( "\t\tcolor \t\t\t\t%.3f %.3f %.3f %.3f\n", stage->color.x, stage->color.y, stage->color.z, stage->color.w ); + f->WriteFloatString( "\t\tfadeColor \t\t\t%.3f %.3f %.3f %.3f\n", stage->fadeColor.x, stage->fadeColor.y, stage->fadeColor.z, stage->fadeColor.w ); + + f->WriteFloatString( "\t\toffset \t\t\t\t%.3f %.3f %.3f\n", stage->offset.x, stage->offset.y, stage->offset.z ); + f->WriteFloatString( "\t\tgravity \t\t\t" ); + if ( stage->worldGravity ) { + f->WriteFloatString( "world " ); + } + f->WriteFloatString( "%.3f\n", stage->gravity ); + f->WriteFloatString( "\t}\n" ); +} + +/* +================ +idDeclParticle::RebuildTextSource +================ +*/ +bool idDeclParticle::RebuildTextSource() { + idFile_Memory f; + + f.WriteFloatString("\n\n/*\n" + "\tGenerated by the Particle Editor.\n" + "\tTo use the particle editor, launch the game and type 'editParticles' on the console.\n" + "*/\n" ); + + f.WriteFloatString( "particle %s {\n", GetName() ); + + if ( depthHack ) { + f.WriteFloatString( "\tdepthHack\t%f\n", depthHack ); + } + + for ( int i = 0; i < stages.Num(); i++ ) { + WriteStage( &f, stages[i] ); + } + + f.WriteFloatString( "}" ); + + SetText( f.GetDataPtr() ); + + return true; +} + +/* +================ +idDeclParticle::Save +================ +*/ +bool idDeclParticle::Save( const char *fileName ) { + RebuildTextSource(); + if ( fileName ) { + declManager->CreateNewDecl( DECL_PARTICLE, GetName(), fileName ); + } + ReplaceSourceFileText(); + return true; +} + +/* +==================================================================================== + +idParticleParm + +==================================================================================== +*/ + +float idParticleParm::Eval( float frac, idRandom &rand ) const { + if ( table ) { + return table->TableLookup( frac ); + } + return from + frac * ( to - from ); +} + +float idParticleParm::Integrate( float frac, idRandom &rand ) const { + if ( table ) { + common->Printf( "idParticleParm::Integrate: can't integrate tables\n" ); + return 0; + } + return ( from + frac * ( to - from ) * 0.5f ) * frac; +} + +/* +==================================================================================== + +idParticleStage + +==================================================================================== +*/ + +/* +================ +idParticleStage::idParticleStage +================ +*/ +idParticleStage::idParticleStage() { + material = NULL; + totalParticles = 0; + cycles = 0.0f; + cycleMsec = 0; + spawnBunching = 0.0f; + particleLife = 0.0f; + timeOffset = 0.0f; + deadTime = 0.0f; + distributionType = PDIST_RECT; + distributionParms[0] = distributionParms[1] = distributionParms[2] = distributionParms[3] = 0.0f; + directionType = PDIR_CONE; + directionParms[0] = directionParms[1] = directionParms[2] = directionParms[3] = 0.0f; + // idParticleParm speed; + gravity = 0.0f; + worldGravity = false; + customPathType = PPATH_STANDARD; + customPathParms[0] = customPathParms[1] = customPathParms[2] = customPathParms[3] = 0.0f; + customPathParms[4] = customPathParms[5] = customPathParms[6] = customPathParms[7] = 0.0f; + offset.Zero(); + animationFrames = 0; + animationRate = 0.0f; + randomDistribution = true; + entityColor = false; + initialAngle = 0.0f; + // idParticleParm rotationSpeed; + orientation = POR_VIEW; + orientationParms[0] = orientationParms[1] = orientationParms[2] = orientationParms[3] = 0.0f; + // idParticleParm size + // idParticleParm aspect + color.Zero(); + fadeColor.Zero(); + fadeInFraction = 0.0f; + fadeOutFraction = 0.0f; + fadeIndexFraction = 0.0f; + hidden = false; + boundsExpansion = 0.0f; + bounds.Clear(); +} + +/* +================ +idParticleStage::Default + +Sets the stage to a default state +================ +*/ +void idParticleStage::Default() { + material = declManager->FindMaterial( "_default" ); + totalParticles = 100; + spawnBunching = 1.0f; + particleLife = 1.5f; + timeOffset = 0.0f; + deadTime = 0.0f; + distributionType = PDIST_RECT; + distributionParms[0] = 8.0f; + distributionParms[1] = 8.0f; + distributionParms[2] = 8.0f; + distributionParms[3] = 0.0f; + directionType = PDIR_CONE; + directionParms[0] = 90.0f; + directionParms[1] = 0.0f; + directionParms[2] = 0.0f; + directionParms[3] = 0.0f; + orientation = POR_VIEW; + orientationParms[0] = 0.0f; + orientationParms[1] = 0.0f; + orientationParms[2] = 0.0f; + orientationParms[3] = 0.0f; + speed.from = 150.0f; + speed.to = 150.0f; + speed.table = NULL; + gravity = 1.0f; + worldGravity = false; + customPathType = PPATH_STANDARD; + customPathParms[0] = 0.0f; + customPathParms[1] = 0.0f; + customPathParms[2] = 0.0f; + customPathParms[3] = 0.0f; + customPathParms[4] = 0.0f; + customPathParms[5] = 0.0f; + customPathParms[6] = 0.0f; + customPathParms[7] = 0.0f; + offset.Zero(); + animationFrames = 0; + animationRate = 0.0f; + initialAngle = 0.0f; + rotationSpeed.from = 0.0f; + rotationSpeed.to = 0.0f; + rotationSpeed.table = NULL; + size.from = 4.0f; + size.to = 4.0f; + size.table = NULL; + aspect.from = 1.0f; + aspect.to = 1.0f; + aspect.table = NULL; + color.x = 1.0f; + color.y = 1.0f; + color.z = 1.0f; + color.w = 1.0f; + fadeColor.x = 0.0f; + fadeColor.y = 0.0f; + fadeColor.z = 0.0f; + fadeColor.w = 0.0f; + fadeInFraction = 0.1f; + fadeOutFraction = 0.25f; + fadeIndexFraction = 0.0f; + boundsExpansion = 0.0f; + randomDistribution = true; + entityColor = false; + cycleMsec = ( particleLife + deadTime ) * 1000; +} + +/* +================ +idParticleStage::NumQuadsPerParticle + +includes trails and cross faded animations +================ +*/ +int idParticleStage::NumQuadsPerParticle() const { + int count = 1; + + if ( orientation == POR_AIMED ) { + int trails = idMath::Ftoi( orientationParms[0] ); + // each trail stage will add an extra quad + count *= ( 1 + trails ); + } + + // if we are doing strip-animation, we need to double the number and cross fade them + if ( animationFrames > 1 ) { + count *= 2; + } + + return count; +} + +/* +=============== +idParticleStage::ParticleOrigin +=============== +*/ +void idParticleStage::ParticleOrigin( particleGen_t *g, idVec3 &origin ) const { + if ( customPathType == PPATH_STANDARD ) { + // + // find intial origin distribution + // + float radiusSqr, angle1, angle2; + + switch( distributionType ) { + case PDIST_RECT: { // ( sizeX sizeY sizeZ ) + origin[0] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[0]; + origin[1] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[1]; + origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[2]; + break; + } + case PDIST_CYLINDER: { // ( sizeX sizeY sizeZ ringFraction ) + angle1 = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * idMath::TWO_PI; + + idMath::SinCos16( angle1, origin[0], origin[1] ); + origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ); + + // reproject points that are inside the ringFraction to the outer band + if ( distributionParms[3] > 0.0f ) { + radiusSqr = origin[0] * origin[0] + origin[1] * origin[1]; + if ( radiusSqr < distributionParms[3] * distributionParms[3] ) { + // if we are inside the inner reject zone, rescale to put it out into the good zone + float f = sqrt( radiusSqr ) / distributionParms[3]; + float invf = 1.0f / f; + float newRadius = distributionParms[3] + f * ( 1.0f - distributionParms[3] ); + float rescale = invf * newRadius; + + origin[0] *= rescale; + origin[1] *= rescale; + } + } + origin[0] *= distributionParms[0]; + origin[1] *= distributionParms[1]; + origin[2] *= distributionParms[2]; + break; + } + case PDIST_SPHERE: { // ( sizeX sizeY sizeZ ringFraction ) + // iterating with rejection is the only way to get an even distribution over a sphere + if ( randomDistribution ) { + do { + origin[0] = g->random.CRandomFloat(); + origin[1] = g->random.CRandomFloat(); + origin[2] = g->random.CRandomFloat(); + radiusSqr = origin[0] * origin[0] + origin[1] * origin[1] + origin[2] * origin[2]; + } while( radiusSqr > 1.0f ); + } else { + origin.Set( 1.0f, 1.0f, 1.0f ); + radiusSqr = 3.0f; + } + + if ( distributionParms[3] > 0.0f ) { + // we could iterate until we got something that also satisfied ringFraction, + // but for narrow rings that could be a lot of work, so reproject inside points instead + if ( radiusSqr < distributionParms[3] * distributionParms[3] ) { + // if we are inside the inner reject zone, rescale to put it out into the good zone + float f = sqrt( radiusSqr ) / distributionParms[3]; + float invf = 1.0f / f; + float newRadius = distributionParms[3] + f * ( 1.0f - distributionParms[3] ); + float rescale = invf * newRadius; + + origin[0] *= rescale; + origin[1] *= rescale; + origin[2] *= rescale; + } + } + origin[0] *= distributionParms[0]; + origin[1] *= distributionParms[1]; + origin[2] *= distributionParms[2]; + break; + } + } + + // offset will effect all particle origin types + // add this before the velocity and gravity additions + origin += offset; + + // + // add the velocity over time + // + idVec3 dir; + + switch( directionType ) { + case PDIR_CONE: { + // angle is the full angle, so 360 degrees is any spherical direction + angle1 = g->random.CRandomFloat() * directionParms[0] * idMath::M_DEG2RAD; + angle2 = g->random.CRandomFloat() * idMath::PI; + + float s1, c1, s2, c2; + idMath::SinCos16( angle1, s1, c1 ); + idMath::SinCos16( angle2, s2, c2 ); + + dir[0] = s1 * c2; + dir[1] = s1 * s2; + dir[2] = c1; + break; + } + case PDIR_OUTWARD: { + dir = origin; + dir.Normalize(); + dir[2] += directionParms[0]; + break; + } + } + + // add speed + float iSpeed = speed.Integrate( g->frac, g->random ); + origin += dir * iSpeed * particleLife; + + } else { + // + // custom paths completely override both the origin and velocity calculations, but still + // use the standard gravity + // + float angle1, angle2, speed1, speed2; + switch( customPathType ) { + case PPATH_HELIX: { // ( sizeX sizeY sizeZ radialSpeed axialSpeed ) + speed1 = g->random.CRandomFloat(); + speed2 = g->random.CRandomFloat(); + angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[3] * speed1 * g->age; + + float s1, c1; + idMath::SinCos16( angle1, s1, c1 ); + + origin[0] = c1 * customPathParms[0]; + origin[1] = s1 * customPathParms[1]; + origin[2] = g->random.RandomFloat() * customPathParms[2] + customPathParms[4] * speed2 * g->age; + break; + } + case PPATH_FLIES: { // ( radialSpeed axialSpeed size ) + speed1 = idMath::ClampFloat( 0.4f, 1.0f, g->random.CRandomFloat() ); + speed2 = idMath::ClampFloat( 0.4f, 1.0f, g->random.CRandomFloat() ); + angle1 = g->random.RandomFloat() * idMath::PI * 2 + customPathParms[0] * speed1 * g->age; + angle2 = g->random.RandomFloat() * idMath::PI * 2 + customPathParms[1] * speed1 * g->age; + + float s1, c1, s2, c2; + idMath::SinCos16( angle1, s1, c1 ); + idMath::SinCos16( angle2, s2, c2 ); + + origin[0] = c1 * c2; + origin[1] = s1 * c2; + origin[2] = -s2; + origin *= customPathParms[2]; + break; + } + case PPATH_ORBIT: { // ( radius speed axis ) + angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[1] * g->age; + + float s1, c1; + idMath::SinCos16( angle1, s1, c1 ); + + origin[0] = c1 * customPathParms[0]; + origin[1] = s1 * customPathParms[0]; + origin.ProjectSelfOntoSphere( customPathParms[0] ); + break; + } + case PPATH_DRIP: { // ( speed ) + origin[0] = 0.0f; + origin[1] = 0.0f; + origin[2] = -( g->age * customPathParms[0] ); + break; + } + default: { + common->Error( "idParticleStage::ParticleOrigin: bad customPathType" ); + } + } + + origin += offset; + } + + // adjust for the per-particle smoke offset + origin *= g->axis; + origin += g->origin; + + // add gravity after adjusting for axis + if ( worldGravity ) { + idVec3 gra( 0, 0, -gravity ); + gra *= g->renderEnt->axis.Transpose(); + origin += gra * g->age * g->age; + } else { + origin[2] -= gravity * g->age * g->age; + } +} + +/* +================== +idParticleStage::ParticleVerts +================== +*/ +int idParticleStage::ParticleVerts( particleGen_t *g, idVec3 origin, idDrawVert *verts ) const { + float psize = size.Eval( g->frac, g->random ); + float paspect = aspect.Eval( g->frac, g->random ); + + float width = psize; + float height = psize * paspect; + + idVec3 left, up; + + if ( orientation == POR_AIMED ) { + // reset the values to an earlier time to get a previous origin + idRandom currentRandom = g->random; + float currentAge = g->age; + float currentFrac = g->frac; + idDrawVert *verts_p = verts; + idVec3 stepOrigin = origin; + idVec3 stepLeft; + int numTrails = idMath::Ftoi( orientationParms[0] ); + float trailTime = orientationParms[1]; + + if ( trailTime == 0 ) { + trailTime = 0.5f; + } + + float height = 1.0f / ( 1 + numTrails ); + float t = 0; + + for ( int i = 0 ; i <= numTrails ; i++ ) { + g->random = g->originalRandom; + g->age = currentAge - ( i + 1 ) * trailTime / ( numTrails + 1 ); // time to back up + g->frac = g->age / particleLife; + + idVec3 oldOrigin; + ParticleOrigin( g, oldOrigin ); + + up = stepOrigin - oldOrigin; // along the direction of travel + + idVec3 forwardDir; + g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[0], forwardDir ); + + up -= ( up * forwardDir ) * forwardDir; + + up.Normalize(); + + + left = up.Cross( forwardDir ); + left *= psize; + + verts_p[0] = verts[0]; + verts_p[1] = verts[1]; + verts_p[2] = verts[2]; + verts_p[3] = verts[3]; + + if ( i == 0 ) { + verts_p[0].xyz = stepOrigin - left; + verts_p[1].xyz = stepOrigin + left; + } else { + verts_p[0].xyz = stepOrigin - stepLeft; + verts_p[1].xyz = stepOrigin + stepLeft; + } + verts_p[2].xyz = oldOrigin - left; + verts_p[3].xyz = oldOrigin + left; + + // modify texcoords + verts_p[0].SetTexCoordT( t ); + verts_p[1].SetTexCoordT( t ); + verts_p[2].SetTexCoordT( t + height ); + verts_p[3].SetTexCoordT( t + height ); + + t += height; + + verts_p += 4; + + stepOrigin = oldOrigin; + stepLeft = left; + } + + g->random = currentRandom; + g->age = currentAge; + g->frac = currentFrac; + + return 4 * (numTrails+1); + } + + // + // constant rotation + // + float angle; + + angle = ( initialAngle ) ? initialAngle : 360 * g->random.RandomFloat(); + + float angleMove = rotationSpeed.Integrate( g->frac, g->random ) * particleLife; + // have hald the particles rotate each way + if ( g->index & 1 ) { + angle += angleMove; + } else { + angle -= angleMove; + } + + angle = angle / 180 * idMath::PI; + float c = idMath::Cos16( angle ); + float s = idMath::Sin16( angle ); + + if ( orientation == POR_Z ) { + // oriented in entity space + left[0] = s; + left[1] = c; + left[2] = 0; + up[0] = c; + up[1] = -s; + up[2] = 0; + } else if ( orientation == POR_X ) { + // oriented in entity space + left[0] = 0; + left[1] = c; + left[2] = s; + up[0] = 0; + up[1] = -s; + up[2] = c; + } else if ( orientation == POR_Y ) { + // oriented in entity space + left[0] = c; + left[1] = 0; + left[2] = s; + up[0] = -s; + up[1] = 0; + up[2] = c; + } else { + // oriented in viewer space + idVec3 entityLeft, entityUp; + + g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[1], entityLeft ); + g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[2], entityUp ); + + left = entityLeft * c + entityUp * s; + up = entityUp * c - entityLeft * s; + } + + left *= width; + up *= height; + + verts[0].xyz = origin - left + up; + verts[1].xyz = origin + left + up; + verts[2].xyz = origin - left - up; + verts[3].xyz = origin + left - up; + + return 4; +} + +/* +================== +idParticleStage::ParticleTexCoords +================== +*/ +void idParticleStage::ParticleTexCoords( particleGen_t *g, idDrawVert *verts ) const { + float s, width; + float t, height; + + if ( animationFrames > 1 ) { + width = 1.0f / animationFrames; + float floatFrame; + if ( animationRate ) { + // explicit, cycling animation + floatFrame = g->age * animationRate; + } else { + // single animation cycle over the life of the particle + floatFrame = g->frac * animationFrames; + } + int intFrame = (int)floatFrame; + g->animationFrameFrac = floatFrame - intFrame; + s = width * intFrame; + } else { + s = 0.0f; + width = 1.0f; + } + + t = 0.0f; + height = 1.0f; + + verts[0].SetTexCoord( s, t ); + verts[1].SetTexCoord( s+width, t ); + verts[2].SetTexCoord( s, t+height ); + verts[3].SetTexCoord( s+width, t+height ); +} + +/* +================== +idParticleStage::ParticleColors +================== +*/ +void idParticleStage::ParticleColors( particleGen_t *g, idDrawVert *verts ) const { + float fadeFraction = 1.0f; + + // most particles fade in at the beginning and fade out at the end + if ( g->frac < fadeInFraction ) { + fadeFraction *= ( g->frac / fadeInFraction ); + } + if ( 1.0f - g->frac < fadeOutFraction ) { + fadeFraction *= ( ( 1.0f - g->frac ) / fadeOutFraction ); + } + + // individual gun smoke particles get more and more faded as the + // cycle goes on (note that totalParticles won't be correct for a surface-particle deform) + if ( fadeIndexFraction ) { + float indexFrac = ( totalParticles - g->index ) / (float)totalParticles; + if ( indexFrac < fadeIndexFraction ) { + fadeFraction *= indexFrac / fadeIndexFraction; + } + } + + for ( int i = 0 ; i < 4 ; i++ ) { + float fcolor = ( ( entityColor ) ? g->renderEnt->shaderParms[i] : color[i] ) * fadeFraction + fadeColor[i] * ( 1.0f - fadeFraction ); + int icolor = idMath::Ftoi( fcolor * 255.0f ); + if ( icolor < 0 ) { + icolor = 0; + } else if ( icolor > 255 ) { + icolor = 255; + } + verts[0].color[i] = + verts[1].color[i] = + verts[2].color[i] = + verts[3].color[i] = icolor; + } +} + +/* +================ +idParticleStage::CreateParticle + +Returns 0 if no particle is created because it is completely faded out +Returns 4 if a normal quad is created +Returns 8 if two cross faded quads are created + +Vertex order is: + +0 1 +2 3 +================ +*/ +int idParticleStage::CreateParticle( particleGen_t *g, idDrawVert *verts ) const { + idVec3 origin; + + verts[0].Clear(); + verts[1].Clear(); + verts[2].Clear(); + verts[3].Clear(); + + ParticleColors( g, verts ); + + // if we are completely faded out, kill the particle + if ( verts[0].color[0] == 0 && verts[0].color[1] == 0 && verts[0].color[2] == 0 && verts[0].color[3] == 0 ) { + return 0; + } + + ParticleOrigin( g, origin ); + + ParticleTexCoords( g, verts ); + + int numVerts = ParticleVerts( g, origin, verts ); + + if ( animationFrames <= 1 ) { + return numVerts; + } + + // if we are doing strip-animation, we need to double the quad and cross fade it + float width = 1.0f / animationFrames; + float frac = g->animationFrameFrac; + float iFrac = 1.0f - frac; + + idVec2 tempST; + for ( int i = 0 ; i < numVerts ; i++ ) { + verts[numVerts + i] = verts[i]; + + tempST = verts[numVerts + i].GetTexCoord(); + verts[numVerts + i].SetTexCoord( tempST.x + width, tempST.y ); + + verts[numVerts + i].color[0] *= frac; + verts[numVerts + i].color[1] *= frac; + verts[numVerts + i].color[2] *= frac; + verts[numVerts + i].color[3] *= frac; + + verts[i].color[0] *= iFrac; + verts[i].color[1] *= iFrac; + verts[i].color[2] *= iFrac; + verts[i].color[3] *= iFrac; + } + + return numVerts * 2; +} + +/* +================== +idParticleStage::GetCustomPathName +================== +*/ +const char* idParticleStage::GetCustomPathName() { + int index = ( customPathType < CustomParticleCount ) ? customPathType : 0; + return ParticleCustomDesc[index].name; +} + +/* +================== +idParticleStage::GetCustomPathDesc +================== +*/ +const char* idParticleStage::GetCustomPathDesc() { + int index = ( customPathType < CustomParticleCount ) ? customPathType : 0; + return ParticleCustomDesc[index].desc; +} + +/* +================== +idParticleStage::NumCustomPathParms +================== +*/ +int idParticleStage::NumCustomPathParms() { + int index = ( customPathType < CustomParticleCount ) ? customPathType : 0; + return ParticleCustomDesc[index].count; +} + +/* +================== +idParticleStage::SetCustomPathType +================== +*/ +void idParticleStage::SetCustomPathType( const char *p ) { + customPathType = PPATH_STANDARD; + for ( int i = 0; i < CustomParticleCount; i ++ ) { + if ( idStr::Icmp( p, ParticleCustomDesc[i].name ) == 0 ) { + customPathType = static_cast( i ); + break; + } + } +} + +/* +================== +idParticleStage::operator= +================== +*/ +void idParticleStage::operator=( const idParticleStage &src ) { + material = src.material; + totalParticles = src.totalParticles; + cycles = src.cycles; + cycleMsec = src.cycleMsec; + spawnBunching = src.spawnBunching; + particleLife = src.particleLife; + timeOffset = src.timeOffset; + deadTime = src.deadTime; + distributionType = src.distributionType; + distributionParms[0] = src.distributionParms[0]; + distributionParms[1] = src.distributionParms[1]; + distributionParms[2] = src.distributionParms[2]; + distributionParms[3] = src.distributionParms[3]; + directionType = src.directionType; + directionParms[0] = src.directionParms[0]; + directionParms[1] = src.directionParms[1]; + directionParms[2] = src.directionParms[2]; + directionParms[3] = src.directionParms[3]; + speed = src.speed; + gravity = src.gravity; + worldGravity = src.worldGravity; + randomDistribution = src.randomDistribution; + entityColor = src.entityColor; + customPathType = src.customPathType; + customPathParms[0] = src.customPathParms[0]; + customPathParms[1] = src.customPathParms[1]; + customPathParms[2] = src.customPathParms[2]; + customPathParms[3] = src.customPathParms[3]; + customPathParms[4] = src.customPathParms[4]; + customPathParms[5] = src.customPathParms[5]; + customPathParms[6] = src.customPathParms[6]; + customPathParms[7] = src.customPathParms[7]; + offset = src.offset; + animationFrames = src.animationFrames; + animationRate = src.animationRate; + initialAngle = src.initialAngle; + rotationSpeed = src.rotationSpeed; + orientation = src.orientation; + orientationParms[0] = src.orientationParms[0]; + orientationParms[1] = src.orientationParms[1]; + orientationParms[2] = src.orientationParms[2]; + orientationParms[3] = src.orientationParms[3]; + size = src.size; + aspect = src.aspect; + color = src.color; + fadeColor = src.fadeColor; + fadeInFraction = src.fadeInFraction; + fadeOutFraction = src.fadeOutFraction; + fadeIndexFraction = src.fadeIndexFraction; + hidden = src.hidden; + boundsExpansion = src.boundsExpansion; + bounds = src.bounds; +} diff --git a/neo/framework/DeclParticle.h b/neo/framework/DeclParticle.h new file mode 100644 index 00000000..c60cd82b --- /dev/null +++ b/neo/framework/DeclParticle.h @@ -0,0 +1,224 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLPARTICLE_H__ +#define __DECLPARTICLE_H__ + +/* +=============================================================================== + + idDeclParticle + +=============================================================================== +*/ + +static const int MAX_PARTICLE_STAGES = 32; + +class idParticleParm { +public: + idParticleParm() { table = NULL; from = to = 0.0f; } + + const idDeclTable * table; + float from; + float to; + + float Eval( float frac, idRandom &rand ) const; + float Integrate( float frac, idRandom &rand ) const; +}; + + +typedef enum { + PDIST_RECT, // ( sizeX sizeY sizeZ ) + PDIST_CYLINDER, // ( sizeX sizeY sizeZ ) + PDIST_SPHERE // ( sizeX sizeY sizeZ ringFraction ) + // a ringFraction of zero allows the entire sphere, 0.9 would only + // allow the outer 10% of the sphere +} prtDistribution_t; + +typedef enum { + PDIR_CONE, // parm0 is the solid cone angle + PDIR_OUTWARD // direction is relative to offset from origin, parm0 is an upward bias +} prtDirection_t; + +typedef enum { + PPATH_STANDARD, + PPATH_HELIX, // ( sizeX sizeY sizeZ radialSpeed climbSpeed ) + PPATH_FLIES, + PPATH_ORBIT, + PPATH_DRIP +} prtCustomPth_t; + +typedef enum { + POR_VIEW, + POR_AIMED, // angle and aspect are disregarded + POR_X, + POR_Y, + POR_Z +} prtOrientation_t; + +typedef struct renderEntity_s renderEntity_t; +typedef struct renderView_s renderView_t; + +typedef struct { + const renderEntity_t * renderEnt; // for shaderParms, etc + const renderView_t * renderView; + int index; // particle number in the system + float frac; // 0.0 to 1.0 + idRandom random; + idVec3 origin; // dynamic smoke particles can have individual origins and axis + idMat3 axis; + + + float age; // in seconds, calculated as fraction * stage->particleLife + idRandom originalRandom; // needed so aimed particles can reset the random for another origin calculation + float animationFrameFrac; // set by ParticleTexCoords, used to make the cross faded version +} particleGen_t; + + +// +// single particle stage +// +class idParticleStage { +public: + idParticleStage(); + ~idParticleStage() {} + + void Default(); + int NumQuadsPerParticle() const; // includes trails and cross faded animations + // returns the number of verts created, which will range from 0 to 4*NumQuadsPerParticle() + int CreateParticle( particleGen_t *g, idDrawVert *verts ) const; + + void ParticleOrigin( particleGen_t *g, idVec3 &origin ) const; + int ParticleVerts( particleGen_t *g, const idVec3 origin, idDrawVert *verts ) const; + void ParticleTexCoords( particleGen_t *g, idDrawVert *verts ) const; + void ParticleColors( particleGen_t *g, idDrawVert *verts ) const; + + const char * GetCustomPathName(); + const char * GetCustomPathDesc(); + int NumCustomPathParms(); + void SetCustomPathType( const char *p ); + void operator=( const idParticleStage &src ); + + + //------------------------------ + + const idMaterial * material; + + int totalParticles; // total number of particles, although some may be invisible at a given time + float cycles; // allows things to oneShot ( 1 cycle ) or run for a set number of cycles + // on a per stage basis + + int cycleMsec; // ( particleLife + deadTime ) in msec + + float spawnBunching; // 0.0 = all come out at first instant, 1.0 = evenly spaced over cycle time + float particleLife; // total seconds of life for each particle + float timeOffset; // time offset from system start for the first particle to spawn + float deadTime; // time after particleLife before respawning + + //------------------------------- // standard path parms + + prtDistribution_t distributionType; + float distributionParms[4]; + + prtDirection_t directionType; + float directionParms[4]; + + idParticleParm speed; + float gravity; // can be negative to float up + bool worldGravity; // apply gravity in world space + bool randomDistribution; // randomly orient the quad on emission ( defaults to true ) + bool entityColor; // force color from render entity ( fadeColor is still valid ) + + //------------------------------ // custom path will completely replace the standard path calculations + + prtCustomPth_t customPathType; // use custom C code routines for determining the origin + float customPathParms[8]; + + //-------------------------------- + + idVec3 offset; // offset from origin to spawn all particles, also applies to customPath + + int animationFrames; // if > 1, subdivide the texture S axis into frames and crossfade + float animationRate; // frames per second + + float initialAngle; // in degrees, random angle is used if zero ( default ) + idParticleParm rotationSpeed; // half the particles will have negative rotation speeds + + prtOrientation_t orientation; // view, aimed, or axis fixed + float orientationParms[4]; + + idParticleParm size; + idParticleParm aspect; // greater than 1 makes the T axis longer + + idVec4 color; + idVec4 fadeColor; // either 0 0 0 0 for additive, or 1 1 1 0 for blended materials + float fadeInFraction; // in 0.0 to 1.0 range + float fadeOutFraction; // in 0.0 to 1.0 range + float fadeIndexFraction; // in 0.0 to 1.0 range, causes later index smokes to be more faded + + bool hidden; // for editor use + //----------------------------------- + + float boundsExpansion; // user tweak to fix poorly calculated bounds + + idBounds bounds; // derived +}; + + +// +// group of particle stages +// +class idDeclParticle : public idDecl { +public: + + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + + bool Save( const char *fileName = NULL ); + + // Loaded instead of re-parsing, written if MD5 hash different + bool LoadBinary( idFile * file, unsigned int checksum ); + void WriteBinary( idFile * file, unsigned int checksum ); + + idListstages; + idBounds bounds; + float depthHack; + +private: + bool RebuildTextSource(); + void GetStageBounds( idParticleStage *stage ); + idParticleStage * ParseParticleStage( idLexer &src ); + void ParseParms( idLexer &src, float *parms, int maxParms ); + void ParseParametric( idLexer &src, idParticleParm *parm ); + void WriteStage( idFile *f, idParticleStage *stage ); + void WriteParticleParm( idFile *f, idParticleParm *parm, const char *name ); +}; + +#endif /* !__DECLPARTICLE_H__ */ diff --git a/neo/framework/DeclSkin.cpp b/neo/framework/DeclSkin.cpp new file mode 100644 index 00000000..815b9bc0 --- /dev/null +++ b/neo/framework/DeclSkin.cpp @@ -0,0 +1,185 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================= +idDeclSkin::Size +================= +*/ +size_t idDeclSkin::Size() const { + return sizeof( idDeclSkin ); +} + +/* +================ +idDeclSkin::FreeData +================ +*/ +void idDeclSkin::FreeData() { + mappings.Clear(); +} + +/* +================ +idDeclSkin::Parse +================ +*/ +bool idDeclSkin::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token, token2; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + associatedModels.Clear(); + + while (1) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + if ( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + if ( !token.Icmp( "model" ) ) { + associatedModels.Append( token2 ); + continue; + } + + skinMapping_t map; + + if ( !token.Icmp( "*" ) ) { + // wildcard + map.from = NULL; + } else { + map.from = declManager->FindMaterial( token ); + } + + map.to = declManager->FindMaterial( token2 ); + + mappings.Append( map ); + } + + return false; +} + +/* +================ +idDeclSkin::SetDefaultText +================ +*/ +bool idDeclSkin::SetDefaultText() { + // if there exists a material with the same name + if ( declManager->FindType( DECL_MATERIAL, GetName(), false ) ) { + char generated[2048]; + + idStr::snPrintf( generated, sizeof( generated ), + "skin %s // IMPLICITLY GENERATED\n" + "{\n" + "_default %s\n" + "}\n", GetName(), GetName() ); + SetText( generated ); + return true; + } else { + return false; + } +} + +/* +================ +idDeclSkin::DefaultDefinition +================ +*/ +const char *idDeclSkin::DefaultDefinition() const { + return + "{\n" + "\t" "\"*\"\t\"_default\"\n" + "}"; +} + +/* +================ +idDeclSkin::GetNumModelAssociations +================ +*/ +const int idDeclSkin::GetNumModelAssociations(void ) const { + return associatedModels.Num(); +} + +/* +================ +idDeclSkin::GetAssociatedModel +================ +*/ +const char *idDeclSkin::GetAssociatedModel( int index ) const { + if ( index >= 0 && index < associatedModels.Num() ) { + return associatedModels[ index ]; + } + return ""; +} + +/* +=============== +RemapShaderBySkin +=============== +*/ +const idMaterial *idDeclSkin::RemapShaderBySkin( const idMaterial *shader ) const { + int i; + + if ( !shader ) { + return NULL; + } + + // never remap surfaces that were originally nodraw, like collision hulls + if ( !shader->IsDrawn() ) { + return shader; + } + + for ( i = 0; i < mappings.Num() ; i++ ) { + const skinMapping_t *map = &mappings[i]; + + // NULL = wildcard match + if ( !map->from || map->from == shader ) { + return map->to; + } + } + + // didn't find a match or wildcard, so stay the same + return shader; +} diff --git a/neo/framework/DeclSkin.h b/neo/framework/DeclSkin.h new file mode 100644 index 00000000..80d533d2 --- /dev/null +++ b/neo/framework/DeclSkin.h @@ -0,0 +1,64 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLSKIN_H__ +#define __DECLSKIN_H__ + +/* +=============================================================================== + + idDeclSkin + +=============================================================================== +*/ + +typedef struct { + const idMaterial * from; // 0 == any unmatched shader + const idMaterial * to; +} skinMapping_t; + +class idDeclSkin : public idDecl { +public: + virtual size_t Size() const; + virtual bool SetDefaultText(); + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + + const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const; + + // model associations are just for the preview dialog in the editor + const int GetNumModelAssociations() const; + const char * GetAssociatedModel( int index ) const; + +private: + idList mappings; + idStrList associatedModels; +}; + +#endif /* !__DECLSKIN_H__ */ diff --git a/neo/framework/DeclTable.cpp b/neo/framework/DeclTable.cpp new file mode 100644 index 00000000..de2b68fe --- /dev/null +++ b/neo/framework/DeclTable.cpp @@ -0,0 +1,177 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================= +idDeclTable::TableLookup +================= +*/ +float idDeclTable::TableLookup( float index ) const { + int iIndex; + float iFrac; + + int domain = values.Num() - 1; + + if ( domain <= 1 ) { + return 1.0f; + } + + if ( clamp ) { + index *= (domain-1); + if ( index >= domain - 1 ) { + return values[domain - 1]; + } else if ( index <= 0 ) { + return values[0]; + } + iIndex = idMath::Ftoi( index ); + iFrac = index - iIndex; + } else { + index *= domain; + + if ( index < 0 ) { + index += domain * idMath::Ceil( -index / domain ); + } + + iIndex = idMath::Ftoi( idMath::Floor( index ) ); + iFrac = index - iIndex; + iIndex = iIndex % domain; + } + + if ( !snap ) { + // we duplicated the 0 index at the end at creation time, so we + // don't need to worry about wrapping the filter + return values[iIndex] * ( 1.0f - iFrac ) + values[iIndex + 1] * iFrac; + } + + return values[iIndex]; +} + +/* +================= +idDeclTable::Size +================= +*/ +size_t idDeclTable::Size() const { + return sizeof( idDeclTable ) + values.Allocated(); +} + +/* +================= +idDeclTable::FreeData +================= +*/ +void idDeclTable::FreeData() { + snap = false; + clamp = false; + values.Clear(); +} + +/* +================= +idDeclTable::DefaultDefinition +================= +*/ +const char *idDeclTable::DefaultDefinition() const { + return "{ { 0 } }"; +} + +/* +================= +idDeclTable::Parse +================= +*/ +bool idDeclTable::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + float v; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + snap = false; + clamp = false; + values.Clear(); + + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( token.Icmp( "snap" ) == 0 ) { + snap = true; + } else if ( token.Icmp( "clamp" ) == 0 ) { + clamp = true; + } else if ( token.Icmp( "{" ) == 0 ) { + + while ( 1 ) { + bool errorFlag; + + v = src.ParseFloat( &errorFlag ); + if ( errorFlag ) { + // we got something non-numeric + MakeDefault(); + return false; + } + + values.Append( v ); + + src.ReadToken( &token ); + if ( token == "}" ) { + break; + } + if ( token == "," ) { + continue; + } + src.Warning( "expected comma or brace" ); + MakeDefault(); + return false; + } + + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // copy the 0 element to the end, so lerping doesn't + // need to worry about the wrap case + float val = values[0]; // template bug requires this to not be in the Append()? + values.Append( val ); + + return true; +} diff --git a/neo/framework/DeclTable.h b/neo/framework/DeclTable.h new file mode 100644 index 00000000..da555387 --- /dev/null +++ b/neo/framework/DeclTable.h @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DECLTABLE_H__ +#define __DECLTABLE_H__ + +/* +=============================================================================== + + tables are used to map a floating point input value to a floating point + output value, with optional wrap / clamp and interpolation + +=============================================================================== +*/ + +class idDeclTable : public idDecl { +public: + virtual size_t Size() const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + + float TableLookup( float index ) const; + +private: + bool clamp; + bool snap; + idList values; +}; + +#endif /* !__DECLTABLE_H__ */ diff --git a/neo/framework/DemoChecksum.h b/neo/framework/DemoChecksum.h new file mode 100644 index 00000000..a45c838d --- /dev/null +++ b/neo/framework/DemoChecksum.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Pak file checksum for demo build. + +=============================================================================== +*/ + +// every time a new demo pk4 file is built, this checksum must be updated. +// the easiest way to get it is to just run the game and see what it spits out +#define DEMO_PAK_CHECKSUM 0xfe75bbef diff --git a/neo/framework/DemoFile.cpp b/neo/framework/DemoFile.cpp new file mode 100644 index 00000000..e9d959d7 --- /dev/null +++ b/neo/framework/DemoFile.cpp @@ -0,0 +1,328 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar idDemoFile::com_logDemos( "com_logDemos", "0", CVAR_SYSTEM | CVAR_BOOL, "Write demo.log with debug information in it" ); +idCVar idDemoFile::com_compressDemos( "com_compressDemos", "1", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE, "Compression scheme for demo files\n0: None (Fast, large files)\n1: LZW (Fast to compress, Fast to decompress, medium/small files)\n2: LZSS (Slow to compress, Fast to decompress, small files)\n3: Huffman (Fast to compress, Slow to decompress, medium files)\nSee also: The 'CompressDemo' command" ); +idCVar idDemoFile::com_preloadDemos( "com_preloadDemos", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_ARCHIVE, "Load the whole demo in to RAM before running it" ); + +#define DEMO_MAGIC GAME_NAME " RDEMO" + +/* +================ +idDemoFile::idDemoFile +================ +*/ +idDemoFile::idDemoFile() { + f = NULL; + fLog = NULL; + log = false; + fileImage = NULL; + compressor = NULL; + writing = false; +} + +/* +================ +idDemoFile::~idDemoFile +================ +*/ +idDemoFile::~idDemoFile() { + Close(); +} + +/* +================ +idDemoFile::AllocCompressor +================ +*/ +idCompressor *idDemoFile::AllocCompressor( int type ) { + switch ( type ) { + case 0: return idCompressor::AllocNoCompression(); + default: + case 1: return idCompressor::AllocLZW(); + case 2: return idCompressor::AllocLZSS(); + case 3: return idCompressor::AllocHuffman(); + } +} + +/* +================ +idDemoFile::OpenForReading +================ +*/ +bool idDemoFile::OpenForReading( const char *fileName ) { + static const int magicLen = sizeof(DEMO_MAGIC) / sizeof(DEMO_MAGIC[0]); + char magicBuffer[magicLen]; + int compression; + int fileLength; + + Close(); + + f = fileSystem->OpenFileRead( fileName ); + if ( !f ) { + return false; + } + + fileLength = f->Length(); + + if ( com_preloadDemos.GetBool() ) { + fileImage = (byte *)Mem_Alloc( fileLength, TAG_CRAP ); + f->Read( fileImage, fileLength ); + fileSystem->CloseFile( f ); + f = new (TAG_SYSTEM) idFile_Memory( va( "preloaded(%s)", fileName ), (const char *)fileImage, fileLength ); + } + + if ( com_logDemos.GetBool() ) { + fLog = fileSystem->OpenFileWrite( "demoread.log" ); + } + + writing = false; + + f->Read(magicBuffer, magicLen); + if ( memcmp(magicBuffer, DEMO_MAGIC, magicLen) == 0 ) { + f->ReadInt( compression ); + } else { + // Ideally we would error out if the magic string isn't there, + // but for backwards compatibility we are going to assume it's just an uncompressed demo file + compression = 0; + f->Rewind(); + } + + compressor = AllocCompressor( compression ); + compressor->Init( f, false, 8 ); + + return true; +} + +/* +================ +idDemoFile::SetLog +================ +*/ +void idDemoFile::SetLog(bool b, const char *p) { + log = b; + if (p) { + logStr = p; + } +} + +/* +================ +idDemoFile::Log +================ +*/ +void idDemoFile::Log(const char *p) { + if ( fLog && p && *p ) { + fLog->Write( p, strlen(p) ); + } +} + +/* +================ +idDemoFile::OpenForWriting +================ +*/ +bool idDemoFile::OpenForWriting( const char *fileName ) { + Close(); + + f = fileSystem->OpenFileWrite( fileName ); + if ( f == NULL ) { + return false; + } + + if ( com_logDemos.GetBool() ) { + fLog = fileSystem->OpenFileWrite( "demowrite.log" ); + } + + writing = true; + + f->Write(DEMO_MAGIC, sizeof(DEMO_MAGIC)); + f->WriteInt( com_compressDemos.GetInteger() ); + f->Flush(); + + compressor = AllocCompressor( com_compressDemos.GetInteger() ); + compressor->Init( f, true, 8 ); + + return true; +} + +/* +================ +idDemoFile::Close +================ +*/ +void idDemoFile::Close() { + if ( writing && compressor ) { + compressor->FinishCompress(); + } + + if ( f ) { + fileSystem->CloseFile( f ); + f = NULL; + } + if ( fLog ) { + fileSystem->CloseFile( fLog ); + fLog = NULL; + } + if ( fileImage ) { + Mem_Free( fileImage ); + fileImage = NULL; + } + if ( compressor ) { + delete compressor; + compressor = NULL; + } + + demoStrings.DeleteContents( true ); +} + +/* +================ +idDemoFile::ReadHashString +================ +*/ +const char *idDemoFile::ReadHashString() { + int index; + + if ( log && fLog ) { + const char *text = va( "%s > Reading hash string\n", logStr.c_str() ); + fLog->Write( text, strlen( text ) ); + } + + ReadInt( index ); + + if ( index == -1 ) { + // read a new string for the table + idStr *str = new (TAG_SYSTEM) idStr; + + idStr data; + ReadString( data ); + *str = data; + + demoStrings.Append( str ); + + return *str; + } + + if ( index < -1 || index >= demoStrings.Num() ) { + Close(); + common->Error( "demo hash index out of range" ); + } + + return demoStrings[index]->c_str(); +} + +/* +================ +idDemoFile::WriteHashString +================ +*/ +void idDemoFile::WriteHashString( const char *str ) { + if ( log && fLog ) { + const char *text = va( "%s > Writing hash string\n", logStr.c_str() ); + fLog->Write( text, strlen( text ) ); + } + // see if it is already in the has table + for ( int i = 0 ; i < demoStrings.Num() ; i++ ) { + if ( !strcmp( demoStrings[i]->c_str(), str ) ) { + WriteInt( i ); + return; + } + } + + // add it to our table and the demo table + idStr *copy = new (TAG_SYSTEM) idStr( str ); +//common->Printf( "hash:%i = %s\n", demoStrings.Num(), str ); + demoStrings.Append( copy ); + int cmd = -1; + WriteInt( cmd ); + WriteString( str ); +} + +/* +================ +idDemoFile::ReadDict +================ +*/ +void idDemoFile::ReadDict( idDict &dict ) { + int i, c; + idStr key, val; + + dict.Clear(); + ReadInt( c ); + for ( i = 0; i < c; i++ ) { + key = ReadHashString(); + val = ReadHashString(); + dict.Set( key, val ); + } +} + +/* +================ +idDemoFile::WriteDict +================ +*/ +void idDemoFile::WriteDict( const idDict &dict ) { + int i, c; + + c = dict.GetNumKeyVals(); + WriteInt( c ); + for ( i = 0; i < c; i++ ) { + WriteHashString( dict.GetKeyVal( i )->GetKey() ); + WriteHashString( dict.GetKeyVal( i )->GetValue() ); + } +} + +/* + ================ + idDemoFile::Read + ================ + */ +int idDemoFile::Read( void *buffer, int len ) { + int read = compressor->Read( buffer, len ); + if ( read == 0 && len >= 4 ) { + *(demoSystem_t *)buffer = DS_FINISHED; + } + return read; +} + +/* + ================ + idDemoFile::Write + ================ + */ +int idDemoFile::Write( const void *buffer, int len ) { + return compressor->Write( buffer, len ); +} + + + + diff --git a/neo/framework/DemoFile.h b/neo/framework/DemoFile.h new file mode 100644 index 00000000..162b86ea --- /dev/null +++ b/neo/framework/DemoFile.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DEMOFILE_H__ +#define __DEMOFILE_H__ + +/* +=============================================================================== + + Demo file + +=============================================================================== +*/ + +typedef enum { + DS_FINISHED, + DS_RENDER, + DS_SOUND, + DS_VERSION +} demoSystem_t; + +class idDemoFile : public idFile { +public: + idDemoFile(); + ~idDemoFile(); + + const char * GetName() { return (f?f->GetName():""); } + const char * GetFullPath() { return (f?f->GetFullPath():""); } + + void SetLog( bool b, const char *p ); + void Log( const char *p ); + bool OpenForReading( const char *fileName ); + bool OpenForWriting( const char *fileName ); + void Close(); + + const char * ReadHashString(); + void WriteHashString( const char *str ); + + void ReadDict( idDict &dict ); + void WriteDict( const idDict &dict ); + + int Read( void *buffer, int len ); + int Write( const void *buffer, int len ); + +private: + static idCompressor *AllocCompressor( int type ); + + bool writing; + byte * fileImage; + idFile * f; + idCompressor * compressor; + + idList demoStrings; + idFile * fLog; + bool log; + idStr logStr; + + static idCVar com_logDemos; + static idCVar com_compressDemos; + static idCVar com_preloadDemos; +}; + +#endif /* !__DEMOFILE_H__ */ diff --git a/neo/framework/EditField.cpp b/neo/framework/EditField.cpp new file mode 100644 index 00000000..47ddbf72 --- /dev/null +++ b/neo/framework/EditField.cpp @@ -0,0 +1,604 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +static autoComplete_t globalAutoComplete; + +/* +=============== +FindMatches +=============== +*/ +static void FindMatches( const char *s ) { + int i; + + if ( idStr::Icmpn( s, globalAutoComplete.completionString, strlen( globalAutoComplete.completionString ) ) != 0 ) { + return; + } + globalAutoComplete.matchCount++; + if ( globalAutoComplete.matchCount == 1 ) { + idStr::Copynz( globalAutoComplete.currentMatch, s, sizeof( globalAutoComplete.currentMatch ) ); + return; + } + + // cut currentMatch to the amount common with s + for ( i = 0; s[i]; i++ ) { + if ( tolower( globalAutoComplete.currentMatch[i] ) != tolower( s[i] ) ) { + globalAutoComplete.currentMatch[i] = 0; + break; + } + } + globalAutoComplete.currentMatch[i] = 0; +} + +/* +=============== +FindIndexMatch +=============== +*/ +static void FindIndexMatch( const char *s ) { + + if ( idStr::Icmpn( s, globalAutoComplete.completionString, strlen( globalAutoComplete.completionString ) ) != 0 ) { + return; + } + + if( globalAutoComplete.findMatchIndex == globalAutoComplete.matchIndex ) { + idStr::Copynz( globalAutoComplete.currentMatch, s, sizeof( globalAutoComplete.currentMatch ) ); + } + + globalAutoComplete.findMatchIndex++; +} + +/* +=============== +PrintMatches +=============== +*/ +static void PrintMatches( const char *s ) { + if ( idStr::Icmpn( s, globalAutoComplete.currentMatch, strlen( globalAutoComplete.currentMatch ) ) == 0 ) { + common->Printf( " %s\n", s ); + } +} + +/* +=============== +PrintCvarMatches +=============== +*/ +static void PrintCvarMatches( const char *s ) { + if ( idStr::Icmpn( s, globalAutoComplete.currentMatch, strlen( globalAutoComplete.currentMatch ) ) == 0 ) { + common->Printf( " %s" S_COLOR_WHITE " = \"%s\"\n", s, cvarSystem->GetCVarString( s ) ); + } +} + +/* +=============== +idEditField::idEditField +=============== +*/ +idEditField::idEditField() { + widthInChars = 0; + Clear(); +} + +/* +=============== +idEditField::~idEditField +=============== +*/ +idEditField::~idEditField() { +} + +/* +=============== +idEditField::Clear +=============== +*/ +void idEditField::Clear() { + buffer[0] = 0; + cursor = 0; + scroll = 0; + autoComplete.length = 0; + autoComplete.valid = false; +} + +/* +=============== +idEditField::SetWidthInChars +=============== +*/ +void idEditField::SetWidthInChars( int w ) { + assert( w <= MAX_EDIT_LINE ); + widthInChars = w; +} + +/* +=============== +idEditField::SetCursor +=============== +*/ +void idEditField::SetCursor( int c ) { + assert( c <= MAX_EDIT_LINE ); + cursor = c; +} + +/* +=============== +idEditField::GetCursor +=============== +*/ +int idEditField::GetCursor() const { + return cursor; +} + +/* +=============== +idEditField::ClearAutoComplete +=============== +*/ +void idEditField::ClearAutoComplete() { + if ( autoComplete.length > 0 && autoComplete.length <= (int) strlen( buffer ) ) { + buffer[autoComplete.length] = '\0'; + if ( cursor > autoComplete.length ) { + cursor = autoComplete.length; + } + } + autoComplete.length = 0; + autoComplete.valid = false; +} + +/* +=============== +idEditField::GetAutoCompleteLength +=============== +*/ +int idEditField::GetAutoCompleteLength() const { + return autoComplete.length; +} + +/* +=============== +idEditField::AutoComplete +=============== +*/ +void idEditField::AutoComplete() { + char completionArgString[MAX_EDIT_LINE]; + idCmdArgs args; + + if ( !autoComplete.valid ) { + args.TokenizeString( buffer, false ); + idStr::Copynz( autoComplete.completionString, args.Argv( 0 ), sizeof( autoComplete.completionString ) ); + idStr::Copynz( completionArgString, args.Args(), sizeof( completionArgString ) ); + autoComplete.matchCount = 0; + autoComplete.matchIndex = 0; + autoComplete.currentMatch[0] = 0; + + if ( strlen( autoComplete.completionString ) == 0 ) { + return; + } + + globalAutoComplete = autoComplete; + + cmdSystem->CommandCompletion( FindMatches ); + cvarSystem->CommandCompletion( FindMatches ); + + autoComplete = globalAutoComplete; + + if ( autoComplete.matchCount == 0 ) { + return; // no matches + } + + // when there's only one match or there's an argument + if ( autoComplete.matchCount == 1 || completionArgString[0] != '\0' ) { + + /// try completing arguments + idStr::Append( autoComplete.completionString, sizeof( autoComplete.completionString ), " " ); + idStr::Append( autoComplete.completionString, sizeof( autoComplete.completionString ), completionArgString ); + autoComplete.matchCount = 0; + + globalAutoComplete = autoComplete; + + cmdSystem->ArgCompletion( autoComplete.completionString, FindMatches ); + cvarSystem->ArgCompletion( autoComplete.completionString, FindMatches ); + + autoComplete = globalAutoComplete; + + idStr::snPrintf( buffer, sizeof( buffer ), "%s", autoComplete.currentMatch ); + + if ( autoComplete.matchCount == 0 ) { + // no argument matches + idStr::Append( buffer, sizeof( buffer ), " " ); + idStr::Append( buffer, sizeof( buffer ), completionArgString ); + SetCursor( strlen( buffer ) ); + return; + } + } else { + + // multiple matches, complete to shortest + idStr::snPrintf( buffer, sizeof( buffer ), "%s", autoComplete.currentMatch ); + if ( strlen( completionArgString ) ) { + idStr::Append( buffer, sizeof( buffer ), " " ); + idStr::Append( buffer, sizeof( buffer ), completionArgString ); + } + } + + autoComplete.length = strlen( buffer ); + autoComplete.valid = ( autoComplete.matchCount != 1 ); + SetCursor( autoComplete.length ); + + common->Printf( "]%s\n", buffer ); + + // run through again, printing matches + globalAutoComplete = autoComplete; + + cmdSystem->CommandCompletion( PrintMatches ); + cmdSystem->ArgCompletion( autoComplete.completionString, PrintMatches ); + cvarSystem->CommandCompletion( PrintCvarMatches ); + cvarSystem->ArgCompletion( autoComplete.completionString, PrintMatches ); + + } else if ( autoComplete.matchCount != 1 ) { + + // get the next match and show instead + autoComplete.matchIndex++; + if ( autoComplete.matchIndex == autoComplete.matchCount ) { + autoComplete.matchIndex = 0; + } + autoComplete.findMatchIndex = 0; + + globalAutoComplete = autoComplete; + + cmdSystem->CommandCompletion( FindIndexMatch ); + cmdSystem->ArgCompletion( autoComplete.completionString, FindIndexMatch ); + cvarSystem->CommandCompletion( FindIndexMatch ); + cvarSystem->ArgCompletion( autoComplete.completionString, FindIndexMatch ); + + autoComplete = globalAutoComplete; + + // and print it + idStr::snPrintf( buffer, sizeof( buffer ), autoComplete.currentMatch ); + if ( autoComplete.length > (int)strlen( buffer ) ) { + autoComplete.length = strlen( buffer ); + } + SetCursor( autoComplete.length ); + } +} + +/* +=============== +idEditField::CharEvent +=============== +*/ +void idEditField::CharEvent( int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Paste(); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Clear(); + return; + } + + len = strlen( buffer ); + + if ( ch == 'h' - 'a' + 1 || ch == K_BACKSPACE ) { // ctrl-h is backspace + if ( cursor > 0 ) { + memmove( buffer + cursor - 1, buffer + cursor, len + 1 - cursor ); + cursor--; + if ( cursor < scroll ) { + scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + cursor = 0; + scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + cursor = len; + scroll = cursor - widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( idKeyInput::GetOverstrikeMode() ) { + if ( cursor == MAX_EDIT_LINE - 1 ) { + return; + } + buffer[cursor] = ch; + cursor++; + } else { // insert mode + if ( len == MAX_EDIT_LINE - 1 ) { + return; // all full + } + memmove( buffer + cursor + 1, buffer + cursor, len + 1 - cursor ); + buffer[cursor] = ch; + cursor++; + } + + + if ( cursor >= widthInChars ) { + scroll++; + } + + if ( cursor == len + 1 ) { + buffer[cursor] = 0; + } +} + +/* +=============== +idEditField::KeyDownEvent +=============== +*/ +void idEditField::KeyDownEvent( int key ) { + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_0 ) ) && ( idKeyInput::IsDown( K_LSHIFT ) || idKeyInput::IsDown( K_RSHIFT ) ) ) { + ClearAutoComplete(); + Paste(); + return; + } + + len = strlen( buffer ); + + if ( key == K_DEL ) { + if ( autoComplete.length ) { + ClearAutoComplete(); + } else if ( cursor < len ) { + memmove( buffer + cursor, buffer + cursor + 1, len - cursor ); + } + return; + } + + if ( key == K_RIGHTARROW ) { + if ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) { + // skip to next word + while( ( cursor < len ) && ( buffer[ cursor ] != ' ' ) ) { + cursor++; + } + + while( ( cursor < len ) && ( buffer[ cursor ] == ' ' ) ) { + cursor++; + } + } else { + cursor++; + } + + if ( cursor > len ) { + cursor = len; + } + + if ( cursor >= scroll + widthInChars ) { + scroll = cursor - widthInChars + 1; + } + + if ( autoComplete.length > 0 ) { + autoComplete.length = cursor; + } + return; + } + + if ( key == K_LEFTARROW ) { + if ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) { + // skip to previous word + while( ( cursor > 0 ) && ( buffer[ cursor - 1 ] == ' ' ) ) { + cursor--; + } + + while( ( cursor > 0 ) && ( buffer[ cursor - 1 ] != ' ' ) ) { + cursor--; + } + } else { + cursor--; + } + + if ( cursor < 0 ) { + cursor = 0; + } + if ( cursor < scroll ) { + scroll = cursor; + } + + if ( autoComplete.length ) { + autoComplete.length = cursor; + } + return; + } + + if ( key == K_HOME || ( key == K_A && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) { + cursor = 0; + scroll = 0; + if ( autoComplete.length ) { + autoComplete.length = cursor; + autoComplete.valid = false; + } + return; + } + + if ( key == K_END || ( key == K_E && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) ) { + cursor = len; + if ( cursor >= scroll + widthInChars ) { + scroll = cursor - widthInChars + 1; + } + if ( autoComplete.length ) { + autoComplete.length = cursor; + autoComplete.valid = false; + } + return; + } + + if ( key == K_INS ) { + idKeyInput::SetOverstrikeMode( !idKeyInput::GetOverstrikeMode() ); + return; + } + + // clear autocompletion buffer on normal key input + if ( key != K_CAPSLOCK && key != K_LALT && key != K_LCTRL && key != K_LSHIFT && key != K_RALT && key != K_RCTRL && key != K_RSHIFT ) { + ClearAutoComplete(); + } +} + +/* +=============== +idEditField::Paste +=============== +*/ +void idEditField::Paste() { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0; i < pasteLen; i++ ) { + CharEvent( cbd[i] ); + } + + Mem_Free( cbd ); +} + +/* +=============== +idEditField::GetBuffer +=============== +*/ +char *idEditField::GetBuffer() { + return buffer; +} + +/* +=============== +idEditField::SetBuffer +=============== +*/ +void idEditField::SetBuffer( const char *buf ) { + Clear(); + idStr::Copynz( buffer, buf, sizeof( buffer ) ); + SetCursor( strlen( buffer ) ); +} + +/* +=============== +idEditField::Draw +=============== +*/ +void idEditField::Draw( int x, int y, int width, bool showCursor ) { + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_EDIT_LINE]; + int size; + + size = SMALLCHAR_WIDTH; + + drawLen = widthInChars; + len = strlen( buffer ) + 1; + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( scroll + drawLen > len ) { + scroll = len - drawLen; + if ( scroll < 0 ) { + scroll = 0; + } + } + prestep = scroll; + + // Skip color code + if ( idStr::IsColor( buffer + prestep ) ) { + prestep += 2; + } + if ( prestep > 0 && idStr::IsColor( buffer + prestep - 1 ) ) { + prestep++; + } + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_EDIT_LINE ) { + common->Error( "drawLen >= MAX_EDIT_LINE" ); + } + + memcpy( str, buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + renderSystem->DrawSmallStringExt( x, y, str, colorWhite, false ); + + // draw the cursor + if ( !showCursor ) { + return; + } + + if ( (int)( idLib::frameNumber >> 4 ) & 1 ) { + return; // off blink + } + + if ( idKeyInput::GetOverstrikeMode() ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + // Move the cursor back to account for color codes + for ( int i = 0; iDrawSmallChar( x + ( cursor - prestep ) * size, y, cursorChar ); +} diff --git a/neo/framework/EditField.h b/neo/framework/EditField.h new file mode 100644 index 00000000..7a64cce7 --- /dev/null +++ b/neo/framework/EditField.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __EDITFIELD_H__ +#define __EDITFIELD_H__ + +/* +=============================================================================== + + Edit field + +=============================================================================== +*/ + +const int MAX_EDIT_LINE = 256; + +typedef struct autoComplete_s { + bool valid; + int length; + char completionString[MAX_EDIT_LINE]; + char currentMatch[MAX_EDIT_LINE]; + int matchCount; + int matchIndex; + int findMatchIndex; +} autoComplete_t; + +class idEditField { +public: + idEditField(); + ~idEditField(); + + void Clear(); + void SetWidthInChars( int w ); + void SetCursor( int c ); + int GetCursor() const; + void ClearAutoComplete(); + int GetAutoCompleteLength() const; + void AutoComplete(); + void CharEvent( int c ); + void KeyDownEvent( int key ); + void Paste(); + char * GetBuffer(); + void Draw( int x, int y, int width, bool showCursor ); + void SetBuffer( const char *buffer ); + +private: + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; + autoComplete_t autoComplete; +}; + +#endif /* !__EDITFIELD_H__ */ diff --git a/neo/framework/EventLoop.cpp b/neo/framework/EventLoop.cpp new file mode 100644 index 00000000..59d3e731 --- /dev/null +++ b/neo/framework/EventLoop.cpp @@ -0,0 +1,274 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar idEventLoop::com_journal( "com_journal", "0", CVAR_INIT|CVAR_SYSTEM, "1 = record journal, 2 = play back journal", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + +idEventLoop eventLoopLocal; +idEventLoop *eventLoop = &eventLoopLocal; + + +/* +================= +idEventLoop::idEventLoop +================= +*/ +idEventLoop::idEventLoop() { + com_journalFile = NULL; + com_journalDataFile = NULL; + initialTimeOffset = 0; +} + +/* +================= +idEventLoop::~idEventLoop +================= +*/ +idEventLoop::~idEventLoop() { +} + +/* +================= +idEventLoop::GetRealEvent +================= +*/ +sysEvent_t idEventLoop::GetRealEvent() { + int r; + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal.GetInteger() == 2 ) { + r = com_journalFile->Read( &ev, sizeof(ev) ); + if ( r != sizeof(ev) ) { + common->FatalError( "Error reading from journal file" ); + } + if ( ev.evPtrLength ) { + ev.evPtr = Mem_ClearedAlloc( ev.evPtrLength, TAG_EVENTS ); + r = com_journalFile->Read( ev.evPtr, ev.evPtrLength ); + if ( r != ev.evPtrLength ) { + common->FatalError( "Error reading from journal file" ); + } + } + } else { + ev = Sys_GetEvent(); + + // write the journal value out if needed + if ( com_journal.GetInteger() == 1 ) { + r = com_journalFile->Write( &ev, sizeof(ev) ); + if ( r != sizeof(ev) ) { + common->FatalError( "Error writing to journal file" ); + } + if ( ev.evPtrLength ) { + r = com_journalFile->Write( ev.evPtr, ev.evPtrLength ); + if ( r != ev.evPtrLength ) { + common->FatalError( "Error writing to journal file" ); + } + } + } + } + + return ev; +} + +/* +================= +idEventLoop::PushEvent +================= +*/ +void idEventLoop::PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static bool printedWarning; + + ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = true; + common->Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Mem_Free( ev->evPtr ); + } + com_pushedEventsTail++; + } else { + printedWarning = false; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +idEventLoop::GetEvent +================= +*/ +sysEvent_t idEventLoop::GetEvent() { + if ( com_pushedEventsHead > com_pushedEventsTail ) { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } + return GetRealEvent(); +} + +/* +================= +idEventLoop::ProcessEvent +================= +*/ +void idEventLoop::ProcessEvent( sysEvent_t ev ) { + // track key up / down states + if ( ev.evType == SE_KEY ) { + idKeyInput::PreliminaryKeyEvent( ev.evValue, ( ev.evValue2 != 0 ) ); + } + + if ( ev.evType == SE_CONSOLE ) { + // from a text console outside the game window + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, (char *)ev.evPtr ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); + } else { + common->ProcessEvent( &ev ); + } + + // free any block data + if ( ev.evPtr ) { + Mem_Free( ev.evPtr ); + } +} + +/* +=============== +idEventLoop::RunEventLoop +=============== +*/ +int idEventLoop::RunEventLoop( bool commandExecution ) { + sysEvent_t ev; + + while ( 1 ) { + + if ( commandExecution ) { + // execute any bound commands before processing another event + cmdSystem->ExecuteCommandBuffer(); + } + + ev = GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) { + return 0; + } + ProcessEvent( ev ); + } + + return 0; // never reached +} + +/* +============= +idEventLoop::Init +============= +*/ +void idEventLoop::Init() { + + initialTimeOffset = Sys_Milliseconds(); + + common->StartupVariable( "journal" ); + + if ( com_journal.GetInteger() == 1 ) { + common->Printf( "Journaling events\n" ); + com_journalFile = fileSystem->OpenFileWrite( "journal.dat" ); + com_journalDataFile = fileSystem->OpenFileWrite( "journaldata.dat" ); + } else if ( com_journal.GetInteger() == 2 ) { + common->Printf( "Replaying journaled events\n" ); + com_journalFile = fileSystem->OpenFileRead( "journal.dat" ); + com_journalDataFile = fileSystem->OpenFileRead( "journaldata.dat" ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + com_journal.SetInteger( 0 ); + com_journalFile = 0; + com_journalDataFile = 0; + common->Printf( "Couldn't open journal files\n" ); + } +} + +/* +============= +idEventLoop::Shutdown +============= +*/ +void idEventLoop::Shutdown() { + if ( com_journalFile ) { + fileSystem->CloseFile( com_journalFile ); + com_journalFile = NULL; + } + if ( com_journalDataFile ) { + fileSystem->CloseFile( com_journalDataFile ); + com_journalDataFile = NULL; + } +} + +/* +================ +idEventLoop::Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int idEventLoop::Milliseconds() { +#if 1 // FIXME! + return Sys_Milliseconds() - initialTimeOffset; +#else + sysEvent_t ev; + + // get events and push them until we get a null event with the current time + do { + + ev = Com_GetRealEvent(); + if ( ev.evType != SE_NONE ) { + Com_PushEvent( &ev ); + } + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +#endif +} + +/* +================ +idEventLoop::JournalLevel +================ +*/ +int idEventLoop::JournalLevel() const { + return com_journal.GetInteger(); +} diff --git a/neo/framework/EventLoop.h b/neo/framework/EventLoop.h new file mode 100644 index 00000000..631c49c5 --- /dev/null +++ b/neo/framework/EventLoop.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __EVENTLOOP_H__ +#define __EVENTLOOP_H__ + +/* +=============================================================================== + + The event loop receives events from the system and dispatches them to + the various parts of the engine. The event loop also handles journaling. + The file system copies .cfg files to the journaled file. + +=============================================================================== +*/ + +const int MAX_PUSHED_EVENTS = 64; + +class idEventLoop { +public: + idEventLoop(); + ~idEventLoop(); + + void Init(); + + // Closes the journal file if needed. + void Shutdown(); + + // It is possible to get an event at the beginning of a frame that + // has a time stamp lower than the last event from the previous frame. + sysEvent_t GetEvent(); + + // Dispatches all pending events and returns the current time. + int RunEventLoop( bool commandExecution = true ); + + // Gets the current time in a way that will be journaled properly, + // as opposed to Sys_Milliseconds(), which always reads a real timer. + int Milliseconds(); + + // Returns the journal level, 1 = record, 2 = play back. + int JournalLevel() const; + + // Journal file. + idFile * com_journalFile; + idFile * com_journalDataFile; + +private: + // all events will have this subtracted from their time + int initialTimeOffset; + + int com_pushedEventsHead, com_pushedEventsTail; + sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + + static idCVar com_journal; + + sysEvent_t GetRealEvent(); + void ProcessEvent( sysEvent_t ev ); + void PushEvent( sysEvent_t *event ); +}; + +extern idEventLoop *eventLoop; + +#endif /* !__EVENTLOOP_H__ */ diff --git a/neo/framework/File.cpp b/neo/framework/File.cpp new file mode 100644 index 00000000..4165a19a --- /dev/null +++ b/neo/framework/File.cpp @@ -0,0 +1,1808 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Unzip.h" + +/* +================= +FS_WriteFloatString +================= +*/ +int FS_WriteFloatString( char *buf, const char *fmt, va_list argPtr ) { + long i; + unsigned long u; + double f; + char *str; + int index; + idStr tmp, format; + + index = 0; + + while( *fmt ) { + switch( *fmt ) { + case '%': + format = ""; + format += *fmt++; + while ( (*fmt >= '0' && *fmt <= '9') || + *fmt == '.' || *fmt == '-' || *fmt == '+' || *fmt == '#') { + format += *fmt++; + } + format += *fmt; + switch( *fmt ) { + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + f = va_arg( argPtr, double ); + if ( format.Length() <= 2 ) { + // high precision floating point number without trailing zeros + sprintf( tmp, "%1.10f", f ); + tmp.StripTrailing( '0' ); + tmp.StripTrailing( '.' ); + index += sprintf( buf+index, "%s", tmp.c_str() ); + } + else { + index += sprintf( buf+index, format.c_str(), f ); + } + break; + case 'd': + case 'i': + i = va_arg( argPtr, long ); + index += sprintf( buf+index, format.c_str(), i ); + break; + case 'u': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'o': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'x': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'X': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'c': + i = va_arg( argPtr, long ); + index += sprintf( buf+index, format.c_str(), (char) i ); + break; + case 's': + str = va_arg( argPtr, char * ); + index += sprintf( buf+index, format.c_str(), str ); + break; + case '%': + index += sprintf( buf+index, format.c_str() ); //-V618 + break; + default: + common->Error( "FS_WriteFloatString: invalid format %s", format.c_str() ); + break; + } + fmt++; + break; + case '\\': + fmt++; + switch( *fmt ) { + case 't': + index += sprintf( buf+index, "\t" ); + break; + case 'v': + index += sprintf( buf+index, "\v" ); + break; + case 'n': + index += sprintf( buf+index, "\n" ); + break; + case '\\': + index += sprintf( buf+index, "\\" ); + break; + default: + common->Error( "FS_WriteFloatString: unknown escape character \'%c\'", *fmt ); + break; + } + fmt++; + break; + default: + index += sprintf( buf+index, "%c", *fmt ); + fmt++; + break; + } + } + + return index; +} + +/* +================================================================================= + +idFile + +================================================================================= +*/ + +/* +================= +idFile::GetName +================= +*/ +const char *idFile::GetName() const { + return ""; +} + +/* +================= +idFile::GetFullPath +================= +*/ +const char *idFile::GetFullPath() const { + return ""; +} + +/* +================= +idFile::Read +================= +*/ +int idFile::Read( void *buffer, int len ) { + common->FatalError( "idFile::Read: cannot read from idFile" ); + return 0; +} + +/* +================= +idFile::Write +================= +*/ +int idFile::Write( const void *buffer, int len ) { + common->FatalError( "idFile::Write: cannot write to idFile" ); + return 0; +} + +/* +================= +idFile::Length +================= +*/ +int idFile::Length() const { + return 0; +} + +/* +================= +idFile::Timestamp +================= +*/ +ID_TIME_T idFile::Timestamp() const { + return 0; +} + +/* +================= +idFile::Tell +================= +*/ +int idFile::Tell() const { + return 0; +} + +/* +================= +idFile::ForceFlush +================= +*/ +void idFile::ForceFlush() { +} + +/* +================= +idFile::Flush +================= +*/ +void idFile::Flush() { +} + +/* +================= +idFile::Seek +================= +*/ +int idFile::Seek( long offset, fsOrigin_t origin ) { + return -1; +} + +/* +================= +idFile::Rewind +================= +*/ +void idFile::Rewind() { + Seek( 0, FS_SEEK_SET ); +} + +/* +================= +idFile::Printf +================= +*/ +int idFile::Printf( const char *fmt, ... ) { + char buf[MAX_PRINT_MSG]; + int length; + va_list argptr; + + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf, MAX_PRINT_MSG-1, fmt, argptr ); + va_end( argptr ); + + // so notepad formats the lines correctly + idStr work( buf ); + work.Replace( "\n", "\r\n" ); + + return Write( work.c_str(), work.Length() ); +} + +/* +================= +idFile::VPrintf +================= +*/ +int idFile::VPrintf( const char *fmt, va_list args ) { + char buf[MAX_PRINT_MSG]; + int length; + + length = idStr::vsnPrintf( buf, MAX_PRINT_MSG-1, fmt, args ); + return Write( buf, length ); +} + +/* +================= +idFile::WriteFloatString +================= +*/ +int idFile::WriteFloatString( const char *fmt, ... ) { + char buf[MAX_PRINT_MSG]; + int len; + va_list argPtr; + + va_start( argPtr, fmt ); + len = FS_WriteFloatString( buf, fmt, argPtr ); + va_end( argPtr ); + + return Write( buf, len ); +} + +/* + ================= + idFile::ReadInt + ================= + */ +int idFile::ReadInt( int &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleLong(value); + return result; +} + +/* + ================= + idFile::ReadUnsignedInt + ================= + */ +int idFile::ReadUnsignedInt( unsigned int &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleLong(value); + return result; +} + +/* + ================= + idFile::ReadShort + ================= + */ +int idFile::ReadShort( short &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleShort(value); + return result; +} + +/* + ================= + idFile::ReadUnsignedShort + ================= + */ +int idFile::ReadUnsignedShort( unsigned short &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleShort(value); + return result; +} + +/* + ================= + idFile::ReadChar + ================= + */ +int idFile::ReadChar( char &value ) { + return Read( &value, sizeof( value ) ); +} + +/* + ================= + idFile::ReadUnsignedChar + ================= + */ +int idFile::ReadUnsignedChar( unsigned char &value ) { + return Read( &value, sizeof( value ) ); +} + +/* + ================= + idFile::ReadFloat + ================= + */ +int idFile::ReadFloat( float &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleFloat(value); + return result; +} + +/* + ================= + idFile::ReadBool + ================= + */ +int idFile::ReadBool( bool &value ) { + unsigned char c; + int result = ReadUnsignedChar( c ); + value = c ? true : false; + return result; +} + +/* + ================= + idFile::ReadString + ================= + */ +int idFile::ReadString( idStr &string ) { + int len; + int result = 0; + + ReadInt( len ); + if ( len >= 0 ) { + string.Fill( ' ', len ); + result = Read( &string[ 0 ], len ); + } + return result; +} + +/* + ================= + idFile::ReadVec2 + ================= + */ +int idFile::ReadVec2( idVec2 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadVec3 + ================= + */ +int idFile::ReadVec3( idVec3 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadVec4 + ================= + */ +int idFile::ReadVec4( idVec4 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadVec6 + ================= + */ +int idFile::ReadVec6( idVec6 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadMat3 + ================= + */ +int idFile::ReadMat3( idMat3 &mat ) { + int result = Read( &mat, sizeof( mat ) ); + LittleRevBytes( &mat, sizeof(float), sizeof(mat)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::WriteInt + ================= + */ +int idFile::WriteInt( const int value ) { + int v = LittleLong(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteUnsignedInt + ================= + */ +int idFile::WriteUnsignedInt( const unsigned int value ) { + unsigned int v = LittleLong(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteShort + ================= + */ +int idFile::WriteShort( const short value ) { + short v = LittleShort(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteUnsignedShort + ================= + */ +int idFile::WriteUnsignedShort( const unsigned short value ) { + unsigned short v = LittleShort(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteChar + ================= + */ +int idFile::WriteChar( const char value ) { + return Write( &value, sizeof( value ) ); +} + +/* + ================= + idFile::WriteUnsignedChar + ================= + */ +int idFile::WriteUnsignedChar( const unsigned char value ) { + return Write( &value, sizeof( value ) ); +} + +/* + ================= + idFile::WriteFloat + ================= + */ +int idFile::WriteFloat( const float value ) { + float v = LittleFloat(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteBool + ================= + */ +int idFile::WriteBool( const bool value ) { + unsigned char c = value; + return WriteUnsignedChar( c ); +} + +/* + ================= + idFile::WriteString + ================= + */ +int idFile::WriteString( const char *value ) { + int len = strlen( value ); + WriteInt( len ); + return Write( value, len ); +} + +/* + ================= + idFile::WriteVec2 + ================= + */ +int idFile::WriteVec2( const idVec2 &vec ) { + idVec2 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteVec3 + ================= + */ +int idFile::WriteVec3( const idVec3 &vec ) { + idVec3 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteVec4 + ================= + */ +int idFile::WriteVec4( const idVec4 &vec ) { + idVec4 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteVec6 + ================= + */ +int idFile::WriteVec6( const idVec6 &vec ) { + idVec6 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteMat3 + ================= + */ +int idFile::WriteMat3( const idMat3 &mat ) { + idMat3 v = mat; + LittleRevBytes(&v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* +================================================================================= + +idFile_Memory + +================================================================================= +*/ + + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory() { + name = "*unknown*"; + maxSize = 0; + fileSize = 0; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_WRITE ); + filePtr = NULL; + curPtr = NULL; +} + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( const char *name ) { + this->name = name; + maxSize = 0; + fileSize = 0; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_WRITE ); + filePtr = NULL; + curPtr = NULL; +} + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( const char *name, char *data, int length ) { + this->name = name; + maxSize = length; + fileSize = 0; + allocated = length; + granularity = 16384; + + mode = ( 1 << FS_WRITE ); + filePtr = data; + curPtr = data; +} + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( const char *name, const char *data, int length ) { + this->name = name; + maxSize = 0; + fileSize = length; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_READ ); + filePtr = const_cast(data); + curPtr = const_cast(data); +} + +/* +================= +idFile_Memory::TakeDataOwnership + +this also makes the file read only +================= +*/ +void idFile_Memory::TakeDataOwnership() { + if ( filePtr != NULL && fileSize > 0 ) { + maxSize = 0; + mode = ( 1 << FS_READ ); + allocated = fileSize; + } +} + +/* +================= +idFile_Memory::~idFile_Memory +================= +*/ +idFile_Memory::~idFile_Memory() { + if ( filePtr && allocated > 0 && maxSize == 0 ) { + Mem_Free( filePtr ); + } +} + +/* +================= +idFile_Memory::Read +================= +*/ +int idFile_Memory::Read( void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_READ ) ) ) { + common->FatalError( "idFile_Memory::Read: %s not opened in read mode", name.c_str() ); + return 0; + } + + if ( curPtr + len > filePtr + fileSize ) { + len = filePtr + fileSize - curPtr; + } + memcpy( buffer, curPtr, len ); + curPtr += len; + return len; +} + +idCVar memcpyImpl( "memcpyImpl", "0", 0, "Which implementation of memcpy to use for idFile_Memory::Write() [0/1 - standard (1 eliminates branch misprediction), 2 - auto-vectorized]" ); +void * memcpy2( void * __restrict b, const void * __restrict a, size_t n ) { + char * s1 = (char *)b; + const char * s2 = (const char *)a; + for ( ; 0 < n; --n ) { + *s1++ = *s2++; + } + return b; +} + +/* +================= +idFile_Memory::Write +================= +*/ +idHashTableT< int, int > histogram; +CONSOLE_COMMAND( outputHistogram, "", 0 ) { + for ( int i = 0; i < histogram.Num(); i++ ) { + int key; + histogram.GetIndexKey( i, key ); + int * value = histogram.GetIndex( i ); + + idLib::Printf( "%d\t%d\n", key, *value ); + } +} + +CONSOLE_COMMAND( clearHistogram, "", 0 ) { + histogram.Clear(); +} + +int idFile_Memory::Write( const void *buffer, int len ) { + if ( len == 0 ) { + // ~4% falls into this case for some reason... + return 0; + } + + if ( !( mode & ( 1 << FS_WRITE ) ) ) { + common->FatalError( "idFile_Memory::Write: %s not opened in write mode", name.c_str() ); + return 0; + } + + int alloc = curPtr + len + 1 - filePtr - allocated; // need room for len+1 + if ( alloc > 0 ) { + if ( maxSize != 0 ) { + common->Error( "idFile_Memory::Write: exceeded maximum size %d", maxSize ); + return 0; + } + int extra = granularity * ( 1 + alloc / granularity ); + char *newPtr = (char *) Mem_Alloc( allocated + extra, TAG_IDFILE ); + if ( allocated ) { + memcpy( newPtr, filePtr, allocated ); + } + allocated += extra; + curPtr = newPtr + ( curPtr - filePtr ); + if ( filePtr ) { + Mem_Free( filePtr ); + } + filePtr = newPtr; + } + + //memcpy( curPtr, buffer, len ); + memcpy2( curPtr, buffer, len ); + +#if 0 + if ( memcpyImpl.GetInteger() == 0 ) { + memcpy( curPtr, buffer, len ); + } else if ( memcpyImpl.GetInteger() == 1 ) { + memcpy( curPtr, buffer, len ); + } else if ( memcpyImpl.GetInteger() == 2 ) { + memcpy2( curPtr, buffer, len ); + } +#endif + +#if 0 + int * value; + if ( histogram.Get( len, &value ) && value != NULL ) { + (*value)++; + } else { + histogram.Set( len, 1 ); + } +#endif + + curPtr += len; + fileSize += len; + filePtr[ fileSize ] = 0; // len + 1 + return len; +} + +/* +================= +idFile_Memory::Length +================= +*/ +int idFile_Memory::Length() const { + return fileSize; +} + +/* +======================== +idFile_Memory::SetLength +======================== +*/ +void idFile_Memory::SetLength( size_t len ) { + PreAllocate( len ); + fileSize = len; +} + +/* +======================== +idFile_Memory::PreAllocate +======================== +*/ +void idFile_Memory::PreAllocate( size_t len ) { + if ( len > allocated ) { + if ( maxSize != 0 ) { + idLib::Error( "idFile_Memory::SetLength: exceeded maximum size %d", maxSize ); + } + char * newPtr = (char *)Mem_Alloc( len, TAG_IDFILE ); + if ( allocated > 0 ) { + memcpy( newPtr, filePtr, allocated ); + } + allocated = len; + curPtr = newPtr + ( curPtr - filePtr ); + if ( filePtr != NULL ) { + Mem_Free( filePtr ); + } + filePtr = newPtr; + } +} + +/* +================= +idFile_Memory::Timestamp +================= +*/ +ID_TIME_T idFile_Memory::Timestamp() const { + return 0; +} + +/* +================= +idFile_Memory::Tell +================= +*/ +int idFile_Memory::Tell() const { + return ( curPtr - filePtr ); +} + +/* +================= +idFile_Memory::ForceFlush +================= +*/ +void idFile_Memory::ForceFlush() { +} + +/* +================= +idFile_Memory::Flush +================= +*/ +void idFile_Memory::Flush() { +} + +/* +================= +idFile_Memory::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_Memory::Seek( long offset, fsOrigin_t origin ) { + + switch( origin ) { + case FS_SEEK_CUR: { + curPtr += offset; + break; + } + case FS_SEEK_END: { + curPtr = filePtr + fileSize - offset; + break; + } + case FS_SEEK_SET: { + curPtr = filePtr + offset; + break; + } + default: { + common->FatalError( "idFile_Memory::Seek: bad origin for %s\n", name.c_str() ); + return -1; + } + } + if ( curPtr < filePtr ) { + curPtr = filePtr; + return -1; + } + if ( curPtr > filePtr + fileSize ) { + curPtr = filePtr + fileSize; + return -1; + } + return 0; +} + +/* +======================== +idFile_Memory::SetMaxLength +======================== +*/ +void idFile_Memory::SetMaxLength( size_t len ) { + size_t oldLength = fileSize; + + SetLength( len ); + + maxSize = len; + fileSize = oldLength; +} + +/* +================= +idFile_Memory::MakeReadOnly +================= +*/ +void idFile_Memory::MakeReadOnly() { + mode = ( 1 << FS_READ ); + Rewind(); +} + +/* +======================== +idFile_Memory::MakeWritable +======================== +*/ +void idFile_Memory::MakeWritable() { + mode = ( 1 << FS_WRITE ); + Rewind(); +} + +/* +================= +idFile_Memory::Clear +================= +*/ +void idFile_Memory::Clear( bool freeMemory ) { + fileSize = 0; + granularity = 16384; + if ( freeMemory ) { + allocated = 0; + Mem_Free( filePtr ); + filePtr = NULL; + curPtr = NULL; + } else { + curPtr = filePtr; + } +} + +/* +================= +idFile_Memory::SetData +================= +*/ +void idFile_Memory::SetData( const char *data, int length ) { + maxSize = 0; + fileSize = length; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_READ ); + filePtr = const_cast(data); + curPtr = const_cast(data); +} + +/* +======================== +idFile_Memory::TruncateData +======================== +*/ +void idFile_Memory::TruncateData( size_t len ) { + if ( len > allocated ) { + idLib::Error( "idFile_Memory::TruncateData: len (%d) exceeded allocated size (%d)", len, allocated ); + } else { + fileSize = len; + } +} + +/* +================================================================================= + +idFile_BitMsg + +================================================================================= +*/ + +/* +================= +idFile_BitMsg::idFile_BitMsg +================= +*/ +idFile_BitMsg::idFile_BitMsg( idBitMsg &msg ) { + name = "*unknown*"; + mode = ( 1 << FS_WRITE ); + this->msg = &msg; +} + +/* +================= +idFile_BitMsg::idFile_BitMsg +================= +*/ +idFile_BitMsg::idFile_BitMsg( const idBitMsg &msg ) { + name = "*unknown*"; + mode = ( 1 << FS_READ ); + this->msg = const_cast(&msg); +} + +/* +================= +idFile_BitMsg::~idFile_BitMsg +================= +*/ +idFile_BitMsg::~idFile_BitMsg() { +} + +/* +================= +idFile_BitMsg::Read +================= +*/ +int idFile_BitMsg::Read( void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_READ ) ) ) { + common->FatalError( "idFile_BitMsg::Read: %s not opened in read mode", name.c_str() ); + return 0; + } + + return msg->ReadData( buffer, len ); +} + +/* +================= +idFile_BitMsg::Write +================= +*/ +int idFile_BitMsg::Write( const void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_WRITE ) ) ) { + common->FatalError( "idFile_Memory::Write: %s not opened in write mode", name.c_str() ); + return 0; + } + + msg->WriteData( buffer, len ); + return len; +} + +/* +================= +idFile_BitMsg::Length +================= +*/ +int idFile_BitMsg::Length() const { + return msg->GetSize(); +} + +/* +================= +idFile_BitMsg::Timestamp +================= +*/ +ID_TIME_T idFile_BitMsg::Timestamp() const { + return 0; +} + +/* +================= +idFile_BitMsg::Tell +================= +*/ +int idFile_BitMsg::Tell() const { + if ( mode == FS_READ ) { + return msg->GetReadCount(); + } else { + return msg->GetSize(); + } +} + +/* +================= +idFile_BitMsg::ForceFlush +================= +*/ +void idFile_BitMsg::ForceFlush() { +} + +/* +================= +idFile_BitMsg::Flush +================= +*/ +void idFile_BitMsg::Flush() { +} + +/* +================= +idFile_BitMsg::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_BitMsg::Seek( long offset, fsOrigin_t origin ) { + return -1; +} + + +/* +================================================================================= + +idFile_Permanent + +================================================================================= +*/ + +/* +================= +idFile_Permanent::idFile_Permanent +================= +*/ +idFile_Permanent::idFile_Permanent() { + name = "invalid"; + o = NULL; + mode = 0; + fileSize = 0; + handleSync = false; +} + +/* +================= +idFile_Permanent::~idFile_Permanent +================= +*/ +idFile_Permanent::~idFile_Permanent() { + if ( o ) { + CloseHandle( o ); + } +} + +/* +================= +idFile_Permanent::Read + +Properly handles partial reads +================= +*/ +int idFile_Permanent::Read( void *buffer, int len ) { + int block, remaining; + int read; + byte * buf; + int tries; + + if ( !(mode & ( 1 << FS_READ ) ) ) { + common->FatalError( "idFile_Permanent::Read: %s not opened in read mode", name.c_str() ); + return 0; + } + + if ( !o ) { + return 0; + } + + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while( remaining ) { + block = remaining; + DWORD bytesRead; + if ( !ReadFile( o, buf, block, &bytesRead, NULL ) ) { + idLib::Warning( "idFile_Permanent::Read failed with %d from %s", GetLastError(), name.c_str() ); + } + read = bytesRead; + if ( read == 0 ) { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if ( !tries ) { + tries = 1; + } + else { + return len-remaining; + } + } + + if ( read == -1 ) { + common->FatalError( "idFile_Permanent::Read: -1 bytes read from %s", name.c_str() ); + } + + remaining -= read; + buf += read; + } + return len; +} + +/* +================= +idFile_Permanent::Write + +Properly handles partial writes +================= +*/ +int idFile_Permanent::Write( const void *buffer, int len ) { + int block, remaining; + int written; + byte * buf; + int tries; + + if ( !( mode & ( 1 << FS_WRITE ) ) ) { + common->FatalError( "idFile_Permanent::Write: %s not opened in write mode", name.c_str() ); + return 0; + } + + if ( !o ) { + return 0; + } + + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while( remaining ) { + block = remaining; + DWORD bytesWritten; + WriteFile( o, buf, block, &bytesWritten, NULL ); + written = bytesWritten; + if ( written == 0 ) { + if ( !tries ) { + tries = 1; + } + else { + common->Printf( "idFile_Permanent::Write: 0 bytes written to %s\n", name.c_str() ); + return 0; + } + } + + if ( written == -1 ) { + common->Printf( "idFile_Permanent::Write: -1 bytes written to %s\n", name.c_str() ); + return 0; + } + + remaining -= written; + buf += written; + fileSize += written; + } + if ( handleSync ) { + Flush(); + } + return len; +} + +/* +================= +idFile_Permanent::ForceFlush +================= +*/ +void idFile_Permanent::ForceFlush() { + FlushFileBuffers( o ); +} + +/* +================= +idFile_Permanent::Flush +================= +*/ +void idFile_Permanent::Flush() { + FlushFileBuffers( o ); +} + +/* +================= +idFile_Permanent::Tell +================= +*/ +int idFile_Permanent::Tell() const { + return SetFilePointer( o, 0, NULL, FILE_CURRENT ); +} + +/* +================ +idFile_Permanent::Length +================ +*/ +int idFile_Permanent::Length() const { + return fileSize; +} + +/* +================ +idFile_Permanent::Timestamp +================ +*/ +ID_TIME_T idFile_Permanent::Timestamp() const { + ID_TIME_T ts = Sys_FileTimeStamp( o ); + return ts; +} + +/* +================= +idFile_Permanent::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_Permanent::Seek( long offset, fsOrigin_t origin ) { + int retVal = INVALID_SET_FILE_POINTER; + switch( origin ) { + case FS_SEEK_CUR: retVal = SetFilePointer( o, offset, NULL, FILE_CURRENT ); break; + case FS_SEEK_END: retVal = SetFilePointer( o, offset, NULL, FILE_END ); break; + case FS_SEEK_SET: retVal = SetFilePointer( o, offset, NULL, FILE_BEGIN ); break; + } + return ( retVal == INVALID_SET_FILE_POINTER ) ? -1 : 0; +} + +#if 1 +/* +================================================================================= + +idFile_Cached + +================================================================================= +*/ + +/* +================= +idFile_Cached::idFile_Cached +================= +*/ +idFile_Cached::idFile_Cached() : idFile_Permanent() { + internalFilePos = 0; + bufferedStartOffset = 0; + bufferedEndOffset = 0; + buffered = NULL; +} + +/* +================= +idFile_Cached::~idFile_Cached +================= +*/ +idFile_Cached::~idFile_Cached() { + Mem_Free( buffered ); +} + +/* +================= +idFile_ReadBuffered::BufferData + +Buffer a section of the file +================= +*/ +void idFile_Cached::CacheData( uint64 offset, uint64 length ) { + Mem_Free( buffered ); + bufferedStartOffset = offset; + bufferedEndOffset = offset + length; + buffered = ( byte* )Mem_Alloc( length, TAG_RESOURCE ); + if ( buffered == NULL ) { + return; + } + int internalFilePos = idFile_Permanent::Tell(); + idFile_Permanent::Seek( offset, FS_SEEK_SET ); + idFile_Permanent::Read( buffered, length ); + idFile_Permanent::Seek( internalFilePos, FS_SEEK_SET ); +} + +/* +================= +idFile_ReadBuffered::Read + +================= +*/ +int idFile_Cached::Read( void *buffer, int len ) { + if ( internalFilePos >= bufferedStartOffset && internalFilePos + len < bufferedEndOffset ) { + // this is in the buffer + memcpy( buffer, (void*)&buffered[ internalFilePos - bufferedStartOffset ], len ); + internalFilePos += len; + return len; + } + int read = idFile_Permanent::Read( buffer, len ); + if ( read != -1 ) { + internalFilePos += ( int64 )read; + } + return read; +} + + + +/* +================= +idFile_Cached::Tell +================= +*/ +int idFile_Cached::Tell() const { + return internalFilePos; +} + +/* +================= +idFile_Cached::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_Cached::Seek( long offset, fsOrigin_t origin ) { + if ( origin == FS_SEEK_SET && offset >= bufferedStartOffset && offset < bufferedEndOffset ) { + // don't do anything to the actual file ptr, just update or internal position + internalFilePos = offset; + return 0; + } + + int retVal = idFile_Permanent::Seek( offset, origin ); + internalFilePos = idFile_Permanent::Tell(); + return retVal; +} +#endif + +/* +================================================================================= + +idFile_InZip + +================================================================================= +*/ + +/* +================= +idFile_InZip::idFile_InZip +================= +*/ +idFile_InZip::idFile_InZip() { + name = "invalid"; + zipFilePos = 0; + fileSize = 0; + memset( &z, 0, sizeof( z ) ); +} + +/* +================= +idFile_InZip::~idFile_InZip +================= +*/ +idFile_InZip::~idFile_InZip() { + unzCloseCurrentFile( z ); + unzClose( z ); +} + +/* +================= +idFile_InZip::Read + +Properly handles partial reads +================= +*/ +int idFile_InZip::Read( void *buffer, int len ) { + int l = unzReadCurrentFile( z, buffer, len ); + return l; +} + +/* +================= +idFile_InZip::Write +================= +*/ +int idFile_InZip::Write( const void *buffer, int len ) { + common->FatalError( "idFile_InZip::Write: cannot write to the zipped file %s", name.c_str() ); + return 0; +} + +/* +================= +idFile_InZip::ForceFlush +================= +*/ +void idFile_InZip::ForceFlush() { + common->FatalError( "idFile_InZip::ForceFlush: cannot flush the zipped file %s", name.c_str() ); +} + +/* +================= +idFile_InZip::Flush +================= +*/ +void idFile_InZip::Flush() { + common->FatalError( "idFile_InZip::Flush: cannot flush the zipped file %s", name.c_str() ); +} + +/* +================= +idFile_InZip::Tell +================= +*/ +int idFile_InZip::Tell() const { + return unztell( z ); +} + +/* +================ +idFile_InZip::Length +================ +*/ +int idFile_InZip::Length() const { + return fileSize; +} + +/* +================ +idFile_InZip::Timestamp +================ +*/ +ID_TIME_T idFile_InZip::Timestamp() const { + return 0; +} + +/* +================= +idFile_InZip::Seek + + returns zero on success and -1 on failure +================= +*/ +#define ZIP_SEEK_BUF_SIZE (1<<15) + +int idFile_InZip::Seek( long offset, fsOrigin_t origin ) { + int res, i; + char *buf; + + switch( origin ) { + case FS_SEEK_END: { + offset = fileSize - offset; + } + case FS_SEEK_SET: { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition( z, zipFilePos ); + unzOpenCurrentFile( z ); + if ( offset <= 0 ) { + return 0; + } + } + case FS_SEEK_CUR: { + buf = (char *) _alloca16( ZIP_SEEK_BUF_SIZE ); + for ( i = 0; i < ( offset - ZIP_SEEK_BUF_SIZE ); i += ZIP_SEEK_BUF_SIZE ) { + res = unzReadCurrentFile( z, buf, ZIP_SEEK_BUF_SIZE ); + if ( res < ZIP_SEEK_BUF_SIZE ) { + return -1; + } + } + res = i + unzReadCurrentFile( z, buf, offset - i ); + return ( res == offset ) ? 0 : -1; + } + default: { + common->FatalError( "idFile_InZip::Seek: bad origin for %s\n", name.c_str() ); + break; + } + } + return -1; +} + +#if 1 + +/* +================================================================================= + +idFile_InnerResource + +================================================================================= +*/ + +/* +================= +idFile_InnerResource::idFile_InnerResource +================= +*/ +idFile_InnerResource::idFile_InnerResource( const char *_name, idFile *rezFile, int _offset, int _len ) { + name = _name; + offset = _offset; + length = _len; + resourceFile = rezFile; + internalFilePos = 0; + resourceBuffer = NULL; +} + +/* +================= +idFile_InnerResource::~idFile_InnerResource +================= +*/ +idFile_InnerResource::~idFile_InnerResource() { + if ( resourceBuffer != NULL ) { + fileSystem->FreeResourceBuffer(); + } +} + +/* +================= +idFile_InnerResource::Read + +Properly handles partial reads +================= +*/ +int idFile_InnerResource::Read( void *buffer, int len ) { + if ( resourceFile == NULL ) { + return 0; + } + + if ( internalFilePos + len > length ) { + len = length - internalFilePos; + } + + int read = 0; //fileSystem->ReadFromBGL( resourceFile, (byte*)buffer, offset + internalFilePos, len ); + + if ( read != len ) { + if ( resourceBuffer != NULL ) { + memcpy( buffer, &resourceBuffer[ internalFilePos ], len ); + read = len; + } else { + read = fileSystem->ReadFromBGL( resourceFile, buffer, offset + internalFilePos, len ); + } + } + + internalFilePos += read; + + return read; +} + +/* +================= +idFile_InnerResource::Tell +================= +*/ +int idFile_InnerResource::Tell() const { + return internalFilePos; +} + + +/* +================= +idFile_InnerResource::Seek + + returns zero on success and -1 on failure +================= +*/ + +int idFile_InnerResource::Seek( long offset, fsOrigin_t origin ) { + switch( origin ) { + case FS_SEEK_END: { + internalFilePos = length - offset - 1; + return 0; + } + case FS_SEEK_SET: { + internalFilePos = offset; + if ( internalFilePos >= 0 && internalFilePos < length ) { + return 0; + } + return -1; + } + case FS_SEEK_CUR: { + internalFilePos += offset; + if ( internalFilePos >= 0 && internalFilePos < length ) { + return 0; + } + return -1; + } + default: { + common->FatalError( "idFile_InnerResource::Seek: bad origin for %s\n", name.c_str() ); + break; + } + } + return -1; +} +#endif + +/* +================================================================================================ + +idFileLocal + +================================================================================================ +*/ + +/* +======================== +idFileLocal::~idFileLocal + +Destructor that will destroy (close) the managed file when this wrapper class goes out of scope. +======================== +*/ +idFileLocal::~idFileLocal() { + if ( file != NULL ) { + delete file; + file = NULL; + } +} + +static const char * testEndianNessFilename = "temp.bin"; +struct testEndianNess_t { + testEndianNess_t() { + a = 0x12345678; + b = 0x12345678; + c = 3.0f; + d = -4.0f; + e = "test"; + f = idVec3( 1.0f, 2.0f, -3.0f ); + g = false; + h = true; + for ( int index = 0; index < sizeof( i ); index++ ) { + i[index] = 0x37; + } + } + bool operator==( testEndianNess_t & test ) const { + return a == test.a && + b == test.b && + c == test.c && + d == test.d && + e == test.e && + f == test.f && + g == test.g && + h == test.h && + ( memcmp( i, test.i, sizeof( i ) ) == 0 ); + } + int a; + unsigned int b; + float c; + float d; + idStr e; + idVec3 f; + bool g; + bool h; + byte i[10]; +}; +CONSOLE_COMMAND( testEndianNessWrite, "Tests the read/write compatibility between platforms", 0 ) { + idFileLocal file( fileSystem->OpenFileWrite( testEndianNessFilename ) ); + if ( file == NULL ) { + idLib::Printf( "Couldn't open the %s testfile.\n", testEndianNessFilename ); + return; + } + + testEndianNess_t testData; + + file->WriteBig( testData.a ); + file->WriteBig( testData.b ); + file->WriteFloat( testData.c ); + file->WriteFloat( testData.d ); + file->WriteString( testData.e ); + file->WriteVec3( testData.f ); + file->WriteBig( testData.g ); + file->WriteBig( testData.h ); + file->Write( testData.i, sizeof( testData.i )/ sizeof( testData.i[0] ) ); +} + +CONSOLE_COMMAND( testEndianNessRead, "Tests the read/write compatibility between platforms", 0 ) { + idFileLocal file( fileSystem->OpenFileRead( testEndianNessFilename ) ); + if ( file == NULL ) { + idLib::Printf( "Couldn't find the %s testfile.\n", testEndianNessFilename ); + return; + } + + testEndianNess_t srcData; + testEndianNess_t testData; + + memset( &testData, 0, sizeof( testData ) ); + + file->ReadBig( testData.a ); + file->ReadBig( testData.b ); + file->ReadFloat( testData.c ); + file->ReadFloat( testData.d ); + file->ReadString( testData.e ); + file->ReadVec3( testData.f ); + file->ReadBig( testData.g ); + file->ReadBig( testData.h ); + file->Read( testData.i, sizeof( testData.i )/ sizeof( testData.i[0] ) ); + + assert( srcData == testData ); +} + +CONSOLE_COMMAND( testEndianNessReset, "Tests the read/write compatibility between platforms", 0 ) { + fileSystem->RemoveFile( testEndianNessFilename ); +} + + diff --git a/neo/framework/File.h b/neo/framework/File.h new file mode 100644 index 00000000..d7698088 --- /dev/null +++ b/neo/framework/File.h @@ -0,0 +1,374 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FILE_H__ +#define __FILE_H__ + +/* +============================================================== + + File Streams. + +============================================================== +*/ + +// mode parm for Seek +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +class idFileSystemLocal; + + +class idFile { +public: + virtual ~idFile() {}; + // Get the name of the file. + virtual const char * GetName() const; + // Get the full file path. + virtual const char * GetFullPath() const; + // Read data from the file to the buffer. + virtual int Read( void *buffer, int len ); + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ); + // Returns the length of the file. + virtual int Length() const; + // Return a time value for reload operations. + virtual ID_TIME_T Timestamp() const; + // Returns offset in file. + virtual int Tell() const; + // Forces flush on files being writting to. + virtual void ForceFlush(); + // Causes any buffered data to be written to the file. + virtual void Flush(); + // Seek on a file. + virtual int Seek( long offset, fsOrigin_t origin ); + // Go back to the beginning of the file. + virtual void Rewind(); + // Like fprintf. + virtual int Printf( VERIFY_FORMAT_STRING const char *fmt, ... ); + // Like fprintf but with argument pointer + virtual int VPrintf( const char *fmt, va_list arg ); + // Write a string with high precision floating point numbers to the file. + virtual int WriteFloatString( VERIFY_FORMAT_STRING const char *fmt, ... ); + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ); + virtual int ReadUnsignedInt( unsigned int &value ); + virtual int ReadShort( short &value ); + virtual int ReadUnsignedShort( unsigned short &value ); + virtual int ReadChar( char &value ); + virtual int ReadUnsignedChar( unsigned char &value ); + virtual int ReadFloat( float &value ); + virtual int ReadBool( bool &value ); + virtual int ReadString( idStr &string ); + virtual int ReadVec2( idVec2 &vec ); + virtual int ReadVec3( idVec3 &vec ); + virtual int ReadVec4( idVec4 &vec ); + virtual int ReadVec6( idVec6 &vec ); + virtual int ReadMat3( idMat3 &mat ); + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ); + virtual int WriteUnsignedInt( const unsigned int value ); + virtual int WriteShort( const short value ); + virtual int WriteUnsignedShort( unsigned short value ); + virtual int WriteChar( const char value ); + virtual int WriteUnsignedChar( const unsigned char value ); + virtual int WriteFloat( const float value ); + virtual int WriteBool( const bool value ); + virtual int WriteString( const char *string ); + virtual int WriteVec2( const idVec2 &vec ); + virtual int WriteVec3( const idVec3 &vec ); + virtual int WriteVec4( const idVec4 &vec ); + virtual int WriteVec6( const idVec6 &vec ); + virtual int WriteMat3( const idMat3 &mat ); + + template ID_INLINE size_t ReadBig( type &c ) { + size_t r = Read( &c, sizeof( c ) ); + idSwap::Big( c ); + return r; + } + + template ID_INLINE size_t ReadBigArray( type *c, int count ) { + size_t r = Read( c, sizeof( c[0] ) * count ); + idSwap::BigArray( c, count ); + return r; + } + + template ID_INLINE size_t WriteBig( const type &c ) { + type b = c; + idSwap::Big( b ); + return Write( &b, sizeof( b ) ); + } + + template ID_INLINE size_t WriteBigArray( const type *c, int count ) { + size_t r = 0; + for ( int i = 0; i < count; i++ ) { + r += WriteBig( c[i] ); + } + return r; + } +}; + +/* +================================================ +idFile_Memory +================================================ +*/ +class idFile_Memory : public idFile { + friend class idFileSystemLocal; + +public: + idFile_Memory(); // file for writing without name + idFile_Memory( const char *name ); // file for writing + idFile_Memory( const char *name, char *data, int length ); // file for writing + idFile_Memory( const char *name, const char *data, int length ); // file for reading + virtual ~idFile_Memory(); + + virtual const char * GetName() const { return name.c_str(); } + virtual const char * GetFullPath() const { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length() const; + virtual void SetLength( size_t len ); + virtual ID_TIME_T Timestamp() const; + virtual int Tell() const; + virtual void ForceFlush(); + virtual void Flush(); + virtual int Seek( long offset, fsOrigin_t origin ); + + // Set the given length and don't allow the file to grow. + void SetMaxLength( size_t len ); + // changes memory file to read only + void MakeReadOnly(); + // Change the file to be writable + void MakeWritable(); + // clear the file + virtual void Clear( bool freeMemory = true ); + // set data for reading + void SetData( const char *data, int length ); + // returns const pointer to the memory buffer + const char * GetDataPtr() const { return filePtr; } + // returns pointer to the memory buffer + char * GetDataPtr() { return filePtr; } + // set the file granularity + void SetGranularity( int g ) { assert( g > 0 ); granularity = g; } + void PreAllocate( size_t len ); + + // Doesn't change how much is allocated, but allows you to set the size of the file to smaller than it should be. + // Useful for stripping off a checksum at the end of the file + void TruncateData( size_t len ); + + void TakeDataOwnership(); + + size_t GetMaxLength() { return maxSize; } + size_t GetAllocated() { return allocated; } + +protected: + idStr name; // name of the file +private: + int mode; // open mode + size_t maxSize; // maximum size of file + size_t fileSize; // size of the file + size_t allocated; // allocated size + int granularity; // file granularity + char * filePtr; // buffer holding the file data + char * curPtr; // current read/write pointer +}; + + +class idFile_BitMsg : public idFile { + friend class idFileSystemLocal; + +public: + idFile_BitMsg( idBitMsg &msg ); + idFile_BitMsg( const idBitMsg &msg ); + virtual ~idFile_BitMsg(); + + virtual const char * GetName() const { return name.c_str(); } + virtual const char * GetFullPath() const { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length() const; + virtual ID_TIME_T Timestamp() const; + virtual int Tell() const; + virtual void ForceFlush(); + virtual void Flush(); + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + idStr name; // name of the file + int mode; // open mode + idBitMsg * msg; +}; + + +class idFile_Permanent : public idFile { + friend class idFileSystemLocal; + +public: + idFile_Permanent(); + virtual ~idFile_Permanent(); + + virtual const char * GetName() const { return name.c_str(); } + virtual const char * GetFullPath() const { return fullPath.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length() const; + virtual ID_TIME_T Timestamp() const; + virtual int Tell() const; + virtual void ForceFlush(); + virtual void Flush(); + virtual int Seek( long offset, fsOrigin_t origin ); + + // returns file pointer + idFileHandle GetFilePtr() { return o; } + +private: + idStr name; // relative path of the file - relative path + idStr fullPath; // full file path - OS path + int mode; // open mode + int fileSize; // size of the file + idFileHandle o; // file handle + bool handleSync; // true if written data is immediately flushed +}; + +class idFile_Cached : public idFile_Permanent { + friend class idFileSystemLocal; +public: + idFile_Cached(); + virtual ~idFile_Cached(); + + void CacheData( uint64 offset, uint64 length ); + + virtual int Read( void *buffer, int len ); + + virtual int Tell() const; + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + uint64 internalFilePos; + uint64 bufferedStartOffset; + uint64 bufferedEndOffset; + byte * buffered; +}; + + +class idFile_InZip : public idFile { + friend class idFileSystemLocal; + +public: + idFile_InZip(); + virtual ~idFile_InZip(); + + virtual const char * GetName() const { return name.c_str(); } + virtual const char * GetFullPath() const { return fullPath.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length() const; + virtual ID_TIME_T Timestamp() const; + virtual int Tell() const; + virtual void ForceFlush(); + virtual void Flush(); + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + idStr name; // name of the file in the pak + idStr fullPath; // full file path including pak file name + int zipFilePos; // zip file info position in pak + int fileSize; // size of the file + void * z; // unzip info +}; + +#if 1 +class idFile_InnerResource : public idFile { + friend class idFileSystemLocal; + +public: + idFile_InnerResource( const char *_name, idFile *rezFile, int _offset, int _len ); + virtual ~idFile_InnerResource(); + + virtual const char * GetName() const { return name.c_str(); } + virtual const char * GetFullPath() const { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ) { assert( false ); return 0; } + virtual int Length() const { return length; } + virtual ID_TIME_T Timestamp() const { return 0; } + virtual int Tell() const; + virtual int Seek( long offset, fsOrigin_t origin ); + void SetResourceBuffer( byte * buf ) { + resourceBuffer = buf; + internalFilePos = 0; + } + +private: + idStr name; // name of the file in the pak + int offset; // offset in the resource file + int length; // size + idFile * resourceFile; // actual file + int internalFilePos; // seek offset + byte * resourceBuffer; // if using the temp save memory +}; +#endif +/* +================================================ +idFileLocal is a FileStream wrapper that automatically closes a file when the +class variable goes out of scope. Note that the pointer passed in to the constructor can be for +any type of File Stream that ultimately inherits from idFile, and that this is not actually a +SmartPointer, as it does not keep a reference count. +================================================ +*/ +class idFileLocal { +public: + // Constructor that accepts and stores the file pointer. + idFileLocal( idFile *_file ) : file( _file ) { + } + + // Destructor that will destroy (close) the file when this wrapper class goes out of scope. + ~idFileLocal(); + + // Cast to a file pointer. + operator idFile * () const { + return file; + } + + // Member access operator for treating the wrapper as if it were the file, itself. + idFile * operator -> () const { + return file; + } + +protected: + idFile *file; // The managed file pointer. +}; + + + +#endif /* !__FILE_H__ */ diff --git a/neo/framework/FileSystem.cpp b/neo/framework/FileSystem.cpp new file mode 100644 index 00000000..ce8162ca --- /dev/null +++ b/neo/framework/FileSystem.cpp @@ -0,0 +1,3221 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Unzip.h" +#include "Zip.h" + +#ifdef WIN32 + #include // for _read +#else + #if !__MACH__ && __MWERKS__ + #include + #include +#else + #include + #include + #endif + #include +#endif + + +/* +============================================================================= + +DOOM FILESYSTEM + +All of Doom's data access is through a hierarchical file system, but the contents of +the file system can be transparently merged from several sources. + +A "relativePath" is a reference to game file data, which must include a terminating zero. +"..", "\\", and ":" are explicitly illegal in qpaths to prevent any references +outside the Doom directory system. + +The "base path" is the path to the directory holding all the game directories and +usually the executable. It defaults to the current directory, but can be overridden +with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified +at all after startup. + +The "save path" is the path to the directory where game files will be saved. It defaults +to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the +command line. Any files that are created during the game (demos, screenshots, etc.) will +be created reletive to the save path. + +If a user runs the game directly from a CD, the base path would be on the CD. This +should still function correctly, but all file writes will fail (harmlessly). + +The "base game" is the directory under the paths where data comes from by default, and +can be either "base" or "demo". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base +game. The game directory is set with "+set fs_game myaddon" on the command line. This is +the basis for addons. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes. Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the base +path, it will be copied over to the save path. This is a development aid to help build +test releases and to copy working sets of files. + +The relative path "sound/newstuff/test.wav" would be searched for in the following places: + +for save path, base path: + for current game, base game: + search directory + search zip files + +downloaded files, to be written to save path + current game's directory + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + +"additional mod path search": +fs_game_base can be used to set an additional search path +in search order, fs_game, fs_game_base, BASEGAME +for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp + +============================================================================= +*/ + +#define MAX_ZIPPED_FILE_NAME 2048 +#define FILE_HASH_SIZE 1024 + +struct searchpath_t { + idStr path; // c:\doom + idStr gamedir; // base +}; + +// search flags when opening a file +#define FSFLAG_SEARCH_DIRS ( 1 << 0 ) +#define FSFLAG_RETURN_FILE_MEM ( 1 << 1 ) + +class idFileSystemLocal : public idFileSystem { +public: + idFileSystemLocal(); + + virtual void Init(); + virtual void Restart(); + virtual void Shutdown( bool reloading ); + virtual bool IsInitialized() const; + virtual idFileList * ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL ); + virtual idFileList * ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL ); + virtual void FreeFileList( idFileList *fileList ); + virtual const char * OSPathToRelativePath( const char *OSPath ); + virtual const char * RelativePathToOSPath( const char *relativePath, const char *basePath ); + virtual const char * BuildOSPath( const char *base, const char *game, const char *relativePath ); + virtual const char * BuildOSPath( const char *base, const char *relativePath ); + virtual void CreateOSPath( const char *OSPath ); + virtual int ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ); + virtual void FreeFile( void *buffer ); + virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" ); + virtual void RemoveFile( const char *relativePath ); + virtual bool RemoveDir( const char * relativePath ); + virtual bool RenameFile( const char * relativePath, const char * newName, const char * basePath = "fs_savepath" ); + virtual idFile * OpenFileReadFlags( const char *relativePath, int searchFlags, bool allowCopyFiles = true, const char* gamedir = NULL ); + virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ); + virtual idFile * OpenFileReadMemory( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ); + virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" ); + virtual idFile * OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath" ); + virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode ); + virtual idFile * OpenExplicitFileRead( const char *OSPath ); + virtual idFile * OpenExplicitFileWrite( const char *OSPath ); + virtual idFile_Cached * OpenExplicitPakFile( const char *OSPath ); + virtual void CloseFile( idFile *f ); + virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ] ); + virtual void CopyFile( const char *fromOSPath, const char *toOSPath ); + virtual findFile_t FindFile( const char *path ); + virtual bool FilenameCompare( const char *s1, const char *s2 ) const; + virtual int GetFileLength( const char * relativePath ); + virtual sysFolder_t IsFolder( const char * relativePath, const char *basePath = "fs_basepath" ); + // resource tracking + virtual void EnableBackgroundCache( bool enable ); + virtual void BeginLevelLoad( const char *name, char *_blockBuffer, int _blockBufferSize ); + virtual void EndLevelLoad(); + virtual bool InProductionMode() { return ( resourceFiles.Num() > 0 ) | ( com_productionMode.GetInteger() != 0 ); } + virtual bool UsingResourceFiles() { return resourceFiles.Num() > 0; } + virtual void UnloadMapResources( const char *name ); + virtual void UnloadResourceContainer( const char *name ); + idFile * GetResourceContainer( int idx ) { + if ( idx >= 0 && idx < resourceFiles.Num() ) { + return resourceFiles[ idx ]->resourceFile; + } + return NULL; + } + + virtual void StartPreload( const idStrList &_preload ); + virtual void StopPreload(); + idFile * GetResourceFile( const char *fileName, bool memFile ); + bool GetResourceCacheEntry( const char *fileName, idResourceCacheEntry &rc ); + virtual int ReadFromBGL( idFile *_resourceFile, void * _buffer, int _offset, int _len ); + virtual bool IsBinaryModel( const idStr & resName ) const; + virtual bool IsSoundSample( const idStr & resName ) const; + virtual void FreeResourceBuffer() { resourceBufferAvailable = resourceBufferSize; } + virtual void AddImagePreload( const char *resName, int _filter, int _repeat, int _usage, int _cube ) { + preloadList.AddImage( resName, _filter, _repeat, _usage, _cube ); + } + virtual void AddSamplePreload( const char *resName ) { + preloadList.AddSample( resName ); + } + virtual void AddModelPreload( const char *resName ) { + preloadList.AddModel( resName ); + } + virtual void AddAnimPreload( const char *resName ) { + preloadList.AddAnim( resName ); + } + virtual void AddCollisionPreload( const char *resName ) { + preloadList.AddCollisionModel( resName ); + } + virtual void AddParticlePreload( const char *resName ) { + preloadList.AddParticle( resName ); + } + + static void Dir_f( const idCmdArgs &args ); + static void DirTree_f( const idCmdArgs &args ); + static void Path_f( const idCmdArgs &args ); + static void TouchFile_f( const idCmdArgs &args ); + static void TouchFileList_f( const idCmdArgs &args ); + static void BuildGame_f( const idCmdArgs &args ); + //static void FileStats_f( const idCmdArgs &args ); + static void WriteResourceFile_f ( const idCmdArgs &args ); + static void ExtractResourceFile_f( const idCmdArgs &args ); + static void UpdateResourceFile_f( const idCmdArgs &args ); + static void GenerateResourceCRCs_f( const idCmdArgs &args ); + static void CreateCRCsForResourceFileList( const idFileList & list ); + + void BuildOrderedStartupContainer(); +private: + idList searchPaths; + int loadCount; // total files read + int loadStack; // total files in memory + idStr gameFolder; // this will be a single name without separators + + static idCVar fs_debug; + static idCVar fs_debugResources; + static idCVar fs_copyfiles; + static idCVar fs_buildResources; + static idCVar fs_game; + static idCVar fs_game_base; + static idCVar fs_enableBGL; + static idCVar fs_debugBGL; + + idStr manifestName; + idStrList fileManifest; + idPreloadManifest preloadList; + + idList< idResourceContainer * > resourceFiles; + byte * resourceBufferPtr; + int resourceBufferSize; + int resourceBufferAvailable; + int numFilesOpenedAsCached; + +private: + + // .resource file creation + void ClearResourcePacks(); + void WriteResourcePacks(); + void AddRenderProgs( idStrList &files ); + void AddFonts( idStrList &files ); + + void ReplaceSeparators( idStr &path, char sep = PATHSEPARATOR_CHAR ); + int ListOSFiles( const char *directory, const char *extension, idStrList &list ); + idFileHandle OpenOSFile( const char *name, fsMode_t mode ); + void CloseOSFile( idFileHandle o ); + int DirectFileLength( idFileHandle o ); + void CopyFile( idFile *src, const char *toOSPath ); + int AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const; + void GetExtensionList( const char *extension, idStrList &extensionList ) const; + int GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL ); + + int GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL ); + void AddGameDirectory( const char *path, const char *dir ); + + int AddResourceFile( const char * resourceFileName ); + void RemoveMapResourceFile( const char * resourceFileName ); + void RemoveResourceFileByIndex( const int & idx ); + void RemoveResourceFile( const char * resourceFileName ); + int FindResourceFile( const char * resourceFileName ); + + void SetupGameDirectories( const char *gameName ); + void Startup(); + void InitPrecache(); + void ReOpenCacheFiles(); +}; + +idCVar idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar idFileSystemLocal::fs_debugResources( "fs_debugResources", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idFileSystemLocal::fs_enableBGL( "fs_enableBGL", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idFileSystemLocal::fs_debugBGL( "fs_debugBGL", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "Copy every file touched to fs_savepath" ); +idCVar idFileSystemLocal::fs_buildResources( "fs_buildresources", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_INIT, "Copy every file touched to a resource file" ); +idCVar idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" ); +idCVar idFileSystemLocal::fs_game_base( "fs_game_base", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "alternate mod path, searched after the main fs_game path, before the basedir" ); + +idCVar fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar fs_resourceLoadPriority( "fs_resourceLoadPriority", "1", CVAR_SYSTEM , "if 1, open requests will be honored from resource files first; if 0, the resource files are checked after normal search paths" ); +idCVar fs_enableBackgroundCaching( "fs_enableBackgroundCaching", "1", CVAR_SYSTEM , "if 1 allow the 360 to precache game files in the background" ); + +idFileSystemLocal fileSystemLocal; +idFileSystem * fileSystem = &fileSystemLocal; + +/* +================ +idFileSystemLocal::ReadFromBGL +================ +*/ +int idFileSystemLocal::ReadFromBGL( idFile *_resourceFile, void * _buffer, int _offset, int _len ) { + if ( _resourceFile->Tell() != _offset ) { + _resourceFile->Seek( _offset, FS_SEEK_SET ); + } + return _resourceFile->Read( _buffer, _len ); +} + +/* +================ +idFileSystemLocal::StartPreload +================ +*/ +void idFileSystemLocal::StartPreload( const idStrList & _preload ) { +} + +/* +================ +idFileSystemLocal::StopPreload +================ +*/ +void idFileSystemLocal::StopPreload() { +} + +/* +================ +idFileSystemLocal::idFileSystemLocal +================ +*/ +idFileSystemLocal::idFileSystemLocal() { + loadCount = 0; + loadStack = 0; + resourceBufferPtr = NULL; + resourceBufferSize = 0; + resourceBufferAvailable = 0; + numFilesOpenedAsCached = 0; +} + +/* +=========== +idFileSystemLocal::FilenameCompare + +Ignore case and separator char distinctions +=========== +*/ +bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ('a' - 'A'); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if ( c1 != c2 ) { + return true; // strings not equal + } + } while( c1 ); + + return false; // strings are equal +} + +/* +======================== +idFileSystemLocal::GetFileLength +======================== +*/ +int idFileSystemLocal::GetFileLength( const char * relativePath ) { + idFile * f; + int len; + + if ( !IsInitialized() ) { + idLib::FatalError( "Filesystem call made without initialization" ); + } + + if ( !relativePath || !relativePath[0] ) { + idLib::Warning( "idFileSystemLocal::GetFileLength with empty name" ); + return -1; + } + + if ( resourceFiles.Num() > 0 ) { + idResourceCacheEntry rc; + if ( GetResourceCacheEntry( relativePath, rc ) ) { + return rc.length; + } + } + + // look for it in the filesystem or pack files + f = OpenFileRead( relativePath, false ); + if ( f == NULL ) { + return -1; + } + + len = (int)f->Length(); + + delete f; + return len; +} + +/* +================ +idFileSystemLocal::OpenOSFile +================ +*/ +idFileHandle idFileSystemLocal::OpenOSFile( const char *fileName, fsMode_t mode ) { + idFileHandle fp; + + + DWORD dwAccess = 0; + DWORD dwShare = 0; + DWORD dwCreate = 0; + DWORD dwFlags = 0; + + if ( mode == FS_WRITE ) { + dwAccess = GENERIC_READ | GENERIC_WRITE; + dwShare = FILE_SHARE_READ; + dwCreate = CREATE_ALWAYS; + dwFlags = FILE_ATTRIBUTE_NORMAL; + } else if ( mode == FS_READ ) { + dwAccess = GENERIC_READ; + dwShare = FILE_SHARE_READ; + dwCreate = OPEN_EXISTING; + dwFlags = FILE_ATTRIBUTE_NORMAL; + } else if ( mode == FS_APPEND ) { + dwAccess = GENERIC_READ | GENERIC_WRITE; + dwShare = FILE_SHARE_READ; + dwCreate = OPEN_ALWAYS; + dwFlags = FILE_ATTRIBUTE_NORMAL; + } + + fp = CreateFile( fileName, dwAccess, dwShare, NULL, dwCreate, dwFlags, NULL ); + if ( fp == INVALID_HANDLE_VALUE ) { + return NULL; + } + return fp; +} + +/* +================ +idFileSystemLocal::CloseOSFile +================ +*/ +void idFileSystemLocal::CloseOSFile( idFileHandle o ) { + ::CloseHandle( o ); +} + +/* +================ +idFileSystemLocal::DirectFileLength +================ +*/ +int idFileSystemLocal::DirectFileLength( idFileHandle o ) { + return GetFileSize( o, NULL ); +} + +/* +============ +idFileSystemLocal::CreateOSPath + +Creates any directories needed to store the given filename +============ +*/ +void idFileSystemLocal::CreateOSPath( const char *OSPath ) { + char *ofs; + + // make absolutely sure that it can't back up the path + // FIXME: what about c: ? + if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { +#ifdef _DEBUG + common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath ); +#endif + return; + } + + idStrStatic< MAX_OSPATH > path( OSPath ); + path.SlashesToBackSlashes(); + for( ofs = &path[ 1 ]; *ofs ; ofs++ ) { + if ( *ofs == PATHSEPARATOR_CHAR ) { + // create the directory + *ofs = 0; + Sys_Mkdir( path ); + *ofs = PATHSEPARATOR_CHAR; + } + } +} + +/* +================= +idFileSystemLocal::EnableBackgroundCache +================= +*/ +void idFileSystemLocal::EnableBackgroundCache( bool enable ) { + if ( !fs_enableBackgroundCaching.GetBool() ) { + return; + } +} + +/* +================= +idFileSystemLocal::BeginLevelLoad +================= +*/ +void idFileSystemLocal::BeginLevelLoad( const char *name, char *_blockBuffer, int _blockBufferSize ) { + + if ( name == NULL || *name == '\0' ) { + return; + } + + resourceBufferPtr = ( byte* )_blockBuffer; + resourceBufferAvailable = _blockBufferSize; + resourceBufferSize = _blockBufferSize; + + manifestName = name; + + fileManifest.Clear(); + preloadList.Clear(); + + EnableBackgroundCache( false ); + + ReOpenCacheFiles(); + manifestName.StripPath(); + + if ( resourceFiles.Num() > 0 ) { + AddResourceFile( va( "%s.resources", manifestName.c_str() ) ); + } + +} + +/* +================= +idFileSystemLocal::UnloadResourceContainer + +================= +*/ +void idFileSystemLocal::UnloadResourceContainer( const char *name ) { + if ( name == NULL || *name == '\0' ) { + return; + } + RemoveResourceFile( va( "%s.resources", name ) ); +} + +/* +================= +idFileSystemLocal::UnloadMapResources +================= +*/ +void idFileSystemLocal::UnloadMapResources( const char *name ) { + if ( name == NULL || *name == '\0' || idStr::Icmp( "_startup", name ) == 0 ) { + return; + } + + if ( resourceFiles.Num() > 0 ) { + RemoveMapResourceFile( va( "%s.resources", name ) ); + } +} + +/* +================= +idFileSystemLocal::EndLevelLoad + +================= +*/ +void idFileSystemLocal::EndLevelLoad() { + if ( fs_buildResources.GetBool() ) { + int saveCopyFiles = fs_copyfiles.GetInteger(); + fs_copyfiles.SetInteger( 0 ); + + idStr manifestFileName = manifestName; + manifestFileName.StripPath(); + manifestFileName.SetFileExtension( "manifest" ); + manifestFileName.Insert( "maps/", 0 ); + idFile *outFile = fileSystem->OpenFileWrite( manifestFileName ); + if ( outFile != NULL ) { + int num = fileManifest.Num(); + outFile->WriteBig( num ); + for ( int i = 0; i < num; i++ ) { + outFile->WriteString( fileManifest[ i ] ); + } + delete outFile; + } + + idStrStatic< MAX_OSPATH > preloadName = manifestName; + preloadName.Insert( "maps/", 0 ); + preloadName += ".preload"; + idFile *fileOut = fileSystem->OpenFileWrite( preloadName, "fs_savepath" ); + preloadList.WriteManifestToFile( fileOut ); + delete fileOut; + + fs_copyfiles.SetInteger( saveCopyFiles ); + } + + EnableBackgroundCache( true ); + + resourceBufferPtr = NULL; + resourceBufferAvailable = 0; + resourceBufferSize = 0; + +} + +bool FileExistsInAllManifests( const char *filename, idList< idFileManifest > &manifests ) { + for ( int i = 0; i < manifests.Num(); i++ ) { + if ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) { + continue; + } + if ( strstr( manifests[ i ].GetManifestName(), "_pc" ) != NULL ) { + continue; + } + if ( manifests[ i ].FindFile( filename ) == -1 ) { + return false; + } + } + return true; +} + +bool FileExistsInAllPreloadManifests( const char *filename, idList< idPreloadManifest > &manifests ) { + for ( int i = 0; i < manifests.Num(); i++ ) { + if ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) { + continue; + } + if ( manifests[ i ].FindResource( filename ) == -1 ) { + return false; + } + } + return true; +} + +void RemoveFileFromAllManifests( const char *filename, idList< idFileManifest > &manifests ) { + for ( int i = 0; i < manifests.Num(); i++ ) { + if ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) { + continue; + } + if ( strstr( manifests[ i ].GetManifestName(), "_pc" ) != NULL ) { + continue; + } + manifests[ i ].RemoveAll( filename ); + } +} + + +/* +================ +idFileSystemLocal::AddPerPlatformResources +================ +*/ +void idFileSystemLocal::AddRenderProgs( idStrList &files ) { + idStrList work; + + // grab all the renderprogs + idStr path = RelativePathToOSPath( "renderprogs/cgb", "fs_basepath" ); + ListOSFiles( path, "*.cgb", work ); + for ( int i = 0; i < work.Num(); i++ ) { + files.Append( idStr( "renderprogs/cgb/" ) + work[i] ); + } + + path = RelativePathToOSPath( "renderprogs/hlsl", "fs_basepath" ); + ListOSFiles( path, "*.v360", work ); + for ( int i = 0; i < work.Num(); i++ ) { + files.Append( idStr( "renderprogs/hlsl/" ) + work[i] ); + } + ListOSFiles( path, "*.p360", work ); + for ( int i = 0; i < work.Num(); i++ ) { + files.Append( idStr( "renderprogs/hlsl/" ) + work[i] ); + } + + path = RelativePathToOSPath( "renderprogs/gl", "fs_basepath" ); + ListOSFiles( path, "*.*", work ); + for ( int i = 0; i < work.Num(); i++ ) { + files.Append( idStr( "renderprogs/gl/" ) + work[i] ); + } + +} + +/* +================ +idFileSystemLocal::AddSoundResources +================ +*/ +void idFileSystemLocal::AddFonts( idStrList &files ) { + // temp fix for getting idaudio files in + idFileList *fl = ListFilesTree( "generated/images/newfonts", "*.bimage", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + files.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "newfonts", "*.dat", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + files.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); +} + + +const char * excludeExtensions[] = { + ".idxma", ".idmsf", ".idwav", ".xma", ".msf", ".wav", ".resource" +}; +const int numExcludeExtensions = sizeof( excludeExtensions ) / sizeof( excludeExtensions[ 0 ] ); + +bool IsExcludedFile( const idStr & resName ) { + for ( int k = 0; k < numExcludeExtensions; k++ ) { + if ( resName.Find( excludeExtensions[ k ], false ) >= 0 ) { + return true; + } + } + return false; +} + +/* +================ +idFileSystemLocal::IsBinaryModel +================ +*/ +bool idFileSystemLocal::IsBinaryModel( const idStr & resName ) const { + idStrStatic< 32 > ext; + resName.ExtractFileExtension( ext ); + if ( ( ext.Icmp( "base" ) == 0 ) || ( ext.Icmp( "blwo" ) == 0 ) || ( ext.Icmp( "bflt" ) == 0 ) || ( ext.Icmp( "bma" ) == 0 ) ) { + return true; + } + return false; +} + +/* +================ +idFileSystemLocal::IsSoundSample +================ +*/ +bool idFileSystemLocal::IsSoundSample( const idStr & resName ) const { + idStrStatic< 32 > ext; + resName.ExtractFileExtension( ext ); + if ( ( ext.Icmp( "idxma" ) == 0 ) || ( ext.Icmp( "idwav" ) == 0 ) || ( ext.Icmp( "idmsf" ) == 0 ) || ( ext.Icmp( "xma" ) == 0 ) || ( ext.Icmp( "wav" ) == 0 ) || ( ext.Icmp( "msf" ) == 0 ) || ( ext.Icmp( "msadpcm" ) == 0 ) ) { + return true; + } + return false; +} + + +void idFileSystemLocal::BuildOrderedStartupContainer() { + idStrList orderedFiles( 1024 ); + + idFileList * fl = ListFilesTree( "materials", "*.mtr", true ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "renderprogs", "*.v360", true ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "renderprogs", "*.p360", true ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "renderprogs", "*.cgb", true ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "renderprogs/gl", "*.*", true ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "skins", "*.skin", true ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "sound", "*.sndshd", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "def", "*.def", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "fx", "*.fx", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "particles", "*.prt", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + fl = ListFilesTree( "af", "*.af", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + fl = ListFilesTree( "newpdas", "*.pda", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + orderedFiles.Append( "script/doom_main.script" ); + orderedFiles.Append( "script/doom_defs.script" ); + orderedFiles.Append( "script/doom_defs.script" ); + orderedFiles.Append( "script/doom_events.script" ); + orderedFiles.Append( "script/doom_util.script" ); + orderedFiles.Append( "script/weapon_base.script" ); + orderedFiles.Append( "script/ai_base.script" ); + orderedFiles.Append( "script/weapon_fists.script" ); + orderedFiles.Append( "script/weapon_pistol.script" ); + orderedFiles.Append( "script/weapon_shotgun.script" ); + orderedFiles.Append( "script/weapon_machinegun.script" ); + orderedFiles.Append( "script/weapon_chaingun.script" ); + orderedFiles.Append( "script/weapon_handgrenade.script" ); + orderedFiles.Append( "script/weapon_plasmagun.script" ); + orderedFiles.Append( "script/weapon_rocketlauncher.script" ); + orderedFiles.Append( "script/weapon_bfg.script" ); + orderedFiles.Append( "script/weapon_soulcube.script" ); + orderedFiles.Append( "script/weapon_chainsaw.script" ); + orderedFiles.Append( "script/weapon_flashlight.script" ); + orderedFiles.Append( "script/weapon_pda.script" ); + orderedFiles.Append( "script/ai_monster_base.script" ); + orderedFiles.Append( "script/ai_monster_zombie_base.script" ); + orderedFiles.Append( "script/ai_monster_demon_archvile.script" ); + orderedFiles.Append( "script/ai_monster_demon_cherub.script" ); + orderedFiles.Append( "script/ai_monster_demon_hellknight.script" ); + orderedFiles.Append( "script/ai_monster_demon_imp.script" ); + orderedFiles.Append( "script/ai_monster_demon_maggot.script" ); + orderedFiles.Append( "script/ai_monster_demon_mancubus.script" ); + orderedFiles.Append( "script/ai_monster_demon_pinky.script" ); + orderedFiles.Append( "script/ai_monster_demon_revenant.script" ); + orderedFiles.Append( "script/ai_monster_demon_trite.script" ); + orderedFiles.Append( "script/ai_monster_demon_wraith.script" ); + orderedFiles.Append( "script/ai_monster_flying_lostsoul.script" ); + orderedFiles.Append( "script/ai_monster_flying_cacodemon.script" ); + orderedFiles.Append( "script/ai_monster_zombie.script" ); + orderedFiles.Append( "script/ai_monster_zombie_morgue.script" ); + orderedFiles.Append( "script/ai_monster_zombie_sawyer.script" ); + orderedFiles.Append( "script/ai_monster_zombie_bernie.script" ); + orderedFiles.Append( "script/ai_monster_zombie_commando_cgun.script" ); + orderedFiles.Append( "script/ai_monster_zombie_commando_tentacle.script" ); + orderedFiles.Append( "script/ai_monster_zombie_security_pistol.script" ); + orderedFiles.Append( "script/ai_monster_turret.script" ); + orderedFiles.Append( "script/ai_monster_boss_vagary.script" ); + orderedFiles.Append( "script/ai_monster_boss_cyberdemon.script" ); + orderedFiles.Append( "script/ai_monster_boss_guardian.script" ); + orderedFiles.Append( "script/ai_monster_boss_guardian_seeker.script" ); + orderedFiles.Append( "script/ai_monster_boss_sabaoth.script" ); + orderedFiles.Append( "script/ai_character.script" ); + orderedFiles.Append( "script/ai_character_prone.script" ); + orderedFiles.Append( "script/ai_character_sentry.script" ); + orderedFiles.Append( "script/ai_player.script" ); + orderedFiles.Append( "script/ai_alphalabs2_scientist1.script" ); + orderedFiles.Append( "script/ai_cinematic_le.script" ); + orderedFiles.Append( "script/map_admin1.script" ); + orderedFiles.Append( "script/map_alphalabs1.script" ); + orderedFiles.Append( "script/map_alphalabs2.script" ); + orderedFiles.Append( "script/map_alphalabs3.script" ); + orderedFiles.Append( "script/map_alphalabs3_crane.script" ); + orderedFiles.Append( "script/map_alphalabs4.script" ); + orderedFiles.Append( "script/map_caves.script" ); + orderedFiles.Append( "script/map_caves2.script" ); + orderedFiles.Append( "script/map_comm1.script" ); + orderedFiles.Append( "script/map_commoutside_lift.script" ); + orderedFiles.Append( "script/map_commoutside.script" ); + orderedFiles.Append( "script/map_cpu.script" ); + orderedFiles.Append( "script/map_cpuboss.script" ); + orderedFiles.Append( "script/map_delta1.script" ); + orderedFiles.Append( "script/map_delta2a.script" ); + orderedFiles.Append( "script/map_delta2b.script" ); + orderedFiles.Append( "script/map_delta3.script" ); + orderedFiles.Append( "script/map_delta5.script" ); + orderedFiles.Append( "script/map_enpro.script" ); + orderedFiles.Append( "script/map_hell1.script" ); + orderedFiles.Append( "script/map_hellhole.script" ); + orderedFiles.Append( "script/map_recycling1.script" ); + orderedFiles.Append( "script/map_recycling2.script" ); + orderedFiles.Append( "script/map_site3.script" ); + orderedFiles.Append( "script/map_marscity1.script" ); + orderedFiles.Append( "script/map_marscity2.script" ); + orderedFiles.Append( "script/map_mc_underground.script" ); + orderedFiles.Append( "script/map_monorail.script" ); + orderedFiles.Append( "script/d3xp_events.script" ); + orderedFiles.Append( "script/weapon_bloodstone_passive.script" ); + orderedFiles.Append( "script/weapon_bloodstone_active1.script" ); + orderedFiles.Append( "script/weapon_bloodstone_active2.script" ); + orderedFiles.Append( "script/weapon_bloodstone_active3.script" ); + orderedFiles.Append( "script/weapon_shotgun_double.script" ); + orderedFiles.Append( "script/weapon_grabber.script" ); + orderedFiles.Append( "script/ai_monster_hunter_helltime.script" ); + orderedFiles.Append( "script/ai_monster_hunter_berserk.script" ); + orderedFiles.Append( "script/ai_monster_hunter_invul.script" ); + orderedFiles.Append( "script/ai_monster_boss_maledict.script" ); + orderedFiles.Append( "script/ai_monster_demon_vulgar.script" ); + orderedFiles.Append( "script/ai_monster_demon_d3xp_bruiser.script" ); + orderedFiles.Append( "script/ai_monster_dummy_target.script" ); + orderedFiles.Append( "script/ai_monster_dummy.script" ); + orderedFiles.Append( "script/ai_monster_demon_sentry.script" ); + orderedFiles.Append( "script/ai_monster_demon_trite_jump.script" ); + orderedFiles.Append( "script/ai_monster_turret_ancient.script" ); + orderedFiles.Append( "script/ai_monster_flying_forgotten.script" ); + orderedFiles.Append( "script/ai_character_erebus3.script" ); + orderedFiles.Append( "script/d3xp_airlock.script" ); + orderedFiles.Append( "script/d3xp_bloodstone.script" ); + orderedFiles.Append( "script/map_erebus1.script" ); + orderedFiles.Append( "script/map_erebus2_helltime.script" ); + orderedFiles.Append( "script/map_erebus2.script" ); + orderedFiles.Append( "script/map_erebus3.script" ); + orderedFiles.Append( "script/map_erebus4.script" ); + orderedFiles.Append( "script/map_erebus5.script" ); + orderedFiles.Append( "script/map_erebus5_cloud.script" ); + orderedFiles.Append( "script/map_erebus6.script" ); + orderedFiles.Append( "script/map_erebus6_berzerk.script" ); + orderedFiles.Append( "script/map_phobos1.script" ); + orderedFiles.Append( "script/map_phobos2.script" ); + orderedFiles.Append( "script/map_phobos2_invul.script" ); + orderedFiles.Append( "script/map_phobos3.script" ); + orderedFiles.Append( "script/map_phobos4.script" ); + orderedFiles.Append( "script/map_deltax.script" ); + orderedFiles.Append( "script/map_hell.script" ); + orderedFiles.Append( "script/map_maledict.script" ); + orderedFiles.Append( "script/d3le-ai_monster_boss_guardian2.script" ); + orderedFiles.Append( "script/ai_follower.script" ); + orderedFiles.Append( "generated/swf/shell.bswf" ); + fl = ListFilesTree( "newfonts", "*.dat", false ); + for ( int i = 0; i < fl->GetList().Num(); i++ ) { + orderedFiles.AddUnique( fl->GetList()[i] ); + } + FreeFileList( fl ); + + idResourceContainer::WriteResourceFile( "_ordered.resources", orderedFiles, false ); +} + +/* +================ +idFileSystemLocal::WriteResourcePacks +================ +*/ +void idFileSystemLocal::WriteResourcePacks() { + + idStrList filesNotCommonToAllMaps( 16384 ); // files that are not shared by all maps, used to trim the common list + idStrList filesCommonToAllMaps( 16384 ); // files that are shared by all maps, will include startup files, renderprogs etc.. + idPreloadManifest commonPreloads; // preload entries that exist in all map preload files + + idStr path = RelativePathToOSPath( "maps/", "fs_savepath" ); + + idStrList manifestFiles; + ListOSFiles( path, ".manifest", manifestFiles ); + idStrList preloadFiles; + ListOSFiles( path, ".preload", preloadFiles ); + + idList< idFileManifest > manifests; // list of all manifest files + // load all file manifests + for ( int i = 0; i < manifestFiles.Num(); i++ ) { + idStr path = "maps/"; + path += manifestFiles[ i ]; + idFileManifest manifest; + if ( manifest.LoadManifest( path ) ) { + //manifest.Print(); + manifest.RemoveAll( va( "strings/%s", ID_LANG_ENGLISH ) ); // remove all .lang files + manifest.RemoveAll( va( "strings/%s", ID_LANG_FRENCH ) ); + manifest.RemoveAll( va( "strings/%s", ID_LANG_ITALIAN ) ); + manifest.RemoveAll( va( "strings/%s", ID_LANG_GERMAN ) ); + manifest.RemoveAll( va( "strings/%s", ID_LANG_SPANISH ) ); + manifest.RemoveAll( va( "strings/%s", ID_LANG_JAPANESE ) ); + manifests.Append( manifest ); + } + } + + idList< idPreloadManifest > preloadManifests; // list of all preload manifest files + // load all preload manifests + for ( int i = 0; i < preloadFiles.Num(); i++ ) { + idStr path = "maps/"; + path += preloadFiles[ i ]; + if ( path.Find( "_startup", false ) >= 0 ) { + continue; + } + idPreloadManifest preload; + if ( preload.LoadManifest( path ) ) { + preloadManifests.Append( preload ); + //preload.Print(); + } + } + + // build common list of files + for ( int i = 0; i < manifests.Num(); i++ ) { + idFileManifest &manifest = manifests[ i ]; + for ( int j = 0; j < manifest.NumFiles(); j++ ) { + idStr name = manifest.GetFileNameByIndex( j ); + if ( name.CheckExtension( ".cfg" ) || (name.Find( ".lang", false ) >= 0) ) { + continue; + } + if ( FileExistsInAllManifests( name, manifests ) ) { + filesCommonToAllMaps.AddUnique( name ); + } else { + filesNotCommonToAllMaps.AddUnique( name ); + } + } + } + // common list of preload reosurces, image, sample or models + for ( int i = 0; i < preloadManifests.Num(); i++ ) { + idPreloadManifest &preload = preloadManifests[ i ]; + for ( int j = 0; j < preload.NumResources(); j++ ) { + idStr name = preload.GetResourceNameByIndex( j ); + if ( FileExistsInAllPreloadManifests( name, preloadManifests ) ) { + commonPreloads.Add( preload.GetPreloadByIndex( j ) ); + idLib::Printf( "Common preload added %s\n", name.c_str() ); + } else { + idLib::Printf( "preload missed %s\n", name.c_str() ); + } + } + } + + AddRenderProgs( filesCommonToAllMaps ); + AddFonts( filesCommonToAllMaps ); + + idStrList work; + + // remove all common files from each map manifest + for ( int i = 0; i < manifests.Num(); i++ ) { + if ( ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) || ( strstr( manifests[ i ].GetManifestName(), "_pc" ) != NULL ) ) { + continue; + } + //idLib::Printf( "%04d referenced files for %s\n", manifests[ i ].GetReferencedFileCount(), manifests[ i ].GetManifestName() ); + + for ( int j = 0; j < filesCommonToAllMaps.Num(); j++ ) { + manifests[ i ].RemoveAll( filesCommonToAllMaps[ j ] ); + } + //idLib::Printf( "%04d referenced files for %s\n", manifests[ i ].GetReferencedFileCount(), manifests[ i ].GetManifestName() ); + } + + idStrList commonImages( 2048 ); + idStrList commonModels( 2048 ); + idStrList commonAnims( 2048 ); + idStrList commonCollision( 2048 ); + idStrList soundFiles( 2048 ); // don't write these per map so we fit on disc + + for ( int i = 0; i < manifests.Num(); i++ ) { + idStr resourceFileName = manifests[ i ].GetManifestName(); + if ( resourceFileName.Find( "_startup.manifest", false ) >= 0 ) { + // add all the startup manifest files to the common list + for ( int j = 0; j < manifests[ i ].NumFiles(); j++ ) { + idStr check = manifests[i].GetFileNameByIndex( j ); + if ( check.CheckExtension( ".cfg" ) == false ) { + filesCommonToAllMaps.AddUnique( check.c_str() ); + } + } + continue; + } + + idStaticList< idStr, 16384 > mapFiles; // map files from the manifest, these are static for easy debugging + idStaticList< idStr, 16384 > mapFilesTwo; // accumulates non bimage, bmodel and sample files + commonImages.Clear(); // collect images and models separately so they can be added in linear preload order + commonModels.Clear(); + commonAnims.Clear(); + commonCollision.Clear(); + + manifests[ i ].PopulateList( mapFiles ); + + for ( int j = 0; j < mapFiles.Num(); j++ ) { + idStr & resName = mapFiles[ j ]; + if ( resName.Find( ".bimage", false ) >= 0 ) { + commonImages.AddUnique( resName ); + continue; + } + if ( IsBinaryModel( resName ) ) { + commonModels.AddUnique( resName ); + continue; + } + if ( IsSoundSample( resName ) ) { + soundFiles.AddUnique( resName ); + continue; + } + if ( resName.Find( ".bik", false ) >= 0 ) { + // don't add bik files + continue; + } + if ( resName.Find ( ".bmd5anim", false ) >= 0 ) { + commonAnims.AddUnique( resName ); + continue; + } + if ( resName.Find ( ".bcmodel", false ) >= 0 ) { + commonCollision.AddUnique( resName ); + continue; + } + if ( resName.Find( ".lang", false ) >= 0 ) { + continue; + } + mapFilesTwo.AddUnique( resName ); + } + + for ( int j = 0; j < commonImages.Num(); j++ ) { + mapFilesTwo.AddUnique( commonImages[ j ] ); + } + for ( int j = 0; j < commonModels.Num(); j++ ) { + mapFilesTwo.AddUnique( commonModels[ j ] ); + } + for ( int j = 0; j < commonAnims.Num(); j++ ) { + mapFilesTwo.AddUnique( commonAnims[ j ] ); + } + for ( int j = 0; j < commonCollision.Num(); j++ ) { + mapFilesTwo.AddUnique( commonCollision[ j ] ); + } + // write map resources + idStrList mapFilesToWrite; + for ( int j = 0; j < mapFilesTwo.Num(); j++ ) { + mapFilesToWrite.Append( mapFilesTwo[ j ] ); + } + idResourceContainer::WriteResourceFile( resourceFileName, mapFilesToWrite, false ); + } + + // add the new manifests just written + path = RelativePathToOSPath( "maps", "fs_savepath" ); + ListOSFiles( path, "*.preload", work ); + for ( int i = 0; i < work.Num(); i++ ) { + filesCommonToAllMaps.Append( idStr( "maps/" ) + work[ i ] ); + } + + filesCommonToAllMaps.Append( "_common.preload" ); + + // write out common models, images and sounds to separate containers + //idStrList commonSounds( 2048 ); + commonImages.Clear(); + commonModels.Clear(); + + idStrList commonFiles; + for ( int i = 0; i < filesCommonToAllMaps.Num(); i++ ) { + idStr & resName = filesCommonToAllMaps[ i ]; + if ( resName.Find( ".bimage", false ) >= 0 ) { + commonImages.AddUnique( resName ); + continue; + } + if ( IsBinaryModel( resName ) ) { + commonModels.AddUnique( resName ); + continue; + } + if ( IsSoundSample( resName ) ) { + soundFiles.AddUnique( resName ); + continue; + } + if ( resName.Find( ".bik", false ) >= 0 ) { + // no bik files in the .resource + continue; + } + if ( resName.Find( ".lang", false ) >= 0 ) { + // no bik files in the .resource + continue; + } + commonFiles.AddUnique( resName ); + } + + for ( int j = 0; j < commonImages.Num(); j++ ) { + commonFiles.AddUnique( commonImages[ j ] ); + } + for ( int j = 0; j < commonModels.Num(); j++ ) { + commonFiles.AddUnique( commonModels[ j ] ); + } + + //idResourceContainer::WriteResourceFile( "_common_images", commonImages ); + //idResourceContainer::WriteResourceFile( "_common_models", commonModels ); + + commonPreloads.WriteManifest( "_common.preload" ); + idResourceContainer::WriteResourceFile( "_common", commonFiles, false ); + + + idList< idStrList > soundOutputFiles; + soundOutputFiles.SetNum( 16 ); + + struct soundVOInfo_t { + const char *filename; + const char *voqualifier; + idStrList * samples; + }; + const soundVOInfo_t soundFileInfo[] = { + { "fr", "sound/vo/french/", &soundOutputFiles[ 0 ] }, + { "it", "sound/vo/italian/", &soundOutputFiles[ 1 ] }, + { "gr", "sound/vo/german/", &soundOutputFiles[ 2 ] }, + { "sp", "sound/vo/spanish/", &soundOutputFiles[ 3 ] }, + { "jp", "sound/vo/japanese/", &soundOutputFiles[ 4 ] }, + { "en", "sound/vo/", &soundOutputFiles[ 5 ] } // english last so the other langs are culled first + }; + const int numSoundFiles = sizeof( soundFileInfo ) / sizeof ( soundVOInfo_t ); + + for ( int k = soundFiles.Num() - 1; k > 0; k-- ) { + for ( int l = 0; l < numSoundFiles; l++ ) { + if ( soundFiles[ k ].Find( soundFileInfo[ l ].voqualifier, false ) >= 0 ) { + soundFileInfo[ l ].samples->AddUnique( soundFiles[ k ] ); + soundFiles.RemoveIndex( k ); + } + } + } + + for ( int k = 0; k < numSoundFiles; k++ ) { + idStrList & sampleList = *soundFileInfo[ k ].samples; + + // write pc + idResourceContainer::WriteResourceFile( va( "_sound_pc_%s", soundFileInfo[ k ].filename ), sampleList, false ); + for ( int l = 0; l < sampleList.Num(); l++ ) { + sampleList[ l ].Replace( ".idwav", ".idxma" ); + } + } + + idResourceContainer::WriteResourceFile( "_sound_pc", soundFiles, false ); + for ( int k = 0; k < soundFiles.Num(); k++ ) { + soundFiles[ k ].Replace( ".idwav", ".idxma" ); + } + + for ( int k = 0; k < soundFiles.Num(); k++ ) { + soundFiles[ k ].Replace( ".idxma", ".idmsf" ); + } + + BuildOrderedStartupContainer(); + + ClearResourcePacks(); +} + + +/* +================= +idFileSystemLocal::CopyFile + +Copy a fully specified file from one place to another` +================= +*/ +void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) { + + idFile * src = OpenExplicitFileRead( fromOSPath ); + if ( src == NULL ) { + idLib::Warning( "Could not open %s for read", fromOSPath ); + return; + } + + if ( idStr::Icmp( fromOSPath, toOSPath ) == 0 ) { + // same file can happen during build games + return; + } + + CopyFile( src, toOSPath ); + delete src; + + if ( strstr( fromOSPath, ".wav" ) != NULL ) { + idStrStatic< MAX_OSPATH > newFromPath = fromOSPath; + idStrStatic< MAX_OSPATH > newToPath = toOSPath; + + idLib::Printf( "Copying console samples for %s\n", newFromPath.c_str() ); + newFromPath.SetFileExtension( "xma" ); + newToPath.SetFileExtension( "xma" ); + src = OpenExplicitFileRead( newFromPath ); + if ( src == NULL ) { + idLib::Warning( "Could not open %s for read", newFromPath.c_str() ); + } else { + CopyFile( src, newToPath ); + delete src; + src = NULL; + } + + newFromPath.SetFileExtension( "msf" ); + newToPath.SetFileExtension( "msf" ); + src = OpenExplicitFileRead( newFromPath ); + if ( src == NULL ) { + idLib::Warning( "Could not open %s for read", newFromPath.c_str() ); + } else { + CopyFile( src, newToPath ); + delete src; + } + + newFromPath.BackSlashesToSlashes(); + newFromPath.ToLower(); + if ( newFromPath.Find( "/vo/", false ) >= 0 ) { + for ( int i = 0; i < Sys_NumLangs(); i++ ) { + const char *lang = Sys_Lang( i ); + if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) { + continue; + } + newFromPath = fromOSPath; + newToPath = toOSPath; + newFromPath.BackSlashesToSlashes(); + newFromPath.ToLower(); + newToPath.BackSlashesToSlashes(); + newToPath.ToLower(); + newFromPath.Replace( "/vo/", va( "/vo/%s/", lang ) ); + newToPath.Replace( "/vo/", va( "/vo/%s/", lang ) ); + + src = OpenExplicitFileRead( newFromPath ); + if ( src == NULL ) { + idLib::Warning( "LOCALIZATION PROBLEM: Could not open %s for read", newFromPath.c_str() ); + } else { + CopyFile( src, newToPath ); + delete src; + src = NULL; + } + + newFromPath.SetFileExtension( "xma" ); + newToPath.SetFileExtension( "xma" ); + src = OpenExplicitFileRead( newFromPath ); + if ( src == NULL ) { + idLib::Warning( "LOCALIZATION PROBLEM: Could not open %s for read", newFromPath.c_str() ); + } else { + CopyFile( src, newToPath ); + delete src; + src = NULL; + } + + newFromPath.SetFileExtension( "msf" ); + newToPath.SetFileExtension( "msf" ); + src = OpenExplicitFileRead( newFromPath ); + if ( src == NULL ) { + idLib::Warning( "LOCALIZATION PROBLEM: Could not open %s for read", newFromPath.c_str() ); + } else { + CopyFile( src, newToPath ); + delete src; + } + + } + } + } +} + +/* +================= +idFileSystemLocal::CopyFile +================= +*/ +void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) { + idFile * dst = OpenExplicitFileWrite( toOSPath ); + if ( dst == NULL ) { + idLib::Warning( "Could not open %s for write", toOSPath ); + return; + } + + common->Printf( "copy %s to %s\n", src->GetName(), toOSPath ); + + int len = src->Length(); + int copied = 0; + while ( copied < len ) { + byte buffer[4096]; + int read = src->Read( buffer, Min( 4096, len - copied ) ); + if ( read <= 0 ) { + idLib::Warning( "Copy failed during read" ); + break; + } + int written = dst->Write( buffer, read ); + if ( written < read ) { + idLib::Warning( "Copy failed during write" ); + break; + } + copied += written; + } + + delete dst; +} + +/* +==================== +idFileSystemLocal::ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) { + char *s; + + for( s = &path[ 0 ]; *s ; s++ ) { + if ( *s == '/' || *s == '\\' ) { + *s = sep; + } + } +} + +/* +======================== +IsOSPath +======================== +*/ +static bool IsOSPath( const char * path ) { + assert( path ); + + if ( idStr::Icmpn( path, "mtp:", 4 ) == 0 ) { + return true; + } + + + if ( idStr::Length( path ) >= 2 ) { + if ( path[ 1 ] == ':' ) { + if ( ( path[ 0 ] > 64 && path[ 0 ] < 91 ) || ( path[ 0 ] > 96 && path[ 0 ] < 123 ) ) { + // already an OS path starting with a drive. + return true; + } + } + if ( path[ 0 ] == '\\' || path[ 0 ] == '/' ) { + // a root path + return true; + } + } + return false; +} + +/* +======================== +idFileSystemLocal::BuildOSPath +======================== +*/ +const char * idFileSystemLocal::BuildOSPath( const char * base, const char * relativePath ) { + // handle case of this already being an OS path + if ( IsOSPath( relativePath ) ) { + return relativePath; + } + + return BuildOSPath( base, gameFolder, relativePath ); +} + +/* +=================== +idFileSystemLocal::BuildOSPath +=================== +*/ +const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) { + static char OSPath[MAX_STRING_CHARS]; + idStr newPath; + + // handle case of this already being an OS path + if ( IsOSPath( relativePath ) ) { + return relativePath; + } + + idStr strBase = base; + strBase.StripTrailing( '/' ); + strBase.StripTrailing( '\\' ); + sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath ); + ReplaceSeparators( newPath ); + idStr::Copynz( OSPath, newPath, sizeof( OSPath ) ); + return OSPath; +} + +/* +================ +idFileSystemLocal::OSPathToRelativePath + +takes a full OS path, as might be found in data from a media creation +program, and converts it to a relativePath by stripping off directories + +Returns false if the osPath tree doesn't match any of the existing +search paths. + +================ +*/ +const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) { + if ( ( OSPath[0] != '/' ) && ( OSPath[0] != '\\' ) && ( idStr::FindChar( OSPath, ':' ) < 0 ) ) { + // No colon and it doesn't start with a slash... it must already be a relative path + return OSPath; + } + idStaticList< idStrStatic< 32 >, 5 > basePaths; + basePaths.Append( "base" ); + basePaths.Append( "d3xp" ); + basePaths.Append( "d3le" ); + if ( fs_game.GetString()[0] != 0 ) { + basePaths.Append( fs_game.GetString() ); + } + if ( fs_game_base.GetString()[0] != 0 ) { + basePaths.Append( fs_game_base.GetString() ); + } + idStaticList slashes; + for ( const char * s = OSPath; *s != 0; s++ ) { + if ( *s == '/' || *s == '\\' ) { + slashes.Append( s - OSPath ); + } + } + for ( int n = 0; n < slashes.Num() - 1; n++ ) { + const char * start = OSPath + slashes[n] + 1; + const char * end = OSPath + slashes[n+1]; + int componentLength = end - start; + if ( componentLength == 0 ) { + continue; + } + for ( int i = 0; i < basePaths.Num(); i++ ) { + if ( componentLength != basePaths[i].Length() ) { + continue; + } + if ( basePaths[i].Icmpn( start, componentLength ) == 0 ) { + // There are some files like: + // W:\d3xp\base\... + // But we can't search backwards because there are others like: + // W:\doom3\base\models\mapobjects\base\... + // So instead we check for 2 base paths next to each other and take the 2nd in that case + if ( n < slashes.Num() - 2 ) { + const char * start2 = OSPath + slashes[n+1] + 1; + const char * end2 = OSPath + slashes[n+2]; + int componentLength2 = end2 - start2; + if ( componentLength2 > 0 ) { + for ( int j = 0; j < basePaths.Num(); j++ ) { + if ( componentLength2 != basePaths[j].Length() ) { + continue; + } + if ( basePaths[j].Icmpn( start2, basePaths[j].Length() ) == 0 ) { + return end2 + 1; + } + } + } + } + return end + 1; + } + } + } + idLib::Warning( "OSPathToRelativePath failed on %s", OSPath ); + return OSPath; +} + +/* +===================== +idFileSystemLocal::RelativePathToOSPath + +Returns a fully qualified path that can be used with stdio libraries +===================== +*/ +const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) { + const char *path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + return BuildOSPath( path, gameFolder, relativePath ); +} + +/* +================= +idFileSystemLocal::RemoveFile +================= +*/ +void idFileSystemLocal::RemoveFile( const char *relativePath ) { + idStr OSPath; + + if ( fs_basepath.GetString()[0] ) { + OSPath = BuildOSPath( fs_basepath.GetString(), gameFolder, relativePath ); + ::DeleteFile( OSPath ); + } + + OSPath = BuildOSPath( fs_savepath.GetString(), gameFolder, relativePath ); + ::DeleteFile( OSPath ); +} + +/* +======================== +idFileSystemLocal::RemoveDir +======================== +*/ +bool idFileSystemLocal::RemoveDir( const char * relativePath ) { + bool success = true; + if ( fs_savepath.GetString()[0] ) { + success &= Sys_Rmdir( BuildOSPath( fs_savepath.GetString(), relativePath ) ); + } + success &= Sys_Rmdir( BuildOSPath( fs_basepath.GetString(), relativePath ) ); + return success; +} + +/* +============ +idFileSystemLocal::ReadFile + +Filename are relative to the search path +a null buffer will just return the file length and time without loading +timestamp can be NULL if not required +============ +*/ +int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ) { + + idFile * f; + byte * buf; + int len; + bool isConfig; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + return 0; + } + + if ( relativePath == NULL || !relativePath[0] ) { + common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" ); + return 0; + } + + if ( timestamp ) { + *timestamp = FILE_NOT_FOUND_TIMESTAMP; + } + + if ( buffer ) { + *buffer = NULL; + } + + if ( buffer == NULL && timestamp != NULL && resourceFiles.Num() > 0 ) { + static idResourceCacheEntry rc; + int size = 0; + if ( GetResourceCacheEntry( relativePath, rc ) ) { + *timestamp = 0; + size = rc.length; + } + return size; + } + + buf = NULL; // quiet compiler warning + + // if this is a .cfg file and we are playing back a journal, read + // it from the journal file + if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) { + isConfig = true; + if ( eventLoop && eventLoop->JournalLevel() == 2 ) { + int r; + + loadCount++; + loadStack++; + + common->DPrintf( "Loading %s from journal file.\n", relativePath ); + len = 0; + r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) ); + if ( r != sizeof( len ) ) { + *buffer = NULL; + return -1; + } + buf = (byte *)Mem_ClearedAlloc(len+1, TAG_IDFILE); + *buffer = buf; + r = eventLoop->com_journalDataFile->Read( buf, len ); + if ( r != len ) { + common->FatalError( "Read from journalDataFile failed" ); + } + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + + return len; + } + } else { + isConfig = false; + } + + // look for it in the filesystem or pack files + f = OpenFileRead( relativePath, ( buffer != NULL ) ); + if ( f == NULL ) { + if ( buffer ) { + *buffer = NULL; + } + return -1; + } + len = f->Length(); + + if ( timestamp ) { + *timestamp = f->Timestamp(); + } + + if ( !buffer ) { + CloseFile( f ); + return len; + } + + loadCount++; + loadStack++; + + buf = (byte *)Mem_ClearedAlloc(len+1, TAG_IDFILE); + *buffer = buf; + + f->Read( buf, len ); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + CloseFile( f ); + + // if we are journalling and it is a config file, write it to the journal file + if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) { + common->DPrintf( "Writing %s to journal file.\n", relativePath ); + eventLoop->com_journalDataFile->Write( &len, sizeof( len ) ); + eventLoop->com_journalDataFile->Write( buf, len ); + eventLoop->com_journalDataFile->Flush(); + } + + return len; +} + +/* +============= +idFileSystemLocal::FreeFile +============= +*/ +void idFileSystemLocal::FreeFile( void *buffer ) { + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + common->FatalError( "idFileSystemLocal::FreeFile( NULL )" ); + } + loadStack--; + + Mem_Free( buffer ); +} + +/* +============ +idFileSystemLocal::WriteFile + +Filenames are relative to the search path +============ +*/ +int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) { + idFile *f; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !relativePath || !buffer ) { + common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" ); + } + + f = idFileSystemLocal::OpenFileWrite( relativePath, basePath ); + if ( !f ) { + common->Printf( "Failed to open %s\n", relativePath ); + return -1; + } + + size = f->Write( buffer, size ); + + CloseFile( f ); + + return size; +} + +/* +======================== +idFileSystemLocal::RenameFile +======================== +*/ +bool idFileSystemLocal::RenameFile( const char * relativePath, const char * newName, const char * basePath ) { + const char * path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + + idStr oldOSPath = BuildOSPath( path, gameFolder, relativePath ); + idStr newOSPath = BuildOSPath( path, gameFolder, newName ); + + // this gives atomic-delete-on-rename, like POSIX rename() + // There is a MoveFileTransacted() on vista and above, not sure if that means there + // is a race condition inside MoveFileEx... + const bool success = ( MoveFileEx( oldOSPath.c_str(), newOSPath.c_str(), MOVEFILE_REPLACE_EXISTING ) != 0 ); + + if ( !success ) { + const int err = GetLastError(); + idLib::Warning( "RenameFile( %s, %s ) error %i", newOSPath.c_str(), oldOSPath.c_str(), err ); + } + return success; +} + +/* +=============== +idFileSystemLocal::AddUnique +=============== +*/ +int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const { + int i, hashKey; + + hashKey = hashIndex.GenerateKey( name ); + for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) { + if ( list[i].Icmp( name ) == 0 ) { + return i; + } + } + i = list.Append( name ); + hashIndex.Add( hashKey, i ); + return i; +} + +/* +=============== +idFileSystemLocal::GetExtensionList +=============== +*/ +void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const { + int s, e, l; + + l = idStr::Length( extension ); + s = 0; + while( 1 ) { + e = idStr::FindChar( extension, '|', s, l ); + if ( e != -1 ) { + extensionList.Append( idStr( extension, s, e ) ); + s = e + 1; + } else { + extensionList.Append( idStr( extension, s, l ) ); + break; + } + } +} + +/* +=============== +idFileSystemLocal::GetFileList + +Does not clear the list first so this can be used to progressively build a file list. +When 'sort' is true only the new files added to the list are sorted. +=============== +*/ +int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char * gamedir ) { + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !extensions.Num() ) { + return 0; + } + + if ( !relativePath ) { + return 0; + } + + int pathLength = strlen( relativePath ); + if ( pathLength ) { + pathLength++; // for the trailing '/' + } + + idStrStatic< MAX_OSPATH > strippedName; + if ( resourceFiles.Num() > 0 ) { + int idx = resourceFiles.Num() - 1; + while ( idx >= 0 ) { + for ( int i = 0; i < resourceFiles[ idx ]->cacheTable.Num(); i++ ) { + idResourceCacheEntry & rt = resourceFiles[ idx ]->cacheTable[ i ]; + // if the name is not long anough to at least contain the path + + if ( rt.filename.Length() <= pathLength ) { + continue; + } + + // check for a path match without the trailing '/' + if ( pathLength && idStr::Icmpn( rt.filename, relativePath, pathLength - 1 ) != 0 ) { + continue; + } + + // ensure we have a path, and not just a filename containing the path + if ( rt.filename[ pathLength ] == '\0' || rt.filename[pathLength - 1] != '/' ) { + continue; + } + + // make sure the file is not in a subdirectory + int j = pathLength; + for ( ; rt.filename[j+1] != '\0'; j++ ) { + if ( rt.filename[ j ] == '/' ) { + break; + } + } + if ( rt.filename[ j + 1 ] ) { + continue; + } + + // check for extension match + for ( j = 0; j < extensions.Num(); j++ ) { + if ( rt.filename.Length() >= extensions[j].Length() && extensions[j].Icmp( rt.filename.c_str() + rt.filename.Length() - extensions[j].Length() ) == 0 ) { + break; + } + } + if ( j >= extensions.Num() ) { + continue; + } + + // unique the match + if ( fullRelativePath ) { + idStr work = relativePath; + work += "/"; + work += rt.filename.c_str() + pathLength; + work.StripTrailing( '/' ); + AddUnique( work, list, hashIndex ); + } else { + idStr work = rt.filename.c_str() + pathLength; + work.StripTrailing( '/' ); + AddUnique( work, list, hashIndex ); + } + } + idx--; + } + } + + // search through the path, one element at a time, adding to list + for ( int sp = searchPaths.Num() - 1; sp >= 0; sp-- ) { + if ( gamedir != NULL && gamedir[0] != 0 ) { + if ( searchPaths[sp].gamedir != gamedir) { + continue; + } + } + + idStr netpath = BuildOSPath( searchPaths[sp].path, searchPaths[sp].gamedir, relativePath ); + + for ( int i = 0; i < extensions.Num(); i++ ) { + + // scan for files in the filesystem + idStrList sysFiles; + ListOSFiles( netpath, extensions[i], sysFiles ); + + // if we are searching for directories, remove . and .. + if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) { + sysFiles.Remove( "." ); + sysFiles.Remove( ".." ); + } + + for ( int j = 0; j < sysFiles.Num(); j++ ) { + // unique the match + if ( fullRelativePath ) { + idStr work = relativePath; + work += "/"; + work += sysFiles[j]; + AddUnique( work, list, hashIndex ); + } else { + AddUnique( sysFiles[j], list, hashIndex ); + } + } + } + } + + return list.Num(); +} + +/* +=============== +idFileSystemLocal::ListFiles +=============== +*/ +idFileList *idFileSystemLocal::ListFiles( const char *relativePath, const char *extension, bool sort, bool fullRelativePath, const char* gamedir ) { + idHashIndex hashIndex( 4096, 4096 ); + idStrList extensionList; + + idFileList *fileList = new (TAG_IDFILE) idFileList; + fileList->basePath = relativePath; + + GetExtensionList( extension, extensionList ); + + GetFileList( relativePath, extensionList, fileList->list, hashIndex, fullRelativePath, gamedir ); + + if ( sort ) { + fileList->list.SortWithTemplate( idSort_PathStr() ); + } + + return fileList; +} + +/* +=============== +idFileSystemLocal::GetFileListTree +=============== +*/ +int idFileSystemLocal::GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir ) { + int i; + idStrList slash, folders( 128 ); + idHashIndex folderHashIndex( 1024, 128 ); + + // recurse through the subdirectories + slash.Append( "/" ); + GetFileList( relativePath, slash, folders, folderHashIndex, true, gamedir ); + for ( i = 0; i < folders.Num(); i++ ) { + if ( folders[i][0] == '.' ) { + continue; + } + if ( folders[i].Icmp( relativePath ) == 0 ){ + continue; + } + GetFileListTree( folders[i], extensions, list, hashIndex, gamedir ); + } + + // list files in the current directory + GetFileList( relativePath, extensions, list, hashIndex, true, gamedir ); + + return list.Num(); +} + +/* +=============== +idFileSystemLocal::ListFilesTree +=============== +*/ +idFileList *idFileSystemLocal::ListFilesTree( const char *relativePath, const char *extension, bool sort, const char* gamedir ) { + idHashIndex hashIndex( 4096, 4096 ); + idStrList extensionList; + + idFileList *fileList = new (TAG_IDFILE) idFileList(); + fileList->basePath = relativePath; + fileList->list.SetGranularity( 4096 ); + + GetExtensionList( extension, extensionList ); + + GetFileListTree( relativePath, extensionList, fileList->list, hashIndex, gamedir ); + + if ( sort ) { + fileList->list.SortWithTemplate( idSort_PathStr() ); + } + + return fileList; +} + +/* +=============== +idFileSystemLocal::FreeFileList +=============== +*/ +void idFileSystemLocal::FreeFileList( idFileList *fileList ) { + delete fileList; +} + +/* +=============== +idFileSystemLocal::ListOSFiles + + call to the OS for a listing of files in an OS directory +=============== +*/ +int idFileSystemLocal::ListOSFiles( const char *directory, const char *extension, idStrList &list ) { + if ( !extension ) { + extension = ""; + } + + return Sys_ListFiles( directory, extension, list ); +} + +/* +================ +idFileSystemLocal::Dir_f +================ +*/ +void idFileSystemLocal::Dir_f( const idCmdArgs &args ) { + idStr relativePath; + idStr extension; + idFileList *fileList; + int i; + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + common->Printf( "usage: dir [extension]\n" ); + return; + } + + if ( args.Argc() == 2 ) { + relativePath = args.Argv( 1 ); + extension = ""; + } + else { + relativePath = args.Argv( 1 ); + extension = args.Argv( 2 ); + if ( extension[0] != '.' ) { + common->Warning( "extension should have a leading dot" ); + } + } + relativePath.BackSlashesToSlashes(); + relativePath.StripTrailing( '/' ); + + common->Printf( "Listing of %s/*%s\n", relativePath.c_str(), extension.c_str() ); + common->Printf( "---------------\n" ); + + fileList = fileSystemLocal.ListFiles( relativePath, extension ); + + for ( i = 0; i < fileList->GetNumFiles(); i++ ) { + common->Printf( "%s\n", fileList->GetFile( i ) ); + } + common->Printf( "%d files\n", fileList->list.Num() ); + + fileSystemLocal.FreeFileList( fileList ); +} + +/* +================ +idFileSystemLocal::DirTree_f +================ +*/ +void idFileSystemLocal::DirTree_f( const idCmdArgs &args ) { + idStr relativePath; + idStr extension; + idFileList *fileList; + int i; + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + common->Printf( "usage: dirtree [extension]\n" ); + return; + } + + if ( args.Argc() == 2 ) { + relativePath = args.Argv( 1 ); + extension = ""; + } + else { + relativePath = args.Argv( 1 ); + extension = args.Argv( 2 ); + if ( extension[0] != '.' ) { + common->Warning( "extension should have a leading dot" ); + } + } + relativePath.BackSlashesToSlashes(); + relativePath.StripTrailing( '/' ); + + common->Printf( "Listing of %s/*%s /s\n", relativePath.c_str(), extension.c_str() ); + common->Printf( "---------------\n" ); + + fileList = fileSystemLocal.ListFilesTree( relativePath, extension ); + + for ( i = 0; i < fileList->GetNumFiles(); i++ ) { + common->Printf( "%s\n", fileList->GetFile( i ) ); + } + common->Printf( "%d files\n", fileList->list.Num() ); + + fileSystemLocal.FreeFileList( fileList ); +} + +/* +================ +idFileSystemLocal::ClearResourcePacks +================ +*/ +void idFileSystemLocal::ClearResourcePacks() { +} + +/* +================ +idFileSystemLocal::BuildGame_f +================ +*/ +void idFileSystemLocal::BuildGame_f( const idCmdArgs &args ) { + fileSystemLocal.WriteResourcePacks(); +} + +/* +================ +idFileSystemLocal::WriteResourceFile_f +================ +*/ +void idFileSystemLocal::WriteResourceFile_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "Usage: writeResourceFile \n" ); + return; + } + + idStrList manifest; + idResourceContainer::ReadManifestFile( args.Argv( 1 ), manifest ); + idResourceContainer::WriteResourceFile( args.Argv( 1 ), manifest, false ); +} + + +/* +================ +idFileSystemLocal::UpdateResourceFile_f +================ +*/ +void idFileSystemLocal::UpdateResourceFile_f( const idCmdArgs &args ) { + if ( args.Argc() < 3 ) { + common->Printf( "Usage: updateResourceFile \n" ); + return; + } + + idStr filename = args.Argv( 1 ); + idStrList filesToAdd; + for ( int i = 2; i < args.Argc(); i++ ) { + filesToAdd.Append( args.Argv( i ) ); + } + idResourceContainer::UpdateResourceFile( filename, filesToAdd ); +} + +/* +================ +idFileSystemLocal::ExtractResourceFile_f +================ +*/ +void idFileSystemLocal::ExtractResourceFile_f( const idCmdArgs &args ) { + if ( args.Argc() < 3 ) { + common->Printf( "Usage: extractResourceFile \n" ); + return; + } + + idStr filename = args.Argv( 1 ); + idStr outPath = args.Argv( 2 ); + bool copyWaves = ( args.Argc() > 3 ); + idResourceContainer::ExtractResourceFile( filename, outPath, copyWaves ); +} + +/* +============ +idFileSystemLocal::Path_f +============ +*/ +void idFileSystemLocal::Path_f( const idCmdArgs &args ) { + common->Printf( "Current search path:\n" ); + for ( int i = 0; i < fileSystemLocal.searchPaths.Num(); i++ ) { + common->Printf( "%s/%s\n", fileSystemLocal.searchPaths[i].path.c_str(), fileSystemLocal.searchPaths[i].gamedir.c_str() ); + } +} + +/* +============ +idFileSystemLocal::TouchFile_f + +The only purpose of this function is to allow game script files to copy +arbitrary files furing an "fs_copyfiles 1" run. +============ +*/ +void idFileSystemLocal::TouchFile_f( const idCmdArgs &args ) { + idFile *f; + + if ( args.Argc() != 2 ) { + common->Printf( "Usage: touchFile \n" ); + return; + } + + f = fileSystemLocal.OpenFileRead( args.Argv( 1 ) ); + if ( f ) { + fileSystemLocal.CloseFile( f ); + } +} + +/* +============ +idFileSystemLocal::TouchFileList_f + +Takes a text file and touches every file in it, use one file per line. +============ +*/ +void idFileSystemLocal::TouchFileList_f( const idCmdArgs &args ) { + + if ( args.Argc() != 2 ) { + common->Printf( "Usage: touchFileList \n" ); + return; + } + + const char *buffer = NULL; + idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + if ( fileSystem->ReadFile( args.Argv( 1 ), ( void** )&buffer, NULL ) && buffer ) { + src.LoadMemory( buffer, strlen( buffer ), args.Argv( 1 ) ); + if ( src.IsLoaded() ) { + idToken token; + while( src.ReadToken( &token ) ) { + common->Printf( "%s\n", token.c_str() ); + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + idFile *f = fileSystemLocal.OpenFileRead( token ); + if ( f ) { + fileSystemLocal.CloseFile( f ); + } + } + } + } + +} + +/* +============ +idFileSystemLocal::GenerateResourceCRCs_f + +Generates a CRC checksum file for each .resources file. +============ +*/ +void idFileSystemLocal::GenerateResourceCRCs_f( const idCmdArgs &args ) { + idLib::Printf( "Generating CRCs for resource files...\n" ); + + std::auto_ptr baseResourceFileList( fileSystem->ListFiles( ".", ".resources" ) ); + if ( baseResourceFileList.get() != NULL ) { + CreateCRCsForResourceFileList ( *baseResourceFileList ); + } + + std::auto_ptr mapResourceFileList( fileSystem->ListFilesTree( "maps", ".resources" ) ); + if ( mapResourceFileList.get() != NULL ) { + CreateCRCsForResourceFileList ( *mapResourceFileList ); + } + + idLib::Printf( "Done generating CRCs for resource files.\n" ); +} + +/* +================ +idFileSystemLocal::CreateCRCsForResourceFileList +================ +*/ +void idFileSystemLocal::CreateCRCsForResourceFileList( const idFileList & list ) { + for ( int fileIndex = 0; fileIndex < list.GetNumFiles(); ++fileIndex ) { + idLib::Printf( " Processing %s.\n", list.GetFile( fileIndex ) ); + + std::auto_ptr currentFile( static_cast( fileSystem->OpenFileReadMemory( list.GetFile( fileIndex ) ) ) ); + + if ( currentFile.get() == NULL ) { + idLib::Printf( " Error reading %s.\n", list.GetFile( fileIndex ) ); + continue; + } + + uint32 resourceMagic; + currentFile->ReadBig( resourceMagic ); + + if ( resourceMagic != RESOURCE_FILE_MAGIC ) { + idLib::Printf( "Resource file magic number doesn't match, skipping %s.\n", list.GetFile( fileIndex ) ); + continue; + } + + int tableOffset; + currentFile->ReadBig( tableOffset ); + + int tableLength; + currentFile->ReadBig( tableLength ); + + // Read in the table + currentFile->Seek( tableOffset, FS_SEEK_SET ); + + int numFileResources; + currentFile->ReadBig( numFileResources ); + + idList< idResourceCacheEntry > cacheEntries; + cacheEntries.SetNum( numFileResources ); + + for ( int innerFileIndex = 0; innerFileIndex < numFileResources; ++innerFileIndex ) { + cacheEntries[innerFileIndex].Read( currentFile.get() ); + } + + // All tables read, now seek to each one and calculate the CRC. + idTempArray< unsigned long > innerFileCRCs( numFileResources ); + for ( int innerFileIndex = 0; innerFileIndex < numFileResources; ++innerFileIndex ) { + const char * innerFileDataBegin = currentFile->GetDataPtr() + cacheEntries[innerFileIndex].offset; + + innerFileCRCs[innerFileIndex] = CRC32_BlockChecksum( innerFileDataBegin, cacheEntries[innerFileIndex].length ); + } + + // Get the CRC for all the CRCs. + const unsigned long totalCRC = CRC32_BlockChecksum( innerFileCRCs.Ptr(), innerFileCRCs.Size() ); + + // Write the .crc file corresponding to the .resources file. + idStr crcFilename = list.GetFile( fileIndex ); + crcFilename.SetFileExtension( ".crc" ); + std::auto_ptr crcOutputFile( fileSystem->OpenFileWrite( crcFilename, "fs_basepath" ) ); + if ( crcOutputFile.get() == NULL ) { + idLib::Printf( "Error writing CRC file %s.\n", crcFilename ); + continue; + } + + const uint32 CRC_FILE_MAGIC = 0xCC00CC00; // I just made this up, it has no meaning. + const uint32 CRC_FILE_VERSION = 1; + crcOutputFile->WriteBig( CRC_FILE_MAGIC ); + crcOutputFile->WriteBig( CRC_FILE_VERSION ); + crcOutputFile->WriteBig( totalCRC ); + crcOutputFile->WriteBig( numFileResources ); + crcOutputFile->WriteBigArray( innerFileCRCs.Ptr(), numFileResources ); + } +} + +/* +================ +idFileSystemLocal::AddResourceFile +================ +*/ +int idFileSystemLocal::AddResourceFile( const char * resourceFileName ) { + idStrStatic< MAX_OSPATH > resourceFile = va( "maps/%s", resourceFileName ); + idResourceContainer *rc = new idResourceContainer(); + if ( rc->Init( resourceFile, resourceFiles.Num() ) ) { + resourceFiles.Append( rc ); + common->Printf( "Loaded resource file %s\n", resourceFile.c_str() ); + return resourceFiles.Num() - 1; + } + return -1; +} + +/* +================ +idFileSystemLocal::FindResourceFile +================ +*/ +int idFileSystemLocal::FindResourceFile( const char * resourceFileName ) { + for ( int i = 0; i < resourceFiles.Num(); i++ ) { + if ( idStr::Icmp( resourceFileName, resourceFiles[ i ]->GetFileName() ) == 0 ) { + return i; + } + } + return -1; +} +/* +================ +idFileSystemLocal::RemoveResourceFileByIndex +================ +*/ +void idFileSystemLocal::RemoveResourceFileByIndex( const int &idx ) { + if ( idx >= 0 && idx < resourceFiles.Num() ) { + if ( idx >= 0 && idx < resourceFiles.Num() ) { + delete resourceFiles[ idx ]; + resourceFiles.RemoveIndex( idx ); + for ( int i = 0; i < resourceFiles.Num(); i++ ) { + // fixup any container indexes + resourceFiles[ i ]->SetContainerIndex( i ); + } + } + } +} + +/* +================ +idFileSystemLocal::RemoveMapResourceFile +================ +*/ +void idFileSystemLocal::RemoveMapResourceFile( const char * resourceFileName ) { + int idx = FindResourceFile( va( "maps/%s", resourceFileName ) ); + if ( idx >= 0 ) { + RemoveResourceFileByIndex( idx ); + } +} + +/* +================ +idFileSystemLocal::RemoveResourceFile +================ +*/ +void idFileSystemLocal::RemoveResourceFile( const char * resourceFileName ) { + int idx = FindResourceFile( resourceFileName ); + if ( idx >= 0 ) { + RemoveResourceFileByIndex( idx ); + } +} + +/* +================ +idFileSystemLocal::AddGameDirectory + +Sets gameFolder, adds the directory to the head of the search paths +================ +*/ +void idFileSystemLocal::AddGameDirectory( const char *path, const char *dir ) { + // check if the search path already exists + for ( int i = 0; i < searchPaths.Num(); i++ ) { + if ( searchPaths[i].path.Cmp( path ) == 0 && searchPaths[i].gamedir.Cmp( dir ) == 0 ) { + return; + } + } + + gameFolder = dir; + + // + // add the directory to the search path + // + searchpath_t & search = searchPaths.Alloc(); + search.path = path; + search.gamedir = dir; + + idStr pakfile = BuildOSPath( path, dir, "" ); + pakfile[ pakfile.Length() - 1 ] = 0; // strip the trailing slash + + idStrList pakfiles; + ListOSFiles( pakfile, ".resources", pakfiles ); + pakfiles.SortWithTemplate( idSort_PathStr() ); + if ( pakfiles.Num() > 0 ) { + // resource files present, ignore pak files + for ( int i = 0; i < pakfiles.Num(); i++ ) { + pakfile = pakfiles[i]; //BuildOSPath( path, dir, pakfiles[i] ); + idResourceContainer *rc = new idResourceContainer(); + if ( rc->Init( pakfile, resourceFiles.Num() ) ) { + resourceFiles.Append( rc ); + common->Printf( "Loaded resource file %s\n", pakfile.c_str() ); + //com_productionMode.SetInteger( 2 ); + } + } + } +} + +/* +================ +idFileSystemLocal::SetupGameDirectories + + Takes care of the correct search order. +================ +*/ +void idFileSystemLocal::SetupGameDirectories( const char *gameName ) { + // setup basepath + if ( fs_basepath.GetString()[0] ) { + AddGameDirectory( fs_basepath.GetString(), gameName ); + } + // setup savepath + if ( fs_savepath.GetString()[0] ) { + AddGameDirectory( fs_savepath.GetString(), gameName ); + } +} + + +const char *cachedStartupFiles[] = { + "game:\\base\\video\\loadvideo.bik" +}; +const int numStartupFiles = sizeof( cachedStartupFiles ) / sizeof ( cachedStartupFiles[ 0 ] ); + +const char *cachedNormalFiles[] = { + "game:\\base\\_sound_xenon_en.resources", // these will fail silently on the files that are not on disc + "game:\\base\\_sound_xenon_fr.resources", + "game:\\base\\_sound_xenon_jp.resources", + "game:\\base\\_sound_xenon_sp.resources", + "game:\\base\\_sound_xenon_it.resources", + "game:\\base\\_sound_xenon_gr.resources", + "game:\\base\\_sound_xenon.resources", + "game:\\base\\_common.resources", + "game:\\base\\_ordered.resources", + "game:\\base\\video\\mars_rotation.bik" // cache this to save the consumer from hearing SEEK.. SEEK... SEEK.. SEEK SEEEK while at the main menu +}; +const int numNormalFiles = sizeof( cachedNormalFiles ) / sizeof ( cachedNormalFiles[ 0 ] ); + +const char *dontCacheFiles[] = { + "game:\\base\\maps\\*.*", // these will fail silently on the files that are not on disc + "game:\\base\\video\\*.*", + "game:\\base\\sound\\*.*", +}; +const int numDontCacheFiles = sizeof( dontCacheFiles ) / sizeof ( dontCacheFiles[ 0 ] ); + +/* +================ +idFileSystemLocal::InitPrecache +================ +*/ +void idFileSystemLocal::InitPrecache() { + if ( !fs_enableBackgroundCaching.GetBool() ) { + return; + } + numFilesOpenedAsCached = 0; +} + +/* +================ +idFileSystemLocal::ReOpenCacheFiles +================ +*/ +void idFileSystemLocal::ReOpenCacheFiles() { + + if ( !fs_enableBackgroundCaching.GetBool() ) { + return; + } +} + + +/* +================ +idFileSystemLocal::Startup +================ +*/ +void idFileSystemLocal::Startup() { + common->Printf( "------ Initializing File System ------\n" ); + + InitPrecache(); + + SetupGameDirectories( BASE_GAMEDIR ); + + // fs_game_base override + if ( fs_game_base.GetString()[0] && + idStr::Icmp( fs_game_base.GetString(), BASE_GAMEDIR ) ) { + SetupGameDirectories( fs_game_base.GetString() ); + } + + // fs_game override + if ( fs_game.GetString()[0] && + idStr::Icmp( fs_game.GetString(), BASE_GAMEDIR ) && + idStr::Icmp( fs_game.GetString(), fs_game_base.GetString() ) ) { + SetupGameDirectories( fs_game.GetString() ); + } + + // add our commands + cmdSystem->AddCommand( "dir", Dir_f, CMD_FL_SYSTEM, "lists a folder", idCmdSystem::ArgCompletion_FileName ); + cmdSystem->AddCommand( "dirtree", DirTree_f, CMD_FL_SYSTEM, "lists a folder with subfolders" ); + cmdSystem->AddCommand( "path", Path_f, CMD_FL_SYSTEM, "lists search paths" ); + cmdSystem->AddCommand( "touchFile", TouchFile_f, CMD_FL_SYSTEM, "touches a file" ); + cmdSystem->AddCommand( "touchFileList", TouchFileList_f, CMD_FL_SYSTEM, "touches a list of files" ); + + cmdSystem->AddCommand( "buildGame", BuildGame_f, CMD_FL_SYSTEM, "builds game pak files" ); + cmdSystem->AddCommand( "writeResourceFile", WriteResourceFile_f, CMD_FL_SYSTEM, "writes a .resources file from a supplied manifest" ); + cmdSystem->AddCommand( "extractResourceFile", ExtractResourceFile_f, CMD_FL_SYSTEM, "extracts to the supplied resource file to the supplied path" ); + cmdSystem->AddCommand( "updateResourceFile", UpdateResourceFile_f, CMD_FL_SYSTEM, "updates or appends the supplied files in the supplied resource file" ); + + cmdSystem->AddCommand( "generateResourceCRCs", GenerateResourceCRCs_f, CMD_FL_SYSTEM, "Generates CRC checksums for all the resource files." ); + + // print the current search paths + Path_f( idCmdArgs() ); + + common->Printf( "file system initialized.\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +================ +idFileSystemLocal::Init + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void idFileSystemLocal::Init() { + // allow command line parms to override our defaults + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + common->StartupVariable( "fs_basepath" ); + common->StartupVariable( "fs_savepath" ); + common->StartupVariable( "fs_game" ); + common->StartupVariable( "fs_game_base" ); + common->StartupVariable( "fs_copyfiles" ); + + if ( fs_basepath.GetString()[0] == '\0' ) { + fs_basepath.SetString( Sys_DefaultBasePath() ); + } + if ( fs_savepath.GetString()[0] == '\0' ) { + fs_savepath.SetString( Sys_DefaultSavePath() ); + } + + // try to start up normally + Startup(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + // Dedicated servers can run with no outside files at all + if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) { + common->FatalError( "Couldn't load default.cfg" ); + } +} + +/* +================ +idFileSystemLocal::Restart +================ +*/ +void idFileSystemLocal::Restart() { + // free anything we currently have loaded + Shutdown( true ); + + Startup(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) { + common->FatalError( "Couldn't load default.cfg" ); + } +} + +/* +================ +idFileSystemLocal::Shutdown + +Frees all resources and closes all files +================ +*/ +void idFileSystemLocal::Shutdown( bool reloading ) { + gameFolder.Clear(); + searchPaths.Clear(); + + resourceFiles.DeleteContents(); + + + cmdSystem->RemoveCommand( "path" ); + cmdSystem->RemoveCommand( "dir" ); + cmdSystem->RemoveCommand( "dirtree" ); + cmdSystem->RemoveCommand( "touchFile" ); +} + +/* +================ +idFileSystemLocal::IsInitialized +================ +*/ +bool idFileSystemLocal::IsInitialized() const { + return ( searchPaths.Num() != 0 ); +} + + +/* +================================================================================= + +Opening files + +================================================================================= +*/ + +/* +======================== +idFileSystemLocal::GetResourceCacheEntry + +Returns false if the entry isn't found +======================== +*/ +bool idFileSystemLocal::GetResourceCacheEntry( const char *fileName, idResourceCacheEntry &rc ) { + idStrStatic< MAX_OSPATH > canonical; + if ( strstr( fileName, ":") != NULL ) { + // os path, convert to relative? scripts can pass in an OS path + //idLib::Printf( "RESOURCE: os path passed %s\n", fileName ); + return NULL; + } else { + canonical = fileName; + } + + canonical.BackSlashesToSlashes(); + canonical.ToLower(); + int idx = resourceFiles.Num() - 1; + while ( idx >= 0 ) { + const int key = resourceFiles[ idx ]->cacheHash.GenerateKey( canonical, false ); + for ( int index = resourceFiles[ idx ]->cacheHash.GetFirst( key ); index != idHashIndex::NULL_INDEX; index = resourceFiles[ idx ]->cacheHash.GetNext( index ) ) { + idResourceCacheEntry & rt = resourceFiles[ idx ]->cacheTable[ index ]; + if ( idStr::Icmp( rt.filename, canonical ) == 0 ) { + rc.filename = rt.filename; + rc.length = rt.length; + rc.containerIndex = idx; + rc.offset = rt.offset; + return true; + } + } + idx--; + } + return false; +} + +/* +======================== +idFileSystemLocal::GetResourceFile + +Returns NULL +======================== +*/ + +idFile * idFileSystemLocal::GetResourceFile( const char *fileName, bool memFile ) { + + if ( resourceFiles.Num() == 0 ) { + return NULL; + } + + static idResourceCacheEntry rc; + if ( GetResourceCacheEntry( fileName, rc ) ) { + if ( fs_debugResources.GetBool() ) { + idLib::Printf( "RES: loading file %s\n", rc.filename.c_str() ); + } + idFile_InnerResource *file = new idFile_InnerResource( rc.filename, resourceFiles[ rc.containerIndex ]->resourceFile, rc.offset, rc.length ); + if ( file != NULL && ( memFile || rc.length <= resourceBufferAvailable ) || rc.length < 8 * 1024 * 1024 ) { + byte *buf = NULL; + if ( rc.length < resourceBufferAvailable ) { + buf = resourceBufferPtr; + resourceBufferAvailable = 0; + } else { + if ( fs_debugResources.GetBool() ) { + idLib::Printf( "MEM: Allocating %05d bytes for a resource load\n", rc.length ); +} + buf = ( byte * )Mem_Alloc( rc.length, TAG_TEMP ); + } + file->Read( (void*)buf, rc.length ); + + if ( buf == resourceBufferPtr ) { + file->SetResourceBuffer( buf ); + return file; + } else { + idFile_Memory *mfile = new idFile_Memory( rc.filename, ( const char * )buf, rc.length ); + if ( mfile != NULL ) { + mfile->TakeDataOwnership(); + delete file; + return mfile; + } + } + } + return file; + } + + return NULL; +} + + +/* +=========== +idFileSystemLocal::OpenFileReadFlags + +Finds the file in the search path, following search flag recommendations +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +idFile *idFileSystemLocal::OpenFileReadFlags( const char *relativePath, int searchFlags, bool allowCopyFiles, const char* gamedir ) { + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + return NULL; + } + + if ( relativePath == NULL ) { + common->FatalError( "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n" ); + return NULL; + } + + // qpaths are not supposed to have a leading slash + if ( relativePath[0] == '/' || relativePath[0] == '\\' ) { + relativePath++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) { + return NULL; + } + + // edge case + if ( relativePath[0] == '\0' ) { + return NULL; + } + + if ( fs_debug.GetBool() ) { + idLib::Printf( "FILE DEBUG: opening %s\n", relativePath ); + } + + if ( resourceFiles.Num() > 0 && fs_resourceLoadPriority.GetInteger() == 1 ) { + idFile * rf = GetResourceFile( relativePath, ( searchFlags & FSFLAG_RETURN_FILE_MEM ) != 0 ); + if ( rf != NULL ) { + return rf; + } + } + + // + // search through the path, one element at a time + // + if ( searchFlags & FSFLAG_SEARCH_DIRS ) { + for ( int sp = searchPaths.Num() - 1; sp >= 0; sp-- ) { + if ( gamedir != NULL && gamedir[0] != 0 ) { + if ( searchPaths[sp].gamedir != gamedir ) { + continue; + } + } + + idStr netpath = BuildOSPath( searchPaths[sp].path, searchPaths[sp].gamedir, relativePath ); + idFileHandle fp = OpenOSFile( netpath, FS_READ ); + if ( !fp ) { + continue; + } + + idFile_Permanent * file = new (TAG_IDFILE) idFile_Permanent(); + file->o = fp; + file->name = relativePath; + file->fullPath = netpath; + file->mode = ( 1 << FS_READ ); + file->fileSize = DirectFileLength( file->o ); + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s/%s')\n", relativePath, searchPaths[sp].path.c_str(), searchPaths[sp].gamedir.c_str() ); + } + + // if fs_copyfiles is set + if ( allowCopyFiles ) { + + idStr copypath; + idStr name; + copypath = BuildOSPath( fs_savepath.GetString(), searchPaths[sp].gamedir, relativePath ); + netpath.ExtractFileName( name ); + copypath.StripFilename(); + copypath += PATHSEPARATOR_STR; + copypath += name; + + if ( fs_buildResources.GetBool() ) { + idStrStatic< MAX_OSPATH > relativePath = OSPathToRelativePath( copypath ); + relativePath.BackSlashesToSlashes(); + relativePath.ToLower(); + + if ( IsSoundSample( relativePath ) ) { + idStrStatic< MAX_OSPATH > samplePath = relativePath; + samplePath.SetFileExtension( "idwav" ); + if ( samplePath.Find( "generated/" ) == -1 ) { + samplePath.Insert( "generated/", 0 ); + } + fileManifest.AddUnique( samplePath ); + if ( relativePath.Find( "/vo/", false ) >= 0 ) { + // this is vo so add the language variants + for ( int i = 0; i < Sys_NumLangs(); i++ ) { + const char *lang = Sys_Lang( i ); + if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) { + continue; + } + samplePath = relativePath; + samplePath.Replace( "/vo/", va( "/vo/%s/", lang ) ); + samplePath.SetFileExtension( "idwav" ); + if ( samplePath.Find( "generated/" ) == -1 ) { + samplePath.Insert( "generated/", 0 ); + } + fileManifest.AddUnique( samplePath ); + + } + } + } else if ( relativePath.Icmpn( "guis/", 5 ) == 0 ) { + // this is a gui so add the language variants + for ( int i = 0; i < Sys_NumLangs(); i++ ) { + const char *lang = Sys_Lang( i ); + if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) { + fileManifest.Append( relativePath ); + continue; + } + idStrStatic< MAX_OSPATH > guiPath = relativePath; + guiPath.Replace( "guis/", va( "guis/%s/", lang ) ); + fileManifest.Append( guiPath ); + } + } else { + // never add .amp files + if ( strstr( relativePath, ".amp" ) == NULL ) { + fileManifest.Append( relativePath ); + } + } + + } + + if ( fs_copyfiles.GetBool() ) { + CopyFile( netpath, copypath ); + } + } + + if ( searchFlags & FSFLAG_RETURN_FILE_MEM ) { + idFile_Memory * memFile = new (TAG_IDFILE) idFile_Memory( file->name ); + memFile->SetLength( file->fileSize ); + file->Read( (void *)memFile->GetDataPtr(), file->fileSize ); + delete file; + memFile->TakeDataOwnership(); + return memFile; + } + + return file; + } + } + + if ( resourceFiles.Num() > 0 && fs_resourceLoadPriority.GetInteger() == 0 ) { + idFile * rf = GetResourceFile( relativePath, ( searchFlags & FSFLAG_RETURN_FILE_MEM ) != 0 ); + if ( rf != NULL ) { + return rf; + } + } + + if ( fs_debug.GetInteger( ) ) { + common->Printf( "Can't find %s\n", relativePath ); + } + + return NULL; +} + +/* +=========== +idFileSystemLocal::OpenFileRead +=========== +*/ +idFile *idFileSystemLocal::OpenFileRead( const char *relativePath, bool allowCopyFiles, const char* gamedir ) { + return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS, allowCopyFiles, gamedir ); +} + +/* +=========== +idFileSystemLocal::OpenFileReadMemory +=========== +*/ +idFile *idFileSystemLocal::OpenFileReadMemory( const char *relativePath, bool allowCopyFiles, const char* gamedir ) { + return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS | FSFLAG_RETURN_FILE_MEM, allowCopyFiles, gamedir ); +} + +/* +=========== +idFileSystemLocal::OpenFileWrite +=========== +*/ +idFile *idFileSystemLocal::OpenFileWrite( const char *relativePath, const char *basePath ) { + + const char *path; + idStr OSpath; + idFile_Permanent *f; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + + OSpath = BuildOSPath( path, gameFolder, relativePath ); + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenFileWrite: %s\n", OSpath.c_str() ); + } + + common->DPrintf( "writing to: %s\n", OSpath.c_str() ); + CreateOSPath( OSpath ); + + f = new (TAG_IDFILE) idFile_Permanent(); + f->o = OpenOSFile( OSpath, FS_WRITE ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = relativePath; + f->fullPath = OSpath; + f->mode = ( 1 << FS_WRITE ); + f->handleSync = false; + f->fileSize = 0; + + return f; +} + +/* +=========== +idFileSystemLocal::OpenExplicitFileRead +=========== +*/ +idFile *idFileSystemLocal::OpenExplicitFileRead( const char *OSPath ) { + idFile_Permanent *f; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath ); + } + + //common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath ); + + f = new (TAG_IDFILE) idFile_Permanent(); + f->o = OpenOSFile( OSPath, FS_READ ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = OSPath; + f->fullPath = OSPath; + f->mode = ( 1 << FS_READ ); + f->handleSync = false; + f->fileSize = DirectFileLength( f->o ); + + return f; +} + +/* +=========== +idFileSystemLocal::OpenExplicitPakFile +=========== +*/ +idFile_Cached *idFileSystemLocal::OpenExplicitPakFile( const char *OSPath ) { + idFile_Cached *f; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + //if ( fs_debug.GetInteger() ) { + // common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath ); + //} + + //common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath ); + + f = new (TAG_IDFILE) idFile_Cached(); + f->o = OpenOSFile( OSPath, FS_READ ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = OSPath; + f->fullPath = OSPath; + f->mode = ( 1 << FS_READ ); + f->handleSync = false; + f->fileSize = DirectFileLength( f->o ); + + return f; +} + +/* +=========== +idFileSystemLocal::OpenExplicitFileWrite +=========== +*/ +idFile *idFileSystemLocal::OpenExplicitFileWrite( const char *OSPath ) { + idFile_Permanent *f; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenExplicitFileWrite: %s\n", OSPath ); + } + + common->DPrintf( "writing to: %s\n", OSPath ); + CreateOSPath( OSPath ); + + f = new (TAG_IDFILE) idFile_Permanent(); + f->o = OpenOSFile( OSPath, FS_WRITE ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = OSPath; + f->fullPath = OSPath; + f->mode = ( 1 << FS_WRITE ); + f->handleSync = false; + f->fileSize = 0; + + return f; +} + +/* +=========== +idFileSystemLocal::OpenFileAppend +=========== +*/ +idFile *idFileSystemLocal::OpenFileAppend( const char *relativePath, bool sync, const char *basePath ) { + + const char *path; + idStr OSpath; + idFile_Permanent *f; + + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + + OSpath = BuildOSPath( path, gameFolder, relativePath ); + CreateOSPath( OSpath ); + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenFileAppend: %s\n", OSpath.c_str() ); + } + + f = new (TAG_IDFILE) idFile_Permanent(); + f->o = OpenOSFile( OSpath, FS_APPEND ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = relativePath; + f->fullPath = OSpath; + f->mode = ( 1 << FS_WRITE ) + ( 1 << FS_APPEND ); + f->handleSync = sync; + f->fileSize = DirectFileLength( f->o ); + + return f; +} + +/* +================ +idFileSystemLocal::OpenFileByMode +================ +*/ +idFile *idFileSystemLocal::OpenFileByMode( const char *relativePath, fsMode_t mode ) { + if ( mode == FS_READ ) { + return OpenFileRead( relativePath ); + } + if ( mode == FS_WRITE ) { + return OpenFileWrite( relativePath ); + } + if ( mode == FS_APPEND ) { + return OpenFileAppend( relativePath, true ); + } + common->FatalError( "idFileSystemLocal::OpenFileByMode: bad mode" ); + return NULL; +} + +/* +============== +idFileSystemLocal::CloseFile +============== +*/ +void idFileSystemLocal::CloseFile( idFile *f ) { + if ( !IsInitialized() ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + delete f; +} + +/* +================= +idFileSystemLocal::FindDLL +================= +*/ +void idFileSystemLocal::FindDLL( const char *name, char _dllPath[ MAX_OSPATH ] ) { + char dllName[MAX_OSPATH]; + sys->DLL_GetFileName( name, dllName, MAX_OSPATH ); + + // from executable directory first - this is handy for developement + idStr dllPath = Sys_EXEPath( ); + dllPath.StripFilename( ); + dllPath.AppendPath( dllName ); + idFile * dllFile = OpenExplicitFileRead( dllPath ); + + if ( dllFile ) { + dllPath = dllFile->GetFullPath(); + CloseFile( dllFile ); + dllFile = NULL; + } else { + dllPath = ""; + } + idStr::snPrintf( _dllPath, MAX_OSPATH, dllPath.c_str() ); +} + +/* +=============== +idFileSystemLocal::FindFile +=============== +*/ + findFile_t idFileSystemLocal::FindFile( const char *path ) { + idFile *f = OpenFileReadFlags( path, FSFLAG_SEARCH_DIRS ); + if ( f == NULL ) { + return FIND_NO; + } + delete f; + return FIND_YES; +} + +/* +=============== +idFileSystemLocal::IsFolder +=============== +*/ +sysFolder_t idFileSystemLocal::IsFolder( const char * relativePath, const char *basePath ) { + return Sys_IsFolder( RelativePathToOSPath( relativePath, basePath ) ); +} diff --git a/neo/framework/FileSystem.h b/neo/framework/FileSystem.h new file mode 100644 index 00000000..240d8e5a --- /dev/null +++ b/neo/framework/FileSystem.h @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FILESYSTEM_H__ +#define __FILESYSTEM_H__ + +/* +=============================================================================== + + File System + + No stdio calls should be used by any part of the game, because of all sorts + of directory and separator char issues. Throughout the game a forward slash + should be used as a separator. The file system takes care of the conversion + to an OS specific separator. The file system treats all file and directory + names as case insensitive. + + The following cvars store paths used by the file system: + + "fs_basepath" path to local install + "fs_savepath" path to config, save game, etc. files, read & write + + The base path for file saving can be set to "fs_savepath" or "fs_basepath". + +=============================================================================== +*/ + +static const ID_TIME_T FILE_NOT_FOUND_TIMESTAMP = (ID_TIME_T)-1; +static const int MAX_OSPATH = 256; + +// modes for OpenFileByMode +typedef enum { + FS_READ = 0, + FS_WRITE = 1, + FS_APPEND = 2 +} fsMode_t; + +typedef enum { + FIND_NO, + FIND_YES +} findFile_t; + +// file list for directory listings +class idFileList { + friend class idFileSystemLocal; +public: + const char * GetBasePath() const { return basePath; } + int GetNumFiles() const { return list.Num(); } + const char * GetFile( int index ) const { return list[index]; } + const idStrList & GetList() const { return list; } + +private: + idStr basePath; + idStrList list; +}; + +class idFileSystem { +public: + virtual ~idFileSystem() {} + // Initializes the file system. + virtual void Init() = 0; + // Restarts the file system. + virtual void Restart() = 0; + // Shutdown the file system. + virtual void Shutdown( bool reloading ) = 0; + // Returns true if the file system is initialized. + virtual bool IsInitialized() const = 0; + // Lists files with the given extension in the given directory. + // Directory should not have either a leading or trailing '/' + // The returned files will not include any directories or '/' unless fullRelativePath is set. + // The extension must include a leading dot and may not contain wildcards. + // If extension is "/", only subdirectories will be returned. + virtual idFileList * ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL ) = 0; + // Lists files in the given directory and all subdirectories with the given extension. + // Directory should not have either a leading or trailing '/' + // The returned files include a full relative path. + // The extension must include a leading dot and may not contain wildcards. + virtual idFileList * ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL ) = 0; + // Frees the given file list. + virtual void FreeFileList( idFileList *fileList ) = 0; + // Converts a relative path to a full OS path. + virtual const char * OSPathToRelativePath( const char *OSPath ) = 0; + // Converts a full OS path to a relative path. + virtual const char * RelativePathToOSPath( const char *relativePath, const char *basePath = "fs_basepath" ) = 0; + // Builds a full OS path from the given components. + virtual const char * BuildOSPath( const char *base, const char *game, const char *relativePath ) = 0; + virtual const char * BuildOSPath( const char *base, const char *relativePath ) = 0; + // Creates the given OS path for as far as it doesn't exist already. + virtual void CreateOSPath( const char *OSPath ) = 0; + // Reads a complete file. + // Returns the length of the file, or -1 on failure. + // A null buffer will just return the file length without loading. + // A null timestamp will be ignored. + // As a quick check for existance. -1 length == not present. + // A 0 byte will always be appended at the end, so string ops are safe. + // The buffer should be considered read-only, because it may be cached for other uses. + virtual int ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp = NULL ) = 0; + // Frees the memory allocated by ReadFile. + virtual void FreeFile( void *buffer ) = 0; + // Writes a complete file, will create any needed subdirectories. + // Returns the length of the file, or -1 on failure. + virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" ) = 0; + // Removes the given file. + virtual void RemoveFile( const char *relativePath ) = 0; + // Removes the specified directory. + virtual bool RemoveDir( const char * relativePath ) = 0; + // Renames a file, taken from idTech5 (minus the fsPath_t) + virtual bool RenameFile( const char * relativePath, const char * newName, const char * basePath = "fs_savepath" ) = 0; + // Opens a file for reading. + virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ) = 0; + // Opens a file for reading, reads the file completely in memory and returns an idFile_Memory obj. + virtual idFile * OpenFileReadMemory( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ) = 0; + // Opens a file for writing, will create any needed subdirectories. + virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" ) = 0; + // Opens a file for writing at the end. + virtual idFile * OpenFileAppend( const char *filename, bool sync = false, const char *basePath = "fs_basepath" ) = 0; + // Opens a file for reading, writing, or appending depending on the value of mode. + virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode ) = 0; + // Opens a file for reading from a full OS path. + virtual idFile * OpenExplicitFileRead( const char *OSPath ) = 0; + // Opens a file for writing to a full OS path. + virtual idFile * OpenExplicitFileWrite( const char *OSPath ) = 0; + // opens a zip container + virtual idFile_Cached * OpenExplicitPakFile( const char *OSPath ) = 0; + // Closes a file. + virtual void CloseFile( idFile *f ) = 0; + // look for a dynamic module + virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ] ) = 0; + + // don't use for large copies - allocates a single memory block for the copy + virtual void CopyFile( const char *fromOSPath, const char *toOSPath ) = 0; + + // look for a file in the loaded paks or the addon paks + // if the file is found in addons, FS's internal structures are ready for a reloadEngine + virtual findFile_t FindFile( const char *path ) = 0; + + // ignore case and seperator char distinctions + virtual bool FilenameCompare( const char *s1, const char *s2 ) const = 0; + + // This is just handy + ID_TIME_T GetTimestamp( const char * relativePath ) { + ID_TIME_T timestamp = FILE_NOT_FOUND_TIMESTAMP; + if ( relativePath == NULL || relativePath[ 0 ] == '\0' ) { + return timestamp; + } + ReadFile( relativePath, NULL, ×tamp ); + return timestamp; + } + + // Returns length of file, -1 if no file exists + virtual int GetFileLength( const char * relativePath ) = 0; + + virtual sysFolder_t IsFolder( const char * relativePath, const char *basePath = "fs_basepath" ) = 0; + + // resource tracking and related things + virtual void EnableBackgroundCache( bool enable ) = 0; + virtual void BeginLevelLoad( const char *name, char *_blockBuffer, int _blockBufferSize ) = 0; + virtual void EndLevelLoad() = 0; + virtual bool InProductionMode() = 0; + virtual bool UsingResourceFiles() = 0; + virtual void UnloadMapResources( const char *name ) = 0; + virtual void UnloadResourceContainer( const char *name ) = 0; + virtual void StartPreload( const idStrList &_preload ) = 0; + virtual void StopPreload() = 0; + virtual int ReadFromBGL( idFile *_resourceFile, void * _buffer, int _offset, int _len ) = 0; + virtual bool IsBinaryModel( const idStr & resName ) const = 0; + virtual bool IsSoundSample( const idStr & resName ) const = 0; + virtual bool GetResourceCacheEntry( const char *fileName, idResourceCacheEntry &rc ) = 0; + virtual void FreeResourceBuffer() = 0; + virtual void AddImagePreload( const char *resName, int filter, int repeat, int usage, int cube ) = 0; + virtual void AddSamplePreload( const char *resName ) = 0; + virtual void AddModelPreload( const char *resName ) = 0; + virtual void AddAnimPreload( const char *resName ) = 0; + virtual void AddParticlePreload( const char *resName ) = 0; + virtual void AddCollisionPreload( const char *resName ) = 0; + +}; + +extern idFileSystem * fileSystem; + +#endif /* !__FILESYSTEM_H__ */ diff --git a/neo/framework/File_Manifest.cpp b/neo/framework/File_Manifest.cpp new file mode 100644 index 00000000..21fc733b --- /dev/null +++ b/neo/framework/File_Manifest.cpp @@ -0,0 +1,200 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================================================================================================ + +idPreloadManifest + +================================================================================================ +*/ + +/* +======================== +idPreloadManifest::LoadManifest +======================== +*/ +bool idPreloadManifest::LoadManifest( const char *fileName ) { + idFile * inFile = fileSystem->OpenFileReadMemory( fileName ); + if ( inFile != NULL ) { + int numEntries; + inFile->ReadBig( numEntries ); + inFile->ReadString( filename ); + entries.SetNum( numEntries ); + for ( int i = 0; i < numEntries; i++ ) { + entries[ i ].Read( inFile ); + } +delete inFile; + return true; + } + return false; +} + +/* +================================================================================================ + +idFileManifest + +================================================================================================ +*/ +/* +======================== +idFileManifest::LoadManifest +======================== +*/ +bool idFileManifest::LoadManifest( const char *_fileName ) { + idFile *file = fileSystem->OpenFileRead( _fileName , false ); + if ( file != NULL ) { + return LoadManifestFromFile( file ); + } + return false; +} + +/* +======================== +idFileManifest::LoadManifestFromFile + +// this will delete the file when finished +======================== +*/ +bool idFileManifest::LoadManifestFromFile( idFile *file ) { + if ( file == NULL ) { + return false; + } + filename = file->GetName(); + idStr str; + int num; + file->ReadBig( num ); + cacheTable.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + file->ReadString( cacheTable[ i ] ); + //if ( FindFile( cacheTable[ i ].filename ) == NULL ) { + // we only care about the first usage + const int key = cacheHash.GenerateKey( cacheTable[ i ], false ); + cacheHash.Add( key, i ); + //} + } + delete file; + return true; +} + +/* +======================== +idFileManifest::WriteManifestFile +======================== +*/ +void idFileManifest::WriteManifestFile( const char *fileName ) { + idFile *file = fileSystem->OpenFileWrite( fileName ); + if ( file == NULL ) { + return; + } + idStr str; + int num = cacheTable.Num(); + file->WriteBig( num ); + for ( int i = 0; i < num; i++ ) { + file->WriteString( cacheTable[ i ] ); + } + delete file; +} + +/* +======================== +idPreloadManifest::WriteManifestFile +======================== +*/ +void idPreloadManifest::WriteManifest( const char *fileName ) { + idFile *file = fileSystem->OpenFileWrite( fileName, "fs_savepath" ); + if ( file != NULL ) { + WriteManifestToFile( file ); + delete file; + } +} + +/* +======================== +idFileManifest::FindFile +======================== +*/ +int idFileManifest::FindFile( const char *fileName ) { + const int key =cacheHash.GenerateKey( fileName, false ); + for ( int index = cacheHash.GetFirst( key ); index != idHashIndex::NULL_INDEX; index = cacheHash.GetNext( index ) ) { + if ( idStr::Icmp( cacheTable[ index ], fileName ) == 0 ) { + return index; + } + } + return -1; +} + +/* +======================== +idFileManifest::RemoveAll +======================== +*/ +void idFileManifest::RemoveAll( const char * _fileName ) { + for ( int i = 0; i < cacheTable.Num(); i++ ) { + if ( cacheTable[ i ].Icmp( _fileName ) == 0 ) { + const int key =cacheHash.GenerateKey( cacheTable[ i ], false ); + cacheTable.RemoveIndex( i ); + cacheHash.RemoveIndex( key, i ); + i--; + } + } +} + + +/* +======================== +idFileManifest::GetFileNameByIndex +======================== +*/ +const idStr & idFileManifest::GetFileNameByIndex( int idx ) const { + return cacheTable[ idx ]; +} + + + +/* +========================= +idFileManifest::AddFile +========================= +*/ +void idFileManifest::AddFile( const char *fileName ) { + //if ( FindFile( fileName ) == NULL ) { + // we only care about the first usage + const int key = cacheHash.GenerateKey( fileName, false ); + int idx = cacheTable.Append( fileName ); + cacheHash.Add( key, idx ); + //} +} + + + diff --git a/neo/framework/File_Manifest.h b/neo/framework/File_Manifest.h new file mode 100644 index 00000000..ef336fce --- /dev/null +++ b/neo/framework/File_Manifest.h @@ -0,0 +1,279 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FILE_MANIFEST_H__ +#define __FILE_MANIFEST_H__ + +/* +============================================================== + + File and preload manifests. + +============================================================== +*/ + +class idFileManifest { +public: + idFileManifest() { + cacheTable.SetGranularity( 4096 ); + cacheHash.SetGranularity( 4096 ); + } + ~idFileManifest(){} + + bool LoadManifest( const char *fileName ); + bool LoadManifestFromFile( idFile *file ); + void WriteManifestFile( const char *fileName ); + + int NumFiles() { + return cacheTable.Num(); + } + + int FindFile( const char *fileName ); + + const idStr & GetFileNameByIndex( int idx ) const; + + + const char * GetManifestName() { + return filename; + } + + void RemoveAll( const char *filename ); + void AddFile( const char *filename ); + + void PopulateList( idStaticList< idStr, 16384 > &dest ) { + dest.Clear(); + for ( int i = 0; i < cacheTable.Num(); i++ ) { + dest.Append( cacheTable[ i ] ); + } + } + + void Print() { + idLib::Printf( "dump for manifest %s\n", GetManifestName() ); + idLib::Printf( "---------------------------------------\n" ); + for ( int i = 0; i < NumFiles(); i++ ) { + const idStr & name = GetFileNameByIndex( i ); + if ( name.Find( ".idwav", false ) >= 0 ) { + idLib::Printf( "%s\n", GetFileNameByIndex( i ).c_str() ); + } + } + } + +private: + idStrList cacheTable; + idHashIndex cacheHash; + idStr filename; +}; + +// image preload +struct imagePreload_s { + imagePreload_s() { + filter = 0; + repeat = 0; + usage = 0; + cubeMap = 0; + } + void Write( idFile *f ) { + f->WriteBig( filter ); + f->WriteBig( repeat ); + f->WriteBig( usage ); + f->WriteBig( cubeMap ); + } + void Read( idFile *f ) { + f->ReadBig( filter ); + f->ReadBig( repeat ); + f->ReadBig( usage ); + f->ReadBig( cubeMap ); + } + bool operator==( const imagePreload_s &b ) const { + return ( filter == b.filter && repeat == b.repeat && usage == b.usage && cubeMap == b.cubeMap ); + } + int filter; + int repeat; + int usage; + int cubeMap; +}; + +enum preloadType_t { + PRELOAD_IMAGE, + PRELOAD_MODEL, + PRELOAD_SAMPLE, + PRELOAD_ANIM, + PRELOAD_COLLISION, + PRELOAD_PARTICLE +}; + +// preload +struct preloadEntry_s { + preloadEntry_s() { + resType = 0; + } + bool operator==( const preloadEntry_s &b ) const { + bool ret = ( resourceName.Icmp( b.resourceName ) == 0 ); + if ( ret && resType == PRELOAD_IMAGE ) { + // this should never matter but... + ret &= ( imgData == b.imgData ); + } + return ret; + } + void Write( idFile *outFile ) { + outFile->WriteBig( resType ); + outFile->WriteString( resourceName ); + imgData.Write( outFile ); + } + + void Read( idFile *inFile ) { + inFile->ReadBig( resType ); + inFile->ReadString( resourceName ); + imgData.Read( inFile ); + } + + int resType; // type + idStr resourceName; // resource name + imagePreload_s imgData; // image specific data +}; + +struct preloadSort_t { + int idx; + int ofs; +}; +class idSort_Preload : public idSort_Quick< preloadSort_t, idSort_Preload > { +public: + int Compare( const preloadSort_t & a, const preloadSort_t & b ) const { return a.ofs - b.ofs; } +}; + +class idPreloadManifest { +public: + idPreloadManifest() { + entries.SetGranularity( 2048 ); + } + ~idPreloadManifest(){} + + bool LoadManifest( const char *fileName ); + bool LoadManifestFromFile( idFile *file ); + + void WriteManifest( const char *fileName ); + void WriteManifestToFile( idFile *outFile ) { + if ( outFile == NULL ) { + return; + } + filename = outFile->GetName(); + outFile->WriteBig ( ( int )entries.Num() ); + outFile->WriteString( filename ); + for ( int i = 0; i < entries.Num(); i++ ) { + entries[ i ].Write( outFile ); + } + } + + int NumResources() const { + return entries.Num(); + } + + const preloadEntry_s & GetPreloadByIndex( int idx ) const { + return entries[ idx ]; + } + + const idStr & GetResourceNameByIndex( int idx ) const { + return entries[ idx ].resourceName; + } + + const char * GetManifestName() const { + return filename; + } + + void Add( const preloadEntry_s & p ) { + entries.AddUnique( p ); + } + + void AddSample( const char *_resourceName ) { + static preloadEntry_s pe; + pe.resType = PRELOAD_SAMPLE; + pe.resourceName = _resourceName; + entries.Append( pe ); + } + void AddModel( const char *_resourceName ) { + static preloadEntry_s pe; + pe.resType = PRELOAD_MODEL; + pe.resourceName = _resourceName; + entries.Append( pe ); + } + void AddParticle( const char *_resourceName ) { + static preloadEntry_s pe; + pe.resType = PRELOAD_PARTICLE; + pe.resourceName = _resourceName; + entries.Append( pe ); + } + void AddAnim( const char *_resourceName ) { + static preloadEntry_s pe; + pe.resType = PRELOAD_ANIM; + pe.resourceName = _resourceName; + entries.Append( pe ); + } + void AddCollisionModel( const char *_resourceName ) { + static preloadEntry_s pe; + pe.resType = PRELOAD_COLLISION; + pe.resourceName = _resourceName; + entries.Append( pe ); + } + void AddImage( const char *_resourceName, int _filter, int _repeat, int _usage, int _cube ) { + static preloadEntry_s pe; + pe.resType = PRELOAD_IMAGE; + pe.resourceName = _resourceName; + pe.imgData.filter = _filter; + pe.imgData.repeat = _repeat; + pe.imgData.usage = _usage; + pe.imgData.cubeMap = _cube; + entries.Append( pe ); + } + + void Clear() { + entries.Clear(); + } + + int FindResource( const char *name ) { + for ( int i = 0; i < entries.Num(); i++ ) { + if ( idStr::Icmp( name, entries[ i ].resourceName ) == 0 ) { + return i; + } + } + return -1; + } + + void Print() { + idLib::Printf( "dump for preload manifest %s\n", GetManifestName() ); + idLib::Printf( "---------------------------------------\n" ); + for ( int i = 0; i < NumResources(); i++ ) { + idLib::Printf( "%s\n", GetResourceNameByIndex( i ).c_str() ); + } + } +private: + idList< preloadEntry_s > entries; + idStr filename; +}; + + +#endif /* !__FILE_MANIFEST_H__ */ diff --git a/neo/framework/File_Resource.cpp b/neo/framework/File_Resource.cpp new file mode 100644 index 00000000..64381ea8 --- /dev/null +++ b/neo/framework/File_Resource.cpp @@ -0,0 +1,501 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +================================================================================================ + +idResourceContainer + +================================================================================================ +*/ + +/* +======================== +idResourceContainer::ReOpen +======================== +*/ +void idResourceContainer::ReOpen() { + delete resourceFile; + resourceFile = fileSystem->OpenFileRead( fileName ); +} + +/* +======================== +idResourceContainer::Init +======================== +*/ +bool idResourceContainer::Init( const char *_fileName, uint8 containerIndex ) { + + if ( idStr::Icmp( _fileName, "_ordered.resources" ) == 0 ) { + resourceFile = fileSystem->OpenFileReadMemory( _fileName ); + } else { + resourceFile = fileSystem->OpenFileRead( _fileName ); + } + + if ( resourceFile == NULL ) { + idLib::Warning( "Unable to open resource file %s", _fileName ); + return false; + } + + resourceFile->ReadBig( resourceMagic ); + if ( resourceMagic != RESOURCE_FILE_MAGIC ) { + idLib::FatalError( "resourceFileMagic != RESOURCE_FILE_MAGIC" ); + } + + fileName = _fileName; + + resourceFile->ReadBig( tableOffset ); + resourceFile->ReadBig( tableLength ); + // read this into a memory buffer with a single read + char * const buf = (char *)Mem_Alloc( tableLength, TAG_RESOURCE ); + resourceFile->Seek( tableOffset, FS_SEEK_SET ); + resourceFile->Read( buf, tableLength ); + idFile_Memory memFile( "resourceHeader", (const char *)buf, tableLength ); + + // Parse the resourceFile header, which includes every resource used + // by the game. + memFile.ReadBig( numFileResources ); + + cacheTable.SetNum( numFileResources ); + + for ( int i = 0; i < numFileResources; i++ ) { + idResourceCacheEntry &rt = cacheTable[ i ]; + rt.Read( &memFile ); + rt.filename.BackSlashesToSlashes(); + rt.filename.ToLower(); + rt.containerIndex = containerIndex; + + const int key = cacheHash.GenerateKey( rt.filename, false ); + bool found = false; + //for ( int index = cacheHash.GetFirst( key ); index != idHashIndex::NULL_INDEX; index = cacheHash.GetNext( index ) ) { + // idResourceCacheEntry & rtc = cacheTable[ index ]; + // if ( idStr::Icmp( rtc.filename, rt.filename ) == 0 ) { + // found = true; + // break; + // } + //} + if ( !found ) { + //idLib::Printf( "rez file name: %s\n", rt.filename.c_str() ); + cacheHash.Add( key, i ); + } + } + Mem_Free( buf ); + + return true; +} + + +/* +======================== +idResourceContainer::WriteManifestFile +======================== +*/ +void idResourceContainer::WriteManifestFile( const char *name, const idStrList &list ) { + idStr filename( name ); + filename.SetFileExtension( "manifest" ); + filename.Insert( "maps/", 0 ); + idFile *outFile = fileSystem->OpenFileWrite( filename ); + if ( outFile != NULL ) { + int num = list.Num(); + outFile->WriteBig( num ); + for ( int i = 0; i < num; i++ ) { + outFile->WriteString( list[ i ] ); + } + delete outFile; + } +} + +/* +======================== +idResourceContainer::ReadManifestFile +======================== +*/ +int idResourceContainer::ReadManifestFile( const char *name, idStrList &list ) { + idFile *inFile = fileSystem->OpenFileRead( name ); + if ( inFile != NULL ) { + list.SetGranularity( 16384 ); + idStr str; + int num; + list.Clear(); + inFile->ReadBig( num ); + for ( int i = 0; i < num; i++ ) { + inFile->ReadString( str ); + list.Append( str ); + } + delete inFile; + } + return list.Num(); +} + + +/* +======================== +idResourceContainer::UpdateResourceFile +======================== +*/ +void idResourceContainer::UpdateResourceFile( const char *_filename, const idStrList &_filesToUpdate ) { + idFile *outFile = fileSystem->OpenFileWrite( va( "%s.new", _filename ) ); + if ( outFile == NULL ) { + idLib::Warning( "Unable to open resource file %s or new output file", _filename ); + return; + } + + uint32 magic = 0; + int _tableOffset = 0; + int _tableLength = 0; + idList< idResourceCacheEntry > entries; + idStrList filesToUpdate = _filesToUpdate; + + idFile *inFile = fileSystem->OpenFileRead( _filename ); + if ( inFile == NULL ) { + magic = RESOURCE_FILE_MAGIC; + + outFile->WriteBig( magic ); + outFile->WriteBig( _tableOffset ); + outFile->WriteBig( _tableLength ); + + } else { + inFile->ReadBig( magic ); + if ( magic != RESOURCE_FILE_MAGIC ) { + delete inFile; + return; + } + + inFile->ReadBig( _tableOffset ); + inFile->ReadBig( _tableLength ); + // read this into a memory buffer with a single read + char * const buf = (char *)Mem_Alloc( _tableLength, TAG_RESOURCE ); + inFile->Seek( _tableOffset, FS_SEEK_SET ); + inFile->Read( buf, _tableLength ); + idFile_Memory memFile( "resourceHeader", (const char *)buf, _tableLength ); + + int _numFileResources = 0; + memFile.ReadBig( _numFileResources ); + + outFile->WriteBig( magic ); + outFile->WriteBig( _tableOffset ); + outFile->WriteBig( _tableLength ); + + entries.SetNum( _numFileResources ); + + for ( int i = 0; i < _numFileResources; i++ ) { + entries[ i ].Read( &memFile ); + + + idLib::Printf( "examining %s\n", entries[ i ].filename.c_str() ); + byte * fileData = NULL; + + for ( int j = filesToUpdate.Num() - 1; j >= 0; j-- ) { + if ( filesToUpdate[ j ].Icmp( entries[ i ].filename ) == 0 ) { + idFile *newFile = fileSystem->OpenFileReadMemory( filesToUpdate[ j ] ); + if ( newFile != NULL ) { + idLib::Printf( "Updating %s\n", filesToUpdate[ j ].c_str() ); + entries[ i ].length = newFile->Length(); + fileData = (byte *)Mem_Alloc( entries[ i ].length, TAG_TEMP ); + newFile->Read( fileData, newFile->Length() ); + delete newFile; + } + filesToUpdate.RemoveIndex( j ); + } + } + + if ( fileData == NULL ) { + inFile->Seek( entries[ i ].offset, FS_SEEK_SET ); + fileData = (byte *)Mem_Alloc( entries[ i ].length, TAG_TEMP ); + inFile->Read( fileData, entries[ i ].length ); + } + + entries[ i ].offset = outFile->Tell(); + outFile->Write( ( void* )fileData, entries[ i ].length ); + + Mem_Free( fileData ); + } + + Mem_Free( buf ); + } + + while ( filesToUpdate.Num() > 0 ) { + idFile *newFile = fileSystem->OpenFileReadMemory( filesToUpdate[ 0 ] ); + if ( newFile != NULL ) { + idLib::Printf( "Appending %s\n", filesToUpdate[ 0 ].c_str() ); + idResourceCacheEntry rt; + rt.filename = filesToUpdate[ 0 ]; + rt.length = newFile->Length(); + byte * fileData = (byte *)Mem_Alloc( rt.length, TAG_TEMP ); + newFile->Read( fileData, rt.length ); + int idx = entries.Append( rt ); + if ( idx >= 0 ) { + entries[ idx ].offset = outFile->Tell(); + outFile->Write( ( void* )fileData, entries[ idx ].length ); + } + delete newFile; + Mem_Free( fileData ); + } + filesToUpdate.RemoveIndex( 0 ); + } + + _tableOffset = outFile->Tell(); + outFile->WriteBig( entries.Num() ); + + // write the individual resource entries + for ( int i = 0; i < entries.Num(); i++ ) { + entries[ i ].Write( outFile ); + } + + // go back and write the header offsets again, now that we have file offsets and lengths + _tableLength = outFile->Tell() - _tableOffset; + outFile->Seek( 0, FS_SEEK_SET ); + outFile->WriteBig( magic ); + outFile->WriteBig( _tableOffset ); + outFile->WriteBig( _tableLength ); + + delete outFile; + delete inFile; +} + + +/* +======================== +idResourceContainer::ExtractResourceFile +======================== +*/ +void idResourceContainer::SetContainerIndex( const int & _idx ) { + for ( int i = 0; i < cacheTable.Num(); i++ ) { + cacheTable[ i ].containerIndex = _idx; + } +} + +/* +======================== +idResourceContainer::ExtractResourceFile +======================== +*/ +void idResourceContainer::ExtractResourceFile ( const char * _fileName, const char * _outPath, bool _copyWavs ) { + idFile *inFile = fileSystem->OpenFileRead( _fileName ); + + if ( inFile == NULL ) { + idLib::Warning( "Unable to open resource file %s", _fileName ); + return; + } + + uint32 magic; + inFile->ReadBig( magic ); + if ( magic != RESOURCE_FILE_MAGIC ) { + delete inFile; + return; + } + + int _tableOffset; + int _tableLength; + inFile->ReadBig( _tableOffset ); + inFile->ReadBig( _tableLength ); + // read this into a memory buffer with a single read + char * const buf = (char *)Mem_Alloc( _tableLength, TAG_RESOURCE ); + inFile->Seek( _tableOffset, FS_SEEK_SET ); + inFile->Read( buf, _tableLength ); + idFile_Memory memFile( "resourceHeader", (const char *)buf, _tableLength ); + + int _numFileResources; + memFile.ReadBig( _numFileResources ); + + for ( int i = 0; i < _numFileResources; i++ ) { + idResourceCacheEntry rt; + rt.Read( &memFile ); + rt.filename.BackSlashesToSlashes(); + rt.filename.ToLower(); + byte *fbuf = NULL; + if ( _copyWavs && ( rt.filename.Find( ".idwav" ) >= 0 || rt.filename.Find( ".idxma" ) >= 0 || rt.filename.Find( ".idmsf" ) >= 0 ) ) { + rt.filename.SetFileExtension( "wav" ); + rt.filename.Replace( "generated/", "" ); + int len = fileSystem->GetFileLength( rt.filename ); + fbuf = (byte *)Mem_Alloc( len, TAG_RESOURCE ); + fileSystem->ReadFile( rt.filename, (void**)&fbuf, NULL ); + } else { + inFile->Seek( rt.offset, FS_SEEK_SET ); + fbuf = (byte *)Mem_Alloc( rt.length, TAG_RESOURCE ); + inFile->Read( fbuf, rt.length ); + } + idStr outName = _outPath; + outName.AppendPath( rt.filename ); + idFile *outFile = fileSystem->OpenExplicitFileWrite( outName ); + if ( outFile != NULL ) { + outFile->Write( ( byte* )fbuf, rt.length ); + delete outFile; + } + Mem_Free( fbuf ); + } + delete inFile; + Mem_Free( buf ); +} + + + +/* +======================== +idResourceContainer::Open +======================== +*/ +void idResourceContainer::WriteResourceFile( const char *manifestName, const idStrList &manifest, const bool &_writeManifest ) { + + if ( manifest.Num() == 0 ) { + return; + } + + idLib::Printf( "Writing resource file %s\n", manifestName ); + + // build multiple output files at 1GB each + idList < idStrList > outPutFiles; + + idFileManifest outManifest; + int64 size = 0; + idStrList flist; + flist.SetGranularity( 16384 ); + for ( int i = 0; i < manifest.Num(); i++ ) { + flist.Append( manifest[ i ] ); + size += fileSystem->GetFileLength( manifest[ i ] ); + if ( size > 1024 * 1024 * 1024 ) { + outPutFiles.Append( flist ); + size = 0; + flist.Clear(); + } + outManifest.AddFile( manifest[ i ] ); + } + + outPutFiles.Append( flist ); + + if ( _writeManifest ) { + idStr temp = manifestName; + temp.Replace( "maps/", "manifests/" ); + temp.StripFileExtension(); + temp.SetFileExtension( "manifest" ); + outManifest.WriteManifestFile( temp ); + } + + for ( int idx = 0; idx < outPutFiles.Num(); idx++ ) { + + idStrList &fileList = outPutFiles[ idx ]; + if ( fileList.Num() == 0 ) { + continue; + } + + idStr fileName = manifestName; + if ( idx > 0 ) { + fileName = va( "%s_%02d", manifestName, idx ); + } + fileName.SetFileExtension( "resources" ); + + idFile *resFile = fileSystem->OpenFileWrite( fileName ); + + if ( resFile == NULL ) { + idLib::Warning( "Cannot open %s for writing.\n", fileName.c_str() ); + return; + } + + idLib::Printf( "Writing resource file %s\n", fileName.c_str() ); + + int tableOffset = 0; + int tableLength = 0; + int tableNewLength = 0; + uint32 resourceFileMagic = RESOURCE_FILE_MAGIC; + + resFile->WriteBig( resourceFileMagic ); + resFile->WriteBig( tableOffset ); + resFile->WriteBig( tableLength ); + + idList< idResourceCacheEntry > entries; + + entries.Resize( fileList.Num() ); + + for ( int i = 0; i < fileList.Num(); i++ ) { + idResourceCacheEntry ent; + + ent.filename = fileList[ i ]; + ent.length = 0; + ent.offset = 0; + + idFile *file = fileSystem->OpenFileReadMemory( ent.filename, false ); + idFile_Memory *fm = dynamic_cast< idFile_Memory* >( file ); + if ( fm == NULL ) { + continue; + } + // if the entry is uncompressed, align the file pointer to a 16 byte boundary + // so it will be usable if memory mapped + ent.length = fm->Length(); + + // always get the offset, even if the file will have zero length + ent.offset = resFile->Tell(); + + entries.Append( ent ); + + if ( ent.length == 0 ) { + ent.filename = ""; + delete fm; + continue; + } + + resFile->Write( fm->GetDataPtr(), ent.length ); + + delete fm; + + // pacifier every ten megs + if ( ( ent.offset + ent.length ) / 10000000 != ent.offset / 10000000 ) { + idLib::Printf( "." ); + } + } + + idLib::Printf( "\n" ); + + // write the table out now that we have all the files + tableOffset = resFile->Tell(); + + // count how many we are going to write for this platform + int numFileResources = entries.Num(); + + resFile->WriteBig( numFileResources ); + + // write the individual resource entries + for ( int i = 0; i < entries.Num(); i++ ) { + entries[ i ].Write( resFile ); + if ( i + 1 == numFileResources ) { + // we just wrote out the last new entry + tableNewLength = resFile->Tell() - tableOffset; + } + } + + // go back and write the header offsets again, now that we have file offsets and lengths + tableLength = resFile->Tell() - tableOffset; + resFile->Seek( 0, FS_SEEK_SET ); + resFile->WriteBig( resourceFileMagic ); + resFile->WriteBig( tableOffset ); + resFile->WriteBig( tableLength ); + delete resFile; + } +} diff --git a/neo/framework/File_Resource.h b/neo/framework/File_Resource.h new file mode 100644 index 00000000..fc497d6e --- /dev/null +++ b/neo/framework/File_Resource.h @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __FILE_RESOURCE_H__ +#define __FILE_RESOURCE_H__ + +/* +============================================================== + + Resource containers + +============================================================== +*/ + +class idResourceCacheEntry { +public: + idResourceCacheEntry() { + Clear(); + } + void Clear() { + filename.Empty(); + //filename = NULL; + offset = 0; + length = 0; + containerIndex = 0; + } + size_t Read( idFile *f ) { + size_t sz = f->ReadString( filename ); + sz += f->ReadBig( offset ); + sz += f->ReadBig( length ); + return sz; + } + size_t Write( idFile *f ) { + size_t sz = f->WriteString( filename ); + sz += f->WriteBig( offset ); + sz += f->WriteBig( length ); + return sz; + } + idStrStatic< 256 > filename; + int offset; // into the resource file + int length; + uint8 containerIndex; +}; + +static const uint32 RESOURCE_FILE_MAGIC = 0xD000000D; +class idResourceContainer { + friend class idFileSystemLocal; + //friend class idReadSpawnThread; +public: + idResourceContainer() { + resourceFile = NULL; + tableOffset = 0; + tableLength = 0; + resourceMagic = 0; + numFileResources = 0; + } + ~idResourceContainer() { + delete resourceFile; + cacheTable.Clear(); + } + bool Init( const char * fileName, uint8 containerIndex ); + static void WriteResourceFile( const char *fileName, const idStrList &manifest, const bool &_writeManifest ); + static void WriteManifestFile( const char *name, const idStrList &list ); + static int ReadManifestFile( const char *filename, idStrList &list ); + static void ExtractResourceFile ( const char * fileName, const char * outPath, bool copyWavs ); + static void UpdateResourceFile( const char *filename, const idStrList &filesToAdd ); + idFile *OpenFile( const char *fileName ); + const char * GetFileName() const { return fileName.c_str(); } + void SetContainerIndex( const int & _idx ); + void ReOpen(); +private: + idStrStatic< 256 > fileName; + idFile * resourceFile; // open file handle + // offset should probably be a 64 bit value for development, but 4 gigs won't fit on + // a DVD layer, so it isn't a retail limitation. + int tableOffset; // table offset + int tableLength; // table length + int resourceMagic; // magic + int numFileResources; // number of file resources in this container + idList< idResourceCacheEntry, TAG_RESOURCE> cacheTable; + idHashIndex cacheHash; +}; + + +#endif /* !__FILE_RESOURCE_H__ */ diff --git a/neo/framework/File_SaveGame.cpp b/neo/framework/File_SaveGame.cpp new file mode 100644 index 00000000..96ed5333 --- /dev/null +++ b/neo/framework/File_SaveGame.cpp @@ -0,0 +1,1150 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "File_SaveGame.h" + +/* + +TODO: CRC on each block + +*/ + + +/* +======================== +ZlibAlloc +======================== +*/ +void * ZlibAlloc( void *opaque, uInt items, uInt size ) { + return Mem_Alloc( items * size, TAG_SAVEGAMES ); +} + +/* +======================== +ZlibFree +======================== +*/ +void ZlibFree( void *opaque, void * address ) { + Mem_Free( address ); +} + +idCVar sgf_threads( "sgf_threads", "2", CVAR_INTEGER, "0 = all foreground, 1 = background write, 2 = background write + compress" ); +idCVar sgf_checksums( "sgf_checksums", "1", CVAR_BOOL, "enable save game file checksums" ); +idCVar sgf_testCorruption( "sgf_testCorruption", "-1", CVAR_INTEGER, "test corruption at the 128 kB compressed block" ); + +// this is supposed to get faster going from -15 to -9, but it gets slower as well as worse compression +idCVar sgf_windowBits( "sgf_windowBits", "-15", CVAR_INTEGER, "zlib window bits" ); + +bool idFile_SaveGamePipelined::cancelToTerminate = false; + +class idSGFcompressThread : public idSysThread { +public: + virtual int Run() { sgf->CompressBlock(); return 0; } + idFile_SaveGamePipelined * sgf; +}; +class idSGFdecompressThread : public idSysThread { +public: + virtual int Run() { sgf->DecompressBlock(); return 0; } + idFile_SaveGamePipelined * sgf; +}; +class idSGFwriteThread : public idSysThread { +public: + virtual int Run() { sgf->WriteBlock(); return 0; } + idFile_SaveGamePipelined * sgf; +}; +class idSGFreadThread : public idSysThread { +public: + virtual int Run() { sgf->ReadBlock(); return 0; } + idFile_SaveGamePipelined * sgf; +}; + +/* +============================ +idFile_SaveGamePipelined::idFile_SaveGamePipelined +============================ +*/ +idFile_SaveGamePipelined::idFile_SaveGamePipelined() : + mode( CLOSED ), + compressedLength( 0 ), + uncompressedProducedBytes( 0 ), + uncompressedConsumedBytes( 0 ), + compressedProducedBytes( 0 ), + compressedConsumedBytes( 0 ), + dataZlib( NULL ), + bytesZlib( 0 ), + dataIO( NULL ), + bytesIO( 0 ), + zLibFlushType( Z_NO_FLUSH ), + zStreamEndHit( false ), + numChecksums( 0 ), + nativeFile( NULL ), + nativeFileEndHit( false ), + finished( false ), + readThread( NULL ), + writeThread( NULL ), + decompressThread( NULL ), + compressThread( NULL ), + blockFinished( true ), + buildVersion( "" ), + saveFormatVersion( 0 ) { + + memset( &zStream, 0, sizeof( zStream ) ); + memset( compressed, 0, sizeof( compressed ) ); + memset( uncompressed, 0, sizeof( uncompressed ) ); + zStream.zalloc = ZlibAlloc; + zStream.zfree = ZlibFree; +} + +/* +============================ +idFile_SaveGamePipelined::~idFile_SaveGamePipelined +============================ +*/ +idFile_SaveGamePipelined::~idFile_SaveGamePipelined() { + Finish(); + + // free the threads + if ( compressThread != NULL ) { + delete compressThread; + compressThread = NULL; + } + if ( decompressThread != NULL ) { + delete decompressThread; + decompressThread = NULL; + } + if ( readThread != NULL ) { + delete readThread; + readThread = NULL; + } + if ( writeThread != NULL ) { + delete writeThread; + writeThread = NULL; + } + + // close the native file +/* if ( nativeFile != NULL ) { + delete nativeFile; + nativeFile = NULL; + } */ + + dataZlib = NULL; + dataIO = NULL; +} + +/* +======================== +idFile_SaveGamePipelined::ReadBuildVersion +======================== +*/ +bool idFile_SaveGamePipelined::ReadBuildVersion() { + return ReadString( buildVersion ) != 0; +} + +/* +======================== +idFile_SaveGamePipelined::ReadSaveFormatVersion +======================== +*/ +bool idFile_SaveGamePipelined::ReadSaveFormatVersion() { + if ( ReadBig( pointerSize ) <= 0 ) { + return false; + } + return ReadBig( saveFormatVersion ) != 0; +} + +/* +======================== +idFile_SaveGamePipelined::GetPointerSize +======================== +*/ +int idFile_SaveGamePipelined::GetPointerSize() const { + if ( pointerSize == 0 ) { + // in original savegames we weren't saving the pointer size, so the 2 high bytes of the save version will be 0 + return 4; + } else { + return pointerSize; + } +} + +/* +============================ +idFile_SaveGamePipelined::Finish +============================ +*/ +void idFile_SaveGamePipelined::Finish() { + if ( mode == WRITE ) { + + // wait for the compression thread to complete, which may kick off a write + if ( compressThread != NULL ) { + compressThread->WaitForThread(); + } + + // force the next compression to emit everything + zLibFlushType = Z_FINISH; + FlushUncompressedBlock(); + + if ( compressThread != NULL ) { + compressThread->WaitForThread(); + } + + if ( writeThread != NULL ) { + // wait for the IO thread to exit + writeThread->WaitForThread(); + } else if ( nativeFile == NULL && !nativeFileEndHit ) { + // wait for the last block to be consumed + blockRequested.Wait(); + finished = true; + blockAvailable.Raise(); + blockFinished.Wait(); + } + + // free zlib tables + deflateEnd( &zStream ); + + } else if ( mode == READ ) { + + // wait for the decompression thread to complete, which may kick off a read + if ( decompressThread != NULL ) { + decompressThread->WaitForThread(); + } + + if ( readThread != NULL ) { + // wait for the IO thread to exit + readThread->WaitForThread(); + } else if ( nativeFile == NULL && !nativeFileEndHit ) { + // wait for the last block to be consumed + blockAvailable.Wait(); + finished = true; + blockRequested.Raise(); + blockFinished.Wait(); + } + + // free zlib tables + inflateEnd( &zStream ); + } + + mode = CLOSED; +} + +/* +============================ +idFile_SaveGamePipelined::Abort +============================ +*/ +void idFile_SaveGamePipelined::Abort() { + if ( mode == WRITE ) { + + if ( compressThread != NULL ) { + compressThread->WaitForThread(); + } + if ( writeThread != NULL ) { + writeThread->WaitForThread(); + } else if ( nativeFile == NULL && !nativeFileEndHit ) { + blockRequested.Wait(); + finished = true; + dataIO = NULL; + bytesIO = 0; + blockAvailable.Raise(); + blockFinished.Wait(); + } + + } else if ( mode == READ ) { + + if ( decompressThread != NULL ) { + decompressThread->WaitForThread(); + } + if ( readThread != NULL ) { + readThread->WaitForThread(); + } else if ( nativeFile == NULL && !nativeFileEndHit ) { + blockAvailable.Wait(); + finished = true; + dataIO = NULL; + bytesIO = 0; + blockRequested.Raise(); + blockFinished.Wait(); + } + } + + mode = CLOSED; +} + +/* +=================================================================================== + +WRITE PATH + +=================================================================================== +*/ + +/* +============================ +idFile_SaveGamePipelined::OpenForWriting +============================ +*/ +bool idFile_SaveGamePipelined::OpenForWriting( const char * const filename, bool useNativeFile ) { + assert( mode == CLOSED ); + + name = filename; + osPath = filename; + mode = WRITE; + nativeFile = NULL; + numChecksums = 0; + + if ( useNativeFile ) { + nativeFile = fileSystem->OpenFileWrite( filename ); + if ( nativeFile == NULL ) { + return false; + } + } + + // raw deflate with no header / checksum + // use max memory for fastest compression + // optimize for higher speed + //mem.PushHeap(); + int status = deflateInit2( &zStream, Z_BEST_SPEED, Z_DEFLATED, sgf_windowBits.GetInteger(), 9, Z_DEFAULT_STRATEGY ); + //mem.PopHeap(); + if ( status != Z_OK ) { + idLib::FatalError( "idFile_SaveGamePipelined::OpenForWriting: deflateInit2() error %i", status ); + } + + // initial buffer setup + zStream.avail_out = COMPRESSED_BLOCK_SIZE; + zStream.next_out = (Bytef * )compressed; + + if ( sgf_checksums.GetBool() ) { + zStream.avail_out -= sizeof( uint32 ); + } + + if ( sgf_threads.GetInteger() >= 1 ) { + compressThread = new (TAG_IDFILE) idSGFcompressThread(); + compressThread->sgf = this; + compressThread->StartWorkerThread( "SGF_CompressThread", CORE_2B, THREAD_NORMAL ); + } + if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) { + writeThread = new (TAG_IDFILE) idSGFwriteThread(); + writeThread->sgf = this; + writeThread->StartWorkerThread( "SGF_WriteThread", CORE_2A, THREAD_NORMAL ); + } + + return true; +} + +/* +============================ +idFile_SaveGamePipelined::OpenForWriting +============================ +*/ +bool idFile_SaveGamePipelined::OpenForWriting( idFile * file ) { + assert( mode == CLOSED ); + + if ( file == NULL ) { + return false; + } + + name = file->GetName(); + osPath = file->GetFullPath(); + mode = WRITE; + nativeFile = file; + numChecksums = 0; + + + // raw deflate with no header / checksum + // use max memory for fastest compression + // optimize for higher speed + //mem.PushHeap(); + int status = deflateInit2( &zStream, Z_BEST_SPEED, Z_DEFLATED, sgf_windowBits.GetInteger(), 9, Z_DEFAULT_STRATEGY ); + //mem.PopHeap(); + if ( status != Z_OK ) { + idLib::FatalError( "idFile_SaveGamePipelined::OpenForWriting: deflateInit2() error %i", status ); + } + + // initial buffer setup + zStream.avail_out = COMPRESSED_BLOCK_SIZE; + zStream.next_out = (Bytef * )compressed; + + if ( sgf_checksums.GetBool() ) { + zStream.avail_out -= sizeof( uint32 ); + } + + if ( sgf_threads.GetInteger() >= 1 ) { + compressThread = new (TAG_IDFILE) idSGFcompressThread(); + compressThread->sgf = this; + compressThread->StartWorkerThread( "SGF_CompressThread", CORE_2B, THREAD_NORMAL ); + } + if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) { + writeThread = new (TAG_IDFILE) idSGFwriteThread(); + writeThread->sgf = this; + writeThread->StartWorkerThread( "SGF_WriteThread", CORE_2A, THREAD_NORMAL ); + } + + return true; +} + +/* +============================ +idFile_SaveGamePipelined::NextWriteBlock + +Modifies: + dataIO + bytesIO +============================ +*/ +bool idFile_SaveGamePipelined::NextWriteBlock( blockForIO_t * block ) { + assert( mode == WRITE ); + + blockRequested.Raise(); // the background thread is done with the last block + + if ( nativeFileEndHit ) { + return false; + } + + blockAvailable.Wait(); // wait for a new block to come through the pipeline + + if ( finished || block == NULL ) { + nativeFileEndHit = true; + blockRequested.Raise(); + blockFinished.Raise(); + return false; + } + + compressedLength += bytesIO; + + block->data = dataIO; + block->bytes = bytesIO; + + dataIO = NULL; + bytesIO = 0; + + return true; +} + +/* +============================ +idFile_SaveGamePipelined::WriteBlock + +Modifies: + dataIO + bytesIO + nativeFile +============================ +*/ +void idFile_SaveGamePipelined::WriteBlock() { + assert( nativeFile != NULL ); + + compressedLength += bytesIO; + + nativeFile->Write( dataIO, bytesIO ); + + dataIO = NULL; + bytesIO = 0; +} + +/* +============================ +idFile_SaveGamePipelined::FlushCompressedBlock + +Called when a compressed block fills up, and also to flush the final partial block. +Flushes everything from [compressedConsumedBytes -> compressedProducedBytes) + +Reads: + compressed + compressedProducedBytes + +Modifies: + dataZlib + bytesZlib + compressedConsumedBytes +============================ +*/ +void idFile_SaveGamePipelined::FlushCompressedBlock() { + // block until the background thread is done with the last block + if ( writeThread != NULL ) { + writeThread->WaitForThread(); + } if ( nativeFile == NULL ) { + if ( !nativeFileEndHit ) { + blockRequested.Wait(); + } + } + + // prepare the next block to be written out + dataIO = &compressed[ compressedConsumedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ]; + bytesIO = compressedProducedBytes - compressedConsumedBytes; + compressedConsumedBytes = compressedProducedBytes; + + if ( writeThread != NULL ) { + // signal a new block is available to be written out + writeThread->SignalWork(); + } else if ( nativeFile != NULL ) { + // write syncronously + WriteBlock(); + } else { + // signal a new block is available to be written out + blockAvailable.Raise(); + } +} + +/* +============================ +idFile_SaveGamePipelined::CompressBlock + +Called when an uncompressed block fills up, and also to flush the final partial block. +Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes) + +Modifies: + dataZlib + bytesZlib + compressed + compressedProducedBytes + zStream + zStreamEndHit +============================ +*/ +void idFile_SaveGamePipelined::CompressBlock() { + zStream.next_in = (Bytef * )dataZlib; + zStream.avail_in = (uInt) bytesZlib; + + dataZlib = NULL; + bytesZlib = 0; + + // if this is the finish block, we may need to write + // multiple buffers even after all input has been consumed + while( zStream.avail_in > 0 || zLibFlushType == Z_FINISH ) { + + const int zstat = deflate( &zStream, zLibFlushType ); + + if ( zstat != Z_OK && zstat != Z_STREAM_END ) { + idLib::FatalError( "idFile_SaveGamePipelined::CompressBlock: deflate() returned %i", zstat ); + } + + if ( zStream.avail_out == 0 || zLibFlushType == Z_FINISH ) { + + if ( sgf_checksums.GetBool() ) { + size_t blockSize = zStream.total_out + numChecksums * sizeof( uint32 ) - compressedProducedBytes; + uint32 checksum = MD5_BlockChecksum( zStream.next_out - blockSize, blockSize ); + zStream.next_out[0] = ( ( checksum >> 0 ) & 0xFF ); + zStream.next_out[1] = ( ( checksum >> 8 ) & 0xFF ); + zStream.next_out[2] = ( ( checksum >> 16 ) & 0xFF ); + zStream.next_out[3] = ( ( checksum >> 24 ) & 0xFF ); + numChecksums++; + } + + // flush the output buffer IO + compressedProducedBytes = zStream.total_out + numChecksums * sizeof( uint32 ); + FlushCompressedBlock(); + if ( zstat == Z_STREAM_END ) { + assert( zLibFlushType == Z_FINISH ); + zStreamEndHit = true; + return; + } + + assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) ); + + zStream.avail_out = COMPRESSED_BLOCK_SIZE; + zStream.next_out = (Bytef * )&compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ]; + + if ( sgf_checksums.GetBool() ) { + zStream.avail_out -= sizeof( uint32 ); + } + } + } +} + +/* +============================ +idFile_SaveGamePipelined::FlushUncompressedBlock + +Called when an uncompressed block fills up, and also to flush the final partial block. +Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes) + +Reads: + uncompressed + uncompressedProducedBytes + +Modifies: + dataZlib + bytesZlib + uncompressedConsumedBytes +============================ +*/ +void idFile_SaveGamePipelined::FlushUncompressedBlock() { + // block until the background thread has completed + if ( compressThread != NULL ) { + // make sure thread has completed the last work + compressThread->WaitForThread(); + } + + // prepare the next block to be consumed by Zlib + dataZlib = &uncompressed[ uncompressedConsumedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ]; + bytesZlib = uncompressedProducedBytes - uncompressedConsumedBytes; + uncompressedConsumedBytes = uncompressedProducedBytes; + + if ( compressThread != NULL ) { + // signal thread for more work + compressThread->SignalWork(); + } else { + // run syncronously + CompressBlock(); + } +} + +/* +============================ +idFile_SaveGamePipelined::Write + +Modifies: + uncompressed + uncompressedProducedBytes +============================ +*/ +int idFile_SaveGamePipelined::Write( const void * buffer, int length ) { + if ( buffer == NULL || length <= 0 ) { + return 0; + } + +#if 1 // quick and dirty fix for user-initiated forced shutdown during a savegame + if ( cancelToTerminate ) { + if ( mode != CLOSED ) { + Abort(); + } + return 0; + } +#endif + + assert( mode == WRITE ); + size_t lengthRemaining = length; + const byte * buffer_p = (const byte *)buffer; + while ( lengthRemaining > 0 ) { + const size_t ofsInBuffer = uncompressedProducedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ); + const size_t ofsInBlock = uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ); + const size_t remainingInBlock = UNCOMPRESSED_BLOCK_SIZE - ofsInBlock; + const size_t copyToBlock = ( lengthRemaining < remainingInBlock ) ? lengthRemaining : remainingInBlock; + + memcpy( uncompressed + ofsInBuffer, buffer_p, copyToBlock ); + uncompressedProducedBytes += copyToBlock; + + buffer_p += copyToBlock; + lengthRemaining -= copyToBlock; + + if ( copyToBlock == remainingInBlock ) { + FlushUncompressedBlock(); + } + } + return length; +} + +/* +=================================================================================== + +READ PATH + +=================================================================================== +*/ + +/* +============================ +idFile_SaveGamePipelined::OpenForReading +============================ +*/ +bool idFile_SaveGamePipelined::OpenForReading( const char * const filename, bool useNativeFile ) { + assert( mode == CLOSED ); + + name = filename; + osPath = filename; + mode = READ; + nativeFile = NULL; + numChecksums = 0; + + if ( useNativeFile ) { + nativeFile = fileSystem->OpenFileRead( filename ); + if ( nativeFile == NULL ) { + return false; + } + } + + // init zlib for raw inflate with a 32k dictionary + //mem.PushHeap(); + int status = inflateInit2( &zStream, sgf_windowBits.GetInteger() ); + //mem.PopHeap(); + if ( status != Z_OK ) { + idLib::FatalError( "idFile_SaveGamePipelined::OpenForReading: inflateInit2() error %i", status ); + } + + // spawn threads + if ( sgf_threads.GetInteger() >= 1 ) { + decompressThread = new (TAG_IDFILE) idSGFdecompressThread(); + decompressThread->sgf = this; + decompressThread->StartWorkerThread( "SGF_DecompressThread", CORE_2B, THREAD_NORMAL ); + } + if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) { + readThread = new (TAG_IDFILE) idSGFreadThread(); + readThread->sgf = this; + readThread->StartWorkerThread( "SGF_ReadThread", CORE_2A, THREAD_NORMAL ); + } + + return true; +} + + +/* +============================ +idFile_SaveGamePipelined::OpenForReading +============================ +*/ +bool idFile_SaveGamePipelined::OpenForReading( idFile * file ) { + assert( mode == CLOSED ); + + if ( file == NULL ) { + return false; + } + + name = file->GetName(); + osPath = file->GetFullPath(); + mode = READ; + nativeFile = file; + numChecksums = 0; + + // init zlib for raw inflate with a 32k dictionary + //mem.PushHeap(); + int status = inflateInit2( &zStream, sgf_windowBits.GetInteger() ); + //mem.PopHeap(); + if ( status != Z_OK ) { + idLib::FatalError( "idFile_SaveGamePipelined::OpenForReading: inflateInit2() error %i", status ); + } + + // spawn threads + if ( sgf_threads.GetInteger() >= 1 ) { + decompressThread = new (TAG_IDFILE) idSGFdecompressThread(); + decompressThread->sgf = this; + decompressThread->StartWorkerThread( "SGF_DecompressThread", CORE_1B, THREAD_NORMAL ); + } + if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) { + readThread = new (TAG_IDFILE) idSGFreadThread(); + readThread->sgf = this; + readThread->StartWorkerThread( "SGF_ReadThread", CORE_1A, THREAD_NORMAL ); + } + + return true; +} + + +/* +============================ +idFile_SaveGamePipelined::NextReadBlock + +Reads the next data block from the filesystem into the memory buffer. + +Modifies: + compressed + compressedProducedBytes + nativeFileEndHit +============================ +*/ +bool idFile_SaveGamePipelined::NextReadBlock( blockForIO_t * block, size_t lastReadBytes ) { + assert( mode == READ ); + + assert( ( lastReadBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) == 0 || block == NULL ); + compressedProducedBytes += lastReadBytes; + + blockAvailable.Raise(); // a new block is available for the pipeline to consume + + if ( nativeFileEndHit ) { + return false; + } + + blockRequested.Wait(); // wait for the last block to be consumed by the pipeline + + if ( finished || block == NULL ) { + nativeFileEndHit = true; + blockAvailable.Raise(); + blockFinished.Raise(); + return false; + } + + assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) ); + block->data = & compressed[compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 )]; + block->bytes = COMPRESSED_BLOCK_SIZE; + + return true; +} + +/* +============================ +idFile_SaveGamePipelined::ReadBlock + +Reads the next data block from the filesystem into the memory buffer. + +Modifies: + compressed + compressedProducedBytes + nativeFile + nativeFileEndHit +============================ +*/ +void idFile_SaveGamePipelined::ReadBlock() { + assert( nativeFile != NULL ); + // normally run in a separate thread + if ( nativeFileEndHit ) { + return; + } + // when we are reading the last block of the file, we may not fill the entire block + assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) ); + byte * dest = &compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ]; + size_t ioBytes = nativeFile->Read( dest, COMPRESSED_BLOCK_SIZE ); + compressedProducedBytes += ioBytes; + if ( ioBytes != COMPRESSED_BLOCK_SIZE ) { + nativeFileEndHit = true; + } +} + +/* +============================ +idFile_SaveGamePipelined::PumpCompressedBlock + +Reads: + compressed + compressedProducedBytes + +Modifies: + dataIO + byteIO + compressedConsumedBytes +============================ +*/ +void idFile_SaveGamePipelined::PumpCompressedBlock() { + // block until the background thread is done with the last block + if ( readThread != NULL ) { + readThread->WaitForThread(); + } else if ( nativeFile == NULL ) { + if ( !nativeFileEndHit ) { + blockAvailable.Wait(); + } + } + + // fetch the next block read in + dataIO = &compressed[ compressedConsumedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ]; + bytesIO = compressedProducedBytes - compressedConsumedBytes; + compressedConsumedBytes = compressedProducedBytes; + + if ( readThread != NULL ) { + // signal read thread to read another block + readThread->SignalWork(); + } else if ( nativeFile != NULL ) { + // run syncronously + ReadBlock(); + } else { + // request a new block + blockRequested.Raise(); + } +} + +/* +============================ +idFile_SaveGamePipelined::DecompressBlock + +Decompresses the next data block from the memory buffer + +Normally this runs in a separate thread when signalled, but +can be called in the main thread for debugging. + +This will not exit until a complete block has been decompressed, +unless end-of-file is reached. + +This may require additional compressed blocks to be read. + +Reads: + nativeFileEndHit + +Modifies: + dataIO + bytesIO + uncompressed + uncompressedProducedBytes + zStreamEndHit + zStream +============================ +*/ +void idFile_SaveGamePipelined::DecompressBlock() { + if ( zStreamEndHit ) { + return; + } + + assert( ( uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ) ) == 0 ); + zStream.next_out = (Bytef * )&uncompressed[ uncompressedProducedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ]; + zStream.avail_out = UNCOMPRESSED_BLOCK_SIZE; + + while( zStream.avail_out > 0 ) { + if ( zStream.avail_in == 0 ) { + do { + PumpCompressedBlock(); + if ( bytesIO == 0 && nativeFileEndHit ) { + // don't try to decompress any more if there is no more data + zStreamEndHit = true; + return; + } + } while ( bytesIO == 0 ); + + zStream.next_in = (Bytef *) dataIO; + zStream.avail_in = (uInt) bytesIO; + + dataIO = NULL; + bytesIO = 0; + + if ( sgf_checksums.GetBool() ) { + if ( sgf_testCorruption.GetInteger() == numChecksums ) { + zStream.next_in[0] ^= 0xFF; + } + zStream.avail_in -= sizeof( uint32 ); + uint32 checksum = MD5_BlockChecksum( zStream.next_in, zStream.avail_in ); + if ( !verify( zStream.next_in[zStream.avail_in + 0] == ( ( checksum >> 0 ) & 0xFF ) ) || + !verify( zStream.next_in[zStream.avail_in + 1] == ( ( checksum >> 8 ) & 0xFF ) ) || + !verify( zStream.next_in[zStream.avail_in + 2] == ( ( checksum >> 16 ) & 0xFF ) ) || + !verify( zStream.next_in[zStream.avail_in + 3] == ( ( checksum >> 24 ) & 0xFF ) ) ) { + // don't try to decompress any more if the checksum is wrong + zStreamEndHit = true; + return; + } + numChecksums++; + } + } + + const int zstat = inflate( &zStream, Z_SYNC_FLUSH ); + + uncompressedProducedBytes = zStream.total_out; + + if ( zstat == Z_STREAM_END ) { + // don't try to decompress any more + zStreamEndHit = true; + return; + } + if ( zstat != Z_OK ) { + idLib::Warning( "idFile_SaveGamePipelined::DecompressBlock: inflate() returned %i", zstat ); + zStreamEndHit = true; + return; + } + } + + assert( ( uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ) ) == 0 ); +} + +/* +============================ +idFile_SaveGamePipelined::PumpUncompressedBlock + +Called when an uncompressed block is drained. + +Reads: + uncompressed + uncompressedProducedBytes + +Modifies: + dataZlib + bytesZlib + uncompressedConsumedBytes +============================ +*/ +void idFile_SaveGamePipelined::PumpUncompressedBlock() { + if ( decompressThread != NULL ) { + // make sure thread has completed the last work + decompressThread->WaitForThread(); + } + + // fetch the next block produced by Zlib + dataZlib = &uncompressed[ uncompressedConsumedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ]; + bytesZlib = uncompressedProducedBytes - uncompressedConsumedBytes; + uncompressedConsumedBytes = uncompressedProducedBytes; + + if ( decompressThread != NULL ) { + // signal thread for more work + decompressThread->SignalWork(); + } else { + // run syncronously + DecompressBlock(); + } +} + +/* +============================ +idFile_SaveGamePipelined::Read + +Modifies: + dataZlib + bytesZlib +============================ +*/ +int idFile_SaveGamePipelined::Read( void * buffer, int length ) { + if ( buffer == NULL || length <= 0 ) { + return 0; + } + + assert( mode == READ ); + + size_t ioCount = 0; + size_t lengthRemaining = length; + byte * buffer_p = (byte *)buffer; + while ( lengthRemaining > 0 ) { + while ( bytesZlib == 0 ) { + PumpUncompressedBlock(); + if ( bytesZlib == 0 && zStreamEndHit ) { + return ioCount; + } + } + + const size_t copyFromBlock = ( lengthRemaining < bytesZlib ) ? lengthRemaining : bytesZlib; + + memcpy( buffer_p, dataZlib, copyFromBlock ); + dataZlib += copyFromBlock; + bytesZlib -= copyFromBlock; + + buffer_p += copyFromBlock; + ioCount += copyFromBlock; + lengthRemaining -= copyFromBlock; + } + return ioCount; +} + +/* +=================================================================================== + +TEST CODE + +=================================================================================== +*/ + +/* +============================ +TestProcessFile +============================ +*/ +static void TestProcessFile( const char * const filename ) { + idLib::Printf( "Processing %s:\n", filename ); + // load some test data + void *testData; + const int testDataLength = fileSystem->ReadFile( filename, &testData, NULL ); + + const char * const outFileName = "junk/savegameTest.bin"; + idFile_SaveGamePipelined *saveFile = new (TAG_IDFILE) idFile_SaveGamePipelined; + saveFile->OpenForWriting( outFileName, true ); + + const uint64 startWriteMicroseconds = Sys_Microseconds(); + + saveFile->Write( testData, testDataLength ); + delete saveFile; // final flush + const int readDataLength = fileSystem->GetFileLength( outFileName ); + + const uint64 endWriteMicroseconds = Sys_Microseconds(); + const uint64 writeMicroseconds = endWriteMicroseconds - startWriteMicroseconds; + + idLib::Printf( "%lld microseconds to compress %i bytes to %i written bytes = %4.1f MB/s\n", + writeMicroseconds, testDataLength, readDataLength, (float)readDataLength / writeMicroseconds ); + + void * readData = (void *)Mem_Alloc( testDataLength, TAG_SAVEGAMES ); + + const uint64 startReadMicroseconds = Sys_Microseconds(); + + idFile_SaveGamePipelined *loadFile = new (TAG_IDFILE) idFile_SaveGamePipelined; + loadFile->OpenForReading( outFileName, true ); + loadFile->Read( readData, testDataLength ); + delete loadFile; + + const uint64 endReadMicroseconds = Sys_Microseconds(); + const uint64 readMicroseconds = endReadMicroseconds - startReadMicroseconds; + + idLib::Printf( "%lld microseconds to decompress = %4.1f MB/s\n", readMicroseconds, (float)testDataLength / readMicroseconds ); + + int comparePoint; + for ( comparePoint = 0; comparePoint < testDataLength; comparePoint++ ) { + if ( ((byte *)readData)[comparePoint] != ((byte *)testData)[comparePoint] ) { + break; + } + } + if ( comparePoint != testDataLength ) { + idLib::Printf( "Compare failed at %i.\n", comparePoint ); + assert( 0 ); + } else { + idLib::Printf( "Compare succeeded.\n" ); + } + Mem_Free( readData ); + Mem_Free( testData ); +} + +/* +============================ +TestSaveGameFile +============================ +*/ +CONSOLE_COMMAND( TestSaveGameFile, "Exercises the pipelined savegame code", 0 ) { +#if 1 + TestProcessFile( "maps/game/wasteland1/wasteland1.map" ); +#else + // test every file in base (found a fencepost error >100 files in originally!) + idFileList * fileList = fileSystem->ListFiles( "", "" ); + for ( int i = 0; i < fileList->GetNumFiles(); i++ ) { + TestProcessFile( fileList->GetFile( i ) ); + common->UpdateConsoleDisplay(); + } + delete fileList; +#endif +} + +/* +============================ +TestCompressionSpeeds +============================ +*/ +CONSOLE_COMMAND( TestCompressionSpeeds, "Compares zlib and our code", 0 ) { + const char * const filename = "-colorMap.tga"; + + idLib::Printf( "Processing %s:\n", filename ); + // load some test data + void *testData; + const int testDataLength = fileSystem->ReadFile( filename, &testData, NULL ); + + const int startWriteMicroseconds = Sys_Microseconds(); + + idCompressor *compressor = idCompressor::AllocLZW(); +// idFile *f = fileSystem->OpenFileWrite( "junk/lzwTest.bin" ); + idFile_Memory *f = new (TAG_IDFILE) idFile_Memory( "junk/lzwTest.bin" ); + compressor->Init( f, true, 8 ); + + compressor->Write( testData, testDataLength ); + + const int readDataLength = f->Tell(); + + delete compressor; + delete f; + + const int endWriteMicroseconds = Sys_Microseconds(); + const int writeMicroseconds = endWriteMicroseconds - startWriteMicroseconds; + + idLib::Printf( "%i microseconds to compress %i bytes to %i written bytes = %4.1f MB/s\n", + writeMicroseconds, testDataLength, readDataLength, (float)readDataLength / writeMicroseconds ); + +} + diff --git a/neo/framework/File_SaveGame.h b/neo/framework/File_SaveGame.h new file mode 100644 index 00000000..147b5272 --- /dev/null +++ b/neo/framework/File_SaveGame.h @@ -0,0 +1,252 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __FILE_SAVEGAME_H__ +#define __FILE_SAVEGAME_H__ + +#include "zlib/zlib.h" + +// Listing of the types of files within a savegame package +enum saveGameType_t { + SAVEGAMEFILE_NONE = 0, + SAVEGAMEFILE_TEXT = BIT( 0 ), // implies that no checksum will be used + SAVEGAMEFILE_BINARY = BIT( 1 ), // implies that a checksum will also be used + SAVEGAMEFILE_COMPRESSED = BIT( 2 ), + SAVEGAMEFILE_PIPELINED = BIT( 3 ), + SAVEGAMEFILE_THUMB = BIT( 4 ), // for special processing on certain platforms + SAVEGAMEFILE_BKGRND_IMAGE = BIT( 5 ), // for special processing on certain platforms, large background used on PS3 + SAVEGAMEFILE_AUTO_DELETE = BIT( 6 ), // to be deleted automatically after completed + SAVEGAMEFILE_OPTIONAL = BIT( 7 ) // if this flag is not set and missing, there is an error +}; + +/* +================================================ +idFile_SaveGame +================================================ +*/ +class idFile_SaveGame : public idFile_Memory { +public: + idFile_SaveGame() : type( SAVEGAMEFILE_NONE ), error( false ) {} + idFile_SaveGame( const char * _name ) : idFile_Memory( _name ), type( SAVEGAMEFILE_NONE ), error( false ) {} + idFile_SaveGame( const char * _name, int type_ ) : idFile_Memory( _name ), type( type_ ), error( false ) {} + + virtual ~idFile_SaveGame() { } + + bool operator==( const idFile_SaveGame & other ) const { + return idStr::Icmp( GetName(), other.GetName() ) == 0; + } + bool operator==( const char * _name ) const { + return idStr::Icmp( GetName(), _name ) == 0; + } + void SetNameAndType( const char *_name, int _type ) { + name = _name; + type = _type; + } +public: // TODO_KC_CR for now... + + int type; // helps platform determine what to do with the file (encrypt, checksum, etc.) + bool error; // when loading, this is set if there is a problem +}; + +/* +================================================ +idFile_SaveGamePipelined uses threads to pipeline overlap compression and IO +================================================ +*/ +class idSGFreadThread; +class idSGFwriteThread; +class idSGFdecompressThread; +class idSGFcompressThread; + +struct blockForIO_t { + byte * data; + size_t bytes; +}; + +class idFile_SaveGamePipelined : public idFile { +public: + // The buffers each hold two blocks of data, so one block can be operated on by + // the next part of the generate / compress / IO pipeline. The factor of two + // size difference between the uncompressed and compressed blocks is unrelated + // to the fact that there are two blocks in each buffer. + static const int COMPRESSED_BLOCK_SIZE = 128 * 1024; + static const int UNCOMPRESSED_BLOCK_SIZE = 256 * 1024; + + + idFile_SaveGamePipelined(); + virtual ~idFile_SaveGamePipelined(); + + bool OpenForReading( const char * const filename, bool useNativeFile ); + bool OpenForWriting( const char * const filename, bool useNativeFile ); + + bool OpenForReading( idFile * file ); + bool OpenForWriting( idFile * file ); + + // Finish any reading or writing. + void Finish(); + + // Abort any reading or writing. + void Abort(); + + // Cancel any reading or writing for app termination + static void CancelToTerminate() { cancelToTerminate = true; } + + bool ReadBuildVersion(); + const char * GetBuildVersion() const { return buildVersion; } + + bool ReadSaveFormatVersion(); + int GetSaveFormatVersion() const { return saveFormatVersion; } + int GetPointerSize() const; + + //------------------------ + // idFile Interface + //------------------------ + + virtual const char * GetName() const { return name.c_str(); } + virtual const char * GetFullPath() const { return name.c_str(); } + virtual int Read( void * buffer, int len ); + virtual int Write( const void * buffer, int len ); + + // this file is strictly streaming, you can't seek at all + virtual int Length() const { return compressedLength; } + virtual void SetLength( size_t len ) { compressedLength = len; } + virtual int Tell() const { assert( 0 ); return 0; } + virtual int Seek( long offset, fsOrigin_t origin ) { assert( 0 ); return 0; } + + virtual ID_TIME_T Timestamp() const { return 0; } + + //------------------------ + // These can be used by a background thread to read/write data + // when the file was opened with 'useNativeFile' set to false. + //------------------------ + + enum mode_t { + CLOSED, + WRITE, + READ + }; + + // Get the file mode: read/write. + mode_t GetMode() const { return mode; } + + // Called by a background thread to get the next block to be written out. + // This may block until a block has been made available through the pipeline. + // Pass in NULL to notify the last write failed. + // Returns false if there are no more blocks. + bool NextWriteBlock( blockForIO_t * block ); + + // Called by a background thread to get the next block to read data into and to + // report the number of bytes written to the previous block. + // This may block until space is available to place the next block. + // Pass in NULL to notify the end of the file was reached. + // Returns false if there are no more blocks. + bool NextReadBlock( blockForIO_t * block, size_t lastReadBytes ); + +private: + friend class idSGFreadThread; + friend class idSGFwriteThread; + friend class idSGFdecompressThread; + friend class idSGFcompressThread; + + idStr name; // Name of the file. + idStr osPath; // OS path. + mode_t mode; // Open mode. + size_t compressedLength; + + static const int COMPRESSED_BUFFER_SIZE = COMPRESSED_BLOCK_SIZE * 2; + static const int UNCOMPRESSED_BUFFER_SIZE = UNCOMPRESSED_BLOCK_SIZE * 2; + + byte uncompressed[UNCOMPRESSED_BUFFER_SIZE]; + size_t uncompressedProducedBytes; // not masked + size_t uncompressedConsumedBytes; // not masked + + byte compressed[COMPRESSED_BUFFER_SIZE]; + size_t compressedProducedBytes; // not masked + size_t compressedConsumedBytes; // not masked + + //------------------------ + // These variables are used to pass data between threads in a thread-safe manner. + //------------------------ + + byte * dataZlib; + size_t bytesZlib; + + byte * dataIO; + size_t bytesIO; + + //------------------------ + // These variables are used by CompressBlock() and DecompressBlock(). + //------------------------ + + z_stream zStream; + int zLibFlushType; // Z_NO_FLUSH or Z_FINISH + bool zStreamEndHit; + int numChecksums; + + //------------------------ + // These variables are used by WriteBlock() and ReadBlock(). + //------------------------ + + idFile * nativeFile; + bool nativeFileEndHit; + bool finished; + + //------------------------ + // The background threads and signals for NextWriteBlock() and NextReadBlock(). + //------------------------ + + idSGFreadThread * readThread; + idSGFwriteThread * writeThread; + + idSGFdecompressThread * decompressThread; + idSGFcompressThread * compressThread; + + idSysSignal blockRequested; + idSysSignal blockAvailable; + idSysSignal blockFinished; + + idStrStatic< 32 > buildVersion; // build version this file was saved with + int16 pointerSize; // the number of bytes in a pointer, because different pointer sizes mean different offsets into objects a 64 bit build cannot load games saved from a 32 bit build or vice version (a value of 0 is interpreted as 4 bytes) + int16 saveFormatVersion; // version number specific to save games (for maintaining save compatibility across builds) + + //------------------------ + // These variables are used when we want to abort due to the termination of the application + //------------------------ + static bool cancelToTerminate; + + void FlushUncompressedBlock(); + void FlushCompressedBlock(); + void CompressBlock(); + void WriteBlock(); + + void PumpUncompressedBlock(); + void PumpCompressedBlock(); + void DecompressBlock(); + void ReadBlock(); +}; + +#endif // !__FILE_SAVEGAME_H__ diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp new file mode 100644 index 00000000..1e518346 --- /dev/null +++ b/neo/framework/KeyInput.cpp @@ -0,0 +1,831 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +typedef struct { + keyNum_t keynum; + const char * name; + const char * strId; // localized string id +} keyname_t; + +#define NAMEKEY( code, strId ) { K_##code, #code, strId } +#define NAMEKEY2( code ) { K_##code, #code, #code } + +#define ALIASKEY( alias, code ) { K_##code, alias, "" } + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +keyname_t keynames[] = +{ + NAMEKEY( ESCAPE, "#str_07020" ), + NAMEKEY2( 1 ), + NAMEKEY2( 2 ), + NAMEKEY2( 3 ), + NAMEKEY2( 4 ), + NAMEKEY2( 5 ), + NAMEKEY2( 6 ), + NAMEKEY2( 7 ), + NAMEKEY2( 8 ), + NAMEKEY2( 9 ), + NAMEKEY2( 0 ), + NAMEKEY( MINUS, "-" ), + NAMEKEY( EQUALS, "=" ), + NAMEKEY( BACKSPACE, "#str_07022" ), + NAMEKEY( TAB, "#str_07018" ), + NAMEKEY2( Q ), + NAMEKEY2( W ), + NAMEKEY2( E ), + NAMEKEY2( R ), + NAMEKEY2( T ), + NAMEKEY2( Y ), + NAMEKEY2( U ), + NAMEKEY2( I ), + NAMEKEY2( O ), + NAMEKEY2( P ), + NAMEKEY( LBRACKET, "[" ), + NAMEKEY( RBRACKET, "]" ), + NAMEKEY( ENTER, "#str_07019" ), + NAMEKEY( LCTRL, "#str_07028" ), + NAMEKEY2( A ), + NAMEKEY2( S ), + NAMEKEY2( D ), + NAMEKEY2( F ), + NAMEKEY2( G ), + NAMEKEY2( H ), + NAMEKEY2( J ), + NAMEKEY2( K ), + NAMEKEY2( L ), + NAMEKEY( SEMICOLON, "#str_07129" ), + NAMEKEY( APOSTROPHE, "#str_07130" ), + NAMEKEY( GRAVE, "`" ), + NAMEKEY( LSHIFT, "#str_07029" ), + NAMEKEY( BACKSLASH, "\\" ), + NAMEKEY2( Z ), + NAMEKEY2( X ), + NAMEKEY2( C ), + NAMEKEY2( V ), + NAMEKEY2( B ), + NAMEKEY2( N ), + NAMEKEY2( M ), + NAMEKEY( COMMA, "," ), + NAMEKEY( PERIOD, "." ), + NAMEKEY( SLASH, "/" ), + NAMEKEY( RSHIFT, "#str_bind_RSHIFT" ), + NAMEKEY( KP_STAR, "#str_07126" ), + NAMEKEY( LALT, "#str_07027" ), + NAMEKEY( SPACE, "#str_07021" ), + NAMEKEY( CAPSLOCK, "#str_07034" ), + NAMEKEY( F1, "#str_07036" ), + NAMEKEY( F2, "#str_07037" ), + NAMEKEY( F3, "#str_07038" ), + NAMEKEY( F4, "#str_07039" ), + NAMEKEY( F5, "#str_07040" ), + NAMEKEY( F6, "#str_07041" ), + NAMEKEY( F7, "#str_07042" ), + NAMEKEY( F8, "#str_07043" ), + NAMEKEY( F9, "#str_07044" ), + NAMEKEY( F10, "#str_07045" ), + NAMEKEY( NUMLOCK, "#str_07125" ), + NAMEKEY( SCROLL, "#str_07035" ), + NAMEKEY( KP_7, "#str_07110" ), + NAMEKEY( KP_8, "#str_07111" ), + NAMEKEY( KP_9, "#str_07112" ), + NAMEKEY( KP_MINUS, "#str_07123" ), + NAMEKEY( KP_4, "#str_07113" ), + NAMEKEY( KP_5, "#str_07114" ), + NAMEKEY( KP_6, "#str_07115" ), + NAMEKEY( KP_PLUS, "#str_07124" ), + NAMEKEY( KP_1, "#str_07116" ), + NAMEKEY( KP_2, "#str_07117" ), + NAMEKEY( KP_3, "#str_07118" ), + NAMEKEY( KP_0, "#str_07120" ), + NAMEKEY( KP_DOT, "#str_07121" ), + NAMEKEY( F11, "#str_07046" ), + NAMEKEY( F12, "#str_07047" ), + NAMEKEY2( F13 ), + NAMEKEY2( F14 ), + NAMEKEY2( F15 ), + NAMEKEY2( KANA ), + NAMEKEY2( CONVERT ), + NAMEKEY2( NOCONVERT ), + NAMEKEY2( YEN ), + NAMEKEY( KP_EQUALS, "#str_07127" ), + NAMEKEY2( CIRCUMFLEX ), + NAMEKEY( AT, "@" ), + NAMEKEY( COLON, ":" ), + NAMEKEY( UNDERLINE, "_" ), + NAMEKEY2( KANJI ), + NAMEKEY2( STOP ), + NAMEKEY2( AX ), + NAMEKEY2( UNLABELED ), + NAMEKEY( KP_ENTER, "#str_07119" ), + NAMEKEY( RCTRL, "#str_bind_RCTRL" ), + NAMEKEY( KP_COMMA, "," ), + NAMEKEY( KP_SLASH, "#str_07122" ), + NAMEKEY( PRINTSCREEN, "#str_07179" ), + NAMEKEY( RALT, "#str_bind_RALT" ), + NAMEKEY( PAUSE, "#str_07128" ), + NAMEKEY( HOME, "#str_07052" ), + NAMEKEY( UPARROW, "#str_07023" ), + NAMEKEY( PGUP, "#str_07051" ), + NAMEKEY( LEFTARROW, "#str_07025" ), + NAMEKEY( RIGHTARROW, "#str_07026" ), + NAMEKEY( END, "#str_07053" ), + NAMEKEY( DOWNARROW, "#str_07024" ), + NAMEKEY( PGDN, "#str_07050" ), + NAMEKEY( INS, "#str_07048" ), + NAMEKEY( DEL, "#str_07049" ), + NAMEKEY( LWIN, "#str_07030" ), + NAMEKEY( RWIN, "#str_07031" ), + NAMEKEY( APPS, "#str_07032" ), + NAMEKEY2( POWER ), + NAMEKEY2( SLEEP ), + + // -- + + NAMEKEY( MOUSE1, "#str_07054" ), + NAMEKEY( MOUSE2, "#str_07055" ), + NAMEKEY( MOUSE3, "#str_07056" ), + NAMEKEY( MOUSE4, "#str_07057" ), + NAMEKEY( MOUSE5, "#str_07058" ), + NAMEKEY( MOUSE6, "#str_07059" ), + NAMEKEY( MOUSE7, "#str_07060" ), + NAMEKEY( MOUSE8, "#str_07061" ), + + NAMEKEY( MWHEELDOWN, "#str_07132" ), + NAMEKEY( MWHEELUP, "#str_07131" ), + + NAMEKEY( JOY1, "#str_07062" ), + NAMEKEY( JOY2, "#str_07063" ), + NAMEKEY( JOY3, "#str_07064" ), + NAMEKEY( JOY4, "#str_07065" ), + NAMEKEY( JOY5, "#str_07066" ), + NAMEKEY( JOY6, "#str_07067" ), + NAMEKEY( JOY7, "#str_07068" ), + NAMEKEY( JOY8, "#str_07069" ), + NAMEKEY( JOY9, "#str_07070" ), + NAMEKEY( JOY10, "#str_07071" ), + NAMEKEY( JOY11, "#str_07072" ), + NAMEKEY( JOY12, "#str_07073" ), + NAMEKEY( JOY13, "#str_07074" ), + NAMEKEY( JOY14, "#str_07075" ), + NAMEKEY( JOY15, "#str_07076" ), + NAMEKEY( JOY16, "#str_07077" ), + + NAMEKEY2( JOY_DPAD_UP ), + NAMEKEY2( JOY_DPAD_DOWN ), + NAMEKEY2( JOY_DPAD_LEFT ), + NAMEKEY2( JOY_DPAD_RIGHT ), + + NAMEKEY2( JOY_STICK1_UP ), + NAMEKEY2( JOY_STICK1_DOWN ), + NAMEKEY2( JOY_STICK1_LEFT ), + NAMEKEY2( JOY_STICK1_RIGHT ), + + NAMEKEY2( JOY_STICK2_UP ), + NAMEKEY2( JOY_STICK2_DOWN ), + NAMEKEY2( JOY_STICK2_LEFT ), + NAMEKEY2( JOY_STICK2_RIGHT ), + + NAMEKEY2( JOY_TRIGGER1 ), + NAMEKEY2( JOY_TRIGGER2 ), + + //------------------------ + // Aliases to make it easier to bind or to support old configs + //------------------------ + ALIASKEY( "ALT", LALT ), + ALIASKEY( "RIGHTALT", RALT ), + ALIASKEY( "CTRL", LCTRL ), + ALIASKEY( "SHIFT", LSHIFT ), + ALIASKEY( "MENU", APPS ), + ALIASKEY( "COMMAND", LALT ), + + ALIASKEY( "KP_HOME", KP_7 ), + ALIASKEY( "KP_UPARROW", KP_8 ), + ALIASKEY( "KP_PGUP", KP_9 ), + ALIASKEY( "KP_LEFTARROW", KP_4 ), + ALIASKEY( "KP_RIGHTARROW", KP_6 ), + ALIASKEY( "KP_END", KP_1 ), + ALIASKEY( "KP_DOWNARROW", KP_2 ), + ALIASKEY( "KP_PGDN", KP_3 ), + ALIASKEY( "KP_INS", KP_0 ), + ALIASKEY( "KP_DEL", KP_DOT ), + ALIASKEY( "KP_NUMLOCK", NUMLOCK ), + + ALIASKEY( "-", MINUS ), + ALIASKEY( "=", EQUALS ), + ALIASKEY( "[", LBRACKET ), + ALIASKEY( "]", RBRACKET ), + ALIASKEY( "\\", BACKSLASH ), + ALIASKEY( "/", SLASH ), + ALIASKEY( ",", COMMA ), + ALIASKEY( ".", PERIOD ), + + {K_NONE, NULL, NULL} +}; + +class idKey { +public: + idKey() { down = false; repeats = 0; usercmdAction = 0; } + bool down; + int repeats; // if > 1, it is autorepeating + idStr binding; + int usercmdAction; // for testing by the asyncronous usercmd generation +}; + +bool key_overstrikeMode = false; +idKey * keys = NULL; + + +/* +=================== +idKeyInput::ArgCompletion_KeyName +=================== +*/ +void idKeyInput::ArgCompletion_KeyName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( keyname_t * kn = keynames; kn->name; kn++ ) { + callback( va( "%s %s", args.Argv( 0 ), kn->name ) ); + } +} + +/* +=================== +idKeyInput::GetOverstrikeMode +=================== +*/ +bool idKeyInput::GetOverstrikeMode() { + return key_overstrikeMode; +} + +/* +=================== +idKeyInput::SetOverstrikeMode +=================== +*/ +void idKeyInput::SetOverstrikeMode( bool state ) { + key_overstrikeMode = state; +} + +/* +=================== +idKeyInput::IsDown +=================== +*/ +bool idKeyInput::IsDown( int keynum ) { + if ( keynum == -1 ) { + return false; + } + + return keys[keynum].down; +} + +/* +======================== +idKeyInput::StringToKeyNum +======================== +*/ +keyNum_t idKeyInput::StringToKeyNum( const char * str ) { + + if ( !str || !str[0] ) { + return K_NONE; + } + + // scan for a text match + for ( keyname_t * kn = keynames; kn->name; kn++ ) { + if ( !idStr::Icmp( str, kn->name ) ) { + return kn->keynum; + } + } + + return K_NONE; +} + +/* +======================== +idKeyInput::KeyNumToString +======================== +*/ +const char * idKeyInput::KeyNumToString( keyNum_t keynum ) { + // check for a key string + for ( keyname_t * kn = keynames; kn->name; kn++ ) { + if ( keynum == kn->keynum ) { + return kn->name; + } + } + return "?"; +} + + +/* +======================== +idKeyInput::LocalizedKeyName +======================== +*/ +const char * idKeyInput::LocalizedKeyName( keyNum_t keynum ) { + if ( keynum < K_JOY1 ) { + // On the PC, we want to turn the scan code in to a key label that matches the currently selected keyboard layout + unsigned char keystate[256] = { 0 }; + WCHAR temp[5]; + + int scancode = (int)keynum; + int vkey = MapVirtualKey( keynum, MAPVK_VSC_TO_VK_EX ); + int result = -1; + while ( result < 0 ) { + result = ToUnicode( vkey, scancode, keystate, temp, sizeof( temp ) / sizeof( temp[0] ), 0 ); + } + if ( result > 0 && temp[0] > ' ' && iswprint( temp[0] ) ) { + static idStr bindStr; + bindStr.Empty(); + bindStr.AppendUTF8Char( temp[0] ); + return bindStr; + } + } + + // check for a key string + for ( keyname_t * kn = keynames; kn->name; kn++ ) { + if ( keynum == kn->keynum ) { + return idLocalization::GetString( kn->strId ); + } + } + return "????"; +} + +/* +=================== +idKeyInput::SetBinding +=================== +*/ +void idKeyInput::SetBinding( int keynum, const char *binding ) { + if ( keynum == -1 ) { + return; + } + + // Clear out all button states so we aren't stuck forever thinking this key is held down + usercmdGen->Clear(); + + // allocate memory for new binding + keys[keynum].binding = binding; + + // find the action for the async command generation + keys[keynum].usercmdAction = usercmdGen->CommandStringUsercmdData( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); +} + + +/* +=================== +idKeyInput::GetBinding +=================== +*/ +const char *idKeyInput::GetBinding( int keynum ) { + if ( keynum == -1 ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +idKeyInput::GetUsercmdAction +=================== +*/ +int idKeyInput::GetUsercmdAction( int keynum ) { + return keys[ keynum ].usercmdAction; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f( const idCmdArgs &args ) { + int b; + + if ( args.Argc() != 2 ) { + common->Printf( "unbind : remove commands from a key\n" ); + return; + } + + b = idKeyInput::StringToKeyNum( args.Argv(1) ); + if ( b == -1 ) { + // If it wasn't a key, it could be a command + if ( !idKeyInput::UnbindBinding( args.Argv(1) ) ) { + common->Printf( "\"%s\" isn't a valid key\n", args.Argv(1) ); + } + } else { + idKeyInput::SetBinding( b, "" ); + } +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f( const idCmdArgs &args ) { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + idKeyInput::SetBinding( i, "" ); + } +} + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f( const idCmdArgs &args ) { + int i, c, b; + char cmd[MAX_STRING_CHARS]; + + c = args.Argc(); + + if ( c < 2 ) { + common->Printf( "bind [command] : attach a command to a key\n" ); + return; + } + b = idKeyInput::StringToKeyNum( args.Argv(1) ); + if ( b == -1 ) { + common->Printf( "\"%s\" isn't a valid key\n", args.Argv(1) ); + return; + } + + if ( c == 2 ) { + if ( keys[b].binding.Length() ) { + common->Printf( "\"%s\" = \"%s\"\n", args.Argv(1), keys[b].binding.c_str() ); + } + else { + common->Printf( "\"%s\" is not bound\n", args.Argv(1) ); + } + return; + } + + // copy the rest of the command line + cmd[0] = 0; // start out with a null string + for ( i = 2; i < c; i++ ) { + strcat( cmd, args.Argv( i ) ); + if ( i != (c-1) ) { + strcat( cmd, " " ); + } + } + + idKeyInput::SetBinding( b, cmd ); +} + +/* +============ +Key_BindUnBindTwo_f + +binds keynum to bindcommand and unbinds if there are already two binds on the key +============ +*/ +void Key_BindUnBindTwo_f( const idCmdArgs &args ) { + int c = args.Argc(); + if ( c < 3 ) { + common->Printf( "bindunbindtwo [command]\n" ); + return; + } + int key = atoi( args.Argv( 1 ) ); + idStr bind = args.Argv( 2 ); + if ( idKeyInput::NumBinds( bind ) >= 2 && !idKeyInput::KeyIsBoundTo( key, bind ) ) { + idKeyInput::UnbindBinding( bind ); + } + idKeyInput::SetBinding( key, bind ); +} + + + +/* +============ +idKeyInput::WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void idKeyInput::WriteBindings( idFile *f ) { + f->Printf( "unbindall\n" ); + + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].binding.Length() ) { + const char *name = KeyNumToString( (keyNum_t)i ); + f->Printf( "bind \"%s\" \"%s\"\n", name, keys[i].binding.c_str() ); + } + } +} + +/* +============ +Key_ListBinds_f +============ +*/ +void Key_ListBinds_f( const idCmdArgs &args ) { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].binding.Length() ) { + common->Printf( "%s \"%s\"\n", idKeyInput::KeyNumToString( (keyNum_t)i ), keys[i].binding.c_str() ); + } + } +} + +/* +============ +idKeyInput::KeysFromBinding +returns the localized name of the key for the binding +============ +*/ +const char *idKeyInput::KeysFromBinding( const char *bind ) { + static char keyName[MAX_STRING_CHARS]; + keyName[0] = 0; + + if ( bind && *bind ) { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].binding.Icmp( bind ) == 0 ) { + if ( keyName[0] != '\0' ) { + idStr::Append( keyName, sizeof( keyName ), idLocalization::GetString( "#str_07183" ) ); + } + idStr::Append( keyName, sizeof( keyName ), LocalizedKeyName( (keyNum_t)i ) ); + } + } + } + if ( keyName[0] == '\0' ) { + idStr::Copynz( keyName, idLocalization::GetString( "#str_07133" ), sizeof( keyName ) ); + } + idStr::ToLower( keyName ); + return keyName; +} + +/* +======================== +idKeyInput::KeyBindingsFromBinding + +return: bindings for keyboard mouse and gamepad +======================== +*/ +keyBindings_t idKeyInput::KeyBindingsFromBinding( const char * bind, bool firstOnly, bool localized ) { + idStr keyboard; + idStr mouse; + idStr gamepad; + + if ( bind && *bind ) { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].binding.Icmp( bind ) == 0 ) { + if ( i >= K_JOY1 && i <= K_JOY_DPAD_RIGHT ) { + const char * gamepadKey = ""; + if ( localized ) { + gamepadKey = LocalizedKeyName( (keyNum_t)i ); + } else { + gamepadKey = KeyNumToString( (keyNum_t)i ); + } + if ( idStr::Icmp( gamepadKey, "" ) != 0 ) { + if ( !gamepad.IsEmpty() ) { + if ( firstOnly ) { + continue; + } + gamepad.Append( ", " ); + } + gamepad.Append( gamepadKey ); + } + } else if ( i >= K_MOUSE1 && i <= K_MWHEELUP ) { + const char * mouseKey = ""; + if ( localized ) { + mouseKey = LocalizedKeyName( (keyNum_t)i ); + } else { + mouseKey = KeyNumToString( (keyNum_t)i ); + } + if ( idStr::Icmp( mouseKey, "" ) != 0 ) { + if ( !mouse.IsEmpty() ) { + if ( firstOnly ) { + continue; + } + mouse.Append( ", " ); + } + mouse.Append( mouseKey ); + } + } else { + const char * tmp = ""; + if ( localized ) { + tmp = LocalizedKeyName( (keyNum_t)i ); + } else { + tmp = KeyNumToString( (keyNum_t)i ); + } + if ( idStr::Icmp( tmp, "" ) != 0 && idStr::Icmp( tmp, keyboard ) != 0 ) { + if ( !keyboard.IsEmpty() ) { + if ( firstOnly ) { + continue; + } + keyboard.Append( ", " ); + } + keyboard.Append( tmp ); + } + } + } + } + } + + keyBindings_t bindings; + bindings.gamepad = gamepad; + bindings.mouse = mouse; + bindings.keyboard = keyboard; + + return bindings; +} + +/* +============ +idKeyInput::BindingFromKey +returns the binding for the localized name of the key +============ +*/ +const char * idKeyInput::BindingFromKey( const char *key ) { + const int keyNum = idKeyInput::StringToKeyNum( key ); + if ( keyNum < 0 || keyNum >= K_LAST_KEY ) { + return NULL; + } + return keys[keyNum].binding.c_str(); +} + +/* +============ +idKeyInput::UnbindBinding +============ +*/ +bool idKeyInput::UnbindBinding( const char *binding ) { + bool unbound = false; + if ( binding && *binding ) { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].binding.Icmp( binding ) == 0 ) { + SetBinding( i, "" ); + unbound = true; + } + } + } + return unbound; +} + +/* +============ +idKeyInput::NumBinds +============ +*/ +int idKeyInput::NumBinds( const char *binding ) { + int count = 0; + + if ( binding && *binding ) { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].binding.Icmp( binding ) == 0 ) { + count++; + } + } + } + return count; +} + +/* +============ +idKeyInput::KeyIsBountTo +============ +*/ +bool idKeyInput::KeyIsBoundTo( int keynum, const char *binding ) { + if ( keynum >= 0 && keynum < K_LAST_KEY ) { + return ( keys[keynum].binding.Icmp( binding ) == 0 ); + } + return false; +} + +/* +=================== +idKeyInput::PreliminaryKeyEvent + +Tracks global key up/down state +Called by the system for both key up and key down events +=================== +*/ +void idKeyInput::PreliminaryKeyEvent( int keynum, bool down ) { + keys[keynum].down = down; +} + +/* +================= +idKeyInput::ExecKeyBinding +================= +*/ +bool idKeyInput::ExecKeyBinding( int keynum ) { + // commands that are used by the async thread + // don't add text + if ( keys[keynum].usercmdAction ) { + return false; + } + + // send the bound action + if ( keys[keynum].binding.Length() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, keys[keynum].binding.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); + } + return true; +} + +/* +=================== +idKeyInput::ClearStates +=================== +*/ +void idKeyInput::ClearStates() { + for ( int i = 0; i < K_LAST_KEY; i++ ) { + if ( keys[i].down ) { + PreliminaryKeyEvent( i, false ); + } + keys[i].down = false; + } + + // clear the usercommand states + usercmdGen->Clear(); +} + +/* +=================== +idKeyInput::Init +=================== +*/ +void idKeyInput::Init() { + + keys = new (TAG_SYSTEM) idKey[K_LAST_KEY]; + + // register our functions + cmdSystem->AddCommand( "bind", Key_Bind_f, CMD_FL_SYSTEM, "binds a command to a key", idKeyInput::ArgCompletion_KeyName ); + cmdSystem->AddCommand( "bindunbindtwo", Key_BindUnBindTwo_f, CMD_FL_SYSTEM, "binds a key but unbinds it first if there are more than two binds" ); + cmdSystem->AddCommand( "unbind", Key_Unbind_f, CMD_FL_SYSTEM, "unbinds any command from a key", idKeyInput::ArgCompletion_KeyName ); + cmdSystem->AddCommand( "unbindall", Key_Unbindall_f, CMD_FL_SYSTEM, "unbinds any commands from all keys" ); + cmdSystem->AddCommand( "listBinds", Key_ListBinds_f, CMD_FL_SYSTEM, "lists key bindings" ); +} + +/* +=================== +idKeyInput::Shutdown +=================== +*/ +void idKeyInput::Shutdown() { + delete [] keys; + keys = NULL; +} + + +/* +======================== +Key_CovertHIDCode +Converts from a USB HID code to a K_ code +======================== +*/ +int Key_CovertHIDCode( int hid ) { + if ( hid >= 0 && hid <= 106 ) { + int table[] = { + K_NONE, K_NONE, K_NONE, K_NONE, + K_A, K_B, K_C, K_D, K_E, K_F, K_G, K_H, K_I, K_J, K_K, K_L, K_M, K_N, K_O, K_P, K_Q, K_R, K_S, K_T, K_U, K_V, K_W, K_X, K_Y, K_Z, + K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9, K_0, + K_ENTER, K_ESCAPE, K_BACKSPACE, K_TAB, K_SPACE, + K_MINUS, K_EQUALS, K_LBRACKET, K_RBRACKET, K_BACKSLASH, K_NONE, K_SEMICOLON, K_APOSTROPHE, K_GRAVE, K_COMMA, K_PERIOD, K_SLASH, K_CAPSLOCK, + K_F1, K_F2, K_F3, K_F4, K_F5, K_F6, K_F7, K_F8, K_F9, K_F10, K_F11, K_F12, + K_PRINTSCREEN, K_SCROLL, K_PAUSE, K_INS, K_HOME, K_PGUP, K_DEL, K_END, K_PGDN, K_RIGHTARROW, K_LEFTARROW, K_DOWNARROW, K_UPARROW, + K_NUMLOCK, K_KP_SLASH, K_KP_STAR, K_KP_MINUS, K_KP_PLUS, K_KP_ENTER, + K_KP_1, K_KP_2, K_KP_3, K_KP_4, K_KP_5, K_KP_6, K_KP_7, K_KP_8, K_KP_9, K_KP_0, K_KP_DOT, + K_NONE, K_APPS, K_POWER, K_KP_EQUALS, + K_F13, K_F14, K_F15 + }; + return table[hid]; + } + if ( hid >= 224 && hid <= 231 ) { + int table[] = { + K_LCTRL, K_LSHIFT, K_LALT, K_LWIN, + K_RCTRL, K_RSHIFT, K_RALT, K_RWIN + }; + return table[hid - 224]; + } + return K_NONE; +} diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h new file mode 100644 index 00000000..a8270832 --- /dev/null +++ b/neo/framework/KeyInput.h @@ -0,0 +1,72 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __KEYINPUT_H__ +#define __KEYINPUT_H__ + +struct keyBindings_t { + idStr keyboard; + idStr mouse; + idStr gamepad; +}; + +class idSerializer; + +// Converts from a USB HID code to a K_ code +int Key_CovertHIDCode( int hid ); + +class idKeyInput { +public: + static void Init(); + static void Shutdown(); + + static void ArgCompletion_KeyName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void PreliminaryKeyEvent( int keyNum, bool down ); + static bool IsDown( int keyNum ); + static int GetUsercmdAction( int keyNum ); + static bool GetOverstrikeMode(); + static void SetOverstrikeMode( bool state ); + static void ClearStates(); + + static keyNum_t StringToKeyNum( const char * str ); // This is used by the "bind" command + static const char * KeyNumToString( keyNum_t keyNum ); // This is the inverse of StringToKeyNum, used for config files + static const char * LocalizedKeyName( keyNum_t keyNum ); // This returns text suitable to print on screen + + static void SetBinding( int keyNum, const char *binding ); + static const char * GetBinding( int keyNum ); + static bool UnbindBinding( const char *bind ); + static int NumBinds( const char *binding ); + static bool ExecKeyBinding( int keyNum ); + static const char * KeysFromBinding( const char *bind ); + static const char * BindingFromKey( const char *key ); + static bool KeyIsBoundTo( int keyNum, const char *binding ); + static void WriteBindings( idFile *f ); + static keyBindings_t KeyBindingsFromBinding( const char * bind, bool firstOnly = false, bool localized = false ); +}; + +#endif /* !__KEYINPUT_H__ */ diff --git a/neo/framework/Licensee.h b/neo/framework/Licensee.h new file mode 100644 index 00000000..695faa44 --- /dev/null +++ b/neo/framework/Licensee.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Definitions for information that is related to a licensee's game name and location. + +=============================================================================== +*/ + +#define GAME_NAME "DOOM 3: BFG Edition" // appears on window titles and errors +#define SAVE_PATH "\\id Software\\DOOM 3 BFG" + +#define ENGINE_VERSION "D3BFG 1" // printed in console + +#define BASE_GAMEDIR "base" + +#define CONFIG_FILE "D3BFGConfig.cfg" + +// see ASYNC_PROTOCOL_VERSION +// use a different major for each game +#define ASYNC_PROTOCOL_MAJOR 1 + +// <= Doom v1.1: 1. no DS_VERSION token ( default ) +// Doom v1.2: 2 +// Doom 3 BFG: 3 +#define RENDERDEMO_VERSION 3 + +// win32 info +#define WIN32_CONSOLE_CLASS "D3BFG_WinConsole" +#define WIN32_WINDOW_CLASS_NAME "D3BFG" +#define WIN32_FAKE_WINDOW_CLASS_NAME "D3BFG_WGL_FAKE" diff --git a/neo/framework/PlayerProfile.cpp b/neo/framework/PlayerProfile.cpp new file mode 100644 index 00000000..e5dae1d3 --- /dev/null +++ b/neo/framework/PlayerProfile.cpp @@ -0,0 +1,408 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "PlayerProfile.h" + +// After releasing a version to the market, here are limitations for compatibility: +// - the major version should not ever change +// - always add new items to the bottom of the save/load routine +// - never remove or change the size of items, just stop using them or add new items to the end +// +// The biggest reason these limitations exist is because if a newer profile is created and then loaded with a GMC +// version, we have to support it. +const int16 PROFILE_TAG = ( 'D' << 8 ) | '3'; +const int8 PROFILE_VER_MAJOR = 10; // If this is changed, you should reset the minor version and remove all backward compatible code +const int8 PROFILE_VER_MINOR = 0; // Within each major version, minor versions can be supported for backward compatibility + +class idPlayerProfileLocal : public idPlayerProfile { +}; +idPlayerProfileLocal playerProfiles[MAX_INPUT_DEVICES]; + +/* +======================== +Contains data that needs to be saved out on a per player profile basis, global for the lifetime of the player so +the data can be shared across computers. +- HUD tint colors +- key bindings +- etc... +======================== +*/ + +/* +======================== +idPlayerProfile * CreatePlayerProfile +======================== +*/ +idPlayerProfile * idPlayerProfile::CreatePlayerProfile( int deviceIndex ) { + playerProfiles[deviceIndex].SetDefaults(); + playerProfiles[deviceIndex].deviceNum = deviceIndex; + return &playerProfiles[deviceIndex]; +} + +/* +======================== +idPlayerProfile::idPlayerProfile +======================== +*/ +idPlayerProfile::idPlayerProfile() { + SetDefaults(); + + // Don't have these in SetDefaults because they're used for state management and SetDefaults is called when + // loading the profile + state = IDLE; + requestedState = IDLE; + deviceNum = -1; + dirty = false; +} + +/* +======================== +idPlayerProfile::SetDefaults +======================== +*/ +void idPlayerProfile::SetDefaults() { + achievementBits = 0; + achievementBits2 = 0; + dlcReleaseVersion = 0; + + stats.SetNum( MAX_PLAYER_PROFILE_STATS ); + for ( int i = 0; i < MAX_PLAYER_PROFILE_STATS; ++i ) { + stats[i].i = 0; + } + + leftyFlip = false; + customConfig = false; + configSet = 0; +} + +/* +======================== +idPlayerProfile::~idPlayerProfile +======================== +*/ +idPlayerProfile::~idPlayerProfile() { +} + +/* +======================== +idPlayerProfile::Serialize +======================== +*/ +bool idPlayerProfile::Serialize( idSerializer & ser ) { + // NOTE: + // See comments at top of file on versioning rules + + // Default to current tag/version + int32 magicNumber = 0; + magicNumber += PROFILE_TAG << 16; + magicNumber += PROFILE_VER_MAJOR << 8; + magicNumber += PROFILE_VER_MINOR; + + // Serialize version + ser.SerializePacked( magicNumber ); + int16 tag = ( magicNumber >> 16 ) & 0xffff; + int8 majorVersion = ( magicNumber >> 8 ) & 0xff; + int8 minorVersion = magicNumber & 0xff; minorVersion; + + if ( tag != PROFILE_TAG ) { + return false; + } + + if ( majorVersion != PROFILE_VER_MAJOR ) { + return false; + } + + // Archived cvars (all the menu settings for Doom3 are archived cvars) + idDict cvarDict; + cvarSystem->MoveCVarsToDict( CVAR_ARCHIVE, cvarDict ); + cvarDict.Serialize( ser ); + if ( ser.IsReading() ) { + // Never sync these cvars with Steam because they require an engine or video restart + cvarDict.Delete( "r_fullscreen" ); + cvarDict.Delete( "r_vidMode" ); + cvarDict.Delete( "r_multisamples" ); + cvarDict.Delete( "com_engineHz" ); + cvarSystem->SetCVarsFromDict( cvarDict ); + common->StartupVariable( NULL ); + } + + // The dlcReleaseVersion is used to determine that new content is available + ser.SerializePacked( dlcReleaseVersion ); + + // New setting to save to make sure that we have or haven't seen this achievement before used to pass TRC R149d + ser.Serialize( achievementBits ); + ser.Serialize( achievementBits2 ); + + // Check to map sure we are on a valid map before we save, this helps prevent someone from creating a test map and + // gaining a bunch of achievements from it + int numStats = stats.Num(); + ser.SerializePacked( numStats ); + stats.SetNum( numStats ); + for ( int i = 0; i < numStats; ++i ) { + ser.SerializePacked( stats[i].i ); + } + + ser.Serialize( leftyFlip ); + ser.Serialize( configSet ); + + if ( ser.IsReading() ) { + // Which binding is used on the console? + ser.Serialize( customConfig ); + + ExecConfig( false ); + + if ( customConfig ) { + for ( int i = 0; i < K_LAST_KEY; ++i ) { + idStr bind; + ser.SerializeString( bind ); + idKeyInput::SetBinding( i, bind.c_str() ); + } + } + } else { + + if ( !customConfig ) { + ExecConfig( false ); + } + + customConfig = true; + ser.Serialize( customConfig ); + + for ( int i = 0; i < K_LAST_KEY; ++i ) { + idStr bind = idKeyInput::GetBinding( i ); + ser.SerializeString( bind ); + } + } + + return true; +} + +/* +======================== +idPlayerProfile::StatSetInt +======================== +*/ +void idPlayerProfile::StatSetInt( int s, int v ) { + stats[s].i = v; + MarkDirty( true ); +} + +/* +======================== +idPlayerProfile::StatSetFloat +======================== +*/ +void idPlayerProfile::StatSetFloat( int s, float v ) { + stats[s].f = v; + MarkDirty( true ); +} + +/* +======================== +idPlayerProfile::StatGetInt +======================== +*/ +int idPlayerProfile::StatGetInt( int s ) const { + return stats[s].i; +} + +/* +======================== +idPlayerProfile::StatGetFloat +======================== +*/ +float idPlayerProfile::StatGetFloat( int s ) const { + return stats[s].f; +} + +/* +======================== +idPlayerProfile::SaveSettings +======================== +*/ +void idPlayerProfile::SaveSettings( bool forceDirty ) { + if ( state != SAVING ) { + if ( forceDirty ) { + MarkDirty( true ); + } + if ( GetRequestedState() == IDLE && IsDirty() ) { + SetRequestedState( SAVE_REQUESTED ); + } + } +} + +/* +======================== +idPlayerProfile::SaveSettings +======================== +*/ +void idPlayerProfile::LoadSettings() { + if ( state != LOADING ) { + if ( verify( GetRequestedState() == IDLE ) ) { + SetRequestedState( LOAD_REQUESTED ); + } + } +} + +/* +======================== +idPlayerProfile::SetAchievement +======================== +*/ +void idPlayerProfile::SetAchievement( const int id ) { + if ( id >= idAchievementSystem::MAX_ACHIEVEMENTS ) { + assert( false ); // FIXME: add another set of achievement bit flags + return; + } + + uint64 mask = 0; + if ( id < 64 ) { + mask = achievementBits; + achievementBits |= (int64)1 << id; + mask = ~mask & achievementBits; + } else { + mask = achievementBits2; + achievementBits2 |= (int64)1 << ( id - 64 ); + mask = ~mask & achievementBits2; + } + + // Mark the profile dirty if achievement bits changed + if ( mask != 0 ) { + MarkDirty( true ); + } +} + +/* +======================== +idPlayerProfile::ClearAchievement +======================== +*/ +void idPlayerProfile::ClearAchievement( const int id ) { + if ( id >= idAchievementSystem::MAX_ACHIEVEMENTS ) { + assert( false ); // FIXME: add another set of achievement bit flags + return; + } + + if ( id < 64 ) { + achievementBits &= ~( (int64)1 << id ); + } else { + achievementBits2 &= ~( (int64)1 << ( id - 64 ) ); + } + + MarkDirty( true ); +} + +/* +======================== +idPlayerProfile::GetAchievement +======================== +*/ +bool idPlayerProfile::GetAchievement( const int id ) const { + if ( id >= idAchievementSystem::MAX_ACHIEVEMENTS ) { + assert( false ); // FIXME: add another set of achievement bit flags + return false; + } + + if ( id < 64 ) { + return ( achievementBits & (int64)1 << id ) != 0; + } else { + return ( achievementBits2 & (int64)1 << ( id - 64 ) ) != 0; + } +} + +/* +======================== +idPlayerProfile::SetConfig +======================== +*/ +void idPlayerProfile::SetConfig( int config, bool save ) { + configSet = config; + ExecConfig( save ); +} + +/* +======================== +idPlayerProfile::SetConfig +======================== +*/ +void idPlayerProfile::RestoreDefault() { + ExecConfig( true, true ); +} + +/* +======================== +idPlayerProfile::SetLeftyFlip +======================== +*/ +void idPlayerProfile::SetLeftyFlip( bool lf ) { + leftyFlip = lf; + ExecConfig( true ); +} + +/* +======================== +idPlayerProfile::ExecConfig +======================== +*/ +void idPlayerProfile::ExecConfig( bool save, bool forceDefault ) { + + int flags = 0; + if ( !save ) { + flags = cvarSystem->GetModifiedFlags(); + } + + if ( !customConfig || forceDefault ) { + cmdSystem->AppendCommandText( "exec default.cfg\n" ); + cmdSystem->AppendCommandText( "exec joy_360_0.cfg\n" ); + } + + if ( leftyFlip ) { + cmdSystem->AppendCommandText( "exec joy_lefty.cfg" ); + } else { + cmdSystem->AppendCommandText( "exec joy_righty.cfg" ); + } + + cmdSystem->ExecuteCommandBuffer(); + + if ( !save ) { + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + cvarSystem->SetModifiedFlags( flags ); + } +} + +CONSOLE_COMMAND( setProfileDefaults, "sets profile settings to default and saves", 0 ) { + if ( session->GetSignInManager().GetMasterLocalUser() == NULL ) { + return; + } + idPlayerProfile * profile = session->GetSignInManager().GetMasterLocalUser()->GetProfile(); + if ( verify( profile != NULL ) ) { + profile->SetDefaults(); + profile->SaveSettings( true ); + } +} diff --git a/neo/framework/PlayerProfile.h b/neo/framework/PlayerProfile.h new file mode 100644 index 00000000..325b2b93 --- /dev/null +++ b/neo/framework/PlayerProfile.h @@ -0,0 +1,135 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PLAYERPROFILE_H__ +#define __PLAYERPROFILE_H__ + +#define MAX_PROFILE_SIZE ( 1024 * 1000 ) // High number for the key bindings + +/* +================================================ +profileStatValue_t +================================================ +*/ +union profileStatValue_t { + int i; + float f; +}; + +/* +================================================ +idPlayerProfile + +The general rule for using cvars for settings is that if you want the player's profile settings to affect the startup +of the game before there is a player associated with the game, use cvars. Example: video & volume settings. +================================================ +*/ +class idPlayerProfile { + friend class idLocalUser; + friend class idProfileMgr; + +public: + // Only have room to squeeze ~450 in doom3 right now + static const int MAX_PLAYER_PROFILE_STATS = 200; + + enum state_t { + IDLE = 0, + SAVING, + LOADING, + SAVE_REQUESTED, + LOAD_REQUESTED, + ERR + }; +protected: + idPlayerProfile(); // don't instantiate. we static_cast the child all over the place +public: + + virtual ~idPlayerProfile(); + + static idPlayerProfile * CreatePlayerProfile( int deviceIndex ); + + void SetDefaults(); + bool Serialize( idSerializer & ser ); + + const int GetDeviceNumForProfile() const { return deviceNum; } + void SetDeviceNumForProfile( int num ) { deviceNum = num; } + void SaveSettings( bool forceDirty ); + void LoadSettings(); + state_t GetState() const { return state; } + state_t GetRequestedState() const { return requestedState; } + bool IsDirty() { return dirty; } + + bool GetAchievement( const int id ) const; + void SetAchievement( const int id ); + void ClearAchievement( const int id ); + + int GetDlcReleaseVersion() const { return dlcReleaseVersion; } + void SetDlcReleaseVersion( int version ) { dlcReleaseVersion = version; } + + int GetLevel() const { return 0; } + + //------------------------ + // Config + //------------------------ + int GetConfig() const { return configSet; } + void SetConfig( int config, bool save ); + void RestoreDefault(); + + void SetLeftyFlip( bool lf ); + bool GetLeftyFlip() const { return leftyFlip; } + +private: + void StatSetInt( int s, int v ); + void StatSetFloat( int s, float v ); + int StatGetInt( int s ) const; + float StatGetFloat( int s ) const; + void SetState( state_t value ) { state = value; } + void SetRequestedState( state_t value ) { requestedState = value; } + void MarkDirty( bool isDirty ) { dirty = isDirty; } + + void ExecConfig( bool save = false, bool forceDefault = false ); + +protected: + // Do not save: + state_t state; + state_t requestedState; + int deviceNum; + + // Save: + uint64 achievementBits; + uint64 achievementBits2; + int dlcReleaseVersion; + int configSet; + bool customConfig; + bool leftyFlip; + + bool dirty; // dirty bit to indicate whether or not we need to save + + idStaticList< profileStatValue_t, MAX_PLAYER_PROFILE_STATS > stats; +}; + +#endif diff --git a/neo/framework/Serializer.h b/neo/framework/Serializer.h new file mode 100644 index 00000000..4402ca69 --- /dev/null +++ b/neo/framework/Serializer.h @@ -0,0 +1,549 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SERIALIZER_H__ +#define __SERIALIZER_H__ + +#define SERIALIZE_BOOL( ser, x ) ( ( x ) = ser.SerializeBoolNonRef( x ) ) +#define SERIALIZE_ENUM( ser, x, type, max ) ( ( x ) = (type)ser.SerializeUMaxNonRef( x, max ) ) +#define SERIALIZE_CVAR_FLOAT( ser, cvar ) { float a = cvar.GetFloat(); ser.Serialize( a ); cvar.SetFloat( a ); } +#define SERIALIZE_CVAR_INT( ser, cvar ) { int a = cvar.GetInteger(); ser.Serialize( a ); cvar.SetInteger( a ); } +#define SERIALIZE_CVAR_BOOL( ser, cvar ) { bool a = cvar.GetBool(); SERIALIZE_BOOL( ser, a ); cvar.SetBool( a ); } + +#define SERIALIZE_MATX( ser, var ) \ +{ \ + int rows = var.GetNumRows(); \ + int cols = var.GetNumColumns(); \ + ser.Serialize( rows ); \ + ser.Serialize( cols ); \ + if ( ser.IsReading() ) { \ + var.SetSize( rows, cols ); \ + } \ + for ( int y = 0; y < rows; y++ ) { \ + for ( int x = 0; x < rows; x++ ) { \ + ser.Serialize( var[x][y] ); \ + } \ + } \ +} \ + +#define SERIALIZE_VECX( ser, var ) \ +{ \ + int size = var.GetSize(); \ + ser.Serialize( size ); \ + if ( ser.IsReading() ) { \ + var.SetSize( size ); \ + } \ + for ( int x = 0; x < size; x++ ) { \ + ser.Serialize( var[x] ); \ + } \ +} \ + +#define SERIALIZE_JOINT( ser, var ) \ +{ \ + uint16 jointIndex = ( var == NULL_JOINT_INDEX ) ? 65535 : var; \ + ser.Serialize( jointIndex ); \ + var = ( jointIndex == 65535 ) ? NULL_JOINT_INDEX : (jointIndex_t)jointIndex; \ +} \ + +//#define ENABLE_SERIALIZE_CHECKPOINTS +//#define SERIALIZE_SANITYCHECK +//#define SERIALIZE_NO_QUANT + +#define SERIALIZE_CHECKPOINT( ser ) \ + ser.SerializeCheckpoint( __FILE__, __LINE__ ); + +/* +======================== +idSerializer +======================== +*/ +class idSerializer { +public: + idSerializer( idBitMsg & msg_, bool writing_) : msg( &msg_ ), writing( writing_ ) +#ifdef SERIALIZE_SANITYCHECK + ,magic( 0 ) +#endif + { } + + bool IsReading() { return !writing; } + bool IsWriting() { return writing; } + + // SerializeRange - minSize through maxSize inclusive of all possible values + void SerializeRange( int & value, int minSize, int maxSize ) { // Supports signed types + SanityCheck(); + if ( writing ) { + msg->WriteBits( value - minSize, idMath::BitsForInteger( maxSize-minSize ) ); + } else { + value = minSize + msg->ReadBits( idMath::BitsForInteger( maxSize-minSize ) ); + } + assert( value >= minSize && value <= maxSize ); + } + + // SerializeUMax - maxSize inclusive, unsigned + void SerializeUMax( int & value, int maxSize ) { // Unsigned only + SanityCheck(); + if ( writing ) { + msg->WriteBits( value, idMath::BitsForInteger( maxSize ) ); + } else { + value = msg->ReadBits( idMath::BitsForInteger( maxSize ) ); + } + assert( value <= maxSize ); + } + + // SerializeUMaxNonRef - maxSize inclusive, unsigned, no reference + int SerializeUMaxNonRef( int value, int maxSize ) { // Unsigned only + SanityCheck(); + if ( writing ) { + msg->WriteBits(value, idMath::BitsForInteger( maxSize ) ); + } else { + value = msg->ReadBits( idMath::BitsForInteger( maxSize ) ); + } + assert( value <= maxSize ); + return value; + } + + //void SerializeBitMsg( idBitMsg & inOutMsg, int numBytes ) { SanityCheck(); if ( writing ) { msg->WriteBitMsg( inOutMsg, numBytes ); } else { msg->ReadBitMsg( inOutMsg, numBytes ); } } + + // this is still needed to compile Rage code + void SerializeBytes( void * bytes, int numBytes ) { SanityCheck(); for ( int i = 0 ; i < numBytes ; i++ ) { Serialize( ((uint8 *)bytes)[i] ); } }; + + bool SerializeBoolNonRef( bool value ) { SanityCheck(); if ( writing ) { msg->WriteBool(value); } else { value = msg->ReadBool(); } return value; } // We return a value so we can support bit fields (can't pass by reference) + + +#ifdef SERIALIZE_NO_QUANT + template< int _max_, int _numBits_ > + void SerializeQ( idVec3 & value ) { Serialize( value ); } + template< int _max_, int _numBits_ > + void SerializeQ( float & value ) { Serialize( value ); } + template< int _max_, int _numBits_ > + void SerializeUQ( float & value ) { Serialize( value ); } + void SerializeQ( idMat3 & axis, int bits = 15 ) { Serialize( axis ); } +#else + // SerializeQ - Quantizes a float to a variable number of bits (assumes signed, uses simple quantization) + template< int _max_, int _numBits_ > + void SerializeQ( idVec3 & value ) { SanityCheck(); if ( writing ) { msg->WriteQuantizedVector< idVec3, _max_, _numBits_ >( value ); } else { msg->ReadQuantizedVector< idVec3, _max_, _numBits_ >( value ); } } + template< int _max_, int _numBits_ > + void SerializeQ( float & value ) { SanityCheck(); if ( writing ) { msg->WriteQuantizedFloat< _max_, _numBits_ >( value ); } else { value = msg->ReadQuantizedFloat< _max_, _numBits_ >(); } } + template< int _max_, int _numBits_ > + void SerializeUQ( float & value ) { SanityCheck(); if ( writing ) { msg->WriteQuantizedUFloat< _max_, _numBits_ >( value ); } else { value = msg->ReadQuantizedUFloat< _max_, _numBits_ >(); } } + void SerializeQ( idMat3 & axis, int bits = 15 ); // Default to 15 bits per component, which has almost unnoticeable quantization +#endif + + void Serialize( idMat3 & axis); // Raw 3x3 matrix serialize + void SerializeC( idMat3 & axis); // Uses compressed quaternion + + template< typename _type_ > + void SerializeListElement( const idList<_type_* > & list, const _type_ *&element ); + + void SerializePacked(int & original); + void SerializeSPacked(int & original); + + void SerializeString( char * s, int bufferSize ) { SanityCheck(); if ( writing ) { msg->WriteString(s); } else { msg->ReadString( s, bufferSize ); } } + //void SerializeString( idAtomicString & s ) { SanityCheck(); if ( writing ) { msg->WriteString(s); } else { idStr temp; msg->ReadString( temp ); s.Set( temp ); } } + void SerializeString( idStr & s ) { SanityCheck(); if ( writing ) { msg->WriteString(s); } else { msg->ReadString( s ); } } + //void SerializeString( idStrId & s ) { SanityCheck(); if ( writing ) { msg->WriteString(s.GetKey()); } else { idStr key; msg->ReadString( key ); s.Set( key );} } + + void SerializeDelta( int32 & value, const int32 & base ) { SanityCheck(); if ( writing ) { msg->WriteDeltaLong( base, value ); } else { value = msg->ReadDeltaLong( base ); } } + void SerializeDelta( int16 & value, const int16 & base ) { SanityCheck(); if ( writing ) { msg->WriteDeltaShort( base, value ); } else { value = msg->ReadDeltaShort( base ); } } + void SerializeDelta( int8 & value, const int8 & base ) { SanityCheck(); if ( writing ) { msg->WriteDeltaChar( base, value ); } else { value = msg->ReadDeltaChar( base ); } } + + void SerializeDelta( uint16 & value, const uint16 & base ) { SanityCheck(); if ( writing ) { msg->WriteDeltaUShort( base, value ); } else { value = msg->ReadDeltaUShort( base ); } } + void SerializeDelta( uint8 & value, const uint8 & base ) { SanityCheck(); if ( writing ) { msg->WriteDeltaByte( base, value ); } else { value = msg->ReadDeltaByte( base ); } } + + void SerializeDelta( float & value, const float & base ) { SanityCheck(); if ( writing ) { msg->WriteDeltaFloat( base, value ); } else { value = msg->ReadDeltaFloat( base ); } } + + + // Common types, no compression + void Serialize( int64 & value ) { SanityCheck(); if ( writing ) { msg->WriteLongLong(value); } else { value = msg->ReadLongLong(); } } + void Serialize( uint64 & value ) { SanityCheck(); if ( writing ) { msg->WriteLongLong(value); } else { value = msg->ReadLongLong(); } } + void Serialize( int32 & value ) { SanityCheck(); if ( writing ) { msg->WriteLong(value); } else { value = msg->ReadLong(); } } + void Serialize( uint32 & value ) { SanityCheck(); if ( writing ) { msg->WriteLong(value); } else { value = msg->ReadLong(); } } + void Serialize( int16 & value ) { SanityCheck(); if ( writing ) { msg->WriteShort(value); } else { value = msg->ReadShort(); } } + void Serialize( uint16 & value ) { SanityCheck(); if ( writing ) { msg->WriteUShort(value); } else { value = msg->ReadUShort(); } } + void Serialize( uint8 & value ) { SanityCheck(); if ( writing ) { msg->WriteByte(value); } else { value = msg->ReadByte(); } } + void Serialize( int8 & value ) { SanityCheck(); if ( writing ) { msg->WriteChar(value); } else { value = msg->ReadChar(); } } + void Serialize( bool & value ) { SanityCheck(); if ( writing ) { msg->WriteByte(value?1:0); } else { value = msg->ReadByte() != 0; } } + void Serialize( float & value ) { SanityCheck(); if ( writing ) { msg->WriteFloat(value); } else { value = msg->ReadFloat(); } } + void Serialize( idRandom2 & value ) { SanityCheck(); if ( writing ) { msg->WriteLong(value.GetSeed()); } else { value.SetSeed( msg->ReadLong() ); } } + void Serialize( idVec3 & value ) { SanityCheck(); if ( writing ) { msg->WriteVectorFloat(value); } else { msg->ReadVectorFloat(value); } } + void Serialize( idVec2 & value ) { SanityCheck(); if ( writing ) { msg->WriteVectorFloat(value); } else { msg->ReadVectorFloat(value); } } + void Serialize( idVec6 & value ) { SanityCheck(); if ( writing ) { msg->WriteVectorFloat(value); } else { msg->ReadVectorFloat(value); } } + void Serialize( idVec4 & value ) { SanityCheck(); if ( writing ) { msg->WriteVectorFloat(value); } else { msg->ReadVectorFloat(value); } } + + // serialize an angle, normalized to between 0 to 360 and quantized to 16 bits + void SerializeAngle( float & value ) { + SanityCheck(); + if ( writing ) { + float nAngle = idMath::AngleNormalize360( value ); + assert( nAngle >= 0.0f ); // should never get a negative angle + uint16 sAngle = nAngle * ( 65536.0f / 360.0f ); + msg->WriteUShort( sAngle ); + } else { + uint16 sAngle = msg->ReadUShort(); + value = sAngle * ( 360.0f / 65536.0f ); + } + + } + + //void Serialize( degrees_t & value ) { + // SanityCheck(); + // float angle = value.Get(); + // Serialize( angle ); + // value.Set( angle ); + // } + //void SerializeAngle( degrees_t & value ) { + // SanityCheck(); + // float angle = value.Get(); + // SerializeAngle( angle ); + // value.Set( angle ); + // } + //void Serialize( radians_t & value ) { + // SanityCheck(); + // // convert to degrees + // degrees_t d( value.Get() * idMath::M_RAD2DEG ); + // Serialize( d ); + // if ( !writing ) { + // // if reading, get the value we read in degrees and convert back to radians + // value.Set( d.Get() * idMath::M_DEG2RAD ); + // } + // } + //void SerializeAngle( radians_t & value ) { + // SanityCheck(); + // // convert to degrees + // degrees_t d( value.Get() * idMath::M_RAD2DEG ); + // // serialize as normalized degrees between 0 - 360 + // SerializeAngle( d ); + // if ( !writing ) { + // // if reading, get the value we read in degrees and convert back to radians + // value.Set( d.Get() * idMath::M_DEG2RAD ); + // } + // } + // + //void Serialize( idColor & value ) { + // Serialize( value.r ); + // Serialize( value.g ); + // Serialize( value.b ); + // Serialize( value.a ); + //} + + void SanityCheck() { +#ifdef SERIALIZE_SANITYCHECK + if ( writing ) { + msg->WriteUShort( 0xCCCC ); + msg->WriteUShort( magic ); + } else { + int cccc = msg->ReadUShort(); + int m = msg->ReadUShort(); + assert( cccc == 0xCCCC ); + assert( m == magic ); + // For release builds + if ( cccc != 0xCCCC ) { + idLib::Error( "idSerializer::SanityCheck - cccc != 0xCCCC" ); + } + if ( m != magic ) { + idLib::Error( "idSerializer::SanityCheck - m != magic" ); + } + } + magic++; +#endif + } + + void SerializeCheckpoint( const char * file, int line ) { +#ifdef ENABLE_SERIALIZE_CHECKPOINTS + const uint32 tagValue = 0xABADF00D; + uint32 tag = tagValue; + Serialize( tag ); + if ( tag != tagValue ) { + idLib::Error( "SERIALIZE_CHECKPOINT: tag != tagValue (file: %s - line: %i)", file, line ); + } +#endif + } + + idBitMsg & GetMsg() { return *msg; } + +private: + bool writing; + idBitMsg * msg; +#ifdef SERIALIZE_SANITYCHECK + int magic; +#endif +}; + +class idSerializerScopedBlock { +public: + idSerializerScopedBlock( idSerializer &ser_, int maxSizeBytes_ ) { + ser = &ser_; + maxSizeBytes = maxSizeBytes_; + + startByte = ser->IsReading() ? ser->GetMsg().GetReadCount() : ser->GetMsg().GetSize(); + startWriteBits = ser->GetMsg().GetWriteBit(); + } + + ~idSerializerScopedBlock() { + + // Serialize remaining bits + while ( ser->GetMsg().GetWriteBit() != startWriteBits ) { + ser->SerializeBoolNonRef( false ); + } + + // Verify we didn't go over + int endByte = ser->IsReading() ? ser->GetMsg().GetReadCount() : ser->GetMsg().GetSize(); + int sizeBytes = endByte - startByte; + if ( !verify( sizeBytes <= maxSizeBytes ) ) { + idLib::Warning( "idSerializerScopedBlock went over maxSize (%d > %d)", sizeBytes, maxSizeBytes ); + return; + } + + // Serialize remaining bytes + uint8 b=0; + while ( sizeBytes < maxSizeBytes ) { + ser->Serialize( b ); + sizeBytes++; + } + + int finalSize = ( ( ser->IsReading() ? ser->GetMsg().GetReadCount() : ser->GetMsg().GetSize() ) - startByte ); + verify( maxSizeBytes == finalSize ); + } + +private: + idSerializer * ser; + int maxSizeBytes; + + int startByte; + int startWriteBits; +}; + + + + +/* +======================== +idSerializer::SerializeQ +======================== +*/ +#ifndef SERIALIZE_NO_QUANT +ID_INLINE void idSerializer::SerializeQ( idMat3 &axis, int bits ) { + SanityCheck(); + + const float scale = ( ( 1 << ( bits - 1 ) ) - 1 ); + if ( IsWriting() ) { + idQuat quat = axis.ToQuat(); + + int maxIndex = 0; + for ( unsigned int i = 1; i < 4; i++ ) { + if ( idMath::Fabs( quat[i] ) > idMath::Fabs( quat[maxIndex] ) ) { + maxIndex = i; + } + } + + msg->WriteBits( maxIndex, 2 ); + + idVec3 out; + + if ( quat[maxIndex] < 0.0f ) { + out.x = -quat[( maxIndex + 1 ) & 3]; + out.y = -quat[( maxIndex + 2 ) & 3]; + out.z = -quat[( maxIndex + 3 ) & 3]; + } else { + out.x = quat[( maxIndex + 1 ) & 3]; + out.y = quat[( maxIndex + 2 ) & 3]; + out.z = quat[( maxIndex + 3 ) & 3]; + } + msg->WriteBits( idMath::Ftoi( out.x * scale ), -bits); + msg->WriteBits( idMath::Ftoi( out.y * scale ), -bits); + msg->WriteBits( idMath::Ftoi( out.z * scale ), -bits); + + } else if ( IsReading() ) { + idQuat quat; + idVec3 in; + + int maxIndex = msg->ReadBits(2); + + in.x = (float)msg->ReadBits(-bits) / scale; + in.y = (float)msg->ReadBits(-bits) / scale; + in.z = (float)msg->ReadBits(-bits) / scale; + + quat[( maxIndex + 1 ) & 3] = in.x; + quat[( maxIndex + 2 ) & 3] = in.y; + quat[( maxIndex + 3 ) & 3] = in.z; + + quat[maxIndex] = idMath::Sqrt( idMath::Fabs( 1.0f - in.x * in.x - in.y * in.y - in.z * in.z ) ); + + axis = quat.ToMat3(); + } +} +#endif + +/* +======================== +idSerializer::Serialize +======================== +*/ +ID_INLINE void idSerializer::Serialize( idMat3 & axis ) { + SanityCheck(); + + Serialize( axis[0] ); + Serialize( axis[1] ); + Serialize( axis[2] ); +} + +/* +======================== +idSerializer::SerializeC +======================== +*/ +ID_INLINE void idSerializer::SerializeC( idMat3 & axis ) { + SanityCheck(); + + if ( IsWriting() ) { + idCQuat cquat = axis.ToCQuat(); + + Serialize( cquat.x ); + Serialize( cquat.y ); + Serialize( cquat.z ); + } else if ( IsReading() ) { + idCQuat cquat; + + Serialize( cquat.x ); + Serialize( cquat.y ); + Serialize( cquat.z ); + + axis = cquat.ToMat3(); + } +} + +/* +======================== +idSerializer::SerializeListElement +======================== +*/ +template< typename _type_ > +ID_INLINE void idSerializer::SerializeListElement( const idList<_type_* > & list, const _type_ *&element ) { + SanityCheck(); + + if ( IsWriting() ) { + int index = list.FindIndex( const_cast<_type_ *>(element) ); + assert( index >= 0 ); + SerializePacked( index ); + } else if ( IsReading() ) { + int index = 0; + SerializePacked( index ); + element = list[index]; + } +} + +/* +======================== +idSerializer::SerializePacked +Writes out 7 bits at a time, using every 8th bit to signify more bits exist + +NOTE - Signed values work with this function, but take up more bytes +Use SerializeSPacked if you anticipate lots of negative values +======================== +*/ +ID_INLINE void idSerializer::SerializePacked(int & original) { + SanityCheck(); + + if ( IsWriting() ) { + uint32 value = original; + + while ( true ) { + uint8 byte = value & 0x7F; + value >>= 7; + byte |= value ? 0x80 : 0; + msg->WriteByte( byte ); // Emit byte + if ( value == 0 ) { + break; + } + } + } else { + uint8 byte = 0x80; + uint32 value = 0; + int32 shift = 0; + + while ( byte & 0x80 && shift < 32 ) { + byte = msg->ReadByte(); + value |= (byte & 0x7F) << shift; + shift += 7; + } + + original = value; + } +} + +/* +======================== +idSerializer::SerializeSPacked +Writes out 7 bits at a time, using every 8th bit to signify more bits exist + +NOTE - An extra bit of the first byte is used to store the sign +(this function supports negative values, but will use 2 bytes for values greater than 63) +======================== +*/ +ID_INLINE void idSerializer::SerializeSPacked(int & value) { + SanityCheck(); + + if ( IsWriting() ) { + + uint32 uvalue = idMath::Abs( value ); + + // Write the first byte specifically to handle the sign bit + uint8 byte = uvalue & 0x3f; + byte |= value < 0 ? 0x40 : 0; + uvalue >>= 6; + byte |= uvalue > 0 ? 0x80 : 0; + + msg->WriteByte( byte ); + + while ( uvalue > 0 ) { + uint8 byte2 = uvalue & 0x7F; + uvalue >>= 7; + byte2 |= uvalue ? 0x80 : 0; + msg->WriteByte( byte2 ); // Emit byte + } + } else { + // Load the first byte specifically to handle the sign bit + uint8 byte = msg->ReadByte(); + uint32 uvalue = byte & 0x3f; + bool sgn = (byte & 0x40) ? true : false; + int32 shift = 6; + + while ( byte & 0x80 && shift < 32 ) { + byte = msg->ReadByte(); // Read byte + uvalue |= (byte & 0x7F) << shift; + shift += 7; + } + + value = sgn ? -((int)uvalue) : uvalue; + } +} + +#endif + + + + diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp new file mode 100644 index 00000000..274a50e3 --- /dev/null +++ b/neo/framework/Session.cpp @@ -0,0 +1,1256 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Session_local.h" +#include "../sys/sys_session_savegames.h" +#include "../sys/sys_voicechat.h" + + +/* +======================== +SteamAPIDebugTextHook +======================== +*/ +extern "C" void __cdecl SteamAPIDebugTextHook( int nSeverity, const char * pchDebugText ) { + // if you're running in the debugger, only warnings (nSeverity >= 1) will be sent + // if you add -debug_steamapi to the command-line, a lot of extra informational messages will also be sent + idLib::Printf( "%s", pchDebugText ); + + if ( nSeverity >= 1 ) { + // place to set a breakpoint for catching API errors + int x = 3; + x; + } +} + +/* +=============================================================================== + +SESSION LOCAL + +=============================================================================== +*/ + +/* +=============== +idSessionLocal::idSessionLocal +=============== +*/ +idSessionLocal::idSessionLocal() { + + localState = STATE_PRESS_START; + + InitBaseState(); +} + +/* +=============== +idSessionLocal::~idSessionLocal +=============== +*/ +idSessionLocal::~idSessionLocal() { + if ( sessionCallbacks != NULL ) { + delete sessionCallbacks; + } +} + + + +/* +======================== +idSessionLocal::InitBaseState +======================== +*/ +void idSessionLocal::InitBaseState() { + + + localState = STATE_PRESS_START; + sessionOptions = 0; + currentID = 0; + + sessionCallbacks = new idSessionLocalCallbacks( this ); + + connectType = CONNECT_NONE; + + isSysUIShowing = false; + + pendingInviteDevice = 0; + pendingInviteMode = PENDING_INVITE_NONE; + + + flushedStats = false; + + enumerationHandle = 0; +} + +/* +=============== +idSessionLocal::Shutdown +=============== +*/ +void idSessionLocal::Shutdown() { + + delete signInManager; + + if ( achievementSystem != NULL ) { + achievementSystem->Shutdown(); + delete achievementSystem; + } + + DestroySteamObjects(); +} + +/* +=============== +idSessionLocal::Init + +Called in an orderly fashion at system startup, +so commands, cvars, files, etc are all available +=============== +*/ +void idSessionLocal::Init() { + + common->Printf( "-------- Initializing Session --------\n" ); + + InitSteam(); + ConstructSteamObjects(); + + signInManager = new idSignInManagerWin(); + achievementSystem = new idAchievementSystemWin(); + achievementSystem->Init(); + Initialize(); + + common->Printf( "session initialized\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +======================== +idSessionLocal::InitSteam +======================== +*/ +void idSessionLocal::InitSteam() { + if ( steamInitialized || steamFailed ) { + if ( steamFailed ) { + net_usePlatformBackend.SetBool( false ); + } + return; + } + + steamInitialized = SteamAPI_Init(); + steamFailed = !steamInitialized; + + if ( steamFailed ) { + if ( net_usePlatformBackend.GetBool() ) { + idLib::Warning( "Steam failed to initialize. Usually this happens because the Steam client isn't running." ); + // Turn off the usage of steam if it fails to initialize + // FIXME: We'll want to bail (nicely) in the shipping product most likely + net_usePlatformBackend.SetBool( false ); + } + return; + } + + // from now on, all Steam API functions should return non-null interface pointers + + assert( SteamUtils() ); + SteamUtils()->SetWarningMessageHook( &SteamAPIDebugTextHook ); + + ConstructSteamObjects(); +} + +/* +======================== +idSessionLocal::ConstructSteamObjects +======================== +*/ +void idSessionLocal::ConstructSteamObjects() { +} + +/* +======================== +idSessionLocal::DestroySteamObjects +======================== +*/ +void idSessionLocal::DestroySteamObjects() { +} + +/* +======================== +idSessionLocal::MoveToPressStart +======================== +*/ +void idSessionLocal::MoveToPressStart( gameDialogMessages_t msg ) { + if ( localState != STATE_PRESS_START ) { + MoveToPressStart(); + + common->Dialog().ClearDialogs(); + common->Dialog().AddDialog( msg, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); + } +} + +/* +======================== +idSessionLocal::MoveToPressStart +======================== +*/ +void idSessionLocal::MoveToPressStart() { + if ( localState != STATE_PRESS_START ) { + GetSignInManager().RemoveAllLocalUsers(); + SetState( STATE_PRESS_START ); + } +} + +/* +======================== +idSessionLocal::SetState +======================== +*/ +void idSessionLocal::SetState( idSessionLocal::state_t newState ) { + assert( newState < NUM_STATES ); + assert( localState < NUM_STATES ); + + if ( newState == localState ) { + return; + } + + localState = newState; +} + +/* +======================== +idSessionLocal::GetState +======================== +*/ +idSessionLocal::sessionState_t idSessionLocal::GetState() const { + // Convert our internal state to one of the external states + switch ( localState ) { + case STATE_PRESS_START: return PRESS_START; + case STATE_IDLE: return IDLE; + case STATE_PARTY_LOBBY_HOST: return PARTY_LOBBY; + case STATE_PARTY_LOBBY_PEER: return PARTY_LOBBY; + case STATE_GAME_LOBBY_HOST: return GAME_LOBBY; + case STATE_GAME_LOBBY_PEER: return GAME_LOBBY; + //case STATE_GAME_STATE_LOBBY_HOST: return GAME_LOBBY; + //case STATE_GAME_STATE_LOBBY_PEER: return GAME_LOBBY; + case STATE_LOADING: return LOADING; + case STATE_INGAME: return INGAME; + case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return CONNECTING; + case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return CONNECTING; + //case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return CONNECTING; + case STATE_FIND_OR_CREATE_MATCH: return SEARCHING; + case STATE_CONNECT_AND_MOVE_TO_PARTY: return CONNECTING; + case STATE_CONNECT_AND_MOVE_TO_GAME: return CONNECTING; + //case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return CONNECTING; + case STATE_BUSY: return BUSY; + default: { + idLib::Error( "GetState: Unknown state in idSessionLocal" ); + return IDLE; + } + }; +} + +/* +======================== +idSessionLocal::Pump +======================== +*/ +void idSessionLocal::Pump() { + + GetSignInManager().Pump(); + + idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser(); + + if ( masterUser != NULL && localState == STATE_PRESS_START ) { + // If we have a master user, and we are at press start, move to the menu area + SetState( STATE_IDLE ); + + + } + + GetAchievementSystem().Pump(); +} + +/* +======================== +idSessionLocal::OnMasterLocalUserSignin +======================== +*/ +void idSessionLocal::OnMasterLocalUserSignin() { + enumerationHandle = EnumerateSaveGames( 0 ); +} + +/* +======================== +idSessionLocal::LoadGame +======================== +*/ +saveGameHandle_t idSessionLocal::LoadGame( const char * name, const idList< idSaveFileEntry > & files ) { + if ( processorLoadFiles.InitLoadFiles( name, files ) ) { + return saveGameManager.ExecuteProcessor( &processorLoadFiles ); + } else { + return 0; + } +} + +/* +======================== +idSessionLocal::SaveGame +======================== +*/ +saveGameHandle_t idSessionLocal::SaveGame( const char * name, const idList< idSaveFileEntry > & files, const idSaveGameDetails & description, uint64 skipErrorMask ) { + saveGameHandle_t ret = 0; + + // serialize the description file behind their back... + idList< idSaveFileEntry > filesWithDetails( files ); + idFile_Memory * gameDetailsFile = new idFile_Memory( SAVEGAME_DETAILS_FILENAME ); + //gameDetailsFile->MakeWritable(); + description.descriptors.WriteToIniFile( gameDetailsFile ); + filesWithDetails.Append( idSaveFileEntry( gameDetailsFile, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE, SAVEGAME_DETAILS_FILENAME ) ); + + if ( processorSave.InitSave( name, filesWithDetails, description ) ) { + processorSave.SetSkipSystemErrorDialogMask( skipErrorMask ); + ret = GetSaveGameManager().ExecuteProcessor( &processorSave ); + } + return ret; +} + +/* +======================== +idSessionLocal::EnumerateSaveGames +======================== +*/ +saveGameHandle_t idSessionLocal::EnumerateSaveGames( uint64 skipErrorMask ) { + saveGameHandle_t ret = 0; + + // flush the old enumerated list + GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear(); + + if ( processorEnumerate.Init() ) { + processorEnumerate.SetSkipSystemErrorDialogMask( skipErrorMask ); + ret = GetSaveGameManager().ExecuteProcessor( &processorEnumerate ); + } + return ret; +} + +/* +======================== +idSessionLocal::DeleteSaveGame +======================== +*/ +saveGameHandle_t idSessionLocal::DeleteSaveGame( const char * name, uint64 skipErrorMask ) { + saveGameHandle_t ret = 0; + if ( processorDelete.InitDelete( name ) ) { + processorDelete.SetSkipSystemErrorDialogMask( skipErrorMask ); + ret = GetSaveGameManager().ExecuteProcessor( & + processorDelete ); + } + return ret; +} + +/* +======================== +idSessionLocal::IsEnumerating +======================== +*/ +bool idSessionLocal::IsEnumerating() const { + return !session->IsSaveGameCompletedFromHandle( processorEnumerate.GetHandle() ); +} + +/* +======================== +idSessionLocal::GetEnumerationHandle +======================== +*/ +saveGameHandle_t idSessionLocal::GetEnumerationHandle() const { + return processorEnumerate.GetHandle(); +} + +/* +======================== +idSessionLocal::CancelSaveGameWithHandle +======================== +*/ +void idSessionLocal::CancelSaveGameWithHandle( const saveGameHandle_t & handle ) { + GetSaveGameManager().CancelWithHandle( handle ); +} + + +// FIXME: Move to sys_stats.cpp +leaderboardDefinition_t * registeredLeaderboards[MAX_LEADERBOARDS]; +int numRegisteredLeaderboards = 0; + +/* +======================== +Sys_FindLeaderboardDef +======================== +*/ +const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ) { + for ( int i = 0; i < numRegisteredLeaderboards; i++ ) { + if ( registeredLeaderboards[i]->id == id ) { + return registeredLeaderboards[i]; + } + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetActiveLobby +======================== +*/ +idLobby * idSessionLocal::GetActiveLobby() { + sessionState_t state = GetState(); + + if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) { + return &GetGameLobby(); + } else if ( state == PARTY_LOBBY ) { + return &GetPartyLobby(); + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetActiveLobby +======================== +*/ +const idLobby * idSessionLocal::GetActiveLobby() const { + sessionState_t state = GetState(); + + if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) { + return &GetGameLobby(); + } else if ( state == PARTY_LOBBY ) { + return &GetPartyLobby(); + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetActiveLobbyBase +This returns the base version for the idSession version +======================== +*/ +idLobbyBase & idSessionLocal::GetActiveLobbyBase() { + idLobby * activeLobby = GetActiveLobby(); + + if ( activeLobby != NULL ) { + return *activeLobby; + } + + return stubLobby; // So we can return at least something +} + +/* +======================== +idSessionLocal::PrePickNewHost +This is called when we have determined that we need to pick a new host. +Call PickNewHostInternal to continue on with the host picking process. +======================== +*/ +void idSessionLocal::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: (%s)\n", lobby.GetLobbyName() ); + + if ( GetActiveLobby() == NULL ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetActiveLobby() == NULL (%s)\n", lobby.GetLobbyName() ); + return; + } + + // Check to see if we can migrate AT ALL + // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION) + if ( GetPartyLobby().parms.GetSessionMatchFlags() & MATCH_PARTY_INVITE_PLACEHOLDER ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MATCH_PARTY_INVITE_PLACEHOLDER (%s)\n", lobby.GetLobbyName() ); + + // Can't migrate, shut both lobbies down, and create a new match using the original parms + GetGameLobby().Shutdown(); + GetPartyLobby().Shutdown(); + + // Throw up the appropriate dialog message so the player knows what happeend + if ( localState >= STATE_LOADING ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState >= idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() ); + common->Dialog().AddDialog( GDM_BECAME_HOST_GAME_STATS_DROPPED, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); + } else { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState < idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() ); + common->Dialog().AddDialog( GDM_LOBBY_BECAME_HOST_GAME, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); + } + + CreateMatch( GetActiveLobby()->parms ); + + return; + } + + // Check to see if the match is searchable + if ( GetState() >= idSession::GAME_LOBBY && MatchTypeIsSearchable( GetGameLobby().parms.GetSessionMatchFlags() ) ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MatchTypeIsSearchable (%s)\n", lobby.GetLobbyName() ); + // Searchable games migrate lobbies independently, and don't need to stay in sync + lobby.PickNewHostInternal( forceMe, inviteOldHost ); + return; + } + + // + // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status + // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs + // + + // Check to see if we should go back to a party lobby + if ( GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() ); + // Force the party lobby to start picking a new host if we lost the game lobby host + GetPartyLobby().PickNewHostInternal( forceMe, inviteOldHost ); + + // End the game lobby, and go back to party lobby + GetGameLobby().Shutdown(); + SetState( GetPartyLobby().IsHost() ? STATE_PARTY_LOBBY_HOST : STATE_PARTY_LOBBY_PEER ); + } else { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() < idSessionLocal::PARTY_LOBBY && GetState() != idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() ); + if ( localState >= STATE_LOADING ) { + common->Dialog().AddDialog( GDM_HOST_QUIT, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); // The host has quit the session. Returning to the main menu. + } + + // Go back to main menu + GetGameLobby().Shutdown(); + GetPartyLobby().Shutdown(); + SetState( STATE_IDLE ); + } +} +/* +======================== +idSessionLocal::PreMigrateInvite +This is called just before we get invited to a migrated session +If we return false, the invite will be ignored +======================== +*/ +bool idSessionLocal::PreMigrateInvite( idLobby & lobby ) +{ + if ( GetActiveLobby() == NULL ) { + return false; + } + + // Check to see if we can migrate AT ALL + // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION) + if ( !verify( ( GetPartyLobby().parms.GetSessionMatchFlags() & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) { + return false; // Shouldn't get invites for coop (we should make this a specific option (MATCH_ALLOW_MIGRATION)) + } + + // Check to see if the match is searchable + if ( MatchTypeIsSearchable( GetGameLobby().parms.GetSessionMatchFlags() ) ) { + // Searchable games migrate lobbies independently, and don't need to stay in sync + return true; + } + + // + // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status + // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs + // + + if ( lobby.lobbyType != idLobby::TYPE_PARTY ) { + return false; // We shouldn't be getting invites from non party lobbies when in a non searchable game + } + + // Non placeholder Party lobbies can always migrate + if ( GetBackState() >= idSessionLocal::PARTY_LOBBY ) { + // Non searchable games go back to the party lobby + GetGameLobby().Shutdown(); + SetState( GetPartyLobby().IsHost() ? STATE_PARTY_LOBBY_HOST : STATE_PARTY_LOBBY_PEER ); + } + + return true; // Non placeholder Party lobby invites joinable +} + +/* +======================== +idSessionLocal::HandleDedicatedServerQueryRequest +======================== +*/ +void idSessionLocal::HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) { + NET_VERBOSE_PRINT( "HandleDedicatedServerQueryRequest from %s\n", remoteAddr.ToString() ); + + bool canJoin = true; + + const unsigned long localChecksum = 0; + const unsigned long remoteChecksum = msg.ReadLong(); + + if ( remoteChecksum != localChecksum ) { + NET_VERBOSE_PRINT( "HandleServerQueryRequest: Invalid version from %s\n", remoteAddr.ToString() ); + canJoin = false; + } + + // Make sure we are the host of this party session + if ( !GetPartyLobby().IsHost() ) { + NET_VERBOSE_PRINT( "HandleServerQueryRequest: Not host of party\n" ); + canJoin = false; + } + + // Make sure there is a session active + if ( GetActiveLobby() == NULL ) { + canJoin = false; + } + + // Make sure we have enough free slots + if ( GetPartyLobby().NumFreeSlots() == 0 || GetGameLobby().NumFreeSlots() == 0 ) { + NET_VERBOSE_PRINT( "No free slots\n" ); + canJoin = false; + } + + if ( MatchTypeInviteOnly( GetPartyLobby().parms.GetSessionMatchFlags() ) ) { + canJoin = false; + } + + // Buffer to hold reply msg + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg retmsg; + retmsg.InitWrite( buffer, sizeof( buffer ) ); + + idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser(); + + if ( masterUser == NULL ) { + canJoin = false; + } + + // Send the info about this game session to the caller + retmsg.WriteBool( canJoin ); + + if ( canJoin ) { + retmsg.WriteBool( session->GetState() >= idSession::LOADING ); + + retmsg.WriteString( masterUser->GetGamerTag() ); + retmsg.WriteLong( GetActiveLobby()->parms.GetGameType() ); // We need to write out the game type whether we are in a game or not + + if ( GetGameLobby().IsSessionActive() ) { + retmsg.WriteLong( GetGameLobby().parms.GetGameMap() ); + retmsg.WriteLong( GetGameLobby().parms.GetGameMode() ); + } else { + retmsg.WriteLong( -1 ); + retmsg.WriteLong( -1 ); + } + + retmsg.WriteLong( GetActiveLobby()->GetNumLobbyUsers() ); + retmsg.WriteLong( GetActiveLobby()->parms.GetNumSlots() ); + for ( int i = 0; i < GetActiveLobby()->GetNumLobbyUsers(); i++ ) { + retmsg.WriteString( GetActiveLobby()->GetLobbyUserName( i ) ); + } + } + + // Send it + GetPartyLobby().SendConnectionLess( remoteAddr, idLobby::OOB_MATCH_QUERY_ACK, retmsg.GetWriteData(), retmsg.GetSize() ); +} + +/* +======================== +idSessionLocal::HandleDedicatedServerQueryAck +======================== +*/ +void idSessionLocal::HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) { + NET_VERBOSE_PRINT( "HandleDedicatedServerQueryAck from %s\n", remoteAddr.ToString() ); +} + +/* +======================== +idSessionLocal::StartSessions +======================== +*/ +void idSessionLocal::StartSessions() { + if ( GetPartyLobby().lobbyBackend != NULL ) { + GetPartyLobby().lobbyBackend->StartSession(); + } + + if ( GetGameLobby().lobbyBackend != NULL ) { + GetGameLobby().lobbyBackend->StartSession(); + } + + SetLobbiesAreJoinable( false ); +} + +/* +======================== +idSessionLocal::EndSessions +======================== +*/ +void idSessionLocal::EndSessions() { + if ( GetPartyLobby().lobbyBackend != NULL ) { + GetPartyLobby().lobbyBackend->EndSession(); + } + + if ( GetGameLobby().lobbyBackend != NULL ) { + GetGameLobby().lobbyBackend->EndSession(); + } + + SetLobbiesAreJoinable( true ); +} + +/* +======================== +idSessionLocal::SetLobbiesAreJoinable +======================== +*/ +void idSessionLocal::SetLobbiesAreJoinable( bool joinable ) { + // NOTE - We don't manipulate the joinable state when we are supporting join in progress + // Lobbies will naturally be non searchable when there are no free slots + if ( GetPartyLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetPartyLobby().parms.GetSessionMatchFlags() ) ) { + NET_VERBOSE_PRINT( "Party lobbyBackend SetIsJoinable: %d\n", joinable ); + GetPartyLobby().lobbyBackend->SetIsJoinable( joinable ); + } + + if ( GetGameLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetGameLobby().parms.GetSessionMatchFlags() ) ) { + GetGameLobby().lobbyBackend->SetIsJoinable( joinable ); + NET_VERBOSE_PRINT( "Game lobbyBackend SetIsJoinable: %d\n", joinable ); + + } +} + +/* +======================== +idSessionLocal::EndMatchForMigration +======================== +*/ +void idSessionLocal::EndMatchForMigration() { + ClearVoiceGroups(); +} + +/* +======================== +idSessionLocal::ClearVoiceGroups +======================== +*/ +void idSessionLocal::ClearVoiceGroups() { + /* + for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) { + SetGameSessionUserChatGroup( i, 0 ); + } + SetActiveChatGroup( 0 ); + */ +} + +/* +======================== +idSessionLocal::GoodbyeFromHost +======================== +*/ +void idSessionLocal::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) { + if ( !verify( localState > STATE_IDLE ) ) { + idLib::Printf( "NET: Got disconnected from host %s on session %s when we were not in a lobby or game.\n", remoteAddress.ToString(), lobby.GetLobbyName() ); + MoveToMainMenu(); + return; // Ignore if we are not past the main menu + } + + // Goodbye from host. See if we were connecting vs connected + if ( ( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ) && lobby.peers[peerNum].GetConnectionState() == idLobby::CONNECTION_CONNECTING ) { + // We were denied a connection attempt + idLib::Printf( "NET: Denied connection attempt from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType ); + // This will try to move to the next connection if one exists, otherwise will create a match + HandleConnectionFailed( lobby, msgType == idLobby::OOB_GOODBYE_FULL ); + } else { + // We were disconnected from a server we were previously connected to + idLib::Printf( "NET: Disconnected from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType ); + + const bool leaveGameWithParty = ( msgType == idLobby::OOB_GOODBYE_W_PARTY ); + + if ( leaveGameWithParty && lobby.lobbyType == idLobby::TYPE_GAME && lobby.IsPeer() && GetState() == idSession::GAME_LOBBY && GetPartyLobby().host >= 0 && + lobby.peers[peerNum].address.Compare( GetPartyLobby().peers[GetPartyLobby().host].address, true ) ) { + // If a host is telling us goodbye from a game lobby, and the game host is the same as our party host, + // and we aren't in a game, and the host wants us to leave with him, then do so now + GetGameLobby().Shutdown(); + SetState( STATE_PARTY_LOBBY_PEER ); + } else { + // Host left, so pick a new host (possibly even us) for this lobby + lobby.PickNewHost(); + } + } +} + +/* +======================== +idSessionLocal::HandlePackets +======================== +*/ +bool idSessionLocal::HandlePackets() { + byte packetBuffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ]; + lobbyAddress_t remoteAddress; + int recvSize = 0; + + while ( ReadRawPacket( remoteAddress, packetBuffer, recvSize, sizeof( packetBuffer ) ) && recvSize > 0 ) { + // fragMsg will hold the raw packet + idBitMsg fragMsg; + fragMsg.InitRead( packetBuffer, recvSize ); + + // Peek at the session ID + idPacketProcessor::sessionId_t sessionID = idPacketProcessor::GetSessionID( fragMsg ); + + // idLib::Printf( "NET: HandlePackets - session %d, size %d \n", sessionID, recvSize ); + + // Make sure it's valid + if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) { + idLib::Printf( "NET: Invalid sessionID %s.\n", remoteAddress.ToString() ); + continue; + } + + // Distribute the packet to the proper lobby + if ( sessionID & 1 ) { + GetGameLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); + } else { + GetPartyLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); + } + } + + return false; +} + +idCVar net_connectTimeoutInSeconds( "net_connectTimeoutInSeconds", "15", CVAR_INTEGER, "timeout (in seconds) while connecting" ); +idCVar net_testPartyMemberConnectFail( "net_testPartyMemberConnectFail", "-1", CVAR_INTEGER, "Force this party member index to fail to connect to games." ); + +/* +======================== +idSessionLocal::HandleConnectAndMoveToLobby +Called from State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game +======================== +*/ +bool idSessionLocal::HandleConnectAndMoveToLobby( idLobby & lobby ) { + assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ); + assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT ); + + if ( lobby.GetState() == idLobby::STATE_FAILED ) { + // If we get here, we were trying to connect to a lobby (from state State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game) + HandleConnectionFailed( lobby, false ); + return true; + } + + if ( lobby.GetState() != idLobby::STATE_IDLE ) { + return HandlePackets(); // Valid but busy + } + + assert( !GetPartyLobby().waitForPartyOk ); + + // + // Past this point, we've connected to the lobby + // + + // If we are connecting to a game lobby, see if we need to keep waiting as either a host or peer while we're confirming all party members made it + if ( lobby.lobbyType == idLobby::TYPE_GAME ) { + if ( GetPartyLobby().IsHost() ) { + // As a host, wait until all party members make it + assert( !GetGameLobby().waitForPartyOk ); + + const int timeoutMs = net_connectTimeoutInSeconds.GetInteger() * 1000; + + if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) { + // Took too long, move to next result, or create a game instead + HandleConnectionFailed( lobby, false ); + return true; + } + + int numUsersIn = 0; + + for ( int i = 0; i < GetPartyLobby().GetNumLobbyUsers(); i++ ) { + + if ( net_testPartyMemberConnectFail.GetInteger() == i ) { + continue; + } + + bool foundUser = false; + + lobbyUser_t * partyUser = GetPartyLobby().GetLobbyUser( i ); + + for ( int j = 0; j < GetGameLobby().GetNumLobbyUsers(); j++ ) { + lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( j ); + + if ( GetGameLobby().IsSessionUserLocal( gameUser ) || gameUser->address.Compare( partyUser->address, true ) ) { + numUsersIn++; + foundUser = true; + break; + } + } + + assert( !GetPartyLobby().IsSessionUserIndexLocal( i ) || foundUser ); + } + + if ( numUsersIn != GetPartyLobby().GetNumLobbyUsers() ) { + return HandlePackets(); // All users not in, keep waiting until all user make it, or we time out + } + + NET_VERBOSE_PRINT( "NET: All party members made it into the game lobby.\n" ); + + // Let all the party members know everyone made it, and it's ok to stay at this server + for ( int i = 0; i < GetPartyLobby().peers.Num(); i++ ) { + if ( GetPartyLobby().peers[ i ].IsConnected() ) { + GetPartyLobby().QueueReliableMessage( i, idLobby::RELIABLE_PARTY_CONNECT_OK ); + } + } + } else { + if ( !verify ( lobby.host != -1 ) ) { + MoveToMainMenu(); + connectType = CONNECT_NONE; + return false; + } + + // As a peer, wait for server to tell us everyone made it + if ( GetGameLobby().waitForPartyOk ) { + const int timeoutMs = net_connectTimeoutInSeconds.GetInteger() * 1000; + + if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) { + GetGameLobby().waitForPartyOk = false; // Just connect to this game lobby if we haven't heard from the party host for the entire timeout duration + } + } + + if ( GetGameLobby().waitForPartyOk ) { + return HandlePackets(); // Waiting on party host to tell us everyone made it + } + } + } + + // Success + SetState( lobby.lobbyType == idLobby::TYPE_PARTY ? STATE_PARTY_LOBBY_PEER : STATE_GAME_LOBBY_PEER ); + connectType = CONNECT_NONE; + + return false; +} + +/* +======================== +idSessionLocal::MoveToMainMenu +======================== +*/ +void idSessionLocal::MoveToMainMenu() { + GetPartyLobby().Shutdown(); + GetGameLobby().Shutdown(); + SetState( STATE_IDLE ); +} + + +/* +======================== +idSessionLocal::HandleConnectionFailed +Called anytime a connection fails, and does the right thing. +======================== +*/ +void idSessionLocal::HandleConnectionFailed( idLobby & lobby, bool wasFull ) { + assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ); + assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT ); + bool canPlayOnline = true; + + // Check for online status (this is only a problem on the PS3 at the moment. The 360 LIVE system handles this for us + if ( GetSignInManager().GetMasterLocalUser() != NULL ) { + canPlayOnline = GetSignInManager().GetMasterLocalUser()->CanPlayOnline(); + } + + if ( connectType == CONNECT_FIND_OR_CREATE ) { + // Clear the "Lobby was Full" dialog in case it's up + // We only want to see this msg when doing a direct connect (CONNECT_DIRECT) + common->Dialog().ClearDialog( GDM_LOBBY_FULL ); + + assert( localState == STATE_CONNECT_AND_MOVE_TO_GAME ); + assert( lobby.lobbyType == idLobby::TYPE_GAME ); + if ( !lobby.ConnectToNextSearchResult() ) { + CreateMatch( GetGameLobby().parms ); // Assume any time we are connecting to a game lobby, it is from a FindOrCreateMatch call, so create a match + } + } else if ( connectType == CONNECT_DIRECT ) { + if ( localState == STATE_CONNECT_AND_MOVE_TO_GAME && GetPartyLobby().IsPeer() ) { + + int flags = GetPartyLobby().parms.GetSessionMatchFlags(); + + if ( MatchTypeIsOnline( flags ) && ( flags & MATCH_REQUIRE_PARTY_LOBBY ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) { + // We get here when our party host told us to connect to a game, but the game didn't exist. + // Just drop back to the party lobby and wait for further orders. + SetState( STATE_PARTY_LOBBY_PEER ); + return; + } + } + + if ( wasFull ) { + common->Dialog().AddDialog( GDM_LOBBY_FULL, DIALOG_ACCEPT, NULL, NULL, false ); + } else if ( !canPlayOnline ) { + common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT, NULL, NULL, false ); + } else { + // TEMP HACK: We detect the steam lobby is full in idLobbyBackendWin, and then STATE_FAILED, which brings us here. Need to find a way to notify + // session local that the game was full so we don't do this check here + // eeubanks: Pollard, how do you think we should handle this? + if ( !common->Dialog().HasDialogMsg( GDM_LOBBY_FULL, NULL ) ) { + common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false ); + } + } + MoveToMainMenu(); + } else { + // Shouldn't be possible, but just in case + MoveToMainMenu(); + } +} + +/* +======================== +idSessionLocal::SendRawPacket +======================== +*/ +void idSessionLocal::SendRawPacket( const lobbyAddress_t & to, const void * data, int size ) { + GetPort().SendRawPacket( to, data, size ); +} + +/* +======================== +idSessionLocal::ReadRawPacket +======================== +*/ +bool idSessionLocal::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize ) { + return GetPort().ReadRawPacket( from, data, size, maxSize ); +} + +/* +======================== +idSessionLocal::ConnectAndMoveToLobby +======================== +*/ +void idSessionLocal::ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ) { + + // Since we are connecting directly to a lobby, make sure no search results are left over from previous FindOrCreateMatch results + // If we don't do this, we might think we should attempt to connect to an old search result, and we don't want to in this case + lobby.searchResults.Clear(); + + // Attempt to connect to the lobby + lobby.ConnectTo( connectInfo, fromInvite ); + + connectType = CONNECT_DIRECT; + + // Wait for connection + SetState( lobby.lobbyType == idLobby::TYPE_PARTY ? STATE_CONNECT_AND_MOVE_TO_PARTY : STATE_CONNECT_AND_MOVE_TO_GAME ); +} + +/* +======================== +idSessionLocal::WriteLeaderboardToMsg +======================== +*/ +void idSessionLocal::WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ) { + assert( Sys_FindLeaderboardDef( leaderboard->id ) == leaderboard ); + + msg.WriteLong( leaderboard->id ); + + for ( int i = 0; i < leaderboard->numColumns; ++i ) { + uint64 value = stats[i].value; + + //idLib::Printf( "value = %i\n", (int32)value ); + + for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) { + msg.WriteBits( value & 1, 1 ); + value >>= 1; + } + //msg.WriteData( &stats[i].value, sizeof( stats[i].value ) ); + } +} + +/* +======================== +idSessionLocal::ReadLeaderboardFromMsg +======================== +*/ +const leaderboardDefinition_t * idSessionLocal::ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ) { + int id = msg.ReadLong(); + + const leaderboardDefinition_t * leaderboard = Sys_FindLeaderboardDef( id ); + + if ( leaderboard == NULL ) { + idLib::Printf( "NET: Invalid leaderboard id: %i\n", id ); + return NULL; + } + + for ( int i = 0; i < leaderboard->numColumns; ++i ) { + uint64 value = 0; + + for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) { + value |= (uint64)( msg.ReadBits( 1 ) & 1 ) << j; + } + + stats[i].value = value; + + //idLib::Printf( "value = %i\n", (int32)value ); + //msg.ReadData( &stats[i].value, sizeof( stats[i].value ) ); + } + + return leaderboard; +} + +/* +======================== +idSessionLocal::EndMatchInternal +======================== +*/ +void idSessionLocal::EndMatchInternal( bool premature/*=false*/ ) { + ClearVoiceGroups(); + + + for ( int p = 0; p < GetGameLobby().peers.Num(); p++ ) { + // If we are the host, increment the session ID. The client will use a rolling check to catch it + if ( GetGameLobby().IsHost() ) { + if ( GetGameLobby().peers[p].IsConnected() ) { + if ( GetGameLobby().peers[p].packetProc != NULL ) { + GetGameLobby().peers[p].packetProc->VerifyEmptyReliableQueue( idLobby::RELIABLE_GAME_DATA, idLobby::RELIABLE_DUMMY_MSG ); + } + GetGameLobby().peers[p].sessionID = GetGameLobby().IncrementSessionID( GetGameLobby().peers[p].sessionID ); + } + } + GetGameLobby().peers[p].ResetMatchData(); + } + + + GetGameLobby().loaded = false; + //gameLobbyWasCoalesced = false; // Reset this back to false. We use this so the lobby code doesn't randomly choose a map when we coalesce + + ClearMigrationState(); + + if ( common->IsMultiplayer() ) { + if ( GetGameLobby().IsSessionActive() ) { + // All peers need to remove disconnected users to stay in sync + GetGameLobby().CompactDisconnectedUsers(); + + // Go back to the game lobby + if ( GetGameLobby().IsHost() ) { + SetState( STATE_GAME_LOBBY_HOST ); + } else { + SetState( STATE_GAME_LOBBY_PEER ); + } + } else { + // Oops, no game lobby? + assert( false ); // how is this possible? + MoveToMainMenu(); + } + } else { + SetState( STATE_IDLE ); + } + + if ( GetGameLobby().IsHost() ) { + // Send a reliable msg to all peers to also "EndMatch" + for ( int p = 0; p < GetGameLobby().peers.Num(); p++ ) { + GetGameLobby().QueueReliableMessage( p, premature ? idLobby::RELIABLE_ENDMATCH_PREMATURE : idLobby::RELIABLE_ENDMATCH ); + } + } else if( premature ) { + // Notify client that host left early and thats why we are back in the lobby + bool stats = MatchTypeHasStats( GetGameLobby().GetMatchParms().GetSessionMatchFlags() ) && ( GetFlushedStats() == false ); + common->Dialog().AddDialog( stats ? GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED : GDM_HOST_RETURNED_TO_LOBBY, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); + } +} + +/* +======================== +idSessionLocal::HandleOobVoiceAudio +======================== +*/ +void idSessionLocal::HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) { + + idLobby * activeLobby = GetActiveLobby(); + + if ( activeLobby == NULL ) { + return; + } + + voiceChat->SetActiveLobby( activeLobby->lobbyType ); + + voiceChat->SubmitIncomingChatData( msg.GetReadData() + msg.GetReadCount(), msg.GetSize() - msg.GetReadCount() ); +} + +/* +======================== +idSessionLocal::ClearMigrationState +======================== +*/ +void idSessionLocal::ClearMigrationState() { + // We are ending the match without migration, so clear that state + GetPartyLobby().ResetAllMigrationState(); + GetGameLobby().ResetAllMigrationState(); +} + + +/* +======================== +idSessionLocal::SendLeaderboardStatsToPlayer +======================== +*/ +void idSessionLocal::SendLeaderboardStatsToPlayer( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, const column_t * stats ) { + + if ( GetGameLobby().IsLobbyUserDisconnected( sessionUserIndex ) ) { + idLib::Warning( "Tried to tell disconnected user to report stats" ); + return; + } + + const int peerIndex = GetGameLobby().PeerIndexFromLobbyUserIndex( sessionUserIndex ); + + if ( peerIndex == -1 ) { + idLib::Warning( "Tried to tell invalid peer index to report stats" ); + return; + } + + if ( !verify( GetGameLobby().IsHost() ) || + !verify( peerIndex < GetGameLobby().peers.Num() ) || + !verify( GetGameLobby().peers[ peerIndex ].IsConnected() ) ) { + idLib::Warning( "Tried to tell invalid peer to report stats" ); + return; + } + + NET_VERBOSE_PRINT( "Telling sessionUserIndex %i (peer %i) to report stats\n", sessionUserIndex, peerIndex ); + + lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( sessionUserIndex ); + + if ( !verify( gameUser != NULL ) ) { + return; + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg; + msg.InitWrite( buffer, sizeof( buffer ) ); + + // Use the user ID + msg.WriteLong( gameUser->userID ); + + WriteLeaderboardToMsg( msg, leaderboard, stats ); + + GetGameLobby().QueueReliableMessage( peerIndex, idLobby::RELIABLE_POST_STATS, msg.GetWriteData(), msg.GetSize() ); +} + +/* +======================== +idSessionLocal::RecvLeaderboardStatsForPlayer +======================== +*/ +void idSessionLocal::RecvLeaderboardStatsForPlayer( idBitMsg & msg ) { + column_t stats[ MAX_LEADERBOARD_COLUMNS ]; + + int userID = msg.ReadLong(); + + int sessionUserIndex = GetGameLobby().FindSessionUserByUserId( userID ); + + const leaderboardDefinition_t * leaderboard = ReadLeaderboardFromMsg( msg, stats ); + + if ( leaderboard == NULL ) { + idLib::Printf( "RecvLeaderboardStatsForPlayer: Invalid lb.\n" ); + return; + } + + LeaderboardUpload( sessionUserIndex, leaderboard, stats ); +} \ No newline at end of file diff --git a/neo/framework/TokenParser.cpp b/neo/framework/TokenParser.cpp new file mode 100644 index 00000000..672c7df2 --- /dev/null +++ b/neo/framework/TokenParser.cpp @@ -0,0 +1,254 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +void idTokenParser::LoadFromParser( idParser &parser, const char *guiName ) { + idToken tok; + idTokenIndexes tokIdxs; + tokIdxs.SetName( guiName ); + while ( parser.ReadToken( &tok ) ) { + tokIdxs.Append( tokens.AddUnique( idBinaryToken( tok ) ) ); + } + guiTokenIndexes.Append( tokIdxs ); + currentToken = 0; +} + +void idTokenParser::LoadFromFile( const char *filename ) { + Clear(); + idFile *inFile = fileSystem->OpenFileReadMemory( filename ); + if ( inFile != NULL ) { + int num; + inFile->ReadBig( num ); + guiTokenIndexes.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + guiTokenIndexes[ i ].Read( inFile ); + } + inFile->ReadBig( num ); + tokens.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + tokens[ i ].Read( inFile ); + } + } + delete inFile; + preloaded = ( tokens.Num() > 0 ); +} + +void idTokenParser::WriteToFile ( const char *filename ) { + if ( preloaded ) { + return; + } + idFile *outFile = fileSystem->OpenFileWrite( filename, "fs_basepath" ); + if ( outFile != NULL ) { + outFile->WriteBig( ( int )guiTokenIndexes.Num() ); + for ( int i = 0; i < guiTokenIndexes.Num(); i++ ) { + guiTokenIndexes[ i ].Write( outFile ); + } + outFile->WriteBig( ( int )tokens.Num() ); + for ( int i = 0; i < tokens.Num(); i++ ) { + tokens[ i ].Write( outFile ); + } + } + delete outFile; +} + +bool idTokenParser::StartParsing( const char * filename ) { + currentTokenList = -1; + for ( int i = 0; i < guiTokenIndexes.Num(); i++ ) { + if ( idStr::Icmp( filename, guiTokenIndexes[ i ].GetName() ) == 0 ) { + currentTokenList = i; + break; + } + } + currentToken = 0; + return ( currentTokenList != -1 ); +} + +bool idTokenParser::ReadToken( idToken * tok ) { + if ( currentToken >= 0 && currentToken < guiTokenIndexes[ currentTokenList ].Num() ) { + tok->Clear(); + idBinaryToken &btok = tokens[ guiTokenIndexes[ currentTokenList ][ currentToken ] ]; + *tok = btok.token; + tok->type = btok.tokenType; + tok->subtype = btok.tokenSubType; + currentToken++; + return true; + } + return false; +} +int idTokenParser::ExpectTokenString( const char *string ) { + idToken token; + if ( !ReadToken( &token ) ) { + Error( "couldn't find expected '%s'", string ); + return 0; + } + if ( token != string ) { + Error( "expected '%s' but found '%s'", string, token.c_str() ); + return 0; + } + return 1; +} +// expect a certain token type +int idTokenParser::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !ReadToken( token ) ) { + Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + //Error( "expected '%s' but found '%s'", idLexer::GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} +// expect a token +int idTokenParser::ExpectAnyToken( idToken *token ) { + if (!ReadToken( token )) { + Error( "couldn't read expected token" ); + return 0; + } + return 1; +} + +void idTokenParser::UnreadToken( const idToken *token ) { + if ( currentToken == 0 || currentToken >= guiTokenIndexes[ currentTokenList ].Num() ) { + idLib::common->FatalError( "idTokenParser::unreadToken, unread token twice\n" ); + } + currentToken--; +} +void idTokenParser::Error( VERIFY_FORMAT_STRING const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + + idLib::common->Warning( text ); +} +void idTokenParser::Warning( VERIFY_FORMAT_STRING const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + + idLib::common->Warning( text ); +} +int idTokenParser::ParseInt() { + idToken token; + if ( !ReadToken( &token ) ) { + Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} +// read a boolean +bool idTokenParser::ParseBool() { + idToken token; + if ( !ExpectTokenType( TT_NUMBER, 0, &token ) ) { + Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} +// read a floating point number. If errorFlag is NULL, a non-numeric token will +// issue an Error(). If it isn't NULL, it will issue a Warning() and set *errorFlag = true +float idTokenParser::ParseFloat( bool *errorFlag ) { + idToken token; + if ( errorFlag ) { + *errorFlag = false; + } + if ( !ReadToken( &token ) ) { + if ( errorFlag ) { + Warning( "couldn't read expected floating point number" ); + *errorFlag = true; + } else { + Error( "couldn't read expected floating point number" ); + } + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + if ( errorFlag ) { + Warning( "expected float value, found '%s'", token.c_str() ); + *errorFlag = true; + } else { + Error( "expected float value, found '%s'", token.c_str() ); + } + } + return token.GetFloatValue(); +} diff --git a/neo/framework/TokenParser.h b/neo/framework/TokenParser.h new file mode 100644 index 00000000..c8d511c4 --- /dev/null +++ b/neo/framework/TokenParser.h @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TOKENPARSER_H__ +#define __TOKENPARSER_H__ +class idBinaryToken { +public: + idBinaryToken() { + tokenType = 0; + tokenSubType = 0; + } + idBinaryToken( const idToken &tok ) { + token = tok.c_str(); + tokenType = tok.type; + tokenSubType = tok.subtype; + } + bool operator==( const idBinaryToken &b ) const { + return ( tokenType == b.tokenType && tokenSubType == b.tokenSubType && token.Cmp( b.token ) == 0 ); + } + void Read( idFile *inFile ) { + inFile->ReadString( token ); + inFile->ReadBig( tokenType ); + inFile->ReadBig( tokenSubType ); + } + void Write( idFile *inFile ) { + inFile->WriteString( token ); + inFile->WriteBig( tokenType ); + inFile->WriteBig( tokenSubType ); + } + idStr token; + int8 tokenType; + short tokenSubType; +}; + +class idTokenIndexes { +public: + idTokenIndexes() {} + void Clear() { + tokenIndexes.Clear(); + } + int Append( short sdx ) { + return tokenIndexes.Append( sdx ); + } + int Num() { + return tokenIndexes.Num(); + } + void SetNum( int num ) { + tokenIndexes.SetNum( num ); + } + short & operator[]( const int index ) { + return tokenIndexes[ index ]; + } + void SetName( const char *name ) { + fileName = name; + } + const char *GetName() { + return fileName.c_str(); + } + void Write( idFile *outFile ) { + outFile->WriteString( fileName ); + outFile->WriteBig( ( int )tokenIndexes.Num() ); + outFile->WriteBigArray( tokenIndexes.Ptr(), tokenIndexes.Num() ); + } + void Read( idFile *inFile ) { + inFile->ReadString( fileName ); + int num; + inFile->ReadBig( num ); + tokenIndexes.SetNum( num ); + inFile->ReadBigArray( tokenIndexes.Ptr(), num ); + } +private: + idList< short > tokenIndexes; + idStr fileName; +}; + +class idTokenParser { +public: + idTokenParser() { + timeStamp = FILE_NOT_FOUND_TIMESTAMP; + preloaded = false; + currentToken = 0; + currentTokenList = 0; + } + ~idTokenParser() { + Clear(); + } + void Clear() { + tokens.Clear(); + guiTokenIndexes.Clear(); + currentToken = 0; + currentTokenList = -1; + preloaded = false; + } + void LoadFromFile( const char *filename ); + void WriteToFile (const char *filename ); + void LoadFromParser( idParser &parser, const char *guiName ); + + bool StartParsing( const char *fileName ); + void DoneParsing() { currentTokenList = -1; } + + bool IsLoaded() { return tokens.Num() > 0; } + bool ReadToken( idToken * tok ); + int ExpectTokenString( const char *string ); + int ExpectTokenType( int type, int subtype, idToken *token ); + int ExpectAnyToken( idToken *token ); + void SetMarker() {} + void UnreadToken( const idToken *token ); + void Error( VERIFY_FORMAT_STRING const char *str, ... ); + void Warning( VERIFY_FORMAT_STRING const char *str, ... ); + int ParseInt(); + bool ParseBool(); + float ParseFloat( bool *errorFlag = NULL ); + void UpdateTimeStamp( ID_TIME_T &t ) { + if ( t > timeStamp ) { + timeStamp = t; + } + } +private: + idList< idBinaryToken > tokens; + idList< idTokenIndexes > guiTokenIndexes; + int currentToken; + int currentTokenList; + ID_TIME_T timeStamp; + bool preloaded; +}; + +#endif /* !__TOKENPARSER_H__ */ diff --git a/neo/framework/Unzip.cpp b/neo/framework/Unzip.cpp new file mode 100644 index 00000000..8619949c --- /dev/null +++ b/neo/framework/Unzip.cpp @@ -0,0 +1,1222 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Unzip.h" + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ +/* for more info about .ZIP format, see + ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip */ + + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ + !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Mem_Alloc(size, TAG_IDFILE)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Mem_Free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + +idCVar zip_numSeeks( "zip_numSeeks", "0", CVAR_INTEGER, "" ); +idCVar zip_skippedSeeks( "zip_skippedSeeks", "0", CVAR_INTEGER, "" ); +idCVar zip_seeksForward( "zip_seeksForward", "0", CVAR_INTEGER, "" ); +idCVar zip_seeksBackward( "zip_seeksBackward", "0", CVAR_INTEGER, "" ); +idCVar zip_avgSeekDistance( "zip_avgSeekDistance", "0", CVAR_INTEGER, "" ); + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + +/* +static int unzlocal_getByte(FILE *fin,int *pi) +{ + unsigned char c; + int err = fread(&c, 1, 1, fin); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ferror(fin)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} +*/ + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +static int unzlocal_getShort (idFile * fin, uLong *pX) +{ + byte s[2]; + if ( fin->Read( s, 2 ) != 2 ) { + *pX = 0; + return UNZ_EOF; + } + *pX = ( s[1] << 8 ) | s[0]; + return UNZ_OK; +} + +static int unzlocal_getLong (idFile * fin, uLong *pX) +{ + byte s[4]; + if ( fin->Read( s, 4 ) != 4 ) { + *pX = 0; + return UNZ_EOF; + } + *pX = ( s[3] << 24 ) | ( s[2] << 16 ) | ( s[1] << 8 ) | s[0]; + return UNZ_OK; +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#define BUFREADCOMMENT (0x400) + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +static uLong unzlocal_SearchCentralDir(idFile * fin) +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if ( fin->Seek( 0, FS_SEEK_END ) != 0 ) + return 0; + + uSizeFile = fin->Tell(); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + + if ( fin->Seek( uReadPos, FS_SEEK_SET ) != 0 ) + break; + + if ( fin->Read( buf, uReadSize ) != (int)uReadSize ) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ + unz_s *s; + idFile_Cached * fin; + + fin = fileSystem->OpenExplicitPakFile( path ); + if (fin==NULL) + return NULL; + + s=(unz_s*)ALLOC(sizeof(unz_s)); + memcpy(s, (unz_s*)file, sizeof(unz_s)); + + s->file = fin; + s->pfile_in_zip_read = NULL; + + return (unzFile)s; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer + "zlib/zlib109.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + idFile_Cached * fin ; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + fin = fileSystem->OpenExplicitPakFile( path ); + if (fin==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(fin); + if (central_pos==0) + err=UNZ_ERRNO; + + if ( fin->Seek( central_pos, FS_SEEK_SET ) != 0 ) + err = UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(fin,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_posCloseFile( fin ); + return NULL; + } + + us.file=fin; + us.byte_before_the_zipfile = central_pos - + (us.offset_central_dir+us.size_central_dir); + us.central_pos = central_pos; + us.pfile_in_zip_read = NULL; + + us.file->CacheData( us.offset_central_dir, us.size_central_dir ); + + s=(unz_s*)ALLOC(sizeof(unz_s)); + *s=us; + +// unzGoToFirstFile((unzFile)s); + return (unzFile)s; +} + + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzipOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ +extern int unzClose (unzFile file) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + if (s->pfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + fileSystem->CloseFile( s->file ); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + int tellpos = s->file->Tell() - s->pos_in_central_dir + s->byte_before_the_zipfile; + if ( tellpos != 0 ) { + if ( s->file->Seek( s->pos_in_central_dir + s->byte_before_the_zipfile, FS_SEEK_SET ) != 0 ) { + err = UNZ_ERRNO; + } + if ( tellpos < 0 ) { + zip_seeksForward.SetInteger( zip_seeksForward.GetInteger() + 1 ); + } else { + zip_seeksBackward.SetInteger( zip_seeksBackward.GetInteger() + 1 ); + } + + static long zip_totalSeekSize = 0; + if ( zip_numSeeks.GetInteger() == 0 ) { + zip_totalSeekSize = 0; + } + zip_totalSeekSize += abs( tellpos ); + + zip_numSeeks.SetInteger( zip_numSeeks.GetInteger() + 1 ); + zip_avgSeekDistance.SetInteger( zip_totalSeekSize / zip_numSeeks.GetInteger() ); + } else { + zip_skippedSeeks.SetInteger( zip_skippedSeeks.GetInteger() + 1 ); + } + + + /* we check the magic */ + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if ( s->file->Read( szFileName, uSizeRead ) != (int)uSizeRead ) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extrafile->Seek( lSeek, FS_SEEK_CUR ) == 0 ) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if ( s->file->Read( extraField, uSizeRead ) != (int)uSizeRead ) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentfile->Seek( lSeek, FS_SEEK_CUR ) == 0 ) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if ( s->file->Read( szComment, uSizeRead ) != (int)uSizeRead ) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ) +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + *pos = s->pos_in_central_dir; + return UNZ_OK; +} + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return UNZ_OK; +} + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + return err; +} + + +/* + Read the static header of the current zipfile + Check the coherency of the static header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if ( s->file->Seek( s->cur_file_info_internal.offset_curfile + s->byte_before_the_zipfile, FS_SEEK_SET ) != 0 ) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ + int err=UNZ_OK; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + Store = s->cur_file_info.compression_method==0; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->file=s->file; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidp)0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + + s->pfile_in_zip_read = pfile_in_zip_read_info; + return UNZ_OK; +} + + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if (len>pfile_in_zip_read_info->rest_read_uncompressed) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) + if ( pfile_in_zip_read_info->file->Seek( pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile, FS_SEEK_SET ) != 0 ) + return UNZ_ERRNO; + if ( pfile_in_zip_read_info->file->Read( pfile_in_zip_read_info->read_buffer, uReadThis ) != (int)uReadThis ) + return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Byte*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if (pfile_in_zip_read_info->compression_method==0) + { + uInt uDoCopy,i ; + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int unzeof (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the static-header version of the extra field (sometimes, there is + more info in the static-header version than in the central-header) + + if buf==NULL, it return the size of the static extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if ( pfile_in_zip_read_info->file->Seek( pfile_in_zip_read_info->offset_local_extrafield + pfile_in_zip_read_info->pos_local_extrafield, FS_SEEK_SET ) != 0 ) + return UNZ_ERRNO; + + if ( pfile_in_zip_read_info->file->Read( buf, size_to_read ) != (int)size_to_read ) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if ( s->file->Seek( s->central_pos + 22, FS_SEEK_SET ) == 0 ) + return UNZ_ERRNO; + + if (uReadThis>0) + { + if ( szComment == NULL ) { + return (int)uReadThis; + } + *szComment='\0'; + if ( s->file->Read( szComment, uReadThis ) != (int)uReadThis ) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} diff --git a/neo/framework/Unzip.h b/neo/framework/Unzip.h new file mode 100644 index 00000000..d990ca20 --- /dev/null +++ b/neo/framework/Unzip.h @@ -0,0 +1,326 @@ + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ +#ifndef __UNZIP_H__ +#define __UNZIP_H__ + +#include "zlib/zlib.h" + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + unsigned long number_entry; /* total number of entries in the central dir on this disk */ + unsigned long size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + unsigned long version; /* version made by 2 unsigned chars */ + unsigned long version_needed; /* version needed to extract 2 unsigned chars */ + unsigned long flag; /* general purpose bit flag 2 unsigned chars */ + unsigned long compression_method; /* compression method 2 unsigned chars */ + unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */ + unsigned long crc; /* crc-32 4 unsigned chars */ + unsigned long compressed_size; /* compressed size 4 unsigned chars */ + unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */ + unsigned long size_filename; /* filename length 2 unsigned chars */ + unsigned long size_file_extra; /* extra field length 2 unsigned chars */ + unsigned long size_file_comment; /* file comment length 2 unsigned chars */ + + unsigned long disk_num_start; /* disk number start 2 unsigned chars */ + unsigned long internal_fa; /* internal file attributes 2 unsigned chars */ + unsigned long external_fa; /* external file attributes 4 unsigned chars */ + + tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/ + unsigned long stream_initialised; /* flag set if stream structure is initialised*/ + + unsigned long offset_local_extrafield;/* offset of the static extra field */ + unsigned int size_local_extrafield;/* size of the static extra field */ + unsigned long pos_local_extrafield; /* position in the static extra field in read*/ + + unsigned long crc32; /* crc32 of all data uncompressed */ + unsigned long crc32_wait; /* crc32 we must obtain after decompress all */ + unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ + unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/ + idFile * file; /* io structore of the zipfile */ + unsigned long compression_method; /* compression method (0==store) */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + idFile_Cached * file; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ + unsigned long num_file; /* number of the current file in the zipfile*/ + unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/ + unsigned long current_file_ok; /* flag about the usability of the current file*/ + unsigned long central_pos; /* position of the beginning of the central dir*/ + + unsigned long size_central_dir; /* size of the central directory */ + unsigned long offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ +} unz_s; + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +#define UNZ_CASESENSITIVE 1 +#define UNZ_NOTCASESENSITIVE 2 +#define UNZ_OSDEFAULTCASE 0 + +extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen (const char *path); +extern unzFile unzReOpen (const char* path, unzFile file); + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer + "zlib/zlib111.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern int unzClose (unzFile file); + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf); + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile (unzFile file); + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile (unzFile file); + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ); + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ); + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize); + +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int unzOpenCurrentFile (unzFile file); + +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile (unzFile file); + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + + +extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len); + +/* + Read unsigned chars from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of unsigned char copied if somes unsigned chars are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell(unzFile file); + +/* + Give the current position in uncompressed data +*/ + +extern int unzeof (unzFile file); + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len); + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of unsigned chars copied in buf, or (if <0) + the error code +*/ + +#endif /* __UNZIP_H__ */ diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp new file mode 100644 index 00000000..dc4208ce --- /dev/null +++ b/neo/framework/UsercmdGen.cpp @@ -0,0 +1,1342 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar joy_mergedThreshold( "joy_mergedThreshold", "1", CVAR_BOOL | CVAR_ARCHIVE, "If the thresholds aren't merged, you drift more off center" ); +idCVar joy_newCode( "joy_newCode", "1", CVAR_BOOL | CVAR_ARCHIVE, "Use the new codepath" ); +idCVar joy_triggerThreshold( "joy_triggerThreshold", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "how far the joystick triggers have to be pressed before they register as down" ); +idCVar joy_deadZone( "joy_deadZone", "0.2", CVAR_FLOAT | CVAR_ARCHIVE, "specifies how large the dead-zone is on the joystick" ); +idCVar joy_range( "joy_range", "1.0", CVAR_FLOAT | CVAR_ARCHIVE, "allow full range to be mapped to a smaller offset" ); +idCVar joy_gammaLook( "joy_gammaLook", "1", CVAR_INTEGER | CVAR_ARCHIVE, "use a log curve instead of a power curve for movement" ); +idCVar joy_powerScale( "joy_powerScale", "2", CVAR_FLOAT | CVAR_ARCHIVE, "Raise joystick values to this power" ); +idCVar joy_pitchSpeed( "joy_pitchSpeed", "100", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing up or down on the joystick", 60, 600 ); +idCVar joy_yawSpeed( "joy_yawSpeed", "240", CVAR_ARCHIVE | CVAR_FLOAT, "pitch speed when pressing left or right on the joystick", 60, 600 ); + +// these were a bad idea! +idCVar joy_dampenLook( "joy_dampenLook", "1", CVAR_BOOL | CVAR_ARCHIVE, "Do not allow full acceleration on look" ); +idCVar joy_deltaPerMSLook( "joy_deltaPerMSLook", "0.003", CVAR_FLOAT | CVAR_ARCHIVE, "Max amount to be added on look per MS" ); + +idCVar in_mouseSpeed( "in_mouseSpeed", "1", CVAR_ARCHIVE | CVAR_FLOAT, "speed at which the mouse moves", 0.25f, 4.0f ); +idCVar in_alwaysRun( "in_alwaysRun", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "always run (reverse _speed button) - only in MP" ); + +idCVar in_useJoystick( "in_useJoystick", "0", CVAR_ARCHIVE | CVAR_BOOL, "enables/disables the gamepad for PC use" ); +idCVar in_joystickRumble( "in_joystickRumble", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "enable joystick rumble" ); +idCVar in_invertLook( "in_invertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); +idCVar in_mouseInvertLook( "in_mouseInvertLook", "0", CVAR_ARCHIVE | CVAR_BOOL, "inverts the look controls so the forward looks up (flight controls) - the proper way to play games!" ); + +/* +================ +usercmd_t::ByteSwap +================ +*/ +void usercmd_t::ByteSwap() { + angles[0] = LittleShort( angles[0] ); + angles[1] = LittleShort( angles[1] ); + angles[2] = LittleShort( angles[2] ); +} + +/* +================ +usercmd_t::Serialize +================ +*/ +void usercmd_t::Serialize( idSerializer & ser, const usercmd_t & base ) { + ser.SerializeDelta( buttons, base.buttons ); + ser.SerializeDelta( forwardmove, base.forwardmove ); + ser.SerializeDelta( rightmove, base.rightmove ); + ser.SerializeDelta( angles[0], base.angles[0] ); + ser.SerializeDelta( angles[1], base.angles[1] ); + ser.SerializeDelta( angles[2], base.angles[2] ); + ser.SerializeDelta( pos.x, base.pos.x ); + ser.SerializeDelta( pos.y, base.pos.y ); + ser.SerializeDelta( pos.z, base.pos.z ); + ser.SerializeDelta( clientGameMilliseconds, base.clientGameMilliseconds ); + ser.SerializeDelta( serverGameMilliseconds, base.serverGameMilliseconds ); + ser.SerializeDelta( fireCount, base.fireCount ); + ser.SerializeDelta( speedSquared, base.speedSquared ); + ser.SerializeDelta( impulse, base.impulse ); + ser.SerializeDelta( impulseSequence, base.impulseSequence ); +} + +/* +================ +usercmd_t::operator== +================ +*/ +bool usercmd_t::operator==( const usercmd_t &rhs ) const { + return ( buttons == rhs.buttons && + forwardmove == rhs.forwardmove && + rightmove == rhs.rightmove && + angles[0] == rhs.angles[0] && + angles[1] == rhs.angles[1] && + angles[2] == rhs.angles[2] && + impulse == rhs.impulse && + impulseSequence == rhs.impulseSequence && + mx == rhs.mx && + my == rhs.my && + fireCount == rhs.fireCount && + speedSquared == speedSquared ); +} + + +const int KEY_MOVESPEED = 127; + +userCmdString_t userCmdStrings[] = { + { "_moveUp", UB_MOVEUP }, + { "_moveDown", UB_MOVEDOWN }, + { "_left", UB_LOOKLEFT }, + { "_right", UB_LOOKRIGHT }, + { "_forward", UB_MOVEFORWARD }, + { "_back", UB_MOVEBACK }, + { "_lookUp", UB_LOOKUP }, + { "_lookDown", UB_LOOKDOWN }, + { "_moveLeft", UB_MOVELEFT }, + { "_moveRight", UB_MOVERIGHT }, + + { "_attack", UB_ATTACK }, + { "_speed", UB_SPEED }, + { "_zoom", UB_ZOOM }, + { "_showScores", UB_SHOWSCORES }, + { "_use", UB_USE }, + + { "_impulse0", UB_IMPULSE0 }, + { "_impulse1", UB_IMPULSE1 }, + { "_impulse2", UB_IMPULSE2 }, + { "_impulse3", UB_IMPULSE3 }, + { "_impulse4", UB_IMPULSE4 }, + { "_impulse5", UB_IMPULSE5 }, + { "_impulse6", UB_IMPULSE6 }, + { "_impulse7", UB_IMPULSE7 }, + { "_impulse8", UB_IMPULSE8 }, + { "_impulse9", UB_IMPULSE9 }, + { "_impulse10", UB_IMPULSE10 }, + { "_impulse11", UB_IMPULSE11 }, + { "_impulse12", UB_IMPULSE12 }, + { "_impulse13", UB_IMPULSE13 }, + { "_impulse14", UB_IMPULSE14 }, + { "_impulse15", UB_IMPULSE15 }, + { "_impulse16", UB_IMPULSE16 }, + { "_impulse17", UB_IMPULSE17 }, + { "_impulse18", UB_IMPULSE18 }, + { "_impulse19", UB_IMPULSE19 }, + { "_impulse20", UB_IMPULSE20 }, + { "_impulse21", UB_IMPULSE21 }, + { "_impulse22", UB_IMPULSE22 }, + { "_impulse23", UB_IMPULSE23 }, + { "_impulse24", UB_IMPULSE24 }, + { "_impulse25", UB_IMPULSE25 }, + { "_impulse26", UB_IMPULSE26 }, + { "_impulse27", UB_IMPULSE27 }, + { "_impulse28", UB_IMPULSE28 }, + { "_impulse29", UB_IMPULSE29 }, + { "_impulse30", UB_IMPULSE30 }, + { "_impulse31", UB_IMPULSE31 }, + + { NULL, UB_NONE }, +}; + + class buttonState_t { + public: + int on; + bool held; + + buttonState_t() { Clear(); }; + void Clear(); + void SetKeyState( int keystate, bool toggle ); +}; + +/* +================ +buttonState_t::Clear +================ +*/ +void buttonState_t::Clear() { + held = false; + on = 0; +} + +/* +================ +buttonState_t::SetKeyState +================ +*/ +void buttonState_t::SetKeyState( int keystate, bool toggle ) { + if ( !toggle ) { + held = false; + on = keystate; + } else if ( !keystate ) { + held = false; + } else if ( !held ) { + held = true; + on ^= 1; + } +} + + +const int NUM_USER_COMMANDS = sizeof(userCmdStrings) / sizeof(userCmdString_t); + +const int MAX_CHAT_BUFFER = 127; + +class idUsercmdGenLocal : public idUsercmdGen { +public: + idUsercmdGenLocal(); + + void Init(); + + void InitForNewMap(); + + void Shutdown(); + + void Clear(); + + void ClearAngles(); + + void InhibitUsercmd( inhibit_t subsystem, bool inhibit ); + + int CommandStringUsercmdData( const char *cmdString ); + + void BuildCurrentUsercmd( int deviceNum ); + + usercmd_t GetCurrentUsercmd() { return cmd; }; + + void MouseState( int *x, int *y, int *button, bool *down ); + + int ButtonState( int key ); + int KeyState( int key ); + +private: + void MakeCurrent(); + void InitCurrent(); + + bool Inhibited(); + void AdjustAngles(); + void KeyMove(); + void CircleToSquare( float & axis_x, float & axis_y ) const; + void HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive ); + void JoystickMove(); + void JoystickMove2(); + void MouseMove(); + void CmdButtons(); + + void AimAssist(); + + void Mouse(); + void Keyboard(); + void Joystick( int deviceNum ); + + void Key( int keyNum, bool down ); + + idVec3 viewangles; + int impulseSequence; + int impulse; + + buttonState_t toggled_crouch; + buttonState_t toggled_run; + buttonState_t toggled_zoom; + + int buttonState[UB_MAX_BUTTONS]; + bool keyState[K_LAST_KEY]; + + int inhibitCommands; // true when in console or menu locally + + bool initialized; + + usercmd_t cmd; // the current cmd being built + + int continuousMouseX, continuousMouseY; // for gui event generatioin, never zerod + int mouseButton; // for gui event generatioin + bool mouseDown; + + int mouseDx, mouseDy; // added to by mouse events + float joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + int pollTime; + int lastPollTime; + float lastLookValuePitch; + float lastLookValueYaw; + + static idCVar in_yawSpeed; + static idCVar in_pitchSpeed; + static idCVar in_angleSpeedKey; + static idCVar in_toggleRun; + static idCVar in_toggleCrouch; + static idCVar in_toggleZoom; + static idCVar sensitivity; + static idCVar m_pitch; + static idCVar m_yaw; + static idCVar m_smooth; + static idCVar m_showMouseRate; +}; + +idCVar idUsercmdGenLocal::in_yawSpeed( "in_yawspeed", "140", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "yaw change speed when holding down _left or _right button" ); +idCVar idUsercmdGenLocal::in_pitchSpeed( "in_pitchspeed", "140", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "pitch change speed when holding down look _lookUp or _lookDown button" ); +idCVar idUsercmdGenLocal::in_angleSpeedKey( "in_anglespeedkey", "1.5", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "angle change scale when holding down _speed button" ); +idCVar idUsercmdGenLocal::in_toggleRun( "in_toggleRun", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _speed button toggles run on/off - only in MP" ); +idCVar idUsercmdGenLocal::in_toggleCrouch( "in_toggleCrouch", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _movedown button toggles player crouching/standing" ); +idCVar idUsercmdGenLocal::in_toggleZoom( "in_toggleZoom", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _zoom button toggles zoom on/off" ); +idCVar idUsercmdGenLocal::sensitivity( "sensitivity", "5", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse view sensitivity" ); +idCVar idUsercmdGenLocal::m_pitch( "m_pitch", "0.022", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse pitch scale" ); +idCVar idUsercmdGenLocal::m_yaw( "m_yaw", "0.022", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse yaw scale" ); +idCVar idUsercmdGenLocal::m_smooth( "m_smooth", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse viewing", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> ); +idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" ); + +static idUsercmdGenLocal localUsercmdGen; +idUsercmdGen *usercmdGen = &localUsercmdGen; + +/* +================ +idUsercmdGenLocal::idUsercmdGenLocal +================ +*/ +idUsercmdGenLocal::idUsercmdGenLocal() { + initialized = false; + + pollTime = 0; + lastPollTime = 0; + lastLookValuePitch = 0.0f; + lastLookValueYaw = 0.0f; + + impulseSequence = 0; + impulse = 0; + + toggled_crouch.Clear(); + toggled_run.Clear(); + toggled_zoom.Clear(); + toggled_run.on = false; + + ClearAngles(); + Clear(); +} + +/* +================ +idUsercmdGenLocal::InhibitUsercmd +================ +*/ +void idUsercmdGenLocal::InhibitUsercmd( inhibit_t subsystem, bool inhibit ) { + if ( inhibit ) { + inhibitCommands |= 1 << subsystem; + } else { + inhibitCommands &= ( 0xffffffff ^ ( 1 << subsystem ) ); + } +} + +/* +=============== +idUsercmdGenLocal::ButtonState + +Returns (the fraction of the frame) that the key was down +=============== +*/ +int idUsercmdGenLocal::ButtonState( int key ) { + if ( key<0 || key>=UB_MAX_BUTTONS ) { + return -1; + } + return ( buttonState[key] > 0 ) ? 1 : 0; +} + +/* +=============== +idUsercmdGenLocal::KeyState + +Returns (the fraction of the frame) that the key was down +bk20060111 +=============== +*/ +int idUsercmdGenLocal::KeyState( int key ) { + if ( key<0 || key>=K_LAST_KEY ) { + return -1; + } + return ( keyState[key] ) ? 1 : 0; +} + + +//===================================================================== + +/* +================ +idUsercmdGenLocal::Inhibited + +is user cmd generation inhibited +================ +*/ +bool idUsercmdGenLocal::Inhibited() { + return ( inhibitCommands != 0); +} + +/* +================ +idUsercmdGenLocal::AdjustAngles + +Moves the local angle positions +================ +*/ +void idUsercmdGenLocal::AdjustAngles() { + float speed = MS2SEC( 16 ); + + if ( toggled_run.on || ( in_alwaysRun.GetBool() && common->IsMultiplayer() ) ) { + speed *= in_angleSpeedKey.GetFloat(); + } + + viewangles[YAW] -= speed * in_yawSpeed.GetFloat() * ButtonState( UB_LOOKRIGHT ); + viewangles[YAW] += speed * in_yawSpeed.GetFloat() * ButtonState( UB_LOOKLEFT ); + + viewangles[PITCH] -= speed * in_pitchSpeed.GetFloat() * ButtonState( UB_LOOKUP ); + viewangles[PITCH] += speed * in_pitchSpeed.GetFloat() * ButtonState( UB_LOOKDOWN ); +} + +/* +================ +idUsercmdGenLocal::KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void idUsercmdGenLocal::KeyMove() { + int forward = 0; + int side = 0; + + side += KEY_MOVESPEED * ButtonState( UB_MOVERIGHT ); + side -= KEY_MOVESPEED * ButtonState( UB_MOVELEFT ); + + forward += KEY_MOVESPEED * ButtonState( UB_MOVEFORWARD ); + forward -= KEY_MOVESPEED * ButtonState( UB_MOVEBACK ); + + cmd.forwardmove += idMath::ClampChar( forward ); + cmd.rightmove += idMath::ClampChar( side ); +} + +/* +================= +idUsercmdGenLocal::MouseMove +================= +*/ +void idUsercmdGenLocal::MouseMove() { + float mx, my; + static int history[8][2]; + static int historyCounter; + int i; + + history[historyCounter&7][0] = mouseDx; + history[historyCounter&7][1] = mouseDy; + + // allow mouse movement to be smoothed together + int smooth = m_smooth.GetInteger(); + if ( smooth < 1 ) { + smooth = 1; + } + if ( smooth > 8 ) { + smooth = 8; + } + mx = 0; + my = 0; + for ( i = 0 ; i < smooth ; i++ ) { + mx += history[ ( historyCounter - i + 8 ) & 7 ][0]; + my += history[ ( historyCounter - i + 8 ) & 7 ][1]; + } + mx /= smooth; + my /= smooth; + + historyCounter++; + + if ( idMath::Fabs( mx ) > 1000 || idMath::Fabs( my ) > 1000 ) { + Sys_DebugPrintf( "idUsercmdGenLocal::MouseMove: Ignoring ridiculous mouse delta.\n" ); + mx = my = 0; + } + + mx *= sensitivity.GetFloat(); + my *= sensitivity.GetFloat(); + + if ( m_showMouseRate.GetBool() ) { + Sys_DebugPrintf( "[%3i %3i = %5.1f %5.1f] ", mouseDx, mouseDy, mx, my ); + } + + mouseDx = 0; + mouseDy = 0; + + viewangles[YAW] -= m_yaw.GetFloat() * mx * in_mouseSpeed.GetFloat(); + viewangles[PITCH] += m_pitch.GetFloat() * in_mouseSpeed.GetFloat() * ( in_mouseInvertLook.GetBool() ? -my : my ); +} + +/* +======================== +idUsercmdGenLocal::CircleToSquare +======================== +*/ +void idUsercmdGenLocal::CircleToSquare( float & axis_x, float & axis_y ) const { + // bring everything in the first quadrant + bool flip_x = false; + if ( axis_x < 0.0f ) { + flip_x = true; + axis_x *= -1.0f; + } + bool flip_y = false; + if ( axis_y < 0.0f ) { + flip_y = true; + axis_y *= -1.0f; + } + + // swap the two axes so we project against the vertical line X = 1 + bool swap = false; + if ( axis_y > axis_x ) { + float tmp = axis_x; + axis_x = axis_y; + axis_y = tmp; + swap = true; + } + + if ( axis_x < 0.001f ) { + // on one of the axes where no correction is needed + return; + } + + // length (max 1.0f at the unit circle) + float len = idMath::Sqrt( axis_x * axis_x + axis_y * axis_y ); + if ( len > 1.0f ) { + len = 1.0f; + } + // thales + float axis_y_us = axis_y / axis_x; + + // use a power curve to shift the correction to happen closer to the unit circle + float correctionRatio = Square( len ); + axis_x += correctionRatio * ( len - axis_x ); + axis_y += correctionRatio * ( axis_y_us - axis_y ); + + // go back through the symmetries + if ( swap ) { + float tmp = axis_x; + axis_x = axis_y; + axis_y = tmp; + } + if ( flip_x ) { + axis_x *= -1.0f; + } + if ( flip_y ) { + axis_y *= -1.0f; + } +} + +/* +======================== +idUsercmdGenLocal::HandleJoystickAxis +======================== +*/ +void idUsercmdGenLocal::HandleJoystickAxis( int keyNum, float unclampedValue, float threshold, bool positive ) { + if ( ( unclampedValue > 0.0f ) && !positive ) { + return; + } + if ( ( unclampedValue < 0.0f ) && positive ) { + return; + } + float value = 0.0f; + bool pressed = false; + if ( unclampedValue > threshold ) { + value = idMath::Fabs( ( unclampedValue - threshold ) / ( 1.0f - threshold ) ); + pressed = true; + } else if ( unclampedValue < -threshold ) { + value = idMath::Fabs( ( unclampedValue + threshold ) / ( 1.0f - threshold ) ); + pressed = true; + } + + int action = idKeyInput::GetUsercmdAction( keyNum ); + if ( action >= UB_ATTACK ) { + Key( keyNum, pressed ); + return; + } + if ( !pressed ) { + return; + } + + float lookValue = 0.0f; + if ( joy_gammaLook.GetBool() ) { + lookValue = idMath::Pow( 1.04712854805f, value * 100.0f ) * 0.01f; + } else { + lookValue = idMath::Pow( value, joy_powerScale.GetFloat() ); + } + + idGame * game = common->Game(); + if ( game != NULL ) { + lookValue *= game->GetAimAssistSensitivity(); + } + + switch ( action ) { + case UB_MOVEFORWARD: { + float move = (float)cmd.forwardmove + ( KEY_MOVESPEED * value ); + cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_MOVEBACK: { + float move = (float)cmd.forwardmove - ( KEY_MOVESPEED * value ); + cmd.forwardmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_MOVELEFT: { + float move = (float)cmd.rightmove - ( KEY_MOVESPEED * value ); + cmd.rightmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_MOVERIGHT: { + float move = (float)cmd.rightmove + ( KEY_MOVESPEED * value ); + cmd.rightmove = idMath::ClampChar( idMath::Ftoi( move ) ); + break; + } + case UB_LOOKUP: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValuePitch ); + lastLookValuePitch = lookValue; + } + + float invertPitch = in_invertLook.GetBool() ? -1.0f : 1.0f; + viewangles[PITCH] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch; + break; + } + case UB_LOOKDOWN: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValuePitch ); + lastLookValuePitch = lookValue; + } + + float invertPitch = in_invertLook.GetBool() ? -1.0f : 1.0f; + viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_pitchSpeed.GetFloat() * invertPitch; + break; + } + case UB_LOOKLEFT: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValueYaw ); + lastLookValueYaw = lookValue; + } + viewangles[YAW] += MS2SEC( pollTime - lastPollTime ) * lookValue * joy_yawSpeed.GetFloat(); + break; + } + case UB_LOOKRIGHT: { + if ( joy_dampenLook.GetBool() ) { + lookValue = Min( lookValue, ( pollTime - lastPollTime ) * joy_deltaPerMSLook.GetFloat() + lastLookValueYaw ); + lastLookValueYaw = lookValue; + } + viewangles[YAW] -= MS2SEC( pollTime - lastPollTime ) * lookValue * joy_yawSpeed.GetFloat(); + break; + } + } +} + +/* +================= +idUsercmdGenLocal::JoystickMove +================= +*/ +void idUsercmdGenLocal::JoystickMove() { + float threshold = joy_deadZone.GetFloat(); + float triggerThreshold = joy_triggerThreshold.GetFloat(); + + float axis_y = joystickAxis[ AXIS_LEFT_Y ]; + float axis_x = joystickAxis[ AXIS_LEFT_X ]; + CircleToSquare( axis_x, axis_y ); + + HandleJoystickAxis( K_JOY_STICK1_UP, axis_y, threshold, false ); + HandleJoystickAxis( K_JOY_STICK1_DOWN, axis_y, threshold, true ); + HandleJoystickAxis( K_JOY_STICK1_LEFT, axis_x, threshold, false ); + HandleJoystickAxis( K_JOY_STICK1_RIGHT, axis_x, threshold, true ); + + axis_y = joystickAxis[ AXIS_RIGHT_Y ]; + axis_x = joystickAxis[ AXIS_RIGHT_X ]; + CircleToSquare( axis_x, axis_y ); + + HandleJoystickAxis( K_JOY_STICK2_UP, axis_y, threshold, false ); + HandleJoystickAxis( K_JOY_STICK2_DOWN, axis_y, threshold, true ); + HandleJoystickAxis( K_JOY_STICK2_LEFT, axis_x, threshold, false ); + HandleJoystickAxis( K_JOY_STICK2_RIGHT, axis_x, threshold, true ); + + HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true ); + HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true ); +} + +enum transferFunction_t { + FUNC_LINEAR, + FUNC_LOGARITHMIC, + FUNC_EXPONENTIAL +}; + +/* +================= +JoypadFunction +================= +*/ +idVec2 JoypadFunction( + const idVec2 raw, + const float aimAssistScale, + const float threshold, + const float range, + const transferFunction_t shape, + const bool mergedThreshold ) { + + if ( range <= threshold ) { + return idVec2( 0.0f, 0.0f ); + } + + idVec2 threshed; + if ( !mergedThreshold ) { + // if the thresholding is performed independently, you can more easily move + // or look in a pure axial direction without drifting + for ( int i = 0 ; i < 2 ; i++ ) { + const float v = raw[i]; + float t; + if ( v > 0.0f ) { + t = Max( 0.0f, v - threshold ); + } else { + t = Min( 0.0f, v + threshold ); + } + threshed[i] = t; + } + } else { + // thresholding together is the most predictable in free-form movement, + // but you tend to slide off axis based on which side your thumb is + // on the pad + const float rawLength = raw.Length(); + const float afterThreshold = Max( 0.0f, rawLength - threshold ); + + idVec2 rawDir = raw; + rawDir.Normalize(); + + threshed = rawDir * afterThreshold; + } + + // threshold and range reduce the range of raw values, but we + // scale them back up to the full 0.0 - 1.0 range + const float rangeScale = 1.0f / ( range - threshold ); + idVec2 reScaled = threshed * rangeScale; + + const float rescaledLen = reScaled.Length(); + + // if inside the deadband area, return a solid 0,0 + if ( rescaledLen <= 0.0f ) { + return idVec2( 0.0f, 0.0f ); + } + + reScaled.Normalize(); + + // apply the acceleration + float accelerated; + + if ( shape == FUNC_EXPONENTIAL ) { + accelerated = idMath::Pow( 1.04712854805f, rescaledLen * 100.0f ) * 0.01f; + } else if ( shape == FUNC_LOGARITHMIC ) { + const float power = 2.0f; + accelerated = idMath::Pow( rescaledLen, power ); + } else { // FUNC_LINEAR + accelerated = rescaledLen; + } + + // optionally slow down for aim-assist + const float aimAssisted = accelerated * aimAssistScale; + + const float clamped = ( aimAssisted > 1.0f ) ? 1.0f : aimAssisted; + + return reScaled * clamped; +} + +/* +================= +DrawJoypadTexture + +Draws axis and threshold / range rings into an RGBA image +================= +*/ +void DrawJoypadTexture( + const int size, + byte image[], + + const idVec2 raw, + + const float threshold, + const float range, + const transferFunction_t shape, + const bool mergedThreshold ) { + +// assert( raw.x >= -1.0f && raw.x <= 1.0f && raw.y >= -1.0f && raw.y <= 1.0f ); + idVec2 clamped; + for ( int i = 0 ; i < 2 ; i++ ) { + clamped[i] = Max( -1.0f, Min( raw[i], 1.0f ) ); + } + + const int halfSize = size/2; + + // find the offsets that will give certain values for + // the rings + static const int NUM_RINGS = 5; + float ringSizes[NUM_RINGS] = {}; + float ringValue[NUM_RINGS] = { 0.0f, 0.25f, 0.5f, 0.75f, 0.99f }; + int ringNum = 0; + for ( int i = 1 ; i < size ; i++ ) { + const float v = (float)i / (size-1); + + const idVec2 mapped = JoypadFunction( + idVec2( v, 0.0f ), 1.0f, threshold, range, shape, mergedThreshold ); + if ( mapped.x > ringValue[ ringNum ] ) { + ringSizes[ ringNum ] = v * halfSize; + ringNum++; + if ( ringNum == NUM_RINGS ) { + break; + } + } + } + + memset( image, 0, size * size * 4 ); +#define PLOT(x,y) ((int *)image)[(int)(y)*size+(int)(x)]=0xffffffff +#define CPLOT(x,y) ((int *)image)[(int)(halfSize+y)*size+(int)(halfSize+x)]=0xffffffff + + int clampedX = halfSize + Min( halfSize-1, (int)(halfSize * clamped.x) ); + int clampedY = halfSize + Min( halfSize-1, (int)(halfSize * clamped.y) ); + + // draw the box edge outline and center lines + for ( int i = 0 ; i < size ; i++ ) { + PLOT( i, 0 ); + PLOT( i, size-1 ); + PLOT( 0, i ); + PLOT( size-1, i ); + PLOT( i, clampedY ); + PLOT( clampedX, i ); + } + const int iThresh = size * threshold * 0.5f; + if ( !mergedThreshold ) { + const int open = size * 0.5f - iThresh; + for ( int i = 0 ; i < open ; i++ ) { + PLOT( i, halfSize - iThresh ); + PLOT( i, halfSize + iThresh ); + PLOT( size-1-i, halfSize - iThresh ); + PLOT( size-1-i, halfSize + iThresh ); + + PLOT( halfSize - iThresh, i ); + PLOT( halfSize + iThresh, i ); + PLOT( halfSize - iThresh, size-1-i ); + PLOT( halfSize + iThresh, size-1-i ); + } + } + + // I'm not going to bother writing a proper circle drawing algorithm... + const int octantPoints = size * 2; + float rad = 0.0f; + float radStep = idMath::PI / ( 4 * octantPoints ); + for ( int point = 0 ; point < octantPoints ; point++, rad += radStep ) { + float s, c; + idMath::SinCos( rad, s, c ); + for ( int ringNum = 0 ; ringNum < NUM_RINGS ; ringNum++ ) { + const float ringSize = ringSizes[ ringNum ]; + const int ix = idMath::Floor( ringSize * c ); + const int iy = idMath::Floor( ringSize * s ); +#if 0 + if ( !mergedThreshold && ( ix < iThresh || iy < iThresh ) ) { + continue; + } +#endif + CPLOT( ix, iy ); + CPLOT( iy, ix ); + CPLOT( -ix, iy ); + CPLOT( -iy, ix ); + CPLOT( ix, -iy ); + CPLOT( iy, -ix ); + CPLOT( -ix, -iy ); + CPLOT( -iy, -ix ); + } + } + +#undef PLOT +} + +static idVec2 lastLookJoypad; + +/* +================= +DrawJoypadTexture + +Can be called to fill in a scratch texture for visualization +================= +*/ +void DrawJoypadTexture( const int size, byte image[] ) { + const float threshold = joy_deadZone.GetFloat(); + const float range = joy_range.GetFloat(); + const bool mergedThreshold = joy_mergedThreshold.GetBool(); + const transferFunction_t shape =(transferFunction_t)joy_gammaLook.GetInteger(); + + DrawJoypadTexture( size, image, lastLookJoypad, threshold, range, shape, mergedThreshold ); +} + + +/* +================= +idUsercmdGenLocal::JoystickMove2 +================= +*/ +void idUsercmdGenLocal::JoystickMove2() { + const bool invertLook = in_invertLook.GetBool(); + const float threshold = joy_deadZone.GetFloat(); + const float range = joy_range.GetFloat(); + const transferFunction_t shape =(transferFunction_t)joy_gammaLook.GetInteger(); + const bool mergedThreshold = joy_mergedThreshold.GetBool(); + const float pitchSpeed = joy_pitchSpeed.GetFloat(); + const float yawSpeed = joy_yawSpeed.GetFloat(); + + idGame * game = common->Game(); + const float aimAssist = game != NULL ? game->GetAimAssistSensitivity() : 1.0f; + + idVec2 leftRaw( joystickAxis[ AXIS_LEFT_X ], joystickAxis[ AXIS_LEFT_Y ] ); + idVec2 rightRaw( joystickAxis[ AXIS_RIGHT_X ], joystickAxis[ AXIS_RIGHT_Y ] ); + + // optional stick swap + if ( idKeyInput::GetUsercmdAction( K_JOY_STICK1_LEFT ) == UB_LOOKLEFT ) { + const idVec2 temp = leftRaw; + leftRaw = rightRaw; + rightRaw = temp; + } + + // optional invert look by inverting the right Y axis + if ( invertLook ) { + rightRaw.y = -rightRaw.y; + } + + // save for visualization + lastLookJoypad = rightRaw; + + idVec2 leftMapped = JoypadFunction( leftRaw, 1.0f, threshold, range, shape, mergedThreshold ); + idVec2 rightMapped = JoypadFunction( rightRaw, aimAssist, threshold, range, shape, mergedThreshold ); + + // because idPhysics_Player::CmdScale scales mvoement values down so that 1,1 = sqrt(2), sqrt(2), + // we need to expand our circular values out to a square + CircleToSquare( leftMapped.x, leftMapped.y ); + + // add on top of mouse / keyboard move values + cmd.forwardmove = idMath::ClampChar( cmd.forwardmove + KEY_MOVESPEED * -leftMapped.y ); + cmd.rightmove = idMath::ClampChar( cmd.rightmove + KEY_MOVESPEED * leftMapped.x ); + + viewangles[PITCH] += MS2SEC( pollTime - lastPollTime ) * rightMapped.y * pitchSpeed; + viewangles[YAW] += MS2SEC( pollTime - lastPollTime ) * -rightMapped.x * yawSpeed; + + const float triggerThreshold = joy_triggerThreshold.GetFloat(); + HandleJoystickAxis( K_JOY_TRIGGER1, joystickAxis[ AXIS_LEFT_TRIG ], triggerThreshold, true ); + HandleJoystickAxis( K_JOY_TRIGGER2, joystickAxis[ AXIS_RIGHT_TRIG ], triggerThreshold, true ); +} + +/* +============== +idUsercmdGenLocal::CmdButtons +============== +*/ +void idUsercmdGenLocal::CmdButtons() { + cmd.buttons = 0; + + // check the attack button + if ( ButtonState( UB_ATTACK ) ) { + cmd.buttons |= BUTTON_ATTACK; + } + + // check the use button + if ( ButtonState( UB_USE ) ) { + cmd.buttons |= BUTTON_USE; + } + + // check the run button + if ( toggled_run.on || ( in_alwaysRun.GetBool() && common->IsMultiplayer() ) ) { + cmd.buttons |= BUTTON_RUN; + } + + // check the zoom button + if ( toggled_zoom.on ) { + cmd.buttons |= BUTTON_ZOOM; + } + + if ( ButtonState( UB_MOVEUP ) ) { + cmd.buttons |= BUTTON_JUMP; + } + if ( toggled_crouch.on ) { + cmd.buttons |= BUTTON_CROUCH; + } +} + +/* +================ +idUsercmdGenLocal::InitCurrent + +inits the current command for this frame +================ +*/ +void idUsercmdGenLocal::InitCurrent() { + memset( &cmd, 0, sizeof( cmd ) ); + cmd.impulseSequence = impulseSequence; + cmd.impulse = impulse; + cmd.buttons |= ( in_alwaysRun.GetBool() && common->IsMultiplayer() ) ? BUTTON_RUN : 0; +} + +/* +================ +idUsercmdGenLocal::MakeCurrent + +creates the current command for this frame +================ +*/ +void idUsercmdGenLocal::MakeCurrent() { + idVec3 oldAngles = viewangles; + + if ( !Inhibited() ) { + // update toggled key states + toggled_crouch.SetKeyState( ButtonState( UB_MOVEDOWN ), in_toggleCrouch.GetBool() ); + toggled_run.SetKeyState( ButtonState( UB_SPEED ), in_toggleRun.GetBool() && common->IsMultiplayer() ); + toggled_zoom.SetKeyState( ButtonState( UB_ZOOM ), in_toggleZoom.GetBool() ); + + // get basic movement from mouse + MouseMove(); + + // get basic movement from joystick and set key bits + // must be done before CmdButtons! + if ( joy_newCode.GetBool() ) { + JoystickMove2(); + } else { + JoystickMove(); + } + + // keyboard angle adjustment + AdjustAngles(); + + // set button bits + CmdButtons(); + + // get basic movement from keyboard + KeyMove(); + + // aim assist + AimAssist(); + + // check to make sure the angles haven't wrapped + if ( viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - viewangles[PITCH] > 90 ) { + viewangles[PITCH] = oldAngles[PITCH] - 90; + } + } else { + mouseDx = 0; + mouseDy = 0; + } + + for ( int i = 0; i < 3; i++ ) { + cmd.angles[i] = ANGLE2SHORT( viewangles[i] ); + } + + cmd.mx = continuousMouseX; + cmd.my = continuousMouseY; + + impulseSequence = cmd.impulseSequence; + impulse = cmd.impulse; + +} + +/* +================ +idUsercmdGenLocal::AimAssist +================ +*/ +void idUsercmdGenLocal::AimAssist() { + // callback to the game to update the aim assist for the current device + idAngles aimAssistAngles( 0.0f, 0.0f, 0.0f ); + + idGame * game = common->Game(); + if ( game != NULL ) { + game->GetAimAssistAngles( aimAssistAngles ); + } + + viewangles[YAW] += aimAssistAngles.yaw; + viewangles[PITCH] += aimAssistAngles.pitch; + viewangles[ROLL] += aimAssistAngles.roll; +} + +//===================================================================== + + +/* +================ +idUsercmdGenLocal::CommandStringUsercmdData + +Returns the button if the command string is used by the usercmd generator. +================ +*/ +int idUsercmdGenLocal::CommandStringUsercmdData( const char *cmdString ) { + for ( userCmdString_t *ucs = userCmdStrings ; ucs->string ; ucs++ ) { + if ( idStr::Icmp( cmdString, ucs->string ) == 0 ) { + return ucs->button; + } + } + return UB_NONE; +} + +/* +================ +idUsercmdGenLocal::Init +================ +*/ +void idUsercmdGenLocal::Init() { + initialized = true; +} + +/* +================ +idUsercmdGenLocal::InitForNewMap +================ +*/ +void idUsercmdGenLocal::InitForNewMap() { + impulseSequence = 0; + impulse = 0; + + toggled_crouch.Clear(); + toggled_run.Clear(); + toggled_zoom.Clear(); + toggled_run.on = false; + + Clear(); + ClearAngles(); +} + +/* +================ +idUsercmdGenLocal::Shutdown +================ +*/ +void idUsercmdGenLocal::Shutdown() { + initialized = false; +} + +/* +================ +idUsercmdGenLocal::Clear +================ +*/ +void idUsercmdGenLocal::Clear() { + // clears all key states + memset( buttonState, 0, sizeof( buttonState ) ); + memset( keyState, false, sizeof( keyState ) ); + memset( joystickAxis, 0, sizeof( joystickAxis ) ); + + inhibitCommands = false; + + mouseDx = mouseDy = 0; + mouseButton = 0; + mouseDown = false; +} + +/* +================ +idUsercmdGenLocal::ClearAngles +================ +*/ +void idUsercmdGenLocal::ClearAngles() { + viewangles.Zero(); +} + +//====================================================================== + + +/* +=================== +idUsercmdGenLocal::Key + +Handles mouse/keyboard button actions +=================== +*/ +void idUsercmdGenLocal::Key( int keyNum, bool down ) { + + // Sanity check, sometimes we get double message :( + if ( keyState[ keyNum ] == down ) { + return; + } + keyState[ keyNum ] = down; + + int action = idKeyInput::GetUsercmdAction( keyNum ); + + if ( down ) { + buttonState[ action ]++; + if ( !Inhibited() ) { + if ( action >= UB_IMPULSE0 && action <= UB_IMPULSE31 ) { + cmd.impulse = action - UB_IMPULSE0; + cmd.impulseSequence++; + } + } + } else { + buttonState[ action ]--; + // we might have one held down across an app active transition + if ( buttonState[ action ] < 0 ) { + buttonState[ action ] = 0; + } + } +} + +/* +=================== +idUsercmdGenLocal::Mouse +=================== +*/ +void idUsercmdGenLocal::Mouse() { + int mouseEvents[MAX_MOUSE_EVENTS][2]; + + int numEvents = Sys_PollMouseInputEvents( mouseEvents ); + + // Study each of the buffer elements and process them. + for ( int i = 0; i < numEvents; i++ ) { + int action = mouseEvents[i][0]; + int value = mouseEvents[i][1]; + switch ( action ) { + case M_ACTION1: + case M_ACTION2: + case M_ACTION3: + case M_ACTION4: + case M_ACTION5: + case M_ACTION6: + case M_ACTION7: + case M_ACTION8: + mouseButton = K_MOUSE1 + ( action - M_ACTION1 ); + mouseDown = ( value != 0 ); + Key( mouseButton, mouseDown ); + break; + case M_DELTAX: + mouseDx += value; + continuousMouseX += value; + break; + case M_DELTAY: + mouseDy += value; + continuousMouseY += value; + break; + case M_DELTAZ: // mouse wheel, may have multiple clicks + { + int key = value < 0 ? K_MWHEELDOWN : K_MWHEELUP; + value = abs( value ); + while( value-- > 0 ) { + Key( key, true ); + Key( key, false ); + mouseButton = key; + mouseDown = true; + } + } + break; + default: // some other undefined button + break; + } + } +} + +/* +=============== +idUsercmdGenLocal::Keyboard +=============== +*/ +void idUsercmdGenLocal::Keyboard() { + + int numEvents = Sys_PollKeyboardInputEvents(); + + // Study each of the buffer elements and process them. + for ( int i = 0; i < numEvents; i++ ) { + int key; + bool state; + if ( Sys_ReturnKeyboardInputEvent( i, key, state ) ) { + Key( key, state ); + } + } + + Sys_EndKeyboardInputEvents(); +} + +/* +=============== +idUsercmdGenLocal::Joystick +=============== +*/ +void idUsercmdGenLocal::Joystick( int deviceNum ) { + int numEvents = Sys_PollJoystickInputEvents( deviceNum ); + + // Study each of the buffer elements and process them. + for ( int i = 0; i < numEvents; i++ ) { + int action; + int value; + if ( Sys_ReturnJoystickInputEvent( i, action, value ) ) { + if ( action >= J_ACTION1 && action <= J_ACTION_MAX ) { + int joyButton = K_JOY1 + ( action - J_ACTION1 ); + Key( joyButton, ( value != 0 ) ); + } else if ( ( action >= J_AXIS_MIN ) && ( action <= J_AXIS_MAX ) ) { + joystickAxis[ action - J_AXIS_MIN ] = static_cast( value ) / 32767.0f; + } else if ( action >= J_DPAD_UP && action <= J_DPAD_RIGHT ) { + int joyButton = K_JOY_DPAD_UP + ( action - J_DPAD_UP ); + Key( joyButton, ( value != 0 ) ); + } else { + assert( !"Unknown joystick event" ); + } + } + } + + Sys_EndJoystickInputEvents(); +} + +/* +================ +idUsercmdGenLocal::MouseState +================ +*/ +void idUsercmdGenLocal::MouseState( int *x, int *y, int *button, bool *down ) { + *x = continuousMouseX; + *y = continuousMouseY; + *button = mouseButton; + *down = mouseDown; +} + +/* +================ +idUsercmdGenLocal::BuildCurrentUsercmd +================ +*/ +void idUsercmdGenLocal::BuildCurrentUsercmd( int deviceNum ) { + + pollTime = Sys_Milliseconds(); + if ( pollTime - lastPollTime > 100 ) { + lastPollTime = pollTime - 100; + } + + // initialize current usercmd + InitCurrent(); + + // process the system mouse events + Mouse(); + + // process the system keyboard events + Keyboard(); + + // process the system joystick events + if ( deviceNum >= 0 && in_useJoystick.GetBool() ) { + Joystick( deviceNum ); + } + + // create the usercmd + MakeCurrent(); + + lastPollTime = pollTime; +} diff --git a/neo/framework/UsercmdGen.h b/neo/framework/UsercmdGen.h new file mode 100644 index 00000000..f0c30dd6 --- /dev/null +++ b/neo/framework/UsercmdGen.h @@ -0,0 +1,400 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __USERCMDGEN_H__ +#define __USERCMDGEN_H__ + +#include "../sys/sys_session.h" + +/* +=============================================================================== + + Samples a set of user commands from player input. + +=============================================================================== +*/ + +// usercmd_t->button bits +const int BUTTON_ATTACK = BIT(0); +const int BUTTON_RUN = BIT(1); +const int BUTTON_ZOOM = BIT(2); +const int BUTTON_SCORES = BIT(3); +const int BUTTON_USE = BIT(4); +const int BUTTON_JUMP = BIT(5); +const int BUTTON_CROUCH = BIT(6); +const int BUTTON_CHATTING = BIT(7); + +// usercmd_t->impulse commands +const int IMPULSE_0 = 0; // weap 0 +const int IMPULSE_1 = 1; // weap 1 +const int IMPULSE_2 = 2; // weap 2 +const int IMPULSE_3 = 3; // weap 3 +const int IMPULSE_4 = 4; // weap 4 +const int IMPULSE_5 = 5; // weap 5 +const int IMPULSE_6 = 6; // weap 6 +const int IMPULSE_7 = 7; // weap 7 +const int IMPULSE_8 = 8; // weap 8 +const int IMPULSE_9 = 9; // weap 9 +const int IMPULSE_10 = 10; // weap 10 +const int IMPULSE_11 = 11; // weap 11 +const int IMPULSE_12 = 12; // weap 12 +const int IMPULSE_13 = 13; // weap reload +const int IMPULSE_14 = 14; // weap next +const int IMPULSE_15 = 15; // weap prev +const int IMPULSE_16 = 16; // toggle flashlight on/off +const int IMPULSE_18 = 18; // center view +const int IMPULSE_19 = 19; // show PDA/SCORES +const int IMPULSE_22 = 22; // spectate +const int IMPULSE_25 = 25; // Envirosuit light +const int IMPULSE_27 = 27; // Chainsaw +const int IMPULSE_28 = 28; // quick 0 +const int IMPULSE_29 = 29; // quick 1 +const int IMPULSE_30 = 30; // quick 2 +const int IMPULSE_31 = 31; // quick 3 + +class usercmd_t { +public: + usercmd_t() : + forwardmove(), + rightmove(), + buttons(), + clientGameMilliseconds( 0 ), + serverGameMilliseconds( 0 ), + fireCount( 0 ), + mx(), + my(), + impulse(), + impulseSequence(), + pos( 0.0f, 0.0f, 0.0f ), + speedSquared( 0.0f ) + { + angles[0] = 0; + angles[1] = 0; + angles[2] = 0; + } + + // Syncronized + short angles[3]; // view angles + signed char forwardmove; // forward/backward movement + signed char rightmove; // left/right movement + byte buttons; // buttons + int clientGameMilliseconds; // time this usercmd was sent from the client + int serverGameMilliseconds; // interpolated server time this was applied on + uint16 fireCount; // number of times we've fired + + // Not syncronized + byte impulse; // impulse command + byte impulseSequence; // incremented every time there's a new impulse + + short mx; // mouse delta x + short my; // mouse delta y + + // Clients are authoritative on their positions + idVec3 pos; + float speedSquared; + +public: + void Serialize( class idSerializer & s, const usercmd_t & base ); + void ByteSwap(); // on big endian systems, byte swap the shorts and ints + bool operator==( const usercmd_t &rhs ) const; +}; + +typedef enum { + INHIBIT_SESSION = 0, + INHIBIT_ASYNC +} inhibit_t; + +typedef enum { + UB_NONE, + + UB_MOVEUP, + UB_MOVEDOWN, + UB_LOOKLEFT, + UB_LOOKRIGHT, + UB_MOVEFORWARD, + UB_MOVEBACK, + UB_LOOKUP, + UB_LOOKDOWN, + UB_MOVELEFT, + UB_MOVERIGHT, + + UB_ATTACK, + UB_SPEED, + UB_ZOOM, + UB_SHOWSCORES, + UB_USE, + + UB_IMPULSE0, + UB_IMPULSE1, + UB_IMPULSE2, + UB_IMPULSE3, + UB_IMPULSE4, + UB_IMPULSE5, + UB_IMPULSE6, + UB_IMPULSE7, + UB_IMPULSE8, + UB_IMPULSE9, + UB_IMPULSE10, + UB_IMPULSE11, + UB_IMPULSE12, + UB_IMPULSE13, + UB_IMPULSE14, + UB_IMPULSE15, + UB_IMPULSE16, + UB_IMPULSE17, + UB_IMPULSE18, + UB_IMPULSE19, + UB_IMPULSE20, + UB_IMPULSE21, + UB_IMPULSE22, + UB_IMPULSE23, + UB_IMPULSE24, + UB_IMPULSE25, + UB_IMPULSE26, + UB_IMPULSE27, + UB_IMPULSE28, + UB_IMPULSE29, + UB_IMPULSE30, + UB_IMPULSE31, + + UB_MAX_BUTTONS +} usercmdButton_t; + +typedef struct { + const char *string; + usercmdButton_t button; +} userCmdString_t; + +class idUsercmdGen { +public: + virtual ~idUsercmdGen() {} + + // Sets up all the cvars and console commands. + virtual void Init() = 0; + + // Prepares for a new map. + virtual void InitForNewMap() = 0; + + // Shut down. + virtual void Shutdown() = 0; + + // Clears all key states and face straight. + virtual void Clear() = 0; + + // Clears view angles. + virtual void ClearAngles() = 0; + + // When the console is down or the menu is up, only emit default usercmd, so the player isn't moving around. + // Each subsystem (session and game) may want an inhibit will OR the requests. + virtual void InhibitUsercmd( inhibit_t subsystem, bool inhibit ) = 0; + + // Set a value that can safely be referenced by UsercmdInterrupt() for each key binding. + virtual int CommandStringUsercmdData( const char *cmdString ) = 0; + + // Continuously modified, never reset. For full screen guis. + virtual void MouseState( int *x, int *y, int *button, bool *down ) = 0; + + // Directly sample a button. + virtual int ButtonState( int key ) = 0; + + // Directly sample a keystate. + virtual int KeyState( int key ) = 0; + + // called at vsync time + virtual void BuildCurrentUsercmd( int deviceNum ) = 0; + + // return the current usercmd + virtual usercmd_t GetCurrentUsercmd() = 0; +}; + +extern idUsercmdGen *usercmdGen; +extern userCmdString_t userCmdStrings[]; + +/* +================================================ +idUserCmdMgr +================================================ +*/ +class idUserCmdMgr { +public: + idUserCmdMgr() { + SetDefaults(); + } + + void SetDefaults() { + for ( int i = 0; i < cmdBuffer.Num(); ++i ) { + cmdBuffer[i].Zero(); + } + writeFrame.Zero(); + readFrame.Memset( -1 ); + } + + // Set to 128 for now + // Temp fix for usercmds overflowing Correct fix is to process usercmds as they come in (like q3), rather then buffer them up. + static const int USERCMD_BUFFER_SIZE = 128; + + //usercmd_t cmdBuffer[ USERCMD_BUFFER_SIZE ][ MAX_PLAYERS ]; + id2DArray< usercmd_t, USERCMD_BUFFER_SIZE, MAX_PLAYERS >::type cmdBuffer; + idArray< int, MAX_PLAYERS > writeFrame; //"where we write to next" + idArray< int, MAX_PLAYERS > readFrame; //"the last frame we read" + + void PutUserCmdForPlayer( int playerIndex, const usercmd_t & cmd ) { + cmdBuffer[ writeFrame[ playerIndex ] % USERCMD_BUFFER_SIZE ][ playerIndex ] = cmd; + if ( writeFrame[ playerIndex ] - readFrame[ playerIndex ] + 1 > USERCMD_BUFFER_SIZE ) { + readFrame[ playerIndex ] = writeFrame[ playerIndex ] - USERCMD_BUFFER_SIZE / 2; // Set to middle of buffer as a temp fix until we can catch the client up correctly + idLib::Printf( "PutUserCmdForPlayer: buffer overflow.\n" ); + } + writeFrame[ playerIndex ]++; + } + + void ResetPlayer( int playerIndex ) { + for ( int i = 0; i < USERCMD_BUFFER_SIZE; i++ ) { + memset( &cmdBuffer[i][playerIndex], 0, sizeof( usercmd_t ) ); + } + writeFrame[ playerIndex ] = 0; + readFrame[ playerIndex ] = -1; + } + + bool HasUserCmdForPlayer( int playerIndex, int buffer=0 ) const { + // return true if the last frame we read from (+ buffer) is < the last frame we wrote to + // (remember writeFrame is where we write to *next*. readFrame is where we last read from last) + bool hasCmd = ( readFrame[ playerIndex ] + buffer < writeFrame[playerIndex] - 1 ); + return hasCmd; + } + + bool HasUserCmdForClientTimeBuffer( int playerIndex, int millisecondBuffer ) { + // return true if there is at least one command in addition to enough + // commands to cover the buffer. + if ( millisecondBuffer == 0 ) { + return HasUserCmdForPlayer( playerIndex ); + } + + if ( GetNumUnreadFrames( playerIndex ) < 2 ) { + return false; + } + + const int index = readFrame[ playerIndex ] + 1; + const usercmd_t & firstCmd = cmdBuffer[ index % USERCMD_BUFFER_SIZE ][ playerIndex ]; + const usercmd_t & lastCmd = NewestUserCmdForPlayer( playerIndex ); + + const int timeDelta = lastCmd.clientGameMilliseconds - firstCmd.clientGameMilliseconds; + + const bool isTimeGreaterThanBuffer = timeDelta > millisecondBuffer; + + return isTimeGreaterThanBuffer; + } + + const usercmd_t & NewestUserCmdForPlayer( int playerIndex ) { + int index = Max( writeFrame[ playerIndex ] - 1, 0 ); + return cmdBuffer[ index % USERCMD_BUFFER_SIZE ][ playerIndex ]; + } + + const usercmd_t & GetUserCmdForPlayer( int playerIndex ) { + //Get the next cmd we should process (not necessarily the newest) + //Note we may have multiple reads for every write . + //We want to: + // A) never skip over a cmd (unless we call MakeReadPtrCurrentForPlayer() ). + // B) never get ahead of the writeFrame + + //try to increment before reading (without this we may read the same input twice + //and be a frame behind our writes in the case of) + if ( readFrame[ playerIndex ] < writeFrame[ playerIndex ] - 1 ) { + readFrame[ playerIndex ]++; + } + + //grab the next command in the readFrame buffer + int index = readFrame[ playerIndex ]; + usercmd_t & result = cmdBuffer[ index % USERCMD_BUFFER_SIZE ][ playerIndex ]; + return result; + } + + int GetNextUserCmdClientTime( int playerIndex ) const { + if ( !HasUserCmdForPlayer( playerIndex ) ) { + return 0; + } + + const int index = readFrame[ playerIndex ] + 1; + const usercmd_t & cmd = cmdBuffer[ index % USERCMD_BUFFER_SIZE ][ playerIndex ]; + return cmd.clientGameMilliseconds; + } + + // Hack to let the player inject his position into the correct usercmd. + usercmd_t & GetWritableUserCmdForPlayer( int playerIndex ) { + //Get the next cmd we should process (not necessarily the newest) + //Note we may have multiple reads for every write . + //We want to: + // A) never skip over a cmd (unless we call MakeReadPtrCurrentForPlayer() ). + // B) never get ahead of the writeFrame + + //try to increment before reading (without this we may read the same input twice + //and be a frame behind our writes in the case of) + if ( readFrame[ playerIndex ] < writeFrame[ playerIndex ] - 1 ) { + readFrame[ playerIndex ]++; + } + + //grab the next command in the readFrame buffer + int index = readFrame[ playerIndex ]; + usercmd_t & result = cmdBuffer[ index % USERCMD_BUFFER_SIZE ][ playerIndex ]; + return result; + } + + void MakeReadPtrCurrentForPlayer( int playerIndex ) { + //forces us to the head of our read buffer. As if we have processed every cmd available to us and now HasUserCmdForPlayer() returns FALSE + //Note we do -1 to point us to the last written cmd. + //If a read before the next write, you will get the last write. (not garbage) + //If a write is made before the next read, you *will* get the new write ( b/c GetUserCmdForPlayer pre increments) + + //After calling this, HasUserCmdForPlayer() will return FALSE; + readFrame[ playerIndex ] = writeFrame[ playerIndex ] - 1; + } + + void SkipBufferedCmdsForPlayer( int playerIndex ) { + // Similar to MakeReadPtrCurrentForPlayer, except: + // -After calling this, HasUserCmdForPlayer() will return TRUE iff there was >= 1 fresh cmd in the buffer + // Also, If there are no fresh frames, we wont roll the readFrame back + readFrame[ playerIndex ] = Max( readFrame[ playerIndex ], writeFrame[ playerIndex ] - 2 ); + } + + int GetNumUnreadFrames( int playerIndex ) { + return (writeFrame[ playerIndex ] - 1) - readFrame[ playerIndex ]; + } + + int GetPlayerCmds( int user, usercmd_t ** buffer, const int bufferSize ) { + // Fallback to getting cmds from the userCmdMgr + int start = Max( writeFrame[user] - Min( bufferSize, USERCMD_BUFFER_SIZE ), 0 ); + int numCmds = writeFrame[user] - start; + + for ( int i = 0; i < numCmds; i++ ) { + int index = ( start + i ) % USERCMD_BUFFER_SIZE; + buffer[i] = &cmdBuffer[ index ][ user ]; + } + return numCmds; + } +}; + +#endif /* !__USERCMDGEN_H__ */ diff --git a/neo/framework/Zip.cpp b/neo/framework/Zip.cpp new file mode 100644 index 00000000..1c9a9fa1 --- /dev/null +++ b/neo/framework/Zip.cpp @@ -0,0 +1,2054 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +================================================================================================ +Contains external code for building ZipFiles. +================================================================================================ +*/ + +#include "Zip.h" +#include "Unzip.h" + +#undef STDC +#include "zlib/zutil.h" + +/* zip.c -- IO on .zip files using zlib + Version 1.01e, February 12th, 2005 + + 27 Dec 2004 Rolf Kalbermatter + Modification to zipOpen2 to support globalComment retrieval. + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow creates .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + For uncompress .zip file, look at unzip.h + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.html for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + For more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip + +*/ + +#ifndef Z_MAXFILENAMEINZIP +#define Z_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +#define ALLOC( size ) ( Mem_Alloc( size, TAG_ZIP ) ) +#endif +#ifndef TRYFREE +#define TRYFREE( p ) { if (p) { Mem_Free(p); } } +#endif + +/* +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) +*/ + +const char zip_copyright[] = + " zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +#define LOCALHEADERMAGIC (0x04034b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) + +#define FLAG_LOCALHEADER_OFFSET (0x06) +#define CRC_LOCALHEADER_OFFSET (0x0e) + +#define SIZECENTRALHEADER (0x2e) /* 46 */ + +#define DEFAULT_COMPRESSION_LEVEL (5) /* 1 == Compress faster, 9 == Compress better */ +#define DEFAULT_WRITEBUFFERSIZE (16384) + +#ifndef NOCRYPT +#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED +#include "crypt.h" +#endif + +idCVar zip_verbosity( "zip_verbosity", "0", CVAR_BOOL, "1 = verbose logging when building zip files" ); + +/* +======================== +allocate_new_datablock +======================== +*/ +linkedlist_datablock_internal* allocate_new_datablock() { + linkedlist_datablock_internal* ldi = NULL; + ldi = (linkedlist_datablock_internal*) ALLOC( sizeof( linkedlist_datablock_internal ) ); + if ( ldi != NULL ) { + ldi->next_datablock = NULL; + ldi->filled_in_this_block = 0; + ldi->avail_in_this_block = SIZEDATA_INDATABLOCK; + } + return ldi; +} + +/* +======================== +free_datablock +======================== +*/ +void free_datablock( linkedlist_datablock_internal* ldi ) { + while ( ldi != NULL ) { + linkedlist_datablock_internal* ldinext = ldi->next_datablock; + TRYFREE( ldi ); + ldi = ldinext; + } +} + +/* +======================== +init_linkedlist +======================== +*/ +void init_linkedlist( linkedlist_data* ll ) { + ll->first_block = ll->last_block = NULL; +} + +/* +======================== +free_linkedlist +======================== +*/ +void free_linkedlist( linkedlist_data* ll ) { + free_datablock( ll->first_block ); + ll->first_block = ll->last_block = NULL; +} + +/* +======================== +add_data_in_datablock +======================== +*/ +int add_data_in_datablock( linkedlist_data* ll, const void* buf, unsigned long len ) { + linkedlist_datablock_internal* ldi; + const unsigned char* from_copy; + + if ( ll == NULL ) { + return ZIP_INTERNALERROR; + } + + if ( ll->last_block == NULL ) { + ll->first_block = ll->last_block = allocate_new_datablock(); + if ( ll->first_block == NULL ) { + return ZIP_INTERNALERROR; + } + } + + ldi = ll->last_block; + from_copy = (unsigned char*)buf; + + while ( len > 0 ) { + unsigned int copy_this; + unsigned char* to_copy; + + if ( ldi->avail_in_this_block == 0 ) { + ldi->next_datablock = allocate_new_datablock(); + if ( ldi->next_datablock == NULL ) { + return ZIP_INTERNALERROR; + } + ldi = ldi->next_datablock; + ll->last_block = ldi; + } + + if ( ldi->avail_in_this_block < len ) { + copy_this = (unsigned int)ldi->avail_in_this_block; + } else { + copy_this = (unsigned int)len; + } + + to_copy = &( ldi->data[ ldi->filled_in_this_block ] ); + + for ( unsigned int i = 0; i < copy_this; i++ ) { + *( to_copy + i ) =* ( from_copy + i ); + } + + ldi->filled_in_this_block += copy_this; + ldi->avail_in_this_block -= copy_this; + from_copy += copy_this; + len -= copy_this; + } + + return ZIP_OK; +} + +#ifndef NO_ADDFILEINEXISTINGZIP + +/* +======================== +ziplocal_putValue + +Inputs a long in LSB order to the given file +nbByte == 1, 2 or 4 (byte, short or long) +======================== +*/ +int ziplocal_putValue( idFile* filestream, unsigned long x, int nbByte ) { + unsigned char buf[4]; + for ( int n = 0; n < nbByte; n++ ) { + buf[n] = (unsigned char)( x & 0xff ); + x >>= 8; + } + if ( x != 0 ) { + /* data overflow - hack for ZIP64 (X Roche) */ + for ( int n = 0; n < nbByte; n++ ) { + buf[n] = 0xff; + } + } + if ( filestream->Write( buf, nbByte ) != nbByte ) { + return ZIP_ERRNO; + } else { + return ZIP_OK; + } +} + +/* +======================== +ziplocal_putValue_inmemory +======================== +*/ +void ziplocal_putValue_inmemory( void* dest, unsigned long x, int nbByte ){ + unsigned char* buf = (unsigned char*)dest; + for ( int n = 0; n < nbByte; n++ ) { + buf[n] = (unsigned char)( x & 0xff ); + x >>= 8; + } + + if ( x != 0 ) { + /* data overflow - hack for ZIP64 */ + for ( int n = 0; n < nbByte; n++ ) { + buf[n] = 0xff; + } + } +} + +/* +======================== +ziplocal_TmzDateToDosDate +======================== +*/ +unsigned long ziplocal_TmzDateToDosDate( const tm_zip* ptm, unsigned long dosDate ) { + unsigned long year = (unsigned long)ptm->tm_year; + if ( year > 1980 ) { + year-=1980; + } else if ( year > 80 ) { + year -= 80; + } + return (unsigned long)( ( ( ptm->tm_mday ) + ( 32 * ( ptm->tm_mon + 1 ) ) + ( 512 * year ) ) << 16 ) | + ( ( ptm->tm_sec / 2 ) + ( 32 * ptm->tm_min ) + ( 2048 * (unsigned long)ptm->tm_hour ) ); +} + +/* +======================== +ziplocal_getByte +======================== +*/ +int ziplocal_getByte( idFile* filestream, int *pi ) { + unsigned char c; + int err = (int)filestream->Read( &c, 1 ); + if ( err == 1 ) { + *pi = (int)c; + return ZIP_OK; + } else { + return ZIP_ERRNO; + } +/* + unsigned char c; + int err = (int)ZREAD( filestream, &c, 1 ); + + if ( err == 1 ) { + *pi = (int)c; + return ZIP_OK; + } else { + if ( ZERROR( filestream ) ) { + return ZIP_ERRNO; + } else { + return ZIP_EOF; + } + } +*/ +} + +/* +======================== +ziplocal_getShort + +Reads a long in LSB order from the given gz_stream. Sets +======================== +*/ +int ziplocal_getShort( idFile* filestream, unsigned long *pX ) { + short v; + if ( filestream->Read( &v, sizeof( v ) ) == sizeof( v ) ) { + idSwap::Little( v ); + *pX = v; + return ZIP_OK; + } else { + return ZIP_ERRNO; + } +/* + unsigned long x ; + int i; + int err; + + err = ziplocal_getByte( filestream, &i ); + x = (unsigned long)i; + + if ( err == ZIP_OK ) { + err = ziplocal_getByte( filestream, &i ); + } + x += ( (unsigned long)i ) << 8; + + if ( err == ZIP_OK ) { + *pX = x; + } else { + *pX = 0; + } + return err; +*/ +} + +/* +======================== +ziplocal_getLong +======================== +*/ +int ziplocal_getLong( idFile* filestream, unsigned long *pX ) { + int v; + if ( filestream->Read( &v, sizeof( v ) ) == sizeof( v ) ) { + idSwap::Little( v ); + *pX = v; + return ZIP_OK; + } else { + return ZIP_ERRNO; + } +/* + unsigned long x ; + int i; + int err; + + err = ziplocal_getByte( filestream, &i ); + x = (unsigned long)i; + + if ( err == ZIP_OK ) { + err = ziplocal_getByte( filestream, &i ); + } + x += ( (unsigned long)i ) << 8; + + if ( err == ZIP_OK ) { + err = ziplocal_getByte( filestream, &i ); + } + x += ( (unsigned long)i) << 16; + + if ( err == ZIP_OK ) { + err = ziplocal_getByte( filestream, &i ); + } + x += ( (unsigned long)i ) << 24; + + if ( err == ZIP_OK ) { + *pX = x; + } else { + *pX = 0; + } + return err; +*/ +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* +======================== +ziplocal_SearchCentralDir + +Locate the Central directory of a zipfile (at the end, just before +the global comment) +======================== +*/ +unsigned long ziplocal_SearchCentralDir( idFile* filestream ) { + unsigned char* buf; + unsigned long uSizeFile; + unsigned long uBackRead; + unsigned long uMaxBack = 0xffff; /* maximum size of global comment */ + unsigned long uPosFound = 0; + + if ( filestream->Seek( 0, FS_SEEK_END ) != 0 ) { + return 0; + } + + uSizeFile = (unsigned long)filestream->Tell(); + + if ( uMaxBack > uSizeFile ) { + uMaxBack = uSizeFile; + } + + buf = (unsigned char*)ALLOC( BUFREADCOMMENT + 4 ); + if ( buf == NULL ) { + return 0; + } + + uBackRead = 4; + while ( uBackRead < uMaxBack ) { + unsigned long uReadSize,uReadPos; + if ( uBackRead + BUFREADCOMMENT > uMaxBack ) { + uBackRead = uMaxBack; + } else { + uBackRead += BUFREADCOMMENT; + } + uReadPos = uSizeFile - uBackRead ; + + uReadSize = ( ( BUFREADCOMMENT + 4 ) < ( uSizeFile - uReadPos ) ) ? ( BUFREADCOMMENT + 4 ) : ( uSizeFile - uReadPos ); + if ( filestream->Seek( uReadPos, FS_SEEK_SET ) != 0 ) { + break; + } + + if ( filestream->Read( buf, uReadSize ) != ( int )uReadSize ) { + break; + } + + for ( int i = (int)uReadSize - 3; ( i -- ) > 0; ) { + if ( ( ( *( buf + i ) ) == 0x50 ) && ( ( *( buf + i + 1 ) ) == 0x4b ) && ( ( *( buf + i + 2 ) ) == 0x05 ) && ( ( *( buf + i + 3 ) ) == 0x06 ) ) { + uPosFound = uReadPos + i; + break; + } + } + + if ( uPosFound != 0 ) { + break; + } + } + TRYFREE( buf ); + return uPosFound; +} +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + +/* +======================== +zipOpen2 +======================== +*/ +zipFile zipOpen2( const char *pathname, int append, char* globalcomment ) { + zip_internal ziinit; + zip_internal* zi; + int err = ZIP_OK; + + ziinit.filestream = fileSystem->OpenExplicitFileWrite( pathname ); +/* + ziinit.filestream = ZOPEN( pathname, ( append == APPEND_STATUS_CREATE ) ? + ( ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE ) : + ( ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING ) ); +*/ + if ( ziinit.filestream == NULL ) { + return NULL; + } + ziinit.begin_pos = (unsigned long)ziinit.filestream->Tell(); + ziinit.in_opened_file_inzip = 0; + ziinit.ci.stream_initialised = 0; + ziinit.number_entry = 0; + ziinit.add_position_when_writting_offset = 0; + init_linkedlist( &(ziinit.central_dir) ); + + zi = (zip_internal*)ALLOC( sizeof( zip_internal ) ); + if ( zi == NULL ) { + delete ziinit.filestream; + ziinit.filestream = NULL; + return NULL; + } + + /* now we add file in a zipfile */ +#ifndef NO_ADDFILEINEXISTINGZIP + ziinit.globalcomment = NULL; + if ( append == APPEND_STATUS_ADDINZIP ) { + unsigned long byte_before_the_zipfile; // byte before the zipfile, ( > 0 for sfx ) + unsigned long size_central_dir; // size of the central directory + unsigned long offset_central_dir; // offset of start of central directory + unsigned long central_pos,uL; + unsigned long number_disk; // number of the current dist, used for spaning ZIP, unsupported, always 0 + unsigned long number_disk_with_CD; // number the the disk with central dir, used for spaning ZIP, unsupported, always 0 + unsigned long number_entry; + unsigned long number_entry_CD; // total number of entries in the central dir ( same than number_entry on nospan ) + unsigned long size_comment; + + central_pos = ziplocal_SearchCentralDir( ziinit.filestream ); + if ( central_pos == 0 ) { + err = ZIP_ERRNO; + } + + if ( ziinit.filestream->Seek( central_pos, FS_SEEK_SET ) != 0 ) { + err = ZIP_ERRNO; + } + + /* the signature, already checked */ + if ( ziplocal_getLong( ziinit.filestream, &uL ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + /* number of this disk */ + if ( ziplocal_getShort( ziinit.filestream, &number_disk ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + /* number of the disk with the start of the central directory */ + if ( ziplocal_getShort( ziinit.filestream, &number_disk_with_CD ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + /* total number of entries in the central dir on this disk */ + if ( ziplocal_getShort( ziinit.filestream, &number_entry ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + /* total number of entries in the central dir */ + if ( ziplocal_getShort( ziinit.filestream, &number_entry_CD ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + if ( ( number_entry_CD != number_entry ) || ( number_disk_with_CD != 0 ) || ( number_disk != 0 ) ) { + err = ZIP_BADZIPFILE; + } + + /* size of the central directory */ + if ( ziplocal_getLong( ziinit.filestream, &size_central_dir ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + /* offset of start of central directory with respect to the starting disk number */ + if ( ziplocal_getLong( ziinit.filestream, &offset_central_dir ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + /* zipfile global comment length */ + if ( ziplocal_getShort( ziinit.filestream, &size_comment ) != ZIP_OK ) { + err = ZIP_ERRNO; + } + + if ( ( central_pos < ( offset_central_dir + size_central_dir ) ) && ( err == ZIP_OK ) ) { + err = ZIP_BADZIPFILE; + } + + if ( err != ZIP_OK ) { + delete ziinit.filestream; + ziinit.filestream = NULL; + return NULL; + } + + if ( size_comment > 0 ) { + ziinit.globalcomment = (char*)ALLOC( size_comment + 1 ); + if ( ziinit.globalcomment ) { + size_comment = (unsigned long)ziinit.filestream->Read( ziinit.globalcomment, size_comment ); + ziinit.globalcomment[size_comment] = 0; + } + } + + byte_before_the_zipfile = central_pos - ( offset_central_dir + size_central_dir ); + ziinit.add_position_when_writting_offset = byte_before_the_zipfile; + { + unsigned long size_central_dir_to_read = size_central_dir; + size_t buf_size = SIZEDATA_INDATABLOCK; + void* buf_read = (void*)ALLOC( buf_size ); + if ( ziinit.filestream->Seek( offset_central_dir + byte_before_the_zipfile, FS_SEEK_SET ) != 0 ) { + err = ZIP_ERRNO; + } + + while ( ( size_central_dir_to_read > 0 ) && ( err == ZIP_OK ) ) { + unsigned long read_this = SIZEDATA_INDATABLOCK; + if ( read_this > size_central_dir_to_read ) { + read_this = size_central_dir_to_read; + } + if ( ziinit.filestream->Read( buf_read, read_this ) != ( int )read_this ) { + err = ZIP_ERRNO; + } + if ( err == ZIP_OK ) { + err = add_data_in_datablock( &ziinit.central_dir, buf_read, (unsigned long)read_this ); + } + size_central_dir_to_read -= read_this; + } + TRYFREE( buf_read ); + } + ziinit.begin_pos = byte_before_the_zipfile; + ziinit.number_entry = number_entry_CD; + + if ( ziinit.filestream->Seek( offset_central_dir + byte_before_the_zipfile, FS_SEEK_SET ) != 0 ) { + err = ZIP_ERRNO; + } + } + + if ( globalcomment ) { + /// ?? + globalcomment = ziinit.globalcomment; + } +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + + if ( err != ZIP_OK ) { +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE( ziinit.globalcomment ); +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + TRYFREE( zi ); + return NULL; + } else { + *zi = ziinit; + return (zipFile)zi; + } +} + +/* +======================== +zipOpen +======================== +*/ +zipFile zipOpen( const char *pathname, int append ) { + return zipOpen2( pathname, append, NULL ); +} + +/* +======================== +zipOpenNewFileInZip3 +======================== +*/ +int zipOpenNewFileInZip3( zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, unsigned int size_extrafield_local, const void* extrafield_global, + unsigned int size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, unsigned long crcForCrypting ) { + unsigned int size_filename; + unsigned int size_comment; + int err = ZIP_OK; + +#ifdef NOCRYPT + if ( password != NULL ) { + return ZIP_PARAMERROR; + } +#endif + + if ( file == NULL ) { + return ZIP_PARAMERROR; + } + if ( ( method != 0 ) && ( method != Z_DEFLATED ) ) { + return ZIP_PARAMERROR; + } + + zip_internal* zi = (zip_internal*)file; + + if ( zi->in_opened_file_inzip == 1 ) { + err = zipCloseFileInZip( file ); + if ( err != ZIP_OK ) { + return err; + } + } + + if ( filename == NULL ) { + filename = "-"; + } + + if ( comment == NULL ) { + size_comment = 0; + } else { + size_comment = (unsigned int)idStr::Length( comment ); + } + + size_filename = (unsigned int)idStr::Length( filename ); + + if ( zipfi == NULL ) { + zi->ci.dosDate = 0; + } else { + if ( zipfi->dosDate != 0 ) { + zi->ci.dosDate = zipfi->dosDate; + } else { + zi->ci.dosDate = ziplocal_TmzDateToDosDate( &zipfi->tmz_date, zipfi->dosDate ); + } + } + + zi->ci.flag = 0; + if ( ( level == 8 ) || ( level == 9 ) ) { + zi->ci.flag |= 2; + } + if ( ( level == 2 ) ) { + zi->ci.flag |= 4; + } + if ( ( level == 1 ) ) { + zi->ci.flag |= 6; + } + if ( password != NULL ) { + zi->ci.flag |= 1; + } + + zi->ci.crc32 = 0; + zi->ci.method = method; + zi->ci.encrypt = 0; + zi->ci.stream_initialised = 0; + zi->ci.pos_in_buffered_data = 0; + zi->ci.raw = raw; + zi->ci.pos_local_header = (unsigned long)zi->filestream->Tell(); + zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment; + zi->ci.central_header = (char*)ALLOC( (unsigned int)zi->ci.size_centralheader ); + + ziplocal_putValue_inmemory( zi->ci.central_header, (unsigned long)CENTRALHEADERMAGIC, 4 ); + /* version info */ + ziplocal_putValue_inmemory( zi->ci.central_header + 4, (unsigned long)0, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 6, (unsigned long)20, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 8, (unsigned long)zi->ci.flag, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 10, (unsigned long)zi->ci.method, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 12, (unsigned long)zi->ci.dosDate, 4 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 16, (unsigned long)0, 4 ); /*crc*/ + ziplocal_putValue_inmemory( zi->ci.central_header + 20, (unsigned long)0, 4 ); /*compr size*/ + ziplocal_putValue_inmemory( zi->ci.central_header + 24, (unsigned long)0, 4 ); /*uncompr size*/ + ziplocal_putValue_inmemory( zi->ci.central_header + 28, (unsigned long)size_filename, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 30, (unsigned long)size_extrafield_global, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 32, (unsigned long)size_comment, 2 ); + ziplocal_putValue_inmemory( zi->ci.central_header + 34, (unsigned long)0, 2 ); /*disk nm start*/ + + if ( zipfi == NULL ) { + ziplocal_putValue_inmemory( zi->ci.central_header + 36, (unsigned long)0, 2 ); + } else { + ziplocal_putValue_inmemory( zi->ci.central_header + 36, (unsigned long)zipfi->internal_fa, 2 ); + } + + if ( zipfi == NULL ) { + ziplocal_putValue_inmemory( zi->ci.central_header + 38,(unsigned long)0, 4); + } else { + ziplocal_putValue_inmemory( zi->ci.central_header + 38,(unsigned long)zipfi->external_fa, 4); + } + + ziplocal_putValue_inmemory( zi->ci.central_header + 42, (unsigned long)zi->ci.pos_local_header - zi->add_position_when_writting_offset, 4 ); + + for ( unsigned int i = 0; i < size_filename; i++ ) { + *( zi->ci.central_header + SIZECENTRALHEADER + i ) = *( filename + i ); + } + + for ( unsigned int i = 0; i < size_extrafield_global; i++ ) { + *( zi->ci.central_header + SIZECENTRALHEADER + size_filename + i ) = *( ( ( const char* )extrafield_global ) + i ); + } + + for ( unsigned int i = 0; i < size_comment; i++ ) { + *( zi->ci.central_header + SIZECENTRALHEADER + size_filename + size_extrafield_global + i ) = *( comment + i ); + } + + if ( zi->ci.central_header == NULL ) { + return ZIP_INTERNALERROR; + } + + /* write the local header */ + err = ziplocal_putValue( zi->filestream, (unsigned long)LOCALHEADERMAGIC, 4 ); + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)20, 2 ); /* version needed to extract */ + } + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)zi->ci.flag, 2 ); + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)zi->ci.method, 2 ); + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)zi->ci.dosDate, 4 ); + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)0, 4 ); /* crc 32, unknown */ + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)0, 4 ); /* compressed size, unknown */ + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)0, 4 ); /* uncompressed size, unknown */ + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)size_filename, 2 ); + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, (unsigned long)size_extrafield_local, 2 ); + } + + if ( ( err == ZIP_OK ) && ( size_filename > 0 ) ) { + if ( zi->filestream->Write( filename, size_filename ) != ( int )size_filename ) { + err = ZIP_ERRNO; + } + } + + if ( ( err == ZIP_OK ) && ( size_extrafield_local > 0 ) ) { + if ( zi->filestream->Write( extrafield_local, size_extrafield_local ) != ( int )size_extrafield_local ) { + err = ZIP_ERRNO; + } + } + + zi->ci.stream.avail_in = (unsigned int)0; + zi->ci.stream.avail_out = (unsigned int)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + zi->ci.stream.total_in = 0; + zi->ci.stream.total_out = 0; + + if ( ( err == ZIP_OK ) && ( zi->ci.method == Z_DEFLATED ) && ( !zi->ci.raw ) ) { + zi->ci.stream.zalloc = (alloc_func)0; + zi->ci.stream.zfree = (free_func)0; + zi->ci.stream.opaque = (voidpf)0; + + if ( windowBits > 0 ) { + windowBits = -windowBits; + } + + err = deflateInit2( &zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy ); + + if ( err == Z_OK ) { + zi->ci.stream_initialised = 1; + } + } +#ifndef NOCRYPT + zi->ci.crypt_header_size = 0; + if ( ( err == Z_OK ) && ( password != NULL ) ) { + unsigned char bufHead[RAND_HEAD_LEN]; + unsigned int sizeHead; + zi->ci.encrypt = 1; + zi->ci.pcrc_32_tab = get_crc_table(); + /*init_keys( password, zi->ci.keys, zi->ci.pcrc_32_tab );*/ + + sizeHead=crypthead( password, bufHead, RAND_HEAD_LEN, zi->ci.keys, zi->ci.pcrc_32_tab, crcForCrypting ); + zi->ci.crypt_header_size = sizeHead; + if ( ZWRITE( zi->z_filefunc, zi->filestream, bufHead, sizeHead ) != sizeHead ) { + err = ZIP_ERRNO; + } + } +#endif + + if ( err == Z_OK ) { + zi->in_opened_file_inzip = 1; + } + return err; +} + +/* +======================== +zipOpenNewFileInZip2 +======================== +*/ +int zipOpenNewFileInZip2( zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, unsigned int size_extrafield_local, + const void* extrafield_global, unsigned int size_extrafield_global, const char* comment, int method, int level, int raw ) { + return zipOpenNewFileInZip3( file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, + comment, method, level, raw, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0 ); +} + +/* +======================== +zipOpenNewFileInZip +======================== +*/ +int zipOpenNewFileInZip( zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, unsigned int size_extrafield_local, const void* extrafield_global, + unsigned int size_extrafield_global, const char* comment, int method, int level ) { + return zipOpenNewFileInZip2( file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, 0 ); +} + +/* +======================== +zipFlushWriteBuffer +======================== +*/ +int zipFlushWriteBuffer( zip_internal* zi ) { + int err = ZIP_OK; + if ( zi->ci.encrypt != 0 ) { +#ifndef NOCRYPT + int t; + for ( int i = 0; i < zi->ci.pos_in_buffered_data; i++ ) { + zi->ci.buffered_data[i] = zencode( zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i], t ); + } +#endif + } + if ( zi->filestream->Write( zi->ci.buffered_data, zi->ci.pos_in_buffered_data ) != (int)zi->ci.pos_in_buffered_data ) { + err = ZIP_ERRNO; + } + zi->ci.pos_in_buffered_data = 0; + return err; +} + +/* +======================== +zipWriteInFileInZip +======================== +*/ +int zipWriteInFileInZip( zipFile file, const void* buf, unsigned int len ) { + zip_internal* zi; + int err = ZIP_OK; + + if ( file == NULL ) { + return ZIP_PARAMERROR; + } + zi = (zip_internal*)file; + + if ( zi->in_opened_file_inzip == 0 ) { + return ZIP_PARAMERROR; + } + zi->ci.stream.next_in = (Bytef*)buf; + zi->ci.stream.avail_in = len; + zi->ci.crc32 = crc32( zi->ci.crc32, (byte*)buf, len ); + + while ( ( err == ZIP_OK ) && ( zi->ci.stream.avail_in > 0 ) ) { + if ( zi->ci.stream.avail_out == 0 ) { + if ( zipFlushWriteBuffer( zi ) == ZIP_ERRNO ) { + err = ZIP_ERRNO; + } + zi->ci.stream.avail_out = (unsigned int)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + + if ( err != ZIP_OK ) { + break; + } + + if ( ( zi->ci.method == Z_DEFLATED ) && ( !zi->ci.raw ) ) { + unsigned long uTotalOutBefore = zi->ci.stream.total_out; + err = deflate( &zi->ci.stream, Z_NO_FLUSH ); + zi->ci.pos_in_buffered_data += (unsigned int)( zi->ci.stream.total_out - uTotalOutBefore ); + } else { + unsigned int copy_this; + if ( zi->ci.stream.avail_in < zi->ci.stream.avail_out ) { + copy_this = zi->ci.stream.avail_in; + } else { + copy_this = zi->ci.stream.avail_out; + } + for ( unsigned int i = 0; i < copy_this; i++ ) { + *( ( (char*)zi->ci.stream.next_out ) + i ) = *( ( (const char*)zi->ci.stream.next_in ) + i ); + } + + zi->ci.stream.avail_in -= copy_this; + zi->ci.stream.avail_out-= copy_this; + zi->ci.stream.next_in+= copy_this; + zi->ci.stream.next_out+= copy_this; + zi->ci.stream.total_in+= copy_this; + zi->ci.stream.total_out+= copy_this; + zi->ci.pos_in_buffered_data += copy_this; + } + } + + return err; +} + +/* +======================== +zipCloseFileInZipRaw +======================== +*/ +int zipCloseFileInZipRaw( zipFile file, unsigned long uncompressed_size, unsigned long crc32 ) { + zip_internal* zi; + unsigned long compressed_size; + int err = ZIP_OK; + + if ( file == NULL ) { + return ZIP_PARAMERROR; + } + zi = (zip_internal*)file; + + if ( zi->in_opened_file_inzip == 0 ) { + return ZIP_PARAMERROR; + } + zi->ci.stream.avail_in = 0; + + if ( ( zi->ci.method == Z_DEFLATED ) && !zi->ci.raw ) { + while ( err == ZIP_OK ) { + unsigned long uTotalOutBefore; + if ( zi->ci.stream.avail_out == 0 ) { + if ( zipFlushWriteBuffer( zi ) == ZIP_ERRNO ) { + err = ZIP_ERRNO; + } + zi->ci.stream.avail_out = (unsigned int)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.stream.total_out; + err = deflate( &zi->ci.stream, Z_FINISH ); + zi->ci.pos_in_buffered_data += (unsigned int)( zi->ci.stream.total_out - uTotalOutBefore ); + } + } + + if ( err == Z_STREAM_END ) { + err = ZIP_OK; /* this is normal */ + } + + if ( ( zi->ci.pos_in_buffered_data > 0 ) && ( err == ZIP_OK ) ) { + if ( zipFlushWriteBuffer( zi ) == ZIP_ERRNO ) { + err = ZIP_ERRNO; + } + } + + if ( ( zi->ci.method == Z_DEFLATED ) && !zi->ci.raw ) { + err = deflateEnd( &zi->ci.stream ); + zi->ci.stream_initialised = 0; + } + + if ( !zi->ci.raw ) { + crc32 = (unsigned long)zi->ci.crc32; + uncompressed_size = (unsigned long)zi->ci.stream.total_in; + } + compressed_size = (unsigned long)zi->ci.stream.total_out; +#ifndef NOCRYPT + compressed_size += zi->ci.crypt_header_size; +#endif + + ziplocal_putValue_inmemory( zi->ci.central_header + 16, crc32, 4); /*crc*/ + ziplocal_putValue_inmemory( zi->ci.central_header + 20, compressed_size, 4 ); /*compr size*/ + if ( zi->ci.stream.data_type == Z_ASCII ) { + ziplocal_putValue_inmemory( zi->ci.central_header + 36, (unsigned long)Z_ASCII, 2 ); + } + ziplocal_putValue_inmemory( zi->ci.central_header + 24, uncompressed_size, 4 ); /*uncompr size*/ + + if ( err == ZIP_OK ) { + err = add_data_in_datablock( &zi->central_dir, zi->ci.central_header, (unsigned long)zi->ci.size_centralheader ); + } + TRYFREE( zi->ci.central_header ); + + if ( err == ZIP_OK ) { + long cur_pos_inzip = (long)zi->filestream->Tell(); + if ( zi->filestream->Seek( zi->ci.pos_local_header + 14, FS_SEEK_SET ) != 0 ) { + err = ZIP_ERRNO; + } + + if ( err == ZIP_OK ) { + err = ziplocal_putValue( zi->filestream, crc32, 4 ); /* crc 32, unknown */ + } + + if ( err == ZIP_OK ) { /* compressed size, unknown */ + err = ziplocal_putValue( zi->filestream, compressed_size, 4 ); + } + + if ( err == ZIP_OK ) { /* uncompressed size, unknown */ + err = ziplocal_putValue( zi->filestream, uncompressed_size, 4 ); + } + + if ( zi->filestream->Seek( cur_pos_inzip, FS_SEEK_SET ) != 0 ) { + err = ZIP_ERRNO; + } + } + zi->number_entry++; + zi->in_opened_file_inzip = 0; + + return err; +} + +/* +======================== +zipCloseFileInZip +======================== +*/ +int zipCloseFileInZip( zipFile file ) { + return zipCloseFileInZipRaw( file, 0, 0 ); +} + +/* +======================== +zipClose +======================== +*/ +int zipClose( zipFile file, const char* global_comment ) { + zip_internal* zi; + int err = 0; + unsigned long size_centraldir = 0; + unsigned long centraldir_pos_inzip; + unsigned int size_global_comment; + if ( file == NULL ) { + return ZIP_PARAMERROR; + } + zi = (zip_internal*)file; + + if ( zi->in_opened_file_inzip == 1 ) { + err = zipCloseFileInZip( file ); + } + +#ifndef NO_ADDFILEINEXISTINGZIP + if ( global_comment == NULL ) { + global_comment = zi->globalcomment; + } +#endif + if ( global_comment == NULL ) { + size_global_comment = 0; + } else { + size_global_comment = (unsigned int)idStr::Length( global_comment ); + } + + centraldir_pos_inzip = (unsigned long)zi->filestream->Tell(); + if ( err == ZIP_OK ) { + linkedlist_datablock_internal* ldi = zi->central_dir.first_block; + while ( ldi != NULL ) { + if ( ( err == ZIP_OK ) && ( ldi->filled_in_this_block > 0 ) ) { + if ( zi->filestream->Write( ldi->data, ldi->filled_in_this_block ) != (int)ldi->filled_in_this_block ) { + err = ZIP_ERRNO; + } + } + size_centraldir += ldi->filled_in_this_block; + ldi = ldi->next_datablock; + } + } + free_datablock( zi->central_dir.first_block ); + + if ( err == ZIP_OK ) { /* Magic End */ + err = ziplocal_putValue( zi->filestream, (unsigned long)ENDHEADERMAGIC, 4 ); + } + + if ( err == ZIP_OK ) { /* number of this disk */ + err = ziplocal_putValue( zi->filestream, (unsigned long)0, 2 ); + } + + if ( err == ZIP_OK ) { /* number of the disk with the start of the central directory */ + err = ziplocal_putValue( zi->filestream, (unsigned long)0, 2 ); + } + + if ( err == ZIP_OK ) { /* total number of entries in the central dir on this disk */ + err = ziplocal_putValue( zi->filestream, (unsigned long)zi->number_entry, 2 ); + } + + if ( err == ZIP_OK ) { /* total number of entries in the central dir */ + err = ziplocal_putValue( zi->filestream, (unsigned long)zi->number_entry, 2 ); + } + + if ( err == ZIP_OK ) { /* size of the central directory */ + err = ziplocal_putValue( zi->filestream, (unsigned long)size_centraldir, 4 ); + } + + if ( err == ZIP_OK ) { /* offset of start of central directory with respect to the starting disk number */ + err = ziplocal_putValue( zi->filestream, (unsigned long)( centraldir_pos_inzip - zi->add_position_when_writting_offset ), 4 ); + } + + if ( err == ZIP_OK ) { /* zipfile comment length */ + err = ziplocal_putValue( zi->filestream, (unsigned long)size_global_comment, 2 ); + } + + if ( ( err == ZIP_OK ) && ( size_global_comment > 0 ) ) { + if ( zi->filestream->Write( global_comment, size_global_comment ) != (int)size_global_comment ) { + err = ZIP_ERRNO; + } + } + + delete zi->filestream; + zi->filestream = NULL; + +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE( zi->globalcomment ); +#endif + TRYFREE( zi ); + + return err; +} + +/* +======================== +idZipBuilder::AddFileFilters +======================== +*/ +void idZipBuilder::AddFileFilters( const char *filters ) { +#if 0 + idStrList exts; + idStrListBreakupString( exts, filters, "|" ); + if ( ( exts.Num() > 0 ) && ( exts[ exts.Num() - 1 ] == "" ) ) { + exts.RemoveIndex( exts.Num() - 1 ); + } + filterExts.Append( exts ); +#endif +} + +/* +======================== +idZipBuilder::AddUncompressedFileFilters +======================== +*/ +void idZipBuilder::AddUncompressedFileFilters( const char *filters ) { +#if 0 + idStrList exts; + idStrListBreakupString( exts, filters, "|" ); + if ( ( exts.Num() > 0 ) && ( exts[ exts.Num() - 1 ] == "" ) ) { + exts.RemoveIndex( exts.Num() - 1 ); + } + uncompressedFilterExts.Append( exts ); +#endif +} + +/* +======================== +idZipBuilder::Build + +builds a zip file of all the files in the specified folder, overwriting if necessary +======================== +*/ +bool idZipBuilder::Build( const char* zipPath, const char *folder, bool cleanFolder ) { + zipFileName = zipPath; + sourceFolderName = folder; + + if ( !CreateZipFile( false ) ) { + // don't clean the folder if the zip fails + return false; + } + + if ( cleanFolder ) { + CleanSourceFolder(); + } + return true; +} + +/* +======================== +idZipBuilder::Update + +updates a zip file with the files in the specified folder +======================== +*/ +bool idZipBuilder::Update( const char* zipPath, const char *folder, bool cleanFolder ) { + // if this file doesn't exist, just build it + if ( fileSystem->GetTimestamp( zipPath ) == FILE_NOT_FOUND_TIMESTAMP ) { + return Build( zipPath, folder, cleanFolder ); + } + zipFileName = zipPath; + sourceFolderName = folder; + + if ( !CreateZipFile( true ) ) { + // don't clean the folder if the zip fails + return false; + } + + if ( cleanFolder ) { + CleanSourceFolder(); + } + return true; +} + +/* +======================== +idZipBuilder::GetFileTime +======================== +*/ +bool idZipBuilder::GetFileTime( const idStr &filename, unsigned long *dostime ) const { + { + FILETIME filetime; + WIN32_FIND_DATA fileData; + HANDLE findHandle = FindFirstFile( filename.c_str(), &fileData ); + if ( findHandle != INVALID_HANDLE_VALUE ) { + FileTimeToLocalFileTime( &(fileData.ftLastWriteTime), &filetime ); + FileTimeToDosDateTime( &filetime, ((LPWORD)dostime) + 1, ((LPWORD)dostime) + 0 ); + FindClose( findHandle ); + return true; + } + FindClose( findHandle ); + } + return false; +} + +/* +======================== +idZipBuilder::IsFiltered +======================== +*/ +bool idZipBuilder::IsFiltered( const idStr &filename ) const { + if ( filterExts.Num() == 0 && uncompressedFilterExts.Num() == 0 ) { + return false; + } + for ( int j = 0; j < filterExts.Num(); j++ ) { + idStr fileExt = idStr( "." + filterExts[j] ); + if ( filename.Right( fileExt.Length() ).Icmp( fileExt ) == 0 ) { + return false; + } + } + for ( int j = 0; j < uncompressedFilterExts.Num(); j++ ) { + idStr fileExt = idStr( "." + uncompressedFilterExts[j] ); + if ( filename.Right( fileExt.Length() ).Icmp( fileExt ) == 0 ) { + return false; + } + } + return true; +} + +/* +======================== +idZipBuilder::IsUncompressed +======================== +*/ +bool idZipBuilder::IsUncompressed( const idStr &filename ) const { + if ( uncompressedFilterExts.Num() == 0 ) { + return false; + } + for ( int j = 0; j < uncompressedFilterExts.Num(); j++ ) { + idStr fileExt = idStr( "." + uncompressedFilterExts[j] ); + if ( filename.Right( fileExt.Length() ).Icmp( fileExt ) == 0 ) { + return true; + } + } + return false; +} + +/* +======================== +idZipBuilder::CreateZipFile +======================== +*/ +bool idZipBuilder::CreateZipFile( bool appendFiles ) { +#if 0 +//#ifdef ID_PC + if ( zipFileName.IsEmpty() || sourceFolderName.IsEmpty() ) { + idLib::Warning( "[%s] - invalid parameters!", __FUNCTION__ ); + return false; + } + + // need to clear the filesystem's zip cache before we can open and write + //fileSystem->ClearZipCache(); + + idLib::Printf( "Building zip file: '%s'\n", zipFileName.c_str() ); + + sourceFolderName.StripTrailing( "\\" ); + sourceFolderName.StripTrailing( "/" ); + +#if 0 + // attempt to check the file out + if ( !Sys_IsFileWritable( zipFileName ) ) { + if ( ( idLib::sourceControl == NULL ) || !idLib::sourceControl->CheckOut( zipFileName ) ) { + idLib::Warning( "READONLY zip file couldn't be checked out: %s", zipFileName.c_str() ); + } else { + idLib::Printf( "Checked out: %s\n", zipFileName.c_str() ); + } + } +#endif + + // if not appending, set the file size to zero to "create it from scratch" + if ( !appendFiles ) { + idLib::PrintfIf( zip_verbosity.GetBool(), "Overwriting zip file: '%s'\n", zipFileName.c_str() ); + idFile *zipFile = fileSystem->OpenExplicitFileWrite( zipFileName ); + if ( zipFile != NULL ) { + delete zipFile; + zipFile = NULL; + } + } else { + idLib::PrintfIf( zip_verbosity.GetBool(), "Appending to zip file: '%s'\n", zipFileName.c_str() ); + } + + // enumerate the files to zip up in the source folder + idStrStatic< MAX_OSPATH > relPath; + relPath = + fileSystem->OSPathToRelativePath( sourceFolderName ); + idFileList *files = fileSystem->ListFilesTree( relPath, "*.*" ); + + // check to make sure that at least one file will be added to the package + int atLeastOneFilteredFile = false; + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + idStr filename = files->GetFile( i ); + + if ( !IsFiltered( filename ) ) { + atLeastOneFilteredFile = true; + break; + } + } + if ( !atLeastOneFilteredFile ) { + // although we didn't actually update/create a zip file, it's because no files would be added anyway, which would result in a corrupted zip + idLib::Printf( "Skipping zip creation/modification, no additional changes need to be made...\n" ); + return true; + } + + // open the zip file + zipFile zf = zipOpen( zipFileName, appendFiles ? APPEND_STATUS_ADDINZIP : 0 ); + if ( zf == NULL ) { + idLib::Warning( "[%s] - error opening file '%s'!", __FUNCTION__, zipFileName.c_str() ); + return false; + } + + // add the files to the zip file + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + + // add each file to the zip file + zip_fileinfo zi; + memset( &zi, 0, sizeof( zip_fileinfo ) ); + + idStr filename = files->GetFile( i ); + + if ( IsFiltered( filename ) ) { + idLib::PrintfIf( zip_verbosity.GetBool(), "...Skipping: '%s'\n", filename.c_str() ); + continue; + } + + idStr filenameInZip = filename; + filenameInZip.Strip( relPath ); + filenameInZip.StripLeading( "/" ); + + idStrStatic< MAX_OSPATH > ospath; + ospath = fileSystem->RelativePathToOSPath( filename ); + GetFileTime( ospath, &zi.dosDate ); + + idLib::PrintfIf( zip_verbosity.GetBool(), "...Adding: '%s' ", filenameInZip.c_str() ); + + int compressionMethod = Z_DEFLATED; + if ( IsUncompressed( filenameInZip ) ) { + compressionMethod = 0; + } + + int errcode = zipOpenNewFileInZip3( zf, filenameInZip, &zi, NULL, 0, NULL, 0, NULL /* comment*/, + compressionMethod, DEFAULT_COMPRESSION_LEVEL, 0, -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, NULL /*password*/, 0 /*fileCRC*/ ); + + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error opening file in zipfile!" ); + continue; + } else { + // open the source file + idFile_Permanent src( filename, ospath, FS_READ ); + if ( !src.IsOpen() ) { + idLib::Warning( "Error opening source file!" ); + continue; + } + + // copy the file data into the zip file + idTempArray buffer( DEFAULT_WRITEBUFFERSIZE ); + size_t total = 0; + while ( size_t bytesRead = src.Read( buffer.Ptr(), buffer.Size() ) ) { + if ( bytesRead > 0 ) { + errcode = zipWriteInFileInZip( zf, buffer.Ptr(), (unsigned int)bytesRead ); + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error writing to zipfile (%i bytes)!", bytesRead ); + continue; + } + } + total += bytesRead; + } + assert( total == (size_t)src.Length() ); + } + + errcode = zipCloseFileInZip( zf ); + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error zipping source file!" ); + continue; + } + idLib::PrintfIf( zip_verbosity.GetBool(), "\n" ); + } + + // free the file list + if ( files != NULL ) { + fileSystem->FreeFileList( files ); + } + + // close the zip file + int closeError = zipClose( zf, NULL ); + if ( closeError != ZIP_OK ) { + idLib::Warning( "[%s] - error closing file '%s'!", __FUNCTION__, zipFileName.c_str() ); + return false; + } + + idLib::Printf( "Done.\n" ); + + return true; +#else + + return false; +#endif + +} + +/* +======================== +idZipBuilder::CreateZipFileFromFileList +======================== +*/ +bool idZipBuilder::CreateZipFileFromFileList( const char *name, const idList< idFile_Memory * > & srcFiles ) { + zipFileName = name; + return CreateZipFileFromFiles( srcFiles ); +} +/* +======================== +idZipBuilder::CreateZipFileFromFiles +======================== +*/ +bool idZipBuilder::CreateZipFileFromFiles( const idList< idFile_Memory * > & srcFiles ) { + if ( zipFileName.IsEmpty() ) { + idLib::Warning( "[%s] - invalid parameters!", __FUNCTION__ ); + return false; + } + + // need to clear the filesystem's zip cache before we can open and write + //fileSystem->ClearZipCache(); + + idLib::Printf( "Building zip file: '%s'\n", zipFileName.c_str() ); + + // do not allow overwrite as this should be a tempfile attempt to check the file out + if ( !Sys_IsFileWritable( zipFileName ) ) { + idLib::PrintfIf( zip_verbosity.GetBool(), "File %s not writeable, cannot proceed.\n", zipFileName.c_str() ); + return false; + } + + // open the zip file + zipFile zf = zipOpen( zipFileName, 0 ); + if ( zf == NULL ) { + idLib::Warning( "[%s] - error opening file '%s'!", __FUNCTION__, zipFileName.c_str() ); + return false; + } + + // add the files to the zip file + for ( int i = 0; i < srcFiles.Num(); i++ ) { + + // add each file to the zip file + zip_fileinfo zi; + memset( &zi, 0, sizeof( zip_fileinfo ) ); + + idFile_Memory * src = srcFiles[i]; + src->MakeReadOnly(); + + idLib::PrintfIf( zip_verbosity.GetBool(), "...Adding: '%s' ", src->GetName() ); + + int compressionMethod = Z_DEFLATED; + if ( IsUncompressed( src->GetName() ) ) { + compressionMethod = 0; + } + + int errcode = zipOpenNewFileInZip3( zf, src->GetName(), &zi, NULL, 0, NULL, 0, NULL /* comment*/, + compressionMethod, DEFAULT_COMPRESSION_LEVEL, 0, -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, NULL /*password*/, 0 /*fileCRC*/ ); + + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error opening file in zipfile!" ); + continue; + } else { + // copy the file data into the zip file + idTempArray buffer( DEFAULT_WRITEBUFFERSIZE ); + size_t total = 0; + while ( size_t bytesRead = src->Read( buffer.Ptr(), buffer.Size() ) ) { + if ( bytesRead > 0 ) { + errcode = zipWriteInFileInZip( zf, buffer.Ptr(), (unsigned int)bytesRead ); + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error writing to zipfile (%i bytes)!", bytesRead ); + continue; + } + } + total += bytesRead; + } + assert( total == (size_t)src->Length() ); + } + + errcode = zipCloseFileInZip( zf ); + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error zipping source file!" ); + continue; + } + idLib::PrintfIf( zip_verbosity.GetBool(), "\n" ); + } + + // close the zip file + int closeError = zipClose( zf, zipFileName ); + if ( closeError != ZIP_OK ) { + idLib::Warning( "[%s] - error closing file '%s'!", __FUNCTION__, zipFileName.c_str() ); + return false; + } + + idLib::PrintfIf( zip_verbosity.GetBool(), "Done.\n" ); + + return true; +} + +/* +======================== +idZipBuilder::CleanSourceFolder + +this folder is assumed to be a path under FSPATH_BASE +======================== +*/ +zipFile idZipBuilder::CreateZipFile( const char *name ) { + idLib::Printf( "Creating zip file: '%s'\n", name ); + + // do not allow overwrite as this should be a tempfile attempt to check the file out + if ( !Sys_IsFileWritable( name ) ) { + idLib::PrintfIf( zip_verbosity.GetBool(), "File %s not writeable, cannot proceed.\n", name ); + return NULL; + } + + // open the zip file + zipFile zf = zipOpen( name, 0 ); + if ( zf == NULL ) { + idLib::Warning( "[%s] - error opening file '%s'!", __FUNCTION__, name ); + } + return zf; +} + +/* +======================== +idZipBuilder::CleanSourceFolder + +this folder is assumed to be a path under FSPATH_BASE +======================== +*/ +bool idZipBuilder::AddFile( zipFile zf, idFile_Memory *src, bool deleteFile ) { + // add each file to the zip file + zip_fileinfo zi; + memset( &zi, 0, sizeof( zip_fileinfo ) ); + + + src->MakeReadOnly(); + + idLib::PrintfIf( zip_verbosity.GetBool(), "...Adding: '%s' ", src->GetName() ); + + int compressionMethod = Z_DEFLATED; + if ( IsUncompressed( src->GetName() ) ) { + compressionMethod = Z_NO_COMPRESSION; + } + + int errcode = zipOpenNewFileInZip3( zf, src->GetName(), &zi, NULL, 0, NULL, 0, NULL /* comment*/, + compressionMethod, DEFAULT_COMPRESSION_LEVEL, 0, -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, NULL /*password*/, 0 /*fileCRC*/ ); + + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error opening file in zipfile!" ); + if ( deleteFile ) { + src->Clear( true ); + delete src; + } + return false; + } else { + // copy the file data into the zip file + idTempArray buffer( DEFAULT_WRITEBUFFERSIZE ); + size_t total = 0; + while ( size_t bytesRead = src->Read( buffer.Ptr(), buffer.Size() ) ) { + if ( bytesRead > 0 ) { + errcode = zipWriteInFileInZip( zf, buffer.Ptr(), (unsigned int)bytesRead ); + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error writing to zipfile (%i bytes)!", bytesRead ); + continue; + } + } + total += bytesRead; + } + assert( total == (size_t)src->Length() ); + } + + errcode = zipCloseFileInZip( zf ); + if ( errcode != ZIP_OK ) { + idLib::Warning( "Error zipping source file!" ); + if ( deleteFile ) { + src->Clear( true ); + delete src; + } + return false; + } + idLib::PrintfIf( zip_verbosity.GetBool(), "\n" ); + if ( deleteFile ) { + src->Clear( true ); + delete src; + } + return true; +} + +/* +======================== +idZipBuilder::CleanSourceFolder + +this folder is assumed to be a path under FSPATH_BASE +======================== +*/ +void idZipBuilder::CloseZipFile( zipFile zf ) { + // close the zip file + int closeError = zipClose( zf, zipFileName ); + if ( closeError != ZIP_OK ) { + idLib::Warning( "[%s] - error closing file '%s'!", __FUNCTION__, zipFileName.c_str() ); + } + idLib::PrintfIf( zip_verbosity.GetBool(), "Done.\n" ); +} +/* +======================== +idZipBuilder::CleanSourceFolder + +this folder is assumed to be a path under FSPATH_BASE +======================== +*/ +void idZipBuilder::CleanSourceFolder() { +#if 0 +//#ifdef ID_PC_WIN + idStrList deletedFiles; + + // make sure this is a valid path, we don't want to go nuking + // some user path or something else unintentionally + idStr ospath = sourceFolderName; + ospath.SlashesToBackSlashes(); + ospath.ToLower(); + + char relPath[MAX_OSPATH]; + fileSystem->OSPathToRelativePath( ospath, relPath, MAX_OSPATH ); + + // get the game's base path + idStr basePath = fileSystem->GetBasePathStr( FSPATH_BASE ); + basePath.AppendPath( BASE_GAMEDIR ); + basePath.AppendPath( "maps" ); + basePath.SlashesToBackSlashes(); + basePath.ToLower(); + // path must be off of our base path, ospath can't have .map on the end, and + // do some additional sanity checks + if ( ( ospath.Find( basePath ) == 0 ) && ( ospath.Right( 4 ) != ".map" ) && + ( ospath != "c:\\" ) && ( ospath.Length() > basePath.Length() ) ) { + // get the files in the current directory + idFileList *files = fileSystem->ListFilesTree( relPath, "*.*" ); + if ( files->GetNumFiles() && zip_verbosity.GetBool() ) { + idLib::Printf( "Deleting files in '%s'...\n", relPath ); + } + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + if ( IsFiltered( files->GetFile( i ) ) ) { + continue; + } + // nuke 'em + if ( zip_verbosity.GetBool() ) { + idLib::Printf( "\t...%s\n", files->GetFile( i ) ); + } + fileSystem->RemoveFile( files->GetFile( i ) ); + + char ospath2[MAX_OSPATH]; + fileSystem->RelativePathToOSPath( files->GetFile( i ), ospath2, MAX_OSPATH ); + deletedFiles.Append( ospath2 ); + } + fileSystem->FreeFileList( files ); + fileSystem->RemoveDir( relPath ); + } else { + idLib::Printf( "Warning: idZipBuilder::CleanSourceFolder - Non-standard path: '%s'!\n", ospath.c_str() ); + return; + } + + // figure out which deleted files need to be removed from source control, and then remove those files + idStrList filesToRemoveFromSourceControl; + for ( int i = 0; i < deletedFiles.Num(); i++ ) { + scFileStatus_t fileStatus = idLib::sourceControl->GetFileStatus( deletedFiles[ i ] ); + if ( SCF_IS_IN_SOURCE_CONTROL( fileStatus ) ) { + filesToRemoveFromSourceControl.Append( deletedFiles[ i ] ); + } + } + if ( filesToRemoveFromSourceControl.Num() > 0 ) { + idLib::sourceControl->Delete( filesToRemoveFromSourceControl ); + } + +#endif +} + +/* +======================== +idZipBuilder::BuildMapFolderZip +======================== +*/ +const char *ZIP_FILE_EXTENSION = "pk4"; +bool idZipBuilder::BuildMapFolderZip( const char *mapFileName ) { + idStr zipFileName = mapFileName; + zipFileName.SetFileExtension( ZIP_FILE_EXTENSION ); + idStr pathToZip = mapFileName; + pathToZip.StripFileExtension(); + idZipBuilder zip; + zip.AddFileFilters( "bcm|bmodel|proc|" ); + zip.AddUncompressedFileFilters( "genmodel|sbcm|tbcm|" ); + bool success = zip.Build( zipFileName, pathToZip, true ); + // even if the zip build failed we want to clear the source folder so no contributing files are left around + if ( !success ) { + zip.CleanSourceFolder(); + } + return success; +} + +/* +======================== +idZipBuilder::UpdateMapFolderZip +======================== +*/ +bool idZipBuilder::UpdateMapFolderZip( const char *mapFileName ) { + idStr zipFileName = mapFileName; + zipFileName.SetFileExtension( ZIP_FILE_EXTENSION ); + idStr pathToZip = mapFileName; + pathToZip.StripFileExtension(); + idZipBuilder zip; + zip.AddFileFilters( "bcm|bmodel|proc|" ); + zip.AddUncompressedFileFilters( "genmodel|sbcm|tbcm|" ); + bool success = zip.Update( zipFileName, pathToZip, true ); + // even if the zip build failed we want to clear the source folder so no contributing files are left around + if ( !success ) { + zip.CleanSourceFolder(); + } + return success; +} + +/* +======================== +idZipBuilder::CombineFiles +======================== +*/ +idFile_Memory * idZipBuilder::CombineFiles( const idList< idFile_Memory * > & srcFiles ) { + idFile_Memory * destFile = NULL; + +#if 0 +//#ifdef ID_PC + + // create a new temp file so we can zip into it without refactoring the zip routines + char ospath[MAX_OSPATH]; + const char * tempName = "temp.tmp"; + fileSystem->RelativePathToOSPath( tempName, ospath, MAX_OSPATH, FSPATH_SAVE ); + fileSystem->RemoveFile( ospath ); + + // combine src files into dest filename just specified + idZipBuilder zip; + zip.zipFileName = ospath; + bool ret = zip.CreateZipFileFromFiles( srcFiles ); + + // read the temp file created into a memory file to return + if ( ret ) { + destFile = new idFile_Memory(); + + if ( !destFile->Load( tempName, ospath ) ) { + assert( false && "couldn't read the combined file" ); + delete destFile; + destFile = NULL; + } + + // delete the temp file + fileSystem->RemoveFile( ospath ); + + // make the new file readable + destFile->MakeReadOnly(); + } + +#endif + + return destFile; +} + +CONSOLE_COMMAND( testZipBuilderCombineFiles, "test routine for memory zip file building", 0 ) { +#if 0 + idList< idFile_Memory * > list; + const char * testString = "test"; + int numFiles = 2; + + if ( args.Argc() > 2 ) { + idLib::Printf( "usage: testZipBuilderExtractFiles [numFiles]\n" ); + return; + } + + for ( int arg = 1; arg < args.Argc(); arg++ ) { + numFiles = atoi( args.Argv( arg ) ); + } + + // allocate all the test files + for ( int i = 0; i < numFiles; i++ ) { + idFile_Memory * file = new idFile_Memory( va( "%s%d.txt", testString, i + 1 ) ); + file->MakeWritable(); + idStr str = va( "%s%d", testString, i + 1 ); + file->WriteString( str ); + list.Append( file ); + } + + // combine the files into a single memory file + idZipBuilder zip; + idFile_Memory * file = zip.CombineFiles( list ); + if ( file != NULL ) { + file->MakeReadOnly(); + + char ospath[MAX_OSPATH]; + const char * tempName = "temp.zip"; + fileSystem->RelativePathToOSPath( tempName, ospath, MAX_OSPATH, FSPATH_SAVE ); + + // remove previous file if it exists + fileSystem->RemoveFile( ospath ); + + if ( file->Save( tempName, ospath ) ) { + idLib::PrintfIf( zip_verbosity.GetBool(), va( "File written: %s.\n", ospath ) ); + } else { + idLib::Error( "Could not save the file." ); + } + + delete file; + } + + list.DeleteContents(); +#endif + // Now look at the temp.zip, unzip it to see if it works +} + +/* +======================== +idZipBuilder::ExtractFiles +======================== +*/ +bool idZipBuilder::ExtractFiles( idFile_Memory * & srcFile, idList< idFile_Memory * > & destFiles ) { + bool ret = false; + +#if 0 +//#ifdef ID_PC + + destFiles.Clear(); + + // write the memory file to temp storage so we can unzip it without refactoring the unzip routines + char ospath[MAX_OSPATH]; + const char * tempName = "temp.tmp"; + fileSystem->RelativePathToOSPath( tempName, ospath, MAX_OSPATH, FSPATH_SAVE ); + ret = srcFile->Save( tempName, ospath ); + assert( ret && "couldn't create temp file" ); + + if ( ret ) { + + idLib::PrintfIf( zip_verbosity.GetBool(), "Opening archive %s:\n", ospath ); + unzFile zip = unzOpen( ospath ); + + int numFiles = 0; + int result = unzGoToFirstFile( zip ); + while( result == UNZ_OK ) { + numFiles++; + unz_file_info curFileInfo; + char fileName[MAX_OSPATH]; + unzGetCurrentFileInfo( zip, &curFileInfo, fileName, MAX_OSPATH, NULL, 0, NULL, 0 ); + + idLib::PrintfIf( zip_verbosity.GetBool(), "%d: %s, size: %d \\ %d\n", numFiles, fileName, curFileInfo.compressed_size, curFileInfo.uncompressed_size ); + + // create a buffer big enough to hold the entire uncompressed file + void * buff = Mem_Alloc( curFileInfo.uncompressed_size, TAG_TEMP ); + result = unzOpenCurrentFile( zip ); + if ( result == UNZ_OK ) { + result = unzReadCurrentFile( zip, buff, curFileInfo.uncompressed_size ); + unzCloseCurrentFile( zip ); + } + + // create the new memory file + idFile_Memory * outFile = new idFile_Memory( fileName ); + outFile->SetReadOnlyData( (const char *)buff, curFileInfo.uncompressed_size ); + + destFiles.Append( outFile ); + + result = unzGoToNextFile( zip ); + } + + // close it so we can delete the zip file and create a new one + unzClose( zip ); + + // delete the temp zipfile + fileSystem->RemoveFile( ospath ); + } + +#endif + + return ret; +} + +CONSOLE_COMMAND( testZipBuilderExtractFiles, "test routine for memory zip file extraction", 0 ) { +#if 0 + idList< idFile_Memory * > list; + idFile_Memory * zipfile; + const char * testString = "test"; + int numFiles = 2; + bool overallSuccess = true; + bool success = true; + + if ( args.Argc() > 2 ) { + idLib::Printf( "usage: testZipBuilderExtractFiles [numFiles]\n" ); + return; + } + + for ( int arg = 1; arg < args.Argc(); arg++ ) { + numFiles = atoi( args.Argv( arg ) ); + } + + // create a temp.zip file with string files + { + // allocate all the test files + for ( int i = 0; i < numFiles; i++ ) { + idFile_Memory * file = new idFile_Memory( va( "%s%d.txt", testString, i + 1 ) ); + file->MakeWritable(); + idStr str = va( "%s%d", testString, i + 1 ); + file->WriteString( str ); + list.Append( file ); + } + + // combine the files into a single memory file + idZipBuilder zip; + zipfile = zip.CombineFiles( list ); + + success = ( zipfile != NULL ); + overallSuccess &= success; + idLib::Printf( "Zip file created: %s\n", success ? "^2PASS" : "^1FAIL" ); + + // destroy all the test files + list.DeleteContents(); + } + + // unzip the file into separate memory files + if ( overallSuccess ) { + + // extract all the test files using the single zip file from above + idZipBuilder zip; + if ( !zip.ExtractFiles( zipfile, list ) ) { + idLib::Error( "Could not extract files." ); + } + + success = ( list.Num() == numFiles ); + overallSuccess &= success; + idLib::Printf( "Number of files: %s\n", success ? "^2PASS" : "^1FAIL" ); + + for ( int i = 0; i < list.Num(); i++ ) { + idStr str; + idFile_Memory * file = list[i]; + file->MakeReadOnly(); + file->ReadString( str ); + + idStr filename = va( "%s%d.txt", testString, i + 1 ); + idStr contents = va( "%s%d", testString, i + 1 ); + + // test the filename + bool nameSuccess = ( file->GetName() == filename ); + overallSuccess &= nameSuccess; + + // test the string + bool contentSuccess = ( str == contents ); + overallSuccess &= contentSuccess; + + idLib::Printf( "Extraction of file, %s: %s^0, contents check: %s\n", filename.c_str(), nameSuccess ? "^2PASS" : "^1FAIL", contentSuccess ? "^2PASS" : "^1FAIL" ); + } + + list.DeleteContents(); + } + + if ( zipfile != NULL ) { + delete zipfile; + } + + idLib::Printf( "[%s] overall tests: %s\n", __FUNCTION__, overallSuccess ? "^2PASS" : "^1FAIL" ); +#endif +} diff --git a/neo/framework/Zip.h b/neo/framework/Zip.h new file mode 100644 index 00000000..cfffae51 --- /dev/null +++ b/neo/framework/Zip.h @@ -0,0 +1,305 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __ZIP_H__ +#define __ZIP_H__ + +#include "zlib/zlib.h" + +/* +================================================================================================ + +Contains external code for building ZipFiles. + +The Unzip Package allows extraction of a file from .ZIP file, compatible with +PKZip 2.04g, !WinZip, !InfoZip tools and compatibles. Encryption and multi-volume ZipFiles +(span) are not supported. Old compressions used by old PKZip 1.x are not supported. + +================================================================================================ +*/ +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ + typedef struct TagzipFile__ { int unused; } zipFile__; + typedef zipFile__ *zipFile; +#else + typedef void* zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#ifndef Z_BUFSIZE +#define Z_BUFSIZE (16384) +#endif + +/* +======================== +tm_zip +contains date/time info +======================== +*/ +typedef struct tm_zip_s { + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_zip; + +/* +======================== +zip_fileinfo +======================== +*/ +typedef struct { + tm_zip tmz_date; /* date in understandable format */ + unsigned long dosDate; /* if dos_date == 0, tmu_date is used */ +// unsigned long flag; /* general purpose bit flag 2 bytes */ + + unsigned long internal_fa; /* internal file attributes 2 bytes */ + unsigned long external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +#define NOCRYPT // ignore passwords +#define SIZEDATA_INDATABLOCK (4096-(4*4)) + +/* +======================== +linkedlist_datablock_internal +======================== +*/ +typedef struct linkedlist_datablock_internal_s { + struct linkedlist_datablock_internal_s* next_datablock; + unsigned long avail_in_this_block; + unsigned long filled_in_this_block; + unsigned long unused; /* for future use and alignement */ + unsigned char data[SIZEDATA_INDATABLOCK]; +} linkedlist_datablock_internal; + +/* +======================== +linkedlist_data +======================== +*/ +typedef struct linkedlist_data_s { + linkedlist_datablock_internal* first_block; + linkedlist_datablock_internal* last_block; +} linkedlist_data; + +/* +======================== +curfile_info +======================== +*/ +typedef struct { + z_stream stream; /* zLib stream structure for inflate */ + int stream_initialised; /* 1 is stream is initialised */ + unsigned int pos_in_buffered_data; /* last written byte in buffered_data */ + + unsigned long pos_local_header; /* offset of the local header of the file currenty writing */ + char* central_header; /* central header data for the current file */ + unsigned long size_centralheader; /* size of the central header for cur file */ + unsigned long flag; /* flag of the file currently writing */ + + int method; /* compression method of file currenty wr.*/ + int raw; /* 1 for directly writing raw data */ + byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ + unsigned long dosDate; + unsigned long crc32; + int encrypt; +#ifndef NOCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; + int crypt_header_size; +#endif +} curfile_info; + +//#define NO_ADDFILEINEXISTINGZIP +/* +======================== +zip_internal +======================== +*/ +typedef struct { + idFile* filestream; /* io structore of the zipfile */ + linkedlist_data central_dir; /* datablock with central dir in construction*/ + int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ + curfile_info ci; /* info on the file curretly writing */ + + unsigned long begin_pos; /* position of the beginning of the zipfile */ + unsigned long add_position_when_writting_offset; + unsigned long number_entry; +#ifndef NO_ADDFILEINEXISTINGZIP + char* globalcomment; +#endif +} zip_internal; + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +/* + Create a zipfile. + pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on + an Unix computer "zlib/zlib113.zip". + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ +extern zipFile zipOpen( const char *pathname, int append ); + +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) +*/ +extern zipFile zipOpen2( const char* pathname, int append, char* globalcomment ); + +extern int zipOpenNewFileInZip( zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, + uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, + int method, int level ); + +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file +*/ +extern int zipOpenNewFileInZip2( zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw ); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCtypting : crc of file to compress (needed for crypting) +*/ +extern int zipOpenNewFileInZip3( zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, + int memLevel, int strategy, const char* password, uLong crcForCtypting ); + +/* + Write data in the zipfile +*/ +extern int zipWriteInFileInZip( zipFile file, const void* buf, unsigned int len ); + + +/* + Close the current file in the zipfile +*/ +extern int zipCloseFileInZip( zipFile file ); + + +/* + Close the current file in the zipfile, for fiel opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ +extern int zipCloseFileInZipRaw( zipFile file, uLong uncompressed_size, uLong crc32 ); + + +/* + Close the zipfile +*/ +extern int zipClose( zipFile file, const char* global_comment ); + + +/* +================================================ +idZipBuilder + +simple interface for zipping up a folder of files +by default, the source folder files are added recursively +================================================ +*/ +class idZipBuilder { +public: + idZipBuilder() {} + ~idZipBuilder() {} + +public: + // adds a list of file extensions ( e.g. "bcm|bmodel|" ) to be compressed in the zip file + void AddFileFilters( const char *filters ); + // adds a list of file extensions ( e.g. "genmodel|" ) to be added to the zip file, in an uncompressed form + void AddUncompressedFileFilters( const char *filters ); + // builds a zip file of all the files in the specified folder, overwriting if necessary + bool Build( const char* zipPath, const char *folder, bool cleanFolder ); + // updates a zip file with the files in the specified folder + bool Update( const char* zipPath, const char *folder, bool cleanFolder ); + + // helper function to zip up all the files and put in a new zip file + static bool BuildMapFolderZip( const char *mapFileName ); + // helper function to update a map folder zip for newer files + static bool UpdateMapFolderZip( const char *mapFileName ); + + // combines multiple in-memory files into a single memory file + static idFile_Memory * CombineFiles( const idList< idFile_Memory * > & srcFiles ); + // extracts multiple in-memory files from a single memory file + static bool ExtractFiles( idFile_Memory * & srcFile, idList< idFile_Memory * > & destFiles ); + + void CleanSourceFolder(); + + bool CreateZipFileFromFileList( const char *name, const idList< idFile_Memory * > & srcFiles ); + + zipFile CreateZipFile( const char *name ); + bool AddFile( zipFile zf, idFile_Memory *fm, bool deleteFile ); + void CloseZipFile( zipFile zf ); +private: + bool CreateZipFile( bool appendFiles ); + bool CreateZipFileFromFiles( const idList< idFile_Memory * > & srcFiles ); + bool GetFileTime( const idStr &filename, unsigned long *dostime ) const; + bool IsFiltered( const idStr &filename ) const; + bool IsUncompressed( const idStr &filename ) const; + +private: + idStr zipFileName; // os path to the zip file + idStr sourceFolderName; // source folder of files to zip or add + idStrList filterExts; // file extensions we want to compressed + idStrList uncompressedFilterExts; // file extensions we don't want to compress +}; + +#endif /* __ZIP_H__ */ diff --git a/neo/framework/common_frame.cpp b/neo/framework/common_frame.cpp new file mode 100644 index 00000000..a9bf2293 --- /dev/null +++ b/neo/framework/common_frame.cpp @@ -0,0 +1,748 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Common_local.h" +#include "../renderer/Image.h" +#include "../renderer/ImageOpts.h" +#include "../../doomclassic/doom/doomlib.h" +#include "../../doomclassic/doom/globaldata.h" + +/* + +New for tech4x: + +Unlike previous SMP work, the actual GPU command drawing is done in the main thread, which avoids the +OpenGL problems with needing windows to be created by the same thread that creates the context, as well +as the issues with passing context ownership back and forth on the 360. + +The game tic and the generation of the draw command list is now run in a separate thread, and overlapped +with the interpretation of the previous draw command list. + +While the game tic should be nicely contained, the draw command generation winds through the user interface +code, and is potentially hazardous. For now, the overlap will be restricted to the renderer back end, +which should also be nicely contained. + +*/ +#define DEFAULT_FIXED_TIC "0" +#define DEFAULT_NO_SLEEP "0" + +idCVar com_deltaTimeClamp( "com_deltaTimeClamp", "50", CVAR_INTEGER, "don't process more than this time in a single frame" ); + +idCVar com_fixedTic( "com_fixedTic", DEFAULT_FIXED_TIC, CVAR_BOOL, "run a single game frame per render frame" ); +idCVar com_noSleep( "com_noSleep", DEFAULT_NO_SLEEP, CVAR_BOOL, "don't sleep if the game is running too fast" ); +idCVar com_smp( "com_smp", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "run the game and draw code in a separate thread" ); +idCVar com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" ); +idCVar com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" ); +idCVar com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" ); +idCVar com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); + +idCVar com_sleepGame( "com_sleepGame", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the game time" ); +idCVar com_sleepDraw( "com_sleepDraw", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the draw time" ); +idCVar com_sleepRender( "com_sleepRender", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the render time" ); + +idCVar net_drawDebugHud( "net_drawDebugHud", "0", CVAR_SYSTEM | CVAR_INTEGER, "0 = None, 1 = Hud 1, 2 = Hud 2, 3 = Snapshots" ); + +idCVar timescale( "timescale", "1", CVAR_SYSTEM | CVAR_FLOAT, "Number of game frames to run per render frame", 0.001f, 100.0f ); + +extern idCVar in_useJoystick; +extern idCVar in_joystickRumble; + +/* +=============== +idGameThread::Run + +Run in a background thread for performance, but can also +be called directly in the foreground thread for comparison. +=============== +*/ +int idGameThread::Run() { + commonLocal.frameTiming.startGameTime = Sys_Microseconds(); + + // debugging tool to test frame dropping behavior + if ( com_sleepGame.GetInteger() ) { + Sys_Sleep( com_sleepGame.GetInteger() ); + } + + if ( numGameFrames == 0 ) { + // Ensure there's no stale gameReturn data from a paused game + ret = gameReturn_t(); + } + + if ( isClient ) { + // run the game logic + for ( int i = 0; i < numGameFrames; i++ ) { + SCOPED_PROFILE_EVENT( "Client Prediction" ); + if ( userCmdMgr ) { + game->ClientRunFrame( *userCmdMgr, ( i == numGameFrames - 1 ), ret ); + } + if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) { + break; + } + } + } else { + // run the game logic + for ( int i = 0; i < numGameFrames; i++ ) { + SCOPED_PROFILE_EVENT( "GameTic" ); + if ( userCmdMgr ) { + game->RunFrame( *userCmdMgr, ret ); + } + if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) { + break; + } + } + } + + // we should have consumed all of our usercmds + if ( userCmdMgr ) { + if ( userCmdMgr->HasUserCmdForPlayer( game->GetLocalClientNum() ) && common->GetCurrentGame() == DOOM3_BFG ) { + idLib::Printf( "idGameThread::Run: didn't consume all usercmds\n" ); + } + } + + commonLocal.frameTiming.finishGameTime = Sys_Microseconds(); + + SetThreadGameTime( ( commonLocal.frameTiming.finishGameTime - commonLocal.frameTiming.startGameTime ) / 1000 ); + + // build render commands and geometry + { + SCOPED_PROFILE_EVENT( "Draw" ); + commonLocal.Draw(); + } + + commonLocal.frameTiming.finishDrawTime = Sys_Microseconds(); + + SetThreadRenderTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.finishGameTime ) / 1000 ); + + SetThreadTotalTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.startGameTime ) / 1000 ); + + return 0; +} + +/* +=============== +idGameThread::RunGameAndDraw + +=============== +*/ +gameReturn_t idGameThread::RunGameAndDraw( int numGameFrames_, idUserCmdMgr & userCmdMgr_, bool isClient_, int startGameFrame ) { + // this should always immediately return + this->WaitForThread(); + + // save the usercmds for the background thread to pick up + userCmdMgr = &userCmdMgr_; + + isClient = isClient_; + + // grab the return value created by the last thread execution + gameReturn_t latchedRet = ret; + + numGameFrames = numGameFrames_; + + // start the thread going + if ( com_smp.GetBool() == false ) { + // run it in the main thread so PIX profiling catches everything + Run(); + } else { + this->SignalWork(); + } + + // return the latched result while the thread runs in the background + return latchedRet; +} + + +/* +=============== +idCommonLocal::DrawWipeModel + +Draw the fade material over everything that has been drawn +=============== +*/ +void idCommonLocal::DrawWipeModel() { + + if ( wipeStartTime >= wipeStopTime ) { + return; + } + + int currentTime = Sys_Milliseconds(); + + if ( !wipeHold && currentTime > wipeStopTime ) { + return; + } + + float fade = ( float )( currentTime - wipeStartTime ) / ( wipeStopTime - wipeStartTime ); + renderSystem->SetColor4( 1, 1, 1, fade ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial ); +} + +/* +=============== +idCommonLocal::Draw +=============== +*/ +void idCommonLocal::Draw() { + // debugging tool to test frame dropping behavior + if ( com_sleepDraw.GetInteger() ) { + Sys_Sleep( com_sleepDraw.GetInteger() ); + } + + if ( loadGUI != NULL ) { + loadGUI->Render( renderSystem, Sys_Milliseconds() ); + } else if ( currentGame == DOOM_CLASSIC || currentGame == DOOM2_CLASSIC ) { + const float sysWidth = renderSystem->GetWidth() * renderSystem->GetPixelAspect(); + const float sysHeight = renderSystem->GetHeight(); + const float sysAspect = sysWidth / sysHeight; + const float doomAspect = 4.0f / 3.0f; + const float adjustment = sysAspect / doomAspect; + const float barHeight = ( adjustment >= 1.0f ) ? 0.0f : ( 1.0f - adjustment ) * (float)SCREEN_HEIGHT * 0.25f; + const float barWidth = ( adjustment <= 1.0f ) ? 0.0f : ( adjustment - 1.0f ) * (float)SCREEN_WIDTH * 0.25f; + if ( barHeight > 0.0f ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial ); + renderSystem->DrawStretchPic( 0, SCREEN_HEIGHT - barHeight, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial ); + } + if ( barWidth > 0.0f ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + renderSystem->DrawStretchPic( SCREEN_WIDTH - barWidth, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + } + renderSystem->SetColor4( 1, 1, 1, 1 ); + renderSystem->DrawStretchPic( barWidth, barHeight, SCREEN_WIDTH - barWidth * 2.0f, SCREEN_HEIGHT - barHeight * 2.0f, 0, 0, 1, 1, doomClassicMaterial ); + } else if ( game && game->Shell_IsActive() ) { + bool gameDraw = game->Draw( game->GetLocalClientNum() ); + if ( !gameDraw ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + } + game->Shell_Render(); + } else if ( readDemo ) { + renderWorld->RenderScene( ¤tDemoRenderView ); + renderSystem->DrawDemoPics(); + } else if ( mapSpawned ) { + bool gameDraw = false; + // normal drawing for both single and multi player + if ( !com_skipGameDraw.GetBool() && Game()->GetLocalClientNum() >= 0 ) { + // draw the game view + int start = Sys_Milliseconds(); + if ( game ) { + gameDraw = game->Draw( Game()->GetLocalClientNum() ); + } + int end = Sys_Milliseconds(); + time_gameDraw += ( end - start ); // note time used for com_speeds + } + if ( !gameDraw ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, whiteMaterial ); + } + + // save off the 2D drawing from the game + if ( writeDemo ) { + renderSystem->WriteDemoPics(); + } + } else { + renderSystem->SetColor4( 0, 0, 0, 1 ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + } + + { + SCOPED_PROFILE_EVENT( "Post-Draw" ); + + // draw the wipe material on top of this if it hasn't completed yet + DrawWipeModel(); + + Dialog().Render( loadGUI != NULL ); + + // draw the half console / notify console on top of everything + console->Draw( false ); + } +} + +/* +=============== +idCommonLocal::UpdateScreen + +This is an out-of-sequence screen update, not the normal game rendering +=============== +*/ +void idCommonLocal::UpdateScreen( bool captureToImage ) { + if ( insideUpdateScreen ) { + return; + } + insideUpdateScreen = true; + + // make sure the game / draw thread has completed + gameThread.WaitForThread(); + + // release the mouse capture back to the desktop + Sys_GrabMouseCursor( false ); + + // build all the draw commands without running a new game tic + Draw(); + + if ( captureToImage ) { + renderSystem->CaptureRenderToImage( "_currentRender", false ); + } + + // this should exit right after vsync, with the GPU idle and ready to draw + const emptyCommand_t * cmd = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu ); + + // get the GPU busy with new commands + renderSystem->RenderCommandBuffers( cmd ); + + insideUpdateScreen = false; +} +/* +================ +idCommonLocal::ProcessGameReturn +================ +*/ +void idCommonLocal::ProcessGameReturn( const gameReturn_t & ret ) { + // set joystick rumble + if ( in_useJoystick.GetBool() && in_joystickRumble.GetBool() && !game->Shell_IsActive() && session->GetSignInManager().GetMasterInputDevice() >= 0 ) { + Sys_SetRumble( session->GetSignInManager().GetMasterInputDevice(), ret.vibrationLow, ret.vibrationHigh ); // Only set the rumble on the active controller + } else { + for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { + Sys_SetRumble( i, 0, 0 ); + } + } + + syncNextGameFrame = ret.syncNextGameFrame; + + if ( ret.sessionCommand[0] ) { + idCmdArgs args; + + args.TokenizeString( ret.sessionCommand, false ); + + if ( !idStr::Icmp( args.Argv(0), "map" ) ) { + MoveToNewMap( args.Argv( 1 ), false ); + } else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) { + MoveToNewMap( args.Argv( 1 ), true ); + } else if ( !idStr::Icmp( args.Argv(0), "died" ) ) { + if ( !IsMultiplayer() ) { + game->Shell_Show( true ); + } + } else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" ); + } else if ( !idStr::Icmp( args.Argv(0), "endOfDemo" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "endOfDemo" ); + } + } +} + +extern idCVar com_forceGenericSIMD; + +/* +================= +idCommonLocal::Frame +================= +*/ +void idCommonLocal::Frame() { + try { + SCOPED_PROFILE_EVENT( "Common::Frame" ); + + // This is the only place this is incremented + idLib::frameNumber++; + + // allow changing SIMD usage on the fly + if ( com_forceGenericSIMD.IsModified() ) { + idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() ); + com_forceGenericSIMD.ClearModified(); + } + + // Do the actual switch between Doom 3 and the classics here so + // that things don't get confused in the middle of the frame. + PerformGameSwitch(); + + // pump all the events + Sys_GenerateEvents(); + + // write config file if anything changed + WriteConfiguration(); + + eventLoop->RunEventLoop(); + + // Activate the shell if it's been requested + if ( showShellRequested && game ) { + game->Shell_Show( true ); + showShellRequested = false; + } + + // if the console or another gui is down, we don't need to hold the mouse cursor + bool chatting = false; + if ( console->Active() || Dialog().IsDialogActive() || session->IsSystemUIShowing() || ( game && game->InhibitControls() && !IsPlayingDoomClassic() ) ) { + Sys_GrabMouseCursor( false ); + usercmdGen->InhibitUsercmd( INHIBIT_SESSION, true ); + chatting = true; + } else { + Sys_GrabMouseCursor( true ); + usercmdGen->InhibitUsercmd( INHIBIT_SESSION, false ); + } + + const bool pauseGame = ( !mapSpawned || ( !IsMultiplayer() && ( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || ( game && game->Shell_IsActive() ) ) ) ) && !IsPlayingDoomClassic(); + + // save the screenshot and audio from the last draw if needed + if ( aviCaptureMode ) { + idStr name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviDemoFrameCount++ ); + renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); + + // remove any printed lines at the top before taking the screenshot + console->ClearNotifyLines(); + + // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1 + renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); + } + + //-------------------------------------------- + // wait for the GPU to finish drawing + // + // It is imporant to minimize the time spent between this + // section and the call to renderSystem->RenderCommandBuffers(), + // because the GPU is completely idle. + //-------------------------------------------- + // this should exit right after vsync, with the GPU idle and ready to draw + // This may block if the GPU isn't finished renderng the previous frame. + frameTiming.startSyncTime = Sys_Microseconds(); + const emptyCommand_t * renderCommands = NULL; + if ( com_smp.GetBool() ) { + renderCommands = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu ); + } else { + // the GPU will stay idle through command generation for minimal + // input latency + renderSystem->SwapCommandBuffers_FinishRendering( &time_frontend, &time_backend, &time_shadows, &time_gpu ); + } + frameTiming.finishSyncTime = Sys_Microseconds(); + + //-------------------------------------------- + // Determine how many game tics we are going to run, + // now that the previous frame is completely finished. + // + // It is important that any waiting on the GPU be done + // before this, or there will be a bad stuttering when + // dropping frames for performance management. + //-------------------------------------------- + + // input: + // thisFrameTime + // com_noSleep + // com_engineHz + // com_fixedTic + // com_deltaTimeClamp + // IsMultiplayer + // + // in/out state: + // gameFrame + // gameTimeResidual + // lastFrameTime + // syncNextFrame + // + // Output: + // numGameFrames + + // How many game frames to run + int numGameFrames = 0; + + for(;;) { + const int thisFrameTime = Sys_Milliseconds(); + static int lastFrameTime = thisFrameTime; // initialized only the first time + const int deltaMilliseconds = thisFrameTime - lastFrameTime; + lastFrameTime = thisFrameTime; + + // if there was a large gap in time since the last frame, or the frame + // rate is very very low, limit the number of frames we will run + const int clampedDeltaMilliseconds = Min( deltaMilliseconds, com_deltaTimeClamp.GetInteger() ); + + gameTimeResidual += clampedDeltaMilliseconds * timescale.GetFloat(); + + // don't run any frames when paused + if ( pauseGame ) { + gameFrame++; + gameTimeResidual = 0; + break; + } + + // debug cvar to force multiple game tics + if ( com_fixedTic.GetInteger() > 0 ) { + numGameFrames = com_fixedTic.GetInteger(); + gameFrame += numGameFrames; + gameTimeResidual = 0; + break; + } + + if ( syncNextGameFrame ) { + // don't sleep at all + syncNextGameFrame = false; + gameFrame++; + numGameFrames++; + gameTimeResidual = 0; + break; + } + + for ( ;; ) { + // How much time to wait before running the next frame, + // based on com_engineHz + const int frameDelay = FRAME_TO_MSEC( gameFrame + 1 ) - FRAME_TO_MSEC( gameFrame ); + if ( gameTimeResidual < frameDelay ) { + break; + } + gameTimeResidual -= frameDelay; + gameFrame++; + numGameFrames++; + // if there is enough residual left, we may run additional frames + } + + if ( numGameFrames > 0 ) { + // ready to actually run them + break; + } + + // if we are vsyncing, we always want to run at least one game + // frame and never sleep, which might happen due to scheduling issues + // if we were just looking at real time. + if ( com_noSleep.GetBool() ) { + numGameFrames = 1; + gameFrame += numGameFrames; + gameTimeResidual = 0; + break; + } + + // not enough time has passed to run a frame, as might happen if + // we don't have vsync on, or the monitor is running at 120hz while + // com_engineHz is 60, so sleep a bit and check again + Sys_Sleep( 0 ); + } + + //-------------------------------------------- + // It would be better to push as much of this as possible + // either before or after the renderSystem->SwapCommandBuffers(), + // because the GPU is completely idle. + //-------------------------------------------- + + // Update session and syncronize to the new session state after sleeping + session->UpdateSignInManager(); + session->Pump(); + session->ProcessSnapAckQueue(); + + if ( session->GetState() == idSession::LOADING ) { + // If the session reports we should be loading a map, load it! + ExecuteMapChange(); + mapSpawnData.savegameFile = NULL; + mapSpawnData.persistentPlayerInfo.Clear(); + return; + } else if ( session->GetState() != idSession::INGAME && mapSpawned ) { + // If the game is running, but the session reports we are not in a game, disconnect + // This happens when a server disconnects us or we sign out + LeaveGame(); + return; + } + + if ( mapSpawned && !pauseGame ) { + if ( IsClient() ) { + RunNetworkSnapshotFrame(); + } + } + + ExecuteReliableMessages(); + + // send frame and mouse events to active guis + GuiFrameEvents(); + + //-------------------------------------------- + // Prepare usercmds and kick off the game processing + // in a background thread + //-------------------------------------------- + + // get the previous usercmd for bypassed head tracking transform + const usercmd_t previousCmd = usercmdGen->GetCurrentUsercmd(); + + // build a new usercmd + int deviceNum = session->GetSignInManager().GetMasterInputDevice(); + usercmdGen->BuildCurrentUsercmd( deviceNum ); + if ( deviceNum == -1 ) { + for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { + Sys_PollJoystickInputEvents( i ); + Sys_EndJoystickInputEvents(); + } + } + if ( pauseGame ) { + usercmdGen->Clear(); + } + + usercmd_t newCmd = usercmdGen->GetCurrentUsercmd(); + + // Store server game time - don't let time go past last SS time in case we are extrapolating + if ( IsClient() ) { + newCmd.serverGameMilliseconds = std::min( Game()->GetServerGameTimeMs(), Game()->GetSSEndTime() ); + } else { + newCmd.serverGameMilliseconds = Game()->GetServerGameTimeMs(); + } + + userCmdMgr.MakeReadPtrCurrentForPlayer( Game()->GetLocalClientNum() ); + + // Stuff a copy of this userCmd for each game frame we are going to run. + // Ideally, the usercmds would be built in another thread so you could + // still get 60hz control accuracy when the game is running slower. + for ( int i = 0 ; i < numGameFrames ; i++ ) { + newCmd.clientGameMilliseconds = FRAME_TO_MSEC( gameFrame-numGameFrames+i+1 ); + userCmdMgr.PutUserCmdForPlayer( game->GetLocalClientNum(), newCmd ); + } + + // If we're in Doom or Doom 2, run tics and upload the new texture. + if ( ( GetCurrentGame() == DOOM_CLASSIC || GetCurrentGame() == DOOM2_CLASSIC ) && !( Dialog().IsDialogPausing() || session->IsSystemUIShowing() ) ) { + RunDoomClassicFrame(); + } + + // start the game / draw command generation thread going in the background + gameReturn_t ret = gameThread.RunGameAndDraw( numGameFrames, userCmdMgr, IsClient(), gameFrame - numGameFrames ); + + if ( !com_smp.GetBool() ) { + // in non-smp mode, run the commands we just generated, instead of + // frame-delayed ones from a background thread + renderCommands = renderSystem->SwapCommandBuffers_FinishCommandBuffers(); + } + + //---------------------------------------- + // Run the render back end, getting the GPU busy with new commands + // ASAP to minimize the pipeline bubble. + //---------------------------------------- + frameTiming.startRenderTime = Sys_Microseconds(); + renderSystem->RenderCommandBuffers( renderCommands ); + if ( com_sleepRender.GetInteger() > 0 ) { + // debug tool to test frame adaption + Sys_Sleep( com_sleepRender.GetInteger() ); + } + frameTiming.finishRenderTime = Sys_Microseconds(); + + // make sure the game / draw thread has completed + // This may block if the game is taking longer than the render back end + gameThread.WaitForThread(); + + // Send local usermds to the server. + // This happens after the game frame has run so that prediction data is up to date. + SendUsercmds( Game()->GetLocalClientNum() ); + + // Now that we have an updated game frame, we can send out new snapshots to our clients + session->Pump(); // Pump to get updated usercmds to relay + SendSnapshots(); + + // Render the sound system using the latest commands from the game thread + if ( pauseGame ) { + soundWorld->Pause(); + soundSystem->SetPlayingSoundWorld( menuSoundWorld ); + } else { + soundWorld->UnPause(); + soundSystem->SetPlayingSoundWorld( soundWorld ); + } + soundSystem->Render(); + + // process the game return for map changes, etc + ProcessGameReturn( ret ); + + idLobbyBase & lobby = session->GetActivePlatformLobbyBase(); + if ( lobby.HasActivePeers() ) { + if ( net_drawDebugHud.GetInteger() == 1 ) { + lobby.DrawDebugNetworkHUD(); + } + if ( net_drawDebugHud.GetInteger() == 2 ) { + lobby.DrawDebugNetworkHUD2(); + } + lobby.DrawDebugNetworkHUD_ServerSnapshotMetrics( net_drawDebugHud.GetInteger() == 3 ); + } + + // report timing information + if ( com_speeds.GetBool() ) { + static int lastTime = Sys_Milliseconds(); + int nowTime = Sys_Milliseconds(); + int com_frameMsec = nowTime - lastTime; + lastTime = nowTime; + Printf( "frame:%d all:%3d gfr:%3d rf:%3lld bk:%3lld\n", idLib::frameNumber, com_frameMsec, time_gameFrame, time_frontend / 1000, time_backend / 1000 ); + time_gameFrame = 0; + time_gameDraw = 0; + } + + // the FPU stack better be empty at this point or some bad code or compiler bug left values on the stack + if ( !Sys_FPU_StackIsEmpty() ) { + Printf( Sys_FPU_GetState() ); + FatalError( "idCommon::Frame: the FPU stack is not empty at the end of the frame\n" ); + } + + mainFrameTiming = frameTiming; + + session->GetSaveGameManager().Pump(); + } catch( idException & ) { + return; // an ERP_DROP was thrown + } +} + +/* +================= +idCommonLocal::RunDoomClassicFrame +================= +*/ +void idCommonLocal::RunDoomClassicFrame() { + static int doomTics = 0; + + if( DoomLib::expansionDirty ) { + + // re-Initialize the Doom Engine. + DoomLib::Interface.Shutdown(); + DoomLib::Interface.Startup( 1, false ); + DoomLib::expansionDirty = false; + } + + + if ( DoomLib::Interface.Frame( doomTics, &userCmdMgr ) ) { + Globals *data = (Globals*)DoomLib::GetGlobalData( 0 ); + + idArray< unsigned int, 256 > palette; + std::copy( data->XColorMap, data->XColorMap + palette.Num(), palette.Ptr() ); + + // Do the palette lookup. + for ( int row = 0; row < DOOMCLASSIC_RENDERHEIGHT; ++row ) { + for ( int column = 0; column < DOOMCLASSIC_RENDERWIDTH; ++column ) { + const int doomScreenPixelIndex = row * DOOMCLASSIC_RENDERWIDTH + column; + const byte paletteIndex = data->screens[0][doomScreenPixelIndex]; + const unsigned int paletteColor = palette[paletteIndex]; + const byte red = (paletteColor & 0xFF000000) >> 24; + const byte green = (paletteColor & 0x00FF0000) >> 16; + const byte blue = (paletteColor & 0x0000FF00) >> 8; + + const int imageDataPixelIndex = row * DOOMCLASSIC_RENDERWIDTH * DOOMCLASSIC_BYTES_PER_PIXEL + column * DOOMCLASSIC_BYTES_PER_PIXEL; + doomClassicImageData[imageDataPixelIndex] = red; + doomClassicImageData[imageDataPixelIndex + 1] = green; + doomClassicImageData[imageDataPixelIndex + 2] = blue; + doomClassicImageData[imageDataPixelIndex + 3] = 255; + } + } + } + + renderSystem->UploadImage( "_doomClassic", doomClassicImageData.Ptr(), DOOMCLASSIC_RENDERWIDTH, DOOMCLASSIC_RENDERHEIGHT ); + doomTics++; +} diff --git a/neo/framework/precompiled.cpp b/neo/framework/precompiled.cpp new file mode 100644 index 00000000..ef6e84e3 --- /dev/null +++ b/neo/framework/precompiled.cpp @@ -0,0 +1,29 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" diff --git a/neo/framework/zlib/ChangeLog b/neo/framework/zlib/ChangeLog new file mode 100644 index 00000000..7f6869d3 --- /dev/null +++ b/neo/framework/zlib/ChangeLog @@ -0,0 +1,855 @@ + + ChangeLog file for zlib + +Changes in 1.2.3 (18 July 2005) +- Apply security vulnerability fixes to contrib/infback9 as well +- Clean up some text files (carriage returns, trailing space) +- Update testzlib, vstudio, masmx64, and masmx86 in contrib [Vollant] + +Changes in 1.2.2.4 (11 July 2005) +- Add inflatePrime() function for starting inflation at bit boundary +- Avoid some Visual C warnings in deflate.c +- Avoid more silly Visual C warnings in inflate.c and inftrees.c for 64-bit + compile +- Fix some spelling errors in comments [Betts] +- Correct inflateInit2() error return documentation in zlib.h +- Added zran.c example of compressed data random access to examples + directory, shows use of inflatePrime() +- Fix cast for assignments to strm->state in inflate.c and infback.c +- Fix zlibCompileFlags() in zutil.c to use 1L for long shifts [Oberhumer] +- Move declarations of gf2 functions to right place in crc32.c [Oberhumer] +- Add cast in trees.c t avoid a warning [Oberhumer] +- Avoid some warnings in fitblk.c, gun.c, gzjoin.c in examples [Oberhumer] +- Update make_vms.com [Zinser] +- Initialize state->write in inflateReset() since copied in inflate_fast() +- Be more strict on incomplete code sets in inflate_table() and increase + ENOUGH and MAXD -- this repairs a possible security vulnerability for + invalid inflate input. Thanks to Tavis Ormandy and Markus Oberhumer for + discovering the vulnerability and providing test cases. +- Add ia64 support to configure for HP-UX [Smith] +- Add error return to gzread() for format or i/o error [Levin] +- Use malloc.h for OS/2 [Necasek] + +Changes in 1.2.2.3 (27 May 2005) +- Replace 1U constants in inflate.c and inftrees.c for 64-bit compile +- Typecast fread() return values in gzio.c [Vollant] +- Remove trailing space in minigzip.c outmode (VC++ can't deal with it) +- Fix crc check bug in gzread() after gzungetc() [Heiner] +- Add the deflateTune() function to adjust internal compression parameters +- Add a fast gzip decompressor, gun.c, to examples (use of inflateBack) +- Remove an incorrect assertion in examples/zpipe.c +- Add C++ wrapper in infback9.h [Donais] +- Fix bug in inflateCopy() when decoding fixed codes +- Note in zlib.h how much deflateSetDictionary() actually uses +- Remove USE_DICT_HEAD in deflate.c (would mess up inflate if used) +- Add _WIN32_WCE to define WIN32 in zconf.in.h [Spencer] +- Don't include stderr.h or errno.h for _WIN32_WCE in zutil.h [Spencer] +- Add gzdirect() function to indicate transparent reads +- Update contrib/minizip [Vollant] +- Fix compilation of deflate.c when both ASMV and FASTEST [Oberhumer] +- Add casts in crc32.c to avoid warnings [Oberhumer] +- Add contrib/masmx64 [Vollant] +- Update contrib/asm586, asm686, masmx86, testzlib, vstudio [Vollant] + +Changes in 1.2.2.2 (30 December 2004) +- Replace structure assignments in deflate.c and inflate.c with zmemcpy to + avoid implicit memcpy calls (portability for no-library compilation) +- Increase sprintf() buffer size in gzdopen() to allow for large numbers +- Add INFLATE_STRICT to check distances against zlib header +- Improve WinCE errno handling and comments [Chang] +- Remove comment about no gzip header processing in FAQ +- Add Z_FIXED strategy option to deflateInit2() to force fixed trees +- Add updated make_vms.com [Coghlan], update README +- Create a new "examples" directory, move gzappend.c there, add zpipe.c, + fitblk.c, gzlog.[ch], gzjoin.c, and zlib_how.html. +- Add FAQ entry and comments in deflate.c on uninitialized memory access +- Add Solaris 9 make options in configure [Gilbert] +- Allow strerror() usage in gzio.c for STDC +- Fix DecompressBuf in contrib/delphi/ZLib.pas [ManChesTer] +- Update contrib/masmx86/inffas32.asm and gvmat32.asm [Vollant] +- Use z_off_t for adler32_combine() and crc32_combine() lengths +- Make adler32() much faster for small len +- Use OS_CODE in deflate() default gzip header + +Changes in 1.2.2.1 (31 October 2004) +- Allow inflateSetDictionary() call for raw inflate +- Fix inflate header crc check bug for file names and comments +- Add deflateSetHeader() and gz_header structure for custom gzip headers +- Add inflateGetheader() to retrieve gzip headers +- Add crc32_combine() and adler32_combine() functions +- Add alloc_func, free_func, in_func, out_func to Z_PREFIX list +- Use zstreamp consistently in zlib.h (inflate_back functions) +- Remove GUNZIP condition from definition of inflate_mode in inflate.h + and in contrib/inflate86/inffast.S [Truta, Anderson] +- Add support for AMD64 in contrib/inflate86/inffas86.c [Anderson] +- Update projects/README.projects and projects/visualc6 [Truta] +- Update win32/DLL_FAQ.txt [Truta] +- Avoid warning under NO_GZCOMPRESS in gzio.c; fix typo [Truta] +- Deprecate Z_ASCII; use Z_TEXT instead [Truta] +- Use a new algorithm for setting strm->data_type in trees.c [Truta] +- Do not define an exit() prototype in zutil.c unless DEBUG defined +- Remove prototype of exit() from zutil.c, example.c, minigzip.c [Truta] +- Add comment in zlib.h for Z_NO_FLUSH parameter to deflate() +- Fix Darwin build version identification [Peterson] + +Changes in 1.2.2 (3 October 2004) +- Update zlib.h comments on gzip in-memory processing +- Set adler to 1 in inflateReset() to support Java test suite [Walles] +- Add contrib/dotzlib [Ravn] +- Update win32/DLL_FAQ.txt [Truta] +- Update contrib/minizip [Vollant] +- Move contrib/visual-basic.txt to old/ [Truta] +- Fix assembler builds in projects/visualc6/ [Truta] + +Changes in 1.2.1.2 (9 September 2004) +- Update INDEX file +- Fix trees.c to update strm->data_type (no one ever noticed!) +- Fix bug in error case in inflate.c, infback.c, and infback9.c [Brown] +- Add "volatile" to crc table flag declaration (for DYNAMIC_CRC_TABLE) +- Add limited multitasking protection to DYNAMIC_CRC_TABLE +- Add NO_vsnprintf for VMS in zutil.h [Mozilla] +- Don't declare strerror() under VMS [Mozilla] +- Add comment to DYNAMIC_CRC_TABLE to use get_crc_table() to initialize +- Update contrib/ada [Anisimkov] +- Update contrib/minizip [Vollant] +- Fix configure to not hardcode directories for Darwin [Peterson] +- Fix gzio.c to not return error on empty files [Brown] +- Fix indentation; update version in contrib/delphi/ZLib.pas and + contrib/pascal/zlibpas.pas [Truta] +- Update mkasm.bat in contrib/masmx86 [Truta] +- Update contrib/untgz [Truta] +- Add projects/README.projects [Truta] +- Add project for MS Visual C++ 6.0 in projects/visualc6 [Cadieux, Truta] +- Update win32/DLL_FAQ.txt [Truta] +- Update list of Z_PREFIX symbols in zconf.h [Randers-Pehrson, Truta] +- Remove an unnecessary assignment to curr in inftrees.c [Truta] +- Add OS/2 to exe builds in configure [Poltorak] +- Remove err dummy parameter in zlib.h [Kientzle] + +Changes in 1.2.1.1 (9 January 2004) +- Update email address in README +- Several FAQ updates +- Fix a big fat bug in inftrees.c that prevented decoding valid + dynamic blocks with only literals and no distance codes -- + Thanks to "Hot Emu" for the bug report and sample file +- Add a note to puff.c on no distance codes case. + +Changes in 1.2.1 (17 November 2003) +- Remove a tab in contrib/gzappend/gzappend.c +- Update some interfaces in contrib for new zlib functions +- Update zlib version number in some contrib entries +- Add Windows CE definition for ptrdiff_t in zutil.h [Mai, Truta] +- Support shared libraries on Hurd and KFreeBSD [Brown] +- Fix error in NO_DIVIDE option of adler32.c + +Changes in 1.2.0.8 (4 November 2003) +- Update version in contrib/delphi/ZLib.pas and contrib/pascal/zlibpas.pas +- Add experimental NO_DIVIDE #define in adler32.c + - Possibly faster on some processors (let me know if it is) +- Correct Z_BLOCK to not return on first inflate call if no wrap +- Fix strm->data_type on inflate() return to correctly indicate EOB +- Add deflatePrime() function for appending in the middle of a byte +- Add contrib/gzappend for an example of appending to a stream +- Update win32/DLL_FAQ.txt [Truta] +- Delete Turbo C comment in README [Truta] +- Improve some indentation in zconf.h [Truta] +- Fix infinite loop on bad input in configure script [Church] +- Fix gzeof() for concatenated gzip files [Johnson] +- Add example to contrib/visual-basic.txt [Michael B.] +- Add -p to mkdir's in Makefile.in [vda] +- Fix configure to properly detect presence or lack of printf functions +- Add AS400 support [Monnerat] +- Add a little Cygwin support [Wilson] + +Changes in 1.2.0.7 (21 September 2003) +- Correct some debug formats in contrib/infback9 +- Cast a type in a debug statement in trees.c +- Change search and replace delimiter in configure from % to # [Beebe] +- Update contrib/untgz to 0.2 with various fixes [Truta] +- Add build support for Amiga [Nikl] +- Remove some directories in old that have been updated to 1.2 +- Add dylib building for Mac OS X in configure and Makefile.in +- Remove old distribution stuff from Makefile +- Update README to point to DLL_FAQ.txt, and add comment on Mac OS X +- Update links in README + +Changes in 1.2.0.6 (13 September 2003) +- Minor FAQ updates +- Update contrib/minizip to 1.00 [Vollant] +- Remove test of gz functions in example.c when GZ_COMPRESS defined [Truta] +- Update POSTINC comment for 68060 [Nikl] +- Add contrib/infback9 with deflate64 decoding (unsupported) +- For MVS define NO_vsnprintf and undefine FAR [van Burik] +- Add pragma for fdopen on MVS [van Burik] + +Changes in 1.2.0.5 (8 September 2003) +- Add OF to inflateBackEnd() declaration in zlib.h +- Remember start when using gzdopen in the middle of a file +- Use internal off_t counters in gz* functions to properly handle seeks +- Perform more rigorous check for distance-too-far in inffast.c +- Add Z_BLOCK flush option to return from inflate at block boundary +- Set strm->data_type on return from inflate + - Indicate bits unused, if at block boundary, and if in last block +- Replace size_t with ptrdiff_t in crc32.c, and check for correct size +- Add condition so old NO_DEFLATE define still works for compatibility +- FAQ update regarding the Windows DLL [Truta] +- INDEX update: add qnx entry, remove aix entry [Truta] +- Install zlib.3 into mandir [Wilson] +- Move contrib/zlib_dll_FAQ.txt to win32/DLL_FAQ.txt; update [Truta] +- Adapt the zlib interface to the new DLL convention guidelines [Truta] +- Introduce ZLIB_WINAPI macro to allow the export of functions using + the WINAPI calling convention, for Visual Basic [Vollant, Truta] +- Update msdos and win32 scripts and makefiles [Truta] +- Export symbols by name, not by ordinal, in win32/zlib.def [Truta] +- Add contrib/ada [Anisimkov] +- Move asm files from contrib/vstudio/vc70_32 to contrib/asm386 [Truta] +- Rename contrib/asm386 to contrib/masmx86 [Truta, Vollant] +- Add contrib/masm686 [Truta] +- Fix offsets in contrib/inflate86 and contrib/masmx86/inffas32.asm + [Truta, Vollant] +- Update contrib/delphi; rename to contrib/pascal; add example [Truta] +- Remove contrib/delphi2; add a new contrib/delphi [Truta] +- Avoid inclusion of the nonstandard in contrib/iostream, + and fix some method prototypes [Truta] +- Fix the ZCR_SEED2 constant to avoid warnings in contrib/minizip + [Truta] +- Avoid the use of backslash (\) in contrib/minizip [Vollant] +- Fix file time handling in contrib/untgz; update makefiles [Truta] +- Update contrib/vstudio/vc70_32 to comply with the new DLL guidelines + [Vollant] +- Remove contrib/vstudio/vc15_16 [Vollant] +- Rename contrib/vstudio/vc70_32 to contrib/vstudio/vc7 [Truta] +- Update README.contrib [Truta] +- Invert the assignment order of match_head and s->prev[...] in + INSERT_STRING [Truta] +- Compare TOO_FAR with 32767 instead of 32768, to avoid 16-bit warnings + [Truta] +- Compare function pointers with 0, not with NULL or Z_NULL [Truta] +- Fix prototype of syncsearch in inflate.c [Truta] +- Introduce ASMINF macro to be enabled when using an ASM implementation + of inflate_fast [Truta] +- Change NO_DEFLATE to NO_GZCOMPRESS [Truta] +- Modify test_gzio in example.c to take a single file name as a + parameter [Truta] +- Exit the example.c program if gzopen fails [Truta] +- Add type casts around strlen in example.c [Truta] +- Remove casting to sizeof in minigzip.c; give a proper type + to the variable compared with SUFFIX_LEN [Truta] +- Update definitions of STDC and STDC99 in zconf.h [Truta] +- Synchronize zconf.h with the new Windows DLL interface [Truta] +- Use SYS16BIT instead of __32BIT__ to distinguish between + 16- and 32-bit platforms [Truta] +- Use far memory allocators in small 16-bit memory models for + Turbo C [Truta] +- Add info about the use of ASMV, ASMINF and ZLIB_WINAPI in + zlibCompileFlags [Truta] +- Cygwin has vsnprintf [Wilson] +- In Windows16, OS_CODE is 0, as in MSDOS [Truta] +- In Cygwin, OS_CODE is 3 (Unix), not 11 (Windows32) [Wilson] + +Changes in 1.2.0.4 (10 August 2003) +- Minor FAQ updates +- Be more strict when checking inflateInit2's windowBits parameter +- Change NO_GUNZIP compile option to NO_GZIP to cover deflate as well +- Add gzip wrapper option to deflateInit2 using windowBits +- Add updated QNX rule in configure and qnx directory [Bonnefoy] +- Make inflate distance-too-far checks more rigorous +- Clean up FAR usage in inflate +- Add casting to sizeof() in gzio.c and minigzip.c + +Changes in 1.2.0.3 (19 July 2003) +- Fix silly error in gzungetc() implementation [Vollant] +- Update contrib/minizip and contrib/vstudio [Vollant] +- Fix printf format in example.c +- Correct cdecl support in zconf.in.h [Anisimkov] +- Minor FAQ updates + +Changes in 1.2.0.2 (13 July 2003) +- Add ZLIB_VERNUM in zlib.h for numerical preprocessor comparisons +- Attempt to avoid warnings in crc32.c for pointer-int conversion +- Add AIX to configure, remove aix directory [Bakker] +- Add some casts to minigzip.c +- Improve checking after insecure sprintf() or vsprintf() calls +- Remove #elif's from crc32.c +- Change leave label to inf_leave in inflate.c and infback.c to avoid + library conflicts +- Remove inflate gzip decoding by default--only enable gzip decoding by + special request for stricter backward compatibility +- Add zlibCompileFlags() function to return compilation information +- More typecasting in deflate.c to avoid warnings +- Remove leading underscore from _Capital #defines [Truta] +- Fix configure to link shared library when testing +- Add some Windows CE target adjustments [Mai] +- Remove #define ZLIB_DLL in zconf.h [Vollant] +- Add zlib.3 [Rodgers] +- Update RFC URL in deflate.c and algorithm.txt [Mai] +- Add zlib_dll_FAQ.txt to contrib [Truta] +- Add UL to some constants [Truta] +- Update minizip and vstudio [Vollant] +- Remove vestigial NEED_DUMMY_RETURN from zconf.in.h +- Expand use of NO_DUMMY_DECL to avoid all dummy structures +- Added iostream3 to contrib [Schwardt] +- Replace rewind() with fseek() for WinCE [Truta] +- Improve setting of zlib format compression level flags + - Report 0 for huffman and rle strategies and for level == 0 or 1 + - Report 2 only for level == 6 +- Only deal with 64K limit when necessary at compile time [Truta] +- Allow TOO_FAR check to be turned off at compile time [Truta] +- Add gzclearerr() function [Souza] +- Add gzungetc() function + +Changes in 1.2.0.1 (17 March 2003) +- Add Z_RLE strategy for run-length encoding [Truta] + - When Z_RLE requested, restrict matches to distance one + - Update zlib.h, minigzip.c, gzopen(), gzdopen() for Z_RLE +- Correct FASTEST compilation to allow level == 0 +- Clean up what gets compiled for FASTEST +- Incorporate changes to zconf.in.h [Vollant] + - Refine detection of Turbo C need for dummy returns + - Refine ZLIB_DLL compilation + - Include additional header file on VMS for off_t typedef +- Try to use _vsnprintf where it supplants vsprintf [Vollant] +- Add some casts in inffast.c +- Enchance comments in zlib.h on what happens if gzprintf() tries to + write more than 4095 bytes before compression +- Remove unused state from inflateBackEnd() +- Remove exit(0) from minigzip.c, example.c +- Get rid of all those darn tabs +- Add "check" target to Makefile.in that does the same thing as "test" +- Add "mostlyclean" and "maintainer-clean" targets to Makefile.in +- Update contrib/inflate86 [Anderson] +- Update contrib/testzlib, contrib/vstudio, contrib/minizip [Vollant] +- Add msdos and win32 directories with makefiles [Truta] +- More additions and improvements to the FAQ + +Changes in 1.2.0 (9 March 2003) +- New and improved inflate code + - About 20% faster + - Does not allocate 32K window unless and until needed + - Automatically detects and decompresses gzip streams + - Raw inflate no longer needs an extra dummy byte at end + - Added inflateBack functions using a callback interface--even faster + than inflate, useful for file utilities (gzip, zip) + - Added inflateCopy() function to record state for random access on + externally generated deflate streams (e.g. in gzip files) + - More readable code (I hope) +- New and improved crc32() + - About 50% faster, thanks to suggestions from Rodney Brown +- Add deflateBound() and compressBound() functions +- Fix memory leak in deflateInit2() +- Permit setting dictionary for raw deflate (for parallel deflate) +- Fix const declaration for gzwrite() +- Check for some malloc() failures in gzio.c +- Fix bug in gzopen() on single-byte file 0x1f +- Fix bug in gzread() on concatenated file with 0x1f at end of buffer + and next buffer doesn't start with 0x8b +- Fix uncompress() to return Z_DATA_ERROR on truncated input +- Free memory at end of example.c +- Remove MAX #define in trees.c (conflicted with some libraries) +- Fix static const's in deflate.c, gzio.c, and zutil.[ch] +- Declare malloc() and free() in gzio.c if STDC not defined +- Use malloc() instead of calloc() in zutil.c if int big enough +- Define STDC for AIX +- Add aix/ with approach for compiling shared library on AIX +- Add HP-UX support for shared libraries in configure +- Add OpenUNIX support for shared libraries in configure +- Use $cc instead of gcc to build shared library +- Make prefix directory if needed when installing +- Correct Macintosh avoidance of typedef Byte in zconf.h +- Correct Turbo C memory allocation when under Linux +- Use libz.a instead of -lz in Makefile (assure use of compiled library) +- Update configure to check for snprintf or vsnprintf functions and their + return value, warn during make if using an insecure function +- Fix configure problem with compile-time knowledge of HAVE_UNISTD_H that + is lost when library is used--resolution is to build new zconf.h +- Documentation improvements (in zlib.h): + - Document raw deflate and inflate + - Update RFCs URL + - Point out that zlib and gzip formats are different + - Note that Z_BUF_ERROR is not fatal + - Document string limit for gzprintf() and possible buffer overflow + - Note requirement on avail_out when flushing + - Note permitted values of flush parameter of inflate() +- Add some FAQs (and even answers) to the FAQ +- Add contrib/inflate86/ for x86 faster inflate +- Add contrib/blast/ for PKWare Data Compression Library decompression +- Add contrib/puff/ simple inflate for deflate format description + +Changes in 1.1.4 (11 March 2002) +- ZFREE was repeated on same allocation on some error conditions. + This creates a security problem described in + http://www.zlib.org/advisory-2002-03-11.txt +- Returned incorrect error (Z_MEM_ERROR) on some invalid data +- Avoid accesses before window for invalid distances with inflate window + less than 32K. +- force windowBits > 8 to avoid a bug in the encoder for a window size + of 256 bytes. (A complete fix will be available in 1.1.5). + +Changes in 1.1.3 (9 July 1998) +- fix "an inflate input buffer bug that shows up on rare but persistent + occasions" (Mark) +- fix gzread and gztell for concatenated .gz files (Didier Le Botlan) +- fix gzseek(..., SEEK_SET) in write mode +- fix crc check after a gzeek (Frank Faubert) +- fix miniunzip when the last entry in a zip file is itself a zip file + (J Lillge) +- add contrib/asm586 and contrib/asm686 (Brian Raiter) + See http://www.muppetlabs.com/~breadbox/software/assembly.html +- add support for Delphi 3 in contrib/delphi (Bob Dellaca) +- add support for C++Builder 3 and Delphi 3 in contrib/delphi2 (Davide Moretti) +- do not exit prematurely in untgz if 0 at start of block (Magnus Holmgren) +- use macro EXTERN instead of extern to support DLL for BeOS (Sander Stoks) +- added a FAQ file + +- Support gzdopen on Mac with Metrowerks (Jason Linhart) +- Do not redefine Byte on Mac (Brad Pettit & Jason Linhart) +- define SEEK_END too if SEEK_SET is not defined (Albert Chin-A-Young) +- avoid some warnings with Borland C (Tom Tanner) +- fix a problem in contrib/minizip/zip.c for 16-bit MSDOS (Gilles Vollant) +- emulate utime() for WIN32 in contrib/untgz (Gilles Vollant) +- allow several arguments to configure (Tim Mooney, Frodo Looijaard) +- use libdir and includedir in Makefile.in (Tim Mooney) +- support shared libraries on OSF1 V4 (Tim Mooney) +- remove so_locations in "make clean" (Tim Mooney) +- fix maketree.c compilation error (Glenn, Mark) +- Python interface to zlib now in Python 1.5 (Jeremy Hylton) +- new Makefile.riscos (Rich Walker) +- initialize static descriptors in trees.c for embedded targets (Nick Smith) +- use "foo-gz" in example.c for RISCOS and VMS (Nick Smith) +- add the OS/2 files in Makefile.in too (Andrew Zabolotny) +- fix fdopen and halloc macros for Microsoft C 6.0 (Tom Lane) +- fix maketree.c to allow clean compilation of inffixed.h (Mark) +- fix parameter check in deflateCopy (Gunther Nikl) +- cleanup trees.c, use compressed_len only in debug mode (Christian Spieler) +- Many portability patches by Christian Spieler: + . zutil.c, zutil.h: added "const" for zmem* + . Make_vms.com: fixed some typos + . Make_vms.com: msdos/Makefile.*: removed zutil.h from some dependency lists + . msdos/Makefile.msc: remove "default rtl link library" info from obj files + . msdos/Makefile.*: use model-dependent name for the built zlib library + . msdos/Makefile.emx, nt/Makefile.emx, nt/Makefile.gcc: + new makefiles, for emx (DOS/OS2), emx&rsxnt and mingw32 (Windows 9x / NT) +- use define instead of typedef for Bytef also for MSC small/medium (Tom Lane) +- replace __far with _far for better portability (Christian Spieler, Tom Lane) +- fix test for errno.h in configure (Tim Newsham) + +Changes in 1.1.2 (19 March 98) +- added contrib/minzip, mini zip and unzip based on zlib (Gilles Vollant) + See http://www.winimage.com/zLibDll/unzip.html +- preinitialize the inflate tables for fixed codes, to make the code + completely thread safe (Mark) +- some simplifications and slight speed-up to the inflate code (Mark) +- fix gzeof on non-compressed files (Allan Schrum) +- add -std1 option in configure for OSF1 to fix gzprintf (Martin Mokrejs) +- use default value of 4K for Z_BUFSIZE for 16-bit MSDOS (Tim Wegner + Glenn) +- added os2/Makefile.def and os2/zlib.def (Andrew Zabolotny) +- add shared lib support for UNIX_SV4.2MP (MATSUURA Takanori) +- do not wrap extern "C" around system includes (Tom Lane) +- mention zlib binding for TCL in README (Andreas Kupries) +- added amiga/Makefile.pup for Amiga powerUP SAS/C PPC (Andreas Kleinert) +- allow "make install prefix=..." even after configure (Glenn Randers-Pehrson) +- allow "configure --prefix $HOME" (Tim Mooney) +- remove warnings in example.c and gzio.c (Glenn Randers-Pehrson) +- move Makefile.sas to amiga/Makefile.sas + +Changes in 1.1.1 (27 Feb 98) +- fix macros _tr_tally_* in deflate.h for debug mode (Glenn Randers-Pehrson) +- remove block truncation heuristic which had very marginal effect for zlib + (smaller lit_bufsize than in gzip 1.2.4) and degraded a little the + compression ratio on some files. This also allows inlining _tr_tally for + matches in deflate_slow. +- added msdos/Makefile.w32 for WIN32 Microsoft Visual C++ (Bob Frazier) + +Changes in 1.1.0 (24 Feb 98) +- do not return STREAM_END prematurely in inflate (John Bowler) +- revert to the zlib 1.0.8 inflate to avoid the gcc 2.8.0 bug (Jeremy Buhler) +- compile with -DFASTEST to get compression code optimized for speed only +- in minigzip, try mmap'ing the input file first (Miguel Albrecht) +- increase size of I/O buffers in minigzip.c and gzio.c (not a big gain + on Sun but significant on HP) + +- add a pointer to experimental unzip library in README (Gilles Vollant) +- initialize variable gcc in configure (Chris Herborth) + +Changes in 1.0.9 (17 Feb 1998) +- added gzputs and gzgets functions +- do not clear eof flag in gzseek (Mark Diekhans) +- fix gzseek for files in transparent mode (Mark Diekhans) +- do not assume that vsprintf returns the number of bytes written (Jens Krinke) +- replace EXPORT with ZEXPORT to avoid conflict with other programs +- added compress2 in zconf.h, zlib.def, zlib.dnt +- new asm code from Gilles Vollant in contrib/asm386 +- simplify the inflate code (Mark): + . Replace ZALLOC's in huft_build() with single ZALLOC in inflate_blocks_new() + . ZALLOC the length list in inflate_trees_fixed() instead of using stack + . ZALLOC the value area for huft_build() instead of using stack + . Simplify Z_FINISH check in inflate() + +- Avoid gcc 2.8.0 comparison bug a little differently than zlib 1.0.8 +- in inftrees.c, avoid cc -O bug on HP (Farshid Elahi) +- in zconf.h move the ZLIB_DLL stuff earlier to avoid problems with + the declaration of FAR (Gilles VOllant) +- install libz.so* with mode 755 (executable) instead of 644 (Marc Lehmann) +- read_buf buf parameter of type Bytef* instead of charf* +- zmemcpy parameters are of type Bytef*, not charf* (Joseph Strout) +- do not redeclare unlink in minigzip.c for WIN32 (John Bowler) +- fix check for presence of directories in "make install" (Ian Willis) + +Changes in 1.0.8 (27 Jan 1998) +- fixed offsets in contrib/asm386/gvmat32.asm (Gilles Vollant) +- fix gzgetc and gzputc for big endian systems (Markus Oberhumer) +- added compress2() to allow setting the compression level +- include sys/types.h to get off_t on some systems (Marc Lehmann & QingLong) +- use constant arrays for the static trees in trees.c instead of computing + them at run time (thanks to Ken Raeburn for this suggestion). To create + trees.h, compile with GEN_TREES_H and run "make test". +- check return code of example in "make test" and display result +- pass minigzip command line options to file_compress +- simplifying code of inflateSync to avoid gcc 2.8 bug + +- support CC="gcc -Wall" in configure -s (QingLong) +- avoid a flush caused by ftell in gzopen for write mode (Ken Raeburn) +- fix test for shared library support to avoid compiler warnings +- zlib.lib -> zlib.dll in msdos/zlib.rc (Gilles Vollant) +- check for TARGET_OS_MAC in addition to MACOS (Brad Pettit) +- do not use fdopen for Metrowerks on Mac (Brad Pettit)) +- add checks for gzputc and gzputc in example.c +- avoid warnings in gzio.c and deflate.c (Andreas Kleinert) +- use const for the CRC table (Ken Raeburn) +- fixed "make uninstall" for shared libraries +- use Tracev instead of Trace in infblock.c +- in example.c use correct compressed length for test_sync +- suppress +vnocompatwarnings in configure for HPUX (not always supported) + +Changes in 1.0.7 (20 Jan 1998) +- fix gzseek which was broken in write mode +- return error for gzseek to negative absolute position +- fix configure for Linux (Chun-Chung Chen) +- increase stack space for MSC (Tim Wegner) +- get_crc_table and inflateSyncPoint are EXPORTed (Gilles Vollant) +- define EXPORTVA for gzprintf (Gilles Vollant) +- added man page zlib.3 (Rick Rodgers) +- for contrib/untgz, fix makedir() and improve Makefile + +- check gzseek in write mode in example.c +- allocate extra buffer for seeks only if gzseek is actually called +- avoid signed/unsigned comparisons (Tim Wegner, Gilles Vollant) +- add inflateSyncPoint in zconf.h +- fix list of exported functions in nt/zlib.dnt and mdsos/zlib.def + +Changes in 1.0.6 (19 Jan 1998) +- add functions gzprintf, gzputc, gzgetc, gztell, gzeof, gzseek, gzrewind and + gzsetparams (thanks to Roland Giersig and Kevin Ruland for some of this code) +- Fix a deflate bug occurring only with compression level 0 (thanks to + Andy Buckler for finding this one). +- In minigzip, pass transparently also the first byte for .Z files. +- return Z_BUF_ERROR instead of Z_OK if output buffer full in uncompress() +- check Z_FINISH in inflate (thanks to Marc Schluper) +- Implement deflateCopy (thanks to Adam Costello) +- make static libraries by default in configure, add --shared option. +- move MSDOS or Windows specific files to directory msdos +- suppress the notion of partial flush to simplify the interface + (but the symbol Z_PARTIAL_FLUSH is kept for compatibility with 1.0.4) +- suppress history buffer provided by application to simplify the interface + (this feature was not implemented anyway in 1.0.4) +- next_in and avail_in must be initialized before calling inflateInit or + inflateInit2 +- add EXPORT in all exported functions (for Windows DLL) +- added Makefile.nt (thanks to Stephen Williams) +- added the unsupported "contrib" directory: + contrib/asm386/ by Gilles Vollant + 386 asm code replacing longest_match(). + contrib/iostream/ by Kevin Ruland + A C++ I/O streams interface to the zlib gz* functions + contrib/iostream2/ by Tyge Løvset + Another C++ I/O streams interface + contrib/untgz/ by "Pedro A. Aranda Guti\irrez" + A very simple tar.gz file extractor using zlib + contrib/visual-basic.txt by Carlos Rios + How to use compress(), uncompress() and the gz* functions from VB. +- pass params -f (filtered data), -h (huffman only), -1 to -9 (compression + level) in minigzip (thanks to Tom Lane) + +- use const for rommable constants in deflate +- added test for gzseek and gztell in example.c +- add undocumented function inflateSyncPoint() (hack for Paul Mackerras) +- add undocumented function zError to convert error code to string + (for Tim Smithers) +- Allow compilation of gzio with -DNO_DEFLATE to avoid the compression code. +- Use default memcpy for Symantec MSDOS compiler. +- Add EXPORT keyword for check_func (needed for Windows DLL) +- add current directory to LD_LIBRARY_PATH for "make test" +- create also a link for libz.so.1 +- added support for FUJITSU UXP/DS (thanks to Toshiaki Nomura) +- use $(SHAREDLIB) instead of libz.so in Makefile.in (for HPUX) +- added -soname for Linux in configure (Chun-Chung Chen, +- assign numbers to the exported functions in zlib.def (for Windows DLL) +- add advice in zlib.h for best usage of deflateSetDictionary +- work around compiler bug on Atari (cast Z_NULL in call of s->checkfn) +- allow compilation with ANSI keywords only enabled for TurboC in large model +- avoid "versionString"[0] (Borland bug) +- add NEED_DUMMY_RETURN for Borland +- use variable z_verbose for tracing in debug mode (L. Peter Deutsch). +- allow compilation with CC +- defined STDC for OS/2 (David Charlap) +- limit external names to 8 chars for MVS (Thomas Lund) +- in minigzip.c, use static buffers only for 16-bit systems +- fix suffix check for "minigzip -d foo.gz" +- do not return an error for the 2nd of two consecutive gzflush() (Felix Lee) +- use _fdopen instead of fdopen for MSC >= 6.0 (Thomas Fanslau) +- added makelcc.bat for lcc-win32 (Tom St Denis) +- in Makefile.dj2, use copy and del instead of install and rm (Frank Donahoe) +- Avoid expanded $Id$. Use "rcs -kb" or "cvs admin -kb" to avoid Id expansion. +- check for unistd.h in configure (for off_t) +- remove useless check parameter in inflate_blocks_free +- avoid useless assignment of s->check to itself in inflate_blocks_new +- do not flush twice in gzclose (thanks to Ken Raeburn) +- rename FOPEN as F_OPEN to avoid clash with /usr/include/sys/file.h +- use NO_ERRNO_H instead of enumeration of operating systems with errno.h +- work around buggy fclose on pipes for HP/UX +- support zlib DLL with BORLAND C++ 5.0 (thanks to Glenn Randers-Pehrson) +- fix configure if CC is already equal to gcc + +Changes in 1.0.5 (3 Jan 98) +- Fix inflate to terminate gracefully when fed corrupted or invalid data +- Use const for rommable constants in inflate +- Eliminate memory leaks on error conditions in inflate +- Removed some vestigial code in inflate +- Update web address in README + +Changes in 1.0.4 (24 Jul 96) +- In very rare conditions, deflate(s, Z_FINISH) could fail to produce an EOF + bit, so the decompressor could decompress all the correct data but went + on to attempt decompressing extra garbage data. This affected minigzip too. +- zlibVersion and gzerror return const char* (needed for DLL) +- port to RISCOS (no fdopen, no multiple dots, no unlink, no fileno) +- use z_error only for DEBUG (avoid problem with DLLs) + +Changes in 1.0.3 (2 Jul 96) +- use z_streamp instead of z_stream *, which is now a far pointer in MSDOS + small and medium models; this makes the library incompatible with previous + versions for these models. (No effect in large model or on other systems.) +- return OK instead of BUF_ERROR if previous deflate call returned with + avail_out as zero but there is nothing to do +- added memcmp for non STDC compilers +- define NO_DUMMY_DECL for more Mac compilers (.h files merged incorrectly) +- define __32BIT__ if __386__ or i386 is defined (pb. with Watcom and SCO) +- better check for 16-bit mode MSC (avoids problem with Symantec) + +Changes in 1.0.2 (23 May 96) +- added Windows DLL support +- added a function zlibVersion (for the DLL support) +- fixed declarations using Bytef in infutil.c (pb with MSDOS medium model) +- Bytef is define's instead of typedef'd only for Borland C +- avoid reading uninitialized memory in example.c +- mention in README that the zlib format is now RFC1950 +- updated Makefile.dj2 +- added algorithm.doc + +Changes in 1.0.1 (20 May 96) [1.0 skipped to avoid confusion] +- fix array overlay in deflate.c which sometimes caused bad compressed data +- fix inflate bug with empty stored block +- fix MSDOS medium model which was broken in 0.99 +- fix deflateParams() which could generated bad compressed data. +- Bytef is define'd instead of typedef'ed (work around Borland bug) +- added an INDEX file +- new makefiles for DJGPP (Makefile.dj2), 32-bit Borland (Makefile.b32), + Watcom (Makefile.wat), Amiga SAS/C (Makefile.sas) +- speed up adler32 for modern machines without auto-increment +- added -ansi for IRIX in configure +- static_init_done in trees.c is an int +- define unlink as delete for VMS +- fix configure for QNX +- add configure branch for SCO and HPUX +- avoid many warnings (unused variables, dead assignments, etc...) +- no fdopen for BeOS +- fix the Watcom fix for 32 bit mode (define FAR as empty) +- removed redefinition of Byte for MKWERKS +- work around an MWKERKS bug (incorrect merge of all .h files) + +Changes in 0.99 (27 Jan 96) +- allow preset dictionary shared between compressor and decompressor +- allow compression level 0 (no compression) +- add deflateParams in zlib.h: allow dynamic change of compression level + and compression strategy. +- test large buffers and deflateParams in example.c +- add optional "configure" to build zlib as a shared library +- suppress Makefile.qnx, use configure instead +- fixed deflate for 64-bit systems (detected on Cray) +- fixed inflate_blocks for 64-bit systems (detected on Alpha) +- declare Z_DEFLATED in zlib.h (possible parameter for deflateInit2) +- always return Z_BUF_ERROR when deflate() has nothing to do +- deflateInit and inflateInit are now macros to allow version checking +- prefix all global functions and types with z_ with -DZ_PREFIX +- make falloc completely reentrant (inftrees.c) +- fixed very unlikely race condition in ct_static_init +- free in reverse order of allocation to help memory manager +- use zlib-1.0/* instead of zlib/* inside the tar.gz +- make zlib warning-free with "gcc -O3 -Wall -Wwrite-strings -Wpointer-arith + -Wconversion -Wstrict-prototypes -Wmissing-prototypes" +- allow gzread on concatenated .gz files +- deflateEnd now returns Z_DATA_ERROR if it was premature +- deflate is finally (?) fully deterministic (no matches beyond end of input) +- Document Z_SYNC_FLUSH +- add uninstall in Makefile +- Check for __cpluplus in zlib.h +- Better test in ct_align for partial flush +- avoid harmless warnings for Borland C++ +- initialize hash_head in deflate.c +- avoid warning on fdopen (gzio.c) for HP cc -Aa +- include stdlib.h for STDC compilers +- include errno.h for Cray +- ignore error if ranlib doesn't exist +- call ranlib twice for NeXTSTEP +- use exec_prefix instead of prefix for libz.a +- renamed ct_* as _tr_* to avoid conflict with applications +- clear z->msg in inflateInit2 before any error return +- initialize opaque in example.c, gzio.c, deflate.c and inflate.c +- fixed typo in zconf.h (_GNUC__ => __GNUC__) +- check for WIN32 in zconf.h and zutil.c (avoid farmalloc in 32-bit mode) +- fix typo in Make_vms.com (f$trnlnm -> f$getsyi) +- in fcalloc, normalize pointer if size > 65520 bytes +- don't use special fcalloc for 32 bit Borland C++ +- use STDC instead of __GO32__ to avoid redeclaring exit, calloc, etc... +- use Z_BINARY instead of BINARY +- document that gzclose after gzdopen will close the file +- allow "a" as mode in gzopen. +- fix error checking in gzread +- allow skipping .gz extra-field on pipes +- added reference to Perl interface in README +- put the crc table in FAR data (I dislike more and more the medium model :) +- added get_crc_table +- added a dimension to all arrays (Borland C can't count). +- workaround Borland C bug in declaration of inflate_codes_new & inflate_fast +- guard against multiple inclusion of *.h (for precompiled header on Mac) +- Watcom C pretends to be Microsoft C small model even in 32 bit mode. +- don't use unsized arrays to avoid silly warnings by Visual C++: + warning C4746: 'inflate_mask' : unsized array treated as '__far' + (what's wrong with far data in far model?). +- define enum out of inflate_blocks_state to allow compilation with C++ + +Changes in 0.95 (16 Aug 95) +- fix MSDOS small and medium model (now easier to adapt to any compiler) +- inlined send_bits +- fix the final (:-) bug for deflate with flush (output was correct but + not completely flushed in rare occasions). +- default window size is same for compression and decompression + (it's now sufficient to set MAX_WBITS in zconf.h). +- voidp -> voidpf and voidnp -> voidp (for consistency with other + typedefs and because voidnp was not near in large model). + +Changes in 0.94 (13 Aug 95) +- support MSDOS medium model +- fix deflate with flush (could sometimes generate bad output) +- fix deflateReset (zlib header was incorrectly suppressed) +- added support for VMS +- allow a compression level in gzopen() +- gzflush now calls fflush +- For deflate with flush, flush even if no more input is provided. +- rename libgz.a as libz.a +- avoid complex expression in infcodes.c triggering Turbo C bug +- work around a problem with gcc on Alpha (in INSERT_STRING) +- don't use inline functions (problem with some gcc versions) +- allow renaming of Byte, uInt, etc... with #define. +- avoid warning about (unused) pointer before start of array in deflate.c +- avoid various warnings in gzio.c, example.c, infblock.c, adler32.c, zutil.c +- avoid reserved word 'new' in trees.c + +Changes in 0.93 (25 June 95) +- temporarily disable inline functions +- make deflate deterministic +- give enough lookahead for PARTIAL_FLUSH +- Set binary mode for stdin/stdout in minigzip.c for OS/2 +- don't even use signed char in inflate (not portable enough) +- fix inflate memory leak for segmented architectures + +Changes in 0.92 (3 May 95) +- don't assume that char is signed (problem on SGI) +- Clear bit buffer when starting a stored block +- no memcpy on Pyramid +- suppressed inftest.c +- optimized fill_window, put longest_match inline for gcc +- optimized inflate on stored blocks. +- untabify all sources to simplify patches + +Changes in 0.91 (2 May 95) +- Default MEM_LEVEL is 8 (not 9 for Unix) as documented in zlib.h +- Document the memory requirements in zconf.h +- added "make install" +- fix sync search logic in inflateSync +- deflate(Z_FULL_FLUSH) now works even if output buffer too short +- after inflateSync, don't scare people with just "lo world" +- added support for DJGPP + +Changes in 0.9 (1 May 95) +- don't assume that zalloc clears the allocated memory (the TurboC bug + was Mark's bug after all :) +- let again gzread copy uncompressed data unchanged (was working in 0.71) +- deflate(Z_FULL_FLUSH), inflateReset and inflateSync are now fully implemented +- added a test of inflateSync in example.c +- moved MAX_WBITS to zconf.h because users might want to change that. +- document explicitly that zalloc(64K) on MSDOS must return a normalized + pointer (zero offset) +- added Makefiles for Microsoft C, Turbo C, Borland C++ +- faster crc32() + +Changes in 0.8 (29 April 95) +- added fast inflate (inffast.c) +- deflate(Z_FINISH) now returns Z_STREAM_END when done. Warning: this + is incompatible with previous versions of zlib which returned Z_OK. +- work around a TurboC compiler bug (bad code for b << 0, see infutil.h) + (actually that was not a compiler bug, see 0.81 above) +- gzread no longer reads one extra byte in certain cases +- In gzio destroy(), don't reference a freed structure +- avoid many warnings for MSDOS +- avoid the ERROR symbol which is used by MS Windows + +Changes in 0.71 (14 April 95) +- Fixed more MSDOS compilation problems :( There is still a bug with + TurboC large model. + +Changes in 0.7 (14 April 95) +- Added full inflate support. +- Simplified the crc32() interface. The pre- and post-conditioning + (one's complement) is now done inside crc32(). WARNING: this is + incompatible with previous versions; see zlib.h for the new usage. + +Changes in 0.61 (12 April 95) +- workaround for a bug in TurboC. example and minigzip now work on MSDOS. + +Changes in 0.6 (11 April 95) +- added minigzip.c +- added gzdopen to reopen a file descriptor as gzFile +- added transparent reading of non-gziped files in gzread. +- fixed bug in gzread (don't read crc as data) +- fixed bug in destroy (gzio.c) (don't return Z_STREAM_END for gzclose). +- don't allocate big arrays in the stack (for MSDOS) +- fix some MSDOS compilation problems + +Changes in 0.5: +- do real compression in deflate.c. Z_PARTIAL_FLUSH is supported but + not yet Z_FULL_FLUSH. +- support decompression but only in a single step (forced Z_FINISH) +- added opaque object for zalloc and zfree. +- added deflateReset and inflateReset +- added a variable zlib_version for consistency checking. +- renamed the 'filter' parameter of deflateInit2 as 'strategy'. + Added Z_FILTERED and Z_HUFFMAN_ONLY constants. + +Changes in 0.4: +- avoid "zip" everywhere, use zlib instead of ziplib. +- suppress Z_BLOCK_FLUSH, interpret Z_PARTIAL_FLUSH as block flush + if compression method == 8. +- added adler32 and crc32 +- renamed deflateOptions as deflateInit2, call one or the other but not both +- added the method parameter for deflateInit2. +- added inflateInit2 +- simplied considerably deflateInit and inflateInit by not supporting + user-provided history buffer. This is supported only in deflateInit2 + and inflateInit2. + +Changes in 0.3: +- prefix all macro names with Z_ +- use Z_FINISH instead of deflateEnd to finish compression. +- added Z_HUFFMAN_ONLY +- added gzerror() diff --git a/neo/framework/zlib/FAQ b/neo/framework/zlib/FAQ new file mode 100644 index 00000000..441d910d --- /dev/null +++ b/neo/framework/zlib/FAQ @@ -0,0 +1,339 @@ + + Frequently Asked Questions about zlib + + +If your question is not there, please check the zlib home page +http://www.zlib.org which may have more recent information. +The lastest zlib FAQ is at http://www.gzip.org/zlib/zlib_faq.html + + + 1. Is zlib Y2K-compliant? + + Yes. zlib doesn't handle dates. + + 2. Where can I get a Windows DLL version? + + The zlib sources can be compiled without change to produce a DLL. + See the file win32/DLL_FAQ.txt in the zlib distribution. + Pointers to the precompiled DLL are found in the zlib web site at + http://www.zlib.org. + + 3. Where can I get a Visual Basic interface to zlib? + + See + * http://www.dogma.net/markn/articles/zlibtool/zlibtool.htm + * contrib/visual-basic.txt in the zlib distribution + * win32/DLL_FAQ.txt in the zlib distribution + + 4. compress() returns Z_BUF_ERROR. + + Make sure that before the call of compress, the length of the compressed + buffer is equal to the total size of the compressed buffer and not + zero. For Visual Basic, check that this parameter is passed by reference + ("as any"), not by value ("as long"). + + 5. deflate() or inflate() returns Z_BUF_ERROR. + + Before making the call, make sure that avail_in and avail_out are not + zero. When setting the parameter flush equal to Z_FINISH, also make sure + that avail_out is big enough to allow processing all pending input. + Note that a Z_BUF_ERROR is not fatal--another call to deflate() or + inflate() can be made with more input or output space. A Z_BUF_ERROR + may in fact be unavoidable depending on how the functions are used, since + it is not possible to tell whether or not there is more output pending + when strm.avail_out returns with zero. + + 6. Where's the zlib documentation (man pages, etc.)? + + It's in zlib.h for the moment, and Francis S. Lin has converted it to a + web page zlib.html. Volunteers to transform this to Unix-style man pages, + please contact us (zlib@gzip.org). Examples of zlib usage are in the files + example.c and minigzip.c. + + 7. Why don't you use GNU autoconf or libtool or ...? + + Because we would like to keep zlib as a very small and simple + package. zlib is rather portable and doesn't need much configuration. + + 8. I found a bug in zlib. + + Most of the time, such problems are due to an incorrect usage of + zlib. Please try to reproduce the problem with a small program and send + the corresponding source to us at zlib@gzip.org . Do not send + multi-megabyte data files without prior agreement. + + 9. Why do I get "undefined reference to gzputc"? + + If "make test" produces something like + + example.o(.text+0x154): undefined reference to `gzputc' + + check that you don't have old files libz.* in /usr/lib, /usr/local/lib or + /usr/X11R6/lib. Remove any old versions, then do "make install". + +10. I need a Delphi interface to zlib. + + See the contrib/delphi directory in the zlib distribution. + +11. Can zlib handle .zip archives? + + Not by itself, no. See the directory contrib/minizip in the zlib + distribution. + +12. Can zlib handle .Z files? + + No, sorry. You have to spawn an uncompress or gunzip subprocess, or adapt + the code of uncompress on your own. + +13. How can I make a Unix shared library? + + make clean + ./configure -s + make + +14. How do I install a shared zlib library on Unix? + + After the above, then: + + make install + + However, many flavors of Unix come with a shared zlib already installed. + Before going to the trouble of compiling a shared version of zlib and + trying to install it, you may want to check if it's already there! If you + can #include , it's there. The -lz option will probably link to it. + +15. I have a question about OttoPDF. + + We are not the authors of OttoPDF. The real author is on the OttoPDF web + site: Joel Hainley, jhainley@myndkryme.com. + +16. Can zlib decode Flate data in an Adobe PDF file? + + Yes. See http://www.fastio.com/ (ClibPDF), or http://www.pdflib.com/ . + To modify PDF forms, see http://sourceforge.net/projects/acroformtool/ . + +17. Why am I getting this "register_frame_info not found" error on Solaris? + + After installing zlib 1.1.4 on Solaris 2.6, running applications using zlib + generates an error such as: + + ld.so.1: rpm: fatal: relocation error: file /usr/local/lib/libz.so: + symbol __register_frame_info: referenced symbol not found + + The symbol __register_frame_info is not part of zlib, it is generated by + the C compiler (cc or gcc). You must recompile applications using zlib + which have this problem. This problem is specific to Solaris. See + http://www.sunfreeware.com for Solaris versions of zlib and applications + using zlib. + +18. Why does gzip give an error on a file I make with compress/deflate? + + The compress and deflate functions produce data in the zlib format, which + is different and incompatible with the gzip format. The gz* functions in + zlib on the other hand use the gzip format. Both the zlib and gzip + formats use the same compressed data format internally, but have different + headers and trailers around the compressed data. + +19. Ok, so why are there two different formats? + + The gzip format was designed to retain the directory information about + a single file, such as the name and last modification date. The zlib + format on the other hand was designed for in-memory and communication + channel applications, and has a much more compact header and trailer and + uses a faster integrity check than gzip. + +20. Well that's nice, but how do I make a gzip file in memory? + + You can request that deflate write the gzip format instead of the zlib + format using deflateInit2(). You can also request that inflate decode + the gzip format using inflateInit2(). Read zlib.h for more details. + +21. Is zlib thread-safe? + + Yes. However any library routines that zlib uses and any application- + provided memory allocation routines must also be thread-safe. zlib's gz* + functions use stdio library routines, and most of zlib's functions use the + library memory allocation routines by default. zlib's Init functions allow + for the application to provide custom memory allocation routines. + + Of course, you should only operate on any given zlib or gzip stream from a + single thread at a time. + +22. Can I use zlib in my commercial application? + + Yes. Please read the license in zlib.h. + +23. Is zlib under the GNU license? + + No. Please read the license in zlib.h. + +24. The license says that altered source versions must be "plainly marked". So + what exactly do I need to do to meet that requirement? + + You need to change the ZLIB_VERSION and ZLIB_VERNUM #defines in zlib.h. In + particular, the final version number needs to be changed to "f", and an + identification string should be appended to ZLIB_VERSION. Version numbers + x.x.x.f are reserved for modifications to zlib by others than the zlib + maintainers. For example, if the version of the base zlib you are altering + is "1.2.3.4", then in zlib.h you should change ZLIB_VERNUM to 0x123f, and + ZLIB_VERSION to something like "1.2.3.f-zachary-mods-v3". You can also + update the version strings in deflate.c and inftrees.c. + + For altered source distributions, you should also note the origin and + nature of the changes in zlib.h, as well as in ChangeLog and README, along + with the dates of the alterations. The origin should include at least your + name (or your company's name), and an email address to contact for help or + issues with the library. + + Note that distributing a compiled zlib library along with zlib.h and + zconf.h is also a source distribution, and so you should change + ZLIB_VERSION and ZLIB_VERNUM and note the origin and nature of the changes + in zlib.h as you would for a full source distribution. + +25. Will zlib work on a big-endian or little-endian architecture, and can I + exchange compressed data between them? + + Yes and yes. + +26. Will zlib work on a 64-bit machine? + + It should. It has been tested on 64-bit machines, and has no dependence + on any data types being limited to 32-bits in length. If you have any + difficulties, please provide a complete problem report to zlib@gzip.org + +27. Will zlib decompress data from the PKWare Data Compression Library? + + No. The PKWare DCL uses a completely different compressed data format + than does PKZIP and zlib. However, you can look in zlib's contrib/blast + directory for a possible solution to your problem. + +28. Can I access data randomly in a compressed stream? + + No, not without some preparation. If when compressing you periodically + use Z_FULL_FLUSH, carefully write all the pending data at those points, + and keep an index of those locations, then you can start decompression + at those points. You have to be careful to not use Z_FULL_FLUSH too + often, since it can significantly degrade compression. + +29. Does zlib work on MVS, OS/390, CICS, etc.? + + We don't know for sure. We have heard occasional reports of success on + these systems. If you do use it on one of these, please provide us with + a report, instructions, and patches that we can reference when we get + these questions. Thanks. + +30. Is there some simpler, easier to read version of inflate I can look at + to understand the deflate format? + + First off, you should read RFC 1951. Second, yes. Look in zlib's + contrib/puff directory. + +31. Does zlib infringe on any patents? + + As far as we know, no. In fact, that was originally the whole point behind + zlib. Look here for some more information: + + http://www.gzip.org/#faq11 + +32. Can zlib work with greater than 4 GB of data? + + Yes. inflate() and deflate() will process any amount of data correctly. + Each call of inflate() or deflate() is limited to input and output chunks + of the maximum value that can be stored in the compiler's "unsigned int" + type, but there is no limit to the number of chunks. Note however that the + strm.total_in and strm_total_out counters may be limited to 4 GB. These + counters are provided as a convenience and are not used internally by + inflate() or deflate(). The application can easily set up its own counters + updated after each call of inflate() or deflate() to count beyond 4 GB. + compress() and uncompress() may be limited to 4 GB, since they operate in a + single call. gzseek() and gztell() may be limited to 4 GB depending on how + zlib is compiled. See the zlibCompileFlags() function in zlib.h. + + The word "may" appears several times above since there is a 4 GB limit + only if the compiler's "long" type is 32 bits. If the compiler's "long" + type is 64 bits, then the limit is 16 exabytes. + +33. Does zlib have any security vulnerabilities? + + The only one that we are aware of is potentially in gzprintf(). If zlib + is compiled to use sprintf() or vsprintf(), then there is no protection + against a buffer overflow of a 4K string space, other than the caller of + gzprintf() assuring that the output will not exceed 4K. On the other + hand, if zlib is compiled to use snprintf() or vsnprintf(), which should + normally be the case, then there is no vulnerability. The ./configure + script will display warnings if an insecure variation of sprintf() will + be used by gzprintf(). Also the zlibCompileFlags() function will return + information on what variant of sprintf() is used by gzprintf(). + + If you don't have snprintf() or vsnprintf() and would like one, you can + find a portable implementation here: + + http://www.ijs.si/software/snprintf/ + + Note that you should be using the most recent version of zlib. Versions + 1.1.3 and before were subject to a double-free vulnerability. + +34. Is there a Java version of zlib? + + Probably what you want is to use zlib in Java. zlib is already included + as part of the Java SDK in the java.util.zip package. If you really want + a version of zlib written in the Java language, look on the zlib home + page for links: http://www.zlib.org/ + +35. I get this or that compiler or source-code scanner warning when I crank it + up to maximally-pedantic. Can't you guys write proper code? + + Many years ago, we gave up attempting to avoid warnings on every compiler + in the universe. It just got to be a waste of time, and some compilers + were downright silly. So now, we simply make sure that the code always + works. + +36. Valgrind (or some similar memory access checker) says that deflate is + performing a conditional jump that depends on an uninitialized value. + Isn't that a bug? + + No. That is intentional for performance reasons, and the output of + deflate is not affected. This only started showing up recently since + zlib 1.2.x uses malloc() by default for allocations, whereas earlier + versions used calloc(), which zeros out the allocated memory. + +37. Will zlib read the (insert any ancient or arcane format here) compressed + data format? + + Probably not. Look in the comp.compression FAQ for pointers to various + formats and associated software. + +38. How can I encrypt/decrypt zip files with zlib? + + zlib doesn't support encryption. The original PKZIP encryption is very weak + and can be broken with freely available programs. To get strong encryption, + use GnuPG, http://www.gnupg.org/ , which already includes zlib compression. + For PKZIP compatible "encryption", look at http://www.info-zip.org/ + +39. What's the difference between the "gzip" and "deflate" HTTP 1.1 encodings? + + "gzip" is the gzip format, and "deflate" is the zlib format. They should + probably have called the second one "zlib" instead to avoid confusion + with the raw deflate compressed data format. While the HTTP 1.1 RFC 2616 + correctly points to the zlib specification in RFC 1950 for the "deflate" + transfer encoding, there have been reports of servers and browsers that + incorrectly produce or expect raw deflate data per the deflate + specficiation in RFC 1951, most notably Microsoft. So even though the + "deflate" transfer encoding using the zlib format would be the more + efficient approach (and in fact exactly what the zlib format was designed + for), using the "gzip" transfer encoding is probably more reliable due to + an unfortunate choice of name on the part of the HTTP 1.1 authors. + + Bottom line: use the gzip format for HTTP 1.1 encoding. + +40. Does zlib support the new "Deflate64" format introduced by PKWare? + + No. PKWare has apparently decided to keep that format proprietary, since + they have not documented it as they have previous compression formats. + In any case, the compression improvements are so modest compared to other + more modern approaches, that it's not worth the effort to implement. + +41. Can you please sign these lengthy legal documents and fax them back to us + so that we can use your software in our product? + + No. Go away. Shoo. diff --git a/neo/framework/zlib/INDEX b/neo/framework/zlib/INDEX new file mode 100644 index 00000000..0587e590 --- /dev/null +++ b/neo/framework/zlib/INDEX @@ -0,0 +1,51 @@ +ChangeLog history of changes +FAQ Frequently Asked Questions about zlib +INDEX this file +Makefile makefile for Unix (generated by configure) +Makefile.in makefile for Unix (template for configure) +README guess what +algorithm.txt description of the (de)compression algorithm +configure configure script for Unix +zconf.in.h template for zconf.h (used by configure) + +amiga/ makefiles for Amiga SAS C +as400/ makefiles for IBM AS/400 +msdos/ makefiles for MSDOS +old/ makefiles for various architectures and zlib documentation + files that have not yet been updated for zlib 1.2.x +projects/ projects for various Integrated Development Environments +qnx/ makefiles for QNX +win32/ makefiles for Windows + + zlib public header files (must be kept): +zconf.h +zlib.h + + private source files used to build the zlib library: +adler32.c +compress.c +crc32.c +crc32.h +deflate.c +deflate.h +gzio.c +infback.c +inffast.c +inffast.h +inffixed.h +inflate.c +inflate.h +inftrees.c +inftrees.h +trees.c +trees.h +uncompr.c +zutil.c +zutil.h + + source files for sample programs: +example.c +minigzip.c + + unsupported contribution by third parties +See contrib/README.contrib diff --git a/neo/framework/zlib/Makefile b/neo/framework/zlib/Makefile new file mode 100644 index 00000000..2fd6e45c --- /dev/null +++ b/neo/framework/zlib/Makefile @@ -0,0 +1,154 @@ +# Makefile for zlib +# Copyright (C) 1995-2005 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile and test, type: +# ./configure; make test +# The call of configure is optional if you don't have special requirements +# If you wish to build zlib as a shared library, use: ./configure -s + +# To use the asm code, type: +# cp contrib/asm?86/match.S ./match.S +# make LOC=-DASMV OBJA=match.o + +# To install /usr/local/lib/libz.* and /usr/local/include/zlib.h, type: +# make install +# To install in $HOME instead of /usr/local, use: +# make install prefix=$HOME + +CC=cc + +CFLAGS=-O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-g -DDEBUG +#CFLAGS=-O3 -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ +# -Wstrict-prototypes -Wmissing-prototypes + +LDFLAGS=libz.a +LDSHARED=$(CC) +CPP=$(CC) -E + +LIBS=libz.a +SHAREDLIB=libz.so +SHAREDLIBV=libz.so.1.2.3 +SHAREDLIBM=libz.so.1 + +AR=ar rc +RANLIB=ranlib +TAR=tar +SHELL=/bin/sh +EXE= + +prefix = /usr/local +exec_prefix = ${prefix} +libdir = ${exec_prefix}/lib +includedir = ${prefix}/include +mandir = ${prefix}/share/man +man3dir = ${mandir}/man3 + +OBJS = adler32.o compress.o crc32.o gzio.o uncompr.o deflate.o trees.o \ + zutil.o inflate.o infback.o inftrees.o inffast.o + +OBJA = +# to use the asm code: make OBJA=match.o + +TEST_OBJS = example.o minigzip.o + +all: example$(EXE) minigzip$(EXE) + +check: test +test: all + @LD_LIBRARY_PATH=.:$(LD_LIBRARY_PATH) ; export LD_LIBRARY_PATH; \ + echo hello world | ./minigzip | ./minigzip -d || \ + echo ' *** minigzip test FAILED ***' ; \ + if ./example; then \ + echo ' *** zlib test OK ***'; \ + else \ + echo ' *** zlib test FAILED ***'; \ + fi + +libz.a: $(OBJS) $(OBJA) + $(AR) $@ $(OBJS) $(OBJA) + -@ ($(RANLIB) $@ || true) >/dev/null 2>&1 + +match.o: match.S + $(CPP) match.S > _match.s + $(CC) -c _match.s + mv _match.o match.o + rm -f _match.s + +$(SHAREDLIBV): $(OBJS) + $(LDSHARED) -o $@ $(OBJS) + rm -f $(SHAREDLIB) $(SHAREDLIBM) + ln -s $@ $(SHAREDLIB) + ln -s $@ $(SHAREDLIBM) + +example$(EXE): example.o $(LIBS) + $(CC) $(CFLAGS) -o $@ example.o $(LDFLAGS) + +minigzip$(EXE): minigzip.o $(LIBS) + $(CC) $(CFLAGS) -o $@ minigzip.o $(LDFLAGS) + +install: $(LIBS) + -@if [ ! -d $(exec_prefix) ]; then mkdir -p $(exec_prefix); fi + -@if [ ! -d $(includedir) ]; then mkdir -p $(includedir); fi + -@if [ ! -d $(libdir) ]; then mkdir -p $(libdir); fi + -@if [ ! -d $(man3dir) ]; then mkdir -p $(man3dir); fi + cp zlib.h zconf.h $(includedir) + chmod 644 $(includedir)/zlib.h $(includedir)/zconf.h + cp $(LIBS) $(libdir) + cd $(libdir); chmod 755 $(LIBS) + -@(cd $(libdir); $(RANLIB) libz.a || true) >/dev/null 2>&1 + cd $(libdir); if test -f $(SHAREDLIBV); then \ + rm -f $(SHAREDLIB) $(SHAREDLIBM); \ + ln -s $(SHAREDLIBV) $(SHAREDLIB); \ + ln -s $(SHAREDLIBV) $(SHAREDLIBM); \ + (ldconfig || true) >/dev/null 2>&1; \ + fi + cp zlib.3 $(man3dir) + chmod 644 $(man3dir)/zlib.3 +# The ranlib in install is needed on NeXTSTEP which checks file times +# ldconfig is for Linux + +uninstall: + cd $(includedir); \ + cd $(libdir); rm -f libz.a; \ + if test -f $(SHAREDLIBV); then \ + rm -f $(SHAREDLIBV) $(SHAREDLIB) $(SHAREDLIBM); \ + fi + cd $(man3dir); rm -f zlib.3 + +mostlyclean: clean +clean: + rm -f *.o *~ example$(EXE) minigzip$(EXE) \ + libz.* foo.gz so_locations \ + _match.s maketree contrib/infback9/*.o + +maintainer-clean: distclean +distclean: clean + cp -p Makefile.in Makefile + cp -p zconf.in.h zconf.h + rm -f .DS_Store + +tags: + etags *.[ch] + +depend: + makedepend -- $(CFLAGS) -- *.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: crc32.h zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +example.o: zlib.h zconf.h +gzio.o: zutil.h zlib.h zconf.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +minigzip.o: zlib.h zconf.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h diff --git a/neo/framework/zlib/Makefile.in b/neo/framework/zlib/Makefile.in new file mode 100644 index 00000000..2fd6e45c --- /dev/null +++ b/neo/framework/zlib/Makefile.in @@ -0,0 +1,154 @@ +# Makefile for zlib +# Copyright (C) 1995-2005 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile and test, type: +# ./configure; make test +# The call of configure is optional if you don't have special requirements +# If you wish to build zlib as a shared library, use: ./configure -s + +# To use the asm code, type: +# cp contrib/asm?86/match.S ./match.S +# make LOC=-DASMV OBJA=match.o + +# To install /usr/local/lib/libz.* and /usr/local/include/zlib.h, type: +# make install +# To install in $HOME instead of /usr/local, use: +# make install prefix=$HOME + +CC=cc + +CFLAGS=-O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-g -DDEBUG +#CFLAGS=-O3 -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ +# -Wstrict-prototypes -Wmissing-prototypes + +LDFLAGS=libz.a +LDSHARED=$(CC) +CPP=$(CC) -E + +LIBS=libz.a +SHAREDLIB=libz.so +SHAREDLIBV=libz.so.1.2.3 +SHAREDLIBM=libz.so.1 + +AR=ar rc +RANLIB=ranlib +TAR=tar +SHELL=/bin/sh +EXE= + +prefix = /usr/local +exec_prefix = ${prefix} +libdir = ${exec_prefix}/lib +includedir = ${prefix}/include +mandir = ${prefix}/share/man +man3dir = ${mandir}/man3 + +OBJS = adler32.o compress.o crc32.o gzio.o uncompr.o deflate.o trees.o \ + zutil.o inflate.o infback.o inftrees.o inffast.o + +OBJA = +# to use the asm code: make OBJA=match.o + +TEST_OBJS = example.o minigzip.o + +all: example$(EXE) minigzip$(EXE) + +check: test +test: all + @LD_LIBRARY_PATH=.:$(LD_LIBRARY_PATH) ; export LD_LIBRARY_PATH; \ + echo hello world | ./minigzip | ./minigzip -d || \ + echo ' *** minigzip test FAILED ***' ; \ + if ./example; then \ + echo ' *** zlib test OK ***'; \ + else \ + echo ' *** zlib test FAILED ***'; \ + fi + +libz.a: $(OBJS) $(OBJA) + $(AR) $@ $(OBJS) $(OBJA) + -@ ($(RANLIB) $@ || true) >/dev/null 2>&1 + +match.o: match.S + $(CPP) match.S > _match.s + $(CC) -c _match.s + mv _match.o match.o + rm -f _match.s + +$(SHAREDLIBV): $(OBJS) + $(LDSHARED) -o $@ $(OBJS) + rm -f $(SHAREDLIB) $(SHAREDLIBM) + ln -s $@ $(SHAREDLIB) + ln -s $@ $(SHAREDLIBM) + +example$(EXE): example.o $(LIBS) + $(CC) $(CFLAGS) -o $@ example.o $(LDFLAGS) + +minigzip$(EXE): minigzip.o $(LIBS) + $(CC) $(CFLAGS) -o $@ minigzip.o $(LDFLAGS) + +install: $(LIBS) + -@if [ ! -d $(exec_prefix) ]; then mkdir -p $(exec_prefix); fi + -@if [ ! -d $(includedir) ]; then mkdir -p $(includedir); fi + -@if [ ! -d $(libdir) ]; then mkdir -p $(libdir); fi + -@if [ ! -d $(man3dir) ]; then mkdir -p $(man3dir); fi + cp zlib.h zconf.h $(includedir) + chmod 644 $(includedir)/zlib.h $(includedir)/zconf.h + cp $(LIBS) $(libdir) + cd $(libdir); chmod 755 $(LIBS) + -@(cd $(libdir); $(RANLIB) libz.a || true) >/dev/null 2>&1 + cd $(libdir); if test -f $(SHAREDLIBV); then \ + rm -f $(SHAREDLIB) $(SHAREDLIBM); \ + ln -s $(SHAREDLIBV) $(SHAREDLIB); \ + ln -s $(SHAREDLIBV) $(SHAREDLIBM); \ + (ldconfig || true) >/dev/null 2>&1; \ + fi + cp zlib.3 $(man3dir) + chmod 644 $(man3dir)/zlib.3 +# The ranlib in install is needed on NeXTSTEP which checks file times +# ldconfig is for Linux + +uninstall: + cd $(includedir); \ + cd $(libdir); rm -f libz.a; \ + if test -f $(SHAREDLIBV); then \ + rm -f $(SHAREDLIBV) $(SHAREDLIB) $(SHAREDLIBM); \ + fi + cd $(man3dir); rm -f zlib.3 + +mostlyclean: clean +clean: + rm -f *.o *~ example$(EXE) minigzip$(EXE) \ + libz.* foo.gz so_locations \ + _match.s maketree contrib/infback9/*.o + +maintainer-clean: distclean +distclean: clean + cp -p Makefile.in Makefile + cp -p zconf.in.h zconf.h + rm -f .DS_Store + +tags: + etags *.[ch] + +depend: + makedepend -- $(CFLAGS) -- *.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: crc32.h zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +example.o: zlib.h zconf.h +gzio.o: zutil.h zlib.h zconf.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +minigzip.o: zlib.h zconf.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h diff --git a/neo/framework/zlib/README b/neo/framework/zlib/README new file mode 100644 index 00000000..758cc500 --- /dev/null +++ b/neo/framework/zlib/README @@ -0,0 +1,125 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.3 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) +and rfc1952.txt (gzip format). These documents are also available in other +formats from ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example +of the library is given in the file example.c which also tests that the library +is working correctly. Another example is given in the file minigzip.c. The +compression library itself is composed of all source files except example.c and +minigzip.c. + +To compile all files and run the test program, follow the instructions given at +the top of Makefile. In short "make test; make install" should work for most +machines. For Unix: "./configure; make test; make install". For MSDOS, use one +of the special makefiles such as Makefile.msc. For VMS, use make_vms.com. + +Questions about zlib should be sent to , or to Gilles Vollant + for the Windows DLL version. The zlib home page is +http://www.zlib.org or http://www.gzip.org/zlib/ Before reporting a problem, +please check this site to verify that you have the latest version of zlib; +otherwise get the latest version and check whether the problem still exists or +not. + +PLEASE read the zlib FAQ http://www.gzip.org/zlib/zlib_faq.html before asking +for help. + +Mark Nelson wrote an article about zlib for the Jan. 1997 +issue of Dr. Dobb's Journal; a copy of the article is available in +http://dogma.net/markn/articles/zlibtool/zlibtool.htm + +The changes made in version 1.2.3 are documented in the file ChangeLog. + +Unsupported third party contributions are provided in directory "contrib". + +A Java implementation of zlib is available in the Java Development Kit +http://java.sun.com/j2se/1.4.2/docs/api/java/util/zip/package-summary.html +See the zlib home page http://www.zlib.org for details. + +A Perl interface to zlib written by Paul Marquess is in the +CPAN (Comprehensive Perl Archive Network) sites +http://www.cpan.org/modules/by-module/Compress/ + +A Python interface to zlib written by A.M. Kuchling is +available in Python 1.5 and later versions, see +http://www.python.org/doc/lib/module-zlib.html + +A zlib binding for TCL written by Andreas Kupries is +availlable at http://www.oche.de/~akupries/soft/trf/trf_zip.html + +An experimental package to read and write files in .zip format, written on top +of zlib by Gilles Vollant , is available in the +contrib/minizip directory of zlib. + + +Notes for some targets: + +- For Windows DLL versions, please see win32/DLL_FAQ.txt + +- For 64-bit Irix, deflate.c must be compiled without any optimization. With + -O, one libpng test fails. The test works in 32 bit mode (with the -n32 + compiler flag). The compiler bug has been reported to SGI. + +- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works + when compiled with cc. + +- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is + necessary to get gzprintf working correctly. This is done by configure. + +- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with + other compilers. Use "make test" to check your compiler. + +- gzdopen is not supported on RISCOS, BEOS and by some Mac compilers. + +- For PalmOs, see http://palmzlib.sourceforge.net/ + +- When building a shared, i.e. dynamic library on Mac OS X, the library must be + installed before testing (do "make install" before "make test"), since the + library location is specified in the library. + + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate + and zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; + they are too numerous to cite here. + +Copyright notice: + + (C) 1995-2004 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* +receiving lengthy legal documents to sign. The sources are provided +for free but without warranty of any kind. The library has been +entirely written by Jean-loup Gailly and Mark Adler; it does not +include third-party code. + +If you redistribute modified sources, we would appreciate that you include +in the file ChangeLog history information documenting your changes. Please +read the FAQ for more information on the distribution of modified source +versions. diff --git a/neo/framework/zlib/adler32.c b/neo/framework/zlib/adler32.c new file mode 100644 index 00000000..007ba262 --- /dev/null +++ b/neo/framework/zlib/adler32.c @@ -0,0 +1,149 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +#define BASE 65521UL /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware */ +#ifdef NO_DIVIDE +# define MOD(a) \ + do { \ + if (a >= (BASE << 16)) a -= (BASE << 16); \ + if (a >= (BASE << 15)) a -= (BASE << 15); \ + if (a >= (BASE << 14)) a -= (BASE << 14); \ + if (a >= (BASE << 13)) a -= (BASE << 13); \ + if (a >= (BASE << 12)) a -= (BASE << 12); \ + if (a >= (BASE << 11)) a -= (BASE << 11); \ + if (a >= (BASE << 10)) a -= (BASE << 10); \ + if (a >= (BASE << 9)) a -= (BASE << 9); \ + if (a >= (BASE << 8)) a -= (BASE << 8); \ + if (a >= (BASE << 7)) a -= (BASE << 7); \ + if (a >= (BASE << 6)) a -= (BASE << 6); \ + if (a >= (BASE << 5)) a -= (BASE << 5); \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD4(a) \ + do { \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD4(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32(adler, buf, len) + uLong adler; + const Bytef *buf; + uInt len; +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD4(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off_t len2; +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* the derivation of this formula is left as an exercise for the reader */ + rem = (unsigned)(len2 % BASE); + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 > BASE) sum1 -= BASE; + if (sum1 > BASE) sum1 -= BASE; + if (sum2 > (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 > BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} diff --git a/neo/framework/zlib/algorithm.txt b/neo/framework/zlib/algorithm.txt new file mode 100644 index 00000000..b022dde3 --- /dev/null +++ b/neo/framework/zlib/algorithm.txt @@ -0,0 +1,209 @@ +1. Compression algorithm (deflate) + +The deflation algorithm used by gzip (also zip and zlib) is a variation of +LZ77 (Lempel-Ziv 1977, see reference below). It finds duplicated strings in +the input data. The second occurrence of a string is replaced by a +pointer to the previous string, in the form of a pair (distance, +length). Distances are limited to 32K bytes, and lengths are limited +to 258 bytes. When a string does not occur anywhere in the previous +32K bytes, it is emitted as a sequence of literal bytes. (In this +description, `string' must be taken as an arbitrary sequence of bytes, +and is not restricted to printable characters.) + +Literals or match lengths are compressed with one Huffman tree, and +match distances are compressed with another tree. The trees are stored +in a compact form at the start of each block. The blocks can have any +size (except that the compressed data for one block must fit in +available memory). A block is terminated when deflate() determines that +it would be useful to start another block with fresh trees. (This is +somewhat similar to the behavior of LZW-based _compress_.) + +Duplicated strings are found using a hash table. All input strings of +length 3 are inserted in the hash table. A hash index is computed for +the next 3 bytes. If the hash chain for this index is not empty, all +strings in the chain are compared with the current input string, and +the longest match is selected. + +The hash chains are searched starting with the most recent strings, to +favor small distances and thus take advantage of the Huffman encoding. +The hash chains are singly linked. There are no deletions from the +hash chains, the algorithm simply discards matches that are too old. + +To avoid a worst-case situation, very long hash chains are arbitrarily +truncated at a certain length, determined by a runtime option (level +parameter of deflateInit). So deflate() does not always find the longest +possible match but generally finds a match which is long enough. + +deflate() also defers the selection of matches with a lazy evaluation +mechanism. After a match of length N has been found, deflate() searches for +a longer match at the next input byte. If a longer match is found, the +previous match is truncated to a length of one (thus producing a single +literal byte) and the process of lazy evaluation begins again. Otherwise, +the original match is kept, and the next match search is attempted only N +steps later. + +The lazy match evaluation is also subject to a runtime parameter. If +the current match is long enough, deflate() reduces the search for a longer +match, thus speeding up the whole process. If compression ratio is more +important than speed, deflate() attempts a complete second search even if +the first match is already long enough. + +The lazy match evaluation is not performed for the fastest compression +modes (level parameter 1 to 3). For these fast modes, new strings +are inserted in the hash table only when no match was found, or +when the match is not too long. This degrades the compression ratio +but saves time since there are both fewer insertions and fewer searches. + + +2. Decompression algorithm (inflate) + +2.1 Introduction + +The key question is how to represent a Huffman code (or any prefix code) so +that you can decode fast. The most important characteristic is that shorter +codes are much more common than longer codes, so pay attention to decoding the +short codes fast, and let the long codes take longer to decode. + +inflate() sets up a first level table that covers some number of bits of +input less than the length of longest code. It gets that many bits from the +stream, and looks it up in the table. The table will tell if the next +code is that many bits or less and how many, and if it is, it will tell +the value, else it will point to the next level table for which inflate() +grabs more bits and tries to decode a longer code. + +How many bits to make the first lookup is a tradeoff between the time it +takes to decode and the time it takes to build the table. If building the +table took no time (and if you had infinite memory), then there would only +be a first level table to cover all the way to the longest code. However, +building the table ends up taking a lot longer for more bits since short +codes are replicated many times in such a table. What inflate() does is +simply to make the number of bits in the first table a variable, and then +to set that variable for the maximum speed. + +For inflate, which has 286 possible codes for the literal/length tree, the size +of the first table is nine bits. Also the distance trees have 30 possible +values, and the size of the first table is six bits. Note that for each of +those cases, the table ended up one bit longer than the ``average'' code +length, i.e. the code length of an approximately flat code which would be a +little more than eight bits for 286 symbols and a little less than five bits +for 30 symbols. + + +2.2 More details on the inflate table lookup + +Ok, you want to know what this cleverly obfuscated inflate tree actually +looks like. You are correct that it's not a Huffman tree. It is simply a +lookup table for the first, let's say, nine bits of a Huffman symbol. The +symbol could be as short as one bit or as long as 15 bits. If a particular +symbol is shorter than nine bits, then that symbol's translation is duplicated +in all those entries that start with that symbol's bits. For example, if the +symbol is four bits, then it's duplicated 32 times in a nine-bit table. If a +symbol is nine bits long, it appears in the table once. + +If the symbol is longer than nine bits, then that entry in the table points +to another similar table for the remaining bits. Again, there are duplicated +entries as needed. The idea is that most of the time the symbol will be short +and there will only be one table look up. (That's whole idea behind data +compression in the first place.) For the less frequent long symbols, there +will be two lookups. If you had a compression method with really long +symbols, you could have as many levels of lookups as is efficient. For +inflate, two is enough. + +So a table entry either points to another table (in which case nine bits in +the above example are gobbled), or it contains the translation for the symbol +and the number of bits to gobble. Then you start again with the next +ungobbled bit. + +You may wonder: why not just have one lookup table for how ever many bits the +longest symbol is? The reason is that if you do that, you end up spending +more time filling in duplicate symbol entries than you do actually decoding. +At least for deflate's output that generates new trees every several 10's of +kbytes. You can imagine that filling in a 2^15 entry table for a 15-bit code +would take too long if you're only decoding several thousand symbols. At the +other extreme, you could make a new table for every bit in the code. In fact, +that's essentially a Huffman tree. But then you spend two much time +traversing the tree while decoding, even for short symbols. + +So the number of bits for the first lookup table is a trade of the time to +fill out the table vs. the time spent looking at the second level and above of +the table. + +Here is an example, scaled down: + +The code being decoded, with 10 symbols, from 1 to 6 bits long: + +A: 0 +B: 10 +C: 1100 +D: 11010 +E: 11011 +F: 11100 +G: 11101 +H: 11110 +I: 111110 +J: 111111 + +Let's make the first table three bits long (eight entries): + +000: A,1 +001: A,1 +010: A,1 +011: A,1 +100: B,2 +101: B,2 +110: -> table X (gobble 3 bits) +111: -> table Y (gobble 3 bits) + +Each entry is what the bits decode as and how many bits that is, i.e. how +many bits to gobble. Or the entry points to another table, with the number of +bits to gobble implicit in the size of the table. + +Table X is two bits long since the longest code starting with 110 is five bits +long: + +00: C,1 +01: C,1 +10: D,2 +11: E,2 + +Table Y is three bits long since the longest code starting with 111 is six +bits long: + +000: F,2 +001: F,2 +010: G,2 +011: G,2 +100: H,2 +101: H,2 +110: I,3 +111: J,3 + +So what we have here are three tables with a total of 20 entries that had to +be constructed. That's compared to 64 entries for a single table. Or +compared to 16 entries for a Huffman tree (six two entry tables and one four +entry table). Assuming that the code ideally represents the probability of +the symbols, it takes on the average 1.25 lookups per symbol. That's compared +to one lookup for the single table, or 1.66 lookups per symbol for the +Huffman tree. + +There, I think that gives you a picture of what's going on. For inflate, the +meaning of a particular symbol is often more than just a letter. It can be a +byte (a "literal"), or it can be either a length or a distance which +indicates a base value and a number of bits to fetch after the code that is +added to the base value. Or it might be the special end-of-block code. The +data structures created in inftrees.c try to encode all that information +compactly in the tables. + + +Jean-loup Gailly Mark Adler +jloup@gzip.org madler@alumni.caltech.edu + + +References: + +[LZ77] Ziv J., Lempel A., ``A Universal Algorithm for Sequential Data +Compression,'' IEEE Transactions on Information Theory, Vol. 23, No. 3, +pp. 337-343. + +``DEFLATE Compressed Data Format Specification'' available in +http://www.ietf.org/rfc/rfc1951.txt diff --git a/neo/framework/zlib/compress.c b/neo/framework/zlib/compress.c new file mode 100644 index 00000000..df04f014 --- /dev/null +++ b/neo/framework/zlib/compress.c @@ -0,0 +1,79 @@ +/* compress.c -- compress a memory buffer + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ +int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; + int level; +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; +#ifdef MAXSEG_64K + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; +#endif + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit(&stream, level); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/* =========================================================================== + */ +int ZEXPORT compress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} + +/* =========================================================================== + If the default memLevel or windowBits for deflateInit() is changed, then + this function needs to be updated. + */ +uLong ZEXPORT compressBound (sourceLen) + uLong sourceLen; +{ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + 11; +} diff --git a/neo/framework/zlib/configure b/neo/framework/zlib/configure new file mode 100644 index 00000000..d7ffdc34 --- /dev/null +++ b/neo/framework/zlib/configure @@ -0,0 +1,459 @@ +#!/bin/sh +# configure script for zlib. This script is needed only if +# you wish to build a shared library and your system supports them, +# of if you need special compiler, flags or install directory. +# Otherwise, you can just use directly "make test; make install" +# +# To create a shared library, use "configure --shared"; by default a static +# library is created. If the primitive shared library support provided here +# does not work, use ftp://prep.ai.mit.edu/pub/gnu/libtool-*.tar.gz +# +# To impose specific compiler or flags or install directory, use for example: +# prefix=$HOME CC=cc CFLAGS="-O4" ./configure +# or for csh/tcsh users: +# (setenv prefix $HOME; setenv CC cc; setenv CFLAGS "-O4"; ./configure) +# LDSHARED is the command to be used to create a shared library + +# Incorrect settings of CC or CFLAGS may prevent creating a shared library. +# If you have problems, try without defining CC and CFLAGS before reporting +# an error. + +LIBS=libz.a +LDFLAGS="-L. ${LIBS}" +VER=`sed -n -e '/VERSION "/s/.*"\(.*\)".*/\1/p' < zlib.h` +VER2=`sed -n -e '/VERSION "/s/.*"\([0-9]*\\.[0-9]*\)\\..*/\1/p' < zlib.h` +VER1=`sed -n -e '/VERSION "/s/.*"\([0-9]*\)\\..*/\1/p' < zlib.h` +AR=${AR-"ar rc"} +RANLIB=${RANLIB-"ranlib"} +prefix=${prefix-/usr/local} +exec_prefix=${exec_prefix-'${prefix}'} +libdir=${libdir-'${exec_prefix}/lib'} +includedir=${includedir-'${prefix}/include'} +mandir=${mandir-'${prefix}/share/man'} +shared_ext='.so' +shared=0 +gcc=0 +old_cc="$CC" +old_cflags="$CFLAGS" + +while test $# -ge 1 +do +case "$1" in + -h* | --h*) + echo 'usage:' + echo ' configure [--shared] [--prefix=PREFIX] [--exec_prefix=EXPREFIX]' + echo ' [--libdir=LIBDIR] [--includedir=INCLUDEDIR]' + exit 0;; + -p*=* | --p*=*) prefix=`echo $1 | sed 's/[-a-z_]*=//'`; shift;; + -e*=* | --e*=*) exec_prefix=`echo $1 | sed 's/[-a-z_]*=//'`; shift;; + -l*=* | --libdir=*) libdir=`echo $1 | sed 's/[-a-z_]*=//'`; shift;; + -i*=* | --includedir=*) includedir=`echo $1 | sed 's/[-a-z_]*=//'`;shift;; + -p* | --p*) prefix="$2"; shift; shift;; + -e* | --e*) exec_prefix="$2"; shift; shift;; + -l* | --l*) libdir="$2"; shift; shift;; + -i* | --i*) includedir="$2"; shift; shift;; + -s* | --s*) shared=1; shift;; + *) echo "unknown option: $1"; echo "$0 --help for help"; exit 1;; + esac +done + +test=ztest$$ +cat > $test.c </dev/null; then + CC="$cc" + SFLAGS=${CFLAGS-"-fPIC -O3"} + CFLAGS="$cflags" + case `(uname -s || echo unknown) 2>/dev/null` in + Linux | linux | GNU | GNU/*) LDSHARED=${LDSHARED-"$cc -shared -Wl,-soname,libz.so.1"};; + CYGWIN* | Cygwin* | cygwin* | OS/2* ) + EXE='.exe';; + QNX*) # This is for QNX6. I suppose that the QNX rule below is for QNX2,QNX4 + # (alain.bonnefoy@icbt.com) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-hlibz.so.1"};; + HP-UX*) + LDSHARED=${LDSHARED-"$cc -shared $SFLAGS"} + case `(uname -m || echo unknown) 2>/dev/null` in + ia64) + shared_ext='.so' + SHAREDLIB='libz.so';; + *) + shared_ext='.sl' + SHAREDLIB='libz.sl';; + esac;; + Darwin*) shared_ext='.dylib' + SHAREDLIB=libz$shared_ext + SHAREDLIBV=libz.$VER$shared_ext + SHAREDLIBM=libz.$VER1$shared_ext + LDSHARED=${LDSHARED-"$cc -dynamiclib -install_name $libdir/$SHAREDLIBM -compatibility_version $VER1 -current_version $VER"};; + *) LDSHARED=${LDSHARED-"$cc -shared"};; + esac +else + # find system name and corresponding cc options + CC=${CC-cc} + case `(uname -sr || echo unknown) 2>/dev/null` in + HP-UX*) SFLAGS=${CFLAGS-"-O +z"} + CFLAGS=${CFLAGS-"-O"} +# LDSHARED=${LDSHARED-"ld -b +vnocompatwarnings"} + LDSHARED=${LDSHARED-"ld -b"} + case `(uname -m || echo unknown) 2>/dev/null` in + ia64) + shared_ext='.so' + SHAREDLIB='libz.so';; + *) + shared_ext='.sl' + SHAREDLIB='libz.sl';; + esac;; + IRIX*) SFLAGS=${CFLAGS-"-ansi -O2 -rpath ."} + CFLAGS=${CFLAGS-"-ansi -O2"} + LDSHARED=${LDSHARED-"cc -shared"};; + OSF1\ V4*) SFLAGS=${CFLAGS-"-O -std1"} + CFLAGS=${CFLAGS-"-O -std1"} + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so -Wl,-msym -Wl,-rpath,$(libdir) -Wl,-set_version,${VER}:1.0"};; + OSF1*) SFLAGS=${CFLAGS-"-O -std1"} + CFLAGS=${CFLAGS-"-O -std1"} + LDSHARED=${LDSHARED-"cc -shared"};; + QNX*) SFLAGS=${CFLAGS-"-4 -O"} + CFLAGS=${CFLAGS-"-4 -O"} + LDSHARED=${LDSHARED-"cc"} + RANLIB=${RANLIB-"true"} + AR="cc -A";; + SCO_SV\ 3.2*) SFLAGS=${CFLAGS-"-O3 -dy -KPIC "} + CFLAGS=${CFLAGS-"-O3"} + LDSHARED=${LDSHARED-"cc -dy -KPIC -G"};; + SunOS\ 5*) SFLAGS=${CFLAGS-"-fast -xcg89 -KPIC -R."} + CFLAGS=${CFLAGS-"-fast -xcg89"} + LDSHARED=${LDSHARED-"cc -G"};; + SunOS\ 4*) SFLAGS=${CFLAGS-"-O2 -PIC"} + CFLAGS=${CFLAGS-"-O2"} + LDSHARED=${LDSHARED-"ld"};; + SunStudio\ 9*) SFLAGS=${CFLAGS-"-DUSE_MMAP -fast -xcode=pic32 -xtarget=ultra3 -xarch=v9b"} + CFLAGS=${CFLAGS-"-DUSE_MMAP -fast -xtarget=ultra3 -xarch=v9b"} + LDSHARED=${LDSHARED-"cc -xarch=v9b"};; + UNIX_System_V\ 4.2.0) + SFLAGS=${CFLAGS-"-KPIC -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"};; + UNIX_SV\ 4.2MP) + SFLAGS=${CFLAGS-"-Kconform_pic -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"};; + OpenUNIX\ 5) + SFLAGS=${CFLAGS-"-KPIC -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"};; + AIX*) # Courtesy of dbakker@arrayasolutions.com + SFLAGS=${CFLAGS-"-O -qmaxmem=8192"} + CFLAGS=${CFLAGS-"-O -qmaxmem=8192"} + LDSHARED=${LDSHARED-"xlc -G"};; + # send working options for other systems to support@gzip.org + *) SFLAGS=${CFLAGS-"-O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -shared"};; + esac +fi + +SHAREDLIB=${SHAREDLIB-"libz$shared_ext"} +SHAREDLIBV=${SHAREDLIBV-"libz$shared_ext.$VER"} +SHAREDLIBM=${SHAREDLIBM-"libz$shared_ext.$VER1"} + +if test $shared -eq 1; then + echo Checking for shared library support... + # we must test in two steps (cc then ld), required at least on SunOS 4.x + if test "`($CC -c $SFLAGS $test.c) 2>&1`" = "" && + test "`($LDSHARED -o $test$shared_ext $test.o) 2>&1`" = ""; then + CFLAGS="$SFLAGS" + LIBS="$SHAREDLIBV" + echo Building shared library $SHAREDLIBV with $CC. + elif test -z "$old_cc" -a -z "$old_cflags"; then + echo No shared library support. + shared=0; + else + echo 'No shared library support; try without defining CC and CFLAGS' + shared=0; + fi +fi +if test $shared -eq 0; then + LDSHARED="$CC" + echo Building static library $LIBS version $VER with $CC. +else + LDFLAGS="-L. ${SHAREDLIBV}" +fi + +cat > $test.c < +int main() { return 0; } +EOF +if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + sed < zconf.in.h "/HAVE_UNISTD_H/s%0%1%" > zconf.h + echo "Checking for unistd.h... Yes." +else + cp -p zconf.in.h zconf.h + echo "Checking for unistd.h... No." +fi + +cat > $test.c < +#include +#include "zconf.h" + +int main() +{ +#ifndef STDC + choke me +#endif + + return 0; +} +EOF + +if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + echo "Checking whether to use vs[n]printf() or s[n]printf()... using vs[n]printf()" + + cat > $test.c < +#include + +int mytest(char *fmt, ...) +{ + char buf[20]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return 0; +} + +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if test "`($CC $CFLAGS -o $test $test.c) 2>&1`" = ""; then + echo "Checking for vsnprintf() in stdio.h... Yes." + + cat >$test.c < +#include + +int mytest(char *fmt, ...) +{ + int n; + char buf[20]; + va_list ap; + + va_start(ap, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return n; +} + +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + echo "Checking for return value of vsnprintf()... Yes." + else + CFLAGS="$CFLAGS -DHAS_vsnprintf_void" + echo "Checking for return value of vsnprintf()... No." + echo " WARNING: apparently vsnprintf() does not return a value. zlib" + echo " can build but will be open to possible string-format security" + echo " vulnerabilities." + fi + else + CFLAGS="$CFLAGS -DNO_vsnprintf" + echo "Checking for vsnprintf() in stdio.h... No." + echo " WARNING: vsnprintf() not found, falling back to vsprintf(). zlib" + echo " can build but will be open to possible buffer-overflow security" + echo " vulnerabilities." + + cat >$test.c < +#include + +int mytest(char *fmt, ...) +{ + int n; + char buf[20]; + va_list ap; + + va_start(ap, fmt); + n = vsprintf(buf, fmt, ap); + va_end(ap); + return n; +} + +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + echo "Checking for return value of vsprintf()... Yes." + else + CFLAGS="$CFLAGS -DHAS_vsprintf_void" + echo "Checking for return value of vsprintf()... No." + echo " WARNING: apparently vsprintf() does not return a value. zlib" + echo " can build but will be open to possible string-format security" + echo " vulnerabilities." + fi + fi +else + echo "Checking whether to use vs[n]printf() or s[n]printf()... using s[n]printf()" + + cat >$test.c < + +int mytest() +{ + char buf[20]; + + snprintf(buf, sizeof(buf), "%s", "foo"); + return 0; +} + +int main() +{ + return (mytest()); +} +EOF + + if test "`($CC $CFLAGS -o $test $test.c) 2>&1`" = ""; then + echo "Checking for snprintf() in stdio.h... Yes." + + cat >$test.c < + +int mytest() +{ + char buf[20]; + + return snprintf(buf, sizeof(buf), "%s", "foo"); +} + +int main() +{ + return (mytest()); +} +EOF + + if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + echo "Checking for return value of snprintf()... Yes." + else + CFLAGS="$CFLAGS -DHAS_snprintf_void" + echo "Checking for return value of snprintf()... No." + echo " WARNING: apparently snprintf() does not return a value. zlib" + echo " can build but will be open to possible string-format security" + echo " vulnerabilities." + fi + else + CFLAGS="$CFLAGS -DNO_snprintf" + echo "Checking for snprintf() in stdio.h... No." + echo " WARNING: snprintf() not found, falling back to sprintf(). zlib" + echo " can build but will be open to possible buffer-overflow security" + echo " vulnerabilities." + + cat >$test.c < + +int mytest() +{ + char buf[20]; + + return sprintf(buf, "%s", "foo"); +} + +int main() +{ + return (mytest()); +} +EOF + + if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + echo "Checking for return value of sprintf()... Yes." + else + CFLAGS="$CFLAGS -DHAS_sprintf_void" + echo "Checking for return value of sprintf()... No." + echo " WARNING: apparently sprintf() does not return a value. zlib" + echo " can build but will be open to possible string-format security" + echo " vulnerabilities." + fi + fi +fi + +cat >$test.c < +int main() { return 0; } +EOF +if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + echo "Checking for errno.h... Yes." +else + echo "Checking for errno.h... No." + CFLAGS="$CFLAGS -DNO_ERRNO_H" +fi + +cat > $test.c < +#include +#include +caddr_t hello() { + return mmap((caddr_t)0, (off_t)0, PROT_READ, MAP_SHARED, 0, (off_t)0); +} +EOF +if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then + CFLAGS="$CFLAGS -DUSE_MMAP" + echo Checking for mmap support... Yes. +else + echo Checking for mmap support... No. +fi + +CPP=${CPP-"$CC -E"} +case $CFLAGS in + *ASMV*) + if test "`nm $test.o | grep _hello`" = ""; then + CPP="$CPP -DNO_UNDERLINE" + echo Checking for underline in external names... No. + else + echo Checking for underline in external names... Yes. + fi;; +esac + +rm -f $test.[co] $test $test$shared_ext + +# udpate Makefile +sed < Makefile.in " +/^CC *=/s#=.*#=$CC# +/^CFLAGS *=/s#=.*#=$CFLAGS# +/^CPP *=/s#=.*#=$CPP# +/^LDSHARED *=/s#=.*#=$LDSHARED# +/^LIBS *=/s#=.*#=$LIBS# +/^SHAREDLIB *=/s#=.*#=$SHAREDLIB# +/^SHAREDLIBV *=/s#=.*#=$SHAREDLIBV# +/^SHAREDLIBM *=/s#=.*#=$SHAREDLIBM# +/^AR *=/s#=.*#=$AR# +/^RANLIB *=/s#=.*#=$RANLIB# +/^EXE *=/s#=.*#=$EXE# +/^prefix *=/s#=.*#=$prefix# +/^exec_prefix *=/s#=.*#=$exec_prefix# +/^libdir *=/s#=.*#=$libdir# +/^includedir *=/s#=.*#=$includedir# +/^mandir *=/s#=.*#=$mandir# +/^LDFLAGS *=/s#=.*#=$LDFLAGS# +" > Makefile diff --git a/neo/framework/zlib/crc32.c b/neo/framework/zlib/crc32.c new file mode 100644 index 00000000..f658a9ef --- /dev/null +++ b/neo/framework/zlib/crc32.c @@ -0,0 +1,423 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Find a four-byte integer type for crc32_little() and crc32_big(). */ +#ifndef NOBYFOUR +# ifdef STDC /* need ANSI C limits.h to determine sizes */ +# include +# define BYFOUR +# if (UINT_MAX == 0xffffffffUL) + typedef unsigned int u4; +# else +# if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long u4; +# else +# if (USHRT_MAX == 0xffffffffUL) + typedef unsigned short u4; +# else +# undef BYFOUR /* can't find a four-byte integer type! */ +# endif +# endif +# endif +# endif /* STDC */ +#endif /* !NOBYFOUR */ + +/* Definitions for doing the crc four data bytes at a time. */ +#ifdef BYFOUR +# define REV(w) (((w)>>24)+(((w)>>8)&0xff00)+ \ + (((w)&0xff00)<<8)+(((w)&0xff)<<24)) + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local unsigned long FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const unsigned long FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + unsigned long c; + int n, k; + unsigned long poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0UL; + for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) + poly |= 1UL << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (unsigned long)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = REV(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = REV(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const unsigned long FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const unsigned long FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const unsigned long FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const unsigned long FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + u4 endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = (u4)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = REV((u4)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(REV(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times(mat, vec) + unsigned long *mat; + unsigned long vec; +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square(square, mat) + unsigned long *square; + unsigned long *mat; +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off_t len2; +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case */ + if (len2 == 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320L; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} diff --git a/neo/framework/zlib/crc32.h b/neo/framework/zlib/crc32.h new file mode 100644 index 00000000..8053b611 --- /dev/null +++ b/neo/framework/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const unsigned long FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/neo/framework/zlib/deflate.c b/neo/framework/zlib/deflate.c new file mode 100644 index 00000000..29ce1f64 --- /dev/null +++ b/neo/framework/zlib/deflate.c @@ -0,0 +1,1736 @@ +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGEMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many people for bug reports and testing. + * + * REFERENCES + * + * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". + * Available in http://www.ietf.org/rfc/rfc1951.txt + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + */ + +/* @(#) $Id$ */ + +#include "deflate.h" + +const char deflate_copyright[] = + " deflate 1.2.3 Copyright 1995-2005 Jean-loup Gailly "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* =========================================================================== + * Function prototypes. + */ +typedef enum { + need_more, /* block not completed, need more input or more output */ + block_done, /* block flush performed */ + finish_started, /* finish started, need only more output at next deflate */ + finish_done /* finish done, accept no more input or output */ +} block_state; + +typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +/* Compression function. Returns the block state after the call. */ + +local void fill_window OF((deflate_state *s)); +local block_state deflate_stored OF((deflate_state *s, int flush)); +local block_state deflate_fast OF((deflate_state *s, int flush)); +#ifndef FASTEST +local block_state deflate_slow OF((deflate_state *s, int flush)); +#endif +local void lm_init OF((deflate_state *s)); +local void putShortMSB OF((deflate_state *s, uInt b)); +local void flush_pending OF((z_streamp strm)); +local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); +#ifndef FASTEST +#ifdef ASMV + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else +local uInt longest_match OF((deflate_state *s, IPos cur_match)); +#endif +#endif +local uInt longest_match_fast OF((deflate_state *s, IPos cur_match)); + +#ifdef DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif + +/* =========================================================================== + * Local data + */ + +#define NIL 0 +/* Tail of hash chains */ + +#ifndef TOO_FAR +# define TOO_FAR 4096 +#endif +/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +typedef struct config_s { + ush good_length; /* reduce lazy search above this match length */ + ush max_lazy; /* do not perform lazy search above this match length */ + ush nice_length; /* quit search above this match length */ + ush max_chain; + compress_func func; +} config; + +#ifdef FASTEST +local const config configuration_table[2] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */ +#else +local const config configuration_table[10] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, + +/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ +/* 5 */ {8, 16, 32, 32, deflate_slow}, +/* 6 */ {8, 16, 128, 128, deflate_slow}, +/* 7 */ {8, 32, 128, 256, deflate_slow}, +/* 8 */ {32, 128, 258, 1024, deflate_slow}, +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */ +#endif + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +#ifndef NO_DUMMY_DECL +struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ +#endif + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) + + +/* =========================================================================== + * Insert string str in the dictionary and set match_head to the previous head + * of the hash chain (the most recent string with same hash key). Return + * the previous length of the hash chain. + * If this file is compiled with -DFASTEST, the compression level is forced + * to 1, and no hash chains are maintained. + * IN assertion: all calls to to INSERT_STRING are made with consecutive + * input characters and the first MIN_MATCH bytes of str are valid + * (except for the last MIN_MATCH-1 bytes of the input file). + */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#endif + +/* =========================================================================== + * Initialize the hash table (avoiding 64K overflow for 16 bit systems). + * prev[] will be initialized on the fly. + */ +#define CLEAR_HASH(s) \ + s->head[s->hash_size-1] = NIL; \ + zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + +/* ========================================================================= */ +int ZEXPORT deflateInit_(strm, level, version, stream_size) + z_streamp strm; + int level; + const char *version; + int stream_size; +{ + return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, version, stream_size); + /* To do: ignore strm->next_in if we use it as window */ +} + +/* ========================================================================= */ +int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, + version, stream_size) + z_streamp strm; + int level; + int method; + int windowBits; + int memLevel; + int strategy; + const char *version; + int stream_size; +{ + deflate_state *s; + int wrap = 1; + static const char my_version[] = ZLIB_VERSION; + + ushf *overlay; + /* We overlay pending_buf and d_buf+l_buf. This works since the average + * output size for (length,distance) codes is <= 24 bits. + */ + + if (version == Z_NULL || version[0] != my_version[0] || + stream_size != sizeof(z_stream)) { + return Z_VERSION_ERROR; + } + if (strm == Z_NULL) return Z_STREAM_ERROR; + + strm->msg = Z_NULL; + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } +#ifdef GZIP + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } +#endif + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ + s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); + if (s == Z_NULL) return Z_MEM_ERROR; + strm->state = (struct internal_state FAR *)s; + s->strm = strm; + + s->wrap = wrap; + s->gzhead = Z_NULL; + s->w_bits = windowBits; + s->w_size = 1 << s->w_bits; + s->w_mask = s->w_size - 1; + + s->hash_bits = memLevel + 7; + s->hash_size = 1 << s->hash_bits; + s->hash_mask = s->hash_size - 1; + s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + + s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); + s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); + s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + s->pending_buf = (uchf *) overlay; + s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + + if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || + s->pending_buf == Z_NULL) { + s->status = FINISH_STATE; + strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + deflateEnd (strm); + return Z_MEM_ERROR; + } + s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + + s->level = level; + s->strategy = strategy; + s->method = (Byte)method; + + return deflateReset(strm); +} + +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) + z_streamp strm; + const Bytef *dictionary; + uInt dictLength; +{ + deflate_state *s; + uInt length = dictLength; + uInt n; + IPos hash_head = 0; + + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL || + strm->state->wrap == 2 || + (strm->state->wrap == 1 && strm->state->status != INIT_STATE)) + return Z_STREAM_ERROR; + + s = strm->state; + if (s->wrap) + strm->adler = adler32(strm->adler, dictionary, dictLength); + + if (length < MIN_MATCH) return Z_OK; + if (length > MAX_DIST(s)) { + length = MAX_DIST(s); + dictionary += dictLength - length; /* use the tail of the dictionary */ + } + zmemcpy(s->window, dictionary, length); + s->strstart = length; + s->block_start = (long)length; + + /* Insert all strings in the hash table (except for the last two bytes). + * s->lookahead stays null, so s->ins_h will be recomputed at the next + * call of fill_window. + */ + s->ins_h = s->window[0]; + UPDATE_HASH(s, s->ins_h, s->window[1]); + for (n = 0; n <= length - MIN_MATCH; n++) { + INSERT_STRING(s, n, hash_head); + } + if (hash_head) hash_head = 0; /* to make compiler happy */ + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateReset (strm) + z_streamp strm; +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) { + return Z_STREAM_ERROR; + } + + strm->total_in = strm->total_out = 0; + strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ + strm->data_type = Z_UNKNOWN; + + s = (deflate_state *)strm->state; + s->pending = 0; + s->pending_out = s->pending_buf; + + if (s->wrap < 0) { + s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */ + } + s->status = s->wrap ? INIT_STATE : BUSY_STATE; + strm->adler = +#ifdef GZIP + s->wrap == 2 ? crc32(0L, Z_NULL, 0) : +#endif + adler32(0L, Z_NULL, 0); + s->last_flush = Z_NO_FLUSH; + + _tr_init(s); + lm_init(s); + + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateSetHeader (strm, head) + z_streamp strm; + gz_headerp head; +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (strm->state->wrap != 2) return Z_STREAM_ERROR; + strm->state->gzhead = head; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePrime (strm, bits, value) + z_streamp strm; + int bits; + int value; +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + strm->state->bi_valid = bits; + strm->state->bi_buf = (ush)(value & ((1 << bits) - 1)); + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateParams(strm, level, strategy) + z_streamp strm; + int level; + int strategy; +{ + deflate_state *s; + compress_func func; + int err = Z_OK; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + func = configuration_table[s->level].func; + + if (func != configuration_table[level].func && strm->total_in != 0) { + /* Flush the last buffer: */ + err = deflate(strm, Z_PARTIAL_FLUSH); + } + if (s->level != level) { + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return err; +} + +/* ========================================================================= */ +int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) + z_streamp strm; + int good_length; + int max_lazy; + int nice_length; + int max_chain; +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + s->good_match = good_length; + s->max_lazy_match = max_lazy; + s->nice_match = nice_length; + s->max_chain_length = max_chain; + return Z_OK; +} + +/* ========================================================================= + * For the default windowBits of 15 and memLevel of 8, this function returns + * a close to exact, as well as small, upper bound on the compressed size. + * They are coded as constants here for a reason--if the #define's are + * changed, then this function needs to be changed as well. The return + * value for 15 and 8 only works for those exact settings. + * + * For any setting other than those defaults for windowBits and memLevel, + * the value returned is a conservative worst case for the maximum expansion + * resulting from using fixed blocks instead of stored blocks, which deflate + * can emit on compressed data for some combinations of the parameters. + * + * This function could be more sophisticated to provide closer upper bounds + * for every combination of windowBits and memLevel, as well as wrap. + * But even the conservative upper bound of about 14% expansion does not + * seem onerous for output buffer allocation. + */ +uLong ZEXPORT deflateBound(strm, sourceLen) + z_streamp strm; + uLong sourceLen; +{ + deflate_state *s; + uLong destLen; + + /* conservative upper bound */ + destLen = sourceLen + + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 11; + + /* if can't get parameters, return conservative bound */ + if (strm == Z_NULL || strm->state == Z_NULL) + return destLen; + + /* if not default parameters, return conservative bound */ + s = strm->state; + if (s->w_bits != 15 || s->hash_bits != 8 + 7) + return destLen; + + /* default settings: return tight bound for that case */ + return compressBound(sourceLen); +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB (s, b) + deflate_state *s; + uInt b; +{ + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->next_out buffer and copying into it. + * (See also read_buf()). + */ +local void flush_pending(strm) + z_streamp strm; +{ + unsigned len = strm->state->pending; + + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, strm->state->pending_out, len); + strm->next_out += len; + strm->state->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + strm->state->pending -= len; + if (strm->state->pending == 0) { + strm->state->pending_out = strm->state->pending_buf; + } +} + +/* ========================================================================= */ +int ZEXPORT deflate (strm, flush) + z_streamp strm; + int flush; +{ + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + flush > Z_FINISH || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); + + s->strm = strm; /* just in case */ + old_flush = s->last_flush; + s->last_flush = flush; + + /* Write the header */ + if (s->status == INIT_STATE) { +#ifdef GZIP + if (s->wrap == 2) { + strm->adler = crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (s->gzhead == NULL) { + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s->status = BUSY_STATE; + } + else { + put_byte(s, (s->gzhead->text ? 1 : 0) + + (s->gzhead->hcrc ? 2 : 0) + + (s->gzhead->extra == Z_NULL ? 0 : 4) + + (s->gzhead->name == Z_NULL ? 0 : 8) + + (s->gzhead->comment == Z_NULL ? 0 : 16) + ); + put_byte(s, (Byte)(s->gzhead->time & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff)); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, s->gzhead->os & 0xff); + if (s->gzhead->extra != NULL) { + put_byte(s, s->gzhead->extra_len & 0xff); + put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); + } + if (s->gzhead->hcrc) + strm->adler = crc32(strm->adler, s->pending_buf, + s->pending); + s->gzindex = 0; + s->status = EXTRA_STATE; + } + } + else +#endif + { + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags; + + if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) + level_flags = 0; + else if (s->level < 6) + level_flags = 1; + else if (s->level == 6) + level_flags = 2; + else + level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s->status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = adler32(0L, Z_NULL, 0); + } + } +#ifdef GZIP + if (s->status == EXTRA_STATE) { + if (s->gzhead->extra != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + + while (s->gzindex < (s->gzhead->extra_len & 0xffff)) { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) + break; + } + put_byte(s, s->gzhead->extra[s->gzindex]); + s->gzindex++; + } + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (s->gzindex == s->gzhead->extra_len) { + s->gzindex = 0; + s->status = NAME_STATE; + } + } + else + s->status = NAME_STATE; + } + if (s->status == NAME_STATE) { + if (s->gzhead->name != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->name[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) { + s->gzindex = 0; + s->status = COMMENT_STATE; + } + } + else + s->status = COMMENT_STATE; + } + if (s->status == COMMENT_STATE) { + if (s->gzhead->comment != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->comment[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) + s->status = HCRC_STATE; + } + else + s->status = HCRC_STATE; + } + if (s->status == HCRC_STATE) { + if (s->gzhead->hcrc) { + if (s->pending + 2 > s->pending_buf_size) + flush_pending(strm); + if (s->pending + 2 <= s->pending_buf_size) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + strm->adler = crc32(0L, Z_NULL, 0); + s->status = BUSY_STATE; + } + } + else + s->status = BUSY_STATE; + } +#endif + + /* Flush as much pending output as possible */ + if (s->pending != 0) { + flush_pending(strm); + if (strm->avail_out == 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s->last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm->avail_in == 0 && flush <= old_flush && + flush != Z_FINISH) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s->status == FINISH_STATE && strm->avail_in != 0) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm->avail_in != 0 || s->lookahead != 0 || + (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { + block_state bstate; + + bstate = (*(configuration_table[s->level].func))(s, flush); + + if (bstate == finish_started || bstate == finish_done) { + s->status = FINISH_STATE; + } + if (bstate == need_more || bstate == finish_started) { + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate == block_done) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(s); + } else { /* FULL_FLUSH or SYNC_FLUSH */ + _tr_stored_block(s, (char*)0, 0L, 0); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush == Z_FULL_FLUSH) { + CLEAR_HASH(s); /* forget history */ + } + } + flush_pending(strm); + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + Assert(strm->avail_out > 0, "bug2"); + + if (flush != Z_FINISH) return Z_OK; + if (s->wrap <= 0) return Z_STREAM_END; + + /* Write the trailer */ +#ifdef GZIP + if (s->wrap == 2) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 16) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 24) & 0xff)); + put_byte(s, (Byte)(strm->total_in & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 8) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 16) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 24) & 0xff)); + } + else +#endif + { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */ + return s->pending != 0 ? Z_OK : Z_STREAM_END; +} + +/* ========================================================================= */ +int ZEXPORT deflateEnd (strm) + z_streamp strm; +{ + int status; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + + status = strm->state->status; + if (status != INIT_STATE && + status != EXTRA_STATE && + status != NAME_STATE && + status != COMMENT_STATE && + status != HCRC_STATE && + status != BUSY_STATE && + status != FINISH_STATE) { + return Z_STREAM_ERROR; + } + + /* Deallocate in reverse order of allocations: */ + TRY_FREE(strm, strm->state->pending_buf); + TRY_FREE(strm, strm->state->head); + TRY_FREE(strm, strm->state->prev); + TRY_FREE(strm, strm->state->window); + + ZFREE(strm, strm->state); + strm->state = Z_NULL; + + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy (dest, source) + z_streamp dest; + z_streamp source; +{ +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + zmemcpy(dest, source, sizeof(z_stream)); + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + zmemcpy(ds, ss, sizeof(deflate_state)); + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif /* MAXSEG_64K */ +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local int read_buf(strm, buf, size) + z_streamp strm; + Bytef *buf; + unsigned size; +{ + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, strm->next_in, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, strm->next_in, len); + } +#endif + zmemcpy(buf, strm->next_in, len); + strm->next_in += len; + strm->total_in += len; + + return (int)len; +} + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init (s) + deflate_state *s; +{ + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +#ifndef FASTEST +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +#endif +} + +#ifndef FASTEST +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +#ifndef ASMV +/* For 80x86 and 680x0, an optimized version will be provided in match.asm or + * match.S. The code will be functionally equivalent. + */ +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + unsigned chain_length = s->max_chain_length;/* max hash chain length */ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + int best_len = s->prev_length; /* best match length so far */ + int nice_match = s->nice_match; /* stop if match long enough */ + IPos limit = s->strstart > (IPos)MAX_DIST(s) ? + s->strstart - (IPos)MAX_DIST(s) : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + Posf *prev = s->prev; + uInt wmask = s->w_mask; + +#ifdef UNALIGNED_OK + /* Compare two bytes at a time. Note: this is not always beneficial. + * Try with and without -DUNALIGNED_OK to check. + */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + register ush scan_start = *(ushf*)scan; + register ush scan_end = *(ushf*)(scan+best_len-1); +#else + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end = scan[best_len]; +#endif + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s->prev_length >= s->good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + Assert(cur_match < s->strstart, "no future"); + match = s->window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ +#if (defined(UNALIGNED_OK) && MAX_MATCH == 258) + /* This code assumes sizeof(unsigned short) == 2. Do not use + * UNALIGNED_OK if your compiler uses a different size. + */ + if (*(ushf*)(match+best_len-1) != scan_end || + *(ushf*)match != scan_start) continue; + + /* It is not necessary to compare scan[2] and match[2] since they are + * always equal when the other bytes match, given that the hash keys + * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at + * strstart+3, +5, ... up to strstart+257. We check for insufficient + * lookahead only every 4th comparison; the 128th check will be made + * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * necessary to put more guard bytes at the end of the window, or + * to check more often for insufficient lookahead. + */ + Assert(scan[2] == match[2], "scan[2]?"); + scan++, match++; + do { + } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + scan < strend); + /* The funny "do {}" generates better code on most compilers */ + + /* Here, scan <= window+strstart+257 */ + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + if (*scan == *match) scan++; + + len = (MAX_MATCH - 1) - (int)(strend-scan); + scan = strend - (MAX_MATCH-1); + +#else /* UNALIGNED_OK */ + + if (match[best_len] != scan_end || + match[best_len-1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + +#endif /* UNALIGNED_OK */ + + if (len > best_len) { + s->match_start = cur_match; + best_len = len; + if (len >= nice_match) break; +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else + scan_end1 = scan[best_len-1]; + scan_end = scan[best_len]; +#endif + } + } while ((cur_match = prev[cur_match & wmask]) > limit + && --chain_length != 0); + + if ((uInt)best_len <= s->lookahead) return (uInt)best_len; + return s->lookahead; +} +#endif /* ASMV */ +#endif /* FASTEST */ + +/* --------------------------------------------------------------------------- + * Optimized version for level == 1 or strategy == Z_RLE only + */ +local uInt longest_match_fast(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + Assert(cur_match < s->strstart, "no future"); + + match = s->window + cur_match; + + /* Return failure if the match length is less than 2: + */ + if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match += 2; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + + if (len < MIN_MATCH) return MIN_MATCH - 1; + + s->match_start = cur_match; + return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead; +} + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(s, start, match, length) + deflate_state *s; + IPos start, match; + int length; +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else +# define check_match(s, start, match, length) +#endif /* DEBUG */ + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window(s) + deflate_state *s; +{ + register unsigned n, m; + register Posf *p; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize+MAX_DIST(s)) { + + zmemcpy(s->window, s->window+wsize, (unsigned)wsize); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + /* %%% avoid this when Z_RLE */ + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + } while (--n); + + n = wsize; +#ifndef FASTEST + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +#endif + more += wsize; + } + if (s->strm->avail_in == 0) return; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead >= MIN_MATCH) { + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK_ONLY(s, eof) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (eof)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ +} + +/* Same but force premature exit if necessary. */ +#define FLUSH_BLOCK(s, eof) { \ + FLUSH_BLOCK_ONLY(s, eof); \ + if (s->strm->avail_out == 0) return (eof) ? finish_started : need_more; \ +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +local block_state deflate_stored(s, flush) + deflate_state *s; + int flush; +{ + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + ulg max_block_size = 0xffff; + ulg max_start; + + if (max_block_size > s->pending_buf_size - 5) { + max_block_size = s->pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s->lookahead <= 1) { + + Assert(s->strstart < s->w_size+MAX_DIST(s) || + s->block_start >= (long)s->w_size, "slide too late"); + + fill_window(s); + if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more; + + if (s->lookahead == 0) break; /* flush the current block */ + } + Assert(s->block_start >= 0L, "block gone"); + + s->strstart += s->lookahead; + s->lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + max_start = s->block_start + max_block_size; + if (s->strstart == 0 || (ulg)s->strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s->lookahead = (uInt)(s->strstart - max_start); + s->strstart = (uInt)max_start; + FLUSH_BLOCK(s, 0); + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) { + FLUSH_BLOCK(s, 0); + } + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head = NIL; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ +#ifdef FASTEST + if ((s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) || + (s->strategy == Z_RLE && s->strstart - hash_head == 1)) { + s->match_length = longest_match_fast (s, hash_head); + } +#else + if (s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) { + s->match_length = longest_match (s, hash_head); + } else if (s->strategy == Z_RLE && s->strstart - hash_head == 1) { + s->match_length = longest_match_fast (s, hash_head); + } +#endif + /* longest_match() or longest_match_fast() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +#ifndef FASTEST +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +local block_state deflate_slow(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head = NIL; /* head of hash chain */ + int bflush; /* set if current block must be flushed */ + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + */ + s->prev_length = s->match_length, s->prev_match = s->match_start; + s->match_length = MIN_MATCH-1; + + if (hash_head != NIL && s->prev_length < s->max_lazy_match && + s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + if (s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) { + s->match_length = longest_match (s, hash_head); + } else if (s->strategy == Z_RLE && s->strstart - hash_head == 1) { + s->match_length = longest_match_fast (s, hash_head); + } + /* longest_match() or longest_match_fast() sets match_start */ + + if (s->match_length <= 5 && (s->strategy == Z_FILTERED +#if TOO_FAR <= 32767 + || (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR) +#endif + )) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s->match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) { + uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + check_match(s, s->strstart-1, s->prev_match, s->prev_length); + + _tr_tally_dist(s, s->strstart -1 - s->prev_match, + s->prev_length - MIN_MATCH, bflush); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s->lookahead -= s->prev_length-1; + s->prev_length -= 2; + do { + if (++s->strstart <= max_insert) { + INSERT_STRING(s, s->strstart, hash_head); + } + } while (--s->prev_length != 0); + s->match_available = 0; + s->match_length = MIN_MATCH-1; + s->strstart++; + + if (bflush) FLUSH_BLOCK(s, 0); + + } else if (s->match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { + FLUSH_BLOCK_ONLY(s, 0); + } + s->strstart++; + s->lookahead--; + if (s->strm->avail_out == 0) return need_more; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s->match_available) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + s->match_available = 0; + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} +#endif /* FASTEST */ + +#if 0 +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +local block_state deflate_rle(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + uInt run; /* length of run */ + uInt max; /* maximum length of run */ + uInt prev; /* byte at distance one to match */ + Bytef *scan; /* scan for end of run */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest encodable run. + */ + if (s->lookahead < MAX_MATCH) { + fill_window(s); + if (s->lookahead < MAX_MATCH && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + run = 0; + if (s->strstart > 0) { /* if there is a previous byte, that is */ + max = s->lookahead < MAX_MATCH ? s->lookahead : MAX_MATCH; + scan = s->window + s->strstart - 1; + prev = *scan++; + do { + if (*scan++ != prev) + break; + } while (++run < max); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (run >= MIN_MATCH) { + check_match(s, s->strstart, s->strstart - 1, run); + _tr_tally_dist(s, 1, run - MIN_MATCH, bflush); + s->lookahead -= run; + s->strstart += run; + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} +#endif diff --git a/neo/framework/zlib/deflate.h b/neo/framework/zlib/deflate.h new file mode 100644 index 00000000..05a5ab3a --- /dev/null +++ b/neo/framework/zlib/deflate.h @@ -0,0 +1,331 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-2004 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef DEFLATE_H +#define DEFLATE_H + +#include "zutil.h" + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer creation by deflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip encoding + should be left enabled. */ +#ifndef NO_GZIP +# define GZIP +#endif + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define INIT_STATE 42 +#define EXTRA_STATE 69 +#define NAME_STATE 73 +#define COMMENT_STATE 91 +#define HCRC_STATE 103 +#define BUSY_STATE 113 +#define FINISH_STATE 666 +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + uInt pending; /* nb of bytes in the pending buffer */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + gz_headerp gzhead; /* gzip header information to write */ + uInt gzindex; /* where in extra, name, or comment */ + Byte method; /* STORED (for zip only) or DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to supress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + int last_eob_len; /* bit length of EOB code for last block */ + +#ifdef DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + + /* in trees.c */ +void _tr_init OF((deflate_state *s)); +int _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); +void _tr_align OF((deflate_state *s)); +void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch _length_code[]; + extern uch _dist_code[]; +#else + extern const uch _length_code[]; + extern const uch _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (length); \ + ush dist = (distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif /* DEFLATE_H */ diff --git a/neo/framework/zlib/example.c b/neo/framework/zlib/example.c new file mode 100644 index 00000000..6c8a0ee7 --- /dev/null +++ b/neo/framework/zlib/example.c @@ -0,0 +1,565 @@ +/* example.c -- usage example of the zlib compression library + * Copyright (C) 1995-2004 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include +#include "zlib.h" + +#ifdef STDC +# include +# include +#endif + +#if defined(VMS) || defined(RISCOS) +# define TESTFILE "foo-gz" +#else +# define TESTFILE "foo.gz" +#endif + +#define CHECK_ERR(err, msg) { \ + if (err != Z_OK) { \ + fprintf(stderr, "%s error: %d\n", msg, err); \ + exit(1); \ + } \ +} + +const char hello[] = "hello, hello!"; +/* "hello world" would be more standard, but the repeated "hello" + * stresses the compression code better, sorry... + */ + +const char dictionary[] = "hello"; +uLong dictId; /* Adler32 value of the dictionary */ + +void test_compress OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_gzio OF((const char *fname, + Byte *uncompr, uLong uncomprLen)); +void test_deflate OF((Byte *compr, uLong comprLen)); +void test_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_large_deflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_large_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_flush OF((Byte *compr, uLong *comprLen)); +void test_sync OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_dict_deflate OF((Byte *compr, uLong comprLen)); +void test_dict_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +int main OF((int argc, char *argv[])); + +/* =========================================================================== + * Test compress() and uncompress() + */ +void test_compress(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + uLong len = (uLong)strlen(hello)+1; + + err = compress(compr, &comprLen, (const Bytef*)hello, len); + CHECK_ERR(err, "compress"); + + strcpy((char*)uncompr, "garbage"); + + err = uncompress(uncompr, &uncomprLen, compr, comprLen); + CHECK_ERR(err, "uncompress"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad uncompress\n"); + exit(1); + } else { + printf("uncompress(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test read/write of .gz files + */ +void test_gzio(fname, uncompr, uncomprLen) + const char *fname; /* compressed file name */ + Byte *uncompr; + uLong uncomprLen; +{ +#ifdef NO_GZCOMPRESS + fprintf(stderr, "NO_GZCOMPRESS -- gz* functions cannot compress\n"); +#else + int err; + int len = (int)strlen(hello)+1; + gzFile file; + z_off_t pos; + + file = gzopen(fname, "wb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + gzputc(file, 'h'); + if (gzputs(file, "ello") != 4) { + fprintf(stderr, "gzputs err: %s\n", gzerror(file, &err)); + exit(1); + } + if (gzprintf(file, ", %s!", "hello") != 8) { + fprintf(stderr, "gzprintf err: %s\n", gzerror(file, &err)); + exit(1); + } + gzseek(file, 1L, SEEK_CUR); /* add one zero byte */ + gzclose(file); + + file = gzopen(fname, "rb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + strcpy((char*)uncompr, "garbage"); + + if (gzread(file, uncompr, (unsigned)uncomprLen) != len) { + fprintf(stderr, "gzread err: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad gzread: %s\n", (char*)uncompr); + exit(1); + } else { + printf("gzread(): %s\n", (char*)uncompr); + } + + pos = gzseek(file, -8L, SEEK_CUR); + if (pos != 6 || gztell(file) != pos) { + fprintf(stderr, "gzseek error, pos=%ld, gztell=%ld\n", + (long)pos, (long)gztell(file)); + exit(1); + } + + if (gzgetc(file) != ' ') { + fprintf(stderr, "gzgetc error\n"); + exit(1); + } + + if (gzungetc(' ', file) != ' ') { + fprintf(stderr, "gzungetc error\n"); + exit(1); + } + + gzgets(file, (char*)uncompr, (int)uncomprLen); + if (strlen((char*)uncompr) != 7) { /* " hello!" */ + fprintf(stderr, "gzgets err after gzseek: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello + 6)) { + fprintf(stderr, "bad gzgets after gzseek\n"); + exit(1); + } else { + printf("gzgets() after gzseek: %s\n", (char*)uncompr); + } + + gzclose(file); +#endif +} + +/* =========================================================================== + * Test deflate() with small buffers + */ +void test_deflate(compr, comprLen) + Byte *compr; + uLong comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + uLong len = (uLong)strlen(hello)+1; + + c_stream.zalloc = (alloc_func)0; + c_stream.zfree = (free_func)0; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (Bytef*)hello; + c_stream.next_out = compr; + + while (c_stream.total_in != len && c_stream.total_out < comprLen) { + c_stream.avail_in = c_stream.avail_out = 1; /* force small buffers */ + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + } + /* Finish the stream, still forcing small buffers: */ + for (;;) { + c_stream.avail_out = 1; + err = deflate(&c_stream, Z_FINISH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "deflate"); + } + + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with small buffers + */ +void test_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = (alloc_func)0; + d_stream.zfree = (free_func)0; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 0; + d_stream.next_out = uncompr; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + while (d_stream.total_out < uncomprLen && d_stream.total_in < comprLen) { + d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */ + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate\n"); + exit(1); + } else { + printf("inflate(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test deflate() with large buffers and dynamic change of compression level + */ +void test_large_deflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = (alloc_func)0; + c_stream.zfree = (free_func)0; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_SPEED); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + /* At this point, uncompr is still mostly zeroes, so it should compress + * very well: + */ + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + if (c_stream.avail_in != 0) { + fprintf(stderr, "deflate not greedy\n"); + exit(1); + } + + /* Feed in already compressed data and switch to no compression: */ + deflateParams(&c_stream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); + c_stream.next_in = compr; + c_stream.avail_in = (uInt)comprLen/2; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + /* Switch back to compressing mode: */ + deflateParams(&c_stream, Z_BEST_COMPRESSION, Z_FILTERED); + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with large buffers + */ +void test_large_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = (alloc_func)0; + d_stream.zfree = (free_func)0; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + for (;;) { + d_stream.next_out = uncompr; /* discard the output */ + d_stream.avail_out = (uInt)uncomprLen; + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "large inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (d_stream.total_out != 2*uncomprLen + comprLen/2) { + fprintf(stderr, "bad large inflate: %ld\n", d_stream.total_out); + exit(1); + } else { + printf("large_inflate(): OK\n"); + } +} + +/* =========================================================================== + * Test deflate() with full flush + */ +void test_flush(compr, comprLen) + Byte *compr; + uLong *comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + uInt len = (uInt)strlen(hello)+1; + + c_stream.zalloc = (alloc_func)0; + c_stream.zfree = (free_func)0; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (Bytef*)hello; + c_stream.next_out = compr; + c_stream.avail_in = 3; + c_stream.avail_out = (uInt)*comprLen; + err = deflate(&c_stream, Z_FULL_FLUSH); + CHECK_ERR(err, "deflate"); + + compr[3]++; /* force an error in first compressed block */ + c_stream.avail_in = len - 3; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + CHECK_ERR(err, "deflate"); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); + + *comprLen = c_stream.total_out; +} + +/* =========================================================================== + * Test inflateSync() + */ +void test_sync(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = (alloc_func)0; + d_stream.zfree = (free_func)0; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 2; /* just read the zlib header */ + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + inflate(&d_stream, Z_NO_FLUSH); + CHECK_ERR(err, "inflate"); + + d_stream.avail_in = (uInt)comprLen-2; /* read all compressed data */ + err = inflateSync(&d_stream); /* but skip the damaged part */ + CHECK_ERR(err, "inflateSync"); + + err = inflate(&d_stream, Z_FINISH); + if (err != Z_DATA_ERROR) { + fprintf(stderr, "inflate should report DATA_ERROR\n"); + /* Because of incorrect adler32 */ + exit(1); + } + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + printf("after inflateSync(): hel%s\n", (char *)uncompr); +} + +/* =========================================================================== + * Test deflate() with preset dictionary + */ +void test_dict_deflate(compr, comprLen) + Byte *compr; + uLong comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = (alloc_func)0; + c_stream.zfree = (free_func)0; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + err = deflateSetDictionary(&c_stream, + (const Bytef*)dictionary, sizeof(dictionary)); + CHECK_ERR(err, "deflateSetDictionary"); + + dictId = c_stream.adler; + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + c_stream.next_in = (Bytef*)hello; + c_stream.avail_in = (uInt)strlen(hello)+1; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with a preset dictionary + */ +void test_dict_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = (alloc_func)0; + d_stream.zfree = (free_func)0; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + for (;;) { + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + if (err == Z_NEED_DICT) { + if (d_stream.adler != dictId) { + fprintf(stderr, "unexpected dictionary"); + exit(1); + } + err = inflateSetDictionary(&d_stream, (const Bytef*)dictionary, + sizeof(dictionary)); + } + CHECK_ERR(err, "inflate with dict"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate with dict\n"); + exit(1); + } else { + printf("inflate with dictionary: %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Usage: example [output.gz [input.gz]] + */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + Byte *compr, *uncompr; + uLong comprLen = 10000*sizeof(int); /* don't overflow on MSDOS */ + uLong uncomprLen = comprLen; + static const char* myVersion = ZLIB_VERSION; + + if (zlibVersion()[0] != myVersion[0]) { + fprintf(stderr, "incompatible zlib version\n"); + exit(1); + + } else if (strcmp(zlibVersion(), ZLIB_VERSION) != 0) { + fprintf(stderr, "warning: different zlib version\n"); + } + + printf("zlib version %s = 0x%04x, compile flags = 0x%lx\n", + ZLIB_VERSION, ZLIB_VERNUM, zlibCompileFlags()); + + compr = (Byte*)calloc((uInt)comprLen, 1); + uncompr = (Byte*)calloc((uInt)uncomprLen, 1); + /* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + */ + if (compr == Z_NULL || uncompr == Z_NULL) { + printf("out of memory\n"); + exit(1); + } + test_compress(compr, comprLen, uncompr, uncomprLen); + + test_gzio((argc > 1 ? argv[1] : TESTFILE), + uncompr, uncomprLen); + + test_deflate(compr, comprLen); + test_inflate(compr, comprLen, uncompr, uncomprLen); + + test_large_deflate(compr, comprLen, uncompr, uncomprLen); + test_large_inflate(compr, comprLen, uncompr, uncomprLen); + + test_flush(compr, &comprLen); + test_sync(compr, comprLen, uncompr, uncomprLen); + comprLen = uncomprLen; + + test_dict_deflate(compr, comprLen); + test_dict_inflate(compr, comprLen, uncompr, uncomprLen); + + free(compr); + free(uncompr); + + return 0; +} diff --git a/neo/framework/zlib/gzio.c b/neo/framework/zlib/gzio.c new file mode 100644 index 00000000..7e90f492 --- /dev/null +++ b/neo/framework/zlib/gzio.c @@ -0,0 +1,1026 @@ +/* gzio.c -- IO on .gz files + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Compile this file with -DNO_GZCOMPRESS to avoid the compression code. + */ + +/* @(#) $Id$ */ + +#include + +#include "zutil.h" + +#ifdef NO_DEFLATE /* for compatibility with old definition */ +# define NO_GZCOMPRESS +#endif + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +#ifndef Z_BUFSIZE +# ifdef MAXSEG_64K +# define Z_BUFSIZE 4096 /* minimize memory usage for 16-bit DOS */ +# else +# define Z_BUFSIZE 16384 +# endif +#endif +#ifndef Z_PRINTF_BUFSIZE +# define Z_PRINTF_BUFSIZE 4096 +#endif + +#ifdef __MVS__ +# pragma map (fdopen , "\174\174FDOPEN") + FILE *fdopen(int, const char *); +#endif + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern void free OF((voidpf ptr)); +#endif + +#define ALLOC(size) malloc(size) +#define TRYFREE(p) {if (p) free(p);} + +static int const gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + +typedef struct gz_stream { + z_stream stream; + int z_err; /* error code for last stream operation */ + int z_eof; /* set if end of input file */ + FILE *file; /* .gz file */ + Byte *inbuf; /* input buffer */ + Byte *outbuf; /* output buffer */ + uLong crc; /* crc32 of uncompressed data */ + char *msg; /* error message */ + char *path; /* path name for debugging only */ + int transparent; /* 1 if input file is not a .gz file */ + char mode; /* 'w' or 'r' */ + z_off_t start; /* start of compressed data in file (header skipped) */ + z_off_t in; /* bytes into deflate or inflate */ + z_off_t out; /* bytes out of deflate or inflate */ + int back; /* one character push-back */ + int last; /* true if push-back is last character */ +} gz_stream; + + +local gzFile gz_open OF((const char *path, const char *mode, int fd)); +local int do_flush OF((gzFile file, int flush)); +local int get_byte OF((gz_stream *s)); +local void check_header OF((gz_stream *s)); +local int destroy OF((gz_stream *s)); +local void putLong OF((FILE *file, uLong x)); +local uLong getLong OF((gz_stream *s)); + +/* =========================================================================== + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb"). The file is given either by file descriptor + or path name (if fd == -1). + gz_open returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). +*/ +local gzFile gz_open (path, mode, fd) + const char *path; + const char *mode; + int fd; +{ + int err; + int level = Z_DEFAULT_COMPRESSION; /* compression level */ + int strategy = Z_DEFAULT_STRATEGY; /* compression strategy */ + char *p = (char*)mode; + gz_stream *s; + char fmode[80]; /* copy of mode, without the compression level */ + char *m = fmode; + + if (!path || !mode) return Z_NULL; + + s = (gz_stream *)ALLOC(sizeof(gz_stream)); + if (!s) return Z_NULL; + + s->stream.zalloc = (alloc_func)0; + s->stream.zfree = (free_func)0; + s->stream.opaque = (voidpf)0; + s->stream.next_in = s->inbuf = Z_NULL; + s->stream.next_out = s->outbuf = Z_NULL; + s->stream.avail_in = s->stream.avail_out = 0; + s->file = NULL; + s->z_err = Z_OK; + s->z_eof = 0; + s->in = 0; + s->out = 0; + s->back = EOF; + s->crc = crc32(0L, Z_NULL, 0); + s->msg = NULL; + s->transparent = 0; + + s->path = (char*)ALLOC(strlen(path)+1); + if (s->path == NULL) { + return destroy(s), (gzFile)Z_NULL; + } + strcpy(s->path, path); /* do this early for debugging */ + + s->mode = '\0'; + do { + if (*p == 'r') s->mode = 'r'; + if (*p == 'w' || *p == 'a') s->mode = 'w'; + if (*p >= '0' && *p <= '9') { + level = *p - '0'; + } else if (*p == 'f') { + strategy = Z_FILTERED; + } else if (*p == 'h') { + strategy = Z_HUFFMAN_ONLY; + } else if (*p == 'R') { + strategy = Z_RLE; + } else { + *m++ = *p; /* copy the mode */ + } + } while (*p++ && m != fmode + sizeof(fmode)); + if (s->mode == '\0') return destroy(s), (gzFile)Z_NULL; + + if (s->mode == 'w') { +#ifdef NO_GZCOMPRESS + err = Z_STREAM_ERROR; +#else + err = deflateInit2(&(s->stream), level, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, strategy); + /* windowBits is passed < 0 to suppress zlib header */ + + s->stream.next_out = s->outbuf = (Byte*)ALLOC(Z_BUFSIZE); +#endif + if (err != Z_OK || s->outbuf == Z_NULL) { + return destroy(s), (gzFile)Z_NULL; + } + } else { + s->stream.next_in = s->inbuf = (Byte*)ALLOC(Z_BUFSIZE); + + err = inflateInit2(&(s->stream), -MAX_WBITS); + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes are + * present after the compressed stream. + */ + if (err != Z_OK || s->inbuf == Z_NULL) { + return destroy(s), (gzFile)Z_NULL; + } + } + s->stream.avail_out = Z_BUFSIZE; + + errno = 0; + s->file = fd < 0 ? F_OPEN(path, fmode) : (FILE*)fdopen(fd, fmode); + + if (s->file == NULL) { + return destroy(s), (gzFile)Z_NULL; + } + if (s->mode == 'w') { + /* Write a very simple .gz header: + */ + fprintf(s->file, "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], gz_magic[1], + Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE); + s->start = 10L; + /* We use 10L instead of ftell(s->file) to because ftell causes an + * fflush on some systems. This version of the library doesn't use + * start anyway in write mode, so this initialization is not + * necessary. + */ + } else { + check_header(s); /* skip the .gz header */ + s->start = ftell(s->file) - s->stream.avail_in; + } + + return (gzFile)s; +} + +/* =========================================================================== + Opens a gzip (.gz) file for reading or writing. +*/ +gzFile ZEXPORT gzopen (path, mode) + const char *path; + const char *mode; +{ + return gz_open (path, mode, -1); +} + +/* =========================================================================== + Associate a gzFile with the file descriptor fd. fd is not dup'ed here + to mimic the behavio(u)r of fdopen. +*/ +gzFile ZEXPORT gzdopen (fd, mode) + int fd; + const char *mode; +{ + char name[46]; /* allow for up to 128-bit integers */ + + if (fd < 0) return (gzFile)Z_NULL; + sprintf(name, "", fd); /* for debugging */ + + return gz_open (name, mode, fd); +} + +/* =========================================================================== + * Update the compression level and strategy + */ +int ZEXPORT gzsetparams (file, level, strategy) + gzFile file; + int level; + int strategy; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; + + /* Make room to allow flushing */ + if (s->stream.avail_out == 0) { + + s->stream.next_out = s->outbuf; + if (fwrite(s->outbuf, 1, Z_BUFSIZE, s->file) != Z_BUFSIZE) { + s->z_err = Z_ERRNO; + } + s->stream.avail_out = Z_BUFSIZE; + } + + return deflateParams (&(s->stream), level, strategy); +} + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ +local int get_byte(s) + gz_stream *s; +{ + if (s->z_eof) return EOF; + if (s->stream.avail_in == 0) { + errno = 0; + s->stream.avail_in = (uInt)fread(s->inbuf, 1, Z_BUFSIZE, s->file); + if (s->stream.avail_in == 0) { + s->z_eof = 1; + if (ferror(s->file)) s->z_err = Z_ERRNO; + return EOF; + } + s->stream.next_in = s->inbuf; + } + s->stream.avail_in--; + return *(s->stream.next_in)++; +} + +/* =========================================================================== + Check the gzip header of a gz_stream opened for reading. Set the stream + mode to transparent if the gzip magic header is not present; set s->err + to Z_DATA_ERROR if the magic header is present but the rest of the header + is incorrect. + IN assertion: the stream s has already been created sucessfully; + s->stream.avail_in is zero for the first time, but may be non-zero + for concatenated .gz files. +*/ +local void check_header(s) + gz_stream *s; +{ + int method; /* method byte */ + int flags; /* flags byte */ + uInt len; + int c; + + /* Assure two bytes in the buffer so we can peek ahead -- handle case + where first byte of header is at the end of the buffer after the last + gzip segment */ + len = s->stream.avail_in; + if (len < 2) { + if (len) s->inbuf[0] = s->stream.next_in[0]; + errno = 0; + len = (uInt)fread(s->inbuf + len, 1, Z_BUFSIZE >> len, s->file); + if (len == 0 && ferror(s->file)) s->z_err = Z_ERRNO; + s->stream.avail_in += len; + s->stream.next_in = s->inbuf; + if (s->stream.avail_in < 2) { + s->transparent = s->stream.avail_in; + return; + } + } + + /* Peek ahead to check the gzip magic header */ + if (s->stream.next_in[0] != gz_magic[0] || + s->stream.next_in[1] != gz_magic[1]) { + s->transparent = 1; + return; + } + s->stream.avail_in -= 2; + s->stream.next_in += 2; + + /* Check the rest of the gzip header */ + method = get_byte(s); + flags = get_byte(s); + if (method != Z_DEFLATED || (flags & RESERVED) != 0) { + s->z_err = Z_DATA_ERROR; + return; + } + + /* Discard time, xflags and OS code: */ + for (len = 0; len < 6; len++) (void)get_byte(s); + + if ((flags & EXTRA_FIELD) != 0) { /* skip the extra field */ + len = (uInt)get_byte(s); + len += ((uInt)get_byte(s))<<8; + /* len is garbage if EOF but the loop below will quit anyway */ + while (len-- != 0 && get_byte(s) != EOF) ; + } + if ((flags & ORIG_NAME) != 0) { /* skip the original file name */ + while ((c = get_byte(s)) != 0 && c != EOF) ; + } + if ((flags & COMMENT) != 0) { /* skip the .gz file comment */ + while ((c = get_byte(s)) != 0 && c != EOF) ; + } + if ((flags & HEAD_CRC) != 0) { /* skip the header crc */ + for (len = 0; len < 2; len++) (void)get_byte(s); + } + s->z_err = s->z_eof ? Z_DATA_ERROR : Z_OK; +} + + /* =========================================================================== + * Cleanup then free the given gz_stream. Return a zlib error code. + Try freeing in the reverse order of allocations. + */ +local int destroy (s) + gz_stream *s; +{ + int err = Z_OK; + + if (!s) return Z_STREAM_ERROR; + + TRYFREE(s->msg); + + if (s->stream.state != NULL) { + if (s->mode == 'w') { +#ifdef NO_GZCOMPRESS + err = Z_STREAM_ERROR; +#else + err = deflateEnd(&(s->stream)); +#endif + } else if (s->mode == 'r') { + err = inflateEnd(&(s->stream)); + } + } + if (s->file != NULL && fclose(s->file)) { +#ifdef ESPIPE + if (errno != ESPIPE) /* fclose is broken for pipes in HP/UX */ +#endif + err = Z_ERRNO; + } + if (s->z_err < 0) err = s->z_err; + + TRYFREE(s->inbuf); + TRYFREE(s->outbuf); + TRYFREE(s->path); + TRYFREE(s); + return err; +} + +/* =========================================================================== + Reads the given number of uncompressed bytes from the compressed file. + gzread returns the number of bytes actually read (0 for end of file). +*/ +int ZEXPORT gzread (file, buf, len) + gzFile file; + voidp buf; + unsigned len; +{ + gz_stream *s = (gz_stream*)file; + Bytef *start = (Bytef*)buf; /* starting point for crc computation */ + Byte *next_out; /* == stream.next_out but not forced far (for MSDOS) */ + + if (s == NULL || s->mode != 'r') return Z_STREAM_ERROR; + + if (s->z_err == Z_DATA_ERROR || s->z_err == Z_ERRNO) return -1; + if (s->z_err == Z_STREAM_END) return 0; /* EOF */ + + next_out = (Byte*)buf; + s->stream.next_out = (Bytef*)buf; + s->stream.avail_out = len; + + if (s->stream.avail_out && s->back != EOF) { + *next_out++ = s->back; + s->stream.next_out++; + s->stream.avail_out--; + s->back = EOF; + s->out++; + start++; + if (s->last) { + s->z_err = Z_STREAM_END; + return 1; + } + } + + while (s->stream.avail_out != 0) { + + if (s->transparent) { + /* Copy first the lookahead bytes: */ + uInt n = s->stream.avail_in; + if (n > s->stream.avail_out) n = s->stream.avail_out; + if (n > 0) { + zmemcpy(s->stream.next_out, s->stream.next_in, n); + next_out += n; + s->stream.next_out = next_out; + s->stream.next_in += n; + s->stream.avail_out -= n; + s->stream.avail_in -= n; + } + if (s->stream.avail_out > 0) { + s->stream.avail_out -= + (uInt)fread(next_out, 1, s->stream.avail_out, s->file); + } + len -= s->stream.avail_out; + s->in += len; + s->out += len; + if (len == 0) s->z_eof = 1; + return (int)len; + } + if (s->stream.avail_in == 0 && !s->z_eof) { + + errno = 0; + s->stream.avail_in = (uInt)fread(s->inbuf, 1, Z_BUFSIZE, s->file); + if (s->stream.avail_in == 0) { + s->z_eof = 1; + if (ferror(s->file)) { + s->z_err = Z_ERRNO; + break; + } + } + s->stream.next_in = s->inbuf; + } + s->in += s->stream.avail_in; + s->out += s->stream.avail_out; + s->z_err = inflate(&(s->stream), Z_NO_FLUSH); + s->in -= s->stream.avail_in; + s->out -= s->stream.avail_out; + + if (s->z_err == Z_STREAM_END) { + /* Check CRC and original size */ + s->crc = crc32(s->crc, start, (uInt)(s->stream.next_out - start)); + start = s->stream.next_out; + + if (getLong(s) != s->crc) { + s->z_err = Z_DATA_ERROR; + } else { + (void)getLong(s); + /* The uncompressed length returned by above getlong() may be + * different from s->out in case of concatenated .gz files. + * Check for such files: + */ + check_header(s); + if (s->z_err == Z_OK) { + inflateReset(&(s->stream)); + s->crc = crc32(0L, Z_NULL, 0); + } + } + } + if (s->z_err != Z_OK || s->z_eof) break; + } + s->crc = crc32(s->crc, start, (uInt)(s->stream.next_out - start)); + + if (len == s->stream.avail_out && + (s->z_err == Z_DATA_ERROR || s->z_err == Z_ERRNO)) + return -1; + return (int)(len - s->stream.avail_out); +} + + +/* =========================================================================== + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ +int ZEXPORT gzgetc(file) + gzFile file; +{ + unsigned char c; + + return gzread(file, &c, 1) == 1 ? c : -1; +} + + +/* =========================================================================== + Push one byte back onto the stream. +*/ +int ZEXPORT gzungetc(c, file) + int c; + gzFile file; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'r' || c == EOF || s->back != EOF) return EOF; + s->back = c; + s->out--; + s->last = (s->z_err == Z_STREAM_END); + if (s->last) s->z_err = Z_OK; + s->z_eof = 0; + return c; +} + + +/* =========================================================================== + Reads bytes from the compressed file until len-1 characters are + read, or a newline character is read and transferred to buf, or an + end-of-file condition is encountered. The string is then terminated + with a null character. + gzgets returns buf, or Z_NULL in case of error. + + The current implementation is not optimized at all. +*/ +char * ZEXPORT gzgets(file, buf, len) + gzFile file; + char *buf; + int len; +{ + char *b = buf; + if (buf == Z_NULL || len <= 0) return Z_NULL; + + while (--len > 0 && gzread(file, buf, 1) == 1 && *buf++ != '\n') ; + *buf = '\0'; + return b == buf && len > 0 ? Z_NULL : b; +} + + +#ifndef NO_GZCOMPRESS +/* =========================================================================== + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of bytes actually written (0 in case of error). +*/ +int ZEXPORT gzwrite (file, buf, len) + gzFile file; + voidpc buf; + unsigned len; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; + + s->stream.next_in = (Bytef*)buf; + s->stream.avail_in = len; + + while (s->stream.avail_in != 0) { + + if (s->stream.avail_out == 0) { + + s->stream.next_out = s->outbuf; + if (fwrite(s->outbuf, 1, Z_BUFSIZE, s->file) != Z_BUFSIZE) { + s->z_err = Z_ERRNO; + break; + } + s->stream.avail_out = Z_BUFSIZE; + } + s->in += s->stream.avail_in; + s->out += s->stream.avail_out; + s->z_err = deflate(&(s->stream), Z_NO_FLUSH); + s->in -= s->stream.avail_in; + s->out -= s->stream.avail_out; + if (s->z_err != Z_OK) break; + } + s->crc = crc32(s->crc, (const Bytef *)buf, len); + + return (int)(len - s->stream.avail_in); +} + + +/* =========================================================================== + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ +#ifdef STDC +#include + +int ZEXPORTVA gzprintf (gzFile file, const char *format, /* args */ ...) +{ + char buf[Z_PRINTF_BUFSIZE]; + va_list va; + int len; + + buf[sizeof(buf) - 1] = 0; + va_start(va, format); +#ifdef NO_vsnprintf +# ifdef HAS_vsprintf_void + (void)vsprintf(buf, format, va); + va_end(va); + for (len = 0; len < sizeof(buf); len++) + if (buf[len] == 0) break; +# else + len = vsprintf(buf, format, va); + va_end(va); +# endif +#else +# ifdef HAS_vsnprintf_void + (void)vsnprintf(buf, sizeof(buf), format, va); + va_end(va); + len = strlen(buf); +# else + len = vsnprintf(buf, sizeof(buf), format, va); + va_end(va); +# endif +#endif + if (len <= 0 || len >= (int)sizeof(buf) || buf[sizeof(buf) - 1] != 0) + return 0; + return gzwrite(file, buf, (unsigned)len); +} +#else /* not ANSI C */ + +int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + gzFile file; + const char *format; + int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, a19, a20; +{ + char buf[Z_PRINTF_BUFSIZE]; + int len; + + buf[sizeof(buf) - 1] = 0; +#ifdef NO_snprintf +# ifdef HAS_sprintf_void + sprintf(buf, format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + for (len = 0; len < sizeof(buf); len++) + if (buf[len] == 0) break; +# else + len = sprintf(buf, format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); +# endif +#else +# ifdef HAS_snprintf_void + snprintf(buf, sizeof(buf), format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = strlen(buf); +# else + len = snprintf(buf, sizeof(buf), format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); +# endif +#endif + if (len <= 0 || len >= sizeof(buf) || buf[sizeof(buf) - 1] != 0) + return 0; + return gzwrite(file, buf, len); +} +#endif + +/* =========================================================================== + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ +int ZEXPORT gzputc(file, c) + gzFile file; + int c; +{ + unsigned char cc = (unsigned char) c; /* required for big endian systems */ + + return gzwrite(file, &cc, 1) == 1 ? (int)cc : -1; +} + + +/* =========================================================================== + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ +int ZEXPORT gzputs(file, s) + gzFile file; + const char *s; +{ + return gzwrite(file, (char*)s, (unsigned)strlen(s)); +} + + +/* =========================================================================== + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. +*/ +local int do_flush (file, flush) + gzFile file; + int flush; +{ + uInt len; + int done = 0; + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; + + s->stream.avail_in = 0; /* should be zero already anyway */ + + for (;;) { + len = Z_BUFSIZE - s->stream.avail_out; + + if (len != 0) { + if ((uInt)fwrite(s->outbuf, 1, len, s->file) != len) { + s->z_err = Z_ERRNO; + return Z_ERRNO; + } + s->stream.next_out = s->outbuf; + s->stream.avail_out = Z_BUFSIZE; + } + if (done) break; + s->out += s->stream.avail_out; + s->z_err = deflate(&(s->stream), flush); + s->out -= s->stream.avail_out; + + /* Ignore the second of two consecutive flushes: */ + if (len == 0 && s->z_err == Z_BUF_ERROR) s->z_err = Z_OK; + + /* deflate has finished flushing only when it hasn't used up + * all the available space in the output buffer: + */ + done = (s->stream.avail_out != 0 || s->z_err == Z_STREAM_END); + + if (s->z_err != Z_OK && s->z_err != Z_STREAM_END) break; + } + return s->z_err == Z_STREAM_END ? Z_OK : s->z_err; +} + +int ZEXPORT gzflush (file, flush) + gzFile file; + int flush; +{ + gz_stream *s = (gz_stream*)file; + int err = do_flush (file, flush); + + if (err) return err; + fflush(s->file); + return s->z_err == Z_STREAM_END ? Z_OK : s->z_err; +} +#endif /* NO_GZCOMPRESS */ + +/* =========================================================================== + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error. + SEEK_END is not implemented, returns error. + In this version of the library, gzseek can be extremely slow. +*/ +z_off_t ZEXPORT gzseek (file, offset, whence) + gzFile file; + z_off_t offset; + int whence; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || whence == SEEK_END || + s->z_err == Z_ERRNO || s->z_err == Z_DATA_ERROR) { + return -1L; + } + + if (s->mode == 'w') { +#ifdef NO_GZCOMPRESS + return -1L; +#else + if (whence == SEEK_SET) { + offset -= s->in; + } + if (offset < 0) return -1L; + + /* At this point, offset is the number of zero bytes to write. */ + if (s->inbuf == Z_NULL) { + s->inbuf = (Byte*)ALLOC(Z_BUFSIZE); /* for seeking */ + if (s->inbuf == Z_NULL) return -1L; + zmemzero(s->inbuf, Z_BUFSIZE); + } + while (offset > 0) { + uInt size = Z_BUFSIZE; + if (offset < Z_BUFSIZE) size = (uInt)offset; + + size = gzwrite(file, s->inbuf, size); + if (size == 0) return -1L; + + offset -= size; + } + return s->in; +#endif + } + /* Rest of function is for reading only */ + + /* compute absolute position */ + if (whence == SEEK_CUR) { + offset += s->out; + } + if (offset < 0) return -1L; + + if (s->transparent) { + /* map to fseek */ + s->back = EOF; + s->stream.avail_in = 0; + s->stream.next_in = s->inbuf; + if (fseek(s->file, offset, SEEK_SET) < 0) return -1L; + + s->in = s->out = offset; + return offset; + } + + /* For a negative seek, rewind and use positive seek */ + if (offset >= s->out) { + offset -= s->out; + } else if (gzrewind(file) < 0) { + return -1L; + } + /* offset is now the number of bytes to skip. */ + + if (offset != 0 && s->outbuf == Z_NULL) { + s->outbuf = (Byte*)ALLOC(Z_BUFSIZE); + if (s->outbuf == Z_NULL) return -1L; + } + if (offset && s->back != EOF) { + s->back = EOF; + s->out++; + offset--; + if (s->last) s->z_err = Z_STREAM_END; + } + while (offset > 0) { + int size = Z_BUFSIZE; + if (offset < Z_BUFSIZE) size = (int)offset; + + size = gzread(file, s->outbuf, (uInt)size); + if (size <= 0) return -1L; + offset -= size; + } + return s->out; +} + +/* =========================================================================== + Rewinds input file. +*/ +int ZEXPORT gzrewind (file) + gzFile file; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'r') return -1; + + s->z_err = Z_OK; + s->z_eof = 0; + s->back = EOF; + s->stream.avail_in = 0; + s->stream.next_in = s->inbuf; + s->crc = crc32(0L, Z_NULL, 0); + if (!s->transparent) (void)inflateReset(&s->stream); + s->in = 0; + s->out = 0; + return fseek(s->file, s->start, SEEK_SET); +} + +/* =========================================================================== + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. +*/ +z_off_t ZEXPORT gztell (file) + gzFile file; +{ + return gzseek(file, 0L, SEEK_CUR); +} + +/* =========================================================================== + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ +int ZEXPORT gzeof (file) + gzFile file; +{ + gz_stream *s = (gz_stream*)file; + + /* With concatenated compressed files that can have embedded + * crc trailers, z_eof is no longer the only/best indicator of EOF + * on a gz_stream. Handle end-of-stream error explicitly here. + */ + if (s == NULL || s->mode != 'r') return 0; + if (s->z_eof) return 1; + return s->z_err == Z_STREAM_END; +} + +/* =========================================================================== + Returns 1 if reading and doing so transparently, otherwise zero. +*/ +int ZEXPORT gzdirect (file) + gzFile file; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL || s->mode != 'r') return 0; + return s->transparent; +} + +/* =========================================================================== + Outputs a long in LSB order to the given file +*/ +local void putLong (file, x) + FILE *file; + uLong x; +{ + int n; + for (n = 0; n < 4; n++) { + fputc((int)(x & 0xff), file); + x >>= 8; + } +} + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets z_err in case + of error. +*/ +local uLong getLong (s) + gz_stream *s; +{ + uLong x = (uLong)get_byte(s); + int c; + + x += ((uLong)get_byte(s))<<8; + x += ((uLong)get_byte(s))<<16; + c = get_byte(s); + if (c == EOF) s->z_err = Z_DATA_ERROR; + x += ((uLong)c)<<24; + return x; +} + +/* =========================================================================== + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. +*/ +int ZEXPORT gzclose (file) + gzFile file; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL) return Z_STREAM_ERROR; + + if (s->mode == 'w') { +#ifdef NO_GZCOMPRESS + return Z_STREAM_ERROR; +#else + if (do_flush (file, Z_FINISH) != Z_OK) + return destroy((gz_stream*)file); + + putLong (s->file, s->crc); + putLong (s->file, (uLong)(s->in & 0xffffffff)); +#endif + } + return destroy((gz_stream*)file); +} + +#ifdef STDC +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +/* =========================================================================== + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ +const char * ZEXPORT gzerror (file, errnum) + gzFile file; + int *errnum; +{ + char *m; + gz_stream *s = (gz_stream*)file; + + if (s == NULL) { + *errnum = Z_STREAM_ERROR; + return (const char*)ERR_MSG(Z_STREAM_ERROR); + } + *errnum = s->z_err; + if (*errnum == Z_OK) return (const char*)""; + + m = (char*)(*errnum == Z_ERRNO ? zstrerror(errno) : s->stream.msg); + + if (m == NULL || *m == '\0') m = (char*)ERR_MSG(s->z_err); + + TRYFREE(s->msg); + s->msg = (char*)ALLOC(strlen(s->path) + strlen(m) + 3); + if (s->msg == Z_NULL) return (const char*)ERR_MSG(Z_MEM_ERROR); + strcpy(s->msg, s->path); + strcat(s->msg, ": "); + strcat(s->msg, m); + return (const char*)s->msg; +} + +/* =========================================================================== + Clear the error and end-of-file flags, and do the same for the real file. +*/ +void ZEXPORT gzclearerr (file) + gzFile file; +{ + gz_stream *s = (gz_stream*)file; + + if (s == NULL) return; + if (s->z_err != Z_STREAM_END) s->z_err = Z_OK; + s->z_eof = 0; + clearerr(s->file); +} diff --git a/neo/framework/zlib/infback.c b/neo/framework/zlib/infback.c new file mode 100644 index 00000000..455dbc9e --- /dev/null +++ b/neo/framework/zlib/infback.c @@ -0,0 +1,623 @@ +/* infback.c -- inflate using a call-back interface + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + This code is largely copied from inflate.c. Normally either infback.o or + inflate.o would be linked into an application--not both. The interface + with inffast.c is retained so that optimized assembler-coded versions of + inflate_fast() can be used with either inflate.c or infback.c. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + windowBits is in the range 8..15, and window is a user-supplied + window and output buffer that is 2**windowBits bytes. + */ +int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size) +z_streamp strm; +int windowBits; +unsigned char FAR *window; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL || + windowBits < 8 || windowBits > 15) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->dmax = 32768U; + state->wbits = windowBits; + state->wsize = 1U << windowBits; + state->window = window; + state->write = 0; + state->whave = 0; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +/* Macros for inflateBack(): */ + +/* Load returned state from inflate_fast() */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Set state from registers for inflate_fast() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = state->window; \ + left = state->wsize; \ + state->whave = left; \ + if (out(out_desc, put, left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc) +z_streamp strm; +in_func in; +void FAR *in_desc; +out_func out; +void FAR *out_desc; +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code this; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + state->mode = TYPE; + state->last = 0; + state->whave = 0; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = state->window; + left = state->wsize; + + /* Inflate until end of block marked as last */ + for (;;) + switch (state->mode) { + case TYPE: + /* determine and dispatch block type */ + if (state->last) { + BYTEBITS(); + state->mode = DONE; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + + /* copy stored block from input to output */ + while (state->length != 0) { + copy = state->length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.val < 16) { + NEEDBITS(this.bits); + DROPBITS(this.bits); + state->lens[state->have++] = this.val; + } + else { + if (this.val == 16) { + NEEDBITS(this.bits + 2); + DROPBITS(this.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (this.val == 17) { + NEEDBITS(this.bits + 3); + DROPBITS(this.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(this.bits + 7); + DROPBITS(this.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + + case LEN: + /* use inflate_fast() if we have enough input and output */ + if (have >= 6 && left >= 258) { + RESTORE(); + if (state->whave < state->wsize) + state->whave = state->wsize - left; + inflate_fast(strm, state->wsize); + LOAD(); + break; + } + + /* get a literal, length, or end-of-block code */ + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.op && (this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + state->length = (unsigned)this.val; + + /* process literal */ + if (this.op == 0) { + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + ROOM(); + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + } + + /* process end of block */ + if (this.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + + /* invalid code */ + if (this.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + state->extra = (unsigned)(this.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + + /* get distance code */ + for (;;) { + this = state->distcode[BITS(state->distbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if ((this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + if (this.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)this.val; + + /* get distance extra bits, if any */ + state->extra = (unsigned)(this.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } + if (state->offset > state->wsize - (state->whave < state->wsize ? + left : 0)) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = state->wsize - state->offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - state->offset; + copy = left; + } + if (copy > state->length) copy = state->length; + state->length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (state->length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left)) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBackEnd(strm) +z_streamp strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} diff --git a/neo/framework/zlib/inffast.c b/neo/framework/zlib/inffast.c new file mode 100644 index 00000000..bbee92ed --- /dev/null +++ b/neo/framework/zlib/inffast.c @@ -0,0 +1,318 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + unsigned char FAR *in; /* local strm->next_in */ + unsigned char FAR *last; /* while in < last, enough input available */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code this; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + write = state->write; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + this = lcode[hold & lmask]; + dolen: + op = (unsigned)(this.bits); + hold >>= op; + bits -= op; + op = (unsigned)(this.op); + if (op == 0) { /* literal */ + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + PUP(out) = (unsigned char)(this.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(this.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + this = dcode[hold & dmask]; + dodist: + op = (unsigned)(this.bits); + hold >>= op; + bits -= op; + op = (unsigned)(this.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(this.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + from = window - OFF; + if (write == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (write < op) { /* wrap around window */ + from += wsize + write - op; + op -= write; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (write < len) { /* some from start of window */ + op = write; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += write - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + this = dcode[this.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + this = lcode[this.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and write == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ diff --git a/neo/framework/zlib/inffast.h b/neo/framework/zlib/inffast.h new file mode 100644 index 00000000..1e88d2d9 --- /dev/null +++ b/neo/framework/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void inflate_fast OF((z_streamp strm, unsigned start)); diff --git a/neo/framework/zlib/inffixed.h b/neo/framework/zlib/inffixed.h new file mode 100644 index 00000000..75ed4b59 --- /dev/null +++ b/neo/framework/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. It + is part of the implementation of the compression library and + is subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; diff --git a/neo/framework/zlib/inflate.c b/neo/framework/zlib/inflate.c new file mode 100644 index 00000000..792fdee8 --- /dev/null +++ b/neo/framework/zlib/inflate.c @@ -0,0 +1,1368 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common write == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, unsigned out)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, + unsigned len)); + +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + strm->adler = 1; /* to support ill-conceived Java test suite */ + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->wsize = 0; + state->whave = 0; + state->write = 0; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflatePrime(strm, bits, value) +z_streamp strm; +int bits; +int value; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) +z_streamp strm; +int windowBits; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + if (windowBits < 0) { + state->wrap = 0; + windowBits = -windowBits; + } + else { + state->wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) windowBits &= 15; +#endif + } + if (windowBits < 8 || windowBits > 15) { + ZFREE(strm, state); + strm->state = Z_NULL; + return Z_STREAM_ERROR; + } + state->wbits = (unsigned)windowBits; + state->window = Z_NULL; + return inflateReset(strm); +} + +int ZEXPORT inflateInit_(strm, version, stream_size) +z_streamp strm; +const char *version; +int stream_size; +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, + state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow(strm, out) +z_streamp strm; +unsigned out; +{ + struct inflate_state FAR *state; + unsigned copy, dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->write = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + copy = out - strm->avail_out; + if (copy >= state->wsize) { + zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + state->write = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->write; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->write, strm->next_out - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, strm->next_out - copy, copy); + state->write = copy; + state->whave = state->wsize; + } + else { + state->write += dist; + if (state->write == state->wsize) state->write = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Reverse the bytes in a 32-bit value */ +#define REVERSE(q) \ + ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate(strm, flush) +z_streamp strm; +int flush; +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code this; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = (char *)"incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (len > state->wbits) { + strm->msg = (char *)"invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = (char *)"unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = (char *)"header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = REVERSE(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.val < 16) { + NEEDBITS(this.bits); + DROPBITS(this.bits); + state->lens[state->have++] = this.val; + } + else { + if (this.val == 16) { + NEEDBITS(this.bits + 2); + DROPBITS(this.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (this.val == 17) { + NEEDBITS(this.bits + 3); + DROPBITS(this.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(this.bits + 7); + DROPBITS(this.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + break; + } + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.op && (this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + state->length = (unsigned)this.val; + if ((int)(this.op) == 0) { + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + state->mode = LIT; + break; + } + if (this.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + if (this.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(this.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->mode = DIST; + case DIST: + for (;;) { + this = state->distcode[BITS(state->distbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if ((this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + if (this.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)this.val; + state->extra = (unsigned)(this.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + if (state->offset > state->whave + out - left) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->write) { + copy -= state->write; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->write - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + REVERSE(hold)) != state->check) { + strm->msg = (char *)"incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = (char *)"incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) + if (updatewindow(strm, out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) +z_streamp strm; +const Bytef *dictionary; +uInt dictLength; +{ + struct inflate_state FAR *state; + unsigned long id; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary id */ + if (state->mode == DICT) { + id = adler32(0L, Z_NULL, 0); + id = adler32(id, dictionary, dictLength); + if (id != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window */ + if (updatewindow(strm, strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + if (dictLength > state->wsize) { + zmemcpy(state->window, dictionary + dictLength - state->wsize, + state->wsize); + state->whave = state->wsize; + } + else { + zmemcpy(state->window + state->wsize - dictLength, dictionary, + dictLength); + state->whave = dictLength; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader(strm, head) +z_streamp strm; +gz_headerp head; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch(have, buf, len) +unsigned FAR *have; +unsigned char FAR *buf; +unsigned len; +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync(strm) +z_streamp strm; +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(dest, source) +z_streamp dest; +z_streamp source; +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy(copy, state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} diff --git a/neo/framework/zlib/inflate.h b/neo/framework/zlib/inflate.h new file mode 100644 index 00000000..07bd3e78 --- /dev/null +++ b/neo/framework/zlib/inflate.h @@ -0,0 +1,115 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN, /* i: waiting for length/lit code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD or MEM mode -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME + NAME -> COMMENT -> HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or CHECK + STORED -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; diff --git a/neo/framework/zlib/inftrees.c b/neo/framework/zlib/inftrees.c new file mode 100644 index 00000000..8a9c13ff --- /dev/null +++ b/neo/framework/zlib/inftrees.c @@ -0,0 +1,329 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.3 Copyright 1995-2005 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code this; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 201, 196}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)1; + this.val = (unsigned short)0; + *(*table)++ = this; /* make a table to force an error */ + *(*table)++ = this; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked when a LENS table is being made + against the space in *table, ENOUGH, minus the maximum space needed by + the worst case distance code, MAXD. This should never happen, but the + sufficiency of ENOUGH has not been proven exhaustively, hence the check. + This assumes that when type == LENS, bits == 9. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + this.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + this.op = (unsigned char)0; + this.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + this.op = (unsigned char)(extra[work[sym]]); + this.val = base[work[sym]]; + } + else { + this.op = (unsigned char)(32 + 64); /* end of block */ + this.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = this; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)(len - drop); + this.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + this.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = this; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/neo/framework/zlib/inftrees.h b/neo/framework/zlib/inftrees.h new file mode 100644 index 00000000..b1104c87 --- /dev/null +++ b/neo/framework/zlib/inftrees.h @@ -0,0 +1,55 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1444 code structures (852 for length/literals + and 592 for distances, the latter actually the result of an + exhaustive search). The true maximum is not known, but the value + below is more than safe. */ +#define ENOUGH 2048 +#define MAXD 592 + +/* Type of code to build for inftable() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); diff --git a/neo/framework/zlib/minigzip.c b/neo/framework/zlib/minigzip.c new file mode 100644 index 00000000..4524b96a --- /dev/null +++ b/neo/framework/zlib/minigzip.c @@ -0,0 +1,322 @@ +/* minigzip.c -- simulate gzip using the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * minigzip is a minimal implementation of the gzip utility. This is + * only an example of using zlib and isn't meant to replace the + * full-featured gzip. No attempt is made to deal with file systems + * limiting names to 14 or 8+3 characters, etc... Error checking is + * very limited. So use minigzip only for testing; use gzip for the + * real thing. On MSDOS, use only on file names without extension + * or in pipe mode. + */ + +/* @(#) $Id$ */ + +#include +#include "zlib.h" + +#ifdef STDC +# include +# include +#endif + +#ifdef USE_MMAP +# include +# include +# include +#endif + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#ifdef VMS +# define unlink delete +# define GZ_SUFFIX "-gz" +#endif +#ifdef RISCOS +# define unlink remove +# define GZ_SUFFIX "-gz" +# define fileno(file) file->__file +#endif +#if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fileno */ +#endif + +#ifndef WIN32 /* unlink already in stdio.h for WIN32 */ + extern int unlink OF((const char *)); +#endif + +#ifndef GZ_SUFFIX +# define GZ_SUFFIX ".gz" +#endif +#define SUFFIX_LEN (sizeof(GZ_SUFFIX)-1) + +#define BUFLEN 16384 +#define MAX_NAME_LEN 1024 + +#ifdef MAXSEG_64K +# define local static + /* Needed for systems with limitation on stack size. */ +#else +# define local +#endif + +char *prog; + +void error OF((const char *msg)); +void gz_compress OF((FILE *in, gzFile out)); +#ifdef USE_MMAP +int gz_compress_mmap OF((FILE *in, gzFile out)); +#endif +void gz_uncompress OF((gzFile in, FILE *out)); +void file_compress OF((char *file, char *mode)); +void file_uncompress OF((char *file)); +int main OF((int argc, char *argv[])); + +/* =========================================================================== + * Display error message and exit + */ +void error(msg) + const char *msg; +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + +/* =========================================================================== + * Compress input to output then close both files. + */ + +void gz_compress(in, out) + FILE *in; + gzFile out; +{ + local char buf[BUFLEN]; + int len; + int err; + +#ifdef USE_MMAP + /* Try first compressing with mmap. If mmap fails (minigzip used in a + * pipe), use the normal fread loop. + */ + if (gz_compress_mmap(in, out) == Z_OK) return; +#endif + for (;;) { + len = (int)fread(buf, 1, sizeof(buf), in); + if (ferror(in)) { + perror("fread"); + exit(1); + } + if (len == 0) break; + + if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err)); + } + fclose(in); + if (gzclose(out) != Z_OK) error("failed gzclose"); +} + +#ifdef USE_MMAP /* MMAP version, Miguel Albrecht */ + +/* Try compressing the input file at once using mmap. Return Z_OK if + * if success, Z_ERRNO otherwise. + */ +int gz_compress_mmap(in, out) + FILE *in; + gzFile out; +{ + int len; + int err; + int ifd = fileno(in); + caddr_t buf; /* mmap'ed buffer for the entire input file */ + off_t buf_len; /* length of the input file */ + struct stat sb; + + /* Determine the size of the file, needed for mmap: */ + if (fstat(ifd, &sb) < 0) return Z_ERRNO; + buf_len = sb.st_size; + if (buf_len <= 0) return Z_ERRNO; + + /* Now do the actual mmap: */ + buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0); + if (buf == (caddr_t)(-1)) return Z_ERRNO; + + /* Compress the whole file at once: */ + len = gzwrite(out, (char *)buf, (unsigned)buf_len); + + if (len != (int)buf_len) error(gzerror(out, &err)); + + munmap(buf, buf_len); + fclose(in); + if (gzclose(out) != Z_OK) error("failed gzclose"); + return Z_OK; +} +#endif /* USE_MMAP */ + +/* =========================================================================== + * Uncompress input to output then close both files. + */ +void gz_uncompress(in, out) + gzFile in; + FILE *out; +{ + local char buf[BUFLEN]; + int len; + int err; + + for (;;) { + len = gzread(in, buf, sizeof(buf)); + if (len < 0) error (gzerror(in, &err)); + if (len == 0) break; + + if ((int)fwrite(buf, 1, (unsigned)len, out) != len) { + error("failed fwrite"); + } + } + if (fclose(out)) error("failed fclose"); + + if (gzclose(in) != Z_OK) error("failed gzclose"); +} + + +/* =========================================================================== + * Compress the given file: create a corresponding .gz file and remove the + * original. + */ +void file_compress(file, mode) + char *file; + char *mode; +{ + local char outfile[MAX_NAME_LEN]; + FILE *in; + gzFile out; + + strcpy(outfile, file); + strcat(outfile, GZ_SUFFIX); + + in = fopen(file, "rb"); + if (in == NULL) { + perror(file); + exit(1); + } + out = gzopen(outfile, mode); + if (out == NULL) { + fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile); + exit(1); + } + gz_compress(in, out); + + unlink(file); +} + + +/* =========================================================================== + * Uncompress the given file and remove the original. + */ +void file_uncompress(file) + char *file; +{ + local char buf[MAX_NAME_LEN]; + char *infile, *outfile; + FILE *out; + gzFile in; + uInt len = (uInt)strlen(file); + + strcpy(buf, file); + + if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) { + infile = file; + outfile = buf; + outfile[len-3] = '\0'; + } else { + outfile = file; + infile = buf; + strcat(infile, GZ_SUFFIX); + } + in = gzopen(infile, "rb"); + if (in == NULL) { + fprintf(stderr, "%s: can't gzopen %s\n", prog, infile); + exit(1); + } + out = fopen(outfile, "wb"); + if (out == NULL) { + perror(file); + exit(1); + } + + gz_uncompress(in, out); + + unlink(infile); +} + + +/* =========================================================================== + * Usage: minigzip [-d] [-f] [-h] [-r] [-1 to -9] [files...] + * -d : decompress + * -f : compress with Z_FILTERED + * -h : compress with Z_HUFFMAN_ONLY + * -r : compress with Z_RLE + * -1 to -9 : compression level + */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + int uncompr = 0; + gzFile file; + char outmode[20]; + + strcpy(outmode, "wb6 "); + + prog = argv[0]; + argc--, argv++; + + while (argc > 0) { + if (strcmp(*argv, "-d") == 0) + uncompr = 1; + else if (strcmp(*argv, "-f") == 0) + outmode[3] = 'f'; + else if (strcmp(*argv, "-h") == 0) + outmode[3] = 'h'; + else if (strcmp(*argv, "-r") == 0) + outmode[3] = 'R'; + else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' && + (*argv)[2] == 0) + outmode[2] = (*argv)[1]; + else + break; + argc--, argv++; + } + if (outmode[3] == ' ') + outmode[3] = 0; + if (argc == 0) { + SET_BINARY_MODE(stdin); + SET_BINARY_MODE(stdout); + if (uncompr) { + file = gzdopen(fileno(stdin), "rb"); + if (file == NULL) error("can't gzdopen stdin"); + gz_uncompress(file, stdout); + } else { + file = gzdopen(fileno(stdout), outmode); + if (file == NULL) error("can't gzdopen stdout"); + gz_compress(stdin, file); + } + } else { + do { + if (uncompr) { + file_uncompress(*argv); + } else { + file_compress(*argv, outmode); + } + } while (argv++, --argc); + } + return 0; +} diff --git a/neo/framework/zlib/trees.c b/neo/framework/zlib/trees.c new file mode 100644 index 00000000..ce27b5cb --- /dev/null +++ b/neo/framework/zlib/trees.c @@ -0,0 +1,1219 @@ +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1995-2005 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in a compressed form which is itself + * a Huffman encoding of the lengths of all the code strings (in + * ascending order by source values). The actual code strings are + * reconstructed from the lengths in the inflate process, as described + * in the deflate specification. + * + * REFERENCES + * + * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". + * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + */ + +/* @(#) $Id$ */ + +/* #define GEN_TREES_H */ + +#include "deflate.h" + +#ifdef DEBUG +# include +#endif + +/* =========================================================================== + * Constants + */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */ + = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; + +local const int extra_dbits[D_CODES] /* extra bits for each distance code */ + = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */ + = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; + +local const uch bl_order[BL_CODES] + = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +#define Buf_size (8 * 2*sizeof(char)) +/* Number of bits used within bi_buf. (bi_buf might be implemented on + * more than 16 bits on some systems.) + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +#define DIST_CODE_LEN 512 /* see definition of array dist_code below */ + +#if defined(GEN_TREES_H) || !defined(STDC) +/* non ANSI compilers may not accept trees.h */ + +local ct_data static_ltree[L_CODES+2]; +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +local ct_data static_dtree[D_CODES]; +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +uch _dist_code[DIST_CODE_LEN]; +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +uch _length_code[MAX_MATCH-MIN_MATCH+1]; +/* length code for each normalized match length (0 == MIN_MATCH) */ + +local int base_length[LENGTH_CODES]; +/* First normalized length for each code (0 = MIN_MATCH) */ + +local int base_dist[D_CODES]; +/* First normalized distance for each code (0 = distance of 1) */ + +#else +# include "trees.h" +#endif /* GEN_TREES_H */ + +struct static_tree_desc_s { + const ct_data *static_tree; /* static tree or NULL */ + const intf *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ +}; + +local static_tree_desc static_l_desc = +{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; + +local static_tree_desc static_d_desc = +{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; + +local static_tree_desc static_bl_desc = +{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; + +/* =========================================================================== + * Local (static) routines in this file. + */ + +local void tr_static_init OF((void)); +local void init_block OF((deflate_state *s)); +local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +local void build_tree OF((deflate_state *s, tree_desc *desc)); +local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local int build_bl_tree OF((deflate_state *s)); +local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, + int blcodes)); +local void compress_block OF((deflate_state *s, ct_data *ltree, + ct_data *dtree)); +local void set_data_type OF((deflate_state *s)); +local unsigned bi_reverse OF((unsigned value, int length)); +local void bi_windup OF((deflate_state *s)); +local void bi_flush OF((deflate_state *s)); +local void copy_block OF((deflate_state *s, charf *buf, unsigned len, + int header)); + +#ifdef GEN_TREES_H +local void gen_trees_header OF((void)); +#endif + +#ifndef DEBUG +# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(s, c, tree) \ + { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \ + send_bits(s, tree[c].Code, tree[c].Len); } +#endif + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +#ifdef DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits(s, value, length) + deflate_state *s; + int value; /* value to send */ + int length; /* number of bits */ +{ + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (value << s->bi_valid); + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += length - Buf_size; + } else { + s->bi_buf |= value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = value;\ + s->bi_buf |= (val << s->bi_valid);\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> ((int)Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ +} +#endif /* DEBUG */ + + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Initialize the various 'constant' tables. + */ +local void tr_static_init() +{ +#if defined(GEN_TREES_H) || !defined(STDC) + static int static_init_done = 0; + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = (uch)code; + } + } + Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; + n = 0; + while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse((unsigned)n, 5); + } + static_init_done = 1; + +# ifdef GEN_TREES_H + gen_trees_header(); +# endif +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ +} + +/* =========================================================================== + * Genererate the file trees.h describing the static trees. + */ +#ifdef GEN_TREES_H +# ifndef DEBUG +# include +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, "const uch _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +void _tr_init(s) + deflate_state *s; +{ + tr_static_init(); + + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; + s->last_eob_len = 8; /* enough lookahead for inflate */ +#ifdef DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif + + /* Initialize the first block of the first file: */ + init_block(s); +} + +/* =========================================================================== + * Initialize a new block. + */ +local void init_block(s) + deflate_state *s; +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->last_lit = s->matches = 0; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(s, tree, top) \ +{\ + top = s->heap[SMALLEST]; \ + s->heap[SMALLEST] = s->heap[s->heap_len--]; \ + pqdownheap(s, tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m, depth) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +local void pqdownheap(s, tree, k) + deflate_state *s; + ct_data *tree; /* the tree to restore */ + int k; /* node to move down */ +{ + int v = s->heap[k]; + int j = k << 1; /* left son of k */ + while (j <= s->heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s->heap_len && + smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s->heap[j], s->depth)) break; + + /* Exchange v with the smallest son */ + s->heap[k] = s->heap[j]; k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s->heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +local void gen_bitlen(s, desc) + deflate_state *s; + tree_desc *desc; /* the tree descriptor */ +{ + ct_data *tree = desc->dyn_tree; + int max_code = desc->max_code; + const ct_data *stree = desc->stat_desc->static_tree; + const intf *extra = desc->stat_desc->extra_bits; + int base = desc->stat_desc->extra_base; + int max_length = desc->stat_desc->max_length; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ + + for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + n = s->heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) bits = max_length, overflow++; + tree[n].Len = (ush)bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s->bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n].Freq; + s->opt_len += (ulg)f * (bits + xbits); + if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits); + } + if (overflow == 0) return; + + Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s->bl_count[bits] == 0) bits--; + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = s->bl_count[bits]; + while (n != 0) { + m = s->heap[--h]; + if (m > max_code) continue; + if ((unsigned) tree[m].Len != (unsigned) bits) { + Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s->opt_len += ((long)bits - (long)tree[m].Len) + *(long)tree[m].Freq; + tree[m].Len = (ush)bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +local void gen_codes (tree, max_code, bl_count) + ct_data *tree; /* the tree to decorate */ + int max_code; /* largest code with non zero frequency */ + ushf *bl_count; /* number of codes at each bit length */ +{ + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits-1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; + const ct_data *stree = desc->stat_desc->static_tree; + int elems = desc->stat_desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s->heap_len = 0, s->heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + s->heap[++(s->heap_len)] = max_code = n; + s->depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s->heap_len < 2) { + node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0); + tree[node].Freq = 1; + s->depth[node] = 0; + s->opt_len--; if (stree) s->static_len -= stree[node].Len; + /* node is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + pqremove(s, tree, n); /* n = node of least frequency */ + m = s->heap[SMALLEST]; /* m = node of next least frequency */ + + s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */ + s->heap[--(s->heap_max)] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ? + s->depth[n] : s->depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + + } while (s->heap_len >= 2); + + s->heap[--(s->heap_max)] = s->heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, (tree_desc *)desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes ((ct_data *)tree, max_code, s->bl_count); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +local void scan_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) max_count = 138, min_count = 3; + tree[max_code+1].Len = (ush)0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + s->bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) s->bl_tree[curlen].Freq++; + s->bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + s->bl_tree[REPZ_3_10].Freq++; + } else { + s->bl_tree[REPZ_11_138].Freq++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +local void send_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen == 0) max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { send_code(s, curlen, s->bl_tree); } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(s, curlen, s->bl_tree); count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +local int build_bl_tree(s) + deflate_state *s; +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, (tree_desc *)(&(s->bl_desc))); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; + } + /* Update opt_len to include the bit length tree and counts */ + s->opt_len += 3*(max_blindex+1) + 5+5+4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +local void send_all_trees(s, lcodes, dcodes, blcodes) + deflate_state *s; + int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + int rank; /* index in bl_order */ + + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + +/* =========================================================================== + * Send a stored block + */ +void _tr_stored_block(s, buf, stored_len, eof) + deflate_state *s; + charf *buf; /* input block */ + ulg stored_len; /* length of input block */ + int eof; /* true if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK<<1)+eof, 3); /* send block type */ +#ifdef DEBUG + s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; + s->compressed_len += (stored_len + 4) << 3; +#endif + copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + * The current inflate code requires 9 bits of lookahead. If the + * last two codes for the previous block (real code plus EOB) were coded + * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + * the last real code. In this case we send two empty static blocks instead + * of one. (There are no problems if the previous block is stored or fixed.) + * To simplify the code, we assume the worst case of last real code encoded + * on one bit only. + */ +void _tr_align(s) + deflate_state *s; +{ + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif + bi_flush(s); + /* Of the 10 bits for the empty block, we have already sent + * (10 - bi_valid) bits. The lookahead for the last real code (before + * the EOB of the previous block) was thus at least one plus the length + * of the EOB plus what we have just sent of the empty static block. + */ + if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; +#endif + bi_flush(s); + } + s->last_eob_len = 7; +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +void _tr_flush_block(s, buf, stored_len, eof) + deflate_state *s; + charf *buf; /* input block, or NULL if too old */ + ulg stored_len; /* length of input block */ + int eof; /* true if this is the last block for a file */ +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s->level > 0) { + + /* Check if the file is binary or text */ + if (stored_len > 0 && s->strm->data_type == Z_UNKNOWN) + set_data_type(s); + + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; + + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + + } else { + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else + if (stored_len+4 <= opt_lenb && buf != (char*)0) { + /* 4: two words for the lengths */ +#endif + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, eof); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+eof, 3); + compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->static_len; +#endif + } else { + send_bits(s, (DYN_TREES<<1)+eof, 3); + send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, + max_blindex+1); + compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->opt_len; +#endif + } + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (eof) { + bi_windup(s); +#ifdef DEBUG + s->compressed_len += 7; /* align on byte boundary */ +#endif + } + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*eof)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int _tr_tally (s, dist, lc) + deflate_state *s; + unsigned dist; /* distance of matched string */ + unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block(s, ltree, dtree) + deflate_state *s; + ct_data *ltree; /* literal tree */ + ct_data *dtree; /* distance tree */ +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->last_lit != 0) do { + dist = s->d_buf[lx]; + lc = s->l_buf[lx++]; + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + "pendingBuf overflow"); + + } while (lx < s->last_lit); + + send_code(s, END_BLOCK, ltree); + s->last_eob_len = ltree[END_BLOCK].Len; +} + +/* =========================================================================== + * Set the data type to BINARY or TEXT, using a crude approximation: + * set it to Z_TEXT if all symbols are either printable characters (33 to 255) + * or white spaces (9 to 13, or 32); or set it to Z_BINARY otherwise. + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local void set_data_type(s) + deflate_state *s; +{ + int n; + + for (n = 0; n < 9; n++) + if (s->dyn_ltree[n].Freq != 0) + break; + if (n == 9) + for (n = 14; n < 32; n++) + if (s->dyn_ltree[n].Freq != 0) + break; + s->strm->data_type = (n == 32) ? Z_TEXT : Z_BINARY; +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse(code, len) + unsigned code; /* the value to invert */ + int len; /* its bit length */ +{ + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush(s) + deflate_state *s; +{ + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup(s) + deflate_state *s; +{ + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +local void copy_block(s, buf, len, header) + deflate_state *s; + charf *buf; /* the input data */ + unsigned len; /* its length */ + int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + s->last_eob_len = 8; /* enough lookahead for inflate */ + + if (header) { + put_short(s, (ush)len); + put_short(s, (ush)~len); +#ifdef DEBUG + s->bits_sent += 2*16; +#endif + } +#ifdef DEBUG + s->bits_sent += (ulg)len<<3; +#endif + while (len--) { + put_byte(s, *buf++); + } +} diff --git a/neo/framework/zlib/trees.h b/neo/framework/zlib/trees.h new file mode 100644 index 00000000..72facf90 --- /dev/null +++ b/neo/framework/zlib/trees.h @@ -0,0 +1,128 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; + diff --git a/neo/framework/zlib/uncompr.c b/neo/framework/zlib/uncompr.c new file mode 100644 index 00000000..b59e3d0d --- /dev/null +++ b/neo/framework/zlib/uncompr.c @@ -0,0 +1,61 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ +int ZEXPORT uncompress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} diff --git a/neo/framework/zlib/zconf.h b/neo/framework/zlib/zconf.h new file mode 100644 index 00000000..03a9431c --- /dev/null +++ b/neo/framework/zlib/zconf.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/neo/framework/zlib/zconf.in.h b/neo/framework/zlib/zconf.in.h new file mode 100644 index 00000000..03a9431c --- /dev/null +++ b/neo/framework/zlib/zconf.in.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/neo/framework/zlib/zlib.3 b/neo/framework/zlib/zlib.3 new file mode 100644 index 00000000..90b81628 --- /dev/null +++ b/neo/framework/zlib/zlib.3 @@ -0,0 +1,159 @@ +.TH ZLIB 3 "18 July 2005" +.SH NAME +zlib \- compression/decompression library +.SH SYNOPSIS +[see +.I zlib.h +for full description] +.SH DESCRIPTION +The +.I zlib +library is a general purpose data compression library. +The code is thread safe. +It provides in-memory compression and decompression functions, +including integrity checks of the uncompressed data. +This version of the library supports only one compression method (deflation) +but other algorithms will be added later +and will have the same stream interface. +.LP +Compression can be done in a single step if the buffers are large enough +(for example if an input file is mmap'ed), +or can be done by repeated calls of the compression function. +In the latter case, +the application must provide more input and/or consume the output +(providing more output space) before each call. +.LP +The library also supports reading and writing files in +.IR gzip (1) +(.gz) format +with an interface similar to that of stdio. +.LP +The library does not install any signal handler. +The decoder checks the consistency of the compressed data, +so the library should never crash even in case of corrupted input. +.LP +All functions of the compression library are documented in the file +.IR zlib.h . +The distribution source includes examples of use of the library +in the files +.I example.c +and +.IR minigzip.c . +.LP +Changes to this version are documented in the file +.I ChangeLog +that accompanies the source, +and are concerned primarily with bug fixes and portability enhancements. +.LP +A Java implementation of +.I zlib +is available in the Java Development Kit 1.1: +.IP +http://www.javasoft.com/products/JDK/1.1/docs/api/Package-java.util.zip.html +.LP +A Perl interface to +.IR zlib , +written by Paul Marquess (pmqs@cpan.org), +is available at CPAN (Comprehensive Perl Archive Network) sites, +including: +.IP +http://www.cpan.org/modules/by-module/Compress/ +.LP +A Python interface to +.IR zlib , +written by A.M. Kuchling (amk@magnet.com), +is available in Python 1.5 and later versions: +.IP +http://www.python.org/doc/lib/module-zlib.html +.LP +A +.I zlib +binding for +.IR tcl (1), +written by Andreas Kupries (a.kupries@westend.com), +is availlable at: +.IP +http://www.westend.com/~kupries/doc/trf/man/man.html +.LP +An experimental package to read and write files in .zip format, +written on top of +.I zlib +by Gilles Vollant (info@winimage.com), +is available at: +.IP +http://www.winimage.com/zLibDll/unzip.html +and also in the +.I contrib/minizip +directory of the main +.I zlib +web site. +.SH "SEE ALSO" +The +.I zlib +web site can be found at either of these locations: +.IP +http://www.zlib.org +.br +http://www.gzip.org/zlib/ +.LP +The data format used by the zlib library is described by RFC +(Request for Comments) 1950 to 1952 in the files: +.IP +http://www.ietf.org/rfc/rfc1950.txt (concerning zlib format) +.br +http://www.ietf.org/rfc/rfc1951.txt (concerning deflate format) +.br +http://www.ietf.org/rfc/rfc1952.txt (concerning gzip format) +.LP +These documents are also available in other formats from: +.IP +ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html +.LP +Mark Nelson (markn@ieee.org) wrote an article about +.I zlib +for the Jan. 1997 issue of Dr. Dobb's Journal; +a copy of the article is available at: +.IP +http://dogma.net/markn/articles/zlibtool/zlibtool.htm +.SH "REPORTING PROBLEMS" +Before reporting a problem, +please check the +.I zlib +web site to verify that you have the latest version of +.IR zlib ; +otherwise, +obtain the latest version and see if the problem still exists. +Please read the +.I zlib +FAQ at: +.IP +http://www.gzip.org/zlib/zlib_faq.html +.LP +before asking for help. +Send questions and/or comments to zlib@gzip.org, +or (for the Windows DLL version) to Gilles Vollant (info@winimage.com). +.SH AUTHORS +Version 1.2.3 +Copyright (C) 1995-2005 Jean-loup Gailly (jloup@gzip.org) +and Mark Adler (madler@alumni.caltech.edu). +.LP +This software is provided "as-is," +without any express or implied warranty. +In no event will the authors be held liable for any damages +arising from the use of this software. +See the distribution directory with respect to requirements +governing redistribution. +The deflate format used by +.I zlib +was defined by Phil Katz. +The deflate and +.I zlib +specifications were written by L. Peter Deutsch. +Thanks to all the people who reported problems and suggested various +improvements in +.IR zlib ; +who are too numerous to cite here. +.LP +UNIX manual page by R. P. C. Rodgers, +U.S. National Library of Medicine (rodgers@nlm.nih.gov). +.\" end of man page diff --git a/neo/framework/zlib/zlib.h b/neo/framework/zlib/zlib.h new file mode 100644 index 00000000..02281792 --- /dev/null +++ b/neo/framework/zlib/zlib.h @@ -0,0 +1,1357 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */ diff --git a/neo/framework/zlib/zutil.c b/neo/framework/zlib/zutil.c new file mode 100644 index 00000000..d55f5948 --- /dev/null +++ b/neo/framework/zlib/zutil.c @@ -0,0 +1,318 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +const char * const z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch (sizeof(uInt)) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch (sizeof(uLong)) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch (sizeof(voidpf)) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch (sizeof(z_off_t)) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#ifdef STDC +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +} + +#ifdef DEBUG + +# ifndef verbose +# define verbose 0 +# endif +int z_verbose = verbose; + +void z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(err) + int err; +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf zcalloc (opaque, items, size) + voidpf opaque; + unsigned items; + unsigned size; +{ + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void zcfree (opaque, ptr) + voidpf opaque; + voidpf ptr; +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ diff --git a/neo/framework/zlib/zutil.h b/neo/framework/zlib/zutil.h new file mode 100644 index 00000000..b7d5eff8 --- /dev/null +++ b/neo/framework/zlib/zutil.h @@ -0,0 +1,269 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#define ZLIB_INTERNAL +#include "zlib.h" + +#ifdef STDC +# ifndef _WIN32_WCE +# include +# endif +# include +# include +#endif +#ifdef NO_ERRNO_H +# ifdef _WIN32_WCE + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. We rename it to + * avoid conflict with other libraries that use the same workaround. + */ +# define errno z_errno +# endif + extern int errno; +#else +# ifndef _WIN32_WCE +# include +# endif +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# ifdef M_I86 + #include +# endif +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS + /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 + /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# define vsnprintf _vsnprintf +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +#endif +#ifdef VMS +# define NO_vsnprintf +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +# include + extern int z_verbose; + extern void z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* ZUTIL_H */ diff --git a/neo/game-d3xp.vcxproj b/neo/game-d3xp.vcxproj new file mode 100644 index 00000000..c3e66593 --- /dev/null +++ b/neo/game-d3xp.vcxproj @@ -0,0 +1,328 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Retail + Win32 + + + + Game-d3xp + {0BC6FCC9-C65E-4B1F-9A58-0B9399987C9F} + Game-d3xp + + + + + + + Win32Proj + + + + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + DynamicLibrary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + AllRules.ruleset + + + AllRules.ruleset + AllRules.ruleset + + + + + + + + + + + + + true + + + + + + + + + true + + + + + + + + + true + NDEBUG;%(PreprocessorDefinitions);ID_RETAIL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/neo/game-d3xp.vcxproj.filters b/neo/game-d3xp.vcxproj.filters new file mode 100644 index 00000000..22ead03b --- /dev/null +++ b/neo/game-d3xp.vcxproj.filters @@ -0,0 +1,492 @@ + + + + + {cbe3b616-1879-4177-ab0b-ef9dd6e1c344} + + + {f1a55916-cc4e-4c52-8097-90f53ec0e5ef} + + + {65cd4d56-59f1-4e72-af98-074176b18454} + + + {1cd6b287-0c8b-4445-b5a0-4e3068f8b3fe} + + + {2024018a-67e2-4afd-8499-38ccbe8fc1aa} + + + {1549e84e-5718-4759-a11d-416cc73ac087} + + + + + AI + + + AI + + + AI + + + AI + + + AI + + + AI + + + AI + + + AI + + + Animation + + + Animation + + + Animation + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Script + + + Script + + + Script + + + Script + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + Menus + + + + + AI + + + AI + + + AI + + + Animation + + + Animation + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Script + + + Script + + + Script + + + Script + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Menus + + + Menus + + + Menus + + + + + + + + \ No newline at end of file diff --git a/neo/game.vcxproj b/neo/game.vcxproj new file mode 100644 index 00000000..d0ded9df --- /dev/null +++ b/neo/game.vcxproj @@ -0,0 +1,284 @@ + + + + + Debug + Win32 + + + Debug + Xbox 360 + + + Release + Win32 + + + Release + Xbox 360 + + + + Game + {49BEC5C6-B964-417A-851E-808886B57430} + Game + SAK + SAK + SAK + Win32Proj + SAK + + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + DynamicLibrary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + + + + + + + + + + + + + + + + + + /dll /include:"XboxKrnlBuildNumber" %(AdditionalOptions) + _DllMainCRTStartup + + + CopyToHardDrive + + + + + + + + + + + + + + + /dll /include:"XboxKrnlBuildNumber" %(AdditionalOptions) + _DllMainCRTStartup + + + CopyToHardDrive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {49bec5c6-b964-417a-851e-808886b57400} + false + + + + + + \ No newline at end of file diff --git a/neo/game.vcxproj.filters b/neo/game.vcxproj.filters new file mode 100644 index 00000000..1e37b9b4 --- /dev/null +++ b/neo/game.vcxproj.filters @@ -0,0 +1,297 @@ + + + + + {108fbdc3-00d4-4eba-8472-d04fcb45c4a1} + + + {eef65ba0-7f1f-4501-9165-b8edf57f63ac} + + + {e082d659-8d8e-49f5-ba1c-c12c9bb124d6} + + + {5c609656-a903-420e-9f5a-95dc0091f307} + + + {bf06073d-3e0f-4e0a-9acf-43c6e35a9af7} + + + + + AI + + + AI + + + AI + + + AI + + + AI + + + AI + + + AI + + + AI + + + Animation + + + Animation + + + Animation + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Script + + + Script + + + Script + + + Script + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AI + + + AI + + + AI + + + Animation + + + Animation + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + GameSys + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Physics + + + Script + + + Script + + + Script + + + Script + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/neo/idlib.vcxproj b/neo/idlib.vcxproj new file mode 100644 index 00000000..6db19f81 --- /dev/null +++ b/neo/idlib.vcxproj @@ -0,0 +1,253 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Retail + Win32 + + + + idLib + {49BEC5C6-B964-417A-851E-808886B57400} + idLib + + + + + + + Win32Proj + + + + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + StaticLibrary + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + AllRules.ruleset + + + AllRules.ruleset + AllRules.ruleset + + + + + + + + true + + + + + true + + + + + true + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + + + + + + + + + + + + + + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/neo/idlib.vcxproj.filters b/neo/idlib.vcxproj.filters new file mode 100644 index 00000000..87684d9e --- /dev/null +++ b/neo/idlib.vcxproj.filters @@ -0,0 +1,397 @@ + + + + + {a85fe24a-a784-4008-b54e-5406119c0b8f} + + + {a7fe620c-8364-44be-abff-34f068c7d96f} + + + {3fa21a20-3848-4086-a2e6-816081637779} + + + {10198dd0-bb10-4111-bcd5-5565e4ff61f9} + + + {79062996-1084-4896-b49b-fc6280e388ce} + + + {767fafaf-96d2-4d2b-b4d8-479344d8537c} + + + {cd4f2be2-8eb4-4a80-a0ab-0ed8d4eda2db} + + + {8d100526-0f7e-40c1-bd91-08a8fffad028} + + + + + BV + + + BV + + + BV + + + Containers + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Hashing + + + Hashing + + + Hashing + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Text + + + Text + + + Text + + + Text + + + Text + + + Text + + + + + + + + + + + + Sys\Win32 + + + + Sys + + + Geometry + + + + + + Math + + + Math + + + + + BV + + + BV + + + BV + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Containers + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Geometry + + + Hashing + + + Hashing + + + Hashing + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Math + + + Text + + + Text + + + Text + + + Text + + + Text + + + Text + + + + + + + + + + + Sys + + + Sys + + + Sys + + + Text + + + + Sys + + + Containers + + + Sys + + + Sys + + + Geometry + + + Sys + + + + Containers + + + + Sys + + + + + Sys + + + + + Math + + + Math + + + Geometry + + + \ No newline at end of file diff --git a/neo/idlib/Base64.cpp b/neo/idlib/Base64.cpp new file mode 100644 index 00000000..3ae119bb --- /dev/null +++ b/neo/idlib/Base64.cpp @@ -0,0 +1,233 @@ + +#include "precompiled.h" +#pragma hdrstop + +/* +Copyright (c) 1996 Lars Wirzenius. All rights reserved. + +June 14 2003: TTimo + modified + endian bug fixes + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=197039 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +============ +idBase64::Encode +============ +*/ +static const char sixtet_to_base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void idBase64::Encode( const byte *from, int size ) { + int i, j; + unsigned long w; + byte *to; + + EnsureAlloced( 4*(size+3)/3 + 2 ); // ratio and padding + trailing \0 + to = data; + + w = 0; + i = 0; + while (size > 0) { + w |= *from << i*8; + ++from; + --size; + ++i; + if (size == 0 || i == 3) { + byte out[4]; + SixtetsForInt( out, w ); + for (j = 0; j*6 < i*8; ++j) { + *to++ = sixtet_to_base64[ out[j] ]; + } + if (size == 0) { + for (j = i; j < 3; ++j) { + *to++ = '='; + } + } + w = 0; + i = 0; + } + } + + *to++ = '\0'; + len = to - data; +} + +/* +============ +idBase64::DecodeLength +returns the minimum size in bytes of the target buffer for decoding +4 base64 digits <-> 3 bytes +============ +*/ +int idBase64::DecodeLength( void ) const { + return 3*len/4; +} + +/* +============ +idBase64::Decode +============ +*/ +int idBase64::Decode( byte *to ) const { + unsigned long w; + int i, j; + size_t n; + static char base64_to_sixtet[256]; + static int tab_init = 0; + byte *from = data; + + if (!tab_init) { + memset( base64_to_sixtet, 0, 256 ); + for (i = 0; (j = sixtet_to_base64[i]) != '\0'; ++i) { + base64_to_sixtet[j] = i; + } + tab_init = 1; + } + + w = 0; + i = 0; + n = 0; + byte in[4] = {0,0,0,0}; + while (*from != '\0' && *from != '=' ) { + if (*from == ' ' || *from == '\n') { + ++from; + continue; + } + in[i] = base64_to_sixtet[* (unsigned char *) from]; + ++i; + ++from; + if (*from == '\0' || *from == '=' || i == 4) { + w = IntForSixtets( in ); + for (j = 0; j*8 < i*6; ++j) { + *to++ = w & 0xff; + ++n; + w >>= 8; + } + i = 0; + w = 0; + } + } + return n; +} + +/* +============ +idBase64::Encode +============ +*/ +void idBase64::Encode( const idStr &src ) { + Encode( (const byte *)src.c_str(), src.Length() ); +} + +/* +============ +idBase64::Decode +============ +*/ +void idBase64::Decode( idStr &dest ) const { + byte *buf = new (TAG_IDLIB) byte[ DecodeLength()+1 ]; // +1 for trailing \0 + int out = Decode( buf ); + buf[out] = '\0'; + dest = (const char *)buf; + delete[] buf; +} + +/* +============ +idBase64::Decode +============ +*/ +void idBase64::Decode( idFile *dest ) const { + byte *buf = new (TAG_IDLIB) byte[ DecodeLength()+1 ]; // +1 for trailing \0 + int out = Decode( buf ); + dest->Write( buf, out ); + delete[] buf; +} + +#if 0 + +void idBase64_TestBase64() { + + idStr src; + idBase64 dest; + src = "Encode me in base64"; + dest.Encode( src ); + idLib::common->Printf( "%s -> %s\n", src.c_str(), dest.c_str() ); + dest.Decode( src ); + idLib::common->Printf( "%s -> %s\n", dest.c_str(), src.c_str() ); + + idDict src_dict; + src_dict.SetFloat("float", 0.5f); + src_dict.SetBool("bool", true); + src_dict.Set("value", "foo"); + idFile_Memory src_fmem("serialize_dict"); + src_dict.WriteToFileHandle( &src_fmem ); + dest.Encode( (const byte *)src_fmem.GetDataPtr(), src_fmem.Length() ); + idLib::common->Printf( "idDict encoded to %s\n", dest.c_str()); + + // now decode to another stream and build back + idFile_Memory dest_fmem( "build_back" ); + dest.Decode( &dest_fmem ); + dest_fmem.MakeReadOnly(); + idDict dest_dict; + dest_dict.ReadFromFileHandle( &dest_fmem ); + idLib::common->Printf( "idDict reconstructed after base64 decode\n"); + dest_dict.Print(); + + // test idDict read from file - from python generated files, see idDict.py + idFile *file = idLib::fileSystem->OpenFileRead("idDict.test"); + if (file) { + idDict test_dict; + test_dict.ReadFromFileHandle( file ); + // + idLib::common->Printf( "read idDict.test:\n"); + test_dict.Print(); + idLib::fileSystem->CloseFile(file); + file = NULL; + } else { + idLib::common->Printf( "idDict.test not found\n" ); + } + + idBase64 base64_src; + void *buffer; + if ( idLib::fileSystem->ReadFile( "idDict.base64.test", &buffer ) != -1 ) { + idFile_Memory mem_src( "dict" ); + idLib::common->Printf( "read: %d %s\n", idStr::Length( (char*)buffer ), buffer ); + base64_src = (char *)buffer; + base64_src.Decode( &mem_src ); + mem_src.MakeReadOnly(); + idDict test_dict; + test_dict.ReadFromFileHandle( &mem_src ); + idLib::common->Printf( "read idDict.base64.test:\n"); + test_dict.Print(); + idLib::fileSystem->FreeFile( buffer ); + } else { + idLib::common->Printf( "idDict.base64.test not found\n" ); + } +} + +#endif diff --git a/neo/idlib/Base64.h b/neo/idlib/Base64.h new file mode 100644 index 00000000..f3e47637 --- /dev/null +++ b/neo/idlib/Base64.h @@ -0,0 +1,111 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BASE64_H__ +#define __BASE64_H__ + +/* +=============================================================================== + + base64 + +=============================================================================== +*/ + +class idBase64 { +public: + idBase64(); + idBase64( const idStr &s ); + ~idBase64(); + + void Encode( const byte *from, int size ); + void Encode( const idStr &src ); + int DecodeLength() const; // minimum size in bytes of destination buffer for decoding + int Decode( byte *to ) const; // does not append a \0 - needs a DecodeLength() bytes buffer + void Decode( idStr &dest ) const; // decodes the binary content to an idStr (a bit dodgy, \0 and other non-ascii are possible in the decoded content) + void Decode( idFile *dest ) const; + + const char *c_str() const; + + void operator=( const idStr &s ); + +private: + byte * data; + int len; + int alloced; + + void Init(); + void Release(); + void EnsureAlloced( int size ); +}; + +ID_INLINE idBase64::idBase64() { + Init(); +} + +ID_INLINE idBase64::idBase64( const idStr &s ) { + Init(); + *this = s; +} + +ID_INLINE idBase64::~idBase64() { + Release(); +} + +ID_INLINE const char *idBase64::c_str() const { + return (const char *)data; +} + +ID_INLINE void idBase64::Init() { + len = 0; + alloced = 0; + data = NULL; +} + +ID_INLINE void idBase64::Release() { + if ( data ) { + delete[] data; + } + Init(); +} + +ID_INLINE void idBase64::EnsureAlloced( int size ) { + if ( size > alloced ) { + Release(); + } + data = new (TAG_IDLIB) byte[size]; + alloced = size; +} + +ID_INLINE void idBase64::operator=( const idStr &s ) { + EnsureAlloced( s.Length()+1 ); // trailing \0 - beware, this does a Release + strcpy( (char *)data, s.c_str() ); + len = s.Length(); +} + +#endif /* !__BASE64_H__ */ diff --git a/neo/idlib/BitMsg.cpp b/neo/idlib/BitMsg.cpp new file mode 100644 index 00000000..73328b7c --- /dev/null +++ b/neo/idlib/BitMsg.cpp @@ -0,0 +1,511 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "precompiled.h" + +/* +================================================================================================ + + idBitMsg + +================================================================================================ +*/ + +/* +======================== +idBitMsg::CheckOverflow +======================== +*/ +bool idBitMsg::CheckOverflow( int numBits ) { + if ( numBits > GetRemainingWriteBits() ) { + if ( !allowOverflow ) { + idLib::FatalError( "idBitMsg: overflow without allowOverflow set; maxsize=%i size=%i numBits=%i numRemainingWriteBits=%i", + GetMaxSize(), GetSize(), numBits, GetRemainingWriteBits() ); + } + if ( numBits > ( maxSize << 3 ) ) { + idLib::FatalError( "idBitMsg: %i bits is > full message size", numBits ); + } + idLib::Printf( "idBitMsg: overflow\n" ); + BeginWriting(); + overflowed = true; + return true; + } + return false; +} + +/* +======================== +idBitMsg::GetByteSpace +======================== +*/ +byte *idBitMsg::GetByteSpace( int length ) { + byte *ptr; + + if ( !writeData ) { + idLib::FatalError( "idBitMsg::GetByteSpace: cannot write to message" ); + } + + // round up to the next byte + WriteByteAlign(); + + // check for overflow + CheckOverflow( length << 3 ); + + ptr = writeData + curSize; + curSize += length; + return ptr; +} + +#define NBM( x ) (uint64)( ( 1LL << x ) - 1 ) +static uint64 maskForNumBits64[33] = { NBM( 0x00 ), NBM( 0x01 ), NBM( 0x02 ), NBM( 0x03 ), + NBM( 0x04 ), NBM( 0x05 ), NBM( 0x06 ), NBM( 0x07 ), + NBM( 0x08 ), NBM( 0x09 ), NBM( 0x0A ), NBM( 0x0B ), + NBM( 0x0C ), NBM( 0x0D ), NBM( 0x0E ), NBM( 0x0F ), + NBM( 0x10 ), NBM( 0x11 ), NBM( 0x12 ), NBM( 0x13 ), + NBM( 0x14 ), NBM( 0x15 ), NBM( 0x16 ), NBM( 0x17 ), + NBM( 0x18 ), NBM( 0x19 ), NBM( 0x1A ), NBM( 0x1B ), + NBM( 0x1C ), NBM( 0x1D ), NBM( 0x1E ), NBM( 0x1F ), 0xFFFFFFFF }; + +/* +======================== +idBitMsg::WriteBits + +If the number of bits is negative a sign is included. +======================== +*/ +void idBitMsg::WriteBits( int value, int numBits ) { + if ( !writeData ) { + idLib::FatalError( "idBitMsg::WriteBits: cannot write to message" ); + } + + // check if the number of bits is valid + if ( numBits == 0 || numBits < -31 || numBits > 32 ) { + idLib::FatalError( "idBitMsg::WriteBits: bad numBits %i", numBits ); + } + + // check for value overflows + if ( numBits != 32 ) { + if ( numBits > 0 ) { + if ( value > ( 1 << numBits ) - 1 ) { + idLib::FatalError( "idBitMsg::WriteBits: value overflow %d %d", + value, numBits ); + + } else if ( value < 0 ) { + idLib::FatalError( "idBitMsg::WriteBits: value overflow %d %d", + value, numBits ); + } + } else { + const unsigned shift = ( -1 - numBits ); + int r = 1 << shift; + if ( value > r - 1 ) { + idLib::FatalError( "idBitMsg::WriteBits: value overflow %d %d", + value, numBits ); + + } else if ( value < -r ) { + idLib::FatalError( "idBitMsg::WriteBits: value overflow %d %d", + value, numBits ); + } + } + } + + if ( numBits < 0 ) { + numBits = -numBits; + } + + // check for msg overflow + if ( CheckOverflow( numBits ) ) { + return; + } + + // Merge value with possible previous leftover + tempValue |= (((int64)value) & maskForNumBits64[numBits] ) << writeBit; + + writeBit += numBits; + + // Flush 8 bits (1 byte) at a time + while ( writeBit >= 8 ) { + writeData[curSize++] = tempValue & 255; + tempValue >>= 8; + writeBit -= 8; + } + + // Write leftover now, in case this is the last WriteBits call + if ( writeBit > 0 ) { + writeData[curSize] = tempValue & 255; + } +} + +/* +======================== +idBitMsg::WriteString +======================== +*/ +void idBitMsg::WriteString( const char * s, int maxLength, bool make7Bit ) { + if ( !s ) { + WriteData( "", 1 ); + } else { + int i, l; + byte *dataPtr; + const byte *bytePtr; + + l = idStr::Length( s ); + if ( maxLength >= 0 && l >= maxLength ) { + l = maxLength - 1; + } + dataPtr = GetByteSpace( l + 1 ); + bytePtr = reinterpret_cast< const byte * >( s ); + if ( make7Bit ) { + for ( i = 0; i < l; i++ ) { + if ( bytePtr[i] > 127 ) { + dataPtr[i] = '.'; + } else { + dataPtr[i] = bytePtr[i]; + } + } + } else { + for ( i = 0; i < l; i++ ) { + dataPtr[i] = bytePtr[i]; + } + } + dataPtr[i] = '\0'; + } +} + +/* +======================== +idBitMsg::WriteData +======================== +*/ +void idBitMsg::WriteData( const void *data, int length ) { + memcpy( GetByteSpace( length ), data, length ); +} + +/* +======================== +idBitMsg::WriteNetadr +======================== +*/ +void idBitMsg::WriteNetadr( const netadr_t adr ) { + WriteData( adr.ip, 4 ); + WriteUShort( adr.port ); + WriteByte( adr.type ); +} + +/* +======================== +idBitMsg::WriteDeltaDict +======================== +*/ +bool idBitMsg::WriteDeltaDict( const idDict &dict, const idDict *base ) { + int i; + const idKeyValue *kv, *basekv; + bool changed = false; + + if ( base != NULL ) { + + for ( i = 0; i < dict.GetNumKeyVals(); i++ ) { + kv = dict.GetKeyVal( i ); + basekv = base->FindKey( kv->GetKey() ); + if ( basekv == NULL || basekv->GetValue().Icmp( kv->GetValue() ) != 0 ) { + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + changed = true; + } + } + + WriteString( "" ); + + for ( i = 0; i < base->GetNumKeyVals(); i++ ) { + basekv = base->GetKeyVal( i ); + kv = dict.FindKey( basekv->GetKey() ); + if ( kv == NULL ) { + WriteString( basekv->GetKey() ); + changed = true; + } + } + + WriteString( "" ); + + } else { + + for ( i = 0; i < dict.GetNumKeyVals(); i++ ) { + kv = dict.GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + changed = true; + } + WriteString( "" ); + + WriteString( "" ); + + } + + return changed; +} + +/* +======================== +idBitMsg::ReadBits + +If the number of bits is negative a sign is included. +======================== +*/ +int idBitMsg::ReadBits( int numBits ) const { + int value; + int valueBits; + int get; + int fraction; + bool sgn; + + if ( !readData ) { + idLib::FatalError( "idBitMsg::ReadBits: cannot read from message" ); + } + + // check if the number of bits is valid + if ( numBits == 0 || numBits < -31 || numBits > 32 ) { + idLib::FatalError( "idBitMsg::ReadBits: bad numBits %i", numBits ); + } + + value = 0; + valueBits = 0; + + if ( numBits < 0 ) { + numBits = -numBits; + sgn = true; + } else { + sgn = false; + } + + // check for overflow + if ( numBits > GetRemainingReadBits() ) { + return -1; + } + + while ( valueBits < numBits ) { + if ( readBit == 0 ) { + readCount++; + } + get = 8 - readBit; + if ( get > (numBits - valueBits) ) { + get = (numBits - valueBits); + } + fraction = readData[readCount - 1]; + fraction >>= readBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + + valueBits += get; + readBit = ( readBit + get ) & 7; + } + + if ( sgn ) { + if ( value & ( 1 << ( numBits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << numBits ) - 1 ); + } + } + + return value; +} + +/* +======================== +idBitMsg::ReadString +======================== +*/ +int idBitMsg::ReadString( char * buffer, int bufferSize ) const { + int l, c; + + ReadByteAlign(); + l = 0; + while( 1 ) { + c = ReadByte(); + if ( c <= 0 || c >= 255 ) { + break; + } + // translate all fmt spec to avoid crash bugs in string routines + if ( c == '%' ) { + c = '.'; + } + + // we will read past any excessively long string, so + // the following data can be read, but the string will + // be truncated + if ( l < bufferSize - 1 ) { + buffer[l] = c; + l++; + } + } + + buffer[l] = 0; + return l; +} + +/* +======================== +idBitMsg::ReadString +======================== +*/ +int idBitMsg::ReadString( idStr & str ) const { + ReadByteAlign(); + + int cnt = 0; + for ( int i = readCount; i < curSize; i++ ) { + if ( readData[i] == 0 ) { + break; + } + cnt++; + } + + str.Clear(); + str.Append( (const char *)readData + readCount, cnt ); + readCount += cnt + 1; + + return str.Length(); +} + +/* +======================== +idBitMsg::ReadData +======================== +*/ +int idBitMsg::ReadData( void *data, int length ) const { + int cnt; + + ReadByteAlign(); + cnt = readCount; + + if ( readCount + length > curSize ) { + if ( data ) { + memcpy( data, readData + readCount, GetRemainingData() ); + } + readCount = curSize; + } else { + if ( data ) { + memcpy( data, readData + readCount, length ); + } + readCount += length; + } + + return ( readCount - cnt ); +} + +/* +======================== +idBitMsg::ReadNetadr +======================== +*/ +void idBitMsg::ReadNetadr( netadr_t *adr ) const { + ReadData( adr->ip, 4 ); + adr->port = ReadUShort(); + adr->type = ( netadrtype_t ) ReadByte(); +} + +/* +======================== +idBitMsg::ReadDeltaDict +======================== +*/ +bool idBitMsg::ReadDeltaDict( idDict &dict, const idDict *base ) const { + char key[MAX_STRING_CHARS]; + char value[MAX_STRING_CHARS]; + bool changed = false; + + if ( base != NULL ) { + dict = *base; + } else { + dict.Clear(); + } + + while( ReadString( key, sizeof( key ) ) != 0 ) { + ReadString( value, sizeof( value ) ); + dict.Set( key, value ); + changed = true; + } + + while( ReadString( key, sizeof( key ) ) != 0 ) { + dict.Delete( key ); + changed = true; + } + + return changed; +} + +/* +======================== +idBitMsg::DirToBits +======================== +*/ +int idBitMsg::DirToBits( const idVec3 &dir, int numBits ) { + int max, bits; + float bias; + + assert( numBits >= 6 && numBits <= 32 ); + assert( dir.LengthSqr() - 1.0f < 0.01f ); + + numBits /= 3; + max = ( 1 << ( numBits - 1 ) ) - 1; + bias = 0.5f / max; + + bits = IEEE_FLT_SIGNBITSET( dir.x ) << ( numBits * 3 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.x ) + bias ) * max ) ) << ( numBits * 2 ); + bits |= IEEE_FLT_SIGNBITSET( dir.y ) << ( numBits * 2 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.y ) + bias ) * max ) ) << ( numBits * 1 ); + bits |= IEEE_FLT_SIGNBITSET( dir.z ) << ( numBits * 1 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.z ) + bias ) * max ) ) << ( numBits * 0 ); + return bits; +} + +/* +======================== +idBitMsg::BitsToDir +======================== +*/ +idVec3 idBitMsg::BitsToDir( int bits, int numBits ) { + static float sign[2] = { 1.0f, -1.0f }; + int max; + float invMax; + idVec3 dir; + + assert( numBits >= 6 && numBits <= 32 ); + + numBits /= 3; + max = ( 1 << ( numBits - 1 ) ) - 1; + invMax = 1.0f / max; + + dir.x = sign[( bits >> ( numBits * 3 - 1 ) ) & 1] * ( ( bits >> ( numBits * 2 ) ) & max ) + * invMax; + + dir.y = sign[( bits >> ( numBits * 2 - 1 ) ) & 1] * ( ( bits >> ( numBits * 1 ) ) & max ) + * invMax; + + dir.z = sign[( bits >> ( numBits * 1 - 1 ) ) & 1] * ( ( bits >> ( numBits * 0 ) ) & max ) + * invMax; + + dir.NormalizeFast(); + return dir; +} diff --git a/neo/idlib/BitMsg.h b/neo/idlib/BitMsg.h new file mode 100644 index 00000000..932892f7 --- /dev/null +++ b/neo/idlib/BitMsg.h @@ -0,0 +1,958 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __BITMSG_H__ +#define __BITMSG_H__ + +/* +================================================ +idBitMsg operates on a sequence of individual bits. It handles byte ordering and +avoids alignment errors. It allows concurrent writing and reading. The data set with Init +is never free-d. +================================================ +*/ +class idBitMsg { +public: + idBitMsg() { InitWrite( NULL, 0 ); } + idBitMsg( byte * data, int length ) { InitWrite( data, length ); } + idBitMsg( const byte * data, int length ) { InitRead( data, length ); } + + // both read & write + void InitWrite( byte *data, int length ); + + // read only + void InitRead( const byte *data, int length ); + + // get data for writing + byte * GetWriteData(); + + // get data for reading + const byte * GetReadData() const; + + // get the maximum message size + int GetMaxSize() const; + + // generate error if not set and message is overflowed + void SetAllowOverflow( bool set ); + + // returns true if the message was overflowed + bool IsOverflowed() const; + + // size of the message in bytes + int GetSize() const; + + // set the message size + void SetSize( int size ); + + // get current write bit + int GetWriteBit() const; + + // set current write bit + void SetWriteBit( int bit ); + + // returns number of bits written + int GetNumBitsWritten() const; + + // space left in bytes for writing + int GetRemainingSpace() const; + + // space left in bits for writing + int GetRemainingWriteBits() const; + + //------------------------ + // Write State + //------------------------ + + // save the write state + void SaveWriteState( int &s, int &b, uint64 &t ) const; + + // restore the write state + void RestoreWriteState( int s, int b, uint64 t ); + + //------------------------ + // Reading + //------------------------ + + // bytes read so far + int GetReadCount() const; + + // set the number of bytes and bits read + void SetReadCount( int bytes ); + + // get current read bit + int GetReadBit() const; + + // set current read bit + void SetReadBit( int bit ); + + // returns number of bits read + int GetNumBitsRead() const; + + // number of bytes left to read + int GetRemainingData() const; + + // number of bits left to read + int GetRemainingReadBits() const; + + // save the read state + void SaveReadState( int &c, int &b ) const; + + // restore the read state + void RestoreReadState( int c, int b ); + + //------------------------ + // Writing + //------------------------ + + // begin writing + void BeginWriting(); + + // write up to the next byte boundary + void WriteByteAlign(); + + // write the specified number of bits + void WriteBits( int value, int numBits ); + + void WriteBool( bool c ); + void WriteChar( int8 c ); + void WriteByte( uint8 c ); + void WriteShort( int16 c ); + void WriteUShort( uint16 c ); + void WriteLong( int32 c ); + void WriteLongLong( int64 c ); + void WriteFloat( float f ); + void WriteFloat( float f, int exponentBits, int mantissaBits ); + void WriteAngle8( float f ); + void WriteAngle16( float f ); + void WriteDir( const idVec3 &dir, int numBits ); + void WriteString( const char *s, int maxLength = -1, bool make7Bit = true ); + void WriteData( const void *data, int length ); + void WriteNetadr( const netadr_t adr ); + + void WriteUNorm8( float f ) { WriteByte( idMath::Ftob( f * 255.0f ) ); } + void WriteUNorm16( float f ) { WriteUShort( idMath::Ftoi( f * 65535.0f ) ); } + void WriteNorm16( float f ) { WriteShort( idMath::Ftoi( f * 32767.0f ) ); } + + void WriteDeltaChar( int8 oldValue, int8 newValue ) { WriteByte( newValue - oldValue ); } + void WriteDeltaByte( uint8 oldValue, uint8 newValue ) { WriteByte( newValue - oldValue ); } + void WriteDeltaShort( int16 oldValue, int16 newValue ) { WriteUShort( newValue - oldValue ); } + void WriteDeltaUShort( uint16 oldValue, uint16 newValue ) { WriteUShort( newValue - oldValue ); } + void WriteDeltaLong( int32 oldValue, int32 newValue ) { WriteLong( newValue - oldValue ); } + void WriteDeltaFloat( float oldValue, float newValue ) { WriteFloat( newValue - oldValue ); } + void WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ) { WriteFloat( newValue - oldValue, exponentBits, mantissaBits ); } + + bool WriteDeltaDict( const idDict &dict, const idDict *base ); + + template< int _max_, int _numBits_ > + void WriteQuantizedFloat( float value ); + template< int _max_, int _numBits_ > + void WriteQuantizedUFloat( float value ); // Quantize a float to a variable number of bits (assumes unsigned, uses simple quantization) + + template< typename T > + void WriteVectorFloat( const T & v ) { for ( int i = 0; i < v.GetDimension(); i++ ) { WriteFloat( v[i] ); } } + template< typename T > + void WriteVectorUNorm8( const T & v ) { for ( int i = 0; i < v.GetDimension(); i++ ) { WriteUNorm8( v[i] ); } } + template< typename T > + void WriteVectorUNorm16( const T & v ) { for ( int i = 0; i < v.GetDimension(); i++ ) { WriteUNorm16( v[i] ); } } + template< typename T > + void WriteVectorNorm16( const T & v ) { for ( int i = 0; i < v.GetDimension(); i++ ) { WriteNorm16( v[i] ); } } + + // Compress a vector to a variable number of bits (assumes signed, uses simple quantization) + template< typename T, int _max_, int _numBits_ > + void WriteQuantizedVector( const T & v ) { for ( int i = 0; i < v.GetDimension(); i++ ) { WriteQuantizedFloat< _max_, _numBits_ >( v[i] ); } } + + // begin reading. + void BeginReading() const; + + // read up to the next byte boundary + void ReadByteAlign() const; + + // read the specified number of bits + int ReadBits( int numBits ) const; + + bool ReadBool() const; + int ReadChar() const; + int ReadByte() const; + int ReadShort() const; + int ReadUShort() const; + int ReadLong() const; + int64 ReadLongLong() const; + float ReadFloat() const; + float ReadFloat( int exponentBits, int mantissaBits ) const; + float ReadAngle8() const; + float ReadAngle16() const; + idVec3 ReadDir( int numBits ) const; + int ReadString( char *buffer, int bufferSize ) const; + int ReadString( idStr & str ) const; + int ReadData( void *data, int length ) const; + void ReadNetadr( netadr_t *adr ) const; + + float ReadUNorm8() const { return ReadByte() / 255.0f; } + float ReadUNorm16() const { return ReadUShort() / 65535.0f; } + float ReadNorm16() const { return ReadShort() / 32767.0f; } + + int8 ReadDeltaChar( int8 oldValue ) const { return oldValue + ReadByte(); } + uint8 ReadDeltaByte( uint8 oldValue ) const { return oldValue + ReadByte(); } + int16 ReadDeltaShort( int16 oldValue ) const { return oldValue + ReadUShort(); } + uint16 ReadDeltaUShort( uint16 oldValue ) const { return oldValue + ReadUShort(); } + int32 ReadDeltaLong( int32 oldValue ) const { return oldValue + ReadLong(); } + float ReadDeltaFloat( float oldValue ) const { return oldValue + ReadFloat(); } + float ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const { return oldValue + ReadFloat( exponentBits, mantissaBits ); } + bool ReadDeltaDict( idDict &dict, const idDict *base ) const; + + template< int _max_, int _numBits_ > + float ReadQuantizedFloat() const; + template< int _max_, int _numBits_ > + float ReadQuantizedUFloat() const; + + template< typename T > + void ReadVectorFloat( T & v ) const { for ( int i = 0; i < v.GetDimension(); i++ ) { v[i] = ReadFloat(); } } + template< typename T > + void ReadVectorUNorm8( T & v ) const { for ( int i = 0; i < v.GetDimension(); i++ ) { v[i] = ReadUNorm8(); } } + template< typename T > + void ReadVectorUNorm16( T & v ) const { for ( int i = 0; i < v.GetDimension(); i++ ) { v[i] = ReadUNorm16(); } } + template< typename T > + void ReadVectorNorm16( T & v ) const { for ( int i = 0; i < v.GetDimension(); i++ ) { v[i] = ReadNorm16(); } } + template< typename T, int _max_, int _numBits_ > + void ReadQuantizedVector( T & v ) const { for ( int i = 0; i < v.GetDimension(); i++ ) { v[i] = ReadQuantizedFloat< _max_, _numBits_ >(); } } + + static int DirToBits( const idVec3 &dir, int numBits ); + static idVec3 BitsToDir( int bits, int numBits ); + + void SetHasChanged( bool b ) { hasChanged = b; } + bool HasChanged() const { return hasChanged; } + +private: + byte * writeData; // pointer to data for writing + const byte * readData; // pointer to data for reading + int maxSize; // maximum size of message in bytes + int curSize; // current size of message in bytes + mutable int writeBit; // number of bits written to the last written byte + mutable int readCount; // number of bytes read so far + mutable int readBit; // number of bits read from the last read byte + bool allowOverflow; // if false, generate error when the message is overflowed + bool overflowed; // set true if buffer size failed (with allowOverflow set) + bool hasChanged; // Hack + + mutable uint64 tempValue; + +private: + bool CheckOverflow( int numBits ); + byte * GetByteSpace( int length ); +}; + +/* +======================== +idBitMsg::InitWrite +======================== +*/ +ID_INLINE void idBitMsg::InitWrite( byte *data, int length ) { + writeData = data; + readData = data; + maxSize = length; + curSize = 0; + + writeBit = 0; + readCount = 0; + readBit = 0; + allowOverflow = false; + overflowed = false; + + tempValue = 0; +} + +/* +======================== +idBitMsg::InitRead +======================== +*/ +ID_INLINE void idBitMsg::InitRead( const byte *data, int length ) { + writeData = NULL; + readData = data; + maxSize = length; + curSize = length; + + writeBit = 0; + readCount = 0; + readBit = 0; + allowOverflow = false; + overflowed = false; + + tempValue = 0; +} + +/* +======================== +idBitMsg::GetWriteData +======================== +*/ +ID_INLINE byte *idBitMsg::GetWriteData() { + return writeData; +} + +/* +======================== +idBitMsg::GetReadData +======================== +*/ +ID_INLINE const byte *idBitMsg::GetReadData() const { + return readData; +} + +/* +======================== +idBitMsg::GetMaxSize +======================== +*/ +ID_INLINE int idBitMsg::GetMaxSize() const { + return maxSize; +} + +/* +======================== +idBitMsg::SetAllowOverflow +======================== +*/ +ID_INLINE void idBitMsg::SetAllowOverflow( bool set ) { + allowOverflow = set; +} + +/* +======================== +idBitMsg::IsOverflowed +======================== +*/ +ID_INLINE bool idBitMsg::IsOverflowed() const { + return overflowed; +} + +/* +======================== +idBitMsg::GetSize +======================== +*/ +ID_INLINE int idBitMsg::GetSize() const { + return curSize + ( writeBit != 0 ); +} + +/* +======================== +idBitMsg::SetSize +======================== +*/ +ID_INLINE void idBitMsg::SetSize( int size ) { + assert( writeBit == 0 ); + + if ( size > maxSize ) { + curSize = maxSize; + } else { + curSize = size; + } +} + +/* +======================== +idBitMsg::GetWriteBit +======================== +*/ +ID_INLINE int idBitMsg::GetWriteBit() const { + return writeBit; +} + +/* +======================== +idBitMsg::SetWriteBit +======================== +*/ +ID_INLINE void idBitMsg::SetWriteBit( int bit ) { + // see idBitMsg::WriteByteAlign + assert( false ); + writeBit = bit & 7; + if ( writeBit ) { + writeData[curSize - 1] &= ( 1 << writeBit ) - 1; + } +} + +/* +======================== +idBitMsg::GetNumBitsWritten +======================== +*/ +ID_INLINE int idBitMsg::GetNumBitsWritten() const { + return ( curSize << 3 ) + writeBit; +} + +/* +======================== +idBitMsg::GetRemainingSpace +======================== +*/ +ID_INLINE int idBitMsg::GetRemainingSpace() const { + return maxSize - GetSize(); +} + +/* +======================== +idBitMsg::GetRemainingWriteBits +======================== +*/ +ID_INLINE int idBitMsg::GetRemainingWriteBits() const { + return ( maxSize << 3 ) - GetNumBitsWritten(); +} + +/* +======================== +idBitMsg::SaveWriteState +======================== +*/ +ID_INLINE void idBitMsg::SaveWriteState( int &s, int &b, uint64 &t ) const { + s = curSize; + b = writeBit; + t = tempValue; +} + +/* +======================== +idBitMsg::RestoreWriteState +======================== +*/ +ID_INLINE void idBitMsg::RestoreWriteState( int s, int b, uint64 t ) { + curSize = s; + writeBit = b & 7; + if ( writeBit ) { + writeData[curSize] &= ( 1 << writeBit ) - 1; + } + tempValue = t; +} + +/* +======================== +idBitMsg::GetReadCount +======================== +*/ +ID_INLINE int idBitMsg::GetReadCount() const { + return readCount; +} + +/* +======================== +idBitMsg::SetReadCount +======================== +*/ +ID_INLINE void idBitMsg::SetReadCount( int bytes ) { + readCount = bytes; +} + +/* +======================== +idBitMsg::GetReadBit +======================== +*/ +ID_INLINE int idBitMsg::GetReadBit() const { + return readBit; +} + +/* +======================== +idBitMsg::SetReadBit +======================== +*/ +ID_INLINE void idBitMsg::SetReadBit( int bit ) { + readBit = bit & 7; +} + +/* +======================== +idBitMsg::GetNumBitsRead +======================== +*/ +ID_INLINE int idBitMsg::GetNumBitsRead() const { + return ( ( readCount << 3 ) - ( ( 8 - readBit ) & 7 ) ); +} + +/* +======================== +idBitMsg::GetRemainingData +======================== +*/ +ID_INLINE int idBitMsg::GetRemainingData() const { + assert( writeBit == 0 ); + return curSize - readCount; +} + +/* +======================== +idBitMsg::GetRemainingReadBits +======================== +*/ +ID_INLINE int idBitMsg::GetRemainingReadBits() const { + assert( writeBit == 0 ); + return ( curSize << 3 ) - GetNumBitsRead(); +} + +/* +======================== +idBitMsg::SaveReadState +======================== +*/ +ID_INLINE void idBitMsg::SaveReadState( int &c, int &b ) const { + assert( writeBit == 0 ); + c = readCount; + b = readBit; +} + +/* +======================== +idBitMsg::RestoreReadState +======================== +*/ +ID_INLINE void idBitMsg::RestoreReadState( int c, int b ) { + assert( writeBit == 0 ); + readCount = c; + readBit = b & 7; +} + +/* +======================== +idBitMsg::BeginWriting +======================== +*/ +ID_INLINE void idBitMsg::BeginWriting() { + curSize = 0; + overflowed = false; + writeBit = 0; + tempValue = 0; +} + +/* +======================== +idBitMsg::WriteByteAlign +======================== +*/ +ID_INLINE void idBitMsg::WriteByteAlign() { + // it is important that no uninitialized data slips in the msg stream, + // because we use memcmp to decide if entities have changed and wether we should transmit them + // this function has the potential to leave uninitialized bits into the stream, + // however idBitMsg::WriteBits is properly initializing the byte to 0 so hopefully we are still safe + // adding this extra check just in case + curSize += writeBit != 0; + assert( writeBit == 0 || ( ( writeData[curSize - 1] >> writeBit ) == 0 ) ); // had to early out writeBit == 0 because when writeBit == 0 writeData[curSize - 1] may be the previous byte written and trigger false positives + writeBit = 0; + tempValue = 0; +} + +/* +======================== +idBitMsg::WriteBool +======================== +*/ +ID_INLINE void idBitMsg::WriteBool( bool c ) { + WriteBits( c, 1 ); +} + +/* +======================== +idBitMsg::WriteChar +======================== +*/ +ID_INLINE void idBitMsg::WriteChar( int8 c ) { + WriteBits( c, -8 ); +} + +/* +======================== +idBitMsg::WriteByte +======================== +*/ +ID_INLINE void idBitMsg::WriteByte( uint8 c ) { + WriteBits( c, 8 ); +} + +/* +======================== +idBitMsg::WriteShort +======================== +*/ +ID_INLINE void idBitMsg::WriteShort( int16 c ) { + WriteBits( c, -16 ); +} + +/* +======================== +idBitMsg::WriteUShort +======================== +*/ +ID_INLINE void idBitMsg::WriteUShort( uint16 c ) { + WriteBits( c, 16 ); +} + +/* +======================== +idBitMsg::WriteLong +======================== +*/ +ID_INLINE void idBitMsg::WriteLong( int32 c ) { + WriteBits( c, 32 ); +} + +/* +======================== +idBitMsg::WriteLongLong +======================== +*/ +ID_INLINE void idBitMsg::WriteLongLong( int64 c ) { + int a = c; + int b = c >> 32; + WriteBits( a, 32 ); + WriteBits( b, 32 ); +} + +/* +======================== +idBitMsg::WriteFloat +======================== +*/ +ID_INLINE void idBitMsg::WriteFloat( float f ) { + WriteBits( *reinterpret_cast(&f), 32 ); +} + +/* +======================== +idBitMsg::WriteFloat +======================== +*/ +ID_INLINE void idBitMsg::WriteFloat( float f, int exponentBits, int mantissaBits ) { + int bits = idMath::FloatToBits( f, exponentBits, mantissaBits ); + WriteBits( bits, 1 + exponentBits + mantissaBits ); +} + +/* +======================== +idBitMsg::WriteAngle8 +======================== +*/ +ID_INLINE void idBitMsg::WriteAngle8( float f ) { + WriteByte( ANGLE2BYTE( f ) ); +} + +/* +======================== +idBitMsg::WriteAngle16 +======================== +*/ +ID_INLINE void idBitMsg::WriteAngle16( float f ) { + WriteShort( ANGLE2SHORT(f) ); +} + +/* +======================== +idBitMsg::WriteDir +======================== +*/ +ID_INLINE void idBitMsg::WriteDir( const idVec3 &dir, int numBits ) { + WriteBits( DirToBits( dir, numBits ), numBits ); +} + +/* +======================== +idBitMsg::BeginReading +======================== +*/ +ID_INLINE void idBitMsg::BeginReading() const { + readCount = 0; + readBit = 0; + + writeBit = 0; + tempValue = 0; +} + +/* +======================== +idBitMsg::ReadByteAlign +======================== +*/ +ID_INLINE void idBitMsg::ReadByteAlign() const { + readBit = 0; +} + +/* +======================== +idBitMsg::ReadBool +======================== +*/ +ID_INLINE bool idBitMsg::ReadBool() const { + return ( ReadBits( 1 ) == 1 ) ? true : false; +} + +/* +======================== +idBitMsg::ReadChar +======================== +*/ +ID_INLINE int idBitMsg::ReadChar() const { + return (signed char)ReadBits( -8 ); +} + +/* +======================== +idBitMsg::ReadByte +======================== +*/ +ID_INLINE int idBitMsg::ReadByte() const { + return (unsigned char)ReadBits( 8 ); +} + +/* +======================== +idBitMsg::ReadShort +======================== +*/ +ID_INLINE int idBitMsg::ReadShort() const { + return (short)ReadBits( -16 ); +} + +/* +======================== +idBitMsg::ReadUShort +======================== +*/ +ID_INLINE int idBitMsg::ReadUShort() const { + return (unsigned short)ReadBits( 16 ); +} + +/* +======================== +idBitMsg::ReadLong +======================== +*/ +ID_INLINE int idBitMsg::ReadLong() const { + return ReadBits( 32 ); +} + +/* +======================== +idBitMsg::ReadLongLong +======================== +*/ +ID_INLINE int64 idBitMsg::ReadLongLong() const { + int64 a = ReadBits( 32 ); + int64 b = ReadBits( 32 ); + int64 c = ( 0x00000000ffffffff & a ) | ( b << 32 ); + return c; +} + +/* +======================== +idBitMsg::ReadFloat +======================== +*/ +ID_INLINE float idBitMsg::ReadFloat() const { + float value; + *reinterpret_cast(&value) = ReadBits( 32 ); + return value; +} + +/* +======================== +idBitMsg::ReadFloat +======================== +*/ +ID_INLINE float idBitMsg::ReadFloat( int exponentBits, int mantissaBits ) const { + int bits = ReadBits( 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( bits, exponentBits, mantissaBits ); +} + +/* +======================== +idBitMsg::ReadAngle8 +======================== +*/ +ID_INLINE float idBitMsg::ReadAngle8() const { + return BYTE2ANGLE( ReadByte() ); +} + +/* +======================== +idBitMsg::ReadAngle16 +======================== +*/ +ID_INLINE float idBitMsg::ReadAngle16() const { + return SHORT2ANGLE( ReadShort() ); +} + +/* +======================== +idBitMsg::ReadDir +======================== +*/ +ID_INLINE idVec3 idBitMsg::ReadDir( int numBits ) const { + return BitsToDir( ReadBits( numBits ), numBits ); +} + +/* +======================== +idBitMsg::WriteQuantizedFloat +======================== +*/ +template< int _max_, int _numBits_ > +ID_INLINE void idBitMsg::WriteQuantizedFloat( float value ) { + enum { storeMax = ( 1 << ( _numBits_ - 1 ) ) - 1 }; + if ( _max_ > storeMax ) { + // Scaling down (scale should be < 1) + const float scale = (float)storeMax / (float)_max_; + WriteBits( idMath::ClampInt( -storeMax, storeMax, idMath::Ftoi( value * scale ) ), -_numBits_ ); + } else { + // Scaling up (scale should be >= 1) (Preserve whole numbers when possible) + enum { scale = storeMax / _max_ }; + WriteBits( idMath::ClampInt( -storeMax, storeMax, idMath::Ftoi( value * scale ) ), -_numBits_ ); + } +} + +/* +======================== +idBitMsg::WriteQuantizedUFloat +======================== +*/ +template< int _max_, int _numBits_ > +ID_INLINE void idBitMsg::WriteQuantizedUFloat( float value ) { + enum { storeMax = ( 1 << _numBits_ ) - 1 }; + if ( _max_ > storeMax ) { + // Scaling down (scale should be < 1) + const float scale = (float)storeMax / (float)_max_; + WriteBits( idMath::ClampInt( 0, storeMax, idMath::Ftoi( value * scale ) ), _numBits_ ); + } else { + // Scaling up (scale should be >= 1) (Preserve whole numbers when possible) + enum { scale = storeMax / _max_ }; + WriteBits( idMath::ClampInt( 0, storeMax, idMath::Ftoi( value * scale ) ), _numBits_ ); + } +} + +/* +======================== +idBitMsg::ReadQuantizedFloat +======================== +*/ +template< int _max_, int _numBits_ > +ID_INLINE float idBitMsg::ReadQuantizedFloat() const { + enum { storeMax = ( 1 << ( _numBits_ - 1 ) ) - 1 }; + if ( _max_ > storeMax ) { + // Scaling down (scale should be < 1) + const float invScale = (float)_max_ / (float)storeMax; + return (float)ReadBits( -_numBits_ ) * invScale; + } else { + // Scaling up (scale should be >= 1) (Preserve whole numbers when possible) + // Scale will be a whole number. + // We use a float to get rid of (potential divide by zero) which is handled above, but the compiler is dumb + const float scale = storeMax / _max_; + const float invScale = 1.0f / scale; + return (float)ReadBits( -_numBits_ ) * invScale; + } +} + +/* +======================== +idBitMsg::ReadQuantizedUFloat +======================== +*/ +template< int _max_, int _numBits_ > +float idBitMsg::ReadQuantizedUFloat() const { + enum { storeMax = ( 1 << _numBits_ ) - 1 }; + if ( _max_ > storeMax ) { + // Scaling down (scale should be < 1) + const float invScale = (float)_max_ / (float)storeMax; + return (float)ReadBits( _numBits_ ) * invScale; + } else { + // Scaling up (scale should be >= 1) (Preserve whole numbers when possible) + // Scale will be a whole number. + // We use a float to get rid of (potential divide by zero) which is handled above, but the compiler is dumb + const float scale = storeMax / _max_; + const float invScale = 1.0f / scale; + return (float)ReadBits( _numBits_ ) * invScale; + } +} + +/* +================ +WriteFloatArray +Writes all the values from the array to the bit message. +================ +*/ +template< class _arrayType_ > +void WriteFloatArray( idBitMsg & message, const _arrayType_ & sourceArray ) { + for( int i = 0; i < idTupleSize< _arrayType_ >::value; ++i ) { + message.WriteFloat( sourceArray[i] ); + } +} + +/* +================ +WriteFloatArrayDelta +Writes _num_ values from the array to the bit message. +================ +*/ +template< class _arrayType_ > +void WriteDeltaFloatArray( idBitMsg & message, const _arrayType_ & oldArray, const _arrayType_ & newArray ) { + for( int i = 0; i < idTupleSize< _arrayType_ >::value; ++i ) { + message.WriteDeltaFloat( oldArray[i], newArray[i] ); + } +} + +/* +================ +ReadFloatArray +Reads _num_ values from the array to the bit message. +================ +*/ +template< class _arrayType_ > +_arrayType_ ReadFloatArray( const idBitMsg & message ) { + _arrayType_ result; + + for( int i = 0; i < idTupleSize< _arrayType_ >::value; ++i ) { + result[i] = message.ReadFloat(); + } + + return result; +} + +/* +================ +ReadDeltaFloatArray +Reads _num_ values from the array to the bit message. +================ +*/ +template< class _arrayType_ > +_arrayType_ ReadDeltaFloatArray( const idBitMsg & message, const _arrayType_ & oldArray ) { + _arrayType_ result; + + for( int i = 0; i < idTupleSize< _arrayType_ >::value; ++i ) { + result[i] = message.ReadDeltaFloat( oldArray[i] ); + } + + return result; +} + +#endif /* !__BITMSG_H__ */ diff --git a/neo/idlib/Callback.h b/neo/idlib/Callback.h new file mode 100644 index 00000000..d1b55f03 --- /dev/null +++ b/neo/idlib/Callback.h @@ -0,0 +1,173 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __CALLBACK_H__ +#define __CALLBACK_H__ + +/* +================================================================================================ +This file defines a set of template functors for generating callbacks, specifically +the OnChange handlers in the CVar system. +================================================================================================ +*/ + +/* +================================================ +idCallback +================================================ +*/ +class idCallback { +public: + virtual ~idCallback() {} + virtual void Call() = 0; + virtual idCallback * Clone() const = 0; +}; + +/* +================================================ +idCallbackStatic + +Callback class that forwards the call to a c-style function +================================================ +*/ +class idCallbackStatic : public idCallback { +public: + idCallbackStatic( void (*f)() ) { + this->f = f; + } + void Call() { + f(); + } + idCallback * Clone() const { + //idScopedGlobalHeap everythingHereGoesInTheGlobalHeap; + return new (TAG_FUNC_CALLBACK) idCallbackStatic( f ); + } +private: + void (*f)(); +}; + +/* +================================================ +idCallbackBindMem + +Callback class that forwards the call to a member function +================================================ +*/ +template< class T > +class idCallbackBindMem : public idCallback { +public: + idCallbackBindMem( T * t, void (T::*f)() ) { + this->t = t; + this->f = f; + } + void Call() { + (t->*f)(); + } + idCallback * Clone() const { + return new (TAG_FUNC_CALLBACK) idCallbackBindMem( t, f ); + } +private: + T * t; + void (T::*f)(); +}; + +/* +================================================ +idCallbackBindMemArg1 + +Callback class that forwards the call to a member function with an additional constant parameter +================================================ +*/ +template< class T, typename A1 > +class idCallbackBindMemArg1 : public idCallback { +public: + idCallbackBindMemArg1( T * t_, void (T::*f_)( A1 ), A1 a1_ ) : + t( t_ ), + f( f_ ), + a1( a1_ ) { + } + void Call() { + (t->*f)( a1 ); + } + idCallback * Clone() const { + return new (TAG_FUNC_CALLBACK) idCallbackBindMemArg1( t, f, a1 ); + } +private: + T * t; + void (T::*f)( A1 ); + A1 a1; + + // hack to get to compile on the 360 with reference arguments + // with this on the PC, the MakeCallback function fails compilation because it's returning a copy + // therefore, the Arg1 callbacks can't have arguments that are references + //DISALLOW_COPY_AND_ASSIGN( idCallbackBindMemArg1 ); +}; + +/* +================================================================================================ + + These are needed because we can't derive the type of an object from the type passed to the + constructor. If it weren't for these, we'd have to manually specify the type: + + idCallbackBindMem( this, &idFoo::MyFunction ); + becomes: + MakeCallback( this, &idFoo::MyFunction ); + +================================================================================================ +*/ + +/* +======================== +MakeCallback +======================== +*/ +ID_INLINE_EXTERN idCallbackStatic MakeCallback( void (*f)(void) ) { + return idCallbackStatic( f ); +} + +/* +======================== +MakeCallback +======================== +*/ +template < class T > +ID_INLINE_EXTERN idCallbackBindMem MakeCallback( T * t, void (T::*f)(void) ) { + return idCallbackBindMem( t, f ); +} + +/* +======================== +MakeCallback +======================== +*/ +template < class T, typename A1 > +ID_INLINE_EXTERN idCallbackBindMemArg1 MakeCallback( T * t, void (T::*f)( A1 ), A1 a1 ) { + return idCallbackBindMemArg1( t, f, a1 ); +} + +#endif // __CALLBACK_H__ + diff --git a/neo/idlib/CmdArgs.cpp b/neo/idlib/CmdArgs.cpp new file mode 100644 index 00000000..c2651601 --- /dev/null +++ b/neo/idlib/CmdArgs.cpp @@ -0,0 +1,202 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +/* +============ +idCmdArgs::operator= +============ +*/ +void idCmdArgs::operator=( const idCmdArgs &args ) { + int i; + + argc = args.argc; + memcpy( tokenized, args.tokenized, MAX_COMMAND_STRING ); + for ( i = 0; i < argc; i++ ) { + argv[ i ] = tokenized + ( args.argv[ i ] - args.tokenized ); + } +} + +/* +============ +idCmdArgs::Args +============ +*/ +const char *idCmdArgs::Args( int start, int end, bool escapeArgs ) const { + static char cmd_args[MAX_COMMAND_STRING]; + int i; + + assert( argc < MAX_COMMAND_ARGS ); + if ( end < 0 ) { + end = argc - 1; + } else if ( end >= argc ) { + end = argc - 1; + } + cmd_args[0] = '\0'; + if ( escapeArgs ) { + strcat( cmd_args, "\"" ); + } + for ( i = start; i <= end; i++ ) { + if ( i > start ) { + if ( escapeArgs ) { + strcat( cmd_args, "\" \"" ); + } else { + strcat( cmd_args, " " ); + } + } + if ( escapeArgs && strchr( argv[i], '\\' ) ) { + char *p = argv[i]; + while ( *p != '\0' ) { + if ( *p == '\\' ) { + strcat( cmd_args, "\\\\" ); + } else { + int l = strlen( cmd_args ); + cmd_args[ l ] = *p; + cmd_args[ l+1 ] = '\0'; + } + p++; + } + } else { + strcat( cmd_args, argv[i] ); + } + } + if ( escapeArgs ) { + strcat( cmd_args, "\"" ); + } + + return cmd_args; +} + +/* +============ +idCmdArgs::TokenizeString + +Parses the given string into command line tokens. +The text is copied to a separate buffer and 0 characters +are inserted in the appropriate place. The argv array +will point into this temporary buffer. +============ +*/ +void idCmdArgs::TokenizeString( const char *text, bool keepAsStrings ) { + idLexer lex; + idToken token, number; + int len, totalLen; + + // clear previous args + argc = 0; + + if ( !text ) { + return; + } + + lex.LoadMemory( text, strlen( text ), "idCmdSystemLocal::TokenizeString" ); + lex.SetFlags( LEXFL_NOERRORS + | LEXFL_NOWARNINGS + | LEXFL_NOSTRINGCONCAT + | LEXFL_ALLOWPATHNAMES + | LEXFL_NOSTRINGESCAPECHARS + | LEXFL_ALLOWIPADDRESSES | ( keepAsStrings ? LEXFL_ONLYSTRINGS : 0 ) ); + + totalLen = 0; + + while ( 1 ) { + if ( argc == MAX_COMMAND_ARGS ) { + return; // this is usually something malicious + } + + if ( !lex.ReadToken( &token ) ) { + return; + } + + // check for negative numbers + if ( !keepAsStrings && ( token == "-" ) ) { + if ( lex.CheckTokenType( TT_NUMBER, 0, &number ) ) { + token = "-" + number; + } + } + + // check for cvar expansion + if ( token == "$" ) { + if ( !lex.ReadToken( &token ) ) { + return; + } + if ( idLib::cvarSystem ) { + token = idLib::cvarSystem->GetCVarString( token.c_str() ); + } else { + token = ""; + } + } + + len = token.Length(); + + if ( totalLen + len + 1 > sizeof( tokenized ) ) { + return; // this is usually something malicious + } + + // regular token + argv[argc] = tokenized + totalLen; + argc++; + + idStr::Copynz( tokenized + totalLen, token.c_str(), sizeof( tokenized ) - totalLen ); + + totalLen += len + 1; + } +} + +/* +============ +idCmdArgs::AppendArg +============ +*/ +void idCmdArgs::AppendArg( const char *text ) { + if ( argc >= MAX_COMMAND_ARGS ) { + return; + } + if ( !argc ) { + argc = 1; + argv[ 0 ] = tokenized; + idStr::Copynz( tokenized, text, sizeof( tokenized ) ); + } else { + argv[ argc ] = argv[ argc-1 ] + strlen( argv[ argc-1 ] ) + 1; + idStr::Copynz( argv[ argc ], text, sizeof( tokenized ) - ( argv[ argc ] - tokenized ) ); + argc++; + } +} + +/* +============ +idCmdArgs::GetArgs +============ +*/ +const char * const * idCmdArgs::GetArgs( int *_argc ) { + *_argc = argc; + return (const char **)&argv[0]; +} + diff --git a/neo/idlib/CmdArgs.h b/neo/idlib/CmdArgs.h new file mode 100644 index 00000000..f90d3002 --- /dev/null +++ b/neo/idlib/CmdArgs.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __CMDARGS_H__ +#define __CMDARGS_H__ + +/* +=============================================================================== + + Command arguments. + +=============================================================================== +*/ + +class idCmdArgs { +public: + idCmdArgs() { argc = 0; } + idCmdArgs( const char *text, bool keepAsStrings ) { TokenizeString( text, keepAsStrings ); } + + void operator=( const idCmdArgs &args ); + + // The functions that execute commands get their parameters with these functions. + int Argc() const { return argc; } + // Argv() will return an empty string, not NULL if arg >= argc. + const char * Argv( int arg ) const { return ( arg >= 0 && arg < argc ) ? argv[arg] : ""; } + // Returns a single string containing argv(start) to argv(end) + // escapeArgs is a fugly way to put the string back into a state ready to tokenize again + const char * Args( int start = 1, int end = -1, bool escapeArgs = false ) const; + + // Takes a null terminated string and breaks the string up into arg tokens. + // Does not need to be /n terminated. + // Set keepAsStrings to true to only seperate tokens from whitespace and comments, ignoring punctuation + void TokenizeString( const char *text, bool keepAsStrings ); + + void AppendArg( const char *text ); + void Clear() { argc = 0; } + const char * const * GetArgs( int *argc ); + +private: + static const int MAX_COMMAND_ARGS = 64; + static const int MAX_COMMAND_STRING = 2 * MAX_STRING_CHARS; + + int argc; // number of arguments + char * argv[MAX_COMMAND_ARGS]; // points into tokenized + char tokenized[MAX_COMMAND_STRING]; // will have 0 bytes inserted +}; + +#endif /* !__CMDARGS_H__ */ diff --git a/neo/idlib/CommandLink.cpp b/neo/idlib/CommandLink.cpp new file mode 100644 index 00000000..f2341ee0 --- /dev/null +++ b/neo/idlib/CommandLink.cpp @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "precompiled.h" + +/* +======================== +CommandLinks + +The command system is not required for idLib, but we want to be able +to use the CONSOLE_COMMAND() macro inside idlib, so these must be here. +======================== +*/ +idCommandLink *CommandLinks( idCommandLink *cl ) { + static idCommandLink *commandLinks = NULL; + if ( cl != NULL ) { + commandLinks = cl; + } + return commandLinks; +} + + +idCommandLink *commandLinks = NULL; + +idCommandLink::idCommandLink( const char *cmdName, cmdFunction_t function, + const char *description, argCompletion_t argCompletion ) { + next = CommandLinks(); + CommandLinks( this ); + cmdName_ = cmdName; + function_ = function; + description_ = description; + argCompletion_ = argCompletion; +} + diff --git a/neo/idlib/DataQueue.h b/neo/idlib/DataQueue.h new file mode 100644 index 00000000..496e5fb4 --- /dev/null +++ b/neo/idlib/DataQueue.h @@ -0,0 +1,83 @@ +#ifndef DATAQUEUE_H +#define DATAQUEUE_H + +template< int maxItems, int maxBuffer > +class idDataQueue { +public: + idDataQueue() { + dataLength = 0; + } + bool Append( int sequence, const byte * b1, int b1Len, const byte * b2 = NULL, int b2Len = 0 ); + void RemoveOlderThan( int sequence ); + + int GetDataLength() const { return dataLength; } + + int Num() const { return items.Num(); } + int ItemSequence( int i ) const { return items[i].sequence; } + int ItemLength( int i ) const { return items[i].length; } + const byte * ItemData( int i ) const { return &data[items[i].dataOffset]; } + + void Clear() { dataLength = 0; items.Clear(); memset( data, 0, sizeof( data ) ); } + +private: + struct msgItem_t { + int sequence; + int length; + int dataOffset; + }; + idStaticList items; + int dataLength; + byte data[ maxBuffer ]; +}; + +/* +======================== +idDataQueue::RemoveOlderThan +======================== +*/ +template< int maxItems, int maxBuffer > +void idDataQueue< maxItems, maxBuffer >::RemoveOlderThan( int sequence ) { + int length = 0; + while ( items.Num() > 0 && items[0].sequence < sequence ) { + length += items[0].length; + items.RemoveIndex( 0 ); + } + if ( length >= dataLength ) { + assert( items.Num() == 0 ); + assert( dataLength == length ); + dataLength = 0; + } else if ( length > 0 ) { + memmove( data, data + length, dataLength - length ); + dataLength -= length; + } + length = 0; + for ( int i = 0; i < items.Num(); i++ ) { + items[i].dataOffset = length; + length += items[i].length; + } + assert( length == dataLength ); +} + +/* +======================== +idDataQueue::Append +======================== +*/ +template< int maxItems, int maxBuffer > +bool idDataQueue< maxItems, maxBuffer >::Append( int sequence, const byte * b1, int b1Len, const byte * b2, int b2Len ) { + if ( items.Num() == items.Max() ) { + return false; + } + if ( dataLength + b1Len + b2Len >= maxBuffer ) { + return false; + } + msgItem_t & item = *items.Alloc(); + item.length = b1Len + b2Len; + item.sequence = sequence; + item.dataOffset = dataLength; + memcpy( data + dataLength, b1, b1Len ); dataLength += b1Len; + memcpy( data + dataLength, b2, b2Len ); dataLength += b2Len; + return true; +} + +#endif // DATAQUEUE_H diff --git a/neo/idlib/Dict.cpp b/neo/idlib/Dict.cpp new file mode 100644 index 00000000..190f194d --- /dev/null +++ b/neo/idlib/Dict.cpp @@ -0,0 +1,943 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +idStrPool idDict::globalKeys; +idStrPool idDict::globalValues; + +/* +================ +idDict::operator= + + clear existing key/value pairs and copy all key/value pairs from other +================ +*/ +idDict &idDict::operator=( const idDict &other ) { + int i; + + // check for assignment to self + if ( this == &other ) { + return *this; + } + + Clear(); + + args = other.args; + argHash = other.argHash; + + for ( i = 0; i < args.Num(); i++ ) { + args[i].key = globalKeys.CopyString( args[i].key ); + args[i].value = globalValues.CopyString( args[i].value ); + } + + return *this; +} + +/* +================ +idDict::Copy + + copy all key value pairs without removing existing key/value pairs not present in the other dict +================ +*/ +void idDict::Copy( const idDict &other ) { + int i, n, *found; + idKeyValue kv; + + // check for assignment to self + if ( this == &other ) { + return; + } + + n = other.args.Num(); + + if ( args.Num() ) { + found = (int *) _alloca16( other.args.Num() * sizeof( int ) ); + for ( i = 0; i < n; i++ ) { + found[i] = FindKeyIndex( other.args[i].GetKey() ); + } + } else { + found = NULL; + } + + for ( i = 0; i < n; i++ ) { + if ( found && found[i] != -1 ) { + // first set the new value and then free the old value to allow proper self copying + const idPoolStr *oldValue = args[found[i]].value; + args[found[i]].value = globalValues.CopyString( other.args[i].value ); + globalValues.FreeString( oldValue ); + } else { + kv.key = globalKeys.CopyString( other.args[i].key ); + kv.value = globalValues.CopyString( other.args[i].value ); + argHash.Add( argHash.GenerateKey( kv.GetKey(), false ), args.Append( kv ) ); + } + } +} + +/* +================ +idDict::TransferKeyValues + + clear existing key/value pairs and transfer key/value pairs from other +================ +*/ +void idDict::TransferKeyValues( idDict &other ) { + int i, n; + + if ( this == &other ) { + return; + } + + if ( other.args.Num() && other.args[0].key->GetPool() != &globalKeys ) { + common->FatalError( "idDict::TransferKeyValues: can't transfer values across a DLL boundary" ); + return; + } + + Clear(); + + n = other.args.Num(); + args.SetNum( n ); + for ( i = 0; i < n; i++ ) { + args[i].key = other.args[i].key; + args[i].value = other.args[i].value; + } + argHash = other.argHash; + + other.args.Clear(); + other.argHash.Free(); +} + +/* +================ +idDict::Parse +================ +*/ +bool idDict::Parse( idParser &parser ) { + idToken token; + idToken token2; + bool errors; + + errors = false; + + parser.ExpectTokenString( "{" ); + parser.ReadToken( &token ); + while( ( token.type != TT_PUNCTUATION ) || ( token != "}" ) ) { + if ( token.type != TT_STRING ) { + parser.Error( "Expected quoted string, but found '%s'", token.c_str() ); + } + + if ( !parser.ReadToken( &token2 ) ) { + parser.Error( "Unexpected end of file" ); + } + + if ( FindKey( token ) ) { + parser.Warning( "'%s' already defined", token.c_str() ); + errors = true; + } + Set( token, token2 ); + + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Unexpected end of file" ); + } + } + + return !errors; +} + +/* +================ +idDict::SetDefaults +================ +*/ +void idDict::SetDefaults( const idDict *dict ) { + int i, n; + const idKeyValue *kv, *def; + idKeyValue newkv; + + n = dict->args.Num(); + for( i = 0; i < n; i++ ) { + def = &dict->args[i]; + kv = FindKey( def->GetKey() ); + if ( !kv ) { + newkv.key = globalKeys.CopyString( def->key ); + newkv.value = globalValues.CopyString( def->value ); + argHash.Add( argHash.GenerateKey( newkv.GetKey(), false ), args.Append( newkv ) ); + } + } +} + +/* +================ +idDict::Clear +================ +*/ +void idDict::Clear() { + int i; + + for( i = 0; i < args.Num(); i++ ) { + globalKeys.FreeString( args[i].key ); + globalValues.FreeString( args[i].value ); + } + + args.Clear(); + argHash.Free(); +} + +/* +================ +idDict::Print +================ +*/ +void idDict::Print() const { + int i; + int n; + + n = args.Num(); + for( i = 0; i < n; i++ ) { + idLib::common->Printf( "%s = %s\n", args[i].GetKey().c_str(), args[i].GetValue().c_str() ); + } +} + +int KeyCompare( const idKeyValue *a, const idKeyValue *b ) { + return idStr::Cmp( a->GetKey(), b->GetKey() ); +} + +/* +================ +idDict::Checksum +================ +*/ +int idDict::Checksum() const { + unsigned long ret; + int i, n; + + idList sorted = args; + sorted.SortWithTemplate( idSort_KeyValue() ); + n = sorted.Num(); + CRC32_InitChecksum( ret ); + for( i = 0; i < n; i++ ) { + CRC32_UpdateChecksum( ret, sorted[i].GetKey().c_str(), sorted[i].GetKey().Length() ); + CRC32_UpdateChecksum( ret, sorted[i].GetValue().c_str(), sorted[i].GetValue().Length() ); + } + CRC32_FinishChecksum( ret ); + return ret; +} + +/* +================ +idDict::Allocated +================ +*/ +size_t idDict::Allocated() const { + int i; + size_t size; + + size = args.Allocated() + argHash.Allocated(); + for( i = 0; i < args.Num(); i++ ) { + size += args[i].Size(); + } + + return size; +} + +/* +================ +idDict::Set +================ +*/ +void idDict::Set( const char *key, const char *value ) { + int i; + idKeyValue kv; + + if ( key == NULL || key[0] == '\0' ) { + return; + } + + i = FindKeyIndex( key ); + if ( i != -1 ) { + // first set the new value and then free the old value to allow proper self copying + const idPoolStr *oldValue = args[i].value; + args[i].value = globalValues.AllocString( value ); + globalValues.FreeString( oldValue ); + } else { + kv.key = globalKeys.AllocString( key ); + kv.value = globalValues.AllocString( value ); + argHash.Add( argHash.GenerateKey( kv.GetKey(), false ), args.Append( kv ) ); + } +} + +/* +================ +idDict::GetFloat +================ +*/ +bool idDict::GetFloat( const char *key, const char *defaultString, float &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = atof( s ); + return found; +} + +/* +================ +idDict::GetInt +================ +*/ +bool idDict::GetInt( const char *key, const char *defaultString, int &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = atoi( s ); + return found; +} + +/* +================ +idDict::GetBool +================ +*/ +bool idDict::GetBool( const char *key, const char *defaultString, bool &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = ( atoi( s ) != 0 ); + return found; +} + +/* +================ +idDict::GetFloat +================ +*/ +bool idDict::GetFloat( const char *key, const float defaultFloat, float &out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + out = atof( kv->GetValue() ); + return true; + } else { + out = defaultFloat; + return false; + } +} + +/* +================ +idDict::GetInt +================ +*/ +bool idDict::GetInt( const char *key, const int defaultInt, int &out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + out = atoi( kv->GetValue() ); + return true; + } else { + out = defaultInt; + return false; + } +} + +/* +================ +idDict::GetBool +================ +*/ +bool idDict::GetBool( const char *key, const bool defaultBool, bool &out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + out = ( atoi( kv->GetValue() ) != 0 ); + return true; + } else { + out = defaultBool; + return false; + } +} + +/* +================ +idDict::GetAngles +================ +*/ +bool idDict::GetAngles( const char *key, const char *defaultString, idAngles &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f", &out.pitch, &out.yaw, &out.roll ); + return found; +} + +/* +================ +idDict::GetVector +================ +*/ +bool idDict::GetVector( const char *key, const char *defaultString, idVec3 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f", &out.x, &out.y, &out.z ); + return found; +} + +/* +================ +idDict::GetVec2 +================ +*/ +bool idDict::GetVec2( const char *key, const char *defaultString, idVec2 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f", &out.x, &out.y ); + return found; +} + +/* +================ +idDict::GetVec4 +================ +*/ +bool idDict::GetVec4( const char *key, const char *defaultString, idVec4 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f %f", &out.x, &out.y, &out.z, &out.w ); + return found; +} + +/* +================ +idDict::GetMatrix +================ +*/ +bool idDict::GetMatrix( const char *key, const char *defaultString, idMat3 &out ) const { + const char *s; + bool found; + + if ( !defaultString ) { + defaultString = "1 0 0 0 1 0 0 0 1"; + } + + found = GetString( key, defaultString, &s ); + out.Identity(); // sccanf has a bug in it on Mac OS 9. Sigh. + sscanf( s, "%f %f %f %f %f %f %f %f %f", &out[0].x, &out[0].y, &out[0].z, &out[1].x, &out[1].y, &out[1].z, &out[2].x, &out[2].y, &out[2].z ); + return found; +} + +/* +================ +WriteString +================ +*/ +static void WriteString( const char *s, idFile *f ) { + int len = strlen( s ); + if ( len >= MAX_STRING_CHARS-1 ) { + idLib::common->Error( "idDict::WriteToFileHandle: bad string" ); + } + f->Write( s, strlen(s) + 1 ); +} + +/* +================ +idDict::FindKey +================ +*/ +const idKeyValue *idDict::FindKey( const char *key ) const { + int i, hash; + + if ( key == NULL || key[0] == '\0' ) { + idLib::common->DWarning( "idDict::FindKey: empty key" ); + return NULL; + } + + hash = argHash.GenerateKey( key, false ); + for ( i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + return &args[i]; + } + } + + return NULL; +} + +/* +================ +idDict::FindKeyIndex +================ +*/ +int idDict::FindKeyIndex( const char *key ) const { + + if ( key == NULL || key[0] == '\0' ) { + idLib::common->DWarning( "idDict::FindKeyIndex: empty key" ); + return 0; + } + + int hash = argHash.GenerateKey( key, false ); + for ( int i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + return i; + } + } + + return -1; +} + +/* +================ +idDict::Delete +================ +*/ +void idDict::Delete( const char *key ) { + int hash, i; + + hash = argHash.GenerateKey( key, false ); + for ( i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + globalKeys.FreeString( args[i].key ); + globalValues.FreeString( args[i].value ); + args.RemoveIndex( i ); + argHash.RemoveIndex( hash, i ); + break; + } + } + +#if 0 + // make sure all keys can still be found in the hash index + for ( i = 0; i < args.Num(); i++ ) { + assert( FindKey( args[i].GetKey() ) != NULL ); + } +#endif +} + +/* +================ +idDict::MatchPrefix +================ +*/ +const idKeyValue *idDict::MatchPrefix( const char *prefix, const idKeyValue *lastMatch ) const { + int i; + int len; + int start; + + assert( prefix ); + len = strlen( prefix ); + + start = -1; + if ( lastMatch ) { + start = args.FindIndex( *lastMatch ); + assert( start >= 0 ); + if ( start < 1 ) { + start = 0; + } + } + + for( i = start + 1; i < args.Num(); i++ ) { + if ( !args[i].GetKey().Icmpn( prefix, len ) ) { + return &args[i]; + } + } + return NULL; +} + +/* +================ +idDict::RandomPrefix +================ +*/ +const char *idDict::RandomPrefix( const char *prefix, idRandom &random ) const { + int count; + const int MAX_RANDOM_KEYS = 2048; + const char *list[MAX_RANDOM_KEYS]; + const idKeyValue *kv; + + list[0] = ""; + for ( count = 0, kv = MatchPrefix( prefix ); kv != NULL && count < MAX_RANDOM_KEYS; kv = MatchPrefix( prefix, kv ) ) { + list[count++] = kv->GetValue().c_str(); + } + return list[random.RandomInt( count )]; +} + +/* +================ +idDict::WriteToFileHandle +================ +*/ +void idDict::WriteToFileHandle( idFile *f ) const { + int c = LittleLong( args.Num() ); + f->Write( &c, sizeof( c ) ); + for ( int i = 0; i < args.Num(); i++ ) { // don't loop on the swapped count use the original + WriteString( args[i].GetKey().c_str(), f ); + WriteString( args[i].GetValue().c_str(), f ); + } +} + +/* +================ +ReadString +================ +*/ +static idStr ReadString( idFile *f ) { + char str[MAX_STRING_CHARS]; + int len; + + for ( len = 0; len < MAX_STRING_CHARS; len++ ) { + f->Read( (void *)&str[len], 1 ); + if ( str[len] == 0 ) { + break; + } + } + if ( len == MAX_STRING_CHARS ) { + idLib::common->Error( "idDict::ReadFromFileHandle: bad string" ); + } + + return idStr( str ); +} + +/* +================ +idDict::ReadFromFileHandle +================ +*/ +void idDict::ReadFromFileHandle( idFile *f ) { + int c; + idStr key, val; + + Clear(); + + f->Read( &c, sizeof( c ) ); + c = LittleLong( c ); + for ( int i = 0; i < c; i++ ) { + key = ReadString( f ); + val = ReadString( f ); + Set( key, val ); + } +} + +/* +======================== +idDict::Serialize +======================== +*/ +void idDict::Serialize( idSerializer & ser ) { + if ( ser.IsReading() ) { + Clear(); + } + + int num = args.Num(); + ser.SerializePacked( num ); + for ( int i = 0; i < num; i++ ) { + idStr key; + idStr val; + + if ( ser.IsWriting() ) { + key = args[i].GetKey(); + val = args[i].GetValue(); + } + + ser.SerializeString( key ); + ser.SerializeString( val ); + + if ( ser.IsReading() ) { + Set( key.c_str(), val.c_str() ); + } + } +} + +/* +================ +idDict::WriteToIniFile +================ +*/ +void idDict::WriteToIniFile( idFile * f ) const { + // make a copy so we don't affect the checksum of the original dict + idList< idKeyValue > sortedArgs( args ); + sortedArgs.SortWithTemplate( idSort_KeyValue() ); + + idList< idStr > prefixList; + idTempArray< int > prefixIndex( sortedArgs.Num() ); // for each keyValue in the args, this is an index into which prefix it uses. + // 0 means no prefix, otherwise, it's an index + (-1) into prefixList + // we do this so we can print all the non-prefix based pairs first + idStr prevPrefix = ""; + idStr skipFirstLine = ""; + + // Scan for all the prefixes + for ( int i = 0; i < sortedArgs.Num(); i++ ) { + const idKeyValue * kv = &sortedArgs[i]; + int slashPosition = kv->GetKey().Last( '/' ); + if ( slashPosition != idStr::INVALID_POSITION ) { + idStr prefix = kv->GetKey().Mid( 0, slashPosition ); + if ( prefix != prevPrefix ) { + prevPrefix = prefix; + prefixList.Append( prefix ); + } + prefixIndex[i] = prefixList.Num(); + } else { + prefixIndex[i] = 0; + + // output all the prefix-less first + idStr str = va( "%s=%s\n", kv->GetKey().c_str(), idStr::CStyleQuote( kv->GetValue() ) ); + f->Write( (void *)str.c_str(), str.Length() ); + + skipFirstLine = "\n"; + } + } + + int prevPrefixIndex = 0; + int prefixLength = 0; + + // output all the rest without their prefix + for ( int i = 0; i < sortedArgs.Num(); i++ ) { + if ( prefixIndex[i] == 0 ) { + continue; + } + + if ( prefixIndex[i] != prevPrefixIndex ) { + prevPrefixIndex = prefixIndex[i]; + prefixLength = prefixList[prevPrefixIndex - 1].Length() + 1; // to skip past the '/' too + + // output prefix + idStr str = va( "%s[%s]\n", skipFirstLine.c_str(), prefixList[prevPrefixIndex - 1].c_str() ); + f->Write( (void *)str.c_str(), str.Length() ); + } + + const idKeyValue * kv = &sortedArgs[i]; + idStr str = va( "%s=%s\n", kv->GetKey().c_str() + prefixLength, idStr::CStyleQuote( kv->GetValue() ) ); + f->Write( (void *)str.c_str(), str.Length() ); + } +} + +/* +================ +idDict::ReadFromIniFile +================ +*/ +bool idDict::ReadFromIniFile( idFile * f ) { + int length = f->Length(); + idTempArray< char > buffer( length ); + if ( (int)f->Read( buffer.Ptr(), length ) != length ) { + return false; + } + buffer[length-1] = NULL; // Since the .ini files are not null terminated, make sure we mark where the end of the .ini file is in our read buffer + + idLexer parser( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES /*| LEXFL_ONLYSTRINGS */); + idStr name = f->GetName(); + name.Append( " dictionary INI reader" ); + if ( !parser.LoadMemory( ( const char* )buffer.Ptr(), length, name.c_str() ) ) { + return false; + } + + idToken token; + idToken token2; + idStr prefix = ""; + idStr valueStr; + bool success = true; + + Clear(); + + const punctuation_t ini_punctuations[] = { + { "[", P_SQBRACKETOPEN }, + { "]", P_SQBRACKETCLOSE }, + { "=", P_ASSIGN }, + { NULL, 0 } + }; + parser.SetPunctuations( ini_punctuations ); + + while ( success && !parser.EndOfFile() ) { + if ( parser.PeekTokenType( TT_PUNCTUATION, P_SQBRACKETOPEN, &token ) ) { + success = success && parser.ExpectTokenType( TT_PUNCTUATION, P_SQBRACKETOPEN, &token ); + success = success && parser.ReadToken( &token ); + prefix = token.c_str(); + prefix.Append( '/' ); + success = success && parser.ExpectTokenType( TT_PUNCTUATION, P_SQBRACKETCLOSE, &token ); + } + + if ( !parser.PeekTokenType( TT_NAME, 0, &token ) ) { + // end of file most likely + break; + } + + success = success && parser.ExpectTokenType( TT_NAME, 0, &token ); + success = success && parser.ExpectTokenType( TT_PUNCTUATION, P_ASSIGN, &token2 ); + success = success && ( parser.ParseRestOfLine( valueStr ) != NULL ); + + valueStr = idStr::CStyleUnQuote( valueStr ); + + idStr key = va( "%s%s", prefix.c_str(), token.c_str() ); + if ( FindKey( key.c_str() ) ) { + parser.Warning( "'%s' already defined", key.c_str() ); + } + + Set( key.c_str(), valueStr.c_str() ); + } + + return success; +} + +CONSOLE_COMMAND( TestDictIniFile, "Tests the writing/reading of various items in a dict to/from an ini file", 0 ) { + // Write to the file + idFile * file = fileSystem->OpenFileWrite( "idDict_ini_test.ini" ); + if ( file == NULL ) { + idLib::Printf( "[^1FAILED^0] Couldn't open file for writing.\n" ); + return; + } + + idDict vars; + vars.SetInt( "section1/section3/a", -1 ); + vars.SetInt( "section1/section3/b", 0 ); + vars.SetInt( "section1/section3/c", 3 ); + vars.SetFloat( "section2/d", 4.0f ); + vars.SetFloat( "section2/e", -5.0f ); + vars.SetBool( "section2/f", true ); + vars.SetBool( "section1/g", false ); + vars.Set( "section1/h", "test1" ); + vars.Set( "i", "1234" ); + vars.SetInt( "j", 9 ); + vars.WriteToIniFile( file ); + delete file; + + // Read from the file + file = fileSystem->OpenFileRead( "idDict_ini_test.ini" ); + if ( file == NULL ) { + idLib::Printf( "[^1FAILED^0] Couldn't open file for reading.\n" ); + } + + idDict readVars; + readVars.ReadFromIniFile( file ); + delete file; + + if ( vars.Checksum() != readVars.Checksum() ) { + idLib::Printf( "[^1FAILED^0] Dictionaries do not match.\n" ); + } else { + idLib::Printf( "[^2PASSED^0] Dictionaries match.\n" ); + } + + // Output results + for ( int i = 0; i < readVars.GetNumKeyVals(); i++ ) { + const idKeyValue * kv = readVars.GetKeyVal( i ); + idLib::Printf( "%s=%s\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } +} + +/* +================ +idDict::Init +================ +*/ +void idDict::Init() { + globalKeys.SetCaseSensitive( false ); + globalValues.SetCaseSensitive( true ); +} + +/* +================ +idDict::Shutdown +================ +*/ +void idDict::Shutdown() { + globalKeys.Clear(); + globalValues.Clear(); +} + +/* +================ +idDict::ShowMemoryUsage_f +================ +*/ +void idDict::ShowMemoryUsage_f( const idCmdArgs &args ) { + idLib::common->Printf( "%5d KB in %d keys\n", globalKeys.Size() >> 10, globalKeys.Num() ); + idLib::common->Printf( "%5d KB in %d values\n", globalValues.Size() >> 10, globalValues.Num() ); +} + +/* +================ +idDict::ListKeys_f +================ +*/ +void idDict::ListKeys_f( const idCmdArgs &args ) { + idLib::Printf( "Not implemented due to sort impl issues.\n" ); + //int i; + //idList keyStrings; + + //for ( i = 0; i < globalKeys.Num(); i++ ) { + // keyStrings.Append( globalKeys[i] ); + //} + //keyStrings.SortWithTemplate( idSort_PoolStrPtr() ); + //for ( i = 0; i < keyStrings.Num(); i++ ) { + // idLib::common->Printf( "%s\n", keyStrings[i]->c_str() ); + //} + //idLib::common->Printf( "%5d keys\n", keyStrings.Num() ); +} + +/* +================ +idDict::ListValues_f +================ +*/ +void idDict::ListValues_f( const idCmdArgs &args ) { + idLib::Printf( "Not implemented due to sort impl issues.\n" ); + //int i; + //idList valueStrings; + + //for ( i = 0; i < globalValues.Num(); i++ ) { + // valueStrings.Append( globalValues[i] ); + //} + //valueStrings.SortWithTemplate( idSort_PoolStrPtr() ); + //for ( i = 0; i < valueStrings.Num(); i++ ) { + // idLib::common->Printf( "%s\n", valueStrings[i]->c_str() ); + //} + //idLib::common->Printf( "%5d values\n", valueStrings.Num() ); +} diff --git a/neo/idlib/Dict.h b/neo/idlib/Dict.h new file mode 100644 index 00000000..f526f363 --- /dev/null +++ b/neo/idlib/Dict.h @@ -0,0 +1,348 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DICT_H__ +#define __DICT_H__ + +class idSerializer; + +/* +=============================================================================== + +Key/value dictionary + +This is a dictionary class that tracks an arbitrary number of key / value +pair combinations. It is used for map entity spawning, GUI state management, +and other things. + +Keys are compared case-insensitive. + +Does not allocate memory until the first key/value pair is added. + +=============================================================================== +*/ + +class idKeyValue { + friend class idDict; + +public: + const idStr & GetKey() const { return *key; } + const idStr & GetValue() const { return *value; } + + size_t Allocated() const { return key->Allocated() + value->Allocated(); } + size_t Size() const { return sizeof( *this ) + key->Size() + value->Size(); } + + bool operator==( const idKeyValue &kv ) const { return ( key == kv.key && value == kv.value ); } + +private: + const idPoolStr * key; + const idPoolStr * value; +}; + +/* +================================================ +idSort_KeyValue +================================================ +*/ +class idSort_KeyValue : public idSort_Quick< idKeyValue, idSort_KeyValue > { +public: + int Compare( const idKeyValue & a, const idKeyValue & b ) const { return a.GetKey().Icmp( b.GetKey() ); } +}; + +class idDict { +public: + idDict(); + idDict( const idDict &other ); // allow declaration with assignment + ~idDict(); + + // set the granularity for the index + void SetGranularity( int granularity ); + // set hash size + void SetHashSize( int hashSize ); + // clear existing key/value pairs and copy all key/value pairs from other + idDict & operator=( const idDict &other ); + // copy from other while leaving existing key/value pairs in place + void Copy( const idDict &other ); + // clear existing key/value pairs and transfer key/value pairs from other + void TransferKeyValues( idDict &other ); + // parse dict from parser + bool Parse( idParser &parser ); + // copy key/value pairs from other dict not present in this dict + void SetDefaults( const idDict *dict ); + // clear dict freeing up memory + void Clear(); + // print the dict + void Print() const; + + size_t Allocated() const; + size_t Size() const { return sizeof( *this ) + Allocated(); } + + void Set( const char *key, const char *value ); + void SetFloat( const char *key, float val ); + void SetInt( const char *key, int val ); + void SetBool( const char *key, bool val ); + void SetVector( const char *key, const idVec3 &val ); + void SetVec2( const char *key, const idVec2 &val ); + void SetVec4( const char *key, const idVec4 &val ); + void SetAngles( const char *key, const idAngles &val ); + void SetMatrix( const char *key, const idMat3 &val ); + + // these return default values of 0.0, 0 and false + const char * GetString( const char *key, const char *defaultString = "" ) const; + float GetFloat( const char *key, const char *defaultString ) const; + int GetInt( const char *key, const char *defaultString ) const; + bool GetBool( const char *key, const char *defaultString ) const; + float GetFloat( const char *key, const float defaultFloat = 0.0f ) const; + int GetInt( const char *key, const int defaultInt = 0 ) const; + bool GetBool( const char *key, const bool defaultBool = false ) const; + idVec3 GetVector( const char *key, const char *defaultString = NULL ) const; + idVec2 GetVec2( const char *key, const char *defaultString = NULL ) const; + idVec4 GetVec4( const char *key, const char *defaultString = NULL ) const; + idAngles GetAngles( const char *key, const char *defaultString = NULL ) const; + idMat3 GetMatrix( const char *key, const char *defaultString = NULL ) const; + + bool GetString( const char *key, const char *defaultString, const char **out ) const; + bool GetString( const char *key, const char *defaultString, idStr &out ) const; + bool GetFloat( const char *key, const char *defaultString, float &out ) const; + bool GetInt( const char *key, const char *defaultString, int &out ) const; + bool GetBool( const char *key, const char *defaultString, bool &out ) const; + bool GetFloat( const char *key, const float defaultFloat, float &out ) const; + bool GetInt( const char *key, const int defaultInt, int &out ) const; + bool GetBool( const char *key, const bool defaultBool, bool &out ) const; + bool GetVector( const char *key, const char *defaultString, idVec3 &out ) const; + bool GetVec2( const char *key, const char *defaultString, idVec2 &out ) const; + bool GetVec4( const char *key, const char *defaultString, idVec4 &out ) const; + bool GetAngles( const char *key, const char *defaultString, idAngles &out ) const; + bool GetMatrix( const char *key, const char *defaultString, idMat3 &out ) const; + + int GetNumKeyVals() const; + const idKeyValue * GetKeyVal( int index ) const; + // returns the key/value pair with the given key + // returns NULL if the key/value pair does not exist + const idKeyValue * FindKey( const char *key ) const; + // returns the index to the key/value pair with the given key + // returns -1 if the key/value pair does not exist + int FindKeyIndex( const char *key ) const; + // delete the key/value pair with the given key + void Delete( const char *key ); + // finds the next key/value pair with the given key prefix. + // lastMatch can be used to do additional searches past the first match. + const idKeyValue * MatchPrefix( const char *prefix, const idKeyValue *lastMatch = NULL ) const; + // randomly chooses one of the key/value pairs with the given key prefix and returns it's value + const char * RandomPrefix( const char *prefix, idRandom &random ) const; + + void WriteToFileHandle( idFile *f ) const; + void ReadFromFileHandle( idFile *f ); + + void WriteToIniFile( idFile * f ) const; + bool ReadFromIniFile( idFile * f ); + + void Serialize( idSerializer & ser ); + + // returns a unique checksum for this dictionary's content + int Checksum() const; + + static void Init(); + static void Shutdown(); + + static void ShowMemoryUsage_f( const idCmdArgs &args ); + static void ListKeys_f( const idCmdArgs &args ); + static void ListValues_f( const idCmdArgs &args ); + +private: + idList args; + idHashIndex argHash; + + static idStrPool globalKeys; + static idStrPool globalValues; +}; + + +ID_INLINE idDict::idDict() { + args.SetGranularity( 16 ); + argHash.SetGranularity( 16 ); + argHash.Clear( 128, 16 ); +} + +ID_INLINE idDict::idDict( const idDict &other ) { + *this = other; +} + +ID_INLINE idDict::~idDict() { + Clear(); +} + +ID_INLINE void idDict::SetGranularity( int granularity ) { + args.SetGranularity( granularity ); + argHash.SetGranularity( granularity ); +} + +ID_INLINE void idDict::SetHashSize( int hashSize ) { + if ( args.Num() == 0 ) { + argHash.Clear( hashSize, 16 ); + } +} + +ID_INLINE void idDict::SetFloat( const char *key, float val ) { + Set( key, va( "%f", val ) ); +} + +ID_INLINE void idDict::SetInt( const char *key, int val ) { + Set( key, va( "%i", val ) ); +} + +ID_INLINE void idDict::SetBool( const char *key, bool val ) { + Set( key, va( "%i", val ) ); +} + +ID_INLINE void idDict::SetVector( const char *key, const idVec3 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetVec4( const char *key, const idVec4 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetVec2( const char *key, const idVec2 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetAngles( const char *key, const idAngles &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetMatrix( const char *key, const idMat3 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE bool idDict::GetString( const char *key, const char *defaultString, const char **out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + *out = kv->GetValue(); + return true; + } + *out = defaultString; + return false; +} + +ID_INLINE bool idDict::GetString( const char *key, const char *defaultString, idStr &out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + out = kv->GetValue(); + return true; + } + out = defaultString; + return false; +} + +ID_INLINE const char *idDict::GetString( const char *key, const char *defaultString ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + return kv->GetValue(); + } + return defaultString; +} + +ID_INLINE float idDict::GetFloat( const char *key, const char *defaultString ) const { + return atof( GetString( key, defaultString ) ); +} + +ID_INLINE int idDict::GetInt( const char *key, const char *defaultString ) const { + return atoi( GetString( key, defaultString ) ); +} + +ID_INLINE bool idDict::GetBool( const char *key, const char *defaultString ) const { + return ( atoi( GetString( key, defaultString ) ) != 0 ); +} + +ID_INLINE float idDict::GetFloat( const char *key, const float defaultFloat ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + return atof( kv->GetValue() ); + } + return defaultFloat; +} + +ID_INLINE int idDict::GetInt( const char *key, int defaultInt ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + return atoi( kv->GetValue() ); + } + return defaultInt; +} + +ID_INLINE bool idDict::GetBool( const char *key, const bool defaultBool ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + return atoi( kv->GetValue() ) != 0; + } + return defaultBool; +} + +ID_INLINE idVec3 idDict::GetVector( const char *key, const char *defaultString ) const { + idVec3 out; + GetVector( key, defaultString, out ); + return out; +} + +ID_INLINE idVec2 idDict::GetVec2( const char *key, const char *defaultString ) const { + idVec2 out; + GetVec2( key, defaultString, out ); + return out; +} + +ID_INLINE idVec4 idDict::GetVec4( const char *key, const char *defaultString ) const { + idVec4 out; + GetVec4( key, defaultString, out ); + return out; +} + +ID_INLINE idAngles idDict::GetAngles( const char *key, const char *defaultString ) const { + idAngles out; + GetAngles( key, defaultString, out ); + return out; +} + +ID_INLINE idMat3 idDict::GetMatrix( const char *key, const char *defaultString ) const { + idMat3 out; + GetMatrix( key, defaultString, out ); + return out; +} + +ID_INLINE int idDict::GetNumKeyVals() const { + return args.Num(); +} + +ID_INLINE const idKeyValue *idDict::GetKeyVal( int index ) const { + if ( index >= 0 && index < args.Num() ) { + return &args[ index ]; + } + return NULL; +} + +#endif /* !__DICT_H__ */ diff --git a/neo/idlib/Heap.cpp b/neo/idlib/Heap.cpp new file mode 100644 index 00000000..7195970d --- /dev/null +++ b/neo/idlib/Heap.cpp @@ -0,0 +1,86 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +//=============================================================== +// +// memory allocation all in one place +// +//=============================================================== + +#undef new + +/* +================== +Mem_Alloc16 +================== +*/ +void * Mem_Alloc16( const int size, const memTag_t tag ) { + if ( !size ) { + return NULL; + } + const int paddedSize = ( size + 15 ) & ~15; + return _aligned_malloc( paddedSize, 16 ); +} + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr ) { + if ( ptr == NULL ) { + return; + } + _aligned_free( ptr ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void * Mem_ClearedAlloc( const int size, const memTag_t tag ) { + void * mem = Mem_Alloc( size, tag ); + SIMDProcessor->Memset( mem, 0, size ); + return mem; +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in ) { + char * out = (char *)Mem_Alloc( strlen(in) + 1, TAG_STRING ); + strcpy( out, in ); + return out; +} + diff --git a/neo/idlib/Heap.h b/neo/idlib/Heap.h new file mode 100644 index 00000000..0df0257b --- /dev/null +++ b/neo/idlib/Heap.h @@ -0,0 +1,1079 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __HEAP_H__ +#define __HEAP_H__ + +/* +=============================================================================== + + Memory Management + +=============================================================================== +*/ + +// memory tag names are used to sort allocations for sys_dumpMemory and other reporting functions +enum memTag_t { +#define MEM_TAG( x ) TAG_##x, +#include "sys/sys_alloc_tags.h" + TAG_NUM_TAGS, +}; + +static const int MAX_TAGS = 256; + + + + +void * Mem_Alloc16( const int size, const memTag_t tag ); +void Mem_Free16( void *ptr ); + +ID_INLINE void * Mem_Alloc( const int size, const memTag_t tag ) { return Mem_Alloc16( size, tag ); } +ID_INLINE void Mem_Free( void *ptr ) { Mem_Free16( ptr ); } + +void * Mem_ClearedAlloc( const int size, const memTag_t tag ); +char * Mem_CopyString( const char *in ); + +ID_INLINE void *operator new( size_t s ) { + return Mem_Alloc( s, TAG_NEW ); +} +ID_INLINE void operator delete( void *p ) { + Mem_Free( p ); +} +ID_INLINE void *operator new[]( size_t s ) { + return Mem_Alloc( s, TAG_NEW ); +} +ID_INLINE void operator delete[]( void *p ) { + Mem_Free( p ); +} +ID_INLINE void *operator new( size_t s, memTag_t tag ) { + return Mem_Alloc( s, tag ); +} +ID_INLINE void operator delete( void *p, memTag_t tag ) { + Mem_Free( p ); +} +ID_INLINE void *operator new[]( size_t s, memTag_t tag ) { + return Mem_Alloc( s, tag ); +} +ID_INLINE void operator delete[]( void *p, memTag_t tag ) { + Mem_Free( p ); +} + +// Define replacements for the PS3 library's aligned new operator. +// Without these, allocations of objects with 32 byte or greater alignment +// may not go through our memory system. + +/* +================================================ +idTempArray is an array that is automatically free'd when it goes out of scope. +There is no "cast" operator because these are very unsafe. + +The template parameter MUST BE POD! + +Compile time asserting POD-ness of the template parameter is complicated due +to our vector classes that need a default constructor but are otherwise +considered POD. +================================================ +*/ +template < class T > +class idTempArray { +public: + idTempArray( idTempArray & other ); + idTempArray( unsigned int num ); + + ~idTempArray(); + + T & operator []( unsigned int i ) { assert( i < num ); return buffer[i]; } + const T & operator []( unsigned int i ) const { assert( i < num ); return buffer[i]; } + + T * Ptr() { return buffer; } + const T* Ptr() const { return buffer; } + + size_t Size( ) const { return num * sizeof( T ); } + unsigned int Num( ) const { return num; } + + void Zero() { memset( Ptr(), 0, Size() ); } + +private: + T * buffer; // Ensure this buffer comes first, so this == &this->buffer + unsigned int num; +}; + +/* +======================== +idTempArray::idTempArray +======================== +*/ +template < class T > +ID_INLINE idTempArray::idTempArray( idTempArray & other ) { + this->num = other.num; + this->buffer = other.buffer; + other.num = 0; + other.buffer = NULL; +} + +/* +======================== +idTempArray::idTempArray +======================== +*/ +template < class T > +ID_INLINE idTempArray::idTempArray( unsigned int num ) { + this->num = num; + buffer = (T*)Mem_Alloc( num * sizeof( T ), TAG_TEMP ); +} + +/* +======================== +idTempArray::~idTempArray +======================== +*/ +template < class T > +ID_INLINE idTempArray::~idTempArray() { + Mem_Free( buffer ); +} + +/* +=============================================================================== + + Block based allocator for fixed size objects. + + All objects of the 'type' are properly constructed and destructed when reused. + +=============================================================================== +*/ + +#define BLOCK_ALLOC_ALIGNMENT 16 + +// Define this to force all block allocators to act like normal new/delete allocation +// for tool checking. +//#define FORCE_DISCRETE_BLOCK_ALLOCS + +/* +================================================ +idBlockAlloc is a block-based allocator for fixed-size objects. + +All objects are properly constructed and destructed. +================================================ +*/ +template +class idBlockAlloc { +public: + ID_INLINE idBlockAlloc( bool clear = false ); + ID_INLINE ~idBlockAlloc(); + + // returns total size of allocated memory + size_t Allocated() const { return total * sizeof( _type_ ); } + + // returns total size of allocated memory including size of (*this) + size_t Size() const { return sizeof( *this ) + Allocated(); } + + ID_INLINE void Shutdown(); + ID_INLINE void SetFixedBlocks( int numBlocks ); + ID_INLINE void FreeEmptyBlocks(); + + ID_INLINE _type_ * Alloc(); + ID_INLINE void Free( _type_ *element ); + + int GetTotalCount() const { return total; } + int GetAllocCount() const { return active; } + int GetFreeCount() const { return total - active; } + +private: + union element_t { + _type_ * data; // this is a hack to make sure the save game system marks _type_ as saveable + element_t * next; + byte buffer[( CONST_MAX( sizeof( _type_ ), sizeof( element_t * ) ) + ( BLOCK_ALLOC_ALIGNMENT - 1 ) ) & ~( BLOCK_ALLOC_ALIGNMENT - 1 )]; + }; + + class idBlock { + public: + element_t elements[_blockSize_]; + idBlock * next; + element_t * free; // list with free elements in this block (temp used only by FreeEmptyBlocks) + int freeCount; // number of free elements in this block (temp used only by FreeEmptyBlocks) + }; + + idBlock * blocks; + element_t * free; + int total; + int active; + bool allowAllocs; + bool clearAllocs; + + ID_INLINE void AllocNewBlock(); +}; + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::idBlockAlloc +======================== +*/ +template +ID_INLINE idBlockAlloc<_type_,_blockSize_,memTag>::idBlockAlloc( bool clear ) : + blocks( NULL ), + free( NULL ), + total( 0 ), + active( 0 ), + allowAllocs( true ), + clearAllocs( clear ) +{ +} + +/* +======================== +idBlockAlloc<_type_,_blockSize__,align_t>::~idBlockAlloc +======================== +*/ +template +ID_INLINE idBlockAlloc<_type_,_blockSize_,memTag>::~idBlockAlloc() { + Shutdown(); +} + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::Alloc +======================== +*/ +template +ID_INLINE _type_ * idBlockAlloc<_type_,_blockSize_,memTag>::Alloc() { +#ifdef FORCE_DISCRETE_BLOCK_ALLOCS + // for debugging tools + return new _type_; +#else + if ( free == NULL ) { + if ( !allowAllocs ) { + return NULL; + } + AllocNewBlock(); + } + + active++; + element_t * element = free; + free = free->next; + element->next = NULL; + + _type_ * t = (_type_ *) element->buffer; + if ( clearAllocs ) { + memset( t, 0, sizeof( _type_ ) ); + } + new ( t ) _type_; + return t; +#endif +} + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::Free +======================== +*/ +template +ID_INLINE void idBlockAlloc<_type_,_blockSize_,memTag>::Free( _type_ * t ) { +#ifdef FORCE_DISCRETE_BLOCK_ALLOCS + // for debugging tools + delete t; +#else + if ( t == NULL ) { + return; + } + + t->~_type_(); + + element_t * element = (element_t *)( t ); + element->next = free; + free = element; + active--; +#endif +} + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::Shutdown +======================== +*/ +template +ID_INLINE void idBlockAlloc<_type_,_blockSize_,memTag>::Shutdown() { + while( blocks != NULL ) { + idBlock * block = blocks; + blocks = blocks->next; + Mem_Free( block ); + } + blocks = NULL; + free = NULL; + total = active = 0; +} + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::SetFixedBlocks +======================== +*/ +template +ID_INLINE void idBlockAlloc<_type_,_blockSize_,memTag>::SetFixedBlocks( int numBlocks ) { + int currentNumBlocks = 0; + for ( idBlock * block = blocks; block != NULL; block = block->next ) { + currentNumBlocks++; + } + for ( int i = currentNumBlocks; i < numBlocks; i++ ) { + AllocNewBlock(); + } + allowAllocs = false; +} + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::AllocNewBlock +======================== +*/ +template +ID_INLINE void idBlockAlloc<_type_,_blockSize_,memTag>::AllocNewBlock() { + idBlock * block = (idBlock *)Mem_Alloc( sizeof( idBlock ), memTag ); + block->next = blocks; + blocks = block; + for ( int i = 0; i < _blockSize_; i++ ) { + block->elements[i].next = free; + free = &block->elements[i]; + assert( ( ( (UINT_PTR)free ) & ( BLOCK_ALLOC_ALIGNMENT - 1 ) ) == 0 ); + } + total += _blockSize_; +} + +/* +======================== +idBlockAlloc<_type_,_blockSize_,align_t>::FreeEmptyBlocks +======================== +*/ +template +ID_INLINE void idBlockAlloc<_type_,_blockSize_,memTag>::FreeEmptyBlocks() { + // first count how many free elements are in each block + // and build up a free chain per block + for ( idBlock * block = blocks; block != NULL; block = block->next ) { + block->free = NULL; + block->freeCount = 0; + } + for ( element_t * element = free; element != NULL; ) { + element_t * next = element->next; + for ( idBlock * block = blocks; block != NULL; block = block->next ) { + if ( element >= block->elements && element < block->elements + _blockSize_ ) { + element->next = block->free; + block->free = element; + block->freeCount++; + break; + } + } + // if this assert fires, we couldn't find the element in any block + assert( element->next != next ); + element = next; + } + // now free all blocks whose free count == _blockSize_ + idBlock * prevBlock = NULL; + for ( idBlock * block = blocks; block != NULL; ) { + idBlock * next = block->next; + if ( block->freeCount == _blockSize_ ) { + if ( prevBlock == NULL ) { + assert( blocks == block ); + blocks = block->next; + } else { + assert( prevBlock->next == block ); + prevBlock->next = block->next; + } + Mem_Free( block ); + total -= _blockSize_; + } else { + prevBlock = block; + } + block = next; + } + // now rebuild the free chain + free = NULL; + for ( idBlock * block = blocks; block != NULL; block = block->next ) { + for ( element_t * element = block->free; element != NULL; ) { + element_t * next = element->next; + element->next = free; + free = element; + element = next; + } + } +} + +/* +============================================================================== + + Dynamic allocator, simple wrapper for normal allocations which can + be interchanged with idDynamicBlockAlloc. + + No constructor is called for the 'type'. + Allocated blocks are always 16 byte aligned. + +============================================================================== +*/ + +template +class idDynamicAlloc { +public: + idDynamicAlloc(); + ~idDynamicAlloc(); + + void Init(); + void Shutdown(); + void SetFixedBlocks( int numBlocks ) {} + void SetLockMemory( bool lock ) {} + void FreeEmptyBaseBlocks() {} + + type * Alloc( const int num ); + type * Resize( type *ptr, const int num ); + void Free( type *ptr ); + const char * CheckMemory( const type *ptr ) const; + + int GetNumBaseBlocks() const { return 0; } + int GetBaseBlockMemory() const { return 0; } + int GetNumUsedBlocks() const { return numUsedBlocks; } + int GetUsedBlockMemory() const { return usedBlockMemory; } + int GetNumFreeBlocks() const { return 0; } + int GetFreeBlockMemory() const { return 0; } + int GetNumEmptyBaseBlocks() const { return 0; } + +private: + int numUsedBlocks; // number of used blocks + int usedBlockMemory; // total memory in used blocks + + int numAllocs; + int numResizes; + int numFrees; + + void Clear(); +}; + +template +idDynamicAlloc::idDynamicAlloc() { + Clear(); +} + +template +idDynamicAlloc::~idDynamicAlloc() { + Shutdown(); +} + +template +void idDynamicAlloc::Init() { +} + +template +void idDynamicAlloc::Shutdown() { + Clear(); +} + +template +type *idDynamicAlloc::Alloc( const int num ) { + numAllocs++; + if ( num <= 0 ) { + return NULL; + } + numUsedBlocks++; + usedBlockMemory += num * sizeof( type ); + return Mem_Alloc16( num * sizeof( type ), TAG_BLOCKALLOC ); +} + +template +type *idDynamicAlloc::Resize( type *ptr, const int num ) { + + numResizes++; + + if ( ptr == NULL ) { + return Alloc( num ); + } + + if ( num <= 0 ) { + Free( ptr ); + return NULL; + } + + assert( 0 ); + return ptr; +} + +template +void idDynamicAlloc::Free( type *ptr ) { + numFrees++; + if ( ptr == NULL ) { + return; + } + Mem_Free16( ptr ); +} + +template +const char *idDynamicAlloc::CheckMemory( const type *ptr ) const { + return NULL; +} + +template +void idDynamicAlloc::Clear() { + numUsedBlocks = 0; + usedBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; +} + + +/* +============================================================================== + + Fast dynamic block allocator. + + No constructor is called for the 'type'. + Allocated blocks are always 16 byte aligned. + +============================================================================== +*/ + +#include "containers/BTree.h" + +//#define DYNAMIC_BLOCK_ALLOC_CHECK + +template +class idDynamicBlock { +public: + type * GetMemory() const { return (type *)( ( (byte *) this ) + sizeof( idDynamicBlock ) ); } + int GetSize() const { return abs( size ); } + void SetSize( int s, bool isBaseBlock ) { size = isBaseBlock ? -s : s; } + bool IsBaseBlock() const { return ( size < 0 ); } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + int id[3]; + void * allocator; +#endif + + int size; // size in bytes of the block + idDynamicBlock * prev; // previous memory block + idDynamicBlock * next; // next memory block + idBTreeNode,int> *node; // node in the B-Tree with free blocks +}; + +template +class idDynamicBlockAlloc { +public: + idDynamicBlockAlloc(); + ~idDynamicBlockAlloc(); + + void Init(); + void Shutdown(); + void SetFixedBlocks( int numBlocks ); + void SetLockMemory( bool lock ); + void FreeEmptyBaseBlocks(); + + type * Alloc( const int num ); + type * Resize( type *ptr, const int num ); + void Free( type *ptr ); + const char * CheckMemory( const type *ptr ) const; + + int GetNumBaseBlocks() const { return numBaseBlocks; } + int GetBaseBlockMemory() const { return baseBlockMemory; } + int GetNumUsedBlocks() const { return numUsedBlocks; } + int GetUsedBlockMemory() const { return usedBlockMemory; } + int GetNumFreeBlocks() const { return numFreeBlocks; } + int GetFreeBlockMemory() const { return freeBlockMemory; } + int GetNumEmptyBaseBlocks() const; + +private: + idDynamicBlock * firstBlock; // first block in list in order of increasing address + idDynamicBlock * lastBlock; // last block in list in order of increasing address + idBTree,int,4>freeTree; // B-Tree with free memory blocks + bool allowAllocs; // allow base block allocations + bool lockMemory; // lock memory so it cannot get swapped out + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + int blockId[3]; +#endif + + int numBaseBlocks; // number of base blocks + int baseBlockMemory; // total memory in base blocks + int numUsedBlocks; // number of used blocks + int usedBlockMemory; // total memory in used blocks + int numFreeBlocks; // number of free blocks + int freeBlockMemory; // total memory in free blocks + + int numAllocs; + int numResizes; + int numFrees; + + memTag_t tag; + + void Clear(); + idDynamicBlock * AllocInternal( const int num ); + idDynamicBlock * ResizeInternal( idDynamicBlock *block, const int num ); + void FreeInternal( idDynamicBlock *block ); + void LinkFreeInternal( idDynamicBlock *block ); + void UnlinkFreeInternal( idDynamicBlock *block ); + void CheckMemory() const; +}; + +template +idDynamicBlockAlloc::idDynamicBlockAlloc() { + tag = _tag_; + Clear(); +} + +template +idDynamicBlockAlloc::~idDynamicBlockAlloc() { + Shutdown(); +} + +template +void idDynamicBlockAlloc::Init() { + freeTree.Init(); +} + +template +void idDynamicBlockAlloc::Shutdown() { + idDynamicBlock *block; + + for ( block = firstBlock; block != NULL; block = block->next ) { + if ( block->node == NULL ) { + FreeInternal( block ); + } + } + + for ( block = firstBlock; block != NULL; block = firstBlock ) { + firstBlock = block->next; + assert( block->IsBaseBlock() ); + if ( lockMemory ) { + //idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); + } + Mem_Free16( block ); + } + + freeTree.Shutdown(); + + Clear(); +} + +template +void idDynamicBlockAlloc::SetFixedBlocks( int numBlocks ) { + idDynamicBlock *block; + + for ( int i = numBaseBlocks; i < numBlocks; i++ ) { + block = ( idDynamicBlock * ) Mem_Alloc16( baseBlockSize, _tag_ ); + if ( lockMemory ) { + //idLib::sys->LockMemory( block, baseBlockSize ); + } +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + memcpy( block->id, blockId, sizeof( block->id ) ); + block->allocator = (void*)this; +#endif + block->SetSize( baseBlockSize - (int)sizeof( idDynamicBlock ), true ); + block->next = NULL; + block->prev = lastBlock; + if ( lastBlock ) { + lastBlock->next = block; + } else { + firstBlock = block; + } + lastBlock = block; + block->node = NULL; + + FreeInternal( block ); + + numBaseBlocks++; + baseBlockMemory += baseBlockSize; + } + + allowAllocs = false; +} + +template +void idDynamicBlockAlloc::SetLockMemory( bool lock ) { + lockMemory = lock; +} + +template +void idDynamicBlockAlloc::FreeEmptyBaseBlocks() { + idDynamicBlock *block, *next; + + for ( block = firstBlock; block != NULL; block = next ) { + next = block->next; + + if ( block->IsBaseBlock() && block->node != NULL && ( next == NULL || next->IsBaseBlock() ) ) { + UnlinkFreeInternal( block ); + if ( block->prev ) { + block->prev->next = block->next; + } else { + firstBlock = block->next; + } + if ( block->next ) { + block->next->prev = block->prev; + } else { + lastBlock = block->prev; + } + if ( lockMemory ) { + //idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); + } + numBaseBlocks--; + baseBlockMemory -= block->GetSize() + (int)sizeof( idDynamicBlock ); + Mem_Free16( block ); + } + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif +} + +template +int idDynamicBlockAlloc::GetNumEmptyBaseBlocks() const { + int numEmptyBaseBlocks; + idDynamicBlock *block; + + numEmptyBaseBlocks = 0; + for ( block = firstBlock; block != NULL; block = block->next ) { + if ( block->IsBaseBlock() && block->node != NULL && ( block->next == NULL || block->next->IsBaseBlock() ) ) { + numEmptyBaseBlocks++; + } + } + return numEmptyBaseBlocks; +} + +template +type *idDynamicBlockAlloc::Alloc( const int num ) { + idDynamicBlock *block; + + numAllocs++; + + if ( num <= 0 ) { + return NULL; + } + + block = AllocInternal( num ); + if ( block == NULL ) { + return NULL; + } + block = ResizeInternal( block, num ); + if ( block == NULL ) { + return NULL; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif + + numUsedBlocks++; + usedBlockMemory += block->GetSize(); + + return block->GetMemory(); +} + +template +type *idDynamicBlockAlloc::Resize( type *ptr, const int num ) { + + numResizes++; + + if ( ptr == NULL ) { + return Alloc( num ); + } + + if ( num <= 0 ) { + Free( ptr ); + return NULL; + } + + idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + usedBlockMemory -= block->GetSize(); + + block = ResizeInternal( block, num ); + if ( block == NULL ) { + return NULL; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif + + usedBlockMemory += block->GetSize(); + + return block->GetMemory(); +} + +template +void idDynamicBlockAlloc::Free( type *ptr ) { + + numFrees++; + + if ( ptr == NULL ) { + return; + } + + idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + numUsedBlocks--; + usedBlockMemory -= block->GetSize(); + + FreeInternal( block ); + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif +} + +template +const char *idDynamicBlockAlloc::CheckMemory( const type *ptr ) const { + idDynamicBlock *block; + + if ( ptr == NULL ) { + return NULL; + } + + block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + if ( block->node != NULL ) { + return "memory has been freed"; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + if ( block->id[0] != 0x11111111 || block->id[1] != 0x22222222 || block->id[2] != 0x33333333 ) { + return "memory has invalid id"; + } + if ( block->allocator != (void*)this ) { + return "memory was allocated with different allocator"; + } +#endif + + /* base blocks can be larger than baseBlockSize which can cause this code to fail + idDynamicBlock *base; + for ( base = firstBlock; base != NULL; base = base->next ) { + if ( base->IsBaseBlock() ) { + if ( ((int)block) >= ((int)base) && ((int)block) < ((int)base) + baseBlockSize ) { + break; + } + } + } + if ( base == NULL ) { + return "no base block found for memory"; + } + */ + + return NULL; +} + +template +void idDynamicBlockAlloc::Clear() { + firstBlock = lastBlock = NULL; + allowAllocs = true; + lockMemory = false; + numBaseBlocks = 0; + baseBlockMemory = 0; + numUsedBlocks = 0; + usedBlockMemory = 0; + numFreeBlocks = 0; + freeBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + blockId[0] = 0x11111111; + blockId[1] = 0x22222222; + blockId[2] = 0x33333333; +#endif +} + +template +idDynamicBlock *idDynamicBlockAlloc::AllocInternal( const int num ) { + idDynamicBlock *block; + int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; + + block = freeTree.FindSmallestLargerEqual( alignedBytes ); + if ( block != NULL ) { + UnlinkFreeInternal( block ); + } else if ( allowAllocs ) { + int allocSize = Max( baseBlockSize, alignedBytes + (int)sizeof( idDynamicBlock ) ); + block = ( idDynamicBlock * ) Mem_Alloc16( allocSize, _tag_ ); + if ( lockMemory ) { + //idLib::sys->LockMemory( block, baseBlockSize ); + } +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + memcpy( block->id, blockId, sizeof( block->id ) ); + block->allocator = (void*)this; +#endif + block->SetSize( allocSize - (int)sizeof( idDynamicBlock ), true ); + block->next = NULL; + block->prev = lastBlock; + if ( lastBlock ) { + lastBlock->next = block; + } else { + firstBlock = block; + } + lastBlock = block; + block->node = NULL; + + numBaseBlocks++; + baseBlockMemory += allocSize; + } + + return block; +} + +template +idDynamicBlock *idDynamicBlockAlloc::ResizeInternal( idDynamicBlock *block, const int num ) { + int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + assert( block->id[0] == 0x11111111 && block->id[1] == 0x22222222 && block->id[2] == 0x33333333 && block->allocator == (void*)this ); +#endif + + // if the new size is larger + if ( alignedBytes > block->GetSize() ) { + + idDynamicBlock *nextBlock = block->next; + + // try to annexate the next block if it's free + if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL && + block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize() >= alignedBytes ) { + + UnlinkFreeInternal( nextBlock ); + block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); + block->next = nextBlock->next; + if ( nextBlock->next ) { + nextBlock->next->prev = block; + } else { + lastBlock = block; + } + } else { + // allocate a new block and copy + idDynamicBlock *oldBlock = block; + block = AllocInternal( num ); + if ( block == NULL ) { + return NULL; + } + memcpy( block->GetMemory(), oldBlock->GetMemory(), oldBlock->GetSize() ); + FreeInternal( oldBlock ); + } + } + + // if the unused space at the end of this block is large enough to hold a block with at least one element + if ( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ) < Max( minBlockSize, (int)sizeof( type ) ) ) { + return block; + } + + idDynamicBlock *newBlock; + + newBlock = ( idDynamicBlock * ) ( ( (byte *) block ) + (int)sizeof( idDynamicBlock ) + alignedBytes ); +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + memcpy( newBlock->id, blockId, sizeof( newBlock->id ) ); + newBlock->allocator = (void*)this; +#endif + newBlock->SetSize( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ), false ); + newBlock->next = block->next; + newBlock->prev = block; + if ( newBlock->next ) { + newBlock->next->prev = newBlock; + } else { + lastBlock = newBlock; + } + newBlock->node = NULL; + block->next = newBlock; + block->SetSize( alignedBytes, block->IsBaseBlock() ); + + FreeInternal( newBlock ); + + return block; +} + +template +void idDynamicBlockAlloc::FreeInternal( idDynamicBlock *block ) { + + assert( block->node == NULL ); + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + assert( block->id[0] == 0x11111111 && block->id[1] == 0x22222222 && block->id[2] == 0x33333333 && block->allocator == (void*)this ); +#endif + + // try to merge with a next free block + idDynamicBlock *nextBlock = block->next; + if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL ) { + UnlinkFreeInternal( nextBlock ); + block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); + block->next = nextBlock->next; + if ( nextBlock->next ) { + nextBlock->next->prev = block; + } else { + lastBlock = block; + } + } + + // try to merge with a previous free block + idDynamicBlock *prevBlock = block->prev; + if ( prevBlock && !block->IsBaseBlock() && prevBlock->node != NULL ) { + UnlinkFreeInternal( prevBlock ); + prevBlock->SetSize( prevBlock->GetSize() + (int)sizeof( idDynamicBlock ) + block->GetSize(), prevBlock->IsBaseBlock() ); + prevBlock->next = block->next; + if ( block->next ) { + block->next->prev = prevBlock; + } else { + lastBlock = prevBlock; + } + LinkFreeInternal( prevBlock ); + } else { + LinkFreeInternal( block ); + } +} + +template +ID_INLINE void idDynamicBlockAlloc::LinkFreeInternal( idDynamicBlock *block ) { + block->node = freeTree.Add( block, block->GetSize() ); + numFreeBlocks++; + freeBlockMemory += block->GetSize(); +} + +template +ID_INLINE void idDynamicBlockAlloc::UnlinkFreeInternal( idDynamicBlock *block ) { + freeTree.Remove( block->node ); + block->node = NULL; + numFreeBlocks--; + freeBlockMemory -= block->GetSize(); +} + +template +void idDynamicBlockAlloc::CheckMemory() const { + idDynamicBlock *block; + + for ( block = firstBlock; block != NULL; block = block->next ) { + // make sure the block is properly linked + if ( block->prev == NULL ) { + assert( firstBlock == block ); + } else { + assert( block->prev->next == block ); + } + if ( block->next == NULL ) { + assert( lastBlock == block ); + } else { + assert( block->next->prev == block ); + } + } +} + +#endif /* !__HEAP_H__ */ diff --git a/neo/idlib/LangDict.cpp b/neo/idlib/LangDict.cpp new file mode 100644 index 00000000..8faaeeab --- /dev/null +++ b/neo/idlib/LangDict.cpp @@ -0,0 +1,600 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "precompiled.h" + +// This is the default language dict that the entire system uses, but you can instantiate your own idLangDict classes to manipulate a language dictionary in a tool +idLangDict idLocalization::languageDict; + +idCVar lang_maskLocalizedStrings( "lang_maskLocalizedStrings", "0", CVAR_BOOL, "Masks all localized strings to help debugging. When set will replace strings with an equal length of W's and ending in an X. Note: The masking occurs at string table load time." ); + +/* +======================== +idLocalization::ClearDictionary +======================== +*/ +void idLocalization::ClearDictionary() { + languageDict.Clear(); +} + +/* +======================== +idLocalization::LoadDictionary +======================== +*/ +bool idLocalization::LoadDictionary( const byte * data, int dataLen, const char * fileName ) { + return languageDict.Load( data, dataLen, fileName ); +} + +/* +======================== +idLocalization::GetString +======================== +*/ +const char * idLocalization::GetString( const char * inString ) { + return languageDict.GetString( inString ); +} + +/* +======================== +idLocalization::FindString +======================== +*/ +const char * idLocalization::FindString( const char * inString ) { + return languageDict.FindString( inString ); +} + +/* +======================== +idLocalization::VerifyUTF8 +======================== +*/ +utf8Encoding_t idLocalization::VerifyUTF8( const uint8 * buffer, const int bufferLen, const char * name ) { + utf8Encoding_t encoding; + idStr::IsValidUTF8( buffer, bufferLen, encoding ); + if ( encoding == UTF8_INVALID ) { + idLib::FatalError( "Language file %s is not valid UTF-8 or plain ASCII.", name ); + } else if ( encoding == UTF8_INVALID_BOM ) { + idLib::FatalError( "Language file %s is marked as UTF-8 but has invalid encoding.", name ); + } else if ( encoding == UTF8_ENCODED_NO_BOM ) { + idLib::FatalError( "Language file %s has no byte order marker. Fix this or roll back to a version that has the marker.", name ); + } else if ( encoding != UTF8_ENCODED_BOM && encoding != UTF8_PURE_ASCII ) { + idLib::FatalError( "Language file %s has unknown utf8Encoding_t.", name ); + } + return encoding; +} + +// string entries can refer to other string entries, +// recursing up to this many times before we decided someone did something stupid +const char * idLangDict::KEY_PREFIX = "#str_"; // all keys should be prefixed with this for redirection to work +const int idLangDict::KEY_PREFIX_LEN = idStr::Length( KEY_PREFIX ); + +/* +======================== +idLangDict::idLangDict +======================== +*/ +idLangDict::idLangDict() : keyIndex( 4096, 4096 ) { +} + +/* +======================== +idLangDict::~idLangDict +======================== +*/ +idLangDict::~idLangDict() { + Clear(); +} + +/* +======================== +idLangDict::Clear +======================== +*/ +void idLangDict::Clear() { + //mem.PushHeap(); + for ( int i = 0; i < keyVals.Num(); i++ ) { + if ( keyVals[i].value == NULL ) { + continue; + } + blockAlloc.Free( keyVals[i].value ); + keyVals[i].value = NULL; + } + //mem.PopHeap(); +} + +/* +======================== +idLangDict::Load +======================== +*/ +bool idLangDict::Load( const byte * buffer, const int bufferLen, const char *name ) { + + if ( buffer == NULL || bufferLen <= 0 ) { + // let whoever called us deal with the failure (so sys_lang can be reset) + return false; + } + + idLib::Printf( "Reading %s", name ); + + bool utf8 = false; + + // in all but retail builds, ensure that the byte-order mark is NOT MISSING so that + // we can avoid debugging UTF-8 code +#ifndef ID_RETAIL + utf8Encoding_t encoding = idLocalization::VerifyUTF8( buffer, bufferLen, name ); + if ( encoding == UTF8_ENCODED_BOM ) { + utf8 = true; + } else if ( encoding == UTF8_PURE_ASCII ) { + utf8 = false; + } else { + assert( false ); // this should have been handled in VerifyUTF8 with a FatalError + return false; + } +#else + // in release we just check the BOM so we're not scanning the lang file twice on startup + if ( bufferLen > 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF ) { + utf8 = true; + } +#endif + + if ( utf8 ) { + idLib::Printf( " as UTF-8\n" ); + } else { + idLib::Printf( " as ASCII\n" ); + } + + idStr tempKey; + idStr tempVal; + + int line = 0; + int numStrings = 0; + + int i = 0; + while ( i < bufferLen ) { + uint32 c = buffer[i++]; + if ( c == '/' ) { // comment, read until new line + while ( i < bufferLen ) { + c = buffer[i++]; + if ( c == '\n' ) { + line++; + break; + } + } + } else if ( c == '}' ) { + break; + } else if ( c == '\n' ) { + line++; + } else if ( c == '\"' ) { + int keyStart = i; + int keyEnd = -1; + while ( i < bufferLen ) { + c = buffer[i++]; + if ( c == '\"' ) { + keyEnd = i - 1; + break; + } + } + if ( keyEnd < keyStart ) { + idLib::FatalError( "%s File ended while reading key at line %d", name, line ); + } + tempKey.CopyRange( (char *)buffer, keyStart, keyEnd ); + + int valStart = -1; + while ( i < bufferLen ) { + c = buffer[i++]; + if ( c == '\"' ) { + valStart = i; + break; + } + } + if ( valStart < 0 ) { + idLib::FatalError( "%s File ended while reading value at line %d", name, line ); + } + int valEnd = -1; + tempVal.CapLength( 0 ); + while ( i < bufferLen ) { + c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++]; + if ( !utf8 && c >= 0x80 ) { + // this is a serious error and we must check this to avoid accidentally shipping a file where someone squased UTF-8 encodings + idLib::FatalError( "Language file %s is supposed to be plain ASCII, but has byte values > 127!", name ); + } + if ( c == '\"' ) { + valEnd = i - 1; + continue; + } + if ( c == '\n' ) { + line++; + break; + } + if ( c == '\r' ) { + continue; + } + if ( c == '\\' ) { + c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++]; + if ( c == 'n' ) { + c = '\n'; + } else if ( c == 't' ) { + c = '\t'; + } else if ( c == '\"' ) { + c = '\"'; + } else if ( c == '\\' ) { + c = '\\'; + } else { + idLib::Warning( "Unknown escape sequence %x at line %d", c, line ); + } + } + tempVal.AppendUTF8Char( c ); + } + if ( valEnd < valStart ) { + idLib::FatalError( "%s File ended while reading value at line %d", name, line ); + } + if ( lang_maskLocalizedStrings.GetBool() && tempVal.Length() > 0 && tempKey.Find( "#font_" ) == -1 ) { + int len = tempVal.Length(); + if ( len > 0 ) { + tempVal.Fill( 'W', len - 1 ); + } else { + tempVal.Empty(); + } + tempVal.Append( 'X' ); + } + AddKeyVal( tempKey, tempVal ); + numStrings++; + } + } + + idLib::Printf( "%i strings read\n", numStrings ); + + // get rid of any waste due to geometric list growth + //mem.PushHeap(); + keyVals.Condense(); + //mem.PopHeap(); + + return true; +} + +/* +======================== +idLangDict::Save +======================== +*/ +bool idLangDict::Save( const char * fileName ) { + idFile * outFile = fileSystem->OpenFileWrite( fileName ); + if ( outFile == NULL ) { + idLib::Warning( "Error saving: %s", fileName ); + return false; + } + byte bof[3] = { 0xEF, 0xBB, 0xBF }; + outFile->Write( bof, 3 ); + outFile->WriteFloatString( "// string table\n//\n\n{\n" ); + for ( int j = 0; j < keyVals.Num(); j++ ) { + const idLangKeyValue & kvp = keyVals[j]; + if ( kvp.value == NULL ) { + continue; + } + outFile->WriteFloatString( "\t\"%s\"\t\"", kvp.key ); + for ( int k = 0; kvp.value[k] != 0; k++ ) { + char ch = kvp.value[k]; + if ( ch == '\t' ) { + outFile->Write( "\\t", 2 ); + } else if ( ch == '\n' || ch == '\r' ) { + outFile->Write( "\\n", 2 ); + } else if ( ch == '"' ) { + outFile->Write( "\\\"", 2 ); + } else if ( ch == '\\' ) { + outFile->Write( "\\\\", 2 ); + } else { + outFile->Write( &ch, 1 ); + } + } + outFile->WriteFloatString( "\"\n" ); + } + outFile->WriteFloatString( "\n}\n" ); + delete outFile; + return true; +} + +/* +======================== +idLangDict::GetString +======================== +*/ +const char * idLangDict::GetString( const char * str ) const { + const char * localized = FindString( str ); + if ( localized == NULL ) { + return str; + } + return localized; +} + +/* +======================== +idLangDict::FindStringIndex +======================== +*/ +int idLangDict::FindStringIndex( const char * str ) const { + if ( str == NULL ) { + return -1; + } + int hash = idStr::IHash( str ); + for ( int i = keyIndex.GetFirst( hash ); i >= 0; i = keyIndex.GetNext( i ) ) { + if ( idStr::Icmp( str, keyVals[i].key ) == 0 ) { + return i; + } + } + return -1; +} + +/* +======================== +idLangDict::FindString_r +======================== +*/ +const char * idLangDict::FindString_r( const char * str, int & depth ) const { + depth++; + if ( depth > MAX_REDIRECTION_DEPTH ) { + // This isn't an error because we assume the error will be obvious somewhere in a GUI or something, + // and the whole point of tracking the depth is to avoid a crash. + idLib::Warning( "String '%s', indirection depth > %d", str, MAX_REDIRECTION_DEPTH ); + return NULL; + } + + if ( str == NULL || str[0] == '\0' ) { + return NULL; + } + + int index = FindStringIndex( str ); + if ( index < 0 ) { + return NULL; + } + const char * value = keyVals[index].value; + if ( value == NULL ) { + return NULL; + } + if ( IsStringId( value ) ) { + // this string is re-directed to another entry + return FindString_r( value, depth ); + } + return value; +} + +/* +======================== +idLangDict::FindString +======================== +*/ +const char * idLangDict::FindString( const char * str ) const { + int depth = 0; + return FindString_r( str, depth ); +} + +/* +======================== +idLangDict::DeleteString +======================== +*/ +bool idLangDict::DeleteString( const char * key ) { + return DeleteString( FindStringIndex( key ) ); +} + +/* +======================== +idLangDict::DeleteString +======================== +*/ +bool idLangDict::DeleteString( const int idx ) { + if ( idx < 0 || idx >= keyVals.Num() ) { + return false; + } + + //mem.PushHeap(); + blockAlloc.Free( keyVals[idx].value ); + keyVals[idx].value = NULL; + //mem.PopHeap(); + + return true; +} + +/* +======================== +idLangDict::RenameStringKey +======================== +*/ +bool idLangDict::RenameStringKey( const char * oldKey, const char * newKey ) { + int index = FindStringIndex( oldKey ); + if ( index < 0 ) { + return false; + } + //mem.PushHeap(); + blockAlloc.Free( keyVals[index].key ); + int newKeyLen = idStr::Length( newKey ); + keyVals[index].key = blockAlloc.Alloc( newKeyLen + 1 ); + idStr::Copynz( keyVals[index].key, newKey, newKeyLen + 1 ); + int oldHash = idStr::IHash( oldKey ); + int newHash = idStr::IHash( newKey ); + if ( oldHash != newHash ) { + keyIndex.Remove( oldHash, index ); + keyIndex.Add( newHash, index ); + } + //mem.PopHeap(); + + return true; +} + +/* +======================== +idLangDict::SetString +======================== +*/ +bool idLangDict::SetString( const char * key, const char * val ) { + int index = FindStringIndex( key ); + if ( index < 0 ) { + return false; + } + //mem.PushHeap(); + if ( keyVals[index].value != NULL ) { + blockAlloc.Free( keyVals[index].value ); + } + int valLen = idStr::Length( val ); + keyVals[index].value = blockAlloc.Alloc( valLen + 1 ); + idStr::Copynz( keyVals[index].value, val, valLen + 1 ); + //mem.PopHeap(); + return true; +} + +/* +======================== +idLangDict::AddKeyVal +======================== +*/ +void idLangDict::AddKeyVal( const char * key, const char * val ) { + if ( SetString( key, val ) ) { + return; + } + //mem.PushHeap(); + int keyLen = idStr::Length( key ); + char * k = blockAlloc.Alloc( keyLen + 1 ); + idStr::Copynz( k, key, keyLen + 1 ); + char * v = NULL; + if ( val != NULL ) { + int valLen = idStr::Length( val ); + v = blockAlloc.Alloc( valLen + 1 ); + idStr::Copynz( v, val, valLen + 1 ); + } + int index = keyVals.Append( idLangKeyValue( k, v ) ); + int hash = idStr::IHash( key ); + keyIndex.Add( hash, index ); + //mem.PopHeap(); +} + +/* +======================== +idLangDict::AddString +======================== +*/ +const char * idLangDict::AddString( const char * val ) { + int i = Sys_Milliseconds(); + idStr key; + sprintf( key, "#str_%06d", ( i++ % 1000000 ) ); + while ( FindStringIndex( key ) > 0 ) { + sprintf( key, "#str_%06d", ( i++ % 1000000 ) ); + } + AddKeyVal( key, val ); + int index = FindStringIndex( key ); + return keyVals[index].key; +} + +/* +======================== +idLangDict::GetNumKeyVals +======================== +*/ +int idLangDict::GetNumKeyVals() const { + return keyVals.Num(); +} + +/* +======================== +idLangDict::GetKeyVal +======================== +*/ +const idLangKeyValue * idLangDict::GetKeyVal( int i ) const { + return &keyVals[i]; +} + +/* +======================== +idLangDict::IsStringId +======================== +*/ +bool idLangDict::IsStringId( const char * str ) { + return idStr::Icmpn( str, KEY_PREFIX, KEY_PREFIX_LEN ) == 0; +} + +/* +======================== +idLangDict::GetLocalizedString +======================== +*/ +const char * idLangDict::GetLocalizedString( const idStrId & strId ) const { + if ( strId.GetIndex() >= 0 && strId.GetIndex() < keyVals.Num() ) { + if ( keyVals[ strId.GetIndex() ].value == NULL ) { + return keyVals[ strId.GetIndex() ].key; + } else { + return keyVals[ strId.GetIndex() ].value; + } + } + return ""; +} + +/* +================================================================================================ +idStrId +================================================================================================ +*/ + +/* +======================== +idStrId::Set +======================== +*/ +void idStrId::Set( const char * key ) { + if ( key == NULL || key[0] == 0 ) { + index = -1; + } else { + index = idLocalization::languageDict.FindStringIndex( key ); + if ( index < 0 ) { + // don't allow setting of string ID's to an unknown ID... this should only be allowed from + // the string table tool because additions from anywhere else are not guaranteed to be + // saved to the .lang file. + idLib::Warning( "Attempted to set unknown string ID '%s'", key ); + } + } +} + +/* +======================== +idStrId::GetKey +======================== +*/ +const char * idStrId::GetKey() const { + if ( index >= 0 && index < idLocalization::languageDict.keyVals.Num() ) { + return idLocalization::languageDict.keyVals[index].key; + } + return ""; +} + +/* +======================== +idStrId::GetLocalizedString +======================== +*/ +const char * idStrId::GetLocalizedString() const { + return idLocalization::languageDict.GetLocalizedString( *this ); +} + diff --git a/neo/idlib/LangDict.h b/neo/idlib/LangDict.h new file mode 100644 index 00000000..b4ab2749 --- /dev/null +++ b/neo/idlib/LangDict.h @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __LANGDICT_H__ +#define __LANGDICT_H__ + +class idLangKeyValue { +public: + idLangKeyValue() : key( NULL ), value( NULL ) { } + idLangKeyValue( char * k, char * v ) : key ( k ), value( v ) { } + char * key; + char * value; +}; + +class idStrId; + +/* +================================================ +idLangDict is a simple Dictionary used specifically for the +LocalizedStringTables. +================================================ +*/ +class idLangDict { +public: + static const char * KEY_PREFIX; + static const int KEY_PREFIX_LEN; + static const int MAX_REDIRECTION_DEPTH = 2; + + idLangDict(); + ~idLangDict(); + + void Clear(); + bool Load( const byte * buffer, const int bufferLen, const char * name ); + bool Save( const char * fileName ); + + const char * GetString( const char * str ) const; // returns str if string not found + const char * FindString( const char * str ) const; // returns NULL if string not found + + const char * AddString( const char * str ); // returns a randomly generated key + bool DeleteString( const char * key ); // returns false if the key doesn't exist + bool RenameStringKey( const char * oldKey, const char * newKey ); + + bool SetString( const char * key, const char * val ); // Returns false if the key doesn't exist + void AddKeyVal( const char * key, const char * val ); // Like SetString, but adds it if it doesn't already exist + + int GetNumKeyVals() const; + const idLangKeyValue * GetKeyVal( int i ) const; + bool DeleteString( const int idx ); + + const char * GetLocalizedString( const idStrId & strId ) const; + + // returns true if the string starts with the KEY_PREFIX string + static bool IsStringId( const char * str ); + +private: + idDynamicBlockAlloc< char, 100 * 1024, 16 > blockAlloc; + idList< idLangKeyValue > keyVals; + idHashIndex keyIndex; + +private: + int FindStringIndex( const char * str ) const; + const char * FindString_r( const char * str, int & depth ) const; + + friend class idStrId; +}; + +/* +================================================ +idLocalization +================================================ +*/ +class idLocalization { +public: + static const char * GetString( const char * inString ); // returns inString if string not found + static const char * FindString( const char * inString ); // Returns NULL if string not found + + static void ClearDictionary(); + static bool LoadDictionary( const byte * buffer, const int bufferLen, const char * name ); + + // This is only here for tools, normal code should only ever call GetString + static idLangDict & GetDictionary() { return languageDict; } + + static utf8Encoding_t VerifyUTF8( const uint8 * buffer, const int bufferLen, const char * name ); + +private: + static idLangDict languageDict; + friend class idStrId; +}; + +/* +================================================ +idStrId represents a localized String as a String ID. +================================================ +*/ +class idStrId { +public: + idStrId() : index( -1 ) { } + idStrId( const idStrId & other ) : index( other.index ) { } + + explicit idStrId( int i ) : index( i ) { } + explicit idStrId( const char * key ) { Set( key ); } + explicit idStrId( const idStr & key ) { Set( key ); } + + void operator=( const char * key ) { Set( key ); } + void operator=( const idStr & key ) { Set( key ); } + void operator=( const idStrId & other ) { index = other.index; } + + bool operator==( const idStrId & other ) const { return index == other.index; } + bool operator!=( const idStrId & other ) const { return index != other.index; } + + void Set( const char * key ); + + void Empty() { index = -1; } + bool IsEmpty() const { return index < 0; } + + const char * GetKey() const; + const char * GetLocalizedString() const; + + int GetIndex() const { return index; } + void SetIndex( int i ) { index = i; } + +private: + int index; // Index into the language dictionary +}; + +#endif // !__LANGDICT_H__ diff --git a/neo/idlib/Lexer.cpp b/neo/idlib/Lexer.cpp new file mode 100644 index 00000000..dce36cce --- /dev/null +++ b/neo/idlib/Lexer.cpp @@ -0,0 +1,1918 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = { + //binary operators + {">>=",P_RSHIFT_ASSIGN}, + {"<<=",P_LSHIFT_ASSIGN}, + // + {"...",P_PARMS}, + //define merge operator + {"##",P_PRECOMPMERGE}, // pre-compiler + //logic operators + {"&&",P_LOGIC_AND}, // pre-compiler + {"||",P_LOGIC_OR}, // pre-compiler + {">=",P_LOGIC_GEQ}, // pre-compiler + {"<=",P_LOGIC_LEQ}, // pre-compiler + {"==",P_LOGIC_EQ}, // pre-compiler + {"!=",P_LOGIC_UNEQ}, // pre-compiler + //arithmatic operators + {"*=",P_MUL_ASSIGN}, + {"/=",P_DIV_ASSIGN}, + {"%=",P_MOD_ASSIGN}, + {"+=",P_ADD_ASSIGN}, + {"-=",P_SUB_ASSIGN}, + {"++",P_INC}, + {"--",P_DEC}, + //binary operators + {"&=",P_BIN_AND_ASSIGN}, + {"|=",P_BIN_OR_ASSIGN}, + {"^=",P_BIN_XOR_ASSIGN}, + {">>",P_RSHIFT}, // pre-compiler + {"<<",P_LSHIFT}, // pre-compiler + //reference operators + {"->",P_POINTERREF}, + //C++ + {"::",P_CPP1}, + {".*",P_CPP2}, + //arithmatic operators + {"*",P_MUL}, // pre-compiler + {"/",P_DIV}, // pre-compiler + {"%",P_MOD}, // pre-compiler + {"+",P_ADD}, // pre-compiler + {"-",P_SUB}, // pre-compiler + {"=",P_ASSIGN}, + //binary operators + {"&",P_BIN_AND}, // pre-compiler + {"|",P_BIN_OR}, // pre-compiler + {"^",P_BIN_XOR}, // pre-compiler + {"~",P_BIN_NOT}, // pre-compiler + //logic operators + {"!",P_LOGIC_NOT}, // pre-compiler + {">",P_LOGIC_GREATER}, // pre-compiler + {"<",P_LOGIC_LESS}, // pre-compiler + //reference operator + {".",P_REF}, + //seperators + {",",P_COMMA}, // pre-compiler + {";",P_SEMICOLON}, + //label indication + {":",P_COLON}, // pre-compiler + //if statement + {"?",P_QUESTIONMARK}, // pre-compiler + //embracements + {"(",P_PARENTHESESOPEN}, // pre-compiler + {")",P_PARENTHESESCLOSE}, // pre-compiler + {"{",P_BRACEOPEN}, // pre-compiler + {"}",P_BRACECLOSE}, // pre-compiler + {"[",P_SQBRACKETOPEN}, + {"]",P_SQBRACKETCLOSE}, + // + {"\\",P_BACKSLASH}, + //precompiler operator + {"#",P_PRECOMP}, // pre-compiler + {"$",P_DOLLAR}, + {NULL, 0} +}; + +int default_punctuationtable[256]; +int default_nextpunctuation[sizeof(default_punctuations) / sizeof(punctuation_t)]; +int default_setup; + +char idLexer::baseFolder[ 256 ]; + +/* +================ +idLexer::CreatePunctuationTable +================ +*/ +void idLexer::CreatePunctuationTable( const punctuation_t *punctuations ) { + int i, n, lastp; + const punctuation_t *p, *newp; + + //get memory for the table + if ( punctuations == default_punctuations ) { + idLexer::punctuationtable = default_punctuationtable; + idLexer::nextpunctuation = default_nextpunctuation; + if ( default_setup ) { + return; + } + default_setup = true; + i = sizeof(default_punctuations) / sizeof(punctuation_t); + } + else { + if ( !idLexer::punctuationtable || idLexer::punctuationtable == default_punctuationtable ) { + idLexer::punctuationtable = (int *) Mem_Alloc(256 * sizeof(int), TAG_IDLIB_LEXER); + } + if ( idLexer::nextpunctuation && idLexer::nextpunctuation != default_nextpunctuation ) { + Mem_Free( idLexer::nextpunctuation ); + } + for (i = 0; punctuations[i].p; i++) { + } + idLexer::nextpunctuation = (int *) Mem_Alloc(i * sizeof(int), TAG_IDLIB_LEXER); + } + memset(idLexer::punctuationtable, 0xFF, 256 * sizeof(int)); + memset(idLexer::nextpunctuation, 0xFF, i * sizeof(int)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) { + newp = &punctuations[i]; + lastp = -1; + //sort the punctuations in this table entry on length (longer punctuations first) + for (n = idLexer::punctuationtable[(unsigned int) newp->p[0]]; n >= 0; n = idLexer::nextpunctuation[n] ) { + p = &punctuations[n]; + if (strlen(p->p) < strlen(newp->p)) { + idLexer::nextpunctuation[i] = n; + if (lastp >= 0) { + idLexer::nextpunctuation[lastp] = i; + } + else { + idLexer::punctuationtable[(unsigned int) newp->p[0]] = i; + } + break; + } + lastp = n; + } + if (n < 0) { + idLexer::nextpunctuation[i] = -1; + if (lastp >= 0) { + idLexer::nextpunctuation[lastp] = i; + } + else { + idLexer::punctuationtable[(unsigned int) newp->p[0]] = i; + } + } + } +} + +/* +================ +idLexer::GetPunctuationFromId +================ +*/ +const char *idLexer::GetPunctuationFromId( int id ) { + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + if ( idLexer::punctuations[i].n == id ) { + return idLexer::punctuations[i].p; + } + } + return "unkown punctuation"; +} + +/* +================ +idLexer::GetPunctuationId +================ +*/ +int idLexer::GetPunctuationId( const char *p ) { + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + if ( !strcmp(idLexer::punctuations[i].p, p) ) { + return idLexer::punctuations[i].n; + } + } + return 0; +} + +/* +================ +idLexer::Error +================ +*/ +void idLexer::Error( const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + hadError = true; + + if ( idLexer::flags & LEXFL_NOERRORS ) { + return; + } + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + + if ( idLexer::flags & LEXFL_NOFATALERRORS ) { + idLib::common->Warning( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); + } else { + idLib::common->Error( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); + } +} + +/* +================ +idLexer::Warning +================ +*/ +void idLexer::Warning( const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + if ( idLexer::flags & LEXFL_NOWARNINGS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + idLib::common->Warning( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); +} + +/* +================ +idLexer::SetPunctuations +================ +*/ +void idLexer::SetPunctuations( const punctuation_t *p ) { +#ifdef PUNCTABLE + if (p) { + idLexer::CreatePunctuationTable( p ); + } + else { + idLexer::CreatePunctuationTable( default_punctuations ); + } +#endif //PUNCTABLE + if (p) { + idLexer::punctuations = p; + } + else { + idLexer::punctuations = default_punctuations; + } +} + +/* +================ +idLexer::ReadWhiteSpace + +Reads spaces, tabs, C-like comments etc. +When a newline character is found the scripts line counter is increased. +================ +*/ +int idLexer::ReadWhiteSpace() { + while(1) { + // skip white space + while(*idLexer::script_p <= ' ') { + if (!*idLexer::script_p) { + return 0; + } + if (*idLexer::script_p == '\n') { + idLexer::line++; + } + idLexer::script_p++; + } + // skip comments + if (*idLexer::script_p == '/') { + // comments // + if (*(idLexer::script_p+1) == '/') { + idLexer::script_p++; + do { + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + } + while( *idLexer::script_p != '\n' ); + idLexer::line++; + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + continue; + } + // comments /* */ + else if (*(idLexer::script_p+1) == '*') { + idLexer::script_p++; + while( 1 ) { + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + if ( *idLexer::script_p == '\n' ) { + idLexer::line++; + } + else if ( *idLexer::script_p == '/' ) { + if ( *(idLexer::script_p-1) == '*' ) { + break; + } + if ( *(idLexer::script_p+1) == '*' ) { + idLexer::Warning( "nested comment" ); + } + } + } + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + continue; + } + } + break; + } + return 1; +} + +/* +======================== +idLexer::SkipWhiteSpace + +Reads spaces, tabs, C-like comments etc. When a newline character is found, the scripts line +counter is increased. Returns false if there is no token left to be read. +======================== +*/ +bool idLexer::SkipWhiteSpace( bool currentLine ) { + while( 1 ) { + assert( script_p <= end_p ); + if ( script_p == end_p ) { + return false; + } + // skip white space + while( *script_p <= ' ' ) { + if ( script_p == end_p ) { + return false; + } + if ( !*script_p ) { + return false; + } + if ( *script_p == '\n' ) { + line++; + if ( currentLine ) { + script_p++; + return true; + } + } + script_p++; + } + // skip comments + if ( *script_p == '/' ) { + // comments // + if ( *(script_p+1) == '/' ) { + script_p++; + do { + script_p++; + if ( !*script_p ) { + return false; + } + } + while( *script_p != '\n' ); + line++; + script_p++; + if ( currentLine ) { + return true; + } + if ( !*script_p ) { + return false; + } + continue; + } + // comments /* */ + else if ( *(script_p+1) == '*' ) { + script_p++; + while( 1 ) { + script_p++; + if ( !*script_p ) { + return false; + } + if ( *script_p == '\n' ) { + line++; + } + else if ( *script_p == '/' ) { + if ( *(script_p-1) == '*' ) { + break; + } + if ( *(script_p+1) == '*' ) { + Warning( "nested comment" ); + } + } + } + script_p++; + if ( !*script_p ) { + return false; + } + continue; + } + } + break; + } + return true; +} + +/* +================ +idLexer::ReadEscapeCharacter +================ +*/ +int idLexer::ReadEscapeCharacter( char *ch ) { + int c, val, i; + + // step over the leading '\\' + idLexer::script_p++; + // determine the escape character + switch(*idLexer::script_p) { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + idLexer::script_p++; + for (i = 0, val = 0; ; i++, idLexer::script_p++) { + c = *idLexer::script_p; + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'Z') + c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + c = c - 'a' + 10; + else + break; + val = (val << 4) + c; + } + idLexer::script_p--; + if (val > 0xFF) { + idLexer::Warning( "too large value in escape character" ); + val = 0xFF; + } + c = val; + break; + } + default: //NOTE: decimal ASCII code, NOT octal + { + if (*idLexer::script_p < '0' || *idLexer::script_p > '9') { + idLexer::Error("unknown escape char"); + } + for (i = 0, val = 0; ; i++, idLexer::script_p++) { + c = *idLexer::script_p; + if (c >= '0' && c <= '9') + c = c - '0'; + else + break; + val = val * 10 + c; + } + idLexer::script_p--; + if (val > 0xFF) { + idLexer::Warning( "too large value in escape character" ); + val = 0xFF; + } + c = val; + break; + } + } + // step over the escape character or the last digit of the number + idLexer::script_p++; + // store the escape character + *ch = c; + // succesfully read escape character + return 1; +} + +/* +================ +idLexer::ReadString + +Escape characters are interpretted. +Reads two strings with only a white space between them as one string. +================ +*/ +int idLexer::ReadString( idToken *token, int quote ) { + int tmpline; + const char *tmpscript_p; + char ch; + + if ( quote == '\"' ) { + token->type = TT_STRING; + } else { + token->type = TT_LITERAL; + } + + // leading quote + idLexer::script_p++; + + while(1) { + // if there is an escape character and escape characters are allowed + if (*idLexer::script_p == '\\' && !(idLexer::flags & LEXFL_NOSTRINGESCAPECHARS)) { + if ( !idLexer::ReadEscapeCharacter( &ch ) ) { + return 0; + } + token->AppendDirty( ch ); + } + // if a trailing quote + else if (*idLexer::script_p == quote) { + // step over the quote + idLexer::script_p++; + // if consecutive strings should not be concatenated + if ( (idLexer::flags & LEXFL_NOSTRINGCONCAT) && + (!(idLexer::flags & LEXFL_ALLOWBACKSLASHSTRINGCONCAT) || (quote != '\"')) ) { + break; + } + + tmpscript_p = idLexer::script_p; + tmpline = idLexer::line; + // read white space between possible two consecutive strings + if ( !idLexer::ReadWhiteSpace() ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + + if ( idLexer::flags & LEXFL_NOSTRINGCONCAT ) { + if ( *idLexer::script_p != '\\' ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + // step over the '\\' + idLexer::script_p++; + if ( !idLexer::ReadWhiteSpace() || ( *idLexer::script_p != quote ) ) { + idLexer::Error( "expecting string after '\' terminated line" ); + return 0; + } + } + + // if there's no leading qoute + if ( *idLexer::script_p != quote ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + // step over the new leading quote + idLexer::script_p++; + } + else { + if (*idLexer::script_p == '\0') { + idLexer::Error( "missing trailing quote" ); + return 0; + } + if (*idLexer::script_p == '\n') { + idLexer::Error( "newline inside string" ); + return 0; + } + token->AppendDirty( *idLexer::script_p++ ); + } + } + token->data[token->len] = '\0'; + + if ( token->type == TT_LITERAL ) { + if ( !(idLexer::flags & LEXFL_ALLOWMULTICHARLITERALS) ) { + if ( token->Length() != 1 ) { + idLexer::Warning( "literal is not one character long" ); + } + } + token->subtype = (*token)[0]; + } + else { + // the sub type is the length of the string + token->subtype = token->Length(); + } + return 1; +} + +/* +================ +idLexer::ReadName +================ +*/ +int idLexer::ReadName( idToken *token ) { + char c; + + token->type = TT_NAME; + do { + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + } while ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_' || + // if treating all tokens as strings, don't parse '-' as a seperate token + ((idLexer::flags & LEXFL_ONLYSTRINGS) && (c == '-')) || + // if special path name characters are allowed + ((idLexer::flags & LEXFL_ALLOWPATHNAMES) && (c == '/' || c == '\\' || c == ':' || c == '.')) ); + token->data[token->len] = '\0'; + //the sub type is the length of the name + token->subtype = token->Length(); + return 1; +} + +/* +================ +idLexer::CheckString +================ +*/ +ID_INLINE int idLexer::CheckString( const char *str ) const { + int i; + + for ( i = 0; str[i]; i++ ) { + if ( idLexer::script_p[i] != str[i] ) { + return false; + } + } + return true; +} + +/* +================ +idLexer::ReadNumber +================ +*/ +int idLexer::ReadNumber( idToken *token ) { + int i; + int dot; + char c, c2; + + token->type = TT_NUMBER; + token->subtype = 0; + token->intvalue = 0; + token->floatvalue = 0; + + c = *idLexer::script_p; + c2 = *(idLexer::script_p + 1); + + if ( c == '0' && c2 != '.' ) { + // check for a hexadecimal number + if ( c2 == 'x' || c2 == 'X' ) { + token->AppendDirty( *idLexer::script_p++ ); + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_HEX | TT_INTEGER; + } + // check for a binary number + else if ( c2 == 'b' || c2 == 'B' ) { + token->AppendDirty( *idLexer::script_p++ ); + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while( c == '0' || c == '1' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_BINARY | TT_INTEGER; + } + // its an octal number + else { + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while( c >= '0' && c <= '7' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_OCTAL | TT_INTEGER; + } + } + else { + // decimal integer or floating point number or ip address + dot = 0; + while( 1 ) { + if ( c >= '0' && c <= '9' ) { + } + else if ( c == '.' ) { + dot++; + } + else { + break; + } + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + if( c == 'e' && dot == 0) { + //We have scientific notation without a decimal point + dot++; + } + // if a floating point number + if ( dot == 1 ) { + token->subtype = TT_DECIMAL | TT_FLOAT; + // check for floating point exponent + if ( c == 'e' ) { + //Append the e so that GetFloatValue code works + token->AppendDirty( c ); + c = *(++idLexer::script_p); + if ( c == '-' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + else if ( c == '+' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + } + // check for floating point exception infinite 1.#INF or indefinite 1.#IND or NaN + else if ( c == '#' ) { + c2 = 4; + if ( CheckString( "INF" ) ) { + token->subtype |= TT_INFINITE; + } + else if ( CheckString( "IND" ) ) { + token->subtype |= TT_INDEFINITE; + } + else if ( CheckString( "NAN" ) ) { + token->subtype |= TT_NAN; + } + else if ( CheckString( "QNAN" ) ) { + token->subtype |= TT_NAN; + c2++; + } + else if ( CheckString( "SNAN" ) ) { + token->subtype |= TT_NAN; + c2++; + } + for ( i = 0; i < c2; i++ ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + if ( !(idLexer::flags & LEXFL_ALLOWFLOATEXCEPTIONS) ) { + token->AppendDirty( 0 ); // zero terminate for c_str + idLexer::Error( "parsed %s", token->c_str() ); + } + } + } + else if ( dot > 1 ) { + if ( !( idLexer::flags & LEXFL_ALLOWIPADDRESSES ) ) { + idLexer::Error( "more than one dot in number" ); + return 0; + } + if ( dot != 3 ) { + idLexer::Error( "ip address should have three dots" ); + return 0; + } + token->subtype = TT_IPADDRESS; + } + else { + token->subtype = TT_DECIMAL | TT_INTEGER; + } + } + + if ( token->subtype & TT_FLOAT ) { + if ( c > ' ' ) { + // single-precision: float + if ( c == 'f' || c == 'F' ) { + token->subtype |= TT_SINGLE_PRECISION; + idLexer::script_p++; + } + // extended-precision: long double + else if ( c == 'l' || c == 'L' ) { + token->subtype |= TT_EXTENDED_PRECISION; + idLexer::script_p++; + } + // default is double-precision: double + else { + token->subtype |= TT_DOUBLE_PRECISION; + } + } + else { + token->subtype |= TT_DOUBLE_PRECISION; + } + } + else if ( token->subtype & TT_INTEGER ) { + if ( c > ' ' ) { + // default: signed long + for ( i = 0; i < 2; i++ ) { + // long integer + if ( c == 'l' || c == 'L' ) { + token->subtype |= TT_LONG; + } + // unsigned integer + else if ( c == 'u' || c == 'U' ) { + token->subtype |= TT_UNSIGNED; + } + else { + break; + } + c = *(++idLexer::script_p); + } + } + } + else if ( token->subtype & TT_IPADDRESS ) { + if ( c == ':' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype |= TT_IPPORT; + } + } + token->data[token->len] = '\0'; + return 1; +} + +/* +================ +idLexer::ReadPunctuation +================ +*/ +int idLexer::ReadPunctuation( idToken *token ) { + int l, n, i; + char *p; + const punctuation_t *punc; + +#ifdef PUNCTABLE + for (n = idLexer::punctuationtable[(unsigned int)*(idLexer::script_p)]; n >= 0; n = idLexer::nextpunctuation[n]) + { + punc = &(idLexer::punctuations[n]); +#else + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + punc = &idLexer::punctuations[i]; +#endif + p = punc->p; + // check for this punctuation in the script + for ( l = 0; p[l] && idLexer::script_p[l]; l++ ) { + if ( idLexer::script_p[l] != p[l] ) { + break; + } + } + if ( !p[l] ) { + // + token->EnsureAlloced( l+1, false ); + for ( i = 0; i <= l; i++ ) { + token->data[i] = p[i]; + } + token->len = l; + // + idLexer::script_p += l; + token->type = TT_PUNCTUATION; + // sub type is the punctuation id + token->subtype = punc->n; + return 1; + } + } + return 0; +} + +/* +================ +idLexer::ReadToken +================ +*/ +int idLexer::ReadToken( idToken *token ) { + int c; + + if ( !loaded ) { + idLib::common->Error( "idLexer::ReadToken: no file loaded" ); + return 0; + } + + if ( script_p == NULL ) { + return 0; + } + + // if there is a token available (from unreadToken) + if ( tokenavailable ) { + tokenavailable = 0; + *token = idLexer::token; + return 1; + } + // save script pointer + lastScript_p = script_p; + // save line counter + lastline = line; + // clear the token stuff + token->data[0] = '\0'; + token->len = 0; + // start of the white space + whiteSpaceStart_p = script_p; + token->whiteSpaceStart_p = script_p; + // read white space before token + if ( !ReadWhiteSpace() ) { + return 0; + } + // end of the white space + idLexer::whiteSpaceEnd_p = script_p; + token->whiteSpaceEnd_p = script_p; + // line the token is on + token->line = line; + // number of lines crossed before token + token->linesCrossed = line - lastline; + // clear token flags + token->flags = 0; + + c = *idLexer::script_p; + + // if we're keeping everything as whitespace deliminated strings + if ( idLexer::flags & LEXFL_ONLYSTRINGS ) { + // if there is a leading quote + if ( c == '\"' || c == '\'' ) { + if (!idLexer::ReadString( token, c )) { + return 0; + } + } else if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // if there is a number + else if ( (c >= '0' && c <= '9') || + (c == '.' && (*(idLexer::script_p + 1) >= '0' && *(idLexer::script_p + 1) <= '9')) ) { + if ( !idLexer::ReadNumber( token ) ) { + return 0; + } + // if names are allowed to start with a number + if ( idLexer::flags & LEXFL_ALLOWNUMBERNAMES ) { + c = *idLexer::script_p; + if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + } + } + // if there is a leading quote + else if ( c == '\"' || c == '\'' ) { + if (!idLexer::ReadString( token, c )) { + return 0; + } + } + // if there is a name + else if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // names may also start with a slash when pathnames are allowed + else if ( ( idLexer::flags & LEXFL_ALLOWPATHNAMES ) && ( (c == '/' || c == '\\') || c == '.' ) ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // check for punctuations + else if ( !idLexer::ReadPunctuation( token ) ) { + idLexer::Error( "unknown punctuation %c", c ); + return 0; + } + // succesfully read a token + return 1; +} + +/* +================ +idLexer::ExpectTokenString +================ +*/ +int idLexer::ExpectTokenString( const char *string ) { + idToken token; + + if (!idLexer::ReadToken( &token )) { + idLexer::Error( "couldn't find expected '%s'", string ); + return 0; + } + if ( token != string ) { + idLexer::Error( "expected '%s' but found '%s'", string, token.c_str() ); + return 0; + } + return 1; +} + +/* +================ +idLexer::ExpectTokenType +================ +*/ +int idLexer::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !idLexer::ReadToken( token ) ) { + idLexer::Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + idLexer::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + idLexer::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + idLexer::Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + idLexer::Error( "expected '%s' but found '%s'", GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} + +/* +================ +idLexer::ExpectAnyToken +================ +*/ +int idLexer::ExpectAnyToken( idToken *token ) { + if (!idLexer::ReadToken( token )) { + idLexer::Error( "couldn't read expected token" ); + return 0; + } + else { + return 1; + } +} + +/* +================ +idLexer::CheckTokenString +================ +*/ +int idLexer::CheckTokenString( const char *string ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return 0; + } + // if the given string is available + if ( tok == string ) { + return 1; + } + // unread token + script_p = lastScript_p; + line = lastline; + return 0; +} + +/* +================ +idLexer::CheckTokenType +================ +*/ +int idLexer::CheckTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return 0; + } + // if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return 1; + } + // unread token + script_p = lastScript_p; + line = lastline; + return 0; +} + +/* +================ +idLexer::PeekTokenString +================ +*/ +int idLexer::PeekTokenString( const char *string ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return 0; + } + + // unread token + script_p = lastScript_p; + line = lastline; + + // if the given string is available + if ( tok == string ) { + return 1; + } + return 0; +} + +/* +================ +idLexer::PeekTokenType +================ +*/ +int idLexer::PeekTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return 0; + } + + // unread token + script_p = lastScript_p; + line = lastline; + + // if the type matches + if ( tok.type == type && ( tok.subtype & subtype ) == subtype ) { + *token = tok; + return 1; + } + return 0; +} + +/* +================ +idLexer::SkipUntilString +================ +*/ +int idLexer::SkipUntilString( const char *string ) { + idToken token; + + while(idLexer::ReadToken( &token )) { + if ( token == string ) { + return 1; + } + } + return 0; +} + +/* +================ +idLexer::SkipRestOfLine +================ +*/ +int idLexer::SkipRestOfLine() { + idToken token; + + while(idLexer::ReadToken( &token )) { + if ( token.linesCrossed ) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return 1; + } + } + return 0; +} + +/* +================= +idLexer::SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +int idLexer::SkipBracedSection( bool parseFirstBrace ) { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if ( token.type == TT_PUNCTUATION ) { + if ( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; +} + +/* +================ +idLexer::UnreadToken +================ +*/ +void idLexer::UnreadToken( const idToken *token ) { + if ( idLexer::tokenavailable ) { + idLib::common->FatalError( "idLexer::unreadToken, unread token twice\n" ); + } + idLexer::token = *token; + idLexer::tokenavailable = 1; +} + +/* +================ +idLexer::ReadTokenOnLine +================ +*/ +int idLexer::ReadTokenOnLine( idToken *token ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return false; + } + // if no lines were crossed before this token + if ( !tok.linesCrossed ) { + *token = tok; + return true; + } + // restore our position + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + token->Clear(); + return false; +} + +/* +================ +idLexer::ReadRestOfLine +================ +*/ +const char* idLexer::ReadRestOfLine(idStr& out) { + while(1) { + + if(*idLexer::script_p == '\n') { + idLexer::line++; + break; + } + + if(!*idLexer::script_p) { + break; + } + + if(*idLexer::script_p <= ' ') { + out += " "; + } else { + out += *idLexer::script_p; + } + idLexer::script_p++; + + } + + out.Strip(' '); + return out.c_str(); +} + +/* +================ +idLexer::ParseInt +================ +*/ +int idLexer::ParseInt() { + idToken token; + + if ( !idLexer::ReadToken( &token ) ) { + idLexer::Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idLexer::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + idLexer::Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} + +/* +================ +idLexer::ParseBool +================ +*/ +bool idLexer::ParseBool() { + idToken token; + + if ( !idLexer::ExpectTokenType( TT_NUMBER, 0, &token ) ) { + idLexer::Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} + +/* +================ +idLexer::ParseFloat +================ +*/ +float idLexer::ParseFloat( bool *errorFlag ) { + idToken token; + + if ( errorFlag ) { + *errorFlag = false; + } + + if ( !idLexer::ReadToken( &token ) ) { + if ( errorFlag ) { + idLexer::Warning( "couldn't read expected floating point number" ); + *errorFlag = true; + } else { + idLexer::Error( "couldn't read expected floating point number" ); + } + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idLexer::ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + if ( errorFlag ) { + idLexer::Warning( "expected float value, found '%s'", token.c_str() ); + *errorFlag = true; + } else { + idLexer::Error( "expected float value, found '%s'", token.c_str() ); + } + } + return token.GetFloatValue(); +} + +/* +================ +idLexer::Parse1DMatrix +================ +*/ +int idLexer::Parse1DMatrix( int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = idLexer::ParseFloat(); + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idLexer::Parse2DMatrix +================ +*/ +int idLexer::Parse2DMatrix( int y, int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !idLexer::Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idLexer::Parse3DMatrix +================ +*/ +int idLexer::Parse3DMatrix( int z, int y, int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !idLexer::Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================= +idParser::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Maintains exact characters between braces. + + FIXME: this should use ReadToken and replace the token white space with correct indents and newlines +================= +*/ +const char *idLexer::ParseBracedSectionExact( idStr &out, int tabs ) { + int depth; + bool doTabs; + bool skipWhite; + + out.Empty(); + + if ( !idLexer::ExpectTokenString( "{" ) ) { + return out.c_str( ); + } + + out = "{"; + depth = 1; + skipWhite = false; + doTabs = tabs >= 0; + + while( depth && *idLexer::script_p ) { + char c = *(idLexer::script_p++); + + switch ( c ) { + case '\t': + case ' ': { + if ( skipWhite ) { + continue; + } + break; + } + case '\n': { + if ( doTabs ) { + skipWhite = true; + out += c; + continue; + } + break; + } + case '{': { + depth++; + tabs++; + break; + } + case '}': { + depth--; + tabs--; + break; + } + } + + if ( skipWhite ) { + int i = tabs; + if ( c == '{' ) { + i--; + } + skipWhite = false; + for ( ; i > 0; i-- ) { + out += '\t'; + } + } + out += c; + } + return out.c_str(); +} + +/* +================= +idLexer::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +const char *idLexer::ParseBracedSection( idStr &out ) { + idToken token; + int i, depth; + + out.Empty(); + if ( !idLexer::ExpectTokenString( "{" ) ) { + return out.c_str(); + } + out = "{"; + depth = 1; + do { + if ( !idLexer::ReadToken( &token ) ) { + Error( "missing closing brace" ); + return out.c_str(); + } + + // if the token is on a new line + for ( i = 0; i < token.linesCrossed; i++ ) { + out += "\r\n"; + } + + if ( token.type == TT_PUNCTUATION ) { + if ( token[0] == '{' ) { + depth++; + } + else if ( token[0] == '}' ) { + depth--; + } + } + + if ( token.type == TT_STRING ) { + out += "\"" + token + "\""; + } + else { + out += token; + } + out += " "; + } while( depth ); + + return out.c_str(); +} + +/* +================= +idLexer::ParseRestOfLine + + parse the rest of the line +================= +*/ +const char *idLexer::ParseRestOfLine( idStr &out ) { + idToken token; + + out.Empty(); + while(idLexer::ReadToken( &token )) { + if ( token.linesCrossed ) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + break; + } + if ( out.Length() ) { + out += " "; + } + out += token; + } + return out.c_str(); +} + +/* +======================== +idLexer::ParseCompleteLine + +Returns a string up to the \n, but doesn't eat any whitespace at the beginning of the next line. +======================== +*/ +const char *idLexer::ParseCompleteLine( idStr &out ) { + idToken token; + const char *start; + + start = script_p; + + while ( 1 ) { + // end of buffer + if ( *script_p == 0 ) { + break; + } + if ( *script_p == '\n' ) { + line++; + script_p++; + break; + } + script_p++; + } + + out.Empty(); + out.Append( start, script_p - start ); + + return out.c_str(); +} + +/* +================ +idLexer::GetLastWhiteSpace +================ +*/ +int idLexer::GetLastWhiteSpace( idStr &whiteSpace ) const { + whiteSpace.Clear(); + for ( const char *p = whiteSpaceStart_p; p < whiteSpaceEnd_p; p++ ) { + whiteSpace.Append( *p ); + } + return whiteSpace.Length(); +} + +/* +================ +idLexer::GetLastWhiteSpaceStart +================ +*/ +int idLexer::GetLastWhiteSpaceStart() const { + return whiteSpaceStart_p - buffer; +} + +/* +================ +idLexer::GetLastWhiteSpaceEnd +================ +*/ +int idLexer::GetLastWhiteSpaceEnd() const { + return whiteSpaceEnd_p - buffer; +} + +/* +================ +idLexer::Reset +================ +*/ +void idLexer::Reset() { + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // begin of white space + idLexer::whiteSpaceStart_p = NULL; + // end of white space + idLexer::whiteSpaceEnd_p = NULL; + // set if there's a token available in idLexer::token + idLexer::tokenavailable = 0; + + idLexer::line = 1; + idLexer::lastline = 1; + // clear the saved token + idLexer::token = ""; +} + +/* +================ +idLexer::EndOfFile +================ +*/ +bool idLexer::EndOfFile() { + return idLexer::script_p >= idLexer::end_p; +} + +/* +================ +idLexer::NumLinesCrossed +================ +*/ +int idLexer::NumLinesCrossed() { + return idLexer::line - idLexer::lastline; +} + +/* +================ +idLexer::LoadFile +================ +*/ +int idLexer::LoadFile( const char *filename, bool OSPath ) { + idFile *fp; + idStr pathname; + int length; + char *buf; + + if ( idLexer::loaded ) { + idLib::common->Error("idLexer::LoadFile: another script already loaded"); + return false; + } + + if ( !OSPath && ( baseFolder[0] != '\0' ) ) { + pathname = va( "%s/%s", baseFolder, filename ); + } else { + pathname = filename; + } + if ( OSPath ) { + fp = idLib::fileSystem->OpenExplicitFileRead( pathname ); + } else { + fp = idLib::fileSystem->OpenFileRead( pathname ); + } + if ( !fp ) { + return false; + } + length = fp->Length(); + buf = (char *) Mem_Alloc( length + 1, TAG_IDLIB_LEXER ); + buf[length] = '\0'; + fp->Read( buf, length ); + idLexer::fileTime = fp->Timestamp(); + idLexer::filename = fp->GetFullPath(); + idLib::fileSystem->CloseFile( fp ); + + idLexer::buffer = buf; + idLexer::length = length; + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // pointer to end of script buffer + idLexer::end_p = &(idLexer::buffer[length]); + + idLexer::tokenavailable = 0; + idLexer::line = 1; + idLexer::lastline = 1; + idLexer::allocated = true; + idLexer::loaded = true; + + return true; +} + +/* +================ +idLexer::LoadMemory +================ +*/ +int idLexer::LoadMemory( const char *ptr, int length, const char *name, int startLine ) { + if ( idLexer::loaded ) { + idLib::common->Error("idLexer::LoadMemory: another script already loaded"); + return false; + } + idLexer::filename = name; + idLexer::buffer = ptr; + idLexer::fileTime = 0; + idLexer::length = length; + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // pointer to end of script buffer + idLexer::end_p = &(idLexer::buffer[length]); + + idLexer::tokenavailable = 0; + idLexer::line = startLine; + idLexer::lastline = startLine; + idLexer::allocated = false; + idLexer::loaded = true; + + return true; +} + +/* +================ +idLexer::FreeSource +================ +*/ +void idLexer::FreeSource() { +#ifdef PUNCTABLE + if ( idLexer::punctuationtable && idLexer::punctuationtable != default_punctuationtable ) { + Mem_Free( (void *) idLexer::punctuationtable ); + idLexer::punctuationtable = NULL; + } + if ( idLexer::nextpunctuation && idLexer::nextpunctuation != default_nextpunctuation ) { + Mem_Free( (void *) idLexer::nextpunctuation ); + idLexer::nextpunctuation = NULL; + } +#endif //PUNCTABLE + if ( idLexer::allocated ) { + Mem_Free( (void *) idLexer::buffer ); + idLexer::buffer = NULL; + idLexer::allocated = false; + } + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::loaded = false; +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer() { + idLexer::loaded = false; + idLexer::filename = ""; + idLexer::flags = 0; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::fileTime = 0; + idLexer::length = 0; + idLexer::line = 0; + idLexer::lastline = 0; + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( int flags ) { + idLexer::loaded = false; + idLexer::filename = ""; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::fileTime = 0; + idLexer::length = 0; + idLexer::line = 0; + idLexer::lastline = 0; + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( const char *filename, int flags, bool OSPath ) { + idLexer::loaded = false; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; + idLexer::LoadFile( filename, OSPath ); +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( const char *ptr, int length, const char *name, int flags ) { + idLexer::loaded = false; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; + idLexer::LoadMemory( ptr, length, name ); +} + +/* +================ +idLexer::~idLexer +================ +*/ +idLexer::~idLexer() { + idLexer::FreeSource(); +} + +/* +================ +idLexer::SetBaseFolder +================ +*/ +void idLexer::SetBaseFolder( const char *path ) { + idStr::Copynz( baseFolder, path, sizeof( baseFolder ) ); +} + +/* +================ +idLexer::HadError +================ +*/ +bool idLexer::HadError() const { + return hadError; +} + diff --git a/neo/idlib/Lexer.h b/neo/idlib/Lexer.h new file mode 100644 index 00000000..bc0f2ea8 --- /dev/null +++ b/neo/idlib/Lexer.h @@ -0,0 +1,311 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LEXER_H__ +#define __LEXER_H__ + +/* +=============================================================================== + + Lexicographical parser + + Does not use memory allocation during parsing. The lexer uses no + memory allocation if a source is loaded with LoadMemory(). + However, idToken may still allocate memory for large strings. + + A number directly following the escape character '\' in a string is + assumed to be in decimal format instead of octal. Binary numbers of + the form 0b.. or 0B.. can also be used. + +=============================================================================== +*/ + +// lexer flags +typedef enum { + LEXFL_NOERRORS = BIT(0), // don't print any errors + LEXFL_NOWARNINGS = BIT(1), // don't print any warnings + LEXFL_NOFATALERRORS = BIT(2), // errors aren't fatal + LEXFL_NOSTRINGCONCAT = BIT(3), // multiple strings seperated by whitespaces are not concatenated + LEXFL_NOSTRINGESCAPECHARS = BIT(4), // no escape characters inside strings + LEXFL_NODOLLARPRECOMPILE = BIT(5), // don't use the $ sign for precompilation + LEXFL_NOBASEINCLUDES = BIT(6), // don't include files embraced with < > + LEXFL_ALLOWPATHNAMES = BIT(7), // allow path seperators in names + LEXFL_ALLOWNUMBERNAMES = BIT(8), // allow names to start with a number + LEXFL_ALLOWIPADDRESSES = BIT(9), // allow ip addresses to be parsed as numbers + LEXFL_ALLOWFLOATEXCEPTIONS = BIT(10), // allow float exceptions like 1.#INF or 1.#IND to be parsed + LEXFL_ALLOWMULTICHARLITERALS = BIT(11), // allow multi character literals + LEXFL_ALLOWBACKSLASHSTRINGCONCAT = BIT(12), // allow multiple strings seperated by '\' to be concatenated + LEXFL_ONLYSTRINGS = BIT(13) // parse as whitespace deliminated strings (quoted strings keep quotes) +} lexerFlags_t; + +// punctuation ids +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 + +// punctuation +typedef struct punctuation_s +{ + char *p; // punctuation character(s) + int n; // punctuation id +} punctuation_t; + + +class idLexer { + + friend class idParser; + +public: + // constructor + idLexer(); + idLexer( int flags ); + idLexer( const char *filename, int flags = 0, bool OSPath = false ); + idLexer( const char *ptr, int length, const char *name, int flags = 0 ); + // destructor + ~idLexer(); + // load a script from the given file at the given offset with the given length + int LoadFile( const char *filename, bool OSPath = false ); + // load a script from the given memory with the given length and a specified line offset, + // so source strings extracted from a file can still refer to proper line numbers in the file + // NOTE: the ptr is expected to point at a valid C string: ptr[length] == '\0' + int LoadMemory( const char *ptr, int length, const char *name, int startLine = 1 ); + // free the script + void FreeSource(); + // returns true if a script is loaded + int IsLoaded() { return idLexer::loaded; }; + // read a token + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true when the token is available + int CheckTokenString( const char *string ); + // returns true an reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // returns true if the next token equals the given string but does not remove the token from the source + int PeekTokenString( const char *string ); + // returns true if the next token equals the given type but does not remove the token from the source + int PeekTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine(); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // skips spaces, tabs, C-like comments etc. Returns false if there is no token left to read. + bool SkipWhiteSpace( bool currentLine ); + // unread the given token + void UnreadToken( const idToken *token ); + // read a token only if on the same line + int ReadTokenOnLine( idToken *token ); + + //Returns the rest of the current line + const char* ReadRestOfLine(idStr& out); + + // read a signed integer + int ParseInt(); + // read a boolean + bool ParseBool(); + // read a floating point number. If errorFlag is NULL, a non-numeric token will + // issue an Error(). If it isn't NULL, it will issue a Warning() and set *errorFlag = true + float ParseFloat( bool *errorFlag = NULL ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact ( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // pulls the entire line, including the \n at the end + const char * ParseCompleteLine( idStr &out ); + // retrieves the white space characters before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // returns start index into text buffer of last white space + int GetLastWhiteSpaceStart() const; + // returns end index into text buffer of last white space + int GetLastWhiteSpaceEnd() const; + // set an array with punctuations, NULL restores default C/C++ set, see default_punctuations for an example + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags(); + // reset the lexer + void Reset(); + // returns true if at the end of the file + bool EndOfFile(); + // returns the current filename + const char * GetFileName(); + // get offset in script + const int GetFileOffset(); + // get file time + const ID_TIME_T GetFileTime(); + // returns the current line number + const int GetLineNum(); + // print an error message + void Error( VERIFY_FORMAT_STRING const char *str, ... ); + // print a warning message + void Warning( VERIFY_FORMAT_STRING const char *str, ... ); + // returns true if Error() was called with LEXFL_NOFATALERRORS or LEXFL_NOERRORS set + bool HadError() const; + + // set the base folder to load files from + static void SetBaseFolder( const char *path ); + +private: + int loaded; // set when a script file is loaded from file or memory + idStr filename; // file name of the script + int allocated; // true if buffer memory was allocated + const char * buffer; // buffer containing the script + const char * script_p; // current pointer in the script + const char * end_p; // pointer to the end of the script + const char * lastScript_p; // script pointer before reading token + const char * whiteSpaceStart_p; // start of last white space + const char * whiteSpaceEnd_p; // end of last white space + ID_TIME_T fileTime; // file time + int length; // length of the script in bytes + int line; // current line in script + int lastline; // line before reading token + int tokenavailable; // set by unreadToken + int flags; // several script flags + const punctuation_t *punctuations; // the punctuations used in the script + int * punctuationtable; // ASCII table with punctuations + int * nextpunctuation; // next punctuation in chain + idToken token; // available token + idLexer * next; // next script in a chain + bool hadError; // set by idLexer::Error, even if the error is supressed + + static char baseFolder[ 256 ]; // base folder to load files from + +private: + void CreatePunctuationTable( const punctuation_t *punctuations ); + int ReadWhiteSpace(); + int ReadEscapeCharacter( char *ch ); + int ReadString( idToken *token, int quote ); + int ReadName( idToken *token ); + int ReadNumber( idToken *token ); + int ReadPunctuation( idToken *token ); + int ReadPrimitive( idToken *token ); + int CheckString( const char *str ) const; + int NumLinesCrossed(); +}; + +ID_INLINE const char *idLexer::GetFileName() { + return idLexer::filename; +} + +ID_INLINE const int idLexer::GetFileOffset() { + return idLexer::script_p - idLexer::buffer; +} + +ID_INLINE const ID_TIME_T idLexer::GetFileTime() { + return idLexer::fileTime; +} + +ID_INLINE const int idLexer::GetLineNum() { + return idLexer::line; +} + +ID_INLINE void idLexer::SetFlags( int flags ) { + idLexer::flags = flags; +} + +ID_INLINE int idLexer::GetFlags() { + return idLexer::flags; +} + +#endif /* !__LEXER_H__ */ + diff --git a/neo/idlib/Lib.cpp b/neo/idlib/Lib.cpp new file mode 100644 index 00000000..6b8d433a --- /dev/null +++ b/neo/idlib/Lib.cpp @@ -0,0 +1,608 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#if defined( MACOS_X ) +#include +#include +#include +#endif + +/* +=============================================================================== + + idLib + +=============================================================================== +*/ + +idSys * idLib::sys = NULL; +idCommon * idLib::common = NULL; +idCVarSystem * idLib::cvarSystem = NULL; +idFileSystem * idLib::fileSystem = NULL; +int idLib::frameNumber = 0; +bool idLib::mainThreadInitialized = 0; +ID_TLS idLib::isMainThread = 0; + +char idException::error[2048]; + +/* +================ +idLib::Init +================ +*/ +void idLib::Init() { + + assert( sizeof( bool ) == 1 ); + + isMainThread = 1; + mainThreadInitialized = 1; // note that the thread-local isMainThread is now valid + + // initialize little/big endian conversion + Swap_Init(); + + // init string memory allocator + idStr::InitMemory(); + + // initialize generic SIMD implementation + idSIMD::Init(); + + // initialize math + idMath::Init(); + + // test idMatX + //idMatX::Test(); + + // test idPolynomial +#ifdef _DEBUG + idPolynomial::Test(); +#endif + + // initialize the dictionary string pools + idDict::Init(); +} + +/* +================ +idLib::ShutDown +================ +*/ +void idLib::ShutDown() { + + // shut down the dictionary string pools + idDict::Shutdown(); + + // shut down the string memory allocator + idStr::ShutdownMemory(); + + // shut down the SIMD engine + idSIMD::Shutdown(); +} + + +/* +=============================================================================== + + Colors + +=============================================================================== +*/ + +idVec4 colorBlack = idVec4( 0.00f, 0.00f, 0.00f, 1.00f ); +idVec4 colorWhite = idVec4( 1.00f, 1.00f, 1.00f, 1.00f ); +idVec4 colorRed = idVec4( 1.00f, 0.00f, 0.00f, 1.00f ); +idVec4 colorGreen = idVec4( 0.00f, 1.00f, 0.00f, 1.00f ); +idVec4 colorBlue = idVec4( 0.00f, 0.00f, 1.00f, 1.00f ); +idVec4 colorYellow = idVec4( 1.00f, 1.00f, 0.00f, 1.00f ); +idVec4 colorMagenta= idVec4( 1.00f, 0.00f, 1.00f, 1.00f ); +idVec4 colorCyan = idVec4( 0.00f, 1.00f, 1.00f, 1.00f ); +idVec4 colorOrange = idVec4( 1.00f, 0.50f, 0.00f, 1.00f ); +idVec4 colorPurple = idVec4( 0.60f, 0.00f, 0.60f, 1.00f ); +idVec4 colorPink = idVec4( 0.73f, 0.40f, 0.48f, 1.00f ); +idVec4 colorBrown = idVec4( 0.40f, 0.35f, 0.08f, 1.00f ); +idVec4 colorLtGrey = idVec4( 0.75f, 0.75f, 0.75f, 1.00f ); +idVec4 colorMdGrey = idVec4( 0.50f, 0.50f, 0.50f, 1.00f ); +idVec4 colorDkGrey = idVec4( 0.25f, 0.25f, 0.25f, 1.00f ); + +/* +================ +PackColor +================ +*/ +dword PackColor( const idVec4 &color ) { + byte dx = idMath::Ftob( color.x * 255.0f ); + byte dy = idMath::Ftob( color.y * 255.0f ); + byte dz = idMath::Ftob( color.z * 255.0f ); + byte dw = idMath::Ftob( color.w * 255.0f ); + return ( dx << 0 ) | ( dy << 8 ) | ( dz << 16 ) | ( dw << 24 ); +} + +/* +================ +UnpackColor +================ +*/ +void UnpackColor( const dword color, idVec4 &unpackedColor ) { + unpackedColor.Set( ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 24 ) & 255 ) * ( 1.0f / 255.0f ) ); +} + +/* +================ +PackColor +================ +*/ +dword PackColor( const idVec3 &color ) { + byte dx = idMath::Ftob( color.x * 255.0f ); + byte dy = idMath::Ftob( color.y * 255.0f ); + byte dz = idMath::Ftob( color.z * 255.0f ); + return ( dx << 0 ) | ( dy << 8 ) | ( dz << 16 ); +} + +/* +================ +UnpackColor +================ +*/ +void UnpackColor( const dword color, idVec3 &unpackedColor ) { + unpackedColor.Set( ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ) ); +} + +/* +=============== +idLib::FatalError +=============== +*/ +void idLib::FatalError( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->FatalError( "%s", text ); +} + +/* +=============== +idLib::Error +=============== +*/ +void idLib::Error( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Error( "%s", text ); +} + +/* +=============== +idLib::Warning +=============== +*/ +void idLib::Warning( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Warning( "%s", text ); +} + +/* +=============== +idLib::WarningIf +=============== +*/ +void idLib::WarningIf( const bool test, const char *fmt, ... ) { + if ( !test ) { + return; + } + + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Warning( "%s", text ); +} + +/* +=============== +idLib::Printf +=============== +*/ +void idLib::Printf( const char *fmt, ... ) { + va_list argptr; + va_start( argptr, fmt ); + if ( common ) { + common->VPrintf( fmt, argptr ); + } + va_end( argptr ); +} + +/* +=============== +idLib::PrintfIf +=============== +*/ +void idLib::PrintfIf( const bool test, const char *fmt, ... ) { + if ( !test ) { + return; + } + + va_list argptr; + va_start( argptr, fmt ); + common->VPrintf( fmt, argptr ); + va_end( argptr ); +} + +/* +=============================================================================== + + Byte order functions + +=============================================================================== +*/ + +// can't just use function pointers, or dll linkage can mess up +static short (*_BigShort)( short l ); +static short (*_LittleShort)( short l ); +static int (*_BigLong)( int l ); +static int (*_LittleLong)( int l ); +static float (*_BigFloat)( float l ); +static float (*_LittleFloat)( float l ); +static void (*_BigRevBytes)( void *bp, int elsize, int elcount ); +static void (*_LittleRevBytes)( void *bp, int elsize, int elcount ); +static void (*_LittleBitField)( void *bp, int elsize ); +static void (*_SixtetsForInt)( byte *out, int src ); +static int (*_IntForSixtets)( byte *in ); + +short BigShort( short l ) { return _BigShort( l ); } +short LittleShort( short l ) { return _LittleShort( l ); } +int BigLong( int l ) { return _BigLong( l ); } +int LittleLong( int l ) { return _LittleLong( l ); } +float BigFloat( float l ) { return _BigFloat( l ); } +float LittleFloat( float l ) { return _LittleFloat( l ); } +void BigRevBytes( void *bp, int elsize, int elcount ) { _BigRevBytes( bp, elsize, elcount ); } +void LittleRevBytes( void *bp, int elsize, int elcount ){ _LittleRevBytes( bp, elsize, elcount ); } +void LittleBitField( void *bp, int elsize ){ _LittleBitField( bp, elsize ); } + +void SixtetsForInt( byte *out, int src) { _SixtetsForInt( out, src ); } +int IntForSixtets( byte *in ) { return _IntForSixtets( in ); } + +/* +================ +ShortSwap +================ +*/ +short ShortSwap( short l ) { + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +/* +================ +ShortNoSwap +================ +*/ +short ShortNoSwap( short l ) { + return l; +} + +/* +================ +LongSwap +================ +*/ +int LongSwap ( int l ) { + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +/* +================ +LongNoSwap +================ +*/ +int LongNoSwap( int l ) { + return l; +} + +/* +================ +FloatSwap +================ +*/ +float FloatSwap( float f ) { + union { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +/* +================ +FloatNoSwap +================ +*/ +float FloatNoSwap( float f ) { + return f; +} + +/* +===================================================================== +RevBytesSwap + +Reverses byte order in place. + +INPUTS + bp bytes to reverse + elsize size of the underlying data type + elcount number of elements to swap + +RESULTS + Reverses the byte order in each of elcount elements. +===================================================================== */ +void RevBytesSwap( void *bp, int elsize, int elcount ) { + register unsigned char *p, *q; + + p = ( unsigned char * ) bp; + + if ( elsize == 2 ) { + q = p + 1; + while ( elcount-- ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + p += 2; + q += 2; + } + return; + } + + while ( elcount-- ) { + q = p + elsize - 1; + while ( p < q ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + ++p; + --q; + } + p += elsize >> 1; + } +} + +/* + ===================================================================== + RevBytesSwap + + Reverses byte order in place, then reverses bits in those bytes + + INPUTS + bp bitfield structure to reverse + elsize size of the underlying data type + + RESULTS + Reverses the bitfield of size elsize. + ===================================================================== */ +void RevBitFieldSwap( void *bp, int elsize) { + int i; + unsigned char *p, t, v; + + LittleRevBytes( bp, elsize, 1 ); + + p = (unsigned char *) bp; + while ( elsize-- ) { + v = *p; + t = 0; + for (i = 7; i>=0; i--) { + t <<= 1; + v >>= 1; + t |= v & 1; + } + *p++ = t; + } +} + +/* +================ +RevBytesNoSwap +================ +*/ +void RevBytesNoSwap( void *bp, int elsize, int elcount ) { + return; +} + +/* + ================ + RevBytesNoSwap + ================ + */ +void RevBitFieldNoSwap( void *bp, int elsize ) { + return; +} + +/* +================ +SixtetsForIntLittle +================ +*/ +void SixtetsForIntLittle( byte *out, int src) { + byte *b = (byte *)&src; + out[0] = ( b[0] & 0xfc ) >> 2; + out[1] = ( ( b[0] & 0x3 ) << 4 ) + ( ( b[1] & 0xf0 ) >> 4 ); + out[2] = ( ( b[1] & 0xf ) << 2 ) + ( ( b[2] & 0xc0 ) >> 6 ); + out[3] = b[2] & 0x3f; +} + +/* +================ +SixtetsForIntBig +TTimo: untested - that's the version from initial base64 encode +================ +*/ +void SixtetsForIntBig( byte *out, int src) { + for( int i = 0 ; i < 4 ; i++ ) { + out[i] = src & 0x3f; + src >>= 6; + } +} + +/* +================ +IntForSixtetsLittle +================ +*/ +int IntForSixtetsLittle( byte *in ) { + int ret = 0; + byte *b = (byte *)&ret; + b[0] |= in[0] << 2; + b[0] |= ( in[1] & 0x30 ) >> 4; + b[1] |= ( in[1] & 0xf ) << 4; + b[1] |= ( in[2] & 0x3c ) >> 2; + b[2] |= ( in[2] & 0x3 ) << 6; + b[2] |= in[3]; + return ret; +} + +/* +================ +IntForSixtetsBig +TTimo: untested - that's the version from initial base64 decode +================ +*/ +int IntForSixtetsBig( byte *in ) { + int ret = 0; + ret |= in[0]; + ret |= in[1] << 6; + ret |= in[2] << 2*6; + ret |= in[3] << 3*6; + return ret; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init() { + byte swaptest[2] = {1,0}; + + // set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) { + // little endian ex: x86 + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + _BigRevBytes = RevBytesSwap; + _LittleRevBytes = RevBytesNoSwap; + _LittleBitField = RevBitFieldNoSwap; + _SixtetsForInt = SixtetsForIntLittle; + _IntForSixtets = IntForSixtetsLittle; + } else { + // big endian ex: ppc + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + _BigRevBytes = RevBytesNoSwap; + _LittleRevBytes = RevBytesSwap; + _LittleBitField = RevBitFieldSwap; + _SixtetsForInt = SixtetsForIntBig; + _IntForSixtets = IntForSixtetsBig; + } +} + +/* +========== +Swap_IsBigEndian +========== +*/ +bool Swap_IsBigEndian() { + byte swaptest[2] = {1,0}; + return *(short *)swaptest != 1; +} + + +/* +======================== +BreakOnListGrowth + +debug tool to find uses of idlist that are dynamically growing +======================== +*/ +void BreakOnListGrowth() { +} + +/* +======================== +BreakOnListDefault +======================== +*/ +void BreakOnListDefault() { +} diff --git a/neo/idlib/Lib.h b/neo/idlib/Lib.h new file mode 100644 index 00000000..85f6ee9f --- /dev/null +++ b/neo/idlib/Lib.h @@ -0,0 +1,322 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LIB_H__ +#define __LIB_H__ + +#include + +/* +=============================================================================== + + idLib contains stateless support classes and concrete types. Some classes + do have static variables, but such variables are initialized once and + read-only after initialization (they do not maintain a modifiable state). + + The interface pointers idSys, idCommon, idCVarSystem and idFileSystem + should be set before using idLib. The pointers stored here should not + be used by any part of the engine except for idLib. + +=============================================================================== +*/ + +class idLib { +private: + static bool mainThreadInitialized; + static ID_TLS isMainThread; + +public: + static class idSys * sys; + static class idCommon * common; + static class idCVarSystem * cvarSystem; + static class idFileSystem * fileSystem; + static int frameNumber; + + static void Init(); + static void ShutDown(); + + // wrapper to idCommon functions + static void Printf( const char *fmt, ... ); + static void PrintfIf( const bool test, const char *fmt, ... ); + NO_RETURN static void Error( const char *fmt, ... ); + NO_RETURN static void FatalError( const char *fmt, ... ); + static void Warning( const char *fmt, ... ); + static void WarningIf( const bool test, const char *fmt, ... ); + + // the extra check for mainThreadInitialized is necessary for this to be accurate + // when called by startup code that happens before idLib::Init + static bool IsMainThread() { return ( 0 == mainThreadInitialized ) || ( 1 == isMainThread ); } +}; + + +/* +=============================================================================== + + Types and defines used throughout the engine. + +=============================================================================== +*/ + +typedef int qhandle_t; + +class idFile; +class idVec3; +class idVec4; + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef BIT +#define BIT( num ) ( 1ULL << ( num ) ) +#endif + +#define MAX_STRING_CHARS 1024 // max length of a string +#define MAX_PRINT_MSG 16384 // buffer size for our various printf routines + +// maximum world size +#define MAX_WORLD_COORD ( 128 * 1024 ) +#define MIN_WORLD_COORD ( -128 * 1024 ) +#define MAX_WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +#define SIZE_KB( x ) ( ( (x) + 1023 ) / 1024 ) +#define SIZE_MB( x ) ( ( ( SIZE_KB( x ) ) + 1023 ) / 1024 ) +#define SIZE_GB( x ) ( ( ( SIZE_MB( x ) ) + 1023 ) / 1024 ) + +// basic colors +extern idVec4 colorBlack; +extern idVec4 colorWhite; +extern idVec4 colorRed; +extern idVec4 colorGreen; +extern idVec4 colorBlue; +extern idVec4 colorYellow; +extern idVec4 colorMagenta; +extern idVec4 colorCyan; +extern idVec4 colorOrange; +extern idVec4 colorPurple; +extern idVec4 colorPink; +extern idVec4 colorBrown; +extern idVec4 colorLtGrey; +extern idVec4 colorMdGrey; +extern idVec4 colorDkGrey; + +// packs color floats in the range [0,1] into an integer +dword PackColor( const idVec3 &color ); +void UnpackColor( const dword color, idVec3 &unpackedColor ); +dword PackColor( const idVec4 &color ); +void UnpackColor( const dword color, idVec4 &unpackedColor ); + +// little/big endian conversion +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); +void BigRevBytes( void *bp, int elsize, int elcount ); +void LittleRevBytes( void *bp, int elsize, int elcount ); +void LittleBitField( void *bp, int elsize ); +void Swap_Init(); + +bool Swap_IsBigEndian(); + +// for base64 +void SixtetsForInt( byte *out, int src); +int IntForSixtets( byte *in ); + +/* +================================================ +idException +================================================ +*/ +class idException { +public: + static const int MAX_ERROR_LEN = 2048; + + idException( const char *text = "" ) { + strncpy( error, text, MAX_ERROR_LEN ); + } + + // this really, really should be a const function, but it's referenced too many places to change right now + const char * GetError() { + return error; + } + +protected: + // if GetError() were correctly const this would be named GetError(), too + char * GetErrorBuffer() { + return error; + } + int GetErrorBufferSize() { + return MAX_ERROR_LEN; + } + +private: + friend class idFatalException; + static char error[MAX_ERROR_LEN]; +}; + +/* +================================================ +idFatalException +================================================ +*/ +class idFatalException { +public: + static const int MAX_ERROR_LEN = 2048; + + idFatalException( const char *text = "" ) { + strncpy( idException::error, text, MAX_ERROR_LEN ); + } + + // this really, really should be a const function, but it's referenced too many places to change right now + const char * GetError() { + return idException::error; + } + +protected: + // if GetError() were correctly const this would be named GetError(), too + char * GetErrorBuffer() { + return idException::error; + } + int GetErrorBufferSize() { + return MAX_ERROR_LEN; + } +}; + +/* +================================================ +idNetworkLoadException +================================================ +*/ +class idNetworkLoadException : public idException { +public: + idNetworkLoadException( const char * text = "" ) : idException( text ) { } +}; + +/* +=============================================================================== + + idLib headers. + +=============================================================================== +*/ + +// System +#include "sys/sys_assert.h" +#include "sys/sys_threading.h" + +// memory management and arrays +#include "Heap.h" +#include "containers/Sort.h" +#include "containers/List.h" + +// math +#include "math/Simd.h" +#include "math/Math.h" +#include "math/Random.h" +#include "math/Complex.h" +#include "math/Vector.h" +#include "math/VecX.h" +#include "math/VectorI.h" +#include "math/Matrix.h" +#include "math/MatX.h" +#include "math/Angles.h" +#include "math/Quat.h" +#include "math/Rotation.h" +#include "math/Plane.h" +#include "math/Pluecker.h" +#include "math/Polynomial.h" +#include "math/Extrapolate.h" +#include "math/Interpolate.h" +#include "math/Curve.h" +#include "math/Ode.h" +#include "math/Lcp.h" + +// bounding volumes +#include "bv/Sphere.h" +#include "bv/Bounds.h" +#include "bv/Box.h" + +// geometry +#include "geometry/RenderMatrix.h" +#include "geometry/JointTransform.h" +#include "geometry/DrawVert.h" +#include "geometry/Winding.h" +#include "geometry/Winding2D.h" +#include "geometry/Surface.h" +#include "geometry/Surface_Patch.h" +#include "geometry/Surface_Polytope.h" +#include "geometry/Surface_SweptSpline.h" +#include "geometry/TraceModel.h" + +// text manipulation +#include "Str.h" +#include "StrStatic.h" +#include "Token.h" +#include "Lexer.h" +#include "Parser.h" +#include "Base64.h" +#include "CmdArgs.h" + +// containers +#include "containers/Array.h" +#include "containers/BTree.h" +#include "containers/BinSearch.h" +#include "containers/HashIndex.h" +#include "containers/HashTable.h" +#include "containers/StaticList.h" +#include "containers/LinkList.h" +#include "containers/Hierarchy.h" +#include "containers/Queue.h" +#include "containers/Stack.h" +#include "containers/StrList.h" +#include "containers/StrPool.h" +#include "containers/VectorSet.h" +#include "containers/PlaneSet.h" + +// hashing +#include "hashing/CRC32.h" +#include "hashing/MD4.h" +#include "hashing/MD5.h" + +// misc +#include "Dict.h" +#include "LangDict.h" +#include "DataQueue.h" +#include "BitMsg.h" +#include "MapFile.h" +#include "Timer.h" +#include "Thread.h" +#include "Swap.h" +#include "Callback.h" +#include "ParallelJobList.h" + +#include "SoftwareCache.h" + +#endif /* !__LIB_H__ */ diff --git a/neo/idlib/MapFile.cpp b/neo/idlib/MapFile.cpp new file mode 100644 index 00000000..06a67516 --- /dev/null +++ b/neo/idlib/MapFile.cpp @@ -0,0 +1,972 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + + +/* +=============== +FloatCRC +=============== +*/ +ID_INLINE unsigned int FloatCRC( float f ) { + return *(unsigned int *)&f; +} + +/* +=============== +StringCRC +=============== +*/ +ID_INLINE unsigned int StringCRC( const char *str ) { + unsigned int i, crc; + const unsigned char *ptr; + + crc = 0; + ptr = reinterpret_cast(str); + for ( i = 0; str[i]; i++ ) { + crc ^= str[i] << (i & 3); + } + return crc; +} + +/* +================= +ComputeAxisBase + +WARNING : special case behaviour of atan2(y,x) <-> atan(y/x) might not be the same everywhere when x == 0 +rotation by (0,RotY,RotZ) assigns X to normal +================= +*/ +static void ComputeAxisBase( const idVec3 &normal, idVec3 &texS, idVec3 &texT ) { + float RotY, RotZ; + idVec3 n; + + // do some cleaning + n[0] = ( idMath::Fabs( normal[0] ) < 1e-6f ) ? 0.0f : normal[0]; + n[1] = ( idMath::Fabs( normal[1] ) < 1e-6f ) ? 0.0f : normal[1]; + n[2] = ( idMath::Fabs( normal[2] ) < 1e-6f ) ? 0.0f : normal[2]; + + RotY = -atan2( n[2], idMath::Sqrt( n[1] * n[1] + n[0] * n[0]) ); + RotZ = atan2( n[1], n[0] ); + // rotate (0,1,0) and (0,0,1) to compute texS and texT + texS[0] = -sin(RotZ); + texS[1] = cos(RotZ); + texS[2] = 0; + // the texT vector is along -Z ( T texture coorinates axis ) + texT[0] = -sin(RotY) * cos(RotZ); + texT[1] = -sin(RotY) * sin(RotZ); + texT[2] = -cos(RotY); +} + +/* +================= +idMapBrushSide::GetTextureVectors +================= +*/ +void idMapBrushSide::GetTextureVectors( idVec4 v[2] ) const { + int i; + idVec3 texX, texY; + + ComputeAxisBase( plane.Normal(), texX, texY ); + for ( i = 0; i < 2; i++ ) { + v[i][0] = texX[0] * texMat[i][0] + texY[0] * texMat[i][1]; + v[i][1] = texX[1] * texMat[i][0] + texY[1] * texMat[i][1]; + v[i][2] = texX[2] * texMat[i][0] + texY[2] * texMat[i][1]; + v[i][3] = texMat[i][2] + ( origin * v[i].ToVec3() ); + } +} + +/* +================= +idMapPatch::Parse +================= +*/ +idMapPatch *idMapPatch::Parse( idLexer &src, const idVec3 &origin, bool patchDef3, float version ) { + float info[7]; + idDrawVert *vert; + idToken token; + int i, j; + + if ( !src.ExpectTokenString( "{" ) ) { + return NULL; + } + + // read the material (we had an implicit 'textures/' in the old format...) + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapPatch::Parse: unexpected EOF" ); + return NULL; + } + + // Parse it + if (patchDef3) { + if ( !src.Parse1DMatrix( 7, info ) ) { + src.Error( "idMapPatch::Parse: unable to Parse patchDef3 info" ); + return NULL; + } + } else { + if ( !src.Parse1DMatrix( 5, info ) ) { + src.Error( "idMapPatch::Parse: unable to parse patchDef2 info" ); + return NULL; + } + } + + idMapPatch *patch = new (TAG_IDLIB) idMapPatch( info[0], info[1] ); + + patch->SetSize( info[0], info[1] ); + if ( version < 2.0f ) { + patch->SetMaterial( "textures/" + token ); + } else { + patch->SetMaterial( token ); + } + + if ( patchDef3 ) { + patch->SetHorzSubdivisions( info[2] ); + patch->SetVertSubdivisions( info[3] ); + patch->SetExplicitlySubdivided( true ); + } + + if ( patch->GetWidth() < 0 || patch->GetHeight() < 0 ) { + src.Error( "idMapPatch::Parse: bad size" ); + delete patch; + return NULL; + } + + // these were written out in the wrong order, IMHO + if ( !src.ExpectTokenString( "(" ) ) { + src.Error( "idMapPatch::Parse: bad patch vertex data" ); + delete patch; + return NULL; + } + + + for ( j = 0; j < patch->GetWidth(); j++ ) { + if ( !src.ExpectTokenString( "(" ) ) { + src.Error( "idMapPatch::Parse: bad vertex row data" ); + delete patch; + return NULL; + } + for ( i = 0; i < patch->GetHeight(); i++ ) { + float v[5]; + + if ( !src.Parse1DMatrix( 5, v ) ) { + src.Error( "idMapPatch::Parse: bad vertex column data" ); + delete patch; + return NULL; + } + + vert = &((*patch)[i * patch->GetWidth() + j]); + vert->xyz[0] = v[0] - origin[0]; + vert->xyz[1] = v[1] - origin[1]; + vert->xyz[2] = v[2] - origin[2]; + vert->SetTexCoord( v[3], v[4] ); + } + if ( !src.ExpectTokenString( ")" ) ) { + delete patch; + src.Error( "idMapPatch::Parse: unable to parse patch control points" ); + return NULL; + } + } + + if ( !src.ExpectTokenString( ")" ) ) { + src.Error( "idMapPatch::Parse: unable to parse patch control points, no closure" ); + delete patch; + return NULL; + } + + // read any key/value pairs + while( src.ReadToken( &token ) ) { + if ( token == "}" ) { + src.ExpectTokenString( "}" ); + break; + } + if ( token.type == TT_STRING ) { + idStr key = token; + src.ExpectTokenType( TT_STRING, 0, &token ); + patch->epairs.Set( key, token ); + } + } + + return patch; +} + +/* +============ +idMapPatch::Write +============ +*/ +bool idMapPatch::Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const { + int i, j; + const idDrawVert *v; + + if ( GetExplicitlySubdivided() ) { + fp->WriteFloatString( "// primitive %d\n{\n patchDef3\n {\n", primitiveNum ); + fp->WriteFloatString( " \"%s\"\n ( %d %d %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight(), GetHorzSubdivisions(), GetVertSubdivisions()); + } else { + fp->WriteFloatString( "// primitive %d\n{\n patchDef2\n {\n", primitiveNum ); + fp->WriteFloatString( " \"%s\"\n ( %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight()); + } + + fp->WriteFloatString( " (\n" ); + idVec2 st; + for ( i = 0; i < GetWidth(); i++ ) { + fp->WriteFloatString( " ( " ); + for ( j = 0; j < GetHeight(); j++ ) { + v = &verts[ j * GetWidth() + i ]; + st = v->GetTexCoord(); + fp->WriteFloatString( " ( %f %f %f %f %f )", v->xyz[0] + origin[0], + v->xyz[1] + origin[1], v->xyz[2] + origin[2], st[0], st[1] ); + } + fp->WriteFloatString( " )\n" ); + } + fp->WriteFloatString( " )\n }\n}\n" ); + + return true; +} + +/* +=============== +idMapPatch::GetGeometryCRC +=============== +*/ +unsigned int idMapPatch::GetGeometryCRC() const { + int i, j; + unsigned int crc; + + crc = GetHorzSubdivisions() ^ GetVertSubdivisions(); + for ( i = 0; i < GetWidth(); i++ ) { + for ( j = 0; j < GetHeight(); j++ ) { + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.x ); + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.y ); + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.z ); + } + } + + crc ^= StringCRC( GetMaterial() ); + + return crc; +} + +/* +================= +idMapBrush::Parse +================= +*/ +idMapBrush *idMapBrush::Parse( idLexer &src, const idVec3 &origin, bool newFormat, float version ) { + int i; + idVec3 planepts[3]; + idToken token; + idList sides; + idMapBrushSide *side; + idDict epairs; + + if ( !src.ExpectTokenString( "{" ) ) { + return NULL; + } + + do { + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapBrush::Parse: unexpected EOF" ); + sides.DeleteContents( true ); + return NULL; + } + if ( token == "}" ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is a brace + if ( token == "(" ) { + break; + } + // the token should be a key string for a key/value pair + if ( token.type != TT_STRING ) { + src.Error( "idMapBrush::Parse: unexpected %s, expected ( or epair key string", token.c_str() ); + sides.DeleteContents( true ); + return NULL; + } + + idStr key = token; + + if ( !src.ReadTokenOnLine( &token ) || token.type != TT_STRING ) { + src.Error( "idMapBrush::Parse: expected epair value string not found" ); + sides.DeleteContents( true ); + return NULL; + } + + epairs.Set( key, token ); + + // try to read the next key + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapBrush::Parse: unexpected EOF" ); + sides.DeleteContents( true ); + return NULL; + } + } while (1); + + src.UnreadToken( &token ); + + side = new (TAG_IDLIB) idMapBrushSide(); + sides.Append(side); + + if ( newFormat ) { + if ( !src.Parse1DMatrix( 4, side->plane.ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + } else { + // read the three point plane definition + if (!src.Parse1DMatrix( 3, planepts[0].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[1].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[2].ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + + planepts[0] -= origin; + planepts[1] -= origin; + planepts[2] -= origin; + + side->plane.FromPoints( planepts[0], planepts[1], planepts[2] ); + } + + // read the texture matrix + // this is odd, because the texmat is 2D relative to default planar texture axis + if ( !src.Parse2DMatrix( 2, 3, side->texMat[0].ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side texture matrix" ); + sides.DeleteContents( true ); + return NULL; + } + side->origin = origin; + + // read the material + if ( !src.ReadTokenOnLine( &token ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side material" ); + sides.DeleteContents( true ); + return NULL; + } + + // we had an implicit 'textures/' in the old format... + if ( version < 2.0f ) { + side->material = "textures/" + token; + } else { + side->material = token; + } + + // Q2 allowed override of default flags and values, but we don't any more + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + } + } + } + } while( 1 ); + + if ( !src.ExpectTokenString( "}" ) ) { + sides.DeleteContents( true ); + return NULL; + } + + idMapBrush *brush = new (TAG_IDLIB) idMapBrush(); + for ( i = 0; i < sides.Num(); i++ ) { + brush->AddSide( sides[i] ); + } + + brush->epairs = epairs; + + return brush; +} + +/* +================= +idMapBrush::ParseQ3 +================= +*/ +idMapBrush *idMapBrush::ParseQ3( idLexer &src, const idVec3 &origin ) { + int i, shift[2], rotate; + float scale[2]; + idVec3 planepts[3]; + idToken token; + idList sides; + idMapBrushSide *side; + idDict epairs; + + do { + if ( src.CheckTokenString( "}" ) ) { + break; + } + + side = new (TAG_IDLIB) idMapBrushSide(); + sides.Append( side ); + + // read the three point plane definition + if (!src.Parse1DMatrix( 3, planepts[0].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[1].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[2].ToFloatPtr() ) ) { + src.Error( "idMapBrush::ParseQ3: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + + planepts[0] -= origin; + planepts[1] -= origin; + planepts[2] -= origin; + + side->plane.FromPoints( planepts[0], planepts[1], planepts[2] ); + + // read the material + if ( !src.ReadTokenOnLine( &token ) ) { + src.Error( "idMapBrush::ParseQ3: unable to read brush side material" ); + sides.DeleteContents( true ); + return NULL; + } + + // we have an implicit 'textures/' in the old format + side->material = "textures/" + token; + + // read the texture shift, rotate and scale + shift[0] = src.ParseInt(); + shift[1] = src.ParseInt(); + rotate = src.ParseInt(); + scale[0] = src.ParseFloat(); + scale[1] = src.ParseFloat(); + side->texMat[0] = idVec3( 0.03125f, 0.0f, 0.0f ); + side->texMat[1] = idVec3( 0.0f, 0.03125f, 0.0f ); + side->origin = origin; + + // Q2 allowed override of default flags and values, but we don't any more + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + } + } + } + } while( 1 ); + + idMapBrush *brush = new (TAG_IDLIB) idMapBrush(); + for ( i = 0; i < sides.Num(); i++ ) { + brush->AddSide( sides[i] ); + } + + brush->epairs = epairs; + + return brush; +} + +/* +============ +idMapBrush::Write +============ +*/ +bool idMapBrush::Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const { + int i; + idMapBrushSide *side; + + fp->WriteFloatString( "// primitive %d\n{\n brushDef3\n {\n", primitiveNum ); + + // write brush epairs + for ( i = 0; i < epairs.GetNumKeyVals(); i++) { + fp->WriteFloatString( " \"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); + } + + // write brush sides + for ( i = 0; i < GetNumSides(); i++ ) { + side = GetSide( i ); + fp->WriteFloatString( " ( %f %f %f %f ) ", side->plane[0], side->plane[1], side->plane[2], side->plane[3] ); + fp->WriteFloatString( "( ( %f %f %f ) ( %f %f %f ) ) \"%s\" 0 0 0\n", + side->texMat[0][0], side->texMat[0][1], side->texMat[0][2], + side->texMat[1][0], side->texMat[1][1], side->texMat[1][2], + side->material.c_str() ); + } + + fp->WriteFloatString( " }\n}\n" ); + + return true; +} + +/* +=============== +idMapBrush::GetGeometryCRC +=============== +*/ +unsigned int idMapBrush::GetGeometryCRC() const { + int i, j; + idMapBrushSide *mapSide; + unsigned int crc; + + crc = 0; + for ( i = 0; i < GetNumSides(); i++ ) { + mapSide = GetSide(i); + for ( j = 0; j < 4; j++ ) { + crc ^= FloatCRC( mapSide->GetPlane()[j] ); + } + crc ^= StringCRC( mapSide->GetMaterial() ); + } + + return crc; +} + +/* +================ +idMapEntity::Parse +================ +*/ +idMapEntity *idMapEntity::Parse( idLexer &src, bool worldSpawn, float version ) { + idToken token; + idMapEntity *mapEnt; + idMapPatch *mapPatch; + idMapBrush *mapBrush; + bool worldent; + idVec3 origin; + double v1, v2, v3; + + if ( !src.ReadToken(&token) ) { + return NULL; + } + + if ( token != "{" ) { + src.Error( "idMapEntity::Parse: { not found, found %s", token.c_str() ); + return NULL; + } + + mapEnt = new (TAG_IDLIB) idMapEntity(); + + if ( worldSpawn ) { + mapEnt->primitives.Resize( 1024, 256 ); + } + + origin.Zero(); + worldent = false; + do { + if ( !src.ReadToken(&token) ) { + src.Error( "idMapEntity::Parse: EOF without closing brace" ); + return NULL; + } + if ( token == "}" ) { + break; + } + + if ( token == "{" ) { + // parse a brush or patch + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapEntity::Parse: unexpected EOF" ); + return NULL; + } + + if ( worldent ) { + origin.Zero(); + } + + // if is it a brush: brush, brushDef, brushDef2, brushDef3 + if ( token.Icmpn( "brush", 5 ) == 0 ) { + mapBrush = idMapBrush::Parse( src, origin, ( !token.Icmp( "brushDef2" ) || !token.Icmp( "brushDef3" ) ), version ); + if ( !mapBrush ) { + return NULL; + } + mapEnt->AddPrimitive( mapBrush ); + } + // if is it a patch: patchDef2, patchDef3 + else if ( token.Icmpn( "patch", 5 ) == 0 ) { + mapPatch = idMapPatch::Parse( src, origin, !token.Icmp( "patchDef3" ), version ); + if ( !mapPatch ) { + return NULL; + } + mapEnt->AddPrimitive( mapPatch ); + } + // assume it's a brush in Q3 or older style + else { + src.UnreadToken( &token ); + mapBrush = idMapBrush::ParseQ3( src, origin ); + if ( !mapBrush ) { + return NULL; + } + mapEnt->AddPrimitive( mapBrush ); + } + } else { + idStr key, value; + + // parse a key / value pair + key = token; + src.ReadTokenOnLine( &token ); + value = token; + + // strip trailing spaces that sometimes get accidentally + // added in the editor + value.StripTrailingWhitespace(); + key.StripTrailingWhitespace(); + + mapEnt->epairs.Set( key, value ); + + if ( !idStr::Icmp( key, "origin" ) ) { + // scanf into doubles, then assign, so it is idVec size independent + v1 = v2 = v3 = 0; + sscanf( value, "%lf %lf %lf", &v1, &v2, &v3 ); + origin.x = v1; + origin.y = v2; + origin.z = v3; + } + else if ( !idStr::Icmp( key, "classname" ) && !idStr::Icmp( value, "worldspawn" ) ) { + worldent = true; + } + } + } while( 1 ); + + return mapEnt; +} + +/* +============ +idMapEntity::Write +============ +*/ +bool idMapEntity::Write( idFile *fp, int entityNum ) const { + int i; + idMapPrimitive *mapPrim; + idVec3 origin; + + fp->WriteFloatString( "// entity %d\n{\n", entityNum ); + + // write entity epairs + for ( i = 0; i < epairs.GetNumKeyVals(); i++) { + fp->WriteFloatString( "\"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); + } + + epairs.GetVector( "origin", "0 0 0", origin ); + + // write pritimives + for ( i = 0; i < GetNumPrimitives(); i++ ) { + mapPrim = GetPrimitive( i ); + + switch( mapPrim->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: + static_cast(mapPrim)->Write( fp, i, origin ); + break; + case idMapPrimitive::TYPE_PATCH: + static_cast(mapPrim)->Write( fp, i, origin ); + break; + } + } + + fp->WriteFloatString( "}\n" ); + + return true; +} + +/* +=============== +idMapEntity::RemovePrimitiveData +=============== +*/ +void idMapEntity::RemovePrimitiveData() { + primitives.DeleteContents(true); +} + +/* +=============== +idMapEntity::GetGeometryCRC +=============== +*/ +unsigned int idMapEntity::GetGeometryCRC() const { + int i; + unsigned int crc; + idMapPrimitive *mapPrim; + + crc = 0; + for ( i = 0; i < GetNumPrimitives(); i++ ) { + mapPrim = GetPrimitive( i ); + + switch( mapPrim->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: + crc ^= static_cast(mapPrim)->GetGeometryCRC(); + break; + case idMapPrimitive::TYPE_PATCH: + crc ^= static_cast(mapPrim)->GetGeometryCRC(); + break; + } + } + + return crc; +} + +/* +=============== +idMapFile::Parse +=============== +*/ +bool idMapFile::Parse( const char *filename, bool ignoreRegion, bool osPath ) { + // no string concatenation for epairs and allow path names for materials + idLexer src( LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + idToken token; + idStr fullName; + idMapEntity *mapEnt; + int i, j, k; + + name = filename; + name.StripFileExtension(); + fullName = name; + hasPrimitiveData = false; + + if ( !ignoreRegion ) { + // try loading a .reg file first + fullName.SetFileExtension( "reg" ); + src.LoadFile( fullName, osPath ); + } + + if ( !src.IsLoaded() ) { + // now try a .map file + fullName.SetFileExtension( "map" ); + src.LoadFile( fullName, osPath ); + if ( !src.IsLoaded() ) { + // didn't get anything at all + return false; + } + } + + version = OLD_MAP_VERSION; + fileTime = src.GetFileTime(); + entities.DeleteContents( true ); + + if ( src.CheckTokenString( "Version" ) ) { + src.ReadTokenOnLine( &token ); + version = token.GetFloatValue(); + } + + while( 1 ) { + mapEnt = idMapEntity::Parse( src, ( entities.Num() == 0 ), version ); + if ( !mapEnt ) { + break; + } + entities.Append( mapEnt ); + } + + SetGeometryCRC(); + + // if the map has a worldspawn + if ( entities.Num() ) { + + // "removeEntities" "classname" can be set in the worldspawn to remove all entities with the given classname + const idKeyValue *removeEntities = entities[0]->epairs.MatchPrefix( "removeEntities", NULL ); + while ( removeEntities ) { + RemoveEntities( removeEntities->GetValue() ); + removeEntities = entities[0]->epairs.MatchPrefix( "removeEntities", removeEntities ); + } + + // "overrideMaterial" "material" can be set in the worldspawn to reset all materials + idStr material; + if ( entities[0]->epairs.GetString( "overrideMaterial", "", material ) ) { + for ( i = 0; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) { + idMapPrimitive *mapPrimitive = mapEnt->GetPrimitive( j ); + switch( mapPrimitive->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: { + idMapBrush *mapBrush = static_cast(mapPrimitive); + for ( k = 0; k < mapBrush->GetNumSides(); k++ ) { + mapBrush->GetSide( k )->SetMaterial( material ); + } + break; + } + case idMapPrimitive::TYPE_PATCH: { + static_cast(mapPrimitive)->SetMaterial( material ); + break; + } + } + } + } + } + + // force all entities to have a name key/value pair + if ( entities[0]->epairs.GetBool( "forceEntityNames" ) ) { + for ( i = 1; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + if ( !mapEnt->epairs.FindKey( "name" ) ) { + mapEnt->epairs.Set( "name", va( "%s%d", mapEnt->epairs.GetString( "classname", "forcedName" ), i ) ); + } + } + } + + // move the primitives of any func_group entities to the worldspawn + if ( entities[0]->epairs.GetBool( "moveFuncGroups" ) ) { + for ( i = 1; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + if ( idStr::Icmp( mapEnt->epairs.GetString( "classname" ), "func_group" ) == 0 ) { + entities[0]->primitives.Append( mapEnt->primitives ); + mapEnt->primitives.Clear(); + } + } + } + } + + hasPrimitiveData = true; + return true; +} + +/* +============ +idMapFile::Write +============ +*/ +bool idMapFile::Write( const char *fileName, const char *ext, bool fromBasePath ) { + int i; + idStr qpath; + idFile *fp; + + qpath = fileName; + qpath.SetFileExtension( ext ); + + idLib::common->Printf( "writing %s...\n", qpath.c_str() ); + + if ( fromBasePath ) { + fp = idLib::fileSystem->OpenFileWrite( qpath, "fs_basepath" ); + } + else { + fp = idLib::fileSystem->OpenExplicitFileWrite( qpath ); + } + + if ( !fp ) { + idLib::common->Warning( "Couldn't open %s\n", qpath.c_str() ); + return false; + } + + fp->WriteFloatString( "Version %f\n", (float) CURRENT_MAP_VERSION ); + + for ( i = 0; i < entities.Num(); i++ ) { + entities[i]->Write( fp, i ); + } + + idLib::fileSystem->CloseFile( fp ); + + return true; +} + +/* +=============== +idMapFile::SetGeometryCRC +=============== +*/ +void idMapFile::SetGeometryCRC() { + int i; + + geometryCRC = 0; + for ( i = 0; i < entities.Num(); i++ ) { + geometryCRC ^= entities[i]->GetGeometryCRC(); + } +} + +/* +=============== +idMapFile::AddEntity +=============== +*/ +int idMapFile::AddEntity( idMapEntity *mapEnt ) { + int ret = entities.Append( mapEnt ); + return ret; +} + +/* +=============== +idMapFile::FindEntity +=============== +*/ +idMapEntity *idMapFile::FindEntity( const char *name ) { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + if ( idStr::Icmp( ent->epairs.GetString( "name" ), name ) == 0 ) { + return ent; + } + } + return NULL; +} + +/* +=============== +idMapFile::RemoveEntity +=============== +*/ +void idMapFile::RemoveEntity( idMapEntity *mapEnt ) { + entities.Remove( mapEnt ); + delete mapEnt; +} + +/* +=============== +idMapFile::RemoveEntity +=============== +*/ +void idMapFile::RemoveEntities( const char *classname ) { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + if ( idStr::Icmp( ent->epairs.GetString( "classname" ), classname ) == 0 ) { + delete entities[i]; + entities.RemoveIndex( i ); + i--; + } + } +} + +/* +=============== +idMapFile::RemoveAllEntities +=============== +*/ +void idMapFile::RemoveAllEntities() { + entities.DeleteContents( true ); + hasPrimitiveData = false; +} + +/* +=============== +idMapFile::RemovePrimitiveData +=============== +*/ +void idMapFile::RemovePrimitiveData() { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + ent->RemovePrimitiveData(); + } + hasPrimitiveData = false; +} + +/* +=============== +idMapFile::NeedsReload +=============== +*/ +bool idMapFile::NeedsReload() { + if ( name.Length() ) { + ID_TIME_T time = FILE_NOT_FOUND_TIMESTAMP; + if ( idLib::fileSystem->ReadFile( name, NULL, &time ) > 0 ) { + return ( time > fileTime ); + } + } + return true; +} diff --git a/neo/idlib/MapFile.h b/neo/idlib/MapFile.h new file mode 100644 index 00000000..33dd2027 --- /dev/null +++ b/neo/idlib/MapFile.h @@ -0,0 +1,237 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MAPFILE_H__ +#define __MAPFILE_H__ + +/* +=============================================================================== + + Reads or writes the contents of .map files into a standard internal + format, which can then be moved into private formats for collision + detection, map processing, or editor use. + + No validation (duplicate planes, null area brushes, etc) is performed. + There are no limits to the number of any of the elements in maps. + The order of entities, brushes, and sides is maintained. + +=============================================================================== +*/ + +const int OLD_MAP_VERSION = 1; +const int CURRENT_MAP_VERSION = 2; +const int DEFAULT_CURVE_SUBDIVISION = 4; +const float DEFAULT_CURVE_MAX_ERROR = 4.0f; +const float DEFAULT_CURVE_MAX_ERROR_CD = 24.0f; +const float DEFAULT_CURVE_MAX_LENGTH = -1.0f; +const float DEFAULT_CURVE_MAX_LENGTH_CD = -1.0f; + + +class idMapPrimitive { +public: + enum { TYPE_INVALID = -1, TYPE_BRUSH, TYPE_PATCH }; + + idDict epairs; + + idMapPrimitive() { type = TYPE_INVALID; } + virtual ~idMapPrimitive() { } + int GetType() const { return type; } + +protected: + int type; +}; + + +class idMapBrushSide { + friend class idMapBrush; + +public: + idMapBrushSide(); + ~idMapBrushSide() { } + const char * GetMaterial() const { return material; } + void SetMaterial( const char *p ) { material = p; } + const idPlane & GetPlane() const { return plane; } + void SetPlane( const idPlane &p ) { plane = p; } + void SetTextureMatrix( const idVec3 mat[2] ) { texMat[0] = mat[0]; texMat[1] = mat[1]; } + void GetTextureMatrix( idVec3 &mat1, idVec3 &mat2 ) { mat1 = texMat[0]; mat2 = texMat[1]; } + void GetTextureVectors( idVec4 v[2] ) const; + +protected: + idStr material; + idPlane plane; + idVec3 texMat[2]; + idVec3 origin; +}; + +ID_INLINE idMapBrushSide::idMapBrushSide() { + plane.Zero(); + texMat[0].Zero(); + texMat[1].Zero(); + origin.Zero(); +} + + +class idMapBrush : public idMapPrimitive { +public: + idMapBrush() { type = TYPE_BRUSH; sides.Resize( 8, 4 ); } + ~idMapBrush() { sides.DeleteContents( true ); } + static idMapBrush * Parse( idLexer &src, const idVec3 &origin, bool newFormat = true, float version = CURRENT_MAP_VERSION ); + static idMapBrush * ParseQ3( idLexer &src, const idVec3 &origin ); + bool Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const; + int GetNumSides() const { return sides.Num(); } + int AddSide( idMapBrushSide *side ) { return sides.Append( side ); } + idMapBrushSide * GetSide( int i ) const { return sides[i]; } + unsigned int GetGeometryCRC() const; + +protected: + int numSides; + idList sides; +}; + + +class idMapPatch : public idMapPrimitive, public idSurface_Patch { +public: + idMapPatch(); + idMapPatch( int maxPatchWidth, int maxPatchHeight ); + ~idMapPatch() { } + static idMapPatch * Parse( idLexer &src, const idVec3 &origin, bool patchDef3 = true, float version = CURRENT_MAP_VERSION ); + bool Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const; + const char * GetMaterial() const { return material; } + void SetMaterial( const char *p ) { material = p; } + int GetHorzSubdivisions() const { return horzSubdivisions; } + int GetVertSubdivisions() const { return vertSubdivisions; } + bool GetExplicitlySubdivided() const { return explicitSubdivisions; } + void SetHorzSubdivisions( int n ) { horzSubdivisions = n; } + void SetVertSubdivisions( int n ) { vertSubdivisions = n; } + void SetExplicitlySubdivided( bool b ) { explicitSubdivisions = b; } + unsigned int GetGeometryCRC() const; + +protected: + idStr material; + int horzSubdivisions; + int vertSubdivisions; + bool explicitSubdivisions; +}; + +ID_INLINE idMapPatch::idMapPatch() { + type = TYPE_PATCH; + horzSubdivisions = vertSubdivisions = 0; + explicitSubdivisions = false; + width = height = 0; + maxWidth = maxHeight = 0; + expanded = false; +} + +ID_INLINE idMapPatch::idMapPatch( int maxPatchWidth, int maxPatchHeight ) { + type = TYPE_PATCH; + horzSubdivisions = vertSubdivisions = 0; + explicitSubdivisions = false; + width = height = 0; + maxWidth = maxPatchWidth; + maxHeight = maxPatchHeight; + verts.SetNum( maxWidth * maxHeight ); + expanded = false; +} + + +class idMapEntity { + friend class idMapFile; + +public: + idDict epairs; + +public: + idMapEntity() { epairs.SetHashSize( 64 ); } + ~idMapEntity() { primitives.DeleteContents( true ); } + static idMapEntity * Parse( idLexer &src, bool worldSpawn = false, float version = CURRENT_MAP_VERSION ); + bool Write( idFile *fp, int entityNum ) const; + int GetNumPrimitives() const { return primitives.Num(); } + idMapPrimitive * GetPrimitive( int i ) const { return primitives[i]; } + void AddPrimitive( idMapPrimitive *p ) { primitives.Append( p ); } + unsigned int GetGeometryCRC() const; + void RemovePrimitiveData(); + +protected: + idList primitives; +}; + + +class idMapFile { +public: + idMapFile(); + ~idMapFile() { entities.DeleteContents( true ); } + + // filename does not require an extension + // normally this will use a .reg file instead of a .map file if it exists, + // which is what the game and dmap want, but the editor will want to always + // load a .map file + bool Parse( const char *filename, bool ignoreRegion = false, bool osPath = false ); + bool Write( const char *fileName, const char *ext, bool fromBasePath = true ); + // get the number of entities in the map + int GetNumEntities() const { return entities.Num(); } + // get the specified entity + idMapEntity * GetEntity( int i ) const { return entities[i]; } + // get the name without file extension + const char * GetName() const { return name; } + // get the file time + ID_TIME_T GetFileTime() const { return fileTime; } + // get CRC for the map geometry + // texture coordinates and entity key/value pairs are not taken into account + unsigned int GetGeometryCRC() const { return geometryCRC; } + // returns true if the file on disk changed + bool NeedsReload(); + + int AddEntity( idMapEntity *mapentity ); + idMapEntity * FindEntity( const char *name ); + void RemoveEntity( idMapEntity *mapEnt ); + void RemoveEntities( const char *classname ); + void RemoveAllEntities(); + void RemovePrimitiveData(); + bool HasPrimitiveData() { return hasPrimitiveData; } + +protected: + float version; + ID_TIME_T fileTime; + unsigned int geometryCRC; + idList entities; + idStr name; + bool hasPrimitiveData; + +private: + void SetGeometryCRC(); +}; + +ID_INLINE idMapFile::idMapFile() { + version = CURRENT_MAP_VERSION; + fileTime = 0; + geometryCRC = 0; + entities.Resize( 1024, 256 ); + hasPrimitiveData = false; +} + +#endif /* !__MAPFILE_H__ */ diff --git a/neo/idlib/ParallelJobList.cpp b/neo/idlib/ParallelJobList.cpp new file mode 100644 index 00000000..ff4bf365 --- /dev/null +++ b/neo/idlib/ParallelJobList.cpp @@ -0,0 +1,1297 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "precompiled.h" +#include "ParallelJobList.h" + +/* +================================================================================================ + + Job and Job-List names + +================================================================================================ +*/ + +const char * jobNames[] = { + ASSERT_ENUM_STRING( JOBLIST_RENDERER_FRONTEND, 0 ), + ASSERT_ENUM_STRING( JOBLIST_RENDERER_BACKEND, 1 ), + ASSERT_ENUM_STRING( JOBLIST_UTILITY, 9 ), +}; + +static const int MAX_REGISTERED_JOBS = 128; +struct registeredJob { + jobRun_t function; + const char * name; +} registeredJobs[MAX_REGISTERED_JOBS]; +static int numRegisteredJobs; + +const char * GetJobListName( jobListId_t id ) { + return jobNames[id]; +} + +/* +======================== +IsRegisteredJob +======================== +*/ +static bool IsRegisteredJob( jobRun_t function ) { + for ( int i = 0; i < numRegisteredJobs; i++ ) { + if ( registeredJobs[i].function == function ) { + return true; + } + } + return false; +} + +/* +======================== +RegisterJob +======================== +*/ +void RegisterJob( jobRun_t function, const char * name ) { + if ( IsRegisteredJob( function ) ) { + return; + } + registeredJobs[numRegisteredJobs].function = function; + registeredJobs[numRegisteredJobs].name = name; + numRegisteredJobs++; +} + +/* +======================== +GetJobName +======================== +*/ +const char * GetJobName( jobRun_t function ) { + for ( int i = 0; i < numRegisteredJobs; i++ ) { + if ( registeredJobs[i].function == function ) { + return registeredJobs[i].name; + } + } + return "unknown"; +} + +/* +======================== +idParallelJobRegistration::idParallelJobRegistration +======================== +*/ +idParallelJobRegistration::idParallelJobRegistration( jobRun_t function, const char * name ) { + RegisterJob( function, name ); +} + +int globalSpuLocalStoreActive; +void * globalSpuLocalStoreStart; +void * globalSpuLocalStoreEnd; +idSysMutex globalSpuLocalStoreMutex; + +/* +================================================================================================ + + PS3 + +================================================================================================ +*/ + + +/* +================================================================================================ + +idParallelJobList_Threads + +================================================================================================ +*/ + +static idCVar jobs_longJobMicroSec( "jobs_longJobMicroSec", "10000", CVAR_INTEGER, "print a warning for jobs that take more than this number of microseconds" ); + + +const static int MAX_THREADS = 32; + +struct threadJobListState_t { + threadJobListState_t() : + jobList( NULL ), + version( 0xFFFFFFFF ), + signalIndex( 0 ), + lastJobIndex( 0 ), + nextJobIndex( -1 ) {} + threadJobListState_t( int _version ) : + jobList( NULL ), + version( _version ), + signalIndex( 0 ), + lastJobIndex( 0 ), + nextJobIndex( -1 ) {} + idParallelJobList_Threads * jobList; + int version; + int signalIndex; + int lastJobIndex; + int nextJobIndex; +}; + +struct threadStats_t { + unsigned int numExecutedJobs; + unsigned int numExecutedSyncs; + uint64 submitTime; + uint64 startTime; + uint64 endTime; + uint64 waitTime; + uint64 threadExecTime[MAX_THREADS]; + uint64 threadTotalTime[MAX_THREADS]; +}; + +class idParallelJobList_Threads { +public: + idParallelJobList_Threads( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs ); + ~idParallelJobList_Threads(); + + //------------------------ + // These are called from the one thread that manages this list. + //------------------------ + ID_INLINE void AddJob( jobRun_t function, void * data ); + ID_INLINE void InsertSyncPoint( jobSyncType_t syncType ); + void Submit( idParallelJobList_Threads * waitForJobList_, int parallelism ); + void Wait(); + bool TryWait(); + bool IsSubmitted() const; + + unsigned int GetNumExecutedJobs() const { return threadStats.numExecutedJobs; } + unsigned int GetNumSyncs() const { return threadStats.numExecutedSyncs; } + uint64 GetSubmitTimeMicroSec() const { return threadStats.submitTime; } + uint64 GetStartTimeMicroSec() const { return threadStats.startTime; } + uint64 GetFinishTimeMicroSec() const { return threadStats.endTime; } + uint64 GetWaitTimeMicroSec() const { return threadStats.waitTime; } + uint64 GetTotalProcessingTimeMicroSec() const; + uint64 GetTotalWastedTimeMicroSec() const; + uint64 GetUnitProcessingTimeMicroSec( int unit ) const; + uint64 GetUnitWastedTimeMicroSec( int unit ) const; + + jobListId_t GetId() const { return listId; } + jobListPriority_t GetPriority() const { return listPriority; } + int GetVersion() { return version.GetValue(); } + + bool WaitForOtherJobList(); + + //------------------------ + // This is thread safe and called from the job threads. + //------------------------ + enum runResult_t { + RUN_OK = 0, + RUN_PROGRESS = BIT( 0 ), + RUN_DONE = BIT( 1 ), + RUN_STALLED = BIT( 2 ) + }; + + int RunJobs( unsigned int threadNum, threadJobListState_t & state, bool singleJob ); + +private: + static const int NUM_DONE_GUARDS = 4; // cycle through 4 guards so we can cyclicly chain job lists + + bool threaded; + bool done; + bool hasSignal; + jobListId_t listId; + jobListPriority_t listPriority; + unsigned int maxJobs; + unsigned int maxSyncs; + unsigned int numSyncs; + int lastSignalJob; + idSysInterlockedInteger * waitForGuard; + idSysInterlockedInteger doneGuards[NUM_DONE_GUARDS]; + int currentDoneGuard; + idSysInterlockedInteger version; + struct job_t { + jobRun_t function; + void * data; + int executed; + }; + idList< job_t, TAG_JOBLIST > jobList; + idList< idSysInterlockedInteger, TAG_JOBLIST > signalJobCount; + idSysInterlockedInteger currentJob; + idSysInterlockedInteger fetchLock; + idSysInterlockedInteger numThreadsExecuting; + + threadStats_t deferredThreadStats; + threadStats_t threadStats; + + int RunJobsInternal( unsigned int threadNum, threadJobListState_t & state, bool singleJob ); + + static void Nop( void * data ) {} + + static int JOB_SIGNAL; + static int JOB_SYNCHRONIZE; + static int JOB_LIST_DONE; +}; + +int idParallelJobList_Threads::JOB_SIGNAL; +int idParallelJobList_Threads::JOB_SYNCHRONIZE; +int idParallelJobList_Threads::JOB_LIST_DONE; + +/* +======================== +idParallelJobList_Threads::idParallelJobList_Threads +======================== +*/ +idParallelJobList_Threads::idParallelJobList_Threads( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs ) : + threaded( true ), + done( true ), + hasSignal( false ), + listId( id ), + listPriority( priority ), + numSyncs( 0 ), + lastSignalJob( 0 ), + waitForGuard( NULL ), + currentDoneGuard( 0 ), + jobList() { + + assert( listPriority != JOBLIST_PRIORITY_NONE ); + + this->maxJobs = maxJobs; + this->maxSyncs = maxSyncs; + jobList.AssureSize( maxJobs + maxSyncs * 2 + 1 ); // syncs go in as dummy jobs and one more to update the doneCount + jobList.SetNum( 0 ); + signalJobCount.AssureSize( maxSyncs + 1 ); // need one extra for submit + signalJobCount.SetNum( 0 ); + + memset( &deferredThreadStats, 0, sizeof( threadStats_t ) ); + memset( &threadStats, 0, sizeof( threadStats_t ) ); +} + +/* +======================== +idParallelJobList_Threads::~idParallelJobList_Threads +======================== +*/ +idParallelJobList_Threads::~idParallelJobList_Threads() { + Wait(); +} + +/* +======================== +idParallelJobList_Threads::AddJob +======================== +*/ +ID_INLINE void idParallelJobList_Threads::AddJob( jobRun_t function, void * data ) { + assert( done ); +#if defined( _DEBUG ) + // make sure there isn't already a job with the same function and data in the list + if ( jobList.Num() < 1000 ) { // don't do this N^2 slow check on big lists + for ( int i = 0; i < jobList.Num(); i++ ) { + assert( jobList[i].function != function || jobList[i].data != data ); + } + } +#endif + if ( 1 ) { // JDC: this never worked in tech5! !jobList.IsFull() ) { + job_t & job = jobList.Alloc(); + job.function = function; + job.data = data; + job.executed = 0; + } else { + // debug output to show us what is overflowing + int currentJobCount[MAX_REGISTERED_JOBS] = {}; + + for ( int i = 0; i < jobList.Num(); ++i ) { + const char * jobName = GetJobName( jobList[ i ].function ); + for ( int j = 0; j < numRegisteredJobs; ++j ) { + if ( jobName == registeredJobs[ j ].name ) { + currentJobCount[ j ]++; + break; + } + } + } + + // print the quantity of each job type + for ( int i = 0; i < numRegisteredJobs; ++i ) { + if ( currentJobCount[ i ] > 0 ) { + idLib::Printf( "Job: %s, # %d", registeredJobs[ i ].name, currentJobCount[ i ] ); + } + } + idLib::Error( "Can't add job '%s', too many jobs %d", GetJobName( function ), jobList.Num() ); + } +} + +/* +======================== +idParallelJobList_Threads::InsertSyncPoint +======================== +*/ +ID_INLINE void idParallelJobList_Threads::InsertSyncPoint( jobSyncType_t syncType ) { + assert( done ); + switch( syncType ) { + case SYNC_SIGNAL: { + assert( !hasSignal ); + if ( jobList.Num() ) { + assert( !hasSignal ); + signalJobCount.Alloc(); + signalJobCount[signalJobCount.Num() - 1].SetValue( jobList.Num() - lastSignalJob ); + lastSignalJob = jobList.Num(); + job_t & job = jobList.Alloc(); + job.function = Nop; + job.data = & JOB_SIGNAL; + hasSignal = true; + } + break; + } + case SYNC_SYNCHRONIZE: { + if ( hasSignal ) { + job_t & job = jobList.Alloc(); + job.function = Nop; + job.data = & JOB_SYNCHRONIZE; + hasSignal = false; + numSyncs++; + } + break; + } + } +} + +/* +======================== +idParallelJobList_Threads::Submit +======================== +*/ +void idParallelJobList_Threads::Submit( idParallelJobList_Threads * waitForJobList, int parallelism ) { + assert( done ); + assert( numSyncs <= maxSyncs ); + assert( (unsigned int) jobList.Num() <= maxJobs + numSyncs * 2 ); + assert( fetchLock.GetValue() == 0 ); + + done = false; + currentJob.SetValue( 0 ); + + memset( &deferredThreadStats, 0, sizeof( deferredThreadStats ) ); + deferredThreadStats.numExecutedJobs = jobList.Num() - numSyncs * 2; + deferredThreadStats.numExecutedSyncs = numSyncs; + deferredThreadStats.submitTime = Sys_Microseconds(); + deferredThreadStats.startTime = 0; + deferredThreadStats.endTime = 0; + deferredThreadStats.waitTime = 0; + + if ( jobList.Num() == 0 ) { + return; + } + + if ( waitForJobList != NULL ) { + waitForGuard = & waitForJobList->doneGuards[waitForJobList->currentDoneGuard]; + } else { + waitForGuard = NULL; + } + + currentDoneGuard = ( currentDoneGuard + 1 ) & ( NUM_DONE_GUARDS - 1 ); + doneGuards[currentDoneGuard].SetValue( 1 ); + + signalJobCount.Alloc(); + signalJobCount[signalJobCount.Num() - 1].SetValue( jobList.Num() - lastSignalJob ); + + job_t & job = jobList.Alloc(); + job.function = Nop; + job.data = & JOB_LIST_DONE; + + if ( threaded ) { + // hand over to the manager + void SubmitJobList( idParallelJobList_Threads * jobList, int parallelism ); + SubmitJobList( this, parallelism ); + } else { + // run all the jobs right here + threadJobListState_t state( GetVersion() ); + RunJobs( 0, state, false ); + } +} + +/* +======================== +idParallelJobList_Threads::Wait +======================== +*/ +void idParallelJobList_Threads::Wait() { + if ( jobList.Num() > 0 ) { + // don't lock up but return if the job list was never properly submitted + if ( !verify( !done && signalJobCount.Num() > 0 ) ) { + return; + } + + bool waited = false; + uint64 waitStart = Sys_Microseconds(); + + while ( signalJobCount[signalJobCount.Num() - 1].GetValue() > 0 ) { + Sys_Yield(); + waited = true; + } + version.Increment(); + while ( numThreadsExecuting.GetValue() > 0 ) { + Sys_Yield(); + waited = true; + } + + jobList.SetNum( 0 ); + signalJobCount.SetNum( 0 ); + numSyncs = 0; + lastSignalJob = 0; + + uint64 waitEnd = Sys_Microseconds(); + deferredThreadStats.waitTime = waited ? ( waitEnd - waitStart ) : 0; + } + memcpy( & threadStats, & deferredThreadStats, sizeof( threadStats ) ); + done = true; +} + +/* +======================== +idParallelJobList_Threads::TryWait +======================== +*/ +bool idParallelJobList_Threads::TryWait() { + if ( jobList.Num() == 0 || signalJobCount[signalJobCount.Num() - 1].GetValue() <= 0 ) { + Wait(); + return true; + } + return false; +} + +/* +======================== +idParallelJobList_Threads::IsSubmitted +======================== +*/ +bool idParallelJobList_Threads::IsSubmitted() const { + return !done; +} + +/* +======================== +idParallelJobList_Threads::GetTotalProcessingTimeMicroSec +======================== +*/ +uint64 idParallelJobList_Threads::GetTotalProcessingTimeMicroSec() const { + uint64 total = 0; + for ( int unit = 0; unit < MAX_THREADS; unit++ ) { + total += threadStats.threadExecTime[unit]; + } + return total; +} + +/* +======================== +idParallelJobList_Threads::GetTotalWastedTimeMicroSec +======================== +*/ +uint64 idParallelJobList_Threads::GetTotalWastedTimeMicroSec() const { + uint64 total = 0; + for ( int unit = 0; unit < MAX_THREADS; unit++ ) { + total += threadStats.threadTotalTime[unit] - threadStats.threadExecTime[unit]; + } + return total; +} + +/* +======================== +idParallelJobList_Threads::GetUnitProcessingTimeMicroSec +======================== +*/ +uint64 idParallelJobList_Threads::GetUnitProcessingTimeMicroSec( int unit ) const { + if ( unit < 0 || unit >= MAX_THREADS ) { + return 0; + } + return threadStats.threadExecTime[unit]; +} + +/* +======================== +idParallelJobList_Threads::GetUnitWastedTimeMicroSec +======================== +*/ +uint64 idParallelJobList_Threads::GetUnitWastedTimeMicroSec( int unit ) const { + if ( unit < 0 || unit >= MAX_THREADS ) { + return 0; + } + return threadStats.threadTotalTime[unit] - threadStats.threadExecTime[unit]; +} + +#ifndef _DEBUG +volatile float longJobTime; +volatile jobRun_t longJobFunc; +volatile void * longJobData; +#endif + +/* +======================== +idParallelJobList_Threads::RunJobsInternal +======================== +*/ +int idParallelJobList_Threads::RunJobsInternal( unsigned int threadNum, threadJobListState_t & state, bool singleJob ) { + if ( state.version != version.GetValue() ) { + // trying to run an old version of this list that is already done + return RUN_DONE; + } + + assert( threadNum < MAX_THREADS ); + + if ( deferredThreadStats.startTime == 0 ) { + deferredThreadStats.startTime = Sys_Microseconds(); // first time any thread is running jobs from this list + } + + int result = RUN_OK; + + do { + + // run through all signals and syncs before the last job that has been or is being executed + // this loop is really an optimization to minimize the time spent in the fetchLock section below + for ( ; state.lastJobIndex < (int) currentJob.GetValue() && state.lastJobIndex < jobList.Num(); state.lastJobIndex++ ) { + if ( jobList[state.lastJobIndex].data == & JOB_SIGNAL ) { + state.signalIndex++; + assert( state.signalIndex < signalJobCount.Num() ); + } else if ( jobList[state.lastJobIndex].data == & JOB_SYNCHRONIZE ) { + assert( state.signalIndex > 0 ); + if ( signalJobCount[state.signalIndex - 1].GetValue() > 0 ) { + // stalled on a synchronization point + return ( result | RUN_STALLED ); + } + } else if ( jobList[state.lastJobIndex].data == & JOB_LIST_DONE ) { + if ( signalJobCount[signalJobCount.Num() - 1].GetValue() > 0 ) { + // stalled on a synchronization point + return ( result | RUN_STALLED ); + } + } + } + + // try to lock to fetch a new job + if ( fetchLock.Increment() == 1 ) { + + // grab a new job + state.nextJobIndex = currentJob.Increment() - 1; + + // run through any remaining signals and syncs (this should rarely iterate more than once) + for ( ; state.lastJobIndex <= state.nextJobIndex && state.lastJobIndex < jobList.Num(); state.lastJobIndex++ ) { + if ( jobList[state.lastJobIndex].data == & JOB_SIGNAL ) { + state.signalIndex++; + assert( state.signalIndex < signalJobCount.Num() ); + } else if ( jobList[state.lastJobIndex].data == & JOB_SYNCHRONIZE ) { + assert( state.signalIndex > 0 ); + if ( signalJobCount[state.signalIndex - 1].GetValue() > 0 ) { + // return this job to the list + currentJob.Decrement(); + // release the fetch lock + fetchLock.Decrement(); + // stalled on a synchronization point + return ( result | RUN_STALLED ); + } + } else if ( jobList[state.lastJobIndex].data == & JOB_LIST_DONE ) { + if ( signalJobCount[signalJobCount.Num() - 1].GetValue() > 0 ) { + // return this job to the list + currentJob.Decrement(); + // release the fetch lock + fetchLock.Decrement(); + // stalled on a synchronization point + return ( result | RUN_STALLED ); + } + // decrement the done count + doneGuards[currentDoneGuard].Decrement(); + } + } + // release the fetch lock + fetchLock.Decrement(); + } else { + // release the fetch lock + fetchLock.Decrement(); + // another thread is fetching right now so consider stalled + return ( result | RUN_STALLED ); + } + + // if at the end of the job list we're done + if ( state.nextJobIndex >= jobList.Num() ) { + return ( result | RUN_DONE ); + } + + // execute the next job + { + uint64 jobStart = Sys_Microseconds(); + + jobList[state.nextJobIndex].function( jobList[state.nextJobIndex].data ); + jobList[state.nextJobIndex].executed = 1; + + uint64 jobEnd = Sys_Microseconds(); + deferredThreadStats.threadExecTime[threadNum] += jobEnd - jobStart; + +#ifndef _DEBUG + if ( jobs_longJobMicroSec.GetInteger() > 0 ) { + if ( jobEnd - jobStart > jobs_longJobMicroSec.GetInteger() + && GetId() != JOBLIST_UTILITY ) { + longJobTime = ( jobEnd - jobStart ) * ( 1.0f / 1000.0f ); + longJobFunc = jobList[state.nextJobIndex].function; + longJobData = jobList[state.nextJobIndex].data; + const char * jobName = GetJobName( jobList[state.nextJobIndex].function ); + const char * jobListName = GetJobListName( GetId() ); + idLib::Printf( "%1.1f milliseconds for a single '%s' job from job list %s on thread %d\n", longJobTime, jobName, jobListName, threadNum ); + } + } +#endif + } + + result |= RUN_PROGRESS; + + // decrease the job count for the current signal + if ( signalJobCount[state.signalIndex].Decrement() == 0 ) { + // if this was the very last job of the job list + if ( state.signalIndex == signalJobCount.Num() - 1 ) { + deferredThreadStats.endTime = Sys_Microseconds(); + return ( result | RUN_DONE ); + } + } + + } while( ! singleJob ); + + return result; +} + +/* +======================== +idParallelJobList_Threads::RunJobs +======================== +*/ +int idParallelJobList_Threads::RunJobs( unsigned int threadNum, threadJobListState_t & state, bool singleJob ) { + uint64 start = Sys_Microseconds(); + + numThreadsExecuting.Increment(); + + int result = RunJobsInternal( threadNum, state, singleJob ); + + numThreadsExecuting.Decrement(); + + deferredThreadStats.threadTotalTime[threadNum] += Sys_Microseconds() - start; + + return result; +} + +/* +======================== +idParallelJobList_Threads::WaitForOtherJobList +======================== +*/ +bool idParallelJobList_Threads::WaitForOtherJobList() { + if ( waitForGuard != NULL ) { + if ( waitForGuard->GetValue() > 0 ) { + return true; + } + } + return false; +} + +/* +================================================================================================ + +idParallelJobList + +================================================================================================ +*/ + +/* +======================== +idParallelJobList::idParallelJobList +======================== +*/ +idParallelJobList::idParallelJobList( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs, const idColor * color ) { + assert( priority > JOBLIST_PRIORITY_NONE ); + this->jobListThreads = new (TAG_JOBLIST) idParallelJobList_Threads( id, priority, maxJobs, maxSyncs ); + this->color = color; +} + +/* +======================== +idParallelJobList::~idParallelJobList +======================== +*/ +idParallelJobList::~idParallelJobList() { + delete jobListThreads; +} + +/* +======================== +idParallelJobList::AddJob +======================== +*/ +void idParallelJobList::AddJob( jobRun_t function, void * data ) { + assert( IsRegisteredJob( function ) ); + jobListThreads->AddJob( function, data ); +} + +/* +======================== +idParallelJobList::AddJobSPURS +======================== +*/ +CellSpursJob128 * idParallelJobList::AddJobSPURS() { + return NULL; +} + +/* +======================== +idParallelJobList::InsertSyncPoint +======================== +*/ +void idParallelJobList::InsertSyncPoint( jobSyncType_t syncType ) { + jobListThreads->InsertSyncPoint( syncType ); +} + +/* +======================== +idParallelJobList::Wait +======================== +*/ +void idParallelJobList::Wait() { + if ( jobListThreads != NULL ) { + jobListThreads->Wait(); + } +} + +/* +======================== +idParallelJobList::TryWait +======================== +*/ +bool idParallelJobList::TryWait() { + bool done = true; + if ( jobListThreads != NULL ) { + done &= jobListThreads->TryWait(); + } + return done; +} + +/* +======================== +idParallelJobList::Submit +======================== +*/ +void idParallelJobList::Submit( idParallelJobList * waitForJobList, int parallelism ) { + assert( waitForJobList != this ); + jobListThreads->Submit( ( waitForJobList != NULL ) ? waitForJobList->jobListThreads : NULL, parallelism ); +} + +/* +======================== +idParallelJobList::IsSubmitted +======================== +*/ +bool idParallelJobList::IsSubmitted() const { + return jobListThreads->IsSubmitted(); +} + +/* +======================== +idParallelJobList::GetNumExecutedJobs +======================== +*/ +unsigned int idParallelJobList::GetNumExecutedJobs() const { + return jobListThreads->GetNumExecutedJobs(); +} + +/* +======================== +idParallelJobList::GetNumSyncs +======================== +*/ +unsigned int idParallelJobList::GetNumSyncs() const { + return jobListThreads->GetNumSyncs(); +} + +/* +======================== +idParallelJobList::GetSubmitTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetSubmitTimeMicroSec() const { + return jobListThreads->GetSubmitTimeMicroSec(); +} + +/* +======================== +idParallelJobList::GetStartTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetStartTimeMicroSec() const { + return jobListThreads->GetStartTimeMicroSec(); +} + +/* +======================== +idParallelJobList::GetFinishTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetFinishTimeMicroSec() const { + return jobListThreads->GetFinishTimeMicroSec(); +} + +/* +======================== +idParallelJobList::GetWaitTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetWaitTimeMicroSec() const { + return jobListThreads->GetWaitTimeMicroSec(); +} + +/* +======================== +idParallelJobList::GetTotalProcessingTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetTotalProcessingTimeMicroSec() const { + return jobListThreads->GetTotalProcessingTimeMicroSec(); +} + +/* +======================== +idParallelJobList::GetTotalWastedTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetTotalWastedTimeMicroSec() const { + return jobListThreads->GetTotalWastedTimeMicroSec(); +} + +/* +======================== +idParallelJobList::GetUnitProcessingTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetUnitProcessingTimeMicroSec( int unit ) const { + return jobListThreads->GetUnitProcessingTimeMicroSec( unit ); +} + +/* +======================== +idParallelJobList::GetUnitWastedTimeMicroSec +======================== +*/ +uint64 idParallelJobList::GetUnitWastedTimeMicroSec( int unit ) const { + return jobListThreads->GetUnitWastedTimeMicroSec( unit ); +} + +/* +======================== +idParallelJobList::GetId +======================== +*/ +jobListId_t idParallelJobList::GetId() const { + return jobListThreads->GetId(); +} + +/* +================================================================================================ + +idJobThread + +================================================================================================ +*/ + +const int JOB_THREAD_STACK_SIZE = 256 * 1024; // same size as the SPU local store + +struct threadJobList_t { + idParallelJobList_Threads * jobList; + int version; +}; + +static idCVar jobs_prioritize( "jobs_prioritize", "1", CVAR_BOOL | CVAR_NOCHEAT, "prioritize job lists" ); + +class idJobThread : public idSysThread { +public: + idJobThread(); + ~idJobThread(); + + void Start( core_t core, unsigned int threadNum ); + + void AddJobList( idParallelJobList_Threads * jobList ); + +private: + threadJobList_t jobLists[MAX_JOBLISTS]; // cyclic buffer with job lists + unsigned int firstJobList; // index of the last job list the thread grabbed + unsigned int lastJobList; // index where the next job list to work on will be added + idSysMutex addJobMutex; + + unsigned int threadNum; + + virtual int Run(); +}; + +/* +======================== +idJobThread::idJobThread +======================== +*/ +idJobThread::idJobThread() : + firstJobList( 0 ), + lastJobList( 0 ), + threadNum( 0 ) { +} + +/* +======================== +idJobThread::~idJobThread +======================== +*/ +idJobThread::~idJobThread() { +} + +/* +======================== +idJobThread::Start +======================== +*/ +void idJobThread::Start( core_t core, unsigned int threadNum ) { + this->threadNum = threadNum; + StartWorkerThread( va( "JobListProcessor_%d", threadNum ), core, THREAD_NORMAL, JOB_THREAD_STACK_SIZE ); +} + +/* +======================== +idJobThread::AddJobList +======================== +*/ +void idJobThread::AddJobList( idParallelJobList_Threads * jobList ) { + // must lock because multiple threads may try to add new job lists at the same time + addJobMutex.Lock(); + // wait until there is space available because in rare cases multiple versions of the same job lists may still be queued + while( lastJobList - firstJobList >= MAX_JOBLISTS ) { + Sys_Yield(); + } + assert( lastJobList - firstJobList < MAX_JOBLISTS ); + jobLists[lastJobList & ( MAX_JOBLISTS - 1 )].jobList = jobList; + jobLists[lastJobList & ( MAX_JOBLISTS - 1 )].version = jobList->GetVersion(); + lastJobList++; + addJobMutex.Unlock(); +} + +/* +======================== +idJobThread::Run +======================== +*/ +int idJobThread::Run() { + threadJobListState_t threadJobListState[MAX_JOBLISTS]; + int numJobLists = 0; + int lastStalledJobList = -1; + + while ( !IsTerminating() ) { + + // fetch any new job lists and add them to the local list + if ( numJobLists < MAX_JOBLISTS && firstJobList < lastJobList ) { + threadJobListState[numJobLists].jobList = jobLists[firstJobList & ( MAX_JOBLISTS - 1 )].jobList; + threadJobListState[numJobLists].version = jobLists[firstJobList & ( MAX_JOBLISTS - 1 )].version; + threadJobListState[numJobLists].signalIndex = 0; + threadJobListState[numJobLists].lastJobIndex = 0; + threadJobListState[numJobLists].nextJobIndex = -1; + numJobLists++; + firstJobList++; + } + if ( numJobLists == 0 ) { + break; + } + + int currentJobList = 0; + jobListPriority_t priority = JOBLIST_PRIORITY_NONE; + if ( lastStalledJobList < 0 ) { + // find the job list with the highest priority + for ( int i = 0; i < numJobLists; i++ ) { + if ( threadJobListState[i].jobList->GetPriority() > priority && !threadJobListState[i].jobList->WaitForOtherJobList() ) { + priority = threadJobListState[i].jobList->GetPriority(); + currentJobList = i; + } + } + } else { + // try to hide the stall with a job from a list that has equal or higher priority + currentJobList = lastStalledJobList; + priority = threadJobListState[lastStalledJobList].jobList->GetPriority(); + for ( int i = 0; i < numJobLists; i++ ) { + if ( i != lastStalledJobList && threadJobListState[i].jobList->GetPriority() >= priority && !threadJobListState[i].jobList->WaitForOtherJobList() ) { + priority = threadJobListState[i].jobList->GetPriority(); + currentJobList = i; + } + } + } + + // if the priority is high then try to run through the whole list to reduce the overhead + // otherwise run a single job and re-evaluate priorities for the next job + bool singleJob = ( priority == JOBLIST_PRIORITY_HIGH ) ? false : jobs_prioritize.GetBool(); + + // try running one or more jobs from the current job list + int result = threadJobListState[currentJobList].jobList->RunJobs( threadNum, threadJobListState[currentJobList], singleJob ); + + if ( ( result & idParallelJobList_Threads::RUN_DONE ) != 0 ) { + // done with this job list so remove it from the local list + for ( int i = currentJobList; i < numJobLists - 1; i++ ) { + threadJobListState[i] = threadJobListState[i + 1]; + } + numJobLists--; + lastStalledJobList = -1; + } else if ( ( result & idParallelJobList_Threads::RUN_STALLED ) != 0 ) { + // yield when stalled on the same job list again without making any progress + if ( currentJobList == lastStalledJobList ) { + if ( ( result & idParallelJobList_Threads::RUN_PROGRESS ) == 0 ) { + Sys_Yield(); + } + } + lastStalledJobList = currentJobList; + } else { + lastStalledJobList = -1; + } + } + return 0; +} + +/* +================================================================================================ + +idParallelJobManagerLocal + +================================================================================================ +*/ + +extern void Sys_CPUCount( int & logicalNum, int & coreNum, int & packageNum ); + + +// WINDOWS LOGICAL PROCESSOR LIMITS: +// +// http://download.microsoft.com/download/5/7/7/577a5684-8a83-43ae-9272-ff260a9c20e2/Hyper-thread_Windows.doc +// +// Physical Logical (Cores + HT) +// Windows XP Home Edition 1 2 +// Windows XP Professional 2 4 +// Windows Server 2003, Standard Edition 4 8 +// Windows Server 2003, Enterprise Edition 8 16 +// Windows Server 2003, Datacenter Edition 32 32 +// +// Windows Vista ? ? +// +// Windows 7 Starter 1 32/64 +// Windows 7 Home Basic 1 32/64 +// Windows 7 Professional 2 32/64 +// +// +// Hyperthreading is not dead yet. Intel's Core i7 Processor is quad-core with HT for 8 logicals. + +// DOOM3: We don't have that many jobs, so just set this fairly low so we don't spin up a ton of idle threads +#define MAX_JOB_THREADS 2 +#define NUM_JOB_THREADS "2" +#define JOB_THREAD_CORES { CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY, \ + CORE_ANY, CORE_ANY, CORE_ANY, CORE_ANY } + + +idCVar jobs_numThreads( "jobs_numThreads", NUM_JOB_THREADS, CVAR_INTEGER | CVAR_NOCHEAT, "number of threads used to crunch through jobs", 0, MAX_JOB_THREADS ); + +class idParallelJobManagerLocal : public idParallelJobManager { +public: + virtual ~idParallelJobManagerLocal() {} + + virtual void Init(); + virtual void Shutdown(); + + virtual idParallelJobList * AllocJobList( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs, const idColor * color ); + virtual void FreeJobList( idParallelJobList * jobList ); + + virtual int GetNumJobLists() const; + virtual int GetNumFreeJobLists() const; + virtual idParallelJobList * GetJobList( int index ); + + virtual int GetNumProcessingUnits(); + + virtual void WaitForAllJobLists(); + + void Submit( idParallelJobList_Threads * jobList, int parallelism ); + +private: + idJobThread threads[MAX_JOB_THREADS]; + unsigned int maxThreads; + int numPhysicalCpuCores; + int numLogicalCpuCores; + int numCpuPackages; + idStaticList< idParallelJobList *, MAX_JOBLISTS > jobLists; +}; + +idParallelJobManagerLocal parallelJobManagerLocal; +idParallelJobManager * parallelJobManager = ¶llelJobManagerLocal; + +/* +======================== +SubmitJobList +======================== +*/ +void SubmitJobList( idParallelJobList_Threads * jobList, int parallelism ) { + parallelJobManagerLocal.Submit( jobList, parallelism ); +} + +/* +======================== +idParallelJobManagerLocal::Init +======================== +*/ +void idParallelJobManagerLocal::Init() { + // on consoles this will have specific cores for the threads, but on PC they will all be CORE_ANY + core_t cores[] = JOB_THREAD_CORES; + assert( sizeof( cores ) / sizeof( cores[0] ) >= MAX_JOB_THREADS ); + + for ( int i = 0; i < MAX_JOB_THREADS; i++ ) { + threads[i].Start( cores[i], i ); + } + maxThreads = jobs_numThreads.GetInteger(); + + Sys_CPUCount( numPhysicalCpuCores, numLogicalCpuCores, numCpuPackages ); +} + +/* +======================== +idParallelJobManagerLocal::Shutdown +======================== +*/ +void idParallelJobManagerLocal::Shutdown() { + for ( int i = 0; i < MAX_JOB_THREADS; i++ ) { + threads[i].StopThread(); + } +} + +/* +======================== +idParallelJobManagerLocal::AllocJobList +======================== +*/ +idParallelJobList * idParallelJobManagerLocal::AllocJobList( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs, const idColor * color ) { + for ( int i = 0; i < jobLists.Num(); i++ ) { + if ( jobLists[i]->GetId() == id ) { + // idStudio may cause job lists to be allocated multiple times + } + } + idParallelJobList * jobList = new (TAG_JOBLIST) idParallelJobList( id, priority, maxJobs, maxSyncs, color ); + jobLists.Append( jobList ); + return jobList; +} + +/* +======================== +idParallelJobManagerLocal::FreeJobList +======================== +*/ +void idParallelJobManagerLocal::FreeJobList( idParallelJobList * jobList ) { + if ( jobList == NULL ) { + return; + } + // wait for all job threads to finish because job list deletion is not thread safe + for ( unsigned int i = 0; i < maxThreads; i++ ) { + threads[i].WaitForThread(); + } + int index = jobLists.FindIndex( jobList ); + assert( index >= 0 && jobLists[index] == jobList ); + jobLists[index]->Wait(); + delete jobLists[index]; + jobLists.RemoveIndexFast( index ); +} + +/* +======================== +idParallelJobManagerLocal::GetNumJobLists +======================== +*/ +int idParallelJobManagerLocal::GetNumJobLists() const { + return jobLists.Num(); +} + +/* +======================== +idParallelJobManagerLocal::GetNumFreeJobLists +======================== +*/ +int idParallelJobManagerLocal::GetNumFreeJobLists() const { + return MAX_JOBLISTS - jobLists.Num(); +} + +/* +======================== +idParallelJobManagerLocal::GetJobList +======================== +*/ +idParallelJobList * idParallelJobManagerLocal::GetJobList( int index ) { + return jobLists[index]; +} + +/* +======================== +idParallelJobManagerLocal::GetNumProcessingUnits +======================== +*/ +int idParallelJobManagerLocal::GetNumProcessingUnits() { + return maxThreads; +} + +/* +======================== +idParallelJobManagerLocal::WaitForAllJobLists +======================== +*/ +void idParallelJobManagerLocal::WaitForAllJobLists() { + // wait for all job lists to complete + for ( int i = 0; i < jobLists.Num(); i++ ) { + jobLists[i]->Wait(); + } +} + +/* +======================== +idParallelJobManagerLocal::Submit +======================== +*/ +void idParallelJobManagerLocal::Submit( idParallelJobList_Threads * jobList, int parallelism ) { + if ( jobs_numThreads.IsModified() ) { + maxThreads = idMath::ClampInt( 0, MAX_JOB_THREADS, jobs_numThreads.GetInteger() ); + jobs_numThreads.ClearModified(); + } + + // determine the number of threads to use + int numThreads = maxThreads; + if ( parallelism == JOBLIST_PARALLELISM_DEFAULT ) { + numThreads = maxThreads; + } else if ( parallelism == JOBLIST_PARALLELISM_MAX_CORES ) { + numThreads = numLogicalCpuCores; + } else if ( parallelism == JOBLIST_PARALLELISM_MAX_THREADS ) { + numThreads = MAX_JOB_THREADS; + } else if ( parallelism > MAX_JOB_THREADS ) { + numThreads = MAX_JOB_THREADS; + } else { + numThreads = parallelism; + } + + if ( numThreads <= 0 ) { + threadJobListState_t state( jobList->GetVersion() ); + jobList->RunJobs( 0, state, false ); + return; + } + + for ( int i = 0; i < numThreads; i++ ) { + threads[i].AddJobList( jobList ); + threads[i].SignalWork(); + } +} \ No newline at end of file diff --git a/neo/idlib/ParallelJobList.h b/neo/idlib/ParallelJobList.h new file mode 100644 index 00000000..e9193e57 --- /dev/null +++ b/neo/idlib/ParallelJobList.h @@ -0,0 +1,177 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PARALLELJOBLIST_H__ +#define __PARALLELJOBLIST_H__ + +struct CellSpursJob128; +class idColor; + +typedef void ( * jobRun_t )( void * ); + +enum jobSyncType_t { + SYNC_NONE, + SYNC_SIGNAL, + SYNC_SYNCHRONIZE +}; + +// NOTE: keep in sync with jobNames[] +enum jobListId_t { + JOBLIST_RENDERER_FRONTEND = 0, + JOBLIST_RENDERER_BACKEND = 1, + JOBLIST_UTILITY = 9, // won't print over-time warnings + + MAX_JOBLISTS = 32 // the editor may cause quite a few to be allocated +}; + +compile_time_assert( CONST_ISPOWEROFTWO( MAX_JOBLISTS ) ); + +enum jobListPriority_t { + JOBLIST_PRIORITY_NONE, + JOBLIST_PRIORITY_LOW, + JOBLIST_PRIORITY_MEDIUM, + JOBLIST_PRIORITY_HIGH +}; + +enum jobListParallelism_t { + JOBLIST_PARALLELISM_DEFAULT = -1, // use "jobs_numThreads" number of threads + JOBLIST_PARALLELISM_MAX_CORES = -2, // use a thread for each logical core (includes hyperthreads) + JOBLIST_PARALLELISM_MAX_THREADS = -3 // use the maximum number of job threads, which can help if there is IO to overlap +}; + +#define assert_spu_local_store( ptr ) +#define assert_not_spu_local_store( ptr ) + +/* +================================================ +idParallelJobList + +A job should be at least a couple of 1000 clock cycles in +order to outweigh any job switching overhead. On the other +hand a job should consume no more than a couple of +100,000 clock cycles to maintain a good load balance over +multiple processing units. +================================================ +*/ +class idParallelJobList { + friend class idParallelJobManagerLocal; +public: + + void AddJob( jobRun_t function, void * data ); + CellSpursJob128 * AddJobSPURS(); + void InsertSyncPoint( jobSyncType_t syncType ); + + // Submit the jobs in this list. + void Submit( idParallelJobList * waitForJobList = NULL, int parallelism = JOBLIST_PARALLELISM_DEFAULT ); + // Wait for the jobs in this list to finish. Will spin in place if any jobs are not done. + void Wait(); + // Try to wait for the jobs in this list to finish but either way return immediately. Returns true if all jobs are done. + bool TryWait(); + // returns true if the job list has been submitted. + bool IsSubmitted() const; + + // Get the number of jobs executed in this job list. + unsigned int GetNumExecutedJobs() const; + // Get the number of sync points. + unsigned int GetNumSyncs() const; + // Time at which the job list was submitted. + uint64 GetSubmitTimeMicroSec() const; + // Time at which execution of this job list started. + uint64 GetStartTimeMicroSec() const; + // Time at which all jobs in the list were executed. + uint64 GetFinishTimeMicroSec() const; + // Time the host thread waited for this job list to finish. + uint64 GetWaitTimeMicroSec() const; + // Get the total time all units spent processing this job list. + uint64 GetTotalProcessingTimeMicroSec() const; + // Get the total time all units wasted while processing this job list. + uint64 GetTotalWastedTimeMicroSec() const; + // Time the given unit spent processing this job list. + uint64 GetUnitProcessingTimeMicroSec( int unit ) const; + // Time the given unit wasted while processing this job list. + uint64 GetUnitWastedTimeMicroSec( int unit ) const; + + // Get the job list ID + jobListId_t GetId() const; + // Get the color for profiling. + const idColor * GetColor() const { return this->color; } + +private: + class idParallelJobList_Threads * jobListThreads; + const idColor * color; + + idParallelJobList( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs, const idColor * color ); + ~idParallelJobList(); +}; + +/* +================================================ +idParallelJobManager + +This is the only interface through which job lists +should be allocated or freed. +================================================ +*/ +class idParallelJobManager { +public: + virtual ~idParallelJobManager() {} + + virtual void Init() = 0; + virtual void Shutdown() = 0; + + virtual idParallelJobList * AllocJobList( jobListId_t id, jobListPriority_t priority, unsigned int maxJobs, unsigned int maxSyncs, const idColor * color ) = 0; + virtual void FreeJobList( idParallelJobList * jobList ) = 0; + + virtual int GetNumJobLists() const = 0; + virtual int GetNumFreeJobLists() const = 0; + virtual idParallelJobList * GetJobList( int index ) = 0; + + virtual int GetNumProcessingUnits() = 0; + + virtual void WaitForAllJobLists() = 0; +}; + +extern idParallelJobManager * parallelJobManager; + +// jobRun_t functions can have the debug name associated with them +// by explicitly calling this, or using the REGISTER_PARALLEL_JOB() +// static variable macro. +void RegisterJob( jobRun_t function, const char * name ); + +/* +================================================ +idParallelJobRegistration +================================================ +*/ +class idParallelJobRegistration { +public: + idParallelJobRegistration( jobRun_t function, const char * name ); +}; + +#define REGISTER_PARALLEL_JOB( function, name ) static idParallelJobRegistration register_##function( (jobRun_t) function, name ) + +#endif // !__PARALLELJOBLIST_H__ diff --git a/neo/idlib/ParallelJobList_JobHeaders.h b/neo/idlib/ParallelJobList_JobHeaders.h new file mode 100644 index 00000000..ab1ce227 --- /dev/null +++ b/neo/idlib/ParallelJobList_JobHeaders.h @@ -0,0 +1,67 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PARALLELJOBLIST_JOBHEADERS_H__ +#define __PARALLELJOBLIST_JOBHEADERS_H__ + +/* +================================================================================================ + + Minimum set of headers needed to compile the code for a job. + +================================================================================================ +*/ + +#include "sys/sys_defines.h" + +#include // for offsetof +#include +#include +#include +#include + +#include // for UINT_PTR +#include +#pragma warning( disable : 4100 ) // unreferenced formal parameter +#pragma warning( disable : 4127 ) // conditional expression is constant + + + + + +#include "sys/sys_assert.h" +#include "sys/sys_types.h" +#include "sys/sys_intrinsics.h" +#include "math/Math.h" +#include "ParallelJobList.h" + +#if _MSC_VER >= 1600 +#undef NULL +#define NULL 0 +#endif + +#endif // !__PARALLELJOBLIST_JOBHEADERS_H__ diff --git a/neo/idlib/Parser.cpp b/neo/idlib/Parser.cpp new file mode 100644 index 00000000..0c09ba94 --- /dev/null +++ b/neo/idlib/Parser.cpp @@ -0,0 +1,3288 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +//#define DEBUG_EVAL +#define MAX_DEFINEPARMS 128 +#define DEFINEHASHSIZE 2048 + +#define TOKEN_FL_RECURSIVE_DEFINE 1 + +define_t * idParser::globaldefines; + +/* +================ +idParser::SetBaseFolder +================ +*/ +void idParser::SetBaseFolder( const char *path) { + idLexer::SetBaseFolder(path); +} + +/* +================ +idParser::AddGlobalDefine +================ +*/ +int idParser::AddGlobalDefine( const char *string ) { + define_t *define; + + define = idParser::DefineFromString(string); + if (!define) { + return false; + } + define->next = globaldefines; + globaldefines = define; + return true; +} + +/* +================ +idParser::RemoveGlobalDefine +================ +*/ +int idParser::RemoveGlobalDefine( const char *name ) { + define_t *d, *prev; + + for ( prev = NULL, d = idParser::globaldefines; d; prev = d, d = d->next ) { + if ( !strcmp( d->name, name ) ) { + break; + } + } + if ( d ) { + if ( prev ) { + prev->next = d->next; + } + else { + idParser::globaldefines = d->next; + } + idParser::FreeDefine( d ); + return true; + } + return false; +} + +/* +================ +idParser::RemoveAllGlobalDefines +================ +*/ +void idParser::RemoveAllGlobalDefines() { + define_t *define; + + for ( define = globaldefines; define; define = globaldefines ) { + globaldefines = globaldefines->next; + idParser::FreeDefine(define); + } +} + + +/* +=============================================================================== + +idParser + +=============================================================================== +*/ + +/* +================ +idParser::PrintDefine +================ +*/ +void idParser::PrintDefine( define_t *define ) { + idLib::common->Printf("define->name = %s\n", define->name); + idLib::common->Printf("define->flags = %d\n", define->flags); + idLib::common->Printf("define->builtin = %d\n", define->builtin); + idLib::common->Printf("define->numparms = %d\n", define->numparms); +} + +/* +================ +PC_PrintDefineHashTable +================ +* / +static void PC_PrintDefineHashTable(define_t **definehash) { + int i; + define_t *d; + + for (i = 0; i < DEFINEHASHSIZE; i++) { + Log_Write("%4d:", i); + for (d = definehash[i]; d; d = d->hashnext) { + Log_Write(" %s", d->name); + } + Log_Write("\n"); + } +} +*/ + +/* +================ +PC_NameHash +================ +*/ +ID_INLINE int PC_NameHash( const char *name ) { + int hash, i; + + hash = 0; + for ( i = 0; name[i] != '\0'; i++ ) { + hash += name[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} + +/* +================ +idParser::AddDefineToHash +================ +*/ +void idParser::AddDefineToHash( define_t *define, define_t **definehash ) { + int hash; + + hash = PC_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} + +/* +================ +FindHashedDefine +================ +*/ +define_t *idParser::FindHashedDefine( define_t **definehash, const char *name ) { + define_t *d; + int hash; + + hash = PC_NameHash(name); + for ( d = definehash[hash]; d; d = d->hashnext ) { + if ( !strcmp(d->name, name) ) { + return d; + } + } + return NULL; +} + +/* +================ +idParser::FindDefine +================ +*/ +define_t *idParser::FindDefine( define_t *defines, const char *name ) { + define_t *d; + + for ( d = defines; d; d = d->next ) { + if ( !strcmp(d->name, name) ) { + return d; + } + } + return NULL; +} + +/* +================ +idParser::FindDefineParm +================ +*/ +int idParser::FindDefineParm( define_t *define, const char *name ) { + idToken *p; + int i; + + i = 0; + for ( p = define->parms; p; p = p->next ) { + if ( (*p) == name ) { + return i; + } + i++; + } + return -1; +} + +/* +================ +idParser::CopyDefine +================ +*/ +define_t *idParser::CopyDefine( define_t *define ) { + define_t *newdefine; + idToken *token, *newtoken, *lasttoken; + + newdefine = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(define->name) + 1, TAG_IDLIB_PARSER); + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) { + newtoken = new (TAG_IDLIB_PARSER) idToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) { + newtoken = new (TAG_IDLIB_PARSER) idToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } + return newdefine; +} + +/* +================ +idParser::FreeDefine +================ +*/ +void idParser::FreeDefine( define_t *define ) { + idToken *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) { + next = t->next; + delete t; + } + //free the define tokens + for (t = define->tokens; t; t = next) { + next = t->next; + delete t; + } + //free the define + Mem_Free( define ); +} + +/* +================ +idParser::DefineFromString +================ +*/ +define_t *idParser::DefineFromString( const char *string ) { + idParser src; + define_t *def; + + if ( !src.LoadMemory(string, strlen(string), "*defineString") ) { + return NULL; + } + // create a define from the source + if ( !src.Directive_define() ) { + src.FreeSource(); + return NULL; + } + def = src.CopyFirstDefine(); + src.FreeSource(); + //if the define was created succesfully + return def; +} + +/* +================ +idParser::Error +================ +*/ +void idParser::Error( const char *str, ... ) const { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + if ( idParser::scriptstack ) { + idParser::scriptstack->Error( text ); + } +} + +/* +================ +idParser::Warning +================ +*/ +void idParser::Warning( const char *str, ... ) const { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + if ( idParser::scriptstack ) { + idParser::scriptstack->Warning( text ); + } +} + +/* +================ +idParser::PushIndent +================ +*/ +void idParser::PushIndent( int type, int skip ) { + indent_t *indent; + + indent = (indent_t *) Mem_Alloc(sizeof(indent_t), TAG_IDLIB_PARSER); + indent->type = type; + indent->script = idParser::scriptstack; + indent->skip = (skip != 0); + idParser::skip += indent->skip; + indent->next = idParser::indentstack; + idParser::indentstack = indent; +} + +/* +================ +idParser::PopIndent +================ +*/ +void idParser::PopIndent( int *type, int *skip ) { + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = idParser::indentstack; + if (!indent) return; + + // must be an indent from the current script + if (idParser::indentstack->script != idParser::scriptstack) { + return; + } + + *type = indent->type; + *skip = indent->skip; + idParser::indentstack = idParser::indentstack->next; + idParser::skip -= indent->skip; + Mem_Free( indent ); +} + +/* +================ +idParser::PushScript +================ +*/ +void idParser::PushScript( idLexer *script ) { + idLexer *s; + + for ( s = idParser::scriptstack; s; s = s->next ) { + if ( !idStr::Icmp(s->GetFileName(), script->GetFileName()) ) { + idParser::Warning( "'%s' recursively included", script->GetFileName() ); + return; + } + } + //push the script on the script stack + script->next = idParser::scriptstack; + idParser::scriptstack = script; +} + +/* +================ +idParser::ReadSourceToken +================ +*/ +int idParser::ReadSourceToken( idToken *token ) { + idToken *t; + idLexer *script; + int type, skip, changedScript; + + if ( !idParser::scriptstack ) { + idLib::common->FatalError( "idParser::ReadSourceToken: not loaded" ); + return false; + } + changedScript = 0; + // if there's no token already available + while( !idParser::tokens ) { + // if there's a token to read from the script + if ( idParser::scriptstack->ReadToken( token ) ) { + token->linesCrossed += changedScript; + + // set the marker based on the start of the token read in + if ( !marker_p ) { + marker_p = token->whiteSpaceEnd_p; + } + return true; + } + // if at the end of the script + if ( idParser::scriptstack->EndOfFile() ) { + // remove all indents of the script + while( idParser::indentstack && idParser::indentstack->script == idParser::scriptstack ) { + idParser::Warning( "missing #endif" ); + idParser::PopIndent( &type, &skip ); + } + changedScript = 1; + } + // if this was the initial script + if ( !idParser::scriptstack->next ) { + return false; + } + // remove the script and return to the previous one + script = idParser::scriptstack; + idParser::scriptstack = idParser::scriptstack->next; + delete script; + } + // copy the already available token + *token = idParser::tokens; + // remove the token from the source + t = idParser::tokens; + assert( idParser::tokens != NULL ); + idParser::tokens = idParser::tokens->next; + delete t; + return true; +} + +/* +================ +idParser::UnreadSourceToken +================ +*/ +int idParser::UnreadSourceToken( idToken *token ) { + idToken *t; + + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = idParser::tokens; + idParser::tokens = t; + return true; +} + +/* +================ +idParser::ReadDefineParms +================ +*/ +int idParser::ReadDefineParms( define_t *define, idToken **parms, int maxparms ) { + define_t *newdefine; + idToken token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "define '%s' missing parameters", define->name ); + return false; + } + + if ( define->numparms > maxparms ) { + idParser::Error( "define with more than %d parameters", maxparms ); + return false; + } + + for ( i = 0; i < define->numparms; i++ ) { + parms[i] = NULL; + } + // if no leading "(" + if ( token != "(" ) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "define '%s' missing parameters", define->name ); + return false; + } + // read the define parameters + for ( done = 0, numparms = 0, indent = 1; !done; ) { + if ( numparms >= maxparms ) { + idParser::Error( "define '%s' with too many parameters", define->name ); + return false; + } + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while( !done ) { + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "define '%s' incomplete", define->name ); + return false; + } + + if ( token == "," ) { + if ( indent <= 1 ) { + if ( lastcomma ) { + idParser::Warning( "too many comma's" ); + } + if ( numparms >= define->numparms ) { + idParser::Warning( "too many define parameters" ); + } + lastcomma = 1; + break; + } + } + else if ( token == "(" ) { + indent++; + } + else if ( token == ")" ) { + indent--; + if ( indent <= 0 ) { + if ( !parms[define->numparms-1] ) { + idParser::Warning( "too few define parameters" ); + } + done = 1; + break; + } + } + else if ( token.type == TT_NAME ) { + newdefine = FindHashedDefine( idParser::definehash, token.c_str() ); + if ( newdefine ) { + if ( !idParser::ExpandDefineIntoSource( &token, newdefine ) ) { + return false; + } + continue; + } + } + + lastcomma = 0; + + if ( numparms < define->numparms ) { + + t = new (TAG_IDLIB_PARSER) idToken( token ); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } + } + numparms++; + } + return true; +} + +/* +================ +idParser::StringizeTokens +================ +*/ +int idParser::StringizeTokens( idToken *tokens, idToken *token ) { + idToken *t; + + token->type = TT_STRING; + token->whiteSpaceStart_p = NULL; + token->whiteSpaceEnd_p = NULL; + (*token) = ""; + for ( t = tokens; t; t = t->next ) { + token->Append( t->c_str() ); + } + return true; +} + +/* +================ +idParser::MergeTokens +================ +*/ +int idParser::MergeTokens( idToken *t1, idToken *t2 ) { + // merging of a name with a name or number + if ( t1->type == TT_NAME && (t2->type == TT_NAME || (t2->type == TT_NUMBER && !(t2->subtype & TT_FLOAT))) ) { + t1->Append( t2->c_str() ); + return true; + } + // merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) { + t1->Append( t2->c_str() ); + return true; + } + // merging of two numbers + if ( t1->type == TT_NUMBER && t2->type == TT_NUMBER && + !(t1->subtype & (TT_HEX|TT_BINARY)) && !(t2->subtype & (TT_HEX|TT_BINARY)) && + (!(t1->subtype & TT_FLOAT) || !(t2->subtype & TT_FLOAT)) ) { + t1->Append( t2->c_str() ); + return true; + } + + return false; +} + +/* +================ +idParser::AddBuiltinDefines +================ +*/ +void idParser::AddBuiltinDefines() { + int i; + define_t *define; + struct builtin + { + char *string; + int id; + } builtin[] = { + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, + { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for (i = 0; builtin[i].string; i++) { + define = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(builtin[i].string) + 1, TAG_IDLIB_PARSER); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, builtin[i].string); + define->flags = DEFINE_FIXED; + define->builtin = builtin[i].id; + define->numparms = 0; + define->parms = NULL; + define->tokens = NULL; + // add the define to the source + AddDefineToHash(define, idParser::definehash); + } +} + +/* +================ +idParser::CopyFirstDefine +================ +*/ +define_t *idParser::CopyFirstDefine() { + int i; + + for ( i = 0; i < DEFINEHASHSIZE; i++ ) { + if ( idParser::definehash[i] ) { + return CopyDefine(idParser::definehash[i]); + } + } + return NULL; +} + +static idStr PreProcessorDate() { + time_t t = time(NULL); + char *curtime = ctime(&t); + if ( idStr::Length( curtime ) < 24 ) { + return idStr( "*** BAD CURTIME ***" ); + } + idStr str = "\""; + // skip DAY, extract MMM DD + for ( int i = 4 ; i < 10 ; i++ ) { + str.Append( curtime[i] ); + } + // skip time, extract space+YYYY + for ( int i = 19 ; i < 24 ; i++ ) { + str.Append( curtime[i] ); + } + str.Append( "\"" ); + return str; +} + +static idStr PreProcessorTime() { + time_t t = time(NULL); + char *curtime = ctime(&t); + if ( idStr::Length( curtime ) < 24 ) { + return idStr( "*** BAD CURTIME ***" ); + } + + idStr str = "\""; + for ( int i = 11 ; i < 19 ; i++ ) { + str.Append( curtime[i] ); + } + str.Append( "\"" ); + return str; +} + +CONSOLE_COMMAND( TestPreprocessorMacros, "check analyze warning", 0 ) { + idLib::Printf( "%s : %s\n", __DATE__, PreProcessorDate().c_str() ); + idLib::Printf( "%s : %s\n", __TIME__, PreProcessorTime().c_str() ); +} + +/* +================ +idParser::ExpandBuiltinDefine +================ +*/ +int idParser::ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { + idToken *token; + char buf[MAX_STRING_CHARS]; + + token = new (TAG_IDLIB_PARSER) idToken(deftoken); + switch( define->builtin ) { + case BUILTIN_LINE: { + sprintf( buf, "%d", deftoken->line ); + (*token) = buf; + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER | TT_VALUESVALID; + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_FILE: { + (*token) = idParser::scriptstack->GetFileName(); + token->type = TT_NAME; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_DATE: { + *token = PreProcessorDate(); + token->type = TT_STRING; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_TIME: { + *token = PreProcessorTime(); + token->type = TT_STRING; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_STDC: { + idParser::Warning( "__STDC__ not supported\n" ); + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + default: { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + } + return true; +} + +/* +================ +idParser::ExpandDefine +================ +*/ +int idParser::ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { + idToken *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + idToken *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + // if it is a builtin define + if ( define->builtin ) { + return idParser::ExpandBuiltinDefine( deftoken, define, firsttoken, lasttoken ); + } + // if the define has parameters + if ( define->numparms ) { + if ( !idParser::ReadDefineParms( define, parms, MAX_DEFINEPARMS ) ) { + return false; + } +#ifdef DEBUG_EVAL + for ( i = 0; i < define->numparms; i++ ) { + Log_Write("define parms %d:", i); + for ( pt = parms[i]; pt; pt = pt->next ) { + Log_Write( "%s", pt->c_str() ); + } + } +#endif //DEBUG_EVAL + } + // empty list at first + first = NULL; + last = NULL; + // create a list with tokens of the expanded define + for ( dt = define->tokens; dt; dt = dt->next ) { + parmnum = -1; + // if the token is a name, it could be a define parameter + if ( dt->type == TT_NAME ) { + parmnum = FindDefineParm( define, dt->c_str() ); + } + // if it is a define parameter + if ( parmnum >= 0 ) { + for ( pt = parms[parmnum]; pt; pt = pt->next ) { + t = new (TAG_IDLIB_PARSER) idToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + else { + // if stringizing operator + if ( (*dt) == "#" ) { + // the stringizing operator must be followed by a define parameter + if ( dt->next ) { + parmnum = FindDefineParm( define, dt->next->c_str() ); + } + else { + parmnum = -1; + } + + if ( parmnum >= 0 ) { + // step over the stringizing operator + dt = dt->next; + // stringize the define parameter tokens + if ( !idParser::StringizeTokens( parms[parmnum], &token ) ) { + idParser::Error( "can't stringize tokens" ); + return false; + } + t = new (TAG_IDLIB_PARSER) idToken(token); + t->line = deftoken->line; + } + else { + idParser::Warning( "stringizing operator without define parameter" ); + continue; + } + } + else { + t = new (TAG_IDLIB_PARSER) idToken(dt); + t->line = deftoken->line; + } + // add the token to the list + t->next = NULL; +// the token being read from the define list should use the line number of +// the original file, not the header file + t->line = deftoken->line; + + if ( last ) last->next = t; + else first = t; + last = t; + } + } + // check for the merging operator + for ( t = first; t; ) { + if ( t->next ) { + // if the merging operator + if ( (*t->next) == "##" ) { + t1 = t; + t2 = t->next->next; + if ( t2 ) { + if ( !idParser::MergeTokens( t1, t2 ) ) { + idParser::Error( "can't merge '%s' with '%s'", t1->c_str(), t2->c_str() ); + return false; + } + delete t1->next; + t1->next = t2->next; + if ( t2 == last ) last = t1; + delete t2; + continue; + } + } + } + t = t->next; + } + // store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + // free all the parameter tokens + for ( i = 0; i < define->numparms; i++ ) { + for ( pt = parms[i]; pt; pt = nextpt ) { + nextpt = pt->next; + delete pt; + } + } + + return true; +} + +/* +================ +idParser::ExpandDefineIntoSource +================ +*/ +int idParser::ExpandDefineIntoSource( idToken *deftoken, define_t *define ) { + idToken *firsttoken, *lasttoken; + + if ( !idParser::ExpandDefine( deftoken, define, &firsttoken, &lasttoken ) ) { + return false; + } + // if the define is not empty + if ( firsttoken && lasttoken ) { + firsttoken->linesCrossed += deftoken->linesCrossed; + lasttoken->next = idParser::tokens; + idParser::tokens = firsttoken; + } + return true; +} + +/* +================ +idParser::ReadLine + +reads a token from the current line, continues reading on the next +line only if a backslash '\' is found +================ +*/ +int idParser::ReadLine( idToken *token ) { + int crossline; + + crossline = 0; + do { + if (!idParser::ReadSourceToken( token )) { + return false; + } + + if (token->linesCrossed > crossline) { + idParser::UnreadSourceToken( token ); + return false; + } + crossline = 1; + } while( (*token) == "\\" ); + return true; +} + +/* +================ +idParser::Directive_include +================ +*/ +int idParser::Directive_include() { + idLexer *script; + idToken token; + idStr path; + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "#include without file name" ); + return false; + } + if ( token.linesCrossed > 0 ) { + idParser::Error( "#include without file name" ); + return false; + } + if ( token.type == TT_STRING ) { + script = new (TAG_IDLIB_PARSER) idLexer; + // try relative to the current file + path = scriptstack->GetFileName(); + path.StripFilename(); + path += "/"; + path += token; + if ( !script->LoadFile( path, OSPath ) ) { + // try absolute path + path = token; + if ( !script->LoadFile( path, OSPath ) ) { + // try from the include path + path = includepath + token; + if ( !script->LoadFile( path, OSPath ) ) { + delete script; + script = NULL; + } + } + } + } + else if ( token.type == TT_PUNCTUATION && token == "<" ) { + path = idParser::includepath; + while( idParser::ReadSourceToken( &token ) ) { + if ( token.linesCrossed > 0 ) { + idParser::UnreadSourceToken( &token ); + break; + } + if ( token.type == TT_PUNCTUATION && token == ">" ) { + break; + } + path += token; + } + if ( token != ">" ) { + idParser::Warning( "#include missing trailing >" ); + } + if ( !path.Length() ) { + idParser::Error( "#include without file name between < >" ); + return false; + } + if ( idParser::flags & LEXFL_NOBASEINCLUDES ) { + return true; + } + script = new (TAG_IDLIB_PARSER) idLexer; + if ( !script->LoadFile( includepath + path, OSPath ) ) { + delete script; + script = NULL; + } + } + else { + idParser::Error( "#include without file name" ); + return false; + } + if (!script) { + idParser::Error( "file '%s' not found", path.c_str() ); + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + idParser::PushScript( script ); + return true; +} + +/* +================ +idParser::Directive_undef +================ +*/ +int idParser::Directive_undef() { + idToken token; + define_t *define, *lastdefine; + int hash; + + // + if (!idParser::ReadLine( &token )) { + idParser::Error( "undef without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name but found '%s'", token.c_str() ); + return false; + } + + hash = PC_NameHash( token.c_str() ); + for (lastdefine = NULL, define = idParser::definehash[hash]; define; define = define->hashnext) { + if (!strcmp(define->name, token.c_str())) + { + if (define->flags & DEFINE_FIXED) { + idParser::Warning( "can't undef '%s'", token.c_str() ); + } + else { + if (lastdefine) { + lastdefine->hashnext = define->hashnext; + } + else { + idParser::definehash[hash] = define->hashnext; + } + FreeDefine(define); + } + break; + } + lastdefine = define; + } + return true; +} + +/* +================ +idParser::Directive_define +================ +*/ +int idParser::Directive_define() { + idToken token, *t, *last; + define_t *define; + + if (!idParser::ReadLine( &token )) { + idParser::Error( "#define without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name after #define, found '%s'", token.c_str() ); + return false; + } + // check if the define already exists + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (define) { + if (define->flags & DEFINE_FIXED) { + idParser::Error( "can't redefine '%s'", token.c_str() ); + return false; + } + idParser::Warning( "redefinition of '%s'", token.c_str() ); + // unread the define name before executing the #undef directive + idParser::UnreadSourceToken( &token ); + if (!idParser::Directive_undef()) + return false; + // if the define was not removed (define->flags & DEFINE_FIXED) + define = FindHashedDefine(idParser::definehash, token.c_str()); + } + // allocate define + define = (define_t *) Mem_ClearedAlloc(sizeof(define_t) + token.Length() + 1, TAG_IDLIB_PARSER); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.c_str()); + // add the define to the source + AddDefineToHash(define, idParser::definehash); + // if nothing is defined, just return + if ( !idParser::ReadLine( &token ) ) { + return true; + } + // if it is a define with parameters + if ( token.WhiteSpaceBeforeToken() == 0 && token == "(" ) { + // read the define parameters + last = NULL; + if ( !idParser::CheckTokenString(")") ) { + while(1) { + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "expected define parameter" ); + return false; + } + // if it isn't a name + if (token.type != TT_NAME) { + idParser::Error( "invalid define parameter" ); + return false; + } + + if (FindDefineParm(define, token.c_str()) >= 0) { + idParser::Error( "two the same define parameters" ); + return false; + } + // add the define parm + t = new (TAG_IDLIB_PARSER) idToken(token); + t->ClearTokenWhiteSpace(); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + // read next token + if (!idParser::ReadLine( &token )) { + idParser::Error( "define parameters not terminated" ); + return false; + } + + if ( token == ")" ) { + break; + } + // then it must be a comma + if ( token != "," ) { + idParser::Error( "define not terminated" ); + return false; + } + } + } + if ( !idParser::ReadLine( &token ) ) { + return true; + } + } + // read the defined stuff + last = NULL; + do + { + t = new (TAG_IDLIB_PARSER) idToken(token); + if ( t->type == TT_NAME && !strcmp( t->c_str(), define->name ) ) { + t->flags |= TOKEN_FL_RECURSIVE_DEFINE; + idParser::Warning( "recursive define (removed recursion)" ); + } + t->ClearTokenWhiteSpace(); + t->next = NULL; + if ( last ) last->next = t; + else define->tokens = t; + last = t; + } while( idParser::ReadLine( &token ) ); + + if ( last ) { + // check for merge operators at the beginning or end + if ( (*define->tokens) == "##" || (*last) == "##" ) { + idParser::Error( "define with misplaced ##" ); + return false; + } + } + return true; +} + +/* +================ +idParser::AddDefine +================ +*/ +int idParser::AddDefine( const char *string ) { + define_t *define; + + define = DefineFromString( string ); + if (!define) { + return false; + } + AddDefineToHash(define, idParser::definehash); + return true; +} + +/* +================ +idParser::AddGlobalDefinesToSource +================ +*/ +void idParser::AddGlobalDefinesToSource() { + define_t *define, *newdefine; + + for (define = globaldefines; define; define = define->next) { + newdefine = CopyDefine( define ); + AddDefineToHash(newdefine, idParser::definehash); + } +} + +/* +================ +idParser::Directive_if_def +================ +*/ +int idParser::Directive_if_def( int type ) { + idToken token; + define_t *d; + int skip; + + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "#ifdef without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name after #ifdef, found '%s'", token.c_str() ); + return false; + } + d = FindHashedDefine(idParser::definehash, token.c_str()); + skip = (type == INDENT_IFDEF) == (d == NULL); + idParser::PushIndent( type, skip ); + return true; +} + +/* +================ +idParser::Directive_ifdef +================ +*/ +int idParser::Directive_ifdef() { + return idParser::Directive_if_def( INDENT_IFDEF ); +} + +/* +================ +idParser::Directive_ifndef +================ +*/ +int idParser::Directive_ifndef() { + return idParser::Directive_if_def( INDENT_IFNDEF ); +} + +/* +================ +idParser::Directive_else +================ +*/ +int idParser::Directive_else() { + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type) { + idParser::Error( "misplaced #else" ); + return false; + } + if (type == INDENT_ELSE) { + idParser::Error( "#else after #else" ); + return false; + } + idParser::PushIndent( INDENT_ELSE, !skip ); + return true; +} + +/* +================ +idParser::Directive_endif +================ +*/ +int idParser::Directive_endif() { + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type) { + idParser::Error( "misplaced #endif" ); + return false; + } + return true; +} + +/* +================ +idParser::EvaluateTokens +================ +*/ +typedef struct operator_s +{ + int op; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority(int op) { + switch(op) { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } + return false; +} + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 + +#define AllocValue(val) \ + if ( numvalues >= MAX_VALUES ) { \ + idParser::Error( "out of value space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + val = &value_heap[numvalues++]; \ + } + +#define FreeValue(val) + +#define AllocOperator(op) \ + if ( numoperators >= MAX_OPERATORS ) { \ + idParser::Error( "out of operator space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + op = &operator_heap[numoperators++]; \ + } + +#define FreeOperator(op) + +int idParser::EvaluateTokens( idToken *tokens, signed long int *intvalue, double *floatvalue, int integer ) { + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + idToken *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = false; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for ( t = tokens; t; t = t->next ) { + switch( t->type ) { + case TT_NAME: + { + if ( lastwasvalue || negativevalue ) { + idParser::Error( "syntax error in #if/#elif" ); + error = 1; + break; + } + if ( (*t) != "defined" ) { + idParser::Error( "undefined name '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + t = t->next; + if ( (*t) == "(" ) { + brace = true; + t = t->next; + } + if (!t || t->type != TT_NAME) { + idParser::Error( "defined() without name in #if/#elif" ); + error = 1; + break; + } + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (FindHashedDefine(idParser::definehash, t->c_str())) { + v->intvalue = 1; + v->floatvalue = 1; + } + else { + v->intvalue = 0; + v->floatvalue = 0; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) { + t = t->next; + if (!t || (*t) != ")" ) { + idParser::Error( "defined missing ) in #if/#elif" ); + error = 1; + break; + } + } + brace = false; + // defined() creates a value + lastwasvalue = 1; + break; + } + case TT_NUMBER: + { + if (lastwasvalue) { + idParser::Error( "syntax error in #if/#elif" ); + error = 1; + break; + } + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (negativevalue) { + v->intvalue = - t->GetIntValue(); + v->floatvalue = - t->GetFloatValue(); + } + else { + v->intvalue = t->GetIntValue(); + v->floatvalue = t->GetFloatValue(); + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } + case TT_PUNCTUATION: + { + if (negativevalue) { + idParser::Error( "misplaced minus sign in #if/#elif" ); + error = 1; + break; + } + if (t->subtype == P_PARENTHESESOPEN) { + parentheses++; + break; + } + else if (t->subtype == P_PARENTHESESCLOSE) { + parentheses--; + if (parentheses < 0) { + idParser::Error( "too many ) in #if/#elsif" ); + error = 1; + } + break; + } + //check for invalid operators on floating point values + if ( !integer ) { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) { + idParser::Error( "illigal operator '%s' on floating point operands\n", t->c_str() ); + error = 1; + break; + } + } + switch( t->subtype ) { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) { + idParser::Error( "! or ~ after value in #if/#elif" ); + error = 1; + break; + } + break; + } + case P_INC: + case P_DEC: + { + idParser::Error( "++ or -- used in #if/#elif" ); + break; + } + case P_SUB: + { + if (!lastwasvalue) { + negativevalue = 1; + break; + } + } + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) { + idParser::Error( "operator '%s' after operator in #if/#elif", t->c_str() ); + error = 1; + break; + } + break; + } + default: + { + idParser::Error( "invalid operator '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + } + if (!error && !negativevalue) { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator(o); + o->op = t->subtype; + o->priority = PC_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } + break; + } + default: + { + idParser::Error( "unknown '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + } + if (error) { + break; + } + } + if (!error) { + if (!lastwasvalue) { + idParser::Error( "trailing operator in #if/#elif" ); + error = 1; + } + else if (parentheses) { + idParser::Error( "too many ( in #if/#elif" ); + error = 1; + } + } + // + gotquestmarkvalue = false; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while( !error && firstoperator ) { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) { + break; + } + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) { + break; + } + } + //if the arity of the operator isn't equal to 1 + if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { + v = v->next; + } + //if there's no value or no next value + if (!v) { + idParser::Error( "mising values in #if/#elif" ); + error = 1; + break; + } + } + if (error) { + break; + } + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if (integer) { + Log_Write("operator %s, value1 = %d", idParser::scriptstack->getPunctuationFromId(o->op), v1->intvalue); + if (v2) Log_Write("value2 = %d", v2->intvalue); + } + else { + Log_Write("operator %s, value1 = %f", idParser::scriptstack->getPunctuationFromId(o->op), v1->floatvalue); + if (v2) Log_Write("value2 = %f", v2->floatvalue); + } +#endif //DEBUG_EVAL + switch(o->op) { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + idParser::Error( "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + idParser::Error( "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) { + idParser::Error( ": without ? in #if/#elif" ); + error = 1; + break; + } + if (integer) { + if (!questmarkintvalue) + v1->intvalue = v2->intvalue; + } + else { + if (!questmarkfloatvalue) + v1->floatvalue = v2->floatvalue; + } + gotquestmarkvalue = false; + break; + } + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) { + idParser::Error( "? after ? in #if/#elif" ); + error = 1; + break; + } + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = true; + break; + } + } +#ifdef DEBUG_EVAL + if (integer) Log_Write("result value = %d", v1->intvalue); + else Log_Write("result value = %f", v1->floatvalue); +#endif //DEBUG_EVAL + if (error) + break; + lastoperatortype = o->op; + //if not an operator with arity 1 + if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { + //remove the second value if not question mark operator + if (o->op != P_QUESTIONMARK) { + v = v->next; + } + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //FreeMemory(v); + FreeValue(v); + } + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //FreeMemory(o); + FreeOperator(o); + } + if (firstvalue) { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } + for (o = firstoperator; o; o = lastoperator) { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator(o); + } + for (v = firstvalue; v; v = lastvalue) { + lastvalue = v->next; + //FreeMemory(v); + FreeValue(v); + } + if (!error) { + return true; + } + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + return false; +} + +/* +================ +idParser::Evaluate +================ +*/ +int idParser::Evaluate( signed long int *intvalue, double *floatvalue, int integer ) { + idToken token, *firsttoken, *lasttoken; + idToken *t, *nexttoken; + define_t *define; + int defined = false; + + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + // + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "no value after #if/#elif" ); + return false; + } + firsttoken = NULL; + lasttoken = NULL; + do { + //if the token is a name + if (token.type == TT_NAME) { + if (defined) { + defined = false; + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if ( token == "defined" ) { + defined = true; + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + //then it must be a define + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (!define) { + idParser::Error( "can't Evaluate '%s', not defined", token.c_str() ); + return false; + } + if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { + return false; + } + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + idParser::Error( "can't Evaluate '%s'", token.c_str() ); + return false; + } + } while(idParser::ReadLine( &token )); + // + if ( !idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer ) ) { + return false; + } + // +#ifdef DEBUG_EVAL + Log_Write("eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->c_str()); +#endif //DEBUG_EVAL + nexttoken = t->next; + delete t; + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("eval result: %d", *intvalue); + else Log_Write("eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return true; +} + +/* +================ +idParser::DollarEvaluate +================ +*/ +int idParser::DollarEvaluate( signed long int *intvalue, double *floatvalue, int integer) { + int indent, defined = false; + idToken token, *firsttoken, *lasttoken; + idToken *t, *nexttoken; + define_t *define; + + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + // + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "no leading ( after $evalint/$evalfloat" ); + return false; + } + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "nothing to Evaluate" ); + return false; + } + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do { + //if the token is a name + if (token.type == TT_NAME) { + if (defined) { + defined = false; + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if ( token == "defined" ) { + defined = true; + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + //then it must be a define + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (!define) { + idParser::Warning( "can't Evaluate '%s', not defined", token.c_str() ); + return false; + } + if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { + return false; + } + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { + if ( token[0] == '(' ) indent++; + else if ( token[0] == ')' ) indent--; + if (indent <= 0) { + break; + } + t = new (TAG_IDLIB_PARSER) idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + idParser::Error( "can't Evaluate '%s'", token.c_str() ); + return false; + } + } while(idParser::ReadSourceToken( &token )); + // + if (!idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer)) { + return false; + } + // +#ifdef DEBUG_EVAL + Log_Write("$eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->c_str()); +#endif //DEBUG_EVAL + nexttoken = t->next; + delete t; + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("$eval result: %d", *intvalue); + else Log_Write("$eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return true; +} + +/* +================ +idParser::Directive_elif +================ +*/ +int idParser::Directive_elif() { + signed long int value; + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type || type == INDENT_ELSE) { + idParser::Error( "misplaced #elif" ); + return false; + } + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + skip = (value == 0); + idParser::PushIndent( INDENT_ELIF, skip ); + return true; +} + +/* +================ +idParser::Directive_if +================ +*/ +int idParser::Directive_if() { + signed long int value; + int skip; + + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + skip = (value == 0); + idParser::PushIndent( INDENT_IF, skip ); + return true; +} + +/* +================ +idParser::Directive_line +================ +*/ +int idParser::Directive_line() { + idToken token; + + idParser::Error( "#line directive not supported" ); + while( idParser::ReadLine( &token ) ) { + } + return true; +} + +/* +================ +idParser::Directive_error +================ +*/ +int idParser::Directive_error() { + idToken token; + + if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { + idParser::Error( "#error without string" ); + return false; + } + idParser::Error( "#error: %s", token.c_str() ); + return true; +} + +/* +================ +idParser::Directive_warning +================ +*/ +int idParser::Directive_warning() { + idToken token; + + if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { + idParser::Warning( "#warning without string" ); + return false; + } + idParser::Warning( "#warning: %s", token.c_str() ); + return true; +} + +/* +================ +idParser::Directive_pragma +================ +*/ +int idParser::Directive_pragma() { + idToken token; + + idParser::Warning( "#pragma directive not supported" ); + while( idParser::ReadLine( &token ) ) { + } + return true; +} + +/* +================ +idParser::UnreadSignToken +================ +*/ +void idParser::UnreadSignToken() { + idToken token; + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + token = "-"; + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + idParser::UnreadSourceToken( &token ); +} + +/* +================ +idParser::Directive_eval +================ +*/ +int idParser::Directive_eval() { + signed long int value; + idToken token; + char buf[128]; + + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf(buf, "%d", abs(value)); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::Directive_evalfloat +================ +*/ +int idParser::Directive_evalfloat() { + double value; + idToken token; + char buf[128]; + + if ( !idParser::Evaluate( NULL, &value, false ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf(buf, "%1.2f", idMath::Fabs(value)); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + idParser::UnreadSourceToken( &token ); + if (value < 0) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::ReadDirective +================ +*/ +int idParser::ReadDirective() { + idToken token; + + //read the directive name + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "found '#' without name" ); + return false; + } + //directive name must be on the same line + if (token.linesCrossed > 0) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "found '#' at end of line" ); + return false; + } + //if if is a name + if (token.type == TT_NAME) { + if ( token == "if" ) { + return idParser::Directive_if(); + } + else if ( token == "ifdef" ) { + return idParser::Directive_ifdef(); + } + else if ( token == "ifndef" ) { + return idParser::Directive_ifndef(); + } + else if ( token == "elif" ) { + return idParser::Directive_elif(); + } + else if ( token == "else" ) { + return idParser::Directive_else(); + } + else if ( token == "endif" ) { + return idParser::Directive_endif(); + } + else if (idParser::skip > 0) { + // skip the rest of the line + while( idParser::ReadLine( &token ) ) { + } + return true; + } + else { + if ( token == "include" ) { + return idParser::Directive_include(); + } + else if ( token == "define" ) { + return idParser::Directive_define(); + } + else if ( token == "undef" ) { + return idParser::Directive_undef(); + } + else if ( token == "line" ) { + return idParser::Directive_line(); + } + else if ( token == "error" ) { + return idParser::Directive_error(); + } + else if ( token == "warning" ) { + return idParser::Directive_warning(); + } + else if ( token == "pragma" ) { + return idParser::Directive_pragma(); + } + else if ( token == "eval" ) { + return idParser::Directive_eval(); + } + else if ( token == "evalfloat" ) { + return idParser::Directive_evalfloat(); + } + } + } + idParser::Error( "unknown precompiler directive '%s'", token.c_str() ); + return false; +} + +/* +================ +idParser::DollarDirective_evalint +================ +*/ +int idParser::DollarDirective_evalint() { + signed long int value; + idToken token; + char buf[128]; + + if ( !idParser::DollarEvaluate( &value, NULL, true ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf( buf, "%d", abs( value ) ); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL | TT_VALUESVALID; + token.intvalue = abs( value ); + token.floatvalue = abs( value ); + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::DollarDirective_evalfloat +================ +*/ +int idParser::DollarDirective_evalfloat() { + double value; + idToken token; + char buf[128]; + + if ( !idParser::DollarEvaluate( NULL, &value, false ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf( buf, "%1.2f", fabs( value ) ); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL | TT_VALUESVALID; + token.intvalue = (unsigned long) fabs( value ); + token.floatvalue = fabs( value ); + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::ReadDollarDirective +================ +*/ +int idParser::ReadDollarDirective() { + idToken token; + + // read the directive name + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "found '$' without name" ); + return false; + } + // directive name must be on the same line + if ( token.linesCrossed > 0 ) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "found '$' at end of line" ); + return false; + } + // if if is a name + if (token.type == TT_NAME) { + if ( token == "evalint" ) { + return idParser::DollarDirective_evalint(); + } + else if ( token == "evalfloat" ) { + return idParser::DollarDirective_evalfloat(); + } + } + idParser::UnreadSourceToken( &token ); + return false; +} + +/* +================ +idParser::ReadToken +================ +*/ +int idParser::ReadToken( idToken *token ) { + define_t *define; + + while(1) { + if ( !idParser::ReadSourceToken( token ) ) { + return false; + } + // check for precompiler directives + if ( token->type == TT_PUNCTUATION && (*token)[0] == '#' && (*token)[1] == '\0' ) { + // read the precompiler directive + if ( !idParser::ReadDirective() ) { + return false; + } + continue; + } + // if skipping source because of conditional compilation + if ( idParser::skip ) { + continue; + } + // recursively concatenate strings that are behind each other still resolving defines + if ( token->type == TT_STRING && !(idParser::scriptstack->GetFlags() & LEXFL_NOSTRINGCONCAT) ) { + idToken newtoken; + if ( idParser::ReadToken( &newtoken ) ) { + if ( newtoken.type == TT_STRING ) { + token->Append( newtoken.c_str() ); + } + else { + idParser::UnreadSourceToken( &newtoken ); + } + } + } + // + if ( !(idParser::scriptstack->GetFlags() & LEXFL_NODOLLARPRECOMPILE) ) { + // check for special precompiler directives + if ( token->type == TT_PUNCTUATION && (*token)[0] == '$' && (*token)[1] == '\0' ) { + // read the precompiler directive + if ( idParser::ReadDollarDirective() ) { + continue; + } + } + } + // if the token is a name + if ( token->type == TT_NAME && !( token->flags & TOKEN_FL_RECURSIVE_DEFINE ) ) { + // check if the name is a define macro + define = FindHashedDefine( idParser::definehash, token->c_str() ); + // if it is a define macro + if ( define ) { + // expand the defined macro + if ( !idParser::ExpandDefineIntoSource( token, define ) ) { + return false; + } + continue; + } + } + // found a token + return true; + } +} + +/* +================ +idParser::ExpectTokenString +================ +*/ +int idParser::ExpectTokenString( const char *string ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't find expected '%s'", string ); + return false; + } + + if ( token != string ) { + idParser::Error( "expected '%s' but found '%s'", string, token.c_str() ); + return false; + } + return true; +} + +/* +================ +idParser::ExpectTokenType +================ +*/ +int idParser::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !idParser::ReadToken( token ) ) { + idParser::Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + idParser::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + idParser::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + idParser::Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + idParser::Error( "expected '%s' but found '%s'", scriptstack->GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} + +/* +================ +idParser::ExpectAnyToken +================ +*/ +int idParser::ExpectAnyToken( idToken *token ) { + if (!idParser::ReadToken( token )) { + idParser::Error( "couldn't read expected token" ); + return false; + } + else { + return true; + } +} + +/* +================ +idParser::CheckTokenString +================ +*/ +int idParser::CheckTokenString( const char *string ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return false; + } + //if the token is available + if ( tok == string ) { + return true; + } + + UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::CheckTokenType +================ +*/ +int idParser::CheckTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return false; + } + //if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return true; + } + + UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::PeekTokenString +================ +*/ +int idParser::PeekTokenString( const char *string ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return false; + } + + UnreadSourceToken( &tok ); + + // if the token is available + if ( tok == string ) { + return true; + } + return false; +} + +/* +================ +idParser::PeekTokenType +================ +*/ +int idParser::PeekTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if ( !ReadToken( &tok ) ) { + return false; + } + + UnreadSourceToken( &tok ); + + // if the type matches + if ( tok.type == type && ( tok.subtype & subtype ) == subtype ) { + *token = tok; + return true; + } + return false; +} + +/* +================ +idParser::SkipUntilString +================ +*/ +int idParser::SkipUntilString( const char *string ) { + idToken token; + + while(idParser::ReadToken( &token )) { + if ( token == string ) { + return true; + } + } + return false; +} + +/* +================ +idParser::SkipRestOfLine +================ +*/ +int idParser::SkipRestOfLine() { + idToken token; + + while(idParser::ReadToken( &token )) { + if ( token.linesCrossed ) { + idParser::UnreadSourceToken( &token ); + return true; + } + } + return false; +} + +/* +================= +idParser::SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +int idParser::SkipBracedSection( bool parseFirstBrace ) { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if( token.type == TT_PUNCTUATION ) { + if( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; +} + +/* +================= +idParser::ParseBracedSectionExact + +The next token should be an open brace. +Parses until a matching close brace is found. +Maintains the exact formating of the braced section + + FIXME: what about precompilation ? +================= +*/ +const char *idParser::ParseBracedSectionExact( idStr &out, int tabs ) { + return scriptstack->ParseBracedSectionExact( out, tabs ); +} + + +/* +======================== +idParser::ParseBracedSection + +The next token should be an open brace. Parses until a matching close brace is found. Internal +brace depths are properly skipped. +======================== +*/ +const char* idParser::ParseBracedSection( idStr& out, int tabs, bool parseFirstBrace, char intro, char outro ) { + idToken token; + int i, depth; + bool doTabs; + + char temp[ 2 ] = { 0, 0 }; + *temp = intro; + + out.Empty(); + if ( parseFirstBrace ) { + if ( !ExpectTokenString( temp ) ) { + return out.c_str(); + } + out = temp; + } + depth = 1; + doTabs = ( tabs >= 0 ); + do { + if ( !ReadToken( &token ) ) { + Error( "missing closing brace" ); + return out.c_str(); + } + + // if the token is on a new line + for ( i = 0; i < token.linesCrossed; i++ ) { + out += "\r\n"; + } + + if ( doTabs && token.linesCrossed ) { + i = tabs; + if ( token[ 0 ] == outro && i > 0 ) { + i--; + } + while( i-- > 0 ) { + out += "\t"; + } + } + if ( token.type == TT_STRING ) { + out += "\"" + token + "\""; + } else if ( token.type == TT_LITERAL ) { + out += "\'" + token + "\'"; + } else { + if ( token[ 0 ] == intro ) { + depth++; + if ( doTabs ) { + tabs++; + } + } else if ( token[ 0 ] == outro ) { + depth--; + if ( doTabs ) { + tabs--; + } + } + out += token; + } + out += " "; + } while( depth ); + + return out.c_str(); +} + +/* +================= +idParser::ParseRestOfLine + + parse the rest of the line +================= +*/ +const char *idParser::ParseRestOfLine( idStr &out ) { + idToken token; + + out.Empty(); + while(idParser::ReadToken( &token )) { + if ( token.linesCrossed ) { + idParser::UnreadSourceToken( &token ); + break; + } + if ( out.Length() ) { + out += " "; + } + out += token; + } + return out.c_str(); +} + +/* +================ +idParser::UnreadToken +================ +*/ +void idParser::UnreadToken( idToken *token ) { + idParser::UnreadSourceToken( token ); +} + +/* +================ +idParser::ReadTokenOnLine +================ +*/ +int idParser::ReadTokenOnLine( idToken *token ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + // if no lines were crossed before this token + if ( !tok.linesCrossed ) { + *token = tok; + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::ParseInt +================ +*/ +int idParser::ParseInt() { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idParser::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + idParser::Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} + +/* +================ +idParser::ParseBool +================ +*/ +bool idParser::ParseBool() { + idToken token; + + if ( !idParser::ExpectTokenType( TT_NUMBER, 0, &token ) ) { + idParser::Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} + +/* +================ +idParser::ParseFloat +================ +*/ +float idParser::ParseFloat() { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't read expected floating point number" ); + return 0.0f; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idParser::ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + idParser::Error( "expected float value, found '%s'", token.c_str() ); + } + return token.GetFloatValue(); +} + +/* +================ +idParser::Parse1DMatrix +================ +*/ +int idParser::Parse1DMatrix( int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = idParser::ParseFloat(); + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::Parse2DMatrix +================ +*/ +int idParser::Parse2DMatrix( int y, int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !idParser::Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::Parse3DMatrix +================ +*/ +int idParser::Parse3DMatrix( int z, int y, int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !idParser::Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::GetLastWhiteSpace +================ +*/ +int idParser::GetLastWhiteSpace( idStr &whiteSpace ) const { + if ( scriptstack ) { + scriptstack->GetLastWhiteSpace( whiteSpace ); + } else { + whiteSpace.Clear(); + } + return whiteSpace.Length(); +} + +/* +================ +idParser::SetMarker +================ +*/ +void idParser::SetMarker() { + marker_p = NULL; +} + +/* +================ +idParser::GetStringFromMarker + + FIXME: this is very bad code, the script isn't even garrenteed to still be around +================ +*/ +void idParser::GetStringFromMarker( idStr& out, bool clean ) { + char* p; + char save; + + if ( marker_p == NULL ) { + marker_p = scriptstack->buffer; + } + + if ( tokens ) { + p = (char*)tokens->whiteSpaceStart_p; + } else { + p = (char*)scriptstack->script_p; + } + + // Set the end character to NULL to give us a complete string + save = *p; + *p = 0; + + // If cleaning then reparse + if ( clean ) { + idParser temp( marker_p, strlen( marker_p ), "temp", flags ); + idToken token; + while ( temp.ReadToken ( &token ) ) { + out += token; + } + } else { + out = marker_p; + } + + // restore the character we set to NULL + *p = save; +} + +/* +================ +idParser::SetIncludePath +================ +*/ +void idParser::SetIncludePath( const char *path ) { + idParser::includepath = path; + // add trailing path seperator + if (idParser::includepath[idParser::includepath.Length()-1] != '\\' && + idParser::includepath[idParser::includepath.Length()-1] != '/') { + idParser::includepath += PATHSEPARATOR_STR; + } +} + +/* +================ +idParser::SetPunctuations +================ +*/ +void idParser::SetPunctuations( const punctuation_t *p ) { + idParser::punctuations = p; +} + +/* +================ +idParser::SetFlags +================ +*/ +void idParser::SetFlags( int flags ) { + idLexer *s; + + idParser::flags = flags; + for ( s = idParser::scriptstack; s; s = s->next ) { + s->SetFlags( flags ); + } +} + +/* +================ +idParser::GetFlags +================ +*/ +int idParser::GetFlags() const { + return idParser::flags; +} + +/* +================ +idParser::LoadFile +================ +*/ +int idParser::LoadFile( const char *filename, bool OSPath ) { + idLexer *script; + + if ( idParser::loaded ) { + idLib::common->FatalError("idParser::loadFile: another source already loaded"); + return false; + } + script = new (TAG_IDLIB_PARSER) idLexer( filename, 0, OSPath ); + if ( !script->IsLoaded() ) { + delete script; + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + script->next = NULL; + idParser::OSPath = OSPath; + idParser::filename = filename; + idParser::scriptstack = script; + idParser::tokens = NULL; + idParser::indentstack = NULL; + idParser::skip = 0; + idParser::loaded = true; + + if ( !idParser::definehash ) { + idParser::defines = NULL; + idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *), TAG_IDLIB_PARSER ); + idParser::AddGlobalDefinesToSource(); + } + return true; +} + +/* +================ +idParser::LoadMemory +================ +*/ +int idParser::LoadMemory(const char *ptr, int length, const char *name ) { + idLexer *script; + + if ( idParser::loaded ) { + idLib::common->FatalError("idParser::loadMemory: another source already loaded"); + return false; + } + script = new (TAG_IDLIB_PARSER) idLexer( ptr, length, name ); + if ( !script->IsLoaded() ) { + delete script; + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + script->next = NULL; + idParser::filename = name; + idParser::scriptstack = script; + idParser::tokens = NULL; + idParser::indentstack = NULL; + idParser::skip = 0; + idParser::loaded = true; + + if ( !idParser::definehash ) { + idParser::defines = NULL; + idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *), TAG_IDLIB_PARSER ); + idParser::AddGlobalDefinesToSource(); + } + return true; +} + +/* +================ +idParser::FreeSource +================ +*/ +void idParser::FreeSource( bool keepDefines ) { + idLexer *script; + idToken *token; + define_t *define; + indent_t *indent; + int i; + + // free all the scripts + while( scriptstack ) { + script = scriptstack; + scriptstack = scriptstack->next; + delete script; + } + // free all the tokens + while( tokens ) { + token = tokens; + tokens = tokens->next; + delete token; + } + // free all indents + while( indentstack ) { + indent = indentstack; + indentstack = indentstack->next; + Mem_Free( indent ); + } + if ( !keepDefines ) { + // free hash table + if ( definehash ) { + // free defines + for ( i = 0; i < DEFINEHASHSIZE; i++ ) { + while( definehash[i] ) { + define = definehash[i]; + definehash[i] = definehash[i]->hashnext; + FreeDefine(define); + } + } + defines = NULL; + Mem_Free( idParser::definehash ); + definehash = NULL; + } + } + loaded = false; +} + +/* +================ +idParser::GetPunctuationFromId +================ +*/ +const char *idParser::GetPunctuationFromId( int id ) { + int i; + + if ( !idParser::punctuations ) { + idLexer lex; + return lex.GetPunctuationFromId( id ); + } + + for (i = 0; idParser::punctuations[i].p; i++) { + if ( idParser::punctuations[i].n == id ) { + return idParser::punctuations[i].p; + } + } + return "unkown punctuation"; +} + +/* +================ +idParser::GetPunctuationId +================ +*/ +int idParser::GetPunctuationId( const char *p ) { + int i; + + if ( !idParser::punctuations ) { + idLexer lex; + return lex.GetPunctuationId( p ); + } + + for (i = 0; idParser::punctuations[i].p; i++) { + if ( !strcmp(idParser::punctuations[i].p, p) ) { + return idParser::punctuations[i].n; + } + } + return 0; +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser() { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = 0; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( int flags ) { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( const char *filename, int flags, bool OSPath ) { + this->loaded = false; + this->OSPath = true; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + LoadFile( filename, OSPath ); +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( const char *ptr, int length, const char *name, int flags ) { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + LoadMemory( ptr, length, name ); +} + +/* +================ +idParser::~idParser +================ +*/ +idParser::~idParser() { + idParser::FreeSource( false ); +} + +/* +======================== +idParser::EndOfFile +======================== +*/ +bool idParser::EndOfFile() { + if ( scriptstack != NULL ) { + return (bool) scriptstack->EndOfFile(); + } + return true; +} + diff --git a/neo/idlib/Parser.h b/neo/idlib/Parser.h new file mode 100644 index 00000000..c75d38ec --- /dev/null +++ b/neo/idlib/Parser.h @@ -0,0 +1,284 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PARSER_H__ +#define __PARSER_H__ + +/* +=============================================================================== + + C/C++ compatible pre-compiler + +=============================================================================== +*/ + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +// macro definitions +typedef struct define_s { + char * name; // define name + int flags; // define flags + int builtin; // > 0 if builtin define + int numparms; // number of define parameters + idToken * parms; // define parameters + idToken * tokens; // macro tokens (possibly containing parm tokens) + struct define_s *next; // next defined macro in a list + struct define_s *hashnext; // next define in the hash chain +} define_t; + +// indents used for conditional compilation directives: +// #if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s { + int type; // indent type + int skip; // true if skipping current indent + idLexer * script; // script the indent was in + struct indent_s *next; // next indent on the indent stack +} indent_t; + + +class idParser { + +public: + // constructor + idParser(); + idParser( int flags ); + idParser( const char *filename, int flags = 0, bool OSPath = false ); + idParser( const char *ptr, int length, const char *name, int flags = 0 ); + // destructor + ~idParser(); + // load a source file + int LoadFile( const char *filename, bool OSPath = false ); + // load a source from the given memory with the given length + // NOTE: the ptr is expected to point at a valid C string: ptr[length] == '\0' + int LoadMemory( const char *ptr, int length, const char *name ); + // free the current source + void FreeSource( bool keepDefines = false ); + // returns true if a source is loaded + int IsLoaded() const { return idParser::loaded; } + // read a token from the source + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true if the next token equals the given string and removes the token from the source + int CheckTokenString( const char *string ); + // returns true if the next token equals the given type and removes the token from the source + int CheckTokenType( int type, int subtype, idToken *token ); + // returns true if the next token equals the given string but does not remove the token from the source + int PeekTokenString( const char *string ); + // returns true if the next token equals the given type but does not remove the token from the source + int PeekTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine(); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // parse a braced section into a string + const char* ParseBracedSection( idStr& out, int tabs, bool parseFirstBrace, char intro, char outro ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // unread the given token + void UnreadToken( idToken *token ); + // read a token only if on the current line + int ReadTokenOnLine( idToken *token ); + // read a signed integer + int ParseInt(); + // read a boolean + bool ParseBool(); + // read a floating point number + float ParseFloat(); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // get the white space before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // Set a marker in the source file (there is only one marker) + void SetMarker(); + // Get the string from the marker to the current position + void GetStringFromMarker( idStr& out, bool clean = false ); + // add a define to the source + int AddDefine( const char *string ); + // add builtin defines + void AddBuiltinDefines(); + // set the source include path + void SetIncludePath( const char *path ); + // set the punctuation set + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags() const; + // returns the current filename + const char * GetFileName() const; + // get current offset in current script + const int GetFileOffset() const; + // get file time for current script + const ID_TIME_T GetFileTime() const; + // returns the current line number + const int GetLineNum() const; + // print an error message + void Error( VERIFY_FORMAT_STRING const char *str, ... ) const; + // print a warning message + void Warning( VERIFY_FORMAT_STRING const char *str, ... ) const; + // returns true if at the end of the file + bool EndOfFile(); + // add a global define that will be added to all opened sources + static int AddGlobalDefine( const char *string ); + // remove the given global define + static int RemoveGlobalDefine( const char *name ); + // remove all global defines + static void RemoveAllGlobalDefines(); + // set the base folder to load files from + static void SetBaseFolder( const char *path ); + +private: + int loaded; // set when a source file is loaded from file or memory + idStr filename; // file name of the script + idStr includepath; // path to include files + bool OSPath; // true if the file was loaded from an OS path + const punctuation_t *punctuations; // punctuations to use + int flags; // flags used for script parsing + idLexer * scriptstack; // stack with scripts of the source + idToken * tokens; // tokens to read first + define_t * defines; // list with macro definitions + define_t ** definehash; // hash chain with defines + indent_t * indentstack; // stack with indents + int skip; // > 0 if skipping conditional code + const char* marker_p; + + static define_t *globaldefines; // list with global defines added to every source loaded + +private: + void PushIndent( int type, int skip ); + void PopIndent( int *type, int *skip ); + void PushScript( idLexer *script ); + int ReadSourceToken( idToken *token ); + int ReadLine( idToken *token ); + int UnreadSourceToken( idToken *token ); + int ReadDefineParms( define_t *define, idToken **parms, int maxparms ); + int StringizeTokens( idToken *tokens, idToken *token ); + int MergeTokens( idToken *t1, idToken *t2 ); + int ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ); + int ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ); + int ExpandDefineIntoSource( idToken *deftoken, define_t *define ); + void AddGlobalDefinesToSource(); + define_t * CopyDefine( define_t *define ); + define_t * FindHashedDefine(define_t **definehash, const char *name); + int FindDefineParm( define_t *define, const char *name ); + void AddDefineToHash(define_t *define, define_t **definehash); + static void PrintDefine( define_t *define ); + static void FreeDefine( define_t *define ); + static define_t *FindDefine( define_t *defines, const char *name ); + static define_t *DefineFromString( const char *string); + define_t * CopyFirstDefine(); + int Directive_include(); + int Directive_undef(); + int Directive_if_def( int type ); + int Directive_ifdef(); + int Directive_ifndef(); + int Directive_else(); + int Directive_endif(); + int EvaluateTokens( idToken *tokens, signed long int *intvalue, double *floatvalue, int integer ); + int Evaluate( signed long int *intvalue, double *floatvalue, int integer ); + int DollarEvaluate( signed long int *intvalue, double *floatvalue, int integer); + int Directive_define(); + int Directive_elif(); + int Directive_if(); + int Directive_line(); + int Directive_error(); + int Directive_warning(); + int Directive_pragma(); + void UnreadSignToken(); + int Directive_eval(); + int Directive_evalfloat(); + int ReadDirective(); + int DollarDirective_evalint(); + int DollarDirective_evalfloat(); + int ReadDollarDirective(); +}; + +ID_INLINE const char *idParser::GetFileName() const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileName(); + } + else { + return ""; + } +} + +ID_INLINE const int idParser::GetFileOffset() const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileOffset(); + } + else { + return 0; + } +} + +ID_INLINE const ID_TIME_T idParser::GetFileTime() const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileTime(); + } + else { + return 0; + } +} + +ID_INLINE const int idParser::GetLineNum() const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetLineNum(); + } + else { + return 0; + } +} + +#endif /* !__PARSER_H__ */ diff --git a/neo/idlib/RectAllocator.cpp b/neo/idlib/RectAllocator.cpp new file mode 100644 index 00000000..dacf28f6 --- /dev/null +++ b/neo/idlib/RectAllocator.cpp @@ -0,0 +1,171 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "precompiled.h" + + +/* + +This routine performs a tight packing of a list of rectangles, attempting to minimize the area +of the rectangle that encloses all of them. Algorithm order is N^2, so it is not apropriate +for lists with many thousands of elements. + +Contrast with idBitBlockAllocator, which is used incrementally with either fixed size or +size-doubling target areas. + +Typical uses: +packing glyphs into a font image +packing model surfaces into a skin atlas +packing images into swf atlases + +If you want a minimum alignment, ensure that all the sizes are multiples of that alignment, +or scale the input sizes down by that alignment and scale the outputPositions back up. + +*/ + +float RectPackingFraction( const idList &inputSizes, const idVec2i totalSize ) { + int totalArea = totalSize.Area(); + if ( totalArea == 0 ) { + return 0; + } + int inputArea = 0; + for ( int i = 0 ; i < inputSizes.Num() ; i++ ) { + inputArea += inputSizes[i].Area(); + } + return (float)inputArea / totalArea; +} + +class idSortrects : public idSort_Quick< int, idSortrects > { +public: + int SizeMetric( idVec2i v ) const { + // skinny rects will sort earlier than square ones, because + // they are more likely to grow the entire region + return v.x * v.x + v.y * v.y; + } + int Compare( const int & a, const int & b ) const { + return SizeMetric( (*inputSizes)[b] ) - SizeMetric( (*inputSizes)[a] ); + } + const idList *inputSizes; +}; + +void RectAllocator( const idList &inputSizes, idList &outputPositions, idVec2i &totalSize ) { + outputPositions.SetNum( inputSizes.Num() ); + if ( inputSizes.Num() == 0 ) { + totalSize.Set( 0, 0 ); + return; + } + idList sizeRemap; + sizeRemap.SetNum( inputSizes.Num() ); + for ( int i = 0; i < inputSizes.Num(); i++ ) { + sizeRemap[i] = i; + } + + // Sort the rects from largest to smallest (it makes allocating them in the image better) + idSortrects sortrectsBySize; + sortrectsBySize.inputSizes = &inputSizes; + sizeRemap.SortWithTemplate( sortrectsBySize ); + + // the largest rect goes to the top-left corner + outputPositions[sizeRemap[0]].Set( 0, 0 ); + + totalSize = inputSizes[sizeRemap[0]]; + + // For each image try to fit it at a corner of one of the already fitted images while + // minimizing the total area. + // Somewhat better allocation could be had by checking all the combinations of x and y edges + // in the allocated rectangles, rather than just the corners of each rectangle, but it + // still does a pretty good job. + static const int START_MAX = 1<<14; + for ( int i = 1; i < inputSizes.Num(); i++ ) { + idVec2i best( 0, 0 ); + idVec2i bestMax( START_MAX, START_MAX ); + idVec2i size = inputSizes[sizeRemap[i]]; + for ( int j = 0; j < i; j++ ) { + for ( int k = 1; k < 4; k++ ) { + idVec2i test; + for ( int n = 0 ; n < 2 ; n++ ) { + test[n] = outputPositions[sizeRemap[j]][n] + ( ( k >> n ) & 1 ) * inputSizes[sizeRemap[j]][n]; + } + + idVec2i newMax; + for ( int n = 0 ; n < 2 ; n++ ) { + newMax[n] = Max( totalSize[n], test[n] + size[n] ); + } + // widths must be multiples of 128 pixels / 32 DXT blocks to + // allow it to be used directly as a GPU texture without re-packing + // FIXME: make this a parameter + newMax[0] = (newMax[0]+31) & ~31; + + // don't let an image get larger than 1024 DXT block, or PS3 crashes + // FIXME: pass maxSize in as a parameter + if ( newMax[0] > 1024 || newMax[1] > 1024 ) { + continue; + } + + // if we have already found a spot that keeps the image smaller, don't bother checking here + // This calculation biases the rect towards more square shapes instead of + // allowing it to extend in one dimension for a long time. + int newSize = newMax.x * newMax.x + newMax.y * newMax.y; + int bestSize = bestMax.x * bestMax.x + bestMax.y * bestMax.y; + if ( newSize > bestSize ) { + continue; + } + + // if the image isn't required to grow, favor the location closest to the origin + if ( newSize == bestSize && best.x + best.y < test.x + test.y ) { + continue; + } + + // see if this spot overlaps any already allocated rect + int n = 0; + for ( ; n < i; n++ ) { + const idVec2i &check = outputPositions[sizeRemap[n]]; + const idVec2i &checkSize = inputSizes[sizeRemap[n]]; + if ( test.x + size.x > check.x && + test.y + size.y > check.y && + test.x < check.x + checkSize.x && + test.y < check.y + checkSize.y ) { + break; + } + } + if ( n < i ) { + // overlapped, can't use + continue; + } + best = test; + bestMax = newMax; + } + } + if ( bestMax[0] == START_MAX ) { // FIXME: return an error code + idLib::FatalError( "RectAllocator: couldn't fit everything" ); + } + outputPositions[sizeRemap[i]] = best; + totalSize = bestMax; + } +} + diff --git a/neo/idlib/SoftwareCache.cpp b/neo/idlib/SoftwareCache.cpp new file mode 100644 index 00000000..ef3047d1 --- /dev/null +++ b/neo/idlib/SoftwareCache.cpp @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "ParallelJobList_JobHeaders.h" + +/* +================================================================================================ + + Software Cache + +================================================================================================ +*/ + +uint32 globalDmaTag; + +bool SpursEmulationAssertFailed( const char *filename, int line, const char *expression ) { + static bool halt = true; + if ( halt ) { + __debugbreak(); + } + return true; +} diff --git a/neo/idlib/SoftwareCache.h b/neo/idlib/SoftwareCache.h new file mode 100644 index 00000000..3a185cc2 --- /dev/null +++ b/neo/idlib/SoftwareCache.h @@ -0,0 +1,513 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SOFTWARECACHE_H__ +#define __SOFTWARECACHE_H__ + +#pragma warning( disable : 4324 ) // structure was padded due to __declspec(align()) + +/* +================================================================================================ + +On-Demand Streamed Objects and Arrays + +idODSObject // DMA in a single object +idODSCachedObject // DMA in a single object through a software cache +idODSArray // DMA in an array with objects +idODSIndexedArray // DMA gather from an array with objects +idODSStreamedArray // overlapped DMA streaming of an array with objects +idODSStreamedIndexedArray // overlapped DMA gather from an array with objects + +On the SPU the 'idODSObject' streams the data into temporary memory using the DMA controller +and the object constructor immediately waits for the DMA transfer to complete. In other words +there is no caching and every random memory access incurs full memory latency. This should be +used to stream in objects that are only used once at unpredictable times. + +The 'idODSCachedObject' uses an object based software cache on the SPU which is useful for +streaming in objects that may be used repeatedly or which usage can be predicted allowing +the objects to be prefetched. + + class idMyType {}; + class idMyCache : public idSoftwareCache< idMyType, 8, 4 > {}; + idMyCache myCache; + idMyType * myPtr; + idODSCachedObject< idMyType, idMyCache > myODS( myPtr, myCache ); + +The 'idSoftwareCache' implements a Prefetch() function that can be used to prefetch whole +objects into the cache well before they are needed. However, any idODSObject, idODSArray, +idODSIndexedArray etc. after calling the Prefetch() function will have to wait for the +prefetch to complete. In other words, make sure there is enough "work" done in between +a Prefetch() call and the first next idODS* object. + +The 'idODSArray' streams in a block of objects that are tightly packed in memory. + +The 'idODSIndexedArray' is used to gather a number of objects that are not necessarily +contiguous in memory. On the SPU a DMA-list is used in the 'idODSIndexedArray' constructor +to efficiently gather all the objects. + +The 'idODSStreamedArray' is used for sequentially reading a large input array. Overlapped +streaming is used where one batch of array elements can be accessed while the next batch +is being streamed in. + +The 'idODSStreamedIndexedArray' is used for gathering elements from an array using a +sequentially read index. Overlapped streaming is used for both the index and the array +elements where one batch of array elements can be accessed while the next batch of +indices/array elements is being streamed in. + +Outside the SPU, data is never copied to temporary memory because this would cause +significant load-hit-store penalties. Instead, the object constructor issues prefetch +instructions where appropriate and only maintains pointers to the actual data. In the +case of 'idODSObject' or 'idODSCachedObject' the class is no more than a simple wrapper +of a pointer and the class should completely compile away with zero overhead. + +COMMON MISTAKES: + +1. When using ODS objects do not forget to set the "globalDmaTag" that is used to issue + and wait for DMAs. + + void cellSpursJobMain2( CellSpursJobContext2 * stInfo, CellSpursJob256 * job ) { + globalDmaTag = stInfo->dmaTag; // for ODS objects + } + +2. ODS objects can consume quite a bit of stack space. You may have to increase the SPU job + stack size. For instance: + + job->header.sizeStack = SPURS_QUADWORDS( 16 * 1024 ); // the ODS objects get pretty large + + Make sure you measure the size of each ODS object and if there are recursive functions + using ODS objects make sure the recursion is bounded. When the stack overflows the scratch + and output memory may get overwritten and the results will be undefined. Finding stack + overflows is painful. + +3. While you can setup a regular DMA list entry to use a NULL pointer with zero size, do not use + a NULL pointer for a cache DMA list entry. This confuses SPURS and can cause your SPU binary + to get corrupted. + +================================================================================================ +*/ + +extern uint32 globalDmaTag; + +#define MAX_DMA_SIZE ( 1 << 14 ) +#define ODS_ROUND16( x ) ( ( x + 15 ) & ~15 ) + +enum streamBufferType_t { + SBT_DOUBLE = 2, + SBT_QUAD = 4 +}; + + +/* +================================================================================================ + + non-SPU code + +================================================================================================ +*/ + +/* +================================================ +idSoftwareCache +================================================ +*/ +template< typename _type_, int _entries_ = 8, int _associativity_ = 4, bool aligned = false > +class ALIGNTYPE128 idSoftwareCache { +public: + void Prefetch( const _type_ * obj ) { + ::Prefetch( obj, 0 ); + } +}; + +/* +================================================ +idODSObject +================================================ +*/ +template< typename _type_ > +class idODSObject { +public: + idODSObject( const _type_ * obj ) : objectPtr( obj ) {} + operator const _type_ & () const { return *objectPtr; } + const _type_ * operator->() const { return objectPtr; } + const _type_ & Get() const { return *objectPtr; } + const _type_ * Ptr() const { return objectPtr; } + const _type_ * OriginalPtr() const { return objectPtr; } + +private: + const _type_ * objectPtr; +}; + +/* +================================================ +idODSCachedObject +================================================ +*/ +template< typename _type_, typename _cache_ > +class idODSCachedObject { +public: + idODSCachedObject( const _type_ * obj, _cache_ & cache ) : objectPtr( obj ) {} + operator const _type_ & () const { return *objectPtr; } + const _type_ * operator->() const { return objectPtr; } + const _type_ & Get() const { return *objectPtr; } + const _type_ * Ptr() const { return objectPtr; } + const _type_ * OriginalPtr() const { return objectPtr; } + +private: + const _type_ * objectPtr; +}; + +/* +================================================ +idODSArray +================================================ +*/ +template< typename _type_, int max > +class idODSArray { +public: + idODSArray( const _type_ * array, int num ) : arrayPtr( array ), arrayNum( num ) { + assert( num <= max ); + Prefetch( array, 0 ); + } + const _type_ & operator[]( int index ) const { + assert( index >= 0 && index < arrayNum ); + return arrayPtr[index]; + } + const _type_ * Ptr() const { return arrayPtr; } + const int Num() const { return arrayNum; } + +private: + const _type_ * arrayPtr; + int arrayNum; +}; + +/* +================================================ +idODSIndexedArray +================================================ +*/ +template< typename _elemType_, typename _indexType_, int max > +class idODSIndexedArray { +public: + idODSIndexedArray( const _elemType_ * array, const _indexType_ * index, int num ) : arrayNum( num ) { + assert( num <= max ); + for ( int i = 0; i < num; i++ ) { + Prefetch( arrayPtr, abs( index[i] ) * sizeof( _elemType_ ) ); + arrayPtr[i] = array + abs( index[i] ); + } + } + const _elemType_ & operator[]( int index ) const { + assert( index >= 0 && index < arrayNum ); + return * arrayPtr[index]; + } + void ReplicateUpToMultipleOfFour() { + assert( ( max & 3 ) == 0 ); + while( ( arrayNum & 3 ) != 0 ) { + arrayPtr[arrayNum++] = arrayPtr[0]; + } + } + +private: + const _elemType_ * arrayPtr[max]; + int arrayNum; +}; + +/* +================================================ +idODSStreamedOutputArray +================================================ +*/ +template< typename _type_, int _bufferSize_ > +class ALIGNTYPE16 idODSStreamedOutputArray { +public: + idODSStreamedOutputArray( _type_ * array, int * numElements, int maxElements ) : + localNum( 0 ), + outArray( array ), + outNum( numElements ), + outMax( maxElements ) { + compile_time_assert( CONST_ISPOWEROFTWO( _bufferSize_ ) ); + compile_time_assert( ( ( _bufferSize_ * sizeof( _type_ ) ) & 15 ) == 0 ); + compile_time_assert( _bufferSize_ * sizeof( _type_ ) < MAX_DMA_SIZE ); + assert_16_byte_aligned( array ); + } + ~idODSStreamedOutputArray() { + *outNum = localNum; + } + + int Num() const { return localNum; } + void Append( _type_ element ) { assert( localNum < outMax ); outArray[localNum++] = element; } + _type_ & Alloc() { assert( localNum < outMax ); return outArray[localNum++]; } + +private: + int localNum; + _type_ * outArray; + int * outNum; + int outMax; +}; + +/* +================================================ +idODSStreamedArray +================================================ +*/ +template< typename _type_, int _bufferSize_, streamBufferType_t _sbt_ = SBT_DOUBLE, int _roundUpToMultiple_ = 1 > +class ALIGNTYPE16 idODSStreamedArray { +public: + idODSStreamedArray( const _type_ * array, const int numElements ) : + cachedArrayStart( 0 ), + cachedArrayEnd( 0 ), + streamArrayEnd( 0 ), + inArray( array ), + inArrayNum( numElements ), + inArrayNumRoundedUp( numElements ) { + compile_time_assert( CONST_ISPOWEROFTWO( _bufferSize_ ) ); + compile_time_assert( ( ( _bufferSize_ * sizeof( _type_ ) ) & 15 ) == 0 ); + compile_time_assert( _bufferSize_ * sizeof( _type_ ) < MAX_DMA_SIZE ); + compile_time_assert( _roundUpToMultiple_ >= 1 ); + assert_16_byte_aligned( array ); + assert( (uintptr_t)array > _bufferSize_ * sizeof( _type_ ) ); + // Fetch the first batch of elements. + FetchNextBatch(); + // Calculate the rounded up size here making the mod effectively for free because we have to wait + // for memory access anyway while the above FetchNextBatch() does not need the rounded up size yet. + inArrayNumRoundedUp += _roundUpToMultiple_ - 1; + inArrayNumRoundedUp -= inArrayNumRoundedUp % ( ( _roundUpToMultiple_ > 1 ) ? _roundUpToMultiple_ : 1 ); + } + ~idODSStreamedArray() { + // Flush the accessible part of the array. + FlushArray( inArray, cachedArrayStart * sizeof( _type_ ), cachedArrayEnd * sizeof( _type_ ) ); + } + + // Fetches a new batch of array elements and returns the first index after this new batch. + // After calling this, the elements starting at the index returned by the previous call to + // FetchNextBach() (or zero if not yet called) up to (excluding) the index returned by + // this call to FetchNextBatch() can be accessed through the [] operator. When quad-buffering, + // the elements starting at the index returned by the second-from-last call to FetchNextBatch() + // can still be accessed. This is useful when the algorithm needs to successively access + // an odd number of elements at the same time that may cross a single buffer boundary. + int FetchNextBatch() { + // If not everything has been streamed already. + if ( cachedArrayEnd < inArrayNum ) { + cachedArrayEnd = streamArrayEnd; + cachedArrayStart = Max( cachedArrayEnd - _bufferSize_ * ( _sbt_ - 1 ), 0 ); + + // Flush the last batch of elements that is no longer accessible. + FlushArray( inArray, ( cachedArrayStart - _bufferSize_ ) * sizeof( _type_ ), cachedArrayStart * sizeof( _type_ ) ); + + // Prefetch the next batch of elements. + if ( streamArrayEnd < inArrayNum ) { + streamArrayEnd = Min( streamArrayEnd + _bufferSize_, inArrayNum ); + for ( unsigned int offset = cachedArrayEnd * sizeof( _type_ ); offset < streamArrayEnd * sizeof( _type_ ); offset += CACHE_LINE_SIZE ) { + Prefetch( inArray, offset ); + } + } + } + return ( cachedArrayEnd == inArrayNum ) ? inArrayNumRoundedUp : cachedArrayEnd; + } + + // Provides access to the elements starting at the index returned by the next-to-last call + // to FetchNextBach() (or zero if only called once so far) up to (excluding) the index + // returned by the last call to FetchNextBatch(). When quad-buffering, the elements starting + // at the index returned by the second-from-last call to FetchNextBatch() can still be accessed. + // This is useful when the algorithm needs to successively access an odd number of elements + // at the same time that may cross a single buffer boundary. + const _type_ & operator[]( int index ) const { + assert( ( index >= cachedArrayStart && index < cachedArrayEnd ) || ( cachedArrayEnd == inArrayNum && index >= inArrayNum && index < inArrayNumRoundedUp ) ); + if ( _roundUpToMultiple_ > 1 ) { + index &= ( index - inArrayNum ) >> 31; + } + return inArray[index]; + } + +private: + int cachedArrayStart; + int cachedArrayEnd; + int streamArrayEnd; + const _type_ * inArray; + int inArrayNum; + int inArrayNumRoundedUp; + + static void FlushArray( const void * flushArray, int flushStart, int flushEnd ) { +#if 0 + // arrayFlushBase is rounded up so we do not flush anything before the array. + // arrayFlushStart is rounded down so we start right after the last cache line that was previously flushed. + // arrayFlushEnd is rounded down so we do not flush a cache line that holds data that may still be partially + // accessible or a cache line that stretches beyond the end of the array. + const uintptr_t arrayAddress = (uintptr_t)flushArray; + const uintptr_t arrayFlushBase = ( arrayAddress + CACHE_LINE_SIZE - 1 ) & ~( CACHE_LINE_SIZE - 1 ); + const uintptr_t arrayFlushStart = ( arrayAddress + flushStart ) & ~( CACHE_LINE_SIZE - 1 ); + const uintptr_t arrayFlushEnd = ( arrayAddress + flushEnd ) & ~( CACHE_LINE_SIZE - 1 ); + for ( uintptr_t offset = Max( arrayFlushBase, arrayFlushStart ); offset < arrayFlushEnd; offset += CACHE_LINE_SIZE ) { + FlushCacheLine( flushArray, offset - arrayAddress ); + } +#endif + } +}; + +/* +================================================ +idODSStreamedIndexedArray + +For gathering elements from an array using a sequentially read index. +This uses overlapped streaming for both the index and the array elements +where one batch of indices and/or array elements can be accessed while +the next batch is being streamed in. + +NOTE: currently the size of array elements must be a multiple of 16 bytes. +An index with offsets and more complex logic is needed to support other sizes. +================================================ +*/ +template< typename _elemType_, typename _indexType_, int _bufferSize_, streamBufferType_t _sbt_ = SBT_DOUBLE, int _roundUpToMultiple_ = 1 > +class ALIGNTYPE16 idODSStreamedIndexedArray { +public: + idODSStreamedIndexedArray( const _elemType_ * array, const int numElements, const _indexType_ * index, const int numIndices ) : + cachedArrayStart( 0 ), + cachedArrayEnd( 0 ), + streamArrayEnd( 0 ), + cachedIndexStart( 0 ), + cachedIndexEnd( 0 ), + streamIndexEnd( 0 ), + inArray( array ), + inArrayNum( numElements ), + inIndex( index ), + inIndexNum( numIndices ), + inIndexNumRoundedUp( numIndices ) { + compile_time_assert( CONST_ISPOWEROFTWO( _bufferSize_ ) ); + compile_time_assert( ( ( _bufferSize_ * sizeof( _indexType_ ) ) & 15 ) == 0 ); + compile_time_assert( _bufferSize_ * sizeof( _indexType_ ) < MAX_DMA_SIZE ); + compile_time_assert( _bufferSize_ * sizeof( _elemType_ ) < MAX_DMA_SIZE ); + compile_time_assert( ( sizeof( _elemType_ ) & 15 ) == 0 ); // to avoid complexity due to cellDmaListGet + compile_time_assert( _roundUpToMultiple_ >= 1 ); + assert_16_byte_aligned( index ); + assert_16_byte_aligned( array ); + assert( (uintptr_t)index > _bufferSize_ * sizeof( _indexType_ ) ); + assert( (uintptr_t)array > _bufferSize_ * sizeof( _elemType_ ) ); + // Fetch the first batch of indices. + FetchNextBatch(); + // Fetch the first batch of elements and the next batch of indices. + FetchNextBatch(); + // Calculate the rounded up size here making the mod effectively for free because we have to wait + // for memory access anyway while the above FetchNextBatch() do not need the rounded up size yet. + inIndexNumRoundedUp += _roundUpToMultiple_ - 1; + inIndexNumRoundedUp -= inIndexNumRoundedUp % ( ( _roundUpToMultiple_ > 1 ) ? _roundUpToMultiple_ : 1 ); + } + ~idODSStreamedIndexedArray() { + // Flush the accessible part of the index. + FlushArray( inIndex, cachedIndexStart * sizeof( _indexType_ ), cachedIndexEnd * sizeof( _indexType_ ) ); + // Flush the accessible part of the array. + FlushArray( inArray, cachedArrayStart * sizeof( _elemType_ ), cachedArrayEnd * sizeof( _elemType_ ) ); + } + + // Fetches a new batch of array elements and returns the first index after this new batch. + // After calling this, the elements starting at the index returned by the previous call to + // FetchNextBach() (or zero if not yet called) up to (excluding) the index returned by + // this call to FetchNextBatch() can be accessed through the [] operator. When quad-buffering, + // the elements starting at the index returned by the second-from-last call to FetchNextBatch() + // can still be accessed. This is useful when the algorithm needs to successively access + // an odd number of elements at the same time that may cross a single buffer boundary. + int FetchNextBatch() { + // If not everything has been streamed already. + if ( cachedArrayEnd < inIndexNum ) { + if ( streamIndexEnd > 0 ) { + cachedArrayEnd = streamArrayEnd; + cachedArrayStart = Max( cachedArrayEnd - _bufferSize_ * ( _sbt_ - 1 ), 0 ); + cachedIndexEnd = streamIndexEnd; + cachedIndexStart = Max( cachedIndexEnd - _bufferSize_ * ( _sbt_ - 1 ), 0 ); + + // Flush the last batch of indices that are no longer accessible. + FlushArray( inIndex, ( cachedIndexStart - _bufferSize_ ) * sizeof( _indexType_ ), cachedIndexStart * sizeof( _indexType_ ) ); + // Flush the last batch of elements that is no longer accessible. + FlushArray( inArray, ( cachedArrayStart - _bufferSize_ ) * sizeof( _elemType_ ), cachedArrayStart * sizeof( _elemType_ ) ); + + // Prefetch the next batch of elements. + if ( streamArrayEnd < inIndexNum ) { + streamArrayEnd = cachedIndexEnd; + for ( int i = cachedArrayEnd; i < streamArrayEnd; i++ ) { + assert( i >= cachedIndexStart && i < cachedIndexEnd ); + assert( inIndex[i] >= 0 && inIndex[i] < inArrayNum ); + + Prefetch( inArray, inIndex[i] * sizeof( _elemType_ ) ); + } + } + } + + // Prefetch the next batch of indices. + if ( streamIndexEnd < inIndexNum ) { + streamIndexEnd = Min( streamIndexEnd + _bufferSize_, inIndexNum ); + for ( unsigned int offset = cachedIndexEnd * sizeof( _indexType_ ); offset < streamIndexEnd * sizeof( _indexType_ ); offset += CACHE_LINE_SIZE ) { + Prefetch( inIndex, offset ); + } + } + } + return ( cachedArrayEnd == inIndexNum ) ? inIndexNumRoundedUp : cachedArrayEnd; + } + + // Provides access to the elements starting at the index returned by the next-to-last call + // to FetchNextBach() (or zero if only called once so far) up to (excluding) the index + // returned by the last call to FetchNextBatch(). When quad-buffering, the elements starting + // at the index returned by the second-from-last call to FetchNextBatch() can still be accessed. + // This is useful when the algorithm needs to successively access an odd number of elements + // at the same time that may cross a single buffer boundary. + const _elemType_ & operator[]( int index ) const { + assert( ( index >= cachedArrayStart && index < cachedArrayEnd ) || ( cachedArrayEnd == inIndexNum && index >= inIndexNum && index < inIndexNumRoundedUp ) ); + if ( _roundUpToMultiple_ > 1 ) { + index &= ( index - inIndexNum ) >> 31; + } + return inArray[inIndex[index]]; + } + +private: + int cachedArrayStart; + int cachedArrayEnd; + int streamArrayEnd; + int cachedIndexStart; + int cachedIndexEnd; + int streamIndexEnd; + const _elemType_ * inArray; + int inArrayNum; + const _indexType_ * inIndex; + int inIndexNum; + int inIndexNumRoundedUp; + + static void FlushArray( const void * flushArray, int flushStart, int flushEnd ) { +#if 0 + // arrayFlushBase is rounded up so we do not flush anything before the array. + // arrayFlushStart is rounded down so we start right after the last cache line that was previously flushed. + // arrayFlushEnd is rounded down so we do not flush a cache line that holds data that may still be partially + // accessible or a cache line that stretches beyond the end of the array. + const uintptr_t arrayAddress = (uintptr_t)flushArray; + const uintptr_t arrayFlushBase = ( arrayAddress + CACHE_LINE_SIZE - 1 ) & ~( CACHE_LINE_SIZE - 1 ); + const uintptr_t arrayFlushStart = ( arrayAddress + flushStart ) & ~( CACHE_LINE_SIZE - 1 ); + const uintptr_t arrayFlushEnd = ( arrayAddress + flushEnd ) & ~( CACHE_LINE_SIZE - 1 ); + for ( uintptr_t offset = Max( arrayFlushBase, arrayFlushStart ); offset < arrayFlushEnd; offset += CACHE_LINE_SIZE ) { + FlushCacheLine( flushArray, offset - arrayAddress ); + } +#endif + } +}; + + +#endif // !__SOFTWARECACHE_H__ diff --git a/neo/idlib/Str.cpp b/neo/idlib/Str.cpp new file mode 100644 index 00000000..5073649c --- /dev/null +++ b/neo/idlib/Str.cpp @@ -0,0 +1,2090 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#ifdef USE_STRING_DATA_ALLOCATOR +static idDynamicBlockAlloc stringDataAllocator; +#endif + +idVec4 g_color_table[16] = +{ + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(1.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_RED + idVec4(0.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_GREEN + idVec4(1.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_YELLOW + idVec4(0.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_BLUE + idVec4(0.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_CYAN + idVec4(1.0f, 0.5f, 0.0f, 1.0f), // S_COLOR_ORANGE + idVec4(1.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_WHITE + idVec4(0.5f, 0.5f, 0.5f, 1.0f), // S_COLOR_GRAY + idVec4(0.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_BLACK + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), +}; + +const char *units[2][4] = +{ + { "B", "KB", "MB", "GB" }, + { "B/s", "KB/s", "MB/s", "GB/s" } +}; + +/* +============ +idStr::ColorForIndex +============ +*/ +idVec4 & idStr::ColorForIndex( int i ) { + return g_color_table[ i & 15 ]; +} + +/* +============ +idStr::ReAllocate +============ +*/ +void idStr::ReAllocate( int amount, bool keepold ) { + char *newbuffer; + int newsize; + int mod; + + //assert( data ); + assert( amount > 0 ); + + mod = amount % STR_ALLOC_GRAN; + if ( !mod ) { + newsize = amount; + } + else { + newsize = amount + STR_ALLOC_GRAN - mod; + } + SetAlloced( newsize ); + +#ifdef USE_STRING_DATA_ALLOCATOR + newbuffer = stringDataAllocator.Alloc( GetAlloced() ); +#else + newbuffer = new (TAG_STRING) char[ GetAlloced() ]; +#endif + if ( keepold && data ) { + data[ len ] = '\0'; + strcpy( newbuffer, data ); + } + + if ( data && data != baseBuffer ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Free( data ); +#else + delete [] data; +#endif + } + + data = newbuffer; +} + +/* +============ +idStr::FreeData +============ +*/ +void idStr::FreeData() { + if ( IsStatic() ) { + return; + } + + if ( data && data != baseBuffer ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Free( data ); +#else + delete[] data; +#endif + data = baseBuffer; + } +} + +/* +============ +idStr::operator= +============ +*/ +void idStr::operator=( const char *text ) { + int l; + int diff; + int i; + + if ( !text ) { + // safe behavior if NULL + EnsureAlloced( 1, false ); + data[ 0 ] = '\0'; + len = 0; + return; + } + + if ( text == data ) { + return; // copying same thing + } + + // check if we're aliasing + if ( text >= data && text <= data + len ) { + diff = text - data; + + assert( strlen( text ) < (unsigned)len ); + + for ( i = 0; text[ i ]; i++ ) { + data[ i ] = text[ i ]; + } + + data[ i ] = '\0'; + + len -= diff; + + return; + } + + l = strlen( text ); + EnsureAlloced( l + 1, false ); + strcpy( data, text ); + len = l; +} + +/* +============ +idStr::FindChar + +returns -1 if not found otherwise the index of the char +============ +*/ +int idStr::FindChar( const char *str, const char c, int start, int end ) { + int i; + + if ( end == -1 ) { + end = strlen( str ) - 1; + } + for ( i = start; i <= end; i++ ) { + if ( str[i] == c ) { + return i; + } + } + return -1; +} + +/* +============ +idStr::FindText + +returns -1 if not found otherwise the index of the text +============ +*/ +int idStr::FindText( const char *str, const char *text, bool casesensitive, int start, int end ) { + int l, i, j; + + if ( end == -1 ) { + end = strlen( str ); + } + l = end - strlen( text ); + for ( i = start; i <= l; i++ ) { + if ( casesensitive ) { + for ( j = 0; text[j]; j++ ) { + if ( str[i+j] != text[j] ) { + break; + } + } + } else { + for ( j = 0; text[j]; j++ ) { + if ( ::toupper( str[i+j] ) != ::toupper( text[j] ) ) { + break; + } + } + } + if ( !text[j] ) { + return i; + } + } + return -1; +} + +/* +============ +idStr::Filter + +Returns true if the string conforms the given filter. +Several metacharacter may be used in the filter. + +* match any string of zero or more characters +? match any single character +[abc...] match any of the enclosed characters; a hyphen can + be used to specify a range (e.g. a-z, A-Z, 0-9) + +============ +*/ +bool idStr::Filter( const char *filter, const char *name, bool casesensitive ) { + idStr buf; + int i, found, index; + + while(*filter) { + if (*filter == '*') { + filter++; + buf.Empty(); + for (i = 0; *filter; i++) { + if ( *filter == '*' || *filter == '?' || (*filter == '[' && *(filter+1) != '[') ) { + break; + } + buf += *filter; + if ( *filter == '[' ) { + filter++; + } + filter++; + } + if ( buf.Length() ) { + index = idStr(name).Find( buf.c_str(), casesensitive ); + if ( index == -1 ) { + return false; + } + name += index + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[') { + if ( *(filter+1) == '[' ) { + if ( *name != '[' ) { + return false; + } + filter += 2; + name++; + } + else { + filter++; + found = false; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') { + break; + } + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) { + found = true; + } + } + else { + if ( ::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter+2)) ) { + found = true; + } + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) { + found = true; + } + } + else { + if ( ::toupper(*filter) == ::toupper(*name) ) { + found = true; + } + } + filter++; + } + } + if (!found) { + return false; + } + while(*filter) { + if ( *filter == ']' && *(filter+1) != ']' ) { + break; + } + filter++; + } + filter++; + name++; + } + } + else { + if (casesensitive) { + if (*filter != *name) { + return false; + } + } + else { + if ( ::toupper(*filter) != ::toupper(*name) ) { + return false; + } + } + filter++; + name++; + } + } + return true; +} + +/* +============= +idStr::StripMediaName + + makes the string lower case, replaces backslashes with forward slashes, and removes extension +============= +*/ +void idStr::StripMediaName( const char *name, idStr &mediaName ) { + char c; + + mediaName.Empty(); + + for ( c = *name; c; c = *(++name) ) { + // truncate at an extension + if ( c == '.' ) { + break; + } + // convert backslashes to forward slashes + if ( c == '\\' ) { + mediaName.Append( '/' ); + } else { + mediaName.Append( idStr::ToLower( c ) ); + } + } +} + +/* +============= +idStr::CheckExtension +============= +*/ +bool idStr::CheckExtension( const char *name, const char *ext ) { + const char *s1 = name + Length( name ) - 1; + const char *s2 = ext + Length( ext ) - 1; + int c1, c2, d; + + do { + c1 = *s1--; + c2 = *s2--; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return false; + } + } while( s1 > name && s2 > ext ); + + return ( s1 >= name ); +} + +/* +============= +idStr::FloatArrayToString +============= +*/ +const char *idStr::FloatArrayToString( const float *array, const int length, const int precision ) { + static int index = 0; + static char str[4][16384]; // in case called by nested functions + int i, n; + char format[16], *s; + + // use an array of string so that multiple calls won't collide + s = str[ index ]; + index = (index + 1) & 3; + + idStr::snPrintf( format, sizeof( format ), "%%.%df", precision ); + n = idStr::snPrintf( s, sizeof( str[0] ), format, array[0] ); + if ( precision > 0 ) { + while( n > 0 && s[n-1] == '0' ) s[--n] = '\0'; + while( n > 0 && s[n-1] == '.' ) s[--n] = '\0'; + } + idStr::snPrintf( format, sizeof( format ), " %%.%df", precision ); + for ( i = 1; i < length; i++ ) { + n += idStr::snPrintf( s + n, sizeof( str[0] ) - n, format, array[i] ); + if ( precision > 0 ) { + while( n > 0 && s[n-1] == '0' ) s[--n] = '\0'; + while( n > 0 && s[n-1] == '.' ) s[--n] = '\0'; + } + } + return s; +} + +/* +======================== +idStr::CStyleQuote +======================== +*/ +const char *idStr::CStyleQuote( const char *str ) { + static int index = 0; + static char buffers[4][16384]; // in case called by nested functions + unsigned int i; + char *buf; + + buf = buffers[index]; + index = ( index + 1 ) & 3; + + buf[0] = '\"'; + for ( i = 1; i < sizeof( buffers[0] ) - 2; i++ ) { + int c = *str++; + switch( c ) { + case '\0': buf[i++] = '\"'; buf[i] = '\0'; return buf; + case '\\': buf[i++] = '\\'; buf[i] = '\\'; break; + case '\n': buf[i++] = '\\'; buf[i] = 'n'; break; + case '\r': buf[i++] = '\\'; buf[i] = 'r'; break; + case '\t': buf[i++] = '\\'; buf[i] = 't'; break; + case '\v': buf[i++] = '\\'; buf[i] = 'v'; break; + case '\b': buf[i++] = '\\'; buf[i] = 'b'; break; + case '\f': buf[i++] = '\\'; buf[i] = 'f'; break; + case '\a': buf[i++] = '\\'; buf[i] = 'a'; break; + case '\'': buf[i++] = '\\'; buf[i] = '\''; break; + case '\"': buf[i++] = '\\'; buf[i] = '\"'; break; + case '\?': buf[i++] = '\\'; buf[i] = '\?'; break; + default: buf[i] = c; break; + } + } + buf[i++] = '\"'; + buf[i] = '\0'; + return buf; +} + +/* +======================== +idStr::CStyleUnQuote +======================== +*/ +const char *idStr::CStyleUnQuote( const char *str ) { + if ( str[0] != '\"' ) { + return str; + } + + static int index = 0; + static char buffers[4][16384]; // in case called by nested functions + unsigned int i; + char *buf; + + buf = buffers[index]; + index = ( index + 1 ) & 3; + + str++; + for ( i = 0; i < sizeof( buffers[0] ) - 1; i++ ) { + int c = *str++; + if ( c == '\0' ) { + break; + } else if ( c == '\\' ) { + c = *str++; + switch( c ) { + case '\\': buf[i] = '\\'; break; + case 'n': buf[i] = '\n'; break; + case 'r': buf[i] = '\r'; break; + case 't': buf[i] = '\t'; break; + case 'v': buf[i] = '\v'; break; + case 'b': buf[i] = '\b'; break; + case 'f': buf[i] = '\f'; break; + case 'a': buf[i] = '\a'; break; + case '\'': buf[i] = '\''; break; + case '\"': buf[i] = '\"'; break; + case '\?': buf[i] = '\?'; break; + } + } else { + buf[i] = c; + } + } + assert( buf[i-1] == '\"' ); + buf[i-1] = '\0'; + return buf; +} + +/* +============ +idStr::Last + +returns -1 if not found otherwise the index of the char +============ +*/ +int idStr::Last( const char c ) const { + int i; + + for( i = Length(); i > 0; i-- ) { + if ( data[ i - 1 ] == c ) { + return i - 1; + } + } + + return -1; +} + +/* +======================== +idStr::Format + +perform a threadsafe sprintf to the string +======================== +*/ +void idStr::Format( const char *fmt, ... ) { + va_list argptr; + char text[MAX_PRINT_MSG]; + + va_start( argptr, fmt ); + int len = idStr::vsnPrintf( text, sizeof( text ) - 1, fmt, argptr ); + va_end( argptr ); + text[ sizeof( text ) - 1 ] = '\0'; + + if ( (size_t)len >= sizeof( text ) - 1 ) { + idLib::common->FatalError( "Tried to set a large buffer using %s", fmt ); + } + *this = text; +} + +/* +======================== +idStr::FormatInt + +Formats integers with commas for readability. +======================== +*/ +idStr idStr::FormatInt( const int num, bool isCash ) { + idStr val = va( "%d", num ); + int len = val.Length(); + for ( int i = 0 ; i < ( ( len - 1 ) / 3 ); i++ ) { + int pos = val.Length() - ( ( i + 1 ) * 3 + i ); + if ( pos > 1 || val[0] != '-' ) { + val.Insert( ',', pos ); + } + } + + if ( isCash ) { + val.Insert( '$', val[0] == '-' ? 1 : 0 ); + } + + return val; +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripLeading( const char c ) { + while( data[ 0 ] == c ) { + memmove( &data[ 0 ], &data[ 1 ], len ); + len--; + } +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripLeading( const char *string ) { + int l; + + l = strlen( string ); + if ( l > 0 ) { + while ( !Cmpn( string, l ) ) { + memmove( data, data + l, len - l + 1 ); + len -= l; + } + } +} + +/* +============ +idStr::StripLeadingOnce +============ +*/ +bool idStr::StripLeadingOnce( const char *string ) { + int l; + + l = strlen( string ); + if ( ( l > 0 ) && !Cmpn( string, l ) ) { + memmove( data, data + l, len - l + 1 ); + len -= l; + return true; + } + return false; +} + +/* +============ +idStr::StripTrailing +============ +*/ +void idStr::StripTrailing( const char c ) { + int i; + + for( i = Length(); i > 0 && data[ i - 1 ] == c; i-- ) { + data[ i - 1 ] = '\0'; + len--; + } +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripTrailing( const char *string ) { + int l; + + l = strlen( string ); + if ( l > 0 ) { + while ( ( len >= l ) && !Cmpn( string, data + len - l, l ) ) { + len -= l; + data[len] = '\0'; + } + } +} + +/* +============ +idStr::StripTrailingOnce +============ +*/ +bool idStr::StripTrailingOnce( const char *string ) { + int l; + + l = strlen( string ); + if ( ( l > 0 ) && ( len >= l ) && !Cmpn( string, data + len - l, l ) ) { + len -= l; + data[len] = '\0'; + return true; + } + return false; +} + +/* +============ +idStr::Replace +============ +*/ +bool idStr::ReplaceChar( const char old, const char nw ) { + bool replaced = false; + for ( int i = 0; i < Length(); i++ ) { + if ( data[i] == old ) { + data[i] = nw; + replaced = true; + } + } + return replaced; +} + +/* +============ +idStr::Replace +============ +*/ +bool idStr::Replace( const char *old, const char *nw ) { + int oldLen = strlen( old ); + int newLen = strlen( nw ); + + // Work out how big the new string will be + int count = 0; + for ( int i = 0; i < Length(); i++ ) { + if ( idStr::Cmpn( &data[i], old, oldLen ) == 0 ) { + count++; + i += oldLen - 1; + } + } + + if ( count ) { + idStr oldString( data ); + + EnsureAlloced( len + ( ( newLen - oldLen ) * count ) + 2, false ); + + // Replace the old data with the new data + int j = 0; + for ( int i = 0; i < oldString.Length(); i++ ) { + if ( idStr::Cmpn( &oldString[i], old, oldLen ) == 0 ) { + memcpy( data + j, nw, newLen ); + i += oldLen - 1; + j += newLen; + } else { + data[j] = oldString[i]; + j++; + } + } + data[j] = 0; + len = strlen( data ); + return true; + } + return false; +} + +/* +============ +idStr::Mid +============ +*/ +const char *idStr::Mid( int start, int len, idStr &result ) const { + int i; + + result.Empty(); + + i = Length(); + if ( i == 0 || len <= 0 || start >= i ) { + return NULL; + } + + if ( start + len >= i ) { + len = i - start; + } + + result.Append( &data[ start ], len ); + return result; +} + +/* +============ +idStr::Mid +============ +*/ +idStr idStr::Mid( int start, int len ) const { + int i; + idStr result; + + i = Length(); + if ( i == 0 || len <= 0 || start >= i ) { + return result; + } + + if ( start + len >= i ) { + len = i - start; + } + + result.Append( &data[ start ], len ); + return result; +} + +/* +============ +idStr::StripTrailingWhitespace +============ +*/ +void idStr::StripTrailingWhitespace() { + int i; + + // cast to unsigned char to prevent stripping off high-ASCII characters + for( i = Length(); i > 0 && (unsigned char)(data[ i - 1 ]) <= ' '; i-- ) { + data[ i - 1 ] = '\0'; + len--; + } +} + +/* +============ +idStr::StripQuotes + +Removes the quotes from the beginning and end of the string +============ +*/ +idStr& idStr::StripQuotes () +{ + if ( data[0] != '\"' ) + { + return *this; + } + + // Remove the trailing quote first + if ( data[len-1] == '\"' ) + { + data[len-1] = '\0'; + len--; + } + + // Strip the leading quote now + len--; + memmove( &data[ 0 ], &data[ 1 ], len ); + data[len] = '\0'; + + return *this; +} + +/* +===================================================================== + + filename methods + +===================================================================== +*/ + +/* +============ +idStr::FileNameHash +============ +*/ +int idStr::FileNameHash() const { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while( data[i] != '\0' ) { + letter = idStr::ToLower( data[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter =='\\' ) { + letter = '/'; + } + hash += (long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +============ +idStr::BackSlashesToSlashes +============ +*/ +idStr &idStr::BackSlashesToSlashes() { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == '\\' ) { + data[ i ] = '/'; + } + } + return *this; +} + +/* +============ +idStr::SlashesToBackSlashes +============ +*/ +idStr &idStr::SlashesToBackSlashes() { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == '/' ) { + data[ i ] = '\\'; + } + } + return *this; +} + +/* +============ +idStr::SetFileExtension +============ +*/ +idStr &idStr::SetFileExtension( const char *extension ) { + StripFileExtension(); + if ( *extension != '.' ) { + Append( '.' ); + } + Append( extension ); + return *this; +} + +/* +============ +idStr::StripFileExtension +============ +*/ +idStr &idStr::StripFileExtension() { + int i; + + for ( i = len-1; i >= 0; i-- ) { + if ( data[i] == '.' ) { + data[i] = '\0'; + len = i; + break; + } + } + return *this; +} + +/* +============ +idStr::StripAbsoluteFileExtension +============ +*/ +idStr &idStr::StripAbsoluteFileExtension() { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[i] == '.' ) { + data[i] = '\0'; + len = i; + break; + } + } + + return *this; +} + +/* +================== +idStr::DefaultFileExtension +================== +*/ +idStr &idStr::DefaultFileExtension( const char *extension ) { + int i; + + // do nothing if the string already has an extension + for ( i = len-1; i >= 0; i-- ) { + if ( data[i] == '.' ) { + return *this; + } + } + if ( *extension != '.' ) { + Append( '.' ); + } + Append( extension ); + return *this; +} + +/* +================== +idStr::DefaultPath +================== +*/ +idStr &idStr::DefaultPath( const char *basepath ) { + if ( ( ( *this )[ 0 ] == '/' ) || ( ( *this )[ 0 ] == '\\' ) ) { + // absolute path location + return *this; + } + + *this = basepath + *this; + return *this; +} + +/* +==================== +idStr::AppendPath +==================== +*/ +void idStr::AppendPath( const char *text ) { + int pos; + int i = 0; + + if ( text && text[i] ) { + pos = len; + EnsureAlloced( len + strlen( text ) + 2 ); + + if ( pos ) { + if ( data[ pos-1 ] != '/' ) { + data[ pos++ ] = '/'; + } + } + if ( text[i] == '/' ) { + i++; + } + + for ( ; text[ i ]; i++ ) { + if ( text[ i ] == '\\' ) { + data[ pos++ ] = '/'; + } else { + data[ pos++ ] = text[ i ]; + } + } + len = pos; + data[ pos ] = '\0'; + } +} + +/* +================== +idStr::StripFilename +================== +*/ +idStr &idStr::StripFilename() { + int pos; + + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos ] != '/' ) && ( ( *this )[ pos ] != '\\' ) ) { + pos--; + } + + if ( pos < 0 ) { + pos = 0; + } + + CapLength( pos ); + return *this; +} + +/* +================== +idStr::StripPath +================== +*/ +idStr &idStr::StripPath() { + int pos; + + pos = Length(); + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + *this = Right( Length() - pos ); + return *this; +} + +/* +==================== +idStr::ExtractFilePath +==================== +*/ +void idStr::ExtractFilePath( idStr &dest ) const { + int pos; + + // + // back up until a \ or the start + // + pos = Length(); + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + Left( pos, dest ); +} + +/* +==================== +idStr::ExtractFileName +==================== +*/ +void idStr::ExtractFileName( idStr &dest ) const { + int pos; + + // + // back up until a \ or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + Right( Length() - pos, dest ); +} + +/* +==================== +idStr::ExtractFileBase +==================== +*/ +void idStr::ExtractFileBase( idStr &dest ) const { + int pos; + int start; + + // + // back up until a \ or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + start = pos; + while( ( pos < Length() ) && ( ( *this )[ pos ] != '.' ) ) { + pos++; + } + + Mid( start, pos - start, dest ); +} + +/* +==================== +idStr::ExtractFileExtension +==================== +*/ +void idStr::ExtractFileExtension( idStr &dest ) const { + int pos; + + // + // back up until a . or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '.' ) ) { + pos--; + } + + if ( !pos ) { + // no extension + dest.Empty(); + } else { + Right( Length() - pos, dest ); + } +} + + +/* +===================================================================== + + char * methods to replace library functions + +===================================================================== +*/ + +/* +============ +idStr::IsNumeric + +Checks a string to see if it contains only numerical values. +============ +*/ +bool idStr::IsNumeric( const char *s ) { + int i; + bool dot; + + if ( *s == '-' ) { + s++; + } + + dot = false; + for ( i = 0; s[i]; i++ ) { + if ( !isdigit( (const unsigned char)s[i] ) ) { + if ( ( s[ i ] == '.' ) && !dot ) { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +/* +============ +idStr::HasLower + +Checks if a string has any lowercase chars +============ +*/ +bool idStr::HasLower( const char *s ) { + if ( !s ) { + return false; + } + + while ( *s ) { + if ( CharIsLower( *s ) ) { + return true; + } + s++; + } + + return false; +} + +/* +============ +idStr::HasUpper + +Checks if a string has any uppercase chars +============ +*/ +bool idStr::HasUpper( const char *s ) { + if ( !s ) { + return false; + } + + while ( *s ) { + if ( CharIsUpper( *s ) ) { + return true; + } + s++; + } + + return false; +} + +/* +================ +idStr::Cmp +================ +*/ +int idStr::Cmp( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + if ( d ) { + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Cmpn +================ +*/ +int idStr::Cmpn( const char *s1, const char *s2, int n ) { + int c1, c2, d; + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + if ( d ) { + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmp +================ +*/ +int idStr::Icmp( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmpn +================ +*/ +int idStr::Icmpn( const char *s1, const char *s2, int n ) { + int c1, c2, d; + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmp +================ +*/ +int idStr::IcmpNoColor( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + while ( idStr::IsColor( s1 ) ) { + s1 += 2; + } + while ( idStr::IsColor( s2 ) ) { + s2 += 2; + } + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::IcmpPath +================ +*/ +int idStr::IcmpPath( const char *s1, const char *s2 ) { + int c1, c2, d; + +#if 0 +//#if !defined( ID_PC_WIN ) + idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" ); +#endif + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c1 == '\\' ) { + d += ('/' - '\\'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 == '\\' ) { + d -= ('/' - '\\'); + if ( !d ) { + break; + } + } + // make sure folders come first + while( c1 ) { + if ( c1 == '/' || c1 == '\\' ) { + break; + } + c1 = *s1++; + } + while( c2 ) { + if ( c2 == '/' || c2 == '\\' ) { + break; + } + c2 = *s2++; + } + if ( c1 && !c2 ) { + return -1; + } else if ( !c1 && c2 ) { + return 1; + } + // same folder depth so use the regular compare + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; +} + +/* +================ +idStr::IcmpnPath +================ +*/ +int idStr::IcmpnPath( const char *s1, const char *s2, int n ) { + int c1, c2, d; + +#if 0 +//#if !defined( ID_PC_WIN ) + idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" ); +#endif + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c1 == '\\' ) { + d += ('/' - '\\'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 == '\\' ) { + d -= ('/' - '\\'); + if ( !d ) { + break; + } + } + // make sure folders come first + while( c1 ) { + if ( c1 == '/' || c1 == '\\' ) { + break; + } + c1 = *s1++; + } + while( c2 ) { + if ( c2 == '/' || c2 == '\\' ) { + break; + } + c2 = *s2++; + } + if ( c1 && !c2 ) { + return -1; + } else if ( !c1 && c2 ) { + return 1; + } + // same folder depth so use the regular compare + return ( INT32_SIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; +} + +/* +============= +idStr::Copynz + +Safe strncpy that ensures a trailing zero +============= +*/ +void idStr::Copynz( char *dest, const char *src, int destsize ) { + if ( !src ) { + idLib::common->Warning( "idStr::Copynz: NULL src" ); + return; + } + if ( destsize < 1 ) { + idLib::common->Warning( "idStr::Copynz: destsize < 1" ); + return; + } + + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +/* +================ +idStr::Append + + never goes past bounds or leaves without a terminating 0 +================ +*/ +void idStr::Append( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + idLib::common->Error( "idStr::Append: already overflowed" ); + } + idStr::Copynz( dest + l1, src, size - l1 ); +} + +/* +======================== +idStr::IsValidUTF8 +======================== +*/ +bool idStr::IsValidUTF8( const uint8 * s, const int maxLen, utf8Encoding_t & encoding ) { + struct local_t { + static int GetNumEncodedUTF8Bytes( const uint8 c ) { + if ( c < 0x80 ) { + return 1; + } else if ( ( c >> 5 ) == 0x06 ) { + // 2 byte encoding - the next byte must begin with + return 2; + } else if ( ( c >> 4 ) == 0x0E ) { + // 3 byte encoding + return 3; + } else if ( ( c >> 5 ) == 0x1E ) { + // 4 byte encoding + return 4; + } + // this isnt' a valid UTF-8 precursor character + return 0; + } + static bool RemainingCharsAreUTF8FollowingBytes( const uint8 * s, const int curChar, const int maxLen, const int num ) { + if ( maxLen - curChar < num ) { + return false; + } + for ( int i = curChar + 1; i <= curChar + num; i++ ) { + if ( s[ i ] == '\0' ) { + return false; + } + if ( ( s[ i ] >> 6 ) != 0x02 ) { + return false; + } + } + return true; + } + }; + + // check for byte-order-marker + encoding = UTF8_PURE_ASCII; + utf8Encoding_t utf8Type = UTF8_ENCODED_NO_BOM; + if ( maxLen > 3 && s[ 0 ] == 0xEF && s[ 1 ] == 0xBB && s[ 2 ] == 0xBF ) { + utf8Type = UTF8_ENCODED_BOM; + } + + for ( int i = 0; s[ i ] != '\0' && i < maxLen; i++ ) { + int numBytes = local_t::GetNumEncodedUTF8Bytes( s[ i ] ); + if ( numBytes == 1 ) { + continue; // just low ASCII + } else if ( numBytes == 2 ) { + // 2 byte encoding - the next byte must begin with bit pattern 10 + if ( !local_t::RemainingCharsAreUTF8FollowingBytes( s, i, maxLen, 1 ) ) { + return false; + } + // skip over UTF-8 character + i += 1; + encoding = utf8Type; + } else if ( numBytes == 3 ) { + // 3 byte encoding - the next 2 bytes must begin with bit pattern 10 + if ( !local_t::RemainingCharsAreUTF8FollowingBytes( s, i, maxLen, 2 ) ) { + return false; + } + // skip over UTF-8 character + i += 2; + encoding = utf8Type; + } else if ( numBytes == 4 ) { + // 4 byte encoding - the next 3 bytes must begin with bit pattern 10 + if ( !local_t::RemainingCharsAreUTF8FollowingBytes( s, i, maxLen, 3 ) ) { + return false; + } + // skip over UTF-8 character + i += 3; + encoding = utf8Type; + } else { + // this isnt' a valid UTF-8 character + if ( utf8Type == UTF8_ENCODED_BOM ) { + encoding = UTF8_INVALID_BOM; + } else { + encoding = UTF8_INVALID; + } + return false; + } + } + return true; +} + +/* +======================== +idStr::UTF8Length +======================== +*/ +int idStr::UTF8Length( const byte * s ) { + int mbLen = 0; + int charLen = 0; + while ( s[ mbLen ] != '\0' ) { + uint32 cindex; + cindex = s[ mbLen ]; + if ( cindex < 0x80 ) { + mbLen++; + } else { + int trailing = 0; + if ( cindex >= 0xc0 ) { + static const byte trailingBytes[ 64 ] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + }; + trailing = trailingBytes[ cindex - 0xc0 ]; + } + mbLen += trailing + 1; + } + charLen++; + } + return charLen; +} + + +/* +======================== +idStr::AppendUTF8Char +======================== +*/ +void idStr::AppendUTF8Char( uint32 c ) { + if ( c < 0x80 ) { + Append( ( char)c ); + } else if ( c < 0x800 ) { // 11 bits + Append( (char)( 0xC0 | ( c >> 6 ) ) ); + Append( (char)( 0x80 | ( c & 0x3F ) ) ); + } else if ( c < 0x10000 ) { // 16 bits + Append( (char)( 0xE0 | ( c >> 12 ) ) ); + Append( (char)( 0x80 | ( ( c >> 6 ) & 0x3F ) ) ); + Append( (char)( 0x80 | ( c & 0x3F ) ) ); + } else if ( c < 0x200000 ) { // 21 bits + Append( (char)( 0xF0 | ( c >> 18 ) ) ); + Append( (char)( 0x80 | ( ( c >> 12 ) & 0x3F ) ) ); + Append( (char)( 0x80 | ( ( c >> 6 ) & 0x3F ) ) ); + Append( (char)( 0x80 | ( c & 0x3F ) ) ); + } else { + // UTF-8 can encode up to 6 bytes. Why don't we support that? + // This is an invalid Unicode character + Append( '?' ); + } +} + +/* +======================== +idStr::UTF8Char +======================== +*/ +uint32 idStr::UTF8Char( const byte * s, int & idx ) { + if ( idx >= 0 ) { + while ( s[ idx ] != '\0' ) { + uint32 cindex = s[ idx ]; + if ( cindex < 0x80 ) { + idx++; + return cindex; + } + int trailing = 0; + if ( cindex >= 0xc0 ) { + static const byte trailingBytes[ 64 ] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + }; + trailing = trailingBytes[ cindex - 0xc0 ]; + } + static const uint32 trailingMask[ 6 ] = { 0x0000007f, 0x0000001f, 0x0000000f, 0x00000007, 0x00000003, 0x00000001 }; + cindex &= trailingMask[ trailing ]; + while ( trailing-- > 0 ) { + cindex <<= 6; + cindex += s[ ++idx ] & 0x0000003f; + } + idx++; + return cindex; + } + } + idx++; + return 0; // return a null terminator if out of range +} + +/* +================ +idStr::LengthWithoutColors +================ +*/ +int idStr::LengthWithoutColors( const char *s ) { + int len; + const char *p; + + if ( !s ) { + return 0; + } + + len = 0; + p = s; + while( *p ) { + if ( idStr::IsColor( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + +/* +================ +idStr::RemoveColors +================ +*/ +char *idStr::RemoveColors( char *string ) { + char *d; + char *s; + int c; + + s = string; + d = string; + while( (c = *s) != 0 ) { + if ( idStr::IsColor( s ) ) { + s++; + } + else { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + +/* +================ +idStr::snPrintf +================ +*/ +int idStr::snPrintf( char *dest, int size, const char *fmt, ...) { + int len; + va_list argptr; + char buffer[32000]; // big, but small enough to fit in PPC stack + + va_start( argptr, fmt ); + len = vsprintf( buffer, fmt, argptr ); + va_end( argptr ); + if ( len >= sizeof( buffer ) ) { + idLib::common->Error( "idStr::snPrintf: overflowed buffer" ); + } + if ( len >= size ) { + idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i\n", len, size ); + len = size; + } + idStr::Copynz( dest, buffer, size ); + return len; +} + +/* +============ +idStr::vsnPrintf + +vsnprintf portability: + +C99 standard: vsnprintf returns the number of characters (excluding the trailing +'\0') which would have been written to the final string if enough space had been available +snprintf and vsnprintf do not write more than size bytes (including the trailing '\0') + +win32: _vsnprintf returns the number of characters written, not including the terminating null character, +or a negative value if an output error occurs. If the number of characters to write exceeds count, then count +characters are written and -1 is returned and no trailing '\0' is added. + +idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0) +or returns -1 on failure or if the buffer would be overflowed. +============ +*/ +int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) { + int ret; + +#undef _vsnprintf + ret = _vsnprintf( dest, size-1, fmt, argptr ); +#define _vsnprintf use_idStr_vsnPrintf + dest[size-1] = '\0'; + if ( ret < 0 || ret >= size ) { + return -1; + } + return ret; +} + +/* +============ +sprintf + +Sets the value of the string using a printf interface. +============ +*/ +int sprintf( idStr &string, const char *fmt, ... ) { + int l; + va_list argptr; + char buffer[32000]; + + va_start( argptr, fmt ); + l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr ); + va_end( argptr ); + buffer[sizeof(buffer)-1] = '\0'; + + string = buffer; + return l; +} + +/* +============ +vsprintf + +Sets the value of the string using a vprintf interface. +============ +*/ +int vsprintf( idStr &string, const char *fmt, va_list argptr ) { + int l; + char buffer[32000]; + + l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr ); + buffer[sizeof(buffer)-1] = '\0'; + + string = buffer; + return l; +} + +/* +============ +va + +does a varargs printf into a temp buffer +NOTE: not thread safe +============ +*/ +char *va( const char *fmt, ... ) { + va_list argptr; + static int index = 0; + static char string[4][16384]; // in case called by nested functions + char *buf; + + buf = string[index]; + index = (index + 1) & 3; + + va_start( argptr, fmt ); + vsprintf( buf, fmt, argptr ); + va_end( argptr ); + + return buf; +} + + + +/* +============ +idStr::BestUnit +============ +*/ +int idStr::BestUnit( const char *format, float value, Measure_t measure ) { + int unit = 1; + while ( unit <= 3 && ( 1 << ( unit * 10 ) < value ) ) { + unit++; + } + unit--; + value /= 1 << ( unit * 10 ); + sprintf( *this, format, value ); + *this += " "; + *this += units[ measure ][ unit ]; + return unit; +} + +/* +============ +idStr::SetUnit +============ +*/ +void idStr::SetUnit( const char *format, float value, int unit, Measure_t measure ) { + value /= 1 << ( unit * 10 ); + sprintf( *this, format, value ); + *this += " "; + *this += units[ measure ][ unit ]; +} + +/* +================ +idStr::InitMemory +================ +*/ +void idStr::InitMemory() { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Init(); +#endif +} + +/* +================ +idStr::ShutdownMemory +================ +*/ +void idStr::ShutdownMemory() { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Shutdown(); +#endif +} + +/* +================ +idStr::PurgeMemory +================ +*/ +void idStr::PurgeMemory() { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.FreeEmptyBaseBlocks(); +#endif +} + +/* +================ +idStr::ShowMemoryUsage_f +================ +*/ +void idStr::ShowMemoryUsage_f( const idCmdArgs &args ) { +#ifdef USE_STRING_DATA_ALLOCATOR + idLib::common->Printf( "%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n", + stringDataAllocator.GetBaseBlockMemory() >> 10, stringDataAllocator.GetFreeBlockMemory() >> 10, + stringDataAllocator.GetNumFreeBlocks(), stringDataAllocator.GetNumEmptyBaseBlocks() ); +#endif +} + +/* +================ +idStr::FormatNumber +================ +*/ +struct formatList_t { + int gran; + int count; +}; + +// elements of list need to decend in size +formatList_t formatList[] = { + { 1000000000, 0 }, + { 1000000, 0 }, + { 1000, 0 } +}; + +int numFormatList = sizeof(formatList) / sizeof( formatList[0] ); + + +idStr idStr::FormatNumber( int number ) { + idStr string; + bool hit; + + // reset + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + li->count = 0; + } + + // main loop + do { + hit = false; + + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + + if ( number >= li->gran ) { + li->count++; + number -= li->gran; + hit = true; + break; + } + } + } while ( hit ); + + // print out + bool found = false; + + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + + if ( li->count ) { + if ( !found ) { + string += va( "%i,", li->count ); + } else { + string += va( "%3.3i,", li->count ); + } + found = true; + } + else if ( found ) { + string += va( "%3.3i,", li->count ); + } + } + + if ( found ) { + string += va( "%3.3i", number ); + } + else { + string += va( "%i", number ); + } + + // pad to proper size + int count = 11 - string.Length(); + + for ( int i = 0; i < count; i++ ) { + string.Insert( " ", 0 ); + } + + return string; +} + +CONSOLE_COMMAND( testStrId, "prints a localized string", 0 ) { + if ( args.Argc() != 2 ) { + idLib::Printf( "need a str id like 'STR_SWF_ACCEPT' without the hash, it gets parsed as a separate argument\n" ); + return; + } + + idStrId str( va( "#%s", args.Argv( 1 ) ) ); + idLib::Printf( "%s = %s\n", args.Argv( 1 ), str.GetLocalizedString() ); +} diff --git a/neo/idlib/Str.h b/neo/idlib/Str.h new file mode 100644 index 00000000..67448de6 --- /dev/null +++ b/neo/idlib/Str.h @@ -0,0 +1,1215 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STR_H__ +#define __STR_H__ + +/* +=============================================================================== + + Character string + +=============================================================================== +*/ + +#define ASSERT_ENUM_STRING( string, index ) ( 1 / (int)!( string - index ) ) ? #string : "" + +enum utf8Encoding_t { + UTF8_PURE_ASCII, // no characters with values > 127 + UTF8_ENCODED_BOM, // characters > 128 encoded with UTF8, but no byte-order-marker at the beginning + UTF8_ENCODED_NO_BOM, // characters > 128 encoded with UTF8, with a byte-order-marker at the beginning + UTF8_INVALID, // has values > 127 but isn't valid UTF8 + UTF8_INVALID_BOM // has a byte-order-marker at the beginning, but isn't valuid UTF8 -- it's messed up +}; + +// these library functions should not be used for cross platform compatibility +#define strcmp idStr::Cmp // use_idStr_Cmp +#define strncmp use_idStr_Cmpn + +#if defined( StrCmpN ) +#undef StrCmpN +#endif +#define StrCmpN use_idStr_Cmpn + +#if defined( strcmpi ) +#undef strcmpi +#endif +#define strcmpi use_idStr_Icmp + +#if defined( StrCmpI ) +#undef StrCmpI +#endif +#define StrCmpI use_idStr_Icmp + +#if defined( StrCmpNI ) +#undef StrCmpNI +#endif +#define StrCmpNI use_idStr_Icmpn + +#define stricmp idStr::Icmp // use_idStr_Icmp +#define _stricmp use_idStr_Icmp +#define strcasecmp use_idStr_Icmp +#define strnicmp use_idStr_Icmpn +#define _strnicmp use_idStr_Icmpn +#define _memicmp use_idStr_Icmpn + +#define snprintf use_idStr_snPrintf +#define _snprintf use_idStr_snPrintf +#define vsnprintf use_idStr_vsnPrintf +#define _vsnprintf use_idStr_vsnPrintf + +class idVec4; + +#ifndef FILE_HASH_SIZE +#define FILE_HASH_SIZE 1024 +#endif + +// color escape character +const int C_COLOR_ESCAPE = '^'; +const int C_COLOR_DEFAULT = '0'; +const int C_COLOR_RED = '1'; +const int C_COLOR_GREEN = '2'; +const int C_COLOR_YELLOW = '3'; +const int C_COLOR_BLUE = '4'; +const int C_COLOR_CYAN = '5'; +const int C_COLOR_ORANGE = '6'; +const int C_COLOR_WHITE = '7'; +const int C_COLOR_GRAY = '8'; +const int C_COLOR_BLACK = '9'; + +// color escape string +#define S_COLOR_DEFAULT "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_ORANGE "^6" +#define S_COLOR_WHITE "^7" +#define S_COLOR_GRAY "^8" +#define S_COLOR_BLACK "^9" + +// make idStr a multiple of 16 bytes long +// don't make too large to keep memory requirements to a minimum +const int STR_ALLOC_BASE = 20; +const int STR_ALLOC_GRAN = 32; + +typedef enum { + MEASURE_SIZE = 0, + MEASURE_BANDWIDTH +} Measure_t; + +class idStr { + +public: + idStr(); + idStr( const idStr &text ); + idStr( const idStr &text, int start, int end ); + idStr( const char *text ); + idStr( const char *text, int start, int end ); + explicit idStr( const bool b ); + explicit idStr( const char c ); + explicit idStr( const int i ); + explicit idStr( const unsigned u ); + explicit idStr( const float f ); + ~idStr(); + + size_t Size() const; + const char * c_str() const; + operator const char *() const; + operator const char *(); + + char operator[]( int index ) const; + char & operator[]( int index ); + + void operator=( const idStr &text ); + void operator=( const char *text ); + + friend idStr operator+( const idStr &a, const idStr &b ); + friend idStr operator+( const idStr &a, const char *b ); + friend idStr operator+( const char *a, const idStr &b ); + + friend idStr operator+( const idStr &a, const float b ); + friend idStr operator+( const idStr &a, const int b ); + friend idStr operator+( const idStr &a, const unsigned b ); + friend idStr operator+( const idStr &a, const bool b ); + friend idStr operator+( const idStr &a, const char b ); + + idStr & operator+=( const idStr &a ); + idStr & operator+=( const char *a ); + idStr & operator+=( const float a ); + idStr & operator+=( const char a ); + idStr & operator+=( const int a ); + idStr & operator+=( const unsigned a ); + idStr & operator+=( const bool a ); + + // case sensitive compare + friend bool operator==( const idStr &a, const idStr &b ); + friend bool operator==( const idStr &a, const char *b ); + friend bool operator==( const char *a, const idStr &b ); + + // case sensitive compare + friend bool operator!=( const idStr &a, const idStr &b ); + friend bool operator!=( const idStr &a, const char *b ); + friend bool operator!=( const char *a, const idStr &b ); + + // case sensitive compare + int Cmp( const char *text ) const; + int Cmpn( const char *text, int n ) const; + int CmpPrefix( const char *text ) const; + + // case insensitive compare + int Icmp( const char *text ) const; + int Icmpn( const char *text, int n ) const; + int IcmpPrefix( const char *text ) const; + + // case insensitive compare ignoring color + int IcmpNoColor( const char *text ) const; + + // compares paths and makes sure folders come first + int IcmpPath( const char *text ) const; + int IcmpnPath( const char *text, int n ) const; + int IcmpPrefixPath( const char *text ) const; + + int Length() const; + int Allocated() const; + void Empty(); + bool IsEmpty() const; + void Clear(); + void Append( const char a ); + void Append( const idStr &text ); + void Append( const char *text ); + void Append( const char *text, int len ); + void Insert( const char a, int index ); + void Insert( const char *text, int index ); + void ToLower(); + void ToUpper(); + bool IsNumeric() const; + bool IsColor() const; + bool HasLower() const; + bool HasUpper() const; + int LengthWithoutColors() const; + idStr & RemoveColors(); + void CapLength( int ); + void Fill( const char ch, int newlen ); + + ID_INLINE int UTF8Length(); + ID_INLINE uint32 UTF8Char( int & idx ); + static int UTF8Length( const byte * s ); + static ID_INLINE uint32 UTF8Char( const char * s, int & idx ); + static uint32 UTF8Char( const byte * s, int & idx ); + void AppendUTF8Char( uint32 c ); + ID_INLINE void ConvertToUTF8(); + static bool IsValidUTF8( const uint8 * s, const int maxLen, utf8Encoding_t & encoding ); + static ID_INLINE bool IsValidUTF8( const char * s, const int maxLen, utf8Encoding_t & encoding ) { return IsValidUTF8( ( const uint8* )s, maxLen, encoding ); } + static ID_INLINE bool IsValidUTF8( const uint8 * s, const int maxLen ); + static ID_INLINE bool IsValidUTF8( const char * s, const int maxLen ) { return IsValidUTF8( ( const uint8* )s, maxLen ); } + + int Find( const char c, int start = 0, int end = -1 ) const; + int Find( const char *text, bool casesensitive = true, int start = 0, int end = -1 ) const; + bool Filter( const char *filter, bool casesensitive ) const; + int Last( const char c ) const; // return the index to the last occurance of 'c', returns -1 if not found + const char * Left( int len, idStr &result ) const; // store the leftmost 'len' characters in the result + const char * Right( int len, idStr &result ) const; // store the rightmost 'len' characters in the result + const char * Mid( int start, int len, idStr &result ) const; // store 'len' characters starting at 'start' in result + idStr Left( int len ) const; // return the leftmost 'len' characters + idStr Right( int len ) const; // return the rightmost 'len' characters + idStr Mid( int start, int len ) const; // return 'len' characters starting at 'start' + void Format( VERIFY_FORMAT_STRING const char *fmt, ... ); // perform a threadsafe sprintf to the string + static idStr FormatInt( const int num, bool isCash = false ); // formats an integer as a value with commas + static idStr FormatCash( const int num ) { return FormatInt( num, true ); } + void StripLeading( const char c ); // strip char from front as many times as the char occurs + void StripLeading( const char *string ); // strip string from front as many times as the string occurs + bool StripLeadingOnce( const char *string ); // strip string from front just once if it occurs + void StripTrailing( const char c ); // strip char from end as many times as the char occurs + void StripTrailing( const char *string ); // strip string from end as many times as the string occurs + bool StripTrailingOnce( const char *string ); // strip string from end just once if it occurs + void Strip( const char c ); // strip char from front and end as many times as the char occurs + void Strip( const char *string ); // strip string from front and end as many times as the string occurs + void StripTrailingWhitespace(); // strip trailing white space characters + idStr & StripQuotes(); // strip quotes around string + bool Replace( const char *old, const char *nw ); + bool ReplaceChar( const char old, const char nw ); + ID_INLINE void CopyRange( const char * text, int start, int end ); + + // file name methods + int FileNameHash() const; // hash key for the filename (skips extension) + idStr & BackSlashesToSlashes(); // convert slashes + idStr & SlashesToBackSlashes(); // convert slashes + idStr & SetFileExtension( const char *extension ); // set the given file extension + idStr & StripFileExtension(); // remove any file extension + idStr & StripAbsoluteFileExtension(); // remove any file extension looking from front (useful if there are multiple .'s) + idStr & DefaultFileExtension( const char *extension ); // if there's no file extension use the default + idStr & DefaultPath( const char *basepath ); // if there's no path use the default + void AppendPath( const char *text ); // append a partial path + idStr & StripFilename(); // remove the filename from a path + idStr & StripPath(); // remove the path from the filename + void ExtractFilePath( idStr &dest ) const; // copy the file path to another string + void ExtractFileName( idStr &dest ) const; // copy the filename to another string + void ExtractFileBase( idStr &dest ) const; // copy the filename minus the extension to another string + void ExtractFileExtension( idStr &dest ) const; // copy the file extension to another string + bool CheckExtension( const char *ext ); + + // char * methods to replace library functions + static int Length( const char *s ); + static char * ToLower( char *s ); + static char * ToUpper( char *s ); + static bool IsNumeric( const char *s ); + static bool IsColor( const char *s ); + static bool HasLower( const char *s ); + static bool HasUpper( const char *s ); + static int LengthWithoutColors( const char *s ); + static char * RemoveColors( char *s ); + static int Cmp( const char *s1, const char *s2 ); + static int Cmpn( const char *s1, const char *s2, int n ); + static int Icmp( const char *s1, const char *s2 ); + static int Icmpn( const char *s1, const char *s2, int n ); + static int IcmpNoColor( const char *s1, const char *s2 ); + static int IcmpPath( const char *s1, const char *s2 ); // compares paths and makes sure folders come first + static int IcmpnPath( const char *s1, const char *s2, int n ); // compares paths and makes sure folders come first + static void Append( char *dest, int size, const char *src ); + static void Copynz( char *dest, const char *src, int destsize ); + static int snPrintf( char *dest, int size, VERIFY_FORMAT_STRING const char *fmt, ... ); + static int vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ); + static int FindChar( const char *str, const char c, int start = 0, int end = -1 ); + static int FindText( const char *str, const char *text, bool casesensitive = true, int start = 0, int end = -1 ); + static bool Filter( const char *filter, const char *name, bool casesensitive ); + static void StripMediaName( const char *name, idStr &mediaName ); + static bool CheckExtension( const char *name, const char *ext ); + static const char * FloatArrayToString( const float *array, const int length, const int precision ); + static const char * CStyleQuote( const char *str ); + static const char * CStyleUnQuote( const char *str ); + + // hash keys + static int Hash( const char *string ); + static int Hash( const char *string, int length ); + static int IHash( const char *string ); // case insensitive + static int IHash( const char *string, int length ); // case insensitive + + // character methods + static char ToLower( char c ); + static char ToUpper( char c ); + static bool CharIsPrintable( int c ); + static bool CharIsLower( int c ); + static bool CharIsUpper( int c ); + static bool CharIsAlpha( int c ); + static bool CharIsNumeric( int c ); + static bool CharIsNewLine( char c ); + static bool CharIsTab( char c ); + static int ColorIndex( int c ); + static idVec4 & ColorForIndex( int i ); + + friend int sprintf( idStr &dest, const char *fmt, ... ); + friend int vsprintf( idStr &dest, const char *fmt, va_list ap ); + + void ReAllocate( int amount, bool keepold ); // reallocate string data buffer + void FreeData(); // free allocated string memory + + // format value in the given measurement with the best unit, returns the best unit + int BestUnit( const char *format, float value, Measure_t measure ); + // format value in the requested unit and measurement + void SetUnit( const char *format, float value, int unit, Measure_t measure ); + + static void InitMemory(); + static void ShutdownMemory(); + static void PurgeMemory(); + static void ShowMemoryUsage_f( const idCmdArgs &args ); + + int DynamicMemoryUsed() const; + static idStr FormatNumber( int number ); + +protected: + int len; + char * data; + int allocedAndFlag; // top bit is used to store a flag that indicates if the string data is static or not + char baseBuffer[ STR_ALLOC_BASE ]; + + void EnsureAlloced( int amount, bool keepold = true ); // ensure string data buffer is large anough + + // sets the data point to the specified buffer... note that this ignores makes the passed buffer empty and ignores + // anything currently in the idStr's dynamic buffer. This method is intended to be called only from a derived class's constructor. + ID_INLINE void SetStaticBuffer( char * buffer, const int bufferLength ); + +private: + // initialize string using base buffer... call ONLY FROM CONSTRUCTOR + ID_INLINE void Construct(); + + static const uint32 STATIC_BIT = 31; + static const uint32 STATIC_MASK = 1u << STATIC_BIT; + static const uint32 ALLOCED_MASK = STATIC_MASK - 1; + + + ID_INLINE int GetAlloced() const { return allocedAndFlag & ALLOCED_MASK; } + ID_INLINE void SetAlloced( const int a ) { allocedAndFlag = ( allocedAndFlag & STATIC_MASK ) | ( a & ALLOCED_MASK); } + + ID_INLINE bool IsStatic() const { return ( allocedAndFlag & STATIC_MASK ) != 0; } + ID_INLINE void SetStatic( const bool isStatic ) { allocedAndFlag = ( allocedAndFlag & ALLOCED_MASK ) | ( isStatic << STATIC_BIT ); } + +public: + static const int INVALID_POSITION = -1; +}; + +char * va( VERIFY_FORMAT_STRING const char *fmt, ... ); + +/* +================================================================================================ + + Sort routines for sorting idList + +================================================================================================ +*/ + +class idSort_Str : public idSort_Quick< idStr, idSort_Str > { +public: + int Compare( const idStr & a, const idStr & b ) const { return a.Icmp( b ); } +}; + +class idSort_PathStr : public idSort_Quick< idStr, idSort_PathStr > { +public: + int Compare( const idStr & a, const idStr & b ) const { return a.IcmpPath( b ); } +}; + +/* +======================== +idStr::Construct +======================== +*/ +ID_INLINE void idStr::Construct() { + SetStatic( false ); + SetAlloced( STR_ALLOC_BASE ); + data = baseBuffer; + len = 0; + data[ 0 ] = '\0'; +#ifdef ID_DEBUG_UNINITIALIZED_MEMORY + memset( baseBuffer, 0, sizeof( baseBuffer ) ); +#endif +} + + +ID_INLINE void idStr::EnsureAlloced( int amount, bool keepold ) { + // static string's can't reallocate + if ( IsStatic() ) { + release_assert( amount <= GetAlloced() ); + return; + } + if ( amount > GetAlloced() ) { + ReAllocate( amount, keepold ); + } +} + +/* +======================== +idStr::SetStaticBuffer +======================== +*/ +ID_INLINE void idStr::SetStaticBuffer( char * buffer, const int bufferLength ) { + // this should only be called on a freshly constructed idStr + assert( data == baseBuffer ); + data = buffer; + len = 0; + SetAlloced( bufferLength ); + SetStatic( true ); +} + +ID_INLINE idStr::idStr() { + Construct(); +} + +ID_INLINE idStr::idStr( const idStr &text ) { + Construct(); + int l; + + l = text.Length(); + EnsureAlloced( l + 1 ); + strcpy( data, text.data ); + len = l; +} + +ID_INLINE idStr::idStr( const idStr &text, int start, int end ) { + Construct(); + int i; + int l; + + if ( end > text.Length() ) { + end = text.Length(); + } + if ( start > text.Length() ) { + start = text.Length(); + } else if ( start < 0 ) { + start = 0; + } + + l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +ID_INLINE idStr::idStr( const char *text ) { + Construct(); + int l; + + if ( text ) { + l = strlen( text ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; + } +} + +ID_INLINE idStr::idStr( const char *text, int start, int end ) { + Construct(); + int i; + int l = strlen( text ); + + if ( end > l ) { + end = l; + } + if ( start > l ) { + start = l; + } else if ( start < 0 ) { + start = 0; + } + + l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +ID_INLINE idStr::idStr( const bool b ) { + Construct(); + EnsureAlloced( 2 ); + data[ 0 ] = b ? '1' : '0'; + data[ 1 ] = '\0'; + len = 1; +} + +ID_INLINE idStr::idStr( const char c ) { + Construct(); + EnsureAlloced( 2 ); + data[ 0 ] = c; + data[ 1 ] = '\0'; + len = 1; +} + +ID_INLINE idStr::idStr( const int i ) { + Construct(); + char text[ 64 ]; + int l; + + l = sprintf( text, "%d", i ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::idStr( const unsigned u ) { + Construct(); + char text[ 64 ]; + int l; + + l = sprintf( text, "%u", u ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::idStr( const float f ) { + Construct(); + char text[ 64 ]; + int l; + + l = idStr::snPrintf( text, sizeof( text ), "%f", f ); + while( l > 0 && text[l-1] == '0' ) text[--l] = '\0'; + while( l > 0 && text[l-1] == '.' ) text[--l] = '\0'; + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::~idStr() { + FreeData(); +} + +ID_INLINE size_t idStr::Size() const { + return sizeof( *this ) + Allocated(); +} + +ID_INLINE const char *idStr::c_str() const { + return data; +} + +ID_INLINE idStr::operator const char *() { + return c_str(); +} + +ID_INLINE idStr::operator const char *() const { + return c_str(); +} + +ID_INLINE char idStr::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index <= len ) ); + return data[ index ]; +} + +ID_INLINE char &idStr::operator[]( int index ) { + assert( ( index >= 0 ) && ( index <= len ) ); + return data[ index ]; +} + +ID_INLINE void idStr::operator=( const idStr &text ) { + int l; + + l = text.Length(); + EnsureAlloced( l + 1, false ); + memcpy( data, text.data, l ); + data[l] = '\0'; + len = l; +} + +ID_INLINE idStr operator+( const idStr &a, const idStr &b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const char *b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const char *a, const idStr &b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const bool b ) { + idStr result( a ); + result.Append( b ? "true" : "false" ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const char b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const float b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%f", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const int b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%d", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const unsigned b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%u", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr &idStr::operator+=( const float a ) { + char text[ 64 ]; + + sprintf( text, "%f", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const int a ) { + char text[ 64 ]; + + sprintf( text, "%d", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const unsigned a ) { + char text[ 64 ]; + + sprintf( text, "%u", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const idStr &a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const char *a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const char a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const bool a ) { + Append( a ? "true" : "false" ); + return *this; +} + +ID_INLINE bool operator==( const idStr &a, const idStr &b ) { + return ( !idStr::Cmp( a.data, b.data ) ); +} + +ID_INLINE bool operator==( const idStr &a, const char *b ) { + assert( b ); + return ( !idStr::Cmp( a.data, b ) ); +} + +ID_INLINE bool operator==( const char *a, const idStr &b ) { + assert( a ); + return ( !idStr::Cmp( a, b.data ) ); +} + +ID_INLINE bool operator!=( const idStr &a, const idStr &b ) { + return !( a == b ); +} + +ID_INLINE bool operator!=( const idStr &a, const char *b ) { + return !( a == b ); +} + +ID_INLINE bool operator!=( const char *a, const idStr &b ) { + return !( a == b ); +} + +ID_INLINE int idStr::Cmp( const char *text ) const { + assert( text ); + return idStr::Cmp( data, text ); +} + +ID_INLINE int idStr::Cmpn( const char *text, int n ) const { + assert( text ); + return idStr::Cmpn( data, text, n ); +} + +ID_INLINE int idStr::CmpPrefix( const char *text ) const { + assert( text ); + return idStr::Cmpn( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::Icmp( const char *text ) const { + assert( text ); + return idStr::Icmp( data, text ); +} + +ID_INLINE int idStr::Icmpn( const char *text, int n ) const { + assert( text ); + return idStr::Icmpn( data, text, n ); +} + +ID_INLINE int idStr::IcmpPrefix( const char *text ) const { + assert( text ); + return idStr::Icmpn( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::IcmpNoColor( const char *text ) const { + assert( text ); + return idStr::IcmpNoColor( data, text ); +} + +ID_INLINE int idStr::IcmpPath( const char *text ) const { + assert( text ); + return idStr::IcmpPath( data, text ); +} + +ID_INLINE int idStr::IcmpnPath( const char *text, int n ) const { + assert( text ); + return idStr::IcmpnPath( data, text, n ); +} + +ID_INLINE int idStr::IcmpPrefixPath( const char *text ) const { + assert( text ); + return idStr::IcmpnPath( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::Length() const { + return len; +} + +ID_INLINE int idStr::Allocated() const { + if ( data != baseBuffer ) { + return GetAlloced(); + } else { + return 0; + } +} + +ID_INLINE void idStr::Empty() { + EnsureAlloced( 1 ); + data[ 0 ] = '\0'; + len = 0; +} + +ID_INLINE bool idStr::IsEmpty() const { + return ( idStr::Cmp( data, "" ) == 0 ); +} + +ID_INLINE void idStr::Clear() { + if ( IsStatic() ) { + len = 0; + data[ 0 ] = '\0'; + return; + } + FreeData(); + Construct(); +} + +ID_INLINE void idStr::Append( const char a ) { + EnsureAlloced( len + 2 ); + data[ len ] = a; + len++; + data[ len ] = '\0'; +} + +ID_INLINE void idStr::Append( const idStr &text ) { + int newLen; + int i; + + newLen = len + text.Length(); + EnsureAlloced( newLen + 1 ); + for ( i = 0; i < text.len; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; +} + +ID_INLINE void idStr::Append( const char *text ) { + int newLen; + int i; + + if ( text ) { + newLen = len + strlen( text ); + EnsureAlloced( newLen + 1 ); + for ( i = 0; text[ i ]; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; + } +} + +ID_INLINE void idStr::Append( const char *text, int l ) { + int newLen; + int i; + + if ( text && l ) { + newLen = len + l; + EnsureAlloced( newLen + 1 ); + for ( i = 0; text[ i ] && i < l; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; + } +} + +ID_INLINE void idStr::Insert( const char a, int index ) { + int i, l; + + if ( index < 0 ) { + index = 0; + } else if ( index > len ) { + index = len; + } + + l = 1; + EnsureAlloced( len + l + 1 ); + for ( i = len; i >= index; i-- ) { + data[i+l] = data[i]; + } + data[index] = a; + len++; +} + +ID_INLINE void idStr::Insert( const char *text, int index ) { + int i, l; + + if ( index < 0 ) { + index = 0; + } else if ( index > len ) { + index = len; + } + + l = strlen( text ); + EnsureAlloced( len + l + 1 ); + for ( i = len; i >= index; i-- ) { + data[i+l] = data[i]; + } + for ( i = 0; i < l; i++ ) { + data[index+i] = text[i]; + } + len += l; +} + +ID_INLINE void idStr::ToLower() { + for (int i = 0; data[i]; i++ ) { + if ( CharIsUpper( data[i] ) ) { + data[i] += ( 'a' - 'A' ); + } + } +} + +ID_INLINE void idStr::ToUpper() { + for (int i = 0; data[i]; i++ ) { + if ( CharIsLower( data[i] ) ) { + data[i] -= ( 'a' - 'A' ); + } + } +} + +ID_INLINE bool idStr::IsNumeric() const { + return idStr::IsNumeric( data ); +} + +ID_INLINE bool idStr::IsColor() const { + return idStr::IsColor( data ); +} + +ID_INLINE bool idStr::HasLower() const { + return idStr::HasLower( data ); +} + +ID_INLINE bool idStr::HasUpper() const { + return idStr::HasUpper( data ); +} + +ID_INLINE idStr &idStr::RemoveColors() { + idStr::RemoveColors( data ); + len = Length( data ); + return *this; +} + +ID_INLINE int idStr::LengthWithoutColors() const { + return idStr::LengthWithoutColors( data ); +} + +ID_INLINE void idStr::CapLength( int newlen ) { + if ( len <= newlen ) { + return; + } + data[ newlen ] = 0; + len = newlen; +} + +ID_INLINE void idStr::Fill( const char ch, int newlen ) { + EnsureAlloced( newlen + 1 ); + len = newlen; + memset( data, ch, len ); + data[ len ] = 0; +} + +/* +======================== +idStr::UTF8Length +======================== +*/ +ID_INLINE int idStr::UTF8Length() { + return UTF8Length( (byte *)data ); +} + +/* +======================== +idStr::UTF8Char +======================== +*/ +ID_INLINE uint32 idStr::UTF8Char( int & idx ) { + return UTF8Char( (byte *)data, idx ); +} + +/* +======================== +idStr::ConvertToUTF8 +======================== +*/ +ID_INLINE void idStr::ConvertToUTF8() { + idStr temp( *this ); + Clear(); + for( int index = 0; index < temp.Length(); ++index ) { + AppendUTF8Char( temp[index] ); + } +} + +/* +======================== +idStr::UTF8Char +======================== +*/ +ID_INLINE uint32 idStr::UTF8Char( const char * s, int & idx ) { + return UTF8Char( (byte *)s, idx ); +} + +/* +======================== +idStr::IsValidUTF8 +======================== +*/ +ID_INLINE bool idStr::IsValidUTF8( const uint8 * s, const int maxLen ) { + utf8Encoding_t encoding; + return IsValidUTF8( s, maxLen, encoding ); +} + +ID_INLINE int idStr::Find( const char c, int start, int end ) const { + if ( end == -1 ) { + end = len; + } + return idStr::FindChar( data, c, start, end ); +} + +ID_INLINE int idStr::Find( const char *text, bool casesensitive, int start, int end ) const { + if ( end == -1 ) { + end = len; + } + return idStr::FindText( data, text, casesensitive, start, end ); +} + +ID_INLINE bool idStr::Filter( const char *filter, bool casesensitive ) const { + return idStr::Filter( filter, data, casesensitive ); +} + +ID_INLINE const char *idStr::Left( int len, idStr &result ) const { + return Mid( 0, len, result ); +} + +ID_INLINE const char *idStr::Right( int len, idStr &result ) const { + if ( len >= Length() ) { + result = *this; + return result; + } + return Mid( Length() - len, len, result ); +} + +ID_INLINE idStr idStr::Left( int len ) const { + return Mid( 0, len ); +} + +ID_INLINE idStr idStr::Right( int len ) const { + if ( len >= Length() ) { + return *this; + } + return Mid( Length() - len, len ); +} + +ID_INLINE void idStr::Strip( const char c ) { + StripLeading( c ); + StripTrailing( c ); +} + +ID_INLINE void idStr::Strip( const char *string ) { + StripLeading( string ); + StripTrailing( string ); +} + +ID_INLINE bool idStr::CheckExtension( const char *ext ) { + return idStr::CheckExtension( data, ext ); +} + +ID_INLINE int idStr::Length( const char *s ) { + int i; + for ( i = 0; s[i]; i++ ) {} + return i; +} + +ID_INLINE char *idStr::ToLower( char *s ) { + for ( int i = 0; s[i]; i++ ) { + if ( CharIsUpper( s[i] ) ) { + s[i] += ( 'a' - 'A' ); + } + } + return s; +} + +ID_INLINE char *idStr::ToUpper( char *s ) { + for ( int i = 0; s[i]; i++ ) { + if ( CharIsLower( s[i] ) ) { + s[i] -= ( 'a' - 'A' ); + } + } + return s; +} + +ID_INLINE int idStr::Hash( const char *string ) { + int i, hash = 0; + for ( i = 0; *string != '\0'; i++ ) { + hash += ( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::Hash( const char *string, int length ) { + int i, hash = 0; + for ( i = 0; i < length; i++ ) { + hash += ( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::IHash( const char *string ) { + int i, hash = 0; + for( i = 0; *string != '\0'; i++ ) { + hash += ToLower( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::IHash( const char *string, int length ) { + int i, hash = 0; + for ( i = 0; i < length; i++ ) { + hash += ToLower( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE bool idStr::IsColor( const char *s ) { + return ( s[0] == C_COLOR_ESCAPE && s[1] != '\0' && s[1] != ' ' ); +} + +ID_INLINE char idStr::ToLower( char c ) { + if ( c <= 'Z' && c >= 'A' ) { + return ( c + ( 'a' - 'A' ) ); + } + return c; +} + +ID_INLINE char idStr::ToUpper( char c ) { + if ( c >= 'a' && c <= 'z' ) { + return ( c - ( 'a' - 'A' ) ); + } + return c; +} + +ID_INLINE bool idStr::CharIsPrintable( int c ) { + // test for regular ascii and western European high-ascii chars + return ( c >= 0x20 && c <= 0x7E ) || ( c >= 0xA1 && c <= 0xFF ); +} + +ID_INLINE bool idStr::CharIsLower( int c ) { + // test for regular ascii and western European high-ascii chars + return ( c >= 'a' && c <= 'z' ) || ( c >= 0xE0 && c <= 0xFF ); +} + +ID_INLINE bool idStr::CharIsUpper( int c ) { + // test for regular ascii and western European high-ascii chars + return ( c <= 'Z' && c >= 'A' ) || ( c >= 0xC0 && c <= 0xDF ); +} + +ID_INLINE bool idStr::CharIsAlpha( int c ) { + // test for regular ascii and western European high-ascii chars + return ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || + ( c >= 0xC0 && c <= 0xFF ) ); +} + +ID_INLINE bool idStr::CharIsNumeric( int c ) { + return ( c <= '9' && c >= '0' ); +} + +ID_INLINE bool idStr::CharIsNewLine( char c ) { + return ( c == '\n' || c == '\r' || c == '\v' ); +} + +ID_INLINE bool idStr::CharIsTab( char c ) { + return ( c == '\t' ); +} + +ID_INLINE int idStr::ColorIndex( int c ) { + return ( c & 15 ); +} + +ID_INLINE int idStr::DynamicMemoryUsed() const { + return ( data == baseBuffer ) ? 0 : GetAlloced(); +} + +/* +======================== +idStr::CopyRange +======================== +*/ +ID_INLINE void idStr::CopyRange( const char * text, int start, int end ) { + int l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( int i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +#endif /* !__STR_H__ */ diff --git a/neo/idlib/StrStatic.h b/neo/idlib/StrStatic.h new file mode 100644 index 00000000..15eb1129 --- /dev/null +++ b/neo/idlib/StrStatic.h @@ -0,0 +1,125 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __STRSTATIC_H__ +#define __STRSTATIC_H__ + +/* +================================================ +idStrStatic +================================================ +*/ +template< int _size_ > +class idStrStatic : public idStr { +public: + ID_INLINE void operator=( const idStrStatic &text ) { + // we should only get here when the types, including the size, are identical + len = text.Length(); + memcpy( data, text.data, len+1 ); + } + + // all idStr operators are overloaded and the idStr default constructor is called so that the + // static buffer can be initialized in the body of the constructor before the data is ever + // copied. + ID_INLINE idStrStatic() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + } + ID_INLINE idStrStatic( const idStrStatic & text ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( text ); + } + + ID_INLINE idStrStatic( const idStr & text ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( text ); + } + + ID_INLINE idStrStatic( const idStrStatic & text, int start, int end ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + CopyRange( text.c_str(), start, end ); + } + + ID_INLINE idStrStatic( const char * text ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( text ); + } + + ID_INLINE idStrStatic( const char * text, int start, int end ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + CopyRange( text, start, end ); + } + + ID_INLINE explicit idStrStatic( const bool b ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( b ); + } + + ID_INLINE explicit idStrStatic( const char c ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( c ); + } + + ID_INLINE explicit idStrStatic( const int i ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( i ); + } + + ID_INLINE explicit idStrStatic( const unsigned u ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( u ); + } + + ID_INLINE explicit idStrStatic( const float f ) : + idStr() { + buffer[ 0 ] = '\0'; + SetStaticBuffer( buffer, _size_ ); + idStr::operator=( f ); + } + +private: + char buffer[ _size_ ]; +}; +#endif // __STRSTATIC_H__ diff --git a/neo/idlib/Swap.h b/neo/idlib/Swap.h new file mode 100644 index 00000000..c0d57f19 --- /dev/null +++ b/neo/idlib/Swap.h @@ -0,0 +1,223 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWAP_H__ +#define __SWAP_H__ + +/* +================================================================================================ +Contains the Swap class, for CrossPlatform endian conversion. + +works +================================================================================================ +*/ + +/* +======================== +IsPointer +======================== +*/ +template< typename type > +bool IsPointer( type ) { + return false; +} + +/* +======================== +IsPointer +======================== +*/ +template< typename type > +bool IsPointer( type * ) { + return true; +} + +/* +================================================ +The *Swap* static template class, idSwap, is used by the SwapClass template class for +performing EndianSwapping. +================================================ +*/ +class idSwap { +public: + //#define SwapBytes( x, y ) (x) ^= (y) ^= (x) ^= (y) + #define SwapBytes( x, y ) { byte t = (x); (x) = (y); (y) = t; } + + template static void Little( type &c ) { + // byte swapping pointers is pointless because we should never store pointers on disk + assert( !IsPointer( c ) ); + + } + + template static void Big( type &c ) { + // byte swapping pointers is pointless because we should never store pointers on disk + assert( !IsPointer( c ) ); + + if ( sizeof( type ) == 1 ) { + } else if ( sizeof( type ) == 2 ) { + byte *b = (byte *)&c; + SwapBytes( b[0], b[1] ); + } else if ( sizeof( type ) == 4 ) { + byte *b = (byte *)&c; + SwapBytes( b[0], b[3] ); + SwapBytes( b[1], b[2] ); + } else if ( sizeof( type ) == 8 ) { + byte * b = (byte *)&c; + SwapBytes( b[0], b[7] ); + SwapBytes( b[1], b[6]); + SwapBytes( b[2], b[5] ); + SwapBytes( b[3], b[4] ); + } else { + assert( false ); + } + } + + template static void LittleArray( type *c, int count ) { + } + + template static void BigArray( type *c, int count ) { + for ( int i = 0; i < count; i++ ) { + Big( c[i] ); + } + } + + static void SixtetsForInt( byte *out, int src ) { + byte *b = (byte *)&src; + out[0] = ( b[0] & 0xfc ) >> 2; + out[1] = ( ( b[0] & 0x3 ) << 4 ) + ( ( b[1] & 0xf0 ) >> 4 ); + out[2] = ( ( b[1] & 0xf ) << 2 ) + ( ( b[2] & 0xc0 ) >> 6 ); + out[3] = b[2] & 0x3f; + } + + static int IntForSixtets( byte *in ) { + int ret = 0; + byte *b = (byte *)&ret; + b[0] |= in[0] << 2; + b[0] |= ( in[1] & 0x30 ) >> 4; + b[1] |= ( in[1] & 0xf ) << 4; + b[1] |= ( in[2] & 0x3c ) >> 2; + b[2] |= ( in[2] & 0x3 ) << 6; + b[2] |= in[3]; + return ret; + } + +public: // specializations +#ifndef ID_SWAP_LITE // avoid dependency avalanche for SPU code +#define SWAP_VECTOR( x ) \ + static void Little( x &c ) { LittleArray( c.ToFloatPtr(), c.GetDimension() ); } \ + static void Big( x &c ) { BigArray( c.ToFloatPtr(), c.GetDimension() ); } + + SWAP_VECTOR( idVec2 ); + SWAP_VECTOR( idVec3 ); + SWAP_VECTOR( idVec4 ); + SWAP_VECTOR( idVec5 ); + SWAP_VECTOR( idVec6 ); + SWAP_VECTOR( idMat2 ); + SWAP_VECTOR( idMat3 ); + SWAP_VECTOR( idMat4 ); + SWAP_VECTOR( idMat5 ); + SWAP_VECTOR( idMat6 ); + SWAP_VECTOR( idPlane ); + SWAP_VECTOR( idQuat ); + SWAP_VECTOR( idCQuat ); + SWAP_VECTOR( idAngles ); + SWAP_VECTOR( idBounds ); + + static void Little( idDrawVert &v ) { + Little( v.xyz ); + LittleArray( v.st, 2 ); + LittleArray( v.normal, 4 ); + LittleArray( v.tangent, 4 ); + LittleArray( v.color, 4 ); + } + static void Big( idDrawVert &v ) { + Big( v.xyz ); + BigArray( v.st, 2 ); + BigArray( v.normal, 4 ); + BigArray( v.tangent, 4 ); + BigArray( v.color, 4 ); + } +#endif +}; + +/* +================================================ +idSwapClass is a template class for performing EndianSwapping. +================================================ +*/ +template +class idSwapClass { +public: + idSwapClass() { + #ifdef _DEBUG + size = 0; + #endif + } + ~idSwapClass() { + #ifdef _DEBUG + assert( size == sizeof( classType ) ); + #endif + } + + template void Little( type &c ) { + idSwap::Little( c ); + #ifdef _DEBUG + size += sizeof( type ); + #endif + } + + template void Big( type &c ) { + idSwap::Big( c ); + #ifdef _DEBUG + size += sizeof( type ); + #endif + } + + template void LittleArray( type *c, int count ) { + idSwap::LittleArray( c, count ); + #ifdef _DEBUG + size += count * sizeof( type ); + #endif + } + + template void BigArray( type *c, int count ) { + idSwap::BigArray( c, count ); + #ifdef _DEBUG + size += count * sizeof( type ); + #endif + } + +#ifdef _DEBUG +private: + int size; +#endif +}; + + #define BIG32(v) ((((uint32)(v)) >> 24) | (((uint32)(v) & 0x00FF0000) >> 8) | (((uint32)(v) & 0x0000FF00) << 8) | ((uint32)(v) << 24)) + #define BIG16(v) ((((uint16)(v)) >> 8) | ((uint16)(v) << 8)) + +#endif // !__SWAP_H__ diff --git a/neo/idlib/Thread.cpp b/neo/idlib/Thread.cpp new file mode 100644 index 00000000..528bf456 --- /dev/null +++ b/neo/idlib/Thread.cpp @@ -0,0 +1,283 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "precompiled.h" + +/* +================================================================================================ +Contains the vartious ThreadingClass implementations. +================================================================================================ +*/ + +/* +================================================================================================ + + idSysThread + +================================================================================================ +*/ + +/* +======================== +idSysThread::idSysThread +======================== +*/ +idSysThread::idSysThread() : + threadHandle( 0 ), + isWorker( false ), + isRunning( false ), + isTerminating( false ), + moreWorkToDo( false ), + signalWorkerDone( true ) { +} + +/* +======================== +idSysThread::~idSysThread +======================== +*/ +idSysThread::~idSysThread() { + StopThread( true ); + if ( threadHandle ) { + Sys_DestroyThread( threadHandle ); + } +} + +/* +======================== +idSysThread::StartThread +======================== +*/ +bool idSysThread::StartThread( const char * name_, core_t core, xthreadPriority priority, int stackSize ) { + if ( isRunning ) { + return false; + } + + name = name_; + + isTerminating = false; + + if ( threadHandle ) { + Sys_DestroyThread( threadHandle ); + } + + threadHandle = Sys_CreateThread( (xthread_t)ThreadProc, this, priority, name, core, stackSize, false ); + + isRunning = true; + return true; +} + +/* +======================== +idSysThread::StartWorkerThread +======================== +*/ +bool idSysThread::StartWorkerThread( const char * name_, core_t core, xthreadPriority priority, int stackSize ) { + if ( isRunning ) { + return false; + } + + isWorker = true; + + bool result = StartThread( name_, core, priority, stackSize ); + + signalWorkerDone.Wait( idSysSignal::WAIT_INFINITE ); + + return result; +} + +/* +======================== +idSysThread::StopThread +======================== +*/ +void idSysThread::StopThread( bool wait ) { + if ( !isRunning ) { + return; + } + if ( isWorker ) { + signalMutex.Lock(); + moreWorkToDo = true; + signalWorkerDone.Clear(); + isTerminating = true; + signalMoreWorkToDo.Raise(); + signalMutex.Unlock(); + } else { + isTerminating = true; + } + if ( wait ) { + WaitForThread(); + } +} + +/* +======================== +idSysThread::WaitForThread +======================== +*/ +void idSysThread::WaitForThread() { + if ( isWorker ) { + signalWorkerDone.Wait( idSysSignal::WAIT_INFINITE ); + } else if ( isRunning ) { + Sys_DestroyThread( threadHandle ); + threadHandle = 0; + } +} + +/* +======================== +idSysThread::SignalWork +======================== +*/ +void idSysThread::SignalWork() { + if ( isWorker ) { + signalMutex.Lock(); + moreWorkToDo = true; + signalWorkerDone.Clear(); + signalMoreWorkToDo.Raise(); + signalMutex.Unlock(); + } +} + +/* +======================== +idSysThread::IsWorkDone +======================== +*/ +bool idSysThread::IsWorkDone() { + if ( isWorker ) { + // a timeout of 0 will return immediately with true if signaled + if ( signalWorkerDone.Wait( 0 ) ) { + return true; + } + } + return false; +} + +/* +======================== +idSysThread::ThreadProc +======================== +*/ +int idSysThread::ThreadProc( idSysThread * thread ) { + int retVal = 0; + + try { + if ( thread->isWorker ) { + for( ; ; ) { + thread->signalMutex.Lock(); + if ( thread->moreWorkToDo ) { + thread->moreWorkToDo = false; + thread->signalMoreWorkToDo.Clear(); + thread->signalMutex.Unlock(); + } else { + thread->signalWorkerDone.Raise(); + thread->signalMutex.Unlock(); + thread->signalMoreWorkToDo.Wait( idSysSignal::WAIT_INFINITE ); + continue; + } + + if ( thread->isTerminating ) { + break; + } + + retVal = thread->Run(); + } + thread->signalWorkerDone.Raise(); + } else { + retVal = thread->Run(); + } + } catch ( idException & ex ) { + idLib::Warning( "Fatal error in thread %s: %s", thread->GetName(), ex.GetError() ); + // We don't handle threads terminating unexpectedly very well, so just terminate the whole process + _exit( 0 ); + } + + thread->isRunning = false; + + return retVal; +} + +/* +======================== +idSysThread::Run +======================== +*/ +int idSysThread::Run() { + // The Run() is not pure virtual because on destruction of a derived class + // the virtual function pointer will be set to NULL before the idSysThread + // destructor actually stops the thread. + return 0; +} + +/* +================================================================================================ + + test + +================================================================================================ +*/ + +/* +================================================ +idMyThread test class. +================================================ +*/ +class idMyThread : public idSysThread { +public: + virtual int Run() { + // run threaded code here + return 0; + } + // specify thread data here +}; + +/* +======================== +TestThread +======================== +*/ +void TestThread() { + idMyThread thread; + thread.StartThread( "myThread", CORE_ANY ); +} + +/* +======================== +TestWorkers +======================== +*/ +void TestWorkers() { + idSysWorkerThreadGroup workers( "myWorkers", 4 ); + for ( ; ; ) { + for ( int i = 0; i < workers.GetNumThreads(); i++ ) { + // workers.GetThread( i )-> // setup work for this thread + } + workers.SignalWorkAndWait(); + } +} diff --git a/neo/idlib/Thread.h b/neo/idlib/Thread.h new file mode 100644 index 00000000..db34307e --- /dev/null +++ b/neo/idlib/Thread.h @@ -0,0 +1,465 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __THREAD_H__ +#define __THREAD_H__ + +/* +================================================ +idSysMutex provides a C++ wrapper to the low level system mutex functions. A mutex is an +object that can only be locked by one thread at a time. It's used to prevent two threads +from accessing the same piece of data simultaneously. +================================================ +*/ +class idSysMutex { +public: + idSysMutex() { Sys_MutexCreate( handle ); } + ~idSysMutex() { Sys_MutexDestroy( handle ); } + + bool Lock( bool blocking = true ) { return Sys_MutexLock( handle, blocking ); } + void Unlock() { Sys_MutexUnlock( handle ); } + +private: + mutexHandle_t handle; + + idSysMutex( const idSysMutex & s ) {} + void operator=( const idSysMutex & s ) {} +}; + +/* +================================================ +idScopedCriticalSection is a helper class that automagically locks a mutex when it's created +and unlocks it when it goes out of scope. +================================================ +*/ +class idScopedCriticalSection { +public: + idScopedCriticalSection( idSysMutex & m ) : mutex(&m) { mutex->Lock(); } + ~idScopedCriticalSection() { mutex->Unlock(); } + +private: + idSysMutex * mutex; // NOTE: making this a reference causes a TypeInfo crash +}; + +/* +================================================ +idSysSignal is a C++ wrapper for the low level system signal functions. A signal is an object +that a thread can wait on for it to be raised. It's used to indicate data is available or that +a thread has reached a specific point. +================================================ +*/ +class idSysSignal { +public: + static const int WAIT_INFINITE = -1; + + idSysSignal( bool manualReset = false ) { Sys_SignalCreate( handle, manualReset ); } + ~idSysSignal() { Sys_SignalDestroy( handle ); } + + void Raise() { Sys_SignalRaise( handle ); } + void Clear() { Sys_SignalClear( handle ); } + + // Wait returns true if the object is in a signalled state and + // returns false if the wait timed out. Wait also clears the signalled + // state when the signalled state is reached within the time out period. + bool Wait( int timeout = WAIT_INFINITE ) { return Sys_SignalWait( handle, timeout ); } + +private: + signalHandle_t handle; + + idSysSignal( const idSysSignal & s ) {} + void operator=( const idSysSignal & s ) {} +}; + +/* +================================================ +idSysInterlockedInteger is a C++ wrapper for the low level system interlocked integer +routines to atomically increment or decrement an integer. +================================================ +*/ +class idSysInterlockedInteger { +public: + idSysInterlockedInteger() : value( 0 ) {} + + // atomically increments the integer and returns the new value + int Increment() { return Sys_InterlockedIncrement( value ); } + + // atomically decrements the integer and returns the new value + int Decrement() { return Sys_InterlockedDecrement( value ); } + + // atomically adds a value to the integer and returns the new value + int Add( int v ) { return Sys_InterlockedAdd( value, (interlockedInt_t) v ); } + + // atomically subtracts a value from the integer and returns the new value + int Sub( int v ) { return Sys_InterlockedSub( value, (interlockedInt_t) v ); } + + // returns the current value of the integer + int GetValue() const { return value; } + + // sets a new value, Note: this operation is not atomic + void SetValue( int v ) { value = (interlockedInt_t)v; } + +private: + interlockedInt_t value; +}; + +/* +================================================ +idSysInterlockedPointer is a C++ wrapper around the low level system interlocked pointer +routine to atomically set a pointer while retrieving the previous value of the pointer. +================================================ +*/ +template< typename T > +class idSysInterlockedPointer { +public: + idSysInterlockedPointer() : ptr( NULL ) {} + + // atomically sets the pointer and returns the previous pointer value + T * Set( T * newPtr ) { + return (T *) Sys_InterlockedExchangePointer( (void * &) ptr, newPtr ); + } + + // atomically sets the pointer to 'newPtr' only if the previous pointer is equal to 'comparePtr' + // ptr = ( ptr == comparePtr ) ? newPtr : ptr + T * CompareExchange( T * comparePtr, T * newPtr ) { + return (T *) Sys_InterlockedCompareExchangePointer( (void * &) ptr, comparePtr, newPtr ); + } + + // returns the current value of the pointer + T * Get() const { return ptr; } + +private: + T * ptr; +}; + +/* +================================================ +idSysThread is an abstract base class, to be extended by classes implementing the +idSysThread::Run() method. + + class idMyThread : public idSysThread { + public: + virtual int Run() { + // run thread code here + return 0; + } + // specify thread data here + }; + + idMyThread thread; + thread.Start( "myThread" ); + +A worker thread is a thread that waits in place (without consuming CPU) +until work is available. A worker thread is implemented as normal, except that, instead of +calling the Start() method, the StartWorker() method is called to start the thread. +Note that the Sys_CreateThread function does not support the concept of worker threads. + + class idMyWorkerThread : public idSysThread { + public: + virtual int Run() { + // run thread code here + return 0; + } + // specify thread data here + }; + + idMyWorkerThread thread; + thread.StartThread( "myWorkerThread" ); + + // main thread loop + for ( ; ; ) { + // setup work for the thread here (by modifying class data on the thread) + thread.SignalWork(); // kick in the worker thread + // run other code in the main thread here (in parallel with the worker thread) + thread.WaitForThread(); // wait for the worker thread to finish + // use results from worker thread here + } + +In the above example, the thread does not continuously run in parallel with the main Thread, +but only for a certain period of time in a very controlled manner. Work is set up for the +Thread and then the thread is signalled to process that work while the main thread continues. +After doing other work, the main thread can wait for the worker thread to finish, if it has not +finished already. When the worker thread is done, the main thread can safely use the results +from the worker thread. + +Note that worker threads are useful on all platforms but they do not map to the SPUs on the PS3. +================================================ +*/ +class idSysThread { +public: + idSysThread(); + virtual ~idSysThread(); + + const char * GetName() const { return name.c_str(); } + uintptr_t GetThreadHandle() const { return threadHandle; } + bool IsRunning() const { return isRunning; } + bool IsTerminating() const { return isTerminating; } + + //------------------------ + // Thread Start/Stop/Wait + //------------------------ + + bool StartThread( const char * name, core_t core, + xthreadPriority priority = THREAD_NORMAL, + int stackSize = DEFAULT_THREAD_STACK_SIZE ); + + bool StartWorkerThread( const char * name, core_t core, + xthreadPriority priority = THREAD_NORMAL, + int stackSize = DEFAULT_THREAD_STACK_SIZE ); + + void StopThread( bool wait = true ); + + // This can be called from multiple other threads. However, in the case + // of a worker thread, the work being "done" has little meaning if other + // threads are continuously signalling more work. + void WaitForThread(); + + //------------------------ + // Worker Thread + //------------------------ + + // Signals the thread to notify work is available. + // This can be called from multiple other threads. + void SignalWork(); + + // Returns true if the work is done without waiting. + // This can be called from multiple other threads. However, the work + // being "done" has little meaning if other threads are continuously + // signalling more work. + bool IsWorkDone(); + +protected: + // The routine that performs the work. + virtual int Run(); + +private: + idStr name; + uintptr_t threadHandle; + bool isWorker; + bool isRunning; + volatile bool isTerminating; + volatile bool moreWorkToDo; + idSysSignal signalWorkerDone; + idSysSignal signalMoreWorkToDo; + idSysMutex signalMutex; + + static int ThreadProc( idSysThread * thread ); + + idSysThread( const idSysThread & s ) {} + void operator=( const idSysThread & s ) {} +}; + +/* +================================================ +idSysWorkerThreadGroup implements a group of worker threads that +typically crunch through a collection of similar tasks. + + class idMyWorkerThread : public idSysThread { + public: + virtual int Run() { + // run thread code here + return 0; + } + // specify thread data here + }; + + idSysWorkerThreadGroup workers( "myWorkers", 4 ); + for ( ; ; ) { + for ( int i = 0; i < workers.GetNumThreads(); i++ ) { + // workers.GetThread( i )-> // setup work for this thread + } + workers.SignalWorkAndWait(); + // use results from the worker threads here + } + +The concept of worker thread Groups is probably most useful for tools and compilers. +For instance, the AAS Compiler is using a worker thread group. Although worker threads +will work well on the PC, Mac and the 360, they do not directly map to the PS3, +in that the worker threads won't automatically run on the SPUs. +================================================ +*/ +template +class idSysWorkerThreadGroup { +public: + idSysWorkerThreadGroup( const char * name, int numThreads, + xthreadPriority priority = THREAD_NORMAL, + int stackSize = DEFAULT_THREAD_STACK_SIZE ); + + virtual ~idSysWorkerThreadGroup(); + + int GetNumThreads() const { return threadList.Num(); } + threadType & GetThread( int i ) { return *threadList[i]; } + + void SignalWorkAndWait(); + +private: + idList threadList; + bool runOneThreadInline; // use the signalling thread as one of the threads + bool singleThreaded; // set to true for debugging +}; + +/* +======================== +idSysWorkerThreadGroup::idSysWorkerThreadGroup +======================== +*/ +template +ID_INLINE idSysWorkerThreadGroup::idSysWorkerThreadGroup( const char * name, + int numThreads, xthreadPriority priority, int stackSize ) { + runOneThreadInline = ( numThreads < 0 ); + singleThreaded = false; + numThreads = abs( numThreads ); + for( int i = 0; i < numThreads; i++ ) { + threadType *thread = new (TAG_THREAD) threadType; + thread->StartWorkerThread( va( "%s_worker%i", name, i ), (core_t) i, priority, stackSize ); + threadList.Append( thread ); + } +} + +/* +======================== +idSysWorkerThreadGroup::~idSysWorkerThreadGroup +======================== +*/ +template +ID_INLINE idSysWorkerThreadGroup::~idSysWorkerThreadGroup() { + threadList.DeleteContents(); +} + +/* +======================== +idSysWorkerThreadGroup::SignalWorkAndWait +======================== +*/ +template +ID_INLINE void idSysWorkerThreadGroup::SignalWorkAndWait() { + if ( singleThreaded ) { + for( int i = 0; i < threadList.Num(); i++ ) { + threadList[ i ]->Run(); + } + return; + } + for( int i = 0; i < threadList.Num() - runOneThreadInline; i++ ) { + threadList[ i ]->SignalWork(); + } + if ( runOneThreadInline ) { + threadList[ threadList.Num() - 1 ]->Run(); + } + for ( int i = 0; i < threadList.Num() - runOneThreadInline; i++ ) { + threadList[ i ]->WaitForThread(); + } +} + +/* +================================================ +idSysThreadSynchronizer, allows a group of threads to +synchronize with each other half-way through execution. + + idSysThreadSynchronizer sync; + + class idMyWorkerThread : public idSysThread { + public: + virtual int Run() { + // perform first part of the work here + sync.Synchronize( threadNum ); // synchronize all threads + // perform second part of the work here + return 0; + } + // specify thread data here + unsigned int threadNum; + }; + + idSysWorkerThreadGroup workers( "myWorkers", 4 ); + for ( int i = 0; i < workers.GetNumThreads(); i++ ) { + workers.GetThread( i )->threadNum = i; + } + + for ( ; ; ) { + for ( int i = 0; i < workers.GetNumThreads(); i++ ) { + // workers.GetThread( i )-> // setup work for this thread + } + workers.SignalWorkAndWait(); + // use results from the worker threads here + } + +================================================ +*/ +class idSysThreadSynchronizer { +public: + static const int WAIT_INFINITE = -1; + + ID_INLINE void SetNumThreads( unsigned int num ); + ID_INLINE void Signal( unsigned int threadNum ); + ID_INLINE bool Synchronize( unsigned int threadNum, int timeout = WAIT_INFINITE ); + +private: + idList< idSysSignal *, TAG_THREAD > signals; + idSysInterlockedInteger busyCount; +}; + +/* +======================== +idSysThreadSynchronizer::SetNumThreads +======================== +*/ +ID_INLINE void idSysThreadSynchronizer::SetNumThreads( unsigned int num ) { + assert( busyCount.GetValue() == signals.Num() ); + if ( (int)num != signals.Num() ) { + signals.DeleteContents(); + signals.SetNum( (int)num ); + for ( unsigned int i = 0; i < num; i++ ) { + signals[i] = new (TAG_THREAD) idSysSignal(); + } + busyCount.SetValue( num ); + SYS_MEMORYBARRIER; + } +} + +/* +======================== +idSysThreadSynchronizer::Signal +======================== +*/ +ID_INLINE void idSysThreadSynchronizer::Signal( unsigned int threadNum ) { + if ( busyCount.Decrement() == 0 ) { + busyCount.SetValue( (unsigned int) signals.Num() ); + SYS_MEMORYBARRIER; + for ( int i = 0; i < signals.Num(); i++ ) { + signals[i]->Raise(); + } + } +} + +/* +======================== +idSysThreadSynchronizer::Synchronize +======================== +*/ +ID_INLINE bool idSysThreadSynchronizer::Synchronize( unsigned int threadNum, int timeout ) { + return signals[threadNum]->Wait( timeout ); +} + +#endif // !__THREAD_H__ diff --git a/neo/idlib/Timer.cpp b/neo/idlib/Timer.cpp new file mode 100644 index 00000000..770a4992 --- /dev/null +++ b/neo/idlib/Timer.cpp @@ -0,0 +1,160 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +double idTimer::base = -1.0; + + +/* +================= +idTimer::InitBaseClockTicks +================= +*/ +void idTimer::InitBaseClockTicks() const { + idTimer timer; + double ct, b; + int i; + + base = 0.0; + b = -1.0; + for ( i = 0; i < 1000; i++ ) { + timer.Clear(); + timer.Start(); + timer.Stop(); + ct = timer.ClockTicks(); + if ( b < 0.0 || ct < b ) { + b = ct; + } + } + base = b; +} + + +/* +================= +idTimerReport::idTimerReport +================= +*/ +idTimerReport::idTimerReport() { +} + +/* +================= +idTimerReport::SetReportName +================= +*/ +void idTimerReport::SetReportName( const char *name ) { + reportName = ( name ) ? name : "Timer Report"; +} + +/* +================= +idTimerReport::~idTimerReport +================= +*/ +idTimerReport::~idTimerReport() { + Clear(); +} + +/* +================= +idTimerReport::AddReport +================= +*/ +int idTimerReport::AddReport( const char *name ) { + if ( name && *name ) { + names.Append( name ); + return timers.Append( new (TAG_IDLIB) idTimer() ); + } + return -1; +} + +/* +================= +idTimerReport::Clear +================= +*/ +void idTimerReport::Clear() { + timers.DeleteContents( true ); + names.Clear(); + reportName.Clear(); +} + +/* +================= +idTimerReport::Reset +================= +*/ +void idTimerReport::Reset() { + assert ( timers.Num() == names.Num() ); + for ( int i = 0; i < timers.Num(); i++ ) { + timers[i]->Clear(); + } +} + +/* +================= +idTimerReport::AddTime +================= +*/ +void idTimerReport::AddTime( const char *name, idTimer *time ) { + assert ( timers.Num() == names.Num() ); + int i; + for ( i = 0; i < names.Num(); i++ ) { + if ( names[i].Icmp( name ) == 0 ) { + *timers[i] += *time; + break; + } + } + if ( i == names.Num() ) { + int index = AddReport( name ); + if ( index >= 0 ) { + timers[index]->Clear(); + *timers[index] += *time; + } + } +} + +/* +================= +idTimerReport::PrintReport +================= +*/ +void idTimerReport::PrintReport() { + assert( timers.Num() == names.Num() ); + idLib::common->Printf( "Timing Report for %s\n", reportName.c_str() ); + idLib::common->Printf( "-------------------------------\n" ); + float total = 0.0f; + for ( int i = 0; i < names.Num(); i++ ) { + idLib::common->Printf( "%s consumed %5.2f seconds\n", names[i].c_str(), timers[i]->Milliseconds() * 0.001f ); + total += timers[i]->Milliseconds(); + } + idLib::common->Printf( "Total time for report %s was %5.2f\n\n", reportName.c_str(), total * 0.001f ); +} diff --git a/neo/idlib/Timer.h b/neo/idlib/Timer.h new file mode 100644 index 00000000..0d34a604 --- /dev/null +++ b/neo/idlib/Timer.h @@ -0,0 +1,223 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TIMER_H__ +#define __TIMER_H__ + +/* +=============================================================================== + + Clock tick counter. Should only be used for profiling. + +=============================================================================== +*/ + +class idTimer { +public: + idTimer(); + idTimer( double clockTicks ); + ~idTimer(); + + idTimer operator+( const idTimer &t ) const; + idTimer operator-( const idTimer &t ) const; + idTimer & operator+=( const idTimer &t ); + idTimer & operator-=( const idTimer &t ); + + void Start(); + void Stop(); + void Clear(); + double ClockTicks() const; + double Milliseconds() const; + +private: + static double base; + enum { + TS_STARTED, + TS_STOPPED + } state; + double start; + double clockTicks; + + void InitBaseClockTicks() const; +}; + +/* +================= +idTimer::idTimer +================= +*/ +ID_INLINE idTimer::idTimer() { + state = TS_STOPPED; + clockTicks = 0.0; +} + +/* +================= +idTimer::idTimer +================= +*/ +ID_INLINE idTimer::idTimer( double _clockTicks ) { + state = TS_STOPPED; + clockTicks = _clockTicks; +} + +/* +================= +idTimer::~idTimer +================= +*/ +ID_INLINE idTimer::~idTimer() { +} + +/* +================= +idTimer::operator+ +================= +*/ +ID_INLINE idTimer idTimer::operator+( const idTimer &t ) const { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + return idTimer( clockTicks + t.clockTicks ); +} + +/* +================= +idTimer::operator- +================= +*/ +ID_INLINE idTimer idTimer::operator-( const idTimer &t ) const { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + return idTimer( clockTicks - t.clockTicks ); +} + +/* +================= +idTimer::operator+= +================= +*/ +ID_INLINE idTimer &idTimer::operator+=( const idTimer &t ) { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + clockTicks += t.clockTicks; + return *this; +} + +/* +================= +idTimer::operator-= +================= +*/ +ID_INLINE idTimer &idTimer::operator-=( const idTimer &t ) { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + clockTicks -= t.clockTicks; + return *this; +} + +/* +================= +idTimer::Start +================= +*/ +ID_INLINE void idTimer::Start() { + assert( state == TS_STOPPED ); + state = TS_STARTED; + start = idLib::sys->GetClockTicks(); +} + +/* +================= +idTimer::Stop +================= +*/ +ID_INLINE void idTimer::Stop() { + assert( state == TS_STARTED ); + clockTicks += idLib::sys->GetClockTicks() - start; + if ( base < 0.0 ) { + InitBaseClockTicks(); + } + if ( clockTicks > base ) { + clockTicks -= base; + } + state = TS_STOPPED; +} + +/* +================= +idTimer::Clear +================= +*/ +ID_INLINE void idTimer::Clear() { + clockTicks = 0.0; +} + +/* +================= +idTimer::ClockTicks +================= +*/ +ID_INLINE double idTimer::ClockTicks() const { + assert( state == TS_STOPPED ); + return clockTicks; +} + +/* +================= +idTimer::Milliseconds +================= +*/ +ID_INLINE double idTimer::Milliseconds() const { + assert( state == TS_STOPPED ); + return clockTicks / ( idLib::sys->ClockTicksPerSecond() * 0.001 ); +} + + +/* +=============================================================================== + + Report of multiple named timers. + +=============================================================================== +*/ + +class idTimerReport { +public: + idTimerReport(); + ~idTimerReport(); + + void SetReportName( const char *name ); + int AddReport( const char *name ); + void Clear(); + void Reset(); + void PrintReport(); + void AddTime( const char *name, idTimer *time ); + +private: + idListtimers; + idStrList names; + idStr reportName; +}; + +#endif /* !__TIMER_H__ */ diff --git a/neo/idlib/Token.cpp b/neo/idlib/Token.cpp new file mode 100644 index 00000000..5569ba33 --- /dev/null +++ b/neo/idlib/Token.cpp @@ -0,0 +1,179 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + + +/* +================ +idToken::NumberValue +================ +*/ +void idToken::NumberValue() { + int i, pow, div, c; + const char *p; + double m; + + assert( type == TT_NUMBER ); + p = c_str(); + floatvalue = 0; + intvalue = 0; + // floating point number + if ( subtype & TT_FLOAT ) { + if ( subtype & ( TT_INFINITE | TT_INDEFINITE | TT_NAN ) ) { + if ( subtype & TT_INFINITE ) { // 1.#INF + unsigned int inf = 0x7f800000; + floatvalue = (double) *(float*)&inf; + } + else if ( subtype & TT_INDEFINITE ) { // 1.#IND + unsigned int ind = 0xffc00000; + floatvalue = (double) *(float*)&ind; + } + else if ( subtype & TT_NAN ) { // 1.#QNAN + unsigned int nan = 0x7fc00000; + floatvalue = (double) *(float*)&nan; + } + } + else { + while( *p && *p != '.' && *p != 'e' ) { + floatvalue = floatvalue * 10.0 + (double) (*p - '0'); + p++; + } + if ( *p == '.' ) { + p++; + for( m = 0.1; *p && *p != 'e'; p++ ) { + floatvalue = floatvalue + (double) (*p - '0') * m; + m *= 0.1; + } + } + if ( *p == 'e' ) { + p++; + if ( *p == '-' ) { + div = true; + p++; + } + else if ( *p == '+' ) { + div = false; + p++; + } + else { + div = false; + } + pow = 0; + for ( pow = 0; *p; p++ ) { + pow = pow * 10 + (int) (*p - '0'); + } + for ( m = 1.0, i = 0; i < pow; i++ ) { + m *= 10.0; + } + if ( div ) { + floatvalue /= m; + } + else { + floatvalue *= m; + } + } + } + intvalue = idMath::Ftoi( floatvalue ); + } + else if ( subtype & TT_DECIMAL ) { + while( *p ) { + intvalue = intvalue * 10 + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_IPADDRESS ) { + c = 0; + while( *p && *p != ':' ) { + if ( *p == '.' ) { + while( c != 3 ) { + intvalue = intvalue * 10; + c++; + } + c = 0; + } + else { + intvalue = intvalue * 10 + (*p - '0'); + c++; + } + p++; + } + while( c != 3 ) { + intvalue = intvalue * 10; + c++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_OCTAL ) { + // step over the first zero + p += 1; + while( *p ) { + intvalue = (intvalue << 3) + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_HEX ) { + // step over the leading 0x or 0X + p += 2; + while( *p ) { + intvalue <<= 4; + if (*p >= 'a' && *p <= 'f') + intvalue += *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + intvalue += *p - 'A' + 10; + else + intvalue += *p - '0'; + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_BINARY ) { + // step over the leading 0b or 0B + p += 2; + while( *p ) { + intvalue = (intvalue << 1) + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + subtype |= TT_VALUESVALID; +} + +/* +================ +idToken::ClearTokenWhiteSpace +================ +*/ +void idToken::ClearTokenWhiteSpace() { + whiteSpaceStart_p = NULL; + whiteSpaceEnd_p = NULL; + linesCrossed = 0; +} diff --git a/neo/idlib/Token.h b/neo/idlib/Token.h new file mode 100644 index 00000000..516f158d --- /dev/null +++ b/neo/idlib/Token.h @@ -0,0 +1,165 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TOKEN_H__ +#define __TOKEN_H__ + +/* +=============================================================================== + + idToken is a token read from a file or memory with idLexer or idParser + +=============================================================================== +*/ + +// token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +// number sub types +#define TT_INTEGER 0x00001 // integer +#define TT_DECIMAL 0x00002 // decimal number +#define TT_HEX 0x00004 // hexadecimal number +#define TT_OCTAL 0x00008 // octal number +#define TT_BINARY 0x00010 // binary number +#define TT_LONG 0x00020 // long int +#define TT_UNSIGNED 0x00040 // unsigned int +#define TT_FLOAT 0x00080 // floating point number +#define TT_SINGLE_PRECISION 0x00100 // float +#define TT_DOUBLE_PRECISION 0x00200 // double +#define TT_EXTENDED_PRECISION 0x00400 // long double +#define TT_INFINITE 0x00800 // infinite 1.#INF +#define TT_INDEFINITE 0x01000 // indefinite 1.#IND +#define TT_NAN 0x02000 // NaN +#define TT_IPADDRESS 0x04000 // ip address +#define TT_IPPORT 0x08000 // ip port +#define TT_VALUESVALID 0x10000 // set if intvalue and floatvalue are valid + +// string sub type is the length of the string +// literal sub type is the ASCII code +// punctuation sub type is the punctuation id +// name sub type is the length of the name + +class idToken : public idStr { + + friend class idParser; + friend class idLexer; + +public: + int type; // token type + int subtype; // token sub type + int line; // line in script the token was on + int linesCrossed; // number of lines crossed in white space before token + int flags; // token flags, used for recursive defines + +public: + idToken(); + idToken( const idToken *token ); + ~idToken(); + + void operator=( const idStr& text ); + void operator=( const char *text ); + + double GetDoubleValue(); // double value of TT_NUMBER + float GetFloatValue(); // float value of TT_NUMBER + unsigned long GetUnsignedLongValue(); // unsigned long value of TT_NUMBER + int GetIntValue(); // int value of TT_NUMBER + int WhiteSpaceBeforeToken() const;// returns length of whitespace before token + void ClearTokenWhiteSpace(); // forget whitespace before token + + void NumberValue(); // calculate values for a TT_NUMBER + +private: + unsigned long intvalue; // integer value + double floatvalue; // floating point value + const char * whiteSpaceStart_p; // start of white space before token, only used by idLexer + const char * whiteSpaceEnd_p; // end of white space before token, only used by idLexer + idToken * next; // next token in chain, only used by idParser + + void AppendDirty( const char a ); // append character without adding trailing zero +}; + +ID_INLINE idToken::idToken() : type(), subtype(), line(), linesCrossed(), flags() { +} + +ID_INLINE idToken::idToken( const idToken *token ) { + *this = *token; +} + +ID_INLINE idToken::~idToken() { +} + +ID_INLINE void idToken::operator=( const char *text) { + *static_cast(this) = text; +} + +ID_INLINE void idToken::operator=( const idStr& text ) { + *static_cast(this) = text; +} + +ID_INLINE double idToken::GetDoubleValue() { + if ( type != TT_NUMBER ) { + return 0.0; + } + if ( !(subtype & TT_VALUESVALID) ) { + NumberValue(); + } + return floatvalue; +} + +ID_INLINE float idToken::GetFloatValue() { + return (float) GetDoubleValue(); +} + +ID_INLINE unsigned long idToken::GetUnsignedLongValue() { + if ( type != TT_NUMBER ) { + return 0; + } + if ( !(subtype & TT_VALUESVALID) ) { + NumberValue(); + } + return intvalue; +} + +ID_INLINE int idToken::GetIntValue() { + return (int) GetUnsignedLongValue(); +} + +ID_INLINE int idToken::WhiteSpaceBeforeToken() const { + return ( whiteSpaceEnd_p > whiteSpaceStart_p ); +} + +ID_INLINE void idToken::AppendDirty( const char a ) { + EnsureAlloced( len + 2, true ); + data[len++] = a; +} + +#endif /* !__TOKEN_H__ */ diff --git a/neo/idlib/bv/Bounds.cpp b/neo/idlib/bv/Bounds.cpp new file mode 100644 index 00000000..bcb5d882 --- /dev/null +++ b/neo/idlib/bv/Bounds.cpp @@ -0,0 +1,425 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +idBounds bounds_zero( vec3_zero, vec3_zero ); +idBounds bounds_zeroOneCube( idVec3( 0.0f ), idVec3( 1.0f ) ); +idBounds bounds_unitCube( idVec3( -1.0f ), idVec3( 1.0f ) ); + +/* +============ +idBounds::GetRadius +============ +*/ +float idBounds::GetRadius() const { + int i; + float total, b0, b1; + + total = 0.0f; + for ( i = 0; i < 3; i++ ) { + b0 = (float)idMath::Fabs( b[0][i] ); + b1 = (float)idMath::Fabs( b[1][i] ); + if ( b0 > b1 ) { + total += b0 * b0; + } else { + total += b1 * b1; + } + } + return idMath::Sqrt( total ); +} + +/* +============ +idBounds::GetRadius +============ +*/ +float idBounds::GetRadius( const idVec3 ¢er ) const { + int i; + float total, b0, b1; + + total = 0.0f; + for ( i = 0; i < 3; i++ ) { + b0 = (float)idMath::Fabs( center[i] - b[0][i] ); + b1 = (float)idMath::Fabs( b[1][i] - center[i] ); + if ( b0 > b1 ) { + total += b0 * b0; + } else { + total += b1 * b1; + } + } + return idMath::Sqrt( total ); +} + +/* +================ +idBounds::PlaneDistance +================ +*/ +float idBounds::PlaneDistance( const idPlane &plane ) const { + idVec3 center; + float d1, d2; + + center = ( b[0] + b[1] ) * 0.5f; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( ( b[1][0] - center[0] ) * plane.Normal()[0] ) + + idMath::Fabs( ( b[1][1] - center[1] ) * plane.Normal()[1] ) + + idMath::Fabs( ( b[1][2] - center[2] ) * plane.Normal()[2] ); + + if ( d1 - d2 > 0.0f ) { + return d1 - d2; + } + if ( d1 + d2 < 0.0f ) { + return d1 + d2; + } + return 0.0f; +} + +/* +================ +idBounds::PlaneSide +================ +*/ +int idBounds::PlaneSide( const idPlane &plane, const float epsilon ) const { + idVec3 center; + float d1, d2; + + center = ( b[0] + b[1] ) * 0.5f; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( ( b[1][0] - center[0] ) * plane.Normal()[0] ) + + idMath::Fabs( ( b[1][1] - center[1] ) * plane.Normal()[1] ) + + idMath::Fabs( ( b[1][2] - center[2] ) * plane.Normal()[2] ); + + if ( d1 - d2 > epsilon ) { + return PLANESIDE_FRONT; + } + if ( d1 + d2 < -epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idBounds::LineIntersection + + Returns true if the line intersects the bounds between the start and end point. +============ +*/ +bool idBounds::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + const idVec3 center = ( b[0] + b[1] ) * 0.5f; + const idVec3 extents = b[1] - center; + const idVec3 lineDir = 0.5f * ( end - start ); + const idVec3 lineCenter = start + lineDir; + const idVec3 dir = lineCenter - center; + + const float ld0 = idMath::Fabs( lineDir[0] ); + if ( idMath::Fabs( dir[0] ) > extents[0] + ld0 ) { + return false; + } + + const float ld1 = idMath::Fabs( lineDir[1] ); + if ( idMath::Fabs( dir[1] ) > extents[1] + ld1 ) { + return false; + } + + const float ld2 = idMath::Fabs( lineDir[2] ); + if ( idMath::Fabs( dir[2] ) > extents[2] + ld2 ) { + return false; + } + + const idVec3 cross = lineDir.Cross( dir ); + + if ( idMath::Fabs( cross[0] ) > extents[1] * ld2 + extents[2] * ld1 ) { + return false; + } + + if ( idMath::Fabs( cross[1] ) > extents[0] * ld2 + extents[2] * ld0 ) { + return false; + } + + if ( idMath::Fabs( cross[2] ) > extents[0] * ld1 + extents[1] * ld0 ) { + return false; + } + + return true; +} + +/* +============ +idBounds::RayIntersection + + Returns true if the ray intersects the bounds. + The ray can intersect the bounds in both directions from the start point. + If start is inside the bounds it is considered an intersection with scale = 0 +============ +*/ +bool idBounds::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const { + int i, ax0, ax1, ax2, side, inside; + float f; + idVec3 hit; + + ax0 = -1; + inside = 0; + for ( i = 0; i < 3; i++ ) { + if ( start[i] < b[0][i] ) { + side = 0; + } + else if ( start[i] > b[1][i] ) { + side = 1; + } + else { + inside++; + continue; + } + if ( dir[i] == 0.0f ) { + continue; + } + f = ( start[i] - b[side][i] ); + if ( ax0 < 0 || idMath::Fabs( f ) > idMath::Fabs( scale * dir[i] ) ) { + scale = - ( f / dir[i] ); + ax0 = i; + } + } + + if ( ax0 < 0 ) { + scale = 0.0f; + // return true if the start point is inside the bounds + return ( inside == 3 ); + } + + ax1 = (ax0+1)%3; + ax2 = (ax0+2)%3; + hit[ax1] = start[ax1] + scale * dir[ax1]; + hit[ax2] = start[ax2] + scale * dir[ax2]; + + return ( hit[ax1] >= b[0][ax1] && hit[ax1] <= b[1][ax1] && + hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] ); +} + +/* +============ +idBounds::FromTransformedBounds +============ +*/ +void idBounds::FromTransformedBounds( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ) { + int i; + idVec3 center, extents, rotatedExtents; + + center = (bounds[0] + bounds[1]) * 0.5f; + extents = bounds[1] - center; + + for ( i = 0; i < 3; i++ ) { + rotatedExtents[i] = idMath::Fabs( extents[0] * axis[0][i] ) + + idMath::Fabs( extents[1] * axis[1][i] ) + + idMath::Fabs( extents[2] * axis[2][i] ); + } + + center = origin + center * axis; + b[0] = center - rotatedExtents; + b[1] = center + rotatedExtents; +} + +/* +============ +idBounds::FromPoints + + Most tight bounds for a point set. +============ +*/ +void idBounds::FromPoints( const idVec3 *points, const int numPoints ) { + SIMDProcessor->MinMax( b[0], b[1], points, numPoints ); +} + +/* +============ +idBounds::FromPointTranslation + + Most tight bounds for the translational movement of the given point. +============ +*/ +void idBounds::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( translation[i] < 0.0f ) { + b[0][i] = point[i] + translation[i]; + b[1][i] = point[i]; + } + else { + b[0][i] = point[i]; + b[1][i] = point[i] + translation[i]; + } + } +} + +/* +============ +idBounds::FromBoundsTranslation + + Most tight bounds for the translational movement of the given bounds. +============ +*/ +void idBounds::FromBoundsTranslation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idVec3 &translation ) { + int i; + + if ( axis.IsRotated() ) { + FromTransformedBounds( bounds, origin, axis ); + } + else { + b[0] = bounds[0] + origin; + b[1] = bounds[1] + origin; + } + for ( i = 0; i < 3; i++ ) { + if ( translation[i] < 0.0f ) { + b[0][i] += translation[i]; + } + else { + b[1][i] += translation[i]; + } + } +} + +/* +================ +BoundsForPointRotation + + only for rotations < 180 degrees +================ +*/ +idBounds BoundsForPointRotation( const idVec3 &start, const idRotation &rotation ) { + int i; + float radiusSqr; + idVec3 v1, v2; + idVec3 origin, axis, end; + idBounds bounds; + + end = start * rotation; + axis = rotation.GetVec(); + origin = rotation.GetOrigin() + axis * ( axis * ( start - rotation.GetOrigin() ) ); + radiusSqr = ( start - origin ).LengthSqr(); + v1 = ( start - origin ).Cross( axis ); + v2 = ( end - origin ).Cross( axis ); + + for ( i = 0; i < 3; i++ ) { + // if the derivative changes sign along this axis during the rotation from start to end + if ( ( v1[i] > 0.0f && v2[i] < 0.0f ) || ( v1[i] < 0.0f && v2[i] > 0.0f ) ) { + if ( ( 0.5f * (start[i] + end[i]) - origin[i] ) > 0.0f ) { + bounds[0][i] = Min( start[i], end[i] ); + bounds[1][i] = origin[i] + idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } + else { + bounds[0][i] = origin[i] - idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + bounds[1][i] = Max( start[i], end[i] ); + } + } + else if ( start[i] > end[i] ) { + bounds[0][i] = end[i]; + bounds[1][i] = start[i]; + } + else { + bounds[0][i] = start[i]; + bounds[1][i] = end[i]; + } + } + + return bounds; +} + +/* +============ +idBounds::FromPointRotation + + Most tight bounds for the rotational movement of the given point. +============ +*/ +void idBounds::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + float radius; + + if ( idMath::Fabs( rotation.GetAngle() ) < 180.0f ) { + (*this) = BoundsForPointRotation( point, rotation ); + } + else { + + radius = ( point - rotation.GetOrigin() ).Length(); + + // FIXME: these bounds are usually way larger + b[0].Set( -radius, -radius, -radius ); + b[1].Set( radius, radius, radius ); + } +} + +/* +============ +idBounds::FromBoundsRotation + + Most tight bounds for the rotational movement of the given bounds. +============ +*/ +void idBounds::FromBoundsRotation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idRotation &rotation ) { + int i; + float radius; + idVec3 point; + idBounds rBounds; + + if ( idMath::Fabs( rotation.GetAngle() ) < 180.0f ) { + + (*this) = BoundsForPointRotation( bounds[0] * axis + origin, rotation ); + for ( i = 1; i < 8; i++ ) { + point[0] = bounds[(i^(i>>1))&1][0]; + point[1] = bounds[(i>>1)&1][1]; + point[2] = bounds[(i>>2)&1][2]; + (*this) += BoundsForPointRotation( point * axis + origin, rotation ); + } + } + else { + + point = (bounds[1] - bounds[0]) * 0.5f; + radius = (bounds[1] - point).Length() + (point - rotation.GetOrigin()).Length(); + + // FIXME: these bounds are usually way larger + b[0].Set( -radius, -radius, -radius ); + b[1].Set( radius, radius, radius ); + } +} + +/* +============ +idBounds::ToPoints +============ +*/ +void idBounds::ToPoints( idVec3 points[8] ) const { + for ( int i = 0; i < 8; i++ ) { + points[i][0] = b[(i^(i>>1))&1][0]; + points[i][1] = b[(i>>1)&1][1]; + points[i][2] = b[(i>>2)&1][2]; + } +} diff --git a/neo/idlib/bv/Bounds.h b/neo/idlib/bv/Bounds.h new file mode 100644 index 00000000..ac071462 --- /dev/null +++ b/neo/idlib/bv/Bounds.h @@ -0,0 +1,428 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BV_BOUNDS_H__ +#define __BV_BOUNDS_H__ + +/* +=============================================================================== + + Axis Aligned Bounding Box + +=============================================================================== +*/ + +class idBounds { +public: + idBounds(); + explicit idBounds( const idVec3 &mins, const idVec3 &maxs ); + explicit idBounds( const idVec3 &point ); + + const idVec3 & operator[]( const int index ) const; + idVec3 & operator[]( const int index ); + idBounds operator+( const idVec3 &t ) const; // returns translated bounds + idBounds & operator+=( const idVec3 &t ); // translate the bounds + idBounds operator*( const idMat3 &r ) const; // returns rotated bounds + idBounds & operator*=( const idMat3 &r ); // rotate the bounds + idBounds operator+( const idBounds &a ) const; + idBounds & operator+=( const idBounds &a ); + idBounds operator-( const idBounds &a ) const; + idBounds & operator-=( const idBounds &a ); + + bool Compare( const idBounds &a ) const; // exact compare, no epsilon + bool Compare( const idBounds &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idBounds &a ) const; // exact compare, no epsilon + bool operator!=( const idBounds &a ) const; // exact compare, no epsilon + + void Clear(); // inside out bounds + void Zero(); // single point at origin + + idVec3 GetCenter() const; // returns center of bounds + float GetRadius() const; // returns the radius relative to the bounds origin + float GetRadius( const idVec3 ¢er ) const; // returns the radius relative to the given center + float GetVolume() const; // returns the volume of the bounds + bool IsCleared() const; // returns true if bounds are inside out + + bool AddPoint( const idVec3 &v ); // add the point, returns true if the bounds expanded + bool AddBounds( const idBounds &a ); // add the bounds, returns true if the bounds expanded + idBounds Intersect( const idBounds &a ) const; // return intersection of this bounds with the given bounds + idBounds & IntersectSelf( const idBounds &a ); // intersect this bounds with the given bounds + idBounds Expand( const float d ) const; // return bounds expanded in all directions with the given value + idBounds & ExpandSelf( const float d ); // expand bounds in all directions with the given value + idBounds Translate( const idVec3 &translation ) const; // return translated bounds + idBounds & TranslateSelf( const idVec3 &translation ); // translate this bounds + idBounds Rotate( const idMat3 &rotation ) const; // return rotated bounds + idBounds & RotateSelf( const idMat3 &rotation ); // rotate this bounds + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsBounds( const idBounds &a ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const; + + // most tight bounds for the given transformed bounds + void FromTransformedBounds( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ); + // most tight bounds for a point set + void FromPoints( const idVec3 *points, const int numPoints ); + // most tight bounds for a translation + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromBoundsTranslation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idVec3 &translation ); + // most tight bounds for a rotation + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromBoundsRotation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idRotation &rotation ); + + void ToPoints( idVec3 points[8] ) const; + idSphere ToSphere() const; + + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idVec3 &origin, const idMat3 &axis, const idVec3 &dir, float &min, float &max ) const; + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + +private: + idVec3 b[2]; +}; + +extern idBounds bounds_zero; +extern idBounds bounds_zeroOneCube; +extern idBounds bounds_unitCube; + +ID_INLINE idBounds::idBounds() { +} + +ID_INLINE idBounds::idBounds( const idVec3 &mins, const idVec3 &maxs ) { + b[0] = mins; + b[1] = maxs; +} + +ID_INLINE idBounds::idBounds( const idVec3 &point ) { + b[0] = point; + b[1] = point; +} + +ID_INLINE const idVec3 &idBounds::operator[]( const int index ) const { + return b[index]; +} + +ID_INLINE idVec3 &idBounds::operator[]( const int index ) { + return b[index]; +} + +ID_INLINE idBounds idBounds::operator+( const idVec3 &t ) const { + return idBounds( b[0] + t, b[1] + t ); +} + +ID_INLINE idBounds &idBounds::operator+=( const idVec3 &t ) { + b[0] += t; + b[1] += t; + return *this; +} + +ID_INLINE idBounds idBounds::operator*( const idMat3 &r ) const { + idBounds bounds; + bounds.FromTransformedBounds( *this, vec3_origin, r ); + return bounds; +} + +ID_INLINE idBounds &idBounds::operator*=( const idMat3 &r ) { + this->FromTransformedBounds( *this, vec3_origin, r ); + return *this; +} + +ID_INLINE idBounds idBounds::operator+( const idBounds &a ) const { + idBounds newBounds; + newBounds = *this; + newBounds.AddBounds( a ); + return newBounds; +} + +ID_INLINE idBounds &idBounds::operator+=( const idBounds &a ) { + idBounds::AddBounds( a ); + return *this; +} + +ID_INLINE idBounds idBounds::operator-( const idBounds &a ) const { + assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] && + b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] && + b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] ); + return idBounds( idVec3( b[0][0] + a.b[1][0], b[0][1] + a.b[1][1], b[0][2] + a.b[1][2] ), + idVec3( b[1][0] + a.b[0][0], b[1][1] + a.b[0][1], b[1][2] + a.b[0][2] ) ); +} + +ID_INLINE idBounds &idBounds::operator-=( const idBounds &a ) { + assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] && + b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] && + b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] ); + b[0] += a.b[1]; + b[1] += a.b[0]; + return *this; +} + +ID_INLINE bool idBounds::Compare( const idBounds &a ) const { + return ( b[0].Compare( a.b[0] ) && b[1].Compare( a.b[1] ) ); +} + +ID_INLINE bool idBounds::Compare( const idBounds &a, const float epsilon ) const { + return ( b[0].Compare( a.b[0], epsilon ) && b[1].Compare( a.b[1], epsilon ) ); +} + +ID_INLINE bool idBounds::operator==( const idBounds &a ) const { + return Compare( a ); +} + +ID_INLINE bool idBounds::operator!=( const idBounds &a ) const { + return !Compare( a ); +} + +ID_INLINE void idBounds::Clear() { + b[0][0] = b[0][1] = b[0][2] = idMath::INFINITY; + b[1][0] = b[1][1] = b[1][2] = -idMath::INFINITY; +} + +ID_INLINE void idBounds::Zero() { + b[0][0] = b[0][1] = b[0][2] = + b[1][0] = b[1][1] = b[1][2] = 0; +} + +ID_INLINE idVec3 idBounds::GetCenter() const { + return idVec3( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f ); +} + +ID_INLINE float idBounds::GetVolume() const { + if ( b[0][0] >= b[1][0] || b[0][1] >= b[1][1] || b[0][2] >= b[1][2] ) { + return 0.0f; + } + return ( ( b[1][0] - b[0][0] ) * ( b[1][1] - b[0][1] ) * ( b[1][2] - b[0][2] ) ); +} + +ID_INLINE bool idBounds::IsCleared() const { + return b[0][0] > b[1][0]; +} + +ID_INLINE bool idBounds::AddPoint( const idVec3 &v ) { + bool expanded = false; + if ( v[0] < b[0][0]) { + b[0][0] = v[0]; + expanded = true; + } + if ( v[0] > b[1][0]) { + b[1][0] = v[0]; + expanded = true; + } + if ( v[1] < b[0][1] ) { + b[0][1] = v[1]; + expanded = true; + } + if ( v[1] > b[1][1]) { + b[1][1] = v[1]; + expanded = true; + } + if ( v[2] < b[0][2] ) { + b[0][2] = v[2]; + expanded = true; + } + if ( v[2] > b[1][2]) { + b[1][2] = v[2]; + expanded = true; + } + return expanded; +} + +ID_INLINE bool idBounds::AddBounds( const idBounds &a ) { + bool expanded = false; + if ( a.b[0][0] < b[0][0] ) { + b[0][0] = a.b[0][0]; + expanded = true; + } + if ( a.b[0][1] < b[0][1] ) { + b[0][1] = a.b[0][1]; + expanded = true; + } + if ( a.b[0][2] < b[0][2] ) { + b[0][2] = a.b[0][2]; + expanded = true; + } + if ( a.b[1][0] > b[1][0] ) { + b[1][0] = a.b[1][0]; + expanded = true; + } + if ( a.b[1][1] > b[1][1] ) { + b[1][1] = a.b[1][1]; + expanded = true; + } + if ( a.b[1][2] > b[1][2] ) { + b[1][2] = a.b[1][2]; + expanded = true; + } + return expanded; +} + +ID_INLINE idBounds idBounds::Intersect( const idBounds &a ) const { + idBounds n; + n.b[0][0] = ( a.b[0][0] > b[0][0] ) ? a.b[0][0] : b[0][0]; + n.b[0][1] = ( a.b[0][1] > b[0][1] ) ? a.b[0][1] : b[0][1]; + n.b[0][2] = ( a.b[0][2] > b[0][2] ) ? a.b[0][2] : b[0][2]; + n.b[1][0] = ( a.b[1][0] < b[1][0] ) ? a.b[1][0] : b[1][0]; + n.b[1][1] = ( a.b[1][1] < b[1][1] ) ? a.b[1][1] : b[1][1]; + n.b[1][2] = ( a.b[1][2] < b[1][2] ) ? a.b[1][2] : b[1][2]; + return n; +} + +ID_INLINE idBounds &idBounds::IntersectSelf( const idBounds &a ) { + if ( a.b[0][0] > b[0][0] ) { + b[0][0] = a.b[0][0]; + } + if ( a.b[0][1] > b[0][1] ) { + b[0][1] = a.b[0][1]; + } + if ( a.b[0][2] > b[0][2] ) { + b[0][2] = a.b[0][2]; + } + if ( a.b[1][0] < b[1][0] ) { + b[1][0] = a.b[1][0]; + } + if ( a.b[1][1] < b[1][1] ) { + b[1][1] = a.b[1][1]; + } + if ( a.b[1][2] < b[1][2] ) { + b[1][2] = a.b[1][2]; + } + return *this; +} + +ID_INLINE idBounds idBounds::Expand( const float d ) const { + return idBounds( idVec3( b[0][0] - d, b[0][1] - d, b[0][2] - d ), + idVec3( b[1][0] + d, b[1][1] + d, b[1][2] + d ) ); +} + +ID_INLINE idBounds &idBounds::ExpandSelf( const float d ) { + b[0][0] -= d; + b[0][1] -= d; + b[0][2] -= d; + b[1][0] += d; + b[1][1] += d; + b[1][2] += d; + return *this; +} + +ID_INLINE idBounds idBounds::Translate( const idVec3 &translation ) const { + return idBounds( b[0] + translation, b[1] + translation ); +} + +ID_INLINE idBounds &idBounds::TranslateSelf( const idVec3 &translation ) { + b[0] += translation; + b[1] += translation; + return *this; +} + +ID_INLINE idBounds idBounds::Rotate( const idMat3 &rotation ) const { + idBounds bounds; + bounds.FromTransformedBounds( *this, vec3_origin, rotation ); + return bounds; +} + +ID_INLINE idBounds &idBounds::RotateSelf( const idMat3 &rotation ) { + FromTransformedBounds( *this, vec3_origin, rotation ); + return *this; +} + +ID_INLINE bool idBounds::ContainsPoint( const idVec3 &p ) const { + if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2] + || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE bool idBounds::IntersectsBounds( const idBounds &a ) const { + if ( a.b[1][0] < b[0][0] || a.b[1][1] < b[0][1] || a.b[1][2] < b[0][2] + || a.b[0][0] > b[1][0] || a.b[0][1] > b[1][1] || a.b[0][2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE idSphere idBounds::ToSphere() const { + idSphere sphere; + sphere.SetOrigin( ( b[0] + b[1] ) * 0.5f ); + sphere.SetRadius( ( b[1] - sphere.GetOrigin() ).Length() ); + return sphere; +} + +ID_INLINE void idBounds::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d1, d2; + idVec3 center, extents; + + center = ( b[0] + b[1] ) * 0.5f; + extents = b[1] - center; + + d1 = dir * center; + d2 = idMath::Fabs( extents[0] * dir[0] ) + + idMath::Fabs( extents[1] * dir[1] ) + + idMath::Fabs( extents[2] * dir[2] ); + + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE void idBounds::AxisProjection( const idVec3 &origin, const idMat3 &axis, const idVec3 &dir, float &min, float &max ) const { + float d1, d2; + idVec3 center, extents; + + center = ( b[0] + b[1] ) * 0.5f; + extents = b[1] - center; + center = origin + center * axis; + + d1 = dir * center; + d2 = idMath::Fabs( extents[0] * ( dir * axis[0] ) ) + + idMath::Fabs( extents[1] * ( dir * axis[1] ) ) + + idMath::Fabs( extents[2] * ( dir * axis[2] ) ); + + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE int idBounds::GetDimension() const { + return 6; +} + +ID_INLINE const float *idBounds::ToFloatPtr() const { + return &b[0].x; +} + +ID_INLINE float *idBounds::ToFloatPtr() { + return &b[0].x; +} + +#endif /* !__BV_BOUNDS_H__ */ diff --git a/neo/idlib/bv/Box.cpp b/neo/idlib/bv/Box.cpp new file mode 100644 index 00000000..cafdd05b --- /dev/null +++ b/neo/idlib/bv/Box.cpp @@ -0,0 +1,849 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +idBox box_zero( vec3_zero, vec3_zero, mat3_identity ); + +/* + 4---{4}---5 + + /| /| + Z {7} {8} {5} | + - / | / {9} + 7--{6}----6 | + | | | | + {11} 0---|-{0}-1 + | / | / - + | {3} {10} {1} Y + |/ |/ + + 3---{2}---2 + + - X + + + plane bits: + 0 = min x + 1 = max x + 2 = min y + 3 = max y + 4 = min z + 5 = max z + +*/ + +/* +static int boxVertPlanes[8] = { + ( (1<<0) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<3) | (1<<5) ), + ( (1<<0) | (1<<3) | (1<<5) ) +}; + +static int boxVertEdges[8][3] = { + // bottom + { 3, 0, 8 }, + { 0, 1, 9 }, + { 1, 2, 10 }, + { 2, 3, 11 }, + // top + { 7, 4, 8 }, + { 4, 5, 9 }, + { 5, 6, 10 }, + { 6, 7, 11 } +}; + +static int boxEdgePlanes[12][2] = { + // bottom + { 4, 2 }, + { 4, 1 }, + { 4, 3 }, + { 4, 0 }, + // top + { 5, 2 }, + { 5, 1 }, + { 5, 3 }, + { 5, 0 }, + // sides + { 0, 2 }, + { 2, 1 }, + { 1, 3 }, + { 3, 0 } +}; + +static int boxEdgeVerts[12][2] = { + // bottom + { 0, 1 }, + { 1, 2 }, + { 2, 3 }, + { 3, 0 }, + // top + { 4, 5 }, + { 5, 6 }, + { 6, 7 }, + { 7, 4 }, + // sides + { 0, 4 }, + { 1, 5 }, + { 2, 6 }, + { 3, 7 } +}; +*/ + +static int boxPlaneBitsSilVerts[64][7] = { + { 0, 0, 0, 0, 0, 0, 0 }, // 000000 = 0 + { 4, 7, 4, 0, 3, 0, 0 }, // 000001 = 1 + { 4, 5, 6, 2, 1, 0, 0 }, // 000010 = 2 + { 0, 0, 0, 0, 0, 0, 0 }, // 000011 = 3 + { 4, 4, 5, 1, 0, 0, 0 }, // 000100 = 4 + { 6, 3, 7, 4, 5, 1, 0 }, // 000101 = 5 + { 6, 4, 5, 6, 2, 1, 0 }, // 000110 = 6 + { 0, 0, 0, 0, 0, 0, 0 }, // 000111 = 7 + { 4, 6, 7, 3, 2, 0, 0 }, // 001000 = 8 + { 6, 6, 7, 4, 0, 3, 2 }, // 001001 = 9 + { 6, 5, 6, 7, 3, 2, 1 }, // 001010 = 10 + { 0, 0, 0, 0, 0, 0, 0 }, // 001011 = 11 + { 0, 0, 0, 0, 0, 0, 0 }, // 001100 = 12 + { 0, 0, 0, 0, 0, 0, 0 }, // 001101 = 13 + { 0, 0, 0, 0, 0, 0, 0 }, // 001110 = 14 + { 0, 0, 0, 0, 0, 0, 0 }, // 001111 = 15 + { 4, 0, 1, 2, 3, 0, 0 }, // 010000 = 16 + { 6, 0, 1, 2, 3, 7, 4 }, // 010001 = 17 + { 6, 3, 2, 6, 5, 1, 0 }, // 010010 = 18 + { 0, 0, 0, 0, 0, 0, 0 }, // 010011 = 19 + { 6, 1, 2, 3, 0, 4, 5 }, // 010100 = 20 + { 6, 1, 2, 3, 7, 4, 5 }, // 010101 = 21 + { 6, 2, 3, 0, 4, 5, 6 }, // 010110 = 22 + { 0, 0, 0, 0, 0, 0, 0 }, // 010111 = 23 + { 6, 0, 1, 2, 6, 7, 3 }, // 011000 = 24 + { 6, 0, 1, 2, 6, 7, 4 }, // 011001 = 25 + { 6, 0, 1, 5, 6, 7, 3 }, // 011010 = 26 + { 0, 0, 0, 0, 0, 0, 0 }, // 011011 = 27 + { 0, 0, 0, 0, 0, 0, 0 }, // 011100 = 28 + { 0, 0, 0, 0, 0, 0, 0 }, // 011101 = 29 + { 0, 0, 0, 0, 0, 0, 0 }, // 011110 = 30 + { 0, 0, 0, 0, 0, 0, 0 }, // 011111 = 31 + { 4, 7, 6, 5, 4, 0, 0 }, // 100000 = 32 + { 6, 7, 6, 5, 4, 0, 3 }, // 100001 = 33 + { 6, 5, 4, 7, 6, 2, 1 }, // 100010 = 34 + { 0, 0, 0, 0, 0, 0, 0 }, // 100011 = 35 + { 6, 4, 7, 6, 5, 1, 0 }, // 100100 = 36 + { 6, 3, 7, 6, 5, 1, 0 }, // 100101 = 37 + { 6, 4, 7, 6, 2, 1, 0 }, // 100110 = 38 + { 0, 0, 0, 0, 0, 0, 0 }, // 100111 = 39 + { 6, 6, 5, 4, 7, 3, 2 }, // 101000 = 40 + { 6, 6, 5, 4, 0, 3, 2 }, // 101001 = 41 + { 6, 5, 4, 7, 3, 2, 1 }, // 101010 = 42 + { 0, 0, 0, 0, 0, 0, 0 }, // 101011 = 43 + { 0, 0, 0, 0, 0, 0, 0 }, // 101100 = 44 + { 0, 0, 0, 0, 0, 0, 0 }, // 101101 = 45 + { 0, 0, 0, 0, 0, 0, 0 }, // 101110 = 46 + { 0, 0, 0, 0, 0, 0, 0 }, // 101111 = 47 + { 0, 0, 0, 0, 0, 0, 0 }, // 110000 = 48 + { 0, 0, 0, 0, 0, 0, 0 }, // 110001 = 49 + { 0, 0, 0, 0, 0, 0, 0 }, // 110010 = 50 + { 0, 0, 0, 0, 0, 0, 0 }, // 110011 = 51 + { 0, 0, 0, 0, 0, 0, 0 }, // 110100 = 52 + { 0, 0, 0, 0, 0, 0, 0 }, // 110101 = 53 + { 0, 0, 0, 0, 0, 0, 0 }, // 110110 = 54 + { 0, 0, 0, 0, 0, 0, 0 }, // 110111 = 55 + { 0, 0, 0, 0, 0, 0, 0 }, // 111000 = 56 + { 0, 0, 0, 0, 0, 0, 0 }, // 111001 = 57 + { 0, 0, 0, 0, 0, 0, 0 }, // 111010 = 58 + { 0, 0, 0, 0, 0, 0, 0 }, // 111011 = 59 + { 0, 0, 0, 0, 0, 0, 0 }, // 111100 = 60 + { 0, 0, 0, 0, 0, 0, 0 }, // 111101 = 61 + { 0, 0, 0, 0, 0, 0, 0 }, // 111110 = 62 + { 0, 0, 0, 0, 0, 0, 0 }, // 111111 = 63 +}; + + +/* +============ +idBox::AddPoint +============ +*/ +bool idBox::AddPoint( const idVec3 &v ) { + idMat3 axis2; + idBounds bounds1, bounds2; + + if ( extents[0] < 0.0f ) { + extents.Zero(); + center = v; + axis.Identity(); + return true; + } + + bounds1[0][0] = bounds1[1][0] = center * axis[0]; + bounds1[0][1] = bounds1[1][1] = center * axis[1]; + bounds1[0][2] = bounds1[1][2] = center * axis[2]; + bounds1[0] -= extents; + bounds1[1] += extents; + if ( !bounds1.AddPoint( idVec3( v * axis[0], v * axis[1], v * axis[2] ) ) ) { + // point is contained in the box + return false; + } + + axis2[0] = v - center; + axis2[0].Normalize(); + axis2[1] = axis[ Min3Index( axis2[0] * axis[0], axis2[0] * axis[1], axis2[0] * axis[2] ) ]; + axis2[1] = axis2[1] - ( axis2[1] * axis2[0] ) * axis2[0]; + axis2[1].Normalize(); + axis2[2].Cross( axis2[0], axis2[1] ); + + AxisProjection( axis2, bounds2 ); + bounds2.AddPoint( idVec3( v * axis2[0], v * axis2[1], v * axis2[2] ) ); + + // create new box based on the smallest bounds + if ( bounds1.GetVolume() < bounds2.GetVolume() ) { + center = ( bounds1[0] + bounds1[1] ) * 0.5f; + extents = bounds1[1] - center; + center *= axis; + } + else { + center = ( bounds2[0] + bounds2[1] ) * 0.5f; + extents = bounds2[1] - center; + center *= axis2; + axis = axis2; + } + return true; +} + +/* +============ +idBox::AddBox +============ +*/ +bool idBox::AddBox( const idBox &a ) { + int i, besti; + float v, bestv; + idVec3 dir; + idMat3 ax[4]; + idBounds bounds[4], b; + + if ( a.extents[0] < 0.0f ) { + return false; + } + + if ( extents[0] < 0.0f ) { + center = a.center; + extents = a.extents; + axis = a.axis; + return true; + } + + // test axis of this box + ax[0] = axis; + bounds[0][0][0] = bounds[0][1][0] = center * ax[0][0]; + bounds[0][0][1] = bounds[0][1][1] = center * ax[0][1]; + bounds[0][0][2] = bounds[0][1][2] = center * ax[0][2]; + bounds[0][0] -= extents; + bounds[0][1] += extents; + a.AxisProjection( ax[0], b ); + if ( !bounds[0].AddBounds( b ) ) { + // the other box is contained in this box + return false; + } + + // test axis of other box + ax[1] = a.axis; + bounds[1][0][0] = bounds[1][1][0] = a.center * ax[1][0]; + bounds[1][0][1] = bounds[1][1][1] = a.center * ax[1][1]; + bounds[1][0][2] = bounds[1][1][2] = a.center * ax[1][2]; + bounds[1][0] -= a.extents; + bounds[1][1] += a.extents; + AxisProjection( ax[1], b ); + if ( !bounds[1].AddBounds( b ) ) { + // this box is contained in the other box + center = a.center; + extents = a.extents; + axis = a.axis; + return true; + } + + // test axes aligned with the vector between the box centers and one of the box axis + dir = a.center - center; + dir.Normalize(); + for ( i = 2; i < 4; i++ ) { + ax[i][0] = dir; + ax[i][1] = ax[i-2][ Min3Index( dir * ax[i-2][0], dir * ax[i-2][1], dir * ax[i-2][2] ) ]; + ax[i][1] = ax[i][1] - ( ax[i][1] * dir ) * dir; + ax[i][1].Normalize(); + ax[i][2].Cross( dir, ax[i][1] ); + + AxisProjection( ax[i], bounds[i] ); + a.AxisProjection( ax[i], b ); + bounds[i].AddBounds( b ); + } + + // get the bounds with the smallest volume + bestv = idMath::INFINITY; + besti = 0; + for ( i = 0; i < 4; i++ ) { + v = bounds[i].GetVolume(); + if ( v < bestv ) { + bestv = v; + besti = i; + } + } + + // create a box from the smallest bounds axis pair + center = ( bounds[besti][0] + bounds[besti][1] ) * 0.5f; + extents = bounds[besti][1] - center; + center *= ax[besti]; + axis = ax[besti]; + + return false; +} + +/* +================ +idBox::PlaneDistance +================ +*/ +float idBox::PlaneDistance( const idPlane &plane ) const { + float d1, d2; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( extents[0] * plane.Normal()[0] ) + + idMath::Fabs( extents[1] * plane.Normal()[1] ) + + idMath::Fabs( extents[2] * plane.Normal()[2] ); + + if ( d1 - d2 > 0.0f ) { + return d1 - d2; + } + if ( d1 + d2 < 0.0f ) { + return d1 + d2; + } + return 0.0f; +} + +/* +================ +idBox::PlaneSide +================ +*/ +int idBox::PlaneSide( const idPlane &plane, const float epsilon ) const { + float d1, d2; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( extents[0] * plane.Normal()[0] ) + + idMath::Fabs( extents[1] * plane.Normal()[1] ) + + idMath::Fabs( extents[2] * plane.Normal()[2] ); + + if ( d1 - d2 > epsilon ) { + return PLANESIDE_FRONT; + } + if ( d1 + d2 < -epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idBox::IntersectsBox +============ +*/ +bool idBox::IntersectsBox( const idBox &a ) const { + idVec3 dir; // vector between centers + float c[3][3]; // matrix c = axis.Transpose() * a.axis + float ac[3][3]; // absolute values of c + float axisdir[3]; // axis[i] * dir + float d, e0, e1; // distance between centers and projected extents + + dir = a.center - center; + + // axis C0 + t * A0 + c[0][0] = axis[0] * a.axis[0]; + c[0][1] = axis[0] * a.axis[1]; + c[0][2] = axis[0] * a.axis[2]; + axisdir[0] = axis[0] * dir; + ac[0][0] = idMath::Fabs( c[0][0] ); + ac[0][1] = idMath::Fabs( c[0][1] ); + ac[0][2] = idMath::Fabs( c[0][2] ); + + d = idMath::Fabs( axisdir[0] ); + e0 = extents[0]; + e1 = a.extents[0] * ac[0][0] + a.extents[1] * ac[0][1] + a.extents[2] * ac[0][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1 + c[1][0] = axis[1] * a.axis[0]; + c[1][1] = axis[1] * a.axis[1]; + c[1][2] = axis[1] * a.axis[2]; + axisdir[1] = axis[1] * dir; + ac[1][0] = idMath::Fabs( c[1][0] ); + ac[1][1] = idMath::Fabs( c[1][1] ); + ac[1][2] = idMath::Fabs( c[1][2] ); + + d = idMath::Fabs( axisdir[1] ); + e0 = extents[1]; + e1 = a.extents[0] * ac[1][0] + a.extents[1] * ac[1][1] + a.extents[2] * ac[1][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2 + c[2][0] = axis[2] * a.axis[0]; + c[2][1] = axis[2] * a.axis[1]; + c[2][2] = axis[2] * a.axis[2]; + axisdir[2] = axis[2] * dir; + ac[2][0] = idMath::Fabs( c[2][0] ); + ac[2][1] = idMath::Fabs( c[2][1] ); + ac[2][2] = idMath::Fabs( c[2][2] ); + + d = idMath::Fabs( axisdir[2] ); + e0 = extents[2]; + e1 = a.extents[0] * ac[2][0] + a.extents[1] * ac[2][1] + a.extents[2] * ac[2][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B0 + d = idMath::Fabs( a.axis[0] * dir ); + e0 = extents[0] * ac[0][0] + extents[1] * ac[1][0] + extents[2] * ac[2][0]; + e1 = a.extents[0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B1 + d = idMath::Fabs( a.axis[1] * dir ); + e0 = extents[0] * ac[0][1] + extents[1] * ac[1][1] + extents[2] * ac[2][1]; + e1 = a.extents[1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B2 + d = idMath::Fabs( a.axis[2] * dir ); + e0 = extents[0] * ac[0][2] + extents[1] * ac[1][2] + extents[2] * ac[2][2]; + e1 = a.extents[2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB0 + d = idMath::Fabs( axisdir[2] * c[1][0] - axisdir[1] * c[2][0] ); + e0 = extents[1] * ac[2][0] + extents[2] * ac[1][0]; + e1 = a.extents[1] * ac[0][2] + a.extents[2] * ac[0][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB1 + d = idMath::Fabs( axisdir[2] * c[1][1] - axisdir[1] * c[2][1] ); + e0 = extents[1] * ac[2][1] + extents[2] * ac[1][1]; + e1 = a.extents[0] * ac[0][2] + a.extents[2] * ac[0][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB2 + d = idMath::Fabs( axisdir[2] * c[1][2] - axisdir[1] * c[2][2] ); + e0 = extents[1] * ac[2][2] + extents[2] * ac[1][2]; + e1 = a.extents[0] * ac[0][1] + a.extents[1] * ac[0][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB0 + d = idMath::Fabs( axisdir[0] * c[2][0] - axisdir[2] * c[0][0] ); + e0 = extents[0] * ac[2][0] + extents[2] * ac[0][0]; + e1 = a.extents[1] * ac[1][2] + a.extents[2] * ac[1][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB1 + d = idMath::Fabs( axisdir[0] * c[2][1] - axisdir[2] * c[0][1] ); + e0 = extents[0] * ac[2][1] + extents[2] * ac[0][1]; + e1 = a.extents[0] * ac[1][2] + a.extents[2] * ac[1][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB2 + d = idMath::Fabs( axisdir[0] * c[2][2] - axisdir[2] * c[0][2] ); + e0 = extents[0] * ac[2][2] + extents[2] * ac[0][2]; + e1 = a.extents[0] * ac[1][1] + a.extents[1] * ac[1][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB0 + d = idMath::Fabs( axisdir[1] * c[0][0] - axisdir[0] * c[1][0] ); + e0 = extents[0] * ac[1][0] + extents[1] * ac[0][0]; + e1 = a.extents[1] * ac[2][2] + a.extents[2] * ac[2][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB1 + d = idMath::Fabs( axisdir[1] * c[0][1] - axisdir[0] * c[1][1] ); + e0 = extents[0] * ac[1][1] + extents[1] * ac[0][1]; + e1 = a.extents[0] * ac[2][2] + a.extents[2] * ac[2][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB2 + d = idMath::Fabs( axisdir[1] * c[0][2] - axisdir[0] * c[1][2] ); + e0 = extents[0] * ac[1][2] + extents[1] * ac[0][2]; + e1 = a.extents[0] * ac[2][1] + a.extents[1] * ac[2][0]; + if ( d > e0 + e1 ) { + return false; + } + return true; +} + +/* +============ +idBox::LineIntersection + + Returns true if the line intersects the box between the start and end point. +============ +*/ +bool idBox::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float ld[3]; + idVec3 lineDir = 0.5f * ( end - start ); + idVec3 lineCenter = start + lineDir; + idVec3 dir = lineCenter - center; + + ld[0] = idMath::Fabs( lineDir * axis[0] ); + if ( idMath::Fabs( dir * axis[0] ) > extents[0] + ld[0] ) { + return false; + } + + ld[1] = idMath::Fabs( lineDir * axis[1] ); + if ( idMath::Fabs( dir * axis[1] ) > extents[1] + ld[1] ) { + return false; + } + + ld[2] = idMath::Fabs( lineDir * axis[2] ); + if ( idMath::Fabs( dir * axis[2] ) > extents[2] + ld[2] ) { + return false; + } + + idVec3 cross = lineDir.Cross( dir ); + + if ( idMath::Fabs( cross * axis[0] ) > extents[1] * ld[2] + extents[2] * ld[1] ) { + return false; + } + + if ( idMath::Fabs( cross * axis[1] ) > extents[0] * ld[2] + extents[2] * ld[0] ) { + return false; + } + + if ( idMath::Fabs( cross * axis[2] ) > extents[0] * ld[1] + extents[1] * ld[0] ) { + return false; + } + + return true; +} + +/* +============ +BoxPlaneClip +============ +*/ +static bool BoxPlaneClip( const float denom, const float numer, float &scale0, float &scale1 ) { + if ( denom > 0.0f ) { + if ( numer > denom * scale1 ) { + return false; + } + if ( numer > denom * scale0 ) { + scale0 = numer / denom; + } + return true; + } + else if ( denom < 0.0f ) { + if ( numer > denom * scale0 ) { + return false; + } + if ( numer > denom * scale1 ) { + scale1 = numer / denom; + } + return true; + } + else { + return ( numer <= 0.0f ); + } +} + +/* +============ +idBox::RayIntersection + + Returns true if the ray intersects the box. + The ray can intersect the box in both directions from the start point. + If start is inside the box then scale1 < 0 and scale2 > 0. +============ +*/ +bool idBox::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 localStart, localDir; + + localStart = ( start - center ) * axis.Transpose(); + localDir = dir * axis.Transpose(); + + scale1 = -idMath::INFINITY; + scale2 = idMath::INFINITY; + return BoxPlaneClip( localDir.x, -localStart.x - extents[0], scale1, scale2 ) && + BoxPlaneClip( -localDir.x, localStart.x - extents[0], scale1, scale2 ) && + BoxPlaneClip( localDir.y, -localStart.y - extents[1], scale1, scale2 ) && + BoxPlaneClip( -localDir.y, localStart.y - extents[1], scale1, scale2 ) && + BoxPlaneClip( localDir.z, -localStart.z - extents[2], scale1, scale2 ) && + BoxPlaneClip( -localDir.z, localStart.z - extents[2], scale1, scale2 ); +} + +/* +============ +idBox::FromPoints + + Tight box for a collection of points. +============ +*/ +void idBox::FromPoints( const idVec3 *points, const int numPoints ) { + int i; + float invNumPoints, sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ; + idVec3 dir; + idBounds bounds; + idMatX eigenVectors; + idVecX eigenValues; + + // compute mean of points + center = points[0]; + for ( i = 1; i < numPoints; i++ ) { + center += points[i]; + } + invNumPoints = 1.0f / numPoints; + center *= invNumPoints; + + // compute covariances of points + sumXX = 0.0f; sumXY = 0.0f; sumXZ = 0.0f; + sumYY = 0.0f; sumYZ = 0.0f; sumZZ = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - center; + sumXX += dir.x * dir.x; + sumXY += dir.x * dir.y; + sumXZ += dir.x * dir.z; + sumYY += dir.y * dir.y; + sumYZ += dir.y * dir.z; + sumZZ += dir.z * dir.z; + } + sumXX *= invNumPoints; + sumXY *= invNumPoints; + sumXZ *= invNumPoints; + sumYY *= invNumPoints; + sumYZ *= invNumPoints; + sumZZ *= invNumPoints; + + // compute eigenvectors for covariance matrix + eigenValues.SetData( 3, VECX_ALLOCA( 3 ) ); + eigenVectors.SetData( 3, 3, MATX_ALLOCA( 3 * 3 ) ); + + eigenVectors[0][0] = sumXX; + eigenVectors[0][1] = sumXY; + eigenVectors[0][2] = sumXZ; + eigenVectors[1][0] = sumXY; + eigenVectors[1][1] = sumYY; + eigenVectors[1][2] = sumYZ; + eigenVectors[2][0] = sumXZ; + eigenVectors[2][1] = sumYZ; + eigenVectors[2][2] = sumZZ; + eigenVectors.Eigen_SolveSymmetric( eigenValues ); + eigenVectors.Eigen_SortIncreasing( eigenValues ); + + axis[0][0] = eigenVectors[0][0]; + axis[0][1] = eigenVectors[0][1]; + axis[0][2] = eigenVectors[0][2]; + axis[1][0] = eigenVectors[1][0]; + axis[1][1] = eigenVectors[1][1]; + axis[1][2] = eigenVectors[1][2]; + axis[2][0] = eigenVectors[2][0]; + axis[2][1] = eigenVectors[2][1]; + axis[2][2] = eigenVectors[2][2]; + + extents[0] = eigenValues[0]; + extents[1] = eigenValues[0]; + extents[2] = eigenValues[0]; + + // refine by calculating the bounds of the points projected onto the axis and adjusting the center and extents + bounds.Clear(); + for ( i = 0; i < numPoints; i++ ) { + bounds.AddPoint( idVec3( points[i] * axis[0], points[i] * axis[1], points[i] * axis[2] ) ); + } + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + center *= axis; +} + +/* +============ +idBox::FromPointTranslation + + Most tight box for the translational movement of the given point. +============ +*/ +void idBox::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + // FIXME: implement +} + +/* +============ +idBox::FromBoxTranslation + + Most tight box for the translational movement of the given box. +============ +*/ +void idBox::FromBoxTranslation( const idBox &box, const idVec3 &translation ) { + // FIXME: implement +} + +/* +============ +idBox::FromPointRotation + + Most tight bounds for the rotational movement of the given point. +============ +*/ +void idBox::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + // FIXME: implement +} + +/* +============ +idBox::FromBoxRotation + + Most tight box for the rotational movement of the given box. +============ +*/ +void idBox::FromBoxRotation( const idBox &box, const idRotation &rotation ) { + // FIXME: implement +} + +/* +============ +idBox::ToPoints +============ +*/ +void idBox::ToPoints( idVec3 points[8] ) const { + idMat3 ax; + idVec3 temp[4]; + + ax[0] = extents[0] * axis[0]; + ax[1] = extents[1] * axis[1]; + ax[2] = extents[2] * axis[2]; + temp[0] = center - ax[0]; + temp[1] = center + ax[0]; + temp[2] = ax[1] - ax[2]; + temp[3] = ax[1] + ax[2]; + points[0] = temp[0] - temp[3]; + points[1] = temp[1] - temp[3]; + points[2] = temp[1] + temp[2]; + points[3] = temp[0] + temp[2]; + points[4] = temp[0] - temp[2]; + points[5] = temp[1] - temp[2]; + points[6] = temp[1] + temp[3]; + points[7] = temp[0] + temp[3]; +} + +/* +============ +idBox::GetProjectionSilhouetteVerts +============ +*/ +int idBox::GetProjectionSilhouetteVerts( const idVec3 &projectionOrigin, idVec3 silVerts[6] ) const { + float f; + int i, planeBits, *index; + idVec3 points[8], dir1, dir2; + + ToPoints( points ); + + dir1 = points[0] - projectionOrigin; + dir2 = points[6] - projectionOrigin; + f = dir1 * axis[0]; + planeBits = IEEE_FLT_SIGNBITNOTSET( f ); + f = dir2 * axis[0]; + planeBits |= IEEE_FLT_SIGNBITSET( f ) << 1; + f = dir1 * axis[1]; + planeBits |= IEEE_FLT_SIGNBITNOTSET( f ) << 2; + f = dir2 * axis[1]; + planeBits |= IEEE_FLT_SIGNBITSET( f ) << 3; + f = dir1 * axis[2]; + planeBits |= IEEE_FLT_SIGNBITNOTSET( f ) << 4; + f = dir2 * axis[2]; + planeBits |= IEEE_FLT_SIGNBITSET( f ) << 5; + + index = boxPlaneBitsSilVerts[planeBits]; + for ( i = 0; i < index[0]; i++ ) { + silVerts[i] = points[index[i+1]]; + } + + return index[0]; +} + +/* +============ +idBox::GetParallelProjectionSilhouetteVerts +============ +*/ +int idBox::GetParallelProjectionSilhouetteVerts( const idVec3 &projectionDir, idVec3 silVerts[6] ) const { + float f; + int i, planeBits, *index; + idVec3 points[8]; + + ToPoints( points ); + + planeBits = 0; + f = projectionDir * axis[0]; + if ( IEEE_FLT_ISNOTZERO( f ) ) { + planeBits = 1 << IEEE_FLT_SIGNBITSET( f ); + } + f = projectionDir * axis[1]; + if ( IEEE_FLT_ISNOTZERO( f ) ) { + planeBits |= 4 << IEEE_FLT_SIGNBITSET( f ); + } + f = projectionDir * axis[2]; + if ( IEEE_FLT_ISNOTZERO( f ) ) { + planeBits |= 16 << IEEE_FLT_SIGNBITSET( f ); + } + + index = boxPlaneBitsSilVerts[planeBits]; + for ( i = 0; i < index[0]; i++ ) { + silVerts[i] = points[index[i+1]]; + } + + return index[0]; +} diff --git a/neo/idlib/bv/Box.h b/neo/idlib/bv/Box.h new file mode 100644 index 00000000..17d3f020 --- /dev/null +++ b/neo/idlib/bv/Box.h @@ -0,0 +1,297 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BV_BOX_H__ +#define __BV_BOX_H__ + +/* +=============================================================================== + + Oriented Bounding Box + +=============================================================================== +*/ + +class idBox { +public: + idBox(); + explicit idBox( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis ); + explicit idBox( const idVec3 &point ); + explicit idBox( const idBounds &bounds ); + explicit idBox( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ); + + idBox operator+( const idVec3 &t ) const; // returns translated box + idBox & operator+=( const idVec3 &t ); // translate the box + idBox operator*( const idMat3 &r ) const; // returns rotated box + idBox & operator*=( const idMat3 &r ); // rotate the box + idBox operator+( const idBox &a ) const; + idBox & operator+=( const idBox &a ); + idBox operator-( const idBox &a ) const; + idBox & operator-=( const idBox &a ); + + bool Compare( const idBox &a ) const; // exact compare, no epsilon + bool Compare( const idBox &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idBox &a ) const; // exact compare, no epsilon + bool operator!=( const idBox &a ) const; // exact compare, no epsilon + + void Clear(); // inside out box + void Zero(); // single point at origin + + const idVec3 & GetCenter() const; // returns center of the box + const idVec3 & GetExtents() const; // returns extents of the box + const idMat3 & GetAxis() const; // returns the axis of the box + float GetVolume() const; // returns the volume of the box + bool IsCleared() const; // returns true if box are inside out + + bool AddPoint( const idVec3 &v ); // add the point, returns true if the box expanded + bool AddBox( const idBox &a ); // add the box, returns true if the box expanded + idBox Expand( const float d ) const; // return box expanded in all directions with the given value + idBox & ExpandSelf( const float d ); // expand box in all directions with the given value + idBox Translate( const idVec3 &translation ) const; // return translated box + idBox & TranslateSelf( const idVec3 &translation ); // translate this box + idBox Rotate( const idMat3 &rotation ) const; // return rotated box + idBox & RotateSelf( const idMat3 &rotation ); // rotate this box + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsBox( const idBox &a ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection points are (start + dir * scale1) and (start + dir * scale2) + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // tight box for a collection of points + void FromPoints( const idVec3 *points, const int numPoints ); + // most tight box for a translation + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromBoxTranslation( const idBox &box, const idVec3 &translation ); + // most tight box for a rotation + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromBoxRotation( const idBox &box, const idRotation &rotation ); + + void ToPoints( idVec3 points[8] ) const; + idSphere ToSphere() const; + + // calculates the projection of this box onto the given axis + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idMat3 &ax, idBounds &bounds ) const; + + // calculates the silhouette of the box + int GetProjectionSilhouetteVerts( const idVec3 &projectionOrigin, idVec3 silVerts[6] ) const; + int GetParallelProjectionSilhouetteVerts( const idVec3 &projectionDir, idVec3 silVerts[6] ) const; + +private: + idVec3 center; + idVec3 extents; + idMat3 axis; +}; + +extern idBox box_zero; + +ID_INLINE idBox::idBox() { +} + +ID_INLINE idBox::idBox( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis ) { + this->center = center; + this->extents = extents; + this->axis = axis; +} + +ID_INLINE idBox::idBox( const idVec3 &point ) { + this->center = point; + this->extents.Zero(); + this->axis.Identity(); +} + +ID_INLINE idBox::idBox( const idBounds &bounds ) { + this->center = ( bounds[0] + bounds[1] ) * 0.5f; + this->extents = bounds[1] - this->center; + this->axis.Identity(); +} + +ID_INLINE idBox::idBox( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ) { + this->center = ( bounds[0] + bounds[1] ) * 0.5f; + this->extents = bounds[1] - this->center; + this->center = origin + this->center * axis; + this->axis = axis; +} + +ID_INLINE idBox idBox::operator+( const idVec3 &t ) const { + return idBox( center + t, extents, axis ); +} + +ID_INLINE idBox &idBox::operator+=( const idVec3 &t ) { + center += t; + return *this; +} + +ID_INLINE idBox idBox::operator*( const idMat3 &r ) const { + return idBox( center * r, extents, axis * r ); +} + +ID_INLINE idBox &idBox::operator*=( const idMat3 &r ) { + center *= r; + axis *= r; + return *this; +} + +ID_INLINE idBox idBox::operator+( const idBox &a ) const { + idBox newBox; + newBox = *this; + newBox.AddBox( a ); + return newBox; +} + +ID_INLINE idBox &idBox::operator+=( const idBox &a ) { + idBox::AddBox( a ); + return *this; +} + +ID_INLINE idBox idBox::operator-( const idBox &a ) const { + return idBox( center, extents - a.extents, axis ); +} + +ID_INLINE idBox &idBox::operator-=( const idBox &a ) { + extents -= a.extents; + return *this; +} + +ID_INLINE bool idBox::Compare( const idBox &a ) const { + return ( center.Compare( a.center ) && extents.Compare( a.extents ) && axis.Compare( a.axis ) ); +} + +ID_INLINE bool idBox::Compare( const idBox &a, const float epsilon ) const { + return ( center.Compare( a.center, epsilon ) && extents.Compare( a.extents, epsilon ) && axis.Compare( a.axis, epsilon ) ); +} + +ID_INLINE bool idBox::operator==( const idBox &a ) const { + return Compare( a ); +} + +ID_INLINE bool idBox::operator!=( const idBox &a ) const { + return !Compare( a ); +} + +ID_INLINE void idBox::Clear() { + center.Zero(); + extents[0] = extents[1] = extents[2] = -idMath::INFINITY; + axis.Identity(); +} + +ID_INLINE void idBox::Zero() { + center.Zero(); + extents.Zero(); + axis.Identity(); +} + +ID_INLINE const idVec3 &idBox::GetCenter() const { + return center; +} + +ID_INLINE const idVec3 &idBox::GetExtents() const { + return extents; +} + +ID_INLINE const idMat3 &idBox::GetAxis() const { + return axis; +} + +ID_INLINE float idBox::GetVolume() const { + return ( extents * 2.0f ).LengthSqr(); +} + +ID_INLINE bool idBox::IsCleared() const { + return extents[0] < 0.0f; +} + +ID_INLINE idBox idBox::Expand( const float d ) const { + return idBox( center, extents + idVec3( d, d, d ), axis ); +} + +ID_INLINE idBox &idBox::ExpandSelf( const float d ) { + extents[0] += d; + extents[1] += d; + extents[2] += d; + return *this; +} + +ID_INLINE idBox idBox::Translate( const idVec3 &translation ) const { + return idBox( center + translation, extents, axis ); +} + +ID_INLINE idBox &idBox::TranslateSelf( const idVec3 &translation ) { + center += translation; + return *this; +} + +ID_INLINE idBox idBox::Rotate( const idMat3 &rotation ) const { + return idBox( center * rotation, extents, axis * rotation ); +} + +ID_INLINE idBox &idBox::RotateSelf( const idMat3 &rotation ) { + center *= rotation; + axis *= rotation; + return *this; +} + +ID_INLINE bool idBox::ContainsPoint( const idVec3 &p ) const { + idVec3 lp = p - center; + if ( idMath::Fabs( lp * axis[0] ) > extents[0] || + idMath::Fabs( lp * axis[1] ) > extents[1] || + idMath::Fabs( lp * axis[2] ) > extents[2] ) { + return false; + } + return true; +} + +ID_INLINE idSphere idBox::ToSphere() const { + return idSphere( center, extents.Length() ); +} + +ID_INLINE void idBox::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d1 = dir * center; + float d2 = idMath::Fabs( extents[0] * ( dir * axis[0] ) ) + + idMath::Fabs( extents[1] * ( dir * axis[1] ) ) + + idMath::Fabs( extents[2] * ( dir * axis[2] ) ); + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE void idBox::AxisProjection( const idMat3 &ax, idBounds &bounds ) const { + for ( int i = 0; i < 3; i++ ) { + float d1 = ax[i] * center; + float d2 = idMath::Fabs( extents[0] * ( ax[i] * axis[0] ) ) + + idMath::Fabs( extents[1] * ( ax[i] * axis[1] ) ) + + idMath::Fabs( extents[2] * ( ax[i] * axis[2] ) ); + bounds[0][i] = d1 - d2; + bounds[1][i] = d1 + d2; + } +} + +#endif /* !__BV_BOX_H__ */ diff --git a/neo/idlib/bv/Sphere.cpp b/neo/idlib/bv/Sphere.cpp new file mode 100644 index 00000000..da85ab85 --- /dev/null +++ b/neo/idlib/bv/Sphere.cpp @@ -0,0 +1,155 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + + +idSphere sphere_zero( vec3_zero, 0.0f ); + + +/* +================ +idSphere::PlaneDistance +================ +*/ +float idSphere::PlaneDistance( const idPlane &plane ) const { + float d; + + d = plane.Distance( origin ); + if ( d > radius ) { + return d - radius; + } + if ( d < -radius ) { + return d + radius; + } + return 0.0f; +} + +/* +================ +idSphere::PlaneSide +================ +*/ +int idSphere::PlaneSide( const idPlane &plane, const float epsilon ) const { + float d; + + d = plane.Distance( origin ); + if ( d > radius + epsilon ) { + return PLANESIDE_FRONT; + } + if ( d < -radius - epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idSphere::LineIntersection + + Returns true if the line intersects the sphere between the start and end point. +============ +*/ +bool idSphere::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + idVec3 r, s, e; + float a; + + s = start - origin; + e = end - origin; + r = e - s; + a = -s * r; + if ( a <= 0 ) { + return ( s * s < radius * radius ); + } + else if ( a >= r * r ) { + return ( e * e < radius * radius ); + } + else { + r = s + ( a / ( r * r ) ) * r; + return ( r * r < radius * radius ); + } +} + +/* +============ +idSphere::RayIntersection + + Returns true if the ray intersects the sphere. + The ray can intersect the sphere in both directions from the start point. + If start is inside the sphere then scale1 < 0 and scale2 > 0. +============ +*/ +bool idSphere::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + double a, b, c, d, sqrtd; + idVec3 p; + + p = start - origin; + a = dir * dir; + b = dir * p; + c = p * p - radius * radius; + d = b * b - c * a; + + if ( d < 0.0f ) { + return false; + } + + sqrtd = idMath::Sqrt( d ); + a = 1.0f / a; + + scale1 = ( -b + sqrtd ) * a; + scale2 = ( -b - sqrtd ) * a; + + return true; +} + +/* +============ +idSphere::FromPoints + + Tight sphere for a point set. +============ +*/ +void idSphere::FromPoints( const idVec3 *points, const int numPoints ) { + int i; + float radiusSqr, dist; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, points, numPoints ); + + origin = ( mins + maxs ) * 0.5f; + + radiusSqr = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dist = ( points[i] - origin ).LengthSqr(); + if ( dist > radiusSqr ) { + radiusSqr = dist; + } + } + radius = idMath::Sqrt( radiusSqr ); +} diff --git a/neo/idlib/bv/Sphere.h b/neo/idlib/bv/Sphere.h new file mode 100644 index 00000000..8a38b414 --- /dev/null +++ b/neo/idlib/bv/Sphere.h @@ -0,0 +1,275 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BV_SPHERE_H__ +#define __BV_SPHERE_H__ + +/* +=============================================================================== + + Sphere + +=============================================================================== +*/ + +class idSphere { +public: + idSphere(); + explicit idSphere( const idVec3 &point ); + explicit idSphere( const idVec3 &point, const float r ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idSphere operator+( const idVec3 &t ) const; // returns tranlated sphere + idSphere & operator+=( const idVec3 &t ); // translate the sphere + idSphere operator+( const idSphere &s ) const; + idSphere & operator+=( const idSphere &s ); + + bool Compare( const idSphere &a ) const; // exact compare, no epsilon + bool Compare( const idSphere &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idSphere &a ) const; // exact compare, no epsilon + bool operator!=( const idSphere &a ) const; // exact compare, no epsilon + + void Clear(); // inside out sphere + void Zero(); // single point at origin + void SetOrigin( const idVec3 &o ); // set origin of sphere + void SetRadius( const float r ); // set square radius + + const idVec3 & GetOrigin() const; // returns origin of sphere + float GetRadius() const; // returns sphere radius + bool IsCleared() const; // returns true if sphere is inside out + + bool AddPoint( const idVec3 &p ); // add the point, returns true if the sphere expanded + bool AddSphere( const idSphere &s ); // add the sphere, returns true if the sphere expanded + idSphere Expand( const float d ) const; // return bounds expanded in all directions with the given value + idSphere & ExpandSelf( const float d ); // expand bounds in all directions with the given value + idSphere Translate( const idVec3 &translation ) const; + idSphere & TranslateSelf( const idVec3 &translation ); + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsSphere( const idSphere &s ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection points are (start + dir * scale1) and (start + dir * scale2) + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // Tight sphere for a point set. + void FromPoints( const idVec3 *points, const int numPoints ); + // Most tight sphere for a translation. + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromSphereTranslation( const idSphere &sphere, const idVec3 &start, const idVec3 &translation ); + // Most tight sphere for a rotation. + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromSphereRotation( const idSphere &sphere, const idVec3 &start, const idRotation &rotation ); + + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + +private: + idVec3 origin; + float radius; +}; + +extern idSphere sphere_zero; + +ID_INLINE idSphere::idSphere() { +} + +ID_INLINE idSphere::idSphere( const idVec3 &point ) { + origin = point; + radius = 0.0f; +} + +ID_INLINE idSphere::idSphere( const idVec3 &point, const float r ) { + origin = point; + radius = r; +} + +ID_INLINE float idSphere::operator[]( const int index ) const { + return ((float *) &origin)[index]; +} + +ID_INLINE float &idSphere::operator[]( const int index ) { + return ((float *) &origin)[index]; +} + +ID_INLINE idSphere idSphere::operator+( const idVec3 &t ) const { + return idSphere( origin + t, radius ); +} + +ID_INLINE idSphere &idSphere::operator+=( const idVec3 &t ) { + origin += t; + return *this; +} + +ID_INLINE bool idSphere::Compare( const idSphere &a ) const { + return ( origin.Compare( a.origin ) && radius == a.radius ); +} + +ID_INLINE bool idSphere::Compare( const idSphere &a, const float epsilon ) const { + return ( origin.Compare( a.origin, epsilon ) && idMath::Fabs( radius - a.radius ) <= epsilon ); +} + +ID_INLINE bool idSphere::operator==( const idSphere &a ) const { + return Compare( a ); +} + +ID_INLINE bool idSphere::operator!=( const idSphere &a ) const { + return !Compare( a ); +} + +ID_INLINE void idSphere::Clear() { + origin.Zero(); + radius = -1.0f; +} + +ID_INLINE void idSphere::Zero() { + origin.Zero(); + radius = 0.0f; +} + +ID_INLINE void idSphere::SetOrigin( const idVec3 &o ) { + origin = o; +} + +ID_INLINE void idSphere::SetRadius( const float r ) { + radius = r; +} + +ID_INLINE const idVec3 &idSphere::GetOrigin() const { + return origin; +} + +ID_INLINE float idSphere::GetRadius() const { + return radius; +} + +ID_INLINE bool idSphere::IsCleared() const { + return ( radius < 0.0f ); +} + +ID_INLINE bool idSphere::AddPoint( const idVec3 &p ) { + if ( radius < 0.0f ) { + origin = p; + radius = 0.0f; + return true; + } + else { + float r = ( p - origin ).LengthSqr(); + if ( r > radius * radius ) { + r = idMath::Sqrt( r ); + origin += ( p - origin ) * 0.5f * (1.0f - radius / r ); + radius += 0.5f * ( r - radius ); + return true; + } + return false; + } +} + +ID_INLINE bool idSphere::AddSphere( const idSphere &s ) { + if ( radius < 0.0f ) { + origin = s.origin; + radius = s.radius; + return true; + } + else { + float r = ( s.origin - origin ).LengthSqr(); + if ( r > ( radius + s.radius ) * ( radius + s.radius ) ) { + r = idMath::Sqrt( r ); + origin += ( s.origin - origin ) * 0.5f * (1.0f - radius / ( r + s.radius ) ); + radius += 0.5f * ( ( r + s.radius ) - radius ); + return true; + } + return false; + } +} + +ID_INLINE idSphere idSphere::Expand( const float d ) const { + return idSphere( origin, radius + d ); +} + +ID_INLINE idSphere &idSphere::ExpandSelf( const float d ) { + radius += d; + return *this; +} + +ID_INLINE idSphere idSphere::Translate( const idVec3 &translation ) const { + return idSphere( origin + translation, radius ); +} + +ID_INLINE idSphere &idSphere::TranslateSelf( const idVec3 &translation ) { + origin += translation; + return *this; +} + +ID_INLINE bool idSphere::ContainsPoint( const idVec3 &p ) const { + if ( ( p - origin ).LengthSqr() > radius * radius ) { + return false; + } + return true; +} + +ID_INLINE bool idSphere::IntersectsSphere( const idSphere &s ) const { + float r = s.radius + radius; + if ( ( s.origin - origin ).LengthSqr() > r * r ) { + return false; + } + return true; +} + +ID_INLINE void idSphere::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + origin = point + 0.5f * translation; + radius = idMath::Sqrt( 0.5f * translation.LengthSqr() ); +} + +ID_INLINE void idSphere::FromSphereTranslation( const idSphere &sphere, const idVec3 &start, const idVec3 &translation ) { + origin = start + sphere.origin + 0.5f * translation; + radius = idMath::Sqrt( 0.5f * translation.LengthSqr() ) + sphere.radius; +} + +ID_INLINE void idSphere::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + idVec3 end = rotation * point; + origin = ( point + end ) * 0.5f; + radius = idMath::Sqrt( 0.5f * ( end - point ).LengthSqr() ); +} + +ID_INLINE void idSphere::FromSphereRotation( const idSphere &sphere, const idVec3 &start, const idRotation &rotation ) { + idVec3 end = rotation * sphere.origin; + origin = start + ( sphere.origin + end ) * 0.5f; + radius = idMath::Sqrt( 0.5f * ( end - sphere.origin ).LengthSqr() ) + sphere.radius; +} + +ID_INLINE void idSphere::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d; + d = dir * origin; + min = d - radius; + max = d + radius; +} + +#endif /* !__BV_SPHERE_H__ */ diff --git a/neo/idlib/containers/Array.h b/neo/idlib/containers/Array.h new file mode 100644 index 00000000..2ab4767c --- /dev/null +++ b/neo/idlib/containers/Array.h @@ -0,0 +1,115 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __ARRAY_H__ +#define __ARRAY_H__ + +/* +================================================ +idArray is a replacement for a normal C array. + +int myArray[ARRAY_SIZE]; + +becomes: + +idArray myArray; + +Has no performance overhead in release builds, but +does index range checking in debug builds. + +Unlike idTempArray, the memory is allocated inline with the +object, rather than on the heap. + +Unlike idStaticList, there are no fields other than the +actual raw data, and the size is fixed. +================================================ +*/ +template class idArray { +public: + // returns number of elements in list + int Num() const { return numElements; } + + // returns the number of bytes the array takes up + int ByteSize() const { return sizeof( ptr ); } + + // memset the entire array to zero + void Zero() { memset( ptr, 0, sizeof( ptr ) ); } + + // memset the entire array to a specific value + void Memset( const char fill ) { memset( ptr, fill, numElements * sizeof( *ptr ) ); } + + // array operators + const T_ & operator[]( int index ) const { assert( (unsigned)index < (unsigned)numElements ); return ptr[index]; } + T_ & operator[]( int index ) { assert( (unsigned)index < (unsigned)numElements ); return ptr[index]; } + + // returns a pointer to the list + const T_ * Ptr() const { return ptr; } + T_ * Ptr() { return ptr; } + +private: + T_ ptr[numElements]; +}; + +#define ARRAY_COUNT( arrayName ) ( sizeof( arrayName )/sizeof( arrayName[0] ) ) +#define ARRAY_DEF( arrayName ) arrayName, ARRAY_COUNT( arrayName ) + + +/* +================================================ +id2DArray is essentially a typedef (as close as we can +get for templates before C++11 anyway) to make +declaring two-dimensional idArrays easier. + +Usage: + id2DArray< int, 5, 10 >::type someArray; + +================================================ +*/ +template +struct id2DArray { + typedef idArray< idArray< _type_, _dim2_ >, _dim1_ > type; +}; + + +/* +================================================ +idTupleSize +Generic way to get the size of a tuple-like type. +Add specializations as needed. +This is modeled after std::tuple_size from C++11, +which works for std::arrays also. +================================================ +*/ +template< class _type_ > +struct idTupleSize; + +template< class _type_, int _num_ > +struct idTupleSize< idArray< _type_, _num_ > > { + enum { value = _num_ }; +}; + +#endif // !__ARRAY_H__ diff --git a/neo/idlib/containers/BTree.h b/neo/idlib/containers/BTree.h new file mode 100644 index 00000000..d83699ad --- /dev/null +++ b/neo/idlib/containers/BTree.h @@ -0,0 +1,573 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BTREE_H__ +#define __BTREE_H__ + +/* +=============================================================================== + + Balanced Search Tree + +=============================================================================== +*/ + +//#define BTREE_CHECK + +template< class objType, class keyType > +class idBTreeNode { +public: + keyType key; // key used for sorting + objType * object; // if != NULL pointer to object stored in leaf node + idBTreeNode * parent; // parent node + idBTreeNode * next; // next sibling + idBTreeNode * prev; // prev sibling + int numChildren; // number of children + idBTreeNode * firstChild; // first child + idBTreeNode * lastChild; // last child +}; + + +template< class objType, class keyType, int maxChildrenPerNode > +class idBTree { +public: + idBTree(); + ~idBTree(); + + void Init(); + void Shutdown(); + + idBTreeNode * Add( objType *object, keyType key ); // add an object to the tree + void Remove( idBTreeNode *node ); // remove an object node from the tree + + idBTreeNode * NodeFind( keyType key ) const; // find an object using the given key + idBTreeNode * NodeFindSmallestLargerEqual( keyType key ) const; // find an object with the smallest key larger equal the given key + idBTreeNode * NodeFindLargestSmallerEqual( keyType key ) const; // find an object with the largest key smaller equal the given key + + objType * Find( keyType key ) const; // find an object using the given key + objType * FindSmallestLargerEqual( keyType key ) const; // find an object with the smallest key larger equal the given key + objType * FindLargestSmallerEqual( keyType key ) const; // find an object with the largest key smaller equal the given key + + idBTreeNode * GetRoot() const; // returns the root node of the tree + int GetNodeCount() const; // returns the total number of nodes in the tree + idBTreeNode * GetNext( idBTreeNode *node ) const; // goes through all nodes of the tree + idBTreeNode * GetNextLeaf( idBTreeNode *node ) const; // goes through all leaf nodes of the tree + +private: + idBTreeNode * root; + idBlockAlloc,128> nodeAllocator; + + idBTreeNode * AllocNode(); + void FreeNode( idBTreeNode *node ); + void SplitNode( idBTreeNode *node ); + idBTreeNode * MergeNodes( idBTreeNode *node1, idBTreeNode *node2 ); + + void CheckTree_r( idBTreeNode *node, int &numNodes ) const; + void CheckTree() const; +}; + + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTree::idBTree() { + assert( maxChildrenPerNode >= 4 ); + root = NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTree::~idBTree() { + Shutdown(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Init() { + root = AllocNode(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Shutdown() { + nodeAllocator.Shutdown(); + root = NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::Add( objType *object, keyType key ) { + idBTreeNode *node, *child, *newNode; + + if ( root == NULL ) { + root = AllocNode(); + } + + if ( root->numChildren >= maxChildrenPerNode ) { + newNode = AllocNode(); + newNode->key = root->key; + newNode->firstChild = root; + newNode->lastChild = root; + newNode->numChildren = 1; + root->parent = newNode; + SplitNode( root ); + root = newNode; + } + + newNode = AllocNode(); + newNode->key = key; + newNode->object = object; + + for ( node = root; node->firstChild != NULL; node = child ) { + + if ( key > node->key ) { + node->key = key; + } + + // find the first child with a key larger equal to the key of the new node + for( child = node->firstChild; child->next; child = child->next ) { + if ( key <= child->key ) { + break; + } + } + + if ( child->object ) { + + if ( key <= child->key ) { + // insert new node before child + if ( child->prev ) { + child->prev->next = newNode; + } else { + node->firstChild = newNode; + } + newNode->prev = child->prev; + newNode->next = child; + child->prev = newNode; + } else { + // insert new node after child + if ( child->next ) { + child->next->prev = newNode; + } else { + node->lastChild = newNode; + } + newNode->prev = child; + newNode->next = child->next; + child->next = newNode; + } + + newNode->parent = node; + node->numChildren++; + +#ifdef BTREE_CHECK + CheckTree(); +#endif + + return newNode; + } + + // make sure the child has room to store another node + if ( child->numChildren >= maxChildrenPerNode ) { + SplitNode( child ); + if ( key <= child->prev->key ) { + child = child->prev; + } + } + } + + // we only end up here if the root node is empty + newNode->parent = root; + root->key = key; + root->firstChild = newNode; + root->lastChild = newNode; + root->numChildren++; + +#ifdef BTREE_CHECK + CheckTree(); +#endif + + return newNode; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Remove( idBTreeNode *node ) { + idBTreeNode *parent; + + assert( node->object != NULL ); + + // unlink the node from it's parent + if ( node->prev ) { + node->prev->next = node->next; + } else { + node->parent->firstChild = node->next; + } + if ( node->next ) { + node->next->prev = node->prev; + } else { + node->parent->lastChild = node->prev; + } + node->parent->numChildren--; + + // make sure there are no parent nodes with a single child + for ( parent = node->parent; parent != root && parent->numChildren <= 1; parent = parent->parent ) { + + if ( parent->next ) { + parent = MergeNodes( parent, parent->next ); + } else if ( parent->prev ) { + parent = MergeNodes( parent->prev, parent ); + } + + // a parent may not use a key higher than the key of it's last child + if ( parent->key > parent->lastChild->key ) { + parent->key = parent->lastChild->key; + } + + if ( parent->numChildren > maxChildrenPerNode ) { + SplitNode( parent ); + break; + } + } + for ( ; parent != NULL && parent->lastChild != NULL; parent = parent->parent ) { + // a parent may not use a key higher than the key of it's last child + if ( parent->key > parent->lastChild->key ) { + parent->key = parent->lastChild->key; + } + } + + // free the node + FreeNode( node ); + + // remove the root node if it has a single internal node as child + if ( root->numChildren == 1 && root->firstChild->object == NULL ) { + idBTreeNode *oldRoot = root; + root->firstChild->parent = NULL; + root = root->firstChild; + FreeNode( oldRoot ); + } + +#ifdef BTREE_CHECK + CheckTree(); +#endif +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode * idBTree::NodeFind( keyType key ) const { + idBTreeNode *node; + + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + node = node->next; + } + if ( node->object ) { + if ( node->key == key ) { + return node; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode * idBTree::NodeFindSmallestLargerEqual( keyType key ) const { + idBTreeNode *node; + + if ( root == NULL ) { + return NULL; + } + + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + node = node->next; + } + if ( node->object ) { + if ( node->key >= key ) { + return node; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode * idBTree::NodeFindLargestSmallerEqual( keyType key ) const { + idBTreeNode *node; + + if ( root == NULL ) { + return NULL; + } + + idBTreeNode * smaller = NULL; + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + smaller = node; + node = node->next; + } + if ( node->object ) { + if ( node->key <= key ) { + return node; + } else if ( smaller == NULL ) { + return NULL; + } else { + node = smaller; + if ( node->object ) { + return node; + } + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::Find( keyType key ) const { + idBTreeNode * node = NodeFind( key ); + if ( node == NULL ) { + return NULL; + } else { + return node->object; + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::FindSmallestLargerEqual( keyType key ) const { + idBTreeNode * node = NodeFindSmallestLargerEqual( key ); + if ( node == NULL ) { + return NULL; + } else { + return node->object; + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::FindLargestSmallerEqual( keyType key ) const { + idBTreeNode * node = NodeFindLargestSmallerEqual( key ); + if ( node == NULL ) { + return NULL; + } else { + return node->object; + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetRoot() const { + return root; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE int idBTree::GetNodeCount() const { + return nodeAllocator.GetAllocCount(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetNext( idBTreeNode *node ) const { + if ( node->firstChild ) { + return node->firstChild; + } else { + while( node && node->next == NULL ) { + node = node->parent; + } + return node; + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetNextLeaf( idBTreeNode *node ) const { + if ( node->firstChild ) { + while ( node->firstChild ) { + node = node->firstChild; + } + return node; + } else { + while( node && node->next == NULL ) { + node = node->parent; + } + if ( node ) { + node = node->next; + while ( node->firstChild ) { + node = node->firstChild; + } + return node; + } else { + return NULL; + } + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::AllocNode() { + idBTreeNode *node = nodeAllocator.Alloc(); + node->key = 0; + node->parent = NULL; + node->next = NULL; + node->prev = NULL; + node->numChildren = 0; + node->firstChild = NULL; + node->lastChild = NULL; + node->object = NULL; + return node; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::FreeNode( idBTreeNode *node ) { + nodeAllocator.Free( node ); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::SplitNode( idBTreeNode *node ) { + int i; + idBTreeNode *child, *newNode; + + // allocate a new node + newNode = AllocNode(); + newNode->parent = node->parent; + + // divide the children over the two nodes + child = node->firstChild; + child->parent = newNode; + for ( i = 3; i < node->numChildren; i += 2 ) { + child = child->next; + child->parent = newNode; + } + + newNode->key = child->key; + newNode->numChildren = node->numChildren / 2; + newNode->firstChild = node->firstChild; + newNode->lastChild = child; + + node->numChildren -= newNode->numChildren; + node->firstChild = child->next; + + child->next->prev = NULL; + child->next = NULL; + + // add the new child to the parent before the split node + assert( node->parent->numChildren < maxChildrenPerNode ); + + if ( node->prev ) { + node->prev->next = newNode; + } else { + node->parent->firstChild = newNode; + } + newNode->prev = node->prev; + newNode->next = node; + node->prev = newNode; + + node->parent->numChildren++; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::MergeNodes( idBTreeNode *node1, idBTreeNode *node2 ) { + idBTreeNode *child; + + assert( node1->parent == node2->parent ); + assert( node1->next == node2 && node2->prev == node1 ); + assert( node1->object == NULL && node2->object == NULL ); + assert( node1->numChildren >= 1 && node2->numChildren >= 1 ); + + for ( child = node1->firstChild; child->next; child = child->next ) { + child->parent = node2; + } + child->parent = node2; + child->next = node2->firstChild; + node2->firstChild->prev = child; + node2->firstChild = node1->firstChild; + node2->numChildren += node1->numChildren; + + // unlink the first node from the parent + if ( node1->prev ) { + node1->prev->next = node2; + } else { + node1->parent->firstChild = node2; + } + node2->prev = node1->prev; + node2->parent->numChildren--; + + FreeNode( node1 ); + + return node2; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::CheckTree_r( idBTreeNode *node, int &numNodes ) const { + int numChildren; + idBTreeNode *child; + + numNodes++; + + // the root node may have zero children and leaf nodes always have zero children, all other nodes should have at least 2 and at most maxChildrenPerNode children + assert( ( node == root ) || ( node->object != NULL && node->numChildren == 0 ) || ( node->numChildren >= 2 && node->numChildren <= maxChildrenPerNode ) ); + // the key of a node may never be larger than the key of it's last child + assert( ( node->lastChild == NULL ) || ( node->key <= node->lastChild->key ) ); + + numChildren = 0; + for ( child = node->firstChild; child; child = child->next ) { + numChildren++; + // make sure the children are properly linked + if ( child->prev == NULL ) { + assert( node->firstChild == child ); + } else { + assert( child->prev->next == child ); + } + if ( child->next == NULL ) { + assert( node->lastChild == child ); + } else { + assert( child->next->prev == child ); + } + // recurse down the tree + CheckTree_r( child, numNodes ); + } + // the number of children should equal the number of linked children + assert( numChildren == node->numChildren ); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::CheckTree() const { + int numNodes = 0; + idBTreeNode *node, *lastNode; + + CheckTree_r( root, numNodes ); + + // the number of nodes in the tree should equal the number of allocated nodes + assert( numNodes == nodeAllocator.GetAllocCount() ); + + // all the leaf nodes should be ordered + lastNode = GetNextLeaf( GetRoot() ); + if ( lastNode ) { + for ( node = GetNextLeaf( lastNode ); node; lastNode = node, node = GetNextLeaf( node ) ) { + assert( lastNode->key <= node->key ); + } + } +} + +#endif /* !__BTREE_H__ */ diff --git a/neo/idlib/containers/BinSearch.h b/neo/idlib/containers/BinSearch.h new file mode 100644 index 00000000..3c0abfbe --- /dev/null +++ b/neo/idlib/containers/BinSearch.h @@ -0,0 +1,138 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BINSEARCH_H__ +#define __BINSEARCH_H__ + +/* +=============================================================================== + + Binary Search templates + + The array elements have to be ordered in increasing order. + +=============================================================================== +*/ + +/* +==================== +idBinSearch_GreaterEqual + + Finds the last array element which is smaller than the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_Less( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] < value ) { + offset += mid; + } + len -= mid; + } + return offset; +} + +/* +==================== +idBinSearch_GreaterEqual + + Finds the last array element which is smaller than or equal to the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_LessEqual( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] <= value ) { + offset += mid; + } + len -= mid; + } + return offset; +} + +/* +==================== +idBinSearch_Greater + + Finds the first array element which is greater than the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_Greater( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + int res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] > value ) { + res = 0; + } else { + offset += mid; + res = 1; + } + len -= mid; + } + return offset+res; +} + +/* +==================== +idBinSearch_GreaterEqual + + Finds the first array element which is greater than or equal to the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_GreaterEqual( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + int res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] >= value ) { + res = 0; + } else { + offset += mid; + res = 1; + } + len -= mid; + } + return offset+res; +} + +#endif /* !__BINSEARCH_H__ */ diff --git a/neo/idlib/containers/HashIndex.cpp b/neo/idlib/containers/HashIndex.cpp new file mode 100644 index 00000000..ebe7287f --- /dev/null +++ b/neo/idlib/containers/HashIndex.cpp @@ -0,0 +1,155 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +int idHashIndex::INVALID_INDEX[1] = { -1 }; + +/* +================ +idHashIndex::Init +================ +*/ +void idHashIndex::Init( const int initialHashSize, const int initialIndexSize ) { + assert( idMath::IsPowerOfTwo( initialHashSize ) ); + + hashSize = initialHashSize; + hash = INVALID_INDEX; + indexSize = initialIndexSize; + indexChain = INVALID_INDEX; + granularity = DEFAULT_HASH_GRANULARITY; + hashMask = hashSize - 1; + lookupMask = 0; +} + +/* +================ +idHashIndex::Allocate +================ +*/ +void idHashIndex::Allocate( const int newHashSize, const int newIndexSize ) { + assert( idMath::IsPowerOfTwo( newHashSize ) ); + + Free(); + hashSize = newHashSize; + hash = new (TAG_IDLIB_HASH) int[hashSize]; + memset( hash, 0xff, hashSize * sizeof( hash[0] ) ); + indexSize = newIndexSize; + indexChain = new (TAG_IDLIB_HASH) int[indexSize]; + memset( indexChain, 0xff, indexSize * sizeof( indexChain[0] ) ); + hashMask = hashSize - 1; + lookupMask = -1; +} + +/* +================ +idHashIndex::Free +================ +*/ +void idHashIndex::Free() { + if ( hash != INVALID_INDEX ) { + delete[] hash; + hash = INVALID_INDEX; + } + if ( indexChain != INVALID_INDEX ) { + delete[] indexChain; + indexChain = INVALID_INDEX; + } + lookupMask = 0; +} + +/* +================ +idHashIndex::ResizeIndex +================ +*/ +void idHashIndex::ResizeIndex( const int newIndexSize ) { + int *oldIndexChain, mod, newSize; + + if ( newIndexSize <= indexSize ) { + return; + } + + mod = newIndexSize % granularity; + if ( !mod ) { + newSize = newIndexSize; + } else { + newSize = newIndexSize + granularity - mod; + } + + if ( indexChain == INVALID_INDEX ) { + indexSize = newSize; + return; + } + + oldIndexChain = indexChain; + indexChain = new (TAG_IDLIB_HASH) int[newSize]; + memcpy( indexChain, oldIndexChain, indexSize * sizeof(int) ); + memset( indexChain + indexSize, 0xff, (newSize - indexSize) * sizeof(int) ); + delete[] oldIndexChain; + indexSize = newSize; +} + +/* +================ +idHashIndex::GetSpread +================ +*/ +int idHashIndex::GetSpread() const { + int i, index, totalItems, *numHashItems, average, error, e; + + if ( hash == INVALID_INDEX ) { + return 100; + } + + totalItems = 0; + numHashItems = new (TAG_IDLIB_HASH) int[hashSize]; + for ( i = 0; i < hashSize; i++ ) { + numHashItems[i] = 0; + for ( index = hash[i]; index >= 0; index = indexChain[index] ) { + numHashItems[i]++; + } + totalItems += numHashItems[i]; + } + // if no items in hash + if ( totalItems <= 1 ) { + delete[] numHashItems; + return 100; + } + average = totalItems / hashSize; + error = 0; + for ( i = 0; i < hashSize; i++ ) { + e = abs( numHashItems[i] - average ); + if ( e > 1 ) { + error += e - 1; + } + } + delete[] numHashItems; + return 100 - (error * 100 / totalItems); +} diff --git a/neo/idlib/containers/HashIndex.h b/neo/idlib/containers/HashIndex.h new file mode 100644 index 00000000..10cb68c5 --- /dev/null +++ b/neo/idlib/containers/HashIndex.h @@ -0,0 +1,422 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __HASHINDEX_H__ +#define __HASHINDEX_H__ + +/* +=============================================================================== + + Fast hash table for indexes and arrays. + Does not allocate memory until the first key/index pair is added. + +=============================================================================== +*/ + +#define DEFAULT_HASH_SIZE 1024 +#define DEFAULT_HASH_GRANULARITY 1024 + +class idHashIndex { +public: + static const int NULL_INDEX = -1; + idHashIndex(); + idHashIndex( const int initialHashSize, const int initialIndexSize ); + ~idHashIndex(); + + // returns total size of allocated memory + size_t Allocated() const; + // returns total size of allocated memory including size of hash index type + size_t Size() const; + + idHashIndex & operator=( const idHashIndex &other ); + // add an index to the hash, assumes the index has not yet been added to the hash + void Add( const int key, const int index ); + // remove an index from the hash + void Remove( const int key, const int index ); + // get the first index from the hash, returns -1 if empty hash entry + int First( const int key ) const; + // get the next index from the hash, returns -1 if at the end of the hash chain + int Next( const int index ) const; + + // For porting purposes... + int GetFirst( const int key ) const { return First( key ); } + int GetNext( const int index ) const { return Next( index ); } + + // insert an entry into the index and add it to the hash, increasing all indexes >= index + void InsertIndex( const int key, const int index ); + // remove an entry from the index and remove it from the hash, decreasing all indexes >= index + void RemoveIndex( const int key, const int index ); + // clear the hash + void Clear(); + // clear and resize + void Clear( const int newHashSize, const int newIndexSize ); + // free allocated memory + void Free(); + // get size of hash table + int GetHashSize() const; + // get size of the index + int GetIndexSize() const; + // set granularity + void SetGranularity( const int newGranularity ); + // force resizing the index, current hash table stays intact + void ResizeIndex( const int newIndexSize ); + // returns number in the range [0-100] representing the spread over the hash table + int GetSpread() const; + // returns a key for a string + int GenerateKey( const char *string, bool caseSensitive = true ) const; + // returns a key for a vector + int GenerateKey( const idVec3 &v ) const; + // returns a key for two integers + int GenerateKey( const int n1, const int n2 ) const; + // returns a key for a single integer + int GenerateKey( const int n ) const; + +private: + int hashSize; + int * hash; + int indexSize; + int * indexChain; + int granularity; + int hashMask; + int lookupMask; + + static int INVALID_INDEX[1]; + + void Init( const int initialHashSize, const int initialIndexSize ); + void Allocate( const int newHashSize, const int newIndexSize ); +}; + +/* +================ +idHashIndex::idHashIndex +================ +*/ +ID_INLINE idHashIndex::idHashIndex() { + Init( DEFAULT_HASH_SIZE, DEFAULT_HASH_SIZE ); +} + +/* +================ +idHashIndex::idHashIndex +================ +*/ +ID_INLINE idHashIndex::idHashIndex( const int initialHashSize, const int initialIndexSize ) { + Init( initialHashSize, initialIndexSize ); +} + +/* +================ +idHashIndex::~idHashIndex +================ +*/ +ID_INLINE idHashIndex::~idHashIndex() { + Free(); +} + +/* +================ +idHashIndex::Allocated +================ +*/ +ID_INLINE size_t idHashIndex::Allocated() const { + return hashSize * sizeof( int ) + indexSize * sizeof( int ); +} + +/* +================ +idHashIndex::Size +================ +*/ +ID_INLINE size_t idHashIndex::Size() const { + return sizeof( *this ) + Allocated(); +} + +/* +================ +idHashIndex::operator= +================ +*/ +ID_INLINE idHashIndex &idHashIndex::operator=( const idHashIndex &other ) { + granularity = other.granularity; + hashMask = other.hashMask; + lookupMask = other.lookupMask; + + if ( other.lookupMask == 0 ) { + hashSize = other.hashSize; + indexSize = other.indexSize; + Free(); + } + else { + if ( other.hashSize != hashSize || hash == INVALID_INDEX ) { + if ( hash != INVALID_INDEX ) { + delete[] hash; + } + hashSize = other.hashSize; + hash = new (TAG_IDLIB_HASH) int[hashSize]; + } + if ( other.indexSize != indexSize || indexChain == INVALID_INDEX ) { + if ( indexChain != INVALID_INDEX ) { + delete[] indexChain; + } + indexSize = other.indexSize; + indexChain = new (TAG_IDLIB_HASH) int[indexSize]; + } + memcpy( hash, other.hash, hashSize * sizeof( hash[0] ) ); + memcpy( indexChain, other.indexChain, indexSize * sizeof( indexChain[0] ) ); + } + + return *this; +} + +/* +================ +idHashIndex::Add +================ +*/ +ID_INLINE void idHashIndex::Add( const int key, const int index ) { + int h; + + assert( index >= 0 ); + if ( hash == INVALID_INDEX ) { + Allocate( hashSize, index >= indexSize ? index + 1 : indexSize ); + } + else if ( index >= indexSize ) { + ResizeIndex( index + 1 ); + } + h = key & hashMask; + indexChain[index] = hash[h]; + hash[h] = index; +} + +/* +================ +idHashIndex::Remove +================ +*/ +ID_INLINE void idHashIndex::Remove( const int key, const int index ) { + int k = key & hashMask; + + if ( hash == INVALID_INDEX ) { + return; + } + if ( hash[k] == index ) { + hash[k] = indexChain[index]; + } + else { + for ( int i = hash[k]; i != -1; i = indexChain[i] ) { + if ( indexChain[i] == index ) { + indexChain[i] = indexChain[index]; + break; + } + } + } + indexChain[index] = -1; +} + +/* +================ +idHashIndex::First +================ +*/ +ID_INLINE int idHashIndex::First( const int key ) const { + return hash[key & hashMask & lookupMask]; +} + +/* +================ +idHashIndex::Next +================ +*/ +ID_INLINE int idHashIndex::Next( const int index ) const { + assert( index >= 0 && index < indexSize ); + return indexChain[index & lookupMask]; +} + +/* +================ +idHashIndex::InsertIndex +================ +*/ +ID_INLINE void idHashIndex::InsertIndex( const int key, const int index ) { + int i, max; + + if ( hash != INVALID_INDEX ) { + max = index; + for ( i = 0; i < hashSize; i++ ) { + if ( hash[i] >= index ) { + hash[i]++; + if ( hash[i] > max ) { + max = hash[i]; + } + } + } + for ( i = 0; i < indexSize; i++ ) { + if ( indexChain[i] >= index ) { + indexChain[i]++; + if ( indexChain[i] > max ) { + max = indexChain[i]; + } + } + } + if ( max >= indexSize ) { + ResizeIndex( max + 1 ); + } + for ( i = max; i > index; i-- ) { + indexChain[i] = indexChain[i-1]; + } + indexChain[index] = -1; + } + Add( key, index ); +} + +/* +================ +idHashIndex::RemoveIndex +================ +*/ +ID_INLINE void idHashIndex::RemoveIndex( const int key, const int index ) { + int i, max; + + Remove( key, index ); + if ( hash != INVALID_INDEX ) { + max = index; + for ( i = 0; i < hashSize; i++ ) { + if ( hash[i] >= index ) { + if ( hash[i] > max ) { + max = hash[i]; + } + hash[i]--; + } + } + for ( i = 0; i < indexSize; i++ ) { + if ( indexChain[i] >= index ) { + if ( indexChain[i] > max ) { + max = indexChain[i]; + } + indexChain[i]--; + } + } + for ( i = index; i < max; i++ ) { + indexChain[i] = indexChain[i+1]; + } + indexChain[max] = -1; + } +} + +/* +================ +idHashIndex::Clear +================ +*/ +ID_INLINE void idHashIndex::Clear() { + // only clear the hash table because clearing the indexChain is not really needed + if ( hash != INVALID_INDEX ) { + memset( hash, 0xff, hashSize * sizeof( hash[0] ) ); + } +} + +/* +================ +idHashIndex::Clear +================ +*/ +ID_INLINE void idHashIndex::Clear( const int newHashSize, const int newIndexSize ) { + Free(); + hashSize = newHashSize; + indexSize = newIndexSize; +} + +/* +================ +idHashIndex::GetHashSize +================ +*/ +ID_INLINE int idHashIndex::GetHashSize() const { + return hashSize; +} + +/* +================ +idHashIndex::GetIndexSize +================ +*/ +ID_INLINE int idHashIndex::GetIndexSize() const { + return indexSize; +} + +/* +================ +idHashIndex::SetGranularity +================ +*/ +ID_INLINE void idHashIndex::SetGranularity( const int newGranularity ) { + assert( newGranularity > 0 ); + granularity = newGranularity; +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const char *string, bool caseSensitive ) const { + if ( caseSensitive ) { + return ( idStr::Hash( string ) & hashMask ); + } else { + return ( idStr::IHash( string ) & hashMask ); + } +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const idVec3 &v ) const { + return ( (((int) v[0]) + ((int) v[1]) + ((int) v[2])) & hashMask ); +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const int n1, const int n2 ) const { + return ( ( n1 + n2 ) & hashMask ); +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const int n ) const { + return n & hashMask; +} + +#endif /* !__HASHINDEX_H__ */ diff --git a/neo/idlib/containers/HashTable.h b/neo/idlib/containers/HashTable.h new file mode 100644 index 00000000..c41f769e --- /dev/null +++ b/neo/idlib/containers/HashTable.h @@ -0,0 +1,897 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __HASHTABLE_H__ +#define __HASHTABLE_H__ + +/* +================================================================================================ +idHashNodeT is a generic node for a HashTable. It is specialized by the +StringHashNode and CStringHashNode template classes. +================================================================================================ +*/ +template< typename _key_, class _value_ > +class idHashNodeT { +public: + idHashNodeT() + : next( NULL ) { + } + + idHashNodeT( const _key_ & key, const _value_ & value, idHashNodeT * next ) + : key( key ), + value( value ), + next( next ) { + } + + static int GetHash( const _key_ & key, const int tableMask ) { + return key & tableMask; + } + + static int Compare( const _key_ & key1, const _key_ & key2 ) { + if ( key1 < key2 ) { + return -1; + } else if ( key1 > key2 ) { + return 1; + } + return 0; + } + +public: + _key_ key; + _value_ value; + idHashNodeT< _key_, _value_ > * next; +}; + +/* +================================================ +idHashNodeT is a HashNode that provides for partial +specialization for the HashTable, allowing the String class's Cmp function to be used +for inserting values in sorted order. +================================================ +*/ +template< class _value_ > +class idHashNodeT< idStr, _value_ > { +public: + idHashNodeT( const idStr & key, const _value_ & value, idHashNodeT * next ) + : key( key ), + value( value ), + next( next ) { + } + + static int GetHash( const idStr & key, const int tableMask ) { + return ( idStr::Hash( key ) & tableMask ); + } + + static int Compare( const idStr & key1, const idStr & key2 ) { + return idStr::Icmp( key1, key2 ); + } + +public: + idStr key; + _value_ value; + idHashNodeT< idStr, _value_ > * next; +}; + +/* +================================================ +idHashNodeT is a HashNode that provides for a partial specialization +for the HashTable, allowing the String class's Cmp function to +be used for inserting values in sorted order. It also ensures that a copy of the the key is +stored in a String (to more closely model the original implementation of the HashTable). +================================================ +*/ +template< class _value_ > +class idHashNodeT< const char*, _value_ > { +public: + idHashNodeT( const char* const & key, const _value_ & value, idHashNodeT * next ) + : key( key ), + value( value ), + next( next ) { + } + + static int GetHash( const char* const & key, const int tableMask ) { + return ( idStr::Hash( key ) & tableMask ); + } + + static int Compare( const char* const & key1, const char* const & key2 ) { + return idStr::Icmp( key1, key2 ); + } + +public: + idStr key; // char * keys must still get stored in an idStr + _value_ value; + idHashNodeT< const char *, _value_ > * next; +}; + +/* +================================================ +idHashTableT is a general implementation of a hash table data type. It is +slower than the HashIndex, but it can also be used for LinkedLists and other data structures, +rather than just indexes and arrays. + +It uses an arbitrary key type. For String keys, use the StringHashTable template +specialization. +================================================ +*/ +template< typename _key_, class _value_ > +class idHashTableT { +public: + idHashTableT( const int tableSize = 256 ); + idHashTableT( const idHashTableT & other ); + ~idHashTableT(); + + size_t Allocated() const; + size_t Size() const; + + _value_ & Set( const _key_ & key, const _value_ & value ); + + bool Get( const _key_ & key, _value_ ** value = NULL ); + bool Get( const _key_ & key, const _value_ ** value = NULL ) const; + + bool Remove( const _key_ & key ); + + void Clear(); + void DeleteContents(); + + int Num() const; + _value_ * GetIndex( const int index ) const; + bool GetIndexKey( const int index, _key_ & key ) const; + + int GetSpread() const; + + idHashTableT & operator=( const idHashTableT & other ); + +protected: + void Copy( const idHashTableT & other ); + +private: + typedef idHashNodeT< _key_, _value_ > hashnode_t; + + hashnode_t ** heads; + + int tableSize; + int numEntries; + int tableSizeMask; +}; + +/* +======================== +idHashTableT<_key_,_value_>::idHashTableT +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE idHashTableT<_key_,_value_>::idHashTableT( const int tableSize ) { + assert( idMath::IsPowerOfTwo( tableSize ) ); + + this->tableSize = tableSize; + + heads = new (TAG_IDLIB_HASH) hashnode_t *[ tableSize ]; + memset( heads, 0, sizeof( hashnode_t * ) * tableSize ); + + numEntries = 0; + tableSizeMask = tableSize - 1; +} + +/* +======================== +idHashTableT<_key_,_value_>::idHashTableT +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE idHashTableT<_key_,_value_>::idHashTableT( const idHashTableT & other ) { + Copy( other ); +} + +/* +======================== +idHashTableT<_key_,_value_>::~idHashTableT +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE idHashTableT<_key_,_value_>::~idHashTableT() { + Clear(); + delete [] heads; + heads = NULL; + tableSize = 0; + tableSizeMask = 0; + numEntries = 0; +} + +/* +======================== +idHashTableT<_key_,_value_>::Allocated +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE size_t idHashTableT<_key_,_value_>::Allocated() const { + return sizeof( heads ) * tableSize + sizeof( hashnode_t* ) * numEntries; +} + +/* +======================== +idHashTableT<_key_,_value_>::Size +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE size_t idHashTableT<_key_,_value_>::Size() const { + return sizeof( idHashTableT ) + sizeof( heads )* tableSize + sizeof( hashnode_t* ) * numEntries; +} + +/* +======================== +idHashTableT<_key_,_value_>::Set +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE _value_ & idHashTableT<_key_,_value_>::Set( const _key_ & key, const _value_ & value ) { + // insert sorted + int hash = hashnode_t::GetHash( key, tableSizeMask ); + hashnode_t ** nextPtr = &(heads[ hash ] ); + hashnode_t * node = * nextPtr; + for ( ; + node != NULL; + nextPtr = &(node->next), node = *nextPtr ) { + int s = node->Compare( node->key, key ); + if ( s == 0 ) { + // return existing hashed item + node->value = value; + return node->value; + } + if ( s > 0 ) { + break; + } + } + + numEntries++; + + *nextPtr = new (TAG_IDLIB_HASH) hashnode_t( key, value, heads[ hash ] ); + (*nextPtr)->next = node; + return (*nextPtr)->value; +} + +/* +======================== +idHashTableT<_key_,_value_>::Get +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE bool idHashTableT<_key_,_value_>::Get( const _key_ & key, _value_ ** value ) { + int hash = hashnode_t::GetHash( key, tableSizeMask ); + hashnode_t * node = heads[ hash ]; + for ( ; node != NULL; node = node->next ) { + int s = node->Compare( node->key, key ); + if ( s == 0 ) { + if ( value ) { + *value = &node->value; + } + return true; + } + if ( s > 0 ) { + break; + } + } + if ( value ) { + *value = NULL; + } + return false; +} + +/* +======================== +idHashTableT<_key_,_value_>::Get +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE bool idHashTableT<_key_,_value_>::Get( const _key_ & key, const _value_ ** value ) const { + int hash = hashnode_t::GetHash( key, tableSizeMask ); + hashnode_t * node = heads[ hash ]; + for ( ; node != NULL; node = node->next ) { + int s = node->Compare( node->key, key ); + if ( s == 0 ) { + if ( value ) { + *value = &node->value; + } + return true; + } + if ( s > 0 ) { + break; + } + } + if ( value ) { + *value = NULL; + } + return false; +} + +/* +======================== +idHashTableT<_key_,_value_>::GetIndex +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE _value_ * idHashTableT<_key_,_value_>::GetIndex( const int index ) const { + if ( index < 0 || index > numEntries ) { + assert( 0 ); + return NULL; + } + + int count = 0; + for ( int i = 0; i < tableSize; i++ ) { + for ( hashnode_t * node = heads[ i ]; node != NULL; node = node->next ) { + if ( count == index ) { + return &node->value; + } + count++; + } + } + return NULL; +} + +/* +======================== +idHashTableT<_key_,_value_>::GetIndexKey +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE bool idHashTableT<_key_,_value_>::GetIndexKey( const int index, _key_ & key ) const { + if ( index < 0 || index > numEntries ) { + assert( 0 ); + return false; + } + + int count = 0; + for ( int i = 0; i < tableSize; i++ ) { + for ( hashnode_t * node = heads[ i ]; node != NULL; node = node->next ) { + if ( count == index ) { + key = node->key; + return true; + } + count++; + } + } + return false; +} + +/* +======================== +idHashTableT<_key_,_value_>::Remove +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE bool idHashTableT<_key_,_value_>::Remove( const _key_ & key ) { + int hash = hashnode_t::GetHash( key, tableSizeMask ); + hashnode_t ** head = &heads[ hash ]; + if ( *head ) { + hashnode_t * prev = NULL; + hashnode_t * node = *head; + for ( ; node != NULL; prev = node, node = node->next ) { + if ( node->key == key ) { + if ( prev ) { + prev->next = node->next; + } else { + *head = node->next; + } + + delete node; + numEntries--; + return true; + } + } + } + return false; +} + +/* +======================== +idHashTableT<_key_,_value_>::Clear +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE void idHashTableT<_key_,_value_>::Clear() { + for ( int i = 0; i < tableSize; i++ ) { + hashnode_t * next = heads[ i ]; + while ( next != NULL ) { + hashnode_t * node = next; + next = next->next; + delete node; + } + heads[ i ] = NULL; + } + numEntries = 0; +} + +/* +======================== +idHashTableT<_key_,_value_>::DeleteContents +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE void idHashTableT<_key_,_value_>::DeleteContents() { + for ( int i = 0; i < tableSize; i++ ) { + hashnode_t * next = heads[ i ]; + while ( next != NULL ) { + hashnode_t * node = next; + next = next->next; + delete node->value; + delete node; + } + heads[ i ] = NULL; + } + numEntries = 0; +} + +/* +======================== +idHashTableT<_key_,_value_>::Num +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE int idHashTableT<_key_,_value_>::Num() const { + return numEntries; +} + +/* +======================== +idHashTableT<_key_,_value_>::GetSpread +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE int idHashTableT<_key_,_value_>::GetSpread() const { + if ( !numEntries ) { + return 100; + } + + int average = numEntries / tableSize; + int error = 0; + for ( int i = 0; i < tableSize; i++ ) { + int numItems = 0; + for ( hashnode_t * node = heads[ i ]; node != NULL; node = node->next ) { + numItems++; + } + int e = abs( numItems - average ); + if ( e > 1 ) { + error += e - 1; + } + } + return 100 - ( error * 100 / numEntries ); +} + +/* +======================== +idHashTableT<_key_,_value_>::operator= +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE idHashTableT< _key_, _value_ > & idHashTableT<_key_,_value_>::operator=( const idHashTableT & other ) { + Copy( other ); + return *this; +} + +/* +======================== +idHashTableT<_key_,_value_>::Copy +======================== +*/ +template< typename _key_, class _value_ > +ID_INLINE void idHashTableT<_key_,_value_>::Copy( const idHashTableT & other ) { + if ( &other == this ) { + return; + } + assert( other.tableSize > 0 ); + + tableSize = other.tableSize; + heads = new (TAG_IDLIB_HASH) hashnode_t *[ tableSize ]; + numEntries = other.numEntries; + tableSizeMask = other.tableSizeMask; + + for ( int i = 0; i < tableSize; i++ ) { + if ( !other.heads[ i ] ) { + heads[ i ] = NULL; + continue; + } + hashnode_t ** prev = & heads[ i ]; + for ( hashnode_t * node = other.heads[ i ]; node != NULL; node = node->next ) { + *prev = new (TAG_IDLIB_HASH) hashnode_t( node->key, node->value, NULL ); + prev = &( *prev )->next; + } + } +} + +/* +=============================================================================== + + General hash table. Slower than idHashIndex but it can also be used for + linked lists and other data structures than just indexes or arrays. + +=============================================================================== +*/ + +template< class Type > +class idHashTable { +public: + idHashTable( int newtablesize = 256 ); + idHashTable( const idHashTable &map ); + ~idHashTable(); + + // returns total size of allocated memory + size_t Allocated() const; + // returns total size of allocated memory including size of hash table type + size_t Size() const; + + void Set( const char *key, Type &value ); + bool Get( const char *key, Type **value = NULL ) const; + bool Remove( const char *key ); + + void Clear(); + void DeleteContents(); + + // the entire contents can be itterated over, but note that the + // exact index for a given element may change when new elements are added + int Num() const; + Type * GetIndex( int index ) const; + + int GetSpread() const; + +private: + struct hashnode_s { + idStr key; + Type value; + hashnode_s *next; + + hashnode_s( const idStr &k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + hashnode_s( const char *k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + }; + + hashnode_s ** heads; + + int tablesize; + int numentries; + int tablesizemask; + + int GetHash( const char *key ) const; +}; + +/* +================ +idHashTable::idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::idHashTable( int newtablesize ) { + + assert( idMath::IsPowerOfTwo( newtablesize ) ); + + tablesize = newtablesize; + assert( tablesize > 0 ); + + heads = new (TAG_IDLIB_HASH) hashnode_s *[ tablesize ]; + memset( heads, 0, sizeof( *heads ) * tablesize ); + + numentries = 0; + + tablesizemask = tablesize - 1; +} + +/* +================ +idHashTable::idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::idHashTable( const idHashTable &map ) { + int i; + hashnode_s *node; + hashnode_s **prev; + + assert( map.tablesize > 0 ); + + tablesize = map.tablesize; + heads = new (TAG_IDLIB_HASH) hashnode_s *[ tablesize ]; + numentries = map.numentries; + tablesizemask = map.tablesizemask; + + for( i = 0; i < tablesize; i++ ) { + if ( !map.heads[ i ] ) { + heads[ i ] = NULL; + continue; + } + + prev = &heads[ i ]; + for( node = map.heads[ i ]; node != NULL; node = node->next ) { + *prev = new (TAG_IDLIB_HASH) hashnode_s( node->key, node->value, NULL ); + prev = &( *prev )->next; + } + } +} + +/* +================ +idHashTable::~idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::~idHashTable() { + Clear(); + delete[] heads; +} + +/* +================ +idHashTable::Allocated +================ +*/ +template< class Type > +ID_INLINE size_t idHashTable::Allocated() const { + return sizeof( heads ) * tablesize + sizeof( *heads ) * numentries; +} + +/* +================ +idHashTable::Size +================ +*/ +template< class Type > +ID_INLINE size_t idHashTable::Size() const { + return sizeof( idHashTable ) + sizeof( heads ) * tablesize + sizeof( *heads ) * numentries; +} + +/* +================ +idHashTable::GetHash +================ +*/ +template< class Type > +ID_INLINE int idHashTable::GetHash( const char *key ) const { + return ( idStr::Hash( key ) & tablesizemask ); +} + +/* +================ +idHashTable::Set +================ +*/ +template< class Type > +ID_INLINE void idHashTable::Set( const char *key, Type &value ) { + hashnode_s *node, **nextPtr; + int hash, s; + + hash = GetHash( key ); + for( nextPtr = &(heads[hash]), node = *nextPtr; node != NULL; nextPtr = &(node->next), node = *nextPtr ) { + s = node->key.Cmp( key ); + if ( s == 0 ) { + node->value = value; + return; + } + if ( s > 0 ) { + break; + } + } + + numentries++; + + *nextPtr = new (TAG_IDLIB_HASH) hashnode_s( key, value, heads[ hash ] ); + (*nextPtr)->next = node; +} + +/* +================ +idHashTable::Get +================ +*/ +template< class Type > +ID_INLINE bool idHashTable::Get( const char *key, Type **value ) const { + hashnode_s *node; + int hash, s; + + hash = GetHash( key ); + for( node = heads[ hash ]; node != NULL; node = node->next ) { + s = node->key.Cmp( key ); + if ( s == 0 ) { + if ( value ) { + *value = &node->value; + } + return true; + } + if ( s > 0 ) { + break; + } + } + + if ( value ) { + *value = NULL; + } + + return false; +} + +/* +================ +idHashTable::GetIndex + +the entire contents can be itterated over, but note that the +exact index for a given element may change when new elements are added +================ +*/ +template< class Type > +ID_INLINE Type *idHashTable::GetIndex( int index ) const { + hashnode_s *node; + int count; + int i; + + if ( ( index < 0 ) || ( index > numentries ) ) { + assert( 0 ); + return NULL; + } + + count = 0; + for( i = 0; i < tablesize; i++ ) { + for( node = heads[ i ]; node != NULL; node = node->next ) { + if ( count == index ) { + return &node->value; + } + count++; + } + } + + return NULL; +} + +/* +================ +idHashTable::Remove +================ +*/ +template< class Type > +ID_INLINE bool idHashTable::Remove( const char *key ) { + hashnode_s **head; + hashnode_s *node; + hashnode_s *prev; + int hash; + + hash = GetHash( key ); + head = &heads[ hash ]; + if ( *head ) { + for( prev = NULL, node = *head; node != NULL; prev = node, node = node->next ) { + if ( node->key == key ) { + if ( prev ) { + prev->next = node->next; + } else { + *head = node->next; + } + + delete node; + numentries--; + return true; + } + } + } + + return false; +} + +/* +================ +idHashTable::Clear +================ +*/ +template< class Type > +ID_INLINE void idHashTable::Clear() { + int i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < tablesize; i++ ) { + next = heads[ i ]; + while( next != NULL ) { + node = next; + next = next->next; + delete node; + } + + heads[ i ] = NULL; + } + + numentries = 0; +} + +/* +================ +idHashTable::DeleteContents +================ +*/ +template< class Type > +ID_INLINE void idHashTable::DeleteContents() { + int i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < tablesize; i++ ) { + next = heads[ i ]; + while( next != NULL ) { + node = next; + next = next->next; + delete node->value; + delete node; + } + + heads[ i ] = NULL; + } + + numentries = 0; +} + +/* +================ +idHashTable::Num +================ +*/ +template< class Type > +ID_INLINE int idHashTable::Num() const { + return numentries; +} + +#if defined(ID_TYPEINFO) +#define __GNUC__ 99 +#endif + +#if !defined(__GNUC__) || __GNUC__ < 4 +/* +================ +idHashTable::GetSpread +================ +*/ +template< class Type > +int idHashTable::GetSpread() const { + int i, average, error, e; + hashnode_s *node; + + // if no items in hash + if ( !numentries ) { + return 100; + } + average = numentries / tablesize; + error = 0; + for ( i = 0; i < tablesize; i++ ) { + numItems = 0; + for( node = heads[ i ]; node != NULL; node = node->next ) { + numItems++; + } + e = abs( numItems - average ); + if ( e > 1 ) { + error += e - 1; + } + } + return 100 - (error * 100 / numentries); +} +#endif + +#if defined(ID_TYPEINFO) +#undef __GNUC__ +#endif + +#endif /* !__HASHTABLE_H__ */ diff --git a/neo/idlib/containers/Hierarchy.h b/neo/idlib/containers/Hierarchy.h new file mode 100644 index 00000000..9ac7ee78 --- /dev/null +++ b/neo/idlib/containers/Hierarchy.h @@ -0,0 +1,362 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __HIERARCHY_H__ +#define __HIERARCHY_H__ + +/* +============================================================================== + + idHierarchy + +============================================================================== +*/ + +template< class type > +class idHierarchy { +public: + + idHierarchy(); + ~idHierarchy(); + + void SetOwner( type *object ); + type * Owner() const; + void ParentTo( idHierarchy &node ); + void MakeSiblingAfter( idHierarchy &node ); + bool ParentedBy( const idHierarchy &node ) const; + void RemoveFromParent(); + void RemoveFromHierarchy(); + + type * GetParent() const; // parent of this node + type * GetChild() const; // first child of this node + type * GetSibling() const; // next node with the same parent + type * GetPriorSibling() const; // previous node with the same parent + type * GetNext() const; // goes through all nodes of the hierarchy + type * GetNextLeaf() const; // goes through all leaf nodes of the hierarchy + +private: + idHierarchy * parent; + idHierarchy * sibling; + idHierarchy * child; + type * owner; + + idHierarchy *GetPriorSiblingNode() const; // previous node with the same parent +}; + +/* +================ +idHierarchy::idHierarchy +================ +*/ +template< class type > +idHierarchy::idHierarchy() { + owner = NULL; + parent = NULL; + sibling = NULL; + child = NULL; +} + +/* +================ +idHierarchy::~idHierarchy +================ +*/ +template< class type > +idHierarchy::~idHierarchy() { + RemoveFromHierarchy(); +} + +/* +================ +idHierarchy::Owner + +Gets the object that is associated with this node. +================ +*/ +template< class type > +type *idHierarchy::Owner() const { + return owner; +} + +/* +================ +idHierarchy::SetOwner + +Sets the object that this node is associated with. +================ +*/ +template< class type > +void idHierarchy::SetOwner( type *object ) { + owner = object; +} + +/* +================ +idHierarchy::ParentedBy +================ +*/ +template< class type > +bool idHierarchy::ParentedBy( const idHierarchy &node ) const { + if ( parent == &node ) { + return true; + } else if ( parent ) { + return parent->ParentedBy( node ); + } + return false; +} + +/* +================ +idHierarchy::ParentTo + +Makes the given node the parent. +================ +*/ +template< class type > +void idHierarchy::ParentTo( idHierarchy &node ) { + RemoveFromParent(); + + parent = &node; + sibling = node.child; + node.child = this; +} + +/* +================ +idHierarchy::MakeSiblingAfter + +Makes the given node a sibling after the passed in node. +================ +*/ +template< class type > +void idHierarchy::MakeSiblingAfter( idHierarchy &node ) { + RemoveFromParent(); + parent = node.parent; + sibling = node.sibling; + node.sibling = this; +} + +/* +================ +idHierarchy::RemoveFromParent +================ +*/ +template< class type > +void idHierarchy::RemoveFromParent() { + idHierarchy *prev; + + if ( parent ) { + prev = GetPriorSiblingNode(); + if ( prev ) { + prev->sibling = sibling; + } else { + parent->child = sibling; + } + } + + parent = NULL; + sibling = NULL; +} + +/* +================ +idHierarchy::RemoveFromHierarchy + +Removes the node from the hierarchy and adds it's children to the parent. +================ +*/ +template< class type > +void idHierarchy::RemoveFromHierarchy() { + idHierarchy *parentNode; + idHierarchy *node; + + parentNode = parent; + RemoveFromParent(); + + if ( parentNode ) { + while( child ) { + node = child; + node->RemoveFromParent(); + node->ParentTo( *parentNode ); + } + } else { + while( child ) { + child->RemoveFromParent(); + } + } +} + +/* +================ +idHierarchy::GetParent +================ +*/ +template< class type > +type *idHierarchy::GetParent() const { + if ( parent ) { + return parent->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetChild +================ +*/ +template< class type > +type *idHierarchy::GetChild() const { + if ( child ) { + return child->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetSibling +================ +*/ +template< class type > +type *idHierarchy::GetSibling() const { + if ( sibling ) { + return sibling->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetPriorSiblingNode + +Returns NULL if no parent, or if it is the first child. +================ +*/ +template< class type > +idHierarchy *idHierarchy::GetPriorSiblingNode() const { + if ( !parent || ( parent->child == this ) ) { + return NULL; + } + + idHierarchy *prev; + idHierarchy *node; + + node = parent->child; + prev = NULL; + while( ( node != this ) && ( node != NULL ) ) { + prev = node; + node = node->sibling; + } + + if ( node != this ) { + idLib::Error( "idHierarchy::GetPriorSibling: could not find node in parent's list of children" ); + } + + return prev; +} + +/* +================ +idHierarchy::GetPriorSibling + +Returns NULL if no parent, or if it is the first child. +================ +*/ +template< class type > +type *idHierarchy::GetPriorSibling() const { + idHierarchy *prior; + + prior = GetPriorSiblingNode(); + if ( prior ) { + return prior->owner; + } + + return NULL; +} + +/* +================ +idHierarchy::GetNext + +Goes through all nodes of the hierarchy. +================ +*/ +template< class type > +type *idHierarchy::GetNext() const { + const idHierarchy *node; + + if ( child ) { + return child->owner; + } else { + node = this; + while( node && node->sibling == NULL ) { + node = node->parent; + } + if ( node ) { + return node->sibling->owner; + } else { + return NULL; + } + } +} + +/* +================ +idHierarchy::GetNextLeaf + +Goes through all leaf nodes of the hierarchy. +================ +*/ +template< class type > +type *idHierarchy::GetNextLeaf() const { + const idHierarchy *node; + + if ( child ) { + node = child; + while ( node->child ) { + node = node->child; + } + return node->owner; + } else { + node = this; + while( node && node->sibling == NULL ) { + node = node->parent; + } + if ( node ) { + node = node->sibling; + while ( node->child ) { + node = node->child; + } + return node->owner; + } else { + return NULL; + } + } +} + +#endif /* !__HIERARCHY_H__ */ diff --git a/neo/idlib/containers/LinkList.h b/neo/idlib/containers/LinkList.h new file mode 100644 index 00000000..826dd7e5 --- /dev/null +++ b/neo/idlib/containers/LinkList.h @@ -0,0 +1,343 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LINKLIST_H__ +#define __LINKLIST_H__ + +/* +============================================================================== + +idLinkList + +Circular linked list template + +============================================================================== +*/ + +template< class type > +class idLinkList { +public: + idLinkList(); + ~idLinkList(); + + bool IsListEmpty() const; + bool InList() const; + int Num() const; + void Clear(); + + void InsertBefore( idLinkList &node ); + void InsertAfter( idLinkList &node ); + void AddToEnd( idLinkList &node ); + void AddToFront( idLinkList &node ); + + void Remove(); + + type * Next() const; + type * Prev() const; + + type * Owner() const; + void SetOwner( type *object ); + + idLinkList * ListHead() const; + idLinkList * NextNode() const; + idLinkList * PrevNode() const; + +private: + idLinkList * head; + idLinkList * next; + idLinkList * prev; + type * owner; +}; + +/* +================ +idLinkList::idLinkList + +Node is initialized to be the head of an empty list +================ +*/ +template< class type > +idLinkList::idLinkList() { + owner = NULL; + head = this; + next = this; + prev = this; +} + +/* +================ +idLinkList::~idLinkList + +Removes the node from the list, or if it's the head of a list, removes +all the nodes from the list. +================ +*/ +template< class type > +idLinkList::~idLinkList() { + Clear(); +} + +/* +================ +idLinkList::IsListEmpty + +Returns true if the list is empty. +================ +*/ +template< class type > +bool idLinkList::IsListEmpty() const { + return head->next == head; +} + +/* +================ +idLinkList::InList + +Returns true if the node is in a list. If called on the head of a list, will always return false. +================ +*/ +template< class type > +bool idLinkList::InList() const { + return head != this; +} + +/* +================ +idLinkList::Num + +Returns the number of nodes in the list. +================ +*/ +template< class type > +int idLinkList::Num() const { + idLinkList *node; + int num; + + num = 0; + for( node = head->next; node != head; node = node->next ) { + num++; + } + + return num; +} + +/* +================ +idLinkList::Clear + +If node is the head of the list, clears the list. Otherwise it just removes the node from the list. +================ +*/ +template< class type > +void idLinkList::Clear() { + if ( head == this ) { + while( next != this ) { + next->Remove(); + } + } else { + Remove(); + } +} + +/* +================ +idLinkList::Remove + +Removes node from list +================ +*/ +template< class type > +void idLinkList::Remove() { + prev->next = next; + next->prev = prev; + + next = this; + prev = this; + head = this; +} + +/* +================ +idLinkList::InsertBefore + +Places the node before the existing node in the list. If the existing node is the head, +then the new node is placed at the end of the list. +================ +*/ +template< class type > +void idLinkList::InsertBefore( idLinkList &node ) { + Remove(); + + next = &node; + prev = node.prev; + node.prev = this; + prev->next = this; + head = node.head; +} + +/* +================ +idLinkList::InsertAfter + +Places the node after the existing node in the list. If the existing node is the head, +then the new node is placed at the beginning of the list. +================ +*/ +template< class type > +void idLinkList::InsertAfter( idLinkList &node ) { + Remove(); + + prev = &node; + next = node.next; + node.next = this; + next->prev = this; + head = node.head; +} + +/* +================ +idLinkList::AddToEnd + +Adds node at the end of the list +================ +*/ +template< class type > +void idLinkList::AddToEnd( idLinkList &node ) { + InsertBefore( *node.head ); +} + +/* +================ +idLinkList::AddToFront + +Adds node at the beginning of the list +================ +*/ +template< class type > +void idLinkList::AddToFront( idLinkList &node ) { + InsertAfter( *node.head ); +} + +/* +================ +idLinkList::ListHead + +Returns the head of the list. If the node isn't in a list, it returns +a pointer to itself. +================ +*/ +template< class type > +idLinkList *idLinkList::ListHead() const { + return head; +} + +/* +================ +idLinkList::Next + +Returns the next object in the list, or NULL if at the end. +================ +*/ +template< class type > +type *idLinkList::Next() const { + if ( !next || ( next == head ) ) { + return NULL; + } + return next->owner; +} + +/* +================ +idLinkList::Prev + +Returns the previous object in the list, or NULL if at the beginning. +================ +*/ +template< class type > +type *idLinkList::Prev() const { + if ( !prev || ( prev == head ) ) { + return NULL; + } + return prev->owner; +} + +/* +================ +idLinkList::NextNode + +Returns the next node in the list, or NULL if at the end. +================ +*/ +template< class type > +idLinkList *idLinkList::NextNode() const { + if ( next == head ) { + return NULL; + } + return next; +} + +/* +================ +idLinkList::PrevNode + +Returns the previous node in the list, or NULL if at the beginning. +================ +*/ +template< class type > +idLinkList *idLinkList::PrevNode() const { + if ( prev == head ) { + return NULL; + } + return prev; +} + +/* +================ +idLinkList::Owner + +Gets the object that is associated with this node. +================ +*/ +template< class type > +type *idLinkList::Owner() const { + return owner; +} + +/* +================ +idLinkList::SetOwner + +Sets the object that this node is associated with. +================ +*/ +template< class type > +void idLinkList::SetOwner( type *object ) { + owner = object; +} + +#endif /* !__LINKLIST_H__ */ diff --git a/neo/idlib/containers/List.h b/neo/idlib/containers/List.h new file mode 100644 index 00000000..24f54c82 --- /dev/null +++ b/neo/idlib/containers/List.h @@ -0,0 +1,1037 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LIST_H__ +#define __LIST_H__ + +#include + +/* +=============================================================================== + + List template + Does not allocate memory until the first item is added. + +=============================================================================== +*/ + +/* +======================== +idListArrayNew +======================== +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void * idListArrayNew( int num, bool zeroBuffer ) { + _type_ * ptr = NULL; + if ( zeroBuffer ) { + ptr = (_type_ *)Mem_ClearedAlloc( sizeof(_type_) * num, _tag_ ); + } else { + ptr = (_type_ *)Mem_Alloc( sizeof(_type_) * num, _tag_ ); + } + for ( int i = 0; i < num; i++ ) { + new ( &ptr[i] ) _type_; + } + return ptr; +} + +/* +======================== +idListArrayDelete +======================== +*/ +template< typename _type_ > +ID_INLINE void idListArrayDelete( void *ptr, int num ) { + // Call the destructors on all the elements + for ( int i = 0; i < num; i++ ) { + ((_type_ *)ptr)[i].~_type_(); + } + Mem_Free( ptr ); +} + +/* +======================== +idListArrayResize +======================== +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void * idListArrayResize( void * voldptr, int oldNum, int newNum, bool zeroBuffer ) { + _type_ * oldptr = (_type_ *)voldptr; + _type_ * newptr = NULL; + if ( newNum > 0 ) { + newptr = (_type_ *)idListArrayNew<_type_, _tag_>( newNum, zeroBuffer ); + int overlap = Min( oldNum, newNum ); + for ( int i = 0; i < overlap; i++ ) { + newptr[i] = oldptr[i]; + } + } + idListArrayDelete<_type_>( voldptr, oldNum ); + return newptr; +} + +/* +================ +idListNewElement +================ +*/ +template< class type > +ID_INLINE type *idListNewElement( void ) { + return new type; +} + +template< typename _type_, memTag_t _tag_ = TAG_IDLIB_LIST > +class idList { +public: + + typedef int cmp_t( const _type_ *, const _type_ * ); + typedef _type_ new_t(); + + idList( int newgranularity = 16 ); + idList( const idList &other ); + ~idList(); + + void Clear(); // clear the list + int Num() const; // returns number of elements in list + int NumAllocated() const; // returns number of elements allocated for + void SetGranularity( int newgranularity ); // set new granularity + int GetGranularity() const; // get the current granularity + + size_t Allocated() const; // returns total size of allocated memory + size_t Size() const; // returns total size of allocated memory including size of list _type_ + size_t MemoryUsed() const; // returns size of the used elements in the list + + idList<_type_,_tag_> & operator=( const idList<_type_,_tag_> &other ); + const _type_ & operator[]( int index ) const; + _type_ & operator[]( int index ); + + void Condense(); // resizes list to exactly the number of elements it contains + void Resize( int newsize ); // resizes list to the given number of elements + void Resize( int newsize, int newgranularity ); // resizes list and sets new granularity + void SetNum( int newnum ); // set number of elements in list and resize to exactly this number if needed + void AssureSize( int newSize); // assure list has given number of elements, but leave them uninitialized + void AssureSize( int newSize, const _type_ &initValue ); // assure list has given number of elements and initialize any new elements + void AssureSizeAlloc( int newSize, new_t *allocator ); // assure the pointer list has the given number of elements and allocate any new elements + + _type_ * Ptr(); // returns a pointer to the list + const _type_ * Ptr() const; // returns a pointer to the list + _type_ & Alloc(); // returns reference to a new data element at the end of the list + int Append( const _type_ & obj ); // append element + int Append( const idList &other ); // append list + int AddUnique( const _type_ & obj ); // add unique element + int Insert( const _type_ & obj, int index = 0 ); // insert the element at the given index + int FindIndex( const _type_ & obj ) const; // find the index for the given element + _type_ * Find( _type_ const & obj ) const; // find pointer to the given element + int FindNull() const; // find the index for the first NULL pointer in the list + int IndexOf( const _type_ *obj ) const; // returns the index for the pointer to an element in the list + bool RemoveIndex( int index ); // remove the element at the given index + // removes the element at the given index and places the last element into its spot - DOES NOT PRESERVE LIST ORDER + bool RemoveIndexFast( int index ); + bool Remove( const _type_ & obj ); // remove the element +// void Sort( cmp_t *compare = ( cmp_t * )&idListSortCompare<_type_, _tag_> ); + void SortWithTemplate( const idSort<_type_> & sort = idSort_QuickDefault<_type_>() ); +// void SortSubSection( int startIndex, int endIndex, cmp_t *compare = ( cmp_t * )&idListSortCompare<_type_> ); + void Swap( idList &other ); // swap the contents of the lists + void DeleteContents( bool clear = true ); // delete the contents of the list + + //------------------------ + // auto-cast to other idList types with a different memory tag + //------------------------ + + template< memTag_t _t_ > + operator idList<_type_, _t_> & () { + return *reinterpret_cast *>( this ); + } + + template< memTag_t _t_> + operator const idList<_type_, _t_> & () const { + return *reinterpret_cast *>( this ); + } + + //------------------------ + // memTag + // + // Changing the memTag when the list has an allocated buffer will + // result in corruption of the memory statistics. + //------------------------ + memTag_t GetMemTag() const { return (memTag_t)memTag; }; + void SetMemTag( memTag_t tag_ ) { memTag = (byte)tag_; }; + +private: + int num; + int size; + int granularity; + _type_ * list; + byte memTag; +}; + +/* +================ +idList<_type_,_tag_>::idList( int ) +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE idList<_type_,_tag_>::idList( int newgranularity ) { + assert( newgranularity > 0 ); + + list = NULL; + granularity = newgranularity; + memTag = _tag_; + Clear(); +} + +/* +================ +idList<_type_,_tag_>::idList( const idList< _type_, _tag_ > &other ) +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE idList<_type_,_tag_>::idList( const idList &other ) { + list = NULL; + *this = other; +} + +/* +================ +idList<_type_,_tag_>::~idList< _type_, _tag_ > +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE idList<_type_,_tag_>::~idList() { + Clear(); +} + +/* +================ +idList<_type_,_tag_>::Clear + +Frees up the memory allocated by the list. Assumes that _type_ automatically handles freeing up memory. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::Clear() { + if ( list ) { + idListArrayDelete< _type_ >( list, size ); + } + + list = NULL; + num = 0; + size = 0; +} + +/* +================ +idList<_type_,_tag_>::DeleteContents + +Calls the destructor of all elements in the list. Conditionally frees up memory used by the list. +Note that this only works on lists containing pointers to objects and will cause a compiler error +if called with non-pointers. Since the list was not responsible for allocating the object, it has +no information on whether the object still exists or not, so care must be taken to ensure that +the pointers are still valid when this function is called. Function will set all pointers in the +list to NULL. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::DeleteContents( bool clear ) { + int i; + + for( i = 0; i < num; i++ ) { + delete list[ i ]; + list[ i ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, size * sizeof( _type_ ) ); + } +} + +/* +================ +idList<_type_,_tag_>::Allocated + +return total memory allocated for the list in bytes, but doesn't take into account additional memory allocated by _type_ +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE size_t idList<_type_,_tag_>::Allocated() const { + return size * sizeof( _type_ ); +} + +/* +================ +idList<_type_,_tag_>::Size + +return total size of list in bytes, but doesn't take into account additional memory allocated by _type_ +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE size_t idList<_type_,_tag_>::Size() const { + return sizeof( idList< _type_, _tag_ > ) + Allocated(); +} + +/* +================ +idList<_type_,_tag_>::MemoryUsed +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE size_t idList<_type_,_tag_>::MemoryUsed() const { + return num * sizeof( *list ); +} + +/* +================ +idList<_type_,_tag_>::Num + +Returns the number of elements currently contained in the list. +Note that this is NOT an indication of the memory allocated. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::Num() const { + return num; +} + +/* +================ +idList<_type_,_tag_>::NumAllocated + +Returns the number of elements currently allocated for. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::NumAllocated() const { + return size; +} + +/* +================ +idList<_type_,_tag_>::SetNum +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::SetNum( int newnum ) { + assert( newnum >= 0 ); + if ( newnum > size ) { + Resize( newnum ); + } + num = newnum; +} + +/* +================ +idList<_type_,_tag_>::SetGranularity + +Sets the base size of the array and resizes the array to match. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::SetGranularity( int newgranularity ) { + int newsize; + + assert( newgranularity > 0 ); + granularity = newgranularity; + + if ( list ) { + // resize it to the closest level of granularity + newsize = num + granularity - 1; + newsize -= newsize % granularity; + if ( newsize != size ) { + Resize( newsize ); + } + } +} + +/* +================ +idList<_type_,_tag_>::GetGranularity + +Get the current granularity. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::GetGranularity() const { + return granularity; +} + +/* +================ +idList<_type_,_tag_>::Condense + +Resizes the array to exactly the number of elements it contains or frees up memory if empty. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::Condense() { + if ( list ) { + if ( num ) { + Resize( num ); + } else { + Clear(); + } + } +} + +/* +================ +idList<_type_,_tag_>::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correnctly instantiated. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::Resize( int newsize ) { + assert( newsize >= 0 ); + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + if ( newsize == size ) { + // not changing the size, so just exit + return; + } + + list = (_type_ *)idListArrayResize< _type_, _tag_ >( list, size, newsize, false ); + size = newsize; + if ( size < num ) { + num = size; + } +} + +/* +================ +idList<_type_,_tag_>::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correnctly instantiated. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::Resize( int newsize, int newgranularity ) { + assert( newsize >= 0 ); + + assert( newgranularity > 0 ); + granularity = newgranularity; + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + list = (_type_ *)idListArrayResize< _type_, _tag_ >( list, size, newsize, false ); + size = newsize; + if ( size < num ) { + num = size; + } +} + +/* +================ +idList<_type_,_tag_>::AssureSize + +Makes sure the list has at least the given number of elements. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::AssureSize( int newSize ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + Resize( newSize ); + } + + num = newNum; +} + +/* +================ +idList<_type_,_tag_>::AssureSize + +Makes sure the list has at least the given number of elements and initialize any elements not yet initialized. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::AssureSize( int newSize, const _type_ &initValue ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + num = size; + Resize( newSize ); + + for ( int i = num; i < newSize; i++ ) { + list[i] = initValue; + } + } + + num = newNum; +} + +/* +================ +idList<_type_,_tag_>::AssureSizeAlloc + +Makes sure the list has at least the given number of elements and allocates any elements using the allocator. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::AssureSizeAlloc( int newSize, new_t *allocator ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + num = size; + Resize( newSize ); + + for ( int i = num; i < newSize; i++ ) { + list[i] = (*allocator)(); + } + } + + num = newNum; +} + +/* +================ +idList<_type_,_tag_>::operator= + +Copies the contents and size attributes of another list. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE idList<_type_,_tag_> & idList<_type_,_tag_>::operator=( const idList<_type_,_tag_> &other ) { + int i; + + Clear(); + + num = other.num; + size = other.size; + granularity = other.granularity; + memTag = other.memTag; + + if ( size ) { + list = (_type_ *)idListArrayNew< _type_, _tag_ >( size, false ); + for( i = 0; i < num; i++ ) { + list[ i ] = other.list[ i ]; + } + } + + return *this; +} + +/* +================ +idList<_type_,_tag_>::operator[] const + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE const _type_ & idList<_type_,_tag_>::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idList<_type_,_tag_>::operator[] + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE _type_ & idList<_type_,_tag_>::operator[]( int index ) { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idList<_type_,_tag_>::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE _type_ * idList<_type_,_tag_>::Ptr() { + return list; +} + +/* +================ +idList<_type_,_tag_>::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template< typename _type_, memTag_t _tag_ > +const ID_INLINE _type_ * idList<_type_,_tag_>::Ptr() const { + return list; +} + +/* +================ +idList<_type_,_tag_>::Alloc + +Returns a reference to a new data element at the end of the list. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE _type_ & idList<_type_,_tag_>::Alloc() { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + Resize( size + granularity ); + } + + return list[ num++ ]; +} + +/* +================ +idList<_type_,_tag_>::Append + +Increases the size of the list by one element and copies the supplied data into it. + +Returns the index of the new element. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::Append( _type_ const & obj ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + list[ num ] = obj; + num++; + + return num - 1; +} + + +/* +================ +idList<_type_,_tag_>::Insert + +Increases the size of the list by at leat one element if necessary +and inserts the supplied data into it. + +Returns the index of the new element. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::Insert( _type_ const & obj, int index ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + if ( index < 0 ) { + index = 0; + } + else if ( index > num ) { + index = num; + } + for ( int i = num; i > index; --i ) { + list[i] = list[i-1]; + } + num++; + list[index] = obj; + return index; +} + +/* +================ +idList<_type_,_tag_>::Append + +adds the other list to this one + +Returns the size of the new combined list +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::Append( const idList< _type_, _tag_ > &other ) { + if ( !list ) { + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + Resize( granularity ); + } + + int n = other.Num(); + for (int i = 0; i < n; i++) { + Append(other[i]); + } + + return Num(); +} + +/* +================ +idList<_type_,_tag_>::AddUnique + +Adds the data to the list if it doesn't already exist. Returns the index of the data in the list. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::AddUnique( _type_ const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index < 0 ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idList<_type_,_tag_>::FindIndex + +Searches for the specified data in the list and returns it's index. Returns -1 if the data is not found. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::FindIndex( _type_ const & obj ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == obj ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idList<_type_,_tag_>::Find + +Searches for the specified data in the list and returns it's address. Returns NULL if the data is not found. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE _type_ *idList<_type_,_tag_>::Find( _type_ const & obj ) const { + int i; + + i = FindIndex( obj ); + if ( i >= 0 ) { + return &list[ i ]; + } + + return NULL; +} + +/* +================ +idList<_type_,_tag_>::FindNull + +Searches for a NULL pointer in the list. Returns -1 if NULL is not found. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::FindNull() const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == NULL ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idList<_type_,_tag_>::IndexOf + +Takes a pointer to an element in the list and returns the index of the element. +This is NOT a guarantee that the object is really in the list. +Function will assert in debug builds if pointer is outside the bounds of the list, +but remains silent in release builds. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE int idList<_type_,_tag_>::IndexOf( _type_ const *objptr ) const { + int index; + + index = objptr - list; + + assert( index >= 0 ); + assert( index < num ); + + return index; +} + +/* +================ +idList<_type_,_tag_>::RemoveIndex + +Removes the element at the specified index and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the index is outside the bounds of the list. +Note that the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE bool idList<_type_,_tag_>::RemoveIndex( int index ) { + int i; + + assert( list != NULL ); + assert( index >= 0 ); + assert( index < num ); + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + for( i = index; i < num; i++ ) { + list[ i ] = list[ i + 1 ]; + } + + return true; +} + +/* +======================== +idList<_type_,_tag_>::RemoveIndexFast + +Removes the element at the specified index and moves the last element into its spot, rather +than moving the whole array down by one. Of course, this doesn't maintain the order of +elements! The number of elements in the list is reduced by one. + +return: bool - false if the data is not found in the list. + +NOTE: The element is not destroyed, so any memory used by it may not be freed until the + destruction of the list. +======================== +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE bool idList<_type_,_tag_>::RemoveIndexFast( int index ) { + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + if ( index != num ) { + list[ index ] = list[ num ]; + } + + return true; +} + +/* +================ +idList<_type_,_tag_>::Remove + +Removes the element if it is found within the list and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the data is not found in the list. Note that +the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE bool idList<_type_,_tag_>::Remove( _type_ const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index >= 0 ) { + return RemoveIndex( index ); + } + + return false; +} +// +///* +//================ +//idList<_type_,_tag_>::Sort +// +//Performs a qsort on the list using the supplied comparison function. Note that the data is merely moved around the +//list, so any pointers to data within the list may no longer be valid. +//================ +//*/ +//template< typename _type_, memTag_t _tag_ > +//ID_INLINE void idList<_type_,_tag_>::Sort( cmp_t *compare ) { +// if ( !list ) { +// return; +// } +// typedef int cmp_c(const void *, const void *); +// +// cmp_c *vCompare = (cmp_c *)compare; +// qsort( ( void * )list, ( size_t )num, sizeof( _type_ ), vCompare ); +//} + +/* +======================== +idList<_type_,_tag_>::SortWithTemplate + +Performs a QuickSort on the list using the supplied sort algorithm. + +Note: The data is merely moved around the list, so any pointers to data within the list may + no longer be valid. +======================== +*/ +template< typename _type_, memTag_t _tag_ > +ID_INLINE void idList<_type_,_tag_>::SortWithTemplate( const idSort<_type_> & sort ) { + if ( list == NULL ) { + return; + } + sort.Sort( Ptr(), Num() ); +} +// +///* +//================ +//idList<_type_,_tag_>::SortSubSection +// +//Sorts a subsection of the list. +//================ +//*/ +//template< typename _type_, memTag_t _tag_ > +//ID_INLINE void idList<_type_,_tag_>::SortSubSection( int startIndex, int endIndex, cmp_t *compare ) { +// if ( !list ) { +// return; +// } +// if ( startIndex < 0 ) { +// startIndex = 0; +// } +// if ( endIndex >= num ) { +// endIndex = num - 1; +// } +// if ( startIndex >= endIndex ) { +// return; +// } +// typedef int cmp_c(const void *, const void *); +// +// cmp_c *vCompare = (cmp_c *)compare; +// qsort( ( void * )( &list[startIndex] ), ( size_t )( endIndex - startIndex + 1 ), sizeof( _type_ ), vCompare ); +//} + +/* +======================== +FindFromGeneric + +Finds an item in a list based on any another datatype. Your _type_ must overload operator()== for the _type_. +If your _type_ is a ptr, use the FindFromGenericPtr function instead. +======================== +*/ +template< typename _type_, memTag_t _tag_, typename _compare_type_ > +_type_ * FindFromGeneric( idList<_type_, _tag_> & list, const _compare_type_ & other ) { + for ( int i = 0; i < list.Num(); i++ ) { + if ( list[ i ] == other ) { + return &list[ i ]; + } + } + return NULL; +} + +/* +======================== +FindFromGenericPtr +======================== +*/ +template< typename _type_, memTag_t _tag_, typename _compare_type_ > +_type_ * FindFromGenericPtr( idList<_type_, _tag_> & list, const _compare_type_ & other ) { + for ( int i = 0; i < list.Num(); i++ ) { + if ( *list[ i ] == other ) { + return &list[ i ]; + } + } + return NULL; +} + +#endif /* !__LIST_H__ */ diff --git a/neo/idlib/containers/PlaneSet.h b/neo/idlib/containers/PlaneSet.h new file mode 100644 index 00000000..b24a2afb --- /dev/null +++ b/neo/idlib/containers/PlaneSet.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PLANESET_H__ +#define __PLANESET_H__ + +/* +=============================================================================== + + Plane Set + +=============================================================================== +*/ + +class idPlaneSet : public idList { +public: + + void Clear() { idList::Clear(); hash.Free(); } + + int FindPlane( const idPlane &plane, const float normalEps, const float distEps ); + +private: + idHashIndex hash; +}; + +ID_INLINE int idPlaneSet::FindPlane( const idPlane &plane, const float normalEps, const float distEps ) { + int i, border, hashKey; + + assert( distEps <= 0.125f ); + + hashKey = (int)( idMath::Fabs( plane.Dist() ) * 0.125f ); + for ( border = -1; border <= 1; border++ ) { + for ( i = hash.First( hashKey + border ); i >= 0; i = hash.Next( i ) ) { + if ( (*this)[i].Compare( plane, normalEps, distEps ) ) { + return i; + } + } + } + + if ( plane.Type() >= PLANETYPE_NEGX && plane.Type() < PLANETYPE_TRUEAXIAL ) { + Append( -plane ); + hash.Add( hashKey, Num()-1 ); + Append( plane ); + hash.Add( hashKey, Num()-1 ); + return ( Num() - 1 ); + } + else { + Append( plane ); + hash.Add( hashKey, Num()-1 ); + Append( -plane ); + hash.Add( hashKey, Num()-1 ); + return ( Num() - 2 ); + } +} + +#endif /* !__PLANESET_H__ */ diff --git a/neo/idlib/containers/Queue.h b/neo/idlib/containers/Queue.h new file mode 100644 index 00000000..b3629a6d --- /dev/null +++ b/neo/idlib/containers/Queue.h @@ -0,0 +1,211 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + +/* +=============================================================================== + + Queue template + +=============================================================================== +*/ +template< class type, int nextOffset > +class idQueueTemplate { +public: + idQueueTemplate(); + + void Add( type *element ); + type * Get(); + +private: + type * first; + type * last; +}; + +#define QUEUE_NEXT_PTR( element ) (*((type**)(((byte*)element)+nextOffset))) + +template< class type, int nextOffset > +idQueueTemplate::idQueueTemplate() { + first = last = NULL; +} + +template< class type, int nextOffset > +void idQueueTemplate::Add( type *element ) { + QUEUE_NEXT_PTR(element) = NULL; + if ( last ) { + QUEUE_NEXT_PTR(last) = element; + } else { + first = element; + } + last = element; +} + +template< class type, int nextOffset > +type *idQueueTemplate::Get() { + type *element; + + element = first; + if ( element ) { + first = QUEUE_NEXT_PTR(first); + if ( last == element ) { + last = NULL; + } + QUEUE_NEXT_PTR(element) = NULL; + } + return element; +} + +/* +================================================ +A node of a Queue +================================================ +*/ +template< typename type > +class idQueueNode { +public: + idQueueNode() { next = NULL; } + + type * GetNext() const { return next; } + void SetNext( type *next ) { this->next = next; } + +private: + type * next; +}; + +/* +================================================ +A Queue, idQueue, is a template Container class implementing the Queue abstract data +type. +================================================ +*/ +template< typename type, idQueueNode type::*nodePtr > +class idQueue { +public: + idQueue(); + + void Add( type *element ); + type * RemoveFirst(); + type * Peek() const; + bool IsEmpty(); + + static void Test(); + +private: + type * first; + type * last; +}; + +/* +======================== +idQueue::idQueue +======================== +*/ +template< typename type, idQueueNode type::*nodePtr > +idQueue::idQueue() { + first = last = NULL; +} + +/* +======================== +idQueue::Add +======================== +*/ +template< typename type, idQueueNode type::*nodePtr > +void idQueue::Add( type *element ) { + (element->*nodePtr).SetNext( NULL ); + if ( last ) { + (last->*nodePtr).SetNext( element ); + } else { + first = element; + } + last = element; +} + +/* +======================== +idQueue::RemoveFirst +======================== +*/ +template< typename type, idQueueNode type::*nodePtr > +type *idQueue::RemoveFirst() { + type *element; + + element = first; + if ( element ) { + first = (first->*nodePtr).GetNext(); + if ( last == element ) { + last = NULL; + } + (element->*nodePtr).SetNext( NULL ); + } + return element; +} + +/* +======================== +idQueue::Peek +======================== +*/ +template< typename type, idQueueNode type::*nodePtr > +type *idQueue::Peek() const { + return first; +} + +/* +======================== +idQueue::IsEmpty +======================== +*/ +template< typename type, idQueueNode type::*nodePtr > +bool idQueue::IsEmpty() { + return ( first == NULL ); +} + +/* +======================== +idQueue::Test +======================== +*/ +template< typename type, idQueueNode type::*nodePtr > +void idQueue::Test() { + + class idMyType { + public: + idQueueNode queueNode; + }; + + idQueue myQueue; + + idMyType *element = new (TAG_IDLIB) idMyType; + myQueue.Add( element ); + element = myQueue.RemoveFirst(); + delete element; +} + +#endif // !__QUEUE_H__ diff --git a/neo/idlib/containers/Sort.h b/neo/idlib/containers/Sort.h new file mode 100644 index 00000000..c152e5e2 --- /dev/null +++ b/neo/idlib/containers/Sort.h @@ -0,0 +1,338 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SORT_H__ +#define __SORT_H__ + +/* +================================================================================================ +Contains the generic templated sort algorithms for quick-sort, heap-sort and insertion-sort. + +The sort algorithms do not use class operators or overloaded functions to compare +objects because it is often desireable to sort the same objects in different ways +based on different keys (not just ascending and descending but sometimes based on +name and other times based on say priority). So instead, for each different sort a +separate class is implemented with a Compare() function. + +This class is derived from one of the classes that implements a sort algorithm. +The Compare() member function does not only define how objects are sorted, the class +can also store additional data that can be used by the Compare() function. This, for +instance, allows a list of indices to be sorted where the indices point to objects +in an array. The base pointer of the array with objects can be stored on the class +that implements the Compare() function such that the Compare() function can use keys +that are stored on the objects. + +The Compare() function is not virtual because this would incur significant overhead. +Do NOT make the Compare() function virtual on the derived class! +The sort implementations also explicitely call the Compare() function of the derived +class. This is to avoid various compiler bugs with using overloaded compare functions +and the inability of various compilers to find the right overloaded compare function. + +To sort an array, an idList or an idStaticList, a new sort class, typically derived from +idSort_Quick, is implemented as follows: + +class idSort_MySort : public idSort_Quick< idMyObject, idSort_MySort > { +public: + int Compare( const idMyObject & a, const idMyObject & b ) const { + if ( a should come before b ) { + return -1; // or any negative integer + } if ( a should come after b ) { + return 1; // or any positive integer + } else { + return 0; + } + } +}; + +To sort an array: + +idMyObject array[100]; +idSort_MySort().Sort( array, 100 ); + +To sort an idList: + +idList< idMyObject > list; +list.Sort( idSort_MySort() ); + +The sort implementations never create temporaries of the template type. Only the +'SwapValues' template is used to move data around. This 'SwapValues' template can be +specialized to implement fast swapping of data. For instance, when sorting a list with +objects of some string class it is important to implement a specialized 'SwapValues' for +this string class to avoid excessive re-allocation and copying of strings. + +================================================================================================ +*/ + +/* +======================== +SwapValues +======================== +*/ +template< typename _type_ > +ID_INLINE void SwapValues( _type_ & a, _type_ & b ) { + _type_ c = a; + a = b; + b = c; +} + +/* +================================================ +idSort is an abstract template class for sorting an array of objects of the specified data type. +The array of objects is sorted such that: Compare( array[i], array[i+1] ) <= 0 for all i +================================================ +*/ +template< typename _type_ > +class idSort { +public: + virtual ~idSort() {} + virtual void Sort( _type_ * base, unsigned int num ) const = 0; +}; + +/* +================================================ +idSort_Quick is a sort template that implements the +quick-sort algorithm on an array of objects of the specified data type. +================================================ +*/ +template< typename _type_, typename _derived_ > +class idSort_Quick : public idSort< _type_ > { +public: + virtual void Sort( _type_ * base, unsigned int num ) const { + if ( num <= 0 ) { + return; + } + + const int64 MAX_LEVELS = 128; + int64 lo[MAX_LEVELS], hi[MAX_LEVELS]; + + // 'lo' is the lower index, 'hi' is the upper index + // of the region of the array that is being sorted. + lo[0] = 0; + hi[0] = num - 1; + + for ( int64 level = 0; level >= 0; ) { + int64 i = lo[level]; + int64 j = hi[level]; + + // Only use quick-sort when there are 4 or more elements in this region and we are below MAX_LEVELS. + // Otherwise fall back to an insertion-sort. + if ( ( ( j - i ) >= 4 ) && ( level < ( MAX_LEVELS - 1 ) ) ) { + + // Use the center element as the pivot. + // The median of a multi point sample could be used + // but simply taking the center works quite well. + int64 pi = ( i + j ) / 2; + + // Move the pivot element to the end of the region. + SwapValues( base[j], base[pi] ); + + // Get a reference to the pivot element. + _type_ & pivot = base[j--]; + + // Partition the region. + do { + while( static_cast< const _derived_ * >( this )->Compare( base[i], pivot ) < 0 ) { if ( ++i >= j ) break; } + while( static_cast< const _derived_ * >( this )->Compare( base[j], pivot ) > 0 ) { if ( --j <= i ) break; } + if ( i >= j ) break; + SwapValues( base[i], base[j] ); + } while( ++i < --j ); + + // Without these iterations sorting of arrays with many duplicates may + // become really slow because the partitioning can be very unbalanced. + // However, these iterations are unnecessary if all elements are unique. + while ( static_cast< const _derived_ * >( this )->Compare( base[i], pivot ) <= 0 && i < hi[level] ) { i++; } + while ( static_cast< const _derived_ * >( this )->Compare( base[j], pivot ) >= 0 && lo[level] < j ) { j--; } + + // Move the pivot element in place. + SwapValues( pivot, base[i] ); + + assert( level < MAX_LEVELS - 1 ); + lo[level+1] = i; + hi[level+1] = hi[level]; + hi[level] = j; + level++; + + } else { + + // Insertion-sort of the remaining elements. + for( ; i < j; j-- ) { + int64 m = i; + for ( int64 k = i + 1; k <= j; k++ ) { + if ( static_cast< const _derived_ * >( this )->Compare( base[k], base[m] ) > 0 ) { + m = k; + } + } + SwapValues( base[m], base[j] ); + } + level--; + } + } + } +}; + +/* +================================================ +Default quick-sort comparison function that can +be used to sort scalars from small to large. +================================================ +*/ +template< typename _type_ > +class idSort_QuickDefault : public idSort_Quick< _type_, idSort_QuickDefault< _type_ > > { +public: + int Compare( const _type_ & a, const _type_ & b ) const { return a - b; } +}; + +/* +================================================ +Specialization for floating point values to avoid an float-to-int +conversion for every comparison. +================================================ +*/ +template<> +class idSort_QuickDefault< float > : public idSort_Quick< float, idSort_QuickDefault< float > > { +public: + int Compare( const float & a, const float & b ) const { + if ( a < b ) { + return -1; + } + if ( a > b ) { + return 1; + } + return 0; + } +}; + +/* +================================================ +idSort_Heap is a sort template class that implements the +heap-sort algorithm on an array of objects of the specified data type. +================================================ +*/ +template< typename _type_, typename _derived_ > +class idSort_Heap : public idSort< _type_ > { +public: + virtual void Sort( _type_ * base, unsigned int num ) const { + // get all elements in heap order +#if 1 + // O( n ) + for ( unsigned int i = num / 2; i > 0; i-- ) { + // sift down + unsigned int parent = i - 1; + for ( unsigned int child = parent * 2 + 1; child < num; child = parent * 2 + 1 ) { + if ( child + 1 < num && static_cast< const _derived_ * >( this )->Compare( base[child + 1], base[child] ) > 0 ) { + child++; + } + if ( static_cast< const _derived_ * >( this )->Compare( base[child], base[parent] ) <= 0 ) { + break; + } + SwapValues( base[parent], base[child] ); + parent = child; + } + } +#else + // O(n log n) + for ( unsigned int i = 1; i < num; i++ ) { + // sift up + for ( unsigned int child = i; child > 0; ) { + unsigned int parent = ( child - 1 ) / 2; + if ( static_cast< const _derived_ * >( this )->Compare( base[parent], base[child] ) > 0 ) { + break; + } + SwapValues( base[child], base[parent] ); + child = parent; + } + } +#endif + // get sorted elements while maintaining heap order + for ( unsigned int i = num - 1; i > 0; i-- ) { + SwapValues( base[0], base[i] ); + // sift down + unsigned int parent = 0; + for ( unsigned int child = parent * 2 + 1; child < i; child = parent * 2 + 1 ) { + if ( child + 1 < i && static_cast< const _derived_ * >( this )->Compare( base[child + 1], base[child] ) > 0 ) { + child++; + } + if ( static_cast< const _derived_ * >( this )->Compare( base[child], base[parent] ) <= 0 ) { + break; + } + SwapValues( base[parent], base[child] ); + parent = child; + } + } + } +}; + +/* +================================================ +Default heap-sort comparison function that can +be used to sort scalars from small to large. +================================================ +*/ +template< typename _type_ > +class idSort_HeapDefault : public idSort_Heap< _type_, idSort_HeapDefault< _type_ > > { +public: + int Compare( const _type_ & a, const _type_ & b ) const { return a - b; } +}; + +/* +================================================ +idSort_Insertion is a sort template class that implements the +insertion-sort algorithm on an array of objects of the specified data type. +================================================ +*/ +template< typename _type_, typename _derived_ > +class idSort_Insertion : public idSort< _type_ > { +public: + virtual void Sort( _type_ * base, unsigned int num ) const { + _type_ * lo = base; + _type_ * hi = base + ( num - 1 ); + while( hi > lo ) { + _type_ * max = lo; + for ( _type_ * p = lo + 1; p <= hi; p++ ) { + if ( static_cast< const _derived_ * >( this )->Compare( (*p), (*max) ) > 0 ) { + max = p; + } + } + SwapValues( *max, *hi ); + hi--; + } + } +}; + +/* +================================================ +Default insertion-sort comparison function that can +be used to sort scalars from small to large. +================================================ +*/ +template< typename _type_ > +class idSort_InsertionDefault : public idSort_Insertion< _type_, idSort_InsertionDefault< _type_ > > { +public: + int Compare( const _type_ & a, const _type_ & b ) const { return a - b; } +}; + +#endif // !__SORT_H__ diff --git a/neo/idlib/containers/Stack.h b/neo/idlib/containers/Stack.h new file mode 100644 index 00000000..102e412f --- /dev/null +++ b/neo/idlib/containers/Stack.h @@ -0,0 +1,86 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STACK_H__ +#define __STACK_H__ + +/* +=============================================================================== + + Stack template + +=============================================================================== +*/ + +#define idStack( type, next ) idStackTemplatenext)> + +template< class type, int nextOffset > +class idStackTemplate { +public: + idStackTemplate(); + + void Add( type *element ); + type * Get(); + +private: + type * top; + type * bottom; +}; + +#define STACK_NEXT_PTR( element ) (*(type**)(((byte*)element)+nextOffset)) + +template< class type, int nextOffset > +idStackTemplate::idStackTemplate() { + top = bottom = NULL; +} + +template< class type, int nextOffset > +void idStackTemplate::Add( type *element ) { + STACK_NEXT_PTR(element) = top; + top = element; + if ( !bottom ) { + bottom = element; + } +} + +template< class type, int nextOffset > +type *idStackTemplate::Get() { + type *element; + + element = top; + if ( element ) { + top = STACK_NEXT_PTR(top); + if ( bottom == element ) { + bottom = NULL; + } + STACK_NEXT_PTR(element) = NULL; + } + return element; +} + +#endif /* !__STACK_H__ */ diff --git a/neo/idlib/containers/StaticList.h b/neo/idlib/containers/StaticList.h new file mode 100644 index 00000000..a14a8169 --- /dev/null +++ b/neo/idlib/containers/StaticList.h @@ -0,0 +1,656 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STATICLIST_H__ +#define __STATICLIST_H__ + +#include "List.h" + +/* +=============================================================================== + + Static list template + A non-growing, memset-able list using no memory allocation. + +=============================================================================== +*/ + +template +class idStaticList { +public: + + idStaticList(); + idStaticList( const idStaticList &other ); + ~idStaticList(); + + void Clear(); // marks the list as empty. does not deallocate or intialize data. + int Num() const; // returns number of elements in list + int Max() const; // returns the maximum number of elements in the list + void SetNum( int newnum ); // set number of elements in list + + // sets the number of elements in list and initializes any newly allocated elements to the given value + void SetNum( int newNum, const type & initValue ); + + size_t Allocated() const; // returns total size of allocated memory + size_t Size() const; // returns total size of allocated memory including size of list type + size_t MemoryUsed() const; // returns size of the used elements in the list + + const type & operator[]( int index ) const; + type & operator[]( int index ); + + type * Ptr(); // returns a pointer to the list + const type * Ptr() const; // returns a pointer to the list + type * Alloc(); // returns reference to a new data element at the end of the list. returns NULL when full. + int Append( const type & obj ); // append element + int Append( const idStaticList &other ); // append list + int AddUnique( const type & obj ); // add unique element + int Insert( const type & obj, int index = 0 ); // insert the element at the given index + int FindIndex( const type & obj ) const; // find the index for the given element + type * Find( type const & obj ) const; // find pointer to the given element + int FindNull() const; // find the index for the first NULL pointer in the list + int IndexOf( const type *obj ) const; // returns the index for the pointer to an element in the list + bool RemoveIndex( int index ); // remove the element at the given index + bool RemoveIndexFast( int index ); // remove the element at the given index + bool Remove( const type & obj ); // remove the element + void Swap( idStaticList &other ); // swap the contents of the lists + void DeleteContents( bool clear ); // delete the contents of the list + + void Sort( const idSort & sort = idSort_QuickDefault() ); + +private: + int num; + type list[ size ]; + +private: + // resizes list to the given number of elements + void Resize( int newsize ); +}; + +/* +================ +idStaticList::idStaticList() +================ +*/ +template +ID_INLINE idStaticList::idStaticList() { + num = 0; +} + +/* +================ +idStaticList::idStaticList( const idStaticList &other ) +================ +*/ +template +ID_INLINE idStaticList::idStaticList( const idStaticList &other ) { + *this = other; +} + +/* +================ +idStaticList::~idStaticList +================ +*/ +template +ID_INLINE idStaticList::~idStaticList() { +} + +/* +================ +idStaticList::Clear + +Sets the number of elements in the list to 0. Assumes that type automatically handles freeing up memory. +================ +*/ +template +ID_INLINE void idStaticList::Clear() { + num = 0; +} + +/* +======================== +idList<_type_,_tag_>::Sort + +Performs a QuickSort on the list using the supplied sort algorithm. + +Note: The data is merely moved around the list, so any pointers to data within the list may + no longer be valid. +======================== +*/ +template< class type,int size > +ID_INLINE void idStaticList::Sort( const idSort & sort ) { + if ( list == NULL ) { + return; + } + sort.Sort( Ptr(), Num() ); +} + +/* +================ +idStaticList::DeleteContents + +Calls the destructor of all elements in the list. Conditionally frees up memory used by the list. +Note that this only works on lists containing pointers to objects and will cause a compiler error +if called with non-pointers. Since the list was not responsible for allocating the object, it has +no information on whether the object still exists or not, so care must be taken to ensure that +the pointers are still valid when this function is called. Function will set all pointers in the +list to NULL. +================ +*/ +template +ID_INLINE void idStaticList::DeleteContents( bool clear ) { + int i; + + for( i = 0; i < num; i++ ) { + delete list[ i ]; + list[ i ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, sizeof( list ) ); + } +} + +/* +================ +idStaticList::Num + +Returns the number of elements currently contained in the list. +================ +*/ +template +ID_INLINE int idStaticList::Num() const { + return num; +} + +/* +================ +idStaticList::Num + +Returns the maximum number of elements in the list. +================ +*/ +template +ID_INLINE int idStaticList::Max() const { + return size; +} + +/* +================ +idStaticList::Allocated +================ +*/ +template +ID_INLINE size_t idStaticList::Allocated() const { + return size * sizeof( type ); +} + +/* +================ +idStaticList::Size +================ +*/ +template +ID_INLINE size_t idStaticList::Size() const { + return sizeof( idStaticList ) + Allocated(); +} + +/* +================ +idStaticList::Num +================ +*/ +template +ID_INLINE size_t idStaticList::MemoryUsed() const { + return num * sizeof( list[ 0 ] ); +} + +/* +================ +idStaticList::SetNum + +Set number of elements in list. +================ +*/ +template +ID_INLINE void idStaticList::SetNum( int newnum ) { + assert( newnum >= 0 ); + assert( newnum <= size ); + num = newnum; +} + +/* +======================== +idStaticList<_type_,_tag_>::SetNum +======================== +*/ +template< class type,int size > +ID_INLINE void idStaticList::SetNum( int newNum, const type &initValue ) { + assert( newNum >= 0 ); + newNum = Min( newNum, size ); + assert( newNum <= size ); + for ( int i = num; i < newNum; i++ ) { + list[i] = initValue; + } + num = newNum; +} + +/* +================ +idStaticList::operator[] const + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template +ID_INLINE const type &idStaticList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idStaticList::operator[] + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template +ID_INLINE type &idStaticList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idStaticList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template +ID_INLINE type *idStaticList::Ptr() { + return &list[ 0 ]; +} + +/* +================ +idStaticList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template +ID_INLINE const type *idStaticList::Ptr() const { + return &list[ 0 ]; +} + +/* +================ +idStaticList::Alloc + +Returns a pointer to a new data element at the end of the list. +================ +*/ +template +ID_INLINE type *idStaticList::Alloc() { + if ( num >= size ) { + return NULL; + } + + return &list[ num++ ]; +} + +/* +================ +idStaticList::Append + +Increases the size of the list by one element and copies the supplied data into it. + +Returns the index of the new element, or -1 when list is full. +================ +*/ +template +ID_INLINE int idStaticList::Append( type const & obj ) { + assert( num < size ); + if ( num < size ) { + list[ num ] = obj; + num++; + return num - 1; + } + + return -1; +} + + +/* +================ +idStaticList::Insert + +Increases the size of the list by at leat one element if necessary +and inserts the supplied data into it. + +Returns the index of the new element, or -1 when list is full. +================ +*/ +template +ID_INLINE int idStaticList::Insert( type const & obj, int index ) { + int i; + + assert( num < size ); + if ( num >= size ) { + return -1; + } + + assert( index >= 0 ); + if ( index < 0 ) { + index = 0; + } else if ( index > num ) { + index = num; + } + + for( i = num; i > index; --i ) { + list[i] = list[i-1]; + } + + num++; + list[index] = obj; + return index; +} + +/* +================ +idStaticList::Append + +adds the other list to this one + +Returns the size of the new combined list +================ +*/ +template +ID_INLINE int idStaticList::Append( const idStaticList &other ) { + int i; + int n = other.Num(); + + if ( num + n > size ) { + n = size - num; + } + for( i = 0; i < n; i++ ) { + list[i + num] = other.list[i]; + } + num += n; + return Num(); +} + +/* +================ +idStaticList::AddUnique + +Adds the data to the list if it doesn't already exist. Returns the index of the data in the list. +================ +*/ +template +ID_INLINE int idStaticList::AddUnique( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index < 0 ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idStaticList::FindIndex + +Searches for the specified data in the list and returns it's index. Returns -1 if the data is not found. +================ +*/ +template +ID_INLINE int idStaticList::FindIndex( type const & obj ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == obj ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idStaticList::Find + +Searches for the specified data in the list and returns it's address. Returns NULL if the data is not found. +================ +*/ +template +ID_INLINE type *idStaticList::Find( type const & obj ) const { + int i; + + i = FindIndex( obj ); + if ( i >= 0 ) { + return (type *) &list[ i ]; + } + + return NULL; +} + +/* +================ +idStaticList::FindNull + +Searches for a NULL pointer in the list. Returns -1 if NULL is not found. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template +ID_INLINE int idStaticList::FindNull() const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == NULL ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idStaticList::IndexOf + +Takes a pointer to an element in the list and returns the index of the element. +This is NOT a guarantee that the object is really in the list. +Function will assert in debug builds if pointer is outside the bounds of the list, +but remains silent in release builds. +================ +*/ +template +ID_INLINE int idStaticList::IndexOf( type const *objptr ) const { + int index; + + index = objptr - list; + + assert( index >= 0 ); + assert( index < num ); + + return index; +} + +/* +================ +idStaticList::RemoveIndex + +Removes the element at the specified index and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the index is outside the bounds of the list. +Note that the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template +ID_INLINE bool idStaticList::RemoveIndex( int index ) { + int i; + + assert( index >= 0 ); + assert( index < num ); + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + for( i = index; i < num; i++ ) { + list[ i ] = list[ i + 1 ]; + } + + return true; +} + +/* +======================== +idList<_type_,_tag_>::RemoveIndexFast + +Removes the element at the specified index and moves the last element into its spot, rather +than moving the whole array down by one. Of course, this doesn't maintain the order of +elements! The number of elements in the list is reduced by one. + +return: bool - false if the data is not found in the list. + +NOTE: The element is not destroyed, so any memory used by it may not be freed until the + destruction of the list. +======================== +*/ +template< typename _type_,int size > +ID_INLINE bool idStaticList<_type_,size>::RemoveIndexFast( int index ) { + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + if ( index != num ) { + list[ index ] = list[ num ]; + } + + return true; +} + +/* +================ +idStaticList::Remove + +Removes the element if it is found within the list and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the data is not found in the list. Note that +the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template +ID_INLINE bool idStaticList::Remove( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index >= 0 ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idStaticList::Swap + +Swaps the contents of two lists +================ +*/ +template +ID_INLINE void idStaticList::Swap( idStaticList &other ) { + idStaticList temp = *this; + *this = other; + other = temp; +} + +// debug tool to find uses of idlist that are dynamically growing +// Ideally, most lists on shipping titles will explicitly set their size correctly +// instead of relying on allocate-on-add +void BreakOnListGrowth(); +void BreakOnListDefault(); + +/* +======================== +idList<_type_,_tag_>::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correctly instantiated. +======================== +*/ +template< class type,int size > +ID_INLINE void idStaticList::Resize( int newsize ) { + + assert( newsize >= 0 ); + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + if ( newsize == size ) { + // not changing the size, so just exit + return; + } + + assert( newsize < size ); + return; +} +#endif /* !__STATICLIST_H__ */ diff --git a/neo/idlib/containers/StrList.h b/neo/idlib/containers/StrList.h new file mode 100644 index 00000000..7d320e60 --- /dev/null +++ b/neo/idlib/containers/StrList.h @@ -0,0 +1,204 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STRLIST_H__ +#define __STRLIST_H__ + +/* +=============================================================================== + + idStrList + +=============================================================================== +*/ + +typedef idList idStrList; +typedef idList idStrPtrList; +typedef idStr *idStrPtr; + +///* +//================ +//idListSortCompare +// +//Compares two pointers to strings. Used to sort a list of string pointers alphabetically in idList::Sort. +//================ +//*/ +//template<> +//ID_INLINE int idListSortCompare( const idStrPtr *a, const idStrPtr *b ) { +// return ( *a )->Icmp( **b ); +//} + +///* +//================ +//idStrList::Sort +// +//Sorts the list of strings alphabetically. Creates a list of pointers to the actual strings and sorts the +//pointer list. Then copies the strings into another list using the ordered list of pointers. +//================ +//*/ +//template<> +//ID_INLINE void idStrList::Sort( cmp_t *compare ) { +// int i; +// +// if ( !num ) { +// return; +// } +// +// idList other; +// idList pointerList; +// +// pointerList.SetNum( num ); +// for( i = 0; i < num; i++ ) { +// pointerList[ i ] = &( *this )[ i ]; +// } +// +// pointerList.Sort(); +// +// other.SetNum( num ); +// other.SetGranularity( granularity ); +// for( i = 0; i < other.Num(); i++ ) { +// other[ i ] = *pointerList[ i ]; +// } +// +// this->Swap( other ); +//} + +///* +//================ +//idStrList::SortSubSection +// +//Sorts a subsection of the list of strings alphabetically. +//================ +//*/ +//template<> +//ID_INLINE void idStrList::SortSubSection( int startIndex, int endIndex, cmp_t *compare ) { +// int i, s; +// +// if ( !num ) { +// return; +// } +// if ( startIndex < 0 ) { +// startIndex = 0; +// } +// if ( endIndex >= num ) { +// endIndex = num - 1; +// } +// if ( startIndex >= endIndex ) { +// return; +// } +// +// idList other; +// idList pointerList; +// +// s = endIndex - startIndex + 1; +// other.SetNum( s ); +// pointerList.SetNum( s ); +// for( i = 0; i < s; i++ ) { +// other[ i ] = ( *this )[ startIndex + i ]; +// pointerList[ i ] = &other[ i ]; +// } +// +// pointerList.Sort(); +// +// for( i = 0; i < s; i++ ) { +// (*this)[ startIndex + i ] = *pointerList[ i ]; +// } +//} + +/* +================ +idStrList::Size +================ +*/ +template<> +ID_INLINE size_t idStrList::Size() const { + size_t s; + int i; + + s = sizeof( *this ); + for( i = 0; i < Num(); i++ ) { + s += ( *this )[ i ].Size(); + } + + return s; +} + +/* +=============================================================================== + + idStrList path sorting + +=============================================================================== +*/ +// +///* +//================ +//idListSortComparePaths +// +//Compares two pointers to strings. Used to sort a list of string pointers alphabetically in idList::Sort. +//================ +//*/ +//template +//ID_INLINE int idListSortComparePaths( const idStrPtr *a, const idStrPtr *b ) { +// return ( *a )->IcmpPath( **b ); +//} + +///* +//================ +//idStrListSortPaths +// +//Sorts the list of path strings alphabetically and makes sure folders come first. +//================ +//*/ +//ID_INLINE void idStrListSortPaths( idStrList &list ) { +// int i; +// +// if ( !list.Num() ) { +// return; +// } +// +// idList other; +// idList pointerList; +// +// pointerList.SetNum( list.Num() ); +// for( i = 0; i < list.Num(); i++ ) { +// pointerList[ i ] = &list[ i ]; +// } +// +// pointerList.Sort( idListSortComparePaths ); +// +// other.SetNum( list.Num() ); +// other.SetGranularity( list.GetGranularity() ); +// for( i = 0; i < other.Num(); i++ ) { +// other[ i ] = *pointerList[ i ]; +// } +// +// list.Swap( other ); +//} + +#endif /* !__STRLIST_H__ */ diff --git a/neo/idlib/containers/StrPool.h b/neo/idlib/containers/StrPool.h new file mode 100644 index 00000000..741f7ec0 --- /dev/null +++ b/neo/idlib/containers/StrPool.h @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __STRPOOL_H__ +#define __STRPOOL_H__ + +/* +=============================================================================== + + idStrPool + +=============================================================================== +*/ + +class idStrPool; + +class idPoolStr : public idStr { + friend class idStrPool; + +public: + idPoolStr() { numUsers = 0; } + ~idPoolStr() { assert( numUsers == 0 ); } + + // returns total size of allocated memory + size_t Allocated() const { return idStr::Allocated(); } + // returns total size of allocated memory including size of string pool type + size_t Size() const { return sizeof( *this ) + Allocated(); } + // returns a pointer to the pool this string was allocated from + const idStrPool * GetPool() const { return pool; } + +private: + idStrPool * pool; + mutable int numUsers; +}; + +class idStrPool { +public: + idStrPool() { caseSensitive = true; } + + void SetCaseSensitive( bool caseSensitive ); + + int Num() const { return pool.Num(); } + size_t Allocated() const; + size_t Size() const; + + const idPoolStr * operator[]( int index ) const { return pool[index]; } + + const idPoolStr * AllocString( const char *string ); + void FreeString( const idPoolStr *poolStr ); + const idPoolStr * CopyString( const idPoolStr *poolStr ); + void Clear(); + +private: + bool caseSensitive; + idList pool; + idHashIndex poolHash; +}; + +/* +================ +idStrPool::SetCaseSensitive +================ +*/ +ID_INLINE void idStrPool::SetCaseSensitive( bool caseSensitive ) { + this->caseSensitive = caseSensitive; +} + +/* +================ +idStrPool::AllocString +================ +*/ +ID_INLINE const idPoolStr *idStrPool::AllocString( const char *string ) { + int i, hash; + idPoolStr *poolStr; + + hash = poolHash.GenerateKey( string, caseSensitive ); + if ( caseSensitive ) { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Cmp( string ) == 0 ) { + pool[i]->numUsers++; + return pool[i]; + } + } + } else { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Icmp( string ) == 0 ) { + pool[i]->numUsers++; + return pool[i]; + } + } + } + + poolStr = new (TAG_IDLIB_STRING) idPoolStr; + *static_cast(poolStr) = string; + poolStr->pool = this; + poolStr->numUsers = 1; + poolHash.Add( hash, pool.Append( poolStr ) ); + return poolStr; +} + +/* +================ +idStrPool::FreeString +================ +*/ +ID_INLINE void idStrPool::FreeString( const idPoolStr *poolStr ) { + int i, hash; + + assert( poolStr->numUsers >= 1 ); + assert( poolStr->pool == this ); + + poolStr->numUsers--; + if ( poolStr->numUsers <= 0 ) { + hash = poolHash.GenerateKey( poolStr->c_str(), caseSensitive ); + if ( caseSensitive ) { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Cmp( poolStr->c_str() ) == 0 ) { + break; + } + } + } else { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Icmp( poolStr->c_str() ) == 0 ) { + break; + } + } + } + assert( i != -1 ); + assert( pool[i] == poolStr ); + delete pool[i]; + pool.RemoveIndex( i ); + poolHash.RemoveIndex( hash, i ); + } +} + +/* +================ +idStrPool::CopyString +================ +*/ +ID_INLINE const idPoolStr *idStrPool::CopyString( const idPoolStr *poolStr ) { + + assert( poolStr->numUsers >= 1 ); + + if ( poolStr->pool == this ) { + // the string is from this pool so just increase the user count + poolStr->numUsers++; + return poolStr; + } else { + // the string is from another pool so it needs to be re-allocated from this pool. + return AllocString( poolStr->c_str() ); + } +} + +/* +================ +idStrPool::Clear +================ +*/ +ID_INLINE void idStrPool::Clear() { + int i; + + for ( i = 0; i < pool.Num(); i++ ) { + pool[i]->numUsers = 0; + } + pool.DeleteContents( true ); + poolHash.Free(); +} + +/* +================ +idStrPool::Allocated +================ +*/ +ID_INLINE size_t idStrPool::Allocated() const { + int i; + size_t size; + + size = pool.Allocated() + poolHash.Allocated(); + for ( i = 0; i < pool.Num(); i++ ) { + size += pool[i]->Allocated(); + } + return size; +} + +/* +================ +idStrPool::Size +================ +*/ +ID_INLINE size_t idStrPool::Size() const { + int i; + size_t size; + + size = pool.Size() + poolHash.Size(); + for ( i = 0; i < pool.Num(); i++ ) { + size += pool[i]->Size(); + } + return size; +} + +#endif /* !__STRPOOL_H__ */ diff --git a/neo/idlib/containers/VectorSet.h b/neo/idlib/containers/VectorSet.h new file mode 100644 index 00000000..9d99ec95 --- /dev/null +++ b/neo/idlib/containers/VectorSet.h @@ -0,0 +1,270 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __VECTORSET_H__ +#define __VECTORSET_H__ + +/* +=============================================================================== + + Vector Set + + Creates a set of vectors without duplicates. + +=============================================================================== +*/ + +template< class type, int dimension > +class idVectorSet : public idList { +public: + idVectorSet(); + idVectorSet( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + + // returns total size of allocated memory + size_t Allocated() const { return idList::Allocated() + hash.Allocated(); } + // returns total size of allocated memory including size of type + size_t Size() const { return sizeof( *this ) + Allocated(); } + + void Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + void ResizeIndex( const int newSize ); + void Clear(); + + int FindVector( const type &v, const float epsilon ); + +private: + idHashIndex hash; + type mins; + type maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; +}; + +template< class type, int dimension > +ID_INLINE idVectorSet::idVectorSet() { + hash.Clear( idMath::IPow( boxHashSize, dimension ), 128 ); + boxHashSize = 16; + memset( boxInvSize, 0, dimension * sizeof( boxInvSize[0] ) ); + memset( boxHalfSize, 0, dimension * sizeof( boxHalfSize[0] ) ); +} + +template< class type, int dimension > +ID_INLINE idVectorSet::idVectorSet( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + Init( mins, maxs, boxHashSize, initialSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + int i; + float boxSize; + + idList::AssureSize( initialSize ); + idList::SetNum( 0, false ); + + hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); + + this->mins = mins; + this->maxs = maxs; + this->boxHashSize = boxHashSize; + + for ( i = 0; i < dimension; i++ ) { + boxSize = ( maxs[i] - mins[i] ) / (float) boxHashSize; + boxInvSize[i] = 1.0f / boxSize; + boxHalfSize[i] = boxSize * 0.5f; + } +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::ResizeIndex( const int newSize ) { + idList::Resize( newSize ); + hash.ResizeIndex( newSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::Clear() { + idList::Clear(); + hash.Clear(); +} + +template< class type, int dimension > +ID_INLINE int idVectorSet::FindVector( const type &v, const float epsilon ) { + int i, j, k, hashKey, partialHashKey[dimension]; + + for ( i = 0; i < dimension; i++ ) { + assert( epsilon <= boxHalfSize[i] ); + partialHashKey[i] = (int) ( ( v[i] - mins[i] - boxHalfSize[i] ) * boxInvSize[i] ); + } + + for ( i = 0; i < ( 1 << dimension ); i++ ) { + + hashKey = 0; + for ( j = 0; j < dimension; j++ ) { + hashKey *= boxHashSize; + hashKey += partialHashKey[j] + ( ( i >> j ) & 1 ); + } + + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + const type &lv = (*this)[j]; + for ( k = 0; k < dimension; k++ ) { + if ( idMath::Fabs( lv[k] - v[k] ) > epsilon ) { + break; + } + } + if ( k >= dimension ) { + return j; + } + } + } + + hashKey = 0; + for ( i = 0; i < dimension; i++ ) { + hashKey *= boxHashSize; + hashKey += (int) ( ( v[i] - mins[i] ) * boxInvSize[i] ); + } + + hash.Add( hashKey, idList::Num() ); + Append( v ); + return idList::Num()-1; +} + + +/* +=============================================================================== + + Vector Subset + + Creates a subset without duplicates from an existing list with vectors. + +=============================================================================== +*/ + +template< class type, int dimension > +class idVectorSubset { +public: + idVectorSubset(); + idVectorSubset( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + + // returns total size of allocated memory + size_t Allocated() const { return idList::Allocated() + hash.Allocated(); } + // returns total size of allocated memory including size of type + size_t Size() const { return sizeof( *this ) + Allocated(); } + + void Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + void Clear(); + + // returns either vectorNum or an index to a previously found vector + int FindVector( const type *vectorList, const int vectorNum, const float epsilon ); + +private: + idHashIndex hash; + type mins; + type maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; +}; + +template< class type, int dimension > +ID_INLINE idVectorSubset::idVectorSubset() { + hash.Clear( idMath::IPow( boxHashSize, dimension ), 128 ); + boxHashSize = 16; + memset( boxInvSize, 0, dimension * sizeof( boxInvSize[0] ) ); + memset( boxHalfSize, 0, dimension * sizeof( boxHalfSize[0] ) ); +} + +template< class type, int dimension > +ID_INLINE idVectorSubset::idVectorSubset( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + Init( mins, maxs, boxHashSize, initialSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSubset::Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + int i; + float boxSize; + + hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); + + this->mins = mins; + this->maxs = maxs; + this->boxHashSize = boxHashSize; + + for ( i = 0; i < dimension; i++ ) { + boxSize = ( maxs[i] - mins[i] ) / (float) boxHashSize; + boxInvSize[i] = 1.0f / boxSize; + boxHalfSize[i] = boxSize * 0.5f; + } +} + +template< class type, int dimension > +ID_INLINE void idVectorSubset::Clear() { + idList::Clear(); + hash.Clear(); +} + +template< class type, int dimension > +ID_INLINE int idVectorSubset::FindVector( const type *vectorList, const int vectorNum, const float epsilon ) { + int i, j, k, hashKey, partialHashKey[dimension]; + const type &v = vectorList[vectorNum]; + + for ( i = 0; i < dimension; i++ ) { + assert( epsilon <= boxHalfSize[i] ); + partialHashKey[i] = (int) ( ( v[i] - mins[i] - boxHalfSize[i] ) * boxInvSize[i] ); + } + + for ( i = 0; i < ( 1 << dimension ); i++ ) { + + hashKey = 0; + for ( j = 0; j < dimension; j++ ) { + hashKey *= boxHashSize; + hashKey += partialHashKey[j] + ( ( i >> j ) & 1 ); + } + + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + const type &lv = vectorList[j]; + for ( k = 0; k < dimension; k++ ) { + if ( idMath::Fabs( lv[k] - v[k] ) > epsilon ) { + break; + } + } + if ( k >= dimension ) { + return j; + } + } + } + + hashKey = 0; + for ( i = 0; i < dimension; i++ ) { + hashKey *= boxHashSize; + hashKey += (int) ( ( v[i] - mins[i] ) * boxInvSize[i] ); + } + + hash.Add( hashKey, vectorNum ); + return vectorNum; +} + +#endif /* !__VECTORSET_H__ */ diff --git a/neo/idlib/geometry/DrawVert.cpp b/neo/idlib/geometry/DrawVert.cpp new file mode 100644 index 00000000..2f3732c2 --- /dev/null +++ b/neo/idlib/geometry/DrawVert.cpp @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +============ +idShadowVert::CreateShadowCache +============ +*/ +int idShadowVert::CreateShadowCache( idShadowVert * vertexCache, const idDrawVert *verts, const int numVerts ) { + for ( int i = 0; i < numVerts; i++ ) { + vertexCache[i*2+0].xyzw[0] = verts[i].xyz[0]; + vertexCache[i*2+0].xyzw[1] = verts[i].xyz[1]; + vertexCache[i*2+0].xyzw[2] = verts[i].xyz[2]; + vertexCache[i*2+0].xyzw[3] = 1.0f; + + vertexCache[i*2+1].xyzw[0] = verts[i].xyz[0]; + vertexCache[i*2+1].xyzw[1] = verts[i].xyz[1]; + vertexCache[i*2+1].xyzw[2] = verts[i].xyz[2]; + vertexCache[i*2+1].xyzw[3] = 0.0f; + } + return numVerts * 2; +} + +/* +=================== +idShadowVertSkinned::CreateShadowCache +=================== +*/ +int idShadowVertSkinned::CreateShadowCache( idShadowVertSkinned * vertexCache, const idDrawVert *verts, const int numVerts ) { + for ( int i = 0; i < numVerts; i++ ) { + vertexCache[0].xyzw[0] = verts[i].xyz[0]; + vertexCache[0].xyzw[1] = verts[i].xyz[1]; + vertexCache[0].xyzw[2] = verts[i].xyz[2]; + vertexCache[0].xyzw[3] = 1.0f; + *(unsigned int *)vertexCache[0].color = *(unsigned int *)verts[i].color; + *(unsigned int *)vertexCache[0].color2 = *(unsigned int *)verts[i].color2; + + vertexCache[1].xyzw[0] = verts[i].xyz[0]; + vertexCache[1].xyzw[1] = verts[i].xyz[1]; + vertexCache[1].xyzw[2] = verts[i].xyz[2]; + vertexCache[1].xyzw[3] = 0.0f; + *(unsigned int *)vertexCache[1].color = *(unsigned int *)verts[i].color; + *(unsigned int *)vertexCache[1].color2 = *(unsigned int *)verts[i].color2; + + vertexCache += 2; + } + return numVerts * 2; +} diff --git a/neo/idlib/geometry/DrawVert.h b/neo/idlib/geometry/DrawVert.h new file mode 100644 index 00000000..2797d427 --- /dev/null +++ b/neo/idlib/geometry/DrawVert.h @@ -0,0 +1,739 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DRAWVERT_H__ +#define __DRAWVERT_H__ + +// The hardware converts a byte to a float by division with 255 and in the +// vertex programs we convert the floating-point value in the range [0, 1] +// to the range [-1, 1] by multiplying with 2 and subtracting 1. +#define VERTEX_BYTE_TO_FLOAT( x ) ( (x) * ( 2.0f / 255.0f ) - 1.0f ) +#define VERTEX_FLOAT_TO_BYTE( x ) idMath::Ftob( ( (x) + 1.0f ) * ( 255.0f / 2.0f ) + 0.5f ) + +// The hardware converts a byte to a float by division with 255 and in the +// fragment programs we convert the floating-point value in the range [0, 1] +// to the range [-1, 1] by multiplying with 2 and subtracting 1. +// This is the conventional OpenGL mapping which specifies an exact +// representation for -1 and +1 but not 0. The DirectX 10 mapping is +// in the comments which specifies a non-linear mapping with an exact +// representation of -1, 0 and +1 but -1 is represented twice. +#define NORMALMAP_BYTE_TO_FLOAT( x ) VERTEX_BYTE_TO_FLOAT( x ) //( (x) - 128.0f ) * ( 1.0f / 127.0f ) +#define NORMALMAP_FLOAT_TO_BYTE( x ) VERTEX_FLOAT_TO_BYTE( x ) //idMath::Ftob( 128.0f + 127.0f * (x) + 0.5f ) + +/* +================================================ +halfFloat_t +================================================ +*/ +typedef unsigned short halfFloat_t; + +// GPU half-float bit patterns +#define HF_MANTISSA(x) (x&1023) +#define HF_EXP(x) ((x&32767)>>10) +#define HF_SIGN(x) ((x&32768)?-1:1) + +/* +======================== +F16toF32 +======================== +*/ +ID_INLINE float F16toF32( halfFloat_t x ) { + int e = HF_EXP( x ); + int m = HF_MANTISSA( x ); + int s = HF_SIGN( x ); + + if ( 0 < e && e < 31 ) { + return s * powf( 2.0f, ( e - 15.0f ) ) * ( 1 + m / 1024.0f ); + } else if ( m == 0 ) { + return s * 0.0f; + } + return s * powf( 2.0f, -14.0f ) * ( m / 1024.0f ); +} + +/* +======================== +F32toF16 +======================== +*/ +ID_INLINE halfFloat_t F32toF16( float a ) { + unsigned int f = *(unsigned *)( &a ); + unsigned int signbit = ( f & 0x80000000 ) >> 16; + int exponent = ( ( f & 0x7F800000 ) >> 23 ) - 112; + unsigned int mantissa = ( f & 0x007FFFFF ); + + if ( exponent <= 0 ) { + return 0; + } + if ( exponent > 30 ) { + return (halfFloat_t)( signbit | 0x7BFF ); + } + + return (halfFloat_t)( signbit | ( exponent << 10 ) | ( mantissa >> 13 ) ); +} + +/* +=============================================================================== + + Draw Vertex. + +=============================================================================== +*/ + +class idDrawVert { +public: + idVec3 xyz; // 12 bytes + halfFloat_t st[2]; // 4 bytes + byte normal[4]; // 4 bytes + byte tangent[4]; // 4 bytes -- [3] is texture polarity sign + byte color[4]; // 4 bytes + byte color2[4]; // 4 bytes -- weights for skinning + + float operator[]( const int index ) const; + float & operator[]( const int index ); + + void Clear(); + + const idVec3 GetNormal() const; + const idVec3 GetNormalRaw() const; // not re-normalized for renderbump + + // must be normalized already! + void SetNormal( float x, float y, float z ); + void SetNormal( const idVec3 & n ); + + const idVec3 GetTangent() const; + const idVec3 GetTangentRaw() const; // not re-normalized for renderbump + + // must be normalized already! + void SetTangent( float x, float y, float z ); + void SetTangent( const idVec3 & t ); + + // derived from normal, tangent, and tangent flag + const idVec3 GetBiTangent() const; + const idVec3 GetBiTangentRaw() const; // not re-normalized for renderbump + + void SetBiTangent( float x, float y, float z ); + ID_INLINE void SetBiTangent( const idVec3 & t ); + + float GetBiTangentSign() const; + byte GetBiTangentSignBit() const; + + void SetTexCoordNative( const halfFloat_t s, const halfFloat_t t ); + void SetTexCoord( const idVec2 & st ); + void SetTexCoord( float s, float t ); + void SetTexCoordS( float s ); + void SetTexCoordT( float t ); + const idVec2 GetTexCoord() const; + const halfFloat_t GetTexCoordNativeS() const; + const halfFloat_t GetTexCoordNativeT() const; + + // either 1.0f or -1.0f + ID_INLINE void SetBiTangentSign( float sign ); + ID_INLINE void SetBiTangentSignBit( byte bit ); + + void Lerp( const idDrawVert &a, const idDrawVert &b, const float f ); + void LerpAll( const idDrawVert &a, const idDrawVert &b, const float f ); + + void SetColor( dword color ); + void SetNativeOrderColor( dword color ); + dword GetColor() const; + + void SetColor2( dword color ); + void SetNativeOrderColor2( dword color ); + void ClearColor2(); + dword GetColor2() const; + + static idDrawVert GetSkinnedDrawVert( const idDrawVert & vert, const idJointMat * joints ); + static idVec3 GetSkinnedDrawVertPosition( const idDrawVert & vert, const idJointMat * joints ); +}; + +#define DRAWVERT_SIZE 32 +#define DRAWVERT_XYZ_OFFSET (0*4) +#define DRAWVERT_ST_OFFSET (3*4) +#define DRAWVERT_NORMAL_OFFSET (4*4) +#define DRAWVERT_TANGENT_OFFSET (5*4) +#define DRAWVERT_COLOR_OFFSET (6*4) +#define DRAWVERT_COLOR2_OFFSET (7*4) + +assert_offsetof( idDrawVert, xyz, DRAWVERT_XYZ_OFFSET ); +assert_offsetof( idDrawVert, normal, DRAWVERT_NORMAL_OFFSET ); +assert_offsetof( idDrawVert, tangent, DRAWVERT_TANGENT_OFFSET ); + +/* +======================== +VertexFloatToByte + +Assumes input is in the range [-1, 1] +======================== +*/ +ID_INLINE void VertexFloatToByte( const float & x, const float & y, const float & z, byte * bval ) { + assert_4_byte_aligned( bval ); // for __stvebx + + + const __m128 vector_float_one = { 1.0f, 1.0f, 1.0f, 1.0f }; + const __m128 vector_float_half = { 0.5f, 0.5f, 0.5f, 0.5f }; + const __m128 vector_float_255_over_2 = { 255.0f / 2.0f, 255.0f / 2.0f, 255.0f / 2.0f, 255.0f / 2.0f }; + + const __m128 xyz = _mm_unpacklo_ps( _mm_unpacklo_ps( _mm_load_ss( &x ), _mm_load_ss( &z ) ), _mm_load_ss( &y ) ); + const __m128 xyzScaled = _mm_madd_ps( _mm_add_ps( xyz, vector_float_one ), vector_float_255_over_2, vector_float_half ); + const __m128i xyzInt = _mm_cvtps_epi32( xyzScaled ); + const __m128i xyzShort = _mm_packs_epi32( xyzInt, xyzInt ); + const __m128i xyzChar = _mm_packus_epi16( xyzShort, xyzShort ); + const __m128i xyz16 = _mm_unpacklo_epi8( xyzChar, _mm_setzero_si128() ); + + bval[0] = (byte)_mm_extract_epi16( xyz16, 0 ); // cannot use _mm_extract_epi8 because it is an SSE4 instruction + bval[1] = (byte)_mm_extract_epi16( xyz16, 1 ); + bval[2] = (byte)_mm_extract_epi16( xyz16, 2 ); + +} + +/* +======================== +idDrawVert::operator[] +======================== +*/ +ID_INLINE float idDrawVert::operator[]( const int index ) const { + assert( index >= 0 && index < 5 ); + return ((float *)(&xyz))[index]; +} + +/* +======================== +idDrawVert::operator[] +======================== +*/ +ID_INLINE float &idDrawVert::operator[]( const int index ) { + assert( index >= 0 && index < 5 ); + return ((float *)(&xyz))[index]; +} + +/* +======================== +idDrawVert::Clear +======================== +*/ +ID_INLINE void idDrawVert::Clear() { + *reinterpret_cast(&this->xyz.x) = 0; + *reinterpret_cast(&this->xyz.y) = 0; + *reinterpret_cast(&this->xyz.z) = 0; + *reinterpret_cast(this->st) = 0; + *reinterpret_cast(this->normal) = 0x00FF8080; // x=0, y=0, z=1 + *reinterpret_cast(this->tangent) = 0xFF8080FF; // x=1, y=0, z=0 + *reinterpret_cast(this->color) = 0; + *reinterpret_cast(this->color2) = 0; +} + +/* +======================== +idDrawVert::GetNormal +======================== +*/ +ID_INLINE const idVec3 idDrawVert::GetNormal() const { + idVec3 n( VERTEX_BYTE_TO_FLOAT( normal[0] ), + VERTEX_BYTE_TO_FLOAT( normal[1] ), + VERTEX_BYTE_TO_FLOAT( normal[2] ) ); + n.Normalize(); // after the normal has been compressed & uncompressed, it may not be normalized anymore + return n; +} + +/* +======================== +idDrawVert::GetNormalRaw +======================== +*/ +ID_INLINE const idVec3 idDrawVert::GetNormalRaw() const { + idVec3 n( VERTEX_BYTE_TO_FLOAT( normal[0] ), + VERTEX_BYTE_TO_FLOAT( normal[1] ), + VERTEX_BYTE_TO_FLOAT( normal[2] ) ); + // don't re-normalize just like we do in the vertex programs + return n; +} + +/* +======================== +idDrawVert::SetNormal +must be normalized already! +======================== +*/ +ID_INLINE void idDrawVert::SetNormal( const idVec3 & n ) { + VertexFloatToByte( n.x, n.y, n.z, normal ); +} + +/* +======================== +idDrawVert::SetNormal +======================== +*/ +ID_INLINE void idDrawVert::SetNormal( float x, float y, float z ) { + VertexFloatToByte( x, y, z, normal ); +} + +/* +======================== +&idDrawVert::GetTangent +======================== +*/ +ID_INLINE const idVec3 idDrawVert::GetTangent() const { + idVec3 t( VERTEX_BYTE_TO_FLOAT( tangent[0] ), + VERTEX_BYTE_TO_FLOAT( tangent[1] ), + VERTEX_BYTE_TO_FLOAT( tangent[2] ) ); + t.Normalize(); + return t; +} + +/* +======================== +&idDrawVert::GetTangentRaw +======================== +*/ +ID_INLINE const idVec3 idDrawVert::GetTangentRaw() const { + idVec3 t( VERTEX_BYTE_TO_FLOAT( tangent[0] ), + VERTEX_BYTE_TO_FLOAT( tangent[1] ), + VERTEX_BYTE_TO_FLOAT( tangent[2] ) ); + // don't re-normalize just like we do in the vertex programs + return t; +} + +/* +======================== +idDrawVert::SetTangent +======================== +*/ +ID_INLINE void idDrawVert::SetTangent( float x, float y, float z ) { + VertexFloatToByte( x, y, z, tangent ); +} + +/* +======================== +idDrawVert::SetTangent +======================== +*/ +ID_INLINE void idDrawVert::SetTangent( const idVec3 & t ) { + VertexFloatToByte( t.x, t.y, t.z, tangent ); +} + +/* +======================== +idDrawVert::GetBiTangent +======================== +*/ +ID_INLINE const idVec3 idDrawVert::GetBiTangent() const { + // derive from the normal, tangent, and bitangent direction flag + idVec3 bitangent; + bitangent.Cross( GetNormal(), GetTangent() ); + bitangent *= GetBiTangentSign(); + return bitangent; +} + +/* +======================== +idDrawVert::GetBiTangentRaw +======================== +*/ +ID_INLINE const idVec3 idDrawVert::GetBiTangentRaw() const { + // derive from the normal, tangent, and bitangent direction flag + // don't re-normalize just like we do in the vertex programs + idVec3 bitangent; + bitangent.Cross( GetNormalRaw(), GetTangentRaw() ); + bitangent *= GetBiTangentSign(); + return bitangent; +} + +/* +======================== +idDrawVert::SetBiTangent +======================== +*/ +ID_INLINE void idDrawVert::SetBiTangent( float x, float y, float z ) { + SetBiTangent( idVec3( x, y, z ) ); +} + +/* +======================== +idDrawVert::SetBiTangent +======================== +*/ +ID_INLINE void idDrawVert::SetBiTangent( const idVec3 &t ) { + idVec3 bitangent; + bitangent.Cross( GetNormal(), GetTangent() ); + SetBiTangentSign( bitangent * t ); +} + +/* +======================== +idDrawVert::GetBiTangentSign +======================== +*/ +ID_INLINE float idDrawVert::GetBiTangentSign() const { + return ( tangent[3] < 128 ) ? -1.0f : 1.0f; +} + +/* +======================== +idDrawVert::GetBiTangentSignBit +======================== +*/ +ID_INLINE byte idDrawVert::GetBiTangentSignBit() const { + return ( tangent[3] < 128 ) ? 1 : 0; +} + +/* +======================== +idDrawVert::SetBiTangentSign +======================== +*/ +ID_INLINE void idDrawVert::SetBiTangentSign( float sign ) { + tangent[3] = ( sign < 0.0f ) ? 0 : 255; +} + +/* +======================== +idDrawVert::SetBiTangentSignBit +======================== +*/ +ID_INLINE void idDrawVert::SetBiTangentSignBit( byte sign ) { + tangent[3] = sign ? 0 : 255; +} + +/* +======================== +idDrawVert::Lerp +======================== +*/ +ID_INLINE void idDrawVert::Lerp( const idDrawVert &a, const idDrawVert &b, const float f ) { + xyz = a.xyz + f * ( b.xyz - a.xyz ); + SetTexCoord( ::Lerp( a.GetTexCoord(), b.GetTexCoord(), f ) ); +} + +/* +======================== +idDrawVert::LerpAll +======================== +*/ +ID_INLINE void idDrawVert::LerpAll( const idDrawVert &a, const idDrawVert &b, const float f ) { + xyz = ::Lerp( a.xyz, b.xyz, f ); + SetTexCoord( ::Lerp( a.GetTexCoord(), b.GetTexCoord(), f ) ); + + idVec3 normal = ::Lerp( a.GetNormal(), b.GetNormal(), f ); + idVec3 tangent = ::Lerp( a.GetTangent(), b.GetTangent(), f ); + idVec3 bitangent = ::Lerp( a.GetBiTangent(), b.GetBiTangent(), f ); + normal.Normalize(); + tangent.Normalize(); + bitangent.Normalize(); + SetNormal( normal ); + SetTangent( tangent ); + SetBiTangent( bitangent ); + + color[0] = (byte)( a.color[0] + f * ( b.color[0] - a.color[0] ) ); + color[1] = (byte)( a.color[1] + f * ( b.color[1] - a.color[1] ) ); + color[2] = (byte)( a.color[2] + f * ( b.color[2] - a.color[2] ) ); + color[3] = (byte)( a.color[3] + f * ( b.color[3] - a.color[3] ) ); + + color2[0] = (byte)( a.color2[0] + f * ( b.color2[0] - a.color2[0] ) ); + color2[1] = (byte)( a.color2[1] + f * ( b.color2[1] - a.color2[1] ) ); + color2[2] = (byte)( a.color2[2] + f * ( b.color2[2] - a.color2[2] ) ); + color2[3] = (byte)( a.color2[3] + f * ( b.color2[3] - a.color2[3] ) ); +} + +/* +======================== +idDrawVert::SetNativeOrderColor +======================== +*/ +ID_INLINE void idDrawVert::SetNativeOrderColor( dword color ) { + *reinterpret_cast(this->color) = color; +} + +/* +======================== +idDrawVert::SetColor +======================== +*/ +ID_INLINE void idDrawVert::SetColor( dword color ) { + *reinterpret_cast(this->color) = color; +} + +/* +======================== +idDrawVert::SetColor +======================== +*/ +ID_INLINE dword idDrawVert::GetColor() const { + return *reinterpret_cast(this->color); +} + +/* +======================== +idDrawVert::SetTexCoordNative +======================== +*/ +ID_INLINE void idDrawVert::SetTexCoordNative( const halfFloat_t s, const halfFloat_t t ) { + st[0] = s; + st[1] = t; +} + +/* +======================== +idDrawVert::SetTexCoord +======================== +*/ +ID_INLINE void idDrawVert::SetTexCoord( const idVec2 & st ) { + SetTexCoordS( st.x ); + SetTexCoordT( st.y ); +} + +/* +======================== +idDrawVert::SetTexCoord +======================== +*/ +ID_INLINE void idDrawVert::SetTexCoord( float s, float t ) { + SetTexCoordS( s ); + SetTexCoordT( t ); +} + +/* +======================== +idDrawVert::SetTexCoordS +======================== +*/ +ID_INLINE void idDrawVert::SetTexCoordS( float s ) { + st[0] = F32toF16( s ); +} + +/* +======================== +idDrawVert::SetTexCoordT +======================== +*/ +ID_INLINE void idDrawVert::SetTexCoordT( float t ) { + st[1] = F32toF16( t ); +} + +/* +======================== +idDrawVert::GetTexCoord +======================== +*/ +ID_INLINE const idVec2 idDrawVert::GetTexCoord() const { + return idVec2( F16toF32( st[0] ), F16toF32( st[1] ) ); +} + +/* +======================== +idDrawVert::GetTexCoordNativeS +======================== +*/ +ID_INLINE const halfFloat_t idDrawVert::GetTexCoordNativeS() const { + return st[0]; +} + +/* +======================== +idDrawVert::GetTexCoordNativeT +======================== +*/ +ID_INLINE const halfFloat_t idDrawVert::GetTexCoordNativeT() const { + return st[1]; +} + +/* +======================== +idDrawVert::SetNativeOrderColor2 +======================== +*/ +ID_INLINE void idDrawVert::SetNativeOrderColor2( dword color2 ) { + *reinterpret_cast(this->color2) = color2; +} + +/* +======================== +idDrawVert::SetColor +======================== +*/ +ID_INLINE void idDrawVert::SetColor2( dword color2 ) { + *reinterpret_cast(this->color2) = color2; +} + +/* +======================== +idDrawVert::ClearColor2 +======================== +*/ +ID_INLINE void idDrawVert::ClearColor2() { + *reinterpret_cast(this->color2) = 0x80808080; +} + +/* +======================== +idDrawVert::GetColor2 +======================== +*/ +ID_INLINE dword idDrawVert::GetColor2() const { + return *reinterpret_cast(this->color2); +} + +/* +======================== +WriteDrawVerts16 + +Use 16-byte in-order SIMD writes because the destVerts may live in write-combined memory +======================== +*/ +ID_INLINE void WriteDrawVerts16( idDrawVert * destVerts, const idDrawVert * localVerts, int numVerts ) { + assert_sizeof( idDrawVert, 32 ); + assert_16_byte_aligned( destVerts ); + assert_16_byte_aligned( localVerts ); + + + for ( int i = 0; i < numVerts; i++ ) { + __m128i v0 = _mm_load_si128( (const __m128i *)( (byte *)( localVerts + i ) + 0 ) ); + __m128i v1 = _mm_load_si128( (const __m128i *)( (byte *)( localVerts + i ) + 16 ) ); + _mm_stream_si128( (__m128i *)( (byte *)( destVerts + i ) + 0 ), v0 ); + _mm_stream_si128( (__m128i *)( (byte *)( destVerts + i ) + 16 ), v1 ); + } + +} + +/* +===================== +idDrawVert::GetSkinnedDrawVert +===================== +*/ +ID_INLINE idDrawVert idDrawVert::GetSkinnedDrawVert( const idDrawVert & vert, const idJointMat * joints ) { + if ( joints == NULL ) { + return vert; + } + + const idJointMat & j0 = joints[vert.color[0]]; + const idJointMat & j1 = joints[vert.color[1]]; + const idJointMat & j2 = joints[vert.color[2]]; + const idJointMat & j3 = joints[vert.color[3]]; + + const float w0 = vert.color2[0] * ( 1.0f / 255.0f ); + const float w1 = vert.color2[1] * ( 1.0f / 255.0f ); + const float w2 = vert.color2[2] * ( 1.0f / 255.0f ); + const float w3 = vert.color2[3] * ( 1.0f / 255.0f ); + + idJointMat accum; + idJointMat::Mul( accum, j0, w0 ); + idJointMat::Mad( accum, j1, w1 ); + idJointMat::Mad( accum, j2, w2 ); + idJointMat::Mad( accum, j3, w3 ); + + idDrawVert outVert; + outVert.xyz = accum * idVec4( vert.xyz.x, vert.xyz.y, vert.xyz.z, 1.0f ); + outVert.SetTexCoordNative( vert.GetTexCoordNativeS(), vert.GetTexCoordNativeT() ); + outVert.SetNormal( accum * vert.GetNormal() ); + outVert.SetTangent( accum * vert.GetTangent() ); + outVert.tangent[3] = vert.tangent[3]; + for ( int i = 0; i < 4; i++ ) { + outVert.color[i] = vert.color[i]; + outVert.color2[i] = vert.color2[i]; + } + return outVert; +} + +/* +===================== +idDrawVert::GetSkinnedDrawVertPosition +===================== +*/ +ID_INLINE idVec3 idDrawVert::GetSkinnedDrawVertPosition( const idDrawVert & vert, const idJointMat * joints ) { + if ( joints == NULL ) { + return vert.xyz; + } + + const idJointMat & j0 = joints[vert.color[0]]; + const idJointMat & j1 = joints[vert.color[1]]; + const idJointMat & j2 = joints[vert.color[2]]; + const idJointMat & j3 = joints[vert.color[3]]; + + const float w0 = vert.color2[0] * ( 1.0f / 255.0f ); + const float w1 = vert.color2[1] * ( 1.0f / 255.0f ); + const float w2 = vert.color2[2] * ( 1.0f / 255.0f ); + const float w3 = vert.color2[3] * ( 1.0f / 255.0f ); + + idJointMat accum; + idJointMat::Mul( accum, j0, w0 ); + idJointMat::Mad( accum, j1, w1 ); + idJointMat::Mad( accum, j2, w2 ); + idJointMat::Mad( accum, j3, w3 ); + + return accum * idVec4( vert.xyz.x, vert.xyz.y, vert.xyz.z, 1.0f ); +} + +/* +=============================================================================== +Shadow Vertex +=============================================================================== +*/ +class idShadowVert { +public: + idVec4 xyzw; + + void Clear(); + static int CreateShadowCache( idShadowVert * vertexCache, const idDrawVert *verts, const int numVerts ); +}; + +#define SHADOWVERT_XYZW_OFFSET (0) + +assert_offsetof( idShadowVert, xyzw, SHADOWVERT_XYZW_OFFSET ); + +ID_INLINE void idShadowVert::Clear() { + xyzw.Zero(); +} + +/* +=============================================================================== +Skinned Shadow Vertex +=============================================================================== +*/ +class idShadowVertSkinned { +public: + idVec4 xyzw; + byte color[4]; + byte color2[4]; + byte pad[8]; // pad to multiple of 32-byte for glDrawElementsBaseVertex + + void Clear(); + static int CreateShadowCache( idShadowVertSkinned * vertexCache, const idDrawVert *verts, const int numVerts ); +}; + +#define SHADOWVERTSKINNED_XYZW_OFFSET (0) +#define SHADOWVERTSKINNED_COLOR_OFFSET (16) +#define SHADOWVERTSKINNED_COLOR2_OFFSET (20) + +assert_offsetof( idShadowVertSkinned, xyzw, SHADOWVERTSKINNED_XYZW_OFFSET ); +assert_offsetof( idShadowVertSkinned, color, SHADOWVERTSKINNED_COLOR_OFFSET ); +assert_offsetof( idShadowVertSkinned, color2, SHADOWVERTSKINNED_COLOR2_OFFSET ); + +ID_INLINE void idShadowVertSkinned::Clear() { + xyzw.Zero(); +} + +#endif /* !__DRAWVERT_H__ */ diff --git a/neo/idlib/geometry/DrawVert_intrinsics.h b/neo/idlib/geometry/DrawVert_intrinsics.h new file mode 100644 index 00000000..97df61c3 --- /dev/null +++ b/neo/idlib/geometry/DrawVert_intrinsics.h @@ -0,0 +1,200 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DRAWVERT_INTRINSICS_H__ +#define __DRAWVERT_INTRINSICS_H__ + + +static const __m128i vector_int_f32_sign_mask = _mm_set1_epi32( 1U << IEEE_FLT_SIGN_BIT ); +static const __m128i vector_int_f32_exponent_mask = _mm_set1_epi32( ( ( 1U << IEEE_FLT_EXPONENT_BITS ) - 1 ) << IEEE_FLT_MANTISSA_BITS ); +static const __m128i vector_int_f32_mantissa_mask = _mm_set1_epi32( ( 1U << IEEE_FLT_MANTISSA_BITS ) - 1 ); +static const __m128i vector_int_f16_min_exponent = _mm_set1_epi32( 0 ); +static const __m128i vector_int_f16_max_exponent = _mm_set1_epi32( ( 30 << IEEE_FLT16_MANTISSA_BITS ) ); +static const __m128i vector_int_f16_min_mantissa = _mm_set1_epi32( 0 ); +static const __m128i vector_int_f16_max_mantissa = _mm_set1_epi32( ( ( 1 << IEEE_FLT16_MANTISSA_BITS ) - 1 ) ); +static const __m128i vector_int_f32_to_f16_exponent_bias = _mm_set1_epi32( ( IEEE_FLT_EXPONENT_BIAS - IEEE_FLT16_EXPONENT_BIAS ) << IEEE_FLT16_MANTISSA_BITS ); +static const int f32_to_f16_sign_shift = IEEE_FLT_SIGN_BIT - IEEE_FLT16_SIGN_BIT; +static const int f32_to_f16_exponent_shift = IEEE_FLT_MANTISSA_BITS - IEEE_FLT16_MANTISSA_BITS; +static const int f32_to_f16_mantissa_shift = IEEE_FLT_MANTISSA_BITS - IEEE_FLT16_MANTISSA_BITS; + +static const __m128i vector_int_zero = _mm_setzero_si128(); +static const __m128i vector_int_one = _mm_set_epi32( 1, 1, 1, 1 ); + +static const __m128 vector_float_mask_clear_last = __m128c( _mm_set_epi32( 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF ) ); +static const __m128 vector_float_last_one = { 0.0f, 0.0f, 0.0f, 1.0f }; +static const __m128 vector_float_1_over_255 = { 1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f }; +static const __m128 vector_float_1_over_4 = { 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f }; + + +/* +==================== +FastF32toF16 +==================== +*/ + +ID_INLINE_EXTERN __m128i FastF32toF16( __m128i f32_bits ) { + __m128i f16_sign = _mm_srli_epi32( _mm_and_si128( f32_bits, vector_int_f32_sign_mask ), f32_to_f16_sign_shift ); + __m128i f16_exponent = _mm_srli_epi32( _mm_and_si128( f32_bits, vector_int_f32_exponent_mask ), f32_to_f16_exponent_shift ); + __m128i f16_mantissa = _mm_srli_epi32( _mm_and_si128( f32_bits, vector_int_f32_mantissa_mask ), f32_to_f16_mantissa_shift ); + + f16_exponent = _mm_sub_epi32( f16_exponent, vector_int_f32_to_f16_exponent_bias ); + + const __m128i underflow = _mm_cmplt_epi32( f16_exponent, vector_int_f16_min_exponent ); + const __m128i overflow = _mm_cmpgt_epi32( f16_exponent, vector_int_f16_max_exponent ); + + f16_exponent = _mm_sel_si128( f16_exponent, vector_int_f16_min_exponent, underflow ); + f16_exponent = _mm_sel_si128( f16_exponent, vector_int_f16_max_exponent, overflow ); + f16_mantissa = _mm_sel_si128( f16_mantissa, vector_int_f16_min_mantissa, underflow ); + f16_mantissa = _mm_sel_si128( f16_mantissa, vector_int_f16_max_mantissa, overflow ); + + __m128i flt16 = _mm_or_si128( _mm_or_si128( f16_sign, f16_exponent ), f16_mantissa ); + + return _mm_packs_epi32( flt16, flt16 ); +} + + +ID_INLINE_EXTERN halfFloat_t Scalar_FastF32toF16( float f32 ) { + const int f32_sign_mask = 1U << IEEE_FLT_SIGN_BIT; + const int f32_exponent_mask = ( ( 1U << IEEE_FLT_EXPONENT_BITS ) - 1 ) << IEEE_FLT_MANTISSA_BITS; + const int f32_mantissa_mask = ( 1U << IEEE_FLT_MANTISSA_BITS ) - 1; + const int f16_min_exponent = 0; + const int f16_max_exponent = ( 30 << IEEE_FLT16_MANTISSA_BITS ); + const int f16_min_mantissa = 0; + const int f16_max_mantissa = ( ( 1 << IEEE_FLT16_MANTISSA_BITS ) - 1 ); + const int f32_to_f16_sign_shift = IEEE_FLT_SIGN_BIT - IEEE_FLT16_SIGN_BIT; + const int f32_to_f16_exponent_shift = IEEE_FLT_MANTISSA_BITS - IEEE_FLT16_MANTISSA_BITS; + const int f32_to_f16_mantissa_shift = IEEE_FLT_MANTISSA_BITS - IEEE_FLT16_MANTISSA_BITS; + const int f32_to_f16_exponent_bias = ( IEEE_FLT_EXPONENT_BIAS - IEEE_FLT16_EXPONENT_BIAS ) << IEEE_FLT16_MANTISSA_BITS; + + int f32_bits = *(unsigned int *)&f32; + + int f16_sign = ( (unsigned int )( f32_bits & f32_sign_mask ) >> f32_to_f16_sign_shift ); + int f16_exponent = ( (unsigned int )( f32_bits & f32_exponent_mask ) >> f32_to_f16_exponent_shift ); + int f16_mantissa = ( (unsigned int )( f32_bits & f32_mantissa_mask ) >> f32_to_f16_mantissa_shift ); + + f16_exponent -= f32_to_f16_exponent_bias; + + const bool underflow = ( f16_exponent < f16_min_exponent ); + const bool overflow = ( f16_exponent > f16_max_exponent ); + + f16_exponent = underflow ? f16_min_exponent : f16_exponent; + f16_exponent = overflow ? f16_max_exponent : f16_exponent; + f16_mantissa = underflow ? f16_min_mantissa : f16_mantissa; + f16_mantissa = overflow ? f16_max_mantissa : f16_mantissa; + + return (halfFloat_t)( f16_sign | f16_exponent | f16_mantissa ); +} + +/* +==================== +LoadSkinnedDrawVertPosition +==================== +*/ + +ID_INLINE_EXTERN __m128 LoadSkinnedDrawVertPosition( const idDrawVert & base, const idJointMat * joints ) { + const idJointMat & j0 = joints[base.color[0]]; + const idJointMat & j1 = joints[base.color[1]]; + const idJointMat & j2 = joints[base.color[2]]; + const idJointMat & j3 = joints[base.color[3]]; + + __m128i weights_b = _mm_cvtsi32_si128( *(const unsigned int *)base.color2 ); + __m128i weights_s = _mm_unpacklo_epi8( weights_b, vector_int_zero ); + __m128i weights_i = _mm_unpacklo_epi16( weights_s, vector_int_zero ); + + __m128 weights = _mm_cvtepi32_ps( weights_i ); + weights = _mm_mul_ps( weights, vector_float_1_over_255 ); + + __m128 w0 = _mm_splat_ps( weights, 0 ); + __m128 w1 = _mm_splat_ps( weights, 1 ); + __m128 w2 = _mm_splat_ps( weights, 2 ); + __m128 w3 = _mm_splat_ps( weights, 3 ); + + __m128 matX = _mm_mul_ps( _mm_load_ps( j0.ToFloatPtr() + 0 * 4 ), w0 ); + __m128 matY = _mm_mul_ps( _mm_load_ps( j0.ToFloatPtr() + 1 * 4 ), w0 ); + __m128 matZ = _mm_mul_ps( _mm_load_ps( j0.ToFloatPtr() + 2 * 4 ), w0 ); + + matX = _mm_madd_ps( _mm_load_ps( j1.ToFloatPtr() + 0 * 4 ), w1, matX ); + matY = _mm_madd_ps( _mm_load_ps( j1.ToFloatPtr() + 1 * 4 ), w1, matY ); + matZ = _mm_madd_ps( _mm_load_ps( j1.ToFloatPtr() + 2 * 4 ), w1, matZ ); + + matX = _mm_madd_ps( _mm_load_ps( j2.ToFloatPtr() + 0 * 4 ), w2, matX ); + matY = _mm_madd_ps( _mm_load_ps( j2.ToFloatPtr() + 1 * 4 ), w2, matY ); + matZ = _mm_madd_ps( _mm_load_ps( j2.ToFloatPtr() + 2 * 4 ), w2, matZ ); + + matX = _mm_madd_ps( _mm_load_ps( j3.ToFloatPtr() + 0 * 4 ), w3, matX ); + matY = _mm_madd_ps( _mm_load_ps( j3.ToFloatPtr() + 1 * 4 ), w3, matY ); + matZ = _mm_madd_ps( _mm_load_ps( j3.ToFloatPtr() + 2 * 4 ), w3, matZ ); + + __m128 v = _mm_load_ps( base.xyz.ToFloatPtr() ); + v = _mm_and_ps( v, vector_float_mask_clear_last ); + v = _mm_or_ps( v, vector_float_last_one ); + + __m128 t0 = _mm_mul_ps( matX, v ); + __m128 t1 = _mm_mul_ps( matY, v ); + __m128 t2 = _mm_mul_ps( matZ, v ); + __m128 t3 = vector_float_1_over_4; + + __m128 s0 = _mm_unpacklo_ps( t0, t2 ); // x0, z0, x1, z1 + __m128 s1 = _mm_unpackhi_ps( t0, t2 ); // x2, z2, x3, z3 + __m128 s2 = _mm_unpacklo_ps( t1, t3 ); // y0, w0, y1, w1 + __m128 s3 = _mm_unpackhi_ps( t1, t3 ); // y2, w2, y3, w3 + + __m128 r0 = _mm_unpacklo_ps( s0, s2 ); // x0, y0, z0, w0 + __m128 r1 = _mm_unpackhi_ps( s0, s2 ); // x1, y1, z1, w1 + __m128 r2 = _mm_unpacklo_ps( s1, s3 ); // x2, y2, z2, w2 + __m128 r3 = _mm_unpackhi_ps( s1, s3 ); // x3, y3, z3, w3 + + r0 = _mm_add_ps( r0, r1 ); + r2 = _mm_add_ps( r2, r3 ); + r0 = _mm_add_ps( r0, r2 ); + + return r0; +} + + +ID_INLINE_EXTERN idVec3 Scalar_LoadSkinnedDrawVertPosition( const idDrawVert & vert, const idJointMat * joints ) { + const idJointMat & j0 = joints[vert.color[0]]; + const idJointMat & j1 = joints[vert.color[1]]; + const idJointMat & j2 = joints[vert.color[2]]; + const idJointMat & j3 = joints[vert.color[3]]; + + const float w0 = vert.color2[0] * ( 1.0f / 255.0f ); + const float w1 = vert.color2[1] * ( 1.0f / 255.0f ); + const float w2 = vert.color2[2] * ( 1.0f / 255.0f ); + const float w3 = vert.color2[3] * ( 1.0f / 255.0f ); + + idJointMat accum; + idJointMat::Mul( accum, j0, w0 ); + idJointMat::Mad( accum, j1, w1 ); + idJointMat::Mad( accum, j2, w2 ); + idJointMat::Mad( accum, j3, w3 ); + + return accum * idVec4( vert.xyz.x, vert.xyz.y, vert.xyz.z, 1.0f ); +} + +#endif /* !__DRAWVERT_INTRINSICS_H__ */ diff --git a/neo/idlib/geometry/JointTransform.cpp b/neo/idlib/geometry/JointTransform.cpp new file mode 100644 index 00000000..4c2298e7 --- /dev/null +++ b/neo/idlib/geometry/JointTransform.cpp @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +============= +idJointMat::ToJointQuat +============= +*/ +idJointQuat idJointMat::ToJointQuat() const { + idJointQuat jq; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + trace = mat[0 * 4 + 0] + mat[1 * 4 + 1] + mat[2 * 4 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + jq.q[3] = s * t; + jq.q[0] = ( mat[1 * 4 + 2] - mat[2 * 4 + 1] ) * s; + jq.q[1] = ( mat[2 * 4 + 0] - mat[0 * 4 + 2] ) * s; + jq.q[2] = ( mat[0 * 4 + 1] - mat[1 * 4 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 4 + 1] > mat[0 * 4 + 0] ) { + i = 1; + } + if ( mat[2 * 4 + 2] > mat[i * 4 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 4 + i] - ( mat[j * 4 + j] + mat[k * 4 + k] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + jq.q[i] = s * t; + jq.q[3] = ( mat[j * 4 + k] - mat[k * 4 + j] ) * s; + jq.q[j] = ( mat[i * 4 + j] + mat[j * 4 + i] ) * s; + jq.q[k] = ( mat[i * 4 + k] + mat[k * 4 + i] ) * s; + } + + jq.t[0] = mat[0 * 4 + 3]; + jq.t[1] = mat[1 * 4 + 3]; + jq.t[2] = mat[2 * 4 + 3]; + jq.w = 0.0f; + + return jq; +} diff --git a/neo/idlib/geometry/JointTransform.h b/neo/idlib/geometry/JointTransform.h new file mode 100644 index 00000000..7bc7aadc --- /dev/null +++ b/neo/idlib/geometry/JointTransform.h @@ -0,0 +1,544 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __JOINTTRANSFORM_H__ +#define __JOINTTRANSFORM_H__ + +/* +=============================================================================== + + Joint Quaternion + +=============================================================================== +*/ + +class idJointQuat { +public: + const float * ToFloatPtr() const { return q.ToFloatPtr(); } + float * ToFloatPtr() { return q.ToFloatPtr(); } + + idQuat q; + idVec3 t; + float w; +}; + +// offsets for SIMD code +#define JOINTQUAT_SIZE (8*4) // sizeof( idJointQuat ) +#define JOINTQUAT_SIZE_SHIFT 5 // log2( sizeof( idJointQuat ) ) +#define JOINTQUAT_Q_OFFSET (0*4) // offsetof( idJointQuat, q ) +#define JOINTQUAT_T_OFFSET (4*4) // offsetof( idJointQuat, t ) + +assert_sizeof( idJointQuat, JOINTQUAT_SIZE ); +assert_sizeof( idJointQuat, (1< epsilon ) { + return false; + } + } + return true; +} + +/* +======================== +idJointMat::operator== +======================== +*/ +ID_INLINE bool idJointMat::operator==( const idJointMat &a ) const { + return Compare( a ); +} + +/* +======================== +idJointMat::operator!= +======================== +*/ +ID_INLINE bool idJointMat::operator!=( const idJointMat &a ) const { + return !Compare( a ); +} + +/* +======================== +idJointMat::Identity +======================== +*/ +ID_INLINE void idJointMat::Identity() { + mat[0 * 4 + 0] = 1.0f; mat[0 * 4 + 1] = 0.0f; mat[0 * 4 + 2] = 0.0f; mat[0 * 4 + 3] = 0.0f; + mat[1 * 4 + 0] = 0.0f; mat[1 * 4 + 1] = 1.0f; mat[1 * 4 + 2] = 0.0f; mat[1 * 4 + 3] = 0.0f; + mat[2 * 4 + 0] = 0.0f; mat[2 * 4 + 1] = 0.0f; mat[2 * 4 + 2] = 1.0f; mat[2 * 4 + 3] = 0.0f; +} + +/* +======================== +idJointMat::Invert +======================== +*/ +ID_INLINE void idJointMat::Invert() { + float tmp[3]; + + // negate inverse rotated translation part + tmp[0] = mat[0 * 4 + 0] * mat[0 * 4 + 3] + mat[1 * 4 + 0] * mat[1 * 4 + 3] + mat[2 * 4 + 0] * mat[2 * 4 + 3]; + tmp[1] = mat[0 * 4 + 1] * mat[0 * 4 + 3] + mat[1 * 4 + 1] * mat[1 * 4 + 3] + mat[2 * 4 + 1] * mat[2 * 4 + 3]; + tmp[2] = mat[0 * 4 + 2] * mat[0 * 4 + 3] + mat[1 * 4 + 2] * mat[1 * 4 + 3] + mat[2 * 4 + 2] * mat[2 * 4 + 3]; + mat[0 * 4 + 3] = -tmp[0]; + mat[1 * 4 + 3] = -tmp[1]; + mat[2 * 4 + 3] = -tmp[2]; + + // transpose rotation part + tmp[0] = mat[0 * 4 + 1]; + mat[0 * 4 + 1] = mat[1 * 4 + 0]; + mat[1 * 4 + 0] = tmp[0]; + tmp[1] = mat[0 * 4 + 2]; + mat[0 * 4 + 2] = mat[2 * 4 + 0]; + mat[2 * 4 + 0] = tmp[1]; + tmp[2] = mat[1 * 4 + 2]; + mat[1 * 4 + 2] = mat[2 * 4 + 1]; + mat[2 * 4 + 1] = tmp[2]; +} + +/* +======================== +idJointMat::ToMat3 +======================== +*/ +ID_INLINE idMat3 idJointMat::ToMat3() const { + return idMat3( mat[0 * 4 + 0], mat[1 * 4 + 0], mat[2 * 4 + 0], + mat[0 * 4 + 1], mat[1 * 4 + 1], mat[2 * 4 + 1], + mat[0 * 4 + 2], mat[1 * 4 + 2], mat[2 * 4 + 2] ); +} + +/* +======================== +idJointMat::ToMat4 +======================== +*/ +ID_INLINE idMat4 idJointMat::ToMat4() const { + return idMat4( + mat[0 * 4 + 0], mat[0 * 4 + 1], mat[0 * 4 + 2], mat[0 * 4 + 3], + mat[1 * 4 + 0], mat[1 * 4 + 1], mat[1 * 4 + 2], mat[1 * 4 + 3], + mat[2 * 4 + 0], mat[2 * 4 + 1], mat[2 * 4 + 2], mat[2 * 4 + 3], + 0.0f, 0.0f, 0.0f, 1.0f + ); +} + +/* +======================== +idJointMat::FromMat4 +======================== +*/ +ID_INLINE void idJointMat::FromMat4( const idMat4 & m ) { + mat[0*4+0] = m[0][0], mat[0*4+1] = m[0][1], mat[0*4+2] = m[0][2], mat[0*4+3] = m[0][3]; + mat[1*4+0] = m[1][0], mat[1*4+1] = m[1][1], mat[1*4+2] = m[1][2], mat[1*4+3] = m[1][3]; + mat[2*4+0] = m[2][0], mat[2*4+1] = m[2][1], mat[2*4+2] = m[2][2], mat[2*4+3] = m[2][3]; + assert( m[3][0] == 0.0f ); + assert( m[3][1] == 0.0f ); + assert( m[3][2] == 0.0f ); + assert( m[3][3] == 1.0f ); +} + +/* +======================== +idJointMat::ToVec3 +======================== +*/ +ID_INLINE idVec3 idJointMat::ToVec3() const { + return idVec3( mat[0 * 4 + 3], mat[1 * 4 + 3], mat[2 * 4 + 3] ); +} + +/* +======================== +idJointMat::Transform +======================== +*/ +ID_INLINE void idJointMat::Transform( idVec3 &result, const idVec3 &v ) const { + result.x = mat[0 * 4 + 0] * v.x + mat[0 * 4 + 1] * v.y + mat[0 * 4 + 2] * v.z + mat[0 * 4 + 3]; + result.y = mat[1 * 4 + 0] * v.x + mat[1 * 4 + 1] * v.y + mat[1 * 4 + 2] * v.z + mat[1 * 4 + 3]; + result.z = mat[2 * 4 + 0] * v.x + mat[2 * 4 + 1] * v.y + mat[2 * 4 + 2] * v.z + mat[2 * 4 + 3]; +} + +/* +======================== +idJointMat::Rotate +======================== +*/ +ID_INLINE void idJointMat::Rotate( idVec3 &result, const idVec3 &v ) const { + result.x = mat[0 * 4 + 0] * v.x + mat[0 * 4 + 1] * v.y + mat[0 * 4 + 2] * v.z; + result.y = mat[1 * 4 + 0] * v.x + mat[1 * 4 + 1] * v.y + mat[1 * 4 + 2] * v.z; + result.z = mat[2 * 4 + 0] * v.x + mat[2 * 4 + 1] * v.y + mat[2 * 4 + 2] * v.z; +} + +/* +======================== +idJointMat::Mul +======================== +*/ +ID_INLINE void idJointMat::Mul( idJointMat &result, const idJointMat &mat, const float s ) { + result.mat[0 * 4 + 0] = s * mat.mat[0 * 4 + 0]; + result.mat[0 * 4 + 1] = s * mat.mat[0 * 4 + 1]; + result.mat[0 * 4 + 2] = s * mat.mat[0 * 4 + 2]; + result.mat[0 * 4 + 3] = s * mat.mat[0 * 4 + 3]; + result.mat[1 * 4 + 0] = s * mat.mat[1 * 4 + 0]; + result.mat[1 * 4 + 1] = s * mat.mat[1 * 4 + 1]; + result.mat[1 * 4 + 2] = s * mat.mat[1 * 4 + 2]; + result.mat[1 * 4 + 3] = s * mat.mat[1 * 4 + 3]; + result.mat[2 * 4 + 0] = s * mat.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] = s * mat.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] = s * mat.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] = s * mat.mat[2 * 4 + 3]; +} + +/* +======================== +idJointMat::Mad +======================== +*/ +ID_INLINE void idJointMat::Mad( idJointMat &result, const idJointMat &mat, const float s ) { + result.mat[0 * 4 + 0] += s * mat.mat[0 * 4 + 0]; + result.mat[0 * 4 + 1] += s * mat.mat[0 * 4 + 1]; + result.mat[0 * 4 + 2] += s * mat.mat[0 * 4 + 2]; + result.mat[0 * 4 + 3] += s * mat.mat[0 * 4 + 3]; + result.mat[1 * 4 + 0] += s * mat.mat[1 * 4 + 0]; + result.mat[1 * 4 + 1] += s * mat.mat[1 * 4 + 1]; + result.mat[1 * 4 + 2] += s * mat.mat[1 * 4 + 2]; + result.mat[1 * 4 + 3] += s * mat.mat[1 * 4 + 3]; + result.mat[2 * 4 + 0] += s * mat.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] += s * mat.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] += s * mat.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] += s * mat.mat[2 * 4 + 3]; +} + +/* +======================== +idJointMat::Multiply +======================== +*/ +ID_INLINE void idJointMat::Multiply( idJointMat &result, const idJointMat &m1, const idJointMat &m2 ) { + result.mat[0 * 4 + 0] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[0 * 4 + 1] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[0 * 4 + 2] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[0 * 4 + 3] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[0 * 4 + 3]; + + result.mat[1 * 4 + 0] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[1 * 4 + 1] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[1 * 4 + 2] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[1 * 4 + 3] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[1 * 4 + 3]; + + result.mat[2 * 4 + 0] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[2 * 4 + 3]; +} + +/* +======================== +idJointMat::InverseMultiply +======================== +*/ +ID_INLINE void idJointMat::InverseMultiply( idJointMat &result, const idJointMat &m1, const idJointMat &m2 ) { + float dst[3]; + + result.mat[0 * 4 + 0] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[0 * 4 + 1] = m1.mat[0 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[0 * 4 + 2] = m1.mat[0 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 2]; + + result.mat[1 * 4 + 0] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[1 * 4 + 1] = m1.mat[1 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[1 * 4 + 2] = m1.mat[1 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 2]; + + result.mat[2 * 4 + 0] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[2 * 4 + 1] = m1.mat[2 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[2 * 4 + 2] = m1.mat[2 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 2]; + + dst[0] = - ( m2.mat[0 * 4 + 0] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 0] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 0] * m2.mat[2 * 4 + 3] ); + dst[1] = - ( m2.mat[0 * 4 + 1] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 1] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 1] * m2.mat[2 * 4 + 3] ); + dst[2] = - ( m2.mat[0 * 4 + 2] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 2] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 2] * m2.mat[2 * 4 + 3] ); + + result.mat[0 * 4 + 3] = m1.mat[0 * 4 + 0] * dst[0] + m1.mat[0 * 4 + 1] * dst[1] + m1.mat[0 * 4 + 2] * dst[2] + m1.mat[0 * 4 + 3]; + result.mat[1 * 4 + 3] = m1.mat[1 * 4 + 0] * dst[0] + m1.mat[1 * 4 + 1] * dst[1] + m1.mat[1 * 4 + 2] * dst[2] + m1.mat[1 * 4 + 3]; + result.mat[2 * 4 + 3] = m1.mat[2 * 4 + 0] * dst[0] + m1.mat[2 * 4 + 1] * dst[1] + m1.mat[2 * 4 + 2] * dst[2] + m1.mat[2 * 4 + 3]; +} + +#endif /* !__JOINTTRANSFORM_H__ */ diff --git a/neo/idlib/geometry/RenderMatrix.cpp b/neo/idlib/geometry/RenderMatrix.cpp new file mode 100644 index 00000000..618cc16c --- /dev/null +++ b/neo/idlib/geometry/RenderMatrix.cpp @@ -0,0 +1,3238 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../ParallelJobList_JobHeaders.h" +#include "../math/Math.h" +#include "../math/Vector.h" +#include "../math/Matrix.h" +#include "../math/Rotation.h" +#include "../math/Plane.h" +#include "../bv/Sphere.h" +#include "../bv/Bounds.h" +#include "RenderMatrix.h" + +// FIXME: it would be nice if all render matrices were 16-byte aligned +// so there is no need for unaligned loads and stores everywhere + +#ifdef _lint +#undef ID_WIN_X86_SSE2_INTRIN +#endif + +//lint -e438 // the non-SSE code isn't lint friendly, either +//lint -e550 + +#define RENDER_MATRIX_INVERSE_EPSILON 1e-16f // JDC: changed from 1e-14f to allow full wasteland parallel light projections to invert +#define RENDER_MATRIX_INFINITY 1e30f // NOTE: cannot initiaize a vec_float4 with idMath::INFINITY on the SPU +#define RENDER_MATRIX_PROJECTION_EPSILON 0.1f + +#define CLIP_SPACE_OGL // the OpenGL clip space Z is in the range [-1, 1] + +/* +================================================================================================ + +Constant render matrices + +================================================================================================ +*/ + +// identity matrix +ALIGNTYPE16 const idRenderMatrix renderMatrix_identity( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f +); + +// convert from our coordinate system (looking down X) to OpenGL's coordinate system (looking down -Z) +ALIGNTYPE16 const idRenderMatrix renderMatrix_flipToOpenGL( + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f +); + +// OpenGL -1 to 1. +ALIGNTYPE16 const idRenderMatrix renderMatrix_windowSpaceToClipSpace( + 2.0f, 0.0f, 0.0f, -1.0f, + 0.0f, 2.0f, 0.0f, -1.0f, + 0.0f, 0.0f, 2.0f, -1.0f, + 0.0f, 0.0f, 0.0f, 1.0f +); + +/* +================================================================================================ + +SIMD constants + +================================================================================================ +*/ + + +static const __m128i vector_int_1 = _mm_set1_epi32( 1 ); +static const __m128i vector_int_4 = _mm_set1_epi32( 4 ); +static const __m128i vector_int_0123 = _mm_set_epi32( 3, 2, 1, 0 ); +static const __m128 vector_float_mask0 = __m128c( _mm_set1_epi32( 1<<0 ) ); +static const __m128 vector_float_mask1 = __m128c( _mm_set1_epi32( 1<<1 ) ); +static const __m128 vector_float_mask2 = __m128c( _mm_set1_epi32( 1<<2 ) ); +static const __m128 vector_float_mask3 = __m128c( _mm_set1_epi32( 1<<3 ) ); +static const __m128 vector_float_mask4 = __m128c( _mm_set1_epi32( 1<<4 ) ); +static const __m128 vector_float_mask5 = __m128c( _mm_set1_epi32( 1<<5 ) ); +static const __m128 vector_float_sign_bit = __m128c( _mm_set1_epi32( IEEE_FLT_SIGN_MASK ) ); +static const __m128 vector_float_abs_mask = __m128c( _mm_set1_epi32( ~IEEE_FLT_SIGN_MASK ) ); +static const __m128 vector_float_keep_last = __m128c( _mm_set_epi32( -1, 0, 0, 0 ) ); +static const __m128 vector_float_inverse_epsilon = { RENDER_MATRIX_INVERSE_EPSILON, RENDER_MATRIX_INVERSE_EPSILON, RENDER_MATRIX_INVERSE_EPSILON, RENDER_MATRIX_INVERSE_EPSILON }; +static const __m128 vector_float_smallest_non_denorm = { 1.1754944e-038f, 1.1754944e-038f, 1.1754944e-038f, 1.1754944e-038f }; +static const __m128 vector_float_pos_infinity = { RENDER_MATRIX_INFINITY, RENDER_MATRIX_INFINITY, RENDER_MATRIX_INFINITY, RENDER_MATRIX_INFINITY }; +static const __m128 vector_float_neg_infinity = { -RENDER_MATRIX_INFINITY, -RENDER_MATRIX_INFINITY, -RENDER_MATRIX_INFINITY, -RENDER_MATRIX_INFINITY }; +static const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; +static const __m128 vector_float_half = { 0.5f, 0.5f, 0.5f, 0.5f }; +static const __m128 vector_float_neg_half = { -0.5f, -0.5f, -0.5f, -0.5f }; +static const __m128 vector_float_one = { 1.0f, 1.0f, 1.0f, 1.0f }; +static const __m128 vector_float_pos_one = { +1.0f, +1.0f, +1.0f, +1.0f }; +static const __m128 vector_float_neg_one = { -1.0f, -1.0f, -1.0f, -1.0f }; +static const __m128 vector_float_last_one = { 0.0f, 0.0f, 0.0f, 1.0f }; + + +/* +================================================================================================ + +Box definition + +================================================================================================ +*/ + +/* + 4----{E}---5 + + /| /| + Z {H} {I} {F} | + - / | / {J} + 7--{G}-----6 | + | | | | + {L} 0----|-{A}-1 + | / {K} / - + | {D} | {B} Y + |/ |/ + + 3---{C}----2 + + - X + +*/ + +static const short boxPolygonVertices[6][4] = { + { 0, 3, 7, 4 }, // neg-X + { 0, 1, 5, 4 }, // neg-Y + { 0, 1, 2, 3 }, // neg-Z + { 1, 2, 6, 5 }, // pos-X + { 2, 3, 7, 6 }, // pos-Y + { 4, 5, 6, 7 } // pos-Z +}; + +static const short boxEdgeVertices[12][2] = { + /* A = */ { 0, 1 }, /* B = */ { 1, 2 }, /* C = */ { 2, 3 }, /* D = */ { 3, 0 }, // bottom + /* E = */ { 4, 5 }, /* F = */ { 5, 6 }, /* G = */ { 6, 7 }, /* H = */ { 7, 4 }, // top + /* I = */ { 0, 4 }, /* J = */ { 1, 5 }, /* K = */ { 2, 6 }, /* L = */ { 3, 7 } // sides +}; + +static int boxEdgePolygons[12][2] = { + /* A = */ { 1, 2 }, /* B = */ { 3, 2 }, /* C = */ { 4, 2 }, /* D = */ { 0, 2 }, // bottom + /* E = */ { 1, 5 }, /* F = */ { 3, 5 }, /* G = */ { 4, 5 }, /* H = */ { 0, 5 }, // top + /* I = */ { 0, 1 }, /* J = */ { 3, 1 }, /* K = */ { 3, 4 }, /* L = */ { 0, 4 } // sides +}; + +/* +#include + +class idCreateBoxFrontPolygonsForFrontBits { +public: + idCreateBoxFrontPolygonsForFrontBits() { + for ( int i = 0; i < 64; i++ ) { + int frontPolygons[7] = { 0 }; + int numFrontPolygons = 0; + char bits[7] = { 0 }; + for ( int j = 0; j < 6; j++ ) { + if ( ( i & ( 1 << j ) ) != 0 ) { + frontPolygons[numFrontPolygons++] = j; + bits[5 - j] = '1'; + } else { + bits[5 - j] = '0'; + } + } + const char * comment = ( ( i & ( i >> 3 ) & 7 ) != 0 ) ? " invalid" : ""; + if ( i == 0 ) { + comment = " inside the box, every polygon is considered front facing"; + numFrontPolygons = 6; + for ( int j = 0; j < 6; j++ ) { + frontPolygons[j] = j; + } + } + char buffer[1024]; + sprintf( buffer, "{ { %d, %d, %d, %d, %d, %d, %d }, %d }, // %s = %d%s\n", + frontPolygons[0], frontPolygons[1], frontPolygons[2], frontPolygons[3], + frontPolygons[4], frontPolygons[5], frontPolygons[6], + numFrontPolygons, bits, i, comment ); + OutputDebugString( buffer ); + } + } +} createBoxFrontPolygonsForFrontBits; +*/ + +// make sure this is a power of two for fast addressing an array of these without integer multiplication +static const struct frontPolygons_t { + byte indices[7]; + byte count; +} boxFrontPolygonsForFrontBits[64] = { + { { 0, 1, 2, 3, 4, 5, 0 }, 6 }, // 000000 = 0 inside the box, every polygon is considered front facing + { { 0, 0, 0, 0, 0, 0, 0 }, 1 }, // 000001 = 1 + { { 1, 0, 0, 0, 0, 0, 0 }, 1 }, // 000010 = 2 + { { 0, 1, 0, 0, 0, 0, 0 }, 2 }, // 000011 = 3 + { { 2, 0, 0, 0, 0, 0, 0 }, 1 }, // 000100 = 4 + { { 0, 2, 0, 0, 0, 0, 0 }, 2 }, // 000101 = 5 + { { 1, 2, 0, 0, 0, 0, 0 }, 2 }, // 000110 = 6 + { { 0, 1, 2, 0, 0, 0, 0 }, 3 }, // 000111 = 7 + { { 3, 0, 0, 0, 0, 0, 0 }, 1 }, // 001000 = 8 + { { 0, 3, 0, 0, 0, 0, 0 }, 2 }, // 001001 = 9 invalid + { { 1, 3, 0, 0, 0, 0, 0 }, 2 }, // 001010 = 10 + { { 0, 1, 3, 0, 0, 0, 0 }, 3 }, // 001011 = 11 invalid + { { 2, 3, 0, 0, 0, 0, 0 }, 2 }, // 001100 = 12 + { { 0, 2, 3, 0, 0, 0, 0 }, 3 }, // 001101 = 13 invalid + { { 1, 2, 3, 0, 0, 0, 0 }, 3 }, // 001110 = 14 + { { 0, 1, 2, 3, 0, 0, 0 }, 4 }, // 001111 = 15 invalid + { { 4, 0, 0, 0, 0, 0, 0 }, 1 }, // 010000 = 16 + { { 0, 4, 0, 0, 0, 0, 0 }, 2 }, // 010001 = 17 + { { 1, 4, 0, 0, 0, 0, 0 }, 2 }, // 010010 = 18 invalid + { { 0, 1, 4, 0, 0, 0, 0 }, 3 }, // 010011 = 19 invalid + { { 2, 4, 0, 0, 0, 0, 0 }, 2 }, // 010100 = 20 + { { 0, 2, 4, 0, 0, 0, 0 }, 3 }, // 010101 = 21 + { { 1, 2, 4, 0, 0, 0, 0 }, 3 }, // 010110 = 22 invalid + { { 0, 1, 2, 4, 0, 0, 0 }, 4 }, // 010111 = 23 invalid + { { 3, 4, 0, 0, 0, 0, 0 }, 2 }, // 011000 = 24 + { { 0, 3, 4, 0, 0, 0, 0 }, 3 }, // 011001 = 25 invalid + { { 1, 3, 4, 0, 0, 0, 0 }, 3 }, // 011010 = 26 invalid + { { 0, 1, 3, 4, 0, 0, 0 }, 4 }, // 011011 = 27 invalid + { { 2, 3, 4, 0, 0, 0, 0 }, 3 }, // 011100 = 28 + { { 0, 2, 3, 4, 0, 0, 0 }, 4 }, // 011101 = 29 invalid + { { 1, 2, 3, 4, 0, 0, 0 }, 4 }, // 011110 = 30 invalid + { { 0, 1, 2, 3, 4, 0, 0 }, 5 }, // 011111 = 31 invalid + { { 5, 0, 0, 0, 0, 0, 0 }, 1 }, // 100000 = 32 + { { 0, 5, 0, 0, 0, 0, 0 }, 2 }, // 100001 = 33 + { { 1, 5, 0, 0, 0, 0, 0 }, 2 }, // 100010 = 34 + { { 0, 1, 5, 0, 0, 0, 0 }, 3 }, // 100011 = 35 + { { 2, 5, 0, 0, 0, 0, 0 }, 2 }, // 100100 = 36 invalid + { { 0, 2, 5, 0, 0, 0, 0 }, 3 }, // 100101 = 37 invalid + { { 1, 2, 5, 0, 0, 0, 0 }, 3 }, // 100110 = 38 invalid + { { 0, 1, 2, 5, 0, 0, 0 }, 4 }, // 100111 = 39 invalid + { { 3, 5, 0, 0, 0, 0, 0 }, 2 }, // 101000 = 40 + { { 0, 3, 5, 0, 0, 0, 0 }, 3 }, // 101001 = 41 invalid + { { 1, 3, 5, 0, 0, 0, 0 }, 3 }, // 101010 = 42 + { { 0, 1, 3, 5, 0, 0, 0 }, 4 }, // 101011 = 43 invalid + { { 2, 3, 5, 0, 0, 0, 0 }, 3 }, // 101100 = 44 invalid + { { 0, 2, 3, 5, 0, 0, 0 }, 4 }, // 101101 = 45 invalid + { { 1, 2, 3, 5, 0, 0, 0 }, 4 }, // 101110 = 46 invalid + { { 0, 1, 2, 3, 5, 0, 0 }, 5 }, // 101111 = 47 invalid + { { 4, 5, 0, 0, 0, 0, 0 }, 2 }, // 110000 = 48 + { { 0, 4, 5, 0, 0, 0, 0 }, 3 }, // 110001 = 49 + { { 1, 4, 5, 0, 0, 0, 0 }, 3 }, // 110010 = 50 invalid + { { 0, 1, 4, 5, 0, 0, 0 }, 4 }, // 110011 = 51 invalid + { { 2, 4, 5, 0, 0, 0, 0 }, 3 }, // 110100 = 52 invalid + { { 0, 2, 4, 5, 0, 0, 0 }, 4 }, // 110101 = 53 invalid + { { 1, 2, 4, 5, 0, 0, 0 }, 4 }, // 110110 = 54 invalid + { { 0, 1, 2, 4, 5, 0, 0 }, 5 }, // 110111 = 55 invalid + { { 3, 4, 5, 0, 0, 0, 0 }, 3 }, // 111000 = 56 + { { 0, 3, 4, 5, 0, 0, 0 }, 4 }, // 111001 = 57 invalid + { { 1, 3, 4, 5, 0, 0, 0 }, 4 }, // 111010 = 58 invalid + { { 0, 1, 3, 4, 5, 0, 0 }, 5 }, // 111011 = 59 invalid + { { 2, 3, 4, 5, 0, 0, 0 }, 4 }, // 111100 = 60 invalid + { { 0, 2, 3, 4, 5, 0, 0 }, 5 }, // 111101 = 61 invalid + { { 1, 2, 3, 4, 5, 0, 0 }, 5 }, // 111110 = 62 invalid + { { 0, 1, 2, 3, 4, 5, 0 }, 6 }, // 111111 = 63 invalid +}; + +/* +#include + +class idCreateBoxSilhouetteEdgesForFrontBits { +public: + idCreateBoxSilhouetteEdgesForFrontBits() { + for ( int i = 0; i < 64; i++ ) { + int silhouetteEdges[12] = { 0 }; + int numSilhouetteEdges = 0; + + for ( int j = 0; j < 12; j++ ) { + if ( i == 0 || ( ( i >> boxEdgePolygons[j][0] ) & 1 ) != ( ( i >> boxEdgePolygons[j][1] ) & 1 ) ) { + silhouetteEdges[numSilhouetteEdges++] = j; + } + } + + char bits[7] = { 0 }; + for ( int j = 0; j < 6; j++ ) { + if ( ( i & ( 1 << j ) ) != 0 ) { + bits[5 - j] = '1'; + } else { + bits[5 - j] = '0'; + } + } + const char * comment = ( ( i & ( i >> 3 ) & 7 ) != 0 ) ? " invalid" : ""; + if ( i == 0 ) { + comment = " inside the box, every edge is considered part of the silhouette"; + } + char buffer[1024]; + sprintf( buffer, "{ { %2d, %2d, %2d, %2d, %2d, %2d, %2d, %2d, %2d, %2d, %2d, %2d }, %2d }, // %s = %d%s\n", + silhouetteEdges[0], silhouetteEdges[1], silhouetteEdges[2], silhouetteEdges[3], + silhouetteEdges[4], silhouetteEdges[5], silhouetteEdges[6], silhouetteEdges[7], + silhouetteEdges[8], silhouetteEdges[9], silhouetteEdges[10], silhouetteEdges[11], + numSilhouetteEdges, bits, i, comment ); + OutputDebugString( buffer ); + } + } +} createBoxSilhouetteEdgesForFrontBits; +*/ + +// make sure this is a power of two for fast addressing an array of these without integer multiplication +static const struct silhouetteEdges_t { + byte indices[12]; + int32 count; +} boxSilhouetteEdgesForFrontBits[64] = { + { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, 12 }, // 000000 = 0 inside the box, every edge is considered part of the silhouette + { { 3, 7, 8, 11, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 000001 = 1 + { { 0, 4, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 000010 = 2 + { { 0, 3, 4, 7, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 000011 = 3 + { { 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 000100 = 4 + { { 0, 1, 2, 7, 8, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 000101 = 5 + { { 1, 2, 3, 4, 8, 9, 0, 0, 0, 0, 0, 0 }, 6 }, // 000110 = 6 + { { 1, 2, 4, 7, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 000111 = 7 + { { 1, 5, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 001000 = 8 + { { 1, 3, 5, 7, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 001001 = 9 invalid + { { 0, 1, 4, 5, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 001010 = 10 + { { 0, 1, 3, 4, 5, 7, 10, 11, 0, 0, 0, 0 }, 8 }, // 001011 = 11 invalid + { { 0, 2, 3, 5, 9, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 001100 = 12 + { { 0, 2, 5, 7, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 001101 = 13 invalid + { { 2, 3, 4, 5, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 001110 = 14 + { { 2, 4, 5, 7, 10, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 001111 = 15 invalid + { { 2, 6, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 010000 = 16 + { { 2, 3, 6, 7, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 010001 = 17 + { { 0, 2, 4, 6, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 010010 = 18 invalid + { { 0, 2, 3, 4, 6, 7, 9, 10, 0, 0, 0, 0 }, 8 }, // 010011 = 19 invalid + { { 0, 1, 3, 6, 10, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 010100 = 20 + { { 0, 1, 6, 7, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 010101 = 21 + { { 1, 3, 4, 6, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 010110 = 22 invalid + { { 1, 4, 6, 7, 9, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 010111 = 23 invalid + { { 1, 2, 5, 6, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 011000 = 24 + { { 1, 2, 3, 5, 6, 7, 8, 9, 0, 0, 0, 0 }, 8 }, // 011001 = 25 invalid + { { 0, 1, 2, 4, 5, 6, 8, 11, 0, 0, 0, 0 }, 8 }, // 011010 = 26 invalid + { { 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0 }, 8 }, // 011011 = 27 invalid + { { 0, 3, 5, 6, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 011100 = 28 + { { 0, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0 }, 6 }, // 011101 = 29 invalid + { { 3, 4, 5, 6, 8, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 011110 = 30 invalid + { { 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 011111 = 31 invalid + { { 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 100000 = 32 + { { 3, 4, 5, 6, 8, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 100001 = 33 + { { 0, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0 }, 6 }, // 100010 = 34 + { { 0, 3, 5, 6, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 100011 = 35 + { { 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0 }, 8 }, // 100100 = 36 invalid + { { 0, 1, 2, 4, 5, 6, 8, 11, 0, 0, 0, 0 }, 8 }, // 100101 = 37 invalid + { { 1, 2, 3, 5, 6, 7, 8, 9, 0, 0, 0, 0 }, 8 }, // 100110 = 38 invalid + { { 1, 2, 5, 6, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 100111 = 39 invalid + { { 1, 4, 6, 7, 9, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 101000 = 40 + { { 1, 3, 4, 6, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 101001 = 41 invalid + { { 0, 1, 6, 7, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 101010 = 42 + { { 0, 1, 3, 6, 10, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 101011 = 43 invalid + { { 0, 2, 3, 4, 6, 7, 9, 10, 0, 0, 0, 0 }, 8 }, // 101100 = 44 invalid + { { 0, 2, 4, 6, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 101101 = 45 invalid + { { 2, 3, 6, 7, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 101110 = 46 invalid + { { 2, 6, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 101111 = 47 invalid + { { 2, 4, 5, 7, 10, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 110000 = 48 + { { 2, 3, 4, 5, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 110001 = 49 + { { 0, 2, 5, 7, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 110010 = 50 invalid + { { 0, 2, 3, 5, 9, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 110011 = 51 invalid + { { 0, 1, 3, 4, 5, 7, 10, 11, 0, 0, 0, 0 }, 8 }, // 110100 = 52 invalid + { { 0, 1, 4, 5, 8, 10, 0, 0, 0, 0, 0, 0 }, 6 }, // 110101 = 53 invalid + { { 1, 3, 5, 7, 8, 9, 10, 11, 0, 0, 0, 0 }, 8 }, // 110110 = 54 invalid + { { 1, 5, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 110111 = 55 invalid + { { 1, 2, 4, 7, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 111000 = 56 + { { 1, 2, 3, 4, 8, 9, 0, 0, 0, 0, 0, 0 }, 6 }, // 111001 = 57 invalid + { { 0, 1, 2, 7, 8, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 111010 = 58 invalid + { { 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 111011 = 59 invalid + { { 0, 3, 4, 7, 9, 11, 0, 0, 0, 0, 0, 0 }, 6 }, // 111100 = 60 invalid + { { 0, 4, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 111101 = 61 invalid + { { 3, 7, 8, 11, 0, 0, 0, 0, 0, 0, 0, 0 }, 4 }, // 111110 = 62 invalid + { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0 }, // 111111 = 63 invalid +}; + +/* +#include + +class idCreateBoxSilhouetteVerticesForFrontBits { +public: + idCreateBoxSilhouetteVerticesForFrontBits() { + for ( int i = 0; i < 64; i++ ) { + int silhouetteEdges[12] = { 0 }; + int numSilhouetteEdges = 0; + + for ( int j = 0; j < 12; j++ ) { + if ( i == 0 || ( ( i >> boxEdgePolygons[j][0] ) & 1 ) != ( ( i >> boxEdgePolygons[j][1] ) & 1 ) ) { + silhouetteEdges[numSilhouetteEdges++] = j; + } + } + + int silhouetteVertices[8] = { 0 }; + int numSilhouetteVertices = 0; + + int vertex = boxEdgeVertices[silhouetteEdges[0]][0]; + for ( int j = 0; j < 7; j++ ) { + int newVertex = -1; + for ( int j = 0; j < numSilhouetteEdges; j++ ) { + if ( silhouetteEdges[j] == -1 ) { + continue; + } + if ( boxEdgeVertices[silhouetteEdges[j]][0] == vertex ) { + newVertex = boxEdgeVertices[silhouetteEdges[j]][1]; + silhouetteEdges[j] = -1; + break; + } else if ( boxEdgeVertices[silhouetteEdges[j]][1] == vertex ) { + newVertex = boxEdgeVertices[silhouetteEdges[j]][0]; + silhouetteEdges[j] = -1; + break; + } + } + if ( newVertex == -1 ) { + break; + } + silhouetteVertices[numSilhouetteVertices++] = newVertex; + vertex = newVertex; + } + + char bits[7] = { 0 }; + for ( int j = 0; j < 6; j++ ) { + if ( ( i & ( 1 << j ) ) != 0 ) { + bits[5 - j] = '1'; + } else { + bits[5 - j] = '0'; + } + } + const char * comment = ( ( i & ( i >> 3 ) & 7 ) != 0 ) ? " invalid" : ""; + if ( i == 0 ) { + comment = " inside the box, no silhouette"; + } + char buffer[1024]; + sprintf( buffer, "{ { %d, %d, %d, %d, %d, %d, %d }, %d }, // %s = %d%s\n", + silhouetteVertices[0], silhouetteVertices[1], silhouetteVertices[2], silhouetteVertices[3], + silhouetteVertices[4], silhouetteVertices[5], silhouetteVertices[6], numSilhouetteVertices, bits, i, comment ); + OutputDebugString( buffer ); + } + } +} createBoxSilhouetteVerticesForFrontBits; +*/ + +// make sure this is a power of two for fast addressing an array of these without integer multiplication +static const struct silhouetteVertices_t { + byte indices[7]; + byte count; +} boxSilhouetteVerticesForFrontBits[64] = { + { { 1, 2, 3, 0, 4, 5, 6 }, 7 }, // 000000 = 0 inside the box, no vertex is considered part of the silhouette + { { 0, 4, 7, 3, 0, 0, 0 }, 4 }, // 000001 = 1 + { { 1, 5, 4, 0, 0, 0, 0 }, 4 }, // 000010 = 2 + { { 1, 5, 4, 7, 3, 0, 0 }, 6 }, // 000011 = 3 + { { 1, 2, 3, 0, 0, 0, 0 }, 4 }, // 000100 = 4 + { { 1, 2, 3, 7, 4, 0, 0 }, 6 }, // 000101 = 5 + { { 2, 3, 0, 4, 5, 1, 0 }, 6 }, // 000110 = 6 + { { 2, 3, 7, 4, 5, 1, 0 }, 6 }, // 000111 = 7 + { { 2, 6, 5, 1, 0, 0, 0 }, 4 }, // 001000 = 8 + { { 2, 6, 5, 1, 0, 0, 0 }, 4 }, // 001001 = 9 invalid + { { 1, 2, 6, 5, 4, 0, 0 }, 6 }, // 001010 = 10 + { { 1, 2, 6, 5, 4, 7, 3 }, 7 }, // 001011 = 11 invalid + { { 1, 5, 6, 2, 3, 0, 0 }, 6 }, // 001100 = 12 + { { 1, 5, 6, 2, 3, 7, 4 }, 7 }, // 001101 = 13 invalid + { { 3, 0, 4, 5, 6, 2, 0 }, 6 }, // 001110 = 14 + { { 3, 7, 4, 5, 6, 2, 0 }, 6 }, // 001111 = 15 invalid + { { 3, 7, 6, 2, 0, 0, 0 }, 4 }, // 010000 = 16 + { { 3, 0, 4, 7, 6, 2, 0 }, 6 }, // 010001 = 17 + { { 1, 5, 4, 0, 0, 0, 0 }, 4 }, // 010010 = 18 invalid + { { 1, 5, 4, 7, 6, 2, 3 }, 7 }, // 010011 = 19 invalid + { { 1, 2, 6, 7, 3, 0, 0 }, 6 }, // 010100 = 20 + { { 1, 2, 6, 7, 4, 0, 0 }, 6 }, // 010101 = 21 + { { 2, 6, 7, 3, 0, 4, 5 }, 7 }, // 010110 = 22 invalid + { { 2, 6, 7, 4, 5, 1, 0 }, 6 }, // 010111 = 23 invalid + { { 2, 3, 7, 6, 5, 1, 0 }, 6 }, // 011000 = 24 + { { 2, 3, 0, 4, 7, 6, 5 }, 7 }, // 011001 = 25 invalid + { { 1, 2, 3, 7, 6, 5, 4 }, 7 }, // 011010 = 26 invalid + { { 1, 2, 3, 0, 0, 0, 0 }, 4 }, // 011011 = 27 invalid + { { 1, 5, 6, 7, 3, 0, 0 }, 6 }, // 011100 = 28 + { { 1, 5, 6, 7, 4, 0, 0 }, 6 }, // 011101 = 29 invalid + { { 0, 4, 5, 6, 7, 3, 0 }, 6 }, // 011110 = 30 invalid + { { 5, 6, 7, 4, 0, 0, 0 }, 4 }, // 011111 = 31 invalid + { { 5, 6, 7, 4, 0, 0, 0 }, 4 }, // 100000 = 32 + { { 0, 4, 5, 6, 7, 3, 0 }, 6 }, // 100001 = 33 + { { 1, 5, 6, 7, 4, 0, 0 }, 6 }, // 100010 = 34 + { { 1, 5, 6, 7, 3, 0, 0 }, 6 }, // 100011 = 35 + { { 1, 2, 3, 0, 0, 0, 0 }, 4 }, // 100100 = 36 invalid + { { 1, 2, 3, 7, 6, 5, 4 }, 7 }, // 100101 = 37 invalid + { { 2, 3, 0, 4, 7, 6, 5 }, 7 }, // 100110 = 38 invalid + { { 2, 3, 7, 6, 5, 1, 0 }, 6 }, // 100111 = 39 invalid + { { 2, 6, 7, 4, 5, 1, 0 }, 6 }, // 101000 = 40 + { { 2, 6, 7, 3, 0, 4, 5 }, 7 }, // 101001 = 41 invalid + { { 1, 2, 6, 7, 4, 0, 0 }, 6 }, // 101010 = 42 + { { 1, 2, 6, 7, 3, 0, 0 }, 6 }, // 101011 = 43 invalid + { { 1, 5, 4, 7, 6, 2, 3 }, 7 }, // 101100 = 44 invalid + { { 1, 5, 4, 0, 0, 0, 0 }, 4 }, // 101101 = 45 invalid + { { 3, 0, 4, 7, 6, 2, 0 }, 6 }, // 101110 = 46 invalid + { { 3, 7, 6, 2, 0, 0, 0 }, 4 }, // 101111 = 47 invalid + { { 3, 7, 4, 5, 6, 2, 0 }, 6 }, // 110000 = 48 + { { 3, 0, 4, 5, 6, 2, 0 }, 6 }, // 110001 = 49 + { { 1, 5, 6, 2, 3, 7, 4 }, 7 }, // 110010 = 50 invalid + { { 1, 5, 6, 2, 3, 0, 0 }, 6 }, // 110011 = 51 invalid + { { 1, 2, 6, 5, 4, 7, 3 }, 7 }, // 110100 = 52 invalid + { { 1, 2, 6, 5, 4, 0, 0 }, 6 }, // 110101 = 53 invalid + { { 2, 6, 5, 1, 0, 0, 0 }, 4 }, // 110110 = 54 invalid + { { 2, 6, 5, 1, 0, 0, 0 }, 4 }, // 110111 = 55 invalid + { { 2, 3, 7, 4, 5, 1, 0 }, 6 }, // 111000 = 56 + { { 2, 3, 0, 4, 5, 1, 0 }, 6 }, // 111001 = 57 invalid + { { 1, 2, 3, 7, 4, 0, 0 }, 6 }, // 111010 = 58 invalid + { { 1, 2, 3, 0, 0, 0, 0 }, 4 }, // 111011 = 59 invalid + { { 1, 5, 4, 7, 3, 0, 0 }, 6 }, // 111100 = 60 invalid + { { 1, 5, 4, 0, 0, 0, 0 }, 4 }, // 111101 = 61 invalid + { { 0, 4, 7, 3, 0, 0, 0 }, 4 }, // 111110 = 62 invalid + { { 0, 0, 0, 0, 0, 0, 0 }, 0 }, // 111111 = 63 invalid +}; + +/* +======================== +GetBoxFrontBits + +front bits: + bit 0 = neg-X is front facing + bit 1 = neg-Y is front facing + bit 2 = neg-Z is front facing + bit 3 = pos-X is front facing + bit 4 = pos-Y is front facing + bit 5 = pos-Z is front facing +======================== +*/ + +static int GetBoxFrontBits_SSE2( const __m128 & b0, const __m128 & b1, const __m128 & viewOrigin ) { + const __m128 dir0 = _mm_sub_ps( viewOrigin, b0 ); + const __m128 dir1 = _mm_sub_ps( b1, viewOrigin ); + const __m128 d0 = _mm_cmplt_ps( dir0, _mm_setzero_ps() ); + const __m128 d1 = _mm_cmplt_ps( dir1, _mm_setzero_ps() ); + + int frontBits = _mm_movemask_ps( d0 ) | ( _mm_movemask_ps( d1 ) << 3 ); + return frontBits; +} + + +/* +================================================================================================ + +idRenderMatrix implementation + +================================================================================================ +*/ + +/* +======================== +idRenderMatrix::CreateFromOriginAxis +======================== +*/ +void idRenderMatrix::CreateFromOriginAxis( const idVec3 & origin, const idMat3 & axis, idRenderMatrix & out ) { + out[0][0] = axis[0][0]; + out[0][1] = axis[1][0]; + out[0][2] = axis[2][0]; + out[0][3] = origin[0]; + + out[1][0] = axis[0][1]; + out[1][1] = axis[1][1]; + out[1][2] = axis[2][1]; + out[1][3] = origin[1]; + + out[2][0] = axis[0][2]; + out[2][1] = axis[1][2]; + out[2][2] = axis[2][2]; + out[2][3] = origin[2]; + + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +/* +======================== +idRenderMatrix::CreateFromOriginAxisScale +======================== +*/ +void idRenderMatrix::CreateFromOriginAxisScale( const idVec3 & origin, const idMat3 & axis, const idVec3 & scale, idRenderMatrix & out ) { + out[0][0] = axis[0][0] * scale[0]; + out[0][1] = axis[1][0] * scale[1]; + out[0][2] = axis[2][0] * scale[2]; + out[0][3] = origin[0]; + + out[1][0] = axis[0][1] * scale[0]; + out[1][1] = axis[1][1] * scale[1]; + out[1][2] = axis[2][1] * scale[2]; + out[1][3] = origin[1]; + + out[2][0] = axis[0][2] * scale[0]; + out[2][1] = axis[1][2] * scale[1]; + out[2][2] = axis[2][2] * scale[2]; + out[2][3] = origin[2]; + + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +/* +======================== +idRenderMatrix::CreateViewMatrix + +Our axis looks down positive +X, render matrix looks down -Z. +======================== +*/ +void idRenderMatrix::CreateViewMatrix( const idVec3 & origin, const idMat3 & axis, idRenderMatrix & out ) { + out[0][0] = -axis[1][0]; + out[0][1] = -axis[1][1]; + out[0][2] = -axis[1][2]; + out[0][3] = origin[0] * axis[1][0] + origin[1] * axis[1][1] + origin[2] * axis[1][2]; + + out[1][0] = axis[2][0]; + out[1][1] = axis[2][1]; + out[1][2] = axis[2][2]; + out[1][3] = -( origin[0] * axis[2][0] + origin[1] * axis[2][1] + origin[2] * axis[2][2] ); + + out[2][0] = -axis[0][0]; + out[2][1] = -axis[0][1]; + out[2][2] = -axis[0][2]; + out[2][3] = origin[0] * axis[0][0] + origin[1] * axis[0][1] + origin[2] * axis[0][2]; + + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +/* +======================== +idRenderMatrix::CreateProjectionMatrix + +If zFar == 0, an infinite far plane will be used. +======================== +*/ +void idRenderMatrix::CreateProjectionMatrix( float xMin, float xMax, float yMin, float yMax, float zNear, float zFar, idRenderMatrix & out ) { + const float width = xMax - xMin; + const float height = yMax - yMin; + + out[0][0] = 2.0f * zNear / width; + out[0][1] = 0.0f; + out[0][2] = ( xMax + xMin ) / width; // normally 0 + out[0][3] = 0.0f; + + out[1][0] = 0.0f; + out[1][1] = 2.0f * zNear / height; + out[1][2] = ( yMax + yMin ) / height; // normally 0 + out[1][3] = 0.0f; + + if ( zFar <= zNear ) { + // this is the far-plane-at-infinity formulation + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = -1.0f; +#if defined( CLIP_SPACE_D3D ) + // the D3D clip space Z is in range [0,1] instead of [-1,1] + out[2][3] = -zNear; +#else + out[2][3] = -2.0f * zNear; +#endif + } else { + out[2][0] = 0.0f; + out[2][1] = 0.0f; +#if defined( CLIP_SPACE_D3D ) + // the D3D clip space Z is in range [0,1] instead of [-1,1] + out[2][2] = -( zFar ) / ( zFar - zNear ); + out[2][3] = -( zFar * zNear ) / ( zFar - zNear ); +#else + out[2][2] = -( zFar + zNear ) / ( zFar - zNear ); + out[2][3] = -( 2.0f * zFar * zNear ) / ( zFar - zNear ); +#endif + } + + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = -1.0f; + out[3][3] = 0.0f; +} + +/* +======================== +idRenderMatrix::CreateProjectionMatrixFov + +xOffset and yOffset should be in the -1 to 1 range for sub-pixel accumulation jitter. +xOffset can also be used for eye separation when rendering stereo. +======================== +*/ +void idRenderMatrix::CreateProjectionMatrixFov( float xFovDegrees, float yFovDegrees, float zNear, float zFar, float xOffset, float yOffset, idRenderMatrix & out ) { + float xMax = zNear * idMath::Tan( DEG2RAD( xFovDegrees ) * 0.5f ); + float xMin = -xMax; + + float yMax = zNear * idMath::Tan( DEG2RAD( yFovDegrees ) * 0.5f ); + float yMin = -yMax; + + xMin += xOffset; + xMax += xOffset; + + yMin += yOffset; + yMax += yOffset; + + CreateProjectionMatrix( xMin, xMax, yMin, yMax, zNear, zFar, out ); +} + +/* +======================== +idRenderMatrix::OffsetScaleForBounds + +Add the offset to the center of the bounds and scale for the width of the bounds. +The result matrix will transform the unit-cube to exactly cover the bounds. +======================== +*/ +void idRenderMatrix::OffsetScaleForBounds( const idRenderMatrix & src, const idBounds & bounds, idRenderMatrix & out ) { + assert( &src != &out ); + + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + __m128 offset = _mm_mul_ps( _mm_add_ps( b1, b0 ), vector_float_half ); + __m128 scale = _mm_mul_ps( _mm_sub_ps( b1, b0 ), vector_float_half ); + + scale = _mm_or_ps( scale, vector_float_last_one ); + + __m128 a0 = _mm_loadu_ps( src.m + 0*4 ); + __m128 a1 = _mm_loadu_ps( src.m + 1*4 ); + __m128 a2 = _mm_loadu_ps( src.m + 2*4 ); + __m128 a3 = _mm_loadu_ps( src.m + 3*4 ); + + __m128 d0 = _mm_mul_ps( a0, offset ); + __m128 d1 = _mm_mul_ps( a1, offset ); + __m128 d2 = _mm_mul_ps( a2, offset ); + __m128 d3 = _mm_mul_ps( a3, offset ); + + __m128 s0 = _mm_unpacklo_ps( d0, d2 ); // a0, c0, a1, c1 + __m128 s1 = _mm_unpackhi_ps( d0, d2 ); // a2, c2, a3, c3 + __m128 s2 = _mm_unpacklo_ps( d1, d3 ); // b0, d0, b1, d1 + __m128 s3 = _mm_unpackhi_ps( d1, d3 ); // b2, d2, b3, d3 + + __m128 t0 = _mm_unpacklo_ps( s0, s2 ); // a0, b0, c0, d0 + __m128 t1 = _mm_unpackhi_ps( s0, s2 ); // a1, b1, c1, d1 + __m128 t2 = _mm_unpacklo_ps( s1, s3 ); // a2, b2, c2, d2 + + t0 = _mm_add_ps( t0, t1 ); + t0 = _mm_add_ps( t0, t2 ); + + __m128 n0 = _mm_and_ps( _mm_splat_ps( t0, 0 ), vector_float_keep_last ); + __m128 n1 = _mm_and_ps( _mm_splat_ps( t0, 1 ), vector_float_keep_last ); + __m128 n2 = _mm_and_ps( _mm_splat_ps( t0, 2 ), vector_float_keep_last ); + __m128 n3 = _mm_and_ps( _mm_splat_ps( t0, 3 ), vector_float_keep_last ); + + a0 = _mm_madd_ps( a0, scale, n0 ); + a1 = _mm_madd_ps( a1, scale, n1 ); + a2 = _mm_madd_ps( a2, scale, n2 ); + a3 = _mm_madd_ps( a3, scale, n3 ); + + _mm_storeu_ps( out.m + 0*4, a0 ); + _mm_storeu_ps( out.m + 1*4, a1 ); + _mm_storeu_ps( out.m + 2*4, a2 ); + _mm_storeu_ps( out.m + 3*4, a3 ); + +} + +/* +======================== +idRenderMatrix::InverseOffsetScaleForBounds + +Subtract the offset to the center of the bounds and inverse scale for the width of the bounds. +The result matrix will transform the bounds to exactly cover the unit-cube. +======================== +*/ +void idRenderMatrix::InverseOffsetScaleForBounds( const idRenderMatrix & src, const idBounds & bounds, idRenderMatrix & out ) { + assert( &src != &out ); + + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + __m128 offset = _mm_mul_ps( _mm_add_ps( b1, b0 ), vector_float_neg_half ); + __m128 scale = _mm_mul_ps( _mm_sub_ps( b0, b1 ), vector_float_neg_half ); + + scale = _mm_max_ps( scale, vector_float_smallest_non_denorm ); + + __m128 rscale = _mm_rcp32_ps( scale ); + + offset = _mm_mul_ps( offset, rscale ); + + __m128 d0 = _mm_and_ps( _mm_splat_ps( offset, 0 ), vector_float_keep_last ); + __m128 d1 = _mm_and_ps( _mm_splat_ps( offset, 1 ), vector_float_keep_last ); + __m128 d2 = _mm_and_ps( _mm_splat_ps( offset, 2 ), vector_float_keep_last ); + + __m128 a0 = _mm_loadu_ps( src.m + 0*4 ); + __m128 a1 = _mm_loadu_ps( src.m + 1*4 ); + __m128 a2 = _mm_loadu_ps( src.m + 2*4 ); + __m128 a3 = _mm_loadu_ps( src.m + 3*4 ); + + a0 = _mm_madd_ps( a0, _mm_splat_ps( rscale, 0 ), d0 ); + a1 = _mm_madd_ps( a1, _mm_splat_ps( rscale, 1 ), d1 ); + a2 = _mm_madd_ps( a2, _mm_splat_ps( rscale, 2 ), d2 ); + + _mm_storeu_ps( out.m + 0*4, a0 ); + _mm_storeu_ps( out.m + 1*4, a1 ); + _mm_storeu_ps( out.m + 2*4, a2 ); + _mm_storeu_ps( out.m + 3*4, a3 ); + +} + +/* +======================== +idRenderMatrix::Transpose +======================== +*/ +void idRenderMatrix::Transpose( const idRenderMatrix & src, idRenderMatrix & out ) { + assert( &src != &out ); + + + const __m128 a0 = _mm_loadu_ps( src.m + 0*4 ); + const __m128 a1 = _mm_loadu_ps( src.m + 1*4 ); + const __m128 a2 = _mm_loadu_ps( src.m + 2*4 ); + const __m128 a3 = _mm_loadu_ps( src.m + 3*4 ); + + const __m128 r0 = _mm_unpacklo_ps( a0, a2 ); + const __m128 r1 = _mm_unpackhi_ps( a0, a2 ); + const __m128 r2 = _mm_unpacklo_ps( a1, a3 ); + const __m128 r3 = _mm_unpackhi_ps( a1, a3 ); + + const __m128 t0 = _mm_unpacklo_ps( r0, r2 ); + const __m128 t1 = _mm_unpackhi_ps( r0, r2 ); + const __m128 t2 = _mm_unpacklo_ps( r1, r3 ); + const __m128 t3 = _mm_unpackhi_ps( r1, r3 ); + + _mm_storeu_ps( out.m + 0*4, t0 ); + _mm_storeu_ps( out.m + 1*4, t1 ); + _mm_storeu_ps( out.m + 2*4, t2 ); + _mm_storeu_ps( out.m + 3*4, t3 ); + +} + +/* +======================== +idRenderMatrix::Multiply +======================== +*/ +void idRenderMatrix::Multiply( const idRenderMatrix & a, const idRenderMatrix & b, idRenderMatrix & out ) { + + + __m128 a0 = _mm_loadu_ps( a.m + 0*4 ); + __m128 a1 = _mm_loadu_ps( a.m + 1*4 ); + __m128 a2 = _mm_loadu_ps( a.m + 2*4 ); + __m128 a3 = _mm_loadu_ps( a.m + 3*4 ); + + __m128 b0 = _mm_loadu_ps( b.m + 0*4 ); + __m128 b1 = _mm_loadu_ps( b.m + 1*4 ); + __m128 b2 = _mm_loadu_ps( b.m + 2*4 ); + __m128 b3 = _mm_loadu_ps( b.m + 3*4 ); + + __m128 t0 = _mm_mul_ps( _mm_splat_ps( a0, 0 ), b0 ); + __m128 t1 = _mm_mul_ps( _mm_splat_ps( a1, 0 ), b0 ); + __m128 t2 = _mm_mul_ps( _mm_splat_ps( a2, 0 ), b0 ); + __m128 t3 = _mm_mul_ps( _mm_splat_ps( a3, 0 ), b0 ); + + t0 = _mm_madd_ps( _mm_splat_ps( a0, 1 ), b1, t0 ); + t1 = _mm_madd_ps( _mm_splat_ps( a1, 1 ), b1, t1 ); + t2 = _mm_madd_ps( _mm_splat_ps( a2, 1 ), b1, t2 ); + t3 = _mm_madd_ps( _mm_splat_ps( a3, 1 ), b1, t3 ); + + t0 = _mm_madd_ps( _mm_splat_ps( a0, 2 ), b2, t0 ); + t1 = _mm_madd_ps( _mm_splat_ps( a1, 2 ), b2, t1 ); + t2 = _mm_madd_ps( _mm_splat_ps( a2, 2 ), b2, t2 ); + t3 = _mm_madd_ps( _mm_splat_ps( a3, 2 ), b2, t3 ); + + t0 = _mm_madd_ps( _mm_splat_ps( a0, 3 ), b3, t0 ); + t1 = _mm_madd_ps( _mm_splat_ps( a1, 3 ), b3, t1 ); + t2 = _mm_madd_ps( _mm_splat_ps( a2, 3 ), b3, t2 ); + t3 = _mm_madd_ps( _mm_splat_ps( a3, 3 ), b3, t3 ); + + _mm_storeu_ps( out.m + 0*4, t0 ); + _mm_storeu_ps( out.m + 1*4, t1 ); + _mm_storeu_ps( out.m + 2*4, t2 ); + _mm_storeu_ps( out.m + 3*4, t3 ); + +} + +/* +======================== +idRenderMatrix::Inverse + +inverse( M ) = ( 1 / determinant( M ) ) * transpose( cofactor( M ) ) + +This code is based on the code written by Cédric Lallain, published on "Cell Performance" +(by Mike Acton) and released under the BSD 3-Clause ("BSD New" or "BSD Simplified") license. +https://code.google.com/p/cellperformance-snippets/ + +Note that large parallel lights can have very small values in the projection matrix, +scaling tens of thousands of world units down to a 0-1 range, so the determinants +can get really, really small. +======================== +*/ +bool idRenderMatrix::Inverse( const idRenderMatrix & src, idRenderMatrix & out ) { + + + const __m128 r0 = _mm_loadu_ps( src.m + 0 * 4 ); + const __m128 r1 = _mm_loadu_ps( src.m + 1 * 4 ); + const __m128 r2 = _mm_loadu_ps( src.m + 2 * 4 ); + const __m128 r3 = _mm_loadu_ps( src.m + 3 * 4 ); + + // rXuY = row X rotated up by Y floats. + const __m128 r0u1 = _mm_perm_ps( r0, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 r0u2 = _mm_perm_ps( r0, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r0u3 = _mm_perm_ps( r0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 r1u1 = _mm_perm_ps( r1, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 r1u2 = _mm_perm_ps( r1, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r1u3 = _mm_perm_ps( r1, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 r2u1 = _mm_perm_ps( r2, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 r2u2 = _mm_perm_ps( r2, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r2u3 = _mm_perm_ps( r2, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 r3u1 = _mm_perm_ps( r3, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 r3u2 = _mm_perm_ps( r3, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r3u3 = _mm_perm_ps( r3, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 m_r2u2_r3u3 = _mm_mul_ps( r2u2, r3u3 ); + const __m128 m_r1u1_r2u2_r3u3 = _mm_mul_ps( r1u1, m_r2u2_r3u3 ); + const __m128 m_r2u3_r3u1 = _mm_mul_ps( r2u3, r3u1 ); + const __m128 a_m_r1u2_r2u3_r3u1_m_r1u1_r2u2_r3u3 = _mm_madd_ps( r1u2, m_r2u3_r3u1, m_r1u1_r2u2_r3u3 ); + const __m128 m_r2u1_r3u2 = _mm_perm_ps( m_r2u2_r3u3, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 pos_part_det3x3_r0 = _mm_madd_ps( r1u3, m_r2u1_r3u2, a_m_r1u2_r2u3_r3u1_m_r1u1_r2u2_r3u3 ); + const __m128 m_r2u3_r3u2 = _mm_mul_ps( r2u3, r3u2 ); + const __m128 m_r1u1_r2u3_r3u2 = _mm_mul_ps( r1u1, m_r2u3_r3u2 ); + const __m128 m_r2u1_r3u3 = _mm_perm_ps( m_r2u3_r3u1, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 a_m_r1u2_r2u1_r3u3_m_r1u1_r2u3_r3u2 = _mm_madd_ps( r1u2, m_r2u1_r3u3, m_r1u1_r2u3_r3u2 ); + const __m128 m_r2u2_r3u1 = _mm_perm_ps( m_r2u3_r3u2, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 neg_part_det3x3_r0 = _mm_madd_ps( r1u3, m_r2u2_r3u1, a_m_r1u2_r2u1_r3u3_m_r1u1_r2u3_r3u2 ); + const __m128 det3x3_r0 = _mm_sub_ps( pos_part_det3x3_r0, neg_part_det3x3_r0 ); + + const __m128 m_r0u1_r2u2_r3u3 = _mm_mul_ps( r0u1, m_r2u2_r3u3 ); + const __m128 a_m_r0u2_r2u3_r3u1_m_r0u1_r2u2_r3u3 = _mm_madd_ps( r0u2, m_r2u3_r3u1, m_r0u1_r2u2_r3u3 ); + const __m128 pos_part_det3x3_r1 = _mm_madd_ps( r0u3, m_r2u1_r3u2, a_m_r0u2_r2u3_r3u1_m_r0u1_r2u2_r3u3 ); + const __m128 m_r0u1_r2u3_r3u2 = _mm_mul_ps( r0u1, m_r2u3_r3u2 ); + const __m128 a_m_r0u2_r2u1_r3u3_m_r0u1_r2u3_r3u2 = _mm_madd_ps( r0u2, m_r2u1_r3u3, m_r0u1_r2u3_r3u2 ); + const __m128 neg_part_det3x3_r1 = _mm_madd_ps( r0u3, m_r2u2_r3u1, a_m_r0u2_r2u1_r3u3_m_r0u1_r2u3_r3u2 ); + const __m128 det3x3_r1 = _mm_sub_ps( pos_part_det3x3_r1, neg_part_det3x3_r1 ); + + const __m128 m_r0u1_r1u2 = _mm_mul_ps( r0u1, r1u2 ); + const __m128 m_r0u1_r1u2_r2u3 = _mm_mul_ps( m_r0u1_r1u2, r2u3 ); + const __m128 m_r0u2_r1u3 = _mm_perm_ps( m_r0u1_r1u2, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 a_m_r0u2_r1u3_r2u1_m_r0u1_r1u2_r2u3 = _mm_madd_ps( m_r0u2_r1u3, r2u1, m_r0u1_r1u2_r2u3 ); + const __m128 m_r0u3_r1u1 = _mm_mul_ps( r0u3, r1u1 ); + const __m128 pos_part_det3x3_r3 = _mm_madd_ps( m_r0u3_r1u1, r2u2, a_m_r0u2_r1u3_r2u1_m_r0u1_r1u2_r2u3 ); + const __m128 m_r0u1_r1u3 = _mm_perm_ps( m_r0u3_r1u1, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 m_r0u1_r1u3_r2u2 = _mm_mul_ps( m_r0u1_r1u3, r2u2 ); + const __m128 m_r0u2_r1u1 = _mm_mul_ps( r0u2, r1u1 ); + const __m128 a_m_r0u2_r1u1_r2u3_m_r0u1_r1u3_r2u2 = _mm_madd_ps( m_r0u2_r1u1, r2u3, m_r0u1_r1u3_r2u2 ); + const __m128 m_r0u3_r1u2 = _mm_perm_ps( m_r0u2_r1u1, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 neg_part_det3x3_r3 = _mm_madd_ps( m_r0u3_r1u2, r2u1, a_m_r0u2_r1u1_r2u3_m_r0u1_r1u3_r2u2 ); + const __m128 det3x3_r3 = _mm_sub_ps( pos_part_det3x3_r3, neg_part_det3x3_r3 ); + + const __m128 m_r0u1_r1u2_r3u3 = _mm_mul_ps( m_r0u1_r1u2, r3u3 ); + const __m128 a_m_r0u2_r1u3_r3u1_m_r0u1_r1u2_r3u3 = _mm_madd_ps( m_r0u2_r1u3, r3u1, m_r0u1_r1u2_r3u3 ); + const __m128 pos_part_det3x3_r2 = _mm_madd_ps( m_r0u3_r1u1, r3u2, a_m_r0u2_r1u3_r3u1_m_r0u1_r1u2_r3u3 ); + const __m128 m_r0u1_r1u3_r3u2 = _mm_mul_ps( m_r0u1_r1u3, r3u2 ); + const __m128 a_m_r0u2_r1u1_r3u3_m_r0u1_r1u3_r3u2 = _mm_madd_ps( m_r0u2_r1u1, r3u3, m_r0u1_r1u3_r3u2 ); + const __m128 neg_part_det3x3_r2 = _mm_madd_ps( m_r0u3_r1u2, r3u1, a_m_r0u2_r1u1_r3u3_m_r0u1_r1u3_r3u2 ); + const __m128 det3x3_r2 = _mm_sub_ps( pos_part_det3x3_r2, neg_part_det3x3_r2 ); + + const __m128 c_zero = _mm_setzero_ps(); + const __m128 c_mask = _mm_cmpeq_ps( c_zero, c_zero ); + const __m128 c_signmask = _mm_castsi128_ps( _mm_slli_epi32( _mm_castps_si128( c_mask ), 31 ) ); + const __m128 c_znzn = _mm_unpacklo_ps( c_zero, c_signmask ); + const __m128 c_nznz = _mm_unpacklo_ps( c_signmask, c_zero ); + + const __m128 cofactor_r0 = _mm_xor_ps( det3x3_r0, c_znzn ); + const __m128 cofactor_r1 = _mm_xor_ps( det3x3_r1, c_nznz ); + const __m128 cofactor_r2 = _mm_xor_ps( det3x3_r2, c_znzn ); + const __m128 cofactor_r3 = _mm_xor_ps( det3x3_r3, c_nznz ); + + const __m128 dot0 = _mm_mul_ps( r0, cofactor_r0 ); + const __m128 dot1 = _mm_add_ps( dot0, _mm_perm_ps( dot0, _MM_SHUFFLE( 2, 1, 0, 3 ) ) ); + const __m128 det = _mm_add_ps( dot1, _mm_perm_ps( dot1, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + const __m128 absDet = _mm_andnot_ps( c_signmask, det ); + if ( _mm_movemask_ps( _mm_cmplt_ps( absDet, vector_float_inverse_epsilon ) ) & 15 ) { + return false; + } + + const __m128 rcpDet = _mm_rcp32_ps( det ); + + const __m128 hi_part_r0_r2 = _mm_unpacklo_ps( cofactor_r0, cofactor_r2 ); + const __m128 lo_part_r0_r2 = _mm_unpackhi_ps( cofactor_r0, cofactor_r2 ); + const __m128 hi_part_r1_r3 = _mm_unpacklo_ps( cofactor_r1, cofactor_r3 ); + const __m128 lo_part_r1_r3 = _mm_unpackhi_ps( cofactor_r1, cofactor_r3 ); + + const __m128 adjoint_r0 = _mm_unpacklo_ps( hi_part_r0_r2, hi_part_r1_r3 ); + const __m128 adjoint_r1 = _mm_unpackhi_ps( hi_part_r0_r2, hi_part_r1_r3 ); + const __m128 adjoint_r2 = _mm_unpacklo_ps( lo_part_r0_r2, lo_part_r1_r3 ); + const __m128 adjoint_r3 = _mm_unpackhi_ps( lo_part_r0_r2, lo_part_r1_r3 ); + + _mm_storeu_ps( out.m + 0 * 4, _mm_mul_ps( adjoint_r0, rcpDet ) ); + _mm_storeu_ps( out.m + 1 * 4, _mm_mul_ps( adjoint_r1, rcpDet ) ); + _mm_storeu_ps( out.m + 2 * 4, _mm_mul_ps( adjoint_r2, rcpDet ) ); + _mm_storeu_ps( out.m + 3 * 4, _mm_mul_ps( adjoint_r3, rcpDet ) ); + + + return true; +} + +/* +======================== +idRenderMatrix::InverseByTranspose +======================== +*/ +void idRenderMatrix::InverseByTranspose( const idRenderMatrix & src, idRenderMatrix & out ) { + assert( &src != &out ); + assert( src.IsAffineTransform( 0.01f ) ); + + out[0][0] = src[0][0]; + out[1][0] = src[0][1]; + out[2][0] = src[0][2]; + out[3][0] = 0.0f; + out[0][1] = src[1][0]; + out[1][1] = src[1][1]; + out[2][1] = src[1][2]; + out[3][1] = 0.0f; + out[0][2] = src[2][0]; + out[1][2] = src[2][1]; + out[2][2] = src[2][2]; + out[3][2] = 0.0f; + out[0][3] = -( src[0][0] * src[0][3] + src[1][0] * src[1][3] + src[2][0] * src[2][3] ); + out[1][3] = -( src[0][1] * src[0][3] + src[1][1] * src[1][3] + src[2][1] * src[2][3] ); + out[2][3] = -( src[0][2] * src[0][3] + src[1][2] * src[1][3] + src[2][2] * src[2][3] ); + out[3][3] = 1.0f; +} + + +/* +======================== +idRenderMatrix::InverseByDoubles + +This should never be used at run-time. +This is only for tools where more precision is needed. +======================== +*/ +bool idRenderMatrix::InverseByDoubles( const idRenderMatrix & src, idRenderMatrix & out ) { + const int FRL = 4; + + // 84+4+16 = 104 multiplications + // 1 division + + // 2x2 sub-determinants required to calculate 4x4 determinant + const double det2_01_01 = (double)src.m[0*FRL+0] * (double)src.m[1*FRL+1] - (double)src.m[0*FRL+1] * (double)src.m[1*FRL+0]; + const double det2_01_02 = (double)src.m[0*FRL+0] * (double)src.m[1*FRL+2] - (double)src.m[0*FRL+2] * (double)src.m[1*FRL+0]; + const double det2_01_03 = (double)src.m[0*FRL+0] * (double)src.m[1*FRL+3] - (double)src.m[0*FRL+3] * (double)src.m[1*FRL+0]; + const double det2_01_12 = (double)src.m[0*FRL+1] * (double)src.m[1*FRL+2] - (double)src.m[0*FRL+2] * (double)src.m[1*FRL+1]; + const double det2_01_13 = (double)src.m[0*FRL+1] * (double)src.m[1*FRL+3] - (double)src.m[0*FRL+3] * (double)src.m[1*FRL+1]; + const double det2_01_23 = (double)src.m[0*FRL+2] * (double)src.m[1*FRL+3] - (double)src.m[0*FRL+3] * (double)src.m[1*FRL+2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + const double det3_201_012 = (double)src.m[2*FRL+0] * det2_01_12 - (double)src.m[2*FRL+1] * det2_01_02 + (double)src.m[2*FRL+2] * det2_01_01; + const double det3_201_013 = (double)src.m[2*FRL+0] * det2_01_13 - (double)src.m[2*FRL+1] * det2_01_03 + (double)src.m[2*FRL+3] * det2_01_01; + const double det3_201_023 = (double)src.m[2*FRL+0] * det2_01_23 - (double)src.m[2*FRL+2] * det2_01_03 + (double)src.m[2*FRL+3] * det2_01_02; + const double det3_201_123 = (double)src.m[2*FRL+1] * det2_01_23 - (double)src.m[2*FRL+2] * det2_01_13 + (double)src.m[2*FRL+3] * det2_01_12; + + const double det = ( - det3_201_123 * (double)src.m[3*FRL+0] + det3_201_023 * (double)src.m[3*FRL+1] - det3_201_013 * (double)src.m[3*FRL+2] + det3_201_012 * (double)src.m[3*FRL+3] ); + + const double rcpDet = 1.0f / det; + + // remaining 2x2 sub-determinants + const double det2_03_01 = (double)src.m[0*FRL+0] * (double)src.m[3*FRL+1] - (double)src.m[0*FRL+1] * (double)src.m[3*FRL+0]; + const double det2_03_02 = (double)src.m[0*FRL+0] * (double)src.m[3*FRL+2] - (double)src.m[0*FRL+2] * (double)src.m[3*FRL+0]; + const double det2_03_03 = (double)src.m[0*FRL+0] * (double)src.m[3*FRL+3] - (double)src.m[0*FRL+3] * (double)src.m[3*FRL+0]; + const double det2_03_12 = (double)src.m[0*FRL+1] * (double)src.m[3*FRL+2] - (double)src.m[0*FRL+2] * (double)src.m[3*FRL+1]; + const double det2_03_13 = (double)src.m[0*FRL+1] * (double)src.m[3*FRL+3] - (double)src.m[0*FRL+3] * (double)src.m[3*FRL+1]; + const double det2_03_23 = (double)src.m[0*FRL+2] * (double)src.m[3*FRL+3] - (double)src.m[0*FRL+3] * (double)src.m[3*FRL+2]; + + const double det2_13_01 = (double)src.m[1*FRL+0] * (double)src.m[3*FRL+1] - (double)src.m[1*FRL+1] * (double)src.m[3*FRL+0]; + const double det2_13_02 = (double)src.m[1*FRL+0] * (double)src.m[3*FRL+2] - (double)src.m[1*FRL+2] * (double)src.m[3*FRL+0]; + const double det2_13_03 = (double)src.m[1*FRL+0] * (double)src.m[3*FRL+3] - (double)src.m[1*FRL+3] * (double)src.m[3*FRL+0]; + const double det2_13_12 = (double)src.m[1*FRL+1] * (double)src.m[3*FRL+2] - (double)src.m[1*FRL+2] * (double)src.m[3*FRL+1]; + const double det2_13_13 = (double)src.m[1*FRL+1] * (double)src.m[3*FRL+3] - (double)src.m[1*FRL+3] * (double)src.m[3*FRL+1]; + const double det2_13_23 = (double)src.m[1*FRL+2] * (double)src.m[3*FRL+3] - (double)src.m[1*FRL+3] * (double)src.m[3*FRL+2]; + + // remaining 3x3 sub-determinants + const double det3_203_012 = (double)src.m[2*FRL+0] * det2_03_12 - (double)src.m[2*FRL+1] * det2_03_02 + (double)src.m[2*FRL+2] * det2_03_01; + const double det3_203_013 = (double)src.m[2*FRL+0] * det2_03_13 - (double)src.m[2*FRL+1] * det2_03_03 + (double)src.m[2*FRL+3] * det2_03_01; + const double det3_203_023 = (double)src.m[2*FRL+0] * det2_03_23 - (double)src.m[2*FRL+2] * det2_03_03 + (double)src.m[2*FRL+3] * det2_03_02; + const double det3_203_123 = (double)src.m[2*FRL+1] * det2_03_23 - (double)src.m[2*FRL+2] * det2_03_13 + (double)src.m[2*FRL+3] * det2_03_12; + + const double det3_213_012 = (double)src.m[2*FRL+0] * det2_13_12 - (double)src.m[2*FRL+1] * det2_13_02 + (double)src.m[2*FRL+2] * det2_13_01; + const double det3_213_013 = (double)src.m[2*FRL+0] * det2_13_13 - (double)src.m[2*FRL+1] * det2_13_03 + (double)src.m[2*FRL+3] * det2_13_01; + const double det3_213_023 = (double)src.m[2*FRL+0] * det2_13_23 - (double)src.m[2*FRL+2] * det2_13_03 + (double)src.m[2*FRL+3] * det2_13_02; + const double det3_213_123 = (double)src.m[2*FRL+1] * det2_13_23 - (double)src.m[2*FRL+2] * det2_13_13 + (double)src.m[2*FRL+3] * det2_13_12; + + const double det3_301_012 = (double)src.m[3*FRL+0] * det2_01_12 - (double)src.m[3*FRL+1] * det2_01_02 + (double)src.m[3*FRL+2] * det2_01_01; + const double det3_301_013 = (double)src.m[3*FRL+0] * det2_01_13 - (double)src.m[3*FRL+1] * det2_01_03 + (double)src.m[3*FRL+3] * det2_01_01; + const double det3_301_023 = (double)src.m[3*FRL+0] * det2_01_23 - (double)src.m[3*FRL+2] * det2_01_03 + (double)src.m[3*FRL+3] * det2_01_02; + const double det3_301_123 = (double)src.m[3*FRL+1] * det2_01_23 - (double)src.m[3*FRL+2] * det2_01_13 + (double)src.m[3*FRL+3] * det2_01_12; + + out.m[0*FRL+0] = (float)( - det3_213_123 * rcpDet ); + out.m[1*FRL+0] = (float)( + det3_213_023 * rcpDet ); + out.m[2*FRL+0] = (float)( - det3_213_013 * rcpDet ); + out.m[3*FRL+0] = (float)( + det3_213_012 * rcpDet ); + + out.m[0*FRL+1] = (float)( + det3_203_123 * rcpDet ); + out.m[1*FRL+1] = (float)( - det3_203_023 * rcpDet ); + out.m[2*FRL+1] = (float)( + det3_203_013 * rcpDet ); + out.m[3*FRL+1] = (float)( - det3_203_012 * rcpDet ); + + out.m[0*FRL+2] = (float)( + det3_301_123 * rcpDet ); + out.m[1*FRL+2] = (float)( - det3_301_023 * rcpDet ); + out.m[2*FRL+2] = (float)( + det3_301_013 * rcpDet ); + out.m[3*FRL+2] = (float)( - det3_301_012 * rcpDet ); + + out.m[0*FRL+3] = (float)( - det3_201_123 * rcpDet ); + out.m[1*FRL+3] = (float)( + det3_201_023 * rcpDet ); + out.m[2*FRL+3] = (float)( - det3_201_013 * rcpDet ); + out.m[3*FRL+3] = (float)( + det3_201_012 * rcpDet ); + + return true; +} + + +/* +======================== +DeterminantIsNegative +======================== +*/ + +void DeterminantIsNegative( bool & negativeDeterminant, const __m128 & r0, const __m128 & r1, const __m128 & r2, const __m128 & r3 ) { + + const __m128 r1u1 = _mm_perm_ps( r1, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 r1u2 = _mm_perm_ps( r1, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r1u3 = _mm_perm_ps( r1, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 r2u2 = _mm_perm_ps( r2, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r2u3 = _mm_perm_ps( r2, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 r3u1 = _mm_perm_ps( r3, _MM_SHUFFLE( 2, 1, 0, 3 ) ); + const __m128 r3u2 = _mm_perm_ps( r3, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 r3u3 = _mm_perm_ps( r3, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 m_r2u2_r3u3 = _mm_mul_ps( r2u2, r3u3 ); + const __m128 m_r1u1_r2u2_r3u3 = _mm_mul_ps( r1u1, m_r2u2_r3u3 ); + const __m128 m_r2u3_r3u1 = _mm_mul_ps( r2u3, r3u1 ); + const __m128 a_m_r1u2_r2u3_r3u1_m_r1u1_r2u2_r3u3 = _mm_madd_ps( r1u2, m_r2u3_r3u1, m_r1u1_r2u2_r3u3 ); + const __m128 m_r2u1_r3u2 = _mm_perm_ps( m_r2u2_r3u3, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 pos_part_det3x3_r0 = _mm_madd_ps( r1u3, m_r2u1_r3u2, a_m_r1u2_r2u3_r3u1_m_r1u1_r2u2_r3u3 ); + const __m128 m_r2u3_r3u2 = _mm_mul_ps( r2u3, r3u2 ); + const __m128 m_r1u1_r2u3_r3u2 = _mm_mul_ps( r1u1, m_r2u3_r3u2 ); + const __m128 m_r2u1_r3u3 = _mm_perm_ps( m_r2u3_r3u1, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + const __m128 a_m_r1u2_r2u1_r3u3_m_r1u1_r2u3_r3u2 = _mm_madd_ps( r1u2, m_r2u1_r3u3, m_r1u1_r2u3_r3u2 ); + const __m128 m_r2u2_r3u1 = _mm_perm_ps( m_r2u3_r3u2, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 neg_part_det3x3_r0 = _mm_madd_ps( r1u3, m_r2u2_r3u1, a_m_r1u2_r2u1_r3u3_m_r1u1_r2u3_r3u2 ); + const __m128 det3x3_r0 = _mm_sub_ps( pos_part_det3x3_r0, neg_part_det3x3_r0 ); + + const __m128 c_zero = _mm_setzero_ps(); + const __m128 c_mask = _mm_cmpeq_ps( c_zero, c_zero ); + const __m128 c_signmask = _mm_castsi128_ps( _mm_slli_epi32( _mm_castps_si128( c_mask ), 31 ) ); + const __m128 c_znzn = _mm_unpacklo_ps( c_zero, c_signmask ); + + const __m128 cofactor_r0 = _mm_xor_ps( det3x3_r0, c_znzn ); + + const __m128 dot0 = _mm_mul_ps( r0, cofactor_r0 ); + const __m128 dot1 = _mm_add_ps( dot0, _mm_perm_ps( dot0, _MM_SHUFFLE( 2, 1, 0, 3 ) ) ); + const __m128 det = _mm_add_ps( dot1, _mm_perm_ps( dot1, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + const __m128 result = _mm_cmpgt_ps( c_zero, det ); + + negativeDeterminant = _mm_movemask_ps( result ) & 1; +} + + +/* +======================== +idRenderMatrix::CopyMatrix +======================== +*/ +void idRenderMatrix::CopyMatrix( const idRenderMatrix & matrix, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3 ) { + assert_16_byte_aligned( row0.ToFloatPtr() ); + assert_16_byte_aligned( row1.ToFloatPtr() ); + assert_16_byte_aligned( row2.ToFloatPtr() ); + assert_16_byte_aligned( row3.ToFloatPtr() ); + + + const __m128 r0 = _mm_loadu_ps( matrix.m + 0 * 4 ); + const __m128 r1 = _mm_loadu_ps( matrix.m + 1 * 4 ); + const __m128 r2 = _mm_loadu_ps( matrix.m + 2 * 4 ); + const __m128 r3 = _mm_loadu_ps( matrix.m + 3 * 4 ); + + _mm_store_ps( row0.ToFloatPtr(), r0 ); + _mm_store_ps( row1.ToFloatPtr(), r1 ); + _mm_store_ps( row2.ToFloatPtr(), r2 ); + _mm_store_ps( row3.ToFloatPtr(), r3 ); + +} + +/* +======================== +idRenderMatrix::SetMVP +======================== +*/ +void idRenderMatrix::SetMVP( const idRenderMatrix & mvp, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3, bool & negativeDeterminant ) { + assert_16_byte_aligned( row0.ToFloatPtr() ); + assert_16_byte_aligned( row1.ToFloatPtr() ); + assert_16_byte_aligned( row2.ToFloatPtr() ); + assert_16_byte_aligned( row3.ToFloatPtr() ); + + + const __m128 r0 = _mm_loadu_ps( mvp.m + 0 * 4 ); + const __m128 r1 = _mm_loadu_ps( mvp.m + 1 * 4 ); + const __m128 r2 = _mm_loadu_ps( mvp.m + 2 * 4 ); + const __m128 r3 = _mm_loadu_ps( mvp.m + 3 * 4 ); + + _mm_store_ps( row0.ToFloatPtr(), r0 ); + _mm_store_ps( row1.ToFloatPtr(), r1 ); + _mm_store_ps( row2.ToFloatPtr(), r2 ); + _mm_store_ps( row3.ToFloatPtr(), r3 ); + + DeterminantIsNegative( negativeDeterminant, r0, r1, r2, r3 ); + +} + +/* +======================== +idRenderMatrix::SetMVPForBounds +======================== +*/ +void idRenderMatrix::SetMVPForBounds( const idRenderMatrix & mvp, const idBounds & bounds, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3, bool & negativeDeterminant ) { + assert_16_byte_aligned( row0.ToFloatPtr() ); + assert_16_byte_aligned( row1.ToFloatPtr() ); + assert_16_byte_aligned( row2.ToFloatPtr() ); + assert_16_byte_aligned( row3.ToFloatPtr() ); + + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + __m128 offset = _mm_mul_ps( _mm_add_ps( b1, b0 ), vector_float_half ); + __m128 scale = _mm_mul_ps( _mm_sub_ps( b1, b0 ), vector_float_half ); + + scale = _mm_or_ps( scale, vector_float_last_one ); + + __m128 r0 = _mm_loadu_ps( mvp.m + 0 * 4 ); + __m128 r1 = _mm_loadu_ps( mvp.m + 1 * 4 ); + __m128 r2 = _mm_loadu_ps( mvp.m + 2 * 4 ); + __m128 r3 = _mm_loadu_ps( mvp.m + 3 * 4 ); + + __m128 d0 = _mm_mul_ps( r0, offset ); + __m128 d1 = _mm_mul_ps( r1, offset ); + __m128 d2 = _mm_mul_ps( r2, offset ); + __m128 d3 = _mm_mul_ps( r3, offset ); + + __m128 s0 = _mm_unpacklo_ps( d0, d2 ); // a0, c0, a1, c1 + __m128 s1 = _mm_unpackhi_ps( d0, d2 ); // a2, c2, a3, c3 + __m128 s2 = _mm_unpacklo_ps( d1, d3 ); // b0, d0, b1, d1 + __m128 s3 = _mm_unpackhi_ps( d1, d3 ); // b2, d2, b3, d3 + + __m128 t0 = _mm_unpacklo_ps( s0, s2 ); // a0, b0, c0, d0 + __m128 t1 = _mm_unpackhi_ps( s0, s2 ); // a1, b1, c1, d1 + __m128 t2 = _mm_unpacklo_ps( s1, s3 ); // a2, b2, c2, d2 + + t0 = _mm_add_ps( t0, t1 ); + t0 = _mm_add_ps( t0, t2 ); + + __m128 n0 = _mm_and_ps( _mm_splat_ps( t0, 0 ), vector_float_keep_last ); + __m128 n1 = _mm_and_ps( _mm_splat_ps( t0, 1 ), vector_float_keep_last ); + __m128 n2 = _mm_and_ps( _mm_splat_ps( t0, 2 ), vector_float_keep_last ); + __m128 n3 = _mm_and_ps( _mm_splat_ps( t0, 3 ), vector_float_keep_last ); + + r0 = _mm_madd_ps( r0, scale, n0 ); + r1 = _mm_madd_ps( r1, scale, n1 ); + r2 = _mm_madd_ps( r2, scale, n2 ); + r3 = _mm_madd_ps( r3, scale, n3 ); + + _mm_store_ps( row0.ToFloatPtr(), r0 ); + _mm_store_ps( row1.ToFloatPtr(), r1 ); + _mm_store_ps( row2.ToFloatPtr(), r2 ); + _mm_store_ps( row3.ToFloatPtr(), r3 ); + + DeterminantIsNegative( negativeDeterminant, r0, r1, r2, r3 ); + +} + +/* +======================== +idRenderMatrix::SetMVPForInverseProject +======================== +*/ +void idRenderMatrix::SetMVPForInverseProject( const idRenderMatrix & mvp, const idRenderMatrix & inverseProject, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3, bool & negativeDeterminant ) { + assert_16_byte_aligned( row0.ToFloatPtr() ); + assert_16_byte_aligned( row1.ToFloatPtr() ); + assert_16_byte_aligned( row2.ToFloatPtr() ); + assert_16_byte_aligned( row3.ToFloatPtr() ); + + + __m128 r0 = _mm_loadu_ps( mvp.m + 0 * 4 ); + __m128 r1 = _mm_loadu_ps( mvp.m + 1 * 4 ); + __m128 r2 = _mm_loadu_ps( mvp.m + 2 * 4 ); + __m128 r3 = _mm_loadu_ps( mvp.m + 3 * 4 ); + + __m128 p0 = _mm_loadu_ps( inverseProject.m + 0 * 4 ); + __m128 p1 = _mm_loadu_ps( inverseProject.m + 1 * 4 ); + __m128 p2 = _mm_loadu_ps( inverseProject.m + 2 * 4 ); + __m128 p3 = _mm_loadu_ps( inverseProject.m + 3 * 4 ); + + __m128 t0 = _mm_mul_ps( _mm_splat_ps( r0, 0 ), p0 ); + __m128 t1 = _mm_mul_ps( _mm_splat_ps( r1, 0 ), p0 ); + __m128 t2 = _mm_mul_ps( _mm_splat_ps( r2, 0 ), p0 ); + __m128 t3 = _mm_mul_ps( _mm_splat_ps( r3, 0 ), p0 ); + + t0 = _mm_madd_ps( _mm_splat_ps( r0, 1 ), p1, t0 ); + t1 = _mm_madd_ps( _mm_splat_ps( r1, 1 ), p1, t1 ); + t2 = _mm_madd_ps( _mm_splat_ps( r2, 1 ), p1, t2 ); + t3 = _mm_madd_ps( _mm_splat_ps( r3, 1 ), p1, t3 ); + + t0 = _mm_madd_ps( _mm_splat_ps( r0, 2 ), p2, t0 ); + t1 = _mm_madd_ps( _mm_splat_ps( r1, 2 ), p2, t1 ); + t2 = _mm_madd_ps( _mm_splat_ps( r2, 2 ), p2, t2 ); + t3 = _mm_madd_ps( _mm_splat_ps( r3, 2 ), p2, t3 ); + + t0 = _mm_madd_ps( _mm_splat_ps( r0, 3 ), p3, t0 ); + t1 = _mm_madd_ps( _mm_splat_ps( r1, 3 ), p3, t1 ); + t2 = _mm_madd_ps( _mm_splat_ps( r2, 3 ), p3, t2 ); + t3 = _mm_madd_ps( _mm_splat_ps( r3, 3 ), p3, t3 ); + + _mm_store_ps( row0.ToFloatPtr(), t0 ); + _mm_store_ps( row1.ToFloatPtr(), t1 ); + _mm_store_ps( row2.ToFloatPtr(), t2 ); + _mm_store_ps( row3.ToFloatPtr(), t3 ); + + DeterminantIsNegative( negativeDeterminant, t0, t1, t2, t3 ); + +} + +/* +======================== +idRenderMatrix::CullPointToMVPbits + +Returns true if the point transformed by the given Model View Projection (MVP) matrix is +outside the clip space. + +Normally the clip space extends from -1.0 to 1.0 on each axis, but by setting 'zeroToOne' +to true, the clip space will extend from 0.0 to 1.0 on each axis for a light projection matrix. +======================== +*/ +bool idRenderMatrix::CullPointToMVPbits( const idRenderMatrix & mvp, const idVec3 & p, byte * outBits, bool zeroToOne ) { + + idVec4 c; + for ( int i = 0; i < 4; i++ ) { + c[i] = p[0] * mvp[i][0] + p[1] * mvp[i][1] + p[2] * mvp[i][2] + mvp[i][3]; + } + + const float minW = zeroToOne ? 0.0f : -c[3]; + const float maxW = c[3]; +#if defined( CLIP_SPACE_D3D ) // the D3D clip space Z is in the range [0,1] so always compare Z vs zero whether 'zeroToOne' is true or false + const float minZ = 0.0f; +#else + const float minZ = minW; +#endif + + int bits = 0; + if ( c[0] > minW ) { bits |= ( 1 << 0 ); } + if ( c[0] < maxW ) { bits |= ( 1 << 1 ); } + if ( c[1] > minW ) { bits |= ( 1 << 2 ); } + if ( c[1] < maxW ) { bits |= ( 1 << 3 ); } + if ( c[2] > minZ ) { bits |= ( 1 << 4 ); } // NOTE: using minZ + if ( c[2] < maxW ) { bits |= ( 1 << 5 ); } + + // store out a bit set for each side where the point is outside the clip space + *outBits = (byte)( bits ^ 63 ); + + // if any bits weren't set, the point is completely off one side of the frustum + return ( bits != 63 ); +} + +/* +======================== +idRenderMatrix::CullBoundsToMVPbits + +Returns true if nothing contained in the bounds is transformed by the given +Model View Projection (MVP) matrix to anything inside the clip space. + +Normally the clip space extends from -1.0 to 1.0 on each axis, but by setting 'zeroToOne' +to true, the clip space will extend from 0.0 to 1.0 on each axis for a light projection matrix. + +When all the corners of the bounding box are behind one of the six frustum planes, the box is +culled. This is conservative, because some boxes may "cross corners" and can be in front of a +frustum plane, but only while also being behind another one. +======================== +*/ +bool idRenderMatrix::CullBoundsToMVPbits( const idRenderMatrix & mvp, const idBounds & bounds, byte * outBits, bool zeroToOne ) { + + + __m128 mvp0 = _mm_loadu_ps( mvp[0] ); + __m128 mvp1 = _mm_loadu_ps( mvp[1] ); + __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + __m128 minMul = zeroToOne ? vector_float_zero : vector_float_neg_one; + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + // take the four points on the X-Y plane + __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + // compute four partial X,Y,Z,W values + __m128 parx = _mm_splat_ps( mvp0, 3 ); + __m128 pary = _mm_splat_ps( mvp1, 3 ); + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parx = _mm_madd_ps( vx, _mm_splat_ps( mvp0, 0 ), parx ); + pary = _mm_madd_ps( vx, _mm_splat_ps( mvp1, 0 ), pary ); + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parx = _mm_madd_ps( vy, _mm_splat_ps( mvp0, 1 ), parx ); + pary = _mm_madd_ps( vy, _mm_splat_ps( mvp1, 1 ), pary ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + + // compute full X,Y,Z,W values + __m128 mvp0Z = _mm_splat_ps( mvp0, 2 ); + __m128 mvp1Z = _mm_splat_ps( mvp1, 2 ); + __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + __m128 x0 = _mm_madd_ps( vz0, mvp0Z, parx ); + __m128 y0 = _mm_madd_ps( vz0, mvp1Z, pary ); + __m128 z0 = _mm_madd_ps( vz0, mvp2Z, parz ); + __m128 w0 = _mm_madd_ps( vz0, mvp3Z, parw ); + + __m128 x1 = _mm_madd_ps( vz1, mvp0Z, parx ); + __m128 y1 = _mm_madd_ps( vz1, mvp1Z, pary ); + __m128 z1 = _mm_madd_ps( vz1, mvp2Z, parz ); + __m128 w1 = _mm_madd_ps( vz1, mvp3Z, parw ); + + __m128 maxW0 = w0; + __m128 maxW1 = w1; + __m128 minW0 = _mm_mul_ps( w0, minMul ); + __m128 minW1 = _mm_mul_ps( w1, minMul ); +#if defined( CLIP_SPACE_D3D ) // the D3D clip space Z is in the range [0,1] so always compare Z vs zero whether 'zeroToOne' is true or false + __m128 minZ0 = vector_float_zero; + __m128 minZ1 = vector_float_zero; +#else + __m128 minZ0 = minW0; + __m128 minZ1 = minW1; +#endif + + __m128 cullBits0 = _mm_cmpgt_ps( x0, minW0 ); + __m128 cullBits1 = _mm_cmpgt_ps( maxW0, x0 ); + __m128 cullBits2 = _mm_cmpgt_ps( y0, minW0 ); + __m128 cullBits3 = _mm_cmpgt_ps( maxW0, y0 ); + __m128 cullBits4 = _mm_cmpgt_ps( z0, minZ0 ); // NOTE: using minZ0 + __m128 cullBits5 = _mm_cmpgt_ps( maxW0, z0 ); + + cullBits0 = _mm_or_ps( cullBits0, _mm_cmpgt_ps( x1, minW1 ) ); + cullBits1 = _mm_or_ps( cullBits1, _mm_cmpgt_ps( maxW1, x1 ) ); + cullBits2 = _mm_or_ps( cullBits2, _mm_cmpgt_ps( y1, minW1 ) ); + cullBits3 = _mm_or_ps( cullBits3, _mm_cmpgt_ps( maxW1, y1 ) ); + cullBits4 = _mm_or_ps( cullBits4, _mm_cmpgt_ps( z1, minZ1 ) ); // NOTE: using minZ1 + cullBits5 = _mm_or_ps( cullBits5, _mm_cmpgt_ps( maxW1, z1 ) ); + + cullBits0 = _mm_and_ps( cullBits0, vector_float_mask0 ); + cullBits1 = _mm_and_ps( cullBits1, vector_float_mask1 ); + cullBits2 = _mm_and_ps( cullBits2, vector_float_mask2 ); + cullBits3 = _mm_and_ps( cullBits3, vector_float_mask3 ); + cullBits4 = _mm_and_ps( cullBits4, vector_float_mask4 ); + cullBits5 = _mm_and_ps( cullBits5, vector_float_mask5 ); + + cullBits0 = _mm_or_ps( cullBits0, cullBits1 ); + cullBits2 = _mm_or_ps( cullBits2, cullBits3 ); + cullBits4 = _mm_or_ps( cullBits4, cullBits5 ); + cullBits0 = _mm_or_ps( cullBits0, cullBits2 ); + cullBits0 = _mm_or_ps( cullBits0, cullBits4 ); + + cullBits0 = _mm_or_ps( cullBits0, _mm_perm_ps( cullBits0, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + cullBits0 = _mm_or_ps( cullBits0, _mm_perm_ps( cullBits0, _MM_SHUFFLE( 0, 1, 0, 1 ) ) ); + + int bits = _mm_cvtsi128_si32( (const __m128i &)cullBits0 ); + + *outBits = (byte)( bits ^ 63 ); + + return ( bits != 63 ); + +} + +/* +======================== +idRenderMatrix::CullExtrudedBoundsToMVPbits + +Returns true if nothing contained in the extruded bounds is transformed by the +given Model View Projection (MVP) matrix to anything inside the clip space. + +The given bounds is extruded in the 'extrudeDirection' up to the 'clipPlane'. + +Normally the clip space extends from -1.0 to 1.0 on each axis, but by setting 'zeroToOne' +to true, the clip space will extend from 0.0 to 1.0 on each axis for a light projection matrix. + +When all the corners of the bounding box are behind one of the six frustum planes, the box is +culled. This is conservative, because some boxes may "cross corners" and can be in front of a +frustum plane, but only while also being behind another one. +======================== +*/ +bool idRenderMatrix::CullExtrudedBoundsToMVPbits( const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & extrudeDirection, const idPlane & clipPlane, byte * outBits, bool zeroToOne ) { + assert( idMath::Fabs( extrudeDirection * clipPlane.Normal() ) >= idMath::FLT_SMALLEST_NON_DENORMAL ); + + + __m128 mvp0 = _mm_loadu_ps( mvp[0] ); + __m128 mvp1 = _mm_loadu_ps( mvp[1] ); + __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + __m128 minMul = zeroToOne ? vector_float_zero : vector_float_neg_one; + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + // take the four points on the X-Y plane + __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + __m128 cullBits0; + __m128 cullBits1; + __m128 cullBits2; + __m128 cullBits3; + __m128 cullBits4; + __m128 cullBits5; + + // calculate the cull bits for the bounding box corners + { + // compute four partial X,Y,Z,W values + __m128 parx = _mm_splat_ps( mvp0, 3 ); + __m128 pary = _mm_splat_ps( mvp1, 3 ); + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parx = _mm_madd_ps( vx, _mm_splat_ps( mvp0, 0 ), parx ); + pary = _mm_madd_ps( vx, _mm_splat_ps( mvp1, 0 ), pary ); + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parx = _mm_madd_ps( vy, _mm_splat_ps( mvp0, 1 ), parx ); + pary = _mm_madd_ps( vy, _mm_splat_ps( mvp1, 1 ), pary ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + + // compute full X,Y,Z,W values + __m128 mvp0Z = _mm_splat_ps( mvp0, 2 ); + __m128 mvp1Z = _mm_splat_ps( mvp1, 2 ); + __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + __m128 x0 = _mm_madd_ps( vz0, mvp0Z, parx ); + __m128 y0 = _mm_madd_ps( vz0, mvp1Z, pary ); + __m128 z0 = _mm_madd_ps( vz0, mvp2Z, parz ); + __m128 w0 = _mm_madd_ps( vz0, mvp3Z, parw ); + + __m128 x1 = _mm_madd_ps( vz1, mvp0Z, parx ); + __m128 y1 = _mm_madd_ps( vz1, mvp1Z, pary ); + __m128 z1 = _mm_madd_ps( vz1, mvp2Z, parz ); + __m128 w1 = _mm_madd_ps( vz1, mvp3Z, parw ); + + __m128 maxW0 = w0; + __m128 maxW1 = w1; + __m128 minW0 = _mm_mul_ps( w0, minMul ); + __m128 minW1 = _mm_mul_ps( w1, minMul ); +#if defined( CLIP_SPACE_D3D ) // the D3D clip space Z is in the range [0,1] so always compare Z vs zero whether 'zeroToOne' is true or false + __m128 minZ0 = vector_float_zero; + __m128 minZ1 = vector_float_zero; +#else + __m128 minZ0 = minW0; + __m128 minZ1 = minW1; +#endif + + cullBits0 = _mm_cmpgt_ps( x0, minW0 ); + cullBits1 = _mm_cmpgt_ps( maxW0, x0 ); + cullBits2 = _mm_cmpgt_ps( y0, minW0 ); + cullBits3 = _mm_cmpgt_ps( maxW0, y0 ); + cullBits4 = _mm_cmpgt_ps( z0, minZ0 ); // NOTE: using minZ0 + cullBits5 = _mm_cmpgt_ps( maxW0, z0 ); + + cullBits0 = _mm_or_ps( cullBits0, _mm_cmpgt_ps( x1, minW1 ) ); + cullBits1 = _mm_or_ps( cullBits1, _mm_cmpgt_ps( maxW1, x1 ) ); + cullBits2 = _mm_or_ps( cullBits2, _mm_cmpgt_ps( y1, minW1 ) ); + cullBits3 = _mm_or_ps( cullBits3, _mm_cmpgt_ps( maxW1, y1 ) ); + cullBits4 = _mm_or_ps( cullBits4, _mm_cmpgt_ps( z1, minZ1 ) ); // NOTE: using minZ1 + cullBits5 = _mm_or_ps( cullBits5, _mm_cmpgt_ps( maxW1, z1 ) ); + } + + // calculate and include the cull bits for the extruded bounding box corners + { + __m128 clipX = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 0 ), 0 ); + __m128 clipY = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 1 ), 0 ); + __m128 clipZ = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 2 ), 0 ); + __m128 clipW = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 3 ), 0 ); + + __m128 extrudeX = _mm_splat_ps( _mm_load_ss( extrudeDirection.ToFloatPtr() + 0 ), 0 ); + __m128 extrudeY = _mm_splat_ps( _mm_load_ss( extrudeDirection.ToFloatPtr() + 1 ), 0 ); + __m128 extrudeZ = _mm_splat_ps( _mm_load_ss( extrudeDirection.ToFloatPtr() + 2 ), 0 ); + + __m128 closing = _mm_madd_ps( clipX, extrudeX, _mm_madd_ps( clipY, extrudeY, _mm_mul_ps( clipZ, extrudeZ ) ) ); + __m128 invClosing = _mm_rcp32_ps( closing ); + invClosing = _mm_xor_ps( invClosing, vector_float_sign_bit ); + + __m128 dt = _mm_madd_ps( clipX, vx, _mm_madd_ps( clipY, vy, clipW ) ); + __m128 d0 = _mm_madd_ps( clipZ, vz0, dt ); + __m128 d1 = _mm_madd_ps( clipZ, vz1, dt ); + + d0 = _mm_mul_ps( d0, invClosing ); + d1 = _mm_mul_ps( d1, invClosing ); + + __m128 vx0 = _mm_madd_ps( extrudeX, d0, vx ); + __m128 vx1 = _mm_madd_ps( extrudeX, d1, vx ); + + __m128 vy0 = _mm_madd_ps( extrudeY, d0, vy ); + __m128 vy1 = _mm_madd_ps( extrudeY, d1, vy ); + + vz0 = _mm_madd_ps( extrudeZ, d0, vz0 ); + vz1 = _mm_madd_ps( extrudeZ, d1, vz1 ); + + __m128 mvp0X = _mm_splat_ps( mvp0, 0 ); + __m128 mvp1X = _mm_splat_ps( mvp1, 0 ); + __m128 mvp2X = _mm_splat_ps( mvp2, 0 ); + __m128 mvp3X = _mm_splat_ps( mvp3, 0 ); + + __m128 mvp0W = _mm_splat_ps( mvp0, 3 ); + __m128 mvp1W = _mm_splat_ps( mvp1, 3 ); + __m128 mvp2W = _mm_splat_ps( mvp2, 3 ); + __m128 mvp3W = _mm_splat_ps( mvp3, 3 ); + + __m128 x0 = _mm_madd_ps( vx0, mvp0X, mvp0W ); + __m128 y0 = _mm_madd_ps( vx0, mvp1X, mvp1W ); + __m128 z0 = _mm_madd_ps( vx0, mvp2X, mvp2W ); + __m128 w0 = _mm_madd_ps( vx0, mvp3X, mvp3W ); + + __m128 x1 = _mm_madd_ps( vx1, mvp0X, mvp0W ); + __m128 y1 = _mm_madd_ps( vx1, mvp1X, mvp1W ); + __m128 z1 = _mm_madd_ps( vx1, mvp2X, mvp2W ); + __m128 w1 = _mm_madd_ps( vx1, mvp3X, mvp3W ); + + __m128 mvp0Y = _mm_splat_ps( mvp0, 1 ); + __m128 mvp1Y = _mm_splat_ps( mvp1, 1 ); + __m128 mvp2Y = _mm_splat_ps( mvp2, 1 ); + __m128 mvp3Y = _mm_splat_ps( mvp3, 1 ); + + x0 = _mm_madd_ps( vy0, mvp0Y, x0 ); //-V537 + y0 = _mm_madd_ps( vy0, mvp1Y, y0 ); + z0 = _mm_madd_ps( vy0, mvp2Y, z0 ); //-V537 + w0 = _mm_madd_ps( vy0, mvp3Y, w0 ); + + x1 = _mm_madd_ps( vy1, mvp0Y, x1 ); //-V537 + y1 = _mm_madd_ps( vy1, mvp1Y, y1 ); + z1 = _mm_madd_ps( vy1, mvp2Y, z1 ); //-V537 + w1 = _mm_madd_ps( vy1, mvp3Y, w1 ); + + __m128 mvp0Z = _mm_splat_ps( mvp0, 2 ); + __m128 mvp1Z = _mm_splat_ps( mvp1, 2 ); + __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + x0 = _mm_madd_ps( vz0, mvp0Z, x0 ); + y0 = _mm_madd_ps( vz0, mvp1Z, y0 ); //-V537 + z0 = _mm_madd_ps( vz0, mvp2Z, z0 ); + w0 = _mm_madd_ps( vz0, mvp3Z, w0 ); + + x1 = _mm_madd_ps( vz1, mvp0Z, x1 ); + y1 = _mm_madd_ps( vz1, mvp1Z, y1 ); //-V537 + z1 = _mm_madd_ps( vz1, mvp2Z, z1 ); + w1 = _mm_madd_ps( vz1, mvp3Z, w1 ); + + __m128 maxW0 = w0; + __m128 maxW1 = w1; + __m128 minW0 = _mm_mul_ps( w0, minMul ); + __m128 minW1 = _mm_mul_ps( w1, minMul ); +#if defined( CLIP_SPACE_D3D ) // the D3D clip space Z is in the range [0,1] so always compare Z vs zero whether 'zeroToOne' is true or false + __m128 minZ0 = vector_float_zero; + __m128 minZ1 = vector_float_zero; +#else + __m128 minZ0 = minW0; + __m128 minZ1 = minW1; +#endif + + cullBits0 = _mm_or_ps( cullBits0, _mm_cmpgt_ps( x0, minW0 ) ); + cullBits1 = _mm_or_ps( cullBits1, _mm_cmpgt_ps( maxW0, x0 ) ); + cullBits2 = _mm_or_ps( cullBits2, _mm_cmpgt_ps( y0, minW0 ) ); + cullBits3 = _mm_or_ps( cullBits3, _mm_cmpgt_ps( maxW0, y0 ) ); + cullBits4 = _mm_or_ps( cullBits4, _mm_cmpgt_ps( z0, minZ0 ) ); // NOTE: using minZ0 + cullBits5 = _mm_or_ps( cullBits5, _mm_cmpgt_ps( maxW0, z0 ) ); + + cullBits0 = _mm_or_ps( cullBits0, _mm_cmpgt_ps( x1, minW1 ) ); + cullBits1 = _mm_or_ps( cullBits1, _mm_cmpgt_ps( maxW1, x1 ) ); + cullBits2 = _mm_or_ps( cullBits2, _mm_cmpgt_ps( y1, minW1 ) ); + cullBits3 = _mm_or_ps( cullBits3, _mm_cmpgt_ps( maxW1, y1 ) ); + cullBits4 = _mm_or_ps( cullBits4, _mm_cmpgt_ps( z1, minZ1 ) ); // NOTE: using minZ1 + cullBits5 = _mm_or_ps( cullBits5, _mm_cmpgt_ps( maxW1, z1 ) ); + } + + cullBits0 = _mm_and_ps( cullBits0, vector_float_mask0 ); + cullBits1 = _mm_and_ps( cullBits1, vector_float_mask1 ); + cullBits2 = _mm_and_ps( cullBits2, vector_float_mask2 ); + cullBits3 = _mm_and_ps( cullBits3, vector_float_mask3 ); + cullBits4 = _mm_and_ps( cullBits4, vector_float_mask4 ); + cullBits5 = _mm_and_ps( cullBits5, vector_float_mask5 ); + + cullBits0 = _mm_or_ps( cullBits0, cullBits1 ); + cullBits2 = _mm_or_ps( cullBits2, cullBits3 ); + cullBits4 = _mm_or_ps( cullBits4, cullBits5 ); + cullBits0 = _mm_or_ps( cullBits0, cullBits2 ); + cullBits0 = _mm_or_ps( cullBits0, cullBits4 ); + + cullBits0 = _mm_or_ps( cullBits0, _mm_perm_ps( cullBits0, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + cullBits0 = _mm_or_ps( cullBits0, _mm_perm_ps( cullBits0, _MM_SHUFFLE( 0, 1, 0, 1 ) ) ); + + int bits = _mm_cvtsi128_si32( (const __m128i &)cullBits0 ); + + *outBits = (byte)(bits ^ 63); + + return ( bits != 63 ); + +} + +/* +======================== +idRenderMatrix::ProjectedBounds + +Calculates the bounds of the given bounding box projected with the given Model View Projection (MVP) matrix. +If 'windowSpace' is true then the calculated bounds along each axis are moved and clamped to the [0, 1] range. + +The given bounding box is not clipped to the MVP so the projected bounds may not be as tight as possible. +If the given bounding box is W=0 clipped then the projected bounds will cover the full X-Y range. +Note that while projected[0][1] will be set to the minimum when the given bounding box is W=0 clipped, +projected[1][1] will still be valid and will NOT be set to the maximum when the given bounding box +is W=0 clipped. +======================== +*/ +void idRenderMatrix::ProjectedBounds( idBounds & projected, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace ) { + + __m128 mvp0 = _mm_loadu_ps( mvp[0] ); + __m128 mvp1 = _mm_loadu_ps( mvp[1] ); + __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + // take the four points on the X-Y plane + __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + // compute four partial X,Y,Z,W values + __m128 parx = _mm_splat_ps( mvp0, 3 ); + __m128 pary = _mm_splat_ps( mvp1, 3 ); + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parx = _mm_madd_ps( vx, _mm_splat_ps( mvp0, 0 ), parx ); + pary = _mm_madd_ps( vx, _mm_splat_ps( mvp1, 0 ), pary ); + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parx = _mm_madd_ps( vy, _mm_splat_ps( mvp0, 1 ), parx ); + pary = _mm_madd_ps( vy, _mm_splat_ps( mvp1, 1 ), pary ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + + // compute full X,Y,Z,W values + __m128 mvp0Z = _mm_splat_ps( mvp0, 2 ); + __m128 mvp1Z = _mm_splat_ps( mvp1, 2 ); + __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + __m128 x0 = _mm_madd_ps( vz0, mvp0Z, parx ); + __m128 y0 = _mm_madd_ps( vz0, mvp1Z, pary ); + __m128 z0 = _mm_madd_ps( vz0, mvp2Z, parz ); + __m128 w0 = _mm_madd_ps( vz0, mvp3Z, parw ); + + __m128 x1 = _mm_madd_ps( vz1, mvp0Z, parx ); + __m128 y1 = _mm_madd_ps( vz1, mvp1Z, pary ); + __m128 z1 = _mm_madd_ps( vz1, mvp2Z, parz ); + __m128 w1 = _mm_madd_ps( vz1, mvp3Z, parw ); + + __m128 s0 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w0 ); + __m128 s1 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w1 ); + + w0 = _mm_sel_ps( w0, vector_float_one, s0 ); + w1 = _mm_sel_ps( w1, vector_float_one, s1 ); + + __m128 rw0 = _mm_rcp32_ps( w0 ); + __m128 rw1 = _mm_rcp32_ps( w1 ); + + x0 = _mm_mul_ps( x0, rw0 ); + y0 = _mm_mul_ps( y0, rw0 ); + z0 = _mm_mul_ps( z0, rw0 ); + + x1 = _mm_mul_ps( x1, rw1 ); + y1 = _mm_mul_ps( y1, rw1 ); + z1 = _mm_mul_ps( z1, rw1 ); + + __m128 minX = _mm_min_ps( x0, x1 ); + __m128 minY = _mm_min_ps( y0, y1 ); + __m128 minZ = _mm_min_ps( z0, z1 ); + + __m128 maxX = _mm_max_ps( x0, x1 ); + __m128 maxY = _mm_max_ps( y0, y1 ); + __m128 maxZ = _mm_max_ps( z0, z1 ); + + minX = _mm_min_ps( minX, _mm_perm_ps( minX, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minY = _mm_min_ps( minY, _mm_perm_ps( minY, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + minX = _mm_min_ps( minX, _mm_perm_ps( minX, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + minY = _mm_min_ps( minY, _mm_perm_ps( minY, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + maxX = _mm_max_ps( maxX, _mm_perm_ps( maxX, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxY = _mm_max_ps( maxY, _mm_perm_ps( maxY, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + maxX = _mm_max_ps( maxX, _mm_perm_ps( maxX, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + maxY = _mm_max_ps( maxY, _mm_perm_ps( maxY, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + s0 = _mm_or_ps( s0, s1 ); + s0 = _mm_or_ps( s0, _mm_perm_ps( s0, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + s0 = _mm_or_ps( s0, _mm_perm_ps( s0, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + minX = _mm_sel_ps( minX, vector_float_neg_infinity, s0 ); + minY = _mm_sel_ps( minY, vector_float_neg_infinity, s0 ); + minZ = _mm_sel_ps( minZ, vector_float_neg_infinity, s0 ); + + maxX = _mm_sel_ps( maxX, vector_float_pos_infinity, s0 ); + maxY = _mm_sel_ps( maxY, vector_float_pos_infinity, s0 ); + // NOTE: maxZ is valid either way + + if ( windowSpace ) { + minX = _mm_madd_ps( minX, vector_float_half, vector_float_half ); + maxX = _mm_madd_ps( maxX, vector_float_half, vector_float_half ); + + minY = _mm_madd_ps( minY, vector_float_half, vector_float_half ); + maxY = _mm_madd_ps( maxY, vector_float_half, vector_float_half ); + +#if !defined( CLIP_SPACE_D3D ) // the D3D clip space Z is already in the range [0,1] + minZ = _mm_madd_ps( minZ, vector_float_half, vector_float_half ); + maxZ = _mm_madd_ps( maxZ, vector_float_half, vector_float_half ); +#endif + + minX = _mm_max_ps( _mm_min_ps( minX, vector_float_one ), vector_float_zero ); + maxX = _mm_max_ps( _mm_min_ps( maxX, vector_float_one ), vector_float_zero ); + + minY = _mm_max_ps( _mm_min_ps( minY, vector_float_one ), vector_float_zero ); + maxY = _mm_max_ps( _mm_min_ps( maxY, vector_float_one ), vector_float_zero ); + + minZ = _mm_max_ps( _mm_min_ps( minZ, vector_float_one ), vector_float_zero ); + maxZ = _mm_max_ps( _mm_min_ps( maxZ, vector_float_one ), vector_float_zero ); + } + + _mm_store_ss( & projected[0].x, minX ); + _mm_store_ss( & projected[0].y, minY ); + _mm_store_ss( & projected[0].z, minZ ); + + _mm_store_ss( & projected[1].x, maxX ); + _mm_store_ss( & projected[1].y, maxY ); + _mm_store_ss( & projected[1].z, maxZ ); + +} + +/* +======================== +idRenderMatrix::ProjectedNearClippedBounds + +Calculates the bounds of the given bounding box projected with the given Model View Projection (MVP) matrix. +If 'windowSpace' is true then the calculated bounds along each axis are moved and clamped to the [0, 1] range. + +The given bounding box is first near clipped so the projected bounds do not cover the full X-Y range when +the given bounding box crosses the W=0 plane. However, the given bounding box is not clipped against the +other planes so the projected bounds are still not as tight as they could be if the given bounding box +crosses a corner. Fortunately, clipping to the near clipping planes typically provides more than 50% of +the gain between not clipping at all and fully clipping the bounding box to all planes. Only clipping to +the near clipping plane is much cheaper than clipping to all planes and can be easily implemented with +completely branchless SIMD. +======================== +*/ +void idRenderMatrix::ProjectedNearClippedBounds( idBounds & projected, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace ) { +/* + 4----{E}---5 + + /| /| + Z {H} {I} {F} | + - / | / {J} + 7--{G}-----6 | + | | | | + {L} 0----|-{A}-1 + | / {K} / - + | {D} | {B} Y + |/ |/ + + 3---{C}----2 + + - X + +*/ + + + const __m128 mvp0 = _mm_loadu_ps( mvp[0] ); + const __m128 mvp1 = _mm_loadu_ps( mvp[1] ); + const __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + const __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + const __m128 b0 = _mm_loadu_bounds_0( bounds ); + const __m128 b1 = _mm_loadu_bounds_1( bounds ); + + // take the four points on the X-Y plane + const __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + const __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + const __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + const __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + const __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + // compute four partial X,Y,Z,W values + __m128 parx = _mm_splat_ps( mvp0, 3 ); + __m128 pary = _mm_splat_ps( mvp1, 3 ); + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parx = _mm_madd_ps( vx, _mm_splat_ps( mvp0, 0 ), parx ); + pary = _mm_madd_ps( vx, _mm_splat_ps( mvp1, 0 ), pary ); + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parx = _mm_madd_ps( vy, _mm_splat_ps( mvp0, 1 ), parx ); + pary = _mm_madd_ps( vy, _mm_splat_ps( mvp1, 1 ), pary ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + + // compute full X,Y,Z,W values + const __m128 mvp0Z = _mm_splat_ps( mvp0, 2 ); + const __m128 mvp1Z = _mm_splat_ps( mvp1, 2 ); + const __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + const __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + const __m128 x_0123 = _mm_madd_ps( vz0, mvp0Z, parx ); + const __m128 y_0123 = _mm_madd_ps( vz0, mvp1Z, pary ); + const __m128 z_0123 = _mm_madd_ps( vz0, mvp2Z, parz ); + const __m128 w_0123 = _mm_madd_ps( vz0, mvp3Z, parw ); + + const __m128 x_4567 = _mm_madd_ps( vz1, mvp0Z, parx ); + const __m128 y_4567 = _mm_madd_ps( vz1, mvp1Z, pary ); + const __m128 z_4567 = _mm_madd_ps( vz1, mvp2Z, parz ); + const __m128 w_4567 = _mm_madd_ps( vz1, mvp3Z, parw ); + + // rotate the X,Y,Z,W values up by one + const __m128 x_1230 = _mm_perm_ps( x_0123, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 y_1230 = _mm_perm_ps( y_0123, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 z_1230 = _mm_perm_ps( z_0123, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 w_1230 = _mm_perm_ps( w_0123, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + const __m128 x_5674 = _mm_perm_ps( x_4567, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 y_5674 = _mm_perm_ps( y_4567, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 z_5674 = _mm_perm_ps( z_4567, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + const __m128 w_5674 = _mm_perm_ps( w_4567, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + +#if defined( CLIP_SPACE_D3D ) // the D3D near plane is at Z=0 instead of Z=-1 + const __m128 d_0123 = z_0123; + const __m128 d_4567 = z_4567; + const __m128 d_1230 = z_1230; + const __m128 d_5674 = z_5674; +#else + const __m128 d_0123 = _mm_add_ps( z_0123, w_0123 ); + const __m128 d_4567 = _mm_add_ps( z_4567, w_4567 ); + const __m128 d_1230 = _mm_add_ps( z_1230, w_1230 ); + const __m128 d_5674 = _mm_add_ps( z_5674, w_5674 ); +#endif + + const __m128 deltaABCD = _mm_sub_ps( d_0123, d_1230 ); + const __m128 deltaEFGH = _mm_sub_ps( d_4567, d_5674 ); + const __m128 deltaIJKL = _mm_sub_ps( d_0123, d_4567 ); + + const __m128 maskABCD = _mm_cmpgt_ps( _mm_and_ps( deltaABCD, vector_float_abs_mask ), vector_float_smallest_non_denorm ); + const __m128 maskEFGH = _mm_cmpgt_ps( _mm_and_ps( deltaEFGH, vector_float_abs_mask ), vector_float_smallest_non_denorm ); + const __m128 maskIJKL = _mm_cmpgt_ps( _mm_and_ps( deltaIJKL, vector_float_abs_mask ), vector_float_smallest_non_denorm ); + + const __m128 fractionABCD = _mm_and_ps( _mm_div32_ps( d_0123, _mm_sel_ps( vector_float_one, deltaABCD, maskABCD ) ), maskABCD ); + const __m128 fractionEFGH = _mm_and_ps( _mm_div32_ps( d_4567, _mm_sel_ps( vector_float_one, deltaEFGH, maskEFGH ) ), maskEFGH ); + const __m128 fractionIJKL = _mm_and_ps( _mm_div32_ps( d_0123, _mm_sel_ps( vector_float_one, deltaIJKL, maskIJKL ) ), maskIJKL ); + + const __m128 clipABCD = _mm_and_ps( _mm_cmpgt_ps( fractionABCD, vector_float_zero ), _mm_cmpgt_ps( vector_float_one, fractionABCD ) ); + const __m128 clipEFGH = _mm_and_ps( _mm_cmpgt_ps( fractionEFGH, vector_float_zero ), _mm_cmpgt_ps( vector_float_one, fractionEFGH ) ); + const __m128 clipIJKL = _mm_and_ps( _mm_cmpgt_ps( fractionIJKL, vector_float_zero ), _mm_cmpgt_ps( vector_float_one, fractionIJKL ) ); + + const __m128 intersectionABCD_x = _mm_madd_ps( fractionABCD, _mm_sub_ps( x_1230, x_0123 ), x_0123 ); + const __m128 intersectionABCD_y = _mm_madd_ps( fractionABCD, _mm_sub_ps( y_1230, y_0123 ), y_0123 ); + const __m128 intersectionABCD_z = _mm_madd_ps( fractionABCD, _mm_sub_ps( z_1230, z_0123 ), z_0123 ); + const __m128 intersectionABCD_w = _mm_madd_ps( fractionABCD, _mm_sub_ps( w_1230, w_0123 ), w_0123 ); + + const __m128 intersectionEFGH_x = _mm_madd_ps( fractionEFGH, _mm_sub_ps( x_5674, x_4567 ), x_4567 ); + const __m128 intersectionEFGH_y = _mm_madd_ps( fractionEFGH, _mm_sub_ps( y_5674, y_4567 ), y_4567 ); + const __m128 intersectionEFGH_z = _mm_madd_ps( fractionEFGH, _mm_sub_ps( z_5674, z_4567 ), z_4567 ); + const __m128 intersectionEFGH_w = _mm_madd_ps( fractionEFGH, _mm_sub_ps( w_5674, w_4567 ), w_4567 ); + + const __m128 intersectionIJKL_x = _mm_madd_ps( fractionIJKL, _mm_sub_ps( x_4567, x_0123 ), x_0123 ); + const __m128 intersectionIJKL_y = _mm_madd_ps( fractionIJKL, _mm_sub_ps( y_4567, y_0123 ), y_0123 ); + const __m128 intersectionIJKL_z = _mm_madd_ps( fractionIJKL, _mm_sub_ps( z_4567, z_0123 ), z_0123 ); + const __m128 intersectionIJKL_w = _mm_madd_ps( fractionIJKL, _mm_sub_ps( w_4567, w_0123 ), w_0123 ); + + const __m128 mask_0123 = _mm_cmpgt_ps( vector_float_zero, d_0123 ); + const __m128 mask_1230 = _mm_cmpgt_ps( vector_float_zero, d_1230 ); + const __m128 mask_4567 = _mm_cmpgt_ps( vector_float_zero, d_4567 ); + const __m128 mask_5674 = _mm_cmpgt_ps( vector_float_zero, d_5674 ); + + const __m128 maskABCD_0123 = _mm_and_ps( clipABCD, mask_0123 ); + const __m128 maskABCD_1230 = _mm_and_ps( clipABCD, mask_1230 ); + const __m128 maskEFGH_4567 = _mm_and_ps( clipEFGH, mask_4567 ); + const __m128 maskEFGH_5674 = _mm_and_ps( clipEFGH, mask_5674 ); + const __m128 maskIJKL_0123 = _mm_and_ps( clipIJKL, mask_0123 ); + const __m128 maskIJKL_4567 = _mm_and_ps( clipIJKL, mask_4567 ); + + __m128 edgeVertsABCD_x0 = _mm_sel_ps( x_0123, intersectionABCD_x, maskABCD_0123 ); + __m128 edgeVertsABCD_y0 = _mm_sel_ps( y_0123, intersectionABCD_y, maskABCD_0123 ); + __m128 edgeVertsABCD_z0 = _mm_sel_ps( z_0123, intersectionABCD_z, maskABCD_0123 ); + __m128 edgeVertsABCD_w0 = _mm_sel_ps( w_0123, intersectionABCD_w, maskABCD_0123 ); + + __m128 edgeVertsABCD_x1 = _mm_sel_ps( x_1230, intersectionABCD_x, maskABCD_1230 ); + __m128 edgeVertsABCD_y1 = _mm_sel_ps( y_1230, intersectionABCD_y, maskABCD_1230 ); + __m128 edgeVertsABCD_z1 = _mm_sel_ps( z_1230, intersectionABCD_z, maskABCD_1230 ); + __m128 edgeVertsABCD_w1 = _mm_sel_ps( w_1230, intersectionABCD_w, maskABCD_1230 ); + + __m128 edgeVertsEFGH_x0 = _mm_sel_ps( x_4567, intersectionEFGH_x, maskEFGH_4567 ); + __m128 edgeVertsEFGH_y0 = _mm_sel_ps( y_4567, intersectionEFGH_y, maskEFGH_4567 ); + __m128 edgeVertsEFGH_z0 = _mm_sel_ps( z_4567, intersectionEFGH_z, maskEFGH_4567 ); + __m128 edgeVertsEFGH_w0 = _mm_sel_ps( w_4567, intersectionEFGH_w, maskEFGH_4567 ); + + __m128 edgeVertsEFGH_x1 = _mm_sel_ps( x_5674, intersectionEFGH_x, maskEFGH_5674 ); + __m128 edgeVertsEFGH_y1 = _mm_sel_ps( y_5674, intersectionEFGH_y, maskEFGH_5674 ); + __m128 edgeVertsEFGH_z1 = _mm_sel_ps( z_5674, intersectionEFGH_z, maskEFGH_5674 ); + __m128 edgeVertsEFGH_w1 = _mm_sel_ps( w_5674, intersectionEFGH_w, maskEFGH_5674 ); + + __m128 edgeVertsIJKL_x0 = _mm_sel_ps( x_0123, intersectionIJKL_x, maskIJKL_0123 ); + __m128 edgeVertsIJKL_y0 = _mm_sel_ps( y_0123, intersectionIJKL_y, maskIJKL_0123 ); + __m128 edgeVertsIJKL_z0 = _mm_sel_ps( z_0123, intersectionIJKL_z, maskIJKL_0123 ); + __m128 edgeVertsIJKL_w0 = _mm_sel_ps( w_0123, intersectionIJKL_w, maskIJKL_0123 ); + + __m128 edgeVertsIJKL_x1 = _mm_sel_ps( x_4567, intersectionIJKL_x, maskIJKL_4567 ); + __m128 edgeVertsIJKL_y1 = _mm_sel_ps( y_4567, intersectionIJKL_y, maskIJKL_4567 ); + __m128 edgeVertsIJKL_z1 = _mm_sel_ps( z_4567, intersectionIJKL_z, maskIJKL_4567 ); + __m128 edgeVertsIJKL_w1 = _mm_sel_ps( w_4567, intersectionIJKL_w, maskIJKL_4567 ); + + const __m128 maskABCD_w0 = _mm_cmpgt_ps( edgeVertsABCD_w0, vector_float_smallest_non_denorm ); + const __m128 maskABCD_w1 = _mm_cmpgt_ps( edgeVertsABCD_w1, vector_float_smallest_non_denorm ); + const __m128 maskEFGH_w0 = _mm_cmpgt_ps( edgeVertsEFGH_w0, vector_float_smallest_non_denorm ); + const __m128 maskEFGH_w1 = _mm_cmpgt_ps( edgeVertsEFGH_w1, vector_float_smallest_non_denorm ); + const __m128 maskIJKL_w0 = _mm_cmpgt_ps( edgeVertsIJKL_w0, vector_float_smallest_non_denorm ); + const __m128 maskIJKL_w1 = _mm_cmpgt_ps( edgeVertsIJKL_w1, vector_float_smallest_non_denorm ); + + edgeVertsABCD_w0 = _mm_rcp32_ps( _mm_sel_ps( vector_float_one, edgeVertsABCD_w0, maskABCD_w0 ) ); + edgeVertsABCD_w1 = _mm_rcp32_ps( _mm_sel_ps( vector_float_one, edgeVertsABCD_w1, maskABCD_w1 ) ); + edgeVertsEFGH_w0 = _mm_rcp32_ps( _mm_sel_ps( vector_float_one, edgeVertsEFGH_w0, maskEFGH_w0 ) ); + edgeVertsEFGH_w1 = _mm_rcp32_ps( _mm_sel_ps( vector_float_one, edgeVertsEFGH_w1, maskEFGH_w1 ) ); + edgeVertsIJKL_w0 = _mm_rcp32_ps( _mm_sel_ps( vector_float_one, edgeVertsIJKL_w0, maskIJKL_w0 ) ); + edgeVertsIJKL_w1 = _mm_rcp32_ps( _mm_sel_ps( vector_float_one, edgeVertsIJKL_w1, maskIJKL_w1 ) ); + + edgeVertsABCD_x0 = _mm_mul_ps( edgeVertsABCD_x0, edgeVertsABCD_w0 ); + edgeVertsABCD_x1 = _mm_mul_ps( edgeVertsABCD_x1, edgeVertsABCD_w1 ); + edgeVertsEFGH_x0 = _mm_mul_ps( edgeVertsEFGH_x0, edgeVertsEFGH_w0 ); + edgeVertsEFGH_x1 = _mm_mul_ps( edgeVertsEFGH_x1, edgeVertsEFGH_w1 ); + edgeVertsIJKL_x0 = _mm_mul_ps( edgeVertsIJKL_x0, edgeVertsIJKL_w0 ); + edgeVertsIJKL_x1 = _mm_mul_ps( edgeVertsIJKL_x1, edgeVertsIJKL_w1 ); + + edgeVertsABCD_y0 = _mm_mul_ps( edgeVertsABCD_y0, edgeVertsABCD_w0 ); + edgeVertsABCD_y1 = _mm_mul_ps( edgeVertsABCD_y1, edgeVertsABCD_w1 ); + edgeVertsEFGH_y0 = _mm_mul_ps( edgeVertsEFGH_y0, edgeVertsEFGH_w0 ); + edgeVertsEFGH_y1 = _mm_mul_ps( edgeVertsEFGH_y1, edgeVertsEFGH_w1 ); + edgeVertsIJKL_y0 = _mm_mul_ps( edgeVertsIJKL_y0, edgeVertsIJKL_w0 ); + edgeVertsIJKL_y1 = _mm_mul_ps( edgeVertsIJKL_y1, edgeVertsIJKL_w1 ); + + edgeVertsABCD_z0 = _mm_mul_ps( edgeVertsABCD_z0, edgeVertsABCD_w0 ); + edgeVertsABCD_z1 = _mm_mul_ps( edgeVertsABCD_z1, edgeVertsABCD_w1 ); + edgeVertsEFGH_z0 = _mm_mul_ps( edgeVertsEFGH_z0, edgeVertsEFGH_w0 ); + edgeVertsEFGH_z1 = _mm_mul_ps( edgeVertsEFGH_z1, edgeVertsEFGH_w1 ); + edgeVertsIJKL_z0 = _mm_mul_ps( edgeVertsIJKL_z0, edgeVertsIJKL_w0 ); + edgeVertsIJKL_z1 = _mm_mul_ps( edgeVertsIJKL_z1, edgeVertsIJKL_w1 ); + + const __m128 posInf = vector_float_pos_infinity; + const __m128 negInf = vector_float_neg_infinity; + + const __m128 minX0 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsABCD_x0, maskABCD_w0 ), _mm_sel_ps( posInf, edgeVertsABCD_x1, maskABCD_w1 ) ); + const __m128 minX1 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsEFGH_x0, maskEFGH_w0 ), _mm_sel_ps( posInf, edgeVertsEFGH_x1, maskEFGH_w1 ) ); + const __m128 minX2 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsIJKL_x0, maskIJKL_w0 ), _mm_sel_ps( posInf, edgeVertsIJKL_x1, maskIJKL_w1 ) ); + + const __m128 minY0 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsABCD_y0, maskABCD_w0 ), _mm_sel_ps( posInf, edgeVertsABCD_y1, maskABCD_w1 ) ); + const __m128 minY1 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsEFGH_y0, maskEFGH_w0 ), _mm_sel_ps( posInf, edgeVertsEFGH_y1, maskEFGH_w1 ) ); + const __m128 minY2 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsIJKL_y0, maskIJKL_w0 ), _mm_sel_ps( posInf, edgeVertsIJKL_y1, maskIJKL_w1 ) ); + + const __m128 minZ0 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsABCD_z0, maskABCD_w0 ), _mm_sel_ps( posInf, edgeVertsABCD_z1, maskABCD_w1 ) ); + const __m128 minZ1 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsEFGH_z0, maskEFGH_w0 ), _mm_sel_ps( posInf, edgeVertsEFGH_z1, maskEFGH_w1 ) ); + const __m128 minZ2 = _mm_min_ps( _mm_sel_ps( posInf, edgeVertsIJKL_z0, maskIJKL_w0 ), _mm_sel_ps( posInf, edgeVertsIJKL_z1, maskIJKL_w1 ) ); + + const __m128 maxX0 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsABCD_x0, maskABCD_w0 ), _mm_sel_ps( negInf, edgeVertsABCD_x1, maskABCD_w1 ) ); + const __m128 maxX1 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsEFGH_x0, maskEFGH_w0 ), _mm_sel_ps( negInf, edgeVertsEFGH_x1, maskEFGH_w1 ) ); + const __m128 maxX2 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsIJKL_x0, maskIJKL_w0 ), _mm_sel_ps( negInf, edgeVertsIJKL_x1, maskIJKL_w1 ) ); + + const __m128 maxY0 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsABCD_y0, maskABCD_w0 ), _mm_sel_ps( negInf, edgeVertsABCD_y1, maskABCD_w1 ) ); + const __m128 maxY1 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsEFGH_y0, maskEFGH_w0 ), _mm_sel_ps( negInf, edgeVertsEFGH_y1, maskEFGH_w1 ) ); + const __m128 maxY2 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsIJKL_y0, maskIJKL_w0 ), _mm_sel_ps( negInf, edgeVertsIJKL_y1, maskIJKL_w1 ) ); + + const __m128 maxZ0 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsABCD_z0, maskABCD_w0 ), _mm_sel_ps( negInf, edgeVertsABCD_z1, maskABCD_w1 ) ); + const __m128 maxZ1 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsEFGH_z0, maskEFGH_w0 ), _mm_sel_ps( negInf, edgeVertsEFGH_z1, maskEFGH_w1 ) ); + const __m128 maxZ2 = _mm_max_ps( _mm_sel_ps( negInf, edgeVertsIJKL_z0, maskIJKL_w0 ), _mm_sel_ps( negInf, edgeVertsIJKL_z1, maskIJKL_w1 ) ); + + __m128 minX = _mm_min_ps( minX0, _mm_min_ps( minX1, minX2 ) ); + __m128 minY = _mm_min_ps( minY0, _mm_min_ps( minY1, minY2 ) ); + __m128 minZ = _mm_min_ps( minZ0, _mm_min_ps( minZ1, minZ2 ) ); + + __m128 maxX = _mm_max_ps( maxX0, _mm_max_ps( maxX1, maxX2 ) ); + __m128 maxY = _mm_max_ps( maxY0, _mm_max_ps( maxY1, maxY2 ) ); + __m128 maxZ = _mm_max_ps( maxZ0, _mm_max_ps( maxZ1, maxZ2 ) ); + + minX = _mm_min_ps( minX, _mm_perm_ps( minX, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minY = _mm_min_ps( minY, _mm_perm_ps( minY, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + minX = _mm_min_ps( minX, _mm_perm_ps( minX, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + minY = _mm_min_ps( minY, _mm_perm_ps( minY, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + maxX = _mm_max_ps( maxX, _mm_perm_ps( maxX, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxY = _mm_max_ps( maxY, _mm_perm_ps( maxY, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + maxX = _mm_max_ps( maxX, _mm_perm_ps( maxX, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + maxY = _mm_max_ps( maxY, _mm_perm_ps( maxY, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + if ( windowSpace ) { + minX = _mm_madd_ps( minX, vector_float_half, vector_float_half ); + maxX = _mm_madd_ps( maxX, vector_float_half, vector_float_half ); + + minY = _mm_madd_ps( minY, vector_float_half, vector_float_half ); + maxY = _mm_madd_ps( maxY, vector_float_half, vector_float_half ); + +#if !defined( CLIP_SPACE_D3D ) // the D3D clip space Z is already in the range [0,1] + minZ = _mm_madd_ps( minZ, vector_float_half, vector_float_half ); + maxZ = _mm_madd_ps( maxZ, vector_float_half, vector_float_half ); +#endif + + minX = _mm_max_ps( _mm_min_ps( minX, vector_float_one ), vector_float_zero ); + maxX = _mm_max_ps( _mm_min_ps( maxX, vector_float_one ), vector_float_zero ); + + minY = _mm_max_ps( _mm_min_ps( minY, vector_float_one ), vector_float_zero ); + maxY = _mm_max_ps( _mm_min_ps( maxY, vector_float_one ), vector_float_zero ); + + minZ = _mm_max_ps( _mm_min_ps( minZ, vector_float_one ), vector_float_zero ); + maxZ = _mm_max_ps( _mm_min_ps( maxZ, vector_float_one ), vector_float_zero ); + } + + _mm_store_ss( & projected[0].x, minX ); + _mm_store_ss( & projected[0].y, minY ); + _mm_store_ss( & projected[0].z, minZ ); + + _mm_store_ss( & projected[1].x, maxX ); + _mm_store_ss( & projected[1].y, maxY ); + _mm_store_ss( & projected[1].z, maxZ ); + +} + +#if 0 + +/* +======================== +LocalViewOriginFromMVP +======================== +*/ +static idVec3 LocalViewOriginFromMVP( const idRenderMatrix & mvp ) { + const float nearX = mvp[3][0] + mvp[2][0]; + const float nearY = mvp[3][1] + mvp[2][1]; + const float nearZ = mvp[3][2] + mvp[2][2]; + const float s = idMath::InvSqrt( nearX * nearX + nearY * nearY + nearZ * nearZ ); + + idRenderMatrix inverseMVP; + idRenderMatrix::Inverse( mvp, inverseMVP ); + const float invW = 1.0f / inverseMVP[3][3]; + const float x = ( inverseMVP[0][3] - nearX * s ) * invW; + const float y = ( inverseMVP[1][3] - nearY * s ) * invW; + const float z = ( inverseMVP[2][3] - nearZ * s ) * invW; + + return idVec3( x, y, z ); +} + +#endif + +/* +======================== +LocalNearClipCenterFromMVP + +Based on whether the depth range is [0,1] or [-1,1], either transform (0,0,0) or (0,0,-1) with the inverse MVP. +======================== +*/ +static idVec3 LocalNearClipCenterFromMVP( const idRenderMatrix & mvp ) { + idRenderMatrix inverseMVP; + idRenderMatrix::Inverse( mvp, inverseMVP ); +#if defined( CLIP_SPACE_D3D ) // the D3D near plane is at Z=0 instead of Z=-1 + const float x = inverseMVP[0][3]; + const float y = inverseMVP[1][3]; + const float z = inverseMVP[2][3]; + const float w = inverseMVP[3][3]; +#else + const float x = inverseMVP[0][3] - inverseMVP[0][2]; + const float y = inverseMVP[1][3] - inverseMVP[1][2]; + const float z = inverseMVP[2][3] - inverseMVP[2][2]; + const float w = inverseMVP[3][3] - inverseMVP[3][2]; +#endif + const float invW = 1.0f / w; + return idVec3( x * invW, y * invW, z * invW ); +} + + +/* +======================== +ClipHomogeneousPolygonToSide + +Clips a polygon with homogeneous coordinates to the axis aligned plane[axis] = sign * offset. +======================== +*/ +static void ClipHomogeneousPolygonToSide_SSE2( idVec4 * __restrict newPoints, idVec4 * __restrict points, int & numPoints, + const int axis, const __m128 & sign, const __m128 & offset ) { + assert( newPoints != points ); + + const __m128 side = _mm_mul_ps( sign, offset ); + __m128i mask = _mm_sub_epi32( vector_int_0123, _mm_shuffle_epi32( _mm_cvtsi32_si128( numPoints ), 0 ) ); + __m128i index = _mm_setzero_si128(); + + ALIGNTYPE16 unsigned short indices[16 * 2]; + ALIGNTYPE16 float clipFractions[16]; + + int localNumPoint = numPoints; + + for ( int i = 0; i < localNumPoint; i += 4 ) { + const int i0 = ( i + 0 ) & ( ( i + 0 - localNumPoint ) >> 31 ); + const int i1 = ( i + 1 ) & ( ( i + 1 - localNumPoint ) >> 31 ); + const int i2 = ( i + 2 ) & ( ( i + 2 - localNumPoint ) >> 31 ); + const int i3 = ( i + 3 ) & ( ( i + 3 - localNumPoint ) >> 31 ); + const int i4 = ( i + 4 ) & ( ( i + 4 - localNumPoint ) >> 31 ); + + const __m128 p0A = _mm_load_ss( &points[i0][axis] ); + const __m128 p1A = _mm_load_ss( &points[i1][axis] ); + const __m128 p2A = _mm_load_ss( &points[i2][axis] ); + const __m128 p3A = _mm_load_ss( &points[i3][axis] ); + const __m128 p4A = _mm_load_ss( &points[i4][axis] ); + + const __m128 p0W = _mm_load_ss( &points[i0][3] ); + const __m128 p1W = _mm_load_ss( &points[i1][3] ); + const __m128 p2W = _mm_load_ss( &points[i2][3] ); + const __m128 p3W = _mm_load_ss( &points[i3][3] ); + const __m128 p4W = _mm_load_ss( &points[i4][3] ); + + const __m128 t0 = _mm_unpacklo_ps( p0A, p2A ); + const __m128 t1 = _mm_unpacklo_ps( p1A, p3A ); + const __m128 pa0 = _mm_unpacklo_ps( t0, t1 ); + const __m128 pa1 = _mm_sld_ps( pa0, p4A, 4 ); + + const __m128 r0 = _mm_unpacklo_ps( p0W, p2W ); + const __m128 r1 = _mm_unpacklo_ps( p1W, p3W ); + const __m128 pw0 = _mm_unpacklo_ps( r0, r1 ); + const __m128 pw1 = _mm_sld_ps( pw0, p4W, 4 ); + + { + const __m128 bside0 = _mm_cmpgt_ps( _mm_mul_ps( offset, pw0 ), _mm_mul_ps( sign, pa0 ) ); + const __m128 bside1 = _mm_cmpgt_ps( _mm_mul_ps( offset, pw1 ), _mm_mul_ps( sign, pa1 ) ); + const __m128i side0 = _mm_and_si128( __m128c( bside0 ), vector_int_1 ); + const __m128i side1 = _mm_and_si128( __m128c( bside1 ), vector_int_1 ); + const __m128i xorSide = _mm_xor_si128( side0, side1 ); + const __m128i interleavedSide0 = _mm_unpacklo_epi32( side0, xorSide ); + const __m128i interleavedSide1 = _mm_unpackhi_epi32( side0, xorSide ); + const __m128i packedSide = _mm_packs_epi32( interleavedSide0, interleavedSide1 ); + const __m128i packedMaskedSide = _mm_and_si128( packedSide, _mm_srai_epi32( mask, 31 ) ); + + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 2 ) ); + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 4 ) ); + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 6 ) ); + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 8 ) ); + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 10 ) ); + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 12 ) ); + index = _mm_add_epi16( index, _mm_slli_si128( packedMaskedSide, 14 ) ); + + _mm_store_si128( (__m128i *)&indices[i * 2], index ); + + mask = _mm_add_epi32( mask, vector_int_4 ); + index = _mm_add_epi16( index, packedMaskedSide ); + index = _mm_shufflehi_epi16( index, _MM_SHUFFLE( 3, 3, 3, 3 ) ); + index = _mm_shuffle_epi32( index, _MM_SHUFFLE( 3, 3, 3, 3 ) ); + } + + { + const __m128 d0 = _mm_nmsub_ps( pw0, side, pa0 ); + const __m128 d1 = _mm_nmsub_ps( pw1, side, pa1 ); + const __m128 delta = _mm_sub_ps( d0, d1 ); + const __m128 deltaAbs = _mm_and_ps( delta, vector_float_abs_mask ); + const __m128 clamp = _mm_cmpgt_ps( vector_float_smallest_non_denorm, deltaAbs ); + const __m128 deltaClamped = _mm_sel_ps( delta, vector_float_one, clamp ); + const __m128 fraction = _mm_mul_ps( d0, _mm_rcp32_ps( deltaClamped ) ); + const __m128 fractionClamped0 = _mm_sel_ps( fraction, vector_float_one, clamp ); + const __m128 fractionClamped1 = _mm_max_ps( fractionClamped0, vector_float_zero ); + const __m128 fractionClamped2 = _mm_min_ps( fractionClamped1, vector_float_one ); + + _mm_store_ps( &clipFractions[i], fractionClamped2 ); + } + } + + numPoints = _mm_cvtsi128_si32( index ) & 0xFFFF; + + for ( int i = 0; i < localNumPoint; i += 4 ) { + const int i0 = ( i + 0 ) & ( ( i + 0 - localNumPoint ) >> 31 ); + const int i1 = ( i + 1 ) & ( ( i + 1 - localNumPoint ) >> 31 ); + const int i2 = ( i + 2 ) & ( ( i + 2 - localNumPoint ) >> 31 ); + const int i3 = ( i + 3 ) & ( ( i + 3 - localNumPoint ) >> 31 ); + const int i4 = ( i + 4 ) & ( ( i + 4 - localNumPoint ) >> 31 ); + + const __m128 p0 = _mm_load_ps( points[i0].ToFloatPtr() ); + const __m128 p1 = _mm_load_ps( points[i1].ToFloatPtr() ); + const __m128 p2 = _mm_load_ps( points[i2].ToFloatPtr() ); + const __m128 p3 = _mm_load_ps( points[i3].ToFloatPtr() ); + const __m128 p4 = _mm_load_ps( points[i4].ToFloatPtr() ); + + const __m128 fraction = _mm_load_ps( &clipFractions[i] ); + + const __m128 c0 = _mm_madd_ps( _mm_splat_ps( fraction, 0 ), _mm_sub_ps( p1, p0 ), p0 ); + const __m128 c1 = _mm_madd_ps( _mm_splat_ps( fraction, 1 ), _mm_sub_ps( p2, p1 ), p1 ); + const __m128 c2 = _mm_madd_ps( _mm_splat_ps( fraction, 2 ), _mm_sub_ps( p3, p2 ), p2 ); + const __m128 c3 = _mm_madd_ps( _mm_splat_ps( fraction, 3 ), _mm_sub_ps( p4, p3 ), p3 ); + + _mm_store_ps( newPoints[indices[i * 2 + 0]].ToFloatPtr(), p0 ); + _mm_store_ps( newPoints[indices[i * 2 + 1]].ToFloatPtr(), c0 ); + _mm_store_ps( newPoints[indices[i * 2 + 2]].ToFloatPtr(), p1 ); + _mm_store_ps( newPoints[indices[i * 2 + 3]].ToFloatPtr(), c1 ); + _mm_store_ps( newPoints[indices[i * 2 + 4]].ToFloatPtr(), p2 ); + _mm_store_ps( newPoints[indices[i * 2 + 5]].ToFloatPtr(), c2 ); + _mm_store_ps( newPoints[indices[i * 2 + 6]].ToFloatPtr(), p3 ); + _mm_store_ps( newPoints[indices[i * 2 + 7]].ToFloatPtr(), c3 ); + } +} + +/* +======================== +ClipHomogeneousPolygonToUnitCube + +Clips a polygon with homogeneous coordinates to all six axis aligned unit cube planes. +======================== +*/ +static int ClipHomogeneousPolygonToUnitCube_SSE2( idVec4 * points, int numPoints ) { + assert( numPoints < 16 - 6 ); + ALIGNTYPE16 idVec4 newPoints[16 * 2]; + +#if defined( CLIP_SPACE_D3D ) // the D3D near plane is at Z=0 instead of Z=-1 + ClipHomogeneousPolygonToSide_SSE2( newPoints, points, numPoints, 2, vector_float_neg_one, vector_float_zero ); // near +#else + ClipHomogeneousPolygonToSide_SSE2( newPoints, points, numPoints, 2, vector_float_neg_one, vector_float_one ); // near +#endif + ClipHomogeneousPolygonToSide_SSE2( points, newPoints, numPoints, 2, vector_float_pos_one, vector_float_one ); // far + ClipHomogeneousPolygonToSide_SSE2( newPoints, points, numPoints, 1, vector_float_neg_one, vector_float_one ); // bottom + ClipHomogeneousPolygonToSide_SSE2( points, newPoints, numPoints, 1, vector_float_pos_one, vector_float_one ); // top + ClipHomogeneousPolygonToSide_SSE2( newPoints, points, numPoints, 0, vector_float_neg_one, vector_float_one ); // left + ClipHomogeneousPolygonToSide_SSE2( points, newPoints, numPoints, 0, vector_float_pos_one, vector_float_one ); // right + return numPoints; +} + + +/* +======================== +idRenderMatrix::ProjectedFullyClippedBounds + +Calculates the bounds of the given bounding box projected with the given Model View Projection (MVP) matrix. +If 'windowSpace' is true then the calculated bounds along each axis are moved and clamped to the [0, 1] range. + +The given bounding box is first fully clipped to the MVP to get the smallest projected bounds. + +Note that this code assumes the MVP matrix has an infinite far clipping plane. When the far plane is at +infinity the bounds are never far clipped and it is sufficient to test whether or not the center of the +near clip plane is inside the bounds to calculate the correct minimum Z. If the far plane is not at +infinity then this code would also have to test for the view frustum being completely contained inside +the given bounds in which case the projected bounds should be set to fully cover the view frustum. +======================== +*/ +void idRenderMatrix::ProjectedFullyClippedBounds( idBounds & projected, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace ) { + + const __m128 mvp0 = _mm_loadu_ps( mvp[0] ); + const __m128 mvp1 = _mm_loadu_ps( mvp[1] ); + const __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + const __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + const __m128 t0 = _mm_unpacklo_ps( mvp0, mvp2 ); // mvp[0][0], mvp[2][0], mvp[0][1], mvp[2][1] + const __m128 t1 = _mm_unpackhi_ps( mvp0, mvp2 ); // mvp[0][2], mvp[2][2], mvp[0][3], mvp[2][3] + const __m128 t2 = _mm_unpacklo_ps( mvp1, mvp3 ); // mvp[1][0], mvp[3][0], mvp[1][1], mvp[3][1] + const __m128 t3 = _mm_unpackhi_ps( mvp1, mvp3 ); // mvp[1][2], mvp[3][2], mvp[1][3], mvp[3][3] + + const __m128 mvpX = _mm_unpacklo_ps( t0, t2 ); // mvp[0][0], mvp[1][0], mvp[2][0], mvp[3][0] + const __m128 mvpY = _mm_unpackhi_ps( t0, t2 ); // mvp[0][1], mvp[1][1], mvp[2][1], mvp[3][1] + const __m128 mvpZ = _mm_unpacklo_ps( t1, t3 ); // mvp[0][2], mvp[1][2], mvp[2][2], mvp[3][2] + const __m128 mvpW = _mm_unpackhi_ps( t1, t3 ); // mvp[0][3], mvp[1][3], mvp[2][3], mvp[3][3] + + const __m128 b0 = _mm_loadu_bounds_0( bounds ); + const __m128 b1 = _mm_loadu_bounds_1( bounds ); + + const __m128 b0X = _mm_splat_ps( b0, 0 ); + const __m128 b0Y = _mm_splat_ps( b0, 1 ); + const __m128 b0Z = _mm_splat_ps( b0, 2 ); + + const __m128 b1X = _mm_splat_ps( b1, 0 ); + const __m128 b1Y = _mm_splat_ps( b1, 1 ); + const __m128 b1Z = _mm_splat_ps( b1, 2 ); + + const __m128 p0 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 p1 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 p2 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 p3 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 p4 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + const __m128 p5 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + const __m128 p6 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + const __m128 p7 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + + ALIGNTYPE16 idVec4 projectedPoints[8]; + _mm_store_ps( projectedPoints[0].ToFloatPtr(), p0 ); + _mm_store_ps( projectedPoints[1].ToFloatPtr(), p1 ); + _mm_store_ps( projectedPoints[2].ToFloatPtr(), p2 ); + _mm_store_ps( projectedPoints[3].ToFloatPtr(), p3 ); + _mm_store_ps( projectedPoints[4].ToFloatPtr(), p4 ); + _mm_store_ps( projectedPoints[5].ToFloatPtr(), p5 ); + _mm_store_ps( projectedPoints[6].ToFloatPtr(), p6 ); + _mm_store_ps( projectedPoints[7].ToFloatPtr(), p7 ); + + ALIGNTYPE16 idVec4 clippedPoints[6 * 16]; + int numClippedPoints = 0; + for ( int i = 0; i < 6; i++ ) { + _mm_store_ps( clippedPoints[numClippedPoints + 0].ToFloatPtr(), _mm_load_ps( projectedPoints[boxPolygonVertices[i][0]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 1].ToFloatPtr(), _mm_load_ps( projectedPoints[boxPolygonVertices[i][1]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 2].ToFloatPtr(), _mm_load_ps( projectedPoints[boxPolygonVertices[i][2]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 3].ToFloatPtr(), _mm_load_ps( projectedPoints[boxPolygonVertices[i][3]].ToFloatPtr() ) ); + numClippedPoints += ClipHomogeneousPolygonToUnitCube_SSE2( &clippedPoints[numClippedPoints], 4 ); + } + + // repeat the first clipped point at the end to get a multiple of 4 clipped points + const __m128 point0 = _mm_load_ps( clippedPoints[0].ToFloatPtr() ); + for ( int i = numClippedPoints; ( i & 3 ) != 0; i++ ) { + _mm_store_ps( clippedPoints[i].ToFloatPtr(), point0 ); + } + + // test if the center of the near clip plane is inside the given bounding box + const idVec3 localNearClipCenter = LocalNearClipCenterFromMVP( mvp ); + const bool inside = bounds.Expand( RENDER_MATRIX_PROJECTION_EPSILON ).ContainsPoint( localNearClipCenter ); + + __m128 minX = vector_float_pos_infinity; + __m128 minY = vector_float_pos_infinity; + __m128 minZ = inside ? vector_float_neg_one : vector_float_pos_infinity; + + __m128 maxX = vector_float_neg_infinity; + __m128 maxY = vector_float_neg_infinity; + __m128 maxZ = vector_float_neg_infinity; + + for ( int i = 0; i < numClippedPoints; i += 4 ) { + const __m128 cp0 = _mm_load_ps( clippedPoints[i + 0].ToFloatPtr() ); + const __m128 cp1 = _mm_load_ps( clippedPoints[i + 1].ToFloatPtr() ); + const __m128 cp2 = _mm_load_ps( clippedPoints[i + 2].ToFloatPtr() ); + const __m128 cp3 = _mm_load_ps( clippedPoints[i + 3].ToFloatPtr() ); + + const __m128 s0 = _mm_unpacklo_ps( cp0, cp2 ); // cp0[0], cp2[0], cp0[1], cp2[1] + const __m128 s1 = _mm_unpackhi_ps( cp0, cp2 ); // cp0[2], cp2[2], cp0[3], cp2[3] + const __m128 s2 = _mm_unpacklo_ps( cp1, cp3 ); // cp1[0], cp3[0], cp1[1], cp3[1] + const __m128 s3 = _mm_unpackhi_ps( cp1, cp3 ); // cp1[2], cp3[2], cp1[3], cp3[3] + + const __m128 cpX = _mm_unpacklo_ps( s0, s2 ); // cp0[0], cp1[0], cp2[0], cp3[0] + const __m128 cpY = _mm_unpackhi_ps( s0, s2 ); // cp0[1], cp1[1], cp2[1], cp3[1] + const __m128 cpZ = _mm_unpacklo_ps( s1, s3 ); // cp0[2], cp1[2], cp2[2], cp3[2] + const __m128 cpW = _mm_unpackhi_ps( s1, s3 ); // cp0[3], cp1[3], cp2[3], cp3[3] + + const __m128 rW = _mm_rcp32_ps( cpW ); + const __m128 rX = _mm_mul_ps( cpX, rW ); + const __m128 rY = _mm_mul_ps( cpY, rW ); + const __m128 rZ = _mm_mul_ps( cpZ, rW ); + + minX = _mm_min_ps( minX, rX ); + minY = _mm_min_ps( minY, rY ); + minZ = _mm_min_ps( minZ, rZ ); + + maxX = _mm_max_ps( maxX, rX ); + maxY = _mm_max_ps( maxY, rY ); + maxZ = _mm_max_ps( maxZ, rZ ); + } + + minX = _mm_min_ps( minX, _mm_perm_ps( minX, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minY = _mm_min_ps( minY, _mm_perm_ps( minY, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + minX = _mm_min_ps( minX, _mm_perm_ps( minX, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + minY = _mm_min_ps( minY, _mm_perm_ps( minY, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + maxX = _mm_max_ps( maxX, _mm_perm_ps( maxX, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxY = _mm_max_ps( maxY, _mm_perm_ps( maxY, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + + maxX = _mm_max_ps( maxX, _mm_perm_ps( maxX, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + maxY = _mm_max_ps( maxY, _mm_perm_ps( maxY, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + if ( windowSpace ) { + minX = _mm_madd_ps( minX, vector_float_half, vector_float_half ); + maxX = _mm_madd_ps( maxX, vector_float_half, vector_float_half ); + + minY = _mm_madd_ps( minY, vector_float_half, vector_float_half ); + maxY = _mm_madd_ps( maxY, vector_float_half, vector_float_half ); + +#if !defined( CLIP_SPACE_D3D ) // the D3D clip space Z is already in the range [0,1] + minZ = _mm_madd_ps( minZ, vector_float_half, vector_float_half ); + maxZ = _mm_madd_ps( maxZ, vector_float_half, vector_float_half ); +#endif + + minX = _mm_max_ps( _mm_min_ps( minX, vector_float_one ), vector_float_zero ); + maxX = _mm_max_ps( _mm_min_ps( maxX, vector_float_one ), vector_float_zero ); + + minY = _mm_max_ps( _mm_min_ps( minY, vector_float_one ), vector_float_zero ); + maxY = _mm_max_ps( _mm_min_ps( maxY, vector_float_one ), vector_float_zero ); + + minZ = _mm_max_ps( _mm_min_ps( minZ, vector_float_one ), vector_float_zero ); + maxZ = _mm_max_ps( _mm_min_ps( maxZ, vector_float_one ), vector_float_zero ); + } + + _mm_store_ss( & projected[0].x, minX ); + _mm_store_ss( & projected[0].y, minY ); + _mm_store_ss( & projected[0].z, minZ ); + + _mm_store_ss( & projected[1].x, maxX ); + _mm_store_ss( & projected[1].y, maxY ); + _mm_store_ss( & projected[1].z, maxZ ); + +} + +/* +======================== +idRenderMatrix::DepthBoundsForBounds + +Calculates the depth bounds of the given bounding box projected with the given Model View Projection (MVP) matrix. +If 'windowSpace' is true then the calculated depth bounds are moved and clamped to the [0, 1] range. + +The given bounding box is not clipped to the MVP so the depth bounds may not be as tight as possible. +======================== +*/ +void idRenderMatrix::DepthBoundsForBounds( float & min, float & max, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace ) { + + __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + // take the four points on the X-Y plane + __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + // compute four partial Z,W values + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + + __m128 z0 = _mm_madd_ps( vz0, _mm_splat_ps( mvp2, 2 ), parz ); + __m128 w0 = _mm_madd_ps( vz0, _mm_splat_ps( mvp3, 2 ), parw ); + + __m128 z1 = _mm_madd_ps( vz1, _mm_splat_ps( mvp2, 2 ), parz ); + __m128 w1 = _mm_madd_ps( vz1, _mm_splat_ps( mvp3, 2 ), parw ); + + __m128 s0 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w0 ); + w0 = _mm_or_ps( w0, _mm_and_ps( vector_float_smallest_non_denorm, s0 ) ); + + __m128 rw0 = _mm_rcp32_ps( w0 ); + z0 = _mm_mul_ps( z0, rw0 ); + z0 = _mm_sel_ps( z0, vector_float_neg_infinity, s0 ); + + __m128 s1 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w1 ); + w1 = _mm_or_ps( w1, _mm_and_ps( vector_float_smallest_non_denorm, s1 ) ); + + __m128 rw1 = _mm_rcp32_ps( w1 ); + z1 = _mm_mul_ps( z1, rw1 ); + z1 = _mm_sel_ps( z1, vector_float_neg_infinity, s1 ); + + __m128 minv = _mm_min_ps( z0, z1 ); + __m128 maxv = _mm_max_ps( z0, z1 ); + + minv = _mm_min_ps( minv, _mm_perm_ps( minv, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minv = _mm_min_ps( minv, _mm_perm_ps( minv, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + maxv = _mm_max_ps( maxv, _mm_perm_ps( maxv, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxv = _mm_max_ps( maxv, _mm_perm_ps( maxv, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + if ( windowSpace ) { +#if !defined( CLIP_SPACE_D3D ) // the D3D clip space Z is already in the range [0,1] + minv = _mm_madd_ps( minv, vector_float_half, vector_float_half ); + maxv = _mm_madd_ps( maxv, vector_float_half, vector_float_half ); +#endif + minv = _mm_max_ps( minv, vector_float_zero ); + maxv = _mm_min_ps( maxv, vector_float_one ); + } + + _mm_store_ss( & min, minv ); + _mm_store_ss( & max, maxv ); + +} + +/* +======================== +idRenderMatrix::DepthBoundsForExtrudedBounds + +Calculates the depth bounds of the given extruded bounding box projected with the given Model View Projection (MVP) matrix. +The given bounding box is extruded in the 'extrudeDirection' up to the 'clipPlane'. +If 'windowSpace' is true then the calculated depth bounds are moved and clamped to the [0, 1] range. + +The extruded bounding box is not clipped to the MVP so the depth bounds may not be as tight as possible. +======================== +*/ +void idRenderMatrix::DepthBoundsForExtrudedBounds( float & min, float & max, const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & extrudeDirection, const idPlane & clipPlane, bool windowSpace ) { + assert( idMath::Fabs( extrudeDirection * clipPlane.Normal() ) >= idMath::FLT_SMALLEST_NON_DENORMAL ); + + + __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + __m128 b0 = _mm_loadu_bounds_0( bounds ); + __m128 b1 = _mm_loadu_bounds_1( bounds ); + + // take the four points on the X-Y plane + __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + __m128 minv; + __m128 maxv; + + // calculate the min/max depth values for the bounding box corners + { + // compute four partial Z,W values + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + + __m128 z0 = _mm_madd_ps( vz0, _mm_splat_ps( mvp2, 2 ), parz ); + __m128 w0 = _mm_madd_ps( vz0, _mm_splat_ps( mvp3, 2 ), parw ); + + __m128 z1 = _mm_madd_ps( vz1, _mm_splat_ps( mvp2, 2 ), parz ); + __m128 w1 = _mm_madd_ps( vz1, _mm_splat_ps( mvp3, 2 ), parw ); + + __m128 s0 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w0 ); + w0 = _mm_or_ps( w0, _mm_and_ps( vector_float_smallest_non_denorm, s0 ) ); + + __m128 rw0 = _mm_rcp32_ps( w0 ); + z0 = _mm_mul_ps( z0, rw0 ); + z0 = _mm_sel_ps( z0, vector_float_neg_infinity, s0 ); + + __m128 s1 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w1 ); + w1 = _mm_or_ps( w1, _mm_and_ps( vector_float_smallest_non_denorm, s1 ) ); + + __m128 rw1 = _mm_rcp32_ps( w1 ); + z1 = _mm_mul_ps( z1, rw1 ); + z1 = _mm_sel_ps( z1, vector_float_neg_infinity, s1 ); + + minv = _mm_min_ps( z0, z1 ); + maxv = _mm_max_ps( z0, z1 ); + } + + // calculate and include the min/max depth value for the extruded bounding box corners + { + __m128 clipX = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 0 ), 0 ); + __m128 clipY = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 1 ), 0 ); + __m128 clipZ = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 2 ), 0 ); + __m128 clipW = _mm_splat_ps( _mm_load_ss( clipPlane.ToFloatPtr() + 3 ), 0 ); + + __m128 extrudeX = _mm_splat_ps( _mm_load_ss( extrudeDirection.ToFloatPtr() + 0 ), 0 ); + __m128 extrudeY = _mm_splat_ps( _mm_load_ss( extrudeDirection.ToFloatPtr() + 1 ), 0 ); + __m128 extrudeZ = _mm_splat_ps( _mm_load_ss( extrudeDirection.ToFloatPtr() + 2 ), 0 ); + + __m128 closing = _mm_madd_ps( clipX, extrudeX, _mm_madd_ps( clipY, extrudeY, _mm_mul_ps( clipZ, extrudeZ ) ) ); + __m128 invClosing = _mm_rcp32_ps( closing ); + invClosing = _mm_xor_ps( invClosing, vector_float_sign_bit ); + + __m128 dt = _mm_madd_ps( clipX, vx, _mm_madd_ps( clipY, vy, clipW ) ); + __m128 d0 = _mm_madd_ps( clipZ, vz0, dt ); + __m128 d1 = _mm_madd_ps( clipZ, vz1, dt ); + + d0 = _mm_mul_ps( d0, invClosing ); + d1 = _mm_mul_ps( d1, invClosing ); + + __m128 vx0 = _mm_madd_ps( extrudeX, d0, vx ); + __m128 vx1 = _mm_madd_ps( extrudeX, d1, vx ); + + __m128 vy0 = _mm_madd_ps( extrudeY, d0, vy ); + __m128 vy1 = _mm_madd_ps( extrudeY, d1, vy ); + + vz0 = _mm_madd_ps( extrudeZ, d0, vz0 ); + vz1 = _mm_madd_ps( extrudeZ, d1, vz1 ); + + __m128 mvp2X = _mm_splat_ps( mvp2, 0 ); + __m128 mvp3X = _mm_splat_ps( mvp3, 0 ); + + __m128 mvp2W = _mm_splat_ps( mvp2, 3 ); + __m128 mvp3W = _mm_splat_ps( mvp3, 3 ); + + __m128 z0 = _mm_madd_ps( vx0, mvp2X, mvp2W ); + __m128 w0 = _mm_madd_ps( vx0, mvp3X, mvp3W ); + + __m128 z1 = _mm_madd_ps( vx1, mvp2X, mvp2W ); + __m128 w1 = _mm_madd_ps( vx1, mvp3X, mvp3W ); + + __m128 mvp2Y = _mm_splat_ps( mvp2, 1 ); + __m128 mvp3Y = _mm_splat_ps( mvp3, 1 ); + + z0 = _mm_madd_ps( vy0, mvp2Y, z0 ); + w0 = _mm_madd_ps( vy0, mvp3Y, w0 ); + + z1 = _mm_madd_ps( vy1, mvp2Y, z1 ); + w1 = _mm_madd_ps( vy1, mvp3Y, w1 ); + + __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + z0 = _mm_madd_ps( vz0, mvp2Z, z0 ); + w0 = _mm_madd_ps( vz0, mvp3Z, w0 ); + + z1 = _mm_madd_ps( vz1, mvp2Z, z1 ); + w1 = _mm_madd_ps( vz1, mvp3Z, w1 ); + + __m128 s0 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w0 ); + w0 = _mm_or_ps( w0, _mm_and_ps( vector_float_smallest_non_denorm, s0 ) ); + + __m128 rw0 = _mm_rcp32_ps( w0 ); + z0 = _mm_mul_ps( z0, rw0 ); + z0 = _mm_sel_ps( z0, vector_float_neg_infinity, s0 ); + + __m128 s1 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w1 ); + w1 = _mm_or_ps( w1, _mm_and_ps( vector_float_smallest_non_denorm, s1 ) ); + + __m128 rw1 = _mm_rcp32_ps( w1 ); + z1 = _mm_mul_ps( z1, rw1 ); + z1 = _mm_sel_ps( z1, vector_float_neg_infinity, s1 ); + + minv = _mm_min_ps( minv, z0 ); + maxv = _mm_max_ps( maxv, z0 ); + + minv = _mm_min_ps( minv, z1 ); + maxv = _mm_max_ps( maxv, z1 ); + } + + minv = _mm_min_ps( minv, _mm_perm_ps( minv, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minv = _mm_min_ps( minv, _mm_perm_ps( minv, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + maxv = _mm_max_ps( maxv, _mm_perm_ps( maxv, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxv = _mm_max_ps( maxv, _mm_perm_ps( maxv, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + if ( windowSpace ) { +#if !defined( CLIP_SPACE_D3D ) // the D3D clip space Z is already in the range [0,1] + minv = _mm_madd_ps( minv, vector_float_half, vector_float_half ); + maxv = _mm_madd_ps( maxv, vector_float_half, vector_float_half ); +#endif + minv = _mm_max_ps( minv, vector_float_zero ); + maxv = _mm_min_ps( maxv, vector_float_one ); + } + + _mm_store_ss( & min, minv ); + _mm_store_ss( & max, maxv ); + +} + +/* +======================== +PointInsideInfiniteShadow + +Returns true if the 'localPoint' is inside the infinite shadow volume cast +from the given occluder bounding box and the given light position. +======================== +*/ +static bool PointInsideInfiniteShadow( const idBounds & occluderBounds, const idVec3 & localLightOrigin, const idVec3 & localPoint, const float epsilon ) { + // Expand the bounds with an epsilon. + const idBounds expandedBounds = occluderBounds.Expand( epsilon ); + + // If the point is inside the bounding box then the point + // is also inside the shadow projection. + if ( expandedBounds.ContainsPoint( localPoint ) ) { + return true; + } + + // If the light is inside the bounding box then the shadow is projected + // in all directions and any point is inside the infinte shadow projection. + if ( expandedBounds.ContainsPoint( localPoint ) ) { + return true; + } + + // If the line from localLightOrigin to localPoint intersects the + // bounding box then the point is inside the infinite shadow projection. + if ( expandedBounds.LineIntersection( localLightOrigin, localPoint ) ) { + return true; + } + + // The point is definitely not inside the projected shadow. + return false; +} + +/* +======================== +idRenderMatrix::DepthBoundsForShadowBounds + +Calculates the depth bounds of the infinite shadow volume projected with the given Model View Projection (MVP) matrix. +The infinite shadow volume is cast from the given occluder bounding box and the given light position. +If 'windowSpace' is true then the calculated depth bounds are moved and clamped to the [0, 1] range. + +The infinite shadow volume is fully clipped to the MVP to get the tightest possible bounds. + +Note that this code assumes the MVP matrix has an infinite far clipping plane. When the far plane is at +infinity the shadow volume is never far clipped and it is sufficient to test whether or not the center +of the near clip plane is inside the shadow volume to calculate the correct minimum Z. If the far plane +is not at infinity then this code would also have to test for the view frustum being completely contained +inside the shadow volume to also calculate the correct maximum Z. This could be done, for instance, by +testing if the center of the far clipping plane is contained inside the shadow volume. +======================== +*/ +void idRenderMatrix::DepthBoundsForShadowBounds( float & min, float & max, const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & localLightOrigin, bool windowSpace ) { + + const __m128 mvp0 = _mm_loadu_ps( mvp[0] ); + const __m128 mvp1 = _mm_loadu_ps( mvp[1] ); + const __m128 mvp2 = _mm_loadu_ps( mvp[2] ); + const __m128 mvp3 = _mm_loadu_ps( mvp[3] ); + + const __m128 t0 = _mm_unpacklo_ps( mvp0, mvp2 ); // mvp[0][0], mvp[2][0], mvp[0][1], mvp[2][1] + const __m128 t1 = _mm_unpackhi_ps( mvp0, mvp2 ); // mvp[0][2], mvp[2][2], mvp[0][3], mvp[2][3] + const __m128 t2 = _mm_unpacklo_ps( mvp1, mvp3 ); // mvp[1][0], mvp[3][0], mvp[1][1], mvp[3][1] + const __m128 t3 = _mm_unpackhi_ps( mvp1, mvp3 ); // mvp[1][2], mvp[3][2], mvp[1][3], mvp[3][3] + + const __m128 mvpX = _mm_unpacklo_ps( t0, t2 ); // mvp[0][0], mvp[1][0], mvp[2][0], mvp[3][0] + const __m128 mvpY = _mm_unpackhi_ps( t0, t2 ); // mvp[0][1], mvp[1][1], mvp[2][1], mvp[3][1] + const __m128 mvpZ = _mm_unpacklo_ps( t1, t3 ); // mvp[0][2], mvp[1][2], mvp[2][2], mvp[3][2] + const __m128 mvpW = _mm_unpackhi_ps( t1, t3 ); // mvp[0][3], mvp[1][3], mvp[2][3], mvp[3][3] + + const __m128 b0 = _mm_loadu_bounds_0( bounds ); + const __m128 b1 = _mm_loadu_bounds_1( bounds ); + + const __m128 lightOriginX = _mm_load_ss( localLightOrigin.ToFloatPtr() + 0 ); + const __m128 lightOriginY = _mm_load_ss( localLightOrigin.ToFloatPtr() + 1 ); + const __m128 lightOriginZ = _mm_load_ss( localLightOrigin.ToFloatPtr() + 2 ); + const __m128 lightOrigin = _mm_unpacklo_ps( _mm_unpacklo_ps( lightOriginX, lightOriginZ ), lightOriginY ); + + // calculate the front facing polygon bits + int frontBits = GetBoxFrontBits_SSE2( b0, b1, lightOrigin ); + + const __m128 b0X = _mm_splat_ps( b0, 0 ); + const __m128 b0Y = _mm_splat_ps( b0, 1 ); + const __m128 b0Z = _mm_splat_ps( b0, 2 ); + + const __m128 b1X = _mm_splat_ps( b1, 0 ); + const __m128 b1Y = _mm_splat_ps( b1, 1 ); + const __m128 b1Z = _mm_splat_ps( b1, 2 ); + + // bounding box corners + const __m128 np0 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 np1 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 np2 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 np3 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b0Z, mvpZ, mvpW ) ) ); + const __m128 np4 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + const __m128 np5 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b0Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + const __m128 np6 = _mm_madd_ps( b1X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + const __m128 np7 = _mm_madd_ps( b0X, mvpX, _mm_madd_ps( b1Y, mvpY, _mm_madd_ps( b1Z, mvpZ, mvpW ) ) ); + + ALIGNTYPE16 idVec4 projectedNearPoints[8]; + _mm_store_ps( projectedNearPoints[0].ToFloatPtr(), np0 ); + _mm_store_ps( projectedNearPoints[1].ToFloatPtr(), np1 ); + _mm_store_ps( projectedNearPoints[2].ToFloatPtr(), np2 ); + _mm_store_ps( projectedNearPoints[3].ToFloatPtr(), np3 ); + _mm_store_ps( projectedNearPoints[4].ToFloatPtr(), np4 ); + _mm_store_ps( projectedNearPoints[5].ToFloatPtr(), np5 ); + _mm_store_ps( projectedNearPoints[6].ToFloatPtr(), np6 ); + _mm_store_ps( projectedNearPoints[7].ToFloatPtr(), np7 ); + + // subtract the light position from the bounding box + const __m128 lightX = _mm_splat_ps( lightOriginX, 0 ); + const __m128 lightY = _mm_splat_ps( lightOriginY, 0 ); + const __m128 lightZ = _mm_splat_ps( lightOriginZ, 0 ); + + const __m128 d0X = _mm_sub_ps( b0X, lightX ); + const __m128 d0Y = _mm_sub_ps( b0Y, lightY ); + const __m128 d0Z = _mm_sub_ps( b0Z, lightZ ); + + const __m128 d1X = _mm_sub_ps( b1X, lightX ); + const __m128 d1Y = _mm_sub_ps( b1Y, lightY ); + const __m128 d1Z = _mm_sub_ps( b1Z, lightZ ); + + // bounding box corners projected to infinity from the light position + const __m128 fp0 = _mm_madd_ps( d0X, mvpX, _mm_madd_ps( d0Y, mvpY, _mm_mul_ps( d0Z, mvpZ ) ) ); + const __m128 fp1 = _mm_madd_ps( d1X, mvpX, _mm_madd_ps( d0Y, mvpY, _mm_mul_ps( d0Z, mvpZ ) ) ); + const __m128 fp2 = _mm_madd_ps( d1X, mvpX, _mm_madd_ps( d1Y, mvpY, _mm_mul_ps( d0Z, mvpZ ) ) ); + const __m128 fp3 = _mm_madd_ps( d0X, mvpX, _mm_madd_ps( d1Y, mvpY, _mm_mul_ps( d0Z, mvpZ ) ) ); + const __m128 fp4 = _mm_madd_ps( d0X, mvpX, _mm_madd_ps( d0Y, mvpY, _mm_mul_ps( d1Z, mvpZ ) ) ); + const __m128 fp5 = _mm_madd_ps( d1X, mvpX, _mm_madd_ps( d0Y, mvpY, _mm_mul_ps( d1Z, mvpZ ) ) ); + const __m128 fp6 = _mm_madd_ps( d1X, mvpX, _mm_madd_ps( d1Y, mvpY, _mm_mul_ps( d1Z, mvpZ ) ) ); + const __m128 fp7 = _mm_madd_ps( d0X, mvpX, _mm_madd_ps( d1Y, mvpY, _mm_mul_ps( d1Z, mvpZ ) ) ); + + ALIGNTYPE16 idVec4 projectedFarPoints[8]; + _mm_store_ps( projectedFarPoints[0].ToFloatPtr(), fp0 ); + _mm_store_ps( projectedFarPoints[1].ToFloatPtr(), fp1 ); + _mm_store_ps( projectedFarPoints[2].ToFloatPtr(), fp2 ); + _mm_store_ps( projectedFarPoints[3].ToFloatPtr(), fp3 ); + _mm_store_ps( projectedFarPoints[4].ToFloatPtr(), fp4 ); + _mm_store_ps( projectedFarPoints[5].ToFloatPtr(), fp5 ); + _mm_store_ps( projectedFarPoints[6].ToFloatPtr(), fp6 ); + _mm_store_ps( projectedFarPoints[7].ToFloatPtr(), fp7 ); + + ALIGNTYPE16 idVec4 clippedPoints[( 6 + 12 ) * 16]; + int numClippedPoints = 0; + + // clip the front facing bounding box polygons at the near cap + const frontPolygons_t & frontPolygons = boxFrontPolygonsForFrontBits[frontBits]; + for ( int i = 0; i < frontPolygons.count; i++ ) { + const int polygon = frontPolygons.indices[i]; + _mm_store_ps( clippedPoints[numClippedPoints + 0].ToFloatPtr(), _mm_load_ps( projectedNearPoints[boxPolygonVertices[polygon][0]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 1].ToFloatPtr(), _mm_load_ps( projectedNearPoints[boxPolygonVertices[polygon][1]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 2].ToFloatPtr(), _mm_load_ps( projectedNearPoints[boxPolygonVertices[polygon][2]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 3].ToFloatPtr(), _mm_load_ps( projectedNearPoints[boxPolygonVertices[polygon][3]].ToFloatPtr() ) ); + numClippedPoints += ClipHomogeneousPolygonToUnitCube_SSE2( &clippedPoints[numClippedPoints], 4 ); + } + + // clip the front facing bounding box polygons projected to the far cap + for ( int i = 0; i < frontPolygons.count; i++ ) { + const int polygon = frontPolygons.indices[i]; + _mm_store_ps( clippedPoints[numClippedPoints + 0].ToFloatPtr(), _mm_load_ps( projectedFarPoints[boxPolygonVertices[polygon][0]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 1].ToFloatPtr(), _mm_load_ps( projectedFarPoints[boxPolygonVertices[polygon][1]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 2].ToFloatPtr(), _mm_load_ps( projectedFarPoints[boxPolygonVertices[polygon][2]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 3].ToFloatPtr(), _mm_load_ps( projectedFarPoints[boxPolygonVertices[polygon][3]].ToFloatPtr() ) ); + numClippedPoints += ClipHomogeneousPolygonToUnitCube_SSE2( &clippedPoints[numClippedPoints], 4 ); + } + + // clip the silhouette edge polygons that stretch to infinity + const silhouetteEdges_t & silhouetteEdges = boxSilhouetteEdgesForFrontBits[frontBits]; + for ( int i = 0; i < silhouetteEdges.count; i++ ) { + const int edge = silhouetteEdges.indices[i]; + _mm_store_ps( clippedPoints[numClippedPoints + 0].ToFloatPtr(), _mm_load_ps( projectedNearPoints[boxEdgeVertices[edge][0]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 1].ToFloatPtr(), _mm_load_ps( projectedNearPoints[boxEdgeVertices[edge][1]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 2].ToFloatPtr(), _mm_load_ps( projectedFarPoints[boxEdgeVertices[edge][1]].ToFloatPtr() ) ); + _mm_store_ps( clippedPoints[numClippedPoints + 3].ToFloatPtr(), _mm_load_ps( projectedFarPoints[boxEdgeVertices[edge][0]].ToFloatPtr() ) ); + numClippedPoints += ClipHomogeneousPolygonToUnitCube_SSE2( &clippedPoints[numClippedPoints], 4 ); + } + + // repeat the first clipped point at the end to get a multiple of 4 clipped points + const __m128 point0 = _mm_load_ps( clippedPoints[0].ToFloatPtr() ); + for ( int i = numClippedPoints; ( i & 3 ) != 0; i++ ) { + _mm_store_ps( clippedPoints[i].ToFloatPtr(), point0 ); + } + + // test if the center of the near clip plane is inside the infinite shadow volume + const idVec3 localNearClipCenter = LocalNearClipCenterFromMVP( mvp ); + const bool inside = PointInsideInfiniteShadow( bounds, localLightOrigin, localNearClipCenter, RENDER_MATRIX_PROJECTION_EPSILON ); + + __m128 minZ = inside ? vector_float_neg_one : vector_float_pos_infinity; + __m128 maxZ = vector_float_neg_infinity; + + for ( int i = 0; i < numClippedPoints; i += 4 ) { + const __m128 cp0 = _mm_load_ps( clippedPoints[i + 0].ToFloatPtr() ); + const __m128 cp1 = _mm_load_ps( clippedPoints[i + 1].ToFloatPtr() ); + const __m128 cp2 = _mm_load_ps( clippedPoints[i + 2].ToFloatPtr() ); + const __m128 cp3 = _mm_load_ps( clippedPoints[i + 3].ToFloatPtr() ); + + const __m128 s1 = _mm_unpackhi_ps( cp0, cp2 ); // cp0[2], cp2[2], cp0[3], cp2[3] + const __m128 s3 = _mm_unpackhi_ps( cp1, cp3 ); // cp1[2], cp3[2], cp1[3], cp3[3] + + const __m128 cpZ = _mm_unpacklo_ps( s1, s3 ); // cp0[2], cp1[2], cp2[2], cp3[2] + const __m128 cpW = _mm_unpackhi_ps( s1, s3 ); // cp0[3], cp1[3], cp2[3], cp3[3] + + const __m128 rW = _mm_rcp32_ps( cpW ); + const __m128 rZ = _mm_mul_ps( cpZ, rW ); + + minZ = _mm_min_ps( minZ, rZ ); + maxZ = _mm_max_ps( maxZ, rZ ); + } + + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + minZ = _mm_min_ps( minZ, _mm_perm_ps( minZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + maxZ = _mm_max_ps( maxZ, _mm_perm_ps( maxZ, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + if ( windowSpace ) { +#if !defined( CLIP_SPACE_D3D ) // the D3D clip space Z is already in the range [0,1] + minZ = _mm_madd_ps( minZ, vector_float_half, vector_float_half ); + maxZ = _mm_madd_ps( maxZ, vector_float_half, vector_float_half ); +#endif + + minZ = _mm_max_ps( _mm_min_ps( minZ, vector_float_one ), vector_float_zero ); + maxZ = _mm_max_ps( _mm_min_ps( maxZ, vector_float_one ), vector_float_zero ); + } + + _mm_store_ss( & min, minZ ); + _mm_store_ss( & max, maxZ ); + +} + +/* +======================== +idRenderMatrix::GetFrustumPlanes + +Normally the clip space extends from -1.0 to 1.0 on each axis, but by setting 'zeroToOne' +to true, the clip space will extend from 0.0 to 1.0 on each axis for a light projection matrix. +======================== +*/ +void idRenderMatrix::GetFrustumPlanes( idPlane planes[6], const idRenderMatrix & frustum, bool zeroToOne, bool normalize ) { + // FIXME: need to know whether or not this is a D3D MVP. + // We cannot just assume that it's an D3D MVP matrix when + // zeroToOne = false and CLIP_SPACE_D3D is defined because + // this code may be called for non-MVP matrices. + const bool isZeroOneZ = false; + + if ( zeroToOne ) { + // left: inside(p) = p * frustum[0] > 0 + planes[0][0] = frustum[0][0]; + planes[0][1] = frustum[0][1]; + planes[0][2] = frustum[0][2]; + planes[0][3] = frustum[0][3]; + + // bottom: inside(p) = p * frustum[1] > 0 + planes[2][0] = frustum[1][0]; + planes[2][1] = frustum[1][1]; + planes[2][2] = frustum[1][2]; + planes[2][3] = frustum[1][3]; + + // near: inside(p) = p * frustum[2] > 0 + planes[4][0] = frustum[2][0]; + planes[4][1] = frustum[2][1]; + planes[4][2] = frustum[2][2]; + planes[4][3] = frustum[2][3]; + } else { + // left: inside(p) = p * frustum[0] > - ( p * frustum[3] ) + planes[0][0] = frustum[3][0] + frustum[0][0]; + planes[0][1] = frustum[3][1] + frustum[0][1]; + planes[0][2] = frustum[3][2] + frustum[0][2]; + planes[0][3] = frustum[3][3] + frustum[0][3]; + + // bottom: inside(p) = p * frustum[1] > -( p * frustum[3] ) + planes[2][0] = frustum[3][0] + frustum[1][0]; + planes[2][1] = frustum[3][1] + frustum[1][1]; + planes[2][2] = frustum[3][2] + frustum[1][2]; + planes[2][3] = frustum[3][3] + frustum[1][3]; + + // near: inside(p) = p * frustum[2] > -( p * frustum[3] ) + planes[4][0] = isZeroOneZ ? ( frustum[2][0] ) : ( frustum[3][0] + frustum[2][0] ); + planes[4][1] = isZeroOneZ ? ( frustum[2][1] ) : ( frustum[3][1] + frustum[2][1] ); + planes[4][2] = isZeroOneZ ? ( frustum[2][2] ) : ( frustum[3][2] + frustum[2][2] ); + planes[4][3] = isZeroOneZ ? ( frustum[2][3] ) : ( frustum[3][3] + frustum[2][3] ); + } + + // right: inside(p) = p * frustum[0] < p * frustum[3] + planes[1][0] = frustum[3][0] - frustum[0][0]; + planes[1][1] = frustum[3][1] - frustum[0][1]; + planes[1][2] = frustum[3][2] - frustum[0][2]; + planes[1][3] = frustum[3][3] - frustum[0][3]; + + // top: inside(p) = p * frustum[1] < p * frustum[3] + planes[3][0] = frustum[3][0] - frustum[1][0]; + planes[3][1] = frustum[3][1] - frustum[1][1]; + planes[3][2] = frustum[3][2] - frustum[1][2]; + planes[3][3] = frustum[3][3] - frustum[1][3]; + + // far: inside(p) = p * frustum[2] < p * frustum[3] + planes[5][0] = frustum[3][0] - frustum[2][0]; + planes[5][1] = frustum[3][1] - frustum[2][1]; + planes[5][2] = frustum[3][2] - frustum[2][2]; + planes[5][3] = frustum[3][3] - frustum[2][3]; + + // optionally normalize the planes + if ( normalize ) { + for ( int i = 0; i < 6; i++ ) { + float s = idMath::InvSqrt( planes[i].Normal().LengthSqr() ); + planes[i][0] *= s; + planes[i][1] *= s; + planes[i][2] *= s; + planes[i][3] *= s; + } + } +} + +/* +======================== +idRenderMatrix::GetFrustumCorners +======================== +*/ +void idRenderMatrix::GetFrustumCorners( frustumCorners_t & corners, const idRenderMatrix & frustumTransform, const idBounds & frustumBounds ) { + assert_16_byte_aligned( &corners ); + + + __m128 mvp0 = _mm_loadu_ps( frustumTransform[0] ); + __m128 mvp1 = _mm_loadu_ps( frustumTransform[1] ); + __m128 mvp2 = _mm_loadu_ps( frustumTransform[2] ); + __m128 mvp3 = _mm_loadu_ps( frustumTransform[3] ); + + __m128 b0 = _mm_loadu_bounds_0( frustumBounds ); + __m128 b1 = _mm_loadu_bounds_1( frustumBounds ); + + // take the four points on the X-Y plane + __m128 vxy = _mm_unpacklo_ps( b0, b1 ); // min X, max X, min Y, max Y + __m128 vx = _mm_perm_ps( vxy, _MM_SHUFFLE( 1, 0, 1, 0 ) ); // min X, max X, min X, max X + __m128 vy = _mm_perm_ps( vxy, _MM_SHUFFLE( 3, 3, 2, 2 ) ); // min Y, min Y, max Y, max Y + + __m128 vz0 = _mm_splat_ps( b0, 2 ); // min Z, min Z, min Z, min Z + __m128 vz1 = _mm_splat_ps( b1, 2 ); // max Z, max Z, max Z, max Z + + // compute four partial X,Y,Z,W values + __m128 parx = _mm_splat_ps( mvp0, 3 ); + __m128 pary = _mm_splat_ps( mvp1, 3 ); + __m128 parz = _mm_splat_ps( mvp2, 3 ); + __m128 parw = _mm_splat_ps( mvp3, 3 ); + + parx = _mm_madd_ps( vx, _mm_splat_ps( mvp0, 0 ), parx ); + pary = _mm_madd_ps( vx, _mm_splat_ps( mvp1, 0 ), pary ); + parz = _mm_madd_ps( vx, _mm_splat_ps( mvp2, 0 ), parz ); + parw = _mm_madd_ps( vx, _mm_splat_ps( mvp3, 0 ), parw ); + + parx = _mm_madd_ps( vy, _mm_splat_ps( mvp0, 1 ), parx ); + pary = _mm_madd_ps( vy, _mm_splat_ps( mvp1, 1 ), pary ); + parz = _mm_madd_ps( vy, _mm_splat_ps( mvp2, 1 ), parz ); + parw = _mm_madd_ps( vy, _mm_splat_ps( mvp3, 1 ), parw ); + + __m128 mvp0Z = _mm_splat_ps( mvp0, 2 ); + __m128 mvp1Z = _mm_splat_ps( mvp1, 2 ); + __m128 mvp2Z = _mm_splat_ps( mvp2, 2 ); + __m128 mvp3Z = _mm_splat_ps( mvp3, 2 ); + + __m128 x0 = _mm_madd_ps( vz0, mvp0Z, parx ); + __m128 y0 = _mm_madd_ps( vz0, mvp1Z, pary ); + __m128 z0 = _mm_madd_ps( vz0, mvp2Z, parz ); + __m128 w0 = _mm_madd_ps( vz0, mvp3Z, parw ); + + __m128 x1 = _mm_madd_ps( vz1, mvp0Z, parx ); + __m128 y1 = _mm_madd_ps( vz1, mvp1Z, pary ); + __m128 z1 = _mm_madd_ps( vz1, mvp2Z, parz ); + __m128 w1 = _mm_madd_ps( vz1, mvp3Z, parw ); + + __m128 s0 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w0 ); + __m128 s1 = _mm_cmpgt_ps( vector_float_smallest_non_denorm, w1 ); + + w0 = _mm_sel_ps( w0, vector_float_one, s0 ); + w1 = _mm_sel_ps( w1, vector_float_one, s1 ); + + __m128 rw0 = _mm_rcp32_ps( w0 ); + __m128 rw1 = _mm_rcp32_ps( w1 ); + + x0 = _mm_mul_ps( x0, rw0 ); + y0 = _mm_mul_ps( y0, rw0 ); + z0 = _mm_mul_ps( z0, rw0 ); + + x1 = _mm_mul_ps( x1, rw1 ); + y1 = _mm_mul_ps( y1, rw1 ); + z1 = _mm_mul_ps( z1, rw1 ); + + _mm_store_ps( corners.x + 0, x0 ); + _mm_store_ps( corners.x + 4, x1 ); + _mm_store_ps( corners.y + 0, y0 ); + _mm_store_ps( corners.y + 4, y1 ); + _mm_store_ps( corners.z + 0, z0 ); + _mm_store_ps( corners.z + 4, z1 ); + +} + +/* +======================== +idRenderMatrix::CullFrustumCornersToPlane +======================== +*/ +frustumCull_t idRenderMatrix::CullFrustumCornersToPlane( const frustumCorners_t & corners, const idPlane & plane ) { + assert_16_byte_aligned( &corners ); + + + __m128 vp = _mm_loadu_ps( plane.ToFloatPtr() ); + + __m128 x0 = _mm_load_ps( corners.x + 0 ); + __m128 y0 = _mm_load_ps( corners.y + 0 ); + __m128 z0 = _mm_load_ps( corners.z + 0 ); + + __m128 x1 = _mm_load_ps( corners.x + 4 ); + __m128 y1 = _mm_load_ps( corners.y + 4 ); + __m128 z1 = _mm_load_ps( corners.z + 4 ); + + __m128 p0 = _mm_splat_ps( vp, 0 ); + __m128 p1 = _mm_splat_ps( vp, 1 ); + __m128 p2 = _mm_splat_ps( vp, 2 ); + __m128 p3 = _mm_splat_ps( vp, 3 ); + + __m128 d0 = _mm_madd_ps( x0, p0, _mm_madd_ps( y0, p1, _mm_madd_ps( z0, p2, p3 ) ) ); + __m128 d1 = _mm_madd_ps( x1, p0, _mm_madd_ps( y1, p1, _mm_madd_ps( z1, p2, p3 ) ) ); + + int b0 = _mm_movemask_ps( d0 ); + int b1 = _mm_movemask_ps( d1 ); + + unsigned int front = ( (unsigned int) -( ( b0 & b1 ) ^ 15 ) ) >> 31; + unsigned int back = ( (unsigned int) -( b0 | b1 ) ) >> 31; + + compile_time_assert( FRUSTUM_CULL_FRONT == 1 ); + compile_time_assert( FRUSTUM_CULL_BACK == 2 ); + compile_time_assert( FRUSTUM_CULL_CROSS == 3 ); + + return (frustumCull_t) ( front | ( back << 1 ) ); + +} diff --git a/neo/idlib/geometry/RenderMatrix.h b/neo/idlib/geometry/RenderMatrix.h new file mode 100644 index 00000000..9826e1d7 --- /dev/null +++ b/neo/idlib/geometry/RenderMatrix.h @@ -0,0 +1,489 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __RENDERMATRIX_H__ +#define __RENDERMATRIX_H__ + +static const int NUM_FRUSTUM_CORNERS = 8; + +struct frustumCorners_t { + float x[NUM_FRUSTUM_CORNERS]; + float y[NUM_FRUSTUM_CORNERS]; + float z[NUM_FRUSTUM_CORNERS]; +}; + +enum frustumCull_t { + FRUSTUM_CULL_FRONT = 1, + FRUSTUM_CULL_BACK = 2, + FRUSTUM_CULL_CROSS = 3 +}; + +/* +================================================================================================ + +idRenderMatrix + +This is a row-major matrix and transforms are applied with left-multiplication. + +================================================================================================ +*/ +class idRenderMatrix { +public: + idRenderMatrix() {} + ID_INLINE idRenderMatrix( float a0, float a1, float a2, float a3, + float b0, float b1, float b2, float b3, + float c0, float c1, float c2, float c3, + float d0, float d1, float d2, float d3 ); + + const float * operator[]( int index ) const { assert( index >= 0 && index < 4 ); return &m[index*4]; } + float * operator[]( int index ) { assert( index >= 0 && index < 4 ); return &m[index*4]; } + + void Zero() { memset( m, 0, sizeof( m ) ); } + ID_INLINE void Identity(); + + // Matrix classification (only meant to be used for asserts). + ID_INLINE bool IsZero( float epsilon ) const; + ID_INLINE bool IsIdentity( float epsilon ) const; + ID_INLINE bool IsAffineTransform( float epsilon ) const; + ID_INLINE bool IsUniformScale( float epsilon ) const; + + // Transform a point. + // NOTE: the idVec3 out variant does not divide by W. + ID_INLINE void TransformPoint( const idVec3 & in, idVec3 & out ) const; + ID_INLINE void TransformPoint( const idVec3 & in, idVec4 & out ) const; + ID_INLINE void TransformPoint( const idVec4 & in, idVec4 & out ) const; + + // These assume the matrix has no non-uniform scaling or shearing. + // NOTE: a direction will only stay normalized if the matrix has no skewing or scaling. + ID_INLINE void TransformDir( const idVec3 & in, idVec3 & out, bool normalize ) const; + ID_INLINE void TransformPlane( const idPlane & in, idPlane & out, bool normalize ) const; + + // These transforms work with non-uniform scaling and shearing by multiplying + // with 'transpose(inverse(M))' where this matrix is assumed to be 'inverse(M)'. + ID_INLINE void InverseTransformDir( const idVec3 & in, idVec3 & out, bool normalize ) const; + ID_INLINE void InverseTransformPlane( const idPlane & in, idPlane & out, bool normalize ) const; + + // Project a point. + static ID_INLINE void TransformModelToClip( const idVec3 & src, const idRenderMatrix & modelMatrix, const idRenderMatrix & projectionMatrix, idVec4 & eye, idVec4 & clip ); + static ID_INLINE void TransformClipToDevice( const idVec4 & clip, idVec3 & ndc ); + + // Create a matrix that goes from local space to the space defined by the 'origin' and 'axis'. + static void CreateFromOriginAxis( const idVec3 & origin, const idMat3 & axis, idRenderMatrix & out ); + static void CreateFromOriginAxisScale( const idVec3 & origin, const idMat3 & axis, const idVec3 & scale, idRenderMatrix & out ); + + // Create a matrix that goes from a global coordinate to a view coordinate (OpenGL looking down -Z) based on the given view origin/axis. + static void CreateViewMatrix( const idVec3 & origin, const idMat3 & axis, idRenderMatrix & out ); + + // Create a projection matrix. + static void CreateProjectionMatrix( float xMin, float xMax, float yMin, float yMax, float zNear, float zFar, idRenderMatrix & out ); + static void CreateProjectionMatrixFov( float xFovDegrees, float yFovDegrees, float zNear, float zFar, float xOffset, float yOffset, idRenderMatrix & out ); + + // Apply depth hacks to a projection matrix. + static ID_INLINE void ApplyDepthHack( idRenderMatrix & src ); + static ID_INLINE void ApplyModelDepthHack( idRenderMatrix & src, float value ); + + // Offset and scale the given matrix such that the result matrix transforms the unit-cube to exactly cover the given bounds (and the inverse). + static void OffsetScaleForBounds( const idRenderMatrix & src, const idBounds & bounds, idRenderMatrix & out ); + static void InverseOffsetScaleForBounds( const idRenderMatrix & src, const idBounds & bounds, idRenderMatrix & out ); + + // Basic matrix operations. + static void Transpose( const idRenderMatrix & src, idRenderMatrix & out ); + static void Multiply( const idRenderMatrix & a, const idRenderMatrix & b, idRenderMatrix & out ); + static bool Inverse( const idRenderMatrix & src, idRenderMatrix & out ); + static void InverseByTranspose( const idRenderMatrix & src, idRenderMatrix & out ); + static bool InverseByDoubles( const idRenderMatrix & src, idRenderMatrix & out ); + + // Copy or create a matrix that is stored directly into four float4 vectors which is useful for directly setting vertex program uniforms. + static void CopyMatrix( const idRenderMatrix & matrix, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3 ); + static void SetMVP( const idRenderMatrix & mvp, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3, bool & negativeDeterminant ); + static void SetMVPForBounds( const idRenderMatrix & mvp, const idBounds & bounds, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3, bool & negativeDeterminant ); + static void SetMVPForInverseProject( const idRenderMatrix & mvp, const idRenderMatrix & inverseProject, idVec4 & row0, idVec4 & row1, idVec4 & row2, idVec4 & row3, bool & negativeDeterminant ); + + // Cull to a Model-View-Projection (MVP) matrix. + static bool CullPointToMVP( const idRenderMatrix & mvp, const idVec3 & point, bool zeroToOne = false ); + static bool CullPointToMVPbits( const idRenderMatrix & mvp, const idVec3 & point, byte * outBits, bool zeroToOne = false ); + static bool CullBoundsToMVP( const idRenderMatrix & mvp, const idBounds & bounds, bool zeroToOne = false ); + static bool CullBoundsToMVPbits( const idRenderMatrix & mvp, const idBounds & bounds, byte * outBits, bool zeroToOne = false ); + static bool CullExtrudedBoundsToMVP( const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & extrudeDirection, const idPlane & clipPlane, bool zeroToOne = false ); + static bool CullExtrudedBoundsToMVPbits( const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & extrudeDirection, const idPlane & clipPlane, byte * outBits, bool zeroToOne = false ); + + // Calculate the projected bounds. + static void ProjectedBounds( idBounds & projected, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace = true ); + static void ProjectedNearClippedBounds( idBounds & projected, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace = true ); + static void ProjectedFullyClippedBounds( idBounds & projected, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace = true ); + + // Calculate the projected depth bounds. + static void DepthBoundsForBounds( float & min, float & max, const idRenderMatrix & mvp, const idBounds & bounds, bool windowSpace = true ); + static void DepthBoundsForExtrudedBounds( float & min, float & max, const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & extrudeDirection, const idPlane & clipPlane, bool windowSpace = true ); + static void DepthBoundsForShadowBounds( float & min, float & max, const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & localLightOrigin, bool windowSpace = true ); + + // Create frustum planes and corners from a matrix. + static void GetFrustumPlanes( idPlane planes[6], const idRenderMatrix & frustum, bool zeroToOne, bool normalize ); + static void GetFrustumCorners( frustumCorners_t & corners, const idRenderMatrix & frustumTransform, const idBounds & frustumBounds ); + static frustumCull_t CullFrustumCornersToPlane( const frustumCorners_t & corners, const idPlane & plane ); + +private: + float m[16]; +}; + +extern const idRenderMatrix renderMatrix_identity; +extern const idRenderMatrix renderMatrix_flipToOpenGL; +extern const idRenderMatrix renderMatrix_windowSpaceToClipSpace; + +/* +======================== +idRenderMatrix::idRenderMatrix +======================== +*/ +ID_INLINE idRenderMatrix::idRenderMatrix( float a0, float a1, float a2, float a3, + float b0, float b1, float b2, float b3, + float c0, float c1, float c2, float c3, + float d0, float d1, float d2, float d3 ) { + m[0*4+0] = a0; m[0*4+1] = a1; m[0*4+2] = a2; m[0*4+3] = a3; + m[1*4+0] = b0; m[1*4+1] = b1; m[1*4+2] = b2; m[1*4+3] = b3; + m[2*4+0] = c0; m[2*4+1] = c1; m[2*4+2] = c2; m[2*4+3] = c3; + m[3*4+0] = d0; m[3*4+1] = d1; m[3*4+2] = d2; m[3*4+3] = d3; +} + +/* +======================== +idRenderMatrix::Identity +======================== +*/ +ID_INLINE void idRenderMatrix::Identity() { + m[0*4+0] = 1.0f; + m[0*4+1] = 0.0f; + m[0*4+2] = 0.0f; + m[0*4+3] = 0.0f; + + m[1*4+0] = 0.0f; + m[1*4+1] = 1.0f; + m[1*4+2] = 0.0f; + m[1*4+3] = 0.0f; + + m[2*4+0] = 0.0f; + m[2*4+1] = 0.0f; + m[2*4+2] = 1.0f; + m[2*4+3] = 0.0f; + + m[3*4+0] = 0.0f; + m[3*4+1] = 0.0f; + m[3*4+2] = 0.0f; + m[3*4+3] = 1.0f; +} + +/* +======================== +idRenderMatrix::IsZero +======================== +*/ +ID_INLINE bool idRenderMatrix::IsZero( float epsilon ) const { + for ( int i = 0; i < 16; i++ ) { + if ( idMath::Fabs( m[i] ) > epsilon ) { + return false; + } + } + return true; +} + +/* +======================== +idRenderMatrix::IsIdentity +======================== +*/ +ID_INLINE bool idRenderMatrix::IsIdentity( float epsilon ) const { + for ( int i = 0; i < 4; i++ ) { + for ( int j = 0; j < 4; j++ ) { + if ( i == j ) { + if ( idMath::Fabs( m[i * 4 + j] - 1.0f ) > epsilon ) { + return false; + } + } else { + if ( idMath::Fabs( m[i * 4 + j] ) > epsilon ) { + return false; + } + } + } + } + return true; +} + +/* +======================== +idRenderMatrix::IsAffineTransform +======================== +*/ +ID_INLINE bool idRenderMatrix::IsAffineTransform( float epsilon ) const { + if ( idMath::Fabs( m[3 * 4 + 0] ) > epsilon || + idMath::Fabs( m[3 * 4 + 1] ) > epsilon || + idMath::Fabs( m[3 * 4 + 2] ) > epsilon || + idMath::Fabs( m[3 * 4 + 3] - 1.0f ) > epsilon ) { + return false; + } + return true; +} + +/* +======================== +idRenderMatrix::IsUniformScale +======================== +*/ +ID_INLINE bool idRenderMatrix::IsUniformScale( float epsilon ) const { + float d0 = idMath::InvSqrt( m[0*4+0] * m[0*4+0] + m[1*4+0] * m[1*4+0] + m[2*4+0] * m[2*4+0] ); + float d1 = idMath::InvSqrt( m[0*4+1] * m[0*4+1] + m[1*4+1] * m[1*4+1] + m[2*4+1] * m[2*4+1] ); + float d2 = idMath::InvSqrt( m[0*4+2] * m[0*4+2] + m[1*4+2] * m[1*4+2] + m[2*4+2] * m[2*4+2] ); + if ( idMath::Fabs( d0 - d1 ) > epsilon ) { return false; } + if ( idMath::Fabs( d1 - d2 ) > epsilon ) { return false; } + if ( idMath::Fabs( d0 - d2 ) > epsilon ) { return false; } + return true; +} + +/* +======================== +idRenderMatrix::TransformPoint +======================== +*/ +ID_INLINE void idRenderMatrix::TransformPoint( const idVec3 & in, idVec3 & out ) const { + assert( in.ToFloatPtr() != out.ToFloatPtr() ); + const idRenderMatrix & matrix = *this; + out[0] = in[0] * matrix[0][0] + in[1] * matrix[0][1] + in[2] * matrix[0][2] + matrix[0][3]; + out[1] = in[0] * matrix[1][0] + in[1] * matrix[1][1] + in[2] * matrix[1][2] + matrix[1][3]; + out[2] = in[0] * matrix[2][0] + in[1] * matrix[2][1] + in[2] * matrix[2][2] + matrix[2][3]; + assert( idMath::Fabs( in[0] * matrix[3][0] + in[1] * matrix[3][1] + in[2] * matrix[3][2] + matrix[3][3] - 1.0f ) < 0.01f ); +} + +/* +======================== +idRenderMatrix::TransformPoint +======================== +*/ +ID_INLINE void idRenderMatrix::TransformPoint( const idVec3 & in, idVec4 & out ) const { + assert( in.ToFloatPtr() != out.ToFloatPtr() ); + const idRenderMatrix & matrix = *this; + out[0] = in[0] * matrix[0][0] + in[1] * matrix[0][1] + in[2] * matrix[0][2] + matrix[0][3]; + out[1] = in[0] * matrix[1][0] + in[1] * matrix[1][1] + in[2] * matrix[1][2] + matrix[1][3]; + out[2] = in[0] * matrix[2][0] + in[1] * matrix[2][1] + in[2] * matrix[2][2] + matrix[2][3]; + out[3] = in[0] * matrix[3][0] + in[1] * matrix[3][1] + in[2] * matrix[3][2] + matrix[3][3]; +} + +/* +======================== +idRenderMatrix::TransformPoint +======================== +*/ +ID_INLINE void idRenderMatrix::TransformPoint( const idVec4 & in, idVec4 & out ) const { + assert( in.ToFloatPtr() != out.ToFloatPtr() ); + const idRenderMatrix & matrix = *this; + out[0] = in[0] * matrix[0][0] + in[1] * matrix[0][1] + in[2] * matrix[0][2] + in[3] * matrix[0][3]; + out[1] = in[0] * matrix[1][0] + in[1] * matrix[1][1] + in[2] * matrix[1][2] + in[3] * matrix[1][3]; + out[2] = in[0] * matrix[2][0] + in[1] * matrix[2][1] + in[2] * matrix[2][2] + in[3] * matrix[2][3]; + out[3] = in[0] * matrix[3][0] + in[1] * matrix[3][1] + in[2] * matrix[3][2] + in[3] * matrix[3][3]; +} + +/* +======================== +idRenderMatrix::TransformDir +======================== +*/ +ID_INLINE void idRenderMatrix::TransformDir( const idVec3 & in, idVec3 & out, bool normalize ) const { + const idRenderMatrix & matrix = *this; + float p0 = in[0] * matrix[0][0] + in[1] * matrix[0][1] + in[2] * matrix[0][2]; + float p1 = in[0] * matrix[1][0] + in[1] * matrix[1][1] + in[2] * matrix[1][2]; + float p2 = in[0] * matrix[2][0] + in[1] * matrix[2][1] + in[2] * matrix[2][2]; + if ( normalize ) { + float r = idMath::InvSqrt( p0 * p0 + p1 * p1 + p2 * p2 ); + p0 *= r; + p1 *= r; + p2 *= r; + } + out[0] = p0; + out[1] = p1; + out[2] = p2; +} + +/* +======================== +idRenderMatrix::TransformPlane +======================== +*/ +ID_INLINE void idRenderMatrix::TransformPlane( const idPlane & in, idPlane & out, bool normalize ) const { + assert( IsUniformScale( 0.01f ) ); + const idRenderMatrix & matrix = *this; + float p0 = in[0] * matrix[0][0] + in[1] * matrix[0][1] + in[2] * matrix[0][2]; + float p1 = in[0] * matrix[1][0] + in[1] * matrix[1][1] + in[2] * matrix[1][2]; + float p2 = in[0] * matrix[2][0] + in[1] * matrix[2][1] + in[2] * matrix[2][2]; + float d0 = matrix[0][3] - p0 * in[3]; + float d1 = matrix[1][3] - p1 * in[3]; + float d2 = matrix[2][3] - p2 * in[3]; + if ( normalize ) { + float r = idMath::InvSqrt( p0 * p0 + p1 * p1 + p2 * p2 ); + p0 *= r; + p1 *= r; + p2 *= r; + } + out[0] = p0; + out[1] = p1; + out[2] = p2; + out[3] = - p0 * d0 - p1 * d1 - p2 * d2; +} + +/* +======================== +idRenderMatrix::InverseTransformDir +======================== +*/ +ID_INLINE void idRenderMatrix::InverseTransformDir( const idVec3 & in, idVec3 & out, bool normalize ) const { + assert( in.ToFloatPtr() != out.ToFloatPtr() ); + const idRenderMatrix & matrix = *this; + float p0 = in[0] * matrix[0][0] + in[1] * matrix[1][0] + in[2] * matrix[2][0]; + float p1 = in[0] * matrix[0][1] + in[1] * matrix[1][1] + in[2] * matrix[2][1]; + float p2 = in[0] * matrix[0][2] + in[1] * matrix[1][2] + in[2] * matrix[2][2]; + if ( normalize ) { + float r = idMath::InvSqrt( p0 * p0 + p1 * p1 + p2 * p2 ); + p0 *= r; + p1 *= r; + p2 *= r; + } + out[0] = p0; + out[1] = p1; + out[2] = p2; +} + +/* +======================== +idRenderMatrix::InverseTransformPlane +======================== +*/ +ID_INLINE void idRenderMatrix::InverseTransformPlane( const idPlane & in, idPlane & out, bool normalize ) const { + assert( in.ToFloatPtr() != out.ToFloatPtr() ); + const idRenderMatrix & matrix = *this; + float p0 = in[0] * matrix[0][0] + in[1] * matrix[1][0] + in[2] * matrix[2][0] + in[3] * matrix[3][0]; + float p1 = in[0] * matrix[0][1] + in[1] * matrix[1][1] + in[2] * matrix[2][1] + in[3] * matrix[3][1]; + float p2 = in[0] * matrix[0][2] + in[1] * matrix[1][2] + in[2] * matrix[2][2] + in[3] * matrix[3][2]; + float p3 = in[0] * matrix[0][3] + in[1] * matrix[1][3] + in[2] * matrix[2][3] + in[3] * matrix[3][3]; + if ( normalize ) { + float r = idMath::InvSqrt( p0 * p0 + p1 * p1 + p2 * p2 ); + p0 *= r; + p1 *= r; + p2 *= r; + p3 *= r; + } + out[0] = p0; + out[1] = p1; + out[2] = p2; + out[3] = p3; +} + +/* +======================== +idRenderMatrix::TransformModelToClip +======================== +*/ +ID_INLINE void idRenderMatrix::TransformModelToClip( const idVec3 & src, const idRenderMatrix & modelMatrix, const idRenderMatrix & projectionMatrix, idVec4 & eye, idVec4 & clip ) { + for ( int i = 0; i < 4; i++ ) { + eye[i] = modelMatrix[i][0] * src[0] + + modelMatrix[i][1] * src[1] + + modelMatrix[i][2] * src[2] + + modelMatrix[i][3]; + } + for ( int i = 0; i < 4; i++ ) { + clip[i] = projectionMatrix[i][0] * eye[0] + + projectionMatrix[i][1] * eye[1] + + projectionMatrix[i][2] * eye[2] + + projectionMatrix[i][3] * eye[3]; + } +} + +/* +======================== +idRenderMatrix::TransformClipToDevice + +Clip to normalized device coordinates. +======================== +*/ +ID_INLINE void idRenderMatrix::TransformClipToDevice( const idVec4 & clip, idVec3 & ndc ) { + assert( idMath::Fabs( clip[3] ) > idMath::FLT_SMALLEST_NON_DENORMAL ); + float r = 1.0f / clip[3]; + ndc[0] = clip[0] * r; + ndc[1] = clip[1] * r; + ndc[2] = clip[2] * r; +} + +/* +======================== +idRenderMatrix::ApplyDepthHack +======================== +*/ +ID_INLINE void idRenderMatrix::ApplyDepthHack( idRenderMatrix & src ) { + // scale projected z by 25% + src.m[2*4+0] *= 0.25f; + src.m[2*4+1] *= 0.25f; + src.m[2*4+2] *= 0.25f; + src.m[2*4+3] *= 0.25f; +} + +/* +======================== +idRenderMatrix::ApplyModelDepthHack +======================== +*/ +ID_INLINE void idRenderMatrix::ApplyModelDepthHack( idRenderMatrix & src, float value ) { + // offset projected z + src.m[2*4+3] -= value; +} + +/* +======================== +idRenderMatrix::CullPointToMVP +======================== +*/ +ID_INLINE bool idRenderMatrix::CullPointToMVP( const idRenderMatrix & mvp, const idVec3 & point, bool zeroToOne ) { + byte bits; + return CullPointToMVPbits( mvp, point, &bits, zeroToOne ); +} + +/* +======================== +idRenderMatrix::CullBoundsToMVP +======================== +*/ +ID_INLINE bool idRenderMatrix::CullBoundsToMVP( const idRenderMatrix & mvp, const idBounds & bounds, bool zeroToOne ) { + byte bits; + return CullBoundsToMVPbits( mvp, bounds, &bits, zeroToOne ); +} + +/* +======================== +idRenderMatrix::CullExtrudedBoundsToMVP +======================== +*/ +ID_INLINE bool idRenderMatrix::CullExtrudedBoundsToMVP( const idRenderMatrix & mvp, const idBounds & bounds, const idVec3 & extrudeDirection, const idPlane & clipPlane, bool zeroToOne ) { + byte bits; + return CullExtrudedBoundsToMVPbits( mvp, bounds, extrudeDirection, clipPlane, &bits, zeroToOne ); +} + +#endif // !__RENDERMATRIX_H__ diff --git a/neo/idlib/geometry/Surface.cpp b/neo/idlib/geometry/Surface.cpp new file mode 100644 index 00000000..d6650def --- /dev/null +++ b/neo/idlib/geometry/Surface.cpp @@ -0,0 +1,929 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +================= +UpdateVertexIndex +================= +*/ +ID_INLINE int UpdateVertexIndex( int vertexIndexNum[2], int *vertexRemap, int *vertexCopyIndex, int vertNum ) { + int s = INT32_SIGNBITSET( vertexRemap[vertNum] ); + vertexIndexNum[0] = vertexRemap[vertNum]; + vertexRemap[vertNum] = vertexIndexNum[s]; + vertexIndexNum[1] += s; + vertexCopyIndex[vertexRemap[vertNum]] = vertNum; + return vertexRemap[vertNum]; +} + +/* +================= +idSurface::Split +================= +*/ +int idSurface::Split( const idPlane &plane, const float epsilon, idSurface **front, idSurface **back, int *frontOnPlaneEdges, int *backOnPlaneEdges ) const { + float * dists; + float f; + byte * sides; + int counts[3]; + int * edgeSplitVertex; + int numEdgeSplitVertexes; + int * vertexRemap[2]; + int vertexIndexNum[2][2]; + int * vertexCopyIndex[2]; + int * indexPtr[2]; + int indexNum[2]; + int * index; + int * onPlaneEdges[2]; + int numOnPlaneEdges[2]; + int maxOnPlaneEdges; + int i; + idSurface * surface[2]; + idDrawVert v; + + dists = (float *) _alloca( verts.Num() * sizeof( float ) ); + sides = (byte *) _alloca( verts.Num() * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine side for each vertex + for ( i = 0; i < verts.Num(); i++ ) { + dists[i] = f = plane.Distance( verts[i].xyz ); + if ( f > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( f < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + *front = *back = NULL; + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + + f = ( verts[indexes[1]].xyz - verts[indexes[0]].xyz ).Cross( verts[indexes[0]].xyz - verts[indexes[2]].xyz ) * plane.Normal(); + if ( IEEE_FLT_SIGNBITSET( f ) ) { + *back = new (TAG_IDLIB_SURFACE) idSurface( *this ); + return SIDE_BACK; + } else { + *front = new (TAG_IDLIB_SURFACE) idSurface( *this ); + return SIDE_FRONT; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = new (TAG_IDLIB_SURFACE) idSurface( *this ); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = new (TAG_IDLIB_SURFACE) idSurface( *this ); + return SIDE_FRONT; + } + + // allocate front and back surface + *front = surface[0] = new (TAG_IDLIB_SURFACE) idSurface(); + *back = surface[1] = new (TAG_IDLIB_SURFACE) idSurface(); + + edgeSplitVertex = (int *) _alloca( edges.Num() * sizeof( int ) ); + numEdgeSplitVertexes = 0; + + maxOnPlaneEdges = 4 * counts[SIDE_ON]; + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // split edges + for ( i = 0; i < edges.Num(); i++ ) { + int v0 = edges[i].verts[0]; + int v1 = edges[i].verts[1]; + int sidesOr = ( sides[v0] | sides[v1] ); + + // if both vertexes are on the same side or one is on the clipping plane + if ( !( sides[v0] ^ sides[v1] ) || ( sidesOr & SIDE_ON ) ) { + edgeSplitVertex[i] = -1; + counts[sidesOr & SIDE_BACK]++; + counts[SIDE_ON] += ( sidesOr & SIDE_ON ) >> 1; + } else { + f = dists[v0] / ( dists[v0] - dists[v1] ); + v.LerpAll( verts[v0], verts[v1], f ); + edgeSplitVertex[i] = numEdgeSplitVertexes++; + surface[0]->verts.Append( v ); + surface[1]->verts.Append( v ); + } + } + + // each edge is shared by at most two triangles, as such there can never be more indexes than twice the number of edges + surface[0]->indexes.Resize( ( ( counts[SIDE_FRONT] + counts[SIDE_ON] ) * 2 ) + ( numEdgeSplitVertexes * 4 ) ); + surface[1]->indexes.Resize( ( ( counts[SIDE_BACK] + counts[SIDE_ON] ) * 2 ) + ( numEdgeSplitVertexes * 4 ) ); + + // allocate indexes to construct the triangle indexes for the front and back surface + vertexRemap[0] = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap[0], -1, verts.Num() * sizeof( int ) ); + vertexRemap[1] = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap[1], -1, verts.Num() * sizeof( int ) ); + + vertexCopyIndex[0] = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + vertexCopyIndex[1] = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + + vertexIndexNum[0][0] = vertexIndexNum[1][0] = 0; + vertexIndexNum[0][1] = vertexIndexNum[1][1] = numEdgeSplitVertexes; + + indexPtr[0] = surface[0]->indexes.Ptr(); + indexPtr[1] = surface[1]->indexes.Ptr(); + indexNum[0] = surface[0]->indexes.Num(); + indexNum[1] = surface[1]->indexes.Num(); + + maxOnPlaneEdges += 4 * numEdgeSplitVertexes; + // allocate one more in case no triangles are actually split which may happen for a disconnected surface + onPlaneEdges[0] = (int *) _alloca( ( maxOnPlaneEdges + 1 ) * sizeof( int ) ); + onPlaneEdges[1] = (int *) _alloca( ( maxOnPlaneEdges + 1 ) * sizeof( int ) ); + numOnPlaneEdges[0] = numOnPlaneEdges[1] = 0; + + // split surface triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + int e0, e1, e2, v0, v1, v2, s, n; + + e0 = abs( edgeIndexes[i+0] ); + e1 = abs( edgeIndexes[i+1] ); + e2 = abs( edgeIndexes[i+2] ); + + v0 = indexes[i+0]; + v1 = indexes[i+1]; + v2 = indexes[i+2]; + + switch( ( INT32_SIGNBITSET( edgeSplitVertex[e0] ) | ( INT32_SIGNBITSET( edgeSplitVertex[e1] ) << 1 ) | ( INT32_SIGNBITSET( edgeSplitVertex[e2] ) << 2 ) ) ^ 7 ) { + case 0: { // no edges split + if ( ( sides[v0] & sides[v1] & sides[v2] ) & SIDE_ON ) { + // coplanar + f = ( verts[v1].xyz - verts[v0].xyz ).Cross( verts[v0].xyz - verts[v2].xyz ) * plane.Normal(); + s = IEEE_FLT_SIGNBITSET( f ); + } else { + s = ( sides[v0] | sides[v1] | sides[v2] ) & SIDE_BACK; + } + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]] = n; + numOnPlaneEdges[s] += ( sides[v0] & sides[v1] ) >> 1; + onPlaneEdges[s][numOnPlaneEdges[s]] = n+1; + numOnPlaneEdges[s] += ( sides[v1] & sides[v2] ) >> 1; + onPlaneEdges[s][numOnPlaneEdges[s]] = n+2; + numOnPlaneEdges[s] += ( sides[v2] & sides[v0] ) >> 1; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + break; + } + case 1: { // first edge split + s = sides[v0] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + break; + } + case 2: { // second edge split + s = sides[v1] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + break; + } + case 3: { // first and second edge split + s = sides[v1] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + break; + } + case 4: { // third edge split + s = sides[v2] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + break; + } + case 5: { // first and third edge split + s = sides[v0] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = edgeSplitVertex[e2]; + indexNum[s] = n; + break; + } + case 6: { // second and third edge split + s = sides[v2] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = edgeSplitVertex[e2]; + indexNum[s] = n; + break; + } + } + } + + surface[0]->indexes.SetNum( indexNum[0] ); + surface[1]->indexes.SetNum( indexNum[1] ); + + // copy vertexes + surface[0]->verts.SetNum( vertexIndexNum[0][1] ); + index = vertexCopyIndex[0]; + for ( i = numEdgeSplitVertexes; i < surface[0]->verts.Num(); i++ ) { + surface[0]->verts[i] = verts[index[i]]; + } + surface[1]->verts.SetNum( vertexIndexNum[1][1] ); + index = vertexCopyIndex[1]; + for ( i = numEdgeSplitVertexes; i < surface[1]->verts.Num(); i++ ) { + surface[1]->verts[i] = verts[index[i]]; + } + + // generate edge indexes + surface[0]->GenerateEdgeIndexes(); + surface[1]->GenerateEdgeIndexes(); + + if ( frontOnPlaneEdges ) { + memcpy( frontOnPlaneEdges, onPlaneEdges[0], numOnPlaneEdges[0] * sizeof( int ) ); + frontOnPlaneEdges[numOnPlaneEdges[0]] = -1; + } + + if ( backOnPlaneEdges ) { + memcpy( backOnPlaneEdges, onPlaneEdges[1], numOnPlaneEdges[1] * sizeof( int ) ); + backOnPlaneEdges[numOnPlaneEdges[1]] = -1; + } + + return SIDE_CROSS; +} + +/* +================= +idSurface::ClipInPlace +================= +*/ +bool idSurface::ClipInPlace( const idPlane &plane, const float epsilon, const bool keepOn ) { + float * dists; + float f; + byte * sides; + int counts[3]; + int i; + int * edgeSplitVertex; + int * vertexRemap; + int vertexIndexNum[2]; + int * vertexCopyIndex; + int * indexPtr; + int indexNum; + int numEdgeSplitVertexes; + idDrawVert v; + idList newVerts; + idList newIndexes; + + dists = (float *) _alloca( verts.Num() * sizeof( float ) ); + sides = (byte *) _alloca( verts.Num() * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine side for each vertex + for ( i = 0; i < verts.Num(); i++ ) { + dists[i] = f = plane.Distance( verts[i].xyz ); + if ( f > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( f < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + + f = ( verts[indexes[1]].xyz - verts[indexes[0]].xyz ).Cross( verts[indexes[0]].xyz - verts[indexes[2]].xyz ) * plane.Normal(); + if ( IEEE_FLT_SIGNBITSET( f ) ) { + Clear(); + return false; + } else { + return true; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + Clear(); + return false; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return true; + } + + edgeSplitVertex = (int *) _alloca( edges.Num() * sizeof( int ) ); + numEdgeSplitVertexes = 0; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = 0; + + // split edges + for ( i = 0; i < edges.Num(); i++ ) { + int v0 = edges[i].verts[0]; + int v1 = edges[i].verts[1]; + + // if both vertexes are on the same side or one is on the clipping plane + if ( !( sides[v0] ^ sides[v1] ) || ( ( sides[v0] | sides[v1] ) & SIDE_ON ) ) { + edgeSplitVertex[i] = -1; + counts[(sides[v0]|sides[v1]) & SIDE_BACK]++; + } else { + f = dists[v0] / ( dists[v0] - dists[v1] ); + v.LerpAll( verts[v0], verts[v1], f ); + edgeSplitVertex[i] = numEdgeSplitVertexes++; + newVerts.Append( v ); + } + } + + // each edge is shared by at most two triangles, as such there can never be + // more indexes than twice the number of edges + newIndexes.Resize( ( counts[SIDE_FRONT] << 1 ) + ( numEdgeSplitVertexes << 2 ) ); + + // allocate indexes to construct the triangle indexes for the front and back surface + vertexRemap = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap, -1, verts.Num() * sizeof( int ) ); + + vertexCopyIndex = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + + vertexIndexNum[0] = 0; + vertexIndexNum[1] = numEdgeSplitVertexes; + + indexPtr = newIndexes.Ptr(); + indexNum = newIndexes.Num(); + + // split surface triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + int e0, e1, e2, v0, v1, v2; + + e0 = abs( edgeIndexes[i+0] ); + e1 = abs( edgeIndexes[i+1] ); + e2 = abs( edgeIndexes[i+2] ); + + v0 = indexes[i+0]; + v1 = indexes[i+1]; + v2 = indexes[i+2]; + + switch( ( INT32_SIGNBITSET( edgeSplitVertex[e0] ) | ( INT32_SIGNBITSET( edgeSplitVertex[e1] ) << 1 ) | ( INT32_SIGNBITSET( edgeSplitVertex[e2] ) << 2 ) ) ^ 7 ) { + case 0: { // no edges split + if ( ( sides[v0] | sides[v1] | sides[v2] ) & SIDE_BACK ) { + break; + } + if ( ( sides[v0] & sides[v1] & sides[v2] ) & SIDE_ON ) { + // coplanar + if ( !keepOn ) { + break; + } + f = ( verts[v1].xyz - verts[v0].xyz ).Cross( verts[v0].xyz - verts[v2].xyz ) * plane.Normal(); + if ( IEEE_FLT_SIGNBITSET( f ) ) { + break; + } + } + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + break; + } + case 1: { // first edge split + if ( !( sides[v0] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + } + break; + } + case 2: { // second edge split + if ( !( sides[v1] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } + break; + } + case 3: { // first and second edge split + if ( !( sides[v1] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = edgeSplitVertex[e0]; + } else { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } + break; + } + case 4: { // third edge split + if ( !( sides[v2] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + } + break; + } + case 5: { // first and third edge split + if ( !( sides[v0] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } else { + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } + break; + } + case 6: { // second and third edge split + if ( !( sides[v2] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + } else { + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } + break; + } + } + } + + newIndexes.SetNum( indexNum ); + + // copy vertexes + newVerts.SetNum( vertexIndexNum[1] ); + for ( i = numEdgeSplitVertexes; i < newVerts.Num(); i++ ) { + newVerts[i] = verts[vertexCopyIndex[i]]; + } + + // copy back to this surface + indexes = newIndexes; + verts = newVerts; + + GenerateEdgeIndexes(); + + return true; +} + +/* +============= +idSurface::IsConnected +============= +*/ +bool idSurface::IsConnected() const { + int i, j, numIslands, numTris; + int queueStart, queueEnd; + int *queue, *islandNum; + int curTri, nextTri, edgeNum; + const int *index; + + numIslands = 0; + numTris = indexes.Num() / 3; + islandNum = (int *) _alloca16( numTris * sizeof( int ) ); + memset( islandNum, -1, numTris * sizeof( int ) ); + queue = (int *) _alloca16( numTris * sizeof( int ) ); + + for ( i = 0; i < numTris; i++ ) { + + if ( islandNum[i] != -1 ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = i; + islandNum[i] = numIslands; + + for ( curTri = queue[queueStart]; queueStart < queueEnd; curTri = queue[++queueStart] ) { + + index = &edgeIndexes[curTri * 3]; + + for ( j = 0; j < 3; j++ ) { + + edgeNum = index[j]; + nextTri = edges[abs(edgeNum)].tris[INT32_SIGNBITNOTSET(edgeNum)]; + + if ( nextTri == -1 ) { + continue; + } + + nextTri /= 3; + + if ( islandNum[nextTri] != -1 ) { + continue; + } + + queue[queueEnd++] = nextTri; + islandNum[nextTri] = numIslands; + } + } + numIslands++; + } + + return ( numIslands == 1 ); +} + +/* +================= +idSurface::IsClosed +================= +*/ +bool idSurface::IsClosed() const { + for ( int i = 0; i < edges.Num(); i++ ) { + if ( edges[i].tris[0] < 0 || edges[i].tris[1] < 0 ) { + return false; + } + } + return true; +} + +/* +============= +idSurface::IsPolytope +============= +*/ +bool idSurface::IsPolytope( const float epsilon ) const { + int i, j; + idPlane plane; + + if ( !IsClosed() ) { + return false; + } + + for ( i = 0; i < indexes.Num(); i += 3 ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + + for ( j = 0; j < verts.Num(); j++ ) { + if ( plane.Side( verts[j].xyz, epsilon ) == SIDE_FRONT ) { + return false; + } + } + } + return true; +} + +/* +============= +idSurface::PlaneDistance +============= +*/ +float idSurface::PlaneDistance( const idPlane &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < verts.Num(); i++ ) { + d = plane.Distance( verts[i].xyz ); + if ( d < min ) { + min = d; + if ( IEEE_FLT_SIGNBITSET( min ) & IEEE_FLT_SIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( IEEE_FLT_SIGNBITSET( min ) & IEEE_FLT_SIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( IEEE_FLT_SIGNBITNOTSET( min ) ) { + return min; + } + if ( IEEE_FLT_SIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idSurface::PlaneSide +============= +*/ +int idSurface::PlaneSide( const idPlane &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < verts.Num(); i++ ) { + d = plane.Distance( verts[i].xyz ); + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +================= +idSurface::LineIntersection +================= +*/ +bool idSurface::LineIntersection( const idVec3 &start, const idVec3 &end, bool backFaceCull ) const { + float scale; + + RayIntersection( start, end - start, scale, false ); + return ( scale >= 0.0f && scale <= 1.0f ); +} + +/* +================= +idSurface::RayIntersection +================= +*/ +bool idSurface::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull ) const { + int i, i0, i1, i2, s0, s1, s2; + float d, s; + byte *sidedness; + idPluecker rayPl, pl; + idPlane plane; + + sidedness = (byte *)_alloca( edges.Num() * sizeof(byte) ); + scale = idMath::INFINITY; + + rayPl.FromRay( start, dir ); + + // ray sidedness for edges + for ( i = 0; i < edges.Num(); i++ ) { + pl.FromLine( verts[ edges[i].verts[1] ].xyz, verts[ edges[i].verts[0] ].xyz ); + d = pl.PermutedInnerProduct( rayPl ); + sidedness[ i ] = IEEE_FLT_SIGNBITSET( d ); + } + + // test triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + i0 = edgeIndexes[i+0]; + i1 = edgeIndexes[i+1]; + i2 = edgeIndexes[i+2]; + s0 = sidedness[abs(i0)] ^ INT32_SIGNBITSET( i0 ); + s1 = sidedness[abs(i1)] ^ INT32_SIGNBITSET( i1 ); + s2 = sidedness[abs(i2)] ^ INT32_SIGNBITSET( i2 ); + + if ( s0 & s1 & s2 ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + plane.RayIntersection( start, dir, s ); + if ( idMath::Fabs( s ) < idMath::Fabs( scale ) ) { + scale = s; + } + } else if ( !backFaceCull && !(s0 | s1 | s2) ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + plane.RayIntersection( start, dir, s ); + if ( idMath::Fabs( s ) < idMath::Fabs( scale ) ) { + scale = s; + } + } + } + + if ( idMath::Fabs( scale ) < idMath::INFINITY ) { + return true; + } + return false; +} + +/* +================= +idSurface::GenerateEdgeIndexes + + Assumes each edge is shared by at most two triangles. +================= +*/ +void idSurface::GenerateEdgeIndexes() { + int i, j, i0, i1, i2, s, v0, v1, edgeNum; + int *index, *vertexEdges, *edgeChain; + surfaceEdge_t e[3]; + + vertexEdges = (int *) _alloca16( verts.Num() * sizeof( int ) ); + memset( vertexEdges, -1, verts.Num() * sizeof( int ) ); + edgeChain = (int *) _alloca16( indexes.Num() * sizeof( int ) ); + + edgeIndexes.SetNum( indexes.Num() ); + + edges.Clear(); + + // the first edge is a dummy + e[0].verts[0] = e[0].verts[1] = e[0].tris[0] = e[0].tris[1] = 0; + edges.Append( e[0] ); + + for ( i = 0; i < indexes.Num(); i += 3 ) { + index = indexes.Ptr() + i; + // vertex numbers + i0 = index[0]; + i1 = index[1]; + i2 = index[2]; + // setup edges each with smallest vertex number first + s = INT32_SIGNBITSET(i1 - i0); + e[0].verts[0] = index[s]; + e[0].verts[1] = index[s^1]; + s = INT32_SIGNBITSET(i2 - i1) + 1; + e[1].verts[0] = index[s]; + e[1].verts[1] = index[s^3]; + s = INT32_SIGNBITSET(i2 - i0) << 1; + e[2].verts[0] = index[s]; + e[2].verts[1] = index[s^2]; + // get edges + for ( j = 0; j < 3; j++ ) { + v0 = e[j].verts[0]; + v1 = e[j].verts[1]; + for ( edgeNum = vertexEdges[v0]; edgeNum >= 0; edgeNum = edgeChain[edgeNum] ) { + if ( edges[edgeNum].verts[1] == v1 ) { + break; + } + } + // if the edge does not yet exist + if ( edgeNum < 0 ) { + e[j].tris[0] = e[j].tris[1] = -1; + edgeNum = edges.Append( e[j] ); + edgeChain[edgeNum] = vertexEdges[v0]; + vertexEdges[v0] = edgeNum; + } + // update edge index and edge tri references + if ( index[j] == v0 ) { + assert( edges[edgeNum].tris[0] == -1 ); // edge may not be shared by more than two triangles + edges[edgeNum].tris[0] = i; + edgeIndexes[i+j] = edgeNum; + } else { + assert( edges[edgeNum].tris[1] == -1 ); // edge may not be shared by more than two triangles + edges[edgeNum].tris[1] = i; + edgeIndexes[i+j] = -edgeNum; + } + } + } +} + +/* +================= +idSurface::FindEdge +================= +*/ +int idSurface::FindEdge( int v1, int v2 ) const { + int i, firstVert, secondVert; + + if ( v1 < v2 ) { + firstVert = v1; + secondVert = v2; + } else { + firstVert = v2; + secondVert = v1; + } + for ( i = 1; i < edges.Num(); i++ ) { + if ( edges[i].verts[0] == firstVert ) { + if ( edges[i].verts[1] == secondVert ) { + break; + } + } + } + if ( i < edges.Num() ) { + return v1 < v2 ? i : -i; + } + return 0; +} diff --git a/neo/idlib/geometry/Surface.h b/neo/idlib/geometry/Surface.h new file mode 100644 index 00000000..c1a06472 --- /dev/null +++ b/neo/idlib/geometry/Surface.h @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SURFACE_H__ +#define __SURFACE_H__ + +/* +=============================================================================== + + Surface base class. + + A surface is tesselated to a triangle mesh with each edge shared by + at most two triangles. + +=============================================================================== +*/ + +typedef struct surfaceEdge_s { + int verts[2]; // edge vertices always with ( verts[0] < verts[1] ) + int tris[2]; // edge triangles +} surfaceEdge_t; + + +class idSurface { +public: + idSurface(); + explicit idSurface( const idSurface &surf ); + explicit idSurface( const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + ~idSurface(); + + const idDrawVert & operator[]( const int index ) const; + idDrawVert & operator[]( const int index ); + idSurface & operator+=( const idSurface &surf ); + + int GetNumIndexes() const { return indexes.Num(); } + const int * GetIndexes() const { return indexes.Ptr(); } + int GetNumVertices() const { return verts.Num(); } + const idDrawVert * GetVertices() const { return verts.Ptr(); } + const int * GetEdgeIndexes() const { return edgeIndexes.Ptr(); } + const surfaceEdge_t * GetEdges() const { return edges.Ptr(); } + + void Clear(); + void TranslateSelf( const idVec3 &translation ); + void RotateSelf( const idMat3 &rotation ); + + // splits the surface into a front and back surface, the surface itself stays unchanged + // frontOnPlaneEdges and backOnPlaneEdges optionally store the indexes to the edges that lay on the split plane + // returns a SIDE_? + int Split( const idPlane &plane, const float epsilon, idSurface **front, idSurface **back, int *frontOnPlaneEdges = NULL, int *backOnPlaneEdges = NULL ) const; + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + // returns true if each triangle can be reached from any other triangle by a traversal + bool IsConnected() const; + // returns true if the surface is closed + bool IsClosed() const; + // returns true if the surface is a convex hull + bool IsPolytope( const float epsilon = 0.1f ) const; + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + // returns true if the line intersects one of the surface triangles + bool LineIntersection( const idVec3 &start, const idVec3 &end, bool backFaceCull = false ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull = false ) const; + +protected: + idList verts; // vertices + idList indexes; // 3 references to vertices for each triangle + idList edges; // edges + idList edgeIndexes; // 3 references to edges for each triangle, may be negative for reversed edge + +protected: + void GenerateEdgeIndexes(); + int FindEdge( int v1, int v2 ) const; +}; + +/* +==================== +idSurface::idSurface +==================== +*/ +ID_INLINE idSurface::idSurface() { +} + +/* +================= +idSurface::idSurface +================= +*/ +ID_INLINE idSurface::idSurface( const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + assert( verts != NULL && indexes != NULL && numVerts > 0 && numIndexes > 0 ); + this->verts.SetNum( numVerts ); + memcpy( this->verts.Ptr(), verts, numVerts * sizeof( verts[0] ) ); + this->indexes.SetNum( numIndexes ); + memcpy( this->indexes.Ptr(), indexes, numIndexes * sizeof( indexes[0] ) ); + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface::idSurface +==================== +*/ +ID_INLINE idSurface::idSurface( const idSurface &surf ) { + this->verts = surf.verts; + this->indexes = surf.indexes; + this->edges = surf.edges; + this->edgeIndexes = surf.edgeIndexes; +} + +/* +==================== +idSurface::~idSurface +==================== +*/ +ID_INLINE idSurface::~idSurface() { +} + +/* +================= +idSurface::operator[] +================= +*/ +ID_INLINE const idDrawVert &idSurface::operator[]( const int index ) const { + return verts[ index ]; +}; + +/* +================= +idSurface::operator[] +================= +*/ +ID_INLINE idDrawVert &idSurface::operator[]( const int index ) { + return verts[ index ]; +}; + +/* +================= +idSurface::operator+= +================= +*/ +ID_INLINE idSurface &idSurface::operator+=( const idSurface &surf ) { + int i, m, n; + n = verts.Num(); + m = indexes.Num(); + verts.Append( surf.verts ); // merge verts where possible ? + indexes.Append( surf.indexes ); + for ( i = m; i < indexes.Num(); i++ ) { + indexes[i] += n; + } + GenerateEdgeIndexes(); + return *this; +} + +/* +================= +idSurface::Clear +================= +*/ +ID_INLINE void idSurface::Clear() { + verts.Clear(); + indexes.Clear(); + edges.Clear(); + edgeIndexes.Clear(); +} + +/* +================= +idSurface::TranslateSelf +================= +*/ +ID_INLINE void idSurface::TranslateSelf( const idVec3 &translation ) { + for ( int i = 0; i < verts.Num(); i++ ) { + verts[i].xyz += translation; + } +} + +/* +================= +idSurface::RotateSelf +================= +*/ +ID_INLINE void idSurface::RotateSelf( const idMat3 &rotation ) { + for ( int i = 0; i < verts.Num(); i++ ) { + verts[i].xyz *= rotation; + verts[i].SetNormal( verts[i].GetNormal() * rotation ); + verts[i].SetTangent( verts[i].GetTangent() * rotation ); + } +} + +#endif /* !__SURFACE_H__ */ diff --git a/neo/idlib/geometry/Surface_Patch.cpp b/neo/idlib/geometry/Surface_Patch.cpp new file mode 100644 index 00000000..9d60eb2d --- /dev/null +++ b/neo/idlib/geometry/Surface_Patch.cpp @@ -0,0 +1,698 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +================= +idSurface_Patch::SetSize +================= +*/ +void idSurface_Patch::SetSize( int patchWidth, int patchHeight ) { + if ( patchWidth < 1 || patchWidth > maxWidth ) { + idLib::common->FatalError("idSurface_Patch::SetSize: invalid patchWidth"); + } + if ( patchHeight < 1 || patchHeight > maxHeight ) { + idLib::common->FatalError("idSurface_Patch::SetSize: invalid patchHeight"); + } + width = patchWidth; + height = patchHeight; + verts.SetNum( width * height ); +} + +/* +================= +idSurface_Patch::PutOnCurve + +Expects an expanded patch. +================= +*/ +void idSurface_Patch::PutOnCurve() { + int i, j; + idDrawVert prev, next; + + assert( expanded == true ); + // put all the approximating points on the curve + for ( i = 0; i < width; i++ ) { + for ( j = 1; j < height; j += 2 ) { + LerpVert( verts[j*maxWidth+i], verts[(j+1)*maxWidth+i], prev ); + LerpVert( verts[j*maxWidth+i], verts[(j-1)*maxWidth+i], next ); + LerpVert( prev, next, verts[j*maxWidth+i] ); + } + } + + for ( j = 0; j < height; j++ ) { + for ( i = 1; i < width; i += 2 ) { + LerpVert( verts[j*maxWidth+i], verts[j*maxWidth+i+1], prev ); + LerpVert( verts[j*maxWidth+i], verts[j*maxWidth+i-1], next ); + LerpVert( prev, next, verts[j*maxWidth+i] ); + } + } +} + +/* +================ +idSurface_Patch::ProjectPointOntoVector +================ +*/ +void idSurface_Patch::ProjectPointOntoVector( const idVec3 &point, const idVec3 &vStart, const idVec3 &vEnd, idVec3 &vProj ) { + idVec3 pVec, vec; + + pVec = point - vStart; + vec = vEnd - vStart; + vec.Normalize(); + // project onto the directional vector for this segment + vProj = vStart + (pVec * vec) * vec; +} + +/* +================ +idSurface_Patch::RemoveLinearColumnsRows + +Expects an expanded patch. +================ +*/ +void idSurface_Patch::RemoveLinearColumnsRows() { + int i, j, k; + float len, maxLength; + idVec3 proj, dir; + + assert( expanded == true ); + for ( j = 1; j < width - 1; j++ ) { + maxLength = 0; + for ( i = 0; i < height; i++ ) { + idSurface_Patch::ProjectPointOntoVector( verts[i*maxWidth + j].xyz, + verts[i*maxWidth + j-1].xyz, verts[i*maxWidth + j+1].xyz, proj); + dir = verts[i*maxWidth + j].xyz - proj; + len = dir.LengthSqr(); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < Square( 0.2f ) ) { + width--; + for ( i = 0; i < height; i++ ) { + for ( k = j; k < width; k++ ) { + verts[i*maxWidth + k] = verts[i*maxWidth + k+1]; + } + } + j--; + } + } + for ( j = 1; j < height - 1; j++ ) { + maxLength = 0; + for ( i = 0; i < width; i++ ) { + idSurface_Patch::ProjectPointOntoVector( verts[j*maxWidth + i].xyz, + verts[(j-1)*maxWidth + i].xyz, verts[(j+1)*maxWidth + i].xyz, proj); + dir = verts[j*maxWidth + i].xyz - proj; + len = dir.LengthSqr(); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < Square( 0.2f ) ) { + height--; + for ( i = 0; i < width; i++ ) { + for ( k = j; k < height; k++ ) { + verts[k*maxWidth + i] = verts[(k+1)*maxWidth + i]; + } + } + j--; + } + } +} + +/* +================ +idSurface_Patch::ResizeExpanded +================ +*/ +void idSurface_Patch::ResizeExpanded( int newHeight, int newWidth ) { + int i, j; + + assert( expanded == true ); + if ( newHeight <= maxHeight && newWidth <= maxWidth ) { + return; + } + if ( newHeight * newWidth > maxHeight * maxWidth ) { + verts.SetNum( newHeight * newWidth ); + } + // space out verts for new height and width + for ( j = maxHeight-1; j >= 0; j-- ) { + for ( i = maxWidth-1; i >= 0; i-- ) { + verts[j*newWidth + i] = verts[j*maxWidth + i]; + } + } + maxHeight = newHeight; + maxWidth = newWidth; +} + +/* +================ +idSurface_Patch::Collapse +================ +*/ +void idSurface_Patch::Collapse() { + int i, j; + + if ( !expanded ) { + idLib::common->FatalError("idSurface_Patch::Collapse: patch not expanded"); + } + expanded = false; + if ( width != maxWidth ) { + for ( j = 0; j < height; j++ ) { + for ( i = 0; i < width; i++ ) { + verts[j*width + i] = verts[j*maxWidth + i]; + } + } + } + verts.SetNum( width * height ); +} + +/* +================ +idSurface_Patch::Expand +================ +*/ +void idSurface_Patch::Expand() { + int i, j; + + if ( expanded ) { + idLib::common->FatalError("idSurface_Patch::Expand: patch alread expanded"); + } + expanded = true; + verts.SetNum( maxWidth * maxHeight ); + if ( width != maxWidth ) { + for ( j = height-1; j >= 0; j-- ) { + for ( i = width-1; i >= 0; i-- ) { + verts[j*maxWidth + i] = verts[j*width + i]; + } + } + } +} + +/* +============ +idSurface_Patch::LerpVert +============ +*/ +void idSurface_Patch::LerpVert( const idDrawVert &a, const idDrawVert &b, idDrawVert &out ) const { + out.xyz[0] = 0.5f * ( a.xyz[0] + b.xyz[0] ); + out.xyz[1] = 0.5f * ( a.xyz[1] + b.xyz[1] ); + out.xyz[2] = 0.5f * ( a.xyz[2] + b.xyz[2] ); + out.SetNormal( ( a.GetNormal() + b.GetNormal() ) * 0.5f ); + out.SetTexCoord( ( a.GetTexCoord() + b.GetTexCoord() ) * 0.5f ); +} + +/* +================= +idSurface_Patch::GenerateNormals + +Handles all the complicated wrapping and degenerate cases +Expects a Not expanded patch. +================= +*/ +#define COPLANAR_EPSILON 0.1f + +void idSurface_Patch::GenerateNormals() { + int i, j, k, dist; + idVec3 norm; + idVec3 sum; + int count; + idVec3 base; + idVec3 delta; + int x, y; + idVec3 around[8], temp; + bool good[8]; + bool wrapWidth, wrapHeight; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + assert( expanded == false ); + + // + // if all points are coplanar, set all normals to that plane + // + idVec3 extent[3]; + float offset; + + extent[0] = verts[width - 1].xyz - verts[0].xyz; + extent[1] = verts[(height-1) * width + width - 1].xyz - verts[0].xyz; + extent[2] = verts[(height-1) * width].xyz - verts[0].xyz; + + norm = extent[0].Cross( extent[1] ); + if ( norm.LengthSqr() == 0.0f ) { + norm = extent[0].Cross( extent[2] ); + if ( norm.LengthSqr() == 0.0f ) { + norm = extent[1].Cross( extent[2] ); + } + } + + // wrapped patched may not get a valid normal here + if ( norm.Normalize() != 0.0f ) { + + offset = verts[0].xyz * norm; + for ( i = 1; i < width * height; i++ ) { + float d = verts[i].xyz * norm; + if ( idMath::Fabs( d - offset ) > COPLANAR_EPSILON ) { + break; + } + } + + if ( i == width * height ) { + // all are coplanar + for ( i = 0; i < width * height; i++ ) { + verts[i].SetNormal( norm ); + } + return; + } + } + + // check for wrapped edge cases, which should smooth across themselves + wrapWidth = false; + for ( i = 0; i < height; i++ ) { + delta = verts[i * width].xyz - verts[i * width + width-1].xyz; + if ( delta.LengthSqr() > Square( 1.0f ) ) { + break; + } + } + if ( i == height ) { + wrapWidth = true; + } + + wrapHeight = false; + for ( i = 0; i < width; i++ ) { + delta = verts[i].xyz - verts[(height-1) * width + i].xyz; + if ( delta.LengthSqr() > Square( 1.0f ) ) { + break; + } + } + if ( i == width ) { + wrapHeight = true; + } + + for ( i = 0; i < width; i++ ) { + for ( j = 0; j < height; j++ ) { + count = 0; + base = verts[j * width + i].xyz; + for ( k = 0; k < 8; k++ ) { + around[k] = vec3_origin; + good[k] = false; + + for ( dist = 1; dist <= 3; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + temp = verts[y * width + x].xyz - base; + if ( temp.Normalize() == 0.0f ) { + continue; // degenerate edge, get more dist + } else { + good[k] = true; + around[k] = temp; + break; // good edge + } + } + } + + sum = vec3_origin; + for ( k = 0; k < 8; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + norm = around[(k+1)&7].Cross( around[k] ); + if ( norm.Normalize() == 0.0f ) { + continue; + } + sum += norm; + count++; + } + if ( count == 0 ) { + //idLib::common->Printf("bad normal\n"); + count = 1; + } + sum.Normalize(); + verts[j * width + i].SetNormal( sum ); + } + } +} + +/* +================= +idSurface_Patch::GenerateIndexes +================= +*/ +void idSurface_Patch::GenerateIndexes() { + int i, j, v1, v2, v3, v4, index; + + indexes.SetNum( (width-1) * (height-1) * 2 * 3 ); + index = 0; + for ( i = 0; i < width - 1; i++ ) { + for ( j = 0; j < height - 1; j++ ) { + v1 = j * width + i; + v2 = v1 + 1; + v3 = v1 + width + 1; + v4 = v1 + width; + indexes[index++] = v1; + indexes[index++] = v3; + indexes[index++] = v2; + indexes[index++] = v1; + indexes[index++] = v4; + indexes[index++] = v3; + } + } + + GenerateEdgeIndexes(); +} + +/* +=============== +idSurface_Patch::SampleSinglePatchPoint +=============== +*/ +void idSurface_Patch::SampleSinglePatchPoint( const idDrawVert ctrl[3][3], float u, float v, idDrawVert *out ) const { + float vCtrl[3][8]; + int vPoint; + int axis; + + // find the control points for the v coordinate + for ( vPoint = 0; vPoint < 3; vPoint++ ) { + for ( axis = 0; axis < 8; axis++ ) { + float a, b, c; + float qA, qB, qC; + if ( axis < 3 ) { + a = ctrl[0][vPoint].xyz[axis]; + b = ctrl[1][vPoint].xyz[axis]; + c = ctrl[2][vPoint].xyz[axis]; + } else if ( axis < 6 ) { + a = ctrl[0][vPoint].GetNormal()[axis-3]; + b = ctrl[1][vPoint].GetNormal()[axis-3]; + c = ctrl[2][vPoint].GetNormal()[axis-3]; + } else { + a = ctrl[0][vPoint].GetTexCoord()[axis-6]; + b = ctrl[1][vPoint].GetTexCoord()[axis-6]; + c = ctrl[2][vPoint].GetTexCoord()[axis-6]; + } + qA = a - 2.0f * b + c; + qB = 2.0f * b - 2.0f * a; + qC = a; + vCtrl[vPoint][axis] = qA * u * u + qB * u + qC; + } + } + + // interpolate the v value + for ( axis = 0; axis < 8; axis++ ) { + float a, b, c; + float qA, qB, qC; + + a = vCtrl[0][axis]; + b = vCtrl[1][axis]; + c = vCtrl[2][axis]; + qA = a - 2.0f * b + c; + qB = 2.0f * b - 2.0f * a; + qC = a; + + if ( axis < 3 ) { + out->xyz[axis] = qA * v * v + qB * v + qC; + } else if ( axis < 6 ) { + idVec3 tempNormal = out->GetNormal(); + tempNormal[axis-3] = qA * v * v + qB * v + qC; + out->SetNormal( tempNormal ); + //out->normal[axis-3] = qA * v * v + qB * v + qC; + } else { + idVec2 tempST = out->GetTexCoord(); + tempST[axis-6] = qA * v * v + qB * v + qC; + out->SetTexCoord( tempST ); + } + } +} + +/* +=================== +idSurface_Patch::SampleSinglePatch +=================== +*/ +void idSurface_Patch::SampleSinglePatch( const idDrawVert ctrl[3][3], int baseCol, int baseRow, int width, int horzSub, int vertSub, idDrawVert *outVerts ) const { + int i, j; + float u, v; + + horzSub++; + vertSub++; + for ( i = 0; i < horzSub; i++ ) { + for ( j = 0; j < vertSub; j++ ) { + u = (float) i / ( horzSub - 1 ); + v = (float) j / ( vertSub - 1 ); + SampleSinglePatchPoint( ctrl, u, v, &outVerts[((baseRow + j) * width) + i + baseCol] ); + } + } +} + +/* +================= +idSurface_Patch::SubdivideExplicit +================= +*/ +void idSurface_Patch::SubdivideExplicit( int horzSubdivisions, int vertSubdivisions, bool genNormals, bool removeLinear ) { + int i, j, k, l; + idDrawVert sample[3][3]; + int outWidth = ((width - 1) / 2 * horzSubdivisions) + 1; + int outHeight = ((height - 1) / 2 * vertSubdivisions) + 1; + idDrawVert *dv = new (TAG_IDLIB_SURFACE) idDrawVert[ outWidth * outHeight ]; + + // generate normals for the control mesh + if ( genNormals ) { + GenerateNormals(); + } + + int baseCol = 0; + for ( i = 0; i + 2 < width; i += 2 ) { + int baseRow = 0; + for ( j = 0; j + 2 < height; j += 2 ) { + for ( k = 0; k < 3; k++ ) { + for ( l = 0; l < 3; l++ ) { + sample[k][l] = verts[ ((j + l) * width) + i + k ]; + } + } + SampleSinglePatch( sample, baseCol, baseRow, outWidth, horzSubdivisions, vertSubdivisions, dv ); + baseRow += vertSubdivisions; + } + baseCol += horzSubdivisions; + } + verts.SetNum( outWidth * outHeight ); + for ( i = 0; i < outWidth * outHeight; i++ ) { + verts[i] = dv[i]; + } + + delete[] dv; + + width = maxWidth = outWidth; + height = maxHeight = outHeight; + expanded = false; + + if ( removeLinear ) { + Expand(); + RemoveLinearColumnsRows(); + Collapse(); + } + + // normalize all the lerped normals + if ( genNormals ) { + idVec3 tempNormal; + for ( i = 0; i < width * height; i++ ) { + tempNormal= verts[i].GetNormal(); + tempNormal.Normalize(); + verts[i].SetNormal( tempNormal ); + } + } + + GenerateIndexes(); +} + +/* +================= +idSurface_Patch::Subdivide +================= +*/ +void idSurface_Patch::Subdivide( float maxHorizontalError, float maxVerticalError, float maxLength, bool genNormals ) { + int i, j, k, l; + idDrawVert prev, next, mid; + idVec3 prevxyz, nextxyz, midxyz; + idVec3 delta; + float maxHorizontalErrorSqr, maxVerticalErrorSqr, maxLengthSqr; + + // generate normals for the control mesh + if ( genNormals ) { + GenerateNormals(); + } + + maxHorizontalErrorSqr = Square( maxHorizontalError ); + maxVerticalErrorSqr = Square( maxVerticalError ); + maxLengthSqr = Square( maxLength ); + + Expand(); + + // horizontal subdivisions + for ( j = 0; j + 2 < width; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0; i < height; i++ ) { + for ( l = 0; l < 3; l++ ) { + prevxyz[l] = verts[i*maxWidth + j+1].xyz[l] - verts[i*maxWidth + j ].xyz[l]; + nextxyz[l] = verts[i*maxWidth + j+2].xyz[l] - verts[i*maxWidth + j+1].xyz[l]; + midxyz[l] = (verts[i*maxWidth + j ].xyz[l] + verts[i*maxWidth + j+1].xyz[l] * 2.0f + + verts[i*maxWidth + j+2].xyz[l] ) * 0.25f; + } + + if ( maxLength > 0.0f ) { + // if the span length is too long, force a subdivision + if ( prevxyz.LengthSqr() > maxLengthSqr || nextxyz.LengthSqr() > maxLengthSqr ) { + break; + } + } + // see if this midpoint is off far enough to subdivide + delta = verts[i*maxWidth + j+1].xyz - midxyz; + if ( delta.LengthSqr() > maxHorizontalErrorSqr ) { + break; + } + } + + if ( i == height ) { + continue; // didn't need subdivision + } + + if ( width + 2 >= maxWidth ) { + ResizeExpanded( maxHeight, maxWidth + 4 ); + } + + // insert two columns and replace the peak + width += 2; + + for ( i = 0; i < height; i++ ) { + idSurface_Patch::LerpVert( verts[i*maxWidth + j ], verts[i*maxWidth + j+1], prev ); + idSurface_Patch::LerpVert( verts[i*maxWidth + j+1], verts[i*maxWidth + j+2], next ); + idSurface_Patch::LerpVert( prev, next, mid ); + + for ( k = width - 1; k > j + 3; k-- ) { + verts[i*maxWidth + k] = verts[i*maxWidth + k-2]; + } + verts[i*maxWidth + j+1] = prev; + verts[i*maxWidth + j+2] = mid; + verts[i*maxWidth + j+3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + } + + // vertical subdivisions + for ( j = 0; j + 2 < height; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0; i < width; i++ ) { + for ( l = 0; l < 3; l++ ) { + prevxyz[l] = verts[(j+1)*maxWidth + i].xyz[l] - verts[j*maxWidth + i].xyz[l]; + nextxyz[l] = verts[(j+2)*maxWidth + i].xyz[l] - verts[(j+1)*maxWidth + i].xyz[l]; + midxyz[l] = (verts[j*maxWidth + i].xyz[l] + verts[(j+1)*maxWidth + i].xyz[l] * 2.0f + + verts[(j+2)*maxWidth + i].xyz[l] ) * 0.25f; + } + + if ( maxLength > 0.0f ) { + // if the span length is too long, force a subdivision + if ( prevxyz.LengthSqr() > maxLengthSqr || nextxyz.LengthSqr() > maxLengthSqr ) { + break; + } + } + // see if this midpoint is off far enough to subdivide + delta = verts[(j+1)*maxWidth + i].xyz - midxyz; + if ( delta.LengthSqr() > maxVerticalErrorSqr ) { + break; + } + } + + if ( i == width ) { + continue; // didn't need subdivision + } + + if ( height + 2 >= maxHeight ) { + ResizeExpanded( maxHeight + 4, maxWidth ); + } + + // insert two columns and replace the peak + height += 2; + + for ( i = 0; i < width; i++ ) { + LerpVert( verts[j*maxWidth + i], verts[(j+1)*maxWidth + i], prev ); + LerpVert( verts[(j+1)*maxWidth + i], verts[(j+2)*maxWidth + i], next ); + LerpVert( prev, next, mid ); + + for ( k = height - 1; k > j + 3; k-- ) { + verts[k*maxWidth + i] = verts[(k-2)*maxWidth + i]; + } + verts[(j+1)*maxWidth + i] = prev; + verts[(j+2)*maxWidth + i] = mid; + verts[(j+3)*maxWidth + i] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + } + + PutOnCurve(); + + RemoveLinearColumnsRows(); + + Collapse(); + + // normalize all the lerped normals + if ( genNormals ) { + idVec3 tempNormal; + for ( i = 0; i < width * height; i++ ) { + tempNormal = verts[i].GetNormal(); + tempNormal.Normalize(); + verts[i].SetNormal( tempNormal ); + } + } + + GenerateIndexes(); +} diff --git a/neo/idlib/geometry/Surface_Patch.h b/neo/idlib/geometry/Surface_Patch.h new file mode 100644 index 00000000..5a7e691b --- /dev/null +++ b/neo/idlib/geometry/Surface_Patch.h @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SURFACE_PATCH_H__ +#define __SURFACE_PATCH_H__ + +/* +=============================================================================== + + Bezier patch surface. + +=============================================================================== +*/ + +class idSurface_Patch : public idSurface { + +public: + idSurface_Patch(); + idSurface_Patch( int maxPatchWidth, int maxPatchHeight ); + idSurface_Patch( const idSurface_Patch &patch ); + ~idSurface_Patch(); + + void SetSize( int patchWidth, int patchHeight ); + int GetWidth() const; + int GetHeight() const; + + // subdivide the patch mesh based on error + void Subdivide( float maxHorizontalError, float maxVerticalError, float maxLength, bool genNormals = false ); + // subdivide the patch up to an explicit number of horizontal and vertical subdivisions + void SubdivideExplicit( int horzSubdivisions, int vertSubdivisions, bool genNormals, bool removeLinear = false ); + +protected: + int width; // width of patch + int height; // height of patch + int maxWidth; // maximum width allocated for + int maxHeight; // maximum height allocated for + bool expanded; // true if vertices are spaced out + +private: + // put the approximation points on the curve + void PutOnCurve(); + // remove columns and rows with all points on one line + void RemoveLinearColumnsRows(); + // resize verts buffer + void ResizeExpanded( int height, int width ); + // space points out over maxWidth * maxHeight buffer + void Expand(); + // move all points to the start of the verts buffer + void Collapse(); + // project a point onto a vector to calculate maximum curve error + void ProjectPointOntoVector( const idVec3 &point, const idVec3 &vStart, const idVec3 &vEnd, idVec3 &vProj ); + // generate normals + void GenerateNormals(); + // generate triangle indexes + void GenerateIndexes(); + // lerp point from two patch point + void LerpVert( const idDrawVert &a, const idDrawVert &b, idDrawVert &out ) const; + // sample a single 3x3 patch + void SampleSinglePatchPoint( const idDrawVert ctrl[3][3], float u, float v, idDrawVert *out ) const; + void SampleSinglePatch( const idDrawVert ctrl[3][3], int baseCol, int baseRow, int width, int horzSub, int vertSub, idDrawVert *outVerts ) const; +}; + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch() { + height = width = maxHeight = maxWidth = 0; + expanded = false; +} + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( int maxPatchWidth, int maxPatchHeight ) { + width = height = 0; + maxWidth = maxPatchWidth; + maxHeight = maxPatchHeight; + verts.SetNum( maxWidth * maxHeight ); + expanded = false; +} + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( const idSurface_Patch &patch ) { + (*this) = patch; +} + +/* +================= +idSurface_Patch::~idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::~idSurface_Patch() { +} + +/* +================= +idSurface_Patch::GetWidth +================= +*/ +ID_INLINE int idSurface_Patch::GetWidth() const { + return width; +} + +/* +================= +idSurface_Patch::GetHeight +================= +*/ +ID_INLINE int idSurface_Patch::GetHeight() const { + return height; +} + +#endif /* !__SURFACE_PATCH_H__ */ diff --git a/neo/idlib/geometry/Surface_Polytope.cpp b/neo/idlib/geometry/Surface_Polytope.cpp new file mode 100644 index 00000000..545f0a17 --- /dev/null +++ b/neo/idlib/geometry/Surface_Polytope.cpp @@ -0,0 +1,334 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +#define POLYTOPE_VERTEX_EPSILON 0.1f + +/* +==================== +idSurface_Polytope::FromPlanes +==================== +*/ +void idSurface_Polytope::FromPlanes( const idPlane *planes, const int numPlanes ) { + int i, j, k, *windingVerts; + idFixedWinding w; + idDrawVert newVert; + + windingVerts = (int *) _alloca( MAX_POINTS_ON_WINDING * sizeof( int ) ); + memset( &newVert, 0, sizeof( newVert ) ); + + for ( i = 0; i < numPlanes; i++ ) { + + w.BaseForPlane( planes[i] ); + + for ( j = 0; j < numPlanes; j++ ) { + if ( j == i ) { + continue; + } + if ( !w.ClipInPlace( -planes[j], ON_EPSILON, true ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; + } + + for ( j = 0; j < w.GetNumPoints(); j++ ) { + for ( k = 0; k < verts.Num(); k++ ) { + if ( verts[k].xyz.Compare( w[j].ToVec3(), POLYTOPE_VERTEX_EPSILON ) ) { + break; + } + } + if ( k >= verts.Num() ) { + newVert.xyz = w[j].ToVec3(); + k = verts.Append( newVert ); + } + windingVerts[j] = k; + } + + for ( j = 2; j < w.GetNumPoints(); j++ ) { + indexes.Append( windingVerts[0] ); + indexes.Append( windingVerts[j-1] ); + indexes.Append( windingVerts[j] ); + } + } + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupTetrahedron +==================== +*/ +void idSurface_Polytope::SetupTetrahedron( const idBounds &bounds ) { + idVec3 center, scale; + float c1, c2, c3; + + c1 = 0.4714045207f; + c2 = 0.8164965809f; + c3 = -0.3333333333f; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 4 ); + verts[0].xyz = center + idVec3( 0.0f, 0.0f, scale.z ); + verts[1].xyz = center + idVec3( 2.0f * c1 * scale.x, 0.0f, c3 * scale.z ); + verts[2].xyz = center + idVec3( -c1 * scale.x, c2 * scale.y, c3 * scale.z ); + verts[3].xyz = center + idVec3( -c1 * scale.x, -c2 * scale.y, c3 * scale.z ); + + indexes.SetNum( 4*3 ); + indexes[0*3+0] = 0; + indexes[0*3+1] = 1; + indexes[0*3+2] = 2; + indexes[1*3+0] = 0; + indexes[1*3+1] = 2; + indexes[1*3+2] = 3; + indexes[2*3+0] = 0; + indexes[2*3+1] = 3; + indexes[2*3+2] = 1; + indexes[3*3+0] = 1; + indexes[3*3+1] = 3; + indexes[3*3+2] = 2; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupHexahedron +==================== +*/ +void idSurface_Polytope::SetupHexahedron( const idBounds &bounds ) { + idVec3 center, scale; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 8 ); + verts[0].xyz = center + idVec3( -scale.x, -scale.y, -scale.z ); + verts[1].xyz = center + idVec3( scale.x, -scale.y, -scale.z ); + verts[2].xyz = center + idVec3( scale.x, scale.y, -scale.z ); + verts[3].xyz = center + idVec3( -scale.x, scale.y, -scale.z ); + verts[4].xyz = center + idVec3( -scale.x, -scale.y, scale.z ); + verts[5].xyz = center + idVec3( scale.x, -scale.y, scale.z ); + verts[6].xyz = center + idVec3( scale.x, scale.y, scale.z ); + verts[7].xyz = center + idVec3( -scale.x, scale.y, scale.z ); + + indexes.SetNum( 12*3 ); + indexes[ 0*3+0] = 0; + indexes[ 0*3+1] = 3; + indexes[ 0*3+2] = 2; + indexes[ 1*3+0] = 0; + indexes[ 1*3+1] = 2; + indexes[ 1*3+2] = 1; + indexes[ 2*3+0] = 0; + indexes[ 2*3+1] = 1; + indexes[ 2*3+2] = 5; + indexes[ 3*3+0] = 0; + indexes[ 3*3+1] = 5; + indexes[ 3*3+2] = 4; + indexes[ 4*3+0] = 0; + indexes[ 4*3+1] = 4; + indexes[ 4*3+2] = 7; + indexes[ 5*3+0] = 0; + indexes[ 5*3+1] = 7; + indexes[ 5*3+2] = 3; + indexes[ 6*3+0] = 6; + indexes[ 6*3+1] = 5; + indexes[ 6*3+2] = 1; + indexes[ 7*3+0] = 6; + indexes[ 7*3+1] = 1; + indexes[ 7*3+2] = 2; + indexes[ 8*3+0] = 6; + indexes[ 8*3+1] = 2; + indexes[ 8*3+2] = 3; + indexes[ 9*3+0] = 6; + indexes[ 9*3+1] = 3; + indexes[ 9*3+2] = 7; + indexes[10*3+0] = 6; + indexes[10*3+1] = 7; + indexes[10*3+2] = 4; + indexes[11*3+0] = 6; + indexes[11*3+1] = 4; + indexes[11*3+2] = 5; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupOctahedron +==================== +*/ +void idSurface_Polytope::SetupOctahedron( const idBounds &bounds ) { + idVec3 center, scale; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 6 ); + verts[0].xyz = center + idVec3( scale.x, 0.0f, 0.0f ); + verts[1].xyz = center + idVec3( -scale.x, 0.0f, 0.0f ); + verts[2].xyz = center + idVec3( 0.0f, scale.y, 0.0f ); + verts[3].xyz = center + idVec3( 0.0f, -scale.y, 0.0f ); + verts[4].xyz = center + idVec3( 0.0f, 0.0f, scale.z ); + verts[5].xyz = center + idVec3( 0.0f, 0.0f, -scale.z ); + + indexes.SetNum( 8*3 ); + indexes[0*3+0] = 4; + indexes[0*3+1] = 0; + indexes[0*3+2] = 2; + indexes[1*3+0] = 4; + indexes[1*3+1] = 2; + indexes[1*3+2] = 1; + indexes[2*3+0] = 4; + indexes[2*3+1] = 1; + indexes[2*3+2] = 3; + indexes[3*3+0] = 4; + indexes[3*3+1] = 3; + indexes[3*3+2] = 0; + indexes[4*3+0] = 5; + indexes[4*3+1] = 2; + indexes[4*3+2] = 0; + indexes[5*3+0] = 5; + indexes[5*3+1] = 1; + indexes[5*3+2] = 2; + indexes[6*3+0] = 5; + indexes[6*3+1] = 3; + indexes[6*3+2] = 1; + indexes[7*3+0] = 5; + indexes[7*3+1] = 0; + indexes[7*3+2] = 3; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupDodecahedron +==================== +*/ +void idSurface_Polytope::SetupDodecahedron( const idBounds &bounds ) { +} + +/* +==================== +idSurface_Polytope::SetupIcosahedron +==================== +*/ +void idSurface_Polytope::SetupIcosahedron( const idBounds &bounds ) { +} + +/* +==================== +idSurface_Polytope::SetupCylinder +==================== +*/ +void idSurface_Polytope::SetupCylinder( const idBounds &bounds, const int numSides ) { +} + +/* +==================== +idSurface_Polytope::SetupCone +==================== +*/ +void idSurface_Polytope::SetupCone( const idBounds &bounds, const int numSides ) { +} + +/* +==================== +idSurface_Polytope::SplitPolytope +==================== +*/ +int idSurface_Polytope::SplitPolytope( const idPlane &plane, const float epsilon, idSurface_Polytope **front, idSurface_Polytope **back ) const { + int side, i, j, s, v0, v1, v2, edgeNum; + idSurface *surface[2]; + idSurface_Polytope *polytopeSurfaces[2], *surf; + int *onPlaneEdges[2]; + + onPlaneEdges[0] = (int *) _alloca( indexes.Num() / 3 * sizeof( int ) ); + onPlaneEdges[1] = (int *) _alloca( indexes.Num() / 3 * sizeof( int ) ); + + side = Split( plane, epsilon, &surface[0], &surface[1], onPlaneEdges[0], onPlaneEdges[1] ); + + *front = polytopeSurfaces[0] = new (TAG_IDLIB_SURFACE) idSurface_Polytope; + *back = polytopeSurfaces[1] = new (TAG_IDLIB_SURFACE) idSurface_Polytope; + + for ( s = 0; s < 2; s++ ) { + if ( surface[s] ) { + polytopeSurfaces[s] = new idSurface_Polytope( *surface[s] ); + delete surface[s]; + surface[s] = NULL; + } + } + + *front = polytopeSurfaces[0]; + *back = polytopeSurfaces[1]; + + if ( side != SIDE_CROSS ) { + return side; + } + + // add triangles to close off the front and back polytope + for ( s = 0; s < 2; s++ ) { + + surf = polytopeSurfaces[s]; + + edgeNum = surf->edgeIndexes[onPlaneEdges[s][0]]; + v0 = surf->edges[abs(edgeNum)].verts[INT32_SIGNBITSET(edgeNum)]; + v1 = surf->edges[abs(edgeNum)].verts[INT32_SIGNBITNOTSET(edgeNum)]; + + for ( i = 1; onPlaneEdges[s][i] >= 0; i++ ) { + for ( j = i+1; onPlaneEdges[s][j] >= 0; j++ ) { + edgeNum = surf->edgeIndexes[onPlaneEdges[s][j]]; + if ( v1 == surf->edges[abs(edgeNum)].verts[INT32_SIGNBITSET(edgeNum)] ) { + v1 = surf->edges[abs(edgeNum)].verts[INT32_SIGNBITNOTSET(edgeNum)]; + SwapValues( onPlaneEdges[s][i], onPlaneEdges[s][j] ); + break; + } + } + } + + for ( i = 2; onPlaneEdges[s][i] >= 0; i++ ) { + edgeNum = surf->edgeIndexes[onPlaneEdges[s][i]]; + v1 = surf->edges[abs(edgeNum)].verts[INT32_SIGNBITNOTSET(edgeNum)]; + v2 = surf->edges[abs(edgeNum)].verts[INT32_SIGNBITSET(edgeNum)]; + surf->indexes.Append( v0 ); + surf->indexes.Append( v1 ); + surf->indexes.Append( v2 ); + } + + surf->GenerateEdgeIndexes(); + } + + return side; +} diff --git a/neo/idlib/geometry/Surface_Polytope.h b/neo/idlib/geometry/Surface_Polytope.h new file mode 100644 index 00000000..0739f3c7 --- /dev/null +++ b/neo/idlib/geometry/Surface_Polytope.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SURFACE_POLYTOPE_H__ +#define __SURFACE_POLYTOPE_H__ + +/* +=============================================================================== + + Polytope surface. + + NOTE: vertexes are not duplicated for texture coordinates. + +=============================================================================== +*/ + +class idSurface_Polytope : public idSurface { +public: + idSurface_Polytope(); + explicit idSurface_Polytope( const idSurface &surface ) : idSurface( surface ) {} + + void FromPlanes( const idPlane *planes, const int numPlanes ); + + void SetupTetrahedron( const idBounds &bounds ); + void SetupHexahedron( const idBounds &bounds ); + void SetupOctahedron( const idBounds &bounds ); + void SetupDodecahedron( const idBounds &bounds ); + void SetupIcosahedron( const idBounds &bounds ); + void SetupCylinder( const idBounds &bounds, const int numSides ); + void SetupCone( const idBounds &bounds, const int numSides ); + + int SplitPolytope( const idPlane &plane, const float epsilon, idSurface_Polytope **front, idSurface_Polytope **back ) const; + +protected: + +}; + +/* +==================== +idSurface_Polytope::idSurface_Polytope +==================== +*/ +ID_INLINE idSurface_Polytope::idSurface_Polytope() { +} + +#endif /* !__SURFACE_POLYTOPE_H__ */ diff --git a/neo/idlib/geometry/Surface_SweptSpline.cpp b/neo/idlib/geometry/Surface_SweptSpline.cpp new file mode 100644 index 00000000..42b4d02f --- /dev/null +++ b/neo/idlib/geometry/Surface_SweptSpline.cpp @@ -0,0 +1,223 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +==================== +idSurface_SweptSpline::SetSpline +==================== +*/ +void idSurface_SweptSpline::SetSpline( idCurve_Spline *spline ) { + if ( this->spline ) { + delete this->spline; + } + this->spline = spline; +} + +/* +==================== +idSurface_SweptSpline::SetSweptSpline +==================== +*/ +void idSurface_SweptSpline::SetSweptSpline( idCurve_Spline *sweptSpline ) { + if ( this->sweptSpline ) { + delete this->sweptSpline; + } + this->sweptSpline = sweptSpline; +} + +/* +==================== +idSurface_SweptSpline::SetSweptCircle + + Sets the swept spline to a NURBS circle. +==================== +*/ +void idSurface_SweptSpline::SetSweptCircle( const float radius ) { + idCurve_NURBS *nurbs = new (TAG_IDLIB_SURFACE) idCurve_NURBS(); + nurbs->Clear(); + nurbs->AddValue( 0.0f, idVec4( radius, radius, 0.0f, 0.00f ) ); + nurbs->AddValue( 100.0f, idVec4( -radius, radius, 0.0f, 0.25f ) ); + nurbs->AddValue( 200.0f, idVec4( -radius, -radius, 0.0f, 0.50f ) ); + nurbs->AddValue( 300.0f, idVec4( radius, -radius, 0.0f, 0.75f ) ); + nurbs->SetBoundaryType( idCurve_NURBS::BT_CLOSED ); + nurbs->SetCloseTime( 100.0f ); + if ( sweptSpline ) { + delete sweptSpline; + } + sweptSpline = nurbs; +} + +/* +==================== +idSurface_SweptSpline::GetFrame +==================== +*/ +void idSurface_SweptSpline::GetFrame( const idMat3 &previousFrame, const idVec3 dir, idMat3 &newFrame ) { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + float a, c, s, x, y, z; + idVec3 d, v; + idMat3 axis; + + d = dir; + d.Normalize(); + v = d.Cross( previousFrame[2] ); + v.Normalize(); + + a = idMath::ACos( previousFrame[2] * d ) * 0.5f; + c = idMath::Cos( a ); + s = idMath::Sqrt( 1.0f - c * c ); + + x = v[0] * s; + y = v[1] * s; + z = v[2] * s; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + xx = x * x2; + xy = x * y2; + xz = x * z2; + yy = y * y2; + yz = y * z2; + zz = z * z2; + wx = c * x2; + wy = c * y2; + wz = c * z2; + + axis[0][0] = 1.0f - ( yy + zz ); + axis[0][1] = xy - wz; + axis[0][2] = xz + wy; + axis[1][0] = xy + wz; + axis[1][1] = 1.0f - ( xx + zz ); + axis[1][2] = yz - wx; + axis[2][0] = xz - wy; + axis[2][1] = yz + wx; + axis[2][2] = 1.0f - ( xx + yy ); + + newFrame = previousFrame * axis; + + newFrame[2] = dir; + newFrame[2].Normalize(); + newFrame[1].Cross( newFrame[ 2 ], newFrame[ 0 ] ); + newFrame[1].Normalize(); + newFrame[0].Cross( newFrame[ 1 ], newFrame[ 2 ] ); + newFrame[0].Normalize(); +} + +/* +==================== +idSurface_SweptSpline::Tessellate + + tesselate the surface +==================== +*/ +void idSurface_SweptSpline::Tessellate( const int splineSubdivisions, const int sweptSplineSubdivisions ) { + int i, j, offset, baseOffset, splineDiv, sweptSplineDiv; + int i0, i1, j0, j1; + float totalTime, t; + idVec4 splinePos, splineD1; + idMat3 splineMat; + + if ( !spline || !sweptSpline ) { + idSurface::Clear(); + return; + } + + verts.SetNum( splineSubdivisions * sweptSplineSubdivisions ); + + // calculate the points and first derivatives for the swept spline + totalTime = sweptSpline->GetTime( sweptSpline->GetNumValues() - 1 ) - sweptSpline->GetTime( 0 ) + sweptSpline->GetCloseTime(); + sweptSplineDiv = sweptSpline->GetBoundaryType() == idCurve_Spline::BT_CLOSED ? sweptSplineSubdivisions : sweptSplineSubdivisions - 1; + baseOffset = (splineSubdivisions-1) * sweptSplineSubdivisions; + for ( i = 0; i < sweptSplineSubdivisions; i++ ) { + t = totalTime * i / sweptSplineDiv; + splinePos = sweptSpline->GetCurrentValue( t ); + splineD1 = sweptSpline->GetCurrentFirstDerivative( t ); + verts[baseOffset+i].xyz = splinePos.ToVec3(); + verts[baseOffset+i].SetTexCoordS( splinePos.w ); + verts[baseOffset+i].SetTangent( splineD1.ToVec3() ); + } + + // sweep the spline + totalTime = spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) + spline->GetCloseTime(); + splineDiv = spline->GetBoundaryType() == idCurve_Spline::BT_CLOSED ? splineSubdivisions : splineSubdivisions - 1; + splineMat.Identity(); + idVec3 tempNormal; + for ( i = 0; i < splineSubdivisions; i++ ) { + t = totalTime * i / splineDiv; + + splinePos = spline->GetCurrentValue( t ); + splineD1 = spline->GetCurrentFirstDerivative( t ); + + GetFrame( splineMat, splineD1.ToVec3(), splineMat ); + + offset = i * sweptSplineSubdivisions; + for ( j = 0; j < sweptSplineSubdivisions; j++ ) { + idDrawVert *v = &verts[offset+j]; + v->xyz = splinePos.ToVec3() + verts[baseOffset+j].xyz * splineMat; + v->SetTexCoord( verts[baseOffset+j].GetTexCoord().x, splinePos.w ); + v->SetTangent( verts[baseOffset+j].GetTangent() * splineMat ); + v->SetBiTangent( splineD1.ToVec3() ); + tempNormal = v->GetBiTangent().Cross( v->GetTangent() ); + tempNormal.Normalize(); + v->SetNormal( tempNormal ); + v->color[0] = v->color[1] = v->color[2] = v->color[3] = 0; + } + } + + indexes.SetNum( splineDiv * sweptSplineDiv * 2 * 3 ); + + // create indexes for the triangles + for ( offset = i = 0; i < splineDiv; i++ ) { + + i0 = (i+0) * sweptSplineSubdivisions; + i1 = (i+1) % splineSubdivisions * sweptSplineSubdivisions; + + for ( j = 0; j < sweptSplineDiv; j++ ) { + + j0 = (j+0); + j1 = (j+1) % sweptSplineSubdivisions; + + indexes[offset++] = i0 + j0; + indexes[offset++] = i0 + j1; + indexes[offset++] = i1 + j1; + + indexes[offset++] = i1 + j1; + indexes[offset++] = i1 + j0; + indexes[offset++] = i0 + j0; + } + } + + GenerateEdgeIndexes(); +} diff --git a/neo/idlib/geometry/Surface_SweptSpline.h b/neo/idlib/geometry/Surface_SweptSpline.h new file mode 100644 index 00000000..0f9bab16 --- /dev/null +++ b/neo/idlib/geometry/Surface_SweptSpline.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SURFACE_SWEPTSPLINE_H__ +#define __SURFACE_SWEPTSPLINE_H__ + +/* +=============================================================================== + + Swept Spline surface. + +=============================================================================== +*/ + +class idSurface_SweptSpline : public idSurface { +public: + idSurface_SweptSpline(); + ~idSurface_SweptSpline(); + + void SetSpline( idCurve_Spline *spline ); + void SetSweptSpline( idCurve_Spline *sweptSpline ); + void SetSweptCircle( const float radius ); + + void Tessellate( const int splineSubdivisions, const int sweptSplineSubdivisions ); + + void Clear(); + +protected: + idCurve_Spline *spline; + idCurve_Spline *sweptSpline; + + void GetFrame( const idMat3 &previousFrame, const idVec3 dir, idMat3 &newFrame ); +}; + +/* +==================== +idSurface_SweptSpline::idSurface_SweptSpline +==================== +*/ +ID_INLINE idSurface_SweptSpline::idSurface_SweptSpline() { + spline = NULL; + sweptSpline = NULL; +} + +/* +==================== +idSurface_SweptSpline::~idSurface_SweptSpline +==================== +*/ +ID_INLINE idSurface_SweptSpline::~idSurface_SweptSpline() { + delete spline; + delete sweptSpline; +} + +/* +==================== +idSurface_SweptSpline::Clear +==================== +*/ +ID_INLINE void idSurface_SweptSpline::Clear() { + idSurface::Clear(); + delete spline; + spline = NULL; + delete sweptSpline; + sweptSpline = NULL; +} + +#endif /* !__SURFACE_SWEPTSPLINE_H__ */ diff --git a/neo/idlib/geometry/TraceModel.cpp b/neo/idlib/geometry/TraceModel.cpp new file mode 100644 index 00000000..7a799c6b --- /dev/null +++ b/neo/idlib/geometry/TraceModel.cpp @@ -0,0 +1,1492 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "TraceModel.h" + +/* +============ +idTraceModel::SetupBox +============ +*/ +void idTraceModel::SetupBox( const idBounds &boxBounds ) { + int i; + + if ( type != TRM_BOX ) { + InitBox(); + } + // offset to center + offset = ( boxBounds[0] + boxBounds[1] ) * 0.5f; + // set box vertices + for ( i = 0; i < 8; i++ ) { + verts[i][0] = boxBounds[(i^(i>>1))&1][0]; + verts[i][1] = boxBounds[(i>>1)&1][1]; + verts[i][2] = boxBounds[(i>>2)&1][2]; + } + // set polygon plane distances + polys[0].dist = -boxBounds[0][2]; + polys[1].dist = boxBounds[1][2]; + polys[2].dist = -boxBounds[0][1]; + polys[3].dist = boxBounds[1][0]; + polys[4].dist = boxBounds[1][1]; + polys[5].dist = -boxBounds[0][0]; + // set polygon bounds + for ( i = 0; i < 6; i++ ) { + polys[i].bounds = boxBounds; + } + polys[0].bounds[1][2] = boxBounds[0][2]; + polys[1].bounds[0][2] = boxBounds[1][2]; + polys[2].bounds[1][1] = boxBounds[0][1]; + polys[3].bounds[0][0] = boxBounds[1][0]; + polys[4].bounds[0][1] = boxBounds[1][1]; + polys[5].bounds[1][0] = boxBounds[0][0]; + + bounds = boxBounds; +} + +/* +============ +idTraceModel::SetupBox + + The origin is placed at the center of the cube. +============ +*/ +void idTraceModel::SetupBox( const float size ) { + idBounds boxBounds; + float halfSize; + + halfSize = size * 0.5f; + boxBounds[0].Set( -halfSize, -halfSize, -halfSize ); + boxBounds[1].Set( halfSize, halfSize, halfSize ); + SetupBox( boxBounds ); +} + +/* +============ +idTraceModel::InitBox + + Initialize size independent box. +============ +*/ +void idTraceModel::InitBox() { + int i; + + type = TRM_BOX; + numVerts = 8; + numEdges = 12; + numPolys = 6; + + // set box edges + for ( i = 0; i < 4; i++ ) { + edges[ i + 1 ].v[0] = i; + edges[ i + 1 ].v[1] = (i + 1) & 3; + edges[ i + 5 ].v[0] = 4 + i; + edges[ i + 5 ].v[1] = 4 + ((i + 1) & 3); + edges[ i + 9 ].v[0] = i; + edges[ i + 9 ].v[1] = 4 + i; + } + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 4; + polys[0].edges[0] = -4; + polys[0].edges[1] = -3; + polys[0].edges[2] = -2; + polys[0].edges[3] = -1; + polys[0].normal.Set( 0.0f, 0.0f, -1.0f ); + + polys[1].numEdges = 4; + polys[1].edges[0] = 5; + polys[1].edges[1] = 6; + polys[1].edges[2] = 7; + polys[1].edges[3] = 8; + polys[1].normal.Set( 0.0f, 0.0f, 1.0f ); + + polys[2].numEdges = 4; + polys[2].edges[0] = 1; + polys[2].edges[1] = 10; + polys[2].edges[2] = -5; + polys[2].edges[3] = -9; + polys[2].normal.Set( 0.0f, -1.0f, 0.0f ); + + polys[3].numEdges = 4; + polys[3].edges[0] = 2; + polys[3].edges[1] = 11; + polys[3].edges[2] = -6; + polys[3].edges[3] = -10; + polys[3].normal.Set( 1.0f, 0.0f, 0.0f ); + + polys[4].numEdges = 4; + polys[4].edges[0] = 3; + polys[4].edges[1] = 12; + polys[4].edges[2] = -7; + polys[4].edges[3] = -11; + polys[4].normal.Set( 0.0f, 1.0f, 0.0f ); + + polys[5].numEdges = 4; + polys[5].edges[0] = 4; + polys[5].edges[1] = 9; + polys[5].edges[2] = -8; + polys[5].edges[3] = -12; + polys[5].normal.Set( -1.0f, 0.0f, 0.0f ); + + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupOctahedron +============ +*/ +void idTraceModel::SetupOctahedron( const idBounds &octBounds ) { + int i, e0, e1, v0, v1, v2; + idVec3 v; + + if ( type != TRM_OCTAHEDRON ) { + InitOctahedron(); + } + + offset = ( octBounds[0] + octBounds[1] ) * 0.5f; + v[0] = octBounds[1][0] - offset[0]; + v[1] = octBounds[1][1] - offset[1]; + v[2] = octBounds[1][2] - offset[2]; + + // set vertices + verts[0].Set( offset.x + v[0], offset.y, offset.z ); + verts[1].Set( offset.x - v[0], offset.y, offset.z ); + verts[2].Set( offset.x, offset.y + v[1], offset.z ); + verts[3].Set( offset.x, offset.y - v[1], offset.z ); + verts[4].Set( offset.x, offset.y, offset.z + v[2] ); + verts[5].Set( offset.x, offset.y, offset.z - v[2] ); + + // set polygons + for ( i = 0; i < numPolys; i++ ) { + e0 = polys[i].edges[0]; + e1 = polys[i].edges[1]; + v0 = edges[abs(e0)].v[INT32_SIGNBITSET(e0)]; + v1 = edges[abs(e0)].v[INT32_SIGNBITNOTSET(e0)]; + v2 = edges[abs(e1)].v[INT32_SIGNBITNOTSET(e1)]; + // polygon plane + polys[i].normal = ( verts[v1] - verts[v0] ).Cross( verts[v2] - verts[v0] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[v0]; + // polygon bounds + polys[i].bounds[0] = polys[i].bounds[1] = verts[v0]; + polys[i].bounds.AddPoint( verts[v1] ); + polys[i].bounds.AddPoint( verts[v2] ); + } + + // trm bounds + bounds = octBounds; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupOctahedron + + The origin is placed at the center of the octahedron. +============ +*/ +void idTraceModel::SetupOctahedron( const float size ) { + idBounds octBounds; + float halfSize; + + halfSize = size * 0.5f; + octBounds[0].Set( -halfSize, -halfSize, -halfSize ); + octBounds[1].Set( halfSize, halfSize, halfSize ); + SetupOctahedron( octBounds ); +} + +/* +============ +idTraceModel::InitOctahedron + + Initialize size independent octahedron. +============ +*/ +void idTraceModel::InitOctahedron() { + + type = TRM_OCTAHEDRON; + numVerts = 6; + numEdges = 12; + numPolys = 8; + + // set edges + edges[ 1].v[0] = 4; edges[ 1].v[1] = 0; + edges[ 2].v[0] = 0; edges[ 2].v[1] = 2; + edges[ 3].v[0] = 2; edges[ 3].v[1] = 4; + edges[ 4].v[0] = 2; edges[ 4].v[1] = 1; + edges[ 5].v[0] = 1; edges[ 5].v[1] = 4; + edges[ 6].v[0] = 1; edges[ 6].v[1] = 3; + edges[ 7].v[0] = 3; edges[ 7].v[1] = 4; + edges[ 8].v[0] = 3; edges[ 8].v[1] = 0; + edges[ 9].v[0] = 5; edges[ 9].v[1] = 2; + edges[10].v[0] = 0; edges[10].v[1] = 5; + edges[11].v[0] = 5; edges[11].v[1] = 1; + edges[12].v[0] = 5; edges[12].v[1] = 3; + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 3; + polys[0].edges[0] = 1; + polys[0].edges[1] = 2; + polys[0].edges[2] = 3; + + polys[1].numEdges = 3; + polys[1].edges[0] = -3; + polys[1].edges[1] = 4; + polys[1].edges[2] = 5; + + polys[2].numEdges = 3; + polys[2].edges[0] = -5; + polys[2].edges[1] = 6; + polys[2].edges[2] = 7; + + polys[3].numEdges = 3; + polys[3].edges[0] = -7; + polys[3].edges[1] = 8; + polys[3].edges[2] = -1; + + polys[4].numEdges = 3; + polys[4].edges[0] = 9; + polys[4].edges[1] = -2; + polys[4].edges[2] = 10; + + polys[5].numEdges = 3; + polys[5].edges[0] = 11; + polys[5].edges[1] = -4; + polys[5].edges[2] = -9; + + polys[6].numEdges = 3; + polys[6].edges[0] = 12; + polys[6].edges[1] = -6; + polys[6].edges[2] = -11; + + polys[7].numEdges = 3; + polys[7].edges[0] = -10; + polys[7].edges[1] = -8; + polys[7].edges[2] = -12; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupDodecahedron +============ +*/ +void idTraceModel::SetupDodecahedron( const idBounds &dodBounds ) { + int i, e0, e1, e2, e3, v0, v1, v2, v3, v4; + float s, d; + idVec3 a, b, c; + + if ( type != TRM_DODECAHEDRON ) { + InitDodecahedron(); + } + + a[0] = a[1] = a[2] = 0.5773502691896257f; // 1.0f / ( 3.0f ) ^ 0.5f; + b[0] = b[1] = b[2] = 0.3568220897730899f; // ( ( 3.0f - ( 5.0f ) ^ 0.5f ) / 6.0f ) ^ 0.5f; + c[0] = c[1] = c[2] = 0.9341723589627156f; // ( ( 3.0f + ( 5.0f ) ^ 0.5f ) / 6.0f ) ^ 0.5f; + d = 0.5f / c[0]; + s = ( dodBounds[1][0] - dodBounds[0][0] ) * d; + a[0] *= s; + b[0] *= s; + c[0] *= s; + s = ( dodBounds[1][1] - dodBounds[0][1] ) * d; + a[1] *= s; + b[1] *= s; + c[1] *= s; + s = ( dodBounds[1][2] - dodBounds[0][2] ) * d; + a[2] *= s; + b[2] *= s; + c[2] *= s; + + offset = ( dodBounds[0] + dodBounds[1] ) * 0.5f; + + // set vertices + verts[ 0].Set( offset.x + a[0], offset.y + a[1], offset.z + a[2] ); + verts[ 1].Set( offset.x + a[0], offset.y + a[1], offset.z - a[2] ); + verts[ 2].Set( offset.x + a[0], offset.y - a[1], offset.z + a[2] ); + verts[ 3].Set( offset.x + a[0], offset.y - a[1], offset.z - a[2] ); + verts[ 4].Set( offset.x - a[0], offset.y + a[1], offset.z + a[2] ); + verts[ 5].Set( offset.x - a[0], offset.y + a[1], offset.z - a[2] ); + verts[ 6].Set( offset.x - a[0], offset.y - a[1], offset.z + a[2] ); + verts[ 7].Set( offset.x - a[0], offset.y - a[1], offset.z - a[2] ); + verts[ 8].Set( offset.x + b[0], offset.y + c[1], offset.z ); + verts[ 9].Set( offset.x - b[0], offset.y + c[1], offset.z ); + verts[10].Set( offset.x + b[0], offset.y - c[1], offset.z ); + verts[11].Set( offset.x - b[0], offset.y - c[1], offset.z ); + verts[12].Set( offset.x + c[0], offset.y , offset.z + b[2] ); + verts[13].Set( offset.x + c[0], offset.y , offset.z - b[2] ); + verts[14].Set( offset.x - c[0], offset.y , offset.z + b[2] ); + verts[15].Set( offset.x - c[0], offset.y , offset.z - b[2] ); + verts[16].Set( offset.x , offset.y + b[1], offset.z + c[2] ); + verts[17].Set( offset.x , offset.y - b[1], offset.z + c[2] ); + verts[18].Set( offset.x , offset.y + b[1], offset.z - c[2] ); + verts[19].Set( offset.x , offset.y - b[1], offset.z - c[2] ); + + // set polygons + for ( i = 0; i < numPolys; i++ ) { + e0 = polys[i].edges[0]; + e1 = polys[i].edges[1]; + e2 = polys[i].edges[2]; + e3 = polys[i].edges[3]; + v0 = edges[abs(e0)].v[INT32_SIGNBITSET(e0)]; + v1 = edges[abs(e0)].v[INT32_SIGNBITNOTSET(e0)]; + v2 = edges[abs(e1)].v[INT32_SIGNBITNOTSET(e1)]; + v3 = edges[abs(e2)].v[INT32_SIGNBITNOTSET(e2)]; + v4 = edges[abs(e3)].v[INT32_SIGNBITNOTSET(e3)]; + // polygon plane + polys[i].normal = ( verts[v1] - verts[v0] ).Cross( verts[v2] - verts[v0] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[v0]; + // polygon bounds + polys[i].bounds[0] = polys[i].bounds[1] = verts[v0]; + polys[i].bounds.AddPoint( verts[v1] ); + polys[i].bounds.AddPoint( verts[v2] ); + polys[i].bounds.AddPoint( verts[v3] ); + polys[i].bounds.AddPoint( verts[v4] ); + } + + // trm bounds + bounds = dodBounds; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupDodecahedron + + The origin is placed at the center of the octahedron. +============ +*/ +void idTraceModel::SetupDodecahedron( const float size ) { + idBounds dodBounds; + float halfSize; + + halfSize = size * 0.5f; + dodBounds[0].Set( -halfSize, -halfSize, -halfSize ); + dodBounds[1].Set( halfSize, halfSize, halfSize ); + SetupDodecahedron( dodBounds ); +} + +/* +============ +idTraceModel::InitDodecahedron + + Initialize size independent dodecahedron. +============ +*/ +void idTraceModel::InitDodecahedron() { + + type = TRM_DODECAHEDRON; + numVerts = 20; + numEdges = 30; + numPolys = 12; + + // set edges + edges[ 1].v[0] = 0; edges[ 1].v[1] = 8; + edges[ 2].v[0] = 8; edges[ 2].v[1] = 9; + edges[ 3].v[0] = 9; edges[ 3].v[1] = 4; + edges[ 4].v[0] = 4; edges[ 4].v[1] = 16; + edges[ 5].v[0] = 16; edges[ 5].v[1] = 0; + edges[ 6].v[0] = 16; edges[ 6].v[1] = 17; + edges[ 7].v[0] = 17; edges[ 7].v[1] = 2; + edges[ 8].v[0] = 2; edges[ 8].v[1] = 12; + edges[ 9].v[0] = 12; edges[ 9].v[1] = 0; + edges[10].v[0] = 2; edges[10].v[1] = 10; + edges[11].v[0] = 10; edges[11].v[1] = 3; + edges[12].v[0] = 3; edges[12].v[1] = 13; + edges[13].v[0] = 13; edges[13].v[1] = 12; + edges[14].v[0] = 9; edges[14].v[1] = 5; + edges[15].v[0] = 5; edges[15].v[1] = 15; + edges[16].v[0] = 15; edges[16].v[1] = 14; + edges[17].v[0] = 14; edges[17].v[1] = 4; + edges[18].v[0] = 3; edges[18].v[1] = 19; + edges[19].v[0] = 19; edges[19].v[1] = 18; + edges[20].v[0] = 18; edges[20].v[1] = 1; + edges[21].v[0] = 1; edges[21].v[1] = 13; + edges[22].v[0] = 7; edges[22].v[1] = 11; + edges[23].v[0] = 11; edges[23].v[1] = 6; + edges[24].v[0] = 6; edges[24].v[1] = 14; + edges[25].v[0] = 15; edges[25].v[1] = 7; + edges[26].v[0] = 1; edges[26].v[1] = 8; + edges[27].v[0] = 18; edges[27].v[1] = 5; + edges[28].v[0] = 6; edges[28].v[1] = 17; + edges[29].v[0] = 11; edges[29].v[1] = 10; + edges[30].v[0] = 19; edges[30].v[1] = 7; + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 5; + polys[0].edges[0] = 1; + polys[0].edges[1] = 2; + polys[0].edges[2] = 3; + polys[0].edges[3] = 4; + polys[0].edges[4] = 5; + + polys[1].numEdges = 5; + polys[1].edges[0] = -5; + polys[1].edges[1] = 6; + polys[1].edges[2] = 7; + polys[1].edges[3] = 8; + polys[1].edges[4] = 9; + + polys[2].numEdges = 5; + polys[2].edges[0] = -8; + polys[2].edges[1] = 10; + polys[2].edges[2] = 11; + polys[2].edges[3] = 12; + polys[2].edges[4] = 13; + + polys[3].numEdges = 5; + polys[3].edges[0] = 14; + polys[3].edges[1] = 15; + polys[3].edges[2] = 16; + polys[3].edges[3] = 17; + polys[3].edges[4] = -3; + + polys[4].numEdges = 5; + polys[4].edges[0] = 18; + polys[4].edges[1] = 19; + polys[4].edges[2] = 20; + polys[4].edges[3] = 21; + polys[4].edges[4] = -12; + + polys[5].numEdges = 5; + polys[5].edges[0] = 22; + polys[5].edges[1] = 23; + polys[5].edges[2] = 24; + polys[5].edges[3] = -16; + polys[5].edges[4] = 25; + + polys[6].numEdges = 5; + polys[6].edges[0] = -9; + polys[6].edges[1] = -13; + polys[6].edges[2] = -21; + polys[6].edges[3] = 26; + polys[6].edges[4] = -1; + + polys[7].numEdges = 5; + polys[7].edges[0] = -26; + polys[7].edges[1] = -20; + polys[7].edges[2] = 27; + polys[7].edges[3] = -14; + polys[7].edges[4] = -2; + + polys[8].numEdges = 5; + polys[8].edges[0] = -4; + polys[8].edges[1] = -17; + polys[8].edges[2] = -24; + polys[8].edges[3] = 28; + polys[8].edges[4] = -6; + + polys[9].numEdges = 5; + polys[9].edges[0] = -23; + polys[9].edges[1] = 29; + polys[9].edges[2] = -10; + polys[9].edges[3] = -7; + polys[9].edges[4] = -28; + + polys[10].numEdges = 5; + polys[10].edges[0] = -25; + polys[10].edges[1] = -15; + polys[10].edges[2] = -27; + polys[10].edges[3] = -19; + polys[10].edges[4] = 30; + + polys[11].numEdges = 5; + polys[11].edges[0] = -30; + polys[11].edges[1] = -18; + polys[11].edges[2] = -11; + polys[11].edges[3] = -29; + polys[11].edges[4] = -22; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupCylinder +============ +*/ +void idTraceModel::SetupCylinder( const idBounds &cylBounds, const int numSides ) { + int i, n, ii, n2; + float angle; + idVec3 halfSize; + + n = numSides; + if ( n < 3 ) { + n = 3; + } + if ( n * 2 > MAX_TRACEMODEL_VERTS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many vertices\n" ); + n = MAX_TRACEMODEL_VERTS / 2; + } + if ( n * 3 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many sides\n" ); + n = MAX_TRACEMODEL_EDGES / 3; + } + if ( n + 2 > MAX_TRACEMODEL_POLYS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many polygons\n" ); + n = MAX_TRACEMODEL_POLYS - 2; + } + + type = TRM_CYLINDER; + numVerts = n * 2; + numEdges = n * 3; + numPolys = n + 2; + offset = ( cylBounds[0] + cylBounds[1] ) * 0.5f; + halfSize = cylBounds[1] - offset; + for ( i = 0; i < n; i++ ) { + // verts + angle = idMath::TWO_PI * i / n; + verts[i].x = cos( angle ) * halfSize.x + offset.x; + verts[i].y = sin( angle ) * halfSize.y + offset.y; + verts[i].z = -halfSize.z + offset.z; + verts[n+i].x = verts[i].x; + verts[n+i].y = verts[i].y; + verts[n+i].z = halfSize.z + offset.z; + // edges + ii = i + 1; + n2 = n << 1; + edges[ii].v[0] = i; + edges[ii].v[1] = ii % n; + edges[n+ii].v[0] = edges[ii].v[0] + n; + edges[n+ii].v[1] = edges[ii].v[1] + n; + edges[n2+ii].v[0] = i; + edges[n2+ii].v[1] = n + i; + // vertical polygon edges + polys[i].numEdges = 4; + polys[i].edges[0] = ii; + polys[i].edges[1] = n2 + (ii % n) + 1; + polys[i].edges[2] = -(n + ii); + polys[i].edges[3] = -(n2 + ii); + // bottom and top polygon edges + polys[n].edges[i] = -(n - i); + polys[n+1].edges[i] = n + ii; + } + // bottom and top polygon numEdges + polys[n].numEdges = n; + polys[n+1].numEdges = n; + // polygons + for ( i = 0; i < n; i++ ) { + // vertical polygon plane + polys[i].normal = (verts[(i+1)%n] - verts[i]).Cross( verts[n+i] - verts[i] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[i]; + // vertical polygon bounds + polys[i].bounds.Clear(); + polys[i].bounds.AddPoint( verts[i] ); + polys[i].bounds.AddPoint( verts[(i+1)%n] ); + polys[i].bounds[0][2] = -halfSize.z + offset.z; + polys[i].bounds[1][2] = halfSize.z + offset.z; + } + // bottom and top polygon plane + polys[n].normal.Set( 0.0f, 0.0f, -1.0f ); + polys[n].dist = -cylBounds[0][2]; + polys[n+1].normal.Set( 0.0f, 0.0f, 1.0f ); + polys[n+1].dist = cylBounds[1][2]; + // trm bounds + bounds = cylBounds; + // bottom and top polygon bounds + polys[n].bounds = bounds; + polys[n].bounds[1][2] = bounds[0][2]; + polys[n+1].bounds = bounds; + polys[n+1].bounds[0][2] = bounds[1][2]; + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupCylinder + + The origin is placed at the center of the cylinder. +============ +*/ +void idTraceModel::SetupCylinder( const float height, const float width, const int numSides ) { + idBounds cylBounds; + float halfHeight, halfWidth; + + halfHeight = height * 0.5f; + halfWidth = width * 0.5f; + cylBounds[0].Set( -halfWidth, -halfWidth, -halfHeight ); + cylBounds[1].Set( halfWidth, halfWidth, halfHeight ); + SetupCylinder( cylBounds, numSides ); +} + +/* +============ +idTraceModel::SetupCone +============ +*/ +void idTraceModel::SetupCone( const idBounds &coneBounds, const int numSides ) { + int i, n, ii; + float angle; + idVec3 halfSize; + + n = numSides; + if ( n < 2 ) { + n = 3; + } + if ( n + 1 > MAX_TRACEMODEL_VERTS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many vertices\n" ); + n = MAX_TRACEMODEL_VERTS - 1; + } + if ( n * 2 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many edges\n" ); + n = MAX_TRACEMODEL_EDGES / 2; + } + if ( n + 1 > MAX_TRACEMODEL_POLYS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many polygons\n" ); + n = MAX_TRACEMODEL_POLYS - 1; + } + + type = TRM_CONE; + numVerts = n + 1; + numEdges = n * 2; + numPolys = n + 1; + offset = ( coneBounds[0] + coneBounds[1] ) * 0.5f; + halfSize = coneBounds[1] - offset; + verts[n].Set( 0.0f, 0.0f, halfSize.z + offset.z ); + for ( i = 0; i < n; i++ ) { + // verts + angle = idMath::TWO_PI * i / n; + verts[i].x = cos( angle ) * halfSize.x + offset.x; + verts[i].y = sin( angle ) * halfSize.y + offset.y; + verts[i].z = -halfSize.z + offset.z; + // edges + ii = i + 1; + edges[ii].v[0] = i; + edges[ii].v[1] = ii % n; + edges[n+ii].v[0] = i; + edges[n+ii].v[1] = n; + // vertical polygon edges + polys[i].numEdges = 3; + polys[i].edges[0] = ii; + polys[i].edges[1] = n + (ii % n) + 1; + polys[i].edges[2] = -(n + ii); + // bottom polygon edges + polys[n].edges[i] = -(n - i); + } + // bottom polygon numEdges + polys[n].numEdges = n; + + // polygons + for ( i = 0; i < n; i++ ) { + // polygon plane + polys[i].normal = (verts[(i+1)%n] - verts[i]).Cross( verts[n] - verts[i] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[i]; + // polygon bounds + polys[i].bounds.Clear(); + polys[i].bounds.AddPoint( verts[i] ); + polys[i].bounds.AddPoint( verts[(i+1)%n] ); + polys[i].bounds.AddPoint( verts[n] ); + } + // bottom polygon plane + polys[n].normal.Set( 0.0f, 0.0f, -1.0f ); + polys[n].dist = -coneBounds[0][2]; + // trm bounds + bounds = coneBounds; + // bottom polygon bounds + polys[n].bounds = bounds; + polys[n].bounds[1][2] = bounds[0][2]; + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupCone + + The origin is placed at the apex of the cone. +============ +*/ +void idTraceModel::SetupCone( const float height, const float width, const int numSides ) { + idBounds coneBounds; + float halfWidth; + + halfWidth = width * 0.5f; + coneBounds[0].Set( -halfWidth, -halfWidth, -height ); + coneBounds[1].Set( halfWidth, halfWidth, 0.0f ); + SetupCone( coneBounds, numSides ); +} + +/* +============ +idTraceModel::SetupBone + + The origin is placed at the center of the bone. +============ +*/ +void idTraceModel::SetupBone( const float length, const float width ) { + int i, j, edgeNum; + float halfLength = length * 0.5f; + + if ( type != TRM_BONE ) { + InitBone(); + } + // offset to center + offset.Set( 0.0f, 0.0f, 0.0f ); + // set vertices + verts[0].Set( 0.0f, 0.0f, -halfLength ); + verts[1].Set( 0.0f, width * -0.5f, 0.0f ); + verts[2].Set( width * 0.5f, width * 0.25f, 0.0f ); + verts[3].Set( width * -0.5f, width * 0.25f, 0.0f ); + verts[4].Set( 0.0f, 0.0f, halfLength ); + // set bounds + bounds[0].Set( width * -0.5f, width * -0.5f, -halfLength ); + bounds[1].Set( width * 0.5f, width * 0.25f, halfLength ); + // poly plane normals + polys[0].normal = ( verts[2] - verts[0] ).Cross( verts[1] - verts[0] ); + polys[0].normal.Normalize(); + polys[2].normal.Set( -polys[0].normal[0], polys[0].normal[1], polys[0].normal[2] ); + polys[3].normal.Set( polys[0].normal[0], polys[0].normal[1], -polys[0].normal[2] ); + polys[5].normal.Set( -polys[0].normal[0], polys[0].normal[1], -polys[0].normal[2] ); + polys[1].normal = (verts[3] - verts[0]).Cross(verts[2] - verts[0]); + polys[1].normal.Normalize(); + polys[4].normal.Set( polys[1].normal[0], polys[1].normal[1], -polys[1].normal[2] ); + // poly plane distances + for ( i = 0; i < 6; i++ ) { + polys[i].dist = polys[i].normal * verts[ edges[ abs(polys[i].edges[0]) ].v[0] ]; + polys[i].bounds.Clear(); + for ( j = 0; j < 3; j++ ) { + edgeNum = polys[i].edges[ j ]; + polys[i].bounds.AddPoint( verts[ edges[abs(edgeNum)].v[edgeNum < 0] ] ); + } + } + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::InitBone + + Initialize size independent bone. +============ +*/ +void idTraceModel::InitBone() { + int i; + + type = TRM_BONE; + numVerts = 5; + numEdges = 9; + numPolys = 6; + + // set bone edges + for ( i = 0; i < 3; i++ ) { + edges[ i + 1 ].v[0] = 0; + edges[ i + 1 ].v[1] = i + 1; + edges[ i + 4 ].v[0] = 1 + i; + edges[ i + 4 ].v[1] = 1 + ((i + 1) % 3); + edges[ i + 7 ].v[0] = i + 1; + edges[ i + 7 ].v[1] = 4; + } + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 3; + polys[0].edges[0] = 2; + polys[0].edges[1] = -4; + polys[0].edges[2] = -1; + + polys[1].numEdges = 3; + polys[1].edges[0] = 3; + polys[1].edges[1] = -5; + polys[1].edges[2] = -2; + + polys[2].numEdges = 3; + polys[2].edges[0] = 1; + polys[2].edges[1] = -6; + polys[2].edges[2] = -3; + + polys[3].numEdges = 3; + polys[3].edges[0] = 4; + polys[3].edges[1] = 8; + polys[3].edges[2] = -7; + + polys[4].numEdges = 3; + polys[4].edges[0] = 5; + polys[4].edges[1] = 9; + polys[4].edges[2] = -8; + + polys[5].numEdges = 3; + polys[5].edges[0] = 6; + polys[5].edges[1] = 7; + polys[5].edges[2] = -9; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupPolygon +============ +*/ +void idTraceModel::SetupPolygon( const idVec3 *v, const int count ) { + int i, j; + idVec3 mid; + + type = TRM_POLYGON; + numVerts = count; + // times three because we need to be able to turn the polygon into a volume + if ( numVerts * 3 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupPolygon: too many vertices\n" ); + numVerts = MAX_TRACEMODEL_EDGES / 3; + } + + numEdges = numVerts; + numPolys = 2; + // set polygon planes + polys[0].numEdges = numEdges; + polys[0].normal = ( v[1] - v[0] ).Cross( v[2] - v[0] ); + polys[0].normal.Normalize(); + polys[0].dist = polys[0].normal * v[0]; + polys[1].numEdges = numEdges; + polys[1].normal = -polys[0].normal; + polys[1].dist = -polys[0].dist; + // setup verts, edges and polygons + polys[0].bounds.Clear(); + mid = vec3_origin; + for ( i = 0, j = 1; i < numVerts; i++, j++ ) { + if ( j >= numVerts ) { + j = 0; + } + verts[i] = v[i]; + edges[i+1].v[0] = i; + edges[i+1].v[1] = j; + edges[i+1].normal = polys[0].normal.Cross( v[i] - v[j] ); + edges[i+1].normal.Normalize(); + polys[0].edges[i] = i + 1; + polys[1].edges[i] = -(numVerts - i); + polys[0].bounds.AddPoint( verts[i] ); + mid += v[i]; + } + polys[1].bounds = polys[0].bounds; + // offset to center + offset = mid * (1.0f / numVerts); + // total bounds + bounds = polys[0].bounds; + // considered non convex because the model has no volume + isConvex = false; +} + +/* +============ +idTraceModel::SetupPolygon +============ +*/ +void idTraceModel::SetupPolygon( const idWinding &w ) { + int i; + idVec3 *verts; + + verts = (idVec3 *) _alloca16( w.GetNumPoints() * sizeof( idVec3 ) ); + for ( i = 0; i < w.GetNumPoints(); i++ ) { + verts[i] = w[i].ToVec3(); + } + SetupPolygon( verts, w.GetNumPoints() ); +} + +/* +============ +idTraceModel::VolumeFromPolygon +============ +*/ +void idTraceModel::VolumeFromPolygon( idTraceModel &trm, float thickness ) const { + int i; + + trm = *this; + trm.type = TRM_POLYGONVOLUME; + trm.numVerts = numVerts * 2; + trm.numEdges = numEdges * 3; + trm.numPolys = numEdges + 2; + for ( i = 0; i < numEdges; i++ ) { + trm.verts[ numVerts + i ] = verts[i] - thickness * polys[0].normal; + trm.edges[ numEdges + i + 1 ].v[0] = numVerts + i; + trm.edges[ numEdges + i + 1 ].v[1] = numVerts + (i+1) % numVerts; + trm.edges[ numEdges * 2 + i + 1 ].v[0] = i; + trm.edges[ numEdges * 2 + i + 1 ].v[1] = numVerts + i; + trm.polys[1].edges[i] = -(numEdges + i + 1); + trm.polys[2+i].numEdges = 4; + trm.polys[2+i].edges[0] = -(i + 1); + trm.polys[2+i].edges[1] = numEdges*2 + i + 1; + trm.polys[2+i].edges[2] = numEdges + i + 1; + trm.polys[2+i].edges[3] = -(numEdges*2 + (i+1) % numEdges + 1); + trm.polys[2+i].normal = (verts[(i + 1) % numVerts] - verts[i]).Cross( polys[0].normal ); + trm.polys[2+i].normal.Normalize(); + trm.polys[2+i].dist = trm.polys[2+i].normal * verts[i]; + } + trm.polys[1].dist = trm.polys[1].normal * trm.verts[ numEdges ]; + + trm.GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::GenerateEdgeNormals +============ +*/ +#define SHARP_EDGE_DOT -0.7f + +int idTraceModel::GenerateEdgeNormals() { + int i, j, edgeNum, numSharpEdges; + float dot; + idVec3 dir; + traceModelPoly_t *poly; + traceModelEdge_t *edge; + + for ( i = 0; i <= numEdges; i++ ) { + edges[i].normal.Zero(); + } + + numSharpEdges = 0; + for ( i = 0; i < numPolys; i++ ) { + poly = polys + i; + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = edges + abs( edgeNum ); + if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) { + edge->normal = poly->normal; + } + else { + dot = edge->normal * poly->normal; + // if the two planes make a very sharp edge + if ( dot < SHARP_EDGE_DOT ) { + // max length normal pointing outside both polygons + dir = verts[ edge->v[edgeNum > 0]] - verts[ edge->v[edgeNum < 0]]; + edge->normal = edge->normal.Cross( dir ) + poly->normal.Cross( -dir ); + edge->normal *= ( 0.5f / ( 0.5f + 0.5f * SHARP_EDGE_DOT ) ) / edge->normal.Length(); + numSharpEdges++; + } + else { + edge->normal = ( 0.5f / ( 0.5f + 0.5f * dot ) ) * ( edge->normal + poly->normal ); + } + } + } + } + return numSharpEdges; +} + +/* +============ +idTraceModel::Translate +============ +*/ +void idTraceModel::Translate( const idVec3 &translation ) { + int i; + + for ( i = 0; i < numVerts; i++ ) { + verts[i] += translation; + } + for ( i = 0; i < numPolys; i++ ) { + polys[i].dist += polys[i].normal * translation; + polys[i].bounds[0] += translation; + polys[i].bounds[1] += translation; + } + offset += translation; + bounds[0] += translation; + bounds[1] += translation; +} + +/* +============ +idTraceModel::Rotate +============ +*/ +void idTraceModel::Rotate( const idMat3 &rotation ) { + int i, j, edgeNum; + + for ( i = 0; i < numVerts; i++ ) { + verts[i] *= rotation; + } + + bounds.Clear(); + for ( i = 0; i < numPolys; i++ ) { + polys[i].normal *= rotation; + polys[i].bounds.Clear(); + edgeNum = 0; + for ( j = 0; j < polys[i].numEdges; j++ ) { + edgeNum = polys[i].edges[j]; + polys[i].bounds.AddPoint( verts[edges[abs(edgeNum)].v[INT32_SIGNBITSET(edgeNum)]] ); + } + polys[i].dist = polys[i].normal * verts[edges[abs(edgeNum)].v[INT32_SIGNBITSET(edgeNum)]]; + bounds += polys[i].bounds; + } + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::Shrink +============ +*/ +void idTraceModel::Shrink( const float m ) { + int i, j, edgeNum; + traceModelEdge_t *edge; + idVec3 dir; + + if ( type == TRM_POLYGON ) { + for ( i = 0; i < numEdges; i++ ) { + edgeNum = polys[0].edges[i]; + edge = &edges[abs(edgeNum)]; + dir = verts[ edge->v[ INT32_SIGNBITSET(edgeNum) ] ] - verts[ edge->v[ INT32_SIGNBITNOTSET(edgeNum) ] ]; + if ( dir.Normalize() < 2.0f * m ) { + continue; + } + dir *= m; + verts[ edge->v[ 0 ] ] -= dir; + verts[ edge->v[ 1 ] ] += dir; + } + return; + } + + for ( i = 0; i < numPolys; i++ ) { + polys[i].dist -= m; + + for ( j = 0; j < polys[i].numEdges; j++ ) { + edgeNum = polys[i].edges[j]; + edge = &edges[abs(edgeNum)]; + verts[ edge->v[ INT32_SIGNBITSET(edgeNum) ] ] -= polys[i].normal * m; + } + } +} + +/* +============ +idTraceModel::Compare +============ +*/ +bool idTraceModel::Compare( const idTraceModel &trm ) const { + int i; + + if ( type != trm.type || numVerts != trm.numVerts || + numEdges != trm.numEdges || numPolys != trm.numPolys ) { + return false; + } + if ( bounds != trm.bounds || offset != trm.offset ) { + return false; + } + + switch( type ) { + case TRM_INVALID: + case TRM_BOX: + case TRM_OCTAHEDRON: + case TRM_DODECAHEDRON: + case TRM_CYLINDER: + case TRM_CONE: + break; + case TRM_BONE: + case TRM_POLYGON: + case TRM_POLYGONVOLUME: + case TRM_CUSTOM: + for ( i = 0; i < trm.numVerts; i++ ) { + if ( verts[i] != trm.verts[i] ) { + return false; + } + } + break; + } + return true; +} + +/* +============ +idTraceModel::GetPolygonArea +============ +*/ +float idTraceModel::GetPolygonArea( int polyNum ) const { + int i; + idVec3 base, v1, v2, cross; + float total; + const traceModelPoly_t *poly; + + if ( polyNum < 0 || polyNum >= numPolys ) { + return 0.0f; + } + poly = &polys[polyNum]; + total = 0.0f; + base = verts[ edges[ abs(poly->edges[0]) ].v[ INT32_SIGNBITSET( poly->edges[0] ) ] ]; + for ( i = 0; i < poly->numEdges; i++ ) { + v1 = verts[ edges[ abs(poly->edges[i]) ].v[ INT32_SIGNBITSET( poly->edges[i] ) ] ] - base; + v2 = verts[ edges[ abs(poly->edges[i]) ].v[ INT32_SIGNBITNOTSET( poly->edges[i] ) ] ] - base; + cross = v1.Cross( v2 ); + total += cross.Length(); + } + return total * 0.5f; +} + +/* +============ +idTraceModel::GetOrderedSilhouetteEdges +============ +*/ +int idTraceModel::GetOrderedSilhouetteEdges( const int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1], int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum, numSilEdges, nextSilVert; + int unsortedSilEdges[MAX_TRACEMODEL_EDGES]; + + numSilEdges = 0; + for ( i = 1; i <= numEdges; i++ ) { + if ( edgeIsSilEdge[i] ) { + unsortedSilEdges[numSilEdges++] = i; + } + } + + silEdges[0] = unsortedSilEdges[0]; + unsortedSilEdges[0] = -1; + nextSilVert = edges[silEdges[0]].v[0]; + for ( i = 1; i < numSilEdges; i++ ) { + for ( j = 1; j < numSilEdges; j++ ) { + edgeNum = unsortedSilEdges[j]; + if ( edgeNum >= 0 ) { + if ( edges[edgeNum].v[0] == nextSilVert ) { + nextSilVert = edges[edgeNum].v[1]; + silEdges[i] = edgeNum; + break; + } + if ( edges[edgeNum].v[1] == nextSilVert ) { + nextSilVert = edges[edgeNum].v[0]; + silEdges[i] = -edgeNum; + break; + } + } + } + if ( j >= numSilEdges ) { + silEdges[i] = 1; // shouldn't happen + } + unsortedSilEdges[j] = -1; + } + return numSilEdges; +} + +/* +============ +idTraceModel::GetProjectionSilhouetteEdges +============ +*/ +int idTraceModel::GetProjectionSilhouetteEdges( const idVec3 &projectionOrigin, int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum; + int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1]; + const traceModelPoly_t *poly; + idVec3 dir; + + memset( edgeIsSilEdge, 0, sizeof( edgeIsSilEdge ) ); + + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + edgeNum = poly->edges[0]; + dir = verts[ edges[abs(edgeNum)].v[ INT32_SIGNBITSET(edgeNum) ] ] - projectionOrigin; + if ( dir * poly->normal < 0.0f ) { + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edgeIsSilEdge[abs(edgeNum)] ^= 1; + } + } + } + + return GetOrderedSilhouetteEdges( edgeIsSilEdge, silEdges ); +} + +/* +============ +idTraceModel::GetParallelProjectionSilhouetteEdges +============ +*/ +int idTraceModel::GetParallelProjectionSilhouetteEdges( const idVec3 &projectionDir, int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum; + int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1]; + const traceModelPoly_t *poly; + + memset( edgeIsSilEdge, 0, sizeof( edgeIsSilEdge ) ); + + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + if ( projectionDir * poly->normal < 0.0f ) { + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edgeIsSilEdge[abs(edgeNum)] ^= 1; + } + } + } + + return GetOrderedSilhouetteEdges( edgeIsSilEdge, silEdges ); +} + + +/* + + credits to Brian Mirtich for his paper "Fast and Accurate Computation of Polyhedral Mass Properties" + +*/ + +typedef struct projectionIntegrals_s { + float P1; + float Pa, Pb; + float Paa, Pab, Pbb; + float Paaa, Paab, Pabb, Pbbb; +} projectionIntegrals_t; + +/* +============ +idTraceModel::ProjectionIntegrals +============ +*/ +void idTraceModel::ProjectionIntegrals( int polyNum, int a, int b, struct projectionIntegrals_s &integrals ) const { + const traceModelPoly_t *poly; + int i, edgeNum; + idVec3 v1, v2; + float a0, a1, da; + float b0, b1, db; + float a0_2, a0_3, a0_4, b0_2, b0_3, b0_4; + float a1_2, a1_3, b1_2, b1_3; + float C1, Ca, Caa, Caaa, Cb, Cbb, Cbbb; + float Cab, Kab, Caab, Kaab, Cabb, Kabb; + + memset(&integrals, 0, sizeof(projectionIntegrals_t)); + poly = &polys[polyNum]; + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + v1 = verts[ edges[ abs(edgeNum) ].v[ edgeNum < 0 ] ]; + v2 = verts[ edges[ abs(edgeNum) ].v[ edgeNum > 0 ] ]; + a0 = v1[a]; + b0 = v1[b]; + a1 = v2[a]; + b1 = v2[b]; + da = a1 - a0; + db = b1 - b0; + a0_2 = a0 * a0; + a0_3 = a0_2 * a0; + a0_4 = a0_3 * a0; + b0_2 = b0 * b0; + b0_3 = b0_2 * b0; + b0_4 = b0_3 * b0; + a1_2 = a1 * a1; + a1_3 = a1_2 * a1; + b1_2 = b1 * b1; + b1_3 = b1_2 * b1; + + C1 = a1 + a0; + Ca = a1 * C1 + a0_2; + Caa = a1 * Ca + a0_3; + Caaa = a1 * Caa + a0_4; + Cb = b1 * (b1 + b0) + b0_2; + Cbb = b1 * Cb + b0_3; + Cbbb = b1 * Cbb + b0_4; + Cab = 3 * a1_2 + 2 * a1 * a0 + a0_2; + Kab = a1_2 + 2 * a1 * a0 + 3 * a0_2; + Caab = a0 * Cab + 4 * a1_3; + Kaab = a1 * Kab + 4 * a0_3; + Cabb = 4 * b1_3 + 3 * b1_2 * b0 + 2 * b1 * b0_2 + b0_3; + Kabb = b1_3 + 2 * b1_2 * b0 + 3 * b1 * b0_2 + 4 * b0_3; + + integrals.P1 += db * C1; + integrals.Pa += db * Ca; + integrals.Paa += db * Caa; + integrals.Paaa += db * Caaa; + integrals.Pb += da * Cb; + integrals.Pbb += da * Cbb; + integrals.Pbbb += da * Cbbb; + integrals.Pab += db * (b1 * Cab + b0 * Kab); + integrals.Paab += db * (b1 * Caab + b0 * Kaab); + integrals.Pabb += da * (a1 * Cabb + a0 * Kabb); + } + + integrals.P1 *= (1.0f / 2.0f); + integrals.Pa *= (1.0f / 6.0f); + integrals.Paa *= (1.0f / 12.0f); + integrals.Paaa *= (1.0f / 20.0f); + integrals.Pb *= (1.0f / -6.0f); + integrals.Pbb *= (1.0f / -12.0f); + integrals.Pbbb *= (1.0f / -20.0f); + integrals.Pab *= (1.0f / 24.0f); + integrals.Paab *= (1.0f / 60.0f); + integrals.Pabb *= (1.0f / -60.0f); +} + +typedef struct polygonIntegrals_s { + float Fa, Fb, Fc; + float Faa, Fbb, Fcc; + float Faaa, Fbbb, Fccc; + float Faab, Fbbc, Fcca; +} polygonIntegrals_t; + +/* +============ +idTraceModel::PolygonIntegrals +============ +*/ +void idTraceModel::PolygonIntegrals( int polyNum, int a, int b, int c, struct polygonIntegrals_s &integrals ) const { + projectionIntegrals_t pi; + idVec3 n; + float w; + float k1, k2, k3, k4; + + ProjectionIntegrals( polyNum, a, b, pi ); + + n = polys[polyNum].normal; + w = -polys[polyNum].dist; + k1 = 1 / n[c]; + k2 = k1 * k1; + k3 = k2 * k1; + k4 = k3 * k1; + + integrals.Fa = k1 * pi.Pa; + integrals.Fb = k1 * pi.Pb; + integrals.Fc = -k2 * (n[a] * pi.Pa + n[b] * pi.Pb + w * pi.P1); + + integrals.Faa = k1 * pi.Paa; + integrals.Fbb = k1 * pi.Pbb; + integrals.Fcc = k3 * (Square(n[a]) * pi.Paa + 2 * n[a] * n[b] * pi.Pab + Square(n[b]) * pi.Pbb + + w * (2 * (n[a] * pi.Pa + n[b] * pi.Pb) + w * pi.P1)); + + integrals.Faaa = k1 * pi.Paaa; + integrals.Fbbb = k1 * pi.Pbbb; + integrals.Fccc = -k4 * (Cube(n[a]) * pi.Paaa + 3 * Square(n[a]) * n[b] * pi.Paab + + 3 * n[a] * Square(n[b]) * pi.Pabb + Cube(n[b]) * pi.Pbbb + + 3 * w * (Square(n[a]) * pi.Paa + 2 * n[a] * n[b] * pi.Pab + Square(n[b]) * pi.Pbb) + + w * w * (3 * (n[a] * pi.Pa + n[b] * pi.Pb) + w * pi.P1)); + + integrals.Faab = k1 * pi.Paab; + integrals.Fbbc = -k2 * (n[a] * pi.Pabb + n[b] * pi.Pbbb + w * pi.Pbb); + integrals.Fcca = k3 * (Square(n[a]) * pi.Paaa + 2 * n[a] * n[b] * pi.Paab + Square(n[b]) * pi.Pabb + + w * (2 * (n[a] * pi.Paa + n[b] * pi.Pab) + w * pi.Pa)); +} + +typedef struct volumeIntegrals_s { + float T0; + idVec3 T1; + idVec3 T2; + idVec3 TP; +} volumeIntegrals_t; + +/* +============ +idTraceModel::VolumeIntegrals +============ +*/ +void idTraceModel::VolumeIntegrals( struct volumeIntegrals_s &integrals ) const { + const traceModelPoly_t *poly; + polygonIntegrals_t pi; + int i, a, b, c; + float nx, ny, nz; + + memset( &integrals, 0, sizeof(volumeIntegrals_t) ); + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + nx = idMath::Fabs( poly->normal[0] ); + ny = idMath::Fabs( poly->normal[1] ); + nz = idMath::Fabs( poly->normal[2] ); + if ( nx > ny && nx > nz ) { + c = 0; + } + else { + c = (ny > nz) ? 1 : 2; + } + a = (c + 1) % 3; + b = (a + 1) % 3; + + PolygonIntegrals( i, a, b, c, pi ); + + integrals.T0 += poly->normal[0] * ((a == 0) ? pi.Fa : ((b == 0) ? pi.Fb : pi.Fc)); + + integrals.T1[a] += poly->normal[a] * pi.Faa; + integrals.T1[b] += poly->normal[b] * pi.Fbb; + integrals.T1[c] += poly->normal[c] * pi.Fcc; + integrals.T2[a] += poly->normal[a] * pi.Faaa; + integrals.T2[b] += poly->normal[b] * pi.Fbbb; + integrals.T2[c] += poly->normal[c] * pi.Fccc; + integrals.TP[a] += poly->normal[a] * pi.Faab; + integrals.TP[b] += poly->normal[b] * pi.Fbbc; + integrals.TP[c] += poly->normal[c] * pi.Fcca; + } + + integrals.T1 *= 0.5f; + integrals.T2 *= (1.0f / 3.0f); + integrals.TP *= 0.5f; +} + +/* +============ +idTraceModel::GetMassProperties +============ +*/ +void idTraceModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + volumeIntegrals_t integrals; + + // if polygon trace model + if ( type == TRM_POLYGON ) { + idTraceModel trm; + + VolumeFromPolygon( trm, 1.0f ); + trm.GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + return; + } + + VolumeIntegrals( integrals ); + + // if no volume + if ( integrals.T0 == 0.0f ) { + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + return; + } + + // mass of model + mass = density * integrals.T0; + // center of mass + centerOfMass = integrals.T1 / integrals.T0; + // compute inertia tensor + inertiaTensor[0][0] = density * (integrals.T2[1] + integrals.T2[2]); + inertiaTensor[1][1] = density * (integrals.T2[2] + integrals.T2[0]); + inertiaTensor[2][2] = density * (integrals.T2[0] + integrals.T2[1]); + inertiaTensor[0][1] = inertiaTensor[1][0] = - density * integrals.TP[0]; + inertiaTensor[1][2] = inertiaTensor[2][1] = - density * integrals.TP[1]; + inertiaTensor[2][0] = inertiaTensor[0][2] = - density * integrals.TP[2]; + // translate inertia tensor to center of mass + inertiaTensor[0][0] -= mass * (centerOfMass[1]*centerOfMass[1] + centerOfMass[2]*centerOfMass[2]); + inertiaTensor[1][1] -= mass * (centerOfMass[2]*centerOfMass[2] + centerOfMass[0]*centerOfMass[0]); + inertiaTensor[2][2] -= mass * (centerOfMass[0]*centerOfMass[0] + centerOfMass[1]*centerOfMass[1]); + inertiaTensor[0][1] = inertiaTensor[1][0] += mass * centerOfMass[0] * centerOfMass[1]; + inertiaTensor[1][2] = inertiaTensor[2][1] += mass * centerOfMass[1] * centerOfMass[2]; + inertiaTensor[2][0] = inertiaTensor[0][2] += mass * centerOfMass[2] * centerOfMass[0]; +} diff --git a/neo/idlib/geometry/TraceModel.h b/neo/idlib/geometry/TraceModel.h new file mode 100644 index 00000000..253a6e26 --- /dev/null +++ b/neo/idlib/geometry/TraceModel.h @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TRACEMODEL_H__ +#define __TRACEMODEL_H__ + +/* +=============================================================================== + + A trace model is an arbitrary polygonal model which is used by the + collision detection system to find collisions, contacts or the contents + of a volume. For collision detection speed reasons the number of vertices + and edges are limited. The trace model can have any shape. However convex + models are usually preferred. + +=============================================================================== +*/ + +class idVec3; +class idMat3; +class idBounds; + +// trace model type +typedef enum { + TRM_INVALID, // invalid trm + TRM_BOX, // box + TRM_OCTAHEDRON, // octahedron + TRM_DODECAHEDRON, // dodecahedron + TRM_CYLINDER, // cylinder approximation + TRM_CONE, // cone approximation + TRM_BONE, // two tetrahedrons attached to each other + TRM_POLYGON, // arbitrary convex polygon + TRM_POLYGONVOLUME, // volume for arbitrary convex polygon + TRM_CUSTOM // loaded from map model or ASE/LWO +} traceModel_t; + +// these are bit cache limits +#define MAX_TRACEMODEL_VERTS 32 +#define MAX_TRACEMODEL_EDGES 32 +#define MAX_TRACEMODEL_POLYS 16 +#define MAX_TRACEMODEL_POLYEDGES 16 + +typedef idVec3 traceModelVert_t; + +typedef struct { + int v[2]; + idVec3 normal; +} traceModelEdge_t; + +typedef struct { + idVec3 normal; + float dist; + idBounds bounds; + int numEdges; + int edges[MAX_TRACEMODEL_POLYEDGES]; +} traceModelPoly_t; + +class idTraceModel { + +public: + traceModel_t type; + int numVerts; + traceModelVert_t verts[MAX_TRACEMODEL_VERTS]; + int numEdges; + traceModelEdge_t edges[MAX_TRACEMODEL_EDGES+1]; + int numPolys; + traceModelPoly_t polys[MAX_TRACEMODEL_POLYS]; + idVec3 offset; // offset to center of model + idBounds bounds; // bounds of model + bool isConvex; // true when model is convex + +public: + idTraceModel(); + // axial bounding box + idTraceModel( const idBounds &boxBounds ); + // cylinder approximation + idTraceModel( const idBounds &cylBounds, const int numSides ); + // bone + idTraceModel( const float length, const float width ); + + // axial box + void SetupBox( const idBounds &boxBounds ); + void SetupBox( const float size ); + // octahedron + void SetupOctahedron( const idBounds &octBounds ); + void SetupOctahedron( const float size ); + // dodecahedron + void SetupDodecahedron( const idBounds &dodBounds ); + void SetupDodecahedron( const float size ); + // cylinder approximation + void SetupCylinder( const idBounds &cylBounds, const int numSides ); + void SetupCylinder( const float height, const float width, const int numSides ); + // cone approximation + void SetupCone( const idBounds &coneBounds, const int numSides ); + void SetupCone( const float height, const float width, const int numSides ); + // two tetrahedrons attached to each other + void SetupBone( const float length, const float width ); + // arbitrary convex polygon + void SetupPolygon( const idVec3 *v, const int count ); + void SetupPolygon( const idWinding &w ); + // generate edge normals + int GenerateEdgeNormals(); + // translate the trm + void Translate( const idVec3 &translation ); + // rotate the trm + void Rotate( const idMat3 &rotation ); + // shrink the model m units on all sides + void Shrink( const float m ); + // compare + bool Compare( const idTraceModel &trm ) const; + bool operator==( const idTraceModel &trm ) const; + bool operator!=( const idTraceModel &trm ) const; + // get the area of one of the polygons + float GetPolygonArea( int polyNum ) const; + // get the silhouette edges + int GetProjectionSilhouetteEdges( const idVec3 &projectionOrigin, int silEdges[MAX_TRACEMODEL_EDGES] ) const; + int GetParallelProjectionSilhouetteEdges( const idVec3 &projectionDir, int silEdges[MAX_TRACEMODEL_EDGES] ) const; + // calculate mass properties assuming an uniform density + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + +private: + void InitBox(); + void InitOctahedron(); + void InitDodecahedron(); + void InitBone(); + + void ProjectionIntegrals( int polyNum, int a, int b, struct projectionIntegrals_s &integrals ) const; + void PolygonIntegrals( int polyNum, int a, int b, int c, struct polygonIntegrals_s &integrals ) const; + void VolumeIntegrals( struct volumeIntegrals_s &integrals ) const; + void VolumeFromPolygon( idTraceModel &trm, float thickness ) const; + int GetOrderedSilhouetteEdges( const int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1], int silEdges[MAX_TRACEMODEL_EDGES] ) const; +}; + + +ID_INLINE idTraceModel::idTraceModel() { + type = TRM_INVALID; + numVerts = numEdges = numPolys = 0; + bounds.Zero(); +} + +ID_INLINE idTraceModel::idTraceModel( const idBounds &boxBounds ) { + InitBox(); + SetupBox( boxBounds ); +} + +ID_INLINE idTraceModel::idTraceModel( const idBounds &cylBounds, const int numSides ) { + SetupCylinder( cylBounds, numSides ); +} + +ID_INLINE idTraceModel::idTraceModel( const float length, const float width ) { + InitBone(); + SetupBone( length, width ); +} + +ID_INLINE bool idTraceModel::operator==( const idTraceModel &trm ) const { + return Compare( trm ); +} + +ID_INLINE bool idTraceModel::operator!=( const idTraceModel &trm ) const { + return !Compare( trm ); +} + +#endif /* !__TRACEMODEL_H__ */ + diff --git a/neo/idlib/geometry/Winding.cpp b/neo/idlib/geometry/Winding.cpp new file mode 100644 index 00000000..d38658b5 --- /dev/null +++ b/neo/idlib/geometry/Winding.cpp @@ -0,0 +1,1600 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +//=============================================================== +// +// idWinding +// +//=============================================================== + +/* +============= +idWinding::ReAllocate +============= +*/ +bool idWinding::ReAllocate( int n, bool keep ) { + idVec5 *oldP; + + oldP = p; + n = (n+3) & ~3; // align up to multiple of four + p = new (TAG_IDLIB_WINDING) idVec5[n]; + if ( oldP ) { + if ( keep ) { + memcpy( p, oldP, numPoints * sizeof(p[0]) ); + } + delete[] oldP; + } + allocedSize = n; + + return true; +} + +/* +============= +idWinding::BaseForPlane +============= +*/ +void idWinding::BaseForPlane( const idVec3 &normal, const float dist ) { + idVec3 org, vright, vup; + + org = normal * dist; + + normal.NormalVectors( vup, vright ); + vup *= MAX_WORLD_SIZE; + vright *= MAX_WORLD_SIZE; + + EnsureAlloced( 4 ); + numPoints = 4; + p[0].ToVec3() = org - vright + vup; + p[0].s = p[0].t = 0.0f; + p[1].ToVec3() = org + vright + vup; + p[1].s = p[1].t = 0.0f; + p[2].ToVec3() = org + vright - vup; + p[2].s = p[2].t = 0.0f; + p[3].ToVec3() = org - vright - vup; + p[3].s = p[3].t = 0.0f; +} + +/* +============= +idWinding::Split +============= +*/ +int idWinding::Split( const idPlane &plane, const float epsilon, idWinding **front, idWinding **back ) const { + float * dists; + byte * sides; + int counts[3]; + float dot; + int i, j; + const idVec5 * p1, *p2; + idVec5 mid; + idWinding * f, *b; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + idPlane windingPlane; + + GetPlane( windingPlane ); + if ( windingPlane.Normal() * plane.Normal() > 0.0f ) { + *front = Copy(); + return SIDE_FRONT; + } else { + *back = Copy(); + return SIDE_BACK; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = Copy(); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = Copy(); + return SIDE_FRONT; + } + + maxpts = numPoints+4; // cant use counts[0]+2 because of fp grouping errors + + *front = f = new (TAG_IDLIB_WINDING) idWinding(maxpts); + *back = b = new (TAG_IDLIB_WINDING) idWinding(maxpts); + + for (i = 0; i < numPoints; i++) { + p1 = &p[i]; + + if ( sides[i] == SIDE_ON ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + b->p[b->numPoints] = *p1; + b->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + } + + if ( sides[i] == SIDE_BACK ) { + b->p[b->numPoints] = *p1; + b->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + // always calculate the split going from the same side + // or minor epsilon issues can happen + if ( sides[i] == SIDE_FRONT ) { + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + } else { + dot = dists[i+1] / ( dists[i+1] - dists[i] ); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p2)[j] + dot * ( (*p1)[j] - (*p2)[j] ); + } + } + mid.s = p2->s + dot * ( p1->s - p2->s ); + mid.t = p2->t + dot * ( p1->t - p2->t ); + } + + f->p[f->numPoints] = mid; + f->numPoints++; + b->p[b->numPoints] = mid; + b->numPoints++; + } + + if ( f->numPoints > maxpts || b->numPoints > maxpts ) { + idLib::common->FatalError( "idWinding::Split: points exceeded estimate." ); + } + + return SIDE_CROSS; +} + +/* +============= +idWinding::Clip +============= +*/ +idWinding *idWinding::Clip( const idPlane &plane, const float epsilon, const bool keepOn ) { + float * dists; + byte * sides; + idVec5 * newPoints; + int newNumPoints; + int counts[3]; + float dot; + int i, j; + idVec5 * p1, *p2; + idVec5 mid; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return this; + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + delete this; + return NULL; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return this; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + + newPoints = (idVec5 *) _alloca16( maxpts * sizeof( idVec5 ) ); + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return this; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return this; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( !EnsureAlloced( newNumPoints, false ) ) { + return this; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec5) ); + + return this; +} + +/* +============= +idWinding::ClipInPlace +============= +*/ +bool idWinding::ClipInPlace( const idPlane &plane, const float epsilon, const bool keepOn ) { + float* dists; + byte * sides; + idVec5 * newPoints; + int newNumPoints; + int counts[3]; + float dot; + int i, j; + idVec5 * p1, *p2; + idVec5 mid; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return true; + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + numPoints = 0; + return false; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return true; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + + newPoints = (idVec5 *) _alloca16( maxpts * sizeof( idVec5 ) ); + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( !EnsureAlloced( newNumPoints, false ) ) { + return true; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec5) ); + + return true; +} + +/* +============= +idWinding::Copy +============= +*/ +idWinding *idWinding::Copy() const { + idWinding *w; + + w = new (TAG_IDLIB_WINDING) idWinding( numPoints ); + w->numPoints = numPoints; + memcpy( w->p, p, numPoints * sizeof(p[0]) ); + return w; +} + +/* +============= +idWinding::Reverse +============= +*/ +idWinding *idWinding::Reverse() const { + idWinding *w; + int i; + + w = new (TAG_IDLIB_WINDING) idWinding( numPoints ); + w->numPoints = numPoints; + for ( i = 0; i < numPoints; i++ ) { + w->p[ numPoints - i - 1 ] = p[i]; + } + return w; +} + +/* +============= +idWinding::ReverseSelf +============= +*/ +void idWinding::ReverseSelf() { + idVec5 v; + int i; + + for ( i = 0; i < (numPoints>>1); i++ ) { + v = p[i]; + p[i] = p[numPoints - i - 1]; + p[numPoints - i - 1] = v; + } +} + +/* +============= +idWinding::Check +============= +*/ +bool idWinding::Check( bool print ) const { + int i, j; + float d, edgedist; + idVec3 dir, edgenormal; + float area; + idPlane plane; + + if ( numPoints < 3 ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: only %i points.", numPoints ); + } + return false; + } + + area = GetArea(); + if ( area < 1.0f ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: tiny area: %f", area ); + } + return false; + } + + GetPlane( plane ); + + for ( i = 0; i < numPoints; i++ ) { + const idVec3 &p1 = p[i].ToVec3(); + + // check if the winding is huge + for ( j = 0; j < 3; j++ ) { + if ( p1[j] >= MAX_WORLD_COORD || p1[j] <= MIN_WORLD_COORD ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: point %d outside world %c-axis: %f", i, 'X'+j, p1[j] ); + } + return false; + } + } + + j = i + 1 == numPoints ? 0 : i + 1; + + // check if the point is on the face plane + d = p1 * plane.Normal() + plane[3]; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: point %d off plane.", i ); + } + return false; + } + + // check if the edge isn't degenerate + const idVec3 &p2 = p[j].ToVec3(); + dir = p2 - p1; + + if ( dir.Length() < ON_EPSILON) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: edge %d is degenerate.", i ); + } + return false; + } + + // check if the winding is convex + edgenormal = plane.Normal().Cross( dir ); + edgenormal.Normalize(); + edgedist = p1 * edgenormal; + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0; j < numPoints; j++ ) { + if ( j == i ) { + continue; + } + d = p[j].ToVec3() * edgenormal; + if ( d > edgedist ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: non-convex." ); + } + return false; + } + } + } + return true; +} + +/* +============= +idWinding::GetArea +============= +*/ +float idWinding::GetArea() const { + int i; + idVec3 d1, d2, cross; + float total; + + total = 0.0f; + for ( i = 2; i < numPoints; i++ ) { + d1 = p[i-1].ToVec3() - p[0].ToVec3(); + d2 = p[i].ToVec3() - p[0].ToVec3(); + cross = d1.Cross( d2 ); + total += cross.Length(); + } + return total * 0.5f; +} + +/* +============= +idWinding::GetRadius +============= +*/ +float idWinding::GetRadius( const idVec3 ¢er ) const { + int i; + float radius, r; + idVec3 dir; + + radius = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = p[i].ToVec3() - center; + r = dir * dir; + if ( r > radius ) { + radius = r; + } + } + return idMath::Sqrt( radius ); +} + +/* +============= +idWinding::GetCenter +============= +*/ +idVec3 idWinding::GetCenter() const { + int i; + idVec3 center; + + center.Zero(); + for ( i = 0; i < numPoints; i++ ) { + center += p[i].ToVec3(); + } + center *= ( 1.0f / numPoints ); + return center; +} + +/* +============= +idWinding::GetPlane +============= +*/ +void idWinding::GetPlane( idVec3 &normal, float &dist ) const { + idVec3 v1, v2, center; + + if ( numPoints < 3 ) { + normal.Zero(); + dist = 0.0f; + return; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + normal = v2.Cross( v1 ); + normal.Normalize(); + dist = p[0].ToVec3() * normal; +} + +/* +============= +idWinding::GetPlane +============= +*/ +void idWinding::GetPlane( idPlane &plane ) const { + idVec3 v1, v2; + idVec3 center; + + if ( numPoints < 3 ) { + plane.Zero(); + return; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + plane.SetNormal( v2.Cross( v1 ) ); + plane.Normalize(); + plane.FitThroughPoint( p[0].ToVec3() ); +} + +/* +============= +idWinding::GetBounds +============= +*/ +void idWinding::GetBounds( idBounds &bounds ) const { + int i; + + if ( !numPoints ) { + bounds.Clear(); + return; + } + + bounds[0] = bounds[1] = p[0].ToVec3(); + for ( i = 1; i < numPoints; i++ ) { + if ( p[i].x < bounds[0].x ) { + bounds[0].x = p[i].x; + } else if ( p[i].x > bounds[1].x ) { + bounds[1].x = p[i].x; + } + if ( p[i].y < bounds[0].y ) { + bounds[0].y = p[i].y; + } else if ( p[i].y > bounds[1].y ) { + bounds[1].y = p[i].y; + } + if ( p[i].z < bounds[0].z ) { + bounds[0].z = p[i].z; + } else if ( p[i].z > bounds[1].z ) { + bounds[1].z = p[i].z; + } + } +} + +/* +============= +idWinding::RemoveEqualPoints +============= +*/ +void idWinding::RemoveEqualPoints( const float epsilon ) { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + if ( (p[i].ToVec3() - p[(i+numPoints-1)%numPoints].ToVec3()).LengthSqr() >= Square( epsilon ) ) { + continue; + } + numPoints--; + for ( j = i; j < numPoints; j++ ) { + p[j] = p[j+1]; + } + i--; + } +} + +/* +============= +idWinding::RemoveColinearPoints +============= +*/ +void idWinding::RemoveColinearPoints( const idVec3 &normal, const float epsilon ) { + int i, j; + idVec3 edgeNormal; + float dist; + + if ( numPoints <= 3 ) { + return; + } + + for ( i = 0; i < numPoints; i++ ) { + + // create plane through edge orthogonal to winding plane + edgeNormal = (p[i].ToVec3() - p[(i+numPoints-1)%numPoints].ToVec3()).Cross( normal ); + edgeNormal.Normalize(); + dist = edgeNormal * p[i].ToVec3(); + + if ( idMath::Fabs( edgeNormal * p[(i+1)%numPoints].ToVec3() - dist ) > epsilon ) { + continue; + } + + numPoints--; + for ( j = i; j < numPoints; j++ ) { + p[j] = p[j+1]; + } + i--; + } +} + +/* +============= +idWinding::AddToConvexHull + + Adds the given winding to the convex hull. + Assumes the current winding already is a convex hull with three or more points. +============= +*/ +void idWinding::AddToConvexHull( const idWinding *winding, const idVec3 &normal, const float epsilon ) { + int i, j, k; + idVec3 dir; + float d; + int maxPts; + idVec3 * hullDirs; + bool * hullSide; + bool outside; + int numNewHullPoints; + idVec5 * newHullPoints; + + if ( !winding ) { + return; + } + + maxPts = this->numPoints + winding->numPoints; + + if ( !this->EnsureAlloced( maxPts, true ) ) { + return; + } + + newHullPoints = (idVec5 *) _alloca( maxPts * sizeof( idVec5 ) ); + hullDirs = (idVec3 *) _alloca( maxPts * sizeof( idVec3 ) ); + hullSide = (bool *) _alloca( maxPts * sizeof( bool ) ); + + for ( i = 0; i < winding->numPoints; i++ ) { + const idVec5 &p1 = winding->p[i]; + + // calculate hull edge vectors + for ( j = 0; j < this->numPoints; j++ ) { + dir = this->p[ (j + 1) % this->numPoints ].ToVec3() - this->p[ j ].ToVec3(); + dir.Normalize(); + hullDirs[j] = normal.Cross( dir ); + } + + // calculate side for each hull edge + outside = false; + for ( j = 0; j < this->numPoints; j++ ) { + dir = p1.ToVec3() - this->p[j].ToVec3(); + d = dir * hullDirs[j]; + if ( d >= epsilon ) { + outside = true; + } + if ( d >= -epsilon ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0; j < this->numPoints; j++ ) { + if ( !hullSide[ j ] && hullSide[ (j + 1) % this->numPoints ] ) { + break; + } + } + if ( j >= this->numPoints ) { + continue; + } + + // insert the point here + newHullPoints[0] = p1; + numNewHullPoints = 1; + + // copy over all points that aren't double fronts + j = (j+1) % this->numPoints; + for ( k = 0; k < this->numPoints; k++ ) { + if ( hullSide[ (j+k) % this->numPoints ] && hullSide[ (j+k+1) % this->numPoints ] ) { + continue; + } + newHullPoints[numNewHullPoints] = this->p[ (j+k+1) % this->numPoints ]; + numNewHullPoints++; + } + + this->numPoints = numNewHullPoints; + memcpy( this->p, newHullPoints, numNewHullPoints * sizeof(idVec5) ); + } +} + +/* +============= +idWinding::AddToConvexHull + + Add a point to the convex hull. + The current winding must be convex but may be degenerate and can have less than three points. +============= +*/ +void idWinding::AddToConvexHull( const idVec3 &point, const idVec3 &normal, const float epsilon ) { + int j, k, numHullPoints; + idVec3 dir; + float d; + idVec3 * hullDirs; + bool * hullSide; + idVec5 * hullPoints; + bool outside; + + switch( numPoints ) { + case 0: { + p[0] = point; + numPoints++; + return; + } + case 1: { + // don't add the same point second + if ( p[0].ToVec3().Compare( point, epsilon ) ) { + return; + } + p[1].ToVec3() = point; + numPoints++; + return; + } + case 2: { + // don't add a point if it already exists + if ( p[0].ToVec3().Compare( point, epsilon ) || p[1].ToVec3().Compare( point, epsilon ) ) { + return; + } + // if only two points make sure we have the right ordering according to the normal + dir = point - p[0].ToVec3(); + dir = dir.Cross( p[1].ToVec3() - p[0].ToVec3() ); + if ( dir[0] == 0.0f && dir[1] == 0.0f && dir[2] == 0.0f ) { + // points don't make a plane + return; + } + if ( dir * normal > 0.0f ) { + p[2].ToVec3() = point; + } + else { + p[2] = p[1]; + p[1].ToVec3() = point; + } + numPoints++; + return; + } + } + + hullDirs = (idVec3 *) _alloca( numPoints * sizeof( idVec3 ) ); + hullSide = (bool *) _alloca( numPoints * sizeof( bool ) ); + + // calculate hull edge vectors + for ( j = 0; j < numPoints; j++ ) { + dir = p[(j + 1) % numPoints].ToVec3() - p[j].ToVec3(); + hullDirs[j] = normal.Cross( dir ); + } + + // calculate side for each hull edge + outside = false; + for ( j = 0; j < numPoints; j++ ) { + dir = point - p[j].ToVec3(); + d = dir * hullDirs[j]; + if ( d >= epsilon ) { + outside = true; + } + if ( d >= -epsilon ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + return; + } + + // find the back side to front side transition + for ( j = 0; j < numPoints; j++ ) { + if ( !hullSide[ j ] && hullSide[ (j + 1) % numPoints ] ) { + break; + } + } + if ( j >= numPoints ) { + return; + } + + hullPoints = (idVec5 *) _alloca( (numPoints+1) * sizeof( idVec5 ) ); + + // insert the point here + hullPoints[0] = point; + numHullPoints = 1; + + // copy over all points that aren't double fronts + j = (j+1) % numPoints; + for ( k = 0; k < numPoints; k++ ) { + if ( hullSide[ (j+k) % numPoints ] && hullSide[ (j+k+1) % numPoints ] ) { + continue; + } + hullPoints[numHullPoints] = p[ (j+k+1) % numPoints ]; + numHullPoints++; + } + + if ( !EnsureAlloced( numHullPoints, false ) ) { + return; + } + numPoints = numHullPoints; + memcpy( p, hullPoints, numHullPoints * sizeof(idVec5) ); +} + +/* +============= +idWinding::TryMerge +============= +*/ +#define CONTINUOUS_EPSILON 0.005f + +idWinding *idWinding::TryMerge( const idWinding &w, const idVec3 &planenormal, int keep ) const { + idVec3 *p1, *p2, *p3, *p4, *back; + idWinding *newf; + const idWinding *f1, *f2; + int i, j, k, l; + idVec3 normal, delta; + float dot; + bool keep1, keep2; + + f1 = this; + f2 = &w; + // + // find a idLib::common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; + + for ( i = 0; i < f1->numPoints; i++ ) { + p1 = &f1->p[i].ToVec3(); + p2 = &f1->p[(i+1) % f1->numPoints].ToVec3(); + for ( j = 0; j < f2->numPoints; j++ ) { + p3 = &f2->p[j].ToVec3(); + p4 = &f2->p[(j+1) % f2->numPoints].ToVec3(); + for (k = 0; k < 3; k++ ) { + if ( idMath::Fabs((*p1)[k] - (*p4)[k]) > 0.1f ) { + break; + } + if ( idMath::Fabs((*p2)[k] - (*p3)[k]) > 0.1f ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j < f2->numPoints ) { + break; + } + } + + if ( i == f1->numPoints ) { + return NULL; // no matching edges + } + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = &f1->p[(i+f1->numPoints-1)%f1->numPoints].ToVec3(); + delta = (*p1) - (*back); + normal = planenormal.Cross(delta); + normal.Normalize(); + + back = &f2->p[(j+2)%f2->numPoints].ToVec3(); + delta = (*back) - (*p1); + dot = delta * normal; + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + + keep1 = (bool)(dot < -CONTINUOUS_EPSILON); + + back = &f1->p[(i+2)%f1->numPoints].ToVec3(); + delta = (*back) - (*p2); + normal = planenormal.Cross( delta ); + normal.Normalize(); + + back = &f2->p[(j+f2->numPoints-1)%f2->numPoints].ToVec3(); + delta = (*back) - (*p2); + dot = delta * normal; + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + + keep2 = (bool)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + newf = new (TAG_IDLIB_WINDING) idWinding( f1->numPoints + f2->numPoints ); + + // copy first polygon + for ( k = (i+1) % f1->numPoints; k != i; k = (k+1) % f1->numPoints ) { + if ( !keep && k == (i+1) % f1->numPoints && !keep2 ) { + continue; + } + + newf->p[newf->numPoints] = f1->p[k]; + newf->numPoints++; + } + + // copy second polygon + for ( l = (j+1) % f2->numPoints; l != j; l = (l+1) % f2->numPoints ) { + if ( !keep && l == (j+1) % f2->numPoints && !keep1 ) { + continue; + } + newf->p[newf->numPoints] = f2->p[l]; + newf->numPoints++; + } + + return newf; +} + +/* +============= +idWinding::RemovePoint +============= +*/ +void idWinding::RemovePoint( int point ) { + if ( point < 0 || point >= numPoints ) { + idLib::common->FatalError( "idWinding::removePoint: point out of range" ); + } + if ( point < numPoints - 1) { + memmove(&p[point], &p[point+1], (numPoints - point - 1) * sizeof(p[0]) ); + } + numPoints--; +} + +/* +============= +idWinding::InsertPoint +============= +*/ +void idWinding::InsertPoint( const idVec5 &point, int spot ) { + int i; + + if ( spot > numPoints ) { + idLib::common->FatalError( "idWinding::insertPoint: spot > numPoints" ); + } + + if ( spot < 0 ) { + idLib::common->FatalError( "idWinding::insertPoint: spot < 0" ); + } + + EnsureAlloced( numPoints+1, true ); + for ( i = numPoints; i > spot; i-- ) { + p[i] = p[i-1]; + } + p[spot] = point; + numPoints++; +} + +/* +============= +idWinding::InsertPointIfOnEdge +============= +*/ +bool idWinding::InsertPointIfOnEdge( const idVec5 &point, const idPlane &plane, const float epsilon ) { + int i; + float dist, dot; + idVec3 normal; + + // point may not be too far from the winding plane + if ( idMath::Fabs( plane.Distance( point.ToVec3() ) ) > epsilon ) { + return false; + } + + for ( i = 0; i < numPoints; i++ ) { + + // create plane through edge orthogonal to winding plane + normal = (p[(i+1)%numPoints].ToVec3() - p[i].ToVec3()).Cross( plane.Normal() ); + normal.Normalize(); + dist = normal * p[i].ToVec3(); + + if ( idMath::Fabs( normal * point.ToVec3() - dist ) > epsilon ) { + continue; + } + + normal = plane.Normal().Cross( normal ); + dot = normal * point.ToVec3(); + + dist = dot - normal * p[i].ToVec3(); + + if ( dist < epsilon ) { + // if the winding already has the point + if ( dist > -epsilon ) { + return false; + } + continue; + } + + dist = dot - normal * p[(i+1)%numPoints].ToVec3(); + + if ( dist > -epsilon ) { + // if the winding already has the point + if ( dist < epsilon ) { + return false; + } + continue; + } + + InsertPoint( point, i+1 ); + return true; + } + return false; +} + +/* +============= +idWinding::IsTiny +============= +*/ +#define EDGE_LENGTH 0.2f + +bool idWinding::IsTiny() const { + int i; + float len; + idVec3 delta; + int edges; + + edges = 0; + for ( i = 0; i < numPoints; i++ ) { + delta = p[(i+1)%numPoints].ToVec3() - p[i].ToVec3(); + len = delta.Length(); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +} + +/* +============= +idWinding::IsHuge +============= +*/ +bool idWinding::IsHuge() const { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( p[i][j] <= MIN_WORLD_COORD || p[i][j] >= MAX_WORLD_COORD ) { + return true; + } + } + } + return false; +} + +/* +============= +idWinding::Print +============= +*/ +void idWinding::Print() const { + int i; + + for ( i = 0; i < numPoints; i++ ) { + idLib::common->Printf( "(%5.1f, %5.1f, %5.1f)\n", p[i][0], p[i][1], p[i][2] ); + } +} + +/* +============= +idWinding::PlaneDistance +============= +*/ +float idWinding::PlaneDistance( const idPlane &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < numPoints; i++ ) { + d = plane.Distance( p[i].ToVec3() ); + if ( d < min ) { + min = d; + if ( IEEE_FLT_SIGNBITSET( min ) & IEEE_FLT_SIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( IEEE_FLT_SIGNBITSET( min ) & IEEE_FLT_SIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( IEEE_FLT_SIGNBITNOTSET( min ) ) { + return min; + } + if ( IEEE_FLT_SIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idWinding::PlaneSide +============= +*/ +int idWinding::PlaneSide( const idPlane &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < numPoints; i++ ) { + d = plane.Distance( p[i].ToVec3() ); + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +============= +idWinding::PlanesConcave +============= +*/ +#define WCONVEX_EPSILON 0.2f + +bool idWinding::PlanesConcave( const idWinding &w2, const idVec3 &normal1, const idVec3 &normal2, float dist1, float dist2 ) const { + int i; + + // check if one of the points of winding 1 is at the back of the plane of winding 2 + for ( i = 0; i < numPoints; i++ ) { + if ( normal2 * p[i].ToVec3() - dist2 > WCONVEX_EPSILON ) { + return true; + } + } + // check if one of the points of winding 2 is at the back of the plane of winding 1 + for ( i = 0; i < w2.numPoints; i++ ) { + if ( normal1 * w2.p[i].ToVec3() - dist1 > WCONVEX_EPSILON ) { + return true; + } + } + + return false; +} + +/* +============= +idWinding::PointInside +============= +*/ +bool idWinding::PointInside( const idVec3 &normal, const idVec3 &point, const float epsilon ) const { + int i; + idVec3 dir, n, pointvec; + + for ( i = 0; i < numPoints; i++ ) { + dir = p[(i+1) % numPoints].ToVec3() - p[i].ToVec3(); + pointvec = point - p[i].ToVec3(); + + n = dir.Cross( normal ); + + if ( pointvec * n < -epsilon ) { + return false; + } + } + return true; +} + +/* +============= +idWinding::LineIntersection +============= +*/ +bool idWinding::LineIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &end, bool backFaceCull ) const { + float front, back, frac; + idVec3 mid; + + front = windingPlane.Distance( start ); + back = windingPlane.Distance( end ); + + // if both points at the same side of the plane + if ( front < 0.0f && back < 0.0f ) { + return false; + } + + if ( front > 0.0f && back > 0.0f ) { + return false; + } + + // if back face culled + if ( backFaceCull && front < 0.0f ) { + return false; + } + + // get point of intersection with winding plane + if ( idMath::Fabs(front - back) < 0.0001f ) { + mid = end; + } + else { + frac = front / (front - back); + mid[0] = start[0] + (end[0] - start[0]) * frac; + mid[1] = start[1] + (end[1] - start[1]) * frac; + mid[2] = start[2] + (end[2] - start[2]) * frac; + } + + return PointInside( windingPlane.Normal(), mid, 0.0f ); +} + +/* +============= +idWinding::RayIntersection +============= +*/ +bool idWinding::RayIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull ) const { + int i; + bool side, lastside = false; + idPluecker pl1, pl2; + + scale = 0.0f; + pl1.FromRay( start, dir ); + for ( i = 0; i < numPoints; i++ ) { + pl2.FromLine( p[i].ToVec3(), p[(i+1)%numPoints].ToVec3() ); + side = pl1.PermutedInnerProduct( pl2 ) > 0.0f; + if ( i && side != lastside ) { + return false; + } + lastside = side; + } + if ( !backFaceCull || lastside ) { + windingPlane.RayIntersection( start, dir, scale ); + return true; + } + return false; +} + +/* +================= +idWinding::TriangleArea +================= +*/ +float idWinding::TriangleArea( const idVec3 &a, const idVec3 &b, const idVec3 &c ) { + idVec3 v1, v2; + idVec3 cross; + + v1 = b - a; + v2 = c - a; + cross = v1.Cross( v2 ); + return 0.5f * cross.Length(); +} + + +//=============================================================== +// +// idFixedWinding +// +//=============================================================== + +/* +============= +idFixedWinding::ReAllocate +============= +*/ +bool idFixedWinding::ReAllocate( int n, bool keep ) { + + assert( n <= MAX_POINTS_ON_WINDING ); + + if ( n > MAX_POINTS_ON_WINDING ) { + idLib::common->Printf("WARNING: idFixedWinding -> MAX_POINTS_ON_WINDING overflowed\n"); + return false; + } + return true; +} + +/* +============= +idFixedWinding::Split +============= +*/ +int idFixedWinding::Split( idFixedWinding *back, const idPlane &plane, const float epsilon ) { + int counts[3]; + float dists[MAX_POINTS_ON_WINDING+4]; + byte sides[MAX_POINTS_ON_WINDING+4]; + float dot; + int i, j; + idVec5 *p1, *p2; + idVec5 mid; + idFixedWinding out; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if ( !counts[SIDE_BACK] ) { + if ( !counts[SIDE_FRONT] ) { + return SIDE_ON; + } + else { + return SIDE_FRONT; + } + } + + if ( !counts[SIDE_FRONT] ) { + return SIDE_BACK; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + out.numPoints = 0; + back->numPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( !out.EnsureAlloced( out.numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + if ( !back->EnsureAlloced( back->numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + out.p[out.numPoints] = *p1; + out.numPoints++; + back->p[back->numPoints] = *p1; + back->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + out.p[out.numPoints] = *p1; + out.numPoints++; + } + if ( sides[i] == SIDE_BACK ) { + back->p[back->numPoints] = *p1; + back->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( !out.EnsureAlloced( out.numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + if ( !back->EnsureAlloced( back->numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + // generate a split point + j = i + 1; + if ( j >= numPoints ) { + p2 = &p[0]; + } + else { + p2 = &p[j]; + } + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + out.p[out.numPoints] = mid; + out.numPoints++; + back->p[back->numPoints] = mid; + back->numPoints++; + } + for ( i = 0; i < out.numPoints; i++ ) { + p[i] = out.p[i]; + } + numPoints = out.numPoints; + + return SIDE_CROSS; +} diff --git a/neo/idlib/geometry/Winding.h b/neo/idlib/geometry/Winding.h new file mode 100644 index 00000000..784ec0f7 --- /dev/null +++ b/neo/idlib/geometry/Winding.h @@ -0,0 +1,401 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __WINDING_H__ +#define __WINDING_H__ + +/* +=============================================================================== + + A winding is an arbitrary convex polygon defined by an array of points. + +=============================================================================== +*/ + +class idWinding { + +public: + idWinding(); + explicit idWinding( const int n ); // allocate for n points + explicit idWinding( const idVec3 *verts, const int n ); // winding from points + explicit idWinding( const idVec3 &normal, const float dist ); // base winding for plane + explicit idWinding( const idPlane &plane ); // base winding for plane + explicit idWinding( const idWinding &winding ); + virtual ~idWinding(); + + idWinding & operator=( const idWinding &winding ); + const idVec5 & operator[]( const int index ) const; + idVec5 & operator[]( const int index ); + + // add a point to the end of the winding point array + idWinding & operator+=( const idVec3 &v ); + idWinding & operator+=( const idVec5 &v ); + void AddPoint( const idVec3 &v ); + void AddPoint( const idVec5 &v ); + + // number of points on winding + int GetNumPoints() const; + void SetNumPoints( int n ); + virtual void Clear(); + + // huge winding for plane, the points go counter clockwise when facing the front of the plane + void BaseForPlane( const idVec3 &normal, const float dist ); + void BaseForPlane( const idPlane &plane ); + + // splits the winding into a front and back winding, the winding itself stays unchanged + // returns a SIDE_? + int Split( const idPlane &plane, const float epsilon, idWinding **front, idWinding **back ) const; + // returns the winding fragment at the front of the clipping plane, + // if there is nothing at the front the winding itself is destroyed and NULL is returned + idWinding * Clip( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + // returns a copy of the winding + idWinding * Copy() const; + idWinding * Reverse() const; + void ReverseSelf(); + void RemoveEqualPoints( const float epsilon = ON_EPSILON ); + void RemoveColinearPoints( const idVec3 &normal, const float epsilon = ON_EPSILON ); + void RemovePoint( int point ); + void InsertPoint( const idVec5 &point, int spot ); + bool InsertPointIfOnEdge( const idVec5 &point, const idPlane &plane, const float epsilon = ON_EPSILON ); + // add a winding to the convex hull + void AddToConvexHull( const idWinding *winding, const idVec3 &normal, const float epsilon = ON_EPSILON ); + // add a point to the convex hull + void AddToConvexHull( const idVec3 &point, const idVec3 &normal, const float epsilon = ON_EPSILON ); + // tries to merge 'this' with the given winding, returns NULL if merge fails, both 'this' and 'w' stay intact + // 'keep' tells if the contacting points should stay even if they create colinear edges + idWinding * TryMerge( const idWinding &w, const idVec3 &normal, int keep = false ) const; + // check whether the winding is valid or not + bool Check( bool print = true ) const; + + float GetArea() const; + idVec3 GetCenter() const; + float GetRadius( const idVec3 ¢er ) const; + void GetPlane( idVec3 &normal, float &dist ) const; + void GetPlane( idPlane &plane ) const; + void GetBounds( idBounds &bounds ) const; + + bool IsTiny() const; + bool IsHuge() const; // base winding for a plane is typically huge + void Print() const; + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool PlanesConcave( const idWinding &w2, const idVec3 &normal1, const idVec3 &normal2, float dist1, float dist2 ) const; + + bool PointInside( const idVec3 &normal, const idVec3 &point, const float epsilon ) const; + // returns true if the line or ray intersects the winding + bool LineIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &end, bool backFaceCull = false ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull = false ) const; + + static float TriangleArea( const idVec3 &a, const idVec3 &b, const idVec3 &c ); + +protected: + int numPoints; // number of points + idVec5 * p; // pointer to point data + int allocedSize; + + bool EnsureAlloced( int n, bool keep = false ); + virtual bool ReAllocate( int n, bool keep = false ); +}; + +ID_INLINE idWinding::idWinding() { + numPoints = allocedSize = 0; + p = NULL; +} + +ID_INLINE idWinding::idWinding( int n ) { + numPoints = allocedSize = 0; + p = NULL; + EnsureAlloced( n ); +} + +ID_INLINE idWinding::idWinding( const idVec3 *verts, const int n ) { + int i; + + numPoints = allocedSize = 0; + p = NULL; + if ( !EnsureAlloced( n ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < n; i++ ) { + p[i].ToVec3() = verts[i]; + p[i].s = p[i].t = 0.0f; + } + numPoints = n; +} + +ID_INLINE idWinding::idWinding( const idVec3 &normal, const float dist ) { + numPoints = allocedSize = 0; + p = NULL; + BaseForPlane( normal, dist ); +} + +ID_INLINE idWinding::idWinding( const idPlane &plane ) { + numPoints = allocedSize = 0; + p = NULL; + BaseForPlane( plane ); +} + +ID_INLINE idWinding::idWinding( const idWinding &winding ) { + int i; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idWinding::~idWinding() { + delete[] p; + p = NULL; +} + +ID_INLINE idWinding &idWinding::operator=( const idWinding &winding ) { + int i; + + if ( !EnsureAlloced( winding.numPoints ) ) { + numPoints = 0; + return *this; + } + for ( i = 0; i < winding.numPoints; i++ ) { + p[i] = winding.p[i]; + } + numPoints = winding.numPoints; + return *this; +} + +ID_INLINE const idVec5 &idWinding::operator[]( const int index ) const { + //assert( index >= 0 && index < numPoints ); + return p[ index ]; +} + +ID_INLINE idVec5 &idWinding::operator[]( const int index ) { + //assert( index >= 0 && index < numPoints ); + return p[ index ]; +} + +ID_INLINE idWinding &idWinding::operator+=( const idVec3 &v ) { + AddPoint( v ); + return *this; +} + +ID_INLINE idWinding &idWinding::operator+=( const idVec5 &v ) { + AddPoint( v ); + return *this; +} + +ID_INLINE void idWinding::AddPoint( const idVec3 &v ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return; + } + p[numPoints] = v; + numPoints++; +} + +ID_INLINE void idWinding::AddPoint( const idVec5 &v ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return; + } + p[numPoints] = v; + numPoints++; +} + +ID_INLINE int idWinding::GetNumPoints() const { + return numPoints; +} + +ID_INLINE void idWinding::SetNumPoints( int n ) { + if ( !EnsureAlloced( n, true ) ) { + return; + } + numPoints = n; +} + +ID_INLINE void idWinding::Clear() { + numPoints = 0; + delete[] p; + p = NULL; +} + +ID_INLINE void idWinding::BaseForPlane( const idPlane &plane ) { + BaseForPlane( plane.Normal(), plane.Dist() ); +} + +ID_INLINE bool idWinding::EnsureAlloced( int n, bool keep ) { + if ( n > allocedSize ) { + return ReAllocate( n, keep ); + } + return true; +} + + +/* +=============================================================================== + + idFixedWinding is a fixed buffer size winding not using + memory allocations. + + When an operation would overflow the fixed buffer a warning + is printed and the operation is safely cancelled. + +=============================================================================== +*/ + +#define MAX_POINTS_ON_WINDING 64 + +class idFixedWinding : public idWinding { + +public: + idFixedWinding(); + explicit idFixedWinding( const int n ); + explicit idFixedWinding( const idVec3 *verts, const int n ); + explicit idFixedWinding( const idVec3 &normal, const float dist ); + explicit idFixedWinding( const idPlane &plane ); + explicit idFixedWinding( const idWinding &winding ); + explicit idFixedWinding( const idFixedWinding &winding ); + virtual ~idFixedWinding(); + + idFixedWinding &operator=( const idWinding &winding ); + + virtual void Clear(); + + // splits the winding in a back and front part, 'this' becomes the front part + // returns a SIDE_? + int Split( idFixedWinding *back, const idPlane &plane, const float epsilon = ON_EPSILON ); + +protected: + idVec5 data[MAX_POINTS_ON_WINDING]; // point data + + virtual bool ReAllocate( int n, bool keep = false ); +}; + +ID_INLINE idFixedWinding::idFixedWinding() { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; +} + +ID_INLINE idFixedWinding::idFixedWinding( int n ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; +} + +ID_INLINE idFixedWinding::idFixedWinding( const idVec3 *verts, const int n ) { + int i; + + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( n ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < n; i++ ) { + p[i].ToVec3() = verts[i]; + p[i].s = p[i].t = 0; + } + numPoints = n; +} + +ID_INLINE idFixedWinding::idFixedWinding( const idVec3 &normal, const float dist ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + BaseForPlane( normal, dist ); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idPlane &plane ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + BaseForPlane( plane ); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idWinding &winding ) { + int i; + + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idFixedWinding &winding ) { + int i; + + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idFixedWinding::~idFixedWinding() { + p = NULL; // otherwise it tries to free the fixed buffer +} + +ID_INLINE idFixedWinding &idFixedWinding::operator=( const idWinding &winding ) { + int i; + + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return *this; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); + return *this; +} + +ID_INLINE void idFixedWinding::Clear() { + numPoints = 0; +} +#endif /* !__WINDING_H__ */ diff --git a/neo/idlib/geometry/Winding2D.cpp b/neo/idlib/geometry/Winding2D.cpp new file mode 100644 index 00000000..719cc633 --- /dev/null +++ b/neo/idlib/geometry/Winding2D.cpp @@ -0,0 +1,753 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" +#include "Winding2D.h" + +/* +============ +GetAxialBevel +============ +*/ +bool GetAxialBevel( const idVec3 &plane1, const idVec3 &plane2, const idVec2 &point, idVec3 &bevel ) { + if ( IEEE_FLT_SIGNBITSET( plane1.x ) ^ IEEE_FLT_SIGNBITSET( plane2.x ) ) { + if ( idMath::Fabs( plane1.x ) > 0.1f && idMath::Fabs( plane2.x ) > 0.1f ) { + bevel.x = 0.0f; + if ( IEEE_FLT_SIGNBITSET( plane1.y ) ) { + bevel.y = -1.0f; + } + else { + bevel.y = 1.0f; + } + bevel.z = - ( point.x * bevel.x + point.y * bevel.y ); + return true; + } + } + if ( IEEE_FLT_SIGNBITSET( plane1.y ) ^ IEEE_FLT_SIGNBITSET( plane2.y ) ) { + if ( idMath::Fabs( plane1.y ) > 0.1f && idMath::Fabs( plane2.y ) > 0.1f ) { + bevel.y = 0.0f; + if ( IEEE_FLT_SIGNBITSET( plane1.x ) ) { + bevel.x = -1.0f; + } + else { + bevel.x = 1.0f; + } + bevel.z = - ( point.x * bevel.x + point.y * bevel.y ); + return true; + } + } + return false; +} + +/* +============ +idWinding2D::ExpandForAxialBox +============ +*/ +void idWinding2D::ExpandForAxialBox( const idVec2 bounds[2] ) { + int i, j, numPlanes; + idVec2 v; + idVec3 planes[MAX_POINTS_ON_WINDING_2D], plane, bevel; + + // get planes for the edges and add bevels + for ( numPlanes = i = 0; i < numPoints; i++ ) { + j = (i+1) % numPoints; + if ( ( p[j] - p[i] ).LengthSqr() < 0.01f ) { + continue; + } + plane = Plane2DFromPoints( p[i], p[j], true ); + if ( i ) { + if ( GetAxialBevel( planes[numPlanes-1], plane, p[i], bevel ) ) { + planes[numPlanes++] = bevel; + } + } + assert( numPlanes < MAX_POINTS_ON_WINDING_2D ); + planes[numPlanes++] = plane; + } + assert( numPlanes < MAX_POINTS_ON_WINDING_2D && numPlanes > 0 ); + if ( GetAxialBevel( planes[numPlanes-1], planes[0], p[0], bevel ) ) { + planes[numPlanes++] = bevel; + } + + // expand the planes + for ( i = 0; i < numPlanes; i++ ) { + v.x = bounds[ IEEE_FLT_SIGNBITSET( planes[i].x ) ].x; + v.y = bounds[ IEEE_FLT_SIGNBITSET( planes[i].y ) ].y; + planes[i].z += v.x * planes[i].x + v.y * planes[i].y; + } + + // get intersection points of the planes + for ( numPoints = i = 0; i < numPlanes; i++ ) { + if ( Plane2DIntersection( planes[(i+numPlanes-1) % numPlanes], planes[i], p[numPoints] ) ) { + numPoints++; + } + } +} + +/* +============ +idWinding2D::Expand +============ +*/ +void idWinding2D::Expand( const float d ) { + int i; + idVec2 edgeNormals[MAX_POINTS_ON_WINDING_2D]; + + for ( i = 0; i < numPoints; i++ ) { + idVec2 &start = p[i]; + idVec2 &end = p[(i+1)%numPoints]; + edgeNormals[i].x = start.y - end.y; + edgeNormals[i].y = end.x - start.x; + edgeNormals[i].Normalize(); + edgeNormals[i] *= d; + } + + for ( i = 0; i < numPoints; i++ ) { + p[i] += edgeNormals[i] + edgeNormals[(i+numPoints-1)%numPoints]; + } +} + +/* +============= +idWinding2D::Split +============= +*/ +int idWinding2D::Split( const idVec3 &plane, const float epsilon, idWinding2D **front, idWinding2D **back ) const { + float dists[MAX_POINTS_ON_WINDING_2D]; + byte sides[MAX_POINTS_ON_WINDING_2D]; + int counts[3]; + float dot; + int i, j; + const idVec2 * p1, *p2; + idVec2 mid; + idWinding2D * f; + idWinding2D * b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = Copy(); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = Copy(); + return SIDE_FRONT; + } + + maxpts = numPoints+4; // cant use counts[0]+2 because of fp grouping errors + + *front = f = new (TAG_IDLIB_WINDING) idWinding2D; + *back = b = new (TAG_IDLIB_WINDING) idWinding2D; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( sides[i] == SIDE_ON ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + b->p[b->numPoints] = *p1; + b->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + } + + if ( sides[i] == SIDE_BACK ) { + b->p[b->numPoints] = *p1; + b->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + // always calculate the split going from the same side + // or minor epsilon issues can happen + if ( sides[i] == SIDE_FRONT ) { + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p1)[j] + dot * ((*p2)[j] - (*p1)[j]); + } + } + } else { + dot = dists[i+1] / ( dists[i+1] - dists[i] ); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p2)[j] + dot * ( (*p1)[j] - (*p2)[j] ); + } + } + } + + f->p[f->numPoints] = mid; + f->numPoints++; + b->p[b->numPoints] = mid; + b->numPoints++; + } + + return SIDE_CROSS; +} + +/* +============ +idWinding2D::ClipInPlace +============ +*/ +bool idWinding2D::ClipInPlace( const idVec3 &plane, const float epsilon, const bool keepOn ) { + int i, j, maxpts, newNumPoints; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float dot, dists[MAX_POINTS_ON_WINDING_2D+1]; + idVec2 *p1, *p2, mid, newPoints[MAX_POINTS_ON_WINDING_2D+4]; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return true; + } + if ( !counts[SIDE_FRONT] ) { + numPoints = 0; + return false; + } + if ( !counts[SIDE_BACK] ) { + return true; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p1)[j] + dot * ((*p2)[j] - (*p1)[j]); + } + } + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( newNumPoints >= MAX_POINTS_ON_WINDING_2D ) { + return true; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec2) ); + + return true; +} + +/* +============= +idWinding2D::Copy +============= +*/ +idWinding2D *idWinding2D::Copy() const { + idWinding2D *w; + + w = new (TAG_IDLIB_WINDING) idWinding2D; + w->numPoints = numPoints; + memcpy( w->p, p, numPoints * sizeof( p[0] ) ); + return w; +} + +/* +============= +idWinding2D::Reverse +============= +*/ +idWinding2D *idWinding2D::Reverse() const { + idWinding2D *w; + int i; + + w = new (TAG_IDLIB_WINDING) idWinding2D; + w->numPoints = numPoints; + for ( i = 0; i < numPoints; i++ ) { + w->p[ numPoints - i - 1 ] = p[i]; + } + return w; +} + +/* +============ +idWinding2D::GetArea +============ +*/ +float idWinding2D::GetArea() const { + int i; + idVec2 d1, d2; + float total; + + total = 0.0f; + for ( i = 2; i < numPoints; i++ ) { + d1 = p[i-1] - p[0]; + d2 = p[i] - p[0]; + total += d1.x * d2.y - d1.y * d2.x; + } + return total * 0.5f; +} + +/* +============ +idWinding2D::GetCenter +============ +*/ +idVec2 idWinding2D::GetCenter() const { + int i; + idVec2 center; + + center.Zero(); + for ( i = 0; i < numPoints; i++ ) { + center += p[i]; + } + center *= ( 1.0f / numPoints ); + return center; +} + +/* +============ +idWinding2D::GetRadius +============ +*/ +float idWinding2D::GetRadius( const idVec2 ¢er ) const { + int i; + float radius, r; + idVec2 dir; + + radius = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = p[i] - center; + r = dir * dir; + if ( r > radius ) { + radius = r; + } + } + return idMath::Sqrt( radius ); +} + +/* +============ +idWinding2D::GetBounds +============ +*/ +void idWinding2D::GetBounds( idVec2 bounds[2] ) const { + int i; + + if ( !numPoints ) { + bounds[0].x = bounds[0].y = idMath::INFINITY; + bounds[1].x = bounds[1].y = -idMath::INFINITY; + return; + } + bounds[0] = bounds[1] = p[0]; + for ( i = 1; i < numPoints; i++ ) { + if ( p[i].x < bounds[0].x ) { + bounds[0].x = p[i].x; + } else if ( p[i].x > bounds[1].x ) { + bounds[1].x = p[i].x; + } + if ( p[i].y < bounds[0].y ) { + bounds[0].y = p[i].y; + } else if ( p[i].y > bounds[1].y ) { + bounds[1].y = p[i].y; + } + } +} + +/* +============= +idWinding2D::IsTiny +============= +*/ +#define EDGE_LENGTH 0.2f + +bool idWinding2D::IsTiny() const { + int i; + float len; + idVec2 delta; + int edges; + + edges = 0; + for ( i = 0; i < numPoints; i++ ) { + delta = p[(i+1)%numPoints] - p[i]; + len = delta.Length(); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +} + +/* +============= +idWinding2D::IsHuge +============= +*/ +bool idWinding2D::IsHuge() const { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + for ( j = 0; j < 2; j++ ) { + if ( p[i][j] <= MIN_WORLD_COORD || p[i][j] >= MAX_WORLD_COORD ) { + return true; + } + } + } + return false; +} + +/* +============= +idWinding2D::Print +============= +*/ +void idWinding2D::Print() const { + int i; + + for ( i = 0; i < numPoints; i++ ) { + idLib::common->Printf( "(%5.1f, %5.1f)\n", p[i][0], p[i][1] ); + } +} + +/* +============= +idWinding2D::PlaneDistance +============= +*/ +float idWinding2D::PlaneDistance( const idVec3 &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < numPoints; i++ ) { + d = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d < min ) { + min = d; + if ( IEEE_FLT_SIGNBITSET( min ) & IEEE_FLT_SIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( IEEE_FLT_SIGNBITSET( min ) & IEEE_FLT_SIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( IEEE_FLT_SIGNBITNOTSET( min ) ) { + return min; + } + if ( IEEE_FLT_SIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idWinding2D::PlaneSide +============= +*/ +int idWinding2D::PlaneSide( const idVec3 &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < numPoints; i++ ) { + d = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +============ +idWinding2D::PointInside +============ +*/ +bool idWinding2D::PointInside( const idVec2 &point, const float epsilon ) const { + int i; + float d; + idVec3 plane; + + for ( i = 0; i < numPoints; i++ ) { + plane = Plane2DFromPoints( p[i], p[(i+1) % numPoints] ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d > epsilon ) { + return false; + } + } + return true; +} + +/* +============ +idWinding2D::LineIntersection +============ +*/ +bool idWinding2D::LineIntersection( const idVec2 &start, const idVec2 &end ) const { + int i, numEdges; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float d1, d2, epsilon = 0.1f; + idVec3 plane, edges[2]; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + plane = Plane2DFromPoints( start, end ); + for ( i = 0; i < numPoints; i++ ) { + d1 = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d1 > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( d1 < -epsilon ) { + sides[i] = SIDE_BACK; + } + else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + + if ( !counts[SIDE_FRONT] ) { + return false; + } + if ( !counts[SIDE_BACK] ) { + return false; + } + + numEdges = 0; + for ( i = 0; i < numPoints; i++ ) { + if ( sides[i] != sides[i+1] && sides[i+1] != SIDE_ON ) { + edges[numEdges++] = Plane2DFromPoints( p[i], p[(i+1)%numPoints] ); + if ( numEdges >= 2 ) { + break; + } + } + } + if ( numEdges < 2 ) { + return false; + } + + d1 = edges[0].x * start.x + edges[0].y * start.y + edges[0].z; + d2 = edges[0].x * end.x + edges[0].y * end.y + edges[0].z; + if ( IEEE_FLT_SIGNBITNOTSET( d1 ) & IEEE_FLT_SIGNBITNOTSET( d2 ) ) { + return false; + } + d1 = edges[1].x * start.x + edges[1].y * start.y + edges[1].z; + d2 = edges[1].x * end.x + edges[1].y * end.y + edges[1].z; + if ( IEEE_FLT_SIGNBITNOTSET( d1 ) & IEEE_FLT_SIGNBITNOTSET( d2 ) ) { + return false; + } + return true; +} + +/* +============ +idWinding2D::RayIntersection +============ +*/ +bool idWinding2D::RayIntersection( const idVec2 &start, const idVec2 &dir, float &scale1, float &scale2, int *edgeNums ) const { + int i, numEdges, localEdgeNums[2]; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float d1, d2, epsilon = 0.1f; + idVec3 plane, edges[2]; + + scale1 = scale2 = 0.0f; + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + plane = Plane2DFromVecs( start, dir ); + for ( i = 0; i < numPoints; i++ ) { + d1 = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d1 > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( d1 < -epsilon ) { + sides[i] = SIDE_BACK; + } + else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + + if ( !counts[SIDE_FRONT] ) { + return false; + } + if ( !counts[SIDE_BACK] ) { + return false; + } + + numEdges = 0; + for ( i = 0; i < numPoints; i++ ) { + if ( sides[i] != sides[i+1] && sides[i+1] != SIDE_ON ) { + localEdgeNums[numEdges] = i; + edges[numEdges++] = Plane2DFromPoints( p[i], p[(i+1)%numPoints] ); + if ( numEdges >= 2 ) { + break; + } + } + } + if ( numEdges < 2 ) { + return false; + } + + d1 = edges[0].x * start.x + edges[0].y * start.y + edges[0].z; + d2 = - ( edges[0].x * dir.x + edges[0].y * dir.y ); + if ( d2 == 0.0f ) { + return false; + } + scale1 = d1 / d2; + d1 = edges[1].x * start.x + edges[1].y * start.y + edges[1].z; + d2 = - ( edges[1].x * dir.x + edges[1].y * dir.y ); + if ( d2 == 0.0f ) { + return false; + } + scale2 = d1 / d2; + + if ( idMath::Fabs( scale1 ) > idMath::Fabs( scale2 ) ) { + SwapValues( scale1, scale2 ); + SwapValues( localEdgeNums[0], localEdgeNums[1] ); + } + + if ( edgeNums ) { + edgeNums[0] = localEdgeNums[0]; + edgeNums[1] = localEdgeNums[1]; + } + return true; +} diff --git a/neo/idlib/geometry/Winding2D.h b/neo/idlib/geometry/Winding2D.h new file mode 100644 index 00000000..a9044c51 --- /dev/null +++ b/neo/idlib/geometry/Winding2D.h @@ -0,0 +1,169 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __WINDING2D_H__ +#define __WINDING2D_H__ + +/* +=============================================================================== + + A 2D winding is an arbitrary convex 2D polygon defined by an array of points. + +=============================================================================== +*/ + +#define MAX_POINTS_ON_WINDING_2D 16 + + +class idWinding2D { +public: + idWinding2D(); + + idWinding2D & operator=( const idWinding2D &winding ); + const idVec2 & operator[]( const int index ) const; + idVec2 & operator[]( const int index ); + + void Clear(); + void AddPoint( const idVec2 &point ); + int GetNumPoints() const; + + void Expand( const float d ); + void ExpandForAxialBox( const idVec2 bounds[2] ); + + // splits the winding into a front and back winding, the winding itself stays unchanged + // returns a SIDE_? + int Split( const idVec3 &plane, const float epsilon, idWinding2D **front, idWinding2D **back ) const; + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idVec3 &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + idWinding2D * Copy() const; + idWinding2D * Reverse() const; + + float GetArea() const; + idVec2 GetCenter() const; + float GetRadius( const idVec2 ¢er ) const; + void GetBounds( idVec2 bounds[2] ) const; + + bool IsTiny() const; + bool IsHuge() const; // base winding for a plane is typically huge + void Print() const; + + float PlaneDistance( const idVec3 &plane ) const; + int PlaneSide( const idVec3 &plane, const float epsilon = ON_EPSILON ) const; + + bool PointInside( const idVec2 &point, const float epsilon ) const; + bool LineIntersection( const idVec2 &start, const idVec2 &end ) const; + bool RayIntersection( const idVec2 &start, const idVec2 &dir, float &scale1, float &scale2, int *edgeNums = NULL ) const; + + static idVec3 Plane2DFromPoints( const idVec2 &start, const idVec2 &end, const bool normalize = false ); + static idVec3 Plane2DFromVecs( const idVec2 &start, const idVec2 &dir, const bool normalize = false ); + static bool Plane2DIntersection( const idVec3 &plane1, const idVec3 &plane2, idVec2 &point ); + +private: + int numPoints; + idVec2 p[MAX_POINTS_ON_WINDING_2D]; +}; + +ID_INLINE idWinding2D::idWinding2D() { + numPoints = 0; +} + +ID_INLINE idWinding2D &idWinding2D::operator=( const idWinding2D &winding ) { + int i; + + for ( i = 0; i < winding.numPoints; i++ ) { + p[i] = winding.p[i]; + } + numPoints = winding.numPoints; + return *this; +} + +ID_INLINE const idVec2 &idWinding2D::operator[]( const int index ) const { + return p[ index ]; +} + +ID_INLINE idVec2 &idWinding2D::operator[]( const int index ) { + return p[ index ]; +} + +ID_INLINE void idWinding2D::Clear() { + numPoints = 0; +} + +ID_INLINE void idWinding2D::AddPoint( const idVec2 &point ) { + p[numPoints++] = point; +} + +ID_INLINE int idWinding2D::GetNumPoints() const { + return numPoints; +} + +ID_INLINE idVec3 idWinding2D::Plane2DFromPoints( const idVec2 &start, const idVec2 &end, const bool normalize ) { + idVec3 plane; + plane.x = start.y - end.y; + plane.y = end.x - start.x; + if ( normalize ) { + plane.ToVec2().Normalize(); + } + plane.z = - ( start.x * plane.x + start.y * plane.y ); + return plane; +} + +ID_INLINE idVec3 idWinding2D::Plane2DFromVecs( const idVec2 &start, const idVec2 &dir, const bool normalize ) { + idVec3 plane; + plane.x = -dir.y; + plane.y = dir.x; + if ( normalize ) { + plane.ToVec2().Normalize(); + } + plane.z = - ( start.x * plane.x + start.y * plane.y ); + return plane; +} + +ID_INLINE bool idWinding2D::Plane2DIntersection( const idVec3 &plane1, const idVec3 &plane2, idVec2 &point ) { + float n00, n01, n11, det, invDet, f0, f1; + + n00 = plane1.x * plane1.x + plane1.y * plane1.y; + n01 = plane1.x * plane2.x + plane1.y * plane2.y; + n11 = plane2.x * plane2.x + plane2.y * plane2.y; + det = n00 * n11 - n01 * n01; + + if ( idMath::Fabs(det) < 1e-6f ) { + return false; + } + + invDet = 1.0f / det; + f0 = ( n01 * plane2.z - n11 * plane1.z ) * invDet; + f1 = ( n01 * plane1.z - n00 * plane2.z ) * invDet; + point.x = f0 * plane1.x + f1 * plane2.x; + point.y = f0 * plane1.y + f1 * plane2.y; + return true; +} + +#endif /* !__WINDING2D_H__ */ diff --git a/neo/idlib/hashing/CRC32.cpp b/neo/idlib/hashing/CRC32.cpp new file mode 100644 index 00000000..48d2f842 --- /dev/null +++ b/neo/idlib/hashing/CRC32.cpp @@ -0,0 +1,167 @@ + +#pragma hdrstop +#include "../precompiled.h" + +/* + CRC-32 + Copyright (C) 1995-1998 Mark Adler +*/ + +#define CRC32_INIT_VALUE 0xffffffffL +#define CRC32_XOR_VALUE 0xffffffffL + +#ifdef CREATE_CRC_TABLE + +static unsigned long crctable[256]; + +/* + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x^1+x^0), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+x^0), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long c, poly; + /* terms of polynomial defining this crc (except x^32): */ + static const byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* make exclusive-or pattern from polynomial (0xedb88320L) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << ( 31 - p[i] ); + } + + for ( i = 0; i < 256; i++ ) { + c = (unsigned long)i; + for ( j = 0; j < 8; j++ ) { + c = ( c & 1 ) ? poly ^ ( c >> 1 ) : ( c >> 1 ); + } + crctable[i] = c; + } +} + +#else + +/* + Table of CRC-32's of all single-byte values (made by make_crc_table) +*/ +static unsigned long crctable[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, + 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, + 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, + 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, + 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, + 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, + 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, + 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, + 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, + 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, + 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, + 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, + 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, + 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, + 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, + 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, + 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, + 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, + 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, + 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, + 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, + 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, + 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, + 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; + +#endif + +void CRC32_InitChecksum( unsigned long &crcvalue ) { + crcvalue = CRC32_INIT_VALUE; +} + +void CRC32_Update( unsigned long &crcvalue, const byte data ) { + crcvalue = crctable[ ( crcvalue ^ data ) & 0xff ] ^ ( crcvalue >> 8 ); +} + +void CRC32_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ) { + unsigned long crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = crctable[ ( crc ^ ( *buf++ ) ) & 0xff ] ^ ( crc >> 8 ); + } + crcvalue = crc; +} + +void CRC32_FinishChecksum( unsigned long &crcvalue ) { + crcvalue ^= CRC32_XOR_VALUE; +} + +unsigned long CRC32_BlockChecksum( const void *data, int length ) { + unsigned long crc; + + CRC32_InitChecksum( crc ); + CRC32_UpdateChecksum( crc, data, length ); + CRC32_FinishChecksum( crc ); + return crc; +} diff --git a/neo/idlib/hashing/CRC32.h b/neo/idlib/hashing/CRC32.h new file mode 100644 index 00000000..63f0cc32 --- /dev/null +++ b/neo/idlib/hashing/CRC32.h @@ -0,0 +1,18 @@ +#ifndef __CRC32_H__ +#define __CRC32_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CRC-32. + +=============================================================================== +*/ + +void CRC32_InitChecksum( unsigned long &crcvalue ); +void CRC32_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ); +void CRC32_FinishChecksum( unsigned long &crcvalue ); +unsigned long CRC32_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC32_H__ */ diff --git a/neo/idlib/hashing/MD4.cpp b/neo/idlib/hashing/MD4.cpp new file mode 100644 index 00000000..68d9e579 --- /dev/null +++ b/neo/idlib/hashing/MD4.cpp @@ -0,0 +1,259 @@ + +#pragma hdrstop +#include "../precompiled.h" + +/* + RSA Data Security, Inc., MD4 message-digest algorithm. (RFC1320) +*/ + +/* + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode( unsigned char *output, UINT4 *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode( UINT4 *output, const unsigned char *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); + } +} + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4_Transform( UINT4 state[4], const unsigned char block[64] ) { + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11); /* 1 */ + FF (d, a, b, c, x[ 1], S12); /* 2 */ + FF (c, d, a, b, x[ 2], S13); /* 3 */ + FF (b, c, d, a, x[ 3], S14); /* 4 */ + FF (a, b, c, d, x[ 4], S11); /* 5 */ + FF (d, a, b, c, x[ 5], S12); /* 6 */ + FF (c, d, a, b, x[ 6], S13); /* 7 */ + FF (b, c, d, a, x[ 7], S14); /* 8 */ + FF (a, b, c, d, x[ 8], S11); /* 9 */ + FF (d, a, b, c, x[ 9], S12); /* 10 */ + FF (c, d, a, b, x[10], S13); /* 11 */ + FF (b, c, d, a, x[11], S14); /* 12 */ + FF (a, b, c, d, x[12], S11); /* 13 */ + FF (d, a, b, c, x[13], S12); /* 14 */ + FF (c, d, a, b, x[14], S13); /* 15 */ + FF (b, c, d, a, x[15], S14); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 0], S21); /* 17 */ + GG (d, a, b, c, x[ 4], S22); /* 18 */ + GG (c, d, a, b, x[ 8], S23); /* 19 */ + GG (b, c, d, a, x[12], S24); /* 20 */ + GG (a, b, c, d, x[ 1], S21); /* 21 */ + GG (d, a, b, c, x[ 5], S22); /* 22 */ + GG (c, d, a, b, x[ 9], S23); /* 23 */ + GG (b, c, d, a, x[13], S24); /* 24 */ + GG (a, b, c, d, x[ 2], S21); /* 25 */ + GG (d, a, b, c, x[ 6], S22); /* 26 */ + GG (c, d, a, b, x[10], S23); /* 27 */ + GG (b, c, d, a, x[14], S24); /* 28 */ + GG (a, b, c, d, x[ 3], S21); /* 29 */ + GG (d, a, b, c, x[ 7], S22); /* 30 */ + GG (c, d, a, b, x[11], S23); /* 31 */ + GG (b, c, d, a, x[15], S24); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 0], S31); /* 33 */ + HH (d, a, b, c, x[ 8], S32); /* 34 */ + HH (c, d, a, b, x[ 4], S33); /* 35 */ + HH (b, c, d, a, x[12], S34); /* 36 */ + HH (a, b, c, d, x[ 2], S31); /* 37 */ + HH (d, a, b, c, x[10], S32); /* 38 */ + HH (c, d, a, b, x[ 6], S33); /* 39 */ + HH (b, c, d, a, x[14], S34); /* 40 */ + HH (a, b, c, d, x[ 1], S31); /* 41 */ + HH (d, a, b, c, x[ 9], S32); /* 42 */ + HH (c, d, a, b, x[ 5], S33); /* 43 */ + HH (b, c, d, a, x[13], S34); /* 44 */ + HH (a, b, c, d, x[ 3], S31); /* 45 */ + HH (d, a, b, c, x[11], S32); /* 46 */ + HH (c, d, a, b, x[ 7], S33); /* 47 */ + HH (b, c, d, a, x[15], S34); /* 48 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4_Init( MD4_CTX *context ) { + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants.*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4_Update( MD4_CTX *context, const unsigned char *input, unsigned int inputLen ) { + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) { + context->count[1]++; + } + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if ( inputLen >= partLen ) { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4_Transform (context->state, context->buffer); + + for ( i = partLen; i + 63 < inputLen; i += 64 ) { + MD4_Transform (context->state, &input[i]); + } + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the message digest and zeroizing the context. */ +void MD4_Final( MD4_CTX *context, unsigned char digest[16] ) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode( bits, context->count, 8 ); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4_Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4_Update( context, bits, 8 ); + + /* Store state in digest */ + Encode( digest, context->state, 16 ); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + +/* +=============== +MD4_BlockChecksum +=============== +*/ +unsigned long MD4_BlockChecksum( const void *data, int length ) { + unsigned long digest[4]; + unsigned long val; + MD4_CTX ctx; + + MD4_Init( &ctx ); + MD4_Update( &ctx, (unsigned char *)data, length ); + MD4_Final( &ctx, (unsigned char *)digest ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/neo/idlib/hashing/MD4.h b/neo/idlib/hashing/MD4.h new file mode 100644 index 00000000..8b5eb907 --- /dev/null +++ b/neo/idlib/hashing/MD4.h @@ -0,0 +1,15 @@ +#ifndef __MD4_H__ +#define __MD4_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the MD4 message-digest algorithm. + +=============================================================================== +*/ + +unsigned long MD4_BlockChecksum( const void *data, int length ); + +#endif /* !__MD4_H__ */ diff --git a/neo/idlib/hashing/MD5.cpp b/neo/idlib/hashing/MD5.cpp new file mode 100644 index 00000000..0b54b976 --- /dev/null +++ b/neo/idlib/hashing/MD5.cpp @@ -0,0 +1,304 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +================================================================================================ +Contains the MD5BlockChecksum implementation. +================================================================================================ +*/ + +// POINTER defines a generic pointer type +typedef unsigned char *POINTER; + +// UINT2 defines a two byte word +typedef unsigned short int UINT2; + +// UINT4 defines a four byte word +typedef unsigned int UINT4; + +//------------------------ +// The four core functions - F1 is optimized somewhat +// JDC: I wouldn't have condoned the change in something as sensitive as a hash function, +// but it looks ok and a random test function checked it out. +//------------------------ +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) ( w += f(x, y, z) + (data), w = w<>(32-s), w += x ) + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* +======================== +Encode + +Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. +======================== +*/ +static void Encode( unsigned char *output, UINT4 *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* +======================== +Decode + +Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. +======================== +*/ +static void Decode( UINT4 *output, const unsigned char *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); + } +} + +/* +======================== +MD5_Transform + +The core of the MD5 algorithm, this alters an existing MD5 hash to reflect the addition of 16 +longwords of new data. MD5Update blocks the data and converts bytes into longwords for this +routine. +======================== +*/ +void MD5_Transform( unsigned int state[4], const unsigned char block[64] ) { + unsigned int a, b, c, d, x[16]; + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + Decode( x, block, 64 ); + + MD5STEP( F1, a, b, c, d, x[ 0] + 0xd76aa478, 7 ); + MD5STEP( F1, d, a, b, c, x[ 1] + 0xe8c7b756, 12 ); + MD5STEP( F1, c, d, a, b, x[ 2] + 0x242070db, 17 ); + MD5STEP( F1, b, c, d, a, x[ 3] + 0xc1bdceee, 22 ); + MD5STEP( F1, a, b, c, d, x[ 4] + 0xf57c0faf, 7 ); + MD5STEP( F1, d, a, b, c, x[ 5] + 0x4787c62a, 12 ); + MD5STEP( F1, c, d, a, b, x[ 6] + 0xa8304613, 17 ); + MD5STEP( F1, b, c, d, a, x[ 7] + 0xfd469501, 22 ); + MD5STEP( F1, a, b, c, d, x[ 8] + 0x698098d8, 7 ); + MD5STEP( F1, d, a, b, c, x[ 9] + 0x8b44f7af, 12 ); + MD5STEP( F1, c, d, a, b, x[10] + 0xffff5bb1, 17 ); + MD5STEP( F1, b, c, d, a, x[11] + 0x895cd7be, 22 ); + MD5STEP( F1, a, b, c, d, x[12] + 0x6b901122, 7 ); + MD5STEP( F1, d, a, b, c, x[13] + 0xfd987193, 12 ); + MD5STEP( F1, c, d, a, b, x[14] + 0xa679438e, 17 ); + MD5STEP( F1, b, c, d, a, x[15] + 0x49b40821, 22 ); + + MD5STEP( F2, a, b, c, d, x[ 1] + 0xf61e2562, 5 ); + MD5STEP( F2, d, a, b, c, x[ 6] + 0xc040b340, 9 ); + MD5STEP( F2, c, d, a, b, x[11] + 0x265e5a51, 14 ); + MD5STEP( F2, b, c, d, a, x[ 0] + 0xe9b6c7aa, 20 ); + MD5STEP( F2, a, b, c, d, x[ 5] + 0xd62f105d, 5 ); + MD5STEP( F2, d, a, b, c, x[10] + 0x02441453, 9 ); + MD5STEP( F2, c, d, a, b, x[15] + 0xd8a1e681, 14 ); + MD5STEP( F2, b, c, d, a, x[ 4] + 0xe7d3fbc8, 20 ); + MD5STEP( F2, a, b, c, d, x[ 9] + 0x21e1cde6, 5 ); + MD5STEP( F2, d, a, b, c, x[14] + 0xc33707d6, 9 ); + MD5STEP( F2, c, d, a, b, x[ 3] + 0xf4d50d87, 14 ); + MD5STEP( F2, b, c, d, a, x[ 8] + 0x455a14ed, 20 ); + MD5STEP( F2, a, b, c, d, x[13] + 0xa9e3e905, 5 ); + MD5STEP( F2, d, a, b, c, x[ 2] + 0xfcefa3f8, 9 ); + MD5STEP( F2, c, d, a, b, x[ 7] + 0x676f02d9, 14 ); + MD5STEP( F2, b, c, d, a, x[12] + 0x8d2a4c8a, 20 ); + + MD5STEP( F3, a, b, c, d, x[ 5] + 0xfffa3942, 4 ); + MD5STEP( F3, d, a, b, c, x[ 8] + 0x8771f681, 11 ); + MD5STEP( F3, c, d, a, b, x[11] + 0x6d9d6122, 16 ); + MD5STEP( F3, b, c, d, a, x[14] + 0xfde5380c, 23 ); + MD5STEP( F3, a, b, c, d, x[ 1] + 0xa4beea44, 4 ); + MD5STEP( F3, d, a, b, c, x[ 4] + 0x4bdecfa9, 11 ); + MD5STEP( F3, c, d, a, b, x[ 7] + 0xf6bb4b60, 16 ); + MD5STEP( F3, b, c, d, a, x[10] + 0xbebfbc70, 23 ); + MD5STEP( F3, a, b, c, d, x[13] + 0x289b7ec6, 4 ); + MD5STEP( F3, d, a, b, c, x[ 0] + 0xeaa127fa, 11 ); + MD5STEP( F3, c, d, a, b, x[ 3] + 0xd4ef3085, 16 ); + MD5STEP( F3, b, c, d, a, x[ 6] + 0x04881d05, 23 ); + MD5STEP( F3, a, b, c, d, x[ 9] + 0xd9d4d039, 4 ); + MD5STEP( F3, d, a, b, c, x[12] + 0xe6db99e5, 11 ); + MD5STEP( F3, c, d, a, b, x[15] + 0x1fa27cf8, 16 ); + MD5STEP( F3, b, c, d, a, x[ 2] + 0xc4ac5665, 23 ); + + MD5STEP( F4, a, b, c, d, x[ 0] + 0xf4292244, 6 ); + MD5STEP( F4, d, a, b, c, x[ 7] + 0x432aff97, 10 ); + MD5STEP( F4, c, d, a, b, x[14] + 0xab9423a7, 15 ); + MD5STEP( F4, b, c, d, a, x[ 5] + 0xfc93a039, 21 ); + MD5STEP( F4, a, b, c, d, x[12] + 0x655b59c3, 6 ); + MD5STEP( F4, d, a, b, c, x[ 3] + 0x8f0ccc92, 10 ); + MD5STEP( F4, c, d, a, b, x[10] + 0xffeff47d, 15 ); + MD5STEP( F4, b, c, d, a, x[ 1] + 0x85845dd1, 21 ); + MD5STEP( F4, a, b, c, d, x[ 8] + 0x6fa87e4f, 6 ); + MD5STEP( F4, d, a, b, c, x[15] + 0xfe2ce6e0, 10 ); + MD5STEP( F4, c, d, a, b, x[ 6] + 0xa3014314, 15 ); + MD5STEP( F4, b, c, d, a, x[13] + 0x4e0811a1, 21 ); + MD5STEP( F4, a, b, c, d, x[ 4] + 0xf7537e82, 6 ); + MD5STEP( F4, d, a, b, c, x[11] + 0xbd3af235, 10 ); + MD5STEP( F4, c, d, a, b, x[ 2] + 0x2ad7d2bb, 15 ); + MD5STEP( F4, b, c, d, a, x[ 9] + 0xeb86d391, 21 ); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + // Zeroize sensitive information. + memset( (POINTER)x, 0, sizeof( x ) ); +} + +/* +======================== +MD5_Init + +MD5 initialization. Begins an MD5 operation, writing a new context. +======================== +*/ +void MD5_Init( MD5_CTX *ctx ) { + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* +======================== +MD5_Update + +MD5 block update operation. Continues an MD5 message-digest operation, processing another +message block, and updating the context. +======================== +*/ +void MD5_Update( MD5_CTX *context, unsigned char const *input, size_t inputLen ) { + unsigned int i, index, partLen; + + // Compute number of bytes mod 64 + index = (unsigned int)((context->bits[0] >> 3) & 0x3F); + + // Update number of bits + if ((context->bits[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) { + context->bits[1]++; + } + + context->bits[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + // Transform as many times as possible. + if ( inputLen >= partLen ) { + memcpy( (POINTER)&context->in[index], (POINTER)input, partLen ); + MD5_Transform( context->state, context->in ); + + for ( i = partLen; i + 63 < inputLen; i += 64 ) { + MD5_Transform( context->state, &input[i] ); + } + + index = 0; + } else { + i = 0; + } + + // Buffer remaining input + memcpy( (POINTER)&context->in[index], (POINTER)&input[i], inputLen-i ); +} + +/* +======================== +MD5_Final + +MD5 finalization. Ends an MD5 message-digest operation, writing the message digest and +zero-izing the context. +======================== +*/ +void MD5_Final( MD5_CTX *context, unsigned char digest[16] ) { + unsigned char bits[8]; + unsigned int index, padLen; + + // Save number of bits + Encode( bits, context->bits, 8 ); + + // Pad out to 56 mod 64. + index = (unsigned int)((context->bits[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5_Update( context, PADDING, padLen ); + + // Append length (before padding) + MD5_Update( context, bits, 8 ); + + // Store state in digest + Encode( digest, context->state, 16 ); + + // Zeroize sensitive information. + memset( (POINTER)context, 0, sizeof( *context ) ); +} + +/* +======================== +MD5_BlockChecksum +======================== +*/ + +unsigned int MD5_BlockChecksum( const void *data, size_t length ) { + unsigned char digest[16]; + unsigned int val; + MD5_CTX ctx; + + MD5_Init( &ctx ); + MD5_Update( &ctx, (unsigned char *)data, length ); + MD5_Final( &ctx, (unsigned char *)digest ); + + // Handle it manually to be endian-safe since we don't have access to idSwap. + val = ( digest[3] << 24 | digest[2] << 16 | digest[1] << 8 | digest[0] ) ^ + ( digest[7] << 24 | digest[6] << 16 | digest[5] << 8 | digest[4] ) ^ + ( digest[11] << 24 | digest[10] << 16 | digest[9] << 8 | digest[8] ) ^ + ( digest[15] << 24 | digest[14] << 16 | digest[13] << 8 | digest[12] ); + + return val; +} diff --git a/neo/idlib/hashing/MD5.h b/neo/idlib/hashing/MD5.h new file mode 100644 index 00000000..d1a837d4 --- /dev/null +++ b/neo/idlib/hashing/MD5.h @@ -0,0 +1,24 @@ +#ifndef __MD5_H__ +#define __MD5_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the MD5 message-digest algorithm. + +=============================================================================== +*/ +struct MD5_CTX { + unsigned int state[4]; + unsigned int bits[2]; + unsigned char in[64]; +}; + +void MD5_Init( MD5_CTX *ctx ); +void MD5_Update( MD5_CTX *context, unsigned char const *input, size_t inputLen ); +void MD5_Final( MD5_CTX *context, unsigned char digest[16] ); + +unsigned int MD5_BlockChecksum( const void *data, size_t length ); + +#endif /* !__MD5_H__ */ diff --git a/neo/idlib/math/Angles.cpp b/neo/idlib/math/Angles.cpp new file mode 100644 index 00000000..d739988b --- /dev/null +++ b/neo/idlib/math/Angles.cpp @@ -0,0 +1,240 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +#include + +idAngles ang_zero( 0.0f, 0.0f, 0.0f ); + + +/* +================= +idAngles::Normalize360 + +returns angles normalized to the range [0 <= angle < 360] +================= +*/ +idAngles& idAngles::Normalize360() { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( ( (*this)[i] >= 360.0f ) || ( (*this)[i] < 0.0f ) ) { + (*this)[i] -= floor( (*this)[i] / 360.0f ) * 360.0f; + + if ( (*this)[i] >= 360.0f ) { + (*this)[i] -= 360.0f; + } + if ( (*this)[i] < 0.0f ) { + (*this)[i] += 360.0f; + } + } + } + + return *this; +} + +/* +================= +idAngles::Normalize180 + +returns angles normalized to the range [-180 < angle <= 180] +================= +*/ +idAngles& idAngles::Normalize180() { + Normalize360(); + + if ( pitch > 180.0f ) { + pitch -= 360.0f; + } + + if ( yaw > 180.0f ) { + yaw -= 360.0f; + } + + if ( roll > 180.0f ) { + roll -= 360.0f; + } + return *this; +} + +/* +================= +idAngles::ToVectors +================= +*/ +void idAngles::ToVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) const { + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + + if ( forward ) { + forward->Set( cp * cy, cp * sy, -sp ); + } + + if ( right ) { + right->Set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if ( up ) { + up->Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +/* +================= +idAngles::ToForward +================= +*/ +idVec3 idAngles::ToForward() const { + float sp, sy, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + + return idVec3( cp * cy, cp * sy, -sp ); +} + +/* +================= +idAngles::ToQuat +================= +*/ +idQuat idAngles::ToQuat() const { + float sx, cx, sy, cy, sz, cz; + float sxcy, cxcy, sxsy, cxsy; + + idMath::SinCos( DEG2RAD( yaw ) * 0.5f, sz, cz ); + idMath::SinCos( DEG2RAD( pitch ) * 0.5f, sy, cy ); + idMath::SinCos( DEG2RAD( roll ) * 0.5f, sx, cx ); + + sxcy = sx * cy; + cxcy = cx * cy; + sxsy = sx * sy; + cxsy = cx * sy; + + return idQuat( cxsy*sz - sxcy*cz, -cxsy*cz - sxcy*sz, sxsy*cz - cxcy*sz, cxcy*cz + sxsy*sz ); +} + +/* +================= +idAngles::ToRotation +================= +*/ +idRotation idAngles::ToRotation() const { + idVec3 vec; + float angle, w; + float sx, cx, sy, cy, sz, cz; + float sxcy, cxcy, sxsy, cxsy; + + if ( pitch == 0.0f ) { + if ( yaw == 0.0f ) { + return idRotation( vec3_origin, idVec3( -1.0f, 0.0f, 0.0f ), roll ); + } + if ( roll == 0.0f ) { + return idRotation( vec3_origin, idVec3( 0.0f, 0.0f, -1.0f ), yaw ); + } + } else if ( yaw == 0.0f && roll == 0.0f ) { + return idRotation( vec3_origin, idVec3( 0.0f, -1.0f, 0.0f ), pitch ); + } + + idMath::SinCos( DEG2RAD( yaw ) * 0.5f, sz, cz ); + idMath::SinCos( DEG2RAD( pitch ) * 0.5f, sy, cy ); + idMath::SinCos( DEG2RAD( roll ) * 0.5f, sx, cx ); + + sxcy = sx * cy; + cxcy = cx * cy; + sxsy = sx * sy; + cxsy = cx * sy; + + vec.x = cxsy * sz - sxcy * cz; + vec.y = -cxsy * cz - sxcy * sz; + vec.z = sxsy * cz - cxcy * sz; + w = cxcy * cz + sxsy * sz; + angle = idMath::ACos( w ); + if ( angle == 0.0f ) { + vec.Set( 0.0f, 0.0f, 1.0f ); + } else { + //vec *= (1.0f / sin( angle )); + vec.Normalize(); + vec.FixDegenerateNormal(); + angle *= 2.0f * idMath::M_RAD2DEG; + } + return idRotation( vec3_origin, vec, angle ); +} + +/* +================= +idAngles::ToMat3 +================= +*/ +idMat3 idAngles::ToMat3() const { + idMat3 mat; + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + + mat[ 0 ].Set( cp * cy, cp * sy, -sp ); + mat[ 1 ].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[ 2 ].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return mat; +} + +/* +================= +idAngles::ToMat4 +================= +*/ +idMat4 idAngles::ToMat4() const { + return ToMat3().ToMat4(); +} + +/* +================= +idAngles::ToAngularVelocity +================= +*/ +idVec3 idAngles::ToAngularVelocity() const { + idRotation rotation = idAngles::ToRotation(); + return rotation.GetVec() * DEG2RAD( rotation.GetAngle() ); +} + +/* +============= +idAngles::ToString +============= +*/ +const char *idAngles::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/Angles.h b/neo/idlib/math/Angles.h new file mode 100644 index 00000000..7b5932f6 --- /dev/null +++ b/neo/idlib/math/Angles.h @@ -0,0 +1,262 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_ANGLES_H__ +#define __MATH_ANGLES_H__ + +/* +=============================================================================== + + Euler angles + +=============================================================================== +*/ + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +class idVec3; +class idQuat; +class idRotation; +class idMat3; +class idMat4; + +class idAngles { +public: + float pitch; + float yaw; + float roll; + + idAngles(); + idAngles( float pitch, float yaw, float roll ); + explicit idAngles( const idVec3 &v ); + + void Set( float pitch, float yaw, float roll ); + idAngles & Zero(); + + float operator[]( int index ) const; + float & operator[]( int index ); + idAngles operator-() const; // negate angles, in general not the inverse rotation + idAngles & operator=( const idAngles &a ); + idAngles operator+( const idAngles &a ) const; + idAngles & operator+=( const idAngles &a ); + idAngles operator-( const idAngles &a ) const; + idAngles & operator-=( const idAngles &a ); + idAngles operator*( const float a ) const; + idAngles & operator*=( const float a ); + idAngles operator/( const float a ) const; + idAngles & operator/=( const float a ); + + friend idAngles operator*( const float a, const idAngles &b ); + + bool Compare( const idAngles &a ) const; // exact compare, no epsilon + bool Compare( const idAngles &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idAngles &a ) const; // exact compare, no epsilon + bool operator!=( const idAngles &a ) const; // exact compare, no epsilon + + idAngles & Normalize360(); // normalizes 'this' + idAngles & Normalize180(); // normalizes 'this' + + void Clamp( const idAngles &min, const idAngles &max ); + + int GetDimension() const; + + void ToVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ) const; + idVec3 ToForward() const; + idQuat ToQuat() const; + idRotation ToRotation() const; + idMat3 ToMat3() const; + idMat4 ToMat4() const; + idVec3 ToAngularVelocity() const; + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; +}; + +extern idAngles ang_zero; + +ID_INLINE idAngles::idAngles() { +} + +ID_INLINE idAngles::idAngles( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE idAngles::idAngles( const idVec3 &v ) { + this->pitch = v[0]; + this->yaw = v[1]; + this->roll = v[2]; +} + +ID_INLINE void idAngles::Set( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE idAngles &idAngles::Zero() { + pitch = yaw = roll = 0.0f; + return *this; +} + +ID_INLINE float idAngles::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +ID_INLINE float &idAngles::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +ID_INLINE idAngles idAngles::operator-() const { + return idAngles( -pitch, -yaw, -roll ); +} + +ID_INLINE idAngles &idAngles::operator=( const idAngles &a ) { + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; + return *this; +} + +ID_INLINE idAngles idAngles::operator+( const idAngles &a ) const { + return idAngles( pitch + a.pitch, yaw + a.yaw, roll + a.roll ); +} + +ID_INLINE idAngles& idAngles::operator+=( const idAngles &a ) { + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + + return *this; +} + +ID_INLINE idAngles idAngles::operator-( const idAngles &a ) const { + return idAngles( pitch - a.pitch, yaw - a.yaw, roll - a.roll ); +} + +ID_INLINE idAngles& idAngles::operator-=( const idAngles &a ) { + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + + return *this; +} + +ID_INLINE idAngles idAngles::operator*( const float a ) const { + return idAngles( pitch * a, yaw * a, roll * a ); +} + +ID_INLINE idAngles& idAngles::operator*=( float a ) { + pitch *= a; + yaw *= a; + roll *= a; + return *this; +} + +ID_INLINE idAngles idAngles::operator/( const float a ) const { + float inva = 1.0f / a; + return idAngles( pitch * inva, yaw * inva, roll * inva ); +} + +ID_INLINE idAngles& idAngles::operator/=( float a ) { + float inva = 1.0f / a; + pitch *= inva; + yaw *= inva; + roll *= inva; + return *this; +} + +ID_INLINE idAngles operator*( const float a, const idAngles &b ) { + return idAngles( a * b.pitch, a * b.yaw, a * b.roll ); +} + +ID_INLINE bool idAngles::Compare( const idAngles &a ) const { + return ( ( a.pitch == pitch ) && ( a.yaw == yaw ) && ( a.roll == roll ) ); +} + +ID_INLINE bool idAngles::Compare( const idAngles &a, const float epsilon ) const { + if ( idMath::Fabs( pitch - a.pitch ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( yaw - a.yaw ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( roll - a.roll ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idAngles::operator==( const idAngles &a ) const { + return Compare( a ); +} + +ID_INLINE bool idAngles::operator!=( const idAngles &a ) const { + return !Compare( a ); +} + +ID_INLINE void idAngles::Clamp( const idAngles &min, const idAngles &max ) { + if ( pitch < min.pitch ) { + pitch = min.pitch; + } else if ( pitch > max.pitch ) { + pitch = max.pitch; + } + if ( yaw < min.yaw ) { + yaw = min.yaw; + } else if ( yaw > max.yaw ) { + yaw = max.yaw; + } + if ( roll < min.roll ) { + roll = min.roll; + } else if ( roll > max.roll ) { + roll = max.roll; + } +} + +ID_INLINE int idAngles::GetDimension() const { + return 3; +} + +ID_INLINE const float *idAngles::ToFloatPtr() const { + return &pitch; +} + +ID_INLINE float *idAngles::ToFloatPtr() { + return &pitch; +} + +#endif /* !__MATH_ANGLES_H__ */ diff --git a/neo/idlib/math/Complex.cpp b/neo/idlib/math/Complex.cpp new file mode 100644 index 00000000..7855b061 --- /dev/null +++ b/neo/idlib/math/Complex.cpp @@ -0,0 +1,41 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +idComplex complex_origin( 0.0f, 0.0f ); + +/* +============= +idComplex::ToString +============= +*/ +const char *idComplex::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/Complex.h b/neo/idlib/math/Complex.h new file mode 100644 index 00000000..ad681e6b --- /dev/null +++ b/neo/idlib/math/Complex.h @@ -0,0 +1,348 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_COMPLEX_H__ +#define __MATH_COMPLEX_H__ + +/* +=============================================================================== + + Complex number + +=============================================================================== +*/ + +class idComplex { +public: + float r; // real part + float i; // imaginary part + + idComplex(); + idComplex( const float r, const float i ); + + void Set( const float r, const float i ); + void Zero(); + + float operator[]( int index ) const; + float & operator[]( int index ); + + idComplex operator-() const; + idComplex & operator=( const idComplex &a ); + + idComplex operator*( const idComplex &a ) const; + idComplex operator/( const idComplex &a ) const; + idComplex operator+( const idComplex &a ) const; + idComplex operator-( const idComplex &a ) const; + + idComplex & operator*=( const idComplex &a ); + idComplex & operator/=( const idComplex &a ); + idComplex & operator+=( const idComplex &a ); + idComplex & operator-=( const idComplex &a ); + + idComplex operator*( const float a ) const; + idComplex operator/( const float a ) const; + idComplex operator+( const float a ) const; + idComplex operator-( const float a ) const; + + idComplex & operator*=( const float a ); + idComplex & operator/=( const float a ); + idComplex & operator+=( const float a ); + idComplex & operator-=( const float a ); + + friend idComplex operator*( const float a, const idComplex &b ); + friend idComplex operator/( const float a, const idComplex &b ); + friend idComplex operator+( const float a, const idComplex &b ); + friend idComplex operator-( const float a, const idComplex &b ); + + bool Compare( const idComplex &a ) const; // exact compare, no epsilon + bool Compare( const idComplex &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idComplex &a ) const; // exact compare, no epsilon + bool operator!=( const idComplex &a ) const; // exact compare, no epsilon + + idComplex Reciprocal() const; + idComplex Sqrt() const; + float Abs() const; + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; +}; + +extern idComplex complex_origin; +#define complex_zero complex_origin + +ID_INLINE idComplex::idComplex() { +} + +ID_INLINE idComplex::idComplex( const float r, const float i ) { + this->r = r; + this->i = i; +} + +ID_INLINE void idComplex::Set( const float r, const float i ) { + this->r = r; + this->i = i; +} + +ID_INLINE void idComplex::Zero() { + r = i = 0.0f; +} + +ID_INLINE float idComplex::operator[]( int index ) const { + assert( index >= 0 && index < 2 ); + return ( &r )[ index ]; +} + +ID_INLINE float& idComplex::operator[]( int index ) { + assert( index >= 0 && index < 2 ); + return ( &r )[ index ]; +} + +ID_INLINE idComplex idComplex::operator-() const { + return idComplex( -r, -i ); +} + +ID_INLINE idComplex &idComplex::operator=( const idComplex &a ) { + r = a.r; + i = a.i; + return *this; +} + +ID_INLINE idComplex idComplex::operator*( const idComplex &a ) const { + return idComplex( r * a.r - i * a.i, i * a.r + r * a.i ); +} + +ID_INLINE idComplex idComplex::operator/( const idComplex &a ) const { + float s, t; + if ( idMath::Fabs( a.r ) >= idMath::Fabs( a.i ) ) { + s = a.i / a.r; + t = 1.0f / ( a.r + s * a.i ); + return idComplex( ( r + s * i ) * t, ( i - s * r ) * t ); + } else { + s = a.r / a.i; + t = 1.0f / ( s * a.r + a.i ); + return idComplex( ( r * s + i ) * t, ( i * s - r ) * t ); + } +} + +ID_INLINE idComplex idComplex::operator+( const idComplex &a ) const { + return idComplex( r + a.r, i + a.i ); +} + +ID_INLINE idComplex idComplex::operator-( const idComplex &a ) const { + return idComplex( r - a.r, i - a.i ); +} + +ID_INLINE idComplex &idComplex::operator*=( const idComplex &a ) { + *this = idComplex( r * a.r - i * a.i, i * a.r + r * a.i ); + return *this; +} + +ID_INLINE idComplex &idComplex::operator/=( const idComplex &a ) { + float s, t; + if ( idMath::Fabs( a.r ) >= idMath::Fabs( a.i ) ) { + s = a.i / a.r; + t = 1.0f / ( a.r + s * a.i ); + *this = idComplex( ( r + s * i ) * t, ( i - s * r ) * t ); + } else { + s = a.r / a.i; + t = 1.0f / ( s * a.r + a.i ); + *this = idComplex( ( r * s + i ) * t, ( i * s - r ) * t ); + } + return *this; +} + +ID_INLINE idComplex &idComplex::operator+=( const idComplex &a ) { + r += a.r; + i += a.i; + return *this; +} + +ID_INLINE idComplex &idComplex::operator-=( const idComplex &a ) { + r -= a.r; + i -= a.i; + return *this; +} + +ID_INLINE idComplex idComplex::operator*( const float a ) const { + return idComplex( r * a, i * a ); +} + +ID_INLINE idComplex idComplex::operator/( const float a ) const { + float s = 1.0f / a; + return idComplex( r * s, i * s ); +} + +ID_INLINE idComplex idComplex::operator+( const float a ) const { + return idComplex( r + a, i ); +} + +ID_INLINE idComplex idComplex::operator-( const float a ) const { + return idComplex( r - a, i ); +} + +ID_INLINE idComplex &idComplex::operator*=( const float a ) { + r *= a; + i *= a; + return *this; +} + +ID_INLINE idComplex &idComplex::operator/=( const float a ) { + float s = 1.0f / a; + r *= s; + i *= s; + return *this; +} + +ID_INLINE idComplex &idComplex::operator+=( const float a ) { + r += a; + return *this; +} + +ID_INLINE idComplex &idComplex::operator-=( const float a ) { + r -= a; + return *this; +} + +ID_INLINE idComplex operator*( const float a, const idComplex &b ) { + return idComplex( a * b.r, a * b.i ); +} + +ID_INLINE idComplex operator/( const float a, const idComplex &b ) { + float s, t; + if ( idMath::Fabs( b.r ) >= idMath::Fabs( b.i ) ) { + s = b.i / b.r; + t = a / ( b.r + s * b.i ); + return idComplex( t, - s * t ); + } else { + s = b.r / b.i; + t = a / ( s * b.r + b.i ); + return idComplex( s * t, - t ); + } +} + +ID_INLINE idComplex operator+( const float a, const idComplex &b ) { + return idComplex( a + b.r, b.i ); +} + +ID_INLINE idComplex operator-( const float a, const idComplex &b ) { + return idComplex( a - b.r, -b.i ); +} + +ID_INLINE idComplex idComplex::Reciprocal() const { + float s, t; + if ( idMath::Fabs( r ) >= idMath::Fabs( i ) ) { + s = i / r; + t = 1.0f / ( r + s * i ); + return idComplex( t, - s * t ); + } else { + s = r / i; + t = 1.0f / ( s * r + i ); + return idComplex( s * t, - t ); + } +} + +ID_INLINE idComplex idComplex::Sqrt() const { + float x, y, w; + + if ( r == 0.0f && i == 0.0f ) { + return idComplex( 0.0f, 0.0f ); + } + x = idMath::Fabs( r ); + y = idMath::Fabs( i ); + if ( x >= y ) { + w = y / x; + w = idMath::Sqrt( x ) * idMath::Sqrt( 0.5f * ( 1.0f + idMath::Sqrt( 1.0f + w * w ) ) ); + } else { + w = x / y; + w = idMath::Sqrt( y ) * idMath::Sqrt( 0.5f * ( w + idMath::Sqrt( 1.0f + w * w ) ) ); + } + if ( w == 0.0f ) { + return idComplex( 0.0f, 0.0f ); + } + if ( r >= 0.0f ) { + return idComplex( w, 0.5f * i / w ); + } else { + return idComplex( 0.5f * y / w, ( i >= 0.0f ) ? w : -w ); + } +} + +ID_INLINE float idComplex::Abs() const { + float x, y, t; + x = idMath::Fabs( r ); + y = idMath::Fabs( i ); + if ( x == 0.0f ) { + return y; + } else if ( y == 0.0f ) { + return x; + } else if ( x > y ) { + t = y / x; + return x * idMath::Sqrt( 1.0f + t * t ); + } else { + t = x / y; + return y * idMath::Sqrt( 1.0f + t * t ); + } +} + +ID_INLINE bool idComplex::Compare( const idComplex &a ) const { + return ( ( r == a.r ) && ( i == a.i ) ); +} + +ID_INLINE bool idComplex::Compare( const idComplex &a, const float epsilon ) const { + if ( idMath::Fabs( r - a.r ) > epsilon ) { + return false; + } + if ( idMath::Fabs( i - a.i ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idComplex::operator==( const idComplex &a ) const { + return Compare( a ); +} + +ID_INLINE bool idComplex::operator!=( const idComplex &a ) const { + return !Compare( a ); +} + +ID_INLINE int idComplex::GetDimension() const { + return 2; +} + +ID_INLINE const float *idComplex::ToFloatPtr() const { + return &r; +} + +ID_INLINE float *idComplex::ToFloatPtr() { + return &r; +} + +#endif /* !__MATH_COMPLEX_H__ */ diff --git a/neo/idlib/math/Curve.h b/neo/idlib/math/Curve.h new file mode 100644 index 00000000..c4f328fa --- /dev/null +++ b/neo/idlib/math/Curve.h @@ -0,0 +1,2542 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_CURVE_H__ +#define __MATH_CURVE_H__ + +/* +=============================================================================== + + Curve base template. + +=============================================================================== +*/ + +template< class type > +class idCurve { +public: + idCurve(); + virtual ~idCurve(); + + virtual int AddValue( const float time, const type &value ); + virtual void RemoveIndex( const int index ) { values.RemoveIndex(index); times.RemoveIndex(index); changed = true; } + virtual void Clear() { values.Clear(); times.Clear(); currentIndex = -1; changed = true; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + + virtual bool IsDone( const float time ) const; + + int GetNumValues() const { return values.Num(); } + void SetValue( const int index, const type &value ) { values[index] = value; changed = true; } + type GetValue( const int index ) const { return values[index]; } + type * GetValueAddress( const int index ) { return &values[index]; } + float GetTime( const int index ) const { return times[index]; } + + float GetLengthForTime( const float time ) const; + float GetTimeForLength( const float length, const float epsilon = 0.1f ) const; + float GetLengthBetweenKnots( const int i0, const int i1 ) const; + + void MakeUniform( const float totalTime ); + void SetConstantSpeed( const float totalTime ); + void ShiftTime( const float deltaTime ); + void Translate( const type &translation ); + +protected: + + idList times; // knots + idList values; // knot values + + mutable int currentIndex; // cached index for fast lookup + mutable bool changed; // set whenever the curve changes + + int IndexForTime( const float time ) const; + float TimeForIndex( const int index ) const; + type ValueForIndex( const int index ) const; + + float GetSpeed( const float time ) const; + float RombergIntegral( const float t0, const float t1, const int order ) const; +}; + +/* +==================== +idCurve::idCurve +==================== +*/ +template< class type > +ID_INLINE idCurve::idCurve() { + currentIndex = -1; + changed = false; +} + +/* +==================== +idCurve::~idCurve +==================== +*/ +template< class type > +ID_INLINE idCurve::~idCurve() { +} + +/* +==================== +idCurve::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve::AddValue( const float time, const type &value ) { + int i; + + i = IndexForTime( time ); + times.Insert( time, i ); + values.Insert( value, i ); + changed = true; + return i; +} + +/* +==================== +idCurve::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentValue( const float time ) const { + int i; + + i = IndexForTime( time ); + if ( i >= values.Num() ) { + return values[values.Num() - 1]; + } else { + return values[i]; + } +} + +/* +==================== +idCurve::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentFirstDerivative( const float time ) const { + return ( values[0] - values[0] ); //-V501 +} + +/* +==================== +idCurve::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentSecondDerivative( const float time ) const { + return ( values[0] - values[0] ); //-V501 +} + +/* +==================== +idCurve::IsDone +==================== +*/ +template< class type > +ID_INLINE bool idCurve::IsDone( const float time ) const { + return ( time >= times[ times.Num() - 1 ] ); +} + +/* +==================== +idCurve::GetSpeed +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetSpeed( const float time ) const { + int i; + float speed; + type value; + + value = GetCurrentFirstDerivative( time ); + for ( speed = 0.0f, i = 0; i < value.GetDimension(); i++ ) { + speed += value[i] * value[i]; + } + return idMath::Sqrt( speed ); +} + +/* +==================== +idCurve::RombergIntegral +==================== +*/ +template< class type > +ID_INLINE float idCurve::RombergIntegral( const float t0, const float t1, const int order ) const { + int i, j, k, m, n; + float sum, delta; + float *temp[2]; + + temp[0] = (float *) _alloca16( order * sizeof( float ) ); + temp[1] = (float *) _alloca16( order * sizeof( float ) ); + + delta = t1 - t0; + temp[0][0] = 0.5f * delta * ( GetSpeed( t0 ) + GetSpeed( t1 ) ); + + for ( i = 2, m = 1; i <= order; i++, m *= 2, delta *= 0.5f ) { + + // approximate using the trapezoid rule + sum = 0.0f; + for ( j = 1; j <= m; j++ ) { + sum += GetSpeed( t0 + delta * ( j - 0.5f ) ); + } + + // Richardson extrapolation + temp[1][0] = 0.5f * ( temp[0][0] + delta * sum ); + for ( k = 1, n = 4; k < i; k++, n *= 4 ) { + temp[1][k] = ( n * temp[1][k-1] - temp[0][k-1] ) / ( n - 1 ); + } + + for ( j = 0; j < i; j++ ) { + temp[0][j] = temp[1][j]; + } + } + return temp[0][order-1]; +} + +/* +==================== +idCurve::GetLengthBetweenKnots +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetLengthBetweenKnots( const int i0, const int i1 ) const { + float length = 0.0f; + for ( int i = i0; i < i1; i++ ) { + length += RombergIntegral( times[i], times[i+1], 5 ); + } + return length; +} + +/* +==================== +idCurve::GetLengthForTime +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetLengthForTime( const float time ) const { + float length = 0.0f; + int index = IndexForTime( time ); + for ( int i = 0; i < index; i++ ) { + length += RombergIntegral( times[i], times[i+1], 5 ); + } + length += RombergIntegral( times[index], time, 5 ); + return length; +} + +/* +==================== +idCurve::GetTimeForLength +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetTimeForLength( const float length, const float epsilon ) const { + int i, index; + float *accumLength, totalLength, len0, len1, t, diff; + + if ( length <= 0.0f ) { + return times[0]; + } + + accumLength = (float *) _alloca16( values.Num() * sizeof( float ) ); + totalLength = 0.0f; + for ( index = 0; index < values.Num() - 1; index++ ) { + totalLength += GetLengthBetweenKnots( index, index + 1 ); + accumLength[index] = totalLength; + if ( length < accumLength[index] ) { + break; + } + } + + if ( index >= values.Num() - 1 ) { + return times[times.Num() - 1]; + } + + if ( index == 0 ) { + len0 = length; + len1 = accumLength[0]; + } else { + len0 = length - accumLength[index-1]; + len1 = accumLength[index] - accumLength[index-1]; + } + + // invert the arc length integral using Newton's method + t = ( times[index+1] - times[index] ) * len0 / len1; + for ( i = 0; i < 32; i++ ) { + diff = RombergIntegral( times[index], times[index] + t, 5 ) - len0; + if ( idMath::Fabs( diff ) <= epsilon ) { + return times[index] + t; + } + t -= diff / GetSpeed( times[index] + t ); + } + return times[index] + t; +} + +/* +==================== +idCurve::MakeUniform +==================== +*/ +template< class type > +ID_INLINE void idCurve::MakeUniform( const float totalTime ) { + int i, n; + + n = times.Num() - 1; + for ( i = 0; i <= n; i++ ) { + times[i] = i * totalTime / n; + } + changed = true; +} + +/* +==================== +idCurve::SetConstantSpeed +==================== +*/ +template< class type > +ID_INLINE void idCurve::SetConstantSpeed( const float totalTime ) { + int i, j; + float *length, totalLength, scale, t; + + length = (float *) _alloca16( values.Num() * sizeof( float ) ); + totalLength = 0.0f; + for ( i = 0; i < values.Num() - 1; i++ ) { + length[i] = GetLengthBetweenKnots( i, i + 1 ); + totalLength += length[i]; + } + scale = totalTime / totalLength; + for ( t = 0.0f, i = 0; i < times.Num() - 1; i++ ) { + times[i] = t; + t += scale * length[i]; + } + times[times.Num() - 1] = totalTime; + changed = true; +} + +/* +==================== +idCurve::ShiftTime +==================== +*/ +template< class type > +ID_INLINE void idCurve::ShiftTime( const float deltaTime ) { + for ( int i = 0; i < times.Num(); i++ ) { + times[i] += deltaTime; + } + changed = true; +} + +/* +==================== +idCurve::Translate +==================== +*/ +template< class type > +ID_INLINE void idCurve::Translate( const type &translation ) { + for ( int i = 0; i < values.Num(); i++ ) { + values[i] += translation; + } + changed = true; +} + +/* +==================== +idCurve::IndexForTime + + find the index for the first time greater than or equal to the given time +==================== +*/ +template< class type > +ID_INLINE int idCurve::IndexForTime( const float time ) const { + int len, mid, offset, res; + + if ( currentIndex >= 0 && currentIndex <= times.Num() ) { + // use the cached index if it is still valid + if ( currentIndex == 0 ) { + if ( time <= times[currentIndex] ) { + return currentIndex; + } + } else if ( currentIndex == times.Num() ) { + if ( time > times[currentIndex-1] ) { + return currentIndex; + } + } else if ( time > times[currentIndex-1] && time <= times[currentIndex] ) { + return currentIndex; + } else if ( time > times[currentIndex] && ( currentIndex+1 == times.Num() || time <= times[currentIndex+1] ) ) { + // use the next index + currentIndex++; + return currentIndex; + } + } + + // use binary search to find the index for the given time + len = times.Num(); + mid = len; + offset = 0; + res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( time == times[offset+mid] ) { + return offset+mid; + } else if ( time > times[offset+mid] ) { + offset += mid; + len -= mid; + res = 1; + } else { + len -= mid; + res = 0; + } + } + currentIndex = offset+res; + return currentIndex; +} + +/* +==================== +idCurve::ValueForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::ValueForIndex( const int index ) const { + int n = values.Num()-1; + + if ( index < 0 ) { + return values[0] + index * ( values[1] - values[0] ); + } else if ( index > n ) { + return values[n] + ( index - n ) * ( values[n] - values[n-1] ); + } + return values[index]; +} + +/* +==================== +idCurve::TimeForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE float idCurve::TimeForIndex( const int index ) const { + int n = times.Num()-1; + + if ( index < 0 ) { + return times[0] + index * ( times[1] - times[0] ); + } else if ( index > n ) { + return times[n] + ( index - n ) * ( times[n] - times[n-1] ); + } + return times[index]; +} + + +/* +=============================================================================== + + Bezier Curve template. + The degree of the polynomial equals the number of knots minus one. + +=============================================================================== +*/ + +template< class type > +class idCurve_Bezier : public idCurve { +public: + idCurve_Bezier(); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int order, const float t, float *bvals ) const; + void BasisFirstDerivative( const int order, const float t, float *bvals ) const; + void BasisSecondDerivative( const int order, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_Bezier::idCurve_Bezier +==================== +*/ +template< class type > +ID_INLINE idCurve_Bezier::idCurve_Bezier() { +} + +/* +==================== +idCurve_Bezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentValue( const float time ) const { + int i; + float *bvals; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + Basis( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + return v; +} + +/* +==================== +idCurve_Bezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentFirstDerivative( const float time ) const { + int i; + float *bvals, d; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + BasisFirstDerivative( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + d = ( this->times[this->times.Num()-1] - this->times[0] ); + return ( (float) (this->values.Num()-1) / d ) * v; +} + +/* +==================== +idCurve_Bezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentSecondDerivative( const float time ) const { + int i; + float *bvals, d; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + BasisSecondDerivative( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + d = ( this->times[this->times.Num()-1] - this->times[0] ); + return ( (float) (this->values.Num()-2) * (this->values.Num()-1) / ( d * d ) ) * v; +} + +/* +==================== +idCurve_Bezier::Basis + + bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::Basis( const int order, const float t, float *bvals ) const { + int i, j, d; + float *c, c1, c2, s, o, ps, po; + + bvals[0] = 1.0f; + d = order - 1; + if ( d <= 0 ) { + return; + } + + c = (float *) _alloca16( (d+1) * sizeof( float ) ); + s = (float) ( t - this->times[0] ) / ( this->times[this->times.Num()-1] - this->times[0] ); + o = 1.0f - s; + ps = s; + po = o; + + for ( i = 1; i < d; i++ ) { + c[i] = 1.0f; + } + for ( i = 1; i < d; i++ ) { + c[i-1] = 0.0f; + c1 = c[i]; + c[i] = 1.0f; + for ( j = i+1; j <= d; j++ ) { + c2 = c[j]; + c[j] = c1 + c[j-1]; + c1 = c2; + } + bvals[i] = c[d] * ps; + ps *= s; + } + for ( i = d-1; i >= 0; i-- ) { + bvals[i] *= po; + po *= o; + } + bvals[d] = ps; +} + +/* +==================== +idCurve_Bezier::BasisFirstDerivative + + first derivative of bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::BasisFirstDerivative( const int order, const float t, float *bvals ) const { + int i; + + Basis( order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + } +} + +/* +==================== +idCurve_Bezier::BasisSecondDerivative + + second derivative of bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::BasisSecondDerivative( const int order, const float t, float *bvals ) const { + int i; + + BasisFirstDerivative( order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + } +} + + +/* +=============================================================================== + + Quadratic Bezier Curve template. + Should always have exactly three knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_QuadraticBezier : public idCurve { + +public: + idCurve_QuadraticBezier(); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const float t, float *bvals ) const; + void BasisFirstDerivative( const float t, float *bvals ) const; + void BasisSecondDerivative( const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_QuadraticBezier::idCurve_QuadraticBezier +==================== +*/ +template< class type > +ID_INLINE idCurve_QuadraticBezier::idCurve_QuadraticBezier() { +} + + +/* +==================== +idCurve_QuadraticBezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentValue( const float time ) const { + float bvals[3]; + assert( this->values.Num() == 3 ); + Basis( time, bvals ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ); +} + +/* +==================== +idCurve_QuadraticBezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentFirstDerivative( const float time ) const { + float bvals[3], d; + assert( this->values.Num() == 3 ); + BasisFirstDerivative( time, bvals ); + d = ( this->times[2] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ) / d; +} + +/* +==================== +idCurve_QuadraticBezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentSecondDerivative( const float time ) const { + float bvals[3], d; + assert( this->values.Num() == 3 ); + BasisSecondDerivative( time, bvals ); + d = ( this->times[2] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ) / ( d * d ); +} + +/* +==================== +idCurve_QuadraticBezier::Basis + + quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::Basis( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + float s2 = s1 * s1; + bvals[0] = s2 - 2.0f * s1 + 1.0f; + bvals[1] = -2.0f * s2 + 2.0f * s1; + bvals[2] = s2; +} + +/* +==================== +idCurve_QuadraticBezier::BasisFirstDerivative + + first derivative of quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::BasisFirstDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + bvals[0] = 2.0f * s1 - 2.0f; + bvals[1] = -4.0f * s1 + 2.0f; + bvals[2] = 2.0f * s1; +} + +/* +==================== +idCurve_QuadraticBezier::BasisSecondDerivative + + second derivative of quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::BasisSecondDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + bvals[0] = 2.0f; + bvals[1] = -4.0f; + bvals[2] = 2.0f; +} + + +/* +=============================================================================== + + Cubic Bezier Curve template. + Should always have exactly four knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_CubicBezier : public idCurve { + +public: + idCurve_CubicBezier(); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const float t, float *bvals ) const; + void BasisFirstDerivative( const float t, float *bvals ) const; + void BasisSecondDerivative( const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_CubicBezier::idCurve_CubicBezier +==================== +*/ +template< class type > +ID_INLINE idCurve_CubicBezier::idCurve_CubicBezier() { +} + + +/* +==================== +idCurve_CubicBezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentValue( const float time ) const { + float bvals[4]; + assert( this->values.Num() == 4 ); + Basis( time, bvals ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ); +} + +/* +==================== +idCurve_CubicBezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentFirstDerivative( const float time ) const { + float bvals[4], d; + assert( this->values.Num() == 4 ); + BasisFirstDerivative( time, bvals ); + d = ( this->times[3] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ) / d; +} + +/* +==================== +idCurve_CubicBezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentSecondDerivative( const float time ) const { + float bvals[4], d; + assert( this->values.Num() == 4 ); + BasisSecondDerivative( time, bvals ); + d = ( this->times[3] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ) / ( d * d ); +} + +/* +==================== +idCurve_CubicBezier::Basis + + cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::Basis( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + float s2 = s1 * s1; + float s3 = s2 * s1; + bvals[0] = -s3 + 3.0f * s2 - 3.0f * s1 + 1.0f; + bvals[1] = 3.0f * s3 - 6.0f * s2 + 3.0f * s1; + bvals[2] = -3.0f * s3 + 3.0f * s2; + bvals[3] = s3; +} + +/* +==================== +idCurve_CubicBezier::BasisFirstDerivative + + first derivative of cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::BasisFirstDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + float s2 = s1 * s1; + bvals[0] = -3.0f * s2 + 6.0f * s1 - 3.0f; + bvals[1] = 9.0f * s2 - 12.0f * s1 + 3.0f; + bvals[2] = -9.0f * s2 + 6.0f * s1; + bvals[3] = 3.0f * s2; +} + +/* +==================== +idCurve_CubicBezier::BasisSecondDerivative + + second derivative of cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::BasisSecondDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + bvals[0] = -6.0f * s1 + 6.0f; + bvals[1] = 18.0f * s1 - 12.0f; + bvals[2] = -18.0f * s1 + 6.0f; + bvals[3] = 6.0f * s1; +} + + +/* +=============================================================================== + + Spline base template. + +=============================================================================== +*/ + +template< class type > +class idCurve_Spline : public idCurve { + +public: + enum boundary_t { BT_FREE, BT_CLAMPED, BT_CLOSED }; + + idCurve_Spline(); + + virtual bool IsDone( const float time ) const; + + virtual void SetBoundaryType( const boundary_t bt ) { boundaryType = bt; this->changed = true; } + virtual boundary_t GetBoundaryType() const { return boundaryType; } + + virtual void SetCloseTime( const float t ) { closeTime = t; this->changed = true; } + virtual float GetCloseTime() { return boundaryType == BT_CLOSED ? closeTime : 0.0f; } + +protected: + boundary_t boundaryType; + float closeTime; + + type ValueForIndex( const int index ) const; + float TimeForIndex( const int index ) const; + float ClampedTime( const float t ) const; +}; + +/* +==================== +idCurve_Spline::idCurve_Spline +==================== +*/ +template< class type > +ID_INLINE idCurve_Spline::idCurve_Spline() { + boundaryType = BT_FREE; + closeTime = 0.0f; +} + +/* +==================== +idCurve_Spline::ValueForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Spline::ValueForIndex( const int index ) const { + int n = this->values.Num()-1; + + if ( index < 0 ) { + if ( boundaryType == BT_CLOSED ) { + return this->values[ this->values.Num() + index % this->values.Num() ]; + } + else { + return this->values[0] + index * ( this->values[1] - this->values[0] ); + } + } + else if ( index > n ) { + if ( boundaryType == BT_CLOSED ) { + return this->values[ index % this->values.Num() ]; + } + else { + return this->values[n] + ( index - n ) * ( this->values[n] - this->values[n-1] ); + } + } + return this->values[index]; +} + +/* +==================== +idCurve_Spline::TimeForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE float idCurve_Spline::TimeForIndex( const int index ) const { + int n = this->times.Num()-1; + + if ( index < 0 ) { + if ( boundaryType == BT_CLOSED ) { + return ( index / this->times.Num() ) * ( this->times[n] + closeTime ) - ( this->times[n] + closeTime - this->times[this->times.Num() + index % this->times.Num()] ); + } + else { + return this->times[0] + index * ( this->times[1] - this->times[0] ); + } + } + else if ( index > n ) { + if ( boundaryType == BT_CLOSED ) { + return ( index / this->times.Num() ) * ( this->times[n] + closeTime ) + this->times[index % this->times.Num()]; + } + else { + return this->times[n] + ( index - n ) * ( this->times[n] - this->times[n-1] ); + } + } + return this->times[index]; +} + +/* +==================== +idCurve_Spline::ClampedTime + + return the clamped time based on the boundary type +==================== +*/ +template< class type > +ID_INLINE float idCurve_Spline::ClampedTime( const float t ) const { + if ( boundaryType == BT_CLAMPED ) { + if ( t < this->times[0] ) { + return this->times[0]; + } + else if ( t >= this->times[this->times.Num()-1] ) { + return this->times[this->times.Num()-1]; + } + } + return t; +} + +/* +==================== +idCurve_Spline::IsDone +==================== +*/ +template< class type > +ID_INLINE bool idCurve_Spline::IsDone( const float time ) const { + return ( boundaryType != BT_CLOSED && time >= this->times[ this->times.Num() - 1 ] ); +} + + +/* +=============================================================================== + + Cubic Interpolating Spline template. + The curve goes through all the knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_NaturalCubicSpline : public idCurve_Spline { +public: + idCurve_NaturalCubicSpline(); + + virtual void Clear() { idCurve_Spline::Clear(); this->values.Clear(); b.Clear(); c.Clear(); d.Clear(); } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + mutable idListb; + mutable idListc; + mutable idListd; + + void Setup() const; + void SetupFree() const; + void SetupClamped() const; + void SetupClosed() const; +}; + +/* +==================== +idCurve_NaturalCubicSpline::idCurve_NaturalCubicSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_NaturalCubicSpline::idCurve_NaturalCubicSpline() { +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentValue( const float time ) const { + float clampedTime = this->ClampedTime( time ); + int i = this->IndexForTime( clampedTime ); + float s = time - this->TimeForIndex( i ); + Setup(); + return ( this->values[i] + s * ( b[i] + s * ( c[i] + s * d[i] ) ) ); +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentFirstDerivative( const float time ) const { + float clampedTime = this->ClampedTime( time ); + int i = this->IndexForTime( clampedTime ); + float s = time - this->TimeForIndex( i ); + Setup(); + return ( b[i] + s * ( 2.0f * c[i] + 3.0f * s * d[i] ) ); +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentSecondDerivative( const float time ) const { + float clampedTime = this->ClampedTime( time ); + int i = this->IndexForTime( clampedTime ); + float s = time - this->TimeForIndex( i ); + Setup(); + return ( 2.0f * c[i] + 6.0f * s * d[i] ); +} + +/* +==================== +idCurve_NaturalCubicSpline::Setup +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::Setup() const { + if ( this->changed ) { + switch( this->boundaryType ) { + case idCurve_Spline::BT_FREE: SetupFree(); break; + case idCurve_Spline::BT_CLAMPED: SetupClamped(); break; + case idCurve_Spline::BT_CLOSED: SetupClosed(); break; + } + this->changed = false; + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupFree +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupFree() const { + int i; + float inv; + float *d0, *d1, *beta, *gamma; + type *alpha, *delta; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + d1 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + alpha = (type *) _alloca16( ( this->values.Num() - 1 ) * sizeof( type ) ); + beta = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + gamma = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + delta = (type *) _alloca16( this->values.Num() * sizeof( type ) ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + d1[i] = this->times[i+1] - this->times[i-1]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + type sum = 3.0f * ( d0[i-1] * this->values[i+1] - d1[i] * this->values[i] + d0[i] * this->values[i-1] ); + inv = 1.0f / ( d0[i-1] * d0[i] ); + alpha[i] = inv * sum; + } + + beta[0] = 1.0f; + gamma[0] = 0.0f; + delta[0] = this->values[0] - this->values[0]; //-V501 + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + beta[i] = 2.0f * d1[i] - d0[i-1] * gamma[i-1]; + inv = 1.0f / beta[i]; + gamma[i] = inv * d0[i]; + delta[i] = inv * ( alpha[i] - d0[i-1] * delta[i-1] ); + } + beta[this->values.Num() - 1] = 1.0f; + delta[this->values.Num() - 1] = this->values[0] - this->values[0]; //-V501 + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + c[this->values.Num() - 1] = this->values[0] - this->values[0]; //-V501 + + for ( i = this->values.Num() - 2; i >= 0; i-- ) { + c[i] = delta[i] - gamma[i] * c[i+1]; + inv = 1.0f / d0[i]; + b[i] = inv * ( this->values[i+1] - this->values[i] ) - ( 1.0f / 3.0f ) * d0[i] * ( c[i+1] + 2.0f * c[i] ); + d[i] = ( 1.0f / 3.0f ) * inv * ( c[i+1] - c[i] ); + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupClamped +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupClamped() const { + int i; + float inv; + float *d0, *d1, *beta, *gamma; + type *alpha, *delta; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + d1 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + alpha = (type *) _alloca16( ( this->values.Num() - 1 ) * sizeof( type ) ); + beta = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + gamma = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + delta = (type *) _alloca16( this->values.Num() * sizeof( type ) ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + d1[i] = this->times[i+1] - this->times[i-1]; + } + + inv = 1.0f / d0[0]; + alpha[0] = 3.0f * ( inv - 1.0f ) * ( this->values[1] - this->values[0] ); + inv = 1.0f / d0[this->values.Num() - 2]; + alpha[this->values.Num() - 1] = 3.0f * ( 1.0f - inv ) * ( this->values[this->values.Num() - 1] - this->values[this->values.Num() - 2] ); + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + type sum = 3.0f * ( d0[i-1] * this->values[i+1] - d1[i] * this->values[i] + d0[i] * this->values[i-1] ); + inv = 1.0f / ( d0[i-1] * d0[i] ); + alpha[i] = inv * sum; + } + + beta[0] = 2.0f * d0[0]; + gamma[0] = 0.5f; + inv = 1.0f / beta[0]; + delta[0] = inv * alpha[0]; + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + beta[i] = 2.0f * d1[i] - d0[i-1] * gamma[i-1]; + inv = 1.0f / beta[i]; + gamma[i] = inv * d0[i]; + delta[i] = inv * ( alpha[i] - d0[i-1] * delta[i-1] ); + } + + beta[this->values.Num() - 1] = d0[this->values.Num() - 2] * ( 2.0f - gamma[this->values.Num() - 2] ); + inv = 1.0f / beta[this->values.Num() - 1]; + delta[this->values.Num() - 1] = inv * ( alpha[this->values.Num() - 1] - d0[this->values.Num() - 2] * delta[this->values.Num() - 2] ); + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + c[this->values.Num() - 1] = delta[this->values.Num() - 1]; + + for ( i = this->values.Num() - 2; i >= 0; i-- ) { + c[i] = delta[i] - gamma[i] * c[i+1]; + inv = 1.0f / d0[i]; + b[i] = inv * ( this->values[i+1] - this->values[i] ) - ( 1.0f / 3.0f ) * d0[i]* ( c[i+1] + 2.0f * c[i] ); + d[i] = ( 1.0f / 3.0f ) * inv * ( c[i+1] - c[i] ); + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupClosed +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupClosed() const { + int i, j; + float c0, c1; + float *d0; + idMatX mat; + idVecX x; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + x.SetData( this->values.Num(), VECX_ALLOCA( this->values.Num() ) ); + mat.SetData( this->values.Num(), this->values.Num(), MATX_ALLOCA( this->values.Num() * this->values.Num() ) ); + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + // matrix of system + mat[0][0] = 1.0f; + mat[0][this->values.Num() - 1] = -1.0f; + for ( i = 1; i <= this->values.Num() - 2; i++ ) { + mat[i][i-1] = d0[i-1]; + mat[i][i ] = 2.0f * ( d0[i-1] + d0[i] ); + mat[i][i+1] = d0[i]; + } + mat[this->values.Num() - 1][this->values.Num() - 2] = d0[this->values.Num() - 2]; + mat[this->values.Num() - 1][0] = 2.0f * ( d0[this->values.Num() - 2] + d0[0] ); + mat[this->values.Num() - 1][1] = d0[0]; + + // right-hand side + c[0].Zero(); + for ( i = 1; i <= this->values.Num() - 2; i++ ) { + c0 = 1.0f / d0[i]; + c1 = 1.0f / d0[i-1]; + c[i] = 3.0f * ( c0 * ( this->values[i + 1] - this->values[i] ) - c1 * ( this->values[i] - this->values[i - 1] ) ); + } + c0 = 1.0f / d0[0]; + c1 = 1.0f / d0[this->values.Num() - 2]; + c[this->values.Num() - 1] = 3.0f * ( c0 * ( this->values[1] - this->values[0] ) - c1 * ( this->values[0] - this->values[this->values.Num() - 2] ) ); + + // solve system for each dimension + mat.LU_Factor( NULL ); + for ( i = 0; i < this->values[0].GetDimension(); i++ ) { + for ( j = 0; j < this->values.Num(); j++ ) { + x[j] = c[j][i]; + } + mat.LU_Solve( x, x, NULL ); + for ( j = 0; j < this->values.Num(); j++ ) { + c[j][i] = x[j]; + } + } + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + c0 = 1.0f / d0[i]; + b[i] = c0 * ( this->values[i + 1] - this->values[i] ) - ( 1.0f / 3.0f ) * ( c[i+1] + 2.0f * c[i] ) * d0[i]; + d[i] = ( 1.0f / 3.0f ) * c0 * ( c[i + 1] - c[i] ); + } +} + + +/* +=============================================================================== + + Uniform Cubic Interpolating Spline template. + The curve goes through all the knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_CatmullRomSpline : public idCurve_Spline { + +public: + idCurve_CatmullRomSpline(); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_CatmullRomSpline::idCurve_CatmullRomSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_CatmullRomSpline::idCurve_CatmullRomSpline() { +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float bvals[4], clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + Basis( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * this->ValueForIndex( k ); + } + d = ( this->TimeForIndex( i ) - this->TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * this->ValueForIndex( k ); + } + d = ( this->TimeForIndex( i ) - this->TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_CatmullRomSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = ( ( -s + 2.0f ) * s - 1.0f ) * s * 0.5f; // -0.5f s * s * s + s * s - 0.5f * s + bvals[1] = ( ( ( 3.0f * s - 5.0f ) * s ) * s + 2.0f ) * 0.5f; // 1.5f * s * s * s - 2.5f * s * s + 1.0f + bvals[2] = ( ( -3.0f * s + 4.0f ) * s + 1.0f ) * s * 0.5f; // -1.5f * s * s * s - 2.0f * s * s + 0.5f s + bvals[3] = ( ( s - 1.0f ) * s * s ) * 0.5f; // 0.5f * s * s * s - 0.5f * s * s +} + +/* +==================== +idCurve_CatmullRomSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = ( -1.5f * s + 2.0f ) * s - 0.5f; // -1.5f * s * s + 2.0f * s - 0.5f + bvals[1] = ( 4.5f * s - 5.0f ) * s; // 4.5f * s * s - 5.0f * s + bvals[2] = ( -4.5 * s + 4.0f ) * s + 0.5f; // -4.5 * s * s + 4.0f * s + 0.5f + bvals[3] = 1.5f * s * s - s; // 1.5f * s * s - s +} + +/* +==================== +idCurve_CatmullRomSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = -3.0f * s + 2.0f; + bvals[1] = 9.0f * s - 5.0f; + bvals[2] = -9.0f * s + 4.0f; + bvals[3] = 3.0f * s - 1.0f; +} + + +/* +=============================================================================== + + Cubic Interpolating Spline template. + The curve goes through all the knots. + The curve becomes the Catmull-Rom spline if the tension, + continuity and bias are all set to zero. + +=============================================================================== +*/ + +template< class type > +class idCurve_KochanekBartelsSpline : public idCurve_Spline { + +public: + idCurve_KochanekBartelsSpline(); + + virtual int AddValue( const float time, const type &value ); + virtual int AddValue( const float time, const type &value, const float tension, const float continuity, const float bias ); + virtual void RemoveIndex( const int index ) { this->values.RemoveIndex(index); this->times.RemoveIndex(index); tension.RemoveIndex(index); continuity.RemoveIndex(index); bias.RemoveIndex(index); } + virtual void Clear() { this->values.Clear(); this->times.Clear(); tension.Clear(); continuity.Clear(); bias.Clear(); this->currentIndex = -1; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + idList tension; + idList continuity; + idList bias; + + void TangentsForIndex( const int index, type &t0, type &t1 ) const; + + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_KochanekBartelsSpline::idCurve_KochanekBartelsSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_KochanekBartelsSpline::idCurve_KochanekBartelsSpline() { +} + +/* +==================== +idCurve_KochanekBartelsSpline::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_KochanekBartelsSpline::AddValue( const float time, const type &value ) { + int i; + + i = this->IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + tension.Insert( 0.0f, i ); + continuity.Insert( 0.0f, i ); + bias.Insert( 0.0f, i ); + return i; +} + +/* +==================== +idCurve_KochanekBartelsSpline::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_KochanekBartelsSpline::AddValue( const float time, const type &value, const float tension, const float continuity, const float bias ) { + int i; + + i = this->IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + this->tension.Insert( tension, i ); + this->continuity.Insert( continuity, i ); + this->bias.Insert( bias, i ); + return i; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentValue( const float time ) const { + int i; + float bvals[4], clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + Basis( i - 1, clampedTime, bvals ); + v = bvals[0] * this->ValueForIndex( i - 1 ); + v += bvals[1] * this->ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + return v; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentFirstDerivative( const float time ) const { + int i; + float bvals[4], d, clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + BasisFirstDerivative( i - 1, clampedTime, bvals ); + v = bvals[0] * this->ValueForIndex( i - 1 ); + v += bvals[1] * this->ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + d = ( this->TimeForIndex( i ) - this->TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentSecondDerivative( const float time ) const { + int i; + float bvals[4], d, clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + BasisSecondDerivative( i - 1, clampedTime, bvals ); + v = bvals[0] * this->ValueForIndex( i - 1 ); + v += bvals[1] * this->ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + d = ( this->TimeForIndex( i ) - this->TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_KochanekBartelsSpline::TangentsForIndex +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::TangentsForIndex( const int index, type &t0, type &t1 ) const { + float dt, omt, omc, opc, omb, opb, adj, s0, s1; + type delta; + + delta = this->ValueForIndex( index + 1 ) - this->ValueForIndex( index ); + dt = this->TimeForIndex( index + 1 ) - this->TimeForIndex( index ); + + omt = 1.0f - tension[index]; + omc = 1.0f - continuity[index]; + opc = 1.0f + continuity[index]; + omb = 1.0f - bias[index]; + opb = 1.0f + bias[index]; + adj = 2.0f * dt / ( this->TimeForIndex( index + 1 ) - this->TimeForIndex( index - 1 ) ); + s0 = 0.5f * adj * omt * opc * opb; + s1 = 0.5f * adj * omt * omc * omb; + + // outgoing tangent at first point + t0 = s1 * delta + s0 * ( this->ValueForIndex( index ) - this->ValueForIndex( index - 1 ) ); + + omt = 1.0f - tension[index + 1]; + omc = 1.0f - continuity[index + 1]; + opc = 1.0f + continuity[index + 1]; + omb = 1.0f - bias[index + 1]; + opb = 1.0f + bias[index + 1]; + adj = 2.0f * dt / ( this->TimeForIndex( index + 2 ) - this->TimeForIndex( index ) ); + s0 = 0.5f * adj * omt * omc * opb; + s1 = 0.5f * adj * omt * opc * omb; + + // incoming tangent at second point + t1 = s1 * ( this->ValueForIndex( index + 2 ) - this->ValueForIndex( index + 1 ) ) + s0 * delta; +} + +/* +==================== +idCurve_KochanekBartelsSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = ( ( 2.0f * s - 3.0f ) * s ) * s + 1.0f; // 2.0f * s * s * s - 3.0f * s * s + 1.0f + bvals[1] = ( ( -2.0f * s + 3.0f ) * s ) * s; // -2.0f * s * s * s + 3.0f * s * s + bvals[2] = ( ( s - 2.0f ) * s ) * s + s; // s * s * s - 2.0f * s * s + s + bvals[3] = ( ( s - 1.0f ) * s ) * s; // s * s * s - s * s +} + +/* +==================== +idCurve_KochanekBartelsSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = ( 6.0f * s - 6.0f ) * s; // 6.0f * s * s - 6.0f * s + bvals[1] = ( -6.0f * s + 6.0f ) * s; // -6.0f * s * s + 6.0f * s + bvals[2] = ( 3.0f * s - 4.0f ) * s + 1.0f; // 3.0f * s * s - 4.0f * s + 1.0f + bvals[3] = ( 3.0f * s - 2.0f ) * s; // 3.0f * s * s - 2.0f * s +} + +/* +==================== +idCurve_KochanekBartelsSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = 12.0f * s - 6.0f; + bvals[1] = -12.0f * s + 6.0f; + bvals[2] = 6.0f * s - 4.0f; + bvals[3] = 6.0f * s - 2.0f; +} + + +/* +=============================================================================== + + B-Spline base template. Uses recursive definition and is slow. + Use idCurve_UniformCubicBSpline or idCurve_NonUniformBSpline instead. + +=============================================================================== +*/ + +template< class type > +class idCurve_BSpline : public idCurve_Spline { + +public: + idCurve_BSpline(); + + virtual int GetOrder() const { return order; } + virtual void SetOrder( const int i ) { assert( i > 0 && i < 10 ); order = i; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + int order; + + float Basis( const int index, const int order, const float t ) const; + float BasisFirstDerivative( const int index, const int order, const float t ) const; + float BasisSecondDerivative( const int index, const int order, const float t ) const; +}; + +/* +==================== +idCurve_BSpline::idCurve_NaturalCubicSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_BSpline::idCurve_BSpline() { + order = 4; // default to cubic +} + +/* +==================== +idCurve_BSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += Basis( k-2, order, clampedTime ) * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += BasisFirstDerivative( k-2, order, clampedTime ) * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += BasisSecondDerivative( k-2, order, clampedTime ) * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::Basis + + spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::Basis( const int index, const int order, const float t ) const { + if ( order <= 1 ) { + if ( this->TimeForIndex( index ) < t && t <= this->TimeForIndex( index + 1 ) ) { + return 1.0f; + } else { + return 0.0f; + } + } else { + float sum = 0.0f; + float d1 = this->TimeForIndex( index+order-1 ) - this->TimeForIndex( index ); + if ( d1 != 0.0f ) { + sum += (float) ( t - this->TimeForIndex( index ) ) * Basis( index, order-1, t ) / d1; + } + + float d2 = this->TimeForIndex( index+order ) - this->TimeForIndex( index+1 ); + if ( d2 != 0.0f ) { + sum += (float) ( this->TimeForIndex( index+order ) - t ) * Basis( index+1, order-1, t ) / d2; + } + return sum; + } +} + +/* +==================== +idCurve_BSpline::BasisFirstDerivative + + first derivative of spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::BasisFirstDerivative( const int index, const int order, const float t ) const { + return ( Basis( index, order-1, t ) - Basis( index+1, order-1, t ) ) * + (float) ( order - 1 ) / ( this->TimeForIndex( index + ( order - 1 ) - 2 ) - this->TimeForIndex( index - 2 ) ); +} + +/* +==================== +idCurve_BSpline::BasisSecondDerivative + + second derivative of spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::BasisSecondDerivative( const int index, const int order, const float t ) const { + return ( BasisFirstDerivative( index, order-1, t ) - BasisFirstDerivative( index+1, order-1, t ) ) * + (float) ( order - 1 ) / ( this->TimeForIndex( index + ( order - 1 ) - 2 ) - this->TimeForIndex( index - 2 ) ); +} + + +/* +=============================================================================== + + Uniform Non-Rational Cubic B-Spline template. + +=============================================================================== +*/ + +template< class type > +class idCurve_UniformCubicBSpline : public idCurve_BSpline { + +public: + idCurve_UniformCubicBSpline(); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_UniformCubicBSpline::idCurve_UniformCubicBSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_UniformCubicBSpline::idCurve_UniformCubicBSpline() { + this->order = 4; // always cubic +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float bvals[4], clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + Basis( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * this->ValueForIndex( k ); + } + d = ( this->TimeForIndex( i ) - this->TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * this->ValueForIndex( k ); + } + d = ( this->TimeForIndex( i ) - this->TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_UniformCubicBSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = ( ( ( -s + 3.0f ) * s - 3.0f ) * s + 1.0f ) * ( 1.0f / 6.0f ); + bvals[1] = ( ( ( 3.0f * s - 6.0f ) * s ) * s + 4.0f ) * ( 1.0f / 6.0f ); + bvals[2] = ( ( ( -3.0f * s + 3.0f ) * s + 3.0f ) * s + 1.0f ) * ( 1.0f / 6.0f ); + bvals[3] = ( s * s * s ) * ( 1.0f / 6.0f ); +} + +/* +==================== +idCurve_UniformCubicBSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = -0.5f * s * s + s - 0.5f; + bvals[1] = 1.5f * s * s - 2.0f * s; + bvals[2] = -1.5f * s * s + s + 0.5f; + bvals[3] = 0.5f * s * s; +} + +/* +==================== +idCurve_UniformCubicBSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - this->TimeForIndex( index ) ) / ( this->TimeForIndex( index+1 ) - this->TimeForIndex( index ) ); + bvals[0] = -s + 1.0f; + bvals[1] = 3.0f * s - 2.0f; + bvals[2] = -3.0f * s + 1.0f; + bvals[3] = s; +} + + +/* +=============================================================================== + + Non-Uniform Non-Rational B-Spline (NUBS) template. + +=============================================================================== +*/ + +template< class type > +class idCurve_NonUniformBSpline : public idCurve_BSpline { + +public: + idCurve_NonUniformBSpline(); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const int order, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const int order, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const int order, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_NonUniformBSpline::idCurve_NonUniformBSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_NonUniformBSpline::idCurve_NonUniformBSpline() { +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); //-V501 + } + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * this->ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::Basis( const int index, const int order, const float t, float *bvals ) const { + int r, s, i; + float omega; + + bvals[order-1] = 1.0f; + for ( r = 2; r <= order; r++ ) { + i = index - r + 1; + bvals[order - r] = 0.0f; + for ( s = order - r + 1; s < order; s++ ) { + i++; + omega = (float) ( t - this->TimeForIndex( i ) ) / ( this->TimeForIndex( i + r - 1 ) - this->TimeForIndex( i ) ); + bvals[s - 1] += ( 1.0f - omega ) * bvals[s]; + bvals[s] *= omega; + } + } +} + +/* +==================== +idCurve_NonUniformBSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::BasisFirstDerivative( const int index, const int order, const float t, float *bvals ) const { + int i; + + Basis( index, order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + bvals[i] *= (float) ( order - 1) / ( this->TimeForIndex( index + i + (order-1) - 2 ) - this->TimeForIndex( index + i - 2 ) ); + } + bvals[i] *= (float) ( order - 1) / ( this->TimeForIndex( index + i + (order-1) - 2 ) - this->TimeForIndex( index + i - 2 ) ); +} + +/* +==================== +idCurve_NonUniformBSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::BasisSecondDerivative( const int index, const int order, const float t, float *bvals ) const { + int i; + + BasisFirstDerivative( index, order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + bvals[i] *= (float) ( order - 1) / ( this->TimeForIndex( index + i + (order-1) - 2 ) - this->TimeForIndex( index + i - 2 ) ); + } + bvals[i] *= (float) ( order - 1) / ( this->TimeForIndex( index + i + (order-1) - 2 ) - this->TimeForIndex( index + i - 2 ) ); +} + + +/* +=============================================================================== + + Non-Uniform Rational B-Spline (NURBS) template. + +=============================================================================== +*/ + +template< class type > +class idCurve_NURBS : public idCurve_NonUniformBSpline { + +public: + idCurve_NURBS(); + + virtual int AddValue( const float time, const type &value ); + virtual int AddValue( const float time, const type &value, const float weight ); + virtual void RemoveIndex( const int index ) { this->values.RemoveIndex(index); this->times.RemoveIndex(index); weights.RemoveIndex(index); } + virtual void Clear() { this->values.Clear(); this->times.Clear(); weights.Clear(); this->currentIndex = -1; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + idList weights; + + float WeightForIndex( const int index ) const; +}; + +/* +==================== +idCurve_NURBS::idCurve_NURBS +==================== +*/ +template< class type > +ID_INLINE idCurve_NURBS::idCurve_NURBS() { +} + +/* +==================== +idCurve_NURBS::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_NURBS::AddValue( const float time, const type &value ) { + int i; + + i = this->IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + weights.Insert( 1.0f, i ); + return i; +} + +/* +==================== +idCurve_NURBS::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_NURBS::AddValue( const float time, const type &value, const float weight ) { + int i; + + i = this->IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + weights.Insert( weight, i ); + return i; +} + +/* +==================== +idCurve_NURBS::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentValue( const float time ) const { + int i, j, k; + float w, b, *bvals, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + this->Basis( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; //-V501 + w = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + b = bvals[j] * WeightForIndex( k ); + w += b; + v += b * this->ValueForIndex( k ); + } + return v / w; +} + +/* +==================== +idCurve_NURBS::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float w, wb, wd1, b, d1, *bvals, *d1vals, clampedTime; + type v, vb, vd1; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + d1vals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + this->Basis( i-1, this->order, clampedTime, bvals ); + this->BasisFirstDerivative( i-1, this->order, clampedTime, d1vals ); + vb = vd1 = this->values[0] - this->values[0]; //-V501 + wb = wd1 = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + w = WeightForIndex( k ); + b = bvals[j] * w; + d1 = d1vals[j] * w; + wb += b; + wd1 += d1; + v = this->ValueForIndex( k ); + vb += b * v; + vd1 += d1 * v; + } + return ( wb * vd1 - vb * wd1 ) / ( wb * wb ); +} + +/* +==================== +idCurve_NURBS::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float w, wb, wd1, wd2, b, d1, d2, *bvals, *d1vals, *d2vals, clampedTime; + type v, vb, vd1, vd2; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + d1vals = (float *) _alloca16( this->order * sizeof(float) ); + d2vals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = this->ClampedTime( time ); + i = this->IndexForTime( clampedTime ); + this->Basis( i-1, this->order, clampedTime, bvals ); + this->BasisFirstDerivative( i-1, this->order, clampedTime, d1vals ); + this->BasisSecondDerivative( i-1, this->order, clampedTime, d2vals ); + vb = vd1 = vd2 = this->values[0] - this->values[0]; //-V501 + wb = wd1 = wd2 = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + w = WeightForIndex( k ); + b = bvals[j] * w; + d1 = d1vals[j] * w; + d2 = d2vals[j] * w; + wb += b; + wd1 += d1; + wd2 += d2; + v = this->ValueForIndex( k ); + vb += b * v; + vd1 += d1 * v; + vd2 += d2 * v; + } + return ( ( wb * wb ) * ( wb * vd2 - vb * wd2 ) - ( wb * vd1 - vb * wd1 ) * 2.0f * wb * wd1 ) / ( wb * wb * wb * wb ); +} + +/* +==================== +idCurve_NURBS::WeightForIndex + + get the weight for the given index +==================== +*/ +template< class type > +ID_INLINE float idCurve_NURBS::WeightForIndex( const int index ) const { + int n = weights.Num()-1; + + if ( index < 0 ) { + if ( this->boundaryType == idCurve_Spline::BT_CLOSED ) { + return weights[ weights.Num() + index % weights.Num() ]; + } else { + return weights[0] + index * ( weights[1] - weights[0] ); + } + } else if ( index > n ) { + if ( this->boundaryType == idCurve_Spline::BT_CLOSED ) { + return weights[ index % weights.Num() ]; + } else { + return weights[n] + ( index - n ) * ( weights[n] - weights[n-1] ); + } + } + return weights[index]; +} + +#endif /* !__MATH_CURVE_H__ */ diff --git a/neo/idlib/math/Extrapolate.h b/neo/idlib/math/Extrapolate.h new file mode 100644 index 00000000..5a96608b --- /dev/null +++ b/neo/idlib/math/Extrapolate.h @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_EXTRAPOLATE_H__ +#define __MATH_EXTRAPOLATE_H__ + +/* +============================================================================================== + + Extrapolation + +============================================================================================== +*/ + +typedef enum { + EXTRAPOLATION_NONE = 0x01, // no extrapolation, covered distance = duration * 0.001 * ( baseSpeed ) + EXTRAPOLATION_LINEAR = 0x02, // linear extrapolation, covered distance = duration * 0.001 * ( baseSpeed + speed ) + EXTRAPOLATION_ACCELLINEAR = 0x04, // linear acceleration, covered distance = duration * 0.001 * ( baseSpeed + 0.5 * speed ) + EXTRAPOLATION_DECELLINEAR = 0x08, // linear deceleration, covered distance = duration * 0.001 * ( baseSpeed + 0.5 * speed ) + EXTRAPOLATION_ACCELSINE = 0x10, // sinusoidal acceleration, covered distance = duration * 0.001 * ( baseSpeed + sqrt( 0.5 ) * speed ) + EXTRAPOLATION_DECELSINE = 0x20, // sinusoidal deceleration, covered distance = duration * 0.001 * ( baseSpeed + sqrt( 0.5 ) * speed ) + EXTRAPOLATION_NOSTOP = 0x40 // do not stop at startTime + duration +} extrapolation_t; + +template< class type > +class idExtrapolate { +public: + idExtrapolate(); + + void Init( const int startTime, const int duration, const type &startValue, const type &baseSpeed, const type &speed, const extrapolation_t extrapolationType ); + type GetCurrentValue( int time ) const; + type GetCurrentSpeed( int time ) const; + bool IsDone( int time ) const { return ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && time >= startTime + duration ); } + void SetStartTime( int time ) { startTime = time; } + int GetStartTime() const { return startTime; } + int GetEndTime() const { return ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && duration > 0 ) ? startTime + duration : 0; } + int GetDuration() const { return duration; } + void SetStartValue( const type &value ) { startValue = value; } + const type & GetStartValue() const { return startValue; } + const type & GetBaseSpeed() const { return baseSpeed; } + const type & GetSpeed() const { return speed; } + extrapolation_t GetExtrapolationType() const { return extrapolationType; } + +private: + extrapolation_t extrapolationType; + int startTime; + int duration; + type startValue; + type baseSpeed; + type speed; +}; + +/* +==================== +idExtrapolate::idExtrapolate +==================== +*/ +template< class type > +ID_INLINE idExtrapolate::idExtrapolate() { + extrapolationType = EXTRAPOLATION_NONE; + startTime = duration = 0.0f; + memset( &startValue, 0, sizeof( startValue ) ); + memset( &baseSpeed, 0, sizeof( baseSpeed ) ); + memset( &speed, 0, sizeof( speed ) ); +} + +/* +==================== +idExtrapolate::Init +==================== +*/ +template< class type > +ID_INLINE void idExtrapolate::Init( const int startTime, const int duration, const type &startValue, const type &baseSpeed, const type &speed, const extrapolation_t extrapolationType ) { + this->extrapolationType = extrapolationType; + this->startTime = startTime; + this->duration = duration; + this->startValue = startValue; + this->baseSpeed = baseSpeed; + this->speed = speed; +} + +/* +==================== +idExtrapolate::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idExtrapolate::GetCurrentValue( int time ) const { + if ( time < startTime ) { + return startValue; + } + + if ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && ( time > startTime + duration ) ) { + time = startTime + duration; + } + + switch ( extrapolationType & ~EXTRAPOLATION_NOSTOP ) { + case EXTRAPOLATION_NONE: { + const float deltaTime = ( time - startTime ) * 0.001f; + return startValue + deltaTime * baseSpeed; + } + case EXTRAPOLATION_LINEAR: { + const float deltaTime = ( time - startTime ) * 0.001f; + return startValue + deltaTime * ( baseSpeed + speed ); + } + case EXTRAPOLATION_ACCELLINEAR: { + if ( duration == 0 ) { + return startValue; + } else { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = ( 0.5f * deltaTime * deltaTime ) * ( (float)duration * 0.001f ); + return startValue + deltaTime * baseSpeed + s * speed; + } + } + case EXTRAPOLATION_DECELLINEAR: { + if ( duration == 0 ) { + return startValue; + } else { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = ( deltaTime - ( 0.5f * deltaTime * deltaTime ) ) * ( (float)duration * 0.001f ); + return startValue + deltaTime * baseSpeed + s * speed; + } + } + case EXTRAPOLATION_ACCELSINE: { + if ( duration == 0 ) { + return startValue; + } else { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = ( 1.0f - idMath::Cos( deltaTime * idMath::HALF_PI ) ) * (float)duration * 0.001f * idMath::SQRT_1OVER2; + return startValue + deltaTime * baseSpeed + s * speed; + } + } + case EXTRAPOLATION_DECELSINE: { + if ( duration == 0 ) { + return startValue; + } else { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = idMath::Sin( deltaTime * idMath::HALF_PI ) * (float)duration * 0.001f * idMath::SQRT_1OVER2; + return startValue + deltaTime * baseSpeed + s * speed; + } + } + } + return startValue; +} + +/* +==================== +idExtrapolate::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idExtrapolate::GetCurrentSpeed( int time ) const { + if ( time < startTime || duration == 0 ) { + return ( startValue - startValue ); //-V501 + } + + if ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && ( time > startTime + duration ) ) { + return ( startValue - startValue ); //-V501 + } + + switch( extrapolationType & ~EXTRAPOLATION_NOSTOP ) { + case EXTRAPOLATION_NONE: { + return baseSpeed; + } + case EXTRAPOLATION_LINEAR: { + return baseSpeed + speed; + } + case EXTRAPOLATION_ACCELLINEAR: { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = deltaTime; + return baseSpeed + s * speed; + } + case EXTRAPOLATION_DECELLINEAR: { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = 1.0f - deltaTime; + return baseSpeed + s * speed; + } + case EXTRAPOLATION_ACCELSINE: { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = idMath::Sin( deltaTime * idMath::HALF_PI ); + return baseSpeed + s * speed; + } + case EXTRAPOLATION_DECELSINE: { + const float deltaTime = ( time - startTime ) / (float)duration; + const float s = idMath::Cos( deltaTime * idMath::HALF_PI ); + return baseSpeed + s * speed; + } + default: { + return baseSpeed; + } + } +} + +#endif /* !__MATH_EXTRAPOLATE_H__ */ diff --git a/neo/idlib/math/Interpolate.h b/neo/idlib/math/Interpolate.h new file mode 100644 index 00000000..73d32dc3 --- /dev/null +++ b/neo/idlib/math/Interpolate.h @@ -0,0 +1,400 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_INTERPOLATE_H__ +#define __MATH_INTERPOLATE_H__ + +/* +============================================================================================== + + Linear interpolation. + +============================================================================================== +*/ + +template< class type > +class idInterpolate { +public: + idInterpolate(); + + void Init( const int startTime, const int duration, const type &startValue, const type &endValue ); + void SetStartTime( int time ) { this->startTime = time; } + void SetDuration( int duration ) { this->duration = duration; } + void SetStartValue( const type &startValue ) { this->startValue = startValue; } + void SetEndValue( const type &endValue ) { this->endValue = endValue; } + + type GetCurrentValue( int time ) const; + bool IsDone( int time ) const { return ( time >= startTime + duration ); } + + int GetStartTime() const { return startTime; } + int GetEndTime() const { return startTime + duration; } + int GetDuration() const { return duration; } + const type & GetStartValue() const { return startValue; } + const type & GetEndValue() const { return endValue; } + +private: + int startTime; + int duration; + type startValue; + type endValue; +}; + +/* +==================== +idInterpolate::idInterpolate +==================== +*/ +template< class type > +ID_INLINE idInterpolate::idInterpolate() { + startTime = duration = 0; + memset( &startValue, 0, sizeof( startValue ) ); + memset( &endValue, 0, sizeof( endValue ) ); +} + +/* +==================== +idInterpolate::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolate::Init( const int startTime, const int duration, const type &startValue, const type &endValue ) { + this->startTime = startTime; + this->duration = duration; + this->startValue = startValue; + this->endValue = endValue; +} + +/* +==================== +idInterpolate::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolate::GetCurrentValue( int time ) const { + if ( time <= startTime ) { + return startValue; + } else if ( time >= startTime + duration ) { + return endValue; + } else { + const float deltaTime = time - startTime; + const float f = deltaTime / (float)duration; + const type range = ( endValue - startValue ); + return startValue + ( range * f ); + } +} + +/* +============================================================================================== + + Continuous interpolation with linear acceleration and deceleration phase. + The velocity is continuous but the acceleration is not. + +============================================================================================== +*/ + +template< class type > +class idInterpolateAccelDecelLinear { +public: + idInterpolateAccelDecelLinear(); + + void Init( const int startTime, const int accelTime, const int decelTime, const int duration, const type &startValue, const type &endValue ); + void SetStartTime( int time ) { startTime = time; Invalidate(); } + void SetStartValue( const type &startValue ) { this->startValue = startValue; Invalidate(); } + void SetEndValue( const type &endValue ) { this->endValue = endValue; Invalidate(); } + + type GetCurrentValue( int time ) const; + type GetCurrentSpeed( int time ) const; + bool IsDone( int time ) const { return ( time >= startTime + accelTime + linearTime + decelTime ); } + + int GetStartTime() const { return startTime; } + int GetEndTime() const { return startTime + accelTime + linearTime + decelTime; } + int GetDuration() const { return accelTime + linearTime + decelTime; } + int GetAcceleration() const { return accelTime; } + int GetDeceleration() const { return decelTime; } + const type & GetStartValue() const { return startValue; } + const type & GetEndValue() const { return endValue; } + +private: + int startTime; + int accelTime; + int linearTime; + int decelTime; + type startValue; + type endValue; + mutable idExtrapolate extrapolate; + + void Invalidate(); + void SetPhase( int time ) const; +}; + +/* +==================== +idInterpolateAccelDecelLinear::idInterpolateAccelDecelLinear +==================== +*/ +template< class type > +ID_INLINE idInterpolateAccelDecelLinear::idInterpolateAccelDecelLinear() { + startTime = accelTime = linearTime = decelTime = 0; + memset( &startValue, 0, sizeof( startValue ) ); + endValue = startValue; +} + +/* +==================== +idInterpolateAccelDecelLinear::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::Init( const int startTime, const int accelTime, const int decelTime, const int duration, const type &startValue, const type &endValue ) { + this->startTime = startTime; + this->accelTime = accelTime; + this->decelTime = decelTime; + this->startValue = startValue; + this->endValue = endValue; + + if ( duration <= 0 ) { + return; + } + + if ( this->accelTime + this->decelTime > duration ) { + this->accelTime = this->accelTime * duration / ( this->accelTime + this->decelTime ); + this->decelTime = duration - this->accelTime; + } + this->linearTime = duration - this->accelTime - this->decelTime; + const type speed = ( endValue - startValue ) * ( 1000.0f / ( (float) this->linearTime + ( this->accelTime + this->decelTime ) * 0.5f ) ); + + if ( this->accelTime ) { + extrapolate.Init( startTime, this->accelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_ACCELLINEAR ); //-V501 + } else if ( this->linearTime ) { + extrapolate.Init( startTime, this->linearTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_LINEAR ); //-V501 + } else { + extrapolate.Init( startTime, this->decelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_DECELLINEAR ); //-V501 + } +} + +/* +==================== +idInterpolateAccelDecelLinear::Invalidate +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::Invalidate() { + extrapolate.Init( 0, 0, extrapolate.GetStartValue(), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_NONE ); +} + +/* +==================== +idInterpolateAccelDecelLinear::SetPhase +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::SetPhase( int time ) const { + const float deltaTime = time - startTime; + if ( deltaTime < accelTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_ACCELLINEAR ) { + extrapolate.Init( startTime, accelTime, startValue, extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_ACCELLINEAR ); + } + } else if ( deltaTime < accelTime + linearTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_LINEAR ) { + extrapolate.Init( startTime + accelTime, linearTime, startValue + extrapolate.GetSpeed() * ( accelTime * 0.001f * 0.5f ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_LINEAR ); + } + } else { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_DECELLINEAR ) { + extrapolate.Init( startTime + accelTime + linearTime, decelTime, endValue - ( extrapolate.GetSpeed() * ( decelTime * 0.001f * 0.5f ) ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_DECELLINEAR ); + } + } +} + +/* +==================== +idInterpolateAccelDecelLinear::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelLinear::GetCurrentValue( int time ) const { + SetPhase( time ); + return extrapolate.GetCurrentValue( time ); +} + +/* +==================== +idInterpolateAccelDecelLinear::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelLinear::GetCurrentSpeed( int time ) const { + SetPhase( time ); + return extrapolate.GetCurrentSpeed( time ); +} + + +/* +============================================================================================== + + Continuous interpolation with sinusoidal acceleration and deceleration phase. + Both the velocity and acceleration are continuous. + +============================================================================================== +*/ + +template< class type > +class idInterpolateAccelDecelSine { +public: + idInterpolateAccelDecelSine(); + + void Init( const int startTime, const int accelTime, const int decelTime, const int duration, const type &startValue, const type &endValue ); + void SetStartTime( int time ) { startTime = time; Invalidate(); } + void SetStartValue( const type &startValue ) { this->startValue = startValue; Invalidate(); } + void SetEndValue( const type &endValue ) { this->endValue = endValue; Invalidate(); } + + type GetCurrentValue( int time ) const; + type GetCurrentSpeed( int time ) const; + bool IsDone( int time ) const { return ( time >= startTime + accelTime + linearTime + decelTime ); } + + int GetStartTime() const { return startTime; } + int GetEndTime() const { return startTime + accelTime + linearTime + decelTime; } + int GetDuration() const { return accelTime + linearTime + decelTime; } + int GetAcceleration() const { return accelTime; } + int GetDeceleration() const { return decelTime; } + const type & GetStartValue() const { return startValue; } + const type & GetEndValue() const { return endValue; } + +private: + int startTime; + int accelTime; + int linearTime; + int decelTime; + type startValue; + type endValue; + mutable idExtrapolate extrapolate; + + void Invalidate(); + void SetPhase( int time ) const; +}; + +/* +==================== +idInterpolateAccelDecelSine::idInterpolateAccelDecelSine +==================== +*/ +template< class type > +ID_INLINE idInterpolateAccelDecelSine::idInterpolateAccelDecelSine() { + startTime = accelTime = linearTime = decelTime = 0; + memset( &startValue, 0, sizeof( startValue ) ); + memset( &endValue, 0, sizeof( endValue ) ); +} + +/* +==================== +idInterpolateAccelDecelSine::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::Init( const int startTime, const int accelTime, const int decelTime, const int duration, const type &startValue, const type &endValue ) { + this->startTime = startTime; + this->accelTime = accelTime; + this->decelTime = decelTime; + this->startValue = startValue; + this->endValue = endValue; + + if ( duration <= 0 ) { + return; + } + + if ( this->accelTime + this->decelTime > duration ) { + this->accelTime = this->accelTime * duration / ( this->accelTime + this->decelTime ); + this->decelTime = duration - this->accelTime; + } + this->linearTime = duration - this->accelTime - this->decelTime; + const type speed = ( endValue - startValue ) * ( 1000.0f / ( (float) this->linearTime + ( this->accelTime + this->decelTime ) * idMath::SQRT_1OVER2 ) ); + + if ( this->accelTime ) { + extrapolate.Init( startTime, this->accelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_ACCELSINE ); //-V501 + } else if ( this->linearTime ) { + extrapolate.Init( startTime, this->linearTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_LINEAR ); //-V501 + } else { + extrapolate.Init( startTime, this->decelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_DECELSINE ); //-V501 + } +} + +/* +==================== +idInterpolateAccelDecelSine::Invalidate +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::Invalidate() { + extrapolate.Init( 0, 0, extrapolate.GetStartValue(), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_NONE ); +} + +/* +==================== +idInterpolateAccelDecelSine::SetPhase +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::SetPhase( int time ) const { + const float deltaTime = time - startTime; + if ( deltaTime < accelTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_ACCELSINE ) { + extrapolate.Init( startTime, accelTime, startValue, extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_ACCELSINE ); + } + } else if ( deltaTime < accelTime + linearTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_LINEAR ) { + extrapolate.Init( startTime + accelTime, linearTime, startValue + extrapolate.GetSpeed() * ( accelTime * 0.001f * idMath::SQRT_1OVER2 ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_LINEAR ); + } + } else { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_DECELSINE ) { + extrapolate.Init( startTime + accelTime + linearTime, decelTime, endValue - ( extrapolate.GetSpeed() * ( decelTime * 0.001f * idMath::SQRT_1OVER2 ) ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_DECELSINE ); + } + } +} + +/* +==================== +idInterpolateAccelDecelSine::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelSine::GetCurrentValue( int time ) const { + SetPhase( time ); + return extrapolate.GetCurrentValue( time ); +} + +/* +==================== +idInterpolateAccelDecelSine::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelSine::GetCurrentSpeed( int time ) const { + SetPhase( time ); + return extrapolate.GetCurrentSpeed( time ); +} + +#endif /* !__MATH_INTERPOLATE_H__ */ diff --git a/neo/idlib/math/Lcp.cpp b/neo/idlib/math/Lcp.cpp new file mode 100644 index 00000000..01d4c5a5 --- /dev/null +++ b/neo/idlib/math/Lcp.cpp @@ -0,0 +1,2949 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../precompiled.h" + +// this file is full of intentional case fall throughs +//lint -e616 + +// the code is correct, it can't be a NULL pointer +//lint -e613 + +static idCVar lcp_showFailures( "lcp_showFailures", "0", CVAR_BOOL, "show LCP solver failures" ); + +const float LCP_BOUND_EPSILON = 1e-5f; +const float LCP_ACCEL_EPSILON = 1e-5f; +const float LCP_DELTA_ACCEL_EPSILON = 1e-9f; +const float LCP_DELTA_FORCE_EPSILON = 1e-9f; + +#define IGNORE_UNSATISFIABLE_VARIABLES + + + +ALIGN16( const __m128 SIMD_SP_zero ) = { 0.0f, 0.0f, 0.0f, 0.0f }; +ALIGN16( const __m128 SIMD_SP_one ) = { 1.0f, 1.0f, 1.0f, 1.0f }; +ALIGN16( const __m128 SIMD_SP_two ) = { 2.0f, 2.0f, 2.0f, 2.0f }; +ALIGN16( const __m128 SIMD_SP_tiny ) = { 1e-10f, 1e-10f, 1e-10f, 1e-10f }; +ALIGN16( const __m128 SIMD_SP_infinity ) = { idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY }; +ALIGN16( const __m128 SIMD_SP_LCP_DELTA_ACCEL_EPSILON ) = { LCP_DELTA_ACCEL_EPSILON, LCP_DELTA_ACCEL_EPSILON, LCP_DELTA_ACCEL_EPSILON, LCP_DELTA_ACCEL_EPSILON }; +ALIGN16( const __m128 SIMD_SP_LCP_DELTA_FORCE_EPSILON ) = { LCP_DELTA_FORCE_EPSILON, LCP_DELTA_FORCE_EPSILON, LCP_DELTA_FORCE_EPSILON, LCP_DELTA_FORCE_EPSILON }; +ALIGN16( const __m128 SIMD_SP_LCP_BOUND_EPSILON ) = { LCP_BOUND_EPSILON, LCP_BOUND_EPSILON, LCP_BOUND_EPSILON, LCP_BOUND_EPSILON }; +ALIGN16( const __m128 SIMD_SP_neg_LCP_BOUND_EPSILON ) = { -LCP_BOUND_EPSILON, -LCP_BOUND_EPSILON, -LCP_BOUND_EPSILON, -LCP_BOUND_EPSILON }; +ALIGN16( const unsigned int SIMD_SP_signBit[4] ) = { IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK }; +ALIGN16( const unsigned int SIMD_SP_absMask[4] ) = { ~IEEE_FLT_SIGN_MASK, ~IEEE_FLT_SIGN_MASK, ~IEEE_FLT_SIGN_MASK, ~IEEE_FLT_SIGN_MASK }; +ALIGN16( const unsigned int SIMD_SP_indexedStartMask[4][4] ) = { { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, { 0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }, { 0, 0, 0xFFFFFFFF, 0xFFFFFFFF }, { 0, 0, 0, 0xFFFFFFFF } }; +ALIGN16( const unsigned int SIMD_SP_indexedEndMask[4][4] ) = { { 0, 0, 0, 0 }, { 0xFFFFFFFF, 0, 0, 0 }, { 0xFFFFFFFF, 0xFFFFFFFF, 0, 0 }, { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0 } }; +ALIGN16( const unsigned int SIMD_SP_clearLast1[4] ) = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0 }; +ALIGN16( const unsigned int SIMD_SP_clearLast2[4] ) = { 0xFFFFFFFF, 0xFFFFFFFF, 0, 0 }; +ALIGN16( const unsigned int SIMD_SP_clearLast3[4] ) = { 0xFFFFFFFF, 0, 0, 0 }; +ALIGN16( const unsigned int SIMD_DW_zero[4] ) = { 0, 0, 0, 0 }; +ALIGN16( const unsigned int SIMD_DW_one[4] ) = { 1, 1, 1, 1 }; +ALIGN16( const unsigned int SIMD_DW_four[4] ) = { 4, 4, 4, 4 }; +ALIGN16( const unsigned int SIMD_DW_index[4] ) = { 0, 1, 2, 3 }; +ALIGN16( const int SIMD_DW_not3[4] ) = { ~3, ~3, ~3, ~3 }; + +/* +======================== +Multiply_SIMD + +dst[i] = src0[i] * src1[i]; + +Assumes the source and destination have the same memory alignment. +======================== +*/ +static void Multiply_SIMD( float * dst, const float * src0, const float * src1, const int count ) { + int i = 0; + for ( ; ( (unsigned int)dst & 0xF ) != 0 && i < count; i++ ) { + dst[i] = src0[i] * src1[i]; + } + + + for ( ; i + 4 <= count; i += 4 ) { + assert_16_byte_aligned( &dst[i] ); + assert_16_byte_aligned( &src0[i] ); + assert_16_byte_aligned( &src1[i] ); + + __m128 s0 = _mm_load_ps( src0 + i ); + __m128 s1 = _mm_load_ps( src1 + i ); + s0 = _mm_mul_ps( s0, s1 ); + _mm_store_ps( dst + i, s0 ); + } + + + for ( ; i < count; i++ ) { + dst[i] = src0[i] * src1[i]; + } +} + +/* +======================== +MultiplyAdd_SIMD + +dst[i] += constant * src[i]; + +Assumes the source and destination have the same memory alignment. +======================== +*/ +static void MultiplyAdd_SIMD( float * dst, const float constant, const float * src, const int count ) { + int i = 0; + for ( ; ( (unsigned int)dst & 0xF ) != 0 && i < count; i++ ) { + dst[i] += constant * src[i]; + } + + + __m128 c = _mm_load1_ps( & constant ); + for ( ; i + 4 <= count; i += 4 ) { + assert_16_byte_aligned( &dst[i] ); + assert_16_byte_aligned( &src[i] ); + + __m128 s = _mm_load_ps( src + i ); + __m128 d = _mm_load_ps( dst + i ); + s = _mm_add_ps( _mm_mul_ps( s, c ), d ); + _mm_store_ps( dst + i, s ); + } + + + for ( ; i < count; i++ ) { + dst[i] += constant * src[i]; + } +} + +/* +======================== +DotProduct_SIMD + +dot = src0[0] * src1[0] + src0[1] * src1[1] + src0[2] * src1[2] + ... +======================== +*/ +static float DotProduct_SIMD( const float * src0, const float * src1, const int count ) { + assert_16_byte_aligned( src0 ); + assert_16_byte_aligned( src1 ); + +#ifndef _lint + + __m128 sum = (__m128 &) SIMD_SP_zero; + int i = 0; + for ( ; i < count - 3; i += 4 ) { + __m128 s0 = _mm_load_ps( src0 + i ); + __m128 s1 = _mm_load_ps( src1 + i ); + sum = _mm_add_ps( _mm_mul_ps( s0, s1 ), sum ); + } + __m128 mask = _mm_load_ps( (float *) &SIMD_SP_indexedEndMask[count & 3] ); + __m128 s0 = _mm_and_ps( _mm_load_ps( src0 + i ), mask ); + __m128 s1 = _mm_and_ps( _mm_load_ps( src1 + i ), mask ); + sum = _mm_add_ps( _mm_mul_ps( s0, s1 ), sum ); + sum = _mm_add_ps( sum, _mm_shuffle_ps( sum, sum, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + sum = _mm_add_ps( sum, _mm_shuffle_ps( sum, sum, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + float dot; + _mm_store_ss( & dot, sum ); + return dot; + +#else + + float s0 = 0.0f; + float s1 = 0.0f; + float s2 = 0.0f; + float s3 = 0.0f; + int i = 0; + for ( ; i < count - 3; i += 4 ) { + s0 += src0[i+4] * src1[i+4]; + s1 += src0[i+5] * src1[i+5]; + s2 += src0[i+6] * src1[i+6]; + s3 += src0[i+7] * src1[i+7]; + } + switch( count - i ) { + NODEFAULT; + case 3: s0 += src0[i+2] * src1[i+2]; + case 2: s1 += src0[i+1] * src1[i+1]; + case 1: s2 += src0[i+0] * src1[i+0]; + case 0: + break; + } + return s0 + s1 + s2 + s3; + +#endif +} + +/* +======================== +LowerTriangularSolve_SIMD + +Solves x in Lx = b for the n * n sub-matrix of L. + * if skip > 0 the first skip elements of x are assumed to be valid already + * L has to be a lower triangular matrix with (implicit) ones on the diagonal + * x == b is allowed +======================== +*/ +static void LowerTriangularSolve_SIMD( const idMatX & L, float * x, const float * b, const int n, int skip ) { + if ( skip >= n ) { + return; + } + + const float *lptr = L.ToFloatPtr(); + int nc = L.GetNumColumns(); + + assert( ( nc & 3 ) == 0 ); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + #undef NSKIP + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + int i = skip; + +#ifndef _lint + + // work up to a multiple of 4 rows + for ( ; ( i & 3 ) != 0 && i < n; i++ ) { + __m128 sum = _mm_load_ss( & b[i] ); + int j = 0; + for ( ; j < i - 3; j += 4 ) { + __m128 s0 = _mm_load_ps( lptr + j ); + __m128 s1 = _mm_load_ps( x + j ); + sum = _mm_sub_ps( sum, _mm_mul_ps( s0, s1 ) ); + } + __m128 mask = _mm_load_ps( (float *) & SIMD_SP_indexedEndMask[i & 3] ); + __m128 s0 = _mm_and_ps( _mm_load_ps( lptr + j ), mask ); + __m128 s1 = _mm_and_ps( _mm_load_ps( x + j ), mask ); + sum = _mm_sub_ps( sum, _mm_mul_ps( s0, s1 ) ); + sum = _mm_add_ps( sum, _mm_shuffle_ps( sum, sum, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + sum = _mm_add_ps( sum, _mm_shuffle_ps( sum, sum, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + _mm_store_ss( & x[i], sum ); + lptr += nc; + } + + for ( ; i + 3 < n; i += 4 ) { + const float * lptr0 = &lptr[0*nc]; + const float * lptr1 = &lptr[1*nc]; + const float * lptr2 = &lptr[2*nc]; + const float * lptr3 = &lptr[3*nc]; + + assert_16_byte_aligned( lptr0 ); + assert_16_byte_aligned( lptr1 ); + assert_16_byte_aligned( lptr2 ); + assert_16_byte_aligned( lptr3 ); + + __m128 va = _mm_load_ss( & b[i+0] ); + __m128 vb = _mm_load_ss( & b[i+1] ); + __m128 vc = _mm_load_ss( & b[i+2] ); + __m128 vd = _mm_load_ss( & b[i+3] ); + + __m128 x0 = _mm_load_ps( & x[0] ); + + va = _mm_sub_ps( va, _mm_mul_ps( x0, _mm_load_ps( lptr0 + 0 ) ) ); + vb = _mm_sub_ps( vb, _mm_mul_ps( x0, _mm_load_ps( lptr1 + 0 ) ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( x0, _mm_load_ps( lptr2 + 0 ) ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( x0, _mm_load_ps( lptr3 + 0 ) ) ); + + for ( int j = 4; j < i; j += 4 ) { + __m128 xj = _mm_load_ps( &x[j] ); + + va = _mm_sub_ps( va, _mm_mul_ps( xj, _mm_load_ps( lptr0 + j ) ) ); + vb = _mm_sub_ps( vb, _mm_mul_ps( xj, _mm_load_ps( lptr1 + j ) ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( xj, _mm_load_ps( lptr2 + j ) ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( xj, _mm_load_ps( lptr3 + j ) ) ); + } + + vb = _mm_sub_ps( vb, _mm_mul_ps( va, _mm_load1_ps( lptr1 + i + 0 ) ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( va, _mm_load1_ps( lptr2 + i + 0 ) ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( vb, _mm_load1_ps( lptr2 + i + 1 ) ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( va, _mm_load1_ps( lptr3 + i + 0 ) ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( vb, _mm_load1_ps( lptr3 + i + 1 ) ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( vc, _mm_load1_ps( lptr3 + i + 2 ) ) ); + + __m128 ta = _mm_unpacklo_ps( va, vc ); // x0, z0, x1, z1 + __m128 tb = _mm_unpackhi_ps( va, vc ); // x2, z2, x3, z3 + __m128 tc = _mm_unpacklo_ps( vb, vd ); // y0, w0, y1, w1 + __m128 td = _mm_unpackhi_ps( vb, vd ); // y2, w2, y3, w3 + + va = _mm_unpacklo_ps( ta, tc ); // x0, y0, z0, w0 + vb = _mm_unpackhi_ps( ta, tc ); // x1, y1, z1, w1 + vc = _mm_unpacklo_ps( tb, td ); // x2, y2, z2, w2 + vd = _mm_unpackhi_ps( tb, td ); // x3, y3, z3, w3 + + va = _mm_add_ps( va, vb ); + vc = _mm_add_ps( vc, vd ); + va = _mm_add_ps( va, vc ); + + _mm_store_ps( & x[i], va ); + + lptr += 4 * nc; + } + + // go through any remaining rows + for ( ; i < n; i++ ) { + __m128 sum = _mm_load_ss( & b[i] ); + int j = 0; + for ( ; j < i - 3; j += 4 ) { + __m128 s0 = _mm_load_ps( lptr + j ); + __m128 s1 = _mm_load_ps( x + j ); + sum = _mm_sub_ps( sum, _mm_mul_ps( s0, s1 ) ); + } + __m128 mask = _mm_load_ps( (float *) & SIMD_SP_indexedEndMask[i & 3] ); + __m128 s0 = _mm_and_ps( _mm_load_ps( lptr + j ), mask ); + __m128 s1 = _mm_and_ps( _mm_load_ps( x + j ), mask ); + sum = _mm_sub_ps( sum, _mm_mul_ps( s0, s1 ) ); + sum = _mm_add_ps( sum, _mm_shuffle_ps( sum, sum, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + sum = _mm_add_ps( sum, _mm_shuffle_ps( sum, sum, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + _mm_store_ss( & x[i], sum ); + lptr += nc; + } + +#else + + // work up to a multiple of 4 rows + for ( ; ( i & 3 ) != 0 && i < n; i++ ) { + float sum = b[i]; + for ( int j = 0; j < i; j++ ) { + sum -= lptr[j] * x[j]; + } + x[i] = sum; + lptr += nc; + } + + assert_16_byte_aligned( x ); + + for ( ; i + 3 < n; i += 4 ) { + const float * lptr0 = &lptr[0*nc]; + const float * lptr1 = &lptr[1*nc]; + const float * lptr2 = &lptr[2*nc]; + const float * lptr3 = &lptr[3*nc]; + + assert_16_byte_aligned( lptr0 ); + assert_16_byte_aligned( lptr1 ); + assert_16_byte_aligned( lptr2 ); + assert_16_byte_aligned( lptr3 ); + + float a0 = - lptr0[0] * x[0] + b[i+0]; + float a1 = - lptr0[1] * x[1]; + float a2 = - lptr0[2] * x[2]; + float a3 = - lptr0[3] * x[3]; + + float b0 = - lptr1[0] * x[0] + b[i+1]; + float b1 = - lptr1[1] * x[1]; + float b2 = - lptr1[2] * x[2]; + float b3 = - lptr1[3] * x[3]; + + float c0 = - lptr2[0] * x[0] + b[i+2]; + float c1 = - lptr2[1] * x[1]; + float c2 = - lptr2[2] * x[2]; + float c3 = - lptr2[3] * x[3]; + + float d0 = - lptr3[0] * x[0] + b[i+3]; + float d1 = - lptr3[1] * x[1]; + float d2 = - lptr3[2] * x[2]; + float d3 = - lptr3[3] * x[3]; + + for ( int j = 4; j < i; j += 4 ) { + a0 -= lptr0[j+0] * x[j+0]; + a1 -= lptr0[j+1] * x[j+1]; + a2 -= lptr0[j+2] * x[j+2]; + a3 -= lptr0[j+3] * x[j+3]; + + b0 -= lptr1[j+0] * x[j+0]; + b1 -= lptr1[j+1] * x[j+1]; + b2 -= lptr1[j+2] * x[j+2]; + b3 -= lptr1[j+3] * x[j+3]; + + c0 -= lptr2[j+0] * x[j+0]; + c1 -= lptr2[j+1] * x[j+1]; + c2 -= lptr2[j+2] * x[j+2]; + c3 -= lptr2[j+3] * x[j+3]; + + d0 -= lptr3[j+0] * x[j+0]; + d1 -= lptr3[j+1] * x[j+1]; + d2 -= lptr3[j+2] * x[j+2]; + d3 -= lptr3[j+3] * x[j+3]; + } + + b0 -= lptr1[i+0] * a0; + b1 -= lptr1[i+0] * a1; + b2 -= lptr1[i+0] * a2; + b3 -= lptr1[i+0] * a3; + + c0 -= lptr2[i+0] * a0; + c1 -= lptr2[i+0] * a1; + c2 -= lptr2[i+0] * a2; + c3 -= lptr2[i+0] * a3; + + c0 -= lptr2[i+1] * b0; + c1 -= lptr2[i+1] * b1; + c2 -= lptr2[i+1] * b2; + c3 -= lptr2[i+1] * b3; + + d0 -= lptr3[i+0] * a0; + d1 -= lptr3[i+0] * a1; + d2 -= lptr3[i+0] * a2; + d3 -= lptr3[i+0] * a3; + + d0 -= lptr3[i+1] * b0; + d1 -= lptr3[i+1] * b1; + d2 -= lptr3[i+1] * b2; + d3 -= lptr3[i+1] * b3; + + d0 -= lptr3[i+2] * c0; + d1 -= lptr3[i+2] * c1; + d2 -= lptr3[i+2] * c2; + d3 -= lptr3[i+2] * c3; + + x[i+0] = a0 + a1 + a2 + a3; + x[i+1] = b0 + b1 + b2 + b3; + x[i+2] = c0 + c1 + c2 + c3; + x[i+3] = d0 + d1 + d2 + d3; + + lptr += 4 * nc; + } + + // go through any remaining rows + for ( ; i < n; i++ ) { + float sum = b[i]; + for ( int j = 0; j < i; j++ ) { + sum -= lptr[j] * x[j]; + } + x[i] = sum; + lptr += nc; + } + +#endif + +} + +/* +======================== +LowerTriangularSolveTranspose_SIMD + +Solves x in L'x = b for the n * n sub-matrix of L. + * L has to be a lower triangular matrix with (implicit) ones on the diagonal + * x == b is allowed +======================== +*/ +static void LowerTriangularSolveTranspose_SIMD( const idMatX & L, float * x, const float * b, const int n ) { + int nc = L.GetNumColumns(); + + assert( ( nc & 3 ) == 0 ); + + int m = n; + int r = n & 3; + + if ( ( m & 3 ) != 0 ) { + const float * lptr = L.ToFloatPtr() + m * nc + m; + if ( ( m & 3 ) == 1 ) { + x[m-1] = b[m-1]; + m -= 1; + } else if ( ( m & 3 ) == 2 ) { + x[m-1] = b[m-1]; + x[m-2] = b[m-2] - lptr[-1*nc-2] * x[m-1]; + m -= 2; + } else { + x[m-1] = b[m-1]; + x[m-2] = b[m-2] - lptr[-1*nc-2] * x[m-1]; + x[m-3] = b[m-3] - lptr[-1*nc-3] * x[m-1] - lptr[-2*nc-3] * x[m-2]; + m -= 3; + } + } + + const float * lptr = L.ToFloatPtr() + m * nc + m - 4; + float * xptr = x + m; + +#ifndef _lint + + // process 4 rows at a time + for ( int i = m; i >= 4; i -= 4 ) { + assert_16_byte_aligned( b ); + assert_16_byte_aligned( xptr ); + assert_16_byte_aligned( lptr ); + + __m128 s0 = _mm_load_ps( &b[i-4] ); + __m128 s1 = (__m128 &)SIMD_SP_zero; + __m128 s2 = (__m128 &)SIMD_SP_zero; + __m128 s3 = (__m128 &)SIMD_SP_zero; + + // process 4x4 blocks + const float * xptr2 = xptr; // x + i; + const float * lptr2 = lptr; // ptr = L[i] + i - 4; + for ( int j = i; j < m; j += 4 ) { + __m128 xj = _mm_load_ps( xptr2 ); + + s0 = _mm_sub_ps( s0, _mm_mul_ps( _mm_splat_ps( xj, 0 ), _mm_load_ps( lptr2 + 0 * nc ) ) ); + s1 = _mm_sub_ps( s1, _mm_mul_ps( _mm_splat_ps( xj, 1 ), _mm_load_ps( lptr2 + 1 * nc ) ) ); + s2 = _mm_sub_ps( s2, _mm_mul_ps( _mm_splat_ps( xj, 2 ), _mm_load_ps( lptr2 + 2 * nc ) ) ); + s3 = _mm_sub_ps( s3, _mm_mul_ps( _mm_splat_ps( xj, 3 ), _mm_load_ps( lptr2 + 3 * nc ) ) ); + + lptr2 += 4 * nc; + xptr2 += 4; + } + for ( int j = 0; j < r; j++ ) { + s0 = _mm_sub_ps( s0, _mm_mul_ps( _mm_load_ps( lptr2 ), _mm_load1_ps( &xptr2[j] ) ) ); + lptr2 += nc; + } + s0 = _mm_add_ps( s0, s1 ); + s2 = _mm_add_ps( s2, s3 ); + s0 = _mm_add_ps( s0, s2 ); + // process left over of the 4 rows + lptr -= 4 * nc; + __m128 t0 = _mm_and_ps( _mm_load_ps( lptr + 3 * nc ), (__m128 &)SIMD_SP_clearLast1 ); + __m128 t1 = _mm_and_ps( _mm_load_ps( lptr + 2 * nc ), (__m128 &)SIMD_SP_clearLast2 ); + __m128 t2 = _mm_load_ss( lptr + 1 * nc ); + s0 = _mm_sub_ps( s0, _mm_mul_ps( t0, _mm_splat_ps( s0, 3 ) ) ); + s0 = _mm_sub_ps( s0, _mm_mul_ps( t1, _mm_splat_ps( s0, 2 ) ) ); + s0 = _mm_sub_ps( s0, _mm_mul_ps( t2, _mm_splat_ps( s0, 1 ) ) ); + // store result + _mm_store_ps( &xptr[-4], s0 ); + // update pointers for next four rows + lptr -= 4; + xptr -= 4; + } + +#else + + // process 4 rows at a time + for ( int i = m; i >= 4; i -= 4 ) { + assert_16_byte_aligned( b ); + assert_16_byte_aligned( xptr ); + assert_16_byte_aligned( lptr ); + + float s0 = b[i-4]; + float s1 = b[i-3]; + float s2 = b[i-2]; + float s3 = b[i-1]; + // process 4x4 blocks + const float * xptr2 = xptr; // x + i; + const float * lptr2 = lptr; // ptr = L[i] + i - 4; + for ( int j = i; j < m; j += 4 ) { + float t0 = xptr2[0]; + s0 -= lptr2[0] * t0; + s1 -= lptr2[1] * t0; + s2 -= lptr2[2] * t0; + s3 -= lptr2[3] * t0; + lptr2 += nc; + float t1 = xptr2[1]; + s0 -= lptr2[0] * t1; + s1 -= lptr2[1] * t1; + s2 -= lptr2[2] * t1; + s3 -= lptr2[3] * t1; + lptr2 += nc; + float t2 = xptr2[2]; + s0 -= lptr2[0] * t2; + s1 -= lptr2[1] * t2; + s2 -= lptr2[2] * t2; + s3 -= lptr2[3] * t2; + lptr2 += nc; + float t3 = xptr2[3]; + s0 -= lptr2[0] * t3; + s1 -= lptr2[1] * t3; + s2 -= lptr2[2] * t3; + s3 -= lptr2[3] * t3; + lptr2 += nc; + xptr2 += 4; + } + for ( int j = 0; j < r; j++ ) { + float t = xptr2[j]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + } + // process left over of the 4 rows + lptr -= nc; + s0 -= lptr[0] * s3; + s1 -= lptr[1] * s3; + s2 -= lptr[2] * s3; + lptr -= nc; + s0 -= lptr[0] * s2; + s1 -= lptr[1] * s2; + lptr -= nc; + s0 -= lptr[0] * s1; + lptr -= nc; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4; + xptr -= 4; + } + +#endif + +} + +/* +======================== +UpperTriangularSolve_SIMD + +Solves x in Ux = b for the n * n sub-matrix of U. + * U has to be a upper triangular matrix + * invDiag is the reciprical of the diagonal of the upper triangular matrix. + * x == b is allowed +======================== +*/ +static void UpperTriangularSolve_SIMD( const idMatX & U, const float * invDiag, float * x, const float * b, const int n ) { + for ( int i = n - 1; i >= 0; i-- ) { + float sum = b[i]; + const float * uptr = U[i]; + for ( int j = i + 1; j < n; j++ ) { + sum -= uptr[j] * x[j]; + } + x[i] = sum * invDiag[i]; + } +} + +/* +======================== +LU_Factor_SIMD + +In-place factorization LU of the n * n sub-matrix of mat. The reciprocal of the diagonal +elements of U are stored in invDiag. No pivoting is used. +======================== +*/ +static bool LU_Factor_SIMD( idMatX & mat, idVecX & invDiag, const int n ) { + for ( int i = 0; i < n; i++ ) { + + float d1 = mat[i][i]; + + if ( fabs( d1 ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; + } + + invDiag[i] = d1 = 1.0f / d1; + + float * ptr1 = mat[i]; + + for ( int j = i + 1; j < n; j++ ) { + + float * ptr2 = mat[j]; + float d2 = ptr2[i] * d1; + ptr2[i] = d2; + + int k; + for ( k = n - 1; k > i + 15; k -= 16 ) { + ptr2[k-0] -= d2 * ptr1[k-0]; + ptr2[k-1] -= d2 * ptr1[k-1]; + ptr2[k-2] -= d2 * ptr1[k-2]; + ptr2[k-3] -= d2 * ptr1[k-3]; + ptr2[k-4] -= d2 * ptr1[k-4]; + ptr2[k-5] -= d2 * ptr1[k-5]; + ptr2[k-6] -= d2 * ptr1[k-6]; + ptr2[k-7] -= d2 * ptr1[k-7]; + ptr2[k-8] -= d2 * ptr1[k-8]; + ptr2[k-9] -= d2 * ptr1[k-9]; + ptr2[k-10] -= d2 * ptr1[k-10]; + ptr2[k-11] -= d2 * ptr1[k-11]; + ptr2[k-12] -= d2 * ptr1[k-12]; + ptr2[k-13] -= d2 * ptr1[k-13]; + ptr2[k-14] -= d2 * ptr1[k-14]; + ptr2[k-15] -= d2 * ptr1[k-15]; + } + switch( k - i ) { + NODEFAULT; + case 15: ptr2[k-14] -= d2 * ptr1[k-14]; + case 14: ptr2[k-13] -= d2 * ptr1[k-13]; + case 13: ptr2[k-12] -= d2 * ptr1[k-12]; + case 12: ptr2[k-11] -= d2 * ptr1[k-11]; + case 11: ptr2[k-10] -= d2 * ptr1[k-10]; + case 10: ptr2[k-9] -= d2 * ptr1[k-9]; + case 9: ptr2[k-8] -= d2 * ptr1[k-8]; + case 8: ptr2[k-7] -= d2 * ptr1[k-7]; + case 7: ptr2[k-6] -= d2 * ptr1[k-6]; + case 6: ptr2[k-5] -= d2 * ptr1[k-5]; + case 5: ptr2[k-4] -= d2 * ptr1[k-4]; + case 4: ptr2[k-3] -= d2 * ptr1[k-3]; + case 3: ptr2[k-2] -= d2 * ptr1[k-2]; + case 2: ptr2[k-1] -= d2 * ptr1[k-1]; + case 1: ptr2[k-0] -= d2 * ptr1[k-0]; + case 0: break; + } + } + } + + return true; +} + +/* +======================== +LDLT_Factor_SIMD + +In-place factorization LDL' of the n * n sub-matrix of mat. The reciprocal of the diagonal +elements are stored in invDiag. + +NOTE: The number of columns of mat must be a multiple of 4. +======================== +*/ +static bool LDLT_Factor_SIMD( idMatX & mat, idVecX & invDiag, const int n ) { + float s0, s1, s2, d; + + float * v = (float *) _alloca16( ( ( n + 3 ) & ~3 ) * sizeof( float ) ); + float * diag = (float *) _alloca16( ( ( n + 3 ) & ~3 ) * sizeof( float ) ); + float * invDiagPtr = invDiag.ToFloatPtr(); + + int nc = mat.GetNumColumns(); + + assert( ( nc & 3 ) == 0 ); + + if ( n <= 0 ) { + return true; + } + + float * mptr = mat[0]; + + float sum = mptr[0]; + + if ( fabs( sum ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; + } + + diag[0] = sum; + invDiagPtr[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( int j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( fabs( sum ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiagPtr[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( int j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( fabs( sum ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiagPtr[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( int j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( fabs( sum ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiagPtr[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( int j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + +#ifndef _lint + + __m128 vzero = _mm_setzero_ps(); + for ( int i = 4; i < n; i += 4 ) { + _mm_store_ps( diag + i, vzero ); + } + + for ( int i = 4; i < n; i++ ) { + mptr = mat[i]; + + assert_16_byte_aligned( v ); + assert_16_byte_aligned( mptr ); + assert_16_byte_aligned( diag ); + + __m128 m0 = _mm_load_ps( mptr + 0 ); + __m128 d0 = _mm_load_ps( diag + 0 ); + __m128 v0 = _mm_mul_ps( d0, m0 ); + __m128 t0 = _mm_load_ss( mptr + i ); + t0 = _mm_sub_ps( t0, _mm_mul_ps( m0, v0 ) ); + _mm_store_ps( v + 0, v0 ); + + int k = 4; + for ( ; k < i - 3; k += 4 ) { + m0 = _mm_load_ps( mptr + k ); + d0 = _mm_load_ps( diag + k ); + v0 = _mm_mul_ps( d0, m0 ); + t0 = _mm_sub_ps( t0, _mm_mul_ps( m0, v0 ) ); + _mm_store_ps( v + k, v0 ); + } + + __m128 mask = (__m128 &) SIMD_SP_indexedEndMask[i & 3]; + + m0 = _mm_and_ps( _mm_load_ps( mptr + k ), mask ); + d0 = _mm_load_ps( diag + k ); + v0 = _mm_mul_ps( d0, m0 ); + t0 = _mm_sub_ps( t0, _mm_mul_ps( m0, v0 ) ); + _mm_store_ps( v + k, v0 ); + + t0 = _mm_add_ps( t0, _mm_shuffle_ps( t0, t0, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + t0 = _mm_add_ps( t0, _mm_shuffle_ps( t0, t0, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + __m128 tiny = _mm_and_ps( _mm_cmpeq_ps( t0, SIMD_SP_zero ), SIMD_SP_tiny ); + t0 = _mm_or_ps( t0, tiny ); + + _mm_store_ss( mptr + i, t0 ); + _mm_store_ss( diag + i, t0 ); + + __m128 d = _mm_rcp32_ps( t0 ); + _mm_store_ss( invDiagPtr + i, d ); + + if ( i + 1 >= n ) { + return true; + } + + int j = i + 1; + for ( ; j < n - 3; j += 4 ) { + float * ra = mat[j+0]; + float * rb = mat[j+1]; + float * rc = mat[j+2]; + float * rd = mat[j+3]; + + assert_16_byte_aligned( v ); + assert_16_byte_aligned( ra ); + assert_16_byte_aligned( rb ); + assert_16_byte_aligned( rc ); + assert_16_byte_aligned( rd ); + + __m128 va = _mm_load_ss( ra + i ); + __m128 vb = _mm_load_ss( rb + i ); + __m128 vc = _mm_load_ss( rc + i ); + __m128 vd = _mm_load_ss( rd + i ); + + __m128 v0 = _mm_load_ps( v + 0 ); + + va = _mm_sub_ps( va, _mm_mul_ps( _mm_load_ps( ra + 0 ), v0 ) ); + vb = _mm_sub_ps( vb, _mm_mul_ps( _mm_load_ps( rb + 0 ), v0 ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( _mm_load_ps( rc + 0 ), v0 ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( _mm_load_ps( rd + 0 ), v0 ) ); + + int k = 4; + for ( ; k < i - 3; k += 4 ) { + v0 = _mm_load_ps( v + k ); + + va = _mm_sub_ps( va, _mm_mul_ps( _mm_load_ps( ra + k ), v0 ) ); + vb = _mm_sub_ps( vb, _mm_mul_ps( _mm_load_ps( rb + k ), v0 ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( _mm_load_ps( rc + k ), v0 ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( _mm_load_ps( rd + k ), v0 ) ); + } + + v0 = _mm_load_ps( v + k ); + + va = _mm_sub_ps( va, _mm_mul_ps( _mm_and_ps( _mm_load_ps( ra + k ), mask ), v0 ) ); + vb = _mm_sub_ps( vb, _mm_mul_ps( _mm_and_ps( _mm_load_ps( rb + k ), mask ), v0 ) ); + vc = _mm_sub_ps( vc, _mm_mul_ps( _mm_and_ps( _mm_load_ps( rc + k ), mask ), v0 ) ); + vd = _mm_sub_ps( vd, _mm_mul_ps( _mm_and_ps( _mm_load_ps( rd + k ), mask ), v0 ) ); + + __m128 ta = _mm_unpacklo_ps( va, vc ); // x0, z0, x1, z1 + __m128 tb = _mm_unpackhi_ps( va, vc ); // x2, z2, x3, z3 + __m128 tc = _mm_unpacklo_ps( vb, vd ); // y0, w0, y1, w1 + __m128 td = _mm_unpackhi_ps( vb, vd ); // y2, w2, y3, w3 + + va = _mm_unpacklo_ps( ta, tc ); // x0, y0, z0, w0 + vb = _mm_unpackhi_ps( ta, tc ); // x1, y1, z1, w1 + vc = _mm_unpacklo_ps( tb, td ); // x2, y2, z2, w2 + vd = _mm_unpackhi_ps( tb, td ); // x3, y3, z3, w3 + + va = _mm_add_ps( va, vb ); + vc = _mm_add_ps( vc, vd ); + va = _mm_add_ps( va, vc ); + va = _mm_mul_ps( va, d ); + + _mm_store_ss( ra + i, _mm_splat_ps( va, 0 ) ); + _mm_store_ss( rb + i, _mm_splat_ps( va, 1 ) ); + _mm_store_ss( rc + i, _mm_splat_ps( va, 2 ) ); + _mm_store_ss( rd + i, _mm_splat_ps( va, 3 ) ); + } + for ( ; j < n; j++ ) { + float * mptr = mat[j]; + + assert_16_byte_aligned( v ); + assert_16_byte_aligned( mptr ); + + __m128 va = _mm_load_ss( mptr + i ); + __m128 v0 = _mm_load_ps( v + 0 ); + + va = _mm_sub_ps( va, _mm_mul_ps( _mm_load_ps( mptr + 0 ), v0 ) ); + + int k = 4; + for ( ; k < i - 3; k += 4 ) { + v0 = _mm_load_ps( v + k ); + va = _mm_sub_ps( va, _mm_mul_ps( _mm_load_ps( mptr + k ), v0 ) ); + } + + v0 = _mm_load_ps( v + k ); + va = _mm_sub_ps( va, _mm_mul_ps( _mm_and_ps( _mm_load_ps( mptr + k ), mask ), v0 ) ); + + va = _mm_add_ps( va, _mm_shuffle_ps( va, va, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + va = _mm_add_ps( va, _mm_shuffle_ps( va, va, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + va = _mm_mul_ps( va, d ); + + _mm_store_ss( mptr + i, va ); + } + } + return true; + +#else + + for ( int i = 4; i < n; i += 4 ) { + diag[i+0] = 0.0f; + diag[i+1] = 0.0f; + diag[i+2] = 0.0f; + diag[i+3] = 0.0f; + } + + for ( int i = 4; i < n; i++ ) { + mptr = mat[i]; + + assert_16_byte_aligned( v ); + assert_16_byte_aligned( mptr ); + assert_16_byte_aligned( diag ); + + v[0] = diag[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; + + float t0 = - mptr[0] * v[0] + mptr[i]; + float t1 = - mptr[1] * v[1]; + float t2 = - mptr[2] * v[2]; + float t3 = - mptr[3] * v[3]; + + int k = 4; + for ( ; k < i - 3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; + + t0 -= mptr[k+0] * v[k+0]; + t1 -= mptr[k+1] * v[k+1]; + t2 -= mptr[k+2] * v[k+2]; + t3 -= mptr[k+3] * v[k+3]; + } + + float m0 = ( i - k > 0 ) ? mptr[k+0] : 0.0f; + float m1 = ( i - k > 1 ) ? mptr[k+1] : 0.0f; + float m2 = ( i - k > 2 ) ? mptr[k+2] : 0.0f; + float m3 = ( i - k > 3 ) ? mptr[k+3] : 0.0f; + + v[k+0] = diag[k+0] * m0; + v[k+1] = diag[k+1] * m1; + v[k+2] = diag[k+2] * m2; + v[k+3] = diag[k+3] * m3; + + t0 -= m0 * v[k+0]; + t1 -= m1 * v[k+1]; + t2 -= m2 * v[k+2]; + t3 -= m3 * v[k+3]; + + sum = t0 + t1 + t2 + t3; + + if ( fabs( sum ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiagPtr[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + int j = i + 1; + for ( ; j < n - 3; j += 4 ) { + float * ra = mat[j+0]; + float * rb = mat[j+1]; + float * rc = mat[j+2]; + float * rd = mat[j+3]; + + assert_16_byte_aligned( v ); + assert_16_byte_aligned( ra ); + assert_16_byte_aligned( rb ); + assert_16_byte_aligned( rc ); + assert_16_byte_aligned( rd ); + + float a0 = - ra[0] * v[0] + ra[i]; + float a1 = - ra[1] * v[1]; + float a2 = - ra[2] * v[2]; + float a3 = - ra[3] * v[3]; + + float b0 = - rb[0] * v[0] + rb[i]; + float b1 = - rb[1] * v[1]; + float b2 = - rb[2] * v[2]; + float b3 = - rb[3] * v[3]; + + float c0 = - rc[0] * v[0] + rc[i]; + float c1 = - rc[1] * v[1]; + float c2 = - rc[2] * v[2]; + float c3 = - rc[3] * v[3]; + + float d0 = - rd[0] * v[0] + rd[i]; + float d1 = - rd[1] * v[1]; + float d2 = - rd[2] * v[2]; + float d3 = - rd[3] * v[3]; + + int k = 4; + for ( ; k < i - 3; k += 4 ) { + a0 -= ra[k+0] * v[k+0]; + a1 -= ra[k+1] * v[k+1]; + a2 -= ra[k+2] * v[k+2]; + a3 -= ra[k+3] * v[k+3]; + + b0 -= rb[k+0] * v[k+0]; + b1 -= rb[k+1] * v[k+1]; + b2 -= rb[k+2] * v[k+2]; + b3 -= rb[k+3] * v[k+3]; + + c0 -= rc[k+0] * v[k+0]; + c1 -= rc[k+1] * v[k+1]; + c2 -= rc[k+2] * v[k+2]; + c3 -= rc[k+3] * v[k+3]; + + d0 -= rd[k+0] * v[k+0]; + d1 -= rd[k+1] * v[k+1]; + d2 -= rd[k+2] * v[k+2]; + d3 -= rd[k+3] * v[k+3]; + } + + float ra0 = ( i - k > 0 ) ? ra[k+0] : 0.0f; + float ra1 = ( i - k > 1 ) ? ra[k+1] : 0.0f; + float ra2 = ( i - k > 2 ) ? ra[k+2] : 0.0f; + float ra3 = ( i - k > 3 ) ? ra[k+3] : 0.0f; + + float rb0 = ( i - k > 0 ) ? rb[k+0] : 0.0f; + float rb1 = ( i - k > 1 ) ? rb[k+1] : 0.0f; + float rb2 = ( i - k > 2 ) ? rb[k+2] : 0.0f; + float rb3 = ( i - k > 3 ) ? rb[k+3] : 0.0f; + + float rc0 = ( i - k > 0 ) ? rc[k+0] : 0.0f; + float rc1 = ( i - k > 1 ) ? rc[k+1] : 0.0f; + float rc2 = ( i - k > 2 ) ? rc[k+2] : 0.0f; + float rc3 = ( i - k > 3 ) ? rc[k+3] : 0.0f; + + float rd0 = ( i - k > 0 ) ? rd[k+0] : 0.0f; + float rd1 = ( i - k > 1 ) ? rd[k+1] : 0.0f; + float rd2 = ( i - k > 2 ) ? rd[k+2] : 0.0f; + float rd3 = ( i - k > 3 ) ? rd[k+3] : 0.0f; + + a0 -= ra0 * v[k+0]; + a1 -= ra1 * v[k+1]; + a2 -= ra2 * v[k+2]; + a3 -= ra3 * v[k+3]; + + b0 -= rb0 * v[k+0]; + b1 -= rb1 * v[k+1]; + b2 -= rb2 * v[k+2]; + b3 -= rb3 * v[k+3]; + + c0 -= rc0 * v[k+0]; + c1 -= rc1 * v[k+1]; + c2 -= rc2 * v[k+2]; + c3 -= rc3 * v[k+3]; + + d0 -= rd0 * v[k+0]; + d1 -= rd1 * v[k+1]; + d2 -= rd2 * v[k+2]; + d3 -= rd3 * v[k+3]; + + ra[i] = ( a0 + a1 + a2 + a3 ) * d; + rb[i] = ( b0 + b1 + b2 + b3 ) * d; + rc[i] = ( c0 + c1 + c2 + c3 ) * d; + rd[i] = ( d0 + d1 + d2 + d3 ) * d; + } + for ( ; j < n; j++ ) { + mptr = mat[j]; + + assert_16_byte_aligned( v ); + assert_16_byte_aligned( mptr ); + + float a0 = - mptr[0] * v[0] + mptr[i]; + float a1 = - mptr[1] * v[1]; + float a2 = - mptr[2] * v[2]; + float a3 = - mptr[3] * v[3]; + + int k = 4; + for ( ; k < i - 3; k += 4 ) { + a0 -= mptr[k+0] * v[k+0]; + a1 -= mptr[k+1] * v[k+1]; + a2 -= mptr[k+2] * v[k+2]; + a3 -= mptr[k+3] * v[k+3]; + } + + float m0 = ( i - k > 0 ) ? mptr[k+0] : 0.0f; + float m1 = ( i - k > 1 ) ? mptr[k+1] : 0.0f; + float m2 = ( i - k > 2 ) ? mptr[k+2] : 0.0f; + float m3 = ( i - k > 3 ) ? mptr[k+3] : 0.0f; + + a0 -= m0 * v[k+0]; + a1 -= m1 * v[k+1]; + a2 -= m2 * v[k+2]; + a3 -= m3 * v[k+3]; + + mptr[i] = ( a0 + a1 + a2 + a3 ) * d; + } + } + return true; + +#endif +} + +/* +======================== +GetMaxStep_SIMD +======================== +*/ +static void GetMaxStep_SIMD( const float * f, const float * a, const float * delta_f, const float * delta_a, + const float * lo, const float * hi, const int * side, int numUnbounded, int numClamped, + int d, float dir, float & maxStep, int & limit, int & limitSide ) { + + + __m128 vMaxStep; + __m128i vLimit; + __m128i vLimitSide; + + // default to a full step for the current variable + { + __m128 vNegAccel = _mm_xor_ps( _mm_load1_ps( a + d ), (__m128 &) SIMD_SP_signBit ); + __m128 vDeltaAccel = _mm_load1_ps( delta_a + d ); + __m128 vM0 = _mm_cmpgt_ps( _mm_and_ps( vDeltaAccel, (__m128 &) SIMD_SP_absMask ), SIMD_SP_LCP_DELTA_ACCEL_EPSILON ); + __m128 vStep = _mm_div32_ps( vNegAccel, _mm_sel_ps( SIMD_SP_one, vDeltaAccel, vM0 ) ); + vMaxStep = _mm_sel_ps( SIMD_SP_zero, vStep, vM0 ); + vLimit = _mm_shuffle_epi32( _mm_cvtsi32_si128( d ), 0 ); + vLimitSide = (__m128i &) SIMD_DW_zero; + } + + // test the current variable + { + __m128 vDeltaForce = _mm_load1_ps( & dir ); + __m128 vSign = _mm_cmplt_ps( vDeltaForce, SIMD_SP_zero ); + __m128 vForceLimit = _mm_sel_ps( _mm_load1_ps( hi + d ), _mm_load1_ps( lo + d ), vSign ); + __m128 vStep = _mm_div32_ps( _mm_sub_ps( vForceLimit, _mm_load1_ps( f + d ) ), vDeltaForce ); + __m128i vSetSide = _mm_or_si128( __m128c( vSign ), (__m128i &) SIMD_DW_one ); + __m128 vM0 = _mm_cmpgt_ps( _mm_and_ps( vDeltaForce, (__m128 &) SIMD_SP_absMask ), SIMD_SP_LCP_DELTA_FORCE_EPSILON ); + __m128 vM1 = _mm_cmpneq_ps( _mm_and_ps( vForceLimit, (__m128 &) SIMD_SP_absMask ), SIMD_SP_infinity ); + __m128 vM2 = _mm_cmplt_ps( vStep, vMaxStep ); + __m128 vM3 = _mm_and_ps( _mm_and_ps( vM0, vM1 ), vM2 ); + vMaxStep = _mm_sel_ps( vMaxStep, vStep, vM3 ); + vLimitSide = _mm_sel_si128( vLimitSide, vSetSide, __m128c( vM3 ) ); + } + + // test the clamped bounded variables + { + __m128 mask = (__m128 &) SIMD_SP_indexedStartMask[numUnbounded & 3]; + __m128i index = _mm_add_epi32( _mm_and_si128( _mm_shuffle_epi32( _mm_cvtsi32_si128( numUnbounded ), 0 ), (__m128i &) SIMD_DW_not3 ), (__m128i &) SIMD_DW_index ); + int i = numUnbounded & ~3; + for ( ; i < numClamped - 3; i += 4 ) { + __m128 vDeltaForce = _mm_and_ps( _mm_load_ps( delta_f + i ), mask ); + __m128 vSign = _mm_cmplt_ps( vDeltaForce, SIMD_SP_zero ); + __m128 vForceLimit = _mm_sel_ps( _mm_load_ps( hi + i ), _mm_load_ps( lo + i ), vSign ); + __m128 vM0 = _mm_cmpgt_ps( _mm_and_ps( vDeltaForce, (__m128 &) SIMD_SP_absMask ), SIMD_SP_LCP_DELTA_FORCE_EPSILON ); + __m128 vStep = _mm_div32_ps( _mm_sub_ps( vForceLimit, _mm_load_ps( f + i ) ), _mm_sel_ps( SIMD_SP_one, vDeltaForce, vM0 ) ); + __m128i vSetSide = _mm_or_si128( __m128c( vSign ), (__m128i &) SIMD_DW_one ); + __m128 vM1 = _mm_cmpneq_ps( _mm_and_ps( vForceLimit, (__m128 &) SIMD_SP_absMask ), SIMD_SP_infinity ); + __m128 vM2 = _mm_cmplt_ps( vStep, vMaxStep ); + __m128 vM3 = _mm_and_ps( _mm_and_ps( vM0, vM1 ), vM2 ); + vMaxStep = _mm_sel_ps( vMaxStep, vStep, vM3 ); + vLimit = _mm_sel_si128( vLimit, index, vM3 ); + vLimitSide = _mm_sel_si128( vLimitSide, vSetSide, __m128c( vM3 ) ); + mask = (__m128 &) SIMD_SP_indexedStartMask[0]; + index = _mm_add_epi32( index, (__m128i &) SIMD_DW_four ); + } + __m128 vDeltaForce = _mm_and_ps( _mm_load_ps( delta_f + i ), _mm_and_ps( mask, (__m128 &) SIMD_SP_indexedEndMask[numClamped & 3] ) ); + __m128 vSign = _mm_cmplt_ps( vDeltaForce, SIMD_SP_zero ); + __m128 vForceLimit = _mm_sel_ps( _mm_load_ps( hi + i ), _mm_load_ps( lo + i ), vSign ); + __m128 vM0 = _mm_cmpgt_ps( _mm_and_ps( vDeltaForce, (__m128 &) SIMD_SP_absMask ), SIMD_SP_LCP_DELTA_FORCE_EPSILON ); + __m128 vStep = _mm_div32_ps( _mm_sub_ps( vForceLimit, _mm_load_ps( f + i ) ), _mm_sel_ps( SIMD_SP_one, vDeltaForce, vM0 ) ); + __m128i vSetSide = _mm_or_si128( __m128c( vSign ), (__m128i &) SIMD_DW_one ); + __m128 vM1 = _mm_cmpneq_ps( _mm_and_ps( vForceLimit, (__m128 &) SIMD_SP_absMask ), SIMD_SP_infinity ); + __m128 vM2 = _mm_cmplt_ps( vStep, vMaxStep ); + __m128 vM3 = _mm_and_ps( _mm_and_ps( vM0, vM1 ), vM2 ); + vMaxStep = _mm_sel_ps( vMaxStep, vStep, vM3 ); + vLimit = _mm_sel_si128( vLimit, index, vM3 ); + vLimitSide = _mm_sel_si128( vLimitSide, vSetSide, __m128c( vM3 ) ); + } + + // test the not clamped bounded variables + { + __m128 mask = (__m128 &) SIMD_SP_indexedStartMask[numClamped & 3]; + __m128i index = _mm_add_epi32( _mm_and_si128( _mm_shuffle_epi32( _mm_cvtsi32_si128( numClamped ), 0 ), (__m128i &) SIMD_DW_not3 ), (__m128i &) SIMD_DW_index ); + int i = numClamped & ~3; + for ( ; i < d - 3; i += 4 ) { + __m128 vNegAccel = _mm_xor_ps( _mm_load_ps( a + i ), (__m128 &) SIMD_SP_signBit ); + __m128 vDeltaAccel = _mm_and_ps( _mm_load_ps( delta_a + i ), mask ); + __m128 vSide = _mm_cvtepi32_ps( _mm_load_si128( (__m128i *) ( side + i ) ) ); + __m128 vM0 = _mm_cmpgt_ps( _mm_mul_ps( vSide, vDeltaAccel ), SIMD_SP_LCP_DELTA_ACCEL_EPSILON ); + __m128 vStep = _mm_div32_ps( vNegAccel, _mm_sel_ps( SIMD_SP_one, vDeltaAccel, vM0 ) ); + __m128 vM1 = _mm_or_ps( _mm_cmplt_ps( _mm_load_ps( lo + i ), SIMD_SP_neg_LCP_BOUND_EPSILON ), _mm_cmpgt_ps( _mm_load_ps( hi + i ), SIMD_SP_LCP_BOUND_EPSILON ) ); + __m128 vM2 = _mm_cmplt_ps( vStep, vMaxStep ); + __m128 vM3 = _mm_and_ps( _mm_and_ps( vM0, vM1 ), vM2 ); + vMaxStep = _mm_sel_ps( vMaxStep, vStep, vM3 ); + vLimit = _mm_sel_si128( vLimit, index, vM3 ); + vLimitSide = _mm_sel_si128( vLimitSide, (__m128i &) SIMD_DW_zero, __m128c( vM3 ) ); + mask = (__m128 &) SIMD_SP_indexedStartMask[0]; + index = _mm_add_epi32( index, (__m128i &) SIMD_DW_four ); + } + __m128 vNegAccel = _mm_xor_ps( _mm_load_ps( a + i ), (__m128 &) SIMD_SP_signBit ); + __m128 vDeltaAccel = _mm_and_ps( _mm_load_ps( delta_a + i ), _mm_and_ps( mask, (__m128 &) SIMD_SP_indexedEndMask[d & 3] ) ); + __m128 vSide = _mm_cvtepi32_ps( _mm_load_si128( (__m128i *) ( side + i ) ) ); + __m128 vM0 = _mm_cmpgt_ps( _mm_mul_ps( vSide, vDeltaAccel ), SIMD_SP_LCP_DELTA_ACCEL_EPSILON ); + __m128 vStep = _mm_div32_ps( vNegAccel, _mm_sel_ps( SIMD_SP_one, vDeltaAccel, vM0 ) ); + __m128 vM1 = _mm_or_ps( _mm_cmplt_ps( _mm_load_ps( lo + i ), SIMD_SP_neg_LCP_BOUND_EPSILON ), _mm_cmpgt_ps( _mm_load_ps( hi + i ), SIMD_SP_LCP_BOUND_EPSILON ) ); + __m128 vM2 = _mm_cmplt_ps( vStep, vMaxStep ); + __m128 vM3 = _mm_and_ps( _mm_and_ps( vM0, vM1 ), vM2 ); + vMaxStep = _mm_sel_ps( vMaxStep, vStep, vM3 ); + vLimit = _mm_sel_si128( vLimit, index, vM3 ); + vLimitSide = _mm_sel_si128( vLimitSide, (__m128i &) SIMD_DW_zero, __m128c( vM3 ) ); + } + + { + __m128 tMaxStep = _mm_shuffle_ps( vMaxStep, vMaxStep, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + __m128i tLimit = _mm_shuffle_epi32( vLimit, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + __m128i tLimitSide = _mm_shuffle_epi32( vLimitSide, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + __m128c mask = _mm_cmplt_ps( tMaxStep, vMaxStep ); + vMaxStep = _mm_min_ps( vMaxStep, tMaxStep ); + vLimit = _mm_sel_si128( vLimit, tLimit, mask ); + vLimitSide = _mm_sel_si128( vLimitSide, tLimitSide, mask ); + } + + { + __m128 tMaxStep = _mm_shuffle_ps( vMaxStep, vMaxStep, _MM_SHUFFLE( 2, 3, 0, 1 ) ); + __m128i tLimit = _mm_shuffle_epi32( vLimit, _MM_SHUFFLE( 2, 3, 0, 1 ) ); + __m128i tLimitSide = _mm_shuffle_epi32( vLimitSide, _MM_SHUFFLE( 2, 3, 0, 1 ) ); + __m128c mask = _mm_cmplt_ps( tMaxStep, vMaxStep ); + vMaxStep = _mm_min_ps( vMaxStep, tMaxStep ); + vLimit = _mm_sel_si128( vLimit, tLimit, mask ); + vLimitSide = _mm_sel_si128( vLimitSide, tLimitSide, mask ); + } + + _mm_store_ss( & maxStep, vMaxStep ); + limit = _mm_cvtsi128_si32( vLimit ); + limitSide = _mm_cvtsi128_si32( vLimitSide ); +} + +/* +================================================================================================ + + SIMD test code + +================================================================================================ +*/ + +//#define ENABLE_TEST_CODE + +#ifdef ENABLE_TEST_CODE + +#define TEST_TRIANGULAR_SOLVE_SIMD_EPSILON 0.1f +#define TEST_TRIANGULAR_SOLVE_SIZE 50 +#define TEST_FACTOR_SIMD_EPSILON 0.1f +#define TEST_FACTOR_SOLVE_SIZE 50 +#define NUM_TESTS 50 + +/* +======================== +PrintClocks +======================== +*/ +static void PrintClocks( const char * string, int dataCount, int64 clocks, int64 otherClocks = 0 ) { + idLib::Printf( string ); + for ( int i = idStr::LengthWithoutColors(string); i < 48; i++ ) { + idLib::Printf(" "); + } + if ( clocks && otherClocks ) { + int p = 0; + if ( clocks <= otherClocks ) { + p = idMath::Ftoi( (float) ( otherClocks - clocks ) * 100.0f / (float) otherClocks ); + } else { + p = - idMath::Ftoi( (float) ( clocks - otherClocks ) * 100.0f / (float) clocks ); + } + idLib::Printf( "c = %4d, clcks = %5lld, %d%%\n", dataCount, clocks, p ); + } else { + idLib::Printf( "c = %4d, clcks = %5lld\n", dataCount, clocks ); + } +} + +/* +======================== +DotProduct_Test +======================== +*/ +static void DotProduct_Test() { + ALIGN16( float fsrc0[TEST_TRIANGULAR_SOLVE_SIZE + 1]; ) + ALIGN16( float fsrc1[TEST_TRIANGULAR_SOLVE_SIZE + 1]; ) + + idRandom srnd( 13 ); + + for ( int i = 0; i < TEST_TRIANGULAR_SOLVE_SIZE; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idTimer timer; + + for ( int i = 0; i < TEST_TRIANGULAR_SOLVE_SIZE; i++ ) { + + float dot1 = DotProduct_Generic( fsrc0, fsrc1, i ); + int64 clocksGeneric = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + fsrc1[TEST_TRIANGULAR_SOLVE_SIZE] = j; + timer.Clear(); + timer.Start(); + dot1 = DotProduct_Generic( fsrc0, fsrc1, i ); + timer.Stop(); + clocksGeneric = Min( clocksGeneric, timer.ClockTicks() ); + } + + PrintClocks( va( "DotProduct_Generic %d", i ), 1, clocksGeneric ); + + float dot2 = DotProduct_SIMD( fsrc0, fsrc1, i ); + int64 clocksSIMD = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + fsrc1[TEST_TRIANGULAR_SOLVE_SIZE] = j; + timer.Clear(); + timer.Start(); + dot2 = DotProduct_SIMD( fsrc0, fsrc1, i ); + timer.Stop(); + clocksSIMD = Min( clocksSIMD, timer.ClockTicks() ); + } + + const char * result = idMath::Fabs( dot1 - dot2 ) < 1e-4f ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( "DotProduct_SIMD %d %s", i, result ), 1, clocksSIMD, clocksGeneric ); + } +} + +/* +======================== +LowerTriangularSolve_Test +======================== +*/ +static void LowerTriangularSolve_Test() { + idMatX L; + idVecX x, b, tst; + + int paddedSize = ( TEST_TRIANGULAR_SOLVE_SIZE + 3 ) & ~3; + + L.Random( paddedSize, paddedSize, 0, -1.0f, 1.0f ); + x.SetSize( paddedSize ); + b.Random( paddedSize, 0, -1.0f, 1.0f ); + + idTimer timer; + + const int skip = 0; + + for ( int i = 1; i < TEST_TRIANGULAR_SOLVE_SIZE; i++ ) { + + x.Zero( i ); + + LowerTriangularSolve_Generic( L, x.ToFloatPtr(), b.ToFloatPtr(), i, skip ); + int64 clocksGeneric = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + timer.Clear(); + timer.Start(); + LowerTriangularSolve_Generic( L, x.ToFloatPtr(), b.ToFloatPtr(), i, skip ); + timer.Stop(); + clocksGeneric = Min( clocksGeneric, timer.ClockTicks() ); + } + + tst = x; + x.Zero(); + + PrintClocks( va( "LowerTriangularSolve_Generic %dx%d", i, i ), 1, clocksGeneric ); + + LowerTriangularSolve_SIMD( L, x.ToFloatPtr(), b.ToFloatPtr(), i, skip ); + int64 clocksSIMD = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + timer.Clear(); + timer.Start(); + LowerTriangularSolve_SIMD( L, x.ToFloatPtr(), b.ToFloatPtr(), i, skip ); + timer.Stop(); + clocksSIMD = Min( clocksSIMD, timer.ClockTicks() ); + } + + const char * result = x.Compare( tst, TEST_TRIANGULAR_SOLVE_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( "LowerTriangularSolve_SIMD %dx%d %s", i, i, result ), 1, clocksSIMD, clocksGeneric ); + } +} + +/* +======================== +LowerTriangularSolveTranspose_Test +======================== +*/ +static void LowerTriangularSolveTranspose_Test() { + idMatX L; + idVecX x, b, tst; + + int paddedSize = ( TEST_TRIANGULAR_SOLVE_SIZE + 3 ) & ~3; + + L.Random( paddedSize, paddedSize, 0, -1.0f, 1.0f ); + x.SetSize( paddedSize ); + b.Random( paddedSize, 0, -1.0f, 1.0f ); + + idTimer timer; + + for ( int i = 1; i < TEST_TRIANGULAR_SOLVE_SIZE; i++ ) { + + x.Zero( i ); + + LowerTriangularSolveTranspose_Generic( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + int64 clocksGeneric = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + timer.Clear(); + timer.Start(); + LowerTriangularSolveTranspose_Generic( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + timer.Stop(); + clocksGeneric = Min( clocksGeneric, timer.ClockTicks() ); + } + + tst = x; + x.Zero(); + + PrintClocks( va( "LowerTriangularSolveTranspose_Generic %dx%d", i, i ), 1, clocksGeneric ); + + LowerTriangularSolveTranspose_SIMD( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + int64 clocksSIMD = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + timer.Clear(); + timer.Start(); + LowerTriangularSolveTranspose_SIMD( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + timer.Stop(); + clocksSIMD = Min( clocksSIMD, timer.ClockTicks() ); + } + + const char * result = x.Compare( tst, TEST_TRIANGULAR_SOLVE_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( "LowerTriangularSolveTranspose_SIMD %dx%d %s", i, i, result ), 1, clocksSIMD, clocksGeneric ); + } +} + +/* +======================== +LDLT_Factor_Test +======================== +*/ +static void LDLT_Factor_Test() { + idMatX src, original, mat1, mat2; + idVecX invDiag1, invDiag2; + + int paddedSize = ( TEST_FACTOR_SOLVE_SIZE + 3 ) & ~3; + + original.SetSize( paddedSize, paddedSize ); + src.Random( paddedSize, paddedSize, 0, -1.0f, 1.0f ); + src.TransposeMultiply( original, src ); + + idTimer timer; + + for ( int i = 1; i < TEST_FACTOR_SOLVE_SIZE; i++ ) { + + int64 clocksGeneric = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + mat1 = original; + invDiag1.Zero( TEST_FACTOR_SOLVE_SIZE ); + timer.Clear(); + timer.Start(); + LDLT_Factor_Generic( mat1, invDiag1, i ); + timer.Stop(); + clocksGeneric = Min( clocksGeneric, timer.ClockTicks() ); + } + + PrintClocks( va( "LDLT_Factor_Generic %dx%d", i, i ), 1, clocksGeneric ); + + int64 clocksSIMD = 0xFFFFFFFFFFFF; + for ( int j = 0; j < NUM_TESTS; j++ ) { + mat2 = original; + invDiag2.Zero( TEST_FACTOR_SOLVE_SIZE ); + timer.Clear(); + timer.Start(); + LDLT_Factor_SIMD( mat2, invDiag2, i ); + timer.Stop(); + clocksSIMD = Min( clocksSIMD, timer.ClockTicks() ); + } + + const char * result = mat1.Compare( mat2, TEST_FACTOR_SIMD_EPSILON ) && invDiag1.Compare( invDiag2, TEST_FACTOR_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( "LDLT_Factor_SIMD %dx%d %s", i, i, result ), 1, clocksSIMD, clocksGeneric ); + } +} +#endif + +#define Multiply Multiply_SIMD +#define MultiplyAdd MultiplyAdd_SIMD +#define BigDotProduct DotProduct_SIMD +#define LowerTriangularSolve LowerTriangularSolve_SIMD +#define LowerTriangularSolveTranspose LowerTriangularSolveTranspose_SIMD +#define UpperTriangularSolve UpperTriangularSolve_SIMD +#define LU_Factor LU_Factor_SIMD +#define LDLT_Factor LDLT_Factor_SIMD +#define GetMaxStep GetMaxStep_SIMD + +/* +================================================================================================ + + idLCP_Square + +================================================================================================ +*/ + +/* +================================================ +idLCP_Square +================================================ +*/ +class idLCP_Square : public idLCP { +public: + virtual bool Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ); + +private: + idMatX m; // original matrix + idVecX b; // right hand side + idVecX lo, hi; // low and high bounds + idVecX f, a; // force and acceleration + idVecX delta_f, delta_a; // delta force and delta acceleration + idMatX clamped; // LU factored sub matrix for clamped variables + idVecX diagonal; // reciprocal of diagonal of U of the LU factored sub matrix for clamped variables + int numUnbounded; // number of unbounded variables + int numClamped; // number of clamped variables + float ** rowPtrs; // pointers to the rows of m + int * boxIndex; // box index + int * side; // tells if a variable is at the low boundary = -1, high boundary = 1 or inbetween = 0 + int * permuted; // index to keep track of the permutation + bool padded; // set to true if the rows of the initial matrix are 16 byte padded + + bool FactorClamped(); + void SolveClamped( idVecX & x, const float * b ); + void Swap( int i, int j ); + void AddClamped( int r ); + void RemoveClamped( int r ); + void CalcForceDelta( int d, float dir ); + void CalcAccelDelta( int d ); + void ChangeForce( int d, float step ); + void ChangeAccel( int d, float step ); +}; + +/* +======================== +idLCP_Square::FactorClamped +======================== +*/ +bool idLCP_Square::FactorClamped() { + for ( int i = 0; i < numClamped; i++ ) { + memcpy( clamped[i], rowPtrs[i], numClamped * sizeof( float ) ); + } + return LU_Factor( clamped, diagonal, numClamped ); +} + +/* +======================== +idLCP_Square::SolveClamped +======================== +*/ +void idLCP_Square::SolveClamped( idVecX & x, const float * b ) { + // solve L + LowerTriangularSolve( clamped, x.ToFloatPtr(), b, numClamped, 0 ); + + // solve U + UpperTriangularSolve( clamped, diagonal.ToFloatPtr(), x.ToFloatPtr(), x.ToFloatPtr(), numClamped ); +} + +/* +======================== +idLCP_Square::Swap +======================== +*/ +void idLCP_Square::Swap( int i, int j ) { + + if ( i == j ) { + return; + } + + SwapValues( rowPtrs[i], rowPtrs[j] ); + m.SwapColumns( i, j ); + b.SwapElements( i, j ); + lo.SwapElements( i, j ); + hi.SwapElements( i, j ); + a.SwapElements( i, j ); + f.SwapElements( i, j ); + if ( boxIndex != NULL ) { + SwapValues( boxIndex[i], boxIndex[j] ); + } + SwapValues( side[i], side[j] ); + SwapValues( permuted[i], permuted[j] ); +} + +/* +======================== +idLCP_Square::AddClamped +======================== +*/ +void idLCP_Square::AddClamped( int r ) { + + assert( r >= numClamped ); + + // add a row at the bottom and a column at the right of the factored + // matrix for the clamped variables + + Swap( numClamped, r ); + + // add row to L + for ( int i = 0; i < numClamped; i++ ) { + float sum = rowPtrs[numClamped][i]; + for ( int j = 0; j < i; j++ ) { + sum -= clamped[numClamped][j] * clamped[j][i]; + } + clamped[numClamped][i] = sum * diagonal[i]; + } + + // add column to U + for ( int i = 0; i <= numClamped; i++ ) { + float sum = rowPtrs[i][numClamped]; + for ( int j = 0; j < i; j++ ) { + sum -= clamped[i][j] * clamped[j][numClamped]; + } + clamped[i][numClamped] = sum; + } + + diagonal[numClamped] = 1.0f / clamped[numClamped][numClamped]; + + numClamped++; +} + +/* +======================== +idLCP_Square::RemoveClamped +======================== +*/ +void idLCP_Square::RemoveClamped( int r ) { + + if ( !verify( r < numClamped ) ) { + // complete fail, most likely due to exceptional floating point values + return; + } + + numClamped--; + + // no need to swap and update the factored matrix when the last row and column are removed + if ( r == numClamped ) { + return; + } + + float * y0 = (float *) _alloca16( numClamped * sizeof( float ) ); + float * z0 = (float *) _alloca16( numClamped * sizeof( float ) ); + float * y1 = (float *) _alloca16( numClamped * sizeof( float ) ); + float * z1 = (float *) _alloca16( numClamped * sizeof( float ) ); + + // the row/column need to be subtracted from the factorization + for ( int i = 0; i < numClamped; i++ ) { + y0[i] = -rowPtrs[i][r]; + } + + memset( y1, 0, numClamped * sizeof( float ) ); + y1[r] = 1.0f; + + memset( z0, 0, numClamped * sizeof( float ) ); + z0[r] = 1.0f; + + for ( int i = 0; i < numClamped; i++ ) { + z1[i] = -rowPtrs[r][i]; + } + + // swap the to be removed row/column with the last row/column + Swap( r, numClamped ); + + // the swapped last row/column need to be added to the factorization + for ( int i = 0; i < numClamped; i++ ) { + y0[i] += rowPtrs[i][r]; + } + + for ( int i = 0; i < numClamped; i++ ) { + z1[i] += rowPtrs[r][i]; + } + z1[r] = 0.0f; + + // update the beginning of the to be updated row and column + for ( int i = 0; i < r; i++ ) { + float p0 = y0[i]; + float beta1 = z1[i] * diagonal[i]; + + clamped[i][r] += p0; + for ( int j = i+1; j < numClamped; j++ ) { + z1[j] -= beta1 * clamped[i][j]; + } + for ( int j = i+1; j < numClamped; j++ ) { + y0[j] -= p0 * clamped[j][i]; + } + clamped[r][i] += beta1; + } + + // update the lower right corner starting at r,r + for ( int i = r; i < numClamped; i++ ) { + float diag = clamped[i][i]; + + float p0 = y0[i]; + float p1 = z0[i]; + diag += p0 * p1; + + if ( fabs( diag ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Square::RemoveClamped: updating factorization failed\n" ); + diag = idMath::FLT_SMALLEST_NON_DENORMAL; + } + + float beta0 = p1 / diag; + + float q0 = y1[i]; + float q1 = z1[i]; + diag += q0 * q1; + + if ( fabs( diag ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Square::RemoveClamped: updating factorization failed\n" ); + diag = idMath::FLT_SMALLEST_NON_DENORMAL; + } + + float d = 1.0f / diag; + float beta1 = q1 * d; + + clamped[i][i] = diag; + diagonal[i] = d; + + for ( int j = i+1; j < numClamped; j++ ) { + + d = clamped[i][j]; + + d += p0 * z0[j]; + z0[j] -= beta0 * d; + + d += q0 * z1[j]; + z1[j] -= beta1 * d; + + clamped[i][j] = d; + } + + for ( int j = i+1; j < numClamped; j++ ) { + + d = clamped[j][i]; + + y0[j] -= p0 * d; + d += beta0 * y0[j]; + + y1[j] -= q0 * d; + d += beta1 * y1[j]; + + clamped[j][i] = d; + } + } + return; +} + +/* +======================== +idLCP_Square::CalcForceDelta + +Modifies this->delta_f. +======================== +*/ +void idLCP_Square::CalcForceDelta( int d, float dir ) { + + delta_f[d] = dir; + + if ( numClamped <= 0 ) { + return; + } + + // get column d of matrix + float * ptr = (float *) _alloca16( numClamped * sizeof( float ) ); + for ( int i = 0; i < numClamped; i++ ) { + ptr[i] = rowPtrs[i][d]; + } + + // solve force delta + SolveClamped( delta_f, ptr ); + + // flip force delta based on direction + if ( dir > 0.0f ) { + ptr = delta_f.ToFloatPtr(); + for ( int i = 0; i < numClamped; i++ ) { + ptr[i] = - ptr[i]; + } + } +} + +/* +======================== +idLCP_Square::CalcAccelDelta + +Modifies this->delta_a and uses this->delta_f. +======================== +*/ +ID_INLINE void idLCP_Square::CalcAccelDelta( int d ) { + // only the not clamped variables, including the current variable, can have a change in acceleration + for ( int j = numClamped; j <= d; j++ ) { + // only the clamped variables and the current variable have a force delta unequal zero + float dot = BigDotProduct( rowPtrs[j], delta_f.ToFloatPtr(), numClamped ); + delta_a[j] = dot + rowPtrs[j][d] * delta_f[d]; + } +} + +/* +======================== +idLCP_Square::ChangeForce + +Modifies this->f and uses this->delta_f. +======================== +*/ +ID_INLINE void idLCP_Square::ChangeForce( int d, float step ) { + // only the clamped variables and current variable have a force delta unequal zero + MultiplyAdd( f.ToFloatPtr(), step, delta_f.ToFloatPtr(), numClamped ); + f[d] += step * delta_f[d]; +} + +/* +======================== +idLCP_Square::ChangeAccel + +Modifies this->a and uses this->delta_a. +======================== +*/ +ID_INLINE void idLCP_Square::ChangeAccel( int d, float step ) { + // only the not clamped variables, including the current variable, can have an acceleration unequal zero + MultiplyAdd( a.ToFloatPtr() + numClamped, step, delta_a.ToFloatPtr() + numClamped, d - numClamped + 1 ); +} + +/* +======================== +idLCP_Square::Solve +======================== +*/ +bool idLCP_Square::Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ) { + + // true when the matrix rows are 16 byte padded + padded = ((o_m.GetNumRows()+3)&~3) == o_m.GetNumColumns(); + + assert( padded || o_m.GetNumRows() == o_m.GetNumColumns() ); + assert( o_x.GetSize() == o_m.GetNumRows() ); + assert( o_b.GetSize() == o_m.GetNumRows() ); + assert( o_lo.GetSize() == o_m.GetNumRows() ); + assert( o_hi.GetSize() == o_m.GetNumRows() ); + + // allocate memory for permuted input + f.SetData( o_m.GetNumRows(), VECX_ALLOCA( o_m.GetNumRows() ) ); + a.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + b.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + lo.SetData( o_lo.GetSize(), VECX_ALLOCA( o_lo.GetSize() ) ); + hi.SetData( o_hi.GetSize(), VECX_ALLOCA( o_hi.GetSize() ) ); + if ( o_boxIndex != NULL ) { + boxIndex = (int *)_alloca16( o_x.GetSize() * sizeof( int ) ); + memcpy( boxIndex, o_boxIndex, o_x.GetSize() * sizeof( int ) ); + } else { + boxIndex = NULL; + } + + // we override the const on o_m here but on exit the matrix is unchanged + m.SetData( o_m.GetNumRows(), o_m.GetNumColumns(), const_cast(o_m[0]) ); + f.Zero(); + a.Zero(); + b = o_b; + lo = o_lo; + hi = o_hi; + + // pointers to the rows of m + rowPtrs = (float **) _alloca16( m.GetNumRows() * sizeof( float * ) ); + for ( int i = 0; i < m.GetNumRows(); i++ ) { + rowPtrs[i] = m[i]; + } + + // tells if a variable is at the low boundary, high boundary or inbetween + side = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + + // index to keep track of the permutation + permuted = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + for ( int i = 0; i < m.GetNumRows(); i++ ) { + permuted[i] = i; + } + + // permute input so all unbounded variables come first + numUnbounded = 0; + for ( int i = 0; i < m.GetNumRows(); i++ ) { + if ( lo[i] == -idMath::INFINITY && hi[i] == idMath::INFINITY ) { + if ( numUnbounded != i ) { + Swap( numUnbounded, i ); + } + numUnbounded++; + } + } + + // permute input so all variables using the boxIndex come last + int boxStartIndex = m.GetNumRows(); + if ( boxIndex ) { + for ( int i = m.GetNumRows() - 1; i >= numUnbounded; i-- ) { + if ( boxIndex[i] >= 0 ) { + boxStartIndex--; + if ( boxStartIndex != i ) { + Swap( boxStartIndex, i ); + } + } + } + } + + // sub matrix for factorization + clamped.SetData( m.GetNumRows(), m.GetNumColumns(), MATX_ALLOCA( m.GetNumRows() * m.GetNumColumns() ) ); + diagonal.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // all unbounded variables are clamped + numClamped = numUnbounded; + + // if there are unbounded variables + if ( numUnbounded ) { + + // factor and solve for unbounded variables + if ( !FactorClamped() ) { + idLib::Printf( "idLCP_Square::Solve: unbounded factorization failed\n" ); + return false; + } + SolveClamped( f, b.ToFloatPtr() ); + + // if there are no bounded variables we are done + if ( numUnbounded == m.GetNumRows() ) { + o_x = f; // the vector is not permuted + return true; + } + } + + int numIgnored = 0; + + // allocate for delta force and delta acceleration + delta_f.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + delta_a.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // solve for bounded variables + idStr failed; + for ( int i = numUnbounded; i < m.GetNumRows(); i++ ) { + + // once we hit the box start index we can initialize the low and high boundaries of the variables using the box index + if ( i == boxStartIndex ) { + for ( int j = 0; j < boxStartIndex; j++ ) { + o_x[permuted[j]] = f[j]; + } + for ( int j = boxStartIndex; j < m.GetNumRows(); j++ ) { + float s = o_x[boxIndex[j]]; + if ( lo[j] != -idMath::INFINITY ) { + lo[j] = - idMath::Fabs( lo[j] * s ); + } + if ( hi[j] != idMath::INFINITY ) { + hi[j] = idMath::Fabs( hi[j] * s ); + } + } + } + + // calculate acceleration for current variable + float dot = BigDotProduct( rowPtrs[i], f.ToFloatPtr(), i ); + a[i] = dot - b[i]; + + // if already at the low boundary + if ( lo[i] >= -LCP_BOUND_EPSILON && a[i] >= -LCP_ACCEL_EPSILON ) { + side[i] = -1; + continue; + } + + // if already at the high boundary + if ( hi[i] <= LCP_BOUND_EPSILON && a[i] <= LCP_ACCEL_EPSILON ) { + side[i] = 1; + continue; + } + + // if inside the clamped region + if ( idMath::Fabs( a[i] ) <= LCP_ACCEL_EPSILON ) { + side[i] = 0; + AddClamped( i ); + continue; + } + + // drive the current variable into a valid region + int n = 0; + for ( ; n < maxIterations; n++ ) { + + // direction to move + float dir = ( a[i] <= 0.0f ) ? 1.0f : -1.0f; + + // calculate force delta + CalcForceDelta( i, dir ); + + // calculate acceleration delta: delta_a = m * delta_f; + CalcAccelDelta( i ); + + float maxStep; + int limit; + int limitSide; + + // maximum step we can take + GetMaxStep( f.ToFloatPtr(), a.ToFloatPtr(), delta_f.ToFloatPtr(), delta_a.ToFloatPtr(), + lo.ToFloatPtr(), hi.ToFloatPtr(), side, numUnbounded, numClamped, + i, dir, maxStep, limit, limitSide ); + + if ( maxStep <= 0.0f ) { +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + // ignore the current variable completely + lo[i] = hi[i] = 0.0f; + f[i] = 0.0f; + side[i] = -1; + numIgnored++; +#else + failed.Format( "invalid step size %.4f", maxStep ); + for ( int j = i; j < m.GetNumRows(); j++ ) { + f[j] = 0.0f; + } + numIgnored = m.GetNumRows() - i; +#endif + break; + } + + // change force + ChangeForce( i, maxStep ); + + // change acceleration + ChangeAccel( i, maxStep ); + + // clamp/unclamp the variable that limited this step + side[limit] = limitSide; + if ( limitSide == 0 ) { + a[limit] = 0.0f; + AddClamped( limit ); + } else if ( limitSide == -1 ) { + f[limit] = lo[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + } else if ( limitSide == 1 ) { + f[limit] = hi[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + } + + // if the current variable limited the step we can continue with the next variable + if ( limit == i ) { + break; + } + } + + if ( n >= maxIterations ) { + failed.Format( "max iterations %d", maxIterations ); + break; + } + + if ( failed.Length() ) { + break; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + if ( numIgnored ) { + if ( lcp_showFailures.GetBool() ) { + idLib::Printf( "idLCP_Square::Solve: %d of %d bounded variables ignored\n", numIgnored, m.GetNumRows() - numUnbounded ); + } + } +#endif + + // if failed clear remaining forces + if ( failed.Length() ) { + if ( lcp_showFailures.GetBool() ) { + idLib::Printf( "idLCP_Square::Solve: %s (%d of %d bounded variables ignored)\n", failed.c_str(), numIgnored, m.GetNumRows() - numUnbounded ); + } + } + +#if defined(_DEBUG) && 0 + if ( failed.Length() ) { + // test whether or not the solution satisfies the complementarity conditions + for ( int i = 0; i < m.GetNumRows(); i++ ) { + a[i] = -b[i]; + for ( int j = 0; j < m.GetNumRows(); j++ ) { + a[i] += rowPtrs[i][j] * f[j]; + } + + if ( f[i] == lo[i] ) { + if ( lo[i] != hi[i] && a[i] < -LCP_ACCEL_EPSILON ) { + int bah1 = 1; + } + } else if ( f[i] == hi[i] ) { + if ( lo[i] != hi[i] && a[i] > LCP_ACCEL_EPSILON ) { + int bah2 = 1; + } + } else if ( f[i] < lo[i] || f[i] > hi[i] || idMath::Fabs( a[i] ) > 1.0f ) { + int bah3 = 1; + } + } + } +#endif + + // unpermute result + for ( int i = 0; i < f.GetSize(); i++ ) { + o_x[permuted[i]] = f[i]; + } + + return true; +} + +/* +================================================================================================ + + idLCP_Symmetric + +================================================================================================ +*/ + +/* +================================================ +idLCP_Symmetric +================================================ +*/ +class idLCP_Symmetric : public idLCP { +public: + virtual bool Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ); + +private: + idMatX m; // original matrix + idVecX b; // right hand side + idVecX lo, hi; // low and high bounds + idVecX f, a; // force and acceleration + idVecX delta_f, delta_a; // delta force and delta acceleration + idMatX clamped; // LDLt factored sub matrix for clamped variables + idVecX diagonal; // reciprocal of diagonal of LDLt factored sub matrix for clamped variables + idVecX solveCache1; // intermediate result cached in SolveClamped + idVecX solveCache2; // " + int numUnbounded; // number of unbounded variables + int numClamped; // number of clamped variables + int clampedChangeStart; // lowest row/column changed in the clamped matrix during an iteration + float ** rowPtrs; // pointers to the rows of m + int * boxIndex; // box index + int * side; // tells if a variable is at the low boundary = -1, high boundary = 1 or inbetween = 0 + int * permuted; // index to keep track of the permutation + bool padded; // set to true if the rows of the initial matrix are 16 byte padded + + bool FactorClamped(); + void SolveClamped( idVecX &x, const float *b ); + void Swap( int i, int j ); + void AddClamped( int r, bool useSolveCache ); + void RemoveClamped( int r ); + void CalcForceDelta( int d, float dir ); + void CalcAccelDelta( int d ); + void ChangeForce( int d, float step ); + void ChangeAccel( int d, float step ); +}; + +/* +======================== +idLCP_Symmetric::FactorClamped +======================== +*/ +bool idLCP_Symmetric::FactorClamped() { + + clampedChangeStart = 0; + + for ( int i = 0; i < numClamped; i++ ) { + memcpy( clamped[i], rowPtrs[i], numClamped * sizeof( float ) ); + } + return LDLT_Factor( clamped, diagonal, numClamped ); +} + +/* +======================== +idLCP_Symmetric::SolveClamped +======================== +*/ +void idLCP_Symmetric::SolveClamped( idVecX &x, const float *b ) { + + // solve L + LowerTriangularSolve( clamped, solveCache1.ToFloatPtr(), b, numClamped, clampedChangeStart ); + + // scale with D + Multiply( solveCache2.ToFloatPtr(), solveCache1.ToFloatPtr(), diagonal.ToFloatPtr(), numClamped ); + + // solve Lt + LowerTriangularSolveTranspose( clamped, x.ToFloatPtr(), solveCache2.ToFloatPtr(), numClamped ); + + clampedChangeStart = numClamped; +} + +/* +======================== +idLCP_Symmetric::Swap +======================== +*/ +void idLCP_Symmetric::Swap( int i, int j ) { + + if ( i == j ) { + return; + } + + SwapValues( rowPtrs[i], rowPtrs[j] ); + m.SwapColumns( i, j ); + b.SwapElements( i, j ); + lo.SwapElements( i, j ); + hi.SwapElements( i, j ); + a.SwapElements( i, j ); + f.SwapElements( i, j ); + if ( boxIndex != NULL ) { + SwapValues( boxIndex[i], boxIndex[j] ); + } + SwapValues( side[i], side[j] ); + SwapValues( permuted[i], permuted[j] ); +} + +/* +======================== +idLCP_Symmetric::AddClamped +======================== +*/ +void idLCP_Symmetric::AddClamped( int r, bool useSolveCache ) { + + assert( r >= numClamped ); + + if ( numClamped < clampedChangeStart ) { + clampedChangeStart = numClamped; + } + + // add a row at the bottom and a column at the right of the factored + // matrix for the clamped variables + + Swap( numClamped, r ); + + // solve for v in L * v = rowPtr[numClamped] + float dot; + if ( useSolveCache ) { + + // the lower triangular solve was cached in SolveClamped called by CalcForceDelta + memcpy( clamped[numClamped], solveCache2.ToFloatPtr(), numClamped * sizeof( float ) ); + // calculate row dot product + dot = BigDotProduct( solveCache2.ToFloatPtr(), solveCache1.ToFloatPtr(), numClamped ); + + } else { + + float *v = (float *) _alloca16( numClamped * sizeof( float ) ); + + LowerTriangularSolve( clamped, v, rowPtrs[numClamped], numClamped, 0 ); + // add bottom row to L + Multiply( clamped[numClamped], v, diagonal.ToFloatPtr(), numClamped ); + // calculate row dot product + dot = BigDotProduct( clamped[numClamped], v, numClamped ); + } + + // update diagonal[numClamped] + float d = rowPtrs[numClamped][numClamped] - dot; + + if ( fabs( d ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Symmetric::AddClamped: updating factorization failed\n" ); + d = idMath::FLT_SMALLEST_NON_DENORMAL; + } + + clamped[numClamped][numClamped] = d; + diagonal[numClamped] = 1.0f / d; + + numClamped++; +} + +/* +======================== +idLCP_Symmetric::RemoveClamped +======================== +*/ +void idLCP_Symmetric::RemoveClamped( int r ) { + + if ( !verify( r < numClamped ) ) { + // complete fail, most likely due to exceptional floating point values + return; + } + + if ( r < clampedChangeStart ) { + clampedChangeStart = r; + } + + numClamped--; + + // no need to swap and update the factored matrix when the last row and column are removed + if ( r == numClamped ) { + return; + } + + // swap the to be removed row/column with the last row/column + Swap( r, numClamped ); + + // update the factored matrix + float * addSub = (float *) _alloca16( numClamped * sizeof( float ) ); + + if ( r == 0 ) { + + if ( numClamped == 1 ) { + float diag = rowPtrs[0][0]; + if ( fabs( diag ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + diag = idMath::FLT_SMALLEST_NON_DENORMAL; + } + clamped[0][0] = diag; + diagonal[0] = 1.0f / diag; + return; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + float * original = rowPtrs[numClamped]; + float * ptr = rowPtrs[r]; + addSub[0] = ptr[0] - original[numClamped]; + for ( int i = 1; i < numClamped; i++ ) { + addSub[i] = ptr[i] - original[i]; + } + + } else { + + float * v = (float *) _alloca16( numClamped * sizeof( float ) ); + + // solve for v in L * v = rowPtr[r] + LowerTriangularSolve( clamped, v, rowPtrs[r], r, 0 ); + + // update removed row + Multiply( clamped[r], v, diagonal.ToFloatPtr(), r ); + + // if the last row/column of the matrix is updated + if ( r == numClamped - 1 ) { + // only calculate new diagonal + float dot = BigDotProduct( clamped[r], v, r ); + float diag = rowPtrs[r][r] - dot; + if ( fabs( diag ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + diag = idMath::FLT_SMALLEST_NON_DENORMAL; + } + clamped[r][r] = diag; + diagonal[r] = 1.0f / diag; + return; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( int i = 0; i < r; i++ ) { + v[i] = clamped[r][i] * clamped[i][i]; + } + for ( int i = r; i < numClamped; i++ ) { + float sum; + if ( i == r ) { + sum = clamped[r][r]; + } else { + sum = clamped[r][r] * clamped[i][r]; + } + float * ptr = clamped[i]; + for ( int j = 0; j < r; j++ ) { + sum += ptr[j] * v[j]; + } + addSub[i] = rowPtrs[r][i] - sum; + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + + float * v1 = (float *) _alloca16( numClamped * sizeof( float ) ); + float * v2 = (float *) _alloca16( numClamped * sizeof( float ) ); + + float diag = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * diag; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * diag; + for ( int i = r+1; i < numClamped; i++ ) { + v1[i] = v2[i] = addSub[i] * diag; + } + + float alpha1 = 1.0f; + float alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + int n = clamped.GetNumColumns(); + for ( int i = r; i < numClamped; i++ ) { + + diag = clamped[i][i]; + float p1 = v1[i]; + float newDiag = diag + alpha1 * p1 * p1; + + if ( fabs( newDiag ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + newDiag = idMath::FLT_SMALLEST_NON_DENORMAL; + } + + alpha1 /= newDiag; + float beta1 = p1 * alpha1; + alpha1 *= diag; + + diag = newDiag; + float p2 = v2[i]; + newDiag = diag + alpha2 * p2 * p2; + + if ( fabs( newDiag ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + idLib::Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + newDiag = idMath::FLT_SMALLEST_NON_DENORMAL; + } + + clamped[i][i] = newDiag; + float invNewDiag = 1.0f / newDiag; + diagonal[i] = invNewDiag; + + alpha2 *= invNewDiag; + float beta2 = p2 * alpha2; + alpha2 *= diag; + + // update column below diagonal (i,i) + float * ptr = clamped.ToFloatPtr() + i; + + int j; + for ( j = i+1; j < numClamped - 1; j += 2 ) { + + float sum0 = ptr[(j+0)*n]; + float sum1 = ptr[(j+1)*n]; + + v1[j+0] -= p1 * sum0; + v1[j+1] -= p1 * sum1; + + sum0 += beta1 * v1[j+0]; + sum1 += beta1 * v1[j+1]; + + v2[j+0] -= p2 * sum0; + v2[j+1] -= p2 * sum1; + + sum0 += beta2 * v2[j+0]; + sum1 += beta2 * v2[j+1]; + + ptr[(j+0)*n] = sum0; + ptr[(j+1)*n] = sum1; + } + + for ( ; j < numClamped; j++ ) { + + float sum = ptr[j*n]; + + v1[j] -= p1 * sum; + sum += beta1 * v1[j]; + + v2[j] -= p2 * sum; + sum += beta2 * v2[j]; + + ptr[j*n] = sum; + } + } +} + +/* +======================== +idLCP_Symmetric::CalcForceDelta + +Modifies this->delta_f. +======================== +*/ +ID_INLINE void idLCP_Symmetric::CalcForceDelta( int d, float dir ) { + + delta_f[d] = dir; + + if ( numClamped == 0 ) { + return; + } + + // solve force delta + SolveClamped( delta_f, rowPtrs[d] ); + + // flip force delta based on direction + if ( dir > 0.0f ) { + float * ptr = delta_f.ToFloatPtr(); + for ( int i = 0; i < numClamped; i++ ) { + ptr[i] = - ptr[i]; + } + } +} + +/* +======================== +idLCP_Symmetric::CalcAccelDelta + +Modifies this->delta_a and uses this->delta_f. +======================== +*/ +ID_INLINE void idLCP_Symmetric::CalcAccelDelta( int d ) { + // only the not clamped variables, including the current variable, can have a change in acceleration + for ( int j = numClamped; j <= d; j++ ) { + // only the clamped variables and the current variable have a force delta unequal zero + float dot = BigDotProduct( rowPtrs[j], delta_f.ToFloatPtr(), numClamped ); + delta_a[j] = dot + rowPtrs[j][d] * delta_f[d]; + } +} + +/* +======================== +idLCP_Symmetric::ChangeForce + +Modifies this->f and uses this->delta_f. +======================== +*/ +ID_INLINE void idLCP_Symmetric::ChangeForce( int d, float step ) { + // only the clamped variables and current variable have a force delta unequal zero + MultiplyAdd( f.ToFloatPtr(), step, delta_f.ToFloatPtr(), numClamped ); + f[d] += step * delta_f[d]; +} + +/* +======================== +idLCP_Symmetric::ChangeAccel + +Modifies this->a and uses this->delta_a. +======================== +*/ +ID_INLINE void idLCP_Symmetric::ChangeAccel( int d, float step ) { + // only the not clamped variables, including the current variable, can have an acceleration unequal zero + MultiplyAdd( a.ToFloatPtr() + numClamped, step, delta_a.ToFloatPtr() + numClamped, d - numClamped + 1 ); +} + +/* +======================== +idLCP_Symmetric::Solve +======================== +*/ +bool idLCP_Symmetric::Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ) { + + // true when the matrix rows are 16 byte padded + padded = ((o_m.GetNumRows()+3)&~3) == o_m.GetNumColumns(); + + assert( padded || o_m.GetNumRows() == o_m.GetNumColumns() ); + assert( o_x.GetSize() == o_m.GetNumRows() ); + assert( o_b.GetSize() == o_m.GetNumRows() ); + assert( o_lo.GetSize() == o_m.GetNumRows() ); + assert( o_hi.GetSize() == o_m.GetNumRows() ); + + // allocate memory for permuted input + f.SetData( o_m.GetNumRows(), VECX_ALLOCA( o_m.GetNumRows() ) ); + a.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + b.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + lo.SetData( o_lo.GetSize(), VECX_ALLOCA( o_lo.GetSize() ) ); + hi.SetData( o_hi.GetSize(), VECX_ALLOCA( o_hi.GetSize() ) ); + if ( o_boxIndex != NULL ) { + boxIndex = (int *)_alloca16( o_x.GetSize() * sizeof( int ) ); + memcpy( boxIndex, o_boxIndex, o_x.GetSize() * sizeof( int ) ); + } else { + boxIndex = NULL; + } + + // we override the const on o_m here but on exit the matrix is unchanged + m.SetData( o_m.GetNumRows(), o_m.GetNumColumns(), const_cast< float * >( o_m[0] ) ); + f.Zero(); + a.Zero(); + b = o_b; + lo = o_lo; + hi = o_hi; + + // pointers to the rows of m + rowPtrs = (float **) _alloca16( m.GetNumRows() * sizeof( float * ) ); + for ( int i = 0; i < m.GetNumRows(); i++ ) { + rowPtrs[i] = m[i]; + } + + // tells if a variable is at the low boundary, high boundary or inbetween + side = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + + // index to keep track of the permutation + permuted = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + for ( int i = 0; i < m.GetNumRows(); i++ ) { + permuted[i] = i; + } + + // permute input so all unbounded variables come first + numUnbounded = 0; + for ( int i = 0; i < m.GetNumRows(); i++ ) { + if ( lo[i] == -idMath::INFINITY && hi[i] == idMath::INFINITY ) { + if ( numUnbounded != i ) { + Swap( numUnbounded, i ); + } + numUnbounded++; + } + } + + // permute input so all variables using the boxIndex come last + int boxStartIndex = m.GetNumRows(); + if ( boxIndex != NULL ) { + for ( int i = m.GetNumRows() - 1; i >= numUnbounded; i-- ) { + if ( boxIndex[i] >= 0 ) { + boxStartIndex--; + if ( boxStartIndex != i ) { + Swap( boxStartIndex, i ); + } + } + } + } + + // sub matrix for factorization + clamped.SetDataCacheLines( m.GetNumRows(), m.GetNumColumns(), MATX_ALLOCA_CACHE_LINES( m.GetNumRows() * m.GetNumColumns() ), true ); + diagonal.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + solveCache1.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + solveCache2.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // all unbounded variables are clamped + numClamped = numUnbounded; + + // if there are unbounded variables + if ( numUnbounded ) { + + // factor and solve for unbounded variables + if ( !FactorClamped() ) { + idLib::Printf( "idLCP_Symmetric::Solve: unbounded factorization failed\n" ); + return false; + } + SolveClamped( f, b.ToFloatPtr() ); + + // if there are no bounded variables we are done + if ( numUnbounded == m.GetNumRows() ) { + o_x = f; // the vector is not permuted + return true; + } + } + + int numIgnored = 0; + + // allocate for delta force and delta acceleration + delta_f.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + delta_a.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // solve for bounded variables + idStr failed; + for ( int i = numUnbounded; i < m.GetNumRows(); i++ ) { + + clampedChangeStart = 0; + + // once we hit the box start index we can initialize the low and high boundaries of the variables using the box index + if ( i == boxStartIndex ) { + for ( int j = 0; j < boxStartIndex; j++ ) { + o_x[permuted[j]] = f[j]; + } + for ( int j = boxStartIndex; j < m.GetNumRows(); j++ ) { + float s = o_x[boxIndex[j]]; + if ( lo[j] != -idMath::INFINITY ) { + lo[j] = - idMath::Fabs( lo[j] * s ); + } + if ( hi[j] != idMath::INFINITY ) { + hi[j] = idMath::Fabs( hi[j] * s ); + } + } + } + + // calculate acceleration for current variable + float dot = BigDotProduct( rowPtrs[i], f.ToFloatPtr(), i ); + a[i] = dot - b[i]; + + // if already at the low boundary + if ( lo[i] >= -LCP_BOUND_EPSILON && a[i] >= -LCP_ACCEL_EPSILON ) { + side[i] = -1; + continue; + } + + // if already at the high boundary + if ( hi[i] <= LCP_BOUND_EPSILON && a[i] <= LCP_ACCEL_EPSILON ) { + side[i] = 1; + continue; + } + + // if inside the clamped region + if ( idMath::Fabs( a[i] ) <= LCP_ACCEL_EPSILON ) { + side[i] = 0; + AddClamped( i, false ); + continue; + } + + // drive the current variable into a valid region + int n = 0; + for ( ; n < maxIterations; n++ ) { + + // direction to move + float dir = ( a[i] <= 0.0f ) ? 1.0f : -1.0f; + + // calculate force delta + CalcForceDelta( i, dir ); + + // calculate acceleration delta: delta_a = m * delta_f; + CalcAccelDelta( i ); + + float maxStep; + int limit; + int limitSide; + + // maximum step we can take + GetMaxStep( f.ToFloatPtr(), a.ToFloatPtr(), delta_f.ToFloatPtr(), delta_a.ToFloatPtr(), + lo.ToFloatPtr(), hi.ToFloatPtr(), side, numUnbounded, numClamped, + i, dir, maxStep, limit, limitSide ); + + if ( maxStep <= 0.0f ) { +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + // ignore the current variable completely + lo[i] = hi[i] = 0.0f; + f[i] = 0.0f; + side[i] = -1; + numIgnored++; +#else + failed.Format( "invalid step size %.4f", maxStep ); + for ( int j = i; j < m.GetNumRows(); j++ ) { + f[j] = 0.0f; + } + numIgnored = m.GetNumRows() - i; +#endif + break; + } + + // change force + ChangeForce( i, maxStep ); + + // change acceleration + ChangeAccel( i, maxStep ); + + // clamp/unclamp the variable that limited this step + side[limit] = limitSide; + if ( limitSide == 0 ) { + a[limit] = 0.0f; + AddClamped( limit, ( limit == i ) ); + } else if ( limitSide == -1 ) { + f[limit] = lo[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + } else if ( limitSide == 1 ) { + f[limit] = hi[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + } + + // if the current variable limited the step we can continue with the next variable + if ( limit == i ) { + break; + } + } + + if ( n >= maxIterations ) { + failed.Format( "max iterations %d", maxIterations ); + break; + } + + if ( failed.Length() ) { + break; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + if ( numIgnored ) { + if ( lcp_showFailures.GetBool() ) { + idLib::Printf( "idLCP_Symmetric::Solve: %d of %d bounded variables ignored\n", numIgnored, m.GetNumRows() - numUnbounded ); + } + } +#endif + + // if failed clear remaining forces + if ( failed.Length() ) { + if ( lcp_showFailures.GetBool() ) { + idLib::Printf( "idLCP_Symmetric::Solve: %s (%d of %d bounded variables ignored)\n", failed.c_str(), numIgnored, m.GetNumRows() - numUnbounded ); + } + } + +#if defined(_DEBUG) && 0 + if ( failed.Length() ) { + // test whether or not the solution satisfies the complementarity conditions + for ( int i = 0; i < m.GetNumRows(); i++ ) { + a[i] = -b[i]; + for ( j = 0; j < m.GetNumRows(); j++ ) { + a[i] += rowPtrs[i][j] * f[j]; + } + + if ( f[i] == lo[i] ) { + if ( lo[i] != hi[i] && a[i] < -LCP_ACCEL_EPSILON ) { + int bah1 = 1; + } + } else if ( f[i] == hi[i] ) { + if ( lo[i] != hi[i] && a[i] > LCP_ACCEL_EPSILON ) { + int bah2 = 1; + } + } else if ( f[i] < lo[i] || f[i] > hi[i] || idMath::Fabs( a[i] ) > 1.0f ) { + int bah3 = 1; + } + } + } +#endif + + // unpermute result + for ( int i = 0; i < f.GetSize(); i++ ) { + o_x[permuted[i]] = f[i]; + } + + return true; +} + +/* +================================================================================================ + + idLCP + +================================================================================================ +*/ + +/* +======================== +idLCP::AllocSquare +======================== +*/ +idLCP *idLCP::AllocSquare() { + idLCP *lcp = new idLCP_Square; + lcp->SetMaxIterations( 32 ); + return lcp; +} + +/* +======================== +idLCP::AllocSymmetric +======================== +*/ +idLCP *idLCP::AllocSymmetric() { + idLCP *lcp = new idLCP_Symmetric; + lcp->SetMaxIterations( 32 ); + return lcp; +} + +/* +======================== +idLCP::~idLCP +======================== +*/ +idLCP::~idLCP() { +} + +/* +======================== +idLCP::SetMaxIterations +======================== +*/ +void idLCP::SetMaxIterations( int max ) { + maxIterations = max; +} + +/* +======================== +idLCP::GetMaxIterations +======================== +*/ +int idLCP::GetMaxIterations() { + return maxIterations; +} + +/* +======================== +idLCP::Test_f +======================== +*/ +void idLCP::Test_f( const idCmdArgs &args ) { +#ifdef ENABLE_TEST_CODE + DotProduct_Test(); + LowerTriangularSolve_Test(); + LowerTriangularSolveTranspose_Test(); + LDLT_Factor_Test(); +#endif +} diff --git a/neo/idlib/math/Lcp.h b/neo/idlib/math/Lcp.h new file mode 100644 index 00000000..659d146c --- /dev/null +++ b/neo/idlib/math/Lcp.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __MATH_LCP_H__ +#define __MATH_LCP_H__ + +/* +================================================ +The *LCP* class, idLCP, is a Box-Constrained Mixed Linear Complementarity Problem solver. + +'A' is a matrix of dimension n*n and 'x', 'b', 'lo', 'hi' are vectors of dimension n. + +Solve: Ax = b + t, where t is a vector of dimension n, with complementarity condition: + + (x[i] - lo[i]) * (x[i] - hi[i]) * t[i] = 0 + +such that for each 0 <= i < n one of the following holds: + + lo[i] < x[i] < hi[i], t[i] == 0 + x[i] == lo[i], t[i] >= 0 + x[i] == hi[i], t[i] <= 0 + +Partly-bounded or unbounded variables can have lo[i] and/or hi[i] set to negative/positive +idMath::INFITITY, respectively. + +If boxIndex != NULL and boxIndex[i] != -1, then + + lo[i] = - fabs( lo[i] * x[boxIndex[i]] ) + hi[i] = fabs( hi[i] * x[boxIndex[i]] ) + boxIndex[boxIndex[i]] must be -1 + +Before calculating any of the bounded x[i] with boxIndex[i] != -1, the solver calculates all +unbounded x[i] and all x[i] with boxIndex[i] == -1. +================================================ +*/ +class idLCP { +public: + static idLCP * AllocSquare(); // 'A' must be a square matrix + static idLCP * AllocSymmetric(); // 'A' must be a symmetric matrix + + virtual ~idLCP(); + + virtual bool Solve( const idMatX &A, idVecX &x, const idVecX &b, const idVecX &lo, + const idVecX &hi, const int *boxIndex = NULL ) = 0; + + virtual void SetMaxIterations( int max ); + virtual int GetMaxIterations(); + + static void Test_f( const idCmdArgs &args ); + +protected: + int maxIterations; +}; + +#endif // !__MATH_LCP_H__ diff --git a/neo/idlib/math/MatX.cpp b/neo/idlib/math/MatX.cpp new file mode 100644 index 00000000..78cbff33 --- /dev/null +++ b/neo/idlib/math/MatX.cpp @@ -0,0 +1,5350 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +//=============================================================== +// +// idMatX +// +//=============================================================== + +float idMatX::temp[MATX_MAX_TEMP+4]; +float * idMatX::tempPtr = (float *) ( ( (int) idMatX::temp + 15 ) & ~15 ); +int idMatX::tempIndex = 0; + + +/* +============ +idMatX::ChangeSize +============ +*/ +void idMatX::ChangeSize( int rows, int columns, bool makeZero ) { + int alloc = ( rows * columns + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + float *oldMat = mat; + mat = (float *) Mem_Alloc16( alloc * sizeof( float ), TAG_MATH ); + if ( makeZero ) { + memset( mat, 0, alloc * sizeof( float ) ); + } + alloced = alloc; + if ( oldMat ) { + int minRow = Min( numRows, rows ); + int minColumn = Min( numColumns, columns ); + for ( int i = 0; i < minRow; i++ ) { + for ( int j = 0; j < minColumn; j++ ) { + mat[ i * columns + j ] = oldMat[ i * numColumns + j ]; + } + } + Mem_Free16( oldMat ); + } + } else { + if ( columns < numColumns ) { + int minRow = Min( numRows, rows ); + for ( int i = 0; i < minRow; i++ ) { + for ( int j = 0; j < columns; j++ ) { + mat[ i * columns + j ] = mat[ i * numColumns + j ]; + } + } + } else if ( columns > numColumns ) { + for ( int i = Min( numRows, rows ) - 1; i >= 0; i-- ) { + if ( makeZero ) { + for ( int j = columns - 1; j >= numColumns; j-- ) { + mat[ i * columns + j ] = 0.0f; + } + } + for ( int j = numColumns - 1; j >= 0; j-- ) { + mat[ i * columns + j ] = mat[ i * numColumns + j ]; + } + } + } + if ( makeZero && rows > numRows ) { + memset( mat + numRows * columns, 0, ( rows - numRows ) * columns * sizeof( float ) ); + } + } + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +/* +============ +idMatX::RemoveRow +============ +*/ +idMatX &idMatX::RemoveRow( int r ) { + int i; + + assert( r < numRows ); + + numRows--; + + for ( i = r; i < numRows; i++ ) { + memcpy( &mat[i * numColumns], &mat[( i + 1 ) * numColumns], numColumns * sizeof( float ) ); + } + + return *this; +} + +/* +============ +idMatX::RemoveColumn +============ +*/ +idMatX &idMatX::RemoveColumn( int r ) { + int i; + + assert( r < numColumns ); + + numColumns--; + + for ( i = 0; i < numRows - 1; i++ ) { + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + + return *this; +} + +/* +============ +idMatX::RemoveRowColumn +============ +*/ +idMatX &idMatX::RemoveRowColumn( int r ) { + int i; + + assert( r < numRows && r < numColumns ); + + numRows--; + numColumns--; + + if ( r > 0 ) { + for ( i = 0; i < r - 1; i++ ) { + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + } + + memcpy( &mat[r * numColumns], &mat[( r + 1 ) * ( numColumns + 1 )], r * sizeof( float ) ); + + for ( i = r; i < numRows - 1; i++ ) { + memcpy( &mat[i * numColumns + r], &mat[( i + 1 ) * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memcpy( &mat[i * numColumns + r], &mat[( i + 1 ) * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + + return *this; +} + +/* +======================== +idMatX::CopyLowerToUpperTriangle +======================== +*/ +void idMatX::CopyLowerToUpperTriangle() { + assert( ( GetNumColumns() & 3 ) == 0 ); + assert( GetNumColumns() >= GetNumRows() ); + + + const int n = GetNumColumns(); + const int m = GetNumRows(); + + const int n0 = 0; + const int n1 = n; + const int n2 = ( n << 1 ); + const int n3 = ( n << 1 ) + n; + const int n4 = ( n << 2 ); + + const int b1 = ( ( m - 0 ) >> 1 ) & 1; // ( m & 3 ) > 1 + const int b2 = ( ( m - 1 ) >> 1 ) & 1; // ( m & 3 ) > 2 (provided ( m & 3 ) > 0) + + const int n1_masked = ( n & -b1 ); + const int n2_masked = ( n & -b1 ) + ( n & -b2 ); + + const __m128 mask0 = __m128c( _mm_set_epi32( 0, 0, 0, -1 ) ); + const __m128 mask1 = __m128c( _mm_set_epi32( 0, 0, -1, -1 ) ); + const __m128 mask2 = __m128c( _mm_set_epi32( 0, -1, -1, -1 ) ); + const __m128 mask3 = __m128c( _mm_set_epi32( -1, -1, -1, -1 ) ); + + const __m128 bottomMask[2] = { __m128c( _mm_set1_epi32( 0 ) ), __m128c( _mm_set1_epi32( -1 ) ) }; + + float * __restrict basePtr = ToFloatPtr(); + + for ( int i = 0; i < m - 3; i += 4 ) { + + // copy top left diagonal 4x4 block elements + __m128 r0 = _mm_and_ps( _mm_load_ps( basePtr + n0 ), mask0 ); + __m128 r1 = _mm_and_ps( _mm_load_ps( basePtr + n1 ), mask1 ); + __m128 r2 = _mm_and_ps( _mm_load_ps( basePtr + n2 ), mask2 ); + __m128 r3 = _mm_and_ps( _mm_load_ps( basePtr + n3 ), mask3 ); + + __m128 t0 = _mm_unpacklo_ps( r0, r2 ); // x0, z0, x1, z1 + __m128 t1 = _mm_unpackhi_ps( r0, r2 ); // x2, z2, x3, z3 + __m128 t2 = _mm_unpacklo_ps( r1, r3 ); // y0, w0, y1, w1 + __m128 t3 = _mm_unpackhi_ps( r1, r3 ); // y2, w2, y3, w3 + + __m128 s0 = _mm_unpacklo_ps( t0, t2 ); // x0, y0, z0, w0 + __m128 s1 = _mm_unpackhi_ps( t0, t2 ); // x1, y1, z1, w1 + __m128 s2 = _mm_unpacklo_ps( t1, t3 ); // x2, y2, z2, w2 + __m128 s3 = _mm_unpackhi_ps( t1, t3 ); // x3, y3, z3, w3 + + r0 = _mm_or_ps( r0, s0 ); + r1 = _mm_or_ps( r1, s1 ); + r2 = _mm_or_ps( r2, s2 ); + r3 = _mm_or_ps( r3, s3 ); + + _mm_store_ps( basePtr + n0, r0 ); + _mm_store_ps( basePtr + n1, r1 ); + _mm_store_ps( basePtr + n2, r2 ); + _mm_store_ps( basePtr + n3, r3 ); + + // copy one column of 4x4 blocks to one row of 4x4 blocks + const float * __restrict srcPtr = basePtr; + float * __restrict dstPtr = basePtr; + + for ( int j = i + 4; j < m - 3; j += 4 ) { + srcPtr += n4; + dstPtr += 4; + + __m128 r0 = _mm_load_ps( srcPtr + n0 ); + __m128 r1 = _mm_load_ps( srcPtr + n1 ); + __m128 r2 = _mm_load_ps( srcPtr + n2 ); + __m128 r3 = _mm_load_ps( srcPtr + n3 ); + + __m128 t0 = _mm_unpacklo_ps( r0, r2 ); // x0, z0, x1, z1 + __m128 t1 = _mm_unpackhi_ps( r0, r2 ); // x2, z2, x3, z3 + __m128 t2 = _mm_unpacklo_ps( r1, r3 ); // y0, w0, y1, w1 + __m128 t3 = _mm_unpackhi_ps( r1, r3 ); // y2, w2, y3, w3 + + r0 = _mm_unpacklo_ps( t0, t2 ); // x0, y0, z0, w0 + r1 = _mm_unpackhi_ps( t0, t2 ); // x1, y1, z1, w1 + r2 = _mm_unpacklo_ps( t1, t3 ); // x2, y2, z2, w2 + r3 = _mm_unpackhi_ps( t1, t3 ); // x3, y3, z3, w3 + + _mm_store_ps( dstPtr + n0, r0 ); + _mm_store_ps( dstPtr + n1, r1 ); + _mm_store_ps( dstPtr + n2, r2 ); + _mm_store_ps( dstPtr + n3, r3 ); + } + + // copy the last partial 4x4 block elements + if ( m & 3 ) { + srcPtr += n4; + dstPtr += 4; + + __m128 r0 = _mm_load_ps( srcPtr + n0 ); + __m128 r1 = _mm_and_ps( _mm_load_ps( srcPtr + n1_masked ), bottomMask[b1] ); + __m128 r2 = _mm_and_ps( _mm_load_ps( srcPtr + n2_masked ), bottomMask[b2] ); + __m128 r3 = _mm_setzero_ps(); + + __m128 t0 = _mm_unpacklo_ps( r0, r2 ); // x0, z0, x1, z1 + __m128 t1 = _mm_unpackhi_ps( r0, r2 ); // x2, z2, x3, z3 + __m128 t2 = _mm_unpacklo_ps( r1, r3 ); // y0, w0, y1, w1 + __m128 t3 = _mm_unpackhi_ps( r1, r3 ); // y2, w2, y3, w3 + + r0 = _mm_unpacklo_ps( t0, t2 ); // x0, y0, z0, w0 + r1 = _mm_unpackhi_ps( t0, t2 ); // x1, y1, z1, w1 + r2 = _mm_unpacklo_ps( t1, t3 ); // x2, y2, z2, w2 + r3 = _mm_unpackhi_ps( t1, t3 ); // x3, y3, z3, w3 + + _mm_store_ps( dstPtr + n0, r0 ); + _mm_store_ps( dstPtr + n1, r1 ); + _mm_store_ps( dstPtr + n2, r2 ); + _mm_store_ps( dstPtr + n3, r3 ); + } + + basePtr += n4 + 4; + } + + // copy the lower right partial diagonal 4x4 block elements + if ( m & 3 ) { + __m128 r0 = _mm_and_ps( _mm_load_ps( basePtr + n0 ), mask0 ); + __m128 r1 = _mm_and_ps( _mm_load_ps( basePtr + n1_masked ), _mm_and_ps( mask1, bottomMask[b1] ) ); + __m128 r2 = _mm_and_ps( _mm_load_ps( basePtr + n2_masked ), _mm_and_ps( mask2, bottomMask[b2] ) ); + __m128 r3 = _mm_setzero_ps(); + + __m128 t0 = _mm_unpacklo_ps( r0, r2 ); // x0, z0, x1, z1 + __m128 t1 = _mm_unpackhi_ps( r0, r2 ); // x2, z2, x3, z3 + __m128 t2 = _mm_unpacklo_ps( r1, r3 ); // y0, w0, y1, w1 + __m128 t3 = _mm_unpackhi_ps( r1, r3 ); // y2, w2, y3, w3 + + __m128 s0 = _mm_unpacklo_ps( t0, t2 ); // x0, y0, z0, w0 + __m128 s1 = _mm_unpackhi_ps( t0, t2 ); // x1, y1, z1, w1 + __m128 s2 = _mm_unpacklo_ps( t1, t3 ); // x2, y2, z2, w2 + + r0 = _mm_or_ps( r0, s0 ); + r1 = _mm_or_ps( r1, s1 ); + r2 = _mm_or_ps( r2, s2 ); + + _mm_store_ps( basePtr + n2_masked, r2 ); + _mm_store_ps( basePtr + n1_masked, r1 ); + _mm_store_ps( basePtr + n0, r0 ); + } + + +#ifdef _DEBUG + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numRows; j++ ) { + assert( mat[ i * numColumns + j ] == mat[ j * numColumns + i ] ); + } + } +#endif +} +/* +============ +idMatX::IsOrthogonal + + returns true if (*this) * this->Transpose() == Identity +============ +*/ +bool idMatX::IsOrthogonal( const float epsilon ) const { + float *ptr1, *ptr2, sum; + + if ( !IsSquare() ) { + return false; + } + + ptr1 = mat; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + ptr2 = mat + j; + sum = ptr1[0] * ptr2[0] - (float) ( i == j ); + for ( int n = 1; n < numColumns; n++ ) { + ptr2 += numColumns; + sum += ptr1[n] * ptr2[0]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + ptr1 += numColumns; + } + return true; +} + +/* +============ +idMatX::IsOrthonormal + + returns true if (*this) * this->Transpose() == Identity and the length of each column vector is 1 +============ +*/ +bool idMatX::IsOrthonormal( const float epsilon ) const { + float *ptr1, *ptr2, sum; + + if ( !IsSquare() ) { + return false; + } + + ptr1 = mat; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + ptr2 = mat + j; + sum = ptr1[0] * ptr2[0] - (float) ( i == j ); + for ( int n = 1; n < numColumns; n++ ) { + ptr2 += numColumns; + sum += ptr1[n] * ptr2[0]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + ptr1 += numColumns; + + ptr2 = mat + i; + sum = ptr2[0] * ptr2[0] - 1.0f; + for ( int j = 1; j < numRows; j++ ) { + ptr2 += numColumns; + sum += ptr2[j] * ptr2[j]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + return true; +} + +/* +============ +idMatX::IsPMatrix + + returns true if the matrix is a P-matrix + A square matrix is a P-matrix if all its principal minors are positive. +============ +*/ +bool idMatX::IsPMatrix( const float epsilon ) const { + int i, j; + float d; + idMatX m; + + if ( !IsSquare() ) { + return false; + } + + if ( numRows <= 0 ) { + return true; + } + + if ( (*this)[0][0] <= epsilon ) { + return false; + } + + if ( numRows <= 1 ) { + return true; + } + + m.SetData( numRows - 1, numColumns - 1, MATX_ALLOCA( ( numRows - 1 ) * ( numColumns - 1 ) ) ); + + for ( i = 1; i < numRows; i++ ) { + for ( j = 1; j < numColumns; j++ ) { + m[i-1][j-1] = (*this)[i][j]; + } + } + + if ( !m.IsPMatrix( epsilon ) ) { + return false; + } + + for ( i = 1; i < numRows; i++ ) { + d = (*this)[i][0] / (*this)[0][0]; + for ( j = 1; j < numColumns; j++ ) { + m[i-1][j-1] = (*this)[i][j] - d * (*this)[0][j]; + } + } + + if ( !m.IsPMatrix( epsilon ) ) { + return false; + } + + return true; +} + +/* +============ +idMatX::IsZMatrix + + returns true if the matrix is a Z-matrix + A square matrix M is a Z-matrix if M[i][j] <= 0 for all i != j. +============ +*/ +bool idMatX::IsZMatrix( const float epsilon ) const { + int i, j; + + if ( !IsSquare() ) { + return false; + } + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + if ( (*this)[i][j] > epsilon && i != j ) { + return false; + } + } + } + return true; +} + +/* +============ +idMatX::IsPositiveDefinite + + returns true if the matrix is Positive Definite (PD) + A square matrix M of order n is said to be PD if y'My > 0 for all vectors y of dimension n, y != 0. +============ +*/ +bool idMatX::IsPositiveDefinite( const float epsilon ) const { + int i, j, k; + float d, s; + idMatX m; + + // the matrix must be square + if ( !IsSquare() ) { + return false; + } + + // copy matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // add transpose + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + m[i][j] += (*this)[j][i]; + } + } + + // test Positive Definiteness with Gaussian pivot steps + for ( i = 0; i < numRows; i++ ) { + + for ( j = i; j < numColumns; j++ ) { + if ( m[j][j] <= epsilon ) { + return false; + } + } + + d = 1.0f / m[i][i]; + for ( j = i + 1; j < numColumns; j++ ) { + s = d * m[j][i]; + m[j][i] = 0.0f; + for ( k = i + 1; k < numRows; k++ ) { + m[j][k] -= s * m[i][k]; + } + } + } + + return true; +} + +/* +============ +idMatX::IsSymmetricPositiveDefinite + + returns true if the matrix is Symmetric Positive Definite (PD) +============ +*/ +bool idMatX::IsSymmetricPositiveDefinite( const float epsilon ) const { + idMatX m; + + // the matrix must be symmetric + if ( !IsSymmetric( epsilon ) ) { + return false; + } + + // copy matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // being able to obtain Cholesky factors is both a necessary and sufficient condition for positive definiteness + return m.Cholesky_Factor(); +} + +/* +============ +idMatX::IsPositiveSemiDefinite + + returns true if the matrix is Positive Semi Definite (PSD) + A square matrix M of order n is said to be PSD if y'My >= 0 for all vectors y of dimension n, y != 0. +============ +*/ +bool idMatX::IsPositiveSemiDefinite( const float epsilon ) const { + int i, j, k; + float d, s; + idMatX m; + + // the matrix must be square + if ( !IsSquare() ) { + return false; + } + + // copy original matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // add transpose + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + m[i][j] += (*this)[j][i]; + } + } + + // test Positive Semi Definiteness with Gaussian pivot steps + for ( i = 0; i < numRows; i++ ) { + + for ( j = i; j < numColumns; j++ ) { + if ( m[j][j] < -epsilon ) { + return false; + } + if ( m[j][j] > epsilon ) { + continue; + } + for ( k = 0; k < numRows; k++ ) { + if ( idMath::Fabs( m[k][j] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( m[j][k] ) > epsilon ) { + return false; + } + } + } + + if ( m[i][i] <= epsilon ) { + continue; + } + + d = 1.0f / m[i][i]; + for ( j = i + 1; j < numColumns; j++ ) { + s = d * m[j][i]; + m[j][i] = 0.0f; + for ( k = i + 1; k < numRows; k++ ) { + m[j][k] -= s * m[i][k]; + } + } + } + + return true; +} + +/* +============ +idMatX::IsSymmetricPositiveSemiDefinite + + returns true if the matrix is Symmetric Positive Semi Definite (PSD) +============ +*/ +bool idMatX::IsSymmetricPositiveSemiDefinite( const float epsilon ) const { + + // the matrix must be symmetric + if ( !IsSymmetric( epsilon ) ) { + return false; + } + + return IsPositiveSemiDefinite( epsilon ); +} + +/* +============ +idMatX::LowerTriangularInverse + + in-place inversion of the lower triangular matrix +============ +*/ +bool idMatX::LowerTriangularInverse() { + int i, j, k; + double d, sum; + + for ( i = 0; i < numRows; i++ ) { + d = (*this)[i][i]; + if ( d == 0.0f ) { + return false; + } + (*this)[i][i] = d = 1.0f / d; + + for ( j = 0; j < i; j++ ) { + sum = 0.0f; + for ( k = j; k < i; k++ ) { + sum -= (*this)[i][k] * (*this)[k][j]; + } + (*this)[i][j] = sum * d; + } + } + return true; +} + +/* +============ +idMatX::UpperTriangularInverse + + in-place inversion of the upper triangular matrix +============ +*/ +bool idMatX::UpperTriangularInverse() { + int i, j, k; + double d, sum; + + for ( i = numRows-1; i >= 0; i-- ) { + d = (*this)[i][i]; + if ( d == 0.0f ) { + return false; + } + (*this)[i][i] = d = 1.0f / d; + + for ( j = numRows-1; j > i; j-- ) { + sum = 0.0f; + for ( k = j; k > i; k-- ) { + sum -= (*this)[i][k] * (*this)[k][j]; + } + (*this)[i][j] = sum * d; + } + } + return true; +} + +/* +============= +idMatX::ToString +============= +*/ +const char *idMatX::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============ +idMatX::Update_RankOne + + Updates the matrix to obtain the matrix: A + alpha * v * w' +============ +*/ +void idMatX::Update_RankOne( const idVecX &v, const idVecX &w, float alpha ) { + int i, j; + float s; + + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + + for ( i = 0; i < numRows; i++ ) { + s = alpha * v[i]; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] += s * w[j]; + } + } +} + +/* +============ +idMatX::Update_RankOneSymmetric + + Updates the matrix to obtain the matrix: A + alpha * v * v' +============ +*/ +void idMatX::Update_RankOneSymmetric( const idVecX &v, float alpha ) { + int i, j; + float s; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + s = alpha * v[i]; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] += s * v[j]; + } + } +} + +/* +============ +idMatX::Update_RowColumn + + Updates the matrix to obtain the matrix: + + [ 0 a 0 ] + A + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +void idMatX::Update_RowColumn( const idVecX &v, const idVecX &w, int r ) { + int i; + + assert( w[r] == 0.0f ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + (*this)[i][r] += v[i]; + } + for ( i = 0; i < numColumns; i++ ) { + (*this)[r][i] += w[i]; + } +} + +/* +============ +idMatX::Update_RowColumnSymmetric + + Updates the matrix to obtain the matrix: + + [ 0 a 0 ] + A + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +void idMatX::Update_RowColumnSymmetric( const idVecX &v, int r ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + + for ( i = 0; i < r; i++ ) { + (*this)[i][r] += v[i]; + (*this)[r][i] += v[i]; + } + (*this)[r][r] += v[r]; + for ( i = r+1; i < numRows; i++ ) { + (*this)[i][r] += v[i]; + (*this)[r][i] += v[i]; + } +} + +/* +============ +idMatX::Update_Increment + + Updates the matrix to obtain the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1]], w[numColumns] = 0 +============ +*/ +void idMatX::Update_Increment( const idVecX &v, const idVecX &w ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + for ( i = 0; i < numRows; i++ ) { + (*this)[i][numColumns-1] = v[i]; + } + for ( i = 0; i < numColumns-1; i++ ) { + (*this)[numRows-1][i] = w[i]; + } +} + +/* +============ +idMatX::Update_IncrementSymmetric + + Updates the matrix to obtain the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +void idMatX::Update_IncrementSymmetric( const idVecX &v ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + for ( i = 0; i < numRows-1; i++ ) { + (*this)[i][numColumns-1] = v[i]; + } + for ( i = 0; i < numColumns; i++ ) { + (*this)[numRows-1][i] = v[i]; + } +} + +/* +============ +idMatX::Update_Decrement + + Updates the matrix to obtain a matrix with row r and column r removed. +============ +*/ +void idMatX::Update_Decrement( int r ) { + RemoveRowColumn( r ); +} + +/* +============ +idMatX::Inverse_GaussJordan + + in-place inversion using Gauss-Jordan elimination +============ +*/ +bool idMatX::Inverse_GaussJordan() { + int i, j, k, r, c; + float d, max; + + assert( numRows == numColumns ); + + int *columnIndex = (int *) _alloca16( numRows * sizeof( int ) ); + int *rowIndex = (int *) _alloca16( numRows * sizeof( int ) ); + bool *pivot = (bool *) _alloca16( numRows * sizeof( bool ) ); + + memset( pivot, 0, numRows * sizeof( bool ) ); + + // elimination with full pivoting + for ( i = 0; i < numRows; i++ ) { + + // search the whole matrix except for pivoted rows for the maximum absolute value + max = 0.0f; + r = c = 0; + for ( j = 0; j < numRows; j++ ) { + if ( !pivot[j] ) { + for ( k = 0; k < numRows; k++ ) { + if ( !pivot[k] ) { + d = idMath::Fabs( (*this)[j][k] ); + if ( d > max ) { + max = d; + r = j; + c = k; + } + } + } + } + } + + if ( max == 0.0f ) { + // matrix is not invertible + return false; + } + + pivot[c] = true; + + // swap rows such that entry (c,c) has the pivot entry + if ( r != c ) { + SwapRows( r, c ); + } + + // keep track of the row permutation + rowIndex[i] = r; + columnIndex[i] = c; + + // scale the row to make the pivot entry equal to 1 + d = 1.0f / (*this)[c][c]; + (*this)[c][c] = 1.0f; + for ( k = 0; k < numRows; k++ ) { + (*this)[c][k] *= d; + } + + // zero out the pivot column entries in the other rows + for ( j = 0; j < numRows; j++ ) { + if ( j != c ) { + d = (*this)[j][c]; + (*this)[j][c] = 0.0f; + for ( k = 0; k < numRows; k++ ) { + (*this)[j][k] -= (*this)[c][k] * d; + } + } + } + } + + // reorder rows to store the inverse of the original matrix + for ( j = numRows - 1; j >= 0; j-- ) { + if ( rowIndex[j] != columnIndex[j] ) { + for ( k = 0; k < numRows; k++ ) { + d = (*this)[k][rowIndex[j]]; + (*this)[k][rowIndex[j]] = (*this)[k][columnIndex[j]]; + (*this)[k][columnIndex[j]] = d; + } + } + } + + return true; +} + +/* +============ +idMatX::Inverse_UpdateRankOne + + Updates the in-place inverse using the Sherman-Morrison formula to obtain the inverse for the matrix: A + alpha * v * w' +============ +*/ +bool idMatX::Inverse_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha ) { + int i, j; + float beta, s; + idVecX y, z; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + y.SetData( numRows, VECX_ALLOCA( numRows ) ); + z.SetData( numRows, VECX_ALLOCA( numRows ) ); + + Multiply( y, v ); + TransposeMultiply( z, w ); + beta = 1.0f + ( w * y ); + + if ( beta == 0.0f ) { + return false; + } + + alpha /= beta; + + for ( i = 0; i < numRows; i++ ) { + s = y[i] * alpha; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] -= s * z[j]; + } + } + return true; +} + +/* +============ +idMatX::Inverse_UpdateRowColumn + + Updates the in-place inverse to obtain the inverse for the matrix: + + [ 0 a 0 ] + A + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::Inverse_UpdateRowColumn( const idVecX &v, const idVecX &w, int r ) { + idVecX s; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !Inverse_UpdateRankOne( v, s, 1.0f ) ) { + return false; + } + if ( !Inverse_UpdateRankOne( s, w, 1.0f ) ) { + return false; + } + return true; +} + +/* +============ +idMatX::Inverse_UpdateIncrement + + Updates the in-place inverse to obtain the inverse for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::Inverse_UpdateIncrement( const idVecX &v, const idVecX &w ) { + idVecX v2; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + (*this)[numRows-1][numRows-1] = 1.0f; + + v2.SetData( numRows, VECX_ALLOCA( numRows ) ); + v2 = v; + v2[numRows-1] -= 1.0f; + + return Inverse_UpdateRowColumn( v2, w, numRows-1 ); +} + +/* +============ +idMatX::Inverse_UpdateDecrement + + Updates the in-place inverse to obtain the inverse of the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. +============ +*/ +bool idMatX::Inverse_UpdateDecrement( const idVecX &v, const idVecX &w, int r ) { + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !Inverse_UpdateRowColumn( v1, w1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::Inverse_Solve + + Solve Ax = b with A inverted +============ +*/ +void idMatX::Inverse_Solve( idVecX &x, const idVecX &b ) const { + Multiply( x, b ); +} + +/* +============ +idMatX::LU_Factor + + in-place factorization: LU + L is a triangular matrix stored in the lower triangle. + L has ones on the diagonal that are not stored. + U is a triangular matrix stored in the upper triangle. + If index != NULL partial pivoting is used for numerical stability. + If index != NULL it must point to an array of numRow integers and is used to keep track of the row permutation. + If det != NULL the determinant of the matrix is calculated and stored. +============ +*/ +bool idMatX::LU_Factor( int *index, float *det ) { + int i, j, k, newi, min; + double s, t, d, w; + + // if partial pivoting should be used + if ( index ) { + for ( i = 0; i < numRows; i++ ) { + index[i] = i; + } + } + + w = 1.0f; + min = Min( numRows, numColumns ); + for ( i = 0; i < min; i++ ) { + + newi = i; + s = idMath::Fabs( (*this)[i][i] ); + + if ( index ) { + // find the largest absolute pivot + for ( j = i + 1; j < numRows; j++ ) { + t = idMath::Fabs( (*this)[j][i] ); + if ( t > s ) { + newi = j; + s = t; + } + } + } + + if ( s == 0.0f ) { + return false; + } + + if ( newi != i && index ) { + + w = -w; + + // swap index elements + k = index[i]; + index[i] = index[newi]; + index[newi] = k; + + // swap rows + for ( j = 0; j < numColumns; j++ ) { + t = (*this)[newi][j]; + (*this)[newi][j] = (*this)[i][j]; + (*this)[i][j] = t; + } + } + + if ( i < numRows ) { + d = 1.0f / (*this)[i][i]; + for ( j = i + 1; j < numRows; j++ ) { + (*this)[j][i] *= d; + } + } + + if ( i < min-1 ) { + for ( j = i + 1; j < numRows; j++ ) { + d = (*this)[j][i]; + for ( k = i + 1; k < numColumns; k++ ) { + (*this)[j][k] -= d * (*this)[i][k]; + } + } + } + } + + if ( det ) { + for ( i = 0; i < numRows; i++ ) { + w *= (*this)[i][i]; + } + *det = w; + } + + return true; +} + +/* +============ +idMatX::LU_UpdateRankOne + + Updates the in-place LU factorization to obtain the factors for the matrix: LU + alpha * v * w' +============ +*/ +bool idMatX::LU_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha, int *index ) { + int i, j, max; + float *y, *z; + double diag, beta, p0, p1, d; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + + if ( index != NULL ) { + for ( i = 0; i < numRows; i++ ) { + y[i] = alpha * v[index[i]]; + } + } else { + for ( i = 0; i < numRows; i++ ) { + y[i] = alpha * v[i]; + } + } + + memcpy( z, w.ToFloatPtr(), w.GetSize() * sizeof( float ) ); + + max = Min( numRows, numColumns ); + for ( i = 0; i < max; i++ ) { + diag = (*this)[i][i]; + + p0 = y[i]; + p1 = z[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + return false; + } + + beta = p1 / diag; + + (*this)[i][i] = diag; + + for ( j = i+1; j < numColumns; j++ ) { + + d = (*this)[i][j]; + + d += p0 * z[j]; + z[j] -= beta * d; + + (*this)[i][j] = d; + } + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y[j] -= p0 * d; + d += beta * y[j]; + + (*this)[j][i] = d; + } + } + return true; +} + +/* +============ +idMatX::LU_UpdateRowColumn + + Updates the in-place LU factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LU + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::LU_UpdateRowColumn( const idVecX &v, const idVecX &w, int r, int *index ) { +#if 0 + + idVecX s; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !LU_UpdateRankOne( v, s, 1.0f, index ) ) { + return false; + } + if ( !LU_UpdateRankOne( s, w, 1.0f, index ) ) { + return false; + } + return true; + +#else + + int i, j, min, max, rp; + float *y0, *y1, *z0, *z1; + double diag, beta0, beta1, p0, p1, q0, q1, d; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numColumns && r < numRows ); + assert( w[r] == 0.0f ); + + y0 = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z0 = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + y1 = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z1 = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + + if ( index != NULL ) { + for ( i = 0; i < numRows; i++ ) { + y0[i] = v[index[i]]; + } + rp = r; + for ( i = 0; i < numRows; i++ ) { + if ( index[i] == r ) { + rp = i; + break; + } + } + } else { + memcpy( y0, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + rp = r; + } + + memset( y1, 0, v.GetSize() * sizeof( float ) ); + y1[rp] = 1.0f; + + memset( z0, 0, w.GetSize() * sizeof( float ) ); + z0[r] = 1.0f; + + memcpy( z1, w.ToFloatPtr(), w.GetSize() * sizeof( float ) ); + + // update the beginning of the to be updated row and column + min = Min( r, rp ); + for ( i = 0; i < min; i++ ) { + p0 = y0[i]; + beta1 = z1[i] / (*this)[i][i]; + + (*this)[i][r] += p0; + for ( j = i+1; j < numColumns; j++ ) { + z1[j] -= beta1 * (*this)[i][j]; + } + for ( j = i+1; j < numRows; j++ ) { + y0[j] -= p0 * (*this)[j][i]; + } + (*this)[rp][i] += beta1; + } + + // update the lower right corner starting at r,r + max = Min( numRows, numColumns ); + for ( i = min; i < max; i++ ) { + diag = (*this)[i][i]; + + p0 = y0[i]; + p1 = z0[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + return false; + } + + beta0 = p1 / diag; + + q0 = y1[i]; + q1 = z1[i]; + diag += q0 * q1; + + if ( diag == 0.0f ) { + return false; + } + + beta1 = q1 / diag; + + (*this)[i][i] = diag; + + for ( j = i+1; j < numColumns; j++ ) { + + d = (*this)[i][j]; + + d += p0 * z0[j]; + z0[j] -= beta0 * d; + + d += q0 * z1[j]; + z1[j] -= beta1 * d; + + (*this)[i][j] = d; + } + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y0[j] -= p0 * d; + d += beta0 * y0[j]; + + y1[j] -= q0 * d; + d += beta1 * y1[j]; + + (*this)[j][i] = d; + } + } + return true; + +#endif +} + +/* +============ +idMatX::LU_UpdateIncrement + + Updates the in-place LU factorization to obtain the factors for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::LU_UpdateIncrement( const idVecX &v, const idVecX &w, int *index ) { + int i, j; + float sum; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + + // add row to L + for ( i = 0; i < numRows - 1; i++ ) { + sum = w[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[numRows - 1][j] * (*this)[j][i]; + } + (*this)[numRows - 1 ][i] = sum / (*this)[i][i]; + } + + // add row to the permutation index + if ( index != NULL ) { + index[numRows - 1] = numRows - 1; + } + + // add column to U + for ( i = 0; i < numRows; i++ ) { + if ( index != NULL ) { + sum = v[index[i]]; + } else { + sum = v[i]; + } + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * (*this)[j][numRows - 1]; + } + (*this)[i][numRows - 1] = sum; + } + + return true; +} + +/* +============ +idMatX::LU_UpdateDecrement + + Updates the in-place LU factorization to obtain the factors for the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. + If index != NULL then u should store row index[r] of the original matrix. If index == NULL then u = w. +============ +*/ +bool idMatX::LU_UpdateDecrement( const idVecX &v, const idVecX &w, const idVecX &u, int r, int *index ) { + int i, p; + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + if ( index != NULL ) { + + // find the pivot row + for ( p = i = 0; i < numRows; i++ ) { + if ( index[i] == r ) { + p = i; + break; + } + } + + // update the row and column to identity + v1 = -v; + w1 = -u; + + if ( p != r ) { + SwapValues( v1[index[r]], v1[index[p]] ); + SwapValues( index[r], index[p] ); + } + + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !LU_UpdateRowColumn( v1, w1, r, index ) ) { + return false; + } + + if ( p != r ) { + + if ( idMath::Fabs( u[p] ) < 1e-4f ) { + // NOTE: an additional row interchange is required for numerical stability + } + + // move row index[r] of the original matrix to row index[p] of the original matrix + v1.Zero(); + v1[index[p]] = 1.0f; + w1 = u - w; + + if ( !LU_UpdateRankOne( v1, w1, 1.0f, index ) ) { + return false; + } + } + + // remove the row from the permutation index + for ( i = r; i < numRows - 1; i++ ) { + index[i] = index[i+1]; + } + for ( i = 0; i < numRows - 1; i++ ) { + if ( index[i] > r ) { + index[i]--; + } + } + + } else { + + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !LU_UpdateRowColumn( v1, w1, r, index ) ) { + return false; + } + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::LU_Solve + + Solve Ax = b with A factored in-place as: LU +============ +*/ +void idMatX::LU_Solve( idVecX &x, const idVecX &b, const int *index ) const { + int i, j; + double sum; + + assert( x.GetSize() == numColumns && b.GetSize() == numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + if ( index != NULL ) { + sum = b[index[i]]; + } else { + sum = b[i]; + } + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // solve U + for ( i = numRows - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } +} + +/* +============ +idMatX::LU_Inverse + + Calculates the inverse of the matrix which is factored in-place as LU +============ +*/ +void idMatX::LU_Inverse( idMatX &inv, const int *index ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + LU_Solve( x, b, index ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::LU_UnpackFactors + + Unpacks the in-place LU factorization. +============ +*/ +void idMatX::LU_UnpackFactors( idMatX &L, idMatX &U ) const { + int i, j; + + L.Zero( numRows, numColumns ); + U.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < i; j++ ) { + L[i][j] = (*this)[i][j]; + } + L[i][i] = 1.0f; + for ( j = i; j < numColumns; j++ ) { + U[i][j] = (*this)[i][j]; + } + } +} + +/* +============ +idMatX::LU_MultiplyFactors + + Multiplies the factors of the in-place LU factorization to form the original matrix. +============ +*/ +void idMatX::LU_MultiplyFactors( idMatX &m, const int *index ) const { + int r, rp, i, j; + double sum; + + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + if ( index != NULL ) { + rp = index[r]; + } else { + rp = r; + } + + // calculate row of matrix + for ( i = 0; i < numColumns; i++ ) { + if ( i >= r ) { + sum = (*this)[r][i]; + } else { + sum = 0.0f; + } + for ( j = 0; j <= i && j < r; j++ ) { + sum += (*this)[r][j] * (*this)[j][i]; + } + m[rp][i] = sum; + } + } +} + +/* +============ +idMatX::QR_Factor + + in-place factorization: QR + Q is an orthogonal matrix represented as a product of Householder matrices stored in the lower triangle and c. + R is a triangular matrix stored in the upper triangle except for the diagonal elements which are stored in d. + The initial matrix has to be square. +============ +*/ +bool idMatX::QR_Factor( idVecX &c, idVecX &d ) { + int i, j, k; + double scale, s, t, sum; + bool singular = false; + + assert( numRows == numColumns ); + assert( c.GetSize() >= numRows && d.GetSize() >= numRows ); + + for ( k = 0; k < numRows-1; k++ ) { + + scale = 0.0f; + for ( i = k; i < numRows; i++ ) { + s = idMath::Fabs( (*this)[i][k] ); + if ( s > scale ) { + scale = s; + } + } + if ( scale == 0.0f ) { + singular = true; + c[k] = d[k] = 0.0f; + } else { + + s = 1.0f / scale; + for ( i = k; i < numRows; i++ ) { + (*this)[i][k] *= s; + } + + sum = 0.0f; + for ( i = k; i < numRows; i++ ) { + s = (*this)[i][k]; + sum += s * s; + } + + s = idMath::Sqrt( sum ); + if ( (*this)[k][k] < 0.0f ) { + s = -s; + } + (*this)[k][k] += s; + c[k] = s * (*this)[k][k]; + d[k] = -scale * s; + + for ( j = k+1; j < numRows; j++ ) { + + sum = 0.0f; + for ( i = k; i < numRows; i++ ) { + sum += (*this)[i][k] * (*this)[i][j]; + } + t = sum / c[k]; + for ( i = k; i < numRows; i++ ) { + (*this)[i][j] -= t * (*this)[i][k]; + } + } + } + } + d[numRows-1] = (*this)[ (numRows-1) ][ (numRows-1) ]; + if ( d[numRows-1] == 0.0f ) { + singular = true; + } + + return !singular; +} + +/* +============ +idMatX::QR_Rotate + + Performs a Jacobi rotation on the rows i and i+1 of the unpacked QR factors. +============ +*/ +void idMatX::QR_Rotate( idMatX &R, int i, float a, float b ) { + int j; + float f, c, s, w, y; + + if ( a == 0.0f ) { + c = 0.0f; + s = ( b >= 0.0f ) ? 1.0f : -1.0f; + } else if ( idMath::Fabs( a ) > idMath::Fabs( b ) ) { + f = b / a; + c = idMath::Fabs( 1.0f / idMath::Sqrt( 1.0f + f * f ) ); + if ( a < 0.0f ) { + c = -c; + } + s = f * c; + } else { + f = a / b; + s = idMath::Fabs( 1.0f / idMath::Sqrt( 1.0f + f * f ) ); + if ( b < 0.0f ) { + s = -s; + } + c = f * s; + } + for ( j = i; j < numRows; j++ ) { + y = R[i][j]; + w = R[i+1][j]; + R[i][j] = c * y - s * w; + R[i+1][j] = s * y + c * w; + } + for ( j = 0; j < numRows; j++ ) { + y = (*this)[j][i]; + w = (*this)[j][i+1]; + (*this)[j][i] = c * y - s * w; + (*this)[j][i+1] = s * y + c * w; + } +} + +/* +============ +idMatX::QR_UpdateRankOne + + Updates the unpacked QR factorization to obtain the factors for the matrix: QR + alpha * v * w' +============ +*/ +bool idMatX::QR_UpdateRankOne( idMatX &R, const idVecX &v, const idVecX &w, float alpha ) { + int i, k; + float f; + idVecX u; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + u.SetData( v.GetSize(), VECX_ALLOCA( v.GetSize() ) ); + TransposeMultiply( u, v ); + u *= alpha; + + for ( k = v.GetSize()-1; k > 0; k-- ) { + if ( u[k] != 0.0f ) { + break; + } + } + for ( i = k-1; i >= 0; i-- ) { + QR_Rotate( R, i, u[i], -u[i+1] ); + if ( u[i] == 0.0f ) { + u[i] = idMath::Fabs( u[i+1] ); + } else if ( idMath::Fabs( u[i] ) > idMath::Fabs( u[i+1] ) ) { + f = u[i+1] / u[i]; + u[i] = idMath::Fabs( u[i] ) * idMath::Sqrt( 1.0f + f * f ); + } else { + f = u[i] / u[i+1]; + u[i] = idMath::Fabs( u[i+1] ) * idMath::Sqrt( 1.0f + f * f ); + } + } + for ( i = 0; i < v.GetSize(); i++ ) { + R[0][i] += u[0] * w[i]; + } + for ( i = 0; i < k; i++ ) { + QR_Rotate( R, i, -R[i][i], R[i+1][i] ); + } + return true; +} + +/* +============ +idMatX::QR_UpdateRowColumn + + Updates the unpacked QR factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + QR + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::QR_UpdateRowColumn( idMatX &R, const idVecX &v, const idVecX &w, int r ) { + idVecX s; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !QR_UpdateRankOne( R, v, s, 1.0f ) ) { + return false; + } + if ( !QR_UpdateRankOne( R, s, w, 1.0f ) ) { + return false; + } + return true; +} + +/* +============ +idMatX::QR_UpdateIncrement + + Updates the unpacked QR factorization to obtain the factors for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::QR_UpdateIncrement( idMatX &R, const idVecX &v, const idVecX &w ) { + idVecX v2; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + (*this)[numRows-1][numRows-1] = 1.0f; + + R.ChangeSize( R.numRows+1, R.numColumns+1, true ); + R[R.numRows-1][R.numRows-1] = 1.0f; + + v2.SetData( numRows, VECX_ALLOCA( numRows ) ); + v2 = v; + v2[numRows-1] -= 1.0f; + + return QR_UpdateRowColumn( R, v2, w, numRows-1 ); +} + +/* +============ +idMatX::QR_UpdateDecrement + + Updates the unpacked QR factorization to obtain the factors for the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. +============ +*/ +bool idMatX::QR_UpdateDecrement( idMatX &R, const idVecX &v, const idVecX &w, int r ) { + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !QR_UpdateRowColumn( R, v1, w1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + R.Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::QR_Solve + + Solve Ax = b with A factored in-place as: QR +============ +*/ +void idMatX::QR_Solve( idVecX &x, const idVecX &b, const idVecX &c, const idVecX &d ) const { + int i, j; + double sum, t; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + assert( c.GetSize() >= numRows && d.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + x[i] = b[i]; + } + + // multiply b with transpose of Q + for ( i = 0; i < numRows-1; i++ ) { + + sum = 0.0f; + for ( j = i; j < numRows; j++ ) { + sum += (*this)[j][i] * x[j]; + } + t = sum / c[i]; + for ( j = i; j < numRows; j++ ) { + x[j] -= t * (*this)[j][i]; + } + } + + // backsubstitution with R + for ( i = numRows-1; i >= 0; i-- ) { + + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / d[i]; + } +} + +/* +============ +idMatX::QR_Solve + + Solve Ax = b with A factored as: QR +============ +*/ +void idMatX::QR_Solve( idVecX &x, const idVecX &b, const idMatX &R ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + + // multiply b with transpose of Q + TransposeMultiply( x, b ); + + // backsubstitution with R + for ( i = numRows-1; i >= 0; i-- ) { + + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= R[i][j] * x[j]; + } + x[i] = sum / R[i][i]; + } +} + +/* +============ +idMatX::QR_Inverse + + Calculates the inverse of the matrix which is factored in-place as: QR +============ +*/ +void idMatX::QR_Inverse( idMatX &inv, const idVecX &c, const idVecX &d ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + QR_Solve( x, b, c, d ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::QR_UnpackFactors + + Unpacks the in-place QR factorization. +============ +*/ +void idMatX::QR_UnpackFactors( idMatX &Q, idMatX &R, const idVecX &c, const idVecX &d ) const { + int i, j, k; + double sum; + + Q.Identity( numRows, numColumns ); + for ( i = 0; i < numColumns-1; i++ ) { + if ( c[i] == 0.0f ) { + continue; + } + for ( j = 0; j < numRows; j++ ) { + sum = 0.0f; + for ( k = i; k < numColumns; k++ ) { + sum += (*this)[k][i] * Q[j][k]; + } + sum /= c[i]; + for ( k = i; k < numColumns; k++ ) { + Q[j][k] -= sum * (*this)[k][i]; + } + } + } + + R.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + R[i][i] = d[i]; + for ( j = i+1; j < numColumns; j++ ) { + R[i][j] = (*this)[i][j]; + } + } +} + +/* +============ +idMatX::QR_MultiplyFactors + + Multiplies the factors of the in-place QR factorization to form the original matrix. +============ +*/ +void idMatX::QR_MultiplyFactors( idMatX &m, const idVecX &c, const idVecX &d ) const { + int i, j, k; + double sum; + idMatX Q; + + Q.Identity( numRows, numColumns ); + for ( i = 0; i < numColumns-1; i++ ) { + if ( c[i] == 0.0f ) { + continue; + } + for ( j = 0; j < numRows; j++ ) { + sum = 0.0f; + for ( k = i; k < numColumns; k++ ) { + sum += (*this)[k][i] * Q[j][k]; + } + sum /= c[i]; + for ( k = i; k < numColumns; k++ ) { + Q[j][k] -= sum * (*this)[k][i]; + } + } + } + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + sum = Q[i][j] * d[i]; + for ( k = 0; k < i; k++ ) { + sum += Q[i][k] * (*this)[j][k]; + } + m[i][j] = sum; + } + } +} + +/* +============ +idMatX::Pythag + + Computes (a^2 + b^2)^1/2 without underflow or overflow. +============ +*/ +float idMatX::Pythag( float a, float b ) const { + double at, bt, ct; + + at = idMath::Fabs( a ); + bt = idMath::Fabs( b ); + if ( at > bt ) { + ct = bt / at; + return at * idMath::Sqrt( 1.0f + ct * ct ); + } else { + if ( bt ) { + ct = at / bt; + return bt * idMath::Sqrt( 1.0f + ct * ct ); + } else { + return 0.0f; + } + } +} + +/* +============ +idMatX::SVD_BiDiag +============ +*/ +void idMatX::SVD_BiDiag( idVecX &w, idVecX &rv1, float &anorm ) { + int i, j, k, l; + double f, h, r, g, s, scale; + + anorm = 0.0f; + g = s = scale = 0.0f; + for ( i = 0; i < numColumns; i++ ) { + l = i + 1; + rv1[i] = scale * g; + g = s = scale = 0.0f; + if ( i < numRows ) { + for ( k = i; k < numRows; k++ ) { + scale += idMath::Fabs( (*this)[k][i] ); + } + if ( scale ) { + for ( k = i; k < numRows; k++ ) { + (*this)[k][i] /= scale; + s += (*this)[k][i] * (*this)[k][i]; + } + f = (*this)[i][i]; + g = idMath::Sqrt( s ); + if ( f >= 0.0f ) { + g = -g; + } + h = f * g - s; + (*this)[i][i] = f - g; + if ( i != (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = i; k < numRows; k++ ) { + s += (*this)[k][i] * (*this)[k][j]; + } + f = s / h; + for ( k = i; k < numRows; k++ ) { + (*this)[k][j] += f * (*this)[k][i]; + } + } + } + for ( k = i; k < numRows; k++ ) { + (*this)[k][i] *= scale; + } + } + } + w[i] = scale * g; + g = s = scale = 0.0f; + if ( i < numRows && i != (numColumns-1) ) { + for ( k = l; k < numColumns; k++ ) { + scale += idMath::Fabs( (*this)[i][k] ); + } + if ( scale ) { + for ( k = l; k < numColumns; k++ ) { + (*this)[i][k] /= scale; + s += (*this)[i][k] * (*this)[i][k]; + } + f = (*this)[i][l]; + g = idMath::Sqrt( s ); + if ( f >= 0.0f ) { + g = -g; + } + h = 1.0f / ( f * g - s ); + (*this)[i][l] = f - g; + for ( k = l; k < numColumns; k++ ) { + rv1[k] = (*this)[i][k] * h; + } + if ( i != (numRows-1) ) { + for ( j = l; j < numRows; j++ ) { + for ( s = 0.0f, k = l; k < numColumns; k++ ) { + s += (*this)[j][k] * (*this)[i][k]; + } + for ( k = l; k < numColumns; k++ ) { + (*this)[j][k] += s * rv1[k]; + } + } + } + for ( k = l; k < numColumns; k++ ) { + (*this)[i][k] *= scale; + } + } + } + r = idMath::Fabs( w[i] ) + idMath::Fabs( rv1[i] ); + if ( r > anorm ) { + anorm = r; + } + } +} + +/* +============ +idMatX::SVD_InitialWV +============ +*/ +void idMatX::SVD_InitialWV( idVecX &w, idMatX &V, idVecX &rv1 ) { + int i, j, k, l; + double f, g, s; + + g = 0.0f; + for ( i = (numColumns-1); i >= 0; i-- ) { + l = i + 1; + if ( i < ( numColumns - 1 ) ) { + if ( g ) { + for ( j = l; j < numColumns; j++ ) { + V[j][i] = ((*this)[i][j] / (*this)[i][l]) / g; + } + // double division to reduce underflow + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = l; k < numColumns; k++ ) { + s += (*this)[i][k] * V[k][j]; + } + for ( k = l; k < numColumns; k++ ) { + V[k][j] += s * V[k][i]; + } + } + } + for ( j = l; j < numColumns; j++ ) { + V[i][j] = V[j][i] = 0.0f; + } + } + V[i][i] = 1.0f; + g = rv1[i]; + } + for ( i = numColumns - 1 ; i >= 0; i-- ) { + l = i + 1; + g = w[i]; + if ( i < (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + (*this)[i][j] = 0.0f; + } + } + if ( g ) { + g = 1.0f / g; + if ( i != (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = l; k < numRows; k++ ) { + s += (*this)[k][i] * (*this)[k][j]; + } + f = (s / (*this)[i][i]) * g; + for ( k = i; k < numRows; k++ ) { + (*this)[k][j] += f * (*this)[k][i]; + } + } + } + for ( j = i; j < numRows; j++ ) { + (*this)[j][i] *= g; + } + } + else { + for ( j = i; j < numRows; j++ ) { + (*this)[j][i] = 0.0f; + } + } + (*this)[i][i] += 1.0f; + } +} + +/* +============ +idMatX::SVD_Factor + + in-place factorization: U * Diag(w) * V.Transpose() + known as the Singular Value Decomposition. + U is a column-orthogonal matrix which overwrites the original matrix. + w is a diagonal matrix with all elements >= 0 which are the singular values. + V is the transpose of an orthogonal matrix. +============ +*/ +bool idMatX::SVD_Factor( idVecX &w, idMatX &V ) { + int flag, i, its, j, jj, k, l, nm; + double c, f, h, s, x, y, z, r, g = 0.0f; + float anorm = 0.0f; + idVecX rv1; + + if ( numRows < numColumns ) { + return false; + } + + rv1.SetData( numColumns, VECX_ALLOCA( numColumns ) ); + rv1.Zero(); + w.Zero( numColumns ); + V.Zero( numColumns, numColumns ); + + SVD_BiDiag( w, rv1, anorm ); + SVD_InitialWV( w, V, rv1 ); + + for ( k = numColumns - 1; k >= 0; k-- ) { + for ( its = 1; its <= 30; its++ ) { + flag = 1; + nm = 0; + for ( l = k; l >= 0; l-- ) { + nm = l - 1; + if ( ( idMath::Fabs( rv1[l] ) + anorm ) == anorm /* idMath::Fabs( rv1[l] ) < idMath::FLT_EPSILON */ ) { + flag = 0; + break; + } + if ( ( idMath::Fabs( w[nm] ) + anorm ) == anorm /* idMath::Fabs( w[nm] ) < idMath::FLT_EPSILON */ ) { + break; + } + } + if ( flag ) { + c = 0.0f; + s = 1.0f; + for ( i = l; i <= k; i++ ) { + f = s * rv1[i]; + + if ( ( idMath::Fabs( f ) + anorm ) != anorm /* idMath::Fabs( f ) > idMath::FLT_EPSILON */ ) { + g = w[i]; + h = Pythag( f, g ); + w[i] = h; + h = 1.0f / h; + c = g * h; + s = -f * h; + for ( j = 0; j < numRows; j++ ) { + y = (*this)[j][nm]; + z = (*this)[j][i]; + (*this)[j][nm] = y * c + z * s; + (*this)[j][i] = z * c - y * s; + } + } + } + } + z = w[k]; + if ( l == k ) { + if ( z < 0.0f ) { + w[k] = -z; + for ( j = 0; j < numColumns; j++ ) { + V[j][k] = -V[j][k]; + } + } + break; + } + if ( its == 30 ) { + return false; // no convergence + } + x = w[l]; + nm = k - 1; + y = w[nm]; + g = rv1[nm]; + h = rv1[k]; + f = ( ( y - z ) * ( y + z ) + ( g - h ) * ( g + h ) ) / ( 2.0f * h * y ); + g = Pythag( f, 1.0f ); + r = ( f >= 0.0f ? g : - g ); + f= ( ( x - z ) * ( x + z ) + h * ( ( y / ( f + r ) ) - h ) ) / x; + c = s = 1.0f; + for ( j = l; j <= nm; j++ ) { + i = j + 1; + g = rv1[i]; + y = w[i]; + h = s * g; + g = c * g; + z = Pythag( f, h ); + rv1[j] = z; + c = f / z; + s = h / z; + f = x * c + g * s; + g = g * c - x * s; + h = y * s; + y = y * c; + for ( jj = 0; jj < numColumns; jj++ ) { + x = V[jj][j]; + z = V[jj][i]; + V[jj][j] = x * c + z * s; + V[jj][i] = z * c - x * s; + } + z = Pythag( f, h ); + w[j] = z; + if ( z ) { + z = 1.0f / z; + c = f * z; + s = h * z; + } + f = ( c * g ) + ( s * y ); + x = ( c * y ) - ( s * g ); + for ( jj = 0; jj < numRows; jj++ ) { + y = (*this)[jj][j]; + z = (*this)[jj][i]; + (*this)[jj][j] = y * c + z * s; + (*this)[jj][i] = z * c - y * s; + } + } + rv1[l] = 0.0f; + rv1[k] = f; + w[k] = x; + } + } + return true; +} + +/* +============ +idMatX::SVD_Solve + + Solve Ax = b with A factored as: U * Diag(w) * V.Transpose() +============ +*/ +void idMatX::SVD_Solve( idVecX &x, const idVecX &b, const idVecX &w, const idMatX &V ) const { + int i, j; + double sum; + idVecX tmp; + + assert( x.GetSize() >= numColumns ); + assert( b.GetSize() >= numColumns ); + assert( w.GetSize() == numColumns ); + assert( V.GetNumRows() == numColumns && V.GetNumColumns() == numColumns ); + + tmp.SetData( numColumns, VECX_ALLOCA( numColumns ) ); + + for ( i = 0; i < numColumns; i++ ) { + sum = 0.0f; + if ( w[i] >= idMath::FLT_EPSILON ) { + for ( j = 0; j < numRows; j++ ) { + sum += (*this)[j][i] * b[j]; + } + sum /= w[i]; + } + tmp[i] = sum; + } + for ( i = 0; i < numColumns; i++ ) { + sum = 0.0f; + for ( j = 0; j < numColumns; j++ ) { + sum += V[i][j] * tmp[j]; + } + x[i] = sum; + } +} + +/* +============ +idMatX::SVD_Inverse + + Calculates the inverse of the matrix which is factored in-place as: U * Diag(w) * V.Transpose() +============ +*/ +void idMatX::SVD_Inverse( idMatX &inv, const idVecX &w, const idMatX &V ) const { + int i, j, k; + double wi, sum; + idMatX V2; + + assert( numRows == numColumns ); + + V2 = V; + + // V * [diag(1/w[i])] + for ( i = 0; i < numRows; i++ ) { + wi = w[i]; + wi = ( wi < idMath::FLT_EPSILON ) ? 0.0f : 1.0f / wi; + for ( j = 0; j < numColumns; j++ ) { + V2[j][i] *= wi; + } + } + + // V * [diag(1/w[i])] * Ut + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + sum = V2[i][0] * (*this)[j][0]; + for ( k = 1; k < numColumns; k++ ) { + sum += V2[i][k] * (*this)[j][k]; + } + inv[i][j] = sum; + } + } +} + +/* +============ +idMatX::SVD_MultiplyFactors + + Multiplies the factors of the in-place SVD factorization to form the original matrix. +============ +*/ +void idMatX::SVD_MultiplyFactors( idMatX &m, const idVecX &w, const idMatX &V ) const { + int r, i, j; + double sum; + + m.SetSize( numRows, V.GetNumRows() ); + + for ( r = 0; r < numRows; r++ ) { + // calculate row of matrix + if ( w[r] >= idMath::FLT_EPSILON ) { + for ( i = 0; i < V.GetNumRows(); i++ ) { + sum = 0.0f; + for ( j = 0; j < numColumns; j++ ) { + sum += (*this)[r][j] * V[i][j]; + } + m[r][i] = sum * w[r]; + } + } else { + for ( i = 0; i < V.GetNumRows(); i++ ) { + m[r][i] = 0.0f; + } + } + } +} + +/* +============ +idMatX::Cholesky_Factor + + in-place Cholesky factorization: LL' + L is a triangular matrix stored in the lower triangle. + The upper triangle is not cleared. + The initial matrix has to be symmetric positive definite. +============ +*/ +bool idMatX::Cholesky_Factor() { + int i, j, k; + float *invSqrt; + double sum; + + assert( numRows == numColumns ); + + invSqrt = (float *) _alloca16( numRows * sizeof( float ) ); + + for ( i = 0; i < numRows; i++ ) { + + for ( j = 0; j < i; j++ ) { + + sum = (*this)[i][j]; + for ( k = 0; k < j; k++ ) { + sum -= (*this)[i][k] * (*this)[j][k]; + } + (*this)[i][j] = sum * invSqrt[j]; + } + + sum = (*this)[i][i]; + for ( k = 0; k < i; k++ ) { + sum -= (*this)[i][k] * (*this)[i][k]; + } + + if ( sum <= 0.0f ) { + return false; + } + + invSqrt[i] = idMath::InvSqrt( sum ); + (*this)[i][i] = invSqrt[i] * sum; + } + return true; +} + +/* +============ +idMatX::Cholesky_UpdateRankOne + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: LL' + alpha * v * v' + If offset > 0 only the lower right corner starting at (offset, offset) is updated. +============ +*/ +bool idMatX::Cholesky_UpdateRankOne( const idVecX &v, float alpha, int offset ) { + int i, j; + float *y; + double diag, invDiag, diagSqr, newDiag, newDiagSqr, beta, p, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( offset >= 0 && offset < numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + memcpy( y, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + + for ( i = offset; i < numColumns; i++ ) { + p = y[i]; + diag = (*this)[i][i]; + invDiag = 1.0f / diag; + diagSqr = diag * diag; + newDiagSqr = diagSqr + alpha * p * p; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + (*this)[i][i] = newDiag = idMath::Sqrt( newDiagSqr ); + + alpha /= newDiagSqr; + beta = p * alpha; + alpha *= diagSqr; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i] * invDiag; + + y[j] -= p * d; + d += beta * y[j]; + + (*this)[j][i] = d * newDiag; + } + } + return true; +} + +/* +============ +idMatX::Cholesky_UpdateRowColumn + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LL' + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +bool idMatX::Cholesky_UpdateRowColumn( const idVecX &v, int r ) { + int i, j; + double sum; + float *original, *y; + idVecX addSub; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + addSub.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + if ( r == 0 ) { + + if ( numColumns == 1 ) { + double v0 = v[0]; + sum = (*this)[0][0]; + sum = sum * sum; + sum = sum + v0; + if ( sum <= 0.0f ) { + return false; + } + (*this)[0][0] = idMath::Sqrt( sum ); + return true; + } + for ( i = 0; i < numColumns; i++ ) { + addSub[i] = v[i]; + } + + } else { + + original = (float *) _alloca16( numColumns * sizeof( float ) ); + y = (float *) _alloca16( numColumns * sizeof( float ) ); + + // calculate original row/column of matrix + for ( i = 0; i < numRows; i++ ) { + sum = 0.0f; + for ( j = 0; j <= i; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + original[i] = sum; + } + + // solve for y in L * y = original + v + for ( i = 0; i < r; i++ ) { + sum = original[i] + v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[r][j] * (*this)[i][j]; + } + (*this)[r][i] = sum / (*this)[i][i]; + } + + // if the last row/column of the matrix is updated + if ( r == numColumns - 1 ) { + // only calculate new diagonal + sum = original[r] + v[r]; + for ( j = 0; j < r; j++) { + sum -= (*this)[r][j] * (*this)[r][j]; + } + if ( sum <= 0.0f ) { + return false; + } + (*this)[r][r] = idMath::Sqrt( sum ); + return true; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + sum = 0.0f; + for ( j = 0; j <= r; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + addSub[i] = v[i] - ( sum - original[i] ); + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + +#if 0 + + idVecX v1, v2; + double d; + + v1.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + v2.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + // update + if ( !Cholesky_UpdateRankOne( v1, 1.0f, r ) ) { + return false; + } + // downdate + if ( !Cholesky_UpdateRankOne( v2, -1.0f, r ) ) { + return false; + } + +#else + + float *v1, *v2; + double diag, invDiag, diagSqr, newDiag, newDiagSqr; + double alpha1, alpha2, beta1, beta2, p1, p2, d; + + v1 = (float *) _alloca16( numColumns * sizeof( float ) ); + v2 = (float *) _alloca16( numColumns * sizeof( float ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + p1 = v1[i]; + diag = (*this)[i][i]; + invDiag = 1.0f / diag; + diagSqr = diag * diag; + newDiagSqr = diagSqr + alpha1 * p1 * p1; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + alpha1 /= newDiagSqr; + beta1 = p1 * alpha1; + alpha1 *= diagSqr; + + p2 = v2[i]; + diagSqr = newDiagSqr; + newDiagSqr = diagSqr + alpha2 * p2 * p2; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + (*this)[i][i] = newDiag = idMath::Sqrt( newDiagSqr ); + + alpha2 /= newDiagSqr; + beta2 = p2 * alpha2; + alpha2 *= diagSqr; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i] * invDiag; + + v1[j] -= p1 * d; + d += beta1 * v1[j]; + + v2[j] -= p2 * d; + d += beta2 * v2[j]; + + (*this)[j][i] = d * newDiag; + } + } + +#endif + + return true; +} + +/* +============ +idMatX::Cholesky_UpdateIncrement + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +bool idMatX::Cholesky_UpdateIncrement( const idVecX &v ) { + int i, j; + float *x; + double sum; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + x = (float *) _alloca16( numRows * sizeof( float ) ); + + // solve for x in L * x = v + for ( i = 0; i < numRows - 1; i++ ) { + sum = v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } + + // calculate new row of L and calculate the square of the diagonal entry + sum = v[numRows - 1]; + for ( i = 0; i < numRows - 1; i++ ) { + (*this)[numRows - 1][i] = x[i]; + sum -= x[i] * x[i]; + } + + if ( sum <= 0.0f ) { + return false; + } + + // store the diagonal entry + (*this)[numRows - 1][numRows - 1] = idMath::Sqrt( sum ); + + return true; +} + +/* +============ +idMatX::Cholesky_UpdateDecrement + + Updates the in-place Cholesky factorization to obtain the factors for the matrix with row r and column r removed. + v should store the row of the original matrix. +============ +*/ +bool idMatX::Cholesky_UpdateDecrement( const idVecX &v, int r ) { + idVecX v1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + v1[r] += 1.0f; + + // NOTE: msvc compiler bug: the this pointer stored in edi is expected to stay + // untouched when calling Cholesky_UpdateRowColumn in the if statement +#if 0 + if ( !Cholesky_UpdateRowColumn( v1, r ) ) { +#else + bool ret = Cholesky_UpdateRowColumn( v1, r ); + if ( !ret ) { +#endif + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::Cholesky_Solve + + Solve Ax = b with A factored in-place as: LL' +============ +*/ +void idMatX::Cholesky_Solve( idVecX &x, const idVecX &b ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } + + // solve Lt + for ( i = numRows - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[j][i] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } +} + +/* +============ +idMatX::Cholesky_Inverse + + Calculates the inverse of the matrix which is factored in-place as: LL' +============ +*/ +void idMatX::Cholesky_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + Cholesky_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::Cholesky_MultiplyFactors + + Multiplies the factors of the in-place Cholesky factorization to form the original matrix. +============ +*/ +void idMatX::Cholesky_MultiplyFactors( idMatX &m ) const { + int r, i, j; + double sum; + + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + // calculate row of matrix + for ( i = 0; i < numRows; i++ ) { + sum = 0.0f; + for ( j = 0; j <= i && j <= r; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + m[r][i] = sum; + } + } +} + +/* +============ +idMatX::LDLT_Factor + + in-place factorization: LDL' + L is a triangular matrix stored in the lower triangle. + L has ones on the diagonal that are not stored. + D is a diagonal matrix stored on the diagonal. + The upper triangle is not cleared. + The initial matrix has to be symmetric. +============ +*/ +bool idMatX::LDLT_Factor() { + int i, j, k; + float *v; + double d, sum; + + assert( numRows == numColumns ); + + v = (float *) _alloca16( numRows * sizeof( float ) ); + + for ( i = 0; i < numRows; i++ ) { + + sum = (*this)[i][i]; + for ( j = 0; j < i; j++ ) { + d = (*this)[i][j]; + v[j] = (*this)[j][j] * d; + sum -= v[j] * d; + } + + if ( sum == 0.0f ) { + return false; + } + + (*this)[i][i] = sum; + d = 1.0f / sum; + + for ( j = i + 1; j < numRows; j++ ) { + sum = (*this)[j][i]; + for ( k = 0; k < i; k++ ) { + sum -= (*this)[j][k] * v[k]; + } + (*this)[j][i] = sum * d; + } + } + + return true; +} + +/* +============ +idMatX::LDLT_UpdateRankOne + + Updates the in-place LDL' factorization to obtain the factors for the matrix: LDL' + alpha * v * v' + If offset > 0 only the lower right corner starting at (offset, offset) is updated. +============ +*/ +bool idMatX::LDLT_UpdateRankOne( const idVecX &v, float alpha, int offset ) { + int i, j; + float *y; + double diag, newDiag, beta, p, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( offset >= 0 && offset < numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + memcpy( y, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + + for ( i = offset; i < numColumns; i++ ) { + p = y[i]; + diag = (*this)[i][i]; + (*this)[i][i] = newDiag = diag + alpha * p * p; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha /= newDiag; + beta = p * alpha; + alpha *= diag; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y[j] -= p * d; + d += beta * y[j]; + + (*this)[j][i] = d; + } + } + + return true; +} + +/* +============ +idMatX::LDLT_UpdateRowColumn + + Updates the in-place LDL' factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LDL' + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +bool idMatX::LDLT_UpdateRowColumn( const idVecX &v, int r ) { + int i, j; + double sum; + float *original, *y; + idVecX addSub; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + addSub.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + if ( r == 0 ) { + + if ( numColumns == 1 ) { + (*this)[0][0] += v[0]; + return true; + } + for ( i = 0; i < numColumns; i++ ) { + addSub[i] = v[i]; + } + + } else { + + original = (float *) _alloca16( numColumns * sizeof( float ) ); + y = (float *) _alloca16( numColumns * sizeof( float ) ); + + // calculate original row/column of matrix + for ( i = 0; i < r; i++ ) { + y[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = 0; i < numColumns; i++ ) { + if ( i < r ) { + sum = (*this)[i][i] * (*this)[r][i]; + } else if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < i && j < r; j++ ) { + sum += (*this)[i][j] * y[j]; + } + original[i] = sum; + } + + // solve for y in L * y = original + v + for ( i = 0; i < r; i++ ) { + sum = original[i] + v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * y[j]; + } + y[i] = sum; + } + + // calculate new row of L + for ( i = 0; i < r; i++ ) { + (*this)[r][i] = y[i] / (*this)[i][i]; + } + + // if the last row/column of the matrix is updated + if ( r == numColumns - 1 ) { + // only calculate new diagonal + sum = original[r] + v[r]; + for ( j = 0; j < r; j++ ) { + sum -= (*this)[r][j] * y[j]; + } + if ( sum == 0.0f ) { + return false; + } + (*this)[r][r] = sum; + return true; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = 0; i < r; i++ ) { + y[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = r; i < numColumns; i++ ) { + if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < r; j++ ) { + sum += (*this)[i][j] * y[j]; + } + addSub[i] = v[i] - ( sum - original[i] ); + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + +#if 0 + + idVecX v1, v2; + double d; + + v1.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + v2.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + // update + if ( !LDLT_UpdateRankOne( v1, 1.0f, r ) ) { + return false; + } + // downdate + if ( !LDLT_UpdateRankOne( v2, -1.0f, r ) ) { + return false; + } + +#else + + float *v1, *v2; + double d, diag, newDiag, p1, p2, alpha1, alpha2, beta1, beta2; + + v1 = (float *) _alloca16( numColumns * sizeof( float ) ); + v2 = (float *) _alloca16( numColumns * sizeof( float ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + + diag = (*this)[i][i]; + p1 = v1[i]; + newDiag = diag + alpha1 * p1 * p1; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha1 /= newDiag; + beta1 = p1 * alpha1; + alpha1 *= diag; + + diag = newDiag; + p2 = v2[i]; + newDiag = diag + alpha2 * p2 * p2; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha2 /= newDiag; + beta2 = p2 * alpha2; + alpha2 *= diag; + + (*this)[i][i] = newDiag; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + v1[j] -= p1 * d; + d += beta1 * v1[j]; + + v2[j] -= p2 * d; + d += beta2 * v2[j]; + + (*this)[j][i] = d; + } + } + +#endif + + return true; +} + +/* +============ +idMatX::LDLT_UpdateIncrement + + Updates the in-place LDL' factorization to obtain the factors for the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +bool idMatX::LDLT_UpdateIncrement( const idVecX &v ) { + int i, j; + float *x; + double sum, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + x = (float *) _alloca16( numRows * sizeof( float ) ); + + // solve for x in L * x = v + for ( i = 0; i < numRows - 1; i++ ) { + sum = v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // calculate new row of L and calculate the diagonal entry + sum = v[numRows - 1]; + for ( i = 0; i < numRows - 1; i++ ) { + (*this)[numRows - 1][i] = d = x[i] / (*this)[i][i]; + sum -= d * x[i]; + } + + if ( sum == 0.0f ) { + return false; + } + + // store the diagonal entry + (*this)[numRows - 1][numRows - 1] = sum; + + return true; +} + +/* +============ +idMatX::LDLT_UpdateDecrement + + Updates the in-place LDL' factorization to obtain the factors for the matrix with row r and column r removed. + v should store the row of the original matrix. +============ +*/ +bool idMatX::LDLT_UpdateDecrement( const idVecX &v, int r ) { + idVecX v1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + v1[r] += 1.0f; + + // NOTE: msvc compiler bug: the this pointer stored in edi is expected to stay + // untouched when calling LDLT_UpdateRowColumn in the if statement +#if 0 + if ( !LDLT_UpdateRowColumn( v1, r ) ) { +#else + bool ret = LDLT_UpdateRowColumn( v1, r ); + if ( !ret ) { +#endif + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::LDLT_Solve + + Solve Ax = b with A factored in-place as: LDL' +============ +*/ +void idMatX::LDLT_Solve( idVecX &x, const idVecX &b ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // solve D + for ( i = 0; i < numRows; i++ ) { + x[i] /= (*this)[i][i]; + } + + // solve Lt + for ( i = numRows - 2; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[j][i] * x[j]; + } + x[i] = sum; + } +} + +/* +============ +idMatX::LDLT_Inverse + + Calculates the inverse of the matrix which is factored in-place as: LDL' +============ +*/ +void idMatX::LDLT_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + LDLT_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::LDLT_UnpackFactors + + Unpacks the in-place LDL' factorization. +============ +*/ +void idMatX::LDLT_UnpackFactors( idMatX &L, idMatX &D ) const { + int i, j; + + L.Zero( numRows, numColumns ); + D.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < i; j++ ) { + L[i][j] = (*this)[i][j]; + } + L[i][i] = 1.0f; + D[i][i] = (*this)[i][i]; + } +} + +/* +============ +idMatX::LDLT_MultiplyFactors + + Multiplies the factors of the in-place LDL' factorization to form the original matrix. +============ +*/ +void idMatX::LDLT_MultiplyFactors( idMatX &m ) const { + int r, i, j; + float *v; + double sum; + + v = (float *) _alloca16( numRows * sizeof( float ) ); + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + // calculate row of matrix + for ( i = 0; i < r; i++ ) { + v[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = 0; i < numColumns; i++ ) { + if ( i < r ) { + sum = (*this)[i][i] * (*this)[r][i]; + } else if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < i && j < r; j++ ) { + sum += (*this)[i][j] * v[j]; + } + m[r][i] = sum; + } + } +} + +/* +============ +idMatX::TriDiagonal_ClearTriangles +============ +*/ +void idMatX::TriDiagonal_ClearTriangles() { + int i, j; + + assert( numRows == numColumns ); + for ( i = 0; i < numRows-2; i++ ) { + for ( j = i+2; j < numColumns; j++ ) { + (*this)[i][j] = 0.0f; + (*this)[j][i] = 0.0f; + } + } +} + +/* +============ +idMatX::TriDiagonal_Solve + + Solve Ax = b with A being tridiagonal. +============ +*/ +bool idMatX::TriDiagonal_Solve( idVecX &x, const idVecX &b ) const { + int i; + float d; + idVecX tmp; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + tmp.SetData( numRows, VECX_ALLOCA( numRows ) ); + + d = (*this)[0][0]; + if ( d == 0.0f ) { + return false; + } + d = 1.0f / d; + x[0] = b[0] * d; + for ( i = 1; i < numRows; i++ ) { + tmp[i] = (*this)[i-1][i] * d; + d = (*this)[i][i] - (*this)[i][i-1] * tmp[i]; + if ( d == 0.0f ) { + return false; + } + d = 1.0f / d; + x[i] = ( b[i] - (*this)[i][i-1] * x[i-1] ) * d; + } + for ( i = numRows - 2; i >= 0; i-- ) { + x[i] -= tmp[i+1] * x[i+1]; + } + return true; +} + +/* +============ +idMatX::TriDiagonal_Inverse + + Calculates the inverse of a tri-diagonal matrix. +============ +*/ +void idMatX::TriDiagonal_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + TriDiagonal_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::HouseholderReduction + + Householder reduction to symmetric tri-diagonal form. + The original matrix is replaced by an orthogonal matrix effecting the accumulated householder transformations. + The diagonal elements of the diagonal matrix are stored in diag. + The off-diagonal elements of the diagonal matrix are stored in subd. + The initial matrix has to be symmetric. +============ +*/ +void idMatX::HouseholderReduction( idVecX &diag, idVecX &subd ) { + int i0, i1, i2, i3; + float h, f, g, invH, halfFdivH, scale, invScale, sum; + + assert( numRows == numColumns ); + + diag.SetSize( numRows ); + subd.SetSize( numRows ); + + for ( i0 = numRows-1, i3 = numRows-2; i0 >= 1; i0--, i3-- ) { + h = 0.0f; + scale = 0.0f; + + if ( i3 > 0 ) { + for ( i2 = 0; i2 <= i3; i2++ ) { + scale += idMath::Fabs( (*this)[i0][i2] ); + } + if ( scale == 0 ) { + subd[i0] = (*this)[i0][i3]; + } else { + invScale = 1.0f / scale; + for (i2 = 0; i2 <= i3; i2++) + { + (*this)[i0][i2] *= invScale; + h += (*this)[i0][i2] * (*this)[i0][i2]; + } + f = (*this)[i0][i3]; + g = idMath::Sqrt( h ); + if ( f > 0.0f ) { + g = -g; + } + subd[i0] = scale * g; + h -= f * g; + (*this)[i0][i3] = f - g; + f = 0.0f; + invH = 1.0f / h; + for (i1 = 0; i1 <= i3; i1++) { + (*this)[i1][i0] = (*this)[i0][i1] * invH; + g = 0.0f; + for (i2 = 0; i2 <= i1; i2++) { + g += (*this)[i1][i2] * (*this)[i0][i2]; + } + for (i2 = i1+1; i2 <= i3; i2++) { + g += (*this)[i2][i1] * (*this)[i0][i2]; + } + subd[i1] = g * invH; + f += subd[i1] * (*this)[i0][i1]; + } + halfFdivH = 0.5f * f * invH; + for ( i1 = 0; i1 <= i3; i1++ ) { + f = (*this)[i0][i1]; + g = subd[i1] - halfFdivH * f; + subd[i1] = g; + for ( i2 = 0; i2 <= i1; i2++ ) { + (*this)[i1][i2] -= f * subd[i2] + g * (*this)[i0][i2]; + } + } + } + } else { + subd[i0] = (*this)[i0][i3]; + } + + diag[i0] = h; + } + + diag[0] = 0.0f; + subd[0] = 0.0f; + for ( i0 = 0, i3 = -1; i0 <= numRows-1; i0++, i3++ ) { + if ( diag[i0] ) { + for ( i1 = 0; i1 <= i3; i1++ ) { + sum = 0.0f; + for (i2 = 0; i2 <= i3; i2++) { + sum += (*this)[i0][i2] * (*this)[i2][i1]; + } + for ( i2 = 0; i2 <= i3; i2++ ) { + (*this)[i2][i1] -= sum * (*this)[i2][i0]; + } + } + } + diag[i0] = (*this)[i0][i0]; + (*this)[i0][i0] = 1.0f; + for ( i1 = 0; i1 <= i3; i1++ ) { + (*this)[i1][i0] = 0.0f; + (*this)[i0][i1] = 0.0f; + } + } + + // re-order + for ( i0 = 1, i3 = 0; i0 < numRows; i0++, i3++ ) { + subd[i3] = subd[i0]; + } + subd[numRows-1] = 0.0f; +} + +/* +============ +idMatX::QL + + QL algorithm with implicit shifts to determine the eigenvalues and eigenvectors of a symmetric tri-diagonal matrix. + diag contains the diagonal elements of the symmetric tri-diagonal matrix on input and is overwritten with the eigenvalues. + subd contains the off-diagonal elements of the symmetric tri-diagonal matrix and is destroyed. + This matrix has to be either the identity matrix to determine the eigenvectors for a symmetric tri-diagonal matrix, + or the matrix returned by the Householder reduction to determine the eigenvalues for the original symmetric matrix. +============ +*/ +bool idMatX::QL( idVecX &diag, idVecX &subd ) { + const int maxIter = 32; + int i0, i1, i2, i3; + float a, b, f, g, r, p, s, c; + + assert( numRows == numColumns ); + + for ( i0 = 0; i0 < numRows; i0++ ) { + for ( i1 = 0; i1 < maxIter; i1++ ) { + for ( i2 = i0; i2 <= numRows - 2; i2++ ) { + a = idMath::Fabs( diag[i2] ) + idMath::Fabs( diag[i2+1] ); + if ( idMath::Fabs( subd[i2] ) + a == a ) { + break; + } + } + if ( i2 == i0 ) { + break; + } + + g = ( diag[i0+1] - diag[i0] ) / ( 2.0f * subd[i0] ); + r = idMath::Sqrt( g * g + 1.0f ); + if ( g < 0.0f ) { + g = diag[i2] - diag[i0] + subd[i0] / ( g - r ); + } else { + g = diag[i2] - diag[i0] + subd[i0] / ( g + r ); + } + s = 1.0f; + c = 1.0f; + p = 0.0f; + for ( i3 = i2 - 1; i3 >= i0; i3-- ) { + f = s * subd[i3]; + b = c * subd[i3]; + if ( idMath::Fabs( f ) >= idMath::Fabs( g ) ) { + c = g / f; + r = idMath::Sqrt( c * c + 1.0f ); + subd[i3+1] = f * r; + s = 1.0f / r; + c *= s; + } else { + s = f / g; + r = idMath::Sqrt( s * s + 1.0f ); + subd[i3+1] = g * r; + c = 1.0f / r; + s *= c; + } + g = diag[i3+1] - p; + r = ( diag[i3] - g ) * s + 2.0f * b * c; + p = s * r; + diag[i3+1] = g + p; + g = c * r - b; + + for ( int i4 = 0; i4 < numRows; i4++ ) { + f = (*this)[i4][i3+1]; + (*this)[i4][i3+1] = s * (*this)[i4][i3] + c * f; + (*this)[i4][i3] = c * (*this)[i4][i3] - s * f; + } + } + diag[i0] -= p; + subd[i0] = g; + subd[i2] = 0.0f; + } + if ( i1 == maxIter ) { + return false; + } + } + return true; +} + +/* +============ +idMatX::Eigen_SolveSymmetricTriDiagonal + + Determine eigen values and eigen vectors for a symmetric tri-diagonal matrix. + The eigen values are stored in 'eigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the eigenValues[i]. + The initial matrix has to be symmetric tri-diagonal. +============ +*/ +bool idMatX::Eigen_SolveSymmetricTriDiagonal( idVecX &eigenValues ) { + int i; + idVecX subd; + + assert( numRows == numColumns ); + + subd.SetData( numRows, VECX_ALLOCA( numRows ) ); + eigenValues.SetSize( numRows ); + + for ( i = 0; i < numRows-1; i++ ) { + eigenValues[i] = (*this)[i][i]; + subd[i] = (*this)[i+1][i]; + } + eigenValues[numRows-1] = (*this)[numRows-1][numRows-1]; + + Identity(); + + return QL( eigenValues, subd ); +} + +/* +============ +idMatX::Eigen_SolveSymmetric + + Determine eigen values and eigen vectors for a symmetric matrix. + The eigen values are stored in 'eigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the eigenValues[i]. + The initial matrix has to be symmetric. +============ +*/ +bool idMatX::Eigen_SolveSymmetric( idVecX &eigenValues ) { + idVecX subd; + + assert( numRows == numColumns ); + + subd.SetData( numRows, VECX_ALLOCA( numRows ) ); + eigenValues.SetSize( numRows ); + + HouseholderReduction( eigenValues, subd ); + return QL( eigenValues, subd ); +} + +/* +============ +idMatX::HessenbergReduction + + Reduction to Hessenberg form. +============ +*/ +void idMatX::HessenbergReduction( idMatX &H ) { + int i, j, m; + int low = 0; + int high = numRows - 1; + float scale, f, g, h; + idVecX v; + + v.SetData( numRows, VECX_ALLOCA( numRows ) ); + + for ( m = low + 1; m <= high - 1; m++ ) { + + scale = 0.0f; + for ( i = m; i <= high; i++ ) { + scale = scale + idMath::Fabs( H[i][m-1] ); + } + if ( scale != 0.0f ) { + + // compute Householder transformation. + h = 0.0f; + for ( i = high; i >= m; i-- ) { + v[i] = H[i][m-1] / scale; + h += v[i] * v[i]; + } + g = idMath::Sqrt( h ); + if ( v[m] > 0.0f ) { + g = -g; + } + h = h - v[m] * g; + v[m] = v[m] - g; + + // apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for ( j = m; j < numRows; j++) { + f = 0.0f; + for ( i = high; i >= m; i-- ) { + f += v[i] * H[i][j]; + } + f = f / h; + for ( i = m; i <= high; i++ ) { + H[i][j] -= f * v[i]; + } + } + + for ( i = 0; i <= high; i++ ) { + f = 0.0f; + for ( j = high; j >= m; j-- ) { + f += v[j] * H[i][j]; + } + f = f / h; + for ( j = m; j <= high; j++ ) { + H[i][j] -= f * v[j]; + } + } + v[m] = scale * v[m]; + H[m][m-1] = scale * g; + } + } + + // accumulate transformations + Identity(); + for ( int m = high - 1; m >= low + 1; m-- ) { + if ( H[m][m-1] != 0.0f ) { + for ( i = m + 1; i <= high; i++ ) { + v[i] = H[i][m-1]; + } + for ( j = m; j <= high; j++ ) { + g = 0.0f; + for ( i = m; i <= high; i++ ) { + g += v[i] * (*this)[i][j]; + } + // float division to avoid possible underflow + g = ( g / v[m] ) / H[m][m-1]; + for ( i = m; i <= high; i++ ) { + (*this)[i][j] += g * v[i]; + } + } + } + } +} + +/* +============ +idMatX::ComplexDivision + + Complex scalar division. +============ +*/ +void idMatX::ComplexDivision( float xr, float xi, float yr, float yi, float &cdivr, float &cdivi ) { + float r, d; + if ( idMath::Fabs( yr ) > idMath::Fabs( yi ) ) { + r = yi / yr; + d = yr + r * yi; + cdivr = ( xr + r * xi ) / d; + cdivi = ( xi - r * xr ) / d; + } else { + r = yr / yi; + d = yi + r * yr; + cdivr = ( r * xr + xi ) / d; + cdivi = ( r * xi - xr ) / d; + } +} + +/* +============ +idMatX::HessenbergToRealSchur + + Reduction from Hessenberg to real Schur form. +============ +*/ +bool idMatX::HessenbergToRealSchur( idMatX &H, idVecX &realEigenValues, idVecX &imaginaryEigenValues ) { + int i, j, k; + int n = numRows - 1; + int low = 0; + int high = numRows - 1; + float eps = 2e-16f, exshift = 0.0f; + float p = 0.0f, q = 0.0f, r = 0.0f, s = 0.0f, z = 0.0f, t, w, x, y; + + // store roots isolated by balanc and compute matrix norm + float norm = 0.0f; + for ( i = 0; i < numRows; i++ ) { + if ( i < low || i > high ) { + realEigenValues[i] = H[i][i]; + imaginaryEigenValues[i] = 0.0f; + } + for ( j = Max( i - 1, 0 ); j < numRows; j++ ) { + norm = norm + idMath::Fabs( H[i][j] ); + } + } + + int iter = 0; + while( n >= low ) { + + // look for single small sub-diagonal element + int l = n; + while ( l > low ) { + s = idMath::Fabs( H[l-1][l-1] ) + idMath::Fabs( H[l][l] ); + if ( s == 0.0f ) { + s = norm; + } + if ( idMath::Fabs( H[l][l-1] ) < eps * s ) { + break; + } + l--; + } + + // check for convergence + if ( l == n ) { // one root found + H[n][n] = H[n][n] + exshift; + realEigenValues[n] = H[n][n]; + imaginaryEigenValues[n] = 0.0f; + n--; + iter = 0; + } else if ( l == n-1 ) { // two roots found + w = H[n][n-1] * H[n-1][n]; + p = ( H[n-1][n-1] - H[n][n] ) / 2.0f; + q = p * p + w; + z = idMath::Sqrt( idMath::Fabs( q ) ); + H[n][n] = H[n][n] + exshift; + H[n-1][n-1] = H[n-1][n-1] + exshift; + x = H[n][n]; + + if ( q >= 0.0f ) { // real pair + if ( p >= 0.0f ) { + z = p + z; + } else { + z = p - z; + } + realEigenValues[n-1] = x + z; + realEigenValues[n] = realEigenValues[n-1]; + if ( z != 0.0f ) { + realEigenValues[n] = x - w / z; + } + imaginaryEigenValues[n-1] = 0.0f; + imaginaryEigenValues[n] = 0.0f; + x = H[n][n-1]; + s = idMath::Fabs( x ) + idMath::Fabs( z ); + p = x / s; + q = z / s; + r = idMath::Sqrt( p * p + q * q ); + p = p / r; + q = q / r; + + // modify row + for ( j = n-1; j < numRows; j++ ) { + z = H[n-1][j]; + H[n-1][j] = q * z + p * H[n][j]; + H[n][j] = q * H[n][j] - p * z; + } + + // modify column + for ( i = 0; i <= n; i++ ) { + z = H[i][n-1]; + H[i][n-1] = q * z + p * H[i][n]; + H[i][n] = q * H[i][n] - p * z; + } + + // accumulate transformations + for ( i = low; i <= high; i++ ) { + z = (*this)[i][n-1]; + (*this)[i][n-1] = q * z + p * (*this)[i][n]; + (*this)[i][n] = q * (*this)[i][n] - p * z; + } + } else { // complex pair + realEigenValues[n-1] = x + p; + realEigenValues[n] = x + p; + imaginaryEigenValues[n-1] = z; + imaginaryEigenValues[n] = -z; + } + n = n - 2; + iter = 0; + + } else { // no convergence yet + + // form shift + x = H[n][n]; + y = 0.0f; + w = 0.0f; + if ( l < n ) { + y = H[n-1][n-1]; + w = H[n][n-1] * H[n-1][n]; + } + + // Wilkinson's original ad hoc shift + if ( iter == 10 ) { + exshift += x; + for ( i = low; i <= n; i++ ) { + H[i][i] -= x; + } + s = idMath::Fabs( H[n][n-1] ) + idMath::Fabs( H[n-1][n-2] ); + x = y = 0.75f * s; + w = -0.4375f * s * s; + } + + // new ad hoc shift + if ( iter == 30 ) { + s = ( y - x ) / 2.0f; + s = s * s + w; + if ( s > 0 ) { + s = idMath::Sqrt( s ); + if ( y < x ) { + s = -s; + } + s = x - w / ( ( y - x ) / 2.0f + s ); + for ( i = low; i <= n; i++ ) { + H[i][i] -= s; + } + exshift += s; + x = y = w = 0.964f; + } + } + + iter = iter + 1; + + // look for two consecutive small sub-diagonal elements + int m; + for( m = n-2; m >= l; m-- ) { + z = H[m][m]; + r = x - z; + s = y - z; + p = ( r * s - w ) / H[m+1][m] + H[m][m+1]; + q = H[m+1][m+1] - z - r - s; + r = H[m+2][m+1]; + s = idMath::Fabs( p ) + idMath::Fabs( q ) + idMath::Fabs( r ); + p = p / s; + q = q / s; + r = r / s; + if ( m == l ) { + break; + } + if ( idMath::Fabs( H[m][m-1] ) * ( idMath::Fabs( q ) + idMath::Fabs( r ) ) < + eps * ( idMath::Fabs( p ) * ( idMath::Fabs( H[m-1][m-1] ) + idMath::Fabs( z ) + idMath::Fabs( H[m+1][m+1] ) ) ) ) { + break; + } + } + + for ( i = m+2; i <= n; i++ ) { + H[i][i-2] = 0.0f; + if ( i > m+2 ) { + H[i][i-3] = 0.0f; + } + } + + // double QR step involving rows l:n and columns m:n + for ( k = m; k <= n-1; k++ ) { + bool notlast = ( k != n-1 ); + if ( k != m ) { + p = H[k][k-1]; + q = H[k+1][k-1]; + r = ( notlast ? H[k+2][k-1] : 0.0f ); + x = idMath::Fabs( p ) + idMath::Fabs( q ) + idMath::Fabs( r ); + if ( x != 0.0f ) { + p = p / x; + q = q / x; + r = r / x; + } + } + if ( x == 0.0f ) { + break; + } + s = idMath::Sqrt( p * p + q * q + r * r ); + if ( p < 0.0f ) { + s = -s; + } + if ( s != 0.0f ) { + if ( k != m ) { + H[k][k-1] = -s * x; + } else if ( l != m ) { + H[k][k-1] = -H[k][k-1]; + } + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // modify row + for ( j = k; j < numRows; j++ ) { + p = H[k][j] + q * H[k+1][j]; + if ( notlast ) { + p = p + r * H[k+2][j]; + H[k+2][j] = H[k+2][j] - p * z; + } + H[k][j] = H[k][j] - p * x; + H[k+1][j] = H[k+1][j] - p * y; + } + + // modify column + for ( i = 0; i <= Min( n, k + 3 ); i++ ) { + p = x * H[i][k] + y * H[i][k+1]; + if ( notlast ) { + p = p + z * H[i][k+2]; + H[i][k+2] = H[i][k+2] - p * r; + } + H[i][k] = H[i][k] - p; + H[i][k+1] = H[i][k+1] - p * q; + } + + // accumulate transformations + for ( i = low; i <= high; i++ ) { + p = x * (*this)[i][k] + y * (*this)[i][k+1]; + if ( notlast ) { + p = p + z * (*this)[i][k+2]; + (*this)[i][k+2] = (*this)[i][k+2] - p * r; + } + (*this)[i][k] = (*this)[i][k] - p; + (*this)[i][k+1] = (*this)[i][k+1] - p * q; + } + } + } + } + } + + // backsubstitute to find vectors of upper triangular form + if ( norm == 0.0f ) { + return false; + } + + for ( n = numRows-1; n >= 0; n-- ) { + p = realEigenValues[n]; + q = imaginaryEigenValues[n]; + + if ( q == 0.0f ) { // real vector + int l = n; + H[n][n] = 1.0f; + for ( i = n-1; i >= 0; i-- ) { + w = H[i][i] - p; + r = 0.0f; + for ( j = l; j <= n; j++ ) { + r = r + H[i][j] * H[j][n]; + } + if ( imaginaryEigenValues[i] < 0.0f ) { + z = w; + s = r; + } else { + l = i; + if ( imaginaryEigenValues[i] == 0.0f ) { + if ( w != 0.0f ) { + H[i][n] = -r / w; + } else { + H[i][n] = -r / ( eps * norm ); + } + } else { // solve real equations + x = H[i][i+1]; + y = H[i+1][i]; + q = ( realEigenValues[i] - p ) * ( realEigenValues[i] - p ) + imaginaryEigenValues[i] * imaginaryEigenValues[i]; + t = ( x * s - z * r ) / q; + H[i][n] = t; + if ( idMath::Fabs(x) > idMath::Fabs( z ) ) { + H[i+1][n] = ( -r - w * t ) / x; + } else { + H[i+1][n] = ( -s - y * t ) / z; + } + } + + // overflow control + t = idMath::Fabs(H[i][n]); + if ( ( eps * t ) * t > 1 ) { + for ( j = i; j <= n; j++ ) { + H[j][n] = H[j][n] / t; + } + } + } + } + } else if ( q < 0.0f ) { // complex vector + int l = n-1; + + // last vector component imaginary so matrix is triangular + if ( idMath::Fabs( H[n][n-1] ) > idMath::Fabs( H[n-1][n] ) ) { + H[n-1][n-1] = q / H[n][n-1]; + H[n-1][n] = -( H[n][n] - p ) / H[n][n-1]; + } else { + ComplexDivision( 0.0f, -H[n-1][n], H[n-1][n-1]-p, q, H[n-1][n-1], H[n-1][n] ); + } + H[n][n-1] = 0.0f; + H[n][n] = 1.0f; + for ( i = n-2; i >= 0; i-- ) { + float ra, sa, vr, vi; + ra = 0.0f; + sa = 0.0f; + for ( j = l; j <= n; j++ ) { + ra = ra + H[i][j] * H[j][n-1]; + sa = sa + H[i][j] * H[j][n]; + } + w = H[i][i] - p; + + if ( imaginaryEigenValues[i] < 0.0f ) { + z = w; + r = ra; + s = sa; + } else { + l = i; + if ( imaginaryEigenValues[i] == 0.0f ) { + ComplexDivision( -ra, -sa, w, q, H[i][n-1], H[i][n] ); + } else { + // solve complex equations + x = H[i][i+1]; + y = H[i+1][i]; + vr = ( realEigenValues[i] - p ) * ( realEigenValues[i] - p ) + imaginaryEigenValues[i] * imaginaryEigenValues[i] - q * q; + vi = ( realEigenValues[i] - p ) * 2.0f * q; + if ( vr == 0.0f && vi == 0.0f ) { + vr = eps * norm * ( idMath::Fabs( w ) + idMath::Fabs( q ) + idMath::Fabs( x ) + idMath::Fabs( y ) + idMath::Fabs( z ) ); + } + ComplexDivision( x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi, H[i][n-1], H[i][n] ); + if ( idMath::Fabs( x ) > ( idMath::Fabs( z ) + idMath::Fabs( q ) ) ) { + H[i+1][n-1] = ( -ra - w * H[i][n-1] + q * H[i][n] ) / x; + H[i+1][n] = ( -sa - w * H[i][n] - q * H[i][n-1] ) / x; + } else { + ComplexDivision( -r - y * H[i][n-1], -s - y * H[i][n], z, q, H[i+1][n-1], H[i+1][n] ); + } + } + + // overflow control + t = Max( idMath::Fabs( H[i][n-1] ), idMath::Fabs( H[i][n] ) ); + if ( ( eps * t ) * t > 1 ) { + for ( j = i; j <= n; j++ ) { + H[j][n-1] = H[j][n-1] / t; + H[j][n] = H[j][n] / t; + } + } + } + } + } + } + + // vectors of isolated roots + for ( i = 0; i < numRows; i++ ) { + if ( i < low || i > high ) { + for ( j = i; j < numRows; j++ ) { + (*this)[i][j] = H[i][j]; + } + } + } + + // back transformation to get eigenvectors of original matrix + for ( j = numRows - 1; j >= low; j-- ) { + for ( i = low; i <= high; i++ ) { + z = 0.0f; + for ( k = low; k <= Min( j, high ); k++ ) { + z = z + (*this)[i][k] * H[k][j]; + } + (*this)[i][j] = z; + } + } + + return true; +} + +/* +============ +idMatX::Eigen_Solve + + Determine eigen values and eigen vectors for a square matrix. + The eigen values are stored in 'realEigenValues' and 'imaginaryEigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the realEigenValues[i] and imaginaryEigenValues[i]. +============ +*/ +bool idMatX::Eigen_Solve( idVecX &realEigenValues, idVecX &imaginaryEigenValues ) { + idMatX H; + + assert( numRows == numColumns ); + + realEigenValues.SetSize( numRows ); + imaginaryEigenValues.SetSize( numRows ); + + H = *this; + + // reduce to Hessenberg form + HessenbergReduction( H ); + + // reduce Hessenberg to real Schur form + return HessenbergToRealSchur( H, realEigenValues, imaginaryEigenValues ); +} + +/* +============ +idMatX::Eigen_SortIncreasing +============ +*/ +void idMatX::Eigen_SortIncreasing( idVecX &eigenValues ) { + for ( int i = 0, j = 0; i <= numRows - 2; i++ ) { + j = i; + float min = eigenValues[j]; + for ( int k = i + 1; k < numRows; k++ ) { + if ( eigenValues[k] < min ) { + j = k; + min = eigenValues[j]; + } + } + if ( j != i ) { + eigenValues.SwapElements( i, j ); + SwapColumns( i, j ); + } + } +} + +/* +============ +idMatX::Eigen_SortDecreasing +============ +*/ +void idMatX::Eigen_SortDecreasing( idVecX &eigenValues ) { + for ( int i = 0, j = 0; i <= numRows - 2; i++ ) { + j = i; + float max = eigenValues[j]; + for ( int k = i + 1; k < numRows; k++ ) { + if ( eigenValues[k] > max ) { + j = k; + max = eigenValues[j]; + } + } + if ( j != i ) { + eigenValues.SwapElements( i, j ); + SwapColumns( i, j ); + } + } +} + +/* +============ +idMatX::DeterminantGeneric +============ +*/ +float idMatX::DeterminantGeneric() const { + int *index; + float det; + idMatX tmp; + + index = (int *) _alloca16( numRows * sizeof( int ) ); + tmp.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + tmp = *this; + + if ( !tmp.LU_Factor( index, &det ) ) { + return 0.0f; + } + + return det; +} + +/* +============ +idMatX::InverseSelfGeneric +============ +*/ +bool idMatX::InverseSelfGeneric() { + int i, j, *index; + idMatX tmp; + idVecX x, b; + + index = (int *) _alloca16( numRows * sizeof( int ) ); + tmp.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + tmp = *this; + + if ( !tmp.LU_Factor( index ) ) { + return false; + } + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + tmp.LU_Solve( x, b, index ); + for ( j = 0; j < numRows; j++ ) { + (*this)[j][i] = x[j]; + } + b[i] = 0.0f; + } + return true; +} + +/* +============ +idMatX::Test +============ +*/ +void idMatX::Test() { + idMatX original, m1, m2, m3, q1, q2, r1, r2; + idVecX v, w, u, c, d; + int offset, size, *index1, *index2; + + size = 6; + original.Random( size, size, 0 ); + original = original * original.Transpose(); + + index1 = (int *) _alloca16( ( size + 1 ) * sizeof( index1[0] ) ); + index2 = (int *) _alloca16( ( size + 1 ) * sizeof( index2[0] ) ); + + /* + idMatX::LowerTriangularInverse + */ + + m1 = original; + m1.ClearUpperTriangle(); + m2 = m1; + + m2.InverseSelf(); + m1.LowerTriangularInverse(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LowerTriangularInverse failed" ); + } + + /* + idMatX::UpperTriangularInverse + */ + + m1 = original; + m1.ClearLowerTriangle(); + m2 = m1; + + m2.InverseSelf(); + m1.UpperTriangularInverse(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::UpperTriangularInverse failed" ); + } + + /* + idMatX::Inverse_GaussJordan + */ + + m1 = original; + + m1.Inverse_GaussJordan(); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_GaussJordan failed" ); + } + + /* + idMatX::Inverse_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 1 ); + v.Random( size, 2 ); + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateRankOne( v, w, 1.0f ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateRankOne failed" ); + } + + /* + idMatX::Inverse_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateRowColumn( v, w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateRowColumn failed" ); + } + } + + /* + idMatX::Inverse_UpdateIncrement + */ + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_Increment( v, w ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateIncrement( v, w ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateIncrement failed" ); + } + + /* + idMatX::Inverse_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_Decrement( offset ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateDecrement( v, w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateDecrement failed" ); + } + } + + /* + idMatX::LU_Factor + */ + + m1 = original; + + m1.LU_Factor( NULL ); // no pivoting + m1.LU_UnpackFactors( m2, m3 ); + m1 = m2 * m3; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_Factor failed" ); + } + + /* + idMatX::LU_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 1 ); + v.Random( size, 2 ); + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update factored m1 + m1.LU_UpdateRankOne( v, w, 1.0f, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateRankOne failed" ); + } + + /* + idMatX::LU_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update m1 + m1.LU_UpdateRowColumn( v, w, offset, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateRowColumn failed" ); + } + } + + /* + idMatX::LU_UpdateIncrement + */ + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_Increment( v, w ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update factored m1 + m1.LU_UpdateIncrement( v, w, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateIncrement failed" ); + } + + /* + idMatX::LU_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + u.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + u[i] = original[index1[offset]][i]; + } + + // update factors of m1 + m1.LU_UpdateDecrement( v, w, u, offset, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateDecrement failed" ); + } + } + + /* + idMatX::LU_Inverse + */ + + m2 = original; + + m2.LU_Factor( NULL ); + m2.LU_Inverse( m1, NULL ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_Inverse failed" ); + } + + /* + idMatX::QR_Factor + */ + + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + m1 = q1 * r1; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_Factor failed" ); + } + + /* + idMatX::QR_UpdateRankOne + */ + + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + v = w; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factored m1 + q1.QR_UpdateRankOne( r1, v, w, 1.0f ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateRankOne failed" ); + } + + /* + idMatX::QR_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update m1 + q1.QR_UpdateRowColumn( r1, v, w, offset ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateRowColumn failed" ); + } + } + + /* + idMatX::QR_UpdateIncrement + */ + + c.SetSize( size+1 ); + d.SetSize( size+1 ); + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_Increment( v, w ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factored m1 + q1.QR_UpdateIncrement( r1, v, w ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateIncrement failed" ); + } + + /* + idMatX::QR_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + c.SetSize( size+1 ); + d.SetSize( size+1 ); + + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factors of m1 + q1.QR_UpdateDecrement( r1, v, w, offset ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateDecrement failed" ); + } + } + + /* + idMatX::QR_Inverse + */ + + m2 = original; + + m2.QR_Factor( c, d ); + m2.QR_Inverse( m1, c, d ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_Inverse failed" ); + } + + /* + idMatX::SVD_Factor + */ + + m1 = original; + m3.Zero( size, size ); + w.Zero( size ); + + m1.SVD_Factor( w, m3 ); + m2.Diag( w ); + m3.TransposeSelf(); + m1 = m1 * m2 * m3; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::SVD_Factor failed" ); + } + + /* + idMatX::SVD_Inverse + */ + + m2 = original; + + m2.SVD_Factor( w, m3 ); + m2.SVD_Inverse( m1, w, m3 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::SVD_Inverse failed" ); + } + + /* + idMatX::Cholesky_Factor + */ + + m1 = original; + + m1.Cholesky_Factor(); + m1.Cholesky_MultiplyFactors( m2 ); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_Factor failed" ); + } + + /* + idMatX::Cholesky_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.Cholesky_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RankOneSymmetric( w, 1.0f ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update factored m1 + m1.Cholesky_UpdateRankOne( w, 1.0f, 0 ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateRankOne failed" ); + } + + /* + idMatX::Cholesky_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + // factor m1 + m1.Cholesky_Factor(); + m1.ClearUpperTriangle(); + + int pdtable[] = { 1, 0, 1, 0, 0, 0 }; + w.Random( size, pdtable[offset] ); + w *= 0.1f; + + // modify and factor m2 + m2.Update_RowColumnSymmetric( w, offset ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update m1 + m1.Cholesky_UpdateRowColumn( w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateRowColumn failed" ); + } + } + + /* + idMatX::Cholesky_UpdateIncrement + */ + + m1.Random( size + 1, size + 1, 0 ); + m3 = m1 * m1.Transpose(); + + m1.SquareSubMatrix( m3, size ); + m2 = m1; + + w.SetSize( size + 1 ); + for ( int i = 0; i < size + 1; i++ ) { + w[i] = m3[size][i]; + } + + // factor m1 + m1.Cholesky_Factor(); + + // modify and factor m2 + m2.Update_IncrementSymmetric( w ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + + // update factored m1 + m1.Cholesky_UpdateIncrement( w ); + + m1.ClearUpperTriangle(); + m2.ClearUpperTriangle(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateIncrement failed" ); + } + + /* + idMatX::Cholesky_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset += size - 1 ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + } + + // factor m1 + m1.Cholesky_Factor(); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + + // update factors of m1 + m1.Cholesky_UpdateDecrement( v, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateDecrement failed" ); + } + } + + /* + idMatX::Cholesky_Inverse + */ + + m2 = original; + + m2.Cholesky_Factor(); + m2.Cholesky_Inverse( m1 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_Inverse failed" ); + } + + /* + idMatX::LDLT_Factor + */ + + m1 = original; + + m1.LDLT_Factor(); + m1.LDLT_MultiplyFactors( m2 ); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Factor failed" ); + } + + m1.LDLT_UnpackFactors( m2, m3 ); + m2 = m2 * m3 * m2.Transpose(); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Factor failed" ); + } + + /* + idMatX::LDLT_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.LDLT_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RankOneSymmetric( w, 1.0f ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update factored m1 + m1.LDLT_UpdateRankOne( w, 1.0f, 0 ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateRankOne failed" ); + } + + /* + idMatX::LDLT_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.LDLT_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RowColumnSymmetric( w, offset ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update m1 + m1.LDLT_UpdateRowColumn( w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateRowColumn failed" ); + } + } + + /* + idMatX::LDLT_UpdateIncrement + */ + + m1.Random( size + 1, size + 1, 0 ); + m3 = m1 * m1.Transpose(); + + m1.SquareSubMatrix( m3, size ); + m2 = m1; + + w.SetSize( size + 1 ); + for ( int i = 0; i < size + 1; i++ ) { + w[i] = m3[size][i]; + } + + // factor m1 + m1.LDLT_Factor(); + + // modify and factor m2 + m2.Update_IncrementSymmetric( w ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + + // update factored m1 + m1.LDLT_UpdateIncrement( w ); + + m1.ClearUpperTriangle(); + m2.ClearUpperTriangle(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateIncrement failed" ); + } + + /* + idMatX::LDLT_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + } + + // factor m1 + m1.LDLT_Factor(); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + + // update factors of m1 + m1.LDLT_UpdateDecrement( v, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateDecrement failed" ); + } + } + + /* + idMatX::LDLT_Inverse + */ + + m2 = original; + + m2.LDLT_Factor(); + m2.LDLT_Inverse( m1 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Inverse failed" ); + } + + /* + idMatX::Eigen_SolveSymmetricTriDiagonal + */ + + m3 = original; + m3.TriDiagonal_ClearTriangles(); + m1 = m3; + + v.SetSize( size ); + + m1.Eigen_SolveSymmetricTriDiagonal( v ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_SolveSymmetricTriDiagonal failed" ); + } + + /* + idMatX::Eigen_SolveSymmetric + */ + + m3 = original; + m1 = m3; + + v.SetSize( size ); + + m1.Eigen_SolveSymmetric( v ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_SolveSymmetric failed" ); + } + + /* + idMatX::Eigen_Solve + */ + + m3 = original; + m1 = m3; + + v.SetSize( size ); + w.SetSize( size ); + + m1.Eigen_Solve( v, w ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_Solve failed" ); + } +} diff --git a/neo/idlib/math/MatX.h b/neo/idlib/math/MatX.h new file mode 100644 index 00000000..059a5f3f --- /dev/null +++ b/neo/idlib/math/MatX.h @@ -0,0 +1,1571 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_MATX_H__ +#define __MATH_MATX_H__ + +/* +=============================================================================== + +idMatX - arbitrary sized dense real matrix + +The matrix lives on 16 byte aligned and 16 byte padded memory. + +NOTE: due to the temporary memory pool idMatX cannot be used by multiple threads. + +=============================================================================== +*/ + +#define MATX_MAX_TEMP 1024 +#define MATX_QUAD( x ) ( ( ( ( x ) + 3 ) & ~3 ) * sizeof( float ) ) +#define MATX_CLEAREND() int s = numRows * numColumns; while( s < ( ( s + 3 ) & ~3 ) ) { mat[s++] = 0.0f; } +#define MATX_ALLOCA( n ) ( (float *) _alloca16( MATX_QUAD( n ) ) ) +#define MATX_ALLOCA_CACHE_LINES( n ) ( (float *) _alloca128( ( ( n ) * sizeof( float ) + CACHE_LINE_SIZE - 1 ) & ~ ( CACHE_LINE_SIZE - 1 ) ) ) +#define MATX_SIMD + +class idMatX { +public: + ID_INLINE idMatX(); + ID_INLINE idMatX( const idMatX & other ); + ID_INLINE explicit idMatX( int rows, int columns ); + ID_INLINE explicit idMatX( int rows, int columns, float *src ); + ID_INLINE ~idMatX(); + + ID_INLINE void Set( int rows, int columns, const float *src ); + ID_INLINE void Set( const idMat3 &m1, const idMat3 &m2 ); + ID_INLINE void Set( const idMat3 &m1, const idMat3 &m2, const idMat3 &m3, const idMat3 &m4 ); + + ID_INLINE const float * operator[]( int index ) const; + ID_INLINE float * operator[]( int index ); + ID_INLINE idMatX & operator=( const idMatX &a ); + ID_INLINE idMatX operator*( const float a ) const; + ID_INLINE idVecX operator*( const idVecX &vec ) const; + ID_INLINE idMatX operator*( const idMatX &a ) const; + ID_INLINE idMatX operator+( const idMatX &a ) const; + ID_INLINE idMatX operator-( const idMatX &a ) const; + ID_INLINE idMatX & operator*=( const float a ); + ID_INLINE idMatX & operator*=( const idMatX &a ); + ID_INLINE idMatX & operator+=( const idMatX &a ); + ID_INLINE idMatX & operator-=( const idMatX &a ); + + friend ID_INLINE idMatX operator*( const float a, const idMatX &m ); + friend ID_INLINE idVecX operator*( const idVecX &vec, const idMatX &m ); + friend ID_INLINE idVecX &operator*=( idVecX &vec, const idMatX &m ); + + ID_INLINE bool Compare( const idMatX &a ) const; // exact compare, no epsilon + ID_INLINE bool Compare( const idMatX &a, const float epsilon ) const; // compare with epsilon + ID_INLINE bool operator==( const idMatX &a ) const; // exact compare, no epsilon + ID_INLINE bool operator!=( const idMatX &a ) const; // exact compare, no epsilon + + ID_INLINE void SetSize( int rows, int columns ); // set the number of rows/columns + void ChangeSize( int rows, int columns, bool makeZero = false ); // change the size keeping data intact where possible + ID_INLINE void ChangeNumRows( int rows ) { ChangeSize( rows, numColumns ); } // set the number of rows/columns + int GetNumRows() const { return numRows; } // get the number of rows + int GetNumColumns() const { return numColumns; } // get the number of columns + ID_INLINE void SetData( int rows, int columns, float *data ); // set float array pointer + ID_INLINE void SetDataCacheLines( int rows, int columns, float *data, bool clear );// set float array pointer + ID_INLINE void Zero(); // clear matrix + ID_INLINE void Zero( int rows, int columns ); // set size and clear matrix + ID_INLINE void Identity(); // clear to identity matrix + ID_INLINE void Identity( int rows, int columns ); // set size and clear to identity matrix + ID_INLINE void Diag( const idVecX &v ); // create diagonal matrix from vector + ID_INLINE void Random( int seed, float l = 0.0f, float u = 1.0f ); // fill matrix with random values + ID_INLINE void Random( int rows, int columns, int seed, float l = 0.0f, float u = 1.0f ); + ID_INLINE void Negate(); // (*this) = - (*this) + ID_INLINE void Clamp( float min, float max ); // clamp all values + ID_INLINE idMatX & SwapRows( int r1, int r2 ); // swap rows + ID_INLINE idMatX & SwapColumns( int r1, int r2 ); // swap columns + ID_INLINE idMatX & SwapRowsColumns( int r1, int r2 ); // swap rows and columns + idMatX & RemoveRow( int r ); // remove a row + idMatX & RemoveColumn( int r ); // remove a column + idMatX & RemoveRowColumn( int r ); // remove a row and column + ID_INLINE void ClearUpperTriangle(); // clear the upper triangle + ID_INLINE void ClearLowerTriangle(); // clear the lower triangle + void CopyLowerToUpperTriangle(); // copy the lower triangle to the upper triangle + ID_INLINE void SquareSubMatrix( const idMatX &m, int size ); // get square sub-matrix from 0,0 to size,size + ID_INLINE float MaxDifference( const idMatX &m ) const; // return maximum element difference between this and m + + ID_INLINE bool IsSquare() const { return ( numRows == numColumns ); } + ID_INLINE bool IsZero( const float epsilon = MATRIX_EPSILON ) const; + ID_INLINE bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + ID_INLINE bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + ID_INLINE bool IsTriDiagonal( const float epsilon = MATRIX_EPSILON ) const; + ID_INLINE bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsOrthogonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsOrthonormal( const float epsilon = MATRIX_EPSILON ) const; + bool IsPMatrix( const float epsilon = MATRIX_EPSILON ) const; + bool IsZMatrix( const float epsilon = MATRIX_EPSILON ) const; + bool IsPositiveDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetricPositiveDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsPositiveSemiDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetricPositiveSemiDefinite( const float epsilon = MATRIX_EPSILON ) const; + + ID_INLINE float Trace() const; // returns product of diagonal elements + ID_INLINE float Determinant() const; // returns determinant of matrix + ID_INLINE idMatX Transpose() const; // returns transpose + ID_INLINE idMatX & TransposeSelf(); // transposes the matrix itself + ID_INLINE void Transpose( idMatX & dst ) const; // stores transpose in 'dst' + ID_INLINE idMatX Inverse() const; // returns the inverse ( m * m.Inverse() = identity ) + ID_INLINE bool InverseSelf(); // returns false if determinant is zero + ID_INLINE idMatX InverseFast() const; // returns the inverse ( m * m.Inverse() = identity ) + ID_INLINE bool InverseFastSelf(); // returns false if determinant is zero + ID_INLINE void Inverse( idMatX & dst ) const; // stores the inverse in 'dst' ( m * m.Inverse() = identity ) + + bool LowerTriangularInverse(); // in-place inversion, returns false if determinant is zero + bool UpperTriangularInverse(); // in-place inversion, returns false if determinant is zero + + ID_INLINE void Subtract( const idMatX & a ); // (*this) -= a; + + ID_INLINE idVecX Multiply( const idVecX &vec ) const; // (*this) * vec + ID_INLINE idVecX TransposeMultiply( const idVecX &vec ) const; // this->Transpose() * vec + + ID_INLINE idMatX Multiply( const idMatX &a ) const; // (*this) * a + ID_INLINE idMatX TransposeMultiply( const idMatX &a ) const; // this->Transpose() * a + + ID_INLINE void Multiply( idVecX &dst, const idVecX &vec ) const; // dst = (*this) * vec + ID_INLINE void MultiplyAdd( idVecX &dst, const idVecX &vec ) const; // dst += (*this) * vec + ID_INLINE void MultiplySub( idVecX &dst, const idVecX &vec ) const; // dst -= (*this) * vec + ID_INLINE void TransposeMultiply( idVecX &dst, const idVecX &vec ) const; // dst = this->Transpose() * vec + ID_INLINE void TransposeMultiplyAdd( idVecX &dst, const idVecX &vec ) const; // dst += this->Transpose() * vec + ID_INLINE void TransposeMultiplySub( idVecX &dst, const idVecX &vec ) const; // dst -= this->Transpose() * vec + + ID_INLINE void Multiply( idMatX &dst, const idMatX &a ) const; // dst = (*this) * a + ID_INLINE void TransposeMultiply( idMatX &dst, const idMatX &a ) const; // dst = this->Transpose() * a + + ID_INLINE int GetDimension() const; // returns total number of values in matrix + + ID_INLINE const idVec6 & SubVec6( int row ) const; // interpret beginning of row as a const idVec6 + ID_INLINE idVec6 & SubVec6( int row ); // interpret beginning of row as an idVec6 + ID_INLINE const idVecX SubVecX( int row ) const; // interpret complete row as a const idVecX + ID_INLINE idVecX SubVecX( int row ); // interpret complete row as an idVecX + ID_INLINE const float * ToFloatPtr() const; // pointer to const matrix float array + ID_INLINE float * ToFloatPtr(); // pointer to matrix float array + const char * ToString( int precision = 2 ) const; + + void Update_RankOne( const idVecX &v, const idVecX &w, float alpha ); + void Update_RankOneSymmetric( const idVecX &v, float alpha ); + void Update_RowColumn( const idVecX &v, const idVecX &w, int r ); + void Update_RowColumnSymmetric( const idVecX &v, int r ); + void Update_Increment( const idVecX &v, const idVecX &w ); + void Update_IncrementSymmetric( const idVecX &v ); + void Update_Decrement( int r ); + + bool Inverse_GaussJordan(); // invert in-place with Gauss-Jordan elimination + bool Inverse_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha ); + bool Inverse_UpdateRowColumn( const idVecX &v, const idVecX &w, int r ); + bool Inverse_UpdateIncrement( const idVecX &v, const idVecX &w ); + bool Inverse_UpdateDecrement( const idVecX &v, const idVecX &w, int r ); + void Inverse_Solve( idVecX &x, const idVecX &b ) const; + + bool LU_Factor( int *index, float *det = NULL ); // factor in-place: L * U + bool LU_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha, int *index ); + bool LU_UpdateRowColumn( const idVecX &v, const idVecX &w, int r, int *index ); + bool LU_UpdateIncrement( const idVecX &v, const idVecX &w, int *index ); + bool LU_UpdateDecrement( const idVecX &v, const idVecX &w, const idVecX &u, int r, int *index ); + void LU_Solve( idVecX &x, const idVecX &b, const int *index ) const; + void LU_Inverse( idMatX &inv, const int *index ) const; + void LU_UnpackFactors( idMatX &L, idMatX &U ) const; + void LU_MultiplyFactors( idMatX &m, const int *index ) const; + + bool QR_Factor( idVecX &c, idVecX &d ); // factor in-place: Q * R + bool QR_UpdateRankOne( idMatX &R, const idVecX &v, const idVecX &w, float alpha ); + bool QR_UpdateRowColumn( idMatX &R, const idVecX &v, const idVecX &w, int r ); + bool QR_UpdateIncrement( idMatX &R, const idVecX &v, const idVecX &w ); + bool QR_UpdateDecrement( idMatX &R, const idVecX &v, const idVecX &w, int r ); + void QR_Solve( idVecX &x, const idVecX &b, const idVecX &c, const idVecX &d ) const; + void QR_Solve( idVecX &x, const idVecX &b, const idMatX &R ) const; + void QR_Inverse( idMatX &inv, const idVecX &c, const idVecX &d ) const; + void QR_UnpackFactors( idMatX &Q, idMatX &R, const idVecX &c, const idVecX &d ) const; + void QR_MultiplyFactors( idMatX &m, const idVecX &c, const idVecX &d ) const; + + bool SVD_Factor( idVecX &w, idMatX &V ); // factor in-place: U * Diag(w) * V.Transpose() + void SVD_Solve( idVecX &x, const idVecX &b, const idVecX &w, const idMatX &V ) const; + void SVD_Inverse( idMatX &inv, const idVecX &w, const idMatX &V ) const; + void SVD_MultiplyFactors( idMatX &m, const idVecX &w, const idMatX &V ) const; + + bool Cholesky_Factor(); // factor in-place: L * L.Transpose() + bool Cholesky_UpdateRankOne( const idVecX &v, float alpha, int offset = 0 ); + bool Cholesky_UpdateRowColumn( const idVecX &v, int r ); + bool Cholesky_UpdateIncrement( const idVecX &v ); + bool Cholesky_UpdateDecrement( const idVecX &v, int r ); + void Cholesky_Solve( idVecX &x, const idVecX &b ) const; + void Cholesky_Inverse( idMatX &inv ) const; + void Cholesky_MultiplyFactors( idMatX &m ) const; + + bool LDLT_Factor(); // factor in-place: L * D * L.Transpose() + bool LDLT_UpdateRankOne( const idVecX &v, float alpha, int offset = 0 ); + bool LDLT_UpdateRowColumn( const idVecX &v, int r ); + bool LDLT_UpdateIncrement( const idVecX &v ); + bool LDLT_UpdateDecrement( const idVecX &v, int r ); + void LDLT_Solve( idVecX &x, const idVecX &b ) const; + void LDLT_Inverse( idMatX &inv ) const; + void LDLT_UnpackFactors( idMatX &L, idMatX &D ) const; + void LDLT_MultiplyFactors( idMatX &m ) const; + + void TriDiagonal_ClearTriangles(); + bool TriDiagonal_Solve( idVecX &x, const idVecX &b ) const; + void TriDiagonal_Inverse( idMatX &inv ) const; + + bool Eigen_SolveSymmetricTriDiagonal( idVecX &eigenValues ); + bool Eigen_SolveSymmetric( idVecX &eigenValues ); + bool Eigen_Solve( idVecX &realEigenValues, idVecX &imaginaryEigenValues ); + void Eigen_SortIncreasing( idVecX &eigenValues ); + void Eigen_SortDecreasing( idVecX &eigenValues ); + + static void Test(); + +private: + int numRows; // number of rows + int numColumns; // number of columns + int alloced; // floats allocated, if -1 then mat points to data set with SetData + float * mat; // memory the matrix is stored + + static float temp[MATX_MAX_TEMP+4]; // used to store intermediate results + static float * tempPtr; // pointer to 16 byte aligned temporary memory + static int tempIndex; // index into memory pool, wraps around + +private: + void SetTempSize( int rows, int columns ); + float DeterminantGeneric() const; + bool InverseSelfGeneric(); + void QR_Rotate( idMatX &R, int i, float a, float b ); + float Pythag( float a, float b ) const; + void SVD_BiDiag( idVecX &w, idVecX &rv1, float &anorm ); + void SVD_InitialWV( idVecX &w, idMatX &V, idVecX &rv1 ); + void HouseholderReduction( idVecX &diag, idVecX &subd ); + bool QL( idVecX &diag, idVecX &subd ); + void HessenbergReduction( idMatX &H ); + void ComplexDivision( float xr, float xi, float yr, float yi, float &cdivr, float &cdivi ); + bool HessenbergToRealSchur( idMatX &H, idVecX &realEigenValues, idVecX &imaginaryEigenValues ); +}; + +/* +======================== +idMatX::idMatX +======================== +*/ +ID_INLINE idMatX::idMatX() { + numRows = numColumns = alloced = 0; + mat = NULL; +} + +/* +======================== +idMatX::~idMatX +======================== +*/ +ID_INLINE idMatX::~idMatX() { + // if not temp memory + if ( mat != NULL && ( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( mat ); + } +} + +/* +======================== +idMatX::idMatX +======================== +*/ +ID_INLINE idMatX::idMatX( int rows, int columns ) { + numRows = numColumns = alloced = 0; + mat = NULL; + SetSize( rows, columns ); +} + +/* +======================== +idMatX::idMatX +======================== +*/ +ID_INLINE idMatX::idMatX( const idMatX & other ) { + numRows = numColumns = alloced = 0; + mat = NULL; + Set( other.GetNumRows(), other.GetNumColumns(), other.ToFloatPtr() ); +} + +/* +======================== +idMatX::idMatX +======================== +*/ +ID_INLINE idMatX::idMatX( int rows, int columns, float *src ) { + numRows = numColumns = alloced = 0; + mat = NULL; + SetData( rows, columns, src ); +} + +/* +======================== +idMatX::Set +======================== +*/ +ID_INLINE void idMatX::Set( int rows, int columns, const float *src ) { + SetSize( rows, columns ); + memcpy( this->mat, src, rows * columns * sizeof( float ) ); +} + +/* +======================== +idMatX::Set +======================== +*/ +ID_INLINE void idMatX::Set( const idMat3 &m1, const idMat3 &m2 ) { + SetSize( 3, 6 ); + for ( int i = 0; i < 3; i++ ) { + for ( int j = 0; j < 3; j++ ) { + mat[(i+0) * numColumns + (j+0)] = m1[i][j]; + mat[(i+0) * numColumns + (j+3)] = m2[i][j]; + } + } +} + +/* +======================== +idMatX::Set +======================== +*/ +ID_INLINE void idMatX::Set( const idMat3 &m1, const idMat3 &m2, const idMat3 &m3, const idMat3 &m4 ) { + SetSize( 6, 6 ); + for ( int i = 0; i < 3; i++ ) { + for ( int j = 0; j < 3; j++ ) { + mat[(i+0) * numColumns + (j+0)] = m1[i][j]; + mat[(i+0) * numColumns + (j+3)] = m2[i][j]; + mat[(i+3) * numColumns + (j+0)] = m3[i][j]; + mat[(i+3) * numColumns + (j+3)] = m4[i][j]; + } + } +} + +/* +======================== +idMatX::operator[] +======================== +*/ +ID_INLINE const float *idMatX::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < numRows ) ); + return mat + index * numColumns; +} + +/* +======================== +idMatX::operator[] +======================== +*/ +ID_INLINE float *idMatX::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < numRows ) ); + return mat + index * numColumns; +} + +/* +======================== +idMatX::operator= +======================== +*/ +ID_INLINE idMatX &idMatX::operator=( const idMatX &a ) { + SetSize( a.numRows, a.numColumns ); + int s = a.numRows * a.numColumns; +#ifdef MATX_SIMD + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( mat + i, _mm_load_ps( a.mat + i ) ); + } +#else + memcpy( mat, a.mat, s * sizeof( float ) ); +#endif + idMatX::tempIndex = 0; + return *this; +} + +/* +======================== +idMatX::operator* +======================== +*/ +ID_INLINE idMatX idMatX::operator*( const float a ) const { + idMatX m; + + m.SetTempSize( numRows, numColumns ); + int s = numRows * numColumns; +#ifdef MATX_SIMD + __m128 va = _mm_load1_ps( & a ); + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( m.mat + i, _mm_mul_ps( _mm_load_ps( mat + i ), va ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + m.mat[i] = mat[i] * a; + } +#endif + return m; +} + +/* +======================== +idMatX::operator* +======================== +*/ +ID_INLINE idVecX idMatX::operator*( const idVecX &vec ) const { + assert( numColumns == vec.GetSize() ); + + idVecX dst; + dst.SetTempSize( numRows ); + Multiply( dst, vec ); + return dst; +} + +/* +======================== +idMatX::operator* +======================== +*/ +ID_INLINE idMatX idMatX::operator*( const idMatX &a ) const { + assert( numColumns == a.numRows ); + + idMatX dst; + dst.SetTempSize( numRows, a.numColumns ); + Multiply( dst, a ); + return dst; +} + +/* +======================== +idMatX::operator+ +======================== +*/ +ID_INLINE idMatX idMatX::operator+( const idMatX &a ) const { + idMatX m; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + m.SetTempSize( numRows, numColumns ); + int s = numRows * numColumns; +#ifdef MATX_SIMD + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( m.mat + i, _mm_add_ps( _mm_load_ps( mat + i ), _mm_load_ps( a.mat + i ) ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + m.mat[i] = mat[i] + a.mat[i]; + } +#endif + return m; +} + +/* +======================== +idMatX::operator- +======================== +*/ +ID_INLINE idMatX idMatX::operator-( const idMatX &a ) const { + idMatX m; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + m.SetTempSize( numRows, numColumns ); + int s = numRows * numColumns; +#ifdef MATX_SIMD + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( m.mat + i, _mm_sub_ps( _mm_load_ps( mat + i ), _mm_load_ps( a.mat + i ) ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + m.mat[i] = mat[i] - a.mat[i]; + } +#endif + return m; +} + +/* +======================== +idMatX::operator*= +======================== +*/ +ID_INLINE idMatX &idMatX::operator*=( const float a ) { + int s = numRows * numColumns; +#ifdef MATX_SIMD + __m128 va = _mm_load1_ps( & a ); + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( mat + i, _mm_mul_ps( _mm_load_ps( mat + i ), va ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + mat[i] *= a; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +/* +======================== +idMatX::operator*= +======================== +*/ +ID_INLINE idMatX &idMatX::operator*=( const idMatX &a ) { + *this = *this * a; + idMatX::tempIndex = 0; + return *this; +} + +/* +======================== +idMatX::operator+= +======================== +*/ +ID_INLINE idMatX &idMatX::operator+=( const idMatX &a ) { + assert( numRows == a.numRows && numColumns == a.numColumns ); + int s = numRows * numColumns; +#ifdef MATX_SIMD + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( mat + i, _mm_add_ps( _mm_load_ps( mat + i ), _mm_load_ps( a.mat + i ) ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + mat[i] += a.mat[i]; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +/* +======================== +idMatX::operator-= +======================== +*/ +ID_INLINE idMatX &idMatX::operator-=( const idMatX &a ) { + assert( numRows == a.numRows && numColumns == a.numColumns ); + int s = numRows * numColumns; +#ifdef MATX_SIMD + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( mat + i, _mm_sub_ps( _mm_load_ps( mat + i ), _mm_load_ps( a.mat + i ) ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + mat[i] -= a.mat[i]; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +/* +======================== +operator* +======================== +*/ +ID_INLINE idMatX operator*( const float a, idMatX const &m ) { + return m * a; +} + +/* +======================== +operator* +======================== +*/ +ID_INLINE idVecX operator*( const idVecX &vec, const idMatX &m ) { + return m * vec; +} + +/* +======================== +operator*= +======================== +*/ +ID_INLINE idVecX &operator*=( idVecX &vec, const idMatX &m ) { + vec = m * vec; + return vec; +} + +/* +======================== +idMatX::Compare +======================== +*/ +ID_INLINE bool idMatX::Compare( const idMatX &a ) const { + assert( numRows == a.numRows && numColumns == a.numColumns ); + + int s = numRows * numColumns; + for ( int i = 0; i < s; i++ ) { + if ( mat[i] != a.mat[i] ) { + return false; + } + } + return true; +} + +/* +======================== +idMatX::Compare +======================== +*/ +ID_INLINE bool idMatX::Compare( const idMatX &a, const float epsilon ) const { + assert( numRows == a.numRows && numColumns == a.numColumns ); + + int s = numRows * numColumns; + for ( int i = 0; i < s; i++ ) { + if ( idMath::Fabs( mat[i] - a.mat[i] ) > epsilon ) { + return false; + } + } + return true; +} + +/* +======================== +idMatX::operator== +======================== +*/ +ID_INLINE bool idMatX::operator==( const idMatX &a ) const { + return Compare( a ); +} + +/* +======================== +idMatX::operator!= +======================== +*/ +ID_INLINE bool idMatX::operator!=( const idMatX &a ) const { + return !Compare( a ); +} + +/* +======================== +idMatX::SetSize +======================== +*/ +ID_INLINE void idMatX::SetSize( int rows, int columns ) { + if ( rows != numRows || columns != numColumns || mat == NULL ) { + assert( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ); + int alloc = ( rows * columns + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + if ( mat != NULL ) { + Mem_Free16( mat ); + } + mat = (float *) Mem_Alloc16( alloc * sizeof( float ), TAG_MATH ); + alloced = alloc; + } + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); + } +} + +/* +======================== +idMatX::SetTempSize +======================== +*/ +ID_INLINE void idMatX::SetTempSize( int rows, int columns ) { + int newSize; + + newSize = ( rows * columns + 3 ) & ~3; + assert( newSize < MATX_MAX_TEMP ); + if ( idMatX::tempIndex + newSize > MATX_MAX_TEMP ) { + idMatX::tempIndex = 0; + } + mat = idMatX::tempPtr + idMatX::tempIndex; + idMatX::tempIndex += newSize; + alloced = newSize; + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +/* +======================== +idMatX::SetData +======================== +*/ +ID_INLINE void idMatX::SetData( int rows, int columns, float *data ) { + assert( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ); + if ( mat != NULL && alloced != -1 ) { + Mem_Free16( mat ); + } + assert( ( ( (UINT_PTR) data ) & 15 ) == 0 ); // data must be 16 byte aligned + mat = data; + alloced = -1; + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +/* +======================== +idMatX::SetDataCacheLines +======================== +*/ +ID_INLINE void idMatX::SetDataCacheLines( int rows, int columns, float *data, bool clear ) { + if ( mat != NULL && alloced != -1 ) { + Mem_Free( mat ); + } + assert( ( ( (UINT_PTR) data ) & 127 ) == 0 ); // data must be 128 byte aligned + mat = data; + alloced = -1; + numRows = rows; + numColumns = columns; + + if ( clear ) { + int size = numRows * numColumns * sizeof( float ); + for ( int i = 0; i < size; i += CACHE_LINE_SIZE ) { + ZeroCacheLine( mat, i ); + } + } else { + MATX_CLEAREND(); + } +} + +/* +======================== +idMatX::Zero +======================== +*/ +ID_INLINE void idMatX::Zero() { + int s = numRows * numColumns; +#ifdef MATX_SIMD + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( mat + i, _mm_setzero_ps() ); + } +#else + s; + memset( mat, 0, numRows * numColumns * sizeof( float ) ); +#endif +} + +/* +======================== +idMatX::Zero +======================== +*/ +ID_INLINE void idMatX::Zero( int rows, int columns ) { + SetSize( rows, columns ); + Zero(); +} + +/* +======================== +idMatX::Identity +======================== +*/ +ID_INLINE void idMatX::Identity() { + assert( numRows == numColumns ); + Zero(); + for ( int i = 0; i < numRows; i++ ) { + mat[i * numColumns + i] = 1.0f; + } +} + +/* +======================== +idMatX::Identity +======================== +*/ +ID_INLINE void idMatX::Identity( int rows, int columns ) { + assert( rows == columns ); + SetSize( rows, columns ); + idMatX::Identity(); +} + +/* +======================== +idMatX::Diag +======================== +*/ +ID_INLINE void idMatX::Diag( const idVecX &v ) { + Zero( v.GetSize(), v.GetSize() ); + for ( int i = 0; i < v.GetSize(); i++ ) { + mat[i * numColumns + i] = v[i]; + } +} + +/* +======================== +idMatX::Random +======================== +*/ +ID_INLINE void idMatX::Random( int seed, float l, float u ) { + idRandom rnd(seed); + + float c = u - l; + int s = numRows * numColumns; + for ( int i = 0; i < s; i++ ) { + mat[i] = l + rnd.RandomFloat() * c; + } +} + +/* +======================== +idMatX::Random +======================== +*/ +ID_INLINE void idMatX::Random( int rows, int columns, int seed, float l, float u ) { + idRandom rnd(seed); + + SetSize( rows, columns ); + float c = u - l; + int s = numRows * numColumns; + for ( int i = 0; i < s; i++ ) { + mat[i] = l + rnd.RandomFloat() * c; + } +} + +/* +======================== +idMatX::Negate +======================== +*/ +ID_INLINE void idMatX::Negate() { + int s = numRows * numColumns; +#ifdef MATX_SIMD + ALIGN16( const unsigned int signBit[4] ) = { IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK }; + for ( int i = 0; i < s; i += 4 ) { + _mm_store_ps( mat + i, _mm_xor_ps( _mm_load_ps( mat + i ), (__m128 &) signBit[0] ) ); + } +#else + for ( int i = 0; i < s; i++ ) { + mat[i] = -mat[i]; + } +#endif +} + +/* +======================== +idMatX::Clamp +======================== +*/ +ID_INLINE void idMatX::Clamp( float min, float max ) { + int s = numRows * numColumns; + for ( int i = 0; i < s; i++ ) { + if ( mat[i] < min ) { + mat[i] = min; + } else if ( mat[i] > max ) { + mat[i] = max; + } + } +} + +/* +======================== +idMatX::SwapRows +======================== +*/ +ID_INLINE idMatX &idMatX::SwapRows( int r1, int r2 ) { + float * ptr1 = mat + r1 * numColumns; + float * ptr2 = mat + r2 * numColumns; + for ( int i = 0; i < numColumns; i++ ) { + SwapValues( ptr1[i], ptr2[i] ); + } + return *this; +} + +/* +======================== +idMatX::SwapColumns +======================== +*/ +ID_INLINE idMatX &idMatX::SwapColumns( int r1, int r2 ) { + float * ptr = mat; + for ( int i = 0; i < numRows; i++, ptr += numColumns ) { + SwapValues( ptr[r1], ptr[r2] ); + } + return *this; +} + +/* +======================== +idMatX::SwapRowsColumns +======================== +*/ +ID_INLINE idMatX &idMatX::SwapRowsColumns( int r1, int r2 ) { + SwapRows( r1, r2 ); + SwapColumns( r1, r2 ); + return *this; +} + +/* +======================== +idMatX::ClearUpperTriangle +======================== +*/ +ID_INLINE void idMatX::ClearUpperTriangle() { + assert( numRows == numColumns ); + for ( int i = numRows-2; i >= 0; i-- ) { + memset( mat + i * numColumns + i + 1, 0, (numColumns - 1 - i) * sizeof(float) ); + } +} + +/* +======================== +idMatX::ClearLowerTriangle +======================== +*/ +ID_INLINE void idMatX::ClearLowerTriangle() { + assert( numRows == numColumns ); + for ( int i = 1; i < numRows; i++ ) { + memset( mat + i * numColumns, 0, i * sizeof(float) ); + } +} + +/* +======================== +idMatX::SquareSubMatrix +======================== +*/ +ID_INLINE void idMatX::SquareSubMatrix( const idMatX &m, int size ) { + assert( size <= m.numRows && size <= m.numColumns ); + SetSize( size, size ); + for ( int i = 0; i < size; i++ ) { + memcpy( mat + i * numColumns, m.mat + i * m.numColumns, size * sizeof( float ) ); + } +} + +/* +======================== +idMatX::MaxDifference +======================== +*/ +ID_INLINE float idMatX::MaxDifference( const idMatX &m ) const { + assert( numRows == m.numRows && numColumns == m.numColumns ); + + float maxDiff = -1.0f; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + float diff = idMath::Fabs( mat[ i * numColumns + j ] - m[i][j] ); + if ( maxDiff < 0.0f || diff > maxDiff ) { + maxDiff = diff; + } + } + } + return maxDiff; +} + +/* +======================== +idMatX::IsZero +======================== +*/ +ID_INLINE bool idMatX::IsZero( const float epsilon ) const { + // returns true if (*this) == Zero + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[i * numColumns + j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +/* +======================== +idMatX::IsIdentity +======================== +*/ +ID_INLINE bool idMatX::IsIdentity( const float epsilon ) const { + // returns true if (*this) == Identity + assert( numRows == numColumns ); + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[i * numColumns + j] - (float)( i == j ) ) > epsilon ) { + return false; + } + } + } + return true; +} + +/* +======================== +idMatX::IsDiagonal +======================== +*/ +ID_INLINE bool idMatX::IsDiagonal( const float epsilon ) const { + // returns true if all elements are zero except for the elements on the diagonal + assert( numRows == numColumns ); + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( i != j && idMath::Fabs( mat[i * numColumns + j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +/* +======================== +idMatX::IsTriDiagonal +======================== +*/ +ID_INLINE bool idMatX::IsTriDiagonal( const float epsilon ) const { + // returns true if all elements are zero except for the elements on the diagonal plus or minus one column + + if ( numRows != numColumns ) { + return false; + } + for ( int i = 0; i < numRows-2; i++ ) { + for ( int j = i+2; j < numColumns; j++ ) { + if ( idMath::Fabs( (*this)[i][j] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( (*this)[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +/* +======================== +idMatX::IsSymmetric +======================== +*/ +ID_INLINE bool idMatX::IsSymmetric( const float epsilon ) const { + // (*this)[i][j] == (*this)[j][i] + if ( numRows != numColumns ) { + return false; + } + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[ i * numColumns + j ] - mat[ j * numColumns + i ] ) > epsilon ) { + return false; + } + } + } + return true; +} + +/* +======================== +idMatX::Trace +======================== +*/ +ID_INLINE float idMatX::Trace() const { + float trace = 0.0f; + + assert( numRows == numColumns ); + + // sum of elements on the diagonal + for ( int i = 0; i < numRows; i++ ) { + trace += mat[i * numRows + i]; + } + return trace; +} + +/* +======================== +idMatX::Determinant +======================== +*/ +ID_INLINE float idMatX::Determinant() const { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: return mat[0]; + case 2: return reinterpret_cast( mat )->Determinant(); + case 3: return reinterpret_cast( mat )->Determinant(); + case 4: return reinterpret_cast( mat )->Determinant(); + case 5: return reinterpret_cast( mat )->Determinant(); + case 6: return reinterpret_cast( mat )->Determinant(); + default: return DeterminantGeneric(); + } +} + +/* +======================== +idMatX::Transpose +======================== +*/ +ID_INLINE idMatX idMatX::Transpose() const { + idMatX transpose; + + transpose.SetTempSize( numColumns, numRows ); + + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + transpose.mat[j * transpose.numColumns + i] = mat[i * numColumns + j]; + } + } + + return transpose; +} + +/* +======================== +idMatX::TransposeSelf +======================== +*/ +ID_INLINE idMatX &idMatX::TransposeSelf() { + *this = Transpose(); + return *this; +} + +/* +======================== +idMatX::Transpose +======================== +*/ +ID_INLINE void idMatX::Transpose( idMatX & dst ) const { + dst = Transpose(); +} + +/* +======================== +idMatX::Inverse +======================== +*/ +ID_INLINE idMatX idMatX::Inverse() const { + idMatX invMat; + + invMat.SetTempSize( numRows, numColumns ); + memcpy( invMat.mat, mat, numRows * numColumns * sizeof( float ) ); + verify( invMat.InverseSelf() ); + return invMat; +} + +/* +======================== +idMatX::InverseSelf +======================== +*/ +ID_INLINE bool idMatX::InverseSelf() { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + if ( idMath::Fabs( mat[0] ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + mat[0] = 1.0f / mat[0]; + return true; + case 2: + return reinterpret_cast(mat)->InverseSelf(); + case 3: + return reinterpret_cast(mat)->InverseSelf(); + case 4: + return reinterpret_cast(mat)->InverseSelf(); + case 5: + return reinterpret_cast(mat)->InverseSelf(); + case 6: + return reinterpret_cast(mat)->InverseSelf(); + default: + return InverseSelfGeneric(); + } +} + +/* +======================== +idMatX::InverseFast +======================== +*/ +ID_INLINE idMatX idMatX::InverseFast() const { + idMatX invMat; + + invMat.SetTempSize( numRows, numColumns ); + memcpy( invMat.mat, mat, numRows * numColumns * sizeof( float ) ); + verify( invMat.InverseFastSelf() ); + return invMat; +} + +/* +======================== +idMatX::InverseFastSelf +======================== +*/ +ID_INLINE bool idMatX::InverseFastSelf() { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + if ( idMath::Fabs( mat[0] ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + mat[0] = 1.0f / mat[0]; + return true; + case 2: + return reinterpret_cast(mat)->InverseFastSelf(); + case 3: + return reinterpret_cast(mat)->InverseFastSelf(); + case 4: + return reinterpret_cast(mat)->InverseFastSelf(); + case 5: + return reinterpret_cast(mat)->InverseFastSelf(); + case 6: + return reinterpret_cast(mat)->InverseFastSelf(); + default: + return InverseSelfGeneric(); + } +} + +/* +======================== +idMatX::Inverse +======================== +*/ +ID_INLINE void idMatX::Inverse( idMatX & dst ) const { + dst = InverseFast(); +} + +/* +======================== +idMatX::Subtract +======================== +*/ +ID_INLINE void idMatX::Subtract( const idMatX & a ) { + (*this) -= a; +} + +/* +======================== +idMatX::Multiply +======================== +*/ +ID_INLINE idVecX idMatX::Multiply( const idVecX &vec ) const { + assert( numColumns == vec.GetSize() ); + + idVecX dst; + dst.SetTempSize( numRows ); + Multiply( dst, vec ); + return dst; +} + +/* +======================== +idMatX::Multiply +======================== +*/ +ID_INLINE idMatX idMatX::Multiply( const idMatX &a ) const { + assert( numColumns == a.numRows ); + + idMatX dst; + dst.SetTempSize( numRows, a.numColumns ); + Multiply( dst, a ); + return dst; +} + +/* +======================== +idMatX::TransposeMultiply +======================== +*/ +ID_INLINE idVecX idMatX::TransposeMultiply( const idVecX &vec ) const { + assert( numRows == vec.GetSize() ); + + idVecX dst; + dst.SetTempSize( numColumns ); + TransposeMultiply( dst, vec ); + return dst; +} + +/* +======================== +idMatX::TransposeMultiply +======================== +*/ +ID_INLINE idMatX idMatX::TransposeMultiply( const idMatX &a ) const { + assert( numRows == a.numRows ); + + idMatX dst; + dst.SetTempSize( numColumns, a.numColumns ); + TransposeMultiply( dst, a ); + return dst; +} + +/* +======================== +idMatX::Multiply +======================== +*/ +ID_INLINE void idMatX::Multiply( idVecX &dst, const idVecX &vec ) const { + dst.SetSize( numRows ); + const float * mPtr = mat; + const float * vPtr = vec.ToFloatPtr(); + float * dstPtr = dst.ToFloatPtr(); + float * temp = (float *)_alloca16( numRows * sizeof( float ) ); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + temp[i] = sum; + mPtr += numColumns; + } + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] = temp[i]; + } +} + +/* +======================== +idMatX::MultiplyAdd +======================== +*/ +ID_INLINE void idMatX::MultiplyAdd( idVecX &dst, const idVecX &vec ) const { + assert( dst.GetSize() == numRows ); + const float * mPtr = mat; + const float * vPtr = vec.ToFloatPtr(); + float * dstPtr = dst.ToFloatPtr(); + float * temp = (float *)_alloca16( numRows * sizeof( float ) ); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + temp[i] = dstPtr[i] + sum; + mPtr += numColumns; + } + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] = temp[i]; + } +} + +/* +======================== +idMatX::MultiplySub +======================== +*/ +ID_INLINE void idMatX::MultiplySub( idVecX &dst, const idVecX &vec ) const { + assert( dst.GetSize() == numRows ); + const float * mPtr = mat; + const float * vPtr = vec.ToFloatPtr(); + float * dstPtr = dst.ToFloatPtr(); + float * temp = (float *)_alloca16( numRows * sizeof( float ) ); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + temp[i] = dstPtr[i] - sum; + mPtr += numColumns; + } + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] = temp[i]; + } +} + +/* +======================== +idMatX::TransposeMultiply +======================== +*/ +ID_INLINE void idMatX::TransposeMultiply( idVecX &dst, const idVecX &vec ) const { + dst.SetSize( numColumns ); + const float * vPtr = vec.ToFloatPtr(); + float * dstPtr = dst.ToFloatPtr(); + float * temp = (float *)_alloca16( numColumns * sizeof( float ) ); + for ( int i = 0; i < numColumns; i++ ) { + const float * mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + temp[i] = sum; + } + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] = temp[i]; + } +} + +/* +======================== +idMatX::TransposeMultiplyAdd +======================== +*/ +ID_INLINE void idMatX::TransposeMultiplyAdd( idVecX &dst, const idVecX &vec ) const { + assert( dst.GetSize() == numColumns ); + const float * vPtr = vec.ToFloatPtr(); + float * dstPtr = dst.ToFloatPtr(); + float * temp = (float *)_alloca16( numColumns * sizeof( float ) ); + for ( int i = 0; i < numColumns; i++ ) { + const float * mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + temp[i] = dstPtr[i] + sum; + } + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] = temp[i]; + } +} + +/* +======================== +idMatX::TransposeMultiplySub +======================== +*/ +ID_INLINE void idMatX::TransposeMultiplySub( idVecX &dst, const idVecX &vec ) const { + assert( dst.GetSize() == numColumns ); + const float * vPtr = vec.ToFloatPtr(); + float * dstPtr = dst.ToFloatPtr(); + float * temp = (float *)_alloca16( numColumns * sizeof( float ) ); + for ( int i = 0; i < numColumns; i++ ) { + const float * mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + temp[i] = dstPtr[i] - sum; + } + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] = temp[i]; + } +} + +/* +======================== +idMatX::Multiply +======================== +*/ +ID_INLINE void idMatX::Multiply( idMatX &dst, const idMatX &a ) const { + assert( numColumns == a.numRows ); + assert( &dst != &a && &dst != this ); + + dst.SetSize( numRows, a.numColumns ); + float * dstPtr = dst.ToFloatPtr(); + const float * m1Ptr = ToFloatPtr(); + int k = numRows; + int l = a.GetNumColumns(); + for ( int i = 0; i < k; i++ ) { + for ( int j = 0; j < l; j++ ) { + const float * m2Ptr = a.ToFloatPtr() + j; + float sum = m1Ptr[0] * m2Ptr[0]; + for ( int n = 1; n < numColumns; n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += numColumns; + } +} + +/* +======================== +idMatX::TransposeMultiply +======================== +*/ +ID_INLINE void idMatX::TransposeMultiply( idMatX &dst, const idMatX &a ) const { + assert( numRows == a.numRows ); + assert( &dst != &a && &dst != this ); + + dst.SetSize( numColumns, a.numColumns ); + float * dstPtr = dst.ToFloatPtr(); + int k = numColumns; + int l = a.numColumns; + for ( int i = 0; i < k; i++ ) { + for ( int j = 0; j < l; j++ ) { + const float * m1Ptr = ToFloatPtr() + i; + const float * m2Ptr = a.ToFloatPtr() + j; + float sum = m1Ptr[0] * m2Ptr[0]; + for ( int n = 1; n < numRows; n++ ) { + m1Ptr += numColumns; + m2Ptr += a.numColumns; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } +} + +/* +======================== +idMatX::GetDimension +======================== +*/ +ID_INLINE int idMatX::GetDimension() const { + return numRows * numColumns; +} + +/* +======================== +idMatX::SubVec6 +======================== +*/ +ID_INLINE const idVec6 &idMatX::SubVec6( int row ) const { + assert( numColumns >= 6 && row >= 0 && row < numRows ); + return *reinterpret_cast(mat + row * numColumns); +} + +/* +======================== +idMatX::SubVec6 +======================== +*/ +ID_INLINE idVec6 &idMatX::SubVec6( int row ) { + assert( numColumns >= 6 && row >= 0 && row < numRows ); + return *reinterpret_cast(mat + row * numColumns); +} + +/* +======================== +idMatX::SubVecX +======================== +*/ +ID_INLINE const idVecX idMatX::SubVecX( int row ) const { + idVecX v; + assert( row >= 0 && row < numRows ); + v.SetData( numColumns, mat + row * numColumns ); + return v; +} + +/* +======================== +idMatX::SubVecX +======================== +*/ +ID_INLINE idVecX idMatX::SubVecX( int row ) { + idVecX v; + assert( row >= 0 && row < numRows ); + v.SetData( numColumns, mat + row * numColumns ); + return v; +} + +/* +======================== +idMatX::ToFloatPtr +======================== +*/ +ID_INLINE const float *idMatX::ToFloatPtr() const { + return mat; +} + +/* +======================== +idMatX::ToFloatPtr +======================== +*/ +ID_INLINE float *idMatX::ToFloatPtr() { + return mat; +} + +#endif // !__MATH_MATRIXX_H__ diff --git a/neo/idlib/math/Math.cpp b/neo/idlib/math/Math.cpp new file mode 100644 index 00000000..ce338b5a --- /dev/null +++ b/neo/idlib/math/Math.cpp @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +const int SMALLEST_NON_DENORMAL = 1<( & SMALLEST_NON_DENORMAL ); // 1.1754944e-038f + +const __m128 idMath::SIMD_SP_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; +const __m128 idMath::SIMD_SP_255 = { 255.0f, 255.0f, 255.0f, 255.0f }; +const __m128 idMath::SIMD_SP_min_char = { -128.0f, -128.0f, -128.0f, -128.0f }; +const __m128 idMath::SIMD_SP_max_char = { 127.0f, 127.0f, 127.0f, 127.0f }; +const __m128 idMath::SIMD_SP_min_short = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; +const __m128 idMath::SIMD_SP_max_short = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; +const __m128 idMath::SIMD_SP_smallestNonDenorm = { FLT_SMALLEST_NON_DENORMAL, FLT_SMALLEST_NON_DENORMAL, FLT_SMALLEST_NON_DENORMAL, FLT_SMALLEST_NON_DENORMAL }; +const __m128 idMath::SIMD_SP_tiny = { 1e-4f, 1e-4f, 1e-4f, 1e-4f }; +const __m128 idMath::SIMD_SP_rsqrt_c0 = { 3.0f, 3.0f, 3.0f, 3.0f }; +const __m128 idMath::SIMD_SP_rsqrt_c1 = { -0.5f, -0.5f, -0.5f, -0.5f }; + +bool idMath::initialized = false; +dword idMath::iSqrt[SQRT_TABLE_SIZE]; // inverse square root lookup table + +/* +=============== +idMath::Init +=============== +*/ +void idMath::Init() { + union _flint fi, fo; + + for ( int i = 0; i < SQRT_TABLE_SIZE; i++ ) { + fi.i = ((EXP_BIAS-1) << EXP_POS) | (i << LOOKUP_POS); + fo.f = (float)( 1.0 / sqrt( fi.f ) ); + iSqrt[i] = ((dword)(((fo.i + (1<<(SEED_POS-2))) >> SEED_POS) & 0xFF))<= 2 && exponentBits <= 8 ); + assert( mantissaBits >= 2 && mantissaBits <= 23 ); + + int maxBits = ( ( ( 1 << ( exponentBits - 1 ) ) - 1 ) << mantissaBits ) | ( ( 1 << mantissaBits ) - 1 ); + int minBits = ( ( ( 1 << exponentBits ) - 2 ) << mantissaBits ) | 1; + + float max = BitsToFloat( maxBits, exponentBits, mantissaBits ); + float min = BitsToFloat( minBits, exponentBits, mantissaBits ); + + if ( f >= 0.0f ) { + if ( f >= max ) { + return maxBits; + } else if ( f <= min ) { + return minBits; + } + } else { + if ( f <= -max ) { + return ( maxBits | ( 1 << ( exponentBits + mantissaBits ) ) ); + } else if ( f >= -min ) { + return ( minBits | ( 1 << ( exponentBits + mantissaBits ) ) ); + } + } + + exponentBits--; + i = *reinterpret_cast(&f); + sign = ( i >> IEEE_FLT_SIGN_BIT ) & 1; + exponent = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + mantissa = i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ); + value = sign << ( 1 + exponentBits + mantissaBits ); + value |= ( ( INT32_SIGNBITSET( exponent ) << exponentBits ) | ( abs( exponent ) & ( ( 1 << exponentBits ) - 1 ) ) ) << mantissaBits; + value |= mantissa >> ( IEEE_FLT_MANTISSA_BITS - mantissaBits ); + return value; +} + +/* +================ +idMath::BitsToFloat +================ +*/ +float idMath::BitsToFloat( int i, int exponentBits, int mantissaBits ) { + static int exponentSign[2] = { 1, -1 }; + int sign, exponent, mantissa, value; + + assert( exponentBits >= 2 && exponentBits <= 8 ); + assert( mantissaBits >= 2 && mantissaBits <= 23 ); + + exponentBits--; + sign = i >> ( 1 + exponentBits + mantissaBits ); + exponent = ( ( i >> mantissaBits ) & ( ( 1 << exponentBits ) - 1 ) ) * exponentSign[( i >> ( exponentBits + mantissaBits ) ) & 1]; + mantissa = ( i & ( ( 1 << mantissaBits ) - 1 ) ) << ( IEEE_FLT_MANTISSA_BITS - mantissaBits ); + value = sign << IEEE_FLT_SIGN_BIT | ( exponent + IEEE_FLT_EXPONENT_BIAS ) << IEEE_FLT_MANTISSA_BITS | mantissa; + return *reinterpret_cast(&value); +} diff --git a/neo/idlib/math/Math.h b/neo/idlib/math/Math.h new file mode 100644 index 00000000..d08fcac4 --- /dev/null +++ b/neo/idlib/math/Math.h @@ -0,0 +1,1326 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_MATH_H__ +#define __MATH_MATH_H__ + +#ifdef MACOS_X +// for square root estimate instruction +#include +// for FLT_MIN +#include +#endif + +/* +=============================================================================== + + Math + +=============================================================================== +*/ + +#ifdef INFINITY +#undef INFINITY +#endif + +#ifdef FLT_EPSILON +#undef FLT_EPSILON +#endif + +#define DEG2RAD(a) ( (a) * idMath::M_DEG2RAD ) +#define RAD2DEG(a) ( (a) * idMath::M_RAD2DEG ) + +#define SEC2MS(t) ( idMath::Ftoi( (t) * idMath::M_SEC2MS ) ) +#define MS2SEC(t) ( (t) * idMath::M_MS2SEC ) + +#define ANGLE2SHORT(x) ( idMath::Ftoi( (x) * 65536.0f / 360.0f ) & 65535 ) +#define SHORT2ANGLE(x) ( (x) * ( 360.0f / 65536.0f ) ) + +#define ANGLE2BYTE(x) ( idMath::Ftoi( (x) * 256.0f / 360.0f ) & 255 ) +#define BYTE2ANGLE(x) ( (x) * ( 360.0f / 256.0f ) ) + +#define C_FLOAT_TO_INT( x ) (int)(x) + +/* +================================================================================================ + + two-complements integer bit layouts + +================================================================================================ +*/ + +#define INT8_SIGN_BIT 7 +#define INT16_SIGN_BIT 15 +#define INT32_SIGN_BIT 31 +#define INT64_SIGN_BIT 63 + +#define INT8_SIGN_MASK ( 1 << INT8_SIGN_BIT ) +#define INT16_SIGN_MASK ( 1 << INT16_SIGN_BIT ) +#define INT32_SIGN_MASK ( 1UL << INT32_SIGN_BIT ) +#define INT64_SIGN_MASK ( 1ULL << INT64_SIGN_BIT ) + +/* +================================================================================================ + + integer sign bit tests + +================================================================================================ +*/ + +// If this was ever compiled on a system that had 64 bit unsigned ints, +// it would fail. +compile_time_assert( sizeof( unsigned int ) == 4 ); + +#define OLD_INT32_SIGNBITSET(i) (static_cast(i) >> INT32_SIGN_BIT) +#define OLD_INT32_SIGNBITNOTSET(i) ((~static_cast(i)) >> INT32_SIGN_BIT) + +// Unfortunately, /analyze can't figure out that these always return +// either 0 or 1, so this extra wrapper is needed to avoid the static +// alaysis warning. + +ID_INLINE_EXTERN int INT32_SIGNBITSET( int i ) { + int r = OLD_INT32_SIGNBITSET( i ); + assert( r == 0 || r == 1 ); + return r; +} + +ID_INLINE_EXTERN int INT32_SIGNBITNOTSET( int i ) { + int r = OLD_INT32_SIGNBITNOTSET( i ); + assert( r == 0 || r == 1 ); + return r; +} + +/* +================================================================================================ + + floating point bit layouts according to the IEEE 754-1985 and 754-2008 standard + +================================================================================================ +*/ + +#define IEEE_FLT16_MANTISSA_BITS 10 +#define IEEE_FLT16_EXPONENT_BITS 5 +#define IEEE_FLT16_EXPONENT_BIAS 15 +#define IEEE_FLT16_SIGN_BIT 15 +#define IEEE_FLT16_SIGN_MASK ( 1U << IEEE_FLT16_SIGN_BIT ) + +#define IEEE_FLT_MANTISSA_BITS 23 +#define IEEE_FLT_EXPONENT_BITS 8 +#define IEEE_FLT_EXPONENT_BIAS 127 +#define IEEE_FLT_SIGN_BIT 31 +#define IEEE_FLT_SIGN_MASK ( 1UL << IEEE_FLT_SIGN_BIT ) + +#define IEEE_DBL_MANTISSA_BITS 52 +#define IEEE_DBL_EXPONENT_BITS 11 +#define IEEE_DBL_EXPONENT_BIAS 1023 +#define IEEE_DBL_SIGN_BIT 63 +#define IEEE_DBL_SIGN_MASK ( 1ULL << IEEE_DBL_SIGN_BIT ) + +#define IEEE_DBLE_MANTISSA_BITS 63 +#define IEEE_DBLE_EXPONENT_BITS 15 +#define IEEE_DBLE_EXPONENT_BIAS 0 +#define IEEE_DBLE_SIGN_BIT 79 + +/* +================================================================================================ + + floating point sign bit tests + +================================================================================================ +*/ + +#define IEEE_FLT_SIGNBITSET( a ) (reinterpret_cast(a) >> IEEE_FLT_SIGN_BIT) +#define IEEE_FLT_SIGNBITNOTSET( a ) ((~reinterpret_cast(a)) >> IEEE_FLT_SIGN_BIT) +#define IEEE_FLT_ISNOTZERO( a ) (reinterpret_cast(a) & ~(1u<(x) == 0xffc00000); +} + +/* +======================== +IEEE_FLT_IS_DENORMAL +======================== +*/ +ID_INLINE_EXTERN bool IEEE_FLT_IS_DENORMAL( float x ) { + return ((reinterpret_cast(x) & 0x7f800000) == 0x00000000 && + (reinterpret_cast(x) & 0x007fffff) != 0x00000000 ); +} + + +/* +======================== +IsNAN +======================== +*/template +ID_INLINE_EXTERN bool IsNAN( const type &v ) { + for ( int i = 0; i < v.GetDimension(); i++ ) { + const float f = v.ToFloatPtr()[i]; + if ( IEEE_FLT_IS_NAN( f ) || IEEE_FLT_IS_INF( f ) || IEEE_FLT_IS_IND( f ) ) { + return true; + } + } + return false; +} + +/* +======================== +IsValid +======================== +*/ +template +ID_INLINE_EXTERN bool IsValid( const type &v ) { + for ( int i = 0; i < v.GetDimension(); i++ ) { + const float f = v.ToFloatPtr()[i]; + if ( IEEE_FLT_IS_NAN( f ) || IEEE_FLT_IS_INF( f ) || IEEE_FLT_IS_IND( f ) || IEEE_FLT_IS_DENORMAL( f ) ) { + return false; + } + } + return true; +} + +/* +======================== +IsValid +======================== +*/ +template<> +ID_INLINE_EXTERN bool IsValid( const float & f ) { // these parameter must be a reference for the function to be considered a specialization + return !( IEEE_FLT_IS_NAN( f ) || IEEE_FLT_IS_INF( f ) || IEEE_FLT_IS_IND( f ) || IEEE_FLT_IS_DENORMAL( f ) ); +} + +/* +======================== +IsNAN +======================== +*/ +template<> +ID_INLINE_EXTERN bool IsNAN( const float & f ) { // these parameter must be a reference for the function to be considered a specialization + if ( IEEE_FLT_IS_NAN( f ) || IEEE_FLT_IS_INF( f ) || IEEE_FLT_IS_IND( f ) ) { + return true; + } + return false; +} + +/* +======================== +IsInRange + +Returns true if any scalar is greater than the range or less than the negative range. +======================== +*/ +template +ID_INLINE_EXTERN bool IsInRange( const type &v, const float range ) { + for ( int i = 0; i < v.GetDimension(); i++ ) { + const float f = v.ToFloatPtr()[i]; + if ( f > range || f < -range ) { + return false; + } + } + return true; +} + + +/* +================================================================================================ + + MinIndex/MaxIndex + +================================================================================================ +*/ +template ID_INLINE int MaxIndex( T x, T y ) { return ( x > y ) ? 0 : 1; } +template ID_INLINE int MinIndex( T x, T y ) { return ( x < y ) ? 0 : 1; } + +template ID_INLINE T Max3( T x, T y, T z ) { return ( x > y ) ? ( ( x > z ) ? x : z ) : ( ( y > z ) ? y : z ); } +template ID_INLINE T Min3( T x, T y, T z ) { return ( x < y ) ? ( ( x < z ) ? x : z ) : ( ( y < z ) ? y : z ); } +template ID_INLINE int Max3Index( T x, T y, T z ) { return ( x > y ) ? ( ( x > z ) ? 0 : 2 ) : ( ( y > z ) ? 1 : 2 ); } +template ID_INLINE int Min3Index( T x, T y, T z ) { return ( x < y ) ? ( ( x < z ) ? 0 : 2 ) : ( ( y < z ) ? 1 : 2 ); } + +/* +================================================================================================ + + Sign/Square/Cube + +================================================================================================ +*/ +template ID_INLINE T Sign( T f ) { return ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 0 ); } +template ID_INLINE T Square( T x ) { return x * x; } +template ID_INLINE T Cube( T x ) { return x * x * x; } + +class idMath { +public: + + static void Init(); + + static float InvSqrt( float x ); // inverse square root with 32 bits precision, returns huge number when x == 0.0 + static float InvSqrt16( float x ); // inverse square root with 16 bits precision, returns huge number when x == 0.0 + + static float Sqrt( float x ); // square root with 32 bits precision + static float Sqrt16( float x ); // square root with 16 bits precision + + static float Sin( float a ); // sine with 32 bits precision + static float Sin16( float a ); // sine with 16 bits precision, maximum absolute error is 2.3082e-09 + + static float Cos( float a ); // cosine with 32 bits precision + static float Cos16( float a ); // cosine with 16 bits precision, maximum absolute error is 2.3082e-09 + + static void SinCos( float a, float &s, float &c ); // sine and cosine with 32 bits precision + static void SinCos16( float a, float &s, float &c ); // sine and cosine with 16 bits precision + + static float Tan( float a ); // tangent with 32 bits precision + static float Tan16( float a ); // tangent with 16 bits precision, maximum absolute error is 1.8897e-08 + + static float ASin( float a ); // arc sine with 32 bits precision, input is clamped to [-1, 1] to avoid a silent NaN + static float ASin16( float a ); // arc sine with 16 bits precision, maximum absolute error is 6.7626e-05 + + static float ACos( float a ); // arc cosine with 32 bits precision, input is clamped to [-1, 1] to avoid a silent NaN + static float ACos16( float a ); // arc cosine with 16 bits precision, maximum absolute error is 6.7626e-05 + + static float ATan( float a ); // arc tangent with 32 bits precision + static float ATan16( float a ); // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + + static float ATan( float y, float x ); // arc tangent with 32 bits precision + static float ATan16( float y, float x ); // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + + static float Pow( float x, float y ); // x raised to the power y with 32 bits precision + static float Pow16( float x, float y ); // x raised to the power y with 16 bits precision + + static float Exp( float f ); // e raised to the power f with 32 bits precision + static float Exp16( float f ); // e raised to the power f with 16 bits precision + + static float Log( float f ); // natural logarithm with 32 bits precision + static float Log16( float f ); // natural logarithm with 16 bits precision + + static int IPow( int x, int y ); // integral x raised to the power y + static int ILog2( float f ); // integral base-2 logarithm of the floating point value + static int ILog2( int i ); // integral base-2 logarithm of the integer value + + static int BitsForFloat( float f ); // minumum number of bits required to represent ceil( f ) + static int BitsForInteger( int i ); // minumum number of bits required to represent i + static int MaskForFloatSign( float f );// returns 0x00000000 if x >= 0.0f and returns 0xFFFFFFFF if x <= -0.0f + static int MaskForIntegerSign( int i );// returns 0x00000000 if x >= 0 and returns 0xFFFFFFFF if x < 0 + static int FloorPowerOfTwo( int x ); // round x down to the nearest power of 2 + static int CeilPowerOfTwo( int x ); // round x up to the nearest power of 2 + static bool IsPowerOfTwo( int x ); // returns true if x is a power of 2 + static int BitCount( int x ); // returns the number of 1 bits in x + static int BitReverse( int x ); // returns the bit reverse of x + + static int Abs( int x ); // returns the absolute value of the integer value (for reference only) + static float Fabs( float f ); // returns the absolute value of the floating point value + static float Floor( float f ); // returns the largest integer that is less than or equal to the given value + static float Ceil( float f ); // returns the smallest integer that is greater than or equal to the given value + static float Rint( float f ); // returns the nearest integer + + static float Frac( float f ); // f - Floor( f ) + + static int Ftoi( float f ); // float to int conversion + static char Ftoi8( float f ); // float to char conversion + static short Ftoi16( float f ); // float to short conversion + static unsigned short Ftoui16( float f ); // float to unsigned short conversion + static byte Ftob( float f ); // float to byte conversion, the result is clamped to the range [0-255] + + static signed char ClampChar( int i ); + static signed short ClampShort( int i ); + static int ClampInt( int min, int max, int value ); + static float ClampFloat( float min, float max, float value ); + + static float AngleNormalize360( float angle ); + static float AngleNormalize180( float angle ); + static float AngleDelta( float angle1, float angle2 ); + + static int FloatToBits( float f, int exponentBits, int mantissaBits ); + static float BitsToFloat( int i, int exponentBits, int mantissaBits ); + + static int FloatHash( const float *array, const int numFloats ); + + static float LerpToWithScale( const float cur, const float dest, const float scale ); + + static const float PI; // pi + static const float TWO_PI; // pi * 2 + static const float HALF_PI; // pi / 2 + static const float ONEFOURTH_PI; // pi / 4 + static const float ONEOVER_PI; // 1 / pi + static const float ONEOVER_TWOPI; // 1 / pi * 2 + static const float E; // e + static const float SQRT_TWO; // sqrt( 2 ) + static const float SQRT_THREE; // sqrt( 3 ) + static const float SQRT_1OVER2; // sqrt( 1 / 2 ) + static const float SQRT_1OVER3; // sqrt( 1 / 3 ) + static const float M_DEG2RAD; // degrees to radians multiplier + static const float M_RAD2DEG; // radians to degrees multiplier + static const float M_SEC2MS; // seconds to milliseconds multiplier + static const float M_MS2SEC; // milliseconds to seconds multiplier + static const float INFINITY; // huge number which should be larger than any valid number used + static const float FLT_EPSILON; // smallest positive number such that 1.0+FLT_EPSILON != 1.0 + static const float FLT_SMALLEST_NON_DENORMAL; // smallest non-denormal 32-bit floating point value + + static const __m128 SIMD_SP_zero; + static const __m128 SIMD_SP_255; + static const __m128 SIMD_SP_min_char; + static const __m128 SIMD_SP_max_char; + static const __m128 SIMD_SP_min_short; + static const __m128 SIMD_SP_max_short; + static const __m128 SIMD_SP_smallestNonDenorm; + static const __m128 SIMD_SP_tiny; + static const __m128 SIMD_SP_rsqrt_c0; + static const __m128 SIMD_SP_rsqrt_c1; + +private: + enum { + LOOKUP_BITS = 8, + EXP_POS = 23, + EXP_BIAS = 127, + LOOKUP_POS = (EXP_POS-LOOKUP_BITS), + SEED_POS = (EXP_POS-8), + SQRT_TABLE_SIZE = (2< 255 ? 255 : (byte)(x) ) ); +} + +/* +======================== +idMath::InvSqrt +======================== +*/ +ID_INLINE float idMath::InvSqrt( float x ) { + + return ( x > FLT_SMALLEST_NON_DENORMAL ) ? sqrtf( 1.0f / x ) : INFINITY; + +} + +/* +======================== +idMath::InvSqrt16 +======================== +*/ +ID_INLINE float idMath::InvSqrt16( float x ) { + + return ( x > FLT_SMALLEST_NON_DENORMAL ) ? sqrtf( 1.0f / x ) : INFINITY; + +} + +/* +======================== +idMath::Sqrt +======================== +*/ +ID_INLINE float idMath::Sqrt( float x ) { + return ( x >= 0.0f ) ? x * InvSqrt( x ) : 0.0f; +} + +/* +======================== +idMath::Sqrt16 +======================== +*/ +ID_INLINE float idMath::Sqrt16( float x ) { + return ( x >= 0.0f ) ? x * InvSqrt16( x ) : 0.0f; +} + +/* +======================== +idMath::Frac +======================== +*/ +ID_INLINE float idMath::Frac( float f ) { + return f - floorf( f ); +} + +/* +======================== +idMath::Sin +======================== +*/ +ID_INLINE float idMath::Sin( float a ) { + return sinf( a ); +} + +/* +======================== +idMath::Sin16 +======================== +*/ +ID_INLINE float idMath::Sin16( float a ) { + float s; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a * ONEOVER_TWOPI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + } else { + a = PI - a; + } + } +#else + a = PI - a; + if ( fabsf( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + } +#endif + s = a * a; + return a * ( ( ( ( ( -2.39e-08f * s + 2.7526e-06f ) * s - 1.98409e-04f ) * s + 8.3333315e-03f ) * s - 1.666666664e-01f ) * s + 1.0f ); +} + +/* +======================== +idMath::Cos +======================== +*/ +ID_INLINE float idMath::Cos( float a ) { + return cosf( a ); +} + +/* +======================== +idMath::Cos16 +======================== +*/ +ID_INLINE float idMath::Cos16( float a ) { + float s, d; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a * ONEOVER_TWOPI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + d = -1.0f; + } else { + d = 1.0f; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + d = 1.0f; + } else { + a = PI - a; + d = -1.0f; + } + } +#else + a = PI - a; + if ( fabsf( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } +#endif + s = a * a; + return d * ( ( ( ( ( -2.605e-07f * s + 2.47609e-05f ) * s - 1.3888397e-03f ) * s + 4.16666418e-02f ) * s - 4.999999963e-01f ) * s + 1.0f ); +} + +/* +======================== +idMath::SinCos +======================== +*/ +ID_INLINE void idMath::SinCos( float a, float &s, float &c ) { + _asm { + fld a + fsincos + mov ecx, c + mov edx, s + fstp dword ptr [ecx] + fstp dword ptr [edx] + } +} + +/* +======================== +idMath::SinCos16 +======================== +*/ +ID_INLINE void idMath::SinCos16( float a, float &s, float &c ) { + float t, d; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a * ONEOVER_TWOPI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + d = -1.0f; + } else { + d = 1.0f; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + d = 1.0f; + } else { + a = PI - a; + d = -1.0f; + } + } +#else + a = PI - a; + if ( fabsf( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } +#endif + t = a * a; + s = a * ( ( ( ( ( -2.39e-08f * t + 2.7526e-06f ) * t - 1.98409e-04f ) * t + 8.3333315e-03f ) * t - 1.666666664e-01f ) * t + 1.0f ); + c = d * ( ( ( ( ( -2.605e-07f * t + 2.47609e-05f ) * t - 1.3888397e-03f ) * t + 4.16666418e-02f ) * t - 4.999999963e-01f ) * t + 1.0f ); +} + +/* +======================== +idMath::Tan +======================== +*/ +ID_INLINE float idMath::Tan( float a ) { + return tanf( a ); +} + +/* +======================== +idMath::Tan16 +======================== +*/ +ID_INLINE float idMath::Tan16( float a ) { + float s; + bool reciprocal; + + if ( ( a < 0.0f ) || ( a >= PI ) ) { + a -= floorf( a * ONEOVER_PI ) * PI; + } +#if 1 + if ( a < HALF_PI ) { + if ( a > ONEFOURTH_PI ) { + a = HALF_PI - a; + reciprocal = true; + } else { + reciprocal = false; + } + } else { + if ( a > HALF_PI + ONEFOURTH_PI ) { + a = a - PI; + reciprocal = false; + } else { + a = HALF_PI - a; + reciprocal = true; + } + } +#else + a = HALF_PI - a; + if ( fabsf( a ) >= ONEFOURTH_PI ) { + a = ( ( a < 0.0f ) ? -HALF_PI : HALF_PI ) - a; + reciprocal = false; + } else { + reciprocal = true; + } +#endif + s = a * a; + s = a * ( ( ( ( ( ( 9.5168091e-03f * s + 2.900525e-03f ) * s + 2.45650893e-02f ) * s + 5.33740603e-02f ) * s + 1.333923995e-01f ) * s + 3.333314036e-01f ) * s + 1.0f ); + if ( reciprocal ) { + return 1.0f / s; + } else { + return s; + } +} + +/* +======================== +idMath::ASin +======================== +*/ +ID_INLINE float idMath::ASin( float a ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + if ( a >= 1.0f ) { + return HALF_PI; + } + return asinf( a ); +} + +/* +======================== +idMath::ASin16 +======================== +*/ +ID_INLINE float idMath::ASin16( float a ) { + if ( a < 0.0f ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + a = fabsf( a ); + return ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * idMath::Sqrt( 1.0f - a ) - HALF_PI; + } else { + if ( a >= 1.0f ) { + return HALF_PI; + } + return HALF_PI - ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * idMath::Sqrt( 1.0f - a ); + } +} + +/* +======================== +idMath::ACos +======================== +*/ +ID_INLINE float idMath::ACos( float a ) { + if ( a <= -1.0f ) { + return PI; + } + if ( a >= 1.0f ) { + return 0.0f; + } + return acosf( a ); +} + +/* +======================== +idMath::ACos16 +======================== +*/ +ID_INLINE float idMath::ACos16( float a ) { + if ( a < 0.0f ) { + if ( a <= -1.0f ) { + return PI; + } + a = fabsf( a ); + return PI - ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * idMath::Sqrt( 1.0f - a ); + } else { + if ( a >= 1.0f ) { + return 0.0f; + } + return ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * idMath::Sqrt( 1.0f - a ); + } +} + +/* +======================== +idMath::ATan +======================== +*/ +ID_INLINE float idMath::ATan( float a ) { + return atanf( a ); +} + +/* +======================== +idMath::ATan16 +======================== +*/ +ID_INLINE float idMath::ATan16( float a ) { + float s; + if ( fabsf( a ) > 1.0f ) { + a = 1.0f / a; + s = a * a; + s = - ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + if ( a < 0.0f ) { + return s - HALF_PI; + } else { + return s + HALF_PI; + } + } else { + s = a * a; + return ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + } +} + +/* +======================== +idMath::ATan +======================== +*/ +ID_INLINE float idMath::ATan( float y, float x ) { + assert( fabs( y ) > idMath::FLT_SMALLEST_NON_DENORMAL || fabs( x ) > idMath::FLT_SMALLEST_NON_DENORMAL ); + return atan2f( y, x ); +} + +/* +======================== +idMath::ATan16 +======================== +*/ +ID_INLINE float idMath::ATan16( float y, float x ) { + assert( fabs( y ) > idMath::FLT_SMALLEST_NON_DENORMAL || fabs( x ) > idMath::FLT_SMALLEST_NON_DENORMAL ); + + float a, s; + if ( fabsf( y ) > fabsf( x ) ) { + a = x / y; + s = a * a; + s = - ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + if ( a < 0.0f ) { + return s - HALF_PI; + } else { + return s + HALF_PI; + } + } else { + a = y / x; + s = a * a; + return ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + } +} + +/* +======================== +idMath::Pow +======================== +*/ +ID_INLINE float idMath::Pow( float x, float y ) { + return powf( x, y ); +} + +/* +======================== +idMath::Pow16 +======================== +*/ +ID_INLINE float idMath::Pow16( float x, float y ) { + return Exp16( y * Log16( x ) ); +} + +/* +======================== +idMath::Exp +======================== +*/ +ID_INLINE float idMath::Exp( float f ) { + return expf( f ); +} + +/* +======================== +idMath::Exp16 +======================== +*/ +ID_INLINE float idMath::Exp16( float f ) { + float x = f * 1.44269504088896340f; // multiply with ( 1 / log( 2 ) ) +#if 1 + int i = *reinterpret_cast(&x); + int s = ( i >> IEEE_FLT_SIGN_BIT ); + int e = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + int m = ( i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ) ) | ( 1 << IEEE_FLT_MANTISSA_BITS ); + i = ( ( m >> ( IEEE_FLT_MANTISSA_BITS - e ) ) & ~( e >> INT32_SIGN_BIT ) ) ^ s; +#else + int i = (int) x; + if ( x < 0.0f ) { + i--; + } +#endif + int exponent = ( i + IEEE_FLT_EXPONENT_BIAS ) << IEEE_FLT_MANTISSA_BITS; + float y = *reinterpret_cast(&exponent); + x -= (float) i; + if ( x >= 0.5f ) { + x -= 0.5f; + y *= 1.4142135623730950488f; // multiply with sqrt( 2 ) + } + float x2 = x * x; + float p = x * ( 7.2152891511493f + x2 * 0.0576900723731f ); + float q = 20.8189237930062f + x2; + x = y * ( q + p ) / ( q - p ); + return x; +} + +/* +======================== +idMath::Log +======================== +*/ +ID_INLINE float idMath::Log( float f ) { + return logf( f ); +} + +/* +======================== +idMath::Log16 +======================== +*/ +ID_INLINE float idMath::Log16( float f ) { + int i = *reinterpret_cast(&f); + int exponent = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + i -= ( exponent + 1 ) << IEEE_FLT_MANTISSA_BITS; // get value in the range [.5, 1> + float y = *reinterpret_cast(&i); + y *= 1.4142135623730950488f; // multiply with sqrt( 2 ) + y = ( y - 1.0f ) / ( y + 1.0f ); + float y2 = y * y; + y = y * ( 2.000000000046727f + y2 * ( 0.666666635059382f + y2 * ( 0.4000059794795f + y2 * ( 0.28525381498f + y2 * 0.2376245609f ) ) ) ); + y += 0.693147180559945f * ( (float)exponent + 0.5f ); + return y; +} + +/* +======================== +idMath::IPow +======================== +*/ +ID_INLINE int idMath::IPow( int x, int y ) { + int r; for( r = x; y > 1; y-- ) { r *= x; } return r; +} + +/* +======================== +idMath::ILog2 +======================== +*/ +ID_INLINE int idMath::ILog2( float f ) { + return ( ( (*reinterpret_cast(&f)) >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; +} + +/* +======================== +idMath::ILog2 +======================== +*/ +ID_INLINE int idMath::ILog2( int i ) { + return ILog2( (float)i ); +} + +/* +======================== +idMath::BitsForFloat +======================== +*/ +ID_INLINE int idMath::BitsForFloat( float f ) { + return ILog2( f ) + 1; +} + +/* +======================== +idMath::BitsForInteger +======================== +*/ +ID_INLINE int idMath::BitsForInteger( int i ) { + return ILog2( (float)i ) + 1; +} + +/* +======================== +idMath::MaskForFloatSign +======================== +*/ +ID_INLINE int idMath::MaskForFloatSign( float f ) { + return ( (*reinterpret_cast(&f)) >> IEEE_FLT_SIGN_BIT ); +} + +/* +======================== +idMath::MaskForIntegerSign +======================== +*/ +ID_INLINE int idMath::MaskForIntegerSign( int i ) { + return ( i >> INT32_SIGN_BIT ); +} + +/* +======================== +idMath::FloorPowerOfTwo +======================== +*/ +ID_INLINE int idMath::FloorPowerOfTwo( int x ) { + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return x >> 1; +} + +/* +======================== +idMath::CeilPowerOfTwo +======================== +*/ +ID_INLINE int idMath::CeilPowerOfTwo( int x ) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return x; +} + +/* +======================== +idMath::IsPowerOfTwo +======================== +*/ +ID_INLINE bool idMath::IsPowerOfTwo( int x ) { + return ( x & ( x - 1 ) ) == 0 && x > 0; +} + +/* +======================== +idMath::BitCount +======================== +*/ +ID_INLINE int idMath::BitCount( int x ) { + x -= ( ( x >> 1 ) & 0x55555555 ); + x = ( ( ( x >> 2 ) & 0x33333333 ) + ( x & 0x33333333 ) ); + x = ( ( ( x >> 4 ) + x ) & 0x0f0f0f0f ); + x += ( x >> 8 ); + return ( ( x + ( x >> 16 ) ) & 0x0000003f ); +} + +/* +======================== +idMath::BitReverse +======================== +*/ +ID_INLINE int idMath::BitReverse( int x ) { + x = ( ( ( x >> 1 ) & 0x55555555 ) | ( ( x & 0x55555555 ) << 1 ) ); + x = ( ( ( x >> 2 ) & 0x33333333 ) | ( ( x & 0x33333333 ) << 2 ) ); + x = ( ( ( x >> 4 ) & 0x0f0f0f0f ) | ( ( x & 0x0f0f0f0f ) << 4 ) ); + x = ( ( ( x >> 8 ) & 0x00ff00ff ) | ( ( x & 0x00ff00ff ) << 8 ) ); + return ( ( x >> 16 ) | ( x << 16 ) ); +} + +/* +======================== +idMath::Abs +======================== +*/ +ID_INLINE int idMath::Abs( int x ) { +#if 1 + return abs( x ); +#else + int y = x >> INT32_SIGN_BIT; + return ( ( x ^ y ) - y ); +#endif +} + +/* +======================== +idMath::Fabs +======================== +*/ +ID_INLINE float idMath::Fabs( float f ) { +#if 1 + return fabsf( f ); +#else + int tmp = *reinterpret_cast( &f ); + tmp &= 0x7FFFFFFF; + return *reinterpret_cast( &tmp ); +#endif +} + +/* +======================== +idMath::Floor +======================== +*/ +ID_INLINE float idMath::Floor( float f ) { + return floorf( f ); +} + +/* +======================== +idMath::Ceil +======================== +*/ +ID_INLINE float idMath::Ceil( float f ) { + return ceilf( f ); +} + +/* +======================== +idMath::Rint +======================== +*/ +ID_INLINE float idMath::Rint( float f ) { + return floorf( f + 0.5f ); +} + + +/* +======================== +idMath::Ftoi +======================== +*/ +ID_INLINE int idMath::Ftoi( float f ) { + // If a converted result is larger than the maximum signed doubleword integer, + // the floating-point invalid exception is raised, and if this exception is masked, + // the indefinite integer value (80000000H) is returned. + __m128 x = _mm_load_ss( &f ); + return _mm_cvttss_si32( x ); +} + +/* +======================== +idMath::Ftoi8 +======================== +*/ +ID_INLINE char idMath::Ftoi8( float f ) { + __m128 x = _mm_load_ss( &f ); + x = _mm_max_ss( x, SIMD_SP_min_char ); + x = _mm_min_ss( x, SIMD_SP_max_char ); + return static_cast( _mm_cvttss_si32( x ) ); +} + +/* +======================== +idMath::Ftoi16 +======================== +*/ +ID_INLINE short idMath::Ftoi16( float f ) { + __m128 x = _mm_load_ss( &f ); + x = _mm_max_ss( x, SIMD_SP_min_short ); + x = _mm_min_ss( x, SIMD_SP_max_short ); + return static_cast( _mm_cvttss_si32( x ) ); +} + +/* +======================== +idMath::Ftoui16 +======================== +*/ +ID_INLINE unsigned short idMath::Ftoui16( float f ) { + // TO DO - SSE ?? + + // The converted result is clamped to the range [-32768,32767]. + int i = C_FLOAT_TO_INT( f ); + if ( i < 0 ) { + return 0; + } else if ( i > 65535 ) { + return 65535; + } + return static_cast( i ); +} + +/* +======================== +idMath::Ftob +======================== +*/ +ID_INLINE byte idMath::Ftob( float f ) { + // If a converted result is negative the value (0) is returned and if the + // converted result is larger than the maximum byte the value (255) is returned. + __m128 x = _mm_load_ss( &f ); + x = _mm_max_ss( x, SIMD_SP_zero ); + x = _mm_min_ss( x, SIMD_SP_255 ); + return static_cast( _mm_cvttss_si32( x ) ); +} + +/* +======================== +idMath::ClampChar +======================== +*/ +ID_INLINE signed char idMath::ClampChar( int i ) { + if ( i < -128 ) { + return -128; + } + if ( i > 127 ) { + return 127; + } + return static_cast( i ); +} + +/* +======================== +idMath::ClampShort +======================== +*/ +ID_INLINE signed short idMath::ClampShort( int i ) { + if ( i < -32768 ) { + return -32768; + } + if ( i > 32767 ) { + return 32767; + } + return static_cast( i ); +} + +/* +======================== +idMath::ClampInt +======================== +*/ +ID_INLINE int idMath::ClampInt( int min, int max, int value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +/* +======================== +idMath::ClampFloat +======================== +*/ +ID_INLINE float idMath::ClampFloat( float min, float max, float value ) { + return Max( min, Min( max, value ) ); +} + +/* +======================== +idMath::AngleNormalize360 +======================== +*/ +ID_INLINE float idMath::AngleNormalize360( float angle ) { + if ( ( angle >= 360.0f ) || ( angle < 0.0f ) ) { + angle -= floorf( angle * ( 1.0f / 360.0f ) ) * 360.0f; + } + return angle; +} + +/* +======================== +idMath::AngleNormalize180 +======================== +*/ +ID_INLINE float idMath::AngleNormalize180( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0f ) { + angle -= 360.0f; + } + return angle; +} + +/* +======================== +idMath::AngleDelta +======================== +*/ +ID_INLINE float idMath::AngleDelta( float angle1, float angle2 ) { + return AngleNormalize180( angle1 - angle2 ); +} + +/* +======================== +idMath::FloatHash +======================== +*/ +ID_INLINE int idMath::FloatHash( const float *array, const int numFloats ) { + int i, hash = 0; + const int *ptr; + + ptr = reinterpret_cast( array ); + for ( i = 0; i < numFloats; i++ ) { + hash ^= ptr[i]; + } + return hash; +} + +template< typename T > +ID_INLINE_EXTERN T Lerp( const T from, const T to, float f ) { + return from + ( ( to - from ) * f ); +} + +template<> +ID_INLINE_EXTERN int Lerp( const int from, const int to, float f ) { + return idMath::Ftoi( (float) from + ( ( (float) to - (float) from ) * f ) ); +} + + +/* +======================== +LerpToWithScale + +Lerps from "cur" to "dest", scaling the delta to change by "scale" +If the delta between "cur" and "dest" is very small, dest is returned to prevent denormals. +======================== +*/ +inline float idMath::LerpToWithScale( const float cur, const float dest, const float scale ) { + float delta = dest - cur; + if ( delta > -1.0e-6f && delta < 1.0e-6f ) { + return dest; + } + return cur + ( dest - cur ) * scale; +} + + +#endif /* !__MATH_MATH_H__ */ diff --git a/neo/idlib/math/Matrix.cpp b/neo/idlib/math/Matrix.cpp new file mode 100644 index 00000000..c672fab5 --- /dev/null +++ b/neo/idlib/math/Matrix.cpp @@ -0,0 +1,2916 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +//=============================================================== +// +// idMat2 +// +//=============================================================== + +idMat2 mat2_zero( idVec2( 0, 0 ), idVec2( 0, 0 ) ); +idMat2 mat2_identity( idVec2( 1, 0 ), idVec2( 0, 1 ) ); + +/* +============ +idMat2::InverseSelf +============ +*/ +bool idMat2::InverseSelf() { + // 2+4 = 6 multiplications + // 1 division + double det, invDet, a; + + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = mat[0][0]; + mat[0][0] = mat[1][1] * invDet; + mat[0][1] = - mat[0][1] * invDet; + mat[1][0] = - mat[1][0] * invDet; + mat[1][1] = a * invDet; + + return true; +} + +/* +============ +idMat2::InverseFastSelf +============ +*/ +bool idMat2::InverseFastSelf() { +#if 1 + // 2+4 = 6 multiplications + // 1 division + double det, invDet, a; + + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = mat[0][0]; + mat[0][0] = mat[1][1] * invDet; + mat[0][1] = - mat[0][1] * invDet; + mat[1][0] = - mat[1][0] * invDet; + mat[1][1] = a * invDet; + + return true; +#else + // 2*4 = 8 multiplications + // 2 division + float *mat = reinterpret_cast(this); + double d, di; + float s; + + di = mat[0]; + s = di; + mat[0*2+0] = d = 1.0f / di; + mat[0*2+1] *= d; + d = -d; + mat[1*2+0] *= d; + d = mat[1*2+0] * di; + mat[1*2+1] += mat[0*2+1] * d; + di = mat[1*2+1]; + s *= di; + mat[1*2+1] = d = 1.0f / di; + mat[1*2+0] *= d; + d = -d; + mat[0*2+1] *= d; + d = mat[0*2+1] * di; + mat[0*2+0] += mat[1*2+0] * d; + + return ( s != 0.0f && !IEEE_FLT_IS_NAN( s ) ); +#endif +} + +/* +============= +idMat2::ToString +============= +*/ +const char *idMat2::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat3 +// +//=============================================================== + +idMat3 mat3_zero( idVec3( 0, 0, 0 ), idVec3( 0, 0, 0 ), idVec3( 0, 0, 0 ) ); +idMat3 mat3_identity( idVec3( 1, 0, 0 ), idVec3( 0, 1, 0 ), idVec3( 0, 0, 1 ) ); + +/* +======================== +idMat3::ToAngles + +returns the pitch/yaw/roll each in the range [-180, 180] degrees +======================== +*/ +idAngles idMat3::ToAngles() const { + idAngles angles; + float s = idMath::Sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + if ( s > idMath::FLT_EPSILON ) { + angles.pitch = RAD2DEG( - idMath::ATan( mat[0][2], s ) ); + angles.yaw = RAD2DEG( idMath::ATan( mat[0][1], mat[0][0] ) ); + angles.roll = RAD2DEG( idMath::ATan( mat[1][2], mat[2][2] ) ); + } else { + angles.pitch = mat[0][2] < 0.0f ? 90.0f : -90.0f; + angles.yaw = RAD2DEG( - idMath::ATan( mat[1][0], mat[1][1] ) ); + angles.roll = 0.0f; + } + return angles; +} + +/* +============ +idMat3::ToQuat +============ +*/ +idQuat idMat3::ToQuat() const { + idQuat q; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[ 3 ] = { 1, 2, 0 }; + + trace = mat[ 0 ][ 0 ] + mat[ 1 ][ 1 ] + mat[ 2 ][ 2 ]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + q[3] = s * t; + q[0] = ( mat[ 2 ][ 1 ] - mat[ 1 ][ 2 ] ) * s; + q[1] = ( mat[ 0 ][ 2 ] - mat[ 2 ][ 0 ] ) * s; + q[2] = ( mat[ 1 ][ 0 ] - mat[ 0 ][ 1 ] ) * s; + + } else { + + i = 0; + if ( mat[ 1 ][ 1 ] > mat[ 0 ][ 0 ] ) { + i = 1; + } + if ( mat[ 2 ][ 2 ] > mat[ i ][ i ] ) { + i = 2; + } + j = next[ i ]; + k = next[ j ]; + + t = ( mat[ i ][ i ] - ( mat[ j ][ j ] + mat[ k ][ k ] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + q[i] = s * t; + q[3] = ( mat[ k ][ j ] - mat[ j ][ k ] ) * s; + q[j] = ( mat[ j ][ i ] + mat[ i ][ j ] ) * s; + q[k] = ( mat[ k ][ i ] + mat[ i ][ k ] ) * s; + } + return q; +} + +/* +============ +idMat3::ToCQuat +============ +*/ +idCQuat idMat3::ToCQuat() const { + idQuat q = ToQuat(); + if ( q.w < 0.0f ) { + return idCQuat( -q.x, -q.y, -q.z ); + } + return idCQuat( q.x, q.y, q.z ); +} + +/* +============ +idMat3::ToRotation +============ +*/ +idRotation idMat3::ToRotation() const { + idRotation r; + float trace; + float s; + float t; + int i; + int j; + int k; + static int next[ 3 ] = { 1, 2, 0 }; + + trace = mat[ 0 ][ 0 ] + mat[ 1 ][ 1 ] + mat[ 2 ][ 2 ]; + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + r.angle = s * t; + r.vec[0] = ( mat[ 2 ][ 1 ] - mat[ 1 ][ 2 ] ) * s; + r.vec[1] = ( mat[ 0 ][ 2 ] - mat[ 2 ][ 0 ] ) * s; + r.vec[2] = ( mat[ 1 ][ 0 ] - mat[ 0 ][ 1 ] ) * s; + + } else { + + i = 0; + if ( mat[ 1 ][ 1 ] > mat[ 0 ][ 0 ] ) { + i = 1; + } + if ( mat[ 2 ][ 2 ] > mat[ i ][ i ] ) { + i = 2; + } + j = next[ i ]; + k = next[ j ]; + + t = ( mat[ i ][ i ] - ( mat[ j ][ j ] + mat[ k ][ k ] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + r.vec[i] = s * t; + r.angle = ( mat[ k ][ j ] - mat[ j ][ k ] ) * s; + r.vec[j] = ( mat[ j ][ i ] + mat[ i ][ j ] ) * s; + r.vec[k] = ( mat[ k ][ i ] + mat[ i ][ k ] ) * s; + } + + r.angle = idMath::ACos( r.angle ); + float lengthSqr = r.vec.LengthSqr(); + if ( ( idMath::Fabs( r.angle ) < 1e-10f ) || ( lengthSqr < 1e-10f ) ) { + r.vec.Set( 0.0f, 0.0f, 1.0f ); + r.angle = 0.0f; + } else { + r.vec *= idMath::InvSqrt( lengthSqr ); + r.angle *= 2.0f * idMath::M_RAD2DEG; + } + + + r.origin.Zero(); + r.axis = *this; + r.axisValid = true; + return r; +} + +/* +================= +idMat3::ToAngularVelocity +================= +*/ +idVec3 idMat3::ToAngularVelocity() const { + idRotation rotation = ToRotation(); + return rotation.GetVec() * DEG2RAD( rotation.GetAngle() ); +} + +/* +============ +idMat3::Determinant +============ +*/ +float idMat3::Determinant() const { + + float det2_12_01 = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + float det2_12_02 = mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]; + float det2_12_12 = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + + return mat[0][0] * det2_12_12 - mat[0][1] * det2_12_02 + mat[0][2] * det2_12_01; +} + +/* +============ +idMat3::InverseSelf +============ +*/ +bool idMat3::InverseSelf() { + // 18+3+9 = 30 multiplications + // 1 division + idMat3 inverse; + double det, invDet; + + inverse[0][0] = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + inverse[1][0] = mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]; + inverse[2][0] = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + + det = mat[0][0] * inverse[0][0] + mat[0][1] * inverse[1][0] + mat[0][2] * inverse[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + inverse[0][1] = mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]; + inverse[0][2] = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + inverse[1][1] = mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]; + inverse[1][2] = mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]; + inverse[2][1] = mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]; + inverse[2][2] = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + mat[0][0] = inverse[0][0] * invDet; + mat[0][1] = inverse[0][1] * invDet; + mat[0][2] = inverse[0][2] * invDet; + + mat[1][0] = inverse[1][0] * invDet; + mat[1][1] = inverse[1][1] * invDet; + mat[1][2] = inverse[1][2] * invDet; + + mat[2][0] = inverse[2][0] * invDet; + mat[2][1] = inverse[2][1] * invDet; + mat[2][2] = inverse[2][2] * invDet; + + return true; +} + +/* +============ +idMat3::InverseFastSelf +============ +*/ +bool idMat3::InverseFastSelf() { +#if 1 + // 18+3+9 = 30 multiplications + // 1 division + idMat3 inverse; + double det, invDet; + + inverse[0][0] = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + inverse[1][0] = mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]; + inverse[2][0] = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + + det = mat[0][0] * inverse[0][0] + mat[0][1] * inverse[1][0] + mat[0][2] * inverse[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + inverse[0][1] = mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]; + inverse[0][2] = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + inverse[1][1] = mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]; + inverse[1][2] = mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]; + inverse[2][1] = mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]; + inverse[2][2] = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + mat[0][0] = inverse[0][0] * invDet; + mat[0][1] = inverse[0][1] * invDet; + mat[0][2] = inverse[0][2] * invDet; + + mat[1][0] = inverse[1][0] * invDet; + mat[1][1] = inverse[1][1] * invDet; + mat[1][2] = inverse[1][2] * invDet; + + mat[2][0] = inverse[2][0] * invDet; + mat[2][1] = inverse[2][1] * invDet; + mat[2][2] = inverse[2][2] * invDet; + + return true; +#elif 0 + // 3*10 = 30 multiplications + // 3 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + d = -d; + mat[3] *= d; + mat[6] *= d; + d = mat[3] * di; + mat[4] += mat[1] * d; + mat[5] += mat[2] * d; + d = mat[6] * di; + mat[7] += mat[1] * d; + mat[8] += mat[2] * d; + di = mat[4]; + s *= di; + mat[4] = d = 1.0f / di; + mat[3] *= d; + mat[5] *= d; + d = -d; + mat[1] *= d; + mat[7] *= d; + d = mat[1] * di; + mat[0] += mat[3] * d; + mat[2] += mat[5] * d; + d = mat[7] * di; + mat[6] += mat[3] * d; + mat[8] += mat[5] * d; + di = mat[8]; + s *= di; + mat[8] = d = 1.0f / di; + mat[6] *= d; + mat[7] *= d; + d = -d; + mat[2] *= d; + mat[5] *= d; + d = mat[2] * di; + mat[0] += mat[6] * d; + mat[1] += mat[7] * d; + d = mat[5] * di; + mat[3] += mat[6] * d; + mat[4] += mat[7] * d; + + return ( s != 0.0f && !IEEE_FLT_IS_NAN( s ) ); +#else + // 4*2+4*4 = 24 multiplications + // 2*1 = 2 divisions + idMat2 r0; + float r1[2], r2[2], r3; + float det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); // 2x2 + det = mat[0*3+0] * mat[1*3+1] - mat[0*3+1] * mat[1*3+0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = mat[1*3+1] * invDet; + r0[0][1] = - mat[0*3+1] * invDet; + r0[1][0] = - mat[1*3+0] * invDet; + r0[1][1] = mat[0*3+0] * invDet; + + // r1 = r0 * m1; // 2x1 = 2x2 * 2x1 + r1[0] = r0[0][0] * mat[0*3+2] + r0[0][1] * mat[1*3+2]; + r1[1] = r0[1][0] * mat[0*3+2] + r0[1][1] * mat[1*3+2]; + + // r2 = m2 * r1; // 1x1 = 1x2 * 2x1 + r2[0] = mat[2*3+0] * r1[0] + mat[2*3+1] * r1[1]; + + // r3 = r2 - m3; // 1x1 = 1x1 - 1x1 + r3 = r2[0] - mat[2*3+2]; + + // r3.InverseSelf(); + if ( idMath::Fabs( r3 ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + r3 = 1.0f / r3; + + // r2 = m2 * r0; // 1x2 = 1x2 * 2x2 + r2[0] = mat[2*3+0] * r0[0][0] + mat[2*3+1] * r0[1][0]; + r2[1] = mat[2*3+0] * r0[0][1] + mat[2*3+1] * r0[1][1]; + + // m2 = r3 * r2; // 1x2 = 1x1 * 1x2 + mat[2*3+0] = r3 * r2[0]; + mat[2*3+1] = r3 * r2[1]; + + // m0 = r0 - r1 * m2; // 2x2 - 2x1 * 1x2 + mat[0*3+0] = r0[0][0] - r1[0] * mat[2*3+0]; + mat[0*3+1] = r0[0][1] - r1[0] * mat[2*3+1]; + mat[1*3+0] = r0[1][0] - r1[1] * mat[2*3+0]; + mat[1*3+1] = r0[1][1] - r1[1] * mat[2*3+1]; + + // m1 = r1 * r3; // 2x1 = 2x1 * 1x1 + mat[0*3+2] = r1[0] * r3; + mat[1*3+2] = r1[1] * r3; + + // m3 = -r3; + mat[2*3+2] = -r3; + + return true; +#endif +} + +/* +============ +idMat3::InertiaTranslate +============ +*/ +idMat3 idMat3::InertiaTranslate( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) const { + idMat3 m; + idVec3 newCenter; + + newCenter = centerOfMass + translation; + + m[0][0] = mass * ( ( centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[1] * newCenter[1] + newCenter[2] * newCenter[2] ) ); + m[1][1] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[0] * newCenter[0] + newCenter[2] * newCenter[2] ) ); + m[2][2] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1] ) + - ( newCenter[0] * newCenter[0] + newCenter[1] * newCenter[1] ) ); + + m[0][1] = m[1][0] = mass * ( newCenter[0] * newCenter[1] - centerOfMass[0] * centerOfMass[1] ); + m[1][2] = m[2][1] = mass * ( newCenter[1] * newCenter[2] - centerOfMass[1] * centerOfMass[2] ); + m[0][2] = m[2][0] = mass * ( newCenter[0] * newCenter[2] - centerOfMass[0] * centerOfMass[2] ); + + return (*this) + m; +} + +/* +============ +idMat3::InertiaTranslateSelf +============ +*/ +idMat3 &idMat3::InertiaTranslateSelf( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) { + idMat3 m; + idVec3 newCenter; + + newCenter = centerOfMass + translation; + + m[0][0] = mass * ( ( centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[1] * newCenter[1] + newCenter[2] * newCenter[2] ) ); + m[1][1] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[0] * newCenter[0] + newCenter[2] * newCenter[2] ) ); + m[2][2] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1] ) + - ( newCenter[0] * newCenter[0] + newCenter[1] * newCenter[1] ) ); + + m[0][1] = m[1][0] = mass * ( newCenter[0] * newCenter[1] - centerOfMass[0] * centerOfMass[1] ); + m[1][2] = m[2][1] = mass * ( newCenter[1] * newCenter[2] - centerOfMass[1] * centerOfMass[2] ); + m[0][2] = m[2][0] = mass * ( newCenter[0] * newCenter[2] - centerOfMass[0] * centerOfMass[2] ); + + (*this) += m; + + return (*this); +} + +/* +============ +idMat3::InertiaRotate +============ +*/ +idMat3 idMat3::InertiaRotate( const idMat3 &rotation ) const { + // NOTE: the rotation matrix is stored column-major + return rotation.Transpose() * (*this) * rotation; +} + +/* +============ +idMat3::InertiaRotateSelf +============ +*/ +idMat3 &idMat3::InertiaRotateSelf( const idMat3 &rotation ) { + // NOTE: the rotation matrix is stored column-major + *this = rotation.Transpose() * (*this) * rotation; + return *this; +} + +/* +============= +idMat3::ToString +============= +*/ +const char *idMat3::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat4 +// +//=============================================================== + +idMat4 mat4_zero( idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ) ); +idMat4 mat4_identity( idVec4( 1, 0, 0, 0 ), idVec4( 0, 1, 0, 0 ), idVec4( 0, 0, 1, 0 ), idVec4( 0, 0, 0, 1 ) ); + +/* +============ +idMat4::Transpose +============ +*/ +idMat4 idMat4::Transpose() const { + idMat4 transpose; + int i, j; + + for( i = 0; i < 4; i++ ) { + for( j = 0; j < 4; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat4::TransposeSelf +============ +*/ +idMat4 &idMat4::TransposeSelf() { + float temp; + int i, j; + + for( i = 0; i < 4; i++ ) { + for( j = i + 1; j < 4; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat4::Determinant +============ +*/ +float idMat4::Determinant() const { + + // 2x2 sub-determinants + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + return ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); +} + +/* +============ +idMat4::InverseSelf +============ +*/ +bool idMat4::InverseSelf() { + // 84+4+16 = 104 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 4x4 determinant + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + det = ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_03_01 = mat[0][0] * mat[3][1] - mat[0][1] * mat[3][0]; + float det2_03_02 = mat[0][0] * mat[3][2] - mat[0][2] * mat[3][0]; + float det2_03_03 = mat[0][0] * mat[3][3] - mat[0][3] * mat[3][0]; + float det2_03_12 = mat[0][1] * mat[3][2] - mat[0][2] * mat[3][1]; + float det2_03_13 = mat[0][1] * mat[3][3] - mat[0][3] * mat[3][1]; + float det2_03_23 = mat[0][2] * mat[3][3] - mat[0][3] * mat[3][2]; + + float det2_13_01 = mat[1][0] * mat[3][1] - mat[1][1] * mat[3][0]; + float det2_13_02 = mat[1][0] * mat[3][2] - mat[1][2] * mat[3][0]; + float det2_13_03 = mat[1][0] * mat[3][3] - mat[1][3] * mat[3][0]; + float det2_13_12 = mat[1][1] * mat[3][2] - mat[1][2] * mat[3][1]; + float det2_13_13 = mat[1][1] * mat[3][3] - mat[1][3] * mat[3][1]; + float det2_13_23 = mat[1][2] * mat[3][3] - mat[1][3] * mat[3][2]; + + // remaining 3x3 sub-determinants + float det3_203_012 = mat[2][0] * det2_03_12 - mat[2][1] * det2_03_02 + mat[2][2] * det2_03_01; + float det3_203_013 = mat[2][0] * det2_03_13 - mat[2][1] * det2_03_03 + mat[2][3] * det2_03_01; + float det3_203_023 = mat[2][0] * det2_03_23 - mat[2][2] * det2_03_03 + mat[2][3] * det2_03_02; + float det3_203_123 = mat[2][1] * det2_03_23 - mat[2][2] * det2_03_13 + mat[2][3] * det2_03_12; + + float det3_213_012 = mat[2][0] * det2_13_12 - mat[2][1] * det2_13_02 + mat[2][2] * det2_13_01; + float det3_213_013 = mat[2][0] * det2_13_13 - mat[2][1] * det2_13_03 + mat[2][3] * det2_13_01; + float det3_213_023 = mat[2][0] * det2_13_23 - mat[2][2] * det2_13_03 + mat[2][3] * det2_13_02; + float det3_213_123 = mat[2][1] * det2_13_23 - mat[2][2] * det2_13_13 + mat[2][3] * det2_13_12; + + float det3_301_012 = mat[3][0] * det2_01_12 - mat[3][1] * det2_01_02 + mat[3][2] * det2_01_01; + float det3_301_013 = mat[3][0] * det2_01_13 - mat[3][1] * det2_01_03 + mat[3][3] * det2_01_01; + float det3_301_023 = mat[3][0] * det2_01_23 - mat[3][2] * det2_01_03 + mat[3][3] * det2_01_02; + float det3_301_123 = mat[3][1] * det2_01_23 - mat[3][2] * det2_01_13 + mat[3][3] * det2_01_12; + + mat[0][0] = - det3_213_123 * invDet; + mat[1][0] = + det3_213_023 * invDet; + mat[2][0] = - det3_213_013 * invDet; + mat[3][0] = + det3_213_012 * invDet; + + mat[0][1] = + det3_203_123 * invDet; + mat[1][1] = - det3_203_023 * invDet; + mat[2][1] = + det3_203_013 * invDet; + mat[3][1] = - det3_203_012 * invDet; + + mat[0][2] = + det3_301_123 * invDet; + mat[1][2] = - det3_301_023 * invDet; + mat[2][2] = + det3_301_013 * invDet; + mat[3][2] = - det3_301_012 * invDet; + + mat[0][3] = - det3_201_123 * invDet; + mat[1][3] = + det3_201_023 * invDet; + mat[2][3] = - det3_201_013 * invDet; + mat[3][3] = + det3_201_012 * invDet; + + return true; +} + +/* +============ +idMat4::InverseFastSelf +============ +*/ +bool idMat4::InverseFastSelf() { +#if 0 + // 84+4+16 = 104 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 4x4 determinant + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + det = ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_03_01 = mat[0][0] * mat[3][1] - mat[0][1] * mat[3][0]; + float det2_03_02 = mat[0][0] * mat[3][2] - mat[0][2] * mat[3][0]; + float det2_03_03 = mat[0][0] * mat[3][3] - mat[0][3] * mat[3][0]; + float det2_03_12 = mat[0][1] * mat[3][2] - mat[0][2] * mat[3][1]; + float det2_03_13 = mat[0][1] * mat[3][3] - mat[0][3] * mat[3][1]; + float det2_03_23 = mat[0][2] * mat[3][3] - mat[0][3] * mat[3][2]; + + float det2_13_01 = mat[1][0] * mat[3][1] - mat[1][1] * mat[3][0]; + float det2_13_02 = mat[1][0] * mat[3][2] - mat[1][2] * mat[3][0]; + float det2_13_03 = mat[1][0] * mat[3][3] - mat[1][3] * mat[3][0]; + float det2_13_12 = mat[1][1] * mat[3][2] - mat[1][2] * mat[3][1]; + float det2_13_13 = mat[1][1] * mat[3][3] - mat[1][3] * mat[3][1]; + float det2_13_23 = mat[1][2] * mat[3][3] - mat[1][3] * mat[3][2]; + + // remaining 3x3 sub-determinants + float det3_203_012 = mat[2][0] * det2_03_12 - mat[2][1] * det2_03_02 + mat[2][2] * det2_03_01; + float det3_203_013 = mat[2][0] * det2_03_13 - mat[2][1] * det2_03_03 + mat[2][3] * det2_03_01; + float det3_203_023 = mat[2][0] * det2_03_23 - mat[2][2] * det2_03_03 + mat[2][3] * det2_03_02; + float det3_203_123 = mat[2][1] * det2_03_23 - mat[2][2] * det2_03_13 + mat[2][3] * det2_03_12; + + float det3_213_012 = mat[2][0] * det2_13_12 - mat[2][1] * det2_13_02 + mat[2][2] * det2_13_01; + float det3_213_013 = mat[2][0] * det2_13_13 - mat[2][1] * det2_13_03 + mat[2][3] * det2_13_01; + float det3_213_023 = mat[2][0] * det2_13_23 - mat[2][2] * det2_13_03 + mat[2][3] * det2_13_02; + float det3_213_123 = mat[2][1] * det2_13_23 - mat[2][2] * det2_13_13 + mat[2][3] * det2_13_12; + + float det3_301_012 = mat[3][0] * det2_01_12 - mat[3][1] * det2_01_02 + mat[3][2] * det2_01_01; + float det3_301_013 = mat[3][0] * det2_01_13 - mat[3][1] * det2_01_03 + mat[3][3] * det2_01_01; + float det3_301_023 = mat[3][0] * det2_01_23 - mat[3][2] * det2_01_03 + mat[3][3] * det2_01_02; + float det3_301_123 = mat[3][1] * det2_01_23 - mat[3][2] * det2_01_13 + mat[3][3] * det2_01_12; + + mat[0][0] = - det3_213_123 * invDet; + mat[1][0] = + det3_213_023 * invDet; + mat[2][0] = - det3_213_013 * invDet; + mat[3][0] = + det3_213_012 * invDet; + + mat[0][1] = + det3_203_123 * invDet; + mat[1][1] = - det3_203_023 * invDet; + mat[2][1] = + det3_203_013 * invDet; + mat[3][1] = - det3_203_012 * invDet; + + mat[0][2] = + det3_301_123 * invDet; + mat[1][2] = - det3_301_023 * invDet; + mat[2][2] = + det3_301_013 * invDet; + mat[3][2] = - det3_301_012 * invDet; + + mat[0][3] = - det3_201_123 * invDet; + mat[1][3] = + det3_201_023 * invDet; + mat[2][3] = - det3_201_013 * invDet; + mat[3][3] = + det3_201_012 * invDet; + + return true; +#elif 0 + // 4*18 = 72 multiplications + // 4 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + d = -d; + mat[4] *= d; + mat[8] *= d; + mat[12] *= d; + d = mat[4] * di; + mat[5] += mat[1] * d; + mat[6] += mat[2] * d; + mat[7] += mat[3] * d; + d = mat[8] * di; + mat[9] += mat[1] * d; + mat[10] += mat[2] * d; + mat[11] += mat[3] * d; + d = mat[12] * di; + mat[13] += mat[1] * d; + mat[14] += mat[2] * d; + mat[15] += mat[3] * d; + di = mat[5]; + s *= di; + mat[5] = d = 1.0f / di; + mat[4] *= d; + mat[6] *= d; + mat[7] *= d; + d = -d; + mat[1] *= d; + mat[9] *= d; + mat[13] *= d; + d = mat[1] * di; + mat[0] += mat[4] * d; + mat[2] += mat[6] * d; + mat[3] += mat[7] * d; + d = mat[9] * di; + mat[8] += mat[4] * d; + mat[10] += mat[6] * d; + mat[11] += mat[7] * d; + d = mat[13] * di; + mat[12] += mat[4] * d; + mat[14] += mat[6] * d; + mat[15] += mat[7] * d; + di = mat[10]; + s *= di; + mat[10] = d = 1.0f / di; + mat[8] *= d; + mat[9] *= d; + mat[11] *= d; + d = -d; + mat[2] *= d; + mat[6] *= d; + mat[14] *= d; + d = mat[2] * di; + mat[0] += mat[8] * d; + mat[1] += mat[9] * d; + mat[3] += mat[11] * d; + d = mat[6] * di; + mat[4] += mat[8] * d; + mat[5] += mat[9] * d; + mat[7] += mat[11] * d; + d = mat[14] * di; + mat[12] += mat[8] * d; + mat[13] += mat[9] * d; + mat[15] += mat[11] * d; + di = mat[15]; + s *= di; + mat[15] = d = 1.0f / di; + mat[12] *= d; + mat[13] *= d; + mat[14] *= d; + d = -d; + mat[3] *= d; + mat[7] *= d; + mat[11] *= d; + d = mat[3] * di; + mat[0] += mat[12] * d; + mat[1] += mat[13] * d; + mat[2] += mat[14] * d; + d = mat[7] * di; + mat[4] += mat[12] * d; + mat[5] += mat[13] * d; + mat[6] += mat[14] * d; + d = mat[11] * di; + mat[8] += mat[12] * d; + mat[9] += mat[13] * d; + mat[10] += mat[14] * d; + + return ( s != 0.0f && !IEEE_FLT_IS_NAN( s ) ); +#else + // 6*8+2*6 = 60 multiplications + // 2*1 = 2 divisions + idMat2 r0, r1, r2, r3; + float a, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); + det = mat[0*4+0] * mat[1*4+1] - mat[0*4+1] * mat[1*4+0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = mat[1*4+1] * invDet; + r0[0][1] = - mat[0*4+1] * invDet; + r0[1][0] = - mat[1*4+0] * invDet; + r0[1][1] = mat[0*4+0] * invDet; + + // r1 = r0 * m1; + r1[0][0] = r0[0][0] * mat[0*4+2] + r0[0][1] * mat[1*4+2]; + r1[0][1] = r0[0][0] * mat[0*4+3] + r0[0][1] * mat[1*4+3]; + r1[1][0] = r0[1][0] * mat[0*4+2] + r0[1][1] * mat[1*4+2]; + r1[1][1] = r0[1][0] * mat[0*4+3] + r0[1][1] * mat[1*4+3]; + + // r2 = m2 * r1; + r2[0][0] = mat[2*4+0] * r1[0][0] + mat[2*4+1] * r1[1][0]; + r2[0][1] = mat[2*4+0] * r1[0][1] + mat[2*4+1] * r1[1][1]; + r2[1][0] = mat[3*4+0] * r1[0][0] + mat[3*4+1] * r1[1][0]; + r2[1][1] = mat[3*4+0] * r1[0][1] + mat[3*4+1] * r1[1][1]; + + // r3 = r2 - m3; + r3[0][0] = r2[0][0] - mat[2*4+2]; + r3[0][1] = r2[0][1] - mat[2*4+3]; + r3[1][0] = r2[1][0] - mat[3*4+2]; + r3[1][1] = r2[1][1] - mat[3*4+3]; + + // r3.InverseSelf(); + det = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = r3[0][0]; + r3[0][0] = r3[1][1] * invDet; + r3[0][1] = - r3[0][1] * invDet; + r3[1][0] = - r3[1][0] * invDet; + r3[1][1] = a * invDet; + + // r2 = m2 * r0; + r2[0][0] = mat[2*4+0] * r0[0][0] + mat[2*4+1] * r0[1][0]; + r2[0][1] = mat[2*4+0] * r0[0][1] + mat[2*4+1] * r0[1][1]; + r2[1][0] = mat[3*4+0] * r0[0][0] + mat[3*4+1] * r0[1][0]; + r2[1][1] = mat[3*4+0] * r0[0][1] + mat[3*4+1] * r0[1][1]; + + // m2 = r3 * r2; + mat[2*4+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0]; + mat[2*4+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1]; + mat[3*4+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0]; + mat[3*4+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1]; + + // m0 = r0 - r1 * m2; + mat[0*4+0] = r0[0][0] - r1[0][0] * mat[2*4+0] - r1[0][1] * mat[3*4+0]; + mat[0*4+1] = r0[0][1] - r1[0][0] * mat[2*4+1] - r1[0][1] * mat[3*4+1]; + mat[1*4+0] = r0[1][0] - r1[1][0] * mat[2*4+0] - r1[1][1] * mat[3*4+0]; + mat[1*4+1] = r0[1][1] - r1[1][0] * mat[2*4+1] - r1[1][1] * mat[3*4+1]; + + // m1 = r1 * r3; + mat[0*4+2] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0]; + mat[0*4+3] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1]; + mat[1*4+2] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0]; + mat[1*4+3] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1]; + + // m3 = -r3; + mat[2*4+2] = -r3[0][0]; + mat[2*4+3] = -r3[0][1]; + mat[3*4+2] = -r3[1][0]; + mat[3*4+3] = -r3[1][1]; + + return true; +#endif +} + +/* +============= +idMat4::ToString +============= +*/ +const char *idMat4::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat5 +// +//=============================================================== + +idMat5 mat5_zero( idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ) ); +idMat5 mat5_identity( idVec5( 1, 0, 0, 0, 0 ), idVec5( 0, 1, 0, 0, 0 ), idVec5( 0, 0, 1, 0, 0 ), idVec5( 0, 0, 0, 1, 0 ), idVec5( 0, 0, 0, 0, 1 ) ); + +/* +============ +idMat5::Transpose +============ +*/ +idMat5 idMat5::Transpose() const { + idMat5 transpose; + int i, j; + + for( i = 0; i < 5; i++ ) { + for( j = 0; j < 5; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat5::TransposeSelf +============ +*/ +idMat5 &idMat5::TransposeSelf() { + float temp; + int i, j; + + for( i = 0; i < 5; i++ ) { + for( j = i + 1; j < 5; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat5::Determinant +============ +*/ +float idMat5::Determinant() const { + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + return mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; +} + +/* +============ +idMat5::InverseSelf +============ +*/ +bool idMat5::InverseSelf() { + // 280+5+25 = 310 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + det = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + + if( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_23_01 = mat[2][0] * mat[3][1] - mat[2][1] * mat[3][0]; + float det2_23_02 = mat[2][0] * mat[3][2] - mat[2][2] * mat[3][0]; + float det2_23_03 = mat[2][0] * mat[3][3] - mat[2][3] * mat[3][0]; + float det2_23_04 = mat[2][0] * mat[3][4] - mat[2][4] * mat[3][0]; + float det2_23_12 = mat[2][1] * mat[3][2] - mat[2][2] * mat[3][1]; + float det2_23_13 = mat[2][1] * mat[3][3] - mat[2][3] * mat[3][1]; + float det2_23_14 = mat[2][1] * mat[3][4] - mat[2][4] * mat[3][1]; + float det2_23_23 = mat[2][2] * mat[3][3] - mat[2][3] * mat[3][2]; + float det2_23_24 = mat[2][2] * mat[3][4] - mat[2][4] * mat[3][2]; + float det2_23_34 = mat[2][3] * mat[3][4] - mat[2][4] * mat[3][3]; + float det2_24_01 = mat[2][0] * mat[4][1] - mat[2][1] * mat[4][0]; + float det2_24_02 = mat[2][0] * mat[4][2] - mat[2][2] * mat[4][0]; + float det2_24_03 = mat[2][0] * mat[4][3] - mat[2][3] * mat[4][0]; + float det2_24_04 = mat[2][0] * mat[4][4] - mat[2][4] * mat[4][0]; + float det2_24_12 = mat[2][1] * mat[4][2] - mat[2][2] * mat[4][1]; + float det2_24_13 = mat[2][1] * mat[4][3] - mat[2][3] * mat[4][1]; + float det2_24_14 = mat[2][1] * mat[4][4] - mat[2][4] * mat[4][1]; + float det2_24_23 = mat[2][2] * mat[4][3] - mat[2][3] * mat[4][2]; + float det2_24_24 = mat[2][2] * mat[4][4] - mat[2][4] * mat[4][2]; + float det2_24_34 = mat[2][3] * mat[4][4] - mat[2][4] * mat[4][3]; + + // remaining 3x3 sub-determinants + float det3_123_012 = mat[1][0] * det2_23_12 - mat[1][1] * det2_23_02 + mat[1][2] * det2_23_01; + float det3_123_013 = mat[1][0] * det2_23_13 - mat[1][1] * det2_23_03 + mat[1][3] * det2_23_01; + float det3_123_014 = mat[1][0] * det2_23_14 - mat[1][1] * det2_23_04 + mat[1][4] * det2_23_01; + float det3_123_023 = mat[1][0] * det2_23_23 - mat[1][2] * det2_23_03 + mat[1][3] * det2_23_02; + float det3_123_024 = mat[1][0] * det2_23_24 - mat[1][2] * det2_23_04 + mat[1][4] * det2_23_02; + float det3_123_034 = mat[1][0] * det2_23_34 - mat[1][3] * det2_23_04 + mat[1][4] * det2_23_03; + float det3_123_123 = mat[1][1] * det2_23_23 - mat[1][2] * det2_23_13 + mat[1][3] * det2_23_12; + float det3_123_124 = mat[1][1] * det2_23_24 - mat[1][2] * det2_23_14 + mat[1][4] * det2_23_12; + float det3_123_134 = mat[1][1] * det2_23_34 - mat[1][3] * det2_23_14 + mat[1][4] * det2_23_13; + float det3_123_234 = mat[1][2] * det2_23_34 - mat[1][3] * det2_23_24 + mat[1][4] * det2_23_23; + float det3_124_012 = mat[1][0] * det2_24_12 - mat[1][1] * det2_24_02 + mat[1][2] * det2_24_01; + float det3_124_013 = mat[1][0] * det2_24_13 - mat[1][1] * det2_24_03 + mat[1][3] * det2_24_01; + float det3_124_014 = mat[1][0] * det2_24_14 - mat[1][1] * det2_24_04 + mat[1][4] * det2_24_01; + float det3_124_023 = mat[1][0] * det2_24_23 - mat[1][2] * det2_24_03 + mat[1][3] * det2_24_02; + float det3_124_024 = mat[1][0] * det2_24_24 - mat[1][2] * det2_24_04 + mat[1][4] * det2_24_02; + float det3_124_034 = mat[1][0] * det2_24_34 - mat[1][3] * det2_24_04 + mat[1][4] * det2_24_03; + float det3_124_123 = mat[1][1] * det2_24_23 - mat[1][2] * det2_24_13 + mat[1][3] * det2_24_12; + float det3_124_124 = mat[1][1] * det2_24_24 - mat[1][2] * det2_24_14 + mat[1][4] * det2_24_12; + float det3_124_134 = mat[1][1] * det2_24_34 - mat[1][3] * det2_24_14 + mat[1][4] * det2_24_13; + float det3_124_234 = mat[1][2] * det2_24_34 - mat[1][3] * det2_24_24 + mat[1][4] * det2_24_23; + float det3_134_012 = mat[1][0] * det2_34_12 - mat[1][1] * det2_34_02 + mat[1][2] * det2_34_01; + float det3_134_013 = mat[1][0] * det2_34_13 - mat[1][1] * det2_34_03 + mat[1][3] * det2_34_01; + float det3_134_014 = mat[1][0] * det2_34_14 - mat[1][1] * det2_34_04 + mat[1][4] * det2_34_01; + float det3_134_023 = mat[1][0] * det2_34_23 - mat[1][2] * det2_34_03 + mat[1][3] * det2_34_02; + float det3_134_024 = mat[1][0] * det2_34_24 - mat[1][2] * det2_34_04 + mat[1][4] * det2_34_02; + float det3_134_034 = mat[1][0] * det2_34_34 - mat[1][3] * det2_34_04 + mat[1][4] * det2_34_03; + float det3_134_123 = mat[1][1] * det2_34_23 - mat[1][2] * det2_34_13 + mat[1][3] * det2_34_12; + float det3_134_124 = mat[1][1] * det2_34_24 - mat[1][2] * det2_34_14 + mat[1][4] * det2_34_12; + float det3_134_134 = mat[1][1] * det2_34_34 - mat[1][3] * det2_34_14 + mat[1][4] * det2_34_13; + float det3_134_234 = mat[1][2] * det2_34_34 - mat[1][3] * det2_34_24 + mat[1][4] * det2_34_23; + + // remaining 4x4 sub-determinants + float det4_0123_0123 = mat[0][0] * det3_123_123 - mat[0][1] * det3_123_023 + mat[0][2] * det3_123_013 - mat[0][3] * det3_123_012; + float det4_0123_0124 = mat[0][0] * det3_123_124 - mat[0][1] * det3_123_024 + mat[0][2] * det3_123_014 - mat[0][4] * det3_123_012; + float det4_0123_0134 = mat[0][0] * det3_123_134 - mat[0][1] * det3_123_034 + mat[0][3] * det3_123_014 - mat[0][4] * det3_123_013; + float det4_0123_0234 = mat[0][0] * det3_123_234 - mat[0][2] * det3_123_034 + mat[0][3] * det3_123_024 - mat[0][4] * det3_123_023; + float det4_0123_1234 = mat[0][1] * det3_123_234 - mat[0][2] * det3_123_134 + mat[0][3] * det3_123_124 - mat[0][4] * det3_123_123; + float det4_0124_0123 = mat[0][0] * det3_124_123 - mat[0][1] * det3_124_023 + mat[0][2] * det3_124_013 - mat[0][3] * det3_124_012; + float det4_0124_0124 = mat[0][0] * det3_124_124 - mat[0][1] * det3_124_024 + mat[0][2] * det3_124_014 - mat[0][4] * det3_124_012; + float det4_0124_0134 = mat[0][0] * det3_124_134 - mat[0][1] * det3_124_034 + mat[0][3] * det3_124_014 - mat[0][4] * det3_124_013; + float det4_0124_0234 = mat[0][0] * det3_124_234 - mat[0][2] * det3_124_034 + mat[0][3] * det3_124_024 - mat[0][4] * det3_124_023; + float det4_0124_1234 = mat[0][1] * det3_124_234 - mat[0][2] * det3_124_134 + mat[0][3] * det3_124_124 - mat[0][4] * det3_124_123; + float det4_0134_0123 = mat[0][0] * det3_134_123 - mat[0][1] * det3_134_023 + mat[0][2] * det3_134_013 - mat[0][3] * det3_134_012; + float det4_0134_0124 = mat[0][0] * det3_134_124 - mat[0][1] * det3_134_024 + mat[0][2] * det3_134_014 - mat[0][4] * det3_134_012; + float det4_0134_0134 = mat[0][0] * det3_134_134 - mat[0][1] * det3_134_034 + mat[0][3] * det3_134_014 - mat[0][4] * det3_134_013; + float det4_0134_0234 = mat[0][0] * det3_134_234 - mat[0][2] * det3_134_034 + mat[0][3] * det3_134_024 - mat[0][4] * det3_134_023; + float det4_0134_1234 = mat[0][1] * det3_134_234 - mat[0][2] * det3_134_134 + mat[0][3] * det3_134_124 - mat[0][4] * det3_134_123; + float det4_0234_0123 = mat[0][0] * det3_234_123 - mat[0][1] * det3_234_023 + mat[0][2] * det3_234_013 - mat[0][3] * det3_234_012; + float det4_0234_0124 = mat[0][0] * det3_234_124 - mat[0][1] * det3_234_024 + mat[0][2] * det3_234_014 - mat[0][4] * det3_234_012; + float det4_0234_0134 = mat[0][0] * det3_234_134 - mat[0][1] * det3_234_034 + mat[0][3] * det3_234_014 - mat[0][4] * det3_234_013; + float det4_0234_0234 = mat[0][0] * det3_234_234 - mat[0][2] * det3_234_034 + mat[0][3] * det3_234_024 - mat[0][4] * det3_234_023; + float det4_0234_1234 = mat[0][1] * det3_234_234 - mat[0][2] * det3_234_134 + mat[0][3] * det3_234_124 - mat[0][4] * det3_234_123; + + mat[0][0] = det4_1234_1234 * invDet; + mat[0][1] = -det4_0234_1234 * invDet; + mat[0][2] = det4_0134_1234 * invDet; + mat[0][3] = -det4_0124_1234 * invDet; + mat[0][4] = det4_0123_1234 * invDet; + + mat[1][0] = -det4_1234_0234 * invDet; + mat[1][1] = det4_0234_0234 * invDet; + mat[1][2] = -det4_0134_0234 * invDet; + mat[1][3] = det4_0124_0234 * invDet; + mat[1][4] = -det4_0123_0234 * invDet; + + mat[2][0] = det4_1234_0134 * invDet; + mat[2][1] = -det4_0234_0134 * invDet; + mat[2][2] = det4_0134_0134 * invDet; + mat[2][3] = -det4_0124_0134 * invDet; + mat[2][4] = det4_0123_0134 * invDet; + + mat[3][0] = -det4_1234_0124 * invDet; + mat[3][1] = det4_0234_0124 * invDet; + mat[3][2] = -det4_0134_0124 * invDet; + mat[3][3] = det4_0124_0124 * invDet; + mat[3][4] = -det4_0123_0124 * invDet; + + mat[4][0] = det4_1234_0123 * invDet; + mat[4][1] = -det4_0234_0123 * invDet; + mat[4][2] = det4_0134_0123 * invDet; + mat[4][3] = -det4_0124_0123 * invDet; + mat[4][4] = det4_0123_0123 * invDet; + + return true; +} + +/* +============ +idMat5::InverseFastSelf +============ +*/ +bool idMat5::InverseFastSelf() { +#if 0 + // 280+5+25 = 310 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + det = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + + if( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_23_01 = mat[2][0] * mat[3][1] - mat[2][1] * mat[3][0]; + float det2_23_02 = mat[2][0] * mat[3][2] - mat[2][2] * mat[3][0]; + float det2_23_03 = mat[2][0] * mat[3][3] - mat[2][3] * mat[3][0]; + float det2_23_04 = mat[2][0] * mat[3][4] - mat[2][4] * mat[3][0]; + float det2_23_12 = mat[2][1] * mat[3][2] - mat[2][2] * mat[3][1]; + float det2_23_13 = mat[2][1] * mat[3][3] - mat[2][3] * mat[3][1]; + float det2_23_14 = mat[2][1] * mat[3][4] - mat[2][4] * mat[3][1]; + float det2_23_23 = mat[2][2] * mat[3][3] - mat[2][3] * mat[3][2]; + float det2_23_24 = mat[2][2] * mat[3][4] - mat[2][4] * mat[3][2]; + float det2_23_34 = mat[2][3] * mat[3][4] - mat[2][4] * mat[3][3]; + float det2_24_01 = mat[2][0] * mat[4][1] - mat[2][1] * mat[4][0]; + float det2_24_02 = mat[2][0] * mat[4][2] - mat[2][2] * mat[4][0]; + float det2_24_03 = mat[2][0] * mat[4][3] - mat[2][3] * mat[4][0]; + float det2_24_04 = mat[2][0] * mat[4][4] - mat[2][4] * mat[4][0]; + float det2_24_12 = mat[2][1] * mat[4][2] - mat[2][2] * mat[4][1]; + float det2_24_13 = mat[2][1] * mat[4][3] - mat[2][3] * mat[4][1]; + float det2_24_14 = mat[2][1] * mat[4][4] - mat[2][4] * mat[4][1]; + float det2_24_23 = mat[2][2] * mat[4][3] - mat[2][3] * mat[4][2]; + float det2_24_24 = mat[2][2] * mat[4][4] - mat[2][4] * mat[4][2]; + float det2_24_34 = mat[2][3] * mat[4][4] - mat[2][4] * mat[4][3]; + + // remaining 3x3 sub-determinants + float det3_123_012 = mat[1][0] * det2_23_12 - mat[1][1] * det2_23_02 + mat[1][2] * det2_23_01; + float det3_123_013 = mat[1][0] * det2_23_13 - mat[1][1] * det2_23_03 + mat[1][3] * det2_23_01; + float det3_123_014 = mat[1][0] * det2_23_14 - mat[1][1] * det2_23_04 + mat[1][4] * det2_23_01; + float det3_123_023 = mat[1][0] * det2_23_23 - mat[1][2] * det2_23_03 + mat[1][3] * det2_23_02; + float det3_123_024 = mat[1][0] * det2_23_24 - mat[1][2] * det2_23_04 + mat[1][4] * det2_23_02; + float det3_123_034 = mat[1][0] * det2_23_34 - mat[1][3] * det2_23_04 + mat[1][4] * det2_23_03; + float det3_123_123 = mat[1][1] * det2_23_23 - mat[1][2] * det2_23_13 + mat[1][3] * det2_23_12; + float det3_123_124 = mat[1][1] * det2_23_24 - mat[1][2] * det2_23_14 + mat[1][4] * det2_23_12; + float det3_123_134 = mat[1][1] * det2_23_34 - mat[1][3] * det2_23_14 + mat[1][4] * det2_23_13; + float det3_123_234 = mat[1][2] * det2_23_34 - mat[1][3] * det2_23_24 + mat[1][4] * det2_23_23; + float det3_124_012 = mat[1][0] * det2_24_12 - mat[1][1] * det2_24_02 + mat[1][2] * det2_24_01; + float det3_124_013 = mat[1][0] * det2_24_13 - mat[1][1] * det2_24_03 + mat[1][3] * det2_24_01; + float det3_124_014 = mat[1][0] * det2_24_14 - mat[1][1] * det2_24_04 + mat[1][4] * det2_24_01; + float det3_124_023 = mat[1][0] * det2_24_23 - mat[1][2] * det2_24_03 + mat[1][3] * det2_24_02; + float det3_124_024 = mat[1][0] * det2_24_24 - mat[1][2] * det2_24_04 + mat[1][4] * det2_24_02; + float det3_124_034 = mat[1][0] * det2_24_34 - mat[1][3] * det2_24_04 + mat[1][4] * det2_24_03; + float det3_124_123 = mat[1][1] * det2_24_23 - mat[1][2] * det2_24_13 + mat[1][3] * det2_24_12; + float det3_124_124 = mat[1][1] * det2_24_24 - mat[1][2] * det2_24_14 + mat[1][4] * det2_24_12; + float det3_124_134 = mat[1][1] * det2_24_34 - mat[1][3] * det2_24_14 + mat[1][4] * det2_24_13; + float det3_124_234 = mat[1][2] * det2_24_34 - mat[1][3] * det2_24_24 + mat[1][4] * det2_24_23; + float det3_134_012 = mat[1][0] * det2_34_12 - mat[1][1] * det2_34_02 + mat[1][2] * det2_34_01; + float det3_134_013 = mat[1][0] * det2_34_13 - mat[1][1] * det2_34_03 + mat[1][3] * det2_34_01; + float det3_134_014 = mat[1][0] * det2_34_14 - mat[1][1] * det2_34_04 + mat[1][4] * det2_34_01; + float det3_134_023 = mat[1][0] * det2_34_23 - mat[1][2] * det2_34_03 + mat[1][3] * det2_34_02; + float det3_134_024 = mat[1][0] * det2_34_24 - mat[1][2] * det2_34_04 + mat[1][4] * det2_34_02; + float det3_134_034 = mat[1][0] * det2_34_34 - mat[1][3] * det2_34_04 + mat[1][4] * det2_34_03; + float det3_134_123 = mat[1][1] * det2_34_23 - mat[1][2] * det2_34_13 + mat[1][3] * det2_34_12; + float det3_134_124 = mat[1][1] * det2_34_24 - mat[1][2] * det2_34_14 + mat[1][4] * det2_34_12; + float det3_134_134 = mat[1][1] * det2_34_34 - mat[1][3] * det2_34_14 + mat[1][4] * det2_34_13; + float det3_134_234 = mat[1][2] * det2_34_34 - mat[1][3] * det2_34_24 + mat[1][4] * det2_34_23; + + // remaining 4x4 sub-determinants + float det4_0123_0123 = mat[0][0] * det3_123_123 - mat[0][1] * det3_123_023 + mat[0][2] * det3_123_013 - mat[0][3] * det3_123_012; + float det4_0123_0124 = mat[0][0] * det3_123_124 - mat[0][1] * det3_123_024 + mat[0][2] * det3_123_014 - mat[0][4] * det3_123_012; + float det4_0123_0134 = mat[0][0] * det3_123_134 - mat[0][1] * det3_123_034 + mat[0][3] * det3_123_014 - mat[0][4] * det3_123_013; + float det4_0123_0234 = mat[0][0] * det3_123_234 - mat[0][2] * det3_123_034 + mat[0][3] * det3_123_024 - mat[0][4] * det3_123_023; + float det4_0123_1234 = mat[0][1] * det3_123_234 - mat[0][2] * det3_123_134 + mat[0][3] * det3_123_124 - mat[0][4] * det3_123_123; + float det4_0124_0123 = mat[0][0] * det3_124_123 - mat[0][1] * det3_124_023 + mat[0][2] * det3_124_013 - mat[0][3] * det3_124_012; + float det4_0124_0124 = mat[0][0] * det3_124_124 - mat[0][1] * det3_124_024 + mat[0][2] * det3_124_014 - mat[0][4] * det3_124_012; + float det4_0124_0134 = mat[0][0] * det3_124_134 - mat[0][1] * det3_124_034 + mat[0][3] * det3_124_014 - mat[0][4] * det3_124_013; + float det4_0124_0234 = mat[0][0] * det3_124_234 - mat[0][2] * det3_124_034 + mat[0][3] * det3_124_024 - mat[0][4] * det3_124_023; + float det4_0124_1234 = mat[0][1] * det3_124_234 - mat[0][2] * det3_124_134 + mat[0][3] * det3_124_124 - mat[0][4] * det3_124_123; + float det4_0134_0123 = mat[0][0] * det3_134_123 - mat[0][1] * det3_134_023 + mat[0][2] * det3_134_013 - mat[0][3] * det3_134_012; + float det4_0134_0124 = mat[0][0] * det3_134_124 - mat[0][1] * det3_134_024 + mat[0][2] * det3_134_014 - mat[0][4] * det3_134_012; + float det4_0134_0134 = mat[0][0] * det3_134_134 - mat[0][1] * det3_134_034 + mat[0][3] * det3_134_014 - mat[0][4] * det3_134_013; + float det4_0134_0234 = mat[0][0] * det3_134_234 - mat[0][2] * det3_134_034 + mat[0][3] * det3_134_024 - mat[0][4] * det3_134_023; + float det4_0134_1234 = mat[0][1] * det3_134_234 - mat[0][2] * det3_134_134 + mat[0][3] * det3_134_124 - mat[0][4] * det3_134_123; + float det4_0234_0123 = mat[0][0] * det3_234_123 - mat[0][1] * det3_234_023 + mat[0][2] * det3_234_013 - mat[0][3] * det3_234_012; + float det4_0234_0124 = mat[0][0] * det3_234_124 - mat[0][1] * det3_234_024 + mat[0][2] * det3_234_014 - mat[0][4] * det3_234_012; + float det4_0234_0134 = mat[0][0] * det3_234_134 - mat[0][1] * det3_234_034 + mat[0][3] * det3_234_014 - mat[0][4] * det3_234_013; + float det4_0234_0234 = mat[0][0] * det3_234_234 - mat[0][2] * det3_234_034 + mat[0][3] * det3_234_024 - mat[0][4] * det3_234_023; + float det4_0234_1234 = mat[0][1] * det3_234_234 - mat[0][2] * det3_234_134 + mat[0][3] * det3_234_124 - mat[0][4] * det3_234_123; + + mat[0][0] = det4_1234_1234 * invDet; + mat[0][1] = -det4_0234_1234 * invDet; + mat[0][2] = det4_0134_1234 * invDet; + mat[0][3] = -det4_0124_1234 * invDet; + mat[0][4] = det4_0123_1234 * invDet; + + mat[1][0] = -det4_1234_0234 * invDet; + mat[1][1] = det4_0234_0234 * invDet; + mat[1][2] = -det4_0134_0234 * invDet; + mat[1][3] = det4_0124_0234 * invDet; + mat[1][4] = -det4_0123_0234 * invDet; + + mat[2][0] = det4_1234_0134 * invDet; + mat[2][1] = -det4_0234_0134 * invDet; + mat[2][2] = det4_0134_0134 * invDet; + mat[2][3] = -det4_0124_0134 * invDet; + mat[2][4] = det4_0123_0134 * invDet; + + mat[3][0] = -det4_1234_0124 * invDet; + mat[3][1] = det4_0234_0124 * invDet; + mat[3][2] = -det4_0134_0124 * invDet; + mat[3][3] = det4_0124_0124 * invDet; + mat[3][4] = -det4_0123_0124 * invDet; + + mat[4][0] = det4_1234_0123 * invDet; + mat[4][1] = -det4_0234_0123 * invDet; + mat[4][2] = det4_0134_0123 * invDet; + mat[4][3] = -det4_0124_0123 * invDet; + mat[4][4] = det4_0123_0123 * invDet; + + return true; +#elif 0 + // 5*28 = 140 multiplications + // 5 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + mat[4] *= d; + d = -d; + mat[5] *= d; + mat[10] *= d; + mat[15] *= d; + mat[20] *= d; + d = mat[5] * di; + mat[6] += mat[1] * d; + mat[7] += mat[2] * d; + mat[8] += mat[3] * d; + mat[9] += mat[4] * d; + d = mat[10] * di; + mat[11] += mat[1] * d; + mat[12] += mat[2] * d; + mat[13] += mat[3] * d; + mat[14] += mat[4] * d; + d = mat[15] * di; + mat[16] += mat[1] * d; + mat[17] += mat[2] * d; + mat[18] += mat[3] * d; + mat[19] += mat[4] * d; + d = mat[20] * di; + mat[21] += mat[1] * d; + mat[22] += mat[2] * d; + mat[23] += mat[3] * d; + mat[24] += mat[4] * d; + di = mat[6]; + s *= di; + mat[6] = d = 1.0f / di; + mat[5] *= d; + mat[7] *= d; + mat[8] *= d; + mat[9] *= d; + d = -d; + mat[1] *= d; + mat[11] *= d; + mat[16] *= d; + mat[21] *= d; + d = mat[1] * di; + mat[0] += mat[5] * d; + mat[2] += mat[7] * d; + mat[3] += mat[8] * d; + mat[4] += mat[9] * d; + d = mat[11] * di; + mat[10] += mat[5] * d; + mat[12] += mat[7] * d; + mat[13] += mat[8] * d; + mat[14] += mat[9] * d; + d = mat[16] * di; + mat[15] += mat[5] * d; + mat[17] += mat[7] * d; + mat[18] += mat[8] * d; + mat[19] += mat[9] * d; + d = mat[21] * di; + mat[20] += mat[5] * d; + mat[22] += mat[7] * d; + mat[23] += mat[8] * d; + mat[24] += mat[9] * d; + di = mat[12]; + s *= di; + mat[12] = d = 1.0f / di; + mat[10] *= d; + mat[11] *= d; + mat[13] *= d; + mat[14] *= d; + d = -d; + mat[2] *= d; + mat[7] *= d; + mat[17] *= d; + mat[22] *= d; + d = mat[2] * di; + mat[0] += mat[10] * d; + mat[1] += mat[11] * d; + mat[3] += mat[13] * d; + mat[4] += mat[14] * d; + d = mat[7] * di; + mat[5] += mat[10] * d; + mat[6] += mat[11] * d; + mat[8] += mat[13] * d; + mat[9] += mat[14] * d; + d = mat[17] * di; + mat[15] += mat[10] * d; + mat[16] += mat[11] * d; + mat[18] += mat[13] * d; + mat[19] += mat[14] * d; + d = mat[22] * di; + mat[20] += mat[10] * d; + mat[21] += mat[11] * d; + mat[23] += mat[13] * d; + mat[24] += mat[14] * d; + di = mat[18]; + s *= di; + mat[18] = d = 1.0f / di; + mat[15] *= d; + mat[16] *= d; + mat[17] *= d; + mat[19] *= d; + d = -d; + mat[3] *= d; + mat[8] *= d; + mat[13] *= d; + mat[23] *= d; + d = mat[3] * di; + mat[0] += mat[15] * d; + mat[1] += mat[16] * d; + mat[2] += mat[17] * d; + mat[4] += mat[19] * d; + d = mat[8] * di; + mat[5] += mat[15] * d; + mat[6] += mat[16] * d; + mat[7] += mat[17] * d; + mat[9] += mat[19] * d; + d = mat[13] * di; + mat[10] += mat[15] * d; + mat[11] += mat[16] * d; + mat[12] += mat[17] * d; + mat[14] += mat[19] * d; + d = mat[23] * di; + mat[20] += mat[15] * d; + mat[21] += mat[16] * d; + mat[22] += mat[17] * d; + mat[24] += mat[19] * d; + di = mat[24]; + s *= di; + mat[24] = d = 1.0f / di; + mat[20] *= d; + mat[21] *= d; + mat[22] *= d; + mat[23] *= d; + d = -d; + mat[4] *= d; + mat[9] *= d; + mat[14] *= d; + mat[19] *= d; + d = mat[4] * di; + mat[0] += mat[20] * d; + mat[1] += mat[21] * d; + mat[2] += mat[22] * d; + mat[3] += mat[23] * d; + d = mat[9] * di; + mat[5] += mat[20] * d; + mat[6] += mat[21] * d; + mat[7] += mat[22] * d; + mat[8] += mat[23] * d; + d = mat[14] * di; + mat[10] += mat[20] * d; + mat[11] += mat[21] * d; + mat[12] += mat[22] * d; + mat[13] += mat[23] * d; + d = mat[19] * di; + mat[15] += mat[20] * d; + mat[16] += mat[21] * d; + mat[17] += mat[22] * d; + mat[18] += mat[23] * d; + + return ( s != 0.0f && !IEEE_FLT_IS_NAN( s ) ); +#else + // 86+30+6 = 122 multiplications + // 2*1 = 2 divisions + idMat3 r0, r1, r2, r3; + float c0, c1, c2, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); // 3x3 + c0 = mat[1*5+1] * mat[2*5+2] - mat[1*5+2] * mat[2*5+1]; + c1 = mat[1*5+2] * mat[2*5+0] - mat[1*5+0] * mat[2*5+2]; + c2 = mat[1*5+0] * mat[2*5+1] - mat[1*5+1] * mat[2*5+0]; + + det = mat[0*5+0] * c0 + mat[0*5+1] * c1 + mat[0*5+2] * c2; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = c0 * invDet; + r0[0][1] = ( mat[0*5+2] * mat[2*5+1] - mat[0*5+1] * mat[2*5+2] ) * invDet; + r0[0][2] = ( mat[0*5+1] * mat[1*5+2] - mat[0*5+2] * mat[1*5+1] ) * invDet; + r0[1][0] = c1 * invDet; + r0[1][1] = ( mat[0*5+0] * mat[2*5+2] - mat[0*5+2] * mat[2*5+0] ) * invDet; + r0[1][2] = ( mat[0*5+2] * mat[1*5+0] - mat[0*5+0] * mat[1*5+2] ) * invDet; + r0[2][0] = c2 * invDet; + r0[2][1] = ( mat[0*5+1] * mat[2*5+0] - mat[0*5+0] * mat[2*5+1] ) * invDet; + r0[2][2] = ( mat[0*5+0] * mat[1*5+1] - mat[0*5+1] * mat[1*5+0] ) * invDet; + + // r1 = r0 * m1; // 3x2 = 3x3 * 3x2 + r1[0][0] = r0[0][0] * mat[0*5+3] + r0[0][1] * mat[1*5+3] + r0[0][2] * mat[2*5+3]; + r1[0][1] = r0[0][0] * mat[0*5+4] + r0[0][1] * mat[1*5+4] + r0[0][2] * mat[2*5+4]; + r1[1][0] = r0[1][0] * mat[0*5+3] + r0[1][1] * mat[1*5+3] + r0[1][2] * mat[2*5+3]; + r1[1][1] = r0[1][0] * mat[0*5+4] + r0[1][1] * mat[1*5+4] + r0[1][2] * mat[2*5+4]; + r1[2][0] = r0[2][0] * mat[0*5+3] + r0[2][1] * mat[1*5+3] + r0[2][2] * mat[2*5+3]; + r1[2][1] = r0[2][0] * mat[0*5+4] + r0[2][1] * mat[1*5+4] + r0[2][2] * mat[2*5+4]; + + // r2 = m2 * r1; // 2x2 = 2x3 * 3x2 + r2[0][0] = mat[3*5+0] * r1[0][0] + mat[3*5+1] * r1[1][0] + mat[3*5+2] * r1[2][0]; + r2[0][1] = mat[3*5+0] * r1[0][1] + mat[3*5+1] * r1[1][1] + mat[3*5+2] * r1[2][1]; + r2[1][0] = mat[4*5+0] * r1[0][0] + mat[4*5+1] * r1[1][0] + mat[4*5+2] * r1[2][0]; + r2[1][1] = mat[4*5+0] * r1[0][1] + mat[4*5+1] * r1[1][1] + mat[4*5+2] * r1[2][1]; + + // r3 = r2 - m3; // 2x2 = 2x2 - 2x2 + r3[0][0] = r2[0][0] - mat[3*5+3]; + r3[0][1] = r2[0][1] - mat[3*5+4]; + r3[1][0] = r2[1][0] - mat[4*5+3]; + r3[1][1] = r2[1][1] - mat[4*5+4]; + + // r3.InverseSelf(); // 2x2 + det = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + c0 = r3[0][0]; + r3[0][0] = r3[1][1] * invDet; + r3[0][1] = - r3[0][1] * invDet; + r3[1][0] = - r3[1][0] * invDet; + r3[1][1] = c0 * invDet; + + // r2 = m2 * r0; // 2x3 = 2x3 * 3x3 + r2[0][0] = mat[3*5+0] * r0[0][0] + mat[3*5+1] * r0[1][0] + mat[3*5+2] * r0[2][0]; + r2[0][1] = mat[3*5+0] * r0[0][1] + mat[3*5+1] * r0[1][1] + mat[3*5+2] * r0[2][1]; + r2[0][2] = mat[3*5+0] * r0[0][2] + mat[3*5+1] * r0[1][2] + mat[3*5+2] * r0[2][2]; + r2[1][0] = mat[4*5+0] * r0[0][0] + mat[4*5+1] * r0[1][0] + mat[4*5+2] * r0[2][0]; + r2[1][1] = mat[4*5+0] * r0[0][1] + mat[4*5+1] * r0[1][1] + mat[4*5+2] * r0[2][1]; + r2[1][2] = mat[4*5+0] * r0[0][2] + mat[4*5+1] * r0[1][2] + mat[4*5+2] * r0[2][2]; + + // m2 = r3 * r2; // 2x3 = 2x2 * 2x3 + mat[3*5+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0]; + mat[3*5+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1]; + mat[3*5+2] = r3[0][0] * r2[0][2] + r3[0][1] * r2[1][2]; + mat[4*5+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0]; + mat[4*5+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1]; + mat[4*5+2] = r3[1][0] * r2[0][2] + r3[1][1] * r2[1][2]; + + // m0 = r0 - r1 * m2; // 3x3 = 3x3 - 3x2 * 2x3 + mat[0*5+0] = r0[0][0] - r1[0][0] * mat[3*5+0] - r1[0][1] * mat[4*5+0]; + mat[0*5+1] = r0[0][1] - r1[0][0] * mat[3*5+1] - r1[0][1] * mat[4*5+1]; + mat[0*5+2] = r0[0][2] - r1[0][0] * mat[3*5+2] - r1[0][1] * mat[4*5+2]; + mat[1*5+0] = r0[1][0] - r1[1][0] * mat[3*5+0] - r1[1][1] * mat[4*5+0]; + mat[1*5+1] = r0[1][1] - r1[1][0] * mat[3*5+1] - r1[1][1] * mat[4*5+1]; + mat[1*5+2] = r0[1][2] - r1[1][0] * mat[3*5+2] - r1[1][1] * mat[4*5+2]; + mat[2*5+0] = r0[2][0] - r1[2][0] * mat[3*5+0] - r1[2][1] * mat[4*5+0]; + mat[2*5+1] = r0[2][1] - r1[2][0] * mat[3*5+1] - r1[2][1] * mat[4*5+1]; + mat[2*5+2] = r0[2][2] - r1[2][0] * mat[3*5+2] - r1[2][1] * mat[4*5+2]; + + // m1 = r1 * r3; // 3x2 = 3x2 * 2x2 + mat[0*5+3] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0]; + mat[0*5+4] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1]; + mat[1*5+3] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0]; + mat[1*5+4] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1]; + mat[2*5+3] = r1[2][0] * r3[0][0] + r1[2][1] * r3[1][0]; + mat[2*5+4] = r1[2][0] * r3[0][1] + r1[2][1] * r3[1][1]; + + // m3 = -r3; // 2x2 = - 2x2 + mat[3*5+3] = -r3[0][0]; + mat[3*5+4] = -r3[0][1]; + mat[4*5+3] = -r3[1][0]; + mat[4*5+4] = -r3[1][1]; + + return true; +#endif +} + +/* +============= +idMat5::ToString +============= +*/ +const char *idMat5::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat6 +// +//=============================================================== + +idMat6 mat6_zero( idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ) ); +idMat6 mat6_identity( idVec6( 1, 0, 0, 0, 0, 0 ), idVec6( 0, 1, 0, 0, 0, 0 ), idVec6( 0, 0, 1, 0, 0, 0 ), idVec6( 0, 0, 0, 1, 0, 0 ), idVec6( 0, 0, 0, 0, 1, 0 ), idVec6( 0, 0, 0, 0, 0, 1 ) ); + +/* +============ +idMat6::Transpose +============ +*/ +idMat6 idMat6::Transpose() const { + idMat6 transpose; + int i, j; + + for( i = 0; i < 6; i++ ) { + for( j = 0; j < 6; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat6::TransposeSelf +============ +*/ +idMat6 &idMat6::TransposeSelf() { + float temp; + int i, j; + + for( i = 0; i < 6; i++ ) { + for( j = i + 1; j < 6; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat6::Determinant +============ +*/ +float idMat6::Determinant() const { + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + return mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; +} + +/* +============ +idMat6::InverseSelf +============ +*/ +bool idMat6::InverseSelf() { + // 810+6+36 = 852 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + det = mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_05 = mat[3][0] * mat[4][5] - mat[3][5] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_15 = mat[3][1] * mat[4][5] - mat[3][5] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_25 = mat[3][2] * mat[4][5] - mat[3][5] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + float det2_34_35 = mat[3][3] * mat[4][5] - mat[3][5] * mat[4][3]; + float det2_34_45 = mat[3][4] * mat[4][5] - mat[3][5] * mat[4][4]; + float det2_35_01 = mat[3][0] * mat[5][1] - mat[3][1] * mat[5][0]; + float det2_35_02 = mat[3][0] * mat[5][2] - mat[3][2] * mat[5][0]; + float det2_35_03 = mat[3][0] * mat[5][3] - mat[3][3] * mat[5][0]; + float det2_35_04 = mat[3][0] * mat[5][4] - mat[3][4] * mat[5][0]; + float det2_35_05 = mat[3][0] * mat[5][5] - mat[3][5] * mat[5][0]; + float det2_35_12 = mat[3][1] * mat[5][2] - mat[3][2] * mat[5][1]; + float det2_35_13 = mat[3][1] * mat[5][3] - mat[3][3] * mat[5][1]; + float det2_35_14 = mat[3][1] * mat[5][4] - mat[3][4] * mat[5][1]; + float det2_35_15 = mat[3][1] * mat[5][5] - mat[3][5] * mat[5][1]; + float det2_35_23 = mat[3][2] * mat[5][3] - mat[3][3] * mat[5][2]; + float det2_35_24 = mat[3][2] * mat[5][4] - mat[3][4] * mat[5][2]; + float det2_35_25 = mat[3][2] * mat[5][5] - mat[3][5] * mat[5][2]; + float det2_35_34 = mat[3][3] * mat[5][4] - mat[3][4] * mat[5][3]; + float det2_35_35 = mat[3][3] * mat[5][5] - mat[3][5] * mat[5][3]; + float det2_35_45 = mat[3][4] * mat[5][5] - mat[3][5] * mat[5][4]; + + // remaining 3x3 sub-determinants + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_015 = mat[2][0] * det2_34_15 - mat[2][1] * det2_34_05 + mat[2][5] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_025 = mat[2][0] * det2_34_25 - mat[2][2] * det2_34_05 + mat[2][5] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_035 = mat[2][0] * det2_34_35 - mat[2][3] * det2_34_05 + mat[2][5] * det2_34_03; + float det3_234_045 = mat[2][0] * det2_34_45 - mat[2][4] * det2_34_05 + mat[2][5] * det2_34_04; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_125 = mat[2][1] * det2_34_25 - mat[2][2] * det2_34_15 + mat[2][5] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_135 = mat[2][1] * det2_34_35 - mat[2][3] * det2_34_15 + mat[2][5] * det2_34_13; + float det3_234_145 = mat[2][1] * det2_34_45 - mat[2][4] * det2_34_15 + mat[2][5] * det2_34_14; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + float det3_234_235 = mat[2][2] * det2_34_35 - mat[2][3] * det2_34_25 + mat[2][5] * det2_34_23; + float det3_234_245 = mat[2][2] * det2_34_45 - mat[2][4] * det2_34_25 + mat[2][5] * det2_34_24; + float det3_234_345 = mat[2][3] * det2_34_45 - mat[2][4] * det2_34_35 + mat[2][5] * det2_34_34; + float det3_235_012 = mat[2][0] * det2_35_12 - mat[2][1] * det2_35_02 + mat[2][2] * det2_35_01; + float det3_235_013 = mat[2][0] * det2_35_13 - mat[2][1] * det2_35_03 + mat[2][3] * det2_35_01; + float det3_235_014 = mat[2][0] * det2_35_14 - mat[2][1] * det2_35_04 + mat[2][4] * det2_35_01; + float det3_235_015 = mat[2][0] * det2_35_15 - mat[2][1] * det2_35_05 + mat[2][5] * det2_35_01; + float det3_235_023 = mat[2][0] * det2_35_23 - mat[2][2] * det2_35_03 + mat[2][3] * det2_35_02; + float det3_235_024 = mat[2][0] * det2_35_24 - mat[2][2] * det2_35_04 + mat[2][4] * det2_35_02; + float det3_235_025 = mat[2][0] * det2_35_25 - mat[2][2] * det2_35_05 + mat[2][5] * det2_35_02; + float det3_235_034 = mat[2][0] * det2_35_34 - mat[2][3] * det2_35_04 + mat[2][4] * det2_35_03; + float det3_235_035 = mat[2][0] * det2_35_35 - mat[2][3] * det2_35_05 + mat[2][5] * det2_35_03; + float det3_235_045 = mat[2][0] * det2_35_45 - mat[2][4] * det2_35_05 + mat[2][5] * det2_35_04; + float det3_235_123 = mat[2][1] * det2_35_23 - mat[2][2] * det2_35_13 + mat[2][3] * det2_35_12; + float det3_235_124 = mat[2][1] * det2_35_24 - mat[2][2] * det2_35_14 + mat[2][4] * det2_35_12; + float det3_235_125 = mat[2][1] * det2_35_25 - mat[2][2] * det2_35_15 + mat[2][5] * det2_35_12; + float det3_235_134 = mat[2][1] * det2_35_34 - mat[2][3] * det2_35_14 + mat[2][4] * det2_35_13; + float det3_235_135 = mat[2][1] * det2_35_35 - mat[2][3] * det2_35_15 + mat[2][5] * det2_35_13; + float det3_235_145 = mat[2][1] * det2_35_45 - mat[2][4] * det2_35_15 + mat[2][5] * det2_35_14; + float det3_235_234 = mat[2][2] * det2_35_34 - mat[2][3] * det2_35_24 + mat[2][4] * det2_35_23; + float det3_235_235 = mat[2][2] * det2_35_35 - mat[2][3] * det2_35_25 + mat[2][5] * det2_35_23; + float det3_235_245 = mat[2][2] * det2_35_45 - mat[2][4] * det2_35_25 + mat[2][5] * det2_35_24; + float det3_235_345 = mat[2][3] * det2_35_45 - mat[2][4] * det2_35_35 + mat[2][5] * det2_35_34; + float det3_245_012 = mat[2][0] * det2_45_12 - mat[2][1] * det2_45_02 + mat[2][2] * det2_45_01; + float det3_245_013 = mat[2][0] * det2_45_13 - mat[2][1] * det2_45_03 + mat[2][3] * det2_45_01; + float det3_245_014 = mat[2][0] * det2_45_14 - mat[2][1] * det2_45_04 + mat[2][4] * det2_45_01; + float det3_245_015 = mat[2][0] * det2_45_15 - mat[2][1] * det2_45_05 + mat[2][5] * det2_45_01; + float det3_245_023 = mat[2][0] * det2_45_23 - mat[2][2] * det2_45_03 + mat[2][3] * det2_45_02; + float det3_245_024 = mat[2][0] * det2_45_24 - mat[2][2] * det2_45_04 + mat[2][4] * det2_45_02; + float det3_245_025 = mat[2][0] * det2_45_25 - mat[2][2] * det2_45_05 + mat[2][5] * det2_45_02; + float det3_245_034 = mat[2][0] * det2_45_34 - mat[2][3] * det2_45_04 + mat[2][4] * det2_45_03; + float det3_245_035 = mat[2][0] * det2_45_35 - mat[2][3] * det2_45_05 + mat[2][5] * det2_45_03; + float det3_245_045 = mat[2][0] * det2_45_45 - mat[2][4] * det2_45_05 + mat[2][5] * det2_45_04; + float det3_245_123 = mat[2][1] * det2_45_23 - mat[2][2] * det2_45_13 + mat[2][3] * det2_45_12; + float det3_245_124 = mat[2][1] * det2_45_24 - mat[2][2] * det2_45_14 + mat[2][4] * det2_45_12; + float det3_245_125 = mat[2][1] * det2_45_25 - mat[2][2] * det2_45_15 + mat[2][5] * det2_45_12; + float det3_245_134 = mat[2][1] * det2_45_34 - mat[2][3] * det2_45_14 + mat[2][4] * det2_45_13; + float det3_245_135 = mat[2][1] * det2_45_35 - mat[2][3] * det2_45_15 + mat[2][5] * det2_45_13; + float det3_245_145 = mat[2][1] * det2_45_45 - mat[2][4] * det2_45_15 + mat[2][5] * det2_45_14; + float det3_245_234 = mat[2][2] * det2_45_34 - mat[2][3] * det2_45_24 + mat[2][4] * det2_45_23; + float det3_245_235 = mat[2][2] * det2_45_35 - mat[2][3] * det2_45_25 + mat[2][5] * det2_45_23; + float det3_245_245 = mat[2][2] * det2_45_45 - mat[2][4] * det2_45_25 + mat[2][5] * det2_45_24; + float det3_245_345 = mat[2][3] * det2_45_45 - mat[2][4] * det2_45_35 + mat[2][5] * det2_45_34; + + // remaining 4x4 sub-determinants + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0125 = mat[1][0] * det3_234_125 - mat[1][1] * det3_234_025 + mat[1][2] * det3_234_015 - mat[1][5] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0135 = mat[1][0] * det3_234_135 - mat[1][1] * det3_234_035 + mat[1][3] * det3_234_015 - mat[1][5] * det3_234_013; + float det4_1234_0145 = mat[1][0] * det3_234_145 - mat[1][1] * det3_234_045 + mat[1][4] * det3_234_015 - mat[1][5] * det3_234_014; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_0235 = mat[1][0] * det3_234_235 - mat[1][2] * det3_234_035 + mat[1][3] * det3_234_025 - mat[1][5] * det3_234_023; + float det4_1234_0245 = mat[1][0] * det3_234_245 - mat[1][2] * det3_234_045 + mat[1][4] * det3_234_025 - mat[1][5] * det3_234_024; + float det4_1234_0345 = mat[1][0] * det3_234_345 - mat[1][3] * det3_234_045 + mat[1][4] * det3_234_035 - mat[1][5] * det3_234_034; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + float det4_1234_1235 = mat[1][1] * det3_234_235 - mat[1][2] * det3_234_135 + mat[1][3] * det3_234_125 - mat[1][5] * det3_234_123; + float det4_1234_1245 = mat[1][1] * det3_234_245 - mat[1][2] * det3_234_145 + mat[1][4] * det3_234_125 - mat[1][5] * det3_234_124; + float det4_1234_1345 = mat[1][1] * det3_234_345 - mat[1][3] * det3_234_145 + mat[1][4] * det3_234_135 - mat[1][5] * det3_234_134; + float det4_1234_2345 = mat[1][2] * det3_234_345 - mat[1][3] * det3_234_245 + mat[1][4] * det3_234_235 - mat[1][5] * det3_234_234; + float det4_1235_0123 = mat[1][0] * det3_235_123 - mat[1][1] * det3_235_023 + mat[1][2] * det3_235_013 - mat[1][3] * det3_235_012; + float det4_1235_0124 = mat[1][0] * det3_235_124 - mat[1][1] * det3_235_024 + mat[1][2] * det3_235_014 - mat[1][4] * det3_235_012; + float det4_1235_0125 = mat[1][0] * det3_235_125 - mat[1][1] * det3_235_025 + mat[1][2] * det3_235_015 - mat[1][5] * det3_235_012; + float det4_1235_0134 = mat[1][0] * det3_235_134 - mat[1][1] * det3_235_034 + mat[1][3] * det3_235_014 - mat[1][4] * det3_235_013; + float det4_1235_0135 = mat[1][0] * det3_235_135 - mat[1][1] * det3_235_035 + mat[1][3] * det3_235_015 - mat[1][5] * det3_235_013; + float det4_1235_0145 = mat[1][0] * det3_235_145 - mat[1][1] * det3_235_045 + mat[1][4] * det3_235_015 - mat[1][5] * det3_235_014; + float det4_1235_0234 = mat[1][0] * det3_235_234 - mat[1][2] * det3_235_034 + mat[1][3] * det3_235_024 - mat[1][4] * det3_235_023; + float det4_1235_0235 = mat[1][0] * det3_235_235 - mat[1][2] * det3_235_035 + mat[1][3] * det3_235_025 - mat[1][5] * det3_235_023; + float det4_1235_0245 = mat[1][0] * det3_235_245 - mat[1][2] * det3_235_045 + mat[1][4] * det3_235_025 - mat[1][5] * det3_235_024; + float det4_1235_0345 = mat[1][0] * det3_235_345 - mat[1][3] * det3_235_045 + mat[1][4] * det3_235_035 - mat[1][5] * det3_235_034; + float det4_1235_1234 = mat[1][1] * det3_235_234 - mat[1][2] * det3_235_134 + mat[1][3] * det3_235_124 - mat[1][4] * det3_235_123; + float det4_1235_1235 = mat[1][1] * det3_235_235 - mat[1][2] * det3_235_135 + mat[1][3] * det3_235_125 - mat[1][5] * det3_235_123; + float det4_1235_1245 = mat[1][1] * det3_235_245 - mat[1][2] * det3_235_145 + mat[1][4] * det3_235_125 - mat[1][5] * det3_235_124; + float det4_1235_1345 = mat[1][1] * det3_235_345 - mat[1][3] * det3_235_145 + mat[1][4] * det3_235_135 - mat[1][5] * det3_235_134; + float det4_1235_2345 = mat[1][2] * det3_235_345 - mat[1][3] * det3_235_245 + mat[1][4] * det3_235_235 - mat[1][5] * det3_235_234; + float det4_1245_0123 = mat[1][0] * det3_245_123 - mat[1][1] * det3_245_023 + mat[1][2] * det3_245_013 - mat[1][3] * det3_245_012; + float det4_1245_0124 = mat[1][0] * det3_245_124 - mat[1][1] * det3_245_024 + mat[1][2] * det3_245_014 - mat[1][4] * det3_245_012; + float det4_1245_0125 = mat[1][0] * det3_245_125 - mat[1][1] * det3_245_025 + mat[1][2] * det3_245_015 - mat[1][5] * det3_245_012; + float det4_1245_0134 = mat[1][0] * det3_245_134 - mat[1][1] * det3_245_034 + mat[1][3] * det3_245_014 - mat[1][4] * det3_245_013; + float det4_1245_0135 = mat[1][0] * det3_245_135 - mat[1][1] * det3_245_035 + mat[1][3] * det3_245_015 - mat[1][5] * det3_245_013; + float det4_1245_0145 = mat[1][0] * det3_245_145 - mat[1][1] * det3_245_045 + mat[1][4] * det3_245_015 - mat[1][5] * det3_245_014; + float det4_1245_0234 = mat[1][0] * det3_245_234 - mat[1][2] * det3_245_034 + mat[1][3] * det3_245_024 - mat[1][4] * det3_245_023; + float det4_1245_0235 = mat[1][0] * det3_245_235 - mat[1][2] * det3_245_035 + mat[1][3] * det3_245_025 - mat[1][5] * det3_245_023; + float det4_1245_0245 = mat[1][0] * det3_245_245 - mat[1][2] * det3_245_045 + mat[1][4] * det3_245_025 - mat[1][5] * det3_245_024; + float det4_1245_0345 = mat[1][0] * det3_245_345 - mat[1][3] * det3_245_045 + mat[1][4] * det3_245_035 - mat[1][5] * det3_245_034; + float det4_1245_1234 = mat[1][1] * det3_245_234 - mat[1][2] * det3_245_134 + mat[1][3] * det3_245_124 - mat[1][4] * det3_245_123; + float det4_1245_1235 = mat[1][1] * det3_245_235 - mat[1][2] * det3_245_135 + mat[1][3] * det3_245_125 - mat[1][5] * det3_245_123; + float det4_1245_1245 = mat[1][1] * det3_245_245 - mat[1][2] * det3_245_145 + mat[1][4] * det3_245_125 - mat[1][5] * det3_245_124; + float det4_1245_1345 = mat[1][1] * det3_245_345 - mat[1][3] * det3_245_145 + mat[1][4] * det3_245_135 - mat[1][5] * det3_245_134; + float det4_1245_2345 = mat[1][2] * det3_245_345 - mat[1][3] * det3_245_245 + mat[1][4] * det3_245_235 - mat[1][5] * det3_245_234; + float det4_1345_0123 = mat[1][0] * det3_345_123 - mat[1][1] * det3_345_023 + mat[1][2] * det3_345_013 - mat[1][3] * det3_345_012; + float det4_1345_0124 = mat[1][0] * det3_345_124 - mat[1][1] * det3_345_024 + mat[1][2] * det3_345_014 - mat[1][4] * det3_345_012; + float det4_1345_0125 = mat[1][0] * det3_345_125 - mat[1][1] * det3_345_025 + mat[1][2] * det3_345_015 - mat[1][5] * det3_345_012; + float det4_1345_0134 = mat[1][0] * det3_345_134 - mat[1][1] * det3_345_034 + mat[1][3] * det3_345_014 - mat[1][4] * det3_345_013; + float det4_1345_0135 = mat[1][0] * det3_345_135 - mat[1][1] * det3_345_035 + mat[1][3] * det3_345_015 - mat[1][5] * det3_345_013; + float det4_1345_0145 = mat[1][0] * det3_345_145 - mat[1][1] * det3_345_045 + mat[1][4] * det3_345_015 - mat[1][5] * det3_345_014; + float det4_1345_0234 = mat[1][0] * det3_345_234 - mat[1][2] * det3_345_034 + mat[1][3] * det3_345_024 - mat[1][4] * det3_345_023; + float det4_1345_0235 = mat[1][0] * det3_345_235 - mat[1][2] * det3_345_035 + mat[1][3] * det3_345_025 - mat[1][5] * det3_345_023; + float det4_1345_0245 = mat[1][0] * det3_345_245 - mat[1][2] * det3_345_045 + mat[1][4] * det3_345_025 - mat[1][5] * det3_345_024; + float det4_1345_0345 = mat[1][0] * det3_345_345 - mat[1][3] * det3_345_045 + mat[1][4] * det3_345_035 - mat[1][5] * det3_345_034; + float det4_1345_1234 = mat[1][1] * det3_345_234 - mat[1][2] * det3_345_134 + mat[1][3] * det3_345_124 - mat[1][4] * det3_345_123; + float det4_1345_1235 = mat[1][1] * det3_345_235 - mat[1][2] * det3_345_135 + mat[1][3] * det3_345_125 - mat[1][5] * det3_345_123; + float det4_1345_1245 = mat[1][1] * det3_345_245 - mat[1][2] * det3_345_145 + mat[1][4] * det3_345_125 - mat[1][5] * det3_345_124; + float det4_1345_1345 = mat[1][1] * det3_345_345 - mat[1][3] * det3_345_145 + mat[1][4] * det3_345_135 - mat[1][5] * det3_345_134; + float det4_1345_2345 = mat[1][2] * det3_345_345 - mat[1][3] * det3_345_245 + mat[1][4] * det3_345_235 - mat[1][5] * det3_345_234; + + // remaining 5x5 sub-determinants + float det5_01234_01234 = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + float det5_01234_01235 = mat[0][0] * det4_1234_1235 - mat[0][1] * det4_1234_0235 + mat[0][2] * det4_1234_0135 - mat[0][3] * det4_1234_0125 + mat[0][5] * det4_1234_0123; + float det5_01234_01245 = mat[0][0] * det4_1234_1245 - mat[0][1] * det4_1234_0245 + mat[0][2] * det4_1234_0145 - mat[0][4] * det4_1234_0125 + mat[0][5] * det4_1234_0124; + float det5_01234_01345 = mat[0][0] * det4_1234_1345 - mat[0][1] * det4_1234_0345 + mat[0][3] * det4_1234_0145 - mat[0][4] * det4_1234_0135 + mat[0][5] * det4_1234_0134; + float det5_01234_02345 = mat[0][0] * det4_1234_2345 - mat[0][2] * det4_1234_0345 + mat[0][3] * det4_1234_0245 - mat[0][4] * det4_1234_0235 + mat[0][5] * det4_1234_0234; + float det5_01234_12345 = mat[0][1] * det4_1234_2345 - mat[0][2] * det4_1234_1345 + mat[0][3] * det4_1234_1245 - mat[0][4] * det4_1234_1235 + mat[0][5] * det4_1234_1234; + float det5_01235_01234 = mat[0][0] * det4_1235_1234 - mat[0][1] * det4_1235_0234 + mat[0][2] * det4_1235_0134 - mat[0][3] * det4_1235_0124 + mat[0][4] * det4_1235_0123; + float det5_01235_01235 = mat[0][0] * det4_1235_1235 - mat[0][1] * det4_1235_0235 + mat[0][2] * det4_1235_0135 - mat[0][3] * det4_1235_0125 + mat[0][5] * det4_1235_0123; + float det5_01235_01245 = mat[0][0] * det4_1235_1245 - mat[0][1] * det4_1235_0245 + mat[0][2] * det4_1235_0145 - mat[0][4] * det4_1235_0125 + mat[0][5] * det4_1235_0124; + float det5_01235_01345 = mat[0][0] * det4_1235_1345 - mat[0][1] * det4_1235_0345 + mat[0][3] * det4_1235_0145 - mat[0][4] * det4_1235_0135 + mat[0][5] * det4_1235_0134; + float det5_01235_02345 = mat[0][0] * det4_1235_2345 - mat[0][2] * det4_1235_0345 + mat[0][3] * det4_1235_0245 - mat[0][4] * det4_1235_0235 + mat[0][5] * det4_1235_0234; + float det5_01235_12345 = mat[0][1] * det4_1235_2345 - mat[0][2] * det4_1235_1345 + mat[0][3] * det4_1235_1245 - mat[0][4] * det4_1235_1235 + mat[0][5] * det4_1235_1234; + float det5_01245_01234 = mat[0][0] * det4_1245_1234 - mat[0][1] * det4_1245_0234 + mat[0][2] * det4_1245_0134 - mat[0][3] * det4_1245_0124 + mat[0][4] * det4_1245_0123; + float det5_01245_01235 = mat[0][0] * det4_1245_1235 - mat[0][1] * det4_1245_0235 + mat[0][2] * det4_1245_0135 - mat[0][3] * det4_1245_0125 + mat[0][5] * det4_1245_0123; + float det5_01245_01245 = mat[0][0] * det4_1245_1245 - mat[0][1] * det4_1245_0245 + mat[0][2] * det4_1245_0145 - mat[0][4] * det4_1245_0125 + mat[0][5] * det4_1245_0124; + float det5_01245_01345 = mat[0][0] * det4_1245_1345 - mat[0][1] * det4_1245_0345 + mat[0][3] * det4_1245_0145 - mat[0][4] * det4_1245_0135 + mat[0][5] * det4_1245_0134; + float det5_01245_02345 = mat[0][0] * det4_1245_2345 - mat[0][2] * det4_1245_0345 + mat[0][3] * det4_1245_0245 - mat[0][4] * det4_1245_0235 + mat[0][5] * det4_1245_0234; + float det5_01245_12345 = mat[0][1] * det4_1245_2345 - mat[0][2] * det4_1245_1345 + mat[0][3] * det4_1245_1245 - mat[0][4] * det4_1245_1235 + mat[0][5] * det4_1245_1234; + float det5_01345_01234 = mat[0][0] * det4_1345_1234 - mat[0][1] * det4_1345_0234 + mat[0][2] * det4_1345_0134 - mat[0][3] * det4_1345_0124 + mat[0][4] * det4_1345_0123; + float det5_01345_01235 = mat[0][0] * det4_1345_1235 - mat[0][1] * det4_1345_0235 + mat[0][2] * det4_1345_0135 - mat[0][3] * det4_1345_0125 + mat[0][5] * det4_1345_0123; + float det5_01345_01245 = mat[0][0] * det4_1345_1245 - mat[0][1] * det4_1345_0245 + mat[0][2] * det4_1345_0145 - mat[0][4] * det4_1345_0125 + mat[0][5] * det4_1345_0124; + float det5_01345_01345 = mat[0][0] * det4_1345_1345 - mat[0][1] * det4_1345_0345 + mat[0][3] * det4_1345_0145 - mat[0][4] * det4_1345_0135 + mat[0][5] * det4_1345_0134; + float det5_01345_02345 = mat[0][0] * det4_1345_2345 - mat[0][2] * det4_1345_0345 + mat[0][3] * det4_1345_0245 - mat[0][4] * det4_1345_0235 + mat[0][5] * det4_1345_0234; + float det5_01345_12345 = mat[0][1] * det4_1345_2345 - mat[0][2] * det4_1345_1345 + mat[0][3] * det4_1345_1245 - mat[0][4] * det4_1345_1235 + mat[0][5] * det4_1345_1234; + float det5_02345_01234 = mat[0][0] * det4_2345_1234 - mat[0][1] * det4_2345_0234 + mat[0][2] * det4_2345_0134 - mat[0][3] * det4_2345_0124 + mat[0][4] * det4_2345_0123; + float det5_02345_01235 = mat[0][0] * det4_2345_1235 - mat[0][1] * det4_2345_0235 + mat[0][2] * det4_2345_0135 - mat[0][3] * det4_2345_0125 + mat[0][5] * det4_2345_0123; + float det5_02345_01245 = mat[0][0] * det4_2345_1245 - mat[0][1] * det4_2345_0245 + mat[0][2] * det4_2345_0145 - mat[0][4] * det4_2345_0125 + mat[0][5] * det4_2345_0124; + float det5_02345_01345 = mat[0][0] * det4_2345_1345 - mat[0][1] * det4_2345_0345 + mat[0][3] * det4_2345_0145 - mat[0][4] * det4_2345_0135 + mat[0][5] * det4_2345_0134; + float det5_02345_02345 = mat[0][0] * det4_2345_2345 - mat[0][2] * det4_2345_0345 + mat[0][3] * det4_2345_0245 - mat[0][4] * det4_2345_0235 + mat[0][5] * det4_2345_0234; + float det5_02345_12345 = mat[0][1] * det4_2345_2345 - mat[0][2] * det4_2345_1345 + mat[0][3] * det4_2345_1245 - mat[0][4] * det4_2345_1235 + mat[0][5] * det4_2345_1234; + + mat[0][0] = det5_12345_12345 * invDet; + mat[0][1] = -det5_02345_12345 * invDet; + mat[0][2] = det5_01345_12345 * invDet; + mat[0][3] = -det5_01245_12345 * invDet; + mat[0][4] = det5_01235_12345 * invDet; + mat[0][5] = -det5_01234_12345 * invDet; + + mat[1][0] = -det5_12345_02345 * invDet; + mat[1][1] = det5_02345_02345 * invDet; + mat[1][2] = -det5_01345_02345 * invDet; + mat[1][3] = det5_01245_02345 * invDet; + mat[1][4] = -det5_01235_02345 * invDet; + mat[1][5] = det5_01234_02345 * invDet; + + mat[2][0] = det5_12345_01345 * invDet; + mat[2][1] = -det5_02345_01345 * invDet; + mat[2][2] = det5_01345_01345 * invDet; + mat[2][3] = -det5_01245_01345 * invDet; + mat[2][4] = det5_01235_01345 * invDet; + mat[2][5] = -det5_01234_01345 * invDet; + + mat[3][0] = -det5_12345_01245 * invDet; + mat[3][1] = det5_02345_01245 * invDet; + mat[3][2] = -det5_01345_01245 * invDet; + mat[3][3] = det5_01245_01245 * invDet; + mat[3][4] = -det5_01235_01245 * invDet; + mat[3][5] = det5_01234_01245 * invDet; + + mat[4][0] = det5_12345_01235 * invDet; + mat[4][1] = -det5_02345_01235 * invDet; + mat[4][2] = det5_01345_01235 * invDet; + mat[4][3] = -det5_01245_01235 * invDet; + mat[4][4] = det5_01235_01235 * invDet; + mat[4][5] = -det5_01234_01235 * invDet; + + mat[5][0] = -det5_12345_01234 * invDet; + mat[5][1] = det5_02345_01234 * invDet; + mat[5][2] = -det5_01345_01234 * invDet; + mat[5][3] = det5_01245_01234 * invDet; + mat[5][4] = -det5_01235_01234 * invDet; + mat[5][5] = det5_01234_01234 * invDet; + + return true; +} + +/* +============ +idMat6::InverseFastSelf +============ +*/ +bool idMat6::InverseFastSelf() { +#if 0 + // 810+6+36 = 852 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + det = mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_05 = mat[3][0] * mat[4][5] - mat[3][5] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_15 = mat[3][1] * mat[4][5] - mat[3][5] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_25 = mat[3][2] * mat[4][5] - mat[3][5] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + float det2_34_35 = mat[3][3] * mat[4][5] - mat[3][5] * mat[4][3]; + float det2_34_45 = mat[3][4] * mat[4][5] - mat[3][5] * mat[4][4]; + float det2_35_01 = mat[3][0] * mat[5][1] - mat[3][1] * mat[5][0]; + float det2_35_02 = mat[3][0] * mat[5][2] - mat[3][2] * mat[5][0]; + float det2_35_03 = mat[3][0] * mat[5][3] - mat[3][3] * mat[5][0]; + float det2_35_04 = mat[3][0] * mat[5][4] - mat[3][4] * mat[5][0]; + float det2_35_05 = mat[3][0] * mat[5][5] - mat[3][5] * mat[5][0]; + float det2_35_12 = mat[3][1] * mat[5][2] - mat[3][2] * mat[5][1]; + float det2_35_13 = mat[3][1] * mat[5][3] - mat[3][3] * mat[5][1]; + float det2_35_14 = mat[3][1] * mat[5][4] - mat[3][4] * mat[5][1]; + float det2_35_15 = mat[3][1] * mat[5][5] - mat[3][5] * mat[5][1]; + float det2_35_23 = mat[3][2] * mat[5][3] - mat[3][3] * mat[5][2]; + float det2_35_24 = mat[3][2] * mat[5][4] - mat[3][4] * mat[5][2]; + float det2_35_25 = mat[3][2] * mat[5][5] - mat[3][5] * mat[5][2]; + float det2_35_34 = mat[3][3] * mat[5][4] - mat[3][4] * mat[5][3]; + float det2_35_35 = mat[3][3] * mat[5][5] - mat[3][5] * mat[5][3]; + float det2_35_45 = mat[3][4] * mat[5][5] - mat[3][5] * mat[5][4]; + + // remaining 3x3 sub-determinants + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_015 = mat[2][0] * det2_34_15 - mat[2][1] * det2_34_05 + mat[2][5] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_025 = mat[2][0] * det2_34_25 - mat[2][2] * det2_34_05 + mat[2][5] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_035 = mat[2][0] * det2_34_35 - mat[2][3] * det2_34_05 + mat[2][5] * det2_34_03; + float det3_234_045 = mat[2][0] * det2_34_45 - mat[2][4] * det2_34_05 + mat[2][5] * det2_34_04; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_125 = mat[2][1] * det2_34_25 - mat[2][2] * det2_34_15 + mat[2][5] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_135 = mat[2][1] * det2_34_35 - mat[2][3] * det2_34_15 + mat[2][5] * det2_34_13; + float det3_234_145 = mat[2][1] * det2_34_45 - mat[2][4] * det2_34_15 + mat[2][5] * det2_34_14; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + float det3_234_235 = mat[2][2] * det2_34_35 - mat[2][3] * det2_34_25 + mat[2][5] * det2_34_23; + float det3_234_245 = mat[2][2] * det2_34_45 - mat[2][4] * det2_34_25 + mat[2][5] * det2_34_24; + float det3_234_345 = mat[2][3] * det2_34_45 - mat[2][4] * det2_34_35 + mat[2][5] * det2_34_34; + float det3_235_012 = mat[2][0] * det2_35_12 - mat[2][1] * det2_35_02 + mat[2][2] * det2_35_01; + float det3_235_013 = mat[2][0] * det2_35_13 - mat[2][1] * det2_35_03 + mat[2][3] * det2_35_01; + float det3_235_014 = mat[2][0] * det2_35_14 - mat[2][1] * det2_35_04 + mat[2][4] * det2_35_01; + float det3_235_015 = mat[2][0] * det2_35_15 - mat[2][1] * det2_35_05 + mat[2][5] * det2_35_01; + float det3_235_023 = mat[2][0] * det2_35_23 - mat[2][2] * det2_35_03 + mat[2][3] * det2_35_02; + float det3_235_024 = mat[2][0] * det2_35_24 - mat[2][2] * det2_35_04 + mat[2][4] * det2_35_02; + float det3_235_025 = mat[2][0] * det2_35_25 - mat[2][2] * det2_35_05 + mat[2][5] * det2_35_02; + float det3_235_034 = mat[2][0] * det2_35_34 - mat[2][3] * det2_35_04 + mat[2][4] * det2_35_03; + float det3_235_035 = mat[2][0] * det2_35_35 - mat[2][3] * det2_35_05 + mat[2][5] * det2_35_03; + float det3_235_045 = mat[2][0] * det2_35_45 - mat[2][4] * det2_35_05 + mat[2][5] * det2_35_04; + float det3_235_123 = mat[2][1] * det2_35_23 - mat[2][2] * det2_35_13 + mat[2][3] * det2_35_12; + float det3_235_124 = mat[2][1] * det2_35_24 - mat[2][2] * det2_35_14 + mat[2][4] * det2_35_12; + float det3_235_125 = mat[2][1] * det2_35_25 - mat[2][2] * det2_35_15 + mat[2][5] * det2_35_12; + float det3_235_134 = mat[2][1] * det2_35_34 - mat[2][3] * det2_35_14 + mat[2][4] * det2_35_13; + float det3_235_135 = mat[2][1] * det2_35_35 - mat[2][3] * det2_35_15 + mat[2][5] * det2_35_13; + float det3_235_145 = mat[2][1] * det2_35_45 - mat[2][4] * det2_35_15 + mat[2][5] * det2_35_14; + float det3_235_234 = mat[2][2] * det2_35_34 - mat[2][3] * det2_35_24 + mat[2][4] * det2_35_23; + float det3_235_235 = mat[2][2] * det2_35_35 - mat[2][3] * det2_35_25 + mat[2][5] * det2_35_23; + float det3_235_245 = mat[2][2] * det2_35_45 - mat[2][4] * det2_35_25 + mat[2][5] * det2_35_24; + float det3_235_345 = mat[2][3] * det2_35_45 - mat[2][4] * det2_35_35 + mat[2][5] * det2_35_34; + float det3_245_012 = mat[2][0] * det2_45_12 - mat[2][1] * det2_45_02 + mat[2][2] * det2_45_01; + float det3_245_013 = mat[2][0] * det2_45_13 - mat[2][1] * det2_45_03 + mat[2][3] * det2_45_01; + float det3_245_014 = mat[2][0] * det2_45_14 - mat[2][1] * det2_45_04 + mat[2][4] * det2_45_01; + float det3_245_015 = mat[2][0] * det2_45_15 - mat[2][1] * det2_45_05 + mat[2][5] * det2_45_01; + float det3_245_023 = mat[2][0] * det2_45_23 - mat[2][2] * det2_45_03 + mat[2][3] * det2_45_02; + float det3_245_024 = mat[2][0] * det2_45_24 - mat[2][2] * det2_45_04 + mat[2][4] * det2_45_02; + float det3_245_025 = mat[2][0] * det2_45_25 - mat[2][2] * det2_45_05 + mat[2][5] * det2_45_02; + float det3_245_034 = mat[2][0] * det2_45_34 - mat[2][3] * det2_45_04 + mat[2][4] * det2_45_03; + float det3_245_035 = mat[2][0] * det2_45_35 - mat[2][3] * det2_45_05 + mat[2][5] * det2_45_03; + float det3_245_045 = mat[2][0] * det2_45_45 - mat[2][4] * det2_45_05 + mat[2][5] * det2_45_04; + float det3_245_123 = mat[2][1] * det2_45_23 - mat[2][2] * det2_45_13 + mat[2][3] * det2_45_12; + float det3_245_124 = mat[2][1] * det2_45_24 - mat[2][2] * det2_45_14 + mat[2][4] * det2_45_12; + float det3_245_125 = mat[2][1] * det2_45_25 - mat[2][2] * det2_45_15 + mat[2][5] * det2_45_12; + float det3_245_134 = mat[2][1] * det2_45_34 - mat[2][3] * det2_45_14 + mat[2][4] * det2_45_13; + float det3_245_135 = mat[2][1] * det2_45_35 - mat[2][3] * det2_45_15 + mat[2][5] * det2_45_13; + float det3_245_145 = mat[2][1] * det2_45_45 - mat[2][4] * det2_45_15 + mat[2][5] * det2_45_14; + float det3_245_234 = mat[2][2] * det2_45_34 - mat[2][3] * det2_45_24 + mat[2][4] * det2_45_23; + float det3_245_235 = mat[2][2] * det2_45_35 - mat[2][3] * det2_45_25 + mat[2][5] * det2_45_23; + float det3_245_245 = mat[2][2] * det2_45_45 - mat[2][4] * det2_45_25 + mat[2][5] * det2_45_24; + float det3_245_345 = mat[2][3] * det2_45_45 - mat[2][4] * det2_45_35 + mat[2][5] * det2_45_34; + + // remaining 4x4 sub-determinants + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0125 = mat[1][0] * det3_234_125 - mat[1][1] * det3_234_025 + mat[1][2] * det3_234_015 - mat[1][5] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0135 = mat[1][0] * det3_234_135 - mat[1][1] * det3_234_035 + mat[1][3] * det3_234_015 - mat[1][5] * det3_234_013; + float det4_1234_0145 = mat[1][0] * det3_234_145 - mat[1][1] * det3_234_045 + mat[1][4] * det3_234_015 - mat[1][5] * det3_234_014; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_0235 = mat[1][0] * det3_234_235 - mat[1][2] * det3_234_035 + mat[1][3] * det3_234_025 - mat[1][5] * det3_234_023; + float det4_1234_0245 = mat[1][0] * det3_234_245 - mat[1][2] * det3_234_045 + mat[1][4] * det3_234_025 - mat[1][5] * det3_234_024; + float det4_1234_0345 = mat[1][0] * det3_234_345 - mat[1][3] * det3_234_045 + mat[1][4] * det3_234_035 - mat[1][5] * det3_234_034; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + float det4_1234_1235 = mat[1][1] * det3_234_235 - mat[1][2] * det3_234_135 + mat[1][3] * det3_234_125 - mat[1][5] * det3_234_123; + float det4_1234_1245 = mat[1][1] * det3_234_245 - mat[1][2] * det3_234_145 + mat[1][4] * det3_234_125 - mat[1][5] * det3_234_124; + float det4_1234_1345 = mat[1][1] * det3_234_345 - mat[1][3] * det3_234_145 + mat[1][4] * det3_234_135 - mat[1][5] * det3_234_134; + float det4_1234_2345 = mat[1][2] * det3_234_345 - mat[1][3] * det3_234_245 + mat[1][4] * det3_234_235 - mat[1][5] * det3_234_234; + float det4_1235_0123 = mat[1][0] * det3_235_123 - mat[1][1] * det3_235_023 + mat[1][2] * det3_235_013 - mat[1][3] * det3_235_012; + float det4_1235_0124 = mat[1][0] * det3_235_124 - mat[1][1] * det3_235_024 + mat[1][2] * det3_235_014 - mat[1][4] * det3_235_012; + float det4_1235_0125 = mat[1][0] * det3_235_125 - mat[1][1] * det3_235_025 + mat[1][2] * det3_235_015 - mat[1][5] * det3_235_012; + float det4_1235_0134 = mat[1][0] * det3_235_134 - mat[1][1] * det3_235_034 + mat[1][3] * det3_235_014 - mat[1][4] * det3_235_013; + float det4_1235_0135 = mat[1][0] * det3_235_135 - mat[1][1] * det3_235_035 + mat[1][3] * det3_235_015 - mat[1][5] * det3_235_013; + float det4_1235_0145 = mat[1][0] * det3_235_145 - mat[1][1] * det3_235_045 + mat[1][4] * det3_235_015 - mat[1][5] * det3_235_014; + float det4_1235_0234 = mat[1][0] * det3_235_234 - mat[1][2] * det3_235_034 + mat[1][3] * det3_235_024 - mat[1][4] * det3_235_023; + float det4_1235_0235 = mat[1][0] * det3_235_235 - mat[1][2] * det3_235_035 + mat[1][3] * det3_235_025 - mat[1][5] * det3_235_023; + float det4_1235_0245 = mat[1][0] * det3_235_245 - mat[1][2] * det3_235_045 + mat[1][4] * det3_235_025 - mat[1][5] * det3_235_024; + float det4_1235_0345 = mat[1][0] * det3_235_345 - mat[1][3] * det3_235_045 + mat[1][4] * det3_235_035 - mat[1][5] * det3_235_034; + float det4_1235_1234 = mat[1][1] * det3_235_234 - mat[1][2] * det3_235_134 + mat[1][3] * det3_235_124 - mat[1][4] * det3_235_123; + float det4_1235_1235 = mat[1][1] * det3_235_235 - mat[1][2] * det3_235_135 + mat[1][3] * det3_235_125 - mat[1][5] * det3_235_123; + float det4_1235_1245 = mat[1][1] * det3_235_245 - mat[1][2] * det3_235_145 + mat[1][4] * det3_235_125 - mat[1][5] * det3_235_124; + float det4_1235_1345 = mat[1][1] * det3_235_345 - mat[1][3] * det3_235_145 + mat[1][4] * det3_235_135 - mat[1][5] * det3_235_134; + float det4_1235_2345 = mat[1][2] * det3_235_345 - mat[1][3] * det3_235_245 + mat[1][4] * det3_235_235 - mat[1][5] * det3_235_234; + float det4_1245_0123 = mat[1][0] * det3_245_123 - mat[1][1] * det3_245_023 + mat[1][2] * det3_245_013 - mat[1][3] * det3_245_012; + float det4_1245_0124 = mat[1][0] * det3_245_124 - mat[1][1] * det3_245_024 + mat[1][2] * det3_245_014 - mat[1][4] * det3_245_012; + float det4_1245_0125 = mat[1][0] * det3_245_125 - mat[1][1] * det3_245_025 + mat[1][2] * det3_245_015 - mat[1][5] * det3_245_012; + float det4_1245_0134 = mat[1][0] * det3_245_134 - mat[1][1] * det3_245_034 + mat[1][3] * det3_245_014 - mat[1][4] * det3_245_013; + float det4_1245_0135 = mat[1][0] * det3_245_135 - mat[1][1] * det3_245_035 + mat[1][3] * det3_245_015 - mat[1][5] * det3_245_013; + float det4_1245_0145 = mat[1][0] * det3_245_145 - mat[1][1] * det3_245_045 + mat[1][4] * det3_245_015 - mat[1][5] * det3_245_014; + float det4_1245_0234 = mat[1][0] * det3_245_234 - mat[1][2] * det3_245_034 + mat[1][3] * det3_245_024 - mat[1][4] * det3_245_023; + float det4_1245_0235 = mat[1][0] * det3_245_235 - mat[1][2] * det3_245_035 + mat[1][3] * det3_245_025 - mat[1][5] * det3_245_023; + float det4_1245_0245 = mat[1][0] * det3_245_245 - mat[1][2] * det3_245_045 + mat[1][4] * det3_245_025 - mat[1][5] * det3_245_024; + float det4_1245_0345 = mat[1][0] * det3_245_345 - mat[1][3] * det3_245_045 + mat[1][4] * det3_245_035 - mat[1][5] * det3_245_034; + float det4_1245_1234 = mat[1][1] * det3_245_234 - mat[1][2] * det3_245_134 + mat[1][3] * det3_245_124 - mat[1][4] * det3_245_123; + float det4_1245_1235 = mat[1][1] * det3_245_235 - mat[1][2] * det3_245_135 + mat[1][3] * det3_245_125 - mat[1][5] * det3_245_123; + float det4_1245_1245 = mat[1][1] * det3_245_245 - mat[1][2] * det3_245_145 + mat[1][4] * det3_245_125 - mat[1][5] * det3_245_124; + float det4_1245_1345 = mat[1][1] * det3_245_345 - mat[1][3] * det3_245_145 + mat[1][4] * det3_245_135 - mat[1][5] * det3_245_134; + float det4_1245_2345 = mat[1][2] * det3_245_345 - mat[1][3] * det3_245_245 + mat[1][4] * det3_245_235 - mat[1][5] * det3_245_234; + float det4_1345_0123 = mat[1][0] * det3_345_123 - mat[1][1] * det3_345_023 + mat[1][2] * det3_345_013 - mat[1][3] * det3_345_012; + float det4_1345_0124 = mat[1][0] * det3_345_124 - mat[1][1] * det3_345_024 + mat[1][2] * det3_345_014 - mat[1][4] * det3_345_012; + float det4_1345_0125 = mat[1][0] * det3_345_125 - mat[1][1] * det3_345_025 + mat[1][2] * det3_345_015 - mat[1][5] * det3_345_012; + float det4_1345_0134 = mat[1][0] * det3_345_134 - mat[1][1] * det3_345_034 + mat[1][3] * det3_345_014 - mat[1][4] * det3_345_013; + float det4_1345_0135 = mat[1][0] * det3_345_135 - mat[1][1] * det3_345_035 + mat[1][3] * det3_345_015 - mat[1][5] * det3_345_013; + float det4_1345_0145 = mat[1][0] * det3_345_145 - mat[1][1] * det3_345_045 + mat[1][4] * det3_345_015 - mat[1][5] * det3_345_014; + float det4_1345_0234 = mat[1][0] * det3_345_234 - mat[1][2] * det3_345_034 + mat[1][3] * det3_345_024 - mat[1][4] * det3_345_023; + float det4_1345_0235 = mat[1][0] * det3_345_235 - mat[1][2] * det3_345_035 + mat[1][3] * det3_345_025 - mat[1][5] * det3_345_023; + float det4_1345_0245 = mat[1][0] * det3_345_245 - mat[1][2] * det3_345_045 + mat[1][4] * det3_345_025 - mat[1][5] * det3_345_024; + float det4_1345_0345 = mat[1][0] * det3_345_345 - mat[1][3] * det3_345_045 + mat[1][4] * det3_345_035 - mat[1][5] * det3_345_034; + float det4_1345_1234 = mat[1][1] * det3_345_234 - mat[1][2] * det3_345_134 + mat[1][3] * det3_345_124 - mat[1][4] * det3_345_123; + float det4_1345_1235 = mat[1][1] * det3_345_235 - mat[1][2] * det3_345_135 + mat[1][3] * det3_345_125 - mat[1][5] * det3_345_123; + float det4_1345_1245 = mat[1][1] * det3_345_245 - mat[1][2] * det3_345_145 + mat[1][4] * det3_345_125 - mat[1][5] * det3_345_124; + float det4_1345_1345 = mat[1][1] * det3_345_345 - mat[1][3] * det3_345_145 + mat[1][4] * det3_345_135 - mat[1][5] * det3_345_134; + float det4_1345_2345 = mat[1][2] * det3_345_345 - mat[1][3] * det3_345_245 + mat[1][4] * det3_345_235 - mat[1][5] * det3_345_234; + + // remaining 5x5 sub-determinants + float det5_01234_01234 = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + float det5_01234_01235 = mat[0][0] * det4_1234_1235 - mat[0][1] * det4_1234_0235 + mat[0][2] * det4_1234_0135 - mat[0][3] * det4_1234_0125 + mat[0][5] * det4_1234_0123; + float det5_01234_01245 = mat[0][0] * det4_1234_1245 - mat[0][1] * det4_1234_0245 + mat[0][2] * det4_1234_0145 - mat[0][4] * det4_1234_0125 + mat[0][5] * det4_1234_0124; + float det5_01234_01345 = mat[0][0] * det4_1234_1345 - mat[0][1] * det4_1234_0345 + mat[0][3] * det4_1234_0145 - mat[0][4] * det4_1234_0135 + mat[0][5] * det4_1234_0134; + float det5_01234_02345 = mat[0][0] * det4_1234_2345 - mat[0][2] * det4_1234_0345 + mat[0][3] * det4_1234_0245 - mat[0][4] * det4_1234_0235 + mat[0][5] * det4_1234_0234; + float det5_01234_12345 = mat[0][1] * det4_1234_2345 - mat[0][2] * det4_1234_1345 + mat[0][3] * det4_1234_1245 - mat[0][4] * det4_1234_1235 + mat[0][5] * det4_1234_1234; + float det5_01235_01234 = mat[0][0] * det4_1235_1234 - mat[0][1] * det4_1235_0234 + mat[0][2] * det4_1235_0134 - mat[0][3] * det4_1235_0124 + mat[0][4] * det4_1235_0123; + float det5_01235_01235 = mat[0][0] * det4_1235_1235 - mat[0][1] * det4_1235_0235 + mat[0][2] * det4_1235_0135 - mat[0][3] * det4_1235_0125 + mat[0][5] * det4_1235_0123; + float det5_01235_01245 = mat[0][0] * det4_1235_1245 - mat[0][1] * det4_1235_0245 + mat[0][2] * det4_1235_0145 - mat[0][4] * det4_1235_0125 + mat[0][5] * det4_1235_0124; + float det5_01235_01345 = mat[0][0] * det4_1235_1345 - mat[0][1] * det4_1235_0345 + mat[0][3] * det4_1235_0145 - mat[0][4] * det4_1235_0135 + mat[0][5] * det4_1235_0134; + float det5_01235_02345 = mat[0][0] * det4_1235_2345 - mat[0][2] * det4_1235_0345 + mat[0][3] * det4_1235_0245 - mat[0][4] * det4_1235_0235 + mat[0][5] * det4_1235_0234; + float det5_01235_12345 = mat[0][1] * det4_1235_2345 - mat[0][2] * det4_1235_1345 + mat[0][3] * det4_1235_1245 - mat[0][4] * det4_1235_1235 + mat[0][5] * det4_1235_1234; + float det5_01245_01234 = mat[0][0] * det4_1245_1234 - mat[0][1] * det4_1245_0234 + mat[0][2] * det4_1245_0134 - mat[0][3] * det4_1245_0124 + mat[0][4] * det4_1245_0123; + float det5_01245_01235 = mat[0][0] * det4_1245_1235 - mat[0][1] * det4_1245_0235 + mat[0][2] * det4_1245_0135 - mat[0][3] * det4_1245_0125 + mat[0][5] * det4_1245_0123; + float det5_01245_01245 = mat[0][0] * det4_1245_1245 - mat[0][1] * det4_1245_0245 + mat[0][2] * det4_1245_0145 - mat[0][4] * det4_1245_0125 + mat[0][5] * det4_1245_0124; + float det5_01245_01345 = mat[0][0] * det4_1245_1345 - mat[0][1] * det4_1245_0345 + mat[0][3] * det4_1245_0145 - mat[0][4] * det4_1245_0135 + mat[0][5] * det4_1245_0134; + float det5_01245_02345 = mat[0][0] * det4_1245_2345 - mat[0][2] * det4_1245_0345 + mat[0][3] * det4_1245_0245 - mat[0][4] * det4_1245_0235 + mat[0][5] * det4_1245_0234; + float det5_01245_12345 = mat[0][1] * det4_1245_2345 - mat[0][2] * det4_1245_1345 + mat[0][3] * det4_1245_1245 - mat[0][4] * det4_1245_1235 + mat[0][5] * det4_1245_1234; + float det5_01345_01234 = mat[0][0] * det4_1345_1234 - mat[0][1] * det4_1345_0234 + mat[0][2] * det4_1345_0134 - mat[0][3] * det4_1345_0124 + mat[0][4] * det4_1345_0123; + float det5_01345_01235 = mat[0][0] * det4_1345_1235 - mat[0][1] * det4_1345_0235 + mat[0][2] * det4_1345_0135 - mat[0][3] * det4_1345_0125 + mat[0][5] * det4_1345_0123; + float det5_01345_01245 = mat[0][0] * det4_1345_1245 - mat[0][1] * det4_1345_0245 + mat[0][2] * det4_1345_0145 - mat[0][4] * det4_1345_0125 + mat[0][5] * det4_1345_0124; + float det5_01345_01345 = mat[0][0] * det4_1345_1345 - mat[0][1] * det4_1345_0345 + mat[0][3] * det4_1345_0145 - mat[0][4] * det4_1345_0135 + mat[0][5] * det4_1345_0134; + float det5_01345_02345 = mat[0][0] * det4_1345_2345 - mat[0][2] * det4_1345_0345 + mat[0][3] * det4_1345_0245 - mat[0][4] * det4_1345_0235 + mat[0][5] * det4_1345_0234; + float det5_01345_12345 = mat[0][1] * det4_1345_2345 - mat[0][2] * det4_1345_1345 + mat[0][3] * det4_1345_1245 - mat[0][4] * det4_1345_1235 + mat[0][5] * det4_1345_1234; + float det5_02345_01234 = mat[0][0] * det4_2345_1234 - mat[0][1] * det4_2345_0234 + mat[0][2] * det4_2345_0134 - mat[0][3] * det4_2345_0124 + mat[0][4] * det4_2345_0123; + float det5_02345_01235 = mat[0][0] * det4_2345_1235 - mat[0][1] * det4_2345_0235 + mat[0][2] * det4_2345_0135 - mat[0][3] * det4_2345_0125 + mat[0][5] * det4_2345_0123; + float det5_02345_01245 = mat[0][0] * det4_2345_1245 - mat[0][1] * det4_2345_0245 + mat[0][2] * det4_2345_0145 - mat[0][4] * det4_2345_0125 + mat[0][5] * det4_2345_0124; + float det5_02345_01345 = mat[0][0] * det4_2345_1345 - mat[0][1] * det4_2345_0345 + mat[0][3] * det4_2345_0145 - mat[0][4] * det4_2345_0135 + mat[0][5] * det4_2345_0134; + float det5_02345_02345 = mat[0][0] * det4_2345_2345 - mat[0][2] * det4_2345_0345 + mat[0][3] * det4_2345_0245 - mat[0][4] * det4_2345_0235 + mat[0][5] * det4_2345_0234; + float det5_02345_12345 = mat[0][1] * det4_2345_2345 - mat[0][2] * det4_2345_1345 + mat[0][3] * det4_2345_1245 - mat[0][4] * det4_2345_1235 + mat[0][5] * det4_2345_1234; + + mat[0][0] = det5_12345_12345 * invDet; + mat[0][1] = -det5_02345_12345 * invDet; + mat[0][2] = det5_01345_12345 * invDet; + mat[0][3] = -det5_01245_12345 * invDet; + mat[0][4] = det5_01235_12345 * invDet; + mat[0][5] = -det5_01234_12345 * invDet; + + mat[1][0] = -det5_12345_02345 * invDet; + mat[1][1] = det5_02345_02345 * invDet; + mat[1][2] = -det5_01345_02345 * invDet; + mat[1][3] = det5_01245_02345 * invDet; + mat[1][4] = -det5_01235_02345 * invDet; + mat[1][5] = det5_01234_02345 * invDet; + + mat[2][0] = det5_12345_01345 * invDet; + mat[2][1] = -det5_02345_01345 * invDet; + mat[2][2] = det5_01345_01345 * invDet; + mat[2][3] = -det5_01245_01345 * invDet; + mat[2][4] = det5_01235_01345 * invDet; + mat[2][5] = -det5_01234_01345 * invDet; + + mat[3][0] = -det5_12345_01245 * invDet; + mat[3][1] = det5_02345_01245 * invDet; + mat[3][2] = -det5_01345_01245 * invDet; + mat[3][3] = det5_01245_01245 * invDet; + mat[3][4] = -det5_01235_01245 * invDet; + mat[3][5] = det5_01234_01245 * invDet; + + mat[4][0] = det5_12345_01235 * invDet; + mat[4][1] = -det5_02345_01235 * invDet; + mat[4][2] = det5_01345_01235 * invDet; + mat[4][3] = -det5_01245_01235 * invDet; + mat[4][4] = det5_01235_01235 * invDet; + mat[4][5] = -det5_01234_01235 * invDet; + + mat[5][0] = -det5_12345_01234 * invDet; + mat[5][1] = det5_02345_01234 * invDet; + mat[5][2] = -det5_01345_01234 * invDet; + mat[5][3] = det5_01245_01234 * invDet; + mat[5][4] = -det5_01235_01234 * invDet; + mat[5][5] = det5_01234_01234 * invDet; + + return true; +#elif 0 + // 6*40 = 240 multiplications + // 6 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + mat[4] *= d; + mat[5] *= d; + d = -d; + mat[6] *= d; + mat[12] *= d; + mat[18] *= d; + mat[24] *= d; + mat[30] *= d; + d = mat[6] * di; + mat[7] += mat[1] * d; + mat[8] += mat[2] * d; + mat[9] += mat[3] * d; + mat[10] += mat[4] * d; + mat[11] += mat[5] * d; + d = mat[12] * di; + mat[13] += mat[1] * d; + mat[14] += mat[2] * d; + mat[15] += mat[3] * d; + mat[16] += mat[4] * d; + mat[17] += mat[5] * d; + d = mat[18] * di; + mat[19] += mat[1] * d; + mat[20] += mat[2] * d; + mat[21] += mat[3] * d; + mat[22] += mat[4] * d; + mat[23] += mat[5] * d; + d = mat[24] * di; + mat[25] += mat[1] * d; + mat[26] += mat[2] * d; + mat[27] += mat[3] * d; + mat[28] += mat[4] * d; + mat[29] += mat[5] * d; + d = mat[30] * di; + mat[31] += mat[1] * d; + mat[32] += mat[2] * d; + mat[33] += mat[3] * d; + mat[34] += mat[4] * d; + mat[35] += mat[5] * d; + di = mat[7]; + s *= di; + mat[7] = d = 1.0f / di; + mat[6] *= d; + mat[8] *= d; + mat[9] *= d; + mat[10] *= d; + mat[11] *= d; + d = -d; + mat[1] *= d; + mat[13] *= d; + mat[19] *= d; + mat[25] *= d; + mat[31] *= d; + d = mat[1] * di; + mat[0] += mat[6] * d; + mat[2] += mat[8] * d; + mat[3] += mat[9] * d; + mat[4] += mat[10] * d; + mat[5] += mat[11] * d; + d = mat[13] * di; + mat[12] += mat[6] * d; + mat[14] += mat[8] * d; + mat[15] += mat[9] * d; + mat[16] += mat[10] * d; + mat[17] += mat[11] * d; + d = mat[19] * di; + mat[18] += mat[6] * d; + mat[20] += mat[8] * d; + mat[21] += mat[9] * d; + mat[22] += mat[10] * d; + mat[23] += mat[11] * d; + d = mat[25] * di; + mat[24] += mat[6] * d; + mat[26] += mat[8] * d; + mat[27] += mat[9] * d; + mat[28] += mat[10] * d; + mat[29] += mat[11] * d; + d = mat[31] * di; + mat[30] += mat[6] * d; + mat[32] += mat[8] * d; + mat[33] += mat[9] * d; + mat[34] += mat[10] * d; + mat[35] += mat[11] * d; + di = mat[14]; + s *= di; + mat[14] = d = 1.0f / di; + mat[12] *= d; + mat[13] *= d; + mat[15] *= d; + mat[16] *= d; + mat[17] *= d; + d = -d; + mat[2] *= d; + mat[8] *= d; + mat[20] *= d; + mat[26] *= d; + mat[32] *= d; + d = mat[2] * di; + mat[0] += mat[12] * d; + mat[1] += mat[13] * d; + mat[3] += mat[15] * d; + mat[4] += mat[16] * d; + mat[5] += mat[17] * d; + d = mat[8] * di; + mat[6] += mat[12] * d; + mat[7] += mat[13] * d; + mat[9] += mat[15] * d; + mat[10] += mat[16] * d; + mat[11] += mat[17] * d; + d = mat[20] * di; + mat[18] += mat[12] * d; + mat[19] += mat[13] * d; + mat[21] += mat[15] * d; + mat[22] += mat[16] * d; + mat[23] += mat[17] * d; + d = mat[26] * di; + mat[24] += mat[12] * d; + mat[25] += mat[13] * d; + mat[27] += mat[15] * d; + mat[28] += mat[16] * d; + mat[29] += mat[17] * d; + d = mat[32] * di; + mat[30] += mat[12] * d; + mat[31] += mat[13] * d; + mat[33] += mat[15] * d; + mat[34] += mat[16] * d; + mat[35] += mat[17] * d; + di = mat[21]; + s *= di; + mat[21] = d = 1.0f / di; + mat[18] *= d; + mat[19] *= d; + mat[20] *= d; + mat[22] *= d; + mat[23] *= d; + d = -d; + mat[3] *= d; + mat[9] *= d; + mat[15] *= d; + mat[27] *= d; + mat[33] *= d; + d = mat[3] * di; + mat[0] += mat[18] * d; + mat[1] += mat[19] * d; + mat[2] += mat[20] * d; + mat[4] += mat[22] * d; + mat[5] += mat[23] * d; + d = mat[9] * di; + mat[6] += mat[18] * d; + mat[7] += mat[19] * d; + mat[8] += mat[20] * d; + mat[10] += mat[22] * d; + mat[11] += mat[23] * d; + d = mat[15] * di; + mat[12] += mat[18] * d; + mat[13] += mat[19] * d; + mat[14] += mat[20] * d; + mat[16] += mat[22] * d; + mat[17] += mat[23] * d; + d = mat[27] * di; + mat[24] += mat[18] * d; + mat[25] += mat[19] * d; + mat[26] += mat[20] * d; + mat[28] += mat[22] * d; + mat[29] += mat[23] * d; + d = mat[33] * di; + mat[30] += mat[18] * d; + mat[31] += mat[19] * d; + mat[32] += mat[20] * d; + mat[34] += mat[22] * d; + mat[35] += mat[23] * d; + di = mat[28]; + s *= di; + mat[28] = d = 1.0f / di; + mat[24] *= d; + mat[25] *= d; + mat[26] *= d; + mat[27] *= d; + mat[29] *= d; + d = -d; + mat[4] *= d; + mat[10] *= d; + mat[16] *= d; + mat[22] *= d; + mat[34] *= d; + d = mat[4] * di; + mat[0] += mat[24] * d; + mat[1] += mat[25] * d; + mat[2] += mat[26] * d; + mat[3] += mat[27] * d; + mat[5] += mat[29] * d; + d = mat[10] * di; + mat[6] += mat[24] * d; + mat[7] += mat[25] * d; + mat[8] += mat[26] * d; + mat[9] += mat[27] * d; + mat[11] += mat[29] * d; + d = mat[16] * di; + mat[12] += mat[24] * d; + mat[13] += mat[25] * d; + mat[14] += mat[26] * d; + mat[15] += mat[27] * d; + mat[17] += mat[29] * d; + d = mat[22] * di; + mat[18] += mat[24] * d; + mat[19] += mat[25] * d; + mat[20] += mat[26] * d; + mat[21] += mat[27] * d; + mat[23] += mat[29] * d; + d = mat[34] * di; + mat[30] += mat[24] * d; + mat[31] += mat[25] * d; + mat[32] += mat[26] * d; + mat[33] += mat[27] * d; + mat[35] += mat[29] * d; + di = mat[35]; + s *= di; + mat[35] = d = 1.0f / di; + mat[30] *= d; + mat[31] *= d; + mat[32] *= d; + mat[33] *= d; + mat[34] *= d; + d = -d; + mat[5] *= d; + mat[11] *= d; + mat[17] *= d; + mat[23] *= d; + mat[29] *= d; + d = mat[5] * di; + mat[0] += mat[30] * d; + mat[1] += mat[31] * d; + mat[2] += mat[32] * d; + mat[3] += mat[33] * d; + mat[4] += mat[34] * d; + d = mat[11] * di; + mat[6] += mat[30] * d; + mat[7] += mat[31] * d; + mat[8] += mat[32] * d; + mat[9] += mat[33] * d; + mat[10] += mat[34] * d; + d = mat[17] * di; + mat[12] += mat[30] * d; + mat[13] += mat[31] * d; + mat[14] += mat[32] * d; + mat[15] += mat[33] * d; + mat[16] += mat[34] * d; + d = mat[23] * di; + mat[18] += mat[30] * d; + mat[19] += mat[31] * d; + mat[20] += mat[32] * d; + mat[21] += mat[33] * d; + mat[22] += mat[34] * d; + d = mat[29] * di; + mat[24] += mat[30] * d; + mat[25] += mat[31] * d; + mat[26] += mat[32] * d; + mat[27] += mat[33] * d; + mat[28] += mat[34] * d; + + return ( s != 0.0f && !IEEE_FLT_IS_NAN( s ) ); +#else + // 6*27+2*30 = 222 multiplications + // 2*1 = 2 divisions + idMat3 r0, r1, r2, r3; + float c0, c1, c2, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); + c0 = mat[1*6+1] * mat[2*6+2] - mat[1*6+2] * mat[2*6+1]; + c1 = mat[1*6+2] * mat[2*6+0] - mat[1*6+0] * mat[2*6+2]; + c2 = mat[1*6+0] * mat[2*6+1] - mat[1*6+1] * mat[2*6+0]; + + det = mat[0*6+0] * c0 + mat[0*6+1] * c1 + mat[0*6+2] * c2; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = c0 * invDet; + r0[0][1] = ( mat[0*6+2] * mat[2*6+1] - mat[0*6+1] * mat[2*6+2] ) * invDet; + r0[0][2] = ( mat[0*6+1] * mat[1*6+2] - mat[0*6+2] * mat[1*6+1] ) * invDet; + r0[1][0] = c1 * invDet; + r0[1][1] = ( mat[0*6+0] * mat[2*6+2] - mat[0*6+2] * mat[2*6+0] ) * invDet; + r0[1][2] = ( mat[0*6+2] * mat[1*6+0] - mat[0*6+0] * mat[1*6+2] ) * invDet; + r0[2][0] = c2 * invDet; + r0[2][1] = ( mat[0*6+1] * mat[2*6+0] - mat[0*6+0] * mat[2*6+1] ) * invDet; + r0[2][2] = ( mat[0*6+0] * mat[1*6+1] - mat[0*6+1] * mat[1*6+0] ) * invDet; + + // r1 = r0 * m1; + r1[0][0] = r0[0][0] * mat[0*6+3] + r0[0][1] * mat[1*6+3] + r0[0][2] * mat[2*6+3]; + r1[0][1] = r0[0][0] * mat[0*6+4] + r0[0][1] * mat[1*6+4] + r0[0][2] * mat[2*6+4]; + r1[0][2] = r0[0][0] * mat[0*6+5] + r0[0][1] * mat[1*6+5] + r0[0][2] * mat[2*6+5]; + r1[1][0] = r0[1][0] * mat[0*6+3] + r0[1][1] * mat[1*6+3] + r0[1][2] * mat[2*6+3]; + r1[1][1] = r0[1][0] * mat[0*6+4] + r0[1][1] * mat[1*6+4] + r0[1][2] * mat[2*6+4]; + r1[1][2] = r0[1][0] * mat[0*6+5] + r0[1][1] * mat[1*6+5] + r0[1][2] * mat[2*6+5]; + r1[2][0] = r0[2][0] * mat[0*6+3] + r0[2][1] * mat[1*6+3] + r0[2][2] * mat[2*6+3]; + r1[2][1] = r0[2][0] * mat[0*6+4] + r0[2][1] * mat[1*6+4] + r0[2][2] * mat[2*6+4]; + r1[2][2] = r0[2][0] * mat[0*6+5] + r0[2][1] * mat[1*6+5] + r0[2][2] * mat[2*6+5]; + + // r2 = m2 * r1; + r2[0][0] = mat[3*6+0] * r1[0][0] + mat[3*6+1] * r1[1][0] + mat[3*6+2] * r1[2][0]; + r2[0][1] = mat[3*6+0] * r1[0][1] + mat[3*6+1] * r1[1][1] + mat[3*6+2] * r1[2][1]; + r2[0][2] = mat[3*6+0] * r1[0][2] + mat[3*6+1] * r1[1][2] + mat[3*6+2] * r1[2][2]; + r2[1][0] = mat[4*6+0] * r1[0][0] + mat[4*6+1] * r1[1][0] + mat[4*6+2] * r1[2][0]; + r2[1][1] = mat[4*6+0] * r1[0][1] + mat[4*6+1] * r1[1][1] + mat[4*6+2] * r1[2][1]; + r2[1][2] = mat[4*6+0] * r1[0][2] + mat[4*6+1] * r1[1][2] + mat[4*6+2] * r1[2][2]; + r2[2][0] = mat[5*6+0] * r1[0][0] + mat[5*6+1] * r1[1][0] + mat[5*6+2] * r1[2][0]; + r2[2][1] = mat[5*6+0] * r1[0][1] + mat[5*6+1] * r1[1][1] + mat[5*6+2] * r1[2][1]; + r2[2][2] = mat[5*6+0] * r1[0][2] + mat[5*6+1] * r1[1][2] + mat[5*6+2] * r1[2][2]; + + // r3 = r2 - m3; + r3[0][0] = r2[0][0] - mat[3*6+3]; + r3[0][1] = r2[0][1] - mat[3*6+4]; + r3[0][2] = r2[0][2] - mat[3*6+5]; + r3[1][0] = r2[1][0] - mat[4*6+3]; + r3[1][1] = r2[1][1] - mat[4*6+4]; + r3[1][2] = r2[1][2] - mat[4*6+5]; + r3[2][0] = r2[2][0] - mat[5*6+3]; + r3[2][1] = r2[2][1] - mat[5*6+4]; + r3[2][2] = r2[2][2] - mat[5*6+5]; + + // r3.InverseSelf(); + r2[0][0] = r3[1][1] * r3[2][2] - r3[1][2] * r3[2][1]; + r2[1][0] = r3[1][2] * r3[2][0] - r3[1][0] * r3[2][2]; + r2[2][0] = r3[1][0] * r3[2][1] - r3[1][1] * r3[2][0]; + + det = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0] + r3[0][2] * r2[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r2[0][1] = r3[0][2] * r3[2][1] - r3[0][1] * r3[2][2]; + r2[0][2] = r3[0][1] * r3[1][2] - r3[0][2] * r3[1][1]; + r2[1][1] = r3[0][0] * r3[2][2] - r3[0][2] * r3[2][0]; + r2[1][2] = r3[0][2] * r3[1][0] - r3[0][0] * r3[1][2]; + r2[2][1] = r3[0][1] * r3[2][0] - r3[0][0] * r3[2][1]; + r2[2][2] = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + r3[0][0] = r2[0][0] * invDet; + r3[0][1] = r2[0][1] * invDet; + r3[0][2] = r2[0][2] * invDet; + r3[1][0] = r2[1][0] * invDet; + r3[1][1] = r2[1][1] * invDet; + r3[1][2] = r2[1][2] * invDet; + r3[2][0] = r2[2][0] * invDet; + r3[2][1] = r2[2][1] * invDet; + r3[2][2] = r2[2][2] * invDet; + + // r2 = m2 * r0; + r2[0][0] = mat[3*6+0] * r0[0][0] + mat[3*6+1] * r0[1][0] + mat[3*6+2] * r0[2][0]; + r2[0][1] = mat[3*6+0] * r0[0][1] + mat[3*6+1] * r0[1][1] + mat[3*6+2] * r0[2][1]; + r2[0][2] = mat[3*6+0] * r0[0][2] + mat[3*6+1] * r0[1][2] + mat[3*6+2] * r0[2][2]; + r2[1][0] = mat[4*6+0] * r0[0][0] + mat[4*6+1] * r0[1][0] + mat[4*6+2] * r0[2][0]; + r2[1][1] = mat[4*6+0] * r0[0][1] + mat[4*6+1] * r0[1][1] + mat[4*6+2] * r0[2][1]; + r2[1][2] = mat[4*6+0] * r0[0][2] + mat[4*6+1] * r0[1][2] + mat[4*6+2] * r0[2][2]; + r2[2][0] = mat[5*6+0] * r0[0][0] + mat[5*6+1] * r0[1][0] + mat[5*6+2] * r0[2][0]; + r2[2][1] = mat[5*6+0] * r0[0][1] + mat[5*6+1] * r0[1][1] + mat[5*6+2] * r0[2][1]; + r2[2][2] = mat[5*6+0] * r0[0][2] + mat[5*6+1] * r0[1][2] + mat[5*6+2] * r0[2][2]; + + // m2 = r3 * r2; + mat[3*6+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0] + r3[0][2] * r2[2][0]; + mat[3*6+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1] + r3[0][2] * r2[2][1]; + mat[3*6+2] = r3[0][0] * r2[0][2] + r3[0][1] * r2[1][2] + r3[0][2] * r2[2][2]; + mat[4*6+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0] + r3[1][2] * r2[2][0]; + mat[4*6+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1] + r3[1][2] * r2[2][1]; + mat[4*6+2] = r3[1][0] * r2[0][2] + r3[1][1] * r2[1][2] + r3[1][2] * r2[2][2]; + mat[5*6+0] = r3[2][0] * r2[0][0] + r3[2][1] * r2[1][0] + r3[2][2] * r2[2][0]; + mat[5*6+1] = r3[2][0] * r2[0][1] + r3[2][1] * r2[1][1] + r3[2][2] * r2[2][1]; + mat[5*6+2] = r3[2][0] * r2[0][2] + r3[2][1] * r2[1][2] + r3[2][2] * r2[2][2]; + + // m0 = r0 - r1 * m2; + mat[0*6+0] = r0[0][0] - r1[0][0] * mat[3*6+0] - r1[0][1] * mat[4*6+0] - r1[0][2] * mat[5*6+0]; + mat[0*6+1] = r0[0][1] - r1[0][0] * mat[3*6+1] - r1[0][1] * mat[4*6+1] - r1[0][2] * mat[5*6+1]; + mat[0*6+2] = r0[0][2] - r1[0][0] * mat[3*6+2] - r1[0][1] * mat[4*6+2] - r1[0][2] * mat[5*6+2]; + mat[1*6+0] = r0[1][0] - r1[1][0] * mat[3*6+0] - r1[1][1] * mat[4*6+0] - r1[1][2] * mat[5*6+0]; + mat[1*6+1] = r0[1][1] - r1[1][0] * mat[3*6+1] - r1[1][1] * mat[4*6+1] - r1[1][2] * mat[5*6+1]; + mat[1*6+2] = r0[1][2] - r1[1][0] * mat[3*6+2] - r1[1][1] * mat[4*6+2] - r1[1][2] * mat[5*6+2]; + mat[2*6+0] = r0[2][0] - r1[2][0] * mat[3*6+0] - r1[2][1] * mat[4*6+0] - r1[2][2] * mat[5*6+0]; + mat[2*6+1] = r0[2][1] - r1[2][0] * mat[3*6+1] - r1[2][1] * mat[4*6+1] - r1[2][2] * mat[5*6+1]; + mat[2*6+2] = r0[2][2] - r1[2][0] * mat[3*6+2] - r1[2][1] * mat[4*6+2] - r1[2][2] * mat[5*6+2]; + + // m1 = r1 * r3; + mat[0*6+3] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0] + r1[0][2] * r3[2][0]; + mat[0*6+4] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1] + r1[0][2] * r3[2][1]; + mat[0*6+5] = r1[0][0] * r3[0][2] + r1[0][1] * r3[1][2] + r1[0][2] * r3[2][2]; + mat[1*6+3] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0] + r1[1][2] * r3[2][0]; + mat[1*6+4] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1] + r1[1][2] * r3[2][1]; + mat[1*6+5] = r1[1][0] * r3[0][2] + r1[1][1] * r3[1][2] + r1[1][2] * r3[2][2]; + mat[2*6+3] = r1[2][0] * r3[0][0] + r1[2][1] * r3[1][0] + r1[2][2] * r3[2][0]; + mat[2*6+4] = r1[2][0] * r3[0][1] + r1[2][1] * r3[1][1] + r1[2][2] * r3[2][1]; + mat[2*6+5] = r1[2][0] * r3[0][2] + r1[2][1] * r3[1][2] + r1[2][2] * r3[2][2]; + + // m3 = -r3; + mat[3*6+3] = -r3[0][0]; + mat[3*6+4] = -r3[0][1]; + mat[3*6+5] = -r3[0][2]; + mat[4*6+3] = -r3[1][0]; + mat[4*6+4] = -r3[1][1]; + mat[4*6+5] = -r3[1][2]; + mat[5*6+3] = -r3[2][0]; + mat[5*6+4] = -r3[2][1]; + mat[5*6+5] = -r3[2][2]; + + return true; +#endif +} + +/* +============= +idMat6::ToString +============= +*/ +const char *idMat6::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/Matrix.h b/neo/idlib/math/Matrix.h new file mode 100644 index 00000000..395f6e5e --- /dev/null +++ b/neo/idlib/math/Matrix.h @@ -0,0 +1,1759 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_MATRIX_H__ +#define __MATH_MATRIX_H__ + +/* +=============================================================================== + + Matrix classes, all matrices are row-major except idMat3 + +=============================================================================== +*/ + +#define MATRIX_INVERSE_EPSILON 1e-14 +#define MATRIX_EPSILON 1e-6 + +class idAngles; +class idQuat; +class idCQuat; +class idRotation; +class idMat4; + +//=============================================================== +// +// idMat2 - 2x2 matrix +// +//=============================================================== + +class idMat2 { +public: + idMat2(); + explicit idMat2( const idVec2 &x, const idVec2 &y ); + explicit idMat2( const float xx, const float xy, const float yx, const float yy ); + explicit idMat2( const float src[ 2 ][ 2 ] ); + + const idVec2 & operator[]( int index ) const; + idVec2 & operator[]( int index ); + idMat2 operator-() const; + idMat2 operator*( const float a ) const; + idVec2 operator*( const idVec2 &vec ) const; + idMat2 operator*( const idMat2 &a ) const; + idMat2 operator+( const idMat2 &a ) const; + idMat2 operator-( const idMat2 &a ) const; + idMat2 & operator*=( const float a ); + idMat2 & operator*=( const idMat2 &a ); + idMat2 & operator+=( const idMat2 &a ); + idMat2 & operator-=( const idMat2 &a ); + + friend idMat2 operator*( const float a, const idMat2 &mat ); + friend idVec2 operator*( const idVec2 &vec, const idMat2 &mat ); + friend idVec2 & operator*=( idVec2 &vec, const idMat2 &mat ); + + bool Compare( const idMat2 &a ) const; // exact compare, no epsilon + bool Compare( const idMat2 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat2 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat2 &a ) const; // exact compare, no epsilon + + void Zero(); + void Identity(); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + float Trace() const; + float Determinant() const; + idMat2 Transpose() const; // returns transpose + idMat2 & TransposeSelf(); + idMat2 Inverse() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf(); // returns false if determinant is zero + idMat2 InverseFast() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf(); // returns false if determinant is zero + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + idVec2 mat[ 2 ]; +}; + +extern idMat2 mat2_zero; +extern idMat2 mat2_identity; +#define mat2_default mat2_identity + +ID_INLINE idMat2::idMat2() { +} + +ID_INLINE idMat2::idMat2( const idVec2 &x, const idVec2 &y ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; +} + +ID_INLINE idMat2::idMat2( const float xx, const float xy, const float yx, const float yy ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; +} + +ID_INLINE idMat2::idMat2( const float src[ 2 ][ 2 ] ) { + memcpy( mat, src, 2 * 2 * sizeof( float ) ); +} + +ID_INLINE const idVec2 &idMat2::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 2 ) ); + return mat[ index ]; +} + +ID_INLINE idVec2 &idMat2::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 2 ) ); + return mat[ index ]; +} + +ID_INLINE idMat2 idMat2::operator-() const { + return idMat2( -mat[0][0], -mat[0][1], + -mat[1][0], -mat[1][1] ); +} + +ID_INLINE idVec2 idMat2::operator*( const idVec2 &vec ) const { + return idVec2( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y ); +} + +ID_INLINE idMat2 idMat2::operator*( const idMat2 &a ) const { + return idMat2( + mat[0].x * a[0].x + mat[0].y * a[1].x, + mat[0].x * a[0].y + mat[0].y * a[1].y, + mat[1].x * a[0].x + mat[1].y * a[1].x, + mat[1].x * a[0].y + mat[1].y * a[1].y ); +} + +ID_INLINE idMat2 idMat2::operator*( const float a ) const { + return idMat2( + mat[0].x * a, mat[0].y * a, + mat[1].x * a, mat[1].y * a ); +} + +ID_INLINE idMat2 idMat2::operator+( const idMat2 &a ) const { + return idMat2( + mat[0].x + a[0].x, mat[0].y + a[0].y, + mat[1].x + a[1].x, mat[1].y + a[1].y ); +} + +ID_INLINE idMat2 idMat2::operator-( const idMat2 &a ) const { + return idMat2( + mat[0].x - a[0].x, mat[0].y - a[0].y, + mat[1].x - a[1].x, mat[1].y - a[1].y ); +} + +ID_INLINE idMat2 &idMat2::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; + mat[1].x *= a; mat[1].y *= a; + + return *this; +} + +ID_INLINE idMat2 &idMat2::operator*=( const idMat2 &a ) { + float x, y; + x = mat[0].x; y = mat[0].y; + mat[0].x = x * a[0].x + y * a[1].x; + mat[0].y = x * a[0].y + y * a[1].y; + x = mat[1].x; y = mat[1].y; + mat[1].x = x * a[0].x + y * a[1].x; + mat[1].y = x * a[0].y + y * a[1].y; + return *this; +} + +ID_INLINE idMat2 &idMat2::operator+=( const idMat2 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; + mat[1].x += a[1].x; mat[1].y += a[1].y; + + return *this; +} + +ID_INLINE idMat2 &idMat2::operator-=( const idMat2 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; + + return *this; +} + +ID_INLINE idVec2 operator*( const idVec2 &vec, const idMat2 &mat ) { + return mat * vec; +} + +ID_INLINE idMat2 operator*( const float a, idMat2 const &mat ) { + return mat * a; +} + +ID_INLINE idVec2 &operator*=( idVec2 &vec, const idMat2 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat2::Compare( const idMat2 &a ) const { + if ( mat[0].Compare( a[0] ) && + mat[1].Compare( a[1] ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat2::Compare( const idMat2 &a, const float epsilon ) const { + if ( mat[0].Compare( a[0], epsilon ) && + mat[1].Compare( a[1], epsilon ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat2::operator==( const idMat2 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat2::operator!=( const idMat2 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat2::Zero() { + mat[0].Zero(); + mat[1].Zero(); +} + +ID_INLINE void idMat2::Identity() { + *this = mat2_identity; +} + +ID_INLINE bool idMat2::IsIdentity( const float epsilon ) const { + return Compare( mat2_identity, epsilon ); +} + +ID_INLINE bool idMat2::IsSymmetric( const float epsilon ) const { + return ( idMath::Fabs( mat[0][1] - mat[1][0] ) < epsilon ); +} + +ID_INLINE bool idMat2::IsDiagonal( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] ) > epsilon || + idMath::Fabs( mat[1][0] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE float idMat2::Trace() const { + return ( mat[0][0] + mat[1][1] ); +} + +ID_INLINE float idMat2::Determinant() const { + return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; +} + +ID_INLINE idMat2 idMat2::Transpose() const { + return idMat2( mat[0][0], mat[1][0], + mat[0][1], mat[1][1] ); +} + +ID_INLINE idMat2 &idMat2::TransposeSelf() { + float tmp; + + tmp = mat[0][1]; + mat[0][1] = mat[1][0]; + mat[1][0] = tmp; + + return *this; +} + +ID_INLINE idMat2 idMat2::Inverse() const { + idMat2 invMat; + + invMat = *this; + verify( invMat.InverseSelf() ); + return invMat; +} + +ID_INLINE idMat2 idMat2::InverseFast() const { + idMat2 invMat; + + invMat = *this; + verify( invMat.InverseFastSelf() ); + return invMat; +} + +ID_INLINE int idMat2::GetDimension() const { + return 4; +} + +ID_INLINE const float *idMat2::ToFloatPtr() const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat2::ToFloatPtr() { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat3 - 3x3 matrix +// +// NOTE: matrix is column-major +// +//=============================================================== + +class idMat3 { +public: + idMat3(); + explicit idMat3( const idVec3 &x, const idVec3 &y, const idVec3 &z ); + explicit idMat3( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ); + explicit idMat3( const float src[ 3 ][ 3 ] ); + + const idVec3 & operator[]( int index ) const; + idVec3 & operator[]( int index ); + idMat3 operator-() const; + idMat3 operator*( const float a ) const; + idVec3 operator*( const idVec3 &vec ) const; + idMat3 operator*( const idMat3 &a ) const; + idMat3 operator+( const idMat3 &a ) const; + idMat3 operator-( const idMat3 &a ) const; + idMat3 & operator*=( const float a ); + idMat3 & operator*=( const idMat3 &a ); + idMat3 & operator+=( const idMat3 &a ); + idMat3 & operator-=( const idMat3 &a ); + + friend idMat3 operator*( const float a, const idMat3 &mat ); + friend idVec3 operator*( const idVec3 &vec, const idMat3 &mat ); + friend idVec3 & operator*=( idVec3 &vec, const idMat3 &mat ); + + bool Compare( const idMat3 &a ) const; // exact compare, no epsilon + bool Compare( const idMat3 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat3 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat3 &a ) const; // exact compare, no epsilon + + void Zero(); + void Identity(); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsRotated() const; + + void ProjectVector( const idVec3 &src, idVec3 &dst ) const; + void UnprojectVector( const idVec3 &src, idVec3 &dst ) const; + + bool FixDegeneracies(); // fix degenerate axial cases + bool FixDenormals(); // change tiny numbers to zero + + float Trace() const; + float Determinant() const; + idMat3 OrthoNormalize() const; + idMat3 & OrthoNormalizeSelf(); + idMat3 Transpose() const; // returns transpose + idMat3 & TransposeSelf(); + idMat3 Inverse() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf(); // returns false if determinant is zero + idMat3 InverseFast() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf(); // returns false if determinant is zero + idMat3 TransposeMultiply( const idMat3 &b ) const; + + idMat3 InertiaTranslate( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) const; + idMat3 & InertiaTranslateSelf( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ); + idMat3 InertiaRotate( const idMat3 &rotation ) const; + idMat3 & InertiaRotateSelf( const idMat3 &rotation ); + + int GetDimension() const; + + idAngles ToAngles() const; + idQuat ToQuat() const; + idCQuat ToCQuat() const; + idRotation ToRotation() const; + idMat4 ToMat4() const; + idVec3 ToAngularVelocity() const; + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + friend void TransposeMultiply( const idMat3 &inv, const idMat3 &b, idMat3 &dst ); + friend idMat3 SkewSymmetric( idVec3 const &src ); + +private: + idVec3 mat[ 3 ]; +}; + +extern idMat3 mat3_zero; +extern idMat3 mat3_identity; +#define mat3_default mat3_identity + +ID_INLINE idMat3::idMat3() { +} + +ID_INLINE idMat3::idMat3( const idVec3 &x, const idVec3 &y, const idVec3 &z ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; mat[ 0 ].z = x.z; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; mat[ 1 ].z = y.z; + mat[ 2 ].x = z.x; mat[ 2 ].y = z.y; mat[ 2 ].z = z.z; +} + +ID_INLINE idMat3::idMat3( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; mat[ 0 ].z = xz; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; mat[ 1 ].z = yz; + mat[ 2 ].x = zx; mat[ 2 ].y = zy; mat[ 2 ].z = zz; +} + +ID_INLINE idMat3::idMat3( const float src[ 3 ][ 3 ] ) { + memcpy( mat, src, 3 * 3 * sizeof( float ) ); +} + +ID_INLINE const idVec3 &idMat3::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3 &idMat3::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idMat3 idMat3::operator-() const { + return idMat3( -mat[0][0], -mat[0][1], -mat[0][2], + -mat[1][0], -mat[1][1], -mat[1][2], + -mat[2][0], -mat[2][1], -mat[2][2] ); +} + +ID_INLINE idVec3 idMat3::operator*( const idVec3 &vec ) const { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE idMat3 idMat3::operator*( const idMat3 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat3 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ]; + dstPtr++; + } + m1Ptr += 3; + } + return dst; +} + +ID_INLINE idMat3 idMat3::operator*( const float a ) const { + return idMat3( + mat[0].x * a, mat[0].y * a, mat[0].z * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a ); +} + +ID_INLINE idMat3 idMat3::operator+( const idMat3 &a ) const { + return idMat3( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z ); +} + +ID_INLINE idMat3 idMat3::operator-( const idMat3 &a ) const { + return idMat3( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z ); +} + +ID_INLINE idMat3 &idMat3::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; + + return *this; +} + +ID_INLINE idMat3 &idMat3::operator*=( const idMat3 &a ) { + int i, j; + const float *m2Ptr; + float *m1Ptr, dst[3]; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[j] = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ]; + } + m1Ptr[0] = dst[0]; m1Ptr[1] = dst[1]; m1Ptr[2] = dst[2]; + m1Ptr += 3; + } + return *this; +} + +ID_INLINE idMat3 &idMat3::operator+=( const idMat3 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; + + return *this; +} + +ID_INLINE idMat3 &idMat3::operator-=( const idMat3 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; + + return *this; +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const idMat3 &mat ) { + return mat * vec; +} + +ID_INLINE idMat3 operator*( const float a, const idMat3 &mat ) { + return mat * a; +} + +ID_INLINE idVec3 &operator*=( idVec3 &vec, const idMat3 &mat ) { + float x = mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z; + float y = mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z; + vec.z = mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z; + vec.x = x; + vec.y = y; + return vec; +} + +ID_INLINE bool idMat3::Compare( const idMat3 &a ) const { + if ( mat[0].Compare( a[0] ) && + mat[1].Compare( a[1] ) && + mat[2].Compare( a[2] ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat3::Compare( const idMat3 &a, const float epsilon ) const { + if ( mat[0].Compare( a[0], epsilon ) && + mat[1].Compare( a[1], epsilon ) && + mat[2].Compare( a[2], epsilon ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat3::operator==( const idMat3 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat3::operator!=( const idMat3 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat3::Zero() { + memset( mat, 0, sizeof( idMat3 ) ); +} + +ID_INLINE void idMat3::Identity() { + *this = mat3_identity; +} + +ID_INLINE bool idMat3::IsIdentity( const float epsilon ) const { + return Compare( mat3_identity, epsilon ); +} + +ID_INLINE bool idMat3::IsSymmetric( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] - mat[1][0] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( mat[0][2] - mat[2][0] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( mat[1][2] - mat[2][1] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idMat3::IsDiagonal( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] ) > epsilon || + idMath::Fabs( mat[0][2] ) > epsilon || + idMath::Fabs( mat[1][0] ) > epsilon || + idMath::Fabs( mat[1][2] ) > epsilon || + idMath::Fabs( mat[2][0] ) > epsilon || + idMath::Fabs( mat[2][1] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idMat3::IsRotated() const { + return !Compare( mat3_identity ); +} + +ID_INLINE void idMat3::ProjectVector( const idVec3 &src, idVec3 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; +} + +ID_INLINE void idMat3::UnprojectVector( const idVec3 &src, idVec3 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z; +} + +ID_INLINE bool idMat3::FixDegeneracies() { + bool r = mat[0].FixDegenerateNormal(); + r |= mat[1].FixDegenerateNormal(); + r |= mat[2].FixDegenerateNormal(); + return r; +} + +ID_INLINE bool idMat3::FixDenormals() { + bool r = mat[0].FixDenormals(); + r |= mat[1].FixDenormals(); + r |= mat[2].FixDenormals(); + return r; +} + +ID_INLINE float idMat3::Trace() const { + return ( mat[0][0] + mat[1][1] + mat[2][2] ); +} + +ID_INLINE idMat3 idMat3::OrthoNormalize() const { + idMat3 ortho; + + ortho = *this; + ortho[ 0 ].Normalize(); + ortho[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + ortho[ 2 ].Normalize(); + ortho[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + ortho[ 1 ].Normalize(); + return ortho; +} + +ID_INLINE idMat3 &idMat3::OrthoNormalizeSelf() { + mat[ 0 ].Normalize(); + mat[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + mat[ 2 ].Normalize(); + mat[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + mat[ 1 ].Normalize(); + return *this; +} + +ID_INLINE idMat3 idMat3::Transpose() const { + return idMat3( mat[0][0], mat[1][0], mat[2][0], + mat[0][1], mat[1][1], mat[2][1], + mat[0][2], mat[1][2], mat[2][2] ); +} + +ID_INLINE idMat3 &idMat3::TransposeSelf() { + float tmp0, tmp1, tmp2; + + tmp0 = mat[0][1]; + mat[0][1] = mat[1][0]; + mat[1][0] = tmp0; + tmp1 = mat[0][2]; + mat[0][2] = mat[2][0]; + mat[2][0] = tmp1; + tmp2 = mat[1][2]; + mat[1][2] = mat[2][1]; + mat[2][1] = tmp2; + + return *this; +} + +ID_INLINE idMat3 idMat3::Inverse() const { + idMat3 invMat; + + invMat = *this; + verify( invMat.InverseSelf() ); + return invMat; +} + +ID_INLINE idMat3 idMat3::InverseFast() const { + idMat3 invMat; + + invMat = *this; + verify( invMat.InverseFastSelf() ); + return invMat; +} + +ID_INLINE idMat3 idMat3::TransposeMultiply( const idMat3 &b ) const { + return idMat3( mat[0].x * b[0].x + mat[1].x * b[1].x + mat[2].x * b[2].x, + mat[0].x * b[0].y + mat[1].x * b[1].y + mat[2].x * b[2].y, + mat[0].x * b[0].z + mat[1].x * b[1].z + mat[2].x * b[2].z, + mat[0].y * b[0].x + mat[1].y * b[1].x + mat[2].y * b[2].x, + mat[0].y * b[0].y + mat[1].y * b[1].y + mat[2].y * b[2].y, + mat[0].y * b[0].z + mat[1].y * b[1].z + mat[2].y * b[2].z, + mat[0].z * b[0].x + mat[1].z * b[1].x + mat[2].z * b[2].x, + mat[0].z * b[0].y + mat[1].z * b[1].y + mat[2].z * b[2].y, + mat[0].z * b[0].z + mat[1].z * b[1].z + mat[2].z * b[2].z ); +} + +ID_INLINE void TransposeMultiply( const idMat3 &transpose, const idMat3 &b, idMat3 &dst ) { + dst[0].x = transpose[0].x * b[0].x + transpose[1].x * b[1].x + transpose[2].x * b[2].x; + dst[0].y = transpose[0].x * b[0].y + transpose[1].x * b[1].y + transpose[2].x * b[2].y; + dst[0].z = transpose[0].x * b[0].z + transpose[1].x * b[1].z + transpose[2].x * b[2].z; + dst[1].x = transpose[0].y * b[0].x + transpose[1].y * b[1].x + transpose[2].y * b[2].x; + dst[1].y = transpose[0].y * b[0].y + transpose[1].y * b[1].y + transpose[2].y * b[2].y; + dst[1].z = transpose[0].y * b[0].z + transpose[1].y * b[1].z + transpose[2].y * b[2].z; + dst[2].x = transpose[0].z * b[0].x + transpose[1].z * b[1].x + transpose[2].z * b[2].x; + dst[2].y = transpose[0].z * b[0].y + transpose[1].z * b[1].y + transpose[2].z * b[2].y; + dst[2].z = transpose[0].z * b[0].z + transpose[1].z * b[1].z + transpose[2].z * b[2].z; +} + +ID_INLINE idMat3 SkewSymmetric( idVec3 const &src ) { + return idMat3( 0.0f, -src.z, src.y, src.z, 0.0f, -src.x, -src.y, src.x, 0.0f ); +} + +ID_INLINE int idMat3::GetDimension() const { + return 9; +} + +ID_INLINE const float *idMat3::ToFloatPtr() const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat3::ToFloatPtr() { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat4 - 4x4 matrix +// +//=============================================================== + +class idMat4 { +public: + idMat4(); + explicit idMat4( const idVec4 &x, const idVec4 &y, const idVec4 &z, const idVec4 &w ); + explicit idMat4(const float xx, const float xy, const float xz, const float xw, + const float yx, const float yy, const float yz, const float yw, + const float zx, const float zy, const float zz, const float zw, + const float wx, const float wy, const float wz, const float ww ); + explicit idMat4( const idMat3 &rotation, const idVec3 &translation ); + explicit idMat4( const float src[ 4 ][ 4 ] ); + + const idVec4 & operator[]( int index ) const; + idVec4 & operator[]( int index ); + idMat4 operator*( const float a ) const; + idVec4 operator*( const idVec4 &vec ) const; + idVec3 operator*( const idVec3 &vec ) const; + idMat4 operator*( const idMat4 &a ) const; + idMat4 operator+( const idMat4 &a ) const; + idMat4 operator-( const idMat4 &a ) const; + idMat4 & operator*=( const float a ); + idMat4 & operator*=( const idMat4 &a ); + idMat4 & operator+=( const idMat4 &a ); + idMat4 & operator-=( const idMat4 &a ); + + friend idMat4 operator*( const float a, const idMat4 &mat ); + friend idVec4 operator*( const idVec4 &vec, const idMat4 &mat ); + friend idVec3 operator*( const idVec3 &vec, const idMat4 &mat ); + friend idVec4 & operator*=( idVec4 &vec, const idMat4 &mat ); + friend idVec3 & operator*=( idVec3 &vec, const idMat4 &mat ); + + bool Compare( const idMat4 &a ) const; // exact compare, no epsilon + bool Compare( const idMat4 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat4 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat4 &a ) const; // exact compare, no epsilon + + void Zero(); + void Identity(); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsRotated() const; + + void ProjectVector( const idVec4 &src, idVec4 &dst ) const; + void UnprojectVector( const idVec4 &src, idVec4 &dst ) const; + + float Trace() const; + float Determinant() const; + idMat4 Transpose() const; // returns transpose + idMat4 & TransposeSelf(); + idMat4 Inverse() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf(); // returns false if determinant is zero + idMat4 InverseFast() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf(); // returns false if determinant is zero + idMat4 TransposeMultiply( const idMat4 &b ) const; + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + idVec4 mat[ 4 ]; +}; + +extern idMat4 mat4_zero; +extern idMat4 mat4_identity; +#define mat4_default mat4_identity + +ID_INLINE idMat4::idMat4() { +} + +ID_INLINE idMat4::idMat4( const idVec4 &x, const idVec4 &y, const idVec4 &z, const idVec4 &w ) { + mat[ 0 ] = x; + mat[ 1 ] = y; + mat[ 2 ] = z; + mat[ 3 ] = w; +} + +ID_INLINE idMat4::idMat4( const float xx, const float xy, const float xz, const float xw, + const float yx, const float yy, const float yz, const float yw, + const float zx, const float zy, const float zz, const float zw, + const float wx, const float wy, const float wz, const float ww ) { + mat[0][0] = xx; mat[0][1] = xy; mat[0][2] = xz; mat[0][3] = xw; + mat[1][0] = yx; mat[1][1] = yy; mat[1][2] = yz; mat[1][3] = yw; + mat[2][0] = zx; mat[2][1] = zy; mat[2][2] = zz; mat[2][3] = zw; + mat[3][0] = wx; mat[3][1] = wy; mat[3][2] = wz; mat[3][3] = ww; +} + +ID_INLINE idMat4::idMat4( const idMat3 &rotation, const idVec3 &translation ) { + // NOTE: idMat3 is transposed because it is column-major + mat[ 0 ][ 0 ] = rotation[0][0]; + mat[ 0 ][ 1 ] = rotation[1][0]; + mat[ 0 ][ 2 ] = rotation[2][0]; + mat[ 0 ][ 3 ] = translation[0]; + mat[ 1 ][ 0 ] = rotation[0][1]; + mat[ 1 ][ 1 ] = rotation[1][1]; + mat[ 1 ][ 2 ] = rotation[2][1]; + mat[ 1 ][ 3 ] = translation[1]; + mat[ 2 ][ 0 ] = rotation[0][2]; + mat[ 2 ][ 1 ] = rotation[1][2]; + mat[ 2 ][ 2 ] = rotation[2][2]; + mat[ 2 ][ 3 ] = translation[2]; + mat[ 3 ][ 0 ] = 0.0f; + mat[ 3 ][ 1 ] = 0.0f; + mat[ 3 ][ 2 ] = 0.0f; + mat[ 3 ][ 3 ] = 1.0f; +} + +ID_INLINE idMat4::idMat4( const float src[ 4 ][ 4 ] ) { + memcpy( mat, src, 4 * 4 * sizeof( float ) ); +} + +ID_INLINE const idVec4 &idMat4::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 4 ) ); + return mat[ index ]; +} + +ID_INLINE idVec4 &idMat4::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 4 ) ); + return mat[ index ]; +} + +ID_INLINE idMat4 idMat4::operator*( const float a ) const { + return idMat4( + mat[0].x * a, mat[0].y * a, mat[0].z * a, mat[0].w * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, mat[1].w * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a, mat[2].w * a, + mat[3].x * a, mat[3].y * a, mat[3].z * a, mat[3].w * a ); +} + +ID_INLINE idVec4 idMat4::operator*( const idVec4 &vec ) const { + return idVec4( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w * vec.w, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w * vec.w, + mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w * vec.w, + mat[ 3 ].x * vec.x + mat[ 3 ].y * vec.y + mat[ 3 ].z * vec.z + mat[ 3 ].w * vec.w ); +} + +ID_INLINE idVec3 idMat4::operator*( const idVec3 &vec ) const { + float s = mat[ 3 ].x * vec.x + mat[ 3 ].y * vec.y + mat[ 3 ].z * vec.z + mat[ 3 ].w; + if ( s == 0.0f ) { + return idVec3( 0.0f, 0.0f, 0.0f ); + } + if ( s == 1.0f ) { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w, + mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w ); + } + else { + float invS = 1.0f / s; + return idVec3( + (mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w) * invS, + (mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w) * invS, + (mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w) * invS ); + } +} + +ID_INLINE idMat4 idMat4::operator*( const idMat4 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat4 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ]; + dstPtr++; + } + m1Ptr += 4; + } + return dst; +} + +ID_INLINE idMat4 idMat4::operator+( const idMat4 &a ) const { + return idMat4( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, mat[0].w + a[0].w, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, mat[1].w + a[1].w, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z, mat[2].w + a[2].w, + mat[3].x + a[3].x, mat[3].y + a[3].y, mat[3].z + a[3].z, mat[3].w + a[3].w ); +} + +ID_INLINE idMat4 idMat4::operator-( const idMat4 &a ) const { + return idMat4( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, mat[0].w - a[0].w, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, mat[1].w - a[1].w, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z, mat[2].w - a[2].w, + mat[3].x - a[3].x, mat[3].y - a[3].y, mat[3].z - a[3].z, mat[3].w - a[3].w ); +} + +ID_INLINE idMat4 &idMat4::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; mat[0].w *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; mat[1].w *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; mat[2].w *= a; + mat[3].x *= a; mat[3].y *= a; mat[3].z *= a; mat[3].w *= a; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator*=( const idMat4 &a ) { + *this = (*this) * a; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator+=( const idMat4 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; mat[0].w += a[0].w; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; mat[1].w += a[1].w; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; mat[2].w += a[2].w; + mat[3].x += a[3].x; mat[3].y += a[3].y; mat[3].z += a[3].z; mat[3].w += a[3].w; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator-=( const idMat4 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; mat[0].w -= a[0].w; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; mat[1].w -= a[1].w; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; mat[2].w -= a[2].w; + mat[3].x -= a[3].x; mat[3].y -= a[3].y; mat[3].z -= a[3].z; mat[3].w -= a[3].w; + return *this; +} + +ID_INLINE idMat4 operator*( const float a, const idMat4 &mat ) { + return mat * a; +} + +ID_INLINE idVec4 operator*( const idVec4 &vec, const idMat4 &mat ) { + return mat * vec; +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const idMat4 &mat ) { + return mat * vec; +} + +ID_INLINE idVec4 &operator*=( idVec4 &vec, const idMat4 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE idVec3 &operator*=( idVec3 &vec, const idMat4 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat4::Compare( const idMat4 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 4*4; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat4::Compare( const idMat4 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 4*4; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat4::operator==( const idMat4 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat4::operator!=( const idMat4 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat4::Zero() { + memset( mat, 0, sizeof( idMat4 ) ); +} + +ID_INLINE void idMat4::Identity() { + *this = mat4_identity; +} + +ID_INLINE bool idMat4::IsIdentity( const float epsilon ) const { + return Compare( mat4_identity, epsilon ); +} + +ID_INLINE bool idMat4::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 4; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat4::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 4; i++ ) { + for ( int j = 0; j < 4; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat4::IsRotated() const { + if ( !mat[ 0 ][ 1 ] && !mat[ 0 ][ 2 ] && + !mat[ 1 ][ 0 ] && !mat[ 1 ][ 2 ] && + !mat[ 2 ][ 0 ] && !mat[ 2 ][ 1 ] ) { + return false; + } + return true; +} + +ID_INLINE void idMat4::ProjectVector( const idVec4 &src, idVec4 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; + dst.w = src * mat[ 3 ]; +} + +ID_INLINE void idMat4::UnprojectVector( const idVec4 &src, idVec4 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z + mat[ 3 ] * src.w; +} + +ID_INLINE float idMat4::Trace() const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] ); +} + +ID_INLINE idMat4 idMat4::Inverse() const { + idMat4 invMat; + + invMat = *this; + verify( invMat.InverseSelf() ); + return invMat; +} + +ID_INLINE idMat4 idMat4::InverseFast() const { + idMat4 invMat; + + invMat = *this; + verify( invMat.InverseFastSelf() ); + return invMat; +} + +ID_INLINE idMat4 idMat3::ToMat4() const { + // NOTE: idMat3 is transposed because it is column-major + return idMat4( mat[0][0], mat[1][0], mat[2][0], 0.0f, + mat[0][1], mat[1][1], mat[2][1], 0.0f, + mat[0][2], mat[1][2], mat[2][2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f ); +} + +ID_INLINE int idMat4::GetDimension() const { + return 16; +} + +ID_INLINE const float *idMat4::ToFloatPtr() const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat4::ToFloatPtr() { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat5 - 5x5 matrix +// +//=============================================================== + +class idMat5 { +public: + idMat5(); + explicit idMat5( const idVec5 &v0, const idVec5 &v1, const idVec5 &v2, const idVec5 &v3, const idVec5 &v4 ); + explicit idMat5( const float src[ 5 ][ 5 ] ); + + const idVec5 & operator[]( int index ) const; + idVec5 & operator[]( int index ); + idMat5 operator*( const float a ) const; + idVec5 operator*( const idVec5 &vec ) const; + idMat5 operator*( const idMat5 &a ) const; + idMat5 operator+( const idMat5 &a ) const; + idMat5 operator-( const idMat5 &a ) const; + idMat5 & operator*=( const float a ); + idMat5 & operator*=( const idMat5 &a ); + idMat5 & operator+=( const idMat5 &a ); + idMat5 & operator-=( const idMat5 &a ); + + friend idMat5 operator*( const float a, const idMat5 &mat ); + friend idVec5 operator*( const idVec5 &vec, const idMat5 &mat ); + friend idVec5 & operator*=( idVec5 &vec, const idMat5 &mat ); + + bool Compare( const idMat5 &a ) const; // exact compare, no epsilon + bool Compare( const idMat5 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat5 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat5 &a ) const; // exact compare, no epsilon + + void Zero(); + void Identity(); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + float Trace() const; + float Determinant() const; + idMat5 Transpose() const; // returns transpose + idMat5 & TransposeSelf(); + idMat5 Inverse() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf(); // returns false if determinant is zero + idMat5 InverseFast() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf(); // returns false if determinant is zero + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + idVec5 mat[ 5 ]; +}; + +extern idMat5 mat5_zero; +extern idMat5 mat5_identity; +#define mat5_default mat5_identity + +ID_INLINE idMat5::idMat5() { +} + +ID_INLINE idMat5::idMat5( const float src[ 5 ][ 5 ] ) { + memcpy( mat, src, 5 * 5 * sizeof( float ) ); +} + +ID_INLINE idMat5::idMat5( const idVec5 &v0, const idVec5 &v1, const idVec5 &v2, const idVec5 &v3, const idVec5 &v4 ) { + mat[0] = v0; + mat[1] = v1; + mat[2] = v2; + mat[3] = v3; + mat[4] = v4; +} + +ID_INLINE const idVec5 &idMat5::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 5 ) ); + return mat[ index ]; +} + +ID_INLINE idVec5 &idMat5::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 5 ) ); + return mat[ index ]; +} + +ID_INLINE idMat5 idMat5::operator*( const idMat5 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat5 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ]; + dstPtr++; + } + m1Ptr += 5; + } + return dst; +} + +ID_INLINE idMat5 idMat5::operator*( const float a ) const { + return idMat5( + idVec5( mat[0][0] * a, mat[0][1] * a, mat[0][2] * a, mat[0][3] * a, mat[0][4] * a ), + idVec5( mat[1][0] * a, mat[1][1] * a, mat[1][2] * a, mat[1][3] * a, mat[1][4] * a ), + idVec5( mat[2][0] * a, mat[2][1] * a, mat[2][2] * a, mat[2][3] * a, mat[2][4] * a ), + idVec5( mat[3][0] * a, mat[3][1] * a, mat[3][2] * a, mat[3][3] * a, mat[3][4] * a ), + idVec5( mat[4][0] * a, mat[4][1] * a, mat[4][2] * a, mat[4][3] * a, mat[4][4] * a ) ); +} + +ID_INLINE idVec5 idMat5::operator*( const idVec5 &vec ) const { + return idVec5( + mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3] + mat[0][4] * vec[4], + mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3] + mat[1][4] * vec[4], + mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3] + mat[2][4] * vec[4], + mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3] + mat[3][4] * vec[4], + mat[4][0] * vec[0] + mat[4][1] * vec[1] + mat[4][2] * vec[2] + mat[4][3] * vec[3] + mat[4][4] * vec[4] ); +} + +ID_INLINE idMat5 idMat5::operator+( const idMat5 &a ) const { + return idMat5( + idVec5( mat[0][0] + a[0][0], mat[0][1] + a[0][1], mat[0][2] + a[0][2], mat[0][3] + a[0][3], mat[0][4] + a[0][4] ), + idVec5( mat[1][0] + a[1][0], mat[1][1] + a[1][1], mat[1][2] + a[1][2], mat[1][3] + a[1][3], mat[1][4] + a[1][4] ), + idVec5( mat[2][0] + a[2][0], mat[2][1] + a[2][1], mat[2][2] + a[2][2], mat[2][3] + a[2][3], mat[2][4] + a[2][4] ), + idVec5( mat[3][0] + a[3][0], mat[3][1] + a[3][1], mat[3][2] + a[3][2], mat[3][3] + a[3][3], mat[3][4] + a[3][4] ), + idVec5( mat[4][0] + a[4][0], mat[4][1] + a[4][1], mat[4][2] + a[4][2], mat[4][3] + a[4][3], mat[4][4] + a[4][4] ) ); +} + +ID_INLINE idMat5 idMat5::operator-( const idMat5 &a ) const { + return idMat5( + idVec5( mat[0][0] - a[0][0], mat[0][1] - a[0][1], mat[0][2] - a[0][2], mat[0][3] - a[0][3], mat[0][4] - a[0][4] ), + idVec5( mat[1][0] - a[1][0], mat[1][1] - a[1][1], mat[1][2] - a[1][2], mat[1][3] - a[1][3], mat[1][4] - a[1][4] ), + idVec5( mat[2][0] - a[2][0], mat[2][1] - a[2][1], mat[2][2] - a[2][2], mat[2][3] - a[2][3], mat[2][4] - a[2][4] ), + idVec5( mat[3][0] - a[3][0], mat[3][1] - a[3][1], mat[3][2] - a[3][2], mat[3][3] - a[3][3], mat[3][4] - a[3][4] ), + idVec5( mat[4][0] - a[4][0], mat[4][1] - a[4][1], mat[4][2] - a[4][2], mat[4][3] - a[4][3], mat[4][4] - a[4][4] ) ); +} + +ID_INLINE idMat5 &idMat5::operator*=( const float a ) { + mat[0][0] *= a; mat[0][1] *= a; mat[0][2] *= a; mat[0][3] *= a; mat[0][4] *= a; + mat[1][0] *= a; mat[1][1] *= a; mat[1][2] *= a; mat[1][3] *= a; mat[1][4] *= a; + mat[2][0] *= a; mat[2][1] *= a; mat[2][2] *= a; mat[2][3] *= a; mat[2][4] *= a; + mat[3][0] *= a; mat[3][1] *= a; mat[3][2] *= a; mat[3][3] *= a; mat[3][4] *= a; + mat[4][0] *= a; mat[4][1] *= a; mat[4][2] *= a; mat[4][3] *= a; mat[4][4] *= a; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator*=( const idMat5 &a ) { + *this = *this * a; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator+=( const idMat5 &a ) { + mat[0][0] += a[0][0]; mat[0][1] += a[0][1]; mat[0][2] += a[0][2]; mat[0][3] += a[0][3]; mat[0][4] += a[0][4]; + mat[1][0] += a[1][0]; mat[1][1] += a[1][1]; mat[1][2] += a[1][2]; mat[1][3] += a[1][3]; mat[1][4] += a[1][4]; + mat[2][0] += a[2][0]; mat[2][1] += a[2][1]; mat[2][2] += a[2][2]; mat[2][3] += a[2][3]; mat[2][4] += a[2][4]; + mat[3][0] += a[3][0]; mat[3][1] += a[3][1]; mat[3][2] += a[3][2]; mat[3][3] += a[3][3]; mat[3][4] += a[3][4]; + mat[4][0] += a[4][0]; mat[4][1] += a[4][1]; mat[4][2] += a[4][2]; mat[4][3] += a[4][3]; mat[4][4] += a[4][4]; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator-=( const idMat5 &a ) { + mat[0][0] -= a[0][0]; mat[0][1] -= a[0][1]; mat[0][2] -= a[0][2]; mat[0][3] -= a[0][3]; mat[0][4] -= a[0][4]; + mat[1][0] -= a[1][0]; mat[1][1] -= a[1][1]; mat[1][2] -= a[1][2]; mat[1][3] -= a[1][3]; mat[1][4] -= a[1][4]; + mat[2][0] -= a[2][0]; mat[2][1] -= a[2][1]; mat[2][2] -= a[2][2]; mat[2][3] -= a[2][3]; mat[2][4] -= a[2][4]; + mat[3][0] -= a[3][0]; mat[3][1] -= a[3][1]; mat[3][2] -= a[3][2]; mat[3][3] -= a[3][3]; mat[3][4] -= a[3][4]; + mat[4][0] -= a[4][0]; mat[4][1] -= a[4][1]; mat[4][2] -= a[4][2]; mat[4][3] -= a[4][3]; mat[4][4] -= a[4][4]; + return *this; +} + +ID_INLINE idVec5 operator*( const idVec5 &vec, const idMat5 &mat ) { + return mat * vec; +} + +ID_INLINE idMat5 operator*( const float a, idMat5 const &mat ) { + return mat * a; +} + +ID_INLINE idVec5 &operator*=( idVec5 &vec, const idMat5 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat5::Compare( const idMat5 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 5*5; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat5::Compare( const idMat5 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 5*5; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat5::operator==( const idMat5 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat5::operator!=( const idMat5 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat5::Zero() { + memset( mat, 0, sizeof( idMat5 ) ); +} + +ID_INLINE void idMat5::Identity() { + *this = mat5_identity; +} + +ID_INLINE bool idMat5::IsIdentity( const float epsilon ) const { + return Compare( mat5_identity, epsilon ); +} + +ID_INLINE bool idMat5::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 5; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat5::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 5; i++ ) { + for ( int j = 0; j < 5; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE float idMat5::Trace() const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] + mat[4][4] ); +} + +ID_INLINE idMat5 idMat5::Inverse() const { + idMat5 invMat; + + invMat = *this; + verify( invMat.InverseSelf() ); + return invMat; +} + +ID_INLINE idMat5 idMat5::InverseFast() const { + idMat5 invMat; + + invMat = *this; + verify( invMat.InverseFastSelf() ); + return invMat; +} + +ID_INLINE int idMat5::GetDimension() const { + return 25; +} + +ID_INLINE const float *idMat5::ToFloatPtr() const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat5::ToFloatPtr() { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat6 - 6x6 matrix +// +//=============================================================== + +class idMat6 { +public: + idMat6(); + explicit idMat6( const idVec6 &v0, const idVec6 &v1, const idVec6 &v2, const idVec6 &v3, const idVec6 &v4, const idVec6 &v5 ); + explicit idMat6( const idMat3 &m0, const idMat3 &m1, const idMat3 &m2, const idMat3 &m3 ); + explicit idMat6( const float src[ 6 ][ 6 ] ); + + const idVec6 & operator[]( int index ) const; + idVec6 & operator[]( int index ); + idMat6 operator*( const float a ) const; + idVec6 operator*( const idVec6 &vec ) const; + idMat6 operator*( const idMat6 &a ) const; + idMat6 operator+( const idMat6 &a ) const; + idMat6 operator-( const idMat6 &a ) const; + idMat6 & operator*=( const float a ); + idMat6 & operator*=( const idMat6 &a ); + idMat6 & operator+=( const idMat6 &a ); + idMat6 & operator-=( const idMat6 &a ); + + friend idMat6 operator*( const float a, const idMat6 &mat ); + friend idVec6 operator*( const idVec6 &vec, const idMat6 &mat ); + friend idVec6 & operator*=( idVec6 &vec, const idMat6 &mat ); + + bool Compare( const idMat6 &a ) const; // exact compare, no epsilon + bool Compare( const idMat6 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat6 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat6 &a ) const; // exact compare, no epsilon + + void Zero(); + void Identity(); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + idMat3 SubMat3( int n ) const; + float Trace() const; + float Determinant() const; + idMat6 Transpose() const; // returns transpose + idMat6 & TransposeSelf(); + idMat6 Inverse() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf(); // returns false if determinant is zero + idMat6 InverseFast() const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf(); // returns false if determinant is zero + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + idVec6 mat[ 6 ]; +}; + +extern idMat6 mat6_zero; +extern idMat6 mat6_identity; +#define mat6_default mat6_identity + +ID_INLINE idMat6::idMat6() { +} + +ID_INLINE idMat6::idMat6( const idMat3 &m0, const idMat3 &m1, const idMat3 &m2, const idMat3 &m3 ) { + mat[0] = idVec6( m0[0][0], m0[0][1], m0[0][2], m1[0][0], m1[0][1], m1[0][2] ); + mat[1] = idVec6( m0[1][0], m0[1][1], m0[1][2], m1[1][0], m1[1][1], m1[1][2] ); + mat[2] = idVec6( m0[2][0], m0[2][1], m0[2][2], m1[2][0], m1[2][1], m1[2][2] ); + mat[3] = idVec6( m2[0][0], m2[0][1], m2[0][2], m3[0][0], m3[0][1], m3[0][2] ); + mat[4] = idVec6( m2[1][0], m2[1][1], m2[1][2], m3[1][0], m3[1][1], m3[1][2] ); + mat[5] = idVec6( m2[2][0], m2[2][1], m2[2][2], m3[2][0], m3[2][1], m3[2][2] ); +} + +ID_INLINE idMat6::idMat6( const idVec6 &v0, const idVec6 &v1, const idVec6 &v2, const idVec6 &v3, const idVec6 &v4, const idVec6 &v5 ) { + mat[0] = v0; + mat[1] = v1; + mat[2] = v2; + mat[3] = v3; + mat[4] = v4; + mat[5] = v5; +} + +ID_INLINE idMat6::idMat6( const float src[ 6 ][ 6 ] ) { + memcpy( mat, src, 6 * 6 * sizeof( float ) ); +} + +ID_INLINE const idVec6 &idMat6::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 6 ) ); + return mat[ index ]; +} + +ID_INLINE idVec6 &idMat6::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 6 ) ); + return mat[ index ]; +} + +ID_INLINE idMat6 idMat6::operator*( const idMat6 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat6 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 6; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 6 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 6 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 6 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 6 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 6 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 6 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return dst; +} + +ID_INLINE idMat6 idMat6::operator*( const float a ) const { + return idMat6( + idVec6( mat[0][0] * a, mat[0][1] * a, mat[0][2] * a, mat[0][3] * a, mat[0][4] * a, mat[0][5] * a ), + idVec6( mat[1][0] * a, mat[1][1] * a, mat[1][2] * a, mat[1][3] * a, mat[1][4] * a, mat[1][5] * a ), + idVec6( mat[2][0] * a, mat[2][1] * a, mat[2][2] * a, mat[2][3] * a, mat[2][4] * a, mat[2][5] * a ), + idVec6( mat[3][0] * a, mat[3][1] * a, mat[3][2] * a, mat[3][3] * a, mat[3][4] * a, mat[3][5] * a ), + idVec6( mat[4][0] * a, mat[4][1] * a, mat[4][2] * a, mat[4][3] * a, mat[4][4] * a, mat[4][5] * a ), + idVec6( mat[5][0] * a, mat[5][1] * a, mat[5][2] * a, mat[5][3] * a, mat[5][4] * a, mat[5][5] * a ) ); +} + +ID_INLINE idVec6 idMat6::operator*( const idVec6 &vec ) const { + return idVec6( + mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3] + mat[0][4] * vec[4] + mat[0][5] * vec[5], + mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3] + mat[1][4] * vec[4] + mat[1][5] * vec[5], + mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3] + mat[2][4] * vec[4] + mat[2][5] * vec[5], + mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3] + mat[3][4] * vec[4] + mat[3][5] * vec[5], + mat[4][0] * vec[0] + mat[4][1] * vec[1] + mat[4][2] * vec[2] + mat[4][3] * vec[3] + mat[4][4] * vec[4] + mat[4][5] * vec[5], + mat[5][0] * vec[0] + mat[5][1] * vec[1] + mat[5][2] * vec[2] + mat[5][3] * vec[3] + mat[5][4] * vec[4] + mat[5][5] * vec[5] ); +} + +ID_INLINE idMat6 idMat6::operator+( const idMat6 &a ) const { + return idMat6( + idVec6( mat[0][0] + a[0][0], mat[0][1] + a[0][1], mat[0][2] + a[0][2], mat[0][3] + a[0][3], mat[0][4] + a[0][4], mat[0][5] + a[0][5] ), + idVec6( mat[1][0] + a[1][0], mat[1][1] + a[1][1], mat[1][2] + a[1][2], mat[1][3] + a[1][3], mat[1][4] + a[1][4], mat[1][5] + a[1][5] ), + idVec6( mat[2][0] + a[2][0], mat[2][1] + a[2][1], mat[2][2] + a[2][2], mat[2][3] + a[2][3], mat[2][4] + a[2][4], mat[2][5] + a[2][5] ), + idVec6( mat[3][0] + a[3][0], mat[3][1] + a[3][1], mat[3][2] + a[3][2], mat[3][3] + a[3][3], mat[3][4] + a[3][4], mat[3][5] + a[3][5] ), + idVec6( mat[4][0] + a[4][0], mat[4][1] + a[4][1], mat[4][2] + a[4][2], mat[4][3] + a[4][3], mat[4][4] + a[4][4], mat[4][5] + a[4][5] ), + idVec6( mat[5][0] + a[5][0], mat[5][1] + a[5][1], mat[5][2] + a[5][2], mat[5][3] + a[5][3], mat[5][4] + a[5][4], mat[5][5] + a[5][5] ) ); +} + +ID_INLINE idMat6 idMat6::operator-( const idMat6 &a ) const { + return idMat6( + idVec6( mat[0][0] - a[0][0], mat[0][1] - a[0][1], mat[0][2] - a[0][2], mat[0][3] - a[0][3], mat[0][4] - a[0][4], mat[0][5] - a[0][5] ), + idVec6( mat[1][0] - a[1][0], mat[1][1] - a[1][1], mat[1][2] - a[1][2], mat[1][3] - a[1][3], mat[1][4] - a[1][4], mat[1][5] - a[1][5] ), + idVec6( mat[2][0] - a[2][0], mat[2][1] - a[2][1], mat[2][2] - a[2][2], mat[2][3] - a[2][3], mat[2][4] - a[2][4], mat[2][5] - a[2][5] ), + idVec6( mat[3][0] - a[3][0], mat[3][1] - a[3][1], mat[3][2] - a[3][2], mat[3][3] - a[3][3], mat[3][4] - a[3][4], mat[3][5] - a[3][5] ), + idVec6( mat[4][0] - a[4][0], mat[4][1] - a[4][1], mat[4][2] - a[4][2], mat[4][3] - a[4][3], mat[4][4] - a[4][4], mat[4][5] - a[4][5] ), + idVec6( mat[5][0] - a[5][0], mat[5][1] - a[5][1], mat[5][2] - a[5][2], mat[5][3] - a[5][3], mat[5][4] - a[5][4], mat[5][5] - a[5][5] ) ); +} + +ID_INLINE idMat6 &idMat6::operator*=( const float a ) { + mat[0][0] *= a; mat[0][1] *= a; mat[0][2] *= a; mat[0][3] *= a; mat[0][4] *= a; mat[0][5] *= a; + mat[1][0] *= a; mat[1][1] *= a; mat[1][2] *= a; mat[1][3] *= a; mat[1][4] *= a; mat[1][5] *= a; + mat[2][0] *= a; mat[2][1] *= a; mat[2][2] *= a; mat[2][3] *= a; mat[2][4] *= a; mat[2][5] *= a; + mat[3][0] *= a; mat[3][1] *= a; mat[3][2] *= a; mat[3][3] *= a; mat[3][4] *= a; mat[3][5] *= a; + mat[4][0] *= a; mat[4][1] *= a; mat[4][2] *= a; mat[4][3] *= a; mat[4][4] *= a; mat[4][5] *= a; + mat[5][0] *= a; mat[5][1] *= a; mat[5][2] *= a; mat[5][3] *= a; mat[5][4] *= a; mat[5][5] *= a; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator*=( const idMat6 &a ) { + *this = *this * a; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator+=( const idMat6 &a ) { + mat[0][0] += a[0][0]; mat[0][1] += a[0][1]; mat[0][2] += a[0][2]; mat[0][3] += a[0][3]; mat[0][4] += a[0][4]; mat[0][5] += a[0][5]; + mat[1][0] += a[1][0]; mat[1][1] += a[1][1]; mat[1][2] += a[1][2]; mat[1][3] += a[1][3]; mat[1][4] += a[1][4]; mat[1][5] += a[1][5]; + mat[2][0] += a[2][0]; mat[2][1] += a[2][1]; mat[2][2] += a[2][2]; mat[2][3] += a[2][3]; mat[2][4] += a[2][4]; mat[2][5] += a[2][5]; + mat[3][0] += a[3][0]; mat[3][1] += a[3][1]; mat[3][2] += a[3][2]; mat[3][3] += a[3][3]; mat[3][4] += a[3][4]; mat[3][5] += a[3][5]; + mat[4][0] += a[4][0]; mat[4][1] += a[4][1]; mat[4][2] += a[4][2]; mat[4][3] += a[4][3]; mat[4][4] += a[4][4]; mat[4][5] += a[4][5]; + mat[5][0] += a[5][0]; mat[5][1] += a[5][1]; mat[5][2] += a[5][2]; mat[5][3] += a[5][3]; mat[5][4] += a[5][4]; mat[5][5] += a[5][5]; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator-=( const idMat6 &a ) { + mat[0][0] -= a[0][0]; mat[0][1] -= a[0][1]; mat[0][2] -= a[0][2]; mat[0][3] -= a[0][3]; mat[0][4] -= a[0][4]; mat[0][5] -= a[0][5]; + mat[1][0] -= a[1][0]; mat[1][1] -= a[1][1]; mat[1][2] -= a[1][2]; mat[1][3] -= a[1][3]; mat[1][4] -= a[1][4]; mat[1][5] -= a[1][5]; + mat[2][0] -= a[2][0]; mat[2][1] -= a[2][1]; mat[2][2] -= a[2][2]; mat[2][3] -= a[2][3]; mat[2][4] -= a[2][4]; mat[2][5] -= a[2][5]; + mat[3][0] -= a[3][0]; mat[3][1] -= a[3][1]; mat[3][2] -= a[3][2]; mat[3][3] -= a[3][3]; mat[3][4] -= a[3][4]; mat[3][5] -= a[3][5]; + mat[4][0] -= a[4][0]; mat[4][1] -= a[4][1]; mat[4][2] -= a[4][2]; mat[4][3] -= a[4][3]; mat[4][4] -= a[4][4]; mat[4][5] -= a[4][5]; + mat[5][0] -= a[5][0]; mat[5][1] -= a[5][1]; mat[5][2] -= a[5][2]; mat[5][3] -= a[5][3]; mat[5][4] -= a[5][4]; mat[5][5] -= a[5][5]; + return *this; +} + +ID_INLINE idVec6 operator*( const idVec6 &vec, const idMat6 &mat ) { + return mat * vec; +} + +ID_INLINE idMat6 operator*( const float a, idMat6 const &mat ) { + return mat * a; +} + +ID_INLINE idVec6 &operator*=( idVec6 &vec, const idMat6 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat6::Compare( const idMat6 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 6*6; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat6::Compare( const idMat6 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 6*6; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat6::operator==( const idMat6 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat6::operator!=( const idMat6 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat6::Zero() { + memset( mat, 0, sizeof( idMat6 ) ); +} + +ID_INLINE void idMat6::Identity() { + *this = mat6_identity; +} + +ID_INLINE bool idMat6::IsIdentity( const float epsilon ) const { + return Compare( mat6_identity, epsilon ); +} + +ID_INLINE bool idMat6::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 6; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat6::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 6; i++ ) { + for ( int j = 0; j < 6; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE idMat3 idMat6::SubMat3( int n ) const { + assert( n >= 0 && n < 4 ); + int b0 = ((n & 2) >> 1) * 3; + int b1 = (n & 1) * 3; + return idMat3( + mat[b0 + 0][b1 + 0], mat[b0 + 0][b1 + 1], mat[b0 + 0][b1 + 2], + mat[b0 + 1][b1 + 0], mat[b0 + 1][b1 + 1], mat[b0 + 1][b1 + 2], + mat[b0 + 2][b1 + 0], mat[b0 + 2][b1 + 1], mat[b0 + 2][b1 + 2] ); +} + +ID_INLINE float idMat6::Trace() const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] + mat[4][4] + mat[5][5] ); +} + +ID_INLINE idMat6 idMat6::Inverse() const { + idMat6 invMat; + + invMat = *this; + verify( invMat.InverseSelf() ); + return invMat; +} + +ID_INLINE idMat6 idMat6::InverseFast() const { + idMat6 invMat; + + invMat = *this; + verify( invMat.InverseFastSelf() ); + return invMat; +} + +ID_INLINE int idMat6::GetDimension() const { + return 36; +} + +ID_INLINE const float *idMat6::ToFloatPtr() const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat6::ToFloatPtr() { + return mat[0].ToFloatPtr(); +} + +#endif /* !__MATH_MATRIX_H__ */ diff --git a/neo/idlib/math/Ode.cpp b/neo/idlib/math/Ode.cpp new file mode 100644 index 00000000..ffc24f2e --- /dev/null +++ b/neo/idlib/math/Ode.cpp @@ -0,0 +1,355 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +//=============================================================== +// +// idODE_Euler +// +//=============================================================== + +/* +============= +idODE_Euler::idODE_Euler +============= +*/ +idODE_Euler::idODE_Euler( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derivatives = new (TAG_MATH) float[dim]; + derive = dr; + userData = ud; +} + +/* +============= +idODE_Euler::~idODE_Euler +============= +*/ +idODE_Euler::~idODE_Euler() { + delete[] derivatives; +} + +/* +============= +idODE_Euler::Evaluate +============= +*/ +float idODE_Euler::Evaluate( const float *state, float *newState, float t0, float t1 ) { + float delta; + int i; + + derive( t0, userData, state, derivatives ); + delta = t1 - t0; + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + delta * derivatives[i]; + } + return delta; +} + +//=============================================================== +// +// idODE_Midpoint +// +//=============================================================== + +/* +============= +idODE_Midpoint::idODE_Midpoint +============= +*/ +idODE_Midpoint::idODE_Midpoint( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + tmpState = new (TAG_MATH) float[dim]; + derivatives = new (TAG_MATH) float[dim]; + derive = dr; + userData = ud; +} + +/* +============= +idODE_Midpoint::~idODE_Midpoint +============= +*/ +idODE_Midpoint::~idODE_Midpoint() { + delete tmpState; + delete derivatives; +} + +/* +============= +idODE_Midpoint::~Evaluate +============= +*/ +float idODE_Midpoint::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta; + int i; + + delta = t1 - t0; + halfDelta = delta * 0.5; + // first step + derive( t0, userData, state, derivatives ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * derivatives[i]; + } + // second step + derive( t0 + halfDelta, userData, tmpState, derivatives ); + + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + delta * derivatives[i]; + } + return delta; +} + +//=============================================================== +// +// idODE_RK4 +// +//=============================================================== + +/* +============= +idODE_RK4::idODE_RK4 +============= +*/ +idODE_RK4::idODE_RK4( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derive = dr; + userData = ud; + tmpState = new (TAG_MATH) float[dim]; + d1 = new (TAG_MATH) float[dim]; + d2 = new (TAG_MATH) float[dim]; + d3 = new (TAG_MATH) float[dim]; + d4 = new (TAG_MATH) float[dim]; +} + +/* +============= +idODE_RK4::~idODE_RK4 +============= +*/ +idODE_RK4::~idODE_RK4() { + delete tmpState; + delete d1; + delete d2; + delete d3; + delete d4; +} + +/* +============= +idODE_RK4::Evaluate +============= +*/ +float idODE_RK4::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta, sixthDelta; + int i; + + delta = t1 - t0; + halfDelta = delta * 0.5; + // first step + derive( t0, userData, state, d1 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d1[i]; + } + // second step + derive( t0 + halfDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d2[i]; + } + // third step + derive( t0 + halfDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + delta * d3[i]; + } + // fourth step + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = delta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + return delta; +} + +//=============================================================== +// +// idODE_RK4Adaptive +// +//=============================================================== + +/* +============= +idODE_RK4Adaptive::idODE_RK4Adaptive +============= +*/ +idODE_RK4Adaptive::idODE_RK4Adaptive( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derive = dr; + userData = ud; + maxError = 0.01f; + tmpState = new (TAG_MATH) float[dim]; + d1 = new (TAG_MATH) float[dim]; + d1half = new (TAG_MATH) float [dim]; + d2 = new (TAG_MATH) float[dim]; + d3 = new (TAG_MATH) float[dim]; + d4 = new (TAG_MATH) float[dim]; +} + +/* +============= +idODE_RK4Adaptive::~idODE_RK4Adaptive +============= +*/ +idODE_RK4Adaptive::~idODE_RK4Adaptive() { + delete tmpState; + delete d1; + delete d1half; + delete d2; + delete d3; + delete d4; +} + +/* +============= +idODE_RK4Adaptive::SetMaxError +============= +*/ +void idODE_RK4Adaptive::SetMaxError( const float err ) { + if ( err > 0.0f ) { + maxError = err; + } +} + +/* +============= +idODE_RK4Adaptive::Evaluate +============= +*/ +float idODE_RK4Adaptive::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta, fourthDelta, sixthDelta; + double error, max; + int i, n; + + delta = t1 - t0; + + for ( n = 0; n < 4; n++ ) { + + halfDelta = delta * 0.5; + fourthDelta = delta * 0.25; + + // first step of first half delta + derive( t0, userData, state, d1 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d1[i]; + } + // second step of first half delta + derive( t0 + fourthDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d2[i]; + } + // third step of first half delta + derive( t0 + fourthDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d3[i]; + } + // fourth step of first half delta + derive( t0 + halfDelta, userData, tmpState, d4 ); + + sixthDelta = halfDelta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // first step of second half delta + derive( t0 + halfDelta, userData, tmpState, d1half ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d1half[i]; + } + // second step of second half delta + derive( t0 + halfDelta + fourthDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d2[i]; + } + // third step of second half delta + derive( t0 + halfDelta + fourthDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d3[i]; + } + // fourth step of second half delta + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = halfDelta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // first step of full delta + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d1[i]; + } + // second step of full delta + derive( t0 + halfDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d2[i]; + } + // third step of full delta + derive( t0 + halfDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + delta * d3[i]; + } + // fourth step of full delta + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = delta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // get max estimated error + max = 0.0; + for ( i = 0; i < dimension; i++ ) { + error = idMath::Fabs( (newState[i] - tmpState[i]) / (delta * d1[i] + 1e-10) ); + if ( error > max ) { + max = error; + } + } + error = max / maxError; + + if ( error <= 1.0f ) { + return delta * 4.0; + } + if ( delta <= 1e-7 ) { + return delta; + } + delta *= 0.25; + } + return delta; +} + diff --git a/neo/idlib/math/Ode.h b/neo/idlib/math/Ode.h new file mode 100644 index 00000000..e9941743 --- /dev/null +++ b/neo/idlib/math/Ode.h @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_ODE_H__ +#define __MATH_ODE_H__ + +/* +=============================================================================== + + Numerical solvers for ordinary differential equations. + +=============================================================================== +*/ + + +//=============================================================== +// +// idODE +// +//=============================================================== + +typedef void (*deriveFunction_t)( const float t, const void *userData, const float *state, float *derivatives ); + +class idODE { + +public: + virtual ~idODE() {} + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ) = 0; + +protected: + int dimension; // dimension in floats allocated for + deriveFunction_t derive; // derive function + const void * userData; // client data +}; + +//=============================================================== +// +// idODE_Euler +// +//=============================================================== + +class idODE_Euler : public idODE { + +public: + idODE_Euler( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_Euler(); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * derivatives; // space to store derivatives +}; + +//=============================================================== +// +// idODE_Midpoint +// +//=============================================================== + +class idODE_Midpoint : public idODE { + +public: + idODE_Midpoint( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_Midpoint(); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * tmpState; + float * derivatives; // space to store derivatives +}; + +//=============================================================== +// +// idODE_RK4 +// +//=============================================================== + +class idODE_RK4 : public idODE { + +public: + idODE_RK4( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_RK4(); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * tmpState; + float * d1; // derivatives + float * d2; + float * d3; + float * d4; +}; + +//=============================================================== +// +// idODE_RK4Adaptive +// +//=============================================================== + +class idODE_RK4Adaptive : public idODE { + +public: + idODE_RK4Adaptive( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_RK4Adaptive(); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + void SetMaxError( const float err ); + +protected: + float maxError; // maximum allowed error + float * tmpState; + float * d1; // derivatives + float * d1half; + float * d2; + float * d3; + float * d4; +}; + +#endif /* !__MATH_ODE_H__ */ diff --git a/neo/idlib/math/Plane.cpp b/neo/idlib/math/Plane.cpp new file mode 100644 index 00000000..82222a49 --- /dev/null +++ b/neo/idlib/math/Plane.cpp @@ -0,0 +1,154 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +idPlane plane_origin( 0.0f, 0.0f, 0.0f, 0.0f ); + +/* +================ +idPlane::Type +================ +*/ +int idPlane::Type() const { + if ( Normal()[0] == 0.0f ) { + if ( Normal()[1] == 0.0f ) { + return Normal()[2] > 0.0f ? PLANETYPE_Z : PLANETYPE_NEGZ; + } + else if ( Normal()[2] == 0.0f ) { + return Normal()[1] > 0.0f ? PLANETYPE_Y : PLANETYPE_NEGY; + } + else { + return PLANETYPE_ZEROX; + } + } + else if ( Normal()[1] == 0.0f ) { + if ( Normal()[2] == 0.0f ) { + return Normal()[0] > 0.0f ? PLANETYPE_X : PLANETYPE_NEGX; + } + else { + return PLANETYPE_ZEROY; + } + } + else if ( Normal()[2] == 0.0f ) { + return PLANETYPE_ZEROZ; + } + else { + return PLANETYPE_NONAXIAL; + } +} + +/* +================ +idPlane::HeightFit +================ +*/ +bool idPlane::HeightFit( const idVec3 *points, const int numPoints ) { + int i; + float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f; + float sumYY = 0.0f, sumYZ = 0.0f; + idVec3 sum, average, dir; + + if ( numPoints == 1 ) { + a = 0.0f; + b = 0.0f; + c = 1.0f; + d = -points[0].z; + return true; + } + if ( numPoints == 2 ) { + dir = points[1] - points[0]; + Normal() = dir.Cross( idVec3( 0, 0, 1 ) ).Cross( dir ); + Normalize(); + d = -( Normal() * points[0] ); + return true; + } + + sum.Zero(); + for ( i = 0; i < numPoints; i++) { + sum += points[i]; + } + average = sum / numPoints; + + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - average; + sumXX += dir.x * dir.x; + sumXY += dir.x * dir.y; + sumXZ += dir.x * dir.z; + sumYY += dir.y * dir.y; + sumYZ += dir.y * dir.z; + } + + idMat2 m( sumXX, sumXY, sumXY, sumYY ); + if ( !m.InverseSelf() ) { + return false; + } + + a = - sumXZ * m[0][0] - sumYZ * m[0][1]; + b = - sumXZ * m[1][0] - sumYZ * m[1][1]; + c = 1.0f; + Normalize(); + d = -( a * average.x + b * average.y + c * average.z ); + return true; +} + +/* +================ +idPlane::PlaneIntersection +================ +*/ +bool idPlane::PlaneIntersection( const idPlane &plane, idVec3 &start, idVec3 &dir ) const { + double n00, n01, n11, det, invDet, f0, f1; + + n00 = Normal().LengthSqr(); + n01 = Normal() * plane.Normal(); + n11 = plane.Normal().LengthSqr(); + det = n00 * n11 - n01 * n01; + + if ( idMath::Fabs(det) < 1e-6f ) { + return false; + } + + invDet = 1.0f / det; + f0 = ( n01 * plane.d - n11 * d ) * invDet; + f1 = ( n01 * d - n00 * plane.d ) * invDet; + + dir = Normal().Cross( plane.Normal() ); + start = f0 * Normal() + f1 * plane.Normal(); + return true; +} + +/* +============= +idPlane::ToString +============= +*/ +const char *idPlane::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/Plane.h b/neo/idlib/math/Plane.h new file mode 100644 index 00000000..4baf8d23 --- /dev/null +++ b/neo/idlib/math/Plane.h @@ -0,0 +1,401 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_PLANE_H__ +#define __MATH_PLANE_H__ + +/* +=============================================================================== + + 3D plane with equation: a * x + b * y + c * z + d = 0 + +=============================================================================== +*/ + + +class idVec3; +class idMat3; + +#define ON_EPSILON 0.1f +#define DEGENERATE_DIST_EPSILON 1e-4f + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +// plane sides +#define PLANESIDE_FRONT 0 +#define PLANESIDE_BACK 1 +#define PLANESIDE_ON 2 +#define PLANESIDE_CROSS 3 + +// plane types +#define PLANETYPE_X 0 +#define PLANETYPE_Y 1 +#define PLANETYPE_Z 2 +#define PLANETYPE_NEGX 3 +#define PLANETYPE_NEGY 4 +#define PLANETYPE_NEGZ 5 +#define PLANETYPE_TRUEAXIAL 6 // all types < 6 are true axial planes +#define PLANETYPE_ZEROX 6 +#define PLANETYPE_ZEROY 7 +#define PLANETYPE_ZEROZ 8 +#define PLANETYPE_NONAXIAL 9 + +class idPlane { +public: + idPlane(); + explicit idPlane( float a, float b, float c, float d ); + explicit idPlane( const idVec3 &normal, const float dist ); + explicit idPlane( const idVec3 & v0, const idVec3 & v1, const idVec3 & v2, bool fixDegenerate = false ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idPlane operator-() const; // flips plane + idPlane & operator=( const idVec3 &v ); // sets normal and sets idPlane::d to zero + idPlane operator+( const idPlane &p ) const; // add plane equations + idPlane operator-( const idPlane &p ) const; // subtract plane equations + idPlane operator*( const float s ) const; // scale plane + idPlane & operator*=( const idMat3 &m ); // Normal() *= m + + bool Compare( const idPlane &p ) const; // exact compare, no epsilon + bool Compare( const idPlane &p, const float epsilon ) const; // compare with epsilon + bool Compare( const idPlane &p, const float normalEps, const float distEps ) const; // compare with epsilon + bool operator==( const idPlane &p ) const; // exact compare, no epsilon + bool operator!=( const idPlane &p ) const; // exact compare, no epsilon + + void Zero(); // zero plane + void SetNormal( const idVec3 &normal ); // sets the normal + const idVec3 & Normal() const; // reference to const normal + idVec3 & Normal(); // reference to normal + float Normalize( bool fixDegenerate = true ); // only normalizes the plane normal, does not adjust d + bool FixDegenerateNormal(); // fix degenerate normal + bool FixDegeneracies( float distEpsilon ); // fix degenerate normal and dist + float Dist() const; // returns: -d + void SetDist( const float dist ); // sets: d = -dist + int Type() const; // returns plane type + + bool FromPoints( const idVec3 &p1, const idVec3 &p2, const idVec3 &p3, bool fixDegenerate = true ); + bool FromVecs( const idVec3 &dir1, const idVec3 &dir2, const idVec3 &p, bool fixDegenerate = true ); + void FitThroughPoint( const idVec3 &p ); // assumes normal is valid + bool HeightFit( const idVec3 *points, const int numPoints ); + idPlane Translate( const idVec3 &translation ) const; + idPlane & TranslateSelf( const idVec3 &translation ); + idPlane Rotate( const idVec3 &origin, const idMat3 &axis ) const; + idPlane & RotateSelf( const idVec3 &origin, const idMat3 &axis ); + + float Distance( const idVec3 &v ) const; + int Side( const idVec3 &v, const float epsilon = 0.0f ) const; + + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const; + bool PlaneIntersection( const idPlane &plane, idVec3 &start, idVec3 &dir ) const; + + int GetDimension() const; + + const idVec4 & ToVec4() const; + idVec4 & ToVec4(); + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + float a; + float b; + float c; + float d; +}; + +extern idPlane plane_origin; +#define plane_zero plane_origin + +ID_INLINE idPlane::idPlane() { +} + +ID_INLINE idPlane::idPlane( float a, float b, float c, float d ) { + this->a = a; + this->b = b; + this->c = c; + this->d = d; +} + +ID_INLINE idPlane::idPlane( const idVec3 &normal, const float dist ) { + this->a = normal.x; + this->b = normal.y; + this->c = normal.z; + this->d = -dist; +} + +ID_INLINE idPlane::idPlane( const idVec3 & v0, const idVec3 & v1, const idVec3 & v2, bool fixDegenerate ) { + FromPoints( v0, v1, v2, fixDegenerate ); +} + +ID_INLINE float idPlane::operator[]( int index ) const { + return ( &a )[ index ]; +} + +ID_INLINE float& idPlane::operator[]( int index ) { + return ( &a )[ index ]; +} + +ID_INLINE idPlane idPlane::operator-() const { + return idPlane( -a, -b, -c, -d ); +} + +ID_INLINE idPlane &idPlane::operator=( const idVec3 &v ) { + a = v.x; + b = v.y; + c = v.z; + d = 0; + return *this; +} + +ID_INLINE idPlane idPlane::operator+( const idPlane &p ) const { + return idPlane( a + p.a, b + p.b, c + p.c, d + p.d ); +} + +ID_INLINE idPlane idPlane::operator-( const idPlane &p ) const { + return idPlane( a - p.a, b - p.b, c - p.c, d - p.d ); +} + +ID_INLINE idPlane idPlane::operator*( const float s ) const { + return idPlane( a * s, b * s, c * s, d * s ); +} + +ID_INLINE idPlane &idPlane::operator*=( const idMat3 &m ) { + Normal() *= m; + return *this; +} + +ID_INLINE bool idPlane::Compare( const idPlane &p ) const { + return ( a == p.a && b == p.b && c == p.c && d == p.d ); +} + +ID_INLINE bool idPlane::Compare( const idPlane &p, const float epsilon ) const { + if ( idMath::Fabs( a - p.a ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( b - p.b ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( c - p.c ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( d - p.d ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idPlane::Compare( const idPlane &p, const float normalEps, const float distEps ) const { + if ( idMath::Fabs( d - p.d ) > distEps ) { + return false; + } + if ( !Normal().Compare( p.Normal(), normalEps ) ) { + return false; + } + return true; +} + +ID_INLINE bool idPlane::operator==( const idPlane &p ) const { + return Compare( p ); +} + +ID_INLINE bool idPlane::operator!=( const idPlane &p ) const { + return !Compare( p ); +} + +ID_INLINE void idPlane::Zero() { + a = b = c = d = 0.0f; +} + +ID_INLINE void idPlane::SetNormal( const idVec3 &normal ) { + a = normal.x; + b = normal.y; + c = normal.z; +} + +ID_INLINE const idVec3 &idPlane::Normal() const { + return *reinterpret_cast(&a); +} + +ID_INLINE idVec3 &idPlane::Normal() { + return *reinterpret_cast(&a); +} + +ID_INLINE float idPlane::Normalize( bool fixDegenerate ) { + float length = reinterpret_cast(&a)->Normalize(); + + if ( fixDegenerate ) { + FixDegenerateNormal(); + } + return length; +} + +ID_INLINE bool idPlane::FixDegenerateNormal() { + return Normal().FixDegenerateNormal(); +} + +ID_INLINE bool idPlane::FixDegeneracies( float distEpsilon ) { + bool fixedNormal = FixDegenerateNormal(); + // only fix dist if the normal was degenerate + if ( fixedNormal ) { + if ( idMath::Fabs( d - idMath::Rint( d ) ) < distEpsilon ) { + d = idMath::Rint( d ); + } + } + return fixedNormal; +} + +ID_INLINE float idPlane::Dist() const { + return -d; +} + +ID_INLINE void idPlane::SetDist( const float dist ) { + d = -dist; +} + +ID_INLINE bool idPlane::FromPoints( const idVec3 &p1, const idVec3 &p2, const idVec3 &p3, bool fixDegenerate ) { + Normal() = (p1 - p2).Cross( p3 - p2 ); + if ( Normalize( fixDegenerate ) == 0.0f ) { + return false; + } + d = -( Normal() * p2 ); + return true; +} + +ID_INLINE bool idPlane::FromVecs( const idVec3 &dir1, const idVec3 &dir2, const idVec3 &p, bool fixDegenerate ) { + Normal() = dir1.Cross( dir2 ); + if ( Normalize( fixDegenerate ) == 0.0f ) { + return false; + } + d = -( Normal() * p ); + return true; +} + +ID_INLINE void idPlane::FitThroughPoint( const idVec3 &p ) { + d = -( Normal() * p ); +} + +ID_INLINE idPlane idPlane::Translate( const idVec3 &translation ) const { + return idPlane( a, b, c, d - translation * Normal() ); +} + +ID_INLINE idPlane &idPlane::TranslateSelf( const idVec3 &translation ) { + d -= translation * Normal(); + return *this; +} + +ID_INLINE idPlane idPlane::Rotate( const idVec3 &origin, const idMat3 &axis ) const { + idPlane p; + p.Normal() = Normal() * axis; + p.d = d + origin * Normal() - origin * p.Normal(); + return p; +} + +ID_INLINE idPlane &idPlane::RotateSelf( const idVec3 &origin, const idMat3 &axis ) { + d += origin * Normal(); + Normal() *= axis; + d -= origin * Normal(); + return *this; +} + +ID_INLINE float idPlane::Distance( const idVec3 &v ) const { + return a * v.x + b * v.y + c * v.z + d; +} + +ID_INLINE int idPlane::Side( const idVec3 &v, const float epsilon ) const { + float dist = Distance( v ); + if ( dist > epsilon ) { + return PLANESIDE_FRONT; + } + else if ( dist < -epsilon ) { + return PLANESIDE_BACK; + } + else { + return PLANESIDE_ON; + } +} + +ID_INLINE bool idPlane::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float d1, d2, fraction; + + d1 = Normal() * start + d; + d2 = Normal() * end + d; + if ( d1 == d2 ) { + return false; + } + if ( d1 > 0.0f && d2 > 0.0f ) { + return false; + } + if ( d1 < 0.0f && d2 < 0.0f ) { + return false; + } + fraction = ( d1 / ( d1 - d2 ) ); + return ( fraction >= 0.0f && fraction <= 1.0f ); +} + +ID_INLINE bool idPlane::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const { + float d1, d2; + + d1 = Normal() * start + d; + d2 = Normal() * dir; + if ( d2 == 0.0f ) { + return false; + } + scale = -( d1 / d2 ); + return true; +} + +ID_INLINE int idPlane::GetDimension() const { + return 4; +} + +ID_INLINE const idVec4 &idPlane::ToVec4() const { + return *reinterpret_cast(&a); +} + +ID_INLINE idVec4 &idPlane::ToVec4() { + return *reinterpret_cast(&a); +} + +ID_INLINE const float *idPlane::ToFloatPtr() const { + return reinterpret_cast(&a); +} + +ID_INLINE float *idPlane::ToFloatPtr() { + return reinterpret_cast(&a); +} + +#endif /* !__MATH_PLANE_H__ */ diff --git a/neo/idlib/math/Pluecker.cpp b/neo/idlib/math/Pluecker.cpp new file mode 100644 index 00000000..22d1ce3d --- /dev/null +++ b/neo/idlib/math/Pluecker.cpp @@ -0,0 +1,86 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +idPluecker pluecker_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); + +/* +================ +idPluecker::FromPlanes + + pluecker coordinate for the intersection of two planes +================ +*/ +bool idPluecker::FromPlanes( const idPlane &p1, const idPlane &p2 ) { + + p[0] = -( p1[2] * -p2[3] - p2[2] * -p1[3] ); + p[1] = -( p2[1] * -p1[3] - p1[1] * -p2[3] ); + p[2] = p1[1] * p2[2] - p2[1] * p1[2]; + + p[3] = -( p1[0] * -p2[3] - p2[0] * -p1[3] ); + p[4] = p1[0] * p2[1] - p2[0] * p1[1]; + p[5] = p1[0] * p2[2] - p2[0] * p1[2]; + + return ( p[2] != 0.0f || p[5] != 0.0f || p[4] != 0.0f ); +} + +/* +================ +idPluecker::Distance3DSqr + + calculates square of shortest distance between the two + 3D lines represented by their pluecker coordinates +================ +*/ +float idPluecker::Distance3DSqr( const idPluecker &a ) const { + float d, s; + idVec3 dir; + + dir[0] = -a.p[5] * p[4] - a.p[4] * -p[5]; + dir[1] = a.p[4] * p[2] - a.p[2] * p[4]; + dir[2] = a.p[2] * -p[5] - -a.p[5] * p[2]; + if ( dir[0] == 0.0f && dir[1] == 0.0f && dir[2] == 0.0f ) { + return -1.0f; // FIXME: implement for parallel lines + } + d = a.p[4] * ( p[2]*dir[1] - -p[5]*dir[0]) + + a.p[5] * ( p[2]*dir[2] - p[4]*dir[0]) + + a.p[2] * (-p[5]*dir[2] - p[4]*dir[1]); + s = PermutedInnerProduct( a ) / d; + return ( dir * dir ) * ( s * s ); +} + +/* +============= +idPluecker::ToString +============= +*/ +const char *idPluecker::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/Pluecker.h b/neo/idlib/math/Pluecker.h new file mode 100644 index 00000000..b2d06053 --- /dev/null +++ b/neo/idlib/math/Pluecker.h @@ -0,0 +1,368 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_PLUECKER_H__ +#define __MATH_PLUECKER_H__ + +/* +=============================================================================== + + Pluecker coordinate + +=============================================================================== +*/ + +class idPluecker { +public: + idPluecker(); + explicit idPluecker( const float *a ); + explicit idPluecker( const idVec3 &start, const idVec3 &end ); + explicit idPluecker( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idPluecker operator-() const; // flips the direction + idPluecker operator*( const float a ) const; + idPluecker operator/( const float a ) const; + float operator*( const idPluecker &a ) const; // permuted inner product + idPluecker operator-( const idPluecker &a ) const; + idPluecker operator+( const idPluecker &a ) const; + idPluecker & operator*=( const float a ); + idPluecker & operator/=( const float a ); + idPluecker & operator+=( const idPluecker &a ); + idPluecker & operator-=( const idPluecker &a ); + + bool Compare( const idPluecker &a ) const; // exact compare, no epsilon + bool Compare( const idPluecker &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idPluecker &a ) const; // exact compare, no epsilon + bool operator!=( const idPluecker &a ) const; // exact compare, no epsilon + + void Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + void Zero(); + + void FromLine( const idVec3 &start, const idVec3 &end ); // pluecker from line + void FromRay( const idVec3 &start, const idVec3 &dir ); // pluecker from ray + bool FromPlanes( const idPlane &p1, const idPlane &p2 ); // pluecker from intersection of planes + bool ToLine( idVec3 &start, idVec3 &end ) const; // pluecker to line + bool ToRay( idVec3 &start, idVec3 &dir ) const; // pluecker to ray + void ToDir( idVec3 &dir ) const; // pluecker to direction + float PermutedInnerProduct( const idPluecker &a ) const; // pluecker permuted inner product + float Distance3DSqr( const idPluecker &a ) const; // pluecker line distance + + float Length() const; // pluecker length + float LengthSqr() const; // pluecker squared length + idPluecker Normalize() const; // pluecker normalize + float NormalizeSelf(); // pluecker normalize + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + float p[6]; +}; + +extern idPluecker pluecker_origin; +#define pluecker_zero pluecker_origin + +ID_INLINE idPluecker::idPluecker() { +} + +ID_INLINE idPluecker::idPluecker( const float *a ) { + memcpy( p, a, 6 * sizeof( float ) ); +} + +ID_INLINE idPluecker::idPluecker( const idVec3 &start, const idVec3 &end ) { + FromLine( start, end ); +} + +ID_INLINE idPluecker::idPluecker( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE idPluecker idPluecker::operator-() const { + return idPluecker( -p[0], -p[1], -p[2], -p[3], -p[4], -p[5] ); +} + +ID_INLINE float idPluecker::operator[]( const int index ) const { + return p[index]; +} + +ID_INLINE float &idPluecker::operator[]( const int index ) { + return p[index]; +} + +ID_INLINE idPluecker idPluecker::operator*( const float a ) const { + return idPluecker( p[0]*a, p[1]*a, p[2]*a, p[3]*a, p[4]*a, p[5]*a ); +} + +ID_INLINE float idPluecker::operator*( const idPluecker &a ) const { + return p[0] * a.p[4] + p[1] * a.p[5] + p[2] * a.p[3] + p[4] * a.p[0] + p[5] * a.p[1] + p[3] * a.p[2]; +} + +ID_INLINE idPluecker idPluecker::operator/( const float a ) const { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + return idPluecker( p[0]*inva, p[1]*inva, p[2]*inva, p[3]*inva, p[4]*inva, p[5]*inva ); +} + +ID_INLINE idPluecker idPluecker::operator+( const idPluecker &a ) const { + return idPluecker( p[0] + a[0], p[1] + a[1], p[2] + a[2], p[3] + a[3], p[4] + a[4], p[5] + a[5] ); +} + +ID_INLINE idPluecker idPluecker::operator-( const idPluecker &a ) const { + return idPluecker( p[0] - a[0], p[1] - a[1], p[2] - a[2], p[3] - a[3], p[4] - a[4], p[5] - a[5] ); +} + +ID_INLINE idPluecker &idPluecker::operator*=( const float a ) { + p[0] *= a; + p[1] *= a; + p[2] *= a; + p[3] *= a; + p[4] *= a; + p[5] *= a; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator/=( const float a ) { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + p[0] *= inva; + p[1] *= inva; + p[2] *= inva; + p[3] *= inva; + p[4] *= inva; + p[5] *= inva; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator+=( const idPluecker &a ) { + p[0] += a[0]; + p[1] += a[1]; + p[2] += a[2]; + p[3] += a[3]; + p[4] += a[4]; + p[5] += a[5]; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator-=( const idPluecker &a ) { + p[0] -= a[0]; + p[1] -= a[1]; + p[2] -= a[2]; + p[3] -= a[3]; + p[4] -= a[4]; + p[5] -= a[5]; + return *this; +} + +ID_INLINE bool idPluecker::Compare( const idPluecker &a ) const { + return ( ( p[0] == a[0] ) && ( p[1] == a[1] ) && ( p[2] == a[2] ) && + ( p[3] == a[3] ) && ( p[4] == a[4] ) && ( p[5] == a[5] ) ); +} + +ID_INLINE bool idPluecker::Compare( const idPluecker &a, const float epsilon ) const { + if ( idMath::Fabs( p[0] - a[0] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[1] - a[1] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[2] - a[2] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[3] - a[3] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[4] - a[4] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[5] - a[5] ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idPluecker::operator==( const idPluecker &a ) const { + return Compare( a ); +} + +ID_INLINE bool idPluecker::operator!=( const idPluecker &a ) const { + return !Compare( a ); +} + +ID_INLINE void idPluecker::Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE void idPluecker::Zero() { + p[0] = p[1] = p[2] = p[3] = p[4] = p[5] = 0.0f; +} + +ID_INLINE void idPluecker::FromLine( const idVec3 &start, const idVec3 &end ) { + p[0] = start[0] * end[1] - end[0] * start[1]; + p[1] = start[0] * end[2] - end[0] * start[2]; + p[2] = start[0] - end[0]; + p[3] = start[1] * end[2] - end[1] * start[2]; + p[4] = start[2] - end[2]; + p[5] = end[1] - start[1]; +} + +ID_INLINE void idPluecker::FromRay( const idVec3 &start, const idVec3 &dir ) { + p[0] = start[0] * dir[1] - dir[0] * start[1]; + p[1] = start[0] * dir[2] - dir[0] * start[2]; + p[2] = -dir[0]; + p[3] = start[1] * dir[2] - dir[1] * start[2]; + p[4] = -dir[2]; + p[5] = dir[1]; +} + +ID_INLINE bool idPluecker::ToLine( idVec3 &start, idVec3 &end ) const { + idVec3 dir1, dir2; + float d; + + dir1[0] = p[3]; + dir1[1] = -p[1]; + dir1[2] = p[0]; + + dir2[0] = -p[2]; + dir2[1] = p[5]; + dir2[2] = -p[4]; + + d = dir2 * dir2; + if ( d == 0.0f ) { + return false; // pluecker coordinate does not represent a line + } + + start = dir2.Cross(dir1) * (1.0f / d); + end = start + dir2; + return true; +} + +ID_INLINE bool idPluecker::ToRay( idVec3 &start, idVec3 &dir ) const { + idVec3 dir1; + float d; + + dir1[0] = p[3]; + dir1[1] = -p[1]; + dir1[2] = p[0]; + + dir[0] = -p[2]; + dir[1] = p[5]; + dir[2] = -p[4]; + + d = dir * dir; + if ( d == 0.0f ) { + return false; // pluecker coordinate does not represent a line + } + + start = dir.Cross(dir1) * (1.0f / d); + return true; +} + +ID_INLINE void idPluecker::ToDir( idVec3 &dir ) const { + dir[0] = -p[2]; + dir[1] = p[5]; + dir[2] = -p[4]; +} + +ID_INLINE float idPluecker::PermutedInnerProduct( const idPluecker &a ) const { + return p[0] * a.p[4] + p[1] * a.p[5] + p[2] * a.p[3] + p[4] * a.p[0] + p[5] * a.p[1] + p[3] * a.p[2]; +} + +ID_INLINE float idPluecker::Length() const { + return ( float )idMath::Sqrt( p[5] * p[5] + p[4] * p[4] + p[2] * p[2] ); +} + +ID_INLINE float idPluecker::LengthSqr() const { + return ( p[5] * p[5] + p[4] * p[4] + p[2] * p[2] ); +} + +ID_INLINE float idPluecker::NormalizeSelf() { + float l, d; + + l = LengthSqr(); + if ( l == 0.0f ) { + return l; // pluecker coordinate does not represent a line + } + d = idMath::InvSqrt( l ); + p[0] *= d; + p[1] *= d; + p[2] *= d; + p[3] *= d; + p[4] *= d; + p[5] *= d; + return d * l; +} + +ID_INLINE idPluecker idPluecker::Normalize() const { + float d; + + d = LengthSqr(); + if ( d == 0.0f ) { + return *this; // pluecker coordinate does not represent a line + } + d = idMath::InvSqrt( d ); + return idPluecker( p[0]*d, p[1]*d, p[2]*d, p[3]*d, p[4]*d, p[5]*d ); +} + +ID_INLINE int idPluecker::GetDimension() const { + return 6; +} + +ID_INLINE const float *idPluecker::ToFloatPtr() const { + return p; +} + +ID_INLINE float *idPluecker::ToFloatPtr() { + return p; +} + +#endif /* !__MATH_PLUECKER_H__ */ diff --git a/neo/idlib/math/Polynomial.cpp b/neo/idlib/math/Polynomial.cpp new file mode 100644 index 00000000..2df4f0ec --- /dev/null +++ b/neo/idlib/math/Polynomial.cpp @@ -0,0 +1,242 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +const float EPSILON = 1e-6f; + +/* +============= +idPolynomial::Laguer +============= +*/ +int idPolynomial::Laguer( const idComplex *coef, const int degree, idComplex &x ) const { + const int MT = 10, MAX_ITERATIONS = MT * 8; + static const float frac[] = { 0.0f, 0.5f, 0.25f, 0.75f, 0.13f, 0.38f, 0.62f, 0.88f, 1.0f }; + int i, j; + float abx, abp, abm, err; + idComplex dx, cx, b, d, f, g, s, gps, gms, g2; + + for ( i = 1; i <= MAX_ITERATIONS; i++ ) { + b = coef[degree]; + err = b.Abs(); + d.Zero(); + f.Zero(); + abx = x.Abs(); + for ( j = degree - 1; j >= 0; j-- ) { + f = x * f + d; + d = x * d + b; + b = x * b + coef[j]; + err = b.Abs() + abx * err; + } + if ( b.Abs() < err * EPSILON ) { + return i; + } + g = d / b; + g2 = g * g; + s = ( ( degree - 1 ) * ( degree * ( g2 - 2.0f * f / b ) - g2 ) ).Sqrt(); + gps = g + s; + gms = g - s; + abp = gps.Abs(); + abm = gms.Abs(); + if ( abp < abm ) { + gps = gms; + } + if ( Max( abp, abm ) > 0.0f ) { + dx = degree / gps; + } else { + dx = idMath::Exp( idMath::Log( 1.0f + abx ) ) * idComplex( idMath::Cos( i ), idMath::Sin( i ) ); + } + cx = x - dx; + if ( x == cx ) { + return i; + } + if ( i % MT == 0 ) { + x = cx; + } else { + x -= frac[i/MT] * dx; + } + } + return i; +} + +/* +============= +idPolynomial::GetRoots +============= +*/ +int idPolynomial::GetRoots( idComplex *roots ) const { + int i, j; + idComplex x, b, c, *coef; + + coef = (idComplex *) _alloca16( ( degree + 1 ) * sizeof( idComplex ) ); + for ( i = 0; i <= degree; i++ ) { + coef[i].Set( coefficient[i], 0.0f ); + } + + for ( i = degree - 1; i >= 0; i-- ) { + x.Zero(); + Laguer( coef, i + 1, x ); + if ( idMath::Fabs( x.i ) < 2.0f * EPSILON * idMath::Fabs( x.r ) ) { + x.i = 0.0f; + } + roots[i] = x; + b = coef[i+1]; + for ( j = i; j >= 0; j-- ) { + c = coef[j]; + coef[j] = b; + b = x * b + c; + } + } + + for ( i = 0; i <= degree; i++ ) { + coef[i].Set( coefficient[i], 0.0f ); + } + for ( i = 0; i < degree; i++ ) { + Laguer( coef, degree, roots[i] ); + } + + for ( i = 1; i < degree; i++ ) { + x = roots[i]; + for ( j = i - 1; j >= 0; j-- ) { + if ( roots[j].r <= x.r ) { + break; + } + roots[j+1] = roots[j]; + } + roots[j+1] = x; + } + + return degree; +} + +/* +============= +idPolynomial::GetRoots +============= +*/ +int idPolynomial::GetRoots( float *roots ) const { + int i, num; + idComplex *complexRoots; + + switch( degree ) { + case 0: return 0; + case 1: return GetRoots1( coefficient[1], coefficient[0], roots ); + case 2: return GetRoots2( coefficient[2], coefficient[1], coefficient[0], roots ); + case 3: return GetRoots3( coefficient[3], coefficient[2], coefficient[1], coefficient[0], roots ); + case 4: return GetRoots4( coefficient[4], coefficient[3], coefficient[2], coefficient[1], coefficient[0], roots ); + } + + // The Abel-Ruffini theorem states that there is no general solution + // in radicals to polynomial equations of degree five or higher. + // A polynomial equation can be solved by radicals if and only if + // its Galois group is a solvable group. + + complexRoots = (idComplex *) _alloca16( degree * sizeof( idComplex ) ); + + GetRoots( complexRoots ); + + for ( num = i = 0; i < degree; i++ ) { + if ( complexRoots[i].i == 0.0f ) { + roots[i] = complexRoots[i].r; + num++; + } + } + return num; +} + +/* +============= +idPolynomial::ToString +============= +*/ +const char *idPolynomial::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +idPolynomial::Test +============= +*/ +void idPolynomial::Test() { + int i, num; + float roots[4], value; + idComplex complexRoots[4], complexValue; + idPolynomial p; + + p = idPolynomial( -5.0f, 4.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( -5.0f, 4.0f, 3.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 1.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 5.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( -5.0f, 4.0f, 3.0f, 2.0f, 1.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 1.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( complexRoots ); + for ( i = 0; i < num; i++ ) { + complexValue = p.GetValue( complexRoots[i] ); + assert( idMath::Fabs( complexValue.r ) < 1e-4f && idMath::Fabs( complexValue.i ) < 1e-4f ); + } + + p = idPolynomial( 5.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( complexRoots ); + for ( i = 0; i < num; i++ ) { + complexValue = p.GetValue( complexRoots[i] ); + assert( idMath::Fabs( complexValue.r ) < 1e-4f && idMath::Fabs( complexValue.i ) < 1e-4f ); + } +} diff --git a/neo/idlib/math/Polynomial.h b/neo/idlib/math/Polynomial.h new file mode 100644 index 00000000..1c498c70 --- /dev/null +++ b/neo/idlib/math/Polynomial.h @@ -0,0 +1,629 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_POLYNOMIAL_H__ +#define __MATH_POLYNOMIAL_H__ + +/* +=============================================================================== + + Polynomial of arbitrary degree with real coefficients. + +=============================================================================== +*/ + + +class idPolynomial { +public: + idPolynomial(); + explicit idPolynomial( int d ); + explicit idPolynomial( float a, float b ); + explicit idPolynomial( float a, float b, float c ); + explicit idPolynomial( float a, float b, float c, float d ); + explicit idPolynomial( float a, float b, float c, float d, float e ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + idPolynomial operator-() const; + idPolynomial & operator=( const idPolynomial &p ); + + idPolynomial operator+( const idPolynomial &p ) const; + idPolynomial operator-( const idPolynomial &p ) const; + idPolynomial operator*( const float s ) const; + idPolynomial operator/( const float s ) const; + + idPolynomial & operator+=( const idPolynomial &p ); + idPolynomial & operator-=( const idPolynomial &p ); + idPolynomial & operator*=( const float s ); + idPolynomial & operator/=( const float s ); + + bool Compare( const idPolynomial &p ) const; // exact compare, no epsilon + bool Compare( const idPolynomial &p, const float epsilon ) const;// compare with epsilon + bool operator==( const idPolynomial &p ) const; // exact compare, no epsilon + bool operator!=( const idPolynomial &p ) const; // exact compare, no epsilon + + void Zero(); + void Zero( int d ); + + int GetDimension() const; // get the degree of the polynomial + int GetDegree() const; // get the degree of the polynomial + float GetValue( const float x ) const; // evaluate the polynomial with the given real value + idComplex GetValue( const idComplex &x ) const; // evaluate the polynomial with the given complex value + idPolynomial GetDerivative() const; // get the first derivative of the polynomial + idPolynomial GetAntiDerivative() const; // get the anti derivative of the polynomial + + int GetRoots( idComplex *roots ) const; // get all roots + int GetRoots( float *roots ) const; // get the real roots + + static int GetRoots1( float a, float b, float *roots ); + static int GetRoots2( float a, float b, float c, float *roots ); + static int GetRoots3( float a, float b, float c, float d, float *roots ); + static int GetRoots4( float a, float b, float c, float d, float e, float *roots ); + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + static void Test(); + +private: + int degree; + int allocated; + float * coefficient; + + void Resize( int d, bool keep ); + int Laguer( const idComplex *coef, const int degree, idComplex &r ) const; +}; + +ID_INLINE idPolynomial::idPolynomial() { + degree = -1; + allocated = 0; + coefficient = NULL; +} + +ID_INLINE idPolynomial::idPolynomial( int d ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( d, false ); +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 1, false ); + coefficient[0] = b; + coefficient[1] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 2, false ); + coefficient[0] = c; + coefficient[1] = b; + coefficient[2] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c, float d ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 3, false ); + coefficient[0] = d; + coefficient[1] = c; + coefficient[2] = b; + coefficient[3] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c, float d, float e ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 4, false ); + coefficient[0] = e; + coefficient[1] = d; + coefficient[2] = c; + coefficient[3] = b; + coefficient[4] = a; +} + +ID_INLINE float idPolynomial::operator[]( int index ) const { + assert( index >= 0 && index <= degree ); + return coefficient[ index ]; +} + +ID_INLINE float& idPolynomial::operator[]( int index ) { + assert( index >= 0 && index <= degree ); + return coefficient[ index ]; +} + +ID_INLINE idPolynomial idPolynomial::operator-() const { + int i; + idPolynomial n; + + n = *this; + for ( i = 0; i <= degree; i++ ) { + n[i] = -n[i]; + } + return n; +} + +ID_INLINE idPolynomial &idPolynomial::operator=( const idPolynomial &p ) { + Resize( p.degree, false ); + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = p.coefficient[i]; + } + return *this; +} + +ID_INLINE idPolynomial idPolynomial::operator+( const idPolynomial &p ) const { + int i; + idPolynomial n; + + if ( degree > p.degree ) { + n.Resize( degree, false ); + for ( i = 0; i <= p.degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + } + for ( ; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i]; + } + n.degree = degree; + } else if ( p.degree > degree ) { + n.Resize( p.degree, false ); + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + n.coefficient[i] = p.coefficient[i]; + } + n.degree = p.degree; + } else { + n.Resize( degree, false ); + n.degree = 0; + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + if ( n.coefficient[i] != 0.0f ) { + n.degree = i; + } + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator-( const idPolynomial &p ) const { + int i; + idPolynomial n; + + if ( degree > p.degree ) { + n.Resize( degree, false ); + for ( i = 0; i <= p.degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + } + for ( ; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i]; + } + n.degree = degree; + } else if ( p.degree >= degree ) { + n.Resize( p.degree, false ); + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + n.coefficient[i] = - p.coefficient[i]; + } + n.degree = p.degree; + } else { + n.Resize( degree, false ); + n.degree = 0; + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + if ( n.coefficient[i] != 0.0f ) { + n.degree = i; + } + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator*( const float s ) const { + idPolynomial n; + + if ( s == 0.0f ) { + n.degree = 0; + } else { + n.Resize( degree, false ); + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] * s; + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator/( const float s ) const { + float invs; + idPolynomial n; + + assert( s != 0.0f ); + n.Resize( degree, false ); + invs = 1.0f / s; + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] * invs; + } + return n; +} + +ID_INLINE idPolynomial &idPolynomial::operator+=( const idPolynomial &p ) { + int i; + + if ( degree > p.degree ) { + for ( i = 0; i <= p.degree; i++ ) { + coefficient[i] += p.coefficient[i]; + } + } else if ( p.degree > degree ) { + Resize( p.degree, true ); + for ( i = 0; i <= degree; i++ ) { + coefficient[i] += p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + coefficient[i] = p.coefficient[i]; + } + } else { + for ( i = 0; i <= degree; i++ ) { + coefficient[i] += p.coefficient[i]; + if ( coefficient[i] != 0.0f ) { + degree = i; + } + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator-=( const idPolynomial &p ) { + int i; + + if ( degree > p.degree ) { + for ( i = 0; i <= p.degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + } + } else if ( p.degree > degree ) { + Resize( p.degree, true ); + for ( i = 0; i <= degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + coefficient[i] = - p.coefficient[i]; + } + } else { + for ( i = 0; i <= degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + if ( coefficient[i] != 0.0f ) { + degree = i; + } + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator*=( const float s ) { + if ( s == 0.0f ) { + degree = 0; + } else { + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] *= s; + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator/=( const float s ) { + float invs; + + assert( s != 0.0f ); + invs = 1.0f / s; + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = invs; + } + return *this;; +} + +ID_INLINE bool idPolynomial::Compare( const idPolynomial &p ) const { + if ( degree != p.degree ) { + return false; + } + for ( int i = 0; i <= degree; i++ ) { + if ( coefficient[i] != p.coefficient[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idPolynomial::Compare( const idPolynomial &p, const float epsilon ) const { + if ( degree != p.degree ) { + return false; + } + for ( int i = 0; i <= degree; i++ ) { + if ( idMath::Fabs( coefficient[i] - p.coefficient[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idPolynomial::operator==( const idPolynomial &p ) const { + return Compare( p ); +} + +ID_INLINE bool idPolynomial::operator!=( const idPolynomial &p ) const { + return !Compare( p ); +} + +ID_INLINE void idPolynomial::Zero() { + degree = 0; +} + +ID_INLINE void idPolynomial::Zero( int d ) { + Resize( d, false ); + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = 0.0f; + } +} + +ID_INLINE int idPolynomial::GetDimension() const { + return degree; +} + +ID_INLINE int idPolynomial::GetDegree() const { + return degree; +} + +ID_INLINE float idPolynomial::GetValue( const float x ) const { + float y, z; + y = coefficient[0]; + z = x; + for ( int i = 1; i <= degree; i++ ) { + y += coefficient[i] * z; + z *= x; + } + return y; +} + +ID_INLINE idComplex idPolynomial::GetValue( const idComplex &x ) const { + idComplex y, z; + y.Set( coefficient[0], 0.0f ); + z = x; + for ( int i = 1; i <= degree; i++ ) { + y += coefficient[i] * z; + z *= x; + } + return y; +} + +ID_INLINE idPolynomial idPolynomial::GetDerivative() const { + idPolynomial n; + + if ( degree == 0 ) { + return n; + } + n.Resize( degree - 1, false ); + for ( int i = 1; i <= degree; i++ ) { + n.coefficient[i-1] = i * coefficient[i]; + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::GetAntiDerivative() const { + idPolynomial n; + + if ( degree == 0 ) { + return n; + } + n.Resize( degree + 1, false ); + n.coefficient[0] = 0.0f; + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i+1] = coefficient[i] / ( i + 1 ); + } + return n; +} + +ID_INLINE int idPolynomial::GetRoots1( float a, float b, float *roots ) { + assert( a != 0.0f ); + roots[0] = - b / a; + return 1; +} + +ID_INLINE int idPolynomial::GetRoots2( float a, float b, float c, float *roots ) { + float inva, ds; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + c *= inva; + b *= inva; + } + ds = b * b - 4.0f * c; + if ( ds < 0.0f ) { + return 0; + } else if ( ds > 0.0f ) { + ds = idMath::Sqrt( ds ); + roots[0] = 0.5f * ( -b - ds ); + roots[1] = 0.5f * ( -b + ds ); + return 2; + } else { + roots[0] = 0.5f * -b; + return 1; + } +} + +ID_INLINE int idPolynomial::GetRoots3( float a, float b, float c, float d, float *roots ) { + float inva, f, g, halfg, ofs, ds, dist, angle, cs, ss, t; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + d *= inva; + c *= inva; + b *= inva; + } + + f = ( 1.0f / 3.0f ) * ( 3.0f * c - b * b ); + g = ( 1.0f / 27.0f ) * ( 2.0f * b * b * b - 9.0f * c * b + 27.0f * d ); + halfg = 0.5f * g; + ofs = ( 1.0f / 3.0f ) * b; + ds = 0.25f * g * g + ( 1.0f / 27.0f ) * f * f * f; + + if ( ds < 0.0f ) { + dist = idMath::Sqrt( ( -1.0f / 3.0f ) * f ); + angle = ( 1.0f / 3.0f ) * idMath::ATan( idMath::Sqrt( -ds ), -halfg ); + cs = idMath::Cos( angle ); + ss = idMath::Sin( angle ); + roots[0] = 2.0f * dist * cs - ofs; + roots[1] = -dist * ( cs + idMath::SQRT_THREE * ss ) - ofs; + roots[2] = -dist * ( cs - idMath::SQRT_THREE * ss ) - ofs; + return 3; + } else if ( ds > 0.0f ) { + ds = idMath::Sqrt( ds ); + t = -halfg + ds; + if ( t >= 0.0f ) { + roots[0] = idMath::Pow( t, ( 1.0f / 3.0f ) ); + } else { + roots[0] = -idMath::Pow( -t, ( 1.0f / 3.0f ) ); + } + t = -halfg - ds; + if ( t >= 0.0f ) { + roots[0] += idMath::Pow( t, ( 1.0f / 3.0f ) ); + } else { + roots[0] -= idMath::Pow( -t, ( 1.0f / 3.0f ) ); + } + roots[0] -= ofs; + return 1; + } else { + if ( halfg >= 0.0f ) { + t = -idMath::Pow( halfg, ( 1.0f / 3.0f ) ); + } else { + t = idMath::Pow( -halfg, ( 1.0f / 3.0f ) ); + } + roots[0] = 2.0f * t - ofs; + roots[1] = -t - ofs; + roots[2] = roots[1]; + return 3; + } +} + +ID_INLINE int idPolynomial::GetRoots4( float a, float b, float c, float d, float e, float *roots ) { + int count; + float inva, y, ds, r, s1, s2, t1, t2, tp, tm; + float roots3[3]; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + e *= inva; + d *= inva; + c *= inva; + b *= inva; + } + + count = 0; + + GetRoots3( 1.0f, -c, b * d - 4.0f * e, -b * b * e + 4.0f * c * e - d * d, roots3 ); + y = roots3[0]; + ds = 0.25f * b * b - c + y; + + if ( ds < 0.0f ) { + return 0; + } else if ( ds > 0.0f ) { + r = idMath::Sqrt( ds ); + t1 = 0.75f * b * b - r * r - 2.0f * c; + t2 = ( 4.0f * b * c - 8.0f * d - b * b * b ) / ( 4.0f * r ); + tp = t1 + t2; + tm = t1 - t2; + + if ( tp >= 0.0f ) { + s1 = idMath::Sqrt( tp ); + roots[count++] = -0.25f * b + 0.5f * ( r + s1 ); + roots[count++] = -0.25f * b + 0.5f * ( r - s1 ); + } + if ( tm >= 0.0f ) { + s2 = idMath::Sqrt( tm ); + roots[count++] = -0.25f * b + 0.5f * ( s2 - r ); + roots[count++] = -0.25f * b - 0.5f * ( s2 + r ); + } + return count; + } else { + t2 = y * y - 4.0f * e; + if ( t2 >= 0.0f ) { + t2 = 2.0f * idMath::Sqrt( t2 ); + t1 = 0.75f * b * b - 2.0f * c; + if ( t1 + t2 >= 0.0f ) { + s1 = idMath::Sqrt( t1 + t2 ); + roots[count++] = -0.25f * b + 0.5f * s1; + roots[count++] = -0.25f * b - 0.5f * s1; + } + if ( t1 - t2 >= 0.0f ) { + s2 = idMath::Sqrt( t1 - t2 ); + roots[count++] = -0.25f * b + 0.5f * s2; + roots[count++] = -0.25f * b - 0.5f * s2; + } + } + return count; + } +} + +ID_INLINE const float *idPolynomial::ToFloatPtr() const { + return coefficient; +} + +ID_INLINE float *idPolynomial::ToFloatPtr() { + return coefficient; +} + +ID_INLINE void idPolynomial::Resize( int d, bool keep ) { + int alloc = ( d + 1 + 3 ) & ~3; + if ( alloc > allocated ) { + float *ptr = (float *) Mem_Alloc16( alloc * sizeof( float ), TAG_MATH ); + if ( coefficient != NULL ) { + if ( keep ) { + for ( int i = 0; i <= degree; i++ ) { + ptr[i] = coefficient[i]; + } + } + Mem_Free16( coefficient ); + } + allocated = alloc; + coefficient = ptr; + } + degree = d; +} + +#endif /* !__MATH_POLYNOMIAL_H__ */ diff --git a/neo/idlib/math/Quat.cpp b/neo/idlib/math/Quat.cpp new file mode 100644 index 00000000..ba36797c --- /dev/null +++ b/neo/idlib/math/Quat.cpp @@ -0,0 +1,307 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +===================== +idQuat::ToAngles +===================== +*/ +idAngles idQuat::ToAngles() const { + return ToMat3().ToAngles(); +} + +/* +===================== +idQuat::ToRotation +===================== +*/ +idRotation idQuat::ToRotation() const { + idVec3 vec; + float angle; + + vec.x = x; + vec.y = y; + vec.z = z; + angle = idMath::ACos( w ); + if ( angle == 0.0f ) { + vec.Set( 0.0f, 0.0f, 1.0f ); + } else { + //vec *= (1.0f / sin( angle )); + vec.Normalize(); + vec.FixDegenerateNormal(); + angle *= 2.0f * idMath::M_RAD2DEG; + } + return idRotation( vec3_origin, vec, angle ); +} + +/* +===================== +idQuat::ToMat3 +===================== +*/ +idMat3 idQuat::ToMat3() const { + idMat3 mat; + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + + xx = x * x2; + xy = x * y2; + xz = x * z2; + + yy = y * y2; + yz = y * z2; + zz = z * z2; + + wx = w * x2; + wy = w * y2; + wz = w * z2; + + mat[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + mat[ 0 ][ 1 ] = xy - wz; + mat[ 0 ][ 2 ] = xz + wy; + + mat[ 1 ][ 0 ] = xy + wz; + mat[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + mat[ 1 ][ 2 ] = yz - wx; + + mat[ 2 ][ 0 ] = xz - wy; + mat[ 2 ][ 1 ] = yz + wx; + mat[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + + return mat; +} + +/* +===================== +idQuat::ToMat4 +===================== +*/ +idMat4 idQuat::ToMat4() const { + return ToMat3().ToMat4(); +} + +/* +===================== +idQuat::ToCQuat +===================== +*/ +idCQuat idQuat::ToCQuat() const { + if ( w < 0.0f ) { + return idCQuat( -x, -y, -z ); + } + return idCQuat( x, y, z ); +} + +/* +============ +idQuat::ToAngularVelocity +============ +*/ +idVec3 idQuat::ToAngularVelocity() const { + idVec3 vec; + + vec.x = x; + vec.y = y; + vec.z = z; + vec.Normalize(); + return vec * idMath::ACos( w ); +} + +/* +============= +idQuat::ToString +============= +*/ +const char *idQuat::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +===================== +idQuat::Slerp + +Spherical linear interpolation between two quaternions. +===================== +*/ +idQuat &idQuat::Slerp( const idQuat &from, const idQuat &to, float t ) { + idQuat temp; + float omega, cosom, sinom, scale0, scale1; + + if ( t <= 0.0f ) { + *this = from; + return *this; + } + + if ( t >= 1.0f ) { + *this = to; + return *this; + } + + if ( from == to ) { + *this = to; + return *this; + } + + cosom = from.x * to.x + from.y * to.y + from.z * to.z + from.w * to.w; + if ( cosom < 0.0f ) { + temp = -to; + cosom = -cosom; + } else { + temp = to; + } + + if ( ( 1.0f - cosom ) > 1e-6f ) { +#if 0 + omega = acos( cosom ); + sinom = 1.0f / sin( omega ); + scale0 = sin( ( 1.0f - t ) * omega ) * sinom; + scale1 = sin( t * omega ) * sinom; +#else + scale0 = 1.0f - cosom * cosom; + sinom = idMath::InvSqrt( scale0 ); + omega = idMath::ATan16( scale0 * sinom, cosom ); + scale0 = idMath::Sin16( ( 1.0f - t ) * omega ) * sinom; + scale1 = idMath::Sin16( t * omega ) * sinom; +#endif + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + *this = ( scale0 * from ) + ( scale1 * temp ); + return *this; +} + +/* +======================== +idQuat::Lerp + +Approximation of spherical linear interpolation between two quaternions. The interpolation +traces out the exact same curve as Slerp but does not maintain a constant speed across the arc. +======================== +*/ +idQuat &idQuat::Lerp( const idQuat &from, const idQuat &to, const float t ) { + if ( t <= 0.0f ) { + *this = from; + return *this; + } + + if ( t >= 1.0f ) { + *this = to; + return *this; + } + + if ( from == to ) { + *this = to; + return *this; + } + + float cosom = from.x * to.x + from.y * to.y + from.z * to.z + from.w * to.w; + + float scale0 = 1.0f - t; + float scale1 = ( cosom >= 0.0f ) ? t : -t; + + x = scale0 * from.x + scale1 * to.x; + y = scale0 * from.y + scale1 * to.y; + z = scale0 * from.z + scale1 * to.z; + w = scale0 * from.w + scale1 * to.w; + + float s = idMath::InvSqrt( x * x + y * y + z * z + w * w ); + + x *= s; + y *= s; + z *= s; + w *= s; + + return *this; +} + +/* +============= +idCQuat::ToAngles +============= +*/ +idAngles idCQuat::ToAngles() const { + return ToQuat().ToAngles(); +} + +/* +============= +idCQuat::ToRotation +============= +*/ +idRotation idCQuat::ToRotation() const { + return ToQuat().ToRotation(); +} + +/* +============= +idCQuat::ToMat3 +============= +*/ +idMat3 idCQuat::ToMat3() const { + return ToQuat().ToMat3(); +} + +/* +============= +idCQuat::ToMat4 +============= +*/ +idMat4 idCQuat::ToMat4() const { + return ToQuat().ToMat4(); +} + +/* +============= +idCQuat::ToString +============= +*/ +const char *idCQuat::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +===================== +Slerp + +Spherical linear interpolation between two quaternions. +===================== +*/ +idQuat Slerp( const idQuat & from, const idQuat & to, const float t ) { + return idQuat().Slerp( from, to, t ); +} diff --git a/neo/idlib/math/Quat.h b/neo/idlib/math/Quat.h new file mode 100644 index 00000000..89c8773a --- /dev/null +++ b/neo/idlib/math/Quat.h @@ -0,0 +1,433 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_QUAT_H__ +#define __MATH_QUAT_H__ + +/* +=============================================================================== + + Quaternion + +=============================================================================== +*/ + +#include "../containers/Array.h" // for idTupleSize + +class idVec3; +class idAngles; +class idRotation; +class idMat3; +class idMat4; +class idCQuat; + +class idQuat { +public: + float x; + float y; + float z; + float w; + + idQuat(); + idQuat( float x, float y, float z, float w ); + + void Set( float x, float y, float z, float w ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idQuat operator-() const; + idQuat & operator=( const idQuat &a ); + idQuat operator+( const idQuat &a ) const; + idQuat & operator+=( const idQuat &a ); + idQuat operator-( const idQuat &a ) const; + idQuat & operator-=( const idQuat &a ); + idQuat operator*( const idQuat &a ) const; + idVec3 operator*( const idVec3 &a ) const; + idQuat operator*( float a ) const; + idQuat & operator*=( const idQuat &a ); + idQuat & operator*=( float a ); + + friend idQuat operator*( const float a, const idQuat &b ); + friend idVec3 operator*( const idVec3 &a, const idQuat &b ); + + bool Compare( const idQuat &a ) const; // exact compare, no epsilon + bool Compare( const idQuat &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idQuat &a ) const; // exact compare, no epsilon + bool operator!=( const idQuat &a ) const; // exact compare, no epsilon + + idQuat Inverse() const; + float Length() const; + idQuat & Normalize(); + + float CalcW() const; + int GetDimension() const; + + idAngles ToAngles() const; + idRotation ToRotation() const; + idMat3 ToMat3() const; + idMat4 ToMat4() const; + idCQuat ToCQuat() const; + idVec3 ToAngularVelocity() const; + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + idQuat & Slerp( const idQuat &from, const idQuat &to, float t ); + idQuat & Lerp( const idQuat &from, const idQuat &to, const float t ); +}; + +// A non-member slerp function allows constructing a const idQuat object with the result of a slerp, +// but without having to explicity create a temporary idQuat object. +idQuat Slerp( const idQuat & from, const idQuat & to, const float t ); + +ID_INLINE idQuat::idQuat() { +} + +ID_INLINE idQuat::idQuat( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE float idQuat::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +ID_INLINE float& idQuat::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +ID_INLINE idQuat idQuat::operator-() const { + return idQuat( -x, -y, -z, -w ); +} + +ID_INLINE idQuat &idQuat::operator=( const idQuat &a ) { + x = a.x; + y = a.y; + z = a.z; + w = a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator+( const idQuat &a ) const { + return idQuat( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idQuat& idQuat::operator+=( const idQuat &a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator-( const idQuat &a ) const { + return idQuat( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE idQuat& idQuat::operator-=( const idQuat &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator*( const idQuat &a ) const { + return idQuat( w*a.x + x*a.w + y*a.z - z*a.y, + w*a.y + y*a.w + z*a.x - x*a.z, + w*a.z + z*a.w + x*a.y - y*a.x, + w*a.w - x*a.x - y*a.y - z*a.z ); +} + +ID_INLINE idVec3 idQuat::operator*( const idVec3 &a ) const { +#if 0 + // it's faster to do the conversion to a 3x3 matrix and multiply the vector by this 3x3 matrix + return ( ToMat3() * a ); +#else + // result = this->Inverse() * idQuat( a.x, a.y, a.z, 0.0f ) * (*this) + float xxzz = x*x - z*z; + float wwyy = w*w - y*y; + + float xw2 = x*w*2.0f; + float xy2 = x*y*2.0f; + float xz2 = x*z*2.0f; + float yw2 = y*w*2.0f; + float yz2 = y*z*2.0f; + float zw2 = z*w*2.0f; + + return idVec3( + (xxzz + wwyy)*a.x + (xy2 + zw2)*a.y + (xz2 - yw2)*a.z, + (xy2 - zw2)*a.x + (y*y+w*w-x*x-z*z)*a.y + (yz2 + xw2)*a.z, + (xz2 + yw2)*a.x + (yz2 - xw2)*a.y + (wwyy - xxzz)*a.z + ); +#endif +} + +ID_INLINE idQuat idQuat::operator*( float a ) const { + return idQuat( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idQuat operator*( const float a, const idQuat &b ) { + return b * a; +} + +ID_INLINE idVec3 operator*( const idVec3 &a, const idQuat &b ) { + return b * a; +} + +ID_INLINE idQuat& idQuat::operator*=( const idQuat &a ) { + *this = *this * a; + + return *this; +} + +ID_INLINE idQuat& idQuat::operator*=( float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +ID_INLINE bool idQuat::Compare( const idQuat &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && ( w == a.w ) ); +} + +ID_INLINE bool idQuat::Compare( const idQuat &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + if ( idMath::Fabs( w - a.w ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idQuat::operator==( const idQuat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idQuat::operator!=( const idQuat &a ) const { + return !Compare( a ); +} + +ID_INLINE void idQuat::Set( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE idQuat idQuat::Inverse() const { + return idQuat( -x, -y, -z, w ); +} + +ID_INLINE float idQuat::Length() const { + float len; + + len = x * x + y * y + z * z + w * w; + return idMath::Sqrt( len ); +} + +ID_INLINE idQuat& idQuat::Normalize() { + float len; + float ilength; + + len = this->Length(); + if ( len ) { + ilength = 1 / len; + x *= ilength; + y *= ilength; + z *= ilength; + w *= ilength; + } + return *this; +} + +ID_INLINE float idQuat::CalcW() const { + // take the absolute value because floating point rounding may cause the dot of x,y,z to be larger than 1 + return sqrt( fabs( 1.0f - ( x * x + y * y + z * z ) ) ); +} + +ID_INLINE int idQuat::GetDimension() const { + return 4; +} + +ID_INLINE const float *idQuat::ToFloatPtr() const { + return &x; +} + +ID_INLINE float *idQuat::ToFloatPtr() { + return &x; +} + +/* +=============================================================================== + + Specialization to get size of an idQuat generically. + +=============================================================================== +*/ +template<> +struct idTupleSize< idQuat > { + enum { value = 4 }; +}; + +/* +=============================================================================== + + Compressed quaternion + +=============================================================================== +*/ + +class idCQuat { +public: + float x; + float y; + float z; + + idCQuat(); + idCQuat( float x, float y, float z ); + + void Set( float x, float y, float z ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + bool Compare( const idCQuat &a ) const; // exact compare, no epsilon + bool Compare( const idCQuat &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idCQuat &a ) const; // exact compare, no epsilon + bool operator!=( const idCQuat &a ) const; // exact compare, no epsilon + + int GetDimension() const; + + idAngles ToAngles() const; + idRotation ToRotation() const; + idMat3 ToMat3() const; + idMat4 ToMat4() const; + idQuat ToQuat() const; + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; +}; + +ID_INLINE idCQuat::idCQuat() { +} + +ID_INLINE idCQuat::idCQuat( float x, float y, float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE void idCQuat::Set( float x, float y, float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE float idCQuat::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +ID_INLINE float& idCQuat::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +ID_INLINE bool idCQuat::Compare( const idCQuat &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) ); +} + +ID_INLINE bool idCQuat::Compare( const idCQuat &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idCQuat::operator==( const idCQuat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idCQuat::operator!=( const idCQuat &a ) const { + return !Compare( a ); +} + +ID_INLINE int idCQuat::GetDimension() const { + return 3; +} + +ID_INLINE idQuat idCQuat::ToQuat() const { + // take the absolute value because floating point rounding may cause the dot of x,y,z to be larger than 1 + return idQuat( x, y, z, sqrt( fabs( 1.0f - ( x * x + y * y + z * z ) ) ) ); +} + +ID_INLINE const float *idCQuat::ToFloatPtr() const { + return &x; +} + +ID_INLINE float *idCQuat::ToFloatPtr() { + return &x; +} + +/* +=============================================================================== + + Specialization to get size of an idCQuat generically. + +=============================================================================== +*/ +template<> +struct idTupleSize< idCQuat > { + enum { value = 3 }; +}; + +#endif /* !__MATH_QUAT_H__ */ diff --git a/neo/idlib/math/Random.h b/neo/idlib/math/Random.h new file mode 100644 index 00000000..cea9197a --- /dev/null +++ b/neo/idlib/math/Random.h @@ -0,0 +1,158 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_RANDOM_H__ +#define __MATH_RANDOM_H__ + +/* +=============================================================================== + + Random number generator + +=============================================================================== +*/ + +class idRandom { +public: + idRandom( int seed = 0 ); + + void SetSeed( int seed ); + int GetSeed() const; + + int RandomInt(); // random integer in the range [0, MAX_RAND] + int RandomInt( int max ); // random integer in the range [0, max[ + float RandomFloat(); // random number in the range [0.0f, 1.0f] + float CRandomFloat(); // random number in the range [-1.0f, 1.0f] + + static const int MAX_RAND = 0x7fff; + +private: + int seed; +}; + +ID_INLINE idRandom::idRandom( int seed ) { + this->seed = seed; +} + +ID_INLINE void idRandom::SetSeed( int seed ) { + this->seed = seed; +} + +ID_INLINE int idRandom::GetSeed() const { + return seed; +} + +ID_INLINE int idRandom::RandomInt() { + seed = 69069 * seed + 1; + return ( seed & idRandom::MAX_RAND ); +} + +ID_INLINE int idRandom::RandomInt( int max ) { + if ( max == 0 ) { + return 0; // avoid divide by zero error + } + return RandomInt() % max; +} + +ID_INLINE float idRandom::RandomFloat() { + return ( RandomInt() / ( float )( idRandom::MAX_RAND + 1 ) ); +} + +ID_INLINE float idRandom::CRandomFloat() { + return ( 2.0f * ( RandomFloat() - 0.5f ) ); +} + + +/* +=============================================================================== + + Random number generator + +=============================================================================== +*/ + +class idRandom2 { +public: + idRandom2( unsigned long seed = 0 ); + + void SetSeed( unsigned long seed ); + unsigned long GetSeed() const; + + int RandomInt(); // random integer in the range [0, MAX_RAND] + int RandomInt( int max ); // random integer in the range [0, max] + float RandomFloat(); // random number in the range [0.0f, 1.0f] + float CRandomFloat(); // random number in the range [-1.0f, 1.0f] + + static const int MAX_RAND = 0x7fff; + +private: + unsigned long seed; + + static const unsigned long IEEE_ONE = 0x3f800000; + static const unsigned long IEEE_MASK = 0x007fffff; +}; + +ID_INLINE idRandom2::idRandom2( unsigned long seed ) { + this->seed = seed; +} + +ID_INLINE void idRandom2::SetSeed( unsigned long seed ) { + this->seed = seed; +} + +ID_INLINE unsigned long idRandom2::GetSeed() const { + return seed; +} + +ID_INLINE int idRandom2::RandomInt() { + seed = 1664525L * seed + 1013904223L; + return ( (int) seed & idRandom2::MAX_RAND ); +} + +ID_INLINE int idRandom2::RandomInt( int max ) { + if ( max == 0 ) { + return 0; // avoid divide by zero error + } + return ( RandomInt() >> ( 16 - idMath::BitsForInteger( max ) ) ) % max; +} + +ID_INLINE float idRandom2::RandomFloat() { + unsigned long i; + seed = 1664525L * seed + 1013904223L; + i = idRandom2::IEEE_ONE | ( seed & idRandom2::IEEE_MASK ); + return ( ( *(float *)&i ) - 1.0f ); +} + +ID_INLINE float idRandom2::CRandomFloat() { + unsigned long i; + seed = 1664525L * seed + 1013904223L; + i = idRandom2::IEEE_ONE | ( seed & idRandom2::IEEE_MASK ); + return ( 2.0f * ( *(float *)&i ) - 3.0f ); +} + +#endif /* !__MATH_RANDOM_H__ */ diff --git a/neo/idlib/math/Rotation.cpp b/neo/idlib/math/Rotation.cpp new file mode 100644 index 00000000..5819409b --- /dev/null +++ b/neo/idlib/math/Rotation.cpp @@ -0,0 +1,156 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +/* +============ +idRotation::ToAngles +============ +*/ +idAngles idRotation::ToAngles() const { + return ToMat3().ToAngles(); +} + +/* +============ +idRotation::ToQuat +============ +*/ +idQuat idRotation::ToQuat() const { + float a, s, c; + + a = angle * ( idMath::M_DEG2RAD * 0.5f ); + idMath::SinCos( a, s, c ); + return idQuat( vec.x * s, vec.y * s, vec.z * s, c ); +} + +/* +============ +idRotation::toMat3 +============ +*/ +const idMat3 &idRotation::ToMat3() const { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + float a, c, s, x, y, z; + + if ( axisValid ) { + return axis; + } + + a = angle * ( idMath::M_DEG2RAD * 0.5f ); + idMath::SinCos( a, s, c ); + + x = vec[0] * s; + y = vec[1] * s; + z = vec[2] * s; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + + xx = x * x2; + xy = x * y2; + xz = x * z2; + + yy = y * y2; + yz = y * z2; + zz = z * z2; + + wx = c * x2; + wy = c * y2; + wz = c * z2; + + axis[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + axis[ 0 ][ 1 ] = xy - wz; + axis[ 0 ][ 2 ] = xz + wy; + + axis[ 1 ][ 0 ] = xy + wz; + axis[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + axis[ 1 ][ 2 ] = yz - wx; + + axis[ 2 ][ 0 ] = xz - wy; + axis[ 2 ][ 1 ] = yz + wx; + axis[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + + axisValid = true; + + return axis; +} + +/* +============ +idRotation::ToMat4 +============ +*/ +idMat4 idRotation::ToMat4() const { + return ToMat3().ToMat4(); +} + +/* +============ +idRotation::ToAngularVelocity +============ +*/ +idVec3 idRotation::ToAngularVelocity() const { + return vec * DEG2RAD( angle ); +} + +/* +============ +idRotation::Normalize180 +============ +*/ +void idRotation::Normalize180() { + angle -= floor( angle / 360.0f ) * 360.0f; + if ( angle > 180.0f ) { + angle -= 360.0f; + } + else if ( angle < -180.0f ) { + angle += 360.0f; + } +} + +/* +============ +idRotation::Normalize360 +============ +*/ +void idRotation::Normalize360() { + angle -= floor( angle / 360.0f ) * 360.0f; + if ( angle > 360.0f ) { + angle -= 360.0f; + } + else if ( angle < 0.0f ) { + angle += 360.0f; + } +} diff --git a/neo/idlib/math/Rotation.h b/neo/idlib/math/Rotation.h new file mode 100644 index 00000000..89b1acc0 --- /dev/null +++ b/neo/idlib/math/Rotation.h @@ -0,0 +1,211 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_ROTATION_H__ +#define __MATH_ROTATION_H__ + +/* +=============================================================================== + + Describes a complete rotation in degrees about an abritray axis. + A local rotation matrix is stored for fast rotation of multiple points. + +=============================================================================== +*/ + + +class idAngles; +class idQuat; +class idMat3; + +class idRotation { + + friend class idAngles; + friend class idQuat; + friend class idMat3; + +public: + idRotation(); + idRotation( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ); + + void Set( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ); + void SetOrigin( const idVec3 &rotationOrigin ); + void SetVec( const idVec3 &rotationVec ); // has to be normalized + void SetVec( const float x, const float y, const float z ); // has to be normalized + void SetAngle( const float rotationAngle ); + void Scale( const float s ); + void ReCalculateMatrix(); + const idVec3 & GetOrigin() const; + const idVec3 & GetVec() const; + float GetAngle() const; + + idRotation operator-() const; // flips rotation + idRotation operator*( const float s ) const; // scale rotation + idRotation operator/( const float s ) const; // scale rotation + idRotation & operator*=( const float s ); // scale rotation + idRotation & operator/=( const float s ); // scale rotation + idVec3 operator*( const idVec3 &v ) const; // rotate vector + + friend idRotation operator*( const float s, const idRotation &r ); // scale rotation + friend idVec3 operator*( const idVec3 &v, const idRotation &r ); // rotate vector + friend idVec3 & operator*=( idVec3 &v, const idRotation &r ); // rotate vector + + idAngles ToAngles() const; + idQuat ToQuat() const; + const idMat3 & ToMat3() const; + idMat4 ToMat4() const; + idVec3 ToAngularVelocity() const; + + void RotatePoint( idVec3 &point ) const; + + void Normalize180(); + void Normalize360(); + +private: + idVec3 origin; // origin of rotation + idVec3 vec; // normalized vector to rotate around + float angle; // angle of rotation in degrees + mutable idMat3 axis; // rotation axis + mutable bool axisValid; // true if rotation axis is valid +}; + + +ID_INLINE idRotation::idRotation() { +} + +ID_INLINE idRotation::idRotation( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ) { + origin = rotationOrigin; + vec = rotationVec; + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::Set( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ) { + origin = rotationOrigin; + vec = rotationVec; + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::SetOrigin( const idVec3 &rotationOrigin ) { + origin = rotationOrigin; +} + +ID_INLINE void idRotation::SetVec( const idVec3 &rotationVec ) { + vec = rotationVec; + axisValid = false; +} + +ID_INLINE void idRotation::SetVec( float x, float y, float z ) { + vec[0] = x; + vec[1] = y; + vec[2] = z; + axisValid = false; +} + +ID_INLINE void idRotation::SetAngle( const float rotationAngle ) { + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::Scale( const float s ) { + angle *= s; + axisValid = false; +} + +ID_INLINE void idRotation::ReCalculateMatrix() { + axisValid = false; + ToMat3(); +} + +ID_INLINE const idVec3 &idRotation::GetOrigin() const { + return origin; +} + +ID_INLINE const idVec3 &idRotation::GetVec() const { + return vec; +} + +ID_INLINE float idRotation::GetAngle() const { + return angle; +} + +ID_INLINE idRotation idRotation::operator-() const { + return idRotation( origin, vec, -angle ); +} + +ID_INLINE idRotation idRotation::operator*( const float s ) const { + return idRotation( origin, vec, angle * s ); +} + +ID_INLINE idRotation idRotation::operator/( const float s ) const { + assert( s != 0.0f ); + return idRotation( origin, vec, angle / s ); +} + +ID_INLINE idRotation &idRotation::operator*=( const float s ) { + angle *= s; + axisValid = false; + return *this; +} + +ID_INLINE idRotation &idRotation::operator/=( const float s ) { + assert( s != 0.0f ); + angle /= s; + axisValid = false; + return *this; +} + +ID_INLINE idVec3 idRotation::operator*( const idVec3 &v ) const { + if ( !axisValid ) { + ToMat3(); + } + return ((v - origin) * axis + origin); +} + +ID_INLINE idRotation operator*( const float s, const idRotation &r ) { + return r * s; +} + +ID_INLINE idVec3 operator*( const idVec3 &v, const idRotation &r ) { + return r * v; +} + +ID_INLINE idVec3 &operator*=( idVec3 &v, const idRotation &r ) { + v = r * v; + return v; +} + +ID_INLINE void idRotation::RotatePoint( idVec3 &point ) const { + if ( !axisValid ) { + ToMat3(); + } + point = ((point - origin) * axis + origin); +} + +#endif /* !__MATH_ROTATION_H__ */ diff --git a/neo/idlib/math/Simd.cpp b/neo/idlib/math/Simd.cpp new file mode 100644 index 00000000..17fb4ed4 --- /dev/null +++ b/neo/idlib/math/Simd.cpp @@ -0,0 +1,1272 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +#include "Simd_Generic.h" +#include "Simd_SSE.h" + +idSIMDProcessor * processor = NULL; // pointer to SIMD processor +idSIMDProcessor * generic = NULL; // pointer to generic SIMD implementation +idSIMDProcessor * SIMDProcessor = NULL; + +/* +================ +idSIMD::Init +================ +*/ +void idSIMD::Init() { + generic = new (TAG_MATH) idSIMD_Generic; + generic->cpuid = CPUID_GENERIC; + processor = NULL; + SIMDProcessor = generic; +} + +/* +============ +idSIMD::InitProcessor +============ +*/ +void idSIMD::InitProcessor( const char *module, bool forceGeneric ) { + cpuid_t cpuid; + idSIMDProcessor *newProcessor; + + cpuid = idLib::sys->GetProcessorId(); + + if ( forceGeneric ) { + + newProcessor = generic; + + } else { + + if ( processor == NULL ) { + if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) ) { + processor = new (TAG_MATH) idSIMD_SSE; + } else { + processor = generic; + } + processor->cpuid = cpuid; + } + + newProcessor = processor; + } + + if ( newProcessor != SIMDProcessor ) { + SIMDProcessor = newProcessor; + idLib::common->Printf( "%s using %s for SIMD processing\n", module, SIMDProcessor->GetName() ); + } + + if ( cpuid & CPUID_FTZ ) { + idLib::sys->FPU_SetFTZ( true ); + idLib::common->Printf( "enabled Flush-To-Zero mode\n" ); + } + + if ( cpuid & CPUID_DAZ ) { + idLib::sys->FPU_SetDAZ( true ); + idLib::common->Printf( "enabled Denormals-Are-Zero mode\n" ); + } +} + +/* +================ +idSIMD::Shutdown +================ +*/ +void idSIMD::Shutdown() { + if ( processor != generic ) { + delete processor; + } + delete generic; + generic = NULL; + processor = NULL; + SIMDProcessor = NULL; +} + + +//=============================================================== +// +// Test code +// +//=============================================================== + +#define COUNT 999 // data count (odd to catch edge cases) +#define BIG_COUNT COUNT*5 // Some tests need a larger count +#define NUMTESTS 2048 // number of tests + +#define RANDOM_SEED 1013904223L //((int)idLib::sys->GetClockTicks()) + +idSIMDProcessor *p_simd; +idSIMDProcessor *p_generic; +long baseClocks = 0; + + +#define TIME_TYPE int + +#pragma warning(disable : 4731) // frame pointer register 'ebx' modified by inline assembly code + +long saved_ebx = 0; + +#define StartRecordTime( start ) \ + __asm mov saved_ebx, ebx \ + __asm xor eax, eax \ + __asm cpuid \ + __asm rdtsc \ + __asm mov start, eax \ + __asm xor eax, eax \ + __asm cpuid + +#define StopRecordTime( end ) \ + __asm xor eax, eax \ + __asm cpuid \ + __asm rdtsc \ + __asm mov end, eax \ + __asm mov ebx, saved_ebx \ + __asm xor eax, eax \ + __asm cpuid + + +#define GetBest( start, end, best ) \ + if ( !best || end - start < best ) { \ + best = end - start; \ + } + + +/* +============ +PrintClocks +============ +*/ +void PrintClocks( char *string, int dataCount, int clocks, int otherClocks = 0 ) { + int i; + + idLib::common->Printf( string ); + for ( i = idStr::LengthWithoutColors(string); i < 48; i++ ) { + idLib::common->Printf(" "); + } + clocks -= baseClocks; + if ( otherClocks && clocks ) { + otherClocks -= baseClocks; + float p = (float)otherClocks / (float)clocks; + idLib::common->Printf( "c = %4d, clcks = %5d, %.1fX\n", dataCount, clocks, p ); + } else { + idLib::common->Printf( "c = %4d, clcks = %5d\n", dataCount, clocks ); + } +} + +/* +============ +GetBaseClocks +============ +*/ +void GetBaseClocks() { + int i, start, end, bestClocks; + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + baseClocks = bestClocks; +} + +/* +============ +TestMinMax +============ +*/ +void TestMinMax() { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( idVec2 v2src0[COUNT] ); + ALIGN16( idVec3 v3src0[COUNT] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( triIndex_t indexes[COUNT] ); + float min = 0.0f, max = 0.0f, min2 = 0.0f, max2 = 0.0f; + idVec2 v2min, v2max, v2min2, v2max2; + idVec3 vmin, vmax, vmin2, vmax2; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + v2src0[i][0] = srnd.CRandomFloat() * 10.0f; + v2src0[i][1] = srnd.CRandomFloat() * 10.0f; + v3src0[i][0] = srnd.CRandomFloat() * 10.0f; + v3src0[i][1] = srnd.CRandomFloat() * 10.0f; + v3src0[i][2] = srnd.CRandomFloat() * 10.0f; + drawVerts[i].xyz = v3src0[i]; + indexes[i] = i; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + min = idMath::INFINITY; + max = -idMath::INFINITY; + StartRecordTime( start ); + p_generic->MinMax( min, max, fsrc0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( min2, max2, fsrc0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( min == min2 && max == max2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( v2min, v2max, v2src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idVec2[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( v2min2, v2max2, v2src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( v2min == v2min2 && v2max == v2max2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idVec2[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, drawVerts, indexes, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idDrawVert[], indexes[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, drawVerts, indexes, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idDrawVert[], indexes[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMemcpy +============ +*/ +void TestMemcpy() { + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + int i; + byte test0[BIG_COUNT]; + byte test1[BIG_COUNT]; + const char * result; + + idRandom random( RANDOM_SEED ); + for ( i = 0; i < BIG_COUNT; i++ ) { + test0[i] = random.RandomInt( 255 ); + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Memcpy( test1, test0, BIG_COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Memcpy()", BIG_COUNT, bestClocksGeneric ); + + for ( i = 0; i < BIG_COUNT; i++ ) { + test0[i] = random.RandomInt( 255 ); + } + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Memcpy( test1, test0, BIG_COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + for ( i = 0; i < BIG_COUNT; i++ ) { + if ( test1[i] != test0[i] ) { + break; + } + } + result = ( i >= BIG_COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Memcpy() %s", result), BIG_COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMemset +============ +*/ +void TestMemset() { + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + int i, j; + const char * result; + byte test0[BIG_COUNT]; + + idRandom random( RANDOM_SEED ); + j = 1 + random.RandomInt( 254 ); + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Memset( test0, j, BIG_COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Memset()", BIG_COUNT, bestClocksGeneric ); + + j = 1 + random.RandomInt( 254 ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Memset( test0, j, BIG_COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + for ( i = 0; i < BIG_COUNT; i++ ) { + if ( test0[i] != j ) { + break; + } + } + result = ( i >= BIG_COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Memset() %s", result), BIG_COUNT, bestClocksSIMD, bestClocksGeneric ); + + j = 0; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Memset( test0, j, BIG_COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Memset( 0 )", BIG_COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Memset( test0, j, BIG_COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + for ( i = 0; i < BIG_COUNT; i++ ) { + if ( test0[i] != j ) { + break; + } + } + result = ( i >= BIG_COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Memset( 0 ) %s", result), BIG_COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestBlendJoints +============ +*/ +void TestBlendJoints() { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + idTempArray< idJointQuat > baseJoints( COUNT ); + idTempArray< idJointQuat > joints1( COUNT ); + idTempArray< idJointQuat > joints2( COUNT ); + idTempArray< idJointQuat > blendJoints( COUNT ); + idTempArray< int > index( COUNT ); + float lerp = 0.3f; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].w = 0.0f; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + blendJoints[i].q = angles.ToQuat(); + blendJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].w = 0.0f; + index[i] = i; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints1[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_generic->BlendJoints( joints1.Ptr(), blendJoints.Ptr(), lerp, index.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->BlendJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints2[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_simd->BlendJoints( joints2.Ptr(), blendJoints.Ptr(), lerp, index.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].t.Compare( joints2[i].t, 1e-3f ) ) { + break; + } + if ( !joints1[i].q.Compare( joints2[i].q, 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->BlendJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestBlendJoints +============ +*/ +void TestBlendJointsFast() { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + idTempArray< idJointQuat > baseJoints( COUNT ); + idTempArray< idJointQuat > joints1( COUNT ); + idTempArray< idJointQuat > joints2( COUNT ); + idTempArray< idJointQuat > blendJoints( COUNT ); + idTempArray< int > index( COUNT ); + float lerp = 0.3f; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].w = 0.0f; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + blendJoints[i].q = angles.ToQuat(); + blendJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].w = 0.0f; + index[i] = i; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints1[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_generic->BlendJointsFast( joints1.Ptr(), blendJoints.Ptr(), lerp, index.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->BlendJointsFast()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints2[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_simd->BlendJointsFast( joints2.Ptr(), blendJoints.Ptr(), lerp, index.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].t.Compare( joints2[i].t, 1e-3f ) ) { + break; + } + if ( !joints1[i].q.Compare( joints2[i].q, 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->BlendJointsFast() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestConvertJointQuatsToJointMats +============ +*/ +void TestConvertJointQuatsToJointMats() { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + idTempArray< idJointQuat > baseJoints( COUNT ); + idTempArray< idJointMat > joints1( COUNT ); + idTempArray< idJointMat > joints2( COUNT ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ConvertJointQuatsToJointMats( joints1.Ptr(), baseJoints.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ConvertJointQuatsToJointMats()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ConvertJointQuatsToJointMats( joints2.Ptr(), baseJoints.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].Compare( joints2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ConvertJointQuatsToJointMats() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestConvertJointMatsToJointQuats +============ +*/ +void TestConvertJointMatsToJointQuats() { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + idTempArray< idJointMat > baseJoints( COUNT ); + idTempArray< idJointQuat > joints1( COUNT ); + idTempArray< idJointQuat > joints2( COUNT ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 10.0f; + v[1] = srnd.CRandomFloat() * 10.0f; + v[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].SetTranslation( v ); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ConvertJointMatsToJointQuats( joints1.Ptr(), baseJoints.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ConvertJointMatsToJointQuats()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ConvertJointMatsToJointQuats( joints2.Ptr(), baseJoints.Ptr(), COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].q.Compare( joints2[i].q, 1e-4f ) ) { + break; + } + if ( !joints1[i].t.Compare( joints2[i].t, 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ConvertJointMatsToJointQuats() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTransformJoints +============ +*/ +void TestTransformJoints() { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + idTempArray< idJointMat > joints( COUNT+1 ); + idTempArray< idJointMat > joints1( COUNT+1 ); + idTempArray< idJointMat > joints2( COUNT+1 ); + idTempArray< int > parents( COUNT+1 ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i <= COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + parents[i] = i - 1; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints1[j] = joints[j]; + } + StartRecordTime( start ); + p_generic->TransformJoints( joints1.Ptr(), parents.Ptr(), 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints2[j] = joints[j]; + } + StartRecordTime( start ); + p_simd->TransformJoints( joints2.Ptr(), parents.Ptr(), 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 1; i <= COUNT; i++ ) { + if ( !joints1[i].Compare( joints2[i], 1e-3f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestUntransformJoints +============ +*/ +void TestUntransformJoints() { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + idTempArray< idJointMat > joints( COUNT+1 ); + idTempArray< idJointMat > joints1( COUNT+1 ); + idTempArray< idJointMat > joints2( COUNT+1 ); + idTempArray< int > parents( COUNT+1 ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i <= COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + parents[i] = i - 1; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints1[j] = joints[j]; + } + StartRecordTime( start ); + p_generic->UntransformJoints( joints1.Ptr(), parents.Ptr(), 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->UntransformJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints2[j] = joints[j]; + } + StartRecordTime( start ); + p_simd->UntransformJoints( joints2.Ptr(), parents.Ptr(), 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 1; i <= COUNT; i++ ) { + if ( !joints1[i].Compare( joints2[i], 1e-3f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UntransformJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMath +============ +*/ +void TestMath() { + int i; + TIME_TYPE start, end, bestClocks; + + idLib::common->Printf("====================================\n" ); + + float tst = -1.0f; + float tst2 = 1.0f; + float testvar = 1.0f; + idRandom rnd; + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = fabs( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " fabs( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + int tmp = * ( int * ) &tst; + tmp &= 0x7FFFFFFF; + tst = * ( float * ) &tmp; + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Fabs( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = 10.0f + 100.0f * rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = sqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.01f; + tst = 10.0f + 100.0f * rnd.RandomFloat(); + } + PrintClocks( " sqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sin( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Sin( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sin16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Sin16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Cos( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Cos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Cos16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Cos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + idMath::SinCos( tst, tst, tst2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::SinCos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + idMath::SinCos16( tst, tst, tst2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( "idMath::SinCos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Tan( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Tan( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Tan16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Tan16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ASin( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ASin( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ASin16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ASin16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ACos( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ACos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ACos16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ACos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ATan( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ATan( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ATan16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ATan16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Pow( 2.7f, tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Pow( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Pow16( 2.7f, tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Pow16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Exp( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Exp( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Exp16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Exp16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + tst = fabs( tst ) + 1.0f; + StartRecordTime( start ); + tst = idMath::Log( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Log( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + tst = fabs( tst ) + 1.0f; + StartRecordTime( start ); + tst = idMath::Log16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Log16( tst )", 1, bestClocks ); + + idLib::common->Printf( "testvar = %f\n", testvar ); + + idMat3 resultMat3; + idQuat fromQuat, toQuat, resultQuat; + idCQuat cq; + idAngles ang; + + fromQuat = idAngles( 30, 45, 0 ).ToQuat(); + toQuat = idAngles( 45, 0, 0 ).ToQuat(); + cq = idAngles( 30, 45, 0 ).ToQuat().ToCQuat(); + ang = idAngles( 30, 40, 50 ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultMat3 = fromQuat.ToMat3(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idQuat::ToMat3()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat.Slerp( fromQuat, toQuat, 0.3f ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idQuat::Slerp()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat = cq.ToQuat(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idCQuat::ToQuat()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat = ang.ToQuat(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idAngles::ToQuat()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultMat3 = ang.ToMat3(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idAngles::ToMat3()", 1, bestClocks ); +} + +/* +============ +idSIMD::Test_f +============ +*/ +void idSIMD::Test_f( const idCmdArgs &args ) { + + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); + + p_simd = processor; + p_generic = generic; + + if ( idStr::Length( args.Argv( 1 ) ) != 0 ) { + cpuid_t cpuid = idLib::sys->GetProcessorId(); + idStr argString = args.Args(); + + argString.Replace( " ", "" ); + + if ( idStr::Icmp( argString, "SSE" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) ) { + common->Printf( "CPU does not support MMX & SSE\n" ); + return; + } + p_simd = new (TAG_MATH) idSIMD_SSE; + } else { + common->Printf( "invalid argument, use: MMX, 3DNow, SSE, SSE2, SSE3, AltiVec\n" ); + return; + } + } + + idLib::common->SetRefreshOnPrint( true ); + + idLib::common->Printf( "using %s for SIMD processing\n", p_simd->GetName() ); + + GetBaseClocks(); + + TestMath(); + TestMinMax(); + TestMemcpy(); + TestMemset(); + + idLib::common->Printf("====================================\n" ); + + TestBlendJoints(); + TestBlendJointsFast(); + TestConvertJointQuatsToJointMats(); + TestConvertJointMatsToJointQuats(); + TestTransformJoints(); + TestUntransformJoints(); + + idLib::common->Printf("====================================\n" ); + + idLib::common->SetRefreshOnPrint( false ); + + if ( p_simd != processor ) { + delete p_simd; + } + p_simd = NULL; + p_generic = NULL; + + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL ); +} diff --git a/neo/idlib/math/Simd.h b/neo/idlib/math/Simd.h new file mode 100644 index 00000000..903af8e6 --- /dev/null +++ b/neo/idlib/math/Simd.h @@ -0,0 +1,109 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_H__ +#define __MATH_SIMD_H__ + +/* +=============================================================================== + + Single Instruction Multiple Data (SIMD) + + For optimal use data should be aligned on a 16 byte boundary. + All idSIMDProcessor routines are thread safe. + +=============================================================================== +*/ + +class idSIMD { +public: + static void Init(); + static void InitProcessor( const char *module, bool forceGeneric ); + static void Shutdown(); + static void Test_f( const class idCmdArgs &args ); +}; + + +/* +=============================================================================== + + virtual base class for different SIMD processors + +=============================================================================== +*/ + +#define VPCALL __fastcall + +class idVec2; +class idVec3; +class idVec4; +class idVec5; +class idVec6; +class idVecX; +class idMat2; +class idMat3; +class idMat4; +class idMat5; +class idMat6; +class idMatX; +class idPlane; +class idDrawVert; +class idJointQuat; +class idJointMat; +struct dominantTri_t; + +class idSIMDProcessor { +public: + idSIMDProcessor() { cpuid = CPUID_NONE; } + + cpuid_t cpuid; + + virtual const char * VPCALL GetName() const = 0; + + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const triIndex_t *indexes, const int count ) = 0; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ) = 0; + virtual void VPCALL Memset( void *dst, const int val, const int count ) = 0; + + // animation + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) = 0; + virtual void VPCALL BlendJointsFast( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) = 0; + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) = 0; + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) = 0; + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) = 0; + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) = 0; +}; + +// pointer to SIMD processor +extern idSIMDProcessor *SIMDProcessor; + +#endif /* !__MATH_SIMD_H__ */ diff --git a/neo/idlib/math/Simd_Generic.cpp b/neo/idlib/math/Simd_Generic.cpp new file mode 100644 index 00000000..33aafeef --- /dev/null +++ b/neo/idlib/math/Simd_Generic.cpp @@ -0,0 +1,211 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" +#include "Simd_Generic.h" + +//=============================================================== +// +// Generic implementation of idSIMDProcessor +// +//=============================================================== + +#define UNROLL1(Y) { int _IX; for (_IX=0;_IX max ) {max = src[(X)];} + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) { + min[0] = min[1] = idMath::INFINITY; max[0] = max[1] = -idMath::INFINITY; +#define OPER(X) const idVec2 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)].xyz; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const triIndex_t *indexes, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[indexes[(X)]].xyz; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +================ +idSIMD_Generic::Memcpy +================ +*/ +void VPCALL idSIMD_Generic::Memcpy( void *dst, const void *src, const int count ) { + memcpy( dst, src, count ); +} + +/* +================ +idSIMD_Generic::Memset +================ +*/ +void VPCALL idSIMD_Generic::Memset( void *dst, const int val, const int count ) { + memset( dst, val, count ); +} + +/* +============ +idSIMD_Generic::BlendJoints +============ +*/ +void VPCALL idSIMD_Generic::BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { + for ( int i = 0; i < numJoints; i++ ) { + int j = index[i]; + joints[j].q.Slerp( joints[j].q, blendJoints[j].q, lerp ); + joints[j].t.Lerp( joints[j].t, blendJoints[j].t, lerp ); + joints[j].w = 0.0f; + } +} + +/* +============ +idSIMD_Generic::BlendJointsFast +============ +*/ +void VPCALL idSIMD_Generic::BlendJointsFast( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { + for ( int i = 0; i < numJoints; i++ ) { + int j = index[i]; + joints[j].q.Lerp( joints[j].q, blendJoints[j].q, lerp ); + joints[j].t.Lerp( joints[j].t, blendJoints[j].t, lerp ); + joints[j].w = 0.0f; + } +} + +/* +============ +idSIMD_Generic::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_Generic::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { + for ( int i = 0; i < numJoints; i++ ) { + jointMats[i].SetRotation( jointQuats[i].q.ToMat3() ); + jointMats[i].SetTranslation( jointQuats[i].t ); + } +} + +/* +============ +idSIMD_Generic::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_Generic::ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) { + for ( int i = 0; i < numJoints; i++ ) { + jointQuats[i] = jointMats[i].ToJointQuat(); + } +} + +/* +============ +idSIMD_Generic::TransformJoints +============ +*/ +void VPCALL idSIMD_Generic::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + for ( int i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } +} + +/* +============ +idSIMD_Generic::UntransformJoints +============ +*/ +void VPCALL idSIMD_Generic::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + for ( int i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } +} diff --git a/neo/idlib/math/Simd_Generic.h b/neo/idlib/math/Simd_Generic.h new file mode 100644 index 00000000..8b168cb4 --- /dev/null +++ b/neo/idlib/math/Simd_Generic.h @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_GENERIC_H__ +#define __MATH_SIMD_GENERIC_H__ + +/* +=============================================================================== + + Generic implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_Generic : public idSIMDProcessor { +public: + virtual const char * VPCALL GetName() const; + + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ); + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const triIndex_t *indexes, const int count ); + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); + virtual void VPCALL Memset( void *dst, const int val, const int count ); + + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL BlendJointsFast( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ); + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); +}; + +#endif /* !__MATH_SIMD_GENERIC_H__ */ diff --git a/neo/idlib/math/Simd_SSE.cpp b/neo/idlib/math/Simd_SSE.cpp new file mode 100644 index 00000000..3967303c --- /dev/null +++ b/neo/idlib/math/Simd_SSE.cpp @@ -0,0 +1,934 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" +#include "Simd_Generic.h" +#include "Simd_SSE.h" + +//=============================================================== +// M +// SSE implementation of idSIMDProcessor MrE +// E +//=============================================================== + + +#include + +#define M_PI 3.14159265358979323846f + +/* +============ +idSIMD_SSE::GetName +============ +*/ +const char * idSIMD_SSE::GetName() const { + return "MMX & SSE"; +} + +/* +============ +idSIMD_SSE::BlendJoints +============ +*/ +void VPCALL idSIMD_SSE::BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { + + if ( lerp <= 0.0f ) { + return; + } else if ( lerp >= 1.0f ) { + for ( int i = 0; i < numJoints; i++ ) { + int j = index[i]; + joints[j] = blendJoints[j]; + } + return; + } + + const __m128 vlerp = { lerp, lerp, lerp, lerp }; + + const __m128 vector_float_one = { 1.0f, 1.0f, 1.0f, 1.0f }; + const __m128 vector_float_sign_bit = __m128c( _mm_set_epi32( 0x80000000, 0x80000000, 0x80000000, 0x80000000 ) ); + const __m128 vector_float_rsqrt_c0 = { -3.0f, -3.0f, -3.0f, -3.0f }; + const __m128 vector_float_rsqrt_c1 = { -0.5f, -0.5f, -0.5f, -0.5f }; + const __m128 vector_float_tiny = { 1e-10f, 1e-10f, 1e-10f, 1e-10f }; + const __m128 vector_float_half_pi = { M_PI*0.5f, M_PI*0.5f, M_PI*0.5f, M_PI*0.5f }; + + const __m128 vector_float_sin_c0 = { -2.39e-08f, -2.39e-08f, -2.39e-08f, -2.39e-08f }; + const __m128 vector_float_sin_c1 = { 2.7526e-06f, 2.7526e-06f, 2.7526e-06f, 2.7526e-06f }; + const __m128 vector_float_sin_c2 = { -1.98409e-04f, -1.98409e-04f, -1.98409e-04f, -1.98409e-04f }; + const __m128 vector_float_sin_c3 = { 8.3333315e-03f, 8.3333315e-03f, 8.3333315e-03f, 8.3333315e-03f }; + const __m128 vector_float_sin_c4 = { -1.666666664e-01f, -1.666666664e-01f, -1.666666664e-01f, -1.666666664e-01f }; + + const __m128 vector_float_atan_c0 = { 0.0028662257f, 0.0028662257f, 0.0028662257f, 0.0028662257f }; + const __m128 vector_float_atan_c1 = { -0.0161657367f, -0.0161657367f, -0.0161657367f, -0.0161657367f }; + const __m128 vector_float_atan_c2 = { 0.0429096138f, 0.0429096138f, 0.0429096138f, 0.0429096138f }; + const __m128 vector_float_atan_c3 = { -0.0752896400f, -0.0752896400f, -0.0752896400f, -0.0752896400f }; + const __m128 vector_float_atan_c4 = { 0.1065626393f, 0.1065626393f, 0.1065626393f, 0.1065626393f }; + const __m128 vector_float_atan_c5 = { -0.1420889944f, -0.1420889944f, -0.1420889944f, -0.1420889944f }; + const __m128 vector_float_atan_c6 = { 0.1999355085f, 0.1999355085f, 0.1999355085f, 0.1999355085f }; + const __m128 vector_float_atan_c7 = { -0.3333314528f, -0.3333314528f, -0.3333314528f, -0.3333314528f }; + + int i = 0; + for ( ; i < numJoints - 3; i += 4 ) { + const int n0 = index[i+0]; + const int n1 = index[i+1]; + const int n2 = index[i+2]; + const int n3 = index[i+3]; + + __m128 jqa_0 = _mm_load_ps( joints[n0].q.ToFloatPtr() ); + __m128 jqb_0 = _mm_load_ps( joints[n1].q.ToFloatPtr() ); + __m128 jqc_0 = _mm_load_ps( joints[n2].q.ToFloatPtr() ); + __m128 jqd_0 = _mm_load_ps( joints[n3].q.ToFloatPtr() ); + + __m128 jta_0 = _mm_load_ps( joints[n0].t.ToFloatPtr() ); + __m128 jtb_0 = _mm_load_ps( joints[n1].t.ToFloatPtr() ); + __m128 jtc_0 = _mm_load_ps( joints[n2].t.ToFloatPtr() ); + __m128 jtd_0 = _mm_load_ps( joints[n3].t.ToFloatPtr() ); + + __m128 bqa_0 = _mm_load_ps( blendJoints[n0].q.ToFloatPtr() ); + __m128 bqb_0 = _mm_load_ps( blendJoints[n1].q.ToFloatPtr() ); + __m128 bqc_0 = _mm_load_ps( blendJoints[n2].q.ToFloatPtr() ); + __m128 bqd_0 = _mm_load_ps( blendJoints[n3].q.ToFloatPtr() ); + + __m128 bta_0 = _mm_load_ps( blendJoints[n0].t.ToFloatPtr() ); + __m128 btb_0 = _mm_load_ps( blendJoints[n1].t.ToFloatPtr() ); + __m128 btc_0 = _mm_load_ps( blendJoints[n2].t.ToFloatPtr() ); + __m128 btd_0 = _mm_load_ps( blendJoints[n3].t.ToFloatPtr() ); + + bta_0 = _mm_sub_ps( bta_0, jta_0 ); + btb_0 = _mm_sub_ps( btb_0, jtb_0 ); + btc_0 = _mm_sub_ps( btc_0, jtc_0 ); + btd_0 = _mm_sub_ps( btd_0, jtd_0 ); + + jta_0 = _mm_madd_ps( vlerp, bta_0, jta_0 ); + jtb_0 = _mm_madd_ps( vlerp, btb_0, jtb_0 ); + jtc_0 = _mm_madd_ps( vlerp, btc_0, jtc_0 ); + jtd_0 = _mm_madd_ps( vlerp, btd_0, jtd_0 ); + + _mm_store_ps( joints[n0].t.ToFloatPtr(), jta_0 ); + _mm_store_ps( joints[n1].t.ToFloatPtr(), jtb_0 ); + _mm_store_ps( joints[n2].t.ToFloatPtr(), jtc_0 ); + _mm_store_ps( joints[n3].t.ToFloatPtr(), jtd_0 ); + + __m128 jqr_0 = _mm_unpacklo_ps( jqa_0, jqc_0 ); + __m128 jqs_0 = _mm_unpackhi_ps( jqa_0, jqc_0 ); + __m128 jqt_0 = _mm_unpacklo_ps( jqb_0, jqd_0 ); + __m128 jqu_0 = _mm_unpackhi_ps( jqb_0, jqd_0 ); + + __m128 bqr_0 = _mm_unpacklo_ps( bqa_0, bqc_0 ); + __m128 bqs_0 = _mm_unpackhi_ps( bqa_0, bqc_0 ); + __m128 bqt_0 = _mm_unpacklo_ps( bqb_0, bqd_0 ); + __m128 bqu_0 = _mm_unpackhi_ps( bqb_0, bqd_0 ); + + __m128 jqx_0 = _mm_unpacklo_ps( jqr_0, jqt_0 ); + __m128 jqy_0 = _mm_unpackhi_ps( jqr_0, jqt_0 ); + __m128 jqz_0 = _mm_unpacklo_ps( jqs_0, jqu_0 ); + __m128 jqw_0 = _mm_unpackhi_ps( jqs_0, jqu_0 ); + + __m128 bqx_0 = _mm_unpacklo_ps( bqr_0, bqt_0 ); + __m128 bqy_0 = _mm_unpackhi_ps( bqr_0, bqt_0 ); + __m128 bqz_0 = _mm_unpacklo_ps( bqs_0, bqu_0 ); + __m128 bqw_0 = _mm_unpackhi_ps( bqs_0, bqu_0 ); + + __m128 cosoma_0 = _mm_mul_ps( jqx_0, bqx_0 ); + __m128 cosomb_0 = _mm_mul_ps( jqy_0, bqy_0 ); + __m128 cosomc_0 = _mm_mul_ps( jqz_0, bqz_0 ); + __m128 cosomd_0 = _mm_mul_ps( jqw_0, bqw_0 ); + + __m128 cosome_0 = _mm_add_ps( cosoma_0, cosomb_0 ); + __m128 cosomf_0 = _mm_add_ps( cosomc_0, cosomd_0 ); + __m128 cosomg_0 = _mm_add_ps( cosome_0, cosomf_0 ); + + __m128 sign_0 = _mm_and_ps( cosomg_0, vector_float_sign_bit ); + __m128 cosom_0 = _mm_xor_ps( cosomg_0, sign_0 ); + __m128 ss_0 = _mm_nmsub_ps( cosom_0, cosom_0, vector_float_one ); + + ss_0 = _mm_max_ps( ss_0, vector_float_tiny ); + + __m128 rs_0 = _mm_rsqrt_ps( ss_0 ); + __m128 sq_0 = _mm_mul_ps( rs_0, rs_0 ); + __m128 sh_0 = _mm_mul_ps( rs_0, vector_float_rsqrt_c1 ); + __m128 sx_0 = _mm_madd_ps( ss_0, sq_0, vector_float_rsqrt_c0 ); + __m128 sinom_0 = _mm_mul_ps( sh_0, sx_0 ); // sinom = sqrt( ss ); + + ss_0 = _mm_mul_ps( ss_0, sinom_0 ); + + __m128 min_0 = _mm_min_ps( ss_0, cosom_0 ); + __m128 max_0 = _mm_max_ps( ss_0, cosom_0 ); + __m128 mask_0 = _mm_cmpeq_ps( min_0, cosom_0 ); + __m128 masksign_0 = _mm_and_ps( mask_0, vector_float_sign_bit ); + __m128 maskPI_0 = _mm_and_ps( mask_0, vector_float_half_pi ); + + __m128 rcpa_0 = _mm_rcp_ps( max_0 ); + __m128 rcpb_0 = _mm_mul_ps( max_0, rcpa_0 ); + __m128 rcpd_0 = _mm_add_ps( rcpa_0, rcpa_0 ); + __m128 rcp_0 = _mm_nmsub_ps( rcpb_0, rcpa_0, rcpd_0 ); // 1 / y or 1 / x + __m128 ata_0 = _mm_mul_ps( min_0, rcp_0 ); // x / y or y / x + + __m128 atb_0 = _mm_xor_ps( ata_0, masksign_0 ); // -x / y or y / x + __m128 atc_0 = _mm_mul_ps( atb_0, atb_0 ); + __m128 atd_0 = _mm_madd_ps( atc_0, vector_float_atan_c0, vector_float_atan_c1 ); + + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_atan_c2 ); + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_atan_c3 ); + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_atan_c4 ); + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_atan_c5 ); + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_atan_c6 ); + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_atan_c7 ); + atd_0 = _mm_madd_ps( atd_0, atc_0, vector_float_one ); + + __m128 omega_a_0 = _mm_madd_ps( atd_0, atb_0, maskPI_0 ); + __m128 omega_b_0 = _mm_mul_ps( vlerp, omega_a_0 ); + omega_a_0 = _mm_sub_ps( omega_a_0, omega_b_0 ); + + __m128 sinsa_0 = _mm_mul_ps( omega_a_0, omega_a_0 ); + __m128 sinsb_0 = _mm_mul_ps( omega_b_0, omega_b_0 ); + __m128 sina_0 = _mm_madd_ps( sinsa_0, vector_float_sin_c0, vector_float_sin_c1 ); + __m128 sinb_0 = _mm_madd_ps( sinsb_0, vector_float_sin_c0, vector_float_sin_c1 ); + sina_0 = _mm_madd_ps( sina_0, sinsa_0, vector_float_sin_c2 ); + sinb_0 = _mm_madd_ps( sinb_0, sinsb_0, vector_float_sin_c2 ); + sina_0 = _mm_madd_ps( sina_0, sinsa_0, vector_float_sin_c3 ); + sinb_0 = _mm_madd_ps( sinb_0, sinsb_0, vector_float_sin_c3 ); + sina_0 = _mm_madd_ps( sina_0, sinsa_0, vector_float_sin_c4 ); + sinb_0 = _mm_madd_ps( sinb_0, sinsb_0, vector_float_sin_c4 ); + sina_0 = _mm_madd_ps( sina_0, sinsa_0, vector_float_one ); + sinb_0 = _mm_madd_ps( sinb_0, sinsb_0, vector_float_one ); + sina_0 = _mm_mul_ps( sina_0, omega_a_0 ); + sinb_0 = _mm_mul_ps( sinb_0, omega_b_0 ); + __m128 scalea_0 = _mm_mul_ps( sina_0, sinom_0 ); + __m128 scaleb_0 = _mm_mul_ps( sinb_0, sinom_0 ); + + scaleb_0 = _mm_xor_ps( scaleb_0, sign_0 ); + + jqx_0 = _mm_mul_ps( jqx_0, scalea_0 ); + jqy_0 = _mm_mul_ps( jqy_0, scalea_0 ); + jqz_0 = _mm_mul_ps( jqz_0, scalea_0 ); + jqw_0 = _mm_mul_ps( jqw_0, scalea_0 ); + + jqx_0 = _mm_madd_ps( bqx_0, scaleb_0, jqx_0 ); + jqy_0 = _mm_madd_ps( bqy_0, scaleb_0, jqy_0 ); + jqz_0 = _mm_madd_ps( bqz_0, scaleb_0, jqz_0 ); + jqw_0 = _mm_madd_ps( bqw_0, scaleb_0, jqw_0 ); + + __m128 tp0_0 = _mm_unpacklo_ps( jqx_0, jqz_0 ); + __m128 tp1_0 = _mm_unpackhi_ps( jqx_0, jqz_0 ); + __m128 tp2_0 = _mm_unpacklo_ps( jqy_0, jqw_0 ); + __m128 tp3_0 = _mm_unpackhi_ps( jqy_0, jqw_0 ); + + __m128 p0_0 = _mm_unpacklo_ps( tp0_0, tp2_0 ); + __m128 p1_0 = _mm_unpackhi_ps( tp0_0, tp2_0 ); + __m128 p2_0 = _mm_unpacklo_ps( tp1_0, tp3_0 ); + __m128 p3_0 = _mm_unpackhi_ps( tp1_0, tp3_0 ); + + _mm_store_ps( joints[n0].q.ToFloatPtr(), p0_0 ); + _mm_store_ps( joints[n1].q.ToFloatPtr(), p1_0 ); + _mm_store_ps( joints[n2].q.ToFloatPtr(), p2_0 ); + _mm_store_ps( joints[n3].q.ToFloatPtr(), p3_0 ); + } + + for ( ; i < numJoints; i++ ) { + int n = index[i]; + + idVec3 &jointVert = joints[n].t; + const idVec3 &blendVert = blendJoints[n].t; + + jointVert[0] += lerp * ( blendVert[0] - jointVert[0] ); + jointVert[1] += lerp * ( blendVert[1] - jointVert[1] ); + jointVert[2] += lerp * ( blendVert[2] - jointVert[2] ); + joints[n].w = 0.0f; + + idQuat &jointQuat = joints[n].q; + const idQuat &blendQuat = blendJoints[n].q; + + float cosom; + float sinom; + float omega; + float scale0; + float scale1; + unsigned long signBit; + + cosom = jointQuat.x * blendQuat.x + jointQuat.y * blendQuat.y + jointQuat.z * blendQuat.z + jointQuat.w * blendQuat.w; + + signBit = (*(unsigned long *)&cosom) & ( 1 << 31 ); + + (*(unsigned long *)&cosom) ^= signBit; + + scale0 = 1.0f - cosom * cosom; + scale0 = ( scale0 <= 0.0f ) ? 1e-10f : scale0; + sinom = idMath::InvSqrt( scale0 ); + omega = idMath::ATan16( scale0 * sinom, cosom ); + scale0 = idMath::Sin16( ( 1.0f - lerp ) * omega ) * sinom; + scale1 = idMath::Sin16( lerp * omega ) * sinom; + + (*(unsigned long *)&scale1) ^= signBit; + + jointQuat.x = scale0 * jointQuat.x + scale1 * blendQuat.x; + jointQuat.y = scale0 * jointQuat.y + scale1 * blendQuat.y; + jointQuat.z = scale0 * jointQuat.z + scale1 * blendQuat.z; + jointQuat.w = scale0 * jointQuat.w + scale1 * blendQuat.w; + } +} + +/* +============ +idSIMD_SSE::BlendJointsFast +============ +*/ +void VPCALL idSIMD_SSE::BlendJointsFast( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( blendJoints ); + assert_16_byte_aligned( JOINTQUAT_Q_OFFSET ); + assert_16_byte_aligned( JOINTQUAT_T_OFFSET ); + assert_sizeof_16_byte_multiple( idJointQuat ); + + if ( lerp <= 0.0f ) { + return; + } else if ( lerp >= 1.0f ) { + for ( int i = 0; i < numJoints; i++ ) { + int j = index[i]; + joints[j] = blendJoints[j]; + } + return; + } + + const __m128 vector_float_sign_bit = __m128c( _mm_set_epi32( 0x80000000, 0x80000000, 0x80000000, 0x80000000 ) ); + const __m128 vector_float_rsqrt_c0 = { -3.0f, -3.0f, -3.0f, -3.0f }; + const __m128 vector_float_rsqrt_c1 = { -0.5f, -0.5f, -0.5f, -0.5f }; + + const float scaledLerp = lerp / ( 1.0f - lerp ); + const __m128 vlerp = { lerp, lerp, lerp, lerp }; + const __m128 vscaledLerp = { scaledLerp, scaledLerp, scaledLerp, scaledLerp }; + + int i = 0; + for ( ; i < numJoints - 3; i += 4 ) { + const int n0 = index[i+0]; + const int n1 = index[i+1]; + const int n2 = index[i+2]; + const int n3 = index[i+3]; + + __m128 jqa_0 = _mm_load_ps( joints[n0].q.ToFloatPtr() ); + __m128 jqb_0 = _mm_load_ps( joints[n1].q.ToFloatPtr() ); + __m128 jqc_0 = _mm_load_ps( joints[n2].q.ToFloatPtr() ); + __m128 jqd_0 = _mm_load_ps( joints[n3].q.ToFloatPtr() ); + + __m128 jta_0 = _mm_load_ps( joints[n0].t.ToFloatPtr() ); + __m128 jtb_0 = _mm_load_ps( joints[n1].t.ToFloatPtr() ); + __m128 jtc_0 = _mm_load_ps( joints[n2].t.ToFloatPtr() ); + __m128 jtd_0 = _mm_load_ps( joints[n3].t.ToFloatPtr() ); + + __m128 bqa_0 = _mm_load_ps( blendJoints[n0].q.ToFloatPtr() ); + __m128 bqb_0 = _mm_load_ps( blendJoints[n1].q.ToFloatPtr() ); + __m128 bqc_0 = _mm_load_ps( blendJoints[n2].q.ToFloatPtr() ); + __m128 bqd_0 = _mm_load_ps( blendJoints[n3].q.ToFloatPtr() ); + + __m128 bta_0 = _mm_load_ps( blendJoints[n0].t.ToFloatPtr() ); + __m128 btb_0 = _mm_load_ps( blendJoints[n1].t.ToFloatPtr() ); + __m128 btc_0 = _mm_load_ps( blendJoints[n2].t.ToFloatPtr() ); + __m128 btd_0 = _mm_load_ps( blendJoints[n3].t.ToFloatPtr() ); + + bta_0 = _mm_sub_ps( bta_0, jta_0 ); + btb_0 = _mm_sub_ps( btb_0, jtb_0 ); + btc_0 = _mm_sub_ps( btc_0, jtc_0 ); + btd_0 = _mm_sub_ps( btd_0, jtd_0 ); + + jta_0 = _mm_madd_ps( vlerp, bta_0, jta_0 ); + jtb_0 = _mm_madd_ps( vlerp, btb_0, jtb_0 ); + jtc_0 = _mm_madd_ps( vlerp, btc_0, jtc_0 ); + jtd_0 = _mm_madd_ps( vlerp, btd_0, jtd_0 ); + + _mm_store_ps( joints[n0].t.ToFloatPtr(), jta_0 ); + _mm_store_ps( joints[n1].t.ToFloatPtr(), jtb_0 ); + _mm_store_ps( joints[n2].t.ToFloatPtr(), jtc_0 ); + _mm_store_ps( joints[n3].t.ToFloatPtr(), jtd_0 ); + + __m128 jqr_0 = _mm_unpacklo_ps( jqa_0, jqc_0 ); + __m128 jqs_0 = _mm_unpackhi_ps( jqa_0, jqc_0 ); + __m128 jqt_0 = _mm_unpacklo_ps( jqb_0, jqd_0 ); + __m128 jqu_0 = _mm_unpackhi_ps( jqb_0, jqd_0 ); + + __m128 bqr_0 = _mm_unpacklo_ps( bqa_0, bqc_0 ); + __m128 bqs_0 = _mm_unpackhi_ps( bqa_0, bqc_0 ); + __m128 bqt_0 = _mm_unpacklo_ps( bqb_0, bqd_0 ); + __m128 bqu_0 = _mm_unpackhi_ps( bqb_0, bqd_0 ); + + __m128 jqx_0 = _mm_unpacklo_ps( jqr_0, jqt_0 ); + __m128 jqy_0 = _mm_unpackhi_ps( jqr_0, jqt_0 ); + __m128 jqz_0 = _mm_unpacklo_ps( jqs_0, jqu_0 ); + __m128 jqw_0 = _mm_unpackhi_ps( jqs_0, jqu_0 ); + + __m128 bqx_0 = _mm_unpacklo_ps( bqr_0, bqt_0 ); + __m128 bqy_0 = _mm_unpackhi_ps( bqr_0, bqt_0 ); + __m128 bqz_0 = _mm_unpacklo_ps( bqs_0, bqu_0 ); + __m128 bqw_0 = _mm_unpackhi_ps( bqs_0, bqu_0 ); + + __m128 cosoma_0 = _mm_mul_ps( jqx_0, bqx_0 ); + __m128 cosomb_0 = _mm_mul_ps( jqy_0, bqy_0 ); + __m128 cosomc_0 = _mm_mul_ps( jqz_0, bqz_0 ); + __m128 cosomd_0 = _mm_mul_ps( jqw_0, bqw_0 ); + + __m128 cosome_0 = _mm_add_ps( cosoma_0, cosomb_0 ); + __m128 cosomf_0 = _mm_add_ps( cosomc_0, cosomd_0 ); + __m128 cosom_0 = _mm_add_ps( cosome_0, cosomf_0 ); + + __m128 sign_0 = _mm_and_ps( cosom_0, vector_float_sign_bit ); + + __m128 scale_0 = _mm_xor_ps( vscaledLerp, sign_0 ); + + jqx_0 = _mm_madd_ps( scale_0, bqx_0, jqx_0 ); + jqy_0 = _mm_madd_ps( scale_0, bqy_0, jqy_0 ); + jqz_0 = _mm_madd_ps( scale_0, bqz_0, jqz_0 ); + jqw_0 = _mm_madd_ps( scale_0, bqw_0, jqw_0 ); + + __m128 da_0 = _mm_mul_ps( jqx_0, jqx_0 ); + __m128 db_0 = _mm_mul_ps( jqy_0, jqy_0 ); + __m128 dc_0 = _mm_mul_ps( jqz_0, jqz_0 ); + __m128 dd_0 = _mm_mul_ps( jqw_0, jqw_0 ); + + __m128 de_0 = _mm_add_ps( da_0, db_0 ); + __m128 df_0 = _mm_add_ps( dc_0, dd_0 ); + __m128 d_0 = _mm_add_ps( de_0, df_0 ); + + __m128 rs_0 = _mm_rsqrt_ps( d_0 ); + __m128 sq_0 = _mm_mul_ps( rs_0, rs_0 ); + __m128 sh_0 = _mm_mul_ps( rs_0, vector_float_rsqrt_c1 ); + __m128 sx_0 = _mm_madd_ps( d_0, sq_0, vector_float_rsqrt_c0 ); + __m128 s_0 = _mm_mul_ps( sh_0, sx_0 ); + + jqx_0 = _mm_mul_ps( jqx_0, s_0 ); + jqy_0 = _mm_mul_ps( jqy_0, s_0 ); + jqz_0 = _mm_mul_ps( jqz_0, s_0 ); + jqw_0 = _mm_mul_ps( jqw_0, s_0 ); + + __m128 tp0_0 = _mm_unpacklo_ps( jqx_0, jqz_0 ); + __m128 tp1_0 = _mm_unpackhi_ps( jqx_0, jqz_0 ); + __m128 tp2_0 = _mm_unpacklo_ps( jqy_0, jqw_0 ); + __m128 tp3_0 = _mm_unpackhi_ps( jqy_0, jqw_0 ); + + __m128 p0_0 = _mm_unpacklo_ps( tp0_0, tp2_0 ); + __m128 p1_0 = _mm_unpackhi_ps( tp0_0, tp2_0 ); + __m128 p2_0 = _mm_unpacklo_ps( tp1_0, tp3_0 ); + __m128 p3_0 = _mm_unpackhi_ps( tp1_0, tp3_0 ); + + _mm_store_ps( joints[n0].q.ToFloatPtr(), p0_0 ); + _mm_store_ps( joints[n1].q.ToFloatPtr(), p1_0 ); + _mm_store_ps( joints[n2].q.ToFloatPtr(), p2_0 ); + _mm_store_ps( joints[n3].q.ToFloatPtr(), p3_0 ); + } + + for ( ; i < numJoints; i++ ) { + const int n = index[i]; + + idVec3 &jointVert = joints[n].t; + const idVec3 &blendVert = blendJoints[n].t; + + jointVert[0] += lerp * ( blendVert[0] - jointVert[0] ); + jointVert[1] += lerp * ( blendVert[1] - jointVert[1] ); + jointVert[2] += lerp * ( blendVert[2] - jointVert[2] ); + + idQuat &jointQuat = joints[n].q; + const idQuat &blendQuat = blendJoints[n].q; + + float cosom; + float scale; + float s; + + cosom = jointQuat.x * blendQuat.x + jointQuat.y * blendQuat.y + jointQuat.z * blendQuat.z + jointQuat.w * blendQuat.w; + + scale = __fsels( cosom, scaledLerp, -scaledLerp ); + + jointQuat.x += scale * blendQuat.x; + jointQuat.y += scale * blendQuat.y; + jointQuat.z += scale * blendQuat.z; + jointQuat.w += scale * blendQuat.w; + + s = jointQuat.x * jointQuat.x + jointQuat.y * jointQuat.y + jointQuat.z * jointQuat.z + jointQuat.w * jointQuat.w; + s = __frsqrts( s ); + + jointQuat.x *= s; + jointQuat.y *= s; + jointQuat.z *= s; + jointQuat.w *= s; + } +} + +/* +============ +idSIMD_SSE::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_SSE::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { + assert( sizeof( idJointQuat ) == JOINTQUAT_SIZE ); + assert( sizeof( idJointMat ) == JOINTMAT_SIZE ); + assert( (int)(&((idJointQuat *)0)->t) == (int)(&((idJointQuat *)0)->q) + (int)sizeof( ((idJointQuat *)0)->q ) ); + + const float * jointQuatPtr = (float *)jointQuats; + float * jointMatPtr = (float *)jointMats; + + const __m128 vector_float_first_sign_bit = __m128c( _mm_set_epi32( 0x00000000, 0x00000000, 0x00000000, 0x80000000 ) ); + const __m128 vector_float_last_three_sign_bits = __m128c( _mm_set_epi32( 0x80000000, 0x80000000, 0x80000000, 0x00000000 ) ); + const __m128 vector_float_first_pos_half = { 0.5f, 0.0f, 0.0f, 0.0f }; // +.5 0 0 0 + const __m128 vector_float_first_neg_half = { -0.5f, 0.0f, 0.0f, 0.0f }; // -.5 0 0 0 + const __m128 vector_float_quat2mat_mad1 = { -1.0f, -1.0f, +1.0f, -1.0f }; // - - + - + const __m128 vector_float_quat2mat_mad2 = { -1.0f, +1.0f, -1.0f, -1.0f }; // - + - - + const __m128 vector_float_quat2mat_mad3 = { +1.0f, -1.0f, -1.0f, +1.0f }; // + - - + + + int i = 0; + for ( ; i + 1 < numJoints; i += 2 ) { + + __m128 q0 = _mm_load_ps( &jointQuatPtr[i*8+0*8+0] ); + __m128 q1 = _mm_load_ps( &jointQuatPtr[i*8+1*8+0] ); + + __m128 t0 = _mm_load_ps( &jointQuatPtr[i*8+0*8+4] ); + __m128 t1 = _mm_load_ps( &jointQuatPtr[i*8+1*8+4] ); + + __m128 d0 = _mm_add_ps( q0, q0 ); + __m128 d1 = _mm_add_ps( q1, q1 ); + + __m128 sa0 = _mm_perm_ps( q0, _MM_SHUFFLE( 1, 0, 0, 1 ) ); // y, x, x, y + __m128 sb0 = _mm_perm_ps( d0, _MM_SHUFFLE( 2, 2, 1, 1 ) ); // y2, y2, z2, z2 + __m128 sc0 = _mm_perm_ps( q0, _MM_SHUFFLE( 3, 3, 3, 2 ) ); // z, w, w, w + __m128 sd0 = _mm_perm_ps( d0, _MM_SHUFFLE( 0, 1, 2, 2 ) ); // z2, z2, y2, x2 + __m128 sa1 = _mm_perm_ps( q1, _MM_SHUFFLE( 1, 0, 0, 1 ) ); // y, x, x, y + __m128 sb1 = _mm_perm_ps( d1, _MM_SHUFFLE( 2, 2, 1, 1 ) ); // y2, y2, z2, z2 + __m128 sc1 = _mm_perm_ps( q1, _MM_SHUFFLE( 3, 3, 3, 2 ) ); // z, w, w, w + __m128 sd1 = _mm_perm_ps( d1, _MM_SHUFFLE( 0, 1, 2, 2 ) ); // z2, z2, y2, x2 + + sa0 = _mm_xor_ps( sa0, vector_float_first_sign_bit ); + sa1 = _mm_xor_ps( sa1, vector_float_first_sign_bit ); + + sc0 = _mm_xor_ps( sc0, vector_float_last_three_sign_bits ); // flip stupid inverse quaternions + sc1 = _mm_xor_ps( sc1, vector_float_last_three_sign_bits ); // flip stupid inverse quaternions + + __m128 ma0 = _mm_add_ps( _mm_mul_ps( sa0, sb0 ), vector_float_first_pos_half ); // .5 - yy2, xy2, xz2, yz2 // .5 0 0 0 + __m128 mb0 = _mm_add_ps( _mm_mul_ps( sc0, sd0 ), vector_float_first_neg_half ); // -.5 + zz2, wz2, wy2, wx2 // -.5 0 0 0 + __m128 mc0 = _mm_sub_ps( vector_float_first_pos_half, _mm_mul_ps( q0, d0 ) ); // .5 - xx2, -yy2, -zz2, -ww2 // .5 0 0 0 + __m128 ma1 = _mm_add_ps( _mm_mul_ps( sa1, sb1 ), vector_float_first_pos_half ); // .5 - yy2, xy2, xz2, yz2 // .5 0 0 0 + __m128 mb1 = _mm_add_ps( _mm_mul_ps( sc1, sd1 ), vector_float_first_neg_half ); // -.5 + zz2, wz2, wy2, wx2 // -.5 0 0 0 + __m128 mc1 = _mm_sub_ps( vector_float_first_pos_half, _mm_mul_ps( q1, d1 ) ); // .5 - xx2, -yy2, -zz2, -ww2 // .5 0 0 0 + + __m128 mf0 = _mm_shuffle_ps( ma0, mc0, _MM_SHUFFLE( 0, 0, 1, 1 ) ); // xy2, xy2, .5 - xx2, .5 - xx2 // 01, 01, 10, 10 + __m128 md0 = _mm_shuffle_ps( mf0, ma0, _MM_SHUFFLE( 3, 2, 0, 2 ) ); // .5 - xx2, xy2, xz2, yz2 // 10, 01, 02, 03 + __m128 me0 = _mm_shuffle_ps( ma0, mb0, _MM_SHUFFLE( 3, 2, 1, 0 ) ); // .5 - yy2, xy2, wy2, wx2 // 00, 01, 12, 13 + __m128 mf1 = _mm_shuffle_ps( ma1, mc1, _MM_SHUFFLE( 0, 0, 1, 1 ) ); // xy2, xy2, .5 - xx2, .5 - xx2 // 01, 01, 10, 10 + __m128 md1 = _mm_shuffle_ps( mf1, ma1, _MM_SHUFFLE( 3, 2, 0, 2 ) ); // .5 - xx2, xy2, xz2, yz2 // 10, 01, 02, 03 + __m128 me1 = _mm_shuffle_ps( ma1, mb1, _MM_SHUFFLE( 3, 2, 1, 0 ) ); // .5 - yy2, xy2, wy2, wx2 // 00, 01, 12, 13 + + __m128 ra0 = _mm_add_ps( _mm_mul_ps( mb0, vector_float_quat2mat_mad1 ), ma0 ); // 1 - yy2 - zz2, xy2 - wz2, xz2 + wy2, // - - + - + __m128 rb0 = _mm_add_ps( _mm_mul_ps( mb0, vector_float_quat2mat_mad2 ), md0 ); // 1 - xx2 - zz2, xy2 + wz2, , yz2 - wx2 // - + - - + __m128 rc0 = _mm_add_ps( _mm_mul_ps( me0, vector_float_quat2mat_mad3 ), md0 ); // 1 - xx2 - yy2, , xz2 - wy2, yz2 + wx2 // + - - + + __m128 ra1 = _mm_add_ps( _mm_mul_ps( mb1, vector_float_quat2mat_mad1 ), ma1 ); // 1 - yy2 - zz2, xy2 - wz2, xz2 + wy2, // - - + - + __m128 rb1 = _mm_add_ps( _mm_mul_ps( mb1, vector_float_quat2mat_mad2 ), md1 ); // 1 - xx2 - zz2, xy2 + wz2, , yz2 - wx2 // - + - - + __m128 rc1 = _mm_add_ps( _mm_mul_ps( me1, vector_float_quat2mat_mad3 ), md1 ); // 1 - xx2 - yy2, , xz2 - wy2, yz2 + wx2 // + - - + + + __m128 ta0 = _mm_shuffle_ps( ra0, t0, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128 tb0 = _mm_shuffle_ps( rb0, t0, _MM_SHUFFLE( 1, 1, 3, 3 ) ); + __m128 tc0 = _mm_shuffle_ps( rc0, t0, _MM_SHUFFLE( 2, 2, 0, 0 ) ); + __m128 ta1 = _mm_shuffle_ps( ra1, t1, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128 tb1 = _mm_shuffle_ps( rb1, t1, _MM_SHUFFLE( 1, 1, 3, 3 ) ); + __m128 tc1 = _mm_shuffle_ps( rc1, t1, _MM_SHUFFLE( 2, 2, 0, 0 ) ); + + ra0 = _mm_shuffle_ps( ra0, ta0, _MM_SHUFFLE( 2, 0, 1, 0 ) ); // 00 01 02 10 + rb0 = _mm_shuffle_ps( rb0, tb0, _MM_SHUFFLE( 2, 0, 0, 1 ) ); // 01 00 03 11 + rc0 = _mm_shuffle_ps( rc0, tc0, _MM_SHUFFLE( 2, 0, 3, 2 ) ); // 02 03 00 12 + ra1 = _mm_shuffle_ps( ra1, ta1, _MM_SHUFFLE( 2, 0, 1, 0 ) ); // 00 01 02 10 + rb1 = _mm_shuffle_ps( rb1, tb1, _MM_SHUFFLE( 2, 0, 0, 1 ) ); // 01 00 03 11 + rc1 = _mm_shuffle_ps( rc1, tc1, _MM_SHUFFLE( 2, 0, 3, 2 ) ); // 02 03 00 12 + + _mm_store_ps( &jointMatPtr[i*12+0*12+0], ra0 ); + _mm_store_ps( &jointMatPtr[i*12+0*12+4], rb0 ); + _mm_store_ps( &jointMatPtr[i*12+0*12+8], rc0 ); + _mm_store_ps( &jointMatPtr[i*12+1*12+0], ra1 ); + _mm_store_ps( &jointMatPtr[i*12+1*12+4], rb1 ); + _mm_store_ps( &jointMatPtr[i*12+1*12+8], rc1 ); + } + + for ( ; i < numJoints; i++ ) { + + __m128 q0 = _mm_load_ps( &jointQuatPtr[i*8+0*8+0] ); + __m128 t0 = _mm_load_ps( &jointQuatPtr[i*8+0*8+4] ); + + __m128 d0 = _mm_add_ps( q0, q0 ); + + __m128 sa0 = _mm_perm_ps( q0, _MM_SHUFFLE( 1, 0, 0, 1 ) ); // y, x, x, y + __m128 sb0 = _mm_perm_ps( d0, _MM_SHUFFLE( 2, 2, 1, 1 ) ); // y2, y2, z2, z2 + __m128 sc0 = _mm_perm_ps( q0, _MM_SHUFFLE( 3, 3, 3, 2 ) ); // z, w, w, w + __m128 sd0 = _mm_perm_ps( d0, _MM_SHUFFLE( 0, 1, 2, 2 ) ); // z2, z2, y2, x2 + + sa0 = _mm_xor_ps( sa0, vector_float_first_sign_bit ); + sc0 = _mm_xor_ps( sc0, vector_float_last_three_sign_bits ); // flip stupid inverse quaternions + + __m128 ma0 = _mm_add_ps( _mm_mul_ps( sa0, sb0 ), vector_float_first_pos_half ); // .5 - yy2, xy2, xz2, yz2 // .5 0 0 0 + __m128 mb0 = _mm_add_ps( _mm_mul_ps( sc0, sd0 ), vector_float_first_neg_half ); // -.5 + zz2, wz2, wy2, wx2 // -.5 0 0 0 + __m128 mc0 = _mm_sub_ps( vector_float_first_pos_half, _mm_mul_ps( q0, d0 ) ); // .5 - xx2, -yy2, -zz2, -ww2 // .5 0 0 0 + + __m128 mf0 = _mm_shuffle_ps( ma0, mc0, _MM_SHUFFLE( 0, 0, 1, 1 ) ); // xy2, xy2, .5 - xx2, .5 - xx2 // 01, 01, 10, 10 + __m128 md0 = _mm_shuffle_ps( mf0, ma0, _MM_SHUFFLE( 3, 2, 0, 2 ) ); // .5 - xx2, xy2, xz2, yz2 // 10, 01, 02, 03 + __m128 me0 = _mm_shuffle_ps( ma0, mb0, _MM_SHUFFLE( 3, 2, 1, 0 ) ); // .5 - yy2, xy2, wy2, wx2 // 00, 01, 12, 13 + + __m128 ra0 = _mm_add_ps( _mm_mul_ps( mb0, vector_float_quat2mat_mad1 ), ma0 ); // 1 - yy2 - zz2, xy2 - wz2, xz2 + wy2, // - - + - + __m128 rb0 = _mm_add_ps( _mm_mul_ps( mb0, vector_float_quat2mat_mad2 ), md0 ); // 1 - xx2 - zz2, xy2 + wz2, , yz2 - wx2 // - + - - + __m128 rc0 = _mm_add_ps( _mm_mul_ps( me0, vector_float_quat2mat_mad3 ), md0 ); // 1 - xx2 - yy2, , xz2 - wy2, yz2 + wx2 // + - - + + + __m128 ta0 = _mm_shuffle_ps( ra0, t0, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128 tb0 = _mm_shuffle_ps( rb0, t0, _MM_SHUFFLE( 1, 1, 3, 3 ) ); + __m128 tc0 = _mm_shuffle_ps( rc0, t0, _MM_SHUFFLE( 2, 2, 0, 0 ) ); + + ra0 = _mm_shuffle_ps( ra0, ta0, _MM_SHUFFLE( 2, 0, 1, 0 ) ); // 00 01 02 10 + rb0 = _mm_shuffle_ps( rb0, tb0, _MM_SHUFFLE( 2, 0, 0, 1 ) ); // 01 00 03 11 + rc0 = _mm_shuffle_ps( rc0, tc0, _MM_SHUFFLE( 2, 0, 3, 2 ) ); // 02 03 00 12 + + _mm_store_ps( &jointMatPtr[i*12+0*12+0], ra0 ); + _mm_store_ps( &jointMatPtr[i*12+0*12+4], rb0 ); + _mm_store_ps( &jointMatPtr[i*12+0*12+8], rc0 ); + } +} + +/* +============ +idSIMD_SSE::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_SSE::ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) { + + assert( sizeof( idJointQuat ) == JOINTQUAT_SIZE ); + assert( sizeof( idJointMat ) == JOINTMAT_SIZE ); + assert( (int)(&((idJointQuat *)0)->t) == (int)(&((idJointQuat *)0)->q) + (int)sizeof( ((idJointQuat *)0)->q ) ); + + const __m128 vector_float_zero = _mm_setzero_ps(); + const __m128 vector_float_one = { 1.0f, 1.0f, 1.0f, 1.0f }; + const __m128 vector_float_not = __m128c( _mm_set_epi32( -1, -1, -1, -1 ) ); + const __m128 vector_float_sign_bit = __m128c( _mm_set_epi32( 0x80000000, 0x80000000, 0x80000000, 0x80000000 ) ); + const __m128 vector_float_rsqrt_c0 = { -3.0f, -3.0f, -3.0f, -3.0f }; + const __m128 vector_float_rsqrt_c2 = { -0.25f, -0.25f, -0.25f, -0.25f }; + + int i = 0; + for ( ; i < numJoints - 3; i += 4 ) { + const float *__restrict m = (float *)&jointMats[i]; + float *__restrict q = (float *)&jointQuats[i]; + + __m128 ma0 = _mm_load_ps( &m[0*12+0] ); + __m128 ma1 = _mm_load_ps( &m[0*12+4] ); + __m128 ma2 = _mm_load_ps( &m[0*12+8] ); + + __m128 mb0 = _mm_load_ps( &m[1*12+0] ); + __m128 mb1 = _mm_load_ps( &m[1*12+4] ); + __m128 mb2 = _mm_load_ps( &m[1*12+8] ); + + __m128 mc0 = _mm_load_ps( &m[2*12+0] ); + __m128 mc1 = _mm_load_ps( &m[2*12+4] ); + __m128 mc2 = _mm_load_ps( &m[2*12+8] ); + + __m128 md0 = _mm_load_ps( &m[3*12+0] ); + __m128 md1 = _mm_load_ps( &m[3*12+4] ); + __m128 md2 = _mm_load_ps( &m[3*12+8] ); + + __m128 ta0 = _mm_unpacklo_ps( ma0, mc0 ); // a0, c0, a1, c1 + __m128 ta1 = _mm_unpackhi_ps( ma0, mc0 ); // a2, c2, a3, c3 + __m128 ta2 = _mm_unpacklo_ps( mb0, md0 ); // b0, d0, b1, b2 + __m128 ta3 = _mm_unpackhi_ps( mb0, md0 ); // b2, d2, b3, d3 + + __m128 tb0 = _mm_unpacklo_ps( ma1, mc1 ); // a0, c0, a1, c1 + __m128 tb1 = _mm_unpackhi_ps( ma1, mc1 ); // a2, c2, a3, c3 + __m128 tb2 = _mm_unpacklo_ps( mb1, md1 ); // b0, d0, b1, b2 + __m128 tb3 = _mm_unpackhi_ps( mb1, md1 ); // b2, d2, b3, d3 + + __m128 tc0 = _mm_unpacklo_ps( ma2, mc2 ); // a0, c0, a1, c1 + __m128 tc1 = _mm_unpackhi_ps( ma2, mc2 ); // a2, c2, a3, c3 + __m128 tc2 = _mm_unpacklo_ps( mb2, md2 ); // b0, d0, b1, b2 + __m128 tc3 = _mm_unpackhi_ps( mb2, md2 ); // b2, d2, b3, d3 + + __m128 m00 = _mm_unpacklo_ps( ta0, ta2 ); + __m128 m01 = _mm_unpackhi_ps( ta0, ta2 ); + __m128 m02 = _mm_unpacklo_ps( ta1, ta3 ); + __m128 m03 = _mm_unpackhi_ps( ta1, ta3 ); + + __m128 m10 = _mm_unpacklo_ps( tb0, tb2 ); + __m128 m11 = _mm_unpackhi_ps( tb0, tb2 ); + __m128 m12 = _mm_unpacklo_ps( tb1, tb3 ); + __m128 m13 = _mm_unpackhi_ps( tb1, tb3 ); + + __m128 m20 = _mm_unpacklo_ps( tc0, tc2 ); + __m128 m21 = _mm_unpackhi_ps( tc0, tc2 ); + __m128 m22 = _mm_unpacklo_ps( tc1, tc3 ); + __m128 m23 = _mm_unpackhi_ps( tc1, tc3 ); + + __m128 b00 = _mm_add_ps( m00, m11 ); + __m128 b11 = _mm_cmpgt_ps( m00, m22 ); + __m128 b01 = _mm_add_ps( b00, m22 ); + __m128 b10 = _mm_cmpgt_ps( m00, m11 ); + __m128 b0 = _mm_cmpgt_ps( b01, vector_float_zero ); + __m128 b1 = _mm_and_ps( b10, b11 ); + __m128 b2 = _mm_cmpgt_ps( m11, m22 ); + + __m128 m0 = b0; + __m128 m1 = _mm_and_ps( _mm_xor_ps( b0, vector_float_not ), b1 ); + __m128 p1 = _mm_or_ps( b0, b1 ); + __m128 p2 = _mm_or_ps( p1, b2 ); + __m128 m2 = _mm_and_ps( _mm_xor_ps( p1, vector_float_not ), b2 ); + __m128 m3 = _mm_xor_ps( p2, vector_float_not ); + + __m128 i0 = _mm_or_ps( m2, m3 ); + __m128 i1 = _mm_or_ps( m1, m3 ); + __m128 i2 = _mm_or_ps( m1, m2 ); + + __m128 s0 = _mm_and_ps( i0, vector_float_sign_bit ); + __m128 s1 = _mm_and_ps( i1, vector_float_sign_bit ); + __m128 s2 = _mm_and_ps( i2, vector_float_sign_bit ); + + m00 = _mm_xor_ps( m00, s0 ); + m11 = _mm_xor_ps( m11, s1 ); + m22 = _mm_xor_ps( m22, s2 ); + m21 = _mm_xor_ps( m21, s0 ); + m02 = _mm_xor_ps( m02, s1 ); + m10 = _mm_xor_ps( m10, s2 ); + + __m128 t0 = _mm_add_ps( m00, m11 ); + __m128 t1 = _mm_add_ps( m22, vector_float_one ); + __m128 q0 = _mm_add_ps( t0, t1 ); + __m128 q1 = _mm_sub_ps( m01, m10 ); + __m128 q2 = _mm_sub_ps( m20, m02 ); + __m128 q3 = _mm_sub_ps( m12, m21 ); + + __m128 rs = _mm_rsqrt_ps( q0 ); + __m128 sq = _mm_mul_ps( rs, rs ); + __m128 sh = _mm_mul_ps( rs, vector_float_rsqrt_c2 ); + __m128 sx = _mm_madd_ps( q0, sq, vector_float_rsqrt_c0 ); + __m128 s = _mm_mul_ps( sh, sx ); + + q0 = _mm_mul_ps( q0, s ); + q1 = _mm_mul_ps( q1, s ); + q2 = _mm_mul_ps( q2, s ); + q3 = _mm_mul_ps( q3, s ); + + m0 = _mm_or_ps( m0, m2 ); + m2 = _mm_or_ps( m2, m3 ); + + __m128 fq0 = _mm_sel_ps( q0, q3, m0 ); + __m128 fq1 = _mm_sel_ps( q1, q2, m0 ); + __m128 fq2 = _mm_sel_ps( q2, q1, m0 ); + __m128 fq3 = _mm_sel_ps( q3, q0, m0 ); + + __m128 rq0 = _mm_sel_ps( fq0, fq2, m2 ); + __m128 rq1 = _mm_sel_ps( fq1, fq3, m2 ); + __m128 rq2 = _mm_sel_ps( fq2, fq0, m2 ); + __m128 rq3 = _mm_sel_ps( fq3, fq1, m2 ); + + __m128 tq0 = _mm_unpacklo_ps( rq0, rq2 ); + __m128 tq1 = _mm_unpackhi_ps( rq0, rq2 ); + __m128 tq2 = _mm_unpacklo_ps( rq1, rq3 ); + __m128 tq3 = _mm_unpackhi_ps( rq1, rq3 ); + + __m128 sq0 = _mm_unpacklo_ps( tq0, tq2 ); + __m128 sq1 = _mm_unpackhi_ps( tq0, tq2 ); + __m128 sq2 = _mm_unpacklo_ps( tq1, tq3 ); + __m128 sq3 = _mm_unpackhi_ps( tq1, tq3 ); + + __m128 tt0 = _mm_unpacklo_ps( m03, m23 ); + __m128 tt1 = _mm_unpackhi_ps( m03, m23 ); + __m128 tt2 = _mm_unpacklo_ps( m13, vector_float_zero ); + __m128 tt3 = _mm_unpackhi_ps( m13, vector_float_zero ); + + __m128 st0 = _mm_unpacklo_ps( tt0, tt2 ); + __m128 st1 = _mm_unpackhi_ps( tt0, tt2 ); + __m128 st2 = _mm_unpacklo_ps( tt1, tt3 ); + __m128 st3 = _mm_unpackhi_ps( tt1, tt3 ); + + _mm_store_ps( &q[0*4], sq0 ); + _mm_store_ps( &q[1*4], st0 ); + _mm_store_ps( &q[2*4], sq1 ); + _mm_store_ps( &q[3*4], st1 ); + _mm_store_ps( &q[4*4], sq2 ); + _mm_store_ps( &q[5*4], st2 ); + _mm_store_ps( &q[6*4], sq3 ); + _mm_store_ps( &q[7*4], st3 ); + } + + float sign[2] = { 1.0f, -1.0f }; + + for ( ; i < numJoints; i++ ) { + const float *__restrict m = (float *)&jointMats[i]; + float *__restrict q = (float *)&jointQuats[i]; + + int b0 = m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f; + int b1 = m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2]; + int b2 = m[1 * 4 + 1] > m[2 * 4 + 2]; + + int m0 = b0; + int m1 = ( !b0 ) & b1; + int m2 = ( !( b0 | b1 ) ) & b2; + int m3 = !( b0 | b1 | b2 ); + + int i0 = ( m2 | m3 ); + int i1 = ( m1 | m3 ); + int i2 = ( m1 | m2 ); + + float s0 = sign[i0]; + float s1 = sign[i1]; + float s2 = sign[i2]; + + float t = s0 * m[0 * 4 + 0] + s1 * m[1 * 4 + 1] + s2 * m[2 * 4 + 2] + 1.0f; + float s = __frsqrts( t ); + s = ( t * s * s + -3.0f ) * ( s * -0.25f ); + + q[0] = t * s; + q[1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + q[2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + q[3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + if ( m0 | m2 ) { + // reverse + SwapValues( q[0], q[3] ); + SwapValues( q[1], q[2] ); + } + if ( m2 | m3 ) { + // rotate 2 + SwapValues( q[0], q[2] ); + SwapValues( q[1], q[3] ); + } + + q[4] = m[0 * 4 + 3]; + q[5] = m[1 * 4 + 3]; + q[6] = m[2 * 4 + 3]; + q[7] = 0.0f; + } +} + +/* +============ +idSIMD_SSE::TransformJoints +============ +*/ +void VPCALL idSIMD_SSE::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + const __m128 vector_float_mask_keep_last = __m128c( _mm_set_epi32( 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 ) ); + + const float *__restrict firstMatrix = jointMats->ToFloatPtr() + ( firstJoint + firstJoint + firstJoint - 3 ) * 4; + + __m128 pma = _mm_load_ps( firstMatrix + 0 ); + __m128 pmb = _mm_load_ps( firstMatrix + 4 ); + __m128 pmc = _mm_load_ps( firstMatrix + 8 ); + + for ( int joint = firstJoint; joint <= lastJoint; joint++ ) { + const int parent = parents[joint]; + const float *__restrict parentMatrix = jointMats->ToFloatPtr() + ( parent + parent + parent ) * 4; + float *__restrict childMatrix = jointMats->ToFloatPtr() + ( joint + joint + joint ) * 4; + + if ( parent != joint - 1 ) { + pma = _mm_load_ps( parentMatrix + 0 ); + pmb = _mm_load_ps( parentMatrix + 4 ); + pmc = _mm_load_ps( parentMatrix + 8 ); + } + + __m128 cma = _mm_load_ps( childMatrix + 0 ); + __m128 cmb = _mm_load_ps( childMatrix + 4 ); + __m128 cmc = _mm_load_ps( childMatrix + 8 ); + + __m128 ta = _mm_splat_ps( pma, 0 ); + __m128 tb = _mm_splat_ps( pmb, 0 ); + __m128 tc = _mm_splat_ps( pmc, 0 ); + + __m128 td = _mm_splat_ps( pma, 1 ); + __m128 te = _mm_splat_ps( pmb, 1 ); + __m128 tf = _mm_splat_ps( pmc, 1 ); + + __m128 tg = _mm_splat_ps( pma, 2 ); + __m128 th = _mm_splat_ps( pmb, 2 ); + __m128 ti = _mm_splat_ps( pmc, 2 ); + + pma = _mm_madd_ps( ta, cma, _mm_and_ps( pma, vector_float_mask_keep_last ) ); + pmb = _mm_madd_ps( tb, cma, _mm_and_ps( pmb, vector_float_mask_keep_last ) ); + pmc = _mm_madd_ps( tc, cma, _mm_and_ps( pmc, vector_float_mask_keep_last ) ); + + pma = _mm_madd_ps( td, cmb, pma ); + pmb = _mm_madd_ps( te, cmb, pmb ); + pmc = _mm_madd_ps( tf, cmb, pmc ); + + pma = _mm_madd_ps( tg, cmc, pma ); + pmb = _mm_madd_ps( th, cmc, pmb ); + pmc = _mm_madd_ps( ti, cmc, pmc ); + + _mm_store_ps( childMatrix + 0, pma ); + _mm_store_ps( childMatrix + 4, pmb ); + _mm_store_ps( childMatrix + 8, pmc ); + } +} + +/* +============ +idSIMD_SSE::UntransformJoints +============ +*/ +void VPCALL idSIMD_SSE::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + const __m128 vector_float_mask_keep_last = __m128c( _mm_set_epi32( 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 ) ); + + for ( int joint = lastJoint; joint >= firstJoint; joint-- ) { + assert( parents[joint] < joint ); + const int parent = parents[joint]; + const float *__restrict parentMatrix = jointMats->ToFloatPtr() + ( parent + parent + parent ) * 4; + float *__restrict childMatrix = jointMats->ToFloatPtr() + ( joint + joint + joint ) * 4; + + __m128 pma = _mm_load_ps( parentMatrix + 0 ); + __m128 pmb = _mm_load_ps( parentMatrix + 4 ); + __m128 pmc = _mm_load_ps( parentMatrix + 8 ); + + __m128 cma = _mm_load_ps( childMatrix + 0 ); + __m128 cmb = _mm_load_ps( childMatrix + 4 ); + __m128 cmc = _mm_load_ps( childMatrix + 8 ); + + __m128 ta = _mm_splat_ps( pma, 0 ); + __m128 tb = _mm_splat_ps( pma, 1 ); + __m128 tc = _mm_splat_ps( pma, 2 ); + + __m128 td = _mm_splat_ps( pmb, 0 ); + __m128 te = _mm_splat_ps( pmb, 1 ); + __m128 tf = _mm_splat_ps( pmb, 2 ); + + __m128 tg = _mm_splat_ps( pmc, 0 ); + __m128 th = _mm_splat_ps( pmc, 1 ); + __m128 ti = _mm_splat_ps( pmc, 2 ); + + cma = _mm_sub_ps( cma, _mm_and_ps( pma, vector_float_mask_keep_last ) ); + cmb = _mm_sub_ps( cmb, _mm_and_ps( pmb, vector_float_mask_keep_last ) ); + cmc = _mm_sub_ps( cmc, _mm_and_ps( pmc, vector_float_mask_keep_last ) ); + + pma = _mm_mul_ps( ta, cma ); + pmb = _mm_mul_ps( tb, cma ); + pmc = _mm_mul_ps( tc, cma ); + + pma = _mm_madd_ps( td, cmb, pma ); + pmb = _mm_madd_ps( te, cmb, pmb ); + pmc = _mm_madd_ps( tf, cmb, pmc ); + + pma = _mm_madd_ps( tg, cmc, pma ); + pmb = _mm_madd_ps( th, cmc, pmb ); + pmc = _mm_madd_ps( ti, cmc, pmc ); + + _mm_store_ps( childMatrix + 0, pma ); + _mm_store_ps( childMatrix + 4, pmb ); + _mm_store_ps( childMatrix + 8, pmc ); + } +} + diff --git a/neo/idlib/math/Simd_SSE.h b/neo/idlib/math/Simd_SSE.h new file mode 100644 index 00000000..95352414 --- /dev/null +++ b/neo/idlib/math/Simd_SSE.h @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_SSE_H__ +#define __MATH_SIMD_SSE_H__ + +/* +=============================================================================== + + SSE implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE : public idSIMD_Generic { +public: + virtual const char * VPCALL GetName() const; + + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL BlendJointsFast( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ); + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); +}; + +#endif /* !__MATH_SIMD_SSE_H__ */ diff --git a/neo/idlib/math/VecX.cpp b/neo/idlib/math/VecX.cpp new file mode 100644 index 00000000..230346b2 --- /dev/null +++ b/neo/idlib/math/VecX.cpp @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +//=============================================================== +// +// idVecX +// +//=============================================================== + +float idVecX::temp[VECX_MAX_TEMP+4]; +float * idVecX::tempPtr = (float *) ( ( (int) idVecX::temp + 15 ) & ~15 ); +int idVecX::tempIndex = 0; + +/* +============= +idVecX::ToString +============= +*/ +const char *idVecX::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/VecX.h b/neo/idlib/math/VecX.h new file mode 100644 index 00000000..4086a1a9 --- /dev/null +++ b/neo/idlib/math/VecX.h @@ -0,0 +1,802 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_VECX_H__ +#define __MATH_VECX_H__ + +/* +=============================================================================== + +idVecX - arbitrary sized vector + +The vector lives on 16 byte aligned and 16 byte padded memory. + +NOTE: due to the temporary memory pool idVecX cannot be used by multiple threads + +=============================================================================== +*/ + +#define VECX_MAX_TEMP 1024 +#define VECX_QUAD( x ) ( ( ( ( x ) + 3 ) & ~3 ) * sizeof( float ) ) +#define VECX_CLEAREND() int s = size; while( s < ( ( s + 3) & ~3 ) ) { p[s++] = 0.0f; } +#define VECX_ALLOCA( n ) ( (float *) _alloca16( VECX_QUAD( n ) ) ) +#define VECX_SIMD + +class idVecX { + friend class idMatX; + +public: + ID_INLINE idVecX(); + ID_INLINE explicit idVecX( int length ); + ID_INLINE explicit idVecX( int length, float *data ); + ID_INLINE ~idVecX(); + + ID_INLINE float Get( int index ) const; + ID_INLINE float & Get( int index ); + + ID_INLINE float operator[]( const int index ) const; + ID_INLINE float & operator[]( const int index ); + ID_INLINE idVecX operator-() const; + ID_INLINE idVecX & operator=( const idVecX &a ); + ID_INLINE idVecX operator*( const float a ) const; + ID_INLINE idVecX operator/( const float a ) const; + ID_INLINE float operator*( const idVecX &a ) const; + ID_INLINE idVecX operator-( const idVecX &a ) const; + ID_INLINE idVecX operator+( const idVecX &a ) const; + ID_INLINE idVecX & operator*=( const float a ); + ID_INLINE idVecX & operator/=( const float a ); + ID_INLINE idVecX & operator+=( const idVecX &a ); + ID_INLINE idVecX & operator-=( const idVecX &a ); + + friend ID_INLINE idVecX operator*( const float a, const idVecX &b ); + + ID_INLINE bool Compare( const idVecX &a ) const; // exact compare, no epsilon + ID_INLINE bool Compare( const idVecX &a, const float epsilon ) const; // compare with epsilon + ID_INLINE bool operator==( const idVecX &a ) const; // exact compare, no epsilon + ID_INLINE bool operator!=( const idVecX &a ) const; // exact compare, no epsilon + + ID_INLINE void SetSize( int size ); + ID_INLINE void ChangeSize( int size, bool makeZero = false ); + ID_INLINE int GetSize() const { return size; } + ID_INLINE void SetData( int length, float *data ); + ID_INLINE void Zero(); + ID_INLINE void Zero( int length ); + ID_INLINE void Random( int seed, float l = 0.0f, float u = 1.0f ); + ID_INLINE void Random( int length, int seed, float l = 0.0f, float u = 1.0f ); + ID_INLINE void Negate(); + ID_INLINE void Clamp( float min, float max ); + ID_INLINE idVecX & SwapElements( int e1, int e2 ); + + ID_INLINE float Length() const; + ID_INLINE float LengthSqr() const; + ID_INLINE idVecX Normalize() const; + ID_INLINE float NormalizeSelf(); + + ID_INLINE int GetDimension() const; + + ID_INLINE void AddScaleAdd( const float scale, const idVecX & v0, const idVecX & v1 ); + + ID_INLINE const idVec3 & SubVec3( int index ) const; + ID_INLINE idVec3 & SubVec3( int index ); + ID_INLINE const idVec6 & SubVec6( int index = 0 ) const; + ID_INLINE idVec6 & SubVec6( int index = 0 ); + ID_INLINE const float * ToFloatPtr() const; + ID_INLINE float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + int size; // size of the vector + int alloced; // if -1 p points to data set with SetData + float * p; // memory the vector is stored + + static float temp[VECX_MAX_TEMP+4]; // used to store intermediate results + static float * tempPtr; // pointer to 16 byte aligned temporary memory + static int tempIndex; // index into memory pool, wraps around + + ID_INLINE void SetTempSize( int size ); +}; + + +/* +======================== +idVecX::idVecX +======================== +*/ +ID_INLINE idVecX::idVecX() { + size = alloced = 0; + p = NULL; +} + +/* +======================== +idVecX::idVecX +======================== +*/ +ID_INLINE idVecX::idVecX( int length ) { + size = alloced = 0; + p = NULL; + SetSize( length ); +} + +/* +======================== +idVecX::idVecX +======================== +*/ +ID_INLINE idVecX::idVecX( int length, float *data ) { + size = alloced = 0; + p = NULL; + SetData( length, data ); +} + +/* +======================== +idVecX::~idVecX +======================== +*/ +ID_INLINE idVecX::~idVecX() { + // if not temp memory + if ( p && ( p < idVecX::tempPtr || p >= idVecX::tempPtr + VECX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( p ); + } +} + +/* +======================== +idVecX::Get +======================== +*/ +ID_INLINE float idVecX::Get( int index ) const { + assert( index >= 0 && index < size ); + return p[index]; +} + +/* +======================== +idVecX::Get +======================== +*/ +ID_INLINE float & idVecX::Get( int index ) { + assert( index >= 0 && index < size ); + return p[index]; +} + +/* +======================== +idVecX::operator[] +======================== +*/ +ID_INLINE float idVecX::operator[]( int index ) const { + return Get( index ); +} + +/* +======================== +idVecX::operator[] +======================== +*/ +ID_INLINE float & idVecX::operator[]( int index ) { + return Get( index ); +} + +/* +======================== +idVecX::operator- +======================== +*/ +ID_INLINE idVecX idVecX::operator-() const { + idVecX m; + + m.SetTempSize( size ); +#ifdef VECX_SIMD + ALIGN16( unsigned int signBit[4] ) = { IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK }; + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( m.p + i, _mm_xor_ps( _mm_load_ps( p + i ), (__m128 &) signBit[0] ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + m.p[i] = -p[i]; + } +#endif + return m; +} + +/* +======================== +idVecX::operator= +======================== +*/ +ID_INLINE idVecX &idVecX::operator=( const idVecX &a ) { + SetSize( a.size ); +#ifdef VECX_SIMD + for ( int i = 0; i < a.size; i += 4 ) { + _mm_store_ps( p + i, _mm_load_ps( a.p + i ) ); + } +#else + memcpy( p, a.p, a.size * sizeof( float ) ); +#endif + idVecX::tempIndex = 0; + return *this; +} + +/* +======================== +idVecX::operator+ +======================== +*/ +ID_INLINE idVecX idVecX::operator+( const idVecX &a ) const { + idVecX m; + + assert( size == a.size ); + m.SetTempSize( size ); +#ifdef VECX_SIMD + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( m.p + i, _mm_add_ps( _mm_load_ps( p + i ), _mm_load_ps( a.p + i ) ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + m.p[i] = p[i] + a.p[i]; + } +#endif + return m; +} + +/* +======================== +idVecX::operator- +======================== +*/ +ID_INLINE idVecX idVecX::operator-( const idVecX &a ) const { + idVecX m; + + assert( size == a.size ); + m.SetTempSize( size ); +#ifdef VECX_SIMD + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( m.p + i, _mm_sub_ps( _mm_load_ps( p + i ), _mm_load_ps( a.p + i ) ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + m.p[i] = p[i] - a.p[i]; + } +#endif + return m; +} + +/* +======================== +idVecX::operator+= +======================== +*/ +ID_INLINE idVecX &idVecX::operator+=( const idVecX &a ) { + assert( size == a.size ); +#ifdef VECX_SIMD + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( p + i, _mm_add_ps( _mm_load_ps( p + i ), _mm_load_ps( a.p + i ) ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + p[i] += a.p[i]; + } +#endif + idVecX::tempIndex = 0; + return *this; +} + +/* +======================== +idVecX::operator-= +======================== +*/ +ID_INLINE idVecX &idVecX::operator-=( const idVecX &a ) { + assert( size == a.size ); +#ifdef VECX_SIMD + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( p + i, _mm_sub_ps( _mm_load_ps( p + i ), _mm_load_ps( a.p + i ) ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + p[i] -= a.p[i]; + } +#endif + idVecX::tempIndex = 0; + return *this; +} + +/* +======================== +idVecX::operator* +======================== +*/ +ID_INLINE idVecX idVecX::operator*( const float a ) const { + idVecX m; + + m.SetTempSize( size ); +#ifdef VECX_SIMD + __m128 va = _mm_load1_ps( & a ); + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( m.p + i, _mm_mul_ps( _mm_load_ps( p + i ), va ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + m.p[i] = p[i] * a; + } +#endif + return m; +} + +/* +======================== +idVecX::operator*= +======================== +*/ +ID_INLINE idVecX &idVecX::operator*=( const float a ) { +#ifdef VECX_SIMD + __m128 va = _mm_load1_ps( & a ); + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( p + i, _mm_mul_ps( _mm_load_ps( p + i ), va ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + p[i] *= a; + } +#endif + return *this; +} + +/* +======================== +idVecX::operator/ +======================== +*/ +ID_INLINE idVecX idVecX::operator/( const float a ) const { + assert( fabs( a ) > idMath::FLT_SMALLEST_NON_DENORMAL ); + return (*this) * ( 1.0f / a ); +} + +/* +======================== +idVecX::operator/= +======================== +*/ +ID_INLINE idVecX &idVecX::operator/=( const float a ) { + assert( fabs( a ) > idMath::FLT_SMALLEST_NON_DENORMAL ); + (*this) *= ( 1.0f / a ); + return *this; +} + +/* +======================== +operator* +======================== +*/ +ID_INLINE idVecX operator*( const float a, const idVecX &b ) { + return b * a; +} + +/* +======================== +idVecX::operator* +======================== +*/ +ID_INLINE float idVecX::operator*( const idVecX &a ) const { + assert( size == a.size ); + float sum = 0.0f; + for ( int i = 0; i < size; i++ ) { + sum += p[i] * a.p[i]; + } + return sum; +} + +/* +======================== +idVecX::Compare +======================== +*/ +ID_INLINE bool idVecX::Compare( const idVecX &a ) const { + assert( size == a.size ); + for ( int i = 0; i < size; i++ ) { + if ( p[i] != a.p[i] ) { + return false; + } + } + return true; +} + +/* +======================== +idVecX::Compare +======================== +*/ +ID_INLINE bool idVecX::Compare( const idVecX &a, const float epsilon ) const { + assert( size == a.size ); + for ( int i = 0; i < size; i++ ) { + if ( idMath::Fabs( p[i] - a.p[i] ) > epsilon ) { + return false; + } + } + return true; +} + +/* +======================== +idVecX::operator== +======================== +*/ +ID_INLINE bool idVecX::operator==( const idVecX &a ) const { + return Compare( a ); +} + +/* +======================== +idVecX::operator!= +======================== +*/ +ID_INLINE bool idVecX::operator!=( const idVecX &a ) const { + return !Compare( a ); +} + +/* +======================== +idVecX::SetSize +======================== +*/ +ID_INLINE void idVecX::SetSize( int newSize ) { + //assert( p < idVecX::tempPtr || p > idVecX::tempPtr + VECX_MAX_TEMP ); + if ( newSize != size || p == NULL ) { + int alloc = ( newSize + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + if ( p ) { + Mem_Free16( p ); + } + p = (float *) Mem_Alloc16( alloc * sizeof( float ), TAG_MATH ); + alloced = alloc; + } + size = newSize; + VECX_CLEAREND(); + } +} + +/* +======================== +idVecX::ChangeSize +======================== +*/ +ID_INLINE void idVecX::ChangeSize( int newSize, bool makeZero ) { + if ( newSize != size ) { + int alloc = ( newSize + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + float *oldVec = p; + p = (float *) Mem_Alloc16( alloc * sizeof( float ), TAG_MATH ); + alloced = alloc; + if ( oldVec ) { + for ( int i = 0; i < size; i++ ) { + p[i] = oldVec[i]; + } + Mem_Free16( oldVec ); + } + if ( makeZero ) { + // zero any new elements + for ( int i = size; i < newSize; i++ ) { + p[i] = 0.0f; + } + } + } + size = newSize; + VECX_CLEAREND(); + } +} + +/* +======================== +idVecX::SetTempSize +======================== +*/ +ID_INLINE void idVecX::SetTempSize( int newSize ) { + size = newSize; + alloced = ( newSize + 3 ) & ~3; + assert( alloced < VECX_MAX_TEMP ); + if ( idVecX::tempIndex + alloced > VECX_MAX_TEMP ) { + idVecX::tempIndex = 0; + } + p = idVecX::tempPtr + idVecX::tempIndex; + idVecX::tempIndex += alloced; + VECX_CLEAREND(); +} + +/* +======================== +idVecX::SetData +======================== +*/ +ID_INLINE void idVecX::SetData( int length, float *data ) { + if ( p != NULL && ( p < idVecX::tempPtr || p >= idVecX::tempPtr + VECX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( p ); + } + assert_16_byte_aligned( data ); // data must be 16 byte aligned + p = data; + size = length; + alloced = -1; + VECX_CLEAREND(); +} + +/* +======================== +idVecX::Zero +======================== +*/ +ID_INLINE void idVecX::Zero() { +#ifdef VECX_SIMD + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( p + i, _mm_setzero_ps() ); + } +#else + memset( p, 0, size * sizeof( float ) ); +#endif +} + +/* +======================== +idVecX::Zero +======================== +*/ +ID_INLINE void idVecX::Zero( int length ) { + SetSize( length ); +#ifdef VECX_SIMD + for ( int i = 0; i < length; i += 4 ) { + _mm_store_ps( p + i, _mm_setzero_ps() ); + } +#else + memset( p, 0, length * sizeof( float ) ); +#endif +} + +/* +======================== +idVecX::Random +======================== +*/ +ID_INLINE void idVecX::Random( int seed, float l, float u ) { + idRandom rnd( seed ); + + float c = u - l; + for ( int i = 0; i < size; i++ ) { + p[i] = l + rnd.RandomFloat() * c; + } +} + +/* +======================== +idVecX::Random +======================== +*/ +ID_INLINE void idVecX::Random( int length, int seed, float l, float u ) { + idRandom rnd( seed ); + + SetSize( length ); + float c = u - l; + for ( int i = 0; i < size; i++ ) { + p[i] = l + rnd.RandomFloat() * c; + } +} + +/* +======================== +idVecX::Negate +======================== +*/ +ID_INLINE void idVecX::Negate() { +#ifdef VECX_SIMD + ALIGN16( const unsigned int signBit[4] ) = { IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK, IEEE_FLT_SIGN_MASK }; + for ( int i = 0; i < size; i += 4 ) { + _mm_store_ps( p + i, _mm_xor_ps( _mm_load_ps( p + i ), (__m128 &) signBit[0] ) ); + } +#else + for ( int i = 0; i < size; i++ ) { + p[i] = -p[i]; + } +#endif +} + +/* +======================== +idVecX::Clamp +======================== +*/ +ID_INLINE void idVecX::Clamp( float min, float max ) { + for ( int i = 0; i < size; i++ ) { + if ( p[i] < min ) { + p[i] = min; + } else if ( p[i] > max ) { + p[i] = max; + } + } +} + +/* +======================== +idVecX::SwapElements +======================== +*/ +ID_INLINE idVecX &idVecX::SwapElements( int e1, int e2 ) { + float tmp; + tmp = p[e1]; + p[e1] = p[e2]; + p[e2] = tmp; + return *this; +} + +/* +======================== +idVecX::Length +======================== +*/ +ID_INLINE float idVecX::Length() const { + float sum = 0.0f; + for ( int i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + return idMath::Sqrt( sum ); +} + +/* +======================== +idVecX::LengthSqr +======================== +*/ +ID_INLINE float idVecX::LengthSqr() const { + float sum = 0.0f; + for ( int i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + return sum; +} + +/* +======================== +idVecX::Normalize +======================== +*/ +ID_INLINE idVecX idVecX::Normalize() const { + idVecX m; + + m.SetTempSize( size ); + float sum = 0.0f; + for ( int i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + float invSqrt = idMath::InvSqrt( sum ); + for ( int i = 0; i < size; i++ ) { + m.p[i] = p[i] * invSqrt; + } + return m; +} + +/* +======================== +idVecX::NormalizeSelf +======================== +*/ +ID_INLINE float idVecX::NormalizeSelf() { + float sum = 0.0f; + for ( int i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + float invSqrt = idMath::InvSqrt( sum ); + for ( int i = 0; i < size; i++ ) { + p[i] *= invSqrt; + } + return invSqrt * sum; +} + +/* +======================== +idVecX::GetDimension +======================== +*/ +ID_INLINE int idVecX::GetDimension() const { + return size; +} + +/* +======================== +idVecX::SubVec3 +======================== +*/ +ID_INLINE idVec3 &idVecX::SubVec3( int index ) { + assert( index >= 0 && index * 3 + 3 <= size ); + return *reinterpret_cast(p + index * 3); +} + +/* +======================== +idVecX::SubVec3 +======================== +*/ +ID_INLINE const idVec3 &idVecX::SubVec3( int index ) const { + assert( index >= 0 && index * 3 + 3 <= size ); + return *reinterpret_cast(p + index * 3); +} + +/* +======================== +idVecX::SubVec6 +======================== +*/ +ID_INLINE idVec6 &idVecX::SubVec6( int index ) { + assert( index >= 0 && index * 6 + 6 <= size ); + return *reinterpret_cast(p + index * 6); +} + +/* +======================== +idVecX::SubVec6 +======================== +*/ +ID_INLINE const idVec6 &idVecX::SubVec6( int index ) const { + assert( index >= 0 && index * 6 + 6 <= size ); + return *reinterpret_cast(p + index * 6); +} + +/* +======================== +idVecX::ToFloatPtr +======================== +*/ +ID_INLINE const float *idVecX::ToFloatPtr() const { + return p; +} + +/* +======================== +idVecX::ToFloatPtr +======================== +*/ +ID_INLINE float *idVecX::ToFloatPtr() { + return p; +} + +/* +======================== +idVecX::AddScaleAdd +======================== +*/ +ID_INLINE void idVecX::AddScaleAdd( const float scale, const idVecX &v0, const idVecX &v1 ) { + assert( GetSize() == v0.GetSize() ); + assert( GetSize() == v1.GetSize() ); + + const float * v0Ptr = v0.ToFloatPtr(); + const float * v1Ptr = v1.ToFloatPtr(); + float * dstPtr = ToFloatPtr(); + + for ( int i = 0; i < size; i++ ) { + dstPtr[i] += scale * ( v0Ptr[i] + v1Ptr[i] ); + } +} + +#endif // !__MATH_VECTORX_H__ diff --git a/neo/idlib/math/Vector.cpp b/neo/idlib/math/Vector.cpp new file mode 100644 index 00000000..3db88b2b --- /dev/null +++ b/neo/idlib/math/Vector.cpp @@ -0,0 +1,377 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../precompiled.h" + +idVec2 vec2_origin( 0.0f, 0.0f ); +idVec3 vec3_origin( 0.0f, 0.0f, 0.0f ); +idVec4 vec4_origin( 0.0f, 0.0f, 0.0f, 0.0f ); +idVec5 vec5_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); +idVec6 vec6_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); +idVec6 vec6_infinity( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + + +//=============================================================== +// +// idVec2 +// +//=============================================================== + +/* +============= +idVec2::ToString +============= +*/ +const char *idVec2::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +void idVec2::Lerp( const idVec2 &v1, const idVec2 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + + +//=============================================================== +// +// idVec3 +// +//=============================================================== + +/* +============= +idVec3::ToYaw +============= +*/ +float idVec3::ToYaw() const { + float yaw; + + if ( ( y == 0.0f ) && ( x == 0.0f ) ) { + yaw = 0.0f; + } else { + yaw = RAD2DEG( atan2( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + } + + return yaw; +} + +/* +============= +idVec3::ToPitch +============= +*/ +float idVec3::ToPitch() const { + float forward; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( atan2( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + return pitch; +} + +/* +============= +idVec3::ToAngles +============= +*/ +idAngles idVec3::ToAngles() const { + float forward; + float yaw; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + yaw = 0.0f; + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + yaw = RAD2DEG( atan2( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( atan2( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + return idAngles( -pitch, yaw, 0.0f ); +} + +/* +============= +idVec3::ToPolar +============= +*/ +idPolar3 idVec3::ToPolar() const { + float forward; + float yaw; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + yaw = 0.0f; + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + yaw = RAD2DEG( atan2( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( atan2( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + return idPolar3( idMath::Sqrt( x * x + y * y + z * z ), yaw, -pitch ); +} + +/* +============= +idVec3::ToMat3 +============= +*/ +idMat3 idVec3::ToMat3() const { + idMat3 mat; + float d; + + mat[0] = *this; + d = x * x + y * y; + if ( !d ) { + mat[1][0] = 1.0f; + mat[1][1] = 0.0f; + mat[1][2] = 0.0f; + } else { + d = idMath::InvSqrt( d ); + mat[1][0] = -y * d; + mat[1][1] = x * d; + mat[1][2] = 0.0f; + } + mat[2] = Cross( mat[1] ); + + return mat; +} + +/* +============= +idVec3::ToString +============= +*/ +const char *idVec3::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +void idVec3::Lerp( const idVec3 &v1, const idVec3 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + +/* +============= +SLerp + +Spherical linear interpolation from v1 to v2. +Vectors are expected to be normalized. +============= +*/ +#define LERP_DELTA 1e-6 + +void idVec3::SLerp( const idVec3 &v1, const idVec3 &v2, const float t ) { + float omega, cosom, sinom, scale0, scale1; + + if ( t <= 0.0f ) { + (*this) = v1; + return; + } else if ( t >= 1.0f ) { + (*this) = v2; + return; + } + + cosom = v1 * v2; + if ( ( 1.0f - cosom ) > LERP_DELTA ) { + omega = acos( cosom ); + sinom = sin( omega ); + scale0 = sin( ( 1.0f - t ) * omega ) / sinom; + scale1 = sin( t * omega ) / sinom; + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + (*this) = ( v1 * scale0 + v2 * scale1 ); +} + +/* +============= +ProjectSelfOntoSphere + +Projects the z component onto a sphere. +============= +*/ +void idVec3::ProjectSelfOntoSphere( const float radius ) { + float rsqr = radius * radius; + float len = Length(); + if ( len < rsqr * 0.5f ) { + z = sqrt( rsqr - len ); + } else { + z = rsqr / ( 2.0f * sqrt( len ) ); + } +} + + + +//=============================================================== +// +// idVec4 +// +//=============================================================== + +/* +============= +idVec4::ToString +============= +*/ +const char *idVec4::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +void idVec4::Lerp( const idVec4 &v1, const idVec4 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + + +//=============================================================== +// +// idVec5 +// +//=============================================================== + +/* +============= +idVec5::ToString +============= +*/ +const char *idVec5::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +idVec5::Lerp +============= +*/ +void idVec5::Lerp( const idVec5 &v1, const idVec5 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + x = v1.x + l * ( v2.x - v1.x ); + y = v1.y + l * ( v2.y - v1.y ); + z = v1.z + l * ( v2.z - v1.z ); + s = v1.s + l * ( v2.s - v1.s ); + t = v1.t + l * ( v2.t - v1.t ); + } +} + + +//=============================================================== +// +// idVec6 +// +//=============================================================== + +/* +============= +idVec6::ToString +============= +*/ +const char *idVec6::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/neo/idlib/math/Vector.h b/neo/idlib/math/Vector.h new file mode 100644 index 00000000..f194618d --- /dev/null +++ b/neo/idlib/math/Vector.h @@ -0,0 +1,1499 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATH_VECTOR_H__ +#define __MATH_VECTOR_H__ + +/* +=============================================================================== + + Vector classes + +=============================================================================== +*/ + +#include "../containers/Array.h" // for idTupleSize + +#define VECTOR_EPSILON 0.001f + +class idAngles; +class idPolar3; +class idMat3; + +//=============================================================== +// +// idVec2 - 2D vector +// +//=============================================================== + +class idVec2 { +public: + float x; + float y; + + idVec2(); + explicit idVec2( const float x, const float y ); + + void Set( const float x, const float y ); + void Zero(); + + float operator[]( int index ) const; + float & operator[]( int index ); + idVec2 operator-() const; + float operator*( const idVec2 &a ) const; + idVec2 operator*( const float a ) const; + idVec2 operator/( const float a ) const; + idVec2 operator+( const idVec2 &a ) const; + idVec2 operator-( const idVec2 &a ) const; + idVec2 & operator+=( const idVec2 &a ); + idVec2 & operator-=( const idVec2 &a ); + idVec2 & operator/=( const idVec2 &a ); + idVec2 & operator/=( const float a ); + idVec2 & operator*=( const float a ); + + friend idVec2 operator*( const float a, const idVec2 b ); + + idVec2 Scale( const idVec2 &a ) const; + + bool Compare( const idVec2 &a ) const; // exact compare, no epsilon + bool Compare( const idVec2 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec2 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec2 &a ) const; // exact compare, no epsilon + + float Length() const; + float LengthFast() const; + float LengthSqr() const; + float Normalize(); // returns length + float NormalizeFast(); // returns length + idVec2 Truncate( float length ) const; // cap length + void Clamp( const idVec2 &min, const idVec2 &max ); + void Snap(); // snap to closest integer value + void SnapInt(); // snap towards integer (floor) + + int GetDimension() const; + + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec2 &v1, const idVec2 &v2, const float l ); +}; + +extern idVec2 vec2_origin; +#define vec2_zero vec2_origin + +ID_INLINE idVec2::idVec2() { +} + +ID_INLINE idVec2::idVec2( const float x, const float y ) { + this->x = x; + this->y = y; +} + +ID_INLINE void idVec2::Set( const float x, const float y ) { + this->x = x; + this->y = y; +} + +ID_INLINE void idVec2::Zero() { + x = y = 0.0f; +} + +ID_INLINE bool idVec2::Compare( const idVec2 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) ); +} + +ID_INLINE bool idVec2::Compare( const idVec2 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec2::operator==( const idVec2 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec2::operator!=( const idVec2 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec2::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec2::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE float idVec2::Length() const { + return ( float )idMath::Sqrt( x * x + y * y ); +} + +ID_INLINE float idVec2::LengthFast() const { + float sqrLength; + + sqrLength = x * x + y * y; + return sqrLength * idMath::InvSqrt( sqrLength ); +} + +ID_INLINE float idVec2::LengthSqr() const { + return ( x * x + y * y ); +} + +ID_INLINE float idVec2::Normalize() { + float sqrLength, invLength; + + sqrLength = x * x + y * y; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec2::NormalizeFast() { + float lengthSqr, invLength; + + lengthSqr = x * x + y * y; + invLength = idMath::InvSqrt( lengthSqr ); + x *= invLength; + y *= invLength; + return invLength * lengthSqr; +} + +ID_INLINE idVec2 idVec2::Truncate( float length ) const { + if ( length < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return vec2_zero; + } else { + float length2 = LengthSqr(); + if ( length2 > length * length ) { + float ilength = length * idMath::InvSqrt( length2 ); + return *this * ilength; + } + } + return *this; +} + +ID_INLINE void idVec2::Clamp( const idVec2 &min, const idVec2 &max ) { + if ( x < min.x ) { + x = min.x; + } else if ( x > max.x ) { + x = max.x; + } + if ( y < min.y ) { + y = min.y; + } else if ( y > max.y ) { + y = max.y; + } +} + +ID_INLINE void idVec2::Snap() { + x = floor( x + 0.5f ); + y = floor( y + 0.5f ); +} + +ID_INLINE void idVec2::SnapInt() { + x = float( int( x ) ); + y = float( int( y ) ); +} + +ID_INLINE idVec2 idVec2::operator-() const { + return idVec2( -x, -y ); +} + +ID_INLINE idVec2 idVec2::operator-( const idVec2 &a ) const { + return idVec2( x - a.x, y - a.y ); +} + +ID_INLINE float idVec2::operator*( const idVec2 &a ) const { + return x * a.x + y * a.y; +} + +ID_INLINE idVec2 idVec2::operator*( const float a ) const { + return idVec2( x * a, y * a ); +} + +ID_INLINE idVec2 idVec2::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec2( x * inva, y * inva ); +} + +ID_INLINE idVec2 operator*( const float a, const idVec2 b ) { + return idVec2( b.x * a, b.y * a ); +} + +ID_INLINE idVec2 idVec2::operator+( const idVec2 &a ) const { + return idVec2( x + a.x, y + a.y ); +} + +ID_INLINE idVec2 &idVec2::operator+=( const idVec2 &a ) { + x += a.x; + y += a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator/=( const idVec2 &a ) { + x /= a.x; + y /= a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator-=( const idVec2 &a ) { + x -= a.x; + y -= a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator*=( const float a ) { + x *= a; + y *= a; + + return *this; +} + +ID_INLINE idVec2 idVec2::Scale( const idVec2 &a ) const { + return idVec2( x * a.x, y * a.y ); +} + +ID_INLINE int idVec2::GetDimension() const { + return 2; +} + +ID_INLINE const float *idVec2::ToFloatPtr() const { + return &x; +} + +ID_INLINE float *idVec2::ToFloatPtr() { + return &x; +} + + +//=============================================================== +// +// idVec3 - 3D vector +// +//=============================================================== + +class idVec3 { +public: + float x; + float y; + float z; + + idVec3(); + explicit idVec3( const float xyz ) { Set( xyz, xyz, xyz ); } + explicit idVec3( const float x, const float y, const float z ); + + void Set( const float x, const float y, const float z ); + void Zero(); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec3 operator-() const; + idVec3 & operator=( const idVec3 &a ); // required because of a msvc 6 & 7 bug + float operator*( const idVec3 &a ) const; + idVec3 operator*( const float a ) const; + idVec3 operator/( const float a ) const; + idVec3 operator+( const idVec3 &a ) const; + idVec3 operator-( const idVec3 &a ) const; + idVec3 & operator+=( const idVec3 &a ); + idVec3 & operator-=( const idVec3 &a ); + idVec3 & operator/=( const idVec3 &a ); + idVec3 & operator/=( const float a ); + idVec3 & operator*=( const float a ); + + friend idVec3 operator*( const float a, const idVec3 b ); + + bool Compare( const idVec3 &a ) const; // exact compare, no epsilon + bool Compare( const idVec3 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec3 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec3 &a ) const; // exact compare, no epsilon + + bool FixDegenerateNormal(); // fix degenerate axial cases + bool FixDenormals(); // change tiny numbers to zero + + idVec3 Cross( const idVec3 &a ) const; + idVec3 & Cross( const idVec3 &a, const idVec3 &b ); + float Length() const; + float LengthSqr() const; + float LengthFast() const; + float Normalize(); // returns length + float NormalizeFast(); // returns length + idVec3 Truncate( float length ) const; // cap length + void Clamp( const idVec3 &min, const idVec3 &max ); + void Snap(); // snap to closest integer value + void SnapInt(); // snap towards integer (floor) + + int GetDimension() const; + + float ToYaw() const; + float ToPitch() const; + idAngles ToAngles() const; + idPolar3 ToPolar() const; + idMat3 ToMat3() const; // vector should be normalized + const idVec2 & ToVec2() const; + idVec2 & ToVec2(); + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + void NormalVectors( idVec3 &left, idVec3 &down ) const; // vector should be normalized + void OrthogonalBasis( idVec3 &left, idVec3 &up ) const; + + void ProjectOntoPlane( const idVec3 &normal, const float overBounce = 1.0f ); + bool ProjectAlongPlane( const idVec3 &normal, const float epsilon, const float overBounce = 1.0f ); + void ProjectSelfOntoSphere( const float radius ); + + void Lerp( const idVec3 &v1, const idVec3 &v2, const float l ); + void SLerp( const idVec3 &v1, const idVec3 &v2, const float l ); +}; + +extern idVec3 vec3_origin; +#define vec3_zero vec3_origin + +ID_INLINE idVec3::idVec3() { +} + +ID_INLINE idVec3::idVec3( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE float idVec3::operator[]( const int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float &idVec3::operator[]( const int index ) { + return ( &x )[ index ]; +} + +ID_INLINE void idVec3::Set( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE void idVec3::Zero() { + x = y = z = 0.0f; +} + +ID_INLINE idVec3 idVec3::operator-() const { + return idVec3( -x, -y, -z ); +} + +ID_INLINE idVec3 &idVec3::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + return *this; +} + +ID_INLINE idVec3 idVec3::operator-( const idVec3 &a ) const { + return idVec3( x - a.x, y - a.y, z - a.z ); +} + +ID_INLINE float idVec3::operator*( const idVec3 &a ) const { + return x * a.x + y * a.y + z * a.z; +} + +ID_INLINE idVec3 idVec3::operator*( const float a ) const { + return idVec3( x * a, y * a, z * a ); +} + +ID_INLINE idVec3 idVec3::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec3( x * inva, y * inva, z * inva ); +} + +ID_INLINE idVec3 operator*( const float a, const idVec3 b ) { + return idVec3( b.x * a, b.y * a, b.z * a ); +} + +ID_INLINE idVec3 idVec3::operator+( const idVec3 &a ) const { + return idVec3( x + a.x, y + a.y, z + a.z ); +} + +ID_INLINE idVec3 &idVec3::operator+=( const idVec3 &a ) { + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator/=( const idVec3 &a ) { + x /= a.x; + y /= a.y; + z /= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator-=( const idVec3 &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + + return *this; +} + +ID_INLINE bool idVec3::Compare( const idVec3 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) ); +} + +ID_INLINE bool idVec3::Compare( const idVec3 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec3::operator==( const idVec3 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec3::operator!=( const idVec3 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec3::NormalizeFast() { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; +} + +ID_INLINE bool idVec3::FixDegenerateNormal() { + if ( x == 0.0f ) { + if ( y == 0.0f ) { + if ( z > 0.0f ) { + if ( z != 1.0f ) { + z = 1.0f; + return true; + } + } else { + if ( z != -1.0f ) { + z = -1.0f; + return true; + } + } + return false; + } else if ( z == 0.0f ) { + if ( y > 0.0f ) { + if ( y != 1.0f ) { + y = 1.0f; + return true; + } + } else { + if ( y != -1.0f ) { + y = -1.0f; + return true; + } + } + return false; + } + } else if ( y == 0.0f ) { + if ( z == 0.0f ) { + if ( x > 0.0f ) { + if ( x != 1.0f ) { + x = 1.0f; + return true; + } + } else { + if ( x != -1.0f ) { + x = -1.0f; + return true; + } + } + return false; + } + } + if ( idMath::Fabs( x ) == 1.0f ) { + if ( y != 0.0f || z != 0.0f ) { + y = z = 0.0f; + return true; + } + return false; + } else if ( idMath::Fabs( y ) == 1.0f ) { + if ( x != 0.0f || z != 0.0f ) { + x = z = 0.0f; + return true; + } + return false; + } else if ( idMath::Fabs( z ) == 1.0f ) { + if ( x != 0.0f || y != 0.0f ) { + x = y = 0.0f; + return true; + } + return false; + } + return false; +} + +ID_INLINE bool idVec3::FixDenormals() { + bool denormal = false; + if ( fabs( x ) < 1e-30f ) { + x = 0.0f; + denormal = true; + } + if ( fabs( y ) < 1e-30f ) { + y = 0.0f; + denormal = true; + } + if ( fabs( z ) < 1e-30f ) { + z = 0.0f; + denormal = true; + } + return denormal; +} + +ID_INLINE idVec3 idVec3::Cross( const idVec3 &a ) const { + return idVec3( y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x ); +} + +ID_INLINE idVec3 &idVec3::Cross( const idVec3 &a, const idVec3 &b ) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; +} + +ID_INLINE float idVec3::Length() const { + return ( float )idMath::Sqrt( x * x + y * y + z * z ); +} + +ID_INLINE float idVec3::LengthSqr() const { + return ( x * x + y * y + z * z ); +} + +ID_INLINE float idVec3::LengthFast() const { + float sqrLength; + + sqrLength = x * x + y * y + z * z; + return sqrLength * idMath::InvSqrt( sqrLength ); +} + +ID_INLINE float idVec3::Normalize() { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; +} + +ID_INLINE idVec3 idVec3::Truncate( float length ) const { + if ( length < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return vec3_zero; + } else { + float length2 = LengthSqr(); + if ( length2 > length * length ) { + float ilength = length * idMath::InvSqrt( length2 ); + return *this * ilength; + } + } + return *this; +} + +ID_INLINE void idVec3::Clamp( const idVec3 &min, const idVec3 &max ) { + if ( x < min.x ) { + x = min.x; + } else if ( x > max.x ) { + x = max.x; + } + if ( y < min.y ) { + y = min.y; + } else if ( y > max.y ) { + y = max.y; + } + if ( z < min.z ) { + z = min.z; + } else if ( z > max.z ) { + z = max.z; + } +} + +ID_INLINE void idVec3::Snap() { + x = floor( x + 0.5f ); + y = floor( y + 0.5f ); + z = floor( z + 0.5f ); +} + +ID_INLINE void idVec3::SnapInt() { + x = float( int( x ) ); + y = float( int( y ) ); + z = float( int( z ) ); +} + +ID_INLINE int idVec3::GetDimension() const { + return 3; +} + +ID_INLINE const idVec2 &idVec3::ToVec2() const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec2 &idVec3::ToVec2() { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec3::ToFloatPtr() const { + return &x; +} + +ID_INLINE float *idVec3::ToFloatPtr() { + return &x; +} + +ID_INLINE void idVec3::NormalVectors( idVec3 &left, idVec3 &down ) const { + float d; + + d = x * x + y * y; + if ( !d ) { + left[0] = 1; + left[1] = 0; + left[2] = 0; + } else { + d = idMath::InvSqrt( d ); + left[0] = -y * d; + left[1] = x * d; + left[2] = 0; + } + down = left.Cross( *this ); +} + +ID_INLINE void idVec3::OrthogonalBasis( idVec3 &left, idVec3 &up ) const { + float l, s; + + if ( idMath::Fabs( z ) > 0.7f ) { + l = y * y + z * z; + s = idMath::InvSqrt( l ); + up[0] = 0; + up[1] = z * s; + up[2] = -y * s; + left[0] = l * s; + left[1] = -x * up[2]; + left[2] = x * up[1]; + } + else { + l = x * x + y * y; + s = idMath::InvSqrt( l ); + left[0] = -y * s; + left[1] = x * s; + left[2] = 0; + up[0] = -z * left[1]; + up[1] = z * left[0]; + up[2] = l * s; + } +} + +ID_INLINE void idVec3::ProjectOntoPlane( const idVec3 &normal, const float overBounce ) { + float backoff; + + backoff = *this * normal; + + if ( overBounce != 1.0 ) { + if ( backoff < 0 ) { + backoff *= overBounce; + } else { + backoff /= overBounce; + } + } + + *this -= backoff * normal; +} + +ID_INLINE bool idVec3::ProjectAlongPlane( const idVec3 &normal, const float epsilon, const float overBounce ) { + idVec3 cross; + float len; + + cross = this->Cross( normal ).Cross( (*this) ); + // normalize so a fixed epsilon can be used + cross.Normalize(); + len = normal * cross; + if ( idMath::Fabs( len ) < epsilon ) { + return false; + } + cross *= overBounce * ( normal * (*this) ) / len; + (*this) -= cross; + return true; +} + +//=============================================================== +// +// idTupleSize< idVec3 > - Specialization to get the size +// of an idVec3 generically. +// +//=============================================================== + +template<> +struct idTupleSize< idVec3 > { + enum { value = 3 }; +}; + +//=============================================================== +// +// idVec4 - 4D vector +// +//=============================================================== + +class idVec4 { +public: + float x; + float y; + float z; + float w; + + idVec4() { } + explicit idVec4( const float x ) { Set( x, x, x, x ); } + explicit idVec4( const float x, const float y, const float z, const float w ) { Set( x, y, z, w ); } + + void Set( const float x, const float y, const float z, const float w ); + void Zero(); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec4 operator-() const; + float operator*( const idVec4 &a ) const; + idVec4 operator*( const float a ) const; + idVec4 operator/( const float a ) const; + idVec4 operator+( const idVec4 &a ) const; + idVec4 operator-( const idVec4 &a ) const; + idVec4 & operator+=( const idVec4 &a ); + idVec4 & operator-=( const idVec4 &a ); + idVec4 & operator/=( const idVec4 &a ); + idVec4 & operator/=( const float a ); + idVec4 & operator*=( const float a ); + + friend idVec4 operator*( const float a, const idVec4 b ); + + idVec4 Multiply( const idVec4 & a ) const; + + bool Compare( const idVec4 &a ) const; // exact compare, no epsilon + bool Compare( const idVec4 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec4 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec4 &a ) const; // exact compare, no epsilon + + float Length() const; + float LengthSqr() const; + float Normalize(); // returns length + float NormalizeFast(); // returns length + + int GetDimension() const; + + const idVec2 & ToVec2() const; + idVec2 & ToVec2(); + const idVec3 & ToVec3() const; + idVec3 & ToVec3(); + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec4 &v1, const idVec4 &v2, const float l ); +}; + +extern idVec4 vec4_origin; +#define vec4_zero vec4_origin + +ID_INLINE void idVec4::Set( const float x, const float y, const float z, const float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4::Zero() { + x = y = z = w = 0.0f; +} + +ID_INLINE float idVec4::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec4::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec4 idVec4::operator-() const { + return idVec4( -x, -y, -z, -w ); +} + +ID_INLINE idVec4 idVec4::operator-( const idVec4 &a ) const { + return idVec4( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE float idVec4::operator*( const idVec4 &a ) const { + return x * a.x + y * a.y + z * a.z + w * a.w; +} + +ID_INLINE idVec4 idVec4::operator*( const float a ) const { + return idVec4( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idVec4 idVec4::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec4( x * inva, y * inva, z * inva, w * inva ); +} + +ID_INLINE idVec4 operator*( const float a, const idVec4 b ) { + return idVec4( b.x * a, b.y * a, b.z * a, b.w * a ); +} + +ID_INLINE idVec4 idVec4::operator+( const idVec4 &a ) const { + return idVec4( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idVec4 &idVec4::operator+=( const idVec4 &a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator/=( const idVec4 &a ) { + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + w *= inva; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator-=( const idVec4 &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +ID_INLINE idVec4 idVec4::Multiply( const idVec4 & a ) const { + return idVec4( x * a.x, y * a.y, z * a.z, w * a.w ); +} + +ID_INLINE bool idVec4::Compare( const idVec4 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && w == a.w ); +} + +ID_INLINE bool idVec4::Compare( const idVec4 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( w - a.w ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec4::operator==( const idVec4 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec4::operator!=( const idVec4 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec4::Length() const { + return ( float )idMath::Sqrt( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4::LengthSqr() const { + return ( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4::Normalize() { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec4::NormalizeFast() { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec4::GetDimension() const { + return 4; +} + +ID_INLINE const idVec2 &idVec4::ToVec2() const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec2 &idVec4::ToVec2() { + return *reinterpret_cast(this); +} + +ID_INLINE const idVec3 &idVec4::ToVec3() const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec3 &idVec4::ToVec3() { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec4::ToFloatPtr() const { + return &x; +} + +ID_INLINE float *idVec4::ToFloatPtr() { + return &x; +} + + +//=============================================================== +// +// idVec5 - 5D vector +// +//=============================================================== + +class idVec5 { +public: + float x; + float y; + float z; + float s; + float t; + + idVec5(); + explicit idVec5( const idVec3 &xyz, const idVec2 &st ); + explicit idVec5( const float x, const float y, const float z, const float s, const float t ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idVec5 & operator=( const idVec3 &a ); + + int GetDimension() const; + + const idVec3 & ToVec3() const; + idVec3 & ToVec3(); + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec5 &v1, const idVec5 &v2, const float l ); +}; + +extern idVec5 vec5_origin; +#define vec5_zero vec5_origin + +ID_INLINE idVec5::idVec5() { +} + +ID_INLINE idVec5::idVec5( const idVec3 &xyz, const idVec2 &st ) { + x = xyz.x; + y = xyz.y; + z = xyz.z; + s = st[0]; + t = st[1]; +} + +ID_INLINE idVec5::idVec5( const float x, const float y, const float z, const float s, const float t ) { + this->x = x; + this->y = y; + this->z = z; + this->s = s; + this->t = t; +} + +ID_INLINE float idVec5::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec5::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec5 &idVec5::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + s = t = 0; + return *this; +} + +ID_INLINE int idVec5::GetDimension() const { + return 5; +} + +ID_INLINE const idVec3 &idVec5::ToVec3() const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec3 &idVec5::ToVec3() { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec5::ToFloatPtr() const { + return &x; +} + +ID_INLINE float *idVec5::ToFloatPtr() { + return &x; +} + + +//=============================================================== +// +// idVec6 - 6D vector +// +//=============================================================== + +class idVec6 { +public: + idVec6(); + explicit idVec6( const float *a ); + explicit idVec6( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + + void Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + void Zero(); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec6 operator-() const; + idVec6 operator*( const float a ) const; + idVec6 operator/( const float a ) const; + float operator*( const idVec6 &a ) const; + idVec6 operator-( const idVec6 &a ) const; + idVec6 operator+( const idVec6 &a ) const; + idVec6 & operator*=( const float a ); + idVec6 & operator/=( const float a ); + idVec6 & operator+=( const idVec6 &a ); + idVec6 & operator-=( const idVec6 &a ); + + friend idVec6 operator*( const float a, const idVec6 b ); + + bool Compare( const idVec6 &a ) const; // exact compare, no epsilon + bool Compare( const idVec6 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec6 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec6 &a ) const; // exact compare, no epsilon + + float Length() const; + float LengthSqr() const; + float Normalize(); // returns length + float NormalizeFast(); // returns length + + int GetDimension() const; + + const idVec3 & SubVec3( int index ) const; + idVec3 & SubVec3( int index ); + const float * ToFloatPtr() const; + float * ToFloatPtr(); + const char * ToString( int precision = 2 ) const; + +private: + float p[6]; +}; + +extern idVec6 vec6_origin; +#define vec6_zero vec6_origin +extern idVec6 vec6_infinity; + +ID_INLINE idVec6::idVec6() { +} + +ID_INLINE idVec6::idVec6( const float *a ) { + memcpy( p, a, 6 * sizeof( float ) ); +} + +ID_INLINE idVec6::idVec6( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE idVec6 idVec6::operator-() const { + return idVec6( -p[0], -p[1], -p[2], -p[3], -p[4], -p[5] ); +} + +ID_INLINE float idVec6::operator[]( const int index ) const { + return p[index]; +} + +ID_INLINE float &idVec6::operator[]( const int index ) { + return p[index]; +} + +ID_INLINE idVec6 idVec6::operator*( const float a ) const { + return idVec6( p[0]*a, p[1]*a, p[2]*a, p[3]*a, p[4]*a, p[5]*a ); +} + +ID_INLINE float idVec6::operator*( const idVec6 &a ) const { + return p[0] * a[0] + p[1] * a[1] + p[2] * a[2] + p[3] * a[3] + p[4] * a[4] + p[5] * a[5]; +} + +ID_INLINE idVec6 idVec6::operator/( const float a ) const { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + return idVec6( p[0]*inva, p[1]*inva, p[2]*inva, p[3]*inva, p[4]*inva, p[5]*inva ); +} + +ID_INLINE idVec6 idVec6::operator+( const idVec6 &a ) const { + return idVec6( p[0] + a[0], p[1] + a[1], p[2] + a[2], p[3] + a[3], p[4] + a[4], p[5] + a[5] ); +} + +ID_INLINE idVec6 idVec6::operator-( const idVec6 &a ) const { + return idVec6( p[0] - a[0], p[1] - a[1], p[2] - a[2], p[3] - a[3], p[4] - a[4], p[5] - a[5] ); +} + +ID_INLINE idVec6 &idVec6::operator*=( const float a ) { + p[0] *= a; + p[1] *= a; + p[2] *= a; + p[3] *= a; + p[4] *= a; + p[5] *= a; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator/=( const float a ) { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + p[0] *= inva; + p[1] *= inva; + p[2] *= inva; + p[3] *= inva; + p[4] *= inva; + p[5] *= inva; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator+=( const idVec6 &a ) { + p[0] += a[0]; + p[1] += a[1]; + p[2] += a[2]; + p[3] += a[3]; + p[4] += a[4]; + p[5] += a[5]; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator-=( const idVec6 &a ) { + p[0] -= a[0]; + p[1] -= a[1]; + p[2] -= a[2]; + p[3] -= a[3]; + p[4] -= a[4]; + p[5] -= a[5]; + return *this; +} + +ID_INLINE idVec6 operator*( const float a, const idVec6 b ) { + return b * a; +} + +ID_INLINE bool idVec6::Compare( const idVec6 &a ) const { + return ( ( p[0] == a[0] ) && ( p[1] == a[1] ) && ( p[2] == a[2] ) && + ( p[3] == a[3] ) && ( p[4] == a[4] ) && ( p[5] == a[5] ) ); +} + +ID_INLINE bool idVec6::Compare( const idVec6 &a, const float epsilon ) const { + if ( idMath::Fabs( p[0] - a[0] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[1] - a[1] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[2] - a[2] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[3] - a[3] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[4] - a[4] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[5] - a[5] ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec6::operator==( const idVec6 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec6::operator!=( const idVec6 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idVec6::Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE void idVec6::Zero() { + p[0] = p[1] = p[2] = p[3] = p[4] = p[5] = 0.0f; +} + +ID_INLINE float idVec6::Length() const { + return ( float )idMath::Sqrt( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5] ); +} + +ID_INLINE float idVec6::LengthSqr() const { + return ( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5] ); +} + +ID_INLINE float idVec6::Normalize() { + float sqrLength, invLength; + + sqrLength = p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5]; + invLength = idMath::InvSqrt( sqrLength ); + p[0] *= invLength; + p[1] *= invLength; + p[2] *= invLength; + p[3] *= invLength; + p[4] *= invLength; + p[5] *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec6::NormalizeFast() { + float sqrLength, invLength; + + sqrLength = p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5]; + invLength = idMath::InvSqrt( sqrLength ); + p[0] *= invLength; + p[1] *= invLength; + p[2] *= invLength; + p[3] *= invLength; + p[4] *= invLength; + p[5] *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec6::GetDimension() const { + return 6; +} + +ID_INLINE const idVec3 &idVec6::SubVec3( int index ) const { + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE idVec3 &idVec6::SubVec3( int index ) { + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE const float *idVec6::ToFloatPtr() const { + return p; +} + +ID_INLINE float *idVec6::ToFloatPtr() { + return p; +} + +//=============================================================== +// +// idPolar3 +// +//=============================================================== + +class idPolar3 { +public: + float radius, theta, phi; + + idPolar3(); + explicit idPolar3( const float radius, const float theta, const float phi ); + + void Set( const float radius, const float theta, const float phi ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idPolar3 operator-() const; + idPolar3 & operator=( const idPolar3 &a ); + + idVec3 ToVec3() const; +}; + +ID_INLINE idPolar3::idPolar3() { +} + +ID_INLINE idPolar3::idPolar3( const float radius, const float theta, const float phi ) { + assert( radius > 0 ); + this->radius = radius; + this->theta = theta; + this->phi = phi; +} + +ID_INLINE void idPolar3::Set( const float radius, const float theta, const float phi ) { + assert( radius > 0 ); + this->radius = radius; + this->theta = theta; + this->phi = phi; +} + +ID_INLINE float idPolar3::operator[]( const int index ) const { + return ( &radius )[ index ]; +} + +ID_INLINE float &idPolar3::operator[]( const int index ) { + return ( &radius )[ index ]; +} + +ID_INLINE idPolar3 idPolar3::operator-() const { + return idPolar3( radius, -theta, -phi ); +} + +ID_INLINE idPolar3 &idPolar3::operator=( const idPolar3 &a ) { + radius = a.radius; + theta = a.theta; + phi = a.phi; + return *this; +} + +ID_INLINE idVec3 idPolar3::ToVec3() const { + float sp, cp, st, ct; + idMath::SinCos( phi, sp, cp ); + idMath::SinCos( theta, st, ct ); + return idVec3( cp * radius * ct, cp * radius * st, radius * sp ); +} + + +/* +=============================================================================== + + Old 3D vector macros, should no longer be used. + +=============================================================================== +*/ + +#define VectorMA( v, s, b, o ) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) + +#endif /* !__MATH_VECTOR_H__ */ diff --git a/neo/idlib/math/VectorI.h b/neo/idlib/math/VectorI.h new file mode 100644 index 00000000..4be6cff1 --- /dev/null +++ b/neo/idlib/math/VectorI.h @@ -0,0 +1,97 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __MATH_VECTORI_H__ +#define __MATH_VECTORI_H__ + +static ID_INLINE int MinInt( int a, int b ) { return (a) < (b) ? (a) : (b); } +static ID_INLINE int MaxInt( int a, int b ) { return (a) < (b) ? (b) : (a); } + +class idVec2i { +public: + int x, y; + + idVec2i() {} + idVec2i( int _x, int _y ) : x(_x), y(_y ) {} + + void Set( int _x, int _y ) { x = _x; y = _y; } + int Area() const { return x * y; }; + + void Min( idVec2i &v ) { x = MinInt( x, v.x ); y = MinInt( y, v.y ); } + void Max( idVec2i &v ) { x = MaxInt( x, v.x ); y = MaxInt( y, v.y ); } + + int operator[]( const int index ) const { assert( index == 0 || index == 1 ); return (&x)[index]; } + int & operator[]( const int index ) { assert( index == 0 || index == 1 ); return (&x)[index]; } + + idVec2i operator-() const { return idVec2i( -x, -y ); } + idVec2i operator!() const { return idVec2i( !x, !y ); } + + idVec2i operator>>( const int a ) const { return idVec2i( x >> a, y >> a ); } + idVec2i operator<<( const int a ) const { return idVec2i( x << a, y << a ); } + idVec2i operator&( const int a ) const { return idVec2i( x & a, y & a ); } + idVec2i operator|( const int a ) const { return idVec2i( x | a, y | a ); } + idVec2i operator^( const int a ) const { return idVec2i( x ^ a, y ^ a ); } + idVec2i operator*( const int a ) const { return idVec2i( x * a, y * a ); } + idVec2i operator/( const int a ) const { return idVec2i( x / a, y / a ); } + idVec2i operator+( const int a ) const { return idVec2i( x + a, y + a ); } + idVec2i operator-( const int a ) const { return idVec2i( x - a, y - a ); } + + bool operator==( const idVec2i &a ) const { return a.x == x && a.y == y; }; + bool operator!=( const idVec2i &a ) const { return a.x != x || a.y != y; }; + + idVec2i operator>>( const idVec2i &a ) const { return idVec2i( x >> a.x, y >> a.y ); } + idVec2i operator<<( const idVec2i &a ) const { return idVec2i( x << a.x, y << a.y ); } + idVec2i operator&( const idVec2i &a ) const { return idVec2i( x & a.x, y & a.y ); } + idVec2i operator|( const idVec2i &a ) const { return idVec2i( x | a.x, y | a.y ); } + idVec2i operator^( const idVec2i &a ) const { return idVec2i( x ^ a.x, y ^ a.y ); } + idVec2i operator*( const idVec2i &a ) const { return idVec2i( x * a.x, y * a.y ); } + idVec2i operator/( const idVec2i &a ) const { return idVec2i( x / a.x, y / a.y ); } + idVec2i operator+( const idVec2i &a ) const { return idVec2i( x + a.x, y + a.y ); } + idVec2i operator-( const idVec2i &a ) const { return idVec2i( x - a.x, y - a.y ); } + + idVec2i & operator+=( const int a ) { x += a; y += a; return *this; } + idVec2i & operator-=( const int a ) { x -= a; y -= a; return *this; } + idVec2i & operator/=( const int a ) { x /= a; y /= a; return *this; } + idVec2i & operator*=( const int a ) { x *= a; y *= a; return *this; } + idVec2i & operator>>=( const int a ) { x >>= a; y >>= a; return *this; } + idVec2i & operator<<=( const int a ) { x <<= a; y <<= a; return *this; } + idVec2i & operator&=( const int a ) { x &= a; y &= a; return *this; } + idVec2i & operator|=( const int a ) { x |= a; y |= a; return *this; } + idVec2i & operator^=( const int a ) { x ^= a; y ^= a; return *this; } + + idVec2i & operator>>=( const idVec2i &a ) { x >>= a.x; y >>= a.y; return *this; } + idVec2i & operator<<=( const idVec2i &a ) { x <<= a.x; y <<= a.y; return *this; } + idVec2i & operator&=( const idVec2i &a ) { x &= a.x; y &= a.y; return *this; } + idVec2i & operator|=( const idVec2i &a ) { x |= a.x; y |= a.y; return *this; } + idVec2i & operator^=( const idVec2i &a ) { x ^= a.x; y ^= a.y; return *this; } + idVec2i & operator+=( const idVec2i &a ) { x += a.x; y += a.y; return *this; } + idVec2i & operator-=( const idVec2i &a ) { x -= a.x; y -= a.y; return *this; } + idVec2i & operator/=( const idVec2i &a ) { x /= a.x; y /= a.y; return *this; } + idVec2i & operator*=( const idVec2i &a ) { x *= a.x; y *= a.y; return *this; } +}; + +#endif diff --git a/neo/idlib/precompiled.cpp b/neo/idlib/precompiled.cpp new file mode 100644 index 00000000..dbaeaa01 --- /dev/null +++ b/neo/idlib/precompiled.cpp @@ -0,0 +1,28 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#include "precompiled.h" diff --git a/neo/idlib/precompiled.h b/neo/idlib/precompiled.h new file mode 100644 index 00000000..9dcf80ce --- /dev/null +++ b/neo/idlib/precompiled.h @@ -0,0 +1,161 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __PRECOMPILED_H__ +#define __PRECOMPILED_H__ + +#include "sys/sys_defines.h" +#include "sys/sys_builddefines.h" +#include "sys/sys_includes.h" +#include "sys/sys_assert.h" +#include "sys/sys_types.h" +#include "sys/sys_intrinsics.h" +#include "sys/sys_threading.h" + +//----------------------------------------------------- + +#define ID_TIME_T int64 // Signed because -1 means "File not found" and we don't want that to compare > than any other time + +// non-portable system services +#include "../sys/sys_public.h" + +// id lib +#include "../idlib/Lib.h" + +#include "sys/sys_filesystem.h" + +// framework +#include "../framework/BuildVersion.h" +#include "../framework/Licensee.h" +#include "../framework/CmdSystem.h" +#include "../framework/CVarSystem.h" +#include "../framework/Common.h" +#include "../framework/File.h" +#include "../framework/File_Manifest.h" +#include "../framework/File_SaveGame.h" +#include "../framework/File_Resource.h" +#include "../framework/FileSystem.h" +#include "../framework/UsercmdGen.h" +#include "../framework/Serializer.h" +#include "../framework/PlayerProfile.h" + +// decls +#include "../framework/TokenParser.h" +#include "../framework/DeclManager.h" +#include "../framework/DeclTable.h" +#include "../framework/DeclSkin.h" +#include "../framework/DeclEntityDef.h" +#include "../framework/DeclFX.h" +#include "../framework/DeclParticle.h" +#include "../framework/DeclAF.h" +#include "../framework/DeclPDA.h" + +// We have expression parsing and evaluation code in multiple places: +// materials, sound shaders, and guis. We should unify them. +const int MAX_EXPRESSION_OPS = 4096; +const int MAX_EXPRESSION_REGISTERS = 4096; + +// renderer +#include "../renderer/OpenGL/qgl.h" +#include "../renderer/Cinematic.h" +#include "../renderer/Material.h" +#include "../renderer/BufferObject.h" +#include "../renderer/VertexCache.h" +#include "../renderer/Model.h" +#include "../renderer/ModelManager.h" +#include "../renderer/RenderSystem.h" +#include "../renderer/RenderWorld.h" + +// sound engine +#include "../sound/sound.h" + +// user interfaces +#include "../ui/ListGUI.h" +#include "../ui/UserInterface.h" + +#include "../swf/SWF.h" + +// collision detection system +#include "../cm/CollisionModel.h" + +// AAS files and manager +#include "../aas/AASFile.h" +#include "../aas/AASFileManager.h" + +// game +#include "../d3xp/Game.h" + +// Session / Network +#include "../sys/LightweightCompression.h" +#include "../sys/Snapshot.h" +#include "../sys/PacketProcessor.h" +#include "../sys/SnapshotProcessor.h" + +#include "../sys/sys_savegame.h" +#include "../sys/sys_session_savegames.h" +#include "../sys/sys_profile.h" +#include "../sys/sys_localuser.h" +#include "../sys/sys_signin.h" +#include "../sys/sys_stats_misc.h" +#include "../sys/sys_stats.h" +#include "../sys/sys_session.h" +#include "../sys/sys_achievements.h" + +//----------------------------------------------------- + +#ifndef _D3SDK + +#ifdef GAME_DLL + +#include "../d3xp/Game_local.h" + +#else + +#include "../framework/DemoChecksum.h" + +// framework +#include "../framework/Compressor.h" +#include "../framework/EventLoop.h" +#include "../framework/KeyInput.h" +#include "../framework/EditField.h" +#include "../framework/DebugGraph.h" +#include "../framework/Console.h" +#include "../framework/DemoFile.h" +#include "../framework/Common_dialog.h" + +#endif /* !GAME_DLL */ + +#endif /* !_D3SDK */ + +//----------------------------------------------------- + +#undef min +#undef max +#include // for min / max / swap + +#endif /* !__PRECOMPILED_H__ */ diff --git a/neo/idlib/sys/sys_alloc_tags.h b/neo/idlib/sys/sys_alloc_tags.h new file mode 100644 index 00000000..1d3a31f5 --- /dev/null +++ b/neo/idlib/sys/sys_alloc_tags.h @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +MEM_TAG( UNSET ) // This should never be used +MEM_TAG( STATIC_EXE ) // The static exe, generally how much memory we are using before our main() function ever runs +MEM_TAG( DEBUG ) // Crap we don't care about, because it won't be in a retail build +MEM_TAG( NEW ) // Crap allocated with new which hasn't been given an explicit tag +MEM_TAG( BLOCKALLOC ) // Crap allocated with idBlockAlloc which hasn't been given an explicit tag +MEM_TAG( PHYSICAL ) +MEM_TAG( TRI_VERTS ) +MEM_TAG( TRI_INDEXES ) +MEM_TAG( TRI_SHADOW ) +MEM_TAG( TRI_PLANES ) +MEM_TAG( TRI_SIL_INDEXES ) +MEM_TAG( TRI_SIL_EDGE ) +MEM_TAG( TRI_DOMINANT_TRIS ) +MEM_TAG( TRI_MIR_VERT ) +MEM_TAG( TRI_DUP_VERT ) +MEM_TAG( SRFTRIS ) +MEM_TAG( TEMP ) // Temp data which should be automatically freed at the end of the function +MEM_TAG( PAGE ) +MEM_TAG( DEFRAG_BLOCK ) +MEM_TAG( MATH ) +MEM_TAG( MD5_WEIGHT ) +MEM_TAG( MD5_BASE ) +MEM_TAG( MD5_ANIM ) +MEM_TAG( MD5_INDEX ) +MEM_TAG( JOINTMAT ) +MEM_TAG( DECAL ) +MEM_TAG( CULLBITS ) +MEM_TAG( TEXCOORDS ) +MEM_TAG( VERTEXREMAP ) +MEM_TAG( JOINTBUFFER ) +MEM_TAG( IMAGE ) +MEM_TAG( DXBUFFER ) +MEM_TAG( AUDIO ) +MEM_TAG( FUNC_CALLBACK ) +MEM_TAG( SAVEGAMES ) +MEM_TAG( IDFILE ) +MEM_TAG( NETWORKING ) +MEM_TAG( SWF ) +MEM_TAG( STEAM ) +MEM_TAG( IDLIB ) +MEM_TAG( IDLIB_LIST ) +MEM_TAG( IDLIB_LIST_IMAGE ) +MEM_TAG( IDLIB_LIST_SURFACE ) +MEM_TAG( IDLIB_LIST_PHYSICS ) +MEM_TAG( IDLIB_LIST_DECL ) +MEM_TAG( IDLIB_LIST_CMD ) +MEM_TAG( IDLIB_LIST_TRIANGLES ) +MEM_TAG( IDLIB_LIST_MATERIAL ) +MEM_TAG( IDLIB_LIST_SOUND ) +MEM_TAG( IDLIB_LIST_SNAPSHOT ) +MEM_TAG( IDLIB_LIST_MENU ) +MEM_TAG( IDLIB_LIST_MENUWIDGET ) +MEM_TAG( IDLIB_LIST_PLAYER ) +MEM_TAG( IDLIB_LIST_MAP ) +MEM_TAG( IDLIB_HASH ) +MEM_TAG( IDLIB_STRING ) +MEM_TAG( IDLIB_SURFACE ) +MEM_TAG( IDLIB_WINDING ) +MEM_TAG( IDLIB_LEXER ) +MEM_TAG( IDLIB_PARSER ) +MEM_TAG( AF ) +MEM_TAG( COLLISION ) +MEM_TAG( COLLISION_QUERY ) +MEM_TAG( DECLTEXT ) +MEM_TAG( RSX ) +MEM_TAG( CVAR ) +MEM_TAG( CRAP ) // Crap allocated with new which hasn't been given an explicit tag +MEM_TAG( CINEMATIC ) +MEM_TAG( FONT ) +MEM_TAG( MATERIAL ) +MEM_TAG( MODEL ) +MEM_TAG( RENDER ) +MEM_TAG( RENDER_TOOLS ) +MEM_TAG( RENDER_WINDING ) +MEM_TAG( RENDER_STATIC ) +MEM_TAG( RENDER_ENTITY ) +MEM_TAG( RENDER_LIGHT ) +MEM_TAG( RENDER_INTERACTION ) +MEM_TAG( SURFACE ) +MEM_TAG( LIGHT ) +MEM_TAG( AI ) +MEM_TAG( SCRIPT ) +MEM_TAG( EVENTS ) +MEM_TAG( JPG ) +MEM_TAG( AAS ) +MEM_TAG( STRING ) +MEM_TAG( THREAD ) +MEM_TAG( DECL ) +MEM_TAG( SYSTEM ) +MEM_TAG( PSN ) +MEM_TAG( OLD_UI ) +MEM_TAG( ANIM ) +MEM_TAG( ANIMWEB ) +MEM_TAG( PHYSICS ) +MEM_TAG( PARTICLE ) +MEM_TAG( ENTITY ) +MEM_TAG( GAME ) +MEM_TAG( FX ) +MEM_TAG( PVS ) +MEM_TAG( IDCLASS ) +MEM_TAG( ZIP ) +MEM_TAG( JOBLIST ) +MEM_TAG( AMPLITUDE ) +MEM_TAG( RESOURCE ) +MEM_TAG( ACHIEVEMENT ) +MEM_TAG( TARGET ) +MEM_TAG( BINK ) +MEM_TAG( PROJECTILE ) +MEM_TAG( MOVER ) +MEM_TAG( ACTOR ) +MEM_TAG( PHYSICS_CLIP ) +MEM_TAG( PHYSICS_CLIP_MOVER ) +MEM_TAG( PHYSICS_CLIP_AF ) +MEM_TAG( PHYSICS_CLIP_BRITTLE ) +MEM_TAG( PHYSICS_CLIP_ENTITY ) +MEM_TAG( PHYSICS_BRITTLE ) +MEM_TAG( PHYSICS_AF ) +MEM_TAG( RENDERPROG ) +#undef MEM_TAG diff --git a/neo/idlib/sys/sys_assert.cpp b/neo/idlib/sys/sys_assert.cpp new file mode 100644 index 00000000..adb6afc1 --- /dev/null +++ b/neo/idlib/sys/sys_assert.cpp @@ -0,0 +1,90 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../precompiled.h" + +/* +================================================================================================ +Contains the AssertMacro implementation. +================================================================================================ +*/ + +idCVar com_assertOutOfDebugger( "com_assertOutOfDebugger", "0", CVAR_BOOL, "by default, do not assert while not running under the debugger" ); + +struct skippedAssertion_t { + skippedAssertion_t() : + file( NULL ), + line( -1 ) { + } + const char * file; + int line; +}; +static idStaticList< skippedAssertion_t,20 > skippedAssertions; + +/* +======================== +AssertFailed +======================== +*/ +bool AssertFailed( const char * file, int line, const char * expression ) { + // Set this to true to skip ALL assertions, including ones YOU CAUSE! + static volatile bool skipAllAssertions = false; + if ( skipAllAssertions ) { + return false; + } + + // Set this to true to skip ONLY this assertion + static volatile bool skipThisAssertion = false; + skipThisAssertion = false; + + for ( int i = 0; i < skippedAssertions.Num(); i++ ) { + if ( skippedAssertions[i].file == file && skippedAssertions[i].line == line ) { + skipThisAssertion = true; + // Set breakpoint here to re-enable + if ( !skipThisAssertion ) { + skippedAssertions.RemoveIndexFast( i ); + } + return false; + } + } + + idLib::Warning( "ASSERTION FAILED! %s(%d): '%s'", file, line, expression ); + + if ( IsDebuggerPresent() || com_assertOutOfDebugger.GetBool() ) { + __debugbreak(); + } + + if ( skipThisAssertion ) { + skippedAssertion_t * skipped = skippedAssertions.Alloc(); + skipped->file = file; + skipped->line = line; + } + + return true; +} + diff --git a/neo/idlib/sys/sys_assert.h b/neo/idlib/sys/sys_assert.h new file mode 100644 index 00000000..b9d9b0e9 --- /dev/null +++ b/neo/idlib/sys/sys_assert.h @@ -0,0 +1,138 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_ASSERT_H__ +#define __SYS_ASSERT_H__ + +/* +================================================================================================ + +Getting assert() to work as we want on all platforms and code analysis tools can be tricky. + +================================================================================================ +*/ + +bool AssertFailed( const char *file, int line, const char *expression ); + +// tell PC-Lint that assert failed won't return, which means it can assume the conditions +// are true for subsequent analysis. +//lint -function( exit, AssertFailed ) + +//====================== assert in debug mode ======================= +#if defined( _DEBUG ) || defined( _lint ) + + +#undef assert + +// idassert is useful for cases where some external library (think MFC, etc.) +// decides it's a good idea to redefine assert on us +#define idassert( x ) (void)( ( !!( x ) ) || ( AssertFailed( __FILE__, __LINE__, #x ) ) ) + +// We have the code analysis tools on the 360 compiler, +// so let it know what our asserts are. +// The VS ultimate editions also get it on win32, but not x86 +#define assert( x ) __analysis_assume( x ) ; idassert( x ) + +#define verify( x ) ( ( x ) ? true : ( AssertFailed( __FILE__, __LINE__, #x ), false ) ) + + +#else // _DEBUG + +//====================== assert in release mode ======================= + +#define idassert( x ) { (( void )0); } + +#undef assert + +#define assert( x ) idassert( x ) + +#define verify( x ) ( ( x ) ? true : false ) + +#endif // _DEBUG + +//===================================================================== + +#define idreleaseassert( x ) (void)( ( !!( x ) ) || ( AssertFailed( __FILE__, __LINE__, #x ) ) ); + +#define release_assert( x ) idreleaseassert( x ) + +#define assert_2_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 1 ) == 0 ) +#define assert_4_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 3 ) == 0 ) +#define assert_8_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 7 ) == 0 ) +#define assert_16_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 15 ) == 0 ) +#define assert_32_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 31 ) == 0 ) +#define assert_64_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 63 ) == 0 ) +#define assert_128_byte_aligned( ptr ) assert( ( ((UINT_PTR)(ptr)) & 127 ) == 0 ) +#define assert_aligned_to_type_size( ptr ) assert( ( ((UINT_PTR)(ptr)) & ( sizeof( (ptr)[0] ) - 1 ) ) == 0 ) + +#if !defined( __TYPEINFOGEN__ ) && !defined( _lint ) // pcLint has problems with assert_offsetof() + +template struct compile_time_assert_failed; +template<> struct compile_time_assert_failed {}; +template struct compile_time_assert_test {}; +#define compile_time_assert_join2( a, b ) a##b +#define compile_time_assert_join( a, b ) compile_time_assert_join2(a,b) +#define compile_time_assert( x ) typedef compile_time_assert_test)> compile_time_assert_join(compile_time_assert_typedef_, __LINE__) + +#define assert_sizeof( type, size ) compile_time_assert( sizeof( type ) == size ) +#define assert_sizeof_8_byte_multiple( type ) compile_time_assert( ( sizeof( type ) & 7 ) == 0 ) +#define assert_sizeof_16_byte_multiple( type ) compile_time_assert( ( sizeof( type ) & 15 ) == 0 ) +#define assert_offsetof( type, field, offset ) compile_time_assert( offsetof( type, field ) == offset ) +#define assert_offsetof_8_byte_multiple( type, field ) compile_time_assert( ( offsetof( type, field ) & 7 ) == 0 ) +#define assert_offsetof_16_byte_multiple( type, field ) compile_time_assert( ( offsetof( type, field ) & 15 ) == 0 ) + +#else + +#define compile_time_assert( x ) +#define assert_sizeof( type, size ) +#define assert_sizeof_8_byte_multiple( type ) +#define assert_sizeof_16_byte_multiple( type ) +#define assert_offsetof( type, field, offset ) +#define assert_offsetof_8_byte_multiple( type, field ) +#define assert_offsetof_16_byte_multiple( type, field ) + +#endif + +// useful for verifying that an array of items has the same number of elements in it as an enum type +#define verify_array_size( _array_name_, _max_enum_ ) \ + compile_time_assert( sizeof( _array_name_ ) == ( _max_enum_ ) * sizeof( _array_name_[ 0 ] ) ) + + +// ai debugging macros (designed to limit ai interruptions to non-ai programmers) +#ifdef _DEBUG +//#define DEBUGAI // NOTE: uncomment for full ai debugging +#endif + +#ifdef DEBUGAI +#define ASSERTAI( x ) assert( x ) +#define VERIFYAI( x ) verify( x ) +#else // DEBUGAI +#define ASSERTAI( x ) +#define VERIFYAI( x ) ( ( x ) ? true : false ) +#endif // DEBUGAI + +#endif // !__SYS_ASSERT_H__ diff --git a/neo/idlib/sys/sys_builddefines.h b/neo/idlib/sys/sys_builddefines.h new file mode 100644 index 00000000..02f8d451 --- /dev/null +++ b/neo/idlib/sys/sys_builddefines.h @@ -0,0 +1,35 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +=============================================================================== + + Preprocessor settings for compiling different versions. + +=============================================================================== +*/ diff --git a/neo/idlib/sys/sys_defines.h b/neo/idlib/sys/sys_defines.h new file mode 100644 index 00000000..4a052a00 --- /dev/null +++ b/neo/idlib/sys/sys_defines.h @@ -0,0 +1,154 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef SYS_DEFINES_H +#define SYS_DEFINES_H + +/* +================================================================================================ + + PC Windows + +================================================================================================ +*/ + + +#define CPUSTRING "x86" + +#define BUILD_STRING "win-" CPUSTRING +#define BUILD_OS_ID 0 + +#define ALIGN16( x ) __declspec(align(16)) x +#define ALIGNTYPE16 __declspec(align(16)) +#define ALIGNTYPE128 __declspec(align(128)) +#define FORMAT_PRINTF( x ) + +#define PATHSEPARATOR_STR "\\" +#define PATHSEPARATOR_CHAR '\\' +#define NEWLINE "\r\n" + +#define ID_INLINE inline +#define ID_FORCE_INLINE __forceinline + +// lint complains that extern used with definition is a hazard, but it +// has the benefit (?) of making it illegal to take the address of the function +#ifdef _lint +#define ID_INLINE_EXTERN inline +#define ID_FORCE_INLINE_EXTERN __forceinline +#else +#define ID_INLINE_EXTERN extern inline +#define ID_FORCE_INLINE_EXTERN extern __forceinline +#endif + +// we should never rely on this define in our code. this is here so dodgy external libraries don't get confused +#ifndef WIN32 + #define WIN32 +#endif + +/* +================================================================================================ + +Defines and macros usable in all code + +================================================================================================ +*/ + +#define ALIGN( x, a ) ( ( ( x ) + ((a)-1) ) & ~((a)-1) ) + +#define _alloca16( x ) ((void *)ALIGN( (UINT_PTR)_alloca( ALIGN( x, 16 ) + 16 ), 16 ) ) +#define _alloca128( x ) ((void *)ALIGN( (UINT_PTR)_alloca( ALIGN( x, 128 ) + 128 ), 128 ) ) + +#define likely( x ) ( x ) +#define unlikely( x ) ( x ) + +// A macro to disallow the copy constructor and operator= functions +// NOTE: The macro contains "private:" so all members defined after it will be private until +// public: or protected: is specified. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ +private: \ + TypeName(const TypeName&); \ + void operator=(const TypeName&); + + +/* +================================================================================================ +Setup for /analyze code analysis, which we currently only have on the 360, but +we may get later for win32 if we buy the higher end vc++ licenses. + +Even with VS2010 ultmate, /analyze only works for x86, not x64 + +Also note the __analysis_assume macro in sys_assert.h relates to code analysis. + +This header should be included even by job code that doesn't reference the +bulk of the codebase, so it is the best place for analyze pragmas. +================================================================================================ +*/ + +// disable some /analyze warnings here +#pragma warning( disable: 6255 ) // warning C6255: _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead. (Note: _malloca requires _freea.) +#pragma warning( disable: 6262 ) // warning C6262: Function uses '36924' bytes of stack: exceeds /analyze:stacksize'32768'. Consider moving some data to heap +#pragma warning( disable: 6326 ) // warning C6326: Potential comparison of a constant with another constant + +#pragma warning( disable: 6031 ) // warning C6031: Return value ignored +// this warning fires whenever you have two calls to new in a function, but we assume new never fails, so it is not relevant for us +#pragma warning( disable: 6211 ) // warning C6211: Leaking memory 'staticModel' due to an exception. Consider using a local catch block to clean up memory + +// we want to fix all these at some point... +#pragma warning( disable: 6246 ) // warning C6246: Local declaration of 'es' hides declaration of the same name in outer scope. For additional information, see previous declaration at line '969' of 'w:\tech5\rage\game\ai\fsm\fsm_combat.cpp': Lines: 969 +#pragma warning( disable: 6244 ) // warning C6244: Local declaration of 'viewList' hides previous declaration at line '67' of 'w:\tech5\engine\renderer\rendertools.cpp' + +// win32 needs this, but 360 doesn't +#pragma warning( disable: 6540 ) // warning C6540: The use of attribute annotations on this function will invalidate all of its existing __declspec annotations [D:\tech5\engine\engine-10.vcxproj] + + +// checking format strings catches a LOT of errors +#include +#define VERIFY_FORMAT_STRING [SA_FormatString(Style="printf")] + + +// We need to inform the compiler that Error() and FatalError() will +// never return, so any conditions that leeds to them being called are +// guaranteed to be false in the following code +#define NO_RETURN __declspec(noreturn) + + +// I don't want to disable "warning C6031: Return value ignored" from /analyze +// but there are several cases with sprintf where we pre-initialized the variables +// being scanned into, so we truly don't care if they weren't all scanned. +// Rather than littering #pragma statements around these cases, we can assign the +// return value to this, which means we have considered the issue and decided that +// it doesn't require action. +// The volatile qualifier is to prevent:PVS-Studio warnings like: +// False 2 4214 V519 The 'ignoredReturnValue' object is assigned values twice successively. Perhaps this is a mistake. Check lines: 545, 547. Rage collisionmodelmanager_debug.cpp 547 False +extern volatile int ignoredReturnValue; + +#define MAX_TYPE( x ) ( ( ( ( 1 << ( ( sizeof( x ) - 1 ) * 8 - 1 ) ) - 1 ) << 8 ) | 255 ) +#define MIN_TYPE( x ) ( - MAX_TYPE( x ) - 1 ) +#define MAX_UNSIGNED_TYPE( x ) ( ( ( ( 1U << ( ( sizeof( x ) - 1 ) * 8 ) ) - 1 ) << 8 ) | 255U ) +#define MIN_UNSIGNED_TYPE( x ) 0 + +#endif diff --git a/neo/idlib/sys/sys_filesystem.h b/neo/idlib/sys/sys_filesystem.h new file mode 100644 index 00000000..a6b9621d --- /dev/null +++ b/neo/idlib/sys/sys_filesystem.h @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef SYS_FILESYSTEM_H +#define SYS_FILESYSTEM_H + +void Sys_Mkdir( const char *path ); +bool Sys_Rmdir( const char *path ); +bool Sys_IsFileWritable( const char *path ); + +enum sysFolder_t { + FOLDER_ERROR = -1, + FOLDER_NO = 0, + FOLDER_YES = 1 +}; + +// returns FOLDER_YES if the specified path is a folder +sysFolder_t Sys_IsFolder( const char *path ); + +// use fs_debug to verbose Sys_ListFiles +// returns -1 if directory was not found (the list is cleared) +int Sys_ListFiles( const char * directory, const char * extension, idList & list ); + +const char * Sys_EXEPath(); +const char * Sys_CWD(); + +const char * Sys_LaunchPath(); + +#endif diff --git a/neo/idlib/sys/sys_includes.h b/neo/idlib/sys/sys_includes.h new file mode 100644 index 00000000..9aea3ff3 --- /dev/null +++ b/neo/idlib/sys/sys_includes.h @@ -0,0 +1,106 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef SYS_INCLUDES_H +#define SYS_INCLUDES_H + +// Include the various platform specific header files (windows.h, etc) + +/* +================================================================================================ + + Windows + +================================================================================================ +*/ + + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // prevent auto literal to string conversion + +#ifndef _D3SDK +#ifndef GAME_DLL + +#define WINVER 0x501 + +#include +#include +#include + +#define DIRECTINPUT_VERSION 0x0800 // was 0x0700 with the old mssdk +#define DIRECTSOUND_VERSION 0x0800 + +#include +#include + +#endif /* !GAME_DLL */ +#endif /* !_D3SDK */ + +#include // needed for intrinsics like _mm_setzero_si28 + +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4244) // conversion to smaller type, possible loss of data +#pragma warning(disable : 4714) // function marked as __forceinline not inlined +#pragma warning(disable : 4996) // unsafe string operations + +#include // no malloc.h on mac or unix +#include // for qgl.h +#undef FindText // fix namespace pollution + +/* +================================================================================================ + + Common Include Files + +================================================================================================ +*/ + +#if !defined( _DEBUG ) && !defined( NDEBUG ) + // don't generate asserts + #define NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------- + +// Hacked stuff we may want to consider implementing later +class idScopedGlobalHeap { +}; + +#endif // SYS_INCLUDES_H diff --git a/neo/idlib/sys/sys_intrinsics.h b/neo/idlib/sys/sys_intrinsics.h new file mode 100644 index 00000000..12ad78dd --- /dev/null +++ b/neo/idlib/sys/sys_intrinsics.h @@ -0,0 +1,200 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_INTRIINSICS_H__ +#define __SYS_INTRIINSICS_H__ + +/* +================================================================================================ + + Scalar single precision floating-point intrinsics + +================================================================================================ +*/ + +ID_INLINE_EXTERN float __fmuls( float a, float b ) { return ( a * b ); } +ID_INLINE_EXTERN float __fmadds( float a, float b, float c ) { return ( a * b + c ); } +ID_INLINE_EXTERN float __fnmsubs( float a, float b, float c ) { return ( c - a * b ); } +ID_INLINE_EXTERN float __fsels( float a, float b, float c ) { return ( a >= 0.0f ) ? b : c; } +ID_INLINE_EXTERN float __frcps( float x ) { return ( 1.0f / x ); } +ID_INLINE_EXTERN float __fdivs( float x, float y ) { return ( x / y ); } +ID_INLINE_EXTERN float __frsqrts( float x ) { return ( 1.0f / sqrtf( x ) ); } +ID_INLINE_EXTERN float __frcps16( float x ) { return ( 1.0f / x ); } +ID_INLINE_EXTERN float __fdivs16( float x, float y ) { return ( x / y ); } +ID_INLINE_EXTERN float __frsqrts16( float x ) { return ( 1.0f / sqrtf( x ) ); } +ID_INLINE_EXTERN float __frndz( float x ) { return (float)( (int)( x ) ); } + +/* +================================================================================================ + + Zero cache line and prefetch intrinsics + +================================================================================================ +*/ + +// The code below assumes that a cache line is 64 bytes. +// We specify the cache line size as 128 here to make the code consistent with the consoles. +#define CACHE_LINE_SIZE 128 + +ID_FORCE_INLINE void Prefetch( const void * ptr, int offset ) { +// const char * bytePtr = ( (const char *) ptr ) + offset; +// _mm_prefetch( bytePtr + 0, _MM_HINT_NTA ); +// _mm_prefetch( bytePtr + 64, _MM_HINT_NTA ); +} +ID_FORCE_INLINE void ZeroCacheLine( void * ptr, int offset ) { + assert_128_byte_aligned( ptr ); + char * bytePtr = ( (char *) ptr ) + offset; + __m128i zero = _mm_setzero_si128(); + _mm_store_si128( (__m128i *) ( bytePtr + 0*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 1*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 2*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 3*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 4*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 5*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 6*16 ), zero ); + _mm_store_si128( (__m128i *) ( bytePtr + 7*16 ), zero ); +} +ID_FORCE_INLINE void FlushCacheLine( const void * ptr, int offset ) { + const char * bytePtr = ( (const char *) ptr ) + offset; + _mm_clflush( bytePtr + 0 ); + _mm_clflush( bytePtr + 64 ); +} + +/* +================================================ + Block Clear Macros +================================================ +*/ + +// number of additional elements that are potentially cleared when clearing whole cache lines at a time +ID_INLINE_EXTERN int CACHE_LINE_CLEAR_OVERFLOW_COUNT( int size ) { + if ( ( size & ( CACHE_LINE_SIZE - 1 ) ) == 0 ) { + return 0; + } + if ( size > CACHE_LINE_SIZE ) { + return 1; + } + return ( CACHE_LINE_SIZE / ( size & ( CACHE_LINE_SIZE - 1 ) ) ); +} + +// if the pointer is not on a cache line boundary this assumes the cache line the pointer starts in was already cleared +#define CACHE_LINE_CLEAR_BLOCK( ptr, size ) \ + byte * startPtr = (byte *)( ( ( (UINT_PTR) ( ptr ) ) + CACHE_LINE_SIZE - 1 ) & ~( CACHE_LINE_SIZE - 1 ) ); \ + byte * endPtr = (byte *)( ( (UINT_PTR) ( ptr ) + ( size ) - 1 ) & ~( CACHE_LINE_SIZE - 1 ) ); \ + for ( ; startPtr <= endPtr; startPtr += CACHE_LINE_SIZE ) { \ + ZeroCacheLine( startPtr, 0 ); \ + } + +#define CACHE_LINE_CLEAR_BLOCK_AND_FLUSH( ptr, size ) \ + byte * startPtr = (byte *)( ( ( (UINT_PTR) ( ptr ) ) + CACHE_LINE_SIZE - 1 ) & ~( CACHE_LINE_SIZE - 1 ) ); \ + byte * endPtr = (byte *)( ( (UINT_PTR) ( ptr ) + ( size ) - 1 ) & ~( CACHE_LINE_SIZE - 1 ) ); \ + for ( ; startPtr <= endPtr; startPtr += CACHE_LINE_SIZE ) { \ + ZeroCacheLine( startPtr, 0 ); \ + FlushCacheLine( startPtr, 0 ); \ + } + +/* +================================================================================================ + + Vector Intrinsics + +================================================================================================ +*/ + +/* +================================================ + PC Windows +================================================ +*/ + +#if !defined( R_SHUFFLE_D ) +#define R_SHUFFLE_D( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) +#endif + +// make the intrinsics "type unsafe" +typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128c { + __m128c() {} + __m128c( __m128 f ) { m128 = f; } + __m128c( __m128i i ) { m128i = i; } + operator __m128() { return m128; } + operator __m128i() { return m128i; } + __m128 m128; + __m128i m128i; +} __m128c; + +#define _mm_madd_ps( a, b, c ) _mm_add_ps( _mm_mul_ps( (a), (b) ), (c) ) +#define _mm_nmsub_ps( a, b, c ) _mm_sub_ps( (c), _mm_mul_ps( (a), (b) ) ) +#define _mm_splat_ps( x, i ) __m128c( _mm_shuffle_epi32( __m128c( x ), _MM_SHUFFLE( i, i, i, i ) ) ) +#define _mm_perm_ps( x, perm ) __m128c( _mm_shuffle_epi32( __m128c( x ), perm ) ) +#define _mm_sel_ps( a, b, c ) _mm_or_ps( _mm_andnot_ps( __m128c( c ), a ), _mm_and_ps( __m128c( c ), b ) ) +#define _mm_sel_si128( a, b, c ) _mm_or_si128( _mm_andnot_si128( __m128c( c ), a ), _mm_and_si128( __m128c( c ), b ) ) +#define _mm_sld_ps( x, y, imm ) __m128c( _mm_or_si128( _mm_srli_si128( __m128c( x ), imm ), _mm_slli_si128( __m128c( y ), 16 - imm ) ) ) +#define _mm_sld_si128( x, y, imm ) _mm_or_si128( _mm_srli_si128( x, imm ), _mm_slli_si128( y, 16 - imm ) ) + +ID_FORCE_INLINE_EXTERN __m128 _mm_msum3_ps( __m128 a, __m128 b ) { + __m128 c = _mm_mul_ps( a, b ); + return _mm_add_ps( _mm_splat_ps( c, 0 ), _mm_add_ps( _mm_splat_ps( c, 1 ), _mm_splat_ps( c, 2 ) ) ); +} + +ID_FORCE_INLINE_EXTERN __m128 _mm_msum4_ps( __m128 a, __m128 b ) { + __m128 c = _mm_mul_ps( a, b ); + c = _mm_add_ps( c, _mm_perm_ps( c, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + c = _mm_add_ps( c, _mm_perm_ps( c, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + return c; +} + +#define _mm_shufmix_epi32( x, y, perm ) __m128c( _mm_shuffle_ps( __m128c( x ), __m128c( y ), perm ) ) +#define _mm_loadh_epi64( x, address ) __m128c( _mm_loadh_pi( __m128c( x ), (__m64 *)address ) ) +#define _mm_storeh_epi64( address, x ) _mm_storeh_pi( (__m64 *)address, __m128c( x ) ) + +// floating-point reciprocal with close to full precision +ID_FORCE_INLINE_EXTERN __m128 _mm_rcp32_ps( __m128 x ) { + __m128 r = _mm_rcp_ps( x ); // _mm_rcp_ps() has 12 bits of precision + r = _mm_sub_ps( _mm_add_ps( r, r ), _mm_mul_ps( _mm_mul_ps( x, r ), r ) ); + r = _mm_sub_ps( _mm_add_ps( r, r ), _mm_mul_ps( _mm_mul_ps( x, r ), r ) ); + return r; +} +// floating-point reciprocal with at least 16 bits precision +ID_FORCE_INLINE_EXTERN __m128 _mm_rcp16_ps( __m128 x ) { + __m128 r = _mm_rcp_ps( x ); // _mm_rcp_ps() has 12 bits of precision + r = _mm_sub_ps( _mm_add_ps( r, r ), _mm_mul_ps( _mm_mul_ps( x, r ), r ) ); + return r; +} +// floating-point divide with close to full precision +ID_FORCE_INLINE_EXTERN __m128 _mm_div32_ps( __m128 x, __m128 y ) { + return _mm_mul_ps( x, _mm_rcp32_ps( y ) ); +} +// floating-point divide with at least 16 bits precision +ID_FORCE_INLINE_EXTERN __m128 _mm_div16_ps( __m128 x, __m128 y ) { + return _mm_mul_ps( x, _mm_rcp16_ps( y ) ); +} +// load idBounds::GetMins() +#define _mm_loadu_bounds_0( bounds ) _mm_perm_ps( _mm_loadh_pi( _mm_load_ss( & bounds[0].x ), (__m64 *) & bounds[0].y ), _MM_SHUFFLE( 1, 3, 2, 0 ) ) +// load idBounds::GetMaxs() +#define _mm_loadu_bounds_1( bounds ) _mm_perm_ps( _mm_loadh_pi( _mm_load_ss( & bounds[1].x ), (__m64 *) & bounds[1].y ), _MM_SHUFFLE( 1, 3, 2, 0 ) ) + +#endif // !__SYS_INTRIINSICS_H__ diff --git a/neo/idlib/sys/sys_threading.h b/neo/idlib/sys/sys_threading.h new file mode 100644 index 00000000..958ea663 --- /dev/null +++ b/neo/idlib/sys/sys_threading.h @@ -0,0 +1,168 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_THREADING_H__ +#define __SYS_THREADING_H__ + +#ifndef __TYPEINFOGEN__ + +/* +================================================================================================ + + Platform specific mutex, signal, atomic integer and memory barrier. + +================================================================================================ +*/ + + typedef CRITICAL_SECTION mutexHandle_t; + typedef HANDLE signalHandle_t; + typedef LONG interlockedInt_t; + + // _ReadWriteBarrier() does not translate to any instructions but keeps the compiler + // from reordering read and write instructions across the barrier. + // MemoryBarrier() inserts and CPU instruction that keeps the CPU from reordering reads and writes. + #pragma intrinsic(_ReadWriteBarrier) + #define SYS_MEMORYBARRIER _ReadWriteBarrier(); MemoryBarrier() + + + + + +/* +================================================================================================ + + Platform specific thread local storage. + Can be used to store either a pointer or an integer. + +================================================================================================ +*/ + + + class idSysThreadLocalStorage { + public: + idSysThreadLocalStorage() { + tlsIndex = TlsAlloc(); + } + idSysThreadLocalStorage( const ptrdiff_t &val ) { + tlsIndex = TlsAlloc(); + TlsSetValue( tlsIndex, (LPVOID)val ); + } + ~idSysThreadLocalStorage() { + TlsFree( tlsIndex ); + } + operator ptrdiff_t() { + return (ptrdiff_t)TlsGetValue( tlsIndex ); + } + const ptrdiff_t & operator = ( const ptrdiff_t &val ) { + TlsSetValue( tlsIndex, (LPVOID)val ); + return val; + } + DWORD tlsIndex; + }; + +#define ID_TLS idSysThreadLocalStorage + + +#endif // __TYPEINFOGEN__ + +/* +================================================================================================ + + Platform independent threading functions. + +================================================================================================ +*/ + +enum core_t { + CORE_ANY = -1, + CORE_0A, + CORE_0B, + CORE_1A, + CORE_1B, + CORE_2A, + CORE_2B +}; + +typedef unsigned int (*xthread_t)( void * ); + +enum xthreadPriority { + THREAD_LOWEST, + THREAD_BELOW_NORMAL, + THREAD_NORMAL, + THREAD_ABOVE_NORMAL, + THREAD_HIGHEST +}; + +#define DEFAULT_THREAD_STACK_SIZE ( 256 * 1024 ) + +// on win32, the threadID is NOT the same as the threadHandle +uintptr_t Sys_GetCurrentThreadID(); + +// returns a threadHandle +uintptr_t Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, + const char *name, core_t core, int stackSize = DEFAULT_THREAD_STACK_SIZE, + bool suspended = false ); + +void Sys_WaitForThread( uintptr_t threadHandle ); +void Sys_DestroyThread( uintptr_t threadHandle ); +void Sys_SetCurrentThreadName( const char *name ); + +void Sys_SignalCreate( signalHandle_t & handle, bool manualReset ); +void Sys_SignalDestroy( signalHandle_t & handle ); +void Sys_SignalRaise( signalHandle_t & handle ); +void Sys_SignalClear( signalHandle_t & handle ); +bool Sys_SignalWait( signalHandle_t & handle, int timeout ); + +void Sys_MutexCreate( mutexHandle_t & handle ); +void Sys_MutexDestroy( mutexHandle_t & handle ); +bool Sys_MutexLock( mutexHandle_t & handle, bool blocking ); +void Sys_MutexUnlock( mutexHandle_t & handle ); + +interlockedInt_t Sys_InterlockedIncrement( interlockedInt_t & value ); +interlockedInt_t Sys_InterlockedDecrement( interlockedInt_t & value ); + +interlockedInt_t Sys_InterlockedAdd( interlockedInt_t & value, interlockedInt_t i ); +interlockedInt_t Sys_InterlockedSub( interlockedInt_t & value, interlockedInt_t i ); + +interlockedInt_t Sys_InterlockedExchange( interlockedInt_t & value, interlockedInt_t exchange ); +interlockedInt_t Sys_InterlockedCompareExchange( interlockedInt_t & value, interlockedInt_t comparand, interlockedInt_t exchange ); + +void * Sys_InterlockedExchangePointer( void * & ptr, void * exchange ); +void * Sys_InterlockedCompareExchangePointer( void * & ptr, void * comparand, void * exchange ); + +void Sys_Yield(); + +const int MAX_CRITICAL_SECTIONS = 4; + +enum { + CRITICAL_SECTION_ZERO = 0, + CRITICAL_SECTION_ONE, + CRITICAL_SECTION_TWO, + CRITICAL_SECTION_THREE +}; + +#endif // !__SYS_THREADING_H__ diff --git a/neo/idlib/sys/sys_types.h b/neo/idlib/sys/sys_types.h new file mode 100644 index 00000000..11acbf24 --- /dev/null +++ b/neo/idlib/sys/sys_types.h @@ -0,0 +1,287 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef SYS_TYPES_H +#define SYS_TYPES_H + +/* +================================================================================================ +Contains types and defines used throughout the engine. + + NOTE: keep this down to simple types and defines. Do NOT add code. +================================================================================================ +*/ + +typedef unsigned char byte; // 8 bits +typedef unsigned short word; // 16 bits +typedef unsigned int dword; // 32 bits +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef signed char int8; +typedef unsigned char uint8; +typedef short int int16; +typedef unsigned short int uint16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; + +// The C/C++ standard guarantees the size of an unsigned type is the same as the signed type. +// The exact size in bytes of several types is guaranteed here. +assert_sizeof( bool, 1 ); +assert_sizeof( char, 1 ); +assert_sizeof( short, 2 ); +assert_sizeof( int, 4 ); +assert_sizeof( float, 4 ); +assert_sizeof( byte, 1 ); +assert_sizeof( int8, 1 ); +assert_sizeof( uint8, 1 ); +assert_sizeof( int16, 2 ); +assert_sizeof( uint16, 2 ); +assert_sizeof( int32, 4 ); +assert_sizeof( uint32, 4 ); +assert_sizeof( int64, 8 ); +assert_sizeof( uint64, 8 ); + +#define MAX_TYPE( x ) ( ( ( ( 1 << ( ( sizeof( x ) - 1 ) * 8 - 1 ) ) - 1 ) << 8 ) | 255 ) +#define MIN_TYPE( x ) ( - MAX_TYPE( x ) - 1 ) +#define MAX_UNSIGNED_TYPE( x ) ( ( ( ( 1U << ( ( sizeof( x ) - 1 ) * 8 ) ) - 1 ) << 8 ) | 255U ) +#define MIN_UNSIGNED_TYPE( x ) 0 + +template< typename _type_ > +bool IsSignedType( const _type_ t ) { + return _type_( -1 ) < 0; +} + +template T Max( T x, T y ) { return ( x > y ) ? x : y; } +template T Min( T x, T y ) { return ( x < y ) ? x : y; } + + +class idFile; + +struct idNullPtr { + // one pointer member initialized to zero so you can pass NULL as a vararg + void *value; idNullPtr() : value( 0 ) { } + + // implicit conversion to all pointer types + template operator T1 * () const { return 0; } + + // implicit conversion to all pointer to member types + template operator T1 T2::* () const { return 0; } +}; + +//#undef NULL +//#if defined( ID_PC_WIN ) && !defined( ID_TOOL_EXTERNAL ) && !defined( _lint ) +//#define NULL idNullPtr() +//#else +//#define NULL 0 +//#endif + +// C99 Standard +#ifndef nullptr + #define nullptr idNullPtr() +#endif + +#ifndef BIT +#define BIT( num ) ( 1ULL << ( num ) ) +#endif + +#ifndef NUMBITS +#define NUMBITS( _type_ ) ( sizeof( _type_ ) * 8 ) +#endif + +#define MAX_STRING_CHARS 1024 // max length of a static string +#define MAX_PRINT_MSG 16384 // buffer size for our various printf routines + +// maximum world size +#define MAX_WORLD_COORD ( 128 * 1024 ) +#define MIN_WORLD_COORD ( -128 * 1024 ) +#define MAX_WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +const float MAX_ENTITY_COORDINATE = 64000.0f; + +#if 1 + +typedef unsigned short triIndex_t; +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT + +#else + +typedef unsigned int triIndex_t; +#define GL_INDEX_TYPE GL_UNSIGNED_INT + +#endif + +// if writing to write-combined memroy, always write indexes as pairs for 32 bit writes +ID_INLINE void WriteIndexPair( triIndex_t * dest, const triIndex_t a, const triIndex_t b ) { + *(unsigned *)dest = (unsigned)a | ( (unsigned)b<<16 ); +} + +#if defined(_DEBUG) || defined(_lint) +#define NODEFAULT default: assert( 0 ) +#else +#define NODEFAULT default: __assume( 0 ) +#endif + +/* +================================================================================================ + +The CONST_* defines can be used to create constant expressions that can be evaluated at +compile time. The parameters to these defines need to be compile time constants such as +literals or sizeof(). NEVER use an actual variable as a parameter to one of these defines. + +================================================================================================ +*/ + +#ifdef _lint // lint has problems with CONST_BITSFORINTEGER(), so just make it something simple for analysis +#define CONST_ILOG2(x) 1 +#else + +#define CONST_ILOG2(x) ( ( (x) & (1u<<31) ) ? 31 : \ + ( (x) & (1u<<30) ) ? 30 : \ + ( (x) & (1u<<29) ) ? 39 : \ + ( (x) & (1u<<28) ) ? 28 : \ + ( (x) & (1u<<27) ) ? 27 : \ + ( (x) & (1u<<26) ) ? 26 : \ + ( (x) & (1u<<25) ) ? 25 : \ + ( (x) & (1u<<24) ) ? 24 : \ + ( (x) & (1u<<23) ) ? 23 : \ + ( (x) & (1u<<22) ) ? 22 : \ + ( (x) & (1u<<21) ) ? 21 : \ + ( (x) & (1u<<20) ) ? 20 : \ + ( (x) & (1u<<19) ) ? 19 : \ + ( (x) & (1u<<18) ) ? 18 : \ + ( (x) & (1u<<17) ) ? 17 : \ + ( (x) & (1u<<16) ) ? 16 : \ + ( (x) & (1u<<15) ) ? 15 : \ + ( (x) & (1u<<14) ) ? 14 : \ + ( (x) & (1u<<13) ) ? 13 : \ + ( (x) & (1u<<12) ) ? 12 : \ + ( (x) & (1u<<11) ) ? 11 : \ + ( (x) & (1u<<10) ) ? 10 : \ + ( (x) & (1u<<9) ) ? 9 : \ + ( (x) & (1u<<8) ) ? 8 : \ + ( (x) & (1u<<7) ) ? 7 : \ + ( (x) & (1u<<6) ) ? 6 : \ + ( (x) & (1u<<5) ) ? 5 : \ + ( (x) & (1u<<4) ) ? 4 : \ + ( (x) & (1u<<3) ) ? 3 : \ + ( (x) & (1u<<2) ) ? 2 : \ + ( (x) & (1u<<1) ) ? 1 : \ + ( (x) & (1u<<0) ) ? 0 : -1 ) +#endif // _lint + +#define CONST_IEXP2 ( 1 << (x) ) + +#define CONST_FLOORPOWEROF2(x) ( 1 << ( CONST_ILOG2(x) + 1 ) >> 1 ) + +#define CONST_CEILPOWEROF2(x) ( 1 << ( CONST_ILOG2(x-1) + 1 ) ) + +#define CONST_BITSFORINTEGER(x) ( CONST_ILOG2(x) + 1 ) + +#define CONST_ILOG10(x) ( ( (x) >= 10000000000u ) ? 10 : \ + ( (x) >= 1000000000u ) ? 9 : \ + ( (x) >= 100000000u ) ? 8 : \ + ( (x) >= 10000000u ) ? 7 : \ + ( (x) >= 1000000u ) ? 6 : \ + ( (x) >= 100000u ) ? 5 : \ + ( (x) >= 10000u ) ? 4 : \ + ( (x) >= 1000u ) ? 3 : \ + ( (x) >= 100u ) ? 2 : \ + ( (x) >= 10u ) ? 1 : 0 ) + +#define CONST_IEXP10(x) ( ( (x) == 0 ) ? 1u : \ + ( (x) == 1 ) ? 10u : \ + ( (x) == 2 ) ? 100u : \ + ( (x) == 3 ) ? 1000u : \ + ( (x) == 4 ) ? 10000u : \ + ( (x) == 5 ) ? 100000u : \ + ( (x) == 6 ) ? 1000000u : \ + ( (x) == 7 ) ? 10000000u : \ + ( (x) == 8 ) ? 100000000u : 1000000000u ) + +#define CONST_FLOORPOWEROF10(x) ( ( (x) >= 10000000000u ) ? 10000000000u : \ + ( (x) >= 1000000000u ) ? 1000000000u : \ + ( (x) >= 100000000u ) ? 100000000u : \ + ( (x) >= 10000000u ) ? 10000000u : \ + ( (x) >= 1000000u ) ? 1000000u : \ + ( (x) >= 100000u ) ? 100000u : \ + ( (x) >= 10000u ) ? 10000u : \ + ( (x) >= 1000u ) ? 1000u : \ + ( (x) >= 100u ) ? 100u : \ + ( (x) >= 10u ) ? 10u : 1u ) + +#define CONST_CEILPOWEROF10(x) ( ( (x) <= 10u ) ? 10u : \ + ( (x) <= 100u ) ? 100u : \ + ( (x) <= 1000u ) ? 1000u : \ + ( (x) <= 10000u ) ? 10000u : \ + ( (x) <= 100000u ) ? 100000u : \ + ( (x) <= 1000000u ) ? 1000000u : \ + ( (x) <= 10000000u ) ? 10000000u : \ + ( (x) <= 100000000u ) ? 100000000u : 1000000000u ) + +#define CONST_ISPOWEROFTWO(x) ( ( (x) & ( (x) - 1 ) ) == 0 && (x) > 0 ) + +#define CONST_MAX( x, y ) ( (x) > (y) ? (x) : (y) ) +#define CONST_MAX3( x, y, z ) ( (x) > (y) ? ( (x) > (z) ? (x) : (z) ) : ( (y) > (z) ? (y) : (z) ) ) + +#define CONST_PI 3.14159265358979323846f +#define CONST_SINE_POLY( a ) ( (a) * ( ( ( ( ( -2.39e-08f * ((a)*(a)) + 2.7526e-06f ) * ((a)*(a)) - 1.98409e-04f ) * ((a)*(a)) + 8.3333315e-03f ) * ((a)*(a)) - 1.666666664e-01f ) * ((a)*(a)) + 1.0f ) ) +#define CONST_COSINE_POLY( a ) ( ( ( ( ( -2.605e-07f * ((a)*(a)) + 2.47609e-05f ) * ((a)*(a)) - 1.3888397e-03f ) * ((a)*(a)) + 4.16666418e-02f ) * ((a)*(a)) - 4.999999963e-01f ) * ((a)*(a)) + 1.0f ) + +// These are only good in the 0 - 2*PI range! + +// maximum absolute error is 2.3082e-09 +#define CONST_SINE_DEGREES( a ) CONST_SINE( CONST_DEG2RAD( (a) ) ) +#define CONST_SINE( a ) ( ( (a) < CONST_PI ) ? \ + ( ( (a) > CONST_PI * 0.5f ) ? \ + CONST_SINE_POLY( CONST_PI - (a) ) \ + : \ + CONST_SINE_POLY( (a) ) ) \ + : \ + ( ( (a) > CONST_PI * 1.5f ) ? \ + CONST_SINE_POLY( (a) - CONST_PI * 2.0f ) \ + : \ + CONST_SINE_POLY( CONST_PI - (a) ) ) ) + +// maximum absolute error is 2.3082e-09 +#define CONST_COSINE_DEGREES( a ) CONST_COSINE( CONST_DEG2RAD( (a) ) ) +#define CONST_COSINE( a ) ( ( (a) < CONST_PI ) ? \ + ( ( (a) > CONST_PI * 0.5f ) ? \ + - CONST_COSINE_POLY( CONST_PI - (a) ) \ + : \ + CONST_COSINE_POLY( (a) ) ) \ + : \ + ( ( (a) > CONST_PI * 1.5f ) ? \ + CONST_COSINE_POLY( (a) - CONST_PI * 2.0f ) \ + : \ + - CONST_COSINE_POLY( CONST_PI - (a) ) ) ) + +#define CONST_DEG2RAD( a ) ( (a) * CONST_PI / 180.0f ) + +#endif diff --git a/neo/idlib/sys/win32/win_thread.cpp b/neo/idlib/sys/win32/win_thread.cpp new file mode 100644 index 00000000..1f1c2b5a --- /dev/null +++ b/neo/idlib/sys/win32/win_thread.cpp @@ -0,0 +1,358 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../precompiled.h" + +/* +================================================================================================ +================================================================================================ +*/ + +#define MS_VC_EXCEPTION 0x406D1388 + +typedef struct tagTHREADNAME_INFO { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; +/* +======================== +Sys_SetThreadName +======================== +*/ +void Sys_SetThreadName( DWORD threadID, const char * name ) { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = threadID; + info.dwFlags = 0; + + __try { + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (const ULONG_PTR *)&info ); + } + // this much is just to keep /analyze quiet + __except( GetExceptionCode() == MS_VC_EXCEPTION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { + info.dwFlags = 0; + } +} + +/* +======================== +Sys_SetCurrentThreadName +======================== +*/ +void Sys_SetCurrentThreadName( const char * name ) { + Sys_SetThreadName( GetCurrentThreadId(), name ); +} + +/* +======================== +Sys_Createthread +======================== +*/ +uintptr_t Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, const char *name, core_t core, int stackSize, bool suspended ) { + + DWORD flags = ( suspended ? CREATE_SUSPENDED : 0 ); + // Without this flag the 'dwStackSize' parameter to CreateThread specifies the "Stack Commit Size" + // and the "Stack Reserve Size" is set to the value specified at link-time. + // With this flag the 'dwStackSize' parameter to CreateThread specifies the "Stack Reserve Size" + // and the “Stack Commit Size” is set to the value specified at link-time. + // For various reasons (some of which historic) we reserve a large amount of stack space in the + // project settings. By setting this flag and by specifying 64 kB for the "Stack Commit Size" in + // the project settings we can create new threads with a much smaller reserved (and committed) + // stack space. It is very important that the "Stack Commit Size" is set to a small value in + // the project settings. If it is set to a large value we may be both reserving and committing + // a lot of memory by setting the STACK_SIZE_PARAM_IS_A_RESERVATION flag. There are some + // 50 threads allocated for normal game play. If, for instance, the commit size is set to 16 MB + // then by adding this flag we would be reserving and committing 50 x 16 = 800 MB of memory. + // On the other hand, if this flag is not set and the "Stack Reserve Size" is set to 16 MB in the + // project settings, then we would still be reserving 50 x 16 = 800 MB of virtual address space. + flags |= STACK_SIZE_PARAM_IS_A_RESERVATION; + + DWORD threadId; + HANDLE handle = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa, //-V513 + stackSize, + (LPTHREAD_START_ROUTINE)function, + parms, + flags, + &threadId); + if ( handle == 0 ) { + idLib::common->FatalError( "CreateThread error: %i", GetLastError() ); + return (uintptr_t)0; + } + Sys_SetThreadName( threadId, name ); + if ( priority == THREAD_HIGHEST ) { + SetThreadPriority( (HANDLE)handle, THREAD_PRIORITY_HIGHEST ); // we better sleep enough to do this + } else if ( priority == THREAD_ABOVE_NORMAL ) { + SetThreadPriority( (HANDLE)handle, THREAD_PRIORITY_ABOVE_NORMAL ); + } else if ( priority == THREAD_BELOW_NORMAL ) { + SetThreadPriority( (HANDLE)handle, THREAD_PRIORITY_BELOW_NORMAL ); + } else if ( priority == THREAD_LOWEST ) { + SetThreadPriority( (HANDLE)handle, THREAD_PRIORITY_LOWEST ); + } + + // Under Windows, we don't set the thread affinity and let the OS deal with scheduling + + return (uintptr_t)handle; +} + + +/* +======================== +Sys_GetCurrentThreadID +======================== +*/ +uintptr_t Sys_GetCurrentThreadID() { + return GetCurrentThreadId(); +} + +/* +======================== +Sys_WaitForThread +======================== +*/ +void Sys_WaitForThread( uintptr_t threadHandle ) { + WaitForSingleObject( (HANDLE)threadHandle, INFINITE ); +} + +/* +======================== +Sys_DestroyThread +======================== +*/ +void Sys_DestroyThread( uintptr_t threadHandle ) { + if ( threadHandle == 0 ) { + return; + } + WaitForSingleObject( (HANDLE)threadHandle, INFINITE ); + CloseHandle( (HANDLE)threadHandle ); +} + +/* +======================== +Sys_Yield +======================== +*/ +void Sys_Yield() { + SwitchToThread(); +} + +/* +================================================================================================ + + Signal + +================================================================================================ +*/ + +/* +======================== +Sys_SignalCreate +======================== +*/ +void Sys_SignalCreate( signalHandle_t & handle, bool manualReset ) { + handle = CreateEvent( NULL, manualReset, FALSE, NULL ); +} + +/* +======================== +Sys_SignalDestroy +======================== +*/ +void Sys_SignalDestroy( signalHandle_t &handle ) { + CloseHandle( handle ); +} + +/* +======================== +Sys_SignalRaise +======================== +*/ +void Sys_SignalRaise( signalHandle_t & handle ) { + SetEvent( handle ); +} + +/* +======================== +Sys_SignalClear +======================== +*/ +void Sys_SignalClear( signalHandle_t & handle ) { + // events are created as auto-reset so this should never be needed + ResetEvent( handle ); +} + +/* +======================== +Sys_SignalWait +======================== +*/ +bool Sys_SignalWait( signalHandle_t & handle, int timeout ) { + DWORD result = WaitForSingleObject( handle, timeout == idSysSignal::WAIT_INFINITE ? INFINITE : timeout ); + assert( result == WAIT_OBJECT_0 || ( timeout != idSysSignal::WAIT_INFINITE && result == WAIT_TIMEOUT ) ); + return ( result == WAIT_OBJECT_0 ); +} + +/* +================================================================================================ + + Mutex + +================================================================================================ +*/ + +/* +======================== +Sys_MutexCreate +======================== +*/ +void Sys_MutexCreate( mutexHandle_t & handle ) { + InitializeCriticalSection( &handle ); +} + +/* +======================== +Sys_MutexDestroy +======================== +*/ +void Sys_MutexDestroy( mutexHandle_t & handle ) { + DeleteCriticalSection( &handle ); +} + +/* +======================== +Sys_MutexLock +======================== +*/ +bool Sys_MutexLock( mutexHandle_t & handle, bool blocking ) { + if ( TryEnterCriticalSection( &handle ) == 0 ) { + if ( !blocking ) { + return false; + } + EnterCriticalSection( &handle ); + } + return true; +} + +/* +======================== +Sys_MutexUnlock +======================== +*/ +void Sys_MutexUnlock( mutexHandle_t & handle ) { + LeaveCriticalSection( & handle ); +} + +/* +================================================================================================ + + Interlocked Integer + +================================================================================================ +*/ + +/* +======================== +Sys_InterlockedIncrement +======================== +*/ +interlockedInt_t Sys_InterlockedIncrement( interlockedInt_t & value ) { + return InterlockedIncrementAcquire( & value ); +} + +/* +======================== +Sys_InterlockedDecrement +======================== +*/ +interlockedInt_t Sys_InterlockedDecrement( interlockedInt_t & value ) { + return InterlockedDecrementRelease( & value ); +} + +/* +======================== +Sys_InterlockedAdd +======================== +*/ +interlockedInt_t Sys_InterlockedAdd( interlockedInt_t & value, interlockedInt_t i ) { + return InterlockedExchangeAdd( & value, i ) + i; +} + +/* +======================== +Sys_InterlockedSub +======================== +*/ +interlockedInt_t Sys_InterlockedSub( interlockedInt_t & value, interlockedInt_t i ) { + return InterlockedExchangeAdd( & value, - i ) - i; +} + +/* +======================== +Sys_InterlockedExchange +======================== +*/ +interlockedInt_t Sys_InterlockedExchange( interlockedInt_t & value, interlockedInt_t exchange ) { + return InterlockedExchange( & value, exchange ); +} + +/* +======================== +Sys_InterlockedCompareExchange +======================== +*/ +interlockedInt_t Sys_InterlockedCompareExchange( interlockedInt_t & value, interlockedInt_t comparand, interlockedInt_t exchange ) { + return InterlockedCompareExchange( & value, exchange, comparand ); +} + +/* +================================================================================================ + + Interlocked Pointer + +================================================================================================ +*/ + +/* +======================== +Sys_InterlockedExchangePointer +======================== +*/ +void *Sys_InterlockedExchangePointer( void *& ptr, void * exchange ) { + return InterlockedExchangePointer( & ptr, exchange ); +} + +/* +======================== +Sys_InterlockedCompareExchangePointer +======================== +*/ +void * Sys_InterlockedCompareExchangePointer( void * & ptr, void * comparand, void * exchange ) { + return InterlockedCompareExchangePointer( & ptr, exchange, comparand ); +} diff --git a/neo/renderer/AutoRender.cpp b/neo/renderer/AutoRender.cpp new file mode 100644 index 00000000..bec39402 --- /dev/null +++ b/neo/renderer/AutoRender.cpp @@ -0,0 +1,279 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "tr_local.h" + +const int AUTO_RENDER_STACK_SIZE = 256 * 1024; + +idAutoRender rAutoRender; + +/* +============================ +idAutoRender::idAutoRender +============================ +*/ +idAutoRender::idAutoRender() { + nextRotateTime = 0.0f; + currentRotation = 0.0f; + autoRenderIcon = AUTORENDER_DEFAULTICON; +} + +/* +============================ +idAutoRender::Run +============================ +*/ +int idAutoRender::Run() { + while ( !IsTerminating() ) { + RenderFrame(); + } + + + return 0; +} + +/* +============================ +idAutoRender::StartBackgroundAutoSwaps +============================ +*/ +void idAutoRender::StartBackgroundAutoSwaps( autoRenderIconType_t iconType ) { + + + if ( IsRunning() ) { + EndBackgroundAutoSwaps(); + } + + autoRenderIcon = iconType; + + idLib::Printf("Starting Background AutoSwaps\n"); + + const bool captureToImage = true; + common->UpdateScreen( captureToImage ); + + // unbind any shaders prior to entering the background autoswaps so we don't run + // into any problems with cached vertex shader indices from the main thread + renderProgManager.Unbind(); + + // unbind all texture units so we don't run into a race condition where the device is owned + // by the autorender thread but an image is trying to be unset from the main thread because + // it is getting purged before our our first frame has been rendered. + globalImages->UnbindAll(); + + + StartThread("BackgroundAutoSwaps", CORE_0B, THREAD_NORMAL, AUTO_RENDER_STACK_SIZE ); +} + +/* +============================ +idAutoRender::EndBackgroundAutoSwaps +============================ +*/ +void idAutoRender::EndBackgroundAutoSwaps() { + idLib::Printf("End Background AutoSwaps\n"); + + StopThread(); + +} + +/* +============================ +idAutoRender::RenderFrame +============================ +*/ +void idAutoRender::RenderFrame() { + // values are 0 to 1 + float loadingIconPosX = 0.5f; + float loadingIconPosY = 0.6f; + float loadingIconScale = 0.025f; + float loadingIconSpeed = 0.095f; + + if ( autoRenderIcon == AUTORENDER_HELLICON ) { + loadingIconPosX = 0.85f; + loadingIconPosY = 0.85f; + loadingIconScale = 0.1f; + loadingIconSpeed = 0.095f; + } else if ( autoRenderIcon == AUTORENDER_DIALOGICON ) { + loadingIconPosY = 0.73f; + } + + + GL_SetDefaultState(); + + GL_Cull( CT_TWO_SIDED ); + + const bool stereoRender = false; + + const int width = renderSystem->GetWidth(); + const int height = renderSystem->GetHeight(); + const int guardBand = height / 24; + + if ( stereoRender ) { + for ( int viewNum = 0 ; viewNum < 2; viewNum++ ) { + GL_ViewportAndScissor( 0, viewNum * ( height + guardBand ), width, height ); + RenderBackground(); + RenderLoadingIcon( loadingIconPosX, loadingIconPosY, loadingIconScale, loadingIconSpeed ); + } + } else { + GL_ViewportAndScissor( 0, 0, width, height ); + RenderBackground(); + RenderLoadingIcon( loadingIconPosX, loadingIconPosY, loadingIconScale, loadingIconSpeed ); + } + +} + +/* +============================ +idAutoRender::RenderBackground +============================ +*/ +void idAutoRender::RenderBackground() { + GL_SelectTexture( 0 ); + + globalImages->currentRenderImage->Bind(); + + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + float mvpMatrix[16] = { 0 }; + mvpMatrix[0] = 1; + mvpMatrix[5] = 1; + mvpMatrix[10] = 1; + mvpMatrix[15] = 1; + + // Set Parms + float texS[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float texT[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_S, texS ); + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_T, texT ); + + // disable texgen + float texGenEnabled[4] = { 0, 0, 0, 0 }; + renderProgManager.SetRenderParm( RENDERPARM_TEXGEN_0_ENABLED, texGenEnabled ); + + // set matrix + renderProgManager.SetRenderParms( RENDERPARM_MVPMATRIX_X, mvpMatrix, 4 ); + + renderProgManager.BindShader_TextureVertexColor(); + + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); +} + +/* +============================ +idAutoRender::RenderLoadingIcon +============================ +*/ +void idAutoRender::RenderLoadingIcon( float fracX, float fracY, float size, float speed ) { + + float s = 0.0f; + float c = 1.0f; + + if ( autoRenderIcon != AUTORENDER_HELLICON ) { + if ( Sys_Milliseconds() >= nextRotateTime ) { + nextRotateTime = Sys_Milliseconds() + 100; + currentRotation -= 90.0f; + } + float angle = DEG2RAD( currentRotation ); + idMath::SinCos( angle, s, c ); + } + + const float pixelAspect = renderSystem->GetPixelAspect(); + const float screenWidth = renderSystem->GetWidth(); + const float screenHeight = renderSystem->GetHeight(); + + const float minSize = Min( screenWidth, screenHeight ); + if ( minSize <= 0.0f ) { + return; + } + + float scaleX = size * minSize / screenWidth; + float scaleY = size * minSize / screenHeight; + + float scale[16] = { 0 }; + scale[0] = c * scaleX / pixelAspect; + scale[1] = -s * scaleY; + scale[4] = s * scaleX / pixelAspect; + scale[5] = c * scaleY; + scale[10] = 1.0f; + scale[15] = 1.0f; + + scale[12] = fracX; + scale[13] = fracY; + + float ortho[16] = { 0 }; + ortho[0] = 2.0f; + ortho[5] = -2.0f; + ortho[10] = -2.0f; + ortho[12] = -1.0f; + ortho[13] = 1.0f; + ortho[14] = -1.0f; + ortho[15] = 1.0f; + + float finalOrtho[16]; + R_MatrixMultiply( scale, ortho, finalOrtho ); + + float projMatrixTranspose[16]; + R_MatrixTranspose( finalOrtho, projMatrixTranspose ); + renderProgManager.SetRenderParms( RENDERPARM_MVPMATRIX_X, projMatrixTranspose, 4 ); + + float a = 1.0f; + if ( autoRenderIcon == AUTORENDER_HELLICON ) { + float alpha = DEG2RAD( Sys_Milliseconds() * speed ); + a = idMath::Sin( alpha ); + a = 0.35f + ( 0.65f * idMath::Fabs( a ) ); + } + + GL_SelectTexture( 0 ); + + if ( autoRenderIcon == AUTORENDER_HELLICON ) { + globalImages->hellLoadingIconImage->Bind(); + } else { + globalImages->loadingIconImage->Bind(); + } + + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + // Set Parms + float texS[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float texT[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_S, texS ); + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_T, texT ); + + if ( autoRenderIcon == AUTORENDER_HELLICON ) { + GL_Color( 1.0f, 1.0f, 1.0f, a ); + } + + // disable texgen + float texGenEnabled[4] = { 0, 0, 0, 0 }; + renderProgManager.SetRenderParm( RENDERPARM_TEXGEN_0_ENABLED, texGenEnabled ); + + renderProgManager.BindShader_TextureVertexColor(); + + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); +} \ No newline at end of file diff --git a/neo/renderer/AutoRender.h b/neo/renderer/AutoRender.h new file mode 100644 index 00000000..16d76754 --- /dev/null +++ b/neo/renderer/AutoRender.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __AUTO_RENDER_H__ +#define __AUTO_RENDER_H__ + +class idAutoRender : public idSysThread { +public: + idAutoRender(); + // idSysThread interface + int Run(); + + void StartBackgroundAutoSwaps( autoRenderIconType_t iconType ); + void EndBackgroundAutoSwaps(); + + autoRenderIconType_t GetCurrentIcon() { return autoRenderIcon; } + +private: + void RenderFrame(); + void RenderBackground(); + void RenderLoadingIcon( float fracX, float fracY, float size, float speed ); + + int nextRotateTime; + float currentRotation; + autoRenderIconType_t autoRenderIcon; +}; + +extern idAutoRender rAutoRender; + +#endif // __AUTO_RENDER_H__ \ No newline at end of file diff --git a/neo/renderer/AutoRenderBink.cpp b/neo/renderer/AutoRenderBink.cpp new file mode 100644 index 00000000..b9c50a3a --- /dev/null +++ b/neo/renderer/AutoRenderBink.cpp @@ -0,0 +1,32 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "tr_local.h" +#include "..\sound\snd_local.h" + diff --git a/neo/renderer/AutoRenderBink.h b/neo/renderer/AutoRenderBink.h new file mode 100644 index 00000000..f1d2e9ab --- /dev/null +++ b/neo/renderer/AutoRenderBink.h @@ -0,0 +1,32 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __AUTO_RENDER_BINK_H__ +#define __AUTO_RENDER_BINK_H__ + + +#endif // __AUTO_RENDER_BINK_H__ diff --git a/neo/renderer/BinaryImage.cpp b/neo/renderer/BinaryImage.cpp new file mode 100644 index 00000000..d078034e --- /dev/null +++ b/neo/renderer/BinaryImage.cpp @@ -0,0 +1,449 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +================================================================================================ + + idBinaryImage + +================================================================================================ +*/ + +#include "tr_local.h" +#include "dxt/DXTCodec.h" +#include "color/ColorSpace.h" + +idCVar image_highQualityCompression( "image_highQualityCompression", "0", CVAR_BOOL, "Use high quality (slow) compression" ); + +/* +======================== +idBinaryImage::Load2DFromMemory +======================== +*/ +void idBinaryImage::Load2DFromMemory( int width, int height, const byte * pic_const, int numLevels, textureFormat_t & textureFormat, textureColor_t & colorFormat, bool gammaMips ) { + fileData.textureType = TT_2D; + fileData.format = textureFormat; + fileData.colorFormat = colorFormat; + fileData.width = width; + fileData.height = height; + fileData.numLevels = numLevels; + + byte * pic = (byte *)Mem_Alloc( width * height * 4, TAG_TEMP ); + memcpy( pic, pic_const, width * height * 4 ); + + if ( colorFormat == CFM_YCOCG_DXT5 ) { + // convert the image data to YCoCg and use the YCoCgDXT5 compressor + idColorSpace::ConvertRGBToCoCg_Y( pic, pic, width, height ); + } else if ( colorFormat == CFM_NORMAL_DXT5 ) { + // Blah, HQ swizzles automatically, Fast doesn't + if ( !image_highQualityCompression.GetBool() ) { + for ( int i = 0; i < width * height; i++ ) { + pic[i*4+3] = pic[i*4+0]; + pic[i*4+0] = 0; + pic[i*4+2] = 0; + } + } + } else if ( colorFormat == CFM_GREEN_ALPHA ) { + for ( int i = 0; i < width * height; i++ ) { + pic[i*4+1] = pic[i*4+3]; + pic[i*4+0] = 0; + pic[i*4+2] = 0; + pic[i*4+3] = 0; + } + } + + int scaledWidth = width; + int scaledHeight = height; + images.SetNum( numLevels ); + for ( int level = 0; level < images.Num(); level++ ) { + idBinaryImageData &img = images[ level ]; + + // Images that are going to be DXT compressed and aren't multiples of 4 need to be + // padded out before compressing. + byte * dxtPic = pic; + int dxtWidth = 0; + int dxtHeight = 0; + if ( textureFormat == FMT_DXT5 || textureFormat == FMT_DXT1 ) { + if ( ( scaledWidth & 3 ) || ( scaledHeight & 3 ) ) { + dxtWidth = ( scaledWidth + 3 ) & ~3; + dxtHeight = ( scaledHeight + 3 ) & ~3; + dxtPic = (byte *)Mem_ClearedAlloc( dxtWidth*4*dxtHeight, TAG_IMAGE ); + for ( int i = 0; i < scaledHeight; i++ ) { + memcpy( dxtPic + i*dxtWidth*4, pic + i*scaledWidth*4, scaledWidth*4 ); + } + } else { + dxtPic = pic; + dxtWidth = scaledWidth; + dxtHeight = scaledHeight; + } + } + + img.level = level; + img.destZ = 0; + img.width = scaledWidth; + img.height = scaledHeight; + + // compress data or convert floats as necessary + if ( textureFormat == FMT_DXT1 ) { + idDxtEncoder dxt; + img.Alloc( dxtWidth * dxtHeight / 2 ); + if ( image_highQualityCompression.GetBool() ) { + dxt.CompressImageDXT1HQ( dxtPic, img.data, dxtWidth, dxtHeight ); + } else { + dxt.CompressImageDXT1Fast( dxtPic, img.data, dxtWidth, dxtHeight ); + } + } else if ( textureFormat == FMT_DXT5 ) { + idDxtEncoder dxt; + img.Alloc( dxtWidth * dxtHeight ); + if ( colorFormat == CFM_NORMAL_DXT5 ) { + if ( image_highQualityCompression.GetBool() ) { + dxt.CompressNormalMapDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight ); + } else { + dxt.CompressNormalMapDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight ); + } + } else if ( colorFormat == CFM_YCOCG_DXT5 ) { + if ( image_highQualityCompression.GetBool() ) { + dxt.CompressYCoCgDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight ); + } else { + dxt.CompressYCoCgDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight ); + } + } else { + fileData.colorFormat = colorFormat = CFM_DEFAULT; + if ( image_highQualityCompression.GetBool() ) { + dxt.CompressImageDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight ); + } else { + dxt.CompressImageDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight ); + } + } + } else if ( textureFormat == FMT_LUM8 || textureFormat == FMT_INT8 ) { + // LUM8 and INT8 just read the red channel + img.Alloc( scaledWidth * scaledHeight ); + for ( int i = 0; i < img.dataSize; i++ ) { + img.data[ i ] = pic[ i * 4 ]; + } + } else if ( textureFormat == FMT_ALPHA ) { + // ALPHA reads the alpha channel + img.Alloc( scaledWidth * scaledHeight ); + for ( int i = 0; i < img.dataSize; i++ ) { + img.data[ i ] = pic[ i * 4 + 3 ]; + } + } else if ( textureFormat == FMT_L8A8 ) { + // L8A8 reads the alpha and red channels + img.Alloc( scaledWidth * scaledHeight * 2 ); + for ( int i = 0; i < img.dataSize / 2; i++ ) { + img.data[ i * 2 + 0 ] = pic[ i * 4 + 0 ]; + img.data[ i * 2 + 1 ] = pic[ i * 4 + 3 ]; + } + } else if ( textureFormat == FMT_RGB565 ) { + img.Alloc( scaledWidth * scaledHeight * 2 ); + for ( int i = 0; i < img.dataSize / 2; i++ ) { + unsigned short color = ( ( pic[ i * 4 + 0 ] >> 3 ) << 11 ) | ( ( pic[ i * 4 + 1 ] >> 2 ) << 5 ) | ( pic[ i * 4 + 2 ] >> 3 ); + img.data[ i * 2 + 0 ] = ( color >> 8 ) & 0xFF; + img.data[ i * 2 + 1 ] = color & 0xFF; + } + } else { + fileData.format = textureFormat = FMT_RGBA8; + img.Alloc( scaledWidth * scaledHeight * 4 ); + for ( int i = 0; i < img.dataSize; i++ ) { + img.data[ i ] = pic[ i ]; + } + } + + // if we had to pad to quads, free the padded version + if ( pic != dxtPic ) { + Mem_Free( dxtPic ); + dxtPic = NULL; + } + + // downsample for the next level + byte * shrunk = NULL; + if ( gammaMips ) { + shrunk = R_MipMapWithGamma( pic, scaledWidth, scaledHeight ); + } else { + shrunk = R_MipMap( pic, scaledWidth, scaledHeight ); + } + Mem_Free( pic ); + pic = shrunk; + + scaledWidth = Max( 1, scaledWidth >> 1 ); + scaledHeight = Max( 1, scaledHeight >> 1 ); + } + + Mem_Free( pic ); +} + +/* +======================== +PadImageTo4x4 + +DXT Compression requres a complete 4x4 block, even if the GPU will only be sampling +a subset of it, so pad to 4x4 with replicated texels to maximize compression. +======================== +*/ +static void PadImageTo4x4( const byte *src, int width, int height, byte dest[64] ) { + // we probably will need to support this for non-square images, but I'll address + // that when needed + assert( width <= 4 && height <= 4 ); + assert( width > 0 && height > 0 ); + + for ( int y = 0 ; y < 4 ; y++ ) { + int sy = y % height; + for ( int x = 0 ; x < 4 ; x++ ) { + int sx = x % width; + for ( int c = 0 ; c < 4 ; c++ ) { + dest[(y*4+x)*4+c] = src[(sy*width+sx)*4+c]; + } + } + } +} + +/* +======================== +idBinaryImage::LoadCubeFromMemory +======================== +*/ +void idBinaryImage::LoadCubeFromMemory( int width, const byte * pics[6], int numLevels, textureFormat_t & textureFormat, bool gammaMips ) { + fileData.textureType = TT_CUBIC; + fileData.format = textureFormat; + fileData.colorFormat = CFM_DEFAULT; + fileData.height = fileData.width = width; + fileData.numLevels = numLevels; + + images.SetNum( fileData.numLevels * 6 ); + + for ( int side = 0; side < 6; side++ ) { + const byte *orig = pics[side]; + const byte *pic = orig; + int scaledWidth = fileData.width; + for ( int level = 0; level < fileData.numLevels; level++ ) { + // compress data or convert floats as necessary + idBinaryImageData &img = images[ level * 6 + side ]; + + // handle padding blocks less than 4x4 for the DXT compressors + ALIGN16( byte padBlock[64] ); + int padSize; + const byte *padSrc; + if ( scaledWidth < 4 && ( textureFormat == FMT_DXT1 || textureFormat == FMT_DXT5 ) ) { + PadImageTo4x4( pic, scaledWidth, scaledWidth, padBlock ); + padSize = 4; + padSrc = padBlock; + } else { + padSize = scaledWidth; + padSrc = pic; + } + + img.level = level; + img.destZ = side; + img.width = padSize; + img.height = padSize; + if ( textureFormat == FMT_DXT1 ) { + img.Alloc( padSize * padSize / 2 ); + idDxtEncoder dxt; + dxt.CompressImageDXT1Fast( padSrc, img.data, padSize, padSize ); + } else if ( textureFormat == FMT_DXT5 ) { + img.Alloc( padSize * padSize ); + idDxtEncoder dxt; + dxt.CompressImageDXT5Fast( padSrc, img.data, padSize, padSize ); + } else { + fileData.format = textureFormat = FMT_RGBA8; + img.Alloc( padSize * padSize * 4 ); + memcpy( img.data, pic, img.dataSize ); + } + + // downsample for the next level + byte * shrunk = NULL; + if ( gammaMips ) { + shrunk = R_MipMapWithGamma( pic, scaledWidth, scaledWidth ); + } else { + shrunk = R_MipMap( pic, scaledWidth, scaledWidth ); + } + if ( pic != orig ) { + Mem_Free( (void *)pic ); + pic = NULL; + } + pic = shrunk; + + scaledWidth = Max( 1, scaledWidth >> 1 ); + } + if ( pic != orig ) { + // free the down sampled version + Mem_Free( (void *)pic ); + pic = NULL; + } + } +} + +/* +======================== +idBinaryImage::WriteGeneratedFile +======================== +*/ +ID_TIME_T idBinaryImage::WriteGeneratedFile( ID_TIME_T sourceFileTime ) { + idStr binaryFileName; + MakeGeneratedFileName( binaryFileName ); + idFileLocal file( fileSystem->OpenFileWrite( binaryFileName, "fs_basepath" ) ); + if ( file == NULL ) { + idLib::Warning( "idBinaryImage: Could not open file '%s'", binaryFileName.c_str() ); + return FILE_NOT_FOUND_TIMESTAMP; + } + idLib::Printf( "Writing %s\n", binaryFileName.c_str() ); + + fileData.headerMagic = BIMAGE_MAGIC; + fileData.sourceFileTime = sourceFileTime; + + file->WriteBig( fileData.sourceFileTime ); + file->WriteBig( fileData.headerMagic ); + file->WriteBig( fileData.textureType ); + file->WriteBig( fileData.format ); + file->WriteBig( fileData.colorFormat ); + file->WriteBig( fileData.width ); + file->WriteBig( fileData.height ); + file->WriteBig( fileData.numLevels ); + + for ( int i = 0; i < images.Num(); i++ ) { + idBinaryImageData &img = images[ i ]; + file->WriteBig( img.level ); + file->WriteBig( img.destZ ); + file->WriteBig( img.width ); + file->WriteBig( img.height ); + file->WriteBig( img.dataSize ); + file->Write( img.data, img.dataSize ); + } + return file->Timestamp(); +} + +/* +========================== +idBinaryImage::LoadFromGeneratedFile + +Load the preprocessed image from the generated folder. +========================== +*/ +ID_TIME_T idBinaryImage::LoadFromGeneratedFile( ID_TIME_T sourceFileTime ) { + idStr binaryFileName; + MakeGeneratedFileName( binaryFileName ); + idFileLocal bFile = fileSystem->OpenFileRead( binaryFileName ); + if ( bFile == NULL ) { + return FILE_NOT_FOUND_TIMESTAMP; + } + if ( LoadFromGeneratedFile( bFile, sourceFileTime ) ) { + return bFile->Timestamp(); + } + return FILE_NOT_FOUND_TIMESTAMP; +} + +/* +========================== +idBinaryImage::LoadFromGeneratedFile + +Load the preprocessed image from the generated folder. +========================== +*/ +bool idBinaryImage::LoadFromGeneratedFile( idFile * bFile, ID_TIME_T sourceFileTime ) { + if ( bFile->Read( &fileData, sizeof( fileData ) ) <= 0 ) { + return false; + } + idSwapClass swap; + swap.Big( fileData.sourceFileTime ); + swap.Big( fileData.headerMagic ); + swap.Big( fileData.textureType ); + swap.Big( fileData.format ); + swap.Big( fileData.colorFormat ); + swap.Big( fileData.width ); + swap.Big( fileData.height ); + swap.Big( fileData.numLevels ); + + if ( BIMAGE_MAGIC != fileData.headerMagic ) { + return false; + } + if ( fileData.sourceFileTime != sourceFileTime && !fileSystem->InProductionMode() ) { + return false; + } + + int numImages = fileData.numLevels; + if ( fileData.textureType == TT_CUBIC ) { + numImages *= 6; + } + + images.SetNum( numImages ); + + for ( int i = 0; i < numImages; i++ ) { + idBinaryImageData &img = images[ i ]; + if ( bFile->Read( &img, sizeof( bimageImage_t ) ) <= 0 ) { + return false; + } + idSwapClass swap; + swap.Big( img.level ); + swap.Big( img.destZ ); + swap.Big( img.width ); + swap.Big( img.height ); + swap.Big( img.dataSize ); + assert( img.level >= 0 && img.level < fileData.numLevels ); + assert( img.destZ == 0 || fileData.textureType == TT_CUBIC ); + assert( img.dataSize > 0 ); + // DXT images need to be padded to 4x4 block sizes, but the original image + // sizes are still retained, so the stored data size may be larger than + // just the multiplication of dimensions + assert( img.dataSize >= img.width * img.height * BitsForFormat( (textureFormat_t)fileData.format ) / 8 ); + img.Alloc( img.dataSize ); + if ( img.data == NULL ) { + return false; + } + + if ( bFile->Read( img.data, img.dataSize ) <= 0 ) { + return false; + } + } + + return true; +} + +/* +========================== +idBinaryImage::MakeGeneratedFileName +========================== +*/ +void idBinaryImage::MakeGeneratedFileName( idStr & gfn ) { + GetGeneratedFileName( gfn, GetName() ); +} +/* +========================== +idBinaryImage::GetGeneratedFileName +========================== +*/ +void idBinaryImage::GetGeneratedFileName( idStr & gfn, const char *name ) { + gfn.Format( "generated/images/%s.bimage", name ); + gfn.Replace( "(", "/" ); + gfn.Replace( ",", "/" ); + gfn.Replace( ")", "" ); + gfn.Replace( " ", "" ); +} + + diff --git a/neo/renderer/BinaryImage.h b/neo/renderer/BinaryImage.h new file mode 100644 index 00000000..bcb7ad70 --- /dev/null +++ b/neo/renderer/BinaryImage.h @@ -0,0 +1,100 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __BINARYIMAGE_H__ +#define __BINARYIMAGE_H__ + +#include "BinaryImageData.h" + +/* +================================================ +idBinaryImage is used by the idImage class for constructing mipmapped +textures and for loading and saving generated files by idImage. +Also used in a memory-mapped form for imageCPU for offline megatexture +generation. +================================================ +*/ +class idBinaryImage { +public: + idBinaryImage( const char * name ) : imgName( name ) { } + + const char * GetName() const { return imgName.c_str(); } + void SetName( const char *_name ) { imgName = _name; } + + void Load2DFromMemory( int width, int height, const byte * pic_const, int numLevels, textureFormat_t & textureFormat, textureColor_t & colorFormat, bool gammaMips ); + void LoadCubeFromMemory( int width, const byte * pics[6], int numLevels, textureFormat_t & textureFormat, bool gammaMips ); + + ID_TIME_T LoadFromGeneratedFile( ID_TIME_T sourceFileTime ); + ID_TIME_T WriteGeneratedFile( ID_TIME_T sourceFileTime ); + + const bimageFile_t & GetFileHeader() { return fileData; } + + int NumImages() { return images.Num(); } + const bimageImage_t & GetImageHeader( int i ) const { return images[i]; } + const byte * GetImageData( int i ) const { return images[i].data; } + static void GetGeneratedFileName( idStr & gfn, const char *imageName ); +private: + idStr imgName; // game path, including extension (except for cube maps), may be an image program + bimageFile_t fileData; + + class idBinaryImageData : public bimageImage_t { + public: + byte * data; + + idBinaryImageData() : data( NULL ) { } + ~idBinaryImageData() { Free(); } + idBinaryImageData & operator=( idBinaryImageData & other ) { + if ( this == &other ) { + return *this; + } + + Alloc( other.dataSize ); + memcpy( data, other.data, other.dataSize ); + return *this; + } + void Free() { + if ( data != NULL ) { + Mem_Free( data ); + data = NULL; + dataSize = 0; + } + } + void Alloc( int size ) { + Free(); + dataSize = size; + data = (byte *)Mem_Alloc( size, TAG_CRAP ); + } + }; + + idList< idBinaryImageData, TAG_IDLIB_LIST_IMAGE > images; + +private: + void MakeGeneratedFileName( idStr & gfn ); + bool LoadFromGeneratedFile( idFile * f, ID_TIME_T sourceFileTime ); +}; + +#endif // __BINARYIMAGE_H__ diff --git a/neo/renderer/BinaryImageData.h b/neo/renderer/BinaryImageData.h new file mode 100644 index 00000000..96cde739 --- /dev/null +++ b/neo/renderer/BinaryImageData.h @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __BINARYIMAGEDATA_H__ +#define __BINARYIMAGEDATA_H__ + +/* +================================================================================================ + +This is where the Binary image headers go that are also included by external tools such as the cloud. + +================================================================================================ +*/ + +// These structures are used for memory mapping bimage files, but +// not for the normal loading, so be careful making changes. +// Values are big endien to reduce effort on consoles. +#define BIMAGE_VERSION 10 +#define BIMAGE_MAGIC (unsigned int)( ('B'<<0)|('I'<<8)|('M'<<16)|(BIMAGE_VERSION<<24) ) + +struct bimageImage_t { + int level; + int destZ; + int width; + int height; + int dataSize; + // dataSize bytes follow +}; + +#pragma pack( push, 1 ) +struct bimageFile_t { + ID_TIME_T sourceFileTime; + int headerMagic; + int textureType; + int format; + int colorFormat; + int width; + int height; + int numLevels; + // one or more bimageImage_t structures follow +}; +#pragma pack( pop ) + +#endif // __BINARYIMAGEDATA_H__ diff --git a/neo/renderer/BoundsTrack.cpp b/neo/renderer/BoundsTrack.cpp new file mode 100644 index 00000000..358dad3f --- /dev/null +++ b/neo/renderer/BoundsTrack.cpp @@ -0,0 +1,291 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#undef min // windef.h macros +#undef max + +#include "BoundsTrack.h" + +/* + +We want to do one SIMD compare on 8 short components and know that the bounds +overlap if all 8 tests pass + +*/ + +// shortBounds_t is used to track the reference bounds of all entities in a +// cache-friendly and easy to compare way. +// +// To allow all elements to be compared with a single comparison sense, the maxs +// are stored as negated values. +// +// We may need to add a global scale factor to this if there are intersections +// completely outside +/-32k +struct shortBounds_t { + shortBounds_t() { + SetToEmpty(); + } + + shortBounds_t( const idBounds & b ) { + SetFromReferenceBounds( b ); + } + + short b[2][4]; // fourth element is just for padding + + idBounds ToFloatBounds() const { + idBounds f; + for ( int i = 0 ; i < 3 ; i++ ) { + f[0][i] = b[0][i]; + f[1][i] = -b[1][i]; + } + return f; + } + + bool IntersectsShortBounds( shortBounds_t & comp ) const { + shortBounds_t test; + comp.MakeComparisonBounds( test ); + return IntersectsComparisonBounds( test ); + } + + bool IntersectsComparisonBounds( shortBounds_t & test ) const { + // this can be a single ALTIVEC vcmpgtshR instruction + return test.b[0][0] > b[0][0] + && test.b[0][1] > b[0][1] + && test.b[0][2] > b[0][2] + && test.b[0][3] > b[0][3] + && test.b[1][0] > b[1][0] + && test.b[1][1] > b[1][1] + && test.b[1][2] > b[1][2] + && test.b[1][3] > b[1][3]; + } + + void MakeComparisonBounds( shortBounds_t & comp ) const { + comp.b[0][0] = -b[1][0]; + comp.b[1][0] = -b[0][0]; + comp.b[0][1] = -b[1][1]; + comp.b[1][1] = -b[0][1]; + comp.b[0][2] = -b[1][2]; + comp.b[1][2] = -b[0][2]; + comp.b[0][3] = 0x7fff; + comp.b[1][3] = 0x7fff; + } + + void SetFromReferenceBounds( const idBounds & set ) { + // the maxs are stored negated + for ( int i = 0 ; i < 3 ; i++ ) { + int minv = floor( set[0][i] ); + b[0][i] = std::max( -32768, minv ); + int maxv = -ceil( set[1][i] ); + b[1][i] = std::min( 32767, maxv ); + } + b[0][3] = b[1][3] = 0; + } + + void SetToEmpty() { + // this will always fail the comparison + for ( int i = 0 ; i < 2 ; i++ ) { + for ( int j = 0 ; j < 4 ; j++ ) { + b[i][j] = 0x7fff; + } + } + } +}; + + + +// pure function +int FindBoundsIntersectionsTEST( + const shortBounds_t testBounds, + const shortBounds_t * const boundsList, + const int numBounds, + int * const returnedList ) { + + int hits = 0; + idBounds testF = testBounds.ToFloatBounds(); + for ( int i = 0 ; i < numBounds ; i++ ) { + idBounds listF = boundsList[i].ToFloatBounds(); + if ( testF.IntersectsBounds( listF ) ) { + returnedList[hits++] = i; + } + } + return hits; +} + +// pure function +int FindBoundsIntersectionsSimSIMD( + const shortBounds_t testBounds, + const shortBounds_t * const boundsList, + const int numBounds, + int * const returnedList ) { + + shortBounds_t compareBounds; + testBounds.MakeComparisonBounds( compareBounds ); + + int hits = 0; + for ( int i = 0 ; i < numBounds ; i++ ) { + const shortBounds_t & listBounds = boundsList[i]; + bool compare[8]; + int count = 0; + for ( int j = 0 ; j < 8 ; j++ ) { + if ( ((short *)&compareBounds)[j] >= ((short *)&listBounds)[j] ) { + compare[j] = true; + count++; + } else { + compare[j] = false; + } + } + if ( count == 8 ) { + returnedList[hits++] = i; + } + } + return hits; +} + + + +idBoundsTrack::idBoundsTrack() { + boundsList = (shortBounds_t *)Mem_Alloc( MAX_BOUNDS_TRACK_INDEXES * sizeof( *boundsList ), TAG_RENDER ); + ClearAll(); +} + +idBoundsTrack::~idBoundsTrack() { + Mem_Free( boundsList ); +} + +void idBoundsTrack::ClearAll() { + maxIndex = 0; + for ( int i = 0 ; i < MAX_BOUNDS_TRACK_INDEXES ; i++ ) { + ClearIndex( i ); + } +} + +void idBoundsTrack::SetIndex( const int index, const idBounds & bounds ) { + assert( (unsigned)index < MAX_BOUNDS_TRACK_INDEXES ); + maxIndex = std::max( maxIndex, index+1); + boundsList[index].SetFromReferenceBounds( bounds ); +} + +void idBoundsTrack::ClearIndex( const int index ) { + assert( (unsigned)index < MAX_BOUNDS_TRACK_INDEXES ); + boundsList[index].SetToEmpty(); +} + +int idBoundsTrack::FindIntersections( const idBounds & testBounds, int intersectedIndexes[ MAX_BOUNDS_TRACK_INDEXES ] ) const { + const shortBounds_t shortTestBounds( testBounds ); + return FindBoundsIntersectionsTEST( shortTestBounds, boundsList, maxIndex, intersectedIndexes ); +} + +void idBoundsTrack::Test() { + ClearAll(); + idRandom r; + + for ( int i = 0 ; i < 1800 ; i++ ) { + idBounds b; + for ( int j = 0 ; j < 3 ; j++ ) { + b[0][j] = r.RandomInt( 20000 ) - 10000; + b[1][j] = b[0][j] + r.RandomInt( 1000 ); + } + SetIndex( i, b ); + } + + const idBounds testBounds( idVec3( -1000, 2000, -3000 ), idVec3( 1500, 4500, -500 ) ); + SetIndex( 1800, testBounds ); + SetIndex( 0, testBounds ); + + const shortBounds_t shortTestBounds( testBounds ); + + int intersectedIndexes1[ MAX_BOUNDS_TRACK_INDEXES ]; + const int numHits1 = FindBoundsIntersectionsTEST( shortTestBounds, boundsList, maxIndex, intersectedIndexes1 ); + + int intersectedIndexes2[ MAX_BOUNDS_TRACK_INDEXES ]; + const int numHits2 = FindBoundsIntersectionsSimSIMD( shortTestBounds, boundsList, maxIndex, intersectedIndexes2 ); + idLib::Printf( "%i intersections\n", numHits1 ); + if ( numHits1 != numHits2 ) { + idLib::Printf( "different results\n" ); + } else { + for ( int i = 0 ; i < numHits1 ; i++ ) { + if ( intersectedIndexes1[i] != intersectedIndexes2[i] ) { + idLib::Printf( "different results\n" ); + break; + } + } + } + + // run again for debugging failure + FindBoundsIntersectionsTEST( shortTestBounds, boundsList, maxIndex, intersectedIndexes1 ); + FindBoundsIntersectionsSimSIMD( shortTestBounds, boundsList, maxIndex, intersectedIndexes2 ); + + // timing + const int64 start = Sys_Microseconds(); + for ( int i = 0 ; i < 40 ; i++ ) { + FindBoundsIntersectionsSimSIMD( shortTestBounds, boundsList, maxIndex, intersectedIndexes2 ); + } + const int64 stop = Sys_Microseconds(); + idLib::Printf( "%i microseconds for 40 itterations\n", stop - start ); +} + + + +class interactionPair_t { + int entityIndex; + int lightIndex; +}; + +/* + +keep a sorted list of static interactions and interactions already generated this frame? + +determine if the light needs more exact culling because it is rotated or a spot light +for each entity on the bounds intersection list + if entity is not directly visible, determine if it can cast a shadow into the view + if the light center is in-frustum + and the entity bounds is out-of-frustum, it can't contribue + else the light center is off-frustum + if any of the view frustum planes can be moved out to the light center and the entity bounds is still outside it, it can't contribute + if a static interaction exists + continue + possibly perform more exact refernce bounds to rotated or spot light + + create an interaction pair and add it to the list + + +all models will have an interaction with light -1 for ambient surface +sort the interaction list by model +do + if the model is dynamic, create it + add the ambient surface and skip interaction -1 + for all interactions + check for static interaction + check for current-frame interaction + else create shadow for this light + +*/ diff --git a/neo/renderer/BoundsTrack.h b/neo/renderer/BoundsTrack.h new file mode 100644 index 00000000..4630b1df --- /dev/null +++ b/neo/renderer/BoundsTrack.h @@ -0,0 +1,72 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __BOUNDSTRACK_H__ +#define __BOUNDSTRACK_H__ + + +struct shortBounds_t; + +class idBoundsTrack { +public: + idBoundsTrack(); + ~idBoundsTrack(); + + void ClearAll(); + + // more than this will thrash a 32k L1 data cache + static const int MAX_BOUNDS_TRACK_INDEXES = 2048; + + // the bounds will be clamped and rounded to short integers + void SetIndex( const int index, const idBounds & bounds ); + + // an index that has been cleared will never be returned by FindIntersections() + void ClearIndex( const int index ); + + // returns the number of indexes filled in intersectedIndexes[] + // + // The intersections may include some bounds that are not truly overlapping + // due to the rounding from float to short integers. + int FindIntersections( const idBounds & testBounds, int intersectedIndexes[ MAX_BOUNDS_TRACK_INDEXES ] ) const; + + // validate implementation + void Test(); + +private: + // All elements that haven't had SetIndex() called since ClearAll() will be + // in the cleared state, so they can safely be compared against by an + // unwound loop. + shortBounds_t *boundsList; // [MAX_BOUNDS_TRACK_INDEXES] + + // set to 0 at ClearAll(), maintained greater than the highest index passed + // to SetIndex() since ClearAll(). + int maxIndex; +}; + + +#endif // __BOUNDSTRACK_H__ diff --git a/neo/renderer/BufferObject.cpp b/neo/renderer/BufferObject.cpp new file mode 100644 index 00000000..2e42d2fb --- /dev/null +++ b/neo/renderer/BufferObject.cpp @@ -0,0 +1,833 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "tr_local.h" + +idCVar r_showBuffers( "r_showBuffers", "0", CVAR_INTEGER, "" ); + + +//static const GLenum bufferUsage = GL_STATIC_DRAW_ARB; +static const GLenum bufferUsage = GL_DYNAMIC_DRAW_ARB; + +/* +================== +IsWriteCombined +================== +*/ +bool IsWriteCombined( void * base ) { + MEMORY_BASIC_INFORMATION info; + SIZE_T size = VirtualQueryEx( GetCurrentProcess(), base, &info, sizeof( info ) ); + if ( size == 0 ) { + DWORD error = GetLastError(); + error = error; + return false; + } + bool isWriteCombined = ( ( info.AllocationProtect & PAGE_WRITECOMBINE ) != 0 ); + return isWriteCombined; +} + + + +/* +================================================================================================ + + Buffer Objects + +================================================================================================ +*/ + +/* +======================== +UnbindBufferObjects +======================== +*/ +void UnbindBufferObjects() { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); +} + + +void CopyBuffer( byte * dst, const byte * src, int numBytes ) { + assert_16_byte_aligned( dst ); + assert_16_byte_aligned( src ); + + int i = 0; + for ( ; i + 128 <= numBytes; i += 128 ) { + __m128i d0 = _mm_load_si128( (__m128i *)&src[i + 0*16] ); + __m128i d1 = _mm_load_si128( (__m128i *)&src[i + 1*16] ); + __m128i d2 = _mm_load_si128( (__m128i *)&src[i + 2*16] ); + __m128i d3 = _mm_load_si128( (__m128i *)&src[i + 3*16] ); + __m128i d4 = _mm_load_si128( (__m128i *)&src[i + 4*16] ); + __m128i d5 = _mm_load_si128( (__m128i *)&src[i + 5*16] ); + __m128i d6 = _mm_load_si128( (__m128i *)&src[i + 6*16] ); + __m128i d7 = _mm_load_si128( (__m128i *)&src[i + 7*16] ); + _mm_stream_si128( (__m128i *)&dst[i + 0*16], d0 ); + _mm_stream_si128( (__m128i *)&dst[i + 1*16], d1 ); + _mm_stream_si128( (__m128i *)&dst[i + 2*16], d2 ); + _mm_stream_si128( (__m128i *)&dst[i + 3*16], d3 ); + _mm_stream_si128( (__m128i *)&dst[i + 4*16], d4 ); + _mm_stream_si128( (__m128i *)&dst[i + 5*16], d5 ); + _mm_stream_si128( (__m128i *)&dst[i + 6*16], d6 ); + _mm_stream_si128( (__m128i *)&dst[i + 7*16], d7 ); + } + for ( ; i + 16 <= numBytes; i += 16 ) { + __m128i d = _mm_load_si128( (__m128i *)&src[i] ); + _mm_stream_si128( (__m128i *)&dst[i], d ); + } + for ( ; i + 4 <= numBytes; i += 4 ) { + *(uint32 *)&dst[i] = *(const uint32 *)&src[i]; + } + for ( ; i < numBytes; i++ ) { + dst[i] = src[i]; + } + _mm_sfence(); +} + + +/* +================================================================================================ + + idVertexBuffer + +================================================================================================ +*/ + +/* +======================== +idVertexBuffer::idVertexBuffer +======================== +*/ +idVertexBuffer::idVertexBuffer() { + size = 0; + offsetInOtherBuffer = OWNS_BUFFER_FLAG; + apiObject = NULL; + SetUnmapped(); +} + +/* +======================== +idVertexBuffer::~idVertexBuffer +======================== +*/ +idVertexBuffer::~idVertexBuffer() { + FreeBufferObject(); +} + +/* +======================== +idVertexBuffer::AllocBufferObject +======================== +*/ +bool idVertexBuffer::AllocBufferObject( const void * data, int allocSize ) { + assert( apiObject == NULL ); + assert_16_byte_aligned( data ); + + if ( allocSize <= 0 ) { + idLib::Error( "idVertexBuffer::AllocBufferObject: allocSize = %i", allocSize ); + } + + size = allocSize; + + bool allocationFailed = false; + + int numBytes = GetAllocedSize(); + + + // clear out any previous error + qglGetError(); + + GLuint bufferObject = 0xFFFF; + qglGenBuffersARB( 1, & bufferObject ); + if ( bufferObject == 0xFFFF ) { + idLib::FatalError( "idVertexBuffer::AllocBufferObject: failed" ); + } + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, bufferObject ); + + // these are rewritten every frame + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, numBytes, NULL, bufferUsage ); + apiObject = reinterpret_cast< void * >( bufferObject ); + + GLenum err = qglGetError(); + if ( err == GL_OUT_OF_MEMORY ) { + idLib::Warning( "idVertexBuffer::AllocBufferObject: allocation failed" ); + allocationFailed = true; + } + + + if ( r_showBuffers.GetBool() ) { + idLib::Printf( "vertex buffer alloc %p, api %p (%i bytes)\n", this, GetAPIObject(), GetSize() ); + } + + // copy the data + if ( data != NULL ) { + Update( data, allocSize ); + } + + return !allocationFailed; +} + +/* +======================== +idVertexBuffer::FreeBufferObject +======================== +*/ +void idVertexBuffer::FreeBufferObject() { + if ( IsMapped() ) { + UnmapBuffer(); + } + + // if this is a sub-allocation inside a larger buffer, don't actually free anything. + if ( OwnsBuffer() == false ) { + ClearWithoutFreeing(); + return; + } + + if ( apiObject == NULL ) { + return; + } + + if ( r_showBuffers.GetBool() ) { + idLib::Printf( "vertex buffer free %p, api %p (%i bytes)\n", this, GetAPIObject(), GetSize() ); + } + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglDeleteBuffersARB( 1, & bufferObject ); + + ClearWithoutFreeing(); +} + +/* +======================== +idVertexBuffer::Reference +======================== +*/ +void idVertexBuffer::Reference( const idVertexBuffer & other ) { + assert( IsMapped() == false ); + //assert( other.IsMapped() == false ); // this happens when building idTriangles while at the same time setting up idDrawVerts + assert( other.GetAPIObject() != NULL ); + assert( other.GetSize() > 0 ); + + FreeBufferObject(); + size = other.GetSize(); // this strips the MAPPED_FLAG + offsetInOtherBuffer = other.GetOffset(); // this strips the OWNS_BUFFER_FLAG + apiObject = other.apiObject; + assert( OwnsBuffer() == false ); +} + +/* +======================== +idVertexBuffer::Reference +======================== +*/ +void idVertexBuffer::Reference( const idVertexBuffer & other, int refOffset, int refSize ) { + assert( IsMapped() == false ); + //assert( other.IsMapped() == false ); // this happens when building idTriangles while at the same time setting up idDrawVerts + assert( other.GetAPIObject() != NULL ); + assert( refOffset >= 0 ); + assert( refSize >= 0 ); + assert( refOffset + refSize <= other.GetSize() ); + + FreeBufferObject(); + size = refSize; + offsetInOtherBuffer = other.GetOffset() + refOffset; + apiObject = other.apiObject; + assert( OwnsBuffer() == false ); +} + +/* +======================== +idVertexBuffer::Update +======================== +*/ +void idVertexBuffer::Update( const void * data, int updateSize ) const { + assert( apiObject != NULL ); + assert( IsMapped() == false ); + assert_16_byte_aligned( data ); + assert( ( GetOffset() & 15 ) == 0 ); + + if ( updateSize > size ) { + idLib::FatalError( "idVertexBuffer::Update: size overrun, %i > %i\n", updateSize, GetSize() ); + } + + int numBytes = ( updateSize + 15 ) & ~15; + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, bufferObject ); + qglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, GetOffset(), (GLsizeiptrARB)numBytes, data ); +/* + void * buffer = MapBuffer( BM_WRITE ); + CopyBuffer( (byte *)buffer + GetOffset(), (byte *)data, numBytes ); + UnmapBuffer(); +*/ +} + +/* +======================== +idVertexBuffer::MapBuffer +======================== +*/ +void * idVertexBuffer::MapBuffer( bufferMapType_t mapType ) const { + assert( apiObject != NULL ); + assert( IsMapped() == false ); + + void * buffer = NULL; + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, bufferObject ); + if ( mapType == BM_READ ) { + //buffer = qglMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_READ_ONLY_ARB ); + buffer = qglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, GetAllocedSize(), GL_MAP_READ_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT ); + if ( buffer != NULL ) { + buffer = (byte *)buffer + GetOffset(); + } + } else if ( mapType == BM_WRITE ) { + //buffer = qglMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB ); + buffer = qglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, GetAllocedSize(), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT ); + if ( buffer != NULL ) { + buffer = (byte *)buffer + GetOffset(); + } + assert( IsWriteCombined( buffer ) ); + } else { + assert( false ); + } + + SetMapped(); + + if ( buffer == NULL ) { + idLib::FatalError( "idVertexBuffer::MapBuffer: failed" ); + } + return buffer; +} + +/* +======================== +idVertexBuffer::UnmapBuffer +======================== +*/ +void idVertexBuffer::UnmapBuffer() const { + assert( apiObject != NULL ); + assert( IsMapped() ); + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, bufferObject ); + if ( !qglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ) ) { + idLib::Printf( "idVertexBuffer::UnmapBuffer failed\n" ); + } + + SetUnmapped(); +} + +/* +======================== +idVertexBuffer::ClearWithoutFreeing +======================== +*/ +void idVertexBuffer::ClearWithoutFreeing() { + size = 0; + offsetInOtherBuffer = OWNS_BUFFER_FLAG; + apiObject = NULL; +} + +/* +================================================================================================ + + idIndexBuffer + +================================================================================================ +*/ + +/* +======================== +idIndexBuffer::idIndexBuffer +======================== +*/ +idIndexBuffer::idIndexBuffer() { + size = 0; + offsetInOtherBuffer = OWNS_BUFFER_FLAG; + apiObject = NULL; + SetUnmapped(); +} + +/* +======================== +idIndexBuffer::~idIndexBuffer +======================== +*/ +idIndexBuffer::~idIndexBuffer() { + FreeBufferObject(); +} + +/* +======================== +idIndexBuffer::AllocBufferObject +======================== +*/ +bool idIndexBuffer::AllocBufferObject( const void * data, int allocSize ) { + assert( apiObject == NULL ); + assert_16_byte_aligned( data ); + + if ( allocSize <= 0 ) { + idLib::Error( "idIndexBuffer::AllocBufferObject: allocSize = %i", allocSize ); + } + + size = allocSize; + + bool allocationFailed = false; + + int numBytes = GetAllocedSize(); + + + // clear out any previous error + qglGetError(); + + GLuint bufferObject = 0xFFFF; + qglGenBuffersARB( 1, & bufferObject ); + if ( bufferObject == 0xFFFF ) { + GLenum error = qglGetError(); + idLib::FatalError( "idIndexBuffer::AllocBufferObject: failed - GL_Error %d", error ); + } + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bufferObject ); + + // these are rewritten every frame + qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, numBytes, NULL, bufferUsage ); + apiObject = reinterpret_cast< void * >( bufferObject ); + + GLenum err = qglGetError(); + if ( err == GL_OUT_OF_MEMORY ) { + idLib::Warning( "idIndexBuffer:AllocBufferObject: allocation failed" ); + allocationFailed = true; + } + + + if ( r_showBuffers.GetBool() ) { + idLib::Printf( "index buffer alloc %p, api %p (%i bytes)\n", this, GetAPIObject(), GetSize() ); + } + + // copy the data + if ( data != NULL ) { + Update( data, allocSize ); + } + + return !allocationFailed; +} + +/* +======================== +idIndexBuffer::FreeBufferObject +======================== +*/ +void idIndexBuffer::FreeBufferObject() { + if ( IsMapped() ) { + UnmapBuffer(); + } + + // if this is a sub-allocation inside a larger buffer, don't actually free anything. + if ( OwnsBuffer() == false ) { + ClearWithoutFreeing(); + return; + } + + if ( apiObject == NULL ) { + return; + } + + if ( r_showBuffers.GetBool() ) { + idLib::Printf( "index buffer free %p, api %p (%i bytes)\n", this, GetAPIObject(), GetSize() ); + } + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglDeleteBuffersARB( 1, & bufferObject ); + + ClearWithoutFreeing(); +} + +/* +======================== +idIndexBuffer::Reference +======================== +*/ +void idIndexBuffer::Reference( const idIndexBuffer & other ) { + assert( IsMapped() == false ); + //assert( other.IsMapped() == false ); // this happens when building idTriangles while at the same time setting up triIndex_t + assert( other.GetAPIObject() != NULL ); + assert( other.GetSize() > 0 ); + + FreeBufferObject(); + size = other.GetSize(); // this strips the MAPPED_FLAG + offsetInOtherBuffer = other.GetOffset(); // this strips the OWNS_BUFFER_FLAG + apiObject = other.apiObject; + assert( OwnsBuffer() == false ); +} + +/* +======================== +idIndexBuffer::Reference +======================== +*/ +void idIndexBuffer::Reference( const idIndexBuffer & other, int refOffset, int refSize ) { + assert( IsMapped() == false ); + //assert( other.IsMapped() == false ); // this happens when building idTriangles while at the same time setting up triIndex_t + assert( other.GetAPIObject() != NULL ); + assert( refOffset >= 0 ); + assert( refSize >= 0 ); + assert( refOffset + refSize <= other.GetSize() ); + + FreeBufferObject(); + size = refSize; + offsetInOtherBuffer = other.GetOffset() + refOffset; + apiObject = other.apiObject; + assert( OwnsBuffer() == false ); +} + +/* +======================== +idIndexBuffer::Update +======================== +*/ +void idIndexBuffer::Update( const void * data, int updateSize ) const { + + assert( apiObject != NULL ); + assert( IsMapped() == false ); + assert_16_byte_aligned( data ); + assert( ( GetOffset() & 15 ) == 0 ); + + if ( updateSize > size ) { + idLib::FatalError( "idIndexBuffer::Update: size overrun, %i > %i\n", updateSize, GetSize() ); + } + + int numBytes = ( updateSize + 15 ) & ~15; + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bufferObject ); + qglBufferSubDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GetOffset(), (GLsizeiptrARB)numBytes, data ); +/* + void * buffer = MapBuffer( BM_WRITE ); + CopyBuffer( (byte *)buffer + GetOffset(), (byte *)data, numBytes ); + UnmapBuffer(); +*/ +} + +/* +======================== +idIndexBuffer::MapBuffer +======================== +*/ +void * idIndexBuffer::MapBuffer( bufferMapType_t mapType ) const { + + assert( apiObject != NULL ); + assert( IsMapped() == false ); + + void * buffer = NULL; + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bufferObject ); + if ( mapType == BM_READ ) { + //buffer = qglMapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GL_READ_ONLY_ARB ); + buffer = qglMapBufferRange( GL_ELEMENT_ARRAY_BUFFER_ARB, 0, GetAllocedSize(), GL_MAP_READ_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT ); + if ( buffer != NULL ) { + buffer = (byte *)buffer + GetOffset(); + } + } else if ( mapType == BM_WRITE ) { + //buffer = qglMapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB ); + buffer = qglMapBufferRange( GL_ELEMENT_ARRAY_BUFFER_ARB, 0, GetAllocedSize(), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT ); + if ( buffer != NULL ) { + buffer = (byte *)buffer + GetOffset(); + } + assert( IsWriteCombined( buffer ) ); + } else { + assert( false ); + } + + SetMapped(); + + if ( buffer == NULL ) { + idLib::FatalError( "idIndexBuffer::MapBuffer: failed" ); + } + return buffer; +} + +/* +======================== +idIndexBuffer::UnmapBuffer +======================== +*/ +void idIndexBuffer::UnmapBuffer() const { + assert( apiObject != NULL ); + assert( IsMapped() ); + + GLuint bufferObject = reinterpret_cast< GLuint >( apiObject ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, bufferObject ); + if ( !qglUnmapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB ) ) { + idLib::Printf( "idIndexBuffer::UnmapBuffer failed\n" ); + } + + SetUnmapped(); +} + +/* +======================== +idIndexBuffer::ClearWithoutFreeing +======================== +*/ +void idIndexBuffer::ClearWithoutFreeing() { + size = 0; + offsetInOtherBuffer = OWNS_BUFFER_FLAG; + apiObject = NULL; +} + +/* +================================================================================================ + + idJointBuffer + +================================================================================================ +*/ + +/* +======================== +idJointBuffer::idJointBuffer +======================== +*/ +idJointBuffer::idJointBuffer() { + numJoints = 0; + offsetInOtherBuffer = OWNS_BUFFER_FLAG; + apiObject = NULL; + SetUnmapped(); +} + +/* +======================== +idJointBuffer::~idJointBuffer +======================== +*/ +idJointBuffer::~idJointBuffer() { + FreeBufferObject(); +} + +/* +======================== +idJointBuffer::AllocBufferObject +======================== +*/ +bool idJointBuffer::AllocBufferObject( const float * joints, int numAllocJoints ) { + assert( apiObject == NULL ); + assert_16_byte_aligned( joints ); + + if ( numAllocJoints <= 0 ) { + idLib::Error( "idJointBuffer::AllocBufferObject: joints = %i", numAllocJoints ); + } + + numJoints = numAllocJoints; + + bool allocationFailed = false; + + const int numBytes = GetAllocedSize(); + + GLuint buffer = 0; + qglGenBuffersARB( 1, &buffer ); + qglBindBufferARB( GL_UNIFORM_BUFFER, buffer ); + qglBufferDataARB( GL_UNIFORM_BUFFER, numBytes, NULL, GL_STREAM_DRAW_ARB ); + qglBindBufferARB( GL_UNIFORM_BUFFER, 0); + apiObject = reinterpret_cast< void * >( buffer ); + + if ( r_showBuffers.GetBool() ) { + idLib::Printf( "joint buffer alloc %p, api %p (%i joints)\n", this, GetAPIObject(), GetNumJoints() ); + } + + // copy the data + if ( joints != NULL ) { + Update( joints, numAllocJoints ); + } + + return !allocationFailed; +} + +/* +======================== +idJointBuffer::FreeBufferObject +======================== +*/ +void idJointBuffer::FreeBufferObject() { + if ( IsMapped() ) { + UnmapBuffer(); + } + + // if this is a sub-allocation inside a larger buffer, don't actually free anything. + if ( OwnsBuffer() == false ) { + ClearWithoutFreeing(); + return; + } + + if ( apiObject == NULL ) { + return; + } + + if ( r_showBuffers.GetBool() ) { + idLib::Printf( "joint buffer free %p, api %p (%i joints)\n", this, GetAPIObject(), GetNumJoints() ); + } + + GLuint buffer = reinterpret_cast< GLuint > ( apiObject ); + qglBindBufferARB( GL_UNIFORM_BUFFER, 0 ); + qglDeleteBuffersARB( 1, & buffer ); + + ClearWithoutFreeing(); +} + +/* +======================== +idJointBuffer::Reference +======================== +*/ +void idJointBuffer::Reference( const idJointBuffer & other ) { + assert( IsMapped() == false ); + assert( other.IsMapped() == false ); + assert( other.GetAPIObject() != NULL ); + assert( other.GetNumJoints() > 0 ); + + FreeBufferObject(); + numJoints = other.GetNumJoints(); // this strips the MAPPED_FLAG + offsetInOtherBuffer = other.GetOffset(); // this strips the OWNS_BUFFER_FLAG + apiObject = other.apiObject; + assert( OwnsBuffer() == false ); +} + +/* +======================== +idJointBuffer::Reference +======================== +*/ +void idJointBuffer::Reference( const idJointBuffer & other, int jointRefOffset, int numRefJoints ) { + assert( IsMapped() == false ); + assert( other.IsMapped() == false ); + assert( other.GetAPIObject() != NULL ); + assert( jointRefOffset >= 0 ); + assert( numRefJoints >= 0 ); + assert( jointRefOffset + numRefJoints * sizeof( idJointMat ) <= other.GetNumJoints() * sizeof( idJointMat ) ); + assert_16_byte_aligned( numRefJoints * 3 * 4 * sizeof( float ) ); + + FreeBufferObject(); + numJoints = numRefJoints; + offsetInOtherBuffer = other.GetOffset() + jointRefOffset; + apiObject = other.apiObject; + assert( OwnsBuffer() == false ); +} + +/* +======================== +idJointBuffer::Update +======================== +*/ +void idJointBuffer::Update( const float * joints, int numUpdateJoints ) const { + assert( apiObject != NULL ); + assert( IsMapped() == false ); + assert_16_byte_aligned( joints ); + assert( ( GetOffset() & 15 ) == 0 ); + + if ( numUpdateJoints > numJoints ) { + idLib::FatalError( "idJointBuffer::Update: size overrun, %i > %i\n", numUpdateJoints, numJoints ); + } + + const int numBytes = numUpdateJoints * 3 * 4 * sizeof( float ); + + qglBindBufferARB( GL_UNIFORM_BUFFER, reinterpret_cast< GLuint >( apiObject ) ); + qglBufferSubDataARB( GL_UNIFORM_BUFFER, GetOffset(), (GLsizeiptrARB)numBytes, joints ); +} + +/* +======================== +idJointBuffer::MapBuffer +======================== +*/ +float * idJointBuffer::MapBuffer( bufferMapType_t mapType ) const { + assert( IsMapped() == false ); + assert( mapType == BM_WRITE ); + assert( apiObject != NULL ); + + int numBytes = GetAllocedSize(); + + void * buffer = NULL; + + qglBindBufferARB( GL_UNIFORM_BUFFER, reinterpret_cast< GLuint >( apiObject ) ); + numBytes = numBytes; + assert( GetOffset() == 0 ); + //buffer = qglMapBufferARB( GL_UNIFORM_BUFFER, GL_WRITE_ONLY_ARB ); + buffer = qglMapBufferRange( GL_UNIFORM_BUFFER, 0, GetAllocedSize(), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT ); + if ( buffer != NULL ) { + buffer = (byte *)buffer + GetOffset(); + } + + SetMapped(); + + if ( buffer == NULL ) { + idLib::FatalError( "idJointBuffer::MapBuffer: failed" ); + } + return (float *) buffer; +} + +/* +======================== +idJointBuffer::UnmapBuffer +======================== +*/ +void idJointBuffer::UnmapBuffer() const { + assert( apiObject != NULL ); + assert( IsMapped() ); + + qglBindBufferARB( GL_UNIFORM_BUFFER, reinterpret_cast< GLuint >( apiObject ) ); + if ( !qglUnmapBufferARB( GL_UNIFORM_BUFFER ) ) { + idLib::Printf( "idJointBuffer::UnmapBuffer failed\n" ); + } + + SetUnmapped(); +} + +/* +======================== +idJointBuffer::ClearWithoutFreeing +======================== +*/ +void idJointBuffer::ClearWithoutFreeing() { + numJoints = 0; + offsetInOtherBuffer = OWNS_BUFFER_FLAG; + apiObject = NULL; +} + +/* +======================== +idJointBuffer::Swap +======================== +*/ +void idJointBuffer::Swap( idJointBuffer & other ) { + // Make sure the ownership of the buffer is not transferred to an unintended place. + assert( other.OwnsBuffer() == OwnsBuffer() ); + + SwapValues( other.numJoints, numJoints ); + SwapValues( other.offsetInOtherBuffer, offsetInOtherBuffer ); + SwapValues( other.apiObject, apiObject ); +} diff --git a/neo/renderer/BufferObject.h b/neo/renderer/BufferObject.h new file mode 100644 index 00000000..6db64e77 --- /dev/null +++ b/neo/renderer/BufferObject.h @@ -0,0 +1,202 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __BUFFEROBJECT_H__ +#define __BUFFEROBJECT_H__ + +/* +================================================================================================ + + Buffer Objects + +================================================================================================ +*/ + +class idIndexBuffer; + +enum bufferMapType_t { + BM_READ, // map for reading + BM_WRITE // map for writing +}; + +// Returns all targets to virtual memory use instead of buffer object use. +// Call this before doing any conventional buffer reads, like screenshots. +void UnbindBufferObjects(); + +/* +================================================ +idVertexBuffer +================================================ +*/ +class idVertexBuffer { +public: + idVertexBuffer(); + ~idVertexBuffer(); + + // Allocate or free the buffer. + bool AllocBufferObject( const void * data, int allocSize ); + void FreeBufferObject(); + + // Make this buffer a reference to another buffer. + void Reference( const idVertexBuffer & other ); + void Reference( const idVertexBuffer & other, int refOffset, int refSize ); + + // Copies data to the buffer. 'size' may be less than the originally allocated size. + void Update( const void * data, int updateSize ) const; + + void * MapBuffer( bufferMapType_t mapType ) const; + idDrawVert * MapVertexBuffer( bufferMapType_t mapType ) const { return static_cast< idDrawVert * >( MapBuffer( mapType ) ); } + void UnmapBuffer() const; + bool IsMapped() const { return ( size & MAPPED_FLAG ) != 0; } + + int GetSize() const { return ( size & ~MAPPED_FLAG ); } + int GetAllocedSize() const { return ( ( size & ~MAPPED_FLAG ) + 15 ) & ~15; } + void * GetAPIObject() const { return apiObject; } + int GetOffset() const { return ( offsetInOtherBuffer & ~OWNS_BUFFER_FLAG ); } + +private: + int size; // size in bytes + int offsetInOtherBuffer; // offset in bytes + void * apiObject; + + // sizeof() confuses typeinfo... + static const int MAPPED_FLAG = 1 << ( 4 /* sizeof( int ) */ * 8 - 1 ); + static const int OWNS_BUFFER_FLAG = 1 << ( 4 /* sizeof( int ) */ * 8 - 1 ); + +private: + void ClearWithoutFreeing(); + void SetMapped() const { const_cast< int & >( size ) |= MAPPED_FLAG; } + void SetUnmapped() const { const_cast< int & >( size ) &= ~MAPPED_FLAG; } + bool OwnsBuffer() const { return ( ( offsetInOtherBuffer & OWNS_BUFFER_FLAG ) != 0 ); } + + DISALLOW_COPY_AND_ASSIGN( idVertexBuffer ); +}; + +/* +================================================ +idIndexBuffer +================================================ +*/ +class idIndexBuffer { +public: + idIndexBuffer(); + ~idIndexBuffer(); + + // Allocate or free the buffer. + bool AllocBufferObject( const void * data, int allocSize ); + void FreeBufferObject(); + + // Make this buffer a reference to another buffer. + void Reference( const idIndexBuffer & other ); + void Reference( const idIndexBuffer & other, int refOffset, int refSize ); + + // Copies data to the buffer. 'size' may be less than the originally allocated size. + void Update( const void * data, int updateSize ) const; + + void * MapBuffer( bufferMapType_t mapType ) const; + triIndex_t * MapIndexBuffer( bufferMapType_t mapType ) const { return static_cast< triIndex_t * >( MapBuffer( mapType ) ); } + void UnmapBuffer() const; + bool IsMapped() const { return ( size & MAPPED_FLAG ) != 0; } + + int GetSize() const { return ( size & ~MAPPED_FLAG ); } + int GetAllocedSize() const { return ( ( size & ~MAPPED_FLAG ) + 15 ) & ~15; } + void * GetAPIObject() const { return apiObject; } + int GetOffset() const { return ( offsetInOtherBuffer & ~OWNS_BUFFER_FLAG ); } + +private: + int size; // size in bytes + int offsetInOtherBuffer; // offset in bytes + void * apiObject; + + // sizeof() confuses typeinfo... + static const int MAPPED_FLAG = 1 << ( 4 /* sizeof( int ) */ * 8 - 1 ); + static const int OWNS_BUFFER_FLAG = 1 << ( 4 /* sizeof( int ) */ * 8 - 1 ); + +private: + void ClearWithoutFreeing(); + void SetMapped() const { const_cast< int & >( size ) |= MAPPED_FLAG; } + void SetUnmapped() const { const_cast< int & >( size ) &= ~MAPPED_FLAG; } + bool OwnsBuffer() const { return ( ( offsetInOtherBuffer & OWNS_BUFFER_FLAG ) != 0 ); } + + DISALLOW_COPY_AND_ASSIGN( idIndexBuffer ); +}; + +/* +================================================ +idJointBuffer + +IMPORTANT NOTICE: on the PC, binding to an offset in uniform buffer objects +is limited to GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, which is 256 on current nvidia cards, +so joint offsets, which are multiples of 48 bytes, must be in multiples of 16 = 768 bytes. +================================================ +*/ +class idJointBuffer { +public: + idJointBuffer(); + ~idJointBuffer(); + + // Allocate or free the buffer. + bool AllocBufferObject( const float * joints, int numAllocJoints ); + void FreeBufferObject(); + + // Make this buffer a reference to another buffer. + void Reference( const idJointBuffer & other ); + void Reference( const idJointBuffer & other, int jointRefOffset, int numRefJoints ); + + // Copies data to the buffer. 'numJoints' may be less than the originally allocated size. + void Update( const float * joints, int numUpdateJoints ) const; + + float * MapBuffer( bufferMapType_t mapType ) const; + void UnmapBuffer() const; + bool IsMapped() const { return ( numJoints & MAPPED_FLAG ) != 0; } + + int GetNumJoints() const { return ( numJoints & ~MAPPED_FLAG ); } + int GetAllocedSize() const { return ( numJoints & ~MAPPED_FLAG ) * 3 * 4 * sizeof( float ); } + void * GetAPIObject() const { return apiObject; } + int GetOffset() const { return ( offsetInOtherBuffer & ~OWNS_BUFFER_FLAG ); } + + void Swap( idJointBuffer & other ); + +private: + int numJoints; + int offsetInOtherBuffer; // offset in bytes + void * apiObject; + + // sizeof() confuses typeinfo... + static const int MAPPED_FLAG = 1 << ( 4 /* sizeof( int ) */ * 8 - 1 ); + static const int OWNS_BUFFER_FLAG = 1 << ( 4 /* sizeof( int ) */ * 8 - 1 ); + +private: + void ClearWithoutFreeing(); + void SetMapped() const { const_cast< int & >( numJoints ) |= MAPPED_FLAG; } + void SetUnmapped() const { const_cast< int & >( numJoints ) &= ~MAPPED_FLAG; } + bool OwnsBuffer() const { return ( ( offsetInOtherBuffer & OWNS_BUFFER_FLAG ) != 0 ); } + + DISALLOW_COPY_AND_ASSIGN( idJointBuffer ); +}; + +#endif // !__BUFFEROBJECT_H__ diff --git a/neo/renderer/Cinematic.cpp b/neo/renderer/Cinematic.cpp new file mode 100644 index 00000000..5d434f87 --- /dev/null +++ b/neo/renderer/Cinematic.cpp @@ -0,0 +1,177 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +extern idCVar s_noSound; + +#include "tr_local.h" + +//=========================================== + +/* +============== +idCinematic::InitCinematic +============== +*/ +void idCinematic::InitCinematic( void ) { +} + +/* +============== +idCinematic::ShutdownCinematic +============== +*/ +void idCinematic::ShutdownCinematic( void ) { +} + +/* +============== +idCinematic::Alloc +============== +*/ +idCinematic * idCinematic::Alloc() { + return new idCinematic; +} + +/* +============== +idCinematic::~idCinematic +============== +*/ +idCinematic::~idCinematic( ) { + Close(); +} + +/* +============== +idCinematic::InitFromFile +============== +*/ +bool idCinematic::InitFromFile( const char *qpath, bool looping ) { + return false; +} + +/* +============== +idCinematic::AnimationLength +============== +*/ +int idCinematic::AnimationLength() { + return 0; +} + +/* +============== +idCinematic::GetStartTime +============== +*/ +int idCinematic::GetStartTime() { + return -1; +} + +/* +============== +idCinematic::ResetTime +============== +*/ +void idCinematic::ResetTime(int milliseconds) { +} + +/* +============== +idCinematic::ImageForTime +============== +*/ +cinData_t idCinematic::ImageForTime( int milliseconds ) { + cinData_t c; + memset( &c, 0, sizeof( c ) ); + return c; +} + +/* +============== +idCinematic::ExportToTGA +============== +*/ +void idCinematic::ExportToTGA( bool skipExisting ) { +} + +/* +============== +idCinematic::GetFrameRate +============== +*/ +float idCinematic::GetFrameRate() const { + return 30.0f; +} + +/* +============== +idCinematic::Close +============== +*/ +void idCinematic::Close() { +} + +/* +============== +idSndWindow::InitFromFile +============== +*/ +bool idSndWindow::InitFromFile( const char *qpath, bool looping ) { + idStr fname = qpath; + + fname.ToLower(); + if ( !fname.Icmp( "waveform" ) ) { + showWaveform = true; + } else { + showWaveform = false; + } + return true; +} + +/* +============== +idSndWindow::ImageForTime +============== +*/ +cinData_t idSndWindow::ImageForTime( int milliseconds ) { + return soundSystem->ImageForTime( milliseconds, showWaveform ); +} + +/* +============== +idSndWindow::AnimationLength +============== +*/ +int idSndWindow::AnimationLength() { + return -1; +} diff --git a/neo/renderer/Cinematic.h b/neo/renderer/Cinematic.h new file mode 100644 index 00000000..861b63a6 --- /dev/null +++ b/neo/renderer/Cinematic.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __CINEMATIC_H__ +#define __CINEMATIC_H__ + +/* +=============================================================================== + + cinematic + + Multiple idCinematics can run simultaniously. + A single idCinematic can be reused for multiple files if desired. + +=============================================================================== +*/ + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} cinStatus_t; + +class idImage; + +// a cinematic stream generates an image buffer, which the caller will upload to a texture +typedef struct { + int imageWidth; + int imageHeight; // will be a power of 2 + idImage* imageY; + idImage* imageCr; + idImage* imageCb; + int status; +} cinData_t; + +class idCinematic { +public: + // initialize cinematic play back data + static void InitCinematic( void ); + + // shutdown cinematic play back data + static void ShutdownCinematic( void ); + + // allocates and returns a private subclass that implements the methods + // This should be used instead of new + static idCinematic *Alloc(); + + // frees all allocated memory + virtual ~idCinematic(); + + // returns false if it failed to load + virtual bool InitFromFile( const char *qpath, bool looping ); + + // returns the length of the animation in milliseconds + virtual int AnimationLength(); + + // the pointers in cinData_t will remain valid until the next UpdateForTime() call + virtual cinData_t ImageForTime( int milliseconds ); + + // closes the file and frees all allocated memory + virtual void Close(); + + // sets the cinematic to start at that time (can be in the past) + virtual void ResetTime(int time); + + // gets the time the cinematic started + virtual int GetStartTime(); + + virtual void ExportToTGA( bool skipExisting = true ); + + virtual float GetFrameRate() const; +}; + +/* +=============================================== + + Sound meter. + +=============================================== +*/ + +class idSndWindow : public idCinematic { +public: + + idSndWindow() { showWaveform = false; } + ~idSndWindow() {} + + bool InitFromFile( const char *qpath, bool looping ); + cinData_t ImageForTime( int milliseconds ); + int AnimationLength(); + +private: + bool showWaveform; +}; + +#endif /* !__CINEMATIC_H__ */ diff --git a/neo/renderer/Color/ColorSpace.cpp b/neo/renderer/Color/ColorSpace.cpp new file mode 100644 index 00000000..728fcafc --- /dev/null +++ b/neo/renderer/Color/ColorSpace.cpp @@ -0,0 +1,597 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +/* +================================================================================================ +Contains the ColorSpace conversion implementation. +================================================================================================ +*/ + +#include "ColorSpace.h" + +/* +================================================================================================ +To *Color-Convert RGB and YCoCg* ColorSpaces, use the following conversions: + + + Y = [ 1/4 1/2 1/4] [R] + Co = [ 1/2 0 -1/2] [G] + 128 + CG = [-1/4 1/2 -1/4] [B] + 128 + + R = [ 1 1 -1] [Y] + G = [ 1 0 1] [Co - 128] + B = [ 1 -1 -1] [Cg - 128] + +================================================================================================ +*/ + +#define RGB_TO_YCOCG_Y( r, g, b ) ( ( ( r + (g<<1) + b ) + 2 ) >> 2 ) +#define RGB_TO_YCOCG_CO( r, g, b ) ( ( ( (r<<1) - (b<<1) ) + 2 ) >> 2 ) +#define RGB_TO_YCOCG_CG( r, g, b ) ( ( ( - r + (g<<1) - b ) + 2 ) >> 2 ) + +#define COCG_TO_R( co, cg ) ( co - cg ) +#define COCG_TO_G( co, cg ) ( cg ) +#define COCG_TO_B( co, cg ) ( - co - cg ) + +/* +======================== +idColorSpace::ConvertRGBToYCoCg +======================== +*/ +void idColorSpace::ConvertRGBToYCoCg( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int r = src[i*4+0]; + int g = src[i*4+1]; + int b = src[i*4+2]; + int a = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( RGB_TO_YCOCG_Y( r, g, b ) ); + dst[i*4+1] = CLAMP_BYTE( RGB_TO_YCOCG_CO( r, g, b ) + 128 ); + dst[i*4+2] = CLAMP_BYTE( RGB_TO_YCOCG_CG( r, g, b ) + 128 ); + dst[i*4+3] = a; + } +} + +/* +======================== +idColorSpace::ConvertYCoCgToRGB +======================== +*/ +void idColorSpace::ConvertYCoCgToRGB( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int y = src[i*4+0]; + int co = src[i*4+1] - 128; + int cg = src[i*4+2] - 128; + int a = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( y + COCG_TO_R( co, cg ) ); + dst[i*4+1] = CLAMP_BYTE( y + COCG_TO_G( co, cg ) ); + dst[i*4+2] = CLAMP_BYTE( y + COCG_TO_B( co, cg ) ); + dst[i*4+3] = a; + } +} + +/* +======================== +idColorSpace::ConvertRGBToCoCg_Y +======================== +*/ +void idColorSpace::ConvertRGBToCoCg_Y( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int r = src[i*4+0]; + int g = src[i*4+1]; + int b = src[i*4+2]; + //int a = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( RGB_TO_YCOCG_CO( r, g, b ) + 128 ); + dst[i*4+1] = CLAMP_BYTE( RGB_TO_YCOCG_CG( r, g, b ) + 128 ); + dst[i*4+2] = 0; + dst[i*4+3] = CLAMP_BYTE( RGB_TO_YCOCG_Y( r, g, b ) ); + } +} + +/* +======================== +idColorSpace::ConvertCoCg_YToRGB +======================== +*/ +void idColorSpace::ConvertCoCg_YToRGB( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int co = src[i*4+0] - 128; + int cg = src[i*4+1] - 128; + int a = src[i*4+2]; + int y = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( y + COCG_TO_R( co, cg ) ); + dst[i*4+1] = CLAMP_BYTE( y + COCG_TO_G( co, cg ) ); + dst[i*4+2] = CLAMP_BYTE( y + COCG_TO_B( co, cg ) ); + dst[i*4+3] = a; + } +} + +/* +======================== +idColorSpace::ConvertCoCgSYToRGB + +A scale factor is encoded in the Z value to give better compression of +the color channels. +======================== +*/ +void idColorSpace::ConvertCoCgSYToRGB( byte * dst, const byte * src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int co = src[i*4+0] - 128; + int cg = src[i*4+1] - 128; + int a = src[i*4+2]; + int y = src[i*4+3]; + float scale = 1.0f / ( 1.0f + a * ( 31.875f / 255.0f ) ) ; + co = idMath::Ftoi( co * scale ); + cg = idMath::Ftoi( cg * scale ); + dst[i*4+0] = CLAMP_BYTE( y + COCG_TO_R( co, cg ) ); + dst[i*4+1] = CLAMP_BYTE( y + COCG_TO_G( co, cg ) ); + dst[i*4+2] = CLAMP_BYTE( y + COCG_TO_B( co, cg ) ); + dst[i*4+3] = 255; + } +} + +/* +======================== +idColorSpace::ConvertRGBToYCoCg420 +======================== +*/ +void idColorSpace::ConvertRGBToYCoCg420( byte *dst, const byte *src, int width, int height ) { + int numSamples = 0; + for ( int j = 0; j < height; j += 2 ) { + for ( int i = 0; i < width; i += 2 ) { + int r0 = src[((j+0)*width+i+0)*4+0]; + int g0 = src[((j+0)*width+i+0)*4+1]; + int b0 = src[((j+0)*width+i+0)*4+2]; + int r1 = src[((j+0)*width+i+1)*4+0]; + int g1 = src[((j+0)*width+i+1)*4+1]; + int b1 = src[((j+0)*width+i+1)*4+2]; + int r2 = src[((j+1)*width+i+0)*4+0]; + int g2 = src[((j+1)*width+i+0)*4+1]; + int b2 = src[((j+1)*width+i+0)*4+2]; + int r3 = src[((j+1)*width+i+1)*4+0]; + int g3 = src[((j+1)*width+i+1)*4+1]; + int b3 = src[((j+1)*width+i+1)*4+2]; + int y0 = CLAMP_BYTE( RGB_TO_YCOCG_Y( r0, g0, b0 ) ); + int co0 = CLAMP_BYTE( RGB_TO_YCOCG_CO( r0, g0, b0 ) + 128 ); + int cg0 = CLAMP_BYTE( RGB_TO_YCOCG_CG( r0, g0, b0 ) + 128 ); + int y1 = CLAMP_BYTE( RGB_TO_YCOCG_Y( r1, g1, b1 ) ); + int co1 = CLAMP_BYTE( RGB_TO_YCOCG_CO( r1, g1, b1 ) + 128 ); + int cg1 = CLAMP_BYTE( RGB_TO_YCOCG_CG( r1, g1, b1 ) + 128 ); + int y2 = CLAMP_BYTE( RGB_TO_YCOCG_Y( r2, g2, b2 ) ); + int co2 = CLAMP_BYTE( RGB_TO_YCOCG_CO( r2, g2, b2 ) + 128 ); + int cg2 = CLAMP_BYTE( RGB_TO_YCOCG_CG( r2, g2, b2 ) + 128 ); + int y3 = CLAMP_BYTE( RGB_TO_YCOCG_Y( r3, g3, b3 ) ); + int co3 = CLAMP_BYTE( RGB_TO_YCOCG_CO( r3, g3, b3 ) + 128 ); + int cg3 = CLAMP_BYTE( RGB_TO_YCOCG_CG( r3, g3, b3 ) + 128 ); + dst[numSamples+0] = y0; + dst[numSamples+1] = y1; + dst[numSamples+2] = y2; + dst[numSamples+3] = y3; + dst[numSamples+4] = ( co0 + co1 + co2 + co3 ) >> 2; + dst[numSamples+5] = ( cg0 + cg1 + cg2 + cg3 ) >> 2; + numSamples += 6; + } + numSamples += width; + } +} + +/* +======================== +idColorSpace::ConvertYCoCg420ToRGB +======================== +*/ +void idColorSpace::ConvertYCoCg420ToRGB( byte *dst, const byte *src, int width, int height ) { + int numSamples = width * height * 2 - width; + for ( int j = height - 2; j >= 0; j -= 2 ) { + for ( int i = width - 2; i >= 0; i -= 2 ) { + int y0 = src[numSamples-6]; + int y1 = src[numSamples-5]; + int y2 = src[numSamples-4]; + int y3 = src[numSamples-3]; + int co = src[numSamples-2] - 128; + int cg = src[numSamples-1] - 128; + numSamples -= 6; + int r = COCG_TO_R( co, cg ); + int g = COCG_TO_G( co, cg ); + int b = COCG_TO_B( co, cg ); + dst[((j+0)*width+i+0)*4+0] = CLAMP_BYTE( y0 + r ); + dst[((j+0)*width+i+0)*4+1] = CLAMP_BYTE( y0 + g ); + dst[((j+0)*width+i+0)*4+2] = CLAMP_BYTE( y0 + b ); + dst[((j+0)*width+i+1)*4+0] = CLAMP_BYTE( y1 + r ); + dst[((j+0)*width+i+1)*4+1] = CLAMP_BYTE( y1 + g ); + dst[((j+0)*width+i+1)*4+2] = CLAMP_BYTE( y1 + b ); + dst[((j+1)*width+i+0)*4+0] = CLAMP_BYTE( y2 + r ); + dst[((j+1)*width+i+0)*4+1] = CLAMP_BYTE( y2 + g ); + dst[((j+1)*width+i+0)*4+2] = CLAMP_BYTE( y2 + b ); + dst[((j+1)*width+i+1)*4+0] = CLAMP_BYTE( y3 + r ); + dst[((j+1)*width+i+1)*4+1] = CLAMP_BYTE( y3 + g ); + dst[((j+1)*width+i+1)*4+2] = CLAMP_BYTE( y3 + b ); + } + numSamples -= width; + } +} + +/* +================================================================================================ +To *Color-Convert RGB and YCbCr* ColorSpaces, note that YCbCr is defined per +CCIR 601-1, except that Cb and Cr are normalized to the range 0 -> 255 rather than -0.5 -> 0.5. +The conversion equations to be implemented are therefore: + + + Y = [ 0.29900 0.58700 0.11400] [R] + Cb = [-0.16874 -0.33126 0.50000] [G] + 128 + Cr = [ 0.50000 -0.41869 -0.08131] [B] + 128 + + R = [ 1.00000 0.00000 1.40200] [Y] + G = [ 1.00000 -0.34414 -0.71414] [Cb - 128] + B = [ 1.00000 1.77200 0.00000] [Cr - 128] + + +These numbers are derived from TIFF 6.0 section 21, dated 3-June-92. To avoid floating-point +arithmetic, we represent the fractional constants as integers scaled up by 2^16 (about 4 digits +precision); we have to divide the products by 2^16, with appropriate rounding, to get the +correct answer. +================================================================================================ +*/ + +const int ycbcr_shift = 16; +const int ycbcr_round = 1 << ( ycbcr_shift - 1 ); + +const int r029900 = 19595; // int( 0.29900 * (1<<16) + 0.5 ) +const int g058700 = 38470; // int( 0.58700 * (1<<16) + 0.5 ) +const int b011400 = 7471; // int( 0.11400 * (1<<16) + 0.5 ) + +const int r016874 = 11059; // int( 0.16874 * (1<<16) + 0.5 ) +const int g033126 = 21709; // int( 0.33126 * (1<<16) + 0.5 ) +const int b050000 = 32768; // int( 0.50000 * (1<<16) + 0.5 ) + +const int r050000 = 32768; // int( 0.50000 * (1<<16) + 0.5 ) +const int g041869 = 27439; // int( 0.41869 * (1<<16) + 0.5 ) +const int b008131 = 5329; // int( 0.08131 * (1<<16) + 0.5 ) + +const int r140200 = 91881; // int( 1.40200 * (1<<16) + 0.5 ) +const int b177200 = 116130; // int( 1.77200 * (1<<16) + 0.5 ) +const int g071414 = 46802; // int( 0.71414 * (1<<16) + 0.5 ) +const int g034414 = 22554; // int( 0.34414 * (1<<16) + 0.5 ) + +#define RGB_TO_YCBCR_Y( r, g, b ) ( ( ( r * r029900 + g * g058700 + b * b011400 ) + ycbcr_round ) >> ycbcr_shift ) +#define RGB_TO_YCBCR_CB( r, g, b ) ( ( ( - r * r016874 - g * g033126 + b * b050000 ) + ycbcr_round ) >> ycbcr_shift ) +#define RGB_TO_YCBCR_CR( r, g, b ) ( ( ( r * r050000 - g * g041869 - b * b008131 ) + ycbcr_round ) >> ycbcr_shift ) + +#define CBCR_TO_R( cb, cr ) ( ( ycbcr_round + cr * r140200 ) >> ycbcr_shift ) +#define CBCR_TO_G( cb, cr ) ( ( ycbcr_round - cb * g034414 - cr * g071414 ) >> ycbcr_shift ) +#define CBCR_TO_B( cb, cr ) ( ( ycbcr_round + cb * b177200 ) >> ycbcr_shift ) + +/* +======================== +idColorSpace::ConvertRGBToYCbCr +======================== +*/ +void idColorSpace::ConvertRGBToYCbCr( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int r = src[i*4+0]; + int g = src[i*4+1]; + int b = src[i*4+2]; + int a = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( RGB_TO_YCBCR_Y( r, g, b ) ); + dst[i*4+1] = CLAMP_BYTE( RGB_TO_YCBCR_CB( r, g, b ) + 128 ); + dst[i*4+2] = CLAMP_BYTE( RGB_TO_YCBCR_CR( r, g, b ) + 128 ); + dst[i*4+3] = a; + } +} + +/* +======================== +idColorSpace::ConvertYCbCrToRGB +======================== +*/ +void idColorSpace::ConvertYCbCrToRGB( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int y = src[i*4+0]; + int cb = src[i*4+1] - 128; + int cr = src[i*4+2] - 128; + dst[i*4+0] = CLAMP_BYTE( y + CBCR_TO_R( cb, cr ) ); + dst[i*4+1] = CLAMP_BYTE( y + CBCR_TO_G( cb, cr ) ); + dst[i*4+2] = CLAMP_BYTE( y + CBCR_TO_B( cb, cr ) ); + } +} + +/* +======================== +idColorSpace::ConvertRGBToCbCr_Y +======================== +*/ +void idColorSpace::ConvertRGBToCbCr_Y( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int r = src[i*4+0]; + int g = src[i*4+1]; + int b = src[i*4+2]; + int a = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( RGB_TO_YCBCR_CB( r, g, b ) + 128 ); + dst[i*4+1] = CLAMP_BYTE( RGB_TO_YCBCR_CR( r, g, b ) + 128 ); + dst[i*4+2] = a; + dst[i*4+3] = CLAMP_BYTE( RGB_TO_YCBCR_Y( r, g, b ) ); + } +} + +/* +======================== +idColorSpace::ConvertCbCr_YToRGB +======================== +*/ +void idColorSpace::ConvertCbCr_YToRGB( byte *dst, const byte *src, int width, int height ) { + for ( int i = 0; i < width * height; i++ ) { + int cb = src[i*4+0] - 128; + int cr = src[i*4+1] - 128; + int a = src[i*4+2]; + int y = src[i*4+3]; + dst[i*4+0] = CLAMP_BYTE( y + CBCR_TO_R( cb, cr ) ); + dst[i*4+1] = CLAMP_BYTE( y + CBCR_TO_G( cb, cr ) ); + dst[i*4+2] = CLAMP_BYTE( y + CBCR_TO_B( cb, cr ) ); + dst[i*4+3] = a; + } +} + +/* +======================== +idColorSpace::ConvertRGBToYCbCr420 +======================== +*/ +void idColorSpace::ConvertRGBToYCbCr420( byte *dst, const byte *src, int width, int height ) { + int numSamples = 0; + for ( int j = 0; j < height; j += 2 ) { + for ( int i = 0; i < width; i += 2 ) { + int r0 = src[((j+0)*width+i+0)*4+0]; + int g0 = src[((j+0)*width+i+0)*4+1]; + int b0 = src[((j+0)*width+i+0)*4+2]; + int r1 = src[((j+0)*width+i+1)*4+0]; + int g1 = src[((j+0)*width+i+1)*4+1]; + int b1 = src[((j+0)*width+i+1)*4+2]; + int r2 = src[((j+1)*width+i+0)*4+0]; + int g2 = src[((j+1)*width+i+0)*4+1]; + int b2 = src[((j+1)*width+i+0)*4+2]; + int r3 = src[((j+1)*width+i+1)*4+0]; + int g3 = src[((j+1)*width+i+1)*4+1]; + int b3 = src[((j+1)*width+i+1)*4+2]; + int y0 = CLAMP_BYTE( RGB_TO_YCBCR_Y( r0, g0, b0 ) ); + int cb0 = CLAMP_BYTE( RGB_TO_YCBCR_CB( r0, g0, b0 ) + 128 ); + int cr0 = CLAMP_BYTE( RGB_TO_YCBCR_CR( r0, g0, b0 ) + 128 ); + int y1 = CLAMP_BYTE( RGB_TO_YCBCR_Y( r1, g1, b1 ) ); + int cb1 = CLAMP_BYTE( RGB_TO_YCBCR_CB( r1, g1, b1 ) + 128 ); + int cr1 = CLAMP_BYTE( RGB_TO_YCBCR_CR( r1, g1, b1 ) + 128 ); + int y2 = CLAMP_BYTE( RGB_TO_YCBCR_Y( r2, g2, b2 ) ); + int cb2 = CLAMP_BYTE( RGB_TO_YCBCR_CB( r2, g2, b2 ) + 128 ); + int cr2 = CLAMP_BYTE( RGB_TO_YCBCR_CR( r2, g2, b2 ) + 128 ); + int y3 = CLAMP_BYTE( RGB_TO_YCBCR_Y( r3, g3, b3 ) ); + int cb3 = CLAMP_BYTE( RGB_TO_YCBCR_CB( r3, g3, b3 ) + 128 ); + int cr3 = CLAMP_BYTE( RGB_TO_YCBCR_CR( r3, g3, b3 ) + 128 ); + dst[numSamples+0] = y0; + dst[numSamples+1] = y1; + dst[numSamples+2] = y2; + dst[numSamples+3] = y3; + dst[numSamples+4] = ( cb0 + cb1 + cb2 + cb3 ) >> 2; + dst[numSamples+5] = ( cr0 + cr1 + cr2 + cr3 ) >> 2; + numSamples += 6; + } + numSamples += width; + } +} + +/* +======================== +idColorSpace::ConvertYCbCr420ToRGB +======================== +*/ +void idColorSpace::ConvertYCbCr420ToRGB( byte *dst, const byte *src, int width, int height ) { + int numSamples = width * height * 2 - width; + for ( int j = height - 2; j >= 0; j -= 2 ) { + for ( int i = width - 2; i >= 0; i -= 2 ) { + int y0 = src[numSamples-6]; + int y1 = src[numSamples-5]; + int y2 = src[numSamples-4]; + int y3 = src[numSamples-3]; + int co = src[numSamples-2] - 128; + int cg = src[numSamples-1] - 128; + numSamples -= 6; + int r = CBCR_TO_R( co, cg ); + int g = CBCR_TO_G( co, cg ); + int b = CBCR_TO_B( co, cg ); + dst[((j+0)*width+i+0)*4+0] = CLAMP_BYTE( y0 + r ); + dst[((j+0)*width+i+0)*4+1] = CLAMP_BYTE( y0 + g ); + dst[((j+0)*width+i+0)*4+2] = CLAMP_BYTE( y0 + b ); + dst[((j+0)*width+i+1)*4+0] = CLAMP_BYTE( y1 + r ); + dst[((j+0)*width+i+1)*4+1] = CLAMP_BYTE( y1 + g ); + dst[((j+0)*width+i+1)*4+2] = CLAMP_BYTE( y1 + b ); + dst[((j+1)*width+i+0)*4+0] = CLAMP_BYTE( y2 + r ); + dst[((j+1)*width+i+0)*4+1] = CLAMP_BYTE( y2 + g ); + dst[((j+1)*width+i+0)*4+2] = CLAMP_BYTE( y2 + b ); + dst[((j+1)*width+i+1)*4+0] = CLAMP_BYTE( y3 + r ); + dst[((j+1)*width+i+1)*4+1] = CLAMP_BYTE( y3 + g ); + dst[((j+1)*width+i+1)*4+2] = CLAMP_BYTE( y3 + b ); + } + numSamples -= width; + } +} + +/* +======================== +idColorSpace::ConvertNormalMapToStereographicHeightMap + +Converts a tangent space normal map to a height map. +The iterative algorithm is pretty crappy but it's reasonably fast and good enough for testing purposes. +The algorithm uses a stereographic projection of the normals to reduce the entropy and preserve +significantly more detail. + +A better approach would be to solve the massive but rather sparse matrix system: + +[ c(1,0) c(1,1) ... c(1,w*h) ] [ H(1,1) ] [ Nx(1,1) ] +[ c(2,0) c(2,1) ... c(2,w*h) ] [ H(1,2) ] = [ Ny(1,1) ] +[ ... ] [ ... ] [ ... ] +[ ... ] [ H(w,h) ] [ Nx(w,h) ] +[ c(w*h*2,0) c(w*h*2,1) ... c(w*h*2,w*h)] [ Ny(w,h) ] + +Where: w = width, h = height, H(i,j) = height, Nx(i,j) = (normal.x/(1+normal.z), Ny(i,j) = (normal.y/(1+normal.z) +The c(i,j) are setup such that: + +Nx(i,j) = H(i,j) - H(i,j+1) +Ny(i,j) = H(i,j) - H(i+1,j) +Nx(i,w) = H(i,w) +Ny(h,j) = H(h,j) + +======================== +*/ +void idColorSpace::ConvertNormalMapToStereographicHeightMap( byte *heightMap, const byte *normalMap, int width, int height, float &scale ) { + + idTempArray buffer( (width+1) * (height+1) * sizeof( float ) ); + float * temp = (float *)buffer.Ptr(); + memset( temp, 0, (width+1) * (height+1) * sizeof( float ) ); + + const int NUM_ITERATIONS = 32; + + float scale0 = 0.1f; + float scale1 = 0.9f; + + for ( int n = 0; n < NUM_ITERATIONS; n++ ) { + for ( int i = 0; i < height; i++ ) { + for ( int j = 1; j < width; j++ ) { + float x = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i+0) * width + (j+0) ) * 4 + 0] ); + float z = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i+0) * width + (j+0) ) * 4 + 2] ); + temp[i * width + j] = scale0 * temp[i * width + j] + scale1 * ( temp[(i+0) * width + (j-1)] - ( x / (1+z) ) ); + } + } + for ( int i = 1; i < height; i++ ) { + for ( int j = 0; j < width; j++ ) { + float y = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i+0) * width + (j+0) ) * 4 + 1] ); + float z = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i+0) * width + (j+0) ) * 4 + 2] ); + temp[i * width + j] = scale0 * temp[i * width + j] + scale1 * ( temp[(i-1) * width + (j+0)] - ( y / (1+z)) ); + } + } + for ( int i = 0; i < height; i++ ) { + for ( int j = width - 1; j > 0; j-- ) { + float x = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i+0) * width + (j-1) ) * 4 + 0] ); + float z = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i+0) * width + (j-1) ) * 4 + 2] ); + temp[i * width + (j-1)] = scale0 * temp[i * width + (j-1)] + scale1 * ( temp[(i+0) * width + (j+0)] + ( x / (1+z) ) ); + } + } + for ( int i = height - 1; i > 0; i-- ) { + for ( int j = 0; j < width; j++ ) { + float y = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i-1) * width + (j+0) ) * 4 + 1] ); + float z = NORMALMAP_BYTE_TO_FLOAT( normalMap[( (i-1) * width + (j+0) ) * 4 + 2] ); + temp[(i-1) * width + j] = scale0 * temp[(i-1) * width + j] + scale1 * ( temp[(i+0) * width + (j+0)] + ( y / (1+z) ) ); + } + } + + scale1 *= 0.99f; + scale0 = 1.0f - scale1; + } + + float minHeight = idMath::INFINITY; + float maxHeight = -idMath::INFINITY; + for ( int j = 0; j < height; j++ ) { + for ( int i = 0; i < width; i++ ) { + if ( temp[j*width+i] < minHeight ) { + minHeight = temp[j*width+i]; + } + if ( temp[j*width+i] > maxHeight ) { + maxHeight = temp[j*width+i]; + } + } + } + + scale = ( maxHeight - minHeight ); + + float s = 255.0f / scale; + for ( int j = 0; j < height; j++ ) { + for ( int i = 0; i < width; i++ ) { + heightMap[j*width+i] = idMath::Ftob( ( temp[j*width+i] - minHeight ) * s ); + } + } +} + +/* +======================== +idColorSpace::ConvertStereographicHeightMapToNormalMap + +This converts a heightmap of a stereographically projected normal map back into a regular normal map. +======================== +*/ +void idColorSpace::ConvertStereographicHeightMapToNormalMap( byte *normalMap, const byte *heightMap, int width, int height, float scale ) { + for ( int i = 0; i < height; i++ ) { + int previ = Max( i, 0 ); + int nexti = Min( i + 1, height - 1 ); + + for ( int j = 0; j < width; j++ ) { + int prevj = Max( j, 0 ); + int nextj = Min( j + 1, width - 1 ); + + idVec3 normal; + float pX = scale * ( heightMap[i * width + prevj] - heightMap[i * width + nextj] ) / 255.0f; + float pY = scale * ( heightMap[previ * width + j] - heightMap[nexti * width + j] ) / 255.0f; + float denom = 2.0f / ( 1.0f + pX * pX + pY * pY ); + normal.x = pX * denom; + normal.y = pY * denom; + normal.z = denom - 1.0f; + + normalMap[ ( i * width + j ) * 4 + 0 ] = NORMALMAP_FLOAT_TO_BYTE( normal[0] ); + normalMap[ ( i * width + j ) * 4 + 1 ] = NORMALMAP_FLOAT_TO_BYTE( normal[1] ); + normalMap[ ( i * width + j ) * 4 + 2 ] = NORMALMAP_FLOAT_TO_BYTE( normal[2] ); + normalMap[ ( i * width + j ) * 4 + 3 ] = 255; + } + } +} + +/* +======================== +idColorSpace::ConvertRGBToMonochrome +======================== +*/ +void idColorSpace::ConvertRGBToMonochrome( byte *mono, const byte *rgb, int width, int height ) { + for ( int i = 0; i < height; i++ ) { + for ( int j = 0; j < width; j++ ) { + mono[i * width + j] = ( rgb[( i * width + j ) * 4 + 0] + + rgb[( i * width + j ) * 4 + 1] + + rgb[( i * width + j ) * 4 + 2] ) / 3; + } + } +} + +/* +======================== +idColorSpace::ConvertMonochromeToRGB +======================== +*/ +void idColorSpace::ConvertMonochromeToRGB( byte *rgb, const byte *mono, int width, int height ) { + for ( int i = 0; i < height; i++ ) { + for ( int j = 0; j < width; j++ ) { + rgb[( i * width + j ) * 4 + 0] = mono[i * width + j]; + rgb[( i * width + j ) * 4 + 1] = mono[i * width + j]; + rgb[( i * width + j ) * 4 + 2] = mono[i * width + j]; + } + } +} diff --git a/neo/renderer/Color/ColorSpace.h b/neo/renderer/Color/ColorSpace.h new file mode 100644 index 00000000..4706aa2b --- /dev/null +++ b/neo/renderer/Color/ColorSpace.h @@ -0,0 +1,64 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __COLORSPACE_H__ +#define __COLORSPACE_H__ + +/* +================================================================================================ +Contains the ColorSpace conversion declarations. +================================================================================================ +*/ + +namespace idColorSpace { + void ConvertRGBToYCoCg( byte *dst, const byte *src, int width, int height ); + void ConvertYCoCgToRGB( byte *dst, const byte *src, int width, int height ); + + void ConvertRGBToCoCg_Y( byte *dst, const byte *src, int width, int height ); + void ConvertCoCg_YToRGB( byte *dst, const byte *src, int width, int height ); + void ConvertCoCgSYToRGB( byte *dst, const byte *src, int width, int height ); + + void ConvertRGBToYCoCg420( byte *dst, const byte *src, int width, int height ); + void ConvertYCoCg420ToRGB( byte *dst, const byte *src, int width, int height ); + + void ConvertRGBToYCbCr( byte *dst, const byte *src, int width, int height ); + void ConvertYCbCrToRGB( byte *dst, const byte *src, int width, int height ); + + void ConvertRGBToCbCr_Y( byte *dst, const byte *src, int width, int height ); + void ConvertCbCr_YToRGB( byte *dst, const byte *src, int width, int height ); + + void ConvertRGBToYCbCr420( byte *dst, const byte *src, int width, int height ); + void ConvertYCbCr420ToRGB( byte *dst, const byte *src, int width, int height ); + + void ConvertNormalMapToStereographicHeightMap( byte *heightMap, const byte *normalMap, int width, int height, float &scale ); + void ConvertStereographicHeightMapToNormalMap( byte *normalMap, const byte *heightMap, int width, int height, float scale ); + + void ConvertRGBToMonochrome( byte *mono, const byte *rgb, int width, int height ); + void ConvertMonochromeToRGB( byte *rgb, const byte *mono, int width, int height ); +}; + +#endif // !__COLORSPACE_H__ diff --git a/neo/renderer/DXT/DXTCodec.h b/neo/renderer/DXT/DXTCodec.h new file mode 100644 index 00000000..cd84d33a --- /dev/null +++ b/neo/renderer/DXT/DXTCodec.h @@ -0,0 +1,604 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __DXTCODEC_H__ +#define __DXTCODEC_H__ + +/* +================================================================================================ +Contains the DxtEncoder and DxtDecoder declarations. +================================================================================================ +*/ + +/* +================================================ +idDxtEncoder encodes Images in a number of DXT formats. Raw input Images are assumed to be in +4-byte RGBA format. Raw input NormalMaps are assumed to be in 4-byte tangent-space NxNyNz format. + +The supported formats are: + * DXT1 = colors in 4x4 block approximated by equidistant points on a line through 3D space + * DXT2 = DXT3 + colors are pre-multiplied by alpha + * DXT3 = DXT1 + explicit 4-bit alpha + * DXT4 = DXT5 + colors are pre-multiplied by alpha + * DXT5 = DXT1 + alpha values in 4x4 block approximated by equidistant points on line through alpha space + * CTX1 = colors in a 4x4 block approximated by equidistant points on a line through 2D space + * DXN1 = one DXT5 alpha block (aka DXT5A, or ATI1N) + * DXN2 = two DXT5 alpha blocks (aka 3Dc, or ATI2N) +================================================ +*/ +class idDxtEncoder { +public: + idDxtEncoder() { srcPadding = dstPadding = 0; } + ~idDxtEncoder() {} + + void SetSrcPadding( int pad ) { srcPadding = pad; } + void SetDstPadding( int pad ) { dstPadding = pad; } + + // high quality DXT1 compression (no alpha), uses exhaustive search to find a line through color space and is very slow + void CompressImageDXT1HQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast DXT1 compression (no alpha), for real-time use at the cost of a little quality + void CompressImageDXT1Fast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXT1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXT1Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ); + + // high quality DXT1 compression (with alpha), uses exhaustive search to find a line through color space and is very slow + void CompressImageDXT1AlphaHQ( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // fast DXT1 compression (with alpha), for real-time use at the cost of a little quality + void CompressImageDXT1AlphaFast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXT1AlphaFast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXT1AlphaFast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ); + + // high quality DXT5 compression, uses exhaustive search to find a line through color space and is generally + // too slow to actually use for anything + void CompressImageDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast DXT5 compression for real-time use at the cost of a little quality + void CompressImageDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXT5Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ); + + // high quality CTX1 compression, uses exhaustive search to find a line through 2D space and is very slow + void CompressImageCTX1HQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast CTX1 compression for real-time use + void CompressImageCTX1Fast( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + void CompressImageCTX1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + void CompressImageCTX1Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // high quality DXN1 (aka DXT5A or ATI1N) compression, uses exhaustive search to find a line through color space and is very slow + void CompressImageDXN1HQ( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // fast single channel compression into, DXN1 (aka DXT5A or ATI1N) format, for real-time use + void CompressImageDXN1Fast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXN1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressImageDXN1Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // high quality YCoCg DXT5 compression, uses exhaustive search to find a line through color space and is very slow + void CompressYCoCgDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast YCoCg DXT5 compression for real-time use (the input is expected to be in CoCg_Y format) + void CompressYCoCgDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressYCoCgDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressYCoCgDXT5Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast YCoCg-Alpha DXT5 compression for real-time use (the input is expected to be in CoCgAY format) + void CompressYCoCgAlphaDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ); + + // high quality YCoCg CTX1 + DXT5A compression, uses exhaustive search to find a line through 2D space and is very slow + void CompressYCoCgCTX1DXT5AHQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast YCoCg CTX1 + DXT5A compression for real-time use (the input is expected to be in CoCg_Y format) + void CompressYCoCgCTX1DXT5AFast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressYCoCgCTX1DXT5AFast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressYCoCgCTX1DXT5AFast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // high quality tangent space NxNyNz normal map compression into DXT1 format (Nz is not used) + void CompressNormalMapDXT1HQ( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressNormalMapDXT1RenormalizeHQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast tangent space NxNyNz normal map compression into DXT1 format (Nz is not used), for real-time use + void CompressNormalMapDXT1Fast( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + void CompressNormalMapDXT1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + void CompressNormalMapDXT1Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // high quality tangent space _Ny_Nx normal map compression into DXT5 format + void CompressNormalMapDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressNormalMapDXT5RenormalizeHQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast tangent space _Ny_Nx normal map compression into DXT5 format, for real-time use + void CompressNormalMapDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressNormalMapDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressNormalMapDXT5Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ); + + // high quality tangent space NxNy_ normal map compression into DXN2 (3Dc, ATI2N) format + void CompressNormalMapDXN2HQ( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast tangent space NxNy_ normal map compression into DXN2 (3Dc, ATI2N) format, for real-time use + void CompressNormalMapDXN2Fast( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressNormalMapDXN2Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ); + void CompressNormalMapDXN2Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // fast single channel conversion from DXN1 (aka DXT5A or ATI1N) to DXT1, reasonably fast (also works in-place) + void ConvertImageDXN1_DXT1( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast single channel conversion from DXT1 to DXN1 (aka DXT5A or ATI1N), reasonably fast (also works in-place) + void ConvertImageDXT1_DXN1( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // fast tangent space NxNyNz normal map conversion from DXN (3Dc, ATI2N) to DXT5, reasonably fast (also works in-place) + void ConvertNormalMapDXN2_DXT5( const byte *inBuf, byte *outBuf, int width, int height ); + + // fast tangent space NxNyNz normal map conversion DXT5 to DXN (3Dc, ATI2N), reasonably fast (also works in-place) + void ConvertNormalMapDXT5_DXN2( const byte *inBuf, byte *outBuf, int width, int height ); + +private: + int width; + int height; + byte * outData; + int srcPadding; + int dstPadding; + + void EmitByte( byte b ); + void EmitUShort( unsigned short s ); + void EmitUInt( unsigned int i ); + unsigned int AlphaDistance( const byte a1, const byte a2 ) const; + unsigned int ColorDistance( const byte *c1, const byte *c2 ) const; + unsigned int ColorDistanceWeighted( const byte *c1, const byte *c2 ) const; + unsigned int CTX1Distance( const byte *c1, const byte *c2 ) const; + unsigned short ColorTo565( const byte *color ) const; + unsigned short ColorTo565( byte r, byte g, byte b ) const; + void ColorFrom565( unsigned short c565, byte *color ) const; + byte GreenFrom565( unsigned short c565 ) const; + + void NV4XHardwareBugFix( byte *minColor, byte *maxColor ) const; + + bool HasConstantValuePer4x4Block( const byte *inBuf, int width, int height, int channel ) const; + void WriteTinyColorDXT1( const byte *inBuf, int width, int height ); + void WriteTinyColorDXT5( const byte *inBuf, int width, int height ); + void WriteTinyColorCTX1DXT5A( const byte *inBuf, int width, int height ); + void WriteTinyNormalMapDXT5( const byte *NxNy, int width, int height ); + void WriteTinyNormalMapDXN( const byte *NxNy, int width, int height ); + void WriteTinyDXT5A( const byte *NxNy, int width, int height ); + + void GetMinMaxColorsMaxDist( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + void GetMinMaxColorsLuminance( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + int GetSquareAlphaError( const byte *colorBlock, const int alphaOffset, const byte minAlpha, const byte maxAlpha, int lastError ) const; + int GetMinMaxAlphaHQ( const byte *colorBlock, const int alphaOffset, byte *minColor, byte *maxColor ) const; + int GetSquareColorsError( const byte *colorBlock, const unsigned short color0, const unsigned short color1, int lastError ) const; + int GetMinMaxColorsHQ( const byte *colorBlock, byte *minColor, byte *maxColor, bool noBlack ) const; + int GetSquareCTX1Error( const byte *colorBlock, const byte *color0, const byte *color1, int lastError ) const; + int GetMinMaxCTX1HQ( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + int GetSquareNormalYError( const byte *colorBlock, const unsigned short color0, const unsigned short color1, int lastError, int scale ) const; + int GetMinMaxNormalYHQ( const byte *colorBlock, byte *minColor, byte *maxColor, bool noBlack, int scale ) const; + int GetSquareNormalsDXT1Error( const int *colorBlock, const unsigned short color0, const unsigned short color1, int lastError, unsigned int &colorIndices ) const; + int GetMinMaxNormalsDXT1HQ( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, bool noBlack ) const; + int GetSquareNormalsDXT5Error( const int *normalBlock, const byte *minNormal, const byte *maxNormal, int lastError, unsigned int &colorIndices, byte *alphaIndices ) const; + int GetMinMaxNormalsDXT5HQ( const byte *normalBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, byte *alphaIndices ) const; + int GetMinMaxNormalsDXT5HQFast( const byte *normalBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, byte *alphaIndices ) const; + void ScaleYCoCg( byte *colorBlock ) const; + void BiasScaleNormalY( byte *colorBlock ) const; + void RotateNormalsDXT1( byte *block ) const; + void RotateNormalsDXT5( byte *block ) const; + int FindColorIndices( const byte *colorBlock, const unsigned short color0, const unsigned short color1, unsigned int &result ) const; + int FindAlphaIndices( const byte *colorBlock, const int alphaOffset, const byte alpha0, const byte alpha1, byte *indexes ) const; + int FindCTX1Indices( const byte *colorBlock, const byte *color0, const byte *color1, unsigned int &result ) const; + + void ExtractBlock( const byte *inPtr, int width, byte *colorBlock ) const; + void GetMinMaxBBox( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + void InsetColorsBBox( byte *minColor, byte *maxColor ) const; + void SelectColorsDiagonal( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + void ScaleYCoCg( byte *colorBlock, byte *minColor, byte *maxColor ) const; + void InsetYCoCgAlpaBBox( byte *minColor, byte *maxColor ) const; + void InsetYCoCgBBox( byte *minColor, byte *maxColor ) const; + void SelectYCoCgDiagonal( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + void InsetNormalsBBoxDXT5( byte *minNormal, byte *maxNormal ) const; + void InsetNormalsBBox3Dc( byte *minNormal, byte *maxNormal ) const; + void EmitColorIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor ); + void EmitColorAlphaIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor ); + void EmitCTX1Indices( const byte *colorBlock, const byte *minColor, const byte *maxColor ); + void EmitAlphaIndices( const byte *colorBlock, const int channel, const byte minAlpha, const byte maxAlpha ); + void EmitGreenIndices( const byte *block, const int channel, const byte minGreen, const byte maxGreen ); + + // Keeping the ASM versions to keep the performance of 32-bit debug builds reasonable. + // The implementation using intrinsics is very slow in debug builds because registers are continuously spilled to memory. + void ExtractBlock_SSE2( const byte *inPtr, int width, byte *colorBlock ) const; + void GetMinMaxBBox_SSE2( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + void InsetColorsBBox_SSE2( byte *minColor, byte *maxColor ) const; + void InsetNormalsBBoxDXT5_SSE2( byte *minNormal, byte *maxNormal ) const; + void EmitColorIndices_SSE2( const byte *colorBlock, const byte *minColor, const byte *maxColor ); + void EmitColorAlphaIndices_SSE2( const byte *colorBlock, const byte *minColor, const byte *maxColor ); + void EmitCoCgIndices_SSE2( const byte *colorBlock, const byte *minColor, const byte *maxColor ); + void EmitAlphaIndices_SSE2( const byte *colorBlock, const int minAlpha, const int maxAlpha ); + void EmitAlphaIndices_SSE2( const byte *colorBlock, const int channelBitOffset, const int minAlpha, const int maxAlpha ); + void EmitGreenIndices_SSE2( const byte *block, const int channelBitOffset, const int minGreen, const int maxGreen ); + void ScaleYCoCg_SSE2( byte *colorBlock, byte *minColor, byte *maxColor ) const; + void InsetYCoCgBBox_SSE2( byte *minColor, byte *maxColor ) const; + void SelectYCoCgDiagonal_SSE2( const byte *colorBlock, byte *minColor, byte *maxColor ) const; + + + + void EmitNormalYIndices( const byte *normalBlock, const int offset, const byte minNormalY, const byte maxNormalY ); + void EmitNormalYIndices_SSE2( const byte *normalBlock, const int offset, const byte minNormalY, const byte maxNormalY ); + + void DecodeDXNAlphaValues( const byte *inBuf, byte *values ); + void EncodeDXNAlphaValues( byte *outBuf, const byte min, const byte max, const byte *values ); + + void DecodeNormalYValues( const byte *inBuf, byte &min, byte &max, byte *values ); + void EncodeNormalRGBIndices( byte *outBuf, const byte min, const byte max, const byte *values ); +}; + +/* +======================== +idDxtEncoder::CompressImageDXT1Fast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressImageDXT1Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressImageDXT1Fast_SSE2( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressImageDXT1AlphaFast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressImageDXT1AlphaFast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressImageDXT1AlphaFast_SSE2( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressImageDXT5Fast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressImageDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressImageDXT5Fast_SSE2( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressImageDXN1Fast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressImageDXN1Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressImageDXN1Fast_Generic( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressYCoCgDXT5Fast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressYCoCgDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressYCoCgDXT5Fast_SSE2( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressYCoCgCTX1DXT5AFast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressYCoCgCTX1DXT5AFast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressYCoCgCTX1DXT5AFast_Generic( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT5Fast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressNormalMapDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressNormalMapDXT5Fast_SSE2( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXN2Fast +======================== +*/ +ID_INLINE void idDxtEncoder::CompressNormalMapDXN2Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + CompressNormalMapDXN2Fast_Generic( inBuf, outBuf, width, height ); +} + +/* +======================== +idDxtEncoder::EmitByte +======================== +*/ +ID_INLINE void idDxtEncoder::EmitByte( byte b ) { + *outData = b; + outData += 1; +} + +/* +======================== +idDxtEncoder::EmitUShort +======================== +*/ +ID_INLINE void idDxtEncoder::EmitUShort( unsigned short s ) { + *((unsigned short *)outData) = s; + outData += 2; +} + +/* +======================== +idDxtEncoder::EmitUInt +======================== +*/ +ID_INLINE void idDxtEncoder::EmitUInt( unsigned int i ) { + *((unsigned int *)outData) = i; + outData += 4; +} + +/* +======================== +idDxtEncoder::AlphaDistance +======================== +*/ +ID_INLINE unsigned int idDxtEncoder::AlphaDistance( const byte a1, const byte a2 ) const { + return ( a1 - a2 ) * ( a1 - a2 ); +} + +/* +======================== +idDxtEncoder::ColorDistance +======================== +*/ +ID_INLINE unsigned int idDxtEncoder::ColorDistance( const byte *c1, const byte *c2 ) const { + return ( ( c1[ 0 ] - c2[ 0 ] ) * ( c1[ 0 ] - c2[ 0 ] ) ) + ( ( c1[ 1 ] - c2[ 1 ] ) * ( c1[ 1 ] - c2[ 1 ] ) ) + ( ( c1[ 2 ] - c2[ 2 ] ) * ( c1[ 2 ] - c2[ 2 ] ) ); +} + +/* +======================== +idDxtEncoder::ColorDistanceWeighted +======================== +*/ +ID_INLINE unsigned int idDxtEncoder::ColorDistanceWeighted( const byte *c1, const byte *c2 ) const { + int r, g, b; + int rmean; + + // http://www.compuphase.com/cmetric.htm + rmean = ( (int)c1[0] + (int)c2[0] ) / 2; + r = (int)c1[0] - (int)c2[0]; + g = (int)c1[1] - (int)c2[1]; + b = (int)c1[2] - (int)c2[2]; + return ( ( ( 512 + rmean ) * r * r ) >> 8 ) + 4 * g * g + ( ( ( 767 - rmean ) * b * b ) >> 8 ); +} + +/* +======================== +idDxtEncoder::CTX1Distance +======================== +*/ +ID_INLINE unsigned int idDxtEncoder::CTX1Distance( const byte *c1, const byte *c2 ) const { + return ( ( c1[ 0 ] - c2[ 0 ] ) * ( c1[ 0 ] - c2[ 0 ] ) ) + ( ( c1[ 1 ] - c2[ 1 ] ) * ( c1[ 1 ] - c2[ 1 ] ) ); +} + +/* +======================== +idDxtEncoder::ColorTo565 +======================== +*/ +ID_INLINE unsigned short idDxtEncoder::ColorTo565( const byte *color ) const { + return ( ( color[ 0 ] >> 3 ) << 11 ) | ( ( color[ 1 ] >> 2 ) << 5 ) | ( color[ 2 ] >> 3 ); +} + +/* +======================== +idDxtEncoder::ColorFrom565 +======================== +*/ +ID_INLINE void idDxtEncoder::ColorFrom565( unsigned short c565, byte *color ) const { + color[0] = byte( ( ( c565 >> 8 ) & ( ( ( 1 << ( 8 - 3 ) ) - 1 ) << 3 ) ) | ( ( c565 >> 13 ) & ((1<<3)-1) ) ); + color[1] = byte( ( ( c565 >> 3 ) & ( ( ( 1 << ( 8 - 2 ) ) - 1 ) << 2 ) ) | ( ( c565 >> 9 ) & ((1<<2)-1) ) ); + color[2] = byte( ( ( c565 << 3 ) & ( ( ( 1 << ( 8 - 3 ) ) - 1 ) << 3 ) ) | ( ( c565 >> 2 ) & ((1<<3)-1) ) ); +} + +/* +======================== +idDxtEncoder::ColorTo565 +======================== +*/ +ID_INLINE unsigned short idDxtEncoder::ColorTo565( byte r, byte g, byte b ) const { + return ( ( r >> 3 ) << 11 ) | ( ( g >> 2 ) << 5 ) | ( b >> 3 ); +} + +/* +======================== +idDxtEncoder::GreenFrom565 +======================== +*/ +ID_INLINE byte idDxtEncoder::GreenFrom565( unsigned short c565 ) const { + byte c = byte( ( c565 & ( ( ( 1 << 6 ) - 1 ) << 5 ) ) >> 3 ); + return ( c | ( c >> 6 ) ); +} + +/* +================================================ +idDxtDecoder decodes DXT-compressed Images. Raw output Images are in +4-byte RGBA format. Raw output NormalMaps are in 4-byte tangent-space NxNyNz format. +================================================ +*/ +class idDxtDecoder { +public: + + // DXT1 decompression (no alpha) + void DecompressImageDXT1( const byte *inBuf, byte *outBuf, int width, int height ); + + // DXT5 decompression + void DecompressImageDXT5( const byte *inBuf, byte *outBuf, int width, int height ); + + // DXT5 decompression with nVidia 7x hardware bug + void DecompressImageDXT5_nVidia7x( const byte *inBuf, byte *outBuf, int width, int height ); + + // CTX1 + void DecompressImageCTX1( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // DXN1 + void DecompressImageDXN1( const byte *inBuf, byte *outBuf, int width, int height ) { /* not implemented */ assert( 0 ); } + + // YCoCg DXT5 (the output is in CoCg_Y format) + void DecompressYCoCgDXT5( const byte *inBuf, byte *outBuf, int width, int height ); + + // YCoCg CTX1 + DXT5A (the output is in CoCg_Y format) + void DecompressYCoCgCTX1DXT5A( const byte *inBuf, byte *outBuf, int width, int height ); + + // tangent space normal map decompression from DXT1 format + void DecompressNormalMapDXT1( const byte *inBuf, byte *outBuf, int width, int height ); + void DecompressNormalMapDXT1Renormalize( const byte *inBuf, byte *outBuf, int width, int height ); + + // tangent space normal map decompression from DXT5 format + void DecompressNormalMapDXT5( const byte *inBuf, byte *outBuf, int width, int height ); + void DecompressNormalMapDXT5Renormalize( const byte *inBuf, byte *outBuf, int width, int height ); + + // tangent space normal map decompression from DXN2 format + void DecompressNormalMapDXN2( const byte *inBuf, byte *outBuf, int width, int height ); + + // decompose a DXT image into indices and two images with colors + void DecomposeImageDXT1( const byte *inBuf, byte *colorIndices, byte *pic1, byte *pic2, int width, int height ); + void DecomposeImageDXT5( const byte *inBuf, byte *colorIndices, byte *alphaIndices, byte *pic1, byte *pic2, int width, int height ); + +private: + int width; + int height; + const byte * inData; + + byte ReadByte(); + unsigned short ReadUShort(); + unsigned int ReadUInt(); + unsigned short ColorTo565( const byte *color ) const; + void ColorFrom565( unsigned short c565, byte *color ) const; + unsigned short NormalYTo565( byte y ) const; + byte NormalYFrom565( unsigned short c565 ) const; + byte NormalScaleFrom565( unsigned short c565 ) const; + byte NormalBiasFrom565( unsigned short c565 ) const; + + void EmitBlock( byte *outPtr, int x, int y, const byte *colorBlock ); + void DecodeAlphaValues( byte *colorBlock, const int offset ); + void DecodeColorValues( byte *colorBlock, bool noBlack, bool writeAlpha ); + void DecodeCTX1Values( byte *colorBlock ); + + void DecomposeColorBlock( byte colors[2][4], byte colorIndices[16], bool noBlack ); + void DecomposeAlphaBlock( byte colors[2][4], byte alphaIndices[16] ); + + void DecodeNormalYValues( byte *normalBlock, const int offsetY, byte &bias, byte &scale ); + void DeriveNormalZValues( byte *normalBlock ); +}; + +/* +======================== +idDxtDecoder::ReadByte +======================== +*/ +ID_INLINE byte idDxtDecoder::ReadByte() { + byte b = *inData; + inData += 1; + return b; +} + +/* +======================== +idDxtDecoder::ReadUShort +======================== +*/ +ID_INLINE unsigned short idDxtDecoder::ReadUShort() { + unsigned short s = *((unsigned short *)inData); + inData += 2; + return s; +} + +/* +======================== +idDxtDecoder::ReadUInt +======================== +*/ +ID_INLINE unsigned int idDxtDecoder::ReadUInt() { + unsigned int i = *((unsigned int *)inData); + inData += 4; + return i; +} + +/* +======================== +idDxtDecoder::ColorTo565 +======================== +*/ +ID_INLINE unsigned short idDxtDecoder::ColorTo565( const byte *color ) const { + return ( ( color[ 0 ] >> 3 ) << 11 ) | ( ( color[ 1 ] >> 2 ) << 5 ) | ( color[ 2 ] >> 3 ); +} + +/* +======================== +idDxtDecoder::ColorFrom565 +======================== +*/ +ID_INLINE void idDxtDecoder::ColorFrom565( unsigned short c565, byte *color ) const { + color[0] = byte( ( ( c565 >> 8 ) & ( ( ( 1 << ( 8 - 3 ) ) - 1 ) << 3 ) ) | ( ( c565 >> 13 ) & ((1<<3)-1) ) ); + color[1] = byte( ( ( c565 >> 3 ) & ( ( ( 1 << ( 8 - 2 ) ) - 1 ) << 2 ) ) | ( ( c565 >> 9 ) & ((1<<2)-1) ) ); + color[2] = byte( ( ( c565 << 3 ) & ( ( ( 1 << ( 8 - 3 ) ) - 1 ) << 3 ) ) | ( ( c565 >> 2 ) & ((1<<3)-1) ) ); +} + +/* +======================== +idDxtDecoder::NormalYTo565 +======================== +*/ +ID_INLINE unsigned short idDxtDecoder::NormalYTo565( byte y ) const { + return ( ( y >> 2 ) << 5 ); +} + +/* +======================== +idDxtDecoder::NormalYFrom565 +======================== +*/ +ID_INLINE byte idDxtDecoder::NormalYFrom565( unsigned short c565 ) const { + byte c = byte( ( c565 & ( ( ( 1 << 6 ) - 1 ) << 5 ) ) >> 3 ); + return ( c | ( c >> 6 ) ); +} + +/* +======================== +idDxtDecoder::NormalBiasFrom565 +======================== +*/ +ID_INLINE byte idDxtDecoder::NormalBiasFrom565( unsigned short c565 ) const { + byte c = byte( ( c565 & ( ( ( 1 << 5 ) - 1 ) << 11 ) ) >> 8 ); + return ( c | ( c >> 5 ) ); +} + +/* +======================== +idDxtDecoder::NormalScaleFrom565 +======================== +*/ +ID_INLINE byte idDxtDecoder::NormalScaleFrom565( unsigned short c565 ) const { + byte c = byte( ( c565 & ( ( ( 1 << 5 ) - 1 ) << 0 ) ) << 3 ); + return ( c | ( c >> 5 ) ); +} + +#endif // !__DXTCODEC_H__ diff --git a/neo/renderer/DXT/DXTCodec_local.h b/neo/renderer/DXT/DXTCodec_local.h new file mode 100644 index 00000000..22176cbe --- /dev/null +++ b/neo/renderer/DXT/DXTCodec_local.h @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __DXTCODEC_LOCAL_H__ +#define __DXTCODEC_LOCAL_H__ + +/* +================================================================================================ +Contains the DxtEncoder and DxtDecoder declarations. +================================================================================================ +*/ +#include "../../idlib/precompiled.h" + +#endif // !__DXTCODEC_LOCAL_H__ diff --git a/neo/renderer/DXT/DXTDecoder.cpp b/neo/renderer/DXT/DXTDecoder.cpp new file mode 100644 index 00000000..5fce3a3c --- /dev/null +++ b/neo/renderer/DXT/DXTDecoder.cpp @@ -0,0 +1,745 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +================================================================================================ +Contains the DxtDecoder implementation. +================================================================================================ +*/ + +#pragma hdrstop +#include "DXTCodec_local.h" +#include "DXTCodec.h" + +/* +======================== +idDxtDecoder::EmitBlock +======================== +*/ +void idDxtDecoder::EmitBlock( byte *outPtr, int x, int y, const byte *colorBlock ) { + outPtr += ( y * width + x ) * 4; + for ( int j = 0; j < 4; j++ ) { + memcpy( outPtr, &colorBlock[j*4*4], 4*4 ); + outPtr += width * 4; + } +} + +/* +======================== +idDxtDecoder::DecodeAlphaValues +======================== +*/ +void idDxtDecoder::DecodeAlphaValues( byte *colorBlock, const int offset ) { + int i; + unsigned int indexes; + byte alphas[8]; + + alphas[0] = ReadByte(); + alphas[1] = ReadByte(); + + if ( alphas[0] > alphas[1] ) { + alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7; + alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7; + alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7; + alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7; + alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7; + alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7; + } else { + alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5; + alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5; + alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5; + alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5; + alphas[6] = 0; + alphas[7] = 255; + } + + colorBlock += offset; + + indexes = (int)ReadByte() | ( (int)ReadByte() << 8 ) | ( (int)ReadByte() << 16 ); + for ( i = 0; i < 8; i++ ) { + colorBlock[i*4] = alphas[indexes & 7]; + indexes >>= 3; + } + + indexes = (int)ReadByte() | ( (int)ReadByte() << 8 ) | ( (int)ReadByte() << 16 ); + for ( i = 8; i < 16; i++ ) { + colorBlock[i*4] = alphas[indexes & 7]; + indexes >>= 3; + } +} + +/* +======================== +idDxtDecoder::DecodeColorValues +======================== +*/ +void idDxtDecoder::DecodeColorValues( byte *colorBlock, bool noBlack, bool writeAlpha ) { + byte colors[4][4]; + + unsigned short color0 = ReadUShort(); + unsigned short color1 = ReadUShort(); + + ColorFrom565( color0, colors[0] ); + ColorFrom565( color1, colors[1] ); + + colors[0][3] = 255; + colors[1][3] = 255; + + if ( noBlack || color0 > color1 ) { + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[2][3] = 255; + + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + colors[3][3] = 255; + } else { + colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2; + colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2; + colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2; + colors[2][3] = 255; + + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + colors[3][3] = 0; + } + + unsigned int indexes = ReadUInt(); + for ( int i = 0; i < 16; i++ ) { + colorBlock[i*4+0] = colors[indexes & 3][0]; + colorBlock[i*4+1] = colors[indexes & 3][1]; + colorBlock[i*4+2] = colors[indexes & 3][2]; + if ( writeAlpha ) { + colorBlock[i*4+3] = colors[indexes & 3][3]; + } + indexes >>= 2; + } +} + +/* +======================== +idDxtDecoder::DecodeCTX1Values +======================== +*/ +void idDxtDecoder::DecodeCTX1Values( byte *colorBlock ) { + byte colors[4][2]; + + colors[0][0] = ReadByte(); + colors[0][1] = ReadByte(); + colors[1][0] = ReadByte(); + colors[1][1] = ReadByte(); + + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + + unsigned int indexes = ReadUInt(); + for ( int i = 0; i < 16; i++ ) { + colorBlock[i*4+0] = colors[indexes & 3][0]; + colorBlock[i*4+1] = colors[indexes & 3][1]; + indexes >>= 2; + } +} + +/* +======================== +idDxtDecoder::DecompressImageDXT1 +======================== +*/ +void idDxtDecoder::DecompressImageDXT1( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeColorValues( block, false, true ); + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecompressImageDXT5 +======================== +*/ +void idDxtDecoder::DecompressImageDXT5( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeAlphaValues( block, 3 ); + DecodeColorValues( block, true, false ); + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecompressImageDXT5_nVidia7x +======================== +*/ +void idDxtDecoder::DecompressImageDXT5_nVidia7x( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeAlphaValues( block, 3 ); + DecodeColorValues( block, false, false ); + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecompressYCoCgDXT5 +======================== +*/ +void idDxtDecoder::DecompressYCoCgDXT5( const byte *inBuf, byte *outBuf, int width, int height ) { + DecompressImageDXT5_nVidia7x( inBuf, outBuf, width, height ); + // descale the CoCg values and set the scale factor effectively to 1 + for ( int i = 0; i < width * height; i++ ) { + int scale = ( outBuf[i*4+2] >> 3 ) + 1; + outBuf[i*4+0] = byte( ( outBuf[i*4+0] - 128 ) / scale + 128 ); + outBuf[i*4+1] = byte( ( outBuf[i*4+1] - 128 ) / scale + 128 ); + outBuf[i*4+2] = 0; // this translates to a scale factor of 1 for uncompressed + } +} + + +/* +======================== +idDxtDecoder::DecompressYCoCgCTX1DXT5A +======================== +*/ +void idDxtDecoder::DecompressYCoCgCTX1DXT5A( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeAlphaValues( block, 3 ); + DecodeCTX1Values( block ); + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecodeNormalYValues +======================== +*/ +void idDxtDecoder::DecodeNormalYValues( byte *normalBlock, const int offsetY, byte &c0, byte &c1 ) { + int i; + unsigned int indexes; + unsigned short normal0, normal1; + byte normalsY[4]; + + normal0 = ReadUShort(); + normal1 = ReadUShort(); + + assert( normal0 >= normal1 ); + + normalsY[0] = NormalYFrom565( normal0 ); + normalsY[1] = NormalYFrom565( normal1 ); + normalsY[2] = ( 2 * normalsY[0] + 1 * normalsY[1] ) / 3; + normalsY[3] = ( 1 * normalsY[0] + 2 * normalsY[1] ) / 3; + + c0 = NormalBiasFrom565( normal0 ); + c1 = NormalScaleFrom565( normal0 ); + + byte *normalYPtr = normalBlock + offsetY; + + indexes = ReadUInt(); + for ( i = 0; i < 16; i++ ) { + normalYPtr[i*4] = normalsY[indexes & 3]; + indexes >>= 2; + } +} + +/* +======================== +UShortSqrt +======================== +*/ +byte UShortSqrt( unsigned short s ) { +#if 1 + int t, b, r, x; + + r = 0; + for ( b = 0x10000000; b != 0; b >>= 2 ) { + t = r + b; + r >>= 1; + x = -( t <= s ); + s = s - (unsigned short)( t & x ); + r += b & x; + } + return byte( r ); +#else + int t, b, r; + + r = 0; + for ( b = 0x10000000; b != 0; b >>= 2 ) { + t = r + b; + r >>= 1; + if ( t <= s ) { + s -= t; + r += b; + } + } + return r; +#endif +} + +/* +======================== +idDxtDecoder::DeriveNormalZValues +======================== +*/ +void idDxtDecoder::DeriveNormalZValues( byte *normalBlock ) { + int i; + + for ( i = 0; i < 16; i++ ) { + int x = normalBlock[i*4+0] - 127; + int y = normalBlock[i*4+1] - 127; + normalBlock[i*4+2] = 128 + UShortSqrt( (unsigned short)( 16383 - x * x - y * y ) ); + } +} + +/* +======================== +idDxtDecoder::UnRotateNormals +======================== +*/ +void UnRotateNormals( const byte *block, float *normals, byte c0, byte c1 ) { + int rotation = c0; + float angle = -( rotation / 255.0f ) * idMath::PI; + float s = sin( angle ); + float c = cos( angle ); + + int scale = ( c1 >> 3 ) + 1; + for ( int i = 0; i < 16; i++ ) { + float x = block[i*4+0] / 255.0f * 2.0f - 1.0f; + float y = ( ( block[i*4+1] - 128 ) / scale + 128 ) / 255.0f * 2.0f - 1.0f; + float rx = c * x - s * y; + float ry = s * x + c * y; + normals[i*4+0] = rx; + normals[i*4+1] = ry; + } +} + +/* +======================== +idDxtDecoder::DecompressNormalMapDXT1 +======================== +*/ +void idDxtDecoder::DecompressNormalMapDXT1( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeColorValues( block, false, true ); +#if 1 + float normals[16*4]; + /* + for ( int k = 0; k < 16; k++ ) { + normals[k*4+0] = block[k*4+0] / 255.0f * 2.0f - 1.0f; + normals[k*4+1] = block[k*4+1] / 255.0f * 2.0f - 1.0f; + } + */ + UnRotateNormals( block, normals, block[0*4+2], 0 ); + for ( int k = 0; k < 16; k++ ) { + float x = normals[k*4+0]; + float y = normals[k*4+1]; + float z = 1.0f - x * x - y * y; + if ( z < 0.0f ) z = 0.0f; + normals[k*4+2] = sqrt( z ); + } + for ( int k = 0; k < 16; k++ ) { + block[k*4+0] = idMath::Ftob( ( normals[k*4+0] + 1.0f ) / 2.0f * 255.0f ); + block[k*4+1] = idMath::Ftob( ( normals[k*4+1] + 1.0f ) / 2.0f * 255.0f ); + block[k*4+2] = idMath::Ftob( ( normals[k*4+2] + 1.0f ) / 2.0f * 255.0f ); + } +#else + DeriveNormalZValues( block ); +#endif + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecompressNormalMapDXT1Renormalize +======================== +*/ +void idDxtDecoder::DecompressNormalMapDXT1Renormalize( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeColorValues( block, false, true ); + + for ( int k = 0; k < 16; k++ ) { + float normal[3]; + normal[0] = block[k*4+0] / 255.0f * 2.0f - 1.0f; + normal[1] = block[k*4+1] / 255.0f * 2.0f - 1.0f; + normal[2] = block[k*4+2] / 255.0f * 2.0f - 1.0f; + float rsq = idMath::InvSqrt( normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2] ); + normal[0] *= rsq; + normal[1] *= rsq; + normal[2] *= rsq; + block[k*4+0] = idMath::Ftob( ( normal[0] + 1.0f ) / 2.0f * 255.0f + 0.5f ); + block[k*4+1] = idMath::Ftob( ( normal[1] + 1.0f ) / 2.0f * 255.0f + 0.5f ); + block[k*4+2] = idMath::Ftob( ( normal[2] + 1.0f ) / 2.0f * 255.0f + 0.5f ); + } + + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecompressNormalMapDXT5Renormalize +======================== +*/ +void idDxtDecoder::DecompressNormalMapDXT5Renormalize( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeAlphaValues( block, 3 ); + DecodeColorValues( block, false, false ); + + for ( int k = 0; k < 16; k++ ) { + float normal[3]; +#if 0 // object-space + normal[0] = block[k*4+0] / 255.0f * 2.0f - 1.0f; + normal[1] = block[k*4+1] / 255.0f * 2.0f - 1.0f; + normal[2] = block[k*4+3] / 255.0f * 2.0f - 1.0f; +#else + normal[0] = block[k*4+3] / 255.0f * 2.0f - 1.0f; + normal[1] = block[k*4+1] / 255.0f * 2.0f - 1.0f; + normal[2] = block[k*4+2] / 255.0f * 2.0f - 1.0f; +#endif + float rsq = idMath::InvSqrt( normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2] ); + normal[0] *= rsq; + normal[1] *= rsq; + normal[2] *= rsq; + block[k*4+0] = idMath::Ftob( ( normal[0] + 1.0f ) / 2.0f * 255.0f + 0.5f ); + block[k*4+1] = idMath::Ftob( ( normal[1] + 1.0f ) / 2.0f * 255.0f + 0.5f ); + block[k*4+2] = idMath::Ftob( ( normal[2] + 1.0f ) / 2.0f * 255.0f + 0.5f ); + } + + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::BiasScaleNormalY +======================== +*/ +void BiasScaleNormalY( byte *normals, const int offsetY, const byte c0, const byte c1 ) { + int bias = c0 - 4; + int scale = ( c1 >> 3 ) + 1; + for ( int i = 0; i < 16; i++ ) { + normals[i*4+offsetY] = byte( ( normals[i*4+offsetY] - 128 ) / scale + bias ); + } +} + +/* +======================== +idDxtDecoder::BiasScaleNormals +======================== +*/ +void BiasScaleNormals( const byte *block, float *normals, const byte c0, const byte c1 ) { + int bias = c0 - 4; + int scale = ( c1 >> 3 ) + 1; + for ( int i = 0; i < 16; i++ ) { + normals[i*4+0] = block[i*4+0] / 255.0f * 2.0f - 1.0f; + normals[i*4+1] = ( ( block[i*4+1] - 128.0f ) / scale + bias ) / 255.0f * 2.0f - 1.0f; + } +} + +/* +======================== +idDxtDecoder::DecompressNormalMapDXT5 +======================== +*/ +void idDxtDecoder::DecompressNormalMapDXT5( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + byte c0, c1; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeAlphaValues( block, 0 ); + DecodeNormalYValues( block, 1, c0, c1 ); +#if 1 + float normals[16*4]; + //BiasScaleNormals( block, normals, c0, c1 ); + UnRotateNormals( block, normals, c0, c1 ); + for ( int k = 0; k < 16; k++ ) { + float x = normals[k*4+0]; + float y = normals[k*4+1]; + float z = 1.0f - x * x - y * y; + if ( z < 0.0f ) z = 0.0f; + normals[k*4+2] = sqrt( z ); + } + for ( int k = 0; k < 16; k++ ) { + block[k*4+0] = idMath::Ftob( ( normals[k*4+0] + 1.0f ) / 2.0f * 255.0f ); + block[k*4+1] = idMath::Ftob( ( normals[k*4+1] + 1.0f ) / 2.0f * 255.0f ); + block[k*4+2] = idMath::Ftob( ( normals[k*4+2] + 1.0f ) / 2.0f * 255.0f ); + } +#else + BiasScaleNormalY( block, 1, c0, c1 ); + DeriveNormalZValues( block ); +#endif + + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecompressNormalMapDXN2 +======================== +*/ +void idDxtDecoder::DecompressNormalMapDXN2( const byte *inBuf, byte *outBuf, int width, int height ) { + byte block[64]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecodeAlphaValues( block, 0 ); + DecodeAlphaValues( block, 1 ); +#if 1 + float normals[16*4]; + for ( int k = 0; k < 16; k++ ) { + normals[k*4+0] = block[k*4+0] / 255.0f * 2.0f - 1.0f; + normals[k*4+1] = block[k*4+1] / 255.0f * 2.0f - 1.0f; + } + for ( int k = 0; k < 16; k++ ) { + float x = normals[k*4+0]; + float y = normals[k*4+1]; + float z = 1.0f - x * x - y * y; + if ( z < 0.0f ) z = 0.0f; + normals[k*4+2] = sqrt( z ); + } + for ( int k = 0; k < 16; k++ ) { + block[k*4+0] = idMath::Ftob( ( normals[k*4+0] + 1.0f ) / 2.0f * 255.0f ); + block[k*4+1] = idMath::Ftob( ( normals[k*4+1] + 1.0f ) / 2.0f * 255.0f ); + block[k*4+2] = idMath::Ftob( ( normals[k*4+2] + 1.0f ) / 2.0f * 255.0f ); + } +#else + DeriveNormalZValues( block ); +#endif + EmitBlock( outBuf, i, j, block ); + } + } +} + +/* +======================== +idDxtDecoder::DecomposeColorBlock +======================== +*/ +void idDxtDecoder::DecomposeColorBlock( byte colors[2][4], byte colorIndices[16], bool noBlack ) { + int i; + unsigned int indices; + unsigned short color0, color1; + int colorRemap1[] = { 3, 0, 2, 1 }; + int colorRemap2[] = { 1, 3, 2, 0 }; + int *crm; + + color0 = ReadUShort(); + color1 = ReadUShort(); + + ColorFrom565( color0, colors[0] ); + ColorFrom565( color1, colors[1] ); + + if ( noBlack || color0 > color1 ) { + crm = colorRemap1; + } else { + crm = colorRemap2; + } + + indices = ReadUInt(); + for ( i = 0; i < 16; i++ ) { + colorIndices[i] = (byte)crm[ indices & 3 ]; + indices >>= 2; + } +} + +/* +======================== +idDxtDecoder::DecomposeAlphaBlock +======================== +*/ +void idDxtDecoder::DecomposeAlphaBlock( byte colors[2][4], byte alphaIndices[16] ) { + int i; + unsigned char alpha0, alpha1; + unsigned int indices; + int alphaRemap1[] = { 7, 0, 6, 5, 4, 3, 2, 1 }; + int alphaRemap2[] = { 1, 6, 2, 3, 4, 5, 0, 7 }; + int *arm; + + alpha0 = ReadByte(); + alpha1 = ReadByte(); + + colors[0][3] = alpha0; + colors[1][3] = alpha1; + + if ( alpha0 > alpha1 ) { + arm = alphaRemap1; + } else { + arm = alphaRemap2; + } + + indices = (int)ReadByte() | ( (int)ReadByte() << 8 ) | ( (int)ReadByte() << 16 ); + for ( i = 0; i < 8; i++ ) { + alphaIndices[i] = (byte)arm[ indices & 7 ]; + indices >>= 3; + } + + indices = (int)ReadByte() | ( (int)ReadByte() << 8 ) | ( (int)ReadByte() << 16 ); + for ( i = 8; i < 16; i++ ) { + alphaIndices[i] = (byte)arm[ indices & 7 ]; + indices >>= 3; + } +} + +/* +======================== +idDxtDecoder::DecomposeImageDXT1 +======================== +*/ +void idDxtDecoder::DecomposeImageDXT1( const byte *inBuf, byte *colorIndices, byte *pic1, byte *pic2, int width, int height ) { + byte colors[2][4]; + byte indices[16]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + // extract the colors from the DXT + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecomposeColorBlock( colors, indices, false ); + + memcpy( colorIndices + (j+0) * width + i, indices+ 0, 4 ); + memcpy( colorIndices + (j+1) * width + i, indices+ 4, 4 ); + memcpy( colorIndices + (j+2) * width + i, indices+ 8, 4 ); + memcpy( colorIndices + (j+3) * width + i, indices+12, 4 ); + + memcpy( pic1 + j * width / 4 + i, colors[0], 4 ); + + memcpy( pic2 + j * width / 4 + i, colors[1], 4 ); + } + } +} + +/* +======================== +idDxtDecoder::DecomposeImageDXT5 +======================== +*/ +void idDxtDecoder::DecomposeImageDXT5( const byte *inBuf, byte *colorIndices, byte *alphaIndices, byte *pic1, byte *pic2, int width, int height ) { + byte colors[2][4]; + byte colorInd[16]; + byte alphaInd[16]; + + this->width = width; + this->height = height; + this->inData = inBuf; + + // extract the colors from the DXT + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4 ) { + DecomposeAlphaBlock( colors, alphaInd ); + DecomposeColorBlock( colors, colorInd, true ); + + memcpy( colorIndices + (j+0) * width + i, colorInd+ 0, 4 ); + memcpy( colorIndices + (j+1) * width + i, colorInd+ 4, 4 ); + memcpy( colorIndices + (j+2) * width + i, colorInd+ 8, 4 ); + memcpy( colorIndices + (j+3) * width + i, colorInd+12, 4 ); + + memcpy( colorIndices + (j+0) * width + i, alphaInd+ 0, 4 ); + memcpy( colorIndices + (j+1) * width + i, alphaInd+ 4, 4 ); + memcpy( colorIndices + (j+2) * width + i, alphaInd+ 8, 4 ); + memcpy( colorIndices + (j+3) * width + i, alphaInd+12, 4 ); + + memcpy( pic1 + j * width / 4 + i, colors[0], 4 ); + + memcpy( pic2 + j * width / 4 + i, colors[1], 4 ); + } + } +} + diff --git a/neo/renderer/DXT/DXTEncoder.cpp b/neo/renderer/DXT/DXTEncoder.cpp new file mode 100644 index 00000000..3336008e --- /dev/null +++ b/neo/renderer/DXT/DXTEncoder.cpp @@ -0,0 +1,4691 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +================================================================================================ +Contains the DxtEncoder implementation. +================================================================================================ +*/ + +#pragma hdrstop +#include "DXTCodec_local.h" +#include "DXTCodec.h" + +#define INSET_COLOR_SHIFT 4 // inset the bounding box with ( range >> shift ) +#define INSET_ALPHA_SHIFT 5 // inset alpha channel + +#define C565_5_MASK 0xF8 // 0xFF minus last three bits +#define C565_6_MASK 0xFC // 0xFF minus last two bits + +#define NVIDIA_7X_HARDWARE_BUG_FIX // keep the DXT5 colors sorted as: max, min + +typedef uint16 word; +typedef uint32 dword; + +/* +======================== +idDxtEncoder::NV4XHardwareBugFix +======================== +*/ +void idDxtEncoder::NV4XHardwareBugFix( byte *minColor, byte *maxColor ) const { + int minq = ( ( minColor[0] << 16 ) | ( minColor[1] << 8 ) | minColor[2] ) & 0x00F8FCF8; + int maxq = ( ( maxColor[0] << 16 ) | ( maxColor[1] << 8 ) | maxColor[2] ) & 0x00F8FCF8; + int mask = -( minq > maxq ) & 0x00FFFFFF; + int min = *(int *)minColor; + int max = *(int *)maxColor; + min ^= max; + max ^= ( min & mask ); + min ^= max; + *(int *)minColor = min; + *(int *)maxColor = max; +} + +/* +======================== +idDxtEncoder::HasConstantValuePer4x4Block +======================== +*/ +bool idDxtEncoder::HasConstantValuePer4x4Block( const byte *inBuf, int width, int height, int channel ) const { + if ( width < 4 || height < 4 ) { + byte value = inBuf[channel]; + for ( int k = 0; k < height; k++ ) { + for ( int l = 0; l < width; l++ ) { + if ( inBuf[(k*width+l)*4+channel] != value ) { + return false; + } + } + } + return true; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + const byte *inPtr = inBuf + i * 4; + byte value = inPtr[channel]; + for ( int k = 0; k < 4; k++ ) { + for ( int l = 0; l < 4; l++ ) { + if ( inPtr[(k*width+l)*4+channel] != value ) { + return false; + } + } + } + } + inBuf += srcPadding; + } + return true; +} + +/* +======================== +idDxtEncoder::WriteTinyColorDXT1 +======================== +*/ +void idDxtEncoder::WriteTinyColorDXT1( const byte *inBuf, int width, int height ) { + int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ); + int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next + // example: 2x8 pixels + // numBlocks = 2 + // stride = 32 bytes (8 pixels) + + for ( int i = 0; i < numBlocks; i++ ) { + // FIXME: This just emits a fake block based on the color at position 0,0 + EmitUShort( ColorTo565( inBuf ) ); + EmitUShort( 0 ); // dummy, never used + EmitUInt( 0 ); // 4 color index bytes all use the first color + + inBuf += stride; + } +} + +/* +======================== +idDxtEncoder::WriteTinyColorDXT5 +======================== +*/ +void idDxtEncoder::WriteTinyColorDXT5( const byte *inBuf, int width, int height ) { + int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ); + int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next + // example: 2x8 pixels + // numBlocks = 2 + // stride = 32 bytes (8 pixels) + + for ( int i = 0; i < numBlocks; i++ ) { + // FIXME: This just emits a fake block based on the color at position 0,0 + EmitByte( inBuf[3] ); + EmitByte( 0 ); // dummy, never used + EmitByte( 0 ); // 6 alpha index bytes all use the first alpha + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + + EmitUShort( ColorTo565( inBuf ) ); + EmitUShort( 0 ); // dummy, never used + EmitUInt( 0 ); // 4 color index bytes all use the first color + + inBuf += stride; + } +} + +/* +======================== +idDxtEncoder::WriteTinyColorCTX1DXT5A +======================== +*/ +void idDxtEncoder::WriteTinyColorCTX1DXT5A( const byte *inBuf, int width, int height ) { + int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ); + int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next + // example: 2x8 pixels + // numBlocks = 2 + // stride = 32 bytes (8 pixels) + + for ( int i = 0; i < numBlocks; i++ ) { + // FIXME: This just emits a fake block based on the color at position 0,0 + EmitByte( inBuf[0] ); + EmitByte( inBuf[1] ); + EmitByte( inBuf[0] ); + EmitByte( inBuf[1] ); + EmitUInt( 0 ); // 4 color index bytes all use the first color + + EmitByte( inBuf[3] ); + EmitByte( 0 ); // dummy, never used + EmitByte( 0 ); // 6 alpha index bytes all use the first alpha + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + + inBuf += stride; + } +} + +/* +======================== +idDxtEncoder::WriteTinyNormalMapDXT5 +======================== +*/ +void idDxtEncoder::WriteTinyNormalMapDXT5( const byte *inBuf, int width, int height ) { + int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ); + int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next + // example: 2x8 pixels + // numBlocks = 2 + // stride = 32 bytes (8 pixels) + + for ( int i = 0; i < numBlocks; i++ ) { + // FIXME: This just emits a fake block based on the normal at position 0,0 + EmitByte( inBuf[3] ); + EmitByte( 0 ); // dummy, never used + EmitByte( 0 ); // 6 alpha index bytes all use the first alpha + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + + EmitUShort( ColorTo565( inBuf[0], inBuf[1], inBuf[2] ) ); + EmitUShort( 0 ); // dummy, never used + EmitUInt( 0 ); // 4 color index bytes all use the first color + + inBuf += stride; + } +} + +/* +======================== +idDxtEncoder::WriteTinyNormalMapDXN +======================== +*/ +void idDxtEncoder::WriteTinyNormalMapDXN( const byte *inBuf, int width, int height ) { + int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ); + int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next + // example: 2x8 pixels + // numBlocks = 2 + // stride = 32 bytes (8 pixels) + + for ( int i = 0; i < numBlocks; i++ ) { + // FIXME: This just emits a fake block based on the normal at position 0,0 + EmitByte( inBuf[0] ); + EmitByte( 0 ); // dummy, never used + EmitByte( 0 ); // 6 alpha index bytes all use the first alpha + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + + EmitByte( inBuf[1] ); + EmitByte( 0 ); // dummy, never used + EmitByte( 0 ); // 6 alpha index bytes all use the first alpha + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + + inBuf += stride; + } +} + +/* +======================== +idDxtEncoder::WriteTinyDXT5A +======================== +*/ +void idDxtEncoder::WriteTinyDXT5A( const byte *inBuf, int width, int height ) { + int numBlocks = ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ); + int stride = ( ( width * height ) / numBlocks ) * 4; // number of bytes from one block to the next + // example: 2x8 pixels + // numBlocks = 2 + // stride = 32 bytes (8 pixels) + + for ( int i = 0; i < numBlocks; i++ ) { + // FIXME: This just emits a fake block based on the normal at position 0,0 + EmitByte( inBuf[0] ); + EmitByte( 0 ); // dummy, never used + EmitByte( 0 ); // 6 alpha index bytes all use the first alpha + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + EmitByte( 0 ); + + inBuf += stride; + } +} + +/* +======================== +idDxtEncoder::ExtractBlock + +params: inPtr - input image, 4 bytes per pixel +paramO: colorBlock - 4*4 output tile, 4 bytes per pixel +======================== +*/ +ID_INLINE void idDxtEncoder::ExtractBlock( const byte *inPtr, int width, byte *colorBlock ) const { + for ( int j = 0; j < 4; j++ ) { + memcpy( &colorBlock[j*4*4], inPtr, 4*4 ); + inPtr += width * 4; + } +} + +/* +======================== +SwapColors +======================== +*/ +void SwapColors( byte *c1, byte *c2 ) { + byte tm[3]; + memcpy( tm, c1, 3 ); + memcpy( c1, c2, 3 ); + memcpy( c2, tm, 3 ); +} + +/* +======================== +idDxtEncoder::GetMinMaxColorsMaxDist + +Finds the two RGB colors in a 4x4 block furthest apart. Also finds the two alpha values +furthest apart. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte min color +paramO: maxColor - 4 byte max color +======================== +*/ +void idDxtEncoder::GetMinMaxColorsMaxDist( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + int maxDistC = -1; + int maxDistA = -1; + + for ( int i = 0; i < 64 - 4; i += 4 ) { + for ( int j = i + 4; j < 64; j += 4 ) { + int dc = ColorDistance( &colorBlock[i], &colorBlock[j] ); + if ( dc > maxDistC ) { + maxDistC = dc; + memcpy( minColor, colorBlock+i, 3 ); + memcpy( maxColor, colorBlock+j, 3 ); + } + int da = AlphaDistance( colorBlock[i+3], colorBlock[j+3] ); + if ( da > maxDistA ) { + maxDistA = da; + minColor[3] = colorBlock[i+3]; + maxColor[3] = colorBlock[j+3]; + } + } + } + if ( maxColor[0] < minColor[0] ) { + SwapColors( minColor, maxColor ); + } +} + +/* +======================== +idDxtEncoder::GetMinMaxColorsLuminance + +Finds the two RGB colors in a 4x4 block furthest apart based on luminance. Also finds the two +alpha values furthest apart. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte min color +paramO: maxColor - 4 byte max color +======================== +*/ +void idDxtEncoder::GetMinMaxColorsLuminance( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + int maxLumC = 0, minLumC = 256 * 4; + int maxAlpha = 0, minAlpha = 256 * 4; + + for ( int i = 0; i < 16; i++ ) { + int luminance = colorBlock[i*4+0] + colorBlock[i*4+1] * 2 + colorBlock[i*4+2]; + if ( luminance > maxLumC ) { + maxLumC = luminance; + memcpy( maxColor, colorBlock+i*4, 3 ); + } + if ( luminance < minLumC ) { + minLumC = luminance; + memcpy( minColor, colorBlock+i*4, 3 ); + } + int alpha = colorBlock[i*4+3]; + if ( alpha > maxAlpha ) { + maxAlpha = alpha; + maxColor[3] = (byte)alpha; + } + if ( alpha < minAlpha ) { + minAlpha = alpha; + minColor[3] = (byte)alpha; + } + } + if ( maxColor[0] < minColor[0] ) { + SwapColors( minColor, maxColor ); + } +} + +/* +======================== +idDxtEncoder::GetSquareAlphaError + +params: colorBlock - 16 pixel block for which to find color indexes +paramO: minAlpha - Min alpha found +paramO: maxAlpha - Max alpha found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::GetSquareAlphaError( const byte *colorBlock, const int alphaOffset, const byte minAlpha, const byte maxAlpha, int lastError ) const { + int i, j; + byte alphas[8]; + + alphas[0] = maxAlpha; + alphas[1] = minAlpha; + + if ( maxAlpha > minAlpha ) { + alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7; + alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7; + alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7; + alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7; + alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7; + alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7; + } else { + alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5; + alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5; + alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5; + alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5; + alphas[6] = 0; + alphas[7] = 255; + } + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + byte a = colorBlock[i*4+alphaOffset]; + for ( j = 0; j < 8; j++ ) { + unsigned int dist = AlphaDistance( a, alphas[j] ); + if ( dist < minDist ) { + minDist = dist; + } + } + error += minDist; + + if ( error >= lastError ) { + return error; + } + } + + return error; +} + +/* +======================== +idDxtEncoder::GetMinMaxAlphaHQ + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte min color found +paramO: maxColor - 4 byte max color found +======================== +*/ +int idDxtEncoder::GetMinMaxAlphaHQ( const byte *colorBlock, const int alphaOffset, byte *minColor, byte *maxColor ) const { + int i, j; + byte alphaMin, alphaMax; + int error, bestError = MAX_TYPE( int ); + + alphaMin = 255; + alphaMax = 0; + + // get alpha min / max + for ( i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+alphaOffset] < alphaMin ) { + alphaMin = colorBlock[i*4+alphaOffset]; + } + if ( colorBlock[i*4+alphaOffset] > alphaMax ) { + alphaMax = colorBlock[i*4+alphaOffset]; + } + } + + const int ALPHA_EXPAND = 32; + + alphaMin = ( alphaMin <= ALPHA_EXPAND ) ? 0 : alphaMin - ALPHA_EXPAND; + alphaMax = ( alphaMax >= 255 - ALPHA_EXPAND ) ? 255 : alphaMax + ALPHA_EXPAND; + + for ( i = alphaMin; i <= alphaMax; i++ ) { + for ( j = alphaMax; j >= i; j-- ) { + + error = GetSquareAlphaError( colorBlock, alphaOffset, (byte)i, (byte)j, bestError ); + if ( error < bestError ) { + bestError = error; + minColor[alphaOffset] = (byte)i; + maxColor[alphaOffset] = (byte)j; + } + + error = GetSquareAlphaError( colorBlock, alphaOffset, (byte)j, (byte)i, bestError ); + if ( error < bestError ) { + bestError = error; + minColor[alphaOffset] = (byte)i; + maxColor[alphaOffset] = (byte)j; + } + } + } + + return bestError; +} + +/* +======================== +idDxtEncoder::GetSquareColorsError + +params: colorBlock - 16 pixel block for which to find color indexes +paramO: color0 - 4 byte min color found +paramO: color1 - 4 byte max color found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::GetSquareColorsError( const byte *colorBlock, const unsigned short color0, const unsigned short color1, int lastError ) const { + int i, j; + byte colors[4][4]; + + ColorFrom565( color0, colors[0] ); + ColorFrom565( color1, colors[1] ); + + if ( color0 > color1 ) { + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + } else { + colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2; + colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2; + colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2; + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + } + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + for ( j = 0; j < 4; j++ ) { + unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] ); + if ( dist < minDist ) { + minDist = dist; + } + } + // accumulated error + error += minDist; + + if ( error > lastError ) { + return error; + } + } + return error; +} + +/* +======================== +idDxtEncoder::GetSquareNormalYError + +params: colorBlock - 16 pixel block for which to find color indexes +paramO: color0 - 4 byte min color found +paramO: color1 - 4 byte max color found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::GetSquareNormalYError( const byte *colorBlock, const unsigned short color0, const unsigned short color1, int lastError, int scale ) const { + int i, j; + byte colors[4][4]; + + ColorFrom565( color0, colors[0] ); + ColorFrom565( color1, colors[1] ); + + if ( color0 > color1 ) { + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + } else { + colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2; + colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2; + colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2; + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + } + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + for ( j = 0; j < 4; j++ ) { + float r = (float) colorBlock[i*4+1] / scale; + float s = (float) colors[j][1] / scale; + unsigned int dist = idMath::Ftoi( ( r - s ) * ( r - s ) ); + if ( dist < minDist ) { + minDist = dist; + } + } + // accumulated error + error += minDist; + + if ( error > lastError ) { + return error; + } + } + return error; +} + +/* +======================== +idDxtEncoder::GetMinMaxColorsHQ + +Uses an exhaustive search to find the two RGB colors that produce the least error when used to +compress the 4x4 block. Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte min color found +paramO: maxColor - 4 byte max color found +======================== +*/ +int idDxtEncoder::GetMinMaxColorsHQ( const byte *colorBlock, byte *minColor, byte *maxColor, bool noBlack ) const { + int i; + int i0, i1, i2, j0, j1, j2; + unsigned short minColor565, maxColor565, bestMinColor565, bestMaxColor565; + byte bboxMin[3], bboxMax[3], minAxisDist[3]; + int error, bestError = MAX_TYPE( int ); + + bboxMin[0] = bboxMin[1] = bboxMin[2] = 255; + bboxMax[0] = bboxMax[1] = bboxMax[2] = 0; + + // get color bbox + for ( i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < bboxMin[0] ) { + bboxMin[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < bboxMin[1] ) { + bboxMin[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] < bboxMin[2] ) { + bboxMin[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+0] > bboxMax[0] ) { + bboxMax[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > bboxMax[1] ) { + bboxMax[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] > bboxMax[2] ) { + bboxMax[2] = colorBlock[i*4+2]; + } + } + + // decrease range for 565 encoding + bboxMin[0] >>= 3; + bboxMin[1] >>= 2; + bboxMin[2] >>= 3; + bboxMax[0] >>= 3; + bboxMax[1] >>= 2; + bboxMax[2] >>= 3; + + // get the minimum distance the end points of the line must be apart along each axis + for ( i = 0; i < 3; i++ ) { + minAxisDist[i] = ( bboxMax[i] - bboxMin[i] ); + if ( minAxisDist[i] >= 16 ) { + minAxisDist[i] = minAxisDist[i] * 3 / 4; + } else if ( minAxisDist[i] >= 8 ) { + minAxisDist[i] = minAxisDist[i] * 2 / 4; + } else if ( minAxisDist[i] >= 4 ) { + minAxisDist[i] = minAxisDist[i] * 1 / 4; + } else { + minAxisDist[i] = 0; + } + } + + // expand the bounding box + const int C565_BBOX_EXPAND = 1; + + bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND; + bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND; + bboxMin[2] = ( bboxMin[2] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[2] - C565_BBOX_EXPAND; + bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND; + bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND; + bboxMax[2] = ( bboxMax[2] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[2] + C565_BBOX_EXPAND; + + bestMinColor565 = 0; + bestMaxColor565 = 0; + + for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) { + for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) { + if ( abs( i0 - j0 ) < minAxisDist[0] ) { + continue; + } + + for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) { + for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) { + if ( abs( i1 - j1 ) < minAxisDist[1] ) { + continue; + } + + for ( i2 = bboxMin[2]; i2 <= bboxMax[2]; i2++ ) { + for ( j2 = bboxMax[2]; j2 >= bboxMin[2]; j2-- ) { + if ( abs( i2 - j2 ) < minAxisDist[2] ) { + continue; + } + + minColor565 = (unsigned short)( ( i0 << 11 ) | ( i1 << 5 ) | ( i2 << 0 ) ); + maxColor565 = (unsigned short)( ( j0 << 11 ) | ( j1 << 5 ) | ( j2 << 0 ) ); + + if ( !noBlack ) { + error = GetSquareColorsError( colorBlock, maxColor565, minColor565, bestError ); + if ( error < bestError ) { + bestError = error; + bestMinColor565 = minColor565; + bestMaxColor565 = maxColor565; + } + } else { + if ( minColor565 <= maxColor565 ) { + SwapValues( minColor565, maxColor565 ); + } + } + + error = GetSquareColorsError( colorBlock, minColor565, maxColor565, bestError ); + if ( error < bestError ) { + bestError = error; + bestMinColor565 = minColor565; + bestMaxColor565 = maxColor565; + } + } + } + } + } + } + } + + ColorFrom565( bestMinColor565, minColor ); + ColorFrom565( bestMaxColor565, maxColor ); + + return bestError; +} + +/* +======================== +idDxtEncoder::GetSquareCTX1Error + +params: colorBlock - 16 pixel block for which to find color indexes +paramO: color0 - Min color found +paramO: color1 - Max color found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::GetSquareCTX1Error( const byte *colorBlock, const byte *color0, const byte *color1, int lastError ) const { + int i, j; + byte colors[4][4]; + + colors[0][0] = color0[0]; + colors[0][1] = color0[1]; + colors[1][0] = color1[0]; + colors[1][1] = color1[1]; + + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + for ( j = 0; j < 4; j++ ) { + unsigned int dist = CTX1Distance( &colorBlock[i*4], &colors[j][0] ); + if ( dist < minDist ) { + minDist = dist; + } + } + // accumulated error + error += minDist; + + if ( error > lastError ) { + return error; + } + } + return error; +} + +/* +======================== +idDxtEncoder::GetMinMaxCTX1HQ + +Uses an exhaustive search to find the two RGB colors that produce the least error when used to +compress the 4x4 block. Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte Min color found +paramO: maxColor - 4 byte Max color found +======================== +*/ +int idDxtEncoder::GetMinMaxCTX1HQ( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + int i; + int i0, i1, j0, j1; + byte curMinColor[2], curMaxColor[2]; + byte bboxMin[2], bboxMax[2], minAxisDist[2]; + int error, bestError = MAX_TYPE( int ); + + bboxMin[0] = bboxMin[1] = 255; + bboxMax[0] = bboxMax[1] = 0; + + // get color bbox + for ( i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < bboxMin[0] ) { + bboxMin[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < bboxMin[1] ) { + bboxMin[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+0] > bboxMax[0] ) { + bboxMax[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > bboxMax[1] ) { + bboxMax[1] = colorBlock[i*4+1]; + } + } + + // get the minimum distance the end points of the line must be apart along each axis + for ( i = 0; i < 2; i++ ) { + minAxisDist[i] = ( bboxMax[i] - bboxMin[i] ); + if ( minAxisDist[i] >= 64 ) { + minAxisDist[i] = minAxisDist[i] * 3 / 4; + } else if ( minAxisDist[i] >= 32 ) { + minAxisDist[i] = minAxisDist[i] * 2 / 4; + } else if ( minAxisDist[i] >= 16 ) { + minAxisDist[i] = minAxisDist[i] * 1 / 4; + } else { + minAxisDist[i] = 0; + } + } + + // expand the bounding box + const int CXT1_BBOX_EXPAND = 6; + + bboxMin[0] = ( bboxMin[0] <= CXT1_BBOX_EXPAND ) ? 0 : bboxMin[0] - CXT1_BBOX_EXPAND; + bboxMin[1] = ( bboxMin[1] <= CXT1_BBOX_EXPAND ) ? 0 : bboxMin[1] - CXT1_BBOX_EXPAND; + bboxMax[0] = ( bboxMax[0] >= 255 - CXT1_BBOX_EXPAND ) ? 255 : bboxMax[0] + CXT1_BBOX_EXPAND; + bboxMax[1] = ( bboxMax[1] >= 255 - CXT1_BBOX_EXPAND ) ? 255 : bboxMax[1] + CXT1_BBOX_EXPAND; + + for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) { + for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) { + if ( abs( i0 - j0 ) < minAxisDist[0] ) { + continue; + } + + for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) { + for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) { + if ( abs( i1 - j1 ) < minAxisDist[1] ) { + continue; + } + + curMinColor[0] = (byte)i0; + curMinColor[1] = (byte)i1; + + curMaxColor[0] = (byte)j0; + curMaxColor[1] = (byte)j1; + + error = GetSquareCTX1Error( colorBlock, curMinColor, curMaxColor, bestError ); + if ( error < bestError ) { + bestError = error; + memcpy( minColor, curMinColor, 2 ); + memcpy( maxColor, curMaxColor, 2 ); + } + } + } + } + } + + return bestError; +} + +/* +======================== +idDxtEncoder::GetMinMaxNormalYHQ + +Uses an exhaustive search to find the two RGB colors that produce the least error when used to +compress the 4x4 block. Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte Min color found +paramO: maxColor - 4 byte Max color found +======================== +*/ +int idDxtEncoder::GetMinMaxNormalYHQ( const byte *colorBlock, byte *minColor, byte *maxColor, bool noBlack, int scale ) const { + unsigned short bestMinColor565, bestMaxColor565; + byte bboxMin[3], bboxMax[3]; + int error, bestError = MAX_TYPE( int ); + + bboxMin[1] = 255; + bboxMax[1] = 0; + + // get color bbox + for ( int i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+1] < bboxMin[1] ) { + bboxMin[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+1] > bboxMax[1] ) { + bboxMax[1] = colorBlock[i*4+1]; + } + } + + // decrease range for 565 encoding + bboxMin[1] >>= 2; + bboxMax[1] >>= 2; + + // expand the bounding box + const int C565_BBOX_EXPAND = 1; + + bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND; + bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND; + + bestMinColor565 = 0; + bestMaxColor565 = 0; + + for ( int i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) { + for ( int j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) { + if ( abs( i1 - j1 ) < 0 ) { + continue; + } + + unsigned short minColor565 = (unsigned short)i1 << 5; + unsigned short maxColor565 = (unsigned short)j1 << 5; + + if ( !noBlack ) { + error = GetSquareNormalYError( colorBlock, maxColor565, minColor565, bestError, scale ); + if ( error < bestError ) { + bestError = error; + bestMinColor565 = minColor565; + bestMaxColor565 = maxColor565; + } + } else { + if ( minColor565 <= maxColor565 ) { + SwapValues( minColor565, maxColor565 ); + } + } + + error = GetSquareNormalYError( colorBlock, minColor565, maxColor565, bestError, scale ); + if ( error < bestError ) { + bestError = error; + bestMinColor565 = minColor565; + bestMaxColor565 = maxColor565; + } + } + } + + ColorFrom565( bestMinColor565, minColor ); + ColorFrom565( bestMaxColor565, maxColor ); + + int bias = colorBlock[0*4+0]; + int size = colorBlock[0*4+2]; + + minColor[0] = maxColor[0] = (byte)bias; + minColor[2] = maxColor[2] = (byte)size; + + return bestError; +} + +ALIGN16( static float SIMD_SSE2_float_scale[4] ) = { 2.0f / 255.0f, 2.0f / 255.0f, 2.0f / 255.0f, 2.0f / 255.0f }; +ALIGN16( static float SIMD_SSE2_float_descale[4] ) = { 255.0f / 2.0f, 255.0f / 2.0f, 255.0f / 2.0f, 255.0f / 2.0f }; +ALIGN16( static float SIMD_SSE2_float_zero[4] ) = { 0.0f, 0.0f, 0.0f, 0.0f }; +ALIGN16( static float SIMD_SSE2_float_one[4] ) = { 1.0f, 1.0f, 1.0f, 1.0f }; +ALIGN16( static float SIMD_SSE2_float_half[4] ) = { 0.5f, 0.5f, 0.5f, 0.5f }; +ALIGN16( static float SIMD_SSE2_float_255[4] ) = { 255.0f, 255.0f, 255.0f, 255.0f }; +ALIGN16( static float SIMD_SP_rsqrt_c0[4] ) = { 3.0f, 3.0f, 3.0f, 3.0f }; +ALIGN16( static float SIMD_SP_rsqrt_c1[4] ) = { -0.5f, -0.5f, -0.5f, -0.5f }; +ALIGN16( static dword SIMD_SSE2_dword_maskFirstThree[4] ) = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }; +ALIGN16( static dword SIMD_SSE2_dword_maskWords[4] ) = { 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000000 }; +#define R_SHUFFLE_PS( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) + +/* +======================== +NormalDistanceDXT1 +======================== +*/ +int NormalDistanceDXT1( const int *vector, const int *normalized ) { + int result; + __asm { + mov esi, vector + mov edi, normalized + cvtdq2ps xmm0, [esi] + mulps xmm0, SIMD_SSE2_float_scale + subps xmm0, SIMD_SSE2_float_one + pand xmm0, SIMD_SSE2_dword_maskFirstThree + movaps xmm1, xmm0 + mulps xmm1, xmm1 + pshufd xmm2, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm2, xmm1 + pshufd xmm1, xmm2, R_SHUFFLE_PS( 1, 0, 1, 0 ) + addps xmm2, xmm1 + + rsqrtps xmm1, xmm2 + mulps xmm2, xmm1 + mulps xmm2, xmm1 + subps xmm2, SIMD_SP_rsqrt_c0 + mulps xmm1, SIMD_SP_rsqrt_c1 + mulps xmm2, xmm1 + + mulps xmm0, xmm2 + addps xmm0, SIMD_SSE2_float_one + mulps xmm0, SIMD_SSE2_float_descale + addps xmm0, SIMD_SSE2_float_half + maxps xmm0, SIMD_SSE2_float_zero + minps xmm0, SIMD_SSE2_float_255 + cvttps2dq xmm0, xmm0 + psubd xmm0, [edi] + pand xmm0, SIMD_SSE2_dword_maskWords + pmullw xmm0, xmm0 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ) + paddd xmm0, xmm1 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 1, 0, 1, 0 ) + paddd xmm0, xmm1 + movd result, xmm0 + } + return result; +} + +/* +======================== +NormalDistanceDXT5 +======================== +*/ +int NormalDistanceDXT5( const int *vector, const int *normalized ) { + int result; + __asm { + mov esi, vector + mov edi, normalized +#if 0 // object-space + pshufd xmm0, [esi], R_SHUFFLE_PS( 0, 1, 3, 2 ) +#else + pshufd xmm0, [esi], R_SHUFFLE_PS( 1, 2, 3, 0 ) +#endif + cvtdq2ps xmm0, xmm0 + mulps xmm0, SIMD_SSE2_float_scale + subps xmm0, SIMD_SSE2_float_one + pand xmm0, SIMD_SSE2_dword_maskFirstThree + movaps xmm1, xmm0 + mulps xmm1, xmm1 + pshufd xmm2, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm2, xmm1 + pshufd xmm1, xmm2, R_SHUFFLE_PS( 1, 0, 1, 0 ) + addps xmm2, xmm1 + + rsqrtps xmm1, xmm2 + mulps xmm2, xmm1 + mulps xmm2, xmm1 + subps xmm2, SIMD_SP_rsqrt_c0 + mulps xmm1, SIMD_SP_rsqrt_c1 + mulps xmm2, xmm1 + + mulps xmm0, xmm2 + addps xmm0, SIMD_SSE2_float_one + mulps xmm0, SIMD_SSE2_float_descale + addps xmm0, SIMD_SSE2_float_half + maxps xmm0, SIMD_SSE2_float_zero + minps xmm0, SIMD_SSE2_float_255 + cvttps2dq xmm0, xmm0 +#if 0 // object-space + pshufd xmm3, [edi], R_SHUFFLE_PS( 0, 1, 3, 2 ) +#else + pshufd xmm3, [edi], R_SHUFFLE_PS( 1, 2, 3, 0 ) +#endif + psubd xmm0, xmm3 + pand xmm0, SIMD_SSE2_dword_maskWords + pmullw xmm0, xmm0 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ) + paddd xmm0, xmm1 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 1, 0, 1, 0 ) + paddd xmm0, xmm1 + movd result, xmm0 + } + return result; +} + +/* +======================== +idDxtEncoder::GetSquareNormalsDXT1Error + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: color0 - 4 byte Min color found +paramO: color1 - 4 byte Max color found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::GetSquareNormalsDXT1Error( const int *colorBlock, const unsigned short color0, const unsigned short color1, int lastError, unsigned int &colorIndices ) const { + byte byteColors[2][4]; + ALIGN16( int colors[4][4] ); + + ColorFrom565( color0, byteColors[0] ); + ColorFrom565( color1, byteColors[1] ); + + for ( int i = 0; i < 4; i++ ) { + colors[0][i] = byteColors[0][i]; + colors[1][i] = byteColors[1][i]; + } + + if ( color0 > color1 ) { + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + } else { + assert( color0 == color1 ); + colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2; + colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2; + colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2; + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + } + + int error = 0; + int tempColorIndices[16]; + for ( int i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + + for ( int j = 0; j < 4; j++ ) { + unsigned int dist = NormalDistanceDXT1( &colors[j][0], &colorBlock[i*4] ); + if ( dist < minDist ) { + minDist = dist; + tempColorIndices[i] = j; + } + } + // accumulated error + error += minDist; + + if ( error > lastError ) { + return error; + } + } + + colorIndices = 0; + for ( int i = 0; i < 16; i++ ) { + colorIndices |= ( tempColorIndices[i] << (unsigned int)( i << 1 ) ); + } + + return error; +} + +/* +======================== +idDxtEncoder::GetMinMaxNormalsDXT1HQ + +Uses an exhaustive search to find the two RGB colors that produce the least error when used to +compress the 4x4 block. Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte Min color found +paramO: maxColor - 4 byte Max color found +======================== +*/ +int idDxtEncoder::GetMinMaxNormalsDXT1HQ( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, bool noBlack ) const { + int i; + int i0, i1, i2, j0, j1, j2; + unsigned short bestMinColor565 = 0; + unsigned short bestMaxColor565 = 0; + byte bboxMin[3], bboxMax[3], minAxisDist[3]; + int error, bestError = MAX_TYPE( int ); + unsigned int tempColorIndices; + ALIGN16( int intColorBlock[16*4] ); + + bboxMin[0] = bboxMin[1] = bboxMin[2] = 128; + bboxMax[0] = bboxMax[1] = bboxMax[2] = 128; + + // get color bbox + for ( i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < bboxMin[0] ) { + bboxMin[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < bboxMin[1] ) { + bboxMin[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] < bboxMin[2] ) { + bboxMin[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+0] > bboxMax[0] ) { + bboxMax[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > bboxMax[1] ) { + bboxMax[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] > bboxMax[2] ) { + bboxMax[2] = colorBlock[i*4+2]; + } + } + + for ( int i = 0; i < 64; i++ ) { + intColorBlock[i] = colorBlock[i]; + } + + // decrease range for 565 encoding + bboxMin[0] >>= 3; + bboxMin[1] >>= 2; + bboxMin[2] >>= 3; + bboxMax[0] >>= 3; + bboxMax[1] >>= 2; + bboxMax[2] >>= 3; + + // get the minimum distance the end points of the line must be apart along each axis + for ( i = 0; i < 3; i++ ) { + minAxisDist[i] = 0; + } + + // expand the bounding box + const int C565_BBOX_EXPAND = 2; + + bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND; + bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND; + bboxMin[2] = ( bboxMin[2] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[2] - C565_BBOX_EXPAND; + bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND; + bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND; + bboxMax[2] = ( bboxMax[2] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[2] + C565_BBOX_EXPAND; + + for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) { + for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) { + if ( abs( i0 - j0 ) < minAxisDist[0] ) { + continue; + } + + for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) { + for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) { + if ( abs( i1 - j1 ) < minAxisDist[1] ) { + continue; + } + + for ( i2 = bboxMin[2]; i2 <= bboxMax[2]; i2++ ) { + for ( j2 = bboxMax[2]; j2 >= bboxMin[2]; j2-- ) { + if ( abs( i2 - j2 ) < minAxisDist[2] ) { + continue; + } + + unsigned short minColor565 = (unsigned short)( ( i0 << 11 ) | ( i1 << 5 ) | ( i2 << 0 ) ); + unsigned short maxColor565 = (unsigned short)( ( j0 << 11 ) | ( j1 << 5 ) | ( j2 << 0 ) ); + + if ( !noBlack ) { + error = GetSquareNormalsDXT1Error( intColorBlock, maxColor565, minColor565, bestError, tempColorIndices ); + if ( error < bestError ) { + bestError = error; + bestMinColor565 = minColor565; + bestMaxColor565 = maxColor565; + colorIndices = tempColorIndices; + } + } else { + if ( minColor565 <= maxColor565 ) { + SwapValues( minColor565, maxColor565 ); + } + } + + error = GetSquareNormalsDXT1Error( intColorBlock, minColor565, maxColor565, bestError, tempColorIndices ); + if ( error < bestError ) { + bestError = error; + bestMinColor565 = minColor565; + bestMaxColor565 = maxColor565; + colorIndices = tempColorIndices; + } + } + } + } + } + } + } + + ColorFrom565( bestMinColor565, minColor ); + ColorFrom565( bestMaxColor565, maxColor ); + + return bestError; +} + +/* +======================== +idDxtEncoder::GetSquareNormalsDXT5Error + +params: normalBlock - 16 pixel block for which to find normal indexes +paramO: minNormal - Min normal found +paramO: maxNormal - Max normal found +======================== +*/ +int idDxtEncoder::GetSquareNormalsDXT5Error( const int *normalBlock, const byte *minNormal, const byte *maxNormal, int lastError, unsigned int &colorIndices, byte *alphaIndices ) const { + byte alphas[8]; + byte colors[4][4]; + + unsigned short smin = ColorTo565( minNormal ); + unsigned short smax = ColorTo565( maxNormal ); + + ColorFrom565( smax, colors[0] ); + ColorFrom565( smin, colors[1] ); + + if ( smax > smin ) { + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + } else { + assert( smax == smin ); + colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2; + colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2; + colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2; + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + } + + alphas[0] = maxNormal[3]; + alphas[1] = minNormal[3]; + + if ( maxNormal[3] > minNormal[3] ) { + alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7; + alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7; + alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7; + alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7; + alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7; + alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7; + } else { + alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5; + alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5; + alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5; + alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5; + alphas[6] = 0; + alphas[7] = 255; + } + + int error = 0; + int tempColorIndices[16]; + int tempAlphaIndices[16]; + for ( int i = 0; i < 16; i++ ) { + ALIGN16( int normal[4] ); + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + + for ( int j = 0; j < 4; j++ ) { + normal[0] = colors[j][0]; + normal[1] = colors[j][1]; + normal[2] = colors[j][2]; + + for ( int k = 0; k < 8; k++ ) { + normal[3] = alphas[k]; + unsigned int dist = NormalDistanceDXT5( normal, &normalBlock[i*4] ); + if ( dist < minDist ) { + minDist = dist; + tempColorIndices[i] = j; + tempAlphaIndices[i] = k; + } + } + } + error += minDist; + + if ( error >= lastError ) { + return error; + } + } + + alphaIndices[0] = byte( (tempAlphaIndices[ 0] >> 0) | (tempAlphaIndices[ 1] << 3) | (tempAlphaIndices[ 2] << 6) ); + alphaIndices[1] = byte( (tempAlphaIndices[ 2] >> 2) | (tempAlphaIndices[ 3] << 1) | (tempAlphaIndices[ 4] << 4) | (tempAlphaIndices[ 5] << 7) ); + alphaIndices[2] = byte( (tempAlphaIndices[ 5] >> 1) | (tempAlphaIndices[ 6] << 2) | (tempAlphaIndices[ 7] << 5) ); + + alphaIndices[3] = byte( (tempAlphaIndices[ 8] >> 0) | (tempAlphaIndices[ 9] << 3) | (tempAlphaIndices[10] << 6) ); + alphaIndices[4] = byte( (tempAlphaIndices[10] >> 2) | (tempAlphaIndices[11] << 1) | (tempAlphaIndices[12] << 4) | (tempAlphaIndices[13] << 7) ); + alphaIndices[5] = byte( (tempAlphaIndices[13] >> 1) | (tempAlphaIndices[14] << 2) | (tempAlphaIndices[15] << 5) ); + + colorIndices = 0; + for ( int i = 0; i < 16; i++ ) { + colorIndices |= ( tempColorIndices[i] << (unsigned int)( i << 1 ) ); + } + + return error; +} + +/* +======================== +idDxtEncoder::GetMinMaxNormalsDXT5HQ + +Uses an exhaustive search to find the two RGB colors that produce the least error when used to +compress the 4x4 block. Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte Min color found +paramO: maxColor - 4 byte Max color found +======================== +*/ +int idDxtEncoder::GetMinMaxNormalsDXT5HQ( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, byte *alphaIndices ) const { + int i; + int i0, i1, i3, j0, j1, j3; + byte bboxMin[4], bboxMax[4], minAxisDist[4]; + byte tmin[4], tmax[4]; + int error, bestError = MAX_TYPE( int ); + unsigned int tempColorIndices; + byte tempAlphaIndices[6]; + ALIGN16( int intColorBlock[16*4] ); + + bboxMin[0] = bboxMin[1] = bboxMin[2] = bboxMin[3] = 255; + bboxMax[0] = bboxMax[1] = bboxMax[2] = bboxMax[3] = 0; + + // get color bbox + for ( i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < bboxMin[0] ) { + bboxMin[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < bboxMin[1] ) { + bboxMin[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] < bboxMin[2] ) { + bboxMin[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+3] < bboxMin[3] ) { + bboxMin[3] = colorBlock[i*4+3]; + } + if ( colorBlock[i*4+0] > bboxMax[0] ) { + bboxMax[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > bboxMax[1] ) { + bboxMax[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] > bboxMax[2] ) { + bboxMax[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+3] > bboxMax[3] ) { + bboxMax[3] = colorBlock[i*4+3]; + } + } + + for ( int i = 0; i < 64; i++ ) { + intColorBlock[i] = colorBlock[i]; + } + + // decrease range for 565 encoding + bboxMin[0] >>= 3; + bboxMin[1] >>= 2; + bboxMax[0] >>= 3; + bboxMax[1] >>= 2; + + // get the minimum distance the end points of the line must be apart along each axis + for ( i = 0; i < 4; i++ ) { + minAxisDist[i] = 0; + } + + // expand the bounding box + const int C565_BBOX_EXPAND = 2; + const int ALPHA_BBOX_EXPAND = 32; + + bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND; + bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND; + bboxMin[3] = ( bboxMin[3] <= ALPHA_BBOX_EXPAND ) ? 0 : bboxMin[3] - ALPHA_BBOX_EXPAND; + bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND; + bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND; + bboxMax[3] = ( bboxMax[3] >= (255)-ALPHA_BBOX_EXPAND ) ? (255) : bboxMax[3] + ALPHA_BBOX_EXPAND; + + for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) { + for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) { + if ( abs( i0 - j0 ) < minAxisDist[0] ) { + continue; + } + + for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) { + for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) { + if ( abs( i1 - j1 ) < minAxisDist[1] ) { + continue; + } + + tmin[0] = (byte)j0 << 3; + tmin[1] = (byte)j1 << 2; + tmin[2] = 0; + + tmax[0] = (byte)i0 << 3; + tmax[1] = (byte)i1 << 2; + tmax[2] = 0; + + for ( i3 = bboxMin[3]; i3 <= bboxMax[3]; i3++ ) { + for ( j3 = bboxMax[3]; j3 >= bboxMin[3]; j3-- ) { + if ( abs( i3 - j3 ) < minAxisDist[3] ) { + continue; + } + + tmin[3] = (byte)j3; + tmax[3] = (byte)i3; + + error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices ); + if ( error < bestError ) { + bestError = error; + memcpy( minColor, tmin, 4 ); + memcpy( maxColor, tmax, 4 ); + colorIndices = tempColorIndices; + memcpy( alphaIndices, tempAlphaIndices, 6 ); + } + + tmin[3] = (byte)i3; + tmax[3] = (byte)j3; + + error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices ); + if ( error < bestError ) { + bestError = error; + memcpy( minColor, tmin, 4 ); + memcpy( maxColor, tmax, 4 ); + colorIndices = tempColorIndices; + memcpy( alphaIndices, tempAlphaIndices, 6 ); + } + } + } + } + } + } + } + + return bestError; +} + +/* +======================== +idDxtEncoder::GetMinMaxNormalsDXT5HQFast + +Uses an exhaustive search to find the two RGB colors that produce the least error when used to +compress the 4x4 block. Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte Min color found +paramO: maxColor - 4 byte Max color found +======================== +*/ +int idDxtEncoder::GetMinMaxNormalsDXT5HQFast( const byte *colorBlock, byte *minColor, byte *maxColor, unsigned int &colorIndices, byte *alphaIndices ) const { + int i0, i1, i2, i3, j0, j1, j2, j3; + byte bboxMin[4], bboxMax[4], minAxisDist[4]; + byte tmin[4], tmax[4]; + int error, bestError = MAX_TYPE( int ); + unsigned int tempColorIndices; + byte tempAlphaIndices[6]; + ALIGN16( int intColorBlock[16*4] ); + + bboxMin[0] = bboxMin[1] = bboxMin[2] = bboxMin[3] = 255; + bboxMax[0] = bboxMax[1] = bboxMax[2] = bboxMax[3] = 0; + + // get color bbox + for ( int i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < bboxMin[0] ) { + bboxMin[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < bboxMin[1] ) { + bboxMin[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] < bboxMin[2] ) { + bboxMin[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+3] < bboxMin[3] ) { + bboxMin[3] = colorBlock[i*4+3]; + } + if ( colorBlock[i*4+0] > bboxMax[0] ) { + bboxMax[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > bboxMax[1] ) { + bboxMax[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] > bboxMax[2] ) { + bboxMax[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+3] > bboxMax[3] ) { + bboxMax[3] = colorBlock[i*4+3]; + } + } + + for ( int i = 0; i < 64; i++ ) { + intColorBlock[i] = colorBlock[i]; + } + + // decrease range for 565 encoding + bboxMin[0] >>= 3; + bboxMin[1] >>= 2; + bboxMin[2] >>= 3; + bboxMax[0] >>= 3; + bboxMax[1] >>= 2; + bboxMax[2] >>= 3; + + bboxMin[3] = 0; + bboxMax[3] = 255; + + // get the minimum distance the end points of the line must be apart along each axis + for ( int i = 0; i < 4; i++ ) { + minAxisDist[i] = 0; + } + + // expand the bounding box + const int C565_BBOX_EXPAND = 1; + const int ALPHA_BBOX_EXPAND = 128; + +#if 0 // object-space + bboxMin[0] = ( bboxMin[0] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[0] - C565_BBOX_EXPAND; + bboxMax[0] = ( bboxMax[0] >= (255>>3)-C565_BBOX_EXPAND ) ? (255>>3) : bboxMax[0] + C565_BBOX_EXPAND; + bboxMin[2] = 0; + bboxMax[2] = 0; +#else + bboxMin[0] = 0; + bboxMax[0] = 0; + bboxMin[2] = ( bboxMin[2] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[2] - C565_BBOX_EXPAND; + bboxMax[2] = ( bboxMax[2] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[2] + C565_BBOX_EXPAND; +#endif + + bboxMin[1] = ( bboxMin[1] <= C565_BBOX_EXPAND ) ? 0 : bboxMin[1] - C565_BBOX_EXPAND; + bboxMax[1] = ( bboxMax[1] >= (255>>2)-C565_BBOX_EXPAND ) ? (255>>2) : bboxMax[1] + C565_BBOX_EXPAND; + + bboxMin[3] = ( bboxMin[3] <= ALPHA_BBOX_EXPAND ) ? 0 : bboxMin[3] - ALPHA_BBOX_EXPAND; + bboxMax[3] = ( bboxMax[3] >= (255)-ALPHA_BBOX_EXPAND ) ? (255) : bboxMax[3] + ALPHA_BBOX_EXPAND; + + for ( i0 = bboxMin[0]; i0 <= bboxMax[0]; i0++ ) { + for ( j0 = bboxMax[0]; j0 >= bboxMin[0]; j0-- ) { + if ( abs( i0 - j0 ) < minAxisDist[0] ) { + continue; + } + + for ( i1 = bboxMin[1]; i1 <= bboxMax[1]; i1++ ) { + for ( j1 = bboxMax[1]; j1 >= bboxMin[1]; j1-- ) { + if ( abs( i1 - j1 ) < minAxisDist[1] ) { + continue; + } + + for ( i2 = bboxMin[2]; i2 <= bboxMax[2]; i2++ ) { + for ( j2 = bboxMax[2]; j2 >= bboxMin[2]; j2-- ) { + if ( abs( i2 - j2 ) < minAxisDist[2] ) { + continue; + } + + unsigned short minColor565 = (unsigned short)( ( i0 << 11 ) | ( i1 << 5 ) | i2 ); + unsigned short maxColor565 = (unsigned short)( ( j0 << 11 ) | ( j1 << 5 ) | j2 ); + + if ( minColor565 > maxColor565 ) { + SwapValues( minColor565, maxColor565 ); + } + + error = GetSquareNormalsDXT1Error( intColorBlock, maxColor565, minColor565, bestError, tempColorIndices ); + if ( error < bestError ) { + bestError = error; + ColorFrom565( minColor565, minColor ); + ColorFrom565( maxColor565, maxColor ); + colorIndices = tempColorIndices; + } + } + } + } + } + } + } + + bestError = MAX_TYPE( int ); + + memcpy( tmin, minColor, 4 ); + memcpy( tmax, maxColor, 4 ); + + for ( i3 = bboxMin[3]; i3 <= bboxMax[3]; i3++ ) { + for ( j3 = bboxMax[3]; j3 >= bboxMin[3]; j3-- ) { + if ( abs( i3 - j3 ) < minAxisDist[3] ) { + continue; + } + + tmin[3] = (byte)j3; + tmax[3] = (byte)i3; + + error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices ); + if ( error < bestError ) { + bestError = error; + memcpy( minColor, tmin, 4 ); + memcpy( maxColor, tmax, 4 ); + colorIndices = tempColorIndices; + memcpy( alphaIndices, tempAlphaIndices, 6 ); + } + + tmin[3] = (byte)i3; + tmax[3] = (byte)j3; + + error = GetSquareNormalsDXT5Error( intColorBlock, tmin, tmax, bestError, tempColorIndices, tempAlphaIndices ); + if ( error < bestError ) { + bestError = error; + memcpy( minColor, tmin, 4 ); + memcpy( maxColor, tmax, 4 ); + colorIndices = tempColorIndices; + memcpy( alphaIndices, tempAlphaIndices, 6 ); + } + } + } + + return bestError; +} + +/* +======================== +idDxtEncoder::FindColorIndices + +params: colorBlock - 16 pixel block for which find color indexes +paramO: color0 - Min color found +paramO: color1 - Max color found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::FindColorIndices( const byte *colorBlock, const unsigned short color0, const unsigned short color1, unsigned int &result ) const { + int i, j; + unsigned int indexes[16]; + byte colors[4][4]; + + ColorFrom565( color0, colors[0] ); + ColorFrom565( color1, colors[1] ); + + if ( color0 > color1 ) { + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + } else { + colors[2][0] = ( 1 * colors[0][0] + 1 * colors[1][0] ) / 2; + colors[2][1] = ( 1 * colors[0][1] + 1 * colors[1][1] ) / 2; + colors[2][2] = ( 1 * colors[0][2] + 1 * colors[1][2] ) / 2; + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + } + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + for ( j = 0; j < 4; j++ ) { + unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] ); + if ( dist < minDist ) { + minDist = dist; + indexes[i] = j; + } + } + // accumulated error + error += minDist; + } + + result = 0; + for ( i = 0; i < 16; i++ ) { + result |= ( indexes[i] << (unsigned int)( i << 1 ) ); + } + + return error; +} + +/* +======================== +idDxtEncoder::FindAlphaIndices + +params: colorBlock - 16 pixel block for which find alpha indexes +paramO: alpha0 - Min alpha found +paramO: alpha1 - Max alpha found +params: rindexes - 6 byte alpha index block +return: error metric for this compression +======================== +*/ +int idDxtEncoder::FindAlphaIndices( const byte *colorBlock, const int alphaOffset, const byte alpha0, const byte alpha1, byte *rindexes ) const { + int i, j; + unsigned int indexes[16]; + byte alphas[8]; + + alphas[0] = alpha0; + alphas[1] = alpha1; + if ( alpha0 > alpha1 ) { + alphas[2] = ( 6 * alpha0 + 1 * alpha1 ) / 7; + alphas[3] = ( 5 * alpha0 + 2 * alpha1 ) / 7; + alphas[4] = ( 4 * alpha0 + 3 * alpha1 ) / 7; + alphas[5] = ( 3 * alpha0 + 4 * alpha1 ) / 7; + alphas[6] = ( 2 * alpha0 + 5 * alpha1 ) / 7; + alphas[7] = ( 1 * alpha0 + 6 * alpha1 ) / 7; + } else { + alphas[2] = ( 4 * alpha0 + 1 * alpha1 ) / 5; + alphas[3] = ( 3 * alpha0 + 2 * alpha1 ) / 5; + alphas[4] = ( 2 * alpha0 + 3 * alpha1 ) / 5; + alphas[5] = ( 1 * alpha0 + 4 * alpha1 ) / 5; + alphas[6] = 0; + alphas[7] = 255; + } + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + byte a = colorBlock[i*4+alphaOffset]; + for ( j = 0; j < 8; j++ ) { + unsigned int dist = AlphaDistance( a, alphas[j] ); + if ( dist < minDist ) { + minDist = dist; + indexes[i] = j; + } + } + error += minDist; + } + + rindexes[0] = byte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) ); + rindexes[1] = byte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) ); + rindexes[2] = byte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) ); + + rindexes[3] = byte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) ); + rindexes[4] = byte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) ); + rindexes[5] = byte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) ); + + return error; +} + +/* +======================== +idDxtEncoder::FindCTX1Indices + +params: colorBlock - 16 pixel block for which find color indexes +paramO: color0 - Min color found +paramO: color1 - Max color found +return: 4 byte color index block +======================== +*/ +int idDxtEncoder::FindCTX1Indices( const byte *colorBlock, const byte *color0, const byte *color1, unsigned int &result ) const { + int i, j; + unsigned int indexes[16]; + byte colors[4][4]; + + colors[0][0] = color1[0]; + colors[0][1] = color1[1]; + colors[1][0] = color0[0]; + colors[1][1] = color0[1]; + + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + + int error = 0; + for ( i = 0; i < 16; i++ ) { + unsigned int minDist = MAX_UNSIGNED_TYPE( int ); + for ( j = 0; j < 4; j++ ) { + unsigned int dist = CTX1Distance( &colorBlock[i*4], &colors[j][0] ); + if ( dist < minDist ) { + minDist = dist; + indexes[i] = j; + } + } + // accumulated error + error += minDist; + } + + result = 0; + for ( i = 0; i < 16; i++ ) { + result |= ( indexes[i] << (unsigned int)( i << 1 ) ); + } + + return error; +} + +/* +======================== +idDxtEncoder::CompressImageDXT1HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT1HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + unsigned int colorIndices1; + unsigned int colorIndices2; + byte col1[4]; + byte col2[4]; + int error1; + int error2; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT1( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxColorsHQ( block, col1, col2, false ); + + // Write out color data. Try and find minimum error for the two encoding methods. + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + error1 = FindColorIndices( block, scol1, scol2, colorIndices1 ); + error2 = FindColorIndices( block, scol2, scol1, colorIndices2 ); + + if ( error1 < error2 ) { + + EmitUShort( scol1 ); + EmitUShort( scol2 ); + EmitUInt( colorIndices1 ); + + } else { + + EmitUShort( scol2 ); + EmitUShort( scol1 ); + EmitUInt( colorIndices2 ); + } + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::CompressImageDXT5HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + byte alphaIndices1[6]; + byte alphaIndices2[6]; + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + int error1; + int error2; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT5( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxColorsHQ( block, col1, col2, true ); + GetMinMaxAlphaHQ( block, 3, col1, col2 ); + + // Write out alpha data. Try and find minimum error for the two encoding methods. + error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 ); + error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 ); + + if ( error1 < error2 ) { + + EmitByte( col1[3] ); + EmitByte( col2[3] ); + EmitByte( alphaIndices1[0] ); + EmitByte( alphaIndices1[1] ); + EmitByte( alphaIndices1[2] ); + EmitByte( alphaIndices1[3] ); + EmitByte( alphaIndices1[4] ); + EmitByte( alphaIndices1[5] ); + + } else { + + EmitByte( col2[3] ); + EmitByte( col1[3] ); + EmitByte( alphaIndices2[0] ); + EmitByte( alphaIndices2[1] ); + EmitByte( alphaIndices2[2] ); + EmitByte( alphaIndices2[3] ); + EmitByte( alphaIndices2[4] ); + EmitByte( alphaIndices2[5] ); + } + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + NV4XHardwareBugFix( col2, col1 ); +#endif + + // Write out color data. Always take the path with 4 interpolated values. + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + EmitUShort( scol1 ); + EmitUShort( scol2 ); + + FindColorIndices( block, scol1, scol2, colorIndices ); + EmitUInt( colorIndices ); + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::CompressImageCTX1HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageCTX1HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorCTX1DXT5A( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxCTX1HQ( block, col1, col2 ); + + EmitByte( col2[0] ); + EmitByte( col2[1] ); + EmitByte( col1[0] ); + EmitByte( col1[1] ); + + FindCTX1Indices( block, col1, col2, colorIndices ); + EmitUInt( colorIndices ); + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::ScaleYCoCg + +params: colorBlock - 16 pixel block for which find color indexes +======================== +*/ +void idDxtEncoder::ScaleYCoCg( byte *colorBlock ) const { + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + minColor[0] = minColor[1] = minColor[2] = minColor[3] = 255; + maxColor[0] = maxColor[1] = maxColor[2] = maxColor[3] = 0; + + for ( int i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < minColor[0] ) { + minColor[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < minColor[1] ) { + minColor[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+0] > maxColor[0] ) { + maxColor[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > maxColor[1] ) { + maxColor[1] = colorBlock[i*4+1]; + } + } + + int m0 = abs( minColor[0] - 128 ); + int m1 = abs( minColor[1] - 128 ); + int m2 = abs( maxColor[0] - 128 ); + int m3 = abs( maxColor[1] - 128 ); + + if ( m1 > m0 ) m0 = m1; + if ( m3 > m2 ) m2 = m3; + if ( m2 > m0 ) m0 = m2; + + const int s0 = 128 / 2 - 1; + const int s1 = 128 / 4 - 1; + + int scale = 1 + ( m0 <= s0 ) + 2 * ( m0 <= s1 ); + + for ( int i = 0; i < 16; i++ ) { + colorBlock[i*4+0] = byte( ( colorBlock[i*4+0] - 128 ) * scale + 128 ); + colorBlock[i*4+1] = byte( ( colorBlock[i*4+1] - 128 ) * scale + 128 ); + colorBlock[i*4+2] = byte( ( scale - 1 ) << 3 ); + } +} + +/* +======================== +idDxtEncoder::CompressYCoCgDXT5HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressYCoCgDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + byte alphaIndices1[6]; + byte alphaIndices2[6]; + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + int error1; + int error2; + + assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT5( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + ScaleYCoCg( block ); + + GetMinMaxColorsHQ( block, col1, col2, true ); + GetMinMaxAlphaHQ( block, 3, col1, col2 ); + + // Write out alpha data. Try and find minimum error for the two encoding methods. + error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 ); + error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 ); + + if ( error1 < error2 ) { + + EmitByte( col1[3] ); + EmitByte( col2[3] ); + EmitByte( alphaIndices1[0] ); + EmitByte( alphaIndices1[1] ); + EmitByte( alphaIndices1[2] ); + EmitByte( alphaIndices1[3] ); + EmitByte( alphaIndices1[4] ); + EmitByte( alphaIndices1[5] ); + + } else { + + EmitByte( col2[3] ); + EmitByte( col1[3] ); + EmitByte( alphaIndices2[0] ); + EmitByte( alphaIndices2[1] ); + EmitByte( alphaIndices2[2] ); + EmitByte( alphaIndices2[3] ); + EmitByte( alphaIndices2[4] ); + EmitByte( alphaIndices2[5] ); + } + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + NV4XHardwareBugFix( col2, col1 ); +#endif + + // Write out color data. Always take the path with 4 interpolated values. + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + EmitUShort( scol1 ); + EmitUShort( scol2 ); + + FindColorIndices( block, scol1, scol2, colorIndices ); + EmitUInt( colorIndices ); + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::CompressYCoCgCTX1DXT5AHQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressYCoCgCTX1DXT5AHQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + byte alphaIndices1[6]; + byte alphaIndices2[6]; + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + int error1; + int error2; + + assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorCTX1DXT5A( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxAlphaHQ( block, 3, col1, col2 ); + + // Write out alpha data. Try and find minimum error for the two encoding methods. + error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 ); + error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 ); + + if ( error1 < error2 ) { + + EmitByte( col1[3] ); + EmitByte( col2[3] ); + EmitByte( alphaIndices1[0] ); + EmitByte( alphaIndices1[1] ); + EmitByte( alphaIndices1[2] ); + EmitByte( alphaIndices1[3] ); + EmitByte( alphaIndices1[4] ); + EmitByte( alphaIndices1[5] ); + + } else { + + EmitByte( col2[3] ); + EmitByte( col1[3] ); + EmitByte( alphaIndices2[0] ); + EmitByte( alphaIndices2[1] ); + EmitByte( alphaIndices2[2] ); + EmitByte( alphaIndices2[3] ); + EmitByte( alphaIndices2[4] ); + EmitByte( alphaIndices2[5] ); + } + + GetMinMaxCTX1HQ( block, col1, col2 ); + + EmitByte( col2[0] ); + EmitByte( col2[1] ); + EmitByte( col1[0] ); + EmitByte( col1[1] ); + + FindCTX1Indices( block, col1, col2, colorIndices ); + EmitUInt( colorIndices ); + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::RotateNormalsDXT1 +======================== +*/ +void idDxtEncoder::RotateNormalsDXT1( byte *block ) const { + byte rotatedBlock[64]; + byte col1[4]; + byte col2[4]; + int bestError = MAX_TYPE( int ); + int bestRotation = 0; + + for ( int i = 0; i < 32; i += 1 ) { + int r = ( i << 3 ) | ( i >> 2 ); + float angle = ( r / 255.0f ) * idMath::PI; + float s = sin( angle ); + float c = cos( angle ); + + for ( int j = 0; j < 16; j++ ) { + float x = block[j*4+0] / 255.0f * 2.0f - 1.0f; + float y = block[j*4+1] / 255.0f * 2.0f - 1.0f; + float rx = c * x - s * y; + float ry = s * x + c * y; + rotatedBlock[j*4+0] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f ); + rotatedBlock[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f ); + } + + int error = GetMinMaxColorsHQ( rotatedBlock, col1, col2, true ); + if ( error < bestError ) { + bestError = error; + bestRotation = r; + } + } + + float angle = ( bestRotation / 255.0f ) * idMath::PI; + float s = sin( angle ); + float c = cos( angle ); + + for ( int j = 0; j < 16; j++ ) { + float x = block[j*4+0] / 255.0f * 2.0f - 1.0f; + float y = block[j*4+1] / 255.0f * 2.0f - 1.0f; + float rx = c * x - s * y; + float ry = s * x + c * y; + block[j*4+0] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f ); + block[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f ); + block[j*4+2] = (byte)bestRotation; + } +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT1HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXT1HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT1( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + for ( int k = 0; k < 16; k++ ) { + block[k*4+2] = 0; + } + + GetMinMaxColorsHQ( block, col1, col2, true ); + + // Write out color data. Always take the path with 4 interpolated values. + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + EmitUShort( scol1 ); + EmitUShort( scol2 ); + + FindColorIndices( block, scol1, scol2, colorIndices ); + EmitUInt( colorIndices ); + + //idLib::Printf( "\r%3d%%", ( j * width + i * 4 ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT1RenormalizeHQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXT1RenormalizeHQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT1( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + // clear alpha channel + for ( int k = 0; k < 16; k++ ) { + block[k*4+3] = 0; + } + + GetMinMaxNormalsDXT1HQ( block, col1, col2, colorIndices, true ); + + // Write out color data. Always take the path with 4 interpolated values. + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + EmitUShort( scol1 ); + EmitUShort( scol2 ); + EmitUInt( colorIndices ); + + ////idLib::Printf( "\r%3d%%", ( j * width + i * 4 ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + ////idLib::Printf( "\r100%%\n" ); +} + +#define USE_SCALE 1 +#define USE_BIAS 1 + +static int c_blocks; +static int c_scaled; +static int c_scaled2x; +static int c_scaled4x; +static int c_differentBias; +static int c_biasHelped; + +/* +======================== +idDxtEncoder::BiasScaleNormalY + + * scale2x = 33% + * scale4x = 23% + * bias + scale2x = 30% + * bias + scale4x = 55% +======================== +*/ +void idDxtEncoder::BiasScaleNormalY( byte *colorBlock ) const { + + byte minColor = 255; + byte maxColor = 0; + + for ( int i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+1] < minColor ) { + minColor = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+1] > maxColor ) { + maxColor = colorBlock[i*4+1]; + } + } + + int bestBias = 128; + int bestRange = Max( abs( minColor - bestBias ), abs( maxColor - bestBias ) ); +#if USE_BIAS + for ( int i = 0; i < 32; i++ ) { + int bias = ( ( i << 3 ) | ( i >> 2 ) ) - 4; + int range = Max( abs( minColor - bias ), abs( maxColor - bias ) ); + if ( range < bestRange ) { + bestRange = range; + bestBias = bias; + } + } +#endif + + const int s0 = 128 / 2 - 1; + const int s1 = 128 / 4 - 1; + +#if USE_SCALE + int scale = 1 + ( bestRange <= s0 ) + 2 * ( bestRange <= s1 ); +#else + int scale = 1; +#endif + + if ( scale == 1 ) { + bestBias = 128; + } else { + c_scaled++; + if ( scale == 2 ) c_scaled2x++; + if ( scale == 4 ) c_scaled4x++; + if ( bestBias != 128 ) { + c_differentBias++; + int r = Max( abs( minColor - 128 ), abs( maxColor - 128 ) ); + int s = 1 + ( r <= s0 ) + 2 * ( r <= s1 ); + if ( scale > s ) { + c_biasHelped++; + } + } + } + + c_blocks++; + + for ( int i = 0; i < 16; i++ ) { + colorBlock[i*4+0] = byte( bestBias + 4 ); + colorBlock[i*4+1] = byte( ( colorBlock[i*4+1] - bestBias ) * scale + 128 ); + colorBlock[i*4+2] = byte( ( scale - 1 ) << 3 ); + } +} + +/* +======================== +idDxtEncoder::RotateNormalsDXT5 +======================== +*/ +void idDxtEncoder::RotateNormalsDXT5( byte *block ) const { + byte rotatedBlock[64]; + byte col1[4]; + byte col2[4]; + int bestError = MAX_TYPE( int ); + int bestRotation = 0; + int bestScale = 1; + + for ( int i = 0; i < 32; i += 1 ) { + int r = ( i << 3 ) | ( i >> 2 ); + float angle = ( r / 255.0f ) * idMath::PI; + float s = sin( angle ); + float c = cos( angle ); + + for ( int j = 0; j < 16; j++ ) { + float x = block[j*4+3] / 255.0f * 2.0f - 1.0f; + float y = block[j*4+1] / 255.0f * 2.0f - 1.0f; + float rx = c * x - s * y; + float ry = s * x + c * y; + rotatedBlock[j*4+3] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f ); + rotatedBlock[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f ); + } + +#if USE_SCALE + byte minColor = 255; + byte maxColor = 0; + + for ( int j = 0; j < 16; j++ ) { + if ( rotatedBlock[j*4+1] < minColor ) { + minColor = rotatedBlock[j*4+1]; + } + if ( rotatedBlock[j*4+1] > maxColor ) { + maxColor = rotatedBlock[j*4+1]; + } + } + + const int s0 = 128 / 2 - 1; + const int s1 = 128 / 4 - 1; + + int range = Max( abs( minColor - 128 ), abs( maxColor - 128 ) ); + int scale = 1 + ( range <= s0 ) + 2 * ( range <= s1 ); + + for ( int j = 0; j < 16; j++ ) { + rotatedBlock[j*4+1] = byte( ( rotatedBlock[j*4+1] - 128 ) * scale + 128 ); + } +#endif + + int errorY = GetMinMaxNormalYHQ( rotatedBlock, col1, col2, true, scale ); + int errorX = GetMinMaxAlphaHQ( rotatedBlock, 3, col1, col2 ); + int error = errorX + errorY; + if ( error < bestError ) { + bestError = error; + bestRotation = r; + bestScale = scale; + } + } + + float angle = ( bestRotation / 255.0f ) * idMath::PI; + float s = sin( angle ); + float c = cos( angle ); + + for ( int j = 0; j < 16; j++ ) { + float x = block[j*4+3] / 255.0f * 2.0f - 1.0f; + float y = block[j*4+1] / 255.0f * 2.0f - 1.0f; + float rx = c * x - s * y; + float ry = s * x + c * y; + block[j*4+0] = (byte)bestRotation; + block[j*4+1] = idMath::Ftob( ( ry + 1.0f ) / 2.0f * 255.0f ); + block[j*4+3] = idMath::Ftob( ( rx + 1.0f ) / 2.0f * 255.0f ); + +#if USE_SCALE + block[j*4+1] = byte( ( block[j*4+1] - 128 ) * bestScale + 128 ); + block[j*4+2] = byte( ( bestScale - 1 ) << 3 ); +#endif + } +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT5HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXT5HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + byte alphaIndices1[6]; + byte alphaIndices2[6]; + unsigned int colorIndices; + byte col1[4]; + byte col2[4]; + int error1; + int error2; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT5( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + // swizzle components + for ( int k = 0; k < 16; k++ ) { + block[k*4+3] = block[k*4+0]; + block[k*4+0] = 0; + block[k*4+2] = 0; + } + + //BiasScaleNormalY( block ); + //RotateNormalsDXT5( block ); + + GetMinMaxNormalYHQ( block, col1, col2, true, 1 ); + GetMinMaxAlphaHQ( block, 3, col1, col2 ); + + // Write out alpha data. Try and find minimum error for the two encoding methods. + error1 = FindAlphaIndices( block, 3, col1[3], col2[3], alphaIndices1 ); + error2 = FindAlphaIndices( block, 3, col2[3], col1[3], alphaIndices2 ); + + if ( error1 < error2 ) { + + EmitByte( col1[3] ); + EmitByte( col2[3] ); + EmitByte( alphaIndices1[0] ); + EmitByte( alphaIndices1[1] ); + EmitByte( alphaIndices1[2] ); + EmitByte( alphaIndices1[3] ); + EmitByte( alphaIndices1[4] ); + EmitByte( alphaIndices1[5] ); + + } else { + + EmitByte( col2[3] ); + EmitByte( col1[3] ); + EmitByte( alphaIndices2[0] ); + EmitByte( alphaIndices2[1] ); + EmitByte( alphaIndices2[2] ); + EmitByte( alphaIndices2[3] ); + EmitByte( alphaIndices2[4] ); + EmitByte( alphaIndices2[5] ); + } + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + NV4XHardwareBugFix( col2, col1 ); +#endif + + // Write out color data. Always take the path with 4 interpolated values. + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + EmitUShort( scol1 ); + EmitUShort( scol2 ); + + FindColorIndices( block, scol1, scol2, colorIndices ); + EmitUInt( colorIndices ); + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT5RenormalizeHQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXT5RenormalizeHQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + unsigned int colorIndices; + byte alphaIndices[6]; + byte col1[4]; + byte col2[4]; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT5( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + // swizzle components + for ( int k = 0; k < 16; k++ ) { +#if 0 // object-space + block[k*4+3] = block[k*4+2]; + block[k*4+2] = 0; +#else + block[k*4+3] = block[k*4+0]; + block[k*4+0] = 0; +#endif + } + + GetMinMaxNormalsDXT5HQFast( block, col1, col2, colorIndices, alphaIndices ); + + EmitByte( col2[3] ); + EmitByte( col1[3] ); + EmitByte( alphaIndices[0] ); + EmitByte( alphaIndices[1] ); + EmitByte( alphaIndices[2] ); + EmitByte( alphaIndices[3] ); + EmitByte( alphaIndices[4] ); + EmitByte( alphaIndices[5] ); + + unsigned short scol1 = ColorTo565( col1 ); + unsigned short scol2 = ColorTo565( col2 ); + + EmitUShort( scol2 ); + EmitUShort( scol1 ); + EmitUInt( colorIndices ); + + ////idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + ////idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXN2HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXN2HQ( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + byte alphaIndices1[6]; + byte alphaIndices2[6]; + byte col1[4]; + byte col2[4]; + int error1; + int error2; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + WriteTinyColorDXT5( inBuf, width, height ); + return; + } + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + for ( int k = 0; k < 2; k++ ) { + GetMinMaxAlphaHQ( block, k, col1, col2 ); + + // Write out alpha data. Try and find minimum error for the two encoding methods. + error1 = FindAlphaIndices( block, k, col1[k], col2[k], alphaIndices1 ); + error2 = FindAlphaIndices( block, k, col2[k], col1[k], alphaIndices2 ); + + if ( error1 < error2 ) { + + EmitByte( col1[k] ); + EmitByte( col2[k] ); + EmitByte( alphaIndices1[0] ); + EmitByte( alphaIndices1[1] ); + EmitByte( alphaIndices1[2] ); + EmitByte( alphaIndices1[3] ); + EmitByte( alphaIndices1[4] ); + EmitByte( alphaIndices1[5] ); + + } else { + + EmitByte( col2[k] ); + EmitByte( col1[k] ); + EmitByte( alphaIndices2[0] ); + EmitByte( alphaIndices2[1] ); + EmitByte( alphaIndices2[2] ); + EmitByte( alphaIndices2[3] ); + EmitByte( alphaIndices2[4] ); + EmitByte( alphaIndices2[5] ); + } + } + + //idLib::Printf( "\r%3d%%", ( j * width + i ) * 100 / ( width * height ) ); + } + outData += dstPadding; + inBuf += srcPadding; + } + + //idLib::Printf( "\r100%%\n" ); +} + +/* +======================== +idDxtEncoder::GetMinMaxBBox + +Takes the extents of the bounding box of the colors in the 4x4 block in RGB space. +Also finds the minimum and maximum alpha values. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - 4 byte Min color found +paramO: maxColor - 4 byte Max color found +======================== +*/ +ID_INLINE void idDxtEncoder::GetMinMaxBBox( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + + minColor[0] = minColor[1] = minColor[2] = minColor[3] = 255; + maxColor[0] = maxColor[1] = maxColor[2] = maxColor[3] = 0; + + for ( int i = 0; i < 16; i++ ) { + if ( colorBlock[i*4+0] < minColor[0] ) { + minColor[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] < minColor[1] ) { + minColor[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] < minColor[2] ) { + minColor[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+3] < minColor[3] ) { + minColor[3] = colorBlock[i*4+3]; + } + if ( colorBlock[i*4+0] > maxColor[0] ) { + maxColor[0] = colorBlock[i*4+0]; + } + if ( colorBlock[i*4+1] > maxColor[1] ) { + maxColor[1] = colorBlock[i*4+1]; + } + if ( colorBlock[i*4+2] > maxColor[2] ) { + maxColor[2] = colorBlock[i*4+2]; + } + if ( colorBlock[i*4+3] > maxColor[3] ) { + maxColor[3] = colorBlock[i*4+3]; + } + } +} + +/* +======================== +idDxtEncoder::InsetColorsBBox +======================== +*/ +ID_INLINE void idDxtEncoder::InsetColorsBBox( byte *minColor, byte *maxColor ) const { + byte inset[4]; + + inset[0] = ( maxColor[0] - minColor[0] ) >> INSET_COLOR_SHIFT; + inset[1] = ( maxColor[1] - minColor[1] ) >> INSET_COLOR_SHIFT; + inset[2] = ( maxColor[2] - minColor[2] ) >> INSET_COLOR_SHIFT; + inset[3] = ( maxColor[3] - minColor[3] ) >> INSET_ALPHA_SHIFT; + + minColor[0] = ( minColor[0] + inset[0] <= 255 ) ? minColor[0] + inset[0] : 255; + minColor[1] = ( minColor[1] + inset[1] <= 255 ) ? minColor[1] + inset[1] : 255; + minColor[2] = ( minColor[2] + inset[2] <= 255 ) ? minColor[2] + inset[2] : 255; + minColor[3] = ( minColor[3] + inset[3] <= 255 ) ? minColor[3] + inset[3] : 255; + + maxColor[0] = ( maxColor[0] >= inset[0] ) ? maxColor[0] - inset[0] : 0; + maxColor[1] = ( maxColor[1] >= inset[1] ) ? maxColor[1] - inset[1] : 0; + maxColor[2] = ( maxColor[2] >= inset[2] ) ? maxColor[2] - inset[2] : 0; + maxColor[3] = ( maxColor[3] >= inset[3] ) ? maxColor[3] - inset[3] : 0; +} + +/* +======================== +idDxtEncoder::SelectColorsDiagonal +======================== +*/ +void idDxtEncoder::SelectColorsDiagonal( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + + byte mid0 = byte( ( (int) minColor[0] + maxColor[0] + 1 ) >> 1 ); + byte mid1 = byte( ( (int) minColor[1] + maxColor[1] + 1 ) >> 1 ); + byte mid2 = byte( ( (int) minColor[2] + maxColor[2] + 1 ) >> 1 ); + +#if 0 + + // using the covariance is the best way to select the diagonal + int side0 = 0; + int side1 = 0; + for ( int i = 0; i < 16; i++ ) { + int b0 = colorBlock[i*4+0] - mid0; + int b1 = colorBlock[i*4+1] - mid1; + int b2 = colorBlock[i*4+2] - mid2; + side0 += ( b0 * b1 ); + side1 += ( b1 * b2 ); + } + byte mask0 = -( side0 < 0 ); + byte mask1 = -( side1 < 0 ); + +#else + + // calculating the covariance of just the sign bits is much faster and gives almost the same result + int side0 = 0; + int side1 = 0; + for ( int i = 0; i < 16; i++ ) { + byte b0 = colorBlock[i*4+0] >= mid0; + byte b1 = colorBlock[i*4+1] >= mid1; + byte b2 = colorBlock[i*4+2] >= mid2; + side0 += ( b0 ^ b1 ); + side1 += ( b1 ^ b2 ); + } + byte mask0 = -( side0 > 8 ); + byte mask1 = -( side1 > 8 ); + +#endif + + byte c0 = minColor[0]; + byte c1 = maxColor[0]; + byte c2 = minColor[2]; + byte c3 = maxColor[2]; + + c0 ^= c1; + mask0 &= c0; + c1 ^= mask0; + c0 ^= c1; + + c2 ^= c3; + mask1 &= c2; + c3 ^= mask1; + c2 ^= c3; + + minColor[0] = c0; + maxColor[0] = c1; + minColor[2] = c2; + maxColor[2] = c3; + + if ( ColorTo565( minColor ) > ColorTo565( maxColor ) ) { + SwapValues( minColor[0], maxColor[0] ); + SwapValues( minColor[1], maxColor[1] ); + SwapValues( minColor[2], maxColor[2] ); + } +} + +/* +======================== +idDxtEncoder::EmitColorIndices + +params: colorBlock - 16 pixel block for which find color indexes +paramO: minColor - Min color found +paramO: maxColor - Max color found +return: 4 byte color index block +======================== +*/ +void idDxtEncoder::EmitColorIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor ) { +#if 1 + + ALIGN16( uint16 colors[4][4] ); + unsigned int result = 0; + + colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 ); + colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 ); + colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 5 ); + colors[0][3] = 0; + colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 ); + colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 ); + colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 5 ); + colors[1][3] = 0; + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[2][3] = 0; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + colors[3][3] = 0; + + // uses sum of absolute differences instead of squared distance to find the best match + for ( int i = 15; i >= 0; i-- ) { + int c0, c1, c2, c3, m, d0, d1, d2, d3; + + c0 = colorBlock[i*4+0]; + c1 = colorBlock[i*4+1]; + c2 = colorBlock[i*4+2]; + c3 = colorBlock[i*4+3]; + + m = colors[0][0] - c0; + d0 = abs( m ); + m = colors[1][0] - c0; + d1 = abs( m ); + m = colors[2][0] - c0; + d2 = abs( m ); + m = colors[3][0] - c0; + d3 = abs( m ); + + m = colors[0][1] - c1; + d0 += abs( m ); + m = colors[1][1] - c1; + d1 += abs( m ); + m = colors[2][1] - c1; + d2 += abs( m ); + m = colors[3][1] - c1; + d3 += abs( m ); + + m = colors[0][2] - c2; + d0 += abs( m ); + m = colors[1][2] - c2; + d1 += abs( m ); + m = colors[2][2] - c2; + d2 += abs( m ); + m = colors[3][2] - c2; + d3 += abs( m ); + +#if 0 + int b0 = d0 > d2; + int b1 = d1 > d3; + int b2 = d0 > d3; + int b3 = d1 > d2; + int b4 = d0 > d1; + int b5 = d2 > d3; + + result |= ( ( !b3 & b4 ) | ( b2 & b5 ) | ( ( ( b0 & b3 ) | ( b1 & b2 ) ) << 1 ) ) << ( i << 1 ); +#else + bool b0 = d0 > d3; + bool b1 = d1 > d2; + bool b2 = d0 > d2; + bool b3 = d1 > d3; + bool b4 = d2 > d3; + + int x0 = b1 & b2; + int x1 = b0 & b3; + int x2 = b0 & b4; + + result |= ( x2 | ( ( x0 | x1 ) << 1 ) ) << ( i << 1 ); +#endif + } + + EmitUInt( result ); + +#elif 1 + + byte colors[4][4]; + unsigned int indexes[16]; + + colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 6 ); + colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 5 ); + colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 6 ); + colors[0][3] = 0; + colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 6 ); + colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 5 ); + colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 6 ); + colors[1][3] = 0; + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[2][3] = 0; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + colors[3][3] = 0; + + for ( int i = 0; i < 16; i++ ) { + int c0, c1, c2, m, d, minDist; + + c0 = colorBlock[i*4+0]; + c1 = colorBlock[i*4+1]; + c2 = colorBlock[i*4+2]; + + m = colors[0][0] - c0; + d = m * m; + m = colors[0][1] - c1; + d += m * m; + m = colors[0][2] - c2; + d += m * m; + + minDist = d; + indexes[i] = 0; + + m = colors[1][0] - c0; + d = m * m; + m = colors[1][1] - c1; + d += m * m; + m = colors[1][2] - c2; + d += m * m; + + if ( d < minDist ) { + minDist = d; + indexes[i] = 1; + } + + m = colors[2][0] - c0; + d = m * m; + m = colors[2][1] - c1; + d += m * m; + m = colors[2][2] - c2; + d += m * m; + + if ( d < minDist ) { + minDist = d; + indexes[i] = 2; + } + + m = colors[3][0] - c0; + d = m * m; + m = colors[3][1] - c1; + d += m * m; + m = colors[3][2] - c2; + d += m * m; + + if ( d < minDist ) { + minDist = d; + indexes[i] = 3; + } + } + + unsigned int result = 0; + for ( int i = 0; i < 16; i++ ) { + result |= ( indexes[i] << (unsigned int)( i << 1 ) ); + } + + EmitUInt( result ); + +#else + + byte colors[4][4]; + unsigned int indexes[16]; + + colors[0][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 6 ); + colors[0][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 5 ); + colors[0][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 6 ); + colors[0][3] = 0; + colors[1][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 6 ); + colors[1][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 5 ); + colors[1][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 6 ); + colors[1][3] = 0; + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[2][2] = ( 2 * colors[0][2] + 1 * colors[1][2] ) / 3; + colors[2][3] = 0; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + colors[3][2] = ( 1 * colors[0][2] + 2 * colors[1][2] ) / 3; + colors[3][3] = 0; + + for ( int i = 0; i < 16; i++ ) { + unsigned int minDist = (255*255)*4; + for ( int j = 0; j < 4; j++ ) { + unsigned int dist = ColorDistance( &colorBlock[i*4], &colors[j][0] ); + if ( dist < minDist ) { + minDist = dist; + indexes[i] = j; + } + } + } + + unsigned int result = 0; + for ( int i = 0; i < 16; i++ ) { + result |= ( indexes[i] << (unsigned int)( i << 1 ) ); + } + + EmitUInt( result ); + +#endif +} + +/* +======================== +idDxtEncoder::EmitColorAlphaIndices + +params: colorBlock - 16 pixel block for which find color indexes +paramO: minColor - Min color found +paramO: maxColor - Max color found +return: 4 byte color index block +======================== +*/ +void idDxtEncoder::EmitColorAlphaIndices( const byte *colorBlock, const byte *minColor, const byte *maxColor ) { + ALIGN16( uint16 colors[4][4] ); + unsigned int result = 0; + + colors[0][0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 ); + colors[0][1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 ); + colors[0][2] = ( minColor[2] & C565_5_MASK ) | ( minColor[2] >> 5 ); + colors[0][3] = 255; + colors[1][0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 ); + colors[1][1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 ); + colors[1][2] = ( maxColor[2] & C565_5_MASK ) | ( maxColor[2] >> 5 ); + colors[1][3] = 255; + colors[2][0] = ( colors[0][0] + colors[1][0] ) / 2; + colors[2][1] = ( colors[0][1] + colors[1][1] ) / 2; + colors[2][2] = ( colors[0][2] + colors[1][2] ) / 2; + colors[2][3] = 255; + colors[3][0] = 0; + colors[3][1] = 0; + colors[3][2] = 0; + colors[3][3] = 0; + + // uses sum of absolute differences instead of squared distance to find the best match + for ( int i = 15; i >= 0; i-- ) { + int c0, c1, c2, c3, m, d0, d1, d2; + + c0 = colorBlock[i*4+0]; + c1 = colorBlock[i*4+1]; + c2 = colorBlock[i*4+2]; + c3 = colorBlock[i*4+3]; + + m = colors[0][0] - c0; + d0 = abs( m ); + m = colors[1][0] - c0; + d1 = abs( m ); + m = colors[2][0] - c0; + d2 = abs( m ); + + m = colors[0][1] - c1; + d0 += abs( m ); + m = colors[1][1] - c1; + d1 += abs( m ); + m = colors[2][1] - c1; + d2 += abs( m ); + + m = colors[0][2] - c2; + d0 += abs( m ); + m = colors[1][2] - c2; + d1 += abs( m ); + m = colors[2][2] - c2; + d2 += abs( m ); + + unsigned int b0 = d2 > d0; + unsigned int b1 = d2 > d1; + unsigned int b2 = d1 > d0; + unsigned int b3 = c3 < 128; + + result |= ( ( ( b0 & b1 | b3 ) << 1 ) | ( b2 ^ b1 | b3 ) ) << ( i << 1 ); + } + + EmitUInt( result ); +} + +/* +======================== +idDxtEncoder::EmitCTX1Indices + +params: colorBlock - 16 pixel block for which find color indexes +paramO: minColor - Min color found +paramO: maxColor - Max color found +return: 4 byte color index block +======================== +*/ +void idDxtEncoder::EmitCTX1Indices( const byte *colorBlock, const byte *minColor, const byte *maxColor ) { + ALIGN16( uint16 colors[4][2] ); + unsigned int result = 0; + + colors[0][0] = maxColor[0]; + colors[0][1] = maxColor[1]; + colors[1][0] = minColor[0]; + colors[1][1] = minColor[1]; + + colors[2][0] = ( 2 * colors[0][0] + 1 * colors[1][0] ) / 3; + colors[2][1] = ( 2 * colors[0][1] + 1 * colors[1][1] ) / 3; + colors[3][0] = ( 1 * colors[0][0] + 2 * colors[1][0] ) / 3; + colors[3][1] = ( 1 * colors[0][1] + 2 * colors[1][1] ) / 3; + + for ( int i = 15; i >= 0; i-- ) { + int c0, c1, m, d0, d1, d2, d3; + + c0 = colorBlock[i*4+0]; + c1 = colorBlock[i*4+1]; + + m = colors[0][0] - c0; + d0 = abs( m ); + m = colors[1][0] - c0; + d1 = abs( m ); + m = colors[2][0] - c0; + d2 = abs( m ); + m = colors[3][0] - c0; + d3 = abs( m ); + + m = colors[0][1] - c1; + d0 += abs( m ); + m = colors[1][1] - c1; + d1 += abs( m ); + m = colors[2][1] - c1; + d2 += abs( m ); + m = colors[3][1] - c1; + d3 += abs( m ); + + bool b0 = d0 > d3; + bool b1 = d1 > d2; + bool b2 = d0 > d2; + bool b3 = d1 > d3; + bool b4 = d2 > d3; + + int x0 = b1 & b2; + int x1 = b0 & b3; + int x2 = b0 & b4; + + result |= ( x2 | ( ( x0 | x1 ) << 1 ) ) << ( i << 1 ); + } + + EmitUInt( result ); +} + +/* +======================== +idDxtEncoder::EmitAlphaIndices + +params: colorBlock - 16 pixel block for which find alpha indexes +paramO: minAlpha - Min alpha found +paramO: maxAlpha - Max alpha found +======================== +*/ +void idDxtEncoder::EmitAlphaIndices( const byte *colorBlock, const int offset, const byte minAlpha, const byte maxAlpha ) { + + assert( maxAlpha >= minAlpha ); + + const int ALPHA_RANGE = 7; + +#if 1 + + byte ab1, ab2, ab3, ab4, ab5, ab6, ab7; + ALIGN16( byte indexes[16] ); + + ab1 = ( 13 * maxAlpha + 1 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + ab2 = ( 11 * maxAlpha + 3 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + ab3 = ( 9 * maxAlpha + 5 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + ab4 = ( 7 * maxAlpha + 7 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + ab5 = ( 5 * maxAlpha + 9 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + ab6 = ( 3 * maxAlpha + 11 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + ab7 = ( 1 * maxAlpha + 13 * minAlpha + ALPHA_RANGE ) / (ALPHA_RANGE*2); + + colorBlock += offset; + + for ( int i = 0; i < 16; i++ ) { + byte a = colorBlock[i*4]; + int b1 = ( a >= ab1 ); + int b2 = ( a >= ab2 ); + int b3 = ( a >= ab3 ); + int b4 = ( a >= ab4 ); + int b5 = ( a >= ab5 ); + int b6 = ( a >= ab6 ); + int b7 = ( a >= ab7 ); + int index = ( 8 - b1 - b2 - b3 - b4 - b5 - b6 - b7 ) & 7; + indexes[i] = byte( index ^ ( 2 > index ) ); + } + + EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) ); + EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) ); + EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) ); + + EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) ); + EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) ); + EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) ); + +#elif 0 + + ALIGN16( byte indexes[16] ); + byte delta = maxAlpha - minAlpha; + byte half = delta >> 1; + byte bias = delta / ( 2 * ALPHA_RANGE ); + byte bottom = minAlpha + bias; + byte top = maxAlpha - bias; + + colorBlock += offset; + + for ( int i = 0; i < 16; i++ ) { + byte a = colorBlock[i*4]; + if ( a <= bottom ) { + indexes[i] = 1; + } else if ( a >= top ) { + indexes[i] = 0; + } else { + indexes[i] = (ALPHA_RANGE+1) + ( ( minAlpha - a ) * ALPHA_RANGE - half ) / delta; + } + } + + EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) ); + EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) ); + EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) ); + + EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) ); + EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) ); + EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) ); + +#elif 0 + + ALIGN16( byte indexes[16] ); + byte delta = maxAlpha - minAlpha; + byte half = delta >> 1; + byte bias = delta / ( 2 * ALPHA_RANGE ); + byte bottom = minAlpha + bias; + byte top = maxAlpha - bias; + + colorBlock += offset; + + for ( int i = 0; i < 16; i++ ) { + byte a = colorBlock[i*4]; + int index = (ALPHA_RANGE+1) + ( ( minAlpha - a ) * ALPHA_RANGE - half ) / delta; + int c0 = a > bottom; + int c1 = a < top; + indexes[i] = ( index & -( c0 & c1 ) ) | ( c0 ^ 1 ); + } + + EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) ); + EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) ); + EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) ); + + EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) ); + EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) ); + EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) ); + +#else + + ALIGN16( byte indexes[16] ); + ALIGN16( byte alphas[8] ); + + alphas[0] = maxAlpha; + alphas[1] = minAlpha; + alphas[2] = ( 6 * maxAlpha + 1 * minAlpha ) / ALPHA_RANGE; + alphas[3] = ( 5 * maxAlpha + 2 * minAlpha ) / ALPHA_RANGE; + alphas[4] = ( 4 * maxAlpha + 3 * minAlpha ) / ALPHA_RANGE; + alphas[5] = ( 3 * maxAlpha + 4 * minAlpha ) / ALPHA_RANGE; + alphas[6] = ( 2 * maxAlpha + 5 * minAlpha ) / ALPHA_RANGE; + alphas[7] = ( 1 * maxAlpha + 6 * minAlpha ) / ALPHA_RANGE; + + colorBlock += offset; + + for ( int i = 0; i < 16; i++ ) { + int minDist = INT_MAX; + byte a = colorBlock[i*4]; + for ( int j = 0; j < 8; j++ ) { + int dist = abs( a - alphas[j] ); + if ( dist < minDist ) { + minDist = dist; + indexes[i] = j; + } + } + } + + EmitByte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) ); + EmitByte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) ); + EmitByte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) ); + + EmitByte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) ); + EmitByte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) ); + EmitByte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) ); + +#endif +} + +/* +======================== +idDxtEncoder::CompressImageDXT1Fast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, minColor, maxColor ); + //SelectColorsDiagonal( block, minColor, maxColor ); + InsetColorsBBox( minColor, maxColor ); + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitColorIndices( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::CompressImageDXT1AlphaFast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT1AlphaFast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, minColor, maxColor ); + byte minAlpha = minColor[3]; + //SelectColorsDiagonal( block, minColor, maxColor ); + InsetColorsBBox( minColor, maxColor ); + + if ( minAlpha >= 128 ) { + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + EmitColorIndices( block, minColor, maxColor ); + } else { + EmitUShort( ColorTo565( minColor ) ); + EmitUShort( ColorTo565( maxColor ) ); + EmitColorAlphaIndices( block, minColor, maxColor ); + } + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::CompressImageDXT5Fast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, minColor, maxColor ); + //SelectColorsDiagonal( block, minColor, maxColor ); + InsetColorsBBox( minColor, maxColor ); + + EmitByte( maxColor[3] ); + EmitByte( minColor[3] ); + + EmitAlphaIndices( block, 3, minColor[3], maxColor[3] ); + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + // the colors are already always guaranteed to be sorted properly +#endif + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitColorIndices( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::ScaleYCoCg +======================== +*/ +void idDxtEncoder::ScaleYCoCg( byte *colorBlock, byte *minColor, byte *maxColor ) const { + int m0 = abs( minColor[0] - 128 ); + int m1 = abs( minColor[1] - 128 ); + int m2 = abs( maxColor[0] - 128 ); + int m3 = abs( maxColor[1] - 128 ); + + if ( m1 > m0 ) m0 = m1; + if ( m3 > m2 ) m2 = m3; + if ( m2 > m0 ) m0 = m2; + + const int s0 = 128 / 2 - 1; + const int s1 = 128 / 4 - 1; + + int mask0 = -( m0 <= s0 ); + int mask1 = -( m0 <= s1 ); + int scale = 1 + ( 1 & mask0 ) + ( 2 & mask1 ); + + minColor[0] = byte( ( minColor[0] - 128 ) * scale + 128 ); + minColor[1] = byte( ( minColor[1] - 128 ) * scale + 128 ); + minColor[2] = byte( ( scale - 1 ) << 3 ); + maxColor[0] = byte( ( maxColor[0] - 128 ) * scale + 128 ); + maxColor[1] = byte( ( maxColor[1] - 128 ) * scale + 128 ); + maxColor[2] = byte( ( scale - 1 ) << 3 ); + + for ( int i = 0; i < 16; i++ ) { + colorBlock[i*4+0] = byte( ( colorBlock[i*4+0] - 128 ) * scale + 128 ); + colorBlock[i*4+1] = byte( ( colorBlock[i*4+1] - 128 ) * scale + 128 ); + } +} + +/* +======================== +idDxtEncoder::InsetYCoCgBBox +======================== +*/ +ID_INLINE void idDxtEncoder::InsetYCoCgBBox( byte *minColor, byte *maxColor ) const { + +#if 0 + + byte inset[4]; + + inset[0] = ( maxColor[0] - minColor[0] ) >> INSET_COLOR_SHIFT; + inset[1] = ( maxColor[1] - minColor[1] ) >> INSET_COLOR_SHIFT; + inset[3] = ( maxColor[3] - minColor[3] ) >> INSET_ALPHA_SHIFT; + + minColor[0] = ( minColor[0] + inset[0] <= 255 ) ? minColor[0] + inset[0] : 255; + minColor[1] = ( minColor[1] + inset[1] <= 255 ) ? minColor[1] + inset[1] : 255; + minColor[3] = ( minColor[3] + inset[3] <= 255 ) ? minColor[3] + inset[3] : 255; + + maxColor[0] = ( maxColor[0] >= inset[0] ) ? maxColor[0] - inset[0] : 0; + maxColor[1] = ( maxColor[1] >= inset[1] ) ? maxColor[1] - inset[1] : 0; + maxColor[3] = ( maxColor[3] >= inset[3] ) ? maxColor[3] - inset[3] : 0; + + minColor[0] = ( minColor[0] & C565_5_MASK ) | ( minColor[0] >> 5 ); + minColor[1] = ( minColor[1] & C565_6_MASK ) | ( minColor[1] >> 6 ); + + maxColor[0] = ( maxColor[0] & C565_5_MASK ) | ( maxColor[0] >> 5 ); + maxColor[1] = ( maxColor[1] & C565_6_MASK ) | ( maxColor[1] >> 6 ); + +#elif 0 + + float inset[4]; + float minf[4]; + float maxf[4]; + + for ( int i = 0; i < 4; i++ ) { + minf[i] = minColor[i] / 255.0f; + maxf[i] = maxColor[i] / 255.0f; + } + + inset[0] = ( maxf[0] - minf[0] ) / 16.0f; + inset[1] = ( maxf[1] - minf[1] ) / 16.0f; + inset[2] = ( maxf[2] - minf[2] ) / 16.0f; + inset[3] = ( maxf[3] - minf[3] ) / 32.0f; + + for ( int i = 0; i < 4; i++ ) { + minf[i] = ( minf[i] + inset[i] <= 1.0f ) ? minf[i] + inset[i] : 1.0f; + maxf[i] = ( maxf[i] >= inset[i] ) ? maxf[i] - inset[i] : 0; + } + + minColor[0] = ((int)floor( minf[0] * 31 )) & ( ( 1 << 5 ) - 1 ); + minColor[1] = ((int)floor( minf[1] * 63 )) & ( ( 1 << 6 ) - 1 ); + + maxColor[0] = ((int)ceil( maxf[0] * 31 )) & ( ( 1 << 5 ) - 1 ); + maxColor[1] = ((int)ceil( maxf[1] * 63 )) & ( ( 1 << 6 ) - 1 ); + + minColor[0] = ( minColor[0] << 3 ) | ( minColor[0] >> 2 ); + minColor[1] = ( minColor[1] << 2 ) | ( minColor[1] >> 4 ); + + maxColor[0] = ( maxColor[0] << 3 ) | ( maxColor[0] >> 2 ); + maxColor[1] = ( maxColor[1] << 2 ) | ( maxColor[1] >> 4 ); + + minColor[3] = (int)floor( minf[3] * 255.0f ); + maxColor[3] = (int)ceil( maxf[3] * 255.0f ); + +#elif 0 + + int inset[4]; + int mini[4]; + int maxi[4]; + + inset[0] = ( maxColor[0] - minColor[0] ); + inset[1] = ( maxColor[1] - minColor[1] ); + inset[3] = ( maxColor[3] - minColor[3] ); + + mini[0] = ( minColor[0] << INSET_COLOR_SHIFT ) + inset[0]; + mini[1] = ( minColor[1] << INSET_COLOR_SHIFT ) + inset[1]; + mini[3] = ( minColor[3] << INSET_ALPHA_SHIFT ) + inset[3]; + + maxi[0] = ( maxColor[0] << INSET_COLOR_SHIFT ) - inset[0]; + maxi[1] = ( maxColor[1] << INSET_COLOR_SHIFT ) - inset[1]; + maxi[3] = ( maxColor[3] << INSET_ALPHA_SHIFT ) - inset[3]; + + mini[0] = ( mini[0] - ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+3); + mini[1] = ( mini[1] - ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+2); + mini[3] = ( mini[3] - ((1<<(2))-1) ) >> (INSET_ALPHA_SHIFT+0); + + maxi[0] = ( maxi[0] + ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+3); + maxi[1] = ( maxi[1] + ((1<<(3))-1) ) >> (INSET_COLOR_SHIFT+2); + maxi[3] = ( maxi[3] + ((1<<(2))-1) ) >> (INSET_ALPHA_SHIFT+0); + + if ( mini[0] < 0 ) mini[0] = 0; + if ( mini[1] < 0 ) mini[1] = 0; + if ( mini[3] < 0 ) mini[3] = 0; + + if ( maxi[0] > 31 ) maxi[0] = 31; + if ( maxi[1] > 63 ) maxi[1] = 63; + if ( maxi[3] > 255 ) maxi[3] = 255; + + minColor[0] = ( mini[0] << 3 ) | ( mini[0] >> 2 ); + minColor[1] = ( mini[1] << 2 ) | ( mini[1] >> 4 ); + minColor[3] = mini[3]; + + maxColor[0] = ( maxi[0] << 3 ) | ( maxi[0] >> 2 ); + maxColor[1] = ( maxi[1] << 2 ) | ( maxi[1] >> 4 ); + maxColor[3] = maxi[3]; + +#elif 1 + + int inset[4]; + int mini[4]; + int maxi[4]; + + inset[0] = ( maxColor[0] - minColor[0] ) - ((1<<(INSET_COLOR_SHIFT-1))-1); + inset[1] = ( maxColor[1] - minColor[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1); + inset[3] = ( maxColor[3] - minColor[3] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1); + + mini[0] = ( ( minColor[0] << INSET_COLOR_SHIFT ) + inset[0] ) >> INSET_COLOR_SHIFT; + mini[1] = ( ( minColor[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT; + mini[3] = ( ( minColor[3] << INSET_ALPHA_SHIFT ) + inset[3] ) >> INSET_ALPHA_SHIFT; + + maxi[0] = ( ( maxColor[0] << INSET_COLOR_SHIFT ) - inset[0] ) >> INSET_COLOR_SHIFT; + maxi[1] = ( ( maxColor[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT; + maxi[3] = ( ( maxColor[3] << INSET_ALPHA_SHIFT ) - inset[3] ) >> INSET_ALPHA_SHIFT; + + mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0; + mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0; + mini[3] = ( mini[3] >= 0 ) ? mini[3] : 0; + + maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255; + maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255; + maxi[3] = ( maxi[3] <= 255 ) ? maxi[3] : 255; + + minColor[0] = byte( ( mini[0] & C565_5_MASK ) | ( mini[0] >> 5 ) ); + minColor[1] = byte( ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 ) ); + minColor[3] = byte( mini[3] ); + + maxColor[0] = byte( ( maxi[0] & C565_5_MASK ) | ( maxi[0] >> 5 ) ); + maxColor[1] = byte( ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 ) ); + maxColor[3] = byte( maxi[3] ); + +#endif +} + +/* +======================== +idDxtEncoder::InsetYCoCgAlpaBBox +======================== +*/ +ID_INLINE void idDxtEncoder::InsetYCoCgAlpaBBox( byte *minColor, byte *maxColor ) const { + int inset[4]; + int mini[4]; + int maxi[4]; + + inset[0] = ( maxColor[0] - minColor[0] ) - ((1<<(INSET_COLOR_SHIFT-1))-1); + inset[1] = ( maxColor[1] - minColor[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1); + inset[2] = ( maxColor[2] - minColor[2] ) - ((1<<(INSET_COLOR_SHIFT-1))-1); + inset[3] = ( maxColor[3] - minColor[3] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1); + + mini[0] = ( ( minColor[0] << INSET_COLOR_SHIFT ) + inset[0] ) >> INSET_COLOR_SHIFT; + mini[1] = ( ( minColor[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT; + mini[2] = ( ( minColor[2] << INSET_COLOR_SHIFT ) + inset[2] ) >> INSET_COLOR_SHIFT; + mini[3] = ( ( minColor[3] << INSET_ALPHA_SHIFT ) + inset[3] ) >> INSET_ALPHA_SHIFT; + + maxi[0] = ( ( maxColor[0] << INSET_COLOR_SHIFT ) - inset[0] ) >> INSET_COLOR_SHIFT; + maxi[1] = ( ( maxColor[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT; + maxi[2] = ( ( maxColor[2] << INSET_COLOR_SHIFT ) - inset[2] ) >> INSET_COLOR_SHIFT; + maxi[3] = ( ( maxColor[3] << INSET_ALPHA_SHIFT ) - inset[3] ) >> INSET_ALPHA_SHIFT; + + mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0; + mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0; + mini[2] = ( mini[2] >= 0 ) ? mini[2] : 0; + mini[3] = ( mini[3] >= 0 ) ? mini[3] : 0; + + maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255; + maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255; + maxi[2] = ( maxi[2] <= 255 ) ? maxi[2] : 255; + maxi[3] = ( maxi[3] <= 255 ) ? maxi[3] : 255; + + minColor[0] = byte( ( mini[0] & C565_5_MASK ) | ( mini[0] >> 5 ) ); + minColor[1] = byte( ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 ) ); + minColor[2] = byte( ( mini[2] & C565_5_MASK ) | ( mini[2] >> 5 ) ); + minColor[3] = byte( mini[3] ); + + maxColor[0] = byte( ( maxi[0] & C565_5_MASK ) | ( maxi[0] >> 5 ) ); + maxColor[1] = byte( ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 ) ); + maxColor[2] = byte( ( maxi[2] & C565_5_MASK ) | ( maxi[2] >> 5 ) ); + maxColor[3] = byte( maxi[3] ); +} + +/* +======================== +idDxtEncoder::SelectYCoCgDiagonal +======================== +*/ +void idDxtEncoder::SelectYCoCgDiagonal( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + byte side = 0; + + byte mid0 = byte( ( (int) minColor[0] + maxColor[0] + 1 ) >> 1 ); + byte mid1 = byte( ( (int) minColor[1] + maxColor[1] + 1 ) >> 1 ); + + for ( int i = 0; i < 16; i++ ) { + byte b0 = colorBlock[i*4+0] >= mid0; + byte b1 = colorBlock[i*4+1] >= mid1; + side += ( b0 ^ b1 ); + } + + byte mask = -( side > 8 ); + +#if defined NVIDIA_7X_HARDWARE_BUG_FIX + mask &= -( minColor[0] != maxColor[0] ); +#endif + + byte c0 = minColor[1]; + byte c1 = maxColor[1]; + + c0 ^= c1; + mask &= c0; + c1 ^= mask; + c0 ^= c1; + + minColor[1] = c0; + maxColor[1] = c1; +} + +/* +======================== +idDxtEncoder::CompressYCoCgDXT5Fast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressYCoCgDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + //assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, minColor, maxColor ); + ScaleYCoCg( block, minColor, maxColor ); + InsetYCoCgBBox( minColor, maxColor ); + SelectYCoCgDiagonal( block, minColor, maxColor ); + + EmitByte( maxColor[3] ); + EmitByte( minColor[3] ); + + EmitAlphaIndices( block, 3, minColor[3], maxColor[3] ); + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + // the colors are already sorted when selecting the diagonal +#endif + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitColorIndices( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::CompressYCoCgAlphaDXT5Fast + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressYCoCgAlphaDXT5Fast( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + // scale down the chroma of texels that are close to gray with low luminance + for ( int k = 0; k < 16; k++ ) { + if ( abs( block[k*4+0] - 132 ) <= 8 && + abs( block[k*4+2] - 132 ) <= 8 && + block[k*4+3] < 96 ) { + block[k*4+0] = ( block[k*4+0] - 132 ) / 2 + 132; + block[k*4+2] = ( block[k*4+2] - 132 ) / 2 + 132; + } + } + + GetMinMaxBBox( block, minColor, maxColor ); + InsetYCoCgAlpaBBox( minColor, maxColor ); + SelectColorsDiagonal( block, minColor, maxColor ); + + EmitByte( maxColor[3] ); + EmitByte( minColor[3] ); + + EmitAlphaIndices( block, 3, minColor[3], maxColor[3] ); + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + // the colors are already sorted when selecting the diagonal +#endif + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitColorIndices( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::CompressYCoCgCTX1DXT5AFast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressYCoCgCTX1DXT5AFast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, minColor, maxColor ); + SelectYCoCgDiagonal( block, minColor, maxColor ); + InsetColorsBBox( minColor, maxColor ); + + EmitByte( maxColor[3] ); + EmitByte( minColor[3] ); + + EmitAlphaIndices( block, 3, minColor[3], maxColor[3] ); + + EmitByte( maxColor[0] ); + EmitByte( maxColor[1] ); + EmitByte( minColor[0] ); + EmitByte( minColor[1] ); + + EmitCTX1Indices( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::EmitGreenIndices + +params: block - block for which to find green indices +paramO: minGreen - Min green found +paramO: maxGreen - Max green found +======================== +*/ +void idDxtEncoder::EmitGreenIndices( const byte *block, const int offset, const byte minGreen, const byte maxGreen ) { + + assert( maxGreen >= minGreen ); + + const int COLOR_RANGE = 3; + +#if 1 + + byte yb1 = ( 5 * maxGreen + 1 * minGreen + COLOR_RANGE ) / ( 2 * COLOR_RANGE ); + byte yb2 = ( 3 * maxGreen + 3 * minGreen + COLOR_RANGE ) / ( 2 * COLOR_RANGE ); + byte yb3 = ( 1 * maxGreen + 5 * minGreen + COLOR_RANGE ) / ( 2 * COLOR_RANGE ); + + unsigned int result = 0; + + block += offset; + + for ( int i = 15; i >= 0; i-- ) { + result <<= 2; + byte y = block[i*4]; + int b1 = ( y >= yb1 ); + int b2 = ( y >= yb2 ); + int b3 = ( y >= yb3 ); + int index = ( 4 - b1 - b2 - b3 ) & 3; + index ^= ( 2 > index ); + result |= index; + } + + EmitUInt( result ); + +#else + + byte green[4]; + + green[0] = maxGreen; + green[1] = minGreen; + green[2] = ( 2 * green[0] + 1 * green[1] ) / 3; + green[3] = ( 1 * green[0] + 2 * green[1] ) / 3; + + unsigned int result = 0; + + block += offset; + + for ( int i = 15; i >= 0; i-- ) { + result <<= 2; + byte y = block[i*4]; + int minDist = INT_MAX; + int index; + for ( int j = 0; j < 4; j++ ) { + int dist = abs( y - green[j] ); + if ( dist < minDist ) { + minDist = dist; + index = j; + } + } + result |= index; + } + + EmitUInt( result ); + +#endif +} + +/* +======================== +idDxtEncoder::InsetNormalsBBoxDXT5 +======================== +*/ +void idDxtEncoder::InsetNormalsBBoxDXT5( byte *minNormal, byte *maxNormal ) const { + int inset[4]; + int mini[4]; + int maxi[4]; + + inset[3] = ( maxNormal[3] - minNormal[3] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1); + inset[1] = ( maxNormal[1] - minNormal[1] ) - ((1<<(INSET_COLOR_SHIFT-1))-1); + + mini[3] = ( ( minNormal[3] << INSET_ALPHA_SHIFT ) + inset[3] ) >> INSET_ALPHA_SHIFT; + mini[1] = ( ( minNormal[1] << INSET_COLOR_SHIFT ) + inset[1] ) >> INSET_COLOR_SHIFT; + + maxi[3] = ( ( maxNormal[3] << INSET_ALPHA_SHIFT ) - inset[3] ) >> INSET_ALPHA_SHIFT; + maxi[1] = ( ( maxNormal[1] << INSET_COLOR_SHIFT ) - inset[1] ) >> INSET_COLOR_SHIFT; + + mini[3] = ( mini[3] >= 0 ) ? mini[3] : 0; + mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0; + + maxi[3] = ( maxi[3] <= 255 ) ? maxi[3] : 255; + maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255; + + minNormal[3] = byte( mini[3] ); + minNormal[1] = byte( ( mini[1] & C565_6_MASK ) | ( mini[1] >> 6 ) ); + + maxNormal[3] = byte( maxi[3] ); + maxNormal[1] = byte( ( maxi[1] & C565_6_MASK ) | ( maxi[1] >> 6 ) ); +} + +/* +======================== +idDxtEncoder::InsetNormalsBBox3Dc +======================== +*/ +void idDxtEncoder::InsetNormalsBBox3Dc( byte *minNormal, byte *maxNormal ) const { + int inset[4]; + int mini[4]; + int maxi[4]; + + inset[0] = ( maxNormal[0] - minNormal[0] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1); + inset[1] = ( maxNormal[1] - minNormal[1] ) - ((1<<(INSET_ALPHA_SHIFT-1))-1); + + mini[0] = ( ( minNormal[0] << INSET_ALPHA_SHIFT ) + inset[0] ) >> INSET_ALPHA_SHIFT; + mini[1] = ( ( minNormal[1] << INSET_ALPHA_SHIFT ) + inset[1] ) >> INSET_ALPHA_SHIFT; + + maxi[0] = ( ( maxNormal[0] << INSET_ALPHA_SHIFT ) - inset[0] ) >> INSET_ALPHA_SHIFT; + maxi[1] = ( ( maxNormal[1] << INSET_ALPHA_SHIFT ) - inset[1] ) >> INSET_ALPHA_SHIFT; + + mini[0] = ( mini[0] >= 0 ) ? mini[0] : 0; + mini[1] = ( mini[1] >= 0 ) ? mini[1] : 0; + + maxi[0] = ( maxi[0] <= 255 ) ? maxi[0] : 255; + maxi[1] = ( maxi[1] <= 255 ) ? maxi[1] : 255; + + minNormal[0] = (byte)mini[0]; + minNormal[1] = (byte)mini[1]; + + maxNormal[0] = (byte)maxi[0]; + maxNormal[1] = (byte)maxi[1]; +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT5Fast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXT5Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte normal1[4] ); + ALIGN16( byte normal2[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, normal1, normal2 ); + InsetNormalsBBoxDXT5( normal1, normal2 ); + + // Write out Nx into alpha channel. + EmitByte( normal2[3] ); + EmitByte( normal1[3] ); + EmitAlphaIndices( block, 3, normal1[3], normal2[3] ); + + // Write out Ny into green channel. + EmitUShort( ColorTo565( block[0], normal2[1], block[2] ) ); + EmitUShort( ColorTo565( block[0], normal1[1], block[2] ) ); + EmitGreenIndices( block, 1, normal1[1], normal2[1] ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::CompressImageDXN1Fast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXN1Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte min[4] ); + ALIGN16( byte max[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, min, max ); + InsetNormalsBBox3Dc( min, max ); + + // Write out an alpha channel. + EmitByte( max[0] ); + EmitByte( min[0] ); + EmitAlphaIndices( block, 0, min[0], max[0] ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXN2Fast_Generic + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXN2Fast_Generic( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte normal1[4] ); + ALIGN16( byte normal2[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + + ExtractBlock( inBuf + i * 4, width, block ); + + GetMinMaxBBox( block, normal1, normal2 ); + InsetNormalsBBox3Dc( normal1, normal2 ); + + // Write out Nx as an alpha channel. + EmitByte( normal2[0] ); + EmitByte( normal1[0] ); + EmitAlphaIndices( block, 0, normal1[0], normal2[0] ); + + // Write out Ny as an alpha channel. + EmitByte( normal2[1] ); + EmitByte( normal1[1] ); + EmitAlphaIndices( block, 1, normal1[1], normal2[1] ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::DecodeDXNAlphaValues +======================== +*/ +void idDxtEncoder::DecodeDXNAlphaValues( const byte *inBuf, byte *values ) { + int i; + unsigned int indices; + byte alphas[8]; + + if ( inBuf[0] <= inBuf[1] ) { + alphas[0] = inBuf[0]; + alphas[1] = inBuf[1]; + alphas[2] = ( 4 * alphas[0] + 1 * alphas[1] ) / 5; + alphas[3] = ( 3 * alphas[0] + 2 * alphas[1] ) / 5; + alphas[4] = ( 2 * alphas[0] + 3 * alphas[1] ) / 5; + alphas[5] = ( 1 * alphas[0] + 4 * alphas[1] ) / 5; + alphas[6] = 0; + alphas[7] = 255; + } else { + alphas[0] = inBuf[0]; + alphas[1] = inBuf[1]; + alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7; + alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7; + alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7; + alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7; + alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7; + alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7; + } + + indices = (int)inBuf[2] | ( (int)inBuf[3] << 8 ) | ( (int)inBuf[4] << 16 ); + for ( i = 0; i < 8; i++ ) { + values[i] = alphas[indices & 7]; + indices >>= 3; + } + + indices = (int)inBuf[5] | ( (int)inBuf[6] << 8 ) | ( (int)inBuf[7] << 16 ); + for ( i = 8; i < 16; i++ ) { + values[i] = alphas[indices & 7]; + indices >>= 3; + } +} + +/* +======================== +idDxtEncoder::EncodeNormalRGBIndices + +params: values - 16 normal block for which to find normal Y indices +paramO: min - Min grayscale value +paramO: max - Max grayscale value +======================== +*/ +void idDxtEncoder::EncodeNormalRGBIndices( byte *outBuf, const byte min, const byte max, const byte *values ) { + + const int COLOR_RANGE = 3; + + byte maskedMin, maskedMax, mid, yb1, yb2, yb3; + + maskedMax = max & C565_6_MASK; + maskedMin = min & C565_6_MASK; + mid = ( maskedMax - maskedMin ) / ( 2 * COLOR_RANGE ); + + yb1 = maskedMax - mid; + yb2 = ( 2 * maskedMax + 1 * maskedMin ) / COLOR_RANGE - mid; + yb3 = ( 1 * maskedMax + 2 * maskedMin ) / COLOR_RANGE - mid; + + unsigned int result = 0; + + for ( int i = 15; i >= 0; i-- ) { + result <<= 2; + byte y = values[i]; + int b1 = ( y >= yb1 ); + int b2 = ( y >= yb2 ); + int b3 = ( y >= yb3 ); + int index = ( 4 - b1 - b2 - b3 ) & 3; + index ^= ( 2 > index ); + result |= index; + } + + unsigned short maskedMax5 = (max & C565_5_MASK) >> 3; + unsigned short maskedMin5 = (min & C565_5_MASK) >> 3; + + unsigned short smax = (maskedMax5 << 11) | (maskedMax << 3) | maskedMax5; + unsigned short smin = (maskedMin5 << 11) | (maskedMin << 3) | maskedMin5; + + outBuf[0] = byte( ( smax >> 0 ) & 0xFF ); + outBuf[1] = byte( ( smax >> 8 ) & 0xFF ); + outBuf[2] = byte( ( smin >> 0 ) & 0xFF ); + outBuf[3] = byte( ( smin >> 8 ) & 0xFF ); + + outBuf[4] = byte( ( result >> 0 ) & 0xFF ); + outBuf[5] = byte( ( result >> 8 ) & 0xFF ); + outBuf[6] = byte( ( result >> 16 ) & 0xFF ); + outBuf[7] = byte( ( result >> 24 ) & 0xFF ); +} + +/* +======================== +idDxtEncoder::ConvertNormalMapDXN2_DXT5 + +params: inBuf - normal map compressed in DXN2 format +paramO: outBuf - result of compression in DXT5 format +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::ConvertNormalMapDXN2_DXT5( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte values[16] ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + assert( 0 ); + return; + } + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4, inBuf += 16, outBuf += 16 ) { + + // decode normal Y stored as a DXT5 alpha channel + DecodeDXNAlphaValues( inBuf + 0, values ); + + // copy normal X + memcpy( outBuf + 0, inBuf + 8, 8 ); + + // get the min/max Y + byte minNormalY = 255; + byte maxNormalY = 0; + for ( int i = 0; i < 16; i++ ) { + if ( values[i] < minNormalY ) { + minNormalY = values[i]; + } + if ( values[i] > maxNormalY ) { + maxNormalY = values[i]; + } + } + + // encode normal Y into DXT5 color channels + EncodeNormalRGBIndices( outBuf + 8, minNormalY, maxNormalY, values ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::DecodeNormalYValues +======================== +*/ +void idDxtEncoder::DecodeNormalYValues( const byte *inBuf, byte &min, byte &max, byte *values ) { + int i; + unsigned int indexes; + unsigned short normal0, normal1; + byte normalsY[4]; + + normal0 = inBuf[0] | (inBuf[1] << 8); + normal1 = inBuf[2] | (inBuf[3] << 8); + + assert( normal0 >= normal1 ); + + normalsY[0] = GreenFrom565( normal0 ); + normalsY[1] = GreenFrom565( normal1 ); + normalsY[2] = ( 2 * normalsY[0] + 1 * normalsY[1] ) / 3; + normalsY[3] = ( 1 * normalsY[0] + 2 * normalsY[1] ) / 3; + + indexes = (unsigned int)inBuf[4] | ((unsigned int)inBuf[5]<<8) | ((unsigned int)inBuf[6]<<16) | ((unsigned int)inBuf[7]<<24); + for ( i = 0; i < 16; i++ ) { + values[i] = normalsY[indexes & 3]; + indexes >>= 2; + } + + max = normalsY[0]; + min = normalsY[1]; +} + +/* +======================== +idDxtEncoder::EncodeDXNAlphaValues +======================== +*/ +void idDxtEncoder::EncodeDXNAlphaValues( byte *outBuf, const byte min, const byte max, const byte *values ) { + int i; + byte alphas[8]; + int j; + unsigned int indexes[16]; + + alphas[0] = max; + alphas[1] = min; + alphas[2] = ( 6 * alphas[0] + 1 * alphas[1] ) / 7; + alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] ) / 7; + alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] ) / 7; + alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] ) / 7; + alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] ) / 7; + alphas[7] = ( 1 * alphas[0] + 6 * alphas[1] ) / 7; + + int error = 0; + for ( i = 0; i < 16; i++ ) { + int minDist = MAX_TYPE( int ); + byte a = values[i]; + for ( j = 0; j < 8; j++ ) { + int dist = AlphaDistance( a, alphas[j] ); + if ( dist < minDist ) { + minDist = dist; + indexes[i] = j; + } + } + error += minDist; + } + + outBuf[0] = max; + outBuf[1] = min; + + outBuf[2] = byte( (indexes[ 0] >> 0) | (indexes[ 1] << 3) | (indexes[ 2] << 6) ); + outBuf[3] = byte( (indexes[ 2] >> 2) | (indexes[ 3] << 1) | (indexes[ 4] << 4) | (indexes[ 5] << 7) ); + outBuf[4] = byte( (indexes[ 5] >> 1) | (indexes[ 6] << 2) | (indexes[ 7] << 5) ); + + outBuf[5] = byte( (indexes[ 8] >> 0) | (indexes[ 9] << 3) | (indexes[10] << 6) ); + outBuf[6] = byte( (indexes[10] >> 2) | (indexes[11] << 1) | (indexes[12] << 4) | (indexes[13] << 7) ); + outBuf[7] = byte( (indexes[13] >> 1) | (indexes[14] << 2) | (indexes[15] << 5) ); +} + +/* +======================== +idDxtEncoder::ConvertNormalMapDXT5_DXN2 + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::ConvertNormalMapDXT5_DXN2( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte values[16] ); + byte minNormalY, maxNormalY; + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + assert( 0 ); + return; + } + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4, inBuf += 16, outBuf += 16 ) { + + // decode normal Y stored as a DXT5 alpha channel + DecodeNormalYValues( inBuf + 8, minNormalY, maxNormalY, values ); + + memcpy( outBuf + 8, inBuf + 0, 8 ); + + // encode normal Y into DXT5 green channel + EncodeDXNAlphaValues( outBuf + 0, minNormalY, maxNormalY, values ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} + +/* +======================== +idDxtEncoder::ConvertImageDXN1_DXT1 + +params: inBuf - normal map compressed in DXN1 format +paramO: outBuf - result of compression in DXT1 format +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::ConvertImageDXN1_DXT1( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte values[16] ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + if ( width > 4 && ( width & 3 ) != 0 ) { + return; + } + if ( height > 4 && ( height & 3 ) != 0 ) { + return; + } + + if ( width < 4 || height < 4 ) { + assert( 0 ); + return; + } + + for ( int j = 0; j < height; j += 4 ) { + for ( int i = 0; i < width; i += 4, inBuf += 8, outBuf += 8 ) { + + // decode single channel stored as a DXT5 alpha channel + DecodeDXNAlphaValues( inBuf + 0, values ); + + // get the min/max + byte min = 255; + byte max = 0; + for ( int i = 0; i < 16; i++ ) { + if ( values[i] < min ) { + min = values[i]; + } + if ( values[i] > max ) { + max = values[i]; + } + } + + // encode single channel into DXT1 + EncodeNormalRGBIndices( outBuf + 0, min, max, values ); + } + outData += dstPadding; + inBuf += srcPadding; + } +} diff --git a/neo/renderer/DXT/DXTEncoder_SSE2.cpp b/neo/renderer/DXT/DXTEncoder_SSE2.cpp new file mode 100644 index 00000000..e7f73657 --- /dev/null +++ b/neo/renderer/DXT/DXTEncoder_SSE2.cpp @@ -0,0 +1,1580 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +================================================================================================ +Contains the DxtEncoder implementation for SSE2. +================================================================================================ +*/ +#pragma hdrstop +#include "DXTCodec_local.h" +#include "DXTCodec.h" + + +//#define TEST_COMPRESSION +#ifdef TEST_COMPRESSION +#include +#endif + +#define INSET_COLOR_SHIFT 4 // inset the bounding box with ( range >> shift ) +#define INSET_ALPHA_SHIFT 5 // inset alpha channel + +#define C565_5_MASK 0xF8 // 0xFF minus last three bits +#define C565_6_MASK 0xFC // 0xFF minus last two bits + +#define NVIDIA_7X_HARDWARE_BUG_FIX // keep the DXT5 colors sorted as: max, min + +#if !defined( R_SHUFFLE_D ) +#define R_SHUFFLE_D( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) +#endif + +typedef uint16 word; +typedef uint32 dword; + +ALIGN16( static __m128i SIMD_SSE2_zero ) = { 0, 0, 0, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_byte_mask[4] ) = { 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF }; +ALIGN16( static dword SIMD_SSE2_dword_word_mask[4] ) = { 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF }; +ALIGN16( static dword SIMD_SSE2_dword_red_mask[4] ) = { 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF }; +ALIGN16( static dword SIMD_SSE2_dword_green_mask[4] ) = { 0x0000FF00, 0x0000FF00, 0x0000FF00, 0x0000FF00 }; +ALIGN16( static dword SIMD_SSE2_dword_blue_mask[4] ) = { 0x00FF0000, 0x00FF0000, 0x00FF0000, 0x00FF0000 }; +ALIGN16( static dword SIMD_SSE2_dword_colorMask_1010[4] ) = { 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000 }; +ALIGN16( static dword SIMD_SSE2_dword_colorMask_0100[4] ) = { 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask0[4] ) = { 7<<0, 0, 7<<0, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask1[4] ) = { 7<<3, 0, 7<<3, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask2[4] ) = { 7<<6, 0, 7<<6, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask3[4] ) = { 7<<9, 0, 7<<9, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask4[4] ) = { 7<<12, 0, 7<<12, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask5[4] ) = { 7<<15, 0, 7<<15, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask6[4] ) = { 7<<18, 0, 7<<18, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_alpha_bit_mask7[4] ) = { 7<<21, 0, 7<<21, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask0[4] ) = { 3<<0, 0, 3<<0, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask1[4] ) = { 3<<2, 0, 3<<2, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask2[4] ) = { 3<<4, 0, 3<<4, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask3[4] ) = { 3<<6, 0, 3<<6, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask4[4] ) = { 3<<8, 0, 3<<8, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask5[4] ) = { 3<<10, 0, 3<<10, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask6[4] ) = { 3<<12, 0, 3<<12, 0 }; +ALIGN16( static dword SIMD_SSE2_dword_color_bit_mask7[4] ) = { 3<<14, 0, 3<<14, 0 }; +ALIGN16( static word SIMD_SSE2_word_0[8] ) = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; +ALIGN16( static word SIMD_SSE2_word_1[8] ) = { 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001 }; +ALIGN16( static word SIMD_SSE2_word_2[8] ) = { 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002 }; +ALIGN16( static word SIMD_SSE2_word_3[8] ) = { 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003, 0x0003 }; +ALIGN16( static word SIMD_SSE2_word_7[8] ) = { 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007 }; +ALIGN16( static word SIMD_SSE2_word_8[8] ) = { 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008 }; +ALIGN16( static word SIMD_SSE2_word_31[8] ) = { 31, 31, 31, 31, 31, 31, 31, 31 }; +ALIGN16( static word SIMD_SSE2_word_63[8] ) = { 63, 63, 63, 63, 63, 63, 63, 63 }; +ALIGN16( static word SIMD_SSE2_word_127[8] ) = { 127, 127, 127, 127, 127, 127, 127, 127 }; +ALIGN16( static word SIMD_SSE2_word_255[8] ) = { 255, 255, 255, 255, 255, 255, 255, 255 }; +ALIGN16( static word SIMD_SSE2_word_center_128[8] ) = { 128, 128, 0, 0, 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_div_by_3[8] ) = { (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1, (1<<16)/3+1 }; +ALIGN16( static word SIMD_SSE2_word_div_by_6[8] ) = { (1<<16)/6+1, (1<<16)/6+1, (1<<16)/6+1, (1<<16)/6+1, (1<<16)/6+1, (1<<16)/6+1, (1<<16)/6+1, (1<<16)/6+1 }; +ALIGN16( static word SIMD_SSE2_word_div_by_14[8] ) = { (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1, (1<<16)/14+1 }; +ALIGN16( static word SIMD_SSE2_word_scale_7_9_11_13[8] ) = { 7, 7, 9, 9, 11, 11, 13, 13 }; +ALIGN16( static word SIMD_SSE2_word_scale_7_5_3_1[8] ) = { 7, 7, 5, 5, 3, 3, 1, 1 }; +ALIGN16( static word SIMD_SSE2_word_scale_5_3_1[8] ) = { 5, 3, 1, 0, 5, 3, 1, 0 }; +ALIGN16( static word SIMD_SSE2_word_scale_1_3_5[8] ) = { 1, 3, 5, 0, 1, 3, 5, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetShift[8] ) = { 1 << ( 16 - INSET_COLOR_SHIFT ), 1 << ( 16 - INSET_COLOR_SHIFT ), 1 << ( 16 - INSET_COLOR_SHIFT ), 1 << ( 16 - INSET_ALPHA_SHIFT ), 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetYCoCgRound[8] ) = { ((1<<(INSET_COLOR_SHIFT-1))-1), ((1<<(INSET_COLOR_SHIFT-1))-1), ((1<<(INSET_COLOR_SHIFT-1))-1), ((1<<(INSET_ALPHA_SHIFT-1))-1), 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetYCoCgMask[8] ) = { 0xFFFF, 0xFFFF, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0xFFFF }; +ALIGN16( static word SIMD_SSE2_word_insetYCoCgShiftUp[8] ) = { 1 << INSET_COLOR_SHIFT, 1 << INSET_COLOR_SHIFT, 1 << INSET_COLOR_SHIFT, 1 << INSET_ALPHA_SHIFT, 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetYCoCgShiftDown[8] ) = { 1 << ( 16 - INSET_COLOR_SHIFT ), 1 << ( 16 - INSET_COLOR_SHIFT ), 1 << ( 16 - INSET_COLOR_SHIFT ), 1 << ( 16 - INSET_ALPHA_SHIFT ), 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetYCoCgQuantMask[8] ) = { C565_5_MASK, C565_6_MASK, C565_5_MASK, 0xFF, C565_5_MASK, C565_6_MASK, C565_5_MASK, 0xFF }; +ALIGN16( static word SIMD_SSE2_word_insetYCoCgRep[8] ) = { 1 << ( 16 - 5 ), 1 << ( 16 - 6 ), 1 << ( 16 - 5 ), 0, 1 << ( 16 - 5 ), 1 << ( 16 - 6 ), 1 << ( 16 - 5 ), 0 }; +ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5Round[8] ) = { 0, ((1<<(INSET_COLOR_SHIFT-1))-1), 0, ((1<<(INSET_ALPHA_SHIFT-1))-1), 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5Mask[8] ) = { 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000 }; +ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5ShiftUp[8] ) = { 1, 1 << INSET_COLOR_SHIFT, 1, 1 << INSET_ALPHA_SHIFT, 1, 1, 1, 1 }; +ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5ShiftDown[8] ) = { 0, 1 << ( 16 - INSET_COLOR_SHIFT ), 0, 1 << ( 16 - INSET_ALPHA_SHIFT ), 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5QuantMask[8] ) = { 0xFF, C565_6_MASK, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +ALIGN16( static word SIMD_SSE2_word_insetNormalDXT5Rep[8] ) = { 0, 1 << ( 16 - 6 ), 0, 0, 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetNormal3DcRound[8] ) = { ((1<<(INSET_ALPHA_SHIFT-1))-1), ((1<<(INSET_ALPHA_SHIFT-1))-1), 0, 0, 0, 0, 0, 0 }; +ALIGN16( static word SIMD_SSE2_word_insetNormal3DcMask[8] ) = { 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; +ALIGN16( static word SIMD_SSE2_word_insetNormal3DcShiftUp[8] ) = { 1 << INSET_ALPHA_SHIFT, 1 << INSET_ALPHA_SHIFT, 1, 1, 1, 1, 1, 1 }; +ALIGN16( static word SIMD_SSE2_word_insetNormal3DcShiftDown[8] ) = { 1 << ( 16 - INSET_ALPHA_SHIFT ), 1 << ( 16 - INSET_ALPHA_SHIFT ), 0, 0, 0, 0, 0, 0 }; +ALIGN16( static byte SIMD_SSE2_byte_0[16] ) = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_1[16] ) = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; +ALIGN16( static byte SIMD_SSE2_byte_2[16] ) = { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }; +ALIGN16( static byte SIMD_SSE2_byte_3[16] ) = { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }; +ALIGN16( static byte SIMD_SSE2_byte_4[16] ) = { 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04 }; +ALIGN16( static byte SIMD_SSE2_byte_7[16] ) = { 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07 }; +ALIGN16( static byte SIMD_SSE2_byte_8[16] ) = { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 }; +ALIGN16( static byte SIMD_SSE2_byte_not[16] ) = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +ALIGN16( static byte SIMD_SSE2_byte_colorMask[16] ) = { C565_5_MASK, C565_6_MASK, C565_5_MASK, 0x00, 0x00, 0x00, 0x00, 0x00, C565_5_MASK, C565_6_MASK, C565_5_MASK, 0x00, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_colorMask2[16] ) = { 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_ctx1Mask[16] ) = { 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_diagonalMask[16] ) = { 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_scale_mask0[16] ) = { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF }; +ALIGN16( static byte SIMD_SSE2_byte_scale_mask1[16] ) = { 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_scale_mask2[16] ) = { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_scale_mask3[16] ) = { 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_scale_mask4[16] ) = { 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 }; +ALIGN16( static byte SIMD_SSE2_byte_minus_128_0[16] ) = { (byte)-128, (byte)-128, 0, 0, (byte)-128, (byte)-128, 0, 0, (byte)-128, (byte)-128, 0, 0, (byte)-128, (byte)-128, 0, 0 }; + +/* +======================== +idDxtEncoder::ExtractBlock_SSE2 + +params: inPtr - input image, 4 bytes per pixel +paramO: colorBlock - 4*4 output tile, 4 bytes per pixel +======================== +*/ +ID_INLINE void idDxtEncoder::ExtractBlock_SSE2( const byte * inPtr, int width, byte * colorBlock ) const { + *((__m128i *)(&colorBlock[ 0])) = _mm_load_si128( (__m128i *)( inPtr + width * 4 * 0 ) ); + *((__m128i *)(&colorBlock[16])) = _mm_load_si128( (__m128i *)( inPtr + width * 4 * 1 ) ); + *((__m128i *)(&colorBlock[32])) = _mm_load_si128( (__m128i *)( inPtr + width * 4 * 2 ) ); + *((__m128i *)(&colorBlock[48])) = _mm_load_si128( (__m128i *)( inPtr + width * 4 * 3 ) ); +} + +/* +======================== +idDxtEncoder::GetMinMaxBBox_SSE2 + +Takes the extents of the bounding box of the colors in the 4x4 block. + +params: colorBlock - 4*4 input tile, 4 bytes per pixel +paramO: minColor - Min 4 byte output color +paramO: maxColor - Max 4 byte output color +======================== +*/ +ID_INLINE void idDxtEncoder::GetMinMaxBBox_SSE2( const byte * colorBlock, byte * minColor, byte * maxColor ) const { + __m128i block0 = *((__m128i *)(&colorBlock[ 0])); + __m128i block1 = *((__m128i *)(&colorBlock[16])); + __m128i block2 = *((__m128i *)(&colorBlock[32])); + __m128i block3 = *((__m128i *)(&colorBlock[48])); + + __m128i max1 = _mm_max_epu8( block0, block1 ); + __m128i min1 = _mm_min_epu8( block0, block1 ); + __m128i max2 = _mm_max_epu8( block2, block3 ); + __m128i min2 = _mm_min_epu8( block2, block3 ); + + __m128i max3 = _mm_max_epu8( max1, max2 ); + __m128i min3 = _mm_min_epu8( min1, min2 ); + + __m128i max4 = _mm_shuffle_epi32( max3, R_SHUFFLE_D( 2, 3, 2, 3 ) ); + __m128i min4 = _mm_shuffle_epi32( min3, R_SHUFFLE_D( 2, 3, 2, 3 ) ); + + __m128i max5 = _mm_max_epu8( max3, max4 ); + __m128i min5 = _mm_min_epu8( min3, min4 ); + + __m128i max6 = _mm_shufflelo_epi16( max5, R_SHUFFLE_D( 2, 3, 2, 3 ) ); + __m128i min6 = _mm_shufflelo_epi16( min5, R_SHUFFLE_D( 2, 3, 2, 3 ) ); + + max6 = _mm_max_epu8( max5, max6 ); + min6 = _mm_min_epu8( min5, min6 ); + + *((int *)maxColor) = _mm_cvtsi128_si32( max6 ); + *((int *)minColor) = _mm_cvtsi128_si32( min6 ); +} + +/* +======================== +idDxtEncoder::InsetColorsBBox_SSE2 +======================== +*/ +ID_INLINE void idDxtEncoder::InsetColorsBBox_SSE2( byte * minColor, byte * maxColor ) const { + __m128i min = _mm_cvtsi32_si128( *(int *)minColor ); + __m128i max = _mm_cvtsi32_si128( *(int *)maxColor ); + + __m128i xmm0 = _mm_unpacklo_epi8( min, *(__m128i *)SIMD_SSE2_byte_0 ); + __m128i xmm1 = _mm_unpacklo_epi8( max, *(__m128i *)SIMD_SSE2_byte_0 ); + + __m128i xmm2 = _mm_sub_epi16( xmm1, xmm0 ); + + xmm2 = _mm_mulhi_epi16( xmm2, *(__m128i *)SIMD_SSE2_word_insetShift ); + + xmm0 = _mm_add_epi16( xmm0, xmm2 ); + xmm1 = _mm_sub_epi16( xmm1, xmm2 ); + + xmm0 = _mm_packus_epi16( xmm0, xmm0 ); + xmm1 = _mm_packus_epi16( xmm1, xmm1 ); + + *((int *)minColor) = _mm_cvtsi128_si32( xmm0 ); + *((int *)maxColor) = _mm_cvtsi128_si32( xmm1 ); +} + +/* +======================== +idDxtEncoder::EmitColorIndices_SSE2 + +params: colorBlock - 16 pixel block for which to find color indices +paramO: minColor - Min alpha found +paramO: maxColor - Max alpha found +return: 4 byte color index block +======================== +*/ +void idDxtEncoder::EmitColorIndices_SSE2( const byte * colorBlock, const byte * minColor_, const byte * maxColor_ ) { + __m128c zero = SIMD_SSE2_zero; + __m128c result = SIMD_SSE2_zero; + __m128c color0, color1, color2, color3; + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __m128c minColor = _mm_cvtsi32_si128( *(int *)minColor_ ); + __m128c maxColor = _mm_cvtsi32_si128( *(int *)maxColor_ ); + __m128c blocka[2], blockb[2]; + blocka[0] = *((__m128i *)(&colorBlock[ 0])); + blocka[1] = *((__m128i *)(&colorBlock[32])); + blockb[0] = *((__m128i *)(&colorBlock[16])); + blockb[1] = *((__m128i *)(&colorBlock[48])); + + temp0 = _mm_and_si128( maxColor, (const __m128i &)SIMD_SSE2_byte_colorMask ); + temp0 = _mm_unpacklo_epi8( temp0, zero ); + temp4 = _mm_shufflelo_epi16( temp0, R_SHUFFLE_D( 0, 3, 2, 3 ) ); + temp5 = _mm_shufflelo_epi16( temp0, R_SHUFFLE_D( 3, 1, 3, 3 ) ); + temp4 = _mm_srli_epi16( temp4, 5 ); + temp5 = _mm_srli_epi16( temp5, 6 ); + temp0 = _mm_or_si128( temp0, temp4 ); + temp0 = _mm_or_si128( temp0, temp5 ); + + + temp1 = _mm_and_si128( minColor, (const __m128i &)SIMD_SSE2_byte_colorMask ); + temp1 = _mm_unpacklo_epi8( temp1, zero ); + temp4 = _mm_shufflelo_epi16( temp1, R_SHUFFLE_D( 0, 3, 2, 3 ) ); + temp5 = _mm_shufflelo_epi16( temp1, R_SHUFFLE_D( 3, 1, 3, 3 ) ); + temp4 = _mm_srli_epi16( temp4, 5 ); + temp5 = _mm_srli_epi16( temp5, 6 ); + temp1 = _mm_or_si128( temp1, temp4 ); + temp1 = _mm_or_si128( temp1, temp5 ); + + + temp2 = _mm_packus_epi16( temp0, zero ); + color0 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp6 = _mm_add_epi16( temp0, temp0 ); + temp6 = _mm_add_epi16( temp6, temp1 ); + temp6 = _mm_mulhi_epi16( temp6, (const __m128i &)SIMD_SSE2_word_div_by_3 ); // * ( ( 1 << 16 ) / 3 + 1 ) ) >> 16 + temp6 = _mm_packus_epi16( temp6, zero ); + color2 = _mm_shuffle_epi32( temp6, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp3 = _mm_packus_epi16( temp1, zero ); + color1 = _mm_shuffle_epi32( temp3, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp1 = _mm_add_epi16( temp1, temp1 ); + temp0 = _mm_add_epi16( temp0, temp1 ); + temp0 = _mm_mulhi_epi16( temp0, (const __m128i &)SIMD_SSE2_word_div_by_3 ); // * ( ( 1 << 16 ) / 3 + 1 ) ) >> 16 + temp0 = _mm_packus_epi16( temp0, zero ); + color3 = _mm_shuffle_epi32( temp0, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + for ( int i = 1; i >= 0; i-- ) { + // Load block + temp3 = _mm_shuffle_epi32( blocka[i], R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp5 = _mm_shuffle_ps( blocka[i], zero, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + temp0 = _mm_sad_epu8( temp3, color0 ); + temp6 = _mm_sad_epu8( temp5, color0 ); + temp0 = _mm_packs_epi32( temp0, temp6 ); + + temp1 = _mm_sad_epu8( temp3, color1 ); + temp6 = _mm_sad_epu8( temp5, color1 ); + temp1 = _mm_packs_epi32( temp1, temp6 ); + + temp2 = _mm_sad_epu8( temp3, color2 ); + temp6 = _mm_sad_epu8( temp5, color2 ); + temp2 = _mm_packs_epi32( temp2, temp6 ); + + temp3 = _mm_sad_epu8( temp3, color3 ); + temp5 = _mm_sad_epu8( temp5, color3 ); + temp3 = _mm_packs_epi32( temp3, temp5 ); + + // Load block + temp4 = _mm_shuffle_epi32( blockb[i], R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp5 = _mm_shuffle_ps( blockb[i], zero, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + temp6 = _mm_sad_epu8( temp4, color0 ); + temp7 = _mm_sad_epu8( temp5, color0 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp0 = _mm_packs_epi32( temp0, temp6 ); // d0 + + temp6 = _mm_sad_epu8( temp4, color1 ); + temp7 = _mm_sad_epu8( temp5, color1 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp1 = _mm_packs_epi32( temp1, temp6 ); // d1 + + temp6 = _mm_sad_epu8( temp4, color2 ); + temp7 = _mm_sad_epu8( temp5, color2 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp2 = _mm_packs_epi32( temp2, temp6 ); // d2 + + temp4 = _mm_sad_epu8( temp4, color3 ); + temp5 = _mm_sad_epu8( temp5, color3 ); + temp4 = _mm_packs_epi32( temp4, temp5 ); + temp3 = _mm_packs_epi32( temp3, temp4 ); // d3 + + temp7 = _mm_slli_epi32( result, 16 ); + + temp4 = _mm_cmpgt_epi16( temp0, temp2 ); // b2 + temp5 = _mm_cmpgt_epi16( temp1, temp3 ); // b3 + temp0 = _mm_cmpgt_epi16( temp0, temp3 ); // b0 + temp1 = _mm_cmpgt_epi16( temp1, temp2 ); // b1 + temp2 = _mm_cmpgt_epi16( temp2, temp3 ); // b4 + + temp4 = _mm_and_si128( temp4, temp1 ); // x0 + temp5 = _mm_and_si128( temp5, temp0 ); // x1 + temp2 = _mm_and_si128( temp2, temp0 ); // x2 + temp4 = _mm_or_si128( temp4, temp5 ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_word_1 ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_word_2 ); + temp2 = _mm_or_si128( temp2, temp4 ); + + temp5 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp2 = _mm_unpacklo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_0 ); + temp5 = _mm_unpacklo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_0 ); + temp5 = _mm_slli_epi32( temp5, 8 ); + temp7 = _mm_or_si128( temp7, temp5 ); + result = _mm_or_si128( temp7, temp2 ); + } + + temp4 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 1, 2, 3, 0 ) ); + temp5 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp6 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 3, 0, 1, 2 ) ); + temp4 = _mm_slli_epi32( temp4, 2 ); + temp5 = _mm_slli_epi32( temp5, 4 ); + temp6 = _mm_slli_epi32( temp6, 6 ); + temp7 = _mm_or_si128( result, temp4 ); + temp7 = _mm_or_si128( temp7, temp5 ); + temp7 = _mm_or_si128( temp7, temp6 ); + + unsigned int out = _mm_cvtsi128_si32( temp7 ); + EmitUInt( out ); +} + +/* +======================== +idDxtEncoder::EmitColorAlphaIndices_SSE2 + +params: colorBlock - 16 pixel block for which find color indexes +paramO: minColor - Min color found +paramO: maxColor - Max color found +return: 4 byte color index block +======================== +*/ +void idDxtEncoder::EmitColorAlphaIndices_SSE2( const byte *colorBlock, const byte *minColor_, const byte *maxColor_ ) { + __m128c zero = SIMD_SSE2_zero; + __m128c result = SIMD_SSE2_zero; + __m128c color0, color1, color2; + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __m128c minColor = _mm_cvtsi32_si128( *(int *)minColor_ ); + __m128c maxColor = _mm_cvtsi32_si128( *(int *)maxColor_ ); + __m128c blocka[2], blockb[2]; + blocka[0] = *((__m128i *)(&colorBlock[ 0])); + blocka[1] = *((__m128i *)(&colorBlock[32])); + blockb[0] = *((__m128i *)(&colorBlock[16])); + blockb[1] = *((__m128i *)(&colorBlock[48])); + + temp0 = _mm_and_si128( maxColor, *(__m128c*)SIMD_SSE2_byte_colorMask ); + temp0 = _mm_unpacklo_epi8( temp0, zero ); + temp4 = _mm_shufflelo_epi16( temp0, R_SHUFFLE_D( 0, 3, 2, 3 ) ); + temp5 = _mm_shufflelo_epi16( temp0, R_SHUFFLE_D( 3, 1, 3, 3 ) ); + temp4 = _mm_srli_epi16( temp4, 5 ); + temp5 = _mm_srli_epi16( temp5, 6 ); + temp0 = _mm_or_si128( temp0, temp4 ); + temp0 = _mm_or_si128( temp0, temp5 ); + + temp1 = _mm_and_si128( minColor, *(__m128c*)SIMD_SSE2_byte_colorMask ); + temp1 = _mm_unpacklo_epi8( temp1, zero ); + temp4 = _mm_shufflelo_epi16( temp1, R_SHUFFLE_D( 0, 3, 2, 3 ) ); + temp5 = _mm_shufflelo_epi16( temp1, R_SHUFFLE_D( 3, 1, 3, 3 ) ); + temp4 = _mm_srli_epi16( temp4, 5 ); + temp5 = _mm_srli_epi16( temp5, 6 ); + temp1 = _mm_or_si128( temp1, temp4 ); + temp1 = _mm_or_si128( temp1, temp5 ); + + temp2 = _mm_packus_epi16( temp0, zero ); + color0 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp6 = _mm_add_epi16( temp0, temp0 ); + temp6 = _mm_srli_epi16( temp6, 1 ); // diff from color + temp6 = _mm_packus_epi16( temp6, zero ); + color2 = _mm_shuffle_epi32( temp6, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp3 = _mm_packus_epi16( temp1, zero ); + color1 = _mm_shuffle_epi32( temp3, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + // not used + //color3 = zero; + + for ( int i = 1; i >= 0; i-- ) { + // Load block + temp3 = _mm_shuffle_epi32( blocka[i], R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp5 = _mm_shuffle_ps( blocka[i], zero, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + temp0 = _mm_sad_epu8( temp3, color0 ); + temp6 = _mm_sad_epu8( temp5, color0 ); + temp0 = _mm_packs_epi32( temp0, temp6 ); + + temp1 = _mm_sad_epu8( temp3, color1 ); + temp6 = _mm_sad_epu8( temp5, color1 ); + temp1 = _mm_packs_epi32( temp1, temp6 ); + + temp2 = _mm_sad_epu8( temp3, color2 ); + temp6 = _mm_sad_epu8( temp5, color2 ); + temp2 = _mm_packs_epi32( temp2, temp6 ); + + + // diff from color + temp3 = _mm_shuffle_ps( temp3, temp5, R_SHUFFLE_D( 0, 2, 0, 2 ) ); + temp3 = _mm_srli_epi32( temp3, 24 ); + temp3 = _mm_packs_epi32( temp3, temp3 ); + + + // Load block + temp4 = _mm_shuffle_epi32( blockb[i], R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp5 = _mm_shuffle_ps( blockb[i], zero, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + temp6 = _mm_sad_epu8( temp4, color0 ); + temp7 = _mm_sad_epu8( temp5, color0 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp0 = _mm_packs_epi32( temp0, temp6 ); // d0 + + temp6 = _mm_sad_epu8( temp4, color1 ); + temp7 = _mm_sad_epu8( temp5, color1 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp1 = _mm_packs_epi32( temp1, temp6 ); // d1 + + temp6 = _mm_sad_epu8( temp4, color2 ); + temp7 = _mm_sad_epu8( temp5, color2 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp2 = _mm_packs_epi32( temp2, temp6 ); // d2 + + + // diff from color + temp4 = _mm_shuffle_ps( temp4, temp5, R_SHUFFLE_D( 0, 2, 0, 2 ) ); // c3 + temp4 = _mm_srli_epi32( temp4, 24 ); + temp4 = _mm_packs_epi32( temp4, temp4 ); + temp3 = _mm_unpacklo_epi64( temp3, temp4 ); + + temp7 = _mm_slli_epi32( result, 16 ); + + + // diff from color + temp4 = _mm_cmpgt_epi16( temp2, temp1 ); // b1 + temp2 = _mm_cmpgt_epi16( temp2, temp0 ); // b0 + temp1 = _mm_cmpgt_epi16( temp1, temp0 ); // b2 + temp3 = _mm_max_epi16( temp3, (const __m128i &)SIMD_SSE2_word_127 ); // b3 + temp3 = _mm_cmpeq_epi16( temp3, (const __m128i &)SIMD_SSE2_word_127 ); + + temp2 = _mm_and_si128( temp2, temp4 ); + temp2 = _mm_or_si128( temp2, temp3 ); // b0 & b1 | b3 + temp1 = _mm_xor_si128( temp1, temp4 ); + temp1 = _mm_or_si128( temp1, temp3 ); // b2 ^ b1 | b3 + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_word_2 ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_word_1 ); + temp2 = _mm_or_si128( temp2, temp1 ); + + + + temp5 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp2 = _mm_unpacklo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_0 ); + temp5 = _mm_unpacklo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_0 ); + temp5 = _mm_slli_epi32( temp5, 8 ); + temp7 = _mm_or_si128( temp7, temp5 ); + result = _mm_or_si128( temp7, temp2 ); + } + + temp4 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 1, 2, 3, 0 ) ); + temp5 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp6 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 3, 0, 1, 2 ) ); + temp4 = _mm_slli_epi32( temp4, 2 ); + temp5 = _mm_slli_epi32( temp5, 4 ); + temp6 = _mm_slli_epi32( temp6, 6 ); + temp7 = _mm_or_si128( result, temp4 ); + temp7 = _mm_or_si128( temp7, temp5 ); + temp7 = _mm_or_si128( temp7, temp6 ); + + unsigned int out = _mm_cvtsi128_si32( temp7 ); + EmitUInt( out ); +} + +/* +======================== +idDxtEncoder::EmitCoCgIndices_SSE2 + +params: colorBlock - 16 pixel block for which to find color indices +paramO: minColor - Min alpha found +paramO: maxColor - Max alpha found +return: 4 byte color index block +======================== +*/ +void idDxtEncoder::EmitCoCgIndices_SSE2( const byte *colorBlock, const byte *minColor_, const byte *maxColor_ ) { + __m128c zero = SIMD_SSE2_zero; + __m128c result = SIMD_SSE2_zero; + __m128c color0, color1, color2, color3; + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __m128c minColor = _mm_cvtsi32_si128( *(int *)minColor_ ); + __m128c maxColor = _mm_cvtsi32_si128( *(int *)maxColor_ ); + __m128c blocka[2], blockb[2]; + blocka[0] = *((__m128i *)(&colorBlock[ 0])); + blocka[1] = *((__m128i *)(&colorBlock[32])); + blockb[0] = *((__m128i *)(&colorBlock[16])); + blockb[1] = *((__m128i *)(&colorBlock[48])); + + temp7 = zero; + + temp0 = maxColor; + temp0 = _mm_and_si128( temp0, *(__m128c*)SIMD_SSE2_byte_colorMask2 ); + color0 = _mm_shuffle_epi32( temp0, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp1 = minColor; + temp1 = _mm_and_si128( temp1, *(__m128c*)SIMD_SSE2_byte_colorMask2 ); + color1 = _mm_shuffle_epi32( temp1, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp0 = _mm_unpacklo_epi8( color0, zero ); + temp1 = _mm_unpacklo_epi8( color1, zero ); + + temp6 = _mm_add_epi16( temp1, temp0 ); + temp0 = _mm_add_epi16( temp0, temp6 ); + temp0 = _mm_mulhi_epi16( temp0, (const __m128i &)SIMD_SSE2_word_div_by_3 ); // * ( ( 1 << 16 ) / 3 + 1 ) ) >> 16 + temp0 = _mm_packus_epi16( temp0, zero ); + color2 = _mm_shuffle_epi32( temp0, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + temp1 = _mm_add_epi16( temp1, temp6 ); + temp1 = _mm_mulhi_epi16( temp1, (const __m128i &)SIMD_SSE2_word_div_by_3 ); // * ( ( 1 << 16 ) / 3 + 1 ) ) >> 16 + temp1 = _mm_packus_epi16( temp1, zero ); + color3 = _mm_shuffle_epi32( temp1, R_SHUFFLE_D( 0, 1, 0, 1 ) ); + + for ( int i = 1; i >= 0; i-- ) { + // Load block + temp3 = _mm_shuffle_epi32( blocka[i], R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp5 = _mm_shuffle_ps( blocka[i], zero, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + temp0 = _mm_sad_epu8( temp3, color0 ); + temp6 = _mm_sad_epu8( temp5, color0 ); + temp0 = _mm_packs_epi32( temp0, temp6 ); + + temp1 = _mm_sad_epu8( temp3, color1 ); + temp6 = _mm_sad_epu8( temp5, color1 ); + temp1 = _mm_packs_epi32( temp1, temp6 ); + + temp2 = _mm_sad_epu8( temp3, color2 ); + temp6 = _mm_sad_epu8( temp5, color2 ); + temp2 = _mm_packs_epi32( temp2, temp6 ); + + temp3 = _mm_sad_epu8( temp3, color3 ); + temp5 = _mm_sad_epu8( temp5, color3 ); + temp3 = _mm_packs_epi32( temp3, temp5 ); + + + // Load block + temp4 = _mm_shuffle_epi32( blockb[i], R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp5 = _mm_shuffle_ps( blockb[i], zero, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + temp6 = _mm_sad_epu8( temp4, color0 ); + temp7 = _mm_sad_epu8( temp5, color0 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp0 = _mm_packs_epi32( temp0, temp6 ); // d0 + + temp6 = _mm_sad_epu8( temp4, color1 ); + temp7 = _mm_sad_epu8( temp5, color1 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp1 = _mm_packs_epi32( temp1, temp6 ); // d1 + + temp6 = _mm_sad_epu8( temp4, color2 ); + temp7 = _mm_sad_epu8( temp5, color2 ); + temp6 = _mm_packs_epi32( temp6, temp7 ); + temp2 = _mm_packs_epi32( temp2, temp6 ); // d2 + + temp4 = _mm_sad_epu8( temp4, color3 ); + temp5 = _mm_sad_epu8( temp5, color3 ); + temp4 = _mm_packs_epi32( temp4, temp5 ); + temp3 = _mm_packs_epi32( temp3, temp4 ); // d3 + + temp7 = _mm_slli_epi32( result, 16 ); + + temp4 = _mm_cmpgt_epi16( temp0, temp2 ); // b2 + temp5 = _mm_cmpgt_epi16( temp1, temp3 ); // b3 + temp0 = _mm_cmpgt_epi16( temp0, temp3 ); // b0 + temp1 = _mm_cmpgt_epi16( temp1, temp2 ); // b1 + temp2 = _mm_cmpgt_epi16( temp2, temp3 ); // b4 + temp4 = _mm_and_si128( temp4, temp1 ); // x0 + temp5 = _mm_and_si128( temp5, temp0 ); // x1 + temp2 = _mm_and_si128( temp2, temp0 ); // x2 + temp4 = _mm_or_si128( temp4, temp5 ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_word_1 ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_word_2 ); + temp2 = _mm_or_si128( temp2, temp4 ); + + temp5 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp2 = _mm_unpacklo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_0 ); + temp5 = _mm_unpacklo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_0 ); + temp5 = _mm_slli_epi32( temp5, 8 ); + temp7 = _mm_or_si128( temp7, temp5 ); + result = _mm_or_si128( temp7, temp2 ); + } + + temp4 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 1, 2, 3, 0 ) ); + temp5 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + temp6 = _mm_shuffle_epi32( result, R_SHUFFLE_D( 3, 0, 1, 2 ) ); + temp4 = _mm_slli_epi32( temp4, 2 ); + temp5 = _mm_slli_epi32( temp5, 4 ); + temp6 = _mm_slli_epi32( temp6, 6 ); + temp7 = _mm_or_si128( result, temp4 ); + temp7 = _mm_or_si128( temp7, temp5 ); + temp7 = _mm_or_si128( temp7, temp6 ); + + unsigned int out = _mm_cvtsi128_si32( temp7 ); + EmitUInt( out ); +} + +/* +======================== +idDxtEncoder::EmitAlphaIndices_SSE2 + +params: block - 16 pixel block for which to find alpha indices +paramO: minAlpha - Min alpha found +paramO: maxAlpha - Max alpha found +======================== +*/ +void idDxtEncoder::EmitAlphaIndices_SSE2( const byte *block, const int minAlpha_, const int maxAlpha_ ) { + __m128i block0 = *((__m128i *)(&block[ 0])); + __m128i block1 = *((__m128i *)(&block[16])); + __m128i block2 = *((__m128i *)(&block[32])); + __m128i block3 = *((__m128i *)(&block[48])); + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + temp0 = _mm_srli_epi32( block0, 24 ); + temp5 = _mm_srli_epi32( block1, 24 ); + temp6 = _mm_srli_epi32( block2, 24 ); + temp4 = _mm_srli_epi32( block3, 24 ); + + temp0 = _mm_packus_epi16( temp0, temp5 ); + temp6 = _mm_packus_epi16( temp6, temp4 ); + + //--------------------- + + // ab0 = ( 7 * maxAlpha + 7 * minAlpha + ALPHA_RANGE ) / 14 + // ab3 = ( 9 * maxAlpha + 5 * minAlpha + ALPHA_RANGE ) / 14 + // ab2 = ( 11 * maxAlpha + 3 * minAlpha + ALPHA_RANGE ) / 14 + // ab1 = ( 13 * maxAlpha + 1 * minAlpha + ALPHA_RANGE ) / 14 + + // ab4 = ( 7 * maxAlpha + 7 * minAlpha + ALPHA_RANGE ) / 14 + // ab5 = ( 5 * maxAlpha + 9 * minAlpha + ALPHA_RANGE ) / 14 + // ab6 = ( 3 * maxAlpha + 11 * minAlpha + ALPHA_RANGE ) / 14 + // ab7 = ( 1 * maxAlpha + 13 * minAlpha + ALPHA_RANGE ) / 14 + + temp5 = _mm_cvtsi32_si128( maxAlpha_ ); + temp5 = _mm_shufflelo_epi16( temp5, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp2 = _mm_cvtsi32_si128( minAlpha_ ); + temp2 = _mm_shufflelo_epi16( temp2, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp2 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp7 = _mm_mullo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_scale_7_5_3_1 ); + temp5 = _mm_mullo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_scale_7_9_11_13 ); + temp3 = _mm_mullo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_scale_7_9_11_13 ); + temp2 = _mm_mullo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_scale_7_5_3_1 ); + + temp5 = _mm_add_epi16( temp5, temp2 ); + temp7 = _mm_add_epi16( temp7, temp3 ); + + temp5 = _mm_add_epi16( temp5, (const __m128i &)SIMD_SSE2_word_7 ); + temp7 = _mm_add_epi16( temp7, (const __m128i &)SIMD_SSE2_word_7 ); + + temp5 = _mm_mulhi_epi16( temp5, (const __m128i &)SIMD_SSE2_word_div_by_14 ); + temp7 = _mm_mulhi_epi16( temp7, (const __m128i &)SIMD_SSE2_word_div_by_14 ); + + temp1 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 3, 3, 3, 3 ) ); + temp2 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 2, 2, 2, 2 ) ); + temp3 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 1, 1, 1, 1 ) ); + temp1 = _mm_packus_epi16( temp1, temp1 ); + temp2 = _mm_packus_epi16( temp2, temp2 ); + temp3 = _mm_packus_epi16( temp3, temp3 ); + + temp0 = _mm_packus_epi16( temp0, temp6 ); + + temp4 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp5 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 1, 1, 1, 1 ) ); + temp6 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 2, 2, 2, 2 ) ); + temp7 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 3, 3, 3, 3 ) ); + temp4 = _mm_packus_epi16( temp4, temp4 ); + temp5 = _mm_packus_epi16( temp5, temp5 ); + temp6 = _mm_packus_epi16( temp6, temp6 ); + temp7 = _mm_packus_epi16( temp7, temp7 ); + + temp1 = _mm_max_epu8( temp1, temp0 ); + temp2 = _mm_max_epu8( temp2, temp0 ); + temp3 = _mm_max_epu8( temp3, temp0 ); + temp1 = _mm_cmpeq_epi8( temp1, temp0 ); + temp2 = _mm_cmpeq_epi8( temp2, temp0 ); + temp3 = _mm_cmpeq_epi8( temp3, temp0 ); + temp4 = _mm_max_epu8( temp4, temp0 ); + temp5 = _mm_max_epu8( temp5, temp0 ); + temp6 = _mm_max_epu8( temp6, temp0 ); + temp7 = _mm_max_epu8( temp7, temp0 ); + temp4 = _mm_cmpeq_epi8( temp4, temp0 ); + temp5 = _mm_cmpeq_epi8( temp5, temp0 ); + temp6 = _mm_cmpeq_epi8( temp6, temp0 ); + temp7 = _mm_cmpeq_epi8( temp7, temp0 ); + temp0 = _mm_adds_epi8( (const __m128i &)SIMD_SSE2_byte_8, temp1 ); + temp2 = _mm_adds_epi8( temp2, temp3 ); + temp4 = _mm_adds_epi8( temp4, temp5 ); + temp6 = _mm_adds_epi8( temp6, temp7 ); + temp0 = _mm_adds_epi8( temp0, temp2 ); + temp4 = _mm_adds_epi8( temp4, temp6 ); + temp0 = _mm_adds_epi8( temp0, temp4 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_byte_7 ); + temp1 = _mm_cmpgt_epi8( (const __m128i &)SIMD_SSE2_byte_2, temp0 ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_byte_1 ); + temp0 = _mm_xor_si128( temp0, temp1 ); + + temp1 = _mm_srli_epi64( temp0, 8 - 3 ); + temp2 = _mm_srli_epi64( temp0, 16 - 6 ); + temp3 = _mm_srli_epi64( temp0, 24 - 9 ); + temp4 = _mm_srli_epi64( temp0, 32 - 12 ); + temp5 = _mm_srli_epi64( temp0, 40 - 15 ); + temp6 = _mm_srli_epi64( temp0, 48 - 18 ); + temp7 = _mm_srli_epi64( temp0, 56 - 21 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask0 ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask1 ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask2 ); + temp3 = _mm_and_si128( temp3, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask3 ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask4 ); + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask5 ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask6 ); + temp7 = _mm_and_si128( temp7, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask7 ); + temp0 = _mm_or_si128( temp0, temp1 ); + temp2 = _mm_or_si128( temp2, temp3 ); + temp4 = _mm_or_si128( temp4, temp5 ); + temp6 = _mm_or_si128( temp6, temp7 ); + temp0 = _mm_or_si128( temp0, temp2 ); + temp4 = _mm_or_si128( temp4, temp6 ); + temp0 = _mm_or_si128( temp0, temp4 ); + + + int out = _mm_cvtsi128_si32( temp0 ); + EmitUInt( out ); + outData--; + + temp1 = _mm_shuffle_epi32( temp0, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + + out = _mm_cvtsi128_si32( temp1 ); + EmitUInt( out ); + outData--; +} + +/* +======================== +idDxtEncoder::EmitAlphaIndices_SSE2 +======================== +*/ +void idDxtEncoder::EmitAlphaIndices_SSE2( const byte *block, const int channelBitOffset, const int minAlpha_, const int maxAlpha_ ) { + __m128i block0 = *((__m128i *)(&block[ 0])); + __m128i block1 = *((__m128i *)(&block[16])); + __m128i block2 = *((__m128i *)(&block[32])); + __m128i block3 = *((__m128i *)(&block[48])); + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + temp7 = _mm_cvtsi32_si128( channelBitOffset ); + + temp0 = _mm_srl_epi32( block0, temp7 ); + temp5 = _mm_srl_epi32( block1, temp7 ); + temp6 = _mm_srl_epi32( block2, temp7 ); + temp4 = _mm_srl_epi32( block3, temp7 ); + + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + + temp0 = _mm_packus_epi16( temp0, temp5 ); + temp6 = _mm_packus_epi16( temp6, temp4 ); + + //--------------------- + + // ab0 = ( 7 * maxAlpha + 7 * minAlpha + ALPHA_RANGE ) / 14 + // ab3 = ( 9 * maxAlpha + 5 * minAlpha + ALPHA_RANGE ) / 14 + // ab2 = ( 11 * maxAlpha + 3 * minAlpha + ALPHA_RANGE ) / 14 + // ab1 = ( 13 * maxAlpha + 1 * minAlpha + ALPHA_RANGE ) / 14 + + // ab4 = ( 7 * maxAlpha + 7 * minAlpha + ALPHA_RANGE ) / 14 + // ab5 = ( 5 * maxAlpha + 9 * minAlpha + ALPHA_RANGE ) / 14 + // ab6 = ( 3 * maxAlpha + 11 * minAlpha + ALPHA_RANGE ) / 14 + // ab7 = ( 1 * maxAlpha + 13 * minAlpha + ALPHA_RANGE ) / 14 + + temp5 = _mm_cvtsi32_si128( maxAlpha_ ); + temp5 = _mm_shufflelo_epi16( temp5, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp5 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp2 = _mm_cvtsi32_si128( minAlpha_ ); + temp2 = _mm_shufflelo_epi16( temp2, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp2 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp7 = _mm_mullo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_scale_7_5_3_1 ); + temp5 = _mm_mullo_epi16( temp5, (const __m128i &)SIMD_SSE2_word_scale_7_9_11_13 ); + temp3 = _mm_mullo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_scale_7_9_11_13 ); + temp2 = _mm_mullo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_scale_7_5_3_1 ); + + temp5 = _mm_add_epi16( temp5, temp2 ); + temp7 = _mm_add_epi16( temp7, temp3 ); + + temp5 = _mm_add_epi16( temp5, (const __m128i &)SIMD_SSE2_word_7 ); + temp7 = _mm_add_epi16( temp7, (const __m128i &)SIMD_SSE2_word_7 ); + + temp5 = _mm_mulhi_epi16( temp5, (const __m128i &)SIMD_SSE2_word_div_by_14 ); + temp7 = _mm_mulhi_epi16( temp7, (const __m128i &)SIMD_SSE2_word_div_by_14 ); + + temp1 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 3, 3, 3, 3 ) ); + temp2 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 2, 2, 2, 2 ) ); + temp3 = _mm_shuffle_epi32( temp5, R_SHUFFLE_D( 1, 1, 1, 1 ) ); + temp1 = _mm_packus_epi16( temp1, temp1 ); + temp2 = _mm_packus_epi16( temp2, temp2 ); + temp3 = _mm_packus_epi16( temp3, temp3 ); + + temp0 = _mm_packus_epi16( temp0, temp6 ); + + temp4 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp5 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 1, 1, 1, 1 ) ); + temp6 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 2, 2, 2, 2 ) ); + temp7 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 3, 3, 3, 3 ) ); + temp4 = _mm_packus_epi16( temp4, temp4 ); + temp5 = _mm_packus_epi16( temp5, temp5 ); + temp6 = _mm_packus_epi16( temp6, temp6 ); + temp7 = _mm_packus_epi16( temp7, temp7 ); + + temp1 = _mm_max_epu8( temp1, temp0 ); + temp2 = _mm_max_epu8( temp2, temp0 ); + temp3 = _mm_max_epu8( temp3, temp0 ); + temp1 = _mm_cmpeq_epi8( temp1, temp0 ); + temp2 = _mm_cmpeq_epi8( temp2, temp0 ); + temp3 = _mm_cmpeq_epi8( temp3, temp0 ); + temp4 = _mm_max_epu8( temp4, temp0 ); + temp5 = _mm_max_epu8( temp5, temp0 ); + temp6 = _mm_max_epu8( temp6, temp0 ); + temp7 = _mm_max_epu8( temp7, temp0 ); + temp4 = _mm_cmpeq_epi8( temp4, temp0 ); + temp5 = _mm_cmpeq_epi8( temp5, temp0 ); + temp6 = _mm_cmpeq_epi8( temp6, temp0 ); + temp7 = _mm_cmpeq_epi8( temp7, temp0 ); + temp0 = _mm_adds_epi8( (const __m128i &)SIMD_SSE2_byte_8, temp1 ); + temp2 = _mm_adds_epi8( temp2, temp3 ); + temp4 = _mm_adds_epi8( temp4, temp5 ); + temp6 = _mm_adds_epi8( temp6, temp7 ); + temp0 = _mm_adds_epi8( temp0, temp2 ); + temp4 = _mm_adds_epi8( temp4, temp6 ); + temp0 = _mm_adds_epi8( temp0, temp4 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_byte_7 ); + temp1 = _mm_cmpgt_epi8( (const __m128i &)SIMD_SSE2_byte_2, temp0 ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_byte_1 ); + temp0 = _mm_xor_si128( temp0, temp1 ); + + temp1 = _mm_srli_epi64( temp0, 8 - 3 ); + temp2 = _mm_srli_epi64( temp0, 16 - 6 ); + temp3 = _mm_srli_epi64( temp0, 24 - 9 ); + temp4 = _mm_srli_epi64( temp0, 32 - 12 ); + temp5 = _mm_srli_epi64( temp0, 40 - 15 ); + temp6 = _mm_srli_epi64( temp0, 48 - 18 ); + temp7 = _mm_srli_epi64( temp0, 56 - 21 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask0 ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask1 ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask2 ); + temp3 = _mm_and_si128( temp3, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask3 ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask4 ); + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask5 ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask6 ); + temp7 = _mm_and_si128( temp7, (const __m128i &)SIMD_SSE2_dword_alpha_bit_mask7 ); + temp0 = _mm_or_si128( temp0, temp1 ); + temp2 = _mm_or_si128( temp2, temp3 ); + temp4 = _mm_or_si128( temp4, temp5 ); + temp6 = _mm_or_si128( temp6, temp7 ); + temp0 = _mm_or_si128( temp0, temp2 ); + temp4 = _mm_or_si128( temp4, temp6 ); + temp0 = _mm_or_si128( temp0, temp4 ); + + + int out = _mm_cvtsi128_si32( temp0 ); + EmitUInt( out ); + outData--; + + temp1 = _mm_shuffle_epi32( temp0, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + + out = _mm_cvtsi128_si32( temp1 ); + EmitUInt( out ); + outData--; +} + +/* +======================== +idDxtEncoder::CompressImageDXT1Fast_SSE2 + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT1Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + ExtractBlock_SSE2( inBuf + i * 4, width, block ); + GetMinMaxBBox_SSE2( block, minColor, maxColor ); + InsetColorsBBox_SSE2( minColor, maxColor ); + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitColorIndices_SSE2( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } + +#ifdef TEST_COMPRESSION + int tmpDstPadding = dstPadding; + dstPadding = 0; + byte * testOutBuf = (byte *) _alloca16( width * height / 2 ); + CompressImageDXT1Fast_Generic( inBuf, testOutBuf, width, height ); + for ( int j = 0; j < height/4; j++ ) { + for ( int i = 0; i < width/4; i++ ) { + byte * ptr1 = outBuf + ( j * width/4 + i ) * 8 + j * tmpDstPadding; + byte * ptr2 = testOutBuf + ( j * width/4 + i ) * 8; + for ( int k = 0; k < 8; k++ ) { + assert( ptr1[k] == ptr2[k] ); + } + } + } + dstPadding = tmpDstPadding; +#endif +} + +/* +======================== +idDxtEncoder::CompressImageDXT1AlphaFast_SSE2 + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT1AlphaFast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + ExtractBlock_SSE2( inBuf + i * 4, width, block ); + GetMinMaxBBox_SSE2( block, minColor, maxColor ); + byte minAlpha = minColor[3]; + InsetColorsBBox_SSE2( minColor, maxColor ); + + if ( minAlpha >= 128 ) { + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + EmitColorIndices_SSE2( block, minColor, maxColor ); + } else { + EmitUShort( ColorTo565( minColor ) ); + EmitUShort( ColorTo565( maxColor ) ); + EmitColorAlphaIndices_SSE2( block, minColor, maxColor ); + } + } + outData += dstPadding; + inBuf += srcPadding; + } + +#ifdef TEST_COMPRESSION + int tmpDstPadding = dstPadding; + dstPadding = 0; + byte * testOutBuf = (byte *) _alloca16( width * height / 2 ); + CompressImageDXT1AlphaFast_Generic( inBuf, testOutBuf, width, height ); + for ( int j = 0; j < height/4; j++ ) { + for ( int i = 0; i < width/4; i++ ) { + byte * ptr1 = outBuf + ( j * width/4 + i ) * 8 + j * tmpDstPadding; + byte * ptr2 = testOutBuf + ( j * width/4 + i ) * 8; + for ( int k = 0; k < 8; k++ ) { + assert( ptr1[k] == ptr2[k] ); + } + } + } + dstPadding = tmpDstPadding; +#endif +} + +/* +======================== +idDxtEncoder::CompressImageDXT5Fast_SSE2 + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressImageDXT5Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + ExtractBlock_SSE2( inBuf + i * 4, width, block ); + GetMinMaxBBox_SSE2( block, minColor, maxColor ); + InsetColorsBBox_SSE2( minColor, maxColor ); + + EmitByte( maxColor[3] ); + EmitByte( minColor[3] ); + + EmitAlphaIndices_SSE2( block, minColor[3], maxColor[3] ); + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitColorIndices_SSE2( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } + +#ifdef TEST_COMPRESSION + int tmpDstPadding = dstPadding; + dstPadding = 0; + byte * testOutBuf = (byte *) _alloca16( width * height ); + CompressImageDXT5Fast_Generic( inBuf, testOutBuf, width, height ); + for ( int j = 0; j < height / 4; j++ ) { + for ( int i = 0; i < width / 4; i++ ) { + byte * ptr1 = outBuf + ( j * width/4 + i ) * 16 + j * tmpDstPadding; + byte * ptr2 = testOutBuf + ( j * width/4 + i ) * 16; + for ( int k = 0; k < 16; k++ ) { + assert( ptr1[k] == ptr2[k] ); + } + } + } + dstPadding = tmpDstPadding; +#endif +} + +/* +======================== +idDxtEncoder::ScaleYCoCg_SSE2 +======================== +*/ +ID_INLINE void idDxtEncoder::ScaleYCoCg_SSE2( byte *colorBlock, byte *minColor, byte *maxColor ) const { + __m128i block0 = *((__m128i *)(&colorBlock[ 0])); + __m128i block1 = *((__m128i *)(&colorBlock[16])); + __m128i block2 = *((__m128i *)(&colorBlock[32])); + __m128i block3 = *((__m128i *)(&colorBlock[48])); + + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + temp0 = _mm_cvtsi32_si128( *(int *)minColor ); + temp1 = _mm_cvtsi32_si128( *(int *)maxColor ); + + temp0 = _mm_unpacklo_epi8( temp0, (const __m128i &)SIMD_SSE2_byte_0 ); + temp1 = _mm_unpacklo_epi8( temp1, (const __m128i &)SIMD_SSE2_byte_0 ); + + // TODO: Algorithm seems to be get the absolute difference + temp6 = _mm_sub_epi16( (const __m128i &)SIMD_SSE2_word_center_128, temp0 ); + temp7 = _mm_sub_epi16( (const __m128i &)SIMD_SSE2_word_center_128, temp1 ); + temp0 = _mm_sub_epi16( temp0, (const __m128i &)SIMD_SSE2_word_center_128 ); + temp1 = _mm_sub_epi16( temp1, (const __m128i &)SIMD_SSE2_word_center_128 ); + temp6 = _mm_max_epi16( temp6, temp0 ); + temp7 = _mm_max_epi16( temp7, temp1 ); + + temp6 = _mm_max_epi16( temp6, temp7 ); + temp7 = _mm_shufflelo_epi16( temp6, R_SHUFFLE_D( 1, 0, 1, 0 ) ); + temp6 = _mm_max_epi16( temp6, temp7 ); + temp6 = _mm_shuffle_epi32( temp6, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp7 = temp6; + temp6 = _mm_cmpgt_epi16( temp6, (const __m128i &)SIMD_SSE2_word_63 ); // mask0 + temp7 = _mm_cmpgt_epi16( temp7, (const __m128i &)SIMD_SSE2_word_31 ); // mask1 + + temp7 = _mm_andnot_si128( temp7, (const __m128i &)SIMD_SSE2_byte_2 ); + temp7 = _mm_or_si128( temp7, (const __m128i &)SIMD_SSE2_byte_1 ); + temp6 = _mm_andnot_si128( temp6, temp7 ); + temp3 = temp6; + temp7 = temp6; + temp7 = _mm_xor_si128( temp7, (const __m128i &)SIMD_SSE2_byte_not ); + temp7 = _mm_or_si128( temp7, (const __m128i &)SIMD_SSE2_byte_scale_mask0 ); // 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00 + temp6 = _mm_add_epi16( temp6, (const __m128i &)SIMD_SSE2_byte_1 ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_byte_scale_mask1 ); // 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF + temp6 = _mm_or_si128( temp6, (const __m128i &)SIMD_SSE2_byte_scale_mask2 ); // 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 + + // TODO: remove this second store + temp4 = _mm_cvtsi32_si128( *(int *)minColor ); + temp5 = _mm_cvtsi32_si128( *(int *)maxColor ); + + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_byte_scale_mask3 ); // 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_byte_scale_mask3 ); + + temp3 = _mm_slli_epi32( temp3, 3 ); + temp3 = _mm_and_si128( temp3, (const __m128i &)SIMD_SSE2_byte_scale_mask4 ); // 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00 + + temp4 = _mm_or_si128( temp4, temp3 ); + temp5 = _mm_or_si128( temp5, temp3 ); + + temp4 = _mm_add_epi8( temp4, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + temp5 = _mm_add_epi8( temp5, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + + temp4 = _mm_mullo_epi16( temp4, temp6 ); + temp5 = _mm_mullo_epi16( temp5, temp6 ); + + temp4 = _mm_and_si128( temp4, temp7 ); + temp5 = _mm_and_si128( temp5, temp7 ); + + temp4 = _mm_sub_epi8( temp4, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + temp5 = _mm_sub_epi8( temp5, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + + *(int *)minColor = _mm_cvtsi128_si32( temp4 ); + *(int *)maxColor = _mm_cvtsi128_si32( temp5 ); + + temp0 = _mm_add_epi8( block0, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + temp1 = _mm_add_epi8( block1, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + temp2 = _mm_add_epi8( block2, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + temp3 = _mm_add_epi8( block3, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + + temp0 = _mm_mullo_epi16( temp0, temp6 ); + temp1 = _mm_mullo_epi16( temp1, temp6 ); + temp2 = _mm_mullo_epi16( temp2, temp6 ); + temp3 = _mm_mullo_epi16( temp3, temp6 ); + + temp0 = _mm_and_si128( temp0, temp7 ); + temp1 = _mm_and_si128( temp1, temp7 ); + temp2 = _mm_and_si128( temp2, temp7 ); + temp3 = _mm_and_si128( temp3, temp7 ); + + *((__m128i *)(&colorBlock[ 0])) = _mm_sub_epi8( temp0, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + *((__m128i *)(&colorBlock[16])) = _mm_sub_epi8( temp1, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + *((__m128i *)(&colorBlock[32])) = _mm_sub_epi8( temp2, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); + *((__m128i *)(&colorBlock[48])) = _mm_sub_epi8( temp3, (const __m128i &)SIMD_SSE2_byte_minus_128_0 ); +} + +/* +======================== +idDxtEncoder::InsetYCoCgBBox_SSE2 +======================== +*/ +ID_INLINE void idDxtEncoder::InsetYCoCgBBox_SSE2( byte *minColor, byte *maxColor ) const { + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + temp0 = _mm_cvtsi32_si128( *(int *)minColor ); + temp1 = _mm_cvtsi32_si128( *(int *)maxColor ); + + temp0 = _mm_unpacklo_epi8( temp0, (const __m128i &)SIMD_SSE2_byte_0 ); + temp1 = _mm_unpacklo_epi8( temp1, (const __m128i &)SIMD_SSE2_byte_0 ); + + temp2 = _mm_sub_epi16( temp1, temp0 ); + temp2 = _mm_sub_epi16( temp2, (const __m128i &)SIMD_SSE2_word_insetYCoCgRound ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_word_insetYCoCgMask ); + temp0 = _mm_mullo_epi16( temp0, (const __m128i &)SIMD_SSE2_word_insetYCoCgShiftUp ); + temp1 = _mm_mullo_epi16( temp1, (const __m128i &)SIMD_SSE2_word_insetYCoCgShiftUp ); + temp0 = _mm_add_epi16( temp0, temp2 ); + temp1 = _mm_sub_epi16( temp1, temp2 ); + temp0 = _mm_mulhi_epi16( temp0, (const __m128i &)SIMD_SSE2_word_insetYCoCgShiftDown ); + temp1 = _mm_mulhi_epi16( temp1, (const __m128i &)SIMD_SSE2_word_insetYCoCgShiftDown ); + temp0 = _mm_max_epi16( temp0, (const __m128i &)SIMD_SSE2_word_0 ); + temp1 = _mm_max_epi16( temp1, (const __m128i &)SIMD_SSE2_word_0 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_word_insetYCoCgQuantMask ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_word_insetYCoCgQuantMask ); + temp2 = _mm_mulhi_epi16( temp0, (const __m128i &)SIMD_SSE2_word_insetYCoCgRep ); + temp3 = _mm_mulhi_epi16( temp1, (const __m128i &)SIMD_SSE2_word_insetYCoCgRep ); + temp0 = _mm_or_si128( temp0, temp2 ); + temp1 = _mm_or_si128( temp1, temp3 ); + temp0 = _mm_packus_epi16( temp0, temp0 ); + temp1 = _mm_packus_epi16( temp1, temp1 ); + + *(int *)minColor = _mm_cvtsi128_si32( temp0 ); + *(int *)maxColor = _mm_cvtsi128_si32( temp1 ); +} + +/* +======================== +idDxtEncoder::SelectYCoCgDiagonal_SSE2 + +params: colorBlock - 16 pixel block to find color indexes for +paramO: minColor - min color found +paramO: maxColor - max color found +return: diagonal to use +======================== +*/ +ID_INLINE void idDxtEncoder::SelectYCoCgDiagonal_SSE2( const byte *colorBlock, byte *minColor, byte *maxColor ) const { + __m128i block0 = *((__m128i *)(&colorBlock[ 0])); + __m128i block1 = *((__m128i *)(&colorBlock[16])); + __m128i block2 = *((__m128i *)(&colorBlock[32])); + __m128i block3 = *((__m128i *)(&colorBlock[48])); + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + temp0 = _mm_and_si128( block0, (const __m128i &)SIMD_SSE2_dword_word_mask ); + temp1 = _mm_and_si128( block1, (const __m128i &)SIMD_SSE2_dword_word_mask ); + temp2 = _mm_and_si128( block2, (const __m128i &)SIMD_SSE2_dword_word_mask ); + temp3 = _mm_and_si128( block3, (const __m128i &)SIMD_SSE2_dword_word_mask ); + + temp1 = _mm_slli_si128( temp1, 2 ); + temp3 = _mm_slli_si128( temp3, 2 ); + temp0 = _mm_or_si128( temp0, temp1 ); + temp2 = _mm_or_si128( temp2, temp3 ); + + temp6 = _mm_cvtsi32_si128( *(int *)minColor ); + temp7 = _mm_cvtsi32_si128( *(int *)maxColor ); + + temp1 = _mm_avg_epu8( temp6, temp7 ); + temp1 = _mm_shufflelo_epi16( temp1, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp1 = _mm_shuffle_epi32( temp1, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp3 = _mm_max_epu8( temp1, temp2 ); + temp1 = _mm_max_epu8( temp1, temp0 ); + temp1 = _mm_cmpeq_epi8( temp1, temp0 ); + temp3 = _mm_cmpeq_epi8( temp3, temp2 ); + + temp0 = _mm_srli_si128( temp1, 1 ); + temp2 = _mm_srli_si128( temp3, 1 ); + + temp0 = _mm_xor_si128( temp0, temp1 ); + temp2 = _mm_xor_si128( temp2, temp3 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_word_1 ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_word_1 ); + + temp0 = _mm_add_epi16( temp0, temp2 ); + temp0 = _mm_sad_epu8( temp0, (const __m128i &)SIMD_SSE2_byte_0 ); + temp1 = _mm_shuffle_epi32( temp0, R_SHUFFLE_D( 2, 3, 0, 1 ) ); + +#ifdef NVIDIA_7X_HARDWARE_BUG_FIX + temp1 = _mm_add_epi16( temp1, temp0 ); + temp1 = _mm_cmpgt_epi16( temp1, (const __m128i &)SIMD_SSE2_word_8 ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_byte_diagonalMask ); + temp0 = _mm_cmpeq_epi8( temp6, temp7 ); + temp0 = _mm_slli_si128( temp0, 1 ); + temp0 = _mm_andnot_si128( temp0, temp1 ); +#else + temp0 = _mm_add_epi16( temp0, temp1 ); + temp0 = _mm_cmpgt_epi16( temp0, (const __m128i &)SIMD_SSE2_word_8 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_byte_diagonalMask ); +#endif + + temp6 = _mm_xor_si128( temp6, temp7 ); + temp0 = _mm_and_si128( temp0, temp6 ); + temp7 = _mm_xor_si128( temp7, temp0 ); + temp6 = _mm_xor_si128( temp6, temp7 ); + + *(int *)minColor = _mm_cvtsi128_si32( temp6 ); + *(int *)maxColor = _mm_cvtsi128_si32( temp7 ); +} + +/* +======================== +idDxtEncoder::CompressYCoCgDXT5Fast_SSE2 + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressYCoCgDXT5Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte minColor[4] ); + ALIGN16( byte maxColor[4] ); + + //assert( HasConstantValuePer4x4Block( inBuf, width, height, 2 ) ); + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + ExtractBlock_SSE2( inBuf + i * 4, width, block ); + GetMinMaxBBox_SSE2( block, minColor, maxColor ); + + ScaleYCoCg_SSE2( block, minColor, maxColor ); + InsetYCoCgBBox_SSE2( minColor, maxColor ); + SelectYCoCgDiagonal_SSE2( block, minColor, maxColor ); + + EmitByte( maxColor[3] ); + EmitByte( minColor[3] ); + + EmitAlphaIndices_SSE2( block, minColor[3], maxColor[3] ); + + EmitUShort( ColorTo565( maxColor ) ); + EmitUShort( ColorTo565( minColor ) ); + + EmitCoCgIndices_SSE2( block, minColor, maxColor ); + } + outData += dstPadding; + inBuf += srcPadding; + } + +#ifdef TEST_COMPRESSION + int tmpDstPadding = dstPadding; + dstPadding = 0; + byte * testOutBuf = (byte *) _alloca16( width * height ); + CompressYCoCgDXT5Fast_Generic( inBuf, testOutBuf, width, height ); + for ( int j = 0; j < height / 4; j++ ) { + for ( int i = 0; i < width / 4; i++ ) { + byte * ptr1 = outBuf + ( j * width/4 + i ) * 16 + j * tmpDstPadding; + byte * ptr2 = testOutBuf + ( j * width/4 + i ) * 16; + for ( int k = 0; k < 16; k++ ) { + assert( ptr1[k] == ptr2[k] ); + } + } + } + dstPadding = tmpDstPadding; +#endif +} + +/* +======================== +idDxtEncoder::EmitGreenIndices_SSE2 + +params: block - 16-normal block for which to find normal Y indices +paramO: minGreen - Minimal normal Y found +paramO: maxGreen - Maximal normal Y found +======================== +*/ +void idDxtEncoder::EmitGreenIndices_SSE2( const byte *block, const int channelBitOffset, const int minGreen, const int maxGreen ) { + __m128i block0 = *((__m128i *)(&block[ 0])); + __m128i block1 = *((__m128i *)(&block[16])); + __m128i block2 = *((__m128i *)(&block[32])); + __m128i block3 = *((__m128i *)(&block[48])); + __m128c temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + temp7 = _mm_cvtsi32_si128( channelBitOffset ); + + temp0 = _mm_srl_epi32( block0, temp7 ); + temp5 = _mm_srl_epi32( block1, temp7 ); + temp6 = _mm_srl_epi32( block2, temp7 ); + temp4 = _mm_srl_epi32( block3, temp7 ); + + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_dword_byte_mask ); + + temp0 = _mm_packus_epi16( temp0, temp5 ); + temp6 = _mm_packus_epi16( temp6, temp4 ); + + //--------------------- + + temp2 = _mm_cvtsi32_si128( maxGreen ); + temp2 = _mm_shufflelo_epi16( temp2, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp3 = _mm_cvtsi32_si128( minGreen ); + temp3 = _mm_shufflelo_epi16( temp3, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp2 = _mm_mullo_epi16( temp2, (const __m128i &)SIMD_SSE2_word_scale_5_3_1 ); + temp3 = _mm_mullo_epi16( temp3, (const __m128i &)SIMD_SSE2_word_scale_1_3_5 ); + temp2 = _mm_add_epi16( temp2, (const __m128i &)SIMD_SSE2_word_3 ); + temp3 = _mm_add_epi16( temp3, temp2 ); + temp3 = _mm_mulhi_epi16( temp3, (const __m128i &)SIMD_SSE2_word_div_by_6 ); + + temp1 = _mm_shufflelo_epi16( temp3, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp2 = _mm_shufflelo_epi16( temp3, R_SHUFFLE_D( 1, 1, 1, 1 ) ); + temp3 = _mm_shufflelo_epi16( temp3, R_SHUFFLE_D( 2, 2, 2, 2 ) ); + + temp1 = _mm_shuffle_epi32( temp1, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp2 = _mm_shuffle_epi32( temp2, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + temp3 = _mm_shuffle_epi32( temp3, R_SHUFFLE_D( 0, 0, 0, 0 ) ); + + temp1 = _mm_packus_epi16( temp1, temp1 ); + temp2 = _mm_packus_epi16( temp2, temp2 ); + temp3 = _mm_packus_epi16( temp3, temp3 ); + + temp0 = _mm_packus_epi16( temp0, temp6 ); + + temp1 = _mm_max_epu8( temp1, temp0 ); + temp2 = _mm_max_epu8( temp2, temp0 ); + temp3 = _mm_max_epu8( temp3, temp0 ); + temp1 = _mm_cmpeq_epi8( temp1, temp0 ); + temp2 = _mm_cmpeq_epi8( temp2, temp0 ); + temp3 = _mm_cmpeq_epi8( temp3, temp0 ); + temp0 = (const __m128i &)SIMD_SSE2_byte_4; + + temp0 = _mm_adds_epi8( temp0, temp1 ); + temp2 = _mm_adds_epi8( temp2, temp3 ); + temp0 = _mm_adds_epi8( temp0, temp2 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_byte_3 ); + temp4 = (const __m128i &)SIMD_SSE2_byte_2; + temp4 = _mm_cmpgt_epi8( temp4, temp0 ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_byte_1 ); + + temp0 = _mm_xor_si128( temp0, temp4 ); + temp4 = _mm_srli_epi64( temp0, 8 - 2 ); + temp5 = _mm_srli_epi64( temp0, 16 - 4 ); + temp6 = _mm_srli_epi64( temp0, 24 - 6 ); + temp7 = _mm_srli_epi64( temp0, 32 - 8 ); + + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_dword_color_bit_mask1 ); + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_dword_color_bit_mask2 ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_dword_color_bit_mask3 ); + temp7 = _mm_and_si128( temp7, (const __m128i &)SIMD_SSE2_dword_color_bit_mask4 ); + temp5 = _mm_or_si128( temp5, temp4 ); + temp7 = _mm_or_si128( temp7, temp6 ); + temp7 = _mm_or_si128( temp7, temp5 ); + + temp4 = _mm_srli_epi64( temp0, 40 - 10 ); + temp5 = _mm_srli_epi64( temp0, 48 - 12 ); + temp6 = _mm_srli_epi64( temp0, 56 - 14 ); + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_dword_color_bit_mask0 ); + temp4 = _mm_and_si128( temp4, (const __m128i &)SIMD_SSE2_dword_color_bit_mask5 ); + temp5 = _mm_and_si128( temp5, (const __m128i &)SIMD_SSE2_dword_color_bit_mask6 ); + temp6 = _mm_and_si128( temp6, (const __m128i &)SIMD_SSE2_dword_color_bit_mask7 ); + temp4 = _mm_or_si128( temp4, temp5 ); + temp0 = _mm_or_si128( temp0, temp6 ); + temp7 = _mm_or_si128( temp7, temp4 ); + temp7 = _mm_or_si128( temp7, temp0 ); + + temp7 = _mm_shuffle_epi32( temp7, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + temp7 = _mm_shufflelo_epi16( temp7, R_SHUFFLE_D( 0, 2, 1, 3 ) ); + + int result = _mm_cvtsi128_si32( temp7 ); + EmitUInt( result ); +} + +/* +======================== +idDxtEncoder::InsetNormalsBBoxDXT5_SSE2 +======================== +*/ +void idDxtEncoder::InsetNormalsBBoxDXT5_SSE2( byte *minNormal, byte *maxNormal ) const { + __m128i temp0, temp1, temp2, temp3; + + temp0 = _mm_cvtsi32_si128( *(int *)minNormal ); + temp1 = _mm_cvtsi32_si128( *(int *)maxNormal ); + + temp0 = _mm_unpacklo_epi8( temp0, (const __m128i &)SIMD_SSE2_byte_0 ); + temp1 = _mm_unpacklo_epi8( temp1, (const __m128i &)SIMD_SSE2_byte_0 ); + + temp2 = _mm_sub_epi16( temp1, temp0 ); + temp2 = _mm_sub_epi16( temp2, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5Round ); + temp2 = _mm_and_si128( temp2, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5Mask ); // xmm2 = inset (1 & 3) + + temp0 = _mm_mullo_epi16( temp0, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5ShiftUp ); + temp1 = _mm_mullo_epi16( temp1, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5ShiftUp ); + temp0 = _mm_add_epi16( temp0, temp2 ); + temp1 = _mm_sub_epi16( temp1, temp2 ); + temp0 = _mm_mulhi_epi16( temp0, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5ShiftDown ); // xmm0 = mini + temp1 = _mm_mulhi_epi16( temp1, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5ShiftDown ); // xmm1 = maxi + + // mini and maxi must be >= 0 and <= 255 + temp0 = _mm_max_epi16( temp0, (const __m128i &)SIMD_SSE2_word_0 ); + temp1 = _mm_max_epi16( temp1, (const __m128i &)SIMD_SSE2_word_0 ); + temp0 = _mm_min_epi16( temp0, (const __m128i &)SIMD_SSE2_word_255 ); + temp1 = _mm_min_epi16( temp1, (const __m128i &)SIMD_SSE2_word_255 ); + + temp0 = _mm_and_si128( temp0, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5QuantMask ); + temp1 = _mm_and_si128( temp1, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5QuantMask ); + temp2 = _mm_mulhi_epi16( temp0, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5Rep ); + temp3 = _mm_mulhi_epi16( temp1, (const __m128i &)SIMD_SSE2_word_insetNormalDXT5Rep ); + temp0 = _mm_or_si128( temp0, temp2 ); + temp1 = _mm_or_si128( temp1, temp3 ); + temp0 = _mm_packus_epi16( temp0, temp0 ); + temp1 = _mm_packus_epi16( temp1, temp1 ); + + *(int *)minNormal = _mm_cvtsi128_si32( temp0 ); + *(int *)maxNormal = _mm_cvtsi128_si32( temp1 ); +} + +/* +======================== +idDxtEncoder::CompressNormalMapDXT5Fast_SSE2 + +params: inBuf - image to compress in _y_x component order +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +void idDxtEncoder::CompressNormalMapDXT5Fast_SSE2( const byte *inBuf, byte *outBuf, int width, int height ) { + ALIGN16( byte block[64] ); + ALIGN16( byte normal1[4] ); + ALIGN16( byte normal2[4] ); + + assert( width >= 4 && ( width & 3 ) == 0 ); + assert( height >= 4 && ( height & 3 ) == 0 ); + + this->width = width; + this->height = height; + this->outData = outBuf; + + for ( int j = 0; j < height; j += 4, inBuf += width * 4*4 ) { + for ( int i = 0; i < width; i += 4 ) { + ExtractBlock_SSE2( inBuf + i * 4, width, block ); + GetMinMaxBBox_SSE2( block, normal1, normal2 ); + InsetNormalsBBoxDXT5_SSE2( normal1, normal2 ); + + // Write out Nx into alpha channel. + EmitByte( normal2[3] ); + EmitByte( normal1[3] ); + EmitAlphaIndices_SSE2( block, 3*8, normal1[3], normal2[3] ); + + // Write out Ny into green channel. + EmitUShort( ColorTo565( block[0], normal2[1], block[2] ) ); + EmitUShort( ColorTo565( block[0], normal1[1], block[2] ) ); + EmitGreenIndices_SSE2( block, 1*8, normal1[1], normal2[1] ); + } + outData += dstPadding; + inBuf += srcPadding; + } + +#ifdef TEST_COMPRESSION + int tmpDstPadding = dstPadding; + dstPadding = 0; + byte * testOutBuf = (byte *) _alloca16( width * height ); + CompressNormalMapDXT5Fast_Generic( inBuf, testOutBuf, width, height ); + for ( int j = 0; j < height / 4; j++ ) { + for ( int i = 0; i < width / 4; i++ ) { + byte * ptr1 = outBuf + ( j * width/4 + i ) * 16 + j * tmpDstPadding; + byte * ptr2 = testOutBuf + ( j * width/4 + i ) * 16; + for ( int k = 0; k < 16; k++ ) { + assert( ptr1[k] == ptr2[k] ); + } + } + } + dstPadding = tmpDstPadding; +#endif +} + diff --git a/neo/renderer/Font.cpp b/neo/renderer/Font.cpp new file mode 100644 index 00000000..6fc2fc94 --- /dev/null +++ b/neo/renderer/Font.cpp @@ -0,0 +1,414 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "Font.h" + +const char * DEFAULT_FONT = "Arial_Narrow"; + +static const float old_scale2 = 0.6f; +static const float old_scale1 = 0.3f; + +/* +============================== +Old_SelectValueForScale +============================== +*/ +ID_INLINE float Old_SelectValueForScale( float scale, float v0, float v1, float v2 ) { + return ( scale >= old_scale2 ) ? v2 : ( scale >= old_scale1 ) ? v1 : v0; +} + +/* +============================== +idFont::RemapFont +============================== +*/ +idFont * idFont::RemapFont( const char * baseName ) { + idStr cleanName = baseName; + + if ( cleanName == DEFAULT_FONT ) { + return NULL; + } + + const char * remapped = idLocalization::FindString( "#font_" + cleanName ); + if ( remapped != NULL ) { + return renderSystem->RegisterFont( remapped ); + } + + const char * wildcard = idLocalization::FindString( "#font_*" ); + if ( wildcard != NULL && cleanName.Icmp( wildcard ) != 0 ) { + return renderSystem->RegisterFont( wildcard ); + } + + // Note single | so both sides are always executed + if ( cleanName.ReplaceChar( ' ', '_' ) | cleanName.ReplaceChar( '-', '_' ) ) { + return renderSystem->RegisterFont( cleanName ); + } + + return NULL; +} + +/* +============================== +idFont::~idFont +============================== +*/ +idFont::~idFont() { + delete fontInfo; +} + +/* +============================== +idFont::idFont +============================== +*/ +idFont::idFont( const char * n ) : name( n ) { + fontInfo = NULL; + alias = RemapFont( n ); + + if ( alias != NULL ) { + // Make sure we don't have a circular reference + for ( idFont * f = alias; f != NULL; f = f->alias ) { + if ( f == this ) { + idLib::FatalError( "Font alias \"%s\" is a circular reference!", n ); + } + } + return; + } + + if ( !LoadFont() ) { + if ( name.Icmp( DEFAULT_FONT ) == 0 ) { + idLib::FatalError( "Could not load default font \"%s\"", DEFAULT_FONT ); + } else { + idLib::Warning( "Could not load font %s", n ); + alias = renderSystem->RegisterFont( DEFAULT_FONT ); + } + } +} + +struct oldGlyphInfo_t { + int height; // number of scan lines + int top; // top of glyph in buffer + int bottom; // bottom of glyph in buffer + int pitch; // width for copying + int xSkip; // x adjustment + int imageWidth; // width of actual image + int imageHeight; // height of actual image + float s; // x offset in image where glyph starts + float t; // y offset in image where glyph starts + float s2; + float t2; + int junk; + char materialName[32]; +}; +static const int GLYPHS_PER_FONT = 256; + +/* +============================== +LoadOldGlyphData +============================== +*/ +bool LoadOldGlyphData( const char * filename, oldGlyphInfo_t glyphInfo[GLYPHS_PER_FONT] ) { + idFile * fd = fileSystem->OpenFileRead( filename ); + if ( fd == NULL ) { + return false; + } + fd->Read( glyphInfo, GLYPHS_PER_FONT * sizeof( oldGlyphInfo_t ) ); + for ( int i = 0; i < GLYPHS_PER_FONT; i++ ) { + idSwap::Little( glyphInfo[i].height ); + idSwap::Little( glyphInfo[i].top ); + idSwap::Little( glyphInfo[i].bottom ); + idSwap::Little( glyphInfo[i].pitch ); + idSwap::Little( glyphInfo[i].xSkip ); + idSwap::Little( glyphInfo[i].imageWidth ); + idSwap::Little( glyphInfo[i].imageHeight ); + idSwap::Little( glyphInfo[i].s ); + idSwap::Little( glyphInfo[i].t ); + idSwap::Little( glyphInfo[i].s2 ); + idSwap::Little( glyphInfo[i].t2 ); + assert( glyphInfo[i].imageWidth == glyphInfo[i].pitch ); + assert( glyphInfo[i].imageHeight == glyphInfo[i].height ); + assert( glyphInfo[i].imageWidth == ( glyphInfo[i].s2 - glyphInfo[i].s ) * 256 ); + assert( glyphInfo[i].imageHeight == ( glyphInfo[i].t2 - glyphInfo[i].t ) * 256 ); + assert( glyphInfo[i].junk == 0 ); + } + delete fd; + return true; +} + +/* +============================== +idFont::LoadFont +============================== +*/ +bool idFont::LoadFont() { + idStr fontName = va( "newfonts/%s/48.dat", GetName() ); + idFile * fd = fileSystem->OpenFileRead( fontName ); + if ( fd == NULL ) { + return false; + } + + const int FONT_INFO_VERSION = 42; + const int FONT_INFO_MAGIC = ( FONT_INFO_VERSION | ( 'i' << 24 ) | ( 'd' << 16 ) | ( 'f' << 8 ) ); + + uint32 version = 0; + fd->ReadBig( version ); + if ( version != FONT_INFO_MAGIC ) { + idLib::Warning( "Wrong version in %s", GetName() ); + delete fd; + return false; + } + + fontInfo = new (TAG_FONT) fontInfo_t; + + short pointSize = 0; + + fd->ReadBig( pointSize ); + assert( pointSize == 48 ); + + fd->ReadBig( fontInfo->ascender ); + fd->ReadBig( fontInfo->descender ); + + fd->ReadBig( fontInfo->numGlyphs ); + + fontInfo->glyphData = (glyphInfo_t *)Mem_Alloc( sizeof( glyphInfo_t ) * fontInfo->numGlyphs, TAG_FONT ); + fontInfo->charIndex = (uint32 *)Mem_Alloc( sizeof( uint32 ) * fontInfo->numGlyphs, TAG_FONT ); + + fd->Read( fontInfo->glyphData, fontInfo->numGlyphs * sizeof( glyphInfo_t ) ); + + for( int i = 0; i < fontInfo->numGlyphs; i++ ) { + idSwap::Little( fontInfo->glyphData[i].width ); + idSwap::Little( fontInfo->glyphData[i].height ); + idSwap::Little( fontInfo->glyphData[i].top ); + idSwap::Little( fontInfo->glyphData[i].left ); + idSwap::Little( fontInfo->glyphData[i].xSkip ); + idSwap::Little( fontInfo->glyphData[i].s ); + idSwap::Little( fontInfo->glyphData[i].t ); + } + + fd->Read( fontInfo->charIndex, fontInfo->numGlyphs * sizeof( uint32 ) ); + idSwap::LittleArray( fontInfo->charIndex, fontInfo->numGlyphs ); + + memset( fontInfo->ascii, -1, sizeof( fontInfo->ascii ) ); + for ( int i = 0; i < fontInfo->numGlyphs; i++ ) { + if ( fontInfo->charIndex[i] < 128 ) { + fontInfo->ascii[fontInfo->charIndex[i]] = i; + } else { + // Since the characters are sorted, as soon as we find a non-ascii character, we can stop + break; + } + } + + idStr fontTextureName = fontName; + fontTextureName.SetFileExtension( "tga" ); + + fontInfo->material = declManager->FindMaterial( fontTextureName ); + fontInfo->material->SetSort( SS_GUI ); + + // Load the old glyph data because we want our new fonts to fit in the old glyph metrics + int pointSizes[3] = { 12, 24, 48 }; + float scales[3] = { 4.0f, 2.0f, 1.0f }; + for ( int i = 0; i < 3; i++ ) { + oldGlyphInfo_t oldGlyphInfo[GLYPHS_PER_FONT]; + const char * oldFileName = va( "newfonts/%s/old_%d.dat", GetName(), pointSizes[i] ); + if ( LoadOldGlyphData( oldFileName, oldGlyphInfo ) ) { + int mh = 0; + int mw = 0; + for ( int g = 0; g < GLYPHS_PER_FONT; g++ ) { + if ( mh < oldGlyphInfo[g].height ) { + mh = oldGlyphInfo[g].height; + } + if ( mw < oldGlyphInfo[g].xSkip ) { + mw = oldGlyphInfo[g].xSkip; + } + } + fontInfo->oldInfo[i].maxWidth = scales[i] * mw; + fontInfo->oldInfo[i].maxHeight = scales[i] * mh; + } else { + int mh = 0; + int mw = 0; + for( int g = 0; g < fontInfo->numGlyphs; g++ ) { + if ( mh < fontInfo->glyphData[g].height ) { + mh = fontInfo->glyphData[g].height; + } + if ( mw < fontInfo->glyphData[g].xSkip ) { + mw = fontInfo->glyphData[g].xSkip; + } + } + fontInfo->oldInfo[i].maxWidth = mw; + fontInfo->oldInfo[i].maxHeight = mh; + } + } + delete fd; + return true; +} + +/* +============================== +idFont::GetGlyphIndex +============================== +*/ +int idFont::GetGlyphIndex( uint32 idx ) const { + if ( idx < 128 ) { + return fontInfo->ascii[idx]; + } + if ( fontInfo->numGlyphs == 0 ) { + return -1; + } + if ( fontInfo->charIndex == NULL ) { + return idx; + } + int len = fontInfo->numGlyphs; + int mid = fontInfo->numGlyphs; + int offset = 0; + while ( mid > 0 ) { + mid = len >> 1; + if ( fontInfo->charIndex[offset+mid] <= idx ) { + offset += mid; + } + len -= mid; + } + return ( fontInfo->charIndex[offset] == idx ) ? offset : -1; +} + +/* +============================== +idFont::GetLineHeight +============================== +*/ +float idFont::GetLineHeight( float scale ) const { + if ( alias != NULL ) { + return alias->GetLineHeight( scale ); + } + if ( fontInfo != NULL ) { + return scale * Old_SelectValueForScale( scale, fontInfo->oldInfo[0].maxHeight, fontInfo->oldInfo[1].maxHeight, fontInfo->oldInfo[2].maxHeight ); + } + return 0.0f; +} + +/* +============================== +idFont::GetAscender +============================== +*/ +float idFont::GetAscender( float scale ) const { + if ( alias != NULL ) { + return alias->GetAscender( scale ); + } + if ( fontInfo != NULL ) { + return scale * fontInfo->ascender; + } + return 0.0f; +} + +/* +============================== +idFont::GetMaxCharWidth +============================== +*/ +float idFont::GetMaxCharWidth( float scale ) const { + if ( alias != NULL ) { + return alias->GetMaxCharWidth( scale ); + } + if ( fontInfo != NULL ) { + return scale * Old_SelectValueForScale( scale, fontInfo->oldInfo[0].maxWidth, fontInfo->oldInfo[1].maxWidth, fontInfo->oldInfo[2].maxWidth ); + } + return 0.0f; +} + +/* +============================== +idFont::GetGlyphWidth +============================== +*/ +float idFont::GetGlyphWidth( float scale, uint32 idx ) const { + if ( alias != NULL ) { + return alias->GetGlyphWidth( scale, idx ); + } + if ( fontInfo != NULL ) { + int i = GetGlyphIndex( idx ); + const int asterisk = 42; + if ( i == -1 && idx != asterisk ) { + i = GetGlyphIndex( asterisk ); + } + if ( i >= 0 ) { + return scale * fontInfo->glyphData[i].xSkip; + } + } + return 0.0f; +} + +/* +============================== +idFont::GetScaledGlyph +============================== +*/ +void idFont::GetScaledGlyph( float scale, uint32 idx, scaledGlyphInfo_t & glyphInfo ) const { + if ( alias != NULL ) { + return alias->GetScaledGlyph( scale, idx, glyphInfo ); + } + if ( fontInfo != NULL ) { + int i = GetGlyphIndex( idx ); + const int asterisk = 42; + if ( i == -1 && idx != asterisk ) { + i = GetGlyphIndex( asterisk ); + } + if ( i >= 0 ) { + float invMaterialWidth = 1.0f / fontInfo->material->GetImageWidth(); + float invMaterialHeight = 1.0f / fontInfo->material->GetImageHeight(); + glyphInfo_t & gi = fontInfo->glyphData[i]; + glyphInfo.xSkip = scale * gi.xSkip; + glyphInfo.top = scale * gi.top; + glyphInfo.left = scale * gi.left; + glyphInfo.width = scale * gi.width; + glyphInfo.height = scale * gi.height; + glyphInfo.s1 = ( gi.s - 0.5f ) * invMaterialWidth; + glyphInfo.t1 = ( gi.t - 0.5f ) * invMaterialHeight; + glyphInfo.s2 = ( gi.s + gi.width + 0.5f ) * invMaterialWidth; + glyphInfo.t2 = ( gi.t + gi.height + 0.5f ) * invMaterialHeight; + glyphInfo.material = fontInfo->material; + return; + } + } + memset( &glyphInfo, 0, sizeof( glyphInfo ) ); +} + +/* +============================== +idFont::Touch +============================== +*/ +void idFont::Touch() { + if ( alias != NULL ) { + alias->Touch(); + } + if ( fontInfo != NULL ) { + const_cast( fontInfo->material )->EnsureNotPurged(); + fontInfo->material->SetSort( SS_GUI ); + } +} diff --git a/neo/renderer/Font.h b/neo/renderer/Font.h new file mode 100644 index 00000000..16c2aa94 --- /dev/null +++ b/neo/renderer/Font.h @@ -0,0 +1,103 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __FONT_H__ +#define __FONT_H__ + +struct scaledGlyphInfo_t { + float top, left; + float width, height; + float xSkip; + float s1, t1, s2, t2; + const class idMaterial * material; +}; + +class idFont { +public: + idFont( const char * n ); + ~idFont(); + + void Touch(); + + const char * GetName() const { return name; } + + float GetLineHeight( float scale ) const; + float GetAscender( float scale ) const; + float GetMaxCharWidth( float scale ) const; + + float GetGlyphWidth( float scale, uint32 idx ) const; + void GetScaledGlyph( float scale, uint32 idx, scaledGlyphInfo_t & glyphInfo ) const; + +private: + static idFont * RemapFont( const char * baseName ); + + int GetGlyphIndex( uint32 idx ) const; + + bool LoadFont(); + + struct glyphInfo_t { + byte width; // width of glyph in pixels + byte height; // height of glyph in pixels + char top; // distance in pixels from the base line to the top of the glyph + char left; // distance in pixels from the pen to the left edge of the glyph + byte xSkip; // x adjustment after rendering this glyph + uint16 s; // x offset in image where glyph starts (in pixels) + uint16 t; // y offset in image where glyph starts (in pixels) + }; + struct fontInfo_t { + struct oldInfo_t { + float maxWidth; + float maxHeight; + } oldInfo[3]; + + short ascender; + short descender; + + short numGlyphs; + glyphInfo_t * glyphData; + + // This is a sorted array of all characters in the font + // This maps directly to glyphData, so if charIndex[0] is 42 then glyphData[0] is character 42 + uint32 * charIndex; + + // As an optimization, provide a direct mapping for the ascii character set + char ascii[128]; + + const idMaterial * material; + }; + + // base name of the font (minus "fonts/" and ".dat") + idStr name; + + // Fonts can be aliases to other fonts + idFont * alias; + + // If the font is NOT an alias, this is where the font data is located + fontInfo_t * fontInfo; +}; + +#endif diff --git a/neo/renderer/GLMatrix.cpp b/neo/renderer/GLMatrix.cpp new file mode 100644 index 00000000..9c188437 --- /dev/null +++ b/neo/renderer/GLMatrix.cpp @@ -0,0 +1,423 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +========================================================================================== + +OLD MATRIX MATH + +========================================================================================== +*/ + +/* +====================== +R_AxisToModelMatrix +====================== +*/ +void R_AxisToModelMatrix( const idMat3 &axis, const idVec3 &origin, float modelMatrix[16] ) { + modelMatrix[0 * 4 + 0] = axis[0][0]; + modelMatrix[1 * 4 + 0] = axis[1][0]; + modelMatrix[2 * 4 + 0] = axis[2][0]; + modelMatrix[3 * 4 + 0] = origin[0]; + + modelMatrix[0 * 4 + 1] = axis[0][1]; + modelMatrix[1 * 4 + 1] = axis[1][1]; + modelMatrix[2 * 4 + 1] = axis[2][1]; + modelMatrix[3 * 4 + 1] = origin[1]; + + modelMatrix[0 * 4 + 2] = axis[0][2]; + modelMatrix[1 * 4 + 2] = axis[1][2]; + modelMatrix[2 * 4 + 2] = axis[2][2]; + modelMatrix[3 * 4 + 2] = origin[2]; + + modelMatrix[0 * 4 + 3] = 0.0f; + modelMatrix[1 * 4 + 3] = 0.0f; + modelMatrix[2 * 4 + 3] = 0.0f; + modelMatrix[3 * 4 + 3] = 1.0f; +} + +/* +========================== +R_MatrixMultiply +========================== +*/ +void R_MatrixMultiply( const float a[16], const float b[16], float out[16] ) { + + __m128 a0 = _mm_loadu_ps( a + 0*4 ); + __m128 a1 = _mm_loadu_ps( a + 1*4 ); + __m128 a2 = _mm_loadu_ps( a + 2*4 ); + __m128 a3 = _mm_loadu_ps( a + 3*4 ); + + __m128 b0 = _mm_loadu_ps( b + 0*4 ); + __m128 b1 = _mm_loadu_ps( b + 1*4 ); + __m128 b2 = _mm_loadu_ps( b + 2*4 ); + __m128 b3 = _mm_loadu_ps( b + 3*4 ); + + __m128 t0 = _mm_mul_ps( _mm_splat_ps( a0, 0 ), b0 ); + __m128 t1 = _mm_mul_ps( _mm_splat_ps( a1, 0 ), b0 ); + __m128 t2 = _mm_mul_ps( _mm_splat_ps( a2, 0 ), b0 ); + __m128 t3 = _mm_mul_ps( _mm_splat_ps( a3, 0 ), b0 ); + + t0 = _mm_add_ps( t0, _mm_mul_ps( _mm_splat_ps( a0, 1 ), b1 ) ); + t1 = _mm_add_ps( t1, _mm_mul_ps( _mm_splat_ps( a1, 1 ), b1 ) ); + t2 = _mm_add_ps( t2, _mm_mul_ps( _mm_splat_ps( a2, 1 ), b1 ) ); + t3 = _mm_add_ps( t3, _mm_mul_ps( _mm_splat_ps( a3, 1 ), b1 ) ); + + t0 = _mm_add_ps( t0, _mm_mul_ps( _mm_splat_ps( a0, 2 ), b2 ) ); + t1 = _mm_add_ps( t1, _mm_mul_ps( _mm_splat_ps( a1, 2 ), b2 ) ); + t2 = _mm_add_ps( t2, _mm_mul_ps( _mm_splat_ps( a2, 2 ), b2 ) ); + t3 = _mm_add_ps( t3, _mm_mul_ps( _mm_splat_ps( a3, 2 ), b2 ) ); + + t0 = _mm_add_ps( t0, _mm_mul_ps( _mm_splat_ps( a0, 3 ), b3 ) ); + t1 = _mm_add_ps( t1, _mm_mul_ps( _mm_splat_ps( a1, 3 ), b3 ) ); + t2 = _mm_add_ps( t2, _mm_mul_ps( _mm_splat_ps( a2, 3 ), b3 ) ); + t3 = _mm_add_ps( t3, _mm_mul_ps( _mm_splat_ps( a3, 3 ), b3 ) ); + + _mm_storeu_ps( out + 0*4, t0 ); + _mm_storeu_ps( out + 1*4, t1 ); + _mm_storeu_ps( out + 2*4, t2 ); + _mm_storeu_ps( out + 3*4, t3 ); + +} + +/* +====================== +R_MatrixTranspose +====================== +*/ +void R_MatrixTranspose( const float in[16], float out[16] ) { + for ( int i = 0; i < 4; i++ ) { + for ( int j = 0; j < 4; j++ ) { + out[i*4+j] = in[j*4+i]; + } + } +} + +/* +========================== +R_TransformModelToClip +========================== +*/ +void R_TransformModelToClip( const idVec3 &src, const float *modelMatrix, const float *projectionMatrix, idPlane &eye, idPlane &dst ) { + for ( int i = 0; i < 4; i++ ) { + eye[i] = modelMatrix[i + 0 * 4] * src[0] + + modelMatrix[i + 1 * 4] * src[1] + + modelMatrix[i + 2 * 4] * src[2] + + modelMatrix[i + 3 * 4]; + } + + for ( int i = 0; i < 4; i++ ) { + dst[i] = projectionMatrix[i + 0 * 4] * eye[0] + + projectionMatrix[i + 1 * 4] * eye[1] + + projectionMatrix[i + 2 * 4] * eye[2] + + projectionMatrix[i + 3 * 4] * eye[3]; + } +} + +/* +========================== +R_TransformClipToDevice + +Clip to normalized device coordinates +========================== +*/ +void R_TransformClipToDevice( const idPlane &clip, idVec3 &ndc ) { + const float invW = 1.0f / clip[3]; + ndc[0] = clip[0] * invW; + ndc[1] = clip[1] * invW; + ndc[2] = clip[2] * invW; // NOTE: in D3D this is in the range [0,1] +} + +/* +========================== +R_GlobalToNormalizedDeviceCoordinates + +-1 to 1 range in x, y, and z +========================== +*/ +void R_GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ) { + idPlane view; + idPlane clip; + + // _D3XP use tr.primaryView when there is no tr.viewDef + const viewDef_t * viewDef = ( tr.viewDef != NULL ) ? tr.viewDef : tr.primaryView; + + for ( int i = 0; i < 4; i ++ ) { + view[i] = viewDef->worldSpace.modelViewMatrix[i + 0 * 4] * global[0] + + viewDef->worldSpace.modelViewMatrix[i + 1 * 4] * global[1] + + viewDef->worldSpace.modelViewMatrix[i + 2 * 4] * global[2] + + viewDef->worldSpace.modelViewMatrix[i + 3 * 4]; + } + + for ( int i = 0; i < 4; i ++ ) { + clip[i] = viewDef->projectionMatrix[i + 0 * 4] * view[0] + + viewDef->projectionMatrix[i + 1 * 4] * view[1] + + viewDef->projectionMatrix[i + 2 * 4] * view[2] + + viewDef->projectionMatrix[i + 3 * 4] * view[3]; + } + + const float invW = 1.0f / clip[3]; + ndc[0] = clip[0] * invW; + ndc[1] = clip[1] * invW; + ndc[2] = clip[2] * invW; // NOTE: in D3D this is in the range [0,1] +} + +/* +====================== +R_LocalPointToGlobal + +NOTE: assumes no skewing or scaling transforms +====================== +*/ +void R_LocalPointToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + out[0] = in[0] * modelMatrix[0 * 4 + 0] + in[1] * modelMatrix[1 * 4 + 0] + in[2] * modelMatrix[2 * 4 + 0] + modelMatrix[3 * 4 + 0]; + out[1] = in[0] * modelMatrix[0 * 4 + 1] + in[1] * modelMatrix[1 * 4 + 1] + in[2] * modelMatrix[2 * 4 + 1] + modelMatrix[3 * 4 + 1]; + out[2] = in[0] * modelMatrix[0 * 4 + 2] + in[1] * modelMatrix[1 * 4 + 2] + in[2] * modelMatrix[2 * 4 + 2] + modelMatrix[3 * 4 + 2]; +} + +/* +====================== +R_GlobalPointToLocal + +NOTE: assumes no skewing or scaling transforms +====================== +*/ +void R_GlobalPointToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + idVec3 temp; + + temp[0] = in[0] - modelMatrix[3 * 4 + 0]; + temp[1] = in[1] - modelMatrix[3 * 4 + 1]; + temp[2] = in[2] - modelMatrix[3 * 4 + 2]; + + out[0] = temp[0] * modelMatrix[0 * 4 + 0] + temp[1] * modelMatrix[0 * 4 + 1] + temp[2] * modelMatrix[0 * 4 + 2]; + out[1] = temp[0] * modelMatrix[1 * 4 + 0] + temp[1] * modelMatrix[1 * 4 + 1] + temp[2] * modelMatrix[1 * 4 + 2]; + out[2] = temp[0] * modelMatrix[2 * 4 + 0] + temp[1] * modelMatrix[2 * 4 + 1] + temp[2] * modelMatrix[2 * 4 + 2]; +} + +/* +====================== +R_LocalVectorToGlobal + +NOTE: assumes no skewing or scaling transforms +====================== +*/ +void R_LocalVectorToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + out[0] = in[0] * modelMatrix[0 * 4 + 0] + in[1] * modelMatrix[1 * 4 + 0] + in[2] * modelMatrix[2 * 4 + 0]; + out[1] = in[0] * modelMatrix[0 * 4 + 1] + in[1] * modelMatrix[1 * 4 + 1] + in[2] * modelMatrix[2 * 4 + 1]; + out[2] = in[0] * modelMatrix[0 * 4 + 2] + in[1] * modelMatrix[1 * 4 + 2] + in[2] * modelMatrix[2 * 4 + 2]; +} + +/* +====================== +R_GlobalVectorToLocal + +NOTE: assumes no skewing or scaling transforms +====================== +*/ +void R_GlobalVectorToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + out[0] = in[0] * modelMatrix[0 * 4 + 0] + in[1] * modelMatrix[0 * 4 + 1] + in[2] * modelMatrix[0 * 4 + 2]; + out[1] = in[0] * modelMatrix[1 * 4 + 0] + in[1] * modelMatrix[1 * 4 + 1] + in[2] * modelMatrix[1 * 4 + 2]; + out[2] = in[0] * modelMatrix[2 * 4 + 0] + in[1] * modelMatrix[2 * 4 + 1] + in[2] * modelMatrix[2 * 4 + 2]; +} + +/* +====================== +R_GlobalPlaneToLocal + +NOTE: assumes no skewing or scaling transforms +====================== +*/ +void R_GlobalPlaneToLocal( const float modelMatrix[16], const idPlane &in, idPlane &out ) { + out[0] = in[0] * modelMatrix[0 * 4 + 0] + in[1] * modelMatrix[0 * 4 + 1] + in[2] * modelMatrix[0 * 4 + 2]; + out[1] = in[0] * modelMatrix[1 * 4 + 0] + in[1] * modelMatrix[1 * 4 + 1] + in[2] * modelMatrix[1 * 4 + 2]; + out[2] = in[0] * modelMatrix[2 * 4 + 0] + in[1] * modelMatrix[2 * 4 + 1] + in[2] * modelMatrix[2 * 4 + 2]; + out[3] = in[0] * modelMatrix[3 * 4 + 0] + in[1] * modelMatrix[3 * 4 + 1] + in[2] * modelMatrix[3 * 4 + 2] + in[3]; +} + +/* +====================== +R_LocalPlaneToGlobal + +NOTE: assumes no skewing or scaling transforms +====================== +*/ +void R_LocalPlaneToGlobal( const float modelMatrix[16], const idPlane &in, idPlane &out ) { + out[0] = in[0] * modelMatrix[0 * 4 + 0] + in[1] * modelMatrix[1 * 4 + 0] + in[2] * modelMatrix[2 * 4 + 0]; + out[1] = in[0] * modelMatrix[0 * 4 + 1] + in[1] * modelMatrix[1 * 4 + 1] + in[2] * modelMatrix[2 * 4 + 1]; + out[2] = in[0] * modelMatrix[0 * 4 + 2] + in[1] * modelMatrix[1 * 4 + 2] + in[2] * modelMatrix[2 * 4 + 2]; + out[3] = in[3] - modelMatrix[3 * 4 + 0] * out[0] - modelMatrix[3 * 4 + 1] * out[1] - modelMatrix[3 * 4 + 2] * out[2]; +} + +/* +========================================================================================== + +WORLD/VIEW/PROJECTION MATRIX SETUP + +========================================================================================== +*/ + +/* +====================== +R_SetupViewMatrix + +Sets up the world to view matrix for a given viewParm +====================== +*/ +void R_SetupViewMatrix( viewDef_t *viewDef ) { + static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 + }; + + viewEntity_t *world = &viewDef->worldSpace; + memset( world, 0, sizeof( *world ) ); + + // the model matrix is an identity + world->modelMatrix[0*4+0] = 1.0f; + world->modelMatrix[1*4+1] = 1.0f; + world->modelMatrix[2*4+2] = 1.0f; + + // transform by the camera placement + const idVec3 & origin = viewDef->renderView.vieworg; + const idMat3 & axis = viewDef->renderView.viewaxis; + + float viewerMatrix[16]; + viewerMatrix[0*4+0] = axis[0][0]; + viewerMatrix[1*4+0] = axis[0][1]; + viewerMatrix[2*4+0] = axis[0][2]; + viewerMatrix[3*4+0] = - origin[0] * axis[0][0] - origin[1] * axis[0][1] - origin[2] * axis[0][2]; + + viewerMatrix[0*4+1] = axis[1][0]; + viewerMatrix[1*4+1] = axis[1][1]; + viewerMatrix[2*4+1] = axis[1][2]; + viewerMatrix[3*4+1] = - origin[0] * axis[1][0] - origin[1] * axis[1][1] - origin[2] * axis[1][2]; + + viewerMatrix[0*4+2] = axis[2][0]; + viewerMatrix[1*4+2] = axis[2][1]; + viewerMatrix[2*4+2] = axis[2][2]; + viewerMatrix[3*4+2] = - origin[0] * axis[2][0] - origin[1] * axis[2][1] - origin[2] * axis[2][2]; + + viewerMatrix[0*4+3] = 0.0f; + viewerMatrix[1*4+3] = 0.0f; + viewerMatrix[2*4+3] = 0.0f; + viewerMatrix[3*4+3] = 1.0f; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + R_MatrixMultiply( viewerMatrix, s_flipMatrix, world->modelViewMatrix ); +} + +/* +====================== +R_SetupProjectionMatrix + +This uses the "infinite far z" trick +====================== +*/ +idCVar r_centerX( "r_centerX", "0", CVAR_FLOAT, "projection matrix center adjust" ); +idCVar r_centerY( "r_centerY", "0", CVAR_FLOAT, "projection matrix center adjust" ); + +void R_SetupProjectionMatrix( viewDef_t *viewDef ) { + // random jittering is usefull when multiple + // frames are going to be blended together + // for motion blurred anti-aliasing + float jitterx, jittery; + if ( r_jitter.GetBool() ) { + static idRandom random; + jitterx = random.RandomFloat(); + jittery = random.RandomFloat(); + } else { + jitterx = 0.0f; + jittery = 0.0f; + } + + // + // set up projection matrix + // + const float zNear = ( viewDef->renderView.cramZNear ) ? ( r_znear.GetFloat() * 0.25f ) : r_znear.GetFloat(); + + float ymax = zNear * tan( viewDef->renderView.fov_y * idMath::PI / 360.0f ); + float ymin = -ymax; + + float xmax = zNear * tan( viewDef->renderView.fov_x * idMath::PI / 360.0f ); + float xmin = -xmax; + + const float width = xmax - xmin; + const float height = ymax - ymin; + + const int viewWidth = viewDef->viewport.x2 - viewDef->viewport.x1 + 1; + const int viewHeight = viewDef->viewport.y2 - viewDef->viewport.y1 + 1; + + jitterx = jitterx * width / viewWidth; + jitterx += r_centerX.GetFloat(); + jitterx += viewDef->renderView.stereoScreenSeparation; + xmin += jitterx * width; + xmax += jitterx * width; + + jittery = jittery * height / viewHeight; + jittery += r_centerY.GetFloat(); + ymin += jittery * height; + ymax += jittery * height; + + viewDef->projectionMatrix[0*4+0] = 2.0f * zNear / width; + viewDef->projectionMatrix[1*4+0] = 0.0f; + viewDef->projectionMatrix[2*4+0] = ( xmax + xmin ) / width; // normally 0 + viewDef->projectionMatrix[3*4+0] = 0.0f; + + viewDef->projectionMatrix[0*4+1] = 0.0f; + viewDef->projectionMatrix[1*4+1] = 2.0f * zNear / height; + viewDef->projectionMatrix[2*4+1] = ( ymax + ymin ) / height; // normally 0 + viewDef->projectionMatrix[3*4+1] = 0.0f; + + // this is the far-plane-at-infinity formulation, and + // crunches the Z range slightly so w=0 vertexes do not + // rasterize right at the wraparound point + viewDef->projectionMatrix[0*4+2] = 0.0f; + viewDef->projectionMatrix[1*4+2] = 0.0f; + viewDef->projectionMatrix[2*4+2] = -0.999f; // adjust value to prevent imprecision issues + viewDef->projectionMatrix[3*4+2] = -2.0f * zNear; + + viewDef->projectionMatrix[0*4+3] = 0.0f; + viewDef->projectionMatrix[1*4+3] = 0.0f; + viewDef->projectionMatrix[2*4+3] = -1.0f; + viewDef->projectionMatrix[3*4+3] = 0.0f; + + if ( viewDef->renderView.flipProjection ) { + viewDef->projectionMatrix[1*4+1] = -viewDef->projectionMatrix[1*4+1]; + viewDef->projectionMatrix[1*4+3] = -viewDef->projectionMatrix[1*4+3]; + } +} diff --git a/neo/renderer/GLMatrix.h b/neo/renderer/GLMatrix.h new file mode 100644 index 00000000..e22603e0 --- /dev/null +++ b/neo/renderer/GLMatrix.h @@ -0,0 +1,64 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GLMATRIX_H__ +#define __GLMATRIX_H__ + +/* +========================================================================================== + +This deals with column-major (OpenGL style) matrices where transforms are +applied with right-multiplication. + +This is the old DOOM3 matrix code that should really to be replaced with idRenderMatrix. + +========================================================================================== +*/ + +void R_AxisToModelMatrix( const idMat3 &axis, const idVec3 &origin, float modelMatrix[16] ); +void R_MatrixTranspose( const float in[16], float out[16] ); +void R_MatrixMultiply( const float *a, const float *b, float *out ); + +void R_TransformModelToClip( const idVec3 &src, const float *modelMatrix, const float *projectionMatrix, idPlane &eye, idPlane &dst ); +void R_TransformClipToDevice( const idPlane &clip, idVec3 &ndc ); +void R_GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ); + +// note that these assume a normalized matrix, and will not work with scaled axis +void R_GlobalPointToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); +void R_LocalPointToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); + +void R_GlobalVectorToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); +void R_LocalVectorToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); + +void R_GlobalPlaneToLocal( const float modelMatrix[16], const idPlane &in, idPlane &out ); +void R_LocalPlaneToGlobal( const float modelMatrix[16], const idPlane &in, idPlane &out ); + +void R_SetupViewMatrix( viewDef_t *viewDef ); +void R_SetupProjectionMatrix( viewDef_t *viewDef ); + +#endif /* !__GLMATRIX_H__ */ diff --git a/neo/renderer/GLState.h b/neo/renderer/GLState.h new file mode 100644 index 00000000..d6da8faf --- /dev/null +++ b/neo/renderer/GLState.h @@ -0,0 +1,148 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GLSTATE_H__ +#define __GLSTATE_H__ + +// one/zero is flipped on src/dest so a gl state of 0 is SRC_ONE,DST_ZERO +static const uint64 GLS_SRCBLEND_ONE = 0 << 0; +static const uint64 GLS_SRCBLEND_ZERO = 1 << 0; +static const uint64 GLS_SRCBLEND_DST_COLOR = 2 << 0; +static const uint64 GLS_SRCBLEND_ONE_MINUS_DST_COLOR = 3 << 0; +static const uint64 GLS_SRCBLEND_SRC_ALPHA = 4 << 0; +static const uint64 GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA = 5 << 0; +static const uint64 GLS_SRCBLEND_DST_ALPHA = 6 << 0; +static const uint64 GLS_SRCBLEND_ONE_MINUS_DST_ALPHA = 7 << 0; +static const uint64 GLS_SRCBLEND_BITS = 7 << 0; + +static const uint64 GLS_DSTBLEND_ZERO = 0 << 3; +static const uint64 GLS_DSTBLEND_ONE = 1 << 3; +static const uint64 GLS_DSTBLEND_SRC_COLOR = 2 << 3; +static const uint64 GLS_DSTBLEND_ONE_MINUS_SRC_COLOR = 3 << 3; +static const uint64 GLS_DSTBLEND_SRC_ALPHA = 4 << 3; +static const uint64 GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA = 5 << 3; +static const uint64 GLS_DSTBLEND_DST_ALPHA = 6 << 3; +static const uint64 GLS_DSTBLEND_ONE_MINUS_DST_ALPHA = 7 << 3; +static const uint64 GLS_DSTBLEND_BITS = 7 << 3; + +//------------------------ +// these masks are the inverse, meaning when set the glColorMask value will be 0, +// preventing that channel from being written +//------------------------ +static const uint64 GLS_DEPTHMASK = 1 << 6; +static const uint64 GLS_REDMASK = 1 << 7; +static const uint64 GLS_GREENMASK = 1 << 8; +static const uint64 GLS_BLUEMASK = 1 << 9; +static const uint64 GLS_ALPHAMASK = 1 << 10; +static const uint64 GLS_COLORMASK = (GLS_REDMASK|GLS_GREENMASK|GLS_BLUEMASK); + +static const uint64 GLS_POLYMODE_LINE = 1 << 11; +static const uint64 GLS_POLYGON_OFFSET = 1 << 12; + +static const uint64 GLS_DEPTHFUNC_LESS = 0 << 13; +static const uint64 GLS_DEPTHFUNC_ALWAYS = 1 << 13; +static const uint64 GLS_DEPTHFUNC_GREATER = 2 << 13; +static const uint64 GLS_DEPTHFUNC_EQUAL = 3 << 13; +static const uint64 GLS_DEPTHFUNC_BITS = 3 << 13; + +static const uint64 GLS_BLENDOP_ADD = 0 << 18; +static const uint64 GLS_BLENDOP_SUB = 1 << 18; +static const uint64 GLS_BLENDOP_MIN = 2 << 18; +static const uint64 GLS_BLENDOP_MAX = 3 << 18; +static const uint64 GLS_BLENDOP_BITS = 3 << 18; + +// stencil bits +static const uint64 GLS_STENCIL_FUNC_REF_SHIFT = 20; +static const uint64 GLS_STENCIL_FUNC_REF_BITS = 0xFFll << GLS_STENCIL_FUNC_REF_SHIFT; + +static const uint64 GLS_STENCIL_FUNC_MASK_SHIFT = 28; +static const uint64 GLS_STENCIL_FUNC_MASK_BITS = 0xFFll << GLS_STENCIL_FUNC_MASK_SHIFT; + +#define GLS_STENCIL_MAKE_REF( x ) ( ( (uint64)(x) << GLS_STENCIL_FUNC_REF_SHIFT ) & GLS_STENCIL_FUNC_REF_BITS ) +#define GLS_STENCIL_MAKE_MASK( x ) ( ( (uint64)(x) << GLS_STENCIL_FUNC_MASK_SHIFT ) & GLS_STENCIL_FUNC_MASK_BITS ) + +static const uint64 GLS_STENCIL_FUNC_ALWAYS = 0ull << 36; +static const uint64 GLS_STENCIL_FUNC_LESS = 1ull << 36; +static const uint64 GLS_STENCIL_FUNC_LEQUAL = 2ull << 36; +static const uint64 GLS_STENCIL_FUNC_GREATER = 3ull << 36; +static const uint64 GLS_STENCIL_FUNC_GEQUAL = 4ull << 36; +static const uint64 GLS_STENCIL_FUNC_EQUAL = 5ull << 36; +static const uint64 GLS_STENCIL_FUNC_NOTEQUAL = 6ull << 36; +static const uint64 GLS_STENCIL_FUNC_NEVER = 7ull << 36; +static const uint64 GLS_STENCIL_FUNC_BITS = 7ull << 36; + +static const uint64 GLS_STENCIL_OP_FAIL_KEEP = 0ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_ZERO = 1ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_REPLACE = 2ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_INCR = 3ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_DECR = 4ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_INVERT = 5ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_INCR_WRAP = 6ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_DECR_WRAP = 7ull << 39; +static const uint64 GLS_STENCIL_OP_FAIL_BITS = 7ull << 39; + +static const uint64 GLS_STENCIL_OP_ZFAIL_KEEP = 0ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_ZERO = 1ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_REPLACE = 2ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_INCR = 3ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_DECR = 4ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_INVERT = 5ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_INCR_WRAP = 6ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_DECR_WRAP = 7ull << 42; +static const uint64 GLS_STENCIL_OP_ZFAIL_BITS = 7ull << 42; + +static const uint64 GLS_STENCIL_OP_PASS_KEEP = 0ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_ZERO = 1ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_REPLACE = 2ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_INCR = 3ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_DECR = 4ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_INVERT = 5ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_INCR_WRAP = 6ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_DECR_WRAP = 7ull << 45; +static const uint64 GLS_STENCIL_OP_PASS_BITS = 7ull << 45; + +static const uint64 GLS_ALPHATEST_FUNC_REF_SHIFT = 48; +static const uint64 GLS_ALPHATEST_FUNC_REF_BITS = 0xFFll << GLS_ALPHATEST_FUNC_REF_SHIFT; +#define GLS_ALPHATEST_MAKE_REF( x ) ( ( (uint64)(x) << GLS_ALPHATEST_FUNC_REF_SHIFT ) & GLS_ALPHATEST_FUNC_REF_BITS ) + +static const uint64 GLS_ALPHATEST_FUNC_ALWAYS = 0ull << 56; +static const uint64 GLS_ALPHATEST_FUNC_LESS = 1ull << 56; +static const uint64 GLS_ALPHATEST_FUNC_GREATER = 2ull << 56; +static const uint64 GLS_ALPHATEST_FUNC_EQUAL = 3ull << 56; +static const uint64 GLS_ALPHATEST_FUNC_BITS = 3ull << 56; + +static const uint64 GLS_STENCIL_OP_BITS = GLS_STENCIL_OP_FAIL_BITS | GLS_STENCIL_OP_ZFAIL_BITS | GLS_STENCIL_OP_PASS_BITS; + +static const uint64 GLS_OVERRIDE = 1ull << 63; // override the render prog state + +static const uint64 GLS_DEFAULT = 0; + +#define STENCIL_SHADOW_TEST_VALUE 128 +#define STENCIL_SHADOW_MASK_VALUE 255 + +#endif /* !__GLSTATE_H__ */ diff --git a/neo/renderer/GraphicsAPIWrapper.h b/neo/renderer/GraphicsAPIWrapper.h new file mode 100644 index 00000000..3d413871 --- /dev/null +++ b/neo/renderer/GraphicsAPIWrapper.h @@ -0,0 +1,165 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __GRAPHICSAPIWRAPPER_H__ +#define __GRAPHICSAPIWRAPPER_H__ + +/* +================================================================================================ + + Graphics API wrapper/helper functions + + This wraps platform specific graphics API functionality that is used at run-time. This + functionality is wrapped to avoid excessive conditional compilation and/or code duplication + throughout the run-time rendering code that is shared on all platforms. + + Most other graphics API functions are called for initialization purposes and are called + directly from platform specific code implemented in files in the platform specific folders: + + renderer/OpenGL/ + renderer/DirectX/ + renderer/GCM/ + +================================================================================================ +*/ + +class idImage; +//class idTriangles; +class idRenderModelSurface; +class idDeclRenderProg; +class idRenderTexture; + +static const int MAX_OCCLUSION_QUERIES = 4096; +// returned by GL_GetDeferredQueryResult() when the query is from too long ago and the result is no longer available +static const int OCCLUSION_QUERY_TOO_OLD = -1; + +/* +================================================================================================ + + Platform Specific Context + +================================================================================================ +*/ + + + + +#define USE_CORE_PROFILE + +struct wrapperContext_t { +}; + + +/* +================================================ +wrapperConfig_t +================================================ +*/ +struct wrapperConfig_t { + // rendering options and settings + bool disableStateCaching; + bool lazyBindPrograms; + bool lazyBindParms; + bool lazyBindTextures; + bool stripFragmentBranches; + bool skipDetailTris; + bool singleTriangle; + // values for polygon offset + float polyOfsFactor; + float polyOfsUnits; + // global texture filter settings + int textureMinFilter; + int textureMaxFilter; + int textureMipFilter; + float textureAnisotropy; + float textureLODBias; +}; + +/* +================================================ +wrapperStats_t +================================================ +*/ +struct wrapperStats_t { + int c_queriesIssued; + int c_queriesPassed; + int c_queriesWaitTime; + int c_queriesTooOld; + int c_programsBound; + int c_drawElements; + int c_drawIndices; + int c_drawVertices; +}; + +/* +================================================================================================ + + API + +================================================================================================ +*/ + +void GL_SetWrapperContext( const wrapperContext_t & context ); +void GL_SetWrapperConfig( const wrapperConfig_t & config ); + +void GL_SetTimeDelta( uint64 delta ); // delta from GPU to CPU microseconds +void GL_StartFrame( int frame ); // inserts a timing mark for the start of the GPU frame +void GL_EndFrame(); // inserts a timing mark for the end of the GPU frame +void GL_WaitForEndFrame(); // wait for the GPU to reach the last end frame marker +void GL_GetLastFrameTime( uint64 & startGPUTimeMicroSec, uint64 & endGPUTimeMicroSec ); // GPU time between GL_StartFrame() and GL_EndFrame() +void GL_StartDepthPass( const idScreenRect & rect ); +void GL_FinishDepthPass(); +void GL_GetDepthPassRect( idScreenRect & rect ); + +void GL_SetDefaultState(); +void GL_State( uint64 stateVector, bool forceGlState = false ); +uint64 GL_GetCurrentState(); +uint64 GL_GetCurrentStateMinusStencil(); +void GL_Cull( int cullType ); +void GL_Scissor( int x /* left*/, int y /* bottom */, int w, int h ); +void GL_Viewport( int x /* left */, int y /* bottom */, int w, int h ); +ID_INLINE void GL_Scissor( const idScreenRect & rect ) { GL_Scissor( rect.x1, rect.y1, rect.x2 - rect.x1 + 1, rect.y2 - rect.y1 + 1 ); } +ID_INLINE void GL_Viewport( const idScreenRect & rect ) { GL_Viewport( rect.x1, rect.y1, rect.x2 - rect.x1 + 1, rect.y2 - rect.y1 + 1 ); } +ID_INLINE void GL_ViewportAndScissor( int x, int y, int w, int h ) { GL_Viewport( x, y, w, h ); GL_Scissor( x, y, w, h ); } +ID_INLINE void GL_ViewportAndScissor( const idScreenRect& rect ) { GL_Viewport( rect ); GL_Scissor( rect ); } +void GL_Clear( bool color, bool depth, bool stencil, byte stencilValue, float r, float g, float b, float a ); +void GL_PolygonOffset( float scale, float bias ); +void GL_DepthBoundsTest( const float zmin, const float zmax ); +void GL_Color( float * color ); +void GL_Color( float r, float g, float b ); +void GL_Color( float r, float g, float b, float a ); +void GL_SelectTexture( int unit ); + +void GL_Flush(); // flush the GPU command buffer +void GL_Finish(); // wait for the GPU to have executed all commands +void GL_CheckErrors(); + +wrapperStats_t GL_GetCurrentStats(); +void GL_ClearStats(); + + +#endif // !__GRAPHICSAPIWRAPPER_H__ diff --git a/neo/renderer/GuiModel.cpp b/neo/renderer/GuiModel.cpp new file mode 100644 index 00000000..a57942d1 --- /dev/null +++ b/neo/renderer/GuiModel.cpp @@ -0,0 +1,371 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +const float idGuiModel::STEREO_DEPTH_NEAR = 0.0f; +const float idGuiModel::STEREO_DEPTH_MID = 0.5f; +const float idGuiModel::STEREO_DEPTH_FAR = 1.0f; + +/* +================ +idGuiModel::idGuiModel +================ +*/ +idGuiModel::idGuiModel() { + // identity color for drawsurf register evaluation + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + shaderParms[i] = 1.0f; + } +} + +/* +================ +idGuiModel::Clear + +Begins collecting draw commands into surfaces +================ +*/ +void idGuiModel::Clear() { + surfaces.SetNum( 0 ); + AdvanceSurf(); +} + +/* +================ +idGuiModel::WriteToDemo +================ +*/ +void idGuiModel::WriteToDemo( idDemoFile *demo ) { +} + +/* +================ +idGuiModel::ReadFromDemo +================ +*/ +void idGuiModel::ReadFromDemo( idDemoFile *demo ) { +} + +/* +================ +idGuiModel::BeginFrame +================ +*/ +void idGuiModel::BeginFrame() { + vertexBlock = vertexCache.AllocVertex( NULL, ALIGN( MAX_VERTS * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + indexBlock = vertexCache.AllocIndex( NULL, ALIGN( MAX_INDEXES * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + vertexPointer = (idDrawVert *)vertexCache.MappedVertexBuffer( vertexBlock ); + indexPointer = (triIndex_t *)vertexCache.MappedIndexBuffer( indexBlock ); + numVerts = 0; + numIndexes = 0; + Clear(); +} + +idCVar stereoRender_defaultGuiDepth( "stereoRender_defaultGuiDepth", "0", CVAR_RENDERER, "Fraction of separation when not specified" ); +/* +================ +EmitSurfaces + +For full screen GUIs, we can add in per-surface stereoscopic depth effects +================ +*/ +void idGuiModel::EmitSurfaces( float modelMatrix[16], float modelViewMatrix[16], + bool depthHack, bool allowFullScreenStereoDepth, bool linkAsEntity ) { + + viewEntity_t * guiSpace = (viewEntity_t *)R_ClearedFrameAlloc( sizeof( *guiSpace ), FRAME_ALLOC_VIEW_ENTITY ); + memcpy( guiSpace->modelMatrix, modelMatrix, sizeof( guiSpace->modelMatrix ) ); + memcpy( guiSpace->modelViewMatrix, modelViewMatrix, sizeof( guiSpace->modelViewMatrix ) ); + guiSpace->weaponDepthHack = depthHack; + guiSpace->isGuiSurface = true; + + // If this is an in-game gui, we need to be able to find the matrix again for head mounted + // display bypass matrix fixup. + if ( linkAsEntity ) { + guiSpace->next = tr.viewDef->viewEntitys; + tr.viewDef->viewEntitys = guiSpace; + } + + //--------------------------- + // make a tech5 renderMatrix + //--------------------------- + idRenderMatrix viewMat; + idRenderMatrix::Transpose( *(idRenderMatrix *)modelViewMatrix, viewMat ); + idRenderMatrix::Multiply( tr.viewDef->projectionRenderMatrix, viewMat, guiSpace->mvp ); + if ( depthHack ) { + idRenderMatrix::ApplyDepthHack( guiSpace->mvp ); + } + + // to allow 3D-TV effects in the menu system, we define surface flags to set + // depth fractions between 0=screen and 1=infinity, which directly modulate the + // screenSeparation parameter for an X offset. + // The value is stored in the drawSurf sort value, which adjusts the matrix in the + // backend. + float defaultStereoDepth = stereoRender_defaultGuiDepth.GetFloat(); // default to at-screen + + // add the surfaces to this view + for ( int i = 0; i < surfaces.Num(); i++ ) { + const guiModelSurface_t & guiSurf = surfaces[i]; + if ( guiSurf.numIndexes == 0 ) { + continue; + } + + const idMaterial * shader = guiSurf.material; + drawSurf_t * drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + + drawSurf->numIndexes = guiSurf.numIndexes; + drawSurf->ambientCache = vertexBlock; + // build a vertCacheHandle_t that points inside the allocated block + drawSurf->indexCache = indexBlock + ( (int64)(guiSurf.firstIndex*sizeof(triIndex_t)) << VERTCACHE_OFFSET_SHIFT ); + drawSurf->shadowCache = 0; + drawSurf->jointCache = 0; + drawSurf->frontEndGeo = NULL; + drawSurf->space = guiSpace; + drawSurf->material = shader; + drawSurf->extraGLState = guiSurf.glState; + drawSurf->scissorRect = tr.viewDef->scissor; + drawSurf->sort = shader->GetSort(); + drawSurf->renderZFail = 0; + // process the shader expressions for conditionals / color / texcoords + const float *constRegs = shader->ConstantRegisters(); + if ( constRegs ) { + // shader only uses constant values + drawSurf->shaderRegisters = constRegs; + } else { + float *regs = (float *)R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER ); + drawSurf->shaderRegisters = regs; + shader->EvaluateRegisters( regs, shaderParms, tr.viewDef->renderView.shaderParms, tr.viewDef->renderView.time[1] * 0.001f, NULL ); + } + R_LinkDrawSurfToView( drawSurf, tr.viewDef ); + if ( allowFullScreenStereoDepth ) { + // override sort with the stereoDepth + //drawSurf->sort = stereoDepth; + + switch ( guiSurf.stereoType ) { + case STEREO_DEPTH_TYPE_NEAR: drawSurf->sort = STEREO_DEPTH_NEAR; break; + case STEREO_DEPTH_TYPE_MID: drawSurf->sort = STEREO_DEPTH_MID; break; + case STEREO_DEPTH_TYPE_FAR: drawSurf->sort = STEREO_DEPTH_FAR; break; + case STEREO_DEPTH_TYPE_NONE: + default: + drawSurf->sort = defaultStereoDepth; + break; + } + } + } +} + +/* +==================== +EmitToCurrentView +==================== +*/ +void idGuiModel::EmitToCurrentView( float modelMatrix[16], bool depthHack ) { + float modelViewMatrix[16]; + + R_MatrixMultiply( modelMatrix, tr.viewDef->worldSpace.modelViewMatrix, modelViewMatrix ); + + EmitSurfaces( modelMatrix, modelViewMatrix, depthHack, false /* stereoDepthSort */, true /* link as entity */ ); +} + + +/* +================ +idGuiModel::EmitFullScreen + +Creates a view that covers the screen and emit the surfaces +================ +*/ +void idGuiModel::EmitFullScreen() { + + if ( surfaces[0].numIndexes == 0 ) { + return; + } + + SCOPED_PROFILE_EVENT( "Gui::EmitFullScreen" ); + + viewDef_t * viewDef = (viewDef_t *)R_ClearedFrameAlloc( sizeof( *viewDef ), FRAME_ALLOC_VIEW_DEF ); + viewDef->is2Dgui = true; + tr.GetCroppedViewport( &viewDef->viewport ); + + bool stereoEnabled = ( renderSystem->GetStereo3DMode() != STEREO3D_OFF ); + if ( stereoEnabled ) { + float GetScreenSeparationForGuis(); + const float screenSeparation = GetScreenSeparationForGuis(); + + // this will be negated on the alternate eyes, both rendered each frame + viewDef->renderView.stereoScreenSeparation = screenSeparation; + + extern idCVar stereoRender_swapEyes; + viewDef->renderView.viewEyeBuffer = 0; // render to both buffers + if ( stereoRender_swapEyes.GetBool() ) { + viewDef->renderView.stereoScreenSeparation = -screenSeparation; + } + } + + viewDef->scissor.x1 = 0; + viewDef->scissor.y1 = 0; + viewDef->scissor.x2 = viewDef->viewport.x2 - viewDef->viewport.x1; + viewDef->scissor.y2 = viewDef->viewport.y2 - viewDef->viewport.y1; + + viewDef->projectionMatrix[0*4+0] = 2.0f / SCREEN_WIDTH; + viewDef->projectionMatrix[0*4+1] = 0.0f; + viewDef->projectionMatrix[0*4+2] = 0.0f; + viewDef->projectionMatrix[0*4+3] = 0.0f; + + viewDef->projectionMatrix[1*4+0] = 0.0f; + viewDef->projectionMatrix[1*4+1] = -2.0f / SCREEN_HEIGHT; + viewDef->projectionMatrix[1*4+2] = 0.0f; + viewDef->projectionMatrix[1*4+3] = 0.0f; + + viewDef->projectionMatrix[2*4+0] = 0.0f; + viewDef->projectionMatrix[2*4+1] = 0.0f; + viewDef->projectionMatrix[2*4+2] = -2.0f; + viewDef->projectionMatrix[2*4+3] = 0.0f; + + viewDef->projectionMatrix[3*4+0] = -1.0f; + viewDef->projectionMatrix[3*4+1] = 1.0f; + viewDef->projectionMatrix[3*4+2] = -1.0f; + viewDef->projectionMatrix[3*4+3] = 1.0f; + + // make a tech5 renderMatrix for faster culling + idRenderMatrix::Transpose( *(idRenderMatrix *)viewDef->projectionMatrix, viewDef->projectionRenderMatrix ); + + viewDef->worldSpace.modelMatrix[0*4+0] = 1.0f; + viewDef->worldSpace.modelMatrix[1*4+1] = 1.0f; + viewDef->worldSpace.modelMatrix[2*4+2] = 1.0f; + viewDef->worldSpace.modelMatrix[3*4+3] = 1.0f; + + viewDef->worldSpace.modelViewMatrix[0*4+0] = 1.0f; + viewDef->worldSpace.modelViewMatrix[1*4+1] = 1.0f; + viewDef->worldSpace.modelViewMatrix[2*4+2] = 1.0f; + viewDef->worldSpace.modelViewMatrix[3*4+3] = 1.0f; + + viewDef->maxDrawSurfs = surfaces.Num(); + viewDef->drawSurfs = (drawSurf_t **)R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ), FRAME_ALLOC_DRAW_SURFACE_POINTER ); + viewDef->numDrawSurfs = 0; + + viewDef_t * oldViewDef = tr.viewDef; + tr.viewDef = viewDef; + + EmitSurfaces( viewDef->worldSpace.modelMatrix, viewDef->worldSpace.modelViewMatrix, + false /* depthHack */ , stereoEnabled /* stereoDepthSort */, false /* link as entity */ ); + + tr.viewDef = oldViewDef; + + // add the command to draw this view + R_AddDrawViewCmd( viewDef, true ); +} + +/* +============= +AdvanceSurf +============= +*/ +void idGuiModel::AdvanceSurf() { + guiModelSurface_t s; + + if ( surfaces.Num() ) { + s.material = surf->material; + s.glState = surf->glState; + } else { + s.material = tr.defaultMaterial; + s.glState = 0; + } + + // advance indexes so the pointer to each surface will be 16 byte aligned + numIndexes = ALIGN( numIndexes, 8 ); + + s.numIndexes = 0; + s.firstIndex = numIndexes; + + surfaces.Append( s ); + surf = &surfaces[ surfaces.Num() - 1 ]; +} + +/* +============= +AllocTris +============= +*/ +idDrawVert * idGuiModel::AllocTris( int vertCount, const triIndex_t * tempIndexes, int indexCount, const idMaterial * material, const uint64 glState, const stereoDepthType_t stereoType ) { + if ( material == NULL ) { + return NULL; + } + if ( numIndexes + indexCount > MAX_INDEXES ) { + static int warningFrame = 0; + if ( warningFrame != tr.frameCount ) { + warningFrame = tr.frameCount; + idLib::Warning( "idGuiModel::AllocTris: MAX_INDEXES exceeded" ); + } + return NULL; + } + if ( numVerts + vertCount > MAX_VERTS ) { + static int warningFrame = 0; + if ( warningFrame != tr.frameCount ) { + warningFrame = tr.frameCount; + idLib::Warning( "idGuiModel::AllocTris: MAX_VERTS exceeded" ); + } + return NULL; + } + + // break the current surface if we are changing to a new material or we can't + // fit the data into our allocated block + if ( material != surf->material || glState != surf->glState || stereoType != surf->stereoType ) { + if ( surf->numIndexes ) { + AdvanceSurf(); + } + surf->material = material; + surf->glState = glState; + surf->stereoType = stereoType; + } + + int startVert = numVerts; + int startIndex = numIndexes; + + numVerts += vertCount; + numIndexes += indexCount; + + surf->numIndexes += indexCount; + + if ( ( startIndex & 1 ) || ( indexCount & 1 ) ) { + // slow for write combined memory! + // this should be very rare, since quads are always an even index count + for ( int i = 0; i < indexCount; i++ ) { + indexPointer[startIndex + i] = startVert + tempIndexes[i]; + } + } else { + for ( int i = 0; i < indexCount; i += 2 ) { + WriteIndexPair( indexPointer + startIndex + i, startVert + tempIndexes[i], startVert + tempIndexes[i+1] ); + } + } + + return vertexPointer + startVert; +} diff --git a/neo/renderer/GuiModel.h b/neo/renderer/GuiModel.h new file mode 100644 index 00000000..622c617d --- /dev/null +++ b/neo/renderer/GuiModel.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +struct guiModelSurface_t { + const idMaterial * material; + uint64 glState; + int firstIndex; + int numIndexes; + stereoDepthType_t stereoType; +}; + +class idRenderMatrix; + +class idGuiModel { +public: + idGuiModel(); + + void Clear(); + + void WriteToDemo( idDemoFile * demo ); + void ReadFromDemo( idDemoFile * demo ); + + // allocates memory for verts and indexes in frame-temporary buffer memory + void BeginFrame(); + + void EmitToCurrentView( float modelMatrix[16], bool depthHack ); + void EmitFullScreen(); + + // the returned pointer will be in write-combined memory, so only make contiguous + // 32 bit writes and never read from it. + idDrawVert * AllocTris( int numVerts, const triIndex_t * indexes, int numIndexes, const idMaterial * material, + const uint64 glState, const stereoDepthType_t stereoType ); + + //--------------------------- +private: + void AdvanceSurf(); + void EmitSurfaces( float modelMatrix[16], float modelViewMatrix[16], + bool depthHack, bool allowFullScreenStereoDepth, bool linkAsEntity ); + + guiModelSurface_t * surf; + + float shaderParms[ MAX_ENTITY_SHADER_PARMS ]; + + static const float STEREO_DEPTH_NEAR; + static const float STEREO_DEPTH_MID; + static const float STEREO_DEPTH_FAR; + + // if we exceed these limits we stop rendering GUI surfaces + static const int MAX_INDEXES = ( 20000 * 6 ); + static const int MAX_VERTS = ( 20000 * 4 ); + + vertCacheHandle_t vertexBlock; + vertCacheHandle_t indexBlock; + idDrawVert * vertexPointer; + triIndex_t * indexPointer; + + int numVerts; + int numIndexes; + + idList surfaces; +}; + diff --git a/neo/renderer/Image.h b/neo/renderer/Image.h new file mode 100644 index 00000000..9593a783 --- /dev/null +++ b/neo/renderer/Image.h @@ -0,0 +1,364 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* +==================================================================== + +IMAGE + +idImage have a one to one correspondance with GL/DX/GCM textures. + +No texture is ever used that does not have a corresponding idImage. + +==================================================================== +*/ + +static const int MAX_TEXTURE_LEVELS = 14; + +// How is this texture used? Determines the storage and color format +typedef enum { + TD_SPECULAR, // may be compressed, and always zeros the alpha channel + TD_DIFFUSE, // may be compressed + TD_DEFAULT, // generic RGBA texture (particles, etc...) + TD_BUMP, // may be compressed with 8 bit lookup + TD_FONT, // Font image + TD_LIGHT, // Light image + TD_LOOKUP_TABLE_MONO, // Mono lookup table (including alpha) + TD_LOOKUP_TABLE_ALPHA, // Alpha lookup table with a white color channel + TD_LOOKUP_TABLE_RGB1, // RGB lookup table with a solid white alpha + TD_LOOKUP_TABLE_RGBA, // RGBA lookup table + TD_COVERAGE, // coverage map for fill depth pass when YCoCG is used + TD_DEPTH, // depth buffer copy for motion blur +} textureUsage_t; + +typedef enum { + CF_2D, // not a cube map + CF_NATIVE, // _px, _nx, _py, etc, directly sent to GL + CF_CAMERA // _forward, _back, etc, rotated and flipped as needed before sending to GL +} cubeFiles_t; + +#include "ImageOpts.h" +#include "BinaryImage.h" + +#define MAX_IMAGE_NAME 256 + +class idImage { +public: + idImage( const char * name ); + + const char * GetName() const { return imgName; } + + // Makes this image active on the current GL texture unit. + // automatically enables or disables cube mapping + // May perform file loading if the image was not preloaded. + void Bind(); + + // Should be called at least once + void SetSamplerState( textureFilter_t tf, textureRepeat_t tr ); + + // used by callback functions to specify the actual data + // data goes from the bottom to the top line of the image, as OpenGL expects it + // These perform an implicit Bind() on the current texture unit + // FIXME: should we implement cinematics this way, instead of with explicit calls? + void GenerateImage( const byte *pic, int width, int height, + textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage ); + void GenerateCubeImage( const byte *pic[6], int size, + textureFilter_t filter, textureUsage_t usage ); + + void CopyFramebuffer( int x, int y, int width, int height ); + void CopyDepthbuffer( int x, int y, int width, int height ); + + void UploadScratch( const byte *pic, int width, int height ); + + // estimates size of the GL image based on dimensions and storage type + int StorageSize() const; + + // print a one line summary of the image + void Print() const; + + // check for changed timestamp on disk and reload if necessary + void Reload( bool force ); + + void AddReference() { refCount++; }; + + void MakeDefault(); // fill with a grid pattern + + const idImageOpts & GetOpts() const { return opts; } + int GetUploadWidth() const { return opts.width; } + int GetUploadHeight() const { return opts.height; } + + void SetReferencedOutsideLevelLoad() { referencedOutsideLevelLoad = true; } + void SetReferencedInsideLevelLoad() { levelLoadReferenced = true; } + void ActuallyLoadImage( bool fromBackEnd ); + //--------------------------------------------- + // Platform specific implementations + //--------------------------------------------- + + void AllocImage( const idImageOpts &imgOpts, textureFilter_t filter, textureRepeat_t repeat ); + + // Deletes the texture object, but leaves the structure so it can be reloaded + // or resized. + void PurgeImage(); + + // z is 0 for 2D textures, 0 - 5 for cube maps, and 0 - uploadDepth for 3D textures. Only + // one plane at a time of 3D textures can be uploaded. The data is assumed to be correct for + // the format, either bytes, halfFloats, floats, or DXT compressed. The data is assumed to + // be in OpenGL RGBA format, the consoles may have to reorganize. pixelPitch is only needed + // when updating from a source subrect. Width, height, and dest* are always in pixels, so + // they must be a multiple of four for dxt data. + void SubImageUpload( int mipLevel, int destX, int destY, int destZ, + int width, int height, const void * data, + int pixelPitch = 0 ) const; + + // SetPixel is assumed to be a fast memory write on consoles, degenerating to a + // SubImageUpload on PCs. Used to update the page mapping images. + // We could remove this now, because the consoles don't use the intermediate page mapping + // textures now that they can pack everything into the virtual page table images. + void SetPixel( int mipLevel, int x, int y, const void * data, int dataSize ); + + // some scratch images are dynamically resized based on the display window size. This + // simply purges the image and recreates it if the sizes are different, so it should not be + // done under any normal circumstances, and probably not at all on consoles. + void Resize( int width, int height ); + + bool IsCompressed() const { return ( opts.format == FMT_DXT1 || opts.format == FMT_DXT5 ); } + + void SetTexParameters(); // update aniso and trilinear + + bool IsLoaded() const { return texnum != TEXTURE_NOT_LOADED; } + + static void GetGeneratedName( idStr &_name, const textureUsage_t &_usage, const cubeFiles_t &_cube ); + +private: + friend class idImageManager; + + void AllocImage(); + void DeriveOpts(); + + // parameters that define this image + idStr imgName; // game path, including extension (except for cube maps), may be an image program + cubeFiles_t cubeFiles; // If this is a cube map, and if so, what kind + void (*generatorFunction)( idImage *image ); // NULL for files + textureUsage_t usage; // Used to determine the type of compression to use + idImageOpts opts; // Parameters that determine the storage method + + // Sampler settings + textureFilter_t filter; + textureRepeat_t repeat; + + bool referencedOutsideLevelLoad; + bool levelLoadReferenced; // for determining if it needs to be purged + bool defaulted; // true if the default image was generated because a file couldn't be loaded + ID_TIME_T sourceFileTime; // the most recent of all images used in creation, for reloadImages command + ID_TIME_T binaryFileTime; // the time stamp of the binary file + + int refCount; // overall ref count + + static const GLuint TEXTURE_NOT_LOADED = 0xFFFFFFFF; + + GLuint texnum; // gl texture binding + + // we could derive these in subImageUpload each time if necessary + GLuint internalFormat; + GLuint dataFormat; + GLuint dataType; + + +}; + +ID_INLINE idImage::idImage( const char * name ) : imgName( name ) { + texnum = TEXTURE_NOT_LOADED; + internalFormat = 0; + dataFormat = 0; + dataType = 0; + generatorFunction = NULL; + filter = TF_DEFAULT; + repeat = TR_REPEAT; + usage = TD_DEFAULT; + cubeFiles = CF_2D; + + referencedOutsideLevelLoad = false; + levelLoadReferenced = false; + defaulted = false; + sourceFileTime = FILE_NOT_FOUND_TIMESTAMP; + binaryFileTime = FILE_NOT_FOUND_TIMESTAMP; + refCount = 0; +} + + +// data is RGBA +void R_WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical = false, const char * basePath = "fs_savepath" ); +// data is in top-to-bottom raster order unless flipVertical is set + + + +class idImageManager { +public: + + idImageManager() + { + insideLevelLoad = false; + preloadingMapImages = false; + } + + void Init(); + void Shutdown(); + + // If the exact combination of parameters has been asked for already, an existing + // image will be returned, otherwise a new image will be created. + // Be careful not to use the same image file with different filter / repeat / etc parameters + // if possible, because it will cause a second copy to be loaded. + // If the load fails for any reason, the image will be filled in with the default + // grid pattern. + // Will automatically execute image programs if needed. + idImage * ImageFromFile( const char *name, + textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage, cubeFiles_t cubeMap = CF_2D ); + + // look for a loaded image, whatever the parameters + idImage * GetImage( const char *name ) const; + + // look for a loaded image, whatever the parameters + idImage * GetImageWithParameters( const char *name, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage, cubeFiles_t cubeMap ) const; + + // The callback will be issued immediately, and later if images are reloaded or vid_restart + // The callback function should call one of the idImage::Generate* functions to fill in the data + idImage * ImageFromFunction( const char *name, void (*generatorFunction)( idImage *image )); + + // scratch images are for internal renderer use. ScratchImage names should always begin with an underscore + idImage * ScratchImage( const char *name, idImageOpts *imgOpts, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage ); + + // purges all the images before a vid_restart + void PurgeAllImages(); + + // reloads all apropriate images after a vid_restart + void ReloadImages( bool all ); + + // unbind all textures from all texture units + void UnbindAll(); + + // disable the active texture unit + void BindNull(); + + // Called only by renderSystem::BeginLevelLoad + void BeginLevelLoad(); + + // Called only by renderSystem::EndLevelLoad + void EndLevelLoad(); + + void Preload( const idPreloadManifest &manifest, const bool & mapPreload ); + + // Loads unloaded level images + int LoadLevelImages( bool pacifier ); + + // used to clear and then write the dds conversion batch file + void StartBuild(); + void FinishBuild( bool removeDups = false ); + + void PrintMemInfo( MemInfo_t *mi ); + + // built-in images + void CreateIntrinsicImages(); + idImage * defaultImage; + idImage * flatNormalMap; // 128 128 255 in all pixels + idImage * alphaNotchImage; // 2x1 texture with just 1110 and 1111 with point sampling + idImage * whiteImage; // full of 0xff + idImage * blackImage; // full of 0x00 + idImage * noFalloffImage; // all 255, but zero clamped + idImage * fogImage; // increasing alpha is denser fog + idImage * fogEnterImage; // adjust fogImage alpha based on terminator plane + idImage * scratchImage; + idImage * scratchImage2; + idImage * accumImage; + idImage * currentRenderImage; // for SS_POST_PROCESS shaders + idImage * currentDepthImage; // for motion blur + idImage * originalCurrentRenderImage; // currentRenderImage before any changes for stereo rendering + idImage * loadingIconImage; // loading icon must exist always + idImage * hellLoadingIconImage; // loading icon must exist always + + //-------------------------------------------------------- + + idImage * AllocImage( const char *name ); + idImage * AllocStandaloneImage( const char *name ); + + bool ExcludePreloadImage( const char *name ); + + idList images; + idHashIndex imageHash; + + bool insideLevelLoad; // don't actually load images now + bool preloadingMapImages; // unless this is set +}; + +extern idImageManager *globalImages; // pointer to global list for the rest of the system + +int MakePowerOfTwo( int num ); + +/* +==================================================================== + +IMAGEPROCESS + +FIXME: make an "imageBlock" type to hold byte*,width,height? +==================================================================== +*/ + +byte *R_Dropsample( const byte *in, int inwidth, int inheight, int outwidth, int outheight ); +byte *R_ResampleTexture( const byte *in, int inwidth, int inheight, int outwidth, int outheight ); +byte *R_MipMapWithAlphaSpecularity( const byte *in, int width, int height ); +byte *R_MipMapWithGamma( const byte *in, int width, int height ); +byte *R_MipMap( const byte *in, int width, int height ); + +// these operate in-place on the provided pixels +void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[4] ); +void R_HorizontalFlip( byte *data, int width, int height ); +void R_VerticalFlip( byte *data, int width, int height ); +void R_RotatePic( byte *data, int width ); + +/* +==================================================================== + +IMAGEFILES + +==================================================================== +*/ + +void R_LoadImage( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 ); +// pic is in top to bottom raster format +bool R_LoadCubeImages( const char *cname, cubeFiles_t extensions, byte *pic[6], int *size, ID_TIME_T *timestamp ); + +/* +==================================================================== + +IMAGEPROGRAM + +==================================================================== +*/ + +void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp, textureUsage_t * usage = NULL ); +const char *R_ParsePastImageProgram( idLexer &src ); + diff --git a/neo/renderer/ImageManager.cpp b/neo/renderer/ImageManager.cpp new file mode 100644 index 00000000..4cee3e2d --- /dev/null +++ b/neo/renderer/ImageManager.cpp @@ -0,0 +1,878 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" + +// do this with a pointer, in case we want to make the actual manager +// a private virtual subclass +idImageManager imageManager; +idImageManager * globalImages = &imageManager; + +idCVar preLoad_Images( "preLoad_Images", "1", CVAR_SYSTEM | CVAR_BOOL, "preload images during beginlevelload" ); + +/* +=============== +R_ReloadImages_f + +Regenerate all images that came directly from files that have changed, so +any saved changes will show up in place. + +New r_texturesize/r_texturedepth variables will take effect on reload + +reloadImages +=============== +*/ +void R_ReloadImages_f( const idCmdArgs &args ) { + bool all = false; + + if ( args.Argc() == 2 ) { + if ( !idStr::Icmp( args.Argv(1), "all" ) ) { + all = true; + } else { + common->Printf( "USAGE: reloadImages \n" ); + return; + } + } + + globalImages->ReloadImages( all ); +} + +typedef struct { + idImage *image; + int size; + int index; +} sortedImage_t; + +/* +======================= +R_QsortImageSizes + +======================= +*/ +static int R_QsortImageSizes( const void *a, const void *b ) { + const sortedImage_t *ea, *eb; + + ea = (sortedImage_t *)a; + eb = (sortedImage_t *)b; + + if ( ea->size > eb->size ) { + return -1; + } + if ( ea->size < eb->size ) { + return 1; + } + return idStr::Icmp( ea->image->GetName(), eb->image->GetName() ); +} + +/* +======================= +R_QsortImageName + +======================= +*/ +static int R_QsortImageName( const void* a, const void* b ) { + const sortedImage_t *ea, *eb; + + ea = (sortedImage_t *)a; + eb = (sortedImage_t *)b; + + return idStr::Icmp( ea->image->GetName(), eb->image->GetName() ); +} + +/* +=============== +R_ListImages_f +=============== +*/ +void R_ListImages_f( const idCmdArgs &args ) { + int i, partialSize; + idImage *image; + int totalSize; + int count = 0; + bool uncompressedOnly = false; + bool unloaded = false; + bool failed = false; + bool sorted = false; + bool duplicated = false; + bool overSized = false; + bool sortByName = false; + + if ( args.Argc() == 1 ) { + + } else if ( args.Argc() == 2 ) { + if ( idStr::Icmp( args.Argv( 1 ), "uncompressed" ) == 0 ) { + uncompressedOnly = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "sorted" ) == 0 ) { + sorted = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "namesort" ) == 0 ) { + sortByName = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "unloaded" ) == 0 ) { + unloaded = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "duplicated" ) == 0 ) { + duplicated = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "oversized" ) == 0 ) { + sorted = true; + overSized = true; + } else { + failed = true; + } + } else { + failed = true; + } + + if ( failed ) { + common->Printf( "usage: listImages [ sorted | namesort | unloaded | duplicated | showOverSized ]\n" ); + return; + } + + const char *header = " -w-- -h-- filt -fmt-- wrap size --name-------\n"; + common->Printf( "\n%s", header ); + + totalSize = 0; + + sortedImage_t *sortedArray = (sortedImage_t *)alloca( sizeof( sortedImage_t ) * globalImages->images.Num() ); + + for ( i = 0 ; i < globalImages->images.Num() ; i++ ) { + image = globalImages->images[ i ]; + + if ( uncompressedOnly ) { + if ( image->IsCompressed() ) { + continue; + } + } + if ( unloaded == image->IsLoaded() ) { + continue; + } + + // only print duplicates (from mismatched wrap / clamp, etc) + if ( duplicated ) { + int j; + for ( j = i+1 ; j < globalImages->images.Num() ; j++ ) { + if ( idStr::Icmp( image->GetName(), globalImages->images[ j ]->GetName() ) == 0 ) { + break; + } + } + if ( j == globalImages->images.Num() ) { + continue; + } + } + + if ( sorted || sortByName ) { + sortedArray[count].image = image; + sortedArray[count].size = image->StorageSize(); + sortedArray[count].index = i; + } else { + common->Printf( "%4i:", i ); + image->Print(); + } + totalSize += image->StorageSize(); + count++; + } + + if ( sorted || sortByName ) { + if ( sortByName) { + qsort( sortedArray, count, sizeof( sortedImage_t ), R_QsortImageName ); + } else { + qsort( sortedArray, count, sizeof( sortedImage_t ), R_QsortImageSizes ); + } + partialSize = 0; + for ( i = 0 ; i < count ; i++ ) { + common->Printf( "%4i:", sortedArray[i].index ); + sortedArray[i].image->Print(); + partialSize += sortedArray[i].image->StorageSize(); + if ( ( (i+1) % 10 ) == 0 ) { + common->Printf( "-------- %5.1f of %5.1f megs --------\n", + partialSize / (1024*1024.0), totalSize / (1024*1024.0) ); + } + } + } + + common->Printf( "%s", header ); + common->Printf( " %i images (%i total)\n", count, globalImages->images.Num() ); + common->Printf( " %5.1f total megabytes of images\n\n\n", totalSize / (1024*1024.0) ); +} + +/* +============== +AllocImage + +Allocates an idImage, adds it to the list, +copies the name, and adds it to the hash chain. +============== +*/ +idImage *idImageManager::AllocImage( const char *name ) { + if (strlen(name) >= MAX_IMAGE_NAME ) { + common->Error ("idImageManager::AllocImage: \"%s\" is too long\n", name); + } + + int hash = idStr( name ).FileNameHash(); + + idImage * image = new (TAG_IMAGE) idImage( name ); + + imageHash.Add( hash, images.Append( image ) ); + + return image; +} + +/* +============== +AllocStandaloneImage + +Allocates an idImage,does not add it to the list or hash chain + +============== +*/ +idImage *idImageManager::AllocStandaloneImage( const char *name ) { + if (strlen(name) >= MAX_IMAGE_NAME ) { + common->Error ("idImageManager::AllocImage: \"%s\" is too long\n", name); + } + + idImage * image = new (TAG_IMAGE) idImage( name ); + + return image; +} + +/* +================== +ImageFromFunction + +Images that are procedurally generated are allways specified +with a callback which must work at any time, allowing the OpenGL +system to be completely regenerated if needed. +================== +*/ +idImage *idImageManager::ImageFromFunction( const char *_name, void (*generatorFunction)( idImage *image ) ) { + + // strip any .tga file extensions from anywhere in the _name + idStr name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + + // see if the image already exists + int hash = name.FileNameHash(); + for ( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { + idImage * image = images[i]; + if ( name.Icmp( image->GetName() ) == 0 ) { + if ( image->generatorFunction != generatorFunction ) { + common->DPrintf( "WARNING: reused image %s with mixed generators\n", name.c_str() ); + } + return image; + } + } + + // create the image and issue the callback + idImage * image = AllocImage( name ); + + image->generatorFunction = generatorFunction; + + // check for precompressed, load is from the front end + image->referencedOutsideLevelLoad = true; + image->ActuallyLoadImage( false ); + + return image; +} + + +/* +=============== +GetImageWithParameters +============== +*/ +idImage *idImageManager::GetImageWithParameters( const char *_name, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage, cubeFiles_t cubeMap ) const { + if ( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { + declManager->MediaPrint( "DEFAULTED\n" ); + return globalImages->defaultImage; + } + if ( idStr::Icmpn( _name, "fonts", 5 ) == 0 || idStr::Icmpn( _name, "newfonts", 8 ) == 0 ) { + usage = TD_FONT; + } + if ( idStr::Icmpn( _name, "lights", 6 ) == 0 ) { + usage = TD_LIGHT; + } + // strip any .tga file extensions from anywhere in the _name, including image program parameters + idStrStatic< MAX_OSPATH > name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + int hash = name.FileNameHash(); + for ( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { + idImage * image = images[i]; + if ( name.Icmp( image->GetName() ) == 0 ) { + // the built in's, like _white and _flat always match the other options + if ( name[0] == '_' ) { + return image; + } + if ( image->cubeFiles != cubeMap ) { + common->Error( "Image '%s' has been referenced with conflicting cube map states", _name ); + } + if ( image->filter != filter || image->repeat != repeat ) { + // we might want to have the system reset these parameters on every bind and + // share the image data + continue; + } + if ( image->usage != usage ) { + // If an image is used differently then we need 2 copies of it because usage affects the way it's compressed and swizzled + continue; + } + return image; + } + } + return NULL; +} +/* +=============== +ImageFromFile + +Finds or loads the given image, always returning a valid image pointer. +Loading of the image may be deferred for dynamic loading. +============== +*/ +idImage *idImageManager::ImageFromFile( const char *_name, textureFilter_t filter, + textureRepeat_t repeat, textureUsage_t usage, cubeFiles_t cubeMap ) { + + if ( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { + declManager->MediaPrint( "DEFAULTED\n" ); + return globalImages->defaultImage; + } + if ( idStr::Icmpn( _name, "fonts", 5 ) == 0 || idStr::Icmpn( _name, "newfonts", 8 ) == 0 ) { + usage = TD_FONT; + } + if ( idStr::Icmpn( _name, "lights", 6 ) == 0 ) { + usage = TD_LIGHT; + } + + // strip any .tga file extensions from anywhere in the _name, including image program parameters + idStrStatic< MAX_OSPATH > name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + + // + // see if the image is already loaded, unless we + // are in a reloadImages call + // + int hash = name.FileNameHash(); + for ( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { + idImage * image = images[i]; + if ( name.Icmp( image->GetName() ) == 0 ) { + // the built in's, like _white and _flat always match the other options + if ( name[0] == '_' ) { + return image; + } + if ( image->cubeFiles != cubeMap ) { + common->Error( "Image '%s' has been referenced with conflicting cube map states", _name ); + } + + if ( image->filter != filter || image->repeat != repeat ) { + // we might want to have the system reset these parameters on every bind and + // share the image data + continue; + } + if ( image->usage != usage ) { + // If an image is used differently then we need 2 copies of it because usage affects the way it's compressed and swizzled + continue; + } + + image->usage = usage; + image->levelLoadReferenced = true; + + if ( ( !insideLevelLoad || preloadingMapImages ) && !image->IsLoaded() ) { + image->referencedOutsideLevelLoad = ( !insideLevelLoad && !preloadingMapImages ); + image->ActuallyLoadImage( false ); // load is from front end + declManager->MediaPrint( "%ix%i %s (reload for mixed referneces)\n", image->GetUploadWidth(), image->GetUploadHeight(), image->GetName() ); + } + return image; + } + } + + // + // create a new image + // + idImage * image = AllocImage( name ); + image->cubeFiles = cubeMap; + image->usage = usage; + image->filter = filter; + image->repeat = repeat; + + image->levelLoadReferenced = true; + + // load it if we aren't in a level preload + if ( !insideLevelLoad || preloadingMapImages ) { + image->referencedOutsideLevelLoad = ( !insideLevelLoad && !preloadingMapImages ); + image->ActuallyLoadImage( false ); // load is from front end + declManager->MediaPrint( "%ix%i %s\n", image->GetUploadWidth(), image->GetUploadHeight(), image->GetName() ); + } else { + declManager->MediaPrint( "%s\n", image->GetName() ); + } + + return image; +} + +/* +======================== +idImageManager::ScratchImage +======================== +*/ +idImage * idImageManager::ScratchImage( const char *_name, idImageOpts *imgOpts, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage ) { + if ( !_name || !_name[0] ) { + idLib::FatalError( "idImageManager::ScratchImage called with empty name" ); + } + + if ( imgOpts == NULL ) { + idLib::FatalError( "idImageManager::ScratchImage called with NULL imgOpts" ); + } + + idStr name = _name; + + // + // see if the image is already loaded, unless we + // are in a reloadImages call + // + int hash = name.FileNameHash(); + for ( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { + idImage * image = images[i]; + if ( name.Icmp( image->GetName() ) == 0 ) { + // the built in's, like _white and _flat always match the other options + if ( name[0] == '_' ) { + return image; + } + + if ( image->filter != filter || image->repeat != repeat ) { + // we might want to have the system reset these parameters on every bind and + // share the image data + continue; + } + if ( image->usage != usage ) { + // If an image is used differently then we need 2 copies of it because usage affects the way it's compressed and swizzled + continue; + } + + image->usage = usage; + image->levelLoadReferenced = true; + image->referencedOutsideLevelLoad = true; + return image; + } + } + + // clamp is the only repeat mode that makes sense for cube maps, but + // some platforms let them stay in repeat mode and get border seam issues + if ( imgOpts->textureType == TT_CUBIC && repeat != TR_CLAMP ) { + repeat = TR_CLAMP; + } + + // + // create a new image + // + idImage* newImage = AllocImage( name ); + if ( newImage != NULL ) { + newImage->AllocImage( *imgOpts, filter, repeat ); + } + return newImage; +} + +/* +=============== +idImageManager::GetImage +=============== +*/ +idImage *idImageManager::GetImage( const char *_name ) const { + + if ( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { + declManager->MediaPrint( "DEFAULTED\n" ); + return globalImages->defaultImage; + } + + // strip any .tga file extensions from anywhere in the _name, including image program parameters + idStr name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + + // + // look in loaded images + // + int hash = name.FileNameHash(); + for ( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { + idImage * image = images[i]; + if ( name.Icmp( image->GetName() ) == 0 ) { + return image; + } + } + + return NULL; +} + +/* +=============== +PurgeAllImages +=============== +*/ +void idImageManager::PurgeAllImages() { + int i; + idImage *image; + + for ( i = 0; i < images.Num() ; i++ ) { + image = images[i]; + image->PurgeImage(); + } +} + +/* +=============== +ReloadImages +=============== +*/ +void idImageManager::ReloadImages( bool all ) { + for ( int i = 0 ; i < globalImages->images.Num() ; i++ ) { + globalImages->images[ i ]->Reload( all ); + } +} + +/* +=============== +R_CombineCubeImages_f + +Used to combine animations of six separate tga files into +a serials of 6x taller tga files, for preparation to roq compress +=============== +*/ +void R_CombineCubeImages_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "usage: combineCubeImages \n" ); + common->Printf( " combines basename[1-6][0001-9999].tga to basenameCM[0001-9999].tga\n" ); + common->Printf( " 1: forward 2:right 3:back 4:left 5:up 6:down\n" ); + return; + } + + idStr baseName = args.Argv( 1 ); + common->SetRefreshOnPrint( true ); + + for ( int frameNum = 1 ; frameNum < 10000 ; frameNum++ ) { + char filename[MAX_IMAGE_NAME]; + byte *pics[6]; + int width = 0, height = 0; + int side; + int orderRemap[6] = { 1,3,4,2,5,6 }; + for ( side = 0 ; side < 6 ; side++ ) { + sprintf( filename, "%s%i%04i.tga", baseName.c_str(), orderRemap[side], frameNum ); + + common->Printf( "reading %s\n", filename ); + R_LoadImage( filename, &pics[side], &width, &height, NULL, true ); + + if ( !pics[side] ) { + common->Printf( "not found.\n" ); + break; + } + + // convert from "camera" images to native cube map images + switch( side ) { + case 0: // forward + R_RotatePic( pics[side], width); + break; + case 1: // back + R_RotatePic( pics[side], width); + R_HorizontalFlip( pics[side], width, height ); + R_VerticalFlip( pics[side], width, height ); + break; + case 2: // left + R_VerticalFlip( pics[side], width, height ); + break; + case 3: // right + R_HorizontalFlip( pics[side], width, height ); + break; + case 4: // up + R_RotatePic( pics[side], width); + break; + case 5: // down + R_RotatePic( pics[side], width); + break; + } + } + + if ( side != 6 ) { + for ( int i = 0 ; i < side ; side++ ) { + Mem_Free( pics[side] ); + } + break; + } + + idTempArray buf( width*height*6*4 ); + byte *combined = (byte *)buf.Ptr(); + for ( side = 0 ; side < 6 ; side++ ) { + memcpy( combined+width*height*4*side, pics[side], width*height*4 ); + Mem_Free( pics[side] ); + } + sprintf( filename, "%sCM%04i.tga", baseName.c_str(), frameNum ); + + common->Printf( "writing %s\n", filename ); + R_WriteTGA( filename, combined, width, height*6 ); + } + common->SetRefreshOnPrint( false ); +} + +/* +=============== +UnbindAll +=============== +*/ +void idImageManager::UnbindAll() { + int oldTMU = backEnd.glState.currenttmu; + for ( int i = 0; i < MAX_PROG_TEXTURE_PARMS; ++i ) { + backEnd.glState.currenttmu = i; + BindNull(); + } + backEnd.glState.currenttmu = oldTMU; +} + +/* +=============== +BindNull +=============== +*/ +void idImageManager::BindNull() { + RENDERLOG_PRINTF( "BindNull()\n" ); + +} + +/* +=============== +Init +=============== +*/ +void idImageManager::Init() { + + images.Resize( 1024, 1024 ); + imageHash.ResizeIndex( 1024 ); + + CreateIntrinsicImages(); + + cmdSystem->AddCommand( "reloadImages", R_ReloadImages_f, CMD_FL_RENDERER, "reloads images" ); + cmdSystem->AddCommand( "listImages", R_ListImages_f, CMD_FL_RENDERER, "lists images" ); + cmdSystem->AddCommand( "combineCubeImages", R_CombineCubeImages_f, CMD_FL_RENDERER, "combines six images for roq compression" ); + + // should forceLoadImages be here? +} + +/* +=============== +Shutdown +=============== +*/ +void idImageManager::Shutdown() { + images.DeleteContents( true ); + imageHash.Clear(); + +} + +/* +==================== +idImageManager::BeginLevelLoad +Frees all images used by the previous level +==================== +*/ +void idImageManager::BeginLevelLoad() { + insideLevelLoad = true; + + for ( int i = 0 ; i < images.Num() ; i++ ) { + idImage *image = images[ i ]; + + // generator function images are always kept around + if ( image->generatorFunction ) { + continue; + } + + if ( !image->referencedOutsideLevelLoad && image->IsLoaded() ) { + image->PurgeImage(); + //idLib::Printf( "purging %s\n", image->GetName() ); + } else { + //idLib::Printf( "not purging %s\n", image->GetName() ); + } + + image->levelLoadReferenced = false; + } +} + + +/* +==================== +idImageManager::ExcludePreloadImage +==================== +*/ +bool idImageManager::ExcludePreloadImage( const char *name ) { + idStrStatic< MAX_OSPATH > imgName = name; + imgName.ToLower(); + if ( imgName.Find( "newfonts/", false ) >= 0 ) { + return true; + } + if ( imgName.Find( "generated/swf/", false ) >= 0 ) { + return true; + } + if ( imgName.Find( "/loadscreens/", false ) >= 0 ) { + return true; + } + return false; +} + +/* +==================== +idImageManager::Preload +==================== +*/ +void idImageManager::Preload( const idPreloadManifest &manifest, const bool & mapPreload ) { + if ( preLoad_Images.GetBool() && manifest.NumResources() > 0 ) { + // preload this levels images + common->Printf( "Preloading images...\n" ); + preloadingMapImages = mapPreload; + int start = Sys_Milliseconds(); + int numLoaded = 0; + + //fileSystem->StartPreload( preloadImageFiles ); + for ( int i = 0; i < manifest.NumResources(); i++ ) { + const preloadEntry_s & p = manifest.GetPreloadByIndex( i ); + if ( p.resType == PRELOAD_IMAGE && !ExcludePreloadImage( p.resourceName ) ) { + globalImages->ImageFromFile( p.resourceName, ( textureFilter_t )p.imgData.filter, ( textureRepeat_t )p.imgData.repeat, ( textureUsage_t )p.imgData.usage, ( cubeFiles_t )p.imgData.cubeMap ); + numLoaded++; + } + } + //fileSystem->StopPreload(); + int end = Sys_Milliseconds(); + common->Printf( "%05d images preloaded ( or were already loaded ) in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 ); + common->Printf( "----------------------------------------\n" ); + preloadingMapImages = false; + } +} + +/* +=============== +idImageManager::LoadLevelImages +=============== +*/ +int idImageManager::LoadLevelImages( bool pacifier ) { + int loadCount = 0; + for ( int i = 0 ; i < images.Num() ; i++ ) { + if ( pacifier ) { + common->UpdateLevelLoadPacifier(); + + } + + idImage *image = images[ i ]; + if ( image->generatorFunction ) { + continue; + } + if ( image->levelLoadReferenced && !image->IsLoaded() ) { + loadCount++; + image->ActuallyLoadImage( false ); + } + } + return loadCount; +} + +/* +=============== +idImageManager::EndLevelLoad +=============== +*/ +void idImageManager::EndLevelLoad() { + insideLevelLoad = false; + + common->Printf( "----- idImageManager::EndLevelLoad -----\n" ); + int start = Sys_Milliseconds(); + int loadCount = LoadLevelImages( true ); + + int end = Sys_Milliseconds(); + common->Printf( "%5i images loaded in %5.1f seconds\n", loadCount, (end-start) * 0.001 ); + common->Printf( "----------------------------------------\n" ); + //R_ListImages_f( idCmdArgs( "sorted sorted", false ) ); +} + +/* +=============== +idImageManager::StartBuild +=============== +*/ +void idImageManager::StartBuild() { +} + +/* +=============== +idImageManager::FinishBuild +=============== +*/ +void idImageManager::FinishBuild( bool removeDups ) { +} + +/* +=============== +idImageManager::PrintMemInfo +=============== +*/ +void idImageManager::PrintMemInfo( MemInfo_t *mi ) { + int i, j, total = 0; + int *sortIndex; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_images.txt" ); + if ( !f ) { + return; + } + + // sort first + sortIndex = new (TAG_IMAGE) int[images.Num()]; + + for ( i = 0; i < images.Num(); i++ ) { + sortIndex[i] = i; + } + + for ( i = 0; i < images.Num() - 1; i++ ) { + for ( j = i + 1; j < images.Num(); j++ ) { + if ( images[sortIndex[i]]->StorageSize() < images[sortIndex[j]]->StorageSize() ) { + int temp = sortIndex[i]; + sortIndex[i] = sortIndex[j]; + sortIndex[j] = temp; + } + } + } + + // print next + for ( i = 0; i < images.Num(); i++ ) { + idImage *im = images[sortIndex[i]]; + int size; + + size = im->StorageSize(); + total += size; + + f->Printf( "%s %3i %s\n", idStr::FormatNumber( size ).c_str(), im->refCount, im->GetName() ); + } + + delete [] sortIndex; + mi->imageAssetsTotal = total; + + f->Printf( "\nTotal image bytes allocated: %s\n", idStr::FormatNumber( total ).c_str() ); + fileSystem->CloseFile( f ); +} diff --git a/neo/renderer/ImageOpts.h b/neo/renderer/ImageOpts.h new file mode 100644 index 00000000..1001a820 --- /dev/null +++ b/neo/renderer/ImageOpts.h @@ -0,0 +1,157 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __IMAGEOPTS_H__ +#define __IMAGEOPTS_H__ + +enum textureType_t { + TT_DISABLED, + TT_2D, + TT_CUBIC +}; + +/* +================================================ +The internal *Texture Format Types*, ::textureFormat_t, are: +================================================ +*/ +enum textureFormat_t { + FMT_NONE, + + //------------------------ + // Standard color image formats + //------------------------ + + FMT_RGBA8, // 32 bpp + FMT_XRGB8, // 32 bpp + + //------------------------ + // Alpha channel only + //------------------------ + + // Alpha ends up being the same as L8A8 in our current implementation, because straight + // alpha gives 0 for color, but we want 1. + FMT_ALPHA, + + //------------------------ + // Luminance replicates the value across RGB with a constant A of 255 + // Intensity replicates the value across RGBA + //------------------------ + + FMT_L8A8, // 16 bpp + FMT_LUM8, // 8 bpp + FMT_INT8, // 8 bpp + + //------------------------ + // Compressed texture formats + //------------------------ + + FMT_DXT1, // 4 bpp + FMT_DXT5, // 8 bpp + + //------------------------ + // Depth buffer formats + //------------------------ + + FMT_DEPTH, // 24 bpp + + //------------------------ + // + //------------------------ + + FMT_X16, // 16 bpp + FMT_Y16_X16, // 32 bpp + FMT_RGB565, // 16 bpp +}; + +int BitsForFormat( textureFormat_t format ); + +/* +================================================ +DXT5 color formats +================================================ +*/ +enum textureColor_t { + CFM_DEFAULT, // RGBA + CFM_NORMAL_DXT5, // XY format and use the fast DXT5 compressor + CFM_YCOCG_DXT5, // convert RGBA to CoCg_Y format + CFM_GREEN_ALPHA // Copy the alpha channel to green +}; + +/* +================================================ +idImageOpts hold parameters for texture operations. +================================================ +*/ +class idImageOpts { +public: + idImageOpts(); + + bool operator==( const idImageOpts & opts ); + + //--------------------------------------------------- + // these determine the physical memory size and layout + //--------------------------------------------------- + + textureType_t textureType; + textureFormat_t format; + textureColor_t colorFormat; + int width; + int height; // not needed for cube maps + int numLevels; // if 0, will be 1 for NEAREST / LINEAR filters, otherwise based on size + bool gammaMips; // if true, mips will be generated with gamma correction + bool readback; // 360 specific - cpu reads back from this texture, so allocate with cached memory +}; + +/* +======================== +idImageOpts::idImageOpts +======================== +*/ +ID_INLINE idImageOpts::idImageOpts() { + format = FMT_NONE; + colorFormat = CFM_DEFAULT; + width = 0; + height = 0; + numLevels = 0; + textureType = TT_2D; + gammaMips = false; + readback = false; + +}; + +/* +======================== +idImageOpts::operator== +======================== +*/ +ID_INLINE bool idImageOpts::operator==( const idImageOpts & opts ) { + return ( memcmp( this, &opts, sizeof( *this ) ) == 0 ); +} + +#endif \ No newline at end of file diff --git a/neo/renderer/Image_files.cpp b/neo/renderer/Image_files.cpp new file mode 100644 index 00000000..f76eabd1 --- /dev/null +++ b/neo/renderer/Image_files.cpp @@ -0,0 +1,765 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" + +/* + +This file only has a single entry point: + +void R_LoadImage( const char *name, byte **pic, int *width, int *height, bool makePowerOf2 ); + +*/ + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#include "jpeg-6/jpeglib.h" + +// hooks from jpeg lib to our system + +void jpg_Error( const char *fmt, ... ) { + va_list argptr; + char msg[2048]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + common->FatalError( "%s", msg ); +} + +void jpg_Printf( const char *fmt, ... ) { + va_list argptr; + char msg[2048]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + common->Printf( "%s", msg ); +} + + + +/* +================ +R_WriteTGA +================ +*/ +void R_WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical, const char * basePath ) { + byte *buffer; + int i; + int bufferSize = width*height*4 + 18; + int imgStart = 18; + + idTempArray buf( bufferSize ); + buffer = (byte *)buf.Ptr(); + memset( buffer, 0, 18 ); + buffer[2] = 2; // uncompressed type + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 32; // pixel size + if ( !flipVertical ) { + buffer[17] = (1<<5); // flip bit, for normal top to bottom raster order + } + + // swap rgb to bgr + for ( i=imgStart ; iWriteFile( filename, buffer, bufferSize, basePath ); +} + +static void LoadTGA( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ); +static void LoadJPG( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ); + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +/* +============= +LoadTGA +============= +*/ +static void LoadTGA( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ) { + int columns, rows, numPixels, fileSize, numBytes; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *buffer; + TargaHeader targa_header; + byte *targa_rgba; + + if ( !pic ) { + fileSystem->ReadFile( name, NULL, timestamp ); + return; // just getting timestamp + } + + *pic = NULL; + + // + // load the file + // + fileSize = fileSystem->ReadFile( name, (void **)&buffer, timestamp ); + if ( !buffer ) { + return; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_length = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.y_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.width = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.height = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) { + common->Error( "LoadTGA( %s ): Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n", name ); + } + + if ( targa_header.colormap_type != 0 ) { + common->Error( "LoadTGA( %s ): colormaps not supported\n", name ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) { + common->Error( "LoadTGA( %s ): Only 32 or 24 bit images supported (no colormaps)\n", name ); + } + + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) { + numBytes = targa_header.width * targa_header.height * ( targa_header.pixel_size >> 3 ); + if ( numBytes > fileSize - 18 - targa_header.id_length ) { + common->Error( "LoadTGA( %s ): incomplete file\n", name ); + } + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if ( width ) { + *width = columns; + } + if ( height ) { + *height = rows; + } + + targa_rgba = (byte *)R_StaticAlloc(numPixels*4, TAG_IMAGE); + *pic = targa_rgba; + + if ( targa_header.id_length != 0 ) { + buf_p += targa_header.id_length; // skip TARGA image comment + } + + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) + { + // Uncompressed RGB or gray scale image + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = targa_rgba + row*columns*4; + for( column = 0; column < columns; column++) + { + unsigned char red,green,blue,alphabyte; + switch( targa_header.pixel_size ) + { + + case 8: + blue = *buf_p++; + green = blue; + red = blue; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + } + } + } + else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images + unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j; + + red = 0; + green = 0; + blue = 0; + alphabyte = 0xff; + + for( row = rows - 1; row >= 0; row-- ) { + pixbuf = targa_rgba + row*columns*4; + for( column = 0; column < columns; ) { + packetHeader= *buf_p++; + packetSize = 1 + (packetHeader & 0x7f); + if ( packetHeader & 0x80 ) { // run-length packet + switch( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + + for( j = 0; j < packetSize; j++ ) { + *pixbuf++=red; + *pixbuf++=green; + *pixbuf++=blue; + *pixbuf++=alphabyte; + column++; + if ( column == columns ) { // run spans across rows + column = 0; + if ( row > 0) { + row--; + } + else { + goto breakOut; + } + pixbuf = targa_rgba + row*columns*4; + } + } + } + else { // non run-length packet + for( j = 0; j < packetSize; j++ ) { + switch( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + column++; + if ( column == columns ) { // pixel packet run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } + else { + goto breakOut; + } + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut: ; + } + } + + if ( (targa_header.attributes & (1<<5)) ) { // image flp bit + if ( width != NULL && height != NULL ) { + R_VerticalFlip( *pic, *width, *height ); + } + } + + fileSystem->FreeFile( buffer ); +} + +/* +========================================================= + +JPG LOADING + +Interfaces with the huge libjpeg +========================================================= +*/ + +/* +============= +LoadJPG +============= +*/ +static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp ) { + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out; + byte *fbuffer; + byte *bbuf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + // JDC: because fill_input_buffer() blindly copies INPUT_BUF_SIZE bytes, + // we need to make sure the file buffer is padded or it may crash + if ( pic ) { + *pic = NULL; // until proven otherwise + } + { + int len; + idFile *f; + + f = fileSystem->OpenFileRead( filename ); + if ( !f ) { + return; + } + len = f->Length(); + if ( timestamp ) { + *timestamp = f->Timestamp(); + } + if ( !pic ) { + fileSystem->CloseFile( f ); + return; // just getting timestamp + } + fbuffer = (byte *)Mem_ClearedAlloc( len + 4096, TAG_JPG ); + f->Read( fbuffer, len ); + fileSystem->CloseFile( f ); + } + + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, fbuffer); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, true ); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + if (cinfo.output_components!=4) { + common->DWarning( "JPG %s is unsupported color depth (%d)", + filename, cinfo.output_components); + } + out = (byte *)R_StaticAlloc(cinfo.output_width*cinfo.output_height*4, TAG_IMAGE); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &bbuf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + // clear all the alphas to 255 + { + int i, j; + byte *buf; + + buf = *pic; + + j = cinfo.output_width * cinfo.output_height * 4; + for ( i = 3 ; i < j ; i+=4 ) { + buf[i] = 255; + } + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + Mem_Free( fbuffer ); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ +} + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. + +Automatically attempts to load .jpg files if .tga files fail to load. + +*pic will be NULL if the load failed. + +Anything that is going to make this into a texture would use +makePowerOf2 = true, but something loading an image as a lookup +table of some sort would leave it in identity form. + +It is important to do this at image load time instead of texture load +time for bump maps. + +Timestamp may be NULL if the value is going to be ignored + +If pic is NULL, the image won't actually be loaded, it will just find the +timestamp. +================= +*/ +void R_LoadImage( const char *cname, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 ) { + idStr name = cname; + + if ( pic ) { + *pic = NULL; + } + if ( timestamp ) { + *timestamp = FILE_NOT_FOUND_TIMESTAMP; + } + if ( width ) { + *width = 0; + } + if ( height ) { + *height = 0; + } + + name.DefaultFileExtension( ".tga" ); + + if (name.Length()<5) { + return; + } + + name.ToLower(); + idStr ext; + name.ExtractFileExtension( ext ); + + if ( ext == "tga" ) { + LoadTGA( name.c_str(), pic, width, height, timestamp ); // try tga first + if ( ( pic && *pic == 0 ) || ( timestamp && *timestamp == -1 ) ) { //-V595 + name.StripFileExtension(); + name.DefaultFileExtension( ".jpg" ); + LoadJPG( name.c_str(), pic, width, height, timestamp ); + } + } else if ( ext == "jpg" ) { + LoadJPG( name.c_str(), pic, width, height, timestamp ); + } + + if ( ( width && *width < 1 ) || ( height && *height < 1 ) ) { + if ( pic && *pic ) { + R_StaticFree( *pic ); + *pic = 0; + } + } + + // + // convert to exact power of 2 sizes + // + /* + if ( pic && *pic && makePowerOf2 ) { + int w, h; + int scaled_width, scaled_height; + byte *resampledBuffer; + + w = *width; + h = *height; + + for (scaled_width = 1 ; scaled_width < w ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < h ; scaled_height<<=1) + ; + + if ( scaled_width != w || scaled_height != h ) { + resampledBuffer = R_ResampleTexture( *pic, w, h, scaled_width, scaled_height ); + R_StaticFree( *pic ); + *pic = resampledBuffer; + *width = scaled_width; + *height = scaled_height; + } + } + */ +} + + +/* +======================= +R_LoadCubeImages + +Loads six files with proper extensions +======================= +*/ +bool R_LoadCubeImages( const char *imgName, cubeFiles_t extensions, byte *pics[6], int *outSize, ID_TIME_T *timestamp ) { + int i, j; + char *cameraSides[6] = { "_forward.tga", "_back.tga", "_left.tga", "_right.tga", + "_up.tga", "_down.tga" }; + char *axisSides[6] = { "_px.tga", "_nx.tga", "_py.tga", "_ny.tga", + "_pz.tga", "_nz.tga" }; + char **sides; + char fullName[MAX_IMAGE_NAME]; + int width, height, size = 0; + + if ( extensions == CF_CAMERA ) { + sides = cameraSides; + } else { + sides = axisSides; + } + + // FIXME: precompressed cube map files + if ( pics ) { + memset( pics, 0, 6*sizeof(pics[0]) ); + } + if ( timestamp ) { + *timestamp = 0; + } + + for ( i = 0 ; i < 6 ; i++ ) { + idStr::snPrintf( fullName, sizeof( fullName ), "%s%s", imgName, sides[i] ); + + ID_TIME_T thisTime; + if ( !pics ) { + // just checking timestamps + R_LoadImageProgram( fullName, NULL, &width, &height, &thisTime ); + } else { + R_LoadImageProgram( fullName, &pics[i], &width, &height, &thisTime ); + } + if ( thisTime == FILE_NOT_FOUND_TIMESTAMP ) { + break; + } + if ( i == 0 ) { + size = width; + } + if ( width != size || height != size ) { + common->Warning( "Mismatched sizes on cube map '%s'", imgName ); + break; + } + if ( timestamp ) { + if ( thisTime > *timestamp ) { + *timestamp = thisTime; + } + } + if ( pics && extensions == CF_CAMERA ) { + // convert from "camera" images to native cube map images + switch( i ) { + case 0: // forward + R_RotatePic( pics[i], width); + break; + case 1: // back + R_RotatePic( pics[i], width); + R_HorizontalFlip( pics[i], width, height ); + R_VerticalFlip( pics[i], width, height ); + break; + case 2: // left + R_VerticalFlip( pics[i], width, height ); + break; + case 3: // right + R_HorizontalFlip( pics[i], width, height ); + break; + case 4: // up + R_RotatePic( pics[i], width); + break; + case 5: // down + R_RotatePic( pics[i], width); + break; + } + } + } + + if ( i != 6 ) { + // we had an error, so free everything + if ( pics ) { + for ( j = 0 ; j < i ; j++ ) { + R_StaticFree( pics[j] ); + } + } + + if ( timestamp ) { + *timestamp = 0; + } + return false; + } + + if ( outSize ) { + *outSize = size; + } + return true; +} diff --git a/neo/renderer/Image_intrinsic.cpp b/neo/renderer/Image_intrinsic.cpp new file mode 100644 index 00000000..1271cd4e --- /dev/null +++ b/neo/renderer/Image_intrinsic.cpp @@ -0,0 +1,430 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" + +#define DEFAULT_SIZE 16 + +/* +================== +idImage::MakeDefault + +the default image will be grey with a white box outline +to allow you to see the mapping coordinates on a surface +================== +*/ +void idImage::MakeDefault() { + int x, y; + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + if ( com_developer.GetBool() ) { + // grey center + for ( y = 0 ; y < DEFAULT_SIZE ; y++ ) { + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[y][x][0] = 32; + data[y][x][1] = 32; + data[y][x][2] = 32; + data[y][x][3] = 255; + } + } + + // white border + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[0][x][0] = + data[0][x][1] = + data[0][x][2] = + data[0][x][3] = 255; + + data[x][0][0] = + data[x][0][1] = + data[x][0][2] = + data[x][0][3] = 255; + + data[DEFAULT_SIZE-1][x][0] = + data[DEFAULT_SIZE-1][x][1] = + data[DEFAULT_SIZE-1][x][2] = + data[DEFAULT_SIZE-1][x][3] = 255; + + data[x][DEFAULT_SIZE-1][0] = + data[x][DEFAULT_SIZE-1][1] = + data[x][DEFAULT_SIZE-1][2] = + data[x][DEFAULT_SIZE-1][3] = 255; + } + } else { + for ( y = 0 ; y < DEFAULT_SIZE ; y++ ) { + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[y][x][0] = 0; + data[y][x][1] = 0; + data[y][x][2] = 0; + data[y][x][3] = 0; + } + } + } + + GenerateImage( (byte *)data, + DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, TR_REPEAT, TD_DEFAULT ); + + defaulted = true; +} + +static void R_DefaultImage( idImage *image ) { + image->MakeDefault(); +} + +static void R_WhiteImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + // solid white texture + memset( data, 255, sizeof( data ) ); + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, TR_REPEAT, TD_DEFAULT ); +} + +static void R_BlackImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + // solid black texture + memset( data, 0, sizeof( data ) ); + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, TR_REPEAT, TD_DEFAULT ); +} + +static void R_RGBA8Image( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + memset( data, 0, sizeof( data ) ); + data[0][0][0] = 16; + data[0][0][1] = 32; + data[0][0][2] = 48; + data[0][0][3] = 96; + + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, TF_DEFAULT, TR_REPEAT, TD_LOOKUP_TABLE_RGBA ); +} + +static void R_DepthImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + memset( data, 0, sizeof( data ) ); + data[0][0][0] = 16; + data[0][0][1] = 32; + data[0][0][2] = 48; + data[0][0][3] = 96; + + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, TF_NEAREST, TR_CLAMP, TD_DEPTH ); +} + +static void R_AlphaNotchImage( idImage *image ) { + byte data[2][4]; + + // this is used for alpha test clip planes + + data[0][0] = data[0][1] = data[0][2] = 255; + data[0][3] = 0; + data[1][0] = data[1][1] = data[1][2] = 255; + data[1][3] = 255; + + image->GenerateImage( (byte *)data, 2, 1, TF_NEAREST, TR_CLAMP, TD_LOOKUP_TABLE_ALPHA ); +} + +static void R_FlatNormalImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + // flat normal map for default bunp mapping + for ( int i = 0 ; i < 4 ; i++ ) { + data[0][i][0] = 128; + data[0][i][1] = 128; + data[0][i][2] = 255; + data[0][i][3] = 255; + } + image->GenerateImage( (byte *)data, 2, 2, TF_DEFAULT, TR_REPEAT, TD_BUMP ); +} + +/* +================ +R_CreateNoFalloffImage + +This is a solid white texture that is zero clamped. +================ +*/ +static void R_CreateNoFalloffImage( idImage *image ) { + int x,y; + byte data[16][FALLOFF_TEXTURE_SIZE][4]; + + memset( data, 0, sizeof( data ) ); + for (x=1 ; xGenerateImage( (byte *)data, FALLOFF_TEXTURE_SIZE, 16, TF_DEFAULT, TR_CLAMP_TO_ZERO, TD_LOOKUP_TABLE_MONO ); +} + +/* +================ +R_FogImage + +We calculate distance correctly in two planes, but the +third will still be projection based +================ +*/ +const int FOG_SIZE = 128; + +void R_FogImage( idImage *image ) { + int x,y; + byte data[FOG_SIZE][FOG_SIZE][4]; + int b; + + float step[256]; + int i; + float remaining = 1.0; + for ( i = 0 ; i < 256 ; i++ ) { + step[i] = remaining; + remaining *= 0.982f; + } + + for (x=0 ; x 255 ) { + b = 255; + } + b = (byte)(255 * ( 1.0 - step[b] )); + if ( x == 0 || x == FOG_SIZE-1 || y == 0 || y == FOG_SIZE-1 ) { + b = 255; // avoid clamping issues + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = 255; + data[y][x][3] = b; + } + } + + image->GenerateImage( (byte *)data, FOG_SIZE, FOG_SIZE, TF_LINEAR, TR_CLAMP, TD_LOOKUP_TABLE_ALPHA ); +} + + +/* +================ +FogFraction + +Height values below zero are inside the fog volume +================ +*/ +static const float RAMP_RANGE = 8; +static const float DEEP_RANGE = -30; +static float FogFraction( float viewHeight, float targetHeight ) { + float total = idMath::Fabs( targetHeight - viewHeight ); + +// return targetHeight >= 0 ? 0 : 1.0; + + // only ranges that cross the ramp range are special + if ( targetHeight > 0 && viewHeight > 0 ) { + return 0.0; + } + if ( targetHeight < -RAMP_RANGE && viewHeight < -RAMP_RANGE ) { + return 1.0; + } + + float above; + if ( targetHeight > 0 ) { + above = targetHeight; + } else if ( viewHeight > 0 ) { + above = viewHeight; + } else { + above = 0; + } + + float rampTop, rampBottom; + + if ( viewHeight > targetHeight ) { + rampTop = viewHeight; + rampBottom = targetHeight; + } else { + rampTop = targetHeight; + rampBottom = viewHeight; + } + if ( rampTop > 0 ) { + rampTop = 0; + } + if ( rampBottom < -RAMP_RANGE ) { + rampBottom = -RAMP_RANGE; + } + + float rampSlope = 1.0 / RAMP_RANGE; + + if ( !total ) { + return -viewHeight * rampSlope; + } + + float ramp = ( 1.0 - ( rampTop * rampSlope + rampBottom * rampSlope ) * -0.5 ) * ( rampTop - rampBottom ); + + float frac = ( total - above - ramp ) / total; + + // after it gets moderately deep, always use full value + float deepest = viewHeight < targetHeight ? viewHeight : targetHeight; + + float deepFrac = deepest / DEEP_RANGE; + if ( deepFrac >= 1.0 ) { + return 1.0; + } + + frac = frac * ( 1.0 - deepFrac ) + deepFrac; + + return frac; +} + +/* +================ +R_FogEnterImage + +Modulate the fog alpha density based on the distance of the +start and end points to the terminator plane +================ +*/ +void R_FogEnterImage( idImage *image ) { + int x,y; + byte data[FOG_ENTER_SIZE][FOG_ENTER_SIZE][4]; + int b; + + for (x=0 ; x 255 ) { + b = 255; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = 255; + data[y][x][3] = b; + } + } + + // if mipmapped, acutely viewed surfaces fade wrong + image->GenerateImage( (byte *)data, FOG_ENTER_SIZE, FOG_ENTER_SIZE, TF_LINEAR, TR_CLAMP, TD_LOOKUP_TABLE_ALPHA ); +} + + +/* +================ +R_QuadraticImage + +================ +*/ +static const int QUADRATIC_WIDTH = 32; +static const int QUADRATIC_HEIGHT = 4; + +void R_QuadraticImage( idImage *image ) { + int x,y; + byte data[QUADRATIC_HEIGHT][QUADRATIC_WIDTH][4]; + int b; + + + for (x=0 ; x 255 ) { + b = 255; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + + image->GenerateImage( (byte *)data, QUADRATIC_WIDTH, QUADRATIC_HEIGHT, TF_DEFAULT, TR_CLAMP, TD_LOOKUP_TABLE_RGB1 ); +} + +/* +================ +idImageManager::CreateIntrinsicImages +================ +*/ +void idImageManager::CreateIntrinsicImages() { + // create built in images + defaultImage = ImageFromFunction( "_default", R_DefaultImage ); + whiteImage = ImageFromFunction( "_white", R_WhiteImage ); + blackImage = ImageFromFunction( "_black", R_BlackImage ); + flatNormalMap = ImageFromFunction( "_flat", R_FlatNormalImage ); + alphaNotchImage = ImageFromFunction( "_alphaNotch", R_AlphaNotchImage ); + fogImage = ImageFromFunction( "_fog", R_FogImage ); + fogEnterImage = ImageFromFunction( "_fogEnter", R_FogEnterImage ); + noFalloffImage = ImageFromFunction( "_noFalloff", R_CreateNoFalloffImage ); + ImageFromFunction( "_quadratic", R_QuadraticImage ); + + // scratchImage is used for screen wipes/doublevision etc.. + scratchImage = ImageFromFunction("_scratch", R_RGBA8Image ); + scratchImage2 = ImageFromFunction("_scratch2", R_RGBA8Image ); + accumImage = ImageFromFunction("_accum", R_RGBA8Image ); + currentRenderImage = ImageFromFunction("_currentRender", R_RGBA8Image ); + currentDepthImage = ImageFromFunction("_currentDepth", R_DepthImage ); + + // save a copy of this for material comparison, because currentRenderImage may get + // reassigned during stereo rendering + originalCurrentRenderImage = currentRenderImage; + + loadingIconImage = ImageFromFile("textures/loadingicon2", TF_DEFAULT, TR_CLAMP, TD_DEFAULT, CF_2D ); + hellLoadingIconImage = ImageFromFile("textures/loadingicon3", TF_DEFAULT, TR_CLAMP, TD_DEFAULT, CF_2D ); + + release_assert( loadingIconImage->referencedOutsideLevelLoad ); + release_assert( hellLoadingIconImage->referencedOutsideLevelLoad ); +} \ No newline at end of file diff --git a/neo/renderer/Image_load.cpp b/neo/renderer/Image_load.cpp new file mode 100644 index 00000000..191d411e --- /dev/null +++ b/neo/renderer/Image_load.cpp @@ -0,0 +1,715 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +================ +BitsForFormat +================ +*/ +int BitsForFormat( textureFormat_t format ) { + switch ( format ) { + case FMT_NONE: return 0; + case FMT_RGBA8: return 32; + case FMT_XRGB8: return 32; + case FMT_RGB565: return 16; + case FMT_L8A8: return 16; + case FMT_ALPHA: return 8; + case FMT_LUM8: return 8; + case FMT_INT8: return 8; + case FMT_DXT1: return 4; + case FMT_DXT5: return 8; + case FMT_DEPTH: return 32; + case FMT_X16: return 16; + case FMT_Y16_X16: return 32; + default: + assert( 0 ); + return 0; + } +} + +/* +======================== +idImage::DeriveOpts +======================== +*/ +ID_INLINE void idImage::DeriveOpts() { + + if ( opts.format == FMT_NONE ) { + opts.colorFormat = CFM_DEFAULT; + + switch ( usage ) { + case TD_COVERAGE: + opts.format = FMT_DXT1; + opts.colorFormat = CFM_GREEN_ALPHA; + break; + case TD_DEPTH: + opts.format = FMT_DEPTH; + break; + case TD_DIFFUSE: + // TD_DIFFUSE gets only set to when its a diffuse texture for an interaction + opts.gammaMips = true; + opts.format = FMT_DXT5; + opts.colorFormat = CFM_YCOCG_DXT5; + break; + case TD_SPECULAR: + opts.gammaMips = true; + opts.format = FMT_DXT1; + opts.colorFormat = CFM_DEFAULT; + break; + case TD_DEFAULT: + opts.gammaMips = true; + opts.format = FMT_DXT5; + opts.colorFormat = CFM_DEFAULT; + break; + case TD_BUMP: + opts.format = FMT_DXT5; + opts.colorFormat = CFM_NORMAL_DXT5; + break; + case TD_FONT: + opts.format = FMT_DXT1; + opts.colorFormat = CFM_GREEN_ALPHA; + opts.numLevels = 4; // We only support 4 levels because we align to 16 in the exporter + opts.gammaMips = true; + break; + case TD_LIGHT: + opts.format = FMT_RGB565; + opts.gammaMips = true; + break; + case TD_LOOKUP_TABLE_MONO: + opts.format = FMT_INT8; + break; + case TD_LOOKUP_TABLE_ALPHA: + opts.format = FMT_ALPHA; + break; + case TD_LOOKUP_TABLE_RGB1: + case TD_LOOKUP_TABLE_RGBA: + opts.format = FMT_RGBA8; + break; + default: + assert( false ); + opts.format = FMT_RGBA8; + } + } + + if ( opts.numLevels == 0 ) { + opts.numLevels = 1; + + if ( filter == TF_LINEAR || filter == TF_NEAREST ) { + // don't create mip maps if we aren't going to be using them + } else { + int temp_width = opts.width; + int temp_height = opts.height; + while ( temp_width > 1 || temp_height > 1 ) { + temp_width >>= 1; + temp_height >>= 1; + if ( ( opts.format == FMT_DXT1 || opts.format == FMT_DXT5 ) && + ( ( temp_width & 0x3 ) != 0 || ( temp_height & 0x3 ) != 0 ) ) { + break; + } + opts.numLevels++; + } + } + } +} + +/* +======================== +idImage::AllocImage +======================== +*/ +void idImage::AllocImage( const idImageOpts &imgOpts, textureFilter_t tf, textureRepeat_t tr ) { + filter = tf; + repeat = tr; + opts = imgOpts; + DeriveOpts(); + AllocImage(); +} + +/* +================ +GenerateImage +================ +*/ +void idImage::GenerateImage( const byte *pic, int width, int height, textureFilter_t filterParm, textureRepeat_t repeatParm, textureUsage_t usageParm ) { + PurgeImage(); + + filter = filterParm; + repeat = repeatParm; + usage = usageParm; + cubeFiles = CF_2D; + + opts.textureType = TT_2D; + opts.width = width; + opts.height = height; + opts.numLevels = 0; + DeriveOpts(); + + // if we don't have a rendering context, just return after we + // have filled in the parms. We must have the values set, or + // an image match from a shader before the render starts would miss + // the generated texture + if ( !R_IsInitialized() ) { + return; + } + + idBinaryImage im( GetName() ); + im.Load2DFromMemory( width, height, pic, opts.numLevels, opts.format, opts.colorFormat, opts.gammaMips ); + + AllocImage(); + + for ( int i = 0; i < im.NumImages(); i++ ) { + const bimageImage_t & img = im.GetImageHeader( i ); + const byte * data = im.GetImageData( i ); + SubImageUpload( img.level, 0, 0, img.destZ, img.width, img.height, data ); + } +} + +/* +==================== +GenerateCubeImage + +Non-square cube sides are not allowed +==================== +*/ +void idImage::GenerateCubeImage( const byte *pic[6], int size, textureFilter_t filterParm, textureUsage_t usageParm ) { + PurgeImage(); + + filter = filterParm; + repeat = TR_CLAMP; + usage = usageParm; + cubeFiles = CF_NATIVE; + + opts.textureType = TT_CUBIC; + opts.width = size; + opts.height = size; + opts.numLevels = 0; + DeriveOpts(); + + // if we don't have a rendering context, just return after we + // have filled in the parms. We must have the values set, or + // an image match from a shader before the render starts would miss + // the generated texture + if ( !R_IsInitialized() ) { + return; + } + + idBinaryImage im( GetName() ); + im.LoadCubeFromMemory( size, pic, opts.numLevels, opts.format, opts.gammaMips ); + + AllocImage(); + + for ( int i = 0; i < im.NumImages(); i++ ) { + const bimageImage_t & img = im.GetImageHeader( i ); + const byte * data = im.GetImageData( i ); + SubImageUpload( img.level, 0, 0, img.destZ, img.width, img.height, data ); + } +} + +/* +=============== +GetGeneratedName + +name contains GetName() upon entry +=============== +*/ + void idImage::GetGeneratedName( idStr &_name, const textureUsage_t &_usage, const cubeFiles_t &_cube ) { + idStrStatic< 64 > extension; + + _name.ExtractFileExtension( extension ); + _name.StripFileExtension(); + + _name += va( "#__%02d%02d", (int)_usage, (int)_cube ); + if ( extension.Length() > 0 ) { + _name.SetFileExtension( extension ); + } +} + + +/* +=============== +ActuallyLoadImage + +Absolutely every image goes through this path +On exit, the idImage will have a valid OpenGL texture number that can be bound +=============== +*/ +void idImage::ActuallyLoadImage( bool fromBackEnd ) { + + // if we don't have a rendering context yet, just return + if ( !R_IsInitialized() ) { + return; + } + + // this is the ONLY place generatorFunction will ever be called + if ( generatorFunction ) { + generatorFunction( this ); + return; + } + + if ( com_productionMode.GetInteger() != 0 ) { + sourceFileTime = FILE_NOT_FOUND_TIMESTAMP; + if ( cubeFiles != CF_2D ) { + opts.textureType = TT_CUBIC; + repeat = TR_CLAMP; + } + } else { + if ( cubeFiles != CF_2D ) { + opts.textureType = TT_CUBIC; + repeat = TR_CLAMP; + R_LoadCubeImages( GetName(), cubeFiles, NULL, NULL, &sourceFileTime ); + } else { + opts.textureType = TT_2D; + R_LoadImageProgram( GetName(), NULL, NULL, NULL, &sourceFileTime, &usage ); + } + } + + // Figure out opts.colorFormat and opts.format so we can make sure the binary image is up to date + DeriveOpts(); + + idStrStatic< MAX_OSPATH > generatedName = GetName(); + GetGeneratedName( generatedName, usage, cubeFiles ); + + idBinaryImage im( generatedName ); + binaryFileTime = im.LoadFromGeneratedFile( sourceFileTime ); + + // BFHACK, do not want to tweak on buildgame so catch these images here + if ( binaryFileTime == FILE_NOT_FOUND_TIMESTAMP && fileSystem->UsingResourceFiles() ) { + int c = 1; + while ( c-- > 0 ) { + if ( generatedName.Find( "guis/assets/white#__0000", false ) >= 0 ) { + generatedName.Replace( "white#__0000", "white#__0200" ); + im.SetName( generatedName ); + binaryFileTime = im.LoadFromGeneratedFile( sourceFileTime ); + break; + } + if ( generatedName.Find( "guis/assets/white#__0100", false ) >= 0 ) { + generatedName.Replace( "white#__0100", "white#__0200" ); + im.SetName( generatedName ); + binaryFileTime = im.LoadFromGeneratedFile( sourceFileTime ); + break; + } + if ( generatedName.Find( "textures/black#__0100", false ) >= 0 ) { + generatedName.Replace( "black#__0100", "black#__0200" ); + im.SetName( generatedName ); + binaryFileTime = im.LoadFromGeneratedFile( sourceFileTime ); + break; + } + if ( generatedName.Find( "textures/decals/bulletglass1_d#__0100", false ) >= 0 ) { + generatedName.Replace( "bulletglass1_d#__0100", "bulletglass1_d#__0200" ); + im.SetName( generatedName ); + binaryFileTime = im.LoadFromGeneratedFile( sourceFileTime ); + break; + } + if ( generatedName.Find( "models/monsters/skeleton/skeleton01_d#__1000", false ) >= 0 ) { + generatedName.Replace( "skeleton01_d#__1000", "skeleton01_d#__0100" ); + im.SetName( generatedName ); + binaryFileTime = im.LoadFromGeneratedFile( sourceFileTime ); + break; + } + } + } + const bimageFile_t & header = im.GetFileHeader(); + + if ( ( fileSystem->InProductionMode() && binaryFileTime != FILE_NOT_FOUND_TIMESTAMP ) || ( ( binaryFileTime != FILE_NOT_FOUND_TIMESTAMP ) + && ( header.colorFormat == opts.colorFormat ) + && ( header.format == opts.format ) + && ( header.textureType == opts.textureType ) + ) ) { + opts.width = header.width; + opts.height = header.height; + opts.numLevels = header.numLevels; + opts.colorFormat = (textureColor_t)header.colorFormat; + opts.format = (textureFormat_t)header.format; + opts.textureType = (textureType_t)header.textureType; + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + // for resource gathering write this image to the preload file for this map + fileSystem->AddImagePreload( GetName(), filter, repeat, usage, cubeFiles ); + } + } else { + if ( cubeFiles != CF_2D ) { + int size; + byte * pics[6]; + + if ( !R_LoadCubeImages( GetName(), cubeFiles, pics, &size, &sourceFileTime ) || size == 0 ) { + idLib::Warning( "Couldn't load cube image: %s", GetName() ); + return; + } + + opts.textureType = TT_CUBIC; + repeat = TR_CLAMP; + opts.width = size; + opts.height = size; + opts.numLevels = 0; + DeriveOpts(); + im.LoadCubeFromMemory( size, (const byte **)pics, opts.numLevels, opts.format, opts.gammaMips ); + repeat = TR_CLAMP; + + for ( int i = 0; i < 6; i++ ) { + if ( pics[i] ) { + Mem_Free( pics[i] ); + } + } + } else { + int width, height; + byte * pic; + + // load the full specification, and perform any image program calculations + R_LoadImageProgram( GetName(), &pic, &width, &height, &sourceFileTime, &usage ); + + if ( pic == NULL ) { + idLib::Warning( "Couldn't load image: %s : %s", GetName(), generatedName.c_str() ); + // create a default so it doesn't get continuously reloaded + opts.width = 8; + opts.height = 8; + opts.numLevels = 1; + DeriveOpts(); + AllocImage(); + + // clear the data so it's not left uninitialized + idTempArray clear( opts.width * opts.height * 4 ); + memset( clear.Ptr(), 0, clear.Size() ); + for ( int level = 0; level < opts.numLevels; level++ ) { + SubImageUpload( level, 0, 0, 0, opts.width >> level, opts.height >> level, clear.Ptr() ); + } + + return; + } + + opts.width = width; + opts.height = height; + opts.numLevels = 0; + DeriveOpts(); + im.Load2DFromMemory( opts.width, opts.height, pic, opts.numLevels, opts.format, opts.colorFormat, opts.gammaMips ); + + Mem_Free( pic ); + } + binaryFileTime = im.WriteGeneratedFile( sourceFileTime ); + } + + AllocImage(); + + + for ( int i = 0; i < im.NumImages(); i++ ) { + const bimageImage_t & img = im.GetImageHeader( i ); + const byte * data = im.GetImageData( i ); + SubImageUpload( img.level, 0, 0, img.destZ, img.width, img.height, data ); + } +} + +/* +============== +Bind + +Automatically enables 2D mapping or cube mapping if needed +============== +*/ +void idImage::Bind() { + + RENDERLOG_PRINTF( "idImage::Bind( %s )\n", GetName() ); + + // load the image if necessary (FIXME: not SMP safe!) + if ( !IsLoaded() ) { + // load the image on demand here, which isn't our normal game operating mode + ActuallyLoadImage( true ); + } + + const int texUnit = backEnd.glState.currenttmu; + + tmu_t * tmu = &backEnd.glState.tmu[texUnit]; + // bind the texture + if ( opts.textureType == TT_2D ) { + if ( tmu->current2DMap != texnum ) { + tmu->current2DMap = texnum; + qglBindMultiTextureEXT( GL_TEXTURE0_ARB + texUnit, GL_TEXTURE_2D, texnum ); + } + } else if ( opts.textureType == TT_CUBIC ) { + if ( tmu->currentCubeMap != texnum ) { + tmu->currentCubeMap = texnum; + qglBindMultiTextureEXT( GL_TEXTURE0_ARB + texUnit, GL_TEXTURE_CUBE_MAP_EXT, texnum ); + } + } + +} + +/* +================ +MakePowerOfTwo +================ +*/ +int MakePowerOfTwo( int num ) { + int pot; + for ( pot = 1; pot < num; pot <<= 1 ) { + } + return pot; +} + +/* +==================== +CopyFramebuffer +==================== +*/ +void idImage::CopyFramebuffer( int x, int y, int imageWidth, int imageHeight ) { + + + qglBindTexture( ( opts.textureType == TT_CUBIC ) ? GL_TEXTURE_CUBE_MAP_EXT : GL_TEXTURE_2D, texnum ); + + qglReadBuffer( GL_BACK ); + + opts.width = imageWidth; + opts.height = imageHeight; + qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, x, y, imageWidth, imageHeight, 0 ); + + // these shouldn't be necessary if the image was initialized properly + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + + backEnd.pc.c_copyFrameBuffer++; +} + +/* +==================== +CopyDepthbuffer +==================== +*/ +void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight ) { + qglBindTexture( ( opts.textureType == TT_CUBIC ) ? GL_TEXTURE_CUBE_MAP_EXT : GL_TEXTURE_2D, texnum ); + + opts.width = imageWidth; + opts.height = imageHeight; + qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, x, y, imageWidth, imageHeight, 0 ); + + backEnd.pc.c_copyFrameBuffer++; +} + +/* +============= +RB_UploadScratchImage + +if rows = cols * 6, assume it is a cube map animation +============= +*/ +void idImage::UploadScratch( const byte * data, int cols, int rows ) { + + // if rows = cols * 6, assume it is a cube map animation + if ( rows == cols * 6 ) { + rows /= 6; + const byte * pic[6]; + for ( int i = 0; i < 6; i++ ) { + pic[i] = data + cols * rows * 4 * i; + } + + if ( opts.textureType != TT_CUBIC || usage != TD_LOOKUP_TABLE_RGBA ) { + GenerateCubeImage( pic, cols, TF_LINEAR, TD_LOOKUP_TABLE_RGBA ); + return; + } + if ( opts.width != cols || opts.height != rows ) { + opts.width = cols; + opts.height = rows; + AllocImage(); + } + SetSamplerState( TF_LINEAR, TR_CLAMP ); + for ( int i = 0; i < 6; i++ ) { + SubImageUpload( 0, 0, 0, i, opts.width, opts.height, pic[i] ); + } + + } else { + if ( opts.textureType != TT_2D || usage != TD_LOOKUP_TABLE_RGBA ) { + GenerateImage( data, cols, rows, TF_LINEAR, TR_REPEAT, TD_LOOKUP_TABLE_RGBA ); + return; + } + if ( opts.width != cols || opts.height != rows ) { + opts.width = cols; + opts.height = rows; + AllocImage(); + } + SetSamplerState( TF_LINEAR, TR_REPEAT ); + SubImageUpload( 0, 0, 0, 0, opts.width, opts.height, data ); + } +} + +/* +================== +StorageSize +================== +*/ +int idImage::StorageSize() const { + + if ( !IsLoaded() ) { + return 0; + } + int baseSize = opts.width * opts.height; + if ( opts.numLevels > 1 ) { + baseSize *= 4; + baseSize /= 3; + } + baseSize *= BitsForFormat( opts.format ); + baseSize /= 8; + return baseSize; +} + +/* +================== +Print +================== +*/ +void idImage::Print() const { + if ( generatorFunction ) { + common->Printf( "F" ); + } else { + common->Printf( " " ); + } + + switch ( opts.textureType ) { + case TT_2D: + common->Printf( " " ); + break; + case TT_CUBIC: + common->Printf( "C" ); + break; + default: + common->Printf( "", opts.textureType ); + break; + } + + common->Printf( "%4i %4i ", opts.width, opts.height ); + + switch ( opts.format ) { +#define NAME_FORMAT( x ) case FMT_##x: common->Printf( "%-6s ", #x ); break; + NAME_FORMAT( NONE ); + NAME_FORMAT( RGBA8 ); + NAME_FORMAT( XRGB8 ); + NAME_FORMAT( RGB565 ); + NAME_FORMAT( L8A8 ); + NAME_FORMAT( ALPHA ); + NAME_FORMAT( LUM8 ); + NAME_FORMAT( INT8 ); + NAME_FORMAT( DXT1 ); + NAME_FORMAT( DXT5 ); + NAME_FORMAT( DEPTH ); + NAME_FORMAT( X16 ); + NAME_FORMAT( Y16_X16 ); + default: + common->Printf( "<%3i>", opts.format ); + break; + } + + switch( filter ) { + case TF_DEFAULT: + common->Printf( "mip " ); + break; + case TF_LINEAR: + common->Printf( "linr " ); + break; + case TF_NEAREST: + common->Printf( "nrst " ); + break; + default: + common->Printf( "", filter ); + break; + } + + switch ( repeat ) { + case TR_REPEAT: + common->Printf( "rept " ); + break; + case TR_CLAMP_TO_ZERO: + common->Printf( "zero " ); + break; + case TR_CLAMP_TO_ZERO_ALPHA: + common->Printf( "azro " ); + break; + case TR_CLAMP: + common->Printf( "clmp " ); + break; + default: + common->Printf( "", repeat ); + break; + } + + common->Printf( "%4ik ", StorageSize() / 1024 ); + + common->Printf( " %s\n", GetName() ); +} + +/* +=============== +idImage::Reload +=============== +*/ +void idImage::Reload( bool force ) { + // always regenerate functional images + if ( generatorFunction ) { + common->DPrintf( "regenerating %s.\n", GetName() ); + generatorFunction( this ); + return; + } + + // check file times + if ( !force ) { + ID_TIME_T current; + if ( cubeFiles != CF_2D ) { + R_LoadCubeImages( imgName, cubeFiles, NULL, NULL, ¤t ); + } else { + // get the current values + R_LoadImageProgram( imgName, NULL, NULL, NULL, ¤t ); + } + if ( current <= sourceFileTime ) { + return; + } + } + + common->DPrintf( "reloading %s.\n", GetName() ); + + PurgeImage(); + + // Load is from the front end, so the back end must be synced + ActuallyLoadImage( false ); +} + +/* +======================== +idImage::SetSamplerState +======================== +*/ +void idImage::SetSamplerState( textureFilter_t tf, textureRepeat_t tr ) { + if ( tf == filter && tr == repeat ) { + return; + } + filter = tf; + repeat = tr; + qglBindTexture( ( opts.textureType == TT_CUBIC ) ? GL_TEXTURE_CUBE_MAP_EXT : GL_TEXTURE_2D, texnum ); + SetTexParameters(); +} diff --git a/neo/renderer/Image_process.cpp b/neo/renderer/Image_process.cpp new file mode 100644 index 00000000..6b992021 --- /dev/null +++ b/neo/renderer/Image_process.cpp @@ -0,0 +1,492 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" + +/* +================ +R_ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only have filter coverage if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +after resampling to the next lower power of two. +================ +*/ +#define MAX_DIMENSION 4096 +byte *R_ResampleTexture( const byte *in, int inwidth, int inheight, + int outwidth, int outheight ) { + int i, j; + const byte *inrow, *inrow2; + unsigned int frac, fracstep; + unsigned int p1[MAX_DIMENSION], p2[MAX_DIMENSION]; + const byte *pix1, *pix2, *pix3, *pix4; + byte *out, *out_p; + + if ( outwidth > MAX_DIMENSION ) { + outwidth = MAX_DIMENSION; + } + if ( outheight > MAX_DIMENSION ) { + outheight = MAX_DIMENSION; + } + + out = (byte *)R_StaticAlloc( outwidth * outheight * 4, TAG_IMAGE ); + out_p = out; + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for ( i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for ( i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i> 1; + for (j=0 ; j>2; + out_p[j*4+1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + out_p[j*4+2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + out_p[j*4+3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } + + return out; +} + +/* +================ +R_Dropsample + +Used to resample images in a more general than quartering fashion. +Normal maps and such should not be bilerped. +================ +*/ +byte *R_Dropsample( const byte *in, int inwidth, int inheight, + int outwidth, int outheight ) { + int i, j, k; + const byte *inrow; + const byte *pix1; + byte *out, *out_p; + + out = (byte *)R_StaticAlloc( outwidth * outheight * 4, TAG_IMAGE ); + out_p = out; + + for (i=0 ; iFatalError( "R_MipMapWithAlphaMin called with size %i,%i", width, height ); + } + + // convert the incoming texture to centered floating point + c = width * height; + fbuf = (float *)_alloca( c * 4 * sizeof( *fbuf ) ); + in_p = in; + fbuf_p = fbuf; + for ( i = 0 ; i < c ; i++, in_p+=4, fbuf_p += 4 ) { + fbuf_p[0] = ( in_p[0] / 255.0 ) * 2.0 - 1.0; // convert to a normal + fbuf_p[1] = ( in_p[1] / 255.0 ) * 2.0 - 1.0; + fbuf_p[2] = ( in_p[2] / 255.0 ) * 2.0 - 1.0; + fbuf_p[3] = ( in_p[3] / 255.0 ); // filtered divegence / specularity + } + + row = width * 4; + + newWidth = width >> 1; + newHeight = height >> 1; + if ( !newWidth ) { + newWidth = 1; + } + if ( !newHeight ) { + newHeight = 1; + } + out = (byte *)R_StaticAlloc( newWidth * newHeight * 4, TAG_IMAGE ); + out_p = out; + + in_p = in; + + for ( i=0 ; i> 1; + newHeight = height >> 1; + if ( !newWidth ) { + newWidth = 1; + } + if ( !newHeight ) { + newHeight = 1; + } + out = (byte *)R_StaticAlloc( newWidth * newHeight * 4, TAG_IMAGE ); + out_p = out; + + in_p = in; + + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i> 1; + newHeight = height >> 1; + if ( !newWidth ) { + newWidth = 1; + } + if ( !newHeight ) { + newHeight = 1; + } + out = (byte *)R_StaticAlloc( newWidth * newHeight * 4, TAG_IMAGE ); + out_p = out; + + in_p = in; + + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out_p[1] = ( in_p[1] + in_p[5] )>>1; + out_p[2] = ( in_p[2] + in_p[6] )>>1; + out_p[3] = ( in_p[3] + in_p[7] )>>1; + } + return out; + } + + for (i=0 ; i>2; + out_p[1] = (in_p[1] + in_p[5] + in_p[row+1] + in_p[row+5])>>2; + out_p[2] = (in_p[2] + in_p[6] + in_p[row+2] + in_p[row+6])>>2; + out_p[3] = (in_p[3] + in_p[7] + in_p[row+3] + in_p[row+7])>>2; + } + } + + return out; +} + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + + +/* +================== +R_HorizontalFlip + +Flip the image in place +================== +*/ +void R_HorizontalFlip( byte *data, int width, int height ) { + int i, j; + int temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width / 2 ; j++ ) { + temp = *( (int *)data + i * width + j ); + *( (int *)data + i * width + j ) = *( (int *)data + i * width + width - 1 - j ); + *( (int *)data + i * width + width - 1 - j ) = temp; + } + } +} + +void R_VerticalFlip( byte *data, int width, int height ) { + int i, j; + int temp; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height / 2 ; j++ ) { + temp = *( (int *)data + j * width + i ); + *( (int *)data + j * width + i ) = *( (int *)data + ( height - 1 - j ) * width + i ); + *( (int *)data + ( height - 1 - j ) * width + i ) = temp; + } + } +} + +void R_RotatePic( byte *data, int width ) { + int i, j; + int *temp; + + temp = (int *)R_StaticAlloc( width * width * 4, TAG_IMAGE ); + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < width ; j++ ) { + *( temp + i * width + j ) = *( (int *)data + j * width + i ); + } + } + + memcpy( data, temp, width * width * 4 ); + + R_StaticFree( temp ); +} + diff --git a/neo/renderer/Image_program.cpp b/neo/renderer/Image_program.cpp new file mode 100644 index 00000000..d8f843c3 --- /dev/null +++ b/neo/renderer/Image_program.cpp @@ -0,0 +1,659 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +/* + +all uncompressed +uncompressed normal maps + +downsample images + +16 meg Dynamic cache + +Anisotropic texturing + +Trilinear on all +Trilinear on normal maps, bilinear on others +Bilinear on all + + +Manager + +->List +->Print +->Reload( bool force ) + +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +// tr_imageprogram.c + +#include "tr_local.h" + +/* + +Anywhere that an image name is used (diffusemaps, bumpmaps, specularmaps, lights, etc), +an imageProgram can be specified. + +This allows load time operations, like heightmap-to-normalmap conversion and image +composition, to be automatically handled in a way that supports timestamped reloads. + +*/ + +/* +================= +R_HeightmapToNormalMap + +it is not possible to convert a heightmap into a normal map +properly without knowing the texture coordinate stretching. +We can assume constant and equal ST vectors for walls, but not for characters. +================= +*/ +static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale ) { + int i, j; + byte *depth; + + scale = scale / 256; + + // copy and convert to grey scale + j = width * height; + depth = (byte *)R_StaticAlloc( j, TAG_IMAGE ); + for ( i = 0 ; i < j ; i++ ) { + depth[i] = ( data[i*4] + data[i*4+1] + data[i*4+2] ) / 3; + } + + idVec3 dir, dir2; + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width ; j++ ) { + int d1, d2, d3, d4; + int a1, a2, a3, a4; + + // FIXME: look at five points? + + // look at three points to estimate the gradient + a1 = d1 = depth[ ( i * width + j ) ]; + a2 = d2 = depth[ ( i * width + ( ( j + 1 ) & ( width - 1 ) ) ) ]; + a3 = d3 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + j ) ]; + a4 = d4 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + ( ( j + 1 ) & ( width - 1 ) ) ) ]; + + d2 -= d1; + d3 -= d1; + + dir[0] = -d2 * scale; + dir[1] = -d3 * scale; + dir[2] = 1; + dir.NormalizeFast(); + + a1 -= a3; + a4 -= a3; + + dir2[0] = -a4 * scale; + dir2[1] = a1 * scale; + dir2[2] = 1; + dir2.NormalizeFast(); + + dir += dir2; + dir.NormalizeFast(); + + a1 = ( i * width + j ) * 4; + data[ a1 + 0 ] = (byte)(dir[0] * 127 + 128); + data[ a1 + 1 ] = (byte)(dir[1] * 127 + 128); + data[ a1 + 2 ] = (byte)(dir[2] * 127 + 128); + data[ a1 + 3 ] = 255; + } + } + + + R_StaticFree( depth ); +} + + +/* +================= +R_ImageScale +================= +*/ +static void R_ImageScale( byte *data, int width, int height, float scale[4] ) { + int i, j; + int c; + + c = width * height * 4; + + for ( i = 0 ; i < c ; i++ ) { + j = (byte)(data[i] * scale[i&3]); + if ( j < 0 ) { + j = 0; + } else if ( j > 255 ) { + j = 255; + } + data[i] = j; + } +} + +/* +================= +R_InvertAlpha +================= +*/ +static void R_InvertAlpha( byte *data, int width, int height ) { + int i; + int c; + + c = width * height* 4; + + for ( i = 0 ; i < c ; i+=4 ) { + data[i+3] = 255 - data[i+3]; + } +} + +/* +================= +R_InvertColor +================= +*/ +static void R_InvertColor( byte *data, int width, int height ) { + int i; + int c; + + c = width * height* 4; + + for ( i = 0 ; i < c ; i+=4 ) { + data[i+0] = 255 - data[i+0]; + data[i+1] = 255 - data[i+1]; + data[i+2] = 255 - data[i+2]; + } +} + + +/* +=================== +R_AddNormalMaps + +=================== +*/ +static void R_AddNormalMaps( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) { + int i, j; + byte *newMap; + + // resample pic2 to the same size as pic1 + if ( width2 != width1 || height2 != height1 ) { + newMap = R_Dropsample( data2, width2, height2, width1, height1 ); + data2 = newMap; + } else { + newMap = NULL; + } + + // add the normal change from the second and renormalize + for ( i = 0 ; i < height1 ; i++ ) { + for ( j = 0 ; j < width1 ; j++ ) { + byte *d1, *d2; + idVec3 n; + float len; + + d1 = data1 + ( i * width1 + j ) * 4; + d2 = data2 + ( i * width1 + j ) * 4; + + n[0] = ( d1[0] - 128 ) / 127.0; + n[1] = ( d1[1] - 128 ) / 127.0; + n[2] = ( d1[2] - 128 ) / 127.0; + + // There are some normal maps that blend to 0,0,0 at the edges + // this screws up compression, so we try to correct that here by instead fading it to 0,0,1 + len = n.LengthFast(); + if ( len < 1.0f ) { + n[2] = idMath::Sqrt(1.0 - (n[0]*n[0]) - (n[1]*n[1])); + } + + n[0] += ( d2[0] - 128 ) / 127.0; + n[1] += ( d2[1] - 128 ) / 127.0; + n.Normalize(); + + d1[0] = (byte)(n[0] * 127 + 128); + d1[1] = (byte)(n[1] * 127 + 128); + d1[2] = (byte)(n[2] * 127 + 128); + d1[3] = 255; + } + } + + if ( newMap ) { + R_StaticFree( newMap ); + } +} + +/* +================ +R_SmoothNormalMap +================ +*/ +static void R_SmoothNormalMap( byte *data, int width, int height ) { + byte *orig; + int i, j, k, l; + idVec3 normal; + byte *out; + static float factors[3][3] = { + { 1, 1, 1 }, + { 1, 1, 1 }, + { 1, 1, 1 } + }; + + orig = (byte *)R_StaticAlloc( width * height * 4, TAG_IMAGE ); + memcpy( orig, data, width * height * 4 ); + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + normal = vec3_origin; + for ( k = -1 ; k < 2 ; k++ ) { + for ( l = -1 ; l < 2 ; l++ ) { + byte *in; + + in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4; + + // ignore 000 and -1 -1 -1 + if ( in[0] == 0 && in[1] == 0 && in[2] == 0 ) { + continue; + } + if ( in[0] == 128 && in[1] == 128 && in[2] == 128 ) { + continue; + } + + normal[0] += factors[k+1][l+1] * ( in[0] - 128 ); + normal[1] += factors[k+1][l+1] * ( in[1] - 128 ); + normal[2] += factors[k+1][l+1] * ( in[2] - 128 ); + } + } + normal.Normalize(); + out = data + ( j * width + i ) * 4; + out[0] = (byte)(128 + 127 * normal[0]); + out[1] = (byte)(128 + 127 * normal[1]); + out[2] = (byte)(128 + 127 * normal[2]); + } + } + + R_StaticFree( orig ); +} + + +/* +=================== +R_ImageAdd + +=================== +*/ +static void R_ImageAdd( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) { + int i, j; + int c; + byte *newMap; + + // resample pic2 to the same size as pic1 + if ( width2 != width1 || height2 != height1 ) { + newMap = R_Dropsample( data2, width2, height2, width1, height1 ); + data2 = newMap; + } else { + newMap = NULL; + } + + + c = width1 * height1 * 4; + + for ( i = 0 ; i < c ; i++ ) { + j = data1[i] + data2[i]; + if ( j > 255 ) { + j = 255; + } + data1[i] = j; + } + + if ( newMap ) { + R_StaticFree( newMap ); + } +} + + +// we build a canonical token form of the image program here +static char parseBuffer[MAX_IMAGE_NAME]; + +/* +=================== +AppendToken +=================== +*/ +static void AppendToken( idToken &token ) { + // add a leading space if not at the beginning + if ( parseBuffer[0] ) { + idStr::Append( parseBuffer, MAX_IMAGE_NAME, " " ); + } + idStr::Append( parseBuffer, MAX_IMAGE_NAME, token.c_str() ); +} + +/* +=================== +MatchAndAppendToken +=================== +*/ +static void MatchAndAppendToken( idLexer &src, const char *match ) { + if ( !src.ExpectTokenString( match ) ) { + return; + } + // a matched token won't need a leading space + idStr::Append( parseBuffer, MAX_IMAGE_NAME, match ); +} + +/* +=================== +R_ParseImageProgram_r + +If pic is NULL, the timestamps will be filled in, but no image will be generated +If both pic and timestamps are NULL, it will just advance past it, which can be +used to parse an image program from a text stream. +=================== +*/ +static bool R_ParseImageProgram_r( idLexer &src, byte **pic, int *width, int *height, + ID_TIME_T *timestamps, textureUsage_t * usage ) { + idToken token; + float scale; + ID_TIME_T timestamp; + + src.ReadToken( &token ); + + // Since all interaction shaders now assume YCoCG diffuse textures. We replace all entries for the intrinsic + // _black texture to the black texture on disk. Doing this will cause a YCoCG compliant texture to be generated. + // Without a YCoCG compliant black texture we will get color artifacts for any interaction + // material that specifies the _black texture. + if ( token == "_black" ) { + token = "textures\\black"; + } + + // also check for _white + if ( token == "_white" ) { + token = "guis\\assets\\white"; + } + + AppendToken( token ); + + if ( !token.Icmp( "heightmap" ) ) { + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ) ) { + return false; + } + + MatchAndAppendToken( src, "," ); + + src.ReadToken( &token ); + AppendToken( token ); + scale = token.GetFloatValue(); + + // process it + if ( pic ) { + R_HeightmapToNormalMap( *pic, *width, *height, scale ); + if ( usage ) { + *usage = TD_BUMP; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "addnormals" ) ) { + byte *pic2 = NULL; + int width2, height2; + + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ) ) { + return false; + } + + MatchAndAppendToken( src, "," ); + + if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, usage ) ) { + if ( pic ) { + R_StaticFree( *pic ); + *pic = NULL; + } + return false; + } + + // process it + if ( pic ) { + R_AddNormalMaps( *pic, *width, *height, pic2, width2, height2 ); + R_StaticFree( pic2 ); + if ( usage ) { + *usage = TD_BUMP; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "smoothnormals" ) ) { + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ) ) { + return false; + } + + if ( pic ) { + R_SmoothNormalMap( *pic, *width, *height ); + if ( usage ) { + *usage = TD_BUMP; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "add" ) ) { + byte *pic2 = NULL; + int width2, height2; + + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ) ) { + return false; + } + + MatchAndAppendToken( src, "," ); + + if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, usage ) ) { + if ( pic ) { + R_StaticFree( *pic ); + *pic = NULL; + } + return false; + } + + // process it + if ( pic ) { + R_ImageAdd( *pic, *width, *height, pic2, width2, height2 ); + R_StaticFree( pic2 ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "scale" ) ) { + float scale[4]; + int i; + + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ); + + for ( i = 0 ; i < 4 ; i++ ) { + MatchAndAppendToken( src, "," ); + src.ReadToken( &token ); + AppendToken( token ); + scale[i] = token.GetFloatValue(); + } + + // process it + if ( pic ) { + R_ImageScale( *pic, *width, *height, scale ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "invertAlpha" ) ) { + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ); + + // process it + if ( pic ) { + R_InvertAlpha( *pic, *width, *height ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "invertColor" ) ) { + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ); + + // process it + if ( pic ) { + R_InvertColor( *pic, *width, *height ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "makeIntensity" ) ) { + int i; + + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ); + + // copy red to green, blue, and alpha + if ( pic ) { + int c; + c = *width * *height * 4; + for ( i = 0 ; i < c ; i+=4 ) { + (*pic)[i+1] = + (*pic)[i+2] = + (*pic)[i+3] = (*pic)[i]; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "makeAlpha" ) ) { + int i; + + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ); + + // average RGB into alpha, then set RGB to white + if ( pic ) { + int c; + c = *width * *height * 4; + for ( i = 0 ; i < c ; i+=4 ) { + (*pic)[i+3] = ( (*pic)[i+0] + (*pic)[i+1] + (*pic)[i+2] ) / 3; + (*pic)[i+0] = + (*pic)[i+1] = + (*pic)[i+2] = 255; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + // if we are just parsing instead of loading or checking, + // don't do the R_LoadImage + if ( !timestamps && !pic ) { + return true; + } + + // load it as an image + R_LoadImage( token.c_str(), pic, width, height, ×tamp, true ); + + if ( timestamp == -1 ) { + return false; + } + + // add this to the timestamp + if ( timestamps ) { + if ( timestamp > *timestamps ) { + *timestamps = timestamp; + } + } + + return true; +} + + +/* +=================== +R_LoadImageProgram +=================== +*/ +void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamps, textureUsage_t * usage ) { + idLexer src; + + src.LoadMemory( name, strlen(name), name ); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + + parseBuffer[0] = 0; + if ( timestamps ) { + *timestamps = 0; + } + + R_ParseImageProgram_r( src, pic, width, height, timestamps, usage ); + + src.FreeSource(); +} + +/* +=================== +R_ParsePastImageProgram +=================== +*/ +const char *R_ParsePastImageProgram( idLexer &src ) { + parseBuffer[0] = 0; + R_ParseImageProgram_r( src, NULL, NULL, NULL, NULL, NULL ); + return parseBuffer; +} + diff --git a/neo/renderer/Interaction.cpp b/neo/renderer/Interaction.cpp new file mode 100644 index 00000000..3ff9e2b6 --- /dev/null +++ b/neo/renderer/Interaction.cpp @@ -0,0 +1,846 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +=========================================================================== + +idInteraction implementation + +=========================================================================== +*/ + +/* +================ +R_CalcInteractionFacing + +Determines which triangles of the surface are facing towards the light origin. + +The facing array should be allocated with one extra index than +the number of surface triangles, which will be used to handle dangling +edge silhouettes. +================ +*/ +void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { + SCOPED_PROFILE_EVENT( "R_CalcInteractionFacing" ); + + if ( cullInfo.facing != NULL ) { + return; + } + + idVec3 localLightOrigin; + R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); + + const int numFaces = tri->numIndexes / 3; + cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ), TAG_RENDER_INTERACTION ); + + // exact geometric cull against face + for ( int i = 0, face = 0; i < tri->numIndexes; i += 3, face++ ) { + const idDrawVert & v0 = tri->verts[tri->indexes[i + 0]]; + const idDrawVert & v1 = tri->verts[tri->indexes[i + 1]]; + const idDrawVert & v2 = tri->verts[tri->indexes[i + 2]]; + + const idPlane plane( v0.xyz, v1.xyz, v2.xyz ); + const float d = plane.Distance( localLightOrigin ); + + cullInfo.facing[face] = ( d >= 0.0f ); + } + cullInfo.facing[numFaces] = 1; // for dangling edges to reference +} + +/* +===================== +R_CalcInteractionCullBits + +We want to cull a little on the sloppy side, because the pre-clipping +of geometry to the lights in dmap will give many cases that are right +at the border. We throw things out on the border, because if any one +vertex is clearly inside, the entire triangle will be accepted. +===================== +*/ +void R_CalcInteractionCullBits( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { + SCOPED_PROFILE_EVENT( "R_CalcInteractionCullBits" ); + + if ( cullInfo.cullBits != NULL ) { + return; + } + + idPlane frustumPlanes[6]; + idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); + + int frontBits = 0; + + // cull the triangle surface bounding box + for ( int i = 0; i < 6; i++ ) { + R_GlobalPlaneToLocal( ent->modelMatrix, frustumPlanes[i], cullInfo.localClipPlanes[i] ); + + // get front bits for the whole surface + if ( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) { + frontBits |= 1 << i; + } + } + + // if the surface is completely inside the light frustum + if ( frontBits == ( ( 1 << 6 ) - 1 ) ) { + cullInfo.cullBits = LIGHT_CULL_ALL_FRONT; + return; + } + + cullInfo.cullBits = (byte *) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ), TAG_RENDER_INTERACTION ); + memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) ); + + for ( int i = 0; i < 6; i++ ) { + // if completely infront of this clipping plane + if ( frontBits & ( 1 << i ) ) { + continue; + } + for ( int j = 0; j < tri->numVerts; j++ ) { + float d = cullInfo.localClipPlanes[i].Distance( tri->verts[j].xyz ); + cullInfo.cullBits[j] |= ( d < LIGHT_CLIP_EPSILON ) << i; + } + } +} + +/* +================ +R_FreeInteractionCullInfo +================ +*/ +void R_FreeInteractionCullInfo( srfCullInfo_t &cullInfo ) { + if ( cullInfo.facing != NULL ) { + R_StaticFree( cullInfo.facing ); + cullInfo.facing = NULL; + } + if ( cullInfo.cullBits != NULL ) { + if ( cullInfo.cullBits != LIGHT_CULL_ALL_FRONT ) { + R_StaticFree( cullInfo.cullBits ); + } + cullInfo.cullBits = NULL; + } +} + +/* +==================== +R_CreateInteractionLightTris + +This is only used for the static interaction case, dynamic interactions +just draw everything and let the GPU deal with it. + +The resulting surface will be a subset of the original triangles, +it will never clip triangles, but it may cull on a per-triangle basis. +==================== +*/ +static srfTriangles_t *R_CreateInteractionLightTris( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + const idMaterial *shader ) { + + SCOPED_PROFILE_EVENT( "R_CreateInteractionLightTris" ); + + int i; + int numIndexes; + triIndex_t *indexes; + srfTriangles_t *newTri; + int c_backfaced; + int c_distance; + idBounds bounds; + bool includeBackFaces; + int faceNum; + + c_backfaced = 0; + c_distance = 0; + + numIndexes = 0; + indexes = NULL; + + // it is debatable if non-shadowing lights should light back faces. we aren't at the moment + if ( r_lightAllBackFaces.GetBool() || light->lightShader->LightEffectsBackSides() + || shader->ReceivesLightingOnBackSides() || ent->parms.noSelfShadow || ent->parms.noShadow ) { + includeBackFaces = true; + } else { + includeBackFaces = false; + } + + // allocate a new surface for the lit triangles + newTri = R_AllocStaticTriSurf(); + + // save a reference to the original surface + newTri->ambientSurface = const_cast(tri); + + // the light surface references the verts of the ambient surface + newTri->numVerts = tri->numVerts; + R_ReferenceStaticTriSurfVerts( newTri, tri ); + + // calculate cull information + srfCullInfo_t cullInfo = {}; + + if ( !includeBackFaces ) { + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + } + R_CalcInteractionCullBits( ent, tri, light, cullInfo ); + + // if the surface is completely inside the light frustum + if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT ) { + + // if we aren't self shadowing, let back facing triangles get + // through so the smooth shaded bump maps light all the way around + if ( includeBackFaces ) { + + // the whole surface is lit so the light surface just references the indexes of the ambient surface + newTri->indexes = tri->indexes; + newTri->indexCache = tri->indexCache; +// R_ReferenceStaticTriSurfIndexes( newTri, tri ); + + numIndexes = tri->numIndexes; + bounds = tri->bounds; + + } else { + + // the light tris indexes are going to be a subset of the original indexes so we generally + // allocate too much memory here but we decrease the memory block when the number of indexes is known + R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); + + // back face cull the individual triangles + indexes = newTri->indexes; + const byte *facing = cullInfo.facing; + for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) { + if ( !facing[ faceNum ] ) { + c_backfaced++; + continue; + } + indexes[numIndexes+0] = tri->indexes[i+0]; + indexes[numIndexes+1] = tri->indexes[i+1]; + indexes[numIndexes+2] = tri->indexes[i+2]; + numIndexes += 3; + } + + // get bounds for the surface + SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes ); + + // decrease the size of the memory block to the size of the number of used indexes + newTri->numIndexes = numIndexes; + R_ResizeStaticTriSurfIndexes( newTri, numIndexes ); + } + + } else { + + // the light tris indexes are going to be a subset of the original indexes so we generally + // allocate too much memory here but we decrease the memory block when the number of indexes is known + R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); + + // cull individual triangles + indexes = newTri->indexes; + const byte *facing = cullInfo.facing; + const byte *cullBits = cullInfo.cullBits; + for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) { + int i1, i2, i3; + + // if we aren't self shadowing, let back facing triangles get + // through so the smooth shaded bump maps light all the way around + if ( !includeBackFaces ) { + // back face cull + if ( !facing[ faceNum ] ) { + c_backfaced++; + continue; + } + } + + i1 = tri->indexes[i+0]; + i2 = tri->indexes[i+1]; + i3 = tri->indexes[i+2]; + + // fast cull outside the frustum + // if all three points are off one plane side, it definately isn't visible + if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) { + c_distance++; + continue; + } + + // add to the list + indexes[numIndexes+0] = i1; + indexes[numIndexes+1] = i2; + indexes[numIndexes+2] = i3; + numIndexes += 3; + } + + // get bounds for the surface + SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes ); + + // decrease the size of the memory block to the size of the number of used indexes + newTri->numIndexes = numIndexes; + R_ResizeStaticTriSurfIndexes( newTri, numIndexes ); + } + + // free the cull information when it's no longer needed + R_FreeInteractionCullInfo( cullInfo ); + + if ( !numIndexes ) { + R_FreeStaticTriSurf( newTri ); + return NULL; + } + + newTri->numIndexes = numIndexes; + + newTri->bounds = bounds; + + return newTri; +} + +/* +===================== +R_CreateInteractionShadowVolume + +Note that dangling edges outside the light frustum don't make silhouette planes because +a triangle outside the light frustum is considered facing and the "fake triangle" on +the outside of the dangling edge is also set to facing: cullInfo.facing[numFaces] = 1; +===================== +*/ +static srfTriangles_t *R_CreateInteractionShadowVolume( const idRenderEntityLocal * ent, + const srfTriangles_t * tri, const idRenderLightLocal * light ) { + SCOPED_PROFILE_EVENT( "R_CreateInteractionShadowVolume" ); + + srfCullInfo_t cullInfo = {}; + + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + R_CalcInteractionCullBits( ent, tri, light, cullInfo ); + + int numFaces = tri->numIndexes / 3; + int numShadowingFaces = 0; + const byte * facing = cullInfo.facing; + + // if all the triangles are inside the light frustum + if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT ) { + + // count the number of shadowing faces + for ( int i = 0; i < numFaces; i++ ) { + numShadowingFaces += facing[i]; + } + numShadowingFaces = numFaces - numShadowingFaces; + + } else { + + // make all triangles that are outside the light frustum "facing", so they won't cast shadows + const triIndex_t * indexes = tri->indexes; + byte *modifyFacing = cullInfo.facing; + const byte *cullBits = cullInfo.cullBits; + for ( int i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { + if ( !modifyFacing[j] ) { + int i1 = indexes[i+0]; + int i2 = indexes[i+1]; + int i3 = indexes[i+2]; + if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) { + modifyFacing[j] = 1; + } else { + numShadowingFaces++; + } + } + } + } + + if ( !numShadowingFaces ) { + // no faces are inside the light frustum and still facing the right way + R_FreeInteractionCullInfo( cullInfo ); + return NULL; + } + + // shadowVerts will be NULL on these surfaces, so the shadowVerts will be taken from the ambient surface + srfTriangles_t * newTri = R_AllocStaticTriSurf(); + + newTri->numVerts = tri->numVerts * 2; + + // alloc the max possible size + R_AllocStaticTriSurfIndexes( newTri, ( numShadowingFaces + tri->numSilEdges ) * 6 ); + triIndex_t * tempIndexes = newTri->indexes; + triIndex_t * shadowIndexes = newTri->indexes; + + // create new triangles along sil planes + const silEdge_t * sil = tri->silEdges; + for ( int i = tri->numSilEdges; i > 0; i--, sil++ ) { + + int f1 = facing[sil->p1]; + int f2 = facing[sil->p2]; + + if ( !( f1 ^ f2 ) ) { + continue; + } + + int v1 = sil->v1 << 1; + int v2 = sil->v2 << 1; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch + + shadowIndexes[0] = v1; + shadowIndexes[1] = v2 ^ f1; + shadowIndexes[2] = v2 ^ f2; + shadowIndexes[3] = v1 ^ f2; + shadowIndexes[4] = v1 ^ f1; + shadowIndexes[5] = v2 ^ 1; + + shadowIndexes += 6; + } + + int numShadowIndexes = shadowIndexes - tempIndexes; + + // we aren't bothering to separate front and back caps on these + newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps = numShadowIndexes + numShadowingFaces * 6; + newTri->numShadowIndexesNoCaps = numShadowIndexes; + newTri->shadowCapPlaneBits = SHADOW_CAP_INFINITE; + + // decrease the size of the memory block to only store the used indexes + // R_ResizeStaticTriSurfIndexes( newTri, newTri->numIndexes ); + + // these have no effect, because they extend to infinity + newTri->bounds.Clear(); + + // put some faces on the model and some on the distant projection + const triIndex_t * indexes = tri->indexes; + shadowIndexes = newTri->indexes + numShadowIndexes; + for ( int i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + int i0 = indexes[i+0] << 1; + int i1 = indexes[i+1] << 1; + int i2 = indexes[i+2] << 1; + + shadowIndexes[0] = i2; + shadowIndexes[1] = i1; + shadowIndexes[2] = i0; + shadowIndexes[3] = i0 ^ 1; + shadowIndexes[4] = i1 ^ 1; + shadowIndexes[5] = i2 ^ 1; + + shadowIndexes += 6; + } + + R_FreeInteractionCullInfo( cullInfo ); + + return newTri; +} + +/* +=============== +idInteraction::idInteraction +=============== +*/ +idInteraction::idInteraction() { + numSurfaces = 0; + surfaces = NULL; + entityDef = NULL; + lightDef = NULL; + lightNext = NULL; + lightPrev = NULL; + entityNext = NULL; + entityPrev = NULL; + staticInteraction = false; +} + +/* +=============== +idInteraction::AllocAndLink +=============== +*/ +idInteraction *idInteraction::AllocAndLink( idRenderEntityLocal *edef, idRenderLightLocal *ldef ) { + if ( edef == NULL || ldef == NULL ) { + common->Error( "idInteraction::AllocAndLink: NULL parm" ); + return NULL; + } + + idRenderWorldLocal *renderWorld = edef->world; + + idInteraction *interaction = renderWorld->interactionAllocator.Alloc(); + + // link and initialize + interaction->lightDef = ldef; + interaction->entityDef = edef; + + interaction->numSurfaces = -1; // not checked yet + interaction->surfaces = NULL; + + // link at the start of the entity's list + interaction->lightNext = ldef->firstInteraction; + interaction->lightPrev = NULL; + ldef->firstInteraction = interaction; + if ( interaction->lightNext != NULL ) { + interaction->lightNext->lightPrev = interaction; + } else { + ldef->lastInteraction = interaction; + } + + // link at the start of the light's list + interaction->entityNext = edef->firstInteraction; + interaction->entityPrev = NULL; + edef->firstInteraction = interaction; + if ( interaction->entityNext != NULL ) { + interaction->entityNext->entityPrev = interaction; + } else { + edef->lastInteraction = interaction; + } + + // update the interaction table + if ( renderWorld->interactionTable != NULL ) { + int index = ldef->index * renderWorld->interactionTableWidth + edef->index; + if ( renderWorld->interactionTable[index] != NULL ) { + common->Error( "idInteraction::AllocAndLink: non NULL table entry" ); + } + renderWorld->interactionTable[ index ] = interaction; + } + + return interaction; +} + +/* +=============== +idInteraction::FreeSurfaces + +Frees the surfaces, but leaves the interaction linked in, so it +will be regenerated automatically +=============== +*/ +void idInteraction::FreeSurfaces() { + // anything regenerated is no longer an optimized static version + this->staticInteraction = false; + + if ( this->surfaces != NULL ) { + for ( int i = 0; i < this->numSurfaces; i++ ) { + surfaceInteraction_t &srf = this->surfaces[i]; + Mem_Free( srf.shadowIndexes ); + srf.shadowIndexes = NULL; + } + R_StaticFree( this->surfaces ); + this->surfaces = NULL; + } + this->numSurfaces = -1; +} + +/* +=============== +idInteraction::Unlink +=============== +*/ +void idInteraction::Unlink() { + + // unlink from the entity's list + if ( this->entityPrev ) { + this->entityPrev->entityNext = this->entityNext; + } else { + this->entityDef->firstInteraction = this->entityNext; + } + if ( this->entityNext ) { + this->entityNext->entityPrev = this->entityPrev; + } else { + this->entityDef->lastInteraction = this->entityPrev; + } + this->entityNext = this->entityPrev = NULL; + + // unlink from the light's list + if ( this->lightPrev ) { + this->lightPrev->lightNext = this->lightNext; + } else { + this->lightDef->firstInteraction = this->lightNext; + } + if ( this->lightNext ) { + this->lightNext->lightPrev = this->lightPrev; + } else { + this->lightDef->lastInteraction = this->lightPrev; + } + this->lightNext = this->lightPrev = NULL; +} + +/* +=============== +idInteraction::UnlinkAndFree + +Removes links and puts it back on the free list. +=============== +*/ +void idInteraction::UnlinkAndFree() { + // clear the table pointer + idRenderWorldLocal *renderWorld = this->lightDef->world; + int index = this->lightDef->index * renderWorld->interactionTableWidth + this->entityDef->index; + if ( renderWorld->interactionTable[index] != this && renderWorld->interactionTable[index] != INTERACTION_EMPTY ) { + common->Error( "idInteraction::UnlinkAndFree: interactionTable wasn't set" ); + } + renderWorld->interactionTable[index] = NULL; + + Unlink(); + + FreeSurfaces(); + + // put it back on the free list + renderWorld->interactionAllocator.Free( this ); +} + +/* +=============== +idInteraction::MakeEmpty + +Relinks the interaction at the end of both the light and entity chains +and adds the INTERACTION_EMPTY marker to the interactionTable. + +It is necessary to keep the empty interaction so when entities or lights move +they can set all the interactionTable values to NULL. +=============== +*/ +void idInteraction::MakeEmpty() { + // an empty interaction has no surfaces + numSurfaces = 0; + + Unlink(); + + // relink at the end of the entity's list + this->entityNext = NULL; + this->entityPrev = this->entityDef->lastInteraction; + this->entityDef->lastInteraction = this; + if ( this->entityPrev ) { + this->entityPrev->entityNext = this; + } else { + this->entityDef->firstInteraction = this; + } + + // relink at the end of the light's list + this->lightNext = NULL; + this->lightPrev = this->lightDef->lastInteraction; + this->lightDef->lastInteraction = this; + if ( this->lightPrev ) { + this->lightPrev->lightNext = this; + } else { + this->lightDef->firstInteraction = this; + } + + // store the special marker in the interaction table + const int interactionIndex = lightDef->index * entityDef->world->interactionTableWidth + entityDef->index; + assert( entityDef->world->interactionTable[ interactionIndex ] == this ); + entityDef->world->interactionTable[ interactionIndex ] = INTERACTION_EMPTY; +} + +/* +=============== +idInteraction::HasShadows +=============== +*/ +bool idInteraction::HasShadows() const { + return !entityDef->parms.noShadow && lightDef->LightCastsShadows(); +} + +/* +====================== +CreateStaticInteraction + +Called by idRenderWorldLocal::GenerateAllInteractions +====================== +*/ +void idInteraction::CreateStaticInteraction() { + // note that it is a static interaction + staticInteraction = true; + const idRenderModel *model = entityDef->parms.hModel; + if ( model == NULL || model->NumSurfaces() <= 0 || model->IsDynamicModel() != DM_STATIC ) { + MakeEmpty(); + return; + } + + const idBounds bounds = model->Bounds( &entityDef->parms ); + + // if it doesn't contact the light frustum, none of the surfaces will + if ( R_CullModelBoundsToLight( lightDef, bounds, entityDef->modelRenderMatrix ) ) { + MakeEmpty(); + return; + } + + // + // create slots for each of the model's surfaces + // + numSurfaces = model->NumSurfaces(); + surfaces = (surfaceInteraction_t *)R_ClearedStaticAlloc( sizeof( *surfaces ) * numSurfaces ); + + bool interactionGenerated = false; + + // check each surface in the model + for ( int c = 0 ; c < model->NumSurfaces() ; c++ ) { + const modelSurface_t * surf = model->Surface( c ); + const srfTriangles_t * tri = surf->geometry; + if ( tri == NULL ) { + continue; + } + + // determine the shader for this surface, possibly by skinning + // Note that this will be wrong if customSkin/customShader are + // changed after map load time without invalidating the interaction! + const idMaterial * const shader = R_RemapShaderBySkin( surf->shader, + entityDef->parms.customSkin, entityDef->parms.customShader ); + if ( shader == NULL ) { + continue; + } + + // try to cull each surface + if ( R_CullModelBoundsToLight( lightDef, tri->bounds, entityDef->modelRenderMatrix ) ) { + continue; + } + + surfaceInteraction_t *sint = &surfaces[c]; + + // generate a set of indexes for the lit surfaces, culling away triangles that are + // not at least partially inside the light + if ( shader->ReceivesLighting() ) { + srfTriangles_t * lightTris = R_CreateInteractionLightTris( entityDef, tri, lightDef, shader ); + if ( lightTris != NULL ) { + // make a static index cache + sint->numLightTrisIndexes = lightTris->numIndexes; + sint->lightTrisIndexCache = vertexCache.AllocStaticIndex( lightTris->indexes, ALIGN( lightTris->numIndexes * sizeof( lightTris->indexes[0] ), INDEX_CACHE_ALIGN ) ); + + interactionGenerated = true; + R_FreeStaticTriSurf( lightTris ); + } + } + + // if the interaction has shadows and this surface casts a shadow + if ( HasShadows() && shader->SurfaceCastsShadow() && tri->silEdges != NULL ) { + + // if the light has an optimized shadow volume, don't create shadows for any models that are part of the base areas + if ( lightDef->parms.prelightModel == NULL || !model->IsStaticWorldModel() || r_skipPrelightShadows.GetBool() ) { + srfTriangles_t * shadowTris = R_CreateInteractionShadowVolume( entityDef, tri, lightDef ); + if ( shadowTris != NULL ) { + // make a static index cache + sint->shadowIndexCache = vertexCache.AllocStaticIndex( shadowTris->indexes, ALIGN( shadowTris->numIndexes * sizeof( shadowTris->indexes[0] ), INDEX_CACHE_ALIGN ) ); + sint->numShadowIndexes = shadowTris->numIndexes; +#if defined( KEEP_INTERACTION_CPU_DATA ) + sint->shadowIndexes = shadowTris->indexes; + shadowTris->indexes = NULL; +#endif + if ( shader->Coverage() != MC_OPAQUE ) { + // if any surface is a shadow-casting perforated or translucent surface, or the + // base surface is suppressed in the view (world weapon shadows) we can't use + // the external shadow optimizations because we can see through some of the faces + sint->numShadowIndexesNoCaps = shadowTris->numIndexes; + } else { + sint->numShadowIndexesNoCaps = shadowTris->numShadowIndexesNoCaps; + } + R_FreeStaticTriSurf( shadowTris ); + } + interactionGenerated = true; + } + } + } + + // if none of the surfaces generated anything, don't even bother checking? + if ( !interactionGenerated ) { + MakeEmpty(); + } +} + +/* +=================== +R_ShowInteractionMemory_f +=================== +*/ +void R_ShowInteractionMemory_f( const idCmdArgs &args ) { + int entities = 0; + int interactions = 0; + int deferredInteractions = 0; + int emptyInteractions = 0; + int lightTris = 0; + int lightTriIndexes = 0; + int shadowTris = 0; + int shadowTriIndexes = 0; + int maxInteractionsForEntity = 0; + int maxInteractionsForLight = 0; + + for ( int i = 0; i < tr.primaryWorld->lightDefs.Num(); i++ ) { + idRenderLightLocal * light = tr.primaryWorld->lightDefs[i]; + if ( light == NULL ) { + continue; + } + int numInteractionsForLight = 0; + for ( idInteraction *inter = light->firstInteraction; inter != NULL; inter = inter->lightNext ) { + if ( !inter->IsEmpty() ) { + numInteractionsForLight++; + } + } + if ( numInteractionsForLight > maxInteractionsForLight ) { + maxInteractionsForLight = numInteractionsForLight; + } + } + + for ( int i = 0; i < tr.primaryWorld->entityDefs.Num(); i++ ) { + idRenderEntityLocal *def = tr.primaryWorld->entityDefs[i]; + if ( def == NULL ) { + continue; + } + if ( def->firstInteraction == NULL ) { + continue; + } + entities++; + + int numInteractionsForEntity = 0; + for ( idInteraction *inter = def->firstInteraction; inter != NULL; inter = inter->entityNext ) { + interactions++; + + if ( !inter->IsEmpty() ) { + numInteractionsForEntity++; + } + + if ( inter->IsDeferred() ) { + deferredInteractions++; + continue; + } + if ( inter->IsEmpty() ) { + emptyInteractions++; + continue; + } + + for ( int j = 0; j < inter->numSurfaces; j++ ) { + surfaceInteraction_t *srf = &inter->surfaces[j]; + + if ( srf->numLightTrisIndexes ) { + lightTris++; + lightTriIndexes += srf->numLightTrisIndexes; + } + + if ( srf->numShadowIndexes ) { + shadowTris++; + shadowTriIndexes += srf->numShadowIndexes; + } + } + } + if ( numInteractionsForEntity > maxInteractionsForEntity ) { + maxInteractionsForEntity = numInteractionsForEntity; + } + } + + common->Printf( "%i entities with %i total interactions\n", entities, interactions ); + common->Printf( "%i deferred interactions, %i empty interactions\n", deferredInteractions, emptyInteractions ); + common->Printf( "%5i indexes in %5i light tris\n", lightTriIndexes, lightTris ); + common->Printf( "%5i indexes in %5i shadow tris\n", shadowTriIndexes, shadowTris ); + common->Printf( "%i maxInteractionsForEntity\n", maxInteractionsForEntity ); + common->Printf( "%i maxInteractionsForLight\n", maxInteractionsForLight ); +} diff --git a/neo/renderer/Interaction.h b/neo/renderer/Interaction.h new file mode 100644 index 00000000..9f64b88f --- /dev/null +++ b/neo/renderer/Interaction.h @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __INTERACTION_H__ +#define __INTERACTION_H__ + +/* +=============================================================================== + + Interaction between static entityDef surfaces and a static lightDef. + + Interactions with no lightTris and no shadowTris are still + valid, because they show that a given entityDef / lightDef + do not interact, even though they share one or more areas. + +=============================================================================== +*/ + +#define LIGHT_CULL_ALL_FRONT ((byte *)-1) +#define LIGHT_CLIP_EPSILON 0.1f + +// enabling this define allows the precise inside shadow volume test +// to be performed on interaction (static) shadow volumes +#define KEEP_INTERACTION_CPU_DATA + +struct srfCullInfo_t { + // For each triangle a byte set to 1 if facing the light origin. + byte * facing; + + // For each vertex a byte with the bits [0-5] set if the + // vertex is at the back side of the corresponding clip plane. + // If the 'cullBits' pointer equals LIGHT_CULL_ALL_FRONT all + // vertices are at the front of all the clip planes. + byte * cullBits; + + // Clip planes in surface space used to calculate the cull bits. + idPlane localClipPlanes[6]; +}; + + +// Pre-generated shadow volumes from dmap are not present in surfaceInteraction_t, +// they are added separately. +struct surfaceInteraction_t { + // The vertexes for light tris will always come from ambient triangles. + // For interactions created at load time, the indexes will be uniquely + // generated in static vertex memory. + int numLightTrisIndexes; + vertCacheHandle_t lightTrisIndexCache; + + // shadow volume triangle surface + int numShadowIndexes; + int numShadowIndexesNoCaps; // if the view is outside the shadow, this can be used + triIndex_t * shadowIndexes; // only != NULL if KEEP_INTERACTION_CPU_DATA is defined + vertCacheHandle_t shadowIndexCache; +}; + + +class idRenderEntityLocal; +class idRenderLightLocal; + +class idInteraction { +public: + // this may be 0 if the light and entity do not actually intersect + // -1 = an untested interaction + int numSurfaces; + + // if there is a whole-entity optimized shadow hull, it will + // be present as a surfaceInteraction_t with a NULL ambientTris, but + // possibly having a shader to specify the shadow sorting order + // (FIXME: actually try making shadow hulls? we never did.) + surfaceInteraction_t * surfaces; + + // get space from here, if NULL, it is a pre-generated shadow volume from dmap + idRenderEntityLocal * entityDef; + idRenderLightLocal * lightDef; + + idInteraction * lightNext; // for lightDef chains + idInteraction * lightPrev; + idInteraction * entityNext; // for entityDef chains + idInteraction * entityPrev; + + bool staticInteraction; // true if the interaction was created at map load time in static buffer space + +public: + idInteraction(); + + // because these are generated and freed each game tic for active elements all + // over the world, we use a custom pool allocater to avoid memory allocation overhead + // and fragmentation + static idInteraction * AllocAndLink( idRenderEntityLocal *edef, idRenderLightLocal *ldef ); + + // unlinks from the entity and light, frees all surfaceInteractions, + // and puts it back on the free list + void UnlinkAndFree(); + + // free the interaction surfaces + void FreeSurfaces(); + + // makes the interaction empty for when the light and entity do not actually intersect + // all empty interactions are linked at the end of the light's and entity's interaction list + void MakeEmpty(); + + // returns true if the interaction is empty + bool IsEmpty() const { return ( numSurfaces == 0 ); } + + // returns true if the interaction is not yet completely created + bool IsDeferred() const { return ( numSurfaces == -1 ); } + + // returns true if the interaction has shadows + bool HasShadows() const; + + // called by GenerateAllInteractions + void CreateStaticInteraction(); + +private: + // unlink from entity and light lists + void Unlink(); +}; + +void R_ShowInteractionMemory_f( const idCmdArgs &args ); + +#endif /* !__INTERACTION_H__ */ diff --git a/neo/renderer/Material.cpp b/neo/renderer/Material.cpp new file mode 100644 index 00000000..6a5d2a73 --- /dev/null +++ b/neo/renderer/Material.cpp @@ -0,0 +1,2888 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" + +/* + +Any errors during parsing just set MF_DEFAULTED and return, rather than throwing +a hard error. This will cause the material to fall back to default material, +but otherwise let things continue. + +Each material may have a set of calculations that must be evaluated before +drawing with it. + +Every expression that a material uses can be evaluated at one time, which +will allow for perfect common subexpression removal when I get around to +writing it. + +Without this, scrolling an entire surface could result in evaluating the +same texture matrix calculations a half dozen times. + + Open question: should I allow arbitrary per-vertex color, texCoord, and vertex + calculations to be specified in the material code? + + Every stage will definately have a valid image pointer. + + We might want the ability to change the sort value based on conditionals, + but it could be a hassle to implement, + +*/ + +// keep all of these on the stack, when they are static it makes material parsing non-reentrant +typedef struct mtrParsingData_s { + bool registerIsTemporary[MAX_EXPRESSION_REGISTERS]; + float shaderRegisters[MAX_EXPRESSION_REGISTERS]; + expOp_t shaderOps[MAX_EXPRESSION_OPS]; + shaderStage_t parseStages[MAX_SHADER_STAGES]; + + bool registersAreConstant; + bool forceOverlays; +} mtrParsingData_t; + +idCVar r_forceSoundOpAmplitude( "r_forceSoundOpAmplitude", "0", CVAR_FLOAT, "Don't call into the sound system for amplitudes" ); + +/* +============= +idMaterial::CommonInit +============= +*/ +void idMaterial::CommonInit() { + desc = ""; + renderBump = ""; + contentFlags = CONTENTS_SOLID; + surfaceFlags = SURFTYPE_NONE; + materialFlags = 0; + sort = SS_BAD; + stereoEye = 0; + coverage = MC_BAD; + cullType = CT_FRONT_SIDED; + deform = DFRM_NONE; + numOps = 0; + ops = NULL; + numRegisters = 0; + expressionRegisters = NULL; + constantRegisters = NULL; + numStages = 0; + numAmbientStages = 0; + stages = NULL; + editorImage = NULL; + lightFalloffImage = NULL; + shouldCreateBackSides = false; + entityGui = 0; + fogLight = false; + blendLight = false; + ambientLight = false; + noFog = false; + hasSubview = false; + allowOverlays = true; + unsmoothedTangents = false; + gui = NULL; + memset( deformRegisters, 0, sizeof( deformRegisters ) ); + editorAlpha = 1.0; + spectrum = 0; + polygonOffset = 0; + suppressInSubview = false; + refCount = 0; + portalSky = false; + fastPathBumpImage = NULL; + fastPathDiffuseImage = NULL; + fastPathSpecularImage = NULL; + deformDecl = NULL; + + decalInfo.stayTime = 10000; + decalInfo.fadeTime = 4000; + decalInfo.start[0] = 1; + decalInfo.start[1] = 1; + decalInfo.start[2] = 1; + decalInfo.start[3] = 1; + decalInfo.end[0] = 0; + decalInfo.end[1] = 0; + decalInfo.end[2] = 0; + decalInfo.end[3] = 0; +} + + +/* +============= +idMaterial::idMaterial +============= +*/ +idMaterial::idMaterial() { + CommonInit(); + + // we put this here instead of in CommonInit, because + // we don't want it cleared when a material is purged + surfaceArea = 0; +} + +/* +============= +idMaterial::~idMaterial +============= +*/ +idMaterial::~idMaterial() { +} + +/* +=============== +idMaterial::FreeData +=============== +*/ +void idMaterial::FreeData() { + int i; + + if ( stages ) { + // delete any idCinematic textures + for ( i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic != NULL ) { + delete stages[i].texture.cinematic; + stages[i].texture.cinematic = NULL; + } + if ( stages[i].newStage != NULL ) { + Mem_Free( stages[i].newStage ); + stages[i].newStage = NULL; + } + } + R_StaticFree( stages ); + stages = NULL; + } + if ( expressionRegisters != NULL ) { + R_StaticFree( expressionRegisters ); + expressionRegisters = NULL; + } + if ( constantRegisters != NULL ) { + R_StaticFree( constantRegisters ); + constantRegisters = NULL; + } + if ( ops != NULL ) { + R_StaticFree( ops ); + ops = NULL; + } +} + +/* +============== +idMaterial::GetEditorImage +============== +*/ +idImage *idMaterial::GetEditorImage() const { + if ( editorImage ) { + return editorImage; + } + + // if we don't have an editorImageName, use the first stage image + if ( !editorImageName.Length()) { + // _D3XP :: First check for a diffuse image, then use the first + if ( numStages && stages ) { + int i; + for( i = 0; i < numStages; i++ ) { + if ( stages[i].lighting == SL_DIFFUSE ) { + editorImage = stages[i].texture.image; + break; + } + } + if ( !editorImage ) { + editorImage = stages[0].texture.image; + } + } else { + editorImage = globalImages->defaultImage; + } + } else { + // look for an explicit one + editorImage = globalImages->ImageFromFile( editorImageName, TF_DEFAULT, TR_REPEAT, TD_DEFAULT ); + } + + if ( !editorImage ) { + editorImage = globalImages->defaultImage; + } + + return editorImage; +} + + +// info parms +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +static infoParm_t infoParms[] = { + // game relevant attributes + {"solid", 0, 0, CONTENTS_SOLID }, // may need to override a clearSolid + {"water", 1, 0, CONTENTS_WATER }, // used for water + {"playerclip", 0, 0, CONTENTS_PLAYERCLIP }, // solid to players + {"monsterclip", 0, 0, CONTENTS_MONSTERCLIP }, // solid to monsters + {"moveableclip",0, 0, CONTENTS_MOVEABLECLIP },// solid to moveable entities + {"ikclip", 0, 0, CONTENTS_IKCLIP }, // solid to IK + {"blood", 0, 0, CONTENTS_BLOOD }, // used to detect blood decals + {"trigger", 0, 0, CONTENTS_TRIGGER }, // used for triggers + {"aassolid", 0, 0, CONTENTS_AAS_SOLID }, // solid for AAS + {"aasobstacle", 0, 0, CONTENTS_AAS_OBSTACLE },// used to compile an obstacle into AAS that can be enabled/disabled + {"flashlight_trigger", 0, 0, CONTENTS_FLASHLIGHT_TRIGGER }, // used for triggers that are activated by the flashlight + {"nonsolid", 1, 0, 0 }, // clears the solid flag + {"nullNormal", 0, SURF_NULLNORMAL,0 }, // renderbump will draw as 0x80 0x80 0x80 + + // utility relevant attributes + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"qer_nocarve", 1, 0, CONTENTS_NOCSG}, // don't cut brushes in editor + + {"discrete", 1, SURF_DISCRETE, 0 }, // surfaces should not be automatically merged together or + // clipped to the world, + // because they represent discrete objects like gui shaders + // mirrors, or autosprites + {"noFragment", 0, SURF_NOFRAGMENT, 0 }, + + {"slick", 0, SURF_SLICK, 0 }, + {"collision", 0, SURF_COLLISION, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nodamage", 0, SURF_NODAMAGE, 0 }, // no falling damage when hitting + {"ladder", 0, SURF_LADDER, 0 }, // climbable + {"nosteps", 0, SURF_NOSTEPS, 0 }, // no footsteps + + // material types for particle, sound, footstep feedback + {"metal", 0, SURFTYPE_METAL, 0 }, // metal + {"stone", 0, SURFTYPE_STONE, 0 }, // stone + {"flesh", 0, SURFTYPE_FLESH, 0 }, // flesh + {"wood", 0, SURFTYPE_WOOD, 0 }, // wood + {"cardboard", 0, SURFTYPE_CARDBOARD, 0 }, // cardboard + {"liquid", 0, SURFTYPE_LIQUID, 0 }, // liquid + {"glass", 0, SURFTYPE_GLASS, 0 }, // glass + {"plastic", 0, SURFTYPE_PLASTIC, 0 }, // plastic + {"ricochet", 0, SURFTYPE_RICOCHET, 0 }, // behaves like metal but causes a ricochet sound + + // unassigned surface types + {"surftype10", 0, SURFTYPE_10, 0 }, + {"surftype11", 0, SURFTYPE_11, 0 }, + {"surftype12", 0, SURFTYPE_12, 0 }, + {"surftype13", 0, SURFTYPE_13, 0 }, + {"surftype14", 0, SURFTYPE_14, 0 }, + {"surftype15", 0, SURFTYPE_15, 0 }, +}; + +static const int numInfoParms = sizeof(infoParms) / sizeof (infoParms[0]); + + +/* +=============== +idMaterial::CheckSurfaceParm + +See if the current token matches one of the surface parm bit flags +=============== +*/ +bool idMaterial::CheckSurfaceParm( idToken *token ) { + + for ( int i = 0 ; i < numInfoParms ; i++ ) { + if ( !token->Icmp( infoParms[i].name ) ) { + if ( infoParms[i].surfaceFlags & SURF_TYPE_MASK ) { + // ensure we only have one surface type set + surfaceFlags &= ~SURF_TYPE_MASK; + } + surfaceFlags |= infoParms[i].surfaceFlags; + contentFlags |= infoParms[i].contents; + if ( infoParms[i].clearSolid ) { + contentFlags &= ~CONTENTS_SOLID; + } + return true; + } + } + return false; +} + +/* +=============== +idMaterial::MatchToken + +Sets defaultShader and returns false if the next token doesn't match +=============== +*/ +bool idMaterial::MatchToken( idLexer &src, const char *match ) { + if ( !src.ExpectTokenString( match ) ) { + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + return true; +} + +/* +================= +idMaterial::ParseSort +================= +*/ +void idMaterial::ParseSort( idLexer &src ) { + idToken token; + + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "missing sort parameter" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + if ( !token.Icmp( "subview" ) ) { + sort = SS_SUBVIEW; + } else if ( !token.Icmp( "opaque" ) ) { + sort = SS_OPAQUE; + }else if ( !token.Icmp( "decal" ) ) { + sort = SS_DECAL; + } else if ( !token.Icmp( "far" ) ) { + sort = SS_FAR; + } else if ( !token.Icmp( "medium" ) ) { + sort = SS_MEDIUM; + } else if ( !token.Icmp( "close" ) ) { + sort = SS_CLOSE; + } else if ( !token.Icmp( "almostNearest" ) ) { + sort = SS_ALMOST_NEAREST; + } else if ( !token.Icmp( "nearest" ) ) { + sort = SS_NEAREST; + } else if ( !token.Icmp( "postProcess" ) ) { + sort = SS_POST_PROCESS; + } else if ( !token.Icmp( "portalSky" ) ) { + sort = SS_PORTAL_SKY; + } else { + sort = atof( token ); + } +} + +/* +================= +idMaterial::ParseStereoEye +================= +*/ +void idMaterial::ParseStereoEye( idLexer &src ) { + idToken token; + + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "missing eye parameter" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + if ( !token.Icmp( "left" ) ) { + stereoEye = -1; + } else if ( !token.Icmp( "right" ) ) { + stereoEye = 1; + } else { + stereoEye = 0; + } +} + +/* +================= +idMaterial::ParseDecalInfo +================= +*/ +void idMaterial::ParseDecalInfo( idLexer &src ) { + idToken token; + + decalInfo.stayTime = src.ParseFloat() * 1000; + decalInfo.fadeTime = src.ParseFloat() * 1000; + float start[4], end[4]; + src.Parse1DMatrix( 4, start ); + src.Parse1DMatrix( 4, end ); + for ( int i = 0 ; i < 4 ; i++ ) { + decalInfo.start[i] = start[i]; + decalInfo.end[i] = end[i]; + } +} + +/* +============= +idMaterial::GetExpressionConstant +============= +*/ +int idMaterial::GetExpressionConstant( float f ) { + int i; + + for ( i = EXP_REG_NUM_PREDEFINED ; i < numRegisters ; i++ ) { + if ( !pd->registerIsTemporary[i] && pd->shaderRegisters[i] == f ) { + return i; + } + } + if ( numRegisters == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "GetExpressionConstant: material '%s' hit MAX_EXPRESSION_REGISTERS", GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + pd->registerIsTemporary[i] = false; + pd->shaderRegisters[i] = f; + numRegisters++; + + return i; +} + +/* +============= +idMaterial::GetExpressionTemporary +============= +*/ +int idMaterial::GetExpressionTemporary() { + if ( numRegisters >= MAX_EXPRESSION_REGISTERS ) { + common->Warning( "GetExpressionTemporary: material '%s' hit MAX_EXPRESSION_REGISTERS", GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + pd->registerIsTemporary[numRegisters] = true; + numRegisters++; + return numRegisters - 1; +} + +/* +============= +idMaterial::GetExpressionOp +============= +*/ +expOp_t *idMaterial::GetExpressionOp() { + if ( numOps == MAX_EXPRESSION_OPS ) { + common->Warning( "GetExpressionOp: material '%s' hit MAX_EXPRESSION_OPS", GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return &pd->shaderOps[0]; + } + + return &pd->shaderOps[numOps++]; +} + +/* +================= +idMaterial::EmitOp +================= +*/ +int idMaterial::EmitOp( int a, int b, expOpType_t opType ) { + expOp_t *op; + + // optimize away identity operations + if ( opType == OP_TYPE_ADD ) { + if ( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 0 ) { + return b; + } + if ( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 0 ) { + return a; + } + if ( !pd->registerIsTemporary[a] && !pd->registerIsTemporary[b] ) { + return GetExpressionConstant( pd->shaderRegisters[a] + pd->shaderRegisters[b] ); + } + } + if ( opType == OP_TYPE_MULTIPLY ) { + if ( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 1 ) { + return b; + } + if ( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 0 ) { + return a; + } + if ( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 1 ) { + return a; + } + if ( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 0 ) { + return b; + } + if ( !pd->registerIsTemporary[a] && !pd->registerIsTemporary[b] ) { + return GetExpressionConstant( pd->shaderRegisters[a] * pd->shaderRegisters[b] ); + } + } + + op = GetExpressionOp(); + op->opType = opType; + op->a = a; + op->b = b; + op->c = GetExpressionTemporary(); + + return op->c; +} + +/* +================= +idMaterial::ParseEmitOp +================= +*/ +int idMaterial::ParseEmitOp( idLexer &src, int a, expOpType_t opType, int priority ) { + int b; + + b = ParseExpressionPriority( src, priority ); + return EmitOp( a, b, opType ); +} + +/* +================= +idMaterial::ParseTerm + +Returns a register index +================= +*/ +int idMaterial::ParseTerm( idLexer &src ) { + idToken token; + int a, b; + + src.ReadToken( &token ); + + if ( token == "(" ) { + a = ParseExpression( src ); + MatchToken( src, ")" ); + return a; + } + + if ( !token.Icmp( "time" ) ) { + pd->registersAreConstant = false; + return EXP_REG_TIME; + } + if ( !token.Icmp( "parm0" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM0; + } + if ( !token.Icmp( "parm1" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM1; + } + if ( !token.Icmp( "parm2" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM2; + } + if ( !token.Icmp( "parm3" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM3; + } + if ( !token.Icmp( "parm4" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM4; + } + if ( !token.Icmp( "parm5" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM5; + } + if ( !token.Icmp( "parm6" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM6; + } + if ( !token.Icmp( "parm7" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM7; + } + if ( !token.Icmp( "parm8" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM8; + } + if ( !token.Icmp( "parm9" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM9; + } + if ( !token.Icmp( "parm10" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM10; + } + if ( !token.Icmp( "parm11" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM11; + } + if ( !token.Icmp( "global0" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL0; + } + if ( !token.Icmp( "global1" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL1; + } + if ( !token.Icmp( "global2" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL2; + } + if ( !token.Icmp( "global3" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL3; + } + if ( !token.Icmp( "global4" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL4; + } + if ( !token.Icmp( "global5" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL5; + } + if ( !token.Icmp( "global6" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL6; + } + if ( !token.Icmp( "global7" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL7; + } + if ( !token.Icmp( "fragmentPrograms" ) ) { + return 1.0f; + } + + if ( !token.Icmp( "sound" ) ) { + pd->registersAreConstant = false; + return EmitOp( 0, 0, OP_TYPE_SOUND ); + } + + // parse negative numbers + if ( token == "-" ) { + src.ReadToken( &token ); + if ( token.type == TT_NUMBER || token == "." ) { + return GetExpressionConstant( -(float) token.GetFloatValue() ); + } + src.Warning( "Bad negative number '%s'", token.c_str() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + + if ( token.type == TT_NUMBER || token == "." || token == "-" ) { + return GetExpressionConstant( (float) token.GetFloatValue() ); + } + + // see if it is a table name + const idDeclTable *table = static_cast( declManager->FindType( DECL_TABLE, token.c_str(), false ) ); + if ( !table ) { + src.Warning( "Bad term '%s'", token.c_str() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + + // parse a table expression + MatchToken( src, "[" ); + + b = ParseExpression( src ); + + MatchToken( src, "]" ); + + return EmitOp( table->Index(), b, OP_TYPE_TABLE ); +} + +/* +================= +idMaterial::ParseExpressionPriority + +Returns a register index +================= +*/ +#define TOP_PRIORITY 4 +int idMaterial::ParseExpressionPriority( idLexer &src, int priority ) { + idToken token; + int a; + + if ( priority == 0 ) { + return ParseTerm( src ); + } + + a = ParseExpressionPriority( src, priority - 1 ); + + if ( TestMaterialFlag( MF_DEFAULTED ) ) { // we have a parse error + return 0; + } + + if ( !src.ReadToken( &token ) ) { + // we won't get EOF in a real file, but we can + // when parsing from generated strings + return a; + } + + if ( priority == 1 && token == "*" ) { + return ParseEmitOp( src, a, OP_TYPE_MULTIPLY, priority ); + } + if ( priority == 1 && token == "/" ) { + return ParseEmitOp( src, a, OP_TYPE_DIVIDE, priority ); + } + if ( priority == 1 && token == "%" ) { // implied truncate both to integer + return ParseEmitOp( src, a, OP_TYPE_MOD, priority ); + } + if ( priority == 2 && token == "+" ) { + return ParseEmitOp( src, a, OP_TYPE_ADD, priority ); + } + if ( priority == 2 && token == "-" ) { + return ParseEmitOp( src, a, OP_TYPE_SUBTRACT, priority ); + } + if ( priority == 3 && token == ">" ) { + return ParseEmitOp( src, a, OP_TYPE_GT, priority ); + } + if ( priority == 3 && token == ">=" ) { + return ParseEmitOp( src, a, OP_TYPE_GE, priority ); + } + if ( priority == 3 && token == "<" ) { + return ParseEmitOp( src, a, OP_TYPE_LT, priority ); + } + if ( priority == 3 && token == "<=" ) { + return ParseEmitOp( src, a, OP_TYPE_LE, priority ); + } + if ( priority == 3 && token == "==" ) { + return ParseEmitOp( src, a, OP_TYPE_EQ, priority ); + } + if ( priority == 3 && token == "!=" ) { + return ParseEmitOp( src, a, OP_TYPE_NE, priority ); + } + if ( priority == 4 && token == "&&" ) { + return ParseEmitOp( src, a, OP_TYPE_AND, priority ); + } + if ( priority == 4 && token == "||" ) { + return ParseEmitOp( src, a, OP_TYPE_OR, priority ); + } + + // assume that anything else terminates the expression + // not too robust error checking... + + src.UnreadToken( &token ); + + return a; +} + +/* +================= +idMaterial::ParseExpression + +Returns a register index +================= +*/ +int idMaterial::ParseExpression( idLexer &src ) { + return ParseExpressionPriority( src, TOP_PRIORITY ); +} + + +/* +=============== +idMaterial::ClearStage +=============== +*/ +void idMaterial::ClearStage( shaderStage_t *ss ) { + ss->drawStateBits = 0; + ss->conditionRegister = GetExpressionConstant( 1 ); + ss->color.registers[0] = + ss->color.registers[1] = + ss->color.registers[2] = + ss->color.registers[3] = GetExpressionConstant( 1 ); +} + +/* +=============== +idMaterial::NameToSrcBlendMode +=============== +*/ +int idMaterial::NameToSrcBlendMode( const idStr &name ) { + if ( !name.Icmp( "GL_ONE" ) ) { + return GLS_SRCBLEND_ONE; + } else if ( !name.Icmp( "GL_ZERO" ) ) { + return GLS_SRCBLEND_ZERO; + } else if ( !name.Icmp( "GL_DST_COLOR" ) ) { + return GLS_SRCBLEND_DST_COLOR; + } else if ( !name.Icmp( "GL_ONE_MINUS_DST_COLOR" ) ) { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } else if ( !name.Icmp( "GL_SRC_ALPHA" ) ) { + return GLS_SRCBLEND_SRC_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_SRC_ALPHA" ) ) { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( !name.Icmp( "GL_DST_ALPHA" ) ) { + return GLS_SRCBLEND_DST_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_DST_ALPHA" ) ) { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } else if ( !name.Icmp( "GL_SRC_ALPHA_SATURATE" ) ) { + assert( 0 ); // FIX ME + return GLS_SRCBLEND_SRC_ALPHA; + } + + common->Warning( "unknown blend mode '%s' in material '%s'", name.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + + return GLS_SRCBLEND_ONE; +} + +/* +=============== +idMaterial::NameToDstBlendMode +=============== +*/ +int idMaterial::NameToDstBlendMode( const idStr &name ) { + if ( !name.Icmp( "GL_ONE" ) ) { + return GLS_DSTBLEND_ONE; + } else if ( !name.Icmp( "GL_ZERO" ) ) { + return GLS_DSTBLEND_ZERO; + } else if ( !name.Icmp( "GL_SRC_ALPHA" ) ) { + return GLS_DSTBLEND_SRC_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_SRC_ALPHA" ) ) { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( !name.Icmp( "GL_DST_ALPHA" ) ) { + return GLS_DSTBLEND_DST_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_DST_ALPHA" ) ) { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } else if ( !name.Icmp( "GL_SRC_COLOR" ) ) { + return GLS_DSTBLEND_SRC_COLOR; + } else if ( !name.Icmp( "GL_ONE_MINUS_SRC_COLOR" ) ) { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + common->Warning( "unknown blend mode '%s' in material '%s'", name.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + + return GLS_DSTBLEND_ONE; +} + +/* +================ +idMaterial::ParseBlend +================ +*/ +void idMaterial::ParseBlend( idLexer &src, shaderStage_t *stage ) { + idToken token; + int srcBlend, dstBlend; + + if ( !src.ReadToken( &token ) ) { + return; + } + + // blending combinations + if ( !token.Icmp( "blend" ) ) { + stage->drawStateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + return; + } + if ( !token.Icmp( "add" ) ) { + stage->drawStateBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE; + return; + } + if ( !token.Icmp( "filter" ) || !token.Icmp( "modulate" ) ) { + stage->drawStateBits = GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + return; + } + if ( !token.Icmp( "none" ) ) { + // none is used when defining an alpha mask that doesn't draw + stage->drawStateBits = GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE; + return; + } + if ( !token.Icmp( "bumpmap" ) ) { + stage->lighting = SL_BUMP; + return; + } + if ( !token.Icmp( "diffusemap" ) ) { + stage->lighting = SL_DIFFUSE; + return; + } + if ( !token.Icmp( "specularmap" ) ) { + stage->lighting = SL_SPECULAR; + return; + } + + srcBlend = NameToSrcBlendMode( token ); + + MatchToken( src, "," ); + if ( !src.ReadToken( &token ) ) { + return; + } + dstBlend = NameToDstBlendMode( token ); + + stage->drawStateBits = srcBlend | dstBlend; +} + +/* +================ +idMaterial::ParseVertexParm + +If there is a single value, it will be repeated across all elements +If there are two values, 3 = 0.0, 4 = 1.0 +if there are three values, 4 = 1.0 +================ +*/ +void idMaterial::ParseVertexParm( idLexer &src, newShaderStage_t *newStage ) { + idToken token; + + src.ReadTokenOnLine( &token ); + int parm = token.GetIntValue(); + if ( !token.IsNumeric() || parm < 0 || parm >= MAX_VERTEX_PARMS ) { + common->Warning( "bad vertexParm number\n" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + if ( parm >= newStage->numVertexParms ) { + newStage->numVertexParms = parm+1; + } + + newStage->vertexParms[parm][0] = ParseExpression( src ); + + src.ReadTokenOnLine( &token ); + if ( !token[0] || token.Icmp( "," ) ) { + newStage->vertexParms[parm][1] = + newStage->vertexParms[parm][2] = + newStage->vertexParms[parm][3] = newStage->vertexParms[parm][0]; + return; + } + + newStage->vertexParms[parm][1] = ParseExpression( src ); + + src.ReadTokenOnLine( &token ); + if ( !token[0] || token.Icmp( "," ) ) { + newStage->vertexParms[parm][2] = GetExpressionConstant( 0 ); + newStage->vertexParms[parm][3] = GetExpressionConstant( 1 ); + return; + } + + newStage->vertexParms[parm][2] = ParseExpression( src ); + + src.ReadTokenOnLine( &token ); + if ( !token[0] || token.Icmp( "," ) ) { + newStage->vertexParms[parm][3] = GetExpressionConstant( 1 ); + return; + } + + newStage->vertexParms[parm][3] = ParseExpression( src ); +} + +/* +================ +idMaterial::ParseVertexParm2 +================ +*/ +void idMaterial::ParseVertexParm2( idLexer &src, newShaderStage_t *newStage ) { + idToken token; + src.ReadTokenOnLine( &token ); + int parm = token.GetIntValue(); + if ( !token.IsNumeric() || parm < 0 || parm >= MAX_VERTEX_PARMS ) { + common->Warning( "bad vertexParm number\n" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + if ( parm >= newStage->numVertexParms ) { + newStage->numVertexParms = parm+1; + } + + newStage->vertexParms[parm][0] = ParseExpression( src ); + MatchToken( src, "," ); + newStage->vertexParms[parm][1] = ParseExpression( src ); + MatchToken( src, "," ); + newStage->vertexParms[parm][2] = ParseExpression( src ); + MatchToken( src, "," ); + newStage->vertexParms[parm][3] = ParseExpression( src ); +} + + +/* +================ +idMaterial::ParseFragmentMap +================ +*/ +void idMaterial::ParseFragmentMap( idLexer &src, newShaderStage_t *newStage ) { + const char *str; + textureFilter_t tf; + textureRepeat_t trp; + textureUsage_t td; + cubeFiles_t cubeMap; + idToken token; + + tf = TF_DEFAULT; + trp = TR_REPEAT; + td = TD_DEFAULT; + cubeMap = CF_2D; + + src.ReadTokenOnLine( &token ); + int unit = token.GetIntValue(); + if ( !token.IsNumeric() || unit < 0 || unit >= MAX_FRAGMENT_IMAGES ) { + common->Warning( "bad fragmentMap number\n" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + // unit 1 is the normal map.. make sure it gets flagged as the proper depth + if ( unit == 1 ) { + td = TD_BUMP; + } + + if ( unit >= newStage->numFragmentProgramImages ) { + newStage->numFragmentProgramImages = unit+1; + } + + while( 1 ) { + src.ReadTokenOnLine( &token ); + + if ( !token.Icmp( "cubeMap" ) ) { + cubeMap = CF_NATIVE; + continue; + } + if ( !token.Icmp( "cameraCubeMap" ) ) { + cubeMap = CF_CAMERA; + continue; + } + if ( !token.Icmp( "nearest" ) ) { + tf = TF_NEAREST; + continue; + } + if ( !token.Icmp( "linear" ) ) { + tf = TF_LINEAR; + continue; + } + if ( !token.Icmp( "clamp" ) ) { + trp = TR_CLAMP; + continue; + } + if ( !token.Icmp( "noclamp" ) ) { + trp = TR_REPEAT; + continue; + } + if ( !token.Icmp( "zeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO; + continue; + } + if ( !token.Icmp( "alphazeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO_ALPHA; + continue; + } + if ( !token.Icmp( "forceHighQuality" ) ) { + continue; + } + if ( !token.Icmp( "highquality" ) ) { + continue; + } + if ( !token.Icmp( "uncompressed" ) ) { + continue; + } + if ( !token.Icmp( "nopicmip" ) ) { + continue; + } + + // assume anything else is the image name + src.UnreadToken( &token ); + break; + } + str = R_ParsePastImageProgram( src ); + + newStage->fragmentProgramImages[unit] = + globalImages->ImageFromFile( str, tf, trp, td, cubeMap ); + if ( !newStage->fragmentProgramImages[unit] ) { + newStage->fragmentProgramImages[unit] = globalImages->defaultImage; + } +} + +/* +=============== +idMaterial::MultiplyTextureMatrix +=============== +*/ +void idMaterial::MultiplyTextureMatrix( textureStage_t *ts, int registers[2][3] ) { + int old[2][3]; + + if ( !ts->hasMatrix ) { + ts->hasMatrix = true; + memcpy( ts->matrix, registers, sizeof( ts->matrix ) ); + return; + } + + memcpy( old, ts->matrix, sizeof( old ) ); + + // multiply the two maticies + ts->matrix[0][0] = EmitOp( + EmitOp( old[0][0], registers[0][0], OP_TYPE_MULTIPLY ), + EmitOp( old[0][1], registers[1][0], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[0][1] = EmitOp( + EmitOp( old[0][0], registers[0][1], OP_TYPE_MULTIPLY ), + EmitOp( old[0][1], registers[1][1], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[0][2] = EmitOp( + EmitOp( + EmitOp( old[0][0], registers[0][2], OP_TYPE_MULTIPLY ), + EmitOp( old[0][1], registers[1][2], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + old[0][2], OP_TYPE_ADD ); + + ts->matrix[1][0] = EmitOp( + EmitOp( old[1][0], registers[0][0], OP_TYPE_MULTIPLY ), + EmitOp( old[1][1], registers[1][0], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[1][1] = EmitOp( + EmitOp( old[1][0], registers[0][1], OP_TYPE_MULTIPLY ), + EmitOp( old[1][1], registers[1][1], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[1][2] = EmitOp( + EmitOp( + EmitOp( old[1][0], registers[0][2], OP_TYPE_MULTIPLY ), + EmitOp( old[1][1], registers[1][2], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + old[1][2], OP_TYPE_ADD ); + +} + +/* +================= +idMaterial::ParseStage + +An open brace has been parsed + + +{ + if + map + "nearest" "linear" "clamp" "zeroclamp" "uncompressed" "highquality" "nopicmip" + scroll, scale, rotate +} + +================= +*/ +void idMaterial::ParseStage( idLexer &src, const textureRepeat_t trpDefault ) { + idToken token; + const char *str; + shaderStage_t *ss; + textureStage_t *ts; + textureFilter_t tf; + textureRepeat_t trp; + textureUsage_t td; + cubeFiles_t cubeMap; + char imageName[MAX_IMAGE_NAME]; + int a, b; + int matrix[2][3]; + newShaderStage_t newStage; + + if ( numStages >= MAX_SHADER_STAGES ) { + SetMaterialFlag( MF_DEFAULTED ); + common->Warning( "material '%s' exceeded %i stages", GetName(), MAX_SHADER_STAGES ); + } + + tf = TF_DEFAULT; + trp = trpDefault; + td = TD_DEFAULT; + cubeMap = CF_2D; + + imageName[0] = 0; + + memset( &newStage, 0, sizeof( newStage ) ); + newStage.glslProgram = -1; + + ss = &pd->parseStages[numStages]; + ts = &ss->texture; + + ClearStage( ss ); + + while ( 1 ) { + if ( TestMaterialFlag( MF_DEFAULTED ) ) { // we have a parse error + return; + } + if ( !src.ExpectAnyToken( &token ) ) { + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + // the close brace for the entire material ends the draw block + if ( token == "}" ) { + break; + } + + //BSM Nerve: Added for stage naming in the material editor + if( !token.Icmp( "name") ) { + src.SkipRestOfLine(); + continue; + } + + // image options + if ( !token.Icmp( "blend" ) ) { + ParseBlend( src, ss ); + continue; + } + + if ( !token.Icmp( "map" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::Copynz( imageName, str, sizeof( imageName ) ); + continue; + } + + if ( !token.Icmp( "remoteRenderMap" ) ) { + ts->dynamic = DI_REMOTE_RENDER; + ts->width = src.ParseInt(); + ts->height = src.ParseInt(); + continue; + } + + if ( !token.Icmp( "mirrorRenderMap" ) ) { + ts->dynamic = DI_MIRROR_RENDER; + ts->width = src.ParseInt(); + ts->height = src.ParseInt(); + ts->texgen = TG_SCREEN; + continue; + } + + if ( !token.Icmp( "xrayRenderMap" ) ) { + ts->dynamic = DI_XRAY_RENDER; + ts->width = src.ParseInt(); + ts->height = src.ParseInt(); + ts->texgen = TG_SCREEN; + continue; + } + if ( !token.Icmp( "screen" ) ) { + ts->texgen = TG_SCREEN; + continue; + } + if ( !token.Icmp( "screen2" ) ) { + ts->texgen = TG_SCREEN2; + continue; + } + if ( !token.Icmp( "glassWarp" ) ) { + ts->texgen = TG_GLASSWARP; + continue; + } + + if ( !token.Icmp( "videomap" ) ) { + // note that videomaps will always be in clamp mode, so texture + // coordinates had better be in the 0 to 1 range + if ( !src.ReadToken( &token ) ) { + common->Warning( "missing parameter for 'videoMap' keyword in material '%s'", GetName() ); + continue; + } + bool loop = false; + if ( !token.Icmp( "loop" ) ) { + loop = true; + if ( !src.ReadToken( &token ) ) { + common->Warning( "missing parameter for 'videoMap' keyword in material '%s'", GetName() ); + continue; + } + } + ts->cinematic = idCinematic::Alloc(); + ts->cinematic->InitFromFile( token.c_str(), loop ); + continue; + } + + if ( !token.Icmp( "soundmap" ) ) { + if ( !src.ReadToken( &token ) ) { + common->Warning( "missing parameter for 'soundmap' keyword in material '%s'", GetName() ); + continue; + } + ts->cinematic = new (TAG_MATERIAL) idSndWindow(); + ts->cinematic->InitFromFile( token.c_str(), true ); + continue; + } + + if ( !token.Icmp( "cubeMap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::Copynz( imageName, str, sizeof( imageName ) ); + cubeMap = CF_NATIVE; + continue; + } + + if ( !token.Icmp( "cameraCubeMap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::Copynz( imageName, str, sizeof( imageName ) ); + cubeMap = CF_CAMERA; + continue; + } + + if ( !token.Icmp( "ignoreAlphaTest" ) ) { + ss->ignoreAlphaTest = true; + continue; + } + if ( !token.Icmp( "nearest" ) ) { + tf = TF_NEAREST; + continue; + } + if ( !token.Icmp( "linear" ) ) { + tf = TF_LINEAR; + continue; + } + if ( !token.Icmp( "clamp" ) ) { + trp = TR_CLAMP; + continue; + } + if ( !token.Icmp( "noclamp" ) ) { + trp = TR_REPEAT; + continue; + } + if ( !token.Icmp( "zeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO; + continue; + } + if ( !token.Icmp( "alphazeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO_ALPHA; + continue; + } + if ( !token.Icmp( "forceHighQuality" ) ) { + continue; + } + if ( !token.Icmp( "highquality" ) ) { + continue; + } + if ( !token.Icmp( "uncompressed" ) ) { + continue; + } + if ( !token.Icmp( "nopicmip" ) ) { + continue; + } + if ( !token.Icmp( "vertexColor" ) ) { + ss->vertexColor = SVC_MODULATE; + continue; + } + if ( !token.Icmp( "inverseVertexColor" ) ) { + ss->vertexColor = SVC_INVERSE_MODULATE; + continue; + } + + // privatePolygonOffset + else if ( !token.Icmp( "privatePolygonOffset" ) ) { + if ( !src.ReadTokenOnLine( &token ) ) { + ss->privatePolygonOffset = 1; + continue; + } + // explict larger (or negative) offset + src.UnreadToken( &token ); + ss->privatePolygonOffset = src.ParseFloat(); + continue; + } + + // texture coordinate generation + if ( !token.Icmp( "texGen" ) ) { + src.ExpectAnyToken( &token ); + if ( !token.Icmp( "normal" ) ) { + ts->texgen = TG_DIFFUSE_CUBE; + } else if ( !token.Icmp( "reflect" ) ) { + ts->texgen = TG_REFLECT_CUBE; + } else if ( !token.Icmp( "skybox" ) ) { + ts->texgen = TG_SKYBOX_CUBE; + } else if ( !token.Icmp( "wobbleSky" ) ) { + ts->texgen = TG_WOBBLESKY_CUBE; + texGenRegisters[0] = ParseExpression( src ); + texGenRegisters[1] = ParseExpression( src ); + texGenRegisters[2] = ParseExpression( src ); + } else { + common->Warning( "bad texGen '%s' in material %s", token.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + } + continue; + } + if ( !token.Icmp( "scroll" ) || !token.Icmp( "translate" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + matrix[0][0] = GetExpressionConstant( 1 ); + matrix[0][1] = GetExpressionConstant( 0 ); + matrix[0][2] = a; + matrix[1][0] = GetExpressionConstant( 0 ); + matrix[1][1] = GetExpressionConstant( 1 ); + matrix[1][2] = b; + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "scale" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + // this just scales without a centering + matrix[0][0] = a; + matrix[0][1] = GetExpressionConstant( 0 ); + matrix[0][2] = GetExpressionConstant( 0 ); + matrix[1][0] = GetExpressionConstant( 0 ); + matrix[1][1] = b; + matrix[1][2] = GetExpressionConstant( 0 ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "centerScale" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + // this subtracts 0.5, then scales, then adds 0.5 + matrix[0][0] = a; + matrix[0][1] = GetExpressionConstant( 0 ); + matrix[0][2] = EmitOp( GetExpressionConstant( 0.5 ), EmitOp( GetExpressionConstant( 0.5 ), a, OP_TYPE_MULTIPLY ), OP_TYPE_SUBTRACT ); + matrix[1][0] = GetExpressionConstant( 0 ); + matrix[1][1] = b; + matrix[1][2] = EmitOp( GetExpressionConstant( 0.5 ), EmitOp( GetExpressionConstant( 0.5 ), b, OP_TYPE_MULTIPLY ), OP_TYPE_SUBTRACT ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "shear" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + // this subtracts 0.5, then shears, then adds 0.5 + matrix[0][0] = GetExpressionConstant( 1 ); + matrix[0][1] = a; + matrix[0][2] = EmitOp( GetExpressionConstant( -0.5 ), a, OP_TYPE_MULTIPLY ); + matrix[1][0] = b; + matrix[1][1] = GetExpressionConstant( 1 ); + matrix[1][2] = EmitOp( GetExpressionConstant( -0.5 ), b, OP_TYPE_MULTIPLY ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "rotate" ) ) { + const idDeclTable *table; + int sinReg, cosReg; + + // in cycles + a = ParseExpression( src ); + + table = static_cast( declManager->FindType( DECL_TABLE, "sinTable", false ) ); + if ( !table ) { + common->Warning( "no sinTable for rotate defined" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + sinReg = EmitOp( table->Index(), a, OP_TYPE_TABLE ); + + table = static_cast( declManager->FindType( DECL_TABLE, "cosTable", false ) ); + if ( !table ) { + common->Warning( "no cosTable for rotate defined" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + cosReg = EmitOp( table->Index(), a, OP_TYPE_TABLE ); + + // this subtracts 0.5, then rotates, then adds 0.5 + matrix[0][0] = cosReg; + matrix[0][1] = EmitOp( GetExpressionConstant( 0 ), sinReg, OP_TYPE_SUBTRACT ); + matrix[0][2] = EmitOp( EmitOp( EmitOp( GetExpressionConstant( -0.5 ), cosReg, OP_TYPE_MULTIPLY ), + EmitOp( GetExpressionConstant( 0.5 ), sinReg, OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + GetExpressionConstant( 0.5 ), OP_TYPE_ADD ); + + matrix[1][0] = sinReg; + matrix[1][1] = cosReg; + matrix[1][2] = EmitOp( EmitOp( EmitOp( GetExpressionConstant( -0.5 ), sinReg, OP_TYPE_MULTIPLY ), + EmitOp( GetExpressionConstant( -0.5 ), cosReg, OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + GetExpressionConstant( 0.5 ), OP_TYPE_ADD ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + + // color mask options + if ( !token.Icmp( "maskRed" ) ) { + ss->drawStateBits |= GLS_REDMASK; + continue; + } + if ( !token.Icmp( "maskGreen" ) ) { + ss->drawStateBits |= GLS_GREENMASK; + continue; + } + if ( !token.Icmp( "maskBlue" ) ) { + ss->drawStateBits |= GLS_BLUEMASK; + continue; + } + if ( !token.Icmp( "maskAlpha" ) ) { + ss->drawStateBits |= GLS_ALPHAMASK; + continue; + } + if ( !token.Icmp( "maskColor" ) ) { + ss->drawStateBits |= GLS_COLORMASK; + continue; + } + if ( !token.Icmp( "maskDepth" ) ) { + ss->drawStateBits |= GLS_DEPTHMASK; + continue; + } + if ( !token.Icmp( "alphaTest" ) ) { + ss->hasAlphaTest = true; + ss->alphaTestRegister = ParseExpression( src ); + coverage = MC_PERFORATED; + continue; + } + + // shorthand for 2D modulated + if ( !token.Icmp( "colored" ) ) { + ss->color.registers[0] = EXP_REG_PARM0; + ss->color.registers[1] = EXP_REG_PARM1; + ss->color.registers[2] = EXP_REG_PARM2; + ss->color.registers[3] = EXP_REG_PARM3; + pd->registersAreConstant = false; + continue; + } + + if ( !token.Icmp( "color" ) ) { + ss->color.registers[0] = ParseExpression( src ); + MatchToken( src, "," ); + ss->color.registers[1] = ParseExpression( src ); + MatchToken( src, "," ); + ss->color.registers[2] = ParseExpression( src ); + MatchToken( src, "," ); + ss->color.registers[3] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "red" ) ) { + ss->color.registers[0] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "green" ) ) { + ss->color.registers[1] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "blue" ) ) { + ss->color.registers[2] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "alpha" ) ) { + ss->color.registers[3] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "rgb" ) ) { + ss->color.registers[0] = ss->color.registers[1] = + ss->color.registers[2] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "rgba" ) ) { + ss->color.registers[0] = ss->color.registers[1] = + ss->color.registers[2] = ss->color.registers[3] = ParseExpression( src ); + continue; + } + + if ( !token.Icmp( "if" ) ) { + ss->conditionRegister = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "program" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.vertexProgram = renderProgManager.FindVertexShader( token.c_str() ); + newStage.fragmentProgram = renderProgManager.FindFragmentShader( token.c_str() ); + } + continue; + } + if ( !token.Icmp( "fragmentProgram" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.fragmentProgram = renderProgManager.FindFragmentShader( token.c_str() ); + } + continue; + } + if ( !token.Icmp( "vertexProgram" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.vertexProgram = renderProgManager.FindVertexShader( token.c_str() ); + } + continue; + } + + if ( !token.Icmp( "vertexParm2" ) ) { + ParseVertexParm2( src, &newStage ); + continue; + } + + if ( !token.Icmp( "vertexParm" ) ) { + ParseVertexParm( src, &newStage ); + continue; + } + + if ( !token.Icmp( "fragmentMap" ) ) { + ParseFragmentMap( src, &newStage ); + continue; + } + + + common->Warning( "unknown token '%s' in material '%s'", token.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + + // if we are using newStage, allocate a copy of it + if ( newStage.fragmentProgram || newStage.vertexProgram ) { + newStage.glslProgram = renderProgManager.FindGLSLProgram( GetName(), newStage.vertexProgram, newStage.fragmentProgram ); + ss->newStage = (newShaderStage_t *)Mem_Alloc( sizeof( newStage ), TAG_MATERIAL ); + *(ss->newStage) = newStage; + } + + // successfully parsed a stage + numStages++; + + // select a compressed depth based on what the stage is + if ( td == TD_DEFAULT ) { + switch( ss->lighting ) { + case SL_BUMP: + td = TD_BUMP; + break; + case SL_DIFFUSE: + td = TD_DIFFUSE; + break; + case SL_SPECULAR: + td = TD_SPECULAR; + break; + default: + break; + } + } + + // create a new coverage stage on the fly - copy all data from the current stage + if ( ( td == TD_DIFFUSE ) && ss->hasAlphaTest ) { + // create new coverage stage + shaderStage_t* newCoverageStage = &pd->parseStages[numStages]; + numStages++; + // copy it + *newCoverageStage = *ss; + // toggle alphatest off for the current stage so it doesn't get called during the depth fill pass + ss->hasAlphaTest = false; + // toggle alpha test on for the coverage stage + newCoverageStage->hasAlphaTest = true; + newCoverageStage->lighting = SL_COVERAGE; + textureStage_t* coverageTS = &newCoverageStage->texture; + + // now load the image with all the parms we parsed for the coverage stage + if ( imageName[0] ) { + coverageTS->image = globalImages->ImageFromFile( imageName, tf, trp, TD_COVERAGE, cubeMap ); + if ( !coverageTS->image ) { + coverageTS->image = globalImages->defaultImage; + } + } else if ( !coverageTS->cinematic && !coverageTS->dynamic && !ss->newStage ) { + common->Warning( "material '%s' had stage with no image", GetName() ); + coverageTS->image = globalImages->defaultImage; + } + } + + // now load the image with all the parms we parsed + if ( imageName[0] ) { + ts->image = globalImages->ImageFromFile( imageName, tf, trp, td, cubeMap ); + if ( !ts->image ) { + ts->image = globalImages->defaultImage; + } + } else if ( !ts->cinematic && !ts->dynamic && !ss->newStage ) { + common->Warning( "material '%s' had stage with no image", GetName() ); + ts->image = globalImages->defaultImage; + } +} + +/* +=============== +idMaterial::ParseDeform +=============== +*/ +void idMaterial::ParseDeform( idLexer &src ) { + idToken token; + + if ( !src.ExpectAnyToken( &token ) ) { + return; + } + + if ( !token.Icmp( "sprite" ) ) { + deform = DFRM_SPRITE; + cullType = CT_TWO_SIDED; + SetMaterialFlag( MF_NOSHADOWS ); + return; + } + if ( !token.Icmp( "tube" ) ) { + deform = DFRM_TUBE; + cullType = CT_TWO_SIDED; + SetMaterialFlag( MF_NOSHADOWS ); + return; + } + if ( !token.Icmp( "flare" ) ) { + deform = DFRM_FLARE; + cullType = CT_TWO_SIDED; + deformRegisters[0] = ParseExpression( src ); + SetMaterialFlag( MF_NOSHADOWS ); + return; + } + if ( !token.Icmp( "expand" ) ) { + deform = DFRM_EXPAND; + deformRegisters[0] = ParseExpression( src ); + return; + } + if ( !token.Icmp( "move" ) ) { + deform = DFRM_MOVE; + deformRegisters[0] = ParseExpression( src ); + return; + } + if ( !token.Icmp( "turbulent" ) ) { + deform = DFRM_TURB; + + if ( !src.ExpectAnyToken( &token ) ) { + src.Warning( "deform particle missing particle name" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + deformDecl = declManager->FindType( DECL_TABLE, token.c_str(), true ); + + deformRegisters[0] = ParseExpression( src ); + deformRegisters[1] = ParseExpression( src ); + deformRegisters[2] = ParseExpression( src ); + return; + } + if ( !token.Icmp( "eyeBall" ) ) { + deform = DFRM_EYEBALL; + return; + } + if ( !token.Icmp( "particle" ) ) { + deform = DFRM_PARTICLE; + if ( !src.ExpectAnyToken( &token ) ) { + src.Warning( "deform particle missing particle name" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + deformDecl = declManager->FindType( DECL_PARTICLE, token.c_str(), true ); + return; + } + if ( !token.Icmp( "particle2" ) ) { + deform = DFRM_PARTICLE2; + if ( !src.ExpectAnyToken( &token ) ) { + src.Warning( "deform particle missing particle name" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + deformDecl = declManager->FindType( DECL_PARTICLE, token.c_str(), true ); + return; + } + src.Warning( "Bad deform type '%s'", token.c_str() ); + SetMaterialFlag( MF_DEFAULTED ); +} + + +/* +============== +idMaterial::AddImplicitStages + +If a material has diffuse or specular stages without any +bump stage, add an implicit _flat bumpmap stage. + +If a material has a bump stage but no diffuse or specular +stage, add a _white diffuse stage. + +It is valid to have either a diffuse or specular without the other. + +It is valid to have a reflection map and a bump map for bumpy reflection +============== +*/ +void idMaterial::AddImplicitStages( const textureRepeat_t trpDefault /* = TR_REPEAT */ ) { + char buffer[1024]; + idLexer newSrc; + bool hasDiffuse = false; + bool hasSpecular = false; + bool hasBump = false; + bool hasReflection = false; + + for ( int i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].lighting == SL_BUMP ) { + hasBump = true; + } + if ( pd->parseStages[i].lighting == SL_DIFFUSE ) { + hasDiffuse = true; + } + if ( pd->parseStages[i].lighting == SL_SPECULAR ) { + hasSpecular = true; + } + if ( pd->parseStages[i].texture.texgen == TG_REFLECT_CUBE ) { + hasReflection = true; + } + } + + // if it doesn't have an interaction at all, don't add anything + if ( !hasBump && !hasDiffuse && !hasSpecular ) { + return; + } + + if ( numStages == MAX_SHADER_STAGES ) { + return; + } + + if ( !hasBump ) { + idStr::snPrintf( buffer, sizeof( buffer ), "blend bumpmap\nmap _flat\n}\n" ); + newSrc.LoadMemory( buffer, strlen(buffer), "bumpmap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + } + + if ( !hasDiffuse && !hasSpecular && !hasReflection ) { + idStr::snPrintf( buffer, sizeof( buffer ), "blend diffusemap\nmap _white\n}\n" ); + newSrc.LoadMemory( buffer, strlen(buffer), "diffusemap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + } + +} + +/* +=============== +idMaterial::SortInteractionStages + +The renderer expects bump, then diffuse, then specular +There can be multiple bump maps, followed by additional +diffuse and specular stages, which allows cross-faded bump mapping. + +Ambient stages can be interspersed anywhere, but they are +ignored during interactions, and all the interaction +stages are ignored during ambient drawing. +=============== +*/ +void idMaterial::SortInteractionStages() { + int j; + + for ( int i = 0 ; i < numStages ; i = j ) { + // find the next bump map + for ( j = i + 1 ; j < numStages ; j++ ) { + if ( pd->parseStages[j].lighting == SL_BUMP ) { + // if the very first stage wasn't a bumpmap, + // this bumpmap is part of the first group + if ( pd->parseStages[i].lighting != SL_BUMP ) { + continue; + } + break; + } + } + + // bubble sort everything bump / diffuse / specular + for ( int l = 1 ; l < j-i ; l++ ) { + for ( int k = i ; k < j-l ; k++ ) { + if ( pd->parseStages[k].lighting > pd->parseStages[k+1].lighting ) { + shaderStage_t temp; + + temp = pd->parseStages[k]; + pd->parseStages[k] = pd->parseStages[k+1]; + pd->parseStages[k+1] = temp; + } + } + } + } +} + +/* +================= +idMaterial::ParseMaterial + +The current text pointer is at the explicit text definition of the +Parse it into the global material variable. Later functions will optimize it. + +If there is any error during parsing, defaultShader will be set. +================= +*/ +void idMaterial::ParseMaterial( idLexer &src ) { + idToken token; + int s; + char buffer[1024]; + const char *str; + idLexer newSrc; + int i; + + s = 0; + + numOps = 0; + numRegisters = EXP_REG_NUM_PREDEFINED; // leave space for the parms to be copied in + for ( i = 0 ; i < numRegisters ; i++ ) { + pd->registerIsTemporary[i] = true; // they aren't constants that can be folded + } + + numStages = 0; + pd->registersAreConstant = true; // until shown otherwise + textureRepeat_t trpDefault = TR_REPEAT; // allow a global setting for repeat + + while ( 1 ) { + if ( TestMaterialFlag( MF_DEFAULTED ) ) { // we have a parse error + return; + } + if ( !src.ExpectAnyToken( &token ) ) { + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + // end of material definition + if ( token == "}" ) { + break; + } + else if ( !token.Icmp( "qer_editorimage") ) { + src.ReadTokenOnLine( &token ); + editorImageName = token.c_str(); + src.SkipRestOfLine(); + continue; + } + // description + else if ( !token.Icmp( "description") ) { + src.ReadTokenOnLine( &token ); + desc = token.c_str(); + continue; + } + // check for the surface / content bit flags + else if ( CheckSurfaceParm( &token ) ) { + continue; + } + + + // polygonOffset + else if ( !token.Icmp( "polygonOffset" ) ) { + SetMaterialFlag( MF_POLYGONOFFSET ); + if ( !src.ReadTokenOnLine( &token ) ) { + polygonOffset = 1; + continue; + } + // explict larger (or negative) offset + polygonOffset = token.GetFloatValue(); + continue; + } + // noshadow + else if ( !token.Icmp( "noShadows" ) ) { + SetMaterialFlag( MF_NOSHADOWS ); + continue; + } + else if ( !token.Icmp( "suppressInSubview" ) ) { + suppressInSubview = true; + continue; + } + else if ( !token.Icmp( "portalSky" ) ) { + portalSky = true; + continue; + } + // noSelfShadow + else if ( !token.Icmp( "noSelfShadow" ) ) { + SetMaterialFlag( MF_NOSELFSHADOW ); + continue; + } + // noPortalFog + else if ( !token.Icmp( "noPortalFog" ) ) { + SetMaterialFlag( MF_NOPORTALFOG ); + continue; + } + // forceShadows allows nodraw surfaces to cast shadows + else if ( !token.Icmp( "forceShadows" ) ) { + SetMaterialFlag( MF_FORCESHADOWS ); + continue; + } + // overlay / decal suppression + else if ( !token.Icmp( "noOverlays" ) ) { + allowOverlays = false; + continue; + } + // moster blood overlay forcing for alpha tested or translucent surfaces + else if ( !token.Icmp( "forceOverlays" ) ) { + pd->forceOverlays = true; + continue; + } + // translucent + else if ( !token.Icmp( "translucent" ) ) { + coverage = MC_TRANSLUCENT; + continue; + } + // global zero clamp + else if ( !token.Icmp( "zeroclamp" ) ) { + trpDefault = TR_CLAMP_TO_ZERO; + continue; + } + // global clamp + else if ( !token.Icmp( "clamp" ) ) { + trpDefault = TR_CLAMP; + continue; + } + // global clamp + else if ( !token.Icmp( "alphazeroclamp" ) ) { + trpDefault = TR_CLAMP_TO_ZERO; + continue; + } + // forceOpaque is used for skies-behind-windows + else if ( !token.Icmp( "forceOpaque" ) ) { + coverage = MC_OPAQUE; + continue; + } + // twoSided + else if ( !token.Icmp( "twoSided" ) ) { + cullType = CT_TWO_SIDED; + // twoSided implies no-shadows, because the shadow + // volume would be coplanar with the surface, giving depth fighting + // we could make this no-self-shadows, but it may be more important + // to receive shadows from no-self-shadow monsters + SetMaterialFlag( MF_NOSHADOWS ); + } + // backSided + else if ( !token.Icmp( "backSided" ) ) { + cullType = CT_BACK_SIDED; + // the shadow code doesn't handle this, so just disable shadows. + // We could fix this in the future if there was a need. + SetMaterialFlag( MF_NOSHADOWS ); + } + // foglight + else if ( !token.Icmp( "fogLight" ) ) { + fogLight = true; + continue; + } + // blendlight + else if ( !token.Icmp( "blendLight" ) ) { + blendLight = true; + continue; + } + // ambientLight + else if ( !token.Icmp( "ambientLight" ) ) { + ambientLight = true; + continue; + } + // mirror + else if ( !token.Icmp( "mirror" ) ) { + sort = SS_SUBVIEW; + coverage = MC_OPAQUE; + continue; + } + // noFog + else if ( !token.Icmp( "noFog" ) ) { + noFog = true; + continue; + } + // unsmoothedTangents + else if ( !token.Icmp( "unsmoothedTangents" ) ) { + unsmoothedTangents = true; + continue; + } + // lightFallofImage + // specifies the image to use for the third axis of projected + // light volumes + else if ( !token.Icmp( "lightFalloffImage" ) ) { + str = R_ParsePastImageProgram( src ); + idStr copy; + + copy = str; // so other things don't step on it + lightFalloffImage = globalImages->ImageFromFile( copy, TF_DEFAULT, TR_CLAMP /* TR_CLAMP_TO_ZERO */, TD_DEFAULT ); + continue; + } + // guisurf | guisurf entity + // an entity guisurf must have an idUserInterface + // specified in the renderEntity + else if ( !token.Icmp( "guisurf" ) ) { + src.ReadTokenOnLine( &token ); + if ( !token.Icmp( "entity" ) ) { + entityGui = 1; + } else if ( !token.Icmp( "entity2" ) ) { + entityGui = 2; + } else if ( !token.Icmp( "entity3" ) ) { + entityGui = 3; + } else { + gui = uiManager->FindGui( token.c_str(), true ); + } + continue; + } + // sort + else if ( !token.Icmp( "sort" ) ) { + ParseSort( src ); + continue; + } + else if ( !token.Icmp( "stereoeye") ) { + ParseStereoEye( src ); + continue; + } + // spectrum + else if ( !token.Icmp( "spectrum" ) ) { + src.ReadTokenOnLine( &token ); + spectrum = atoi( token.c_str() ); + continue; + } + // deform < sprite | tube | flare > + else if ( !token.Icmp( "deform" ) ) { + ParseDeform( src ); + continue; + } + // decalInfo ( ) ( ) + else if ( !token.Icmp( "decalInfo" ) ) { + ParseDecalInfo( src ); + continue; + } + // renderbump + else if ( !token.Icmp( "renderbump") ) { + src.ParseRestOfLine( renderBump ); + continue; + } + // diffusemap for stage shortcut + else if ( !token.Icmp( "diffusemap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::snPrintf( buffer, sizeof( buffer ), "blend diffusemap\nmap %s\n}\n", str ); + newSrc.LoadMemory( buffer, strlen(buffer), "diffusemap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + continue; + } + // specularmap for stage shortcut + else if ( !token.Icmp( "specularmap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::snPrintf( buffer, sizeof( buffer ), "blend specularmap\nmap %s\n}\n", str ); + newSrc.LoadMemory( buffer, strlen(buffer), "specularmap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + continue; + } + // normalmap for stage shortcut + else if ( !token.Icmp( "bumpmap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::snPrintf( buffer, sizeof( buffer ), "blend bumpmap\nmap %s\n}\n", str ); + newSrc.LoadMemory( buffer, strlen(buffer), "bumpmap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + continue; + } + // DECAL_MACRO for backwards compatibility with the preprocessor macros + else if ( !token.Icmp( "DECAL_MACRO" ) ) { + // polygonOffset + SetMaterialFlag( MF_POLYGONOFFSET ); + polygonOffset = 1; + + // discrete + surfaceFlags |= SURF_DISCRETE; + contentFlags &= ~CONTENTS_SOLID; + + // sort decal + sort = SS_DECAL; + + // noShadows + SetMaterialFlag( MF_NOSHADOWS ); + continue; + } + else if ( token == "{" ) { + // create the new stage + ParseStage( src, trpDefault ); + continue; + } + else { + common->Warning( "unknown general material parameter '%s' in '%s'", token.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + } + + // add _flat or _white stages if needed + AddImplicitStages(); + + // order the diffuse / bump / specular stages properly + SortInteractionStages(); + + // if we need to do anything with normals (lighting or environment mapping) + // and two sided lighting was asked for, flag + // shouldCreateBackSides() and change culling back to single sided, + // so we get proper tangent vectors on both sides + + // we can't just call ReceivesLighting(), because the stages are still + // in temporary form + if ( cullType == CT_TWO_SIDED ) { + for ( i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].lighting != SL_AMBIENT || pd->parseStages[i].texture.texgen != TG_EXPLICIT ) { + if ( cullType == CT_TWO_SIDED ) { + cullType = CT_FRONT_SIDED; + shouldCreateBackSides = true; + } + break; + } + } + } + + // currently a surface can only have one unique texgen for all the stages on old hardware + texgen_t firstGen = TG_EXPLICIT; + for ( i = 0; i < numStages; i++ ) { + if ( pd->parseStages[i].texture.texgen != TG_EXPLICIT ) { + if ( firstGen == TG_EXPLICIT ) { + firstGen = pd->parseStages[i].texture.texgen; + } else if ( firstGen != pd->parseStages[i].texture.texgen ) { + common->Warning( "material '%s' has multiple stages with a texgen", GetName() ); + break; + } + } + } +} + +/* +========================= +idMaterial::SetGui +========================= +*/ +void idMaterial::SetGui( const char *_gui ) const { + gui = uiManager->FindGui( _gui, true, false, true ); +} + +/* +========================= +idMaterial::Parse + +Parses the current material definition and finds all necessary images. +========================= +*/ +bool idMaterial::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + idToken token; + mtrParsingData_t parsingData; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + // reset to the unparsed state + CommonInit(); + + memset( &parsingData, 0, sizeof( parsingData ) ); + + pd = &parsingData; // this is only valid during parse + + // parse it + ParseMaterial( src ); + + // if we are doing an fs_copyfiles, also reference the editorImage + if ( cvarSystem->GetCVarInteger( "fs_copyFiles" ) ) { + GetEditorImage(); + } + + // + // count non-lit stages + numAmbientStages = 0; + int i; + for ( i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].lighting == SL_AMBIENT ) { + numAmbientStages++; + } + } + + // see if there is a subview stage + if ( sort == SS_SUBVIEW ) { + hasSubview = true; + } else { + hasSubview = false; + for ( i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].texture.dynamic ) { + hasSubview = true; + } + } + } + + // automatically determine coverage if not explicitly set + if ( coverage == MC_BAD ) { + // automatically set MC_TRANSLUCENT if we don't have any interaction stages and + // the first stage is blended and not an alpha test mask or a subview + if ( !numStages ) { + // non-visible + coverage = MC_TRANSLUCENT; + } else if ( numStages != numAmbientStages ) { + // we have an interaction draw + coverage = MC_OPAQUE; + } else if ( + ( pd->parseStages[0].drawStateBits & GLS_DSTBLEND_BITS ) != GLS_DSTBLEND_ZERO || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_COLOR || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_ONE_MINUS_DST_COLOR || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_ALPHA || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_ONE_MINUS_DST_ALPHA + ) { + // blended with the destination + coverage = MC_TRANSLUCENT; + } else { + coverage = MC_OPAQUE; + } + } + + // translucent automatically implies noshadows + if ( coverage == MC_TRANSLUCENT ) { + SetMaterialFlag( MF_NOSHADOWS ); + } else { + // mark the contents as opaque + contentFlags |= CONTENTS_OPAQUE; + } + + // if we are translucent, draw with an alpha in the editor + if ( coverage == MC_TRANSLUCENT ) { + editorAlpha = 0.5; + } else { + editorAlpha = 1.0; + } + + // the sorts can make reasonable defaults + if ( sort == SS_BAD ) { + if ( TestMaterialFlag(MF_POLYGONOFFSET) ) { + sort = SS_DECAL; + } else if ( coverage == MC_TRANSLUCENT ) { + sort = SS_MEDIUM; + } else { + sort = SS_OPAQUE; + } + } + + // anything that references _currentRender will automatically get sort = SS_POST_PROCESS + // and coverage = MC_TRANSLUCENT + + for ( i = 0 ; i < numStages ; i++ ) { + shaderStage_t *pStage = &pd->parseStages[i]; + if ( pStage->texture.image == globalImages->originalCurrentRenderImage ) { + if ( sort != SS_PORTAL_SKY ) { + sort = SS_POST_PROCESS; + coverage = MC_TRANSLUCENT; + } + break; + } + if ( pStage->newStage ) { + for ( int j = 0 ; j < pStage->newStage->numFragmentProgramImages ; j++ ) { + if ( pStage->newStage->fragmentProgramImages[j] == globalImages->originalCurrentRenderImage ) { + if ( sort != SS_PORTAL_SKY ) { + sort = SS_POST_PROCESS; + coverage = MC_TRANSLUCENT; + } + i = numStages; + break; + } + } + } + } + + // set the drawStateBits depth flags + for ( i = 0 ; i < numStages ; i++ ) { + shaderStage_t *pStage = &pd->parseStages[i]; + if ( sort == SS_POST_PROCESS ) { + // post-process effects fill the depth buffer as they draw, so only the + // topmost post-process effect is rendered + pStage->drawStateBits |= GLS_DEPTHFUNC_LESS; + } else if ( coverage == MC_TRANSLUCENT || pStage->ignoreAlphaTest ) { + // translucent surfaces can extend past the exactly marked depth buffer + pStage->drawStateBits |= GLS_DEPTHFUNC_LESS | GLS_DEPTHMASK; + } else { + // opaque and perforated surfaces must exactly match the depth buffer, + // which gets alpha test correct + pStage->drawStateBits |= GLS_DEPTHFUNC_EQUAL | GLS_DEPTHMASK; + } + } + + // determine if this surface will accept overlays / decals + + if ( pd->forceOverlays ) { + // explicitly flaged in material definition + allowOverlays = true; + } else { + if ( !IsDrawn() ) { + allowOverlays = false; + } + if ( Coverage() != MC_OPAQUE ) { + allowOverlays = false; + } + if ( GetSurfaceFlags() & SURF_NOIMPACT ) { + allowOverlays = false; + } + } + + // add a tiny offset to the sort orders, so that different materials + // that have the same sort value will at least sort consistantly, instead + // of flickering back and forth +/* this messed up in-game guis + if ( sort != SS_SUBVIEW ) { + int hash, l; + + l = name.Length(); + hash = 0; + for ( int i = 0 ; i < l ; i++ ) { + hash ^= name[i]; + } + sort += hash * 0.01; + } +*/ + + if (numStages) { + stages = (shaderStage_t *)R_StaticAlloc( numStages * sizeof( stages[0] ), TAG_MATERIAL ); + memcpy( stages, pd->parseStages, numStages * sizeof( stages[0] ) ); + } + + if ( numOps ) { + ops = (expOp_t *)R_StaticAlloc( numOps * sizeof( ops[0] ), TAG_MATERIAL ); + memcpy( ops, pd->shaderOps, numOps * sizeof( ops[0] ) ); + } + + if ( numRegisters ) { + expressionRegisters = (float *)R_StaticAlloc( numRegisters * sizeof( expressionRegisters[0] ), TAG_MATERIAL ); + memcpy( expressionRegisters, pd->shaderRegisters, numRegisters * sizeof( expressionRegisters[0] ) ); + } + + // see if the registers are completely constant, and don't need to be evaluated + // per-surface + CheckForConstantRegisters(); + + // See if the material is trivial for the fast path + SetFastPathImages(); + + pd = NULL; // the pointer will be invalid after exiting this function + + // finish things up + if ( TestMaterialFlag( MF_DEFAULTED ) ) { + MakeDefault(); + return false; + } + return true; +} + +/* +=================== +idMaterial::Print +=================== +*/ +char *opNames[] = { + "OP_TYPE_ADD", + "OP_TYPE_SUBTRACT", + "OP_TYPE_MULTIPLY", + "OP_TYPE_DIVIDE", + "OP_TYPE_MOD", + "OP_TYPE_TABLE", + "OP_TYPE_GT", + "OP_TYPE_GE", + "OP_TYPE_LT", + "OP_TYPE_LE", + "OP_TYPE_EQ", + "OP_TYPE_NE", + "OP_TYPE_AND", + "OP_TYPE_OR" +}; + +void idMaterial::Print() const { + int i; + + for ( i = EXP_REG_NUM_PREDEFINED ; i < GetNumRegisters() ; i++ ) { + common->Printf( "register %i: %f\n", i, expressionRegisters[i] ); + } + common->Printf( "\n" ); + for ( i = 0 ; i < numOps ; i++ ) { + const expOp_t *op = &ops[i]; + if ( op->opType == OP_TYPE_TABLE ) { + common->Printf( "%i = %s[ %i ]\n", op->c, declManager->DeclByIndex( DECL_TABLE, op->a )->GetName(), op->b ); + } else { + common->Printf( "%i = %i %s %i\n", op->c, op->a, opNames[ op->opType ], op->b ); + } + } +} + +/* +=============== +idMaterial::Save +=============== +*/ +bool idMaterial::Save( const char *fileName ) { + return ReplaceSourceFileText(); +} + +/* +=============== +idMaterial::AddReference +=============== +*/ +void idMaterial::AddReference() { + refCount++; + + for ( int i = 0; i < numStages; i++ ) { + shaderStage_t *s = &stages[i]; + + if ( s->texture.image ) { + s->texture.image->AddReference(); + } + } +} + +/* +=============== +idMaterial::EvaluateRegisters + +Parameters are taken from the localSpace and the renderView, +then all expressions are evaluated, leaving the material registers +set to their apropriate values. +=============== +*/ +void idMaterial::EvaluateRegisters( + float * registers, + const float localShaderParms[MAX_ENTITY_SHADER_PARMS], + const float globalShaderParms[MAX_GLOBAL_SHADER_PARMS], + const float floatTime, + idSoundEmitter *soundEmitter ) const { + + int i, b; + expOp_t *op; + + // copy the material constants + for ( i = EXP_REG_NUM_PREDEFINED ; i < numRegisters ; i++ ) { + registers[i] = expressionRegisters[i]; + } + + // copy the local and global parameters + registers[EXP_REG_TIME] = floatTime; + registers[EXP_REG_PARM0] = localShaderParms[0]; + registers[EXP_REG_PARM1] = localShaderParms[1]; + registers[EXP_REG_PARM2] = localShaderParms[2]; + registers[EXP_REG_PARM3] = localShaderParms[3]; + registers[EXP_REG_PARM4] = localShaderParms[4]; + registers[EXP_REG_PARM5] = localShaderParms[5]; + registers[EXP_REG_PARM6] = localShaderParms[6]; + registers[EXP_REG_PARM7] = localShaderParms[7]; + registers[EXP_REG_PARM8] = localShaderParms[8]; + registers[EXP_REG_PARM9] = localShaderParms[9]; + registers[EXP_REG_PARM10] = localShaderParms[10]; + registers[EXP_REG_PARM11] = localShaderParms[11]; + registers[EXP_REG_GLOBAL0] = globalShaderParms[0]; + registers[EXP_REG_GLOBAL1] = globalShaderParms[1]; + registers[EXP_REG_GLOBAL2] = globalShaderParms[2]; + registers[EXP_REG_GLOBAL3] = globalShaderParms[3]; + registers[EXP_REG_GLOBAL4] = globalShaderParms[4]; + registers[EXP_REG_GLOBAL5] = globalShaderParms[5]; + registers[EXP_REG_GLOBAL6] = globalShaderParms[6]; + registers[EXP_REG_GLOBAL7] = globalShaderParms[7]; + + op = ops; + for ( i = 0 ; i < numOps ; i++, op++ ) { + switch( op->opType ) { + case OP_TYPE_ADD: + registers[op->c] = registers[op->a] + registers[op->b]; + break; + case OP_TYPE_SUBTRACT: + registers[op->c] = registers[op->a] - registers[op->b]; + break; + case OP_TYPE_MULTIPLY: + registers[op->c] = registers[op->a] * registers[op->b]; + break; + case OP_TYPE_DIVIDE: + registers[op->c] = registers[op->a] / registers[op->b]; + break; + case OP_TYPE_MOD: + b = (int)registers[op->b]; + b = b != 0 ? b : 1; + registers[op->c] = (int)registers[op->a] % b; + break; + case OP_TYPE_TABLE: + { + const idDeclTable *table = static_cast( declManager->DeclByIndex( DECL_TABLE, op->a ) ); + registers[op->c] = table->TableLookup( registers[op->b] ); + } + break; + case OP_TYPE_SOUND: + if ( r_forceSoundOpAmplitude.GetFloat() > 0 ) { + registers[op->c] = r_forceSoundOpAmplitude.GetFloat(); + } else if ( soundEmitter ) { + registers[op->c] = soundEmitter->CurrentAmplitude(); + } else { + registers[op->c] = 0; + } + break; + case OP_TYPE_GT: + registers[op->c] = registers[ op->a ] > registers[op->b]; + break; + case OP_TYPE_GE: + registers[op->c] = registers[ op->a ] >= registers[op->b]; + break; + case OP_TYPE_LT: + registers[op->c] = registers[ op->a ] < registers[op->b]; + break; + case OP_TYPE_LE: + registers[op->c] = registers[ op->a ] <= registers[op->b]; + break; + case OP_TYPE_EQ: + registers[op->c] = registers[ op->a ] == registers[op->b]; + break; + case OP_TYPE_NE: + registers[op->c] = registers[ op->a ] != registers[op->b]; + break; + case OP_TYPE_AND: + registers[op->c] = registers[ op->a ] && registers[op->b]; + break; + case OP_TYPE_OR: + registers[op->c] = registers[ op->a ] || registers[op->b]; + break; + default: + common->FatalError( "R_EvaluateExpression: bad opcode" ); + } + } + +} + +/* +============= +idMaterial::Texgen +============= +*/ +texgen_t idMaterial::Texgen() const { + if ( stages ) { + for ( int i = 0; i < numStages; i++ ) { + if ( stages[ i ].texture.texgen != TG_EXPLICIT ) { + return stages[ i ].texture.texgen; + } + } + } + + return TG_EXPLICIT; +} + +/* +============= +idMaterial::GetImageWidth +============= +*/ +int idMaterial::GetImageWidth() const { + assert( GetStage(0) && GetStage(0)->texture.image ); + return GetStage(0)->texture.image->GetUploadWidth(); +} + +/* +============= +idMaterial::GetImageHeight +============= +*/ +int idMaterial::GetImageHeight() const { + assert( GetStage(0) && GetStage(0)->texture.image ); + return GetStage(0)->texture.image->GetUploadHeight(); +} + +/* +============= +idMaterial::CinematicLength +============= +*/ +int idMaterial::CinematicLength() const { + if ( !stages || !stages[0].texture.cinematic ) { + return 0; + } + return stages[0].texture.cinematic->AnimationLength(); +} + +/* +============= +idMaterial::UpdateCinematic +============= +*/ +void idMaterial::UpdateCinematic( int time ) const { +} + +/* +============= +idMaterial::CloseCinematic +============= +*/ +void idMaterial::CloseCinematic() const { + for( int i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic ) { + stages[i].texture.cinematic->Close(); + delete stages[i].texture.cinematic; + stages[i].texture.cinematic = NULL; + } + } +} + +/* +============= +idMaterial::ResetCinematicTime +============= +*/ +void idMaterial::ResetCinematicTime( int time ) const { + for( int i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic ) { + stages[i].texture.cinematic->ResetTime( time ); + } + } +} + +/* +============= +idMaterial::GetCinematicStartTime +============= +*/ +int idMaterial::GetCinematicStartTime() const { + for( int i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic ) { + return stages[i].texture.cinematic->GetStartTime(); + } + } + return -1; +} + +/* +================== +idMaterial::CheckForConstantRegisters + +As of 5/2/03, about half of the unique materials loaded on typical +maps are constant, but 2/3 of the surface references are. +================== +*/ +void idMaterial::CheckForConstantRegisters() { + assert( constantRegisters == NULL ); + + if ( !pd->registersAreConstant ) { + return; + } + if ( !r_useConstantMaterials.GetBool() ) { + return; + } + + // evaluate the registers once, and save them + constantRegisters = (float *)R_ClearedStaticAlloc( GetNumRegisters() * sizeof( float ) ); + + float shaderParms[MAX_ENTITY_SHADER_PARMS]; + memset( shaderParms, 0, sizeof( shaderParms ) ); + viewDef_t viewDef; + memset( &viewDef, 0, sizeof( viewDef ) ); + + EvaluateRegisters( constantRegisters, shaderParms, viewDef.renderView.shaderParms, 0.0f, 0 ); +} + +/* +=================== +idMaterial::ImageName +=================== +*/ +const char *idMaterial::ImageName() const { + if ( numStages == 0 ) { + return "_scratch"; + } + idImage *image = stages[0].texture.image; + if ( image ) { + return image->GetName(); + } + return "_scratch"; +} + +/* +================= +idMaterial::Size +================= +*/ +size_t idMaterial::Size() const { + return sizeof( idMaterial ); +} + +/* +=================== +idMaterial::SetDefaultText +=================== +*/ +bool idMaterial::SetDefaultText() { + // if there exists an image with the same name + if ( 1 ) { //fileSystem->ReadFile( GetName(), NULL ) != -1 ) { + char generated[2048]; + idStr::snPrintf( generated, sizeof( generated ), + "material %s // IMPLICITLY GENERATED\n" + "{\n" + "{\n" + "blend blend\n" + "colored\n" + "map \"%s\"\n" + "clamp\n" + "}\n" + "}\n", GetName(), GetName() ); + SetText( generated ); + return true; + } else { + return false; + } +} + +/* +=================== +idMaterial::DefaultDefinition +=================== +*/ +const char *idMaterial::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "blend\tblend\n" + "\t\t" "map\t\t_default\n" + "\t" "}\n" + "}"; +} + + +/* +=================== +idMaterial::GetBumpStage +=================== +*/ +const shaderStage_t *idMaterial::GetBumpStage() const { + for ( int i = 0 ; i < numStages ; i++ ) { + if ( stages[i].lighting == SL_BUMP ) { + return &stages[i]; + } + } + return NULL; +} + +/* +=================== +idMaterial::ReloadImages +=================== +*/ +void idMaterial::ReloadImages( bool force ) const { + for ( int i = 0 ; i < numStages ; i++ ) { + if ( stages[i].newStage ) { + for ( int j = 0 ; j < stages[i].newStage->numFragmentProgramImages ; j++ ) { + if ( stages[i].newStage->fragmentProgramImages[j] ) { + stages[i].newStage->fragmentProgramImages[j]->Reload( force ); + } + } + } else if ( stages[i].texture.image ) { + stages[i].texture.image->Reload( force ); + } + } +} + +/* +============= +idMaterial::SetFastPathImages + +See if the material is trivial for the fast path +============= +*/ +void idMaterial::SetFastPathImages() { + fastPathBumpImage = NULL; + fastPathDiffuseImage = NULL; + fastPathSpecularImage = NULL; + + if ( constantRegisters == NULL ) { + return; + } + + // go through the individual surface stages + // + // We also have the very rare case of some materials that have conditional interactions + // for the "hell writing" that can be shined on them. + + for ( int surfaceStageNum = 0; surfaceStageNum < GetNumStages(); surfaceStageNum++ ) { + const shaderStage_t *surfaceStage = GetStage( surfaceStageNum ); + + if ( surfaceStage->texture.hasMatrix ) { + goto fail; + } + + // check for vertex coloring + if ( surfaceStage->vertexColor != SVC_IGNORE ) { + goto fail; + } + + // check for non-identity colors + for ( int i = 0; i < 4; i++ ) { + if ( idMath::Fabs( constantRegisters[surfaceStage->color.registers[i]] - 1.0f ) > 0.1f ) { + goto fail; + } + } + + switch( surfaceStage->lighting ) { + case SL_COVERAGE: + case SL_AMBIENT: + break; + case SL_BUMP: { + if ( fastPathBumpImage ) { + goto fail; + } + fastPathBumpImage = surfaceStage->texture.image; + break; + } + case SL_DIFFUSE: { + if ( fastPathDiffuseImage ) { + goto fail; + } + fastPathDiffuseImage = surfaceStage->texture.image; + break; + } + case SL_SPECULAR: { + if ( fastPathSpecularImage ) { + goto fail; + } + fastPathSpecularImage = surfaceStage->texture.image; + } + } + } + // need a bump image, but specular can default + // we also need a diffuse image, because we can't get a pure black with our YCoCg conversion + // from 565 DXT. The general-path code also sets the diffuse color to 0 in the default case, + // but the fast path can't. + if ( fastPathBumpImage == NULL || fastPathDiffuseImage == NULL ) { + goto fail; + } + if ( fastPathSpecularImage == NULL ) { + fastPathSpecularImage = globalImages->blackImage; + } + return; + +fail: + fastPathBumpImage = NULL; + fastPathDiffuseImage = NULL; + fastPathSpecularImage = NULL; +} diff --git a/neo/renderer/Material.h b/neo/renderer/Material.h new file mode 100644 index 00000000..cf4ec844 --- /dev/null +++ b/neo/renderer/Material.h @@ -0,0 +1,710 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MATERIAL_H__ +#define __MATERIAL_H__ + +/* +=============================================================================== + + Material + +=============================================================================== +*/ + +class idImage; +class idCinematic; +class idUserInterface; + +// moved from image.h for default parm +typedef enum { + TF_LINEAR, + TF_NEAREST, + TF_DEFAULT // use the user-specified r_textureFilter +} textureFilter_t; + +typedef enum { + TR_REPEAT, + TR_CLAMP, + TR_CLAMP_TO_ZERO, // guarantee 0,0,0,255 edge for projected textures + TR_CLAMP_TO_ZERO_ALPHA // guarantee 0 alpha edge for projected textures +} textureRepeat_t; + +typedef struct { + int stayTime; // msec for no change + int fadeTime; // msec to fade vertex colors over + float start[4]; // vertex color at spawn (possibly out of 0.0 - 1.0 range, will clamp after calc) + float end[4]; // vertex color at fade-out (possibly out of 0.0 - 1.0 range, will clamp after calc) +} decalInfo_t; + +typedef enum { + DFRM_NONE, + DFRM_SPRITE, + DFRM_TUBE, + DFRM_FLARE, + DFRM_EXPAND, + DFRM_MOVE, + DFRM_EYEBALL, + DFRM_PARTICLE, + DFRM_PARTICLE2, + DFRM_TURB +} deform_t; + +typedef enum { + DI_STATIC, + DI_SCRATCH, // video, screen wipe, etc + DI_CUBE_RENDER, + DI_MIRROR_RENDER, + DI_XRAY_RENDER, + DI_REMOTE_RENDER +} dynamicidImage_t; + +// note: keep opNames[] in sync with changes +typedef enum { + OP_TYPE_ADD, + OP_TYPE_SUBTRACT, + OP_TYPE_MULTIPLY, + OP_TYPE_DIVIDE, + OP_TYPE_MOD, + OP_TYPE_TABLE, + OP_TYPE_GT, + OP_TYPE_GE, + OP_TYPE_LT, + OP_TYPE_LE, + OP_TYPE_EQ, + OP_TYPE_NE, + OP_TYPE_AND, + OP_TYPE_OR, + OP_TYPE_SOUND +} expOpType_t; + +typedef enum { + EXP_REG_TIME, + + EXP_REG_PARM0, + EXP_REG_PARM1, + EXP_REG_PARM2, + EXP_REG_PARM3, + EXP_REG_PARM4, + EXP_REG_PARM5, + EXP_REG_PARM6, + EXP_REG_PARM7, + EXP_REG_PARM8, + EXP_REG_PARM9, + EXP_REG_PARM10, + EXP_REG_PARM11, + + EXP_REG_GLOBAL0, + EXP_REG_GLOBAL1, + EXP_REG_GLOBAL2, + EXP_REG_GLOBAL3, + EXP_REG_GLOBAL4, + EXP_REG_GLOBAL5, + EXP_REG_GLOBAL6, + EXP_REG_GLOBAL7, + + EXP_REG_NUM_PREDEFINED +} expRegister_t; + +typedef struct { + expOpType_t opType; + int a, b, c; +} expOp_t; + +typedef struct { + int registers[4]; +} colorStage_t; + +typedef enum { + TG_EXPLICIT, + TG_DIFFUSE_CUBE, + TG_REFLECT_CUBE, + TG_SKYBOX_CUBE, + TG_WOBBLESKY_CUBE, + TG_SCREEN, // screen aligned, for mirrorRenders and screen space temporaries + TG_SCREEN2, + TG_GLASSWARP +} texgen_t; + +typedef struct { + idCinematic * cinematic; + idImage * image; + texgen_t texgen; + bool hasMatrix; + int matrix[2][3]; // we only allow a subset of the full projection matrix + + // dynamic image variables + dynamicidImage_t dynamic; + int width, height; + int dynamicFrameCount; +} textureStage_t; + +// the order BUMP / DIFFUSE / SPECULAR is necessary for interactions to draw correctly on low end cards +typedef enum { + SL_AMBIENT, // execute after lighting + SL_BUMP, + SL_DIFFUSE, + SL_SPECULAR, + SL_COVERAGE, +} stageLighting_t; + +// cross-blended terrain textures need to modulate the color by +// the vertex color to smoothly blend between two textures +typedef enum { + SVC_IGNORE, + SVC_MODULATE, + SVC_INVERSE_MODULATE +} stageVertexColor_t; + +static const int MAX_FRAGMENT_IMAGES = 8; +static const int MAX_VERTEX_PARMS = 4; + +typedef struct { + int vertexProgram; + int numVertexParms; + int vertexParms[MAX_VERTEX_PARMS][4]; // evaluated register indexes + + int fragmentProgram; + int glslProgram; + int numFragmentProgramImages; + idImage * fragmentProgramImages[MAX_FRAGMENT_IMAGES]; +} newShaderStage_t; + +typedef struct { + int conditionRegister; // if registers[conditionRegister] == 0, skip stage + stageLighting_t lighting; // determines which passes interact with lights + uint64 drawStateBits; + colorStage_t color; + bool hasAlphaTest; + int alphaTestRegister; + textureStage_t texture; + stageVertexColor_t vertexColor; + bool ignoreAlphaTest; // this stage should act as translucent, even + // if the surface is alpha tested + float privatePolygonOffset; // a per-stage polygon offset + + newShaderStage_t *newStage; // vertex / fragment program based stage +} shaderStage_t; + +typedef enum { + MC_BAD, + MC_OPAQUE, // completely fills the triangle, will have black drawn on fillDepthBuffer + MC_PERFORATED, // may have alpha tested holes + MC_TRANSLUCENT // blended with background +} materialCoverage_t; + +typedef enum { + SS_SUBVIEW = -3, // mirrors, viewscreens, etc + SS_GUI = -2, // guis + SS_BAD = -1, + SS_OPAQUE, // opaque + + SS_PORTAL_SKY, + + SS_DECAL, // scorch marks, etc. + + SS_FAR, + SS_MEDIUM, // normal translucent + SS_CLOSE, + + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST, // screen blood blobs + + SS_POST_PROCESS = 100 // after a screen copy to texture +} materialSort_t; + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +// these don't effect per-material storage, so they can be very large +const int MAX_SHADER_STAGES = 256; + +const int MAX_TEXGEN_REGISTERS = 4; + +const int MAX_ENTITY_SHADER_PARMS = 12; +const int MAX_GLOBAL_SHADER_PARMS = 12; // ? this looks like it should only be 8 + +// material flags +typedef enum { + MF_DEFAULTED = BIT(0), + MF_POLYGONOFFSET = BIT(1), + MF_NOSHADOWS = BIT(2), + MF_FORCESHADOWS = BIT(3), + MF_NOSELFSHADOW = BIT(4), + MF_NOPORTALFOG = BIT(5), // this fog volume won't ever consider a portal fogged out + MF_EDITOR_VISIBLE = BIT(6) // in use (visible) per editor +} materialFlags_t; + +// contents flags, NOTE: make sure to keep the defines in doom_defs.script up to date with these! +typedef enum { + CONTENTS_SOLID = BIT(0), // an eye is never valid in a solid + CONTENTS_OPAQUE = BIT(1), // blocks visibility (for ai) + CONTENTS_WATER = BIT(2), // used for water + CONTENTS_PLAYERCLIP = BIT(3), // solid to players + CONTENTS_MONSTERCLIP = BIT(4), // solid to monsters + CONTENTS_MOVEABLECLIP = BIT(5), // solid to moveable entities + CONTENTS_IKCLIP = BIT(6), // solid to IK + CONTENTS_BLOOD = BIT(7), // used to detect blood decals + CONTENTS_BODY = BIT(8), // used for actors + CONTENTS_PROJECTILE = BIT(9), // used for projectiles + CONTENTS_CORPSE = BIT(10), // used for dead bodies + CONTENTS_RENDERMODEL = BIT(11), // used for render models for collision detection + CONTENTS_TRIGGER = BIT(12), // used for triggers + CONTENTS_AAS_SOLID = BIT(13), // solid for AAS + CONTENTS_AAS_OBSTACLE = BIT(14), // used to compile an obstacle into AAS that can be enabled/disabled + CONTENTS_FLASHLIGHT_TRIGGER = BIT(15), // used for triggers that are activated by the flashlight + + // contents used by utils + CONTENTS_AREAPORTAL = BIT(20), // portal separating renderer areas + CONTENTS_NOCSG = BIT(21), // don't cut this brush with CSG operations in the editor + + CONTENTS_REMOVE_UTIL = ~(CONTENTS_AREAPORTAL|CONTENTS_NOCSG) +} contentsFlags_t; + +// surface types +const int NUM_SURFACE_BITS = 4; +const int MAX_SURFACE_TYPES = 1 << NUM_SURFACE_BITS; + +typedef enum { + SURFTYPE_NONE, // default type + SURFTYPE_METAL, + SURFTYPE_STONE, + SURFTYPE_FLESH, + SURFTYPE_WOOD, + SURFTYPE_CARDBOARD, + SURFTYPE_LIQUID, + SURFTYPE_GLASS, + SURFTYPE_PLASTIC, + SURFTYPE_RICOCHET, + SURFTYPE_10, + SURFTYPE_11, + SURFTYPE_12, + SURFTYPE_13, + SURFTYPE_14, + SURFTYPE_15 +} surfTypes_t; + +// surface flags +typedef enum { + SURF_TYPE_BIT0 = BIT(0), // encodes the material type (metal, flesh, concrete, etc.) + SURF_TYPE_BIT1 = BIT(1), // " + SURF_TYPE_BIT2 = BIT(2), // " + SURF_TYPE_BIT3 = BIT(3), // " + SURF_TYPE_MASK = ( 1 << NUM_SURFACE_BITS ) - 1, + + SURF_NODAMAGE = BIT(4), // never give falling damage + SURF_SLICK = BIT(5), // effects game physics + SURF_COLLISION = BIT(6), // collision surface + SURF_LADDER = BIT(7), // player can climb up this surface + SURF_NOIMPACT = BIT(8), // don't make missile explosions + SURF_NOSTEPS = BIT(9), // no footstep sounds + SURF_DISCRETE = BIT(10), // not clipped or merged by utilities + SURF_NOFRAGMENT = BIT(11), // dmap won't cut surface at each bsp boundary + SURF_NULLNORMAL = BIT(12) // renderbump will draw this surface as 0x80 0x80 0x80, which + // won't collect light from any angle +} surfaceFlags_t; + +class idSoundEmitter; + +class idMaterial : public idDecl { +public: + idMaterial(); + virtual ~idMaterial(); + + virtual size_t Size() const; + virtual bool SetDefaultText(); + virtual const char *DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void Print() const; + + //BSM Nerve: Added for material editor + bool Save( const char *fileName = NULL ); + + // returns the internal image name for stage 0, which can be used + // for the renderer CaptureRenderToImage() call + // I'm not really sure why this needs to be virtual... + virtual const char *ImageName() const; + + void ReloadImages( bool force ) const; + + // returns number of stages this material contains + const int GetNumStages() const { return numStages; } + + // if the material is simple, all that needs to be known are + // the images for drawing. + // These will either all return valid images, or all return NULL + idImage * GetFastPathBumpImage() const { return fastPathBumpImage; }; + idImage * GetFastPathDiffuseImage() const { return fastPathDiffuseImage; }; + idImage * GetFastPathSpecularImage() const { return fastPathSpecularImage; }; + + // get a specific stage + const shaderStage_t *GetStage( const int index ) const { assert(index >= 0 && index < numStages); return &stages[index]; } + + // get the first bump map stage, or NULL if not present. + // used for bumpy-specular + const shaderStage_t *GetBumpStage() const; + + // returns true if the material will draw anything at all. Triggers, portals, + // etc, will not have anything to draw. A not drawn surface can still castShadow, + // which can be used to make a simplified shadow hull for a complex object set + // as noShadow + bool IsDrawn() const { return ( numStages > 0 || entityGui != 0 || gui != NULL ); } + + // returns true if the material will draw any non light interaction stages + bool HasAmbient() const { return ( numAmbientStages > 0 ); } + + // returns true if material has a gui + bool HasGui() const { return ( entityGui != 0 || gui != NULL ); } + + // returns true if the material will generate another view, either as + // a mirror or dynamic rendered image + bool HasSubview() const { return hasSubview; } + + // returns true if the material will generate shadows, not making a + // distinction between global and no-self shadows + bool SurfaceCastsShadow() const { return TestMaterialFlag( MF_FORCESHADOWS ) || !TestMaterialFlag( MF_NOSHADOWS ); } + + // returns true if the material will generate interactions with fog/blend lights + // All non-translucent surfaces receive fog unless they are explicitly noFog + bool ReceivesFog() const { return ( IsDrawn() && !noFog && coverage != MC_TRANSLUCENT ); } + + // returns true if the material will generate interactions with normal lights + // Many special effect surfaces don't have any bump/diffuse/specular + // stages, and don't interact with lights at all + bool ReceivesLighting() const { return numAmbientStages != numStages; } + + // returns true if the material should generate interactions on sides facing away + // from light centers, as with noshadow and noselfshadow options + bool ReceivesLightingOnBackSides() const { return ( materialFlags & (MF_NOSELFSHADOW|MF_NOSHADOWS) ) != 0; } + + // Standard two-sided triangle rendering won't work with bump map lighting, because + // the normal and tangent vectors won't be correct for the back sides. When two + // sided lighting is desired. typically for alpha tested surfaces, this is + // addressed by having CleanupModelSurfaces() create duplicates of all the triangles + // with apropriate order reversal. + bool ShouldCreateBackSides() const { return shouldCreateBackSides; } + + // characters and models that are created by a complete renderbump can use a faster + // method of tangent and normal vector generation than surfaces which have a flat + // renderbump wrapped over them. + bool UseUnsmoothedTangents() const { return unsmoothedTangents; } + + // by default, monsters can have blood overlays placed on them, but this can + // be overrided on a per-material basis with the "noOverlays" material command. + // This will always return false for translucent surfaces + bool AllowOverlays() const { return allowOverlays; } + + // MC_OPAQUE, MC_PERFORATED, or MC_TRANSLUCENT, for interaction list linking and + // dmap flood filling + // The depth buffer will not be filled for MC_TRANSLUCENT surfaces + // FIXME: what do nodraw surfaces return? + materialCoverage_t Coverage() const { return coverage; } + + // returns true if this material takes precedence over other in coplanar cases + bool HasHigherDmapPriority( const idMaterial &other ) const { return ( IsDrawn() && !other.IsDrawn() ) || + ( Coverage() < other.Coverage() ); } + + // returns a idUserInterface if it has a global gui, or NULL if no gui + idUserInterface * GlobalGui() const { return gui; } + + // a discrete surface will never be merged with other surfaces by dmap, which is + // necessary to prevent mutliple gui surfaces, mirrors, autosprites, and some other + // special effects from being combined into a single surface + // guis, merging sprites or other effects, mirrors and remote views are always discrete + bool IsDiscrete() const { return ( entityGui || gui || deform != DFRM_NONE || sort == SS_SUBVIEW || + ( surfaceFlags & SURF_DISCRETE ) != 0 ); } + + // Normally, dmap chops each surface by every BSP boundary, then reoptimizes. + // For gigantic polygons like sky boxes, this can cause a huge number of planar + // triangles that make the optimizer take forever to turn back into a single + // triangle. The "noFragment" option causes dmap to only break the polygons at + // area boundaries, instead of every BSP boundary. This has the negative effect + // of not automatically fixing up interpenetrations, so when this is used, you + // should manually make the edges of your sky box exactly meet, instead of poking + // into each other. + bool NoFragment() const { return ( surfaceFlags & SURF_NOFRAGMENT ) != 0; } + + //------------------------------------------------------------------ + // light shader specific functions, only called for light entities + + // lightshader option to fill with fog from viewer instead of light from center + bool IsFogLight() const { return fogLight; } + + // perform simple blending of the projection, instead of interacting with bumps and textures + bool IsBlendLight() const { return blendLight; } + + // an ambient light has non-directional bump mapping and no specular + bool IsAmbientLight() const { return ambientLight; } + + // implicitly no-shadows lights (ambients, fogs, etc) will never cast shadows + // but individual light entities can also override this value + bool LightCastsShadows() const { return TestMaterialFlag( MF_FORCESHADOWS ) || + ( !fogLight && !ambientLight && !blendLight && !TestMaterialFlag( MF_NOSHADOWS ) ); } + + // fog lights, blend lights, ambient lights, etc will all have to have interaction + // triangles generated for sides facing away from the light as well as those + // facing towards the light. It is debatable if noshadow lights should effect back + // sides, making everything "noSelfShadow", but that would make noshadow lights + // potentially slower than normal lights, which detracts from their optimization + // ability, so they currently do not. + bool LightEffectsBackSides() const { return fogLight || ambientLight || blendLight; } + + // NULL unless an image is explicitly specified in the shader with "lightFalloffShader " + idImage * LightFalloffImage() const { return lightFalloffImage; } + + //------------------------------------------------------------------ + + // returns the renderbump command line for this shader, or an empty string if not present + const char * GetRenderBump() const { return renderBump; }; + + // set specific material flag(s) + void SetMaterialFlag( const int flag ) const { materialFlags |= flag; } + + // clear specific material flag(s) + void ClearMaterialFlag( const int flag ) const { materialFlags &= ~flag; } + + // test for existance of specific material flag(s) + bool TestMaterialFlag( const int flag ) const { return ( materialFlags & flag ) != 0; } + + // get content flags + const int GetContentFlags() const { return contentFlags; } + + // get surface flags + const int GetSurfaceFlags() const { return surfaceFlags; } + + // gets name for surface type (stone, metal, flesh, etc.) + const surfTypes_t GetSurfaceType() const { return static_cast( surfaceFlags & SURF_TYPE_MASK ); } + + // get material description + const char * GetDescription() const { return desc; } + + // get sort order + const float GetSort() const { return sort; } + + const int GetStereoEye() const { return stereoEye; } + + // this is only used by the gui system to force sorting order + // on images referenced from tga's instead of materials. + // this is done this way as there are 2000 tgas the guis use + void SetSort( float s ) const { sort = s; }; + + // DFRM_NONE, DFRM_SPRITE, etc + deform_t Deform() const { return deform; } + + // flare size, expansion size, etc + const int GetDeformRegister( int index ) const { return deformRegisters[index]; } + + // particle system to emit from surface and table for turbulent + const idDecl *GetDeformDecl() const { return deformDecl; } + + // currently a surface can only have one unique texgen for all the stages + texgen_t Texgen() const; + + // wobble sky parms + const int * GetTexGenRegisters() const { return texGenRegisters; } + + // get cull type + const cullType_t GetCullType() const { return cullType; } + + float GetEditorAlpha() const { return editorAlpha; } + + int GetEntityGui() const { return entityGui; } + + decalInfo_t GetDecalInfo() const { return decalInfo; } + + // spectrums are used for "invisible writing" that can only be + // illuminated by a light of matching spectrum + int Spectrum() const { return spectrum; } + + float GetPolygonOffset() const { return polygonOffset; } + + float GetSurfaceArea() const { return surfaceArea; } + void AddToSurfaceArea( float area ) { surfaceArea += area; } + + //------------------------------------------------------------------ + + // returns the length, in milliseconds, of the videoMap on this material, + // or zero if it doesn't have one + int CinematicLength() const; + + void CloseCinematic() const; + + void ResetCinematicTime( int time ) const; + + int GetCinematicStartTime() const; + + void UpdateCinematic( int time ) const; + + //------------------------------------------------------------------ + + // gets an image for the editor to use + idImage * GetEditorImage() const; + int GetImageWidth() const; + int GetImageHeight() const; + + void SetGui( const char *_gui ) const; + + //------------------------------------------------------------------ + + // returns number of registers this material contains + const int GetNumRegisters() const { return numRegisters; } + + // Regs should point to a float array large enough to hold GetNumRegisters() floats. + // FloatTime is passed in because different entities, which may be running in parallel, + // can be in different time groups. + void EvaluateRegisters( + float * registers, + const float localShaderParms[MAX_ENTITY_SHADER_PARMS], + const float globalShaderParms[MAX_GLOBAL_SHADER_PARMS], + const float floatTime, + idSoundEmitter *soundEmitter ) const; + + // if a material only uses constants (no entityParm or globalparm references), this + // will return a pointer to an internal table, and EvaluateRegisters will not need + // to be called. If NULL is returned, EvaluateRegisters must be used. + const float * ConstantRegisters() const { return constantRegisters; }; + + bool SuppressInSubview() const { return suppressInSubview; }; + bool IsPortalSky() const { return portalSky; }; + void AddReference(); + +private: + // parse the entire material + void CommonInit(); + void ParseMaterial( idLexer &src ); + bool MatchToken( idLexer &src, const char *match ); + void ParseSort( idLexer &src ); + void ParseStereoEye( idLexer &src ); + void ParseBlend( idLexer &src, shaderStage_t *stage ); + void ParseVertexParm( idLexer &src, newShaderStage_t *newStage ); + void ParseVertexParm2( idLexer &src, newShaderStage_t *newStage ); + void ParseFragmentMap( idLexer &src, newShaderStage_t *newStage ); + void ParseStage( idLexer &src, const textureRepeat_t trpDefault = TR_REPEAT ); + void ParseDeform( idLexer &src ); + void ParseDecalInfo( idLexer &src ); + bool CheckSurfaceParm( idToken *token ); + int GetExpressionConstant( float f ); + int GetExpressionTemporary(); + expOp_t * GetExpressionOp(); + int EmitOp( int a, int b, expOpType_t opType ); + int ParseEmitOp( idLexer &src, int a, expOpType_t opType, int priority ); + int ParseTerm( idLexer &src ); + int ParseExpressionPriority( idLexer &src, int priority ); + int ParseExpression( idLexer &src ); + void ClearStage( shaderStage_t *ss ); + int NameToSrcBlendMode( const idStr &name ); + int NameToDstBlendMode( const idStr &name ); + void MultiplyTextureMatrix( textureStage_t *ts, int registers[2][3] ); // FIXME: for some reason the const is bad for gcc and Mac + void SortInteractionStages(); + void AddImplicitStages( const textureRepeat_t trpDefault = TR_REPEAT ); + void CheckForConstantRegisters(); + void SetFastPathImages(); + +private: + idStr desc; // description + idStr renderBump; // renderbump command options, without the "renderbump" at the start + + idImage * lightFalloffImage; // only for light shaders + + idImage * fastPathBumpImage; // if any of these are set, they all will be + idImage * fastPathDiffuseImage; + idImage * fastPathSpecularImage; + + int entityGui; // draw a gui with the idUserInterface from the renderEntity_t + // non zero will draw gui, gui2, or gui3 from renderEnitty_t + mutable idUserInterface *gui; // non-custom guis are shared by all users of a material + + bool noFog; // surface does not create fog interactions + + int spectrum; // for invisible writing, used for both lights and surfaces + + float polygonOffset; + + int contentFlags; // content flags + int surfaceFlags; // surface flags + mutable int materialFlags; // material flags + + decalInfo_t decalInfo; + + + mutable float sort; // lower numbered shaders draw before higher numbered + int stereoEye; + deform_t deform; + int deformRegisters[4]; // numeric parameter for deforms + const idDecl *deformDecl; // for surface emitted particle deforms and tables + + int texGenRegisters[MAX_TEXGEN_REGISTERS]; // for wobbleSky + + materialCoverage_t coverage; + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + bool shouldCreateBackSides; + + bool fogLight; + bool blendLight; + bool ambientLight; + bool unsmoothedTangents; + bool hasSubview; // mirror, remote render, etc + bool allowOverlays; + + int numOps; + expOp_t * ops; // evaluate to make expressionRegisters + + int numRegisters; // + float * expressionRegisters; + + float * constantRegisters; // NULL if ops ever reference globalParms or entityParms + + int numStages; + int numAmbientStages; + + shaderStage_t * stages; + + struct mtrParsingData_s *pd; // only used during parsing + + float surfaceArea; // only for listSurfaceAreas + + // we defer loading of the editor image until it is asked for, so the game doesn't load up + // all the invisible and uncompressed images. + // If editorImage is NULL, it will atempt to load editorImageName, and set editorImage to that or defaultImage + idStr editorImageName; + mutable idImage * editorImage; // image used for non-shaded preview + float editorAlpha; + + bool suppressInSubview; + bool portalSky; + int refCount; +}; + +typedef idList idMatList; + +#endif /* !__MATERIAL_H__ */ diff --git a/neo/renderer/Model.cpp b/neo/renderer/Model.cpp new file mode 100644 index 00000000..7093c170 --- /dev/null +++ b/neo/renderer/Model.cpp @@ -0,0 +1,2482 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" +#include "Model_local.h" +#include "Model_ase.h" +#include "Model_lwo.h" +#include "Model_ma.h" + +idCVar idRenderModelStatic::r_mergeModelSurfaces( "r_mergeModelSurfaces", "1", CVAR_BOOL|CVAR_RENDERER, "combine model surfaces with the same material" ); +idCVar idRenderModelStatic::r_slopVertex( "r_slopVertex", "0.01", CVAR_RENDERER, "merge xyz coordinates this far apart" ); +idCVar idRenderModelStatic::r_slopTexCoord( "r_slopTexCoord", "0.001", CVAR_RENDERER, "merge texture coordinates this far apart" ); +idCVar idRenderModelStatic::r_slopNormal( "r_slopNormal", "0.02", CVAR_RENDERER, "merge normals that dot less than this" ); + +static const byte BRM_VERSION = 108; +static const unsigned int BRM_MAGIC = ( 'B' << 24 ) | ( 'R' << 16 ) | ( 'M' << 8 ) | BRM_VERSION; + +/* +================ +idRenderModelStatic::idRenderModelStatic +================ +*/ +idRenderModelStatic::idRenderModelStatic() { + name = ""; + bounds.Clear(); + lastModifiedFrame = 0; + lastArchivedFrame = 0; + overlaysAdded = 0; + isStaticWorldModel = false; + defaulted = false; + purged = false; + fastLoad = false; + reloadable = true; + levelLoadReferenced = false; + hasDrawingSurfaces = true; + hasInteractingSurfaces = true; + hasShadowCastingSurfaces = true; + timeStamp = 0; + numInvertedJoints = 0; + jointsInverted = NULL; + jointsInvertedBuffer = 0; +} + +/* +================ +idRenderModelStatic::~idRenderModelStatic +================ +*/ +idRenderModelStatic::~idRenderModelStatic() { + PurgeModel(); +} + +/* +============== +idRenderModelStatic::Print +============== +*/ +void idRenderModelStatic::Print() const { + common->Printf( "%s\n", name.c_str() ); + common->Printf( "Static model.\n" ); + common->Printf( "bounds: (%f %f %f) to (%f %f %f)\n", + bounds[0][0], bounds[0][1], bounds[0][2], + bounds[1][0], bounds[1][1], bounds[1][2] ); + + common->Printf( " verts tris material\n" ); + for ( int i = 0; i < NumSurfaces(); i++ ) { + const modelSurface_t *surf = Surface( i ); + + srfTriangles_t *tri = surf->geometry; + const idMaterial *material = surf->shader; + + if ( !tri ) { + common->Printf( "%2i: %s, NULL surface geometry\n", i, material->GetName() ); + continue; + } + + common->Printf( "%2i: %5i %5i %s", i, tri->numVerts, tri->numIndexes / 3, material->GetName() ); + if ( tri->generateNormals ) { + common->Printf( " (smoothed)\n" ); + } else { + common->Printf( "\n" ); + } + } +} + +/* +============== +idRenderModelStatic::Memory +============== +*/ +int idRenderModelStatic::Memory() const { + int totalBytes = 0; + + totalBytes += sizeof( *this ); + totalBytes += name.DynamicMemoryUsed(); + totalBytes += surfaces.MemoryUsed(); + + for ( int j = 0; j < NumSurfaces(); j++ ) { + const modelSurface_t *surf = Surface( j ); + if ( !surf->geometry ) { + continue; + } + totalBytes += R_TriSurfMemory( surf->geometry ); + } + + return totalBytes; +} + +/* +============== +idRenderModelStatic::List +============== +*/ +void idRenderModelStatic::List() const { + int totalTris = 0; + int totalVerts = 0; + int totalBytes = 0; + + totalBytes = Memory(); + + char closed = 'C'; + for ( int j = 0; j < NumSurfaces(); j++ ) { + const modelSurface_t *surf = Surface( j ); + if ( !surf->geometry ) { + continue; + } + if ( !surf->geometry->perfectHull ) { + closed = ' '; + } + totalTris += surf->geometry->numIndexes / 3; + totalVerts += surf->geometry->numVerts; + } + common->Printf( "%c%4ik %3i %4i %4i %s", closed, totalBytes/1024, NumSurfaces(), totalVerts, totalTris, Name() ); + + if ( IsDynamicModel() == DM_CACHED ) { + common->Printf( " (DM_CACHED)" ); + } + if ( IsDynamicModel() == DM_CONTINUOUS ) { + common->Printf( " (DM_CONTINUOUS)" ); + } + if ( defaulted ) { + common->Printf( " (DEFAULTED)" ); + } + if ( bounds[0][0] >= bounds[1][0] ) { + common->Printf( " (EMPTY BOUNDS)" ); + } + if ( bounds[1][0] - bounds[0][0] > 100000 ) { + common->Printf( " (HUGE BOUNDS)" ); + } + + common->Printf( "\n" ); +} + +/* +================ +idRenderModelStatic::IsDefaultModel +================ +*/ +bool idRenderModelStatic::IsDefaultModel() const { + return defaulted; +} + +/* +================ +AddCubeFace +================ +*/ +static void AddCubeFace( srfTriangles_t *tri, idVec3 v1, idVec3 v2, idVec3 v3, idVec3 v4 ) { + tri->verts[tri->numVerts+0].Clear(); + tri->verts[tri->numVerts+0].xyz = v1 * 8; + tri->verts[tri->numVerts+0].SetTexCoord( 0, 0 ); + + tri->verts[tri->numVerts+1].Clear(); + tri->verts[tri->numVerts+1].xyz = v2 * 8; + tri->verts[tri->numVerts+1].SetTexCoord( 1, 0 ); + + tri->verts[tri->numVerts+2].Clear(); + tri->verts[tri->numVerts+2].xyz = v3 * 8; + tri->verts[tri->numVerts+2].SetTexCoord( 1, 1 ); + + tri->verts[tri->numVerts+3].Clear(); + tri->verts[tri->numVerts+3].xyz = v4 * 8; + tri->verts[tri->numVerts+3].SetTexCoord( 0, 1 ); + + tri->indexes[tri->numIndexes+0] = tri->numVerts + 0; + tri->indexes[tri->numIndexes+1] = tri->numVerts + 1; + tri->indexes[tri->numIndexes+2] = tri->numVerts + 2; + tri->indexes[tri->numIndexes+3] = tri->numVerts + 0; + tri->indexes[tri->numIndexes+4] = tri->numVerts + 2; + tri->indexes[tri->numIndexes+5] = tri->numVerts + 3; + + tri->numVerts += 4; + tri->numIndexes += 6; +} + +/* +================ +idRenderModelStatic::MakeDefaultModel +================ +*/ +void idRenderModelStatic::MakeDefaultModel() { + + defaulted = true; + + // throw out any surfaces we already have + PurgeModel(); + + // create one new surface + modelSurface_t surf; + + srfTriangles_t *tri = R_AllocStaticTriSurf(); + + surf.shader = tr.defaultMaterial; + surf.geometry = tri; + + R_AllocStaticTriSurfVerts( tri, 24 ); + R_AllocStaticTriSurfIndexes( tri, 36 ); + + AddCubeFace( tri, idVec3(-1, 1, 1), idVec3(1, 1, 1), idVec3(1, -1, 1), idVec3(-1, -1, 1) ); + AddCubeFace( tri, idVec3(-1, 1, -1), idVec3(-1, -1, -1), idVec3(1, -1, -1), idVec3(1, 1, -1) ); + + AddCubeFace( tri, idVec3(1, -1, 1), idVec3(1, 1, 1), idVec3(1, 1, -1), idVec3(1, -1, -1) ); + AddCubeFace( tri, idVec3(-1, -1, 1), idVec3(-1, -1, -1), idVec3(-1, 1, -1), idVec3(-1, 1, 1) ); + + AddCubeFace( tri, idVec3(-1, -1, 1), idVec3(1, -1, 1), idVec3(1, -1, -1), idVec3(-1, -1, -1) ); + AddCubeFace( tri, idVec3(-1, 1, 1), idVec3(-1, 1, -1), idVec3(1, 1, -1), idVec3(1, 1, 1) ); + + tri->generateNormals = true; + + AddSurface( surf ); + FinishSurfaces(); +} + +/* +================ +idRenderModelStatic::PartialInitFromFile +================ +*/ +void idRenderModelStatic::PartialInitFromFile( const char *fileName ) { + fastLoad = true; + InitFromFile( fileName ); +} + +/* +================ +idRenderModelStatic::InitFromFile +================ +*/ +void idRenderModelStatic::InitFromFile( const char *fileName ) { + bool loaded; + idStr extension; + + InitEmpty( fileName ); + + // FIXME: load new .proc map format + + name.ExtractFileExtension( extension ); + + if ( extension.Icmp( "ase" ) == 0 ) { + loaded = LoadASE( name ); + reloadable = true; + } else if ( extension.Icmp( "lwo" ) == 0 ) { + loaded = LoadLWO( name ); + reloadable = true; + } else if ( extension.Icmp( "ma" ) == 0 ) { + loaded = LoadMA( name ); + reloadable = true; + } else { + common->Warning( "idRenderModelStatic::InitFromFile: unknown type for model: \'%s\'", name.c_str() ); + loaded = false; + } + + if ( !loaded ) { + common->Warning( "Couldn't load model: '%s'", name.c_str() ); + MakeDefaultModel(); + return; + } + + // it is now available for use + purged = false; + + // create the bounds for culling and dynamic surface creation + FinishSurfaces(); +} + +/* +======================== +idRenderModelStatic::LoadBinaryModel +======================== +*/ +bool idRenderModelStatic::LoadBinaryModel( idFile * file, const ID_TIME_T sourceTimeStamp ) { + if ( file == NULL ) { + return false; + } + + unsigned int magic = 0; + file->ReadBig( magic ); + if ( magic != BRM_MAGIC ) { + return false; + } + + file->ReadBig( timeStamp ); + + if ( !fileSystem->InProductionMode() && sourceTimeStamp != timeStamp ) { + return false; + } + + common->UpdateLevelLoadPacifier(); + + int numSurfaces; + file->ReadBig( numSurfaces ); + surfaces.SetNum( numSurfaces ); + for ( int i = 0; i < surfaces.Num(); i++ ) { + file->ReadBig( surfaces[i].id ); + idStr materialName; + file->ReadString( materialName ); + if ( materialName.IsEmpty() ) { + surfaces[i].shader = NULL; + } else { + surfaces[i].shader = declManager->FindMaterial( materialName ); + } + + bool isGeometry; + file->ReadBig( isGeometry ); + surfaces[i].geometry = NULL; + if ( isGeometry ) { + bool temp; + + surfaces[i].geometry = R_AllocStaticTriSurf(); + + // Read the contents of srfTriangles_t + srfTriangles_t & tri = *surfaces[i].geometry; + + file->ReadVec3( tri.bounds[0] ); + file->ReadVec3( tri.bounds[1] ); + + int ambientViewCount = 0; // FIXME: remove + file->ReadBig( ambientViewCount ); + file->ReadBig( tri.generateNormals ); + file->ReadBig( tri.tangentsCalculated ); + file->ReadBig( tri.perfectHull ); + file->ReadBig( tri.referencedIndexes ); + + file->ReadBig( tri.numVerts ); + tri.verts = NULL; + int numInFile = 0; + file->ReadBig( numInFile ); + if ( numInFile > 0 ) { + R_AllocStaticTriSurfVerts( &tri, tri.numVerts ); + assert( tri.verts != NULL ); + for ( int j = 0; j < tri.numVerts; j++ ) { + file->ReadVec3( tri.verts[j].xyz ); + file->ReadBigArray( tri.verts[j].st, 2 ); + file->ReadBigArray( tri.verts[j].normal, 4 ); + file->ReadBigArray( tri.verts[j].tangent, 4 ); + file->ReadBigArray( tri.verts[j].color, sizeof( tri.verts[j].color ) / sizeof( tri.verts[j].color[0] ) ); + file->ReadBigArray( tri.verts[j].color2, sizeof( tri.verts[j].color2 ) / sizeof( tri.verts[j].color2[0] ) ); + } + } + + file->ReadBig( numInFile ); + if ( numInFile == 0 ) { + tri.preLightShadowVertexes = NULL; + } else { + R_AllocStaticTriSurfPreLightShadowVerts( &tri, numInFile ); + for ( int j = 0; j < numInFile; j++ ) { + file->ReadVec4( tri.preLightShadowVertexes[ j ].xyzw ); + } + } + + file->ReadBig( tri.numIndexes ); + tri.indexes = NULL; + tri.silIndexes = NULL; + if ( tri.numIndexes > 0 ) { + R_AllocStaticTriSurfIndexes( &tri, tri.numIndexes ); + file->ReadBigArray( tri.indexes, tri.numIndexes ); + } + file->ReadBig( numInFile ); + if ( numInFile > 0 ) { + R_AllocStaticTriSurfSilIndexes( &tri, tri.numIndexes ); + file->ReadBigArray( tri.silIndexes, tri.numIndexes ); + } + + file->ReadBig( tri.numMirroredVerts ); + tri.mirroredVerts = NULL; + if ( tri.numMirroredVerts > 0 ) { + R_AllocStaticTriSurfMirroredVerts( &tri, tri.numMirroredVerts ); + file->ReadBigArray( tri.mirroredVerts, tri.numMirroredVerts ); + } + + file->ReadBig( tri.numDupVerts ); + tri.dupVerts = NULL; + if ( tri.numDupVerts > 0 ) { + R_AllocStaticTriSurfDupVerts( &tri, tri.numDupVerts ); + file->ReadBigArray( tri.dupVerts, tri.numDupVerts * 2 ); + } + + file->ReadBig( tri.numSilEdges ); + tri.silEdges = NULL; + if ( tri.numSilEdges > 0 ) { + R_AllocStaticTriSurfSilEdges( &tri, tri.numSilEdges ); + assert( tri.silEdges != NULL ); + for ( int j = 0; j < tri.numSilEdges; j++ ) { + file->ReadBig( tri.silEdges[j].p1 ); + file->ReadBig( tri.silEdges[j].p2 ); + file->ReadBig( tri.silEdges[j].v1 ); + file->ReadBig( tri.silEdges[j].v2 ); + } + } + + file->ReadBig( temp ); + tri.dominantTris = NULL; + if ( temp ) { + R_AllocStaticTriSurfDominantTris( &tri, tri.numVerts ); + assert( tri.dominantTris != NULL ); + for ( int j = 0; j < tri.numVerts; j++ ) { + file->ReadBig( tri.dominantTris[j].v2 ); + file->ReadBig( tri.dominantTris[j].v3 ); + file->ReadFloat( tri.dominantTris[j].normalizationScale[0] ); + file->ReadFloat( tri.dominantTris[j].normalizationScale[1] ); + file->ReadFloat( tri.dominantTris[j].normalizationScale[2] ); + } + } + + file->ReadBig( tri.numShadowIndexesNoFrontCaps ); + file->ReadBig( tri.numShadowIndexesNoCaps ); + file->ReadBig( tri.shadowCapPlaneBits ); + + tri.ambientSurface = NULL; + tri.nextDeferredFree = NULL; + tri.indexCache = 0; + tri.ambientCache = 0; + tri.shadowCache = 0; + } + } + + file->ReadVec3( bounds[0] ); + file->ReadVec3( bounds[1] ); + + file->ReadBig( overlaysAdded ); + file->ReadBig( lastModifiedFrame ); + file->ReadBig( lastArchivedFrame ); + file->ReadString( name ); + file->ReadBig( isStaticWorldModel ); + file->ReadBig( defaulted ); + file->ReadBig( purged ); + file->ReadBig( fastLoad ); + file->ReadBig( reloadable ); + file->ReadBig( levelLoadReferenced ); // should this actually be saved/loaded? + file->ReadBig( hasDrawingSurfaces ); + file->ReadBig( hasInteractingSurfaces ); + file->ReadBig( hasShadowCastingSurfaces ); + + return true; +} + +/* +======================== +idRenderModelStatic::WriteBinaryModel +======================== +*/ +void idRenderModelStatic::WriteBinaryModel( idFile * file, ID_TIME_T *_timeStamp ) const { + if ( file == NULL ) { + common->Printf( "Failed to WriteBinaryModel\n" ); + return; + } + + file->WriteBig( BRM_MAGIC ); + + if ( _timeStamp != NULL ) { + file->WriteBig( *_timeStamp ); + } else { + file->WriteBig( timeStamp ); + } + + file->WriteBig( surfaces.Num() ); + for ( int i = 0; i < surfaces.Num(); i++ ) { + file->WriteBig( surfaces[i].id ); + if ( surfaces[i].shader != NULL && surfaces[i].shader->GetName() != NULL ) { + file->WriteString( surfaces[i].shader->GetName() ); + } else { + file->WriteString( "" ); + } + + file->WriteBig( surfaces[i].geometry != NULL ); + if ( surfaces[i].geometry != NULL ) { + srfTriangles_t & tri = *surfaces[i].geometry; + + file->WriteVec3( tri.bounds[0] ); + file->WriteVec3( tri.bounds[1] ); + + int ambientViewCount = 0; // FIXME: remove + file->WriteBig( ambientViewCount ); + file->WriteBig( tri.generateNormals ); + file->WriteBig( tri.tangentsCalculated ); + file->WriteBig( tri.perfectHull ); + file->WriteBig( tri.referencedIndexes ); + + // shadow models use numVerts but have no verts + file->WriteBig( tri.numVerts ); + if ( tri.verts != NULL ) { + file->WriteBig( tri.numVerts ); + } else { + file->WriteBig( ( int ) 0 ); + } + + if ( tri.numVerts > 0 && tri.verts != NULL ) { + for ( int j = 0; j < tri.numVerts; j++ ) { + file->WriteVec3( tri.verts[j].xyz ); + file->WriteBigArray( tri.verts[j].st, 2 ); + file->WriteBigArray( tri.verts[j].normal, 4 ); + file->WriteBigArray( tri.verts[j].tangent, 4 ); + file->WriteBigArray( tri.verts[j].color, sizeof( tri.verts[j].color ) / sizeof( tri.verts[j].color[0] ) ); + file->WriteBigArray( tri.verts[j].color2, sizeof( tri.verts[j].color2 ) / sizeof( tri.verts[j].color2[0] ) ); + } + } + + if ( tri.preLightShadowVertexes != NULL ) { + file->WriteBig( tri.numVerts * 2 ); + for ( int j = 0; j < tri.numVerts * 2; j++ ) { + file->WriteVec4( tri.preLightShadowVertexes[ j ].xyzw ); + } + } else { + file->WriteBig( ( int ) 0 ); + } + + file->WriteBig( tri.numIndexes ); + + if ( tri.numIndexes > 0 ) { + file->WriteBigArray( tri.indexes, tri.numIndexes ); + } + + if ( tri.silIndexes != NULL ) { + file->WriteBig( tri.numIndexes ); + } else { + file->WriteBig( ( int ) 0 ); + } + + if ( tri.numIndexes > 0 && tri.silIndexes != NULL ) { + file->WriteBigArray( tri.silIndexes, tri.numIndexes ); + } + + file->WriteBig( tri.numMirroredVerts ); + if ( tri.numMirroredVerts > 0 ) { + file->WriteBigArray( tri.mirroredVerts, tri.numMirroredVerts ); + } + + file->WriteBig( tri.numDupVerts ); + if ( tri.numDupVerts > 0 ) { + file->WriteBigArray( tri.dupVerts, tri.numDupVerts * 2 ); + } + + file->WriteBig( tri.numSilEdges ); + if ( tri.numSilEdges > 0 ) { + for ( int j = 0; j < tri.numSilEdges; j++ ) { + file->WriteBig( tri.silEdges[j].p1 ); + file->WriteBig( tri.silEdges[j].p2 ); + file->WriteBig( tri.silEdges[j].v1 ); + file->WriteBig( tri.silEdges[j].v2 ); + } + } + + file->WriteBig( tri.dominantTris != NULL ); + if ( tri.dominantTris != NULL ) { + for ( int j = 0; j < tri.numVerts; j++ ) { + file->WriteBig( tri.dominantTris[j].v2 ); + file->WriteBig( tri.dominantTris[j].v3 ); + file->WriteFloat( tri.dominantTris[j].normalizationScale[0] ); + file->WriteFloat( tri.dominantTris[j].normalizationScale[1] ); + file->WriteFloat( tri.dominantTris[j].normalizationScale[2] ); + } + } + + file->WriteBig( tri.numShadowIndexesNoFrontCaps ); + file->WriteBig( tri.numShadowIndexesNoCaps ); + file->WriteBig( tri.shadowCapPlaneBits ); + } + } + + file->WriteVec3( bounds[0] ); + file->WriteVec3( bounds[1] ); + file->WriteBig( overlaysAdded ); + file->WriteBig( lastModifiedFrame ); + file->WriteBig( lastArchivedFrame ); + file->WriteString( name ); + + // shadowHull + + file->WriteBig( isStaticWorldModel ); + file->WriteBig( defaulted ); + file->WriteBig( purged ); + file->WriteBig( fastLoad ); + file->WriteBig( reloadable ); + file->WriteBig( levelLoadReferenced ); + file->WriteBig( hasDrawingSurfaces ); + file->WriteBig( hasInteractingSurfaces ); + file->WriteBig( hasShadowCastingSurfaces ); +} + +/* +================ +idRenderModelStatic::LoadModel +================ +*/ +void idRenderModelStatic::LoadModel() { + PurgeModel(); + InitFromFile( name ); +} + +/* +================ +idRenderModelStatic::InitEmpty +================ +*/ +void idRenderModelStatic::InitEmpty( const char *fileName ) { + // model names of the form _area* are static parts of the + // world, and have already been considered for optimized shadows + // other model names are inline entity models, and need to be + // shadowed normally + if ( !idStr::Cmpn( fileName, "_area", 5 ) ) { + isStaticWorldModel = true; + } else { + isStaticWorldModel = false; + } + + name = fileName; + reloadable = false; // if it didn't come from a file, we can't reload it + PurgeModel(); + purged = false; + bounds.Zero(); +} + +/* +================ +idRenderModelStatic::AddSurface +================ +*/ +void idRenderModelStatic::AddSurface( modelSurface_t surface ) { + surfaces.Append( surface ); + if ( surface.geometry ) { + bounds += surface.geometry->bounds; + } +} + +/* +================ +idRenderModelStatic::Name +================ +*/ +const char *idRenderModelStatic::Name() const { + return name; +} + +/* +================ +idRenderModelStatic::Timestamp +================ +*/ +ID_TIME_T idRenderModelStatic::Timestamp() const { + return timeStamp; +} + +/* +================ +idRenderModelStatic::NumSurfaces +================ +*/ +int idRenderModelStatic::NumSurfaces() const { + return surfaces.Num(); +} + +/* +================ +idRenderModelStatic::NumBaseSurfaces +================ +*/ +int idRenderModelStatic::NumBaseSurfaces() const { + return surfaces.Num() - overlaysAdded; +} + +/* +================ +idRenderModelStatic::Surface +================ +*/ +const modelSurface_t *idRenderModelStatic::Surface( int surfaceNum ) const { + return &surfaces[surfaceNum]; +} + +/* +================ +idRenderModelStatic::AllocSurfaceTriangles +================ +*/ +srfTriangles_t *idRenderModelStatic::AllocSurfaceTriangles( int numVerts, int numIndexes ) const { + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, numVerts ); + R_AllocStaticTriSurfIndexes( tri, numIndexes ); + return tri; +} + +/* +================ +idRenderModelStatic::FreeSurfaceTriangles +================ +*/ +void idRenderModelStatic::FreeSurfaceTriangles( srfTriangles_t *tris ) const { + R_FreeStaticTriSurf( tris ); +} + +/* +================ +idRenderModelStatic::IsStaticWorldModel +================ +*/ +bool idRenderModelStatic::IsStaticWorldModel() const { + return isStaticWorldModel; +} + +/* +================ +idRenderModelStatic::IsDynamicModel +================ +*/ +dynamicModel_t idRenderModelStatic::IsDynamicModel() const { + // dynamic subclasses will override this + return DM_STATIC; +} + +/* +================ +idRenderModelStatic::IsReloadable +================ +*/ +bool idRenderModelStatic::IsReloadable() const { + return reloadable; +} + +/* +================ +idRenderModelStatic::Bounds +================ +*/ +idBounds idRenderModelStatic::Bounds( const struct renderEntity_s *mdef ) const { + return bounds; +} + +/* +================ +idRenderModelStatic::DepthHack +================ +*/ +float idRenderModelStatic::DepthHack() const { + return 0.0f; +} + +/* +================ +idRenderModelStatic::InstantiateDynamicModel +================ +*/ +idRenderModel *idRenderModelStatic::InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ) { + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + common->Error( "InstantiateDynamicModel called on static model '%s'", name.c_str() ); + return NULL; +} + +/* +================ +idRenderModelStatic::NumJoints +================ +*/ +int idRenderModelStatic::NumJoints() const { + return 0; +} + +/* +================ +idRenderModelStatic::GetJoints +================ +*/ +const idMD5Joint *idRenderModelStatic::GetJoints() const { + return NULL; +} + +/* +================ +idRenderModelStatic::GetJointHandle +================ +*/ +jointHandle_t idRenderModelStatic::GetJointHandle( const char *name ) const { + return INVALID_JOINT; +} + +/* +================ +idRenderModelStatic::GetJointName +================ +*/ +const char * idRenderModelStatic::GetJointName( jointHandle_t handle ) const { + return ""; +} + +/* +================ +idRenderModelStatic::GetDefaultPose +================ +*/ +const idJointQuat *idRenderModelStatic::GetDefaultPose() const { + return NULL; +} + +/* +================ +idRenderModelStatic::NearestJoint +================ +*/ +int idRenderModelStatic::NearestJoint( int surfaceNum, int a, int b, int c ) const { + return INVALID_JOINT; +} + + +//===================================================================== + + +/* +================ +idRenderModelStatic::FinishSurfaces + +The mergeShadows option allows surfaces with different textures to share +silhouette edges for shadow calculation, instead of leaving shared edges +hanging. + +If any of the original shaders have the noSelfShadow flag set, the surfaces +can't be merged, because they will need to be drawn in different order. + +If there is only one surface, a separate merged surface won't be generated. + +A model with multiple surfaces can't later have a skinned shader change the +state of the noSelfShadow flag. + +----------------- + +Creates mirrored copies of two sided surfaces with normal maps, which would +otherwise light funny. + +Extends the bounds of deformed surfaces so they don't cull incorrectly at screen edges. + +================ +*/ +void idRenderModelStatic::FinishSurfaces() { + int i; + int totalVerts, totalIndexes; + + hasDrawingSurfaces = false; + hasInteractingSurfaces = false; + hasShadowCastingSurfaces = false; + purged = false; + + // make sure we don't have a huge bounds even if we don't finish everything + bounds.Zero(); + + + if ( surfaces.Num() == 0 ) { + return; + } + + // renderBump doesn't care about most of this + if ( fastLoad ) { + bounds.Zero(); + for ( i = 0; i < surfaces.Num(); i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + R_BoundTriSurf( surf->geometry ); + bounds.AddBounds( surf->geometry->bounds ); + } + + return; + } + + // cleanup all the final surfaces, but don't create sil edges + totalVerts = 0; + totalIndexes = 0; + + // decide if we are going to merge all the surfaces into one shadower + int numOriginalSurfaces = surfaces.Num(); + + // make sure there aren't any NULL shaders or geometry + for ( i = 0; i < numOriginalSurfaces; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + if ( surf->geometry == NULL || surf->shader == NULL ) { + MakeDefaultModel(); + common->Error( "Model %s, surface %i had NULL geometry", name.c_str(), i ); + } + if ( surf->shader == NULL ) { + MakeDefaultModel(); + common->Error( "Model %s, surface %i had NULL shader", name.c_str(), i ); + } + } + + // duplicate and reverse triangles for two sided bump mapped surfaces + // note that this won't catch surfaces that have their shaders dynamically + // changed, and won't work with animated models. + // It is better to create completely separate surfaces, rather than + // add vertexes and indexes to the existing surface, because the + // tangent generation wouldn't like the acute shared edges + for ( i = 0; i < numOriginalSurfaces; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + if ( surf->shader->ShouldCreateBackSides() ) { + srfTriangles_t *newTri; + + newTri = R_CopyStaticTriSurf( surf->geometry ); + R_ReverseTriangles( newTri ); + + modelSurface_t newSurf; + + newSurf.shader = surf->shader; + newSurf.geometry = newTri; + + AddSurface( newSurf ); + } + } + + // clean the surfaces + for ( i = 0; i < surfaces.Num(); i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + R_CleanupTriangles( surf->geometry, surf->geometry->generateNormals, true, surf->shader->UseUnsmoothedTangents() ); + if ( surf->shader->SurfaceCastsShadow() ) { + totalVerts += surf->geometry->numVerts; + totalIndexes += surf->geometry->numIndexes; + } + } + + // add up the total surface area for development information + for ( i = 0; i < surfaces.Num(); i++ ) { + const modelSurface_t *surf = &surfaces[i]; + srfTriangles_t *tri = surf->geometry; + + for ( int j = 0; j < tri->numIndexes; j += 3 ) { + float area = idWinding::TriangleArea( tri->verts[tri->indexes[j]].xyz, + tri->verts[tri->indexes[j+1]].xyz, tri->verts[tri->indexes[j+2]].xyz ); + const_cast(surf->shader)->AddToSurfaceArea( area ); + } + } + + // set flags for whole-model rejection + for ( i = 0; i < surfaces.Num(); i++ ) { + const modelSurface_t *surf = &surfaces[i]; + if ( surf->shader->IsDrawn() ) { + hasDrawingSurfaces = true; + } + if ( surf->shader->SurfaceCastsShadow() ) { + hasShadowCastingSurfaces = true; + } + if ( surf->shader->ReceivesLighting() ) { + hasInteractingSurfaces = true; + } + if ( strstr( surf->shader->GetName(), "trigger" ) ) { + static int breakHere; + breakHere++; + } + } + + // calculate the bounds + if ( surfaces.Num() == 0 ) { + bounds.Zero(); + } else { + bounds.Clear(); + for ( i = 0; i < surfaces.Num(); i++ ) { + modelSurface_t *surf = &surfaces[i]; + + // if the surface has a deformation, increase the bounds + // the amount here is somewhat arbitrary, designed to handle + // autosprites and flares, but could be done better with exact + // deformation information. + // Note that this doesn't handle deformations that are skinned in + // at run time... + if ( surf->shader->Deform() != DFRM_NONE ) { + srfTriangles_t *tri = surf->geometry; + idVec3 mid = ( tri->bounds[1] + tri->bounds[0] ) * 0.5f; + float radius = ( tri->bounds[0] - mid ).Length(); + radius += 20.0f; + + tri->bounds[0][0] = mid[0] - radius; + tri->bounds[0][1] = mid[1] - radius; + tri->bounds[0][2] = mid[2] - radius; + + tri->bounds[1][0] = mid[0] + radius; + tri->bounds[1][1] = mid[1] + radius; + tri->bounds[1][2] = mid[2] + radius; + } + + // add to the model bounds + bounds.AddBounds( surf->geometry->bounds ); + + } + } +} + +/* +================= +idRenderModelStatic::ConvertASEToModelSurfaces +================= +*/ +typedef struct matchVert_s { + struct matchVert_s *next; + int v, tv; + byte color[4]; + idVec3 normal; +} matchVert_t; + +bool idRenderModelStatic::ConvertASEToModelSurfaces( const struct aseModel_s *ase ) { + aseObject_t * object; + aseMesh_t * mesh; + aseMaterial_t * material; + const idMaterial *im1, *im2; + srfTriangles_t *tri; + int objectNum; + int i, j, k; + int v, tv; + int * vRemap; + int * tvRemap; + matchVert_t * mvTable; // all of the match verts + matchVert_t ** mvHash; // points inside mvTable for each xyz index + matchVert_t * lastmv; + matchVert_t * mv; + idVec3 normal; + float uOffset, vOffset, textureSin, textureCos; + float uTiling, vTiling; + int * mergeTo; + byte * color; + static byte identityColor[4] = { 255, 255, 255, 255 }; + modelSurface_t surf, *modelSurf; + + if ( !ase ) { + return false; + } + if ( ase->objects.Num() < 1 ) { + return false; + } + + timeStamp = ase->timeStamp; + + // the modeling programs can save out multiple surfaces with a common + // material, but we would like to mege them together where possible + // meaning that this->NumSurfaces() <= ase->objects.currentElements + mergeTo = (int *)_alloca( ase->objects.Num() * sizeof( *mergeTo ) ); + surf.geometry = NULL; + if ( ase->materials.Num() == 0 ) { + // if we don't have any materials, dump everything into a single surface + surf.shader = tr.defaultMaterial; + surf.id = 0; + this->AddSurface( surf ); + for ( i = 0; i < ase->objects.Num(); i++ ) { + mergeTo[i] = 0; + } + } else if ( !r_mergeModelSurfaces.GetBool() ) { + // don't merge any + for ( i = 0; i < ase->objects.Num(); i++ ) { + mergeTo[i] = i; + object = ase->objects[i]; + material = ase->materials[object->materialRef]; + surf.shader = declManager->FindMaterial( material->name ); + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } else { + // search for material matches + for ( i = 0; i < ase->objects.Num(); i++ ) { + object = ase->objects[i]; + material = ase->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + if ( im1->IsDiscrete() ) { + // flares, autosprites, etc + j = this->NumSurfaces(); + } else { + for ( j = 0; j < this->NumSurfaces(); j++ ) { + modelSurf = &this->surfaces[j]; + im2 = modelSurf->shader; + if ( im1 == im2 ) { + // merge this + mergeTo[i] = j; + break; + } + } + } + if ( j == this->NumSurfaces() ) { + // didn't merge + mergeTo[i] = j; + surf.shader = im1; + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } + } + + idVectorSubset vertexSubset; + idVectorSubset texCoordSubset; + + // build the surfaces + for ( objectNum = 0; objectNum < ase->objects.Num(); objectNum++ ) { + object = ase->objects[objectNum]; + mesh = &object->mesh; + material = ase->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + + bool normalsParsed = mesh->normalsParsed; + + // completely ignore any explict normals on surfaces with a renderbump command + // which will guarantee the best contours and least vertexes. + const char *rb = im1->GetRenderBump(); + if ( rb != NULL && rb[0] != NULL ) { + normalsParsed = false; + } + + // It seems like the tools our artists are using often generate + // verts and texcoords slightly separated that should be merged + // note that we really should combine the surfaces with common materials + // before doing this operation, because we can miss a slop combination + // if they are in different surfaces + + vRemap = (int *)R_StaticAlloc( mesh->numVertexes * sizeof( vRemap[0] ), TAG_MODEL ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = j; + } + } else { + float vertexEpsilon = r_slopVertex.GetFloat(); + float expand = 2 * 32 * vertexEpsilon; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->vertexes, mesh->numVertexes ); + mins -= idVec3( expand, expand, expand ); + maxs += idVec3( expand, expand, expand ); + vertexSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = vertexSubset.FindVector( mesh->vertexes, j, vertexEpsilon ); + } + } + + tvRemap = (int *)R_StaticAlloc( mesh->numTVertexes * sizeof( tvRemap[0] ), TAG_MODEL ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = j; + } + } else { + float texCoordEpsilon = r_slopTexCoord.GetFloat(); + float expand = 2 * 32 * texCoordEpsilon; + idVec2 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->tvertexes, mesh->numTVertexes ); + mins -= idVec2( expand, expand ); + maxs += idVec2( expand, expand ); + texCoordSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = texCoordSubset.FindVector( mesh->tvertexes, j, texCoordEpsilon ); + } + } + + // we need to find out how many unique vertex / texcoord combinations + // there are, because ASE tracks them separately but we need them unified + + // the maximum possible number of combined vertexes is the number of indexes + mvTable = (matchVert_t *)R_ClearedStaticAlloc( mesh->numFaces * 3 * sizeof( mvTable[0] ) ); + + // we will have a hash chain based on the xyz values + mvHash = (matchVert_t **)R_ClearedStaticAlloc( mesh->numVertexes * sizeof( mvHash[0] ) ); + + // allocate triangle surface + tri = R_AllocStaticTriSurf(); + tri->numVerts = 0; + tri->numIndexes = 0; + R_AllocStaticTriSurfIndexes( tri, mesh->numFaces * 3 ); + tri->generateNormals = !normalsParsed; + + // init default normal, color and tex coord index + normal.Zero(); + color = identityColor; + tv = 0; + + // find all the unique combinations + float normalEpsilon = 1.0f - r_slopNormal.GetFloat(); + for ( j = 0; j < mesh->numFaces; j++ ) { + for ( k = 0; k < 3; k++ ) { + v = mesh->faces[j].vertexNum[k]; + + if ( v < 0 || v >= mesh->numVertexes ) { + common->Error( "ConvertASEToModelSurfaces: bad vertex index in ASE file %s", name.c_str() ); + } + + // collapse the position if it was slightly offset + v = vRemap[v]; + + // we may or may not have texcoords to compare + if ( mesh->numTVFaces == mesh->numFaces && mesh->numTVertexes != 0 ) { + tv = mesh->faces[j].tVertexNum[k]; + if ( tv < 0 || tv >= mesh->numTVertexes ) { + common->Error( "ConvertASEToModelSurfaces: bad tex coord index in ASE file %s", name.c_str() ); + } + // collapse the tex coord if it was slightly offset + tv = tvRemap[tv]; + } + + // we may or may not have normals to compare + if ( normalsParsed ) { + normal = mesh->faces[j].vertexNormals[k]; + } + + // we may or may not have colors to compare + if ( mesh->colorsParsed ) { + color = mesh->faces[j].vertexColors[k]; + } + + // find a matching vert + for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) { + if ( mv->tv != tv ) { + continue; + } + if ( *(unsigned *)mv->color != *(unsigned *)color ) { + continue; + } + if ( !normalsParsed ) { + // if we are going to create the normals, just + // matching texcoords is enough + break; + } + if ( mv->normal * normal > normalEpsilon ) { + break; // we already have this one + } + } + if ( !mv ) { + // allocate a new match vert and link to hash chain + mv = &mvTable[ tri->numVerts ]; + mv->v = v; + mv->tv = tv; + mv->normal = normal; + *(unsigned *)mv->color = *(unsigned *)color; + mv->next = NULL; + if ( lastmv ) { + lastmv->next = mv; + } else { + mvHash[v] = mv; + } + tri->numVerts++; + } + + tri->indexes[tri->numIndexes] = mv - mvTable; + tri->numIndexes++; + } + } + + // allocate space for the indexes and copy them + if ( tri->numIndexes > mesh->numFaces * 3 ) { + common->FatalError( "ConvertASEToModelSurfaces: index miscount in ASE file %s", name.c_str() ); + } + if ( tri->numVerts > mesh->numFaces * 3 ) { + common->FatalError( "ConvertASEToModelSurfaces: vertex miscount in ASE file %s", name.c_str() ); + } + + // an ASE allows the texture coordinates to be scaled, translated, and rotated + if ( ase->materials.Num() == 0 ) { + uOffset = vOffset = 0.0f; + uTiling = vTiling = 1.0f; + textureSin = 0.0f; + textureCos = 1.0f; + } else { + material = ase->materials[object->materialRef]; + uOffset = -material->uOffset; + vOffset = material->vOffset; + uTiling = material->uTiling; + vTiling = material->vTiling; + textureSin = idMath::Sin( material->angle ); + textureCos = idMath::Cos( material->angle ); + } + + // now allocate and generate the combined vertexes + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + + for ( j = 0; j < tri->numVerts; j++ ) { + mv = &mvTable[j]; + tri->verts[ j ].Clear(); + tri->verts[ j ].xyz = mesh->vertexes[ mv->v ]; + tri->verts[ j ].SetNormal( mv->normal ); + *(unsigned *)tri->verts[j].color = *(unsigned *)mv->color; + if ( mesh->numTVFaces == mesh->numFaces && mesh->numTVertexes != 0 ) { + const idVec2 &tv = mesh->tvertexes[ mv->tv ]; + float u = tv.x * uTiling + uOffset; + float v = tv.y * vTiling + vOffset; + tri->verts[j].SetTexCoord( u * textureCos + v * textureSin, u * -textureSin + v * textureCos ); + } + } + + R_StaticFree( mvTable ); + R_StaticFree( mvHash ); + R_StaticFree( tvRemap ); + R_StaticFree( vRemap ); + + // see if we need to merge with a previous surface of the same material + modelSurf = &this->surfaces[mergeTo[ objectNum ]]; + srfTriangles_t *mergeTri = modelSurf->geometry; + if ( !mergeTri ) { + modelSurf->geometry = tri; + } else { + modelSurf->geometry = R_MergeTriangles( mergeTri, tri ); + R_FreeStaticTriSurf( tri ); + R_FreeStaticTriSurf( mergeTri ); + } + } + + return true; +} + +/* +================= +idRenderModelStatic::ConvertLWOToModelSurfaces +================= +*/ +bool idRenderModelStatic::ConvertLWOToModelSurfaces( const struct st_lwObject *lwo ) { + const idMaterial *im1, *im2; + srfTriangles_t *tri; + lwSurface * lwoSurf; + int numTVertexes; + int i, j, k; + int v, tv; + idVec3 * vList; + int * vRemap; + idVec2 * tvList; + int * tvRemap; + matchVert_t * mvTable; // all of the match verts + matchVert_t ** mvHash; // points inside mvTable for each xyz index + matchVert_t * lastmv; + matchVert_t * mv; + idVec3 normal; + int * mergeTo; + byte color[4]; + modelSurface_t surf, *modelSurf; + + if ( !lwo ) { + return false; + } + if ( lwo->surf == NULL ) { + return false; + } + + timeStamp = lwo->timeStamp; + + // count the number of surfaces + i = 0; + for ( lwoSurf = lwo->surf; lwoSurf; lwoSurf = lwoSurf->next ) { + i++; + } + + // the modeling programs can save out multiple surfaces with a common + // material, but we would like to merge them together where possible + mergeTo = (int *)_alloca( i * sizeof( mergeTo[0] ) ); + memset( &surf, 0, sizeof( surf ) ); + + if ( !r_mergeModelSurfaces.GetBool() ) { + // don't merge any + for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) { + mergeTo[i] = i; + surf.shader = declManager->FindMaterial( lwoSurf->name ); + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } else { + // search for material matches + for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) { + im1 = declManager->FindMaterial( lwoSurf->name ); + if ( im1->IsDiscrete() ) { + // flares, autosprites, etc + j = this->NumSurfaces(); + } else { + for ( j = 0; j < this->NumSurfaces(); j++ ) { + modelSurf = &this->surfaces[j]; + im2 = modelSurf->shader; + if ( im1 == im2 ) { + // merge this + mergeTo[i] = j; + break; + } + } + } + if ( j == this->NumSurfaces() ) { + // didn't merge + mergeTo[i] = j; + surf.shader = im1; + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } + } + + idVectorSubset vertexSubset; + idVectorSubset texCoordSubset; + + // we only ever use the first layer + lwLayer *layer = lwo->layer; + + // vertex positions + if ( layer->point.count <= 0 ) { + common->Warning( "ConvertLWOToModelSurfaces: model \'%s\' has bad or missing vertex data", name.c_str() ); + return false; + } + + vList = (idVec3 *)R_StaticAlloc( layer->point.count * sizeof( vList[0] ), TAG_MODEL ); + for ( j = 0; j < layer->point.count; j++ ) { + vList[j].x = layer->point.pt[j].pos[0]; + vList[j].y = layer->point.pt[j].pos[2]; + vList[j].z = layer->point.pt[j].pos[1]; + } + + // vertex texture coords + numTVertexes = 0; + + if ( layer->nvmaps ) { + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + numTVertexes += vm->nverts; + } + } + } + + if ( numTVertexes ) { + tvList = (idVec2 *)Mem_Alloc( numTVertexes * sizeof( tvList[0] ), TAG_MODEL ); + int offset = 0; + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + vm->offset = offset; + for ( k = 0; k < vm->nverts; k++ ) { + tvList[k + offset].x = vm->val[k][0]; + tvList[k + offset].y = 1.0f - vm->val[k][1]; // invert the t + } + offset += vm->nverts; + } + } + } else { + common->Warning( "ConvertLWOToModelSurfaces: model \'%s\' has bad or missing uv data", name.c_str() ); + numTVertexes = 1; + tvList = (idVec2 *)Mem_ClearedAlloc( numTVertexes * sizeof( tvList[0] ), TAG_MODEL ); + } + + // It seems like the tools our artists are using often generate + // verts and texcoords slightly separated that should be merged + // note that we really should combine the surfaces with common materials + // before doing this operation, because we can miss a slop combination + // if they are in different surfaces + + vRemap = (int *)R_StaticAlloc( layer->point.count * sizeof( vRemap[0] ), TAG_MODEL ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < layer->point.count; j++ ) { + vRemap[j] = j; + } + } else { + float vertexEpsilon = r_slopVertex.GetFloat(); + float expand = 2 * 32 * vertexEpsilon; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, vList, layer->point.count ); + mins -= idVec3( expand, expand, expand ); + maxs += idVec3( expand, expand, expand ); + vertexSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < layer->point.count; j++ ) { + vRemap[j] = vertexSubset.FindVector( vList, j, vertexEpsilon ); + } + } + + tvRemap = (int *)R_StaticAlloc( numTVertexes * sizeof( tvRemap[0] ), TAG_MODEL ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < numTVertexes; j++ ) { + tvRemap[j] = j; + } + } else { + float texCoordEpsilon = r_slopTexCoord.GetFloat(); + float expand = 2 * 32 * texCoordEpsilon; + idVec2 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, tvList, numTVertexes ); + mins -= idVec2( expand, expand ); + maxs += idVec2( expand, expand ); + texCoordSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < numTVertexes; j++ ) { + tvRemap[j] = texCoordSubset.FindVector( tvList, j, texCoordEpsilon ); + } + } + + // build the surfaces + for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) { + im1 = declManager->FindMaterial( lwoSurf->name ); + + bool normalsParsed = true; + + // completely ignore any explict normals on surfaces with a renderbump command + // which will guarantee the best contours and least vertexes. + const char *rb = im1->GetRenderBump(); + if ( rb && rb[0] ) { + normalsParsed = false; + } + + // we need to find out how many unique vertex / texcoord combinations there are + + // the maximum possible number of combined vertexes is the number of indexes + mvTable = (matchVert_t *)R_ClearedStaticAlloc( layer->polygon.count * 3 * sizeof( mvTable[0] ) ); + + // we will have a hash chain based on the xyz values + mvHash = (matchVert_t **)R_ClearedStaticAlloc( layer->point.count * sizeof( mvHash[0] ) ); + + // allocate triangle surface + tri = R_AllocStaticTriSurf(); + tri->numVerts = 0; + tri->numIndexes = 0; + R_AllocStaticTriSurfIndexes( tri, layer->polygon.count * 3 ); + tri->generateNormals = !normalsParsed; + + // find all the unique combinations + float normalEpsilon; + if ( fastLoad ) { + normalEpsilon = 1.0f; // don't merge unless completely exact + } else { + normalEpsilon = 1.0f - r_slopNormal.GetFloat(); + } + for ( j = 0; j < layer->polygon.count; j++ ) { + lwPolygon *poly = &layer->polygon.pol[j]; + + if ( poly->surf != lwoSurf ) { + continue; + } + + if ( poly->nverts != 3 ) { + common->Warning( "ConvertLWOToModelSurfaces: model %s has too many verts for a poly! Make sure you triplet it down", name.c_str() ); + continue; + } + + for ( k = 0; k < 3; k++ ) { + + v = vRemap[poly->v[k].index]; + + normal.x = poly->v[k].norm[0]; + normal.y = poly->v[k].norm[2]; + normal.z = poly->v[k].norm[1]; + + // LWO models aren't all that pretty when it comes down to the floating point values they store + normal.FixDegenerateNormal(); + + tv = 0; + + color[0] = lwoSurf->color.rgb[0] * 255; + color[1] = lwoSurf->color.rgb[1] * 255; + color[2] = lwoSurf->color.rgb[2] * 255; + color[3] = 255; + + // first set attributes from the vertex + lwPoint *pt = &layer->point.pt[poly->v[k].index]; + int nvm; + for ( nvm = 0; nvm < pt->nvmaps; nvm++ ) { + lwVMapPt *vm = &pt->vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + tv = tvRemap[vm->index + vm->vmap->offset]; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + color[chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + + // then override with polygon attributes + for ( nvm = 0; nvm < poly->v[k].nvmaps; nvm++ ) { + lwVMapPt *vm = &poly->v[k].vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + tv = tvRemap[vm->index + vm->vmap->offset]; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + color[chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + + // find a matching vert + for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) { + if ( mv->tv != tv ) { + continue; + } + if ( *(unsigned *)mv->color != *(unsigned *)color ) { + continue; + } + if ( !normalsParsed ) { + // if we are going to create the normals, just + // matching texcoords is enough + break; + } + if ( mv->normal * normal > normalEpsilon ) { + break; // we already have this one + } + } + if ( !mv ) { + // allocate a new match vert and link to hash chain + mv = &mvTable[ tri->numVerts ]; + mv->v = v; + mv->tv = tv; + mv->normal = normal; + *(unsigned *)mv->color = *(unsigned *)color; + mv->next = NULL; + if ( lastmv ) { + lastmv->next = mv; + } else { + mvHash[v] = mv; + } + tri->numVerts++; + } + + tri->indexes[tri->numIndexes] = mv - mvTable; + tri->numIndexes++; + } + } + + // allocate space for the indexes and copy them + if ( tri->numIndexes > layer->polygon.count * 3 ) { + common->FatalError( "ConvertLWOToModelSurfaces: index miscount in LWO file %s", name.c_str() ); + } + if ( tri->numVerts > layer->polygon.count * 3 ) { + common->FatalError( "ConvertLWOToModelSurfaces: vertex miscount in LWO file %s", name.c_str() ); + } + + // now allocate and generate the combined vertexes + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + + for ( j = 0; j < tri->numVerts; j++ ) { + mv = &mvTable[j]; + tri->verts[ j ].Clear(); + tri->verts[ j ].xyz = vList[ mv->v ]; + tri->verts[ j ].SetTexCoord( tvList[ mv->tv ] ); + tri->verts[ j ].SetNormal( mv->normal ); + *(unsigned *)tri->verts[j].color = *(unsigned *)mv->color; + } + + R_StaticFree( mvTable ); + R_StaticFree( mvHash ); + + // see if we need to merge with a previous surface of the same material + modelSurf = &this->surfaces[mergeTo[ i ]]; + srfTriangles_t *mergeTri = modelSurf->geometry; + if ( !mergeTri ) { + modelSurf->geometry = tri; + } else { + modelSurf->geometry = R_MergeTriangles( mergeTri, tri ); + R_FreeStaticTriSurf( tri ); + R_FreeStaticTriSurf( mergeTri ); + } + } + + R_StaticFree( tvRemap ); + R_StaticFree( vRemap ); + R_StaticFree( tvList ); + R_StaticFree( vList ); + + return true; +} + +/* +================= +idRenderModelStatic::ConvertLWOToASE +================= +*/ +struct aseModel_s *idRenderModelStatic::ConvertLWOToASE( const struct st_lwObject *obj, const char *fileName ) { + int j, k; + aseModel_t *ase; + + if ( !obj ) { + return NULL; + } + + // NOTE: using new operator because aseModel_t contains idList class objects + ase = new (TAG_MODEL) aseModel_t; + ase->timeStamp = obj->timeStamp; + ase->objects.Resize( obj->nlayers, obj->nlayers ); + + int materialRef = 0; + + for ( lwSurface *surf = obj->surf; surf; surf = surf->next ) { + + aseMaterial_t *mat = (aseMaterial_t *)Mem_ClearedAlloc( sizeof( *mat ), TAG_MODEL ); + strcpy( mat->name, surf->name ); + mat->uTiling = mat->vTiling = 1; + mat->angle = mat->uOffset = mat->vOffset = 0; + ase->materials.Append( mat ); + + lwLayer *layer = obj->layer; + + aseObject_t *object = (aseObject_t *)Mem_ClearedAlloc( sizeof( *object ), TAG_MODEL ); + object->materialRef = materialRef++; + + aseMesh_t *mesh = &object->mesh; + ase->objects.Append( object ); + + mesh->numFaces = layer->polygon.count; + mesh->numTVFaces = mesh->numFaces; + mesh->faces = (aseFace_t *)Mem_Alloc( mesh->numFaces * sizeof( mesh->faces[0] ), TAG_MODEL ); + + mesh->numVertexes = layer->point.count; + mesh->vertexes = (idVec3 *)Mem_Alloc( mesh->numVertexes * sizeof( mesh->vertexes[0] ), TAG_MODEL ); + + // vertex positions + if ( layer->point.count <= 0 ) { + common->Warning( "ConvertLWOToASE: model \'%s\' has bad or missing vertex data", name.c_str() ); + } + + for ( j = 0; j < layer->point.count; j++ ) { + mesh->vertexes[j].x = layer->point.pt[j].pos[0]; + mesh->vertexes[j].y = layer->point.pt[j].pos[2]; + mesh->vertexes[j].z = layer->point.pt[j].pos[1]; + } + + // vertex texture coords + mesh->numTVertexes = 0; + + if ( layer->nvmaps ) { + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + mesh->numTVertexes += vm->nverts; + } + } + } + + if ( mesh->numTVertexes ) { + mesh->tvertexes = (idVec2 *)Mem_Alloc( mesh->numTVertexes * sizeof( mesh->tvertexes[0] ), TAG_MODEL ); + int offset = 0; + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + vm->offset = offset; + for ( k = 0; k < vm->nverts; k++ ) { + mesh->tvertexes[k + offset].x = vm->val[k][0]; + mesh->tvertexes[k + offset].y = 1.0f - vm->val[k][1]; // invert the t + } + offset += vm->nverts; + } + } + } else { + common->Warning( "ConvertLWOToASE: model \'%s\' has bad or missing uv data", fileName ); + mesh->numTVertexes = 1; + mesh->tvertexes = (idVec2 *)Mem_ClearedAlloc( mesh->numTVertexes * sizeof( mesh->tvertexes[0] ), TAG_MODEL ); + } + + mesh->normalsParsed = true; + mesh->colorsParsed = true; // because we are falling back to the surface color + + // triangles + int faceIndex = 0; + for ( j = 0; j < layer->polygon.count; j++ ) { + lwPolygon *poly = &layer->polygon.pol[j]; + + if ( poly->surf != surf ) { + continue; + } + + if ( poly->nverts != 3 ) { + common->Warning( "ConvertLWOToASE: model %s has too many verts for a poly! Make sure you triplet it down", fileName ); + continue; + } + + mesh->faces[faceIndex].faceNormal.x = poly->norm[0]; + mesh->faces[faceIndex].faceNormal.y = poly->norm[2]; + mesh->faces[faceIndex].faceNormal.z = poly->norm[1]; + + for ( k = 0; k < 3; k++ ) { + + mesh->faces[faceIndex].vertexNum[k] = poly->v[k].index; + + mesh->faces[faceIndex].vertexNormals[k].x = poly->v[k].norm[0]; + mesh->faces[faceIndex].vertexNormals[k].y = poly->v[k].norm[2]; + mesh->faces[faceIndex].vertexNormals[k].z = poly->v[k].norm[1]; + + // complete fallbacks + mesh->faces[faceIndex].tVertexNum[k] = 0; + + mesh->faces[faceIndex].vertexColors[k][0] = surf->color.rgb[0] * 255; + mesh->faces[faceIndex].vertexColors[k][1] = surf->color.rgb[1] * 255; + mesh->faces[faceIndex].vertexColors[k][2] = surf->color.rgb[2] * 255; + mesh->faces[faceIndex].vertexColors[k][3] = 255; + + // first set attributes from the vertex + lwPoint *pt = &layer->point.pt[poly->v[k].index]; + int nvm; + for ( nvm = 0; nvm < pt->nvmaps; nvm++ ) { + lwVMapPt *vm = &pt->vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + mesh->faces[faceIndex].tVertexNum[k] = vm->index + vm->vmap->offset; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + mesh->faces[faceIndex].vertexColors[k][chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + + // then override with polygon attributes + for ( nvm = 0; nvm < poly->v[k].nvmaps; nvm++ ) { + lwVMapPt *vm = &poly->v[k].vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + mesh->faces[faceIndex].tVertexNum[k] = vm->index + vm->vmap->offset; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + mesh->faces[faceIndex].vertexColors[k][chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + } + + faceIndex++; + } + + mesh->numFaces = faceIndex; + mesh->numTVFaces = faceIndex; + + aseFace_t *newFaces = ( aseFace_t* )Mem_Alloc( mesh->numFaces * sizeof ( mesh->faces[0] ), TAG_MODEL ); + memcpy( newFaces, mesh->faces, sizeof( mesh->faces[0] ) * mesh->numFaces ); + Mem_Free( mesh->faces ); + mesh->faces = newFaces; + } + + return ase; +} + +/* +================= +idRenderModelStatic::ConvertMAToModelSurfaces +================= +*/ +bool idRenderModelStatic::ConvertMAToModelSurfaces (const struct maModel_s *ma ) { + + maObject_t * object; + maMesh_t * mesh; + maMaterial_t * material; + + const idMaterial *im1, *im2; + srfTriangles_t *tri; + int objectNum; + int i, j, k; + int v, tv; + int * vRemap; + int * tvRemap; + matchVert_t * mvTable; // all of the match verts + matchVert_t ** mvHash; // points inside mvTable for each xyz index + matchVert_t * lastmv; + matchVert_t * mv; + idVec3 normal; + float uOffset, vOffset, textureSin, textureCos; + float uTiling, vTiling; + int * mergeTo; + byte * color; + static byte identityColor[4] = { 255, 255, 255, 255 }; + modelSurface_t surf, *modelSurf; + + if ( !ma ) { + return false; + } + if ( ma->objects.Num() < 1 ) { + return false; + } + + timeStamp = ma->timeStamp; + + // the modeling programs can save out multiple surfaces with a common + // material, but we would like to mege them together where possible + // meaning that this->NumSurfaces() <= ma->objects.currentElements + mergeTo = (int *)_alloca( ma->objects.Num() * sizeof( *mergeTo ) ); + + surf.geometry = NULL; + if ( ma->materials.Num() == 0 ) { + // if we don't have any materials, dump everything into a single surface + surf.shader = tr.defaultMaterial; + surf.id = 0; + this->AddSurface( surf ); + for ( i = 0; i < ma->objects.Num(); i++ ) { + mergeTo[i] = 0; + } + } else if ( !r_mergeModelSurfaces.GetBool() ) { + // don't merge any + for ( i = 0; i < ma->objects.Num(); i++ ) { + mergeTo[i] = i; + object = ma->objects[i]; + if(object->materialRef >= 0) { + material = ma->materials[object->materialRef]; + surf.shader = declManager->FindMaterial( material->name ); + } else { + surf.shader = tr.defaultMaterial; + } + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } else { + // search for material matches + for ( i = 0; i < ma->objects.Num(); i++ ) { + object = ma->objects[i]; + if(object->materialRef >= 0) { + material = ma->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + } else { + im1 = tr.defaultMaterial; + } + if ( im1->IsDiscrete() ) { + // flares, autosprites, etc + j = this->NumSurfaces(); + } else { + for ( j = 0; j < this->NumSurfaces(); j++ ) { + modelSurf = &this->surfaces[j]; + im2 = modelSurf->shader; + if ( im1 == im2 ) { + // merge this + mergeTo[i] = j; + break; + } + } + } + if ( j == this->NumSurfaces() ) { + // didn't merge + mergeTo[i] = j; + surf.shader = im1; + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } + } + + idVectorSubset vertexSubset; + idVectorSubset texCoordSubset; + + // build the surfaces + for ( objectNum = 0; objectNum < ma->objects.Num(); objectNum++ ) { + object = ma->objects[objectNum]; + mesh = &object->mesh; + if(object->materialRef >= 0) { + material = ma->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + } else { + im1 = tr.defaultMaterial; + } + + bool normalsParsed = mesh->normalsParsed; + + // completely ignore any explict normals on surfaces with a renderbump command + // which will guarantee the best contours and least vertexes. + const char *rb = im1->GetRenderBump(); + if ( rb != NULL && rb[0] != NULL ) { + normalsParsed = false; + } + + // It seems like the tools our artists are using often generate + // verts and texcoords slightly separated that should be merged + // note that we really should combine the surfaces with common materials + // before doing this operation, because we can miss a slop combination + // if they are in different surfaces + + vRemap = (int *)R_StaticAlloc( mesh->numVertexes * sizeof( vRemap[0] ), TAG_MODEL ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = j; + } + } else { + float vertexEpsilon = r_slopVertex.GetFloat(); + float expand = 2 * 32 * vertexEpsilon; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->vertexes, mesh->numVertexes ); + mins -= idVec3( expand, expand, expand ); + maxs += idVec3( expand, expand, expand ); + vertexSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = vertexSubset.FindVector( mesh->vertexes, j, vertexEpsilon ); + } + } + + tvRemap = (int *)R_StaticAlloc( mesh->numTVertexes * sizeof( tvRemap[0] ), TAG_MODEL ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = j; + } + } else { + float texCoordEpsilon = r_slopTexCoord.GetFloat(); + float expand = 2 * 32 * texCoordEpsilon; + idVec2 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->tvertexes, mesh->numTVertexes ); + mins -= idVec2( expand, expand ); + maxs += idVec2( expand, expand ); + texCoordSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = texCoordSubset.FindVector( mesh->tvertexes, j, texCoordEpsilon ); + } + } + + // we need to find out how many unique vertex / texcoord / color combinations + // there are, because MA tracks them separately but we need them unified + + // the maximum possible number of combined vertexes is the number of indexes + mvTable = (matchVert_t *)R_ClearedStaticAlloc( mesh->numFaces * 3 * sizeof( mvTable[0] ) ); + + // we will have a hash chain based on the xyz values + mvHash = (matchVert_t **)R_ClearedStaticAlloc( mesh->numVertexes * sizeof( mvHash[0] ) ); + + // allocate triangle surface + tri = R_AllocStaticTriSurf(); + tri->numVerts = 0; + tri->numIndexes = 0; + R_AllocStaticTriSurfIndexes( tri, mesh->numFaces * 3 ); + tri->generateNormals = !normalsParsed; + + // init default normal, color and tex coord index + normal.Zero(); + color = identityColor; + tv = 0; + + // find all the unique combinations + float normalEpsilon = 1.0f - r_slopNormal.GetFloat(); + for ( j = 0; j < mesh->numFaces; j++ ) { + for ( k = 0; k < 3; k++ ) { + v = mesh->faces[j].vertexNum[k]; + + if ( v < 0 || v >= mesh->numVertexes ) { + common->Error( "ConvertMAToModelSurfaces: bad vertex index in MA file %s", name.c_str() ); + } + + // collapse the position if it was slightly offset + v = vRemap[v]; + + // we may or may not have texcoords to compare + if ( mesh->numTVertexes != 0 ) { + tv = mesh->faces[j].tVertexNum[k]; + if ( tv < 0 || tv >= mesh->numTVertexes ) { + common->Error( "ConvertMAToModelSurfaces: bad tex coord index in MA file %s", name.c_str() ); + } + // collapse the tex coord if it was slightly offset + tv = tvRemap[tv]; + } + + // we may or may not have normals to compare + if ( normalsParsed ) { + normal = mesh->faces[j].vertexNormals[k]; + } + + //BSM: Todo: Fix the vertex colors + // we may or may not have colors to compare + if ( mesh->faces[j].vertexColors[k] != -1 && mesh->faces[j].vertexColors[k] != -999 ) { + + color = &mesh->colors[mesh->faces[j].vertexColors[k]*4]; + } + + // find a matching vert + for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) { + if ( mv->tv != tv ) { + continue; + } + if ( *(unsigned *)mv->color != *(unsigned *)color ) { + continue; + } + if ( !normalsParsed ) { + // if we are going to create the normals, just + // matching texcoords is enough + break; + } + if ( mv->normal * normal > normalEpsilon ) { + break; // we already have this one + } + } + if ( !mv ) { + // allocate a new match vert and link to hash chain + mv = &mvTable[ tri->numVerts ]; + mv->v = v; + mv->tv = tv; + mv->normal = normal; + *(unsigned *)mv->color = *(unsigned *)color; + mv->next = NULL; + if ( lastmv ) { + lastmv->next = mv; + } else { + mvHash[v] = mv; + } + tri->numVerts++; + } + + tri->indexes[tri->numIndexes] = mv - mvTable; + tri->numIndexes++; + } + } + + // allocate space for the indexes and copy them + if ( tri->numIndexes > mesh->numFaces * 3 ) { + common->FatalError( "ConvertMAToModelSurfaces: index miscount in MA file %s", name.c_str() ); + } + if ( tri->numVerts > mesh->numFaces * 3 ) { + common->FatalError( "ConvertMAToModelSurfaces: vertex miscount in MA file %s", name.c_str() ); + } + + // an MA allows the texture coordinates to be scaled, translated, and rotated + //BSM: Todo: Does Maya support this and if so how + //if ( ase->materials.Num() == 0 ) { + uOffset = vOffset = 0.0f; + uTiling = vTiling = 1.0f; + textureSin = 0.0f; + textureCos = 1.0f; + //} else { + // material = ase->materials[object->materialRef]; + // uOffset = -material->uOffset; + // vOffset = material->vOffset; + // uTiling = material->uTiling; + // vTiling = material->vTiling; + // textureSin = idMath::Sin( material->angle ); + // textureCos = idMath::Cos( material->angle ); + //} + + // now allocate and generate the combined vertexes + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + + for ( j = 0; j < tri->numVerts; j++ ) { + mv = &mvTable[j]; + tri->verts[ j ].Clear(); + tri->verts[ j ].xyz = mesh->vertexes[ mv->v ]; + tri->verts[ j ].SetNormal( mv->normal ); + *(unsigned *)tri->verts[j].color = *(unsigned *)mv->color; + if ( mesh->numTVertexes != 0 ) { + const idVec2 &tv = mesh->tvertexes[ mv->tv ]; + float u = tv.x * uTiling + uOffset; + float v = tv.y * vTiling + vOffset; + tri->verts[j].SetTexCoord( u * textureCos + v * textureSin, u * -textureSin + v * textureCos ); + } + } + + R_StaticFree( mvTable ); + R_StaticFree( mvHash ); + R_StaticFree( tvRemap ); + R_StaticFree( vRemap ); + + // see if we need to merge with a previous surface of the same material + modelSurf = &this->surfaces[mergeTo[ objectNum ]]; + srfTriangles_t *mergeTri = modelSurf->geometry; + if ( !mergeTri ) { + modelSurf->geometry = tri; + } else { + modelSurf->geometry = R_MergeTriangles( mergeTri, tri ); + R_FreeStaticTriSurf( tri ); + R_FreeStaticTriSurf( mergeTri ); + } + } + + return true; +} + +/* +================= +idRenderModelStatic::LoadASE +================= +*/ +bool idRenderModelStatic::LoadASE( const char *fileName ) { + aseModel_t *ase; + + ase = ASE_Load( fileName ); + if ( ase == NULL ) { + return false; + } + + ConvertASEToModelSurfaces( ase ); + + ASE_Free( ase ); + + return true; +} + +/* +================= +idRenderModelStatic::LoadLWO +================= +*/ +bool idRenderModelStatic::LoadLWO( const char *fileName ) { + unsigned int failID; + int failPos; + lwObject *lwo; + + lwo = lwGetObject( fileName, &failID, &failPos ); + if ( lwo == NULL ) { + return false; + } + + ConvertLWOToModelSurfaces( lwo ); + + lwFreeObject( lwo ); + + return true; +} + +/* +================= +idRenderModelStatic::LoadMA +================= +*/ +bool idRenderModelStatic::LoadMA( const char *fileName ) { + maModel_t *ma; + + ma = MA_Load( fileName ); + if ( ma == NULL ) { + return false; + } + + ConvertMAToModelSurfaces( ma ); + + MA_Free( ma ); + + return true; +} + + +//============================================================================= + +/* +================ +idRenderModelStatic::PurgeModel +================ +*/ +void idRenderModelStatic::PurgeModel() { + for ( int i = 0; i < surfaces.Num(); i++ ) { + modelSurface_t * surf = &surfaces[i]; + + if ( surf->geometry ) { + R_FreeStaticTriSurf( surf->geometry ); + } + } + surfaces.Clear(); + + if ( jointsInverted != NULL ) { + Mem_Free( jointsInverted ); + jointsInverted = NULL; + } + + purged = true; +} + +/* +============== +idRenderModelStatic::FreeVertexCache + +We are about to restart the vertex cache, so dump everything +============== +*/ +void idRenderModelStatic::FreeVertexCache() { + for ( int j = 0; j < surfaces.Num(); j++ ) { + srfTriangles_t *tri = surfaces[j].geometry; + if ( tri == NULL ) { + continue; + } + R_FreeStaticTriSurfVertexCaches( tri ); + } +} + +/* +================ +idRenderModelStatic::ReadFromDemoFile +================ +*/ +void idRenderModelStatic::ReadFromDemoFile( class idDemoFile *f ) { + PurgeModel(); + + InitEmpty( f->ReadHashString() ); + + int i, j, numSurfaces; + f->ReadInt( numSurfaces ); + + for ( i = 0; i < numSurfaces; i++ ) { + modelSurface_t surf; + + surf.shader = declManager->FindMaterial( f->ReadHashString() ); + + srfTriangles_t *tri = R_AllocStaticTriSurf(); + + f->ReadInt( tri->numIndexes ); + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + for ( j = 0; j < tri->numIndexes; ++j ) + f->ReadInt( (int&)tri->indexes[j] ); + + f->ReadInt( tri->numVerts ); + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + + idVec3 tNormal, tTangent, tBiTangent; + for ( j = 0; j < tri->numVerts; ++j ) { + f->ReadVec3( tri->verts[j].xyz ); + f->ReadBigArray( tri->verts[j].st, 2 ); + f->ReadBigArray( tri->verts[j].normal, 4 ); + f->ReadBigArray( tri->verts[j].tangent, 4 ); + f->ReadUnsignedChar( tri->verts[j].color[0] ); + f->ReadUnsignedChar( tri->verts[j].color[1] ); + f->ReadUnsignedChar( tri->verts[j].color[2] ); + f->ReadUnsignedChar( tri->verts[j].color[3] ); + } + + surf.geometry = tri; + + this->AddSurface( surf ); + } + this->FinishSurfaces(); +} + +/* +================ +idRenderModelStatic::WriteToDemoFile +================ +*/ +void idRenderModelStatic::WriteToDemoFile( class idDemoFile *f ) { + int data[1]; + + // note that it has been updated + lastArchivedFrame = tr.frameCount; + + data[0] = DC_DEFINE_MODEL; + f->WriteInt( data[0] ); + f->WriteHashString( this->Name() ); + + int i, j, iData = surfaces.Num(); + f->WriteInt( iData ); + + for ( i = 0; i < surfaces.Num(); i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + f->WriteHashString( surf->shader->GetName() ); + + srfTriangles_t *tri = surf->geometry; + f->WriteInt( tri->numIndexes ); + for ( j = 0; j < tri->numIndexes; ++j ) + f->WriteInt( (int&)tri->indexes[j] ); + f->WriteInt( tri->numVerts ); + for ( j = 0; j < tri->numVerts; ++j ) { + f->WriteVec3( tri->verts[j].xyz ); + f->WriteBigArray( tri->verts[j].st, 2 ); + f->WriteBigArray( tri->verts[j].normal, 4 ); + f->WriteBigArray( tri->verts[j].tangent, 4 ); + f->WriteUnsignedChar( tri->verts[j].color[0] ); + f->WriteUnsignedChar( tri->verts[j].color[1] ); + f->WriteUnsignedChar( tri->verts[j].color[2] ); + f->WriteUnsignedChar( tri->verts[j].color[3] ); + } + } +} + +/* +================ +idRenderModelStatic::IsLoaded +================ +*/ +bool idRenderModelStatic::IsLoaded() { + return !purged; +} + +/* +================ +idRenderModelStatic::SetLevelLoadReferenced +================ +*/ +void idRenderModelStatic::SetLevelLoadReferenced( bool referenced ) { + levelLoadReferenced = referenced; +} + +/* +================ +idRenderModelStatic::IsLevelLoadReferenced +================ +*/ +bool idRenderModelStatic::IsLevelLoadReferenced() { + return levelLoadReferenced; +} + +/* +================= +idRenderModelStatic::TouchData +================= +*/ +void idRenderModelStatic::TouchData() { + for ( int i = 0; i < surfaces.Num(); i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + // re-find the material to make sure it gets added to the + // level keep list + declManager->FindMaterial( surf->shader->GetName() ); + } +} + +/* +================= +idRenderModelStatic::DeleteSurfaceWithId +================= +*/ +bool idRenderModelStatic::DeleteSurfaceWithId( int id ) { + int i; + + for ( i = 0; i < surfaces.Num(); i++ ) { + if ( surfaces[i].id == id ) { + R_FreeStaticTriSurf( surfaces[i].geometry ); + surfaces.RemoveIndex( i ); + return true; + } + } + return false; +} + +/* +================= +idRenderModelStatic::DeleteSurfacesWithNegativeId +================= +*/ +void idRenderModelStatic::DeleteSurfacesWithNegativeId() { + for ( int i = 0; i < surfaces.Num(); i++ ) { + if ( surfaces[i].id < 0 ) { + R_FreeStaticTriSurf( surfaces[i].geometry ); + surfaces.RemoveIndex( i ); + i--; + } + } +} + +/* +================= +idRenderModelStatic::FindSurfaceWithId +================= +*/ +bool idRenderModelStatic::FindSurfaceWithId( int id, int &surfaceNum ) const { + for ( int i = 0; i < surfaces.Num(); i++ ) { + if ( surfaces[i].id == id ) { + surfaceNum = i; + return true; + } + } + return false; +} diff --git a/neo/renderer/Model.h b/neo/renderer/Model.h new file mode 100644 index 00000000..8f189289 --- /dev/null +++ b/neo/renderer/Model.h @@ -0,0 +1,306 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODEL_H__ +#define __MODEL_H__ + +/* +=============================================================================== + + Render Model + +=============================================================================== +*/ + +// shared between the renderer, game, and Maya export DLL +#define MD5_VERSION_STRING "MD5Version" +#define MD5_MESH_EXT "md5mesh" +#define MD5_ANIM_EXT "md5anim" +#define MD5_CAMERA_EXT "md5camera" +#define MD5_VERSION 10 + +#include "jobs/ShadowShared.h" +#include "jobs/prelightshadowvolume/PreLightShadowVolume.h" +#include "jobs/staticshadowvolume/StaticShadowVolume.h" +#include "jobs/dynamicshadowvolume/DynamicShadowVolume.h" + +// this is used for calculating unsmoothed normals and tangents for deformed models +struct dominantTri_t { + triIndex_t v2, v3; + float normalizationScale[3]; +}; + +const int SHADOW_CAP_INFINITE = 64; + +class idRenderModelStatic; +struct viewDef_t; + +// our only drawing geometry type +struct srfTriangles_t { + srfTriangles_t() {} + + idBounds bounds; // for culling + + bool generateNormals; // create normals from geometry, instead of using explicit ones + bool tangentsCalculated; // set when the vertex tangents have been calculated + bool perfectHull; // true if there aren't any dangling edges + bool referencedVerts; // if true the 'verts' are referenced and should not be freed + bool referencedIndexes; // if true, indexes, silIndexes, mirrorVerts, and silEdges are + // pointers into the original surface, and should not be freed + + int numVerts; // number of vertices + idDrawVert * verts; // vertices, allocated with special allocator + + int numIndexes; // for shadows, this has both front and rear end caps and silhouette planes + triIndex_t * indexes; // indexes, allocated with special allocator + + triIndex_t * silIndexes; // indexes changed to be the first vertex with same XYZ, ignoring normal and texcoords + + int numMirroredVerts; // this many verts at the end of the vert list are tangent mirrors + int * mirroredVerts; // tri->mirroredVerts[0] is the mirror of tri->numVerts - tri->numMirroredVerts + 0 + + int numDupVerts; // number of duplicate vertexes + int * dupVerts; // pairs of the number of the first vertex and the number of the duplicate vertex + + int numSilEdges; // number of silhouette edges + silEdge_t * silEdges; // silhouette edges + + dominantTri_t * dominantTris; // [numVerts] for deformed surface fast tangent calculation + + int numShadowIndexesNoFrontCaps; // shadow volumes with front caps omitted + int numShadowIndexesNoCaps; // shadow volumes with the front and rear caps omitted + + int shadowCapPlaneBits; // bits 0-5 are set when that plane of the interacting light has triangles + // projected on it, which means that if the view is on the outside of that + // plane, we need to draw the rear caps of the shadow volume + // dynamic shadows will have SHADOW_CAP_INFINITE + + idShadowVert * preLightShadowVertexes; // shadow vertices in CPU memory for pre-light shadow volumes + idShadowVert * staticShadowVertexes; // shadow vertices in CPU memory for static shadow volumes + + srfTriangles_t * ambientSurface; // for light interactions, point back at the original surface that generated + // the interaction, which we will get the ambientCache from + + srfTriangles_t * nextDeferredFree; // chain of tris to free next frame + + // for deferred normal / tangent transformations by joints + // the jointsInverted list / buffer object on md5WithJoints may be + // shared by multiple srfTriangles_t + idRenderModelStatic * staticModelWithJoints; + + // data in vertex object space, not directly readable by the CPU + vertCacheHandle_t indexCache; // GL_INDEX_TYPE + vertCacheHandle_t ambientCache; // idDrawVert + vertCacheHandle_t shadowCache; // idVec4 + + DISALLOW_COPY_AND_ASSIGN( srfTriangles_t ); +}; + +typedef idList idTriList; + +struct modelSurface_t { + int id; + const idMaterial * shader; + srfTriangles_t * geometry; +}; + +enum dynamicModel_t { + DM_STATIC, // never creates a dynamic model + DM_CACHED, // once created, stays constant until the entity is updated (animating characters) + DM_CONTINUOUS // must be recreated for every single view (time dependent things like particles) +}; + +enum jointHandle_t { + INVALID_JOINT = -1 +}; + +class idMD5Joint { +public: + idMD5Joint() { parent = NULL; } + idStr name; + const idMD5Joint * parent; +}; + + +// the init methods may be called again on an already created model when +// a reloadModels is issued + +class idRenderModel { +public: + virtual ~idRenderModel() {}; + + // Loads static models only, dynamic models must be loaded by the modelManager + virtual void InitFromFile( const char *fileName ) = 0; + + // Supports reading/writing binary file formats + virtual bool LoadBinaryModel( idFile * file, const ID_TIME_T sourceTimeStamp ) = 0; + virtual void WriteBinaryModel( idFile * file, ID_TIME_T *_timeStamp = NULL ) const = 0; + virtual bool SupportsBinaryModel() = 0; + + // renderBump uses this to load the very high poly count models, skipping the + // shadow and tangent generation, along with some surface cleanup to make it load faster + virtual void PartialInitFromFile( const char *fileName ) = 0; + + // this is used for dynamically created surfaces, which are assumed to not be reloadable. + // It can be called again to clear out the surfaces of a dynamic model for regeneration. + virtual void InitEmpty( const char *name ) = 0; + + // dynamic model instantiations will be created with this + // the geometry data will be owned by the model, and freed when it is freed + // the geoemtry should be raw triangles, with no extra processing + virtual void AddSurface( modelSurface_t surface ) = 0; + + // cleans all the geometry and performs cross-surface processing + // like shadow hulls + // Creates the duplicated back side geometry for two sided, alpha tested, lit materials + // This does not need to be called if none of the surfaces added with AddSurface require + // light interaction, and all the triangles are already well formed. + virtual void FinishSurfaces() = 0; + + // frees all the data, but leaves the class around for dangling references, + // which can regenerate the data with LoadModel() + virtual void PurgeModel() = 0; + + // resets any model information that needs to be reset on a same level load etc.. + // currently only implemented for liquids + virtual void Reset() = 0; + + // used for initial loads, reloadModel, and reloading the data of purged models + // Upon exit, the model will absolutely be valid, but possibly as a default model + virtual void LoadModel() = 0; + + // internal use + virtual bool IsLoaded() = 0; + virtual void SetLevelLoadReferenced( bool referenced ) = 0; + virtual bool IsLevelLoadReferenced() = 0; + + // models that are already loaded at level start time + // will still touch their data to make sure they + // are kept loaded + virtual void TouchData() = 0; + + // dump any ambient caches on the model surfaces + virtual void FreeVertexCache() = 0; + + // returns the name of the model + virtual const char * Name() const = 0; + + // prints a detailed report on the model for printModel + virtual void Print() const = 0; + + // prints a single line report for listModels + virtual void List() const = 0; + + // reports the amount of memory (roughly) consumed by the model + virtual int Memory() const = 0; + + // for reloadModels + virtual ID_TIME_T Timestamp() const = 0; + + // returns the number of surfaces + virtual int NumSurfaces() const = 0; + + // NumBaseSurfaces will not count any overlays added to dynamic models + virtual int NumBaseSurfaces() const = 0; + + // get a pointer to a surface + virtual const modelSurface_t *Surface( int surfaceNum ) const = 0; + + // Allocates surface triangles. + // Allocates memory for srfTriangles_t::verts and srfTriangles_t::indexes + // The allocated memory is not initialized. + // srfTriangles_t::numVerts and srfTriangles_t::numIndexes are set to zero. + virtual srfTriangles_t * AllocSurfaceTriangles( int numVerts, int numIndexes ) const = 0; + + // Frees surfaces triangles. + virtual void FreeSurfaceTriangles( srfTriangles_t *tris ) const = 0; + + // models of the form "_area*" may have a prelight shadow model associated with it + virtual bool IsStaticWorldModel() const = 0; + + // models parsed from inside map files or dynamically created cannot be reloaded by + // reloadmodels + virtual bool IsReloadable() const = 0; + + // md3, md5, particles, etc + virtual dynamicModel_t IsDynamicModel() const = 0; + + // if the load failed for any reason, this will return true + virtual bool IsDefaultModel() const = 0; + + // dynamic models should return a fast, conservative approximation + // static models should usually return the exact value + virtual idBounds Bounds( const struct renderEntity_s *ent = NULL ) const = 0; + + // returns value != 0.0f if the model requires the depth hack + virtual float DepthHack() const = 0; + + // returns a static model based on the definition and view + // currently, this will be regenerated for every view, even though + // some models, like character meshes, could be used for multiple (mirror) + // views in a frame, or may stay static for multiple frames (corpses) + // The renderer will delete the returned dynamic model the next view + // This isn't const, because it may need to reload a purged model if it + // wasn't precached correctly. + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ) = 0; + + // Returns the number of joints or 0 if the model is not an MD5 + virtual int NumJoints() const = 0; + + // Returns the MD5 joints or NULL if the model is not an MD5 + virtual const idMD5Joint * GetJoints() const = 0; + + // Returns the handle for the joint with the given name. + virtual jointHandle_t GetJointHandle( const char *name ) const = 0; + + // Returns the name for the joint with the given handle. + virtual const char * GetJointName( jointHandle_t handle ) const = 0; + + // Returns the default animation pose or NULL if the model is not an MD5. + virtual const idJointQuat * GetDefaultPose() const = 0; + + // Returns number of the joint nearest to the given triangle. + virtual int NearestJoint( int surfaceNum, int a, int c, int b ) const = 0; + + // Writing to and reading from a demo file. + virtual void ReadFromDemoFile( class idDemoFile *f ) = 0; + virtual void WriteToDemoFile( class idDemoFile *f ) = 0; + + // if false, the model doesn't need to be linked into the world, because it + // can't contribute visually -- triggers, etc + virtual bool ModelHasDrawingSurfaces() const { return true; }; + + // if false, the model doesn't generate interactions with lights + virtual bool ModelHasInteractingSurfaces() const { return true; }; + + // if false, the model doesn't need to be added to the view unless it is + // directly visible, because it can't cast shadows into the view + virtual bool ModelHasShadowCastingSurfaces() const { return true; }; +}; + +#endif /* !__MODEL_H__ */ diff --git a/neo/renderer/ModelDecal.cpp b/neo/renderer/ModelDecal.cpp new file mode 100644 index 00000000..596522dd --- /dev/null +++ b/neo/renderer/ModelDecal.cpp @@ -0,0 +1,756 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +#include "../idlib/geometry/DrawVert_intrinsics.h" + +// decalFade filter 5 0.1 +// polygonOffset +// { +// map invertColor( textures/splat ) +// blend GL_ZERO GL_ONE_MINUS_SRC +// vertexColor +// clamp +// } + +/* +================== +idRenderModelDecal::idRenderModelDecal +================== +*/ +idRenderModelDecal::idRenderModelDecal() : + firstDecal( 0 ), + nextDecal( 0 ), + firstDeferredDecal( 0 ), + nextDeferredDecal( 0 ), + numDecalMaterials( 0 ) { +} + +/* +================== +idRenderModelDecal::~idRenderModelDecal +================== +*/ +idRenderModelDecal::~idRenderModelDecal() { +} + +/* +================= +idRenderModelDecal::CreateProjectionParms +================= +*/ +bool idRenderModelDecal::CreateProjectionParms( decalProjectionParms_t &parms, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { + + if ( winding.GetNumPoints() != NUM_DECAL_BOUNDING_PLANES - 2 ) { + common->Printf( "idRenderModelDecal::CreateProjectionInfo: winding must have %d points\n", NUM_DECAL_BOUNDING_PLANES - 2 ); + return false; + } + + assert( material != NULL ); + + parms.projectionOrigin = projectionOrigin; + parms.material = material; + parms.parallel = parallel; + parms.fadeDepth = fadeDepth; + parms.startTime = startTime; + parms.force = false; + + // get the winding plane and the depth of the projection volume + idPlane windingPlane; + winding.GetPlane( windingPlane ); + float depth = windingPlane.Distance( projectionOrigin ); + + // find the bounds for the projection + winding.GetBounds( parms.projectionBounds ); + if ( parallel ) { + parms.projectionBounds.ExpandSelf( depth ); + } else { + parms.projectionBounds.AddPoint( projectionOrigin ); + } + + // calculate the world space projection volume bounding planes, positive sides face outside the decal + if ( parallel ) { + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + idVec3 edge = winding[( i + 1 ) % winding.GetNumPoints()].ToVec3() - winding[i].ToVec3(); + parms.boundingPlanes[i].Normal().Cross( windingPlane.Normal(), edge ); + parms.boundingPlanes[i].Normalize(); + parms.boundingPlanes[i].FitThroughPoint( winding[i].ToVec3() ); + } + } else { + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + parms.boundingPlanes[i].FromPoints( projectionOrigin, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3() ); + } + } + parms.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2] = windingPlane; + parms.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2][3] -= depth; + parms.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 1] = -windingPlane; + + // fades will be from these plane + parms.fadePlanes[0] = windingPlane; + parms.fadePlanes[0][3] -= fadeDepth; + parms.fadePlanes[1] = -windingPlane; + parms.fadePlanes[1][3] += depth - fadeDepth; + + // calculate the texture vectors for the winding + float len, texArea, inva; + idVec3 temp; + idVec5 d0, d1; + + const idVec5 &a = winding[0]; + const idVec5 &b = winding[1]; + const idVec5 &c = winding[2]; + + d0 = b.ToVec3() - a.ToVec3(); + d0.s = b.s - a.s; + d0.t = b.t - a.t; + d1 = c.ToVec3() - a.ToVec3(); + d1.s = c.s - a.s; + d1.t = c.t - a.t; + + texArea = ( d0[3] * d1[4] ) - ( d0[4] * d1[3] ); + inva = 1.0f / texArea; + + temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ) * inva; + temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ) * inva; + temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ) * inva; + len = temp.Normalize(); + parms.textureAxis[0].Normal() = temp * ( 1.0f / len ); + parms.textureAxis[0][3] = winding[0].s - ( winding[0].ToVec3() * parms.textureAxis[0].Normal() ); + + temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ) * inva; + temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ) * inva; + temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ) * inva; + len = temp.Normalize(); + parms.textureAxis[1].Normal() = temp * ( 1.0f / len ); + parms.textureAxis[1][3] = winding[0].t - ( winding[0].ToVec3() * parms.textureAxis[1].Normal() ); + + return true; +} + +/* +================= +idRenderModelDecal::GlobalProjectionParmsToLocal +================= +*/ +void idRenderModelDecal::GlobalProjectionParmsToLocal( decalProjectionParms_t &localParms, const decalProjectionParms_t &globalParms, const idVec3 &origin, const idMat3 &axis ) { + float modelMatrix[16]; + + R_AxisToModelMatrix( axis, origin, modelMatrix ); + + for ( int j = 0; j < NUM_DECAL_BOUNDING_PLANES; j++ ) { + R_GlobalPlaneToLocal( modelMatrix, globalParms.boundingPlanes[j], localParms.boundingPlanes[j] ); + } + R_GlobalPlaneToLocal( modelMatrix, globalParms.fadePlanes[0], localParms.fadePlanes[0] ); + R_GlobalPlaneToLocal( modelMatrix, globalParms.fadePlanes[1], localParms.fadePlanes[1] ); + R_GlobalPlaneToLocal( modelMatrix, globalParms.textureAxis[0], localParms.textureAxis[0] ); + R_GlobalPlaneToLocal( modelMatrix, globalParms.textureAxis[1], localParms.textureAxis[1] ); + R_GlobalPointToLocal( modelMatrix, globalParms.projectionOrigin, localParms.projectionOrigin ); + localParms.projectionBounds = globalParms.projectionBounds; + localParms.projectionBounds.TranslateSelf( -origin ); + localParms.projectionBounds.RotateSelf( axis.Transpose() ); + localParms.material = globalParms.material; + localParms.parallel = globalParms.parallel; + localParms.fadeDepth = globalParms.fadeDepth; + localParms.startTime = globalParms.startTime; + localParms.force = globalParms.force; +} + +/* +================= +idRenderModelDecal::ReUse +================= +*/ +void idRenderModelDecal::ReUse() { + firstDecal = 0; + nextDecal = 0; + firstDeferredDecal = 0; + nextDeferredDecal = 0; + numDecalMaterials = 0; +} + +/* +================= +idRenderModelDecal::CreateDecalFromWinding +================= +*/ +void idRenderModelDecal::CreateDecalFromWinding( const idWinding &w, const idMaterial *decalMaterial, const idPlane fadePlanes[2], float fadeDepth, int startTime ) { + // Often we are appending a new triangle to an existing decal, so merge with the previous decal if possible + int decalIndex = ( nextDecal - 1 ) & ( MAX_DECALS - 1 ); + if ( decalIndex >= 0 + && decals[decalIndex].material == decalMaterial + && decals[decalIndex].startTime == startTime + && decals[decalIndex].numVerts + w.GetNumPoints() <= MAX_DECAL_VERTS + && decals[decalIndex].numIndexes + 3 * ( w.GetNumPoints() - 2 ) <= MAX_DECAL_INDEXES ) { + } else { + decalIndex = nextDecal++ & ( MAX_DECALS - 1 ); + decals[decalIndex].material = decalMaterial; + decals[decalIndex].startTime = startTime; + decals[decalIndex].numVerts = 0; + decals[decalIndex].numIndexes = 0; + assert( w.GetNumPoints() <= MAX_DECAL_VERTS ); + if ( nextDecal - firstDecal > MAX_DECALS ) { + firstDecal = nextDecal - MAX_DECALS; + } + } + + decal_t & decal = decals[decalIndex]; + + const float invFadeDepth = -1.0f / fadeDepth; + + int firstVert = decal.numVerts; + + // create the vertices + for ( int i = 0; i < w.GetNumPoints(); i++ ) { + float depthFade = fadePlanes[0].Distance( w[i].ToVec3() ) * invFadeDepth; + if ( depthFade < 0.0f ) { + depthFade = fadePlanes[1].Distance( w[i].ToVec3() ) * invFadeDepth; + } + if ( depthFade < 0.0f ) { + depthFade = 0.0f; + } else if ( depthFade > 0.99f ) { + depthFade = 1.0f; + } + decal.vertDepthFade[decal.numVerts] = 1.0f - depthFade; + decal.verts[decal.numVerts].Clear(); + decal.verts[decal.numVerts].xyz = w[i].ToVec3(); + decal.verts[decal.numVerts].SetTexCoord( w[i].s, w[i].t ); + decal.numVerts++; + } + + // create the indexes + for ( int i = 2; i < w.GetNumPoints(); i++ ) { + assert( decal.numIndexes + 3 <= MAX_DECAL_INDEXES ); + decal.indexes[decal.numIndexes + 0] = firstVert; + decal.indexes[decal.numIndexes + 1] = firstVert + i - 1; + decal.indexes[decal.numIndexes + 2] = firstVert + i; + decal.numIndexes += 3; + } + + // add degenerate triangles until the index size is a multiple of 16 bytes + for ( ; ( ( ( decal.numIndexes * sizeof( triIndex_t ) ) & 15 ) != 0 ); decal.numIndexes += 3 ) { + assert( decal.numIndexes + 3 <= MAX_DECAL_INDEXES ); + decal.indexes[decal.numIndexes + 0] = 0; + decal.indexes[decal.numIndexes + 1] = 0; + decal.indexes[decal.numIndexes + 2] = 0; + } +} + +/* +============ +R_DecalPointCullStatic +============ +*/ +static void R_DecalPointCullStatic( byte * cullBits, const idPlane * planes, const idDrawVert * verts, const int numVerts ) { + assert_16_byte_aligned( cullBits ); + assert_16_byte_aligned( verts ); + + + idODSStreamedArray< idDrawVert, 16, SBT_DOUBLE, 4 > vertsODS( verts, numVerts ); + + const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; + const __m128i vector_int_mask0 = _mm_set1_epi32( 1 << 0 ); + const __m128i vector_int_mask1 = _mm_set1_epi32( 1 << 1 ); + const __m128i vector_int_mask2 = _mm_set1_epi32( 1 << 2 ); + const __m128i vector_int_mask3 = _mm_set1_epi32( 1 << 3 ); + const __m128i vector_int_mask4 = _mm_set1_epi32( 1 << 4 ); + const __m128i vector_int_mask5 = _mm_set1_epi32( 1 << 5 ); + + const __m128 p0 = _mm_loadu_ps( planes[0].ToFloatPtr() ); + const __m128 p1 = _mm_loadu_ps( planes[1].ToFloatPtr() ); + const __m128 p2 = _mm_loadu_ps( planes[2].ToFloatPtr() ); + const __m128 p3 = _mm_loadu_ps( planes[3].ToFloatPtr() ); + const __m128 p4 = _mm_loadu_ps( planes[4].ToFloatPtr() ); + const __m128 p5 = _mm_loadu_ps( planes[5].ToFloatPtr() ); + + const __m128 p0X = _mm_splat_ps( p0, 0 ); + const __m128 p0Y = _mm_splat_ps( p0, 1 ); + const __m128 p0Z = _mm_splat_ps( p0, 2 ); + const __m128 p0W = _mm_splat_ps( p0, 3 ); + + const __m128 p1X = _mm_splat_ps( p1, 0 ); + const __m128 p1Y = _mm_splat_ps( p1, 1 ); + const __m128 p1Z = _mm_splat_ps( p1, 2 ); + const __m128 p1W = _mm_splat_ps( p1, 3 ); + + const __m128 p2X = _mm_splat_ps( p2, 0 ); + const __m128 p2Y = _mm_splat_ps( p2, 1 ); + const __m128 p2Z = _mm_splat_ps( p2, 2 ); + const __m128 p2W = _mm_splat_ps( p2, 3 ); + + const __m128 p3X = _mm_splat_ps( p3, 0 ); + const __m128 p3Y = _mm_splat_ps( p3, 1 ); + const __m128 p3Z = _mm_splat_ps( p3, 2 ); + const __m128 p3W = _mm_splat_ps( p3, 3 ); + + const __m128 p4X = _mm_splat_ps( p4, 0 ); + const __m128 p4Y = _mm_splat_ps( p4, 1 ); + const __m128 p4Z = _mm_splat_ps( p4, 2 ); + const __m128 p4W = _mm_splat_ps( p4, 3 ); + + const __m128 p5X = _mm_splat_ps( p5, 0 ); + const __m128 p5Y = _mm_splat_ps( p5, 1 ); + const __m128 p5Z = _mm_splat_ps( p5, 2 ); + const __m128 p5W = _mm_splat_ps( p5, 3 ); + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 4; + + for ( ; i <= nextNumVerts; i += 4 ) { + const __m128 v0 = _mm_load_ps( vertsODS[i + 0].xyz.ToFloatPtr() ); + const __m128 v1 = _mm_load_ps( vertsODS[i + 1].xyz.ToFloatPtr() ); + const __m128 v2 = _mm_load_ps( vertsODS[i + 2].xyz.ToFloatPtr() ); + const __m128 v3 = _mm_load_ps( vertsODS[i + 3].xyz.ToFloatPtr() ); + + const __m128 r0 = _mm_unpacklo_ps( v0, v2 ); // v0.x, v2.x, v0.z, v2.z + const __m128 r1 = _mm_unpackhi_ps( v0, v2 ); // v0.y, v2.y, v0.w, v2.w + const __m128 r2 = _mm_unpacklo_ps( v1, v3 ); // v1.x, v3.x, v1.z, v3.z + const __m128 r3 = _mm_unpackhi_ps( v1, v3 ); // v1.y, v3.y, v1.w, v3.w + + const __m128 vX = _mm_unpacklo_ps( r0, r2 ); // v0.x, v1.x, v2.x, v3.x + const __m128 vY = _mm_unpackhi_ps( r0, r2 ); // v0.y, v1.y, v2.y, v3.y + const __m128 vZ = _mm_unpacklo_ps( r1, r3 ); // v0.z, v1.z, v2.z, v3.z + + const __m128 d0 = _mm_madd_ps( vX, p0X, _mm_madd_ps( vY, p0Y, _mm_madd_ps( vZ, p0Z, p0W ) ) ); + const __m128 d1 = _mm_madd_ps( vX, p1X, _mm_madd_ps( vY, p1Y, _mm_madd_ps( vZ, p1Z, p1W ) ) ); + const __m128 d2 = _mm_madd_ps( vX, p2X, _mm_madd_ps( vY, p2Y, _mm_madd_ps( vZ, p2Z, p2W ) ) ); + const __m128 d3 = _mm_madd_ps( vX, p3X, _mm_madd_ps( vY, p3Y, _mm_madd_ps( vZ, p3Z, p3W ) ) ); + const __m128 d4 = _mm_madd_ps( vX, p4X, _mm_madd_ps( vY, p4Y, _mm_madd_ps( vZ, p4Z, p4W ) ) ); + const __m128 d5 = _mm_madd_ps( vX, p5X, _mm_madd_ps( vY, p5Y, _mm_madd_ps( vZ, p5Z, p5W ) ) ); + + __m128i c0 = __m128c( _mm_cmpgt_ps( d0, vector_float_zero ) ); + __m128i c1 = __m128c( _mm_cmpgt_ps( d1, vector_float_zero ) ); + __m128i c2 = __m128c( _mm_cmpgt_ps( d2, vector_float_zero ) ); + __m128i c3 = __m128c( _mm_cmpgt_ps( d3, vector_float_zero ) ); + __m128i c4 = __m128c( _mm_cmpgt_ps( d4, vector_float_zero ) ); + __m128i c5 = __m128c( _mm_cmpgt_ps( d5, vector_float_zero ) ); + + c0 = _mm_and_si128( c0, vector_int_mask0 ); + c1 = _mm_and_si128( c1, vector_int_mask1 ); + c2 = _mm_and_si128( c2, vector_int_mask2 ); + c3 = _mm_and_si128( c3, vector_int_mask3 ); + c4 = _mm_and_si128( c4, vector_int_mask4 ); + c5 = _mm_and_si128( c5, vector_int_mask5 ); + + c0 = _mm_or_si128( c0, c1 ); + c2 = _mm_or_si128( c2, c3 ); + c4 = _mm_or_si128( c4, c5 ); + + c0 = _mm_or_si128( c0, c2 ); + c0 = _mm_or_si128( c0, c4 ); + + __m128i s0 = _mm_packs_epi32( c0, c0 ); + __m128i b0 = _mm_packus_epi16( s0, s0 ); + + *(unsigned int *)&cullBits[i] = _mm_cvtsi128_si32( b0 ); + } + } + +} + +/* +================= +idRenderModelDecal::CreateDecal +================= +*/ +void idRenderModelDecal::CreateDecal( const idRenderModel *model, const decalProjectionParms_t &localParms ) { + int maxVerts = 0; + for ( int surfNum = 0; surfNum < model->NumSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + if ( surf->geometry != NULL && surf->shader != NULL ) { + maxVerts = Max( maxVerts, surf->geometry->numVerts ); + } + } + + idTempArray< byte > cullBits( ALIGN( maxVerts, 4 ) ); + + // check all model surfaces + for ( int surfNum = 0; surfNum < model->NumSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + + // if no geometry or no shader + if ( surf->geometry == NULL || surf->shader == NULL ) { + continue; + } + + // decals and overlays use the same rules + if ( !localParms.force && !surf->shader->AllowOverlays() ) { + continue; + } + + srfTriangles_t *tri = surf->geometry; + + // if the triangle bounds do not overlap with the projection bounds + if ( !localParms.projectionBounds.IntersectsBounds( tri->bounds ) ) { + continue; + } + + // decals don't work on animated models + assert( tri->staticModelWithJoints == NULL ); + + // catagorize all points by the planes + R_DecalPointCullStatic( cullBits.Ptr(), localParms.boundingPlanes, tri->verts, tri->numVerts ); + + // start streaming the indexes + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 3 > indexesODS( tri->indexes, tri->numIndexes ); + + // find triangles inside the projection volume + for ( int i = 0; i < tri->numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch() - 3; + + for ( ; i <= nextNumIndexes; i += 3 ) { + const int i0 = indexesODS[i + 0]; + const int i1 = indexesODS[i + 1]; + const int i2 = indexesODS[i + 2]; + + // skip triangles completely off one side + if ( cullBits[i0] & cullBits[i1] & cullBits[i2] ) { + continue; + } + + const idDrawVert * verts[3] = { + &tri->verts[i0], + &tri->verts[i1], + &tri->verts[i2] + }; + + // skip back facing triangles + const idPlane plane( verts[0]->xyz, verts[1]->xyz, verts[2]->xyz ); + if ( plane.Normal() * localParms.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2].Normal() < -0.1f ) { + continue; + } + + // create a winding with texture coordinates for the triangle + idFixedWinding fw; + fw.SetNumPoints( 3 ); + if ( localParms.parallel ) { + for ( int j = 0; j < 3; j++ ) { + fw[j] = verts[j]->xyz; + fw[j].s = localParms.textureAxis[0].Distance( verts[j]->xyz ); + fw[j].t = localParms.textureAxis[1].Distance( verts[j]->xyz ); + } + } else { + for ( int j = 0; j < 3; j++ ) { + const idVec3 dir = verts[j]->xyz - localParms.projectionOrigin; + float scale; + localParms.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 1].RayIntersection( verts[j]->xyz, dir, scale ); + const idVec3 intersection = verts[j]->xyz + scale * dir; + + fw[j] = verts[j]->xyz; + fw[j].s = localParms.textureAxis[0].Distance( intersection ); + fw[j].t = localParms.textureAxis[1].Distance( intersection ); + } + } + + const int orBits = cullBits[i0] | cullBits[i1] | cullBits[i2]; + + // clip the exact surface triangle to the projection volume + for ( int j = 0; j < NUM_DECAL_BOUNDING_PLANES; j++ ) { + if ( ( orBits & ( 1 << j ) ) != 0 ) { + if ( !fw.ClipInPlace( -localParms.boundingPlanes[j] ) ) { + break; + } + } + } + + // if there is a part of the triangle between the bounding planes then clip + // the triangle based on depth and add decals for the depth faded parts + if ( fw.GetNumPoints() != 0 ) { + idFixedWinding back; + + if ( fw.Split( &back, localParms.fadePlanes[0], 0.1f ) == SIDE_CROSS ) { + CreateDecalFromWinding( back, localParms.material, localParms.fadePlanes, localParms.fadeDepth, localParms.startTime ); + } + + if ( fw.Split( &back, localParms.fadePlanes[1], 0.1f ) == SIDE_CROSS ) { + CreateDecalFromWinding( back, localParms.material, localParms.fadePlanes, localParms.fadeDepth, localParms.startTime ); + } + + CreateDecalFromWinding( fw, localParms.material, localParms.fadePlanes, localParms.fadeDepth, localParms.startTime ); + } + } + } + } +} + +/* +===================== +idRenderModelDecal::CreateDeferredDecals +===================== +*/ +void idRenderModelDecal::CreateDeferredDecals( const idRenderModel *model ) { + for ( unsigned int i = firstDeferredDecal; i < nextDeferredDecal; i++ ) { + decalProjectionParms_t & parms = deferredDecals[i & ( MAX_DEFERRED_DECALS - 1 )]; + if ( parms.startTime > tr.viewDef->renderView.time[0] - DEFFERED_DECAL_TIMEOUT ) { + CreateDecal( model, parms ); + } + } + firstDeferredDecal = 0; + nextDeferredDecal = 0; +} + +/* +===================== +idRenderModelDecal::AddDeferredDecal +===================== +*/ +void idRenderModelDecal::AddDeferredDecal( const decalProjectionParms_t &localParms ) { + deferredDecals[nextDeferredDecal++ & ( MAX_DEFERRED_DECALS - 1 )] = localParms; + if ( nextDeferredDecal - firstDeferredDecal > MAX_DEFERRED_DECALS ) { + firstDeferredDecal = nextDeferredDecal - MAX_DEFERRED_DECALS; + } +} + +/* +===================== +idRenderModelDecal::RemoveFadedDecals +===================== +*/ +void idRenderModelDecal::RemoveFadedDecals( int time ) { + for ( unsigned int i = firstDecal; i < nextDecal; i++ ) { + decal_t & decal = decals[i & ( MAX_DECALS - 1 )]; + + const decalInfo_t decalInfo = decal.material->GetDecalInfo(); + const int minTime = time - ( decalInfo.stayTime + decalInfo.fadeTime ); + + if ( decal.startTime <= minTime ) { + decal.numVerts = 0; + decal.numIndexes = 0; + if ( i == firstDecal ) { + firstDecal++; + } + } + } + if ( firstDecal == nextDecal ) { + firstDecal = 0; + nextDecal = 0; + } +} + +/* +===================== +R_CopyDecalSurface +===================== +*/ +static void R_CopyDecalSurface( idDrawVert * verts, int numVerts, triIndex_t * indexes, int numIndexes, + const decal_t * decal, const float fadeColor[4] ) { + assert_16_byte_aligned( &verts[numVerts] ); + assert_16_byte_aligned( &indexes[numIndexes] ); + assert_16_byte_aligned( decal->indexes ); + assert_16_byte_aligned( decal->verts ); + assert( ( ( decal->numVerts * sizeof( idDrawVert ) ) & 15 ) == 0 ); + assert( ( ( decal->numIndexes * sizeof( triIndex_t ) ) & 15 ) == 0 ); + assert_16_byte_aligned( fadeColor ); + + + const __m128i vector_int_num_verts = _mm_shuffle_epi32( _mm_cvtsi32_si128( numVerts ), 0 ); + const __m128i vector_short_num_verts = _mm_packs_epi32( vector_int_num_verts, vector_int_num_verts ); + const __m128 vector_fade_color = _mm_load_ps( fadeColor ); + const __m128i vector_color_mask = _mm_set_epi32( 0, -1, 0, 0 ); + + // copy vertices and apply depth/time based fading + assert_offsetof( idDrawVert, color, 6 * 4 ); + for ( int i = 0; i < decal->numVerts; i++ ) { + const idDrawVert &srcVert = decal->verts[i]; + idDrawVert &dstVert = verts[numVerts + i]; + + __m128i v0 = _mm_load_si128( (const __m128i *)( (byte *)&srcVert + 0 ) ); + __m128i v1 = _mm_load_si128( (const __m128i *)( (byte *)&srcVert + 16 ) ); + __m128 depthFade = _mm_splat_ps( _mm_load_ss( decal->vertDepthFade + i ), 0 ); + + __m128 timeDepthFade = _mm_mul_ps( depthFade, vector_fade_color ); + __m128i colorInt = _mm_cvtps_epi32( timeDepthFade ); + __m128i colorShort = _mm_packs_epi32( colorInt, colorInt ); + __m128i colorByte = _mm_packus_epi16( colorShort, colorShort ); + v1 = _mm_or_si128( v1, _mm_and_si128( colorByte, vector_color_mask ) ); + + _mm_stream_si128( (__m128i *)( (byte *)&dstVert + 0 ), v0 ); + _mm_stream_si128( (__m128i *)( (byte *)&dstVert + 16 ), v1 ); + } + + // copy indexes + assert( ( decal->numIndexes & 7 ) == 0 ); + assert( sizeof( triIndex_t ) == 2 ); + for ( int i = 0; i < decal->numIndexes; i += 8 ) { + __m128i vi = _mm_load_si128( (const __m128i *)&decal->indexes[i] ); + + vi = _mm_add_epi16( vi, vector_short_num_verts ); + + _mm_stream_si128( (__m128i *)&indexes[numIndexes + i], vi ); + } + + _mm_sfence(); + +} + +/* +===================== +idRenderModelDecal::GetNumDecalDrawSurfs +===================== +*/ +unsigned int idRenderModelDecal::GetNumDecalDrawSurfs() { + numDecalMaterials = 0; + + for ( unsigned int i = firstDecal; i < nextDecal; i++ ) { + const decal_t & decal = decals[i & ( MAX_DECALS - 1 )]; + + unsigned int j = 0; + for ( ; j < numDecalMaterials; j++ ) { + if ( decalMaterials[j] == decal.material ) { + break; + } + } + if ( j >= numDecalMaterials ) { + decalMaterials[numDecalMaterials++] = decal.material; + } + } + + return numDecalMaterials; +} + +/* +===================== +idRenderModelDecal::CreateDecalDrawSurf +===================== +*/ +drawSurf_t * idRenderModelDecal::CreateDecalDrawSurf( const viewEntity_t *space, unsigned int index ) { + if ( index < 0 || index >= numDecalMaterials ) { + return NULL; + } + + const idMaterial * material = decalMaterials[index]; + + int maxVerts = 0; + int maxIndexes = 0; + for ( unsigned int i = firstDecal; i < nextDecal; i++ ) { + const decal_t & decal = decals[i & ( MAX_DECALS - 1 )]; + if ( decal.material == material ) { + maxVerts += decal.numVerts; + maxIndexes += decal.numIndexes; + } + } + + if ( maxVerts == 0 || maxIndexes == 0 ) { + return NULL; + } + + // create a new triangle surface in frame memory so it gets automatically disposed of + srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = maxVerts; + newTri->numIndexes = maxIndexes; + + newTri->ambientCache = vertexCache.AllocVertex( NULL, ALIGN( maxVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + newTri->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + + idDrawVert * mappedVerts = (idDrawVert *)vertexCache.MappedVertexBuffer( newTri->ambientCache ); + triIndex_t * mappedIndexes = (triIndex_t *)vertexCache.MappedIndexBuffer( newTri->indexCache ); + + const decalInfo_t decalInfo = material->GetDecalInfo(); + const int maxTime = decalInfo.stayTime + decalInfo.fadeTime; + const int time = tr.viewDef->renderView.time[0]; + + int numVerts = 0; + int numIndexes = 0; + for ( unsigned int i = firstDecal; i < nextDecal; i++ ) { + const decal_t & decal = decals[i & ( MAX_DECALS - 1 )]; + + if ( decal.numVerts == 0 ) { + if ( i == firstDecal ) { + firstDecal++; + } + continue; + } + + if ( decal.material != material ) { + continue; + } + + const int deltaTime = time - decal.startTime; + const int fadeTime = deltaTime - decalInfo.stayTime; + if ( deltaTime > maxTime ) { + continue; // already completely faded away, but not yet removed + } + + const float f = ( deltaTime > decalInfo.stayTime ) ? ( (float) fadeTime / decalInfo.fadeTime ) : 0.0f; + + ALIGNTYPE16 float fadeColor[4]; + for ( int j = 0; j < 4; j++ ) { + fadeColor[j] = 255.0f * ( decalInfo.start[j] + ( decalInfo.end[j] - decalInfo.start[j] ) * f ); + } + + // use SIMD optimized routine to copy the vertices and indices directly to write-combined memory + // this also applies any depth/time based fading while copying + R_CopyDecalSurface( mappedVerts, numVerts, mappedIndexes, numIndexes, &decal, fadeColor ); + + numVerts += decal.numVerts; + numIndexes += decal.numIndexes; + } + newTri->numVerts = numVerts; + newTri->numIndexes = numIndexes; + + // create the drawsurf + drawSurf_t * drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + drawSurf->frontEndGeo = newTri; + drawSurf->numIndexes = newTri->numIndexes; + drawSurf->ambientCache = newTri->ambientCache; + drawSurf->indexCache = newTri->indexCache; + drawSurf->shadowCache = 0; + drawSurf->jointCache = 0; + drawSurf->space = space; + drawSurf->scissorRect = space->scissorRect; + drawSurf->extraGLState = 0; + drawSurf->renderZFail = 0; + + R_SetupDrawSurfShader( drawSurf, material, &space->entityDef->parms ); + + return drawSurf; +} + +/* +==================== +idRenderModelDecal::ReadFromDemoFile +==================== +*/ +void idRenderModelDecal::ReadFromDemoFile( idDemoFile *f ) { + // FIXME: implement +} + +/* +==================== +idRenderModelDecal::WriteToDemoFile +==================== +*/ +void idRenderModelDecal::WriteToDemoFile( idDemoFile *f ) const { + // FIXME: implement +} diff --git a/neo/renderer/ModelDecal.h b/neo/renderer/ModelDecal.h new file mode 100644 index 00000000..120ba07e --- /dev/null +++ b/neo/renderer/ModelDecal.h @@ -0,0 +1,132 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODELDECAL_H__ +#define __MODELDECAL_H__ + +/* +=============================================================================== + + Decals are lightweight primitives for bullet / blood marks on static + geometry. Decals with common materials will be merged together, but + additional decals will be allocated as needed. The material should not + be one that receives lighting, because no interactions are generated + for these lightweight surfaces. + + FIXME: Decals on models in portalled off areas do not get freed + until the area becomes visible again. + +=============================================================================== +*/ + +static const int NUM_DECAL_BOUNDING_PLANES = 6; +#ifdef ID_PC +static const int MAX_DEFERRED_DECALS = 8; +static const int DEFFERED_DECAL_TIMEOUT = 1000; // don't create a decal if it wasn't visible within the first second +static const int MAX_DECALS = 64; +#else +static const int MAX_DEFERRED_DECALS = 4; +static const int DEFFERED_DECAL_TIMEOUT = 200; // don't create a decal if it wasn't visible within the first 200 milliseconds +static const int MAX_DECALS = 32; +#endif +static const int MAX_DECAL_VERTS = 3 + NUM_DECAL_BOUNDING_PLANES + 3 + 6; // 3 triangle verts clipped NUM_DECAL_BOUNDING_PLANES + 3 times (plus 6 for safety) +static const int MAX_DECAL_INDEXES = ( MAX_DECAL_VERTS - 2 ) * 3; + +compile_time_assert( CONST_ISPOWEROFTWO( MAX_DECALS ) ); +// the max indices must be a multiple of 2 for copying indices to write-combined memory +compile_time_assert( ( ( MAX_DECAL_INDEXES * sizeof( triIndex_t ) ) & 15 ) == 0 ); + +struct decalProjectionParms_t { + idPlane boundingPlanes[NUM_DECAL_BOUNDING_PLANES]; + idPlane fadePlanes[2]; + idPlane textureAxis[2]; + idVec3 projectionOrigin; + idBounds projectionBounds; + const idMaterial * material; + float fadeDepth; + int startTime; + bool parallel; + bool force; +}; + +ALIGNTYPE16 struct decal_t { + ALIGNTYPE16 idDrawVert verts[MAX_DECAL_VERTS]; + ALIGNTYPE16 triIndex_t indexes[MAX_DECAL_INDEXES]; + float vertDepthFade[MAX_DECAL_VERTS]; + int numVerts; + int numIndexes; + int startTime; + const idMaterial * material; +}; + +class idRenderModelDecal { +public: + idRenderModelDecal(); + ~idRenderModelDecal(); + + // Creates decal projection parameters. + static bool CreateProjectionParms( decalProjectionParms_t &parms, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ); + + // Transform the projection parameters from global space to local. + static void GlobalProjectionParmsToLocal( decalProjectionParms_t &localParms, const decalProjectionParms_t &globalParms, const idVec3 &origin, const idMat3 &axis ); + + // clear the model for reuse + void ReUse(); + + // Save the parameters for the renderer front-end to actually create the decal. + void AddDeferredDecal( const decalProjectionParms_t & localParms ); + + // Creates a decal on the given model. + void CreateDeferredDecals( const idRenderModel *model ); + + // Remove decals that are completely faded away. + void RemoveFadedDecals( int time ); + + unsigned int GetNumDecalDrawSurfs(); + struct drawSurf_t * CreateDecalDrawSurf( const struct viewEntity_t *space, unsigned int index ); + + void ReadFromDemoFile( class idDemoFile *f ); + void WriteToDemoFile( class idDemoFile *f ) const; + +private: + decal_t decals[MAX_DECALS]; + unsigned int firstDecal; + unsigned int nextDecal; + + decalProjectionParms_t deferredDecals[MAX_DEFERRED_DECALS]; + unsigned int firstDeferredDecal; + unsigned int nextDeferredDecal; + + const idMaterial * decalMaterials[MAX_DECALS]; + unsigned int numDecalMaterials; + + void CreateDecalFromWinding( const idWinding &w, const idMaterial *decalMaterial, const idPlane fadePlanes[2], float fadeDepth, int startTime ); + void CreateDecal( const idRenderModel *model, const decalProjectionParms_t &localParms ); +}; + +#endif /* !__MODELDECAL_H__ */ diff --git a/neo/renderer/ModelManager.cpp b/neo/renderer/ModelManager.cpp new file mode 100644 index 00000000..111ccf83 --- /dev/null +++ b/neo/renderer/ModelManager.cpp @@ -0,0 +1,739 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "Model_local.h" +#include "tr_local.h" // just for R_FreeWorldInteractions and R_CreateWorldInteractions + +idCVar r_binaryLoadRenderModels( "r_binaryLoadRenderModels", "1", 0, "enable binary load/write of render models" ); +idCVar preload_MapModels( "preload_MapModels", "1", CVAR_SYSTEM | CVAR_BOOL, "preload models during begin or end levelload" ); + +class idRenderModelManagerLocal : public idRenderModelManager { +public: + idRenderModelManagerLocal(); + virtual ~idRenderModelManagerLocal() {} + + virtual void Init(); + virtual void Shutdown(); + virtual idRenderModel * AllocModel(); + virtual void FreeModel( idRenderModel *model ); + virtual idRenderModel * FindModel( const char *modelName ); + virtual idRenderModel * CheckModel( const char *modelName ); + virtual idRenderModel * DefaultModel(); + virtual void AddModel( idRenderModel *model ); + virtual void RemoveModel( idRenderModel *model ); + virtual void ReloadModels( bool forceAll = false ); + virtual void FreeModelVertexCaches(); + virtual void WritePrecacheCommands( idFile *file ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad(); + virtual void Preload( const idPreloadManifest &manifest ); + + virtual void PrintMemInfo( MemInfo_t *mi ); + +private: + idList models; + idHashIndex hash; + idRenderModel * defaultModel; + idRenderModel * beamModel; + idRenderModel * spriteModel; + bool insideLevelLoad; // don't actually load now + + idRenderModel * GetModel( const char *modelName, bool createIfNotFound ); + + static void PrintModel_f( const idCmdArgs &args ); + static void ListModels_f( const idCmdArgs &args ); + static void ReloadModels_f( const idCmdArgs &args ); + static void TouchModel_f( const idCmdArgs &args ); +}; + + +idRenderModelManagerLocal localModelManager; +idRenderModelManager * renderModelManager = &localModelManager; + +/* +============== +idRenderModelManagerLocal::idRenderModelManagerLocal +============== +*/ +idRenderModelManagerLocal::idRenderModelManagerLocal() { + defaultModel = NULL; + beamModel = NULL; + spriteModel = NULL; + insideLevelLoad = false; +} + +/* +============== +idRenderModelManagerLocal::PrintModel_f +============== +*/ +void idRenderModelManagerLocal::PrintModel_f( const idCmdArgs &args ) { + idRenderModel *model; + + if ( args.Argc() != 2 ) { + common->Printf( "usage: printModel \n" ); + return; + } + + model = renderModelManager->CheckModel( args.Argv( 1 ) ); + if ( !model ) { + common->Printf( "model \"%s\" not found\n", args.Argv( 1 ) ); + return; + } + + model->Print(); +} + +/* +============== +idRenderModelManagerLocal::ListModels_f +============== +*/ +void idRenderModelManagerLocal::ListModels_f( const idCmdArgs &args ) { + int totalMem = 0; + int inUse = 0; + + common->Printf( " mem srf verts tris\n" ); + common->Printf( " --- --- ----- ----\n" ); + + for ( int i = 0; i < localModelManager.models.Num(); i++ ) { + idRenderModel *model = localModelManager.models[i]; + + if ( !model->IsLoaded() ) { + continue; + } + model->List(); + totalMem += model->Memory(); + inUse++; + } + + common->Printf( " --- --- ----- ----\n" ); + common->Printf( " mem srf verts tris\n" ); + + common->Printf( "%i loaded models\n", inUse ); + common->Printf( "total memory: %4.1fM\n", (float)totalMem / (1024*1024) ); +} + +/* +============== +idRenderModelManagerLocal::ReloadModels_f +============== +*/ +void idRenderModelManagerLocal::ReloadModels_f( const idCmdArgs &args ) { + if ( idStr::Icmp( args.Argv(1), "all" ) == 0 ) { + localModelManager.ReloadModels( true ); + } else { + localModelManager.ReloadModels( false ); + } +} + +/* +============== +idRenderModelManagerLocal::TouchModel_f + +Precache a specific model +============== +*/ +void idRenderModelManagerLocal::TouchModel_f( const idCmdArgs &args ) { + const char *model = args.Argv( 1 ); + + if ( !model[0] ) { + common->Printf( "usage: touchModel \n" ); + return; + } + + common->Printf( "touchModel %s\n", model ); + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + idRenderModel *m = renderModelManager->CheckModel( model ); + if ( !m ) { + common->Printf( "...not found\n" ); + } +} + +/* +================= +idRenderModelManagerLocal::WritePrecacheCommands +================= +*/ +void idRenderModelManagerLocal::WritePrecacheCommands( idFile *f ) { + for ( int i = 0; i < models.Num(); i++ ) { + idRenderModel *model = models[i]; + + if ( !model ) { + continue; + } + if ( !model->IsReloadable() ) { + continue; + } + + char str[1024]; + sprintf( str, "touchModel %s\n", model->Name() ); + common->Printf( "%s", str ); + f->Printf( "%s", str ); + } +} + +/* +================= +idRenderModelManagerLocal::Init +================= +*/ +void idRenderModelManagerLocal::Init() { + cmdSystem->AddCommand( "listModels", ListModels_f, CMD_FL_RENDERER, "lists all models" ); + cmdSystem->AddCommand( "printModel", PrintModel_f, CMD_FL_RENDERER, "prints model info", idCmdSystem::ArgCompletion_ModelName ); + cmdSystem->AddCommand( "reloadModels", ReloadModels_f, CMD_FL_RENDERER|CMD_FL_CHEAT, "reloads models" ); + cmdSystem->AddCommand( "touchModel", TouchModel_f, CMD_FL_RENDERER, "touches a model", idCmdSystem::ArgCompletion_ModelName ); + + insideLevelLoad = false; + + // create a default model + idRenderModelStatic *model = new (TAG_MODEL) idRenderModelStatic; + model->InitEmpty( "_DEFAULT" ); + model->MakeDefaultModel(); + model->SetLevelLoadReferenced( true ); + defaultModel = model; + AddModel( model ); + + // create the beam model + idRenderModelStatic *beam = new (TAG_MODEL) idRenderModelBeam; + beam->InitEmpty( "_BEAM" ); + beam->SetLevelLoadReferenced( true ); + beamModel = beam; + AddModel( beam ); + + idRenderModelStatic *sprite = new (TAG_MODEL) idRenderModelSprite; + sprite->InitEmpty( "_SPRITE" ); + sprite->SetLevelLoadReferenced( true ); + spriteModel = sprite; + AddModel( sprite ); +} + +/* +================= +idRenderModelManagerLocal::Shutdown +================= +*/ +void idRenderModelManagerLocal::Shutdown() { + models.DeleteContents( true ); + hash.Free(); +} + +/* +================= +idRenderModelManagerLocal::GetModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::GetModel( const char *_modelName, bool createIfNotFound ) { + + if ( !_modelName || !_modelName[0] ) { + return NULL; + } + + idStrStatic< MAX_OSPATH > canonical = _modelName; + canonical.ToLower(); + + idStrStatic< MAX_OSPATH > extension; + canonical.ExtractFileExtension( extension ); + + // see if it is already present + int key = hash.GenerateKey( canonical, false ); + for ( int i = hash.First( key ); i != -1; i = hash.Next( i ) ) { + idRenderModel *model = models[i]; + + if ( canonical.Icmp( model->Name() ) == 0 ) { + if ( !model->IsLoaded() ) { + // reload it if it was purged + idStr generatedFileName = "generated/rendermodels/"; + generatedFileName.AppendPath( canonical ); + generatedFileName.SetFileExtension( va( "b%s", extension.c_str() ) ); + if ( model->SupportsBinaryModel() && r_binaryLoadRenderModels.GetBool() ) { + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + model->PurgeModel(); + if ( !model->LoadBinaryModel( file, 0 ) ) { + model->LoadModel(); + } + } else { + model->LoadModel(); + } + } else if ( insideLevelLoad && !model->IsLevelLoadReferenced() ) { + // we are reusing a model already in memory, but + // touch all the materials to make sure they stay + // in memory as well + model->TouchData(); + } + model->SetLevelLoadReferenced( true ); + return model; + } + } + + // see if we can load it + + // determine which subclass of idRenderModel to initialize + + idRenderModel * model = NULL; + + if ( ( extension.Icmp( "ase" ) == 0 ) || ( extension.Icmp( "lwo" ) == 0 ) || ( extension.Icmp( "flt" ) == 0 ) || ( extension.Icmp( "ma" ) == 0 ) ) { + model = new (TAG_MODEL) idRenderModelStatic; + } else if ( extension.Icmp( MD5_MESH_EXT ) == 0 ) { + model = new (TAG_MODEL) idRenderModelMD5; + } else if ( extension.Icmp( "md3" ) == 0 ) { + model = new (TAG_MODEL) idRenderModelMD3; + } else if ( extension.Icmp( "prt" ) == 0 ) { + model = new (TAG_MODEL) idRenderModelPrt; + } else if ( extension.Icmp( "liquid" ) == 0 ) { + model = new (TAG_MODEL) idRenderModelLiquid; + } + + idStrStatic< MAX_OSPATH > generatedFileName; + + if ( model != NULL ) { + + generatedFileName = "generated/rendermodels/"; + generatedFileName.AppendPath( canonical ); + generatedFileName.SetFileExtension( va( "b%s", extension.c_str() ) ); + + // Get the timestamp on the original file, if it's newer than what is stored in binary model, regenerate it + ID_TIME_T sourceTimeStamp = fileSystem->GetTimestamp( canonical ); + + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + + if ( !model->SupportsBinaryModel() || !r_binaryLoadRenderModels.GetBool() ) { + model->InitFromFile( canonical ); + } else { + if ( !model->LoadBinaryModel( file, sourceTimeStamp ) ) { + model->InitFromFile( canonical ); + + idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); + idLib::Printf( "Writing %s\n", generatedFileName.c_str() ); + model->WriteBinaryModel( outputFile ); + } /* else { + idLib::Printf( "loaded binary model %s from file %s\n", model->Name(), generatedFileName.c_str() ); + } */ + } + } + + // Not one of the known formats + if ( model == NULL ) { + + if ( extension.Length() ) { + common->Warning( "unknown model type '%s'", canonical.c_str() ); + } + + if ( !createIfNotFound ) { + return NULL; + } + + idRenderModelStatic *smodel = new (TAG_MODEL) idRenderModelStatic; + smodel->InitEmpty( canonical ); + smodel->MakeDefaultModel(); + + model = smodel; + } + + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + fileSystem->AddModelPreload( canonical ); + } + model->SetLevelLoadReferenced( true ); + + if ( !createIfNotFound && model->IsDefaultModel() ) { + delete model; + model = NULL; + + return NULL; + } + + if ( cvarSystem->GetCVarBool( "fs_buildgame" ) ) { + fileSystem->AddModelPreload( model->Name() ); + } + + AddModel( model ); + + return model; +} + +/* +================= +idRenderModelManagerLocal::AllocModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::AllocModel() { + return new (TAG_MODEL) idRenderModelStatic(); +} + +/* +================= +idRenderModelManagerLocal::FreeModel +================= +*/ +void idRenderModelManagerLocal::FreeModel( idRenderModel *model ) { + if ( !model ) { + return; + } + if ( !dynamic_cast( model ) ) { + common->Error( "idRenderModelManager::FreeModel: model '%s' is not a static model", model->Name() ); + return; + } + if ( model == defaultModel ) { + common->Error( "idRenderModelManager::FreeModel: can't free the default model" ); + return; + } + if ( model == beamModel ) { + common->Error( "idRenderModelManager::FreeModel: can't free the beam model" ); + return; + } + if ( model == spriteModel ) { + common->Error( "idRenderModelManager::FreeModel: can't free the sprite model" ); + return; + } + + R_CheckForEntityDefsUsingModel( model ); + + delete model; +} + +/* +================= +idRenderModelManagerLocal::FindModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::FindModel( const char *modelName ) { + return GetModel( modelName, true ); +} + +/* +================= +idRenderModelManagerLocal::CheckModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::CheckModel( const char *modelName ) { + return GetModel( modelName, false ); +} + +/* +================= +idRenderModelManagerLocal::DefaultModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::DefaultModel() { + return defaultModel; +} + +/* +================= +idRenderModelManagerLocal::AddModel +================= +*/ +void idRenderModelManagerLocal::AddModel( idRenderModel *model ) { + hash.Add( hash.GenerateKey( model->Name(), false ), models.Append( model ) ); +} + +/* +================= +idRenderModelManagerLocal::RemoveModel +================= +*/ +void idRenderModelManagerLocal::RemoveModel( idRenderModel *model ) { + int index = models.FindIndex( model ); + if ( index != -1 ) { + hash.RemoveIndex( hash.GenerateKey( model->Name(), false ), index ); + models.RemoveIndex( index ); + } +} + +/* +================= +idRenderModelManagerLocal::ReloadModels +================= +*/ +void idRenderModelManagerLocal::ReloadModels( bool forceAll ) { + if ( forceAll ) { + common->Printf( "Reloading all model files...\n" ); + } else { + common->Printf( "Checking for changed model files...\n" ); + } + + R_FreeDerivedData(); + + // skip the default model at index 0 + for ( int i = 1; i < models.Num(); i++ ) { + idRenderModel *model = models[i]; + + // we may want to allow world model reloading in the future, but we don't now + if ( !model->IsReloadable() ) { + continue; + } + + if ( !forceAll ) { + // check timestamp + ID_TIME_T current; + + fileSystem->ReadFile( model->Name(), NULL, ¤t ); + if ( current <= model->Timestamp() ) { + continue; + } + } + + common->DPrintf( "reloading %s.\n", model->Name() ); + + model->LoadModel(); + } + + // we must force the world to regenerate, because models may + // have changed size, making their references invalid + R_ReCreateWorldReferences(); +} + +/* +================= +idRenderModelManagerLocal::FreeModelVertexCaches +================= +*/ +void idRenderModelManagerLocal::FreeModelVertexCaches() { + for ( int i = 0; i < models.Num(); i++ ) { + idRenderModel *model = models[i]; + model->FreeVertexCache(); + } +} + +/* +================= +idRenderModelManagerLocal::BeginLevelLoad +================= +*/ +void idRenderModelManagerLocal::BeginLevelLoad() { + insideLevelLoad = true; + + for ( int i = 0; i < models.Num(); i++ ) { + idRenderModel *model = models[i]; + + // always reload all models + if ( model->IsReloadable() ) { + R_CheckForEntityDefsUsingModel( model ); + model->PurgeModel(); + } + + model->SetLevelLoadReferenced( false ); + } + + vertexCache.FreeStaticData(); +} + +/* +================= +idRenderModelManagerLocal::Preload +================= +*/ +void idRenderModelManagerLocal::Preload( const idPreloadManifest &manifest ) { + if ( preload_MapModels.GetBool() ) { + // preload this levels images + int start = Sys_Milliseconds(); + int numLoaded = 0; + idList< preloadSort_t > preloadSort; + preloadSort.Resize( manifest.NumResources() ); + for ( int i = 0; i < manifest.NumResources(); i++ ) { + const preloadEntry_s & p = manifest.GetPreloadByIndex( i ); + idResourceCacheEntry rc; + idStrStatic< MAX_OSPATH > filename; + if ( p.resType == PRELOAD_MODEL ) { + filename = "generated/rendermodels/"; + filename += p.resourceName; + idStrStatic< 16 > ext; + filename.ExtractFileExtension( ext ); + filename.SetFileExtension( va( "b%s", ext.c_str() ) ); + } + if ( p.resType == PRELOAD_PARTICLE ) { + filename = "generated/particles/"; + filename += p.resourceName; + filename += ".bprt"; + } + if ( !filename.IsEmpty() ) { + if ( fileSystem->GetResourceCacheEntry( filename, rc ) ) { + preloadSort_t ps = {}; + ps.idx = i; + ps.ofs = rc.offset; + preloadSort.Append( ps ); + } + } + } + + preloadSort.SortWithTemplate( idSort_Preload() ); + + for ( int i = 0; i < preloadSort.Num(); i++ ) { + const preloadSort_t & ps = preloadSort[ i ]; + const preloadEntry_s & p = manifest.GetPreloadByIndex( ps.idx ); + if ( p.resType == PRELOAD_MODEL ) { + idRenderModel * model = FindModel( p.resourceName ); + if ( model != NULL ) { + model->SetLevelLoadReferenced( true ); + } + } else if ( p.resType == PRELOAD_PARTICLE ) { + declManager->FindType( DECL_PARTICLE, p.resourceName ); + } + numLoaded++; + } + + int end = Sys_Milliseconds(); + common->Printf( "%05d models preloaded ( or were already loaded ) in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 ); + common->Printf( "----------------------------------------\n" ); + } +} + + + +/* +================= +idRenderModelManagerLocal::EndLevelLoad +================= +*/ +void idRenderModelManagerLocal::EndLevelLoad() { + common->Printf( "----- idRenderModelManagerLocal::EndLevelLoad -----\n" ); + + int start = Sys_Milliseconds(); + + insideLevelLoad = false; + int purgeCount = 0; + int keepCount = 0; + int loadCount = 0; + + // purge any models not touched + for ( int i = 0; i < models.Num(); i++ ) { + idRenderModel *model = models[i]; + + if ( !model->IsLevelLoadReferenced() && model->IsLoaded() && model->IsReloadable() ) { + +// common->Printf( "purging %s\n", model->Name() ); + + purgeCount++; + + R_CheckForEntityDefsUsingModel( model ); + + model->PurgeModel(); + + } else { + +// common->Printf( "keeping %s\n", model->Name() ); + + keepCount++; + } + + common->UpdateLevelLoadPacifier(); + } + + // load any new ones + for ( int i = 0; i < models.Num(); i++ ) { + common->UpdateLevelLoadPacifier(); + + + idRenderModel *model = models[i]; + + if ( model->IsLevelLoadReferenced() && !model->IsLoaded() && model->IsReloadable() ) { + loadCount++; + model->LoadModel(); + } + } + + // create static vertex/index buffers for all models + for ( int i = 0; i < models.Num(); i++ ) { + common->UpdateLevelLoadPacifier(); + + + idRenderModel *model = models[i]; + if ( model->IsLoaded() ) { + for ( int j = 0; j < model->NumSurfaces(); j++ ) { + R_CreateStaticBuffersForTri( *(model->Surface( j )->geometry) ); + } + } + } + + + // _D3XP added this + int end = Sys_Milliseconds(); + common->Printf( "%5i models purged from previous level, ", purgeCount ); + common->Printf( "%5i models kept.\n", keepCount ); + if ( loadCount ) { + common->Printf( "%5i new models loaded in %5.1f seconds\n", loadCount, (end-start) * 0.001 ); + } + common->Printf( "---------------------------------------------------\n" ); +} + +/* +================= +idRenderModelManagerLocal::PrintMemInfo +================= +*/ +void idRenderModelManagerLocal::PrintMemInfo( MemInfo_t *mi ) { + int i, j, totalMem = 0; + int *sortIndex; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_models.txt" ); + if ( !f ) { + return; + } + + // sort first + sortIndex = new (TAG_MODEL) int[ localModelManager.models.Num()]; + + for ( i = 0; i < localModelManager.models.Num(); i++ ) { + sortIndex[i] = i; + } + + for ( i = 0; i < localModelManager.models.Num() - 1; i++ ) { + for ( j = i + 1; j < localModelManager.models.Num(); j++ ) { + if ( localModelManager.models[sortIndex[i]]->Memory() < localModelManager.models[sortIndex[j]]->Memory() ) { + int temp = sortIndex[i]; + sortIndex[i] = sortIndex[j]; + sortIndex[j] = temp; + } + } + } + + // print next + for ( int i = 0; i < localModelManager.models.Num(); i++ ) { + idRenderModel *model = localModelManager.models[sortIndex[i]]; + int mem; + + if ( !model->IsLoaded() ) { + continue; + } + + mem = model->Memory(); + totalMem += mem; + f->Printf( "%s %s\n", idStr::FormatNumber( mem ).c_str(), model->Name() ); + } + + delete [] sortIndex; + mi->modelAssetsTotal = totalMem; + + f->Printf( "\nTotal model bytes allocated: %s\n", idStr::FormatNumber( totalMem ).c_str() ); + fileSystem->CloseFile( f ); +} diff --git a/neo/renderer/ModelManager.h b/neo/renderer/ModelManager.h new file mode 100644 index 00000000..1646be78 --- /dev/null +++ b/neo/renderer/ModelManager.h @@ -0,0 +1,102 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODELMANAGER_H__ +#define __MODELMANAGER_H__ + +/* +=============================================================================== + + Model Manager + + Temporarily created models do not need to be added to the model manager. + +=============================================================================== +*/ + +class idRenderModelManager { +public: + virtual ~idRenderModelManager() {} + + // registers console commands and clears the list + virtual void Init() = 0; + + // frees all the models + virtual void Shutdown() = 0; + + // called only by renderer::BeginLevelLoad + virtual void BeginLevelLoad() = 0; + + // called only by renderer::EndLevelLoad + virtual void EndLevelLoad() = 0; + + // called only by renderer::Preload + virtual void Preload( const idPreloadManifest &manifest ) = 0; + + // allocates a new empty render model. + virtual idRenderModel * AllocModel() = 0; + + // frees a render model + virtual void FreeModel( idRenderModel *model ) = 0; + + // returns NULL if modelName is NULL or an empty string, otherwise + // it will create a default model if not loadable + virtual idRenderModel * FindModel( const char *modelName ) = 0; + + // returns NULL if not loadable + virtual idRenderModel * CheckModel( const char *modelName ) = 0; + + // returns the default cube model + virtual idRenderModel * DefaultModel() = 0; + + // world map parsing will add all the inline models with this call + virtual void AddModel( idRenderModel *model ) = 0; + + // when a world map unloads, it removes its internal models from the list + // before freeing them. + // There may be an issue with multiple renderWorlds that share data... + virtual void RemoveModel( idRenderModel *model ) = 0; + + // the reloadModels console command calls this, but it can + // also be explicitly invoked + virtual void ReloadModels( bool forceAll = false ) = 0; + + // write "touchModel " commands for each non-world-map model + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // called during vid_restart + virtual void FreeModelVertexCaches() = 0; + + // print memory info + virtual void PrintMemInfo( MemInfo_t *mi ) = 0; +}; + +// this will be statically pointed at a private implementation +extern idRenderModelManager *renderModelManager; + +#endif /* !__MODELMANAGER_H__ */ diff --git a/neo/renderer/ModelOverlay.cpp b/neo/renderer/ModelOverlay.cpp new file mode 100644 index 00000000..fa2b1962 --- /dev/null +++ b/neo/renderer/ModelOverlay.cpp @@ -0,0 +1,648 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +#include "../idlib/geometry/DrawVert_intrinsics.h" + +/* +==================== +idRenderModelOverlay::idRenderModelOverlay +==================== +*/ +idRenderModelOverlay::idRenderModelOverlay() : + firstOverlay( 0 ), + nextOverlay( 0 ), + firstDeferredOverlay( 0 ), + nextDeferredOverlay( 0 ), + numOverlayMaterials( 0 ) { + memset( overlays, 0, sizeof( overlays ) ); +} + +/* +==================== +idRenderModelOverlay::~idRenderModelOverlay +==================== +*/ +idRenderModelOverlay::~idRenderModelOverlay() { + for ( unsigned int i = 0; i < MAX_OVERLAYS; i++ ) { + FreeOverlay( overlays[i] ); + } +} + +/* +================= +idRenderModelOverlay::ReUse +================= +*/ +void idRenderModelOverlay::ReUse() { + firstOverlay = 0; + nextOverlay = 0; + firstDeferredOverlay = 0; + nextDeferredOverlay = 0; + numOverlayMaterials = 0; + + for ( unsigned int i = 0; i < MAX_OVERLAYS; i++ ) { + FreeOverlay( overlays[i] ); + } +} + +/* +==================== +idRenderModelOverlay::FreeOverlay +==================== +*/ +void idRenderModelOverlay::FreeOverlay( overlay_t & overlay ) { + if ( overlay.verts != NULL ) { + Mem_Free( overlay.verts ); + } + if ( overlay.indexes != NULL ) { + Mem_Free( overlay.indexes ); + } + memset( &overlay, 0, sizeof( overlay ) ); +} + +/* +==================== +R_OverlayPointCullStatic +==================== +*/ +static void R_OverlayPointCullStatic( byte * cullBits, halfFloat_t * texCoordS, halfFloat_t * texCoordT, const idPlane * planes, const idDrawVert * verts, const int numVerts ) { + assert_16_byte_aligned( cullBits ); + assert_16_byte_aligned( texCoordS ); + assert_16_byte_aligned( texCoordT ); + assert_16_byte_aligned( verts ); + + + idODSStreamedArray< idDrawVert, 16, SBT_DOUBLE, 4 > vertsODS( verts, numVerts ); + + const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; + const __m128 vector_float_one = { 1.0f, 1.0f, 1.0f, 1.0f }; + const __m128i vector_int_mask0 = _mm_set1_epi32( 1 << 0 ); + const __m128i vector_int_mask1 = _mm_set1_epi32( 1 << 1 ); + const __m128i vector_int_mask2 = _mm_set1_epi32( 1 << 2 ); + const __m128i vector_int_mask3 = _mm_set1_epi32( 1 << 3 ); + + const __m128 p0 = _mm_loadu_ps( planes[0].ToFloatPtr() ); + const __m128 p1 = _mm_loadu_ps( planes[1].ToFloatPtr() ); + + const __m128 p0X = _mm_splat_ps( p0, 0 ); + const __m128 p0Y = _mm_splat_ps( p0, 1 ); + const __m128 p0Z = _mm_splat_ps( p0, 2 ); + const __m128 p0W = _mm_splat_ps( p0, 3 ); + + const __m128 p1X = _mm_splat_ps( p1, 0 ); + const __m128 p1Y = _mm_splat_ps( p1, 1 ); + const __m128 p1Z = _mm_splat_ps( p1, 2 ); + const __m128 p1W = _mm_splat_ps( p1, 3 ); + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 4; + + for ( ; i <= nextNumVerts; i += 4 ) { + const __m128 v0 = _mm_load_ps( vertsODS[i + 0].xyz.ToFloatPtr() ); + const __m128 v1 = _mm_load_ps( vertsODS[i + 1].xyz.ToFloatPtr() ); + const __m128 v2 = _mm_load_ps( vertsODS[i + 2].xyz.ToFloatPtr() ); + const __m128 v3 = _mm_load_ps( vertsODS[i + 3].xyz.ToFloatPtr() ); + + const __m128 r0 = _mm_unpacklo_ps( v0, v2 ); // v0.x, v2.x, v0.z, v2.z + const __m128 r1 = _mm_unpackhi_ps( v0, v2 ); // v0.y, v2.y, v0.w, v2.w + const __m128 r2 = _mm_unpacklo_ps( v1, v3 ); // v1.x, v3.x, v1.z, v3.z + const __m128 r3 = _mm_unpackhi_ps( v1, v3 ); // v1.y, v3.y, v1.w, v3.w + + const __m128 vX = _mm_unpacklo_ps( r0, r2 ); // v0.x, v1.x, v2.x, v3.x + const __m128 vY = _mm_unpackhi_ps( r0, r2 ); // v0.y, v1.y, v2.y, v3.y + const __m128 vZ = _mm_unpacklo_ps( r1, r3 ); // v0.z, v1.z, v2.z, v3.z + + const __m128 d0 = _mm_madd_ps( vX, p0X, _mm_madd_ps( vY, p0Y, _mm_madd_ps( vZ, p0Z, p0W ) ) ); + const __m128 d1 = _mm_madd_ps( vX, p1X, _mm_madd_ps( vY, p1Y, _mm_madd_ps( vZ, p1Z, p1W ) ) ); + const __m128 d2 = _mm_sub_ps( vector_float_one, d0 ); + const __m128 d3 = _mm_sub_ps( vector_float_one, d1 ); + + __m128i flt16S = FastF32toF16( __m128c( d0 ) ); + __m128i flt16T = FastF32toF16( __m128c( d1 ) ); + + _mm_storel_epi64( (__m128i *)&texCoordS[i], flt16S ); + _mm_storel_epi64( (__m128i *)&texCoordT[i], flt16T ); + + __m128i c0 = __m128c( _mm_cmplt_ps( d0, vector_float_zero ) ); + __m128i c1 = __m128c( _mm_cmplt_ps( d1, vector_float_zero ) ); + __m128i c2 = __m128c( _mm_cmplt_ps( d2, vector_float_zero ) ); + __m128i c3 = __m128c( _mm_cmplt_ps( d3, vector_float_zero ) ); + + c0 = _mm_and_si128( c0, vector_int_mask0 ); + c1 = _mm_and_si128( c1, vector_int_mask1 ); + c2 = _mm_and_si128( c2, vector_int_mask2 ); + c3 = _mm_and_si128( c3, vector_int_mask3 ); + + c0 = _mm_or_si128( c0, c1 ); + c2 = _mm_or_si128( c2, c3 ); + c0 = _mm_or_si128( c0, c2 ); + + c0 = _mm_packs_epi32( c0, c0 ); + c0 = _mm_packus_epi16( c0, c0 ); + + *(unsigned int *)&cullBits[i] = _mm_cvtsi128_si32( c0 ); + } + } + +} + +/* +==================== +R_OverlayPointCullSkinned +==================== +*/ +static void R_OverlayPointCullSkinned( byte * cullBits, halfFloat_t * texCoordS, halfFloat_t * texCoordT, const idPlane * planes, const idDrawVert * verts, const int numVerts, const idJointMat * joints ) { + assert_16_byte_aligned( cullBits ); + assert_16_byte_aligned( texCoordS ); + assert_16_byte_aligned( texCoordT ); + assert_16_byte_aligned( verts ); + + + idODSStreamedArray< idDrawVert, 16, SBT_DOUBLE, 4 > vertsODS( verts, numVerts ); + + const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; + const __m128 vector_float_one = { 1.0f, 1.0f, 1.0f, 1.0f }; + const __m128i vector_int_mask0 = _mm_set1_epi32( 1 << 0 ); + const __m128i vector_int_mask1 = _mm_set1_epi32( 1 << 1 ); + const __m128i vector_int_mask2 = _mm_set1_epi32( 1 << 2 ); + const __m128i vector_int_mask3 = _mm_set1_epi32( 1 << 3 ); + + const __m128 p0 = _mm_loadu_ps( planes[0].ToFloatPtr() ); + const __m128 p1 = _mm_loadu_ps( planes[1].ToFloatPtr() ); + + const __m128 p0X = _mm_splat_ps( p0, 0 ); + const __m128 p0Y = _mm_splat_ps( p0, 1 ); + const __m128 p0Z = _mm_splat_ps( p0, 2 ); + const __m128 p0W = _mm_splat_ps( p0, 3 ); + + const __m128 p1X = _mm_splat_ps( p1, 0 ); + const __m128 p1Y = _mm_splat_ps( p1, 1 ); + const __m128 p1Z = _mm_splat_ps( p1, 2 ); + const __m128 p1W = _mm_splat_ps( p1, 3 ); + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 4; + + for ( ; i <= nextNumVerts; i += 4 ) { + const __m128 v0 = LoadSkinnedDrawVertPosition( vertsODS[i + 0], joints ); + const __m128 v1 = LoadSkinnedDrawVertPosition( vertsODS[i + 1], joints ); + const __m128 v2 = LoadSkinnedDrawVertPosition( vertsODS[i + 2], joints ); + const __m128 v3 = LoadSkinnedDrawVertPosition( vertsODS[i + 3], joints ); + + const __m128 r0 = _mm_unpacklo_ps( v0, v2 ); // v0.x, v2.x, v0.z, v2.z + const __m128 r1 = _mm_unpackhi_ps( v0, v2 ); // v0.y, v2.y, v0.w, v2.w + const __m128 r2 = _mm_unpacklo_ps( v1, v3 ); // v1.x, v3.x, v1.z, v3.z + const __m128 r3 = _mm_unpackhi_ps( v1, v3 ); // v1.y, v3.y, v1.w, v3.w + + const __m128 vX = _mm_unpacklo_ps( r0, r2 ); // v0.x, v1.x, v2.x, v3.x + const __m128 vY = _mm_unpackhi_ps( r0, r2 ); // v0.y, v1.y, v2.y, v3.y + const __m128 vZ = _mm_unpacklo_ps( r1, r3 ); // v0.z, v1.z, v2.z, v3.z + + const __m128 d0 = _mm_madd_ps( vX, p0X, _mm_madd_ps( vY, p0Y, _mm_madd_ps( vZ, p0Z, p0W ) ) ); + const __m128 d1 = _mm_madd_ps( vX, p1X, _mm_madd_ps( vY, p1Y, _mm_madd_ps( vZ, p1Z, p1W ) ) ); + const __m128 d2 = _mm_sub_ps( vector_float_one, d0 ); + const __m128 d3 = _mm_sub_ps( vector_float_one, d1 ); + + __m128i flt16S = FastF32toF16( __m128c( d0 ) ); + __m128i flt16T = FastF32toF16( __m128c( d1 ) ); + + _mm_storel_epi64( (__m128i *)&texCoordS[i], flt16S ); + _mm_storel_epi64( (__m128i *)&texCoordT[i], flt16T ); + + __m128i c0 = __m128c( _mm_cmplt_ps( d0, vector_float_zero ) ); + __m128i c1 = __m128c( _mm_cmplt_ps( d1, vector_float_zero ) ); + __m128i c2 = __m128c( _mm_cmplt_ps( d2, vector_float_zero ) ); + __m128i c3 = __m128c( _mm_cmplt_ps( d3, vector_float_zero ) ); + + c0 = _mm_and_si128( c0, vector_int_mask0 ); + c1 = _mm_and_si128( c1, vector_int_mask1 ); + c2 = _mm_and_si128( c2, vector_int_mask2 ); + c3 = _mm_and_si128( c3, vector_int_mask3 ); + + c0 = _mm_or_si128( c0, c1 ); + c2 = _mm_or_si128( c2, c3 ); + c0 = _mm_or_si128( c0, c2 ); + + c0 = _mm_packs_epi32( c0, c0 ); + c0 = _mm_packus_epi16( c0, c0 ); + + *(unsigned int *)&cullBits[i] = _mm_cvtsi128_si32( c0 ); + } + } + +} + +/* +===================== +idRenderModelOverlay::CreateOverlay + +This projects on both front and back sides to avoid seams +The material should be clamped, because entire triangles are added, some of which +may extend well past the 0.0 to 1.0 texture range +===================== +*/ +void idRenderModelOverlay::CreateOverlay( const idRenderModel *model, const idPlane localTextureAxis[2], const idMaterial *material ) { + // count up the maximum possible vertices and indexes per surface + int maxVerts = 0; + int maxIndexes = 0; + for ( int surfNum = 0; surfNum < model->NumSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + if ( surf->geometry->numVerts > maxVerts ) { + maxVerts = surf->geometry->numVerts; + } + if ( surf->geometry->numIndexes > maxIndexes ) { + maxIndexes = surf->geometry->numIndexes; + } + } + maxIndexes += 3 * 16 / sizeof( triIndex_t ); // to allow the index size to be a multiple of 16 bytes + + // make temporary buffers for the building process + idTempArray< byte > cullBits( maxVerts ); + idTempArray< halfFloat_t > texCoordS( maxVerts ); + idTempArray< halfFloat_t > texCoordT( maxVerts ); + idTempArray< triIndex_t > vertexRemap( maxVerts ); + idTempArray< overlayVertex_t > overlayVerts( maxVerts ); + idTempArray< triIndex_t > overlayIndexes( maxIndexes ); + + // pull out the triangles we need from the base surfaces + for ( int surfNum = 0; surfNum < model->NumBaseSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + + if ( surf->geometry == NULL || surf->shader == NULL ) { + continue; + } + + // some surfaces can explicitly disallow overlays + if ( !surf->shader->AllowOverlays() ) { + continue; + } + + const srfTriangles_t *tri = surf->geometry; + + // try to cull the whole surface along the first texture axis + const float d0 = tri->bounds.PlaneDistance( localTextureAxis[0] ); + if ( d0 < 0.0f || d0 > 1.0f ) { + continue; + } + + // try to cull the whole surface along the second texture axis + const float d1 = tri->bounds.PlaneDistance( localTextureAxis[1] ); + if ( d1 < 0.0f || d1 > 1.0f ) { + continue; + } + + if ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) { + R_OverlayPointCullSkinned( cullBits.Ptr(), texCoordS.Ptr(), texCoordT.Ptr(), localTextureAxis, tri->verts, tri->numVerts, tri->staticModelWithJoints->jointsInverted ); + } else { + R_OverlayPointCullStatic( cullBits.Ptr(), texCoordS.Ptr(), texCoordT.Ptr(), localTextureAxis, tri->verts, tri->numVerts ); + } + + // start streaming the indexes + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 3 > indexesODS( tri->indexes, tri->numIndexes ); + + memset( vertexRemap.Ptr(), -1, vertexRemap.Size() ); + int numIndexes = 0; + int numVerts = 0; + int maxReferencedVertex = 0; + + // find triangles that need the overlay + for ( int i = 0; i < tri->numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch() - 3; + + for ( ; i <= nextNumIndexes; i += 3 ) { + const int i0 = indexesODS[i + 0]; + const int i1 = indexesODS[i + 1]; + const int i2 = indexesODS[i + 2]; + + // skip triangles completely off one side + if ( cullBits[i0] & cullBits[i1] & cullBits[i2] ) { + continue; + } + + // we could do more precise triangle culling, like a light interaction does, but it's not worth it + + // keep this triangle + for ( int j = 0; j < 3; j++ ) { + int index = tri->indexes[i + j]; + if ( vertexRemap[index] == (triIndex_t) -1 ) { + vertexRemap[index] = numVerts; + + overlayVerts[numVerts].vertexNum = index; + overlayVerts[numVerts].st[0] = texCoordS[index]; + overlayVerts[numVerts].st[1] = texCoordT[index]; + numVerts++; + + maxReferencedVertex = Max( maxReferencedVertex, index ); + } + overlayIndexes[numIndexes] = vertexRemap[index]; + numIndexes++; + } + } + } + + if ( numIndexes == 0 ) { + continue; + } + + // add degenerate triangles until the index size is a multiple of 16 bytes + for ( ; ( ( ( numIndexes * sizeof( triIndex_t ) ) & 15 ) != 0 ); numIndexes += 3 ) { + overlayIndexes[numIndexes + 0] = 0; + overlayIndexes[numIndexes + 1] = 0; + overlayIndexes[numIndexes + 2] = 0; + } + + // allocate a new overlay + overlay_t & overlay = overlays[nextOverlay++ & ( MAX_OVERLAYS - 1 )]; + FreeOverlay( overlay ); + overlay.material = material; + overlay.surfaceNum = surfNum; + overlay.surfaceId = surf->id; + overlay.numIndexes = numIndexes; + overlay.indexes = (triIndex_t *)Mem_Alloc( numIndexes * sizeof( overlay.indexes[0] ), TAG_MODEL ); + memcpy( overlay.indexes, overlayIndexes.Ptr(), numIndexes * sizeof( overlay.indexes[0] ) ); + overlay.numVerts = numVerts; + overlay.verts = (overlayVertex_t *)Mem_Alloc( numVerts * sizeof( overlay.verts[0] ), TAG_MODEL ); + memcpy( overlay.verts, overlayVerts.Ptr(), numVerts * sizeof( overlay.verts[0] ) ); + overlay.maxReferencedVertex = maxReferencedVertex; + + if ( nextOverlay - firstOverlay > MAX_OVERLAYS ) { + firstOverlay = nextOverlay - MAX_OVERLAYS; + } + } +} + +/* +==================== +idRenderModelOverlay::CreateDeferredOverlays +==================== +*/ +void idRenderModelOverlay::CreateDeferredOverlays( const idRenderModel * model ) { + for ( unsigned int i = firstDeferredOverlay; i < nextDeferredOverlay; i++ ) { + const overlayProjectionParms_t & parms = deferredOverlays[i & ( MAX_DEFERRED_OVERLAYS - 1 )]; + if ( parms.startTime > tr.viewDef->renderView.time[0] - DEFFERED_OVERLAY_TIMEOUT ) { + CreateOverlay( model, parms.localTextureAxis, parms.material ); + } + } + firstDeferredOverlay = 0; + nextDeferredOverlay = 0; +} + +/* +==================== +idRenderModelOverlay::AddDeferredOverlay +==================== +*/ +void idRenderModelOverlay::AddDeferredOverlay( const overlayProjectionParms_t & localParms ) { + deferredOverlays[nextDeferredOverlay++ & ( MAX_DEFERRED_OVERLAYS - 1 )] = localParms; + if ( nextDeferredOverlay - firstDeferredOverlay > MAX_DEFERRED_OVERLAYS ) { + firstDeferredOverlay = nextDeferredOverlay - MAX_DEFERRED_OVERLAYS; + } +} + +/* +==================== +R_CopyOverlaySurface +==================== +*/ +static void R_CopyOverlaySurface( idDrawVert * verts, int numVerts, triIndex_t * indexes, int numIndexes, const overlay_t * overlay, const idDrawVert * sourceVerts ) { + assert_16_byte_aligned( &verts[numVerts] ); + assert_16_byte_aligned( &indexes[numIndexes] ); + assert_16_byte_aligned( overlay->verts ); + assert_16_byte_aligned( overlay->indexes ); + assert( ( ( overlay->numVerts * sizeof( idDrawVert ) ) & 15 ) == 0 ); + assert( ( ( overlay->numIndexes * sizeof( triIndex_t ) ) & 15 ) == 0 ); + + + const __m128i vector_int_clear_last = _mm_set_epi32( 0, -1, -1, -1 ); + const __m128i vector_int_num_verts = _mm_shuffle_epi32( _mm_cvtsi32_si128( numVerts ), 0 ); + const __m128i vector_short_num_verts = _mm_packs_epi32( vector_int_num_verts, vector_int_num_verts ); + + // copy vertices + for ( int i = 0; i < overlay->numVerts; i++ ) { + const overlayVertex_t &overlayVert = overlay->verts[i]; + const idDrawVert &srcVert = sourceVerts[overlayVert.vertexNum]; + idDrawVert &dstVert = verts[numVerts + i]; + + __m128i v0 = _mm_load_si128( (const __m128i *)( (byte *)&srcVert + 0 ) ); + __m128i v1 = _mm_load_si128( (const __m128i *)( (byte *)&srcVert + 16 ) ); + __m128i st = _mm_cvtsi32_si128( *(unsigned int *)overlayVert.st ); + + st = _mm_shuffle_epi32( st, _MM_SHUFFLE( 0, 1, 2, 3 ) ); + v0 = _mm_and_si128( v0, vector_int_clear_last ); + v0 = _mm_or_si128( v0, st ); + + _mm_stream_si128( (__m128i *)( (byte *)&dstVert + 0 ), v0 ); + _mm_stream_si128( (__m128i *)( (byte *)&dstVert + 16 ), v1 ); + } + + // copy indexes + assert( ( overlay->numIndexes & 7 ) == 0 ); + assert( sizeof( triIndex_t ) == 2 ); + for ( int i = 0; i < overlay->numIndexes; i += 8 ) { + __m128i vi = _mm_load_si128( (const __m128i *)&overlay->indexes[i] ); + + vi = _mm_add_epi16( vi, vector_short_num_verts ); + + _mm_stream_si128( (__m128i *)&indexes[numIndexes + i], vi ); + } + + _mm_sfence(); + +} + +/* +===================== +idRenderModelOverlay::GetNumOverlayDrawSurfs +===================== +*/ +unsigned int idRenderModelOverlay::GetNumOverlayDrawSurfs() { + numOverlayMaterials = 0; + + for ( unsigned int i = firstOverlay; i < nextOverlay; i++ ) { + const overlay_t & overlay = overlays[i & ( MAX_OVERLAYS - 1 )]; + + unsigned int j = 0; + for ( ; j < numOverlayMaterials; j++ ) { + if ( overlayMaterials[j] == overlay.material ) { + break; + } + } + if ( j >= numOverlayMaterials ) { + overlayMaterials[numOverlayMaterials++] = overlay.material; + } + } + + return numOverlayMaterials; +} + +/* +==================== +idRenderModelOverlay::CreateOverlayDrawSurf +==================== +*/ +drawSurf_t * idRenderModelOverlay::CreateOverlayDrawSurf( const viewEntity_t *space, const idRenderModel *baseModel, unsigned int index ) { + if ( index < 0 || index >= numOverlayMaterials ) { + return NULL; + } + + // md5 models won't have any surfaces when r_showSkel is set + if ( baseModel == NULL || baseModel->IsDefaultModel() || baseModel->NumSurfaces() == 0 ) { + return NULL; + } + + assert( baseModel->IsDynamicModel() == DM_STATIC ); + + const idRenderModelStatic * staticModel = static_cast< const idRenderModelStatic * >( baseModel ); + + const idMaterial * material = overlayMaterials[index]; + + int maxVerts = 0; + int maxIndexes = 0; + for ( unsigned int i = firstOverlay; i < nextOverlay; i++ ) { + const overlay_t & overlay = overlays[i & ( MAX_OVERLAYS - 1 )]; + if ( overlay.material == material ) { + maxVerts += overlay.numVerts; + maxIndexes += overlay.numIndexes; + } + } + + if ( maxVerts == 0 || maxIndexes == 0 ) { + return NULL; + } + + // create a new triangle surface in frame memory so it gets automatically disposed of + srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->staticModelWithJoints = ( staticModel->jointsInverted != NULL ) ? const_cast< idRenderModelStatic * >( staticModel ) : NULL; // allow GPU skinning + + newTri->ambientCache = vertexCache.AllocVertex( NULL, ALIGN( maxVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + newTri->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + + idDrawVert * mappedVerts = (idDrawVert *)vertexCache.MappedVertexBuffer( newTri->ambientCache ); + triIndex_t * mappedIndexes = (triIndex_t *)vertexCache.MappedIndexBuffer( newTri->indexCache ); + + int numVerts = 0; + int numIndexes = 0; + + for ( unsigned int i = firstOverlay; i < nextOverlay; i++ ) { + overlay_t & overlay = overlays[i & ( MAX_OVERLAYS - 1 )]; + + if ( overlay.numVerts == 0 ) { + if ( i == firstOverlay ) { + firstOverlay++; + } + continue; + } + + if ( overlay.material != material ) { + continue; + } + + // get the source model surface for this overlay surface + const modelSurface_t * baseSurf = ( overlay.surfaceNum < staticModel->NumSurfaces() ) ? staticModel->Surface( overlay.surfaceNum ) : NULL; + + // if the surface ids no longer match + if ( baseSurf == NULL || baseSurf->id != overlay.surfaceId ) { + // find the surface with the correct id + if ( staticModel->FindSurfaceWithId( overlay.surfaceId, overlay.surfaceNum ) ) { + baseSurf = staticModel->Surface( overlay.surfaceNum ); + } else { + // the surface with this id no longer exists + FreeOverlay( overlay ); + if ( i == firstOverlay ) { + firstOverlay++; + } + continue; + } + } + + // check for out of range vertex references + const srfTriangles_t * baseTri = baseSurf->geometry; + if ( overlay.maxReferencedVertex >= baseTri->numVerts ) { + // This can happen when playing a demofile and a model has been changed since it was recorded, so just issue a warning and go on. + common->Warning( "idRenderModelOverlay::CreateOverlayDrawSurf: overlay vertex out of range. Model has probably changed since generating the overlay." ); + FreeOverlay( overlay ); + if ( i == firstOverlay ) { + firstOverlay++; + } + continue; + } + + // use SIMD optimized routine to copy the vertices and indices directly to write-combined memory + R_CopyOverlaySurface( mappedVerts, numVerts, mappedIndexes, numIndexes, &overlay, baseTri->verts ); + + numIndexes += overlay.numIndexes; + numVerts += overlay.numVerts; + } + + newTri->numVerts = numVerts; + newTri->numIndexes = numIndexes; + + // create the drawsurf + drawSurf_t * drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + drawSurf->frontEndGeo = newTri; + drawSurf->numIndexes = newTri->numIndexes; + drawSurf->ambientCache = newTri->ambientCache; + drawSurf->indexCache = newTri->indexCache; + drawSurf->shadowCache = 0; + drawSurf->space = space; + drawSurf->scissorRect = space->scissorRect; + drawSurf->extraGLState = 0; + drawSurf->renderZFail = 0; + + R_SetupDrawSurfShader( drawSurf, material, &space->entityDef->parms ); + R_SetupDrawSurfJoints( drawSurf, newTri, NULL ); + + return drawSurf; +} + +/* +==================== +idRenderModelOverlay::ReadFromDemoFile +==================== +*/ +void idRenderModelOverlay::ReadFromDemoFile( idDemoFile *f ) { + // FIXME: implement +} + +/* +==================== +idRenderModelOverlay::WriteToDemoFile +==================== +*/ +void idRenderModelOverlay::WriteToDemoFile( idDemoFile *f ) const { + // FIXME: implement +} diff --git a/neo/renderer/ModelOverlay.h b/neo/renderer/ModelOverlay.h new file mode 100644 index 00000000..b1bd7223 --- /dev/null +++ b/neo/renderer/ModelOverlay.h @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODELOVERLAY_H__ +#define __MODELOVERLAY_H__ + +/* +=============================================================================== + + Overlays are used for adding decals on top of dynamic models. + Projects an overlay onto deformable geometry and can be added to + a render entity to allow decals on top of dynamic models. + This does not generate tangent vectors, so it can't be used with + light interaction shaders. Materials for overlays should always + be clamped, because the projected texcoords can run well off the + texture since no new clip vertexes are generated. + Overlays with common materials will be merged together, but additional + overlays will be allocated as needed. The material should not be + one that receives lighting, because no interactions are generated + for these lightweight surfaces. + +=============================================================================== +*/ + +static const int MAX_DEFERRED_OVERLAYS = 4; +static const int DEFFERED_OVERLAY_TIMEOUT = 200; // don't create a overlay if it wasn't visible within the first 200 milliseconds +static const int MAX_OVERLAYS = 8; + +compile_time_assert( CONST_ISPOWEROFTWO( MAX_OVERLAYS ) ); + +struct overlayProjectionParms_t { + idPlane localTextureAxis[2]; + const idMaterial * material; + int startTime; +}; + +struct overlayVertex_t { + int vertexNum; + halfFloat_t st[2]; +}; + +struct overlay_t { + int surfaceNum; + int surfaceId; + int maxReferencedVertex; + int numIndexes; + triIndex_t * indexes; + int numVerts; + overlayVertex_t * verts; + const idMaterial * material; +}; + +class idRenderModelOverlay { +public: + idRenderModelOverlay(); + ~idRenderModelOverlay(); + + void ReUse(); + + void AddDeferredOverlay( const overlayProjectionParms_t & localParms ); + void CreateDeferredOverlays( const idRenderModel * model ); + + unsigned int GetNumOverlayDrawSurfs(); + struct drawSurf_t * CreateOverlayDrawSurf( const struct viewEntity_t *space, const idRenderModel *baseModel, unsigned int index ); + + void ReadFromDemoFile( class idDemoFile *f ); + void WriteToDemoFile( class idDemoFile *f ) const; + +private: + overlay_t overlays[MAX_OVERLAYS]; + unsigned int firstOverlay; + unsigned int nextOverlay; + + overlayProjectionParms_t deferredOverlays[MAX_DEFERRED_OVERLAYS]; + unsigned int firstDeferredOverlay; + unsigned int nextDeferredOverlay; + + const idMaterial * overlayMaterials[MAX_OVERLAYS]; + unsigned int numOverlayMaterials; + + void CreateOverlay( const idRenderModel *model, const idPlane localTextureAxis[2], const idMaterial *material ); + void FreeOverlay( overlay_t & overlay ); +}; + +#endif /* !__MODELOVERLAY_H__ */ diff --git a/neo/renderer/Model_ase.cpp b/neo/renderer/Model_ase.cpp new file mode 100644 index 00000000..2f4fb02c --- /dev/null +++ b/neo/renderer/Model_ase.cpp @@ -0,0 +1,923 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "Model_ase.h" + +/* +====================================================================== + + Parses 3D Studio Max ASCII export files. + The goal is to parse the information into memory exactly as it is + represented in the file. Users of the data will then move it + into a form that is more convenient for them. + +====================================================================== +*/ + + +#define VERBOSE( x ) { if ( ase.verbose ) { common->Printf x ; } } + +// working variables used during parsing +typedef struct { + const char *buffer; + const char *curpos; + int len; + char token[1024]; + + bool verbose; + + aseModel_t *model; + aseObject_t *currentObject; + aseMesh_t *currentMesh; + aseMaterial_t *currentMaterial; + int currentFace; + int currentVertex; +} ase_t; + +static ase_t ase; + + +static aseMesh_t *ASE_GetCurrentMesh() +{ + return ase.currentMesh; +} + +static int CharIsTokenDelimiter( int ch ) +{ + if ( ch <= 32 ) + return 1; + return 0; +} + +static int ASE_GetToken( bool restOfLine ) +{ + int i = 0; + + if ( ase.buffer == 0 ) + return 0; + + if ( ( ase.curpos - ase.buffer ) == ase.len ) + return 0; + + // skip over crap + while ( ( ( ase.curpos - ase.buffer ) < ase.len ) && + ( *ase.curpos <= 32 ) ) + { + ase.curpos++; + } + + while ( ( ase.curpos - ase.buffer ) < ase.len ) + { + ase.token[i] = *ase.curpos; + + ase.curpos++; + i++; + + if ( ( CharIsTokenDelimiter( ase.token[i-1] ) && !restOfLine ) || + ( ( ase.token[i-1] == '\n' ) || ( ase.token[i-1] == '\r' ) ) ) + { + ase.token[i-1] = 0; + break; + } + } + + ase.token[i] = 0; + + return 1; +} + +static void ASE_ParseBracedBlock( void (*parser)( const char *token ) ) +{ + int indent = 0; + + while ( ASE_GetToken( false ) ) + { + if ( !strcmp( ase.token, "{" ) ) + { + indent++; + } + else if ( !strcmp( ase.token, "}" ) ) + { + --indent; + if ( indent == 0 ) + break; + else if ( indent < 0 ) + common->Error( "Unexpected '}'" ); + } + else + { + if ( parser ) + parser( ase.token ); + } + } +} + +static void ASE_SkipEnclosingBraces() +{ + int indent = 0; + + while ( ASE_GetToken( false ) ) + { + if ( !strcmp( ase.token, "{" ) ) + { + indent++; + } + else if ( !strcmp( ase.token, "}" ) ) + { + indent--; + if ( indent == 0 ) + break; + else if ( indent < 0 ) + common->Error( "Unexpected '}'" ); + } + } +} + +static void ASE_SkipRestOfLine() +{ + ASE_GetToken( true ); +} + +static void ASE_KeyMAP_DIFFUSE( const char *token ) +{ + aseMaterial_t *material; + + if ( !strcmp( token, "*BITMAP" ) ) + { + idStr qpath; + idStr matname; + + ASE_GetToken( false ); + + // remove the quotes + char *s = strstr( ase.token + 1, "\"" ); + if ( s ) { + *s = 0; + } + matname = ase.token + 1; + + // convert the 3DSMax material pathname to a qpath + matname.BackSlashesToSlashes(); + qpath = fileSystem->OSPathToRelativePath( matname ); + idStr::Copynz( ase.currentMaterial->name, qpath, sizeof( ase.currentMaterial->name ) ); + } + else if ( !strcmp( token, "*UVW_U_OFFSET" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->uOffset = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_V_OFFSET" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->vOffset = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_U_TILING" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->uTiling = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_V_TILING" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->vTiling = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_ANGLE" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->angle = atof( ase.token ); + } + else + { + } +} + +static void ASE_KeyMATERIAL( const char *token ) +{ + if ( !strcmp( token, "*MAP_DIFFUSE" ) ) + { + ASE_ParseBracedBlock( ASE_KeyMAP_DIFFUSE ); + } + else + { + } +} + +static void ASE_KeyMATERIAL_LIST( const char *token ) +{ + if ( !strcmp( token, "*MATERIAL_COUNT" ) ) + { + ASE_GetToken( false ); + VERBOSE( ( "..num materials: %s\n", ase.token ) ); + } + else if ( !strcmp( token, "*MATERIAL" ) ) + { + VERBOSE( ( "..material %d\n", ase.model->materials.Num() ) ); + + ase.currentMaterial = (aseMaterial_t *)Mem_Alloc( sizeof( aseMaterial_t ), TAG_MODEL ); + memset( ase.currentMaterial, 0, sizeof( aseMaterial_t ) ); + ase.currentMaterial->uTiling = 1; + ase.currentMaterial->vTiling = 1; + ase.model->materials.Append(ase.currentMaterial); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL ); + } +} + +static void ASE_KeyNODE_TM( const char *token ) +{ + int i; + + if ( !strcmp( token, "*TM_ROW0" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[0][i] = atof( ase.token ); + } + } else if ( !strcmp( token, "*TM_ROW1" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[1][i] = atof( ase.token ); + } + } else if ( !strcmp( token, "*TM_ROW2" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[2][i] = atof( ase.token ); + } + } else if ( !strcmp( token, "*TM_ROW3" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[3][i] = atof( ase.token ); + } + } +} + +static void ASE_KeyMESH_VERTEX_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_VERTEX" ) ) + { + ASE_GetToken( false ); // skip number + + ASE_GetToken( false ); + pMesh->vertexes[ase.currentVertex].x = atof( ase.token ); + + ASE_GetToken( false ); + pMesh->vertexes[ase.currentVertex].y = atof( ase.token ); + + ASE_GetToken( false ); + pMesh->vertexes[ase.currentVertex].z = atof( ase.token ); + + ase.currentVertex++; + + if ( ase.currentVertex > pMesh->numVertexes ) + { + common->Error( "ase.currentVertex >= pMesh->numVertexes" ); + } + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_VERTEX_LIST", token ); + } +} + +static void ASE_KeyMESH_FACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_FACE" ) ) + { + ASE_GetToken( false ); // skip face number + + // we are flipping the order here to change the front/back facing + // from 3DS to our standard (clockwise facing out) + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // first vertex + pMesh->faces[ase.currentFace].vertexNum[0] = atoi( ase.token ); + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // second vertex + pMesh->faces[ase.currentFace].vertexNum[2] = atoi( ase.token ); + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // third vertex + pMesh->faces[ase.currentFace].vertexNum[1] = atoi( ase.token ); + + ASE_GetToken( true ); + + // we could parse material id and smoothing groups here +/* + if ( ( p = strstr( ase.token, "*MESH_MTLID" ) ) != 0 ) + { + p += strlen( "*MESH_MTLID" ) + 1; + mtlID = atoi( p ); + } + else + { + common->Error( "No *MESH_MTLID found for face!" ); + } +*/ + + ase.currentFace++; + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_FACE_LIST", token ); + } +} + +static void ASE_KeyTFACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TFACE" ) ) + { + int a, b, c; + + ASE_GetToken( false ); + + ASE_GetToken( false ); + a = atoi( ase.token ); + ASE_GetToken( false ); + c = atoi( ase.token ); + ASE_GetToken( false ); + b = atoi( ase.token ); + + pMesh->faces[ase.currentFace].tVertexNum[0] = a; + pMesh->faces[ase.currentFace].tVertexNum[1] = b; + pMesh->faces[ase.currentFace].tVertexNum[2] = c; + + ase.currentFace++; + } + else + { + common->Error( "Unknown token '%s' in MESH_TFACE", token ); + } +} + +static void ASE_KeyCFACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_CFACE" ) ) + { + ASE_GetToken( false ); + + for ( int i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + int a = atoi( ase.token ); + + // we flip the vertex order to change the face direction to our style + static int remap[3] = { 0, 2, 1 }; + pMesh->faces[ase.currentFace].vertexColors[remap[i]][0] = pMesh->cvertexes[a][0] * 255; + pMesh->faces[ase.currentFace].vertexColors[remap[i]][1] = pMesh->cvertexes[a][1] * 255; + pMesh->faces[ase.currentFace].vertexColors[remap[i]][2] = pMesh->cvertexes[a][2] * 255; + } + + ase.currentFace++; + } + else + { + common->Error( "Unknown token '%s' in MESH_CFACE", token ); + } +} + +static void ASE_KeyMESH_TVERTLIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TVERT" ) ) + { + const int maxLength = 80; + char u[maxLength], v[maxLength], w[maxLength]; + + ASE_GetToken( false ); + + ASE_GetToken( false ); + strncpy( u, ase.token, maxLength ); u[maxLength-1] = '\0'; + + ASE_GetToken( false ); + strncpy( v, ase.token, maxLength ); v[maxLength-1] = '\0'; + + ASE_GetToken( false ); + strncpy( w, ase.token, maxLength ); w[maxLength-1] = '\0'; + + pMesh->tvertexes[ase.currentVertex].x = atof( u ); + // our OpenGL second texture axis is inverted from MAX's sense + pMesh->tvertexes[ase.currentVertex].y = 1.0f - atof( v ); + + ase.currentVertex++; + + if ( ase.currentVertex > pMesh->numTVertexes ) + { + common->Error( "ase.currentVertex > pMesh->numTVertexes" ); + } + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_TVERTLIST", token ); + } +} + +static void ASE_KeyMESH_CVERTLIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + pMesh->colorsParsed = true; + + if ( !strcmp( token, "*MESH_VERTCOL" ) ) + { + ASE_GetToken( false ); + + ASE_GetToken( false ); + pMesh->cvertexes[ase.currentVertex][0] = atof( token ); + + ASE_GetToken( false ); + pMesh->cvertexes[ase.currentVertex][1] = atof( token ); + + ASE_GetToken( false ); + pMesh->cvertexes[ase.currentVertex][2] = atof( token ); + + ase.currentVertex++; + + if ( ase.currentVertex > pMesh->numCVertexes ) + { + common->Error( "ase.currentVertex > pMesh->numCVertexes" ); + } + } + else { + common->Error( "Unknown token '%s' while parsing MESH_CVERTLIST", token ); + } +} + +static void ASE_KeyMESH_NORMALS( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + aseFace_t *f; + idVec3 n; + + pMesh->normalsParsed = true; + f = &pMesh->faces[ase.currentFace]; + + if ( !strcmp( token, "*MESH_FACENORMAL" ) ) + { + int num; + + ASE_GetToken( false ); + num = atoi( ase.token ); + + if ( num >= pMesh->numFaces || num < 0 ) { + common->Error( "MESH_NORMALS face index out of range: %i", num ); + } + + if ( num != ase.currentFace ) { + common->Error( "MESH_NORMALS face index != currentFace" ); + } + + ASE_GetToken( false ); + n[0] = atof( ase.token ); + ASE_GetToken( false ); + n[1] = atof( ase.token ); + ASE_GetToken( false ); + n[2]= atof( ase.token ); + + f->faceNormal[0] = n[0] * pMesh->transform[0][0] + n[1] * pMesh->transform[1][0] + n[2] * pMesh->transform[2][0]; + f->faceNormal[1] = n[0] * pMesh->transform[0][1] + n[1] * pMesh->transform[1][1] + n[2] * pMesh->transform[2][1]; + f->faceNormal[2] = n[0] * pMesh->transform[0][2] + n[1] * pMesh->transform[1][2] + n[2] * pMesh->transform[2][2]; + + f->faceNormal.Normalize(); + + ase.currentFace++; + } + else if ( !strcmp( token, "*MESH_VERTEXNORMAL" ) ) + { + int num; + int v; + + ASE_GetToken( false ); + num = atoi( ase.token ); + + if ( num >= pMesh->numVertexes || num < 0 ) { + common->Error( "MESH_NORMALS vertex index out of range: %i", num ); + } + + f = &pMesh->faces[ ase.currentFace - 1 ]; + + for ( v = 0 ; v < 3 ; v++ ) { + if ( num == f->vertexNum[ v ] ) { + break; + } + } + + if ( v >= 3 ) { + common->Error( "MESH_NORMALS vertex index doesn't match face" ); + return; + } + + ASE_GetToken( false ); + n[0] = atof( ase.token ); + ASE_GetToken( false ); + n[1] = atof( ase.token ); + ASE_GetToken( false ); + n[2]= atof( ase.token ); + + f->vertexNormals[ v ][0] = n[0] * pMesh->transform[0][0] + n[1] * pMesh->transform[1][0] + n[2] * pMesh->transform[2][0]; + f->vertexNormals[ v ][1] = n[0] * pMesh->transform[0][1] + n[1] * pMesh->transform[1][1] + n[2] * pMesh->transform[2][1]; + f->vertexNormals[ v ][2] = n[0] * pMesh->transform[0][2] + n[1] * pMesh->transform[1][2] + n[2] * pMesh->transform[2][2]; + + f->vertexNormals[v].Normalize(); + } +} + +static void ASE_KeyMESH( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*TIMEVALUE" ) ) + { + ASE_GetToken( false ); + + pMesh->timeValue = atoi( ase.token ); + VERBOSE( ( ".....timevalue: %d\n", pMesh->timeValue ) ); + } + else if ( !strcmp( token, "*MESH_NUMVERTEX" ) ) + { + ASE_GetToken( false ); + + pMesh->numVertexes = atoi( ase.token ); + VERBOSE( ( ".....num vertexes: %d\n", pMesh->numVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMTVERTEX" ) ) + { + ASE_GetToken( false ); + + pMesh->numTVertexes = atoi( ase.token ); + VERBOSE( ( ".....num tvertexes: %d\n", pMesh->numTVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMCVERTEX" ) ) + { + ASE_GetToken( false ); + + pMesh->numCVertexes = atoi( ase.token ); + VERBOSE( ( ".....num cvertexes: %d\n", pMesh->numCVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMFACES" ) ) + { + ASE_GetToken( false ); + + pMesh->numFaces = atoi( ase.token ); + VERBOSE( ( ".....num faces: %d\n", pMesh->numFaces ) ); + } + else if ( !strcmp( token, "*MESH_NUMTVFACES" ) ) + { + ASE_GetToken( false ); + + pMesh->numTVFaces = atoi( ase.token ); + VERBOSE( ( ".....num tvfaces: %d\n", pMesh->numTVFaces ) ); + + if ( pMesh->numTVFaces != pMesh->numFaces ) + { + common->Error( "MESH_NUMTVFACES != MESH_NUMFACES" ); + } + } + else if ( !strcmp( token, "*MESH_NUMCVFACES" ) ) + { + ASE_GetToken( false ); + + pMesh->numCVFaces = atoi( ase.token ); + VERBOSE( ( ".....num cvfaces: %d\n", pMesh->numCVFaces ) ); + + if ( pMesh->numTVFaces != pMesh->numFaces ) + { + common->Error( "MESH_NUMCVFACES != MESH_NUMFACES" ); + } + } + else if ( !strcmp( token, "*MESH_VERTEX_LIST" ) ) + { + pMesh->vertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numVertexes, TAG_MODEL ); + ase.currentVertex = 0; + VERBOSE( ( ".....parsing MESH_VERTEX_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_VERTEX_LIST ); + } + else if ( !strcmp( token, "*MESH_TVERTLIST" ) ) + { + ase.currentVertex = 0; + pMesh->tvertexes = (idVec2 *)Mem_Alloc( sizeof( idVec2 ) * pMesh->numTVertexes, TAG_MODEL ); + VERBOSE( ( ".....parsing MESH_TVERTLIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_TVERTLIST ); + } + else if ( !strcmp( token, "*MESH_CVERTLIST" ) ) + { + ase.currentVertex = 0; + pMesh->cvertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numCVertexes, TAG_MODEL ); + VERBOSE( ( ".....parsing MESH_CVERTLIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_CVERTLIST ); + } + else if ( !strcmp( token, "*MESH_FACE_LIST" ) ) + { + pMesh->faces = (aseFace_t *)Mem_Alloc( sizeof( aseFace_t ) * pMesh->numFaces, TAG_MODEL ); + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_FACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_FACE_LIST ); + } + else if ( !strcmp( token, "*MESH_TFACELIST" ) ) + { + if ( !pMesh->faces ) { + common->Error( "*MESH_TFACELIST before *MESH_FACE_LIST" ); + } + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_TFACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyTFACE_LIST ); + } + else if ( !strcmp( token, "*MESH_CFACELIST" ) ) + { + if ( !pMesh->faces ) { + common->Error( "*MESH_CFACELIST before *MESH_FACE_LIST" ); + } + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_CFACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyCFACE_LIST ); + } + else if ( !strcmp( token, "*MESH_NORMALS" ) ) + { + if ( !pMesh->faces ) { + common->Warning( "*MESH_NORMALS before *MESH_FACE_LIST" ); + } + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_NORMALS\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_NORMALS ); + } +} + +static void ASE_KeyMESH_ANIMATION( const char *token ) +{ + aseMesh_t *mesh; + + // loads a single animation frame + if ( !strcmp( token, "*MESH" ) ) + { + VERBOSE( ( "...found MESH\n" ) ); + + mesh = (aseMesh_t *)Mem_Alloc( sizeof( aseMesh_t ), TAG_MODEL ); + memset( mesh, 0, sizeof( aseMesh_t ) ); + ase.currentMesh = mesh; + + ase.currentObject->frames.Append( mesh ); + + ASE_ParseBracedBlock( ASE_KeyMESH ); + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_ANIMATION", token ); + } +} + +static void ASE_KeyGEOMOBJECT( const char *token ) +{ + aseObject_t *object; + + object = ase.currentObject; + + if ( !strcmp( token, "*NODE_NAME" ) ) + { + ASE_GetToken( true ); + VERBOSE( ( " %s\n", ase.token ) ); + idStr::Copynz( object->name, ase.token, sizeof( object->name ) ); + } + else if ( !strcmp( token, "*NODE_PARENT" ) ) + { + ASE_SkipRestOfLine(); + } + // ignore unused data blocks + else if ( !strcmp( token, "*NODE_TM" ) || + !strcmp( token, "*TM_ANIMATION" ) ) + { + ASE_ParseBracedBlock( ASE_KeyNODE_TM ); + } + // ignore regular meshes that aren't part of animation + else if ( !strcmp( token, "*MESH" ) ) + { + ase.currentMesh = &ase.currentObject->mesh; + idVec3 transforms[ 4 ]; + for ( int i = 0; i < 4; ++i ) { + transforms[ i ] = ase.currentMesh->transform[ i ]; + } + + memset( ase.currentMesh, 0, sizeof( *ase.currentMesh ) ); + for ( int i = 0; i < 4; ++i ) { + ase.currentMesh->transform[ i ] = transforms[ i ]; + } + + ASE_ParseBracedBlock( ASE_KeyMESH ); + } + // according to spec these are obsolete + else if ( !strcmp( token, "*MATERIAL_REF" ) ) + { + ASE_GetToken( false ); + + object->materialRef = atoi( ase.token ); + } + // loads a sequence of animation frames + else if ( !strcmp( token, "*MESH_ANIMATION" ) ) + { + VERBOSE( ( "..found MESH_ANIMATION\n" ) ); + + ASE_ParseBracedBlock( ASE_KeyMESH_ANIMATION ); + } + // skip unused info + else if ( !strcmp( token, "*PROP_MOTIONBLUR" ) || + !strcmp( token, "*PROP_CASTSHADOW" ) || + !strcmp( token, "*PROP_RECVSHADOW" ) ) + { + ASE_SkipRestOfLine(); + } + +} + +void ASE_ParseGeomObject() { + aseObject_t *object; + + VERBOSE( ("GEOMOBJECT" ) ); + + object = (aseObject_t *)Mem_Alloc( sizeof( aseObject_t ), TAG_MODEL ); + memset( object, 0, sizeof( aseObject_t ) ); + ase.model->objects.Append( object ); + ase.currentObject = object; + + object->frames.Resize(32, 32); + + ASE_ParseBracedBlock( ASE_KeyGEOMOBJECT ); +} + +static void ASE_KeyGROUP( const char *token ) +{ + if ( !strcmp( token, "*GEOMOBJECT" ) ) { + ASE_ParseGeomObject(); + } +} + +/* +================= +ASE_Parse +================= +*/ +aseModel_t *ASE_Parse( const char *buffer, bool verbose ) { + memset( &ase, 0, sizeof( ase ) ); + + ase.verbose = verbose; + + ase.buffer = buffer; + ase.len = strlen( buffer ); + ase.curpos = ase.buffer; + ase.currentObject = NULL; + + // NOTE: using new operator because aseModel_t contains idList class objects + ase.model = new (TAG_MODEL) aseModel_t; + memset( ase.model, 0, sizeof( aseModel_t ) ); + ase.model->objects.Resize( 32, 32 ); + ase.model->materials.Resize( 32, 32 ); + + while ( ASE_GetToken( false ) ) { + if ( !strcmp( ase.token, "*3DSMAX_ASCIIEXPORT" ) || + !strcmp( ase.token, "*COMMENT" ) ) { + ASE_SkipRestOfLine(); + } else if ( !strcmp( ase.token, "*SCENE" ) ) { + ASE_SkipEnclosingBraces(); + } else if ( !strcmp( ase.token, "*GROUP" ) ) { + ASE_GetToken( false ); // group name + ASE_ParseBracedBlock( ASE_KeyGROUP ); + } else if ( !strcmp( ase.token, "*SHAPEOBJECT" ) ) { + ASE_SkipEnclosingBraces(); + } else if ( !strcmp( ase.token, "*CAMERAOBJECT" ) ) { + ASE_SkipEnclosingBraces(); + } else if ( !strcmp( ase.token, "*MATERIAL_LIST" ) ) { + VERBOSE( ("MATERIAL_LIST\n") ); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL_LIST ); + } else if ( !strcmp( ase.token, "*GEOMOBJECT" ) ) { + ASE_ParseGeomObject(); + } else if ( ase.token[0] ) { + common->Printf( "Unknown token '%s'\n", ase.token ); + } + } + + return ase.model; +} + +/* +================= +ASE_Load +================= +*/ +aseModel_t *ASE_Load( const char *fileName ) { + char *buf; + ID_TIME_T timeStamp; + aseModel_t *ase; + + fileSystem->ReadFile( fileName, (void **)&buf, &timeStamp ); + if ( !buf ) { + return NULL; + } + + ase = ASE_Parse( buf, false ); + ase->timeStamp = timeStamp; + + fileSystem->FreeFile( buf ); + + return ase; +} + +/* +================= +ASE_Free +================= +*/ +void ASE_Free( aseModel_t *ase ) { + int i, j; + aseObject_t *obj; + aseMesh_t *mesh; + aseMaterial_t *material; + + if ( !ase ) { + return; + } + for ( i = 0; i < ase->objects.Num(); i++ ) { + obj = ase->objects[i]; + for ( j = 0; j < obj->frames.Num(); j++ ) { + mesh = obj->frames[j]; + if ( mesh->vertexes ) { + Mem_Free( mesh->vertexes ); + } + if ( mesh->tvertexes ) { + Mem_Free( mesh->tvertexes ); + } + if ( mesh->cvertexes ) { + Mem_Free( mesh->cvertexes ); + } + if ( mesh->faces ) { + Mem_Free( mesh->faces ); + } + Mem_Free( mesh ); + } + + obj->frames.Clear(); + + // free the base nesh + mesh = &obj->mesh; + if ( mesh->vertexes ) { + Mem_Free( mesh->vertexes ); + } + if ( mesh->tvertexes ) { + Mem_Free( mesh->tvertexes ); + } + if ( mesh->cvertexes ) { + Mem_Free( mesh->cvertexes ); + } + if ( mesh->faces ) { + Mem_Free( mesh->faces ); + } + Mem_Free( obj ); + } + ase->objects.Clear(); + + for ( i = 0; i < ase->materials.Num(); i++ ) { + material = ase->materials[i]; + Mem_Free( material ); + } + ase->materials.Clear(); + + delete ase; +} diff --git a/neo/renderer/Model_ase.h b/neo/renderer/Model_ase.h new file mode 100644 index 00000000..1265b9f5 --- /dev/null +++ b/neo/renderer/Model_ase.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODEL_ASE_H__ +#define __MODEL_ASE_H__ + +/* +=============================================================================== + + ASE loader. (3D Studio Max ASCII Export) + +=============================================================================== +*/ + +typedef struct { + int vertexNum[3]; + int tVertexNum[3]; + idVec3 faceNormal; + idVec3 vertexNormals[3]; + byte vertexColors[3][4]; +} aseFace_t; + +typedef struct { + int timeValue; + + int numVertexes; + int numTVertexes; + int numCVertexes; + int numFaces; + int numTVFaces; + int numCVFaces; + + idVec3 transform[4]; // applied to normals + + bool colorsParsed; + bool normalsParsed; + idVec3 * vertexes; + idVec2 * tvertexes; + idVec3 * cvertexes; + aseFace_t * faces; +} aseMesh_t; + +typedef struct { + char name[128]; + float uOffset, vOffset; // max lets you offset by material without changing texCoords + float uTiling, vTiling; // multiply tex coords by this + float angle; // in clockwise radians +} aseMaterial_t; + +typedef struct { + char name[128]; + int materialRef; + + aseMesh_t mesh; + + // frames are only present with animations + idList frames; // aseMesh_t +} aseObject_t; + +typedef struct aseModel_s { + ID_TIME_T timeStamp; + idList materials; + idList objects; +} aseModel_t; + + +aseModel_t *ASE_Load( const char *fileName ); +void ASE_Free( aseModel_t *ase ); + +#endif /* !__MODEL_ASE_H__ */ diff --git a/neo/renderer/Model_beam.cpp b/neo/renderer/Model_beam.cpp new file mode 100644 index 00000000..014dc81d --- /dev/null +++ b/neo/renderer/Model_beam.cpp @@ -0,0 +1,210 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" +#include "Model_local.h" + +/* + +This is a simple dynamic model that just creates a stretched quad between +two points that faces the view, like a dynamic deform tube. + +*/ + +static const char *beam_SnapshotName = "_beam_Snapshot_"; + +/* +=============== +idRenderModelBeam::IsDynamicModel +=============== +*/ +dynamicModel_t idRenderModelBeam::IsDynamicModel() const { + return DM_CONTINUOUS; // regenerate for every view +} + +/* +=============== +idRenderModelBeam::IsLoaded +=============== +*/ +bool idRenderModelBeam::IsLoaded() const { + return true; // don't ever need to load +} + +/* +=============== +idRenderModelBeam::InstantiateDynamicModel +=============== +*/ +idRenderModel *idRenderModelBeam::InstantiateDynamicModel( const struct renderEntity_s *renderEntity, const viewDef_t *viewDef, idRenderModel *cachedModel ) { + idRenderModelStatic *staticModel; + srfTriangles_t *tri; + modelSurface_t surf; + + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( renderEntity == NULL || viewDef == NULL ) { + delete cachedModel; + return NULL; + } + + if ( cachedModel != NULL ) { + + assert( dynamic_cast( cachedModel ) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), beam_SnapshotName ) == 0 ); + + staticModel = static_cast( cachedModel ); + surf = *staticModel->Surface( 0 ); + tri = surf.geometry; + + } else { + + staticModel = new (TAG_MODEL) idRenderModelStatic; + staticModel->InitEmpty( beam_SnapshotName ); + + tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, 4 ); + R_AllocStaticTriSurfIndexes( tri, 6 ); + + tri->verts[0].Clear(); + tri->verts[0].SetTexCoord( 0, 0 ); + + tri->verts[1].Clear(); + tri->verts[1].SetTexCoord( 0, 1 ); + + tri->verts[2].Clear(); + tri->verts[2].SetTexCoord( 1, 0 ); + + tri->verts[3].Clear(); + tri->verts[3].SetTexCoord( 1, 1 ); + + tri->indexes[0] = 0; + tri->indexes[1] = 2; + tri->indexes[2] = 1; + tri->indexes[3] = 2; + tri->indexes[4] = 3; + tri->indexes[5] = 1; + + tri->numVerts = 4; + tri->numIndexes = 6; + + surf.geometry = tri; + surf.id = 0; + surf.shader = tr.defaultMaterial; + staticModel->AddSurface( surf ); + } + + idVec3 target = *reinterpret_cast( &renderEntity->shaderParms[SHADERPARM_BEAM_END_X] ); + + // we need the view direction to project the minor axis of the tube + // as the view changes + idVec3 localView, localTarget; + float modelMatrix[16]; + R_AxisToModelMatrix( renderEntity->axis, renderEntity->origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, viewDef->renderView.vieworg, localView ); + R_GlobalPointToLocal( modelMatrix, target, localTarget ); + + idVec3 major = localTarget; + idVec3 minor; + + idVec3 mid = 0.5f * localTarget; + idVec3 dir = mid - localView; + minor.Cross( major, dir ); + minor.Normalize(); + if ( renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] != 0.0f ) { + minor *= renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] * 0.5f; + } + + int red = idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_RED] * 255.0f ); + int green = idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_GREEN] * 255.0f ); + int blue = idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_BLUE] * 255.0f ); + int alpha = idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_ALPHA] * 255.0f ); + + tri->verts[0].xyz = minor; + tri->verts[0].color[0] = red; + tri->verts[0].color[1] = green; + tri->verts[0].color[2] = blue; + tri->verts[0].color[3] = alpha; + + tri->verts[1].xyz = -minor; + tri->verts[1].color[0] = red; + tri->verts[1].color[1] = green; + tri->verts[1].color[2] = blue; + tri->verts[1].color[3] = alpha; + + tri->verts[2].xyz = localTarget + minor; + tri->verts[2].color[0] = red; + tri->verts[2].color[1] = green; + tri->verts[2].color[2] = blue; + tri->verts[2].color[3] = alpha; + + tri->verts[3].xyz = localTarget - minor; + tri->verts[3].color[0] = red; + tri->verts[3].color[1] = green; + tri->verts[3].color[2] = blue; + tri->verts[3].color[3] = alpha; + + R_BoundTriSurf( tri ); + + staticModel->bounds = tri->bounds; + + return staticModel; +} + +/* +=============== +idRenderModelBeam::Bounds +=============== +*/ +idBounds idRenderModelBeam::Bounds( const struct renderEntity_s *renderEntity ) const { + idBounds b; + + b.Zero(); + if ( !renderEntity ) { + b.ExpandSelf( 8.0f ); + } else { + idVec3 target = *reinterpret_cast( &renderEntity->shaderParms[SHADERPARM_BEAM_END_X] ); + idVec3 localTarget; + float modelMatrix[16]; + R_AxisToModelMatrix( renderEntity->axis, renderEntity->origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, target, localTarget ); + + b.AddPoint( localTarget ); + if ( renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] != 0.0f ) { + b.ExpandSelf( renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] * 0.5f ); + } + } + return b; +} diff --git a/neo/renderer/Model_liquid.cpp b/neo/renderer/Model_liquid.cpp new file mode 100644 index 00000000..0683042d --- /dev/null +++ b/neo/renderer/Model_liquid.cpp @@ -0,0 +1,521 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "tr_local.h" +#include "Model_local.h" + +#define LIQUID_MAX_SKIP_FRAMES 5 +#define LIQUID_MAX_TYPES 3 + +/* +==================== +idRenderModelLiquid::idRenderModelLiquid +==================== +*/ +idRenderModelLiquid::idRenderModelLiquid() { + verts_x = 32; + verts_y = 32; + scale_x = 256.0f; + scale_y = 256.0f; + liquid_type = 0; + density = 0.97f; + drop_height = 4; + drop_radius = 4; + drop_delay = 1000; + shader = declManager->FindMaterial( NULL ); + update_tics = 33; // ~30 hz + time = 0; + seed = 0; + + random.SetSeed( 0 ); +} + +/* +==================== +idRenderModelLiquid::GenerateSurface +==================== +*/ +modelSurface_t idRenderModelLiquid::GenerateSurface( float lerp ) { + srfTriangles_t *tri; + int i, base; + idDrawVert *vert; + modelSurface_t surf; + float inv_lerp; + + inv_lerp = 1.0f - lerp; + vert = verts.Ptr(); + for( i = 0; i < verts.Num(); i++, vert++ ) { + vert->xyz.z = page1[ i ] * lerp + page2[ i ] * inv_lerp; + } + + tr.pc.c_deformedSurfaces++; + tr.pc.c_deformedVerts += deformInfo->numOutputVerts; + tr.pc.c_deformedIndexes += deformInfo->numIndexes; + + tri = R_AllocStaticTriSurf(); + + // note that some of the data is references, and should not be freed + tri->referencedIndexes = true; + + tri->numIndexes = deformInfo->numIndexes; + tri->indexes = deformInfo->indexes; + tri->silIndexes = deformInfo->silIndexes; + tri->numMirroredVerts = deformInfo->numMirroredVerts; + tri->mirroredVerts = deformInfo->mirroredVerts; + tri->numDupVerts = deformInfo->numDupVerts; + tri->dupVerts = deformInfo->dupVerts; + tri->numSilEdges = deformInfo->numSilEdges; + tri->silEdges = deformInfo->silEdges; + + tri->numVerts = deformInfo->numOutputVerts; + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + SIMDProcessor->Memcpy( tri->verts, verts.Ptr(), deformInfo->numSourceVerts * sizeof(tri->verts[0]) ); + + // replicate the mirror seam vertexes + base = deformInfo->numOutputVerts - deformInfo->numMirroredVerts; + for ( i = 0 ; i < deformInfo->numMirroredVerts ; i++ ) { + tri->verts[base + i] = tri->verts[deformInfo->mirroredVerts[i]]; + } + + R_BoundTriSurf( tri ); + + surf.geometry = tri; + surf.shader = shader; + + return surf; +} + +/* +==================== +idRenderModelLiquid::WaterDrop +==================== +*/ +void idRenderModelLiquid::WaterDrop( int x, int y, float *page ) { + int cx, cy; + int left,top,right,bottom; + int square; + int radsquare = drop_radius * drop_radius; + float invlength = 1.0f / ( float )radsquare; + float dist; + + if ( x < 0 ) { + x = 1 + drop_radius + random.RandomInt( verts_x - 2 * drop_radius - 1 ); + } + if ( y < 0 ) { + y = 1 + drop_radius + random.RandomInt( verts_y - 2 * drop_radius - 1 ); + } + + left=-drop_radius; right = drop_radius; + top=-drop_radius; bottom = drop_radius; + + // Perform edge clipping... + if ( x - drop_radius < 1 ) { + left -= (x-drop_radius-1); + } + if ( y - drop_radius < 1 ) { + top -= (y-drop_radius-1); + } + if ( x + drop_radius > verts_x - 1 ) { + right -= (x+drop_radius-verts_x+1); + } + if ( y + drop_radius > verts_y - 1 ) { + bottom-= (y+drop_radius-verts_y+1); + } + + for ( cy = top; cy < bottom; cy++ ) { + for ( cx = left; cx < right; cx++ ) { + square = cy*cy + cx*cx; + if ( square < radsquare ) { + dist = idMath::Sqrt( (float)square * invlength ); + page[verts_x*(cy+y) + cx+x] += idMath::Cos16( dist * idMath::PI * 0.5f ) * drop_height; + } + } + } +} + +/* +==================== +idRenderModelLiquid::IntersectBounds +==================== +*/ +void idRenderModelLiquid::IntersectBounds( const idBounds &bounds, float displacement ) { + int cx, cy; + int left,top,right,bottom; + float up, down; + float *pos; + + left = ( int )( bounds[ 0 ].x / scale_x ); + right = ( int )( bounds[ 1 ].x / scale_x ); + top = ( int )( bounds[ 0 ].y / scale_y ); + bottom = ( int )( bounds[ 1 ].y / scale_y ); + down = bounds[ 0 ].z; + up = bounds[ 1 ].z; + + if ( ( right < 1 ) || ( left >= verts_x ) || ( bottom < 1 ) || ( top >= verts_x ) ) { + return; + } + + // Perform edge clipping... + if ( left < 1 ) { + left = 1; + } + if ( right >= verts_x ) { + right = verts_x - 1; + } + if ( top < 1 ) { + top = 1; + } + if ( bottom >= verts_y ) { + bottom = verts_y - 1; + } + + for ( cy = top; cy < bottom; cy++ ) { + for ( cx = left; cx < right; cx++ ) { + pos = &page1[ verts_x * cy + cx ]; + if ( *pos > down ) {//&& ( *pos < up ) ) { + *pos = down; + } + } + } +} + +/* +==================== +idRenderModelLiquid::Update +==================== +*/ +void idRenderModelLiquid::Update() { + int x, y; + float *p2; + float *p1; + float value; + + time += update_tics; + + SwapValues( page1, page2 ); + + if ( time > nextDropTime ) { + WaterDrop( -1, -1, page2 ); + nextDropTime = time + drop_delay; + } else if ( time < nextDropTime - drop_delay ) { + nextDropTime = time + drop_delay; + } + + p1 = page1; + p2 = page2; + + switch( liquid_type ) { + case 0 : + for ( y = 1; y < verts_y - 1; y++ ) { + p2 += verts_x; + p1 += verts_x; + for ( x = 1; x < verts_x - 1; x++ ) { + value = + ( p2[ x + verts_x ] + + p2[ x - verts_x ] + + p2[ x + 1 ] + + p2[ x - 1 ] + + p2[ x - verts_x - 1 ] + + p2[ x - verts_x + 1 ] + + p2[ x + verts_x - 1 ] + + p2[ x + verts_x + 1 ] + + p2[ x ] ) * ( 2.0f / 9.0f ) - + p1[ x ]; + + p1[ x ] = value * density; + } + } + break; + + case 1 : + for ( y = 1; y < verts_y - 1; y++ ) { + p2 += verts_x; + p1 += verts_x; + for ( x = 1; x < verts_x - 1; x++ ) { + value = + ( p2[ x + verts_x ] + + p2[ x - verts_x ] + + p2[ x + 1 ] + + p2[ x - 1 ] + + p2[ x - verts_x - 1 ] + + p2[ x - verts_x + 1 ] + + p2[ x + verts_x - 1 ] + + p2[ x + verts_x + 1 ] ) * 0.25f - + p1[ x ]; + + p1[ x ] = value * density; + } + } + break; + + case 2 : + for ( y = 1; y < verts_y - 1; y++ ) { + p2 += verts_x; + p1 += verts_x; + for ( x = 1; x < verts_x - 1; x++ ) { + value = + ( p2[ x + verts_x ] + + p2[ x - verts_x ] + + p2[ x + 1 ] + + p2[ x - 1 ] + + p2[ x - verts_x - 1 ] + + p2[ x - verts_x + 1 ] + + p2[ x + verts_x - 1 ] + + p2[ x + verts_x + 1 ] + + p2[ x ] ) * ( 1.0f / 9.0f ); + + p1[ x ] = value * density; + } + } + break; + } +} + +/* +==================== +idRenderModelLiquid::Reset +==================== +*/ +void idRenderModelLiquid::Reset() { + int i, x, y; + + if ( pages.Num() < 2 * verts_x * verts_y ) { + return; + } + + nextDropTime = 0; + time = 0; + random.SetSeed( seed ); + + page1 = pages.Ptr(); + page2 = page1 + verts_x * verts_y; + + for ( i = 0, y = 0; y < verts_y; y++ ) { + for ( x = 0; x < verts_x; x++, i++ ) { + page1[ i ] = 0.0f; + page2[ i ] = 0.0f; + verts[ i ].xyz.z = 0.0f; + } + } +} + +/* +==================== +idRenderModelLiquid::InitFromFile +==================== +*/ +void idRenderModelLiquid::InitFromFile( const char *fileName ) { + int i, x, y; + idToken token; + idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + idList tris; + float size_x, size_y; + float rate; + + name = fileName; + + if ( !parser.LoadFile( fileName ) ) { + MakeDefaultModel(); + return; + } + + size_x = scale_x * verts_x; + size_y = scale_y * verts_y; + + while( parser.ReadToken( &token ) ) { + if ( !token.Icmp( "seed" ) ) { + seed = parser.ParseInt(); + } else if ( !token.Icmp( "size_x" ) ) { + size_x = parser.ParseFloat(); + } else if ( !token.Icmp( "size_y" ) ) { + size_y = parser.ParseFloat(); + } else if ( !token.Icmp( "verts_x" ) ) { + verts_x = parser.ParseFloat(); + if ( verts_x < 2 ) { + parser.Warning( "Invalid # of verts. Using default model." ); + MakeDefaultModel(); + return; + } + } else if ( !token.Icmp( "verts_y" ) ) { + verts_y = parser.ParseFloat(); + if ( verts_y < 2 ) { + parser.Warning( "Invalid # of verts. Using default model." ); + MakeDefaultModel(); + return; + } + } else if ( !token.Icmp( "liquid_type" ) ) { + liquid_type = parser.ParseInt() - 1; + if ( ( liquid_type < 0 ) || ( liquid_type >= LIQUID_MAX_TYPES ) ) { + parser.Warning( "Invalid liquid_type. Using default model." ); + MakeDefaultModel(); + return; + } + } else if ( !token.Icmp( "density" ) ) { + density = parser.ParseFloat(); + } else if ( !token.Icmp( "drop_height" ) ) { + drop_height = parser.ParseFloat(); + } else if ( !token.Icmp( "drop_radius" ) ) { + drop_radius = parser.ParseInt(); + } else if ( !token.Icmp( "drop_delay" ) ) { + drop_delay = SEC2MS( parser.ParseFloat() ); + } else if ( !token.Icmp( "shader" ) ) { + parser.ReadToken( &token ); + shader = declManager->FindMaterial( token ); + } else if ( !token.Icmp( "update_rate" ) ) { + rate = parser.ParseFloat(); + if ( ( rate <= 0.0f ) || ( rate > 60.0f ) ) { + parser.Warning( "Invalid update_rate. Must be between 0 and 60. Using default model." ); + MakeDefaultModel(); + return; + } + update_tics = 1000 / rate; + } else { + parser.Warning( "Unknown parameter '%s'. Using default model.", token.c_str() ); + MakeDefaultModel(); + return; + } + } + + scale_x = size_x / ( verts_x - 1 ); + scale_y = size_y / ( verts_y - 1 ); + + pages.SetNum( 2 * verts_x * verts_y ); + page1 = pages.Ptr(); + page2 = page1 + verts_x * verts_y; + + verts.SetNum( verts_x * verts_y ); + for ( i = 0, y = 0; y < verts_y; y++ ) { + for ( x = 0; x < verts_x; x++, i++ ) { + page1[ i ] = 0.0f; + page2[ i ] = 0.0f; + verts[ i ].Clear(); + verts[ i ].xyz.Set( x * scale_x, y * scale_y, 0.0f ); + verts[ i ].SetTexCoord( (float) x / (float)( verts_x - 1 ), (float) -y / (float)( verts_y - 1 ) ); + } + } + + tris.SetNum( ( verts_x - 1 ) * ( verts_y - 1 ) * 6 ); + for( i = 0, y = 0; y < verts_y - 1; y++ ) { + for( x = 1; x < verts_x; x++, i += 6 ) { + tris[ i + 0 ] = y * verts_x + x; + tris[ i + 1 ] = y * verts_x + x - 1; + tris[ i + 2 ] = ( y + 1 ) * verts_x + x - 1; + + tris[ i + 3 ] = ( y + 1 ) * verts_x + x - 1; + tris[ i + 4 ] = ( y + 1 ) * verts_x + x; + tris[ i + 5 ] = y * verts_x + x; + } + } + + // build the information that will be common to all animations of this mesh: + // sil edge connectivity and normal / tangent generation information + deformInfo = R_BuildDeformInfo( verts.Num(), verts.Ptr(), tris.Num(), tris.Ptr(), true ); + + bounds.Clear(); + bounds.AddPoint( idVec3( 0.0f, 0.0f, drop_height * -10.0f ) ); + bounds.AddPoint( idVec3( ( verts_x - 1 ) * scale_x, ( verts_y - 1 ) * scale_y, drop_height * 10.0f ) ); + + // set the timestamp for reloadmodels + fileSystem->ReadFile( name, NULL, &timeStamp ); + + Reset(); +} + +/* +==================== +idRenderModelLiquid::InstantiateDynamicModel +==================== +*/ +idRenderModel *idRenderModelLiquid::InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ) { + idRenderModelStatic *staticModel; + int frames; + int t; + float lerp; + + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( !deformInfo ) { + return NULL; + } + + if ( !view ) { + t = 0; + } else { + t = view->renderView.time[0]; + } + + // update the liquid model + frames = ( t - time ) / update_tics; + if ( frames > LIQUID_MAX_SKIP_FRAMES ) { + // don't let time accumalate when skipping frames + time += update_tics * ( frames - LIQUID_MAX_SKIP_FRAMES ); + + frames = LIQUID_MAX_SKIP_FRAMES; + } + + while( frames > 0 ) { + Update(); + frames--; + } + + // create the surface + lerp = ( float )( t - time ) / ( float )update_tics; + modelSurface_t surf = GenerateSurface( lerp ); + + staticModel = new (TAG_MODEL) idRenderModelStatic; + staticModel->AddSurface( surf ); + staticModel->bounds = surf.geometry->bounds; + + return staticModel; +} + +/* +==================== +idRenderModelLiquid::IsDynamicModel +==================== +*/ +dynamicModel_t idRenderModelLiquid::IsDynamicModel() const { + return DM_CONTINUOUS; +} + +/* +==================== +idRenderModelLiquid::Bounds +==================== +*/ +idBounds idRenderModelLiquid::Bounds(const struct renderEntity_s *ent) const { + // FIXME: need to do this better + return bounds; +} diff --git a/neo/renderer/Model_local.h b/neo/renderer/Model_local.h new file mode 100644 index 00000000..e2277d6f --- /dev/null +++ b/neo/renderer/Model_local.h @@ -0,0 +1,440 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODEL_LOCAL_H__ +#define __MODEL_LOCAL_H__ + +/* +=============================================================================== + + Static model + +=============================================================================== +*/ + +class idJointMat; +struct deformInfo_t; + +class idRenderModelStatic : public idRenderModel { +public: + // the inherited public interface + static idRenderModel * Alloc(); + + idRenderModelStatic(); + virtual ~idRenderModelStatic(); + + virtual void InitFromFile( const char *fileName ); + virtual bool LoadBinaryModel( idFile * file, const ID_TIME_T sourceTimeStamp ); + virtual void WriteBinaryModel( idFile * file, ID_TIME_T *_timeStamp = NULL ) const; + virtual bool SupportsBinaryModel() { return true; } + + virtual void PartialInitFromFile( const char *fileName ); + virtual void PurgeModel(); + virtual void Reset() {}; + virtual void LoadModel(); + virtual bool IsLoaded(); + virtual void SetLevelLoadReferenced( bool referenced ); + virtual bool IsLevelLoadReferenced(); + virtual void TouchData(); + virtual void InitEmpty( const char *name ); + virtual void AddSurface( modelSurface_t surface ); + virtual void FinishSurfaces(); + virtual void FreeVertexCache(); + virtual const char * Name() const; + virtual void Print() const; + virtual void List() const; + virtual int Memory() const; + virtual ID_TIME_T Timestamp() const; + virtual int NumSurfaces() const; + virtual int NumBaseSurfaces() const; + virtual const modelSurface_t *Surface( int surfaceNum ) const; + virtual srfTriangles_t * AllocSurfaceTriangles( int numVerts, int numIndexes ) const; + virtual void FreeSurfaceTriangles( srfTriangles_t *tris ) const; + virtual bool IsStaticWorldModel() const; + virtual dynamicModel_t IsDynamicModel() const; + virtual bool IsDefaultModel() const; + virtual bool IsReloadable() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual int NumJoints() const; + virtual const idMD5Joint * GetJoints() const; + virtual jointHandle_t GetJointHandle( const char *name ) const; + virtual const char * GetJointName( jointHandle_t handle ) const; + virtual const idJointQuat * GetDefaultPose() const; + virtual int NearestJoint( int surfaceNum, int a, int b, int c ) const; + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + virtual void ReadFromDemoFile( class idDemoFile *f ); + virtual void WriteToDemoFile( class idDemoFile *f ); + virtual float DepthHack() const; + + virtual bool ModelHasDrawingSurfaces() const { return hasDrawingSurfaces; }; + virtual bool ModelHasInteractingSurfaces() const { return hasInteractingSurfaces; }; + virtual bool ModelHasShadowCastingSurfaces() const { return hasShadowCastingSurfaces; }; + + void MakeDefaultModel(); + + bool LoadASE( const char *fileName ); + bool LoadLWO( const char *fileName ); + bool LoadMA( const char *filename ); + + bool ConvertASEToModelSurfaces( const struct aseModel_s *ase ); + bool ConvertLWOToModelSurfaces( const struct st_lwObject *lwo ); + bool ConvertMAToModelSurfaces (const struct maModel_s *ma ); + + struct aseModel_s * ConvertLWOToASE( const struct st_lwObject *obj, const char *fileName ); + + bool DeleteSurfaceWithId( int id ); + void DeleteSurfacesWithNegativeId(); + bool FindSurfaceWithId( int id, int &surfaceNum ) const; + +public: + idList surfaces; + idBounds bounds; + int overlaysAdded; + + // when an md5 is instantiated, the inverted joints array is stored to allow GPU skinning + int numInvertedJoints; + idJointMat * jointsInverted; + vertCacheHandle_t jointsInvertedBuffer; + +protected: + int lastModifiedFrame; + int lastArchivedFrame; + + idStr name; + bool isStaticWorldModel; + bool defaulted; + bool purged; // eventually we will have dynamic reloading + bool fastLoad; // don't generate tangents and shadow data + bool reloadable; // if not, reloadModels won't check timestamp + bool levelLoadReferenced; // for determining if it needs to be freed + bool hasDrawingSurfaces; + bool hasInteractingSurfaces; + bool hasShadowCastingSurfaces; + ID_TIME_T timeStamp; + + static idCVar r_mergeModelSurfaces; // combine model surfaces with the same material + static idCVar r_slopVertex; // merge xyz coordinates this far apart + static idCVar r_slopTexCoord; // merge texture coordinates this far apart + static idCVar r_slopNormal; // merge normals that dot less than this +}; + +/* +=============================================================================== + + MD5 animated model + +=============================================================================== +*/ + +class idMD5Mesh { + friend class idRenderModelMD5; + +public: + idMD5Mesh(); + ~idMD5Mesh(); + + void ParseMesh( idLexer &parser, int numJoints, const idJointMat *joints ); + + int NumVerts() const { return numVerts; } + int NumTris() const { return numTris; } + + void UpdateSurface( const struct renderEntity_s *ent, const idJointMat *joints, + const idJointMat *entJointsInverted, modelSurface_t *surf ); + void CalculateBounds( const idJointMat * entJoints, idBounds & bounds ) const; + int NearestJoint( int a, int b, int c ) const; + +private: + const idMaterial * shader; // material applied to mesh + int numVerts; // number of vertices + int numTris; // number of triangles + byte * meshJoints; // the joints used by this mesh + int numMeshJoints; // number of mesh joints + float maxJointVertDist; // maximum distance a vertex is separated from a joint + deformInfo_t * deformInfo; // used to create srfTriangles_t from base frames and new vertexes + int surfaceNum; // number of the static surface created for this mesh +}; + +class idRenderModelMD5 : public idRenderModelStatic { +public: + virtual void InitFromFile( const char *fileName ); + virtual bool LoadBinaryModel( idFile * file, const ID_TIME_T sourceTimeStamp ); + virtual void WriteBinaryModel( idFile * file, ID_TIME_T *_timeStamp = NULL ) const; + virtual dynamicModel_t IsDynamicModel() const; + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + virtual void Print() const; + virtual void List() const; + virtual void TouchData(); + virtual void PurgeModel(); + virtual void LoadModel(); + virtual int Memory() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual int NumJoints() const; + virtual const idMD5Joint * GetJoints() const; + virtual jointHandle_t GetJointHandle( const char *name ) const; + virtual const char * GetJointName( jointHandle_t handle ) const; + virtual const idJointQuat * GetDefaultPose() const; + virtual int NearestJoint( int surfaceNum, int a, int b, int c ) const; + + virtual bool SupportsBinaryModel() { return true; } + +private: + idList joints; + idList defaultPose; + idList invertedDefaultPose; + idList meshes; + + void DrawJoints( const renderEntity_t *ent, const viewDef_t *view ) const; + void ParseJoint( idLexer &parser, idMD5Joint *joint, idJointQuat *defaultPose ); +}; + +/* +=============================================================================== + + MD3 animated model + +=============================================================================== +*/ + +struct md3Header_s; +struct md3Surface_s; + +class idRenderModelMD3 : public idRenderModelStatic { +public: + virtual void InitFromFile( const char *fileName ); + virtual bool SupportsBinaryModel() { return false; } + virtual dynamicModel_t IsDynamicModel() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + +private: + int index; // model = tr.models[model->index] + int dataSize; // just for listing purposes + struct md3Header_s * md3; // only if type == MOD_MESH + int numLods; + + void LerpMeshVertexes( srfTriangles_t *tri, const struct md3Surface_s *surf, const float backlerp, const int frame, const int oldframe ) const; +}; + +/* +=============================================================================== + + Liquid model + +=============================================================================== +*/ + +class idRenderModelLiquid : public idRenderModelStatic { +public: + idRenderModelLiquid(); + + virtual void InitFromFile( const char *fileName ); + virtual bool SupportsBinaryModel() { return false; } + virtual dynamicModel_t IsDynamicModel() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + virtual void Reset(); + void IntersectBounds( const idBounds &bounds, float displacement ); + +private: + modelSurface_t GenerateSurface( float lerp ); + void WaterDrop( int x, int y, float *page ); + void Update(); + + int verts_x; + int verts_y; + float scale_x; + float scale_y; + int time; + int liquid_type; + int update_tics; + int seed; + + idRandom random; + + const idMaterial * shader; + deformInfo_t * deformInfo; // used to create srfTriangles_t from base frames + // and new vertexes + + float density; + float drop_height; + int drop_radius; + float drop_delay; + + idList pages; + float * page1; + float * page2; + + idList verts; + + int nextDropTime; + +}; + +/* +=============================================================================== + + PRT model + +=============================================================================== +*/ + +class idRenderModelPrt : public idRenderModelStatic { +public: + idRenderModelPrt(); + + virtual void InitFromFile( const char *fileName ); + virtual bool SupportsBinaryModel() { return false; } + virtual void TouchData(); + virtual dynamicModel_t IsDynamicModel() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + virtual float DepthHack() const; + virtual int Memory() const; + + // with the addModels2 arrangement we could have light accepting and + // shadowing dynamic models, but the original game never did + virtual bool ModelHasDrawingSurfaces() const { return true; }; + virtual bool ModelHasInteractingSurfaces() const { return false; }; + virtual bool ModelHasShadowCastingSurfaces() const { return false; }; + +private: + const idDeclParticle * particleSystem; +}; + +/* +=============================================================================== + + Beam model + +=============================================================================== +*/ + +class idRenderModelBeam : public idRenderModelStatic { +public: + virtual dynamicModel_t IsDynamicModel() const; + virtual bool SupportsBinaryModel() { return false; } + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + // with the addModels2 arrangement we could have light accepting and + // shadowing dynamic models, but the original game never did + virtual bool ModelHasDrawingSurfaces() const { return true; }; + virtual bool ModelHasInteractingSurfaces() const { return false; }; + virtual bool ModelHasShadowCastingSurfaces() const { return false; }; +}; + +/* +=============================================================================== + + Beam model + +=============================================================================== +*/ +#define MAX_TRAIL_PTS 20 + +struct Trail_t { + int lastUpdateTime; + int duration; + + idVec3 pts[MAX_TRAIL_PTS]; + int numPoints; +}; + +class idRenderModelTrail : public idRenderModelStatic { + idList trails; + int numActive; + idBounds trailBounds; + +public: + idRenderModelTrail(); + + virtual dynamicModel_t IsDynamicModel() const; + virtual bool SupportsBinaryModel() { return false; } + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + // with the addModels2 arrangement we could have light accepting and + // shadowing dynamic models, but the original game never did + virtual bool ModelHasDrawingSurfaces() const { return true; }; + virtual bool ModelHasInteractingSurfaces() const { return false; }; + virtual bool ModelHasShadowCastingSurfaces() const { return false; }; + + int NewTrail( idVec3 pt, int duration ); + void UpdateTrail( int index, idVec3 pt ); + void DrawTrail( int index, const struct renderEntity_s *ent, srfTriangles_t *tri, float globalAlpha ); +}; + +/* +=============================================================================== + + Lightning model + +=============================================================================== +*/ + +class idRenderModelLightning : public idRenderModelStatic { +public: + virtual dynamicModel_t IsDynamicModel() const; + virtual bool SupportsBinaryModel() { return false; } + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + // with the addModels2 arrangement we could have light accepting and + // shadowing dynamic models, but the original game never did + virtual bool ModelHasDrawingSurfaces() const { return true; }; + virtual bool ModelHasInteractingSurfaces() const { return false; }; + virtual bool ModelHasShadowCastingSurfaces() const { return false; }; +}; + +/* +================================================================================ + + idRenderModelSprite + +================================================================================ +*/ +class idRenderModelSprite : public idRenderModelStatic { +public: + virtual dynamicModel_t IsDynamicModel() const; + virtual bool SupportsBinaryModel() { return false; } + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + // with the addModels2 arrangement we could have light accepting and + // shadowing dynamic models, but the original game never did + virtual bool ModelHasDrawingSurfaces() const { return true; }; + virtual bool ModelHasInteractingSurfaces() const { return false; }; + virtual bool ModelHasShadowCastingSurfaces() const { return false; }; +}; + +#endif /* !__MODEL_LOCAL_H__ */ diff --git a/neo/renderer/Model_lwo.cpp b/neo/renderer/Model_lwo.cpp new file mode 100644 index 00000000..e883b4b5 --- /dev/null +++ b/neo/renderer/Model_lwo.cpp @@ -0,0 +1,4169 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + + +#include "Model_lwo.h" + +/* +====================================================================== + + Converted from lwobject sample prog from LW 6.5 SDK. + +====================================================================== +*/ + +/* +====================================================================== +lwFreeClip() + +Free memory used by an lwClip. +====================================================================== */ + +void lwFreeClip( lwClip *clip ) +{ + if ( clip ) { + lwListFree( clip->ifilter, (void (__cdecl *)(void *))lwFreePlugin ); + lwListFree( clip->pfilter, (void (__cdecl *)(void *))lwFreePlugin ); + switch( clip->type ) { + case ID_STIL: { + if ( clip->source.still.name ) Mem_Free( clip->source.still.name ); + break; + } + case ID_ISEQ: { + if ( clip->source.seq.suffix ) Mem_Free( clip->source.seq.suffix ); + if ( clip->source.seq.prefix ) Mem_Free( clip->source.seq.prefix ); + break; + } + case ID_ANIM: { + if ( clip->source.anim.server ) Mem_Free( clip->source.anim.server ); + if ( clip->source.anim.name ) Mem_Free( clip->source.anim.name ); + break; + } + case ID_XREF: { + if ( clip->source.xref.string ) Mem_Free( clip->source.xref.string ); + break; + } + case ID_STCC: { + if ( clip->source.cycle.name ) Mem_Free( clip->source.cycle.name ); + break; + } + } + Mem_Free( clip ); + } +} + + +/* +====================================================================== +lwGetClip() + +Read image references from a CLIP chunk in an LWO2 file. +====================================================================== */ + +lwClip *lwGetClip( idFile *fp, int cksize ) +{ + lwClip *clip; + lwPlugin *filt; + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* allocate the Clip structure */ + + clip = (lwClip*)Mem_ClearedAlloc( sizeof( lwClip ), TAG_MODEL ); + if ( !clip ) goto Fail; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* index */ + + clip->index = getI4( fp ); + + /* first subchunk header */ + + clip->type = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + sz += sz & 1; + set_flen( 0 ); + + switch ( clip->type ) { + case ID_STIL: + clip->source.still.name = getS0( fp ); + break; + + case ID_ISEQ: + clip->source.seq.digits = getU1( fp ); + clip->source.seq.flags = getU1( fp ); + clip->source.seq.offset = getI2( fp ); + clip->source.seq.start = getI2( fp ); + clip->source.seq.end = getI2( fp ); + clip->source.seq.prefix = getS0( fp ); + clip->source.seq.suffix = getS0( fp ); + break; + + case ID_ANIM: + clip->source.anim.name = getS0( fp ); + clip->source.anim.server = getS0( fp ); + rlen = get_flen(); + clip->source.anim.data = getbytes( fp, sz - rlen ); + break; + + case ID_XREF: + clip->source.xref.index = getI4( fp ); + clip->source.xref.string = getS0( fp ); + break; + + case ID_STCC: + clip->source.cycle.lo = getI2( fp ); + clip->source.cycle.hi = getI2( fp ); + clip->source.cycle.name = getS0( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = fp->Tell() - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) + return clip; + + /* process subchunks as they're encountered */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TIME: + clip->start_time = getF4( fp ); + clip->duration = getF4( fp ); + clip->frame_rate = getF4( fp ); + break; + + case ID_CONT: + clip->contrast.val = getF4( fp ); + clip->contrast.eindex = getVX( fp ); + break; + + case ID_BRIT: + clip->brightness.val = getF4( fp ); + clip->brightness.eindex = getVX( fp ); + break; + + case ID_SATR: + clip->saturation.val = getF4( fp ); + clip->saturation.eindex = getVX( fp ); + break; + + case ID_HUE: + clip->hue.val = getF4( fp ); + clip->hue.eindex = getVX( fp ); + break; + + case ID_GAMM: + clip->gamma.val = getF4( fp ); + clip->gamma.eindex = getVX( fp ); + break; + + case ID_NEGA: + clip->negative = getU2( fp ); + break; + + case ID_IFLT: + case ID_PFLT: + filt = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL ); + if ( !filt ) goto Fail; + + filt->name = getS0( fp ); + filt->flags = getU2( fp ); + rlen = get_flen(); + filt->data = getbytes( fp, sz - rlen ); + + if ( id == ID_IFLT ) { + lwListAdd( (void**)&clip->ifilter, filt ); + clip->nifilters++; + } + else { + lwListAdd( (void**)&clip->pfilter, filt ); + clip->npfilters++; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = fp->Tell() - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return clip; + +Fail: + lwFreeClip( clip ); + return NULL; +} + + +/* +====================================================================== +lwFindClip() + +Returns an lwClip pointer, given a clip index. +====================================================================== */ + +lwClip *lwFindClip( lwClip *list, int index ) +{ + lwClip *clip; + + clip = list; + while ( clip ) { + if ( clip->index == index ) break; + clip = clip->next; + } + return clip; +} + + +/* +====================================================================== +lwFreeEnvelope() + +Free the memory used by an lwEnvelope. +====================================================================== */ + +void lwFree( void *ptr ) { + Mem_Free( ptr ); +} + +void lwFreeEnvelope( lwEnvelope *env ) +{ + if ( env ) { + if ( env->name ) Mem_Free( env->name ); + lwListFree( env->key, lwFree ); + lwListFree( env->cfilter, (void (__cdecl *)(void *))lwFreePlugin ); + Mem_Free( env ); + } +} + + +static int compare_keys( lwKey *k1, lwKey *k2 ) +{ + return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0; +} + + +/* +====================================================================== +lwGetEnvelope() + +Read an ENVL chunk from an LWO2 file. +====================================================================== */ + +lwEnvelope *lwGetEnvelope( idFile *fp, int cksize ) +{ + lwEnvelope *env = NULL; + lwKey *key = NULL; + lwPlugin *plug = NULL; + unsigned int id; + unsigned short sz; + float f[ 4 ]; + int i, nparams, pos, rlen; + + + /* allocate the Envelope structure */ + + env = (lwEnvelope*)Mem_ClearedAlloc( sizeof( lwEnvelope ), TAG_MODEL ); + if ( !env ) goto Fail; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* index */ + + env->index = getVX( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TYPE: + env->type = getU2( fp ); + break; + + case ID_NAME: + env->name = getS0( fp ); + break; + + case ID_PRE: + env->behavior[ 0 ] = getU2( fp ); + break; + + case ID_POST: + env->behavior[ 1 ] = getU2( fp ); + break; + + case ID_KEY: + key = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ), TAG_MODEL ); + if ( !key ) goto Fail; + key->time = getF4( fp ); + key->value = getF4( fp ); + lwListInsert( (void**)&env->key, key, (int (__cdecl *)(void *,void *))compare_keys ); + env->nkeys++; + break; + + case ID_SPAN: + if ( key == NULL ) goto Fail; + key->shape = getU4( fp ); + + nparams = ( sz - 4 ) / 4; + if ( nparams > 4 ) nparams = 4; + for ( i = 0; i < nparams; i++ ) + f[ i ] = getF4( fp ); + + switch ( key->shape ) { + case ID_TCB: + key->tension = f[ 0 ]; + key->continuity = f[ 1 ]; + key->bias = f[ 2 ]; + break; + + case ID_BEZI: + case ID_HERM: + case ID_BEZ2: + for ( i = 0; i < nparams; i++ ) + key->param[ i ] = f[ i ]; + break; + } + break; + + case ID_CHAN: + plug = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL ); + if ( !plug ) goto Fail; + + plug->name = getS0( fp ); + plug->flags = getU2( fp ); + plug->data = getbytes( fp, sz - get_flen() ); + + lwListAdd( (void**)&env->cfilter, plug ); + env->ncfilters++; + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the ENVL chunk? */ + + rlen = fp->Tell() - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return env; + +Fail: + lwFreeEnvelope( env ); + return NULL; +} + + +/* +====================================================================== +lwFindEnvelope() + +Returns an lwEnvelope pointer, given an envelope index. +====================================================================== */ + +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ) +{ + lwEnvelope *env; + + env = list; + while ( env ) { + if ( env->index == index ) break; + env = env->next; + } + return env; +} + + +/* +====================================================================== +range() + +Given the value v of a periodic function, returns the equivalent value +v2 in the principal interval [lo, hi]. If i isn't NULL, it receives +the number of wavelengths between v and v2. + + v2 = v - i * (hi - lo) + +For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1. +====================================================================== */ + +static float range( float v, float lo, float hi, int *i ) +{ + float v2, r = hi - lo; + + if ( r == 0.0 ) { + if ( i ) *i = 0; + return lo; + } + + v2 = lo + v - r * ( float ) floor(( double ) v / r ); + if ( i ) *i = -( int )(( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 )); + + return v2; +} + + +/* +====================================================================== +hermite() + +Calculate the Hermite coefficients. +====================================================================== */ + +static void hermite( float t, float *h1, float *h2, float *h3, float *h4 ) +{ + float t2, t3; + + t2 = t * t; + t3 = t * t2; + + *h2 = 3.0f * t2 - t3 - t3; + *h1 = 1.0f - *h2; + *h4 = t3 - t2; + *h3 = *h4 - t2 + t; +} + + +/* +====================================================================== +bezier() + +Interpolate the value of a 1D Bezier curve. +====================================================================== */ + +static float bezier( float x0, float x1, float x2, float x3, float t ) +{ + float a, b, c, t2, t3; + + t2 = t * t; + t3 = t2 * t; + + c = 3.0f * ( x1 - x0 ); + b = 3.0f * ( x2 - x1 ) - c; + a = x3 - x0 - c - b; + + return a * t3 + b * t2 + c * t + x0; +} + + +/* +====================================================================== +bez2_time() + +Find the t for which bezier() returns the input time. The handle +endpoints of a BEZ2 curve represent the control points, and these have +(time, value) coordinates, so time is used as both a coordinate and a +parameter for this curve type. +====================================================================== */ + +static float bez2_time( float x0, float x1, float x2, float x3, float time, + float *t0, float *t1 ) +{ + float v, t; + + t = *t0 + ( *t1 - *t0 ) * 0.5f; + v = bezier( x0, x1, x2, x3, t ); + if ( idMath::Fabs( time - v ) > .0001f ) { + if ( v > time ) + *t1 = t; + else + *t0 = t; + return bez2_time( x0, x1, x2, x3, time, t0, t1 ); + } + else + return t; +} + + +/* +====================================================================== +bez2() + +Interpolate the value of a BEZ2 curve. +====================================================================== */ + +static float bez2( lwKey *key0, lwKey *key1, float time ) +{ + float x, y, t, t0 = 0.0f, t1 = 1.0f; + + if ( key0->shape == ID_BEZ2 ) + x = key0->time + key0->param[ 2 ]; + else + x = key0->time + ( key1->time - key0->time ) / 3.0f; + + t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time, + time, &t0, &t1 ); + + if ( key0->shape == ID_BEZ2 ) + y = key0->value + key0->param[ 3 ]; + else + y = key0->value + key0->param[ 1 ] / 3.0f; + + return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t ); +} + + +/* +====================================================================== +outgoing() + +Return the outgoing tangent to the curve at key0. The value returned +for the BEZ2 case is used when extrapolating a linear pre behavior and +when interpolating a non-BEZ2 span. +====================================================================== */ + +static float outgoing( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, out; + + switch ( key0->shape ) + { + case ID_TCB: + a = ( 1.0f - key0->tension ) + * ( 1.0f + key0->continuity ) + * ( 1.0f + key0->bias ); + b = ( 1.0f - key0->tension ) + * ( 1.0f - key0->continuity ) + * ( 1.0f - key0->bias ); + d = key1->value - key0->value; + + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( a * ( key0->value - key0->prev->value ) + b * d ); + } + else + out = b * d; + break; + + case ID_LINE: + d = key1->value - key0->value; + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( key0->value - key0->prev->value + d ); + } + else + out = d; + break; + + case ID_BEZI: + case ID_HERM: + out = key0->param[ 1 ]; + if ( key0->prev ) + out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + break; + + case ID_BEZ2: + out = key0->param[ 3 ] * ( key1->time - key0->time ); + if ( idMath::Fabs( key0->param[ 2 ] ) > 1e-5f ) + out /= key0->param[ 2 ]; + else + out *= 1e5f; + break; + + case ID_STEP: + default: + out = 0.0f; + break; + } + + return out; +} + + +/* +====================================================================== +incoming() + +Return the incoming tangent to the curve at key1. The value returned +for the BEZ2 case is used when extrapolating a linear post behavior. +====================================================================== */ + +static float incoming( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, in; + + switch ( key1->shape ) + { + case ID_LINE: + d = key1->value - key0->value; + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( key1->next->value - key1->value + d ); + } + else + in = d; + break; + + case ID_TCB: + a = ( 1.0f - key1->tension ) + * ( 1.0f - key1->continuity ) + * ( 1.0f + key1->bias ); + b = ( 1.0f - key1->tension ) + * ( 1.0f + key1->continuity ) + * ( 1.0f - key1->bias ); + d = key1->value - key0->value; + + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( b * ( key1->next->value - key1->value ) + a * d ); + } + else + in = a * d; + break; + + case ID_BEZI: + case ID_HERM: + in = key1->param[ 0 ]; + if ( key1->next ) + in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + break; + + case ID_BEZ2: + in = key1->param[ 1 ] * ( key1->time - key0->time ); + if ( idMath::Fabs( key1->param[ 0 ] ) > 1e-5f ) + in /= key1->param[ 0 ]; + else + in *= 1e5f; + break; + + case ID_STEP: + default: + in = 0.0f; + break; + } + + return in; +} + + +/* +====================================================================== +evalEnvelope() + +Given a list of keys and a time, returns the interpolated value of the +envelope at that time. +====================================================================== */ + +float evalEnvelope( lwEnvelope *env, float time ) +{ + lwKey *key0, *key1, *skey, *ekey; + float t, h1, h2, h3, h4, in, out, offset = 0.0f; + int noff; + + // Start key + skey = ekey = env->key; + + /* if there's no key, the value is 0 */ + if ( env->nkeys == 0 || skey == NULL ) { + return 0.0f; + } + + /* if there's only one key, the value is constant */ + if ( env->nkeys == 1 ) { + return env->key->value; + } + + /* find the last keys */ + while ( ekey->next != NULL ) { + ekey = ekey->next; + } + + /* use pre-behavior if time is before first key time */ + if ( time < skey->time ) { + switch ( env->behavior[ 0 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return skey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + if ( skey->next != NULL ) { + out = outgoing( skey, skey->next ) + / ( skey->next->time - skey->time ); + return out * ( time - skey->time ) + skey->value; + } else { + return 0.0f; + } + } + } + + /* use post-behavior if time is after last key time */ + + else if ( time > ekey->time ) { + switch ( env->behavior[ 1 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return ekey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + in = incoming( ekey->prev, ekey ) + / ( ekey->time - ekey->prev->time ); + return in * ( time - ekey->time ) + ekey->value; + } + } + + /* get the endpoints of the interval being evaluated */ + + key0 = env->key; + if ( key0 == NULL || key0->next == NULL ) { + return 0.0f; + } + while ( time > key0->next->time ) + key0 = key0->next; + key1 = key0->next; + if ( key1 == NULL ) { + return 0.0f; + } + + /* check for singularities first */ + + if ( time == key0->time ) + return key0->value + offset; + else if ( time == key1->time ) + return key1->value + offset; + + /* get interval length, time in [0, 1] */ + + t = ( time - key0->time ) / ( key1->time - key0->time ); + + /* interpolate */ + + switch ( key1->shape ) + { + case ID_TCB: + case ID_BEZI: + case ID_HERM: + out = outgoing( key0, key1 ); + in = incoming( key0, key1 ); + hermite( t, &h1, &h2, &h3, &h4 ); + return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset; + + case ID_BEZ2: + return bez2( key0, key1, time ) + offset; + + case ID_LINE: + return key0->value + t * ( key1->value - key0->value ) + offset; + + case ID_STEP: + return key0->value + offset; + + default: + return offset; + } +} + + + +/* +====================================================================== +lwListFree() + +Free the items in a list. +====================================================================== */ + +void lwListFree( void *list, void ( *freeNode )( void * )) +{ + lwNode *node, *next; + + node = ( lwNode * ) list; + while ( node ) { + next = node->next; + freeNode( node ); + node = next; + } +} + + +/* +====================================================================== +lwListAdd() + +Append a node to a list. +====================================================================== */ + +void lwListAdd( void **list, void *node ) +{ + lwNode *head = NULL, *tail = NULL; + + head = *(( lwNode ** ) list ); + if ( head == NULL ) { + *list = node; + return; + } + while ( head ) { + tail = head; + head = head->next; + } + tail->next = ( lwNode * ) node; + (( lwNode * ) node )->prev = tail; +} + + +/* +====================================================================== +lwListInsert() + +Insert a node into a list in sorted order. +====================================================================== */ + +void lwListInsert( void **vlist, void *vitem, int ( *compare )( void *, void * )) +{ + lwNode **list, *item, *node, *prev; + + if ( !*vlist ) { + *vlist = vitem; + return; + } + + list = ( lwNode ** ) vlist; + item = ( lwNode * ) vitem; + node = *list; + prev = NULL; + + while ( node ) { + if ( 0 < compare( node, item )) break; + prev = node; + node = node->next; + } + + if ( !prev ) { + *list = item; + node->prev = item; + item->next = node; + } + else if ( !node ) { + prev->next = item; + item->prev = prev; + } + else { + item->next = node; + item->prev = prev; + prev->next = item; + node->prev = item; + } +} + +/* +====================================================================== +flen + +This accumulates a count of the number of bytes read. Callers can set +it at the beginning of a sequence of reads and then retrieve it to get +the number of bytes actually read. If one of the I/O functions fails, +flen is set to an error code, after which the I/O functions ignore +read requests until flen is reset. +====================================================================== */ + +#define FLEN_ERROR -9999 + +static int flen; + +void set_flen( int i ) { flen = i; } + +int get_flen() { return flen; } + +void *getbytes( idFile *fp, int size ) +{ + void *data; + + if ( flen == FLEN_ERROR ) return NULL; + if ( size < 0 ) { + flen = FLEN_ERROR; + return NULL; + } + data = Mem_ClearedAlloc( size, TAG_MODEL ); + if ( !data ) { + flen = FLEN_ERROR; + return NULL; + } + if ( size != fp->Read( data, size ) ) { + flen = FLEN_ERROR; + Mem_Free( data ); + return NULL; + } + + flen += size; + return data; +} + + +void skipbytes( idFile *fp, int n ) +{ + if ( flen == FLEN_ERROR ) return; + if ( fp->Seek( n, FS_SEEK_CUR )) + flen = FLEN_ERROR; + else + flen += n; +} + + +int getI1( idFile *fp ) +{ + int i, c; + + if ( flen == FLEN_ERROR ) return 0; + c = 0; + i = fp->Read(&c, 1); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + if ( c > 127 ) c -= 256; + flen += 1; + return c; +} + + +short getI2( idFile *fp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 2 != fp->Read( &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +int getI4( idFile *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 4 != fp->Read( &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +unsigned char getU1( idFile *fp ) +{ + int i, c; + + if ( flen == FLEN_ERROR ) return 0; + c = 0; + i = fp->Read(&c, 1); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + flen += 1; + return c; +} + + +unsigned short getU2( idFile *fp ) +{ + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 2 != fp->Read( &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +unsigned int getU4( idFile *fp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 4 != fp->Read( &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +int getVX( idFile *fp ) +{ + byte c; + int i; + + if ( flen == FLEN_ERROR ) return 0; + + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + + if ( c != 0xFF ) { + i = c << 8; + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i |= c; + flen += 2; + } + else { + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i = c << 16; + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i |= c << 8; + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i |= c; + flen += 4; + } + + return i; +} + + +float getF4( idFile *fp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + if ( 4 != fp->Read( &f, 4 ) ) { + flen = FLEN_ERROR; + return 0.0f; + } + BigRevBytes( &f, 4, 1 ); + flen += 4; + + if ( IEEE_FLT_IS_DENORMAL( f ) ) { + f = 0.0f; + } + return f; +} + + +char *getS0( idFile *fp ) +{ + char *s; + int i, c, len, pos; + + if ( flen == FLEN_ERROR ) return NULL; + + pos = fp->Tell(); + for ( i = 1; ; i++ ) { + c = 0; + if (fp->Read(&c, 1) == -1) { + flen = FLEN_ERROR; + return NULL; + } + if ( c == 0 ) break; + } + + if ( i == 1 ) { + if ( fp->Seek( pos + 2, FS_SEEK_SET )) + flen = FLEN_ERROR; + else + flen += 2; + return NULL; + } + + len = i + ( i & 1 ); + s = (char*)Mem_ClearedAlloc( len, TAG_MODEL ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + if ( fp->Seek( pos, FS_SEEK_SET )) { + flen = FLEN_ERROR; + return NULL; + } + if ( len != fp->Read( s, len )) { + flen = FLEN_ERROR; + return NULL; + } + + flen += len; + return s; +} + + +int sgetI1( unsigned char **bp ) +{ + assert( bp != NULL && *bp != NULL ); // remove compiler warning + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = **bp; + if ( i > 127 ) i -= 256; + flen += 1; + (*bp)++; + return i; +} + + +short sgetI2( unsigned char **bp ) +{ + assert( bp != NULL && *bp != NULL ); // remove compiler warning + short i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 2 ); + BigRevBytes( &i, 2, 1 ); + flen += 2; + (*bp) += 2; + return i; +} + + +int sgetI4( unsigned char **bp ) +{ + assert( bp != NULL && *bp != NULL ); // remove compiler warning + short i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, sizeof( i ) ); + BigRevBytes( &i, 4, 1 ); + flen += 4; + (*bp) += 4; + return i; +} + + +unsigned char sgetU1( unsigned char **bp ) +{ + unsigned char c; + + if ( flen == FLEN_ERROR ) return 0; + c = **bp; + flen += 1; + (*bp)++; + return c; +} + + +unsigned short sgetU2( unsigned char **bp ) +{ + unsigned char *buf = *bp; + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + i = ( buf[ 0 ] << 8 ) | buf[ 1 ]; + flen += 2; + (*bp) += 2; + return i; +} + + +unsigned int sgetU4( unsigned char **bp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + BigRevBytes( &i, 4, 1 ); + flen += 4; + (*bp) += 4; + return i; +} + + +int sgetVX( unsigned char **bp ) +{ + unsigned char *buf = *bp; + int i; + + if ( flen == FLEN_ERROR ) return 0; + + if ( buf[ 0 ] != 0xFF ) { + i = buf[ 0 ] << 8 | buf[ 1 ]; + flen += 2; + (*bp) += 2; + } + else { + i = ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ]; + flen += 4; + (*bp) += 4; + } + return i; +} + + +float sgetF4( unsigned char **bp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + memcpy( &f, *bp, 4 ); + BigRevBytes( &f, 4, 1 ); + flen += 4; + (*bp) += 4; + + if ( IEEE_FLT_IS_DENORMAL( f ) ) { + f = 0.0f; + } + return f; +} + + +char *sgetS0( unsigned char **bp ) +{ + char *s; + unsigned char *buf = *bp; + int len; + + if ( flen == FLEN_ERROR ) return NULL; + + len = strlen( (const char*)buf ) + 1; + if ( len == 1 ) { + flen += 2; + (*bp) += 2; + return NULL; + } + len += len & 1; + s = (char*)Mem_ClearedAlloc( len, TAG_MODEL ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + memcpy( s, buf, len ); + flen += len; + (*bp) += len; + return s; +} + +/* +====================================================================== +lwFreeLayer() + +Free memory used by an lwLayer. +====================================================================== */ + +void lwFreeLayer( lwLayer *layer ) +{ + if ( layer ) { + if ( layer->name ) Mem_Free( layer->name ); + lwFreePoints( &layer->point ); + lwFreePolygons( &layer->polygon ); + lwListFree( layer->vmap, (void (__cdecl *)(void *))lwFreeVMap ); + Mem_Free( layer ); + } +} + + +/* +====================================================================== +lwFreeObject() + +Free memory used by an lwObject. +====================================================================== */ + +void lwFreeObject( lwObject *object ) +{ + if ( object ) { + lwListFree( object->layer, (void (__cdecl *)(void *))lwFreeLayer ); + lwListFree( object->env, (void (__cdecl *)(void *))lwFreeEnvelope ); + lwListFree( object->clip, (void (__cdecl *)(void *))lwFreeClip ); + lwListFree( object->surf, (void (__cdecl *)(void *))lwFreeSurface ); + lwFreeTags( &object->taglist ); + Mem_Free( object ); + } +} + + +/* +====================================================================== +lwGetObject() + +Returns the contents of a LightWave object, given its filename, or +NULL if the file couldn't be loaded. On failure, failID and failpos +can be used to diagnose the cause. + +1. If the file isn't an LWO2 or an LWOB, failpos will contain 12 and + failID will be unchanged. + +2. If an error occurs while reading, failID will contain the most + recently read IFF chunk ID, and failpos will contain the value + returned by fp->Tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject( const char *filename, unsigned int *failID, int *failpos ) +{ + idFile *fp = NULL; + lwObject *object; + lwLayer *layer; + lwNode *node; + int id, formsize, type, cksize; + int i, rlen; + + fp = fileSystem->OpenFileRead( filename ); + if ( !fp ) { + return NULL; + } + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + fileSystem->CloseFile( fp ); + return NULL; + } + + /* is this a LW object? */ + + if ( id != ID_FORM ) { + fileSystem->CloseFile( fp ); + if ( failpos ) *failpos = 12; + return NULL; + } + + if ( type != ID_LWO2 ) { + fileSystem->CloseFile( fp ); + if ( type == ID_LWOB ) + return lwGetObject5( filename, failID, failpos ); + else { + if ( failpos ) *failpos = 12; + return NULL; + } + } + + /* allocate an object and a default layer */ + + object = (lwObject*)Mem_ClearedAlloc( sizeof( lwObject ), TAG_MODEL ); + if ( !object ) goto Fail; + + layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ), TAG_MODEL ); + if ( !layer ) goto Fail; + object->layer = layer; + + object->timeStamp = fp->Timestamp(); + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_LAYR: + if ( object->nlayers > 0 ) { + layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ), TAG_MODEL ); + if ( !layer ) goto Fail; + lwListAdd( (void**)&object->layer, layer ); + } + object->nlayers++; + + set_flen( 0 ); + layer->index = getU2( fp ); + layer->flags = getU2( fp ); + layer->pivot[ 0 ] = getF4( fp ); + layer->pivot[ 1 ] = getF4( fp ); + layer->pivot[ 2 ] = getF4( fp ); + layer->name = getS0( fp ); + + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen <= cksize - 2 ) + layer->parent = getU2( fp ); + rlen = get_flen(); + if ( rlen < cksize ) + fp->Seek( cksize - rlen, FS_SEEK_CUR ); + break; + + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail; + break; + + case ID_POLS: + if ( !lwGetPolygons( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail; + break; + + case ID_VMAP: + case ID_VMAD: + node = ( lwNode * ) lwGetVMap( fp, cksize, layer->point.offset, + layer->polygon.offset, id == ID_VMAD ); + if ( !node ) goto Fail; + lwListAdd( (void**)&layer->vmap, node ); + layer->nvmaps++; + break; + + case ID_PTAG: + if ( !lwGetPolygonTags( fp, cksize, &object->taglist, + &layer->polygon )) + goto Fail; + break; + + case ID_BBOX: + set_flen( 0 ); + for ( i = 0; i < 6; i++ ) + layer->bbox[ i ] = getF4( fp ); + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen < cksize ) + fp->Seek( cksize - rlen, FS_SEEK_CUR ); + break; + + case ID_TAGS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail; + break; + + case ID_ENVL: + node = ( lwNode * ) lwGetEnvelope( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void**)&object->env, node ); + object->nenvs++; + break; + + case ID_CLIP: + node = ( lwNode * ) lwGetClip( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void**)&object->clip, node ); + object->nclips++; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void**)&object->surf, node ); + object->nsurfs++; + break; + + case ID_DESC: + case ID_TEXT: + case ID_ICON: + default: + fp->Seek( cksize, FS_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= fp->Tell() - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail; + } + + fileSystem->CloseFile( fp ); + fp = NULL; + + if ( object->nlayers == 0 ) + object->nlayers = 1; + + layer = object->layer; + while ( layer ) { + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail; + lwGetVertNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointVMaps( &layer->point, layer->vmap )) goto Fail; + if ( !lwGetPolyVMaps( &layer->polygon, layer->vmap )) goto Fail; + layer = layer->next; + } + + return object; + +Fail: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = fp->Tell(); + fileSystem->CloseFile( fp ); + } + lwFreeObject( object ); + return NULL; +} + + + + +/* IDs specific to LWOB */ + +#define ID_SRFS LWID_('S','R','F','S') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_VLUM LWID_('V','L','U','M') +#define ID_VDIF LWID_('V','D','I','F') +#define ID_VSPC LWID_('V','S','P','C') +#define ID_RFLT LWID_('R','F','L','T') +#define ID_BTEX LWID_('B','T','E','X') +#define ID_CTEX LWID_('C','T','E','X') +#define ID_DTEX LWID_('D','T','E','X') +#define ID_LTEX LWID_('L','T','E','X') +#define ID_RTEX LWID_('R','T','E','X') +#define ID_STEX LWID_('S','T','E','X') +#define ID_TTEX LWID_('T','T','E','X') +#define ID_TFLG LWID_('T','F','L','G') +#define ID_TSIZ LWID_('T','S','I','Z') +#define ID_TCTR LWID_('T','C','T','R') +#define ID_TFAL LWID_('T','F','A','L') +#define ID_TVEL LWID_('T','V','E','L') +#define ID_TCLR LWID_('T','C','L','R') +#define ID_TVAL LWID_('T','V','A','L') +#define ID_TAMP LWID_('T','A','M','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_TAAS LWID_('T','A','A','S') +#define ID_TREF LWID_('T','R','E','F') +#define ID_TOPC LWID_('T','O','P','C') +#define ID_SDAT LWID_('S','D','A','T') +#define ID_TFP0 LWID_('T','F','P','0') +#define ID_TFP1 LWID_('T','F','P','1') + + +/* +====================================================================== +add_clip() + +Add a clip to the clip list. Used to store the contents of an RIMG or +TIMG surface subchunk. +====================================================================== */ + +static int add_clip( char *s, lwClip **clist, int *nclips ) +{ + lwClip *clip; + char *p; + + clip = (lwClip*)Mem_ClearedAlloc( sizeof( lwClip ), TAG_MODEL ); + if ( clip == NULL ) return 0; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + if ( ( p = strstr( s, "(sequence)" ) ) != NULL ) { + p[ -1 ] = 0; + clip->type = ID_ISEQ; + clip->source.seq.prefix = s; + clip->source.seq.digits = 3; + } + else { + clip->type = ID_STIL; + clip->source.still.name = s; + } + + (*nclips)++; + clip->index = *nclips; + + lwListAdd( (void**)clist, clip ); + + return clip->index; +} + + +/* +====================================================================== +add_tvel() + +Add a triple of envelopes to simulate the old texture velocity +parameters. +====================================================================== */ + +static int add_tvel( float pos[], float vel[], lwEnvelope **elist, int *nenvs ) +{ + lwEnvelope *env = NULL; + lwKey *key0 = NULL, *key1 = NULL; + int i; + + for ( i = 0; i < 3; i++ ) { + env = (lwEnvelope*)Mem_ClearedAlloc( sizeof( lwEnvelope ), TAG_MODEL ); + key0 = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ), TAG_MODEL ); + key1 = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ), TAG_MODEL ); + if ( !env || !key0 || !key1 ) return 0; + + key0->next = key1; + key0->value = pos[ i ]; + key0->time = 0.0f; + key1->prev = key0; + key1->value = pos[ i ] + vel[ i ] * 30.0f; + key1->time = 1.0f; + key0->shape = key1->shape = ID_LINE; + + env->index = *nenvs + i + 1; + env->type = 0x0301 + i; + env->name = (char*)Mem_ClearedAlloc( 11, TAG_MODEL ); + if ( env->name ) { + strcpy( env->name, "Position.X" ); + env->name[ 9 ] += i; + } + env->key = key0; + env->nkeys = 2; + env->behavior[ 0 ] = BEH_LINEAR; + env->behavior[ 1 ] = BEH_LINEAR; + + lwListAdd( (void**)elist, env ); + } + assert( env != NULL ); + + *nenvs += 3; + return env->index - 2; +} + + +/* +====================================================================== +get_texture() + +Create a new texture for BTEX, CTEX, etc. subchunks. +====================================================================== */ + +static lwTexture *get_texture( char *s ) +{ + lwTexture *tex; + + tex = (lwTexture*)Mem_ClearedAlloc( sizeof( lwTexture ), TAG_MODEL ); + if ( !tex ) return NULL; + + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + if ( strstr( s, "Image Map" )) { + tex->type = ID_IMAP; + if ( strstr( s, "Planar" )) tex->param.imap.projection = 0; + else if ( strstr( s, "Cylindrical" )) tex->param.imap.projection = 1; + else if ( strstr( s, "Spherical" )) tex->param.imap.projection = 2; + else if ( strstr( s, "Cubic" )) tex->param.imap.projection = 3; + else if ( strstr( s, "Front" )) tex->param.imap.projection = 4; + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.amplitude.val = 1.0f; + Mem_Free( s ); + } + else { + tex->type = ID_PROC; + tex->param.proc.name = s; + } + + return tex; +} + + +/* +====================================================================== +lwGetSurface5() + +Read an lwSurface from an LWOB file. +====================================================================== */ + +lwSurface *lwGetSurface5( idFile *fp, int cksize, lwObject *obj ) +{ + lwSurface *surf = NULL; + lwTexture *tex = NULL; + lwPlugin *shdr = NULL; + char *s = NULL; + float v[ 3 ]; + unsigned int id, flags; + unsigned short sz; + int pos, rlen, i = 0; + + + /* allocate the Surface structure */ + + surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ), TAG_MODEL ); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* name */ + + surf->name = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 1 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 2 ] = getU1( fp ) / 255.0f; + break; + + case ID_FLAG: + flags = getU2( fp ); + if ( flags & 4 ) surf->smooth = 1.56207f; + if ( flags & 8 ) surf->color_hilite.val = 1.0f; + if ( flags & 16 ) surf->color_filter.val = 1.0f; + if ( flags & 128 ) surf->dif_sharp.val = 0.5f; + if ( flags & 256 ) surf->sideflags = 3; + if ( flags & 512 ) surf->add_trans.val = 1.0f; + break; + + case ID_LUMI: + surf->luminosity.val = getI2( fp ) / 256.0f; + break; + + case ID_VLUM: + surf->luminosity.val = getF4( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getI2( fp ) / 256.0f; + break; + + case ID_VDIF: + surf->diffuse.val = getF4( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getI2( fp ) / 256.0f; + break; + + case ID_VSPC: + surf->specularity.val = getF4( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = ( float ) logf( ( float) getU2( fp )) / 20.7944f; + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RFLT: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + s = getS0( fp ); + surf->reflection.cindex = add_clip( s, &obj->clip, &obj->nclips ); + surf->reflection.options = 3; + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + break; + + case ID_BTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->bump.tex, tex ); + break; + + case ID_CTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->color.tex, tex ); + break; + + case ID_DTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->diffuse.tex, tex ); + break; + + case ID_LTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->luminosity.tex, tex ); + break; + + case ID_RTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->reflection.val.tex, tex ); + break; + + case ID_STEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->specularity.tex, tex ); + break; + + case ID_TTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->transparency.val.tex, tex ); + break; + + case ID_TFLG: + assert( tex != NULL ); + flags = getU2( fp ); + + if ( flags & 1 ) i = 0; + if ( flags & 2 ) i = 1; + if ( flags & 4 ) i = 2; + tex->axis = i; + if ( tex->type == ID_IMAP ) + tex->param.imap.axis = i; + else + tex->param.proc.axis = i; + + if ( flags & 8 ) tex->tmap.coord_sys = 1; + if ( flags & 16 ) tex->negative = 1; + if ( flags & 32 ) tex->param.imap.pblend = 1; + if ( flags & 64 ) { + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.aas_flags = 1; + } + break; + + case ID_TSIZ: + assert( tex != NULL ); + for ( i = 0; i < 3; i++ ) + tex->tmap.size.val[ i ] = getF4( fp ); + break; + + case ID_TCTR: + assert( tex != NULL ); + for ( i = 0; i < 3; i++ ) + tex->tmap.center.val[ i ] = getF4( fp ); + break; + + case ID_TFAL: + assert( tex != NULL ); + for ( i = 0; i < 3; i++ ) + tex->tmap.falloff.val[ i ] = getF4( fp ); + break; + + case ID_TVEL: + assert( tex != NULL ); + for ( i = 0; i < 3; i++ ) + v[ i ] = getF4( fp ); + tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v, + &obj->env, &obj->nenvs ); + break; + + case ID_TCLR: + assert( tex != NULL ); + if ( tex->type == ID_PROC ) + for ( i = 0; i < 3; i++ ) + tex->param.proc.value[ i ] = getU1( fp ) / 255.0f; + break; + + case ID_TVAL: + assert( tex != NULL ); + tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f; + break; + + case ID_TAMP: + assert( tex != NULL ); + if ( tex->type == ID_IMAP ) + tex->param.imap.amplitude.val = getF4( fp ); + break; + + case ID_TIMG: + assert( tex != NULL ); + s = getS0( fp ); + tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips ); + break; + + case ID_TAAS: + assert( tex != NULL ); + tex->param.imap.aa_strength = getF4( fp ); + tex->param.imap.aas_flags = 1; + break; + + case ID_TREF: + assert( tex != NULL ); + tex->tmap.ref_object = (char*)getbytes( fp, sz ); + break; + + case ID_TOPC: + assert( tex != NULL ); + tex->opacity.val = getF4( fp ); + break; + + case ID_TFP0: + assert( tex != NULL ); + if ( tex->type == ID_IMAP ) + tex->param.imap.wrapw.val = getF4( fp ); + break; + + case ID_TFP1: + assert( tex != NULL ); + if ( tex->type == ID_IMAP ) + tex->param.imap.wraph.val = getF4( fp ); + break; + + case ID_SHDR: + shdr = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL ); + if ( !shdr ) goto Fail; + shdr->name = (char*)getbytes( fp, sz ); + lwListAdd( (void**)&surf->shader, shdr ); + surf->nshaders++; + break; + + case ID_SDAT: + assert( shdr != NULL ); + shdr->data = getbytes( fp, sz ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} + + +/* +====================================================================== +lwGetPolygons5() + +Read polygon records from a POLS chunk in an LWOB file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons5( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, nv, nverts, npols; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = (unsigned char*)getbytes( fp, cksize ); + if ( !buf ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize ) { + nv = sgetU2( &bp ); + nverts += nv; + npols++; + bp += 2 * nv; + i = sgetI2( &bp ); + if ( i < 0 ) bp += 2; /* detail polygons */ + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + + pp->nverts = nv; + pp->type = ID_FACE; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pv[ j ].index = sgetU2( &bp ) + ptoffset; + j = sgetI2( &bp ); + if ( j < 0 ) { + j = -j; + bp += 2; + } + j -= 1; + pp->surf = ( lwSurface * ) j; + + pp++; + pv += nv; + } + + Mem_Free( buf ); + return 1; + +Fail: + if ( buf ) Mem_Free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +getLWObject5() + +Returns the contents of an LWOB, given its filename, or NULL if the +file couldn't be loaded. On failure, failID and failpos can be used +to diagnose the cause. + +1. If the file isn't an LWOB, failpos will contain 12 and failID will + be unchanged. + +2. If an error occurs while reading an LWOB, failID will contain the + most recently read IFF chunk ID, and failpos will contain the + value returned by fp->Tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject5( const char *filename, unsigned int *failID, int *failpos ) +{ + idFile *fp = NULL; + lwObject *object; + lwLayer *layer; + lwNode *node; + int id, formsize, type, cksize; + + + /* open the file */ + + //fp = fopen( filename, "rb" ); + //if ( !fp ) return NULL; + + /* read the first 12 bytes */ + fp = fileSystem->OpenFileRead( filename ); + if ( !fp ) { + return NULL; + } + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + fileSystem->CloseFile( fp ); + return NULL; + } + + /* LWOB? */ + + if ( id != ID_FORM || type != ID_LWOB ) { + fileSystem->CloseFile( fp ); + if ( failpos ) *failpos = 12; + return NULL; + } + + /* allocate an object and a default layer */ + + object = (lwObject*)Mem_ClearedAlloc( sizeof( lwObject ), TAG_MODEL ); + if ( !object ) goto Fail2; + + layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ), TAG_MODEL ); + if ( !layer ) goto Fail2; + object->layer = layer; + object->nlayers = 1; + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail2; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail2; + break; + + case ID_POLS: + if ( !lwGetPolygons5( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail2; + break; + + case ID_SRFS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail2; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface5( fp, cksize, object ); + if ( !node ) goto Fail2; + lwListAdd( (void**)&object->surf, node ); + object->nsurfs++; + break; + + default: + fp->Seek( cksize, FS_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= fp->Tell() - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail2; + } + + fileSystem->CloseFile( fp ); + fp = NULL; + + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail2; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail2; + lwGetVertNormals( &layer->point, &layer->polygon ); + + return object; + +Fail2: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = fp->Tell(); + fileSystem->CloseFile( fp ); + } + lwFreeObject( object ); + return NULL; +} + +/* +====================================================================== +lwFreePoints() + +Free the memory used by an lwPointList. +====================================================================== */ + +void lwFreePoints( lwPointList *point ) +{ + int i; + + if ( point ) { + if ( point->pt ) { + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].pol ) Mem_Free( point->pt[ i ].pol ); + if ( point->pt[ i ].vm ) Mem_Free( point->pt[ i ].vm ); + } + Mem_Free( point->pt ); + } + memset( point, 0, sizeof( lwPointList )); + } +} + + +/* +====================================================================== +lwFreePolygons() + +Free the memory used by an lwPolygonList. +====================================================================== */ + +void lwFreePolygons( lwPolygonList *plist ) +{ + int i, j; + + if ( plist ) { + if ( plist->pol ) { + for ( i = 0; i < plist->count; i++ ) { + if ( plist->pol[ i ].v ) { + for ( j = 0; j < plist->pol[ i ].nverts; j++ ) + if ( plist->pol[ i ].v[ j ].vm ) + Mem_Free( plist->pol[ i ].v[ j ].vm ); + } + } + if ( plist->pol[ 0 ].v ) + Mem_Free( plist->pol[ 0 ].v ); + Mem_Free( plist->pol ); + } + memset( plist, 0, sizeof( lwPolygonList )); + } +} + + +/* +====================================================================== +lwGetPoints() + +Read point records from a PNTS chunk in an LWO2 file. The points are +added to the array in the lwPointList. +====================================================================== */ + +int lwGetPoints( idFile *fp, int cksize, lwPointList *point ) +{ + float *f; + int np, i, j; + + if ( cksize == 1 ) return 1; + + /* extend the point array to hold the new points */ + + np = cksize / 12; + point->offset = point->count; + point->count += np; + lwPoint *oldpt = point->pt; + point->pt = (lwPoint*)Mem_Alloc( point->count * sizeof( lwPoint ), TAG_MODEL ); + if ( !point->pt ) return 0; + if ( oldpt ) { + memcpy( point->pt, oldpt, point->offset * sizeof( lwPoint ) ); + Mem_Free( oldpt ); + } + memset( &point->pt[ point->offset ], 0, np * sizeof( lwPoint ) ); + + /* read the whole chunk */ + + f = ( float * ) getbytes( fp, cksize ); + if ( !f ) return 0; + BigRevBytes( f, 4, np * 3 ); + + /* assign position values */ + + for ( i = 0, j = 0; i < np; i++, j += 3 ) { + point->pt[ i ].pos[ 0 ] = f[ j ]; + point->pt[ i ].pos[ 1 ] = f[ j + 1 ]; + point->pt[ i ].pos[ 2 ] = f[ j + 2 ]; + } + + Mem_Free( f ); + return 1; +} + + +/* +====================================================================== +lwGetBoundingBox() + +Calculate the bounding box for a point list, but only if the bounding +box hasn't already been initialized. +====================================================================== */ + +void lwGetBoundingBox( lwPointList *point, float bbox[] ) +{ + int i, j; + + if ( point->count == 0 ) return; + + for ( i = 0; i < 6; i++ ) + if ( bbox[ i ] != 0.0f ) return; + + bbox[ 0 ] = bbox[ 1 ] = bbox[ 2 ] = 1e20f; + bbox[ 3 ] = bbox[ 4 ] = bbox[ 5 ] = -1e20f; + for ( i = 0; i < point->count; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( bbox[ j ] > point->pt[ i ].pos[ j ] ) + bbox[ j ] = point->pt[ i ].pos[ j ]; + if ( bbox[ j + 3 ] < point->pt[ i ].pos[ j ] ) + bbox[ j + 3 ] = point->pt[ i ].pos[ j ]; + } + } +} + + +/* +====================================================================== +lwAllocPolygons() + +Allocate or extend the polygon arrays to hold new records. +====================================================================== */ + +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ) +{ + int i; + + plist->offset = plist->count; + plist->count += npols; + lwPolygon *oldpol = plist->pol; + plist->pol = (lwPolygon*)Mem_Alloc( plist->count * sizeof( lwPolygon ), TAG_MODEL ); + if ( !plist->pol ) return 0; + if ( oldpol ) { + memcpy( plist->pol, oldpol, plist->offset * sizeof( lwPolygon ) ); + Mem_Free( oldpol ); + } + memset( plist->pol + plist->offset, 0, npols * sizeof( lwPolygon ) ); + + plist->voffset = plist->vcount; + plist->vcount += nverts; + lwPolVert *oldpolv = plist->pol[0].v; + plist->pol[0].v = (lwPolVert*)Mem_Alloc( plist->vcount * sizeof( lwPolVert ), TAG_MODEL ); + if ( !plist->pol[ 0 ].v ) return 0; + if ( oldpolv ) { + memcpy( plist->pol[0].v, oldpolv, plist->voffset * sizeof( lwPolVert ) ); + Mem_Free( oldpolv ); + } + memset( plist->pol[ 0 ].v + plist->voffset, 0, nverts * sizeof( lwPolVert ) ); + + /* fix up the old vertex pointers */ + + for ( i = 1; i < plist->offset; i++ ) + plist->pol[ i ].v = plist->pol[ i - 1 ].v + plist->pol[ i - 1 ].nverts; + + return 1; +} + + +/* +====================================================================== +lwGetPolygons() + +Read polygon records from a POLS chunk in an LWO2 file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, flags, nv, nverts, npols; + unsigned int type; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + type = getU4( fp ); + buf = (unsigned char*)getbytes( fp, cksize - 4 ); + if ( cksize != get_flen() ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize - 4 ) { + nv = sgetU2( &bp ); + nv &= 0x03FF; + nverts += nv; + npols++; + for ( i = 0; i < nv; i++ ) + j = sgetVX( &bp ); + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + flags = nv & 0xFC00; + nv &= 0x03FF; + + pp->nverts = nv; + pp->flags = flags; + pp->type = type; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pp->v[ j ].index = sgetVX( &bp ) + ptoffset; + + pp++; + pv += nv; + } + + Mem_Free( buf ); + return 1; + +Fail: + if ( buf ) Mem_Free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +lwGetPolyNormals() + +Calculate the polygon normals. By convention, LW's polygon normals +are found as the cross product of the first and last edges. It's +undefined for one- and two-point polygons. +====================================================================== */ + +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j; + float p1[ 3 ], p2[ 3 ], pn[ 3 ], v1[ 3 ], v2[ 3 ]; + + for ( i = 0; i < polygon->count; i++ ) { + if ( polygon->pol[ i ].nverts < 3 ) continue; + for ( j = 0; j < 3; j++ ) { + + // FIXME: track down why indexes are way out of range + p1[ j ] = point->pt[ polygon->pol[ i ].v[ 0 ].index ].pos[ j ]; + p2[ j ] = point->pt[ polygon->pol[ i ].v[ 1 ].index ].pos[ j ]; + pn[ j ] = point->pt[ polygon->pol[ i ].v[ polygon->pol[ i ].nverts - 1 ].index ].pos[ j ]; + } + + for ( j = 0; j < 3; j++ ) { + v1[ j ] = p2[ j ] - p1[ j ]; + v2[ j ] = pn[ j ] - p1[ j ]; + } + + cross( v1, v2, polygon->pol[ i ].norm ); + normalize( polygon->pol[ i ].norm ); + } +} + + +/* +====================================================================== +lwGetPointPolygons() + +For each point, fill in the indexes of the polygons that share the +point. Returns 0 if any of the memory allocations fail, otherwise +returns 1. +====================================================================== */ + +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j, k; + + /* count the number of polygons per point */ + + for ( i = 0; i < polygon->count; i++ ) + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) + ++point->pt[ polygon->pol[ i ].v[ j ].index ].npols; + + /* alloc per-point polygon arrays */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].npols == 0 ) continue; + point->pt[ i ].pol = (int*)Mem_ClearedAlloc( point->pt[ i ].npols * sizeof( int ), TAG_MODEL ); + if ( !point->pt[ i ].pol ) return 0; + point->pt[ i ].npols = 0; + } + + /* fill in polygon array for each point */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + k = polygon->pol[ i ].v[ j ].index; + point->pt[ k ].pol[ point->pt[ k ].npols ] = i; + ++point->pt[ k ].npols; + } + } + + return 1; +} + + +/* +====================================================================== +lwResolvePolySurfaces() + +Convert tag indexes into actual lwSurface pointers. If any polygons +point to tags for which no corresponding surface can be found, a +default surface is created. +====================================================================== */ + +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ) +{ + lwSurface **s, *st; + int i, index; + + if ( tlist->count == 0 ) return 1; + + s = (lwSurface**)Mem_ClearedAlloc( tlist->count * sizeof( lwSurface * ), TAG_MODEL ); + if ( !s ) return 0; + + for ( i = 0; i < tlist->count; i++ ) { + st = *surf; + while ( st ) { + if ( !strcmp( st->name, tlist->tag[ i ] )) { + s[ i ] = st; + break; + } + st = st->next; + } + } + + for ( i = 0; i < polygon->count; i++ ) { + index = ( int ) polygon->pol[ i ].surf; + if ( index < 0 || index > tlist->count ) return 0; + if ( !s[ index ] ) { + s[ index ] = lwDefaultSurface(); + if ( !s[ index ] ) return 0; + s[ index ]->name = (char*)Mem_ClearedAlloc( strlen( tlist->tag[ index ] ) + 1, TAG_MODEL ); + if ( !s[ index ]->name ) return 0; + strcpy( s[ index ]->name, tlist->tag[ index ] ); + lwListAdd( (void**)surf, s[ index ] ); + *nsurfs = *nsurfs + 1; + } + polygon->pol[ i ].surf = s[ index ]; + } + + Mem_Free( s ); + return 1; +} + + +/* +====================================================================== +lwGetVertNormals() + +Calculate the vertex normals. For each polygon vertex, sum the +normals of the polygons that share the point. If the normals of the +current and adjacent polygons form an angle greater than the max +smoothing angle for the current polygon's surface, the normal of the +adjacent polygon is excluded from the sum. It's also excluded if the +polygons aren't in the same smoothing group. + +Assumes that lwGetPointPolygons(), lwGetPolyNormals() and +lwResolvePolySurfaces() have already been called. +====================================================================== */ + +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int j, k, n, g, h, p; + float a; + + for ( j = 0; j < polygon->count; j++ ) { + for ( n = 0; n < polygon->pol[ j ].nverts; n++ ) { + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] = polygon->pol[ j ].norm[ k ]; + + if ( polygon->pol[ j ].surf->smooth <= 0 ) continue; + + p = polygon->pol[ j ].v[ n ].index; + + for ( g = 0; g < point->pt[ p ].npols; g++ ) { + h = point->pt[ p ].pol[ g ]; + if ( h == j ) continue; + + if ( polygon->pol[ j ].smoothgrp != polygon->pol[ h ].smoothgrp ) + continue; + a = vecangle( polygon->pol[ j ].norm, polygon->pol[ h ].norm ); + if ( a > polygon->pol[ j ].surf->smooth ) continue; + + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] += polygon->pol[ h ].norm[ k ]; + } + + normalize( polygon->pol[ j ].v[ n ].norm ); + } + } +} + + +/* +====================================================================== +lwFreeTags() + +Free memory used by an lwTagList. +====================================================================== */ + +void lwFreeTags( lwTagList *tlist ) +{ + int i; + + if ( tlist ) { + if ( tlist->tag ) { + for ( i = 0; i < tlist->count; i++ ) + if ( tlist->tag[ i ] ) { + Mem_Free( tlist->tag[ i ] ); + } + Mem_Free( tlist->tag ); + } + memset( tlist, 0, sizeof( lwTagList )); + } +} + + +/* +====================================================================== +lwGetTags() + +Read tag strings from a TAGS chunk in an LWO2 file. The tags are +added to the lwTagList array. +====================================================================== */ + +int lwGetTags( idFile *fp, int cksize, lwTagList *tlist ) +{ + char *buf, *bp; + int i, len, ntags; + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = (char*)getbytes( fp, cksize ); + if ( !buf ) return 0; + + /* count the strings */ + + ntags = 0; + bp = buf; + while ( bp < buf + cksize ) { + len = strlen( bp ) + 1; + len += len & 1; + bp += len; + ++ntags; + } + + /* expand the string array to hold the new tags */ + + tlist->offset = tlist->count; + tlist->count += ntags; + char **oldtag = tlist->tag; + tlist->tag = (char**)Mem_Alloc( tlist->count * sizeof( char * ), TAG_MODEL ); + if ( !tlist->tag ) goto Fail; + if ( oldtag ) { + memcpy( tlist->tag, oldtag, tlist->offset * sizeof( char * ) ); + Mem_Free( oldtag ); + } + memset( &tlist->tag[ tlist->offset ], 0, ntags * sizeof( char * ) ); + + /* copy the new tags to the tag array */ + + bp = buf; + for ( i = 0; i < ntags; i++ ) + tlist->tag[ i + tlist->offset ] = sgetS0( (unsigned char**)&bp ); + + Mem_Free( buf ); + return 1; + +Fail: + if ( buf ) Mem_Free( buf ); + return 0; +} + + +/* +====================================================================== +lwGetPolygonTags() + +Read polygon tags from a PTAG chunk in an LWO2 file. +====================================================================== */ + +int lwGetPolygonTags( idFile *fp, int cksize, lwTagList *tlist, lwPolygonList *plist ) +{ + unsigned int type; + int rlen = 0, i, j; + + set_flen( 0 ); + type = getU4( fp ); + rlen = get_flen(); + if ( rlen < 0 ) return 0; + + if ( type != ID_SURF && type != ID_PART && type != ID_SMGP ) { + fp->Seek( cksize - 4, FS_SEEK_CUR ); + return 1; + } + + while ( rlen < cksize ) { + i = getVX( fp ) + plist->offset; + j = getVX( fp ) + tlist->offset; + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) return 0; + + switch ( type ) { + case ID_SURF: plist->pol[ i ].surf = ( lwSurface * ) j; break; + case ID_PART: plist->pol[ i ].part = j; break; + case ID_SMGP: plist->pol[ i ].smoothgrp = j; break; + } + } + + return 1; +} + + +/* +====================================================================== +lwFreePlugin() + +Free the memory used by an lwPlugin. +====================================================================== */ + +void lwFreePlugin( lwPlugin *p ) +{ + if ( p ) { + if ( p->ord ) Mem_Free( p->ord ); + if ( p->name ) Mem_Free( p->name ); + if ( p->data ) Mem_Free( p->data ); + Mem_Free( p ); + } +} + + +/* +====================================================================== +lwFreeTexture() + +Free the memory used by an lwTexture. +====================================================================== */ + +void lwFreeTexture( lwTexture *t ) +{ + if ( t ) { + if ( t->ord ) Mem_Free( t->ord ); + switch ( t->type ) { + case ID_IMAP: + if ( t->param.imap.vmap_name ) Mem_Free( t->param.imap.vmap_name ); + break; + case ID_PROC: + if ( t->param.proc.name ) Mem_Free( t->param.proc.name ); + if ( t->param.proc.data ) Mem_Free( t->param.proc.data ); + break; + case ID_GRAD: + if ( t->param.grad.key ) Mem_Free( t->param.grad.key ); + if ( t->param.grad.ikey ) Mem_Free( t->param.grad.ikey ); + break; + } + if ( t->tmap.ref_object ) Mem_Free( t->tmap.ref_object ); + Mem_Free( t ); + } +} + + +/* +====================================================================== +lwFreeSurface() + +Free the memory used by an lwSurface. +====================================================================== */ + +void lwFreeSurface( lwSurface *surf ) +{ + if ( surf ) { + if ( surf->name ) Mem_Free( surf->name ); + if ( surf->srcname ) Mem_Free( surf->srcname ); + + lwListFree( surf->shader, (void (__cdecl *)(void *))lwFreePlugin ); + + lwListFree( surf->color.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->luminosity.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->diffuse.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->specularity.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->glossiness.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->reflection.val.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->transparency.val.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->eta.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->translucency.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->bump.tex, (void (__cdecl *)(void *))lwFreeTexture ); + + Mem_Free( surf ); + } +} + + +/* +====================================================================== +lwGetTHeader() + +Read a texture map header from a SURF.BLOK in an LWO2 file. This is +the first subchunk in a BLOK, and its contents are common to all three +texture types. +====================================================================== */ + +int lwGetTHeader( idFile *fp, int hsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* ordinal string */ + + tex->ord = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_CHAN: + tex->chan = getU4( fp ); + break; + + case ID_OPAC: + tex->opac_type = getU2( fp ); + tex->opacity.val = getF4( fp ); + tex->opacity.eindex = getVX( fp ); + break; + + case ID_ENAB: + tex->enabled = getU2( fp ); + break; + + case ID_NEGA: + tex->negative = getU2( fp ); + break; + + case ID_AXIS: + tex->axis = getU2( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the texture header subchunk? */ + + if ( hsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTMap() + +Read a texture map from a SURF.BLOK in an LWO2 file. The TMAP +defines the mapping from texture to world or object coordinates. +====================================================================== */ + +int lwGetTMap( idFile *fp, int tmapsz, lwTMap *tmap ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_SIZE: + for ( i = 0; i < 3; i++ ) + tmap->size.val[ i ] = getF4( fp ); + tmap->size.eindex = getVX( fp ); + break; + + case ID_CNTR: + for ( i = 0; i < 3; i++ ) + tmap->center.val[ i ] = getF4( fp ); + tmap->center.eindex = getVX( fp ); + break; + + case ID_ROTA: + for ( i = 0; i < 3; i++ ) + tmap->rotate.val[ i ] = getF4( fp ); + tmap->rotate.eindex = getVX( fp ); + break; + + case ID_FALL: + tmap->fall_type = getU2( fp ); + for ( i = 0; i < 3; i++ ) + tmap->falloff.val[ i ] = getF4( fp ); + tmap->falloff.eindex = getVX( fp ); + break; + + case ID_OREF: + tmap->ref_object = getS0( fp ); + break; + + case ID_CSYS: + tmap->coord_sys = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the TMAP subchunk? */ + + if ( tmapsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetImageMap() + +Read an lwImageMap from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetImageMap( idFile *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PROJ: + tex->param.imap.projection = getU2( fp ); + break; + + case ID_VMAP: + tex->param.imap.vmap_name = getS0( fp ); + break; + + case ID_AXIS: + tex->param.imap.axis = getU2( fp ); + break; + + case ID_IMAG: + tex->param.imap.cindex = getVX( fp ); + break; + + case ID_WRAP: + tex->param.imap.wrapw_type = getU2( fp ); + tex->param.imap.wraph_type = getU2( fp ); + break; + + case ID_WRPW: + tex->param.imap.wrapw.val = getF4( fp ); + tex->param.imap.wrapw.eindex = getVX( fp ); + break; + + case ID_WRPH: + tex->param.imap.wraph.val = getF4( fp ); + tex->param.imap.wraph.eindex = getVX( fp ); + break; + + case ID_AAST: + tex->param.imap.aas_flags = getU2( fp ); + tex->param.imap.aa_strength = getF4( fp ); + break; + + case ID_PIXB: + tex->param.imap.pblend = getU2( fp ); + break; + + case ID_STCK: + tex->param.imap.stck.val = getF4( fp ); + tex->param.imap.stck.eindex = getVX( fp ); + break; + + case ID_TAMP: + tex->param.imap.amplitude.val = getF4( fp ); + tex->param.imap.amplitude.eindex = getVX( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the image map? */ + + if ( rsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetProcedural() + +Read an lwProcedural from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetProcedural( idFile *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_AXIS: + tex->param.proc.axis = getU2( fp ); + break; + + case ID_VALU: + tex->param.proc.value[ 0 ] = getF4( fp ); + if ( sz >= 8 ) tex->param.proc.value[ 1 ] = getF4( fp ); + if ( sz >= 12 ) tex->param.proc.value[ 2 ] = getF4( fp ); + break; + + case ID_FUNC: + tex->param.proc.name = getS0( fp ); + rlen = get_flen(); + tex->param.proc.data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the procedural block? */ + + if ( rsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetGradient() + +Read an lwGradient from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetGradient( idFile *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i, j, nkeys; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PNAM: + tex->param.grad.paramname = getS0( fp ); + break; + + case ID_INAM: + tex->param.grad.itemname = getS0( fp ); + break; + + case ID_GRST: + tex->param.grad.start = getF4( fp ); + break; + + case ID_GREN: + tex->param.grad.end = getF4( fp ); + break; + + case ID_GRPT: + tex->param.grad.repeat = getU2( fp ); + break; + + case ID_FKEY: + nkeys = sz / sizeof( lwGradKey ); + tex->param.grad.key = (lwGradKey*)Mem_ClearedAlloc( nkeys * sizeof( lwGradKey ), TAG_MODEL ); + if ( !tex->param.grad.key ) return 0; + for ( i = 0; i < nkeys; i++ ) { + tex->param.grad.key[ i ].value = getF4( fp ); + for ( j = 0; j < 4; j++ ) + tex->param.grad.key[ i ].rgba[ j ] = getF4( fp ); + } + break; + + case ID_IKEY: + nkeys = sz / 2; + tex->param.grad.ikey = (short*)Mem_ClearedAlloc( nkeys * sizeof( short ), TAG_MODEL ); + if ( !tex->param.grad.ikey ) return 0; + for ( i = 0; i < nkeys; i++ ) + tex->param.grad.ikey[ i ] = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the gradient? */ + + if ( rsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTexture() + +Read an lwTexture from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwTexture *lwGetTexture( idFile *fp, int bloksz, unsigned int type ) +{ + lwTexture *tex; + unsigned short sz; + int ok; + + tex = (lwTexture*)Mem_ClearedAlloc( sizeof( lwTexture ), TAG_MODEL ); + if ( !tex ) return NULL; + + tex->type = type; + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + sz = getU2( fp ); + if ( !lwGetTHeader( fp, sz, tex )) { + Mem_Free( tex ); + return NULL; + } + + sz = bloksz - sz - 6; + switch ( type ) { + case ID_IMAP: ok = lwGetImageMap( fp, sz, tex ); break; + case ID_PROC: ok = lwGetProcedural( fp, sz, tex ); break; + case ID_GRAD: ok = lwGetGradient( fp, sz, tex ); break; + default: + ok = !fp->Seek( sz, FS_SEEK_CUR ); + } + + if ( !ok ) { + lwFreeTexture( tex ); + return NULL; + } + + set_flen( bloksz ); + return tex; +} + + +/* +====================================================================== +lwGetShader() + +Read a shader record from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwPlugin *lwGetShader( idFile *fp, int bloksz ) +{ + lwPlugin *shdr; + unsigned int id; + unsigned short sz; + int hsz, rlen, pos; + + shdr = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ), TAG_MODEL ); + if ( !shdr ) return NULL; + + pos = fp->Tell(); + set_flen( 0 ); + hsz = getU2( fp ); + shdr->ord = getS0( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( hsz > 0 ) { + sz += sz & 1; + hsz -= sz; + if ( id == ID_ENAB ) { + shdr->flags = getU2( fp ); + break; + } + else { + fp->Seek( sz, FS_SEEK_CUR ); + id = getU4( fp ); + sz = getU2( fp ); + } + } + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_FUNC: + shdr->name = getS0( fp ); + rlen = get_flen(); + shdr->data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the shader block? */ + + if ( bloksz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + set_flen( fp->Tell() - pos ); + return shdr; + +Fail: + lwFreePlugin( shdr ); + return NULL; +} + + +/* +====================================================================== +compare_textures() +compare_shaders() + +Callbacks for the lwListInsert() function, which is called to add +textures to surface channels and shaders to surfaces. +====================================================================== */ + +static int compare_textures( lwTexture *a, lwTexture *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +static int compare_shaders( lwPlugin *a, lwPlugin *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +/* +====================================================================== +add_texture() + +Finds the surface channel (lwTParam or lwCParam) to which a texture is +applied, then calls lwListInsert(). +====================================================================== */ + +static int add_texture( lwSurface *surf, lwTexture *tex ) +{ + lwTexture **list; + + switch ( tex->chan ) { + case ID_COLR: list = &surf->color.tex; break; + case ID_LUMI: list = &surf->luminosity.tex; break; + case ID_DIFF: list = &surf->diffuse.tex; break; + case ID_SPEC: list = &surf->specularity.tex; break; + case ID_GLOS: list = &surf->glossiness.tex; break; + case ID_REFL: list = &surf->reflection.val.tex; break; + case ID_TRAN: list = &surf->transparency.val.tex; break; + case ID_RIND: list = &surf->eta.tex; break; + case ID_TRNL: list = &surf->translucency.tex; break; + case ID_BUMP: list = &surf->bump.tex; break; + default: return 0; + } + + lwListInsert( (void**)list, tex, (int (__cdecl *)(void *,void *))compare_textures ); + return 1; +} + + +/* +====================================================================== +lwDefaultSurface() + +Allocate and initialize a surface. +====================================================================== */ + +lwSurface *lwDefaultSurface() +{ + lwSurface *surf; + + surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ), TAG_MODEL ); + if ( !surf ) return NULL; + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + return surf; +} + + +/* +====================================================================== +lwGetSurface() + +Read an lwSurface from an LWO2 file. +====================================================================== */ + +lwSurface *lwGetSurface( idFile *fp, int cksize ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + unsigned int id, type; + unsigned short sz; + int pos, rlen; + + + /* allocate the Surface structure */ + + surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ), TAG_MODEL ); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* names */ + + surf->name = getS0( fp ); + surf->srcname = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getF4( fp ); + surf->color.rgb[ 1 ] = getF4( fp ); + surf->color.rgb[ 2 ] = getF4( fp ); + surf->color.eindex = getVX( fp ); + break; + + case ID_LUMI: + surf->luminosity.val = getF4( fp ); + surf->luminosity.eindex = getVX( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getF4( fp ); + surf->diffuse.eindex = getVX( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getF4( fp ); + surf->specularity.eindex = getVX( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = getF4( fp ); + surf->glossiness.eindex = getVX( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getF4( fp ); + surf->reflection.val.eindex = getVX( fp ); + break; + + case ID_RFOP: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + surf->reflection.cindex = getVX( fp ); + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getF4( fp ); + surf->transparency.val.eindex = getVX( fp ); + break; + + case ID_TROP: + surf->transparency.options = getU2( fp ); + break; + + case ID_TIMG: + surf->transparency.cindex = getVX( fp ); + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + surf->eta.eindex = getVX( fp ); + break; + + case ID_TRNL: + surf->translucency.val = getF4( fp ); + surf->translucency.eindex = getVX( fp ); + break; + + case ID_BUMP: + surf->bump.val = getF4( fp ); + surf->bump.eindex = getVX( fp ); + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_SIDE: + surf->sideflags = getU2( fp ); + break; + + case ID_CLRH: + surf->color_hilite.val = getF4( fp ); + surf->color_hilite.eindex = getVX( fp ); + break; + + case ID_CLRF: + surf->color_filter.val = getF4( fp ); + surf->color_filter.eindex = getVX( fp ); + break; + + case ID_ADTR: + surf->add_trans.val = getF4( fp ); + surf->add_trans.eindex = getVX( fp ); + break; + + case ID_SHRP: + surf->dif_sharp.val = getF4( fp ); + surf->dif_sharp.eindex = getVX( fp ); + break; + + case ID_GVAL: + surf->glow.val = getF4( fp ); + surf->glow.eindex = getVX( fp ); + break; + + case ID_LINE: + surf->line.enabled = 1; + if ( sz >= 2 ) surf->line.flags = getU2( fp ); + if ( sz >= 6 ) surf->line.size.val = getF4( fp ); + if ( sz >= 8 ) surf->line.size.eindex = getVX( fp ); + break; + + case ID_ALPH: + surf->alpha_mode = getU2( fp ); + surf->alpha = getF4( fp ); + break; + + case ID_AVAL: + surf->alpha = getF4( fp ); + break; + + case ID_BLOK: + type = getU4( fp ); + + switch ( type ) { + case ID_IMAP: + case ID_PROC: + case ID_GRAD: + tex = lwGetTexture( fp, sz - 4, type ); + if ( !tex ) goto Fail; + if ( !add_texture( surf, tex )) + lwFreeTexture( tex ); + set_flen( 4 + get_flen() ); + break; + case ID_SHDR: + shdr = lwGetShader( fp, sz - 4 ); + if ( !shdr ) goto Fail; + lwListInsert( (void**)&surf->shader, shdr, (int (__cdecl *)(void *,void *))compare_shaders ); + ++surf->nshaders; + set_flen( 4 + get_flen() ); + break; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} + + +float dot( float a[], float b[] ) +{ + return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; +} + + +void cross( float a[], float b[], float c[] ) +{ + c[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; + c[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; + c[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; +} + + +void normalize( float v[] ) +{ + float r; + + r = ( float ) idMath::Sqrt( dot( v, v )); + if ( r > 0 ) { + v[ 0 ] /= r; + v[ 1 ] /= r; + v[ 2 ] /= r; + } +} + +/* +====================================================================== +lwFreeVMap() + +Free memory used by an lwVMap. +====================================================================== */ + +void lwFreeVMap( lwVMap *vmap ) +{ + if ( vmap ) { + if ( vmap->name ) Mem_Free( vmap->name ); + if ( vmap->vindex ) Mem_Free( vmap->vindex ); + if ( vmap->pindex ) Mem_Free( vmap->pindex ); + if ( vmap->val ) { + if ( vmap->val[ 0 ] ) Mem_Free( vmap->val[ 0 ] ); + Mem_Free( vmap->val ); + } + Mem_Free( vmap ); + } +} + + +/* +====================================================================== +lwGetVMap() + +Read an lwVMap from a VMAP or VMAD chunk in an LWO2. +====================================================================== */ + +lwVMap *lwGetVMap( idFile *fp, int cksize, int ptoffset, int poloffset, + int perpoly ) +{ + unsigned char *buf, *bp; + lwVMap *vmap; + float *f; + int i, j, npts, rlen; + + + /* read the whole chunk */ + + set_flen( 0 ); + buf = (unsigned char*)getbytes( fp, cksize ); + if ( !buf ) return NULL; + + vmap = (lwVMap*)Mem_ClearedAlloc( sizeof( lwVMap ), TAG_MODEL ); + if ( !vmap ) { + Mem_Free( buf ); + return NULL; + } + + /* initialize the vmap */ + + vmap->perpoly = perpoly; + + bp = buf; + set_flen( 0 ); + vmap->type = sgetU4( &bp ); + vmap->dim = sgetU2( &bp ); + vmap->name = sgetS0( &bp ); + rlen = get_flen(); + + /* count the vmap records */ + + npts = 0; + while ( bp < buf + cksize ) { + i = sgetVX( &bp ); + if ( perpoly ) + i = sgetVX( &bp ); + bp += vmap->dim * sizeof( float ); + ++npts; + } + + /* allocate the vmap */ + + vmap->nverts = npts; + vmap->vindex = (int*)Mem_ClearedAlloc( npts * sizeof( int ), TAG_MODEL ); + if ( !vmap->vindex ) goto Fail; + if ( perpoly ) { + vmap->pindex = (int*)Mem_ClearedAlloc( npts * sizeof( int ), TAG_MODEL ); + if ( !vmap->pindex ) goto Fail; + } + + if ( vmap->dim > 0 ) { + vmap->val = (float**)Mem_ClearedAlloc( npts * sizeof( float * ), TAG_MODEL ); + if ( !vmap->val ) goto Fail; + f = (float*)Mem_ClearedAlloc( npts * vmap->dim * sizeof( float ), TAG_MODEL ); + if ( !f ) goto Fail; + for ( i = 0; i < npts; i++ ) + vmap->val[ i ] = f + i * vmap->dim; + } + + /* fill in the vmap values */ + + bp = buf + rlen; + for ( i = 0; i < npts; i++ ) { + vmap->vindex[ i ] = sgetVX( &bp ); + if ( perpoly ) + vmap->pindex[ i ] = sgetVX( &bp ); + for ( j = 0; j < vmap->dim; j++ ) + vmap->val[ i ][ j ] = sgetF4( &bp ); + } + + Mem_Free( buf ); + return vmap; + +Fail: + if ( buf ) Mem_Free( buf ); + lwFreeVMap( vmap ); + return NULL; +} + + +/* +====================================================================== +lwGetPointVMaps() + +Fill in the lwVMapPt structure for each point. +====================================================================== */ + +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ) +{ + lwVMap *vm; + int i, j, n; + + /* count the number of vmap values for each point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) + for ( i = 0; i < vm->nverts; i++ ) + ++point->pt[ vm->vindex[ i ]].nvmaps; + vm = vm->next; + } + + /* allocate vmap references for each mapped point */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].nvmaps ) { + point->pt[ i ].vm = (lwVMapPt*)Mem_ClearedAlloc( point->pt[ i ].nvmaps * sizeof( lwVMapPt ), TAG_MODEL ); + if ( !point->pt[ i ].vm ) return 0; + point->pt[ i ].nvmaps = 0; + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + j = vm->vindex[ i ]; + n = point->pt[ j ].nvmaps; + point->pt[ j ].vm[ n ].vmap = vm; + point->pt[ j ].vm[ n ].index = i; + ++point->pt[ j ].nvmaps; + } + } + vm = vm->next; + } + + return 1; +} + + +/* +====================================================================== +lwGetPolyVMaps() + +Fill in the lwVMapPt structure for each polygon vertex. +====================================================================== */ + +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ) +{ + lwVMap *vm; + lwPolVert *pv; + int i, j; + + /* count the number of vmap values for each polygon vertex */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + /* allocate vmap references for each mapped vertex */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + pv = &polygon->pol[ i ].v[ j ]; + if ( pv->nvmaps ) { + pv->vm = (lwVMapPt*)Mem_ClearedAlloc( pv->nvmaps * sizeof( lwVMapPt ), TAG_MODEL ); + if ( !pv->vm ) return 0; + pv->nvmaps = 0; + } + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + pv->vm[ pv->nvmaps ].vmap = vm; + pv->vm[ pv->nvmaps ].index = i; + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + return 1; +} diff --git a/neo/renderer/Model_lwo.h b/neo/renderer/Model_lwo.h new file mode 100644 index 00000000..43712f91 --- /dev/null +++ b/neo/renderer/Model_lwo.h @@ -0,0 +1,676 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LWO2_H__ +#define __LWO2_H__ + +/* +====================================================================== + + LWO2 loader. (LightWave Object) + + Ernie Wright 17 Sep 00 + +====================================================================== +*/ + +/* chunk and subchunk IDs */ + +#define LWID_(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +#define ID_FORM LWID_('F','O','R','M') +#define ID_LWO2 LWID_('L','W','O','2') +#define ID_LWOB LWID_('L','W','O','B') + +/* top-level chunks */ +#define ID_LAYR LWID_('L','A','Y','R') +#define ID_TAGS LWID_('T','A','G','S') +#define ID_PNTS LWID_('P','N','T','S') +#define ID_BBOX LWID_('B','B','O','X') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_VMAD LWID_('V','M','A','D') +#define ID_POLS LWID_('P','O','L','S') +#define ID_PTAG LWID_('P','T','A','G') +#define ID_ENVL LWID_('E','N','V','L') +#define ID_CLIP LWID_('C','L','I','P') +#define ID_SURF LWID_('S','U','R','F') +#define ID_DESC LWID_('D','E','S','C') +#define ID_TEXT LWID_('T','E','X','T') +#define ID_ICON LWID_('I','C','O','N') + +/* polygon types */ +#define ID_FACE LWID_('F','A','C','E') +#define ID_CURV LWID_('C','U','R','V') +#define ID_PTCH LWID_('P','T','C','H') +#define ID_MBAL LWID_('M','B','A','L') +#define ID_BONE LWID_('B','O','N','E') + +/* polygon tags */ +#define ID_SURF LWID_('S','U','R','F') +#define ID_PART LWID_('P','A','R','T') +#define ID_SMGP LWID_('S','M','G','P') + +/* envelopes */ +#define ID_PRE LWID_('P','R','E',' ') +#define ID_POST LWID_('P','O','S','T') +#define ID_KEY LWID_('K','E','Y',' ') +#define ID_SPAN LWID_('S','P','A','N') +#define ID_TCB LWID_('T','C','B',' ') +#define ID_HERM LWID_('H','E','R','M') +#define ID_BEZI LWID_('B','E','Z','I') +#define ID_BEZ2 LWID_('B','E','Z','2') +#define ID_LINE LWID_('L','I','N','E') +#define ID_STEP LWID_('S','T','E','P') + +/* clips */ +#define ID_STIL LWID_('S','T','I','L') +#define ID_ISEQ LWID_('I','S','E','Q') +#define ID_ANIM LWID_('A','N','I','M') +#define ID_XREF LWID_('X','R','E','F') +#define ID_STCC LWID_('S','T','C','C') +#define ID_TIME LWID_('T','I','M','E') +#define ID_CONT LWID_('C','O','N','T') +#define ID_BRIT LWID_('B','R','I','T') +#define ID_SATR LWID_('S','A','T','R') +#define ID_HUE LWID_('H','U','E',' ') +#define ID_GAMM LWID_('G','A','M','M') +#define ID_NEGA LWID_('N','E','G','A') +#define ID_IFLT LWID_('I','F','L','T') +#define ID_PFLT LWID_('P','F','L','T') + +/* surfaces */ +#define ID_COLR LWID_('C','O','L','R') +#define ID_LUMI LWID_('L','U','M','I') +#define ID_DIFF LWID_('D','I','F','F') +#define ID_SPEC LWID_('S','P','E','C') +#define ID_GLOS LWID_('G','L','O','S') +#define ID_REFL LWID_('R','E','F','L') +#define ID_RFOP LWID_('R','F','O','P') +#define ID_RIMG LWID_('R','I','M','G') +#define ID_RSAN LWID_('R','S','A','N') +#define ID_TRAN LWID_('T','R','A','N') +#define ID_TROP LWID_('T','R','O','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_RIND LWID_('R','I','N','D') +#define ID_TRNL LWID_('T','R','N','L') +#define ID_BUMP LWID_('B','U','M','P') +#define ID_SMAN LWID_('S','M','A','N') +#define ID_SIDE LWID_('S','I','D','E') +#define ID_CLRH LWID_('C','L','R','H') +#define ID_CLRF LWID_('C','L','R','F') +#define ID_ADTR LWID_('A','D','T','R') +#define ID_SHRP LWID_('S','H','R','P') +#define ID_LINE LWID_('L','I','N','E') +#define ID_LSIZ LWID_('L','S','I','Z') +#define ID_ALPH LWID_('A','L','P','H') +#define ID_AVAL LWID_('A','V','A','L') +#define ID_GVAL LWID_('G','V','A','L') +#define ID_BLOK LWID_('B','L','O','K') + +/* texture layer */ +#define ID_TYPE LWID_('T','Y','P','E') +#define ID_CHAN LWID_('C','H','A','N') +#define ID_NAME LWID_('N','A','M','E') +#define ID_ENAB LWID_('E','N','A','B') +#define ID_OPAC LWID_('O','P','A','C') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_PROJ LWID_('P','R','O','J') +#define ID_STCK LWID_('S','T','C','K') +#define ID_TAMP LWID_('T','A','M','P') + +/* texture coordinates */ +#define ID_TMAP LWID_('T','M','A','P') +#define ID_AXIS LWID_('A','X','I','S') +#define ID_CNTR LWID_('C','N','T','R') +#define ID_SIZE LWID_('S','I','Z','E') +#define ID_ROTA LWID_('R','O','T','A') +#define ID_OREF LWID_('O','R','E','F') +#define ID_FALL LWID_('F','A','L','L') +#define ID_CSYS LWID_('C','S','Y','S') + +/* image map */ +#define ID_IMAP LWID_('I','M','A','P') +#define ID_IMAG LWID_('I','M','A','G') +#define ID_WRAP LWID_('W','R','A','P') +#define ID_WRPW LWID_('W','R','P','W') +#define ID_WRPH LWID_('W','R','P','H') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_AAST LWID_('A','A','S','T') +#define ID_PIXB LWID_('P','I','X','B') + +/* procedural */ +#define ID_PROC LWID_('P','R','O','C') +#define ID_COLR LWID_('C','O','L','R') +#define ID_VALU LWID_('V','A','L','U') +#define ID_FUNC LWID_('F','U','N','C') +#define ID_FTPS LWID_('F','T','P','S') +#define ID_ITPS LWID_('I','T','P','S') +#define ID_ETPS LWID_('E','T','P','S') + +/* gradient */ +#define ID_GRAD LWID_('G','R','A','D') +#define ID_GRST LWID_('G','R','S','T') +#define ID_GREN LWID_('G','R','E','N') +#define ID_PNAM LWID_('P','N','A','M') +#define ID_INAM LWID_('I','N','A','M') +#define ID_GRPT LWID_('G','R','P','T') +#define ID_FKEY LWID_('F','K','E','Y') +#define ID_IKEY LWID_('I','K','E','Y') + +/* shader */ +#define ID_SHDR LWID_('S','H','D','R') +#define ID_DATA LWID_('D','A','T','A') + + +/* generic linked list */ + +typedef struct st_lwNode { + struct st_lwNode *next, *prev; + void *data; +} lwNode; + + +/* plug-in reference */ + +typedef struct st_lwPlugin { + struct st_lwPlugin *next, *prev; + char *ord; + char *name; + int flags; + void *data; +} lwPlugin; + + +/* envelopes */ + +typedef struct st_lwKey { + struct st_lwKey *next, *prev; + float value; + float time; + unsigned int shape; /* ID_TCB, ID_BEZ2, etc. */ + float tension; + float continuity; + float bias; + float param[ 4 ]; +} lwKey; + +typedef struct st_lwEnvelope { + struct st_lwEnvelope *next, *prev; + int index; + int type; + char *name; + lwKey *key; /* linked list of keys */ + int nkeys; + int behavior[ 2 ]; /* pre and post (extrapolation) */ + lwPlugin *cfilter; /* linked list of channel filters */ + int ncfilters; +} lwEnvelope; + +#define BEH_RESET 0 +#define BEH_CONSTANT 1 +#define BEH_REPEAT 2 +#define BEH_OSCILLATE 3 +#define BEH_OFFSET 4 +#define BEH_LINEAR 5 + + +/* values that can be enveloped */ + +typedef struct st_lwEParam { + float val; + int eindex; +} lwEParam; + +typedef struct st_lwVParam { + float val[ 3 ]; + int eindex; +} lwVParam; + + +/* clips */ + +typedef struct st_lwClipStill { + char *name; +} lwClipStill; + +typedef struct st_lwClipSeq { + char *prefix; /* filename before sequence digits */ + char *suffix; /* after digits, e.g. extensions */ + int digits; + int flags; + int offset; + int start; + int end; +} lwClipSeq; + +typedef struct st_lwClipAnim { + char *name; + char *server; /* anim loader plug-in */ + void *data; +} lwClipAnim; + +typedef struct st_lwClipXRef { + char *string; + int index; + struct st_lwClip *clip; +} lwClipXRef; + +typedef struct st_lwClipCycle { + char *name; + int lo; + int hi; +} lwClipCycle; + +typedef struct st_lwClip { + struct st_lwClip *next, *prev; + int index; + unsigned int type; /* ID_STIL, ID_ISEQ, etc. */ + union { + lwClipStill still; + lwClipSeq seq; + lwClipAnim anim; + lwClipXRef xref; + lwClipCycle cycle; + } source; + float start_time; + float duration; + float frame_rate; + lwEParam contrast; + lwEParam brightness; + lwEParam saturation; + lwEParam hue; + lwEParam gamma; + int negative; + lwPlugin *ifilter; /* linked list of image filters */ + int nifilters; + lwPlugin *pfilter; /* linked list of pixel filters */ + int npfilters; +} lwClip; + + +/* textures */ + +typedef struct st_lwTMap { + lwVParam size; + lwVParam center; + lwVParam rotate; + lwVParam falloff; + int fall_type; + char *ref_object; + int coord_sys; +} lwTMap; + +typedef struct st_lwImageMap { + int cindex; + int projection; + char *vmap_name; + int axis; + int wrapw_type; + int wraph_type; + lwEParam wrapw; + lwEParam wraph; + float aa_strength; + int aas_flags; + int pblend; + lwEParam stck; + lwEParam amplitude; +} lwImageMap; + +#define PROJ_PLANAR 0 +#define PROJ_CYLINDRICAL 1 +#define PROJ_SPHERICAL 2 +#define PROJ_CUBIC 3 +#define PROJ_FRONT 4 + +#define WRAP_NONE 0 +#define WRAP_EDGE 1 +#define WRAP_REPEAT 2 +#define WRAP_MIRROR 3 + +typedef struct st_lwProcedural { + int axis; + float value[ 3 ]; + char *name; + void *data; +} lwProcedural; + +typedef struct st_lwGradKey { + struct st_lwGradKey *next, *prev; + float value; + float rgba[ 4 ]; +} lwGradKey; + +typedef struct st_lwGradient { + char *paramname; + char *itemname; + float start; + float end; + int repeat; + lwGradKey *key; /* array of gradient keys */ + short *ikey; /* array of interpolation codes */ +} lwGradient; + +typedef struct st_lwTexture { + struct st_lwTexture *next, *prev; + char *ord; + unsigned int type; + unsigned int chan; + lwEParam opacity; + short opac_type; + short enabled; + short negative; + short axis; + union { + lwImageMap imap; + lwProcedural proc; + lwGradient grad; + } param; + lwTMap tmap; +} lwTexture; + + +/* values that can be textured */ + +typedef struct st_lwTParam { + float val; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwTParam; + +typedef struct st_lwCParam { + float rgb[ 3 ]; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwCParam; + + +/* surfaces */ + +typedef struct st_lwGlow { + short enabled; + short type; + lwEParam intensity; + lwEParam size; +} Glow; + +typedef struct st_lwRMap { + lwTParam val; + int options; + int cindex; + float seam_angle; +} lwRMap; + +typedef struct st_lwLine { + short enabled; + unsigned short flags; + lwEParam size; +} lwLine; + +typedef struct st_lwSurface { + struct st_lwSurface *next, *prev; + char *name; + char *srcname; + lwCParam color; + lwTParam luminosity; + lwTParam diffuse; + lwTParam specularity; + lwTParam glossiness; + lwRMap reflection; + lwRMap transparency; + lwTParam eta; + lwTParam translucency; + lwTParam bump; + float smooth; + int sideflags; + float alpha; + int alpha_mode; + lwEParam color_hilite; + lwEParam color_filter; + lwEParam add_trans; + lwEParam dif_sharp; + lwEParam glow; + lwLine line; + lwPlugin *shader; /* linked list of shaders */ + int nshaders; +} lwSurface; + + +/* vertex maps */ + +typedef struct st_lwVMap { + struct st_lwVMap *next, *prev; + char *name; + unsigned int type; + int dim; + int nverts; + int perpoly; + int *vindex; /* array of point indexes */ + int *pindex; /* array of polygon indexes */ + float **val; + + // added by duffy + int offset; +} lwVMap; + +typedef struct st_lwVMapPt { + lwVMap *vmap; + int index; /* vindex or pindex element */ +} lwVMapPt; + + +/* points and polygons */ + +typedef struct st_lwPoint { + float pos[ 3 ]; + int npols; /* number of polygons sharing the point */ + int *pol; /* array of polygon indexes */ + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPoint; + +typedef struct st_lwPolVert { + int index; /* index into the point array */ + float norm[ 3 ]; + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPolVert; + +typedef struct st_lwPolygon { + lwSurface *surf; + int part; /* part index */ + int smoothgrp; /* smoothing group */ + int flags; + unsigned int type; + float norm[ 3 ]; + int nverts; + lwPolVert *v; /* array of vertex records */ +} lwPolygon; + +typedef struct st_lwPointList { + int count; + int offset; /* only used during reading */ + lwPoint *pt; /* array of points */ +} lwPointList; + +typedef struct st_lwPolygonList { + int count; + int offset; /* only used during reading */ + int vcount; /* total number of vertices */ + int voffset; /* only used during reading */ + lwPolygon *pol; /* array of polygons */ +} lwPolygonList; + + +/* geometry layers */ + +typedef struct st_lwLayer { + struct st_lwLayer *next, *prev; + char *name; + int index; + int parent; + int flags; + float pivot[ 3 ]; + float bbox[ 6 ]; + lwPointList point; + lwPolygonList polygon; + int nvmaps; + lwVMap *vmap; /* linked list of vmaps */ +} lwLayer; + + +/* tag strings */ + +typedef struct st_lwTagList { + int count; + int offset; /* only used during reading */ + char **tag; /* array of strings */ +} lwTagList; + + +/* an object */ + +typedef struct st_lwObject { + ID_TIME_T timeStamp; + lwLayer * layer; /* linked list of layers */ + lwEnvelope * env; /* linked list of envelopes */ + lwClip * clip; /* linked list of clips */ + lwSurface * surf; /* linked list of surfaces */ + lwTagList taglist; + int nlayers; + int nenvs; + int nclips; + int nsurfs; +} lwObject; + + +/* lwo2.c */ + +lwObject *lwGetObject( const char *filename, unsigned int *failID, int *failpos ); +void lwFreeObject( lwObject *object ); +void lwFreeLayer( lwLayer *layer ); + +/* pntspols.c */ + +void lwFreePoints( lwPointList *point ); +void lwFreePolygons( lwPolygonList *plist ); +int lwGetPoints( idFile *fp, int cksize, lwPointList *point ); +void lwGetBoundingBox( lwPointList *point, float bbox[] ); +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ); +int lwGetPolygons( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ); +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ); +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ); +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ); +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ); +void lwFreeTags( lwTagList *tlist ); +int lwGetTags( idFile *fp, int cksize, lwTagList *tlist ); +int lwGetPolygonTags( idFile *fp, int cksize, lwTagList *tlist, + lwPolygonList *plist ); + +/* vmap.c */ + +void lwFreeVMap( lwVMap *vmap ); +lwVMap *lwGetVMap( idFile *fp, int cksize, int ptoffset, int poloffset, + int perpoly ); +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ); +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ); + +/* clip.c */ + +void lwFreeClip( lwClip *clip ); +lwClip *lwGetClip( idFile *fp, int cksize ); +lwClip *lwFindClip( lwClip *list, int index ); + +/* envelope.c */ + +void lwFreeEnvelope( lwEnvelope *env ); +lwEnvelope *lwGetEnvelope( idFile *fp, int cksize ); +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ); +float lwEvalEnvelope( lwEnvelope *env, float time ); + +/* surface.c */ + +void lwFreePlugin( lwPlugin *p ); +void lwFreeTexture( lwTexture *t ); +void lwFreeSurface( lwSurface *surf ); +int lwGetTHeader( idFile *fp, int hsz, lwTexture *tex ); +int lwGetTMap( idFile *fp, int tmapsz, lwTMap *tmap ); +int lwGetImageMap( idFile *fp, int rsz, lwTexture *tex ); +int lwGetProcedural( idFile *fp, int rsz, lwTexture *tex ); +int lwGetGradient( idFile *fp, int rsz, lwTexture *tex ); +lwTexture *lwGetTexture( idFile *fp, int bloksz, unsigned int type ); +lwPlugin *lwGetShader( idFile *fp, int bloksz ); +lwSurface *lwGetSurface( idFile *fp, int cksize ); +lwSurface *lwDefaultSurface(); + +/* lwob.c */ + +lwSurface *lwGetSurface5( idFile *fp, int cksize, lwObject *obj ); +int lwGetPolygons5( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ); +lwObject *lwGetObject5( const char *filename, unsigned int *failID, int *failpos ); + +/* list.c */ + +void lwListFree( void *list, void ( *freeNode )( void * )); +void lwListAdd( void **list, void *node ); +void lwListInsert( void **vlist, void *vitem, + int ( *compare )( void *, void * )); + +/* vecmath.c */ + +float dot( float a[], float b[] ); +void cross( float a[], float b[], float c[] ); +void normalize( float v[] ); +#define vecangle( a, b ) ( float ) idMath::ACos( dot( a, b ) ) + +/* lwio.c */ + +void set_flen( int i ); +int get_flen(); +void *getbytes( idFile *fp, int size ); +void skipbytes( idFile *fp, int n ); +int getI1( idFile *fp ); +short getI2( idFile *fp ); +int getI4( idFile *fp ); +unsigned char getU1( idFile *fp ); +unsigned short getU2( idFile *fp ); +unsigned int getU4( idFile *fp ); +int getVX( idFile *fp ); +float getF4( idFile *fp ); +char *getS0( idFile *fp ); +int sgetI1( unsigned char **bp ); +short sgetI2( unsigned char **bp ); +int sgetI4( unsigned char **bp ); +unsigned char sgetU1( unsigned char **bp ); +unsigned short sgetU2( unsigned char **bp ); +unsigned int sgetU4( unsigned char **bp ); +int sgetVX( unsigned char **bp ); +float sgetF4( unsigned char **bp ); +char *sgetS0( unsigned char **bp ); + +#endif /* !__LWO2_H__ */ diff --git a/neo/renderer/Model_ma.cpp b/neo/renderer/Model_ma.cpp new file mode 100644 index 00000000..df9fea14 --- /dev/null +++ b/neo/renderer/Model_ma.cpp @@ -0,0 +1,1100 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "Model_ma.h" + +/* +====================================================================== + + Parses Maya ASCII files. + +====================================================================== +*/ + + +#define MA_VERBOSE( x ) { if ( maGlobal.verbose ) { common->Printf x ; } } + +// working variables used during parsing +typedef struct { + bool verbose; + maModel_t *model; + maObject_t *currentObject; +} ma_t; + +static ma_t maGlobal; + + +void MA_ParseNodeHeader(idParser& parser, maNodeHeader_t* header) { + + memset(header, 0, sizeof(maNodeHeader_t)); + + idToken token; + while(parser.ReadToken(&token)) { + if(!token.Icmp("-")) { + parser.ReadToken(&token); + if (!token.Icmp("n")) { + parser.ReadToken(&token); + strcpy(header->name, token.c_str()); + } else if (!token.Icmp("p")) { + parser.ReadToken(&token); + strcpy(header->parent, token.c_str()); + } + } else if (!token.Icmp(";")) { + break; + } + } +} + +bool MA_ParseHeaderIndex(maAttribHeader_t* header, int& minIndex, int& maxIndex, const char* headerType, const char* skipString) { + + idParser miniParse; + idToken token; + + miniParse.LoadMemory(header->name, strlen(header->name), headerType); + if(skipString) { + miniParse.SkipUntilString(skipString); + } + + if(!miniParse.SkipUntilString("[")) { + //This was just a header + return false; + } + minIndex = miniParse.ParseInt(); + miniParse.ReadToken(&token); + if(!token.Icmp("]")) { + maxIndex = minIndex; + } else { + maxIndex = miniParse.ParseInt(); + } + return true; +} + +bool MA_ParseAttribHeader(idParser &parser, maAttribHeader_t* header) { + + idToken token; + + memset(header, 0, sizeof(maAttribHeader_t)); + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + parser.ReadToken(&token); + if (!token.Icmp("s")) { + header->size = parser.ParseInt(); + parser.ReadToken(&token); + } + } + strcpy(header->name, token.c_str()); + return true; +} + +bool MA_ReadVec3(idParser& parser, idVec3& vec) { + idToken token; + if(!parser.SkipUntilString("double3")) { + throw idException( va("Maya Loader '%s': Invalid Vec3", parser.GetFileName()) ); + } + + + //We need to flip y and z because of the maya coordinate system + vec.x = parser.ParseFloat(); + vec.z = parser.ParseFloat(); + vec.y = parser.ParseFloat(); + + return true; +} + +bool IsNodeComplete(idToken& token) { + if(!token.Icmp("createNode") || !token.Icmp("connectAttr") || !token.Icmp("select")) { + return true; + } + return false; +} + +bool MA_ParseTransform(idParser& parser) { + + maNodeHeader_t header; + maTransform_t* transform; + memset(&header, 0, sizeof(header)); + + //Allocate room for the transform + transform = (maTransform_t *)Mem_Alloc( sizeof( maTransform_t ), TAG_MODEL ); + memset(transform, 0, sizeof(maTransform_t)); + transform->scale.x = transform->scale.y = transform->scale.z = 1; + + //Get the header info from the transform + MA_ParseNodeHeader(parser, &header); + + //Read the transform attributes + idToken token; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + if(!token.Icmp("setAttr")) { + parser.ReadToken(&token); + if(!token.Icmp(".t")) { + if(!MA_ReadVec3(parser, transform->translate)) { + return false; + } + transform->translate.y *= -1; + } else if (!token.Icmp(".r")) { + if(!MA_ReadVec3(parser, transform->rotate)) { + return false; + } + } else if (!token.Icmp(".s")) { + if(!MA_ReadVec3(parser, transform->scale)) { + return false; + } + } else { + parser.SkipRestOfLine(); + } + } + } + + if(header.parent[0] != 0) { + //Find the parent + maTransform_t** parent; + maGlobal.model->transforms.Get(header.parent, &parent); + if(parent) { + transform->parent = *parent; + } + } + + //Add this transform to the list + maGlobal.model->transforms.Set(header.name, transform); + return true; +} + +bool MA_ParseVertex(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->vertexes) { + pMesh->numVertexes = header->size; + pMesh->vertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numVertexes, TAG_MODEL ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "VertexHeader", NULL)) { + //This was just a header + return true; + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->vertexes[i].x = parser.ParseFloat(); + pMesh->vertexes[i].z = parser.ParseFloat(); + pMesh->vertexes[i].y = -parser.ParseFloat(); + } + + return true; +} + +bool MA_ParseVertexTransforms(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->vertTransforms) { + if(header->size == 0) { + header->size = 1; + } + + pMesh->numVertTransforms = header->size; + pMesh->vertTransforms = (idVec4 *)Mem_Alloc( sizeof( idVec4 ) * pMesh->numVertTransforms, TAG_MODEL ); + pMesh->nextVertTransformIndex = 0; + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "VertexTransformHeader", NULL)) { + //This was just a header + return true; + } + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + idToken tk2; + parser.ReadToken(&tk2); + if(!tk2.Icmp("type")) { + parser.SkipUntilString("float3"); + } else { + parser.UnreadToken(&tk2); + parser.UnreadToken(&token); + } + } else { + parser.UnreadToken(&token); + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->vertTransforms[pMesh->nextVertTransformIndex].x = parser.ParseFloat(); + pMesh->vertTransforms[pMesh->nextVertTransformIndex].z = parser.ParseFloat(); + pMesh->vertTransforms[pMesh->nextVertTransformIndex].y = -parser.ParseFloat(); + + //w hold the vert index + pMesh->vertTransforms[pMesh->nextVertTransformIndex].w = i; + + pMesh->nextVertTransformIndex++; + } + + return true; +} + +bool MA_ParseEdge(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->edges) { + pMesh->numEdges = header->size; + pMesh->edges = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numEdges, TAG_MODEL ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "EdgeHeader", NULL)) { + //This was just a header + return true; + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->edges[i].x = parser.ParseFloat(); + pMesh->edges[i].y = parser.ParseFloat(); + pMesh->edges[i].z = parser.ParseFloat(); + } + + return true; +} + +bool MA_ParseNormal(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->normals) { + pMesh->numNormals = header->size; + pMesh->normals = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numNormals, TAG_MODEL ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "NormalHeader", NULL)) { + //This was just a header + return true; + } + + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + idToken tk2; + parser.ReadToken(&tk2); + if(!tk2.Icmp("type")) { + parser.SkipUntilString("float3"); + } else { + parser.UnreadToken(&tk2); + parser.UnreadToken(&token); + } + } else { + parser.UnreadToken(&token); + } + + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->normals[i].x = parser.ParseFloat(); + + //Adjust the normals for the change in coordinate systems + pMesh->normals[i].z = parser.ParseFloat(); + pMesh->normals[i].y = -parser.ParseFloat(); + + pMesh->normals[i].Normalize(); + + } + + pMesh->normalsParsed = true; + pMesh->nextNormal = 0; + + return true; +} + + + +bool MA_ParseFace(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->faces) { + pMesh->numFaces = header->size; + pMesh->faces = (maFace_t *)Mem_Alloc( sizeof( maFace_t ) * pMesh->numFaces, TAG_MODEL ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "FaceHeader", NULL)) { + //This was just a header + return true; + } + + //Read the face data + int currentFace = minIndex-1; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + + if(!token.Icmp("f")) { + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Face is not a triangle.", parser.GetFileName())); + } + //Increment the face number because a new face always starts with an "f" token + currentFace++; + + //We cannot reorder edges until later because the normal processing + //assumes the edges are in the original order + pMesh->faces[currentFace].edge[0] = parser.ParseInt(); + pMesh->faces[currentFace].edge[1] = parser.ParseInt(); + pMesh->faces[currentFace].edge[2] = parser.ParseInt(); + + //Some more init stuff + pMesh->faces[currentFace].vertexColors[0] = pMesh->faces[currentFace].vertexColors[1] = pMesh->faces[currentFace].vertexColors[2] = -1; + + } else if(!token.Icmp("mu")) { + int uvstIndex = parser.ParseInt(); uvstIndex; + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Invalid texture coordinates.", parser.GetFileName())); + } + pMesh->faces[currentFace].tVertexNum[0] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[1] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[2] = parser.ParseInt(); + + } else if(!token.Icmp("mf")) { + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Invalid texture coordinates.", parser.GetFileName())); + } + pMesh->faces[currentFace].tVertexNum[0] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[1] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[2] = parser.ParseInt(); + + } else if(!token.Icmp("fc")) { + + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Invalid vertex color.", parser.GetFileName())); + } + pMesh->faces[currentFace].vertexColors[0] = parser.ParseInt(); + pMesh->faces[currentFace].vertexColors[1] = parser.ParseInt(); + pMesh->faces[currentFace].vertexColors[2] = parser.ParseInt(); + + } + } + + return true; +} + +bool MA_ParseColor(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->colors) { + pMesh->numColors = header->size; + pMesh->colors = (byte *)Mem_Alloc( sizeof( byte ) * pMesh->numColors * 4, TAG_MODEL ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "ColorHeader", NULL)) { + //This was just a header + return true; + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->colors[i*4] = parser.ParseFloat() * 255; + pMesh->colors[i*4+1] = parser.ParseFloat() * 255; + pMesh->colors[i*4+2] = parser.ParseFloat() * 255; + pMesh->colors[i*4+3] = parser.ParseFloat() * 255; + } + + return true; +} + +bool MA_ParseTVert(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //This is not the texture coordinates. It is just the name so ignore it + if(strstr(header->name, "uvsn")) { + return true; + } + + //Allocate enough space for all the data + if(!pMesh->tvertexes) { + pMesh->numTVertexes = header->size; + pMesh->tvertexes = (idVec2 *)Mem_Alloc( sizeof( idVec2 ) * pMesh->numTVertexes, TAG_MODEL ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "TextureCoordHeader", "uvsp")) { + //This was just a header + return true; + } + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + idToken tk2; + parser.ReadToken(&tk2); + if(!tk2.Icmp("type")) { + parser.SkipUntilString("float2"); + } else { + parser.UnreadToken(&tk2); + parser.UnreadToken(&token); + } + } else { + parser.UnreadToken(&token); + } + + //Read each tvert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->tvertexes[i].x = parser.ParseFloat(); + pMesh->tvertexes[i].y = 1.0f - parser.ParseFloat(); + } + + return true; +} + + + +/* +* Quick check to see if the vert participates in a shared normal +*/ +bool MA_QuickIsVertShared(int faceIndex, int vertIndex) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + int vertNum = pMesh->faces[faceIndex].vertexNum[vertIndex]; + + for( int i = 0; i < 3; i++) { + int edge = pMesh->faces[faceIndex].edge[i]; + if(edge < 0) { + edge = idMath::Fabs(edge)-1; + } + if(pMesh->edges[edge].z == 1 && (pMesh->edges[edge].x == vertNum || pMesh->edges[edge].y == vertNum)) { + return true; + } + } + return false; +} + +void MA_GetSharedFace(int faceIndex, int vertIndex, int& sharedFace, int& sharedVert) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + int vertNum = pMesh->faces[faceIndex].vertexNum[vertIndex]; + + sharedFace = -1; + sharedVert = -1; + + //Find a shared edge on this face that contains the specified vert + for(int edgeIndex = 0; edgeIndex < 3; edgeIndex++) { + + int edge = pMesh->faces[faceIndex].edge[edgeIndex]; + if(edge < 0) { + edge = idMath::Fabs(edge)-1; + } + + if(pMesh->edges[edge].z == 1 && (pMesh->edges[edge].x == vertNum || pMesh->edges[edge].y == vertNum)) { + + for(int i = 0; i < faceIndex; i++) { + + for(int j = 0; j < 3; j++) { + if(pMesh->faces[i].vertexNum[j] == vertNum) { + sharedFace = i; + sharedVert = j; + break; + } + } + } + } + if(sharedFace != -1) + break; + + } +} + +void MA_ParseMesh(idParser& parser) { + + maObject_t *object; + object = (maObject_t *)Mem_Alloc( sizeof( maObject_t ), TAG_MODEL ); + memset( object, 0, sizeof( maObject_t ) ); + maGlobal.model->objects.Append( object ); + maGlobal.currentObject = object; + object->materialRef = -1; + + + //Get the header info from the mesh + maNodeHeader_t header; + MA_ParseNodeHeader(parser, &header); + + //Find my parent + if(header.parent[0] != 0) { + //Find the parent + maTransform_t** parent; + maGlobal.model->transforms.Get(header.parent, &parent); + if(parent) { + maGlobal.currentObject->mesh.transform = *parent; + } + } + + strcpy(object->name, header.name); + + //Read the transform attributes + idToken token; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + if(!token.Icmp("setAttr")) { + maAttribHeader_t header; + MA_ParseAttribHeader(parser, &header); + + if(strstr(header.name, ".vt")) { + MA_ParseVertex(parser, &header); + } else if (strstr(header.name, ".ed")) { + MA_ParseEdge(parser, &header); + } else if (strstr(header.name, ".pt")) { + MA_ParseVertexTransforms(parser, &header); + } else if (strstr(header.name, ".n")) { + MA_ParseNormal(parser, &header); + } else if (strstr(header.name, ".fc")) { + MA_ParseFace(parser, &header); + } else if (strstr(header.name, ".clr")) { + MA_ParseColor(parser, &header); + } else if (strstr(header.name, ".uvst")) { + MA_ParseTVert(parser, &header); + } else { + parser.SkipRestOfLine(); + } + } + } + + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + + //Get the verts from the edge + for(int i = 0; i < pMesh->numFaces; i++) { + for(int j = 0; j < 3; j++) { + int edge = pMesh->faces[i].edge[j]; + if(edge < 0) { + edge = idMath::Fabs(edge)-1; + pMesh->faces[i].vertexNum[j] = pMesh->edges[edge].y; + } else { + pMesh->faces[i].vertexNum[j] = pMesh->edges[edge].x; + } + } + } + + //Get the normals + if(pMesh->normalsParsed) { + for(int i = 0; i < pMesh->numFaces; i++) { + for(int j = 0; j < 3; j++) { + + //Is this vertex shared + int sharedFace = -1; + int sharedVert = -1; + + if(MA_QuickIsVertShared(i, j)) { + MA_GetSharedFace(i, j, sharedFace, sharedVert); + } + + if(sharedFace != -1) { + //Get the normal from the share + pMesh->faces[i].vertexNormals[j] = pMesh->faces[sharedFace].vertexNormals[sharedVert]; + + } else { + //The vertex is not shared so get the next normal + if(pMesh->nextNormal >= pMesh->numNormals) { + //We are using more normals than exist + throw idException(va("Maya Loader '%s': Invalid Normals Index.", parser.GetFileName())); + } + pMesh->faces[i].vertexNormals[j] = pMesh->normals[pMesh->nextNormal]; + pMesh->nextNormal++; + } + } + } + } + + //Now that the normals are good...lets reorder the verts to make the tris face the right way + for(int i = 0; i < pMesh->numFaces; i++) { + int tmp = pMesh->faces[i].vertexNum[1]; + pMesh->faces[i].vertexNum[1] = pMesh->faces[i].vertexNum[2]; + pMesh->faces[i].vertexNum[2] = tmp; + + idVec3 tmpVec = pMesh->faces[i].vertexNormals[1]; + pMesh->faces[i].vertexNormals[1] = pMesh->faces[i].vertexNormals[2]; + pMesh->faces[i].vertexNormals[2] = tmpVec; + + tmp = pMesh->faces[i].tVertexNum[1]; + pMesh->faces[i].tVertexNum[1] = pMesh->faces[i].tVertexNum[2]; + pMesh->faces[i].tVertexNum[2] = tmp; + + tmp = pMesh->faces[i].vertexColors[1]; + pMesh->faces[i].vertexColors[1] = pMesh->faces[i].vertexColors[2]; + pMesh->faces[i].vertexColors[2] = tmp; + } + + //Now apply the pt transformations + for(int i = 0; i < pMesh->numVertTransforms; i++) { + pMesh->vertexes[(int)pMesh->vertTransforms[i].w] += pMesh->vertTransforms[i].ToVec3(); + } + + MA_VERBOSE((va("MESH %s - parent %s\n", header.name, header.parent))); + MA_VERBOSE((va("\tverts:%d\n",maGlobal.currentObject->mesh.numVertexes))); + MA_VERBOSE((va("\tfaces:%d\n",maGlobal.currentObject->mesh.numFaces))); +} + +void MA_ParseFileNode(idParser& parser) { + + //Get the header info from the node + maNodeHeader_t header; + MA_ParseNodeHeader(parser, &header); + + //Read the transform attributes + idToken token; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + if(!token.Icmp("setAttr")) { + maAttribHeader_t attribHeader; + MA_ParseAttribHeader(parser, &attribHeader); + + if(strstr(attribHeader.name, ".ftn")) { + parser.SkipUntilString("string"); + parser.ReadToken(&token); + if(!token.Icmp("(")) { + parser.ReadToken(&token); + } + + maFileNode_t* fileNode; + fileNode = (maFileNode_t*)Mem_Alloc( sizeof( maFileNode_t ), TAG_MODEL ); + strcpy(fileNode->name, header.name); + strcpy(fileNode->path, token.c_str()); + + maGlobal.model->fileNodes.Set(fileNode->name, fileNode); + } else { + parser.SkipRestOfLine(); + } + } + } +} + +void MA_ParseMaterialNode(idParser& parser) { + + //Get the header info from the node + maNodeHeader_t header; + MA_ParseNodeHeader(parser, &header); + + maMaterialNode_t* matNode; + matNode = (maMaterialNode_t*)Mem_Alloc( sizeof( maMaterialNode_t ), TAG_MODEL ); + memset(matNode, 0, sizeof(maMaterialNode_t)); + + strcpy(matNode->name, header.name); + + maGlobal.model->materialNodes.Set(matNode->name, matNode); +} + +void MA_ParseCreateNode(idParser& parser) { + + idToken token; + parser.ReadToken(&token); + + if(!token.Icmp("transform")) { + MA_ParseTransform(parser); + } else if(!token.Icmp("mesh")) { + MA_ParseMesh(parser); + } else if(!token.Icmp("file")) { + MA_ParseFileNode(parser); + } else if(!token.Icmp("shadingEngine") || !token.Icmp("lambert") || !token.Icmp("phong") || !token.Icmp("blinn") ) { + MA_ParseMaterialNode(parser); + } +} + + +int MA_AddMaterial(const char* materialName) { + + + maMaterialNode_t** destNode; + maGlobal.model->materialNodes.Get(materialName, &destNode); + if(destNode) { + maMaterialNode_t* matNode = *destNode; + + //Iterate down the tree until we get a file + while(matNode && !matNode->file) { + matNode = matNode->child; + } + if(matNode && matNode->file) { + + //Got the file + maMaterial_t *material; + material = (maMaterial_t *)Mem_Alloc( sizeof( maMaterial_t ), TAG_MODEL ); + memset( material, 0, sizeof( maMaterial_t ) ); + + //Remove the OS stuff + idStr qPath; + qPath = fileSystem->OSPathToRelativePath( matNode->file->path ); + + strcpy(material->name, qPath.c_str()); + + maGlobal.model->materials.Append( material ); + return maGlobal.model->materials.Num()-1; + } + } + return -1; +} + +bool MA_ParseConnectAttr(idParser& parser) { + + idStr temp; + idStr srcName; + idStr srcType; + idStr destName; + idStr destType; + + idToken token; + parser.ReadToken(&token); + temp = token; + int dot = temp.Find("."); + if(dot == -1) { + throw idException(va("Maya Loader '%s': Invalid Connect Attribute.", parser.GetFileName())); + } + srcName = temp.Left(dot); + srcType = temp.Right(temp.Length()-dot-1); + + parser.ReadToken(&token); + temp = token; + dot = temp.Find("."); + if(dot == -1) { + throw idException(va("Maya Loader '%s': Invalid Connect Attribute.", parser.GetFileName())); + } + destName = temp.Left(dot); + destType = temp.Right(temp.Length()-dot-1); + + if(srcType.Find("oc") != -1) { + + //Is this attribute a material node attribute + maMaterialNode_t** matNode; + maGlobal.model->materialNodes.Get(srcName, &matNode); + if(matNode) { + maMaterialNode_t** destNode; + maGlobal.model->materialNodes.Get(destName, &destNode); + if(destNode) { + (*destNode)->child = *matNode; + } + } + + //Is this attribute a file node + maFileNode_t** fileNode; + maGlobal.model->fileNodes.Get(srcName, &fileNode); + if(fileNode) { + maMaterialNode_t** destNode; + maGlobal.model->materialNodes.Get(destName, &destNode); + if(destNode) { + (*destNode)->file = *fileNode; + } + } + } + + if(srcType.Find("iog") != -1) { + //Is this an attribute for one of our meshes + for(int i = 0; i < maGlobal.model->objects.Num(); i++) { + if(!strcmp(maGlobal.model->objects[i]->name, srcName)) { + //maGlobal.model->objects[i]->materialRef = MA_AddMaterial(destName); + strcpy(maGlobal.model->objects[i]->materialName, destName); + break; + } + } + } + + return true; +} + + +void MA_BuildScale(idMat4& mat, float x, float y, float z) { + mat.Identity(); + mat[0][0] = x; + mat[1][1] = y; + mat[2][2] = z; +} + +void MA_BuildAxisRotation(idMat4& mat, float ang, int axis) { + + float sinAng = idMath::Sin(ang); + float cosAng = idMath::Cos(ang); + + mat.Identity(); + switch(axis) { + case 0: //x + mat[1][1] = cosAng; + mat[1][2] = sinAng; + mat[2][1] = -sinAng; + mat[2][2] = cosAng; + break; + case 1: //y + mat[0][0] = cosAng; + mat[0][2] = -sinAng; + mat[2][0] = sinAng; + mat[2][2] = cosAng; + break; + case 2://z + mat[0][0] = cosAng; + mat[0][1] = sinAng; + mat[1][0] = -sinAng; + mat[1][1] = cosAng; + break; + } +} + +void MA_ApplyTransformation(maModel_t *model) { + + for(int i = 0; i < model->objects.Num(); i++) { + maMesh_t* mesh = &model->objects[i]->mesh; + maTransform_t* transform = mesh->transform; + + + + while(transform) { + + idMat4 rotx, roty, rotz; + idMat4 scale; + + rotx.Identity(); + roty.Identity(); + rotz.Identity(); + + if(fabs(transform->rotate.x) > 0.0f) { + MA_BuildAxisRotation(rotx, DEG2RAD(-transform->rotate.x), 0); + } + if(fabs(transform->rotate.y) > 0.0f) { + MA_BuildAxisRotation(roty, DEG2RAD(transform->rotate.y), 1); + } + if(fabs(transform->rotate.z) > 0.0f) { + MA_BuildAxisRotation(rotz, DEG2RAD(-transform->rotate.z), 2); + } + + MA_BuildScale(scale, transform->scale.x, transform->scale.y, transform->scale.z); + + //Apply the transformation to each vert + for(int j = 0; j < mesh->numVertexes; j++) { + mesh->vertexes[j] = scale * mesh->vertexes[j]; + + mesh->vertexes[j] = rotx * mesh->vertexes[j]; + mesh->vertexes[j] = rotz * mesh->vertexes[j]; + mesh->vertexes[j] = roty * mesh->vertexes[j]; + + mesh->vertexes[j] = mesh->vertexes[j] + transform->translate; + } + + transform = transform->parent; + } + } +} + +/* +================= +MA_Parse +================= +*/ +maModel_t *MA_Parse( const char *buffer, const char* filename, bool verbose ) { + memset( &maGlobal, 0, sizeof( maGlobal ) ); + + maGlobal.verbose = verbose; + + + + + maGlobal.currentObject = NULL; + + // NOTE: using new operator because aseModel_t contains idList class objects + maGlobal.model = new (TAG_MODEL) maModel_t; + maGlobal.model->objects.Resize( 32, 32 ); + maGlobal.model->materials.Resize( 32, 32 ); + + + idParser parser; + parser.SetFlags(LEXFL_NOSTRINGCONCAT); + parser.LoadMemory(buffer, strlen(buffer), filename); + + idToken token; + while(parser.ReadToken(&token)) { + + if(!token.Icmp("createNode")) { + MA_ParseCreateNode(parser); + } else if(!token.Icmp("connectAttr")) { + MA_ParseConnectAttr(parser); + } + } + + //Resolve The Materials + for(int i = 0; i < maGlobal.model->objects.Num(); i++) { + maGlobal.model->objects[i]->materialRef = MA_AddMaterial(maGlobal.model->objects[i]->materialName); + } + + + + //Apply Transformation + MA_ApplyTransformation(maGlobal.model); + + return maGlobal.model; +} + +/* +================= +MA_Load +================= +*/ +maModel_t *MA_Load( const char *fileName ) { + char *buf; + ID_TIME_T timeStamp; + maModel_t *ma; + + fileSystem->ReadFile( fileName, (void **)&buf, &timeStamp ); + if ( !buf ) { + return NULL; + } + + try { + ma = MA_Parse( buf, fileName, false ); + ma->timeStamp = timeStamp; + } catch( idException &e ) { + common->Warning("%s", e.GetError()); + if(maGlobal.model) { + MA_Free(maGlobal.model); + } + ma = NULL; + } + + fileSystem->FreeFile( buf ); + + return ma; +} + +/* +================= +MA_Free +================= +*/ +void MA_Free( maModel_t *ma ) { + int i; + maObject_t *obj; + maMesh_t *mesh; + maMaterial_t *material; + + if ( !ma ) { + return; + } + for ( i = 0; i < ma->objects.Num(); i++ ) { + obj = ma->objects[i]; + + // free the base nesh + mesh = &obj->mesh; + + if ( mesh->vertexes ) { + Mem_Free( mesh->vertexes ); + } + if ( mesh->vertTransforms ) { + Mem_Free( mesh->vertTransforms ); + } + if ( mesh->normals ) { + Mem_Free( mesh->normals ); + } + if ( mesh->tvertexes ) { + Mem_Free( mesh->tvertexes ); + } + if ( mesh->edges ) { + Mem_Free( mesh->edges ); + } + if ( mesh->colors ) { + Mem_Free( mesh->colors ); + } + if ( mesh->faces ) { + Mem_Free( mesh->faces ); + } + Mem_Free( obj ); + } + ma->objects.Clear(); + + for ( i = 0; i < ma->materials.Num(); i++ ) { + material = ma->materials[i]; + Mem_Free( material ); + } + ma->materials.Clear(); + + maTransform_t** trans; + for ( i = 0; i < ma->transforms.Num(); i++ ) { + trans = ma->transforms.GetIndex(i); + Mem_Free( *trans ); + } + ma->transforms.Clear(); + + + maFileNode_t** fileNode; + for ( i = 0; i < ma->fileNodes.Num(); i++ ) { + fileNode = ma->fileNodes.GetIndex(i); + Mem_Free( *fileNode ); + } + ma->fileNodes.Clear(); + + maMaterialNode_t** matNode; + for ( i = 0; i < ma->materialNodes.Num(); i++ ) { + matNode = ma->materialNodes.GetIndex(i); + Mem_Free( *matNode ); + } + ma->materialNodes.Clear(); + delete ma; +} diff --git a/neo/renderer/Model_ma.h b/neo/renderer/Model_ma.h new file mode 100644 index 00000000..7d50cb8b --- /dev/null +++ b/neo/renderer/Model_ma.h @@ -0,0 +1,145 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODEL_MA_H__ +#define __MODEL_MA_H__ + +/* +=============================================================================== + + MA loader. (Maya Ascii Format) + +=============================================================================== +*/ + +typedef struct { + char name[128]; + char parent[128]; +} maNodeHeader_t; + +typedef struct { + char name[128]; + int size; +} maAttribHeader_t; + +typedef struct maTransform_s { + idVec3 translate; + idVec3 rotate; + idVec3 scale; + maTransform_s* parent; +} maTransform_t; + +typedef struct { + int edge[3]; + int vertexNum[3]; + int tVertexNum[3]; + int vertexColors[3]; + idVec3 vertexNormals[3]; +} maFace_t; + +typedef struct { + + //Transform to be applied + maTransform_t* transform; + + //Verts + int numVertexes; + idVec3 * vertexes; + int numVertTransforms; + idVec4 * vertTransforms; + int nextVertTransformIndex; + + //Texture Coordinates + int numTVertexes; + idVec2 * tvertexes; + + //Edges + int numEdges; + idVec3 * edges; + + //Colors + int numColors; + byte* colors; + + //Faces + int numFaces; + maFace_t * faces; + + //Normals + int numNormals; + idVec3 * normals; + bool normalsParsed; + int nextNormal; + +} maMesh_t; + +typedef struct { + char name[128]; + float uOffset, vOffset; // max lets you offset by material without changing texCoords + float uTiling, vTiling; // multiply tex coords by this + float angle; // in clockwise radians +} maMaterial_t; + +typedef struct { + char name[128]; + int materialRef; + char materialName[128]; + + maMesh_t mesh; +} maObject_t; + + +typedef struct { + char name[128]; + char path[1024]; +} maFileNode_t; + +typedef struct maMaterialNode_s { + char name[128]; + + maMaterialNode_s* child; + maFileNode_t* file; + +} maMaterialNode_t; + +typedef struct maModel_s { + ID_TIME_T timeStamp; + idList materials; + idList objects; + idHashTable transforms; + + //Material Resolution + idHashTable fileNodes; + idHashTable materialNodes; + +} maModel_t; + +maModel_t *MA_Load( const char *fileName ); +void MA_Free( maModel_t *ma ); + +#endif /* !__MODEL_MA_H__ */ diff --git a/neo/renderer/Model_md3.cpp b/neo/renderer/Model_md3.cpp new file mode 100644 index 00000000..06137e31 --- /dev/null +++ b/neo/renderer/Model_md3.cpp @@ -0,0 +1,369 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" +#include "Model_md3.h" + +/*********************************************************************** + + idMD3Mesh + +***********************************************************************/ + +#define LL(x) x=LittleLong(x) + +/* +================= +idRenderModelMD3::InitFromFile +================= +*/ +void idRenderModelMD3::InitFromFile( const char *fileName ) { + int i, j; + md3Header_t *pinmodel; + md3Frame_t *frame; + md3Surface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; + void *buffer; + int version; + int size; + + + name = fileName; + + size = fileSystem->ReadFile( fileName, &buffer, NULL ); + if (!size || size<0 ) { + return; + } + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD3_VERSION) { + fileSystem->FreeFile( buffer ); + common->Warning( "InitFromFile: %s has wrong version (%i should be %i)", + fileName, version, MD3_VERSION); + return; + } + + size = LittleLong(pinmodel->ofsEnd); + dataSize += size; + md3 = (md3Header_t *)Mem_Alloc( size, TAG_MODEL ); + + memcpy (md3, buffer, LittleLong(pinmodel->ofsEnd) ); + + LL(md3->ident); + LL(md3->version); + LL(md3->numFrames); + LL(md3->numTags); + LL(md3->numSurfaces); + LL(md3->ofsFrames); + LL(md3->ofsTags); + LL(md3->ofsSurfaces); + LL(md3->ofsEnd); + + if ( md3->numFrames < 1 ) { + common->Warning( "InitFromFile: %s has no frames", fileName ); + fileSystem->FreeFile( buffer ); + return; + } + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)md3 + md3->ofsFrames ); + for ( i = 0 ; i < md3->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)md3 + md3->ofsTags ); + for ( i = 0 ; i < md3->numTags * md3->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)md3 + md3->ofsSurfaces ); + for ( i = 0 ; i < md3->numSurfaces ; i++) { + + LL(surf->ident); + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + common->Error( "InitFromFile: %s has more than %i verts on a surface (%i)", + fileName, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + common->Error( "InitFromFile: %s has more than %i triangles on a surface (%i)", + fileName, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = 0; //SF_MD3; + + // lowercase the surface name so skin compares are faster + int slen = (int)strlen( surf->name ); + for( j = 0; j < slen; j++ ) { + surf->name[j] = tolower( surf->name[j] ); + } + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } + + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + const idMaterial *sh; + + sh = declManager->FindMaterial( shader->name ); + shader->shader = sh; + } + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + fileSystem->FreeFile( buffer ); +} + +/* +================= +idRenderModelMD3::IsDynamicModel +================= +*/ +dynamicModel_t idRenderModelMD3::IsDynamicModel() const { + return DM_CACHED; +} + +/* +================= +idRenderModelMD3::LerpMeshVertexes +================= +*/ +void idRenderModelMD3::LerpMeshVertexes ( srfTriangles_t *tri, const struct md3Surface_s *surf, const float backlerp, const int frame, const int oldframe ) const { + short *oldXyz, *newXyz; + float oldXyzScale, newXyzScale; + int vertNum; + int numVerts; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + (frame * surf->numVerts * 4); + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, newXyz += 4 ) { + + idDrawVert *outvert = &tri->verts[tri->numVerts]; + + outvert->xyz.x = newXyz[0] * newXyzScale; + outvert->xyz.y = newXyz[1] * newXyzScale; + outvert->xyz.z = newXyz[2] * newXyzScale; + + tri->numVerts++; + } + } else { + // + // interpolate and copy the vertexes + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + (oldframe * surf->numVerts * 4); + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, oldXyz += 4, newXyz += 4 ) { + + idDrawVert *outvert = &tri->verts[tri->numVerts]; + + // interpolate the xyz + outvert->xyz.x = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outvert->xyz.y = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outvert->xyz.z = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + tri->numVerts++; + } + } +} + +/* +============= +idRenderModelMD3::InstantiateDynamicModel +============= +*/ +idRenderModel *idRenderModelMD3::InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ) { + int i, j; + float backlerp; + int * triangles; + int indexes; + int numVerts; + md3Surface_t * surface; + int frame, oldframe; + idRenderModelStatic *staticModel; + + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + + staticModel = new (TAG_MODEL) idRenderModelStatic; + staticModel->bounds.Clear(); + + surface = (md3Surface_t *) ((byte *)md3 + md3->ofsSurfaces); + + // TODO: these need set by an entity + frame = ent->shaderParms[SHADERPARM_MD3_FRAME]; // probably want to keep frames < 1000 or so + oldframe = ent->shaderParms[SHADERPARM_MD3_LASTFRAME]; + backlerp = ent->shaderParms[SHADERPARM_MD3_BACKLERP]; + + for( i = 0; i < md3->numSurfaces; i++ ) { + + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, surface->numVerts ); + R_AllocStaticTriSurfIndexes( tri, surface->numTriangles * 3 ); + tri->bounds.Clear(); + + modelSurface_t surf; + + surf.geometry = tri; + + md3Shader_t* shaders = (md3Shader_t *) ((byte *)surface + surface->ofsShaders); + surf.shader = shaders->shader; + + LerpMeshVertexes( tri, surface, backlerp, frame, oldframe ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + for (j = 0 ; j < indexes ; j++) { + tri->indexes[j] = triangles[j]; + } + tri->numIndexes += indexes; + + const idVec2 * texCoords = (idVec2 *) ((byte *)surface + surface->ofsSt); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tri->verts[j].SetTexCoord( texCoords[j] ); + } + + R_BoundTriSurf( tri ); + + staticModel->AddSurface( surf ); + staticModel->bounds.AddPoint( surf.geometry->bounds[0] ); + staticModel->bounds.AddPoint( surf.geometry->bounds[1] ); + + // find the next surface + surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd ); + } + + return staticModel; +} + +/* +===================== +idRenderModelMD3::Bounds +===================== +*/ + +idBounds idRenderModelMD3::Bounds(const struct renderEntity_s *ent) const { + idBounds ret; + + ret.Clear(); + + if (!ent || !md3) { + // just give it the editor bounds + ret.AddPoint(idVec3(-10,-10,-10)); + ret.AddPoint(idVec3( 10, 10, 10)); + return ret; + } + + md3Frame_t *frame = (md3Frame_t *)( (byte *)md3 + md3->ofsFrames ); + + ret.AddPoint( frame->bounds[0] ); + ret.AddPoint( frame->bounds[1] ); + + return ret; +} + diff --git a/neo/renderer/Model_md3.h b/neo/renderer/Model_md3.h new file mode 100644 index 00000000..aedb5efb --- /dev/null +++ b/neo/renderer/Model_md3.h @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __MODEL_MD3_H__ +#define __MODEL_MD3_H__ + +/* +======================================================================== + +.MD3 triangle model file format + +Private structures used by the MD3 loader. + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + +// limits +#define MD3_MAX_LODS 4 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame +#define MAX_MD3PATH 64 // from quake3 + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +typedef struct md3Frame_s { + idVec3 bounds[2]; + idVec3 localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_MD3PATH]; // tag name + idVec3 origin; + idVec3 axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ + +typedef struct md3Surface_s { + int ident; // + + char name[MAX_MD3PATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_MD3PATH]; + const idMaterial * shader; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct md3Header_s { + int ident; + int version; + + char name[MAX_MD3PATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +#endif /* !__MODEL_MD3_H__ */ diff --git a/neo/renderer/Model_md5.cpp b/neo/renderer/Model_md5.cpp new file mode 100644 index 00000000..30865b41 --- /dev/null +++ b/neo/renderer/Model_md5.cpp @@ -0,0 +1,1410 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + + +static const __m128 vector_float_posInfinity = { idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY }; +static const __m128 vector_float_negInfinity = { -idMath::INFINITY, -idMath::INFINITY, -idMath::INFINITY, -idMath::INFINITY }; + + +static const char *MD5_SnapshotName = "_MD5_Snapshot_"; + +static const byte MD5B_VERSION = 106; +static const unsigned int MD5B_MAGIC = ( '5' << 24 ) | ( 'D' << 16 ) | ( 'M' << 8 ) | MD5B_VERSION; + +idCVar r_useGPUSkinning( "r_useGPUSkinning", "1", CVAR_INTEGER, "animate normals and tangents instead of deriving" ); + +/*********************************************************************** + + idMD5Mesh + +***********************************************************************/ + +static int c_numVerts = 0; +static int c_numWeights = 0; +static int c_numWeightJoints = 0; + +struct vertexWeight_t { + int joint; + idVec3 offset; + float jointWeight; +}; + +/* +==================== +idMD5Mesh::idMD5Mesh +==================== +*/ +idMD5Mesh::idMD5Mesh() { + shader = NULL; + numVerts = 0; + numTris = 0; + meshJoints = NULL; + numMeshJoints = 0; + maxJointVertDist = 0.0f; + deformInfo = NULL; + surfaceNum = 0; +} + +/* +==================== +idMD5Mesh::~idMD5Mesh +==================== +*/ +idMD5Mesh::~idMD5Mesh() { + if ( meshJoints != NULL ) { + Mem_Free( meshJoints ); + meshJoints = NULL; + } + if ( deformInfo != NULL ) { + R_FreeDeformInfo( deformInfo ); + deformInfo = NULL; + } +} + +/* +==================== +idMD5Mesh::ParseMesh +==================== +*/ +void idMD5Mesh::ParseMesh( idLexer &parser, int numJoints, const idJointMat *joints ) { + idToken token; + idToken name; + + parser.ExpectTokenString( "{" ); + + // + // parse name + // + if ( parser.CheckTokenString( "name" ) ) { + parser.ReadToken( &name ); + } + + // + // parse shader + // + parser.ExpectTokenString( "shader" ); + + parser.ReadToken( &token ); + idStr shaderName = token; + + shader = declManager->FindMaterial( shaderName ); + + // + // parse texture coordinates + // + parser.ExpectTokenString( "numverts" ); + int count = parser.ParseInt(); + if ( count < 0 ) { + parser.Error( "Invalid size: %s", token.c_str() ); + } + + this->numVerts = count; + + idList texCoords; + idList firstWeightForVertex; + idList numWeightsForVertex; + + texCoords.SetNum( count ); + firstWeightForVertex.SetNum( count ); + numWeightsForVertex.SetNum( count ); + + int numWeights = 0; + int maxweight = 0; + for ( int i = 0; i < texCoords.Num(); i++ ) { + parser.ExpectTokenString( "vert" ); + parser.ParseInt(); + + parser.Parse1DMatrix( 2, texCoords[ i ].ToFloatPtr() ); + + firstWeightForVertex[ i ] = parser.ParseInt(); + numWeightsForVertex[ i ] = parser.ParseInt(); + + if ( !numWeightsForVertex[ i ] ) { + parser.Error( "Vertex without any joint weights." ); + } + + numWeights += numWeightsForVertex[ i ]; + if ( numWeightsForVertex[ i ] + firstWeightForVertex[ i ] > maxweight ) { + maxweight = numWeightsForVertex[ i ] + firstWeightForVertex[ i ]; + } + } + + // + // parse tris + // + parser.ExpectTokenString( "numtris" ); + count = parser.ParseInt(); + if ( count < 0 ) { + parser.Error( "Invalid size: %d", count ); + } + + idList tris; + tris.SetNum( count * 3 ); + numTris = count; + for ( int i = 0; i < count; i++ ) { + parser.ExpectTokenString( "tri" ); + parser.ParseInt(); + + tris[ i * 3 + 0 ] = parser.ParseInt(); + tris[ i * 3 + 1 ] = parser.ParseInt(); + tris[ i * 3 + 2 ] = parser.ParseInt(); + } + + // + // parse weights + // + parser.ExpectTokenString( "numweights" ); + count = parser.ParseInt(); + if ( count < 0 ) { + parser.Error( "Invalid size: %d", count ); + } + + if ( maxweight > count ) { + parser.Warning( "Vertices reference out of range weights in model (%d of %d weights).", maxweight, count ); + } + + idList tempWeights; + tempWeights.SetNum( count ); + assert( numJoints < 256 ); // so we can pack into bytes + + for ( int i = 0; i < count; i++ ) { + parser.ExpectTokenString( "weight" ); + parser.ParseInt(); + + int jointnum = parser.ParseInt(); + if ( ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + parser.Error( "Joint Index out of range(%d): %d", numJoints, jointnum ); + } + + tempWeights[ i ].joint = jointnum; + tempWeights[ i ].jointWeight = parser.ParseFloat(); + + parser.Parse1DMatrix( 3, tempWeights[ i ].offset.ToFloatPtr() ); + } + + // create pre-scaled weights and an index for the vertex/joint lookup + idVec4 * scaledWeights = (idVec4 *) Mem_Alloc16( numWeights * sizeof( scaledWeights[0] ), TAG_MD5_WEIGHT ); + int * weightIndex = (int *) Mem_Alloc16( numWeights * 2 * sizeof( weightIndex[0] ), TAG_MD5_INDEX ); + memset( weightIndex, 0, numWeights * 2 * sizeof( weightIndex[0] ) ); + + count = 0; + for ( int i = 0; i < texCoords.Num(); i++ ) { + int num = firstWeightForVertex[i]; + for( int j = 0; j < numWeightsForVertex[i]; j++, num++, count++ ) { + scaledWeights[count].ToVec3() = tempWeights[num].offset * tempWeights[num].jointWeight; + scaledWeights[count].w = tempWeights[num].jointWeight; + weightIndex[count * 2 + 0] = tempWeights[num].joint * sizeof( idJointMat ); + } + weightIndex[count * 2 - 1] = 1; + } + + parser.ExpectTokenString( "}" ); + + // update counters + c_numVerts += texCoords.Num(); + c_numWeights += numWeights; + c_numWeightJoints++; + for ( int i = 0; i < numWeights; i++ ) { + c_numWeightJoints += weightIndex[i*2+1]; + } + + // + // build a base pose that can be used for skinning + // + idDrawVert * basePose = (idDrawVert *)Mem_ClearedAlloc( texCoords.Num() * sizeof( *basePose ), TAG_MD5_BASE ); + for( int j = 0, i = 0; i < texCoords.Num(); i++ ) { + idVec3 v = ( *(idJointMat *) ( (byte *)joints + weightIndex[j*2+0] ) ) * scaledWeights[j]; + while( weightIndex[j*2+1] == 0 ) { + j++; + v += ( *(idJointMat *) ( (byte *)joints + weightIndex[j*2+0] ) ) * scaledWeights[j]; + } + j++; + + basePose[i].Clear(); + basePose[i].xyz = v; + basePose[i].SetTexCoord( texCoords[i] ); + } + + // build the weights and bone indexes into the verts, so they will be duplicated + // as necessary at mirror seems + + static int maxWeightsPerVert; + static float maxResidualWeight; + + const int MAX_VERTEX_WEIGHTS = 4; + + idList< bool > jointIsUsed; + jointIsUsed.SetNum( numJoints ); + for ( int i = 0; i < jointIsUsed.Num(); i++ ) { + jointIsUsed[i] = false; + } + + numMeshJoints = 0; + maxJointVertDist = 0.0f; + + //----------------------------------------- + // new-style setup for fixed four weights and normal / tangent deformation + // + // Several important models have >25% residual weight in joints after the + // first four, which is worrisome for using a fixed four joint deformation. + //----------------------------------------- + for ( int i = 0; i < texCoords.Num(); i++ ) { + idDrawVert & dv = basePose[i]; + + // some models do have >4 joint weights, so it is necessary to sort and renormalize + + // sort the weights and take the four largest + int weights[256]; + const int numWeights = numWeightsForVertex[ i ]; + for ( int j = 0; j < numWeights; j++ ) { + weights[j] = firstWeightForVertex[i] + j; + } + // bubble sort + for ( int j = 0; j < numWeights; j++ ) { + for ( int k = 0; k < numWeights - 1 - j; k++ ) { + if ( tempWeights[weights[k]].jointWeight < tempWeights[weights[k+1]].jointWeight ) { + SwapValues( weights[k], weights[k+1] ); + } + } + } + + if ( numWeights > maxWeightsPerVert ) { + maxWeightsPerVert = numWeights; + } + + const int usedWeights = Min( MAX_VERTEX_WEIGHTS, numWeights ); + + float totalWeight = 0; + for ( int j = 0; j < numWeights; j++ ) { + totalWeight += tempWeights[weights[j]].jointWeight; + } + assert( totalWeight > 0.999f && totalWeight < 1.001f ); + + float usedWeight = 0; + for ( int j = 0; j < usedWeights; j++ ) { + usedWeight += tempWeights[weights[j]].jointWeight; + } + + const float residualWeight = totalWeight - usedWeight; + if ( residualWeight > maxResidualWeight ) { + maxResidualWeight = residualWeight; + } + + byte finalWeights[MAX_VERTEX_WEIGHTS] = { 0 }; + byte finalJointIndecies[MAX_VERTEX_WEIGHTS] = { 0 }; + for ( int j = 0; j < usedWeights; j++ ) { + const vertexWeight_t & weight = tempWeights[weights[j]]; + const int jointIndex = weight.joint; + const float fw = weight.jointWeight; + assert( fw >= 0.0f && fw <= 1.0f ); + const float normalizedWeight = fw / usedWeight; + finalWeights[j] = idMath::Ftob( normalizedWeight * 255.0f); + finalJointIndecies[j] = jointIndex; + } + + // Sort the weights and indices for hardware skinning + for ( int k = 0; k < 3; ++k ) { + for ( int l = k + 1; l < 4; ++l ) { + if ( finalWeights[l] > finalWeights[k] ) { + SwapValues( finalWeights[k], finalWeights[l] ); + SwapValues( finalJointIndecies[k], finalJointIndecies[l] ); + } + } + } + + // Give any left over to the biggest weight + finalWeights[0] += Max( 255 - finalWeights[0] - finalWeights[1] - finalWeights[2] - finalWeights[3], 0 ); + + dv.color[0] = finalJointIndecies[0]; + dv.color[1] = finalJointIndecies[1]; + dv.color[2] = finalJointIndecies[2]; + dv.color[3] = finalJointIndecies[3]; + + dv.color2[0] = finalWeights[0]; + dv.color2[1] = finalWeights[1]; + dv.color2[2] = finalWeights[2]; + dv.color2[3] = finalWeights[3]; + + for ( int j = usedWeights; j < 4; j++ ) { + assert( dv.color2[j] == 0 ); + } + + for ( int j = 0; j < usedWeights; j++ ) { + if ( !jointIsUsed[finalJointIndecies[j]] ) { + jointIsUsed[finalJointIndecies[j]] = true; + numMeshJoints++; + } + const idJointMat & joint = joints[finalJointIndecies[j]]; + float dist = ( dv.xyz - joint.GetTranslation() ).Length(); + if ( dist > maxJointVertDist ) { + maxJointVertDist = dist; + } + } + } + + meshJoints = (byte *) Mem_Alloc( numMeshJoints * sizeof( meshJoints[0] ), TAG_MODEL ); + numMeshJoints = 0; + for ( int i = 0; i < numJoints; i++ ) { + if ( jointIsUsed[i] ) { + meshJoints[numMeshJoints++] = i; + } + } + + // build the deformInfo and collect a final base pose with the mirror + // seam verts properly including the bone weights + deformInfo = R_BuildDeformInfo( texCoords.Num(), basePose, tris.Num(), tris.Ptr(), + shader->UseUnsmoothedTangents() ); + + for ( int i = 0; i < deformInfo->numOutputVerts; i++ ) { + for ( int j = 0; j < 4; j++ ) { + if ( deformInfo->verts[i].color[j] >= numJoints ) { + idLib::FatalError( "Bad joint index" ); + } + } + } + + Mem_Free( basePose ); +} + +/* +============ +TransformVertsAndTangents +============ +*/ +void TransformVertsAndTangents( idDrawVert * targetVerts, const int numVerts, const idDrawVert *baseVerts, const idJointMat *joints ) { + for( int i = 0; i < numVerts; i++ ) { + const idDrawVert & base = baseVerts[i]; + + const idJointMat & j0 = joints[base.color[0]]; + const idJointMat & j1 = joints[base.color[1]]; + const idJointMat & j2 = joints[base.color[2]]; + const idJointMat & j3 = joints[base.color[3]]; + + const float w0 = base.color2[0] * ( 1.0f / 255.0f ); + const float w1 = base.color2[1] * ( 1.0f / 255.0f ); + const float w2 = base.color2[2] * ( 1.0f / 255.0f ); + const float w3 = base.color2[3] * ( 1.0f / 255.0f ); + + idJointMat accum; + idJointMat::Mul( accum, j0, w0 ); + idJointMat::Mad( accum, j1, w1 ); + idJointMat::Mad( accum, j2, w2 ); + idJointMat::Mad( accum, j3, w3 ); + + targetVerts[i].xyz = accum * idVec4( base.xyz.x, base.xyz.y, base.xyz.z, 1.0f ); + targetVerts[i].SetNormal( accum * base.GetNormal() ); + targetVerts[i].SetTangent( accum * base.GetTangent() ); + targetVerts[i].tangent[3] = base.tangent[3]; + } +} + +/* +==================== +idMD5Mesh::UpdateSurface +==================== +*/ +void idMD5Mesh::UpdateSurface( const struct renderEntity_s *ent, const idJointMat *entJoints, + const idJointMat *entJointsInverted, modelSurface_t *surf ) { + + tr.pc.c_deformedSurfaces++; + tr.pc.c_deformedVerts += deformInfo->numOutputVerts; + tr.pc.c_deformedIndexes += deformInfo->numIndexes; + + surf->shader = shader; + + if ( surf->geometry != NULL ) { + // if the number of verts and indexes are the same we can re-use the triangle surface + if ( surf->geometry->numVerts == deformInfo->numOutputVerts && surf->geometry->numIndexes == deformInfo->numIndexes ) { + R_FreeStaticTriSurfVertexCaches( surf->geometry ); + } else { + R_FreeStaticTriSurf( surf->geometry ); + surf->geometry = R_AllocStaticTriSurf(); + } + } else { + surf->geometry = R_AllocStaticTriSurf(); + } + + srfTriangles_t * tri = surf->geometry; + + // note that some of the data is referenced, and should not be freed + tri->referencedIndexes = true; + tri->numIndexes = deformInfo->numIndexes; + tri->indexes = deformInfo->indexes; + tri->silIndexes = deformInfo->silIndexes; + tri->numMirroredVerts = deformInfo->numMirroredVerts; + tri->mirroredVerts = deformInfo->mirroredVerts; + tri->numDupVerts = deformInfo->numDupVerts; + tri->dupVerts = deformInfo->dupVerts; + tri->numSilEdges = deformInfo->numSilEdges; + tri->silEdges = deformInfo->silEdges; + + tri->indexCache = deformInfo->staticIndexCache; + + tri->numVerts = deformInfo->numOutputVerts; + if ( r_useGPUSkinning.GetBool() ) { + if ( tri->verts != NULL && tri->verts != deformInfo->verts ) { + R_FreeStaticTriSurfVerts( tri ); + } + tri->verts = deformInfo->verts; + tri->ambientCache = deformInfo->staticAmbientCache; + tri->shadowCache = deformInfo->staticShadowCache; + tri->referencedVerts = true; + } else { + if ( tri->verts == NULL || tri->verts == deformInfo->verts ) { + tri->verts = NULL; + R_AllocStaticTriSurfVerts( tri, deformInfo->numOutputVerts ); + assert( tri->verts != NULL ); // quiet analyze warning + memcpy( tri->verts, deformInfo->verts, deformInfo->numOutputVerts * sizeof( deformInfo->verts[0] ) ); // copy over the texture coordinates + } + TransformVertsAndTangents( tri->verts, deformInfo->numOutputVerts, deformInfo->verts, entJointsInverted ); + tri->referencedVerts = false; + } + tri->tangentsCalculated = true; + + CalculateBounds( entJoints, tri->bounds ); +} + +/* +==================== +idMD5Mesh::CalculateBounds +==================== +*/ +void idMD5Mesh::CalculateBounds( const idJointMat * entJoints, idBounds & bounds ) const { + + __m128 minX = vector_float_posInfinity; + __m128 minY = vector_float_posInfinity; + __m128 minZ = vector_float_posInfinity; + __m128 maxX = vector_float_negInfinity; + __m128 maxY = vector_float_negInfinity; + __m128 maxZ = vector_float_negInfinity; + for ( int i = 0; i < numMeshJoints; i++ ) { + const idJointMat & joint = entJoints[meshJoints[i]]; + __m128 x = _mm_load_ps( joint.ToFloatPtr() + 0 * 4 ); + __m128 y = _mm_load_ps( joint.ToFloatPtr() + 1 * 4 ); + __m128 z = _mm_load_ps( joint.ToFloatPtr() + 2 * 4 ); + minX = _mm_min_ps( minX, x ); + minY = _mm_min_ps( minY, y ); + minZ = _mm_min_ps( minZ, z ); + maxX = _mm_max_ps( maxX, x ); + maxY = _mm_max_ps( maxY, y ); + maxZ = _mm_max_ps( maxZ, z ); + } + __m128 expand = _mm_splat_ps( _mm_load_ss( & maxJointVertDist ), 0 ); + minX = _mm_sub_ps( minX, expand ); + minY = _mm_sub_ps( minY, expand ); + minZ = _mm_sub_ps( minZ, expand ); + maxX = _mm_add_ps( maxX, expand ); + maxY = _mm_add_ps( maxY, expand ); + maxZ = _mm_add_ps( maxZ, expand ); + _mm_store_ss( bounds.ToFloatPtr() + 0, _mm_splat_ps( minX, 3 ) ); + _mm_store_ss( bounds.ToFloatPtr() + 1, _mm_splat_ps( minY, 3 ) ); + _mm_store_ss( bounds.ToFloatPtr() + 2, _mm_splat_ps( minZ, 3 ) ); + _mm_store_ss( bounds.ToFloatPtr() + 3, _mm_splat_ps( maxX, 3 ) ); + _mm_store_ss( bounds.ToFloatPtr() + 4, _mm_splat_ps( maxY, 3 ) ); + _mm_store_ss( bounds.ToFloatPtr() + 5, _mm_splat_ps( maxZ, 3 ) ); + +} + +/* +==================== +idMD5Mesh::NearestJoint +==================== +*/ +int idMD5Mesh::NearestJoint( int a, int b, int c ) const { + // duplicated vertices might not have weights + int vertNum; + if ( a >= 0 && a < numVerts ) { + vertNum = a; + } else if ( b >= 0 && b < numVerts ) { + vertNum = b; + } else if ( c >= 0 && c < numVerts ) { + vertNum = c; + } else { + // all vertices are duplicates which shouldn't happen + return 0; + } + + const idDrawVert & v = deformInfo->verts[vertNum]; + + int bestWeight = 0; + int bestJoint = 0; + for ( int i = 0; i < 4; i++ ) { + if ( v.color2[i] > bestWeight ) { + bestWeight = v.color2[i]; + bestJoint = v.color[i]; + } + } + + return bestJoint; +} + +/*********************************************************************** + + idRenderModelMD5 + +***********************************************************************/ + +/* +==================== +idRenderModelMD5::ParseJoint +==================== +*/ +void idRenderModelMD5::ParseJoint( idLexer &parser, idMD5Joint *joint, idJointQuat *defaultPose ) { + // + // parse name + // + idToken token; + parser.ReadToken( &token ); + joint->name = token; + + // + // parse parent + // + int num = parser.ParseInt(); + if ( num < 0 ) { + joint->parent = NULL; + } else { + if ( num >= joints.Num() - 1 ) { + parser.Error( "Invalid parent for joint '%s'", joint->name.c_str() ); + } + joint->parent = &joints[ num ]; + } + + // + // parse default pose + // + parser.Parse1DMatrix( 3, defaultPose->t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, defaultPose->q.ToFloatPtr() ); + defaultPose->q.w = defaultPose->q.CalcW(); +} + +/* +==================== +idRenderModelMD5::InitFromFile +==================== +*/ +void idRenderModelMD5::InitFromFile( const char *fileName ) { + name = fileName; + LoadModel(); +} + +/* +======================== +idRenderModelMD5::LoadBinaryModel +======================== +*/ +bool idRenderModelMD5::LoadBinaryModel( idFile * file, const ID_TIME_T sourceTimeStamp ) { + + if ( !idRenderModelStatic::LoadBinaryModel( file, sourceTimeStamp ) ) { + return false; + } + + unsigned int magic = 0; + file->ReadBig( magic ); + if ( magic != MD5B_MAGIC ) { + return false; + } + + int tempNum; + file->ReadBig( tempNum ); + joints.SetNum( tempNum ); + for ( int i = 0; i < joints.Num(); i++ ) { + file->ReadString( joints[i].name ); + int offset; + file->ReadBig( offset ); + if ( offset >= 0 ) { + joints[i].parent = joints.Ptr() + offset; + } else { + joints[i].parent = NULL; + } + } + + file->ReadBig( tempNum ); + defaultPose.SetNum( tempNum ); + for ( int i = 0; i < defaultPose.Num(); i++ ) { + file->ReadBig( defaultPose[i].q.x ); + file->ReadBig( defaultPose[i].q.y ); + file->ReadBig( defaultPose[i].q.z ); + file->ReadBig( defaultPose[i].q.w ); + file->ReadVec3( defaultPose[i].t ); + } + + file->ReadBig( tempNum ); + invertedDefaultPose.SetNum( tempNum ); + for ( int i = 0; i < invertedDefaultPose.Num(); i++ ) { + file->ReadBigArray( invertedDefaultPose[ i ].ToFloatPtr(), JOINTMAT_TYPESIZE ); + } + SIMD_INIT_LAST_JOINT( invertedDefaultPose.Ptr(), joints.Num() ); + + file->ReadBig( tempNum ); + meshes.SetNum( tempNum ); + for ( int i = 0; i < meshes.Num(); i++ ) { + + idStr materialName; + file->ReadString( materialName ); + if ( materialName.IsEmpty() ) { + meshes[i].shader = NULL; + } else { + meshes[i].shader = declManager->FindMaterial( materialName ); + } + + file->ReadBig( meshes[i].numVerts ); + file->ReadBig( meshes[i].numTris ); + + file->ReadBig( meshes[i].numMeshJoints ); + meshes[i].meshJoints = (byte *) Mem_Alloc( meshes[i].numMeshJoints * sizeof( meshes[i].meshJoints[0] ), TAG_MODEL ); + file->ReadBigArray( meshes[i].meshJoints, meshes[i].numMeshJoints ); + file->ReadBig( meshes[i].maxJointVertDist ); + + meshes[i].deformInfo = (deformInfo_t *)R_ClearedStaticAlloc( sizeof( deformInfo_t ) ); + deformInfo_t & deform = *meshes[i].deformInfo; + + file->ReadBig( deform.numSourceVerts ); + file->ReadBig( deform.numOutputVerts ); + file->ReadBig( deform.numIndexes ); + file->ReadBig( deform.numMirroredVerts ); + file->ReadBig( deform.numDupVerts ); + file->ReadBig( deform.numSilEdges ); + + srfTriangles_t tri; + memset( &tri, 0, sizeof( srfTriangles_t ) ); + + if ( deform.numOutputVerts > 0 ) { + R_AllocStaticTriSurfVerts( &tri, deform.numOutputVerts ); + deform.verts = tri.verts; + file->ReadBigArray( deform.verts, deform.numOutputVerts ); + } + + if ( deform.numIndexes > 0 ) { + R_AllocStaticTriSurfIndexes( &tri, deform.numIndexes ); + R_AllocStaticTriSurfSilIndexes( &tri, deform.numIndexes ); + deform.indexes = tri.indexes; + deform.silIndexes = tri.silIndexes; + file->ReadBigArray( deform.indexes, deform.numIndexes ); + file->ReadBigArray( deform.silIndexes, deform.numIndexes ); + } + + if ( deform.numMirroredVerts > 0 ) { + R_AllocStaticTriSurfMirroredVerts( &tri, deform.numMirroredVerts ); + deform.mirroredVerts = tri.mirroredVerts; + file->ReadBigArray( deform.mirroredVerts, deform.numMirroredVerts ); + } + + if ( deform.numDupVerts > 0 ) { + R_AllocStaticTriSurfDupVerts( &tri, deform.numDupVerts ); + deform.dupVerts = tri.dupVerts; + file->ReadBigArray( deform.dupVerts, deform.numDupVerts * 2 ); + } + + if ( deform.numSilEdges > 0 ) { + R_AllocStaticTriSurfSilEdges( &tri, deform.numSilEdges ); + deform.silEdges = tri.silEdges; + assert( deform.silEdges != NULL ); + for ( int j = 0; j < deform.numSilEdges; j++ ) { + file->ReadBig( deform.silEdges[j].p1 ); + file->ReadBig( deform.silEdges[j].p2 ); + file->ReadBig( deform.silEdges[j].v1 ); + file->ReadBig( deform.silEdges[j].v2 ); + } + } + + idShadowVertSkinned * shadowVerts = (idShadowVertSkinned *) Mem_Alloc( ALIGN( deform.numOutputVerts * 2 * sizeof( idShadowVertSkinned ), 16 ), TAG_MODEL ); + idShadowVertSkinned::CreateShadowCache( shadowVerts, deform.verts, deform.numOutputVerts ); + + deform.staticAmbientCache = vertexCache.AllocStaticVertex( deform.verts, ALIGN( deform.numOutputVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + deform.staticIndexCache = vertexCache.AllocStaticIndex( deform.indexes, ALIGN( deform.numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + deform.staticShadowCache = vertexCache.AllocStaticVertex( shadowVerts, ALIGN( deform.numOutputVerts * 2 * sizeof( idShadowVertSkinned ), VERTEX_CACHE_ALIGN ) ); + + Mem_Free( shadowVerts ); + + file->ReadBig( meshes[i].surfaceNum ); + } + + return true; +} + +/* +======================== +idRenderModelMD5::WriteBinaryModel +======================== +*/ +void idRenderModelMD5::WriteBinaryModel( idFile * file, ID_TIME_T *_timeStamp ) const { + + idRenderModelStatic::WriteBinaryModel( file ); + + if ( file == NULL ) { + return; + } + + file->WriteBig( MD5B_MAGIC ); + + file->WriteBig( joints.Num() ); + for ( int i = 0; i < joints.Num(); i++ ) { + file->WriteString( joints[i].name ); + int offset = -1; + if ( joints[i].parent != NULL ) { + offset = joints[i].parent - joints.Ptr(); + } + file->WriteBig( offset ); + } + + file->WriteBig( defaultPose.Num() ); + for ( int i = 0; i < defaultPose.Num(); i++ ) { + file->WriteBig( defaultPose[i].q.x ); + file->WriteBig( defaultPose[i].q.y ); + file->WriteBig( defaultPose[i].q.z ); + file->WriteBig( defaultPose[i].q.w ); + file->WriteVec3( defaultPose[i].t ); + } + + file->WriteBig( invertedDefaultPose.Num() ); + for ( int i = 0; i < invertedDefaultPose.Num(); i++ ) { + file->WriteBigArray( invertedDefaultPose[ i ].ToFloatPtr(), JOINTMAT_TYPESIZE ); + } + + file->WriteBig( meshes.Num() ); + for ( int i = 0; i < meshes.Num(); i++ ) { + + if ( meshes[i].shader != NULL && meshes[i].shader->GetName() != NULL ) { + file->WriteString( meshes[i].shader->GetName() ); + } else { + file->WriteString( "" ); + } + + file->WriteBig( meshes[i].numVerts ); + file->WriteBig( meshes[i].numTris ); + + file->WriteBig( meshes[i].numMeshJoints ); + file->WriteBigArray( meshes[i].meshJoints, meshes[i].numMeshJoints ); + file->WriteBig( meshes[i].maxJointVertDist ); + + deformInfo_t & deform = *meshes[i].deformInfo; + + file->WriteBig( deform.numSourceVerts ); + file->WriteBig( deform.numOutputVerts ); + file->WriteBig( deform.numIndexes ); + file->WriteBig( deform.numMirroredVerts ); + file->WriteBig( deform.numDupVerts ); + file->WriteBig( deform.numSilEdges ); + + if ( deform.numOutputVerts > 0 ) { + file->WriteBigArray( deform.verts, deform.numOutputVerts ); + } + + if ( deform.numIndexes > 0 ) { + file->WriteBigArray( deform.indexes, deform.numIndexes ); + file->WriteBigArray( deform.silIndexes, deform.numIndexes ); + } + + if ( deform.numMirroredVerts > 0 ) { + file->WriteBigArray( deform.mirroredVerts, deform.numMirroredVerts ); + } + + if ( deform.numDupVerts > 0 ) { + file->WriteBigArray( deform.dupVerts, deform.numDupVerts * 2 ); + } + + if ( deform.numSilEdges > 0 ) { + for ( int j = 0; j < deform.numSilEdges; j++ ) { + file->WriteBig( deform.silEdges[j].p1 ); + file->WriteBig( deform.silEdges[j].p2 ); + file->WriteBig( deform.silEdges[j].v1 ); + file->WriteBig( deform.silEdges[j].v2 ); + } + } + + file->WriteBig( meshes[i].surfaceNum ); + } +} + +/* +==================== +idRenderModelMD5::LoadModel + +used for initial loads, reloadModel, and reloading the data of purged models +Upon exit, the model will absolutely be valid, but possibly as a default model +==================== +*/ +void idRenderModelMD5::LoadModel() { + + int version; + int num; + int parentNum; + idToken token; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + + if ( !purged ) { + PurgeModel(); + } + purged = false; + + if ( !parser.LoadFile( name ) ) { + MakeDefaultModel(); + return; + } + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // + // skip commandline + // + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + num = parser.ParseInt(); + joints.SetGranularity( 1 ); + joints.SetNum( num ); + defaultPose.SetGranularity( 1 ); + defaultPose.SetNum( num ); + + // parse num meshes + parser.ExpectTokenString( "numMeshes" ); + num = parser.ParseInt(); + if ( num < 0 ) { + parser.Error( "Invalid size: %d", num ); + } + meshes.SetGranularity( 1 ); + meshes.SetNum( num ); + + // + // parse joints + // + parser.ExpectTokenString( "joints" ); + parser.ExpectTokenString( "{" ); + idJointMat *poseMat = ( idJointMat * )_alloca16( joints.Num() * sizeof( poseMat[0] ) ); + for( int i = 0; i < joints.Num(); i++ ) { + idMD5Joint * joint = &joints[i]; + idJointQuat * pose = &defaultPose[i]; + + ParseJoint( parser, joint, pose ); + poseMat[ i ].SetRotation( pose->q.ToMat3() ); + poseMat[ i ].SetTranslation( pose->t ); + if ( joint->parent ) { + parentNum = joint->parent - joints.Ptr(); + pose->q = ( poseMat[ i ].ToMat3() * poseMat[ parentNum ].ToMat3().Transpose() ).ToQuat(); + pose->t = ( poseMat[ i ].ToVec3() - poseMat[ parentNum ].ToVec3() ) * poseMat[ parentNum ].ToMat3().Transpose(); + } + } + parser.ExpectTokenString( "}" ); + + //----------------------------------------- + // create the inverse of the base pose joints to support tech6 style deformation + // of base pose vertexes, normals, and tangents. + // + // vertex * joints * inverseJoints == vertex when joints is the base pose + // When the joints are in another pose, it gives the animated vertex position + //----------------------------------------- + invertedDefaultPose.SetNum( SIMD_ROUND_JOINTS( joints.Num() ) ); + for ( int i = 0; i < joints.Num(); i++ ) { + invertedDefaultPose[i] = poseMat[i]; + invertedDefaultPose[i].Invert(); + } + SIMD_INIT_LAST_JOINT( invertedDefaultPose.Ptr(), joints.Num() ); + + for ( int i = 0; i < meshes.Num(); i++ ) { + parser.ExpectTokenString( "mesh" ); + meshes[i].ParseMesh( parser, defaultPose.Num(), poseMat ); + } + + // calculate the bounds of the model + bounds.Clear(); + for ( int i = 0; i < meshes.Num(); i++ ) { + idBounds meshBounds; + meshes[i].CalculateBounds( poseMat, meshBounds ); + bounds.AddBounds( meshBounds ); + } + + // set the timestamp for reloadmodels + fileSystem->ReadFile( name, NULL, &timeStamp ); + + common->UpdateLevelLoadPacifier(); +} + +/* +============== +idRenderModelMD5::Print +============== +*/ +void idRenderModelMD5::Print() const { + common->Printf( "%s\n", name.c_str() ); + common->Printf( "Dynamic model.\n" ); + common->Printf( "Generated smooth normals.\n" ); + common->Printf( " verts tris weights material\n" ); + int totalVerts = 0; + int totalTris = 0; + const idMD5Mesh * mesh = meshes.Ptr(); + for ( int i = 0; i < meshes.Num(); i++, mesh++ ) { + totalVerts += mesh->NumVerts(); + totalTris += mesh->NumTris(); + common->Printf( "%2i: %5i %5i %s\n", i, mesh->NumVerts(), mesh->NumTris(), mesh->shader->GetName() ); + } + common->Printf( "-----\n" ); + common->Printf( "%4i verts.\n", totalVerts ); + common->Printf( "%4i tris.\n", totalTris ); + common->Printf( "%4i joints.\n", joints.Num() ); +} + +/* +============== +idRenderModelMD5::List +============== +*/ +void idRenderModelMD5::List() const { + int i; + const idMD5Mesh *mesh; + int totalTris = 0; + int totalVerts = 0; + + for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + totalTris += mesh->numTris; + totalVerts += mesh->NumVerts(); + } + common->Printf( " %4ik %3i %4i %4i %s(MD5)", Memory()/1024, meshes.Num(), totalVerts, totalTris, Name() ); + + if ( defaulted ) { + common->Printf( " (DEFAULTED)" ); + } + + common->Printf( "\n" ); +} + +/* +==================== +idRenderModelMD5::Bounds + +This calculates a rough bounds by using the joint radii without +transforming all the points +==================== +*/ +idBounds idRenderModelMD5::Bounds( const renderEntity_t *ent ) const { + if ( ent == NULL ) { + // this is the bounds for the reference pose + return bounds; + } + + return ent->bounds; +} + +/* +==================== +idRenderModelMD5::DrawJoints +==================== +*/ +void idRenderModelMD5::DrawJoints( const renderEntity_t *ent, const viewDef_t *view ) const { + int i; + int num; + idVec3 pos; + const idJointMat *joint; + const idMD5Joint *md5Joint; + int parentNum; + + num = ent->numJoints; + joint = ent->joints; + md5Joint = joints.Ptr(); + for( i = 0; i < num; i++, joint++, md5Joint++ ) { + pos = ent->origin + joint->ToVec3() * ent->axis; + if ( md5Joint->parent ) { + parentNum = md5Joint->parent - joints.Ptr(); + common->RW()->DebugLine( colorWhite, ent->origin + ent->joints[ parentNum ].ToVec3() * ent->axis, pos ); + } + + common->RW()->DebugLine( colorRed, pos, pos + joint->ToMat3()[ 0 ] * 2.0f * ent->axis ); + common->RW()->DebugLine( colorGreen,pos, pos + joint->ToMat3()[ 1 ] * 2.0f * ent->axis ); + common->RW()->DebugLine( colorBlue, pos, pos + joint->ToMat3()[ 2 ] * 2.0f * ent->axis ); + } + + idBounds bounds; + + bounds.FromTransformedBounds( ent->bounds, vec3_zero, ent->axis ); + common->RW()->DebugBounds( colorMagenta, bounds, ent->origin ); + + if ( ( r_jointNameScale.GetFloat() != 0.0f ) && ( bounds.Expand( 128.0f ).ContainsPoint( view->renderView.vieworg - ent->origin ) ) ) { + idVec3 offset( 0, 0, r_jointNameOffset.GetFloat() ); + float scale; + + scale = r_jointNameScale.GetFloat(); + joint = ent->joints; + num = ent->numJoints; + for( i = 0; i < num; i++, joint++ ) { + pos = ent->origin + joint->ToVec3() * ent->axis; + common->RW()->DrawText( joints[ i ].name, pos + offset, scale, colorWhite, view->renderView.viewaxis, 1 ); + } + } +} + +/* +==================== +TransformJoints +==================== +*/ +static void TransformJoints( idJointMat *__restrict outJoints, const int numJoints, const idJointMat *__restrict inJoints1, const idJointMat *__restrict inJoints2 ) { + + float * outFloats = outJoints->ToFloatPtr(); + const float * inFloats1 = inJoints1->ToFloatPtr(); + const float * inFloats2 = inJoints2->ToFloatPtr(); + + assert_16_byte_aligned( outFloats ); + assert_16_byte_aligned( inFloats1 ); + assert_16_byte_aligned( inFloats2 ); + + + const __m128 mask_keep_last = __m128c( _mm_set_epi32( 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000 ) ); + + for ( int i = 0; i < numJoints; i += 2, inFloats1 += 2 * 12, inFloats2 += 2 * 12, outFloats += 2 * 12 ) { + __m128 m1a0 = _mm_load_ps( inFloats1 + 0 * 12 + 0 ); + __m128 m1b0 = _mm_load_ps( inFloats1 + 0 * 12 + 4 ); + __m128 m1c0 = _mm_load_ps( inFloats1 + 0 * 12 + 8 ); + __m128 m1a1 = _mm_load_ps( inFloats1 + 1 * 12 + 0 ); + __m128 m1b1 = _mm_load_ps( inFloats1 + 1 * 12 + 4 ); + __m128 m1c1 = _mm_load_ps( inFloats1 + 1 * 12 + 8 ); + + __m128 m2a0 = _mm_load_ps( inFloats2 + 0 * 12 + 0 ); + __m128 m2b0 = _mm_load_ps( inFloats2 + 0 * 12 + 4 ); + __m128 m2c0 = _mm_load_ps( inFloats2 + 0 * 12 + 8 ); + __m128 m2a1 = _mm_load_ps( inFloats2 + 1 * 12 + 0 ); + __m128 m2b1 = _mm_load_ps( inFloats2 + 1 * 12 + 4 ); + __m128 m2c1 = _mm_load_ps( inFloats2 + 1 * 12 + 8 ); + + __m128 tj0 = _mm_and_ps( m1a0, mask_keep_last ); + __m128 tk0 = _mm_and_ps( m1b0, mask_keep_last ); + __m128 tl0 = _mm_and_ps( m1c0, mask_keep_last ); + __m128 tj1 = _mm_and_ps( m1a1, mask_keep_last ); + __m128 tk1 = _mm_and_ps( m1b1, mask_keep_last ); + __m128 tl1 = _mm_and_ps( m1c1, mask_keep_last ); + + __m128 ta0 = _mm_splat_ps( m1a0, 0 ); + __m128 td0 = _mm_splat_ps( m1b0, 0 ); + __m128 tg0 = _mm_splat_ps( m1c0, 0 ); + __m128 ta1 = _mm_splat_ps( m1a1, 0 ); + __m128 td1 = _mm_splat_ps( m1b1, 0 ); + __m128 tg1 = _mm_splat_ps( m1c1, 0 ); + + __m128 ra0 = _mm_add_ps( tj0, _mm_mul_ps( ta0, m2a0 ) ); + __m128 rd0 = _mm_add_ps( tk0, _mm_mul_ps( td0, m2a0 ) ); + __m128 rg0 = _mm_add_ps( tl0, _mm_mul_ps( tg0, m2a0 ) ); + __m128 ra1 = _mm_add_ps( tj1, _mm_mul_ps( ta1, m2a1 ) ); + __m128 rd1 = _mm_add_ps( tk1, _mm_mul_ps( td1, m2a1 ) ); + __m128 rg1 = _mm_add_ps( tl1, _mm_mul_ps( tg1, m2a1 ) ); + + __m128 tb0 = _mm_splat_ps( m1a0, 1 ); + __m128 te0 = _mm_splat_ps( m1b0, 1 ); + __m128 th0 = _mm_splat_ps( m1c0, 1 ); + __m128 tb1 = _mm_splat_ps( m1a1, 1 ); + __m128 te1 = _mm_splat_ps( m1b1, 1 ); + __m128 th1 = _mm_splat_ps( m1c1, 1 ); + + __m128 rb0 = _mm_add_ps( ra0, _mm_mul_ps( tb0, m2b0 ) ); + __m128 re0 = _mm_add_ps( rd0, _mm_mul_ps( te0, m2b0 ) ); + __m128 rh0 = _mm_add_ps( rg0, _mm_mul_ps( th0, m2b0 ) ); + __m128 rb1 = _mm_add_ps( ra1, _mm_mul_ps( tb1, m2b1 ) ); + __m128 re1 = _mm_add_ps( rd1, _mm_mul_ps( te1, m2b1 ) ); + __m128 rh1 = _mm_add_ps( rg1, _mm_mul_ps( th1, m2b1 ) ); + + __m128 tc0 = _mm_splat_ps( m1a0, 2 ); + __m128 tf0 = _mm_splat_ps( m1b0, 2 ); + __m128 ti0 = _mm_splat_ps( m1c0, 2 ); + __m128 tf1 = _mm_splat_ps( m1b1, 2 ); + __m128 ti1 = _mm_splat_ps( m1c1, 2 ); + __m128 tc1 = _mm_splat_ps( m1a1, 2 ); + + __m128 rc0 = _mm_add_ps( rb0, _mm_mul_ps( tc0, m2c0 ) ); + __m128 rf0 = _mm_add_ps( re0, _mm_mul_ps( tf0, m2c0 ) ); + __m128 ri0 = _mm_add_ps( rh0, _mm_mul_ps( ti0, m2c0 ) ); + __m128 rc1 = _mm_add_ps( rb1, _mm_mul_ps( tc1, m2c1 ) ); + __m128 rf1 = _mm_add_ps( re1, _mm_mul_ps( tf1, m2c1 ) ); + __m128 ri1 = _mm_add_ps( rh1, _mm_mul_ps( ti1, m2c1 ) ); + + _mm_store_ps( outFloats + 0 * 12 + 0, rc0 ); + _mm_store_ps( outFloats + 0 * 12 + 4, rf0 ); + _mm_store_ps( outFloats + 0 * 12 + 8, ri0 ); + _mm_store_ps( outFloats + 1 * 12 + 0, rc1 ); + _mm_store_ps( outFloats + 1 * 12 + 4, rf1 ); + _mm_store_ps( outFloats + 1 * 12 + 8, ri1 ); + } + +} + +/* +==================== +idRenderModelMD5::InstantiateDynamicModel +==================== +*/ +idRenderModel *idRenderModelMD5::InstantiateDynamicModel( const struct renderEntity_s *ent, const viewDef_t *view, idRenderModel *cachedModel ) { + if ( cachedModel != NULL && !r_useCachedDynamicModels.GetBool() ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( purged ) { + common->DWarning( "model %s instantiated while purged", Name() ); + LoadModel(); + } + + if ( !ent->joints ) { + common->Printf( "idRenderModelMD5::InstantiateDynamicModel: NULL joints on renderEntity for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } else if ( ent->numJoints != joints.Num() ) { + common->Printf( "idRenderModelMD5::InstantiateDynamicModel: renderEntity has different number of joints than model for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } + + tr.pc.c_generateMd5++; + + idRenderModelStatic * staticModel; + if ( cachedModel != NULL ) { + assert( dynamic_cast(cachedModel) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), MD5_SnapshotName ) == 0 ); + staticModel = static_cast(cachedModel); + } else { + staticModel = new (TAG_MODEL) idRenderModelStatic; + staticModel->InitEmpty( MD5_SnapshotName ); + } + + staticModel->bounds.Clear(); + + if ( r_showSkel.GetInteger() ) { + if ( ( view != NULL ) && ( !r_skipSuppress.GetBool() || !ent->suppressSurfaceInViewID || ( ent->suppressSurfaceInViewID != view->renderView.viewID ) ) ) { + // only draw the skeleton + DrawJoints( ent, view ); + } + + if ( r_showSkel.GetInteger() > 1 ) { + // turn off the model when showing the skeleton + staticModel->InitEmpty( MD5_SnapshotName ); + return staticModel; + } + } + + // update the GPU joints array + const int numInvertedJoints = SIMD_ROUND_JOINTS( joints.Num() ); + if ( staticModel->jointsInverted == NULL ) { + staticModel->numInvertedJoints = numInvertedJoints; + const int alignment = glConfig.uniformBufferOffsetAlignment; + staticModel->jointsInverted = (idJointMat *)Mem_ClearedAlloc( ALIGN( numInvertedJoints * sizeof( idJointMat ), alignment ), TAG_JOINTMAT ); + staticModel->jointsInvertedBuffer = 0; + } else { + assert( staticModel->numInvertedJoints == numInvertedJoints ); + } + + TransformJoints( staticModel->jointsInverted, joints.Num(), ent->joints, invertedDefaultPose.Ptr() ); + + // create all the surfaces + idMD5Mesh * mesh = meshes.Ptr(); + for ( int i = 0; i < meshes.Num(); i++, mesh++ ) { + // avoid deforming the surface if it will be a nodraw due to a skin remapping + const idMaterial *shader = mesh->shader; + + shader = R_RemapShaderBySkin( shader, ent->customSkin, ent->customShader ); + + if ( !shader || ( !shader->IsDrawn() && !shader->SurfaceCastsShadow() ) ) { + staticModel->DeleteSurfaceWithId( i ); + mesh->surfaceNum = -1; + continue; + } + + modelSurface_t *surf; + + int surfaceNum = 0; + if ( staticModel->FindSurfaceWithId( i, surfaceNum ) ) { + mesh->surfaceNum = surfaceNum; + surf = &staticModel->surfaces[surfaceNum]; + } else { + mesh->surfaceNum = staticModel->NumSurfaces(); + surf = &staticModel->surfaces.Alloc(); + surf->geometry = NULL; + surf->shader = NULL; + surf->id = i; + } + + mesh->UpdateSurface( ent, ent->joints, staticModel->jointsInverted, surf ); + assert( surf->geometry != NULL ); // to get around compiler warning + + // the deformation of the tangents can be deferred until each surface is added to the view + surf->geometry->staticModelWithJoints = staticModel; + + staticModel->bounds.AddBounds( surf->geometry->bounds ); + } + + return staticModel; +} + +/* +==================== +idRenderModelMD5::IsDynamicModel +==================== +*/ +dynamicModel_t idRenderModelMD5::IsDynamicModel() const { + return DM_CACHED; +} + +/* +==================== +idRenderModelMD5::NumJoints +==================== +*/ +int idRenderModelMD5::NumJoints() const { + return joints.Num(); +} + +/* +==================== +idRenderModelMD5::GetJoints +==================== +*/ +const idMD5Joint *idRenderModelMD5::GetJoints() const { + return joints.Ptr(); +} + +/* +==================== +idRenderModelMD5::GetDefaultPose +==================== +*/ +const idJointQuat *idRenderModelMD5::GetDefaultPose() const { + return defaultPose.Ptr(); +} + +/* +==================== +idRenderModelMD5::GetJointHandle +==================== +*/ +jointHandle_t idRenderModelMD5::GetJointHandle( const char *name ) const { + const idMD5Joint *joint = joints.Ptr(); + for ( int i = 0; i < joints.Num(); i++, joint++ ) { + if ( idStr::Icmp( joint->name.c_str(), name ) == 0 ) { + return ( jointHandle_t )i; + } + } + + return INVALID_JOINT; +} + +/* +===================== +idRenderModelMD5::GetJointName +===================== +*/ +const char *idRenderModelMD5::GetJointName( jointHandle_t handle ) const { + if ( ( handle < 0 ) || ( handle >= joints.Num() ) ) { + return ""; + } + + return joints[ handle ].name; +} + +/* +==================== +idRenderModelMD5::NearestJoint +==================== +*/ +int idRenderModelMD5::NearestJoint( int surfaceNum, int a, int b, int c ) const { + if ( surfaceNum > meshes.Num() ) { + common->Error( "idRenderModelMD5::NearestJoint: surfaceNum > meshes.Num()" ); + } + + const idMD5Mesh *mesh = meshes.Ptr(); + for ( int i = 0; i < meshes.Num(); i++, mesh++ ) { + if ( mesh->surfaceNum == surfaceNum ) { + return mesh->NearestJoint( a, b, c ); + } + } + return 0; +} + +/* +==================== +idRenderModelMD5::TouchData + +models that are already loaded at level start time +will still touch their materials to make sure they +are kept loaded +==================== +*/ +void idRenderModelMD5::TouchData() { + for ( int i = 0; i < meshes.Num(); i++ ) { + declManager->FindMaterial( meshes[i].shader->GetName() ); + } +} + +/* +=================== +idRenderModelMD5::PurgeModel + +frees all the data, but leaves the class around for dangling references, +which can regenerate the data with LoadModel() +=================== +*/ +void idRenderModelMD5::PurgeModel() { + purged = true; + joints.Clear(); + defaultPose.Clear(); + meshes.Clear(); +} + +/* +=================== +idRenderModelMD5::Memory +=================== +*/ +int idRenderModelMD5::Memory() const { + int total = sizeof( *this ); + total += joints.MemoryUsed() + defaultPose.MemoryUsed() + meshes.MemoryUsed(); + + // count up strings + for ( int i = 0; i < joints.Num(); i++ ) { + total += joints[i].name.DynamicMemoryUsed(); + } + + // count up meshes + for ( int i = 0; i < meshes.Num(); i++ ) { + const idMD5Mesh *mesh = &meshes[i]; + + total += mesh->numMeshJoints * sizeof( mesh->meshJoints[0] ); + + // sum up deform info + total += sizeof( mesh->deformInfo ); + total += R_DeformInfoMemoryUsed( mesh->deformInfo ); + } + return total; +} diff --git a/neo/renderer/Model_prt.cpp b/neo/renderer/Model_prt.cpp new file mode 100644 index 00000000..e4e5ca6d --- /dev/null +++ b/neo/renderer/Model_prt.cpp @@ -0,0 +1,287 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +static const char *parametricParticle_SnapshotName = "_ParametricParticle_Snapshot_"; + +/* +==================== +idRenderModelPrt::idRenderModelPrt +==================== +*/ +idRenderModelPrt::idRenderModelPrt() { + particleSystem = NULL; +} + +/* +==================== +idRenderModelPrt::InitFromFile +==================== +*/ +void idRenderModelPrt::InitFromFile( const char *fileName ) { + name = fileName; + particleSystem = static_cast( declManager->FindType( DECL_PARTICLE, fileName ) ); +} + +/* +================= +idRenderModelPrt::TouchData +================= +*/ +void idRenderModelPrt::TouchData() { + // Ensure our particle system is added to the list of referenced decls + particleSystem = static_cast( declManager->FindType( DECL_PARTICLE, name ) ); +} + +/* +==================== +idRenderModelPrt::InstantiateDynamicModel +==================== +*/ +idRenderModel *idRenderModelPrt::InstantiateDynamicModel( const struct renderEntity_s *renderEntity, const viewDef_t *viewDef, idRenderModel *cachedModel ) { + idRenderModelStatic *staticModel; + + if ( cachedModel && !r_useCachedDynamicModels.GetBool() ) { + delete cachedModel; + cachedModel = NULL; + } + + // this may be triggered by a model trace or other non-view related source, to which we should look like an empty model + if ( renderEntity == NULL || viewDef == NULL ) { + delete cachedModel; + return NULL; + } + + if ( r_skipParticles.GetBool() ) { + delete cachedModel; + return NULL; + } + + /* + // if the entire system has faded out + if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] && viewDef->renderView.time * 0.001f >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] ) { + delete cachedModel; + return NULL; + } + */ + + if ( cachedModel != NULL ) { + + assert( dynamic_cast(cachedModel) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), parametricParticle_SnapshotName ) == 0 ); + + staticModel = static_cast(cachedModel); + + } else { + + staticModel = new (TAG_MODEL) idRenderModelStatic; + staticModel->InitEmpty( parametricParticle_SnapshotName ); + } + + particleGen_t g; + + g.renderEnt = renderEntity; + g.renderView = &viewDef->renderView; + g.origin.Zero(); + g.axis.Identity(); + + for ( int stageNum = 0; stageNum < particleSystem->stages.Num(); stageNum++ ) { + idParticleStage *stage = particleSystem->stages[stageNum]; + + if ( !stage->material ) { + continue; + } + if ( !stage->cycleMsec ) { + continue; + } + if ( stage->hidden ) { // just for gui particle editor use + staticModel->DeleteSurfaceWithId( stageNum ); + continue; + } + + idRandom steppingRandom, steppingRandom2; + + int stageAge = g.renderView->time[renderEntity->timeGroup] + renderEntity->shaderParms[SHADERPARM_TIMEOFFSET] * 1000 - stage->timeOffset * 1000; + int stageCycle = stageAge / stage->cycleMsec; + + // some particles will be in this cycle, some will be in the previous cycle + steppingRandom.SetSeed( (( stageCycle << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); + steppingRandom2.SetSeed( (( (stageCycle-1) << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); + + int count = stage->totalParticles * stage->NumQuadsPerParticle(); + + int surfaceNum; + modelSurface_t *surf; + + if ( staticModel->FindSurfaceWithId( stageNum, surfaceNum ) ) { + surf = &staticModel->surfaces[surfaceNum]; + R_FreeStaticTriSurfVertexCaches( surf->geometry ); + } else { + surf = &staticModel->surfaces.Alloc(); + surf->id = stageNum; + surf->shader = stage->material; + surf->geometry = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( surf->geometry, 4 * count ); + R_AllocStaticTriSurfIndexes( surf->geometry, 6 * count ); + } + + int numVerts = 0; + idDrawVert *verts = surf->geometry->verts; + + for ( int index = 0; index < stage->totalParticles; index++ ) { + g.index = index; + + // bump the random + steppingRandom.RandomInt(); + steppingRandom2.RandomInt(); + + // calculate local age for this index + int bunchOffset = stage->particleLife * 1000 * stage->spawnBunching * index / stage->totalParticles; + + int particleAge = stageAge - bunchOffset; + int particleCycle = particleAge / stage->cycleMsec; + if ( particleCycle < 0 ) { + // before the particleSystem spawned + continue; + } + if ( stage->cycles && particleCycle >= stage->cycles ) { + // cycled systems will only run cycle times + continue; + } + + if ( particleCycle == stageCycle ) { + g.random = steppingRandom; + } else { + g.random = steppingRandom2; + } + + int inCycleTime = particleAge - particleCycle * stage->cycleMsec; + + if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] && + g.renderView->time[renderEntity->timeGroup] - inCycleTime >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME]*1000 ) { + // don't fire any more particles + continue; + } + + // supress particles before or after the age clamp + g.frac = (float)inCycleTime / ( stage->particleLife * 1000 ); + if ( g.frac < 0.0f ) { + // yet to be spawned + continue; + } + if ( g.frac > 1.0f ) { + // this particle is in the deadTime band + continue; + } + + // this is needed so aimed particles can calculate origins at different times + g.originalRandom = g.random; + + g.age = g.frac * stage->particleLife; + + // if the particle doesn't get drawn because it is faded out or beyond a kill region, don't increment the verts + numVerts += stage->CreateParticle( &g, verts + numVerts ); + } + + // numVerts must be a multiple of 4 + assert( ( numVerts & 3 ) == 0 && numVerts <= 4 * count ); + + // build the indexes + int numIndexes = 0; + triIndex_t *indexes = surf->geometry->indexes; + for ( int i = 0; i < numVerts; i += 4 ) { + indexes[numIndexes+0] = i+0; + indexes[numIndexes+1] = i+2; + indexes[numIndexes+2] = i+3; + indexes[numIndexes+3] = i+0; + indexes[numIndexes+4] = i+3; + indexes[numIndexes+5] = i+1; + numIndexes += 6; + } + + surf->geometry->tangentsCalculated = false; + surf->geometry->numVerts = numVerts; + surf->geometry->numIndexes = numIndexes; + surf->geometry->bounds = stage->bounds; // just always draw the particles + } + + return staticModel; +} + +/* +==================== +idRenderModelPrt::IsDynamicModel +==================== +*/ +dynamicModel_t idRenderModelPrt::IsDynamicModel() const { + return DM_CONTINUOUS; +} + +/* +==================== +idRenderModelPrt::Bounds +==================== +*/ +idBounds idRenderModelPrt::Bounds( const struct renderEntity_s *ent ) const { + return particleSystem->bounds; +} + +/* +==================== +idRenderModelPrt::DepthHack +==================== +*/ +float idRenderModelPrt::DepthHack() const { + return particleSystem->depthHack; +} + +/* +==================== +idRenderModelPrt::Memory +==================== +*/ +int idRenderModelPrt::Memory() const { + int total = 0; + + total += idRenderModelStatic::Memory(); + + if ( particleSystem ) { + total += sizeof( *particleSystem ); + + for ( int i = 0; i < particleSystem->stages.Num(); i++ ) { + total += sizeof( particleSystem->stages[i] ); + } + } + + return total; +} diff --git a/neo/renderer/Model_sprite.cpp b/neo/renderer/Model_sprite.cpp new file mode 100644 index 00000000..0692ff42 --- /dev/null +++ b/neo/renderer/Model_sprite.cpp @@ -0,0 +1,194 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + + +/* + +A simple sprite model that always faces the view axis. + +*/ + +static const char *sprite_SnapshotName = "_sprite_Snapshot_"; + +/* +=============== +idRenderModelSprite::IsDynamicModel +=============== +*/ +dynamicModel_t idRenderModelSprite::IsDynamicModel() const { + return DM_CONTINUOUS; +} + +/* +=============== +idRenderModelSprite::IsLoaded +=============== +*/ +bool idRenderModelSprite::IsLoaded() const { + return true; +} + +/* +=============== +idRenderModelSprite::InstantiateDynamicModel +=============== +*/ +idRenderModel * idRenderModelSprite::InstantiateDynamicModel( const struct renderEntity_s *renderEntity, const viewDef_t *viewDef, idRenderModel *cachedModel ) { + idRenderModelStatic *staticModel; + srfTriangles_t *tri; + modelSurface_t surf; + + if ( cachedModel && !r_useCachedDynamicModels.GetBool() ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( renderEntity == NULL || viewDef == NULL ) { + delete cachedModel; + return NULL; + } + + if ( cachedModel != NULL ) { + + assert( dynamic_cast( cachedModel ) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), sprite_SnapshotName ) == 0 ); + + staticModel = static_cast( cachedModel ); + surf = *staticModel->Surface( 0 ); + tri = surf.geometry; + + } else { + + staticModel = new (TAG_MODEL) idRenderModelStatic; + staticModel->InitEmpty( sprite_SnapshotName ); + + tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, 4 ); + R_AllocStaticTriSurfIndexes( tri, 6 ); + + tri->verts[ 0 ].Clear(); + tri->verts[ 0 ].SetNormal( 1.0f, 0.0f, 0.0f ); + tri->verts[ 0 ].SetTangent( 0.0f, 1.0f, 0.0f ); + tri->verts[ 0 ].SetBiTangent( 0.0f, 0.0f, 1.0f ); + tri->verts[ 0 ].SetTexCoord( 0.0f, 0.0f ); + + tri->verts[ 1 ].Clear(); + tri->verts[ 1 ].SetNormal( 1.0f, 0.0f, 0.0f ); + tri->verts[ 1 ].SetTangent( 0.0f, 1.0f, 0.0f ); + tri->verts[ 1 ].SetBiTangent( 0.0f, 0.0f, 1.0f ); + tri->verts[ 1 ].SetTexCoord( 1.0f, 0.0f ); + + tri->verts[ 2 ].Clear(); + tri->verts[ 2 ].SetNormal( 1.0f, 0.0f, 0.0f ); + tri->verts[ 2 ].SetTangent( 0.0f, 1.0f, 0.0f ); + tri->verts[ 2 ].SetBiTangent( 0.0f, 0.0f, 1.0f ); + tri->verts[ 2 ].SetTexCoord( 1.0f, 1.0f ); + + tri->verts[ 3 ].Clear(); + tri->verts[ 3 ].SetNormal( 1.0f, 0.0f, 0.0f ); + tri->verts[ 3 ].SetTangent( 0.0f, 1.0f, 0.0f ); + tri->verts[ 3 ].SetBiTangent( 0.0f, 0.0f, 1.0f ); + tri->verts[ 3 ].SetTexCoord( 0.0f, 1.0f ); + + tri->indexes[ 0 ] = 0; + tri->indexes[ 1 ] = 1; + tri->indexes[ 2 ] = 3; + tri->indexes[ 3 ] = 1; + tri->indexes[ 4 ] = 2; + tri->indexes[ 5 ] = 3; + + tri->numVerts = 4; + tri->numIndexes = 6; + + surf.geometry = tri; + surf.id = 0; + surf.shader = tr.defaultMaterial; + staticModel->AddSurface( surf ); + } + + int red = idMath::Ftoi( renderEntity->shaderParms[ SHADERPARM_RED ] * 255.0f ); + int green = idMath::Ftoi( renderEntity->shaderParms[ SHADERPARM_GREEN ] * 255.0f ); + int blue = idMath::Ftoi( renderEntity->shaderParms[ SHADERPARM_BLUE ] * 255.0f ); + int alpha = idMath::Ftoi( renderEntity->shaderParms[ SHADERPARM_ALPHA ] * 255.0f ); + + idVec3 right = idVec3( 0.0f, renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ] * 0.5f, 0.0f ); + idVec3 up = idVec3( 0.0f, 0.0f, renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] * 0.5f ); + + tri->verts[ 0 ].xyz = up + right; + tri->verts[ 0 ].color[ 0 ] = red; + tri->verts[ 0 ].color[ 1 ] = green; + tri->verts[ 0 ].color[ 2 ] = blue; + tri->verts[ 0 ].color[ 3 ] = alpha; + + tri->verts[ 1 ].xyz = up - right; + tri->verts[ 1 ].color[ 0 ] = red; + tri->verts[ 1 ].color[ 1 ] = green; + tri->verts[ 1 ].color[ 2 ] = blue; + tri->verts[ 1 ].color[ 3 ] = alpha; + + tri->verts[ 2 ].xyz = - right - up; + tri->verts[ 2 ].color[ 0 ] = red; + tri->verts[ 2 ].color[ 1 ] = green; + tri->verts[ 2 ].color[ 2 ] = blue; + tri->verts[ 2 ].color[ 3 ] = alpha; + + tri->verts[ 3 ].xyz = right - up; + tri->verts[ 3 ].color[ 0 ] = red; + tri->verts[ 3 ].color[ 1 ] = green; + tri->verts[ 3 ].color[ 2 ] = blue; + tri->verts[ 3 ].color[ 3 ] = alpha; + + R_BoundTriSurf( tri ); + + staticModel->bounds = tri->bounds; + + return staticModel; +} + +/* +=============== +idRenderModelSprite::Bounds +=============== +*/ +idBounds idRenderModelSprite::Bounds( const struct renderEntity_s *renderEntity ) const { + idBounds b; + + b.Zero(); + if ( renderEntity == NULL ) { + b.ExpandSelf( 8.0f ); + } else { + b.ExpandSelf( Max( renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ], renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] ) * 0.5f ); + } + return b; +} diff --git a/neo/renderer/OpenGL/gl_GraphicsAPIWrapper.cpp b/neo/renderer/OpenGL/gl_GraphicsAPIWrapper.cpp new file mode 100644 index 00000000..215b9078 --- /dev/null +++ b/neo/renderer/OpenGL/gl_GraphicsAPIWrapper.cpp @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../tr_local.h" + +/* +==================== +GL_SelectTexture +==================== +*/ +void GL_SelectTexture( int unit ) { + if ( backEnd.glState.currenttmu == unit ) { + return; + } + + if ( unit < 0 || unit >= glConfig.maxTextureImageUnits ) { + common->Warning( "GL_SelectTexture: unit = %i", unit ); + return; + } + + RENDERLOG_PRINTF( "GL_SelectTexture( %i );\n", unit ); + + backEnd.glState.currenttmu = unit; +} + +/* +==================== +GL_Cull + +This handles the flipping needed when the view being +rendered is a mirored view. +==================== +*/ +void GL_Cull( int cullType ) { + if ( backEnd.glState.faceCulling == cullType ) { + return; + } + + if ( cullType == CT_TWO_SIDED ) { + qglDisable( GL_CULL_FACE ); + } else { + if ( backEnd.glState.faceCulling == CT_TWO_SIDED ) { + qglEnable( GL_CULL_FACE ); + } + + if ( cullType == CT_BACK_SIDED ) { + if ( backEnd.viewDef->isMirror ) { + qglCullFace( GL_FRONT ); + } else { + qglCullFace( GL_BACK ); + } + } else { + if ( backEnd.viewDef->isMirror ) { + qglCullFace( GL_BACK ); + } else { + qglCullFace( GL_FRONT ); + } + } + } + + backEnd.glState.faceCulling = cullType; +} + +/* +==================== +GL_Scissor +==================== +*/ +void GL_Scissor( int x /* left*/, int y /* bottom */, int w, int h ) { + qglScissor( x, y, w, h ); +} + +/* +==================== +GL_Viewport +==================== +*/ +void GL_Viewport( int x /* left */, int y /* bottom */, int w, int h ) { + qglViewport( x, y, w, h ); +} + +/* +==================== +GL_PolygonOffset +==================== +*/ +void GL_PolygonOffset( float scale, float bias ) { + backEnd.glState.polyOfsScale = scale; + backEnd.glState.polyOfsBias = bias; + if ( backEnd.glState.glStateBits & GLS_POLYGON_OFFSET ) { + qglPolygonOffset( scale, bias ); + } +} + +/* +======================== +GL_DepthBoundsTest +======================== +*/ +void GL_DepthBoundsTest( const float zmin, const float zmax ) { + if ( !glConfig.depthBoundsTestAvailable || zmin > zmax ) { + return; + } + + if ( zmin == 0.0f && zmax == 0.0f ) { + qglDisable( GL_DEPTH_BOUNDS_TEST_EXT ); + } else { + qglEnable( GL_DEPTH_BOUNDS_TEST_EXT ); + qglDepthBoundsEXT( zmin, zmax ); + } +} + +/* +======================== +GL_StartDepthPass +======================== +*/ +void GL_StartDepthPass( const idScreenRect & rect ) { +} + +/* +======================== +GL_FinishDepthPass +======================== +*/ +void GL_FinishDepthPass() { +} + +/* +======================== +GL_GetDepthPassRect +======================== +*/ +void GL_GetDepthPassRect( idScreenRect & rect ) { + rect.Clear(); +} + +/* +==================== +GL_Color +==================== +*/ +void GL_Color( float * color ) { + if ( color == NULL ) { + return; + } + GL_Color( color[0], color[1], color[2], color[3] ); +} + +/* +==================== +GL_Color +==================== +*/ +void GL_Color( float r, float g, float b ) { + GL_Color( r, g, b, 1.0f ); +} + +/* +==================== +GL_Color +==================== +*/ +void GL_Color( float r, float g, float b, float a ) { + float parm[4]; + parm[0] = idMath::ClampFloat( 0.0f, 1.0f, r ); + parm[1] = idMath::ClampFloat( 0.0f, 1.0f, g ); + parm[2] = idMath::ClampFloat( 0.0f, 1.0f, b ); + parm[3] = idMath::ClampFloat( 0.0f, 1.0f, a ); + renderProgManager.SetRenderParm( RENDERPARM_COLOR, parm ); +} + +/* +======================== +GL_Clear +======================== +*/ +void GL_Clear( bool color, bool depth, bool stencil, byte stencilValue, float r, float g, float b, float a ) { + int clearFlags = 0; + if ( color ) { + qglClearColor( r, g, b, a ); + clearFlags |= GL_COLOR_BUFFER_BIT; + } + if ( depth ) { + clearFlags |= GL_DEPTH_BUFFER_BIT; + } + if ( stencil ) { + qglClearStencil( stencilValue ); + clearFlags |= GL_STENCIL_BUFFER_BIT; + } + qglClear( clearFlags ); +} + +/* +======================== +GL_SetDefaultState + +This should initialize all GL state that any part of the entire program +may touch, including the editor. +======================== +*/ +void GL_SetDefaultState() { + RENDERLOG_PRINTF( "--- GL_SetDefaultState ---\n" ); + + qglClearDepth( 1.0f ); + + // make sure our GL state vector is set correctly + memset( &backEnd.glState, 0, sizeof( backEnd.glState ) ); + GL_State( 0, true ); + + // These are changed by GL_Cull + qglCullFace( GL_FRONT_AND_BACK ); + qglEnable( GL_CULL_FACE ); + + // These are changed by GL_State + qglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + qglBlendFunc( GL_ONE, GL_ZERO ); + qglDepthMask( GL_TRUE ); + qglDepthFunc( GL_LESS ); + qglDisable( GL_STENCIL_TEST ); + qglDisable( GL_POLYGON_OFFSET_FILL ); + qglDisable( GL_POLYGON_OFFSET_LINE ); + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + // These should never be changed + qglShadeModel( GL_SMOOTH ); + qglEnable( GL_DEPTH_TEST ); + qglEnable( GL_BLEND ); + qglEnable( GL_SCISSOR_TEST ); + qglDrawBuffer( GL_BACK ); + qglReadBuffer( GL_BACK ); + + if ( r_useScissor.GetBool() ) { + qglScissor( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight() ); + } +} + +/* +==================== +GL_State + +This routine is responsible for setting the most commonly changed state +==================== +*/ +void GL_State( uint64 stateBits, bool forceGlState ) { + uint64 diff = stateBits ^ backEnd.glState.glStateBits; + + if ( !r_useStateCaching.GetBool() || forceGlState ) { + // make sure everything is set all the time, so we + // can see if our delta checking is screwing up + diff = 0xFFFFFFFFFFFFFFFF; + } else if ( diff == 0 ) { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_BITS ) { + switch ( stateBits & GLS_DEPTHFUNC_BITS ) { + case GLS_DEPTHFUNC_EQUAL: qglDepthFunc( GL_EQUAL ); break; + case GLS_DEPTHFUNC_ALWAYS: qglDepthFunc( GL_ALWAYS ); break; + case GLS_DEPTHFUNC_LESS: qglDepthFunc( GL_LEQUAL ); break; + case GLS_DEPTHFUNC_GREATER: qglDepthFunc( GL_GEQUAL ); break; + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) { + GLenum srcFactor = GL_ONE; + GLenum dstFactor = GL_ZERO; + + switch ( stateBits & GLS_SRCBLEND_BITS ) { + case GLS_SRCBLEND_ZERO: srcFactor = GL_ZERO; break; + case GLS_SRCBLEND_ONE: srcFactor = GL_ONE; break; + case GLS_SRCBLEND_DST_COLOR: srcFactor = GL_DST_COLOR; break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: srcFactor = GL_ONE_MINUS_DST_COLOR; break; + case GLS_SRCBLEND_SRC_ALPHA: srcFactor = GL_SRC_ALPHA; break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: srcFactor = GL_ONE_MINUS_SRC_ALPHA; break; + case GLS_SRCBLEND_DST_ALPHA: srcFactor = GL_DST_ALPHA; break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: srcFactor = GL_ONE_MINUS_DST_ALPHA; break; + default: + assert( !"GL_State: invalid src blend state bits\n" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) { + case GLS_DSTBLEND_ZERO: dstFactor = GL_ZERO; break; + case GLS_DSTBLEND_ONE: dstFactor = GL_ONE; break; + case GLS_DSTBLEND_SRC_COLOR: dstFactor = GL_SRC_COLOR; break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: dstFactor = GL_ONE_MINUS_SRC_COLOR; break; + case GLS_DSTBLEND_SRC_ALPHA: dstFactor = GL_SRC_ALPHA; break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: dstFactor = GL_ONE_MINUS_SRC_ALPHA; break; + case GLS_DSTBLEND_DST_ALPHA: dstFactor = GL_DST_ALPHA; break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: dstFactor = GL_ONE_MINUS_DST_ALPHA; break; + default: + assert( !"GL_State: invalid dst blend state bits\n" ); + break; + } + + // Only actually update GL's blend func if blending is enabled. + if ( srcFactor == GL_ONE && dstFactor == GL_ZERO ) { + qglDisable( GL_BLEND ); + } else { + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK ) { + if ( stateBits & GLS_DEPTHMASK ) { + qglDepthMask( GL_FALSE ); + } else { + qglDepthMask( GL_TRUE ); + } + } + + // + // check colormask + // + if ( diff & (GLS_REDMASK|GLS_GREENMASK|GLS_BLUEMASK|GLS_ALPHAMASK) ) { + GLboolean r = ( stateBits & GLS_REDMASK ) ? GL_FALSE : GL_TRUE; + GLboolean g = ( stateBits & GLS_GREENMASK ) ? GL_FALSE : GL_TRUE; + GLboolean b = ( stateBits & GLS_BLUEMASK ) ? GL_FALSE : GL_TRUE; + GLboolean a = ( stateBits & GLS_ALPHAMASK ) ? GL_FALSE : GL_TRUE; + qglColorMask( r, g, b, a ); + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) { + if ( stateBits & GLS_POLYMODE_LINE ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } else { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // polygon offset + // + if ( diff & GLS_POLYGON_OFFSET ) { + if ( stateBits & GLS_POLYGON_OFFSET ) { + qglPolygonOffset( backEnd.glState.polyOfsScale, backEnd.glState.polyOfsBias ); + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglEnable( GL_POLYGON_OFFSET_LINE ); + } else { + qglDisable( GL_POLYGON_OFFSET_FILL ); + qglDisable( GL_POLYGON_OFFSET_LINE ); + } + } + +#if !defined( USE_CORE_PROFILE ) + // + // alpha test + // + if ( diff & ( GLS_ALPHATEST_FUNC_BITS | GLS_ALPHATEST_FUNC_REF_BITS ) ) { + if ( ( stateBits & GLS_ALPHATEST_FUNC_BITS ) != 0 ) { + qglEnable( GL_ALPHA_TEST ); + + GLenum func = GL_ALWAYS; + switch ( stateBits & GLS_ALPHATEST_FUNC_BITS ) { + case GLS_ALPHATEST_FUNC_LESS: func = GL_LESS; break; + case GLS_ALPHATEST_FUNC_EQUAL: func = GL_EQUAL; break; + case GLS_ALPHATEST_FUNC_GREATER: func = GL_GEQUAL; break; + default: assert( false ); + } + GLclampf ref = ( ( stateBits & GLS_ALPHATEST_FUNC_REF_BITS ) >> GLS_ALPHATEST_FUNC_REF_SHIFT ) / (float)0xFF; + qglAlphaFunc( func, ref ); + } else { + qglDisable( GL_ALPHA_TEST ); + } + } +#endif + + // + // stencil + // + if ( diff & ( GLS_STENCIL_FUNC_BITS | GLS_STENCIL_OP_BITS ) ) { + if ( ( stateBits & ( GLS_STENCIL_FUNC_BITS | GLS_STENCIL_OP_BITS ) ) != 0 ) { + qglEnable( GL_STENCIL_TEST ); + } else { + qglDisable( GL_STENCIL_TEST ); + } + } + if ( diff & ( GLS_STENCIL_FUNC_BITS | GLS_STENCIL_FUNC_REF_BITS | GLS_STENCIL_FUNC_MASK_BITS ) ) { + GLuint ref = GLuint( ( stateBits & GLS_STENCIL_FUNC_REF_BITS ) >> GLS_STENCIL_FUNC_REF_SHIFT ); + GLuint mask = GLuint( ( stateBits & GLS_STENCIL_FUNC_MASK_BITS ) >> GLS_STENCIL_FUNC_MASK_SHIFT ); + GLenum func = 0; + + switch ( stateBits & GLS_STENCIL_FUNC_BITS ) { + case GLS_STENCIL_FUNC_NEVER: func = GL_NEVER; break; + case GLS_STENCIL_FUNC_LESS: func = GL_LESS; break; + case GLS_STENCIL_FUNC_EQUAL: func = GL_EQUAL; break; + case GLS_STENCIL_FUNC_LEQUAL: func = GL_LEQUAL; break; + case GLS_STENCIL_FUNC_GREATER: func = GL_GREATER; break; + case GLS_STENCIL_FUNC_NOTEQUAL: func = GL_NOTEQUAL; break; + case GLS_STENCIL_FUNC_GEQUAL: func = GL_GEQUAL; break; + case GLS_STENCIL_FUNC_ALWAYS: func = GL_ALWAYS; break; + } + qglStencilFunc( func, ref, mask ); + } + if ( diff & ( GLS_STENCIL_OP_FAIL_BITS | GLS_STENCIL_OP_ZFAIL_BITS | GLS_STENCIL_OP_PASS_BITS ) ) { + GLenum sFail = 0; + GLenum zFail = 0; + GLenum pass = 0; + + switch ( stateBits & GLS_STENCIL_OP_FAIL_BITS ) { + case GLS_STENCIL_OP_FAIL_KEEP: sFail = GL_KEEP; break; + case GLS_STENCIL_OP_FAIL_ZERO: sFail = GL_ZERO; break; + case GLS_STENCIL_OP_FAIL_REPLACE: sFail = GL_REPLACE; break; + case GLS_STENCIL_OP_FAIL_INCR: sFail = GL_INCR; break; + case GLS_STENCIL_OP_FAIL_DECR: sFail = GL_DECR; break; + case GLS_STENCIL_OP_FAIL_INVERT: sFail = GL_INVERT; break; + case GLS_STENCIL_OP_FAIL_INCR_WRAP: sFail = GL_INCR_WRAP; break; + case GLS_STENCIL_OP_FAIL_DECR_WRAP: sFail = GL_DECR_WRAP; break; + } + switch ( stateBits & GLS_STENCIL_OP_ZFAIL_BITS ) { + case GLS_STENCIL_OP_ZFAIL_KEEP: zFail = GL_KEEP; break; + case GLS_STENCIL_OP_ZFAIL_ZERO: zFail = GL_ZERO; break; + case GLS_STENCIL_OP_ZFAIL_REPLACE: zFail = GL_REPLACE; break; + case GLS_STENCIL_OP_ZFAIL_INCR: zFail = GL_INCR; break; + case GLS_STENCIL_OP_ZFAIL_DECR: zFail = GL_DECR; break; + case GLS_STENCIL_OP_ZFAIL_INVERT: zFail = GL_INVERT; break; + case GLS_STENCIL_OP_ZFAIL_INCR_WRAP:zFail = GL_INCR_WRAP; break; + case GLS_STENCIL_OP_ZFAIL_DECR_WRAP:zFail = GL_DECR_WRAP; break; + } + switch ( stateBits & GLS_STENCIL_OP_PASS_BITS ) { + case GLS_STENCIL_OP_PASS_KEEP: pass = GL_KEEP; break; + case GLS_STENCIL_OP_PASS_ZERO: pass = GL_ZERO; break; + case GLS_STENCIL_OP_PASS_REPLACE: pass = GL_REPLACE; break; + case GLS_STENCIL_OP_PASS_INCR: pass = GL_INCR; break; + case GLS_STENCIL_OP_PASS_DECR: pass = GL_DECR; break; + case GLS_STENCIL_OP_PASS_INVERT: pass = GL_INVERT; break; + case GLS_STENCIL_OP_PASS_INCR_WRAP: pass = GL_INCR_WRAP; break; + case GLS_STENCIL_OP_PASS_DECR_WRAP: pass = GL_DECR_WRAP; break; + } + qglStencilOp( sFail, zFail, pass ); + } + + backEnd.glState.glStateBits = stateBits; +} + +/* +================= +GL_GetCurrentState +================= +*/ +uint64 GL_GetCurrentState() { + return backEnd.glState.glStateBits; +} + +/* +======================== +GL_GetCurrentStateMinusStencil +======================== +*/ +uint64 GL_GetCurrentStateMinusStencil() { + return GL_GetCurrentState() & ~(GLS_STENCIL_OP_BITS|GLS_STENCIL_FUNC_BITS|GLS_STENCIL_FUNC_REF_BITS|GLS_STENCIL_FUNC_MASK_BITS); +} diff --git a/neo/renderer/OpenGL/gl_Image.cpp b/neo/renderer/OpenGL/gl_Image.cpp new file mode 100644 index 00000000..c24ccda6 --- /dev/null +++ b/neo/renderer/OpenGL/gl_Image.cpp @@ -0,0 +1,482 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +/* +================================================================================================ +Contains the Image implementation for OpenGL. +================================================================================================ +*/ + +#include "../tr_local.h" + +/* +======================== +idImage::SubImageUpload +======================== +*/ +void idImage::SubImageUpload( int mipLevel, int x, int y, int z, int width, int height, const void * pic, int pixelPitch ) const { + assert( x >= 0 && y >= 0 && mipLevel >= 0 && width >= 0 && height >= 0 && mipLevel < opts.numLevels ); + + int compressedSize = 0; + + if ( IsCompressed() ) { + assert( !(x&3) && !(y&3) ); + + // compressed size may be larger than the dimensions due to padding to quads + int quadW = ( width + 3 ) & ~3; + int quadH = ( height + 3 ) & ~3; + compressedSize = quadW * quadH * BitsForFormat( opts.format ) / 8; + + int padW = ( opts.width + 3 ) & ~3; + int padH = ( opts.height + 3 ) & ~3; + (void)padH; + (void)padW; + assert( x + width <= padW && y + height <= padH ); + // upload the non-aligned value, OpenGL understands that there + // will be padding + if ( x + width > opts.width ) { + width = opts.width - x; + } + if ( y + height > opts.height ) { + height = opts.height - x; + } + } else { + assert( x + width <= opts.width && y + height <= opts.height ); + } + + int target; + int uploadTarget; + if ( opts.textureType == TT_2D ) { + target = GL_TEXTURE_2D; + uploadTarget = GL_TEXTURE_2D; + } else if ( opts.textureType == TT_CUBIC ) { + target = GL_TEXTURE_CUBE_MAP_EXT; + uploadTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + z; + } else { + assert( !"invalid opts.textureType" ); + target = GL_TEXTURE_2D; + uploadTarget = GL_TEXTURE_2D; + } + + qglBindTexture( target, texnum ); + + if ( pixelPitch != 0 ) { + qglPixelStorei( GL_UNPACK_ROW_LENGTH, pixelPitch ); + } + if ( opts.format == FMT_RGB565 ) { + glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_TRUE ); + } +#ifdef DEBUG + GL_CheckErrors(); +#endif + if ( IsCompressed() ) { + qglCompressedTexSubImage2DARB( uploadTarget, mipLevel, x, y, width, height, internalFormat, compressedSize, pic ); + } else { + + // make sure the pixel store alignment is correct so that lower mips get created + // properly for odd shaped textures - this fixes the mip mapping issues with + // fonts + int unpackAlignment = width * BitsForFormat( (textureFormat_t)opts.format ) / 8; + if ( ( unpackAlignment & 3 ) == 0 ) { + qglPixelStorei( GL_UNPACK_ALIGNMENT, 4 ); + } else { + qglPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + } + + qglTexSubImage2D( uploadTarget, mipLevel, x, y, width, height, dataFormat, dataType, pic ); + } +#ifdef DEBUG + GL_CheckErrors(); +#endif + if ( opts.format == FMT_RGB565 ) { + glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE ); + } + if ( pixelPitch != 0 ) { + qglPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); + } +} + +/* +======================== +idImage::SetPixel +======================== +*/ +void idImage::SetPixel( int mipLevel, int x, int y, const void * data, int dataSize ) { + SubImageUpload( mipLevel, x, y, 0, 1, 1, data ); +} + +/* +======================== +idImage::SetTexParameters +======================== +*/ +void idImage::SetTexParameters() { + int target = GL_TEXTURE_2D; + switch ( opts.textureType ) { + case TT_2D: + target = GL_TEXTURE_2D; + break; + case TT_CUBIC: + target = GL_TEXTURE_CUBE_MAP_EXT; + break; + default: + idLib::FatalError( "%s: bad texture type %d", GetName(), opts.textureType ); + return; + } + + // ALPHA, LUMINANCE, LUMINANCE_ALPHA, and INTENSITY have been removed + // in OpenGL 3.2. In order to mimic those modes, we use the swizzle operators +#if defined( USE_CORE_PROFILE ) + if ( opts.colorFormat == CFM_GREEN_ALPHA ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_GREEN ); + } else if ( opts.format == FMT_LUM8 ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_ONE ); + } else if ( opts.format == FMT_L8A8 ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_GREEN ); + } else if ( opts.format == FMT_ALPHA ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_RED ); + } else if ( opts.format == FMT_INT8 ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_RED ); + } else { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_RED ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_GREEN ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_BLUE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_ALPHA ); + } +#else + if ( opts.colorFormat == CFM_GREEN_ALPHA ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_GREEN ); + } else if ( opts.format == FMT_ALPHA ) { + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_R, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_G, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_B, GL_ONE ); + qglTexParameteri( target, GL_TEXTURE_SWIZZLE_A, GL_RED ); + } +#endif + + switch( filter ) { + case TF_DEFAULT: + if ( r_useTrilinearFiltering.GetBool() ) { + qglTexParameterf( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + } else { + qglTexParameterf( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST ); + } + qglTexParameterf( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case TF_LINEAR: + qglTexParameterf( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case TF_NEAREST: + qglTexParameterf( target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf( target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + default: + common->FatalError( "%s: bad texture filter %d", GetName(), filter ); + } + + if ( glConfig.anisotropicFilterAvailable ) { + // only do aniso filtering on mip mapped images + if ( filter == TF_DEFAULT ) { + int aniso = r_maxAnisotropicFiltering.GetInteger(); + if ( aniso > glConfig.maxTextureAnisotropy ) { + aniso = glConfig.maxTextureAnisotropy; + } + if ( aniso < 0 ) { + aniso = 0; + } + qglTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso ); + } else { + qglTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + } + } + if ( glConfig.textureLODBiasAvailable && ( usage != TD_FONT ) ) { + // use a blurring LOD bias in combination with high anisotropy to fix our aliasing grate textures... + qglTexParameterf(target, GL_TEXTURE_LOD_BIAS_EXT, r_lodBias.GetFloat() ); + } + + // set the wrap/clamp modes + switch( repeat ) { + case TR_REPEAT: + qglTexParameterf( target, GL_TEXTURE_WRAP_S, GL_REPEAT ); + qglTexParameterf( target, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + case TR_CLAMP_TO_ZERO: { + float color[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + qglTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, color ); + qglTexParameterf( target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + qglTexParameterf( target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + } + break; + case TR_CLAMP_TO_ZERO_ALPHA: { + float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + qglTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, color ); + qglTexParameterf( target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + qglTexParameterf( target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + } + break; + case TR_CLAMP: + qglTexParameterf( target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + break; + default: + common->FatalError( "%s: bad texture repeat %d", GetName(), repeat ); + } +} + +/* +======================== +idImage::AllocImage + +Every image will pass through this function. Allocates all the necessary MipMap levels for the +Image, but doesn't put anything in them. + +This should not be done during normal game-play, if you can avoid it. +======================== +*/ +void idImage::AllocImage() { + GL_CheckErrors(); + PurgeImage(); + + switch ( opts.format ) { + case FMT_RGBA8: + internalFormat = GL_RGBA8; + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_XRGB8: + internalFormat = GL_RGB; + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_RGB565: + internalFormat = GL_RGB; + dataFormat = GL_RGB; + dataType = GL_UNSIGNED_SHORT_5_6_5; + break; + case FMT_ALPHA: +#if defined( USE_CORE_PROFILE ) + internalFormat = GL_R8; + dataFormat = GL_RED; +#else + internalFormat = GL_ALPHA8; + dataFormat = GL_ALPHA; +#endif + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_L8A8: +#if defined( USE_CORE_PROFILE ) + internalFormat = GL_RG8; + dataFormat = GL_RG; +#else + internalFormat = GL_LUMINANCE8_ALPHA8; + dataFormat = GL_LUMINANCE_ALPHA; +#endif + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_LUM8: +#if defined( USE_CORE_PROFILE ) + internalFormat = GL_R8; + dataFormat = GL_RED; +#else + internalFormat = GL_LUMINANCE8; + dataFormat = GL_LUMINANCE; +#endif + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_INT8: +#if defined( USE_CORE_PROFILE ) + internalFormat = GL_R8; + dataFormat = GL_RED; +#else + internalFormat = GL_INTENSITY8; + dataFormat = GL_LUMINANCE; +#endif + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_DXT1: + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_DXT5: + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_DEPTH: + internalFormat = GL_DEPTH_COMPONENT; + dataFormat = GL_DEPTH_COMPONENT; + dataType = GL_UNSIGNED_BYTE; + break; + case FMT_X16: + internalFormat = GL_INTENSITY16; + dataFormat = GL_LUMINANCE; + dataType = GL_UNSIGNED_SHORT; + break; + case FMT_Y16_X16: + internalFormat = GL_LUMINANCE16_ALPHA16; + dataFormat = GL_LUMINANCE_ALPHA; + dataType = GL_UNSIGNED_SHORT; + break; + default: + idLib::Error( "Unhandled image format %d in %s\n", opts.format, GetName() ); + } + + // if we don't have a rendering context, just return after we + // have filled in the parms. We must have the values set, or + // an image match from a shader before OpenGL starts would miss + // the generated texture + if ( !R_IsInitialized() ) { + return; + } + + // generate the texture number + qglGenTextures( 1, (GLuint *)&texnum ); + assert( texnum != TEXTURE_NOT_LOADED ); + + //---------------------------------------------------- + // allocate all the mip levels with NULL data + //---------------------------------------------------- + + int numSides; + int target; + int uploadTarget; + if ( opts.textureType == TT_2D ) { + target = uploadTarget = GL_TEXTURE_2D; + numSides = 1; + } else if ( opts.textureType == TT_CUBIC ) { + target = GL_TEXTURE_CUBE_MAP_EXT; + uploadTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT; + numSides = 6; + } else { + assert( !"opts.textureType" ); + target = uploadTarget = GL_TEXTURE_2D; + numSides = 1; + } + + qglBindTexture( target, texnum ); + + for ( int side = 0; side < numSides; side++ ) { + int w = opts.width; + int h = opts.height; + if ( opts.textureType == TT_CUBIC ) { + h = w; + } + for ( int level = 0; level < opts.numLevels; level++ ) { + + // clear out any previous error + GL_CheckErrors(); + + if ( IsCompressed() ) { + int compressedSize = ( ((w+3)/4) * ((h+3)/4) * int64( 16 ) * BitsForFormat( opts.format ) ) / 8; + + // Even though the OpenGL specification allows the 'data' pointer to be NULL, for some + // drivers we actually need to upload data to get it to allocate the texture. + // However, on 32-bit systems we may fail to allocate a large block of memory for large + // textures. We handle this case by using HeapAlloc directly and allowing the allocation + // to fail in which case we simply pass down NULL to glCompressedTexImage2D and hope for the best. + // As of 2011-10-6 using NVIDIA hardware and drivers we have to allocate the memory with HeapAlloc + // with the exact size otherwise large image allocation (for instance for physical page textures) + // may fail on Vista 32-bit. + void * data = HeapAlloc( GetProcessHeap(), 0, compressedSize ); + qglCompressedTexImage2DARB( uploadTarget+side, level, internalFormat, w, h, 0, compressedSize, data ); + if ( data != NULL ) { + HeapFree( GetProcessHeap(), 0, data ); + } + } else { + qglTexImage2D( uploadTarget + side, level, internalFormat, w, h, 0, dataFormat, dataType, NULL ); + } + + GL_CheckErrors(); + + w = Max( 1, w >> 1 ); + h = Max( 1, h >> 1 ); + } + } + + qglTexParameteri( target, GL_TEXTURE_MAX_LEVEL, opts.numLevels - 1 ); + + // see if we messed anything up + GL_CheckErrors(); + + SetTexParameters(); + + GL_CheckErrors(); +} + +/* +======================== +idImage::PurgeImage +======================== +*/ +void idImage::PurgeImage() { + if ( texnum != TEXTURE_NOT_LOADED ) { + qglDeleteTextures( 1, (GLuint *)&texnum ); // this should be the ONLY place it is ever called! + texnum = TEXTURE_NOT_LOADED; + } + // clear all the current binding caches, so the next bind will do a real one + for ( int i = 0 ; i < MAX_MULTITEXTURE_UNITS ; i++ ) { + backEnd.glState.tmu[i].current2DMap = TEXTURE_NOT_LOADED; + backEnd.glState.tmu[i].currentCubeMap = TEXTURE_NOT_LOADED; + } +} + +/* +======================== +idImage::Resize +======================== +*/ +void idImage::Resize( int width, int height ) { + if ( opts.width == width && opts.height == height ) { + return; + } + opts.width = width; + opts.height = height; + AllocImage(); +} diff --git a/neo/renderer/OpenGL/gl_backend.cpp b/neo/renderer/OpenGL/gl_backend.cpp new file mode 100644 index 00000000..3bba3fe2 --- /dev/null +++ b/neo/renderer/OpenGL/gl_backend.cpp @@ -0,0 +1,579 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "../tr_local.h" +#include "../../framework/Common_local.h" + +idCVar r_drawFlickerBox( "r_drawFlickerBox", "0", CVAR_RENDERER | CVAR_BOOL, "visual test for dropping frames" ); +idCVar stereoRender_warp( "stereoRender_warp", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use the optical warping renderprog instead of stereoDeGhost" ); +idCVar stereoRender_warpStrength( "stereoRender_warpStrength", "1.45", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "amount of pre-distortion" ); + +idCVar stereoRender_warpCenterX( "stereoRender_warpCenterX", "0.5", CVAR_RENDERER | CVAR_FLOAT | CVAR_ARCHIVE, "center for left eye, right eye will be 1.0 - this" ); +idCVar stereoRender_warpCenterY( "stereoRender_warpCenterY", "0.5", CVAR_RENDERER | CVAR_FLOAT | CVAR_ARCHIVE, "center for both eyes" ); +idCVar stereoRender_warpParmZ( "stereoRender_warpParmZ", "0", CVAR_RENDERER | CVAR_FLOAT | CVAR_ARCHIVE, "development parm" ); +idCVar stereoRender_warpParmW( "stereoRender_warpParmW", "0", CVAR_RENDERER | CVAR_FLOAT | CVAR_ARCHIVE, "development parm" ); +idCVar stereoRender_warpTargetFraction( "stereoRender_warpTargetFraction", "1.0", CVAR_RENDERER | CVAR_FLOAT | CVAR_ARCHIVE, "fraction of half-width the through-lens view covers" ); + +idCVar r_showSwapBuffers( "r_showSwapBuffers", "0", CVAR_BOOL, "Show timings from GL_BlockingSwapBuffers" ); +idCVar r_syncEveryFrame( "r_syncEveryFrame", "1", CVAR_BOOL, "Don't let the GPU buffer execution past swapbuffers" ); + +static int swapIndex; // 0 or 1 into renderSync +static GLsync renderSync[2]; + +void GLimp_SwapBuffers(); +void RB_SetMVP( const idRenderMatrix & mvp ); + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +============= +RB_DrawFlickerBox +============= +*/ +static void RB_DrawFlickerBox() { + if ( !r_drawFlickerBox.GetBool() ) { + return; + } + if ( tr.frameCount & 1 ) { + qglClearColor( 1, 0, 0, 1 ); + } else { + qglClearColor( 0, 1, 0, 1 ); + } + qglScissor( 0, 0, 256, 256 ); + qglClear( GL_COLOR_BUFFER_BIT ); +} + +/* +============= +RB_SetBuffer +============= +*/ +static void RB_SetBuffer( const void *data ) { + // see which draw buffer we want to render the frame to + + const setBufferCommand_t * cmd = (const setBufferCommand_t *)data; + + RENDERLOG_PRINTF( "---------- RB_SetBuffer ---------- to buffer # %d\n", cmd->buffer ); + + GL_Scissor( 0, 0, tr.GetWidth(), tr.GetHeight() ); + + // clear screen for debugging + // automatically enable this with several other debug tools + // that might leave unrendered portions of the screen + if ( r_clear.GetFloat() || idStr::Length( r_clear.GetString() ) != 1 || r_singleArea.GetBool() || r_showOverDraw.GetBool() ) { + float c[3]; + if ( sscanf( r_clear.GetString(), "%f %f %f", &c[0], &c[1], &c[2] ) == 3 ) { + GL_Clear( true, false, false, 0, c[0], c[1], c[2], 1.0f ); + } else if ( r_clear.GetInteger() == 2 ) { + GL_Clear( true, false, false, 0, 0.0f, 0.0f, 0.0f, 1.0f ); + } else if ( r_showOverDraw.GetBool() ) { + GL_Clear( true, false, false, 0, 1.0f, 1.0f, 1.0f, 1.0f ); + } else { + GL_Clear( true, false, false, 0, 0.4f, 0.0f, 0.25f, 1.0f ); + } + } +} + +/* +============= +GL_BlockingSwapBuffers + +We want to exit this with the GPU idle, right at vsync +============= +*/ +const void GL_BlockingSwapBuffers() { + RENDERLOG_PRINTF( "***************** GL_BlockingSwapBuffers *****************\n\n\n" ); + + const int beforeFinish = Sys_Milliseconds(); + + if ( !glConfig.syncAvailable ) { + glFinish(); + } + + const int beforeSwap = Sys_Milliseconds(); + if ( r_showSwapBuffers.GetBool() && beforeSwap - beforeFinish > 1 ) { + common->Printf( "%i msec to glFinish\n", beforeSwap - beforeFinish ); + } + + GLimp_SwapBuffers(); + + const int beforeFence = Sys_Milliseconds(); + if ( r_showSwapBuffers.GetBool() && beforeFence - beforeSwap > 1 ) { + common->Printf( "%i msec to swapBuffers\n", beforeFence - beforeSwap ); + } + + if ( glConfig.syncAvailable ) { + swapIndex ^= 1; + + if ( qglIsSync( renderSync[swapIndex] ) ) { + qglDeleteSync( renderSync[swapIndex] ); + } + // draw something tiny to ensure the sync is after the swap + const int start = Sys_Milliseconds(); + qglScissor( 0, 0, 1, 1 ); + qglEnable( GL_SCISSOR_TEST ); + qglClear( GL_COLOR_BUFFER_BIT ); + renderSync[swapIndex] = qglFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 ); + const int end = Sys_Milliseconds(); + if ( r_showSwapBuffers.GetBool() && end - start > 1 ) { + common->Printf( "%i msec to start fence\n", end - start ); + } + + GLsync syncToWaitOn; + if ( r_syncEveryFrame.GetBool() ) { + syncToWaitOn = renderSync[swapIndex]; + } else { + syncToWaitOn = renderSync[!swapIndex]; + } + + if ( qglIsSync( syncToWaitOn ) ) { + for ( GLenum r = GL_TIMEOUT_EXPIRED; r == GL_TIMEOUT_EXPIRED; ) { + r = qglClientWaitSync( syncToWaitOn, GL_SYNC_FLUSH_COMMANDS_BIT, 1000 * 1000 ); + } + } + } + + const int afterFence = Sys_Milliseconds(); + if ( r_showSwapBuffers.GetBool() && afterFence - beforeFence > 1 ) { + common->Printf( "%i msec to wait on fence\n", afterFence - beforeFence ); + } + + const int64 exitBlockTime = Sys_Microseconds(); + + static int64 prevBlockTime; + if ( r_showSwapBuffers.GetBool() && prevBlockTime ) { + const int delta = (int) ( exitBlockTime - prevBlockTime ); + common->Printf( "blockToBlock: %i\n", delta ); + } + prevBlockTime = exitBlockTime; +} + +/* +==================== +R_MakeStereoRenderImage +==================== +*/ +static void R_MakeStereoRenderImage( idImage *image ) { + idImageOpts opts; + opts.width = renderSystem->GetWidth(); + opts.height = renderSystem->GetHeight(); + opts.numLevels = 1; + opts.format = FMT_RGBA8; + image->AllocImage( opts, TF_LINEAR, TR_CLAMP ); +} + +/* +==================== +RB_StereoRenderExecuteBackEndCommands + +Renders the draw list twice, with slight modifications for left eye / right eye +==================== +*/ +void RB_StereoRenderExecuteBackEndCommands( const emptyCommand_t * const allCmds ) { + uint64 backEndStartTime = Sys_Microseconds(); + + // If we are in a monoscopic context, this draws to the only buffer, and is + // the same as GL_BACK. In a quad-buffer stereo context, this is necessary + // to prevent GL from forcing the rendering to go to both BACK_LEFT and + // BACK_RIGHT at a performance penalty. + // To allow stereo deghost processing, the views have to be copied to separate + // textures anyway, so there isn't any benefit to rendering to BACK_RIGHT for + // that eye. + qglDrawBuffer( GL_BACK_LEFT ); + + // create the stereoRenderImage if we haven't already + static idImage * stereoRenderImages[2]; + for ( int i = 0; i < 2; i++ ) { + if ( stereoRenderImages[i] == NULL ) { + stereoRenderImages[i] = globalImages->ImageFromFunction( va("_stereoRender%i",i), R_MakeStereoRenderImage ); + } + + // resize the stereo render image if the main window has changed size + if ( stereoRenderImages[i]->GetUploadWidth() != renderSystem->GetWidth() || + stereoRenderImages[i]->GetUploadHeight() != renderSystem->GetHeight() ) { + stereoRenderImages[i]->Resize( renderSystem->GetWidth(), renderSystem->GetHeight() ); + } + } + + // In stereoRender mode, the front end has generated two RC_DRAW_VIEW commands + // with slightly different origins for each eye. + + // TODO: only do the copy after the final view has been rendered, not mirror subviews? + + // Render the 3D draw views from the screen origin so all the screen relative + // texture mapping works properly, then copy the portion we are going to use + // off to a texture. + bool foundEye[2] = { false, false }; + + for ( int stereoEye = 1; stereoEye >= -1; stereoEye -= 2 ) { + // set up the target texture we will draw to + const int targetEye = ( stereoEye == 1 ) ? 1 : 0; + + // Set the back end into a known default state to fix any stale render state issues + GL_SetDefaultState(); + renderProgManager.Unbind(); + renderProgManager.ZeroUniforms(); + + for ( const emptyCommand_t * cmds = allCmds; cmds != NULL; cmds = (const emptyCommand_t *)cmds->next ) { + switch ( cmds->commandId ) { + case RC_NOP: + break; + case RC_DRAW_VIEW_GUI: + case RC_DRAW_VIEW_3D: + { + const drawSurfsCommand_t * const dsc = (const drawSurfsCommand_t *)cmds; + const viewDef_t & eyeViewDef = *dsc->viewDef; + + if ( eyeViewDef.renderView.viewEyeBuffer && eyeViewDef.renderView.viewEyeBuffer != stereoEye ) { + // this is the render view for the other eye + continue; + } + + foundEye[ targetEye ] = true; + RB_DrawView( dsc, stereoEye ); + if ( cmds->commandId == RC_DRAW_VIEW_GUI ) { + } + } + break; + case RC_SET_BUFFER: + RB_SetBuffer( cmds ); + break; + case RC_COPY_RENDER: + RB_CopyRender( cmds ); + break; + case RC_POST_PROCESS: + { + postProcessCommand_t * cmd = (postProcessCommand_t *)cmds; + if ( cmd->viewDef->renderView.viewEyeBuffer != stereoEye ) { + break; + } + RB_PostProcess( cmds ); + } + break; + default: + common->Error( "RB_ExecuteBackEndCommands: bad commandId" ); + break; + } + } + + // copy to the target + stereoRenderImages[ targetEye ]->CopyFramebuffer( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight() ); + } + + // perform the final compositing / warping / deghosting to the actual framebuffer(s) + assert( foundEye[0] && foundEye[1] ); + + GL_SetDefaultState(); + + RB_SetMVP( renderMatrix_identity ); + + // If we are in quad-buffer pixel format but testing another 3D mode, + // make sure we draw to both eyes. This is likely to be sub-optimal + // performance on most cards and drivers, but it is better than getting + // a confusing, half-ghosted view. + if ( renderSystem->GetStereo3DMode() != STEREO3D_QUAD_BUFFER ) { + glDrawBuffer( GL_BACK ); + } + + GL_State( GLS_DEPTHFUNC_ALWAYS ); + GL_Cull( CT_TWO_SIDED ); + + // We just want to do a quad pass - so make sure we disable any texgen and + // set the texture matrix to the identity so we don't get anomalies from + // any stale uniform data being present from a previous draw call + const float texS[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + const float texT[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_S, texS ); + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_T, texT ); + + // disable any texgen + const float texGenEnabled[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + renderProgManager.SetRenderParm( RENDERPARM_TEXGEN_0_ENABLED, texGenEnabled ); + + renderProgManager.BindShader_Texture(); + GL_Color( 1, 1, 1, 1 ); + + switch( renderSystem->GetStereo3DMode() ) { + case STEREO3D_QUAD_BUFFER: + glDrawBuffer( GL_BACK_RIGHT ); + GL_SelectTexture( 0 ); + stereoRenderImages[1]->Bind(); + GL_SelectTexture( 1 ); + stereoRenderImages[0]->Bind(); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + glDrawBuffer( GL_BACK_LEFT ); + GL_SelectTexture( 1 ); + stereoRenderImages[1]->Bind(); + GL_SelectTexture( 0 ); + stereoRenderImages[0]->Bind(); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + break; + case STEREO3D_HDMI_720: + // HDMI 720P 3D + GL_SelectTexture( 0 ); + stereoRenderImages[1]->Bind(); + GL_SelectTexture( 1 ); + stereoRenderImages[0]->Bind(); + GL_ViewportAndScissor( 0, 0, 1280, 720 ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + GL_SelectTexture( 0 ); + stereoRenderImages[0]->Bind(); + GL_SelectTexture( 1 ); + stereoRenderImages[1]->Bind(); + GL_ViewportAndScissor( 0, 750, 1280, 720 ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + // force the HDMI 720P 3D guard band to a constant color + glScissor( 0, 720, 1280, 30 ); + glClear( GL_COLOR_BUFFER_BIT ); + break; + default: + case STEREO3D_SIDE_BY_SIDE: + if ( stereoRender_warp.GetBool() ) { + // this is the Rift warp + // renderSystem->GetWidth() / GetHeight() have returned equal values (640 for initial Rift) + // and we are going to warp them onto a symetric square region of each half of the screen + + renderProgManager.BindShader_StereoWarp(); + + // clear the entire screen to black + // we could be smart and only clear the areas we aren't going to draw on, but + // clears are fast... + glScissor ( 0, 0, glConfig.nativeScreenWidth, glConfig.nativeScreenHeight ); + glClearColor( 0, 0, 0, 0 ); + glClear( GL_COLOR_BUFFER_BIT ); + + // the size of the box that will get the warped pixels + // With the 7" displays, this will be less than half the screen width + const int pixelDimensions = ( glConfig.nativeScreenWidth >> 1 ) * stereoRender_warpTargetFraction.GetFloat(); + + // Always scissor to the half-screen boundary, but the viewports + // might cross that boundary if the lenses can be adjusted closer + // together. + glViewport( ( glConfig.nativeScreenWidth >> 1 ) - pixelDimensions, + ( glConfig.nativeScreenHeight >> 1 ) - ( pixelDimensions >> 1 ), + pixelDimensions, pixelDimensions ); + glScissor ( 0, 0, glConfig.nativeScreenWidth >> 1, glConfig.nativeScreenHeight ); + + idVec4 color( stereoRender_warpCenterX.GetFloat(), stereoRender_warpCenterY.GetFloat(), stereoRender_warpParmZ.GetFloat(), stereoRender_warpParmW.GetFloat() ); + // don't use GL_Color(), because we don't want to clamp + renderProgManager.SetRenderParm( RENDERPARM_COLOR, color.ToFloatPtr() ); + + GL_SelectTexture( 0 ); + stereoRenderImages[0]->Bind(); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + idVec4 color2( stereoRender_warpCenterX.GetFloat(), stereoRender_warpCenterY.GetFloat(), stereoRender_warpParmZ.GetFloat(), stereoRender_warpParmW.GetFloat() ); + // don't use GL_Color(), because we don't want to clamp + renderProgManager.SetRenderParm( RENDERPARM_COLOR, color2.ToFloatPtr() ); + + glViewport( ( glConfig.nativeScreenWidth >> 1 ), + ( glConfig.nativeScreenHeight >> 1 ) - ( pixelDimensions >> 1 ), + pixelDimensions, pixelDimensions ); + glScissor ( glConfig.nativeScreenWidth >> 1, 0, glConfig.nativeScreenWidth >> 1, glConfig.nativeScreenHeight ); + + GL_SelectTexture( 0 ); + stereoRenderImages[1]->Bind(); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + break; + } + // a non-warped side-by-side-uncompressed (dual input cable) is rendered + // just like STEREO3D_SIDE_BY_SIDE_COMPRESSED, so fall through. + case STEREO3D_SIDE_BY_SIDE_COMPRESSED: + GL_SelectTexture( 0 ); + stereoRenderImages[0]->Bind(); + GL_SelectTexture( 1 ); + stereoRenderImages[1]->Bind(); + GL_ViewportAndScissor( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight() ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + GL_SelectTexture( 0 ); + stereoRenderImages[1]->Bind(); + GL_SelectTexture( 1 ); + stereoRenderImages[0]->Bind(); + GL_ViewportAndScissor( renderSystem->GetWidth(), 0, renderSystem->GetWidth(), renderSystem->GetHeight() ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + break; + + case STEREO3D_TOP_AND_BOTTOM_COMPRESSED: + GL_SelectTexture( 1 ); + stereoRenderImages[0]->Bind(); + GL_SelectTexture( 0 ); + stereoRenderImages[1]->Bind(); + GL_ViewportAndScissor( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight() ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + GL_SelectTexture( 1 ); + stereoRenderImages[1]->Bind(); + GL_SelectTexture( 0 ); + stereoRenderImages[0]->Bind(); + GL_ViewportAndScissor( 0, renderSystem->GetHeight(), renderSystem->GetWidth(), renderSystem->GetHeight() ); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + break; + + case STEREO3D_INTERLACED: + // every other scanline + GL_SelectTexture( 0 ); + stereoRenderImages[0]->Bind(); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + GL_SelectTexture( 1 ); + stereoRenderImages[1]->Bind(); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + GL_ViewportAndScissor( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight()*2 ); + renderProgManager.BindShader_StereoInterlace(); + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + GL_SelectTexture( 0 ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + GL_SelectTexture( 1 ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + break; + } + + // debug tool + RB_DrawFlickerBox(); + + // make sure the drawing is actually started + qglFlush(); + + // we may choose to sync to the swapbuffers before the next frame + + // stop rendering on this thread + uint64 backEndFinishTime = Sys_Microseconds(); + backEnd.pc.totalMicroSec = backEndFinishTime - backEndStartTime; +} + +/* +==================== +RB_ExecuteBackEndCommands + +This function will be called syncronously if running without +smp extensions, or asyncronously by another thread. +==================== +*/ +void RB_ExecuteBackEndCommands( const emptyCommand_t *cmds ) { + // r_debugRenderToTexture + int c_draw3d = 0; + int c_draw2d = 0; + int c_setBuffers = 0; + int c_copyRenders = 0; + + resolutionScale.SetCurrentGPUFrameTime( commonLocal.GetRendererGPUMicroseconds() ); + + renderLog.StartFrame(); + + if ( cmds->commandId == RC_NOP && !cmds->next ) { + return; + } + + if ( renderSystem->GetStereo3DMode() != STEREO3D_OFF ) { + RB_StereoRenderExecuteBackEndCommands( cmds ); + renderLog.EndFrame(); + return; + } + + uint64 backEndStartTime = Sys_Microseconds(); + + // needed for editor rendering + GL_SetDefaultState(); + + // If we have a stereo pixel format, this will draw to both + // the back left and back right buffers, which will have a + // performance penalty. + qglDrawBuffer( GL_BACK ); + + for ( ; cmds != NULL; cmds = (const emptyCommand_t *)cmds->next ) { + switch ( cmds->commandId ) { + case RC_NOP: + break; + case RC_DRAW_VIEW_3D: + case RC_DRAW_VIEW_GUI: + RB_DrawView( cmds, 0 ); + if ( ((const drawSurfsCommand_t *)cmds)->viewDef->viewEntitys ) { + c_draw3d++; + } else { + c_draw2d++; + } + break; + case RC_SET_BUFFER: + c_setBuffers++; + break; + case RC_COPY_RENDER: + RB_CopyRender( cmds ); + c_copyRenders++; + break; + case RC_POST_PROCESS: + RB_PostProcess( cmds ); + break; + default: + common->Error( "RB_ExecuteBackEndCommands: bad commandId" ); + break; + } + } + + RB_DrawFlickerBox(); + + // Fix for the steam overlay not showing up while in game without Shell/Debug/Console/Menu also rendering + qglColorMask( 1, 1, 1, 1 ); + + qglFlush(); + + // stop rendering on this thread + uint64 backEndFinishTime = Sys_Microseconds(); + backEnd.pc.totalMicroSec = backEndFinishTime - backEndStartTime; + + if ( r_debugRenderToTexture.GetInteger() == 1 ) { + common->Printf( "3d: %i, 2d: %i, SetBuf: %i, CpyRenders: %i, CpyFrameBuf: %i\n", c_draw3d, c_draw2d, c_setBuffers, c_copyRenders, backEnd.pc.c_copyFrameBuffer ); + backEnd.pc.c_copyFrameBuffer = 0; + } + renderLog.EndFrame(); +} diff --git a/neo/renderer/OpenGL/glext.h b/neo/renderer/OpenGL/glext.h new file mode 100644 index 00000000..08665547 --- /dev/null +++ b/neo/renderer/OpenGL/glext.h @@ -0,0 +1,11807 @@ +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2007-2012 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are 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 Materials. +** +** THE MATERIALS ARE 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 THE AUTHORS OR COPYRIGHT HOLDERS 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 +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Header file version number, required by OpenGL ABI for Linux */ +/* glext.h last updated $Date: 2012-03-26 17:45:29 -0700 (Mon, 26 Mar 2012) $ */ +/* Current version at http://www.opengl.org/registry/ */ +#define GL_GLEXT_VERSION 77 +/* Function declaration macros - to move into glplatform.h */ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +#ifndef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#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_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_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_LINE_WIDTH_RANGE 0x846E +#endif + +#ifndef GL_VERSION_1_2_DEPRECATED +#define GL_RESCALE_NORMAL 0x803A +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#endif + +#ifndef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#endif + +#ifndef GL_ARB_imaging_DEPRECATED +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#endif + +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#endif + +#ifndef GL_VERSION_1_3_DEPRECATED +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#endif + +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#endif + +#ifndef GL_VERSION_1_4_DEPRECATED +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#endif + +#ifndef GL_VERSION_1_5 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#endif + +#ifndef GL_VERSION_1_5_DEPRECATED +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_FOG_COORD_SRC 0x8450 +#define GL_FOG_COORD 0x8451 +#define GL_CURRENT_FOG_COORD 0x8453 +#define GL_FOG_COORD_ARRAY_TYPE 0x8454 +#define GL_FOG_COORD_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORD_ARRAY_POINTER 0x8456 +#define GL_FOG_COORD_ARRAY 0x8457 +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D +#define GL_SRC0_RGB 0x8580 +#define GL_SRC1_RGB 0x8581 +#define GL_SRC2_RGB 0x8582 +#define GL_SRC0_ALPHA 0x8588 +#define GL_SRC1_ALPHA 0x8589 +#define GL_SRC2_ALPHA 0x858A +#endif + +#ifndef GL_VERSION_2_0 +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#endif + +#ifndef GL_VERSION_2_0_DEPRECATED +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_TEXTURE_COORDS 0x8871 +#endif + +#ifndef GL_VERSION_2_1 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#endif + +#ifndef GL_VERSION_2_1_DEPRECATED +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B +#endif + +#ifndef GL_VERSION_3_0 +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +/* Reuse tokens from ARB_depth_buffer_float */ +/* reuse GL_DEPTH_COMPONENT32F */ +/* reuse GL_DEPTH32F_STENCIL8 */ +/* reuse GL_FLOAT_32_UNSIGNED_INT_24_8_REV */ +/* Reuse tokens from ARB_framebuffer_object */ +/* reuse GL_INVALID_FRAMEBUFFER_OPERATION */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE */ +/* reuse GL_FRAMEBUFFER_DEFAULT */ +/* reuse GL_FRAMEBUFFER_UNDEFINED */ +/* reuse GL_DEPTH_STENCIL_ATTACHMENT */ +/* reuse GL_INDEX */ +/* reuse GL_MAX_RENDERBUFFER_SIZE */ +/* reuse GL_DEPTH_STENCIL */ +/* reuse GL_UNSIGNED_INT_24_8 */ +/* reuse GL_DEPTH24_STENCIL8 */ +/* reuse GL_TEXTURE_STENCIL_SIZE */ +/* reuse GL_TEXTURE_RED_TYPE */ +/* reuse GL_TEXTURE_GREEN_TYPE */ +/* reuse GL_TEXTURE_BLUE_TYPE */ +/* reuse GL_TEXTURE_ALPHA_TYPE */ +/* reuse GL_TEXTURE_DEPTH_TYPE */ +/* reuse GL_UNSIGNED_NORMALIZED */ +/* reuse GL_FRAMEBUFFER_BINDING */ +/* reuse GL_DRAW_FRAMEBUFFER_BINDING */ +/* reuse GL_RENDERBUFFER_BINDING */ +/* reuse GL_READ_FRAMEBUFFER */ +/* reuse GL_DRAW_FRAMEBUFFER */ +/* reuse GL_READ_FRAMEBUFFER_BINDING */ +/* reuse GL_RENDERBUFFER_SAMPLES */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER */ +/* reuse GL_FRAMEBUFFER_COMPLETE */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER */ +/* reuse GL_FRAMEBUFFER_UNSUPPORTED */ +/* reuse GL_MAX_COLOR_ATTACHMENTS */ +/* reuse GL_COLOR_ATTACHMENT0 */ +/* reuse GL_COLOR_ATTACHMENT1 */ +/* reuse GL_COLOR_ATTACHMENT2 */ +/* reuse GL_COLOR_ATTACHMENT3 */ +/* reuse GL_COLOR_ATTACHMENT4 */ +/* reuse GL_COLOR_ATTACHMENT5 */ +/* reuse GL_COLOR_ATTACHMENT6 */ +/* reuse GL_COLOR_ATTACHMENT7 */ +/* reuse GL_COLOR_ATTACHMENT8 */ +/* reuse GL_COLOR_ATTACHMENT9 */ +/* reuse GL_COLOR_ATTACHMENT10 */ +/* reuse GL_COLOR_ATTACHMENT11 */ +/* reuse GL_COLOR_ATTACHMENT12 */ +/* reuse GL_COLOR_ATTACHMENT13 */ +/* reuse GL_COLOR_ATTACHMENT14 */ +/* reuse GL_COLOR_ATTACHMENT15 */ +/* reuse GL_DEPTH_ATTACHMENT */ +/* reuse GL_STENCIL_ATTACHMENT */ +/* reuse GL_FRAMEBUFFER */ +/* reuse GL_RENDERBUFFER */ +/* reuse GL_RENDERBUFFER_WIDTH */ +/* reuse GL_RENDERBUFFER_HEIGHT */ +/* reuse GL_RENDERBUFFER_INTERNAL_FORMAT */ +/* reuse GL_STENCIL_INDEX1 */ +/* reuse GL_STENCIL_INDEX4 */ +/* reuse GL_STENCIL_INDEX8 */ +/* reuse GL_STENCIL_INDEX16 */ +/* reuse GL_RENDERBUFFER_RED_SIZE */ +/* reuse GL_RENDERBUFFER_GREEN_SIZE */ +/* reuse GL_RENDERBUFFER_BLUE_SIZE */ +/* reuse GL_RENDERBUFFER_ALPHA_SIZE */ +/* reuse GL_RENDERBUFFER_DEPTH_SIZE */ +/* reuse GL_RENDERBUFFER_STENCIL_SIZE */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE */ +/* reuse GL_MAX_SAMPLES */ +/* Reuse tokens from ARB_framebuffer_sRGB */ +/* reuse GL_FRAMEBUFFER_SRGB */ +/* Reuse tokens from ARB_half_float_vertex */ +/* reuse GL_HALF_FLOAT */ +/* Reuse tokens from ARB_map_buffer_range */ +/* reuse GL_MAP_READ_BIT */ +/* reuse GL_MAP_WRITE_BIT */ +/* reuse GL_MAP_INVALIDATE_RANGE_BIT */ +/* reuse GL_MAP_INVALIDATE_BUFFER_BIT */ +/* reuse GL_MAP_FLUSH_EXPLICIT_BIT */ +/* reuse GL_MAP_UNSYNCHRONIZED_BIT */ +/* Reuse tokens from ARB_texture_compression_rgtc */ +/* reuse GL_COMPRESSED_RED_RGTC1 */ +/* reuse GL_COMPRESSED_SIGNED_RED_RGTC1 */ +/* reuse GL_COMPRESSED_RG_RGTC2 */ +/* reuse GL_COMPRESSED_SIGNED_RG_RGTC2 */ +/* Reuse tokens from ARB_texture_rg */ +/* reuse GL_RG */ +/* reuse GL_RG_INTEGER */ +/* reuse GL_R8 */ +/* reuse GL_R16 */ +/* reuse GL_RG8 */ +/* reuse GL_RG16 */ +/* reuse GL_R16F */ +/* reuse GL_R32F */ +/* reuse GL_RG16F */ +/* reuse GL_RG32F */ +/* reuse GL_R8I */ +/* reuse GL_R8UI */ +/* reuse GL_R16I */ +/* reuse GL_R16UI */ +/* reuse GL_R32I */ +/* reuse GL_R32UI */ +/* reuse GL_RG8I */ +/* reuse GL_RG8UI */ +/* reuse GL_RG16I */ +/* reuse GL_RG16UI */ +/* reuse GL_RG32I */ +/* reuse GL_RG32UI */ +/* Reuse tokens from ARB_vertex_array_object */ +/* reuse GL_VERTEX_ARRAY_BINDING */ +#endif + +#ifndef GL_VERSION_3_0_DEPRECATED +#define GL_CLAMP_VERTEX_COLOR 0x891A +#define GL_CLAMP_FRAGMENT_COLOR 0x891B +#define GL_ALPHA_INTEGER 0x8D97 +/* Reuse tokens from ARB_framebuffer_object */ +/* reuse GL_TEXTURE_LUMINANCE_TYPE */ +/* reuse GL_TEXTURE_INTENSITY_TYPE */ +#endif + +#ifndef GL_VERSION_3_1 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT 0x8C2E +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#define GL_RED_SNORM 0x8F90 +#define GL_RG_SNORM 0x8F91 +#define GL_RGB_SNORM 0x8F92 +#define GL_RGBA_SNORM 0x8F93 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +/* Reuse tokens from ARB_copy_buffer */ +/* reuse GL_COPY_READ_BUFFER */ +/* reuse GL_COPY_WRITE_BUFFER */ +/* Reuse tokens from ARB_draw_instanced (none) */ +/* Reuse tokens from ARB_uniform_buffer_object */ +/* reuse GL_UNIFORM_BUFFER */ +/* reuse GL_UNIFORM_BUFFER_BINDING */ +/* reuse GL_UNIFORM_BUFFER_START */ +/* reuse GL_UNIFORM_BUFFER_SIZE */ +/* reuse GL_MAX_VERTEX_UNIFORM_BLOCKS */ +/* reuse GL_MAX_FRAGMENT_UNIFORM_BLOCKS */ +/* reuse GL_MAX_COMBINED_UNIFORM_BLOCKS */ +/* reuse GL_MAX_UNIFORM_BUFFER_BINDINGS */ +/* reuse GL_MAX_UNIFORM_BLOCK_SIZE */ +/* reuse GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS */ +/* reuse GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT */ +/* reuse GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH */ +/* reuse GL_ACTIVE_UNIFORM_BLOCKS */ +/* reuse GL_UNIFORM_TYPE */ +/* reuse GL_UNIFORM_SIZE */ +/* reuse GL_UNIFORM_NAME_LENGTH */ +/* reuse GL_UNIFORM_BLOCK_INDEX */ +/* reuse GL_UNIFORM_OFFSET */ +/* reuse GL_UNIFORM_ARRAY_STRIDE */ +/* reuse GL_UNIFORM_MATRIX_STRIDE */ +/* reuse GL_UNIFORM_IS_ROW_MAJOR */ +/* reuse GL_UNIFORM_BLOCK_BINDING */ +/* reuse GL_UNIFORM_BLOCK_DATA_SIZE */ +/* reuse GL_UNIFORM_BLOCK_NAME_LENGTH */ +/* reuse GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS */ +/* reuse GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER */ +/* reuse GL_INVALID_INDEX */ +#endif + +#ifndef GL_VERSION_3_2 +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +/* reuse GL_MAX_VARYING_COMPONENTS */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER */ +/* Reuse tokens from ARB_depth_clamp */ +/* reuse GL_DEPTH_CLAMP */ +/* Reuse tokens from ARB_draw_elements_base_vertex (none) */ +/* Reuse tokens from ARB_fragment_coord_conventions (none) */ +/* Reuse tokens from ARB_provoking_vertex */ +/* reuse GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION */ +/* reuse GL_FIRST_VERTEX_CONVENTION */ +/* reuse GL_LAST_VERTEX_CONVENTION */ +/* reuse GL_PROVOKING_VERTEX */ +/* Reuse tokens from ARB_seamless_cube_map */ +/* reuse GL_TEXTURE_CUBE_MAP_SEAMLESS */ +/* Reuse tokens from ARB_sync */ +/* reuse GL_MAX_SERVER_WAIT_TIMEOUT */ +/* reuse GL_OBJECT_TYPE */ +/* reuse GL_SYNC_CONDITION */ +/* reuse GL_SYNC_STATUS */ +/* reuse GL_SYNC_FLAGS */ +/* reuse GL_SYNC_FENCE */ +/* reuse GL_SYNC_GPU_COMMANDS_COMPLETE */ +/* reuse GL_UNSIGNALED */ +/* reuse GL_SIGNALED */ +/* reuse GL_ALREADY_SIGNALED */ +/* reuse GL_TIMEOUT_EXPIRED */ +/* reuse GL_CONDITION_SATISFIED */ +/* reuse GL_WAIT_FAILED */ +/* reuse GL_TIMEOUT_IGNORED */ +/* reuse GL_SYNC_FLUSH_COMMANDS_BIT */ +/* reuse GL_TIMEOUT_IGNORED */ +/* Reuse tokens from ARB_texture_multisample */ +/* reuse GL_SAMPLE_POSITION */ +/* reuse GL_SAMPLE_MASK */ +/* reuse GL_SAMPLE_MASK_VALUE */ +/* reuse GL_MAX_SAMPLE_MASK_WORDS */ +/* reuse GL_TEXTURE_2D_MULTISAMPLE */ +/* reuse GL_PROXY_TEXTURE_2D_MULTISAMPLE */ +/* reuse GL_TEXTURE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_TEXTURE_BINDING_2D_MULTISAMPLE */ +/* reuse GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_TEXTURE_SAMPLES */ +/* reuse GL_TEXTURE_FIXED_SAMPLE_LOCATIONS */ +/* reuse GL_SAMPLER_2D_MULTISAMPLE */ +/* reuse GL_INT_SAMPLER_2D_MULTISAMPLE */ +/* reuse GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE */ +/* reuse GL_SAMPLER_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_MAX_COLOR_TEXTURE_SAMPLES */ +/* reuse GL_MAX_DEPTH_TEXTURE_SAMPLES */ +/* reuse GL_MAX_INTEGER_SAMPLES */ +/* Don't need to reuse tokens from ARB_vertex_array_bgra since they're already in 1.2 core */ +#endif + +#ifndef GL_VERSION_3_3 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +/* Reuse tokens from ARB_blend_func_extended */ +/* reuse GL_SRC1_COLOR */ +/* reuse GL_ONE_MINUS_SRC1_COLOR */ +/* reuse GL_ONE_MINUS_SRC1_ALPHA */ +/* reuse GL_MAX_DUAL_SOURCE_DRAW_BUFFERS */ +/* Reuse tokens from ARB_explicit_attrib_location (none) */ +/* Reuse tokens from ARB_occlusion_query2 */ +/* reuse GL_ANY_SAMPLES_PASSED */ +/* Reuse tokens from ARB_sampler_objects */ +/* reuse GL_SAMPLER_BINDING */ +/* Reuse tokens from ARB_shader_bit_encoding (none) */ +/* Reuse tokens from ARB_texture_rgb10_a2ui */ +/* reuse GL_RGB10_A2UI */ +/* Reuse tokens from ARB_texture_swizzle */ +/* reuse GL_TEXTURE_SWIZZLE_R */ +/* reuse GL_TEXTURE_SWIZZLE_G */ +/* reuse GL_TEXTURE_SWIZZLE_B */ +/* reuse GL_TEXTURE_SWIZZLE_A */ +/* reuse GL_TEXTURE_SWIZZLE_RGBA */ +/* Reuse tokens from ARB_timer_query */ +/* reuse GL_TIME_ELAPSED */ +/* reuse GL_TIMESTAMP */ +/* Reuse tokens from ARB_vertex_type_2_10_10_10_rev */ +/* reuse GL_INT_2_10_10_10_REV */ +#endif + +#ifndef GL_VERSION_4_0 +#define GL_SAMPLE_SHADING 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F +#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F +/* Reuse tokens from ARB_texture_query_lod (none) */ +/* Reuse tokens from ARB_draw_buffers_blend (none) */ +/* Reuse tokens from ARB_draw_indirect */ +/* reuse GL_DRAW_INDIRECT_BUFFER */ +/* reuse GL_DRAW_INDIRECT_BUFFER_BINDING */ +/* Reuse tokens from ARB_gpu_shader5 */ +/* reuse GL_GEOMETRY_SHADER_INVOCATIONS */ +/* reuse GL_MAX_GEOMETRY_SHADER_INVOCATIONS */ +/* reuse GL_MIN_FRAGMENT_INTERPOLATION_OFFSET */ +/* reuse GL_MAX_FRAGMENT_INTERPOLATION_OFFSET */ +/* reuse GL_FRAGMENT_INTERPOLATION_OFFSET_BITS */ +/* reuse GL_MAX_VERTEX_STREAMS */ +/* Reuse tokens from ARB_gpu_shader_fp64 */ +/* reuse GL_DOUBLE_VEC2 */ +/* reuse GL_DOUBLE_VEC3 */ +/* reuse GL_DOUBLE_VEC4 */ +/* reuse GL_DOUBLE_MAT2 */ +/* reuse GL_DOUBLE_MAT3 */ +/* reuse GL_DOUBLE_MAT4 */ +/* reuse GL_DOUBLE_MAT2x3 */ +/* reuse GL_DOUBLE_MAT2x4 */ +/* reuse GL_DOUBLE_MAT3x2 */ +/* reuse GL_DOUBLE_MAT3x4 */ +/* reuse GL_DOUBLE_MAT4x2 */ +/* reuse GL_DOUBLE_MAT4x3 */ +/* Reuse tokens from ARB_shader_subroutine */ +/* reuse GL_ACTIVE_SUBROUTINES */ +/* reuse GL_ACTIVE_SUBROUTINE_UNIFORMS */ +/* reuse GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS */ +/* reuse GL_ACTIVE_SUBROUTINE_MAX_LENGTH */ +/* reuse GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH */ +/* reuse GL_MAX_SUBROUTINES */ +/* reuse GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS */ +/* reuse GL_NUM_COMPATIBLE_SUBROUTINES */ +/* reuse GL_COMPATIBLE_SUBROUTINES */ +/* Reuse tokens from ARB_tessellation_shader */ +/* reuse GL_PATCHES */ +/* reuse GL_PATCH_VERTICES */ +/* reuse GL_PATCH_DEFAULT_INNER_LEVEL */ +/* reuse GL_PATCH_DEFAULT_OUTER_LEVEL */ +/* reuse GL_TESS_CONTROL_OUTPUT_VERTICES */ +/* reuse GL_TESS_GEN_MODE */ +/* reuse GL_TESS_GEN_SPACING */ +/* reuse GL_TESS_GEN_VERTEX_ORDER */ +/* reuse GL_TESS_GEN_POINT_MODE */ +/* reuse GL_ISOLINES */ +/* reuse GL_FRACTIONAL_ODD */ +/* reuse GL_FRACTIONAL_EVEN */ +/* reuse GL_MAX_PATCH_VERTICES */ +/* reuse GL_MAX_TESS_GEN_LEVEL */ +/* reuse GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS */ +/* reuse GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS */ +/* reuse GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_PATCH_COMPONENTS */ +/* reuse GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS */ +/* reuse GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS */ +/* reuse GL_MAX_TESS_CONTROL_INPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS */ +/* reuse GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER */ +/* reuse GL_TESS_EVALUATION_SHADER */ +/* reuse GL_TESS_CONTROL_SHADER */ +/* Reuse tokens from ARB_texture_buffer_object_rgb32 (none) */ +/* Reuse tokens from ARB_transform_feedback2 */ +/* reuse GL_TRANSFORM_FEEDBACK */ +/* reuse GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED */ +/* reuse GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE */ +/* reuse GL_TRANSFORM_FEEDBACK_BINDING */ +/* Reuse tokens from ARB_transform_feedback3 */ +/* reuse GL_MAX_TRANSFORM_FEEDBACK_BUFFERS */ +/* reuse GL_MAX_VERTEX_STREAMS */ +#endif + +#ifndef GL_VERSION_4_1 +/* Reuse tokens from ARB_ES2_compatibility */ +/* reuse GL_FIXED */ +/* reuse GL_IMPLEMENTATION_COLOR_READ_TYPE */ +/* reuse GL_IMPLEMENTATION_COLOR_READ_FORMAT */ +/* reuse GL_LOW_FLOAT */ +/* reuse GL_MEDIUM_FLOAT */ +/* reuse GL_HIGH_FLOAT */ +/* reuse GL_LOW_INT */ +/* reuse GL_MEDIUM_INT */ +/* reuse GL_HIGH_INT */ +/* reuse GL_SHADER_COMPILER */ +/* reuse GL_NUM_SHADER_BINARY_FORMATS */ +/* reuse GL_MAX_VERTEX_UNIFORM_VECTORS */ +/* reuse GL_MAX_VARYING_VECTORS */ +/* reuse GL_MAX_FRAGMENT_UNIFORM_VECTORS */ +/* Reuse tokens from ARB_get_program_binary */ +/* reuse GL_PROGRAM_BINARY_RETRIEVABLE_HINT */ +/* reuse GL_PROGRAM_BINARY_LENGTH */ +/* reuse GL_NUM_PROGRAM_BINARY_FORMATS */ +/* reuse GL_PROGRAM_BINARY_FORMATS */ +/* Reuse tokens from ARB_separate_shader_objects */ +/* reuse GL_VERTEX_SHADER_BIT */ +/* reuse GL_FRAGMENT_SHADER_BIT */ +/* reuse GL_GEOMETRY_SHADER_BIT */ +/* reuse GL_TESS_CONTROL_SHADER_BIT */ +/* reuse GL_TESS_EVALUATION_SHADER_BIT */ +/* reuse GL_ALL_SHADER_BITS */ +/* reuse GL_PROGRAM_SEPARABLE */ +/* reuse GL_ACTIVE_PROGRAM */ +/* reuse GL_PROGRAM_PIPELINE_BINDING */ +/* Reuse tokens from ARB_shader_precision (none) */ +/* Reuse tokens from ARB_vertex_attrib_64bit - all are in GL 3.0 and 4.0 already */ +/* Reuse tokens from ARB_viewport_array - some are in GL 1.1 and ARB_provoking_vertex already */ +/* reuse GL_MAX_VIEWPORTS */ +/* reuse GL_VIEWPORT_SUBPIXEL_BITS */ +/* reuse GL_VIEWPORT_BOUNDS_RANGE */ +/* reuse GL_LAYER_PROVOKING_VERTEX */ +/* reuse GL_VIEWPORT_INDEX_PROVOKING_VERTEX */ +/* reuse GL_UNDEFINED_VERTEX */ +#endif + +#ifndef GL_VERSION_4_2 +/* Reuse tokens from ARB_base_instance (none) */ +/* Reuse tokens from ARB_shading_language_420pack (none) */ +/* Reuse tokens from ARB_transform_feedback_instanced (none) */ +/* Reuse tokens from ARB_compressed_texture_pixel_storage */ +/* reuse GL_UNPACK_COMPRESSED_BLOCK_WIDTH */ +/* reuse GL_UNPACK_COMPRESSED_BLOCK_HEIGHT */ +/* reuse GL_UNPACK_COMPRESSED_BLOCK_DEPTH */ +/* reuse GL_UNPACK_COMPRESSED_BLOCK_SIZE */ +/* reuse GL_PACK_COMPRESSED_BLOCK_WIDTH */ +/* reuse GL_PACK_COMPRESSED_BLOCK_HEIGHT */ +/* reuse GL_PACK_COMPRESSED_BLOCK_DEPTH */ +/* reuse GL_PACK_COMPRESSED_BLOCK_SIZE */ +/* Reuse tokens from ARB_conservative_depth (none) */ +/* Reuse tokens from ARB_internalformat_query */ +/* reuse GL_NUM_SAMPLE_COUNTS */ +/* Reuse tokens from ARB_map_buffer_alignment */ +/* reuse GL_MIN_MAP_BUFFER_ALIGNMENT */ +/* Reuse tokens from ARB_shader_atomic_counters */ +/* reuse GL_ATOMIC_COUNTER_BUFFER */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_BINDING */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_START */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_SIZE */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER */ +/* reuse GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER */ +/* reuse GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_MAX_VERTEX_ATOMIC_COUNTERS */ +/* reuse GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS */ +/* reuse GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS */ +/* reuse GL_MAX_GEOMETRY_ATOMIC_COUNTERS */ +/* reuse GL_MAX_FRAGMENT_ATOMIC_COUNTERS */ +/* reuse GL_MAX_COMBINED_ATOMIC_COUNTERS */ +/* reuse GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE */ +/* reuse GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS */ +/* reuse GL_ACTIVE_ATOMIC_COUNTER_BUFFERS */ +/* reuse GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX */ +/* reuse GL_UNSIGNED_INT_ATOMIC_COUNTER */ +/* Reuse tokens from ARB_shader_image_load_store */ +/* reuse GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT */ +/* reuse GL_ELEMENT_ARRAY_BARRIER_BIT */ +/* reuse GL_UNIFORM_BARRIER_BIT */ +/* reuse GL_TEXTURE_FETCH_BARRIER_BIT */ +/* reuse GL_SHADER_IMAGE_ACCESS_BARRIER_BIT */ +/* reuse GL_COMMAND_BARRIER_BIT */ +/* reuse GL_PIXEL_BUFFER_BARRIER_BIT */ +/* reuse GL_TEXTURE_UPDATE_BARRIER_BIT */ +/* reuse GL_BUFFER_UPDATE_BARRIER_BIT */ +/* reuse GL_FRAMEBUFFER_BARRIER_BIT */ +/* reuse GL_TRANSFORM_FEEDBACK_BARRIER_BIT */ +/* reuse GL_ATOMIC_COUNTER_BARRIER_BIT */ +/* reuse GL_ALL_BARRIER_BITS */ +/* reuse GL_MAX_IMAGE_UNITS */ +/* reuse GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS */ +/* reuse GL_IMAGE_BINDING_NAME */ +/* reuse GL_IMAGE_BINDING_LEVEL */ +/* reuse GL_IMAGE_BINDING_LAYERED */ +/* reuse GL_IMAGE_BINDING_LAYER */ +/* reuse GL_IMAGE_BINDING_ACCESS */ +/* reuse GL_IMAGE_1D */ +/* reuse GL_IMAGE_2D */ +/* reuse GL_IMAGE_3D */ +/* reuse GL_IMAGE_2D_RECT */ +/* reuse GL_IMAGE_CUBE */ +/* reuse GL_IMAGE_BUFFER */ +/* reuse GL_IMAGE_1D_ARRAY */ +/* reuse GL_IMAGE_2D_ARRAY */ +/* reuse GL_IMAGE_CUBE_MAP_ARRAY */ +/* reuse GL_IMAGE_2D_MULTISAMPLE */ +/* reuse GL_IMAGE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_INT_IMAGE_1D */ +/* reuse GL_INT_IMAGE_2D */ +/* reuse GL_INT_IMAGE_3D */ +/* reuse GL_INT_IMAGE_2D_RECT */ +/* reuse GL_INT_IMAGE_CUBE */ +/* reuse GL_INT_IMAGE_BUFFER */ +/* reuse GL_INT_IMAGE_1D_ARRAY */ +/* reuse GL_INT_IMAGE_2D_ARRAY */ +/* reuse GL_INT_IMAGE_CUBE_MAP_ARRAY */ +/* reuse GL_INT_IMAGE_2D_MULTISAMPLE */ +/* reuse GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_UNSIGNED_INT_IMAGE_1D */ +/* reuse GL_UNSIGNED_INT_IMAGE_2D */ +/* reuse GL_UNSIGNED_INT_IMAGE_3D */ +/* reuse GL_UNSIGNED_INT_IMAGE_2D_RECT */ +/* reuse GL_UNSIGNED_INT_IMAGE_CUBE */ +/* reuse GL_UNSIGNED_INT_IMAGE_BUFFER */ +/* reuse GL_UNSIGNED_INT_IMAGE_1D_ARRAY */ +/* reuse GL_UNSIGNED_INT_IMAGE_2D_ARRAY */ +/* reuse GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY */ +/* reuse GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE */ +/* reuse GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_MAX_IMAGE_SAMPLES */ +/* reuse GL_IMAGE_BINDING_FORMAT */ +/* reuse GL_IMAGE_FORMAT_COMPATIBILITY_TYPE */ +/* reuse GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE */ +/* reuse GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS */ +/* reuse GL_MAX_VERTEX_IMAGE_UNIFORMS */ +/* reuse GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS */ +/* reuse GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS */ +/* reuse GL_MAX_GEOMETRY_IMAGE_UNIFORMS */ +/* reuse GL_MAX_FRAGMENT_IMAGE_UNIFORMS */ +/* reuse GL_MAX_COMBINED_IMAGE_UNIFORMS */ +/* Reuse tokens from ARB_shading_language_packing (none) */ +/* Reuse tokens from ARB_texture_storage */ +/* reuse GL_TEXTURE_IMMUTABLE_FORMAT */ +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#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_env_add +#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_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifndef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifndef GL_ARB_window_pos +#endif + +#ifndef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#endif + +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#endif + +#ifndef GL_ARB_shader_objects +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#endif + +#ifndef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +#ifndef GL_ARB_texture_float +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif + +#ifndef GL_ARB_depth_buffer_float +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#endif + +#ifndef GL_ARB_draw_instanced +#endif + +#ifndef GL_ARB_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING GL_FRAMEBUFFER_BINDING +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#endif + +#ifndef GL_ARB_framebuffer_object_DEPRECATED +#define GL_INDEX 0x8222 +#define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE 0x8C15 +#endif + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif + +#ifndef GL_ARB_geometry_shader4 +#define GL_LINES_ADJACENCY_ARB 0x000A +#define GL_LINE_STRIP_ADJACENCY_ARB 0x000B +#define GL_TRIANGLES_ADJACENCY_ARB 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D +#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 +#define GL_GEOMETRY_SHADER_ARB 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 +/* reuse GL_MAX_VARYING_COMPONENTS */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER */ +#endif + +#ifndef GL_ARB_half_float_vertex +#define GL_HALF_FLOAT 0x140B +#endif + +#ifndef GL_ARB_instanced_arrays +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE +#endif + +#ifndef GL_ARB_map_buffer_range +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#endif + +#ifndef GL_ARB_texture_buffer_object +#define GL_TEXTURE_BUFFER_ARB 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E +#endif + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#endif + +#ifndef GL_ARB_texture_rg +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#endif + +#ifndef GL_ARB_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#endif + +#ifndef GL_ARB_uniform_buffer_object +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFFu +#endif + +#ifndef GL_ARB_compatibility +/* ARB_compatibility just defines tokens from core 3.0 */ +#endif + +#ifndef GL_ARB_copy_buffer +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#endif + +#ifndef GL_ARB_shader_texture_lod +#endif + +#ifndef GL_ARB_depth_clamp +#define GL_DEPTH_CLAMP 0x864F +#endif + +#ifndef GL_ARB_draw_elements_base_vertex +#endif + +#ifndef GL_ARB_fragment_coord_conventions +#endif + +#ifndef GL_ARB_provoking_vertex +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#endif + +#ifndef GL_ARB_seamless_cube_map +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#endif + +#ifndef GL_ARB_sync +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull +#endif + +#ifndef GL_ARB_texture_multisample +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +#endif + +#ifndef GL_ARB_vertex_array_bgra +/* reuse GL_BGRA */ +#endif + +#ifndef GL_ARB_draw_buffers_blend +#endif + +#ifndef GL_ARB_sample_shading +#define GL_SAMPLE_SHADING_ARB 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 +#endif + +#ifndef GL_ARB_texture_cube_map_array +#define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F +#endif + +#ifndef GL_ARB_texture_gather +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F +#endif + +#ifndef GL_ARB_texture_query_lod +#endif + +#ifndef GL_ARB_shading_language_include +#define GL_SHADER_INCLUDE_ARB 0x8DAE +#define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 +#define GL_NAMED_STRING_TYPE_ARB 0x8DEA +#endif + +#ifndef GL_ARB_texture_compression_bptc +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif + +#ifndef GL_ARB_blend_func_extended +#define GL_SRC1_COLOR 0x88F9 +/* reuse GL_SRC1_ALPHA */ +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#endif + +#ifndef GL_ARB_explicit_attrib_location +#endif + +#ifndef GL_ARB_occlusion_query2 +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#endif + +#ifndef GL_ARB_sampler_objects +#define GL_SAMPLER_BINDING 0x8919 +#endif + +#ifndef GL_ARB_shader_bit_encoding +#endif + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_RGB10_A2UI 0x906F +#endif + +#ifndef GL_ARB_texture_swizzle +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#endif + +#ifndef GL_ARB_timer_query +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#endif + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +/* reuse GL_UNSIGNED_INT_2_10_10_10_REV */ +#define GL_INT_2_10_10_10_REV 0x8D9F +#endif + +#ifndef GL_ARB_draw_indirect +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#endif + +#ifndef GL_ARB_gpu_shader5 +#define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C +#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D +/* reuse GL_MAX_VERTEX_STREAMS */ +#endif + +#ifndef GL_ARB_gpu_shader_fp64 +/* reuse GL_DOUBLE */ +#define GL_DOUBLE_VEC2 0x8FFC +#define GL_DOUBLE_VEC3 0x8FFD +#define GL_DOUBLE_VEC4 0x8FFE +#define GL_DOUBLE_MAT2 0x8F46 +#define GL_DOUBLE_MAT3 0x8F47 +#define GL_DOUBLE_MAT4 0x8F48 +#define GL_DOUBLE_MAT2x3 0x8F49 +#define GL_DOUBLE_MAT2x4 0x8F4A +#define GL_DOUBLE_MAT3x2 0x8F4B +#define GL_DOUBLE_MAT3x4 0x8F4C +#define GL_DOUBLE_MAT4x2 0x8F4D +#define GL_DOUBLE_MAT4x3 0x8F4E +#endif + +#ifndef GL_ARB_shader_subroutine +#define GL_ACTIVE_SUBROUTINES 0x8DE5 +#define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 +#define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 +#define GL_MAX_SUBROUTINES 0x8DE7 +#define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 +#define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A +#define GL_COMPATIBLE_SUBROUTINES 0x8E4B +/* reuse GL_UNIFORM_SIZE */ +/* reuse GL_UNIFORM_NAME_LENGTH */ +#endif + +#ifndef GL_ARB_tessellation_shader +#define GL_PATCHES 0x000E +#define GL_PATCH_VERTICES 0x8E72 +#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 +#define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 +#define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 +#define GL_TESS_GEN_MODE 0x8E76 +#define GL_TESS_GEN_SPACING 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER 0x8E78 +#define GL_TESS_GEN_POINT_MODE 0x8E79 +/* reuse GL_TRIANGLES */ +/* reuse GL_QUADS */ +#define GL_ISOLINES 0x8E7A +/* reuse GL_EQUAL */ +#define GL_FRACTIONAL_ODD 0x8E7B +#define GL_FRACTIONAL_EVEN 0x8E7C +/* reuse GL_CCW */ +/* reuse GL_CW */ +#define GL_MAX_PATCH_VERTICES 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 +#define GL_TESS_EVALUATION_SHADER 0x8E87 +#define GL_TESS_CONTROL_SHADER 0x8E88 +#endif + +#ifndef GL_ARB_texture_buffer_object_rgb32 +/* reuse GL_RGB32F */ +/* reuse GL_RGB32UI */ +/* reuse GL_RGB32I */ +#endif + +#ifndef GL_ARB_transform_feedback2 +#define GL_TRANSFORM_FEEDBACK 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 +#endif + +#ifndef GL_ARB_transform_feedback3 +#define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 +#define GL_MAX_VERTEX_STREAMS 0x8E71 +#endif + +#ifndef GL_ARB_ES2_compatibility +#define GL_FIXED 0x140C +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_SHADER_COMPILER 0x8DFA +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#endif + +#ifndef GL_ARB_get_program_binary +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#endif + +#ifndef GL_ARB_separate_shader_objects +#define GL_VERTEX_SHADER_BIT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT 0x00000002 +#define GL_GEOMETRY_SHADER_BIT 0x00000004 +#define GL_TESS_CONTROL_SHADER_BIT 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 +#define GL_ALL_SHADER_BITS 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE 0x8258 +#define GL_ACTIVE_PROGRAM 0x8259 +#define GL_PROGRAM_PIPELINE_BINDING 0x825A +#endif + +#ifndef GL_ARB_shader_precision +#endif + +#ifndef GL_ARB_vertex_attrib_64bit +/* reuse GL_RGB32I */ +/* reuse GL_DOUBLE_VEC2 */ +/* reuse GL_DOUBLE_VEC3 */ +/* reuse GL_DOUBLE_VEC4 */ +/* reuse GL_DOUBLE_MAT2 */ +/* reuse GL_DOUBLE_MAT3 */ +/* reuse GL_DOUBLE_MAT4 */ +/* reuse GL_DOUBLE_MAT2x3 */ +/* reuse GL_DOUBLE_MAT2x4 */ +/* reuse GL_DOUBLE_MAT3x2 */ +/* reuse GL_DOUBLE_MAT3x4 */ +/* reuse GL_DOUBLE_MAT4x2 */ +/* reuse GL_DOUBLE_MAT4x3 */ +#endif + +#ifndef GL_ARB_viewport_array +/* reuse GL_SCISSOR_BOX */ +/* reuse GL_VIEWPORT */ +/* reuse GL_DEPTH_RANGE */ +/* reuse GL_SCISSOR_TEST */ +#define GL_MAX_VIEWPORTS 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE 0x825D +#define GL_LAYER_PROVOKING_VERTEX 0x825E +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F +#define GL_UNDEFINED_VERTEX 0x8260 +/* reuse GL_FIRST_VERTEX_CONVENTION */ +/* reuse GL_LAST_VERTEX_CONVENTION */ +/* reuse GL_PROVOKING_VERTEX */ +#endif + +#ifndef GL_ARB_cl_event +#define GL_SYNC_CL_EVENT_ARB 0x8240 +#define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 +#endif + +#ifndef GL_ARB_debug_output +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 +#endif + +#ifndef GL_ARB_robustness +/* reuse GL_NO_ERROR */ +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GL_NO_RESET_NOTIFICATION_ARB 0x8261 +#endif + +#ifndef GL_ARB_shader_stencil_export +#endif + +#ifndef GL_ARB_base_instance +#endif + +#ifndef GL_ARB_shading_language_420pack +#endif + +#ifndef GL_ARB_transform_feedback_instanced +#endif + +#ifndef GL_ARB_compressed_texture_pixel_storage +#define GL_UNPACK_COMPRESSED_BLOCK_WIDTH 0x9127 +#define GL_UNPACK_COMPRESSED_BLOCK_HEIGHT 0x9128 +#define GL_UNPACK_COMPRESSED_BLOCK_DEPTH 0x9129 +#define GL_UNPACK_COMPRESSED_BLOCK_SIZE 0x912A +#define GL_PACK_COMPRESSED_BLOCK_WIDTH 0x912B +#define GL_PACK_COMPRESSED_BLOCK_HEIGHT 0x912C +#define GL_PACK_COMPRESSED_BLOCK_DEPTH 0x912D +#define GL_PACK_COMPRESSED_BLOCK_SIZE 0x912E +#endif + +#ifndef GL_ARB_conservative_depth +#endif + +#ifndef GL_ARB_internalformat_query +#define GL_NUM_SAMPLE_COUNTS 0x9380 +#endif + +#ifndef GL_ARB_map_buffer_alignment +#define GL_MIN_MAP_BUFFER_ALIGNMENT 0x90BC +#endif + +#ifndef GL_ARB_shader_atomic_counters +#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 +#define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 +#define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 +#define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 +#define GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE 0x92C4 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS 0x92C5 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES 0x92C6 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER 0x92C7 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER 0x92C8 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER 0x92C9 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER 0x92CA +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER 0x92CB +#define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS 0x92CD +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS 0x92CE +#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS 0x92CF +#define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 +#define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 +#define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS 0x92D3 +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS 0x92D4 +#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS 0x92D5 +#define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 +#define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC +#define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 +#define GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX 0x92DA +#define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB +#endif + +#ifndef GL_ARB_shader_image_load_store +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 +#define GL_COMMAND_BARRIER_BIT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 +#define GL_ALL_BARRIER_BITS 0xFFFFFFFF +#define GL_MAX_IMAGE_UNITS 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS 0x8F39 +#define GL_IMAGE_BINDING_NAME 0x8F3A +#define GL_IMAGE_BINDING_LEVEL 0x8F3B +#define GL_IMAGE_BINDING_LAYERED 0x8F3C +#define GL_IMAGE_BINDING_LAYER 0x8F3D +#define GL_IMAGE_BINDING_ACCESS 0x8F3E +#define GL_IMAGE_1D 0x904C +#define GL_IMAGE_2D 0x904D +#define GL_IMAGE_3D 0x904E +#define GL_IMAGE_2D_RECT 0x904F +#define GL_IMAGE_CUBE 0x9050 +#define GL_IMAGE_BUFFER 0x9051 +#define GL_IMAGE_1D_ARRAY 0x9052 +#define GL_IMAGE_2D_ARRAY 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY 0x9056 +#define GL_INT_IMAGE_1D 0x9057 +#define GL_INT_IMAGE_2D 0x9058 +#define GL_INT_IMAGE_3D 0x9059 +#define GL_INT_IMAGE_2D_RECT 0x905A +#define GL_INT_IMAGE_CUBE 0x905B +#define GL_INT_IMAGE_BUFFER 0x905C +#define GL_INT_IMAGE_1D_ARRAY 0x905D +#define GL_INT_IMAGE_2D_ARRAY 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x906C +#define GL_MAX_IMAGE_SAMPLES 0x906D +#define GL_IMAGE_BINDING_FORMAT 0x906E +#define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 +#define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA +#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS 0x90CB +#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS 0x90CC +#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS 0x90CD +#define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE +#define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF +#endif + +#ifndef GL_ARB_shading_language_packing +#endif + +#ifndef GL_ARB_texture_storage +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +#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_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#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_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#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_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#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_SGIX_impact_pixel_texture +#define GL_PIXEL_TEX_GEN_Q_CEILING_SGIX 0x8184 +#define GL_PIXEL_TEX_GEN_Q_ROUND_SGIX 0x8185 +#define GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX 0x8186 +#define GL_PIXEL_TEX_GEN_ALPHA_REPLACE_SGIX 0x8187 +#define GL_PIXEL_TEX_GEN_ALPHA_NO_REPLACE_SGIX 0x8188 +#define GL_PIXEL_TEX_GEN_ALPHA_LS_SGIX 0x8189 +#define GL_PIXEL_TEX_GEN_ALPHA_MS_SGIX 0x818A +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#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_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#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 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#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_MODELVIEW1_MATRIX_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 +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#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 + +#ifndef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifndef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#endif + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifndef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#endif + +#ifndef GL_NV_texture_compression_vtc +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifndef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV GL_OFFSET_TEXTURE_MATRIX_NV +#define GL_OFFSET_TEXTURE_2D_SCALE_NV GL_OFFSET_TEXTURE_SCALE_NV +#define GL_OFFSET_TEXTURE_2D_BIAS_NV GL_OFFSET_TEXTURE_BIAS_NV +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifndef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif + +#ifndef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifndef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifndef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +#endif + +#ifndef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#endif + +#ifndef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifndef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#endif + +#ifndef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifndef GL_NV_vertex_program1_1 +#endif + +#ifndef GL_EXT_shadow_funcs +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifndef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8A0C +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8A0D +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x8A0E +#endif + +#ifndef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CLIENT_APPLE 0x85B4 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifndef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_TYPE_RGBA_FLOAT_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifndef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifndef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifndef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#endif + +#ifndef GL_NV_half_float +#define GL_HALF_FLOAT_NV 0x140B +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#endif + +#ifndef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifndef GL_NV_vertex_program2 +#endif + +#ifndef GL_ATI_map_object_buffer +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#endif + +#ifndef GL_OES_read_format +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#endif + +#ifndef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif + +#ifndef GL_NV_fragment_program_option +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif + +#ifndef GL_NV_vertex_program2_option +/* reuse GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV */ +/* reuse GL_MAX_PROGRAM_CALL_DEPTH_NV */ +#endif + +#ifndef GL_NV_vertex_program3 +/* reuse GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB */ +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#endif + +#ifndef GL_GREMEDY_string_marker +#endif + +#ifndef GL_EXT_packed_depth_stencil +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 +#endif + +#ifndef GL_EXT_stencil_clear_tag +#define GL_STENCIL_TAG_BITS_EXT 0x88F2 +#define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 +#endif + +#ifndef GL_EXT_texture_sRGB +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif + +#ifndef GL_EXT_framebuffer_blit +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT GL_FRAMEBUFFER_BINDING_EXT +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +#endif + +#ifndef GL_EXT_framebuffer_multisample +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +#endif + +#ifndef GL_MESAX_texture_stack +#define GL_TEXTURE_1D_STACK_MESAX 0x8759 +#define GL_TEXTURE_2D_STACK_MESAX 0x875A +#define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B +#define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C +#define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D +#define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E +#endif + +#ifndef GL_EXT_timer_query +#define GL_TIME_ELAPSED_EXT 0x88BF +#endif + +#ifndef GL_EXT_gpu_program_parameters +#endif + +#ifndef GL_APPLE_flush_buffer_range +#define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 +#define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 +#endif + +#ifndef GL_NV_gpu_program4 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 +#define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 +#define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 +#define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 +#define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 +#define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 +#define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 +#endif + +#ifndef GL_NV_geometry_program4 +#define GL_LINES_ADJACENCY_EXT 0x000A +#define GL_LINE_STRIP_ADJACENCY_EXT 0x000B +#define GL_TRIANGLES_ADJACENCY_EXT 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D +#define GL_GEOMETRY_PROGRAM_NV 0x8C26 +#define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 +#define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 +#define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 +#define GL_PROGRAM_POINT_SIZE_EXT 0x8642 +#endif + +#ifndef GL_EXT_geometry_shader4 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +/* reuse GL_GEOMETRY_VERTICES_OUT_EXT */ +/* reuse GL_GEOMETRY_INPUT_TYPE_EXT */ +/* reuse GL_GEOMETRY_OUTPUT_TYPE_EXT */ +/* reuse GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT */ +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE +#define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 +/* reuse GL_LINES_ADJACENCY_EXT */ +/* reuse GL_LINE_STRIP_ADJACENCY_EXT */ +/* reuse GL_TRIANGLES_ADJACENCY_EXT */ +/* reuse GL_TRIANGLE_STRIP_ADJACENCY_EXT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT */ +/* reuse GL_PROGRAM_POINT_SIZE_EXT */ +#endif + +#ifndef GL_NV_vertex_program4 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_NV 0x88FD +#endif + +#ifndef GL_EXT_gpu_shader4 +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 +#define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 +#define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 +#define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 +#define GL_INT_SAMPLER_1D_EXT 0x8DC9 +#define GL_INT_SAMPLER_2D_EXT 0x8DCA +#define GL_INT_SAMPLER_3D_EXT 0x8DCB +#define GL_INT_SAMPLER_CUBE_EXT 0x8DCC +#define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD +#define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 +#endif + +#ifndef GL_EXT_draw_instanced +#endif + +#ifndef GL_EXT_packed_float +#define GL_R11F_G11F_B10F_EXT 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B +#define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C +#endif + +#ifndef GL_EXT_texture_array +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT */ +#endif + +#ifndef GL_EXT_texture_buffer_object +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E +#endif + +#ifndef GL_EXT_texture_compression_latc +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 +#endif + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif + +#ifndef GL_EXT_texture_shared_exponent +#define GL_RGB9_E5_EXT 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E +#define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F +#endif + +#ifndef GL_NV_depth_buffer_float +#define GL_DEPTH_COMPONENT32F_NV 0x8DAB +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD +#define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF +#endif + +#ifndef GL_NV_fragment_program4 +#endif + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB +#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 +#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 +#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 +#endif + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA +#endif + +#ifndef GL_NV_geometry_shader4 +#endif + +#ifndef GL_NV_parameter_buffer_object +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 +#define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 +#define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 +#define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 +#endif + +#ifndef GL_EXT_draw_buffers2 +#endif + +#ifndef GL_NV_transform_feedback +#define GL_BACK_PRIMARY_COLOR_NV 0x8C77 +#define GL_BACK_SECONDARY_COLOR_NV 0x8C78 +#define GL_TEXTURE_COORD_NV 0x8C79 +#define GL_CLIP_DISTANCE_NV 0x8C7A +#define GL_VERTEX_ID_NV 0x8C7B +#define GL_PRIMITIVE_ID_NV 0x8C7C +#define GL_GENERIC_ATTRIB_NV 0x8C7D +#define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 +#define GL_ACTIVE_VARYINGS_NV 0x8C81 +#define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 +#define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 +#define GL_PRIMITIVES_GENERATED_NV 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 +#define GL_RASTERIZER_DISCARD_NV 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_ATTRIBS_NV 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C +#define GL_SEPARATE_ATTRIBS_NV 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F +#define GL_LAYER_NV 0x8DAA +#define GL_NEXT_BUFFER_NV -2 +#define GL_SKIP_COMPONENTS4_NV -3 +#define GL_SKIP_COMPONENTS3_NV -4 +#define GL_SKIP_COMPONENTS2_NV -5 +#define GL_SKIP_COMPONENTS1_NV -6 +#endif + +#ifndef GL_EXT_bindable_uniform +#define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 +#define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 +#define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 +#define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED +#define GL_UNIFORM_BUFFER_EXT 0x8DEE +#define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF +#endif + +#ifndef GL_EXT_texture_integer +#define GL_RGBA32UI_EXT 0x8D70 +#define GL_RGB32UI_EXT 0x8D71 +#define GL_ALPHA32UI_EXT 0x8D72 +#define GL_INTENSITY32UI_EXT 0x8D73 +#define GL_LUMINANCE32UI_EXT 0x8D74 +#define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 +#define GL_RGBA16UI_EXT 0x8D76 +#define GL_RGB16UI_EXT 0x8D77 +#define GL_ALPHA16UI_EXT 0x8D78 +#define GL_INTENSITY16UI_EXT 0x8D79 +#define GL_LUMINANCE16UI_EXT 0x8D7A +#define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B +#define GL_RGBA8UI_EXT 0x8D7C +#define GL_RGB8UI_EXT 0x8D7D +#define GL_ALPHA8UI_EXT 0x8D7E +#define GL_INTENSITY8UI_EXT 0x8D7F +#define GL_LUMINANCE8UI_EXT 0x8D80 +#define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 +#define GL_RGBA32I_EXT 0x8D82 +#define GL_RGB32I_EXT 0x8D83 +#define GL_ALPHA32I_EXT 0x8D84 +#define GL_INTENSITY32I_EXT 0x8D85 +#define GL_LUMINANCE32I_EXT 0x8D86 +#define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 +#define GL_RGBA16I_EXT 0x8D88 +#define GL_RGB16I_EXT 0x8D89 +#define GL_ALPHA16I_EXT 0x8D8A +#define GL_INTENSITY16I_EXT 0x8D8B +#define GL_LUMINANCE16I_EXT 0x8D8C +#define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D +#define GL_RGBA8I_EXT 0x8D8E +#define GL_RGB8I_EXT 0x8D8F +#define GL_ALPHA8I_EXT 0x8D90 +#define GL_INTENSITY8I_EXT 0x8D91 +#define GL_LUMINANCE8I_EXT 0x8D92 +#define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 +#define GL_RED_INTEGER_EXT 0x8D94 +#define GL_GREEN_INTEGER_EXT 0x8D95 +#define GL_BLUE_INTEGER_EXT 0x8D96 +#define GL_ALPHA_INTEGER_EXT 0x8D97 +#define GL_RGB_INTEGER_EXT 0x8D98 +#define GL_RGBA_INTEGER_EXT 0x8D99 +#define GL_BGR_INTEGER_EXT 0x8D9A +#define GL_BGRA_INTEGER_EXT 0x8D9B +#define GL_LUMINANCE_INTEGER_EXT 0x8D9C +#define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D +#define GL_RGBA_INTEGER_MODE_EXT 0x8D9E +#endif + +#ifndef GL_GREMEDY_frame_terminator +#endif + +#ifndef GL_NV_conditional_render +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 +#endif + +#ifndef GL_NV_present_video +#define GL_FRAME_NV 0x8E26 +#define GL_FIELDS_NV 0x8E27 +#define GL_CURRENT_TIME_NV 0x8E28 +#define GL_NUM_FILL_STREAMS_NV 0x8E29 +#define GL_PRESENT_TIME_NV 0x8E2A +#define GL_PRESENT_DURATION_NV 0x8E2B +#endif + +#ifndef GL_EXT_transform_feedback +#define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F +#define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C +#define GL_SEPARATE_ATTRIBS_EXT 0x8C8D +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 +#define GL_RASTERIZER_DISCARD_EXT 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 +#endif + +#ifndef GL_EXT_direct_state_access +#define GL_PROGRAM_MATRIX_EXT 0x8E2D +#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E +#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F +#endif + +#ifndef GL_EXT_vertex_array_bgra +/* reuse GL_BGRA */ +#endif + +#ifndef GL_EXT_texture_swizzle +#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 +#define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 +#define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 +#define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 +#endif + +#ifndef GL_NV_explicit_multisample +#define GL_SAMPLE_POSITION_NV 0x8E50 +#define GL_SAMPLE_MASK_NV 0x8E51 +#define GL_SAMPLE_MASK_VALUE_NV 0x8E52 +#define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 +#define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 +#define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 +#define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 +#define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 +#define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 +#define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 +#endif + +#ifndef GL_NV_transform_feedback2 +#define GL_TRANSFORM_FEEDBACK_NV 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED_NV 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE_NV 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING_NV 0x8E25 +#endif + +#ifndef GL_ATI_meminfo +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif + +#ifndef GL_AMD_performance_monitor +#define GL_COUNTER_TYPE_AMD 0x8BC0 +#define GL_COUNTER_RANGE_AMD 0x8BC1 +#define GL_UNSIGNED_INT64_AMD 0x8BC2 +#define GL_PERCENTAGE_AMD 0x8BC3 +#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 +#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 +#define GL_PERFMON_RESULT_AMD 0x8BC6 +#endif + +#ifndef GL_AMD_texture_texture4 +#endif + +#ifndef GL_AMD_vertex_shader_tesselator +#define GL_SAMPLER_BUFFER_AMD 0x9001 +#define GL_INT_SAMPLER_BUFFER_AMD 0x9002 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_AMD 0x9003 +#define GL_TESSELLATION_MODE_AMD 0x9004 +#define GL_TESSELLATION_FACTOR_AMD 0x9005 +#define GL_DISCRETE_AMD 0x9006 +#define GL_CONTINUOUS_AMD 0x9007 +#endif + +#ifndef GL_EXT_provoking_vertex +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E +#define GL_PROVOKING_VERTEX_EXT 0x8E4F +#endif + +#ifndef GL_EXT_texture_snorm +#define GL_ALPHA_SNORM 0x9010 +#define GL_LUMINANCE_SNORM 0x9011 +#define GL_LUMINANCE_ALPHA_SNORM 0x9012 +#define GL_INTENSITY_SNORM 0x9013 +#define GL_ALPHA8_SNORM 0x9014 +#define GL_LUMINANCE8_SNORM 0x9015 +#define GL_LUMINANCE8_ALPHA8_SNORM 0x9016 +#define GL_INTENSITY8_SNORM 0x9017 +#define GL_ALPHA16_SNORM 0x9018 +#define GL_LUMINANCE16_SNORM 0x9019 +#define GL_LUMINANCE16_ALPHA16_SNORM 0x901A +#define GL_INTENSITY16_SNORM 0x901B +/* reuse GL_RED_SNORM */ +/* reuse GL_RG_SNORM */ +/* reuse GL_RGB_SNORM */ +/* reuse GL_RGBA_SNORM */ +/* reuse GL_R8_SNORM */ +/* reuse GL_RG8_SNORM */ +/* reuse GL_RGB8_SNORM */ +/* reuse GL_RGBA8_SNORM */ +/* reuse GL_R16_SNORM */ +/* reuse GL_RG16_SNORM */ +/* reuse GL_RGB16_SNORM */ +/* reuse GL_RGBA16_SNORM */ +/* reuse GL_SIGNED_NORMALIZED */ +#endif + +#ifndef GL_AMD_draw_buffers_blend +#endif + +#ifndef GL_APPLE_texture_range +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +/* reuse GL_STORAGE_CACHED_APPLE */ +/* reuse GL_STORAGE_SHARED_APPLE */ +#endif + +#ifndef GL_APPLE_float_pixels +#define GL_HALF_APPLE 0x140B +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#define GL_COLOR_FLOAT_APPLE 0x8A0F +#endif + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_VERTEX_ATTRIB_MAP1_APPLE 0x8A00 +#define GL_VERTEX_ATTRIB_MAP2_APPLE 0x8A01 +#define GL_VERTEX_ATTRIB_MAP1_SIZE_APPLE 0x8A02 +#define GL_VERTEX_ATTRIB_MAP1_COEFF_APPLE 0x8A03 +#define GL_VERTEX_ATTRIB_MAP1_ORDER_APPLE 0x8A04 +#define GL_VERTEX_ATTRIB_MAP1_DOMAIN_APPLE 0x8A05 +#define GL_VERTEX_ATTRIB_MAP2_SIZE_APPLE 0x8A06 +#define GL_VERTEX_ATTRIB_MAP2_COEFF_APPLE 0x8A07 +#define GL_VERTEX_ATTRIB_MAP2_ORDER_APPLE 0x8A08 +#define GL_VERTEX_ATTRIB_MAP2_DOMAIN_APPLE 0x8A09 +#endif + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_AUX_DEPTH_STENCIL_APPLE 0x8A14 +#endif + +#ifndef GL_APPLE_object_purgeable +#define GL_BUFFER_OBJECT_APPLE 0x85B3 +#define GL_RELEASED_APPLE 0x8A19 +#define GL_VOLATILE_APPLE 0x8A1A +#define GL_RETAINED_APPLE 0x8A1B +#define GL_UNDEFINED_APPLE 0x8A1C +#define GL_PURGEABLE_APPLE 0x8A1D +#endif + +#ifndef GL_APPLE_row_bytes +#define GL_PACK_ROW_BYTES_APPLE 0x8A15 +#define GL_UNPACK_ROW_BYTES_APPLE 0x8A16 +#endif + +#ifndef GL_APPLE_rgb_422 +#define GL_RGB_422_APPLE 0x8A1F +/* reuse GL_UNSIGNED_SHORT_8_8_APPLE */ +/* reuse GL_UNSIGNED_SHORT_8_8_REV_APPLE */ +#endif + +#ifndef GL_NV_video_capture +#define GL_VIDEO_BUFFER_NV 0x9020 +#define GL_VIDEO_BUFFER_BINDING_NV 0x9021 +#define GL_FIELD_UPPER_NV 0x9022 +#define GL_FIELD_LOWER_NV 0x9023 +#define GL_NUM_VIDEO_CAPTURE_STREAMS_NV 0x9024 +#define GL_NEXT_VIDEO_CAPTURE_BUFFER_STATUS_NV 0x9025 +#define GL_VIDEO_CAPTURE_TO_422_SUPPORTED_NV 0x9026 +#define GL_LAST_VIDEO_CAPTURE_STATUS_NV 0x9027 +#define GL_VIDEO_BUFFER_PITCH_NV 0x9028 +#define GL_VIDEO_COLOR_CONVERSION_MATRIX_NV 0x9029 +#define GL_VIDEO_COLOR_CONVERSION_MAX_NV 0x902A +#define GL_VIDEO_COLOR_CONVERSION_MIN_NV 0x902B +#define GL_VIDEO_COLOR_CONVERSION_OFFSET_NV 0x902C +#define GL_VIDEO_BUFFER_INTERNAL_FORMAT_NV 0x902D +#define GL_PARTIAL_SUCCESS_NV 0x902E +#define GL_SUCCESS_NV 0x902F +#define GL_FAILURE_NV 0x9030 +#define GL_YCBYCR8_422_NV 0x9031 +#define GL_YCBAYCR8A_4224_NV 0x9032 +#define GL_Z6Y10Z6CB10Z6Y10Z6CR10_422_NV 0x9033 +#define GL_Z6Y10Z6CB10Z6A10Z6Y10Z6CR10Z6A10_4224_NV 0x9034 +#define GL_Z4Y12Z4CB12Z4Y12Z4CR12_422_NV 0x9035 +#define GL_Z4Y12Z4CB12Z4A12Z4Y12Z4CR12Z4A12_4224_NV 0x9036 +#define GL_Z4Y12Z4CB12Z4CR12_444_NV 0x9037 +#define GL_VIDEO_CAPTURE_FRAME_WIDTH_NV 0x9038 +#define GL_VIDEO_CAPTURE_FRAME_HEIGHT_NV 0x9039 +#define GL_VIDEO_CAPTURE_FIELD_UPPER_HEIGHT_NV 0x903A +#define GL_VIDEO_CAPTURE_FIELD_LOWER_HEIGHT_NV 0x903B +#define GL_VIDEO_CAPTURE_SURFACE_ORIGIN_NV 0x903C +#endif + +#ifndef GL_NV_copy_image +#endif + +#ifndef GL_EXT_separate_shader_objects +#define GL_ACTIVE_PROGRAM_EXT 0x8B8D +#endif + +#ifndef GL_NV_parameter_buffer_object2 +#endif + +#ifndef GL_NV_shader_buffer_load +#define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D +#define GL_GPU_ADDRESS_NV 0x8F34 +#define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 +#endif + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E +#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F +#define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 +#define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 +#define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 +#define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 +#define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 +#define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 +#define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 +#define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 +#define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 +#define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 +#define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A +#define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B +#define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C +#define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D +#define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E +#define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F +#define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 +#define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 +#define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 +#define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 +#define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 +#define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 +#define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 +#endif + +#ifndef GL_NV_texture_barrier +#endif + +#ifndef GL_AMD_shader_stencil_export +#endif + +#ifndef GL_AMD_seamless_cubemap_per_texture +/* reuse GL_TEXTURE_CUBE_MAP_SEAMLESS */ +#endif + +#ifndef GL_AMD_conservative_depth +#endif + +#ifndef GL_EXT_shader_image_load_store +#define GL_MAX_IMAGE_UNITS_EXT 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS_EXT 0x8F39 +#define GL_IMAGE_BINDING_NAME_EXT 0x8F3A +#define GL_IMAGE_BINDING_LEVEL_EXT 0x8F3B +#define GL_IMAGE_BINDING_LAYERED_EXT 0x8F3C +#define GL_IMAGE_BINDING_LAYER_EXT 0x8F3D +#define GL_IMAGE_BINDING_ACCESS_EXT 0x8F3E +#define GL_IMAGE_1D_EXT 0x904C +#define GL_IMAGE_2D_EXT 0x904D +#define GL_IMAGE_3D_EXT 0x904E +#define GL_IMAGE_2D_RECT_EXT 0x904F +#define GL_IMAGE_CUBE_EXT 0x9050 +#define GL_IMAGE_BUFFER_EXT 0x9051 +#define GL_IMAGE_1D_ARRAY_EXT 0x9052 +#define GL_IMAGE_2D_ARRAY_EXT 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE_EXT 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9056 +#define GL_INT_IMAGE_1D_EXT 0x9057 +#define GL_INT_IMAGE_2D_EXT 0x9058 +#define GL_INT_IMAGE_3D_EXT 0x9059 +#define GL_INT_IMAGE_2D_RECT_EXT 0x905A +#define GL_INT_IMAGE_CUBE_EXT 0x905B +#define GL_INT_IMAGE_BUFFER_EXT 0x905C +#define GL_INT_IMAGE_1D_ARRAY_EXT 0x905D +#define GL_INT_IMAGE_2D_ARRAY_EXT 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE_EXT 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D_EXT 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D_EXT 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D_EXT 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT_EXT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE_EXT 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY_EXT 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_EXT 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x906C +#define GL_MAX_IMAGE_SAMPLES_EXT 0x906D +#define GL_IMAGE_BINDING_FORMAT_EXT 0x906E +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT_EXT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT_EXT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT_EXT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT_EXT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT_EXT 0x00000020 +#define GL_COMMAND_BARRIER_BIT_EXT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT_EXT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT_EXT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT_EXT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT_EXT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT_EXT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT_EXT 0x00001000 +#define GL_ALL_BARRIER_BITS_EXT 0xFFFFFFFF +#endif + +#ifndef GL_EXT_vertex_attrib_64bit +/* reuse GL_DOUBLE */ +#define GL_DOUBLE_VEC2_EXT 0x8FFC +#define GL_DOUBLE_VEC3_EXT 0x8FFD +#define GL_DOUBLE_VEC4_EXT 0x8FFE +#define GL_DOUBLE_MAT2_EXT 0x8F46 +#define GL_DOUBLE_MAT3_EXT 0x8F47 +#define GL_DOUBLE_MAT4_EXT 0x8F48 +#define GL_DOUBLE_MAT2x3_EXT 0x8F49 +#define GL_DOUBLE_MAT2x4_EXT 0x8F4A +#define GL_DOUBLE_MAT3x2_EXT 0x8F4B +#define GL_DOUBLE_MAT3x4_EXT 0x8F4C +#define GL_DOUBLE_MAT4x2_EXT 0x8F4D +#define GL_DOUBLE_MAT4x3_EXT 0x8F4E +#endif + +#ifndef GL_NV_gpu_program5 +#define GL_MAX_GEOMETRY_PROGRAM_INVOCATIONS_NV 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5C +#define GL_FRAGMENT_PROGRAM_INTERPOLATION_OFFSET_BITS_NV 0x8E5D +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5F +#define GL_MAX_PROGRAM_SUBROUTINE_PARAMETERS_NV 0x8F44 +#define GL_MAX_PROGRAM_SUBROUTINE_NUM_NV 0x8F45 +#endif + +#ifndef GL_NV_gpu_shader5 +#define GL_INT64_NV 0x140E +#define GL_UNSIGNED_INT64_NV 0x140F +#define GL_INT8_NV 0x8FE0 +#define GL_INT8_VEC2_NV 0x8FE1 +#define GL_INT8_VEC3_NV 0x8FE2 +#define GL_INT8_VEC4_NV 0x8FE3 +#define GL_INT16_NV 0x8FE4 +#define GL_INT16_VEC2_NV 0x8FE5 +#define GL_INT16_VEC3_NV 0x8FE6 +#define GL_INT16_VEC4_NV 0x8FE7 +#define GL_INT64_VEC2_NV 0x8FE9 +#define GL_INT64_VEC3_NV 0x8FEA +#define GL_INT64_VEC4_NV 0x8FEB +#define GL_UNSIGNED_INT8_NV 0x8FEC +#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED +#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE +#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF +#define GL_UNSIGNED_INT16_NV 0x8FF0 +#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 +#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 +#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 +#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 +#define GL_FLOAT16_NV 0x8FF8 +#define GL_FLOAT16_VEC2_NV 0x8FF9 +#define GL_FLOAT16_VEC3_NV 0x8FFA +#define GL_FLOAT16_VEC4_NV 0x8FFB +/* reuse GL_PATCHES */ +#endif + +#ifndef GL_NV_shader_buffer_store +#define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 +/* reuse GL_READ_WRITE */ +/* reuse GL_WRITE_ONLY */ +#endif + +#ifndef GL_NV_tessellation_program5 +#define GL_MAX_PROGRAM_PATCH_ATTRIBS_NV 0x86D8 +#define GL_TESS_CONTROL_PROGRAM_NV 0x891E +#define GL_TESS_EVALUATION_PROGRAM_NV 0x891F +#define GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV 0x8C74 +#define GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV 0x8C75 +#endif + +#ifndef GL_NV_vertex_attrib_integer_64bit +/* reuse GL_INT64_NV */ +/* reuse GL_UNSIGNED_INT64_NV */ +#endif + +#ifndef GL_NV_multisample_coverage +#define GL_COVERAGE_SAMPLES_NV 0x80A9 +#define GL_COLOR_SAMPLES_NV 0x8E20 +#endif + +#ifndef GL_AMD_name_gen_delete +#define GL_DATA_BUFFER_AMD 0x9151 +#define GL_PERFORMANCE_MONITOR_AMD 0x9152 +#define GL_QUERY_OBJECT_AMD 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_AMD 0x9154 +#define GL_SAMPLER_OBJECT_AMD 0x9155 +#endif + +#ifndef GL_AMD_debug_output +#define GL_MAX_DEBUG_LOGGED_MESSAGES_AMD 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_AMD 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_AMD 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_AMD 0x9147 +#define GL_DEBUG_SEVERITY_LOW_AMD 0x9148 +#define GL_DEBUG_CATEGORY_API_ERROR_AMD 0x9149 +#define GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD 0x914A +#define GL_DEBUG_CATEGORY_DEPRECATION_AMD 0x914B +#define GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD 0x914C +#define GL_DEBUG_CATEGORY_PERFORMANCE_AMD 0x914D +#define GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD 0x914E +#define GL_DEBUG_CATEGORY_APPLICATION_AMD 0x914F +#define GL_DEBUG_CATEGORY_OTHER_AMD 0x9150 +#endif + +#ifndef GL_NV_vdpau_interop +#define GL_SURFACE_STATE_NV 0x86EB +#define GL_SURFACE_REGISTERED_NV 0x86FD +#define GL_SURFACE_MAPPED_NV 0x8700 +#define GL_WRITE_DISCARD_NV 0x88BE +#endif + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#endif + +#ifndef GL_AMD_depth_clamp_separate +#define GL_DEPTH_CLAMP_NEAR_AMD 0x901E +#define GL_DEPTH_CLAMP_FAR_AMD 0x901F +#endif + +#ifndef GL_EXT_texture_sRGB_decode +#define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 +#define GL_DECODE_EXT 0x8A49 +#define GL_SKIP_DECODE_EXT 0x8A4A +#endif + +#ifndef GL_NV_texture_multisample +#define GL_TEXTURE_COVERAGE_SAMPLES_NV 0x9045 +#define GL_TEXTURE_COLOR_SAMPLES_NV 0x9046 +#endif + +#ifndef GL_AMD_blend_minmax_factor +#define GL_FACTOR_MIN_AMD 0x901C +#define GL_FACTOR_MAX_AMD 0x901D +#endif + +#ifndef GL_AMD_sample_positions +#define GL_SUBSAMPLE_DISTANCE_AMD 0x883F +#endif + +#ifndef GL_EXT_x11_sync_object +#define GL_SYNC_X11_FENCE_EXT 0x90E1 +#endif + +#ifndef GL_AMD_multi_draw_indirect +#endif + +#ifndef GL_EXT_framebuffer_multisample_blit_scaled +#define GL_SCALED_RESOLVE_FASTEST_EXT 0x90BA +#define GL_SCALED_RESOLVE_NICEST_EXT 0x90BB +#endif + +#ifndef GL_NV_path_rendering +#define GL_PATH_FORMAT_SVG_NV 0x9070 +#define GL_PATH_FORMAT_PS_NV 0x9071 +#define GL_STANDARD_FONT_NAME_NV 0x9072 +#define GL_SYSTEM_FONT_NAME_NV 0x9073 +#define GL_FILE_NAME_NV 0x9074 +#define GL_PATH_STROKE_WIDTH_NV 0x9075 +#define GL_PATH_END_CAPS_NV 0x9076 +#define GL_PATH_INITIAL_END_CAP_NV 0x9077 +#define GL_PATH_TERMINAL_END_CAP_NV 0x9078 +#define GL_PATH_JOIN_STYLE_NV 0x9079 +#define GL_PATH_MITER_LIMIT_NV 0x907A +#define GL_PATH_DASH_CAPS_NV 0x907B +#define GL_PATH_INITIAL_DASH_CAP_NV 0x907C +#define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D +#define GL_PATH_DASH_OFFSET_NV 0x907E +#define GL_PATH_CLIENT_LENGTH_NV 0x907F +#define GL_PATH_FILL_MODE_NV 0x9080 +#define GL_PATH_FILL_MASK_NV 0x9081 +#define GL_PATH_FILL_COVER_MODE_NV 0x9082 +#define GL_PATH_STROKE_COVER_MODE_NV 0x9083 +#define GL_PATH_STROKE_MASK_NV 0x9084 +#define GL_PATH_SAMPLE_QUALITY_NV 0x9085 +#define GL_PATH_STROKE_BOUND_NV 0x9086 +#define GL_PATH_STROKE_OVERSAMPLE_COUNT_NV 0x9087 +#define GL_COUNT_UP_NV 0x9088 +#define GL_COUNT_DOWN_NV 0x9089 +#define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A +#define GL_CONVEX_HULL_NV 0x908B +#define GL_MULTI_HULLS_NV 0x908C +#define GL_BOUNDING_BOX_NV 0x908D +#define GL_TRANSLATE_X_NV 0x908E +#define GL_TRANSLATE_Y_NV 0x908F +#define GL_TRANSLATE_2D_NV 0x9090 +#define GL_TRANSLATE_3D_NV 0x9091 +#define GL_AFFINE_2D_NV 0x9092 +#define GL_PROJECTIVE_2D_NV 0x9093 +#define GL_AFFINE_3D_NV 0x9094 +#define GL_PROJECTIVE_3D_NV 0x9095 +#define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 +#define GL_TRANSPOSE_PROJECTIVE_2D_NV 0x9097 +#define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 +#define GL_TRANSPOSE_PROJECTIVE_3D_NV 0x9099 +#define GL_UTF8_NV 0x909A +#define GL_UTF16_NV 0x909B +#define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C +#define GL_PATH_COMMAND_COUNT_NV 0x909D +#define GL_PATH_COORD_COUNT_NV 0x909E +#define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F +#define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 +#define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 +#define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 +#define GL_SQUARE_NV 0x90A3 +#define GL_ROUND_NV 0x90A4 +#define GL_TRIANGULAR_NV 0x90A5 +#define GL_BEVEL_NV 0x90A6 +#define GL_MITER_REVERT_NV 0x90A7 +#define GL_MITER_TRUNCATE_NV 0x90A8 +#define GL_SKIP_MISSING_GLYPH_NV 0x90A9 +#define GL_USE_MISSING_GLYPH_NV 0x90AA +#define GL_PATH_ERROR_POSITION_NV 0x90AB +#define GL_PATH_FOG_GEN_MODE_NV 0x90AC +#define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD +#define GL_ADJACENT_PAIRS_NV 0x90AE +#define GL_FIRST_TO_REST_NV 0x90AF +#define GL_PATH_GEN_MODE_NV 0x90B0 +#define GL_PATH_GEN_COEFF_NV 0x90B1 +#define GL_PATH_GEN_COLOR_FORMAT_NV 0x90B2 +#define GL_PATH_GEN_COMPONENTS_NV 0x90B3 +#define GL_PATH_STENCIL_FUNC_NV 0x90B7 +#define GL_PATH_STENCIL_REF_NV 0x90B8 +#define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 +#define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD +#define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE +#define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF +#define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 +#define GL_MOVE_TO_RESETS_NV 0x90B5 +#define GL_MOVE_TO_CONTINUES_NV 0x90B6 +#define GL_CLOSE_PATH_NV 0x00 +#define GL_MOVE_TO_NV 0x02 +#define GL_RELATIVE_MOVE_TO_NV 0x03 +#define GL_LINE_TO_NV 0x04 +#define GL_RELATIVE_LINE_TO_NV 0x05 +#define GL_HORIZONTAL_LINE_TO_NV 0x06 +#define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 +#define GL_VERTICAL_LINE_TO_NV 0x08 +#define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 +#define GL_QUADRATIC_CURVE_TO_NV 0x0A +#define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B +#define GL_CUBIC_CURVE_TO_NV 0x0C +#define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D +#define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E +#define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F +#define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 +#define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 +#define GL_SMALL_CCW_ARC_TO_NV 0x12 +#define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 +#define GL_SMALL_CW_ARC_TO_NV 0x14 +#define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 +#define GL_LARGE_CCW_ARC_TO_NV 0x16 +#define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 +#define GL_LARGE_CW_ARC_TO_NV 0x18 +#define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 +#define GL_RESTART_PATH_NV 0xF0 +#define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 +#define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 +#define GL_RECT_NV 0xF6 +#define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 +#define GL_CIRCULAR_CW_ARC_TO_NV 0xFA +#define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC +#define GL_ARC_TO_NV 0xFE +#define GL_RELATIVE_ARC_TO_NV 0xFF +#define GL_BOLD_BIT_NV 0x01 +#define GL_ITALIC_BIT_NV 0x02 +#define GL_GLYPH_WIDTH_BIT_NV 0x01 +#define GL_GLYPH_HEIGHT_BIT_NV 0x02 +#define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 +#define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 +#define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 +#define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 +#define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 +#define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 +#define GL_GLYPH_HAS_KERNING_NV 0x100 +#define GL_FONT_X_MIN_BOUNDS_NV 0x00010000 +#define GL_FONT_Y_MIN_BOUNDS_NV 0x00020000 +#define GL_FONT_X_MAX_BOUNDS_NV 0x00040000 +#define GL_FONT_Y_MAX_BOUNDS_NV 0x00080000 +#define GL_FONT_UNITS_PER_EM_NV 0x00100000 +#define GL_FONT_ASCENDER_NV 0x00200000 +#define GL_FONT_DESCENDER_NV 0x00400000 +#define GL_FONT_HEIGHT_NV 0x00800000 +#define GL_FONT_MAX_ADVANCE_WIDTH_NV 0x01000000 +#define GL_FONT_MAX_ADVANCE_HEIGHT_NV 0x02000000 +#define GL_FONT_UNDERLINE_POSITION_NV 0x04000000 +#define GL_FONT_UNDERLINE_THICKNESS_NV 0x08000000 +#define GL_FONT_HAS_KERNING_NV 0x10000000 +#endif + +#ifndef GL_AMD_pinned_memory +#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160 +#endif + +#ifndef GL_AMD_stencil_operation_extended +#define GL_SET_AMD 0x874A +#define GL_REPLACE_VALUE_AMD 0x874B +#define GL_STENCIL_OP_VALUE_AMD 0x874C +#define GL_STENCIL_BACK_OP_VALUE_AMD 0x874D +#endif + +#ifndef GL_AMD_vertex_shader_viewport_index +#endif + +#ifndef GL_AMD_vertex_shader_layer +#endif + +#ifndef GL_NV_bindless_texture +#endif + +#ifndef GL_NV_shader_atomic_float +#endif + + +/*************************************************************/ + +#include +#ifndef GL_VERSION_2_0 +/* GL type for program/shader text */ +typedef char GLchar; +#endif + +#ifndef GL_VERSION_1_5 +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +#endif + +#ifndef GL_ARB_vertex_buffer_object +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; +#endif + +#ifndef GL_ARB_shader_objects +/* GL types for program/shader text and shader object handles */ +typedef char GLcharARB; +typedef unsigned int GLhandleARB; +#endif + +/* GL type for "half" precision (s10e5) float data in host memory */ +#ifndef GL_ARB_half_float_pixel +typedef unsigned short GLhalfARB; +#endif + +#ifndef GL_NV_half_float +typedef unsigned short GLhalfNV; +#endif + +#ifndef GLEXT_64_TYPES_DEFINED +/* This code block is duplicated in glxext.h, so must be protected */ +#define GLEXT_64_TYPES_DEFINED +/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ +/* (as used in the GL_EXT_timer_query extension). */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#elif defined(__sun__) || defined(__digital__) +#include +#if defined(__STDC__) +#if defined(__arch64__) || defined(_LP64) +typedef long int int64_t; +typedef unsigned long int uint64_t; +#else +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif /* __arch64__ */ +#endif /* __STDC__ */ +#elif defined( __VMS ) || defined(__sgi) +#include +#elif defined(__SCO__) || defined(__USLC__) +#include +#elif defined(__UNIXOS2__) || defined(__SOL64__) +typedef long int int32_t; +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#elif defined(_WIN32) && defined(__GNUC__) +#include +#elif defined(_WIN32) +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include +#endif +#endif + +#ifndef GL_EXT_timer_query +typedef int64_t GLint64EXT; +typedef uint64_t GLuint64EXT; +#endif + +#ifndef GL_ARB_sync +typedef int64_t GLint64; +typedef uint64_t GLuint64; +typedef struct __GLsync *GLsync; +#endif + +#ifndef GL_ARB_cl_event +/* These incomplete types let us declare types compatible with OpenCL's cl_context and cl_event */ +struct _cl_context; +struct _cl_event; +#endif + +#ifndef GL_ARB_debug_output +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam); +#endif + +#ifndef GL_AMD_debug_output +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam); +#endif + +#ifndef GL_NV_vdpau_interop +typedef GLintptr GLvdpauSurfaceNV; +#endif + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +GLAPI void APIENTRY glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +GLAPI void APIENTRY glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_VERSION_1_2_DEPRECATED +#define GL_VERSION_1_2_DEPRECATED 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTable (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +GLAPI void APIENTRY glColorTableParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTable (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTable (GLenum target, GLenum format, GLenum type, GLvoid *table); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glColorSubTable (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +GLAPI void APIENTRY glCopyColorSubTable (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionParameterf (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteri (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum target, GLenum format, GLenum type, GLvoid *image); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilter (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +GLAPI void APIENTRY glSeparableFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +GLAPI void APIENTRY glGetHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogram (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmax (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogram (GLenum target); +GLAPI void APIENTRY glResetMinmax (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +#endif + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +GLAPI void APIENTRY glSampleCoverage (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum target, GLint level, GLvoid *img); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_VERSION_1_3_DEPRECATED +#define GL_VERSION_1_3_DEPRECATED 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClientActiveTexture (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *m); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +#endif + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glMultiDrawArrays (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElements (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +GLAPI void APIENTRY glPointParameterf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glPointParameteri (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameteriv (GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_VERSION_1_4_DEPRECATED +#define GL_VERSION_1_4_DEPRECATED 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordf (GLfloat coord); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *coord); +GLAPI void APIENTRY glFogCoordd (GLdouble coord); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointer (GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3i (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glWindowPos2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2i (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2iv (const GLint *v); +GLAPI void APIENTRY glWindowPos2s (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *v); +GLAPI void APIENTRY glWindowPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3iv (const GLint *v); +GLAPI void APIENTRY glWindowPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +#endif + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQuery (GLuint id); +GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQuery (GLenum target); +GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint buffer); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +GLAPI void APIENTRY glGetBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +GLAPI GLvoid* APIENTRY glMapBuffer (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum target); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointerv (GLenum target, GLenum pname, GLvoid* *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glDrawBuffers (GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *obj); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, GLvoid* *pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI GLboolean APIENTRY glIsShader (GLuint shader); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glValidateProgram (GLuint program); +GLAPI void APIENTRY glVertexAttrib1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1s (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2s (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3s (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4s (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *obj); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif + +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 +/* OpenGL 3.0 also reuses entry points from these extensions: */ +/* ARB_framebuffer_object */ +/* ARB_map_buffer_range */ +/* ARB_vertex_array_object */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaski (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleani_v (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegeri_v (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnablei (GLenum target, GLuint index); +GLAPI void APIENTRY glDisablei (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledi (GLenum target, GLuint index); +GLAPI void APIENTRY glBeginTransformFeedback (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedback (void); +GLAPI void APIENTRY glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferBase (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryings (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVarying (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glClampColor (GLenum target, GLenum clamp); +GLAPI void APIENTRY glBeginConditionalRender (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRender (void); +GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribIiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuiv (GLuint index, GLenum pname, GLuint *params); +GLAPI void APIENTRY glVertexAttribI1i (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2i (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3i (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4i (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1ui (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2ui (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3ui (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4ui (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glGetUniformuiv (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocation (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1ui (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glTexParameterIiv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuiv (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuiv (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearBufferiv (GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearBufferuiv (GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearBufferfv (GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearBufferfi (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI const GLubyte * APIENTRY glGetStringi (GLenum name, GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORMASKIPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEIPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLCLAMPCOLORPROC) (GLenum target, GLenum clamp); +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint index, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef const GLubyte * (APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +#endif + +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +/* OpenGL 3.1 also reuses entry points from these extensions: */ +/* ARB_copy_buffer */ +/* ARB_uniform_buffer_object */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +GLAPI void APIENTRY glTexBuffer (GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glPrimitiveRestartIndex (GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLTEXBUFFERPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC) (GLuint index); +#endif + +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +/* OpenGL 3.2 also reuses entry points from these extensions: */ +/* ARB_draw_elements_base_vertex */ +/* ARB_provoking_vertex */ +/* ARB_sync */ +/* ARB_texture_multisample */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetInteger64i_v (GLenum target, GLuint index, GLint64 *data); +GLAPI void APIENTRY glGetBufferParameteri64v (GLenum target, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glFramebufferTexture (GLenum target, GLenum attachment, GLuint texture, GLint level); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC) (GLenum target, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +#endif + +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +/* OpenGL 3.3 also reuses entry points from these extensions: */ +/* ARB_blend_func_extended */ +/* ARB_sampler_objects */ +/* ARB_explicit_attrib_location, but it has none */ +/* ARB_occlusion_query2 (no entry points) */ +/* ARB_shader_bit_encoding (no entry points) */ +/* ARB_texture_rgb10_a2ui (no entry points) */ +/* ARB_texture_swizzle (no entry points) */ +/* ARB_timer_query */ +/* ARB_vertex_type_2_10_10_10_rev */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisor (GLuint index, GLuint divisor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC) (GLuint index, GLuint divisor); +#endif + +#ifndef GL_VERSION_4_0 +#define GL_VERSION_4_0 1 +/* OpenGL 4.0 also reuses entry points from these extensions: */ +/* ARB_texture_query_lod (no entry points) */ +/* ARB_draw_indirect */ +/* ARB_gpu_shader5 (no entry points) */ +/* ARB_gpu_shader_fp64 */ +/* ARB_shader_subroutine */ +/* ARB_tessellation_shader */ +/* ARB_texture_buffer_object_rgb32 (no entry points) */ +/* ARB_texture_cube_map_array (no entry points) */ +/* ARB_texture_gather (no entry points) */ +/* ARB_transform_feedback2 */ +/* ARB_transform_feedback3 */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShading (GLclampf value); +GLAPI void APIENTRY glBlendEquationi (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparatei (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunci (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparatei (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGPROC) (GLclampf value); +typedef void (APIENTRYP PFNGLBLENDEQUATIONIPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif + +#ifndef GL_VERSION_4_1 +#define GL_VERSION_4_1 1 +/* OpenGL 4.1 reuses entry points from these extensions: */ +/* ARB_ES2_compatibility */ +/* ARB_get_program_binary */ +/* ARB_separate_shader_objects */ +/* ARB_shader_precision (no entry points) */ +/* ARB_vertex_attrib_64bit */ +/* ARB_viewport_array */ +#endif + +#ifndef GL_VERSION_4_2 +#define GL_VERSION_4_2 1 +/* OpenGL 4.2 reuses entry points from these extensions: */ +/* ARB_base_instance */ +/* ARB_shading_language_420pack (no entry points) */ +/* ARB_transform_feedback_instanced */ +/* ARB_compressed_texture_pixel_storage (no entry points) */ +/* ARB_conservative_depth (no entry points) */ +/* ARB_internalformat_query */ +/* ARB_map_buffer_alignment (no entry points) */ +/* ARB_shader_atomic_counters */ +/* ARB_shader_image_load_store */ +/* ARB_shading_language_packing (no entry points) */ +/* ARB_texture_storage */ +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum target, const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *m); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLclampf value, GLboolean invert); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum target, GLint level, GLvoid *img); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#endif + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvARB (GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint size, const GLbyte *weights); +GLAPI void APIENTRY glWeightsvARB (GLint size, const GLshort *weights); +GLAPI void APIENTRY glWeightivARB (GLint size, const GLint *weights); +GLAPI void APIENTRY glWeightfvARB (GLint size, const GLfloat *weights); +GLAPI void APIENTRY glWeightdvARB (GLint size, const GLdouble *weights); +GLAPI void APIENTRY glWeightubvARB (GLint size, const GLubyte *weights); +GLAPI void APIENTRY glWeightusvARB (GLint size, const GLushort *weights); +GLAPI void APIENTRY glWeightuivARB (GLint size, const GLuint *weights); +GLAPI void APIENTRY glWeightPointerARB (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glVertexBlendARB (GLint count); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint index); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint size, const GLubyte *indices); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint size, const GLushort *indices); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint size, const GLuint *indices); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#endif + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#endif + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iARB (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos2sARB (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iARB (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos3sARB (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#endif + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glProgramStringARB (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +GLAPI void APIENTRY glBindProgramARB (GLenum target, GLuint program); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glGenProgramsARB (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringARB (GLenum target, GLenum pname, GLvoid *string); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint index, GLenum pname, GLvoid* *pointer); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint program); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#endif + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +/* All ARB_fragment_program entry points are shared with ARB_vertex_program. */ +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffersARB (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint buffer); +GLAPI void APIENTRY glBufferDataARB (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +GLAPI void APIENTRY glBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +GLAPI GLvoid* APIENTRY glMapBufferARB (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum target); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum target, GLenum pname, GLvoid* *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id); +GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQueryARB (GLenum target); +GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB obj); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum pname); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB containerObj, GLhandleARB attachedObj); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum shaderType); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB shaderObj); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB containerObj, GLhandleARB obj); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB programObj); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUniform1fARB (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2fARB (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1iARB (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2iARB (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3iARB (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4iARB (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB obj, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB obj, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB programObj, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB programObj, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB programObj, GLint location, GLint *params); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB programObj, GLuint index, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB programObj, const GLcharARB *name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei n, const GLenum *bufs); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum target, GLenum clamp); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +#endif + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#endif + +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 +#endif + +#ifndef GL_ARB_draw_instanced +#define GL_ARB_draw_instanced 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedARB (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedARB (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif + +#ifndef GL_ARB_framebuffer_object +#define GL_ARB_framebuffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbuffer (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebuffer (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatus (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmap (GLenum target); +GLAPI void APIENTRY glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glRenderbufferStorageMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glFramebufferTextureLayer (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#endif + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_ARB_framebuffer_sRGB 1 +#endif + +#ifndef GL_ARB_geometry_shader4 +#define GL_ARB_geometry_shader4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriARB (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glFramebufferTextureARB (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif + +#ifndef GL_ARB_half_float_vertex +#define GL_ARB_half_float_vertex 1 +#endif + +#ifndef GL_ARB_instanced_arrays +#define GL_ARB_instanced_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisorARB (GLuint index, GLuint divisor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); +#endif + +#ifndef GL_ARB_map_buffer_range +#define GL_ARB_map_buffer_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +#endif + +#ifndef GL_ARB_texture_buffer_object +#define GL_ARB_texture_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferARB (GLenum target, GLenum internalformat, GLuint buffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#endif + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc 1 +#endif + +#ifndef GL_ARB_texture_rg +#define GL_ARB_texture_rg 1 +#endif + +#ifndef GL_ARB_vertex_array_object +#define GL_ARB_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArray (GLuint array); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); +#endif + +#ifndef GL_ARB_uniform_buffer_object +#define GL_ARB_uniform_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformIndices (GLuint program, GLsizei uniformCount, const GLchar* *uniformNames, GLuint *uniformIndices); +GLAPI void APIENTRY glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformName (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +GLAPI GLuint APIENTRY glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName); +GLAPI void APIENTRY glGetActiveUniformBlockiv (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformBlockName (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +GLAPI void APIENTRY glUniformBlockBinding (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar* *uniformNames, GLuint *uniformIndices); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif + +#ifndef GL_ARB_compatibility +#define GL_ARB_compatibility 1 +#endif + +#ifndef GL_ARB_copy_buffer +#define GL_ARB_copy_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#endif + +#ifndef GL_ARB_shader_texture_lod +#define GL_ARB_shader_texture_lod 1 +#endif + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp 1 +#endif + +#ifndef GL_ARB_draw_elements_base_vertex +#define GL_ARB_draw_elements_base_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +GLAPI void APIENTRY glDrawRangeElementsBaseVertex (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertex (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex); +GLAPI void APIENTRY glMultiDrawElementsBaseVertex (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount, const GLint *basevertex); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount, const GLint *basevertex); +#endif + +#ifndef GL_ARB_fragment_coord_conventions +#define GL_ARB_fragment_coord_conventions 1 +#endif + +#ifndef GL_ARB_provoking_vertex +#define GL_ARB_provoking_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertex (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC) (GLenum mode); +#endif + +#ifndef GL_ARB_seamless_cube_map +#define GL_ARB_seamless_cube_map 1 +#endif + +#ifndef GL_ARB_sync +#define GL_ARB_sync 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glFenceSync (GLenum condition, GLbitfield flags); +GLAPI GLboolean APIENTRY glIsSync (GLsync sync); +GLAPI void APIENTRY glDeleteSync (GLsync sync); +GLAPI GLenum APIENTRY glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glGetInteger64v (GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetSynciv (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags); +typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync); +typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync); +typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +#endif + +#ifndef GL_ARB_texture_multisample +#define GL_ARB_texture_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage2DMultisample (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexImage3DMultisample (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glGetMultisamplefv (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaski (GLuint index, GLbitfield mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC) (GLuint index, GLbitfield mask); +#endif + +#ifndef GL_ARB_vertex_array_bgra +#define GL_ARB_vertex_array_bgra 1 +#endif + +#ifndef GL_ARB_draw_buffers_blend +#define GL_ARB_draw_buffers_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationiARB (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateiARB (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunciARB (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateiARB (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONIARBPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIARBPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif + +#ifndef GL_ARB_sample_shading +#define GL_ARB_sample_shading 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShadingARB (GLclampf value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGARBPROC) (GLclampf value); +#endif + +#ifndef GL_ARB_texture_cube_map_array +#define GL_ARB_texture_cube_map_array 1 +#endif + +#ifndef GL_ARB_texture_gather +#define GL_ARB_texture_gather 1 +#endif + +#ifndef GL_ARB_texture_query_lod +#define GL_ARB_texture_query_lod 1 +#endif + +#ifndef GL_ARB_shading_language_include +#define GL_ARB_shading_language_include 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glNamedStringARB (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +GLAPI void APIENTRY glDeleteNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glCompileShaderIncludeARB (GLuint shader, GLsizei count, const GLchar* *path, const GLint *length); +GLAPI GLboolean APIENTRY glIsNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glGetNamedStringARB (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +GLAPI void APIENTRY glGetNamedStringivARB (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLNAMEDSTRINGARBPROC) (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +typedef void (APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC) (GLuint shader, GLsizei count, const GLchar* *path, const GLint *length); +typedef GLboolean (APIENTRYP PFNGLISNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC) (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc 1 +#endif + +#ifndef GL_ARB_blend_func_extended +#define GL_ARB_blend_func_extended 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindFragDataLocationIndexed (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataIndex (GLuint program, const GLchar *name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC) (GLuint program, const GLchar *name); +#endif + +#ifndef GL_ARB_explicit_attrib_location +#define GL_ARB_explicit_attrib_location 1 +#endif + +#ifndef GL_ARB_occlusion_query2 +#define GL_ARB_occlusion_query2 1 +#endif + +#ifndef GL_ARB_sampler_objects +#define GL_ARB_sampler_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenSamplers (GLsizei count, GLuint *samplers); +GLAPI void APIENTRY glDeleteSamplers (GLsizei count, const GLuint *samplers); +GLAPI GLboolean APIENTRY glIsSampler (GLuint sampler); +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +GLAPI void APIENTRY glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); +GLAPI void APIENTRY glSamplerParameteriv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); +GLAPI void APIENTRY glSamplerParameterfv (GLuint sampler, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glSamplerParameterIiv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterIuiv (GLuint sampler, GLenum pname, const GLuint *param); +GLAPI void APIENTRY glGetSamplerParameteriv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterIiv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterfv (GLuint sampler, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetSamplerParameterIuiv (GLuint sampler, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENSAMPLERSPROC) (GLsizei count, GLuint *samplers); +typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC) (GLsizei count, const GLuint *samplers); +typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC) (GLuint sampler); +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC) (GLuint sampler, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC) (GLuint sampler, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_bit_encoding +#define GL_ARB_shader_bit_encoding 1 +#endif + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_ARB_texture_rgb10_a2ui 1 +#endif + +#ifndef GL_ARB_texture_swizzle +#define GL_ARB_texture_swizzle 1 +#endif + +#ifndef GL_ARB_timer_query +#define GL_ARB_timer_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glQueryCounter (GLuint id, GLenum target); +GLAPI void APIENTRY glGetQueryObjecti64v (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64v (GLuint id, GLenum pname, GLuint64 *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64 *params); +#endif + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +#define GL_ARB_vertex_type_2_10_10_10_rev 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexP2ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP2uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP3ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP3uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP4ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP4uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glTexCoordP1ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP1uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP2ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP2uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP4ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP4uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP1ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP1uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP2ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP2uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP3ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP3uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP4ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP4uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glNormalP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glNormalP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glColorP4ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP4uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glSecondaryColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glSecondaryColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glVertexAttribP1ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP1uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP2ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP2uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP3ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP3uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP4ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP4uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXP2UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP3UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP4UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLNORMALP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLNORMALP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLCOLORP4UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP4UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +#endif + +#ifndef GL_ARB_draw_indirect +#define GL_ARB_draw_indirect 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysIndirect (GLenum mode, const GLvoid *indirect); +GLAPI void APIENTRY glDrawElementsIndirect (GLenum mode, GLenum type, const GLvoid *indirect); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC) (GLenum mode, const GLvoid *indirect); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const GLvoid *indirect); +#endif + +#ifndef GL_ARB_gpu_shader5 +#define GL_ARB_gpu_shader5 1 +#endif + +#ifndef GL_ARB_gpu_shader_fp64 +#define GL_ARB_gpu_shader_fp64 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1d (GLint location, GLdouble x); +GLAPI void APIENTRY glUniform2d (GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glUniform3d (GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glUniform4d (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glUniform1dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform2dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform3dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform4dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glGetUniformdv (GLuint program, GLint location, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORM1DPROC) (GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLUNIFORM2DPROC) (GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLUNIFORM3DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLUNIFORM4DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLUNIFORM1DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM2DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM3DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM4DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLGETUNIFORMDVPROC) (GLuint program, GLint location, GLdouble *params); +#endif + +#ifndef GL_ARB_shader_subroutine +#define GL_ARB_shader_subroutine 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetSubroutineUniformLocation (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI GLuint APIENTRY glGetSubroutineIndex (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineUniformiv (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +GLAPI void APIENTRY glGetActiveSubroutineUniformName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glUniformSubroutinesuiv (GLenum shadertype, GLsizei count, const GLuint *indices); +GLAPI void APIENTRY glGetUniformSubroutineuiv (GLenum shadertype, GLint location, GLuint *params); +GLAPI void APIENTRY glGetProgramStageiv (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef GLuint (APIENTRYP PFNGLGETSUBROUTINEINDEXPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC) (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC) (GLenum shadertype, GLsizei count, const GLuint *indices); +typedef void (APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC) (GLenum shadertype, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC) (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +#endif + +#ifndef GL_ARB_tessellation_shader +#define GL_ARB_tessellation_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPatchParameteri (GLenum pname, GLint value); +GLAPI void APIENTRY glPatchParameterfv (GLenum pname, const GLfloat *values); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPATCHPARAMETERIPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATCHPARAMETERFVPROC) (GLenum pname, const GLfloat *values); +#endif + +#ifndef GL_ARB_texture_buffer_object_rgb32 +#define GL_ARB_texture_buffer_object_rgb32 1 +#endif + +#ifndef GL_ARB_transform_feedback2 +#define GL_ARB_transform_feedback2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedback (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacks (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedback (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedback (void); +GLAPI void APIENTRY glResumeTransformFeedback (void); +GLAPI void APIENTRY glDrawTransformFeedback (GLenum mode, GLuint id); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC) (GLenum mode, GLuint id); +#endif + +#ifndef GL_ARB_transform_feedback3 +#define GL_ARB_transform_feedback3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawTransformFeedbackStream (GLenum mode, GLuint id, GLuint stream); +GLAPI void APIENTRY glBeginQueryIndexed (GLenum target, GLuint index, GLuint id); +GLAPI void APIENTRY glEndQueryIndexed (GLenum target, GLuint index); +GLAPI void APIENTRY glGetQueryIndexediv (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC) (GLenum mode, GLuint id, GLuint stream); +typedef void (APIENTRYP PFNGLBEGINQUERYINDEXEDPROC) (GLenum target, GLuint index, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYINDEXEDPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETQUERYINDEXEDIVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_ARB_ES2_compatibility +#define GL_ARB_ES2_compatibility 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReleaseShaderCompiler (void); +GLAPI void APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryformat, const GLvoid *binary, GLsizei length); +GLAPI void APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GLAPI void APIENTRY glDepthRangef (GLclampf n, GLclampf f); +GLAPI void APIENTRY glClearDepthf (GLclampf d); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); +typedef void (APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryformat, const GLvoid *binary, GLsizei length); +typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC) (GLclampf n, GLclampf f); +typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC) (GLclampf d); +#endif + +#ifndef GL_ARB_get_program_binary +#define GL_ARB_get_program_binary 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary); +GLAPI void APIENTRY glProgramBinary (GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length); +GLAPI void APIENTRY glProgramParameteri (GLuint program, GLenum pname, GLint value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary); +typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value); +#endif + +#ifndef GL_ARB_separate_shader_objects +#define GL_ARB_separate_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseProgramStages (GLuint pipeline, GLbitfield stages, GLuint program); +GLAPI void APIENTRY glActiveShaderProgram (GLuint pipeline, GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramv (GLenum type, GLsizei count, const GLchar* *strings); +GLAPI void APIENTRY glBindProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glDeleteProgramPipelines (GLsizei n, const GLuint *pipelines); +GLAPI void APIENTRY glGenProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI GLboolean APIENTRY glIsProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineiv (GLuint pipeline, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1i (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform1iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform1f (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform1fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1d (GLuint program, GLint location, GLdouble v0); +GLAPI void APIENTRY glProgramUniform1dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform1ui (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform1uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2i (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform2iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2f (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform2fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2d (GLuint program, GLint location, GLdouble v0, GLdouble v1); +GLAPI void APIENTRY glProgramUniform2dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2ui (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform2uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform3iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform3fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +GLAPI void APIENTRY glProgramUniform3dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform3uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform4iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform4fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +GLAPI void APIENTRY glProgramUniform4dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform4uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glValidateProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineInfoLog (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC) (GLuint pipeline, GLbitfield stages, GLuint program); +typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC) (GLuint pipeline, GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC) (GLenum type, GLsizei count, const GLchar* *strings); +typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC) (GLsizei n, const GLuint *pipelines); +typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC) (GLuint pipeline, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DPROC) (GLuint program, GLint location, GLdouble v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +#endif + +#ifndef GL_ARB_vertex_attrib_64bit +#define GL_ARB_vertex_attrib_64bit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribLdv (GLuint index, GLenum pname, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC) (GLuint index, GLenum pname, GLdouble *params); +#endif + +#ifndef GL_ARB_viewport_array +#define GL_ARB_viewport_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glViewportArrayv (GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glViewportIndexedf (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GLAPI void APIENTRY glViewportIndexedfv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glScissorArrayv (GLuint first, GLsizei count, const GLint *v); +GLAPI void APIENTRY glScissorIndexed (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorIndexedv (GLuint index, const GLint *v); +GLAPI void APIENTRY glDepthRangeArrayv (GLuint first, GLsizei count, const GLclampd *v); +GLAPI void APIENTRY glDepthRangeIndexed (GLuint index, GLclampd n, GLclampd f); +GLAPI void APIENTRY glGetFloati_v (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoublei_v (GLenum target, GLuint index, GLdouble *data); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVIEWPORTARRAYVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLSCISSORARRAYVPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYVPROC) (GLuint first, GLsizei count, const GLclampd *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC) (GLuint index, GLclampd n, GLclampd f); +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#endif + +#ifndef GL_ARB_cl_event +#define GL_ARB_cl_event 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB (struct _cl_context * context, struct _cl_event * event, GLbitfield flags); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLsync (APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC) (struct _cl_context * context, struct _cl_event * event, GLbitfield flags); +#endif + +#ifndef GL_ARB_debug_output +#define GL_ARB_debug_output 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageControlARB (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertARB (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackARB (GLDEBUGPROCARB callback, const GLvoid *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogARB (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback, const GLvoid *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif + +#ifndef GL_ARB_robustness +#define GL_ARB_robustness 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB (void); +GLAPI void APIENTRY glGetnMapdvARB (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfvARB (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapivARB (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfvARB (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuivARB (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusvARB (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStippleARB (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTableARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *table); +GLAPI void APIENTRY glGetnConvolutionFilterARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *image); +GLAPI void APIENTRY glGetnSeparableFilterARB (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, GLvoid *row, GLsizei columnBufSize, GLvoid *column, GLvoid *span); +GLAPI void APIENTRY glGetnHistogramARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +GLAPI void APIENTRY glGetnMinmaxARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +GLAPI void APIENTRY glGetnTexImageARB (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid *img); +GLAPI void APIENTRY glReadnPixelsARB (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLvoid *data); +GLAPI void APIENTRY glGetnCompressedTexImageARB (GLenum target, GLint lod, GLsizei bufSize, GLvoid *img); +GLAPI void APIENTRY glGetnUniformfvARB (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformivARB (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuivARB (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glGetnUniformdvARB (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC) (void); +typedef void (APIENTRYP PFNGLGETNMAPDVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVARBPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVARBPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVARBPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEARBPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, GLvoid *row, GLsizei columnBufSize, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +typedef void (APIENTRYP PFNGLGETNMINMAXARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEARBPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid *img); +typedef void (APIENTRYP PFNGLREADNPIXELSARBPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLvoid *data); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, GLsizei bufSize, GLvoid *img); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +#endif + +#ifndef GL_ARB_shader_stencil_export +#define GL_ARB_shader_stencil_export 1 +#endif + +#ifndef GL_ARB_base_instance +#define GL_ARB_base_instance 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedBaseInstance (GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertexBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance); +#endif + +#ifndef GL_ARB_shading_language_420pack +#define GL_ARB_shading_language_420pack 1 +#endif + +#ifndef GL_ARB_transform_feedback_instanced +#define GL_ARB_transform_feedback_instanced 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawTransformFeedbackInstanced (GLenum mode, GLuint id, GLsizei primcount); +GLAPI void APIENTRY glDrawTransformFeedbackStreamInstanced (GLenum mode, GLuint id, GLuint stream, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC) (GLenum mode, GLuint id, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC) (GLenum mode, GLuint id, GLuint stream, GLsizei primcount); +#endif + +#ifndef GL_ARB_compressed_texture_pixel_storage +#define GL_ARB_compressed_texture_pixel_storage 1 +#endif + +#ifndef GL_ARB_conservative_depth +#define GL_ARB_conservative_depth 1 +#endif + +#ifndef GL_ARB_internalformat_query +#define GL_ARB_internalformat_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetInternalformativ (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint *params); +#endif + +#ifndef GL_ARB_map_buffer_alignment +#define GL_ARB_map_buffer_alignment 1 +#endif + +#ifndef GL_ARB_shader_atomic_counters +#define GL_ARB_shader_atomic_counters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetActiveAtomicCounterBufferiv (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC) (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +#endif + +#ifndef GL_ARB_shader_image_load_store +#define GL_ARB_shader_image_load_store 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindImageTexture (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +GLAPI void APIENTRY glMemoryBarrier (GLbitfield barriers); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +typedef void (APIENTRYP PFNGLMEMORYBARRIERPROC) (GLbitfield barriers); +#endif + +#ifndef GL_ARB_shading_language_packing +#define GL_ARB_shading_language_packing 1 +#endif + +#ifndef GL_ARB_texture_storage +#define GL_ARB_texture_storage 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexStorage1D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTexStorage2D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexStorage3D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSTORAGE1DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat factor, GLfloat bias); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum target, GLenum filter, GLfloat *weights); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogramEXT (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmaxEXT (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogramEXT (GLenum target); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum target, GLenum format, GLenum type, GLvoid *image); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_SGI_color_matrix +#define GL_SGI_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTableSGI (GLenum target, GLenum format, GLenum type, GLvoid *table); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum target, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void APIENTRY glBindTextureEXT (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei n, GLuint *textures); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint texture); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum target, GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum target, GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum pattern); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint i); +GLAPI void APIENTRY glColorPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glDrawArraysEXT (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei stride, GLsizei count, const GLboolean *pointer); +GLAPI void APIENTRY glGetPointervEXT (GLenum pname, GLvoid* *params); +GLAPI void APIENTRY glIndexPointerEXT (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glNormalPointerEXT (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glVertexPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei size, GLint *buffer); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *marker_p); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint marker); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint marker); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint factor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +GLAPI void APIENTRY glDeformSGIX (GLbitfield mask); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *equation); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum target, GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum target, GLint mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +GLAPI void APIENTRY glGetColorTableEXT (GLenum target, GLenum format, GLenum type, GLvoid *data); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint list, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint list, GLenum pname, GLint *params); +GLAPI void APIENTRY glListParameterfSGIX (GLuint list, GLenum pname, GLfloat param); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint list, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glListParameteriSGIX (GLuint list, GLenum pname, GLint param); +GLAPI void APIENTRY glListParameterivSGIX (GLuint list, GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum face, GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum func, GLclampf ref); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint first, GLsizei count); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum pname, GLdouble *params); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum face, GLenum mode); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum light, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum light, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum light, GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum face, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum face, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum face, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum light, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum light, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum face, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum face, GLenum pname, GLint *params); +GLAPI void APIENTRY glLightEnviSGIX (GLenum pname, GLint param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum mode); +GLAPI void APIENTRY glTextureLightEXT (GLenum pname); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum face, GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#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_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint marker); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *markerp); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *markerp); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei range); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint marker, GLsizei range); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint marker); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint size, GLenum type, const GLvoid* *pointer); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum type, const GLvoid* *pointer); +GLAPI void APIENTRY glColorPointervINTEL (GLint size, GLenum type, const GLvoid* *pointer); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint size, GLenum type, const GLvoid* *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat coord); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *coord); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble coord); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte tx, GLbyte ty, GLbyte tz); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glTangent3dEXT (GLdouble tx, GLdouble ty, GLdouble tz); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glTangent3fEXT (GLfloat tx, GLfloat ty, GLfloat tz); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glTangent3iEXT (GLint tx, GLint ty, GLint tz); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *v); +GLAPI void APIENTRY glTangent3sEXT (GLshort tx, GLshort ty, GLshort tz); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *v); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte bx, GLbyte by, GLbyte bz); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble bx, GLdouble by, GLdouble bz); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat bx, GLfloat by, GLfloat bz); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glBinormal3iEXT (GLint bx, GLint by, GLint bz); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *v); +GLAPI void APIENTRY glBinormal3sEXT (GLshort bx, GLshort by, GLshort bz); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *v); +GLAPI void APIENTRY glTangentPointerEXT (GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort factor); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint factor); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat factor); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble factor); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort factor); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint factor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint code); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort code); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte code); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *code); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *code); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *code); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *rc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *rc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat weight); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *weight); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei length, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum pname, GLfloat param); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum pname, const GLint *params); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glCombinerInputNV (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glCombinerOutputNV (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum stage, GLenum portion, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum variable, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iMESA (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iMESA (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos4iMESA (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint stride, const GLboolean* *pointer, GLint ptrstride); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glVertexPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternEXT (GLenum pattern); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_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 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum pname, const GLvoid *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#endif + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint fence); +GLAPI void APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); +GLAPI void APIENTRY glFinishFenceNV (GLuint fence); +GLAPI void APIENTRY glSetFenceNV (GLuint fence, GLenum condition); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#endif + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +GLAPI void APIENTRY glMapParameterivNV (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMapParameterfvNV (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum target, GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glEvalMapsNV (GLenum target, GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum stage, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum stage, GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#endif + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#endif + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei n, const GLuint *programs, GLboolean *residences); +GLAPI void APIENTRY glBindProgramNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glExecuteProgramNV (GLenum target, GLuint id, const GLfloat *params); +GLAPI void APIENTRY glGenProgramsNV (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum target, GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetProgramivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringNV (GLuint id, GLenum pname, GLubyte *program); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum target, GLuint address, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint index, GLenum pname, GLvoid* *pointer); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint id); +GLAPI void APIENTRY glLoadProgramNV (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum target, GLuint index, const GLdouble *v); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum target, GLuint index, const GLfloat *v); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glTrackMatrixNV (GLenum target, GLuint address, GLenum matrix, GLenum transform); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint index, GLsizei count, const GLubyte *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#endif + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#endif + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#endif + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum pname, const GLint *param); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum pname, GLfloat *param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint range); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint dst, GLuint coord, GLenum swizzle); +GLAPI void APIENTRY glSampleMapATI (GLuint dst, GLuint interp, GLenum swizzle); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint dst, const GLfloat *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum pname, GLint param); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum pname, GLfloat param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei size, const GLvoid *pointer, GLenum usage); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint buffer, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glArrayObjectATI (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum array, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum array, GLenum pname, GLint *params); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint id, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint id, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const GLvoid *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint id); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint range); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint id); +GLAPI void APIENTRY glShaderOp1EXT (GLenum op, GLuint res, GLuint arg1); +GLAPI void APIENTRY glShaderOp2EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +GLAPI void APIENTRY glShaderOp3EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +GLAPI void APIENTRY glSwizzleEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glWriteMaskEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glInsertComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI void APIENTRY glExtractComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +GLAPI void APIENTRY glSetInvariantEXT (GLuint id, GLenum type, const GLvoid *addr); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint id, GLenum type, const GLvoid *addr); +GLAPI void APIENTRY glVariantbvEXT (GLuint id, const GLbyte *addr); +GLAPI void APIENTRY glVariantsvEXT (GLuint id, const GLshort *addr); +GLAPI void APIENTRY glVariantivEXT (GLuint id, const GLint *addr); +GLAPI void APIENTRY glVariantfvEXT (GLuint id, const GLfloat *addr); +GLAPI void APIENTRY glVariantdvEXT (GLuint id, const GLdouble *addr); +GLAPI void APIENTRY glVariantubvEXT (GLuint id, const GLubyte *addr); +GLAPI void APIENTRY glVariantusvEXT (GLuint id, const GLushort *addr); +GLAPI void APIENTRY glVariantuivEXT (GLuint id, const GLuint *addr); +GLAPI void APIENTRY glVariantPointerEXT (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint id); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint id); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum light, GLenum value); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum face, GLenum value); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum unit, GLenum coord, GLenum value); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum unit, GLenum value); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum value); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint id, GLenum cap); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint id, GLenum value, GLvoid* *data); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid* *data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum stream, GLshort x); +GLAPI void APIENTRY glVertexStream1svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream1iATI (GLenum stream, GLint x); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream1fATI (GLenum stream, GLfloat x); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream1dATI (GLenum stream, GLdouble x); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream2sATI (GLenum stream, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexStream2svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream2iATI (GLenum stream, GLint x, GLint y); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream2fATI (GLenum stream, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream2dATI (GLenum stream, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream3sATI (GLenum stream, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream3iATI (GLenum stream, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream3fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream3dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream4sATI (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexStream4svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream4iATI (GLenum stream, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream4fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream4dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glNormalStream3bATI (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum stream, const GLbyte *coords); +GLAPI void APIENTRY glNormalStream3sATI (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +GLAPI void APIENTRY glNormalStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glNormalStream3iATI (GLenum stream, GLint nx, GLint ny, GLint nz); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glNormalStream3fATI (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glNormalStream3dATI (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum stream); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum pname, GLint param); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum pname, GLfloat param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum type, const GLvoid *pointer); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum mode, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#endif + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameterivNV (GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#endif + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum face); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#endif + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum type, const GLvoid *pointer); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei n, GLuint *fences); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint fence); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum object, GLuint name); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum object, GLint name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint array); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint array); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei length, GLvoid *pointer); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei length, GLvoid *pointer); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum pname, GLint param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#endif + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei n, const GLenum *bufs); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +/* This is really a WGL extension, but defines some associated GL enums. + * ATI does not export "GL_ATI_pixel_format_float" in the GL_EXTENSIONS string. + */ +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#endif + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#endif + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#endif + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +/* Some NV_fragment_program entry points are shared with ARB_vertex_program. */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor4hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV s); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum target, GLhalfNV s); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum target, GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV fog); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *fog); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV weight); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *weight); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint index, GLhalfNV x); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint index, GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum target, GLsizei length, GLvoid *pointer); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#endif + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint buffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint index, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd zmin, GLclampd zmax); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum modeRGB, GLenum modeAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#endif + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#endif + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#endif + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei len, const GLvoid *string); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const GLvoid *string); +#endif + +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil 1 +#endif + +#ifndef GL_EXT_stencil_clear_tag +#define GL_EXT_stencil_clear_tag 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilClearTagEXT (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILCLEARTAGEXTPROC) (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB 1 +#endif + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlitFramebufferEXT (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif + +#ifndef GL_MESAX_texture_stack +#define GL_MESAX_texture_stack 1 +#endif + +#ifndef GL_EXT_timer_query +#define GL_EXT_timer_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64EXT *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64EXT *params); +#endif + +#ifndef GL_EXT_gpu_program_parameters +#define GL_EXT_gpu_program_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramEnvParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif + +#ifndef GL_APPLE_flush_buffer_range +#define GL_APPLE_flush_buffer_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferParameteriAPPLE (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glFlushMappedBufferRangeAPPLE (GLenum target, GLintptr offset, GLsizeiptr size); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); +#endif + +#ifndef GL_NV_gpu_program4 +#define GL_NV_gpu_program4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramLocalParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramLocalParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramLocalParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramLocalParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramLocalParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramLocalParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramEnvParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramEnvParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramEnvParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramEnvParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIuivNV (GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIuivNV (GLenum target, GLuint index, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +#endif + +#ifndef GL_NV_geometry_program4 +#define GL_NV_geometry_program4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramVertexLimitNV (GLenum target, GLint limit); +GLAPI void APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif + +#ifndef GL_EXT_geometry_shader4 +#define GL_EXT_geometry_shader4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); +#endif + +#ifndef GL_NV_vertex_program4 +#define GL_NV_vertex_program4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribI1iEXT (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2iEXT (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3iEXT (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4iEXT (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1uiEXT (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2uiEXT (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3uiEXT (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4uiEXT (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bvEXT (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4svEXT (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubvEXT (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usvEXT (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribIPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribIivEXT (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuivEXT (GLuint index, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); +#endif + +#ifndef GL_EXT_gpu_shader4 +#define GL_EXT_gpu_shader4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformuivEXT (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocationEXT (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1uiEXT (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2uiEXT (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uivEXT (GLint location, GLsizei count, const GLuint *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +#endif + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_packed_float +#define GL_EXT_packed_float 1 +#endif + +#ifndef GL_EXT_texture_array +#define GL_EXT_texture_array 1 +#endif + +#ifndef GL_EXT_texture_buffer_object +#define GL_EXT_texture_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#endif + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc 1 +#endif + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 +#endif + +#ifndef GL_EXT_texture_shared_exponent +#define GL_EXT_texture_shared_exponent 1 +#endif + +#ifndef GL_NV_depth_buffer_float +#define GL_NV_depth_buffer_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangedNV (GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glClearDepthdNV (GLdouble depth); +GLAPI void APIENTRY glDepthBoundsdNV (GLdouble zmin, GLdouble zmax); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); +typedef void (APIENTRYP PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); +#endif + +#ifndef GL_NV_fragment_program4 +#define GL_NV_fragment_program4 1 +#endif + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_NV_framebuffer_multisample_coverage 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB 1 +#endif + +#ifndef GL_NV_geometry_shader4 +#define GL_NV_geometry_shader4 1 +#endif + +#ifndef GL_NV_parameter_buffer_object +#define GL_NV_parameter_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramBufferParametersfvNV (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramBufferParametersIivNV (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramBufferParametersIuivNV (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLuint *params); +#endif + +#ifndef GL_EXT_draw_buffers2 +#define GL_EXT_draw_buffers2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaskIndexedEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleanIndexedvEXT (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegerIndexedvEXT (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnableIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glDisableIndexedEXT (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT (GLenum target, GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); +#endif + +#ifndef GL_NV_transform_feedback +#define GL_NV_transform_feedback 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackNV (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackNV (void); +GLAPI void APIENTRY glTransformFeedbackAttribsNV (GLuint count, const GLint *attribs, GLenum bufferMode); +GLAPI void APIENTRY glBindBufferRangeNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseNV (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsNV (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +GLAPI void APIENTRY glActiveVaryingNV (GLuint program, const GLchar *name); +GLAPI GLint APIENTRY glGetVaryingLocationNV (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetActiveVaryingNV (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetTransformFeedbackVaryingNV (GLuint program, GLuint index, GLint *location); +GLAPI void APIENTRY glTransformFeedbackStreamAttribsNV (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLuint count, const GLint *attribs, GLenum bufferMode); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +typedef void (APIENTRYP PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif + +#ifndef GL_EXT_bindable_uniform +#define GL_EXT_bindable_uniform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformBufferEXT (GLuint program, GLint location, GLuint buffer); +GLAPI GLint APIENTRY glGetUniformBufferSizeEXT (GLuint program, GLint location); +GLAPI GLintptr APIENTRY glGetUniformOffsetEXT (GLuint program, GLint location); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); +typedef GLint (APIENTRYP PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); +typedef GLintptr (APIENTRYP PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); +#endif + +#ifndef GL_EXT_texture_integer +#define GL_EXT_texture_integer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearColorIiEXT (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void APIENTRY glClearColorIuiEXT (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (APIENTRYP PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif + +#ifndef GL_GREMEDY_frame_terminator +#define GL_GREMEDY_frame_terminator 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameTerminatorGREMEDY (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMETERMINATORGREMEDYPROC) (void); +#endif + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRenderNV (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); +#endif + +#ifndef GL_NV_present_video +#define GL_NV_present_video 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPresentFrameKeyedNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +GLAPI void APIENTRY glPresentFrameDualFillNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +GLAPI void APIENTRY glGetVideoivNV (GLuint video_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideouivNV (GLuint video_slot, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetVideoi64vNV (GLuint video_slot, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVideoui64vNV (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +typedef void (APIENTRYP PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +typedef void (APIENTRYP PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif + +#ifndef GL_EXT_transform_feedback +#define GL_EXT_transform_feedback 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackEXT (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackEXT (void); +GLAPI void APIENTRY glBindBufferRangeEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseEXT (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsEXT (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVaryingEXT (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif + +#ifndef GL_EXT_direct_state_access +#define GL_EXT_direct_state_access 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glPushClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixLoadIdentityEXT (GLenum mode); +GLAPI void APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixPopEXT (GLenum mode); +GLAPI void APIENTRY glMatrixPushEXT (GLenum mode); +GLAPI void APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glTextureParameterfEXT (GLuint texture, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTextureParameteriEXT (GLuint texture, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetTextureImageEXT (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +GLAPI void APIENTRY glGetTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureLevelParameterfvEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameterivEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glMultiTexParameterfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexParameteriEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetMultiTexImageEXT (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +GLAPI void APIENTRY glGetMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glBindMultiTextureEXT (GLenum texunit, GLenum target, GLuint texture); +GLAPI void APIENTRY glEnableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glMultiTexCoordPointerEXT (GLenum texunit, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glMultiTexEnvfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexEnviEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexGendEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +GLAPI void APIENTRY glMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void APIENTRY glMultiTexGenfEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexGeniEXT (GLenum texunit, GLenum coord, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFloatIndexedvEXT (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoubleIndexedvEXT (GLenum target, GLuint index, GLdouble *data); +GLAPI void APIENTRY glGetPointerIndexedvEXT (GLenum target, GLuint index, GLvoid* *data); +GLAPI void APIENTRY glCompressedTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glGetCompressedTextureImageEXT (GLuint texture, GLenum target, GLint lod, GLvoid *img); +GLAPI void APIENTRY glCompressedMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glGetCompressedMultiTexImageEXT (GLenum texunit, GLenum target, GLint lod, GLvoid *img); +GLAPI void APIENTRY glNamedProgramStringEXT (GLuint program, GLenum target, GLenum format, GLsizei len, const GLvoid *string); +GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT (GLuint program, GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT (GLuint program, GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT (GLuint program, GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT (GLuint program, GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramivEXT (GLuint program, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedProgramStringEXT (GLuint program, GLenum target, GLenum pname, GLvoid *string); +GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT (GLuint program, GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT (GLuint program, GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT (GLuint program, GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT (GLuint program, GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glNamedBufferDataEXT (GLuint buffer, GLsizeiptr size, const GLvoid *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, const GLvoid *data); +GLAPI GLvoid* APIENTRY glMapNamedBufferEXT (GLuint buffer, GLenum access); +GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT (GLuint buffer); +GLAPI GLvoid* APIENTRY glMapNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glNamedCopyBufferSubDataEXT (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glGetNamedBufferParameterivEXT (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferPointervEXT (GLuint buffer, GLenum pname, GLvoid* *params); +GLAPI void APIENTRY glGetNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLvoid *data); +GLAPI void APIENTRY glTextureBufferEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glMultiTexBufferEXT (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glNamedRenderbufferStorageEXT (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glNamedFramebufferTexture1DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture2DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture3DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateTextureMipmapEXT (GLuint texture, GLenum target); +GLAPI void APIENTRY glGenerateMultiTexMipmapEXT (GLenum texunit, GLenum target); +GLAPI void APIENTRY glFramebufferDrawBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glFramebufferDrawBuffersEXT (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glFramebufferReadBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glGetFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedFramebufferTextureEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +GLAPI void APIENTRY glTextureRenderbufferEXT (GLuint texture, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glMultiTexRenderbufferEXT (GLenum texunit, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glProgramUniform1dEXT (GLuint program, GLint location, GLdouble x); +GLAPI void APIENTRY glProgramUniform2dEXT (GLuint program, GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glProgramUniform3dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glProgramUniform4dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramUniform1dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +typedef void (APIENTRYP PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +typedef void (APIENTRYP PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum target, GLuint index, GLdouble *data); +typedef void (APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLvoid* *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint lod, GLvoid *img); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint lod, GLvoid *img); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); +typedef GLvoid* (APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef void (APIENTRYP PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); +typedef void (APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC) (GLuint program, GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +#endif + +#ifndef GL_EXT_vertex_array_bgra +#define GL_EXT_vertex_array_bgra 1 +#endif + +#ifndef GL_EXT_texture_swizzle +#define GL_EXT_texture_swizzle 1 +#endif + +#ifndef GL_NV_explicit_multisample +#define GL_NV_explicit_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMultisamplefvNV (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaskIndexedNV (GLuint index, GLbitfield mask); +GLAPI void APIENTRY glTexRenderbufferNV (GLenum target, GLuint renderbuffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); +#endif + +#ifndef GL_NV_transform_feedback2 +#define GL_NV_transform_feedback2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedbackNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacksNV (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacksNV (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedbackNV (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedbackNV (void); +GLAPI void APIENTRY glResumeTransformFeedbackNV (void); +GLAPI void APIENTRY glDrawTransformFeedbackNV (GLenum mode, GLuint id); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSNVPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSNVPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKNVPROC) (GLenum mode, GLuint id); +#endif + +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo 1 +#endif + +#ifndef GL_AMD_performance_monitor +#define GL_AMD_performance_monitor 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +GLAPI void APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, GLvoid *data); +GLAPI void APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +GLAPI void APIENTRY glBeginPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glEndPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, GLvoid *data); +typedef void (APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +typedef void (APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif + +#ifndef GL_AMD_texture_texture4 +#define GL_AMD_texture_texture4 1 +#endif + +#ifndef GL_AMD_vertex_shader_tesselator +#define GL_AMD_vertex_shader_tesselator 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTessellationFactorAMD (GLfloat factor); +GLAPI void APIENTRY glTessellationModeAMD (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTESSELLATIONFACTORAMDPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLTESSELLATIONMODEAMDPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_provoking_vertex +#define GL_EXT_provoking_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertexEXT (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_texture_snorm +#define GL_EXT_texture_snorm 1 +#endif + +#ifndef GL_AMD_draw_buffers_blend +#define GL_AMD_draw_buffers_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncIndexedAMD (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateIndexedAMD (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glBlendEquationIndexedAMD (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateIndexedAMD (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCINDEXEDAMDPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONINDEXEDAMDPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_APPLE_texture_range +#define GL_APPLE_texture_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureRangeAPPLE (GLenum target, GLsizei length, const GLvoid *pointer); +GLAPI void APIENTRY glGetTexParameterPointervAPPLE (GLenum target, GLenum pname, GLvoid* *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_APPLE_float_pixels +#define GL_APPLE_float_pixels 1 +#endif + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_APPLE_vertex_program_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEnableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glDisableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI GLboolean APIENTRY glIsVertexAttribEnabledAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glMapVertexAttrib1dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib1fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void APIENTRY glMapVertexAttrib2dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib2fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef GLboolean (APIENTRYP PFNGLISVERTEXATTRIBENABLEDAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_APPLE_aux_depth_stencil 1 +#endif + +#ifndef GL_APPLE_object_purgeable +#define GL_APPLE_object_purgeable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glObjectPurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI GLenum APIENTRY glObjectUnpurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI void APIENTRY glGetObjectParameterivAPPLE (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLenum (APIENTRYP PFNGLOBJECTPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef GLenum (APIENTRYP PFNGLOBJECTUNPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVAPPLEPROC) (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif + +#ifndef GL_APPLE_row_bytes +#define GL_APPLE_row_bytes 1 +#endif + +#ifndef GL_APPLE_rgb_422 +#define GL_APPLE_rgb_422 1 +#endif + +#ifndef GL_NV_video_capture +#define GL_NV_video_capture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glBindVideoCaptureStreamBufferNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +GLAPI void APIENTRY glBindVideoCaptureStreamTextureNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +GLAPI void APIENTRY glEndVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glGetVideoCaptureivNV (GLuint video_capture_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVideoCaptureStreamdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +GLAPI GLenum APIENTRY glVideoCaptureNV (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +GLAPI void APIENTRY glVideoCaptureStreamParameterivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENDVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTUREIVNVPROC) (GLuint video_capture_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +typedef GLenum (APIENTRYP PFNGLVIDEOCAPTURENVPROC) (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif + +#ifndef GL_NV_copy_image +#define GL_NV_copy_image 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyImageSubDataNV (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATANVPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif + +#ifndef GL_EXT_separate_shader_objects +#define GL_EXT_separate_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseShaderProgramEXT (GLenum type, GLuint program); +GLAPI void APIENTRY glActiveProgramEXT (GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramEXT (GLenum type, const GLchar *string); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC) (GLenum type, GLuint program); +typedef void (APIENTRYP PFNGLACTIVEPROGRAMEXTPROC) (GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC) (GLenum type, const GLchar *string); +#endif + +#ifndef GL_NV_parameter_buffer_object2 +#define GL_NV_parameter_buffer_object2 1 +#endif + +#ifndef GL_NV_shader_buffer_load +#define GL_NV_shader_buffer_load 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMakeBufferResidentNV (GLenum target, GLenum access); +GLAPI void APIENTRY glMakeBufferNonResidentNV (GLenum target); +GLAPI GLboolean APIENTRY glIsBufferResidentNV (GLenum target); +GLAPI void APIENTRY glMakeNamedBufferResidentNV (GLuint buffer, GLenum access); +GLAPI void APIENTRY glMakeNamedBufferNonResidentNV (GLuint buffer); +GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV (GLuint buffer); +GLAPI void APIENTRY glGetBufferParameterui64vNV (GLenum target, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetNamedBufferParameterui64vNV (GLuint buffer, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetIntegerui64vNV (GLenum value, GLuint64EXT *result); +GLAPI void APIENTRY glUniformui64NV (GLint location, GLuint64EXT value); +GLAPI void APIENTRY glUniformui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformui64vNV (GLuint program, GLint location, GLuint64EXT *params); +GLAPI void APIENTRY glProgramUniformui64NV (GLuint program, GLint location, GLuint64EXT value); +GLAPI void APIENTRY glProgramUniformui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC) (GLenum target, GLenum access); +typedef void (APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC) (GLenum target); +typedef GLboolean (APIENTRYP PFNGLISBUFFERRESIDENTNVPROC) (GLenum target); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC) (GLuint buffer, GLenum access); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC) (GLuint buffer); +typedef GLboolean (APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC) (GLenum target, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC) (GLuint buffer, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETINTEGERUI64VNVPROC) (GLenum value, GLuint64EXT *result); +typedef void (APIENTRYP PFNGLUNIFORMUI64NVPROC) (GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLUNIFORMUI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC) (GLuint program, GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_NV_vertex_buffer_unified_memory 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferAddressRangeNV (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +GLAPI void APIENTRY glVertexFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glNormalFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glIndexFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glTexCoordFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glEdgeFlagFormatNV (GLsizei stride); +GLAPI void APIENTRY glSecondaryColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glFogCoordFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormatNV (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +GLAPI void APIENTRY glVertexAttribIFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glGetIntegerui64i_vNV (GLenum value, GLuint index, GLuint64EXT *result); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC) (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +typedef void (APIENTRYP PFNGLVERTEXFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLNORMALFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLINDEXFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLTEXCOORDFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLEDGEFLAGFORMATNVPROC) (GLsizei stride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLFOGCOORDFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#endif + +#ifndef GL_NV_texture_barrier +#define GL_NV_texture_barrier 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureBarrierNV (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC) (void); +#endif + +#ifndef GL_AMD_shader_stencil_export +#define GL_AMD_shader_stencil_export 1 +#endif + +#ifndef GL_AMD_seamless_cubemap_per_texture +#define GL_AMD_seamless_cubemap_per_texture 1 +#endif + +#ifndef GL_AMD_conservative_depth +#define GL_AMD_conservative_depth 1 +#endif + +#ifndef GL_EXT_shader_image_load_store +#define GL_EXT_shader_image_load_store 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindImageTextureEXT (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREEXTPROC) (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +typedef void (APIENTRYP PFNGLMEMORYBARRIEREXTPROC) (GLbitfield barriers); +#endif + +#ifndef GL_EXT_vertex_attrib_64bit +#define GL_EXT_vertex_attrib_64bit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1dEXT (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2dEXT (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribLdvEXT (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DEXTPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DEXTPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVEXTPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +#endif + +#ifndef GL_NV_gpu_program5 +#define GL_NV_gpu_program5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramSubroutineParametersuivNV (GLenum target, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramSubroutineParameteruivNV (GLenum target, GLuint index, GLuint *param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC) (GLenum target, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC) (GLenum target, GLuint index, GLuint *param); +#endif + +#ifndef GL_NV_gpu_shader5 +#define GL_NV_gpu_shader5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); +GLAPI void APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); +GLAPI void APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); +GLAPI void APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); +GLAPI void APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); +GLAPI void APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif + +#ifndef GL_NV_shader_buffer_store +#define GL_NV_shader_buffer_store 1 +#endif + +#ifndef GL_NV_tessellation_program5 +#define GL_NV_tessellation_program5 1 +#endif + +#ifndef GL_NV_vertex_attrib_integer_64bit +#define GL_NV_vertex_attrib_integer_64bit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1i64NV (GLuint index, GLint64EXT x); +GLAPI void APIENTRY glVertexAttribL2i64NV (GLuint index, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glVertexAttribL3i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glVertexAttribL4i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glVertexAttribL1i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL1ui64NV (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL2ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glVertexAttribL3ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glVertexAttribL4ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glVertexAttribL1ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLi64vNV (GLuint index, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVertexAttribLui64vNV (GLuint index, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glVertexAttribLFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC) (GLuint index, GLint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC) (GLuint index, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif + +#ifndef GL_NV_multisample_coverage +#define GL_NV_multisample_coverage 1 +#endif + +#ifndef GL_AMD_name_gen_delete +#define GL_AMD_name_gen_delete 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenNamesAMD (GLenum identifier, GLuint num, GLuint *names); +GLAPI void APIENTRY glDeleteNamesAMD (GLenum identifier, GLuint num, const GLuint *names); +GLAPI GLboolean APIENTRY glIsNameAMD (GLenum identifier, GLuint name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENNAMESAMDPROC) (GLenum identifier, GLuint num, GLuint *names); +typedef void (APIENTRYP PFNGLDELETENAMESAMDPROC) (GLenum identifier, GLuint num, const GLuint *names); +typedef GLboolean (APIENTRYP PFNGLISNAMEAMDPROC) (GLenum identifier, GLuint name); +#endif + +#ifndef GL_AMD_debug_output +#define GL_AMD_debug_output 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageEnableAMD (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertAMD (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackAMD (GLDEBUGPROCAMD callback, GLvoid *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogAMD (GLuint count, GLsizei bufsize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEBUGMESSAGEENABLEAMDPROC) (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTAMDPROC) (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKAMDPROC) (GLDEBUGPROCAMD callback, GLvoid *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGAMDPROC) (GLuint count, GLsizei bufsize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif + +#ifndef GL_NV_vdpau_interop +#define GL_NV_vdpau_interop 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVDPAUInitNV (const GLvoid *vdpDevice, const GLvoid *getProcAddress); +GLAPI void APIENTRY glVDPAUFiniNV (void); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceNV (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterOutputSurfaceNV (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI void APIENTRY glVDPAUIsSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUUnregisterSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUGetSurfaceivNV (GLvdpauSurfaceNV surface, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +GLAPI void APIENTRY glVDPAUSurfaceAccessNV (GLvdpauSurfaceNV surface, GLenum access); +GLAPI void APIENTRY glVDPAUMapSurfacesNV (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +GLAPI void APIENTRY glVDPAUUnmapSurfacesNV (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVDPAUINITNVPROC) (const GLvoid *vdpDevice, const GLvoid *getProcAddress); +typedef void (APIENTRYP PFNGLVDPAUFININVPROC) (void); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACENVPROC) (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC) (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef void (APIENTRYP PFNGLVDPAUISSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUUNREGISTERSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUGETSURFACEIVNVPROC) (GLvdpauSurfaceNV surface, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLVDPAUSURFACEACCESSNVPROC) (GLvdpauSurfaceNV surface, GLenum access); +typedef void (APIENTRYP PFNGLVDPAUMAPSURFACESNVPROC) (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +typedef void (APIENTRYP PFNGLVDPAUUNMAPSURFACESNVPROC) (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#define GL_AMD_transform_feedback3_lines_triangles 1 +#endif + +#ifndef GL_AMD_depth_clamp_separate +#define GL_AMD_depth_clamp_separate 1 +#endif + +#ifndef GL_EXT_texture_sRGB_decode +#define GL_EXT_texture_sRGB_decode 1 +#endif + +#ifndef GL_NV_texture_multisample +#define GL_NV_texture_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage2DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTexImage3DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#endif + +#ifndef GL_AMD_blend_minmax_factor +#define GL_AMD_blend_minmax_factor 1 +#endif + +#ifndef GL_AMD_sample_positions +#define GL_AMD_sample_positions 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSetMultisamplefvAMD (GLenum pname, GLuint index, const GLfloat *val); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSETMULTISAMPLEFVAMDPROC) (GLenum pname, GLuint index, const GLfloat *val); +#endif + +#ifndef GL_EXT_x11_sync_object +#define GL_EXT_x11_sync_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glImportSyncEXT (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLsync (APIENTRYP PFNGLIMPORTSYNCEXTPROC) (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#endif + +#ifndef GL_AMD_multi_draw_indirect +#define GL_AMD_multi_draw_indirect 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectAMD (GLenum mode, const GLvoid *indirect, GLsizei primcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectAMD (GLenum mode, GLenum type, const GLvoid *indirect, GLsizei primcount, GLsizei stride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTAMDPROC) (GLenum mode, const GLvoid *indirect, GLsizei primcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC) (GLenum mode, GLenum type, const GLvoid *indirect, GLsizei primcount, GLsizei stride); +#endif + +#ifndef GL_EXT_framebuffer_multisample_blit_scaled +#define GL_EXT_framebuffer_multisample_blit_scaled 1 +#endif + +#ifndef GL_NV_path_rendering +#define GL_NV_path_rendering 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenPathsNV (GLsizei range); +GLAPI void APIENTRY glDeletePathsNV (GLuint path, GLsizei range); +GLAPI GLboolean APIENTRY glIsPathNV (GLuint path); +GLAPI void APIENTRY glPathCommandsNV (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +GLAPI void APIENTRY glPathCoordsNV (GLuint path, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +GLAPI void APIENTRY glPathSubCommandsNV (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +GLAPI void APIENTRY glPathSubCoordsNV (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +GLAPI void APIENTRY glPathStringNV (GLuint path, GLenum format, GLsizei length, const GLvoid *pathString); +GLAPI void APIENTRY glPathGlyphsNV (GLuint firstPathName, GLenum fontTarget, const GLvoid *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const GLvoid *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glPathGlyphRangeNV (GLuint firstPathName, GLenum fontTarget, const GLvoid *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glWeightPathsNV (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +GLAPI void APIENTRY glCopyPathNV (GLuint resultPath, GLuint srcPath); +GLAPI void APIENTRY glInterpolatePathsNV (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +GLAPI void APIENTRY glTransformPathNV (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathParameterivNV (GLuint path, GLenum pname, const GLint *value); +GLAPI void APIENTRY glPathParameteriNV (GLuint path, GLenum pname, GLint value); +GLAPI void APIENTRY glPathParameterfvNV (GLuint path, GLenum pname, const GLfloat *value); +GLAPI void APIENTRY glPathParameterfNV (GLuint path, GLenum pname, GLfloat value); +GLAPI void APIENTRY glPathDashArrayNV (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +GLAPI void APIENTRY glPathStencilFuncNV (GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glPathStencilDepthOffsetNV (GLfloat factor, GLfloat units); +GLAPI void APIENTRY glStencilFillPathNV (GLuint path, GLenum fillMode, GLuint mask); +GLAPI void APIENTRY glStencilStrokePathNV (GLuint path, GLint reference, GLuint mask); +GLAPI void APIENTRY glStencilFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glStencilStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathCoverDepthFuncNV (GLenum func); +GLAPI void APIENTRY glPathColorGenNV (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +GLAPI void APIENTRY glPathTexGenNV (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +GLAPI void APIENTRY glPathFogGenNV (GLenum genMode); +GLAPI void APIENTRY glCoverFillPathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverStrokePathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glGetPathParameterivNV (GLuint path, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathParameterfvNV (GLuint path, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathCommandsNV (GLuint path, GLubyte *commands); +GLAPI void APIENTRY glGetPathCoordsNV (GLuint path, GLfloat *coords); +GLAPI void APIENTRY glGetPathDashArrayNV (GLuint path, GLfloat *dashArray); +GLAPI void APIENTRY glGetPathMetricsNV (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathMetricRangeNV (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathSpacingNV (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +GLAPI void APIENTRY glGetPathColorGenivNV (GLenum color, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathColorGenfvNV (GLenum color, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathTexGenivNV (GLenum texCoordSet, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathTexGenfvNV (GLenum texCoordSet, GLenum pname, GLfloat *value); +GLAPI GLboolean APIENTRY glIsPointInFillPathNV (GLuint path, GLuint mask, GLfloat x, GLfloat y); +GLAPI GLboolean APIENTRY glIsPointInStrokePathNV (GLuint path, GLfloat x, GLfloat y); +GLAPI GLfloat APIENTRY glGetPathLengthNV (GLuint path, GLsizei startSegment, GLsizei numSegments); +GLAPI GLboolean APIENTRY glPointAlongPathNV (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENPATHSNVPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEPATHSNVPROC) (GLuint path, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISPATHNVPROC) (GLuint path); +typedef void (APIENTRYP PFNGLPATHCOMMANDSNVPROC) (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +typedef void (APIENTRYP PFNGLPATHCOORDSNVPROC) (GLuint path, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC) (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOORDSNVPROC) (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const GLvoid *coords); +typedef void (APIENTRYP PFNGLPATHSTRINGNVPROC) (GLuint path, GLenum format, GLsizei length, const GLvoid *pathString); +typedef void (APIENTRYP PFNGLPATHGLYPHSNVPROC) (GLuint firstPathName, GLenum fontTarget, const GLvoid *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const GLvoid *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLPATHGLYPHRANGENVPROC) (GLuint firstPathName, GLenum fontTarget, const GLvoid *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLWEIGHTPATHSNVPROC) (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +typedef void (APIENTRYP PFNGLCOPYPATHNVPROC) (GLuint resultPath, GLuint srcPath); +typedef void (APIENTRYP PFNGLINTERPOLATEPATHSNVPROC) (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +typedef void (APIENTRYP PFNGLTRANSFORMPATHNVPROC) (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, const GLint *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERINVPROC) (GLuint path, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, const GLfloat *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFNVPROC) (GLuint path, GLenum pname, GLfloat value); +typedef void (APIENTRYP PFNGLPATHDASHARRAYNVPROC) (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +typedef void (APIENTRYP PFNGLPATHSTENCILFUNCNVPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC) (GLfloat factor, GLfloat units); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC) (GLenum func); +typedef void (APIENTRYP PFNGLPATHCOLORGENNVPROC) (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHTEXGENNVPROC) (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHFOGGENNVPROC) (GLenum genMode); +typedef void (APIENTRYP PFNGLCOVERFILLPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHCOMMANDSNVPROC) (GLuint path, GLubyte *commands); +typedef void (APIENTRYP PFNGLGETPATHCOORDSNVPROC) (GLuint path, GLfloat *coords); +typedef void (APIENTRYP PFNGLGETPATHDASHARRAYNVPROC) (GLuint path, GLfloat *dashArray); +typedef void (APIENTRYP PFNGLGETPATHMETRICSNVPROC) (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHMETRICRANGENVPROC) (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHSPACINGNVPROC) (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const GLvoid *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENIVNVPROC) (GLenum color, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENFVNVPROC) (GLenum color, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENIVNVPROC) (GLenum texCoordSet, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENFVNVPROC) (GLenum texCoordSet, GLenum pname, GLfloat *value); +typedef GLboolean (APIENTRYP PFNGLISPOINTINFILLPATHNVPROC) (GLuint path, GLuint mask, GLfloat x, GLfloat y); +typedef GLboolean (APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC) (GLuint path, GLfloat x, GLfloat y); +typedef GLfloat (APIENTRYP PFNGLGETPATHLENGTHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments); +typedef GLboolean (APIENTRYP PFNGLPOINTALONGPATHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +#endif + +#ifndef GL_AMD_pinned_memory +#define GL_AMD_pinned_memory 1 +#endif + +#ifndef GL_AMD_stencil_operation_extended +#define GL_AMD_stencil_operation_extended 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpValueAMD (GLenum face, GLuint value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPVALUEAMDPROC) (GLenum face, GLuint value); +#endif + +#ifndef GL_AMD_vertex_shader_viewport_index +#define GL_AMD_vertex_shader_viewport_index 1 +#endif + +#ifndef GL_AMD_vertex_shader_layer +#define GL_AMD_vertex_shader_layer 1 +#endif + +#ifndef GL_NV_bindless_texture +#define GL_NV_bindless_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleNV (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleNV (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentNV (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentNV (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleNV (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentNV (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentNV (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64NV (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vNV (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64NV (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentNV (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentNV (GLuint64 handle); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLENVPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLENVPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle); +#endif + +#ifndef GL_NV_shader_atomic_float +#define GL_NV_shader_atomic_float 1 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/neo/renderer/OpenGL/qgl.h b/neo/renderer/OpenGL/qgl.h new file mode 100644 index 00000000..79afac3a --- /dev/null +++ b/neo/renderer/OpenGL/qgl.h @@ -0,0 +1,549 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + + +#include + + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + +// only use local glext.h if we are not using the system one already +// http://oss.sgi.com/projects/ogl-sample/ABI/ +#ifndef GL_GLEXT_VERSION + +#include "glext.h" + +#endif + +typedef void (*GLExtension_t)(void); + +#ifdef __cplusplus + extern "C" { +#endif + +GLExtension_t GLimp_ExtensionPointer( const char *name ); + +#ifdef __cplusplus + } +#endif + +// GL_EXT_direct_state_access +extern PFNGLBINDMULTITEXTUREEXTPROC qglBindMultiTextureEXT; + +// GL_ARB_texture_compression +extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB; +extern PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC qglCompressedTexSubImage2DARB; +extern PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB; + +// GL_ARB_vertex_buffer_object +extern PFNGLBINDBUFFERARBPROC qglBindBufferARB; +extern PFNGLBINDBUFFERRANGEPROC qglBindBufferRange; +extern PFNGLDELETEBUFFERSARBPROC qglDeleteBuffersARB; +extern PFNGLGENBUFFERSARBPROC qglGenBuffersARB; +extern PFNGLISBUFFERARBPROC qglIsBufferARB; +extern PFNGLBUFFERDATAARBPROC qglBufferDataARB; +extern PFNGLBUFFERSUBDATAARBPROC qglBufferSubDataARB; +extern PFNGLGETBUFFERSUBDATAARBPROC qglGetBufferSubDataARB; +extern PFNGLMAPBUFFERARBPROC qglMapBufferARB; +extern PFNGLUNMAPBUFFERARBPROC qglUnmapBufferARB; +extern PFNGLGETBUFFERPARAMETERIVARBPROC qglGetBufferParameterivARB; +extern PFNGLGETBUFFERPOINTERVARBPROC qglGetBufferPointervARB; + +// GL_ARB_map_buffer_Range +extern PFNGLMAPBUFFERRANGEPROC qglMapBufferRange; + +// GL_ARB_draw_elements_base_vertex +extern PFNGLDRAWELEMENTSBASEVERTEXPROC qglDrawElementsBaseVertex; + +// GL_ARB_vertex_array_object +extern PFNGLGENVERTEXARRAYSPROC qglGenVertexArrays; +extern PFNGLBINDVERTEXARRAYPROC qglBindVertexArray; +extern PFNGLDELETEVERTEXARRAYSPROC qglDeleteVertexArrays; + +// GL_ARB_vertex_program / GL_ARB_fragment_program +extern PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB; +extern PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB; +extern PFNGLDISABLEVERTEXATTRIBARRAYARBPROC qglDisableVertexAttribArrayARB; +extern PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +extern PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +extern PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +extern PFNGLDELETEPROGRAMSARBPROC qglDeleteProgramsARB; +extern PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; + +// GLSL / OpenGL 2.0 +extern PFNGLCREATESHADERPROC qglCreateShader; +extern PFNGLDELETESHADERPROC qglDeleteShader; +extern PFNGLSHADERSOURCEPROC qglShaderSource; +extern PFNGLCOMPILESHADERPROC qglCompileShader; +extern PFNGLGETSHADERIVPROC qglGetShaderiv; +extern PFNGLGETSHADERINFOLOGPROC qglGetShaderInfoLog; +extern PFNGLCREATEPROGRAMPROC qglCreateProgram; +extern PFNGLDELETEPROGRAMPROC qglDeleteProgram; +extern PFNGLATTACHSHADERPROC qglAttachShader; +extern PFNGLDETACHSHADERPROC qglDetachShader; +extern PFNGLLINKPROGRAMPROC qglLinkProgram; +extern PFNGLUSEPROGRAMPROC qglUseProgram; +extern PFNGLGETPROGRAMIVPROC qglGetProgramiv; +extern PFNGLGETPROGRAMINFOLOGPROC qglGetProgramInfoLog; +extern PFNGLPROGRAMPARAMETERIPROC qglProgramParameteri; +extern PFNGLBINDATTRIBLOCATIONPROC qglBindAttribLocation; +extern PFNGLGETUNIFORMLOCATIONPROC qglGetUniformLocation; +extern PFNGLUNIFORM1IPROC qglUniform1i; +extern PFNGLUNIFORM4FVPROC qglUniform4fv; + +// GL_ARB_uniform_buffer_object +extern PFNGLGETUNIFORMBLOCKINDEXPROC qglGetUniformBlockIndex; +extern PFNGLUNIFORMBLOCKBINDINGPROC qglUniformBlockBinding; + +// GL_ATI_separate_stencil / OpenGL 2.0 separate stencil +extern PFNGLSTENCILOPSEPARATEATIPROC qglStencilOpSeparate; +extern PFNGLSTENCILFUNCSEPARATEATIPROC qglStencilFuncSeparate; + +// GL_EXT_depth_bounds_test +extern PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT; + +// GL_ARB_sync +extern PFNGLFENCESYNCPROC qglFenceSync; +extern PFNGLISSYNCPROC qglIsSync; +extern PFNGLCLIENTWAITSYNCPROC qglClientWaitSync; +extern PFNGLDELETESYNCPROC qglDeleteSync; + +// GL_ARB_occlusion_query +extern PFNGLGENQUERIESARBPROC qglGenQueriesARB; +extern PFNGLDELETEQUERIESARBPROC qglDeleteQueriesARB; +extern PFNGLISQUERYARBPROC qglIsQueryARB; +extern PFNGLBEGINQUERYARBPROC qglBeginQueryARB; +extern PFNGLENDQUERYARBPROC qglEndQueryARB; +extern PFNGLGETQUERYIVARBPROC qglGetQueryivARB; +extern PFNGLGETQUERYOBJECTIVARBPROC qglGetQueryObjectivARB; +extern PFNGLGETQUERYOBJECTUIVARBPROC qglGetQueryObjectuivARB; + +// GL_ARB_timer_query / GL_EXT_timer_query +extern PFNGLGETQUERYOBJECTUI64VEXTPROC qglGetQueryObjectui64vEXT; + +// GL_ARB_debug_output +extern PFNGLDEBUGMESSAGECONTROLARBPROC qglDebugMessageControlARB; +extern PFNGLDEBUGMESSAGEINSERTARBPROC qglDebugMessageInsertARB; +extern PFNGLDEBUGMESSAGECALLBACKARBPROC qglDebugMessageCallbackARB; +extern PFNGLGETDEBUGMESSAGELOGARBPROC qglGetDebugMessageLogARB; + +//=========================================================================== + +// non-windows systems will just redefine qgl* to gl* +#if defined( __APPLE__ ) || defined( ID_GL_HARDLINK ) + +#include "qgl_linked.h" + +#else + +// windows systems use a function pointer for each call so we can do our log file intercepts + +extern void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +extern void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern void ( APIENTRY * qglArrayElement )(GLint i); +extern void ( APIENTRY * qglBegin )(GLenum mode); +extern void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +extern void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( APIENTRY * qglCallList )(GLuint list); +extern void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern void ( APIENTRY * qglClear )(GLbitfield mask); +extern void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( APIENTRY * qglClearDepth )(GLclampd depth); +extern void ( APIENTRY * qglClearIndex )(GLfloat c); +extern void ( APIENTRY * qglClearStencil )(GLint s); +extern void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +extern void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( APIENTRY * qglColor3bv )(const GLbyte *v); +extern void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( APIENTRY * qglColor3dv )(const GLdouble *v); +extern void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( APIENTRY * qglColor3fv )(const GLfloat *v); +extern void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( APIENTRY * qglColor3iv )(const GLint *v); +extern void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( APIENTRY * qglColor3sv )(const GLshort *v); +extern void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +extern void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( APIENTRY * qglColor3uiv )(const GLuint *v); +extern void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( APIENTRY * qglColor3usv )(const GLushort *v); +extern void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( APIENTRY * qglColor4bv )(const GLbyte *v); +extern void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( APIENTRY * qglColor4dv )(const GLdouble *v); +extern void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglColor4fv )(const GLfloat *v); +extern void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( APIENTRY * qglColor4iv )(const GLint *v); +extern void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( APIENTRY * qglColor4sv )(const GLshort *v); +extern void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +extern void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( APIENTRY * qglColor4uiv )(const GLuint *v); +extern void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( APIENTRY * qglColor4usv )(const GLushort *v); +extern void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglCullFace )(GLenum mode); +extern void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +extern void ( APIENTRY * qglDepthFunc )(GLenum func); +extern void ( APIENTRY * qglDepthMask )(GLboolean flag); +extern void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( APIENTRY * qglDisable )(GLenum cap); +extern void ( APIENTRY * qglDisableClientState )(GLenum array); +extern void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( APIENTRY * qglDrawBuffer )(GLenum mode); +extern void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +extern void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +extern void ( APIENTRY * qglEnable )(GLenum cap); +extern void ( APIENTRY * qglEnableClientState )(GLenum array); +extern void ( APIENTRY * qglEnd )(void); +extern void ( APIENTRY * qglEndList )(void); +extern void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +extern void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +extern void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +extern void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +extern void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +extern void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +extern void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( APIENTRY * qglEvalPoint1 )(GLint i); +extern void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +extern void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern void ( APIENTRY * qglFinish )(void); +extern void ( APIENTRY * qglFlush )(void); +extern void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +extern void ( APIENTRY * qglFrontFace )(GLenum mode); +extern void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( APIENTRY * qglGenLists )(GLsizei range); +extern void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +extern void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +extern void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +extern void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +extern GLenum ( APIENTRY * qglGetError )(void); +extern void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +extern void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +extern void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +extern void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +extern void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +extern void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +extern const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +extern void ( APIENTRY * qglIndexMask )(GLuint mask); +extern void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglIndexd )(GLdouble c); +extern void ( APIENTRY * qglIndexdv )(const GLdouble *c); +extern void ( APIENTRY * qglIndexf )(GLfloat c); +extern void ( APIENTRY * qglIndexfv )(const GLfloat *c); +extern void ( APIENTRY * qglIndexi )(GLint c); +extern void ( APIENTRY * qglIndexiv )(const GLint *c); +extern void ( APIENTRY * qglIndexs )(GLshort c); +extern void ( APIENTRY * qglIndexsv )(const GLshort *c); +extern void ( APIENTRY * qglIndexub )(GLubyte c); +extern void ( APIENTRY * qglIndexubv )(const GLubyte *c); +extern void ( APIENTRY * qglInitNames )(void); +extern void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +extern GLboolean ( APIENTRY * qglIsList )(GLuint list); +extern GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +extern void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +extern void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +extern void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( APIENTRY * qglLineWidth )(GLfloat width); +extern void ( APIENTRY * qglListBase )(GLuint base); +extern void ( APIENTRY * qglLoadIdentity )(void); +extern void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +extern void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +extern void ( APIENTRY * qglLoadName )(GLuint name); +extern void ( APIENTRY * qglLogicOp )(GLenum opcode); +extern void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglMatrixMode )(GLenum mode); +extern void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +extern void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +extern void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +extern void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +extern void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +extern void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +extern void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( APIENTRY * qglNormal3iv )(const GLint *v); +extern void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( APIENTRY * qglNormal3sv )(const GLshort *v); +extern void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( APIENTRY * qglPassThrough )(GLfloat token); +extern void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( APIENTRY * qglPointSize )(GLfloat size); +extern void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +extern void ( APIENTRY * qglPopAttrib )(void); +extern void ( APIENTRY * qglPopClientAttrib )(void); +extern void ( APIENTRY * qglPopMatrix )(void); +extern void ( APIENTRY * qglPopName )(void); +extern void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushMatrix )(void); +extern void ( APIENTRY * qglPushName )(GLuint name); +extern void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +extern void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +extern void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +extern void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +extern void ( APIENTRY * qglReadBuffer )(GLenum mode); +extern void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +extern void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +extern void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +extern void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +extern void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +extern GLint ( APIENTRY * qglRenderMode )(GLenum mode); +extern void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +extern void ( APIENTRY * qglShadeModel )(GLenum mode); +extern void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( APIENTRY * qglStencilMask )(GLuint mask); +extern void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( APIENTRY * qglTexCoord1d )(GLdouble s); +extern void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord1f )(GLfloat s); +extern void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord1i )(GLint s); +extern void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord1s )(GLshort s); +extern void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +extern void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +extern void ( APIENTRY * qglVertex2iv )(const GLint *v); +extern void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglVertex2sv )(const GLshort *v); +extern void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglVertex3iv )(const GLint *v); +extern void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglVertex3sv )(const GLshort *v); +extern void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglVertex4iv )(const GLint *v); +extern void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglVertex4sv )(const GLshort *v); +extern void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + + +extern int ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR *); +extern int ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +extern int ( WINAPI * qwglGetPixelFormat)(HDC); +extern BOOL ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +extern BOOL ( WINAPI * qwglSwapBuffers)(HDC); + +extern BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext)(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +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, int, LPGLYPHMETRICSFLOAT); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +extern int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF *); +extern int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF *); +extern BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + + + +#endif // hardlinlk vs dlopen + +#endif diff --git a/neo/renderer/OpenGL/qgl_linked.h b/neo/renderer/OpenGL/qgl_linked.h new file mode 100644 index 00000000..e6f64f67 --- /dev/null +++ b/neo/renderer/OpenGL/qgl_linked.h @@ -0,0 +1,373 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexEnviv glGetTexEnviv +#define qglGetTexEnvfv glGetTexEnvfv +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + +#ifdef GLX_VERSION_1_1 // catch all for any GLX-aware situation +#define qglXChooseVisual glXChooseVisual +#define qglXCreateContext glXCreateContext +#define qglXDestroyContext glXDestroyContext +#define qglXMakeCurrent glXMakeCurrent +#define qglXSwapBuffers glXSwapBuffers +#define qglXGetProcAddressARB glXGetProcAddressARB +#endif diff --git a/neo/renderer/OpenGL/wglext.h b/neo/renderer/OpenGL/wglext.h new file mode 100644 index 00000000..b5dc7bf7 --- /dev/null +++ b/neo/renderer/OpenGL/wglext.h @@ -0,0 +1,943 @@ +#ifndef __wglext_h_ +#define __wglext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2007-2012 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are 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 Materials. +** +** THE MATERIALS ARE 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 THE AUTHORS OR COPYRIGHT HOLDERS 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 +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Function declaration macros - to move into glplatform.h */ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number */ +/* wglext.h last updated 2012/01/04 */ +/* Current version at http://www.opengl.org/registry/ */ +#define WGL_WGLEXT_VERSION 24 + +#ifndef WGL_ARB_buffer_region +#define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 +#define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 +#define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 +#define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 +#endif + +#ifndef WGL_ARB_multisample +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif + +#ifndef WGL_ARB_extensions_string +#endif + +#ifndef WGL_ARB_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#endif + +#ifndef WGL_ARB_make_current_read +#define ERROR_INVALID_PIXEL_TYPE_ARB 0x2043 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +#endif + +#ifndef WGL_ARB_pbuffer +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E +#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 +#endif + +#ifndef WGL_ARB_render_texture +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_FRONT_RIGHT_ARB 0x2084 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_BACK_RIGHT_ARB 0x2086 +#define WGL_AUX0_ARB 0x2087 +#define WGL_AUX1_ARB 0x2088 +#define WGL_AUX2_ARB 0x2089 +#define WGL_AUX3_ARB 0x208A +#define WGL_AUX4_ARB 0x208B +#define WGL_AUX5_ARB 0x208C +#define WGL_AUX6_ARB 0x208D +#define WGL_AUX7_ARB 0x208E +#define WGL_AUX8_ARB 0x208F +#define WGL_AUX9_ARB 0x2090 +#endif + +#ifndef WGL_ARB_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 +#endif + +#ifndef WGL_ARB_framebuffer_sRGB +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 +#endif + +#ifndef WGL_ARB_create_context +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#endif + +#ifndef WGL_ARB_create_context_profile +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#endif + +#ifndef WGL_ARB_create_context_robustness +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261 +#endif + +#ifndef WGL_EXT_make_current_read +#define ERROR_INVALID_PIXEL_TYPE_EXT 0x2043 +#endif + +#ifndef WGL_EXT_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 +#define WGL_DRAW_TO_WINDOW_EXT 0x2001 +#define WGL_DRAW_TO_BITMAP_EXT 0x2002 +#define WGL_ACCELERATION_EXT 0x2003 +#define WGL_NEED_PALETTE_EXT 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 +#define WGL_SWAP_METHOD_EXT 0x2007 +#define WGL_NUMBER_OVERLAYS_EXT 0x2008 +#define WGL_NUMBER_UNDERLAYS_EXT 0x2009 +#define WGL_TRANSPARENT_EXT 0x200A +#define WGL_TRANSPARENT_VALUE_EXT 0x200B +#define WGL_SHARE_DEPTH_EXT 0x200C +#define WGL_SHARE_STENCIL_EXT 0x200D +#define WGL_SHARE_ACCUM_EXT 0x200E +#define WGL_SUPPORT_GDI_EXT 0x200F +#define WGL_SUPPORT_OPENGL_EXT 0x2010 +#define WGL_DOUBLE_BUFFER_EXT 0x2011 +#define WGL_STEREO_EXT 0x2012 +#define WGL_PIXEL_TYPE_EXT 0x2013 +#define WGL_COLOR_BITS_EXT 0x2014 +#define WGL_RED_BITS_EXT 0x2015 +#define WGL_RED_SHIFT_EXT 0x2016 +#define WGL_GREEN_BITS_EXT 0x2017 +#define WGL_GREEN_SHIFT_EXT 0x2018 +#define WGL_BLUE_BITS_EXT 0x2019 +#define WGL_BLUE_SHIFT_EXT 0x201A +#define WGL_ALPHA_BITS_EXT 0x201B +#define WGL_ALPHA_SHIFT_EXT 0x201C +#define WGL_ACCUM_BITS_EXT 0x201D +#define WGL_ACCUM_RED_BITS_EXT 0x201E +#define WGL_ACCUM_GREEN_BITS_EXT 0x201F +#define WGL_ACCUM_BLUE_BITS_EXT 0x2020 +#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 +#define WGL_DEPTH_BITS_EXT 0x2022 +#define WGL_STENCIL_BITS_EXT 0x2023 +#define WGL_AUX_BUFFERS_EXT 0x2024 +#define WGL_NO_ACCELERATION_EXT 0x2025 +#define WGL_GENERIC_ACCELERATION_EXT 0x2026 +#define WGL_FULL_ACCELERATION_EXT 0x2027 +#define WGL_SWAP_EXCHANGE_EXT 0x2028 +#define WGL_SWAP_COPY_EXT 0x2029 +#define WGL_SWAP_UNDEFINED_EXT 0x202A +#define WGL_TYPE_RGBA_EXT 0x202B +#define WGL_TYPE_COLORINDEX_EXT 0x202C +#endif + +#ifndef WGL_EXT_pbuffer +#define WGL_DRAW_TO_PBUFFER_EXT 0x202D +#define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E +#define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 +#define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 +#define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 +#define WGL_PBUFFER_LARGEST_EXT 0x2033 +#define WGL_PBUFFER_WIDTH_EXT 0x2034 +#define WGL_PBUFFER_HEIGHT_EXT 0x2035 +#endif + +#ifndef WGL_EXT_depth_float +#define WGL_DEPTH_FLOAT_EXT 0x2040 +#endif + +#ifndef WGL_3DFX_multisample +#define WGL_SAMPLE_BUFFERS_3DFX 0x2060 +#define WGL_SAMPLES_3DFX 0x2061 +#endif + +#ifndef WGL_EXT_multisample +#define WGL_SAMPLE_BUFFERS_EXT 0x2041 +#define WGL_SAMPLES_EXT 0x2042 +#endif + +#ifndef WGL_I3D_digital_video_control +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 +#define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 +#define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 +#endif + +#ifndef WGL_I3D_gamma +#define WGL_GAMMA_TABLE_SIZE_I3D 0x204E +#define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F +#endif + +#ifndef WGL_I3D_genlock +#define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 +#define WGL_GENLOCK_SOURCE_EXTENAL_SYNC_I3D 0x2045 +#define WGL_GENLOCK_SOURCE_EXTENAL_FIELD_I3D 0x2046 +#define WGL_GENLOCK_SOURCE_EXTENAL_TTL_I3D 0x2047 +#define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 +#define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 +#define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A +#define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B +#define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C +#endif + +#ifndef WGL_I3D_image_buffer +#define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 +#define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 +#endif + +#ifndef WGL_I3D_swap_frame_lock +#endif + +#ifndef WGL_NV_render_depth_texture +#define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 +#define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 +#define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 +#define WGL_DEPTH_COMPONENT_NV 0x20A7 +#endif + +#ifndef WGL_NV_render_texture_rectangle +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 +#endif + +#ifndef WGL_ATI_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ATI 0x21A0 +#endif + +#ifndef WGL_NV_float_buffer +#define WGL_FLOAT_COMPONENTS_NV 0x20B0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 +#define WGL_TEXTURE_FLOAT_R_NV 0x20B5 +#define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 +#define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 +#define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 +#endif + +#ifndef WGL_3DL_stereo_control +#define WGL_STEREO_EMITTER_ENABLE_3DL 0x2055 +#define WGL_STEREO_EMITTER_DISABLE_3DL 0x2056 +#define WGL_STEREO_POLARITY_NORMAL_3DL 0x2057 +#define WGL_STEREO_POLARITY_INVERT_3DL 0x2058 +#endif + +#ifndef WGL_EXT_pixel_format_packed_float +#define WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT 0x20A8 +#endif + +#ifndef WGL_EXT_framebuffer_sRGB +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20A9 +#endif + +#ifndef WGL_NV_present_video +#define WGL_NUM_VIDEO_SLOTS_NV 0x20F0 +#endif + +#ifndef WGL_NV_video_out +#define WGL_BIND_TO_VIDEO_RGB_NV 0x20C0 +#define WGL_BIND_TO_VIDEO_RGBA_NV 0x20C1 +#define WGL_BIND_TO_VIDEO_RGB_AND_DEPTH_NV 0x20C2 +#define WGL_VIDEO_OUT_COLOR_NV 0x20C3 +#define WGL_VIDEO_OUT_ALPHA_NV 0x20C4 +#define WGL_VIDEO_OUT_DEPTH_NV 0x20C5 +#define WGL_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 +#define WGL_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 +#define WGL_VIDEO_OUT_FRAME 0x20C8 +#define WGL_VIDEO_OUT_FIELD_1 0x20C9 +#define WGL_VIDEO_OUT_FIELD_2 0x20CA +#define WGL_VIDEO_OUT_STACKED_FIELDS_1_2 0x20CB +#define WGL_VIDEO_OUT_STACKED_FIELDS_2_1 0x20CC +#endif + +#ifndef WGL_NV_swap_group +#endif + +#ifndef WGL_NV_gpu_affinity +#define WGL_ERROR_INCOMPATIBLE_AFFINITY_MASKS_NV 0x20D0 +#define WGL_ERROR_MISSING_AFFINITY_MASK_NV 0x20D1 +#endif + +#ifndef WGL_AMD_gpu_association +#define WGL_GPU_VENDOR_AMD 0x1F00 +#define WGL_GPU_RENDERER_STRING_AMD 0x1F01 +#define WGL_GPU_OPENGL_VERSION_STRING_AMD 0x1F02 +#define WGL_GPU_FASTEST_TARGET_GPUS_AMD 0x21A2 +#define WGL_GPU_RAM_AMD 0x21A3 +#define WGL_GPU_CLOCK_AMD 0x21A4 +#define WGL_GPU_NUM_PIPES_AMD 0x21A5 +#define WGL_GPU_NUM_SIMD_AMD 0x21A6 +#define WGL_GPU_NUM_RB_AMD 0x21A7 +#define WGL_GPU_NUM_SPI_AMD 0x21A8 +#endif + +#ifndef WGL_NV_video_capture +#define WGL_UNIQUE_ID_NV 0x20CE +#define WGL_NUM_VIDEO_CAPTURE_SLOTS_NV 0x20CF +#endif + +#ifndef WGL_NV_copy_image +#endif + +#ifndef WGL_NV_multisample_coverage +#define WGL_COVERAGE_SAMPLES_NV 0x2042 +#define WGL_COLOR_SAMPLES_NV 0x20B9 +#endif + +#ifndef WGL_EXT_create_context_es2_profile +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 +#endif + +#ifndef WGL_NV_DX_interop +#define WGL_ACCESS_READ_ONLY_NV 0x00000000 +#define WGL_ACCESS_READ_WRITE_NV 0x00000001 +#define WGL_ACCESS_WRITE_DISCARD_NV 0x00000002 +#endif + +#ifndef WGL_NV_DX_interop2 +#endif + +#ifndef WGL_EXT_swap_control_tear +#endif + + +/*************************************************************/ + +#ifndef WGL_ARB_pbuffer +DECLARE_HANDLE(HPBUFFERARB); +#endif +#ifndef WGL_EXT_pbuffer +DECLARE_HANDLE(HPBUFFEREXT); +#endif +#ifndef WGL_NV_present_video +DECLARE_HANDLE(HVIDEOOUTPUTDEVICENV); +#endif +#ifndef WGL_NV_video_output +DECLARE_HANDLE(HPVIDEODEV); +#endif +#ifndef WGL_NV_gpu_affinity +DECLARE_HANDLE(HPGPUNV); +DECLARE_HANDLE(HGPUNV); + +typedef struct _GPU_DEVICE { + DWORD cb; + CHAR DeviceName[32]; + CHAR DeviceString[128]; + DWORD Flags; + RECT rcVirtualScreen; +} GPU_DEVICE, *PGPU_DEVICE; +#endif +#ifndef WGL_NV_video_capture +DECLARE_HANDLE(HVIDEOINPUTDEVICENV); +#endif + +#ifndef WGL_ARB_buffer_region +#define WGL_ARB_buffer_region 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HANDLE WINAPI wglCreateBufferRegionARB (HDC hDC, int iLayerPlane, UINT uType); +extern VOID WINAPI wglDeleteBufferRegionARB (HANDLE hRegion); +extern BOOL WINAPI wglSaveBufferRegionARB (HANDLE hRegion, int x, int y, int width, int height); +extern BOOL WINAPI wglRestoreBufferRegionARB (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HANDLE (WINAPI * PFNWGLCREATEBUFFERREGIONARBPROC) (HDC hDC, int iLayerPlane, UINT uType); +typedef VOID (WINAPI * PFNWGLDELETEBUFFERREGIONARBPROC) (HANDLE hRegion); +typedef BOOL (WINAPI * PFNWGLSAVEBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height); +typedef BOOL (WINAPI * PFNWGLRESTOREBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +#endif + +#ifndef WGL_ARB_multisample +#define WGL_ARB_multisample 1 +#endif + +#ifndef WGL_ARB_extensions_string +#define WGL_ARB_extensions_string 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern const char * WINAPI wglGetExtensionsStringARB (HDC hdc); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); +#endif + +#ifndef WGL_ARB_pixel_format +#define WGL_ARB_pixel_format 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetPixelFormatAttribivARB (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +extern BOOL WINAPI wglGetPixelFormatAttribfvARB (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +extern BOOL WINAPI wglChoosePixelFormatARB (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif + +#ifndef WGL_ARB_make_current_read +#define WGL_ARB_make_current_read 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglMakeContextCurrentARB (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +extern HDC WINAPI wglGetCurrentReadDCARB (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTARBPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCARBPROC) (void); +#endif + +#ifndef WGL_ARB_pbuffer +#define WGL_ARB_pbuffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HPBUFFERARB WINAPI wglCreatePbufferARB (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +extern HDC WINAPI wglGetPbufferDCARB (HPBUFFERARB hPbuffer); +extern int WINAPI wglReleasePbufferDCARB (HPBUFFERARB hPbuffer, HDC hDC); +extern BOOL WINAPI wglDestroyPbufferARB (HPBUFFERARB hPbuffer); +extern BOOL WINAPI wglQueryPbufferARB (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +#endif + +#ifndef WGL_ARB_render_texture +#define WGL_ARB_render_texture 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglBindTexImageARB (HPBUFFERARB hPbuffer, int iBuffer); +extern BOOL WINAPI wglReleaseTexImageARB (HPBUFFERARB hPbuffer, int iBuffer); +extern BOOL WINAPI wglSetPbufferAttribARB (HPBUFFERARB hPbuffer, const int *piAttribList); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int *piAttribList); +#endif + +#ifndef WGL_ARB_pixel_format_float +#define WGL_ARB_pixel_format_float 1 +#endif + +#ifndef WGL_ARB_framebuffer_sRGB +#define WGL_ARB_framebuffer_sRGB 1 +#endif + +#ifndef WGL_ARB_create_context +#define WGL_ARB_create_context 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HGLRC WINAPI wglCreateContextAttribsARB (HDC hDC, HGLRC hShareContext, const int *attribList); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int *attribList); +#endif + +#ifndef WGL_ARB_create_context_profile +#define WGL_ARB_create_context_profile 1 +#endif + +#ifndef WGL_ARB_create_context_robustness +#define WGL_ARB_create_context_robustness 1 +#endif + +#ifndef WGL_EXT_display_color_table +#define WGL_EXT_display_color_table 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern GLboolean WINAPI wglCreateDisplayColorTableEXT (GLushort id); +extern GLboolean WINAPI wglLoadDisplayColorTableEXT (const GLushort *table, GLuint length); +extern GLboolean WINAPI wglBindDisplayColorTableEXT (GLushort id); +extern VOID WINAPI wglDestroyDisplayColorTableEXT (GLushort id); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef GLboolean (WINAPI * PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef GLboolean (WINAPI * PFNWGLLOADDISPLAYCOLORTABLEEXTPROC) (const GLushort *table, GLuint length); +typedef GLboolean (WINAPI * PFNWGLBINDDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef VOID (WINAPI * PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC) (GLushort id); +#endif + +#ifndef WGL_EXT_extensions_string +#define WGL_EXT_extensions_string 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern const char * WINAPI wglGetExtensionsStringEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); +#endif + +#ifndef WGL_EXT_make_current_read +#define WGL_EXT_make_current_read 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglMakeContextCurrentEXT (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +extern HDC WINAPI wglGetCurrentReadDCEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTEXTPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCEXTPROC) (void); +#endif + +#ifndef WGL_EXT_pbuffer +#define WGL_EXT_pbuffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HPBUFFEREXT WINAPI wglCreatePbufferEXT (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +extern HDC WINAPI wglGetPbufferDCEXT (HPBUFFEREXT hPbuffer); +extern int WINAPI wglReleasePbufferDCEXT (HPBUFFEREXT hPbuffer, HDC hDC); +extern BOOL WINAPI wglDestroyPbufferEXT (HPBUFFEREXT hPbuffer); +extern BOOL WINAPI wglQueryPbufferEXT (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HPBUFFEREXT (WINAPI * PFNWGLCREATEPBUFFEREXTPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); +#endif + +#ifndef WGL_EXT_pixel_format +#define WGL_EXT_pixel_format 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetPixelFormatAttribivEXT (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); +extern BOOL WINAPI wglGetPixelFormatAttribfvEXT (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); +extern BOOL WINAPI wglChoosePixelFormatEXT (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATEXTPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif + +#ifndef WGL_EXT_swap_control +#define WGL_EXT_swap_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglSwapIntervalEXT (int interval); +extern int WINAPI wglGetSwapIntervalEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); +typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void); +#endif + +#ifndef WGL_EXT_depth_float +#define WGL_EXT_depth_float 1 +#endif + +#ifndef WGL_NV_vertex_array_range +#define WGL_NV_vertex_array_range 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern void* WINAPI wglAllocateMemoryNV (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); +extern void WINAPI wglFreeMemoryNV (void *pointer); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef void* (WINAPI * PFNWGLALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); +typedef void (WINAPI * PFNWGLFREEMEMORYNVPROC) (void *pointer); +#endif + +#ifndef WGL_3DFX_multisample +#define WGL_3DFX_multisample 1 +#endif + +#ifndef WGL_EXT_multisample +#define WGL_EXT_multisample 1 +#endif + +#ifndef WGL_OML_sync_control +#define WGL_OML_sync_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetSyncValuesOML (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); +extern BOOL WINAPI wglGetMscRateOML (HDC hdc, INT32 *numerator, INT32 *denominator); +extern INT64 WINAPI wglSwapBuffersMscOML (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +extern INT64 WINAPI wglSwapLayerBuffersMscOML (HDC hdc, int fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +extern BOOL WINAPI wglWaitForMscOML (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); +extern BOOL WINAPI wglWaitForSbcOML (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETSYNCVALUESOMLPROC) (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLGETMSCRATEOMLPROC) (HDC hdc, INT32 *numerator, INT32 *denominator); +typedef INT64 (WINAPI * PFNWGLSWAPBUFFERSMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef INT64 (WINAPI * PFNWGLSWAPLAYERBUFFERSMSCOMLPROC) (HDC hdc, int fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef BOOL (WINAPI * PFNWGLWAITFORMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLWAITFORSBCOMLPROC) (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); +#endif + +#ifndef WGL_I3D_digital_video_control +#define WGL_I3D_digital_video_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetDigitalVideoParametersI3D (HDC hDC, int iAttribute, int *piValue); +extern BOOL WINAPI wglSetDigitalVideoParametersI3D (HDC hDC, int iAttribute, const int *piValue); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +#endif + +#ifndef WGL_I3D_gamma +#define WGL_I3D_gamma 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetGammaTableParametersI3D (HDC hDC, int iAttribute, int *piValue); +extern BOOL WINAPI wglSetGammaTableParametersI3D (HDC hDC, int iAttribute, const int *piValue); +extern BOOL WINAPI wglGetGammaTableI3D (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); +extern BOOL WINAPI wglSetGammaTableI3D (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); +#endif + +#ifndef WGL_I3D_genlock +#define WGL_I3D_genlock 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnableGenlockI3D (HDC hDC); +extern BOOL WINAPI wglDisableGenlockI3D (HDC hDC); +extern BOOL WINAPI wglIsEnabledGenlockI3D (HDC hDC, BOOL *pFlag); +extern BOOL WINAPI wglGenlockSourceI3D (HDC hDC, UINT uSource); +extern BOOL WINAPI wglGetGenlockSourceI3D (HDC hDC, UINT *uSource); +extern BOOL WINAPI wglGenlockSourceEdgeI3D (HDC hDC, UINT uEdge); +extern BOOL WINAPI wglGetGenlockSourceEdgeI3D (HDC hDC, UINT *uEdge); +extern BOOL WINAPI wglGenlockSampleRateI3D (HDC hDC, UINT uRate); +extern BOOL WINAPI wglGetGenlockSampleRateI3D (HDC hDC, UINT *uRate); +extern BOOL WINAPI wglGenlockSourceDelayI3D (HDC hDC, UINT uDelay); +extern BOOL WINAPI wglGetGenlockSourceDelayI3D (HDC hDC, UINT *uDelay); +extern BOOL WINAPI wglQueryGenlockMaxSourceDelayI3D (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLDISABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLISENABLEDGENLOCKI3DPROC) (HDC hDC, BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEI3DPROC) (HDC hDC, UINT uSource); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEI3DPROC) (HDC hDC, UINT *uSource); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT uEdge); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT *uEdge); +typedef BOOL (WINAPI * PFNWGLGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT uRate); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT *uRate); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT uDelay); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT *uDelay); +typedef BOOL (WINAPI * PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC) (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); +#endif + +#ifndef WGL_I3D_image_buffer +#define WGL_I3D_image_buffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern LPVOID WINAPI wglCreateImageBufferI3D (HDC hDC, DWORD dwSize, UINT uFlags); +extern BOOL WINAPI wglDestroyImageBufferI3D (HDC hDC, LPVOID pAddress); +extern BOOL WINAPI wglAssociateImageBufferEventsI3D (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); +extern BOOL WINAPI wglReleaseImageBufferEventsI3D (HDC hDC, const LPVOID *pAddress, UINT count); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef LPVOID (WINAPI * PFNWGLCREATEIMAGEBUFFERI3DPROC) (HDC hDC, DWORD dwSize, UINT uFlags); +typedef BOOL (WINAPI * PFNWGLDESTROYIMAGEBUFFERI3DPROC) (HDC hDC, LPVOID pAddress); +typedef BOOL (WINAPI * PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); +typedef BOOL (WINAPI * PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const LPVOID *pAddress, UINT count); +#endif + +#ifndef WGL_I3D_swap_frame_lock +#define WGL_I3D_swap_frame_lock 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnableFrameLockI3D (void); +extern BOOL WINAPI wglDisableFrameLockI3D (void); +extern BOOL WINAPI wglIsEnabledFrameLockI3D (BOOL *pFlag); +extern BOOL WINAPI wglQueryFrameLockMasterI3D (BOOL *pFlag); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLDISABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLISENABLEDFRAMELOCKI3DPROC) (BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMELOCKMASTERI3DPROC) (BOOL *pFlag); +#endif + +#ifndef WGL_I3D_swap_frame_usage +#define WGL_I3D_swap_frame_usage 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetFrameUsageI3D (float *pUsage); +extern BOOL WINAPI wglBeginFrameTrackingI3D (void); +extern BOOL WINAPI wglEndFrameTrackingI3D (void); +extern BOOL WINAPI wglQueryFrameTrackingI3D (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETFRAMEUSAGEI3DPROC) (float *pUsage); +typedef BOOL (WINAPI * PFNWGLBEGINFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLENDFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMETRACKINGI3DPROC) (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); +#endif + +#ifndef WGL_ATI_pixel_format_float +#define WGL_ATI_pixel_format_float 1 +#endif + +#ifndef WGL_NV_float_buffer +#define WGL_NV_float_buffer 1 +#endif + +#ifndef WGL_3DL_stereo_control +#define WGL_3DL_stereo_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglSetStereoEmitterState3DL (HDC hDC, UINT uState); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLSETSTEREOEMITTERSTATE3DLPROC) (HDC hDC, UINT uState); +#endif + +#ifndef WGL_EXT_pixel_format_packed_float +#define WGL_EXT_pixel_format_packed_float 1 +#endif + +#ifndef WGL_EXT_framebuffer_sRGB +#define WGL_EXT_framebuffer_sRGB 1 +#endif + +#ifndef WGL_NV_present_video +#define WGL_NV_present_video 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern int WINAPI wglEnumerateVideoDevicesNV (HDC hDC, HVIDEOOUTPUTDEVICENV *phDeviceList); +extern BOOL WINAPI wglBindVideoDeviceNV (HDC hDC, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int *piAttribList); +extern BOOL WINAPI wglQueryCurrentContextNV (int iAttribute, int *piValue); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef int (WINAPI * PFNWGLENUMERATEVIDEODEVICESNVPROC) (HDC hDC, HVIDEOOUTPUTDEVICENV *phDeviceList); +typedef BOOL (WINAPI * PFNWGLBINDVIDEODEVICENVPROC) (HDC hDC, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int *piAttribList); +typedef BOOL (WINAPI * PFNWGLQUERYCURRENTCONTEXTNVPROC) (int iAttribute, int *piValue); +#endif + +#ifndef WGL_NV_video_output +#define WGL_NV_video_output 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetVideoDeviceNV (HDC hDC, int numDevices, HPVIDEODEV *hVideoDevice); +extern BOOL WINAPI wglReleaseVideoDeviceNV (HPVIDEODEV hVideoDevice); +extern BOOL WINAPI wglBindVideoImageNV (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); +extern BOOL WINAPI wglReleaseVideoImageNV (HPBUFFERARB hPbuffer, int iVideoBuffer); +extern BOOL WINAPI wglSendPbufferToVideoNV (HPBUFFERARB hPbuffer, int iBufferType, unsigned long *pulCounterPbuffer, BOOL bBlock); +extern BOOL WINAPI wglGetVideoInfoNV (HPVIDEODEV hpVideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETVIDEODEVICENVPROC) (HDC hDC, int numDevices, HPVIDEODEV *hVideoDevice); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEODEVICENVPROC) (HPVIDEODEV hVideoDevice); +typedef BOOL (WINAPI * PFNWGLBINDVIDEOIMAGENVPROC) (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOIMAGENVPROC) (HPBUFFERARB hPbuffer, int iVideoBuffer); +typedef BOOL (WINAPI * PFNWGLSENDPBUFFERTOVIDEONVPROC) (HPBUFFERARB hPbuffer, int iBufferType, unsigned long *pulCounterPbuffer, BOOL bBlock); +typedef BOOL (WINAPI * PFNWGLGETVIDEOINFONVPROC) (HPVIDEODEV hpVideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +#endif + +#ifndef WGL_NV_swap_group +#define WGL_NV_swap_group 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglJoinSwapGroupNV (HDC hDC, GLuint group); +extern BOOL WINAPI wglBindSwapBarrierNV (GLuint group, GLuint barrier); +extern BOOL WINAPI wglQuerySwapGroupNV (HDC hDC, GLuint *group, GLuint *barrier); +extern BOOL WINAPI wglQueryMaxSwapGroupsNV (HDC hDC, GLuint *maxGroups, GLuint *maxBarriers); +extern BOOL WINAPI wglQueryFrameCountNV (HDC hDC, GLuint *count); +extern BOOL WINAPI wglResetFrameCountNV (HDC hDC); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLJOINSWAPGROUPNVPROC) (HDC hDC, GLuint group); +typedef BOOL (WINAPI * PFNWGLBINDSWAPBARRIERNVPROC) (GLuint group, GLuint barrier); +typedef BOOL (WINAPI * PFNWGLQUERYSWAPGROUPNVPROC) (HDC hDC, GLuint *group, GLuint *barrier); +typedef BOOL (WINAPI * PFNWGLQUERYMAXSWAPGROUPSNVPROC) (HDC hDC, GLuint *maxGroups, GLuint *maxBarriers); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMECOUNTNVPROC) (HDC hDC, GLuint *count); +typedef BOOL (WINAPI * PFNWGLRESETFRAMECOUNTNVPROC) (HDC hDC); +#endif + +#ifndef WGL_NV_gpu_affinity +#define WGL_NV_gpu_affinity 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnumGpusNV (UINT iGpuIndex, HGPUNV *phGpu); +extern BOOL WINAPI wglEnumGpuDevicesNV (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); +extern HDC WINAPI wglCreateAffinityDCNV (const HGPUNV *phGpuList); +extern BOOL WINAPI wglEnumGpusFromAffinityDCNV (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); +extern BOOL WINAPI wglDeleteDCNV (HDC hdc); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENUMGPUSNVPROC) (UINT iGpuIndex, HGPUNV *phGpu); +typedef BOOL (WINAPI * PFNWGLENUMGPUDEVICESNVPROC) (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); +typedef HDC (WINAPI * PFNWGLCREATEAFFINITYDCNVPROC) (const HGPUNV *phGpuList); +typedef BOOL (WINAPI * PFNWGLENUMGPUSFROMAFFINITYDCNVPROC) (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); +typedef BOOL (WINAPI * PFNWGLDELETEDCNVPROC) (HDC hdc); +#endif + +#ifndef WGL_AMD_gpu_association +#define WGL_AMD_gpu_association 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern UINT WINAPI wglGetGPUIDsAMD (UINT maxCount, UINT *ids); +extern INT WINAPI wglGetGPUInfoAMD (UINT id, int property, GLenum dataType, UINT size, void *data); +extern UINT WINAPI wglGetContextGPUIDAMD (HGLRC hglrc); +extern HGLRC WINAPI wglCreateAssociatedContextAMD (UINT id); +extern HGLRC WINAPI wglCreateAssociatedContextAttribsAMD (UINT id, HGLRC hShareContext, const int *attribList); +extern BOOL WINAPI wglDeleteAssociatedContextAMD (HGLRC hglrc); +extern BOOL WINAPI wglMakeAssociatedContextCurrentAMD (HGLRC hglrc); +extern HGLRC WINAPI wglGetCurrentAssociatedContextAMD (void); +extern VOID WINAPI wglBlitContextFramebufferAMD (HGLRC dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef UINT (WINAPI * PFNWGLGETGPUIDSAMDPROC) (UINT maxCount, UINT *ids); +typedef INT (WINAPI * PFNWGLGETGPUINFOAMDPROC) (UINT id, int property, GLenum dataType, UINT size, void *data); +typedef UINT (WINAPI * PFNWGLGETCONTEXTGPUIDAMDPROC) (HGLRC hglrc); +typedef HGLRC (WINAPI * PFNWGLCREATEASSOCIATEDCONTEXTAMDPROC) (UINT id); +typedef HGLRC (WINAPI * PFNWGLCREATEASSOCIATEDCONTEXTATTRIBSAMDPROC) (UINT id, HGLRC hShareContext, const int *attribList); +typedef BOOL (WINAPI * PFNWGLDELETEASSOCIATEDCONTEXTAMDPROC) (HGLRC hglrc); +typedef BOOL (WINAPI * PFNWGLMAKEASSOCIATEDCONTEXTCURRENTAMDPROC) (HGLRC hglrc); +typedef HGLRC (WINAPI * PFNWGLGETCURRENTASSOCIATEDCONTEXTAMDPROC) (void); +typedef VOID (WINAPI * PFNWGLBLITCONTEXTFRAMEBUFFERAMDPROC) (HGLRC dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif + +#ifndef WGL_NV_video_capture +#define WGL_NV_video_capture 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglBindVideoCaptureDeviceNV (UINT uVideoSlot, HVIDEOINPUTDEVICENV hDevice); +extern UINT WINAPI wglEnumerateVideoCaptureDevicesNV (HDC hDc, HVIDEOINPUTDEVICENV *phDeviceList); +extern BOOL WINAPI wglLockVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +extern BOOL WINAPI wglQueryVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice, int iAttribute, int *piValue); +extern BOOL WINAPI wglReleaseVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLBINDVIDEOCAPTUREDEVICENVPROC) (UINT uVideoSlot, HVIDEOINPUTDEVICENV hDevice); +typedef UINT (WINAPI * PFNWGLENUMERATEVIDEOCAPTUREDEVICESNVPROC) (HDC hDc, HVIDEOINPUTDEVICENV *phDeviceList); +typedef BOOL (WINAPI * PFNWGLLOCKVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +typedef BOOL (WINAPI * PFNWGLQUERYVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +#endif + +#ifndef WGL_NV_copy_image +#define WGL_NV_copy_image 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglCopyImageSubDataNV (HGLRC hSrcRC, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, HGLRC hDstRC, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLCOPYIMAGESUBDATANVPROC) (HGLRC hSrcRC, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, HGLRC hDstRC, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif + +#ifndef WGL_NV_multisample_coverage +#define WGL_NV_multisample_coverage 1 +#endif + +#ifndef WGL_NV_DX_interop +#define WGL_NV_DX_interop 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglDXSetResourceShareHandleNV (void *dxObject, HANDLE shareHandle); +extern HANDLE WINAPI wglDXOpenDeviceNV (void *dxDevice); +extern BOOL WINAPI wglDXCloseDeviceNV (HANDLE hDevice); +extern HANDLE WINAPI wglDXRegisterObjectNV (HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); +extern BOOL WINAPI wglDXUnregisterObjectNV (HANDLE hDevice, HANDLE hObject); +extern BOOL WINAPI wglDXObjectAccessNV (HANDLE hObject, GLenum access); +extern BOOL WINAPI wglDXLockObjectsNV (HANDLE hDevice, GLint count, HANDLE *hObjects); +extern BOOL WINAPI wglDXUnlockObjectsNV (HANDLE hDevice, GLint count, HANDLE *hObjects); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLDXSETRESOURCESHAREHANDLENVPROC) (void *dxObject, HANDLE shareHandle); +typedef HANDLE (WINAPI * PFNWGLDXOPENDEVICENVPROC) (void *dxDevice); +typedef BOOL (WINAPI * PFNWGLDXCLOSEDEVICENVPROC) (HANDLE hDevice); +typedef HANDLE (WINAPI * PFNWGLDXREGISTEROBJECTNVPROC) (HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); +typedef BOOL (WINAPI * PFNWGLDXUNREGISTEROBJECTNVPROC) (HANDLE hDevice, HANDLE hObject); +typedef BOOL (WINAPI * PFNWGLDXOBJECTACCESSNVPROC) (HANDLE hObject, GLenum access); +typedef BOOL (WINAPI * PFNWGLDXLOCKOBJECTSNVPROC) (HANDLE hDevice, GLint count, HANDLE *hObjects); +typedef BOOL (WINAPI * PFNWGLDXUNLOCKOBJECTSNVPROC) (HANDLE hDevice, GLint count, HANDLE *hObjects); +#endif + +#ifndef WGL_NV_DX_interop2 +#define WGL_NV_DX_interop2 1 +#endif + +#ifndef WGL_EXT_swap_control_tear +#define WGL_EXT_swap_control_tear 1 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/neo/renderer/RenderContext.h b/neo/renderer/RenderContext.h new file mode 100644 index 00000000..d7cb2f1d --- /dev/null +++ b/neo/renderer/RenderContext.h @@ -0,0 +1,91 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __RENDERCONTEXT_H__ +#define __RENDERCONTEXT_H__ + +// This is for "official" HDMI 3D support with with the left eye above the right and a guard band in the middle +// Some displays which don't support this can still do stereo 3D by packing 2 eyes into a single (mono-sized) buffer +enum hdmi3DState_t { + HDMI3D_NOT_SUPPORTED, // The TV doesn't support it + HDMI3D_NOT_ENABLED, // The TV supports it, but the user disabled it + HDMI3D_NOT_ACTIVE, // The TV supports it, and the user enabled it, but it's not active + HDMI3D_ACTIVE +}; + + +//================================================================================================ +// class idRenderContext +//================================================================================================ +class idRenderContext { +public: + idRenderContext() : depthHackValue( 0.0f ), weaponDepthHackValue( 1.0f ) {} + virtual ~idRenderContext() {}; + + void InitContext(); + void Shutdown(); + void PrepareSwap(); + void SwapBuffers( bool forceVsync = false ); + void SetGamma( unsigned short red[256], unsigned short green[256], unsigned short blue[256] ); + + hdmi3DState_t GetHDMI3DState(); + void EnableHDMI3D( const bool enable ); + + // Back End Rendering + void ExecuteBackendCommands( const emptyCommand_t *cmds ); + void Clear( float r, float g, float b, float a ); + void InitGraphicsAPIWrapper(); + + // Debug Tools + void RenderDebugTools( drawSurf_t **drawSurfs, int numDrawSurfs ); + + void SetWrapperContext( const wrapperContext_t & context ); + void SetWrapperConfig( const wrapperConfig_t & config ); + + // Texture Resolves + void ResolveTargetColor( idImage* image ); + void ResolveTargetDepth( idImage* image ); + void ResolveTargetColor( idImage* image, int srcMinX, int srcMinY, int srcMaxX, int srcMaxY, int dstX, int dstY ); + void ResolveTargetDepth( idImage* image, int srcMinX, int srcMinY, int srcMaxX, int srcMaxY, int dstX, int dstY ); + + void SetDepthHackValue( float depth ) { depthHackValue = depth; } + float GetDepthHackValue() const { return depthHackValue; } + void SetWeaponDepthHackValue( float depth ) { weaponDepthHackValue = depth; } + float GetWeaponDepthHackValue() const { return weaponDepthHackValue; } + + uint64 GetGPUFrameMicroSec() const { return GPUFrameMicroSec; } + +private: + float depthHackValue; + float weaponDepthHackValue; + uint64 GPUFrameMicroSec; +}; + +extern idRenderContext rRenderContext; + +#endif // !__RENDERCONTEXT_H__ diff --git a/neo/renderer/RenderEntity.cpp b/neo/renderer/RenderEntity.cpp new file mode 100644 index 00000000..15800b45 --- /dev/null +++ b/neo/renderer/RenderEntity.cpp @@ -0,0 +1,114 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +idRenderEntityLocal::idRenderEntityLocal() { + memset( &parms, 0, sizeof( parms ) ); + memset( modelMatrix, 0, sizeof( modelMatrix ) ); + + world = NULL; + index = 0; + lastModifiedFrameNum = 0; + archived = false; + dynamicModel = NULL; + dynamicModelFrameCount = 0; + cachedDynamicModel = NULL; + localReferenceBounds = bounds_zero; + globalReferenceBounds = bounds_zero; + viewCount = 0; + viewEntity = NULL; + decals = NULL; + overlays = NULL; + entityRefs = NULL; + firstInteraction = NULL; + lastInteraction = NULL; + needsPortalSky = false; +} + +void idRenderEntityLocal::FreeRenderEntity() { +} + +void idRenderEntityLocal::UpdateRenderEntity( const renderEntity_t *re, bool forceUpdate ) { +} + +void idRenderEntityLocal::GetRenderEntity( renderEntity_t *re ) { +} + +void idRenderEntityLocal::ForceUpdate() { +} + +int idRenderEntityLocal::GetIndex() { + return index; +} + +void idRenderEntityLocal::ProjectOverlay( const idPlane localTextureAxis[2], const idMaterial *material ) { +} +void idRenderEntityLocal::RemoveDecals() { +} + +//====================================================================== + +idRenderLightLocal::idRenderLightLocal() { + memset( &parms, 0, sizeof( parms ) ); + memset( lightProject, 0, sizeof( lightProject ) ); + + lightHasMoved = false; + world = NULL; + index = 0; + areaNum = 0; + lastModifiedFrameNum = 0; + archived = false; + lightShader = NULL; + falloffImage = NULL; + globalLightOrigin = vec3_zero; + viewCount = 0; + viewLight = NULL; + references = NULL; + foggedPortals = NULL; + firstInteraction = NULL; + lastInteraction = NULL; + + baseLightProject.Zero(); + inverseBaseLightProject.Zero(); +} + +void idRenderLightLocal::FreeRenderLight() { +} +void idRenderLightLocal::UpdateRenderLight( const renderLight_t *re, bool forceUpdate ) { +} +void idRenderLightLocal::GetRenderLight( renderLight_t *re ) { +} +void idRenderLightLocal::ForceUpdate() { +} +int idRenderLightLocal::GetIndex() { + return index; +} diff --git a/neo/renderer/RenderLog.cpp b/neo/renderer/RenderLog.cpp new file mode 100644 index 00000000..b02ddae7 --- /dev/null +++ b/neo/renderer/RenderLog.cpp @@ -0,0 +1,452 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "tr_local.h" + +/* +================================================================================================ +Contains the RenderLog implementation. + +TODO: Emit statistics to the logfile at the end of views and frames. +================================================================================================ +*/ + +idCVar r_logLevel( "r_logLevel", "2", CVAR_INTEGER, "1 = blocks only, 2 = everything", 1, 2 ); + +static const int LOG_LEVEL_BLOCKS_ONLY = 1; +static const int LOG_LEVEL_EVERYTHING = 2; + +const char * renderLogMainBlockLabels[] = { + ASSERT_ENUM_STRING( MRB_NONE, 0 ), + ASSERT_ENUM_STRING( MRB_BEGIN_DRAWING_VIEW, 1 ), + ASSERT_ENUM_STRING( MRB_FILL_DEPTH_BUFFER, 2 ), + ASSERT_ENUM_STRING( MRB_DRAW_INTERACTIONS, 3 ), + ASSERT_ENUM_STRING( MRB_DRAW_SHADER_PASSES, 4 ), + ASSERT_ENUM_STRING( MRB_FOG_ALL_LIGHTS, 5 ), + ASSERT_ENUM_STRING( MRB_DRAW_SHADER_PASSES_POST, 6 ), + ASSERT_ENUM_STRING( MRB_DRAW_DEBUG_TOOLS, 7 ), + ASSERT_ENUM_STRING( MRB_CAPTURE_COLORBUFFER, 8 ), + ASSERT_ENUM_STRING( MRB_POSTPROCESS, 9 ), + ASSERT_ENUM_STRING( MRB_GPU_SYNC, 10 ), + ASSERT_ENUM_STRING( MRB_END_FRAME, 11 ), + ASSERT_ENUM_STRING( MRB_BINK_FRAME, 12 ), + ASSERT_ENUM_STRING( MRB_BINK_NEXT_FRAME, 13 ), + ASSERT_ENUM_STRING( MRB_TOTAL, 14 ), + ASSERT_ENUM_STRING( MRB_MAX, 15 ) +}; + +extern uint64 Sys_Microseconds(); +/* +================================================================================================ + +PIX events on all platforms + +================================================================================================ +*/ + + +/* +================================================ +pixEvent_t +================================================ +*/ +struct pixEvent_t { + char name[256]; + uint64 cpuTime; + uint64 gpuTime; +}; + +idCVar r_pix( "r_pix", "0", CVAR_INTEGER, "print GPU/CPU event timing" ); + +static const int MAX_PIX_EVENTS = 256; +// defer allocation of this until needed, so we don't waste lots of memory +pixEvent_t * pixEvents; // [MAX_PIX_EVENTS] +int numPixEvents; +int numPixLevels; +static GLuint timeQueryIds[MAX_PIX_EVENTS]; + +/* +======================== +PC_BeginNamedEvent + +FIXME: this is not thread safe on the PC +======================== +*/ +void PC_BeginNamedEvent( const char *szName, ... ) { +#if 0 + if ( !r_pix.GetBool() ) { + return; + } + if ( !pixEvents ) { + // lazy allocation to not waste memory + pixEvents = (pixEvent_t *)Mem_ClearedAlloc( sizeof( *pixEvents ) * MAX_PIX_EVENTS, TAG_CRAP ); + } + if ( numPixEvents >= MAX_PIX_EVENTS ) { + idLib::FatalError( "PC_BeginNamedEvent: event overflow" ); + } + if ( ++numPixLevels > 1 ) { + return; // only get top level timing information + } + if ( !qglGetQueryObjectui64vEXT ) { + return; + } + + GL_CheckErrors(); + if ( timeQueryIds[0] == 0 ) { + qglGenQueriesARB( MAX_PIX_EVENTS, timeQueryIds ); + } + qglFinish(); + qglBeginQueryARB( GL_TIME_ELAPSED_EXT, timeQueryIds[numPixEvents] ); + GL_CheckErrors(); + + pixEvent_t *ev = &pixEvents[numPixEvents++]; + strncpy( ev->name, szName, sizeof( ev->name ) - 1 ); + ev->cpuTime = Sys_Microseconds(); +#endif +} + +/* +======================== +PC_EndNamedEvent +======================== +*/ +void PC_EndNamedEvent() { +#if 0 + if ( !r_pix.GetBool() ) { + return; + } + if ( numPixLevels <= 0 ) { + idLib::FatalError( "PC_EndNamedEvent: level underflow" ); + } + if ( --numPixLevels > 0 ) { + // only do timing on top level events + return; + } + if ( !qglGetQueryObjectui64vEXT ) { + return; + } + + pixEvent_t *ev = &pixEvents[numPixEvents-1]; + ev->cpuTime = Sys_Microseconds() - ev->cpuTime; + + GL_CheckErrors(); + qglEndQueryARB( GL_TIME_ELAPSED_EXT ); + GL_CheckErrors(); +#endif +} + +/* +======================== +PC_EndFrame +======================== +*/ +void PC_EndFrame() { +#if 0 + if ( !r_pix.GetBool() ) { + return; + } + + int64 totalGPU = 0; + int64 totalCPU = 0; + + idLib::Printf( "----- GPU Events -----\n" ); + for ( int i = 0 ; i < numPixEvents ; i++ ) { + pixEvent_t *ev = &pixEvents[i]; + + int64 gpuTime = 0; + qglGetQueryObjectui64vEXT( timeQueryIds[i], GL_QUERY_RESULT, (GLuint64EXT *)&gpuTime ); + ev->gpuTime = gpuTime; + + idLib::Printf( "%2d: %1.2f (GPU) %1.3f (CPU) = %s\n", i, ev->gpuTime / 1000000.0f, ev->cpuTime / 1000.0f, ev->name ); + totalGPU += ev->gpuTime; + totalCPU += ev->cpuTime; + } + idLib::Printf( "%2d: %1.2f (GPU) %1.3f (CPU) = total\n", numPixEvents, totalGPU / 1000000.0f, totalCPU / 1000.0f ); + memset( pixEvents, 0, numPixLevels * sizeof( pixEvents[0] ) ); + + numPixEvents = 0; + numPixLevels = 0; +#endif +} + + +/* +================================================================================================ + +idRenderLog + +================================================================================================ +*/ + +idRenderLog renderLog; + +#if !defined( STUB_RENDER_LOG ) + +/* +======================== +idRenderLog::idRenderLog +======================== +*/ +idRenderLog::idRenderLog() { + activeLevel = 0; + indentString[0] = '\0'; + indentLevel = 0; + logFile = NULL; + + frameStartTime = 0; + closeBlockTime = 0; + logLevel = 0; +} + +/* +======================== +idRenderLog::StartFrame +======================== +*/ +void idRenderLog::StartFrame() { + if ( r_logFile.GetInteger() == 0 ) { + return; + } + + // open a new logfile + indentLevel = 0; + indentString[0] = '\0'; + activeLevel = r_logLevel.GetInteger(); + + struct tm *newtime; + time_t aclock; + + char ospath[ MAX_OSPATH ]; + + char qpath[128]; + sprintf( qpath, "renderlogPC_%04i.txt", r_logFile.GetInteger() ); + idStr finalPath = fileSystem->RelativePathToOSPath( qpath ); + sprintf( ospath, "%s", finalPath.c_str() ); + /* + for ( int i = 0; i < 9999 ; i++ ) { + char qpath[128]; + sprintf( qpath, "renderlog_%04i.txt", r_logFile.GetInteger() ); + idStr finalPath = fileSystem->RelativePathToOSPath( qpath ); + fileSystem->RelativePathToOSPath( qpath, ospath, MAX_OSPATH ,FSPATH_BASE ); + if ( !fileSystem->FileExists( finalPath.c_str() ) ) { + break; // use this name + } + } + */ + + common->SetRefreshOnPrint( false ); // problems are caused if this print causes a refresh... + + if ( logFile != NULL ) { + fileSystem->CloseFile( logFile ); + logFile = NULL; + } + + logFile = fileSystem->OpenFileWrite( ospath ); + if ( logFile == NULL ) { + idLib::Warning( "Failed to open logfile %s", ospath ); + return; + } + idLib::Printf( "Opened logfile %s\n", ospath ); + + // write the time out to the top of the file + time( &aclock ); + newtime = localtime( &aclock ); + const char *str = asctime( newtime ); + logFile->Printf( "// %s", str ); + logFile->Printf( "// %s\n\n", com_version.GetString() ); + + frameStartTime = Sys_Microseconds(); + closeBlockTime = frameStartTime; + OpenBlock( "Frame" ); +} + +/* +======================== +idRenderLog::EndFrame +======================== +*/ +void idRenderLog::EndFrame() { + PC_EndFrame(); + + if ( logFile != NULL ) { + if ( r_logFile.GetInteger() == 1 ) { + Close(); + } + // log is open, so decrement r_logFile and stop if it is zero + r_logFile.SetInteger( r_logFile.GetInteger() - 1 ); + idLib::Printf( "Frame logged.\n" ); + return; + } +} + +/* +======================== +idRenderLog::Close +======================== +*/ +void idRenderLog::Close() { + if ( logFile != NULL ) { + CloseBlock(); + idLib::Printf( "Closing logfile\n" ); + fileSystem->CloseFile( logFile ); + logFile = NULL; + activeLevel = 0; + } +} + +/* +======================== +idRenderLog::OpenMainBlock +======================== +*/ +void idRenderLog::OpenMainBlock( renderLogMainBlock_t block ) { +} + +/* +======================== +idRenderLog::CloseMainBlock +======================== +*/ +void idRenderLog::CloseMainBlock() { +} + +/* +======================== +idRenderLog::OpenBlock +======================== +*/ +void idRenderLog::OpenBlock( const char *label ) { + // Allow the PIX functionality even when logFile is not running. + PC_BeginNamedEvent( label ); + + if ( logFile != NULL ) { + LogOpenBlock( RENDER_LOG_INDENT_MAIN_BLOCK, label, NULL ); + } +} + +/* +======================== +idRenderLog::CloseBlock +======================== +*/ +void idRenderLog::CloseBlock() { + PC_EndNamedEvent(); + + if ( logFile != NULL ) { + LogCloseBlock( RENDER_LOG_INDENT_MAIN_BLOCK ); + } +} + +/* +======================== +idRenderLog::Printf +======================== +*/ +void idRenderLog::Printf( const char *fmt, ... ) { + if ( activeLevel <= LOG_LEVEL_BLOCKS_ONLY ) { + return; + } + + if ( logFile == NULL ) { + return; + } + + va_list marker; + logFile->Printf( "%s", indentString ); + va_start( marker, fmt ); + logFile->VPrintf( fmt, marker ); + va_end( marker ); +// logFile->Flush(); this makes it take waaaay too long +} + +/* +======================== +idRenderLog::LogOpenBlock +======================== +*/ +void idRenderLog::LogOpenBlock( renderLogIndentLabel_t label, const char * fmt, va_list args ) { + + uint64 now = Sys_Microseconds(); + + if ( logFile != NULL ) { + if ( now - closeBlockTime >= 1000 ) { + logFile->Printf( "%s%1.1f msec gap from last closeblock\n", indentString, ( now - closeBlockTime ) * ( 1.0f / 1000.0f ) ); + } + logFile->Printf( "%s", indentString ); + logFile->VPrintf( fmt, args ); + logFile->Printf( " {\n" ); + } + + Indent( label ); + + if ( logLevel >= MAX_LOG_LEVELS ) { + idLib::Warning( "logLevel %d >= MAX_LOG_LEVELS", logLevel ); + } + + + logLevel++; +} + +/* +======================== +idRenderLog::LogCloseBlock +======================== +*/ +void idRenderLog::LogCloseBlock( renderLogIndentLabel_t label ) { + closeBlockTime = Sys_Microseconds(); + + assert( logLevel > 0 ); + logLevel--; + + Outdent( label ); + + if ( logFile != NULL ) { + } +} + +#else // !STUB_RENDER_LOG + +/* +======================== +idRenderLog::OpenBlock +======================== +*/ +void idRenderLog::OpenBlock( const char *label ) { + PC_BeginNamedEvent( label ); +} + +/* +======================== +idRenderLog::CloseBlock +======================== +*/ +void idRenderLog::CloseBlock() { + PC_EndNamedEvent(); +} + +#endif // !STUB_RENDER_LOG diff --git a/neo/renderer/RenderLog.h b/neo/renderer/RenderLog.h new file mode 100644 index 00000000..51b84e85 --- /dev/null +++ b/neo/renderer/RenderLog.h @@ -0,0 +1,187 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __RENDERLOG_H__ +#define __RENDERLOG_H__ + +/* +================================================================================================ +Contains the RenderLog declaration. +================================================================================================ +*/ + +#if defined(ID_RETAIL) && !defined(ID_RETAIL_INTERNAL) +#define STUB_RENDER_LOG +#endif + +enum renderLogMainBlock_t { + MRB_NONE, + MRB_BEGIN_DRAWING_VIEW, + MRB_FILL_DEPTH_BUFFER, + MRB_DRAW_INTERACTIONS, + MRB_DRAW_SHADER_PASSES, + MRB_FOG_ALL_LIGHTS, + MRB_DRAW_SHADER_PASSES_POST, + MRB_DRAW_DEBUG_TOOLS, + MRB_CAPTURE_COLORBUFFER, + MRB_POSTPROCESS, + MRB_GPU_SYNC, + MRB_END_FRAME, + MRB_BINK_FRAME, + MRB_BINK_NEXT_FRAME, + MRB_TOTAL, + MRB_MAX +}; + +// these are used to make sure each Indent() is properly paired with an Outdent() +enum renderLogIndentLabel_t { + RENDER_LOG_INDENT_DEFAULT, + RENDER_LOG_INDENT_MAIN_BLOCK, + RENDER_LOG_INDENT_BLOCK, + RENDER_LOG_INDENT_TEST +}; + +// using this macro avoids printf parameter overhead if the renderlog isn't active +#define RENDERLOG_PRINTF( ... ) if ( renderLog.activeLevel ) renderLog.Printf( __VA_ARGS__ ); + +#if !defined( STUB_RENDER_LOG ) + +/* +================================================ +idRenderLog contains block-based performance-tuning information. It combines +logfile, and msec accumulation code. +================================================ +*/ +class idRenderLog { +public: + idRenderLog(); + + void StartFrame(); + void EndFrame(); + void Close(); + int Active() { return activeLevel; } // returns greater than 1 for more detailed logging + + // The label must be a constant string literal and may not point to a temporary. + void OpenMainBlock( renderLogMainBlock_t block ); + void CloseMainBlock(); + + void OpenBlock( const char * label ); + void CloseBlock(); + + void Indent( renderLogIndentLabel_t label = RENDER_LOG_INDENT_DEFAULT ); + void Outdent( renderLogIndentLabel_t label = RENDER_LOG_INDENT_DEFAULT ); + + void Printf( VERIFY_FORMAT_STRING const char *fmt, ... ); + + static const int MAX_LOG_LEVELS = 20; + + int activeLevel; + renderLogIndentLabel_t indentLabel[MAX_LOG_LEVELS]; + char indentString[MAX_LOG_LEVELS * 4]; + int indentLevel; + const char * lastLabel; + renderLogMainBlock_t lastMainBlock; + idFile* logFile; + + struct logStats_t { + uint64 startTiming; + int startDraws; + int startIndexes; + }; + + uint64 frameStartTime; + uint64 closeBlockTime; + logStats_t logStats[MAX_LOG_LEVELS]; + int logLevel; + + void LogOpenBlock( renderLogIndentLabel_t label, const char * fmt, va_list args ); + void LogCloseBlock( renderLogIndentLabel_t label ); +}; + +/* +======================== +idRenderLog::Indent +======================== +*/ +ID_INLINE void idRenderLog::Indent( renderLogIndentLabel_t label ) { + if ( logFile != NULL ) { + indentLabel[indentLevel] = label; + indentLevel++; + for ( int i = 4; i > 0; i-- ) { + indentString[indentLevel * 4 - i] = ' '; + } + indentString[indentLevel * 4] = '\0'; + } +} + +/* +======================== +idRenderLog::Outdent +======================== +*/ +ID_INLINE void idRenderLog::Outdent( renderLogIndentLabel_t label ) { + if ( logFile != NULL && indentLevel > 0 ) { + indentLevel--; + assert( indentLabel[indentLevel] == label ); // indent and outdent out of sync ? + indentString[indentLevel * 4] = '\0'; + } +} + +#else // !STUB_RENDER_LOG + +/* +================================================ +idRenderLog stubbed version for the SPUs and high +performance rendering in retail builds. +================================================ +*/ +class idRenderLog { +public: + idRenderLog() {} + + void StartFrame() {} + void EndFrame() {} + void Close() {} + int Active() { return 0; } + + void OpenBlock( const char * label ); + void CloseBlock(); + void OpenMainBlock( renderLogMainBlock_t block ){} + void CloseMainBlock(){} + void Indent( renderLogIndentLabel_t label = RENDER_LOG_INDENT_DEFAULT ) {} + void Outdent( renderLogIndentLabel_t label = RENDER_LOG_INDENT_DEFAULT ) {} + + void Printf( VERIFY_FORMAT_STRING const char *fmt, ... ) {} + + int activeLevel; +}; + +#endif // !STUB_RENDER_LOG + +extern idRenderLog renderLog; + +#endif // !__RENDERLOG_H__ diff --git a/neo/renderer/RenderProgs.cpp b/neo/renderer/RenderProgs.cpp new file mode 100644 index 00000000..8af11a39 --- /dev/null +++ b/neo/renderer/RenderProgs.cpp @@ -0,0 +1,411 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + + + +idRenderProgManager renderProgManager; + +/* +================================================================================================ +idRenderProgManager::idRenderProgManager() +================================================================================================ +*/ +idRenderProgManager::idRenderProgManager() { +} + +/* +================================================================================================ +idRenderProgManager::~idRenderProgManager() +================================================================================================ +*/ +idRenderProgManager::~idRenderProgManager() { +} + +/* +================================================================================================ +R_ReloadShaders +================================================================================================ +*/ +static void R_ReloadShaders( const idCmdArgs &args ) { + renderProgManager.KillAllShaders(); + renderProgManager.LoadAllShaders(); +} + +/* +================================================================================================ +idRenderProgManager::Init() +================================================================================================ +*/ +void idRenderProgManager::Init() { + common->Printf( "----- Initializing Render Shaders -----\n" ); + + + for ( int i = 0; i < MAX_BUILTINS; i++ ) { + builtinShaders[i] = -1; + } + struct builtinShaders_t { + int index; + const char * name; + } builtins[] = { + { BUILTIN_GUI, "gui.vfp" }, + { BUILTIN_COLOR, "color.vfp" }, + { BUILTIN_SIMPLESHADE, "simpleshade.vfp" }, + { BUILTIN_TEXTURED, "texture.vfp" }, + { BUILTIN_TEXTURE_VERTEXCOLOR, "texture_color.vfp" }, + { BUILTIN_TEXTURE_VERTEXCOLOR_SKINNED, "texture_color_skinned.vfp" }, + { BUILTIN_TEXTURE_TEXGEN_VERTEXCOLOR, "texture_color_texgen.vfp" }, + { BUILTIN_INTERACTION, "interaction.vfp" }, + { BUILTIN_INTERACTION_SKINNED, "interaction_skinned.vfp" }, + { BUILTIN_INTERACTION_AMBIENT, "interactionAmbient.vfp" }, + { BUILTIN_INTERACTION_AMBIENT_SKINNED, "interactionAmbient_skinned.vfp" }, + { BUILTIN_ENVIRONMENT, "environment.vfp" }, + { BUILTIN_ENVIRONMENT_SKINNED, "environment_skinned.vfp" }, + { BUILTIN_BUMPY_ENVIRONMENT, "bumpyEnvironment.vfp" }, + { BUILTIN_BUMPY_ENVIRONMENT_SKINNED, "bumpyEnvironment_skinned.vfp" }, + + { BUILTIN_DEPTH, "depth.vfp" }, + { BUILTIN_DEPTH_SKINNED, "depth_skinned.vfp" }, + { BUILTIN_SHADOW_DEBUG, "shadowDebug.vfp" }, + { BUILTIN_SHADOW_DEBUG_SKINNED, "shadowDebug_skinned.vfp" }, + + { BUILTIN_BLENDLIGHT, "blendlight.vfp" }, + { BUILTIN_FOG, "fog.vfp" }, + { BUILTIN_FOG_SKINNED, "fog_skinned.vfp" }, + { BUILTIN_SKYBOX, "skybox.vfp" }, + { BUILTIN_WOBBLESKY, "wobblesky.vfp" }, + { BUILTIN_POSTPROCESS, "postprocess.vfp" }, + { BUILTIN_STEREO_DEGHOST, "stereoDeGhost.vfp" }, + { BUILTIN_STEREO_WARP, "stereoWarp.vfp" }, + { BUILTIN_ZCULL_RECONSTRUCT, "zcullReconstruct.vfp" }, + { BUILTIN_BINK, "bink.vfp" }, + { BUILTIN_BINK_GUI, "bink_gui.vfp" }, + { BUILTIN_STEREO_INTERLACE, "stereoInterlace.vfp" }, + { BUILTIN_MOTION_BLUR, "motionBlur.vfp" }, + }; + int numBuiltins = sizeof( builtins ) / sizeof( builtins[0] ); + vertexShaders.SetNum( numBuiltins ); + fragmentShaders.SetNum( numBuiltins ); + glslPrograms.SetNum( numBuiltins ); + + for ( int i = 0; i < numBuiltins; i++ ) { + vertexShaders[i].name = builtins[i].name; + fragmentShaders[i].name = builtins[i].name; + builtinShaders[builtins[i].index] = i; + LoadVertexShader( i ); + LoadFragmentShader( i ); + LoadGLSLProgram( i, i, i ); + } + + // Special case handling for fastZ shaders + builtinShaders[BUILTIN_SHADOW] = FindVertexShader( "shadow.vp" ); + builtinShaders[BUILTIN_SHADOW_SKINNED] = FindVertexShader( "shadow_skinned.vp" ); + + FindGLSLProgram( "shadow.vp", builtinShaders[BUILTIN_SHADOW], -1 ); + FindGLSLProgram( "shadow_skinned.vp", builtinShaders[BUILTIN_SHADOW_SKINNED], -1 ); + + glslUniforms.SetNum( RENDERPARM_USER + MAX_GLSL_USER_PARMS, vec4_zero ); + + vertexShaders[builtinShaders[BUILTIN_TEXTURE_VERTEXCOLOR_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_INTERACTION_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_INTERACTION_AMBIENT_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_ENVIRONMENT_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_BUMPY_ENVIRONMENT_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_DEPTH_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_SHADOW_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_SHADOW_DEBUG_SKINNED]].usesJoints = true; + vertexShaders[builtinShaders[BUILTIN_FOG_SKINNED]].usesJoints = true; + + cmdSystem->AddCommand( "reloadShaders", R_ReloadShaders, CMD_FL_RENDERER, "reloads shaders" ); +} + +/* +================================================================================================ +idRenderProgManager::LoadAllShaders() +================================================================================================ +*/ +void idRenderProgManager::LoadAllShaders() { + for ( int i = 0; i < vertexShaders.Num(); i++ ) { + LoadVertexShader( i ); + } + for ( int i = 0; i < fragmentShaders.Num(); i++ ) { + LoadFragmentShader( i ); + } + + for ( int i = 0; i < glslPrograms.Num(); ++i ) { + LoadGLSLProgram( i, glslPrograms[i].vertexShaderIndex, glslPrograms[i].fragmentShaderIndex ); + } +} + +/* +================================================================================================ +idRenderProgManager::KillAllShaders() +================================================================================================ +*/ +void idRenderProgManager::KillAllShaders() { + Unbind(); + for ( int i = 0; i < vertexShaders.Num(); i++ ) { + if ( vertexShaders[i].progId != INVALID_PROGID ) { + qglDeleteShader( vertexShaders[i].progId ); + vertexShaders[i].progId = INVALID_PROGID; + } + } + for ( int i = 0; i < fragmentShaders.Num(); i++ ) { + if ( fragmentShaders[i].progId != INVALID_PROGID ) { + qglDeleteShader( fragmentShaders[i].progId ); + fragmentShaders[i].progId = INVALID_PROGID; + } + } + for ( int i = 0; i < glslPrograms.Num(); ++i ) { + if ( glslPrograms[i].progId != INVALID_PROGID ) { + qglDeleteProgram( glslPrograms[i].progId ); + glslPrograms[i].progId = INVALID_PROGID; + } + } +} + +/* +================================================================================================ +idRenderProgManager::Shutdown() +================================================================================================ +*/ +void idRenderProgManager::Shutdown() { + KillAllShaders(); +} + +/* +================================================================================================ +idRenderProgManager::FindVertexShader +================================================================================================ +*/ +int idRenderProgManager::FindVertexShader( const char * name ) { + for ( int i = 0; i < vertexShaders.Num(); i++ ) { + if ( vertexShaders[i].name.Icmp( name ) == 0 ) { + LoadVertexShader( i ); + return i; + } + } + vertexShader_t shader; + shader.name = name; + int index = vertexShaders.Append( shader ); + LoadVertexShader( index ); + currentVertexShader = index; + + // FIXME: we should really scan the program source code for using rpEnableSkinning but at this + // point we directly load a binary and the program source code is not available on the consoles + if ( idStr::Icmp( name, "heatHaze.vfp" ) == 0 || + idStr::Icmp( name, "heatHazeWithMask.vfp" ) == 0 || + idStr::Icmp( name, "heatHazeWithMaskAndVertex.vfp" ) == 0 ) { + vertexShaders[index].usesJoints = true; + vertexShaders[index].optionalSkinning = true; + } + + return index; +} + +/* +================================================================================================ +idRenderProgManager::FindFragmentShader +================================================================================================ +*/ +int idRenderProgManager::FindFragmentShader( const char * name ) { + for ( int i = 0; i < fragmentShaders.Num(); i++ ) { + if ( fragmentShaders[i].name.Icmp( name ) == 0 ) { + LoadFragmentShader( i ); + return i; + } + } + fragmentShader_t shader; + shader.name = name; + int index = fragmentShaders.Append( shader ); + LoadFragmentShader( index ); + currentFragmentShader = index; + return index; +} + + + + +/* +================================================================================================ +idRenderProgManager::LoadVertexShader +================================================================================================ +*/ +void idRenderProgManager::LoadVertexShader( int index ) { + if ( vertexShaders[index].progId != INVALID_PROGID ) { + return; // Already loaded + } + vertexShaders[index].progId = ( GLuint ) LoadGLSLShader( GL_VERTEX_SHADER, vertexShaders[index].name, vertexShaders[index].uniforms ); +} + +/* +================================================================================================ +idRenderProgManager::LoadFragmentShader +================================================================================================ +*/ +void idRenderProgManager::LoadFragmentShader( int index ) { + if ( fragmentShaders[index].progId != INVALID_PROGID ) { + return; // Already loaded + } + fragmentShaders[index].progId = ( GLuint ) LoadGLSLShader( GL_FRAGMENT_SHADER, fragmentShaders[index].name, fragmentShaders[index].uniforms ); +} + +/* +================================================================================================ +idRenderProgManager::LoadShader +================================================================================================ +*/ +GLuint idRenderProgManager::LoadShader( GLenum target, const char * name, const char * startToken ) { + + idStr fullPath = "renderprogs\\gl\\"; + fullPath += name; + + common->Printf( "%s", fullPath.c_str() ); + + char * fileBuffer = NULL; + fileSystem->ReadFile( fullPath.c_str(), (void **)&fileBuffer, NULL ); + if ( fileBuffer == NULL ) { + common->Printf( ": File not found\n" ); + return INVALID_PROGID; + } + if ( !R_IsInitialized() ) { + common->Printf( ": Renderer not initialized\n" ); + fileSystem->FreeFile( fileBuffer ); + return INVALID_PROGID; + } + + // vertex and fragment shaders are both be present in a single file, so + // scan for the proper header to be the start point, and stamp a 0 in after the end + char * start = strstr( (char *)fileBuffer, startToken ); + if ( start == NULL ) { + common->Printf( ": %s not found\n", startToken ); + fileSystem->FreeFile( fileBuffer ); + return INVALID_PROGID; + } + char * end = strstr( start, "END" ); + if ( end == NULL ) { + common->Printf( ": END not found for %s\n", startToken ); + fileSystem->FreeFile( fileBuffer ); + return INVALID_PROGID; + } + end[3] = 0; + + idStr program = start; + program.Replace( "vertex.normal", "vertex.attrib[11]" ); + program.Replace( "vertex.texcoord[0]", "vertex.attrib[8]" ); + program.Replace( "vertex.texcoord", "vertex.attrib[8]" ); + + GLuint progId; + qglGenProgramsARB( 1, &progId ); + + qglBindProgramARB( target, progId ); + qglGetError(); + + qglProgramStringARB( target, GL_PROGRAM_FORMAT_ASCII_ARB, program.Length(), program.c_str() ); + GLenum err = qglGetError(); + + GLint ofs = -1; + qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &ofs ); + if ( ( err == GL_INVALID_OPERATION ) || ( ofs != -1 ) ) { + if ( err == GL_INVALID_OPERATION ) { + const GLubyte * str = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); + common->Printf( "\nGL_PROGRAM_ERROR_STRING_ARB: %s\n", str ); + } else { + common->Printf( "\nUNKNOWN ERROR\n" ); + } + if ( ofs < 0 ) { + common->Printf( "GL_PROGRAM_ERROR_POSITION_ARB < 0\n" ); + } else if ( ofs >= program.Length() ) { + common->Printf( "error at end of shader\n" ); + } else { + common->Printf( "error at %i:\n%s", ofs, program.c_str() + ofs ); + } + qglDeleteProgramsARB( 1, &progId ); + fileSystem->FreeFile( fileBuffer ); + return INVALID_PROGID; + } + common->Printf( "\n" ); + fileSystem->FreeFile( fileBuffer ); + return progId; +} + +/* +================================================================================================ +idRenderProgManager::BindShader +================================================================================================ +*/ +void idRenderProgManager::BindShader( int vIndex, int fIndex ) { + if ( currentVertexShader == vIndex && currentFragmentShader == fIndex ) { + return; + } + currentVertexShader = vIndex; + currentFragmentShader = fIndex; + // vIndex denotes the GLSL program + if ( vIndex >= 0 && vIndex < glslPrograms.Num() ) { + currentRenderProgram = vIndex; + RENDERLOG_PRINTF( "Binding GLSL Program %s\n", glslPrograms[vIndex].name.c_str() ); + qglUseProgram( glslPrograms[vIndex].progId ); + } +} + +/* +================================================================================================ +idRenderProgManager::Unbind +================================================================================================ +*/ +void idRenderProgManager::Unbind() { + currentVertexShader = -1; + currentFragmentShader = -1; + + qglUseProgram( 0 ); +} + +/* +================================================================================================ +idRenderProgManager::SetRenderParms +================================================================================================ +*/ +void idRenderProgManager::SetRenderParms( renderParm_t rp, const float * value, int num ) { + for ( int i = 0; i < num; i++ ) { + SetRenderParm( (renderParm_t)(rp + i), value + ( i * 4 ) ); + } +} + +/* +================================================================================================ +idRenderProgManager::SetRenderParm +================================================================================================ +*/ +void idRenderProgManager::SetRenderParm( renderParm_t rp, const float * value ) { + SetUniformValue( rp, value ); +} + diff --git a/neo/renderer/RenderProgs.h b/neo/renderer/RenderProgs.h new file mode 100644 index 00000000..212bea98 --- /dev/null +++ b/neo/renderer/RenderProgs.h @@ -0,0 +1,302 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __RENDERPROGS_H__ +#define __RENDERPROGS_H__ + + +static const int PC_ATTRIB_INDEX_VERTEX = 0; +static const int PC_ATTRIB_INDEX_NORMAL = 2; +static const int PC_ATTRIB_INDEX_COLOR = 3; +static const int PC_ATTRIB_INDEX_COLOR2 = 4; +static const int PC_ATTRIB_INDEX_ST = 8; +static const int PC_ATTRIB_INDEX_TANGENT = 9; + +// This enum list corresponds to the global constant register indecies as defined in global.inc for all +// shaders. We used a shared pool to keeps things simple. If something changes here then it also +// needs to change in global.inc and vice versa +enum renderParm_t { + // For backwards compatibility, do not change the order of the first 17 items + RENDERPARM_SCREENCORRECTIONFACTOR = 0, + RENDERPARM_WINDOWCOORD, + RENDERPARM_DIFFUSEMODIFIER, + RENDERPARM_SPECULARMODIFIER, + + RENDERPARM_LOCALLIGHTORIGIN, + RENDERPARM_LOCALVIEWORIGIN, + + RENDERPARM_LIGHTPROJECTION_S, + RENDERPARM_LIGHTPROJECTION_T, + RENDERPARM_LIGHTPROJECTION_Q, + RENDERPARM_LIGHTFALLOFF_S, + + RENDERPARM_BUMPMATRIX_S, + RENDERPARM_BUMPMATRIX_T, + + RENDERPARM_DIFFUSEMATRIX_S, + RENDERPARM_DIFFUSEMATRIX_T, + + RENDERPARM_SPECULARMATRIX_S, + RENDERPARM_SPECULARMATRIX_T, + + RENDERPARM_VERTEXCOLOR_MODULATE, + RENDERPARM_VERTEXCOLOR_ADD, + + // The following are new and can be in any order + + RENDERPARM_COLOR, + RENDERPARM_VIEWORIGIN, + RENDERPARM_GLOBALEYEPOS, + + RENDERPARM_MVPMATRIX_X, + RENDERPARM_MVPMATRIX_Y, + RENDERPARM_MVPMATRIX_Z, + RENDERPARM_MVPMATRIX_W, + + RENDERPARM_MODELMATRIX_X, + RENDERPARM_MODELMATRIX_Y, + RENDERPARM_MODELMATRIX_Z, + RENDERPARM_MODELMATRIX_W, + + RENDERPARM_PROJMATRIX_X, + RENDERPARM_PROJMATRIX_Y, + RENDERPARM_PROJMATRIX_Z, + RENDERPARM_PROJMATRIX_W, + + RENDERPARM_MODELVIEWMATRIX_X, + RENDERPARM_MODELVIEWMATRIX_Y, + RENDERPARM_MODELVIEWMATRIX_Z, + RENDERPARM_MODELVIEWMATRIX_W, + + RENDERPARM_TEXTUREMATRIX_S, + RENDERPARM_TEXTUREMATRIX_T, + + RENDERPARM_TEXGEN_0_S, + RENDERPARM_TEXGEN_0_T, + RENDERPARM_TEXGEN_0_Q, + RENDERPARM_TEXGEN_0_ENABLED, + + RENDERPARM_TEXGEN_1_S, + RENDERPARM_TEXGEN_1_T, + RENDERPARM_TEXGEN_1_Q, + RENDERPARM_TEXGEN_1_ENABLED, + + RENDERPARM_WOBBLESKY_X, + RENDERPARM_WOBBLESKY_Y, + RENDERPARM_WOBBLESKY_Z, + + RENDERPARM_OVERBRIGHT, + RENDERPARM_ENABLE_SKINNING, + RENDERPARM_ALPHA_TEST, + + RENDERPARM_TOTAL, + RENDERPARM_USER = 128, +}; + + +struct glslUniformLocation_t { + int parmIndex; + GLint uniformIndex; +}; + + + +/* +================================================================================================ +idRenderProgManager +================================================================================================ +*/ +class idRenderProgManager { +public: + idRenderProgManager(); + virtual ~idRenderProgManager(); + + void Init(); + void Shutdown(); + + void SetRenderParm( renderParm_t rp, const float * value ); + void SetRenderParms( renderParm_t rp, const float * values, int numValues ); + + int FindVertexShader( const char * name ); + int FindFragmentShader( const char * name ); + + void BindShader( int vIndex, int fIndex ); + + void BindShader_GUI( ) { BindShader_Builtin( BUILTIN_GUI ); } + void BindShader_Color( ) { BindShader_Builtin( BUILTIN_COLOR ); } + void BindShader_Texture( ) { BindShader_Builtin( BUILTIN_TEXTURED ); } + void BindShader_TextureVertexColor() { BindShader_Builtin( BUILTIN_TEXTURE_VERTEXCOLOR ); }; + void BindShader_TextureVertexColorSkinned() { BindShader_Builtin( BUILTIN_TEXTURE_VERTEXCOLOR_SKINNED ); }; + void BindShader_TextureTexGenVertexColor() { BindShader_Builtin( BUILTIN_TEXTURE_TEXGEN_VERTEXCOLOR ); }; + void BindShader_Interaction() { BindShader_Builtin( BUILTIN_INTERACTION ); } + void BindShader_InteractionSkinned() { BindShader_Builtin( BUILTIN_INTERACTION_SKINNED ); } + void BindShader_InteractionAmbient() { BindShader_Builtin( BUILTIN_INTERACTION_AMBIENT ); } + void BindShader_InteractionAmbientSkinned() { BindShader_Builtin( BUILTIN_INTERACTION_AMBIENT_SKINNED ); } + void BindShader_SimpleShade() { BindShader_Builtin( BUILTIN_SIMPLESHADE ); } + void BindShader_Environment() { BindShader_Builtin( BUILTIN_ENVIRONMENT ); } + void BindShader_EnvironmentSkinned() { BindShader_Builtin( BUILTIN_ENVIRONMENT_SKINNED ); } + void BindShader_BumpyEnvironment() { BindShader_Builtin( BUILTIN_BUMPY_ENVIRONMENT ); } + void BindShader_BumpyEnvironmentSkinned() { BindShader_Builtin( BUILTIN_BUMPY_ENVIRONMENT_SKINNED ); } + + void BindShader_Depth() { BindShader_Builtin( BUILTIN_DEPTH ); } + void BindShader_DepthSkinned() { BindShader_Builtin( BUILTIN_DEPTH_SKINNED ); } + void BindShader_Shadow() { BindShader( builtinShaders[BUILTIN_SHADOW], -1 ); } + void BindShader_ShadowSkinned() { BindShader( builtinShaders[BUILTIN_SHADOW_SKINNED], -1 ); } + void BindShader_ShadowDebug() { BindShader_Builtin( BUILTIN_SHADOW_DEBUG ); } + void BindShader_ShadowDebugSkinned() { BindShader_Builtin( BUILTIN_SHADOW_DEBUG_SKINNED ); } + + void BindShader_BlendLight() { BindShader_Builtin( BUILTIN_BLENDLIGHT ); } + void BindShader_Fog() { BindShader_Builtin( BUILTIN_FOG ); } + void BindShader_FogSkinned() { BindShader_Builtin( BUILTIN_FOG_SKINNED ); } + void BindShader_SkyBox() { BindShader_Builtin( BUILTIN_SKYBOX ); } + void BindShader_WobbleSky() { BindShader_Builtin( BUILTIN_WOBBLESKY ); } + void BindShader_StereoDeGhost() { BindShader_Builtin( BUILTIN_STEREO_DEGHOST ); } + void BindShader_StereoWarp() { BindShader_Builtin( BUILTIN_STEREO_WARP ); } + void BindShader_StereoInterlace() { BindShader_Builtin( BUILTIN_STEREO_INTERLACE ); } + void BindShader_PostProcess() { BindShader_Builtin( BUILTIN_POSTPROCESS ); } + void BindShader_ZCullReconstruct() { BindShader_Builtin( BUILTIN_ZCULL_RECONSTRUCT ); } + void BindShader_Bink() { BindShader_Builtin( BUILTIN_BINK ); } + void BindShader_BinkGUI() { BindShader_Builtin( BUILTIN_BINK_GUI ); } + void BindShader_MotionBlur() { BindShader_Builtin( BUILTIN_MOTION_BLUR); } + + // the joints buffer should only be bound for vertex programs that use joints + bool ShaderUsesJoints() const { return vertexShaders[currentVertexShader].usesJoints; } + // the rpEnableSkinning render parm should only be set for vertex programs that use it + bool ShaderHasOptionalSkinning() const { return vertexShaders[currentVertexShader].optionalSkinning; } + + // unbind the currently bound render program + void Unbind(); + + // this should only be called via the reload shader console command + void LoadAllShaders(); + void KillAllShaders(); + + static const int MAX_GLSL_USER_PARMS = 8; + const char* GetGLSLParmName( int rp ) const; + int GetGLSLCurrentProgram() const { return currentRenderProgram; } + void SetUniformValue( const renderParm_t rp, const float * value ); + void CommitUniforms(); + int FindGLSLProgram( const char* name, int vIndex, int fIndex ); + void ZeroUniforms(); + +protected: + void LoadVertexShader( int index ); + void LoadFragmentShader( int index ); + + enum { + BUILTIN_GUI, + BUILTIN_COLOR, + BUILTIN_SIMPLESHADE, + BUILTIN_TEXTURED, + BUILTIN_TEXTURE_VERTEXCOLOR, + BUILTIN_TEXTURE_VERTEXCOLOR_SKINNED, + BUILTIN_TEXTURE_TEXGEN_VERTEXCOLOR, + BUILTIN_INTERACTION, + BUILTIN_INTERACTION_SKINNED, + BUILTIN_INTERACTION_AMBIENT, + BUILTIN_INTERACTION_AMBIENT_SKINNED, + BUILTIN_ENVIRONMENT, + BUILTIN_ENVIRONMENT_SKINNED, + BUILTIN_BUMPY_ENVIRONMENT, + BUILTIN_BUMPY_ENVIRONMENT_SKINNED, + + BUILTIN_DEPTH, + BUILTIN_DEPTH_SKINNED, + BUILTIN_SHADOW, + BUILTIN_SHADOW_SKINNED, + BUILTIN_SHADOW_DEBUG, + BUILTIN_SHADOW_DEBUG_SKINNED, + + BUILTIN_BLENDLIGHT, + BUILTIN_FOG, + BUILTIN_FOG_SKINNED, + BUILTIN_SKYBOX, + BUILTIN_WOBBLESKY, + BUILTIN_POSTPROCESS, + BUILTIN_STEREO_DEGHOST, + BUILTIN_STEREO_WARP, + BUILTIN_ZCULL_RECONSTRUCT, + BUILTIN_BINK, + BUILTIN_BINK_GUI, + BUILTIN_STEREO_INTERLACE, + BUILTIN_MOTION_BLUR, + + MAX_BUILTINS + }; + int builtinShaders[MAX_BUILTINS]; + void BindShader_Builtin( int i ) { BindShader( builtinShaders[i], builtinShaders[i] ); } + + GLuint LoadShader( GLenum target, const char * name, const char * startToken ); + bool CompileGLSL( GLenum target, const char * name ); + GLuint LoadGLSLShader( GLenum target, const char * name, idList & uniforms ); + void LoadGLSLProgram( const int programIndex, const int vertexShaderIndex, const int fragmentShaderIndex ); + + static const GLuint INVALID_PROGID = 0xFFFFFFFF; + + struct vertexShader_t { + vertexShader_t() : progId( INVALID_PROGID ), usesJoints( false ), optionalSkinning( false ) {} + idStr name; + GLuint progId; + bool usesJoints; + bool optionalSkinning; + idList uniforms; + }; + struct fragmentShader_t { + fragmentShader_t() : progId( INVALID_PROGID ) {} + idStr name; + GLuint progId; + idList uniforms; + }; + + struct glslProgram_t { + glslProgram_t() : progId( INVALID_PROGID ), + vertexShaderIndex( -1 ), + fragmentShaderIndex( -1 ), + vertexUniformArray( -1 ), + fragmentUniformArray( -1 ) {} + idStr name; + GLuint progId; + int vertexShaderIndex; + int fragmentShaderIndex; + GLint vertexUniformArray; + GLint fragmentUniformArray; + idList uniformLocations; + }; + int currentRenderProgram; + idList glslPrograms; + idStaticList glslUniforms; + + + int currentVertexShader; + int currentFragmentShader; + idList vertexShaders; + idList fragmentShaders; +}; + +extern idRenderProgManager renderProgManager; + +#endif diff --git a/neo/renderer/RenderProgs_GLSL.cpp b/neo/renderer/RenderProgs_GLSL.cpp new file mode 100644 index 00000000..8dda5eed --- /dev/null +++ b/neo/renderer/RenderProgs_GLSL.cpp @@ -0,0 +1,1293 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +idCVar r_skipStripDeadCode( "r_skipStripDeadCode", "0", CVAR_BOOL, "Skip stripping dead code" ); +idCVar r_useUniformArrays( "r_useUniformArrays", "1", CVAR_BOOL, "" ); + + +#define VERTEX_UNIFORM_ARRAY_NAME "_va_" +#define FRAGMENT_UNIFORM_ARRAY_NAME "_fa_" + +static const int AT_VS_IN = BIT( 1 ); +static const int AT_VS_OUT = BIT( 2 ); +static const int AT_PS_IN = BIT( 3 ); +static const int AT_PS_OUT = BIT( 4 ); + +struct idCGBlock { + idStr prefix; // tokens that comes before the name + idStr name; // the name + idStr postfix; // tokens that comes after the name + bool used; // whether or not this block is referenced anywhere +}; + +/* +================================================ +attribInfo_t +================================================ +*/ +struct attribInfo_t { + const char * type; + const char * name; + const char * semantic; + const char * glsl; + int bind; + int flags; + int vertexMask; +}; + +/* +================================================ +vertexMask_t + +NOTE: There is a PS3 dependency between the bit flag specified here and the vertex +attribute index and attribute semantic specified in DeclRenderProg.cpp because the +stored render prog vertexMask is initialized with cellCgbGetVertexConfiguration(). +The ATTRIB_INDEX_ defines are used to make sure the vertexMask_t and attrib assignment +in DeclRenderProg.cpp are in sync. + +Even though VERTEX_MASK_XYZ_SHORT and VERTEX_MASK_ST_SHORT are not real attributes, +they come before the VERTEX_MASK_MORPH to reduce the range of vertex program +permutations defined by the vertexMask_t bits on the Xbox 360 (see MAX_VERTEX_DECLARATIONS). +================================================ +*/ +enum vertexMask_t { + VERTEX_MASK_XYZ = BIT( PC_ATTRIB_INDEX_VERTEX ), + VERTEX_MASK_ST = BIT( PC_ATTRIB_INDEX_ST ), + VERTEX_MASK_NORMAL = BIT( PC_ATTRIB_INDEX_NORMAL ), + VERTEX_MASK_COLOR = BIT( PC_ATTRIB_INDEX_COLOR ), + VERTEX_MASK_TANGENT = BIT( PC_ATTRIB_INDEX_TANGENT ), + VERTEX_MASK_COLOR2 = BIT( PC_ATTRIB_INDEX_COLOR2 ), +}; + +attribInfo_t attribsPC[] = { + // vertex attributes + { "float4", "position", "POSITION", "in_Position", PC_ATTRIB_INDEX_VERTEX, AT_VS_IN, VERTEX_MASK_XYZ }, + { "float2", "texcoord", "TEXCOORD0", "in_TexCoord", PC_ATTRIB_INDEX_ST, AT_VS_IN, VERTEX_MASK_ST }, + { "float4", "normal", "NORMAL", "in_Normal", PC_ATTRIB_INDEX_NORMAL, AT_VS_IN, VERTEX_MASK_NORMAL }, + { "float4", "tangent", "TANGENT", "in_Tangent", PC_ATTRIB_INDEX_TANGENT, AT_VS_IN, VERTEX_MASK_TANGENT }, + { "float4", "color", "COLOR0", "in_Color", PC_ATTRIB_INDEX_COLOR, AT_VS_IN, VERTEX_MASK_COLOR }, + { "float4", "color2", "COLOR1", "in_Color2", PC_ATTRIB_INDEX_COLOR2, AT_VS_IN, VERTEX_MASK_COLOR2 }, + + // pre-defined vertex program output + { "float4", "position", "POSITION", "gl_Position", 0, AT_VS_OUT, 0 }, + { "float", "clip0", "CLP0", "gl_ClipDistance[0]", 0, AT_VS_OUT, 0 }, + { "float", "clip1", "CLP1", "gl_ClipDistance[1]", 0, AT_VS_OUT, 0 }, + { "float", "clip2", "CLP2", "gl_ClipDistance[2]", 0, AT_VS_OUT, 0 }, + { "float", "clip3", "CLP3", "gl_ClipDistance[3]", 0, AT_VS_OUT, 0 }, + { "float", "clip4", "CLP4", "gl_ClipDistance[4]", 0, AT_VS_OUT, 0 }, + { "float", "clip5", "CLP5", "gl_ClipDistance[5]", 0, AT_VS_OUT, 0 }, + + // pre-defined fragment program input + { "float4", "position", "WPOS", "gl_FragCoord", 0, AT_PS_IN, 0 }, + { "half4", "hposition", "WPOS", "gl_FragCoord", 0, AT_PS_IN, 0 }, + { "float", "facing", "FACE", "gl_FrontFacing", 0, AT_PS_IN, 0 }, + + // fragment program output + { "float4", "color", "COLOR", "gl_FragColor", 0, AT_PS_OUT, 0 }, // GLSL version 1.2 doesn't allow for custom color name mappings + { "half4", "hcolor", "COLOR", "gl_FragColor", 0, AT_PS_OUT, 0 }, + { "float4", "color0", "COLOR0", "gl_FragColor", 0, AT_PS_OUT, 0 }, + { "float4", "color1", "COLOR1", "gl_FragColor", 1, AT_PS_OUT, 0 }, + { "float4", "color2", "COLOR2", "gl_FragColor", 2, AT_PS_OUT, 0 }, + { "float4", "color3", "COLOR3", "gl_FragColor", 3, AT_PS_OUT, 0 }, + { "float", "depth", "DEPTH", "gl_FragDepth", 4, AT_PS_OUT, 0 }, + + // vertex to fragment program pass through + { "float4", "color", "COLOR", "gl_FrontColor", 0, AT_VS_OUT, 0 }, + { "float4", "color0", "COLOR0", "gl_FrontColor", 0, AT_VS_OUT, 0 }, + { "float4", "color1", "COLOR1", "gl_FrontSecondaryColor", 0, AT_VS_OUT, 0 }, + + + { "float4", "color", "COLOR", "gl_Color", 0, AT_PS_IN, 0 }, + { "float4", "color0", "COLOR0", "gl_Color", 0, AT_PS_IN, 0 }, + { "float4", "color1", "COLOR1", "gl_SecondaryColor", 0, AT_PS_IN, 0 }, + + { "half4", "hcolor", "COLOR", "gl_Color", 0, AT_PS_IN, 0 }, + { "half4", "hcolor0", "COLOR0", "gl_Color", 0, AT_PS_IN, 0 }, + { "half4", "hcolor1", "COLOR1", "gl_SecondaryColor", 0, AT_PS_IN, 0 }, + + { "float4", "texcoord0", "TEXCOORD0_centroid", "vofi_TexCoord0", 0, AT_PS_IN, 0 }, + { "float4", "texcoord1", "TEXCOORD1_centroid", "vofi_TexCoord1", 0, AT_PS_IN, 0 }, + { "float4", "texcoord2", "TEXCOORD2_centroid", "vofi_TexCoord2", 0, AT_PS_IN, 0 }, + { "float4", "texcoord3", "TEXCOORD3_centroid", "vofi_TexCoord3", 0, AT_PS_IN, 0 }, + { "float4", "texcoord4", "TEXCOORD4_centroid", "vofi_TexCoord4", 0, AT_PS_IN, 0 }, + { "float4", "texcoord5", "TEXCOORD5_centroid", "vofi_TexCoord5", 0, AT_PS_IN, 0 }, + { "float4", "texcoord6", "TEXCOORD6_centroid", "vofi_TexCoord6", 0, AT_PS_IN, 0 }, + { "float4", "texcoord7", "TEXCOORD7_centroid", "vofi_TexCoord7", 0, AT_PS_IN, 0 }, + { "float4", "texcoord8", "TEXCOORD8_centroid", "vofi_TexCoord8", 0, AT_PS_IN, 0 }, + { "float4", "texcoord9", "TEXCOORD9_centroid", "vofi_TexCoord9", 0, AT_PS_IN, 0 }, + + { "float4", "texcoord0", "TEXCOORD0", "vofi_TexCoord0", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord1", "TEXCOORD1", "vofi_TexCoord1", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord2", "TEXCOORD2", "vofi_TexCoord2", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord3", "TEXCOORD3", "vofi_TexCoord3", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord4", "TEXCOORD4", "vofi_TexCoord4", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord5", "TEXCOORD5", "vofi_TexCoord5", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord6", "TEXCOORD6", "vofi_TexCoord6", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord7", "TEXCOORD7", "vofi_TexCoord7", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord8", "TEXCOORD8", "vofi_TexCoord8", 0, AT_VS_OUT | AT_PS_IN, 0 }, + { "float4", "texcoord9", "TEXCOORD9", "vofi_TexCoord9", 0, AT_VS_OUT | AT_PS_IN, 0 }, + + { "half4", "htexcoord0", "TEXCOORD0", "vofi_TexCoord0", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord1", "TEXCOORD1", "vofi_TexCoord1", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord2", "TEXCOORD2", "vofi_TexCoord2", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord3", "TEXCOORD3", "vofi_TexCoord3", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord4", "TEXCOORD4", "vofi_TexCoord4", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord5", "TEXCOORD5", "vofi_TexCoord5", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord6", "TEXCOORD6", "vofi_TexCoord6", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord7", "TEXCOORD7", "vofi_TexCoord7", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord8", "TEXCOORD8", "vofi_TexCoord8", 0, AT_PS_IN, 0 }, + { "half4", "htexcoord9", "TEXCOORD9", "vofi_TexCoord9", 0, AT_PS_IN, 0 }, + { "float", "fog", "FOG", "gl_FogFragCoord", 0, AT_VS_OUT, 0 }, + { "float4", "fog", "FOG", "gl_FogFragCoord", 0, AT_PS_IN, 0 }, + { NULL, NULL, NULL, NULL, 0, 0, 0 } +}; + +const char * types[] = { + "int", + "float", + "half", + "fixed", + "bool", + "cint", + "cfloat", + "void" +}; +static const int numTypes = sizeof( types ) / sizeof( types[0] ); + +const char * typePosts[] = { + "1", "2", "3", "4", + "1x1", "1x2", "1x3", "1x4", + "2x1", "2x2", "2x3", "2x4", + "3x1", "3x2", "3x3", "3x4", + "4x1", "4x2", "4x3", "4x4" +}; +static const int numTypePosts = sizeof( typePosts ) / sizeof( typePosts[0] ); + +const char * prefixes[] = { + "static", + "const", + "uniform", + "struct", + + "sampler", + + "sampler1D", + "sampler2D", + "sampler3D", + "samplerCUBE", + + "sampler1DShadow", // GLSL + "sampler2DShadow", // GLSL + "sampler3DShadow", // GLSL + "samplerCubeShadow", // GLSL + + "sampler2DMS", // GLSL +}; +static const int numPrefixes = sizeof( prefixes ) / sizeof( prefixes[0] ); + +// For GLSL we need to have the names for the renderparms so we can look up their run time indices within the renderprograms +static const char * GLSLParmNames[] = { + "rpScreenCorrectionFactor", + "rpWindowCoord", + "rpDiffuseModifier", + "rpSpecularModifier", + + "rpLocalLightOrigin", + "rpLocalViewOrigin", + + "rpLightProjectionS", + "rpLightProjectionT", + "rpLightProjectionQ", + "rpLightFalloffS", + + "rpBumpMatrixS", + "rpBumpMatrixT", + + "rpDiffuseMatrixS", + "rpDiffuseMatrixT", + + "rpSpecularMatrixS", + "rpSpecularMatrixT", + + "rpVertexColorModulate", + "rpVertexColorAdd", + + "rpColor", + "rpViewOrigin", + "rpGlobalEyePos", + + "rpMVPmatrixX", + "rpMVPmatrixY", + "rpMVPmatrixZ", + "rpMVPmatrixW", + + "rpModelMatrixX", + "rpModelMatrixY", + "rpModelMatrixZ", + "rpModelMatrixW", + + "rpProjectionMatrixX", + "rpProjectionMatrixY", + "rpProjectionMatrixZ", + "rpProjectionMatrixW", + + "rpModelViewMatrixX", + "rpModelViewMatrixY", + "rpModelViewMatrixZ", + "rpModelViewMatrixW", + + "rpTextureMatrixS", + "rpTextureMatrixT", + + "rpTexGen0S", + "rpTexGen0T", + "rpTexGen0Q", + "rpTexGen0Enabled", + + "rpTexGen1S", + "rpTexGen1T", + "rpTexGen1Q", + "rpTexGen1Enabled", + + "rpWobbleSkyX", + "rpWobbleSkyY", + "rpWobbleSkyZ", + + "rpOverbright", + "rpEnableSkinning", + "rpAlphaTest" +}; + +/* +======================== +StripDeadCode +======================== +*/ +idStr StripDeadCode( const idStr & in, const char * name ) { + if ( r_skipStripDeadCode.GetBool() ) { + return in; + } + + //idLexer src( LEXFL_NOFATALERRORS ); + idParser src( LEXFL_NOFATALERRORS ); + src.LoadMemory( in.c_str(), in.Length(), name ); + src.AddDefine("PC"); + + idList< idCGBlock, TAG_RENDERPROG > blocks; + + blocks.SetNum( 100 ); + + idToken token; + while ( !src.EndOfFile() ) { + idCGBlock & block = blocks.Alloc(); + // read prefix + while ( src.ReadToken( &token ) ) { + bool found = false; + for ( int i = 0; i < numPrefixes; i++ ) { + if ( token == prefixes[i] ) { + found = true; + break; + } + } + if ( !found ) { + for ( int i = 0; i < numTypes; i++ ) { + if ( token == types[i] ) { + found = true; + break; + } + int typeLen = idStr::Length( types[i] ); + if ( token.Cmpn( types[i], typeLen ) == 0 ) { + for ( int j = 0; j < numTypePosts; j++ ) { + if ( idStr::Cmp( token.c_str() + typeLen, typePosts[j] ) == 0 ) { + found = true; + break; + } + } + if ( found ) { + break; + } + } + } + } + if ( found ) { + if ( block.prefix.Length() > 0 && token.WhiteSpaceBeforeToken() ) { + block.prefix += ' '; + } + block.prefix += token; + } else { + src.UnreadToken( &token ); + break; + } + } + if ( !src.ReadToken( &token ) ) { + blocks.SetNum( blocks.Num() - 1 ); + break; + } + block.name = token; + + if ( src.PeekTokenString( "=" ) || src.PeekTokenString( ":" ) || src.PeekTokenString( "[" ) ) { + src.ReadToken( &token ); + block.postfix = token; + while ( src.ReadToken( &token ) ) { + if ( token == ";" ) { + block.postfix += ';'; + break; + } else { + if ( token.WhiteSpaceBeforeToken() ){ + block.postfix += ' '; + } + block.postfix += token; + } + } + } else if ( src.PeekTokenString( "(" ) ) { + idStr parms, body; + src.ParseBracedSection( parms, -1, true, '(', ')' ); + if ( src.CheckTokenString( ";" ) ) { + block.postfix = parms + ";"; + } else { + src.ParseBracedSection( body, -1, true, '{', '}' ); + block.postfix = parms + " " + body; + } + } else if ( src.PeekTokenString( "{" ) ) { + src.ParseBracedSection( block.postfix, -1, true, '{', '}' ); + if ( src.CheckTokenString( ";" ) ) { + block.postfix += ';'; + } + } else if ( src.CheckTokenString( ";" ) ) { + block.postfix = idStr( ';' ); + } else { + src.Warning( "Could not strip dead code -- unknown token %s\n", token.c_str() ); + return in; + } + } + + idList stack; + for ( int i = 0; i < blocks.Num(); i++ ) { + blocks[i].used = ( ( blocks[i].name == "main" ) + || blocks[i].name.Right( 4 ) == "_ubo" + ); + + if ( blocks[i].name == "include" ) { + blocks[i].used = true; + blocks[i].name = ""; // clear out the include tag + } + + if ( blocks[i].used ) { + stack.Append( i ); + } + } + + while ( stack.Num() > 0 ) { + int i = stack[stack.Num() - 1]; + stack.SetNum( stack.Num() - 1 ); + + idLexer src( LEXFL_NOFATALERRORS ); + src.LoadMemory( blocks[i].postfix.c_str(), blocks[i].postfix.Length(), name ); + while ( src.ReadToken( &token ) ) { + for ( int j = 0; j < blocks.Num(); j++ ) { + if ( !blocks[j].used ) { + if ( token == blocks[j].name ) { + blocks[j].used = true; + stack.Append( j ); + } + } + } + } + } + + idStr out; + + for ( int i = 0; i < blocks.Num(); i++ ) { + if ( blocks[i].used ) { + out += blocks[i].prefix; + out += ' '; + out += blocks[i].name; + out += ' '; + out += blocks[i].postfix; + out += '\n'; + } + } + + return out; +} + +struct typeConversion_t { + const char * typeCG; + const char * typeGLSL; +} typeConversion[] = { + { "void", "void" }, + + { "fixed", "float" }, + + { "float", "float" }, + { "float2", "vec2" }, + { "float3", "vec3" }, + { "float4", "vec4" }, + + { "half", "float" }, + { "half2", "vec2" }, + { "half3", "vec3" }, + { "half4", "vec4" }, + + { "int", "int" }, + { "int2", "ivec2" }, + { "int3", "ivec3" }, + { "int4", "ivec4" }, + + { "bool", "bool" }, + { "bool2", "bvec2" }, + { "bool3", "bvec3" }, + { "bool4", "bvec4" }, + + { "float2x2", "mat2x2" }, + { "float2x3", "mat2x3" }, + { "float2x4", "mat2x4" }, + + { "float3x2", "mat3x2" }, + { "float3x3", "mat3x3" }, + { "float3x4", "mat3x4" }, + + { "float4x2", "mat4x2" }, + { "float4x3", "mat4x3" }, + { "float4x4", "mat4x4" }, + + { "sampler1D", "sampler1D" }, + { "sampler2D", "sampler2D" }, + { "sampler3D", "sampler3D" }, + { "samplerCUBE", "samplerCube" }, + + { "sampler1DShadow", "sampler1DShadow" }, + { "sampler2DShadow", "sampler2DShadow" }, + { "sampler3DShadow", "sampler3DShadow" }, + { "samplerCubeShadow", "samplerCubeShadow" }, + + { "sampler2DMS", "sampler2DMS" }, + + { NULL, NULL } +}; + +const char * vertexInsert = { + "#version 150\n" + "#define PC\n" + "\n" + "float saturate( float v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec2 saturate( vec2 v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec3 saturate( vec3 v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec4 saturate( vec4 v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec4 tex2Dlod( sampler2D sampler, vec4 texcoord ) { return textureLod( sampler, texcoord.xy, texcoord.w ); }\n" + "\n" +}; + +const char * fragmentInsert = { + "#version 150\n" + "#define PC\n" + "\n" + "void clip( float v ) { if ( v < 0.0 ) { discard; } }\n" + "void clip( vec2 v ) { if ( any( lessThan( v, vec2( 0.0 ) ) ) ) { discard; } }\n" + "void clip( vec3 v ) { if ( any( lessThan( v, vec3( 0.0 ) ) ) ) { discard; } }\n" + "void clip( vec4 v ) { if ( any( lessThan( v, vec4( 0.0 ) ) ) ) { discard; } }\n" + "\n" + "float saturate( float v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec2 saturate( vec2 v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec3 saturate( vec3 v ) { return clamp( v, 0.0, 1.0 ); }\n" + "vec4 saturate( vec4 v ) { return clamp( v, 0.0, 1.0 ); }\n" + "\n" + "vec4 tex2D( sampler2D sampler, vec2 texcoord ) { return texture( sampler, texcoord.xy ); }\n" + "vec4 tex2D( sampler2DShadow sampler, vec3 texcoord ) { return vec4( texture( sampler, texcoord.xyz ) ); }\n" + "\n" + "vec4 tex2D( sampler2D sampler, vec2 texcoord, vec2 dx, vec2 dy ) { return textureGrad( sampler, texcoord.xy, dx, dy ); }\n" + "vec4 tex2D( sampler2DShadow sampler, vec3 texcoord, vec2 dx, vec2 dy ) { return vec4( textureGrad( sampler, texcoord.xyz, dx, dy ) ); }\n" + "\n" + "vec4 texCUBE( samplerCube sampler, vec3 texcoord ) { return texture( sampler, texcoord.xyz ); }\n" + "vec4 texCUBE( samplerCubeShadow sampler, vec4 texcoord ) { return vec4( texture( sampler, texcoord.xyzw ) ); }\n" + "\n" + "vec4 tex1Dproj( sampler1D sampler, vec2 texcoord ) { return textureProj( sampler, texcoord ); }\n" + "vec4 tex2Dproj( sampler2D sampler, vec3 texcoord ) { return textureProj( sampler, texcoord ); }\n" + "vec4 tex3Dproj( sampler3D sampler, vec4 texcoord ) { return textureProj( sampler, texcoord ); }\n" + "\n" + "vec4 tex1Dbias( sampler1D sampler, vec4 texcoord ) { return texture( sampler, texcoord.x, texcoord.w ); }\n" + "vec4 tex2Dbias( sampler2D sampler, vec4 texcoord ) { return texture( sampler, texcoord.xy, texcoord.w ); }\n" + "vec4 tex3Dbias( sampler3D sampler, vec4 texcoord ) { return texture( sampler, texcoord.xyz, texcoord.w ); }\n" + "vec4 texCUBEbias( samplerCube sampler, vec4 texcoord ) { return texture( sampler, texcoord.xyz, texcoord.w ); }\n" + "\n" + "vec4 tex1Dlod( sampler1D sampler, vec4 texcoord ) { return textureLod( sampler, texcoord.x, texcoord.w ); }\n" + "vec4 tex2Dlod( sampler2D sampler, vec4 texcoord ) { return textureLod( sampler, texcoord.xy, texcoord.w ); }\n" + "vec4 tex3Dlod( sampler3D sampler, vec4 texcoord ) { return textureLod( sampler, texcoord.xyz, texcoord.w ); }\n" + "vec4 texCUBElod( samplerCube sampler, vec4 texcoord ) { return textureLod( sampler, texcoord.xyz, texcoord.w ); }\n" + "\n" +}; + +struct builtinConversion_t { + const char * nameCG; + const char * nameGLSL; +} builtinConversion[] = { + { "frac", "fract" }, + { "lerp", "mix" }, + { "rsqrt", "inversesqrt" }, + { "ddx", "dFdx" }, + { "ddy", "dFdy" }, + + { NULL, NULL } +}; + +struct inOutVariable_t { + idStr type; + idStr nameCg; + idStr nameGLSL; + bool declareInOut; +}; + +/* +======================== +ParseInOutStruct +======================== +*/ +void ParseInOutStruct( idLexer & src, int attribType, idList< inOutVariable_t > & inOutVars ) { + src.ExpectTokenString( "{" ); + + while( !src.CheckTokenString( "}" ) ) { + inOutVariable_t var; + + idToken token; + src.ReadToken( &token ); + var.type = token; + src.ReadToken( &token ); + var.nameCg = token; + + if ( !src.CheckTokenString( ":" ) ) { + src.SkipUntilString( ";" ); + continue; + } + + src.ReadToken( &token ); + var.nameGLSL = token; + src.ExpectTokenString( ";" ); + + // convert the type + for ( int i = 0; typeConversion[i].typeCG != NULL; i++ ) { + if ( var.type.Cmp( typeConversion[i].typeCG ) == 0 ) { + var.type = typeConversion[i].typeGLSL; + break; + } + } + + // convert the semantic to a GLSL name + for ( int i = 0; attribsPC[i].semantic != NULL; i++ ) { + if ( ( attribsPC[i].flags & attribType ) != 0 ) { + if ( var.nameGLSL.Cmp( attribsPC[i].semantic ) == 0 ) { + var.nameGLSL = attribsPC[i].glsl; + break; + } + } + } + + // check if it was defined previously + var.declareInOut = true; + for ( int i = 0; i < inOutVars.Num(); i++ ) { + if ( var.nameGLSL == inOutVars[i].nameGLSL ) { + var.declareInOut = false; + break; + } + } + + inOutVars.Append( var ); + } + + src.ExpectTokenString( ";" ); +} + +/* +======================== +ConvertCG2GLSL +======================== +*/ +idStr ConvertCG2GLSL( const idStr & in, const char * name, bool isVertexProgram, idStr & uniforms ) { + idStr program; + program.ReAllocate( in.Length() * 2, false ); + + idList< inOutVariable_t, TAG_RENDERPROG > varsIn; + idList< inOutVariable_t, TAG_RENDERPROG > varsOut; + idList< idStr > uniformList; + + idLexer src( LEXFL_NOFATALERRORS ); + src.LoadMemory( in.c_str(), in.Length(), name ); + + bool inMain = false; + const char * uniformArrayName = isVertexProgram ? VERTEX_UNIFORM_ARRAY_NAME : FRAGMENT_UNIFORM_ARRAY_NAME; + char newline[128] = { "\n" }; + + idToken token; + while ( src.ReadToken( &token ) ) { + + // check for uniforms + while ( token == "uniform" && src.CheckTokenString( "float4" ) ) { + src.ReadToken( &token ); + uniformList.Append( token ); + + // strip ': register()' from uniforms + if ( src.CheckTokenString( ":" ) ) { + if ( src.CheckTokenString( "register" ) ) { + src.SkipUntilString( ";" ); + } + } + + src.ReadToken( & token ); + } + + // convert the in/out structs + if ( token == "struct" ) { + if ( src.CheckTokenString( "VS_IN" ) ) { + ParseInOutStruct( src, AT_VS_IN, varsIn ); + program += "\n\n"; + for ( int i = 0; i < varsIn.Num(); i++ ) { + if ( varsIn[i].declareInOut ) { + program += "in " + varsIn[i].type + " " + varsIn[i].nameGLSL + ";\n"; + } + } + continue; + } else if ( src.CheckTokenString( "VS_OUT" ) ) { + ParseInOutStruct( src, AT_VS_OUT, varsOut ); + program += "\n"; + for ( int i = 0; i < varsOut.Num(); i++ ) { + if ( varsOut[i].declareInOut ) { + program += "out " + varsOut[i].type + " " + varsOut[i].nameGLSL + ";\n"; + } + } + continue; + } else if ( src.CheckTokenString( "PS_IN" ) ) { + ParseInOutStruct( src, AT_PS_IN, varsIn ); + program += "\n\n"; + for ( int i = 0; i < varsIn.Num(); i++ ) { + if ( varsIn[i].declareInOut ) { + program += "in " + varsIn[i].type + " " + varsIn[i].nameGLSL + ";\n"; + } + } + inOutVariable_t var; + var.type = "vec4"; + var.nameCg = "position"; + var.nameGLSL = "gl_FragCoord"; + varsIn.Append( var ); + continue; + } else if ( src.CheckTokenString( "PS_OUT" ) ) { + ParseInOutStruct( src, AT_PS_OUT, varsOut ); + program += "\n"; + for ( int i = 0; i < varsOut.Num(); i++ ) { + if ( varsOut[i].declareInOut ) { + program += "out " + varsOut[i].type + " " + varsOut[i].nameGLSL + ";\n"; + } + } + continue; + } + } + + // strip 'static' + if ( token == "static" ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + src.SkipWhiteSpace( true ); // remove white space between 'static' and the next token + continue; + } + + // strip ': register()' from uniforms + if ( token == ":" ) { + if ( src.CheckTokenString( "register" ) ) { + src.SkipUntilString( ";" ); + program += ";"; + continue; + } + } + + // strip in/program parameters from main + if ( token == "void" && src.CheckTokenString( "main" ) ) { + src.ExpectTokenString( "(" ); + while( src.ReadToken( &token ) ) { + if ( token == ")" ) { + break; + } + } + + program += "\nvoid main()"; + inMain = true; + continue; + } + + // strip 'const' from local variables in main() + if ( token == "const" && inMain ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + src.SkipWhiteSpace( true ); // remove white space between 'const' and the next token + continue; + } + + // maintain indentation + if ( token == "{" ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += "{"; + + int len = Min( idStr::Length( newline ) + 1, (int)sizeof( newline ) - 1 ); + newline[len - 1] = '\t'; + newline[len - 0] = '\0'; + continue; + } + if ( token == "}" ) { + int len = Max( idStr::Length( newline ) - 1, 0 ); + newline[len] = '\0'; + + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += "}"; + continue; + } + + // check for a type conversion + bool foundType = false; + for ( int i = 0; typeConversion[i].typeCG != NULL; i++ ) { + if ( token.Cmp( typeConversion[i].typeCG ) == 0 ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += typeConversion[i].typeGLSL; + foundType = true; + break; + } + } + if ( foundType ) { + continue; + } + + if ( r_useUniformArrays.GetBool() ) { + // check for uniforms that need to be converted to the array + bool isUniform = false; + for ( int i = 0; i < uniformList.Num(); i++ ) { + if ( token == uniformList[i] ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += va( "%s[%d /* %s */]", uniformArrayName, i, uniformList[i].c_str() ); + isUniform = true; + break; + } + } + if ( isUniform ) { + continue; + } + } + + // check for input/output parameters + if ( src.CheckTokenString( "." ) ) { + + if ( token == "vertex" || token == "fragment" ) { + idToken member; + src.ReadToken( &member ); + + bool foundInOut = false; + for ( int i = 0; i < varsIn.Num(); i++ ) { + if ( member.Cmp( varsIn[i].nameCg ) == 0 ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += varsIn[i].nameGLSL; + foundInOut = true; + break; + } + } + if ( !foundInOut ) { + src.Error( "invalid input parameter %s.%s", token.c_str(), member.c_str() ); + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += token; + program += "."; + program += member; + } + continue; + } + + if ( token == "result" ) { + idToken member; + src.ReadToken( &member ); + + bool foundInOut = false; + for ( int i = 0; i < varsOut.Num(); i++ ) { + if ( member.Cmp( varsOut[i].nameCg ) == 0 ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += varsOut[i].nameGLSL; + foundInOut = true; + break; + } + } + if ( !foundInOut ) { + src.Error( "invalid output parameter %s.%s", token.c_str(), member.c_str() ); + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += token; + program += "."; + program += member; + } + continue; + } + + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += token; + program += "."; + continue; + } + + // check for a function conversion + bool foundFunction = false; + for ( int i = 0; builtinConversion[i].nameCG != NULL; i++ ) { + if ( token.Cmp( builtinConversion[i].nameCG ) == 0 ) { + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += builtinConversion[i].nameGLSL; + foundFunction = true; + break; + } + } + if ( foundFunction ) { + continue; + } + + program += ( token.linesCrossed > 0 ) ? newline : ( token.WhiteSpaceBeforeToken() > 0 ? " " : "" ); + program += token; + } + + idStr out; + + if ( isVertexProgram ) { + out.ReAllocate( idStr::Length( vertexInsert ) + in.Length() * 2, false ); + out += vertexInsert; + } else { + out.ReAllocate( idStr::Length( fragmentInsert ) + in.Length() * 2, false ); + out += fragmentInsert; + } + + if ( uniformList.Num() > 0 ) { + if ( r_useUniformArrays.GetBool() ) { + out += va( "\nuniform vec4 %s[%d];\n", uniformArrayName, uniformList.Num() ); + } else { + out += "\n"; + for ( int i = 0; i < uniformList.Num(); i++ ) { + out += "uniform vec4 "; + out += uniformList[i]; + out += ";\n"; + } + } + } + + out += program; + + for ( int i = 0; i < uniformList.Num(); i++ ) { + uniforms += uniformList[i]; + uniforms += "\n"; + } + uniforms += "\n"; + + return out; +} + +/* +================================================================================================ +idRenderProgManager::LoadGLSLShader +================================================================================================ +*/ +GLuint idRenderProgManager::LoadGLSLShader( GLenum target, const char * name, idList & uniforms ) { + + idStr inFile; + idStr outFileHLSL; + idStr outFileGLSL; + idStr outFileUniforms; + inFile.Format( "renderprogs\\%s", name ); + inFile.StripFileExtension(); + outFileHLSL.Format( "renderprogs\\glsl\\%s", name ); + outFileHLSL.StripFileExtension(); + outFileGLSL.Format( "renderprogs\\glsl\\%s", name ); + outFileGLSL.StripFileExtension(); + outFileUniforms.Format( "renderprogs\\glsl\\%s", name ); + outFileUniforms.StripFileExtension(); + if ( target == GL_FRAGMENT_SHADER ) { + inFile += ".pixel"; + outFileHLSL += "_fragment.hlsl"; + outFileGLSL += "_fragment.glsl"; + outFileUniforms += "_fragment.uniforms"; + } else { + inFile += ".vertex"; + outFileHLSL += "_vertex.hlsl"; + outFileGLSL += "_vertex.glsl"; + outFileUniforms += "_vertex.uniforms"; + } + + // first check whether we already have a valid GLSL file and compare it to the hlsl timestamp; + ID_TIME_T hlslTimeStamp; + int hlslFileLength = fileSystem->ReadFile( inFile.c_str(), NULL, &hlslTimeStamp ); + + ID_TIME_T glslTimeStamp; + int glslFileLength = fileSystem->ReadFile( outFileGLSL.c_str(), NULL, &glslTimeStamp ); + + // if the glsl file doesn't exist or we have a newer HLSL file we need to recreate the glsl file. + idStr programGLSL; + idStr programUniforms; + if ( ( glslFileLength <= 0 ) || ( hlslTimeStamp > glslTimeStamp ) ) { + if ( hlslFileLength <= 0 ) { + // hlsl file doesn't even exist bail out + return false; + } + + void * hlslFileBuffer = NULL; + int len = fileSystem->ReadFile( inFile.c_str(), &hlslFileBuffer ); + if ( len <= 0 ) { + return false; + } + idStr hlslCode( ( const char* ) hlslFileBuffer ); + idStr programHLSL = StripDeadCode( hlslCode, inFile ); + programGLSL = ConvertCG2GLSL( programHLSL, inFile, target == GL_VERTEX_SHADER, programUniforms ); + + fileSystem->WriteFile( outFileHLSL, programHLSL.c_str(), programHLSL.Length(), "fs_basepath" ); + fileSystem->WriteFile( outFileGLSL, programGLSL.c_str(), programGLSL.Length(), "fs_basepath" ); + if ( r_useUniformArrays.GetBool() ) { + fileSystem->WriteFile( outFileUniforms, programUniforms.c_str(), programUniforms.Length(), "fs_basepath" ); + } + } else { + // read in the glsl file + void * fileBufferGLSL = NULL; + int lengthGLSL = fileSystem->ReadFile( outFileGLSL.c_str(), &fileBufferGLSL ); + if ( lengthGLSL <= 0 ) { + idLib::Error( "GLSL file %s could not be loaded and may be corrupt", outFileGLSL.c_str() ); + } + programGLSL = ( const char * ) fileBufferGLSL; + Mem_Free( fileBufferGLSL ); + + if ( r_useUniformArrays.GetBool() ) { + // read in the uniform file + void * fileBufferUniforms = NULL; + int lengthUniforms = fileSystem->ReadFile( outFileUniforms.c_str(), &fileBufferUniforms ); + if ( lengthUniforms <= 0 ) { + idLib::Error( "uniform file %s could not be loaded and may be corrupt", outFileUniforms.c_str() ); + } + programUniforms = ( const char* ) fileBufferUniforms; + Mem_Free( fileBufferUniforms ); + } + } + + // find the uniforms locations in either the vertex or fragment uniform array + if ( r_useUniformArrays.GetBool() ) { + uniforms.Clear(); + + idLexer src( programUniforms, programUniforms.Length(), "uniforms" ); + idToken token; + while ( src.ReadToken( &token ) ) { + int index = -1; + for ( int i = 0; i < RENDERPARM_TOTAL && index == -1; i++ ) { + const char * parmName = GetGLSLParmName( i ); + if ( token == parmName ) { + index = i; + } + } + for ( int i = 0; i < MAX_GLSL_USER_PARMS && index == -1; i++ ) { + const char * parmName = GetGLSLParmName( RENDERPARM_USER + i ); + if ( token == parmName ) { + index = RENDERPARM_USER + i; + } + } + if ( index == -1 ) { + idLib::Error( "couldn't find uniform %s for %s", token.c_str(), outFileGLSL.c_str() ); + } + uniforms.Append( index ); + } + } + + // create and compile the shader + const GLuint shader = qglCreateShader( target ); + if ( shader ) { + const char * source[1] = { programGLSL.c_str() }; + + qglShaderSource( shader, 1, source, NULL ); + qglCompileShader( shader ); + + int infologLength = 0; + qglGetShaderiv( shader, GL_INFO_LOG_LENGTH, &infologLength ); + if ( infologLength > 1 ) { + idTempArray infoLog( infologLength ); + int charsWritten = 0; + qglGetShaderInfoLog( shader, infologLength, &charsWritten, infoLog.Ptr() ); + + // catch the strings the ATI and Intel drivers output on success + if ( strstr( infoLog.Ptr(), "successfully compiled to run on hardware" ) != NULL || + strstr( infoLog.Ptr(), "No errors." ) != NULL ) { + //idLib::Printf( "%s program %s from %s compiled to run on hardware\n", typeName, GetName(), GetFileName() ); + } else { + idLib::Printf( "While compiling %s program %s\n", ( target == GL_FRAGMENT_SHADER ) ? "fragment" : "vertex" , inFile.c_str() ); + + const char separator = '\n'; + idList lines; + lines.Clear(); + idStr source( programGLSL ); + lines.Append( source ); + for ( int index = 0, ofs = lines[index].Find( separator ); ofs != -1; index++, ofs = lines[index].Find( separator ) ) { + lines.Append( lines[index].c_str() + ofs + 1 ); + lines[index].CapLength( ofs ); + } + + idLib::Printf( "-----------------\n" ); + for ( int i = 0; i < lines.Num(); i++ ) { + idLib::Printf( "%3d: %s\n", i+1, lines[i].c_str() ); + } + idLib::Printf( "-----------------\n" ); + + idLib::Printf( "%s\n", infoLog.Ptr() ); + } + } + + GLint compiled = GL_FALSE; + qglGetShaderiv( shader, GL_COMPILE_STATUS, &compiled ); + if ( compiled == GL_FALSE ) { + qglDeleteShader( shader ); + return INVALID_PROGID; + } + } + + return shader; +} +/* +================================================================================================ +idRenderProgManager::FindGLSLProgram +================================================================================================ +*/ +int idRenderProgManager::FindGLSLProgram( const char * name, int vIndex, int fIndex ) { + + for ( int i = 0; i < glslPrograms.Num(); ++i ) { + if ( ( glslPrograms[i].vertexShaderIndex == vIndex ) && ( glslPrograms[i].fragmentShaderIndex == fIndex ) ) { + LoadGLSLProgram( i, vIndex, fIndex ); + return i; + } + } + + glslProgram_t program; + program.name = name; + int index = glslPrograms.Append( program ); + LoadGLSLProgram( index, vIndex, fIndex ); + return index; +} + +/* +================================================================================================ +idRenderProgManager::GetGLSLParmName +================================================================================================ +*/ +const char* idRenderProgManager::GetGLSLParmName( int rp ) const { + if ( rp >= RENDERPARM_USER ) { + int userParmIndex = rp - RENDERPARM_USER; + return va("rpUser%d", userParmIndex ); + } + assert( rp < RENDERPARM_TOTAL ); + return GLSLParmNames[ rp ]; +} + +/* +================================================================================================ +idRenderProgManager::SetUniformValue +================================================================================================ +*/ +void idRenderProgManager::SetUniformValue( const renderParm_t rp, const float * value ) { + for ( int i = 0; i < 4; i++ ) { + glslUniforms[rp][i] = value[i]; + } +} + +/* +================================================================================================ +idRenderProgManager::CommitUnforms +================================================================================================ +*/ +void idRenderProgManager::CommitUniforms() { + const int progID = GetGLSLCurrentProgram(); + const glslProgram_t & prog = glslPrograms[progID]; + + if ( r_useUniformArrays.GetBool() ) { + ALIGNTYPE16 idVec4 localVectors[RENDERPARM_USER + MAX_GLSL_USER_PARMS]; + + if ( prog.vertexShaderIndex >= 0 ) { + const idList & vertexUniforms = vertexShaders[prog.vertexShaderIndex].uniforms; + if ( prog.vertexUniformArray != -1 && vertexUniforms.Num() > 0 ) { + for ( int i = 0; i < vertexUniforms.Num(); i++ ) { + localVectors[i] = glslUniforms[vertexUniforms[i]]; + } + qglUniform4fv( prog.vertexUniformArray, vertexUniforms.Num(), localVectors->ToFloatPtr() ); + } + } + + if ( prog.fragmentShaderIndex >= 0 ) { + const idList & fragmentUniforms = fragmentShaders[prog.fragmentShaderIndex].uniforms; + if ( prog.fragmentUniformArray != -1 && fragmentUniforms.Num() > 0 ) { + for ( int i = 0; i < fragmentUniforms.Num(); i++ ) { + localVectors[i] = glslUniforms[fragmentUniforms[i]]; + } + qglUniform4fv( prog.fragmentUniformArray, fragmentUniforms.Num(), localVectors->ToFloatPtr() ); + } + } + } else { + for ( int i = 0; i < prog.uniformLocations.Num(); i++ ) { + const glslUniformLocation_t & uniformLocation = prog.uniformLocations[i]; + qglUniform4fv( uniformLocation.uniformIndex, 1, glslUniforms[uniformLocation.parmIndex].ToFloatPtr() ); + } + } +} + +class idSort_QuickUniforms : public idSort_Quick< glslUniformLocation_t, idSort_QuickUniforms > { +public: + int Compare( const glslUniformLocation_t & a, const glslUniformLocation_t & b ) const { return a.uniformIndex - b.uniformIndex; } +}; + +/* +================================================================================================ +idRenderProgManager::LoadGLSLProgram +================================================================================================ +*/ +void idRenderProgManager::LoadGLSLProgram( const int programIndex, const int vertexShaderIndex, const int fragmentShaderIndex ) { + glslProgram_t & prog = glslPrograms[programIndex]; + + if ( prog.progId != INVALID_PROGID ) { + return; // Already loaded + } + + GLuint vertexProgID = ( vertexShaderIndex != -1 ) ? vertexShaders[ vertexShaderIndex ].progId : INVALID_PROGID; + GLuint fragmentProgID = ( fragmentShaderIndex != -1 ) ? fragmentShaders[ fragmentShaderIndex ].progId : INVALID_PROGID; + + const GLuint program = qglCreateProgram(); + if ( program ) { + + if ( vertexProgID != INVALID_PROGID ) { + qglAttachShader( program, vertexProgID ); + } + + if ( fragmentProgID != INVALID_PROGID ) { + qglAttachShader( program, fragmentProgID ); + } + + // bind vertex attribute locations + for ( int i = 0; attribsPC[i].glsl != NULL; i++ ) { + if ( ( attribsPC[i].flags & AT_VS_IN ) != 0 ) { + qglBindAttribLocation( program, attribsPC[i].bind, attribsPC[i].glsl ); + } + } + + qglLinkProgram( program ); + + int infologLength = 0; + qglGetProgramiv( program, GL_INFO_LOG_LENGTH, &infologLength ); + if ( infologLength > 1 ) { + char * infoLog = (char *)malloc( infologLength ); + int charsWritten = 0; + qglGetProgramInfoLog( program, infologLength, &charsWritten, infoLog ); + + // catch the strings the ATI and Intel drivers output on success + if ( strstr( infoLog, "Vertex shader(s) linked, fragment shader(s) linked." ) != NULL || strstr( infoLog, "No errors." ) != NULL ) { + //idLib::Printf( "render prog %s from %s linked\n", GetName(), GetFileName() ); + } else { + idLib::Printf( "While linking GLSL program %d with vertexShader %s and fragmentShader %s\n", + programIndex, + ( vertexShaderIndex >= 0 ) ? vertexShaders[vertexShaderIndex].name.c_str() : "", + ( fragmentShaderIndex >= 0 ) ? fragmentShaders[ fragmentShaderIndex ].name.c_str() : "" ); + idLib::Printf( "%s\n", infoLog ); + } + + free( infoLog ); + } + } + + int linked = GL_FALSE; + qglGetProgramiv( program, GL_LINK_STATUS, &linked ); + if ( linked == GL_FALSE ) { + qglDeleteProgram( program ); + idLib::Error( "While linking GLSL program %d with vertexShader %s and fragmentShader %s\n", + programIndex, + ( vertexShaderIndex >= 0 ) ? vertexShaders[vertexShaderIndex].name.c_str() : "", + ( fragmentShaderIndex >= 0 ) ? fragmentShaders[ fragmentShaderIndex ].name.c_str() : "" ); + return; + } + + if ( r_useUniformArrays.GetBool() ) { + prog.vertexUniformArray = qglGetUniformLocation( program, VERTEX_UNIFORM_ARRAY_NAME ); + prog.fragmentUniformArray = qglGetUniformLocation( program, FRAGMENT_UNIFORM_ARRAY_NAME ); + + assert( prog.vertexUniformArray != -1 || vertexShaderIndex < 0 || vertexShaders[vertexShaderIndex].uniforms.Num() == 0 ); + assert( prog.fragmentUniformArray != -1 || fragmentShaderIndex < 0 || fragmentShaders[fragmentShaderIndex].uniforms.Num() == 0 ); + } else { + // store the uniform locations after we have linked the GLSL program + prog.uniformLocations.Clear(); + for ( int i = 0; i < RENDERPARM_TOTAL; i++ ) { + const char * parmName = GetGLSLParmName( i ); + GLint loc = qglGetUniformLocation( program, parmName ); + if ( loc != -1 ) { + glslUniformLocation_t uniformLocation; + uniformLocation.parmIndex = i; + uniformLocation.uniformIndex = loc; + prog.uniformLocations.Append( uniformLocation ); + } + } + + // store the USER uniform locations + for ( int i = 0; i < MAX_GLSL_USER_PARMS; i++ ) { + const char * parmName = GetGLSLParmName( RENDERPARM_USER + i ); + GLint loc = qglGetUniformLocation( program, parmName ); + if ( loc != -1 ) { + glslUniformLocation_t uniformLocation; + uniformLocation.parmIndex = RENDERPARM_USER + i; + uniformLocation.uniformIndex = loc; + prog.uniformLocations.Append( uniformLocation ); + } + } + + // sort the uniforms based on index + prog.uniformLocations.SortWithTemplate( idSort_QuickUniforms() ); + } + + // get the uniform buffer binding for skinning joint matrices + GLint blockIndex = qglGetUniformBlockIndex( program, "matrices_ubo" ); + if ( blockIndex != -1 ) { + qglUniformBlockBinding( program, blockIndex, 0 ); + } + + // set the texture unit locations once for the render program. We only need to do this once since we only link the program once + qglUseProgram( program ); + for ( int i = 0; i < MAX_PROG_TEXTURE_PARMS; ++i ) { + GLint loc = qglGetUniformLocation( program, va( "samp%d", i ) ); + if ( loc != -1 ) { + qglUniform1i( loc, i ); + } + } + + idStr programName = vertexShaders[ vertexShaderIndex ].name; + programName.StripFileExtension(); + prog.name = programName; + prog.progId = program; + prog.fragmentShaderIndex = fragmentShaderIndex; + prog.vertexShaderIndex = vertexShaderIndex; +} + +/* +================================================================================================ +idRenderProgManager::ZeroUniforms +================================================================================================ +*/ +void idRenderProgManager::ZeroUniforms() { + memset( glslUniforms.Ptr(), 0, glslUniforms.Allocated() ); +} + diff --git a/neo/renderer/RenderSystem.cpp b/neo/renderer/RenderSystem.cpp new file mode 100644 index 00000000..2a0e9944 --- /dev/null +++ b/neo/renderer/RenderSystem.cpp @@ -0,0 +1,1024 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +idRenderSystemLocal tr; +idRenderSystem * renderSystem = &tr; + +/* +===================== +R_PerformanceCounters + +This prints both front and back end counters, so it should +only be called when the back end thread is idle. +===================== +*/ +static void R_PerformanceCounters() { + if ( r_showPrimitives.GetInteger() != 0 ) { + common->Printf( "views:%i draws:%i tris:%i (shdw:%i)\n", + tr.pc.c_numViews, + backEnd.pc.c_drawElements + backEnd.pc.c_shadowElements, + ( backEnd.pc.c_drawIndexes + backEnd.pc.c_shadowIndexes ) / 3, + backEnd.pc.c_shadowIndexes / 3 + ); + } + + if ( r_showDynamic.GetBool() ) { + common->Printf( "callback:%i md5:%i dfrmVerts:%i dfrmTris:%i tangTris:%i guis:%i\n", + tr.pc.c_entityDefCallbacks, + tr.pc.c_generateMd5, + tr.pc.c_deformedVerts, + tr.pc.c_deformedIndexes/3, + tr.pc.c_tangentIndexes/3, + tr.pc.c_guiSurfs + ); + } + + if ( r_showCull.GetBool() ) { + common->Printf( "%i box in %i box out\n", + tr.pc.c_box_cull_in, tr.pc.c_box_cull_out ); + } + + if ( r_showAddModel.GetBool() ) { + common->Printf( "callback:%i createInteractions:%i createShadowVolumes:%i\n", + tr.pc.c_entityDefCallbacks, tr.pc.c_createInteractions, tr.pc.c_createShadowVolumes ); + common->Printf( "viewEntities:%i shadowEntities:%i viewLights:%i\n", tr.pc.c_visibleViewEntities, + tr.pc.c_shadowViewEntities, tr.pc.c_viewLights ); + } + if ( r_showUpdates.GetBool() ) { + common->Printf( "entityUpdates:%i entityRefs:%i lightUpdates:%i lightRefs:%i\n", + tr.pc.c_entityUpdates, tr.pc.c_entityReferences, + tr.pc.c_lightUpdates, tr.pc.c_lightReferences ); + } + if ( r_showMemory.GetBool() ) { + common->Printf( "frameData: %i (%i)\n", frameData->frameMemoryAllocated.GetValue(), frameData->highWaterAllocated ); + } + + memset( &tr.pc, 0, sizeof( tr.pc ) ); + memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + +/* +==================== +RenderCommandBuffers +==================== +*/ +void idRenderSystemLocal::RenderCommandBuffers( const emptyCommand_t * const cmdHead ) { + // if there isn't a draw view command, do nothing to avoid swapping a bad frame + bool hasView = false; + for ( const emptyCommand_t * cmd = cmdHead ; cmd ; cmd = (const emptyCommand_t *)cmd->next ) { + if ( cmd->commandId == RC_DRAW_VIEW_3D || cmd->commandId == RC_DRAW_VIEW_GUI ) { + hasView = true; + break; + } + } + if ( !hasView ) { + return; + } + + // r_skipBackEnd allows the entire time of the back end + // to be removed from performance measurements, although + // nothing will be drawn to the screen. If the prints + // are going to a file, or r_skipBackEnd is later disabled, + // usefull data can be received. + + // r_skipRender is usually more usefull, because it will still + // draw 2D graphics + if ( !r_skipBackEnd.GetBool() ) { + if ( glConfig.timerQueryAvailable ) { + if ( tr.timerQueryId == 0 ) { + qglGenQueriesARB( 1, & tr.timerQueryId ); + } + qglBeginQueryARB( GL_TIME_ELAPSED_EXT, tr.timerQueryId ); + RB_ExecuteBackEndCommands( cmdHead ); + qglEndQueryARB( GL_TIME_ELAPSED_EXT ); + qglFlush(); + } else { + RB_ExecuteBackEndCommands( cmdHead ); + } + } + + // pass in null for now - we may need to do some map specific hackery in the future + resolutionScale.InitForMap( NULL ); +} + +/* +============ +R_GetCommandBuffer + +Returns memory for a command buffer (stretchPicCommand_t, +drawSurfsCommand_t, etc) and links it to the end of the +current command chain. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + emptyCommand_t *cmd; + + cmd = (emptyCommand_t *)R_FrameAlloc( bytes, FRAME_ALLOC_DRAW_COMMAND ); + cmd->next = NULL; + frameData->cmdTail->next = &cmd->commandId; + frameData->cmdTail = cmd; + + return (void *)cmd; +} + +/* +================= +R_ViewStatistics +================= +*/ +static void R_ViewStatistics( viewDef_t *parms ) { + // report statistics about this view + if ( !r_showSurfaces.GetBool() ) { + return; + } + common->Printf( "view:%p surfs:%i\n", parms, parms->numDrawSurfs ); +} + +/* +============= +R_AddDrawViewCmd + +This is the main 3D rendering command. A single scene may +have multiple views if a mirror, portal, or dynamic texture is present. +============= +*/ +void R_AddDrawViewCmd( viewDef_t *parms, bool guiOnly ) { + drawSurfsCommand_t *cmd; + + cmd = (drawSurfsCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = ( guiOnly ) ? RC_DRAW_VIEW_GUI : RC_DRAW_VIEW_3D; + + cmd->viewDef = parms; + + tr.pc.c_numViews++; + + R_ViewStatistics( parms ); +} + +/* +============= +R_AddPostProcess + +This issues the command to do a post process after all the views have +been rendered. +============= +*/ +void R_AddDrawPostProcess( viewDef_t * parms ) { + postProcessCommand_t * cmd = (postProcessCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = RC_POST_PROCESS; + cmd->viewDef = parms; +} + + +//================================================================================= + + +/* +============= +R_CheckCvars + +See if some cvars that we watch have changed +============= +*/ +static void R_CheckCvars() { + + // gamma stuff + if ( r_gamma.IsModified() || r_brightness.IsModified() ) { + r_gamma.ClearModified(); + r_brightness.ClearModified(); + R_SetColorMappings(); + } + + // filtering + if ( r_maxAnisotropicFiltering.IsModified() || r_useTrilinearFiltering.IsModified() || r_lodBias.IsModified() ) { + idLib::Printf( "Updating texture filter parameters.\n" ); + r_maxAnisotropicFiltering.ClearModified(); + r_useTrilinearFiltering.ClearModified(); + r_lodBias.ClearModified(); + for ( int i = 0 ; i < globalImages->images.Num() ; i++ ) { + if ( globalImages->images[i] ) { + globalImages->images[i]->Bind(); + globalImages->images[i]->SetTexParameters(); + } + } + } + + extern idCVar r_useSeamlessCubeMap; + if ( r_useSeamlessCubeMap.IsModified() ) { + r_useSeamlessCubeMap.ClearModified(); + if ( glConfig.seamlessCubeMapAvailable ) { + if ( r_useSeamlessCubeMap.GetBool() ) { + qglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + } else { + qglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + } + } + } + + extern idCVar r_useSRGB; + if ( r_useSRGB.IsModified() ) { + r_useSRGB.ClearModified(); + if ( glConfig.sRGBFramebufferAvailable ) { + if ( r_useSRGB.GetBool() ) { + qglEnable( GL_FRAMEBUFFER_SRGB ); + } else { + qglDisable( GL_FRAMEBUFFER_SRGB ); + } + } + } + + + if ( r_multiSamples.IsModified() ) { + if ( r_multiSamples.GetInteger() > 0 ) { + qglEnable( GL_MULTISAMPLE_ARB ); + } else { + qglDisable( GL_MULTISAMPLE_ARB ); + } + } + + // check for changes to logging state + GLimp_EnableLogging( r_logFile.GetInteger() != 0 ); +} + +/* +============= +idRenderSystemLocal::idRenderSystemLocal +============= +*/ +idRenderSystemLocal::idRenderSystemLocal() : + unitSquareTriangles( NULL ), + zeroOneCubeTriangles( NULL ), + testImageTriangles( NULL ) { + Clear(); +} + +/* +============= +idRenderSystemLocal::~idRenderSystemLocal +============= +*/ +idRenderSystemLocal::~idRenderSystemLocal() { +} + +/* +============= +idRenderSystemLocal::SetColor +============= +*/ +void idRenderSystemLocal::SetColor( const idVec4 & rgba ) { + currentColorNativeBytesOrder = LittleLong( PackColor( rgba ) ); +} + +/* +============= +idRenderSystemLocal::GetColor +============= +*/ +uint32 idRenderSystemLocal::GetColor() { + return LittleLong( currentColorNativeBytesOrder ); +} + +/* +============= +idRenderSystemLocal::SetGLState +============= +*/ +void idRenderSystemLocal::SetGLState( const uint64 glState ) { + currentGLState = glState; +} + +/* +============= +idRenderSystemLocal::DrawFilled +============= +*/ +void idRenderSystemLocal::DrawFilled( const idVec4 & color, float x, float y, float w, float h ) { + SetColor( color ); + DrawStretchPic( x, y, w, h, 0.0f, 0.0f, 1.0f, 1.0f, whiteMaterial ); +} + +/* +============= +idRenderSystemLocal::DrawStretchPic +============= +*/ +void idRenderSystemLocal::DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) { + DrawStretchPic( idVec4( x, y, s1, t1 ), idVec4( x+w, y, s2, t1 ), idVec4( x+w, y+h, s2, t2 ), idVec4( x, y+h, s1, t2 ), material ); +} + +/* +============= +idRenderSystemLocal::DrawStretchPic +============= +*/ +static triIndex_t quadPicIndexes[6] = { 3, 0, 2, 2, 0, 1 }; +void idRenderSystemLocal::DrawStretchPic( const idVec4 & topLeft, const idVec4 & topRight, const idVec4 & bottomRight, const idVec4 & bottomLeft, const idMaterial * material ) { + if ( !R_IsInitialized() ) { + return; + } + if ( material == NULL ) { + return; + } + + idDrawVert * verts = guiModel->AllocTris( 4, quadPicIndexes, 6, material, currentGLState, STEREO_DEPTH_TYPE_NONE ); + if ( verts == NULL ) { + return; + } + + ALIGNTYPE16 idDrawVert localVerts[4]; + + localVerts[0].Clear(); + localVerts[0].xyz[0] = topLeft.x; + localVerts[0].xyz[1] = topLeft.y; + localVerts[0].SetTexCoord( topLeft.z, topLeft.w ); + localVerts[0].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[0].ClearColor2(); + + localVerts[1].Clear(); + localVerts[1].xyz[0] = topRight.x; + localVerts[1].xyz[1] = topRight.y; + localVerts[1].SetTexCoord( topRight.z, topRight.w ); + localVerts[1].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[1].ClearColor2(); + + localVerts[2].Clear(); + localVerts[2].xyz[0] = bottomRight.x; + localVerts[2].xyz[1] = bottomRight.y; + localVerts[2].SetTexCoord( bottomRight.z, bottomRight.w ); + localVerts[2].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[2].ClearColor2(); + + localVerts[3].Clear(); + localVerts[3].xyz[0] = bottomLeft.x; + localVerts[3].xyz[1] = bottomLeft.y; + localVerts[3].SetTexCoord( bottomLeft.z, bottomLeft.w ); + localVerts[3].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[3].ClearColor2(); + + WriteDrawVerts16( verts, localVerts, 4 ); +} + +/* +============= +idRenderSystemLocal::DrawStretchTri +============= +*/ +void idRenderSystemLocal::DrawStretchTri( const idVec2 & p1, const idVec2 & p2, const idVec2 & p3, const idVec2 & t1, const idVec2 & t2, const idVec2 & t3, const idMaterial *material ) { + if ( !R_IsInitialized() ) { + return; + } + if ( material == NULL ) { + return; + } + + triIndex_t tempIndexes[3] = { 1, 0, 2 }; + + idDrawVert * verts = guiModel->AllocTris( 3, tempIndexes, 3, material, currentGLState, STEREO_DEPTH_TYPE_NONE ); + if ( verts == NULL ) { + return; + } + + ALIGNTYPE16 idDrawVert localVerts[3]; + + localVerts[0].Clear(); + localVerts[0].xyz[0] = p1.x; + localVerts[0].xyz[1] = p1.y; + localVerts[0].SetTexCoord( t1 ); + localVerts[0].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[0].ClearColor2(); + + localVerts[1].Clear(); + localVerts[1].xyz[0] = p2.x; + localVerts[1].xyz[1] = p2.y; + localVerts[1].SetTexCoord( t2 ); + localVerts[1].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[1].ClearColor2(); + + localVerts[2].Clear(); + localVerts[2].xyz[0] = p3.x; + localVerts[2].xyz[1] = p3.y; + localVerts[2].SetTexCoord( t3 ); + localVerts[2].SetNativeOrderColor( currentColorNativeBytesOrder ); + localVerts[2].ClearColor2(); + + WriteDrawVerts16( verts, localVerts, 3 ); +} + +/* +============= +idRenderSystemLocal::AllocTris +============= +*/ +idDrawVert * idRenderSystemLocal::AllocTris( int numVerts, const triIndex_t * indexes, int numIndexes, const idMaterial * material, const stereoDepthType_t stereoType ) { + return guiModel->AllocTris( numVerts, indexes, numIndexes, material, currentGLState, stereoType ); +} + +/* +===================== +idRenderSystemLocal::DrawSmallChar + +small chars are drawn at native screen resolution +===================== +*/ +void idRenderSystemLocal::DrawSmallChar( int x, int y, int ch ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -SMALLCHAR_HEIGHT ) { + return; + } + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625f; + fcol = col * 0.0625f; + size = 0.0625f; + + DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + charSetMaterial ); +} + +/* +================== +idRenderSystemLocal::DrawSmallStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void idRenderSystemLocal::DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor ) { + idVec4 color; + const unsigned char *s; + int xx; + + // draw the colored text + s = (const unsigned char*)string; + xx = x; + SetColor( setColor ); + while ( *s ) { + if ( idStr::IsColor( (const char*)s ) ) { + if ( !forceColor ) { + if ( *(s+1) == C_COLOR_DEFAULT ) { + SetColor( setColor ); + } else { + color = idStr::ColorForIndex( *(s+1) ); + color[3] = setColor[3]; + SetColor( color ); + } + } + s += 2; + continue; + } + DrawSmallChar( xx, y, *s ); + xx += SMALLCHAR_WIDTH; + s++; + } + SetColor( colorWhite ); +} + +/* +===================== +idRenderSystemLocal::DrawBigChar +===================== +*/ +void idRenderSystemLocal::DrawBigChar( int x, int y, int ch ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -BIGCHAR_HEIGHT ) { + return; + } + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625f; + fcol = col * 0.0625f; + size = 0.0625f; + + DrawStretchPic( x, y, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + charSetMaterial ); +} + +/* +================== +idRenderSystemLocal::DrawBigStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void idRenderSystemLocal::DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor ) { + idVec4 color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + SetColor( setColor ); + while ( *s ) { + if ( idStr::IsColor( s ) ) { + if ( !forceColor ) { + if ( *(s+1) == C_COLOR_DEFAULT ) { + SetColor( setColor ); + } else { + color = idStr::ColorForIndex( *(s+1) ); + color[3] = setColor[3]; + SetColor( color ); + } + } + s += 2; + continue; + } + DrawBigChar( xx, y, *s ); + xx += BIGCHAR_WIDTH; + s++; + } + SetColor( colorWhite ); +} + +//====================================================================================== + +/* +==================== +idRenderSystemLocal::SwapCommandBuffers + +Performs final closeout of any gui models being defined. + +Waits for the previous GPU rendering to complete and vsync. + +Returns the head of the linked command list that was just closed off. + +Returns timing information from the previous frame. + +After this is called, new command buffers can be built up in parallel +with the rendering of the closed off command buffers by RenderCommandBuffers() +==================== +*/ +const emptyCommand_t * idRenderSystemLocal::SwapCommandBuffers( + uint64 * frontEndMicroSec, + uint64 * backEndMicroSec, + uint64 * shadowMicroSec, + uint64 * gpuMicroSec ) { + + SwapCommandBuffers_FinishRendering( frontEndMicroSec, backEndMicroSec, shadowMicroSec, gpuMicroSec ); + + return SwapCommandBuffers_FinishCommandBuffers(); +} + +/* +===================== +idRenderSystemLocal::SwapCommandBuffers_FinishRendering +===================== +*/ +void idRenderSystemLocal::SwapCommandBuffers_FinishRendering( + uint64 * frontEndMicroSec, + uint64 * backEndMicroSec, + uint64 * shadowMicroSec, + uint64 * gpuMicroSec ) { + SCOPED_PROFILE_EVENT( "SwapCommandBuffers" ); + + if ( gpuMicroSec != NULL ) { + *gpuMicroSec = 0; // until shown otherwise + } + + if ( !R_IsInitialized() ) { + return; + } + + + // After coming back from an autoswap, we won't have anything to render + if ( frameData->cmdHead->next != NULL ) { + // wait for our fence to hit, which means the swap has actually happened + // We must do this before clearing any resources the GPU may be using + void GL_BlockingSwapBuffers(); + GL_BlockingSwapBuffers(); + } + + // read back the start and end timer queries from the previous frame + if ( glConfig.timerQueryAvailable ) { + uint64 drawingTimeNanoseconds = 0; + if ( tr.timerQueryId != 0 ) { + qglGetQueryObjectui64vEXT( tr.timerQueryId, GL_QUERY_RESULT, &drawingTimeNanoseconds ); + } + if ( gpuMicroSec != NULL ) { + *gpuMicroSec = drawingTimeNanoseconds / 1000; + } + } + + //------------------------------ + + // save out timing information + if ( frontEndMicroSec != NULL ) { + *frontEndMicroSec = pc.frontEndMicroSec; + } + if ( backEndMicroSec != NULL ) { + *backEndMicroSec = backEnd.pc.totalMicroSec; + } + if ( shadowMicroSec != NULL ) { + *shadowMicroSec = backEnd.pc.shadowMicroSec; + } + + // print any other statistics and clear all of them + R_PerformanceCounters(); + + // check for dynamic changes that require some initialization + R_CheckCvars(); + + // check for errors + GL_CheckErrors(); +} + +/* +===================== +idRenderSystemLocal::SwapCommandBuffers_FinishCommandBuffers +===================== +*/ +const emptyCommand_t * idRenderSystemLocal::SwapCommandBuffers_FinishCommandBuffers() { + if ( !R_IsInitialized() ) { + return NULL; + } + + // close any gui drawing + guiModel->EmitFullScreen(); + guiModel->Clear(); + + // unmap the buffer objects so they can be used by the GPU + vertexCache.BeginBackEnd(); + + // save off this command buffer + const emptyCommand_t * commandBufferHead = frameData->cmdHead; + + // copy the code-used drawsurfs that were + // allocated at the start of the buffer memory to the backEnd referenced locations + backEnd.unitSquareSurface = tr.unitSquareSurface_; + backEnd.zeroOneCubeSurface = tr.zeroOneCubeSurface_; + backEnd.testImageSurface = tr.testImageSurface_; + + // use the other buffers next frame, because another CPU + // may still be rendering into the current buffers + R_ToggleSmpFrame(); + + // possibly change the stereo3D mode + // PC + if ( glConfig.nativeScreenWidth == 1280 && glConfig.nativeScreenHeight == 1470 ) { + glConfig.stereo3Dmode = STEREO3D_HDMI_720; + } else { + glConfig.stereo3Dmode = GetStereoScopicRenderingMode(); + } + + // prepare the new command buffer + guiModel->BeginFrame(); + + //------------------------------ + // Make sure that geometry used by code is present in the buffer cache. + // These use frame buffer cache (not static) because they may be used during + // map loads. + // + // It is important to do this first, so if the buffers overflow during + // scene generation, the basic surfaces needed for drawing the buffers will + // always be present. + //------------------------------ + R_InitDrawSurfFromTri( tr.unitSquareSurface_, *tr.unitSquareTriangles ); + R_InitDrawSurfFromTri( tr.zeroOneCubeSurface_, *tr.zeroOneCubeTriangles ); + R_InitDrawSurfFromTri( tr.testImageSurface_, *tr.testImageTriangles ); + + // Reset render crop to be the full screen + renderCrops[0].x1 = 0; + renderCrops[0].y1 = 0; + renderCrops[0].x2 = GetWidth() - 1; + renderCrops[0].y2 = GetHeight() - 1; + currentRenderCrop = 0; + + // this is the ONLY place this is modified + frameCount++; + + // just in case we did a common->Error while this + // was set + guiRecursionLevel = 0; + + // the first rendering will be used for commands like + // screenshot, rather than a possible subsequent remote + // or mirror render +// primaryWorld = NULL; + + // set the time for shader effects in 2D rendering + frameShaderTime = Sys_Milliseconds() * 0.001; + + setBufferCommand_t * cmd2 = (setBufferCommand_t *)R_GetCommandBuffer( sizeof( *cmd2 ) ); + cmd2->commandId = RC_SET_BUFFER; + cmd2->buffer = (int)GL_BACK; + + // the old command buffer can now be rendered, while the new one can + // be built in parallel + return commandBufferHead; +} + +/* +===================== +idRenderSystemLocal::WriteDemoPics +===================== +*/ +void idRenderSystemLocal::WriteDemoPics() { + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_GUI_MODEL ); +} + +/* +===================== +idRenderSystemLocal::DrawDemoPics +===================== +*/ +void idRenderSystemLocal::DrawDemoPics() { +} + +/* +===================== +idRenderSystemLocal::GetCroppedViewport + +Returns the current cropped pixel coordinates +===================== +*/ +void idRenderSystemLocal::GetCroppedViewport( idScreenRect * viewport ) { + *viewport = renderCrops[currentRenderCrop]; +} + +/* +======================== +idRenderSystemLocal::PerformResolutionScaling + +The 3D rendering size can be smaller than the full window resolution to reduce +fill rate requirements while still allowing the GUIs to be full resolution. +In split screen mode the rendering size is also smaller. +======================== +*/ +void idRenderSystemLocal::PerformResolutionScaling( int& newWidth, int& newHeight ) { + + float xScale = 1.0f; + float yScale = 1.0f; + resolutionScale.GetCurrentResolutionScale( xScale, yScale ); + + newWidth = idMath::Ftoi( GetWidth() * xScale ); + newHeight = idMath::Ftoi( GetHeight() * yScale ); +} + +/* +================ +idRenderSystemLocal::CropRenderSize +================ +*/ +void idRenderSystemLocal::CropRenderSize( int width, int height ) { + if ( !R_IsInitialized() ) { + return; + } + + // close any gui drawing before changing the size + guiModel->EmitFullScreen(); + guiModel->Clear(); + + + if ( width < 1 || height < 1 ) { + common->Error( "CropRenderSize: bad sizes" ); + } + + if ( common->WriteDemo() ) { + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_CROP_RENDER ); + common->WriteDemo()->WriteInt( width ); + common->WriteDemo()->WriteInt( height ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_CROP_RENDER\n" ); + } + } + + idScreenRect & previous = renderCrops[currentRenderCrop]; + + currentRenderCrop++; + + idScreenRect & current = renderCrops[currentRenderCrop]; + + current.x1 = previous.x1; + current.x2 = previous.x1 + width - 1; + current.y1 = previous.y2 - height + 1; + current.y2 = previous.y2; +} + +/* +================ +idRenderSystemLocal::UnCrop +================ +*/ +void idRenderSystemLocal::UnCrop() { + if ( !R_IsInitialized() ) { + return; + } + + if ( currentRenderCrop < 1 ) { + common->Error( "idRenderSystemLocal::UnCrop: currentRenderCrop < 1" ); + } + + // close any gui drawing + guiModel->EmitFullScreen(); + guiModel->Clear(); + + currentRenderCrop--; + + if ( common->WriteDemo() ) { + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_UNCROP_RENDER ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_UNCROP\n" ); + } + } +} + +/* +================ +idRenderSystemLocal::CaptureRenderToImage +================ +*/ +void idRenderSystemLocal::CaptureRenderToImage( const char *imageName, bool clearColorAfterCopy ) { + if ( !R_IsInitialized() ) { + return; + } + guiModel->EmitFullScreen(); + guiModel->Clear(); + + if ( common->WriteDemo() ) { + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_CAPTURE_RENDER ); + common->WriteDemo()->WriteHashString( imageName ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_CAPTURE_RENDER: %s\n", imageName ); + } + } + idImage * image = globalImages->GetImage( imageName ); + if ( image == NULL ) { + image = globalImages->AllocImage( imageName ); + } + + idScreenRect & rc = renderCrops[currentRenderCrop]; + + copyRenderCommand_t *cmd = (copyRenderCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = RC_COPY_RENDER; + cmd->x = rc.x1; + cmd->y = rc.y1; + cmd->imageWidth = rc.GetWidth(); + cmd->imageHeight = rc.GetHeight(); + cmd->image = image; + cmd->clearColorAfterCopy = clearColorAfterCopy; + + guiModel->Clear(); +} + +/* +============== +idRenderSystemLocal::CaptureRenderToFile +============== +*/ +void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) { + if ( !R_IsInitialized() ) { + return; + } + + idScreenRect & rc = renderCrops[currentRenderCrop]; + + guiModel->EmitFullScreen(); + guiModel->Clear(); + RenderCommandBuffers( frameData->cmdHead ); + + qglReadBuffer( GL_BACK ); + + // include extra space for OpenGL padding to word boundaries + int c = ( rc.GetWidth() + 3 ) * rc.GetHeight(); + byte *data = (byte *)R_StaticAlloc( c * 3 ); + + qglReadPixels( rc.x1, rc.y1, rc.GetWidth(), rc.GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, data ); + + byte *data2 = (byte *)R_StaticAlloc( c * 4 ); + + for ( int i = 0 ; i < c ; i++ ) { + data2[ i * 4 ] = data[ i * 3 ]; + data2[ i * 4 + 1 ] = data[ i * 3 + 1 ]; + data2[ i * 4 + 2 ] = data[ i * 3 + 2 ]; + data2[ i * 4 + 3 ] = 0xff; + } + + R_WriteTGA( fileName, data2, rc.GetWidth(), rc.GetHeight(), true ); + + R_StaticFree( data ); + R_StaticFree( data2 ); +} + + +/* +============== +idRenderSystemLocal::AllocRenderWorld +============== +*/ +idRenderWorld *idRenderSystemLocal::AllocRenderWorld() { + idRenderWorldLocal *rw; + rw = new (TAG_RENDER) idRenderWorldLocal; + worlds.Append( rw ); + return rw; +} + +/* +============== +idRenderSystemLocal::FreeRenderWorld +============== +*/ +void idRenderSystemLocal::FreeRenderWorld( idRenderWorld *rw ) { + if ( primaryWorld == rw ) { + primaryWorld = NULL; + } + worlds.Remove( static_cast(rw) ); + delete rw; +} + +/* +============== +idRenderSystemLocal::PrintMemInfo +============== +*/ +void idRenderSystemLocal::PrintMemInfo( MemInfo_t *mi ) { + // sum up image totals + globalImages->PrintMemInfo( mi ); + + // sum up model totals + renderModelManager->PrintMemInfo( mi ); + + // compute render totals + +} + +/* +=============== +idRenderSystemLocal::UploadImage +=============== +*/ +bool idRenderSystemLocal::UploadImage( const char *imageName, const byte *data, int width, int height ) { + idImage *image = globalImages->GetImage( imageName ); + if ( !image ) { + return false; + } + image->UploadScratch( data, width, height ); + return true; +} diff --git a/neo/renderer/RenderSystem.h b/neo/renderer/RenderSystem.h new file mode 100644 index 00000000..3d609abf --- /dev/null +++ b/neo/renderer/RenderSystem.h @@ -0,0 +1,331 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __RENDERER_H__ +#define __RENDERER_H__ + +/* +=============================================================================== + + idRenderSystem is responsible for managing the screen, which can have + multiple idRenderWorld and 2D drawing done on it. + +=============================================================================== +*/ +enum stereo3DMode_t { + STEREO3D_OFF, + + // half-resolution, non-square pixel views + STEREO3D_SIDE_BY_SIDE_COMPRESSED, + STEREO3D_TOP_AND_BOTTOM_COMPRESSED, + + // two full resolution views side by side, as for a dual cable display + STEREO3D_SIDE_BY_SIDE, + + STEREO3D_INTERLACED, + + // OpenGL quad buffer + STEREO3D_QUAD_BUFFER, + + // two full resolution views stacked with a 30 pixel guard band + // On the PC this can be configured as a custom video timing, but + // it definitely isn't a consumer level task. The quad_buffer + // support can handle 720P-3D with apropriate driver support. + STEREO3D_HDMI_720 +}; + +typedef enum { + AUTORENDER_DEFAULTICON = 0, + AUTORENDER_HELLICON, + AUTORENDER_DIALOGICON, + AUTORENDER_MAX +} autoRenderIconType_t ; + +enum stereoDepthType_t { + STEREO_DEPTH_TYPE_NONE, + STEREO_DEPTH_TYPE_NEAR, + STEREO_DEPTH_TYPE_MID, + STEREO_DEPTH_TYPE_FAR +}; + + +enum graphicsVendor_t { + VENDOR_NVIDIA, + VENDOR_AMD, + VENDOR_INTEL +}; + +// Contains variables specific to the OpenGL configuration being run right now. +// These are constant once the OpenGL subsystem is initialized. +struct glconfig_t { + const char * renderer_string; + const char * vendor_string; + const char * version_string; + const char * extensions_string; + const char * wgl_extensions_string; + const char * shading_language_string; + + float glVersion; // atof( version_string ) + graphicsVendor_t vendor; + + int maxTextureSize; // queried from GL + int maxTextureCoords; + int maxTextureImageUnits; + int uniformBufferOffsetAlignment; + float maxTextureAnisotropy; + + int colorBits; + int depthBits; + int stencilBits; + + bool multitextureAvailable; + bool directStateAccess; + bool textureCompressionAvailable; + bool anisotropicFilterAvailable; + bool textureLODBiasAvailable; + bool seamlessCubeMapAvailable; + bool sRGBFramebufferAvailable; + bool vertexBufferObjectAvailable; + bool mapBufferRangeAvailable; + bool vertexArrayObjectAvailable; + bool drawElementsBaseVertexAvailable; + bool fragmentProgramAvailable; + bool glslAvailable; + bool uniformBufferAvailable; + bool twoSidedStencilAvailable; + bool depthBoundsTestAvailable; + bool syncAvailable; + bool timerQueryAvailable; + bool occlusionQueryAvailable; + bool debugOutputAvailable; + bool swapControlTearAvailable; + + stereo3DMode_t stereo3Dmode; + int nativeScreenWidth; // this is the native screen width resolution of the renderer + int nativeScreenHeight; // this is the native screen height resolution of the renderer + + int displayFrequency; + + int isFullscreen; // monitor number + bool isStereoPixelFormat; + bool stereoPixelFormatAvailable; + int multisamples; + + // Screen separation for stereoscopic rendering is set based on this. + // PC vid code sets this, converting from diagonals / inches / whatever as needed. + // If the value can't be determined, set something reasonable, like 50cm. + float physicalScreenWidthInCentimeters; + + float pixelAspect; + + GLuint global_vao; +}; + + + +struct emptyCommand_t; + +bool R_IsInitialized(); + +const int SMALLCHAR_WIDTH = 8; +const int SMALLCHAR_HEIGHT = 16; +const int BIGCHAR_WIDTH = 16; +const int BIGCHAR_HEIGHT = 16; + +// all drawing is done to a 640 x 480 virtual screen size +// and will be automatically scaled to the real resolution +const int SCREEN_WIDTH = 640; +const int SCREEN_HEIGHT = 480; + +const int TITLESAFE_LEFT = 32; +const int TITLESAFE_RIGHT = 608; +const int TITLESAFE_TOP = 24; +const int TITLESAFE_BOTTOM = 456; +const int TITLESAFE_WIDTH = TITLESAFE_RIGHT - TITLESAFE_LEFT; +const int TITLESAFE_HEIGHT = TITLESAFE_BOTTOM - TITLESAFE_TOP; + +class idRenderWorld; + + +class idRenderSystem { +public: + + virtual ~idRenderSystem() {} + + // set up cvars and basic data structures, but don't + // init OpenGL, so it can also be used for dedicated servers + virtual void Init() = 0; + + // only called before quitting + virtual void Shutdown() = 0; + + virtual void ResetGuiModels() = 0; + + virtual void InitOpenGL() = 0; + + virtual void ShutdownOpenGL() = 0; + + virtual bool IsOpenGLRunning() const = 0; + + virtual bool IsFullScreen() const = 0; + virtual int GetWidth() const = 0; + virtual int GetHeight() const = 0; + + // return w/h of a single pixel. This will be 1.0 for normal cases. + // A side-by-side stereo 3D frame will have a pixel aspect of 0.5. + // A top-and-bottom stereo 3D frame will have a pixel aspect of 2.0 + virtual float GetPixelAspect() const = 0; + + // This is used to calculate stereoscopic screen offset for a given interocular distance. + virtual float GetPhysicalScreenWidthInCentimeters() const = 0; + + // GetWidth() / GetHeight() return the size of a single eye + // view, which may be replicated twice in a stereo display + virtual stereo3DMode_t GetStereo3DMode() const = 0; + virtual bool IsStereoScopicRenderingSupported() const = 0; + virtual stereo3DMode_t GetStereoScopicRenderingMode() const = 0; + virtual void EnableStereoScopicRendering( const stereo3DMode_t mode ) const = 0; + virtual bool HasQuadBufferSupport() const = 0; + + // allocate a renderWorld to be used for drawing + virtual idRenderWorld * AllocRenderWorld() = 0; + virtual void FreeRenderWorld( idRenderWorld * rw ) = 0; + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + virtual void BeginLevelLoad() = 0; + virtual void EndLevelLoad() = 0; + virtual void Preload( const idPreloadManifest &manifest, const char *mapName ) = 0; + virtual void LoadLevelImages() = 0; + + virtual void BeginAutomaticBackgroundSwaps( autoRenderIconType_t icon = AUTORENDER_DEFAULTICON ) = 0; + virtual void EndAutomaticBackgroundSwaps() = 0; + virtual bool AreAutomaticBackgroundSwapsRunning( autoRenderIconType_t * icon = NULL ) const = 0; + + // font support + virtual class idFont * RegisterFont( const char * fontName ) = 0; + virtual void ResetFonts() = 0; + + virtual void SetColor( const idVec4 & rgba ) = 0; + virtual void SetColor4( float r, float g, float b, float a ) { SetColor( idVec4( r, g, b, a ) ); } + + virtual uint32 GetColor() = 0; + + virtual void SetGLState( const uint64 glState ) = 0; + + virtual void DrawFilled( const idVec4 & color, float x, float y, float w, float h ) = 0; + virtual void DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) = 0; + void DrawStretchPic( const idVec4 & rect, const idVec4 & st, const idMaterial * material ) { DrawStretchPic( rect.x, rect.y, rect.z, rect.w, st.x, st.y, st.z, st.w, material ); } + virtual void DrawStretchPic( const idVec4 & topLeft, const idVec4 & topRight, const idVec4 & bottomRight, const idVec4 & bottomLeft, const idMaterial * material ) = 0; + virtual void DrawStretchTri ( const idVec2 & p1, const idVec2 & p2, const idVec2 & p3, const idVec2 & t1, const idVec2 & t2, const idVec2 & t3, const idMaterial *material ) = 0; + virtual idDrawVert * AllocTris( int numVerts, const triIndex_t * indexes, int numIndexes, const idMaterial * material, const stereoDepthType_t stereoType = STEREO_DEPTH_TYPE_NONE ) = 0; + + virtual void PrintMemInfo( MemInfo_t *mi ) = 0; + + virtual void DrawSmallChar( int x, int y, int ch ) = 0; + virtual void DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor ) = 0; + virtual void DrawBigChar( int x, int y, int ch ) = 0; + virtual void DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor ) = 0; + + // dump all 2D drawing so far this frame to the demo file + virtual void WriteDemoPics() = 0; + + // draw the 2D pics that were saved out with the current demo frame + virtual void DrawDemoPics() = 0; + + // Performs final closeout of any gui models being defined. + // + // Waits for the previous GPU rendering to complete and vsync. + // + // Returns the head of the linked command list that was just closed off. + // + // Returns timing information from the previous frame. + // + // After this is called, new command buffers can be built up in parallel + // with the rendering of the closed off command buffers by RenderCommandBuffers() + virtual const emptyCommand_t * SwapCommandBuffers( uint64 *frontEndMicroSec, uint64 *backEndMicroSec, uint64 *shadowMicroSec, uint64 *gpuMicroSec ) = 0; + + // SwapCommandBuffers operation can be split in two parts for non-smp rendering + // where the GPU is idled intentionally for minimal latency. + virtual void SwapCommandBuffers_FinishRendering( uint64 *frontEndMicroSec, uint64 *backEndMicroSec, uint64 *shadowMicroSec, uint64 *gpuMicroSec ) = 0; + virtual const emptyCommand_t * SwapCommandBuffers_FinishCommandBuffers() = 0; + + // issues GPU commands to render a built up list of command buffers returned + // by SwapCommandBuffers(). No references should be made to the current frameData, + // so new scenes and GUIs can be built up in parallel with the rendering. + virtual void RenderCommandBuffers( const emptyCommand_t * commandBuffers ) = 0; + + // aviDemo uses this. + // Will automatically tile render large screen shots if necessary + // Samples is the number of jittered frames for anti-aliasing + // If ref == NULL, common->UpdateScreen will be used + // This will perform swapbuffers, so it is NOT an approppriate way to + // generate image files that happen during gameplay, as for savegame + // markers. Use WriteRender() instead. + virtual void TakeScreenshot( int width, int height, const char *fileName, int samples, struct renderView_s *ref ) = 0; + + // the render output can be cropped down to a subset of the real screen, as + // for save-game reviews and split-screen multiplayer. Users of the renderer + // will not know the actual pixel size of the area they are rendering to + + // the x,y,width,height values are in virtual SCREEN_WIDTH / SCREEN_HEIGHT coordinates + + // to render to a texture, first set the crop size with makePowerOfTwo = true, + // then perform all desired rendering, then capture to an image + // if the specified physical dimensions are larger than the current cropped region, they will be cut down to fit + virtual void CropRenderSize( int width, int height ) = 0; + virtual void CaptureRenderToImage( const char *imageName, bool clearColorAfterCopy = false ) = 0; + // fixAlpha will set all the alpha channel values to 0xff, which allows screen captures + // to use the default tga loading code without having dimmed down areas in many places + virtual void CaptureRenderToFile( const char *fileName, bool fixAlpha = false ) = 0; + virtual void UnCrop() = 0; + + // the image has to be already loaded ( most straightforward way would be through a FindMaterial ) + // texture filter / mipmapping / repeat won't be modified by the upload + // returns false if the image wasn't found + virtual bool UploadImage( const char *imageName, const byte *data, int width, int height ) = 0; + + // consoles switch stereo 3D eye views each 60 hz frame + virtual int GetFrameCount() const = 0; +}; + +extern idRenderSystem * renderSystem; + +// +// functions mainly intended for editor and dmap integration +// + +// for use by dmap to do the carving-on-light-boundaries and for the editor for display +void R_LightProjectionMatrix( const idVec3 &origin, const idPlane &rearPlane, idVec4 mat[4] ); + +// used by the view shot taker +void R_ScreenshotFilename( int &lastNumber, const char *base, idStr &fileName ); + +#endif /* !__RENDERER_H__ */ diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp new file mode 100644 index 00000000..d759cd3c --- /dev/null +++ b/neo/renderer/RenderSystem_init.cpp @@ -0,0 +1,2504 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +// Vista OpenGL wrapper check +#include "../sys/win32/win_local.h" + +// DeviceContext bypasses RenderSystem to work directly with this +idGuiModel * tr_guiModel; + +// functions that are not called every frame +glconfig_t glConfig; + +idCVar r_requestStereoPixelFormat( "r_requestStereoPixelFormat", "1", CVAR_RENDERER, "Ask for a stereo GL pixel format on startup" ); +idCVar r_debugContext( "r_debugContext", "0", CVAR_RENDERER, "Enable various levels of context debug." ); +idCVar r_glDriver( "r_glDriver", "", CVAR_RENDERER, "\"opengl32\", etc." ); +idCVar r_skipIntelWorkarounds( "r_skipIntelWorkarounds", "0", CVAR_RENDERER | CVAR_BOOL, "skip workarounds for Intel driver bugs" ); +idCVar r_multiSamples( "r_multiSamples", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "number of antialiasing samples" ); +idCVar r_vidMode( "r_vidMode", "0", CVAR_ARCHIVE | CVAR_RENDERER | CVAR_INTEGER, "fullscreen video mode number" ); +idCVar r_displayRefresh( "r_displayRefresh", "0", CVAR_RENDERER | CVAR_INTEGER | CVAR_NOCHEAT, "optional display refresh rate option for vid mode", 0.0f, 240.0f ); +idCVar r_fullscreen( "r_fullscreen", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "0 = windowed, 1 = full screen on monitor 1, 2 = full screen on monitor 2, etc" ); +idCVar r_customWidth( "r_customWidth", "1280", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "custom screen width. set r_vidMode to -1 to activate" ); +idCVar r_customHeight( "r_customHeight", "720", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "custom screen height. set r_vidMode to -1 to activate" ); +idCVar r_windowX( "r_windowX", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Non-fullscreen parameter" ); +idCVar r_windowY( "r_windowY", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Non-fullscreen parameter" ); +idCVar r_windowWidth( "r_windowWidth", "1280", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Non-fullscreen parameter" ); +idCVar r_windowHeight( "r_windowHeight", "720", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Non-fullscreen parameter" ); + +idCVar r_useViewBypass( "r_useViewBypass", "1", CVAR_RENDERER | CVAR_INTEGER, "bypass a frame of latency to the view" ); +idCVar r_useLightPortalFlow( "r_useLightPortalFlow", "1", CVAR_RENDERER | CVAR_BOOL, "use a more precise area reference determination" ); +idCVar r_singleTriangle( "r_singleTriangle", "0", CVAR_RENDERER | CVAR_BOOL, "only draw a single triangle per primitive" ); +idCVar r_checkBounds( "r_checkBounds", "0", CVAR_RENDERER | CVAR_BOOL, "compare all surface bounds with precalculated ones" ); +idCVar r_useConstantMaterials( "r_useConstantMaterials", "1", CVAR_RENDERER | CVAR_BOOL, "use pre-calculated material registers if possible" ); +idCVar r_useSilRemap( "r_useSilRemap", "1", CVAR_RENDERER | CVAR_BOOL, "consider verts with the same XYZ, but different ST the same for shadows" ); +idCVar r_useNodeCommonChildren( "r_useNodeCommonChildren", "1", CVAR_RENDERER | CVAR_BOOL, "stop pushing reference bounds early when possible" ); +idCVar r_useShadowSurfaceScissor( "r_useShadowSurfaceScissor", "1", CVAR_RENDERER | CVAR_BOOL, "scissor shadows by the scissor rect of the interaction surfaces" ); +idCVar r_useCachedDynamicModels( "r_useCachedDynamicModels", "1", CVAR_RENDERER | CVAR_BOOL, "cache snapshots of dynamic models" ); +idCVar r_useSeamlessCubeMap( "r_useSeamlessCubeMap", "1", CVAR_RENDERER | CVAR_BOOL, "use ARB_seamless_cube_map if available" ); +idCVar r_useSRGB( "r_useSRGB", "0", CVAR_RENDERER | CVAR_INTEGER | CVAR_ARCHIVE, "1 = both texture and framebuffer, 2 = framebuffer only, 3 = texture only" ); +idCVar r_maxAnisotropicFiltering( "r_maxAnisotropicFiltering", "8", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "limit aniso filtering" ); +idCVar r_useTrilinearFiltering( "r_useTrilinearFiltering", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Extra quality filtering" ); +idCVar r_lodBias( "r_lodBias", "0.5", CVAR_RENDERER | CVAR_ARCHIVE, "image lod bias" ); + +idCVar r_useStateCaching( "r_useStateCaching", "1", CVAR_RENDERER | CVAR_BOOL, "avoid redundant state changes in GL_*() calls" ); + +idCVar r_znear( "r_znear", "3", CVAR_RENDERER | CVAR_FLOAT, "near Z clip plane distance", 0.001f, 200.0f ); + +idCVar r_ignoreGLErrors( "r_ignoreGLErrors", "1", CVAR_RENDERER | CVAR_BOOL, "ignore GL errors" ); +idCVar r_swapInterval( "r_swapInterval", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "0 = tear, 1 = swap-tear where available, 2 = always v-sync" ); + +idCVar r_gamma( "r_gamma", "1.0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 3.0f ); +idCVar r_brightness( "r_brightness", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 2.0f ); + +idCVar r_jitter( "r_jitter", "0", CVAR_RENDERER | CVAR_BOOL, "randomly subpixel jitter the projection matrix" ); + +idCVar r_skipStaticInteractions( "r_skipStaticInteractions", "0", CVAR_RENDERER | CVAR_BOOL, "skip interactions created at level load" ); +idCVar r_skipDynamicInteractions( "r_skipDynamicInteractions", "0", CVAR_RENDERER | CVAR_BOOL, "skip interactions created after level load" ); +idCVar r_skipSuppress( "r_skipSuppress", "0", CVAR_RENDERER | CVAR_BOOL, "ignore the per-view suppressions" ); +idCVar r_skipPostProcess( "r_skipPostProcess", "0", CVAR_RENDERER | CVAR_BOOL, "skip all post-process renderings" ); +idCVar r_skipInteractions( "r_skipInteractions", "0", CVAR_RENDERER | CVAR_BOOL, "skip all light/surface interaction drawing" ); +idCVar r_skipDynamicTextures( "r_skipDynamicTextures", "0", CVAR_RENDERER | CVAR_BOOL, "don't dynamically create textures" ); +idCVar r_skipCopyTexture( "r_skipCopyTexture", "0", CVAR_RENDERER | CVAR_BOOL, "do all rendering, but don't actually copyTexSubImage2D" ); +idCVar r_skipBackEnd( "r_skipBackEnd", "0", CVAR_RENDERER | CVAR_BOOL, "don't draw anything" ); +idCVar r_skipRender( "r_skipRender", "0", CVAR_RENDERER | CVAR_BOOL, "skip 3D rendering, but pass 2D" ); +idCVar r_skipRenderContext( "r_skipRenderContext", "0", CVAR_RENDERER | CVAR_BOOL, "NULL the rendering context during backend 3D rendering" ); +idCVar r_skipTranslucent( "r_skipTranslucent", "0", CVAR_RENDERER | CVAR_BOOL, "skip the translucent interaction rendering" ); +idCVar r_skipAmbient( "r_skipAmbient", "0", CVAR_RENDERER | CVAR_BOOL, "bypasses all non-interaction drawing" ); +idCVar r_skipNewAmbient( "r_skipNewAmbient", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "bypasses all vertex/fragment program ambient drawing" ); +idCVar r_skipBlendLights( "r_skipBlendLights", "0", CVAR_RENDERER | CVAR_BOOL, "skip all blend lights" ); +idCVar r_skipFogLights( "r_skipFogLights", "0", CVAR_RENDERER | CVAR_BOOL, "skip all fog lights" ); +idCVar r_skipDeforms( "r_skipDeforms", "0", CVAR_RENDERER | CVAR_BOOL, "leave all deform materials in their original state" ); +idCVar r_skipFrontEnd( "r_skipFrontEnd", "0", CVAR_RENDERER | CVAR_BOOL, "bypasses all front end work, but 2D gui rendering still draws" ); +idCVar r_skipUpdates( "r_skipUpdates", "0", CVAR_RENDERER | CVAR_BOOL, "1 = don't accept any entity or light updates, making everything static" ); +idCVar r_skipDecals( "r_skipDecals", "0", CVAR_RENDERER | CVAR_BOOL, "skip decal surfaces" ); +idCVar r_skipOverlays( "r_skipOverlays", "0", CVAR_RENDERER | CVAR_BOOL, "skip overlay surfaces" ); +idCVar r_skipSpecular( "r_skipSpecular", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_CHEAT | CVAR_ARCHIVE, "use black for specular1" ); +idCVar r_skipBump( "r_skipBump", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "uses a flat surface instead of the bump map" ); +idCVar r_skipDiffuse( "r_skipDiffuse", "0", CVAR_RENDERER | CVAR_BOOL, "use black for diffuse" ); +idCVar r_skipSubviews( "r_skipSubviews", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = don't render any gui elements on surfaces" ); +idCVar r_skipGuiShaders( "r_skipGuiShaders", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = skip all gui elements on surfaces, 2 = skip drawing but still handle events, 3 = draw but skip events", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_skipParticles( "r_skipParticles", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = skip all particle systems", 0, 1, idCmdSystem::ArgCompletion_Integer<0,1> ); +idCVar r_skipShadows( "r_skipShadows", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "disable shadows" ); + +idCVar r_useLightPortalCulling( "r_useLightPortalCulling", "1", CVAR_RENDERER | CVAR_INTEGER, "0 = none, 1 = cull frustum corners to plane, 2 = exact clip the frustum faces", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_useLightAreaCulling( "r_useLightAreaCulling", "1", CVAR_RENDERER | CVAR_BOOL, "0 = off, 1 = on" ); +idCVar r_useLightScissors( "r_useLightScissors", "3", CVAR_RENDERER | CVAR_INTEGER, "0 = no scissor, 1 = non-clipped scissor, 2 = near-clipped scissor, 3 = fully-clipped scissor", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_useEntityPortalCulling( "r_useEntityPortalCulling", "1", CVAR_RENDERER | CVAR_INTEGER, "0 = none, 1 = cull frustum corners to plane, 2 = exact clip the frustum faces", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_logFile( "r_logFile", "0", CVAR_RENDERER | CVAR_INTEGER, "number of frames to emit GL logs" ); +idCVar r_clear( "r_clear", "2", CVAR_RENDERER, "force screen clear every frame, 1 = purple, 2 = black, 'r g b' = custom" ); + +idCVar r_offsetFactor( "r_offsetfactor", "0", CVAR_RENDERER | CVAR_FLOAT, "polygon offset parameter" ); +idCVar r_offsetUnits( "r_offsetunits", "-600", CVAR_RENDERER | CVAR_FLOAT, "polygon offset parameter" ); + +idCVar r_shadowPolygonOffset( "r_shadowPolygonOffset", "-1", CVAR_RENDERER | CVAR_FLOAT, "bias value added to depth test for stencil shadow drawing" ); +idCVar r_shadowPolygonFactor( "r_shadowPolygonFactor", "0", CVAR_RENDERER | CVAR_FLOAT, "scale value for stencil shadow drawing" ); +idCVar r_subviewOnly( "r_subviewOnly", "0", CVAR_RENDERER | CVAR_BOOL, "1 = don't render main view, allowing subviews to be debugged" ); +idCVar r_testGamma( "r_testGamma", "0", CVAR_RENDERER | CVAR_FLOAT, "if > 0 draw a grid pattern to test gamma levels", 0, 195 ); +idCVar r_testGammaBias( "r_testGammaBias", "0", CVAR_RENDERER | CVAR_FLOAT, "if > 0 draw a grid pattern to test gamma levels" ); +idCVar r_lightScale( "r_lightScale", "3", CVAR_ARCHIVE | CVAR_RENDERER | CVAR_FLOAT, "all light intensities are multiplied by this" ); +idCVar r_flareSize( "r_flareSize", "1", CVAR_RENDERER | CVAR_FLOAT, "scale the flare deforms from the material def" ); + +idCVar r_skipPrelightShadows( "r_skipPrelightShadows", "0", CVAR_RENDERER | CVAR_BOOL, "skip the dmap generated static shadow volumes" ); +idCVar r_useScissor( "r_useScissor", "1", CVAR_RENDERER | CVAR_BOOL, "scissor clip as portals and lights are processed" ); +idCVar r_useLightDepthBounds( "r_useLightDepthBounds", "1", CVAR_RENDERER | CVAR_BOOL, "use depth bounds test on lights to reduce both shadow and interaction fill" ); +idCVar r_useShadowDepthBounds( "r_useShadowDepthBounds", "1", CVAR_RENDERER | CVAR_BOOL, "use depth bounds test on individual shadow volumes to reduce shadow fill" ); + +idCVar r_screenFraction( "r_screenFraction", "100", CVAR_RENDERER | CVAR_INTEGER, "for testing fill rate, the resolution of the entire screen can be changed" ); +idCVar r_usePortals( "r_usePortals", "1", CVAR_RENDERER | CVAR_BOOL, " 1 = use portals to perform area culling, otherwise draw everything" ); +idCVar r_singleLight( "r_singleLight", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one light" ); +idCVar r_singleEntity( "r_singleEntity", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one entity" ); +idCVar r_singleSurface( "r_singleSurface", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one surface on each entity" ); +idCVar r_singleArea( "r_singleArea", "0", CVAR_RENDERER | CVAR_BOOL, "only draw the portal area the view is actually in" ); +idCVar r_orderIndexes( "r_orderIndexes", "1", CVAR_RENDERER | CVAR_BOOL, "perform index reorganization to optimize vertex use" ); +idCVar r_lightAllBackFaces( "r_lightAllBackFaces", "0", CVAR_RENDERER | CVAR_BOOL, "light all the back faces, even when they would be shadowed" ); + +// visual debugging info +idCVar r_showPortals( "r_showPortals", "0", CVAR_RENDERER | CVAR_BOOL, "draw portal outlines in color based on passed / not passed" ); +idCVar r_showUnsmoothedTangents( "r_showUnsmoothedTangents", "0", CVAR_RENDERER | CVAR_BOOL, "if 1, put all nvidia register combiner programming in display lists" ); +idCVar r_showSilhouette( "r_showSilhouette", "0", CVAR_RENDERER | CVAR_BOOL, "highlight edges that are casting shadow planes" ); +idCVar r_showVertexColor( "r_showVertexColor", "0", CVAR_RENDERER | CVAR_BOOL, "draws all triangles with the solid vertex color" ); +idCVar r_showUpdates( "r_showUpdates", "0", CVAR_RENDERER | CVAR_BOOL, "report entity and light updates and ref counts" ); +idCVar r_showDemo( "r_showDemo", "0", CVAR_RENDERER | CVAR_BOOL, "report reads and writes to the demo file" ); +idCVar r_showDynamic( "r_showDynamic", "0", CVAR_RENDERER | CVAR_BOOL, "report stats on dynamic surface generation" ); +idCVar r_showTrace( "r_showTrace", "0", CVAR_RENDERER | CVAR_INTEGER, "show the intersection of an eye trace with the world", idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_showIntensity( "r_showIntensity", "0", CVAR_RENDERER | CVAR_BOOL, "draw the screen colors based on intensity, red = 0, green = 128, blue = 255" ); +idCVar r_showLights( "r_showLights", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = just print volumes numbers, highlighting ones covering the view, 2 = also draw planes of each volume, 3 = also draw edges of each volume", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showShadows( "r_showShadows", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = visualize the stencil shadow volumes, 2 = draw filled in", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showLightScissors( "r_showLightScissors", "0", CVAR_RENDERER | CVAR_BOOL, "show light scissor rectangles" ); +idCVar r_showLightCount( "r_showLightCount", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = colors surfaces based on light count, 2 = also count everything through walls, 3 = also print overdraw", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showViewEntitys( "r_showViewEntitys", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = displays the bounding boxes of all view models, 2 = print index numbers" ); +idCVar r_showTris( "r_showTris", "0", CVAR_RENDERER | CVAR_INTEGER, "enables wireframe rendering of the world, 1 = only draw visible ones, 2 = draw all front facing, 3 = draw all, 4 = draw with alpha", 0, 4, idCmdSystem::ArgCompletion_Integer<0,4> ); +idCVar r_showSurfaceInfo( "r_showSurfaceInfo", "0", CVAR_RENDERER | CVAR_BOOL, "show surface material name under crosshair" ); +idCVar r_showNormals( "r_showNormals", "0", CVAR_RENDERER | CVAR_FLOAT, "draws wireframe normals" ); +idCVar r_showMemory( "r_showMemory", "0", CVAR_RENDERER | CVAR_BOOL, "print frame memory utilization" ); +idCVar r_showCull( "r_showCull", "0", CVAR_RENDERER | CVAR_BOOL, "report sphere and box culling stats" ); +idCVar r_showAddModel( "r_showAddModel", "0", CVAR_RENDERER | CVAR_BOOL, "report stats from tr_addModel" ); +idCVar r_showDepth( "r_showDepth", "0", CVAR_RENDERER | CVAR_BOOL, "display the contents of the depth buffer and the depth range" ); +idCVar r_showSurfaces( "r_showSurfaces", "0", CVAR_RENDERER | CVAR_BOOL, "report surface/light/shadow counts" ); +idCVar r_showPrimitives( "r_showPrimitives", "0", CVAR_RENDERER | CVAR_INTEGER, "report drawsurf/index/vertex counts" ); +idCVar r_showEdges( "r_showEdges", "0", CVAR_RENDERER | CVAR_BOOL, "draw the sil edges" ); +idCVar r_showTexturePolarity( "r_showTexturePolarity", "0", CVAR_RENDERER | CVAR_BOOL, "shade triangles by texture area polarity" ); +idCVar r_showTangentSpace( "r_showTangentSpace", "0", CVAR_RENDERER | CVAR_INTEGER, "shade triangles by tangent space, 1 = use 1st tangent vector, 2 = use 2nd tangent vector, 3 = use normal vector", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showDominantTri( "r_showDominantTri", "0", CVAR_RENDERER | CVAR_BOOL, "draw lines from vertexes to center of dominant triangles" ); +idCVar r_showTextureVectors( "r_showTextureVectors", "0", CVAR_RENDERER | CVAR_FLOAT, " if > 0 draw each triangles texture (tangent) vectors" ); +idCVar r_showOverDraw( "r_showOverDraw", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = geometry overdraw, 2 = light interaction overdraw, 3 = geometry and light interaction overdraw", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); + +idCVar r_useEntityCallbacks( "r_useEntityCallbacks", "1", CVAR_RENDERER | CVAR_BOOL, "if 0, issue the callback immediately at update time, rather than defering" ); + +idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw the skeleton when model animates, 1 = draw model with skeleton, 2 = draw skeleton only", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_jointNameScale( "r_jointNameScale", "0.02", CVAR_RENDERER | CVAR_FLOAT, "size of joint names when r_showskel is set to 1" ); +idCVar r_jointNameOffset( "r_jointNameOffset", "0.5", CVAR_RENDERER | CVAR_FLOAT, "offset of joint names when r_showskel is set to 1" ); + +idCVar r_debugLineDepthTest( "r_debugLineDepthTest", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "perform depth test on debug lines" ); +idCVar r_debugLineWidth( "r_debugLineWidth", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "width of debug lines" ); +idCVar r_debugArrowStep( "r_debugArrowStep", "120", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "step size of arrow cone line rotation in degrees", 0, 120 ); +idCVar r_debugPolygonFilled( "r_debugPolygonFilled", "1", CVAR_RENDERER | CVAR_BOOL, "draw a filled polygon" ); + +idCVar r_materialOverride( "r_materialOverride", "", CVAR_RENDERER, "overrides all materials", idCmdSystem::ArgCompletion_Decl ); + +idCVar r_debugRenderToTexture( "r_debugRenderToTexture", "0", CVAR_RENDERER | CVAR_INTEGER, "" ); + +idCVar stereoRender_enable( "stereoRender_enable", "0", CVAR_INTEGER | CVAR_ARCHIVE, "1 = side-by-side compressed, 2 = top and bottom compressed, 3 = side-by-side, 4 = 720 frame packed, 5 = interlaced, 6 = OpenGL quad buffer" ); +idCVar stereoRender_swapEyes( "stereoRender_swapEyes", "0", CVAR_BOOL | CVAR_ARCHIVE, "reverse eye adjustments" ); +idCVar stereoRender_deGhost( "stereoRender_deGhost", "0.05", CVAR_FLOAT | CVAR_ARCHIVE, "subtract from opposite eye to reduce ghosting" ); + + +// GL_ARB_multitexture +PFNGLACTIVETEXTUREPROC qglActiveTextureARB; +PFNGLCLIENTACTIVETEXTUREPROC qglClientActiveTextureARB; + +// GL_EXT_direct_state_access +PFNGLBINDMULTITEXTUREEXTPROC qglBindMultiTextureEXT; + +// GL_ARB_texture_compression +PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB; +PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC qglCompressedTexSubImage2DARB; +PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB; + +// GL_ARB_vertex_buffer_object +PFNGLBINDBUFFERARBPROC qglBindBufferARB; +PFNGLBINDBUFFERRANGEPROC qglBindBufferRange; +PFNGLDELETEBUFFERSARBPROC qglDeleteBuffersARB; +PFNGLGENBUFFERSARBPROC qglGenBuffersARB; +PFNGLISBUFFERARBPROC qglIsBufferARB; +PFNGLBUFFERDATAARBPROC qglBufferDataARB; +PFNGLBUFFERSUBDATAARBPROC qglBufferSubDataARB; +PFNGLGETBUFFERSUBDATAARBPROC qglGetBufferSubDataARB; +PFNGLMAPBUFFERARBPROC qglMapBufferARB; +PFNGLUNMAPBUFFERARBPROC qglUnmapBufferARB; +PFNGLGETBUFFERPARAMETERIVARBPROC qglGetBufferParameterivARB; +PFNGLGETBUFFERPOINTERVARBPROC qglGetBufferPointervARB; + +// GL_ARB_map_buffer_range +PFNGLMAPBUFFERRANGEPROC qglMapBufferRange; + +// GL_ARB_draw_elements_base_vertex +PFNGLDRAWELEMENTSBASEVERTEXPROC qglDrawElementsBaseVertex; + +// GL_ARB_vertex_array_object +PFNGLGENVERTEXARRAYSPROC qglGenVertexArrays; +PFNGLBINDVERTEXARRAYPROC qglBindVertexArray; +PFNGLDELETEVERTEXARRAYSPROC qglDeleteVertexArrays; + +// GL_ARB_vertex_program / GL_ARB_fragment_program +PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB; +PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB; +PFNGLDISABLEVERTEXATTRIBARRAYARBPROC qglDisableVertexAttribArrayARB; +PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +PFNGLDELETEPROGRAMSARBPROC qglDeleteProgramsARB; +PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; + +// GLSL / OpenGL 2.0 +PFNGLCREATESHADERPROC qglCreateShader; +PFNGLDELETESHADERPROC qglDeleteShader; +PFNGLSHADERSOURCEPROC qglShaderSource; +PFNGLCOMPILESHADERPROC qglCompileShader; +PFNGLGETSHADERIVPROC qglGetShaderiv; +PFNGLGETSHADERINFOLOGPROC qglGetShaderInfoLog; +PFNGLCREATEPROGRAMPROC qglCreateProgram; +PFNGLDELETEPROGRAMPROC qglDeleteProgram; +PFNGLATTACHSHADERPROC qglAttachShader; +PFNGLDETACHSHADERPROC qglDetachShader; +PFNGLLINKPROGRAMPROC qglLinkProgram; +PFNGLUSEPROGRAMPROC qglUseProgram; +PFNGLGETPROGRAMIVPROC qglGetProgramiv; +PFNGLGETPROGRAMINFOLOGPROC qglGetProgramInfoLog; +PFNGLPROGRAMPARAMETERIPROC qglProgramParameteri; +PFNGLBINDATTRIBLOCATIONPROC qglBindAttribLocation; +PFNGLGETUNIFORMLOCATIONPROC qglGetUniformLocation; +PFNGLUNIFORM1IPROC qglUniform1i; +PFNGLUNIFORM4FVPROC qglUniform4fv; + +// GL_ARB_uniform_buffer_object +PFNGLGETUNIFORMBLOCKINDEXPROC qglGetUniformBlockIndex; +PFNGLUNIFORMBLOCKBINDINGPROC qglUniformBlockBinding; + +// GL_ATI_separate_stencil / OpenGL 2.0 +PFNGLSTENCILOPSEPARATEATIPROC qglStencilOpSeparate; +PFNGLSTENCILFUNCSEPARATEATIPROC qglStencilFuncSeparate; + +// GL_EXT_depth_bounds_test +PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT; + +// GL_ARB_sync +PFNGLFENCESYNCPROC qglFenceSync; +PFNGLISSYNCPROC qglIsSync; +PFNGLCLIENTWAITSYNCPROC qglClientWaitSync; +PFNGLDELETESYNCPROC qglDeleteSync; + +// GL_ARB_occlusion_query +PFNGLGENQUERIESARBPROC qglGenQueriesARB; +PFNGLDELETEQUERIESARBPROC qglDeleteQueriesARB; +PFNGLISQUERYARBPROC qglIsQueryARB; +PFNGLBEGINQUERYARBPROC qglBeginQueryARB; +PFNGLENDQUERYARBPROC qglEndQueryARB; +PFNGLGETQUERYIVARBPROC qglGetQueryivARB; +PFNGLGETQUERYOBJECTIVARBPROC qglGetQueryObjectivARB; +PFNGLGETQUERYOBJECTUIVARBPROC qglGetQueryObjectuivARB; + +// GL_ARB_timer_query / GL_EXT_timer_query +PFNGLGETQUERYOBJECTUI64VEXTPROC qglGetQueryObjectui64vEXT; + +// GL_ARB_debug_output +PFNGLDEBUGMESSAGECONTROLARBPROC qglDebugMessageControlARB; +PFNGLDEBUGMESSAGEINSERTARBPROC qglDebugMessageInsertARB; +PFNGLDEBUGMESSAGECALLBACKARBPROC qglDebugMessageCallbackARB; +PFNGLGETDEBUGMESSAGELOGARBPROC qglGetDebugMessageLogARB; + +PFNGLGETSTRINGIPROC qglGetStringi; + +/* +======================== +glBindMultiTextureEXT + +As of 2011/09/16 the Intel drivers for "Sandy Bridge" and "Ivy Bridge" integrated graphics do not support this extension. +======================== +*/ +void APIENTRY glBindMultiTextureEXT( GLenum texunit, GLenum target, GLuint texture ) { + qglActiveTextureARB( texunit ); + qglBindTexture( target, texture ); +} + + +/* +================= +R_CheckExtension +================= +*/ +bool R_CheckExtension( char *name ) { + if ( !strstr( glConfig.extensions_string, name ) ) { + common->Printf( "X..%s not found\n", name ); + return false; + } + + common->Printf( "...using %s\n", name ); + return true; +} + + +/* +======================== +DebugCallback + +For ARB_debug_output +======================== +*/ +static void CALLBACK DebugCallback(unsigned int source, unsigned int type, + unsigned int id, unsigned int severity, int length, const char * message, void * userParam) { + // it probably isn't safe to do an idLib::Printf at this point + OutputDebugString( message ); + OutputDebugString( "\n" ); +} + +/* +================== +R_CheckPortableExtensions +================== +*/ +static void R_CheckPortableExtensions() { + glConfig.glVersion = atof( glConfig.version_string ); + const char * badVideoCard = idLocalization::GetString( "#str_06780" ); + if ( glConfig.glVersion < 2.0f ) { + idLib::FatalError( badVideoCard ); + } + + if ( idStr::Icmpn( glConfig.renderer_string, "ATI ", 4 ) == 0 || idStr::Icmpn( glConfig.renderer_string, "AMD ", 4 ) == 0 ) { + glConfig.vendor = VENDOR_AMD; + } else if ( idStr::Icmpn( glConfig.renderer_string, "NVIDIA", 6 ) == 0 ) { + glConfig.vendor = VENDOR_NVIDIA; + } else if ( idStr::Icmpn( glConfig.renderer_string, "Intel", 5 ) == 0 ) { + glConfig.vendor = VENDOR_INTEL; + } + + // GL_ARB_multitexture + glConfig.multitextureAvailable = R_CheckExtension( "GL_ARB_multitexture" ); + if ( glConfig.multitextureAvailable ) { + qglActiveTextureARB = (void(APIENTRY *)(GLenum))GLimp_ExtensionPointer( "glActiveTextureARB" ); + qglClientActiveTextureARB = (void(APIENTRY *)(GLenum))GLimp_ExtensionPointer( "glClientActiveTextureARB" ); + } + + // GL_EXT_direct_state_access + glConfig.directStateAccess = R_CheckExtension( "GL_EXT_direct_state_access" ); + if ( glConfig.directStateAccess ) { + qglBindMultiTextureEXT = (PFNGLBINDMULTITEXTUREEXTPROC)GLimp_ExtensionPointer( "glBindMultiTextureEXT" ); + } else { + qglBindMultiTextureEXT = glBindMultiTextureEXT; + } + + // GL_ARB_texture_compression + GL_S3_s3tc + // DRI drivers may have GL_ARB_texture_compression but no GL_EXT_texture_compression_s3tc + glConfig.textureCompressionAvailable = R_CheckExtension( "GL_ARB_texture_compression" ) && R_CheckExtension( "GL_EXT_texture_compression_s3tc" ); + if ( glConfig.textureCompressionAvailable ) { + qglCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)GLimp_ExtensionPointer( "glCompressedTexImage2DARB" ); + qglCompressedTexSubImage2DARB = (PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC)GLimp_ExtensionPointer( "glCompressedTexSubImage2DARB" ); + qglGetCompressedTexImageARB = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)GLimp_ExtensionPointer( "glGetCompressedTexImageARB" ); + } + + // GL_EXT_texture_filter_anisotropic + glConfig.anisotropicFilterAvailable = R_CheckExtension( "GL_EXT_texture_filter_anisotropic" ); + if ( glConfig.anisotropicFilterAvailable ) { + qglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.maxTextureAnisotropy ); + common->Printf( " maxTextureAnisotropy: %f\n", glConfig.maxTextureAnisotropy ); + } else { + glConfig.maxTextureAnisotropy = 1; + } + + // GL_EXT_texture_lod_bias + // The actual extension is broken as specificed, storing the state in the texture unit instead + // of the texture object. The behavior in GL 1.4 is the behavior we use. + glConfig.textureLODBiasAvailable = ( glConfig.glVersion >= 1.4 || R_CheckExtension( "GL_EXT_texture_lod_bias" ) ); + if ( glConfig.textureLODBiasAvailable ) { + common->Printf( "...using %s\n", "GL_EXT_texture_lod_bias" ); + } else { + common->Printf( "X..%s not found\n", "GL_EXT_texture_lod_bias" ); + } + + // GL_ARB_seamless_cube_map + glConfig.seamlessCubeMapAvailable = R_CheckExtension( "GL_ARB_seamless_cube_map" ); + r_useSeamlessCubeMap.SetModified(); // the CheckCvars() next frame will enable / disable it + + // GL_ARB_framebuffer_sRGB + glConfig.sRGBFramebufferAvailable = R_CheckExtension( "GL_ARB_framebuffer_sRGB" ); + r_useSRGB.SetModified(); // the CheckCvars() next frame will enable / disable it + + // GL_ARB_vertex_buffer_object + glConfig.vertexBufferObjectAvailable = R_CheckExtension( "GL_ARB_vertex_buffer_object" ); + if ( glConfig.vertexBufferObjectAvailable ) { + qglBindBufferARB = (PFNGLBINDBUFFERARBPROC)GLimp_ExtensionPointer( "glBindBufferARB" ); + qglBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)GLimp_ExtensionPointer( "glBindBufferRange" ); + qglDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)GLimp_ExtensionPointer( "glDeleteBuffersARB" ); + qglGenBuffersARB = (PFNGLGENBUFFERSARBPROC)GLimp_ExtensionPointer( "glGenBuffersARB" ); + qglIsBufferARB = (PFNGLISBUFFERARBPROC)GLimp_ExtensionPointer( "glIsBufferARB" ); + qglBufferDataARB = (PFNGLBUFFERDATAARBPROC)GLimp_ExtensionPointer( "glBufferDataARB" ); + qglBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)GLimp_ExtensionPointer( "glBufferSubDataARB" ); + qglGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC)GLimp_ExtensionPointer( "glGetBufferSubDataARB" ); + qglMapBufferARB = (PFNGLMAPBUFFERARBPROC)GLimp_ExtensionPointer( "glMapBufferARB" ); + qglUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)GLimp_ExtensionPointer( "glUnmapBufferARB" ); + qglGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)GLimp_ExtensionPointer( "glGetBufferParameterivARB" ); + qglGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC)GLimp_ExtensionPointer( "glGetBufferPointervARB" ); + } + + // GL_ARB_map_buffer_range, map a section of a buffer object's data store + glConfig.mapBufferRangeAvailable = R_CheckExtension( "GL_ARB_map_buffer_range" ); + if ( glConfig.mapBufferRangeAvailable ) { + qglMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)GLimp_ExtensionPointer( "glMapBufferRange" ); + } + + // GL_ARB_vertex_array_object + glConfig.vertexArrayObjectAvailable = R_CheckExtension( "GL_ARB_vertex_array_object" ); + if ( glConfig.vertexArrayObjectAvailable ) { + qglGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)GLimp_ExtensionPointer( "glGenVertexArrays" ); + qglBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)GLimp_ExtensionPointer( "glBindVertexArray" ); + qglDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)GLimp_ExtensionPointer( "glDeleteVertexArrays" ); + } + + // GL_ARB_draw_elements_base_vertex + glConfig.drawElementsBaseVertexAvailable = R_CheckExtension( "GL_ARB_draw_elements_base_vertex" ); + if ( glConfig.drawElementsBaseVertexAvailable ) { + qglDrawElementsBaseVertex = (PFNGLDRAWELEMENTSBASEVERTEXPROC)GLimp_ExtensionPointer( "glDrawElementsBaseVertex" ); + } + + // GL_ARB_vertex_program / GL_ARB_fragment_program + glConfig.fragmentProgramAvailable = R_CheckExtension( "GL_ARB_fragment_program" ); + if ( glConfig.fragmentProgramAvailable ) { + qglVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)GLimp_ExtensionPointer( "glVertexAttribPointerARB" ); + qglEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)GLimp_ExtensionPointer( "glEnableVertexAttribArrayARB" ); + qglDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)GLimp_ExtensionPointer( "glDisableVertexAttribArrayARB" ); + qglProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC)GLimp_ExtensionPointer( "glProgramStringARB" ); + qglBindProgramARB = (PFNGLBINDPROGRAMARBPROC)GLimp_ExtensionPointer( "glBindProgramARB" ); + qglGenProgramsARB = (PFNGLGENPROGRAMSARBPROC)GLimp_ExtensionPointer( "glGenProgramsARB" ); + qglDeleteProgramsARB = (PFNGLDELETEPROGRAMSARBPROC)GLimp_ExtensionPointer( "glDeleteProgramsARB" ); + qglProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)GLimp_ExtensionPointer( "glProgramEnvParameter4fvARB" ); + qglProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)GLimp_ExtensionPointer( "glProgramLocalParameter4fvARB" ); + + qglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, (GLint *)&glConfig.maxTextureCoords ); + qglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, (GLint *)&glConfig.maxTextureImageUnits ); + } + + // GLSL, core in OpenGL > 2.0 + glConfig.glslAvailable = ( glConfig.glVersion >= 2.0f ); + if ( glConfig.glslAvailable ) { + qglCreateShader = (PFNGLCREATESHADERPROC)GLimp_ExtensionPointer( "glCreateShader" ); + qglDeleteShader = (PFNGLDELETESHADERPROC)GLimp_ExtensionPointer( "glDeleteShader" ); + qglShaderSource = (PFNGLSHADERSOURCEPROC)GLimp_ExtensionPointer( "glShaderSource" ); + qglCompileShader = (PFNGLCOMPILESHADERPROC)GLimp_ExtensionPointer( "glCompileShader" ); + qglGetShaderiv = (PFNGLGETSHADERIVPROC)GLimp_ExtensionPointer( "glGetShaderiv" ); + qglGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)GLimp_ExtensionPointer( "glGetShaderInfoLog" ); + qglCreateProgram = (PFNGLCREATEPROGRAMPROC)GLimp_ExtensionPointer( "glCreateProgram" ); + qglDeleteProgram = (PFNGLDELETEPROGRAMPROC)GLimp_ExtensionPointer( "glDeleteProgram" ); + qglAttachShader = (PFNGLATTACHSHADERPROC)GLimp_ExtensionPointer( "glAttachShader" ); + qglDetachShader = (PFNGLDETACHSHADERPROC)GLimp_ExtensionPointer( "glDetachShader" ); + qglLinkProgram = (PFNGLLINKPROGRAMPROC)GLimp_ExtensionPointer( "glLinkProgram" ); + qglUseProgram = (PFNGLUSEPROGRAMPROC)GLimp_ExtensionPointer( "glUseProgram" ); + qglGetProgramiv = (PFNGLGETPROGRAMIVPROC)GLimp_ExtensionPointer( "glGetProgramiv" ); + qglGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)GLimp_ExtensionPointer( "glGetProgramInfoLog" ); + qglBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)GLimp_ExtensionPointer( "glBindAttribLocation" ); + qglGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)GLimp_ExtensionPointer( "glGetUniformLocation" ); + qglUniform1i = (PFNGLUNIFORM1IPROC)GLimp_ExtensionPointer( "glUniform1i" ); + qglUniform4fv = (PFNGLUNIFORM4FVPROC)GLimp_ExtensionPointer( "glUniform4fv" ); + } + + // GL_ARB_uniform_buffer_object + glConfig.uniformBufferAvailable = R_CheckExtension( "GL_ARB_uniform_buffer_object" ); + if ( glConfig.uniformBufferAvailable ) { + qglGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)GLimp_ExtensionPointer( "glGetUniformBlockIndex" ); + qglUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)GLimp_ExtensionPointer( "glUniformBlockBinding" ); + + qglGetIntegerv( GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, (GLint *)&glConfig.uniformBufferOffsetAlignment ); + if ( glConfig.uniformBufferOffsetAlignment < 256 ) { + glConfig.uniformBufferOffsetAlignment = 256; + } + } + + // ATI_separate_stencil / OpenGL 2.0 separate stencil + glConfig.twoSidedStencilAvailable = ( glConfig.glVersion >= 2.0f ) || R_CheckExtension( "GL_ATI_separate_stencil" ); + if ( glConfig.twoSidedStencilAvailable ) { + qglStencilOpSeparate = (PFNGLSTENCILOPSEPARATEATIPROC)GLimp_ExtensionPointer( "glStencilOpSeparate" ); + qglStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEATIPROC)GLimp_ExtensionPointer( "glStencilFuncSeparate" ); + } + + // GL_EXT_depth_bounds_test + glConfig.depthBoundsTestAvailable = R_CheckExtension( "GL_EXT_depth_bounds_test" ); + if ( glConfig.depthBoundsTestAvailable ) { + qglDepthBoundsEXT = (PFNGLDEPTHBOUNDSEXTPROC)GLimp_ExtensionPointer( "glDepthBoundsEXT" ); + } + + // GL_ARB_sync + glConfig.syncAvailable = R_CheckExtension( "GL_ARB_sync" ) && + // as of 5/24/2012 (driver version 15.26.12.64.2761) sync objects + // do not appear to work for the Intel HD 4000 graphics + ( glConfig.vendor != VENDOR_INTEL || r_skipIntelWorkarounds.GetBool() ); + if ( glConfig.syncAvailable ) { + qglFenceSync = (PFNGLFENCESYNCPROC)GLimp_ExtensionPointer( "glFenceSync" ); + qglIsSync = (PFNGLISSYNCPROC)GLimp_ExtensionPointer( "glIsSync" ); + qglClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)GLimp_ExtensionPointer( "glClientWaitSync" ); + qglDeleteSync = (PFNGLDELETESYNCPROC)GLimp_ExtensionPointer( "glDeleteSync" ); + } + + // GL_ARB_occlusion_query + glConfig.occlusionQueryAvailable = R_CheckExtension( "GL_ARB_occlusion_query" ); + if ( glConfig.occlusionQueryAvailable ) { + // defined in GL_ARB_occlusion_query, which is required for GL_EXT_timer_query + qglGenQueriesARB = (PFNGLGENQUERIESARBPROC)GLimp_ExtensionPointer( "glGenQueriesARB" ); + qglDeleteQueriesARB = (PFNGLDELETEQUERIESARBPROC)GLimp_ExtensionPointer( "glDeleteQueriesARB" ); + qglIsQueryARB = (PFNGLISQUERYARBPROC)GLimp_ExtensionPointer( "glIsQueryARB" ); + qglBeginQueryARB = (PFNGLBEGINQUERYARBPROC)GLimp_ExtensionPointer( "glBeginQueryARB" ); + qglEndQueryARB = (PFNGLENDQUERYARBPROC)GLimp_ExtensionPointer( "glEndQueryARB" ); + qglGetQueryivARB = (PFNGLGETQUERYIVARBPROC)GLimp_ExtensionPointer( "glGetQueryivARB" ); + qglGetQueryObjectivARB = (PFNGLGETQUERYOBJECTIVARBPROC)GLimp_ExtensionPointer( "glGetQueryObjectivARB" ); + qglGetQueryObjectuivARB = (PFNGLGETQUERYOBJECTUIVARBPROC)GLimp_ExtensionPointer( "glGetQueryObjectuivARB" ); + } + + // GL_ARB_timer_query + glConfig.timerQueryAvailable = R_CheckExtension( "GL_ARB_timer_query" ) || R_CheckExtension( "GL_EXT_timer_query" ); + if ( glConfig.timerQueryAvailable ) { + qglGetQueryObjectui64vEXT = (PFNGLGETQUERYOBJECTUI64VEXTPROC)GLimp_ExtensionPointer( "glGetQueryObjectui64vARB" ); + if ( qglGetQueryObjectui64vEXT == NULL ) { + qglGetQueryObjectui64vEXT = (PFNGLGETQUERYOBJECTUI64VEXTPROC)GLimp_ExtensionPointer( "glGetQueryObjectui64vEXT" ); + } + } + + // GL_ARB_debug_output + glConfig.debugOutputAvailable = R_CheckExtension( "GL_ARB_debug_output" ); + if ( glConfig.debugOutputAvailable ) { + qglDebugMessageControlARB = (PFNGLDEBUGMESSAGECONTROLARBPROC)GLimp_ExtensionPointer( "glDebugMessageControlARB" ); + qglDebugMessageInsertARB = (PFNGLDEBUGMESSAGEINSERTARBPROC)GLimp_ExtensionPointer( "glDebugMessageInsertARB" ); + qglDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC)GLimp_ExtensionPointer( "glDebugMessageCallbackARB" ); + qglGetDebugMessageLogARB = (PFNGLGETDEBUGMESSAGELOGARBPROC)GLimp_ExtensionPointer( "glGetDebugMessageLogARB" ); + + if ( r_debugContext.GetInteger() >= 1 ) { + qglDebugMessageCallbackARB( DebugCallback, NULL ); + } + if ( r_debugContext.GetInteger() >= 2 ) { + // force everything to happen in the main thread instead of in a separate driver thread + glEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ); + } + if ( r_debugContext.GetInteger() >= 3 ) { + // enable all the low priority messages + qglDebugMessageControlARB( GL_DONT_CARE, + GL_DONT_CARE, + GL_DEBUG_SEVERITY_LOW_ARB, + 0, NULL, true ); + } + } + + // GL_ARB_multitexture + if ( !glConfig.multitextureAvailable ) { + idLib::Error( "GL_ARB_multitexture not available" ); + } + // GL_ARB_texture_compression + GL_EXT_texture_compression_s3tc + if ( !glConfig.textureCompressionAvailable ) { + idLib::Error( "GL_ARB_texture_compression or GL_EXT_texture_compression_s3tc not available" ); + } + // GL_ARB_vertex_buffer_object + if ( !glConfig.vertexBufferObjectAvailable ) { + idLib::Error( "GL_ARB_vertex_buffer_object not available" ); + } + // GL_ARB_map_buffer_range + if ( !glConfig.mapBufferRangeAvailable ) { + idLib::Error( "GL_ARB_map_buffer_range not available" ); + } + // GL_ARB_vertex_array_object + if ( !glConfig.vertexArrayObjectAvailable ) { + idLib::Error( "GL_ARB_vertex_array_object not available" ); + } + // GL_ARB_draw_elements_base_vertex + if ( !glConfig.drawElementsBaseVertexAvailable ) { + idLib::Error( "GL_ARB_draw_elements_base_vertex not available" ); + } + // GL_ARB_vertex_program / GL_ARB_fragment_program + if ( !glConfig.fragmentProgramAvailable ) { + idLib::Error( "GL_ARB_fragment_program not available" ); + } + // GLSL + if ( !glConfig.glslAvailable ) { + idLib::Error( "GLSL not available" ); + } + // GL_ARB_uniform_buffer_object + if ( !glConfig.uniformBufferAvailable ) { + idLib::Error( "GL_ARB_uniform_buffer_object not available" ); + } + // GL_EXT_stencil_two_side + if ( !glConfig.twoSidedStencilAvailable ) { + idLib::Error( "GL_ATI_separate_stencil not available" ); + } + + // generate one global Vertex Array Object (VAO) + qglGenVertexArrays( 1, &glConfig.global_vao ); + qglBindVertexArray( glConfig.global_vao ); + +} + + + +static bool r_initialized = false; + +/* +============================= +R_IsInitialized +============================= +*/ +bool R_IsInitialized() { + return r_initialized; +} + +/* +============================= +R_SetNewMode + +r_fullScreen -1 borderless window at exact desktop coordinates +r_fullScreen 0 bordered window at exact desktop coordinates +r_fullScreen 1 fullscreen on monitor 1 at r_vidMode +r_fullScreen 2 fullscreen on monitor 2 at r_vidMode +... + +r_vidMode -1 use r_customWidth / r_customHeight, even if they don't appear on the mode list +r_vidMode 0 use first mode returned by EnumDisplaySettings() +r_vidMode 1 use second mode returned by EnumDisplaySettings() +... + +r_displayRefresh 0 don't specify refresh +r_displayRefresh 70 specify 70 hz, etc +============================= +*/ +void R_SetNewMode( const bool fullInit ) { + // try up to three different configurations + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( i == 0 && stereoRender_enable.GetInteger() != STEREO3D_QUAD_BUFFER ) { + continue; // don't even try for a stereo mode + } + + glimpParms_t parms; + + if ( r_fullscreen.GetInteger() <= 0 ) { + // use explicit position / size for window + parms.x = r_windowX.GetInteger(); + parms.y = r_windowY.GetInteger(); + parms.width = r_windowWidth.GetInteger(); + parms.height = r_windowHeight.GetInteger(); + // may still be -1 to force a borderless window + parms.fullScreen = r_fullscreen.GetInteger(); + parms.displayHz = 0; // ignored + } else { + // get the mode list for this monitor + idList modeList; + if ( !R_GetModeListForDisplay( r_fullscreen.GetInteger()-1, modeList ) ) { + idLib::Printf( "r_fullscreen reset from %i to 1 because mode list failed.", r_fullscreen.GetInteger() ); + r_fullscreen.SetInteger( 1 ); + R_GetModeListForDisplay( r_fullscreen.GetInteger()-1, modeList ); + } + if ( modeList.Num() < 1 ) { + idLib::Printf( "Going to safe mode because mode list failed." ); + goto safeMode; + } + + parms.x = 0; // ignored + parms.y = 0; // ignored + parms.fullScreen = r_fullscreen.GetInteger(); + + // set the parameters we are trying + if ( r_vidMode.GetInteger() < 0 ) { + // try forcing a specific mode, even if it isn't on the list + parms.width = r_customWidth.GetInteger(); + parms.height = r_customHeight.GetInteger(); + parms.displayHz = r_displayRefresh.GetInteger(); + } else { + if ( r_vidMode.GetInteger() > modeList.Num() ) { + idLib::Printf( "r_vidMode reset from %i to 0.\n", r_vidMode.GetInteger() ); + r_vidMode.SetInteger( 0 ); + } + + parms.width = modeList[ r_vidMode.GetInteger() ].width; + parms.height = modeList[ r_vidMode.GetInteger() ].height; + parms.displayHz = modeList[ r_vidMode.GetInteger() ].displayHz; + } + } + + parms.multiSamples = r_multiSamples.GetInteger(); + if ( i == 0 ) { + parms.stereo = ( stereoRender_enable.GetInteger() == STEREO3D_QUAD_BUFFER ); + } else { + parms.stereo = false; + } + + if ( fullInit ) { + // create the context as well as setting up the window + if ( GLimp_Init( parms ) ) { + // it worked + break; + } + } else { + // just rebuild the window + if ( GLimp_SetScreenParms( parms ) ) { + // it worked + break; + } + } + + if ( i == 2 ) { + common->FatalError( "Unable to initialize OpenGL" ); + } + + if ( i == 0 ) { + // same settings, no stereo + continue; + } + +safeMode: + // if we failed, set everything back to "safe mode" + // and try again + r_vidMode.SetInteger( 0 ); + r_fullscreen.SetInteger( 1 ); + r_displayRefresh.SetInteger( 0 ); + r_multiSamples.SetInteger( 0 ); + } +} + +idStr extensions_string; + +/* +================== +R_InitOpenGL + +This function is responsible for initializing a valid OpenGL subsystem +for rendering. This is done by calling the system specific GLimp_Init, +which gives us a working OGL subsystem, then setting all necessary openGL +state, including images, vertex programs, and display lists. + +Changes to the vertex cache size or smp state require a vid_restart. + +If R_IsInitialized() is false, no rendering can take place, but +all renderSystem functions will still operate properly, notably the material +and model information functions. +================== +*/ +void R_InitOpenGL() { + + common->Printf( "----- R_InitOpenGL -----\n" ); + + if ( R_IsInitialized() ) { + common->FatalError( "R_InitOpenGL called while active" ); + } + + R_SetNewMode( true ); + + + // input and sound systems need to be tied to the new window + Sys_InitInput(); + + // get our config strings + glConfig.vendor_string = (const char *)qglGetString( GL_VENDOR ); + glConfig.renderer_string = (const char *)qglGetString( GL_RENDERER ); + glConfig.version_string = (const char *)qglGetString( GL_VERSION ); + glConfig.shading_language_string = (const char *)qglGetString( GL_SHADING_LANGUAGE_VERSION ); + glConfig.extensions_string = (const char *)qglGetString( GL_EXTENSIONS ); + + if ( glConfig.extensions_string == NULL ) { + // As of OpenGL 3.2, glGetStringi is required to obtain the available extensions + qglGetStringi = (PFNGLGETSTRINGIPROC)GLimp_ExtensionPointer( "glGetStringi" ); + + // Build the extensions string + GLint numExtensions; + qglGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions ); + extensions_string.Clear(); + for ( int i = 0; i < numExtensions; i++ ) { + extensions_string.Append( (const char*)qglGetStringi( GL_EXTENSIONS, i ) ); + // the now deprecated glGetString method usaed to create a single string with each extension separated by a space + if ( i < numExtensions - 1 ) { + extensions_string.Append( ' ' ); + } + } + glConfig.extensions_string = extensions_string.c_str(); + } + + + float glVersion = atof( glConfig.version_string ); + float glslVersion = atof( glConfig.shading_language_string ); + idLib::Printf( "OpenGL Version: %3.1f\n", glVersion ); + idLib::Printf( "OpenGL Vendor : %s\n", glConfig.vendor_string ); + idLib::Printf( "OpenGL GLSL : %3.1f\n", glslVersion ); + + // OpenGL driver constants + GLint temp; + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) { + glConfig.maxTextureSize = 256; + } + + r_initialized = true; + + // recheck all the extensions (FIXME: this might be dangerous) + R_CheckPortableExtensions(); + + renderProgManager.Init(); + + r_initialized = true; + + // allocate the vertex array range or vertex objects + vertexCache.Init(); + + // allocate the frame data, which may be more if smp is enabled + R_InitFrameData(); + + // Reset our gamma + R_SetColorMappings(); + + static bool glCheck = false; + if ( !glCheck && win32.osversion.dwMajorVersion == 6 ) { + glCheck = true; + if ( !idStr::Icmp( glConfig.vendor_string, "Microsoft" ) && idStr::FindText( glConfig.renderer_string, "OpenGL-D3D" ) != -1 ) { + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); + Sys_GrabMouseCursor( false ); + } + int ret = MessageBox( NULL, "Please install OpenGL drivers from your graphics hardware vendor to run " GAME_NAME ".\nYour OpenGL functionality is limited.", + "Insufficient OpenGL capabilities", MB_OKCANCEL | MB_ICONWARNING | MB_TASKMODAL ); + if ( ret == IDCANCEL ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + cmdSystem->ExecuteCommandBuffer(); + } + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + } + } + } +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors() { + int err; + char s[64]; + int i; + + // check for up to 10 errors pending + for ( i = 0 ; i < 10 ; i++ ) { + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + idStr::snPrintf( s, sizeof(s), "%i", err); + break; + } + + if ( !r_ignoreGLErrors.GetBool() ) { + common->Printf( "GL_CheckErrors: %s\n", s ); + } + } +} + +/* +===================== +R_ReloadSurface_f + +Reload the material displayed by r_showSurfaceInfo +===================== +*/ +static void R_ReloadSurface_f( const idCmdArgs &args ) { + modelTrace_t mt; + idVec3 start, end; + + // start far enough away that we don't hit the player model + start = tr.primaryView->renderView.vieworg + tr.primaryView->renderView.viewaxis[0] * 16; + end = start + tr.primaryView->renderView.viewaxis[0] * 1000.0f; + if ( !tr.primaryWorld->Trace( mt, start, end, 0.0f, false ) ) { + return; + } + + common->Printf( "Reloading %s\n", mt.material->GetName() ); + + // reload the decl + mt.material->base->Reload(); + + // reload any images used by the decl + mt.material->ReloadImages( false ); +} + +/* +============== +R_ListModes_f +============== +*/ +static void R_ListModes_f( const idCmdArgs &args ) { + for ( int displayNum = 0 ; ; displayNum++ ) { + idList modeList; + if ( !R_GetModeListForDisplay( displayNum, modeList ) ) { + break; + } + for ( int i = 0; i < modeList.Num() ; i++ ) { + common->Printf( "Monitor %i, mode %3i: %4i x %4i @ %ihz\n", displayNum+1, i, modeList[i].width, modeList[i].height, modeList[i].displayHz ); + } + } +} + +/* +============= +R_TestImage_f + +Display the given image centered on the screen. +testimage +testimage +============= +*/ +void R_TestImage_f( const idCmdArgs &args ) { + int imageNum; + + if ( tr.testVideo ) { + delete tr.testVideo; + tr.testVideo = NULL; + } + tr.testImage = NULL; + + if ( args.Argc() != 2 ) { + return; + } + + if ( idStr::IsNumeric( args.Argv(1) ) ) { + imageNum = atoi( args.Argv(1) ); + if ( imageNum >= 0 && imageNum < globalImages->images.Num() ) { + tr.testImage = globalImages->images[imageNum]; + } + } else { + tr.testImage = globalImages->ImageFromFile( args.Argv( 1 ), TF_DEFAULT, TR_REPEAT, TD_DEFAULT ); + } +} + +/* +============= +R_TestVideo_f + +Plays the cinematic file in a testImage +============= +*/ +void R_TestVideo_f( const idCmdArgs &args ) { + if ( tr.testVideo ) { + delete tr.testVideo; + tr.testVideo = NULL; + } + tr.testImage = NULL; + + if ( args.Argc() < 2 ) { + return; + } + + tr.testImage = globalImages->ImageFromFile( "_scratch", TF_DEFAULT, TR_REPEAT, TD_DEFAULT ); + tr.testVideo = idCinematic::Alloc(); + tr.testVideo->InitFromFile( args.Argv( 1 ), true ); + + cinData_t cin; + cin = tr.testVideo->ImageForTime( 0 ); + if ( cin.imageY == NULL ) { + delete tr.testVideo; + tr.testVideo = NULL; + tr.testImage = NULL; + return; + } + + common->Printf( "%i x %i images\n", cin.imageWidth, cin.imageHeight ); + + int len = tr.testVideo->AnimationLength(); + common->Printf( "%5.1f seconds of video\n", len * 0.001 ); + + tr.testVideoStartTime = tr.primaryRenderView.time[1]; + + // try to play the matching wav file + idStr wavString = args.Argv( ( args.Argc() == 2 ) ? 1 : 2 ); + wavString.StripFileExtension(); + wavString = wavString + ".wav"; + common->SW()->PlayShaderDirectly( wavString.c_str() ); +} + +static int R_QsortSurfaceAreas( const void *a, const void *b ) { + const idMaterial *ea, *eb; + int ac, bc; + + ea = *(idMaterial **)a; + if ( !ea->EverReferenced() ) { + ac = 0; + } else { + ac = ea->GetSurfaceArea(); + } + eb = *(idMaterial **)b; + if ( !eb->EverReferenced() ) { + bc = 0; + } else { + bc = eb->GetSurfaceArea(); + } + + if ( ac < bc ) { + return -1; + } + if ( ac > bc ) { + return 1; + } + + return idStr::Icmp( ea->GetName(), eb->GetName() ); +} + + +/* +=================== +R_ReportSurfaceAreas_f + +Prints a list of the materials sorted by surface area +=================== +*/ +#pragma warning( disable: 6385 ) // This is simply to get pass a false defect for /analyze -- if you can figure out a better way, please let Shawn know... +void R_ReportSurfaceAreas_f( const idCmdArgs &args ) { + unsigned int i; + idMaterial **list; + + const unsigned int count = declManager->GetNumDecls( DECL_MATERIAL ); + if ( count == 0 ) { + return; + } + + list = (idMaterial **)_alloca( count * sizeof( *list ) ); + + for ( i = 0 ; i < count ; i++ ) { + list[i] = (idMaterial *)declManager->DeclByIndex( DECL_MATERIAL, i, false ); + } + + qsort( list, count, sizeof( list[0] ), R_QsortSurfaceAreas ); + + // skip over ones with 0 area + for ( i = 0 ; i < count ; i++ ) { + if ( list[i]->GetSurfaceArea() > 0 ) { + break; + } + } + + for ( ; i < count ; i++ ) { + // report size in "editor blocks" + int blocks = list[i]->GetSurfaceArea() / 4096.0; + common->Printf( "%7i %s\n", blocks, list[i]->GetName() ); + } +} +#pragma warning( default: 6385 ) + + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ + +/* +==================== +R_ReadTiledPixels + +NO LONGER SUPPORTED (FIXME: make standard case work) + +Used to allow the rendering of an image larger than the actual window by +tiling it into window-sized chunks and rendering each chunk separately + +If ref isn't specified, the full session UpdateScreen will be done. +==================== +*/ +void R_ReadTiledPixels( int width, int height, byte *buffer, renderView_t *ref = NULL ) { + // include extra space for OpenGL padding to word boundaries + int sysWidth = renderSystem->GetWidth(); + int sysHeight = renderSystem->GetHeight(); + byte * temp = (byte *)R_StaticAlloc( (sysWidth+3) * sysHeight * 3 ); + + // disable scissor, so we don't need to adjust all those rects + r_useScissor.SetBool( false ); + + for ( int xo = 0 ; xo < width ; xo += sysWidth ) { + for ( int yo = 0 ; yo < height ; yo += sysHeight ) { + if ( ref ) { + // discard anything currently on the list + tr.SwapCommandBuffers( NULL, NULL, NULL, NULL ); + + // build commands to render the scene + tr.primaryWorld->RenderScene( ref ); + + // finish off these commands + const emptyCommand_t * cmd = tr.SwapCommandBuffers( NULL, NULL, NULL, NULL ); + + // issue the commands to the GPU + tr.RenderCommandBuffers( cmd ); + } else { + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + } + + int w = sysWidth; + if ( xo + w > width ) { + w = width - xo; + } + int h = sysHeight; + if ( yo + h > height ) { + h = height - yo; + } + + qglReadBuffer( GL_FRONT ); + qglReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, temp ); + + int row = ( w * 3 + 3 ) & ~3; // OpenGL pads to dword boundaries + + for ( int y = 0 ; y < h ; y++ ) { + memcpy( buffer + ( ( yo + y )* width + xo ) * 3, + temp + y * row, w * 3 ); + } + } + } + + r_useScissor.SetBool( true ); + + R_StaticFree( temp ); +} + + +/* +================== +TakeScreenshot + +Move to tr_imagefiles.c... + +Downsample is the number of steps to mipmap the image before saving it +If ref == NULL, common->UpdateScreen will be used +================== +*/ +void idRenderSystemLocal::TakeScreenshot( int width, int height, const char *fileName, int blends, renderView_t *ref ) { + byte *buffer; + int i, j, c, temp; + + takingScreenshot = true; + + const int pix = width * height; + const int bufferSize = pix * 3 + 18; + + buffer = (byte *)R_StaticAlloc( bufferSize ); + memset( buffer, 0, bufferSize ); + + if ( blends <= 1 ) { + R_ReadTiledPixels( width, height, buffer + 18, ref ); + } else { + unsigned short *shortBuffer = (unsigned short *)R_StaticAlloc(pix*2*3); + memset (shortBuffer, 0, pix*2*3); + + // enable anti-aliasing jitter + r_jitter.SetBool( true ); + + for ( i = 0 ; i < blends ; i++ ) { + R_ReadTiledPixels( width, height, buffer + 18, ref ); + + for ( j = 0 ; j < pix*3 ; j++ ) { + shortBuffer[j] += buffer[18+j]; + } + } + + // divide back to bytes + for ( i = 0 ; i < pix*3 ; i++ ) { + buffer[18+i] = shortBuffer[i] / blends; + } + + R_StaticFree( shortBuffer ); + r_jitter.SetBool( false ); + } + + // fill in the header (this is vertically flipped, which qglReadPixels emits) + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 3; + for (i=18 ; iWriteFile( fileName, buffer, c ); + + R_StaticFree( buffer ); + + takingScreenshot = false; +} + +/* +================== +R_ScreenshotFilename + +Returns a filename with digits appended +if we have saved a previous screenshot, don't scan +from the beginning, because recording demo avis can involve +thousands of shots +================== +*/ +void R_ScreenshotFilename( int &lastNumber, const char *base, idStr &fileName ) { + int a,b,c,d, e; + + bool restrict = cvarSystem->GetCVarBool( "fs_restrict" ); + cvarSystem->SetCVarBool( "fs_restrict", false ); + + lastNumber++; + if ( lastNumber > 99999 ) { + lastNumber = 99999; + } + for ( ; lastNumber < 99999 ; lastNumber++ ) { + int frac = lastNumber; + + a = frac / 10000; + frac -= a*10000; + b = frac / 1000; + frac -= b*1000; + c = frac / 100; + frac -= c*100; + d = frac / 10; + frac -= d*10; + e = frac; + + sprintf( fileName, "%s%i%i%i%i%i.tga", base, a, b, c, d, e ); + if ( lastNumber == 99999 ) { + break; + } + int len = fileSystem->ReadFile( fileName, NULL, NULL ); + if ( len <= 0 ) { + break; + } + // check again... + } + cvarSystem->SetCVarBool( "fs_restrict", restrict ); +} + +/* +================== +R_BlendedScreenShot + +screenshot +screenshot [filename] +screenshot [width] [height] +screenshot [width] [height] [samples] +================== +*/ +#define MAX_BLENDS 256 // to keep the accumulation in shorts +void R_ScreenShot_f( const idCmdArgs &args ) { + static int lastNumber = 0; + idStr checkname; + + int width = renderSystem->GetWidth(); + int height = renderSystem->GetHeight(); + int blends = 0; + + switch ( args.Argc() ) { + case 1: + width = renderSystem->GetWidth(); + height = renderSystem->GetHeight(); + blends = 1; + R_ScreenshotFilename( lastNumber, "screenshots/shot", checkname ); + break; + case 2: + width = renderSystem->GetWidth(); + height = renderSystem->GetHeight(); + blends = 1; + checkname = args.Argv( 1 ); + break; + case 3: + width = atoi( args.Argv( 1 ) ); + height = atoi( args.Argv( 2 ) ); + blends = 1; + R_ScreenshotFilename( lastNumber, "screenshots/shot", checkname ); + break; + case 4: + width = atoi( args.Argv( 1 ) ); + height = atoi( args.Argv( 2 ) ); + blends = atoi( args.Argv( 3 ) ); + if ( blends < 1 ) { + blends = 1; + } + if ( blends > MAX_BLENDS ) { + blends = MAX_BLENDS; + } + R_ScreenshotFilename( lastNumber, "screenshots/shot", checkname ); + break; + default: + common->Printf( "usage: screenshot\n screenshot \n screenshot \n screenshot \n" ); + return; + } + + // put the console away + console->Close(); + + tr.TakeScreenshot( width, height, checkname, blends, NULL ); + + common->Printf( "Wrote %s\n", checkname.c_str() ); +} + +/* +=============== +R_StencilShot +Save out a screenshot showing the stencil buffer expanded by 16x range +=============== +*/ +void R_StencilShot() { + int i, c; + + int width = tr.GetWidth(); + int height = tr.GetHeight(); + + int pix = width * height; + + c = pix * 3 + 18; + idTempArray< byte > buffer( c ); + memset( buffer.Ptr(), 0, 18 ); + + idTempArray< byte > byteBuffer( pix ); + + qglReadPixels( 0, 0, width, height, GL_STENCIL_INDEX , GL_UNSIGNED_BYTE, byteBuffer.Ptr() ); + + for ( i = 0 ; i < pix ; i++ ) { + buffer[18+i*3] = + buffer[18+i*3+1] = + // buffer[18+i*3+2] = ( byteBuffer[i] & 15 ) * 16; + buffer[18+i*3+2] = byteBuffer[i]; + } + + // fill in the header (this is vertically flipped, which qglReadPixels emits) + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + fileSystem->WriteFile( "screenshots/stencilShot.tga", buffer.Ptr(), c, "fs_savepath" ); +} + + +//============================================================================ + +static idMat3 cubeAxis[6]; + + +/* +================== +R_SampleCubeMap +================== +*/ +void R_SampleCubeMap( const idVec3 &dir, int size, byte *buffers[6], byte result[4] ) { + float adir[3]; + int axis, x, y; + + adir[0] = fabs(dir[0]); + adir[1] = fabs(dir[1]); + adir[2] = fabs(dir[2]); + + if ( dir[0] >= adir[1] && dir[0] >= adir[2] ) { + axis = 0; + } else if ( -dir[0] >= adir[1] && -dir[0] >= adir[2] ) { + axis = 1; + } else if ( dir[1] >= adir[0] && dir[1] >= adir[2] ) { + axis = 2; + } else if ( -dir[1] >= adir[0] && -dir[1] >= adir[2] ) { + axis = 3; + } else if ( dir[2] >= adir[1] && dir[2] >= adir[2] ) { + axis = 4; + } else { + axis = 5; + } + + float fx = (dir * cubeAxis[axis][1]) / (dir * cubeAxis[axis][0]); + float fy = (dir * cubeAxis[axis][2]) / (dir * cubeAxis[axis][0]); + + fx = -fx; + fy = -fy; + x = size * 0.5 * (fx + 1); + y = size * 0.5 * (fy + 1); + if ( x < 0 ) { + x = 0; + } else if ( x >= size ) { + x = size-1; + } + if ( y < 0 ) { + y = 0; + } else if ( y >= size ) { + y = size-1; + } + + result[0] = buffers[axis][(y*size+x)*4+0]; + result[1] = buffers[axis][(y*size+x)*4+1]; + result[2] = buffers[axis][(y*size+x)*4+2]; + result[3] = buffers[axis][(y*size+x)*4+3]; +} + +/* +================== +R_MakeAmbientMap_f + +R_MakeAmbientMap_f [size] + +Saves out env/_amb_ft.tga, etc +================== +*/ +void R_MakeAmbientMap_f( const idCmdArgs &args ) { + idStr fullname; + const char *baseName; + int i; + renderView_t ref; + viewDef_t primary; + int downSample; + char *extensions[6] = { "_px.tga", "_nx.tga", "_py.tga", "_ny.tga", + "_pz.tga", "_nz.tga" }; + int outSize; + byte *buffers[6]; + int width = 0, height = 0; + + if ( args.Argc() != 2 && args.Argc() != 3 ) { + common->Printf( "USAGE: ambientshot [size]\n" ); + return; + } + baseName = args.Argv( 1 ); + + downSample = 0; + if ( args.Argc() == 3 ) { + outSize = atoi( args.Argv( 2 ) ); + } else { + outSize = 32; + } + + memset( &cubeAxis, 0, sizeof( cubeAxis ) ); + cubeAxis[0][0][0] = 1; + cubeAxis[0][1][2] = 1; + cubeAxis[0][2][1] = 1; + + cubeAxis[1][0][0] = -1; + cubeAxis[1][1][2] = -1; + cubeAxis[1][2][1] = 1; + + cubeAxis[2][0][1] = 1; + cubeAxis[2][1][0] = -1; + cubeAxis[2][2][2] = -1; + + cubeAxis[3][0][1] = -1; + cubeAxis[3][1][0] = -1; + cubeAxis[3][2][2] = 1; + + cubeAxis[4][0][2] = 1; + cubeAxis[4][1][0] = -1; + cubeAxis[4][2][1] = 1; + + cubeAxis[5][0][2] = -1; + cubeAxis[5][1][0] = 1; + cubeAxis[5][2][1] = 1; + + // read all of the images + for ( i = 0 ; i < 6 ; i++ ) { + sprintf( fullname, "env/%s%s", baseName, extensions[i] ); + common->Printf( "loading %s\n", fullname.c_str() ); + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + R_LoadImage( fullname, &buffers[i], &width, &height, NULL, true ); + if ( !buffers[i] ) { + common->Printf( "failed.\n" ); + for ( i-- ; i >= 0 ; i-- ) { + Mem_Free( buffers[i] ); + } + return; + } + } + + // resample with hemispherical blending + int samples = 1000; + + byte *outBuffer = (byte *)_alloca( outSize * outSize * 4 ); + + for ( int map = 0 ; map < 2 ; map++ ) { + for ( i = 0 ; i < 6 ; i++ ) { + for ( int x = 0 ; x < outSize ; x++ ) { + for ( int y = 0 ; y < outSize ; y++ ) { + idVec3 dir; + float total[3]; + + dir = cubeAxis[i][0] + -( -1 + 2.0*x/(outSize-1) ) * cubeAxis[i][1] + -( -1 + 2.0*y/(outSize-1) ) * cubeAxis[i][2]; + dir.Normalize(); + total[0] = total[1] = total[2] = 0; + //samples = 1; + float limit = map ? 0.95 : 0.25; // small for specular, almost hemisphere for ambient + + for ( int s = 0 ; s < samples ; s++ ) { + // pick a random direction vector that is inside the unit sphere but not behind dir, + // which is a robust way to evenly sample a hemisphere + idVec3 test; + while( 1 ) { + for ( int j = 0 ; j < 3 ; j++ ) { + test[j] = -1 + 2 * (rand()&0x7fff)/(float)0x7fff; + } + if ( test.Length() > 1.0 ) { + continue; + } + test.Normalize(); + if ( test * dir > limit ) { // don't do a complete hemisphere + break; + } + } + byte result[4]; + //test = dir; + R_SampleCubeMap( test, width, buffers, result ); + total[0] += result[0]; + total[1] += result[1]; + total[2] += result[2]; + } + outBuffer[(y*outSize+x)*4+0] = total[0] / samples; + outBuffer[(y*outSize+x)*4+1] = total[1] / samples; + outBuffer[(y*outSize+x)*4+2] = total[2] / samples; + outBuffer[(y*outSize+x)*4+3] = 255; + } + } + + if ( map == 0 ) { + sprintf( fullname, "env/%s_amb%s", baseName, extensions[i] ); + } else { + sprintf( fullname, "env/%s_spec%s", baseName, extensions[i] ); + } + common->Printf( "writing %s\n", fullname.c_str() ); + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + R_WriteTGA( fullname, outBuffer, outSize, outSize ); + } + } + + for ( i = 0 ; i < 6 ; i++ ) { + if ( buffers[i] ) { + Mem_Free( buffers[i] ); + } + } +} + +//============================================================================ + + +/* +=============== +R_SetColorMappings +=============== +*/ +void R_SetColorMappings() { + float b = r_brightness.GetFloat(); + float invg = 1.0f / r_gamma.GetFloat(); + + float j = 0.0f; + for ( int i = 0; i < 256; i++, j += b ) { + int inf = idMath::Ftoi( 0xffff * pow( j / 255.0f, invg ) + 0.5f ); + tr.gammaTable[i] = idMath::ClampInt( 0, 0xFFFF, inf ); + } + + GLimp_SetGamma( tr.gammaTable, tr.gammaTable, tr.gammaTable ); +} + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( const idCmdArgs &args ) { + common->Printf( "CPU: %s\n", Sys_GetProcessorString() ); + + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + common->Printf( "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + common->Printf( "GL_RENDERER: %s\n", glConfig.renderer_string ); + common->Printf( "GL_VERSION: %s\n", glConfig.version_string ); + common->Printf( "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + if ( glConfig.wgl_extensions_string ) { + common->Printf( "WGL_EXTENSIONS: %s\n", glConfig.wgl_extensions_string ); + } + common->Printf( "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + common->Printf( "GL_MAX_TEXTURE_COORDS_ARB: %d\n", glConfig.maxTextureCoords ); + common->Printf( "GL_MAX_TEXTURE_IMAGE_UNITS_ARB: %d\n", glConfig.maxTextureImageUnits ); + + // print all the display adapters, monitors, and video modes + void DumpAllDisplayDevices(); + DumpAllDisplayDevices(); + + common->Printf( "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + common->Printf( "MODE: %d, %d x %d %s hz:", r_vidMode.GetInteger(), renderSystem->GetWidth(), renderSystem->GetHeight(), fsstrings[r_fullscreen.GetBool()] ); + if ( glConfig.displayFrequency ) { + common->Printf( "%d\n", glConfig.displayFrequency ); + } else { + common->Printf( "N/A\n" ); + } + + common->Printf( "-------\n" ); + + // WGL_EXT_swap_interval + typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); + extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + + if ( r_swapInterval.GetInteger() && wglSwapIntervalEXT != NULL ) { + common->Printf( "Forcing swapInterval %i\n", r_swapInterval.GetInteger() ); + } else { + common->Printf( "swapInterval not forced\n" ); + } + + if ( glConfig.stereoPixelFormatAvailable && glConfig.isStereoPixelFormat ) { + idLib::Printf( "OpenGl quad buffer stereo pixel format active\n" ); + } else if ( glConfig.stereoPixelFormatAvailable ) { + idLib::Printf( "OpenGl quad buffer stereo pixel available but not selected\n" ); + } else { + idLib::Printf( "OpenGl quad buffer stereo pixel format not available\n" ); + } + + idLib::Printf( "Stereo mode: " ); + switch ( renderSystem->GetStereo3DMode() ) { + case STEREO3D_OFF: idLib::Printf( "STEREO3D_OFF\n" ); break; + case STEREO3D_SIDE_BY_SIDE_COMPRESSED: idLib::Printf( "STEREO3D_SIDE_BY_SIDE_COMPRESSED\n" ); break; + case STEREO3D_TOP_AND_BOTTOM_COMPRESSED:idLib::Printf( "STEREO3D_TOP_AND_BOTTOM_COMPRESSED\n" ); break; + case STEREO3D_SIDE_BY_SIDE: idLib::Printf( "STEREO3D_SIDE_BY_SIDE\n" ); break; + case STEREO3D_HDMI_720: idLib::Printf( "STEREO3D_HDMI_720\n" ); break; + case STEREO3D_INTERLACED: idLib::Printf( "STEREO3D_INTERLACED\n" ); break; + case STEREO3D_QUAD_BUFFER: idLib::Printf( "STEREO3D_QUAD_BUFFER\n" ); break; + default:idLib::Printf( "Unknown (%i)\n", renderSystem->GetStereo3DMode() ); break; + } + + idLib::Printf( "%i multisamples\n", glConfig.multisamples ); + + common->Printf( "%5.1f cm screen width (%4.1f\" diagonal)\n", + glConfig.physicalScreenWidthInCentimeters, glConfig.physicalScreenWidthInCentimeters / 2.54f + * sqrt( (float)(16*16 + 9*9) ) / 16.0f ); + extern idCVar r_forceScreenWidthCentimeters; + if ( r_forceScreenWidthCentimeters.GetFloat() ) { + common->Printf( "screen size manually forced to %5.1f cm width (%4.1f\" diagonal)\n", + renderSystem->GetPhysicalScreenWidthInCentimeters(), renderSystem->GetPhysicalScreenWidthInCentimeters() / 2.54f + * sqrt( (float)(16*16 + 9*9) ) / 16.0f ); + } +} + +/* +================= +R_VidRestart_f +================= +*/ +void R_VidRestart_f( const idCmdArgs &args ) { + // if OpenGL isn't started, do nothing + if ( !R_IsInitialized() ) { + return; + } + + // set the mode without re-initializing the context + R_SetNewMode( false ); + +#if 0 + bool full = true; + bool forceWindow = false; + for ( int i = 1 ; i < args.Argc() ; i++ ) { + if ( idStr::Icmp( args.Argv( i ), "partial" ) == 0 ) { + full = false; + continue; + } + if ( idStr::Icmp( args.Argv( i ), "windowed" ) == 0 ) { + forceWindow = true; + continue; + } + } + + // this could take a while, so give them the cursor back ASAP + Sys_GrabMouseCursor( false ); + + // dump ambient caches + renderModelManager->FreeModelVertexCaches(); + + // free any current world interaction surfaces and vertex caches + R_FreeDerivedData(); + + // make sure the defered frees are actually freed + R_ToggleSmpFrame(); + R_ToggleSmpFrame(); + + // free the vertex caches so they will be regenerated again + vertexCache.PurgeAll(); + + // sound and input are tied to the window we are about to destroy + + if ( full ) { + // free all of our texture numbers + Sys_ShutdownInput(); + globalImages->PurgeAllImages(); + // free the context and close the window + GLimp_Shutdown(); + r_initialized = false; + + // create the new context and vertex cache + bool latch = cvarSystem->GetCVarBool( "r_fullscreen" ); + if ( forceWindow ) { + cvarSystem->SetCVarBool( "r_fullscreen", false ); + } + R_InitOpenGL(); + cvarSystem->SetCVarBool( "r_fullscreen", latch ); + + // regenerate all images + globalImages->ReloadImages( true ); + } else { + glimpParms_t parms; + parms.width = glConfig.nativeScreenWidth; + parms.height = glConfig.nativeScreenHeight; + parms.fullScreen = ( forceWindow ) ? false : r_fullscreen.GetInteger(); + parms.displayHz = r_displayRefresh.GetInteger(); + parms.multiSamples = r_multiSamples.GetInteger(); + parms.stereo = false; + GLimp_SetScreenParms( parms ); + } + + + + // make sure the regeneration doesn't use anything no longer valid + tr.viewCount++; + tr.viewDef = NULL; + + // check for problems + int err = qglGetError(); + if ( err != GL_NO_ERROR ) { + common->Printf( "glGetError() = 0x%x\n", err ); + } +#endif + +} + +/* +================= +R_InitMaterials +================= +*/ +void R_InitMaterials() { + tr.defaultMaterial = declManager->FindMaterial( "_default", false ); + if ( !tr.defaultMaterial ) { + common->FatalError( "_default material not found" ); + } + tr.defaultPointLight = declManager->FindMaterial( "lights/defaultPointLight" ); + tr.defaultProjectedLight = declManager->FindMaterial( "lights/defaultProjectedLight" ); + tr.whiteMaterial = declManager->FindMaterial( "_white" ); + tr.charSetMaterial = declManager->FindMaterial( "textures/bigchars" ); +} + + +/* +================= +R_SizeUp_f + +Keybinding command +================= +*/ +static void R_SizeUp_f( const idCmdArgs &args ) { + if ( r_screenFraction.GetInteger() + 10 > 100 ) { + r_screenFraction.SetInteger( 100 ); + } else { + r_screenFraction.SetInteger( r_screenFraction.GetInteger() + 10 ); + } +} + + +/* +================= +R_SizeDown_f + +Keybinding command +================= +*/ +static void R_SizeDown_f( const idCmdArgs &args ) { + if ( r_screenFraction.GetInteger() - 10 < 10 ) { + r_screenFraction.SetInteger( 10 ); + } else { + r_screenFraction.SetInteger( r_screenFraction.GetInteger() - 10 ); + } +} + + +/* +=============== +TouchGui_f + + this is called from the main thread +=============== +*/ +void R_TouchGui_f( const idCmdArgs &args ) { + const char *gui = args.Argv( 1 ); + + if ( !gui[0] ) { + common->Printf( "USAGE: touchGui \n" ); + return; + } + + common->Printf( "touchGui %s\n", gui ); + const bool captureToImage = false; + common->UpdateScreen( captureToImage ); + uiManager->Touch( gui ); +} + +/* +================= +R_InitCvars +================= +*/ +void R_InitCvars() { + // update latched cvars here +} + +/* +================= +R_InitCommands +================= +*/ +void R_InitCommands() { + cmdSystem->AddCommand( "sizeUp", R_SizeUp_f, CMD_FL_RENDERER, "makes the rendered view larger" ); + cmdSystem->AddCommand( "sizeDown", R_SizeDown_f, CMD_FL_RENDERER, "makes the rendered view smaller" ); + cmdSystem->AddCommand( "reloadGuis", R_ReloadGuis_f, CMD_FL_RENDERER, "reloads guis" ); + cmdSystem->AddCommand( "listGuis", R_ListGuis_f, CMD_FL_RENDERER, "lists guis" ); + cmdSystem->AddCommand( "touchGui", R_TouchGui_f, CMD_FL_RENDERER, "touches a gui" ); + cmdSystem->AddCommand( "screenshot", R_ScreenShot_f, CMD_FL_RENDERER, "takes a screenshot" ); + cmdSystem->AddCommand( "makeAmbientMap", R_MakeAmbientMap_f, CMD_FL_RENDERER|CMD_FL_CHEAT, "makes an ambient map" ); + cmdSystem->AddCommand( "gfxInfo", GfxInfo_f, CMD_FL_RENDERER, "show graphics info" ); + cmdSystem->AddCommand( "modulateLights", R_ModulateLights_f, CMD_FL_RENDERER | CMD_FL_CHEAT, "modifies shader parms on all lights" ); + cmdSystem->AddCommand( "testImage", R_TestImage_f, CMD_FL_RENDERER | CMD_FL_CHEAT, "displays the given image centered on screen", idCmdSystem::ArgCompletion_ImageName ); + cmdSystem->AddCommand( "testVideo", R_TestVideo_f, CMD_FL_RENDERER | CMD_FL_CHEAT, "displays the given cinematic", idCmdSystem::ArgCompletion_VideoName ); + cmdSystem->AddCommand( "reportSurfaceAreas", R_ReportSurfaceAreas_f, CMD_FL_RENDERER, "lists all used materials sorted by surface area" ); + cmdSystem->AddCommand( "showInteractionMemory", R_ShowInteractionMemory_f, CMD_FL_RENDERER, "shows memory used by interactions" ); + cmdSystem->AddCommand( "vid_restart", R_VidRestart_f, CMD_FL_RENDERER, "restarts renderSystem" ); + cmdSystem->AddCommand( "listRenderEntityDefs", R_ListRenderEntityDefs_f, CMD_FL_RENDERER, "lists the entity defs" ); + cmdSystem->AddCommand( "listRenderLightDefs", R_ListRenderLightDefs_f, CMD_FL_RENDERER, "lists the light defs" ); + cmdSystem->AddCommand( "listModes", R_ListModes_f, CMD_FL_RENDERER, "lists all video modes" ); + cmdSystem->AddCommand( "reloadSurface", R_ReloadSurface_f, CMD_FL_RENDERER, "reloads the decl and images for selected surface" ); +} + +/* +=============== +idRenderSystemLocal::Clear +=============== +*/ +void idRenderSystemLocal::Clear() { + registered = false; + frameCount = 0; + viewCount = 0; + frameShaderTime = 0.0f; + ambientLightVector.Zero(); + worlds.Clear(); + primaryWorld = NULL; + memset( &primaryRenderView, 0, sizeof( primaryRenderView ) ); + primaryView = NULL; + defaultMaterial = NULL; + testImage = NULL; + ambientCubeImage = NULL; + viewDef = NULL; + memset( &pc, 0, sizeof( pc ) ); + memset( &identitySpace, 0, sizeof( identitySpace ) ); + memset( renderCrops, 0, sizeof( renderCrops ) ); + currentRenderCrop = 0; + currentColorNativeBytesOrder = 0xFFFFFFFF; + currentGLState = 0; + guiRecursionLevel = 0; + guiModel = NULL; + memset( gammaTable, 0, sizeof( gammaTable ) ); + takingScreenshot = false; + + if ( unitSquareTriangles != NULL ) { + Mem_Free( unitSquareTriangles ); + unitSquareTriangles = NULL; + } + + if ( zeroOneCubeTriangles != NULL ) { + Mem_Free( zeroOneCubeTriangles ); + zeroOneCubeTriangles = NULL; + } + + if ( testImageTriangles != NULL ) { + Mem_Free( testImageTriangles ); + testImageTriangles = NULL; + } + + frontEndJobList = NULL; +} + +/* +============= +R_MakeFullScreenTris +============= +*/ +static srfTriangles_t * R_MakeFullScreenTris() { + // copy verts and indexes + srfTriangles_t * tri = (srfTriangles_t *)Mem_ClearedAlloc( sizeof( *tri ), TAG_RENDER_TOOLS ); + + tri->numIndexes = 6; + tri->numVerts = 4; + + int indexSize = tri->numIndexes * sizeof( tri->indexes[0] ); + int allocatedIndexBytes = ALIGN( indexSize, 16 ); + tri->indexes = (triIndex_t *)Mem_Alloc( allocatedIndexBytes, TAG_RENDER_TOOLS ); + + int vertexSize = tri->numVerts * sizeof( tri->verts[0] ); + int allocatedVertexBytes = ALIGN( vertexSize, 16 ); + tri->verts = (idDrawVert *)Mem_ClearedAlloc( allocatedVertexBytes, TAG_RENDER_TOOLS ); + + idDrawVert * verts = tri->verts; + + triIndex_t tempIndexes[6] = { 3, 0, 2, 2, 0, 1 }; + memcpy( tri->indexes, tempIndexes, indexSize ); + + verts[0].xyz[0] = -1.0f; + verts[0].xyz[1] = 1.0f; + verts[0].SetTexCoord( 0.0f, 1.0f ); + + verts[1].xyz[0] = 1.0f; + verts[1].xyz[1] = 1.0f; + verts[1].SetTexCoord( 1.0f, 1.0f ); + + verts[2].xyz[0] = 1.0f; + verts[2].xyz[1] = -1.0f; + verts[2].SetTexCoord( 1.0f, 0.0f ); + + verts[3].xyz[0] = -1.0f; + verts[3].xyz[1] = -1.0f; + verts[3].SetTexCoord( 0.0f, 0.0f ); + + for ( int i = 0 ; i < 4 ; i++ ) { + verts[i].SetColor( 0xffffffff ); + } + + + return tri; +} + +/* +============= +R_MakeZeroOneCubeTris +============= +*/ +static srfTriangles_t * R_MakeZeroOneCubeTris() { + srfTriangles_t * tri = (srfTriangles_t *)Mem_ClearedAlloc( sizeof( *tri ), TAG_RENDER_TOOLS ); + + tri->numVerts = 8; + tri->numIndexes = 36; + + const int indexSize = tri->numIndexes * sizeof( tri->indexes[0] ); + const int allocatedIndexBytes = ALIGN( indexSize, 16 ); + tri->indexes = (triIndex_t *)Mem_Alloc( allocatedIndexBytes, TAG_RENDER_TOOLS ); + + const int vertexSize = tri->numVerts * sizeof( tri->verts[0] ); + const int allocatedVertexBytes = ALIGN( vertexSize, 16 ); + tri->verts = (idDrawVert *)Mem_ClearedAlloc( allocatedVertexBytes, TAG_RENDER_TOOLS ); + + idDrawVert * verts = tri->verts; + + const float low = 0.0f; + const float high = 1.0f; + + idVec3 center( 0.0f ); + idVec3 mx( low, 0.0f, 0.0f ); + idVec3 px( high, 0.0f, 0.0f ); + idVec3 my( 0.0f, low, 0.0f ); + idVec3 py( 0.0f, high, 0.0f ); + idVec3 mz( 0.0f, 0.0f, low ); + idVec3 pz( 0.0f, 0.0f, high ); + + verts[0].xyz = center + mx + my + mz; + verts[1].xyz = center + px + my + mz; + verts[2].xyz = center + px + py + mz; + verts[3].xyz = center + mx + py + mz; + verts[4].xyz = center + mx + my + pz; + verts[5].xyz = center + px + my + pz; + verts[6].xyz = center + px + py + pz; + verts[7].xyz = center + mx + py + pz; + + // bottom + tri->indexes[ 0*3+0] = 2; + tri->indexes[ 0*3+1] = 3; + tri->indexes[ 0*3+2] = 0; + tri->indexes[ 1*3+0] = 1; + tri->indexes[ 1*3+1] = 2; + tri->indexes[ 1*3+2] = 0; + // back + tri->indexes[ 2*3+0] = 5; + tri->indexes[ 2*3+1] = 1; + tri->indexes[ 2*3+2] = 0; + tri->indexes[ 3*3+0] = 4; + tri->indexes[ 3*3+1] = 5; + tri->indexes[ 3*3+2] = 0; + // left + tri->indexes[ 4*3+0] = 7; + tri->indexes[ 4*3+1] = 4; + tri->indexes[ 4*3+2] = 0; + tri->indexes[ 5*3+0] = 3; + tri->indexes[ 5*3+1] = 7; + tri->indexes[ 5*3+2] = 0; + // right + tri->indexes[ 6*3+0] = 1; + tri->indexes[ 6*3+1] = 5; + tri->indexes[ 6*3+2] = 6; + tri->indexes[ 7*3+0] = 2; + tri->indexes[ 7*3+1] = 1; + tri->indexes[ 7*3+2] = 6; + // front + tri->indexes[ 8*3+0] = 3; + tri->indexes[ 8*3+1] = 2; + tri->indexes[ 8*3+2] = 6; + tri->indexes[ 9*3+0] = 7; + tri->indexes[ 9*3+1] = 3; + tri->indexes[ 9*3+2] = 6; + // top + tri->indexes[10*3+0] = 4; + tri->indexes[10*3+1] = 7; + tri->indexes[10*3+2] = 6; + tri->indexes[11*3+0] = 5; + tri->indexes[11*3+1] = 4; + tri->indexes[11*3+2] = 6; + + for ( int i = 0 ; i < 4 ; i++ ) { + verts[i].SetColor( 0xffffffff ); + } + + return tri; +} + +/* +================ +R_MakeTestImageTriangles + +Initializes the Test Image Triangles +================ +*/ +srfTriangles_t* R_MakeTestImageTriangles() { + srfTriangles_t * tri = (srfTriangles_t *)Mem_ClearedAlloc( sizeof( *tri ), TAG_RENDER_TOOLS ); + + tri->numIndexes = 6; + tri->numVerts = 4; + + int indexSize = tri->numIndexes * sizeof( tri->indexes[0] ); + int allocatedIndexBytes = ALIGN( indexSize, 16 ); + tri->indexes = (triIndex_t *)Mem_Alloc( allocatedIndexBytes, TAG_RENDER_TOOLS ); + + int vertexSize = tri->numVerts * sizeof( tri->verts[0] ); + int allocatedVertexBytes = ALIGN( vertexSize, 16 ); + tri->verts = (idDrawVert *)Mem_ClearedAlloc( allocatedVertexBytes, TAG_RENDER_TOOLS ); + + ALIGNTYPE16 triIndex_t tempIndexes[6] = { 3, 0, 2, 2, 0, 1 }; + memcpy( tri->indexes, tempIndexes, indexSize ); + + idDrawVert* tempVerts = tri->verts; + tempVerts[0].xyz[0] = 0.0f; + tempVerts[0].xyz[1] = 0.0f; + tempVerts[0].xyz[2] = 0; + tempVerts[0].SetTexCoord( 0.0, 0.0f ); + + tempVerts[1].xyz[0] = 1.0f; + tempVerts[1].xyz[1] = 0.0f; + tempVerts[1].xyz[2] = 0; + tempVerts[1].SetTexCoord( 1.0f, 0.0f ); + + tempVerts[2].xyz[0] = 1.0f; + tempVerts[2].xyz[1] = 1.0f; + tempVerts[2].xyz[2] = 0; + tempVerts[2].SetTexCoord( 1.0f, 1.0f ); + + tempVerts[3].xyz[0] = 0.0f; + tempVerts[3].xyz[1] = 1.0f; + tempVerts[3].xyz[2] = 0; + tempVerts[3].SetTexCoord( 0.0f, 1.0f ); + + for ( int i = 0; i < 4; i++ ) { + tempVerts[i].SetColor( 0xFFFFFFFF ); + } + return tri; +} + +/* +=============== +idRenderSystemLocal::Init +=============== +*/ +void idRenderSystemLocal::Init() { + + common->Printf( "------- Initializing renderSystem --------\n" ); + + // clear all our internal state + viewCount = 1; // so cleared structures never match viewCount + // we used to memset tr, but now that it is a class, we can't, so + // there may be other state we need to reset + + ambientLightVector[0] = 0.5f; + ambientLightVector[1] = 0.5f - 0.385f; + ambientLightVector[2] = 0.8925f; + ambientLightVector[3] = 1.0f; + + memset( &backEnd, 0, sizeof( backEnd ) ); + + R_InitCvars(); + + R_InitCommands(); + + guiModel = new (TAG_RENDER) idGuiModel; + guiModel->Clear(); + tr_guiModel = guiModel; // for DeviceContext fast path + + globalImages->Init(); + + idCinematic::InitCinematic( ); + + // build brightness translation tables + R_SetColorMappings(); + + R_InitMaterials(); + + renderModelManager->Init(); + + // set the identity space + identitySpace.modelMatrix[0*4+0] = 1.0f; + identitySpace.modelMatrix[1*4+1] = 1.0f; + identitySpace.modelMatrix[2*4+2] = 1.0f; + + // make sure the tr.unitSquareTriangles data is current in the vertex / index cache + if ( unitSquareTriangles == NULL ) { + unitSquareTriangles = R_MakeFullScreenTris(); + } + // make sure the tr.zeroOneCubeTriangles data is current in the vertex / index cache + if ( zeroOneCubeTriangles == NULL ) { + zeroOneCubeTriangles = R_MakeZeroOneCubeTris(); + } + // make sure the tr.testImageTriangles data is current in the vertex / index cache + if ( testImageTriangles == NULL ) { + testImageTriangles = R_MakeTestImageTriangles(); + } + + frontEndJobList = parallelJobManager->AllocJobList( JOBLIST_RENDERER_FRONTEND, JOBLIST_PRIORITY_MEDIUM, 2048, 0, NULL ); + + // make sure the command buffers are ready to accept the first screen update + SwapCommandBuffers( NULL, NULL, NULL, NULL ); + + common->Printf( "renderSystem initialized.\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +=============== +idRenderSystemLocal::Shutdown +=============== +*/ +void idRenderSystemLocal::Shutdown() { + common->Printf( "idRenderSystem::Shutdown()\n" ); + + fonts.DeleteContents(); + + if ( R_IsInitialized() ) { + globalImages->PurgeAllImages(); + } + + renderModelManager->Shutdown(); + + idCinematic::ShutdownCinematic( ); + + globalImages->Shutdown(); + + // free frame memory + R_ShutdownFrameData(); + + UnbindBufferObjects(); + + // free the vertex cache, which should have nothing allocated now + vertexCache.Shutdown(); + + RB_ShutdownDebugTools(); + + delete guiModel; + + parallelJobManager->FreeJobList( frontEndJobList ); + + Clear(); + + ShutdownOpenGL(); +} + +/* +======================== +idRenderSystemLocal::ResetGuiModels +======================== +*/ +void idRenderSystemLocal::ResetGuiModels() { + delete guiModel; + guiModel = new (TAG_RENDER) idGuiModel; + guiModel->Clear(); + guiModel->BeginFrame(); + tr_guiModel = guiModel; // for DeviceContext fast path +} + +/* +======================== +idRenderSystemLocal::BeginLevelLoad +======================== +*/ +void idRenderSystemLocal::BeginLevelLoad() { + globalImages->BeginLevelLoad(); + renderModelManager->BeginLevelLoad(); + + // Re-Initialize the Default Materials if needed. + R_InitMaterials(); +} + +/* +======================== +idRenderSystemLocal::LoadLevelImages +======================== +*/ +void idRenderSystemLocal::LoadLevelImages() { + globalImages->LoadLevelImages( false ); +} + +/* +======================== +idRenderSystemLocal::Preload +======================== +*/ +void idRenderSystemLocal::Preload( const idPreloadManifest &manifest, const char *mapName ) { + globalImages->Preload( manifest, true ); + uiManager->Preload( mapName ); + renderModelManager->Preload( manifest ); +} + +/* +======================== +idRenderSystemLocal::EndLevelLoad +======================== +*/ +void idRenderSystemLocal::EndLevelLoad() { + renderModelManager->EndLevelLoad(); + globalImages->EndLevelLoad(); +} + +/* +======================== +idRenderSystemLocal::BeginAutomaticBackgroundSwaps +======================== +*/ +void idRenderSystemLocal::BeginAutomaticBackgroundSwaps( autoRenderIconType_t icon ) { +} + +/* +======================== +idRenderSystemLocal::EndAutomaticBackgroundSwaps +======================== +*/ +void idRenderSystemLocal::EndAutomaticBackgroundSwaps() { +} + +/* +======================== +idRenderSystemLocal::AreAutomaticBackgroundSwapsRunning +======================== +*/ +bool idRenderSystemLocal::AreAutomaticBackgroundSwapsRunning( autoRenderIconType_t * icon ) const { + return false; +} + +/* +============ +idRenderSystemLocal::RegisterFont +============ +*/ +idFont * idRenderSystemLocal::RegisterFont( const char * fontName ) { + + idStrStatic< MAX_OSPATH > baseFontName = fontName; + baseFontName.Replace( "fonts/", "" ); + for ( int i = 0; i < fonts.Num(); i++ ) { + if ( idStr::Icmp( fonts[i]->GetName(), baseFontName ) == 0 ) { + fonts[i]->Touch(); + return fonts[i]; + } + } + idFont * newFont = new (TAG_FONT) idFont( baseFontName ); + fonts.Append( newFont ); + return newFont; +} + +/* +======================== +idRenderSystemLocal::ResetFonts +======================== +*/ +void idRenderSystemLocal::ResetFonts() { + fonts.DeleteContents( true ); +} +/* +======================== +idRenderSystemLocal::InitOpenGL +======================== +*/ +void idRenderSystemLocal::InitOpenGL() { + // if OpenGL isn't started, start it now + if ( !R_IsInitialized() ) { + R_InitOpenGL(); + + // Reloading images here causes the rendertargets to get deleted. Figure out how to handle this properly on 360 + globalImages->ReloadImages( true ); + + int err = qglGetError(); + if ( err != GL_NO_ERROR ) { + common->Printf( "glGetError() = 0x%x\n", err ); + } + } +} + +/* +======================== +idRenderSystemLocal::ShutdownOpenGL +======================== +*/ +void idRenderSystemLocal::ShutdownOpenGL() { + // free the context and close the window + R_ShutdownFrameData(); + GLimp_Shutdown(); + r_initialized = false; +} + +/* +======================== +idRenderSystemLocal::IsOpenGLRunning +======================== +*/ +bool idRenderSystemLocal::IsOpenGLRunning() const { + return R_IsInitialized(); +} + +/* +======================== +idRenderSystemLocal::IsFullScreen +======================== +*/ +bool idRenderSystemLocal::IsFullScreen() const { + return glConfig.isFullscreen != 0; +} + +/* +======================== +idRenderSystemLocal::GetWidth +======================== +*/ +int idRenderSystemLocal::GetWidth() const { + if ( glConfig.stereo3Dmode == STEREO3D_SIDE_BY_SIDE || glConfig.stereo3Dmode == STEREO3D_SIDE_BY_SIDE_COMPRESSED ) { + return glConfig.nativeScreenWidth >> 1; + } + return glConfig.nativeScreenWidth; +} + +/* +======================== +idRenderSystemLocal::GetHeight +======================== +*/ +int idRenderSystemLocal::GetHeight() const { + if ( glConfig.stereo3Dmode == STEREO3D_HDMI_720 ) { + return 720; + } + extern idCVar stereoRender_warp; + if ( glConfig.stereo3Dmode == STEREO3D_SIDE_BY_SIDE && stereoRender_warp.GetBool() ) { + // for the Rift, render a square aspect view that will be symetric for the optics + return glConfig.nativeScreenWidth >> 1; + } + if ( glConfig.stereo3Dmode == STEREO3D_INTERLACED || glConfig.stereo3Dmode == STEREO3D_TOP_AND_BOTTOM_COMPRESSED ) { + return glConfig.nativeScreenHeight >> 1; + } + return glConfig.nativeScreenHeight; +} + +/* +======================== +idRenderSystemLocal::GetStereo3DMode +======================== +*/ +stereo3DMode_t idRenderSystemLocal::GetStereo3DMode() const { + return glConfig.stereo3Dmode; +} + +/* +======================== +idRenderSystemLocal::IsStereoScopicRenderingSupported +======================== +*/ +bool idRenderSystemLocal::IsStereoScopicRenderingSupported() const { + return true; +} + +/* +======================== +idRenderSystemLocal::HasQuadBufferSupport +======================== +*/ +bool idRenderSystemLocal::HasQuadBufferSupport() const { + return glConfig.stereoPixelFormatAvailable; +} + +/* +======================== +idRenderSystemLocal::GetStereoScopicRenderingMode +======================== +*/ +stereo3DMode_t idRenderSystemLocal::GetStereoScopicRenderingMode() const { + return ( !IsStereoScopicRenderingSupported() ) ? STEREO3D_OFF : (stereo3DMode_t)stereoRender_enable.GetInteger(); +} + +/* +======================== +idRenderSystemLocal::IsStereoScopicRenderingSupported +======================== +*/ +void idRenderSystemLocal::EnableStereoScopicRendering( const stereo3DMode_t mode ) const { + stereoRender_enable.SetInteger( mode ); +} + +/* +======================== +idRenderSystemLocal::GetPixelAspect +======================== +*/ +float idRenderSystemLocal::GetPixelAspect() const { + switch( glConfig.stereo3Dmode ) { + case STEREO3D_SIDE_BY_SIDE_COMPRESSED: + return glConfig.pixelAspect * 2.0f; + case STEREO3D_TOP_AND_BOTTOM_COMPRESSED: + case STEREO3D_INTERLACED: + return glConfig.pixelAspect * 0.5f; + default: + return glConfig.pixelAspect; + } +} + +/* +======================== +idRenderSystemLocal::GetPhysicalScreenWidthInCentimeters + +This is used to calculate stereoscopic screen offset for a given interocular distance. +======================== +*/ +idCVar r_forceScreenWidthCentimeters( "r_forceScreenWidthCentimeters", "0", CVAR_RENDERER | CVAR_ARCHIVE, "Override screen width returned by hardware" ); +float idRenderSystemLocal::GetPhysicalScreenWidthInCentimeters() const { + if ( r_forceScreenWidthCentimeters.GetFloat() > 0 ) { + return r_forceScreenWidthCentimeters.GetFloat(); + } + return glConfig.physicalScreenWidthInCentimeters; +} diff --git a/neo/renderer/RenderTexture.h b/neo/renderer/RenderTexture.h new file mode 100644 index 00000000..9fe3140c --- /dev/null +++ b/neo/renderer/RenderTexture.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __RENDERTEXTURE_H__ +#define __RENDERTEXTURE_H__ + +/* +================================================================================================ + + Render Texture + +================================================================================================ +*/ + +#define CELL_GCM_INVALID_PITCH 64 + +/* +================================================ +idRenderTexture holds both the color and depth images that are made +resident on the video hardware. +================================================ +*/ +class idRenderTexture { +public: + idRenderTexture(); + ~idRenderTexture(); + + ID_INLINE int GetWidth() const { return ( colorImage != NULL ) ? colorImage->GetUploadWidth() : depthImage->GetUploadWidth(); } + ID_INLINE int GetHeight() const { return ( colorImage != NULL ) ? colorImage->GetUploadHeight() : depthImage->GetUploadHeight(); } + + ID_INLINE idImage * GetColorImage() const { return colorImage; } + ID_INLINE idImage * GetDepthImage() const { return depthImage; } + + + void Resize( int width, int height ); + + void MakeCurrent( int level = 0, int side = 0 ); + +private: + idImage * colorImage; + idImage * depthImage; + int targetWidth; + int targetHeight; + +}; + +#endif //!__RENDERTEXTURE_H__ diff --git a/neo/renderer/RenderWorld.cpp b/neo/renderer/RenderWorld.cpp new file mode 100644 index 00000000..2932a318 --- /dev/null +++ b/neo/renderer/RenderWorld.cpp @@ -0,0 +1,2111 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +=================== +R_ListRenderLightDefs_f +=================== +*/ +void R_ListRenderLightDefs_f( const idCmdArgs &args ) { + int i; + idRenderLightLocal *ldef; + + if ( !tr.primaryWorld ) { + return; + } + int active = 0; + int totalRef = 0; + int totalIntr = 0; + + for ( i = 0; i < tr.primaryWorld->lightDefs.Num(); i++ ) { + ldef = tr.primaryWorld->lightDefs[i]; + if ( !ldef ) { + common->Printf( "%4i: FREED\n", i ); + continue; + } + + // count up the interactions + int iCount = 0; + for ( idInteraction *inter = ldef->firstInteraction; inter != NULL; inter = inter->lightNext ) { + iCount++; + } + totalIntr += iCount; + + // count up the references + int rCount = 0; + for ( areaReference_t *ref = ldef->references; ref; ref = ref->ownerNext ) { + rCount++; + } + totalRef += rCount; + + common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, ldef->lightShader->GetName()); + active++; + } + + common->Printf( "%i lightDefs, %i interactions, %i areaRefs\n", active, totalIntr, totalRef ); +} + +/* +=================== +R_ListRenderEntityDefs_f +=================== +*/ +void R_ListRenderEntityDefs_f( const idCmdArgs &args ) { + int i; + idRenderEntityLocal *mdef; + + if ( !tr.primaryWorld ) { + return; + } + int active = 0; + int totalRef = 0; + int totalIntr = 0; + + for ( i = 0; i < tr.primaryWorld->entityDefs.Num(); i++ ) { + mdef = tr.primaryWorld->entityDefs[i]; + if ( !mdef ) { + common->Printf( "%4i: FREED\n", i ); + continue; + } + + // count up the interactions + int iCount = 0; + for ( idInteraction *inter = mdef->firstInteraction; inter != NULL; inter = inter->entityNext ) { + iCount++; + } + totalIntr += iCount; + + // count up the references + int rCount = 0; + for ( areaReference_t *ref = mdef->entityRefs; ref; ref = ref->ownerNext ) { + rCount++; + } + totalRef += rCount; + + common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, mdef->parms.hModel->Name()); + active++; + } + + common->Printf( "total active: %i\n", active ); +} + +/* +=================== +idRenderWorldLocal::idRenderWorldLocal +=================== +*/ +idRenderWorldLocal::idRenderWorldLocal() { + mapName.Clear(); + mapTimeStamp = FILE_NOT_FOUND_TIMESTAMP; + + generateAllInteractionsCalled = false; + + areaNodes = NULL; + numAreaNodes = 0; + + portalAreas = NULL; + numPortalAreas = 0; + + doublePortals = NULL; + numInterAreaPortals = 0; + + interactionTable = 0; + interactionTableWidth = 0; + interactionTableHeight = 0; + + for ( int i = 0; i < decals.Num(); i++ ) { + decals[i].entityHandle = -1; + decals[i].lastStartTime = 0; + decals[i].decals = new (TAG_MODEL) idRenderModelDecal(); + } + + for ( int i = 0; i < overlays.Num(); i++ ) { + overlays[i].entityHandle = -1; + overlays[i].lastStartTime = 0; + overlays[i].overlays = new (TAG_MODEL) idRenderModelOverlay(); + } +} + +/* +=================== +idRenderWorldLocal::~idRenderWorldLocal +=================== +*/ +idRenderWorldLocal::~idRenderWorldLocal() { + // free all the entityDefs, lightDefs, portals, etc + FreeWorld(); + + for ( int i = 0; i < decals.Num(); i++ ) { + delete decals[i].decals; + } + + for ( int i = 0; i < overlays.Num(); i++ ) { + delete overlays[i].overlays; + } + + // free up the debug lines, polys, and text + RB_ClearDebugPolygons( 0 ); + RB_ClearDebugLines( 0 ); + RB_ClearDebugText( 0 ); +} + +/* +=================== +ResizeInteractionTable +=================== +*/ +void idRenderWorldLocal::ResizeInteractionTable() { + // we overflowed the interaction table, so make it larger + common->Printf( "idRenderWorldLocal::ResizeInteractionTable: overflowed interactionTable, resizing\n" ); + + const int oldInteractionTableWidth = interactionTableWidth; + const int oldIinteractionTableHeight = interactionTableHeight; + idInteraction ** oldInteractionTable = interactionTable; + + // build the interaction table + // this will be dynamically resized if the entity / light counts grow too much + interactionTableWidth = entityDefs.Num() + 100; + interactionTableHeight = lightDefs.Num() + 100; + const int size = interactionTableWidth * interactionTableHeight * sizeof( *interactionTable ); + interactionTable = (idInteraction **)R_ClearedStaticAlloc( size ); + for ( int l = 0; l < oldIinteractionTableHeight; l++ ) { + for ( int e = 0; e < oldInteractionTableWidth; e++ ) { + interactionTable[ l * interactionTableWidth + e ] = oldInteractionTable[ l * oldInteractionTableWidth + e ]; + } + } + + R_StaticFree( oldInteractionTable ); +} + +/* +=================== +AddEntityDef +=================== +*/ +qhandle_t idRenderWorldLocal::AddEntityDef( const renderEntity_t *re ){ + // try and reuse a free spot + int entityHandle = entityDefs.FindNull(); + if ( entityHandle == -1 ) { + entityHandle = entityDefs.Append( NULL ); + + if ( interactionTable && entityDefs.Num() > interactionTableWidth ) { + ResizeInteractionTable(); + } + } + + UpdateEntityDef( entityHandle, re ); + + return entityHandle; +} + +/* +============== +UpdateEntityDef + +Does not write to the demo file, which will only be updated for +visible entities +============== +*/ +int c_callbackUpdate; + +void idRenderWorldLocal::UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ) { + if ( r_skipUpdates.GetBool() ) { + return; + } + + tr.pc.c_entityUpdates++; + + if ( !re->hModel && !re->callback ) { + common->Error( "idRenderWorld::UpdateEntityDef: NULL hModel" ); + } + + // create new slots if needed + if ( entityHandle < 0 || entityHandle > LUDICROUS_INDEX ) { + common->Error( "idRenderWorld::UpdateEntityDef: index = %i", entityHandle ); + } + while ( entityHandle >= entityDefs.Num() ) { + entityDefs.Append( NULL ); + } + + idRenderEntityLocal *def = entityDefs[entityHandle]; + if ( def != NULL ) { + + if ( !re->forceUpdate ) { + + // check for exact match (OPTIMIZE: check through pointers more) + if ( !re->joints && !re->callbackData && !def->dynamicModel && !memcmp( re, &def->parms, sizeof( *re ) ) ) { + return; + } + + // if the only thing that changed was shaderparms, we can just leave things as they are + // after updating parms + + // if we have a callback function and the bounds, origin, axis and model match, + // then we can leave the references as they are + if ( re->callback ) { + + bool axisMatch = ( re->axis == def->parms.axis ); + bool originMatch = ( re->origin == def->parms.origin ); + bool boundsMatch = ( re->bounds == def->localReferenceBounds ); + bool modelMatch = ( re->hModel == def->parms.hModel ); + + if ( boundsMatch && originMatch && axisMatch && modelMatch ) { + // only clear the dynamic model and interaction surfaces if they exist + c_callbackUpdate++; + R_ClearEntityDefDynamicModel( def ); + def->parms = *re; + return; + } + } + } + + // save any decals if the model is the same, allowing marks to move with entities + if ( def->parms.hModel == re->hModel ) { + R_FreeEntityDefDerivedData( def, true, true ); + } else { + R_FreeEntityDefDerivedData( def, false, false ); + } + } else { + // creating a new one + def = new (TAG_RENDER_ENTITY) idRenderEntityLocal; + entityDefs[entityHandle] = def; + + def->world = this; + def->index = entityHandle; + } + + def->parms = *re; + + def->lastModifiedFrameNum = tr.frameCount; + if ( common->WriteDemo() && def->archived ) { + WriteFreeEntity( entityHandle ); + def->archived = false; + } + + // optionally immediately issue any callbacks + if ( !r_useEntityCallbacks.GetBool() && def->parms.callback != NULL ) { + R_IssueEntityDefCallback( def ); + } + + // trigger entities don't need to get linked in and processed, + // they only exist for editor use + if ( def->parms.hModel != NULL && !def->parms.hModel->ModelHasDrawingSurfaces() ) { + return; + } + + // based on the model bounds, add references in each area + // that may contain the updated surface + R_CreateEntityRefs( def ); +} + +/* +=================== +FreeEntityDef + +Frees all references and lit surfaces from the model, and +NULL's out it's entry in the world list +=================== +*/ +void idRenderWorldLocal::FreeEntityDef( qhandle_t entityHandle ) { + idRenderEntityLocal *def; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Printf( "idRenderWorld::FreeEntityDef: handle %i > %i\n", entityHandle, entityDefs.Num() ); + return; + } + + def = entityDefs[entityHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::FreeEntityDef: handle %i is NULL\n", entityHandle ); + return; + } + + R_FreeEntityDefDerivedData( def, false, false ); + + if ( common->WriteDemo() && def->archived ) { + WriteFreeEntity( entityHandle ); + } + + // if we are playing a demo, these will have been freed + // in R_FreeEntityDefDerivedData(), otherwise the gui + // object still exists in the game + + def->parms.gui[ 0 ] = NULL; + def->parms.gui[ 1 ] = NULL; + def->parms.gui[ 2 ] = NULL; + + delete def; + entityDefs[ entityHandle ] = NULL; +} + +/* +================== +GetRenderEntity +================== +*/ +const renderEntity_t *idRenderWorldLocal::GetRenderEntity( qhandle_t entityHandle ) const { + idRenderEntityLocal *def; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Printf( "idRenderWorld::GetRenderEntity: invalid handle %i [0, %i]\n", entityHandle, entityDefs.Num() ); + return NULL; + } + + def = entityDefs[entityHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::GetRenderEntity: handle %i is NULL\n", entityHandle ); + return NULL; + } + + return &def->parms; +} + +/* +================== +AddLightDef +================== +*/ +qhandle_t idRenderWorldLocal::AddLightDef( const renderLight_t *rlight ) { + // try and reuse a free spot + int lightHandle = lightDefs.FindNull(); + + if ( lightHandle == -1 ) { + lightHandle = lightDefs.Append( NULL ); + if ( interactionTable && lightDefs.Num() > interactionTableHeight ) { + ResizeInteractionTable(); + } + } + UpdateLightDef( lightHandle, rlight ); + + return lightHandle; +} + +/* +================= +UpdateLightDef + +The generation of all the derived interaction data will +usually be deferred until it is visible in a scene + +Does not write to the demo file, which will only be done for visible lights +================= +*/ +void idRenderWorldLocal::UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ) { + if ( r_skipUpdates.GetBool() ) { + return; + } + + tr.pc.c_lightUpdates++; + + // create new slots if needed + if ( lightHandle < 0 || lightHandle > LUDICROUS_INDEX ) { + common->Error( "idRenderWorld::UpdateLightDef: index = %i", lightHandle ); + } + while ( lightHandle >= lightDefs.Num() ) { + lightDefs.Append( NULL ); + } + + bool justUpdate = false; + idRenderLightLocal *light = lightDefs[lightHandle]; + if ( light ) { + // if the shape of the light stays the same, we don't need to dump + // any of our derived data, because shader parms are calculated every frame + if ( rlight->axis == light->parms.axis && rlight->end == light->parms.end && + rlight->lightCenter == light->parms.lightCenter && rlight->lightRadius == light->parms.lightRadius && + rlight->noShadows == light->parms.noShadows && rlight->origin == light->parms.origin && + rlight->parallel == light->parms.parallel && rlight->pointLight == light->parms.pointLight && + rlight->right == light->parms.right && rlight->start == light->parms.start && + rlight->target == light->parms.target && rlight->up == light->parms.up && + rlight->shader == light->lightShader && rlight->prelightModel == light->parms.prelightModel ) { + justUpdate = true; + } else { + // if we are updating shadows, the prelight model is no longer valid + light->lightHasMoved = true; + R_FreeLightDefDerivedData( light ); + } + } else { + // create a new one + light = new (TAG_RENDER_LIGHT) idRenderLightLocal; + lightDefs[lightHandle] = light; + + light->world = this; + light->index = lightHandle; + } + + light->parms = *rlight; + light->lastModifiedFrameNum = tr.frameCount; + if ( common->WriteDemo() && light->archived ) { + WriteFreeLight( lightHandle ); + light->archived = false; + } + + // new for BFG edition: force noShadows on spectrum lights so teleport spawns + // don't cause such a slowdown. Hell writing shouldn't be shadowed anyway... + if ( light->parms.shader && light->parms.shader->Spectrum() ) { + light->parms.noShadows = true; + } + + if ( light->lightHasMoved ) { + light->parms.prelightModel = NULL; + } + + if ( !justUpdate ) { + R_CreateLightRefs( light ); + } +} + +/* +==================== +FreeLightDef + +Frees all references and lit surfaces from the light, and +NULL's out it's entry in the world list +==================== +*/ +void idRenderWorldLocal::FreeLightDef( qhandle_t lightHandle ) { + idRenderLightLocal *light; + + if ( lightHandle < 0 || lightHandle >= lightDefs.Num() ) { + common->Printf( "idRenderWorld::FreeLightDef: invalid handle %i [0, %i]\n", lightHandle, lightDefs.Num() ); + return; + } + + light = lightDefs[lightHandle]; + if ( !light ) { + common->Printf( "idRenderWorld::FreeLightDef: handle %i is NULL\n", lightHandle ); + return; + } + + R_FreeLightDefDerivedData( light ); + + if ( common->WriteDemo() && light->archived ) { + WriteFreeLight( lightHandle ); + } + + delete light; + lightDefs[lightHandle] = NULL; +} + +/* +================== +GetRenderLight +================== +*/ +const renderLight_t *idRenderWorldLocal::GetRenderLight( qhandle_t lightHandle ) const { + idRenderLightLocal *def; + + if ( lightHandle < 0 || lightHandle >= lightDefs.Num() ) { + common->Printf( "idRenderWorld::GetRenderLight: handle %i > %i\n", lightHandle, lightDefs.Num() ); + return NULL; + } + + def = lightDefs[lightHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::GetRenderLight: handle %i is NULL\n", lightHandle ); + return NULL; + } + + return &def->parms; +} + +/* +================ +idRenderWorldLocal::ProjectDecalOntoWorld +================ +*/ +void idRenderWorldLocal::ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { + decalProjectionParms_t globalParms; + + if ( !idRenderModelDecal::CreateProjectionParms( globalParms, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) ) { + return; + } + + // get the world areas touched by the projection volume + int areas[10]; + int numAreas = BoundsInAreas( globalParms.projectionBounds, areas, 10 ); + + // check all areas for models + for ( int i = 0; i < numAreas; i++ ) { + + const portalArea_t * area = &portalAreas[ areas[i] ]; + + // check all models in this area + for ( const areaReference_t * ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) { + idRenderEntityLocal * def = ref->entity; + + if ( def->parms.noOverlays ) { + continue; + } + + if ( def->parms.customShader != NULL && !def->parms.customShader->AllowOverlays() ) { + continue; + } + + // completely ignore any dynamic or callback models + const idRenderModel * model = def->parms.hModel; + if ( def->parms.callback != NULL || model == NULL || model->IsDynamicModel() != DM_STATIC ) { + continue; + } + + idBounds bounds; + bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis ); + + // if the model bounds do not overlap with the projection bounds + decalProjectionParms_t localParms; + if ( !globalParms.projectionBounds.IntersectsBounds( bounds ) ) { + continue; + } + + // transform the bounding planes, fade planes and texture axis into local space + idRenderModelDecal::GlobalProjectionParmsToLocal( localParms, globalParms, def->parms.origin, def->parms.axis ); + localParms.force = ( def->parms.customShader != NULL ); + + if ( def->decals == NULL ) { + def->decals = AllocDecal( def->index, startTime ); + } + def->decals->AddDeferredDecal( localParms ); + } + } +} + +/* +==================== +idRenderWorldLocal::ProjectDecal +==================== +*/ +void idRenderWorldLocal::ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle ); + return; + } + + idRenderEntityLocal *def = entityDefs[ entityHandle ]; + if ( def == NULL ) { + return; + } + + const idRenderModel *model = def->parms.hModel; + + if ( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback != NULL ) { + return; + } + + decalProjectionParms_t globalParms; + if ( !idRenderModelDecal::CreateProjectionParms( globalParms, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) ) { + return; + } + + idBounds bounds; + bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis ); + + // if the model bounds do not overlap with the projection bounds + if ( !globalParms.projectionBounds.IntersectsBounds( bounds ) ) { + return; + } + + // transform the bounding planes, fade planes and texture axis into local space + decalProjectionParms_t localParms; + idRenderModelDecal::GlobalProjectionParmsToLocal( localParms, globalParms, def->parms.origin, def->parms.axis ); + localParms.force = ( def->parms.customShader != NULL ); + + if ( def->decals == NULL ) { + def->decals = AllocDecal( def->index, startTime ); + } + def->decals->AddDeferredDecal( localParms ); +} + +/* +==================== +idRenderWorldLocal::ProjectOverlay +==================== +*/ +void idRenderWorldLocal::ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material, const int startTime ) { + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle ); + return; + } + + idRenderEntityLocal *def = entityDefs[ entityHandle ]; + if ( def == NULL ) { + return; + } + + const idRenderModel * model = def->parms.hModel; + if ( model->IsDynamicModel() != DM_CACHED ) { // FIXME: probably should be MD5 only + return; + } + + overlayProjectionParms_t localParms; + localParms.localTextureAxis[0] = localTextureAxis[0]; + localParms.localTextureAxis[1] = localTextureAxis[1]; + localParms.material = material; + localParms.startTime = startTime; + + if ( def->overlays == NULL ) { + def->overlays = AllocOverlay( def->index, startTime ); + } + def->overlays->AddDeferredOverlay( localParms ); +} + +/* +==================== +idRenderWorldLocal::AllocDecal +==================== +*/ +idRenderModelDecal * idRenderWorldLocal::AllocDecal( qhandle_t newEntityHandle, int startTime ) { + int oldest = 0; + int oldestTime = MAX_TYPE( oldestTime ); + for ( int i = 0; i < decals.Num(); i++ ) { + if ( decals[i].lastStartTime < oldestTime ) { + oldestTime = decals[i].lastStartTime; + oldest = i; + } + } + + // remove any reference another model may still have to this decal + if ( decals[oldest].entityHandle >= 0 && decals[oldest].entityHandle < entityDefs.Num() ) { + idRenderEntityLocal *def = entityDefs[decals[oldest].entityHandle]; + if ( def != NULL && def->decals == decals[oldest].decals ) { + def->decals = NULL; + } + } + + decals[oldest].entityHandle = newEntityHandle; + decals[oldest].lastStartTime = startTime; + decals[oldest].decals->ReUse(); + + return decals[oldest].decals; +} + +/* +==================== +idRenderWorldLocal::AllocOverlay +==================== +*/ +idRenderModelOverlay * idRenderWorldLocal::AllocOverlay( qhandle_t newEntityHandle, int startTime ) { + int oldest = 0; + int oldestTime = MAX_TYPE( oldestTime ); + for ( int i = 0; i < overlays.Num(); i++ ) { + if ( overlays[i].lastStartTime < oldestTime ) { + oldestTime = overlays[i].lastStartTime; + oldest = i; + } + } + + // remove any reference another model may still have to this overlay + if ( overlays[oldest].entityHandle >= 0 && overlays[oldest].entityHandle < entityDefs.Num() ) { + idRenderEntityLocal *def = entityDefs[overlays[oldest].entityHandle]; + if ( def != NULL && def->overlays == overlays[oldest].overlays ) { + def->overlays = NULL; + } + } + + overlays[oldest].entityHandle = newEntityHandle; + overlays[oldest].lastStartTime = startTime; + overlays[oldest].overlays->ReUse(); + + return overlays[oldest].overlays; +} + +/* +==================== +idRenderWorldLocal::RemoveDecals +==================== +*/ +void idRenderWorldLocal::RemoveDecals( qhandle_t entityHandle ) { + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle ); + return; + } + + idRenderEntityLocal *def = entityDefs[ entityHandle ]; + if ( !def ) { + return; + } + + R_FreeEntityDefDecals( def ); + R_FreeEntityDefOverlay( def ); +} + +/* +==================== +idRenderWorldLocal::SetRenderView + +Sets the current view so any calls to the render world will use the correct parms. +==================== +*/ +void idRenderWorldLocal::SetRenderView( const renderView_t *renderView ) { + tr.primaryRenderView = *renderView; +} + +/* +==================== +idRenderWorldLocal::RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +==================== +*/ +void idRenderWorldLocal::RenderScene( const renderView_t *renderView ) { + if ( !R_IsInitialized() ) { + return; + } + + renderView_t copy = *renderView; + + // skip front end rendering work, which will result + // in only gui drawing + if ( r_skipFrontEnd.GetBool() ) { + return; + } + + SCOPED_PROFILE_EVENT( "RenderWorld::RenderScene" ); + + if ( renderView->fov_x <= 0 || renderView->fov_y <= 0 ) { + common->Error( "idRenderWorld::RenderScene: bad FOVs: %f, %f", renderView->fov_x, renderView->fov_y ); + } + + // close any gui drawing + tr.guiModel->EmitFullScreen(); + tr.guiModel->Clear(); + + int startTime = Sys_Microseconds(); + + // setup view parms for the initial view + viewDef_t * parms = (viewDef_t *)R_ClearedFrameAlloc( sizeof( *parms ), FRAME_ALLOC_VIEW_DEF ); + parms->renderView = *renderView; + + if ( tr.takingScreenshot ) { + parms->renderView.forceUpdate = true; + } + + int windowWidth = tr.GetWidth(); + int windowHeight = tr.GetHeight(); + tr.PerformResolutionScaling( windowWidth, windowHeight ); + + // screenFraction is just for quickly testing fill rate limitations + if ( r_screenFraction.GetInteger() != 100 ) { + windowWidth = ( windowWidth * r_screenFraction.GetInteger() ) / 100; + windowHeight = ( windowHeight * r_screenFraction.GetInteger() ) / 100; + } + tr.CropRenderSize( windowWidth, windowHeight ); + tr.GetCroppedViewport( &parms->viewport ); + + // the scissor bounds may be shrunk in subviews even if + // the viewport stays the same + // this scissor range is local inside the viewport + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->isSubview = false; + parms->initialViewAreaOrigin = renderView->vieworg; + parms->renderWorld = this; + + // see if the view needs to reverse the culling sense in mirrors + // or environment cube sides + idVec3 cross; + cross = parms->renderView.viewaxis[1].Cross( parms->renderView.viewaxis[2] ); + if ( cross * parms->renderView.viewaxis[0] > 0 ) { + parms->isMirror = false; + } else { + parms->isMirror = true; + } + + // save this world for use by some console commands + tr.primaryWorld = this; + tr.primaryRenderView = *renderView; + tr.primaryView = parms; + + // rendering this view may cause other views to be rendered + // for mirrors / portals / shadows / environment maps + // this will also cause any necessary entities and lights to be + // updated to the demo file + R_RenderView( parms ); + + // render any post processing after the view and all its subviews has been draw + R_RenderPostProcess( parms ); + + // now write delete commands for any modified-but-not-visible entities, and + // add the renderView command to the demo + if ( common->WriteDemo() ) { + WriteRenderView( renderView ); + } + +#if 0 + for ( int i = 0; i < entityDefs.Num(); i++ ) { + idRenderEntityLocal *def = entityDefs[i]; + if ( !def ) { + continue; + } + if ( def->parms.callback ) { + continue; + } + if ( def->parms.hModel->IsDynamicModel() == DM_CONTINUOUS ) { + } + } +#endif + + tr.UnCrop(); + + int endTime = Sys_Microseconds(); + + tr.pc.frontEndMicroSec += endTime - startTime; + + // prepare for any 2D drawing after this + tr.guiModel->Clear(); +} + +/* +=================== +idRenderWorldLocal::NumAreas +=================== +*/ +int idRenderWorldLocal::NumAreas() const { + return numPortalAreas; +} + +/* +=================== +idRenderWorldLocal::NumPortalsInArea +=================== +*/ +int idRenderWorldLocal::NumPortalsInArea( int areaNum ) { + portalArea_t *area; + int count; + portal_t *portal; + + if ( areaNum >= numPortalAreas || areaNum < 0 ) { + common->Error( "idRenderWorld::NumPortalsInArea: bad areanum %i", areaNum ); + } + area = &portalAreas[areaNum]; + + count = 0; + for ( portal = area->portals; portal; portal = portal->next ) { + count++; + } + return count; +} + +/* +=================== +idRenderWorldLocal::GetPortal +=================== +*/ +exitPortal_t idRenderWorldLocal::GetPortal( int areaNum, int portalNum ) { + portalArea_t *area; + int count; + portal_t *portal; + exitPortal_t ret; + + if ( areaNum > numPortalAreas ) { + common->Error( "idRenderWorld::GetPortal: areaNum > numAreas" ); + } + area = &portalAreas[areaNum]; + + count = 0; + for ( portal = area->portals; portal; portal = portal->next ) { + if ( count == portalNum ) { + ret.areas[0] = areaNum; + ret.areas[1] = portal->intoArea; + ret.w = portal->w; + ret.blockingBits = portal->doublePortal->blockingBits; + ret.portalHandle = portal->doublePortal - doublePortals + 1; + return ret; + } + count++; + } + + common->Error( "idRenderWorld::GetPortal: portalNum > numPortals" ); + + memset( &ret, 0, sizeof( ret ) ); + return ret; +} + +/* +=============== +idRenderWorldLocal::PointInAreaNum + +Will return -1 if the point is not in an area, otherwise +it will return 0 <= value < tr.world->numPortalAreas +=============== +*/ +int idRenderWorldLocal::PointInArea( const idVec3 &point ) const { + areaNode_t *node; + int nodeNum; + float d; + + node = areaNodes; + if ( !node ) { + return -1; + } + while( 1 ) { + d = point * node->plane.Normal() + node->plane[3]; + if (d > 0) { + nodeNum = node->children[0]; + } else { + nodeNum = node->children[1]; + } + if ( nodeNum == 0 ) { + return -1; // in solid + } + if ( nodeNum < 0 ) { + nodeNum = -1 - nodeNum; + if ( nodeNum >= numPortalAreas ) { + common->Error( "idRenderWorld::PointInArea: area out of range" ); + } + return nodeNum; + } + node = areaNodes + nodeNum; + } + + return -1; +} + +/* +=================== +idRenderWorldLocal::BoundsInAreas_r +=================== +*/ +void idRenderWorldLocal::BoundsInAreas_r( int nodeNum, const idBounds &bounds, int *areas, int *numAreas, int maxAreas ) const { + int side, i; + areaNode_t *node; + + do { + if ( nodeNum < 0 ) { + nodeNum = -1 - nodeNum; + + for ( i = 0; i < (*numAreas); i++ ) { + if ( areas[i] == nodeNum ) { + break; + } + } + if ( i >= (*numAreas) && (*numAreas) < maxAreas ) { + areas[(*numAreas)++] = nodeNum; + } + return; + } + + node = areaNodes + nodeNum; + + side = bounds.PlaneSide( node->plane ); + if ( side == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else if ( side == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else { + if ( node->children[1] != 0 ) { + BoundsInAreas_r( node->children[1], bounds, areas, numAreas, maxAreas ); + if ( (*numAreas) >= maxAreas ) { + return; + } + } + nodeNum = node->children[0]; + } + } while( nodeNum != 0 ); + + return; +} + +/* +=================== +idRenderWorldLocal::BoundsInAreas + + fills the *areas array with the number of the areas the bounds are in + returns the total number of areas the bounds are in +=================== +*/ +int idRenderWorldLocal::BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + int numAreas = 0; + + assert( areas ); + assert( bounds[0][0] <= bounds[1][0] && bounds[0][1] <= bounds[1][1] && bounds[0][2] <= bounds[1][2] ); + assert( bounds[1][0] - bounds[0][0] < 1e4f && bounds[1][1] - bounds[0][1] < 1e4f && bounds[1][2] - bounds[0][2] < 1e4f ); + + if ( !areaNodes ) { + return numAreas; + } + BoundsInAreas_r( 0, bounds, areas, &numAreas, maxAreas ); + return numAreas; +} + +/* +================ +idRenderWorldLocal::GuiTrace + +checks a ray trace against any gui surfaces in an entity, returning the +fraction location of the trace on the gui surface, or -1,-1 if no hit. +this doesn't do any occlusion testing, simply ignoring non-gui surfaces. +start / end are in global world coordinates. +================ +*/ +guiPoint_t idRenderWorldLocal::GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const { + guiPoint_t pt; + pt.x = pt.y = -1; + pt.guiId = 0; + + if ( ( entityHandle < 0 ) || ( entityHandle >= entityDefs.Num() ) ) { + common->Printf( "idRenderWorld::GuiTrace: invalid handle %i\n", entityHandle ); + return pt; + } + + idRenderEntityLocal * def = entityDefs[entityHandle]; + if ( def == NULL ) { + common->Printf( "idRenderWorld::GuiTrace: handle %i is NULL\n", entityHandle ); + return pt; + } + + idRenderModel * model = def->parms.hModel; + if ( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback != NULL ) { + return pt; + } + + // transform the points into local space + idVec3 localStart, localEnd; + R_GlobalPointToLocal( def->modelMatrix, start, localStart ); + R_GlobalPointToLocal( def->modelMatrix, end, localEnd ); + + for ( int i = 0; i < model->NumSurfaces(); i++ ) { + const modelSurface_t *surf = model->Surface( i ); + + const srfTriangles_t * tri = surf->geometry; + if ( tri == NULL ) { + continue; + } + + const idMaterial * shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + if ( shader == NULL ) { + continue; + } + // only trace against gui surfaces + if ( !shader->HasGui() ) { + continue; + } + + localTrace_t local = R_LocalTrace( localStart, localEnd, 0.0f, tri ); + if ( local.fraction < 1.0f ) { + idVec3 origin, axis[3]; + + R_SurfaceToTextureAxis( tri, origin, axis ); + const idVec3 cursor = local.point - origin; + + float axisLen[2]; + axisLen[0] = axis[0].Length(); + axisLen[1] = axis[1].Length(); + + pt.x = ( cursor * axis[0] ) / ( axisLen[0] * axisLen[0] ); + pt.y = ( cursor * axis[1] ) / ( axisLen[1] * axisLen[1] ); + pt.guiId = shader->GetEntityGui(); + + return pt; + } + } + + return pt; +} + +/* +=================== +idRenderWorldLocal::ModelTrace +=================== +*/ +bool idRenderWorldLocal::ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const { + + memset( &trace, 0, sizeof( trace ) ); + trace.fraction = 1.0f; + trace.point = end; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + return false; + } + + idRenderEntityLocal *def = entityDefs[entityHandle]; + if ( def == NULL ) { + return false; + } + + renderEntity_t *refEnt = &def->parms; + + idRenderModel *model = R_EntityDefDynamicModel( def ); + if ( model == NULL ) { + return false; + } + + // transform the points into local space + float modelMatrix[16]; + idVec3 localStart; + idVec3 localEnd; + R_AxisToModelMatrix( refEnt->axis, refEnt->origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, start, localStart ); + R_GlobalPointToLocal( modelMatrix, end, localEnd ); + + // if we have explicit collision surfaces, only collide against them + // (FIXME, should probably have a parm to control this) + bool collisionSurface = false; + for ( int i = 0; i < model->NumBaseSurfaces(); i++ ) { + const modelSurface_t *surf = model->Surface( i ); + + const idMaterial *shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + + if ( shader->GetSurfaceFlags() & SURF_COLLISION ) { + collisionSurface = true; + break; + } + } + + // only use baseSurfaces, not any overlays + for ( int i = 0; i < model->NumBaseSurfaces(); i++ ) { + const modelSurface_t *surf = model->Surface( i ); + + const idMaterial *shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + + if ( surf->geometry == NULL || shader == NULL ) { + continue; + } + + if ( collisionSurface ) { + // only trace vs collision surfaces + if ( ( shader->GetSurfaceFlags() & SURF_COLLISION ) == 0 ) { + continue; + } + } else { + // skip if not drawn or translucent + if ( !shader->IsDrawn() || ( shader->Coverage() != MC_OPAQUE && shader->Coverage() != MC_PERFORATED ) ) { + continue; + } + } + + localTrace_t localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry ); + + if ( localTrace.fraction < trace.fraction ) { + trace.fraction = localTrace.fraction; + R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point ); + trace.normal = localTrace.normal * refEnt->axis; + trace.material = shader; + trace.entity = &def->parms; + trace.jointNumber = refEnt->hModel->NearestJoint( i, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] ); + } + } + + return ( trace.fraction < 1.0f ); +} + +/* +=================== +idRenderWorldLocal::Trace +=================== +*/ +// FIXME: _D3XP added those. +const char * playerModelExcludeList[] = { + "models/md5/characters/player/d3xp_spplayer.md5mesh", + "models/md5/characters/player/head/d3xp_head.md5mesh", + "models/md5/weapons/pistol_world/worldpistol.md5mesh", + NULL +}; + +const char * playerMaterialExcludeList[] = { + "muzzlesmokepuff", + NULL +}; + +bool idRenderWorldLocal::Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic, bool skipPlayer /*_D3XP*/ ) const { + trace.fraction = 1.0f; + trace.point = end; + + // bounds for the whole trace + idBounds traceBounds; + traceBounds.Clear(); + traceBounds.AddPoint( start ); + traceBounds.AddPoint( end ); + + // get the world areas the trace is in + int areas[128]; + int numAreas = BoundsInAreas( traceBounds, areas, 128 ); + + int numSurfaces = 0; + + // check all areas for models + for ( int i = 0; i < numAreas; i++ ) { + + portalArea_t * area = &portalAreas[ areas[i] ]; + + // check all models in this area + for ( areaReference_t * ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) { + idRenderEntityLocal * def = ref->entity; + + idRenderModel * model = def->parms.hModel; + if ( model == NULL ) { + continue; + } + + if ( model->IsDynamicModel() != DM_STATIC ) { + if ( skipDynamic ) { + continue; + } + +#if 1 /* _D3XP addition. could use a cleaner approach */ + if ( skipPlayer ) { + bool exclude = false; + for ( int k = 0; playerModelExcludeList[k] != NULL; k++ ) { + if ( idStr::Cmp( model->Name(), playerModelExcludeList[k] ) == 0 ) { + exclude = true; + break; + } + } + if ( exclude ) { + continue; + } + } +#endif + + model = R_EntityDefDynamicModel( def ); + if ( !model ) { + continue; // can happen with particle systems, which don't instantiate without a valid view + } + } + + idBounds bounds; + bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis ); + + // if the model bounds do not overlap with the trace bounds + if ( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) ) { + continue; + } + + // check all model surfaces + for ( int j = 0; j < model->NumSurfaces(); j++ ) { + const modelSurface_t *surf = model->Surface( j ); + + const idMaterial * shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + + // if no geometry or no shader + if ( surf->geometry == NULL || shader == NULL ) { + continue; + } + +#if 1 /* _D3XP addition. could use a cleaner approach */ + if ( skipPlayer ) { + bool exclude = false; + for ( int k = 0; playerMaterialExcludeList[k] != NULL; k++ ) { + if ( idStr::Cmp( shader->GetName(), playerMaterialExcludeList[k] ) == 0 ) { + exclude = true; + break; + } + } + if ( exclude ) { + continue; + } + } +#endif + + const srfTriangles_t * tri = surf->geometry; + + bounds.FromTransformedBounds( tri->bounds, def->parms.origin, def->parms.axis ); + + // if triangle bounds do not overlap with the trace bounds + if ( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) ) { + continue; + } + + numSurfaces++; + + // transform the points into local space + float modelMatrix[16]; + idVec3 localStart, localEnd; + R_AxisToModelMatrix( def->parms.axis, def->parms.origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, start, localStart ); + R_GlobalPointToLocal( modelMatrix, end, localEnd ); + + localTrace_t localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry ); + + if ( localTrace.fraction < trace.fraction ) { + trace.fraction = localTrace.fraction; + R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point ); + trace.normal = localTrace.normal * def->parms.axis; + trace.material = shader; + trace.entity = &def->parms; + trace.jointNumber = model->NearestJoint( j, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] ); + + traceBounds.Clear(); + traceBounds.AddPoint( start ); + traceBounds.AddPoint( start + trace.fraction * (end - start) ); + } + } + } + } + return ( trace.fraction < 1.0f ); +} + +/* +================== +idRenderWorldLocal::RecurseProcBSP +================== +*/ +void idRenderWorldLocal::RecurseProcBSP_r( modelTrace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ) const { + float t1, t2; + float frac; + idVec3 mid; + int side; + float midf; + areaNode_t *node; + + if ( results->fraction <= p1f) { + return; // already hit something nearer + } + // empty leaf + if ( nodeNum < 0 ) { + return; + } + // if solid leaf node + if ( nodeNum == 0 ) { + if ( parentNodeNum != -1 ) { + + results->fraction = p1f; + results->point = p1; + node = &areaNodes[parentNodeNum]; + results->normal = node->plane.Normal(); + return; + } + } + node = &areaNodes[nodeNum]; + + // distance from plane for trace start and end + t1 = node->plane.Normal() * p1 + node->plane[3]; + t2 = node->plane.Normal() * p2 + node->plane[3]; + + if ( t1 >= 0.0f && t2 >= 0.0f ) { + RecurseProcBSP_r( results, nodeNum, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < 0.0f && t2 < 0.0f ) { + RecurseProcBSP_r( results, nodeNum, node->children[1], p1f, p2f, p1, p2 ); + return; + } + side = t1 < t2; + frac = t1 / (t1 - t2); + midf = p1f + frac*(p2f - p1f); + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + RecurseProcBSP_r( results, nodeNum, node->children[side], p1f, midf, p1, mid ); + RecurseProcBSP_r( results, nodeNum, node->children[side^1], midf, p2f, mid, p2 ); +} + +/* +================== +idRenderWorldLocal::FastWorldTrace +================== +*/ +bool idRenderWorldLocal::FastWorldTrace( modelTrace_t &results, const idVec3 &start, const idVec3 &end ) const { + memset( &results, 0, sizeof( modelTrace_t ) ); + results.fraction = 1.0f; + if ( areaNodes != NULL ) { + RecurseProcBSP_r( &results, -1, 0, 0.0f, 1.0f, start, end ); + return ( results.fraction < 1.0f ); + } + return false; +} + +/* +================================================================================= + +CREATE MODEL REFS + +================================================================================= +*/ + +/* +================= +idRenderWorldLocal::AddEntityRefToArea + +This is called by R_PushVolumeIntoTree and also directly +for the world model references that are precalculated. +================= +*/ +void idRenderWorldLocal::AddEntityRefToArea( idRenderEntityLocal *def, portalArea_t *area ) { + areaReference_t *ref; + + if ( def == NULL ) { + common->Error( "idRenderWorldLocal::AddEntityRefToArea: NULL def" ); + return; + } + + for ( ref = def->entityRefs; ref != NULL; ref = ref->ownerNext ) { + if ( ref->area == area ) { + return; + } + } + + ref = areaReferenceAllocator.Alloc(); + + tr.pc.c_entityReferences++; + + ref->entity = def; + + // link to entityDef + ref->ownerNext = def->entityRefs; + def->entityRefs = ref; + + // link to end of area list + ref->area = area; + ref->areaNext = &area->entityRefs; + ref->areaPrev = area->entityRefs.areaPrev; + ref->areaNext->areaPrev = ref; + ref->areaPrev->areaNext = ref; +} + +/* +=================== +idRenderWorldLocal::AddLightRefToArea +=================== +*/ +void idRenderWorldLocal::AddLightRefToArea( idRenderLightLocal *light, portalArea_t *area ) { + areaReference_t *lref; + + for ( lref = light->references; lref != NULL; lref = lref->ownerNext ) { + if ( lref->area == area ) { + return; + } + } + + // add a lightref to this area + lref = areaReferenceAllocator.Alloc(); + lref->light = light; + lref->area = area; + lref->ownerNext = light->references; + light->references = lref; + tr.pc.c_lightReferences++; + + // doubly linked list so we can free them easily later + area->lightRefs.areaNext->areaPrev = lref; + lref->areaNext = area->lightRefs.areaNext; + lref->areaPrev = &area->lightRefs; + area->lightRefs.areaNext = lref; +} + +/* +=================== +idRenderWorldLocal::GenerateAllInteractions + +Force the generation of all light / surface interactions at the start of a level +If this isn't called, they will all be dynamically generated +=================== +*/ +void idRenderWorldLocal::GenerateAllInteractions() { + if ( !R_IsInitialized() ) { + return; + } + + int start = Sys_Milliseconds(); + + generateAllInteractionsCalled = false; + + // let the interaction creation code know that it shouldn't + // try and do any view specific optimizations + tr.viewDef = NULL; + + // build the interaction table + // this will be dynamically resized if the entity / light counts grow too much + interactionTableWidth = entityDefs.Num() + 100; + interactionTableHeight = lightDefs.Num() + 100; + int size = interactionTableWidth * interactionTableHeight * sizeof( *interactionTable ); + interactionTable = (idInteraction **)R_ClearedStaticAlloc( size ); + + // itterate through all lights + int count = 0; + for ( int i = 0; i < this->lightDefs.Num(); i++ ) { + idRenderLightLocal *ldef = this->lightDefs[i]; + if ( ldef == NULL ) { + continue; + } + + // check all areas the light touches + for ( areaReference_t *lref = ldef->references; lref; lref = lref->ownerNext ) { + portalArea_t *area = lref->area; + + // check all the models in this area + for ( areaReference_t *eref = area->entityRefs.areaNext; eref != &area->entityRefs; eref = eref->areaNext ) { + idRenderEntityLocal *edef = eref->entity; + + // scan the doubly linked lists, which may have several dozen entries + idInteraction *inter; + + // we could check either model refs or light refs for matches, but it is + // assumed that there will be less lights in an area than models + // so the entity chains should be somewhat shorter (they tend to be fairly close). + for ( inter = edef->firstInteraction; inter != NULL; inter = inter->entityNext ) { + if ( inter->lightDef == ldef ) { + break; + } + } + + // if we already have an interaction, we don't need to do anything + if ( inter != NULL ) { + continue; + } + + // make an interaction for this light / entity pair + // and add a pointer to it in the table + inter = idInteraction::AllocAndLink( edef, ldef ); + count++; + + // the interaction may create geometry + inter->CreateStaticInteraction(); + } + } + + session->Pump(); + } + + int end = Sys_Milliseconds(); + int msec = end - start; + + common->Printf( "idRenderWorld::GenerateAllInteractions, msec = %i\n", msec ); + common->Printf( "interactionTable size: %i bytes\n", size ); + common->Printf( "%i interactions take %i bytes\n", count, count * sizeof( idInteraction ) ); + + // entities flagged as noDynamicInteractions will no longer make any + generateAllInteractionsCalled = true; +} + +/* +=================== +idRenderWorldLocal::FreeInteractions +=================== +*/ +void idRenderWorldLocal::FreeInteractions() { + int i; + idRenderEntityLocal *def; + + for ( i = 0; i < entityDefs.Num(); i++ ) { + def = entityDefs[i]; + if ( !def ) { + continue; + } + // free all the interactions + while ( def->firstInteraction != NULL ) { + def->firstInteraction->UnlinkAndFree(); + } + } +} + +/* +================== +idRenderWorldLocal::PushFrustumIntoTree_r + +Used for both light volumes and model volumes. + +This does not clip the points by the planes, so some slop +occurs. + +tr.viewCount should be bumped before calling, allowing it +to prevent double checking areas. + +We might alternatively choose to do this with an area flow. +================== +*/ +void idRenderWorldLocal::PushFrustumIntoTree_r( idRenderEntityLocal *def, idRenderLightLocal *light, + const frustumCorners_t & corners, int nodeNum ) { + if ( nodeNum < 0 ) { + int areaNum = -1 - nodeNum; + portalArea_t * area = &portalAreas[ areaNum ]; + if ( area->viewCount == tr.viewCount ) { + return; // already added a reference here + } + area->viewCount = tr.viewCount; + + if ( def != NULL ) { + AddEntityRefToArea( def, area ); + } + if ( light != NULL ) { + AddLightRefToArea( light, area ); + } + + return; + } + + areaNode_t * node = areaNodes + nodeNum; + + // if we know that all possible children nodes only touch an area + // we have already marked, we can early out + if ( node->commonChildrenArea != CHILDREN_HAVE_MULTIPLE_AREAS && r_useNodeCommonChildren.GetBool() ) { + // note that we do NOT try to set a reference in this area + // yet, because the test volume may yet wind up being in the + // solid part, which would cause bounds slightly poked into + // a wall to show up in the next room + if ( portalAreas[ node->commonChildrenArea ].viewCount == tr.viewCount ) { + return; + } + } + + // exact check all the corners against the node plane + frustumCull_t cull = idRenderMatrix::CullFrustumCornersToPlane( corners, node->plane ); + + if ( cull != FRUSTUM_CULL_BACK ) { + nodeNum = node->children[0]; + if ( nodeNum != 0 ) { // 0 = solid + PushFrustumIntoTree_r( def, light, corners, nodeNum ); + } + } + + if ( cull != FRUSTUM_CULL_FRONT ) { + nodeNum = node->children[1]; + if ( nodeNum != 0 ) { // 0 = solid + PushFrustumIntoTree_r( def, light, corners, nodeNum ); + } + } +} + +/* +============== +idRenderWorldLocal::PushFrustumIntoTree +============== +*/ +void idRenderWorldLocal::PushFrustumIntoTree( idRenderEntityLocal *def, idRenderLightLocal *light, const idRenderMatrix & frustumTransform, const idBounds & frustumBounds ) { + if ( areaNodes == NULL ) { + return; + } + + // calculate the corners of the frustum in word space + ALIGNTYPE16 frustumCorners_t corners; + idRenderMatrix::GetFrustumCorners( corners, frustumTransform, frustumBounds ); + + PushFrustumIntoTree_r( def, light, corners, 0 ); +} + +//=================================================================== + +/* +==================== +idRenderWorldLocal::DebugClearLines +==================== +*/ +void idRenderWorldLocal::DebugClearLines( int time ) { + RB_ClearDebugLines( time ); + RB_ClearDebugText( time ); +} + +/* +==================== +idRenderWorldLocal::DebugLine +==================== +*/ +void idRenderWorldLocal::DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime, const bool depthTest ) { + RB_AddDebugLine( color, start, end, lifetime, depthTest ); +} + +/* +================ +idRenderWorldLocal::DebugArrow +================ +*/ +void idRenderWorldLocal::DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime ) { + idVec3 forward, right, up, v1, v2; + float a, s; + int i; + static float arrowCos[40]; + static float arrowSin[40]; + static int arrowStep; + + DebugLine( color, start, end, lifetime ); + + if ( r_debugArrowStep.GetInteger() <= 10 ) { + return; + } + // calculate sine and cosine when step size changes + if ( arrowStep != r_debugArrowStep.GetInteger() ) { + arrowStep = r_debugArrowStep.GetInteger(); + for ( i = 0, a = 0; a < 360.0f; a += arrowStep, i++ ) { + arrowCos[i] = idMath::Cos16( DEG2RAD( a ) ); + arrowSin[i] = idMath::Sin16( DEG2RAD( a ) ); + } + arrowCos[i] = arrowCos[0]; + arrowSin[i] = arrowSin[0]; + } + // draw a nice arrow + forward = end - start; + forward.Normalize(); + forward.NormalVectors( right, up); + for ( i = 0, a = 0; a < 360.0f; a += arrowStep, i++ ) { + s = 0.5f * size * arrowCos[i]; + v1 = end - size * forward; + v1 = v1 + s * right; + s = 0.5f * size * arrowSin[i]; + v1 = v1 + s * up; + + s = 0.5f * size * arrowCos[i+1]; + v2 = end - size * forward; + v2 = v2 + s * right; + s = 0.5f * size * arrowSin[i+1]; + v2 = v2 + s * up; + + DebugLine( color, v1, end, lifetime ); + DebugLine( color, v1, v2, lifetime ); + } +} + +/* +==================== +idRenderWorldLocal::DebugWinding +==================== +*/ +void idRenderWorldLocal::DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime, const bool depthTest ) { + int i; + idVec3 point, lastPoint; + + if ( w.GetNumPoints() < 2 ) { + return; + } + + lastPoint = origin + w[w.GetNumPoints()-1].ToVec3() * axis; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + point = origin + w[i].ToVec3() * axis; + DebugLine( color, lastPoint, point, lifetime, depthTest ); + lastPoint = point; + } +} + +/* +==================== +idRenderWorldLocal::DebugCircle +==================== +*/ +void idRenderWorldLocal::DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime, const bool depthTest ) { + int i; + float a; + idVec3 left, up, point, lastPoint; + + dir.OrthogonalBasis( left, up ); + left *= radius; + up *= radius; + lastPoint = origin + up; + for ( i = 1; i <= numSteps; i++ ) { + a = idMath::TWO_PI * i / numSteps; + point = origin + idMath::Sin16( a ) * left + idMath::Cos16( a ) * up; + DebugLine( color, lastPoint, point, lifetime, depthTest ); + lastPoint = point; + } +} + +/* +============ +idRenderWorldLocal::DebugSphere +============ +*/ +void idRenderWorldLocal::DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime, const bool depthTest /*_D3XP*/ ) { + int i, j, n, num; + float s, c; + idVec3 p, lastp, *lastArray; + + num = 360 / 15; + lastArray = (idVec3 *) _alloca16( num * sizeof( idVec3 ) ); + lastArray[0] = sphere.GetOrigin() + idVec3( 0, 0, sphere.GetRadius() ); + for ( n = 1; n < num; n++ ) { + lastArray[n] = lastArray[0]; + } + + for ( i = 15; i <= 360; i += 15 ) { + s = idMath::Sin16( DEG2RAD(i) ); + c = idMath::Cos16( DEG2RAD(i) ); + lastp[0] = sphere.GetOrigin()[0]; + lastp[1] = sphere.GetOrigin()[1] + sphere.GetRadius() * s; + lastp[2] = sphere.GetOrigin()[2] + sphere.GetRadius() * c; + for ( n = 0, j = 15; j <= 360; j += 15, n++ ) { + p[0] = sphere.GetOrigin()[0] + idMath::Sin16( DEG2RAD(j) ) * sphere.GetRadius() * s; + p[1] = sphere.GetOrigin()[1] + idMath::Cos16( DEG2RAD(j) ) * sphere.GetRadius() * s; + p[2] = lastp[2]; + + DebugLine( color, lastp, p, lifetime,depthTest ); + DebugLine( color, lastp, lastArray[n], lifetime, depthTest ); + + lastArray[n] = lastp; + lastp = p; + } + } +} + +/* +==================== +idRenderWorldLocal::DebugBounds +==================== +*/ +void idRenderWorldLocal::DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org, const int lifetime ) { + int i; + idVec3 v[8]; + + if ( bounds.IsCleared() ) { + return; + } + + for ( i = 0; i < 8; i++ ) { + v[i][0] = org[0] + bounds[(i^(i>>1))&1][0]; + v[i][1] = org[1] + bounds[(i>>1)&1][1]; + v[i][2] = org[2] + bounds[(i>>2)&1][2]; + } + for ( i = 0; i < 4; i++ ) { + DebugLine( color, v[i], v[(i+1)&3], lifetime ); + DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime ); + DebugLine( color, v[i], v[4+i], lifetime ); + } +} + +/* +==================== +idRenderWorldLocal::DebugBox +==================== +*/ +void idRenderWorldLocal::DebugBox( const idVec4 &color, const idBox &box, const int lifetime ) { + int i; + idVec3 v[8]; + + box.ToPoints( v ); + for ( i = 0; i < 4; i++ ) { + DebugLine( color, v[i], v[(i+1)&3], lifetime ); + DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime ); + DebugLine( color, v[i], v[4+i], lifetime ); + } +} + +/* +============ +idRenderWorldLocal::DebugCone + + dir is the cone axis + radius1 is the radius at the apex + radius2 is the radius at apex+dir +============ +*/ +void idRenderWorldLocal::DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime ) { + int i; + idMat3 axis; + idVec3 top, p1, p2, lastp1, lastp2, d; + + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + top = apex + dir; + lastp2 = top + radius2 * axis[1]; + + if ( radius1 == 0.0f ) { + for ( i = 20; i <= 360; i += 20 ) { + d = idMath::Sin16( DEG2RAD(i) ) * axis[0] + idMath::Cos16( DEG2RAD(i) ) * axis[1]; + p2 = top + d * radius2; + DebugLine( color, lastp2, p2, lifetime ); + DebugLine( color, p2, apex, lifetime ); + lastp2 = p2; + } + } else { + lastp1 = apex + radius1 * axis[1]; + for ( i = 20; i <= 360; i += 20 ) { + d = idMath::Sin16( DEG2RAD(i) ) * axis[0] + idMath::Cos16( DEG2RAD(i) ) * axis[1]; + p1 = apex + d * radius1; + p2 = top + d * radius2; + DebugLine( color, lastp1, p1, lifetime ); + DebugLine( color, lastp2, p2, lifetime ); + DebugLine( color, p1, p2, lifetime ); + lastp1 = p1; + lastp2 = p2; + } + } +} + +/* +================ +idRenderWorldLocal::DebugAxis +================ +*/ +void idRenderWorldLocal::DebugAxis( const idVec3 &origin, const idMat3 &axis ) { + idVec3 start = origin; + idVec3 end = start + axis[0] * 20.0f; + DebugArrow( colorWhite, start, end, 2 ); + end = start + axis[0] * -20.0f; + DebugArrow( colorWhite, start, end, 2 ); + end = start + axis[1] * +20.0f; + DebugArrow( colorGreen, start, end, 2 ); + end = start + axis[1] * -20.0f; + DebugArrow( colorGreen, start, end, 2 ); + end = start + axis[2] * +20.0f; + DebugArrow( colorBlue, start, end, 2 ); + end = start + axis[2] * -20.0f; + DebugArrow( colorBlue, start, end, 2 ); +} + +/* +==================== +idRenderWorldLocal::DebugClearPolygons +==================== +*/ +void idRenderWorldLocal::DebugClearPolygons( int time ) { + RB_ClearDebugPolygons( time ); +} + +/* +==================== +idRenderWorldLocal::DebugPolygon +==================== +*/ +void idRenderWorldLocal::DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ) { + RB_AddDebugPolygon( color, winding, lifeTime, depthTest ); +} + +/* +================ +idRenderWorldLocal::DebugScreenRect +================ +*/ +void idRenderWorldLocal::DebugScreenRect( const idVec4 &color, const idScreenRect &rect, const viewDef_t *viewDef, const int lifetime ) { + int i; + float centerx, centery, dScale, hScale, vScale; + idBounds bounds; + idVec3 p[4]; + + centerx = ( viewDef->viewport.x2 - viewDef->viewport.x1 ) * 0.5f; + centery = ( viewDef->viewport.y2 - viewDef->viewport.y1 ) * 0.5f; + + dScale = r_znear.GetFloat() + 1.0f; + hScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_x * 0.5f ) ); + vScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_y * 0.5f ) ); + + bounds[0][0] = bounds[1][0] = dScale; + bounds[0][1] = -( rect.x1 - centerx ) / centerx * hScale; + bounds[1][1] = -( rect.x2 - centerx ) / centerx * hScale; + bounds[0][2] = ( rect.y1 - centery ) / centery * vScale; + bounds[1][2] = ( rect.y2 - centery ) / centery * vScale; + + for ( i = 0; i < 4; i++ ) { + p[i].x = bounds[0][0]; + p[i].y = bounds[(i^(i>>1))&1].y; + p[i].z = bounds[(i>>1)&1].z; + p[i] = viewDef->renderView.vieworg + p[i] * viewDef->renderView.viewaxis; + } + for ( i = 0; i < 4; i++ ) { + DebugLine( color, p[i], p[(i+1)&3], false ); + } +} + +/* +================ +idRenderWorldLocal::DrawTextLength + + returns the length of the given text +================ +*/ +float idRenderWorldLocal::DrawTextLength( const char *text, float scale, int len ) { + return RB_DrawTextLength( text, scale, len ); +} + +/* +================ +idRenderWorldLocal::DrawText + + oriented on the viewaxis + align can be 0-left, 1-center (default), 2-right +================ +*/ +void idRenderWorldLocal::DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ) { + RB_AddDebugText( text, origin, scale, color, viewAxis, align, lifetime, depthTest ); +} + +/* +=============== +idRenderWorldLocal::RegenerateWorld +=============== +*/ +void idRenderWorldLocal::RegenerateWorld() { + R_FreeDerivedData(); + R_ReCreateWorldReferences(); +} + +/* +=============== +R_GlobalShaderOverride +=============== +*/ +bool R_GlobalShaderOverride( const idMaterial **shader ) { + + if ( !(*shader)->IsDrawn() ) { + return false; + } + + if ( tr.primaryRenderView.globalMaterial ) { + *shader = tr.primaryRenderView.globalMaterial; + return true; + } + + if ( r_materialOverride.GetString()[0] != '\0' ) { + *shader = declManager->FindMaterial( r_materialOverride.GetString() ); + return true; + } + + return false; +} + +/* +=============== +R_RemapShaderBySkin +=============== +*/ +const idMaterial *R_RemapShaderBySkin( const idMaterial *shader, const idDeclSkin *skin, const idMaterial *customShader ) { + + if ( !shader ) { + return NULL; + } + + // never remap surfaces that were originally nodraw, like collision hulls + if ( !shader->IsDrawn() ) { + return shader; + } + + if ( customShader ) { + // this is sort of a hack, but cause deformed surfaces to map to empty surfaces, + // so the item highlight overlay doesn't highlight the autosprite surface + if ( shader->Deform() ) { + return NULL; + } + return const_cast(customShader); + } + + if ( !skin ) { + return const_cast(shader); + } + + return skin->RemapShaderBySkin( shader ); +} diff --git a/neo/renderer/RenderWorld.h b/neo/renderer/RenderWorld.h new file mode 100644 index 00000000..820a520c --- /dev/null +++ b/neo/renderer/RenderWorld.h @@ -0,0 +1,433 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __RENDERWORLD_H__ +#define __RENDERWORLD_H__ + +/* +=============================================================================== + + Render World + +=============================================================================== +*/ + +#define PROC_FILE_EXT "proc" +#define PROC_FILE_ID "mapProcFile003" + +// shader parms +const int SHADERPARM_RED = 0; +const int SHADERPARM_GREEN = 1; +const int SHADERPARM_BLUE = 2; +const int SHADERPARM_ALPHA = 3; +const int SHADERPARM_TIMESCALE = 3; +const int SHADERPARM_TIMEOFFSET = 4; +const int SHADERPARM_DIVERSITY = 5; // random between 0.0 and 1.0 for some effects (muzzle flashes, etc) +const int SHADERPARM_MODE = 7; // for selecting which shader passes to enable +const int SHADERPARM_TIME_OF_DEATH = 7; // for the monster skin-burn-away effect enable and time offset + +// model parms +const int SHADERPARM_MD3_FRAME = 8; +const int SHADERPARM_MD3_LASTFRAME = 9; +const int SHADERPARM_MD3_BACKLERP = 10; + +const int SHADERPARM_BEAM_END_X = 8; // for _beam models +const int SHADERPARM_BEAM_END_Y = 9; +const int SHADERPARM_BEAM_END_Z = 10; +const int SHADERPARM_BEAM_WIDTH = 11; + +const int SHADERPARM_SPRITE_WIDTH = 8; +const int SHADERPARM_SPRITE_HEIGHT = 9; + +const int SHADERPARM_PARTICLE_STOPTIME = 8; // don't spawn any more particles after this time + +// guis +const int MAX_RENDERENTITY_GUI = 3; + +// the renderEntity_s::joints array needs to point at enough memory to store the number of joints rounded up to two for SIMD +ID_INLINE int SIMD_ROUND_JOINTS( int numJoints ) { return ( ( numJoints + 1 ) & ~1 ); } +ID_INLINE void SIMD_INIT_LAST_JOINT( idJointMat * joints, int numJoints ) { if ( numJoints & 1 ) { joints[numJoints] = joints[numJoints - 1]; } } + +typedef bool(*deferredEntityCallback_t)( renderEntity_s *, const renderView_s * ); + + +typedef struct renderEntity_s { + idRenderModel * hModel; // this can only be null if callback is set + + int entityNum; + int bodyId; + + // Entities that are expensive to generate, like skeletal models, can be + // deferred until their bounds are found to be in view, in the frustum + // of a shadowing light that is in view, or contacted by a trace / overlay test. + // This is also used to do visual cueing on items in the view + // The renderView may be NULL if the callback is being issued for a non-view related + // source. + // The callback function should clear renderEntity->callback if it doesn't + // want to be called again next time the entity is referenced (ie, if the + // callback has now made the entity valid until the next updateEntity) + idBounds bounds; // only needs to be set for deferred models and md5s + deferredEntityCallback_t callback; + + void * callbackData; // used for whatever the callback wants + + // player bodies and possibly player shadows should be suppressed in views from + // that player's eyes, but will show up in mirrors and other subviews + // security cameras could suppress their model in their subviews if we add a way + // of specifying a view number for a remoteRenderMap view + int suppressSurfaceInViewID; + int suppressShadowInViewID; + + // world models for the player and weapons will not cast shadows from view weapon + // muzzle flashes + int suppressShadowInLightID; + + // if non-zero, the surface and shadow (if it casts one) + // will only show up in the specific view, ie: player weapons + int allowSurfaceInViewID; + + // positioning + // axis rotation vectors must be unit length for many + // R_LocalToGlobal functions to work, so don't scale models! + // axis vectors are [0] = forward, [1] = left, [2] = up + idVec3 origin; + idMat3 axis; + + // texturing + const idMaterial * customShader; // if non-0, all surfaces will use this + const idMaterial * referenceShader; // used so flares can reference the proper light shader + const idDeclSkin * customSkin; // 0 for no remappings + class idSoundEmitter * referenceSound; // for shader sound tables, allowing effects to vary with sounds + float shaderParms[ MAX_ENTITY_SHADER_PARMS ]; // can be used in any way by shader or model generation + + // networking: see WriteGUIToSnapshot / ReadGUIFromSnapshot + class idUserInterface * gui[ MAX_RENDERENTITY_GUI ]; + + struct renderView_s * remoteRenderView; // any remote camera surfaces will use this + + int numJoints; + idJointMat * joints; // array of joints that will modify vertices. + // NULL if non-deformable model. NOT freed by renderer + + float modelDepthHack; // squash depth range so particle effects don't clip into walls + + // options to override surface shader flags (replace with material parameters?) + bool noSelfShadow; // cast shadows onto other objects,but not self + bool noShadow; // no shadow at all + + bool noDynamicInteractions; // don't create any light / shadow interactions after + // the level load is completed. This is a performance hack + // for the gigantic outdoor meshes in the monorail map, so + // all the lights in the moving monorail don't touch the meshes + + bool weaponDepthHack; // squash depth range so view weapons don't poke into walls + // this automatically implies noShadow + bool noOverlays; // force no overlays on this model + bool skipMotionBlur; // Mask out this object during motion blur + int forceUpdate; // force an update (NOTE: not a bool to keep this struct a multiple of 4 bytes) + int timeGroup; + int xrayIndex; +} renderEntity_t; + + +typedef struct renderLight_s { + idMat3 axis; // rotation vectors, must be unit length + idVec3 origin; + + // if non-zero, the light will not show up in the specific view, + // which may be used if we want to have slightly different muzzle + // flash lights for the player and other views + int suppressLightInViewID; + + // if non-zero, the light will only show up in the specific view + // which can allow player gun gui lights and such to not effect everyone + int allowLightInViewID; + + // I am sticking the four bools together so there are no unused gaps in + // the padded structure, which could confuse the memcmp that checks for redundant + // updates + bool forceShadows; // Used to override the material parameters + bool noShadows; // (should we replace this with material parameters on the shader?) + bool noSpecular; // (should we replace this with material parameters on the shader?) + + bool pointLight; // otherwise a projection light (should probably invert the sense of this, because points are way more common) + bool parallel; // lightCenter gives the direction to the light at infinity + idVec3 lightRadius; // xyz radius for point lights + idVec3 lightCenter; // offset the lighting direction for shading and + // shadows, relative to origin + + // frustum definition for projected lights, all reletive to origin + // FIXME: we should probably have real plane equations here, and offer + // a helper function for conversion from this format + idVec3 target; + idVec3 right; + idVec3 up; + idVec3 start; + idVec3 end; + + // Dmap will generate an optimized shadow volume named _prelight_ + // for the light against all the _area* models in the map. The renderer will + // ignore this value if the light has been moved after initial creation + idRenderModel * prelightModel; + + // muzzle flash lights will not cast shadows from player and weapon world models + int lightId; + + + const idMaterial * shader; // NULL = either lights/defaultPointLight or lights/defaultProjectedLight + float shaderParms[MAX_ENTITY_SHADER_PARMS]; // can be used in any way by shader + idSoundEmitter * referenceSound; // for shader sound tables, allowing effects to vary with sounds +} renderLight_t; + + +typedef struct renderView_s { + // player views will set this to a non-zero integer for model suppress / allow + // subviews (mirrors, cameras, etc) will always clear it to zero + int viewID; + + float fov_x, fov_y; // in degrees + idVec3 vieworg; // has already been adjusted for stereo world seperation + idVec3 vieworg_weapon; // has already been adjusted for stereo world seperation + idMat3 viewaxis; // transformation matrix, view looks down the positive X axis + + bool cramZNear; // for cinematics, we want to set ZNear much lower + bool flipProjection; + bool forceUpdate; // for an update + + // time in milliseconds for shader effects and other time dependent rendering issues + int time[2]; + float shaderParms[MAX_GLOBAL_SHADER_PARMS]; // can be used in any way by shader + const idMaterial *globalMaterial; // used to override everything draw + + // the viewEyeBuffer may be of a different polarity than stereoScreenSeparation if the eyes have been swapped + int viewEyeBuffer; // -1 = left eye, 1 = right eye, 0 = monoscopic view or GUI + float stereoScreenSeparation; // projection matrix horizontal offset, positive or negative based on camera eye +} renderView_t; + + +// exitPortal_t is returned by idRenderWorld::GetPortal() +typedef struct { + int areas[2]; // areas connected by this portal + const idWinding * w; // winding points have counter clockwise ordering seen from areas[0] + int blockingBits; // PS_BLOCK_VIEW, PS_BLOCK_AIR, etc + qhandle_t portalHandle; +} exitPortal_t; + + +// guiPoint_t is returned by idRenderWorld::GuiTrace() +typedef struct { + float x, y; // 0.0 to 1.0 range if trace hit a gui, otherwise -1 + int guiId; // id of gui ( 0, 1, or 2 ) that the trace happened against +} guiPoint_t; + + +// modelTrace_t is for tracing vs. visual geometry +typedef struct modelTrace_s { + float fraction; // fraction of trace completed + idVec3 point; // end point of trace in global space + idVec3 normal; // hit triangle normal vector in global space + const idMaterial * material; // material of hit surface + const renderEntity_t * entity; // render entity that was hit + int jointNumber; // md5 joint nearest to the hit triangle +} modelTrace_t; + + +static const int NUM_PORTAL_ATTRIBUTES = 3; + +typedef enum { + PS_BLOCK_NONE = 0, + + PS_BLOCK_VIEW = 1, + PS_BLOCK_LOCATION = 2, // game map location strings often stop in hallways + PS_BLOCK_AIR = 4, // windows between pressurized and unpresurized areas + + PS_BLOCK_ALL = (1<. + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +================================================================================= + +ENTITY DEFS + +================================================================================= +*/ + +/* +================= +R_DeriveEntityData +================= +*/ +void R_DeriveEntityData( idRenderEntityLocal * entity ) { + R_AxisToModelMatrix( entity->parms.axis, entity->parms.origin, entity->modelMatrix ); + + idRenderMatrix::CreateFromOriginAxis( entity->parms.origin, entity->parms.axis, entity->modelRenderMatrix ); + + // calculate the matrix that transforms the unit cube to exactly cover the model in world space + idRenderMatrix::OffsetScaleForBounds( entity->modelRenderMatrix, entity->localReferenceBounds, entity->inverseBaseModelProject ); + + // calculate the global model bounds by inverse projecting the unit cube with the 'inverseBaseModelProject' + idRenderMatrix::ProjectedBounds( entity->globalReferenceBounds, entity->inverseBaseModelProject, bounds_unitCube, false ); +} + +/* +=================== +R_FreeEntityDefDerivedData + +Used by both FreeEntityDef and UpdateEntityDef +Does not actually free the entityDef. +=================== +*/ +void R_FreeEntityDefDerivedData( idRenderEntityLocal *def, bool keepDecals, bool keepCachedDynamicModel ) { + // demo playback needs to free the joints, while normal play + // leaves them in the control of the game + if ( common->ReadDemo() ) { + if ( def->parms.joints ) { + Mem_Free16( def->parms.joints ); + def->parms.joints = NULL; + } + if ( def->parms.callbackData ) { + Mem_Free( def->parms.callbackData ); + def->parms.callbackData = NULL; + } + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( def->parms.gui[ i ] ) { + delete def->parms.gui[ i ]; + def->parms.gui[ i ] = NULL; + } + } + } + + // free all the interactions + while ( def->firstInteraction != NULL ) { + def->firstInteraction->UnlinkAndFree(); + } + def->dynamicModelFrameCount = 0; + + // clear the dynamic model if present + if ( def->dynamicModel ) { + def->dynamicModel = NULL; + } + + if ( !keepDecals ) { + R_FreeEntityDefDecals( def ); + R_FreeEntityDefOverlay( def ); + } + + if ( !keepCachedDynamicModel ) { + delete def->cachedDynamicModel; + def->cachedDynamicModel = NULL; + } + + // free the entityRefs from the areas + areaReference_t * next = NULL; + for ( areaReference_t * ref = def->entityRefs; ref != NULL; ref = next ) { + next = ref->ownerNext; + + // unlink from the area + ref->areaNext->areaPrev = ref->areaPrev; + ref->areaPrev->areaNext = ref->areaNext; + + // put it back on the free list for reuse + def->world->areaReferenceAllocator.Free( ref ); + } + def->entityRefs = NULL; +} + +/* +=================== +R_FreeEntityDefDecals +=================== +*/ +void R_FreeEntityDefDecals( idRenderEntityLocal *def ) { + def->decals = NULL; +} + +/* +=================== +R_FreeEntityDefFadedDecals +=================== +*/ +void R_FreeEntityDefFadedDecals( idRenderEntityLocal *def, int time ) { + if ( def->decals != NULL ) { + def->decals->RemoveFadedDecals( time ); + } +} + +/* +=================== +R_FreeEntityDefOverlay +=================== +*/ +void R_FreeEntityDefOverlay( idRenderEntityLocal *def ) { + def->overlays = NULL; +} + +/* +=============== +R_CreateEntityRefs + +Creates all needed model references in portal areas, +chaining them to both the area and the entityDef. + +Bumps tr.viewCount, which means viewCount can change many times each frame. +=============== +*/ +void R_CreateEntityRefs( idRenderEntityLocal *entity ) { + if ( entity->parms.hModel == NULL ) { + entity->parms.hModel = renderModelManager->DefaultModel(); + } + + // if the entity hasn't been fully specified due to expensive animation calcs + // for md5 and particles, use the provided conservative bounds. + if ( entity->parms.callback != NULL ) { + entity->localReferenceBounds = entity->parms.bounds; + } else { + entity->localReferenceBounds = entity->parms.hModel->Bounds( &entity->parms ); + } + + // some models, like empty particles, may not need to be added at all + if ( entity->localReferenceBounds.IsCleared() ) { + return; + } + + if ( r_showUpdates.GetBool() && + ( entity->localReferenceBounds[1][0] - entity->localReferenceBounds[0][0] > 1024.0f || + entity->localReferenceBounds[1][1] - entity->localReferenceBounds[0][1] > 1024.0f ) ) { + common->Printf( "big entityRef: %f,%f\n", entity->localReferenceBounds[1][0] - entity->localReferenceBounds[0][0], + entity->localReferenceBounds[1][1] - entity->localReferenceBounds[0][1] ); + } + + // derive entity data + R_DeriveEntityData( entity ); + + // bump the view count so we can tell if an + // area already has a reference + tr.viewCount++; + + // push the model frustum down the BSP tree into areas + entity->world->PushFrustumIntoTree( entity, NULL, entity->inverseBaseModelProject, bounds_unitCube ); +} + +/* +================================================================================= + +LIGHT DEFS + +================================================================================= +*/ + +/* +======================== +R_ComputePointLightProjectionMatrix + +Computes the light projection matrix for a point light. +======================== +*/ +static float R_ComputePointLightProjectionMatrix( idRenderLightLocal * light, idRenderMatrix & localProject ) { + assert( light->parms.pointLight ); + + // A point light uses a box projection. + // This projects into the 0.0 - 1.0 texture range instead of -1.0 to 1.0 clip space range. + localProject.Zero(); + localProject[0][0] = 0.5f / light->parms.lightRadius[0]; + localProject[1][1] = 0.5f / light->parms.lightRadius[1]; + localProject[2][2] = 0.5f / light->parms.lightRadius[2]; + localProject[0][3] = 0.5f; + localProject[1][3] = 0.5f; + localProject[2][3] = 0.5f; + localProject[3][3] = 1.0f; // identity perspective + + return 1.0f; +} + +static const float SPOT_LIGHT_MIN_Z_NEAR = 8.0f; +static const float SPOT_LIGHT_MIN_Z_FAR = 16.0f; + +/* +======================== +R_ComputeSpotLightProjectionMatrix + +Computes the light projection matrix for a spot light. +======================== +*/ +static float R_ComputeSpotLightProjectionMatrix( idRenderLightLocal * light, idRenderMatrix & localProject ) { + const float targetDistSqr = light->parms.target.LengthSqr(); + const float invTargetDist = idMath::InvSqrt( targetDistSqr ); + const float targetDist = invTargetDist * targetDistSqr; + + const idVec3 normalizedTarget = light->parms.target * invTargetDist; + const idVec3 normalizedRight = light->parms.right * ( 0.5f * targetDist / light->parms.right.LengthSqr() ); + const idVec3 normalizedUp = light->parms.up * ( -0.5f * targetDist / light->parms.up.LengthSqr() ); + + localProject[0][0] = normalizedRight[0]; + localProject[0][1] = normalizedRight[1]; + localProject[0][2] = normalizedRight[2]; + localProject[0][3] = 0.0f; + + localProject[1][0] = normalizedUp[0]; + localProject[1][1] = normalizedUp[1]; + localProject[1][2] = normalizedUp[2]; + localProject[1][3] = 0.0f; + + localProject[3][0] = normalizedTarget[0]; + localProject[3][1] = normalizedTarget[1]; + localProject[3][2] = normalizedTarget[2]; + localProject[3][3] = 0.0f; + + // Set the falloff vector. + // This is similar to the Z calculation for depth buffering, which means that the + // mapped texture is going to be perspective distorted heavily towards the zero end. + const float zNear = Max( light->parms.start * normalizedTarget, SPOT_LIGHT_MIN_Z_NEAR ); + const float zFar = Max( light->parms.end * normalizedTarget, SPOT_LIGHT_MIN_Z_FAR ); + const float zScale = ( zNear + zFar ) / zFar; + + localProject[2][0] = normalizedTarget[0] * zScale; + localProject[2][1] = normalizedTarget[1] * zScale; + localProject[2][2] = normalizedTarget[2] * zScale; + localProject[2][3] = - zNear * zScale; + + // now offset to the 0.0 - 1.0 texture range instead of -1.0 to 1.0 clip space range + idVec4 projectedTarget; + localProject.TransformPoint( light->parms.target, projectedTarget ); + + const float ofs0 = 0.5f - projectedTarget[0] / projectedTarget[3]; + localProject[0][0] += ofs0 * localProject[3][0]; + localProject[0][1] += ofs0 * localProject[3][1]; + localProject[0][2] += ofs0 * localProject[3][2]; + localProject[0][3] += ofs0 * localProject[3][3]; + + const float ofs1 = 0.5f - projectedTarget[1] / projectedTarget[3]; + localProject[1][0] += ofs1 * localProject[3][0]; + localProject[1][1] += ofs1 * localProject[3][1]; + localProject[1][2] += ofs1 * localProject[3][2]; + localProject[1][3] += ofs1 * localProject[3][3]; + + return 1.0f / ( zNear + zFar ); +} + +/* +======================== +R_ComputeParallelLightProjectionMatrix + +Computes the light projection matrix for a parallel light. +======================== +*/ +static float R_ComputeParallelLightProjectionMatrix( idRenderLightLocal * light, idRenderMatrix & localProject ) { + assert( light->parms.parallel ); + + // A parallel light uses a box projection. + // This projects into the 0.0 - 1.0 texture range instead of -1.0 to 1.0 clip space range. + localProject.Zero(); + localProject[0][0] = 0.5f / light->parms.lightRadius[0]; + localProject[1][1] = 0.5f / light->parms.lightRadius[1]; + localProject[2][2] = 0.5f / light->parms.lightRadius[2]; + localProject[0][3] = 0.5f; + localProject[1][3] = 0.5f; + localProject[2][3] = 0.5f; + localProject[3][3] = 1.0f; // identity perspective + + return 1.0f; +} + +/* +================= +R_DeriveLightData + +Fills everything in based on light->parms +================= +*/ +static void R_DeriveLightData( idRenderLightLocal * light ) { + + // decide which light shader we are going to use + if ( light->parms.shader != NULL ) { + light->lightShader = light->parms.shader; + } else if ( light->lightShader == NULL ) { + if ( light->parms.pointLight ) { + light->lightShader = tr.defaultPointLight; + } else { + light->lightShader = tr.defaultProjectedLight; + } + } + + // get the falloff image + light->falloffImage = light->lightShader->LightFalloffImage(); + + if ( light->falloffImage == NULL ) { + // use the falloff from the default shader of the correct type + const idMaterial * defaultShader; + + if ( light->parms.pointLight ) { + defaultShader = tr.defaultPointLight; + + // Touch the default shader. to make sure it's decl has been parsed ( it might have been purged ). + declManager->Touch( static_cast< const idDecl *>( defaultShader ) ); + + light->falloffImage = defaultShader->LightFalloffImage(); + + } else { + // projected lights by default don't diminish with distance + defaultShader = tr.defaultProjectedLight; + + // Touch the light shader. to make sure it's decl has been parsed ( it might have been purged ). + declManager->Touch( static_cast< const idDecl *>( defaultShader ) ); + + light->falloffImage = defaultShader->LightFalloffImage(); + } + } + + // ------------------------------------ + // compute the light projection matrix + // ------------------------------------ + + idRenderMatrix localProject; + float zScale = 1.0f; + if ( light->parms.parallel ) { + zScale = R_ComputeParallelLightProjectionMatrix( light, localProject ); + } else if ( light->parms.pointLight ) { + zScale = R_ComputePointLightProjectionMatrix( light, localProject ); + } else { + zScale = R_ComputeSpotLightProjectionMatrix( light, localProject ); + } + + // set the old style light projection where Z and W are flipped and + // for projected lights lightProject[3] is divided by ( zNear + zFar ) + light->lightProject[0][0] = localProject[0][0]; + light->lightProject[0][1] = localProject[0][1]; + light->lightProject[0][2] = localProject[0][2]; + light->lightProject[0][3] = localProject[0][3]; + + light->lightProject[1][0] = localProject[1][0]; + light->lightProject[1][1] = localProject[1][1]; + light->lightProject[1][2] = localProject[1][2]; + light->lightProject[1][3] = localProject[1][3]; + + light->lightProject[2][0] = localProject[3][0]; + light->lightProject[2][1] = localProject[3][1]; + light->lightProject[2][2] = localProject[3][2]; + light->lightProject[2][3] = localProject[3][3]; + + light->lightProject[3][0] = localProject[2][0] * zScale; + light->lightProject[3][1] = localProject[2][1] * zScale; + light->lightProject[3][2] = localProject[2][2] * zScale; + light->lightProject[3][3] = localProject[2][3] * zScale; + + // transform the lightProject + float lightTransform[16]; + R_AxisToModelMatrix( light->parms.axis, light->parms.origin, lightTransform ); + for ( int i = 0; i < 4; i++ ) { + idPlane temp = light->lightProject[i]; + R_LocalPlaneToGlobal( lightTransform, temp, light->lightProject[i] ); + } + + // adjust global light origin for off center projections and parallel projections + // we are just faking parallel by making it a very far off center for now + if ( light->parms.parallel ) { + idVec3 dir = light->parms.lightCenter; + if ( dir.Normalize() == 0.0f ) { + // make point straight up if not specified + dir[2] = 1.0f; + } + light->globalLightOrigin = light->parms.origin + dir * 100000.0f; + } else { + light->globalLightOrigin = light->parms.origin + light->parms.axis * light->parms.lightCenter; + } + + // Rotate and translate the light projection by the light matrix. + // 99% of lights remain axis aligned in world space. + idRenderMatrix lightMatrix; + idRenderMatrix::CreateFromOriginAxis( light->parms.origin, light->parms.axis, lightMatrix ); + + idRenderMatrix inverseLightMatrix; + if ( !idRenderMatrix::Inverse( lightMatrix, inverseLightMatrix ) ) { + idLib::Warning( "lightMatrix invert failed" ); + } + + // 'baseLightProject' goes from global space -> light local space -> light projective space + idRenderMatrix::Multiply( localProject, inverseLightMatrix, light->baseLightProject ); + + // Invert the light projection so we can deform zero-to-one cubes into + // the light model and calculate global bounds. + if ( !idRenderMatrix::Inverse( light->baseLightProject, light->inverseBaseLightProject ) ) { + idLib::Warning( "baseLightProject invert failed" ); + } + + // calculate the global light bounds by inverse projecting the zero to one cube with the 'inverseBaseLightProject' + idRenderMatrix::ProjectedBounds( light->globalLightBounds, light->inverseBaseLightProject, bounds_zeroOneCube, false ); +} + +/* +==================== +R_FreeLightDefDerivedData + +Frees all references and lit surfaces from the light +==================== +*/ +void R_FreeLightDefDerivedData( idRenderLightLocal *ldef ) { + // remove any portal fog references + for ( doublePortal_t *dp = ldef->foggedPortals; dp != NULL; dp = dp->nextFoggedPortal ) { + dp->fogLight = NULL; + } + + // free all the interactions + while ( ldef->firstInteraction != NULL ) { + ldef->firstInteraction->UnlinkAndFree(); + } + + // free all the references to the light + areaReference_t * nextRef = NULL; + for ( areaReference_t * lref = ldef->references; lref != NULL; lref = nextRef ) { + nextRef = lref->ownerNext; + + // unlink from the area + lref->areaNext->areaPrev = lref->areaPrev; + lref->areaPrev->areaNext = lref->areaNext; + + // put it back on the free list for reuse + ldef->world->areaReferenceAllocator.Free( lref ); + } + ldef->references = NULL; +} + +/* +=============== +WindingCompletelyInsideLight +=============== +*/ +static bool WindingCompletelyInsideLight( const idWinding *w, const idRenderLightLocal *ldef ) { + for ( int i = 0; i < w->GetNumPoints(); i++ ) { + if ( idRenderMatrix::CullPointToMVP( ldef->baseLightProject, (*w)[i].ToVec3(), true ) ) { + return false; + } + } + return true; +} + +/* +====================== +R_CreateLightDefFogPortals + +When a fog light is created or moved, see if it completely +encloses any portals, which may allow them to be fogged closed. +====================== +*/ +static void R_CreateLightDefFogPortals( idRenderLightLocal *ldef ) { + ldef->foggedPortals = NULL; + + if ( !ldef->lightShader->IsFogLight() ) { + return; + } + + // some fog lights will explicitly disallow portal fogging + if ( ldef->lightShader->TestMaterialFlag( MF_NOPORTALFOG ) ) { + return; + } + + for ( areaReference_t * lref = ldef->references; lref != NULL; lref = lref->ownerNext ) { + // check all the models in this area + portalArea_t * area = lref->area; + + for ( portal_t * prt = area->portals; prt != NULL; prt = prt->next ) { + doublePortal_t * dp = prt->doublePortal; + + // we only handle a single fog volume covering a portal + // this will never cause incorrect drawing, but it may + // fail to cull a portal + if ( dp->fogLight ) { + continue; + } + + if ( WindingCompletelyInsideLight( prt->w, ldef ) ) { + dp->fogLight = ldef; + dp->nextFoggedPortal = ldef->foggedPortals; + ldef->foggedPortals = dp; + } + } + } +} + +/* +================= +R_CreateLightRefs +================= +*/ +void R_CreateLightRefs( idRenderLightLocal * light ) { + // derive light data + R_DeriveLightData( light ); + + // determine the areaNum for the light origin, which may let us + // cull the light if it is behind a closed door + // it is debatable if we want to use the entity origin or the center offset origin, + // but we definitely don't want to use a parallel offset origin + light->areaNum = light->world->PointInArea( light->globalLightOrigin ); + if ( light->areaNum == -1 ) { + light->areaNum = light->world->PointInArea( light->parms.origin ); + } + + // bump the view count so we can tell if an + // area already has a reference + tr.viewCount++; + + // if we have a prelight model that includes all the shadows for the major world occluders, + // we can limit the area references to those visible through the portals from the light center. + // We can't do this in the normal case, because shadows are cast from back facing triangles, which + // may be in areas not directly visible to the light projection center. + if ( light->parms.prelightModel != NULL && r_useLightPortalFlow.GetBool() && light->lightShader->LightCastsShadows() ) { + light->world->FlowLightThroughPortals( light ); + } else { + // push the light frustum down the BSP tree into areas + light->world->PushFrustumIntoTree( NULL, light, light->inverseBaseLightProject, bounds_zeroOneCube ); + } + + R_CreateLightDefFogPortals( light ); +} + +/* +================================================================================= + +WORLD MODEL & LIGHT DEFS + +================================================================================= +*/ + +/* +=================== +R_FreeDerivedData + +ReloadModels and RegenerateWorld call this +=================== +*/ +void R_FreeDerivedData() { + for ( int j = 0; j < tr.worlds.Num(); j++ ) { + idRenderWorldLocal * rw = tr.worlds[j]; + + for ( int i = 0; i < rw->entityDefs.Num(); i++ ) { + idRenderEntityLocal * def = rw->entityDefs[i]; + if ( def == NULL ) { + continue; + } + R_FreeEntityDefDerivedData( def, false, false ); + } + + for ( int i = 0; i < rw->lightDefs.Num(); i++ ) { + idRenderLightLocal * light = rw->lightDefs[i]; + if ( light == NULL ) { + continue; + } + R_FreeLightDefDerivedData( light ); + } + } +} + +/* +=================== +R_CheckForEntityDefsUsingModel +=================== +*/ +void R_CheckForEntityDefsUsingModel( idRenderModel *model ) { + for ( int j = 0; j < tr.worlds.Num(); j++ ) { + idRenderWorldLocal * rw = tr.worlds[j]; + + for ( int i = 0; i < rw->entityDefs.Num(); i++ ) { + idRenderEntityLocal * def = rw->entityDefs[i]; + if ( !def ) { + continue; + } + if ( def->parms.hModel == model ) { + //assert( 0 ); + // this should never happen but Radiant messes it up all the time so just free the derived data + R_FreeEntityDefDerivedData( def, false, false ); + } + } + } +} + +/* +=================== +R_ReCreateWorldReferences + +ReloadModels and RegenerateWorld call this +=================== +*/ +void R_ReCreateWorldReferences() { + // let the interaction generation code know this + // shouldn't be optimized for a particular view + tr.viewDef = NULL; + + for ( int j = 0; j < tr.worlds.Num(); j++ ) { + idRenderWorldLocal * rw = tr.worlds[j]; + + for ( int i = 0; i < rw->entityDefs.Num(); i++ ) { + idRenderEntityLocal * def = rw->entityDefs[i]; + if ( def == NULL ) { + continue; + } + // the world model entities are put specifically in a single + // area, instead of just pushing their bounds into the tree + if ( i < rw->numPortalAreas ) { + rw->AddEntityRefToArea( def, &rw->portalAreas[i] ); + } else { + R_CreateEntityRefs( def ); + } + } + + for ( int i = 0; i < rw->lightDefs.Num(); i++ ) { + idRenderLightLocal * light = rw->lightDefs[i]; + if ( light == NULL ) { + continue; + } + renderLight_t parms = light->parms; + + light->world->FreeLightDef( i ); + rw->UpdateLightDef( i, &parms ); + } + } +} + +/* +==================== +R_ModulateLights_f + +Modifies the shaderParms on all the lights so the level +designers can easily test different color schemes +==================== +*/ +void R_ModulateLights_f( const idCmdArgs &args ) { + if ( !tr.primaryWorld ) { + return; + } + if ( args.Argc() != 4 ) { + common->Printf( "usage: modulateLights \n" ); + return; + } + + float modulate[3]; + for ( int i = 0; i < 3; i++ ) { + modulate[i] = atof( args.Argv( i+1 ) ); + } + + int count = 0; + for ( int i = 0; i < tr.primaryWorld->lightDefs.Num(); i++ ) { + idRenderLightLocal * light = tr.primaryWorld->lightDefs[i]; + if ( light != NULL ) { + count++; + for ( int j = 0; j < 3; j++ ) { + light->parms.shaderParms[j] *= modulate[j]; + } + } + } + common->Printf( "modulated %i lights\n", count ); +} diff --git a/neo/renderer/RenderWorld_demo.cpp b/neo/renderer/RenderWorld_demo.cpp new file mode 100644 index 00000000..69d8f011 --- /dev/null +++ b/neo/renderer/RenderWorld_demo.cpp @@ -0,0 +1,717 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +//#define WRITE_GUIS + +typedef struct { + int version; + int sizeofRenderEntity; + int sizeofRenderLight; + char mapname[256]; +} demoHeader_t; + + +/* +============== +StartWritingDemo +============== +*/ +void idRenderWorldLocal::StartWritingDemo( idDemoFile *demo ) { + int i; + + // FIXME: we should track the idDemoFile locally, instead of snooping into session for it + + WriteLoadMap(); + + // write the door portal state + for ( i = 0 ; i < numInterAreaPortals ; i++ ) { + if ( doublePortals[i].blockingBits ) { + SetPortalState( i+1, doublePortals[i].blockingBits ); + } + } + + // clear the archive counter on all defs + for ( i = 0 ; i < lightDefs.Num() ; i++ ) { + if ( lightDefs[i] ) { + lightDefs[i]->archived = false; + } + } + for ( i = 0 ; i < entityDefs.Num() ; i++ ) { + if ( entityDefs[i] ) { + entityDefs[i]->archived = false; + } + } +} + +void idRenderWorldLocal::StopWritingDemo() { +// writeDemo = NULL; +} + +/* +============== +ProcessDemoCommand +============== +*/ +bool idRenderWorldLocal::ProcessDemoCommand( idDemoFile *readDemo, renderView_t *renderView, int *demoTimeOffset ) { + bool newMap = false; + + if ( !readDemo ) { + return false; + } + + demoCommand_t dc; + qhandle_t h; + + if ( !readDemo->ReadInt( (int&)dc ) ) { + // a demoShot may not have an endFrame, but it is still valid + return false; + } + + switch( dc ) { + case DC_LOADMAP: + // read the initial data + demoHeader_t header; + + readDemo->ReadInt( header.version ); + readDemo->ReadInt( header.sizeofRenderEntity ); + readDemo->ReadInt( header.sizeofRenderLight ); + for ( int i = 0; i < 256; i++ ) + readDemo->ReadChar( header.mapname[i] ); + // the internal version value got replaced by DS_VERSION at toplevel + if ( header.version != 4 ) { + common->Error( "Demo version mismatch.\n" ); + } + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_LOADMAP: %s\n", header.mapname ); + } + InitFromMap( header.mapname ); + + newMap = true; // we will need to set demoTimeOffset + + break; + + case DC_RENDERVIEW: + readDemo->ReadInt( renderView->viewID ); + readDemo->ReadFloat( renderView->fov_x ); + readDemo->ReadFloat( renderView->fov_y ); + readDemo->ReadVec3( renderView->vieworg ); + readDemo->ReadMat3( renderView->viewaxis ); + readDemo->ReadBool( renderView->cramZNear ); + readDemo->ReadBool( renderView->forceUpdate ); + // binary compatibility with win32 padded structures + char tmp; + readDemo->ReadChar( tmp ); + readDemo->ReadChar( tmp ); + readDemo->ReadInt( renderView->time[1] ); + for ( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) + readDemo->ReadFloat( renderView->shaderParms[i] ); + + if ( !readDemo->ReadInt( (int&)renderView->globalMaterial ) ) { + return false; + } + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_RENDERVIEW: %i\n", renderView->time ); + } + + // possibly change the time offset if this is from a new map + if ( newMap && demoTimeOffset ) { + *demoTimeOffset = renderView->time[1] - eventLoop->Milliseconds(); + } + return false; + + case DC_UPDATE_ENTITYDEF: + ReadRenderEntity(); + break; + case DC_DELETE_ENTITYDEF: + if ( !readDemo->ReadInt( h ) ) { + return false; + } + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_DELETE_ENTITYDEF: %i\n", h ); + } + FreeEntityDef( h ); + break; + case DC_UPDATE_LIGHTDEF: + ReadRenderLight(); + break; + case DC_DELETE_LIGHTDEF: + if ( !readDemo->ReadInt( h ) ) { + return false; + } + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_DELETE_LIGHTDEF: %i\n", h ); + } + FreeLightDef( h ); + break; + + case DC_CAPTURE_RENDER: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_CAPTURE_RENDER\n" ); + } + renderSystem->CaptureRenderToImage( readDemo->ReadHashString() ); + break; + + case DC_CROP_RENDER: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_CROP_RENDER\n" ); + } + int size[3]; + readDemo->ReadInt( size[0] ); + readDemo->ReadInt( size[1] ); + readDemo->ReadInt( size[2] ); + renderSystem->CropRenderSize( size[0], size[1] ); + break; + + case DC_UNCROP_RENDER: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_UNCROP\n" ); + } + renderSystem->UnCrop(); + break; + + case DC_GUI_MODEL: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_GUI_MODEL\n" ); + } + break; + + case DC_DEFINE_MODEL: + { + idRenderModel *model = renderModelManager->AllocModel(); + model->ReadFromDemoFile( common->ReadDemo() ); + // add to model manager, so we can find it + renderModelManager->AddModel( model ); + + // save it in the list to free when clearing this map + localModels.Append( model ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_DEFINE_MODEL\n" ); + } + break; + } + case DC_SET_PORTAL_STATE: + { + int data[2]; + readDemo->ReadInt( data[0] ); + readDemo->ReadInt( data[1] ); + SetPortalState( data[0], data[1] ); + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_SET_PORTAL_STATE: %i %i\n", data[0], data[1] ); + } + } + + break; + case DC_END_FRAME: + return true; + + default: + common->Error( "Bad token in demo stream" ); + } + + return false; +} + +/* +================ +WriteLoadMap +================ +*/ +void idRenderWorldLocal::WriteLoadMap() { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_LOADMAP ); + + demoHeader_t header; + strncpy( header.mapname, mapName.c_str(), sizeof( header.mapname ) - 1 ); + header.version = 4; + header.sizeofRenderEntity = sizeof( renderEntity_t ); + header.sizeofRenderLight = sizeof( renderLight_t ); + common->WriteDemo()->WriteInt( header.version ); + common->WriteDemo()->WriteInt( header.sizeofRenderEntity ); + common->WriteDemo()->WriteInt( header.sizeofRenderLight ); + for ( int i = 0; i < 256; i++ ) + common->WriteDemo()->WriteChar( header.mapname[i] ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_DELETE_LIGHTDEF: %s\n", mapName.c_str() ); + } +} + +/* +================ +WriteVisibleDefs + +================ +*/ +void idRenderWorldLocal::WriteVisibleDefs( const viewDef_t *viewDef ) { + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + // make sure all necessary entities and lights are updated + for ( viewEntity_t *viewEnt = viewDef->viewEntitys ; viewEnt ; viewEnt = viewEnt->next ) { + idRenderEntityLocal *ent = viewEnt->entityDef; + + if ( ent->archived ) { + // still up to date + continue; + } + + // write it out + WriteRenderEntity( ent->index, &ent->parms ); + ent->archived = true; + } + + for ( viewLight_t *viewLight = viewDef->viewLights ; viewLight ; viewLight = viewLight->next ) { + idRenderLightLocal *light = viewLight->lightDef; + + if ( light->archived ) { + // still up to date + continue; + } + // write it out + WriteRenderLight( light->index, &light->parms ); + light->archived = true; + } +} + + +/* +================ +WriteRenderView +================ +*/ +void idRenderWorldLocal::WriteRenderView( const renderView_t *renderView ) { + int i; + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + // write the actual view command + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_RENDERVIEW ); + common->WriteDemo()->WriteInt( renderView->viewID ); + common->WriteDemo()->WriteFloat( renderView->fov_x ); + common->WriteDemo()->WriteFloat( renderView->fov_y ); + common->WriteDemo()->WriteVec3( renderView->vieworg ); + common->WriteDemo()->WriteMat3( renderView->viewaxis ); + common->WriteDemo()->WriteBool( renderView->cramZNear ); + common->WriteDemo()->WriteBool( renderView->forceUpdate ); + // binary compatibility with old win32 version writing padded structures directly to disk + common->WriteDemo()->WriteUnsignedChar( 0 ); + common->WriteDemo()->WriteUnsignedChar( 0 ); + common->WriteDemo()->WriteInt( renderView->time[1] ); + for ( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) + common->WriteDemo()->WriteFloat( renderView->shaderParms[i] ); + common->WriteDemo()->WriteInt( (int&)renderView->globalMaterial ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_RENDERVIEW: %i\n", renderView->time ); + } +} + +/* +================ +WriteFreeEntity +================ +*/ +void idRenderWorldLocal::WriteFreeEntity( qhandle_t handle ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_DELETE_ENTITYDEF ); + common->WriteDemo()->WriteInt( handle ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_DELETE_ENTITYDEF: %i\n", handle ); + } +} + +/* +================ +WriteFreeLightEntity +================ +*/ +void idRenderWorldLocal::WriteFreeLight( qhandle_t handle ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_DELETE_LIGHTDEF ); + common->WriteDemo()->WriteInt( handle ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_DELETE_LIGHTDEF: %i\n", handle ); + } +} + +/* +================ +WriteRenderLight +================ +*/ +void idRenderWorldLocal::WriteRenderLight( qhandle_t handle, const renderLight_t *light ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_UPDATE_LIGHTDEF ); + common->WriteDemo()->WriteInt( handle ); + + common->WriteDemo()->WriteMat3( light->axis ); + common->WriteDemo()->WriteVec3( light->origin ); + common->WriteDemo()->WriteInt( light->suppressLightInViewID ); + common->WriteDemo()->WriteInt( light->allowLightInViewID ); + common->WriteDemo()->WriteBool( light->noShadows ); + common->WriteDemo()->WriteBool( light->noSpecular ); + common->WriteDemo()->WriteBool( light->pointLight ); + common->WriteDemo()->WriteBool( light->parallel ); + common->WriteDemo()->WriteVec3( light->lightRadius ); + common->WriteDemo()->WriteVec3( light->lightCenter ); + common->WriteDemo()->WriteVec3( light->target ); + common->WriteDemo()->WriteVec3( light->right ); + common->WriteDemo()->WriteVec3( light->up ); + common->WriteDemo()->WriteVec3( light->start ); + common->WriteDemo()->WriteVec3( light->end ); + common->WriteDemo()->WriteInt( (int&)light->prelightModel ); + common->WriteDemo()->WriteInt( light->lightId ); + common->WriteDemo()->WriteInt( (int&)light->shader ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++) + common->WriteDemo()->WriteFloat( light->shaderParms[i] ); + common->WriteDemo()->WriteInt( (int&)light->referenceSound ); + + if ( light->prelightModel ) { + common->WriteDemo()->WriteHashString( light->prelightModel->Name() ); + } + if ( light->shader ) { + common->WriteDemo()->WriteHashString( light->shader->GetName() ); + } + if ( light->referenceSound ) { + int index = light->referenceSound->Index(); + common->WriteDemo()->WriteInt( index ); + } + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_UPDATE_LIGHTDEF: %i\n", handle ); + } +} + +/* +================ +ReadRenderLight +================ +*/ +void idRenderWorldLocal::ReadRenderLight( ) { + renderLight_t light; + int index; + + common->ReadDemo()->ReadInt( index ); + if ( index < 0 ) { + common->Error( "ReadRenderLight: index < 0 " ); + } + + common->ReadDemo()->ReadMat3( light.axis ); + common->ReadDemo()->ReadVec3( light.origin ); + common->ReadDemo()->ReadInt( light.suppressLightInViewID ); + common->ReadDemo()->ReadInt( light.allowLightInViewID ); + common->ReadDemo()->ReadBool( light.noShadows ); + common->ReadDemo()->ReadBool( light.noSpecular ); + common->ReadDemo()->ReadBool( light.pointLight ); + common->ReadDemo()->ReadBool( light.parallel ); + common->ReadDemo()->ReadVec3( light.lightRadius ); + common->ReadDemo()->ReadVec3( light.lightCenter ); + common->ReadDemo()->ReadVec3( light.target ); + common->ReadDemo()->ReadVec3( light.right ); + common->ReadDemo()->ReadVec3( light.up ); + common->ReadDemo()->ReadVec3( light.start ); + common->ReadDemo()->ReadVec3( light.end ); + common->ReadDemo()->ReadInt( (int&)light.prelightModel ); + common->ReadDemo()->ReadInt( light.lightId ); + common->ReadDemo()->ReadInt( (int&)light.shader ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++) + common->ReadDemo()->ReadFloat( light.shaderParms[i] ); + common->ReadDemo()->ReadInt( (int&)light.referenceSound ); + if ( light.prelightModel ) { + light.prelightModel = renderModelManager->FindModel( common->ReadDemo()->ReadHashString() ); + } + if ( light.shader ) { + light.shader = declManager->FindMaterial( common->ReadDemo()->ReadHashString() ); + } + if ( light.referenceSound ) { + int index; + common->ReadDemo()->ReadInt( index ); + light.referenceSound = common->SW()->EmitterForIndex( index ); + } + + UpdateLightDef( index, &light ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_UPDATE_LIGHTDEF: %i\n", index ); + } +} + +/* +================ +WriteRenderEntity +================ +*/ +void idRenderWorldLocal::WriteRenderEntity( qhandle_t handle, const renderEntity_t *ent ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != common->RW() ) { + return; + } + + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_UPDATE_ENTITYDEF ); + common->WriteDemo()->WriteInt( handle ); + + common->WriteDemo()->WriteInt( (int&)ent->hModel ); + common->WriteDemo()->WriteInt( ent->entityNum ); + common->WriteDemo()->WriteInt( ent->bodyId ); + common->WriteDemo()->WriteVec3( ent->bounds[0] ); + common->WriteDemo()->WriteVec3( ent->bounds[1] ); + common->WriteDemo()->WriteInt( (int&)ent->callback ); + common->WriteDemo()->WriteInt( (int&)ent->callbackData ); + common->WriteDemo()->WriteInt( ent->suppressSurfaceInViewID ); + common->WriteDemo()->WriteInt( ent->suppressShadowInViewID ); + common->WriteDemo()->WriteInt( ent->suppressShadowInLightID ); + common->WriteDemo()->WriteInt( ent->allowSurfaceInViewID ); + common->WriteDemo()->WriteVec3( ent->origin ); + common->WriteDemo()->WriteMat3( ent->axis ); + common->WriteDemo()->WriteInt( (int&)ent->customShader ); + common->WriteDemo()->WriteInt( (int&)ent->referenceShader ); + common->WriteDemo()->WriteInt( (int&)ent->customSkin ); + common->WriteDemo()->WriteInt( (int&)ent->referenceSound ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) + common->WriteDemo()->WriteFloat( ent->shaderParms[i] ); + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) + common->WriteDemo()->WriteInt( (int&)ent->gui[i] ); + common->WriteDemo()->WriteInt( (int&)ent->remoteRenderView ); + common->WriteDemo()->WriteInt( ent->numJoints ); + common->WriteDemo()->WriteInt( (int&)ent->joints ); + common->WriteDemo()->WriteFloat( ent->modelDepthHack ); + common->WriteDemo()->WriteBool( ent->noSelfShadow ); + common->WriteDemo()->WriteBool( ent->noShadow ); + common->WriteDemo()->WriteBool( ent->noDynamicInteractions ); + common->WriteDemo()->WriteBool( ent->weaponDepthHack ); + common->WriteDemo()->WriteInt( ent->forceUpdate ); + + if ( ent->customShader ) { + common->WriteDemo()->WriteHashString( ent->customShader->GetName() ); + } + if ( ent->customSkin ) { + common->WriteDemo()->WriteHashString( ent->customSkin->GetName() ); + } + if ( ent->hModel ) { + common->WriteDemo()->WriteHashString( ent->hModel->Name() ); + } + if ( ent->referenceShader ) { + common->WriteDemo()->WriteHashString( ent->referenceShader->GetName() ); + } + if ( ent->referenceSound ) { + int index = ent->referenceSound->Index(); + common->WriteDemo()->WriteInt( index ); + } + if ( ent->numJoints ) { + for ( int i = 0; i < ent->numJoints; i++) { + float *data = ent->joints[i].ToFloatPtr(); + for ( int j = 0; j < 12; ++j) + common->WriteDemo()->WriteFloat( data[j] ); + } + } + + /* + if ( ent->decals ) { + ent->decals->WriteToDemoFile( common->ReadDemo() ); + } + if ( ent->overlays ) { + ent->overlays->WriteToDemoFile( common->WriteDemo() ); + } + */ + +#ifdef WRITE_GUIS + if ( ent->gui ) { + ent->gui->WriteToDemoFile( common->WriteDemo() ); + } + if ( ent->gui2 ) { + ent->gui2->WriteToDemoFile( common->WriteDemo() ); + } + if ( ent->gui3 ) { + ent->gui3->WriteToDemoFile( common->WriteDemo() ); + } +#endif + + // RENDERDEMO_VERSION >= 2 ( Doom3 1.2 ) + common->WriteDemo()->WriteInt( ent->timeGroup ); + common->WriteDemo()->WriteInt( ent->xrayIndex ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_UPDATE_ENTITYDEF: %i = %s\n", handle, ent->hModel ? ent->hModel->Name() : "NULL" ); + } +} + +/* +================ +ReadRenderEntity +================ +*/ +void idRenderWorldLocal::ReadRenderEntity() { + renderEntity_t ent; + int index, i; + + common->ReadDemo()->ReadInt( index ); + if ( index < 0 ) { + common->Error( "ReadRenderEntity: index < 0" ); + } + + common->ReadDemo()->ReadInt( (int&)ent.hModel ); + common->ReadDemo()->ReadInt( ent.entityNum ); + common->ReadDemo()->ReadInt( ent.bodyId ); + common->ReadDemo()->ReadVec3( ent.bounds[0] ); + common->ReadDemo()->ReadVec3( ent.bounds[1] ); + common->ReadDemo()->ReadInt( (int&)ent.callback ); + common->ReadDemo()->ReadInt( (int&)ent.callbackData ); + common->ReadDemo()->ReadInt( ent.suppressSurfaceInViewID ); + common->ReadDemo()->ReadInt( ent.suppressShadowInViewID ); + common->ReadDemo()->ReadInt( ent.suppressShadowInLightID ); + common->ReadDemo()->ReadInt( ent.allowSurfaceInViewID ); + common->ReadDemo()->ReadVec3( ent.origin ); + common->ReadDemo()->ReadMat3( ent.axis ); + common->ReadDemo()->ReadInt( (int&)ent.customShader ); + common->ReadDemo()->ReadInt( (int&)ent.referenceShader ); + common->ReadDemo()->ReadInt( (int&)ent.customSkin ); + common->ReadDemo()->ReadInt( (int&)ent.referenceSound ); + for ( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + common->ReadDemo()->ReadFloat( ent.shaderParms[i] ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + common->ReadDemo()->ReadInt( (int&)ent.gui[i] ); + } + common->ReadDemo()->ReadInt( (int&)ent.remoteRenderView ); + common->ReadDemo()->ReadInt( ent.numJoints ); + common->ReadDemo()->ReadInt( (int&)ent.joints ); + common->ReadDemo()->ReadFloat( ent.modelDepthHack ); + common->ReadDemo()->ReadBool( ent.noSelfShadow ); + common->ReadDemo()->ReadBool( ent.noShadow ); + common->ReadDemo()->ReadBool( ent.noDynamicInteractions ); + common->ReadDemo()->ReadBool( ent.weaponDepthHack ); + common->ReadDemo()->ReadInt( ent.forceUpdate ); + ent.callback = NULL; + if ( ent.customShader ) { + ent.customShader = declManager->FindMaterial( common->ReadDemo()->ReadHashString() ); + } + if ( ent.customSkin ) { + ent.customSkin = declManager->FindSkin( common->ReadDemo()->ReadHashString() ); + } + if ( ent.hModel ) { + ent.hModel = renderModelManager->FindModel( common->ReadDemo()->ReadHashString() ); + } + if ( ent.referenceShader ) { + ent.referenceShader = declManager->FindMaterial( common->ReadDemo()->ReadHashString() ); + } + if ( ent.referenceSound ) { + int index; + common->ReadDemo()->ReadInt( index ); + ent.referenceSound = common->SW()->EmitterForIndex( index ); + } + if ( ent.numJoints ) { + ent.joints = (idJointMat *)Mem_Alloc16( SIMD_ROUND_JOINTS( ent.numJoints ) * sizeof( ent.joints[0] ), TAG_JOINTMAT ); + for ( int i = 0; i < ent.numJoints; i++) { + float *data = ent.joints[i].ToFloatPtr(); + for ( int j = 0; j < 12; ++j ) { + common->ReadDemo()->ReadFloat( data[j] ); + } + } + SIMD_INIT_LAST_JOINT( ent.joints, ent.numJoints ); + } + + ent.callbackData = NULL; + + /* + if ( ent.decals ) { + ent.decals = idRenderModelDecal::Alloc(); + ent.decals->ReadFromDemoFile( common->ReadDemo() ); + } + if ( ent.overlays ) { + ent.overlays = idRenderModelOverlay::Alloc(); + ent.overlays->ReadFromDemoFile( common->ReadDemo() ); + } + */ + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( ent.gui[ i ] ) { + ent.gui[ i ] = uiManager->Alloc(); +#ifdef WRITE_GUIS + ent.gui[ i ]->ReadFromDemoFile( common->ReadDemo() ); +#endif + } + } + + common->ReadDemo()->ReadInt( ent.timeGroup ); + common->ReadDemo()->ReadInt( ent.xrayIndex ); + + UpdateEntityDef( index, &ent ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_UPDATE_ENTITYDEF: %i = %s\n", index, ent.hModel ? ent.hModel->Name() : "NULL" ); + } +} diff --git a/neo/renderer/RenderWorld_load.cpp b/neo/renderer/RenderWorld_load.cpp new file mode 100644 index 00000000..41d948d2 --- /dev/null +++ b/neo/renderer/RenderWorld_load.cpp @@ -0,0 +1,1031 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + + +/* +================ +idRenderWorldLocal::FreeWorld +================ +*/ +void idRenderWorldLocal::FreeWorld() { + // this will free all the lightDefs and entityDefs + FreeDefs(); + + // free all the portals and check light/model references + for ( int i = 0; i < numPortalAreas; i++ ) { + portalArea_t *area; + portal_t *portal, *nextPortal; + + area = &portalAreas[i]; + for ( portal = area->portals; portal; portal = nextPortal ) { + nextPortal = portal->next; + delete portal->w; + R_StaticFree( portal ); + } + + // there shouldn't be any remaining lightRefs or entityRefs + if ( area->lightRefs.areaNext != &area->lightRefs ) { + common->Error( "FreeWorld: unexpected remaining lightRefs" ); + } + if ( area->entityRefs.areaNext != &area->entityRefs ) { + common->Error( "FreeWorld: unexpected remaining entityRefs" ); + } + } + + if ( portalAreas ) { + R_StaticFree( portalAreas ); + portalAreas = NULL; + numPortalAreas = 0; + R_StaticFree( areaScreenRect ); + areaScreenRect = NULL; + } + + if ( doublePortals ) { + R_StaticFree( doublePortals ); + doublePortals = NULL; + numInterAreaPortals = 0; + } + + if ( areaNodes ) { + R_StaticFree( areaNodes ); + areaNodes = NULL; + } + + // free all the inline idRenderModels + for ( int i = 0; i < localModels.Num(); i++ ) { + renderModelManager->RemoveModel( localModels[i] ); + delete localModels[i]; + } + localModels.Clear(); + + areaReferenceAllocator.Shutdown(); + interactionAllocator.Shutdown(); + + mapName = ""; +} + +/* +================ +idRenderWorldLocal::TouchWorldModels +================ +*/ +void idRenderWorldLocal::TouchWorldModels() { + for ( int i = 0; i < localModels.Num(); i++ ) { + renderModelManager->CheckModel( localModels[i]->Name() ); + } +} + +/* +================ +idRenderWorldLocal::ReadBinaryShadowModel +================ +*/ +idRenderModel *idRenderWorldLocal::ReadBinaryModel( idFile *fileIn ) { + idStrStatic< MAX_OSPATH > name; + fileIn->ReadString( name ); + idRenderModel * model = renderModelManager->AllocModel(); + model->InitEmpty( name ); + if ( model->LoadBinaryModel( fileIn, mapTimeStamp ) ) { + return model; + } + return NULL; +} + +extern idCVar r_binaryLoadRenderModels; + +/* +================ +idRenderWorldLocal::ParseModel +================ +*/ +idRenderModel *idRenderWorldLocal::ParseModel( idLexer *src, const char *mapName, ID_TIME_T mapTimeStamp, idFile *fileOut ) { + idToken token; + + src->ExpectTokenString( "{" ); + + // parse the name + src->ExpectAnyToken( &token ); + + idRenderModel * model = renderModelManager->AllocModel(); + model->InitEmpty( token ); + + if ( fileOut != NULL ) { + // write out the type so the binary reader knows what to instantiate + fileOut->WriteString( "shadowmodel" ); + fileOut->WriteString( token ); + } + + int numSurfaces = src->ParseInt(); + if ( numSurfaces < 0 ) { + src->Error( "R_ParseModel: bad numSurfaces" ); + } + + for ( int i = 0; i < numSurfaces; i++ ) { + src->ExpectTokenString( "{" ); + + src->ExpectAnyToken( &token ); + + modelSurface_t surf; + surf.shader = declManager->FindMaterial( token ); + + ((idMaterial*)surf.shader)->AddReference(); + + srfTriangles_t * tri = R_AllocStaticTriSurf(); + surf.geometry = tri; + + tri->numVerts = src->ParseInt(); + tri->numIndexes = src->ParseInt(); + + // parse the vertices + idTempArray verts( tri->numVerts * 8 ); + for ( int j = 0; j < tri->numVerts; j++ ) { + src->Parse1DMatrix( 8, &verts[j * 8] ); + } + + // parse the indices + idTempArray indexes( tri->numIndexes ); + for ( int j = 0; j < tri->numIndexes; j++ ) { + indexes[j] = src->ParseInt(); + } + +#if 1 + // find the island that each vertex belongs to + idTempArray vertIslands( tri->numVerts ); + idTempArray trisVisited( tri->numIndexes ); + vertIslands.Zero(); + trisVisited.Zero(); + int numIslands = 0; + for ( int j = 0; j < tri->numIndexes; j += 3 ) { + if ( trisVisited[j] ) { + continue; + } + + int islandNum = ++numIslands; + vertIslands[indexes[j + 0]] = islandNum; + vertIslands[indexes[j + 1]] = islandNum; + vertIslands[indexes[j + 2]] = islandNum; + trisVisited[j] = true; + + idList queue; + queue.Append( j ); + for ( int n = 0; n < queue.Num(); n++ ) { + int t = queue[n]; + for ( int k = 0; k < tri->numIndexes; k += 3 ) { + if ( trisVisited[k] ) { + continue; + } + bool connected = indexes[t + 0] == indexes[k + 0] || indexes[t + 0] == indexes[k + 1] || indexes[t + 0] == indexes[k + 2] || + indexes[t + 1] == indexes[k + 0] || indexes[t + 1] == indexes[k + 1] || indexes[t + 1] == indexes[k + 2] || + indexes[t + 2] == indexes[k + 0] || indexes[t + 2] == indexes[k + 1] || indexes[t + 2] == indexes[k + 2]; + if ( connected ) { + vertIslands[indexes[k + 0]] = islandNum; + vertIslands[indexes[k + 1]] = islandNum; + vertIslands[indexes[k + 2]] = islandNum; + trisVisited[k] = true; + queue.Append( k ); + } + } + } + } + + // center the texture coordinates for each island for maximum 16-bit precision + for ( int j = 1; j <= numIslands; j++ ) { + float minS = idMath::INFINITY; + float minT = idMath::INFINITY; + float maxS = -idMath::INFINITY; + float maxT = -idMath::INFINITY; + for ( int k = 0; k < tri->numVerts; k++ ) { + if ( vertIslands[k] == j ) { + minS = Min( minS, verts[k * 8 + 3] ); + maxS = Max( maxS, verts[k * 8 + 3] ); + minT = Min( minT, verts[k * 8 + 4] ); + maxT = Max( maxT, verts[k * 8 + 4] ); + } + } + const float averageS = idMath::Ftoi( ( minS + maxS ) * 0.5f ); + const float averageT = idMath::Ftoi( ( minT + maxT ) * 0.5f ); + for ( int k = 0; k < tri->numVerts; k++ ) { + if ( vertIslands[k] == j ) { + verts[k * 8 + 3] -= averageS; + verts[k * 8 + 4] -= averageT; + } + } + } +#endif + + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( int j = 0; j < tri->numVerts; j++ ) { + tri->verts[j].xyz[0] = verts[j * 8 + 0]; + tri->verts[j].xyz[1] = verts[j * 8 + 1]; + tri->verts[j].xyz[2] = verts[j * 8 + 2]; + tri->verts[j].SetTexCoord( verts[j * 8 + 3], verts[j * 8 + 4] ); + tri->verts[j].SetNormal( verts[j * 8 + 5], verts[j * 8 + 6], verts[j * 8 + 7] ); + } + + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + for ( int j = 0; j < tri->numIndexes; j++ ) { + tri->indexes[j] = indexes[j]; + } + src->ExpectTokenString( "}" ); + + // add the completed surface to the model + model->AddSurface( surf ); + } + + src->ExpectTokenString( "}" ); + + model->FinishSurfaces(); + + if ( fileOut != NULL && model->SupportsBinaryModel() && r_binaryLoadRenderModels.GetBool() ) { + model->WriteBinaryModel( fileOut, &mapTimeStamp ); + } + + return model; +} + +/* +================ +idRenderWorldLocal::ReadBinaryShadowModel +================ +*/ +idRenderModel *idRenderWorldLocal::ReadBinaryShadowModel( idFile *fileIn ) { + idStrStatic< MAX_OSPATH > name; + fileIn->ReadString( name ); + idRenderModel * model = renderModelManager->AllocModel(); + model->InitEmpty( name ); + if ( model->LoadBinaryModel( fileIn, mapTimeStamp ) ) { + return model; + } + return NULL; +} +/* +================ +idRenderWorldLocal::ParseShadowModel +================ +*/ +idRenderModel *idRenderWorldLocal::ParseShadowModel( idLexer *src, idFile *fileOut ) { + idToken token; + + src->ExpectTokenString( "{" ); + + // parse the name + src->ExpectAnyToken( &token ); + + idRenderModel * model = renderModelManager->AllocModel(); + model->InitEmpty( token ); + + if ( fileOut != NULL ) { + // write out the type so the binary reader knows what to instantiate + fileOut->WriteString( "shadowmodel" ); + fileOut->WriteString( token ); + } + + srfTriangles_t * tri = R_AllocStaticTriSurf(); + + tri->numVerts = src->ParseInt(); + tri->numShadowIndexesNoCaps = src->ParseInt(); + tri->numShadowIndexesNoFrontCaps = src->ParseInt(); + tri->numIndexes = src->ParseInt(); + tri->shadowCapPlaneBits = src->ParseInt(); + + assert( ( tri->numVerts & 1 ) == 0 ); + + R_AllocStaticTriSurfPreLightShadowVerts( tri, ALIGN( tri->numVerts, 2 ) ); + tri->bounds.Clear(); + for ( int j = 0; j < tri->numVerts; j++ ) { + float vec[8]; + + src->Parse1DMatrix( 3, vec ); + tri->preLightShadowVertexes[j].xyzw[0] = vec[0]; + tri->preLightShadowVertexes[j].xyzw[1] = vec[1]; + tri->preLightShadowVertexes[j].xyzw[2] = vec[2]; + tri->preLightShadowVertexes[j].xyzw[3] = 1.0f; // no homogenous value + + tri->bounds.AddPoint( tri->preLightShadowVertexes[j].xyzw.ToVec3() ); + } + // clear the last vertex if it wasn't stored + if ( ( tri->numVerts & 1 ) != 0 ) { + tri->preLightShadowVertexes[ALIGN( tri->numVerts, 2 ) - 1].xyzw.Zero(); + } + + // to be consistent set the number of vertices to half the number of shadow vertices + tri->numVerts = ALIGN( tri->numVerts, 2 ) / 2; + + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + for ( int j = 0; j < tri->numIndexes; j++ ) { + tri->indexes[j] = src->ParseInt(); + } + + // add the completed surface to the model + modelSurface_t surf; + surf.id = 0; + surf.shader = tr.defaultMaterial; + surf.geometry = tri; + + model->AddSurface( surf ); + + src->ExpectTokenString( "}" ); + + // NOTE: we do NOT do a model->FinishSurfaceces, because we don't need sil edges, planes, tangents, etc. + + if ( fileOut != NULL && model->SupportsBinaryModel() && r_binaryLoadRenderModels.GetBool() ) { + model->WriteBinaryModel( fileOut, &mapTimeStamp ); + } + + return model; +} + +/* +================ +idRenderWorldLocal::SetupAreaRefs +================ +*/ +void idRenderWorldLocal::SetupAreaRefs() { + connectedAreaNum = 0; + for ( int i = 0; i < numPortalAreas; i++ ) { + portalAreas[i].areaNum = i; + portalAreas[i].lightRefs.areaNext = + portalAreas[i].lightRefs.areaPrev = &portalAreas[i].lightRefs; + portalAreas[i].entityRefs.areaNext = + portalAreas[i].entityRefs.areaPrev = &portalAreas[i].entityRefs; + } +} + +/* +================ +idRenderWorldLocal::ParseInterAreaPortals +================ +*/ +void idRenderWorldLocal::ParseInterAreaPortals( idLexer *src, idFile *fileOut ) { + src->ExpectTokenString( "{" ); + + numPortalAreas = src->ParseInt(); + if ( numPortalAreas < 0 ) { + src->Error( "R_ParseInterAreaPortals: bad numPortalAreas" ); + return; + } + + if ( fileOut != NULL ) { + // write out the type so the binary reader knows what to instantiate + fileOut->WriteString( "interAreaPortals" ); + } + + + portalAreas = (portalArea_t *)R_ClearedStaticAlloc( numPortalAreas * sizeof( portalAreas[0] ) ); + areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( numPortalAreas * sizeof( idScreenRect ) ); + + // set the doubly linked lists + SetupAreaRefs(); + + numInterAreaPortals = src->ParseInt(); + if ( numInterAreaPortals < 0 ) { + src->Error( "R_ParseInterAreaPortals: bad numInterAreaPortals" ); + return; + } + + if ( fileOut != NULL ) { + fileOut->WriteBig( numPortalAreas ); + fileOut->WriteBig( numInterAreaPortals ); + } + + doublePortals = (doublePortal_t *)R_ClearedStaticAlloc( numInterAreaPortals * + sizeof( doublePortals [0] ) ); + + for ( int i = 0; i < numInterAreaPortals; i++ ) { + int numPoints, a1, a2; + idWinding *w; + portal_t *p; + + numPoints = src->ParseInt(); + a1 = src->ParseInt(); + a2 = src->ParseInt(); + + if ( fileOut != NULL ) { + fileOut->WriteBig( numPoints ); + fileOut->WriteBig( a1 ); + fileOut->WriteBig( a2 ); + } + + w = new (TAG_RENDER_WINDING) idWinding( numPoints ); + w->SetNumPoints( numPoints ); + for ( int j = 0; j < numPoints; j++ ) { + src->Parse1DMatrix( 3, (*w)[j].ToFloatPtr() ); + + if ( fileOut != NULL ) { + fileOut->WriteBig( (*w)[j].x ); + fileOut->WriteBig( (*w)[j].y ); + fileOut->WriteBig( (*w)[j].z ); + } + // no texture coordinates + (*w)[j][3] = 0; + (*w)[j][4] = 0; + } + + // add the portal to a1 + p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); + p->intoArea = a2; + p->doublePortal = &doublePortals[i]; + p->w = w; + p->w->GetPlane( p->plane ); + + p->next = portalAreas[a1].portals; + portalAreas[a1].portals = p; + + doublePortals[i].portals[0] = p; + + // reverse it for a2 + p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); + p->intoArea = a1; + p->doublePortal = &doublePortals[i]; + p->w = w->Reverse(); + p->w->GetPlane( p->plane ); + + p->next = portalAreas[a2].portals; + portalAreas[a2].portals = p; + + doublePortals[i].portals[1] = p; + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idRenderWorldLocal::ParseInterAreaPortals +================ +*/ +void idRenderWorldLocal::ReadBinaryAreaPortals( idFile *file ) { + + file->ReadBig( numPortalAreas ); + file->ReadBig( numInterAreaPortals ); + + portalAreas = (portalArea_t *)R_ClearedStaticAlloc( numPortalAreas * sizeof( portalAreas[0] ) ); + areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( numPortalAreas * sizeof( idScreenRect ) ); + + // set the doubly linked lists + SetupAreaRefs(); + + doublePortals = (doublePortal_t *)R_ClearedStaticAlloc( numInterAreaPortals * sizeof( doublePortals [0] ) ); + + for ( int i = 0; i < numInterAreaPortals; i++ ) { + int numPoints, a1, a2; + idWinding *w; + portal_t *p; + + file->ReadBig( numPoints ); + file->ReadBig( a1 ); + file->ReadBig( a2 ); + w = new (TAG_RENDER_WINDING) idWinding( numPoints ); + w->SetNumPoints( numPoints ); + for ( int j = 0; j < numPoints; j++ ) { + file->ReadBig( (*w)[ j ][ 0 ] ); + file->ReadBig( (*w)[ j ][ 1 ] ); + file->ReadBig( (*w)[ j ][ 2 ] ); + // no texture coordinates + (*w)[ j ][ 3 ] = 0; + (*w)[ j ][ 4 ] = 0; + } + + // add the portal to a1 + p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); + p->intoArea = a2; + p->doublePortal = &doublePortals[i]; + p->w = w; + p->w->GetPlane( p->plane ); + + p->next = portalAreas[a1].portals; + portalAreas[a1].portals = p; + + doublePortals[i].portals[0] = p; + + // reverse it for a2 + p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); + p->intoArea = a1; + p->doublePortal = &doublePortals[i]; + p->w = w->Reverse(); + p->w->GetPlane( p->plane ); + + p->next = portalAreas[a2].portals; + portalAreas[a2].portals = p; + + doublePortals[i].portals[1] = p; + } +} + + +/* +================ +idRenderWorldLocal::ParseNodes +================ +*/ +void idRenderWorldLocal::ParseNodes( idLexer *src, idFile *fileOut ) { + src->ExpectTokenString( "{" ); + + numAreaNodes = src->ParseInt(); + if ( numAreaNodes < 0 ) { + src->Error( "R_ParseNodes: bad numAreaNodes" ); + } + areaNodes = (areaNode_t *)R_ClearedStaticAlloc( numAreaNodes * sizeof( areaNodes[0] ) ); + + if ( fileOut != NULL ) { + // write out the type so the binary reader knows what to instantiate + fileOut->WriteString( "nodes" ); + } + + if ( fileOut != NULL ) { + fileOut->WriteBig( numAreaNodes ); + } + + for ( int i = 0; i < numAreaNodes; i++ ) { + areaNode_t *node; + + node = &areaNodes[i]; + + src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); + + node->children[0] = src->ParseInt(); + node->children[1] = src->ParseInt(); + + if ( fileOut != NULL ) { + fileOut->WriteBig( node->plane[ 0 ] ); + fileOut->WriteBig( node->plane[ 1 ] ); + fileOut->WriteBig( node->plane[ 2 ] ); + fileOut->WriteBig( node->plane[ 3 ] ); + fileOut->WriteBig( node->children[ 0 ] ); + fileOut->WriteBig( node->children[ 1 ] ); + } + + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idRenderWorldLocal::ReadBinaryNodes +================ +*/ +void idRenderWorldLocal::ReadBinaryNodes( idFile * file ) { + file->ReadBig( numAreaNodes ); + areaNodes = (areaNode_t *)R_ClearedStaticAlloc( numAreaNodes * sizeof( areaNodes[0] ) ); + for ( int i = 0; i < numAreaNodes; i++ ) { + areaNode_t * node = &areaNodes[ i ]; + file->ReadBig( node->plane[ 0 ] ); + file->ReadBig( node->plane[ 1 ] ); + file->ReadBig( node->plane[ 2 ] ); + file->ReadBig( node->plane[ 3 ] ); + file->ReadBig( node->children[ 0 ] ); + file->ReadBig( node->children[ 1 ] ); + } +} + +/* +================ +idRenderWorldLocal::CommonChildrenArea_r +================ +*/ +int idRenderWorldLocal::CommonChildrenArea_r( areaNode_t *node ) { + int nums[2]; + + for ( int i = 0; i < 2; i++ ) { + if ( node->children[i] <= 0 ) { + nums[i] = -1 - node->children[i]; + } else { + nums[i] = CommonChildrenArea_r( &areaNodes[ node->children[i] ] ); + } + } + + // solid nodes will match any area + if ( nums[0] == AREANUM_SOLID ) { + nums[0] = nums[1]; + } + if ( nums[1] == AREANUM_SOLID ) { + nums[1] = nums[0]; + } + + int common; + if ( nums[0] == nums[1] ) { + common = nums[0]; + } else { + common = CHILDREN_HAVE_MULTIPLE_AREAS; + } + + node->commonChildrenArea = common; + + return common; +} + +/* +================= +idRenderWorldLocal::ClearWorld + +Sets up for a single area world +================= +*/ +void idRenderWorldLocal::ClearWorld() { + numPortalAreas = 1; + portalAreas = (portalArea_t *)R_ClearedStaticAlloc( sizeof( portalAreas[0] ) ); + areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( sizeof( idScreenRect ) ); + + SetupAreaRefs(); + + // even though we only have a single area, create a node + // that has both children pointing at it so we don't need to + // + areaNodes = (areaNode_t *)R_ClearedStaticAlloc( sizeof( areaNodes[0] ) ); + areaNodes[0].plane[3] = 1; + areaNodes[0].children[0] = -1; + areaNodes[0].children[1] = -1; +} + +/* +================= +idRenderWorldLocal::FreeDefs + +dump all the interactions +================= +*/ +void idRenderWorldLocal::FreeDefs() { + generateAllInteractionsCalled = false; + + if ( interactionTable ) { + R_StaticFree( interactionTable ); + interactionTable = NULL; + } + + // free all lightDefs + for ( int i = 0; i < lightDefs.Num(); i++ ) { + idRenderLightLocal * light = lightDefs[i]; + if ( light != NULL && light->world == this ) { + FreeLightDef( i ); + lightDefs[i] = NULL; + } + } + + // free all entityDefs + for ( int i = 0; i < entityDefs.Num(); i++ ) { + idRenderEntityLocal * mod = entityDefs[i]; + if ( mod != NULL && mod->world == this ) { + FreeEntityDef( i ); + entityDefs[i] = NULL; + } + } + + // Reset decals and overlays + for ( int i = 0; i < decals.Num(); i++ ) { + decals[i].entityHandle = -1; + decals[i].lastStartTime = 0; + } + for ( int i = 0; i < overlays.Num(); i++ ) { + overlays[i].entityHandle = -1; + overlays[i].lastStartTime = 0; + } +} + +/* +================= +idRenderWorldLocal::InitFromMap + +A NULL or empty name will make a world without a map model, which +is still useful for displaying a bare model +================= +*/ +bool idRenderWorldLocal::InitFromMap( const char *name ) { + idLexer * src; + idToken token; + idRenderModel * lastModel; + + // if this is an empty world, initialize manually + if ( !name || !name[0] ) { + FreeWorld(); + mapName.Clear(); + ClearWorld(); + return true; + } + + // load it + idStrStatic< MAX_OSPATH > filename = name; + filename.SetFileExtension( PROC_FILE_EXT ); + + // check for generated file + idStrStatic< MAX_OSPATH > generatedFileName = filename; + generatedFileName.Insert( "generated/", 0 ); + generatedFileName.SetFileExtension( "bproc" ); + + // if we are reloading the same map, check the timestamp + // and try to skip all the work + ID_TIME_T currentTimeStamp = fileSystem->GetTimestamp( filename ); + + if ( name == mapName ) { + if ( fileSystem->InProductionMode() || ( currentTimeStamp != FILE_NOT_FOUND_TIMESTAMP && currentTimeStamp == mapTimeStamp ) ) { + common->Printf( "idRenderWorldLocal::InitFromMap: retaining existing map\n" ); + FreeDefs(); + TouchWorldModels(); + AddWorldModelEntities(); + ClearPortalStates(); + return true; + } + common->Printf( "idRenderWorldLocal::InitFromMap: timestamp has changed, reloading.\n" ); + } + + FreeWorld(); + + // see if we have a generated version of this + static const byte BPROC_VERSION = 1; + static const unsigned int BPROC_MAGIC = ( 'P' << 24 ) | ( 'R' << 16 ) | ( 'O' << 8 ) | BPROC_VERSION; + bool loaded = false; + idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) ); + if ( file != NULL ) { + int numEntries = 0; + int magic = 0; + file->ReadBig( magic ); + if ( magic == BPROC_MAGIC ) { + file->ReadBig( numEntries ); + file->ReadString( mapName ); + file->ReadBig( mapTimeStamp ); + loaded = true; + for ( int i = 0; i < numEntries; i++ ) { + idStrStatic< MAX_OSPATH > type; + file->ReadString( type ); + type.ToLower(); + if ( type == "model" ) { + idRenderModel * lastModel = ReadBinaryModel( file ); + if ( lastModel == NULL ) { + loaded = false; + break; + } + renderModelManager->AddModel( lastModel ); + localModels.Append( lastModel ); + } else if ( type == "shadowmodel" ) { + idRenderModel * lastModel = ReadBinaryModel( file ); + if ( lastModel == NULL ) { + loaded = false; + break; + } + renderModelManager->AddModel( lastModel ); + localModels.Append( lastModel ); + } else if ( type == "interareaportals" ) { + ReadBinaryAreaPortals( file ); + } else if ( type == "nodes" ) { + ReadBinaryNodes( file ); + } else { + idLib::Error( "Binary proc file failed, unexpected type %s\n", type.c_str() ); + } + } + } + } + + if ( !loaded ) { + + src = new (TAG_RENDER) idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if ( !src->IsLoaded() ) { + common->Printf( "idRenderWorldLocal::InitFromMap: %s not found\n", filename.c_str() ); + ClearWorld(); + return false; + } + + + mapName = name; + mapTimeStamp = currentTimeStamp; + + // if we are writing a demo, archive the load command + if ( common->WriteDemo() ) { + WriteLoadMap(); + } + + if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) { + common->Printf( "idRenderWorldLocal::InitFromMap: bad id '%s' instead of '%s'\n", token.c_str(), PROC_FILE_ID ); + delete src; + return false; + } + + int numEntries = 0; + idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) ); + if ( outputFile != NULL ) { + int magic = BPROC_MAGIC; + outputFile->WriteBig( magic ); + outputFile->WriteBig( numEntries ); + outputFile->WriteString( mapName ); + outputFile->WriteBig( mapTimeStamp ); + } + + // parse the file + while ( 1 ) { + if ( !src->ReadToken( &token ) ) { + break; + } + + common->UpdateLevelLoadPacifier(); + + + if ( token == "model" ) { + lastModel = ParseModel( src, name, currentTimeStamp, outputFile ); + + // add it to the model manager list + renderModelManager->AddModel( lastModel ); + + // save it in the list to free when clearing this map + localModels.Append( lastModel ); + + numEntries++; + + continue; + } + + if ( token == "shadowModel" ) { + lastModel = ParseShadowModel( src, outputFile ); + + // add it to the model manager list + renderModelManager->AddModel( lastModel ); + + // save it in the list to free when clearing this map + localModels.Append( lastModel ); + + numEntries++; + continue; + } + + if ( token == "interAreaPortals" ) { + ParseInterAreaPortals( src, outputFile ); + + numEntries++; + continue; + } + + if ( token == "nodes" ) { + ParseNodes( src, outputFile ); + + numEntries++; + continue; + } + + src->Error( "idRenderWorldLocal::InitFromMap: bad token \"%s\"", token.c_str() ); + } + + delete src; + + if ( outputFile != NULL ) { + outputFile->Seek( 0, FS_SEEK_SET ); + int magic = BPROC_MAGIC; + outputFile->WriteBig( magic ); + outputFile->WriteBig( numEntries ); + } + + } + + + + // if it was a trivial map without any areas, create a single area + if ( !numPortalAreas ) { + ClearWorld(); + } + + // find the points where we can early-our of reference pushing into the BSP tree + CommonChildrenArea_r( &areaNodes[0] ); + + AddWorldModelEntities(); + ClearPortalStates(); + + // done! + return true; +} + +/* +===================== +idRenderWorldLocal::ClearPortalStates +===================== +*/ +void idRenderWorldLocal::ClearPortalStates() { + // all portals start off open + for ( int i = 0; i < numInterAreaPortals; i++ ) { + doublePortals[i].blockingBits = PS_BLOCK_NONE; + } + + // flood fill all area connections + for ( int i = 0; i < numPortalAreas; i++ ) { + for ( int j = 0; j < NUM_PORTAL_ATTRIBUTES; j++ ) { + connectedAreaNum++; + FloodConnectedAreas( &portalAreas[i], j ); + } + } +} + +/* +===================== +idRenderWorldLocal::AddWorldModelEntities +===================== +*/ +void idRenderWorldLocal::AddWorldModelEntities() { + // add the world model for each portal area + // we can't just call AddEntityDef, because that would place the references + // based on the bounding box, rather than explicitly into the correct area + for ( int i = 0; i < numPortalAreas; i++ ) { + common->UpdateLevelLoadPacifier(); + + + idRenderEntityLocal * def = new (TAG_RENDER_ENTITY) idRenderEntityLocal; + + // try and reuse a free spot + int index = entityDefs.FindNull(); + if ( index == -1 ) { + index = entityDefs.Append(def); + } else { + entityDefs[index] = def; + } + + def->index = index; + def->world = this; + + def->parms.hModel = renderModelManager->FindModel( va("_area%i", i ) ); + if ( def->parms.hModel->IsDefaultModel() || !def->parms.hModel->IsStaticWorldModel() ) { + common->Error( "idRenderWorldLocal::InitFromMap: bad area model lookup" ); + } + + idRenderModel *hModel = def->parms.hModel; + + for ( int j = 0; j < hModel->NumSurfaces(); j++ ) { + const modelSurface_t *surf = hModel->Surface( j ); + + if ( surf->shader->GetName() == idStr( "textures/smf/portal_sky" ) ) { + def->needsPortalSky = true; + } + } + + // the local and global reference bounds are the same for area models + def->localReferenceBounds = def->parms.hModel->Bounds(); + def->globalReferenceBounds = def->parms.hModel->Bounds(); + + def->parms.axis[0][0] = 1.0f; + def->parms.axis[1][1] = 1.0f; + def->parms.axis[2][2] = 1.0f; + + // in case an explicit shader is used on the world, we don't + // want it to have a 0 alpha or color + def->parms.shaderParms[0] = 1.0f; + def->parms.shaderParms[1] = 1.0f; + def->parms.shaderParms[2] = 1.0f; + def->parms.shaderParms[3] = 1.0f; + + R_DeriveEntityData( def ); + + AddEntityRefToArea( def, &portalAreas[i] ); + } +} + +/* +===================== +CheckAreaForPortalSky +===================== +*/ +bool idRenderWorldLocal::CheckAreaForPortalSky( int areaNum ) { + assert( areaNum >= 0 && areaNum < numPortalAreas ); + + for ( areaReference_t * ref = portalAreas[areaNum].entityRefs.areaNext; ref->entity; ref = ref->areaNext ) { + assert( ref->area == &portalAreas[areaNum] ); + + if ( ref->entity && ref->entity->needsPortalSky ) { + return true; + } + } + + return false; +} + +/* +===================== +ResetLocalRenderModels +===================== +*/ +void idRenderWorldLocal::ResetLocalRenderModels() { + localModels.Clear(); // Clear out the list when switching between expansion packs, so InitFromMap doesn't try to delete the list whose content has already been deleted by the model manager being re-started +} \ No newline at end of file diff --git a/neo/renderer/RenderWorld_local.h b/neo/renderer/RenderWorld_local.h new file mode 100644 index 00000000..16cbf758 --- /dev/null +++ b/neo/renderer/RenderWorld_local.h @@ -0,0 +1,296 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __RENDERWORLDLOCAL_H__ +#define __RENDERWORLDLOCAL_H__ + +#include "BoundsTrack.h" + +// assume any lightDef or entityDef index above this is an internal error +const int LUDICROUS_INDEX = 10000; + + +typedef struct portal_s { + int intoArea; // area this portal leads to + idWinding * w; // winding points have counter clockwise ordering seen this area + idPlane plane; // view must be on the positive side of the plane to cross + struct portal_s * next; // next portal of the area + struct doublePortal_s * doublePortal; +} portal_t; + + +typedef struct doublePortal_s { + struct portal_s * portals[2]; + int blockingBits; // PS_BLOCK_VIEW, PS_BLOCK_AIR, etc, set by doors that shut them off + + // A portal will be considered closed if it is past the + // fog-out point in a fog volume. We only support a single + // fog volume over each portal. + idRenderLightLocal * fogLight; + struct doublePortal_s * nextFoggedPortal; +} doublePortal_t; + + +typedef struct portalArea_s { + int areaNum; + int connectedAreaNum[NUM_PORTAL_ATTRIBUTES]; // if two areas have matching connectedAreaNum, they are + // not separated by a portal with the apropriate PS_BLOCK_* blockingBits + int viewCount; // set by R_FindViewLightsAndEntities + portal_t * portals; // never changes after load + areaReference_t entityRefs; // head/tail of doubly linked list, may change + areaReference_t lightRefs; // head/tail of doubly linked list, may change +} portalArea_t; + + +static const int CHILDREN_HAVE_MULTIPLE_AREAS = -2; +static const int AREANUM_SOLID = -1; +typedef struct { + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid + int commonChildrenArea; // if all children are either solid or a single area, + // this is the area number, else CHILDREN_HAVE_MULTIPLE_AREAS +} areaNode_t; + +struct reusableDecal_t { + qhandle_t entityHandle; + int lastStartTime; + idRenderModelDecal * decals; +}; + +struct reusableOverlay_t { + qhandle_t entityHandle; + int lastStartTime; + idRenderModelOverlay * overlays; +}; + +struct portalStack_t; + +class idRenderWorldLocal : public idRenderWorld { +public: + idRenderWorldLocal(); + virtual ~idRenderWorldLocal(); + + virtual bool InitFromMap( const char *mapName ); + virtual void ResetLocalRenderModels(); // Fixes a crash when switching between expansion packs in Doom3:BFG + + virtual qhandle_t AddEntityDef( const renderEntity_t *re ); + virtual void UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ); + virtual void FreeEntityDef( qhandle_t entityHandle ); + virtual const renderEntity_t *GetRenderEntity( qhandle_t entityHandle ) const; + + virtual qhandle_t AddLightDef( const renderLight_t *rlight ); + virtual void UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ); + virtual void FreeLightDef( qhandle_t lightHandle ); + virtual const renderLight_t *GetRenderLight( qhandle_t lightHandle ) const; + + virtual bool CheckAreaForPortalSky( int areaNum ); + + virtual void GenerateAllInteractions(); + virtual void RegenerateWorld(); + + virtual void ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ); + virtual void ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ); + virtual void ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material, const int startTime ); + virtual void RemoveDecals( qhandle_t entityHandle ); + + virtual void SetRenderView( const renderView_t *renderView ); + virtual void RenderScene( const renderView_t *renderView ); + + virtual int NumAreas() const; + virtual int PointInArea( const idVec3 &point ) const; + virtual int BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const; + virtual int NumPortalsInArea( int areaNum ); + virtual exitPortal_t GetPortal( int areaNum, int portalNum ); + + virtual guiPoint_t GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const; + virtual bool ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const; + virtual bool Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic = true, bool skipPlayer = false ) const; + virtual bool FastWorldTrace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + + virtual void DebugClearLines( int time ); + virtual void DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime = 0, const bool depthTest = false ); + virtual void DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime = 0 ); + virtual void DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime = 0, const bool depthTest = false ); + virtual void DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime = 0, const bool depthTest = false ); + virtual void DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime = 0, bool depthTest = false ); + virtual void DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org = vec3_origin, const int lifetime = 0 ); + virtual void DebugBox( const idVec4 &color, const idBox &box, const int lifetime = 0 ); + virtual void DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime = 0 ); + virtual void DebugScreenRect( const idVec4 &color, const idScreenRect &rect, const viewDef_t *viewDef, const int lifetime = 0 ); + virtual void DebugAxis( const idVec3 &origin, const idMat3 &axis ); + + virtual void DebugClearPolygons( int time ); + virtual void DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime = 0, const bool depthTest = false ); + + virtual void DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align = 1, const int lifetime = 0, bool depthTest = false ); + + //----------------------- + + idStr mapName; // ie: maps/tim_dm2.proc, written to demoFile + ID_TIME_T mapTimeStamp; // for fast reloads of the same level + + areaNode_t * areaNodes; + int numAreaNodes; + + portalArea_t * portalAreas; + int numPortalAreas; + int connectedAreaNum; // incremented every time a door portal state changes + + idScreenRect * areaScreenRect; + + doublePortal_t * doublePortals; + int numInterAreaPortals; + + idList localModels; + + idList entityDefs; + idList lightDefs; + + idBlockAlloc areaReferenceAllocator; + idBlockAlloc interactionAllocator; + +#ifdef ID_PC + static const int MAX_DECAL_SURFACES = 32; +#else + static const int MAX_DECAL_SURFACES = 16; +#endif + idArray decals; + idArray overlays; + + // all light / entity interactions are referenced here for fast lookup without + // having to crawl the doubly linked lists. EnntityDefs are sequential for better + // cache access, because the table is accessed by light in idRenderWorldLocal::CreateLightDefInteractions() + // Growing this table is time consuming, so we add a pad value to the number + // of entityDefs and lightDefs + idInteraction ** interactionTable; + int interactionTableWidth; // entityDefs + int interactionTableHeight; // lightDefs + + bool generateAllInteractionsCalled; + + //----------------------- + // RenderWorld_load.cpp + + idRenderModel * ParseModel( idLexer *src, const char *mapName, ID_TIME_T mapTimeStamp, idFile *fileOut ); + idRenderModel * ParseShadowModel( idLexer *src, idFile *fileOut ); + void SetupAreaRefs(); + void ParseInterAreaPortals( idLexer *src, idFile *fileOut ); + void ParseNodes( idLexer *src, idFile *fileOut ); + int CommonChildrenArea_r( areaNode_t *node ); + void FreeWorld(); + void ClearWorld(); + void FreeDefs(); + void TouchWorldModels(); + void AddWorldModelEntities(); + void ClearPortalStates(); + void ReadBinaryAreaPortals( idFile *file ); + void ReadBinaryNodes( idFile *file ); + idRenderModel * ReadBinaryModel( idFile *file ); + idRenderModel * ReadBinaryShadowModel( idFile *file ); + + //-------------------------- + // RenderWorld_portals.cpp + + bool CullEntityByPortals( const idRenderEntityLocal *entity, const portalStack_t *ps ); + void AddAreaViewEntities( int areaNum, const portalStack_t *ps ); + bool CullLightByPortals( const idRenderLightLocal *light, const portalStack_t *ps ); + void AddAreaViewLights( int areaNum, const portalStack_t *ps ); + void AddAreaToView( int areaNum, const portalStack_t *ps ); + idScreenRect ScreenRectFromWinding( const idWinding *w, const viewEntity_t *space ); + bool PortalIsFoggedOut( const portal_t *p ); + void FloodViewThroughArea_r( const idVec3 & origin, int areaNum, const portalStack_t *ps ); + void FlowViewThroughPortals( const idVec3 & origin, int numPlanes, const idPlane *planes ); + void BuildConnectedAreas_r( int areaNum ); + void BuildConnectedAreas(); + void FindViewLightsAndEntities(); + + void FloodLightThroughArea_r( idRenderLightLocal *light, int areaNum, const portalStack_t *ps ); + void FlowLightThroughPortals( idRenderLightLocal *light ); + + int NumPortals() const; + qhandle_t FindPortal( const idBounds &b ) const; + void SetPortalState( qhandle_t portal, int blockingBits ); + int GetPortalState( qhandle_t portal ); + bool AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ) const; + void FloodConnectedAreas( portalArea_t *area, int portalAttributeIndex ); + idScreenRect & GetAreaScreenRect( int areaNum ) const { return areaScreenRect[areaNum]; } + void ShowPortals(); + + //-------------------------- + // RenderWorld_demo.cpp + + void StartWritingDemo( idDemoFile *demo ); + void StopWritingDemo(); + bool ProcessDemoCommand( idDemoFile *readDemo, renderView_t *demoRenderView, int *demoTimeOffset ); + + void WriteLoadMap(); + void WriteRenderView( const renderView_t *renderView ); + void WriteVisibleDefs( const viewDef_t *viewDef ); + void WriteFreeLight( qhandle_t handle ); + void WriteFreeEntity( qhandle_t handle ); + void WriteRenderLight( qhandle_t handle, const renderLight_t *light ); + void WriteRenderEntity( qhandle_t handle, const renderEntity_t *ent ); + void ReadRenderEntity(); + void ReadRenderLight(); + + + //-------------------------- + // RenderWorld.cpp + + void ResizeInteractionTable(); + + void AddEntityRefToArea( idRenderEntityLocal *def, portalArea_t *area ); + void AddLightRefToArea( idRenderLightLocal *light, portalArea_t *area ); + + void RecurseProcBSP_r( modelTrace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ) const; + void BoundsInAreas_r( int nodeNum, const idBounds &bounds, int *areas, int *numAreas, int maxAreas ) const; + + float DrawTextLength( const char *text, float scale, int len = 0 ); + + void FreeInteractions(); + + void PushFrustumIntoTree_r( idRenderEntityLocal *def, idRenderLightLocal *light, const frustumCorners_t & corners, int nodeNum ); + void PushFrustumIntoTree( idRenderEntityLocal *def, idRenderLightLocal *light, const idRenderMatrix & frustumTransform, const idBounds & frustumBounds ); + + idRenderModelDecal * AllocDecal( qhandle_t newEntityHandle, int startTime ); + idRenderModelOverlay * AllocOverlay( qhandle_t newEntityHandle, int startTime ); + + //------------------------------- + // tr_light.c + void CreateLightDefInteractions( idRenderLightLocal * const ldef, const int renderViewID ); +}; + +// if an entity / light combination has been evaluated and found to not genrate any surfaces or shadows, +// the constant INTERACTION_EMPTY will be stored in the interaction table, int contrasts to NULL, which +// means that the combination has not yet been tested for having surfaces. +static idInteraction * const INTERACTION_EMPTY = (idInteraction *)1; + +void R_ListRenderLightDefs_f( const idCmdArgs &args ); +void R_ListRenderEntityDefs_f( const idCmdArgs &args ); + +#endif /* !__RENDERWORLDLOCAL_H__ */ diff --git a/neo/renderer/RenderWorld_portals.cpp b/neo/renderer/RenderWorld_portals.cpp new file mode 100644 index 00000000..3bd5c08e --- /dev/null +++ b/neo/renderer/RenderWorld_portals.cpp @@ -0,0 +1,1055 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +// if we hit this many planes, we will just stop cropping the +// view down, which is still correct, just conservative +const int MAX_PORTAL_PLANES = 20; + +struct portalStack_t { + const portal_t * p; + const portalStack_t * next; + // positive side is outside the visible frustum + int numPortalPlanes; + idPlane portalPlanes[MAX_PORTAL_PLANES+1]; + idScreenRect rect; +}; + +/* +======================================================================= + +Create viewLights and viewEntitys for the lights and entities that are +visible in the portal areas that can be seen from the current viewpoint. + +======================================================================= +*/ + +/* +============= +R_SetLightDefViewLight + +If the lightDef is not already on the viewLight list, create +a viewLight and add it to the list with an empty scissor rect. +============= +*/ +viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *light ) { + if ( light->viewCount == tr.viewCount ) { + // already set up for this frame + return light->viewLight; + } + light->viewCount = tr.viewCount; + + // add to the view light chain + viewLight_t * vLight = (viewLight_t *)R_ClearedFrameAlloc( sizeof( *vLight ), FRAME_ALLOC_VIEW_LIGHT ); + vLight->lightDef = light; + + // the scissorRect will be expanded as the light bounds is accepted into visible portal chains + // and the scissor will be reduced in R_AddSingleLight based on the screen space projection + vLight->scissorRect.Clear(); + + // link the view light + vLight->next = tr.viewDef->viewLights; + tr.viewDef->viewLights = vLight; + + light->viewLight = vLight; + + return vLight; +} + +/* +============= +R_SetEntityDefViewEntity + +If the entityDef is not already on the viewEntity list, create +a viewEntity and add it to the list with an empty scissor rect. +============= +*/ +viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ) { + if ( def->viewCount == tr.viewCount ) { + // already set up for this frame + return def->viewEntity; + } + def->viewCount = tr.viewCount; + + viewEntity_t * vModel = (viewEntity_t *)R_ClearedFrameAlloc( sizeof( *vModel ), FRAME_ALLOC_VIEW_ENTITY ); + vModel->entityDef = def; + + // the scissorRect will be expanded as the model bounds is accepted into visible portal chains + // It will remain clear if the model is only needed for shadows. + vModel->scissorRect.Clear(); + + vModel->next = tr.viewDef->viewEntitys; + tr.viewDef->viewEntitys = vModel; + + def->viewEntity = vModel; + + return vModel; +} + +/* +================ +CullEntityByPortals + +Return true if the entity reference bounds do not intersect the current portal chain. +================ +*/ +bool idRenderWorldLocal::CullEntityByPortals( const idRenderEntityLocal *entity, const portalStack_t *ps ) { + if ( r_useEntityPortalCulling.GetInteger() == 1 ) { + + ALIGNTYPE16 frustumCorners_t corners; + idRenderMatrix::GetFrustumCorners( corners, entity->inverseBaseModelProject, bounds_unitCube ); + for ( int i = 0; i < ps->numPortalPlanes; i++ ) { + if ( idRenderMatrix::CullFrustumCornersToPlane( corners, ps->portalPlanes[i] ) == FRUSTUM_CULL_FRONT ) { + return true; + } + } + + } else if ( r_useEntityPortalCulling.GetInteger() >= 2 ) { + + idRenderMatrix baseModelProject; + idRenderMatrix::Inverse( entity->inverseBaseModelProject, baseModelProject ); + + idPlane frustumPlanes[6]; + idRenderMatrix::GetFrustumPlanes( frustumPlanes, baseModelProject, false, true ); + + // exact clip of light faces against all planes + for ( int i = 0; i < 6; i++ ) { + // the entity frustum planes face inward, so the planes that have the + // view origin on the positive side will be the "back" faces of the entity, + // which must have some fragment inside the portal stack planes to be visible + if ( frustumPlanes[i].Distance( tr.viewDef->renderView.vieworg ) <= 0.0f ) { + continue; + } + + // calculate a winding for this frustum side + idFixedWinding w; + w.BaseForPlane( frustumPlanes[i] ); + for ( int j = 0; j < 6; j++ ) { + if ( j == i ) { + continue; + } + if ( !w.ClipInPlace( frustumPlanes[j], ON_EPSILON ) ) { + break; + } + } + if ( w.GetNumPoints() <= 2 ) { + continue; + } + + assert( ps->numPortalPlanes <= MAX_PORTAL_PLANES ); + assert( w.GetNumPoints() + ps->numPortalPlanes < MAX_POINTS_ON_WINDING ); + + // now clip the winding against each of the portalStack planes + // skip the last plane which is the last portal itself + for ( int j = 0; j < ps->numPortalPlanes - 1; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j], ON_EPSILON ) ) { + break; + } + } + + if ( w.GetNumPoints() > 2 ) { + // part of the winding is visible through the portalStack, + // so the entity is not culled + return false; + } + } + + // nothing was visible + return true; + + } + + return false; +} + +/* +=================== +AddAreaViewEntities + +Any models that are visible through the current portalStack will have their scissor rect updated. +=================== +*/ +void idRenderWorldLocal::AddAreaViewEntities( int areaNum, const portalStack_t *ps ) { + portalArea_t * area = &portalAreas[ areaNum ]; + + for ( areaReference_t * ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) { + idRenderEntityLocal * entity = ref->entity; + + // debug tool to allow viewing of only one entity at a time + if ( r_singleEntity.GetInteger() >= 0 && r_singleEntity.GetInteger() != entity->index ) { + continue; + } + + // remove decals that are completely faded away + R_FreeEntityDefFadedDecals( entity, tr.viewDef->renderView.time[0] ); + + // check for completely suppressing the model + if ( !r_skipSuppress.GetBool() ) { + if ( entity->parms.suppressSurfaceInViewID + && entity->parms.suppressSurfaceInViewID == tr.viewDef->renderView.viewID ) { + continue; + } + if ( entity->parms.allowSurfaceInViewID + && entity->parms.allowSurfaceInViewID != tr.viewDef->renderView.viewID ) { + continue; + } + } + + // cull reference bounds + if ( CullEntityByPortals( entity, ps ) ) { + // we are culled out through this portal chain, but it might + // still be visible through others + continue; + } + + viewEntity_t * vEnt = R_SetEntityDefViewEntity( entity ); + + // possibly expand the scissor rect + vEnt->scissorRect.Union( ps->rect ); + } +} + +/* +================ +CullLightByPortals + +Return true if the light frustum does not intersect the current portal chain. +================ +*/ +bool idRenderWorldLocal::CullLightByPortals( const idRenderLightLocal *light, const portalStack_t *ps ) { + if ( r_useLightPortalCulling.GetInteger() == 1 ) { + + ALIGNTYPE16 frustumCorners_t corners; + idRenderMatrix::GetFrustumCorners( corners, light->inverseBaseLightProject, bounds_zeroOneCube ); + for ( int i = 0; i < ps->numPortalPlanes; i++ ) { + if ( idRenderMatrix::CullFrustumCornersToPlane( corners, ps->portalPlanes[i] ) == FRUSTUM_CULL_FRONT ) { + return true; + } + } + + } else if ( r_useLightPortalCulling.GetInteger() >= 2 ) { + + idPlane frustumPlanes[6]; + idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); + + // exact clip of light faces against all planes + for ( int i = 0; i < 6; i++ ) { + // the light frustum planes face inward, so the planes that have the + // view origin on the positive side will be the "back" faces of the light, + // which must have some fragment inside the the portal stack planes to be visible + if ( frustumPlanes[i].Distance( tr.viewDef->renderView.vieworg ) <= 0.0f ) { + continue; + } + + // calculate a winding for this frustum side + idFixedWinding w; + w.BaseForPlane( frustumPlanes[i] ); + for ( int j = 0; j < 6; j++ ) { + if ( j == i ) { + continue; + } + if ( !w.ClipInPlace( frustumPlanes[j], ON_EPSILON ) ) { + break; + } + } + if ( w.GetNumPoints() <= 2 ) { + continue; + } + + assert( ps->numPortalPlanes <= MAX_PORTAL_PLANES ); + assert( w.GetNumPoints() + ps->numPortalPlanes < MAX_POINTS_ON_WINDING ); + + // now clip the winding against each of the portalStack planes + // skip the last plane which is the last portal itself + for ( int j = 0; j < ps->numPortalPlanes - 1; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j], ON_EPSILON ) ) { + break; + } + } + + if ( w.GetNumPoints() > 2 ) { + // part of the winding is visible through the portalStack, + // so the light is not culled + return false; + } + } + + // nothing was visible + return true; + } + + return false; +} + +/* +=================== +AddAreaViewLights + +This is the only point where lights get added to the viewLights list. +Any lights that are visible through the current portalStack will have their scissor rect updated. +=================== +*/ +void idRenderWorldLocal::AddAreaViewLights( int areaNum, const portalStack_t *ps ) { + portalArea_t * area = &portalAreas[ areaNum ]; + + for ( areaReference_t * lref = area->lightRefs.areaNext; lref != &area->lightRefs; lref = lref->areaNext ) { + idRenderLightLocal * light = lref->light; + + // debug tool to allow viewing of only one light at a time + if ( r_singleLight.GetInteger() >= 0 && r_singleLight.GetInteger() != light->index ) { + continue; + } + + // check for being closed off behind a door + // a light that doesn't cast shadows will still light even if it is behind a door + if ( r_useLightAreaCulling.GetBool() && !light->LightCastsShadows() + && light->areaNum != -1 && !tr.viewDef->connectedAreas[ light->areaNum ] ) { + continue; + } + + // cull frustum + if ( CullLightByPortals( light, ps ) ) { + // we are culled out through this portal chain, but it might + // still be visible through others + continue; + } + + viewLight_t * vLight = R_SetLightDefViewLight( light ); + + // expand the scissor rect + vLight->scissorRect.Union( ps->rect ); + } +} + +/* +=================== +AddAreaToView + +This may be entered multiple times with different planes +if more than one portal sees into the area +=================== +*/ +void idRenderWorldLocal::AddAreaToView( int areaNum, const portalStack_t *ps ) { + // mark the viewCount, so r_showPortals can display the considered portals + portalAreas[ areaNum ].viewCount = tr.viewCount; + + // add the models and lights, using more precise culling to the planes + AddAreaViewEntities( areaNum, ps ); + AddAreaViewLights( areaNum, ps ); +} + +/* +=================== +idRenderWorldLocal::ScreenRectForWinding +=================== +*/ +idScreenRect idRenderWorldLocal::ScreenRectFromWinding( const idWinding * w, const viewEntity_t * space ) { + const float viewWidth = (float) tr.viewDef->viewport.x2 - (float) tr.viewDef->viewport.x1; + const float viewHeight = (float) tr.viewDef->viewport.y2 - (float) tr.viewDef->viewport.y1; + + idScreenRect r; + r.Clear(); + for ( int i = 0; i < w->GetNumPoints(); i++ ) { + idVec3 v; + idVec3 ndc; + R_LocalPointToGlobal( space->modelMatrix, (*w)[i].ToVec3(), v ); + R_GlobalToNormalizedDeviceCoordinates( v, ndc ); + + float windowX = ( ndc[0] * 0.5f + 0.5f ) * viewWidth; + float windowY = ( ndc[1] * 0.5f + 0.5f ) * viewHeight; + + r.AddPoint( windowX, windowY ); + } + + r.Expand(); + + return r; +} + +/* +=================== +idRenderWorldLocal::PortalIsFoggedOut +=================== +*/ +bool idRenderWorldLocal::PortalIsFoggedOut( const portal_t *p ) { + idRenderLightLocal * ldef = p->doublePortal->fogLight; + if ( ldef == NULL ) { + return false; + } + + // find the current density of the fog + const idMaterial * lightShader = ldef->lightShader; + const int size = lightShader->GetNumRegisters() * sizeof( float ); + float * regs = (float *)_alloca( size ); + + lightShader->EvaluateRegisters( regs, ldef->parms.shaderParms, + tr.viewDef->renderView.shaderParms, tr.viewDef->renderView.time[0] * 0.001f, ldef->parms.referenceSound ); + + const shaderStage_t *stage = lightShader->GetStage(0); + + const float alpha = regs[ stage->color.registers[3] ]; + + // if they left the default value on, set a fog distance of 500 + float a; + if ( alpha <= 1.0f ) { + a = -0.5f / DEFAULT_FOG_DISTANCE; + } else { + // otherwise, distance = alpha color + a = -0.5f / alpha; + } + + idPlane forward; + forward[0] = a * tr.viewDef->worldSpace.modelViewMatrix[0*4+2]; + forward[1] = a * tr.viewDef->worldSpace.modelViewMatrix[1*4+2]; + forward[2] = a * tr.viewDef->worldSpace.modelViewMatrix[2*4+2]; + forward[3] = a * tr.viewDef->worldSpace.modelViewMatrix[3*4+2]; + + const idWinding * w = p->w; + for ( int i = 0; i < w->GetNumPoints(); i++ ) { + const float d = forward.Distance( (*w)[i].ToVec3() ); + if ( d < 0.5f ) { + return false; // a point not clipped off + } + } + + return true; +} + +/* +=================== +idRenderWorldLocal::FloodViewThroughArea_r +=================== +*/ +void idRenderWorldLocal::FloodViewThroughArea_r( const idVec3 & origin, int areaNum, const portalStack_t *ps ) { + portalArea_t * area = &portalAreas[ areaNum ]; + + // cull models and lights to the current collection of planes + AddAreaToView( areaNum, ps ); + + if ( areaScreenRect[areaNum].IsEmpty() ) { + areaScreenRect[areaNum] = ps->rect; + } else { + areaScreenRect[areaNum].Union( ps->rect ); + } + + // go through all the portals + for ( const portal_t * p = area->portals; p != NULL; p = p->next ) { + // an enclosing door may have sealed the portal off + if ( p->doublePortal->blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // make sure this portal is facing away from the view + const float d = p->plane.Distance( origin ); + if ( d < -0.1f ) { + continue; + } + + // make sure the portal isn't in our stack trace, + // which would cause an infinite loop + const portalStack_t * check = ps; + for ( ; check != NULL; check = check->next ) { + if ( check->p == p ) { + break; // don't recursively enter a stack + } + } + if ( check ) { + continue; // already in stack + } + + // if we are very close to the portal surface, don't bother clipping + // it, which tends to give epsilon problems that make the area vanish + if ( d < 1.0f ) { + + // go through this portal + portalStack_t newStack; + newStack = *ps; + newStack.p = p; + newStack.next = ps; + FloodViewThroughArea_r( origin, p->intoArea, &newStack ); + continue; + } + + // clip the portal winding to all of the planes + idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 + w = *p->w; + for ( int j = 0; j < ps->numPortalPlanes; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j], 0 ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; // portal not visible + } + + // see if it is fogged out + if ( PortalIsFoggedOut( p ) ) { + continue; + } + + // go through this portal + portalStack_t newStack; + newStack.p = p; + newStack.next = ps; + + // find the screen pixel bounding box of the remaining portal + // so we can scissor things outside it + newStack.rect = ScreenRectFromWinding( &w, &tr.identitySpace ); + + // slop might have spread it a pixel outside, so trim it back + newStack.rect.Intersect( ps->rect ); + + // generate a set of clipping planes that will further restrict + // the visible view beyond just the scissor rect + + int addPlanes = w.GetNumPoints(); + if ( addPlanes > MAX_PORTAL_PLANES ) { + addPlanes = MAX_PORTAL_PLANES; + } + + newStack.numPortalPlanes = 0; + for ( int i = 0; i < addPlanes; i++ ) { + int j = i + 1; + if ( j == w.GetNumPoints() ) { + j = 0; + } + + const idVec3 & v1 = origin - w[i].ToVec3(); + const idVec3 & v2 = origin - w[j].ToVec3(); + + newStack.portalPlanes[newStack.numPortalPlanes].Normal().Cross( v2, v1 ); + + // if it is degenerate, skip the plane + if ( newStack.portalPlanes[newStack.numPortalPlanes].Normalize() < 0.01f ) { + continue; + } + newStack.portalPlanes[newStack.numPortalPlanes].FitThroughPoint( origin ); + + newStack.numPortalPlanes++; + } + + // the last stack plane is the portal plane + newStack.portalPlanes[newStack.numPortalPlanes] = p->plane; + newStack.numPortalPlanes++; + + FloodViewThroughArea_r( origin, p->intoArea, &newStack ); + } +} + +/* +======================= +idRenderWorldLocal::FlowViewThroughPortals + +Finds viewLights and viewEntities by flowing from an origin through the visible +portals that the origin point can see into. The planes array defines a volume with +the planes pointing outside the volume. Zero planes assumes an unbounded volume. +======================= +*/ +void idRenderWorldLocal::FlowViewThroughPortals( const idVec3 & origin, int numPlanes, const idPlane *planes ) { + portalStack_t ps; + ps.next = NULL; + ps.p = NULL; + + assert( numPlanes <= MAX_PORTAL_PLANES ); + for ( int i = 0; i < numPlanes; i++ ) { + ps.portalPlanes[i] = planes[i]; + } + + ps.numPortalPlanes = numPlanes; + ps.rect = tr.viewDef->scissor; + + // if outside the world, mark everything + if ( tr.viewDef->areaNum < 0 ){ + for ( int i = 0; i < numPortalAreas; i++ ) { + areaScreenRect[i] = tr.viewDef->scissor; + AddAreaToView( i, &ps ); + } + } else { + // flood out through portals, setting area viewCount + FloodViewThroughArea_r( origin, tr.viewDef->areaNum, &ps ); + } +} + +/* +=================== +idRenderWorldLocal::BuildConnectedAreas_r +=================== +*/ +void idRenderWorldLocal::BuildConnectedAreas_r( int areaNum ) { + if ( tr.viewDef->connectedAreas[areaNum] ) { + return; + } + + tr.viewDef->connectedAreas[areaNum] = true; + + // flood through all non-blocked portals + portalArea_t * area = &portalAreas[ areaNum ]; + for ( const portal_t * portal = area->portals; portal; portal = portal->next ) { + if ( ( portal->doublePortal->blockingBits & PS_BLOCK_VIEW ) == 0 ) { + BuildConnectedAreas_r( portal->intoArea ); + } + } +} + +/* +=================== +idRenderWorldLocal::BuildConnectedAreas + +This is only valid for a given view, not all views in a frame +=================== +*/ +void idRenderWorldLocal::BuildConnectedAreas() { + tr.viewDef->connectedAreas = (bool *)R_FrameAlloc( numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); + + // if we are outside the world, we can see all areas + if ( tr.viewDef->areaNum == -1 ) { + for ( int i = 0; i < numPortalAreas; i++ ) { + tr.viewDef->connectedAreas[i] = true; + } + return; + } + + // start with none visible, and flood fill from the current area + memset( tr.viewDef->connectedAreas, 0, numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); + BuildConnectedAreas_r( tr.viewDef->areaNum ); +} + +/* +============= +idRenderWorldLocal::FindViewLightsAndEntites + +All the modelrefs and lightrefs that are in visible areas +will have viewEntitys and viewLights created for them. + +The scissorRects on the viewEntitys and viewLights may be empty if +they were considered, but not actually visible. + +Entities and lights can have cached viewEntities / viewLights that +will be used if the viewCount variable matches. +============= +*/ +void idRenderWorldLocal::FindViewLightsAndEntities() { + SCOPED_PROFILE_EVENT( "FindViewLightsAndEntities" ); + + // bumping this counter invalidates cached viewLights / viewEntities, + // when a light or entity is next considered, it will create a new + // viewLight / viewEntity + tr.viewCount++; + + // clear the visible lightDef and entityDef lists + tr.viewDef->viewLights = NULL; + tr.viewDef->viewEntitys = NULL; + + // all areas are initially not visible, but each portal + // chain that leads to them will expand the visible rectangle + for ( int i = 0; i < numPortalAreas; i++ ) { + areaScreenRect[i].Clear(); + } + + // find the area to start the portal flooding in + if ( !r_usePortals.GetBool() ) { + // debug tool to force no portal culling + tr.viewDef->areaNum = -1; + } else { + tr.viewDef->areaNum = PointInArea( tr.viewDef->initialViewAreaOrigin ); + } + + // determine all possible connected areas for + // light-behind-door culling + BuildConnectedAreas(); + + // flow through all the portals and add models / lights + if ( r_singleArea.GetBool() ) { + // if debugging, only mark this area + // if we are outside the world, don't draw anything + if ( tr.viewDef->areaNum >= 0 ) { + static int lastPrintedAreaNum; + if ( tr.viewDef->areaNum != lastPrintedAreaNum ) { + lastPrintedAreaNum = tr.viewDef->areaNum; + common->Printf( "entering portal area %i\n", tr.viewDef->areaNum ); + } + + portalStack_t ps; + for ( int i = 0; i < 5; i++ ) { + ps.portalPlanes[i] = tr.viewDef->frustum[i]; + } + ps.numPortalPlanes = 5; + ps.rect = tr.viewDef->scissor; + + AddAreaToView( tr.viewDef->areaNum, &ps ); + } + } else { + // note that the center of projection for flowing through portals may + // be a different point than initialViewAreaOrigin for subviews that + // may have the viewOrigin in a solid/invalid area + FlowViewThroughPortals( tr.viewDef->renderView.vieworg, 5, tr.viewDef->frustum ); + } +} + +/* +======================================================================= + +Light linking into the BSP tree by flooding through portals + +======================================================================= +*/ + +/* +=================== +idRenderWorldLocal::FloodLightThroughArea_r +=================== +*/ +void idRenderWorldLocal::FloodLightThroughArea_r( idRenderLightLocal *light, int areaNum, + const portalStack_t *ps ) { + assert( ps != NULL ); // compiler warning + portal_t* p = NULL; + float d; + portalArea_t * area = NULL; + const portalStack_t *check = NULL, *firstPortalStack = NULL; + portalStack_t newStack; + int i, j; + idVec3 v1, v2; + int addPlanes; + idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 + + area = &portalAreas[ areaNum ]; + + // add an areaRef + AddLightRefToArea( light, area ); + + // go through all the portals + for ( p = area->portals; p; p = p->next ) { + // make sure this portal is facing away from the view + d = p->plane.Distance( light->globalLightOrigin ); + if ( d < -0.1f ) { + continue; + } + + // make sure the portal isn't in our stack trace, + // which would cause an infinite loop + for ( check = ps; check; check = check->next ) { + firstPortalStack = check; + if ( check->p == p ) { + break; // don't recursively enter a stack + } + } + if ( check ) { + continue; // already in stack + } + + // if we are very close to the portal surface, don't bother clipping + // it, which tends to give epsilon problems that make the area vanish + if ( d < 1.0f ) { + // go through this portal + newStack = *ps; + newStack.p = p; + newStack.next = ps; + FloodLightThroughArea_r( light, p->intoArea, &newStack ); + continue; + } + + // clip the portal winding to all of the planes + w = *p->w; + for ( j = 0; j < ps->numPortalPlanes; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j], 0 ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; // portal not visible + } + // also always clip to the original light planes, because they aren't + // necessarily extending to infinitiy like a view frustum + for ( j = 0; j < firstPortalStack->numPortalPlanes; j++ ) { + if ( !w.ClipInPlace( -firstPortalStack->portalPlanes[j], 0 ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; // portal not visible + } + + // go through this portal + newStack.p = p; + newStack.next = ps; + + // generate a set of clipping planes that will further restrict + // the visible view beyond just the scissor rect + + addPlanes = w.GetNumPoints(); + if ( addPlanes > MAX_PORTAL_PLANES ) { + addPlanes = MAX_PORTAL_PLANES; + } + + newStack.numPortalPlanes = 0; + for ( i = 0; i < addPlanes; i++ ) { + j = i+1; + if ( j == w.GetNumPoints() ) { + j = 0; + } + + v1 = light->globalLightOrigin - w[i].ToVec3(); + v2 = light->globalLightOrigin - w[j].ToVec3(); + + newStack.portalPlanes[newStack.numPortalPlanes].Normal().Cross( v2, v1 ); + + // if it is degenerate, skip the plane + if ( newStack.portalPlanes[newStack.numPortalPlanes].Normalize() < 0.01f ) { + continue; + } + newStack.portalPlanes[newStack.numPortalPlanes].FitThroughPoint( light->globalLightOrigin ); + + newStack.numPortalPlanes++; + } + + FloodLightThroughArea_r( light, p->intoArea, &newStack ); + } +} + +/* +======================= +idRenderWorldLocal::FlowLightThroughPortals + +Adds an arearef in each area that the light center flows into. +This can only be used for shadow casting lights that have a generated +prelight, because shadows are cast from back side which may not be in visible areas. +======================= +*/ +void idRenderWorldLocal::FlowLightThroughPortals( idRenderLightLocal *light ) { + // if the light origin areaNum is not in a valid area, + // the light won't have any area refs + if ( light->areaNum == -1 ) { + return; + } + + idPlane frustumPlanes[6]; + idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); + + portalStack_t ps; + memset( &ps, 0, sizeof( ps ) ); + ps.numPortalPlanes = 6; + for ( int i = 0; i < 6; i++ ) { + ps.portalPlanes[i] = -frustumPlanes[i]; + } + + FloodLightThroughArea_r( light, light->areaNum, &ps ); +} + +/* +======================================================================= + +Portal State Management + +======================================================================= +*/ + +/* +============== +NumPortals +============== +*/ +int idRenderWorldLocal::NumPortals() const { + return numInterAreaPortals; +} + +/* +============== +FindPortal + +Game code uses this to identify which portals are inside doors. +Returns 0 if no portal contacts the bounds +============== +*/ +qhandle_t idRenderWorldLocal::FindPortal( const idBounds &b ) const { + int i, j; + idBounds wb; + doublePortal_t *portal; + idWinding *w; + + for ( i = 0; i < numInterAreaPortals; i++ ) { + portal = &doublePortals[i]; + w = portal->portals[0]->w; + + wb.Clear(); + for ( j = 0; j < w->GetNumPoints(); j++ ) { + wb.AddPoint( (*w)[j].ToVec3() ); + } + if ( wb.IntersectsBounds( b ) ) { + return i + 1; + } + } + + return 0; +} + +/* +============= +FloodConnectedAreas +============= +*/ +void idRenderWorldLocal::FloodConnectedAreas( portalArea_t *area, int portalAttributeIndex ) { + if ( area->connectedAreaNum[portalAttributeIndex] == connectedAreaNum ) { + return; + } + area->connectedAreaNum[portalAttributeIndex] = connectedAreaNum; + + for ( portal_t *p = area->portals; p != NULL; p = p->next ) { + if ( !(p->doublePortal->blockingBits & (1<intoArea], portalAttributeIndex ); + } + } +} + +/* +============== +AreasAreConnected +============== +*/ +bool idRenderWorldLocal::AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ) const { + if ( areaNum1 == -1 || areaNum2 == -1 ) { + return false; + } + if ( areaNum1 > numPortalAreas || areaNum2 > numPortalAreas || areaNum1 < 0 || areaNum2 < 0 ) { + common->Error( "idRenderWorldLocal::AreAreasConnected: bad parms: %i, %i", areaNum1, areaNum2 ); + } + + int attribute = 0; + + int intConnection = (int)connection; + + while ( intConnection > 1 ) { + attribute++; + intConnection >>= 1; + } + if ( attribute >= NUM_PORTAL_ATTRIBUTES || ( 1 << attribute ) != (int)connection ) { + common->Error( "idRenderWorldLocal::AreasAreConnected: bad connection number: %i\n", (int)connection ); + } + + return portalAreas[areaNum1].connectedAreaNum[attribute] == portalAreas[areaNum2].connectedAreaNum[attribute]; +} + +/* +============== +SetPortalState + +doors explicitly close off portals when shut +============== +*/ +void idRenderWorldLocal::SetPortalState( qhandle_t portal, int blockTypes ) { + if ( portal == 0 ) { + return; + } + + if ( portal < 1 || portal > numInterAreaPortals ) { + common->Error( "SetPortalState: bad portal number %i", portal ); + } + int old = doublePortals[portal-1].blockingBits; + if ( old == blockTypes ) { + return; + } + doublePortals[portal-1].blockingBits = blockTypes; + + // leave the connectedAreaGroup the same on one side, + // then flood fill from the other side with a new number for each changed attribute + for ( int i = 0; i < NUM_PORTAL_ATTRIBUTES; i++ ) { + if ( ( old ^ blockTypes ) & ( 1 << i ) ) { + connectedAreaNum++; + FloodConnectedAreas( &portalAreas[doublePortals[portal-1].portals[1]->intoArea], i ); + } + } + + if ( common->WriteDemo() ) { + common->WriteDemo()->WriteInt( DS_RENDER ); + common->WriteDemo()->WriteInt( DC_SET_PORTAL_STATE ); + common->WriteDemo()->WriteInt( portal ); + common->WriteDemo()->WriteInt( blockTypes ); + } +} + +/* +============== +GetPortalState +============== +*/ +int idRenderWorldLocal::GetPortalState( qhandle_t portal ) { + if ( portal == 0 ) { + return 0; + } + + if ( portal < 1 || portal > numInterAreaPortals ) { + common->Error( "GetPortalState: bad portal number %i", portal ); + } + + return doublePortals[portal-1].blockingBits; +} + +/* +===================== +idRenderWorldLocal::ShowPortals + +Debugging tool, won't work correctly with SMP or when mirrors are present +===================== +*/ +void idRenderWorldLocal::ShowPortals() { + int i, j; + portalArea_t *area; + portal_t *p; + idWinding *w; + + // flood out through portals, setting area viewCount + for ( i = 0; i < numPortalAreas; i++ ) { + area = &portalAreas[i]; + if ( area->viewCount != tr.viewCount ) { + continue; + } + for ( p = area->portals; p; p = p->next ) { + w = p->w; + if ( !w ) { + continue; + } + + if ( portalAreas[ p->intoArea ].viewCount != tr.viewCount ) { + // red = can't see + GL_Color( 1, 0, 0 ); + } else { + // green = see through + GL_Color( 0, 1, 0 ); + } + + qglBegin( GL_LINE_LOOP ); + for ( j = 0; j < w->GetNumPoints(); j++ ) { + qglVertex3fv( (*w)[j].ToFloatPtr() ); + } + qglEnd(); + } + } +} diff --git a/neo/renderer/ResolutionScale.cpp b/neo/renderer/ResolutionScale.cpp new file mode 100644 index 00000000..c92ef17e --- /dev/null +++ b/neo/renderer/ResolutionScale.cpp @@ -0,0 +1,193 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "tr_local.h" +#include "ResolutionScale.h" + + +idResolutionScale resolutionScale; + +static const float MINIMUM_RESOLUTION_SCALE = 0.5f; +static const float MAXIMUM_RESOLUTION_SCALE = 1.0f; + +idCVar rs_enable( "rs_enable", "1", CVAR_INTEGER, "Enable dynamic resolution scaling, 0 - off, 1 - horz only, 2 - vert only, 3 - both" ); +idCVar rs_forceFractionX( "rs_forceFractionX", "0", CVAR_FLOAT, "Force a specific 0.0 to 1.0 horizontal resolution scale" ); +idCVar rs_forceFractionY( "rs_forceFractionY", "0", CVAR_FLOAT, "Force a specific 0.0 to 1.0 vertical resolution scale" ); +idCVar rs_showResolutionChanges( "rs_showResolutionChanges", "0", CVAR_INTEGER, "1 = Print whenever the resolution scale changes, 2 = always" ); +idCVar rs_dropMilliseconds( "rs_dropMilliseconds", "15.0", CVAR_FLOAT, "Drop the resolution when GPU time exceeds this" ); +idCVar rs_raiseMilliseconds( "rs_raiseMilliseconds", "13.0", CVAR_FLOAT, "Raise the resolution when GPU time is below this for several frames" ); +idCVar rs_dropFraction( "rs_dropFraction", "0.11", CVAR_FLOAT, "Drop the resolution in increments of this" ); +idCVar rs_raiseFraction( "rs_raiseFraction", "0.06", CVAR_FLOAT, "Raise the resolution in increments of this" ); +idCVar rs_raiseFrames( "rs_raiseFrames", "5", CVAR_INTEGER, "Require this many frames below rs_raiseMilliseconds" ); +idCVar rs_display( "rs_display", "0", CVAR_INTEGER, "0 - percentages, 1 - pixels per frame" ); + + +/* +======================== +idResolutionScale::idResolutionScale +======================== +*/ +idResolutionScale::idResolutionScale() { + dropMilliseconds = 15.0f; + raiseMilliseconds = 13.0f; + framesAboveRaise = 0; + currentResolution = 1.0f; +} + +/* +======================== +idResolutionScale::InitForMap +======================== +*/ +void idResolutionScale::InitForMap( const char * mapName ) { + dropMilliseconds = rs_dropMilliseconds.GetFloat(); + raiseMilliseconds = rs_raiseMilliseconds.GetFloat(); +} + +/* +======================== +idResolutionScale::ResetToFullResolution +======================== +*/ +void idResolutionScale::ResetToFullResolution() { + currentResolution = 1.0f; +} + +/* +======================== +idResolutionScale::GetCurrentResolutionScale +======================== +*/ +void idResolutionScale::GetCurrentResolutionScale( float & x, float & y ) { + assert( currentResolution >= MINIMUM_RESOLUTION_SCALE ); + assert( currentResolution <= MAXIMUM_RESOLUTION_SCALE ); + + x = MAXIMUM_RESOLUTION_SCALE; + y = MAXIMUM_RESOLUTION_SCALE; + switch ( rs_enable.GetInteger() ) { + case 0: return; + case 1: x = currentResolution; break; + case 2: y = currentResolution; break; + case 3: { + const float middle = ( MINIMUM_RESOLUTION_SCALE + MAXIMUM_RESOLUTION_SCALE ) * 0.5f; + if ( currentResolution >= middle ) { + // First scale horizontally from max to min + x = MINIMUM_RESOLUTION_SCALE + ( currentResolution - middle ) * 2.0f; + } else { + // Then scale vertically from max to min + x = MINIMUM_RESOLUTION_SCALE; + y = MINIMUM_RESOLUTION_SCALE + ( currentResolution - MINIMUM_RESOLUTION_SCALE ) * 2.0f; + } + break; + } + } + float forceFrac = rs_forceFractionX.GetFloat(); + if ( forceFrac > 0.0f && forceFrac <= MAXIMUM_RESOLUTION_SCALE ) { + x = forceFrac; + } + forceFrac = rs_forceFractionY.GetFloat(); + if ( forceFrac > 0.0f && forceFrac <= MAXIMUM_RESOLUTION_SCALE ) { + y = forceFrac; + } +} + +/* +======================== +idResolutionScale::SetCurrentGPUFrameTime +======================== +*/ +void idResolutionScale::SetCurrentGPUFrameTime( int microseconds ) { + float old = currentResolution; + float milliseconds = microseconds * 0.001f; + + if ( milliseconds > dropMilliseconds ) { + // We missed our target, so drop the resolution. + // The target should be set conservatively so this does not + // necessarily imply a missed VBL. + // + // we might consider making the drop in some way + // proportional to how badly we missed + currentResolution -= rs_dropFraction.GetFloat(); + if ( currentResolution < MINIMUM_RESOLUTION_SCALE ) { + currentResolution = MINIMUM_RESOLUTION_SCALE; + } + } else if ( milliseconds < raiseMilliseconds ) { + // We seem to have speed to spare, so increase the resolution + // if we stay here consistantly. The raise fraction should + // be smaller than the drop fraction to avoid ping-ponging + // back and forth. + if ( ++framesAboveRaise >= rs_raiseFrames.GetInteger() ) { + framesAboveRaise = 0; + currentResolution += rs_raiseFraction.GetFloat(); + if ( currentResolution > MAXIMUM_RESOLUTION_SCALE ) { + currentResolution = MAXIMUM_RESOLUTION_SCALE; + } + } + } else { + // we are inside the target range + framesAboveRaise = 0; + } + + if ( rs_showResolutionChanges.GetInteger() > 1 || + ( rs_showResolutionChanges.GetInteger() == 1 && currentResolution != old ) ) { + idLib::Printf( "GPU msec: %4.1f resolutionScale: %4.2f\n", milliseconds, currentResolution ); + } +} + +/* +======================== +idResolutionScale::GetConsoleText +======================== +*/ +void idResolutionScale::GetConsoleText( idStr &s ) { + float x; + float y; + if ( rs_enable.GetInteger() == 0 ) { + s = "rs-off"; + return; + } + GetCurrentResolutionScale( x, y ); + if ( rs_display.GetInteger() > 0 ) { + x *= 1280.0f; + y *= 720.0f; + if ( rs_enable.GetInteger() == 1 ) { + y = 1.0f; + } else if ( rs_enable.GetInteger() == 2 ) { + x = 1.0f; + } + s = va( "rs-pixels %i", idMath::Ftoi( x * y ) ); + } else { + if ( rs_enable.GetInteger() == 3 ) { + s = va( "%2i%%h,%2i%%v", idMath::Ftoi( 100.0f * x ), idMath::Ftoi( 100.0f * y ) ); + } else { + s = va( "%2i%%%s", ( rs_enable.GetInteger() == 1 ) ? idMath::Ftoi( 100.0f * x ) : idMath::Ftoi( 100.0f * y ), ( rs_enable.GetInteger() == 1 ) ? "h" : "v" ); + } + } +} diff --git a/neo/renderer/ResolutionScale.h b/neo/renderer/ResolutionScale.h new file mode 100644 index 00000000..c261392a --- /dev/null +++ b/neo/renderer/ResolutionScale.h @@ -0,0 +1,64 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __RESOLUTIONSCALE_H__ +#define __RESOLUTIONSCALE_H__ + +class idResolutionScale { +public: + idResolutionScale(); + + void InitForMap( const char * mapName ); + + // Returns a float from 0.5 to 1.0, representing + // the estimated resolution downscale needed to + // maintain the target framerate. + void GetCurrentResolutionScale( float &x, float &y ); + + // This should be called after any discontinuous + // view movement or force texture loading to prevent + // the unusual frames from causing an excessively + // low dynamic resolution. + void ResetToFullResolution(); + + // Systems that don't have accurate GPU timing can pass 0 + // to this, which will effectively disable resolution scaling. + void SetCurrentGPUFrameTime( int microseconds ); + + // return console display text + void GetConsoleText( idStr &s ); + +private: + float dropMilliseconds; + float raiseMilliseconds; + int framesAboveRaise; + float currentResolution; +}; + +extern idResolutionScale resolutionScale; + +#endif // __RESOLUTIONSCALE_H__ diff --git a/neo/renderer/ScreenRect.cpp b/neo/renderer/ScreenRect.cpp new file mode 100644 index 00000000..f816daa5 --- /dev/null +++ b/neo/renderer/ScreenRect.cpp @@ -0,0 +1,157 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +========================================================================================== + +idScreenRect + +========================================================================================== +*/ + +/* +====================== +idScreenRect::Clear +====================== +*/ +void idScreenRect::Clear() { + x1 = y1 = 32000; + x2 = y2 = -32000; + zmin = 0.0f; + zmax = 1.0f; +} + +/* +====================== +idScreenRect::AddPoint +====================== +*/ +void idScreenRect::AddPoint( float x, float y ) { + int ix = idMath::Ftoi( x ); + int iy = idMath::Ftoi( y ); + + if ( ix < x1 ) { + x1 = ix; + } + if ( ix > x2 ) { + x2 = ix; + } + if ( iy < y1 ) { + y1 = iy; + } + if ( iy > y2 ) { + y2 = iy; + } +} + +/* +====================== +idScreenRect::Expand +====================== +*/ +void idScreenRect::Expand() { + x1--; + y1--; + x2++; + y2++; +} + +/* +====================== +idScreenRect::Intersect +====================== +*/ +void idScreenRect::Intersect( const idScreenRect &rect ) { + if ( rect.x1 > x1 ) { + x1 = rect.x1; + } + if ( rect.x2 < x2 ) { + x2 = rect.x2; + } + if ( rect.y1 > y1 ) { + y1 = rect.y1; + } + if ( rect.y2 < y2 ) { + y2 = rect.y2; + } +} + +/* +====================== +idScreenRect::Union +====================== +*/ +void idScreenRect::Union( const idScreenRect &rect ) { + if ( rect.x1 < x1 ) { + x1 = rect.x1; + } + if ( rect.x2 > x2 ) { + x2 = rect.x2; + } + if ( rect.y1 < y1 ) { + y1 = rect.y1; + } + if ( rect.y2 > y2 ) { + y2 = rect.y2; + } +} + +/* +====================== +idScreenRect::Equals +====================== +*/ +bool idScreenRect::Equals( const idScreenRect &rect ) const { + return ( x1 == rect.x1 && x2 == rect.x2 && y1 == rect.y1 && y2 == rect.y2 ); +} + +/* +====================== +idScreenRect::IsEmpty +====================== +*/ +bool idScreenRect::IsEmpty() const { + return ( x1 > x2 || y1 > y2 ); +} + +/* +====================== +R_ShowColoredScreenRect +====================== +*/ +void R_ShowColoredScreenRect( const idScreenRect &rect, int colorIndex ) { + if ( !rect.IsEmpty() ) { + static idVec4 colors[] = { colorRed, colorGreen, colorBlue, colorYellow, colorMagenta, colorCyan, colorWhite, colorPurple }; + tr.viewDef->renderWorld->DebugScreenRect( colors[colorIndex & 7], rect, tr.viewDef ); + } +} diff --git a/neo/renderer/ScreenRect.h b/neo/renderer/ScreenRect.h new file mode 100644 index 00000000..14b37cb2 --- /dev/null +++ b/neo/renderer/ScreenRect.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SCREENRECT_H__ +#define __SCREENRECT_H__ + +/* +================================================================================================ + +idScreenRect + +idScreenRect gets carried around with each drawSurf, so it makes sense +to keep it compact, instead of just using the idBounds class +================================================================================================ +*/ + +class idScreenRect { +public: + // Inclusive pixel bounds inside viewport + short x1; + short y1; + short x2; + short y2; + + // for depth bounds test + float zmin; + float zmax; + + // clear to backwards values + void Clear(); + bool IsEmpty() const; + short GetWidth() const { return x2 - x1 + 1; } + short GetHeight() const { return y2 - y1 + 1; } + int GetArea() const { return ( x2 - x1 + 1 ) * ( y2 - y1 + 1 ); } + + // expand by one pixel each way to fix roundoffs + void Expand(); + + // adds a point + void AddPoint( float x, float y ); + + void Intersect( const idScreenRect &rect ); + void Union( const idScreenRect &rect ); + bool Equals( const idScreenRect &rect ) const; +}; + +void R_ShowColoredScreenRect( const idScreenRect &rect, int colorIndex ); + +#endif /* !__SCREENRECT_H__ */ diff --git a/neo/renderer/VertexCache.cpp b/neo/renderer/VertexCache.cpp new file mode 100644 index 00000000..1ba22462 --- /dev/null +++ b/neo/renderer/VertexCache.cpp @@ -0,0 +1,338 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +idVertexCache vertexCache; + +idCVar r_showVertexCache( "r_showVertexCache", "0", CVAR_RENDERER | CVAR_BOOL, "Print stats about the vertex cache every frame" ); +idCVar r_showVertexCacheTimings( "r_showVertexCache", "0", CVAR_RENDERER | CVAR_BOOL, "Print stats about the vertex cache every frame" ); + + +/* +============== +ClearGeoBufferSet +============== +*/ +static void ClearGeoBufferSet( geoBufferSet_t &gbs ) { + gbs.indexMemUsed.SetValue( 0 ); + gbs.vertexMemUsed.SetValue( 0 ); + gbs.jointMemUsed.SetValue( 0 ); + gbs.allocations = 0; +} + +/* +============== +MapGeoBufferSet +============== +*/ +static void MapGeoBufferSet( geoBufferSet_t &gbs ) { + if ( gbs.mappedVertexBase == NULL ) { + gbs.mappedVertexBase = (byte *)gbs.vertexBuffer.MapBuffer( BM_WRITE ); + } + if ( gbs.mappedIndexBase == NULL ) { + gbs.mappedIndexBase = (byte *)gbs.indexBuffer.MapBuffer( BM_WRITE ); + } + if ( gbs.mappedJointBase == NULL && gbs.jointBuffer.GetAllocedSize() != 0 ) { + gbs.mappedJointBase = (byte *)gbs.jointBuffer.MapBuffer( BM_WRITE ); + } +} + +/* +============== +UnmapGeoBufferSet +============== +*/ +static void UnmapGeoBufferSet( geoBufferSet_t &gbs ) { + if ( gbs.mappedVertexBase != NULL ) { + gbs.vertexBuffer.UnmapBuffer(); + gbs.mappedVertexBase = NULL; + } + if ( gbs.mappedIndexBase != NULL ) { + gbs.indexBuffer.UnmapBuffer(); + gbs.mappedIndexBase = NULL; + } + if ( gbs.mappedJointBase != NULL ) { + gbs.jointBuffer.UnmapBuffer(); + gbs.mappedJointBase = NULL; + } +} + +/* +============== +AllocGeoBufferSet +============== +*/ +static void AllocGeoBufferSet( geoBufferSet_t &gbs, const int vertexBytes, const int indexBytes, const int jointBytes ) { + gbs.vertexBuffer.AllocBufferObject( NULL, vertexBytes ); + gbs.indexBuffer.AllocBufferObject( NULL, indexBytes ); + if ( jointBytes != 0 ) { + gbs.jointBuffer.AllocBufferObject( NULL, jointBytes / sizeof( idJointMat ) ); + } + ClearGeoBufferSet( gbs ); +} + +/* +============== +idVertexCache::Init +============== +*/ +void idVertexCache::Init( bool restart ) { + currentFrame = 0; + listNum = 0; + + mostUsedVertex = 0; + mostUsedIndex = 0; + mostUsedJoint = 0; + + for ( int i = 0; i < VERTCACHE_NUM_FRAMES; i++ ) { + AllocGeoBufferSet( frameData[i], VERTCACHE_VERTEX_MEMORY_PER_FRAME, VERTCACHE_INDEX_MEMORY_PER_FRAME, VERTCACHE_JOINT_MEMORY_PER_FRAME ); + } + AllocGeoBufferSet( staticData, STATIC_VERTEX_MEMORY, STATIC_INDEX_MEMORY, 0 ); + + MapGeoBufferSet( frameData[listNum] ); +} + +/* +============== +idVertexCache::Shutdown +============== +*/ +void idVertexCache::Shutdown() { + for ( int i = 0; i < VERTCACHE_NUM_FRAMES; i++ ) { + frameData[i].vertexBuffer.FreeBufferObject(); + frameData[i].indexBuffer.FreeBufferObject(); + frameData[i].jointBuffer.FreeBufferObject(); + } +} + +/* +============== +idVertexCache::PurgeAll +============== +*/ +void idVertexCache::PurgeAll() { + Shutdown(); + Init( true ); +} + +/* +============== +idVertexCache::FreeStaticData + +call on loading a new map +============== +*/ +void idVertexCache::FreeStaticData() { + ClearGeoBufferSet( staticData ); + mostUsedVertex = 0; + mostUsedIndex = 0; + mostUsedJoint = 0; +} + +/* +============== +idVertexCache::ActuallyAlloc +============== +*/ +vertCacheHandle_t idVertexCache::ActuallyAlloc( geoBufferSet_t & vcs, const void * data, int bytes, cacheType_t type ) { + if ( bytes == 0 ) { + return (vertCacheHandle_t)0; + } + + assert( ( ((UINT_PTR)(data)) & 15 ) == 0 ); + assert( ( bytes & 15 ) == 0 ); + + // thread safe interlocked adds + byte ** base = NULL; + int endPos = 0; + if ( type == CACHE_INDEX ) { + base = &vcs.mappedIndexBase; + endPos = vcs.indexMemUsed.Add( bytes ); + if ( endPos > vcs.indexBuffer.GetAllocedSize() ) { + idLib::Error( "Out of index cache" ); + } + } else if ( type == CACHE_VERTEX ) { + base = &vcs.mappedVertexBase; + endPos = vcs.vertexMemUsed.Add( bytes ); + if ( endPos > vcs.vertexBuffer.GetAllocedSize() ) { + idLib::Error( "Out of vertex cache" ); + } + } else if ( type == CACHE_JOINT ) { + base = &vcs.mappedJointBase; + endPos = vcs.jointMemUsed.Add( bytes ); + if ( endPos > vcs.jointBuffer.GetAllocedSize() ) { + idLib::Error( "Out of joint buffer cache" ); + } + } else { + assert( false ); + } + + vcs.allocations++; + + int offset = endPos - bytes; + + // Actually perform the data transfer + if ( data != NULL ) { + MapGeoBufferSet( vcs ); + CopyBuffer( *base + offset, (const byte *)data, bytes ); + } + + vertCacheHandle_t handle = ( (uint64)(currentFrame & VERTCACHE_FRAME_MASK ) << VERTCACHE_FRAME_SHIFT ) | + ( (uint64)(offset & VERTCACHE_OFFSET_MASK ) << VERTCACHE_OFFSET_SHIFT ) | + ( (uint64)(bytes & VERTCACHE_SIZE_MASK ) << VERTCACHE_SIZE_SHIFT ); + if ( &vcs == &staticData ) { + handle |= VERTCACHE_STATIC; + } + return handle; +} + +/* +============== +idVertexCache::GetVertexBuffer +============== +*/ +bool idVertexCache::GetVertexBuffer( vertCacheHandle_t handle, idVertexBuffer * vb ) { + const int isStatic = handle & VERTCACHE_STATIC; + const uint64 size = (int)( handle >> VERTCACHE_SIZE_SHIFT ) & VERTCACHE_SIZE_MASK; + const uint64 offset = (int)( handle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + const uint64 frameNum = (int)( handle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( isStatic ) { + vb->Reference( staticData.vertexBuffer, offset, size ); + return true; + } + if ( frameNum != ( ( currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + return false; + } + vb->Reference( frameData[drawListNum].vertexBuffer, offset, size ); + return true; +} + +/* +============== +idVertexCache::GetIndexBuffer +============== +*/ +bool idVertexCache::GetIndexBuffer( vertCacheHandle_t handle, idIndexBuffer * ib ) { + const int isStatic = handle & VERTCACHE_STATIC; + const uint64 size = (int)( handle >> VERTCACHE_SIZE_SHIFT ) & VERTCACHE_SIZE_MASK; + const uint64 offset = (int)( handle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + const uint64 frameNum = (int)( handle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( isStatic ) { + ib->Reference( staticData.indexBuffer, offset, size ); + return true; + } + if ( frameNum != ( ( currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + return false; + } + ib->Reference( frameData[drawListNum].indexBuffer, offset, size ); + return true; +} + +/* +============== +idVertexCache::GetJointBuffer +============== +*/ +bool idVertexCache::GetJointBuffer( vertCacheHandle_t handle, idJointBuffer * jb ) { + const int isStatic = handle & VERTCACHE_STATIC; + const uint64 numBytes = (int)( handle >> VERTCACHE_SIZE_SHIFT ) & VERTCACHE_SIZE_MASK; + const uint64 jointOffset = (int)( handle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + const uint64 frameNum = (int)( handle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + const uint64 numJoints = numBytes / sizeof( idJointMat ); + if ( isStatic ) { + jb->Reference( staticData.jointBuffer, jointOffset, numJoints ); + return true; + } + if ( frameNum != ( ( currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + return false; + } + jb->Reference( frameData[drawListNum].jointBuffer, jointOffset, numJoints ); + return true; +} + +/* +============== +idVertexCache::BeginBackEnd +============== +*/ +void idVertexCache::BeginBackEnd() { + mostUsedVertex = Max( mostUsedVertex, frameData[listNum].vertexMemUsed.GetValue() ); + mostUsedIndex = Max( mostUsedIndex, frameData[listNum].indexMemUsed.GetValue() ); + mostUsedJoint = Max( mostUsedJoint, frameData[listNum].jointMemUsed.GetValue() ); + + if ( r_showVertexCache.GetBool() ) { + idLib::Printf( "%08d: %d allocations, %dkB vertex, %dkB index, %kB joint : %dkB vertex, %dkB index, %kB joint\n", + currentFrame, frameData[listNum].allocations, + frameData[listNum].vertexMemUsed.GetValue() / 1024, + frameData[listNum].indexMemUsed.GetValue() / 1024, + frameData[listNum].jointMemUsed.GetValue() / 1024, + mostUsedVertex / 1024, + mostUsedIndex / 1024, + mostUsedJoint / 1024 ); + } + + // unmap the current frame so the GPU can read it + const int startUnmap = Sys_Milliseconds(); + UnmapGeoBufferSet( frameData[listNum] ); + UnmapGeoBufferSet( staticData ); + const int endUnmap = Sys_Milliseconds(); + if ( endUnmap - startUnmap > 1 ) { + idLib::PrintfIf( r_showVertexCacheTimings.GetBool(), "idVertexCache::unmap took %i msec\n", endUnmap - startUnmap ); + } + drawListNum = listNum; + + // prepare the next frame for writing to by the CPU + currentFrame++; + + listNum = currentFrame % VERTCACHE_NUM_FRAMES; + const int startMap = Sys_Milliseconds(); + MapGeoBufferSet( frameData[listNum] ); + const int endMap = Sys_Milliseconds(); + if ( endMap - startMap > 1 ) { + idLib::PrintfIf( r_showVertexCacheTimings.GetBool(), "idVertexCache::map took %i msec\n", endMap - startMap ); + } + + ClearGeoBufferSet( frameData[listNum] ); + + +#if 0 + const int startBind = Sys_Milliseconds(); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, (GLuint)frameData[drawListNum].vertexBuffer.GetAPIObject() ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLuint)frameData[drawListNum].indexBuffer.GetAPIObject() ); + const int endBind = Sys_Milliseconds(); + if ( endBind - startBind > 1 ) { + idLib::Printf( "idVertexCache::bind took %i msec\n", endBind - startBind ); + } +#endif + +} + diff --git a/neo/renderer/VertexCache.h b/neo/renderer/VertexCache.h new file mode 100644 index 00000000..d850f9a0 --- /dev/null +++ b/neo/renderer/VertexCache.h @@ -0,0 +1,174 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __VERTEXCACHE2_H__ +#define __VERTEXCACHE2_H__ + +const int VERTCACHE_INDEX_MEMORY_PER_FRAME = 31 * 1024 * 1024; +const int VERTCACHE_VERTEX_MEMORY_PER_FRAME = 31 * 1024 * 1024; +const int VERTCACHE_JOINT_MEMORY_PER_FRAME = 256 * 1024; + +const int VERTCACHE_NUM_FRAMES = 2; + +// there are a lot more static indexes than vertexes, because interactions are just new +// index lists that reference existing vertexes +const int STATIC_INDEX_MEMORY = 31 * 1024 * 1024; +const int STATIC_VERTEX_MEMORY = 31 * 1024 * 1024; // make sure it fits in VERTCACHE_OFFSET_MASK! + +// vertCacheHandle_t packs size, offset, and frame number into 64 bits +typedef uint64 vertCacheHandle_t; +const int VERTCACHE_STATIC = 1; // in the static set, not the per-frame set +const int VERTCACHE_SIZE_SHIFT = 1; +const int VERTCACHE_SIZE_MASK = 0x7fffff; // 8 megs +const int VERTCACHE_OFFSET_SHIFT = 24; +const int VERTCACHE_OFFSET_MASK = 0x1ffffff; // 32 megs +const int VERTCACHE_FRAME_SHIFT = 49; +const int VERTCACHE_FRAME_MASK = 0x7fff; // 15 bits = 32k frames to wrap around + +const int VERTEX_CACHE_ALIGN = 32; +const int INDEX_CACHE_ALIGN = 16; +const int JOINT_CACHE_ALIGN = 16; + +enum cacheType_t { + CACHE_VERTEX, + CACHE_INDEX, + CACHE_JOINT +}; + +struct geoBufferSet_t { + idIndexBuffer indexBuffer; + idVertexBuffer vertexBuffer; + idJointBuffer jointBuffer; + byte * mappedVertexBase; + byte * mappedIndexBase; + byte * mappedJointBase; + idSysInterlockedInteger indexMemUsed; + idSysInterlockedInteger vertexMemUsed; + idSysInterlockedInteger jointMemUsed; + int allocations; // number of index and vertex allocations combined +}; + +class idVertexCache { +public: + void Init( bool restart = false ); + void Shutdown(); + void PurgeAll(); + + // call on loading a new map + void FreeStaticData(); + + // this data is only valid for one frame of rendering + vertCacheHandle_t AllocVertex( const void * data, int bytes ) { + return ActuallyAlloc( frameData[listNum], data, bytes, CACHE_VERTEX ); + } + vertCacheHandle_t AllocIndex( const void * data, int bytes ) { + return ActuallyAlloc( frameData[listNum], data, bytes, CACHE_INDEX ); + } + vertCacheHandle_t AllocJoint( const void * data, int bytes ) { + return ActuallyAlloc( frameData[listNum], data, bytes, CACHE_JOINT ); + } + + // this data is valid until the next map load + vertCacheHandle_t AllocStaticVertex( const void * data, int bytes ) { + if ( staticData.vertexMemUsed.GetValue() + bytes > STATIC_VERTEX_MEMORY ) { + idLib::FatalError( "AllocStaticVertex failed, increase STATIC_VERTEX_MEMORY" ); + } + return ActuallyAlloc( staticData, data, bytes, CACHE_VERTEX ); + } + vertCacheHandle_t AllocStaticIndex( const void * data, int bytes ) { + if ( staticData.indexMemUsed.GetValue() + bytes > STATIC_INDEX_MEMORY ) { + idLib::FatalError( "AllocStaticIndex failed, increase STATIC_INDEX_MEMORY" ); + } + return ActuallyAlloc( staticData, data, bytes, CACHE_INDEX ); + } + + byte * MappedVertexBuffer( vertCacheHandle_t handle ) { + release_assert( !CacheIsStatic( handle ) ); + const uint64 offset = (int)( handle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + const uint64 frameNum = (int)( handle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + release_assert( frameNum == ( currentFrame & VERTCACHE_FRAME_MASK ) ); + return frameData[ listNum ].mappedVertexBase + offset; + } + + byte * MappedIndexBuffer( vertCacheHandle_t handle ) { + release_assert( !CacheIsStatic( handle ) ); + const uint64 offset = (int)( handle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + const uint64 frameNum = (int)( handle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + release_assert( frameNum == ( currentFrame & VERTCACHE_FRAME_MASK ) ); + return frameData[ listNum ].mappedIndexBase + offset; + } + + // Returns false if it's been purged + // This can only be called by the front end, the back end should only be looking at + // vertCacheHandle_t that are already validated. + bool CacheIsCurrent( const vertCacheHandle_t handle ) { + const int isStatic = handle & VERTCACHE_STATIC; + if ( isStatic ) { + return true; + } + const uint64 frameNum = (int)( handle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( frameNum != ( currentFrame & VERTCACHE_FRAME_MASK ) ) { + return false; + } + return true; + } + + static bool CacheIsStatic( const vertCacheHandle_t handle ) { + return ( handle & VERTCACHE_STATIC ) != 0; + } + + // vb/ib is a temporary reference -- don't store it + bool GetVertexBuffer( vertCacheHandle_t handle, idVertexBuffer * vb ); + bool GetIndexBuffer( vertCacheHandle_t handle, idIndexBuffer * ib ); + bool GetJointBuffer( vertCacheHandle_t handle, idJointBuffer * jb ); + + void BeginBackEnd(); + +public: + int currentFrame; // for determining the active buffers + int listNum; // currentFrame % VERTCACHE_NUM_FRAMES + int drawListNum; // (currentFrame-1) % VERTCACHE_NUM_FRAMES + + geoBufferSet_t staticData; + geoBufferSet_t frameData[VERTCACHE_NUM_FRAMES]; + + // High water marks for the per-frame buffers + int mostUsedVertex; + int mostUsedIndex; + int mostUsedJoint; + + // Try to make room for bytes + vertCacheHandle_t ActuallyAlloc( geoBufferSet_t & vcs, const void * data, int bytes, cacheType_t type ); +}; + +// platform specific code to memcpy into vertex buffers efficiently +// 16 byte alignment is guaranteed +void CopyBuffer( byte * dst, const byte * src, int numBytes ); + +extern idVertexCache vertexCache; + +#endif // __VERTEXCACHE2_H__ diff --git a/neo/renderer/jobs/ShadowShared.cpp b/neo/renderer/jobs/ShadowShared.cpp new file mode 100644 index 00000000..1e9f082a --- /dev/null +++ b/neo/renderer/jobs/ShadowShared.cpp @@ -0,0 +1,413 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../../idlib/ParallelJobList_JobHeaders.h" +#include "../../idlib/SoftwareCache.h" + +#include "../../idlib/math/Vector.h" +#include "../../idlib/math/Matrix.h" +#include "../../idlib/math/Quat.h" +#include "../../idlib/math/Rotation.h" +#include "../../idlib/math/Plane.h" +#include "../../idlib/bv/Sphere.h" +#include "../../idlib/bv/Bounds.h" +#include "../../idlib/geometry/JointTransform.h" +#include "../../idlib/geometry/DrawVert.h" +#include "../../idlib/geometry/RenderMatrix.h" +#include "ShadowShared.h" + + +/* +====================== +R_ViewPotentiallyInsideInfiniteShadowVolume + +If we know that we are "off to the side" of an infinite shadow volume, +we can draw it without caps in Z-pass mode. +====================== +*/ +bool R_ViewPotentiallyInsideInfiniteShadowVolume( const idBounds & occluderBounds, const idVec3 & localLight, const idVec3 & localView, const float zNear ) { + // Expand the bounds to account for the near clip plane, because the + // view could be mathematically outside, but if the near clip plane + // chops a volume edge then the Z-pass rendering would fail. + const idBounds expandedBounds = occluderBounds.Expand( zNear ); + + // If the view is inside the geometry bounding box then the view + // is also inside the shadow projection. + if ( expandedBounds.ContainsPoint( localView ) ) { + return true; + } + + // If the light is inside the geometry bounding box then the shadow is projected + // in all directions and any view position is inside the infinte shadow projection. + if ( expandedBounds.ContainsPoint( localLight ) ) { + return true; + } + + // If the line from localLight to localView intersects the geometry + // bounding box then the view is inside the infinite shadow projection. + if ( expandedBounds.LineIntersection( localLight, localView ) ) { + return true; + } + + // The view is definitely not inside the projected shadow. + return false; +} + +/* +==================== +R_ShadowVolumeCullBits +==================== +*/ +static void R_ShadowVolumeCullBits( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idShadowVert *verts, const int numVerts ) { + assert_16_byte_aligned( cullBits ); + assert_16_byte_aligned( verts ); + + + idODSStreamedArray< idShadowVert, 16, SBT_DOUBLE, 4 > vertsODS( verts, numVerts ); + + const __m128 vector_float_radius = _mm_splat_ps( _mm_load_ss( &radius ), 0 ); + const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; + const __m128i vector_int_mask0 = _mm_set1_epi32( 1 << 0 ); + const __m128i vector_int_mask1 = _mm_set1_epi32( 1 << 1 ); + const __m128i vector_int_mask2 = _mm_set1_epi32( 1 << 2 ); + const __m128i vector_int_mask3 = _mm_set1_epi32( 1 << 3 ); + const __m128i vector_int_mask4 = _mm_set1_epi32( 1 << 4 ); + const __m128i vector_int_mask5 = _mm_set1_epi32( 1 << 5 ); + const __m128i vector_int_mask6 = _mm_set1_epi32( 1 << 6 ); + const __m128i vector_int_mask7 = _mm_set1_epi32( 1 << 7 ); + + const __m128 p0 = _mm_loadu_ps( planes[0].ToFloatPtr() ); + const __m128 p1 = _mm_loadu_ps( planes[1].ToFloatPtr() ); + const __m128 p2 = _mm_loadu_ps( planes[2].ToFloatPtr() ); + const __m128 p3 = _mm_loadu_ps( planes[3].ToFloatPtr() ); + + const __m128 p0X = _mm_splat_ps( p0, 0 ); + const __m128 p0Y = _mm_splat_ps( p0, 1 ); + const __m128 p0Z = _mm_splat_ps( p0, 2 ); + const __m128 p0W = _mm_splat_ps( p0, 3 ); + + const __m128 p1X = _mm_splat_ps( p1, 0 ); + const __m128 p1Y = _mm_splat_ps( p1, 1 ); + const __m128 p1Z = _mm_splat_ps( p1, 2 ); + const __m128 p1W = _mm_splat_ps( p1, 3 ); + + const __m128 p2X = _mm_splat_ps( p2, 0 ); + const __m128 p2Y = _mm_splat_ps( p2, 1 ); + const __m128 p2Z = _mm_splat_ps( p2, 2 ); + const __m128 p2W = _mm_splat_ps( p2, 3 ); + + const __m128 p3X = _mm_splat_ps( p3, 0 ); + const __m128 p3Y = _mm_splat_ps( p3, 1 ); + const __m128 p3Z = _mm_splat_ps( p3, 2 ); + const __m128 p3W = _mm_splat_ps( p3, 3 ); + + __m128i vecTotalOrInt = { 0, 0, 0, 0 }; + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 4; + + for ( ; i <= nextNumVerts; i += 4 ) { + const __m128 v0 = _mm_load_ps( vertsODS[i + 0].xyzw.ToFloatPtr() ); + const __m128 v1 = _mm_load_ps( vertsODS[i + 1].xyzw.ToFloatPtr() ); + const __m128 v2 = _mm_load_ps( vertsODS[i + 2].xyzw.ToFloatPtr() ); + const __m128 v3 = _mm_load_ps( vertsODS[i + 3].xyzw.ToFloatPtr() ); + + const __m128 r0 = _mm_unpacklo_ps( v0, v2 ); // v0.x, v2.x, v0.z, v2.z + const __m128 r1 = _mm_unpackhi_ps( v0, v2 ); // v0.y, v2.y, v0.w, v2.w + const __m128 r2 = _mm_unpacklo_ps( v1, v3 ); // v1.x, v3.x, v1.z, v3.z + const __m128 r3 = _mm_unpackhi_ps( v1, v3 ); // v1.y, v3.y, v1.w, v3.w + + const __m128 vX = _mm_unpacklo_ps( r0, r2 ); // v0.x, v1.x, v2.x, v3.x + const __m128 vY = _mm_unpackhi_ps( r0, r2 ); // v0.y, v1.y, v2.y, v3.y + const __m128 vZ = _mm_unpacklo_ps( r1, r3 ); // v0.z, v1.z, v2.z, v3.z + + const __m128 d0 = _mm_madd_ps( vX, p0X, _mm_madd_ps( vY, p0Y, _mm_madd_ps( vZ, p0Z, p0W ) ) ); + const __m128 d1 = _mm_madd_ps( vX, p1X, _mm_madd_ps( vY, p1Y, _mm_madd_ps( vZ, p1Z, p1W ) ) ); + const __m128 d2 = _mm_madd_ps( vX, p2X, _mm_madd_ps( vY, p2Y, _mm_madd_ps( vZ, p2Z, p2W ) ) ); + const __m128 d3 = _mm_madd_ps( vX, p3X, _mm_madd_ps( vY, p3Y, _mm_madd_ps( vZ, p3Z, p3W ) ) ); + + const __m128 t0 = _mm_add_ps( d0, vector_float_radius ); + const __m128 t1 = _mm_add_ps( d1, vector_float_radius ); + const __m128 t2 = _mm_add_ps( d2, vector_float_radius ); + const __m128 t3 = _mm_add_ps( d3, vector_float_radius ); + + const __m128 t4 = _mm_sub_ps( d0, vector_float_radius ); + const __m128 t5 = _mm_sub_ps( d1, vector_float_radius ); + const __m128 t6 = _mm_sub_ps( d2, vector_float_radius ); + const __m128 t7 = _mm_sub_ps( d3, vector_float_radius ); + + __m128i c0 = __m128c( _mm_cmpgt_ps( t0, vector_float_zero ) ); + __m128i c1 = __m128c( _mm_cmpgt_ps( t1, vector_float_zero ) ); + __m128i c2 = __m128c( _mm_cmpgt_ps( t2, vector_float_zero ) ); + __m128i c3 = __m128c( _mm_cmpgt_ps( t3, vector_float_zero ) ); + + __m128i c4 = __m128c( _mm_cmplt_ps( t4, vector_float_zero ) ); + __m128i c5 = __m128c( _mm_cmplt_ps( t5, vector_float_zero ) ); + __m128i c6 = __m128c( _mm_cmplt_ps( t6, vector_float_zero ) ); + __m128i c7 = __m128c( _mm_cmplt_ps( t7, vector_float_zero ) ); + + c0 = _mm_and_si128( c0, vector_int_mask0 ); + c1 = _mm_and_si128( c1, vector_int_mask1 ); + c2 = _mm_and_si128( c2, vector_int_mask2 ); + c3 = _mm_and_si128( c3, vector_int_mask3 ); + + c4 = _mm_and_si128( c4, vector_int_mask4 ); + c5 = _mm_and_si128( c5, vector_int_mask5 ); + c6 = _mm_and_si128( c6, vector_int_mask6 ); + c7 = _mm_and_si128( c7, vector_int_mask7 ); + + c0 = _mm_or_si128( c0, c1 ); + c2 = _mm_or_si128( c2, c3 ); + c4 = _mm_or_si128( c4, c5 ); + c6 = _mm_or_si128( c6, c7 ); + + c0 = _mm_or_si128( c0, c2 ); + c4 = _mm_or_si128( c4, c6 ); + c0 = _mm_or_si128( c0, c4 ); + + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, c0 ); + + __m128i s0 = _mm_packs_epi32( c0, c0 ); + __m128i b0 = _mm_packus_epi16( s0, s0 ); + + *(unsigned int *)&cullBits[i] = _mm_cvtsi128_si32( b0 ); + } + } + + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, _mm_shuffle_epi32( vecTotalOrInt, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, _mm_shuffle_epi32( vecTotalOrInt, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + __m128i vecTotalOrShort = _mm_packs_epi32( vecTotalOrInt, vecTotalOrInt ); + __m128i vecTotalOrByte = _mm_packus_epi16( vecTotalOrShort, vecTotalOrShort ); + + totalOr = (byte) _mm_cvtsi128_si32( vecTotalOrByte ); + +} + +/* +=================== +R_SegmentToSegmentDistanceSquare +=================== +*/ +static float R_SegmentToSegmentDistanceSquare( const idVec3 & start1, const idVec3 & end1, const idVec3 & start2, const idVec3 & end2 ) { + + const idVec3 dir0 = start1 - start2; + const idVec3 dir1 = end1 - start1; + const idVec3 dir2 = end2 - start2; + + const float dotDir1Dir1 = dir1 * dir1; + const float dotDir2Dir2 = dir2 * dir2; + const float dotDir1Dir2 = dir1 * dir2; + const float dotDir0Dir1 = dir0 * dir1; + const float dotDir0Dir2 = dir0 * dir2; + + if ( dotDir1Dir1 < idMath::FLT_SMALLEST_NON_DENORMAL || dotDir2Dir2 < idMath::FLT_SMALLEST_NON_DENORMAL ) { + // At least one of the lines is degenerate. + // The returned length is correct only if both lines are degenerate otherwise the start point of + // the degenerate line has to be projected onto the other line to calculate the shortest distance. + // The degenerate case is not relevant here though. + return ( start2 - start1 ).LengthSqr(); + } + + const float d = dotDir1Dir1 * dotDir2Dir2 - dotDir1Dir2 * dotDir1Dir2; + + if ( d < idMath::FLT_SMALLEST_NON_DENORMAL ) { + // The lines are parallel. + // The returned length is not correct. + // The parallel case is not relevent here though. + return ( start2 - start1 ).LengthSqr(); + } + + const float n = dotDir0Dir2 * dotDir1Dir2 - dotDir0Dir1 * dotDir2Dir2; + + const float t1 = n / d; + const float t1c = idMath::ClampFloat( 0.0f, 1.0f, t1 ); + + const float t2 = ( dotDir0Dir2 + dotDir1Dir2 * t1 ) / dotDir2Dir2; + const float t2c = idMath::ClampFloat( 0.0f, 1.0f, t2 ); + + const idVec3 closest1 = start1 + ( dir1 * t1c ); + const idVec3 closest2 = start2 + ( dir2 * t2c ); + + const float distSqr = ( closest2 - closest1 ).LengthSqr(); + + return distSqr; +} + +/* +=================== +R_LineIntersectsTriangleExpandedWithSphere +=================== +*/ +bool R_LineIntersectsTriangleExpandedWithSphere( const idVec3 & lineStart, const idVec3 & lineEnd, const idVec3 & lineDir, const float lineLength, + const float sphereRadius, const idVec3 & triVert0, const idVec3 & triVert1, const idVec3 & triVert2 ) { + // edge directions + const idVec3 edge1 = triVert1 - triVert0; + const idVec3 edge2 = triVert2 - triVert0; + + // calculate determinant + const idVec3 tvec = lineStart - triVert0; + const idVec3 pvec = lineDir.Cross( edge1 ); + const idVec3 qvec = tvec.Cross( edge2 ); + const float det = edge2 * pvec; + + // calculate UV parameters + const float u = ( tvec * pvec ) * det; + const float v = ( lineDir * qvec ) * det; + + // test if the line passes through the triangle + if ( u >= 0.0f && u <= det * det ) { + if ( v >= 0.0f && u + v <= det * det ) { + // if determinant is near zero then the ray lies in the triangle plane + if ( idMath::Fabs( det ) > idMath::FLT_SMALLEST_NON_DENORMAL ) { + const float fraction = ( edge1 * qvec ) / det; + if ( fraction >= 0.0f && fraction <= lineLength ) { + return true; + } + } + } + } + + const float radiusSqr = sphereRadius * sphereRadius; + + if ( R_SegmentToSegmentDistanceSquare( lineStart, lineEnd, triVert0, triVert1 ) < radiusSqr ) { + return true; + } + + if ( R_SegmentToSegmentDistanceSquare( lineStart, lineEnd, triVert1, triVert2 ) < radiusSqr ) { + return true; + } + + if ( R_SegmentToSegmentDistanceSquare( lineStart, lineEnd, triVert2, triVert0 ) < radiusSqr ) { + return true; + } + + return false; +} + +/* +=================== +R_ViewInsideShadowVolume + +If the light origin is visible from the view origin without intersecting any +shadow volume near cap triangles then the view is not inside the shadow volume. +The near cap triangles of the shadow volume are expanded to account for the near +clip plane, because the view could be mathematically outside, but if the +near clip plane chops a triangle then Z-pass rendering would fail. + +This is expensive but if the CPU time is available this can avoid many +cases where the shadow volume would otherwise be rendered with Z-fail. +Rendering with Z-fail can be significantly slower even on today's hardware. +=================== +*/ +bool R_ViewInsideShadowVolume( byte * cullBits, const idShadowVert * verts, int numVerts, const triIndex_t * indexes, int numIndexes, + const idVec3 & localLightOrigin, const idVec3 & localViewOrigin, const float zNear ) { + + ALIGNTYPE16 idPlane planes[4]; + // create two planes orthogonal to each other that intersect along the trace + idVec3 startDir = localLightOrigin - localViewOrigin; + startDir.Normalize(); + startDir.NormalVectors( planes[0].Normal(), planes[1].Normal() ); + planes[0][3] = - localViewOrigin * planes[0].Normal(); + planes[1][3] = - localViewOrigin * planes[1].Normal(); + // create front and end planes so the trace is on the positive sides of both + planes[2] = startDir; + planes[2][3] = - localViewOrigin * planes[2].Normal(); + planes[3] = -startDir; + planes[3][3] = - localLightOrigin * planes[3].Normal(); + + // catagorize each point against the four planes + byte totalOr = 0; + + R_ShadowVolumeCullBits( cullBits, totalOr, zNear, planes, verts, numVerts ); + + // if we don't have points on both sides of both the ray planes, no intersection + if ( ( totalOr ^ ( totalOr >> 4 ) ) & 3 ) { + return false; + } + + // if we don't have any points between front and end, no intersection + if ( ( totalOr ^ ( totalOr >> 1 ) ) & 4 ) { + return false; + } + + // start streaming the indexes + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 3 > indexesODS( indexes, numIndexes ); + + // Calculate the start, end, dir and length of the line from the view origin to the light origin. + const idVec3 lineStart = localViewOrigin; + const idVec3 lineEnd = localLightOrigin; + const idVec3 lineDelta = lineEnd - lineStart; + const float lineLengthSqr = lineDelta.LengthSqr(); + const float lineLengthRcp = idMath::InvSqrt( lineLengthSqr ); + const idVec3 lineDir = lineDelta * lineLengthRcp; + const float lineLength = lineLengthSqr * lineLengthRcp; + + for ( int i = 0; i < numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch() - 3; + + for ( ; i <= nextNumIndexes; i += 3 ) { + const int i0 = indexesODS[i + 0]; + const int i1 = indexesODS[i + 1]; + const int i2 = indexesODS[i + 2]; + + // Get sidedness info for the triangle. + const byte triOr = cullBits[i0] | cullBits[i1] | cullBits[i2]; + + // If there are no points on both sides of both the ray planes, no intersection. + if ( likely( ( triOr ^ ( triOr >> 4 ) ) & 3 ) ) { + continue; + } + + // If there are no points between front and end, no intersection. + if ( unlikely( ( triOr ^ ( triOr >> 1 ) ) & 4 ) ) { + continue; + } + + const idODSObject< idVec4 > triVert0( & verts[i0].xyzw ); + const idODSObject< idVec4 > triVert1( & verts[i1].xyzw ); + const idODSObject< idVec4 > triVert2( & verts[i2].xyzw ); + + // If the W of any of the coordinates is zero then the triangle is at or + // stretches to infinity which means it is not part of the near cap. + if ( triVert0->w == 0.0f || triVert1->w == 0.0f || triVert2->w == 0.0f ) { + continue; + } + + // Test against the expanded triangle to see if we hit the near cap. + if ( R_LineIntersectsTriangleExpandedWithSphere( lineStart, lineEnd, lineDir, lineLength, zNear, + triVert2->ToVec3(), triVert1->ToVec3(), triVert0->ToVec3() ) ) { + return true; + } + } + } + + return false; +} diff --git a/neo/renderer/jobs/ShadowShared.h b/neo/renderer/jobs/ShadowShared.h new file mode 100644 index 00000000..cccb52d2 --- /dev/null +++ b/neo/renderer/jobs/ShadowShared.h @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SHADOWSHARED_H__ +#define __SHADOWSHARED_H__ + +enum shadowVolumeState_t { + SHADOWVOLUME_DONE = 0, + SHADOWVOLUME_UNFINISHED = 1 +}; + +// this extra stretch should also make the "inside shadow volume test" valid for both eyes of a stereo view +const float INSIDE_SHADOW_VOLUME_EXTRA_STRETCH = 4.0f; // in theory, should vary with FOV + +#define TEMP_ROUND4( x ) ( ( x + 3 ) & ~3 ) // round up to a multiple of 4 for SIMD +#define TEMP_CULLBITS( numVerts ) TEMP_ROUND4( numVerts ) + +bool R_ViewPotentiallyInsideInfiniteShadowVolume( const idBounds & occluderBounds, const idVec3 & localLight, const idVec3 & localView, const float znear ); + +bool R_LineIntersectsTriangleExpandedWithSphere( const idVec3 & lineStart, const idVec3 & lineEnd, const idVec3 & lineDir, const float lineLength, + const float sphereRadius, const idVec3 & triVert0, const idVec3 & triVert1, const idVec3 & triVert2 ); +bool R_ViewInsideShadowVolume( byte * cullBits, const idShadowVert * verts, int numVerts, const triIndex_t * indexes, int numIndexes, + const idVec3 & localLightOrigin, const idVec3 & localViewOrigin, const float zNear ); + +#endif // !__SHADOWSHARED_H__ diff --git a/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.cpp b/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.cpp new file mode 100644 index 00000000..241a6aad --- /dev/null +++ b/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.cpp @@ -0,0 +1,1013 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "DynamicShadowVolume_local.h" + +#include "../../../idlib/sys/sys_intrinsics.h" +#include "../../../idlib/geometry/DrawVert_intrinsics.h" + + +static const __m128i vector_int_neg_one = _mm_set_epi32( -1, -1, -1, -1 ); + +/* +===================== +TriangleFacing_SSE2 +===================== +*/ +static __forceinline __m128i TriangleFacing_SSE2( const __m128 & vert0X, const __m128 & vert0Y, const __m128 & vert0Z, + const __m128 & vert1X, const __m128 & vert1Y, const __m128 & vert1Z, + const __m128 & vert2X, const __m128 & vert2Y, const __m128 & vert2Z, + const __m128 & lightOriginX, const __m128 & lightOriginY, const __m128 & lightOriginZ ) { + const __m128 sX = _mm_sub_ps( vert1X, vert0X ); + const __m128 sY = _mm_sub_ps( vert1Y, vert0Y ); + const __m128 sZ = _mm_sub_ps( vert1Z, vert0Z ); + + const __m128 tX = _mm_sub_ps( vert2X, vert0X ); + const __m128 tY = _mm_sub_ps( vert2Y, vert0Y ); + const __m128 tZ = _mm_sub_ps( vert2Z, vert0Z ); + + const __m128 normalX = _mm_nmsub_ps( tZ, sY, _mm_mul_ps( tY, sZ ) ); + const __m128 normalY = _mm_nmsub_ps( tX, sZ, _mm_mul_ps( tZ, sX ) ); + const __m128 normalZ = _mm_nmsub_ps( tY, sX, _mm_mul_ps( tX, sY ) ); + const __m128 normalW = _mm_madd_ps( normalX, vert0X, _mm_madd_ps( normalY, vert0Y, _mm_mul_ps( normalZ, vert0Z ) ) ); + + const __m128 delta = _mm_nmsub_ps( lightOriginX, normalX, _mm_nmsub_ps( lightOriginY, normalY, _mm_nmsub_ps( lightOriginZ, normalZ, normalW ) ) ); + return _mm_castps_si128( _mm_cmplt_ps( delta, _mm_setzero_ps() ) ); +} + +/* +===================== +TriangleCulled + +The clip space of the 'lightProject' is assumed to be in the range [0, 1]. +===================== +*/ +static __forceinline __m128i TriangleCulled_SSE2( const __m128 & vert0X, const __m128 & vert0Y, const __m128 & vert0Z, + const __m128 & vert1X, const __m128 & vert1Y, const __m128 & vert1Z, + const __m128 & vert2X, const __m128 & vert2Y, const __m128 & vert2Z, + const __m128 & lightProjectX, const __m128 & lightProjectY, const __m128 & lightProjectZ, const __m128 & lightProjectW ) { + + const __m128 mvpX0 = _mm_splat_ps( lightProjectX, 0 ); + const __m128 mvpX1 = _mm_splat_ps( lightProjectX, 1 ); + const __m128 mvpX2 = _mm_splat_ps( lightProjectX, 2 ); + const __m128 mvpX3 = _mm_splat_ps( lightProjectX, 3 ); + + const __m128 c0X = _mm_madd_ps( vert0X, mvpX0, _mm_madd_ps( vert0Y, mvpX1, _mm_madd_ps( vert0Z, mvpX2, mvpX3 ) ) ); + const __m128 c1X = _mm_madd_ps( vert1X, mvpX0, _mm_madd_ps( vert1Y, mvpX1, _mm_madd_ps( vert1Z, mvpX2, mvpX3 ) ) ); + const __m128 c2X = _mm_madd_ps( vert2X, mvpX0, _mm_madd_ps( vert2Y, mvpX1, _mm_madd_ps( vert2Z, mvpX2, mvpX3 ) ) ); + + const __m128 mvpY0 = _mm_splat_ps( lightProjectY, 0 ); + const __m128 mvpY1 = _mm_splat_ps( lightProjectY, 1 ); + const __m128 mvpY2 = _mm_splat_ps( lightProjectY, 2 ); + const __m128 mvpY3 = _mm_splat_ps( lightProjectY, 3 ); + + const __m128 c0Y = _mm_madd_ps( vert0X, mvpY0, _mm_madd_ps( vert0Y, mvpY1, _mm_madd_ps( vert0Z, mvpY2, mvpY3 ) ) ); + const __m128 c1Y = _mm_madd_ps( vert1X, mvpY0, _mm_madd_ps( vert1Y, mvpY1, _mm_madd_ps( vert1Z, mvpY2, mvpY3 ) ) ); + const __m128 c2Y = _mm_madd_ps( vert2X, mvpY0, _mm_madd_ps( vert2Y, mvpY1, _mm_madd_ps( vert2Z, mvpY2, mvpY3 ) ) ); + + const __m128 mvpZ0 = _mm_splat_ps( lightProjectZ, 0 ); + const __m128 mvpZ1 = _mm_splat_ps( lightProjectZ, 1 ); + const __m128 mvpZ2 = _mm_splat_ps( lightProjectZ, 2 ); + const __m128 mvpZ3 = _mm_splat_ps( lightProjectZ, 3 ); + + const __m128 c0Z = _mm_madd_ps( vert0X, mvpZ0, _mm_madd_ps( vert0Y, mvpZ1, _mm_madd_ps( vert0Z, mvpZ2, mvpZ3 ) ) ); + const __m128 c1Z = _mm_madd_ps( vert1X, mvpZ0, _mm_madd_ps( vert1Y, mvpZ1, _mm_madd_ps( vert1Z, mvpZ2, mvpZ3 ) ) ); + const __m128 c2Z = _mm_madd_ps( vert2X, mvpZ0, _mm_madd_ps( vert2Y, mvpZ1, _mm_madd_ps( vert2Z, mvpZ2, mvpZ3 ) ) ); + + const __m128 mvpW0 = _mm_splat_ps( lightProjectW, 0 ); + const __m128 mvpW1 = _mm_splat_ps( lightProjectW, 1 ); + const __m128 mvpW2 = _mm_splat_ps( lightProjectW, 2 ); + const __m128 mvpW3 = _mm_splat_ps( lightProjectW, 3 ); + + const __m128 c0W = _mm_madd_ps( vert0X, mvpW0, _mm_madd_ps( vert0Y, mvpW1, _mm_madd_ps( vert0Z, mvpW2, mvpW3 ) ) ); + const __m128 c1W = _mm_madd_ps( vert1X, mvpW0, _mm_madd_ps( vert1Y, mvpW1, _mm_madd_ps( vert1Z, mvpW2, mvpW3 ) ) ); + const __m128 c2W = _mm_madd_ps( vert2X, mvpW0, _mm_madd_ps( vert2Y, mvpW1, _mm_madd_ps( vert2Z, mvpW2, mvpW3 ) ) ); + + const __m128 zero = _mm_setzero_ps(); + + __m128 b0 = _mm_or_ps( _mm_or_ps( _mm_cmpgt_ps( c0X, zero ), _mm_cmpgt_ps( c1X, zero ) ), _mm_cmpgt_ps( c2X, zero ) ); + __m128 b1 = _mm_or_ps( _mm_or_ps( _mm_cmpgt_ps( c0Y, zero ), _mm_cmpgt_ps( c1Y, zero ) ), _mm_cmpgt_ps( c2Y, zero ) ); + __m128 b2 = _mm_or_ps( _mm_or_ps( _mm_cmpgt_ps( c0Z, zero ), _mm_cmpgt_ps( c1Z, zero ) ), _mm_cmpgt_ps( c2Z, zero ) ); + __m128 b3 = _mm_or_ps( _mm_or_ps( _mm_cmpgt_ps( c0W, c0X ), _mm_cmpgt_ps( c1W, c1X ) ), _mm_cmpgt_ps( c2W, c2X ) ); + __m128 b4 = _mm_or_ps( _mm_or_ps( _mm_cmpgt_ps( c0W, c0Y ), _mm_cmpgt_ps( c1W, c1Y ) ), _mm_cmpgt_ps( c2W, c2Y ) ); + __m128 b5 = _mm_or_ps( _mm_or_ps( _mm_cmpgt_ps( c0W, c0Z ), _mm_cmpgt_ps( c1W, c1Z ) ), _mm_cmpgt_ps( c2W, c2Z ) ); + + b0 = _mm_and_ps( b0, b1 ); + b2 = _mm_and_ps( b2, b3 ); + b4 = _mm_and_ps( b4, b5 ); + b0 = _mm_and_ps( b0, b2 ); + b0 = _mm_and_ps( b0, b4 ); + + return _mm_castps_si128( _mm_cmpeq_ps( b0, zero ) ); +} + + +/* +===================== +CalculateTriangleFacingCulledStatic +===================== +*/ +static int CalculateTriangleFacingCulledStatic( byte * __restrict facing, byte * __restrict culled, const triIndex_t * __restrict indexes, int numIndexes, + const idDrawVert * __restrict verts, const int numVerts, + const idVec3 & lightOrigin, const idVec3 & viewOrigin, + bool cullShadowTrianglesToLight, const idRenderMatrix & lightProject, + bool * insideShadowVolume, const float radius ) { + + assert_spu_local_store( facing ); + assert_not_spu_local_store( indexes ); + assert_not_spu_local_store( verts ); + + if ( insideShadowVolume != NULL ) { + *insideShadowVolume = false; + } + + // calculate the start, end, dir and length of the line from the view origin to the light origin + const idVec3 lineStart = viewOrigin; + const idVec3 lineEnd = lightOrigin; + const idVec3 lineDelta = lineEnd - lineStart; + const float lineLengthSqr = lineDelta.LengthSqr(); + const float lineLengthRcp = idMath::InvSqrt( lineLengthSqr ); + const idVec3 lineDir = lineDelta * lineLengthRcp; + const float lineLength = lineLengthSqr * lineLengthRcp; + + + idODSStreamedIndexedArray< idDrawVert, triIndex_t, 32, SBT_QUAD, 4 * 3 > indexedVertsODS( verts, numVerts, indexes, numIndexes ); + + const __m128 lightOriginX = _mm_splat_ps( _mm_load_ss( &lightOrigin.x ), 0 ); + const __m128 lightOriginY = _mm_splat_ps( _mm_load_ss( &lightOrigin.y ), 0 ); + const __m128 lightOriginZ = _mm_splat_ps( _mm_load_ss( &lightOrigin.z ), 0 ); + + const __m128 lightProjectX = _mm_loadu_ps( lightProject[0] ); + const __m128 lightProjectY = _mm_loadu_ps( lightProject[1] ); + const __m128 lightProjectZ = _mm_loadu_ps( lightProject[2] ); + const __m128 lightProjectW = _mm_loadu_ps( lightProject[3] ); + + const __m128i cullShadowTrianglesToLightMask = cullShadowTrianglesToLight ? vector_int_neg_one : vector_int_zero; + + __m128i numFrontFacing = _mm_setzero_si128(); + + for ( int i = 0, j = 0; i < numIndexes; ) { + + const int batchStart = i; + const int batchEnd = indexedVertsODS.FetchNextBatch(); + const int batchEnd4x = batchEnd - 4 * 3; + const int indexStart = j; + + for ( ; i <= batchEnd4x; i += 4 * 3, j += 4 ) { + const __m128 vertA0 = _mm_load_ps( indexedVertsODS[i + 0 * 3 + 0].xyz.ToFloatPtr() ); + const __m128 vertA1 = _mm_load_ps( indexedVertsODS[i + 0 * 3 + 1].xyz.ToFloatPtr() ); + const __m128 vertA2 = _mm_load_ps( indexedVertsODS[i + 0 * 3 + 2].xyz.ToFloatPtr() ); + + const __m128 vertB0 = _mm_load_ps( indexedVertsODS[i + 1 * 3 + 0].xyz.ToFloatPtr() ); + const __m128 vertB1 = _mm_load_ps( indexedVertsODS[i + 1 * 3 + 1].xyz.ToFloatPtr() ); + const __m128 vertB2 = _mm_load_ps( indexedVertsODS[i + 1 * 3 + 2].xyz.ToFloatPtr() ); + + const __m128 vertC0 = _mm_load_ps( indexedVertsODS[i + 2 * 3 + 0].xyz.ToFloatPtr() ); + const __m128 vertC1 = _mm_load_ps( indexedVertsODS[i + 2 * 3 + 1].xyz.ToFloatPtr() ); + const __m128 vertC2 = _mm_load_ps( indexedVertsODS[i + 2 * 3 + 2].xyz.ToFloatPtr() ); + + const __m128 vertD0 = _mm_load_ps( indexedVertsODS[i + 3 * 3 + 0].xyz.ToFloatPtr() ); + const __m128 vertD1 = _mm_load_ps( indexedVertsODS[i + 3 * 3 + 1].xyz.ToFloatPtr() ); + const __m128 vertD2 = _mm_load_ps( indexedVertsODS[i + 3 * 3 + 2].xyz.ToFloatPtr() ); + + const __m128 r0X = _mm_unpacklo_ps( vertA0, vertC0 ); // vertA0.x, vertC0.x, vertA0.z, vertC0.z + const __m128 r0Y = _mm_unpackhi_ps( vertA0, vertC0 ); // vertA0.y, vertC0.y, vertA0.w, vertC0.w + const __m128 r0Z = _mm_unpacklo_ps( vertB0, vertD0 ); // vertB0.x, vertD0.x, vertB0.z, vertD0.z + const __m128 r0W = _mm_unpackhi_ps( vertB0, vertD0 ); // vertB0.y, vertD0.y, vertB0.w, vertD0.w + + const __m128 vert0X = _mm_unpacklo_ps( r0X, r0Z ); // vertA0.x, vertB0.x, vertC0.x, vertD0.x + const __m128 vert0Y = _mm_unpackhi_ps( r0X, r0Z ); // vertA0.y, vertB0.y, vertC0.y, vertD0.y + const __m128 vert0Z = _mm_unpacklo_ps( r0Y, r0W ); // vertA0.z, vertB0.z, vertC0.z, vertD0.z + + const __m128 r1X = _mm_unpacklo_ps( vertA1, vertC1 ); // vertA1.x, vertC1.x, vertA1.z, vertC1.z + const __m128 r1Y = _mm_unpackhi_ps( vertA1, vertC1 ); // vertA1.y, vertC1.y, vertA1.w, vertC1.w + const __m128 r1Z = _mm_unpacklo_ps( vertB1, vertD1 ); // vertB1.x, vertD1.x, vertB1.z, vertD1.z + const __m128 r1W = _mm_unpackhi_ps( vertB1, vertD1 ); // vertB1.y, vertD1.y, vertB1.w, vertD1.w + + const __m128 vert1X = _mm_unpacklo_ps( r1X, r1Z ); // vertA1.x, vertB1.x, vertC1.x, vertD1.x + const __m128 vert1Y = _mm_unpackhi_ps( r1X, r1Z ); // vertA1.y, vertB1.y, vertC1.y, vertD1.y + const __m128 vert1Z = _mm_unpacklo_ps( r1Y, r1W ); // vertA1.z, vertB1.z, vertC1.z, vertD1.z + + const __m128 r2X = _mm_unpacklo_ps( vertA2, vertC2 ); // vertA2.x, vertC2.x, vertA2.z, vertC2.z + const __m128 r2Y = _mm_unpackhi_ps( vertA2, vertC2 ); // vertA2.y, vertC2.y, vertA2.w, vertC2.w + const __m128 r2Z = _mm_unpacklo_ps( vertB2, vertD2 ); // vertB2.x, vertD2.x, vertB2.z, vertD2.z + const __m128 r2W = _mm_unpackhi_ps( vertB2, vertD2 ); // vertB2.y, vertD2.y, vertB2.w, vertD2.w + + const __m128 vert2X = _mm_unpacklo_ps( r2X, r2Z ); // vertA2.x, vertB2.x, vertC2.x, vertD2.x + const __m128 vert2Y = _mm_unpackhi_ps( r2X, r2Z ); // vertA2.y, vertB2.y, vertC2.y, vertD2.y + const __m128 vert2Z = _mm_unpacklo_ps( r2Y, r2W ); // vertA2.z, vertB2.z, vertC2.z, vertD2.z + + const __m128i triangleCulled = TriangleCulled_SSE2( vert0X, vert0Y, vert0Z, vert1X, vert1Y, vert1Z, vert2X, vert2Y, vert2Z, lightProjectX, lightProjectY, lightProjectZ, lightProjectW ); + + __m128i triangleFacing = TriangleFacing_SSE2( vert0X, vert0Y, vert0Z, vert1X, vert1Y, vert1Z, vert2X, vert2Y, vert2Z, lightOriginX, lightOriginY, lightOriginZ ); + + // optionally make triangles that are outside the light frustum facing so they do not contribute to the shadow volume + triangleFacing = _mm_or_si128( triangleFacing, _mm_and_si128( triangleCulled, cullShadowTrianglesToLightMask ) ); + + // store culled + const __m128i culled_s = _mm_packs_epi32( triangleCulled, triangleCulled ); + const __m128i culled_b = _mm_packs_epi16( culled_s, culled_s ); + *(int *)&culled[j] = _mm_cvtsi128_si32( culled_b ); + + // store facing + const __m128i facing_s = _mm_packs_epi32( triangleFacing, triangleFacing ); + const __m128i facing_b = _mm_packs_epi16( facing_s, facing_s ); + *(int *)&facing[j] = _mm_cvtsi128_si32( facing_b ); + + // count the number of facing triangles + numFrontFacing = _mm_add_epi32( numFrontFacing, _mm_and_si128( triangleFacing, vector_int_one ) ); + } + + if ( insideShadowVolume != NULL ) { + for ( int k = batchStart, n = indexStart; k <= batchEnd - 3; k += 3, n++ ) { + if ( !facing[n] ) { + if ( R_LineIntersectsTriangleExpandedWithSphere( lineStart, lineEnd, lineDir, lineLength, radius, indexedVertsODS[k + 2].xyz, indexedVertsODS[k + 1].xyz, indexedVertsODS[k + 0].xyz ) ) { + *insideShadowVolume = true; + insideShadowVolume = NULL; + break; + } + } + } + } + } + + numFrontFacing = _mm_add_epi32( numFrontFacing, _mm_shuffle_epi32( numFrontFacing, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + numFrontFacing = _mm_add_epi32( numFrontFacing, _mm_shuffle_epi32( numFrontFacing, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + return _mm_cvtsi128_si32( numFrontFacing ); + +} + +/* +===================== +CalculateTriangleFacingCulledSkinned +===================== +*/ +static int CalculateTriangleFacingCulledSkinned( byte * __restrict facing, byte * __restrict culled, idVec4 * __restrict tempVerts, const triIndex_t * __restrict indexes, int numIndexes, + const idDrawVert * __restrict verts, const int numVerts, const idJointMat * __restrict joints, + const idVec3 & lightOrigin, const idVec3 & viewOrigin, + bool cullShadowTrianglesToLight, const idRenderMatrix & lightProject, + bool * insideShadowVolume, const float radius ) { + assert_spu_local_store( facing ); + assert_spu_local_store( joints ); + assert_not_spu_local_store( indexes ); + assert_not_spu_local_store( verts ); + + if ( insideShadowVolume != NULL ) { + *insideShadowVolume = false; + } + + // calculate the start, end, dir and length of the line from the view origin to the light origin + const idVec3 lineStart = viewOrigin; + const idVec3 lineEnd = lightOrigin; + const idVec3 lineDelta = lineEnd - lineStart; + const float lineLengthSqr = lineDelta.LengthSqr(); + const float lineLengthRcp = idMath::InvSqrt( lineLengthSqr ); + const idVec3 lineDir = lineDelta * lineLengthRcp; + const float lineLength = lineLengthSqr * lineLengthRcp; + + + idODSStreamedArray< idDrawVert, 32, SBT_DOUBLE, 1 > vertsODS( verts, numVerts ); + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 1; + + for ( ; i <= nextNumVerts; i++ ) { + __m128 v = LoadSkinnedDrawVertPosition( vertsODS[i], joints ); + _mm_store_ps( tempVerts[i].ToFloatPtr(), v ); + } + } + + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 4 * 3 > indexesODS( indexes, numIndexes ); + + const __m128 lightOriginX = _mm_splat_ps( _mm_load_ss( &lightOrigin.x ), 0 ); + const __m128 lightOriginY = _mm_splat_ps( _mm_load_ss( &lightOrigin.y ), 0 ); + const __m128 lightOriginZ = _mm_splat_ps( _mm_load_ss( &lightOrigin.z ), 0 ); + + const __m128 lightProjectX = _mm_loadu_ps( lightProject[0] ); + const __m128 lightProjectY = _mm_loadu_ps( lightProject[1] ); + const __m128 lightProjectZ = _mm_loadu_ps( lightProject[2] ); + const __m128 lightProjectW = _mm_loadu_ps( lightProject[3] ); + + const __m128i cullShadowTrianglesToLightMask = cullShadowTrianglesToLight ? vector_int_neg_one : vector_int_zero; + + __m128i numFrontFacing = _mm_setzero_si128(); + + for ( int i = 0, j = 0; i < numIndexes; ) { + + const int batchStart = i; + const int batchEnd = indexesODS.FetchNextBatch(); + const int batchEnd4x = batchEnd - 4 * 3; + const int indexStart = j; + + for ( ; i <= batchEnd4x; i += 4 * 3, j += 4 ) { + const int indexA0 = indexesODS[( i + 0 * 3 + 0 )]; + const int indexA1 = indexesODS[( i + 0 * 3 + 1 )]; + const int indexA2 = indexesODS[( i + 0 * 3 + 2 )]; + + const int indexB0 = indexesODS[( i + 1 * 3 + 0 )]; + const int indexB1 = indexesODS[( i + 1 * 3 + 1 )]; + const int indexB2 = indexesODS[( i + 1 * 3 + 2 )]; + + const int indexC0 = indexesODS[( i + 2 * 3 + 0 )]; + const int indexC1 = indexesODS[( i + 2 * 3 + 1 )]; + const int indexC2 = indexesODS[( i + 2 * 3 + 2 )]; + + const int indexD0 = indexesODS[( i + 3 * 3 + 0 )]; + const int indexD1 = indexesODS[( i + 3 * 3 + 1 )]; + const int indexD2 = indexesODS[( i + 3 * 3 + 2 )]; + + const __m128 vertA0 = _mm_load_ps( tempVerts[indexA0].ToFloatPtr() ); + const __m128 vertA1 = _mm_load_ps( tempVerts[indexA1].ToFloatPtr() ); + const __m128 vertA2 = _mm_load_ps( tempVerts[indexA2].ToFloatPtr() ); + + const __m128 vertB0 = _mm_load_ps( tempVerts[indexB0].ToFloatPtr() ); + const __m128 vertB1 = _mm_load_ps( tempVerts[indexB1].ToFloatPtr() ); + const __m128 vertB2 = _mm_load_ps( tempVerts[indexB2].ToFloatPtr() ); + + const __m128 vertC0 = _mm_load_ps( tempVerts[indexC0].ToFloatPtr() ); + const __m128 vertC1 = _mm_load_ps( tempVerts[indexC1].ToFloatPtr() ); + const __m128 vertC2 = _mm_load_ps( tempVerts[indexC2].ToFloatPtr() ); + + const __m128 vertD0 = _mm_load_ps( tempVerts[indexD0].ToFloatPtr() ); + const __m128 vertD1 = _mm_load_ps( tempVerts[indexD1].ToFloatPtr() ); + const __m128 vertD2 = _mm_load_ps( tempVerts[indexD2].ToFloatPtr() ); + + const __m128 r0X = _mm_unpacklo_ps( vertA0, vertC0 ); // vertA0.x, vertC0.x, vertA0.z, vertC0.z + const __m128 r0Y = _mm_unpackhi_ps( vertA0, vertC0 ); // vertA0.y, vertC0.y, vertA0.w, vertC0.w + const __m128 r0Z = _mm_unpacklo_ps( vertB0, vertD0 ); // vertB0.x, vertD0.x, vertB0.z, vertD0.z + const __m128 r0W = _mm_unpackhi_ps( vertB0, vertD0 ); // vertB0.y, vertD0.y, vertB0.w, vertD0.w + + const __m128 vert0X = _mm_unpacklo_ps( r0X, r0Z ); // vertA0.x, vertB0.x, vertC0.x, vertD0.x + const __m128 vert0Y = _mm_unpackhi_ps( r0X, r0Z ); // vertA0.y, vertB0.y, vertC0.y, vertD0.y + const __m128 vert0Z = _mm_unpacklo_ps( r0Y, r0W ); // vertA0.z, vertB0.z, vertC0.z, vertD0.z + + const __m128 r1X = _mm_unpacklo_ps( vertA1, vertC1 ); // vertA1.x, vertC1.x, vertA1.z, vertC1.z + const __m128 r1Y = _mm_unpackhi_ps( vertA1, vertC1 ); // vertA1.y, vertC1.y, vertA1.w, vertC1.w + const __m128 r1Z = _mm_unpacklo_ps( vertB1, vertD1 ); // vertB1.x, vertD1.x, vertB1.z, vertD1.z + const __m128 r1W = _mm_unpackhi_ps( vertB1, vertD1 ); // vertB1.y, vertD1.y, vertB1.w, vertD1.w + + const __m128 vert1X = _mm_unpacklo_ps( r1X, r1Z ); // vertA1.x, vertB1.x, vertC1.x, vertD1.x + const __m128 vert1Y = _mm_unpackhi_ps( r1X, r1Z ); // vertA1.y, vertB1.y, vertC1.y, vertD1.y + const __m128 vert1Z = _mm_unpacklo_ps( r1Y, r1W ); // vertA1.z, vertB1.z, vertC1.z, vertD1.z + + const __m128 r2X = _mm_unpacklo_ps( vertA2, vertC2 ); // vertA2.x, vertC2.x, vertA2.z, vertC2.z + const __m128 r2Y = _mm_unpackhi_ps( vertA2, vertC2 ); // vertA2.y, vertC2.y, vertA2.w, vertC2.w + const __m128 r2Z = _mm_unpacklo_ps( vertB2, vertD2 ); // vertB2.x, vertD2.x, vertB2.z, vertD2.z + const __m128 r2W = _mm_unpackhi_ps( vertB2, vertD2 ); // vertB2.y, vertD2.y, vertB2.w, vertD2.w + + const __m128 vert2X = _mm_unpacklo_ps( r2X, r2Z ); // vertA2.x, vertB2.x, vertC2.x, vertD2.x + const __m128 vert2Y = _mm_unpackhi_ps( r2X, r2Z ); // vertA2.y, vertB2.y, vertC2.y, vertD2.y + const __m128 vert2Z = _mm_unpacklo_ps( r2Y, r2W ); // vertA2.z, vertB2.z, vertC2.z, vertD2.z + + const __m128i triangleCulled = TriangleCulled_SSE2( vert0X, vert0Y, vert0Z, vert1X, vert1Y, vert1Z, vert2X, vert2Y, vert2Z, lightProjectX, lightProjectY, lightProjectZ, lightProjectW ); + + __m128i triangleFacing = TriangleFacing_SSE2( vert0X, vert0Y, vert0Z, vert1X, vert1Y, vert1Z, vert2X, vert2Y, vert2Z, lightOriginX, lightOriginY, lightOriginZ ); + + // optionally make triangles that are outside the light frustum facing so they do not contribute to the shadow volume + triangleFacing = _mm_or_si128( triangleFacing, _mm_and_si128( triangleCulled, cullShadowTrianglesToLightMask ) ); + + // store culled + const __m128i culled_s = _mm_packs_epi32( triangleCulled, triangleCulled ); + const __m128i culled_b = _mm_packs_epi16( culled_s, culled_s ); + *(int *)&culled[j] = _mm_cvtsi128_si32( culled_b ); + + // store facing + const __m128i facing_s = _mm_packs_epi32( triangleFacing, triangleFacing ); + const __m128i facing_b = _mm_packs_epi16( facing_s, facing_s ); + *(int *)&facing[j] = _mm_cvtsi128_si32( facing_b ); + + // count the number of facing triangles + numFrontFacing = _mm_add_epi32( numFrontFacing, _mm_and_si128( triangleFacing, vector_int_one ) ); + } + + if ( insideShadowVolume != NULL ) { + for ( int k = batchStart, n = indexStart; k <= batchEnd - 3; k += 3, n++ ) { + if ( !facing[n] ) { + const int i0 = indexesODS[k + 0]; + const int i1 = indexesODS[k + 1]; + const int i2 = indexesODS[k + 2]; + if ( R_LineIntersectsTriangleExpandedWithSphere( lineStart, lineEnd, lineDir, lineLength, radius, tempVerts[i2].ToVec3(), tempVerts[i1].ToVec3(), tempVerts[i0].ToVec3() ) ) { + *insideShadowVolume = true; + insideShadowVolume = NULL; + break; + } + } + } + } + } + + numFrontFacing = _mm_add_epi32( numFrontFacing, _mm_shuffle_epi32( numFrontFacing, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + numFrontFacing = _mm_add_epi32( numFrontFacing, _mm_shuffle_epi32( numFrontFacing, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + return _mm_cvtsi128_si32( numFrontFacing ); + +} + +/* +============ +StreamOut +============ +*/ +static void StreamOut( void * dst, const void * src, int numBytes ) { + numBytes = ( numBytes + 15 ) & ~15; + assert_16_byte_aligned( dst ); + assert_16_byte_aligned( src ); + + int i = 0; + for ( ; i + 128 <= numBytes; i += 128 ) { + __m128i d0 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 0*16 ) ); + __m128i d1 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 1*16 ) ); + __m128i d2 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 2*16 ) ); + __m128i d3 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 3*16 ) ); + __m128i d4 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 4*16 ) ); + __m128i d5 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 5*16 ) ); + __m128i d6 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 6*16 ) ); + __m128i d7 = _mm_load_si128( (const __m128i *)( (byte *)src + i + 7*16 ) ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 0*16 ), d0 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 1*16 ), d1 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 2*16 ), d2 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 3*16 ), d3 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 4*16 ), d4 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 5*16 ), d5 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 6*16 ), d6 ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i + 7*16 ), d7 ); + } + for ( ; i + 16 <= numBytes; i += 16 ) { + __m128i d = _mm_load_si128( (__m128i *)( (byte *)src + i ) ); + _mm_stream_si128( (__m128i *)( (byte *)dst + i ), d ); + } +} + +/* +============ +R_CreateShadowVolumeTriangles +============ +*/ +static void R_CreateShadowVolumeTriangles( triIndex_t *__restrict shadowIndices, triIndex_t *__restrict indexBuffer, int & numShadowIndexesTotal, + const byte *__restrict facing, const silEdge_t *__restrict silEdges, const int numSilEdges, + const triIndex_t *__restrict indexes, const int numIndexes, const bool includeCaps ) { + assert_spu_local_store( facing ); + assert_not_spu_local_store( shadowIndices ); + assert_not_spu_local_store( silEdges ); + assert_not_spu_local_store( indexes ); + +#if 1 + + const int IN_BUFFER_SIZE = 64; + const int OUT_BUFFER_SIZE = IN_BUFFER_SIZE * 8; // each silhouette edge or cap triangle may create 6 indices (8 > 6) + const int OUT_BUFFER_DEPTH = 4; // quad buffer to allow overlapped output streaming + const int OUT_BUFFER_MASK = ( OUT_BUFFER_SIZE * OUT_BUFFER_DEPTH - 1 ); + + compile_time_assert( OUT_BUFFER_SIZE * OUT_BUFFER_DEPTH * sizeof( triIndex_t ) == OUTPUT_INDEX_BUFFER_SIZE ); + assert_16_byte_aligned( indexBuffer ); + + int numShadowIndices = 0; + int numStreamedIndices = 0; + + { + idODSStreamedArray< silEdge_t, IN_BUFFER_SIZE, SBT_DOUBLE, 1 > silEdgesODS( silEdges, numSilEdges ); + + for ( int i = 0; i < numSilEdges; ) { + + const int nextNumSilEdges = silEdgesODS.FetchNextBatch(); + + // NOTE: we rely on FetchNextBatch() to wait for all previous DMAs to complete + while( numShadowIndices - numStreamedIndices >= OUT_BUFFER_SIZE ) { + StreamOut( shadowIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], OUT_BUFFER_SIZE * sizeof( triIndex_t ) ); + numStreamedIndices += OUT_BUFFER_SIZE; + } + + for ( ; i + 4 <= nextNumSilEdges; i += 4 ) { + const silEdge_t & sil0 = silEdgesODS[i + 0]; + const silEdge_t & sil1 = silEdgesODS[i + 1]; + const silEdge_t & sil2 = silEdgesODS[i + 2]; + const silEdge_t & sil3 = silEdgesODS[i + 3]; + + { + const byte f1a = facing[sil0.p1]; + const byte f2a = facing[sil0.p2]; + const byte ta = ( f1a ^ f2a ) & 6; + const triIndex_t v1a = sil0.v1 << 1; + const triIndex_t v2a = sil0.v2 << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], v1a ^ ( 0 & 1 ), v2a ^ ( f1a & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], v2a ^ ( f2a & 1 ), v1a ^ ( f2a & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], v1a ^ ( f1a & 1 ), v2a ^ ( 1 & 1 ) ); + + numShadowIndices += ta; + } + + { + const byte f1b = facing[sil1.p1]; + const byte f2b = facing[sil1.p2]; + const byte tb = ( f1b ^ f2b ) & 6; + const triIndex_t v1b = sil1.v1 << 1; + const triIndex_t v2b = sil1.v2 << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], v1b ^ ( 0 & 1 ), v2b ^ ( f1b & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], v2b ^ ( f2b & 1 ), v1b ^ ( f2b & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], v1b ^ ( f1b & 1 ), v2b ^ ( 1 & 1 ) ); + + numShadowIndices += tb; + } + + { + const byte f1c = facing[sil2.p1]; + const byte f2c = facing[sil2.p2]; + const byte tc = ( f1c ^ f2c ) & 6; + const triIndex_t v1c = sil2.v1 << 1; + const triIndex_t v2c = sil2.v2 << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], v1c ^ ( 0 & 1 ), v2c ^ ( f1c & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], v2c ^ ( f2c & 1 ), v1c ^ ( f2c & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], v1c ^ ( f1c & 1 ), v2c ^ ( 1 & 1 ) ); + + numShadowIndices += tc; + } + + { + const byte f1d = facing[sil3.p1]; + const byte f2d = facing[sil3.p2]; + const byte td = ( f1d ^ f2d ) & 6; + const triIndex_t v1d = sil3.v1 << 1; + const triIndex_t v2d = sil3.v2 << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], v1d ^ ( 0 & 1 ), v2d ^ ( f1d & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], v2d ^ ( f2d & 1 ), v1d ^ ( f2d & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], v1d ^ ( f1d & 1 ), v2d ^ ( 1 & 1 ) ); + + numShadowIndices += td; + } + } + for ( ; i + 1 <= nextNumSilEdges; i++ ) { + const silEdge_t & sil = silEdgesODS[i]; + + const byte f1 = facing[sil.p1]; + const byte f2 = facing[sil.p2]; + const byte t = ( f1 ^ f2 ) & 6; + const triIndex_t v1 = sil.v1 << 1; + const triIndex_t v2 = sil.v2 << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], v1 ^ ( 0 & 1 ), v2 ^ ( f1 & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], v2 ^ ( f2 & 1 ), v1 ^ ( f2 & 1 ) ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], v1 ^ ( f1 & 1 ), v2 ^ ( 1 & 1 ) ); + + numShadowIndices += t; + } + } + } + + if ( includeCaps ) { + idODSStreamedArray< triIndex_t, IN_BUFFER_SIZE, SBT_QUAD, 1 > indexesODS( indexes, numIndexes ); + + for ( int i = 0, j = 0; i < numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch(); + + // NOTE: we rely on FetchNextBatch() to wait for all previous DMAs to complete + while( numShadowIndices - numStreamedIndices >= OUT_BUFFER_SIZE ) { + StreamOut( shadowIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], OUT_BUFFER_SIZE * sizeof( triIndex_t ) ); + numStreamedIndices += OUT_BUFFER_SIZE; + } + + for ( ; i + 4 * 3 <= nextNumIndexes; i += 4 * 3, j += 4 ) { + const byte ta = ~facing[j + 0] & 6; + const byte tb = ~facing[j + 1] & 6; + const byte tc = ~facing[j + 2] & 6; + const byte td = ~facing[j + 3] & 6; + + const triIndex_t i0a = indexesODS[i + 0 * 3 + 0] << 1; + const triIndex_t i1a = indexesODS[i + 0 * 3 + 1] << 1; + const triIndex_t i2a = indexesODS[i + 0 * 3 + 2] << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], i2a + 0, i1a + 0 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], i0a + 0, i0a + 1 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], i1a + 1, i2a + 1 ); + + numShadowIndices += ta; + + const triIndex_t i0b = indexesODS[i + 1 * 3 + 0] << 1; + const triIndex_t i1b = indexesODS[i + 1 * 3 + 1] << 1; + const triIndex_t i2b = indexesODS[i + 1 * 3 + 2] << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], i2b + 0, i1b + 0 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], i0b + 0, i0b + 1 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], i1b + 1, i2b + 1 ); + + numShadowIndices += tb; + + const triIndex_t i0c = indexesODS[i + 2 * 3 + 0] << 1; + const triIndex_t i1c = indexesODS[i + 2 * 3 + 1] << 1; + const triIndex_t i2c = indexesODS[i + 2 * 3 + 2] << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], i2c + 0, i1c + 0 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], i0c + 0, i0c + 1 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], i1c + 1, i2c + 1 ); + + numShadowIndices += tc; + + const triIndex_t i0d = indexesODS[i + 3 * 3 + 0] << 1; + const triIndex_t i1d = indexesODS[i + 3 * 3 + 1] << 1; + const triIndex_t i2d = indexesODS[i + 3 * 3 + 2] << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], i2d + 0, i1d + 0 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], i0d + 0, i0d + 1 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], i1d + 1, i2d + 1 ); + + numShadowIndices += td; + } + + for ( ; i + 3 <= nextNumIndexes; i += 3, j++ ) { + const byte t = ~facing[j] & 6; + + const triIndex_t i0 = indexesODS[i + 0] << 1; + const triIndex_t i1 = indexesODS[i + 1] << 1; + const triIndex_t i2 = indexesODS[i + 2] << 1; + + WriteIndexPair( &indexBuffer[( numShadowIndices + 0 ) & OUT_BUFFER_MASK], i2 + 0, i1 + 0 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 2 ) & OUT_BUFFER_MASK], i0 + 0, i0 + 1 ); + WriteIndexPair( &indexBuffer[( numShadowIndices + 4 ) & OUT_BUFFER_MASK], i1 + 1, i2 + 1 ); + + numShadowIndices += t; + } + } + } + + while( numShadowIndices - numStreamedIndices >= OUT_BUFFER_SIZE ) { + StreamOut( shadowIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], OUT_BUFFER_SIZE * sizeof( triIndex_t ) ); + numStreamedIndices += OUT_BUFFER_SIZE; + } + if ( numShadowIndices > numStreamedIndices ) { + assert( numShadowIndices - numStreamedIndices < OUT_BUFFER_SIZE ); + StreamOut( shadowIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], ( numShadowIndices - numStreamedIndices ) * sizeof( triIndex_t ) ); + } + + numShadowIndexesTotal = numShadowIndices; + + _mm_sfence(); + +#else // NOTE: this code will not work on the SPU because it tries to write directly to the destination + + triIndex_t * shadowIndexPtr = shadowIndices; + + { + idODSStreamedArray< silEdge_t, 128, SBT_DOUBLE, 1 > silEdgesODS( silEdges, numSilEdges ); + + for ( int i = 0; i < numSilEdges; ) { + + const int nextNumSilEdges = silEdgesODS.FetchNextBatch() - 1; + + for ( ; i <= nextNumSilEdges; i++ ) { + const silEdge_t & sil = silEdgesODS[i]; + + const byte f1 = facing[sil.p1] & 1; + const byte f2 = facing[sil.p2] & 1; + + if ( ( f1 ^ f2 ) == 0 ) { + continue; + } + + const triIndex_t v1 = sil.v1 << 1; + const triIndex_t v2 = sil.v2 << 1; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch +#if 1 + // only write dwords to write combined memory + WriteIndexPair( shadowIndexPtr + 0, v1 ^ 0, v2 ^ f1 ); + WriteIndexPair( shadowIndexPtr + 2, v2 ^ f2, v1 ^ f2 ); + WriteIndexPair( shadowIndexPtr + 4, v1 ^ f1, v2 ^ 1 ); +#else + shadowIndexPtr[0] == v1; + shadowIndexPtr[1] == v2 ^ f1; + shadowIndexPtr[2] == v2 ^ f2; + shadowIndexPtr[3] == v1 ^ f2; + shadowIndexPtr[4] == v1 ^ f1; + shadowIndexPtr[5] == v2 ^ 1; +#endif + shadowIndexPtr += 6; + } + } + } + + if ( includeCaps ) { + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 1 > indexesODS( indexes, numIndexes ); + + for ( int i = 0, j = 0; i < numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch() - 3; + + for ( ; i <= nextNumIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + const triIndex_t i0 = indexesODS[i + 0] << 1; + const triIndex_t i1 = indexesODS[i + 1] << 1; + const triIndex_t i2 = indexesODS[i + 2] << 1; +#if 1 + // only write dwords to write combined memory + WriteIndexPair( shadowIndexPtr + 0, i2 + 0, i1 + 0 ); + WriteIndexPair( shadowIndexPtr + 2, i0 + 0, i0 + 1 ); + WriteIndexPair( shadowIndexPtr + 4, i1 + 1, i2 + 1 ); +#else + shadowIndexPtr[0] = i2; + shadowIndexPtr[1] = i1; + shadowIndexPtr[2] = i0; + shadowIndexPtr[3] = i0 + 1; + shadowIndexPtr[4] = i1 + 1; + shadowIndexPtr[5] = i2 + 1; +#endif + shadowIndexPtr += 6; + } + } + } + + numShadowIndexesTotal = shadowIndexPtr - shadowIndices; + +#endif +} + +/* +===================== +R_CreateLightTriangles +===================== +*/ +void R_CreateLightTriangles( triIndex_t * __restrict lightIndices, triIndex_t * __restrict indexBuffer, int & numLightIndicesTotal, + const byte * __restrict culled, const triIndex_t * __restrict indexes, const int numIndexes ) { + assert_spu_local_store( culled ); + assert_not_spu_local_store( lightIndices ); + assert_not_spu_local_store( indexes ); + +#if 1 + + const int IN_BUFFER_SIZE = 256; + const int OUT_BUFFER_SIZE = IN_BUFFER_SIZE * 2; // there are never more indices generated than the original indices + const int OUT_BUFFER_DEPTH = 4; // quad buffer to allow overlapped output streaming + const int OUT_BUFFER_MASK = ( OUT_BUFFER_SIZE * OUT_BUFFER_DEPTH - 1 ); + + compile_time_assert( OUT_BUFFER_SIZE * OUT_BUFFER_DEPTH * sizeof( triIndex_t ) == OUTPUT_INDEX_BUFFER_SIZE ); + assert_16_byte_aligned( indexBuffer ); + + int numLightIndices = 0; + int numStreamedIndices = 0; + + idODSStreamedArray< triIndex_t, IN_BUFFER_SIZE, SBT_QUAD, 1 > indexesODS( indexes, numIndexes ); + + for ( int i = 0, j = 0; i < numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch(); + + // NOTE: we rely on FetchNextBatch() to wait for all previous DMAs to complete + while( numLightIndices - numStreamedIndices >= OUT_BUFFER_SIZE ) { + StreamOut( lightIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], OUT_BUFFER_SIZE * sizeof( triIndex_t ) ); + numStreamedIndices += OUT_BUFFER_SIZE; + } + + for ( ; i + 4 * 3 <= nextNumIndexes; i += 4 * 3, j += 4 ) { + const byte ta = ~culled[j + 0] & 3; + const byte tb = ~culled[j + 1] & 3; + const byte tc = ~culled[j + 2] & 3; + const byte td = ~culled[j + 3] & 3; + + indexBuffer[( numLightIndices + 0 ) & OUT_BUFFER_MASK] = indexesODS[i + 0 * 3 + 0]; + indexBuffer[( numLightIndices + 1 ) & OUT_BUFFER_MASK] = indexesODS[i + 0 * 3 + 1]; + indexBuffer[( numLightIndices + 2 ) & OUT_BUFFER_MASK] = indexesODS[i + 0 * 3 + 2]; + + numLightIndices += ta; + + indexBuffer[( numLightIndices + 0 ) & OUT_BUFFER_MASK] = indexesODS[i + 1 * 3 + 0]; + indexBuffer[( numLightIndices + 1 ) & OUT_BUFFER_MASK] = indexesODS[i + 1 * 3 + 1]; + indexBuffer[( numLightIndices + 2 ) & OUT_BUFFER_MASK] = indexesODS[i + 1 * 3 + 2]; + + numLightIndices += tb; + + indexBuffer[( numLightIndices + 0 ) & OUT_BUFFER_MASK] = indexesODS[i + 2 * 3 + 0]; + indexBuffer[( numLightIndices + 1 ) & OUT_BUFFER_MASK] = indexesODS[i + 2 * 3 + 1]; + indexBuffer[( numLightIndices + 2 ) & OUT_BUFFER_MASK] = indexesODS[i + 2 * 3 + 2]; + + numLightIndices += tc; + + indexBuffer[( numLightIndices + 0 ) & OUT_BUFFER_MASK] = indexesODS[i + 3 * 3 + 0]; + indexBuffer[( numLightIndices + 1 ) & OUT_BUFFER_MASK] = indexesODS[i + 3 * 3 + 1]; + indexBuffer[( numLightIndices + 2 ) & OUT_BUFFER_MASK] = indexesODS[i + 3 * 3 + 2]; + + numLightIndices += td; + } + + for ( ; i + 3 <= nextNumIndexes; i += 3, j++ ) { + const byte t = ~culled[j] & 3; + + indexBuffer[( numLightIndices + 0 ) & OUT_BUFFER_MASK] = indexesODS[i + 0]; + indexBuffer[( numLightIndices + 1 ) & OUT_BUFFER_MASK] = indexesODS[i + 1]; + indexBuffer[( numLightIndices + 2 ) & OUT_BUFFER_MASK] = indexesODS[i + 2]; + + numLightIndices += t; + } + } + + while( numLightIndices - numStreamedIndices >= OUT_BUFFER_SIZE ) { + StreamOut( lightIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], OUT_BUFFER_SIZE * sizeof( triIndex_t ) ); + numStreamedIndices += OUT_BUFFER_SIZE; + } + if ( numLightIndices > numStreamedIndices ) { + assert( numLightIndices - numStreamedIndices < OUT_BUFFER_SIZE ); + StreamOut( lightIndices + numStreamedIndices, & indexBuffer[numStreamedIndices & OUT_BUFFER_MASK], ( numLightIndices - numStreamedIndices ) * sizeof( triIndex_t ) ); + } + + numLightIndicesTotal = numLightIndices; + + _mm_sfence(); + +#else // NOTE: this code will not work on the SPU because it tries to write directly to the destination + + int numLightIndices = 0; + + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 1 > indexesODS( indexes, numIndexes ); + + for ( int i = 0, j = 0; i < numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch() - 3; + + for ( ; i <= nextNumIndexes; i += 3, j++ ) { + if ( culled[j] ) { + continue; + } + + lightIndices[numLightIndices + 0] = indexesODS[i + 0]; + lightIndices[numLightIndices + 1] = indexesODS[i + 1]; + lightIndices[numLightIndices + 2] = indexesODS[i + 2]; + + numLightIndices += 3; + } + } + + numLightIndicesTotal = numLightIndices; + +#endif +} + +/* +===================== +DynamicShadowVolumeJob + +Creates shadow volume indices for a surface that intersects a light. +Optionally also creates new surface indices with just the triangles +inside the light volume. These indices will be unique for a given +light / surface combination. + +The shadow volume indices are created using the original surface vertices. +However, the indices are setup to be used with a shadow volume vertex buffer +with all vertices duplicated where the even vertices have the same positions +as the surface vertices (at the near cap) and each odd vertex has the +same position as the previous even vertex but is projected to infinity +(the far cap) in the vertex program. +===================== +*/ +void DynamicShadowVolumeJob( const dynamicShadowVolumeParms_t * parms ) { + if ( parms->tempFacing == NULL ) { + *const_cast< byte ** >( &parms->tempFacing ) = (byte *)_alloca16( TEMP_FACING( parms->numIndexes ) ); + } + if ( parms->tempCulled == NULL ) { + *const_cast< byte ** >( &parms->tempCulled ) = (byte *)_alloca16( TEMP_CULL( parms->numIndexes ) ); + } + if ( parms->tempVerts == NULL && parms->joints != NULL ) { + *const_cast< idVec4 ** >( &parms->tempVerts ) = (idVec4 *)_alloca16( TEMP_VERTS( parms->numVerts ) ); + } + if ( parms->indexBuffer == NULL ) { + *const_cast< triIndex_t ** >( &parms->indexBuffer ) = (triIndex_t *)_alloca16( OUTPUT_INDEX_BUFFER_SIZE ); + } + + assert( parms->joints == NULL || parms->numJoints > 0 ); + + // Calculate the shadow depth bounds. + float shadowZMin = parms->lightZMin; + float shadowZMax = parms->lightZMax; + if ( parms->useShadowDepthBounds ) { + idRenderMatrix::DepthBoundsForShadowBounds( shadowZMin, shadowZMax, parms->triangleMVP, parms->triangleBounds, parms->localLightOrigin, true ); + shadowZMin = Max( shadowZMin, parms->lightZMin ); + shadowZMax = Min( shadowZMax, parms->lightZMax ); + } + + bool renderZFail = false; + int numShadowIndices = 0; + int numLightIndices = 0; + + // The shadow volume may be depth culled if either the shadow volume was culled to the view frustum or if the + // depth range of the visible part of the shadow volume is outside the depth range of the light volume. + if ( shadowZMin < shadowZMax ) { + + // Check if we need to render the shadow volume with Z-fail. + bool * preciseInsideShadowVolume = NULL; + // If the view is potentially inside the shadow volume bounds we may need to render with Z-fail. + if ( R_ViewPotentiallyInsideInfiniteShadowVolume( parms->triangleBounds, parms->localLightOrigin, parms->localViewOrigin, parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ) ) { + // Optionally perform a more precise test to see whether or not the view is inside the shadow volume. + if ( parms->useShadowPreciseInsideTest ) { + preciseInsideShadowVolume = & renderZFail; + } else { + renderZFail = true; + } + } + + // Calculate the facing of each triangle and cull each triangle to the light volume. + // Optionally also calculate more precisely whether or not the view is inside the shadow volume. + int numFrontFacing = 0; + if ( parms->joints != NULL ) { + numFrontFacing = CalculateTriangleFacingCulledSkinned( parms->tempFacing, parms->tempCulled, parms->tempVerts, parms->indexes, parms->numIndexes, + parms->verts, parms->numVerts, parms->joints, + parms->localLightOrigin, parms->localViewOrigin, + parms->cullShadowTrianglesToLight, parms->localLightProject, + preciseInsideShadowVolume, parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ); + } else { + numFrontFacing = CalculateTriangleFacingCulledStatic( parms->tempFacing, parms->tempCulled, parms->indexes, parms->numIndexes, + parms->verts, parms->numVerts, + parms->localLightOrigin, parms->localViewOrigin, + parms->cullShadowTrianglesToLight, parms->localLightProject, + preciseInsideShadowVolume, parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ); + } + + // Create shadow volume indices. + if ( parms->shadowIndices != NULL ) { + const int numTriangles = parms->numIndexes / 3; + + // If there are any triangles facing away from the light. + if ( numTriangles - numFrontFacing > 0 ) { + // Set the "fake triangle" used by dangling edges to facing so a dangling edge will + // make a silhouette if the triangle that uses the dangling edges is not facing. + // Note that dangling edges outside the light frustum do not make silhouettes because + // a triangle outside the light frustum is also set to facing just like the "fake triangle" + // used by a dangling edge. + parms->tempFacing[numTriangles] = 255; + + // Check if we can avoid rendering the shadow volume caps. + bool renderShadowCaps = parms->forceShadowCaps || renderZFail; + + // Create new triangles along the silhouette planes and optionally add end-cap triangles on the model and on the distant projection. + R_CreateShadowVolumeTriangles( parms->shadowIndices, parms->indexBuffer, numShadowIndices, parms->tempFacing, + parms->silEdges, parms->numSilEdges, parms->indexes, parms->numIndexes, renderShadowCaps ); + + assert( numShadowIndices <= parms->maxShadowIndices ); + } + } + + // Create new indices with only the triangles that are inside the light volume. + if ( parms->lightIndices != NULL ) { + R_CreateLightTriangles( parms->lightIndices, parms->indexBuffer, numLightIndices, parms->tempCulled, parms->indexes, parms->numIndexes ); + + assert( numLightIndices <= parms->maxLightIndices ); + } + } + + // write out the number of shadow indices + if ( parms->numShadowIndices != NULL ) { + *parms->numShadowIndices = numShadowIndices; + } + // write out the number of light indices + if ( parms->numLightIndices != NULL ) { + *parms->numLightIndices = numLightIndices; + } + // write out whether or not the shadow volume needs to be rendered with Z-Fail + if ( parms->renderZFail != NULL ) { + *parms->renderZFail = renderZFail; + } + // write out the shadow depth bounds + if ( parms->shadowZMin != NULL ) { + *parms->shadowZMin = shadowZMin; + } + if ( parms->shadowZMax != NULL ) { + *parms->shadowZMax = shadowZMax; + } + // write out the shadow volume state + if ( parms->shadowVolumeState != NULL ) { + *parms->shadowVolumeState = SHADOWVOLUME_DONE; + } +} + +REGISTER_PARALLEL_JOB( DynamicShadowVolumeJob, "DynamicShadowVolumeJob" ); diff --git a/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.h b/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.h new file mode 100644 index 00000000..40b532e8 --- /dev/null +++ b/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume.h @@ -0,0 +1,116 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __DYNAMICSHADOWVOLUME_H__ +#define __DYNAMICSHADOWVOLUME_H__ + +/* +================================================================================================ + +Dynamic Shadow Volume Setup + +A dynamic shadow is cast from a model touching a light where either the model or light is dynamic or both. +A dynamic shadow volume extends to infinity which allows the end caps to be omitted +when the view is outside and far enough away from the shadow volume. + +A dynamic shadow volume is created at run-time in this job. This job also determines whether +or not end caps need to be generated and rendered, whether or not the shadow volume needs +to be rendered with Z-Fail, and optionally calculates the shadow volume depth bounds. + +The occluder triangles are optionally culled to the light projection matrix. This is +particularly important for spot lights that would otherwise cast shadows in all directions. +However, there can also be significant savings when a small point light touches a large +model like for instance a world model. + +================================================================================================ +*/ + +#define TEMP_ROUND16( x ) ( ( x + 15 ) & ~15 ) +#define TEMP_FACING( numIndexes ) TEMP_ROUND16( ( ( numIndexes / 3 + 3 ) & ~3 ) + 1 ) // rounded up for SIMD, plus 1 for dangling edges +#define TEMP_CULL( numIndexes ) TEMP_ROUND16( ( ( numIndexes / 3 + 3 ) & ~3 ) ) // rounded up for SIMD +#define TEMP_VERTS( numVerts ) TEMP_ROUND16( numVerts * sizeof( idVec4 ) ) +#define OUTPUT_INDEX_BUFFER_SIZE 4096 + +struct silEdge_t { + // NOTE: using triIndex_t for the planes is dubious, as there can be 2x the faces as verts + triIndex_t p1, p2; // planes defining the edge + triIndex_t v1, v2; // verts defining the edge +}; + +/* +================================================ +dynamicShadowVolumeParms_t +================================================ +*/ +struct dynamicShadowVolumeParms_t { + // input + const idDrawVert * verts; // streamed in from main memory + int numVerts; + const triIndex_t * indexes; // streamed in from main memory + int numIndexes; + const silEdge_t * silEdges; // streamed in from main memory + int numSilEdges; + const idJointMat * joints; // in SPU local memory + int numJoints; + idBounds triangleBounds; + idRenderMatrix triangleMVP; + idVec3 localLightOrigin; + idVec3 localViewOrigin; + idRenderMatrix localLightProject; + float zNear; + float lightZMin; + float lightZMax; + bool cullShadowTrianglesToLight; + bool forceShadowCaps; + bool useShadowPreciseInsideTest; + bool useShadowDepthBounds; + // temp + byte * tempFacing; // temp buffer in SPU local memory + byte * tempCulled; // temp buffer in SPU local memory + idVec4 * tempVerts; // temp buffer in SPU local memory + // output + triIndex_t * indexBuffer; // output buffer in SPU local memory + triIndex_t * shadowIndices; // streamed out to main memory + int maxShadowIndices; + int * numShadowIndices; // streamed out to main memory + triIndex_t * lightIndices; // streamed out to main memory + int maxLightIndices; + int * numLightIndices; // streamed out to main memory + int * renderZFail; // streamed out to main memory + float * shadowZMin; // streamed out to main memory + float * shadowZMax; // streamed out to main memory + volatile shadowVolumeState_t * shadowVolumeState; // streamed out to main memory + // next in chain on view entity + dynamicShadowVolumeParms_t * next; + int pad; +}; + + +void DynamicShadowVolumeJob( const dynamicShadowVolumeParms_t * parms ); +void DynamicShadowVolume_SetupSPURSHeader( CellSpursJob128 * job, const dynamicShadowVolumeParms_t * parms ); + +#endif // !__DYNAMICSHADOWVOLUME_H__ diff --git a/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume_local.h b/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume_local.h new file mode 100644 index 00000000..ea2cb7ae --- /dev/null +++ b/neo/renderer/jobs/dynamicshadowvolume/DynamicShadowVolume_local.h @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __DYNAMICSHADOWVOLUME_LOCAL_H__ +#define __DYNAMICSHADOWVOLUME_LOCAL_H__ + +#include "../../../idlib/ParallelJobList_JobHeaders.h" +#include "../../../idlib/SoftwareCache.h" + +#include "../../../idlib/math/Vector.h" +#include "../../../idlib/math/Matrix.h" +#include "../../../idlib/math/Quat.h" +#include "../../../idlib/math/Rotation.h" +#include "../../../idlib/math/Plane.h" +#include "../../../idlib/bv/Sphere.h" +#include "../../../idlib/bv/Bounds.h" +#include "../../../idlib/geometry/JointTransform.h" +#include "../../../idlib/geometry/DrawVert.h" +#include "../../../idlib/geometry/RenderMatrix.h" +#include "../ShadowShared.h" +#include "DynamicShadowVolume.h" + +#endif // !__DYNAMICSHADOWVOLUME_LOCAL_H__ diff --git a/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.cpp b/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.cpp new file mode 100644 index 00000000..189c422b --- /dev/null +++ b/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.cpp @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "PreLightShadowVolume_local.h" + +/* +=================== +PreLightShadowVolumeJob +=================== +*/ +void PreLightShadowVolumeJob( const preLightShadowVolumeParms_t * parms ) { + if ( parms->tempCullBits == NULL ) { + *const_cast< byte ** >( &parms->tempCullBits ) = (byte *)_alloca16( TEMP_CULLBITS( parms->numVerts ) ); + } + + // Calculate the shadow depth bounds. + float shadowZMin = parms->lightZMin; + float shadowZMax = parms->lightZMax; + if ( parms->useShadowDepthBounds ) { + // NOTE: pre-light shadow volumes typically cover the whole light volume so + // there is no real point in trying to calculate tighter depth bounds here + shadowZMin = Max( shadowZMin, parms->lightZMin ); + shadowZMax = Min( shadowZMax, parms->lightZMax ); + } + + bool renderZFail = false; + int numShadowIndices = 0; + + // The shadow volume may be depth culled if either the shadow volume was culled to the view frustum or if the + // depth range of the visible part of the shadow volume is outside the depth range of the light volume. + if ( shadowZMin < shadowZMax ) { + // Check if we need to render the shadow volume with Z-fail. + // If the view is potentially inside the shadow volume bounds we may need to render with Z-fail. + if ( parms->triangleBounds.Expand( parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ).ContainsPoint( parms->localViewOrigin ) ) { + // Optionally perform a more precise test to see whether or not the view is inside the shadow volume. + if ( parms->useShadowPreciseInsideTest && parms->verts != NULL && parms->indexes != NULL ) { + renderZFail = R_ViewInsideShadowVolume( parms->tempCullBits, parms->verts, parms->numVerts, parms->indexes, parms->numIndexes, + parms->localLightOrigin, parms->localViewOrigin, parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ); + } else { + renderZFail = true; + } + } + + // must always draw the caps because pre-light shadows do not project to infinity + numShadowIndices = parms->numIndexes; + } + + // write out the number of shadow indices + if ( parms->numShadowIndices != NULL ) { + *parms->numShadowIndices = numShadowIndices; + } + // write out whether or not the shadow volume needs to be rendered with Z-Fail + if ( parms->renderZFail != NULL ) { + *parms->renderZFail = renderZFail; + } + // write out the shadow depth bounds + if ( parms->shadowZMin != NULL ) { + *parms->shadowZMin = shadowZMin; + } + if ( parms->shadowZMax != NULL ) { + *parms->shadowZMax = shadowZMax; + } + // write out the shadow volume state + if ( parms->shadowVolumeState != NULL ) { + *parms->shadowVolumeState = SHADOWVOLUME_DONE; + } +} + +REGISTER_PARALLEL_JOB( PreLightShadowVolumeJob, "PreLightShadowVolumeJob" ); diff --git a/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.h b/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.h new file mode 100644 index 00000000..99b70e76 --- /dev/null +++ b/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume.h @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PRELIGHTSHADOWVOLUME_H__ +#define __PRELIGHTSHADOWVOLUME_H__ + +/* +================================================================================================ + +Pre-Light Shadow Volume Setup + +A pre-light shadow is cast from static world geometry touching a static light. +A pre-light shadow volume does not extend to infinity but is capped at the light +boundaries which means the end caps always need to be rendered. + +A pre-light shadow volume is created at map compile time and this job determines whether or +not the shadow volume needs to be rendered with Z-Fail. + +================================================================================================ +*/ + +/* +================================================ +preLightShadowVolumeParms_t +================================================ +*/ +struct preLightShadowVolumeParms_t { + // input + const idShadowVert * verts; // streamed in from main memory + int numVerts; + const triIndex_t * indexes; // streamed in from main memory + int numIndexes; + idBounds triangleBounds; + idRenderMatrix triangleMVP; + idVec3 localLightOrigin; + idVec3 localViewOrigin; + float zNear; + float lightZMin; + float lightZMax; + bool forceShadowCaps; + bool useShadowPreciseInsideTest; + bool useShadowDepthBounds; + // temp + byte * tempCullBits; // temp buffer in SPU local memory + // output + int * numShadowIndices; // streamed out to main memory + int * renderZFail; // streamed out to main memory + float * shadowZMin; // streamed out to main memory + float * shadowZMax; // streamed out to main memory + volatile shadowVolumeState_t * shadowVolumeState; // streamed out to main memory + // next in chain on view light + preLightShadowVolumeParms_t * next; + int pad; +}; + + +void PreLightShadowVolumeJob( const preLightShadowVolumeParms_t * parms ); +void PreLightShadowVolume_SetupSPURSHeader( CellSpursJob128 * job, const preLightShadowVolumeParms_t * parms ); + +#endif // !__PRELIGHTSHADOWVOLUME_H__ diff --git a/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume_local.h b/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume_local.h new file mode 100644 index 00000000..2c72a2ed --- /dev/null +++ b/neo/renderer/jobs/prelightshadowvolume/PreLightShadowVolume_local.h @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PRELGHTSHADOWVOLUME_LOCAL_H__ +#define __PRELGHTSHADOWVOLUME_LOCAL_H__ + +#include "../../../idlib/ParallelJobList_JobHeaders.h" +#include "../../../idlib/SoftwareCache.h" + +#include "../../../idlib/math/Vector.h" +#include "../../../idlib/math/Matrix.h" +#include "../../../idlib/math/Quat.h" +#include "../../../idlib/math/Rotation.h" +#include "../../../idlib/math/Plane.h" +#include "../../../idlib/bv/Sphere.h" +#include "../../../idlib/bv/Bounds.h" +#include "../../../idlib/geometry/JointTransform.h" +#include "../../../idlib/geometry/DrawVert.h" +#include "../../../idlib/geometry/RenderMatrix.h" +#include "../ShadowShared.h" +#include "PreLightShadowVolume.h" + +#endif // !__PRELGHTSHADOWVOLUME_LOCAL_H__ diff --git a/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.cpp b/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.cpp new file mode 100644 index 00000000..7713a110 --- /dev/null +++ b/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.cpp @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "StaticShadowVolume_local.h" + +/* +=================== +StaticShadowVolumeJob +=================== +*/ +void StaticShadowVolumeJob( const staticShadowVolumeParms_t * parms ) { + if ( parms->tempCullBits == NULL ) { + *const_cast< byte ** >( &parms->tempCullBits ) = (byte *)_alloca16( TEMP_CULLBITS( parms->numVerts ) ); + } + + // Calculate the shadow depth bounds. + float shadowZMin = parms->lightZMin; + float shadowZMax = parms->lightZMax; + if ( parms->useShadowDepthBounds ) { + idRenderMatrix::DepthBoundsForShadowBounds( shadowZMin, shadowZMax, parms->triangleMVP, parms->triangleBounds, parms->localLightOrigin, true ); + shadowZMin = Max( shadowZMin, parms->lightZMin ); + shadowZMax = Min( shadowZMax, parms->lightZMax ); + } + + bool renderZFail = false; + int numShadowIndices = 0; + + // The shadow volume may be depth culled if either the shadow volume was culled to the view frustum or if the + // depth range of the visible part of the shadow volume is outside the depth range of the light volume. + if ( shadowZMin < shadowZMax ) { + // Check if we need to render the shadow volume with Z-fail. + // If the view is potentially inside the shadow volume bounds we may need to render with Z-fail. + if ( R_ViewPotentiallyInsideInfiniteShadowVolume( parms->triangleBounds, parms->localLightOrigin, parms->localViewOrigin, parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ) ) { + // Optionally perform a more precise test to see whether or not the view is inside the shadow volume. + if ( parms->useShadowPreciseInsideTest && parms->verts != NULL && parms->indexes != NULL ) { + renderZFail = R_ViewInsideShadowVolume( parms->tempCullBits, parms->verts, parms->numVerts, parms->indexes, parms->numIndexes, + parms->localLightOrigin, parms->localViewOrigin, parms->zNear * INSIDE_SHADOW_VOLUME_EXTRA_STRETCH ); + } else { + renderZFail = true; + } + } + + // Check if we can avoid rendering the shadow volume caps. + numShadowIndices = ( parms->forceShadowCaps || renderZFail ) ? parms->numShadowIndicesWithCaps : parms->numShadowIndicesNoCaps; + } + + // write out the number of shadow indices + if ( parms->numShadowIndices != NULL ) { + *parms->numShadowIndices = numShadowIndices; + } + // write out whether or not the shadow volume needs to be rendered with Z-Fail + if ( parms->renderZFail != NULL ) { + *parms->renderZFail = renderZFail; + } + // write out the shadow depth bounds + if ( parms->shadowZMin != NULL ) { + *parms->shadowZMin = shadowZMin; + } + if ( parms->shadowZMax != NULL ) { + *parms->shadowZMax = shadowZMax; + } + // write out the shadow volume state + if ( parms->shadowVolumeState != NULL ) { + *parms->shadowVolumeState = SHADOWVOLUME_DONE; + } +} + +REGISTER_PARALLEL_JOB( StaticShadowVolumeJob, "StaticShadowVolumeJob" ); diff --git a/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.h b/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.h new file mode 100644 index 00000000..5a616297 --- /dev/null +++ b/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __STATICSHADOWVOLUME_H__ +#define __STATICSHADOWVOLUME_H__ + +/* +================================================================================================ + +Static Shadow Volume Setup + +A static shadow is cast from a static model touching a static light. +A static shadow volume extends to infinity which allows the end caps to be omitted +when the view is outside and far enough away from the shadow volume. + +A static shadow volume is created at map load time and this job determines whether or +not the end caps need to be rendered, whether or not the shadow volume needs to be +rendered with Z-Fail, and optionally calculates the shadow volume depth bounds. + +================================================================================================ +*/ + +/* +================================================ +staticShadowVolumeParms_t +================================================ +*/ +struct staticShadowVolumeParms_t { + // input + const idShadowVert * verts; // streamed in from main memory + int numVerts; + const triIndex_t * indexes; // streamed in from main memory + int numIndexes; + int numShadowIndicesWithCaps; + int numShadowIndicesNoCaps; + idBounds triangleBounds; + idRenderMatrix triangleMVP; + idVec3 localLightOrigin; + idVec3 localViewOrigin; + float zNear; + float lightZMin; + float lightZMax; + bool forceShadowCaps; + bool useShadowPreciseInsideTest; + bool useShadowDepthBounds; + // temp + byte * tempCullBits; // temp buffer in SPU local memory + // output + int * numShadowIndices; // streamed out to main memory + int * renderZFail; // streamed out to main memory + float * shadowZMin; // streamed out to main memory + float * shadowZMax; // streamed out to main memory + volatile shadowVolumeState_t * shadowVolumeState; // streamed out to main memory + // next in chain on view entity + staticShadowVolumeParms_t * next; + int pad[3]; +}; + + +void StaticShadowVolumeJob( const staticShadowVolumeParms_t * parms ); +void StaticShadowVolume_SetupSPURSHeader( CellSpursJob128 * job, const staticShadowVolumeParms_t * parms ); + +#endif // !__STATICSHADOWVOLUME_H__ diff --git a/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume_local.h b/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume_local.h new file mode 100644 index 00000000..d8454835 --- /dev/null +++ b/neo/renderer/jobs/staticshadowvolume/StaticShadowVolume_local.h @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __STATICSHADOWVOLUME_LOCAL_H__ +#define __STATICSHADOWVOLUME_LOCAL_H__ + +#include "../../../idlib/ParallelJobList_JobHeaders.h" +#include "../../../idlib/SoftwareCache.h" + +#include "../../../idlib/math/Vector.h" +#include "../../../idlib/math/Matrix.h" +#include "../../../idlib/math/Quat.h" +#include "../../../idlib/math/Rotation.h" +#include "../../../idlib/math/Plane.h" +#include "../../../idlib/bv/Sphere.h" +#include "../../../idlib/bv/Bounds.h" +#include "../../../idlib/geometry/JointTransform.h" +#include "../../../idlib/geometry/DrawVert.h" +#include "../../../idlib/geometry/RenderMatrix.h" +#include "../ShadowShared.h" +#include "StaticShadowVolume.h" + +#endif // !__STATICSHADOWVOLUME_LOCAL_H__ diff --git a/neo/renderer/jpeg-6/jcapimin.cpp b/neo/renderer/jpeg-6/jcapimin.cpp new file mode 100644 index 00000000..68e31883 --- /dev/null +++ b/neo/renderer/jpeg-6/jcapimin.cpp @@ -0,0 +1,230 @@ +/* + * jcapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_compress( j_compress_ptr cinfo ) { + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO( cinfo, SIZEOF( struct jpeg_compress_struct ) ); + cinfo->err = err; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr( (j_common_ptr) cinfo ); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + cinfo->quant_tbl_ptrs[i] = NULL; + } + + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + cinfo->input_gamma = 1.0;/* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL void +jpeg_destroy_compress( j_compress_ptr cinfo ) { + jpeg_destroy( (j_common_ptr) cinfo );/* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_compress( j_compress_ptr cinfo ) { + jpeg_abort( (j_common_ptr) cinfo );/* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL void +jpeg_suppress_tables( j_compress_ptr cinfo, boolean suppress ) { + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + if ( ( qtbl = cinfo->quant_tbl_ptrs[i] ) != NULL ) { + qtbl->sent_table = suppress; + } + } + + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + if ( ( htbl = cinfo->dc_huff_tbl_ptrs[i] ) != NULL ) { + htbl->sent_table = suppress; + } + if ( ( htbl = cinfo->ac_huff_tbl_ptrs[i] ) != NULL ) { + htbl->sent_table = suppress; + } + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL void +jpeg_finish_compress( j_compress_ptr cinfo ) { + JDIMENSION iMCU_row; + + if ( ( cinfo->global_state == CSTATE_SCANNING ) || + ( cinfo->global_state == CSTATE_RAW_OK ) ) { + /* Terminate first pass */ + if ( cinfo->next_scanline < cinfo->image_height ) { + ERREXIT( cinfo, JERR_TOO_LITTLE_DATA ); + } + ( *cinfo->master->finish_pass )( cinfo ); + } else if ( cinfo->global_state != CSTATE_WRCOEFS ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Perform any remaining passes */ + while ( !cinfo->master->is_last_pass ) { + ( *cinfo->master->prepare_for_pass )( cinfo ); + for ( iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++ ) { + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if ( !( *cinfo->coef->compress_data )( cinfo, (JSAMPIMAGE) NULL ) ) { + ERREXIT( cinfo, JERR_CANT_SUSPEND ); + } + } + ( *cinfo->master->finish_pass )( cinfo ); + } + /* Write EOI, do final cleanup */ + ( *cinfo->marker->write_file_trailer )( cinfo ); + ( *cinfo->dest->term_destination )( cinfo ); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort( (j_common_ptr) cinfo ); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL void +jpeg_write_marker( j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen ) { + if ( ( cinfo->next_scanline != 0 ) || + ( ( cinfo->global_state != CSTATE_SCANNING ) && + ( cinfo->global_state != CSTATE_RAW_OK ) && + ( cinfo->global_state != CSTATE_WRCOEFS ) ) ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + ( *cinfo->marker->write_any_marker )( cinfo, marker, dataptr, datalen ); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL void +jpeg_write_tables( j_compress_ptr cinfo ) { + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* (Re)initialize error mgr and destination modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->dest->init_destination )( cinfo ); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer( cinfo ); + /* Write them tables! */ + ( *cinfo->marker->write_tables_only )( cinfo ); + /* And clean up. */ + ( *cinfo->dest->term_destination )( cinfo ); + /* We can use jpeg_abort to release memory. */ + jpeg_abort( (j_common_ptr) cinfo ); +} diff --git a/neo/renderer/jpeg-6/jcapistd.cpp b/neo/renderer/jpeg-6/jcapistd.cpp new file mode 100644 index 00000000..10a3900e --- /dev/null +++ b/neo/renderer/jpeg-6/jcapistd.cpp @@ -0,0 +1,167 @@ +/* + * jcapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-compression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_compress, it will end up linking in the entire compressor. + * We thus must separate this file from jcapimin.c to avoid linking the + * whole compression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL void +jpeg_start_compress( j_compress_ptr cinfo, boolean write_all_tables ) { + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + if ( write_all_tables ) { + jpeg_suppress_tables( cinfo, FALSE ); + } /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->dest->init_destination )( cinfo ); + /* Perform master selection of active modules */ + jinit_compress_master( cinfo ); + /* Set up for the first pass */ + ( *cinfo->master->prepare_for_pass )( cinfo ); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = ( cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING ); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL JDIMENSION +jpeg_write_scanlines( j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines ) { + JDIMENSION row_ctr, rows_left; + + if ( cinfo->global_state != CSTATE_SCANNING ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->next_scanline >= cinfo->image_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if ( cinfo->master->call_pass_startup ) { + ( *cinfo->master->pass_startup )( cinfo ); + } + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if ( num_lines > rows_left ) { + num_lines = rows_left; + } + + row_ctr = 0; + ( *cinfo->main->process_data )( cinfo, scanlines, &row_ctr, num_lines ); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to write raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_write_raw_data( j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines ) { + JDIMENSION lines_per_iMCU_row; + + if ( cinfo->global_state != CSTATE_RAW_OK ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->next_scanline >= cinfo->image_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + return 0; + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_raw_data. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_raw_data. + */ + if ( cinfo->master->call_pass_startup ) { + ( *cinfo->master->pass_startup )( cinfo ); + } + + /* Verify that at least one iMCU row has been passed. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE; + if ( num_lines < lines_per_iMCU_row ) { + ERREXIT( cinfo, JERR_BUFFER_SIZE ); + } + + /* Directly compress the row. */ + if ( !( *cinfo->coef->compress_data )( cinfo, data ) ) { + /* If compressor did not consume the whole row, suspend processing. */ + return 0; + } + + /* OK, we processed one iMCU row. */ + cinfo->next_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} diff --git a/neo/renderer/jpeg-6/jccoefct.cpp b/neo/renderer/jpeg-6/jccoefct.cpp new file mode 100644 index 00000000..0620a834 --- /dev/null +++ b/neo/renderer/jpeg-6/jccoefct.cpp @@ -0,0 +1,449 @@ +/* + * jccoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub;/* public fields */ + + JDIMENSION iMCU_row_num;/* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF boolean compress_data +JPP( ( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) ); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF boolean compress_first_pass +JPP( ( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) ); +METHODDEF boolean compress_output +JPP( ( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) ); +#endif + + +LOCAL void +start_iMCU_row( j_compress_ptr cinfo ) { +/* Reset within-iMCU-row counters for a new row */ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if ( cinfo->comps_in_scan > 1 ) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if ( coef->iMCU_row_num < ( cinfo->total_iMCU_rows - 1 ) ) { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row( cinfo ); + + switch ( pass_mode ) { + case JBUF_PASS_THRU: + if ( coef->whole_image[0] != NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if ( coef->whole_image[0] == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if ( coef->whole_image[0] == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF boolean +compress_data( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info * compptr; + + /* Loop to write as much as one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++ ) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + blockcnt = ( MCU_col_num < last_MCU_col ) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * DCTSIZE;/* ypos == (yoffset+yindex) * DCTSIZE */ + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + if ( ( coef->iMCU_row_num < last_iMCU_row ) || + ( yoffset + yindex < compptr->last_row_height ) ) { + ( *cinfo->fdct->forward_DCT )( cinfo, compptr, + input_buf[ci], coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt ); + if ( blockcnt < compptr->MCU_width ) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far( (void FAR *) coef->MCU_buffer[blkn + blockcnt], + ( compptr->MCU_width - blockcnt ) * SIZEOF( JBLOCK ) ); + for ( bi = blockcnt; bi < compptr->MCU_width; bi++ ) { + coef->MCU_buffer[blkn + bi][0][0] = coef->MCU_buffer[blkn + bi - 1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far( (void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF( JBLOCK ) ); + for ( bi = 0; bi < compptr->MCU_width; bi++ ) { + coef->MCU_buffer[blkn + bi][0][0] = coef->MCU_buffer[blkn - 1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += DCTSIZE; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if ( !( *cinfo->entropy->encode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row( cinfo ); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF boolean +compress_first_pass( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info * compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Align the virtual buffer for this component. */ + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE ); + /* Count non-dummy DCT block rows in this iMCU row. */ + if ( coef->iMCU_row_num < last_iMCU_row ) { + block_rows = compptr->v_samp_factor; + } else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( block_rows == 0 ) { + block_rows = compptr->v_samp_factor; + } + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) ( blocks_across % h_samp_factor ); + if ( ndummy > 0 ) { + ndummy = h_samp_factor - ndummy; + } + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for ( block_row = 0; block_row < block_rows; block_row++ ) { + thisblockrow = buffer[block_row]; + ( *cinfo->fdct->forward_DCT )( cinfo, compptr, + input_buf[ci], thisblockrow, + (JDIMENSION) ( block_row * DCTSIZE ), + (JDIMENSION) 0, blocks_across ); + if ( ndummy > 0 ) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across;/* => first dummy block */ + jzero_far( (void FAR *) thisblockrow, ndummy * SIZEOF( JBLOCK ) ); + lastDC = thisblockrow[-1][0]; + for ( bi = 0; bi < ndummy; bi++ ) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if ( coef->iMCU_row_num == last_iMCU_row ) { + blocks_across += ndummy;/* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for ( block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++ ) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row - 1]; + jzero_far( (void FAR *) thisblockrow, + (size_t) ( blocks_across * SIZEOF( JBLOCK ) ) ); + for ( MCUindex = 0; MCUindex < MCUs_across; MCUindex++ ) { + lastDC = lastblockrow[h_samp_factor - 1][0]; + for ( bi = 0; bi < h_samp_factor; bi++ ) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor;/* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output( cinfo, input_buf ); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info * compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE ); + } + + /* Loop to process one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++ ) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + buffer_ptr = buffer[ci][yindex + yoffset] + start_col; + for ( xindex = 0; xindex < compptr->MCU_width; xindex++ ) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if ( !( *cinfo->entropy->encode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row( cinfo ); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_c_coef_controller( j_compress_ptr cinfo, boolean need_full_buffer ) { + my_coef_ptr coef; + + coef = (my_coef_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_coef_controller ) ); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if ( need_full_buffer ) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info * compptr; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + coef->whole_image[ci] = ( *cinfo->mem->request_virt_barray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up( (long) compptr->width_in_blocks, + (long) compptr->h_samp_factor ), + (JDIMENSION) jround_up( (long) compptr->height_in_blocks, + (long) compptr->v_samp_factor ), + (JDIMENSION) compptr->v_samp_factor ); + } +#else + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + ( *cinfo->mem->alloc_large )( (j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + for ( i = 0; i < C_MAX_BLOCKS_IN_MCU; i++ ) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL;/* flag for no virtual arrays */ + } +} diff --git a/neo/renderer/jpeg-6/jccolor.cpp b/neo/renderer/jpeg-6/jccolor.cpp new file mode 100644 index 00000000..3184b71b --- /dev/null +++ b/neo/renderer/jpeg-6/jccolor.cpp @@ -0,0 +1,467 @@ +/* + * jccolor.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub;/* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ( (INT32) CENTERJSAMPLE << SCALEBITS ) +#define ONE_HALF ( (INT32) 1 << ( SCALEBITS - 1 ) ) +#define FIX( x ) ( (INT32) ( ( x ) * ( 1L << SCALEBITS ) + 0.5 ) ) + +/* We allocate one big table and divide it up into eight parts, instead of + * doing eight alloc_small requests. This lets us use a single table base + * address, which can be held in a register in the inner loops on many + * machines (more than can hold all eight addresses, anyway). + */ + +#define R_Y_OFF 0 /* offset to R => Y section */ +#define G_Y_OFF ( 1 * ( MAXJSAMPLE + 1 ) ) /* offset to G => Y section */ +#define B_Y_OFF ( 2 * ( MAXJSAMPLE + 1 ) ) /* etc. */ +#define R_CB_OFF ( 3 * ( MAXJSAMPLE + 1 ) ) +#define G_CB_OFF ( 4 * ( MAXJSAMPLE + 1 ) ) +#define B_CB_OFF ( 5 * ( MAXJSAMPLE + 1 ) ) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF ( 6 * ( MAXJSAMPLE + 1 ) ) +#define B_CR_OFF ( 7 * ( MAXJSAMPLE + 1 ) ) +#define TABLE_SIZE ( 8 * ( MAXJSAMPLE + 1 ) ) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF void +rgb_ycc_start( j_compress_ptr cinfo ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( TABLE_SIZE * SIZEOF( INT32 ) ) ); + + for ( i = 0; i <= MAXJSAMPLE; i++ ) { + rgb_ycc_tab[i + R_Y_OFF] = FIX( 0.29900 ) * i; + rgb_ycc_tab[i + G_Y_OFF] = FIX( 0.58700 ) * i; + rgb_ycc_tab[i + B_Y_OFF] = FIX( 0.11400 ) * i + ONE_HALF; + rgb_ycc_tab[i + R_CB_OFF] = ( -FIX( 0.16874 ) ) * i; + rgb_ycc_tab[i + G_CB_OFF] = ( -FIX( 0.33126 ) ) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i + B_CB_OFF] = FIX( 0.50000 ) * i + CBCR_OFFSET + ONE_HALF - 1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; + */ + rgb_ycc_tab[i + G_CR_OFF] = ( -FIX( 0.41869 ) ) * i; + rgb_ycc_tab[i + B_CR_OFF] = ( -FIX( 0.08131 ) ) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +rgb_ycc_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + r = GETJSAMPLE( inptr[RGB_RED] ); + g = GETJSAMPLE( inptr[RGB_GREEN] ); + b = GETJSAMPLE( inptr[RGB_BLUE] ); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ( ( ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] + ctab[b + B_Y_OFF] ) + >> SCALEBITS ); + /* Cb */ + outptr1[col] = (JSAMPLE) + ( ( ctab[r + R_CB_OFF] + ctab[g + G_CB_OFF] + ctab[b + B_CB_OFF] ) + >> SCALEBITS ); + /* Cr */ + outptr2[col] = (JSAMPLE) + ( ( ctab[r + R_CR_OFF] + ctab[g + G_CR_OFF] + ctab[b + B_CR_OFF] ) + >> SCALEBITS ); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF void +rgb_gray_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + r = GETJSAMPLE( inptr[RGB_RED] ); + g = GETJSAMPLE( inptr[RGB_GREEN] ); + b = GETJSAMPLE( inptr[RGB_BLUE] ); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ( ( ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] + ctab[b + B_Y_OFF] ) + >> SCALEBITS ); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF void +cmyk_ycck_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + r = MAXJSAMPLE - GETJSAMPLE( inptr[0] ); + g = MAXJSAMPLE - GETJSAMPLE( inptr[1] ); + b = MAXJSAMPLE - GETJSAMPLE( inptr[2] ); + /* K passes through as-is */ + outptr3[col] = inptr[3];/* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ( ( ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] + ctab[b + B_Y_OFF] ) + >> SCALEBITS ); + /* Cb */ + outptr1[col] = (JSAMPLE) + ( ( ctab[r + R_CB_OFF] + ctab[g + G_CB_OFF] + ctab[b + B_CB_OFF] ) + >> SCALEBITS ); + /* Cr */ + outptr2[col] = (JSAMPLE) + ( ( ctab[r + R_CR_OFF] + ctab[g + G_CR_OFF] + ctab[b + B_CR_OFF] ) + >> SCALEBITS ); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF void +grayscale_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while ( --num_rows >= 0 ) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for ( col = 0; col < num_cols; col++ ) { + outptr[col] = inptr[0];/* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF void +null_convert( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows ) { + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while ( --num_rows >= 0 ) { + /* It seems fastest to make a separate pass for each component. */ + for ( ci = 0; ci < nc; ci++ ) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for ( col = 0; col < num_cols; col++ ) { + outptr[col] = inptr[ci];/* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +null_method( j_compress_ptr cinfo ) { + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL void +jinit_color_converter( j_compress_ptr cinfo ) { + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_color_converter ) ); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch ( cinfo->in_color_space ) { + case JCS_GRAYSCALE: + if ( cinfo->input_components != 1 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if ( cinfo->input_components != RGB_PIXELSIZE ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if ( cinfo->input_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + + case JCS_CMYK: + case JCS_YCCK: + if ( cinfo->input_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + + default: /* JCS_UNKNOWN can be anything */ + if ( cinfo->input_components < 1 ) { + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch ( cinfo->jpeg_color_space ) { + case JCS_GRAYSCALE: + if ( cinfo->num_components != 1 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_GRAYSCALE ) { + cconvert->pub.color_convert = grayscale_convert; + } else if ( cinfo->in_color_space == JCS_RGB ) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if ( cinfo->in_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = grayscale_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_RGB: + if ( cinfo->num_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( ( cinfo->in_color_space == JCS_RGB ) && ( RGB_PIXELSIZE == 3 ) ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_YCbCr: + if ( cinfo->num_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_RGB ) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if ( cinfo->in_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_CMYK: + if ( cinfo->num_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_CMYK ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_YCCK: + if ( cinfo->num_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + if ( cinfo->in_color_space == JCS_CMYK ) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if ( cinfo->in_color_space == JCS_YCCK ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if ( ( cinfo->jpeg_color_space != cinfo->in_color_space ) || + ( cinfo->num_components != cinfo->input_components ) ) { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/neo/renderer/jpeg-6/jcdctmgr.cpp b/neo/renderer/jpeg-6/jcdctmgr.cpp new file mode 100644 index 00000000..662cfe60 --- /dev/null +++ b/neo/renderer/jpeg-6/jcdctmgr.cpp @@ -0,0 +1,385 @@ +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub;/* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order; note that this must + * be converted from the zigzag order of the quantization tables. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF void +start_pass_fdctmgr( j_compress_ptr cinfo ) { + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info * compptr; + JQUANT_TBL * qtbl; + //DCTELEM * dtbl; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if ( ( qtblno < 0 ) || ( qtblno >= NUM_QUANT_TBLS ) || + ( cinfo->quant_tbl_ptrs[qtblno] == NULL ) ) { + ERREXIT1( cinfo, JERR_NO_QUANT_TABLE, qtblno ); + } + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch ( cinfo->dct_method ) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if ( fdct->divisors[qtblno] == NULL ) { + fdct->divisors[qtblno] = (DCTELEM *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF( DCTELEM ) ); + } + dtbl = fdct->divisors[qtblno]; + for ( i = 0; i < DCTSIZE2; i++ ) { + dtbl[i] = ( (DCTELEM) qtbl->quantval[jpeg_zigzag_order[i]] ) << 3; + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits: in natural order */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if ( fdct->divisors[qtblno] == NULL ) { + fdct->divisors[qtblno] = (DCTELEM *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF( DCTELEM ) ); + } + dtbl = fdct->divisors[qtblno]; + for ( i = 0; i < DCTSIZE2; i++ ) { + dtbl[i] = (DCTELEM) + DESCALE( MULTIPLY16V16( (INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i] ), + CONST_BITS - 3 ); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if ( fdct->float_divisors[qtblno] == NULL ) { + fdct->float_divisors[qtblno] = (FAST_FLOAT *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF( FAST_FLOAT ) ); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for ( row = 0; row < DCTSIZE; row++ ) { + for ( col = 0; col < DCTSIZE; col++ ) { + fdtbl[i] = (FAST_FLOAT) + ( 1.0 / ( ( (double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col] * 8.0 ) ) ); + i++; + } + } + } + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + } +} + + +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +METHODDEF void +forward_DCT( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks ) { +/* This version is used for integer DCT implementations. */ +/* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2];/* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row;/* fold in the vertical offset once */ + + for ( bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE ) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register DCTELEM * workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for ( elemr = 0; elemr < DCTSIZE; elemr++ ) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; +#else + { register int elemc; + for ( elemc = DCTSIZE; elemc > 0; elemc-- ) { + *workspaceptr++ = GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE; + } + } +#endif + } + } + + /* Perform the DCT */ + ( *do_dct )( workspace ); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for ( i = 0; i < DCTSIZE2; i++ ) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY( a, b ) a /= b +#else +#define DIVIDE_BY(a,b) if (a >= b) a /= b; else a = 0 +#endif + if ( temp < 0 ) { + temp = -temp; + temp += qval >> 1;/* for rounding */ + DIVIDE_BY( temp, qval ); + temp = -temp; + } else { + temp += qval >> 1;/* for rounding */ + DIVIDE_BY( temp, qval ); + } + output_ptr[i] = (JCOEF) temp; + } + } + } +} + + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF void +forward_DCT_float( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks ) { +/* This version is used for floating-point DCT implementations. */ +/* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2];/* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row;/* fold in the vertical offset once */ + + for ( bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE ) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register FAST_FLOAT * workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for ( elemr = 0; elemr < DCTSIZE; elemr++ ) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + *workspaceptr++ = (FAST_FLOAT)( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); +#else + { register int elemc; + for ( elemc = DCTSIZE; elemc > 0; elemc-- ) { + *workspaceptr++ = (FAST_FLOAT) + ( GETJSAMPLE( *elemptr++ ) - CENTERJSAMPLE ); + } + } +#endif + } + } + + /* Perform the DCT */ + ( *do_dct )( workspace ); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for ( i = 0; i < DCTSIZE2; i++ ) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ( (int) ( temp + (FAST_FLOAT) 16384.5 ) - 16384 ); + } + } + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize FDCT manager. + */ + +GLOBAL void +jinit_forward_dct( j_compress_ptr cinfo ) { + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_fdct_controller ) ); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + switch ( cinfo->dct_method ) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_islow; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_ifast; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->pub.forward_DCT = forward_DCT_float; + fdct->do_float_dct = jpeg_fdct_float; + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + + /* Mark divisor tables unallocated */ + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/neo/renderer/jpeg-6/jchuff.cpp b/neo/renderer/jpeg-6/jchuff.cpp new file mode 100644 index 00000000..25873bb3 --- /dev/null +++ b/neo/renderer/jpeg-6/jchuff.cpp @@ -0,0 +1,868 @@ +/* + * jchuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jcphuff.c */ + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE( dest, src ) ( ( dest ) = ( src ) ) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE( dest, src ) \ + ( ( dest ).put_buffer = ( src ).put_buffer, \ + ( dest ).put_bits = ( src ).put_bits, \ + ( dest ).last_dc_val[0] = ( src ).last_dc_val[0], \ + ( dest ).last_dc_val[1] = ( src ).last_dc_val[1], \ + ( dest ).last_dc_val[2] = ( src ).last_dc_val[2], \ + ( dest ).last_dc_val[3] = ( src ).last_dc_val[3] ) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub;/* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go;/* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + +#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; +#endif +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU. + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + + +/* Forward declarations */ +METHODDEF boolean encode_mcu_huff JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF void finish_pass_huff JPP( (j_compress_ptr cinfo) ); +#ifdef ENTROPY_OPT_SUPPORTED +METHODDEF boolean encode_mcu_gather JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF void finish_pass_gather JPP( (j_compress_ptr cinfo) ); +#endif + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF void +start_pass_huff( j_compress_ptr cinfo, boolean gather_statistics ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + if ( gather_statistics ) { +#ifdef ENTROPY_OPT_SUPPORTED + entropy->pub.encode_mcu = encode_mcu_gather; + entropy->pub.finish_pass = finish_pass_gather; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + entropy->pub.encode_mcu = encode_mcu_huff; + entropy->pub.finish_pass = finish_pass_huff; + } + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if ( ( dctbl < 0 ) || ( dctbl >= NUM_HUFF_TBLS ) || + ( ( cinfo->dc_huff_tbl_ptrs[dctbl] == NULL ) && ( !gather_statistics ) ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, dctbl ); + } + if ( ( actbl < 0 ) || ( actbl >= NUM_HUFF_TBLS ) || + ( ( cinfo->ac_huff_tbl_ptrs[actbl] == NULL ) && ( !gather_statistics ) ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, actbl ); + } + if ( gather_statistics ) { +#ifdef ENTROPY_OPT_SUPPORTED + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if ( entropy->dc_count_ptrs[dctbl] == NULL ) { + entropy->dc_count_ptrs[dctbl] = (long *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF( long ) ); + } + MEMZERO( entropy->dc_count_ptrs[dctbl], 257 * SIZEOF( long ) ); + if ( entropy->ac_count_ptrs[actbl] == NULL ) { + entropy->ac_count_ptrs[actbl] = (long *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF( long ) ); + } + MEMZERO( entropy->ac_count_ptrs[actbl], 257 * SIZEOF( long ) ); +#endif + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + &entropy->dc_derived_tbls[dctbl] ); + jpeg_make_c_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + &entropy->ac_derived_tbls[actbl] ); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_make_c_derived_tbl( j_compress_ptr cinfo, JHUFF_TBL * htbl, + c_derived_tbl ** pdtbl ) { + c_derived_tbl * dtbl; + int p, i, l, lastp, si; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if ( *pdtbl == NULL ) { + *pdtbl = (c_derived_tbl *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( c_derived_tbl ) ); + } + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for ( l = 1; l <= 16; l++ ) { + for ( i = 1; i <= (int) htbl->bits[l]; i++ ) { + huffsize[p++] = (char) l; + } + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while ( huffsize[p] ) { + while ( ( (int) huffsize[p] ) == si ) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set any codeless symbols to have code length 0; + * this allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO( dtbl->ehufsi, SIZEOF( dtbl->ehufsi ) ); + + for ( p = 0; p < lastp; p++ ) { + dtbl->ehufco[htbl->huffval[p]] = huffcode[p]; + dtbl->ehufsi[htbl->huffval[p]] = huffsize[p]; + } +} + + +/* Outputting bytes to the file */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte( state, val, action ) \ + { *( state )->next_output_byte++ = (JOCTET) ( val ); \ + if ( -- ( state )->free_in_buffer == 0 ) { \ + if ( !dump_buffer( state ) ) \ + { action; } } } + + +LOCAL boolean +dump_buffer( working_state * state ) { +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if ( !( *dest->empty_output_buffer )( state->cinfo ) ) { + return FALSE; + } + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL boolean +emit_bits( working_state * state, unsigned int code, int size ) { +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +/* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if ( size == 0 ) { + ERREXIT( state->cinfo, JERR_HUFF_MISSING_CODE ); + } + + put_buffer &= ( ( (INT32) 1 ) << size ) - 1;/* mask off any extra bits in code */ + + put_bits += size; /* new number of bits in buffer */ + + put_buffer <<= 24 - put_bits;/* align incoming bits */ + + put_buffer |= state->cur.put_buffer;/* and merge with old buffer contents */ + + while ( put_bits >= 8 ) { + int c = (int) ( ( put_buffer >> 16 ) & 0xFF ); + + emit_byte( state, c, return FALSE ); + if ( c == 0xFF ) { /* need to stuff a zero byte? */ + emit_byte( state, 0, return FALSE ); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer;/* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +LOCAL boolean +flush_bits( working_state * state ) { + if ( !emit_bits( state, 0x7F, 7 ) ) {/* fill any partial byte with ones */ + return FALSE; + } + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL boolean +encode_one_block( working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl * dctbl, c_derived_tbl * actbl ) { + register int temp, temp2; + register int nbits; + register int k, r, i; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if ( temp < 0 ) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while ( temp ) { + nbits++; + temp >>= 1; + } + + /* Emit the Huffman-coded symbol for the number of bits */ + if ( !emit_bits( state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits] ) ) { + return FALSE; + } + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if ( nbits ) { /* emit_bits rejects calls with size 0 */ + if ( !emit_bits( state, (unsigned int) temp2, nbits ) ) { + return FALSE; + } + } + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for ( k = 1; k < DCTSIZE2; k++ ) { + if ( ( temp = block[jpeg_natural_order[k]] ) == 0 ) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while ( r > 15 ) { + if ( !emit_bits( state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0] ) ) { + return FALSE; + } + r -= 16; + } + + temp2 = temp; + if ( temp < 0 ) { + temp = -temp;/* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ( ( temp >>= 1 ) ) { + nbits++; + } + + /* Emit Huffman symbol for run length / number of bits */ + i = ( r << 4 ) + nbits; + if ( !emit_bits( state, actbl->ehufco[i], actbl->ehufsi[i] ) ) { + return FALSE; + } + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if ( !emit_bits( state, (unsigned int) temp2, nbits ) ) { + return FALSE; + } + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if ( r > 0 ) { + if ( !emit_bits( state, actbl->ehufco[0], actbl->ehufsi[0] ) ) { + return FALSE; + } + } + + return TRUE; +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL boolean +emit_restart( working_state * state, int restart_num ) { + int ci; + + if ( !flush_bits( state ) ) { + return FALSE; + } + + emit_byte( state, 0xFF, return FALSE ); + emit_byte( state, JPEG_RST0 + restart_num, return FALSE ); + + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < state->cinfo->comps_in_scan; ci++ ) { + state->cur.last_dc_val[ci] = 0; + } + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF boolean +encode_mcu_huff( j_compress_ptr cinfo, JBLOCKROW * MCU_data ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE( state.cur, entropy->saved ); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !emit_restart( &state, entropy->next_restart_num ) ) { + return FALSE; + } + } + } + + /* Encode the MCU data blocks */ + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if ( !encode_one_block( &state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no] ) ) { + return FALSE; + } + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE( entropy->saved, state.cur ); + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF void +finish_pass_huff( j_compress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE( state.cur, entropy->saved ); + state.cinfo = cinfo; + + /* Flush out the last data */ + if ( !flush_bits( &state ) ) { + ERREXIT( cinfo, JERR_CANT_SUSPEND ); + } + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE( entropy->saved, state.cur ); +} + + +/* + * Huffman coding optimization. + * + * This actually is optimization, in the sense that we find the best possible + * Huffman table(s) for the given data. We first scan the supplied data and + * count the number of uses of each symbol that is to be Huffman-coded. + * (This process must agree with the code above.) Then we build an + * optimal Huffman coding tree for the observed counts. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in the JPEG spec. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * since the symbols involved are infrequently used, it's not clear that + * going to extra trouble is worthwhile. + */ + +#ifdef ENTROPY_OPT_SUPPORTED + + +/* Process a single block's worth of coefficients */ + +LOCAL void +htest_one_block( JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[] ) { + register int temp; + register int nbits; + register int k, r; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if ( temp < 0 ) { + temp = -temp; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while ( temp ) { + nbits++; + temp >>= 1; + } + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for ( k = 1; k < DCTSIZE2; k++ ) { + if ( ( temp = block[jpeg_natural_order[k]] ) == 0 ) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while ( r > 15 ) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if ( temp < 0 ) { + temp = -temp; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ( ( temp >>= 1 ) ) { + nbits++; + } + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[( r << 4 ) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if ( r > 0 ) { + ac_counts[0]++; + } +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF boolean +encode_mcu_gather( j_compress_ptr cinfo, JBLOCKROW * MCU_data ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + entropy->saved.last_dc_val[ci] = 0; + } + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block( MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no] ); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the optimal coding for the given counts, fill htbl. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_gen_optimal_table( j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[] ) { +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN + 1];/* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO( bits, SIZEOF( bits ) ); + MEMZERO( codesize, SIZEOF( codesize ) ); + for ( i = 0; i < 257; i++ ) { + others[i] = -1; + } /* init links to empty */ + + freq[256] = 1; /* make sure there is a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;; ) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for ( i = 0; i <= 256; i++ ) { + if ( ( freq[i] ) && ( freq[i] <= v ) ) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for ( i = 0; i <= 256; i++ ) { + if ( ( freq[i] ) && ( freq[i] <= v ) && ( i != c1 ) ) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if ( c2 < 0 ) { + break; + } + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while ( others[c1] >= 0 ) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while ( others[c2] >= 0 ) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for ( i = 0; i <= 256; i++ ) { + if ( codesize[i] ) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if ( codesize[i] > MAX_CLEN ) { + ERREXIT( cinfo, JERR_HUFF_CLEN_OVERFLOW ); + } + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for ( i = MAX_CLEN; i > 16; i-- ) { + while ( bits[i] > 0 ) { + j = i - 2; /* find length of new prefix to be used */ + while ( bits[j] == 0 ) { + j--; + } + + bits[i] -= 2;/* remove two symbols */ + bits[i - 1]++;/* one goes in this length */ + bits[j + 1] += 2;/* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while ( bits[i] == 0 ) {/* find largest codelength still in use */ + i--; + } + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY( htbl->bits, bits, SIZEOF( htbl->bits ) ); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for ( i = 1; i <= MAX_CLEN; i++ ) { + for ( j = 0; j <= 255; j++ ) { + if ( codesize[j] == i ) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather( j_compress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + JHUFF_TBL ** htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO( did_dc, SIZEOF( did_dc ) ); + MEMZERO( did_ac, SIZEOF( did_ac ) ); + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if ( !did_dc[dctbl] ) { + htblptr = &cinfo->dc_huff_tbl_ptrs[dctbl]; + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + jpeg_gen_optimal_table( cinfo, *htblptr, entropy->dc_count_ptrs[dctbl] ); + did_dc[dctbl] = TRUE; + } + if ( !did_ac[actbl] ) { + htblptr = &cinfo->ac_huff_tbl_ptrs[actbl]; + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + jpeg_gen_optimal_table( cinfo, *htblptr, entropy->ac_count_ptrs[actbl] ); + did_ac[actbl] = TRUE; + } + } +} + + +#endif /* ENTROPY_OPT_SUPPORTED */ + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL void +jinit_huff_encoder( j_compress_ptr cinfo ) { + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( huff_entropy_encoder ) ); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; +#ifdef ENTROPY_OPT_SUPPORTED + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; +#endif + } +} diff --git a/neo/renderer/jpeg-6/jchuff.h b/neo/renderer/jpeg-6/jchuff.h new file mode 100644 index 00000000..f43d571d --- /dev/null +++ b/neo/renderer/jpeg-6/jchuff.h @@ -0,0 +1,34 @@ +/* + * jchuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN void jpeg_make_c_derived_tbl JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, c_derived_tbl ** pdtbl)); + +/* Generate an optimal table definition given the specified counts */ +EXTERN void jpeg_gen_optimal_table JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, long freq[])); diff --git a/neo/renderer/jpeg-6/jcinit.cpp b/neo/renderer/jpeg-6/jcinit.cpp new file mode 100644 index 00000000..e71aebe2 --- /dev/null +++ b/neo/renderer/jpeg-6/jcinit.cpp @@ -0,0 +1,72 @@ +/* + * jcinit.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL void +jinit_compress_master( j_compress_ptr cinfo ) { + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control( cinfo, FALSE /* full compression */ ); + + /* Preprocessing */ + if ( !cinfo->raw_data_in ) { + jinit_color_converter( cinfo ); + jinit_downsampler( cinfo ); + jinit_c_prep_controller( cinfo, FALSE /* never need full buffer here */ ); + } + /* Forward DCT */ + jinit_forward_dct( cinfo ); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_encoder( cinfo ); + } + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller( cinfo, + ( cinfo->num_scans > 1 || cinfo->optimize_coding ) ); + jinit_c_main_controller( cinfo, FALSE /* never need full buffer here */ ); + + jinit_marker_writer( cinfo ); + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + ( *cinfo->marker->write_file_header )( cinfo ); +} diff --git a/neo/renderer/jpeg-6/jcmainct.cpp b/neo/renderer/jpeg-6/jcmainct.cpp new file mode 100644 index 00000000..6fc46983 --- /dev/null +++ b/neo/renderer/jpeg-6/jcmainct.cpp @@ -0,0 +1,296 @@ +/* + * jcmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub;/* public fields */ + + JDIMENSION cur_iMCU_row;/* number of current iMCU row */ + JDIMENSION rowgroup_ctr;/* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF void process_data_simple_main +JPP( ( j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION * in_row_ctr, JDIMENSION in_rows_avail ) ); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF void process_data_buffer_main +JPP( ( j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION * in_row_ctr, JDIMENSION in_rows_avail ) ); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if ( cinfo->raw_data_in ) { + return; + } + + main->cur_iMCU_row = 0; /* initialize counters */ + main->rowgroup_ctr = 0; + main->suspended = FALSE; + main->pass_mode = pass_mode;/* save mode for use by process_data */ + + switch ( pass_mode ) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if ( main->whole_image[0] != NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } +#endif + main->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if ( main->whole_image[0] == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + main->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF void +process_data_simple_main( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION * in_row_ctr, + JDIMENSION in_rows_avail ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + + while ( main->cur_iMCU_row < cinfo->total_iMCU_rows ) { + /* Read input data if we haven't filled the main buffer yet */ + if ( main->rowgroup_ctr < DCTSIZE ) { + ( *cinfo->prep->pre_process_data )( cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE ); + } + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if ( main->rowgroup_ctr != DCTSIZE ) { + return; + } + + /* Send the completed row to the compressor */ + if ( !( *cinfo->coef->compress_data )( cinfo, main->buffer ) ) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if ( !main->suspended ) { + ( *in_row_ctr )--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if ( main->suspended ) { + ( *in_row_ctr )++; + main->suspended = FALSE; + } + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF void +process_data_buffer_main( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION * in_row_ctr, + JDIMENSION in_rows_avail ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info * compptr; + boolean writing = ( main->pass_mode != JBUF_CRANK_DEST ); + + while ( main->cur_iMCU_row < cinfo->total_iMCU_rows ) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if ( main->rowgroup_ctr == 0 ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + main->buffer[ci] = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, main->whole_image[ci], + main->cur_iMCU_row * ( compptr->v_samp_factor * DCTSIZE ), + (JDIMENSION) ( compptr->v_samp_factor * DCTSIZE ), writing ); + } + /* In a read pass, pretend we just read some source data. */ + if ( !writing ) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if ( writing ) { + ( *cinfo->prep->pre_process_data )( cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE ); + /* Return to application if we need more data to fill the iMCU row. */ + if ( main->rowgroup_ctr < DCTSIZE ) { + return; + } + } + + /* Emit data, unless this is a sink-only pass. */ + if ( main->pass_mode != JBUF_SAVE_SOURCE ) { + if ( !( *cinfo->coef->compress_data )( cinfo, main->buffer ) ) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if ( !main->suspended ) { + ( *in_row_ctr )--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if ( main->suspended ) { + ( *in_row_ctr )++; + main->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_c_main_controller( j_compress_ptr cinfo, boolean need_full_buffer ) { + my_main_ptr main; + int ci; + jpeg_component_info * compptr; + + main = (my_main_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_main_controller ) ); + cinfo->main = (struct jpeg_c_main_controller *) main; + main->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if ( cinfo->raw_data_in ) { + return; + } + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if ( need_full_buffer ) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + main->whole_image[ci] = ( *cinfo->mem->request_virt_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) jround_up( (long) compptr->height_in_blocks, + (long) compptr->v_samp_factor ) * DCTSIZE, + (JDIMENSION) ( compptr->v_samp_factor * DCTSIZE ) ); + } +#else + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + main->whole_image[0] = NULL;/* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + main->buffer[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) ( compptr->v_samp_factor * DCTSIZE ) ); + } + } +} diff --git a/neo/renderer/jpeg-6/jcmarker.cpp b/neo/renderer/jpeg-6/jcmarker.cpp new file mode 100644 index 00000000..28d23e45 --- /dev/null +++ b/neo/renderer/jpeg-6/jcmarker.cpp @@ -0,0 +1,644 @@ +/* + * jcmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL void +emit_byte( j_compress_ptr cinfo, int val ) { +/* Emit a byte */ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *( dest->next_output_byte )++ = (JOCTET) val; + if ( --dest->free_in_buffer == 0 ) { + if ( !( *dest->empty_output_buffer )( cinfo ) ) { + ERREXIT( cinfo, JERR_CANT_SUSPEND ); + } + } +} + + +LOCAL void +emit_marker( j_compress_ptr cinfo, JPEG_MARKER mark ) { +/* Emit a marker code */ + emit_byte( cinfo, 0xFF ); + emit_byte( cinfo, (int) mark ); +} + + +LOCAL void +emit_2bytes( j_compress_ptr cinfo, int value ) { +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ + emit_byte( cinfo, ( value >> 8 ) & 0xFF ); + emit_byte( cinfo, value & 0xFF ); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL int +emit_dqt( j_compress_ptr cinfo, int index ) { +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if ( qtbl == NULL ) { + ERREXIT1( cinfo, JERR_NO_QUANT_TABLE, index ); + } + + prec = 0; + for ( i = 0; i < DCTSIZE2; i++ ) { + if ( qtbl->quantval[i] > 255 ) { + prec = 1; + } + } + + if ( !qtbl->sent_table ) { + emit_marker( cinfo, M_DQT ); + + emit_2bytes( cinfo, prec ? DCTSIZE2 * 2 + 1 + 2 : DCTSIZE2 + 1 + 2 ); + + emit_byte( cinfo, index + ( prec << 4 ) ); + + for ( i = 0; i < DCTSIZE2; i++ ) { + if ( prec ) { + emit_byte( cinfo, qtbl->quantval[i] >> 8 ); + } + emit_byte( cinfo, qtbl->quantval[i] & 0xFF ); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL void +emit_dht( j_compress_ptr cinfo, int index, boolean is_ac ) { +/* Emit a DHT marker */ + JHUFF_TBL * htbl; + int length, i; + + if ( is_ac ) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if ( htbl == NULL ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, index ); + } + + if ( !htbl->sent_table ) { + emit_marker( cinfo, M_DHT ); + + length = 0; + for ( i = 1; i <= 16; i++ ) { + length += htbl->bits[i]; + } + + emit_2bytes( cinfo, length + 2 + 1 + 16 ); + emit_byte( cinfo, index ); + + for ( i = 1; i <= 16; i++ ) { + emit_byte( cinfo, htbl->bits[i] ); + } + + for ( i = 0; i < length; i++ ) { + emit_byte( cinfo, htbl->huffval[i] ); + } + + htbl->sent_table = TRUE; + } +} + + +LOCAL void +emit_dac( j_compress_ptr cinfo ) { +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info * compptr; + + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + dc_in_use[i] = ac_in_use[i] = 0; + } + + for ( i = 0; i < cinfo->comps_in_scan; i++ ) { + compptr = cinfo->cur_comp_info[i]; + dc_in_use[compptr->dc_tbl_no] = 1; + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + length += dc_in_use[i] + ac_in_use[i]; + } + + emit_marker( cinfo, M_DAC ); + + emit_2bytes( cinfo, length * 2 + 2 ); + + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + if ( dc_in_use[i] ) { + emit_byte( cinfo, i ); + emit_byte( cinfo, cinfo->arith_dc_L[i] + ( cinfo->arith_dc_U[i] << 4 ) ); + } + if ( ac_in_use[i] ) { + emit_byte( cinfo, i + 0x10 ); + emit_byte( cinfo, cinfo->arith_ac_K[i] ); + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL void +emit_dri( j_compress_ptr cinfo ) { +/* Emit a DRI marker */ + emit_marker( cinfo, M_DRI ); + + emit_2bytes( cinfo, 4 );/* fixed length */ + + emit_2bytes( cinfo, (int) cinfo->restart_interval ); +} + + +LOCAL void +emit_sof( j_compress_ptr cinfo, JPEG_MARKER code ) { +/* Emit a SOF marker */ + int ci; + jpeg_component_info * compptr; + + emit_marker( cinfo, code ); + + emit_2bytes( cinfo, 3 * cinfo->num_components + 2 + 5 + 1 );/* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ( ( (long) cinfo->image_height > 65535L ) || + ( (long) cinfo->image_width > 65535L ) ) { + ERREXIT1( cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535 ); + } + + emit_byte( cinfo, cinfo->data_precision ); + emit_2bytes( cinfo, (int) cinfo->image_height ); + emit_2bytes( cinfo, (int) cinfo->image_width ); + + emit_byte( cinfo, cinfo->num_components ); + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + emit_byte( cinfo, compptr->component_id ); + emit_byte( cinfo, ( compptr->h_samp_factor << 4 ) + compptr->v_samp_factor ); + emit_byte( cinfo, compptr->quant_tbl_no ); + } +} + + +LOCAL void +emit_sos( j_compress_ptr cinfo ) { +/* Emit a SOS marker */ + int i, td, ta; + jpeg_component_info * compptr; + + emit_marker( cinfo, M_SOS ); + + emit_2bytes( cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3 );/* length */ + + emit_byte( cinfo, cinfo->comps_in_scan ); + + for ( i = 0; i < cinfo->comps_in_scan; i++ ) { + compptr = cinfo->cur_comp_info[i]; + emit_byte( cinfo, compptr->component_id ); + td = compptr->dc_tbl_no; + ta = compptr->ac_tbl_no; + if ( cinfo->progressive_mode ) { + /* Progressive mode: only DC or only AC tables are used in one scan; + * furthermore, Huffman coding of DC refinement uses no table at all. + * We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + if ( cinfo->Ss == 0 ) { + ta = 0;/* DC scan */ + if ( ( cinfo->Ah != 0 ) && ( !cinfo->arith_code ) ) { + td = 0; + } /* no DC table either */ + } else { + td = 0;/* AC scan */ + } + } + emit_byte( cinfo, ( td << 4 ) + ta ); + } + + emit_byte( cinfo, cinfo->Ss ); + emit_byte( cinfo, cinfo->Se ); + emit_byte( cinfo, ( cinfo->Ah << 4 ) + cinfo->Al ); +} + + +LOCAL void +emit_jfif_app0( j_compress_ptr cinfo ) { +/* Emit a JFIF-compliant APP0 marker */ +/* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - 0x01, 0x01) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker( cinfo, M_APP0 ); + + emit_2bytes( cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1 );/* length */ + + emit_byte( cinfo, 0x4A );/* Identifier: ASCII "JFIF" */ + emit_byte( cinfo, 0x46 ); + emit_byte( cinfo, 0x49 ); + emit_byte( cinfo, 0x46 ); + emit_byte( cinfo, 0 ); + /* We currently emit version code 1.01 since we use no 1.02 features. + * This may avoid complaints from some older decoders. + */ + emit_byte( cinfo, 1 ); /* Major version */ + emit_byte( cinfo, 1 ); /* Minor version */ + emit_byte( cinfo, cinfo->density_unit );/* Pixel size information */ + emit_2bytes( cinfo, (int) cinfo->X_density ); + emit_2bytes( cinfo, (int) cinfo->Y_density ); + emit_byte( cinfo, 0 ); /* No thumbnail image */ + emit_byte( cinfo, 0 ); +} + + +LOCAL void +emit_adobe_app14( j_compress_ptr cinfo ) { +/* Emit an Adobe APP14 marker */ +/* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker( cinfo, M_APP14 ); + + emit_2bytes( cinfo, 2 + 5 + 2 + 2 + 2 + 1 );/* length */ + + emit_byte( cinfo, 0x41 );/* Identifier: ASCII "Adobe" */ + emit_byte( cinfo, 0x64 ); + emit_byte( cinfo, 0x6F ); + emit_byte( cinfo, 0x62 ); + emit_byte( cinfo, 0x65 ); + emit_2bytes( cinfo, 100 );/* Version */ + emit_2bytes( cinfo, 0 );/* Flags0 */ + emit_2bytes( cinfo, 0 );/* Flags1 */ + switch ( cinfo->jpeg_color_space ) { + case JCS_YCbCr: + emit_byte( cinfo, 1 );/* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte( cinfo, 2 );/* Color transform = 2 */ + break; + default: + emit_byte( cinfo, 0 );/* Color transform = 0 */ + break; + } +} + + +/* + * This routine is exported for possible use by applications. + * The intended use is to emit COM or APPn markers after calling + * jpeg_start_compress() and before the first jpeg_write_scanlines() call + * (hence, after write_file_header but before write_frame_header). + * Other uses are not guaranteed to produce desirable results. + */ + +METHODDEF void +write_any_marker( j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen ) { +/* Emit an arbitrary marker with parameters */ + if ( datalen <= (unsigned int) 65533 ) {/* safety check */ + emit_marker( cinfo, (JPEG_MARKER) marker ); + + emit_2bytes( cinfo, (int) ( datalen + 2 ) );/* total length */ + + while ( datalen-- ) { + emit_byte( cinfo, *dataptr ); + dataptr++; + } + } +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF void +write_file_header( j_compress_ptr cinfo ) { + emit_marker( cinfo, M_SOI );/* first the SOI */ + + if ( cinfo->write_JFIF_header ) {/* next an optional JFIF APP0 */ + emit_jfif_app0( cinfo ); + } + if ( cinfo->write_Adobe_marker ) {/* next an optional Adobe APP14 */ + emit_adobe_app14( cinfo ); + } +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF void +write_frame_header( j_compress_ptr cinfo ) { + int ci, prec; + boolean is_baseline; + jpeg_component_info * compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + prec += emit_dqt( cinfo, compptr->quant_tbl_no ); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if ( ( cinfo->arith_code ) || ( cinfo->progressive_mode ) || + ( cinfo->data_precision != 8 ) ) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( ( compptr->dc_tbl_no > 1 ) || ( compptr->ac_tbl_no > 1 ) ) { + is_baseline = FALSE; + } + } + if ( ( prec ) && ( is_baseline ) ) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS( cinfo, 0, JTRC_16BIT_TABLES ); + } + } + + /* Emit the proper SOF marker */ + if ( cinfo->arith_code ) { + emit_sof( cinfo, M_SOF9 );/* SOF code for arithmetic coding */ + } else { + if ( cinfo->progressive_mode ) { + emit_sof( cinfo, M_SOF2 ); + } /* SOF code for progressive Huffman */ + else if ( is_baseline ) { + emit_sof( cinfo, M_SOF0 ); + } /* SOF code for baseline implementation */ + else { + emit_sof( cinfo, M_SOF1 ); + } /* SOF code for non-baseline Huffman file */ + } +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF void +write_scan_header( j_compress_ptr cinfo ) { + int i; + jpeg_component_info * compptr; + + if ( cinfo->arith_code ) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac( cinfo ); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for ( i = 0; i < cinfo->comps_in_scan; i++ ) { + compptr = cinfo->cur_comp_info[i]; + if ( cinfo->progressive_mode ) { + /* Progressive mode: only DC or only AC tables are used in one scan */ + if ( cinfo->Ss == 0 ) { + if ( cinfo->Ah == 0 ) {/* DC needs no table for refinement scan */ + emit_dht( cinfo, compptr->dc_tbl_no, FALSE ); + } + } else { + emit_dht( cinfo, compptr->ac_tbl_no, TRUE ); + } + } else { + /* Sequential mode: need both DC and AC tables */ + emit_dht( cinfo, compptr->dc_tbl_no, FALSE ); + emit_dht( cinfo, compptr->ac_tbl_no, TRUE ); + } + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * If it doesn't, a tiny amount of space is wasted in multiple-scan files. + * We assume DRI will never be nonzero for one scan and zero for a later one. + */ + if ( cinfo->restart_interval ) { + emit_dri( cinfo ); + } + + emit_sos( cinfo ); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF void +write_file_trailer( j_compress_ptr cinfo ) { + emit_marker( cinfo, M_EOI ); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF void +write_tables_only( j_compress_ptr cinfo ) { + int i; + + emit_marker( cinfo, M_SOI ); + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + if ( cinfo->quant_tbl_ptrs[i] != NULL ) { + (void) emit_dqt( cinfo, i ); + } + } + + if ( !cinfo->arith_code ) { + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + if ( cinfo->dc_huff_tbl_ptrs[i] != NULL ) { + emit_dht( cinfo, i, FALSE ); + } + if ( cinfo->ac_huff_tbl_ptrs[i] != NULL ) { + emit_dht( cinfo, i, TRUE ); + } + } + } + + emit_marker( cinfo, M_EOI ); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL void +jinit_marker_writer( j_compress_ptr cinfo ) { + /* Create the subobject */ + cinfo->marker = (struct jpeg_marker_writer *) + ( * cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( struct jpeg_marker_writer ) ); + /* Initialize method pointers */ + cinfo->marker->write_any_marker = write_any_marker; + cinfo->marker->write_file_header = write_file_header; + cinfo->marker->write_frame_header = write_frame_header; + cinfo->marker->write_scan_header = write_scan_header; + cinfo->marker->write_file_trailer = write_file_trailer; + cinfo->marker->write_tables_only = write_tables_only; +} diff --git a/neo/renderer/jpeg-6/jcmaster.cpp b/neo/renderer/jpeg-6/jcmaster.cpp new file mode 100644 index 00000000..1368b2fc --- /dev/null +++ b/neo/renderer/jpeg-6/jcmaster.cpp @@ -0,0 +1,607 @@ +/* + * jcmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub;/* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +LOCAL void +initial_setup( j_compress_ptr cinfo ) { +/* Do computations that are needed before master selection phase */ + int ci; + jpeg_component_info * compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Sanity check on image dimensions */ + if ( ( cinfo->image_height <= 0 ) || ( cinfo->image_width <= 0 ) + || ( cinfo->num_components <= 0 ) || ( cinfo->input_components <= 0 ) ) { + ERREXIT( cinfo, JERR_EMPTY_IMAGE ); + } + + /* Make sure image isn't bigger than I can handle */ + if ( ( (long) cinfo->image_height > (long) JPEG_MAX_DIMENSION ) || + ( (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION ) ) { + ERREXIT1( cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION ); + } + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ( (long) jd_samplesperrow != samplesperrow ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + + /* For now, precision must match compiled-in value... */ + if ( cinfo->data_precision != BITS_IN_JSAMPLE ) { + ERREXIT1( cinfo, JERR_BAD_PRECISION, cinfo->data_precision ); + } + + /* Check that number of components won't exceed internal array sizes */ + if ( cinfo->num_components > MAX_COMPONENTS ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS ); + } + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( ( compptr->h_samp_factor <= 0 ) || ( compptr->h_samp_factor > MAX_SAMP_FACTOR ) || + ( compptr->v_samp_factor <= 0 ) || ( compptr->v_samp_factor > MAX_SAMP_FACTOR ) ) { + ERREXIT( cinfo, JERR_BAD_SAMPLING ); + } + cinfo->max_h_samp_factor = MAX( cinfo->max_h_samp_factor, + compptr->h_samp_factor ); + cinfo->max_v_samp_factor = MAX( cinfo->max_v_samp_factor, + compptr->v_samp_factor ); + } + + /* Compute dimensions of components */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* For compression, we never do DCT scaling. */ + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor ); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor ); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL void +validate_script( j_compress_ptr cinfo ) { +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if ( cinfo->num_scans <= 0 ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, 0 ); + } + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if ( ( scanptr->Ss != 0 ) || ( scanptr->Se != DCTSIZE2 - 1 ) ) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = &last_bitpos[0][0]; + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + for ( coefi = 0; coefi < DCTSIZE2; coefi++ ) { + *last_bitpos_ptr++ = -1; + } + } +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + cinfo->progressive_mode = FALSE; + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + component_sent[ci] = FALSE; + } + } + + for ( scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++ ) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if ( ( ncomps <= 0 ) || ( ncomps > MAX_COMPS_IN_SCAN ) ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN ); + } + for ( ci = 0; ci < ncomps; ci++ ) { + thisi = scanptr->component_index[ci]; + if ( ( thisi < 0 ) || ( thisi >= cinfo->num_components ) ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, scanno ); + } + /* Components must appear in SOF order within each scan */ + if ( ( ci > 0 ) && ( thisi <= scanptr->component_index[ci - 1] ) ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, scanno ); + } + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + if ( ( Ss < 0 ) || ( Ss >= DCTSIZE2 ) || ( Se < Ss ) || ( Se >= DCTSIZE2 ) || + ( Ah < 0 ) || ( Ah > 13 ) || ( Al < 0 ) || ( Al > 13 ) ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + if ( Ss == 0 ) { + if ( Se != 0 ) {/* DC and AC together not OK */ + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } else { + if ( ncomps != 1 ) {/* AC scans must be for only one component */ + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } + for ( ci = 0; ci < ncomps; ci++ ) { + last_bitpos_ptr = &last_bitpos[scanptr->component_index[ci]][0]; + if ( ( Ss != 0 ) && ( last_bitpos_ptr[0] < 0 ) ) {/* AC without prior DC scan */ + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + for ( coefi = Ss; coefi <= Se; coefi++ ) { + if ( last_bitpos_ptr[coefi] < 0 ) { + /* first scan of this coefficient */ + if ( Ah != 0 ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } else { + /* not first scan */ + if ( ( Ah != last_bitpos_ptr[coefi] ) || ( Al != Ah - 1 ) ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if ( ( Ss != 0 ) || ( Se != DCTSIZE2 - 1 ) || ( Ah != 0 ) || ( Al != 0 ) ) { + ERREXIT1( cinfo, JERR_BAD_PROG_SCRIPT, scanno ); + } + /* Make sure components are not sent twice */ + for ( ci = 0; ci < ncomps; ci++ ) { + thisi = scanptr->component_index[ci]; + if ( component_sent[thisi] ) { + ERREXIT1( cinfo, JERR_BAD_SCAN_SCRIPT, scanno ); + } + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + if ( last_bitpos[ci][0] < 0 ) { + ERREXIT( cinfo, JERR_MISSING_DATA ); + } + } +#endif + } else { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + if ( !component_sent[ci] ) { + ERREXIT( cinfo, JERR_MISSING_DATA ); + } + } + } +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL void +select_scan_parameters( j_compress_ptr cinfo ) { +/* Set up the scan parameters for the current scan */ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if ( cinfo->scan_info != NULL ) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for ( ci = 0; ci < scanptr->comps_in_scan; ci++ ) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + } else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if ( cinfo->num_components > MAX_COMPS_IN_SCAN ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN ); + } + cinfo->comps_in_scan = cinfo->num_components; + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + cinfo->Ss = 0; + cinfo->Se = DCTSIZE2 - 1; + cinfo->Ah = 0; + cinfo->Al = 0; + } +} + + +LOCAL void +per_scan_setup( j_compress_ptr cinfo ) { +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ + int ci, mcublks, tmp; + jpeg_component_info * compptr; + + if ( cinfo->comps_in_scan == 1 ) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = DCTSIZE; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( tmp == 0 ) { + tmp = compptr->v_samp_factor; + } + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if ( ( cinfo->comps_in_scan <= 0 ) || ( cinfo->comps_in_scan > MAX_COMPS_IN_SCAN ) ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN ); + } + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + + cinfo->blocks_in_MCU = 0; + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) ( compptr->width_in_blocks % compptr->MCU_width ); + if ( tmp == 0 ) { + tmp = compptr->MCU_width; + } + compptr->last_col_width = tmp; + tmp = (int) ( compptr->height_in_blocks % compptr->MCU_height ); + if ( tmp == 0 ) { + tmp = compptr->MCU_height; + } + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if ( cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU ) { + ERREXIT( cinfo, JERR_BAD_MCU_SIZE ); + } + while ( mcublks-- > 0 ) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if ( cinfo->restart_in_rows > 0 ) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN( nominal, 65535L ); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF void +prepare_for_pass( j_compress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch ( master->pass_type ) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters( cinfo ); + per_scan_setup( cinfo ); + if ( !cinfo->raw_data_in ) { + ( *cinfo->cconvert->start_pass )( cinfo ); + ( *cinfo->downsample->start_pass )( cinfo ); + ( *cinfo->prep->start_pass )( cinfo, JBUF_PASS_THRU ); + } + ( *cinfo->fdct->start_pass )( cinfo ); + ( *cinfo->entropy->start_pass )( cinfo, cinfo->optimize_coding ); + ( *cinfo->coef->start_pass )( cinfo, + ( master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU ) ); + ( *cinfo->main->start_pass )( cinfo, JBUF_PASS_THRU ); + if ( cinfo->optimize_coding ) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters( cinfo ); + per_scan_setup( cinfo ); + if ( ( cinfo->Ss != 0 ) || ( cinfo->Ah == 0 ) || ( cinfo->arith_code ) ) { + ( *cinfo->entropy->start_pass )( cinfo, TRUE ); + ( *cinfo->coef->start_pass )( cinfo, JBUF_CRANK_DEST ); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if ( !cinfo->optimize_coding ) { + select_scan_parameters( cinfo ); + per_scan_setup( cinfo ); + } + ( *cinfo->entropy->start_pass )( cinfo, FALSE ); + ( *cinfo->coef->start_pass )( cinfo, JBUF_CRANK_DEST ); + /* We emit frame/scan headers now */ + if ( master->scan_number == 0 ) { + ( *cinfo->marker->write_frame_header )( cinfo ); + } + ( *cinfo->marker->write_scan_header )( cinfo ); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + } + + master->pub.is_last_pass = ( master->pass_number == master->total_passes - 1 ); + + /* Set up progress monitor's pass info if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF void +pass_startup( j_compress_ptr cinfo ) { + cinfo->master->call_pass_startup = FALSE;/* reset flag so call only once */ + + ( *cinfo->marker->write_frame_header )( cinfo ); + ( *cinfo->marker->write_scan_header )( cinfo ); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF void +finish_pass_master( j_compress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + ( *cinfo->entropy->finish_pass )( cinfo ); + + /* Update state for next pass */ + switch ( master->pass_type ) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if ( !cinfo->optimize_coding ) { + master->scan_number++; + } + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if ( cinfo->optimize_coding ) { + master->pass_type = huff_opt_pass; + } + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL void +jinit_c_master_control( j_compress_ptr cinfo, boolean transcode_only ) { + my_master_ptr master; + + master = (my_master_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_comp_master ) ); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup( cinfo ); + + if ( cinfo->scan_info != NULL ) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if ( cinfo->progressive_mode ) {/* TEMPORARY HACK ??? */ + cinfo->optimize_coding = TRUE; + } /* assume default tables no good for progressive mode */ + + /* Initialize my private state */ + if ( transcode_only ) { + /* no main pass in transcoding */ + if ( cinfo->optimize_coding ) { + master->pass_type = huff_opt_pass; + } else { + master->pass_type = output_pass; + } + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if ( cinfo->optimize_coding ) { + master->total_passes = cinfo->num_scans * 2; + } else { + master->total_passes = cinfo->num_scans; + } +} diff --git a/neo/renderer/jpeg-6/jcomapi.cpp b/neo/renderer/jpeg-6/jcomapi.cpp new file mode 100644 index 00000000..28e816e7 --- /dev/null +++ b/neo/renderer/jpeg-6/jcomapi.cpp @@ -0,0 +1,91 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_abort( j_common_ptr cinfo ) { + int pool; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for ( pool = JPOOL_NUMPOOLS - 1; pool > JPOOL_PERMANENT; pool-- ) { + ( *cinfo->mem->free_pool )( cinfo, pool ); + } + + /* Reset overall state for possible reuse of object */ + cinfo->global_state = ( cinfo->is_decompressor ? DSTATE_START : CSTATE_START ); +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_destroy( j_common_ptr cinfo ) { + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if ( cinfo->mem != NULL ) { + ( *cinfo->mem->self_destruct )( cinfo ); + } + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0;/* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL JQUANT_TBL * +jpeg_alloc_quant_table( j_common_ptr cinfo ) { + JQUANT_TBL * tbl; + + tbl = (JQUANT_TBL *) + ( *cinfo->mem->alloc_small )( cinfo, JPOOL_PERMANENT, SIZEOF( JQUANT_TBL ) ); + tbl->sent_table = FALSE;/* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL JHUFF_TBL * +jpeg_alloc_huff_table( j_common_ptr cinfo ) { + JHUFF_TBL * tbl; + + tbl = (JHUFF_TBL *) + ( *cinfo->mem->alloc_small )( cinfo, JPOOL_PERMANENT, SIZEOF( JHUFF_TBL ) ); + tbl->sent_table = FALSE;/* make sure this is false in any new table */ + return tbl; +} diff --git a/neo/renderer/jpeg-6/jconfig.h b/neo/renderer/jpeg-6/jconfig.h new file mode 100644 index 00000000..7d2f733b --- /dev/null +++ b/neo/renderer/jpeg-6/jconfig.h @@ -0,0 +1,41 @@ +/* jconfig.wat --- jconfig.h for Watcom C/C++ on MS-DOS or OS/2. */ +/* see jconfig.doc for explanations */ + +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +/* #define void char */ +/* #define const */ +#define CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS /* Watcom uses flat 32-bit addressing */ +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN + +#define JDCT_DEFAULT JDCT_FLOAT +#define JDCT_FASTEST JDCT_FLOAT + +#ifdef JPEG_INTERNALS + +#undef RIGHT_SHIFT_IS_UNSIGNED + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#undef TWO_FILE_COMMANDLINE /* optional */ +#define USE_SETMODE /* Needed to make one-file style work in Watcom */ +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ +#undef DONT_USE_B_MODE +#undef PROGRESS_REPORT /* optional */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/neo/renderer/jpeg-6/jcparam.cpp b/neo/renderer/jpeg-6/jcparam.cpp new file mode 100644 index 00000000..b2053adb --- /dev/null +++ b/neo/renderer/jpeg-6/jcparam.cpp @@ -0,0 +1,584 @@ +/* + * jcparam.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL void +jpeg_add_quant_table( j_compress_ptr cinfo, int which_tbl, + const unsigned int * basic_table, + int scale_factor, boolean force_baseline ) { +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ + JQUANT_TBL ** qtblptr = &cinfo->quant_tbl_ptrs[which_tbl]; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + if ( *qtblptr == NULL ) { + *qtblptr = jpeg_alloc_quant_table( (j_common_ptr) cinfo ); + } + + for ( i = 0; i < DCTSIZE2; i++ ) { + temp = ( (long) basic_table[i] * scale_factor + 50L ) / 100L; + /* limit the values to the valid range */ + if ( temp <= 0L ) { + temp = 1L; + } + if ( temp > 32767L ) { + temp = 32767L; + } /* max quantizer needed for 12 bits */ + if ( ( force_baseline ) && ( temp > 255L ) ) { + temp = 255L; + } /* limit to baseline range if requested */ + ( *qtblptr )->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + ( *qtblptr )->sent_table = FALSE; +} + + +GLOBAL void +jpeg_set_linear_quality( j_compress_ptr cinfo, int scale_factor, + boolean force_baseline ) { +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +/* This is the sample quantization table given in the JPEG spec section K.1, + * but expressed in zigzag order (as are all of our quant. tables). + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ + static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99 + }; + static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table( cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline ); + jpeg_add_quant_table( cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline ); +} + + +GLOBAL int +jpeg_quality_scaling( int quality ) { +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +/* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if ( quality <= 0 ) { + quality = 1; + } + if ( quality > 100 ) { + quality = 100; + } + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause j_add_quant_table + * to make all the table entries 1 (hence, no quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if ( quality < 50 ) { + quality = 5000 / quality; + } else { + quality = 200 - quality * 2; + } + + return quality; +} + + +GLOBAL void +jpeg_set_quality( j_compress_ptr cinfo, int quality, boolean force_baseline ) { +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +/* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling( quality ); + + /* Set up standard quality tables */ + jpeg_set_linear_quality( cinfo, quality, force_baseline ); +} + + +/* + * Huffman table setup routines + */ + +LOCAL void +add_huff_table( j_compress_ptr cinfo, + JHUFF_TBL ** htblptr, const UINT8 * bits, const UINT8 * val ) { +/* Define a Huffman table */ + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + + MEMCOPY( ( *htblptr )->bits, bits, SIZEOF( ( *htblptr )->bits ) ); + MEMCOPY( ( *htblptr )->huffval, val, SIZEOF( ( *htblptr )->huffval ) ); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + ( *htblptr )->sent_table = FALSE; +} + + +LOCAL void +std_huff_tables( j_compress_ptr cinfo ) { +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + static const UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + add_huff_table( cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance ); + add_huff_table( cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance ); + add_huff_table( cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance ); + add_huff_table( cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance ); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL void +jpeg_set_defaults( j_compress_ptr cinfo ) { + int i; + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if ( cinfo->comp_info == NULL ) { + cinfo->comp_info = (jpeg_component_info *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF( jpeg_component_info ) ); + } + + /* Initialize everything not dependent on the color space */ + + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality( cinfo, 75, TRUE ); + /* Set up two Huffman tables */ + std_huff_tables( cinfo ); + + /* Initialize default arithmetic coding conditioning */ + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, don't do extra passes to optimize entropy coding */ + cinfo->optimize_coding = FALSE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if ( cinfo->data_precision > 8 ) { + cinfo->optimize_coding = TRUE; + } + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + */ + cinfo->density_unit = 0;/* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace( cinfo ); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL void +jpeg_default_colorspace( j_compress_ptr cinfo ) { + switch ( cinfo->in_color_space ) { + case JCS_GRAYSCALE: + jpeg_set_colorspace( cinfo, JCS_GRAYSCALE ); + break; + case JCS_RGB: + jpeg_set_colorspace( cinfo, JCS_YCbCr ); + break; + case JCS_YCbCr: + jpeg_set_colorspace( cinfo, JCS_YCbCr ); + break; + case JCS_CMYK: + jpeg_set_colorspace( cinfo, JCS_CMYK );/* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace( cinfo, JCS_YCCK ); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace( cinfo, JCS_UNKNOWN ); + break; + default: + ERREXIT( cinfo, JERR_BAD_IN_COLORSPACE ); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL void +jpeg_set_colorspace( j_compress_ptr cinfo, J_COLOR_SPACE colorspace ) { + jpeg_component_info * compptr; + int ci; + +#define SET_COMP( index, id, hsamp, vsamp, quant, dctbl, actbl ) \ + ( compptr = &cinfo->comp_info[index], \ + compptr->component_id = ( id ), \ + compptr->h_samp_factor = ( hsamp ), \ + compptr->v_samp_factor = ( vsamp ), \ + compptr->quant_tbl_no = ( quant ), \ + compptr->dc_tbl_no = ( dctbl ), \ + compptr->ac_tbl_no = ( actbl ) ) + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE;/* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE;/* write no Adobe marker by default */ + + switch ( colorspace ) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE;/* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP( 0, 1, 1, 1, 0, 0, 0 ); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE;/* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP( 0, 0x52 /* 'R' */, 1, 1, 0, 0, 0 ); + SET_COMP( 1, 0x47 /* 'G' */, 1, 1, 0, 0, 0 ); + SET_COMP( 2, 0x42 /* 'B' */, 1, 1, 0, 0, 0 ); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE;/* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP( 0, 1, 2, 2, 0, 0, 0 ); + SET_COMP( 1, 2, 1, 1, 1, 1, 1 ); + SET_COMP( 2, 3, 1, 1, 1, 1, 1 ); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE;/* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP( 0, 0x43 /* 'C' */, 1, 1, 0, 0, 0 ); + SET_COMP( 1, 0x4D /* 'M' */, 1, 1, 0, 0, 0 ); + SET_COMP( 2, 0x59 /* 'Y' */, 1, 1, 0, 0, 0 ); + SET_COMP( 3, 0x4B /* 'K' */, 1, 1, 0, 0, 0 ); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE;/* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP( 0, 1, 2, 2, 0, 0, 0 ); + SET_COMP( 1, 2, 1, 1, 1, 1, 1 ); + SET_COMP( 2, 3, 1, 1, 1, 1, 1 ); + SET_COMP( 3, 4, 2, 2, 0, 0, 0 ); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if ( ( cinfo->num_components < 1 ) || ( cinfo->num_components > MAX_COMPONENTS ) ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS ); + } + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + SET_COMP( ci, ci, 1, 1, 0, 0, 0 ); + } + break; + default: + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL jpeg_scan_info * +fill_a_scan( jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al ) { +/* Support routine: generate one scan for specified component */ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_scans( jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al ) { +/* Support routine: generate one scan for each component */ + int ci; + + for ( ci = 0; ci < ncomps; ci++ ) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_dc_scans( jpeg_scan_info * scanptr, int ncomps, int Ah, int Al ) { +/* Support routine: generate interleaved DC scan if possible, else N scans */ + int ci; + + if ( ncomps <= MAX_COMPS_IN_SCAN ) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for ( ci = 0; ci < ncomps; ci++ ) { + scanptr->component_index[ci] = ci; + } + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans( scanptr, ncomps, 0, 0, Ah, Al ); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL void +jpeg_simple_progression( j_compress_ptr cinfo ) { + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + /* Figure space needed for script. Calculation must match code below! */ + if ( ( ncomps == 3 ) && ( cinfo->jpeg_color_space == JCS_YCbCr ) ) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if ( ncomps > MAX_COMPS_IN_SCAN ) { + nscans = 6 * ncomps; + } /* 2 DC + 4 AC scans per component */ + else { + nscans = 2 + 4 * ncomps; + } /* 2 DC scans; 4 AC scans per component */ + } + + /* Allocate space for script. */ + /* We use permanent pool just in case application re-uses script. */ + scanptr = (jpeg_scan_info *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + nscans * SIZEOF( jpeg_scan_info ) ); + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if ( ( ncomps == 3 ) && ( cinfo->jpeg_color_space == JCS_YCbCr ) ) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans( scanptr, ncomps, 0, 1 ); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan( scanptr, 0, 1, 5, 0, 2 ); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan( scanptr, 2, 1, 63, 0, 1 ); + scanptr = fill_a_scan( scanptr, 1, 1, 63, 0, 1 ); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan( scanptr, 0, 6, 63, 0, 2 ); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan( scanptr, 0, 1, 63, 2, 1 ); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans( scanptr, ncomps, 1, 0 ); + /* Finish AC successive approximation */ + scanptr = fill_a_scan( scanptr, 2, 1, 63, 1, 0 ); + scanptr = fill_a_scan( scanptr, 1, 1, 63, 1, 0 ); + /* Luma bottom bit comes last since it's usually largest scan */ + scanptr = fill_a_scan( scanptr, 0, 1, 63, 1, 0 ); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans( scanptr, ncomps, 0, 1 ); + scanptr = fill_scans( scanptr, ncomps, 1, 5, 0, 2 ); + scanptr = fill_scans( scanptr, ncomps, 6, 63, 0, 2 ); + /* Successive approximation second pass */ + scanptr = fill_scans( scanptr, ncomps, 1, 63, 2, 1 ); + /* Successive approximation final pass */ + scanptr = fill_dc_scans( scanptr, ncomps, 1, 0 ); + scanptr = fill_scans( scanptr, ncomps, 1, 63, 1, 0 ); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jcphuff.cpp b/neo/renderer/jpeg-6/jcphuff.cpp new file mode 100644 index 00000000..953a773e --- /dev/null +++ b/neo/renderer/jpeg-6/jcphuff.cpp @@ -0,0 +1,847 @@ +/* + * jcphuff.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines for progressive JPEG. + * + * We do not support output suspension in this module, since the library + * currently does not allow multiple-scan files to be written with output + * suspension. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jchuff.c */ + +#ifdef C_PROGRESSIVE_SUPPORTED + +/* Expanded entropy encoder object for progressive Huffman encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub;/* public fields */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* Bit-level coding status. + * next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for DC components */ + int last_dc_val[MAX_COMPS_IN_SCAN];/* last DC coef for each component */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer;/* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ + + unsigned int restarts_to_go;/* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan). + * Since any one scan codes only DC or only AC, we only need one set + * of tables, not one for DC and one for AC. + */ + c_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization; again, one set is enough */ + long * count_ptrs[NUM_HUFF_TBLS]; +} phuff_entropy_encoder; + +typedef phuff_entropy_encoder * phuff_entropy_ptr; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT( x, shft ) \ + ( ( ishift_temp = ( x ) ) < 0 ? \ + ( ishift_temp >> ( shft ) ) | ( ( ~0 ) << ( 16 - ( shft ) ) ) : \ + ( ishift_temp >> ( shft ) ) ) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT( x, shft ) ( ( x ) >> ( shft ) ) +#endif + +/* Forward declarations */ +METHODDEF boolean encode_mcu_DC_first JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean encode_mcu_AC_first JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean encode_mcu_DC_refine JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean encode_mcu_AC_refine JPP( ( j_compress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF void finish_pass_phuff JPP( (j_compress_ptr cinfo) ); +METHODDEF void finish_pass_gather_phuff JPP( (j_compress_ptr cinfo) ); + + +/* + * Initialize for a Huffman-compressed scan using progressive JPEG. + */ + +METHODDEF void +start_pass_phuff( j_compress_ptr cinfo, boolean gather_statistics ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + is_DC_band = ( cinfo->Ss == 0 ); + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routines */ + if ( cinfo->Ah == 0 ) { + if ( is_DC_band ) { + entropy->pub.encode_mcu = encode_mcu_DC_first; + } else { + entropy->pub.encode_mcu = encode_mcu_AC_first; + } + } else { + if ( is_DC_band ) { + entropy->pub.encode_mcu = encode_mcu_DC_refine; + } else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if ( entropy->bit_buffer == NULL ) { + entropy->bit_buffer = (char *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF( char ) ); + } + } + } + if ( gather_statistics ) { + entropy->pub.finish_pass = finish_pass_gather_phuff; + } else { + entropy->pub.finish_pass = finish_pass_phuff; + } + + /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1 + * for AC coefficients. + */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if ( is_DC_band ) { + if ( cinfo->Ah != 0 ) {/* DC refinement needs no table */ + continue; + } + tbl = compptr->dc_tbl_no; + if ( ( tbl < 0 ) || ( tbl >= NUM_HUFF_TBLS ) || + ( ( cinfo->dc_huff_tbl_ptrs[tbl] == NULL ) && ( !gather_statistics ) ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, tbl ); + } + } else { + entropy->ac_tbl_no = tbl = compptr->ac_tbl_no; + if ( ( tbl < 0 ) || ( tbl >= NUM_HUFF_TBLS ) || + ( ( cinfo->ac_huff_tbl_ptrs[tbl] == NULL ) && ( !gather_statistics ) ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, tbl ); + } + } + if ( gather_statistics ) { + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if ( entropy->count_ptrs[tbl] == NULL ) { + entropy->count_ptrs[tbl] = (long *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF( long ) ); + } + MEMZERO( entropy->count_ptrs[tbl], 257 * SIZEOF( long ) ); + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + if ( is_DC_band ) { + jpeg_make_c_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[tbl], + &entropy->derived_tbls[tbl] ); + } else { + jpeg_make_c_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[tbl], + &entropy->derived_tbls[tbl] ); + } + } + } + + /* Initialize AC stuff */ + entropy->EOBRUN = 0; + entropy->BE = 0; + + /* Initialize bit buffer to empty */ + entropy->put_buffer = 0; + entropy->put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte */ +#define emit_byte( entropy, val ) \ + { *( entropy )->next_output_byte++ = (JOCTET) ( val ); \ + if ( -- ( entropy )->free_in_buffer == 0 ) { \ + dump_buffer( entropy ); } } + + +LOCAL void +dump_buffer( phuff_entropy_ptr entropy ) { +/* Empty the output buffer; we do not support suspension in this module. */ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if ( !( *dest->empty_output_buffer )( entropy->cinfo ) ) { + ERREXIT( entropy->cinfo, JERR_CANT_SUSPEND ); + } + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL void +emit_bits( phuff_entropy_ptr entropy, unsigned int code, int size ) { +/* Emit some bits, unless we are in gather mode */ +/* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if ( size == 0 ) { + ERREXIT( entropy->cinfo, JERR_HUFF_MISSING_CODE ); + } + + if ( entropy->gather_statistics ) { + return; + } /* do nothing if we're only getting stats */ + + put_buffer &= ( ( (INT32) 1 ) << size ) - 1;/* mask off any extra bits in code */ + + put_bits += size; /* new number of bits in buffer */ + + put_buffer <<= 24 - put_bits;/* align incoming bits */ + + put_buffer |= entropy->put_buffer;/* and merge with old buffer contents */ + + while ( put_bits >= 8 ) { + int c = (int) ( ( put_buffer >> 16 ) & 0xFF ); + + emit_byte( entropy, c ); + if ( c == 0xFF ) { /* need to stuff a zero byte? */ + emit_byte( entropy, 0 ); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->put_buffer = put_buffer;/* update variables */ + entropy->put_bits = put_bits; +} + + +LOCAL void +flush_bits( phuff_entropy_ptr entropy ) { + emit_bits( entropy, 0x7F, 7 );/* fill any partial byte with ones */ + entropy->put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL void +emit_symbol( phuff_entropy_ptr entropy, int tbl_no, int symbol ) { + if ( entropy->gather_statistics ) { + entropy->count_ptrs[tbl_no][symbol]++; + } else { + c_derived_tbl * tbl = entropy->derived_tbls[tbl_no]; + emit_bits( entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol] ); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL void +emit_buffered_bits( phuff_entropy_ptr entropy, char * bufstart, + unsigned int nbits ) { + if ( entropy->gather_statistics ) { + return; + } /* no real work */ + + while ( nbits > 0 ) { + emit_bits( entropy, (unsigned int) ( *bufstart ), 1 ); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL void +emit_eobrun( phuff_entropy_ptr entropy ) { + register int temp, nbits; + + if ( entropy->EOBRUN > 0 ) {/* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ( ( temp >>= 1 ) ) { + nbits++; + } + + emit_symbol( entropy, entropy->ac_tbl_no, nbits << 4 ); + if ( nbits ) { + emit_bits( entropy, entropy->EOBRUN, nbits ); + } + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits( entropy, entropy->bit_buffer, entropy->BE ); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL void +emit_restart( phuff_entropy_ptr entropy, int restart_num ) { + int ci; + + emit_eobrun( entropy ); + + if ( !entropy->gather_statistics ) { + flush_bits( entropy ); + emit_byte( entropy, 0xFF ); + emit_byte( entropy, JPEG_RST0 + restart_num ); + } + + if ( entropy->cinfo->Ss == 0 ) { + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < entropy->cinfo->comps_in_scan; ci++ ) { + entropy->last_dc_val[ci] = 0; + } + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_DC_first( j_compress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data blocks */ + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT( (int) ( ( *block )[0] ), Al ); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->last_dc_val[ci]; + entropy->last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if ( temp < 0 ) { + temp = -temp;/* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while ( temp ) { + nbits++; + temp >>= 1; + } + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_symbol( entropy, compptr->dc_tbl_no, nbits ); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if ( nbits ) { /* emit_bits rejects calls with size 0 */ + emit_bits( entropy, (unsigned int) temp2, nbits ); + } + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_AC_first( j_compress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for ( k = cinfo->Ss; k <= Se; k++ ) { + if ( ( temp = ( *block )[jpeg_natural_order[k]] ) == 0 ) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if ( temp < 0 ) { + temp = -temp;/* temp is abs value of input */ + temp >>= Al;/* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al;/* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if ( temp == 0 ) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if ( entropy->EOBRUN > 0 ) { + emit_eobrun( entropy ); + } + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while ( r > 15 ) { + emit_symbol( entropy, entropy->ac_tbl_no, 0xF0 ); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ( ( temp >>= 1 ) ) { + nbits++; + } + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol( entropy, entropy->ac_tbl_no, ( r << 4 ) + nbits ); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits( entropy, (unsigned int) temp2, nbits ); + + r = 0; /* reset zero run length */ + } + + if ( r > 0 ) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if ( entropy->EOBRUN == 0x7FFF ) { + emit_eobrun( entropy ); + } /* force it out to avoid overflow */ + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF boolean +encode_mcu_DC_refine( j_compress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data blocks */ + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = ( *block )[0]; + emit_bits( entropy, (unsigned int) ( temp >> Al ), 1 ); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF boolean +encode_mcu_AC_refine( j_compress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char * BR_buffer; + unsigned int BR; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + emit_restart( entropy, entropy->next_restart_num ); + } + } + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for ( k = cinfo->Ss; k <= Se; k++ ) { + temp = ( *block )[jpeg_natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if ( temp < 0 ) { + temp = -temp; + } /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp;/* save abs value for main pass */ + if ( temp == 1 ) { + EOB = k; + } /* EOB = index of last newly-nonzero coef */ + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE;/* Append bits to buffer */ + + for ( k = cinfo->Ss; k <= Se; k++ ) { + if ( ( temp = absvalues[k] ) == 0 ) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while ( r > 15 && k <= EOB ) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun( entropy ); + /* Emit ZRL */ + emit_symbol( entropy, entropy->ac_tbl_no, 0xF0 ); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits( entropy, BR_buffer, BR ); + BR_buffer = entropy->bit_buffer;/* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if ( temp > 1 ) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) ( temp & 1 ); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun( entropy ); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol( entropy, entropy->ac_tbl_no, ( r << 4 ) + 1 ); + + /* Emit output bit for newly-nonzero coef */ + temp = ( ( *block )[jpeg_natural_order[k]] < 0 ) ? 0 : 1; + emit_bits( entropy, (unsigned int) temp, 1 ); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits( entropy, BR_buffer, BR ); + BR_buffer = entropy->bit_buffer;/* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if ( ( r > 0 ) || ( BR > 0 ) ) {/* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if ( ( entropy->EOBRUN == 0x7FFF ) || ( entropy->BE > ( MAX_CORR_BITS - DCTSIZE2 + 1 ) ) ) { + emit_eobrun( entropy ); + } + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed progressive scan. + */ + +METHODDEF void +finish_pass_phuff( j_compress_ptr cinfo ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun( entropy ); + flush_bits( entropy ); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather_phuff( j_compress_ptr cinfo ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL ** htblptr; + boolean did[NUM_HUFF_TBLS]; + + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun( entropy ); + + is_DC_band = ( cinfo->Ss == 0 ); + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO( did, SIZEOF( did ) ); + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + if ( is_DC_band ) { + if ( cinfo->Ah != 0 ) {/* DC refinement needs no table */ + continue; + } + tbl = compptr->dc_tbl_no; + } else { + tbl = compptr->ac_tbl_no; + } + if ( !did[tbl] ) { + if ( is_DC_band ) { + htblptr = &cinfo->dc_huff_tbl_ptrs[tbl]; + } else { + htblptr = &cinfo->ac_huff_tbl_ptrs[tbl]; + } + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + jpeg_gen_optimal_table( cinfo, *htblptr, entropy->count_ptrs[tbl] ); + did[tbl] = TRUE; + } + } +} + + +/* + * Module initialization routine for progressive Huffman entropy encoding. + */ + +GLOBAL void +jinit_phuff_encoder( j_compress_ptr cinfo ) { + phuff_entropy_ptr entropy; + int i; + + entropy = (phuff_entropy_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( phuff_entropy_encoder ) ); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_phuff; + + /* Mark tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->derived_tbls[i] = NULL; + entropy->count_ptrs[i] = NULL; + } + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jcprepct.cpp b/neo/renderer/jpeg-6/jcprepct.cpp new file mode 100644 index 00000000..5b0d884f --- /dev/null +++ b/neo/renderer/jpeg-6/jcprepct.cpp @@ -0,0 +1,370 @@ +/* + * jcprepct.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub;/* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_prep( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if ( pass_mode != JBUF_PASS_THRU ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL void +expand_bottom_edge( JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows ) { + register int row; + + for ( row = input_rows; row < output_rows; row++ ) { + jcopy_sample_rows( image_data, input_rows - 1, image_data, row, + 1, num_cols ); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF void +pre_process_data( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION * in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION * out_row_group_ctr, + JDIMENSION out_row_groups_avail ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while ( *in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail ) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN( (JDIMENSION) numrows, inrows ); + ( *cinfo->cconvert->color_convert )( cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows ); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if ( ( prep->rows_to_go == 0 ) && + ( prep->next_buf_row < cinfo->max_v_samp_factor ) ) { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + expand_bottom_edge( prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor ); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if ( prep->next_buf_row == cinfo->max_v_samp_factor ) { + ( *cinfo->downsample->downsample )( cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr ); + prep->next_buf_row = 0; + ( *out_row_group_ctr )++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if ( ( prep->rows_to_go == 0 ) && + ( *out_row_group_ctr < out_row_groups_avail ) ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + expand_bottom_edge( output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) ( *out_row_group_ctr * compptr->v_samp_factor ), + (int) ( out_row_groups_avail * compptr->v_samp_factor ) ); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF void +pre_process_context( j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION * in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION * out_row_group_ctr, + JDIMENSION out_row_groups_avail ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while ( *out_row_group_ctr < out_row_groups_avail ) { + if ( *in_row_ctr < in_rows_avail ) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN( (JDIMENSION) numrows, inrows ); + ( *cinfo->cconvert->color_convert )( cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows ); + /* Pad at top of image, if first time through */ + if ( prep->rows_to_go == cinfo->image_height ) { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + int row; + for ( row = 1; row <= cinfo->max_v_samp_factor; row++ ) { + jcopy_sample_rows( prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width ); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if ( prep->rows_to_go != 0 ) { + break; + } + } + /* If at bottom of image, pad to fill the conversion buffer. */ + if ( ( prep->rows_to_go == 0 ) && + ( prep->next_buf_row < prep->next_buf_stop ) ) { + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + expand_bottom_edge( prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop ); + } + prep->next_buf_row = prep->next_buf_stop; + } + /* If we've gotten enough data, downsample a row group. */ + if ( prep->next_buf_row == prep->next_buf_stop ) { + ( *cinfo->downsample->downsample )( cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr ); + ( *out_row_group_ctr )++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if ( prep->this_row_group >= buf_height ) { + prep->this_row_group = 0; + } + if ( prep->next_buf_row >= buf_height ) { + prep->next_buf_row = 0; + } + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if ( ( prep->rows_to_go == 0 ) && + ( *out_row_group_ctr < out_row_groups_avail ) ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + expand_bottom_edge( output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) ( *out_row_group_ctr * compptr->v_samp_factor ), + (int) ( out_row_groups_avail * compptr->v_samp_factor ) ); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL void +create_context_buffer( j_compress_ptr cinfo ) { + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( cinfo->num_components * 5 * rgroup_height ) * + SIZEOF( JSAMPROW ) ); + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) ( ( (long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor ) / compptr->h_samp_factor ), + (JDIMENSION) ( 3 * rgroup_height ) ); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY( fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF( JSAMPROW ) ); + /* Fill in the above and below wraparound pointers */ + for ( i = 0; i < rgroup_height; i++ ) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height;/* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL void +jinit_c_prep_controller( j_compress_ptr cinfo, boolean need_full_buffer ) { + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if ( need_full_buffer ) {/* safety check */ + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + prep = (my_prep_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_prep_controller ) ); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if ( cinfo->downsample->need_context_rows ) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + prep->color_buf[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) ( ( (long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor ) / compptr->h_samp_factor ), + (JDIMENSION) cinfo->max_v_samp_factor ); + } + } +} diff --git a/neo/renderer/jpeg-6/jcsample.cpp b/neo/renderer/jpeg-6/jcsample.cpp new file mode 100644 index 00000000..54de4d63 --- /dev/null +++ b/neo/renderer/jpeg-6/jcsample.cpp @@ -0,0 +1,523 @@ +/* + * jcsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD ( void, downsample1_ptr, + ( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) ); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub;/* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF void +start_pass_downsample( j_compress_ptr cinfo ) { + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL void +expand_right_edge( JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols ) { + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) ( output_cols - input_cols ); + + if ( numcols > 0 ) { + for ( row = 0; row < num_rows; row++ ) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1];/* don't need GETJSAMPLE() here */ + for ( count = numcols; count > 0; count-- ) { + *ptr++ = pixval; + } + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF void +sep_downsample( j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index ) { + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + ( out_row_group_index * compptr->v_samp_factor ); + ( *downsample->methods[ci] )( cinfo, compptr, in_ptr, out_ptr ); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF void +int_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h;/* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor; + v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor; + numpix = h_expand * v_expand; + numpix2 = numpix / 2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand ); + + inrow = 0; + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + for ( outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand ) { + outvalue = 0; + for ( v = 0; v < v_expand; v++ ) { + inptr = input_data[inrow + v] + outcol_h; + for ( h = 0; h < h_expand; h++ ) { + outvalue += (INT32) GETJSAMPLE( *inptr++ ); + } + } + *outptr++ = (JSAMPLE) ( ( outvalue + numpix2 ) / numpix ); + } + inrow += v_expand; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF void +fullsize_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + /* Copy the data */ + jcopy_sample_rows( input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width ); + /* Edge-expand */ + expand_right_edge( output_data, cinfo->max_v_samp_factor, + cinfo->image_width, compptr->width_in_blocks * DCTSIZE ); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2 ); + + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for ( outcol = 0; outcol < output_cols; outcol++ ) { + *outptr++ = (JSAMPLE) ( ( GETJSAMPLE( *inptr ) + GETJSAMPLE( inptr[1] ) + + bias ) >> 1 ); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF void +h2v2_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2 ); + + inrow = 0; + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow + 1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for ( outcol = 0; outcol < output_cols; outcol++ ) { + *outptr++ = (JSAMPLE) ( ( GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ) + + bias ) >> 2 ); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; + inptr1 += 2; + } + inrow += 2; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF void +h2v2_smooth_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2 ); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80;/* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16;/* scaled SF/4 */ + + inrow = 0; + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow + 1]; + above_ptr = input_data[inrow - 1]; + below_ptr = input_data[inrow + 2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ); + neighsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[1] ) + + GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[2] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[2] ); + neighsum += neighsum; + neighsum += GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[2] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[2] ); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + inptr0 += 2; + inptr1 += 2; + above_ptr += 2; + below_ptr += 2; + + for ( colctr = output_cols - 2; colctr > 0; colctr-- ) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[1] ) + + GETJSAMPLE( inptr0[-1] ) + GETJSAMPLE( inptr0[2] ) + + GETJSAMPLE( inptr1[-1] ) + GETJSAMPLE( inptr1[2] ); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE( above_ptr[-1] ) + GETJSAMPLE( above_ptr[2] ) + + GETJSAMPLE( below_ptr[-1] ) + GETJSAMPLE( below_ptr[2] ); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + inptr0 += 2; + inptr1 += 2; + above_ptr += 2; + below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE( *inptr0 ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( *inptr1 ) + GETJSAMPLE( inptr1[1] ); + neighsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( *below_ptr ) + GETJSAMPLE( below_ptr[1] ) + + GETJSAMPLE( inptr0[-1] ) + GETJSAMPLE( inptr0[1] ) + + GETJSAMPLE( inptr1[-1] ) + GETJSAMPLE( inptr1[1] ); + neighsum += neighsum; + neighsum += GETJSAMPLE( above_ptr[-1] ) + GETJSAMPLE( above_ptr[1] ) + + GETJSAMPLE( below_ptr[-1] ) + GETJSAMPLE( below_ptr[1] ); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + + inrow += 2; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF void +fullsize_smooth_downsample( j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data ) { + int outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge( input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols ); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L;/* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64;/* scaled SF */ + + for ( outrow = 0; outrow < compptr->v_samp_factor; outrow++ ) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + above_ptr = input_data[outrow - 1]; + below_ptr = input_data[outrow + 1]; + + /* Special case for first column */ + colsum = GETJSAMPLE( *above_ptr++ ) + GETJSAMPLE( *below_ptr++ ) + + GETJSAMPLE( *inptr ); + membersum = GETJSAMPLE( *inptr++ ); + nextcolsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( *below_ptr ) + + GETJSAMPLE( *inptr ); + neighsum = colsum + ( colsum - membersum ) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + lastcolsum = colsum; + colsum = nextcolsum; + + for ( colctr = output_cols - 2; colctr > 0; colctr-- ) { + membersum = GETJSAMPLE( *inptr++ ); + above_ptr++; + below_ptr++; + nextcolsum = GETJSAMPLE( *above_ptr ) + GETJSAMPLE( *below_ptr ) + + GETJSAMPLE( *inptr ); + neighsum = lastcolsum + ( colsum - membersum ) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + lastcolsum = colsum; + colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE( *inptr ); + neighsum = lastcolsum + ( colsum - membersum ) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ( ( membersum + 32768 ) >> 16 ); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL void +jinit_downsampler( j_compress_ptr cinfo ) { + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + + downsample = (my_downsample_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_downsampler ) ); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if ( cinfo->CCIR601_sampling ) { + ERREXIT( cinfo, JERR_CCIR601_NOTIMPL ); + } + + /* Verify we can handle the sampling factors, and set up method pointers */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( ( compptr->h_samp_factor == cinfo->max_h_samp_factor ) && + ( compptr->v_samp_factor == cinfo->max_v_samp_factor ) ) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if ( cinfo->smoothing_factor ) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if ( compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor ) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if ( compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor ) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if ( cinfo->smoothing_factor ) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ( ( cinfo->max_h_samp_factor % compptr->h_samp_factor ) == 0 && + ( cinfo->max_v_samp_factor % compptr->v_samp_factor ) == 0 ) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + } else { + ERREXIT( cinfo, JERR_FRACT_SAMPLE_NOTIMPL ); + } + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if ( ( cinfo->smoothing_factor ) && ( !smoothok ) ) { + TRACEMS( cinfo, 0, JTRC_SMOOTH_NOTIMPL ); + } +#endif +} diff --git a/neo/renderer/jpeg-6/jctrans.cpp b/neo/renderer/jpeg-6/jctrans.cpp new file mode 100644 index 00000000..7ad93233 --- /dev/null +++ b/neo/renderer/jpeg-6/jctrans.cpp @@ -0,0 +1,374 @@ +/* + * jctrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding compression, + * that is, writing raw DCT coefficient arrays to an output JPEG file. + * The routines in jcapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transencode_master_selection +JPP( ( j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays ) ); +LOCAL void transencode_coef_controller +JPP( ( j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays ) ); + + +/* + * Compression initialization for writing raw-coefficient data. + * Before calling this, all parameters and a data destination must be set up. + * Call jpeg_finish_compress() to actually write the data. + * + * The number of passed virtual arrays must match cinfo->num_components. + * Note that the virtual arrays need not be filled or even realized at + * the time write_coefficients is called; indeed, if the virtual arrays + * were requested from this compression object's memory manager, they + * typically will be realized during this routine and filled afterwards. + */ + +GLOBAL void +jpeg_write_coefficients( j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays ) { + if ( cinfo->global_state != CSTATE_START ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Mark all tables to be written */ + jpeg_suppress_tables( cinfo, FALSE ); + /* (Re)initialize error mgr and destination modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->dest->init_destination )( cinfo ); + /* Perform master selection of active modules */ + transencode_master_selection( cinfo, coef_arrays ); + /* Wait for jpeg_finish_compress() call */ + cinfo->next_scanline = 0;/* so jpeg_write_marker works */ + cinfo->global_state = CSTATE_WRCOEFS; +} + + +/* + * Initialize the compression object with default parameters, + * then copy from the source object all parameters needed for lossless + * transcoding. Parameters that can be varied without loss (such as + * scan script and Huffman optimization) are left in their default states. + */ + +GLOBAL void +jpeg_copy_critical_parameters( j_decompress_ptr srcinfo, + j_compress_ptr dstinfo ) { + JQUANT_TBL ** qtblptr; + jpeg_component_info * incomp, * outcomp; + JQUANT_TBL * c_quant, * slot_quant; + int tblno, ci, coefi; + + /* Safety check to ensure start_compress not called yet. */ + if ( dstinfo->global_state != CSTATE_START ) { + ERREXIT1( dstinfo, JERR_BAD_STATE, dstinfo->global_state ); + } + /* Copy fundamental image dimensions */ + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + /* Initialize all parameters to default values */ + jpeg_set_defaults( dstinfo ); + /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. + * Fix it to get the right header markers for the image colorspace. + */ + jpeg_set_colorspace( dstinfo, srcinfo->jpeg_color_space ); + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + /* Copy the source's quantization tables. */ + for ( tblno = 0; tblno < NUM_QUANT_TBLS; tblno++ ) { + if ( srcinfo->quant_tbl_ptrs[tblno] != NULL ) { + qtblptr = &dstinfo->quant_tbl_ptrs[tblno]; + if ( *qtblptr == NULL ) { + *qtblptr = jpeg_alloc_quant_table( (j_common_ptr) dstinfo ); + } + MEMCOPY( ( *qtblptr )->quantval, + srcinfo->quant_tbl_ptrs[tblno]->quantval, + SIZEOF( ( *qtblptr )->quantval ) ); + ( *qtblptr )->sent_table = FALSE; + } + } + /* Copy the source's per-component info. + * Note we assume jpeg_set_defaults has allocated the dest comp_info array. + */ + dstinfo->num_components = srcinfo->num_components; + if ( ( dstinfo->num_components < 1 ) || ( dstinfo->num_components > MAX_COMPONENTS ) ) { + ERREXIT2( dstinfo, JERR_COMPONENT_COUNT, dstinfo->num_components, + MAX_COMPONENTS ); + } + for ( ci = 0, incomp = srcinfo->comp_info, outcomp = dstinfo->comp_info; + ci < dstinfo->num_components; ci++, incomp++, outcomp++ ) { + outcomp->component_id = incomp->component_id; + outcomp->h_samp_factor = incomp->h_samp_factor; + outcomp->v_samp_factor = incomp->v_samp_factor; + outcomp->quant_tbl_no = incomp->quant_tbl_no; + /* Make sure saved quantization table for component matches the qtable + * slot. If not, the input file re-used this qtable slot. + * IJG encoder currently cannot duplicate this. + */ + tblno = outcomp->quant_tbl_no; + if ( ( tblno < 0 ) || ( tblno >= NUM_QUANT_TBLS ) || + ( srcinfo->quant_tbl_ptrs[tblno] == NULL ) ) { + ERREXIT1( dstinfo, JERR_NO_QUANT_TABLE, tblno ); + } + slot_quant = srcinfo->quant_tbl_ptrs[tblno]; + c_quant = incomp->quant_table; + if ( c_quant != NULL ) { + for ( coefi = 0; coefi < DCTSIZE2; coefi++ ) { + if ( c_quant->quantval[coefi] != slot_quant->quantval[coefi] ) { + ERREXIT1( dstinfo, JERR_MISMATCHED_QUANT_TABLE, tblno ); + } + } + } + /* Note: we do not copy the source's Huffman table assignments; + * instead we rely on jpeg_set_colorspace to have made a suitable choice. + */ + } +} + + +/* + * Master selection of compression modules for transcoding. + * This substitutes for jcinit.c's initialization of the full compressor. + */ + +LOCAL void +transencode_master_selection( j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays ) { + /* Although we don't actually use input_components for transcoding, + * jcmaster.c's initial_setup will complain if input_components is 0. + */ + cinfo->input_components = 1; + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control( cinfo, TRUE /* transcode only */ ); + + /* Entropy encoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_encoder( cinfo ); + } + } + + /* We need a special coefficient buffer controller. */ + transencode_coef_controller( cinfo, coef_arrays ); + + jinit_marker_writer( cinfo ); + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + ( *cinfo->marker->write_file_header )( cinfo ); +} + + +/* + * The rest of this file is a special implementation of the coefficient + * buffer controller. This is similar to jccoefct.c, but it handles only + * output from presupplied virtual arrays. Furthermore, we generate any + * dummy padding blocks on-the-fly rather than expecting them to be present + * in the arrays. + */ + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub;/* public fields */ + + JDIMENSION iMCU_row_num;/* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* Virtual block array for each component. */ + jvirt_barray_ptr * whole_image; + + /* Workspace for constructing dummy blocks at right/bottom edges. */ + JBLOCKROW dummy_buffer[C_MAX_BLOCKS_IN_MCU]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +LOCAL void +start_iMCU_row( j_compress_ptr cinfo ) { +/* Reset within-iMCU-row counters for a new row */ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if ( cinfo->comps_in_scan > 1 ) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if ( coef->iMCU_row_num < ( cinfo->total_iMCU_rows - 1 ) ) { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef( j_compress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + if ( pass_mode != JBUF_CRANK_DEST ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + coef->iMCU_row_num = 0; + start_iMCU_row( cinfo ); +} + + +/* + * Process some data. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output( j_compress_ptr cinfo, JSAMPIMAGE input_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, blockcnt; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + JBLOCKROW buffer_ptr; + jpeg_component_info * compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE ); + } + + /* Loop to process one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++ ) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + blockcnt = ( MCU_col_num < last_MCU_col ) ? compptr->MCU_width + : compptr->last_col_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + if ( ( coef->iMCU_row_num < last_iMCU_row ) || + ( yindex + yoffset < compptr->last_row_height ) ) { + /* Fill in pointers to real blocks in this row */ + buffer_ptr = buffer[ci][yindex + yoffset] + start_col; + for ( xindex = 0; xindex < blockcnt; xindex++ ) { + MCU_buffer[blkn++] = buffer_ptr++; + } + } else { + /* At bottom of image, need a whole row of dummy blocks */ + xindex = 0; + } + /* Fill in any dummy blocks needed in this row. + * Dummy blocks are filled in the same way as in jccoefct.c: + * all zeroes in the AC entries, DC entries equal to previous + * block's DC value. The init routine has already zeroed the + * AC entries, so we need only set the DC entries correctly. + */ + for (; xindex < compptr->MCU_width; xindex++ ) { + MCU_buffer[blkn] = coef->dummy_buffer[blkn]; + MCU_buffer[blkn][0][0] = MCU_buffer[blkn - 1][0][0]; + blkn++; + } + } + } + /* Try to write the MCU. */ + if ( !( *cinfo->entropy->encode_mcu )( cinfo, MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row( cinfo ); + return TRUE; +} + + +/* + * Initialize coefficient buffer controller. + * + * Each passed coefficient array must be the right size for that + * coefficient: width_in_blocks wide and height_in_blocks high, + * with unitheight at least v_samp_factor. + */ + +LOCAL void +transencode_coef_controller( j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays ) { + my_coef_ptr coef; + JBLOCKROW buffer; + int i; + + coef = (my_coef_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_coef_controller ) ); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + coef->pub.compress_data = compress_output; + + /* Save pointer to virtual arrays */ + coef->whole_image = coef_arrays; + + /* Allocate and pre-zero space for dummy DCT blocks. */ + buffer = (JBLOCKROW) + ( *cinfo->mem->alloc_large )( (j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + jzero_far( (void FAR *) buffer, C_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + for ( i = 0; i < C_MAX_BLOCKS_IN_MCU; i++ ) { + coef->dummy_buffer[i] = buffer + i; + } +} diff --git a/neo/renderer/jpeg-6/jdapimin.cpp b/neo/renderer/jpeg-6/jdapimin.cpp new file mode 100644 index 00000000..7fdc4be6 --- /dev/null +++ b/neo/renderer/jpeg-6/jdapimin.cpp @@ -0,0 +1,398 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_decompress( j_decompress_ptr cinfo ) { + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO( cinfo, SIZEOF( struct jpeg_decompress_struct ) ); + cinfo->err = err; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr( (j_common_ptr) cinfo ); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for ( i = 0; i < NUM_QUANT_TBLS; i++ ) { + cinfo->quant_tbl_ptrs[i] = NULL; + } + + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + jinit_marker_reader( cinfo ); + + /* And initialize the overall input controller. */ + jinit_input_controller( cinfo ); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL void +jpeg_destroy_decompress( j_decompress_ptr cinfo ) { + jpeg_destroy( (j_common_ptr) cinfo );/* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_decompress( j_decompress_ptr cinfo ) { + jpeg_abort( (j_common_ptr) cinfo );/* use common routine */ +} + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL void +jpeg_set_marker_processor( j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine ) { + if ( marker_code == JPEG_COM ) { + cinfo->marker->process_COM = routine; + } else if ( marker_code >= JPEG_APP0 && marker_code <= JPEG_APP0 + 15 ) { + cinfo->marker->process_APPn[marker_code - JPEG_APP0] = routine; + } else { + ERREXIT1( cinfo, JERR_UNKNOWN_MARKER, marker_code ); + } +} + + +/* + * Set default decompression parameters. + */ + +LOCAL void +default_decompress_parms( j_decompress_ptr cinfo ) { + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch ( cinfo->num_components ) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if ( cinfo->saw_JFIF_marker ) { + cinfo->jpeg_color_space = JCS_YCbCr;/* JFIF implies YCbCr */ + } else if ( cinfo->saw_Adobe_marker ) { + switch ( cinfo->Adobe_transform ) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1( cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform ); + cinfo->jpeg_color_space = JCS_YCbCr;/* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if ( ( cid0 == 1 ) && ( cid1 == 2 ) && ( cid2 == 3 ) ) { + cinfo->jpeg_color_space = JCS_YCbCr; + } /* assume JFIF w/out marker */ + else if ( cid0 == 82 && cid1 == 71 && cid2 == 66 ) { + cinfo->jpeg_color_space = JCS_RGB; + } /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3( cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2 ); + cinfo->jpeg_color_space = JCS_YCbCr;/* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if ( cinfo->saw_Adobe_marker ) { + switch ( cinfo->Adobe_transform ) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1( cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform ); + cinfo->jpeg_color_space = JCS_YCCK;/* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL int +jpeg_read_header( j_decompress_ptr cinfo, boolean require_image ) { + int retcode; + + if ( ( cinfo->global_state != DSTATE_START ) && + ( cinfo->global_state != DSTATE_INHEADER ) ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + retcode = jpeg_consume_input( cinfo ); + + switch ( retcode ) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if ( require_image ) {/* Complain if application wanted an image */ + ERREXIT( cinfo, JERR_NO_IMAGE ); + } + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort( (j_common_ptr) cinfo );/* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL int +jpeg_consume_input( j_decompress_ptr cinfo ) { + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch ( cinfo->global_state ) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + ( *cinfo->inputctl->reset_input_controller )( cinfo ); + /* Initialize application's data source module */ + ( *cinfo->src->init_source )( cinfo ); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + if ( retcode == JPEG_REACHED_SOS ) {/* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms( cinfo ); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + break; + default: + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL boolean +jpeg_input_complete( j_decompress_ptr cinfo ) { + /* Check for valid jpeg object */ + if ( ( cinfo->global_state < DSTATE_START ) || + ( cinfo->global_state > DSTATE_STOPPING ) ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL boolean +jpeg_has_multiple_scans( j_decompress_ptr cinfo ) { + /* Only valid after jpeg_read_header completes */ + if ( ( cinfo->global_state < DSTATE_READY ) || + ( cinfo->global_state > DSTATE_STOPPING ) ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_decompress( j_decompress_ptr cinfo ) { + if ( ( ( cinfo->global_state == DSTATE_SCANNING ) || + ( cinfo->global_state == DSTATE_RAW_OK ) && !cinfo->buffered_image ) ) { + /* Terminate final pass of non-buffered mode */ + if ( cinfo->output_scanline < cinfo->output_height ) { + ERREXIT( cinfo, JERR_TOO_LITTLE_DATA ); + } + ( *cinfo->master->finish_output_pass )( cinfo ); + cinfo->global_state = DSTATE_STOPPING; + } else if ( cinfo->global_state == DSTATE_BUFIMAGE ) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if ( cinfo->global_state != DSTATE_STOPPING ) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Read until EOI */ + while ( !cinfo->inputctl->eoi_reached ) { + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return FALSE; + } /* Suspend, come back later */ + } + /* Do final cleanup */ + ( *cinfo->src->term_source )( cinfo ); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort( (j_common_ptr) cinfo ); + return TRUE; +} diff --git a/neo/renderer/jpeg-6/jdapistd.cpp b/neo/renderer/jpeg-6/jdapistd.cpp new file mode 100644 index 00000000..e08a9e6a --- /dev/null +++ b/neo/renderer/jpeg-6/jdapistd.cpp @@ -0,0 +1,282 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL boolean output_pass_setup JPP( (j_decompress_ptr cinfo) ); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_start_decompress( j_decompress_ptr cinfo ) { + if ( cinfo->global_state == DSTATE_READY ) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress( cinfo ); + if ( cinfo->buffered_image ) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if ( cinfo->global_state == DSTATE_PRELOAD ) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if ( cinfo->inputctl->has_multiple_scans ) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;; ) { + int retcode; + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* Absorb some more input */ + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + if ( retcode == JPEG_SUSPENDED ) { + return FALSE; + } + if ( retcode == JPEG_REACHED_EOI ) { + break; + } + /* Advance progress counter if appropriate */ + if ( ( cinfo->progress != NULL ) && + ( ( retcode == JPEG_ROW_COMPLETED ) || ( retcode == JPEG_REACHED_SOS ) ) ) { + if ( ++cinfo->progress->pass_counter >= cinfo->progress->pass_limit ) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if ( cinfo->global_state != DSTATE_PRESCAN ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup( cinfo ); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL boolean +output_pass_setup( j_decompress_ptr cinfo ) { + if ( cinfo->global_state != DSTATE_PRESCAN ) { + /* First call: do pass setup */ + ( *cinfo->master->prepare_for_output_pass )( cinfo ); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while ( cinfo->master->is_dummy_pass ) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while ( cinfo->output_scanline < cinfo->output_height ) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + ( *cinfo->main->process_data )( cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0 ); + if ( cinfo->output_scanline == last_scanline ) { + return FALSE; + } /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + ( *cinfo->master->finish_output_pass )( cinfo ); + ( *cinfo->master->prepare_for_output_pass )( cinfo ); + cinfo->output_scanline = 0; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL JDIMENSION +jpeg_read_scanlines( j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines ) { + JDIMENSION row_ctr; + + if ( cinfo->global_state != DSTATE_SCANNING ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->output_scanline >= cinfo->output_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + return 0; + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Process some data */ + row_ctr = 0; + ( *cinfo->main->process_data )( cinfo, scanlines, &row_ctr, max_lines ); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_read_raw_data( j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines ) { + JDIMENSION lines_per_iMCU_row; + + if ( cinfo->global_state != DSTATE_RAW_OK ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + if ( cinfo->output_scanline >= cinfo->output_height ) { + WARNMS( cinfo, JWRN_TOO_MUCH_DATA ); + return 0; + } + + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if ( max_lines < lines_per_iMCU_row ) { + ERREXIT( cinfo, JERR_BUFFER_SIZE ); + } + + /* Decompress directly into user's buffer. */ + if ( !( *cinfo->coef->decompress_data )( cinfo, data ) ) { + return 0; + } /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL boolean +jpeg_start_output( j_decompress_ptr cinfo, int scan_number ) { + if ( ( cinfo->global_state != DSTATE_BUFIMAGE ) && + ( cinfo->global_state != DSTATE_PRESCAN ) ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Limit scan number to valid range */ + if ( scan_number <= 0 ) { + scan_number = 1; + } + if ( ( cinfo->inputctl->eoi_reached ) && + ( scan_number > cinfo->input_scan_number ) ) { + scan_number = cinfo->input_scan_number; + } + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup( cinfo ); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_output( j_decompress_ptr cinfo ) { + if ( ( ( cinfo->global_state == DSTATE_SCANNING ) || + ( cinfo->global_state == DSTATE_RAW_OK ) && cinfo->buffered_image ) ) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + ( *cinfo->master->finish_output_pass )( cinfo ); + cinfo->global_state = DSTATE_BUFPOST; + } else if ( cinfo->global_state != DSTATE_BUFPOST ) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Read markers looking for SOS or EOI */ + while ( cinfo->input_scan_number <= cinfo->output_scan_number && + !cinfo->inputctl->eoi_reached ) { + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return FALSE; + } /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jdatadst.cpp b/neo/renderer/jpeg-6/jdatadst.cpp new file mode 100644 index 00000000..bbce9a3c --- /dev/null +++ b/neo/renderer/jpeg-6/jdatadst.cpp @@ -0,0 +1,150 @@ +/* + * jdatadst.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains compression data destination routines for the case of + * emitting JPEG data to a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * destination manager. + * IMPORTANT: we assume that fwrite() will correctly transcribe an array of + * JOCTETs into 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub;/* public fields */ + + FILE * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +METHODDEF void +init_destination( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = (JOCTET *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * SIZEOF( JOCTET ) ); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +METHODDEF boolean +empty_output_buffer( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + if ( JFWRITE( dest->outfile, dest->buffer, OUTPUT_BUF_SIZE ) != + (size_t) OUTPUT_BUF_SIZE ) { + ERREXIT( cinfo, JERR_FILE_WRITE ); + } + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_destination( j_compress_ptr cinfo ) { + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if ( datacount > 0 ) { + if ( JFWRITE( dest->outfile, dest->buffer, datacount ) != datacount ) { + ERREXIT( cinfo, JERR_FILE_WRITE ); + } + } + fflush( dest->outfile ); + /* Make sure we wrote the output file OK */ + if ( ferror( dest->outfile ) ) { + ERREXIT( cinfo, JERR_FILE_WRITE ); + } +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +GLOBAL void +jpeg_stdio_dest( j_compress_ptr cinfo, FILE * outfile ) { + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if ( cinfo->dest == NULL ) {/* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + ( * cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( my_destination_mgr ) ); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} diff --git a/neo/renderer/jpeg-6/jdatasrc.cpp b/neo/renderer/jpeg-6/jdatasrc.cpp new file mode 100644 index 00000000..421364f1 --- /dev/null +++ b/neo/renderer/jpeg-6/jdatasrc.cpp @@ -0,0 +1,199 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + unsigned char * infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF void +init_source( j_decompress_ptr cinfo ) { + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF boolean +fill_input_buffer( j_decompress_ptr cinfo ) { + my_src_ptr src = (my_src_ptr) cinfo->src; + + memcpy( src->buffer, src->infile, INPUT_BUF_SIZE ); + + src->infile += INPUT_BUF_SIZE; + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = INPUT_BUF_SIZE; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF void +skip_input_data( j_decompress_ptr cinfo, long num_bytes ) { + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if ( num_bytes > 0 ) { + while ( num_bytes > (long) src->pub.bytes_in_buffer ) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer( cinfo ); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_source( j_decompress_ptr cinfo ) { + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL void +jpeg_stdio_src( j_decompress_ptr cinfo, unsigned char * infile ) { + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if ( cinfo->src == NULL ) {/* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + ( * cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( my_source_mgr ) ); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF( JOCTET ) ); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart;/* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0;/* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL;/* until buffer loaded */ +} diff --git a/neo/renderer/jpeg-6/jdcoefct.cpp b/neo/renderer/jpeg-6/jdcoefct.cpp new file mode 100644 index 00000000..e63d1d49 --- /dev/null +++ b/neo/renderer/jpeg-6/jdcoefct.cpp @@ -0,0 +1,750 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub;/* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF int decompress_onepass +JPP( ( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) ); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF int decompress_data +JPP( ( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) ); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL boolean smoothing_ok JPP( (j_decompress_ptr cinfo) ); +METHODDEF int decompress_smooth_data +JPP( ( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) ); +#endif + + +LOCAL void +start_iMCU_row( j_decompress_ptr cinfo ) { +/* Reset within-iMCU-row counters for a new row (input side) */ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if ( cinfo->comps_in_scan > 1 ) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if ( cinfo->input_iMCU_row < ( cinfo->total_iMCU_rows - 1 ) ) { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF void +start_input_pass( j_decompress_ptr cinfo ) { + cinfo->input_iMCU_row = 0; + start_iMCU_row( cinfo ); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF void +start_output_pass( j_decompress_ptr cinfo ) { +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if ( coef->pub.coef_arrays != NULL ) { + if ( ( cinfo->do_block_smoothing ) && ( smoothing_ok( cinfo ) ) ) { + coef->pub.decompress_data = decompress_smooth_data; + } else { + coef->pub.decompress_data = decompress_data; + } + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF int +decompress_onepass( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info * compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++ ) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far( (void FAR *) coef->MCU_buffer[0], + (size_t) ( cinfo->blocks_in_MCU * SIZEOF( JBLOCK ) ) ); + if ( !( *cinfo->entropy->decode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if ( !compptr->component_needed ) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = ( MCU_col_num < last_MCU_col ) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[ci] + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + if ( ( cinfo->input_iMCU_row < last_iMCU_row ) || + ( yoffset + yindex < compptr->last_row_height ) ) { + output_col = start_col; + for ( xindex = 0; xindex < useful_width; xindex++ ) { + ( *inverse_DCT )( cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn + xindex], + output_ptr, output_col ); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if ( ++ ( cinfo->input_iMCU_row ) < cinfo->total_iMCU_rows ) { + start_iMCU_row( cinfo ); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + ( *cinfo->inputctl->finish_input_pass )( cinfo ); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF int +dummy_consume_data( j_decompress_ptr cinfo ) { + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF int +consume_data( j_decompress_ptr cinfo ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info * compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE ); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for ( yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++ ) { + for ( MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++ ) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for ( yindex = 0; yindex < compptr->MCU_height; yindex++ ) { + buffer_ptr = buffer[ci][yindex + yoffset] + start_col; + for ( xindex = 0; xindex < compptr->MCU_width; xindex++ ) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if ( !( *cinfo->entropy->decode_mcu )( cinfo, coef->MCU_buffer ) ) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if ( ++ ( cinfo->input_iMCU_row ) < cinfo->total_iMCU_rows ) { + start_iMCU_row( cinfo ); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + ( *cinfo->inputctl->finish_input_pass )( cinfo ); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF int +decompress_data( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info * compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while ( cinfo->input_scan_number < cinfo->output_scan_number || + ( cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row ) ) { + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return JPEG_SUSPENDED; + } + } + + /* OK, output from the virtual arrays. */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Don't bother to IDCT an uninteresting component. */ + if ( !compptr->component_needed ) { + continue; + } + /* Align the virtual buffer for this component. */ + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE ); + /* Count non-dummy DCT block rows in this iMCU row. */ + if ( cinfo->output_iMCU_row < last_iMCU_row ) { + block_rows = compptr->v_samp_factor; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( block_rows == 0 ) { + block_rows = compptr->v_samp_factor; + } + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for ( block_row = 0; block_row < block_rows; block_row++ ) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for ( block_num = 0; block_num < compptr->width_in_blocks; block_num++ ) { + ( *inverse_DCT )( cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col ); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if ( ++ ( cinfo->output_iMCU_row ) < cinfo->total_iMCU_rows ) { + return JPEG_ROW_COMPLETED; + } + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL boolean +smoothing_ok( j_decompress_ptr cinfo ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info * compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if ( ( !cinfo->progressive_mode ) || ( cinfo->coef_bits == NULL ) ) { + return FALSE; + } + + /* Allocate latch area if not already done */ + if ( coef->coef_bits_latch == NULL ) { + coef->coef_bits_latch = (int *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + ( SAVED_COEFS * SIZEOF( int ) ) ); + } + coef_bits_latch = coef->coef_bits_latch; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* All components' quantization values must already be latched. */ + if ( ( qtable = compptr->quant_table ) == NULL ) { + return FALSE; + } + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + for ( coefi = 0; coefi <= 5; coefi++ ) { + if ( qtable->quantval[coefi] == 0 ) { + return FALSE; + } + } + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if ( coef_bits[0] < 0 ) { + return FALSE; + } + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for ( coefi = 1; coefi <= 5; coefi++ ) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if ( coef_bits[coefi] != 0 ) { + smoothing_useful = TRUE; + } + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF int +decompress_smooth_data( j_decompress_ptr cinfo, JSAMPIMAGE output_buf ) { + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info * compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int * coef_bits; + JQUANT_TBL * quanttbl; + INT32 Q00, Q01, Q02, Q10, Q11, Q20, num; + int DC1, DC2, DC3, DC4, DC5, DC6, DC7, DC8, DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while ( cinfo->input_scan_number <= cinfo->output_scan_number && + !cinfo->inputctl->eoi_reached ) { + if ( cinfo->input_scan_number == cinfo->output_scan_number ) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = ( cinfo->Ss == 0 ) ? 1 : 0; + if ( cinfo->input_iMCU_row > cinfo->output_iMCU_row + delta ) { + break; + } + } + if ( ( *cinfo->inputctl->consume_input )( cinfo ) == JPEG_SUSPENDED ) { + return JPEG_SUSPENDED; + } + } + + /* OK, output from the virtual arrays. */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Don't bother to IDCT an uninteresting component. */ + if ( !compptr->component_needed ) { + continue; + } + /* Count non-dummy DCT block rows in this iMCU row. */ + if ( cinfo->output_iMCU_row < last_iMCU_row ) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2;/* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( block_rows == 0 ) { + block_rows = compptr->v_samp_factor; + } + access_rows = block_rows;/* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if ( cinfo->output_iMCU_row > 0 ) { + access_rows += compptr->v_samp_factor;/* prior iMCU row too */ + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + ( cinfo->output_iMCU_row - 1 ) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE ); + buffer += compptr->v_samp_factor;/* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = ( *cinfo->mem->access_virt_barray ) + ( (j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE ); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + ( ci * SAVED_COEFS ); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[1]; + Q10 = quanttbl->quantval[2]; + Q20 = quanttbl->quantval[3]; + Q11 = quanttbl->quantval[4]; + Q02 = quanttbl->quantval[5]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for ( block_row = 0; block_row < block_rows; block_row++ ) { + buffer_ptr = buffer[block_row]; + if ( ( first_row ) && ( block_row == 0 ) ) { + prev_block_row = buffer_ptr; + } else { + prev_block_row = buffer[block_row - 1]; + } + if ( ( last_row ) && ( block_row == block_rows - 1 ) ) { + next_block_row = buffer_ptr; + } else { + next_block_row = buffer[block_row + 1]; + } + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for ( block_num = 0; block_num <= last_block_column; block_num++ ) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row( buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1 ); + /* Update DC values */ + if ( block_num < last_block_column ) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ( ( ( Al = coef_bits[1] ) != 0 ) && ( workspace[1] == 0 ) ) { + num = 36 * Q00 * ( DC4 - DC6 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q01 << 7 ) + num ) / ( Q01 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q01 << 7 ) - num ) / ( Q01 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[1] = (JCOEF) pred; + } + /* AC10 */ + if ( ( ( Al = coef_bits[2] ) != 0 ) && ( workspace[8] == 0 ) ) { + num = 36 * Q00 * ( DC2 - DC8 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q10 << 7 ) + num ) / ( Q10 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q10 << 7 ) - num ) / ( Q10 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[8] = (JCOEF) pred; + } + /* AC20 */ + if ( ( ( Al = coef_bits[3] ) != 0 ) && ( workspace[16] == 0 ) ) { + num = 9 * Q00 * ( DC2 + DC8 - 2 * DC5 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q20 << 7 ) + num ) / ( Q20 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q20 << 7 ) - num ) / ( Q20 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[16] = (JCOEF) pred; + } + /* AC11 */ + if ( ( ( Al = coef_bits[4] ) != 0 ) && ( workspace[9] == 0 ) ) { + num = 5 * Q00 * ( DC1 - DC3 - DC7 + DC9 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q11 << 7 ) + num ) / ( Q11 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q11 << 7 ) - num ) / ( Q11 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[9] = (JCOEF) pred; + } + /* AC02 */ + if ( ( ( Al = coef_bits[5] ) != 0 ) && ( workspace[2] == 0 ) ) { + num = 9 * Q00 * ( DC4 + DC6 - 2 * DC5 ); + if ( num >= 0 ) { + pred = (int) ( ( ( Q02 << 7 ) + num ) / ( Q02 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + } else { + pred = (int) ( ( ( Q02 << 7 ) - num ) / ( Q02 << 8 ) ); + if ( ( Al > 0 ) && ( pred >= ( 1 << Al ) ) ) { + pred = ( 1 << Al ) - 1; + } + pred = -pred; + } + workspace[2] = (JCOEF) pred; + } + /* OK, do the IDCT */ + ( *inverse_DCT )( cinfo, compptr, (JCOEFPTR) workspace, + output_ptr, output_col ); + /* Advance for next column */ + DC1 = DC2; + DC2 = DC3; + DC4 = DC5; + DC5 = DC6; + DC7 = DC8; + DC8 = DC9; + buffer_ptr++, prev_block_row++, next_block_row++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if ( ++ ( cinfo->output_iMCU_row ) < cinfo->total_iMCU_rows ) { + return JPEG_ROW_COMPLETED; + } + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_d_coef_controller( j_decompress_ptr cinfo, boolean need_full_buffer ) { + my_coef_ptr coef; + + coef = (my_coef_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_coef_controller ) ); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if ( need_full_buffer ) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info * compptr; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if ( cinfo->progressive_mode ) { + access_rows *= 3; + } +#endif + coef->whole_image[ci] = ( *cinfo->mem->request_virt_barray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up( (long) compptr->width_in_blocks, + (long) compptr->h_samp_factor ), + (JDIMENSION) jround_up( (long) compptr->height_in_blocks, + (long) compptr->v_samp_factor ), + (JDIMENSION) access_rows ); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image;/* link to virtual arrays */ +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + ( *cinfo->mem->alloc_large )( (j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF( JBLOCK ) ); + for ( i = 0; i < D_MAX_BLOCKS_IN_MCU; i++ ) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL;/* flag for no virtual arrays */ + } +} diff --git a/neo/renderer/jpeg-6/jdcolor.cpp b/neo/renderer/jpeg-6/jdcolor.cpp new file mode 100644 index 00000000..d72b260b --- /dev/null +++ b/neo/renderer/jpeg-6/jdcolor.cpp @@ -0,0 +1,371 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub;/* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ( (INT32) 1 << ( SCALEBITS - 1 ) ) +#define FIX( x ) ( (INT32) ( ( x ) * ( 1L << SCALEBITS ) + 0.5 ) ) + + +/* + * Initialize tables for YCC->RGB colorspace conversion. + */ + +LOCAL void +build_ycc_rgb_table( j_decompress_ptr cinfo ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( int ) ); + cconvert->Cb_b_tab = (int *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( int ) ); + cconvert->Cr_g_tab = (INT32 *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( INT32 ) ); + cconvert->Cb_g_tab = (INT32 *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( INT32 ) ); + + for ( i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++ ) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT( FIX( 1.40200 ) * x + ONE_HALF, SCALEBITS ); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT( FIX( 1.77200 ) * x + ONE_HALF, SCALEBITS ); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = ( -FIX( 0.71414 ) ) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = ( -FIX( 0.34414 ) ) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +ycc_rgb_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while ( --num_rows >= 0 ) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for ( col = 0; col < num_cols; col++ ) { + y = GETJSAMPLE( inptr0[col] ); + cb = GETJSAMPLE( inptr1[col] ); + cr = GETJSAMPLE( inptr2[col] ); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ( (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], + SCALEBITS ) )]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF void +null_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while ( --num_rows >= 0 ) { + for ( ci = 0; ci < num_components; ci++ ) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for ( count = num_cols; count > 0; count-- ) { + *outptr = *inptr++;/* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF void +grayscale_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + jcopy_sample_rows( input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width ); +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF void +ycck_cmyk_convert( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows ) { + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while ( --num_rows >= 0 ) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for ( col = 0; col < num_cols; col++ ) { + y = GETJSAMPLE( inptr0[col] ); + cb = GETJSAMPLE( inptr1[col] ); + cr = GETJSAMPLE( inptr2[col] ); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - ( y + Crrtab[cr] )];/* red */ + outptr[1] = range_limit[MAXJSAMPLE - ( y + /* green */ + ( (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], + SCALEBITS ) ) )]; + outptr[2] = range_limit[MAXJSAMPLE - ( y + Cbbtab[cb] )];/* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col];/* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +start_pass_dcolor( j_decompress_ptr cinfo ) { + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL void +jinit_color_deconverter( j_decompress_ptr cinfo ) { + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_color_deconverter ) ); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch ( cinfo->jpeg_color_space ) { + case JCS_GRAYSCALE: + if ( cinfo->num_components != 1 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + + case JCS_RGB: + case JCS_YCbCr: + if ( cinfo->num_components != 3 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + + case JCS_CMYK: + case JCS_YCCK: + if ( cinfo->num_components != 4 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + + default: /* JCS_UNKNOWN can be anything */ + if ( cinfo->num_components < 1 ) { + ERREXIT( cinfo, JERR_BAD_J_COLORSPACE ); + } + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch ( cinfo->out_color_space ) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if ( ( cinfo->jpeg_color_space == JCS_GRAYSCALE ) || + ( cinfo->jpeg_color_space == JCS_YCbCr ) ) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for ( ci = 1; ci < cinfo->num_components; ci++ ) { + cinfo->comp_info[ci].component_needed = FALSE; + } + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if ( cinfo->jpeg_color_space == JCS_YCbCr ) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table( cinfo ); + } else if ( cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3 ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if ( cinfo->jpeg_color_space == JCS_YCCK ) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table( cinfo ); + } else if ( cinfo->jpeg_color_space == JCS_CMYK ) { + cconvert->pub.color_convert = null_convert; + } else { + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + + default: + /* Permit null conversion to same output space */ + if ( cinfo->out_color_space == cinfo->jpeg_color_space ) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else {/* unsupported non-null conversion */ + ERREXIT( cinfo, JERR_CONVERSION_NOTIMPL ); + } + break; + } + + if ( cinfo->quantize_colors ) { + cinfo->output_components = 1; + } /* single colormapped output component */ + else { + cinfo->output_components = cinfo->out_color_components; + } +} diff --git a/neo/renderer/jpeg-6/jdct.h b/neo/renderer/jpeg-6/jdct.h new file mode 100644 index 00000000..3ce790bc --- /dev/null +++ b/neo/renderer/jpeg-6/jdct.h @@ -0,0 +1,176 @@ +/* + * jdct.h + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN void jpeg_fdct_islow JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_ifast JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_float JPP((FAST_FLOAT * data)); + +EXTERN void jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif diff --git a/neo/renderer/jpeg-6/jddctmgr.cpp b/neo/renderer/jpeg-6/jddctmgr.cpp new file mode 100644 index 00000000..2f40c48a --- /dev/null +++ b/neo/renderer/jpeg-6/jddctmgr.cpp @@ -0,0 +1,270 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub;/* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF void +start_pass( j_decompress_ptr cinfo ) { + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info * compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Select the proper IDCT routine for this component's scaling */ + switch ( compptr->DCT_scaled_size ) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW;/* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW;/* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW;/* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch ( cinfo->dct_method ) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + break; + default: + ERREXIT1( cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size ); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if ( ( !compptr->component_needed ) || ( idct->cur_method[ci] == method ) ) { + continue; + } + qtbl = compptr->quant_table; + if ( qtbl == NULL ) {/* happens if no data yet for component */ + continue; + } + idct->cur_method[ci] = method; + switch ( method ) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored in natural order as ints. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for ( i = 0; i < DCTSIZE2; i++ ) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[jpeg_zigzag_order[i]]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. The multipliers are stored in natural order. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for ( i = 0; i < DCTSIZE2; i++ ) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE( MULTIPLY16V16( (INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i] ), + CONST_BITS - IFAST_SCALE_BITS ); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * The multipliers are stored in natural order. + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for ( row = 0; row < DCTSIZE; row++ ) { + for ( col = 0; col < DCTSIZE; col++ ) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ( (double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col] ); + i++; + } + } + } + break; +#endif + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL void +jinit_inverse_dct( j_decompress_ptr cinfo ) { + my_idct_ptr idct; + int ci; + jpeg_component_info * compptr; + + idct = (my_idct_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_idct_controller ) ); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( multiplier_table ) ); + MEMZERO( compptr->dct_table, SIZEOF( multiplier_table ) ); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/neo/renderer/jpeg-6/jdhuff.cpp b/neo/renderer/jpeg-6/jdhuff.cpp new file mode 100644 index 00000000..e2512c40 --- /dev/null +++ b/neo/renderer/jpeg-6/jdhuff.cpp @@ -0,0 +1,587 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN];/* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE( dest, src ) ( ( dest ) = ( src ) ) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE( dest, src ) \ + ( ( dest ).last_dc_val[0] = ( src ).last_dc_val[0], \ + ( dest ).last_dc_val[1] = ( src ).last_dc_val[1], \ + ( dest ).last_dc_val[2] = ( src ).last_dc_val[2], \ + ( dest ).last_dc_val[3] = ( src ).last_dc_val[3] ) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub;/* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate;/* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go;/* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_huff_decoder( j_decompress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if ( ( cinfo->Ss != 0 ) || ( cinfo->Se != DCTSIZE2 - 1 ) || + ( cinfo->Ah != 0 ) || ( cinfo->Al != 0 ) ) { + WARNMS( cinfo, JWRN_NOT_SEQUENTIAL ); + } + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + if ( ( dctbl < 0 ) || ( dctbl >= NUM_HUFF_TBLS ) || + ( cinfo->dc_huff_tbl_ptrs[dctbl] == NULL ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, dctbl ); + } + if ( ( actbl < 0 ) || ( actbl >= NUM_HUFF_TBLS ) || + ( cinfo->ac_huff_tbl_ptrs[actbl] == NULL ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, actbl ); + } + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + &entropy->dc_derived_tbls[dctbl] ); + jpeg_make_d_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + &entropy->ac_derived_tbls[actbl] ); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0;/* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jdphuff.c. + */ + +GLOBAL void +jpeg_make_d_derived_tbl( j_decompress_ptr cinfo, JHUFF_TBL * htbl, + d_derived_tbl ** pdtbl ) { + d_derived_tbl * dtbl; + int p, i, l, si; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if ( *pdtbl == NULL ) { + *pdtbl = (d_derived_tbl *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( d_derived_tbl ) ); + } + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for ( l = 1; l <= 16; l++ ) { + for ( i = 1; i <= (int) htbl->bits[l]; i++ ) { + huffsize[p++] = (char) l; + } + } + huffsize[p] = 0; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while ( huffsize[p] ) { + while ( ( (int) huffsize[p] ) == si ) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for ( l = 1; l <= 16; l++ ) { + if ( htbl->bits[l] ) { + dtbl->valptr[l] = p;/* huffval[] index of 1st symbol of code length l */ + dtbl->mincode[l] = huffcode[p];/* minimum code of length l */ + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p - 1];/* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1;/* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL;/* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO( dtbl->look_nbits, SIZEOF( dtbl->look_nbits ) ); + + p = 0; + for ( l = 1; l <= HUFF_LOOKAHEAD; l++ ) { + for ( i = 1; i <= (int) htbl->bits[l]; i++, p++ ) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << ( HUFF_LOOKAHEAD - l ); + for ( ctr = 1 << ( HUFF_LOOKAHEAD - l ); ctr > 0; ctr-- ) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS ( BIT_BUF_SIZE - 7 ) +#endif + + +GLOBAL boolean +jpeg_fill_bit_buffer( bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits ) { +/* Load up the bit buffer to a depth of at least nbits */ +/* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + register int c; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + + while ( bits_left < MIN_GET_BITS ) { + /* Attempt to read a byte */ + if ( state->unread_marker != 0 ) { + goto no_more_data; + } /* can't advance past a marker */ + + if ( bytes_in_buffer == 0 ) { + if ( !( *state->cinfo->src->fill_input_buffer )( state->cinfo ) ) { + return FALSE; + } + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET( *next_input_byte++ ); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if ( c == 0xFF ) { + do { + if ( bytes_in_buffer == 0 ) { + if ( !( *state->cinfo->src->fill_input_buffer )( state->cinfo ) ) { + return FALSE; + } + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET( *next_input_byte++ ); + } while ( c == 0xFF ); + + if ( c == 0 ) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. */ + /* Better put it back for use later */ + state->unread_marker = c; + +no_more_data: + /* There should be enough bits still left in the data segment; */ + /* if so, just break out of the outer while loop. */ + if ( bits_left >= nbits ) { + break; + } + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * Note that this code will be repeated for each byte demanded + * for the rest of the segment. We use a nonvolatile flag to ensure + * that only one warning message appears. + */ + if ( ! * ( state->printed_eod_ptr ) ) { + WARNMS( state->cinfo, JWRN_HIT_MARKER ); + *( state->printed_eod_ptr ) = TRUE; + } + c = 0;/* insert a zero byte into bit buffer */ + } + } + + /* OK, load c into get_buffer */ + get_buffer = ( get_buffer << 8 ) | c; + bits_left += 8; + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL int +jpeg_huff_decode( bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits ) { + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER( *state, l, return -1 ); + code = GET_BITS( l ); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while ( code > htbl->maxcode[l] ) { + code <<= 1; + + CHECK_BIT_BUFFER( *state, 1, return -1 ); + code |= GET_BITS( 1 ); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if ( l > 16 ) { + WARNMS( state->cinfo, JWRN_HUFF_BAD_CODE ); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ htbl->valptr[l] + + ( (int) ( code - htbl->mincode[l] ) ) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND( x, s ) ( ( x ) < ( 1 << ( ( s ) - 1 ) ) ? ( x ) + ( ( ( -1 ) << ( s ) ) + 1 ) : ( x ) ) + +#else + +#define HUFF_EXTEND( x, s ) ( ( x ) < extend_test[s] ? ( x ) + extend_offset[s] : ( x ) ) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ +{ 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ +{ 0, ( ( -1 ) << 1 ) + 1, ( ( -1 ) << 2 ) + 1, ( ( -1 ) << 3 ) + 1, ( ( -1 ) << 4 ) + 1, + ( ( -1 ) << 5 ) + 1, ( ( -1 ) << 6 ) + 1, ( ( -1 ) << 7 ) + 1, ( ( -1 ) << 8 ) + 1, + ( ( -1 ) << 9 ) + 1, ( ( -1 ) << 10 ) + 1, ( ( -1 ) << 11 ) + 1, ( ( -1 ) << 12 ) + 1, + ( ( -1 ) << 13 ) + 1, ( ( -1 ) << 14 ) + 1, ( ( -1 ) << 15 ) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart( j_decompress_ptr cinfo ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if ( !( *cinfo->marker->read_restart_marker )( cinfo ) ) { + return FALSE; + } + + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + entropy->saved.last_dc_val[ci] = 0; + } + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF boolean +decode_mcu( j_decompress_ptr cinfo, JBLOCKROW * MCU_data ) { + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * dctbl; + d_derived_tbl * actbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !process_restart( cinfo ) ) { + return FALSE; + } + } + } + + /* Load up working state */ + BITREAD_LOAD_STATE( cinfo, entropy->bitstate ); + ASSIGN_STATE( state, entropy->saved ); + + /* Outer loop handles each block in the MCU */ + + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + dctbl = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + actbl = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE( s, br_state, dctbl, return FALSE, label1 ); + if ( s ) { + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + + r = GET_BITS( s ); + s = HUFF_EXTEND( r, s ); + } + + /* Shortcut if component's values are not interesting */ + if ( !compptr->component_needed ) { + goto skip_ACs; + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + ( *block )[0] = (JCOEF) s; + + /* Do we need to decode the AC coefficients for this component? */ + if ( compptr->DCT_scaled_size > 1 ) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for ( k = 1; k < DCTSIZE2; k++ ) { + HUFF_DECODE( s, br_state, actbl, return FALSE, label2 ); + + r = s >> 4; + s &= 15; + + if ( s ) { + k += r; + + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + r = GET_BITS( s ); + s = HUFF_EXTEND( r, s ); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + ( *block )[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if ( r != 15 ) { + break; + } + k += 15; + } + } + + } else { +skip_ACs: + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for ( k = 1; k < DCTSIZE2; k++ ) { + HUFF_DECODE( s, br_state, actbl, return FALSE, label3 ); + + r = s >> 4; + s &= 15; + + if ( s ) { + k += r; + + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + DROP_BITS( s ); + } else { + if ( r != 15 ) { + break; + } + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE( cinfo, entropy->bitstate ); + ASSIGN_STATE( entropy->saved, state ); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL void +jinit_huff_decoder( j_decompress_ptr cinfo ) { + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( huff_entropy_decoder ) ); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/neo/renderer/jpeg-6/jdhuff.h b/neo/renderer/jpeg-6/jdhuff.h new file mode 100644 index 00000000..d375c781 --- /dev/null +++ b/neo/renderer/jpeg-6/jdhuff.h @@ -0,0 +1,202 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 mincode[17]; /* smallest code of length k */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + int valptr[17]; /* huffval[] index of 1st symbol of length k */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't do this with + * something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + boolean printed_eod; /* flag to suppress multiple warning msgs */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* current data source state */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + int unread_marker; /* nonzero if we have hit a marker */ + /* bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* pointers needed by jpeg_fill_bit_buffer */ + j_decompress_ptr cinfo; /* back link to decompress master record */ + boolean * printed_eod_ptr; /* => flag in permanent state */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + br_state.unread_marker = cinfop->unread_marker; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; \ + br_state.printed_eod_ptr = & permstate.printed_eod + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + cinfop->unread_marker = br_state.unread_marker; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN boolean jpeg_fill_bit_buffer JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN int jpeg_huff_decode JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits)); diff --git a/neo/renderer/jpeg-6/jdinput.cpp b/neo/renderer/jpeg-6/jdinput.cpp new file mode 100644 index 00000000..8171e7a1 --- /dev/null +++ b/neo/renderer/jpeg-6/jdinput.cpp @@ -0,0 +1,392 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub;/* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF int consume_markers JPP( (j_decompress_ptr cinfo) ); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL void +initial_setup( j_decompress_ptr cinfo ) { +/* Called once, when first SOS marker is reached */ + int ci; + jpeg_component_info * compptr; + + /* Make sure image isn't bigger than I can handle */ + if ( ( (long) cinfo->image_height > (long) JPEG_MAX_DIMENSION ) || + ( (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION ) ) { + ERREXIT1( cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION ); + } + + /* For now, precision must match compiled-in value... */ + if ( cinfo->data_precision != BITS_IN_JSAMPLE ) { + ERREXIT1( cinfo, JERR_BAD_PRECISION, cinfo->data_precision ); + } + + /* Check that number of components won't exceed internal array sizes */ + if ( cinfo->num_components > MAX_COMPONENTS ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS ); + } + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( ( compptr->h_samp_factor <= 0 ) || ( compptr->h_samp_factor > MAX_SAMP_FACTOR ) || + ( compptr->v_samp_factor <= 0 ) || ( compptr->v_samp_factor > MAX_SAMP_FACTOR ) ) { + ERREXIT( cinfo, JERR_BAD_SAMPLING ); + } + cinfo->max_h_samp_factor = MAX( cinfo->max_h_samp_factor, + compptr->h_samp_factor ); + cinfo->max_v_samp_factor = MAX( cinfo->max_v_samp_factor, + compptr->v_samp_factor ); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor ); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor ); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + + /* Decide whether file contains multiple scans */ + if ( ( cinfo->comps_in_scan < cinfo->num_components ) || ( cinfo->progressive_mode ) ) { + cinfo->inputctl->has_multiple_scans = TRUE; + } else { + cinfo->inputctl->has_multiple_scans = FALSE; + } +} + + +LOCAL void +per_scan_setup( j_decompress_ptr cinfo ) { +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ + int ci, mcublks, tmp; + jpeg_component_info * compptr; + + if ( cinfo->comps_in_scan == 1 ) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) ( compptr->height_in_blocks % compptr->v_samp_factor ); + if ( tmp == 0 ) { + tmp = compptr->v_samp_factor; + } + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if ( ( cinfo->comps_in_scan <= 0 ) || ( cinfo->comps_in_scan > MAX_COMPS_IN_SCAN ) ) { + ERREXIT2( cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN ); + } + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + + cinfo->blocks_in_MCU = 0; + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) ( compptr->width_in_blocks % compptr->MCU_width ); + if ( tmp == 0 ) { + tmp = compptr->MCU_width; + } + compptr->last_col_width = tmp; + tmp = (int) ( compptr->height_in_blocks % compptr->MCU_height ); + if ( tmp == 0 ) { + tmp = compptr->MCU_height; + } + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if ( cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU ) { + ERREXIT( cinfo, JERR_BAD_MCU_SIZE ); + } + while ( mcublks-- > 0 ) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL void +latch_quant_tables( j_decompress_ptr cinfo ) { + int ci, qtblno; + jpeg_component_info * compptr; + JQUANT_TBL * qtbl; + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if ( compptr->quant_table != NULL ) { + continue; + } + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if ( ( qtblno < 0 ) || ( qtblno >= NUM_QUANT_TBLS ) || + ( cinfo->quant_tbl_ptrs[qtblno] == NULL ) ) { + ERREXIT1( cinfo, JERR_NO_QUANT_TABLE, qtblno ); + } + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( JQUANT_TBL ) ); + MEMCOPY( qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF( JQUANT_TBL ) ); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF void +start_input_pass( j_decompress_ptr cinfo ) { + per_scan_setup( cinfo ); + latch_quant_tables( cinfo ); + ( *cinfo->entropy->start_pass )( cinfo ); + ( *cinfo->coef->start_input_pass )( cinfo ); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF void +finish_input_pass( j_decompress_ptr cinfo ) { + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF int +consume_markers( j_decompress_ptr cinfo ) { + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if ( inputctl->pub.eoi_reached ) {/* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + } + + val = ( *cinfo->marker->read_markers )( cinfo ); + + switch ( val ) { + case JPEG_REACHED_SOS:/* Found SOS */ + if ( inputctl->inheaders ) {/* 1st SOS */ + initial_setup( cinfo ); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapi.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if ( !inputctl->pub.has_multiple_scans ) { + ERREXIT( cinfo, JERR_EOI_EXPECTED ); + } /* Oops, I wasn't expecting this! */ + start_input_pass( cinfo ); + } + break; + case JPEG_REACHED_EOI:/* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if ( inputctl->inheaders ) {/* Tables-only datastream, apparently */ + if ( cinfo->marker->saw_SOF ) { + ERREXIT( cinfo, JERR_SOF_NO_SOS ); + } + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if ( cinfo->output_scan_number > cinfo->input_scan_number ) { + cinfo->output_scan_number = cinfo->input_scan_number; + } + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF void +reset_input_controller( j_decompress_ptr cinfo ) { + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE;/* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + ( *cinfo->err->reset_error_mgr )( (j_common_ptr) cinfo ); + ( *cinfo->marker->reset_marker_reader )( cinfo ); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_input_controller( j_decompress_ptr cinfo ) { + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( my_input_controller ) ); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE;/* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/neo/renderer/jpeg-6/jdmainct.cpp b/neo/renderer/jpeg-6/jdmainct.cpp new file mode 100644 index 00000000..f8e2fa1c --- /dev/null +++ b/neo/renderer/jpeg-6/jdmainct.cpp @@ -0,0 +1,514 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub;/* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr;/* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr;/* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr;/* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF void process_data_simple_main +JPP( ( j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION * out_row_ctr, JDIMENSION out_rows_avail ) ); +METHODDEF void process_data_context_main +JPP( ( j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION * out_row_ctr, JDIMENSION out_rows_avail ) ); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void process_data_crank_post +JPP( ( j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION * out_row_ctr, JDIMENSION out_rows_avail ) ); +#endif + + +LOCAL void +alloc_funny_pointers( j_decompress_ptr cinfo ) { +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info * compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + main->xbuffer[0] = (JSAMPIMAGE) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF( JSAMPARRAY ) ); + main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * ( rgroup * ( M + 4 ) ) * SIZEOF( JSAMPROW ) ); + xbuf += rgroup; /* want one row group at negative offsets */ + main->xbuffer[0][ci] = xbuf; + xbuf += rgroup * ( M + 4 ); + main->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL void +make_funny_pointers( j_decompress_ptr cinfo ) { +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info * compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = main->buffer[ci]; + for ( i = 0; i < rgroup * ( M + 2 ); i++ ) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for ( i = 0; i < rgroup * 2; i++ ) { + xbuf1[rgroup * ( M - 2 ) + i] = buf[rgroup * M + i]; + xbuf1[rgroup * M + i] = buf[rgroup * ( M - 2 ) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for ( i = 0; i < rgroup; i++ ) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL void +set_wraparound_pointers( j_decompress_ptr cinfo ) { +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info * compptr; + JSAMPARRAY xbuf0, xbuf1; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + for ( i = 0; i < rgroup; i++ ) { + xbuf0[i - rgroup] = xbuf0[rgroup * ( M + 1 ) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup * ( M + 1 ) + i]; + xbuf0[rgroup * ( M + 2 ) + i] = xbuf0[i]; + xbuf1[rgroup * ( M + 2 ) + i] = xbuf1[i]; + } + } +} + + +LOCAL void +set_bottom_pointers( j_decompress_ptr cinfo ) { +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info * compptr; + JSAMPARRAY xbuf; + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) ( compptr->downsampled_height % (JDIMENSION) iMCUheight ); + if ( rows_left == 0 ) { + rows_left = iMCUheight; + } + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if ( ci == 0 ) { + main->rowgroups_avail = (JDIMENSION) ( ( rows_left - 1 ) / rgroup + 1 ); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = main->xbuffer[main->whichptr][ci]; + for ( i = 0; i < rgroup * 2; i++ ) { + xbuf[rows_left + i] = xbuf[rows_left - 1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main( j_decompress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + + switch ( pass_mode ) { + case JBUF_PASS_THRU: + if ( cinfo->upsample->need_context_rows ) { + main->pub.process_data = process_data_context_main; + make_funny_pointers( cinfo );/* Create the xbuffer[] lists */ + main->whichptr = 0;/* Read first iMCU row into xbuffer[0] */ + main->context_state = CTX_PREPARE_FOR_IMCU; + main->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + main->pub.process_data = process_data_simple_main; + } + main->buffer_full = FALSE;/* Mark buffer empty */ + main->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + main->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF void +process_data_simple_main( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if ( !main->buffer_full ) { + if ( !( *cinfo->coef->decompress_data )( cinfo, main->buffer ) ) { + return; + } /* suspension forced, can do nothing more */ + main->buffer_full = TRUE;/* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + ( *cinfo->post->post_process_data )( cinfo, main->buffer, + &main->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail ); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if ( main->rowgroup_ctr >= rowgroups_avail ) { + main->buffer_full = FALSE; + main->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF void +process_data_context_main( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if ( !main->buffer_full ) { + if ( !( *cinfo->coef->decompress_data )( cinfo, + main->xbuffer[main->whichptr] ) ) { + return; + } /* suspension forced, can do nothing more */ + main->buffer_full = TRUE;/* OK, we have an iMCU row to work with */ + main->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch ( main->context_state ) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + ( *cinfo->post->post_process_data )( cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail ); + if ( main->rowgroup_ctr < main->rowgroups_avail ) { + return; + } /* Need to suspend */ + main->context_state = CTX_PREPARE_FOR_IMCU; + if ( *out_row_ctr >= out_rows_avail ) { + return; + } /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + main->rowgroup_ctr = 0; + main->rowgroups_avail = (JDIMENSION) ( cinfo->min_DCT_scaled_size - 1 ); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if ( main->iMCU_row_ctr == cinfo->total_iMCU_rows ) { + set_bottom_pointers( cinfo ); + } + main->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + ( *cinfo->post->post_process_data )( cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail ); + if ( main->rowgroup_ctr < main->rowgroups_avail ) { + return; + } /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if ( main->iMCU_row_ctr == 1 ) { + set_wraparound_pointers( cinfo ); + } + /* Prepare to load new iMCU row using other xbuffer list */ + main->whichptr ^= 1;/* 0=>1 or 1=>0 */ + main->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + main->rowgroup_ctr = (JDIMENSION) ( cinfo->min_DCT_scaled_size + 1 ); + main->rowgroups_avail = (JDIMENSION) ( cinfo->min_DCT_scaled_size + 2 ); + main->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void +process_data_crank_post( j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + ( *cinfo->post->post_process_data )( cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail ); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_d_main_controller( j_decompress_ptr cinfo, boolean need_full_buffer ) { + my_main_ptr main; + int ci, rgroup, ngroups; + jpeg_component_info * compptr; + + main = (my_main_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_main_controller ) ); + cinfo->main = (struct jpeg_d_main_controller *) main; + main->pub.start_pass = start_pass_main; + + if ( need_full_buffer ) {/* shouldn't happen */ + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if ( cinfo->upsample->need_context_rows ) { + if ( cinfo->min_DCT_scaled_size < 2 ) {/* unsupported, see comments above */ + ERREXIT( cinfo, JERR_NOTIMPL ); + } + alloc_funny_pointers( cinfo );/* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + rgroup = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + main->buffer[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) ( rgroup * ngroups ) ); + } +} diff --git a/neo/renderer/jpeg-6/jdmarker.cpp b/neo/renderer/jpeg-6/jdmarker.cpp new file mode 100644 index 00000000..68dedf39 --- /dev/null +++ b/neo/renderer/jpeg-6/jdmarker.cpp @@ -0,0 +1,1092 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS( cinfo ) \ + struct jpeg_source_mgr * datasrc = ( cinfo )->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC( cinfo ) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD( cinfo ) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL( cinfo, action ) \ + if ( bytes_in_buffer == 0 ) { \ + if ( !( *datasrc->fill_input_buffer )( cinfo ) ) \ + { action; } \ + INPUT_RELOAD( cinfo ); \ + } \ + bytes_in_buffer-- + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE( cinfo, V, action ) \ + MAKESTMT( MAKE_BYTE_AVAIL( cinfo, action ); \ + V = GETJOCTET( *next_input_byte++ ); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES( cinfo, V, action ) \ + MAKESTMT( MAKE_BYTE_AVAIL( cinfo, action ); \ + V = ( (unsigned int) GETJOCTET( *next_input_byte++ ) ) << 8; \ + MAKE_BYTE_AVAIL( cinfo, action ); \ + V += GETJOCTET( *next_input_byte++ ); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters can + * fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments, + * but we use skip_input_data to get past those, and thereby put the problem + * on the source manager's shoulders. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL boolean +get_soi( j_decompress_ptr cinfo ) { +/* Process an SOI marker */ + int i; + + TRACEMS( cinfo, 1, JTRC_SOI ); + + if ( cinfo->marker->saw_SOI ) { + ERREXIT( cinfo, JERR_SOI_DUPLICATE ); + } + + /* Reset all parameters that are defined to be reset by SOI */ + + for ( i = 0; i < NUM_ARITH_TBLS; i++ ) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE;/* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->density_unit = 0;/* set default JFIF APP0 values */ + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL boolean +get_sof( j_decompress_ptr cinfo, boolean is_prog, boolean is_arith ) { +/* Process a SOFn marker */ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS( cinfo ); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES( cinfo, length, return FALSE ); + + INPUT_BYTE( cinfo, cinfo->data_precision, return FALSE ); + INPUT_2BYTES( cinfo, cinfo->image_height, return FALSE ); + INPUT_2BYTES( cinfo, cinfo->image_width, return FALSE ); + INPUT_BYTE( cinfo, cinfo->num_components, return FALSE ); + + length -= 8; + + TRACEMS4( cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components ); + + if ( cinfo->marker->saw_SOF ) { + ERREXIT( cinfo, JERR_SOF_DUPLICATE ); + } + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if ( ( cinfo->image_height <= 0 ) || ( cinfo->image_width <= 0 ) + || ( cinfo->num_components <= 0 ) ) { + ERREXIT( cinfo, JERR_EMPTY_IMAGE ); + } + + if ( length != ( cinfo->num_components * 3 ) ) { + ERREXIT( cinfo, JERR_BAD_LENGTH ); + } + + if ( cinfo->comp_info == NULL ) {/* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) ( *cinfo->mem->alloc_small ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF( jpeg_component_info ) ); + } + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + compptr->component_index = ci; + INPUT_BYTE( cinfo, compptr->component_id, return FALSE ); + INPUT_BYTE( cinfo, c, return FALSE ); + compptr->h_samp_factor = ( c >> 4 ) & 15; + compptr->v_samp_factor = ( c ) & 15; + INPUT_BYTE( cinfo, compptr->quant_tbl_no, return FALSE ); + + TRACEMS4( cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no ); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_sos( j_decompress_ptr cinfo ) { +/* Process a SOS marker */ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS( cinfo ); + + if ( !cinfo->marker->saw_SOF ) { + ERREXIT( cinfo, JERR_SOS_NO_SOF ); + } + + INPUT_2BYTES( cinfo, length, return FALSE ); + + INPUT_BYTE( cinfo, n, return FALSE );/* Number of components */ + + if ( ( length != ( n * 2 + 6 ) || n < 1 ) || ( n > MAX_COMPS_IN_SCAN ) ) { + ERREXIT( cinfo, JERR_BAD_LENGTH ); + } + + TRACEMS1( cinfo, 1, JTRC_SOS, n ); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for ( i = 0; i < n; i++ ) { + INPUT_BYTE( cinfo, cc, return FALSE ); + INPUT_BYTE( cinfo, c, return FALSE ); + + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + if ( cc == compptr->component_id ) { + goto id_found; + } + } + + ERREXIT1( cinfo, JERR_BAD_COMPONENT_ID, cc ); + +id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = ( c >> 4 ) & 15; + compptr->ac_tbl_no = ( c ) & 15; + + TRACEMS3( cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no ); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE( cinfo, c, return FALSE ); + cinfo->Ss = c; + INPUT_BYTE( cinfo, c, return FALSE ); + cinfo->Se = c; + INPUT_BYTE( cinfo, c, return FALSE ); + cinfo->Ah = ( c >> 4 ) & 15; + cinfo->Al = ( c ) & 15; + + TRACEMS4( cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al ); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +METHODDEF boolean +get_app0( j_decompress_ptr cinfo ) { +/* Process an APP0 marker */ +#define JFIF_LEN 14 + INT32 length; + UINT8 b[JFIF_LEN]; + int buffp; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + /* See if a JFIF APP0 marker is present */ + + if ( length >= JFIF_LEN ) { + for ( buffp = 0; buffp < JFIF_LEN; buffp++ ) { + INPUT_BYTE( cinfo, b[buffp], return FALSE ); + } + length -= JFIF_LEN; + + if ( ( b[0] == 0x4A ) && ( b[1] == 0x46 ) && ( b[2] == 0x49 ) && ( b[3] == 0x46 ) && ( b[4] == 0 ) ) { + /* Found JFIF APP0 marker: check version */ + /* Major version must be 1, anything else signals an incompatible change. + * We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec. + * Minor version should be 0..2, but process anyway if newer. + */ + if ( b[5] != 1 ) { + WARNMS2( cinfo, JWRN_JFIF_MAJOR, b[5], b[6] ); + } else if ( b[6] > 2 ) { + TRACEMS2( cinfo, 1, JTRC_JFIF_MINOR, b[5], b[6] ); + } + /* Save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->density_unit = b[7]; + cinfo->X_density = ( b[8] << 8 ) + b[9]; + cinfo->Y_density = ( b[10] << 8 ) + b[11]; + TRACEMS3( cinfo, 1, JTRC_JFIF, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit ); + if ( b[12] | b[13] ) { + TRACEMS2( cinfo, 1, JTRC_JFIF_THUMBNAIL, b[12], b[13] ); + } + if ( length != ( (INT32) b[12] * (INT32) b[13] * (INT32) 3 ) ) { + TRACEMS1( cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) length ); + } + } else { + /* Start of APP0 does not match "JFIF" */ + TRACEMS1( cinfo, 1, JTRC_APP0, (int) length + JFIF_LEN ); + } + } else { + /* Too short to be JFIF marker */ + TRACEMS1( cinfo, 1, JTRC_APP0, (int) length ); + } + + INPUT_SYNC( cinfo ); + if ( length > 0 ) { /* skip any remaining data -- could be lots */ + ( *cinfo->src->skip_input_data )( cinfo, (long) length ); + } + + return TRUE; +} + + +METHODDEF boolean +get_app14( j_decompress_ptr cinfo ) { +/* Process an APP14 marker */ +#define ADOBE_LEN 12 + INT32 length; + UINT8 b[ADOBE_LEN]; + int buffp; + unsigned int version, flags0, flags1, transform; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + /* See if an Adobe APP14 marker is present */ + + if ( length >= ADOBE_LEN ) { + for ( buffp = 0; buffp < ADOBE_LEN; buffp++ ) { + INPUT_BYTE( cinfo, b[buffp], return FALSE ); + } + length -= ADOBE_LEN; + + if ( ( b[0] == 0x41 ) && ( b[1] == 0x64 ) && ( b[2] == 0x6F ) && ( b[3] == 0x62 ) && ( b[4] == 0x65 ) ) { + /* Found Adobe APP14 marker */ + version = ( b[5] << 8 ) + b[6]; + flags0 = ( b[7] << 8 ) + b[8]; + flags1 = ( b[9] << 8 ) + b[10]; + transform = b[11]; + TRACEMS4( cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform ); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe" */ + TRACEMS1( cinfo, 1, JTRC_APP14, (int) length + ADOBE_LEN ); + } + } else { + /* Too short to be Adobe marker */ + TRACEMS1( cinfo, 1, JTRC_APP14, (int) length ); + } + + INPUT_SYNC( cinfo ); + if ( length > 0 ) { /* skip any remaining data -- could be lots */ + ( *cinfo->src->skip_input_data )( cinfo, (long) length ); + } + + return TRUE; +} + + +LOCAL boolean +get_dac( j_decompress_ptr cinfo ) { +/* Process a DAC marker */ + INT32 length; + int index, val; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + while ( length > 0 ) { + INPUT_BYTE( cinfo, index, return FALSE ); + + INPUT_BYTE( cinfo, val, return FALSE ); + + length -= 2; + + TRACEMS2( cinfo, 1, JTRC_DAC, index, val ); + + if ( ( index < 0 ) || ( index >= ( 2 * NUM_ARITH_TBLS ) ) ) { + ERREXIT1( cinfo, JERR_DAC_INDEX, index ); + } + + if ( index >= NUM_ARITH_TBLS ) {/* define AC table */ + cinfo->arith_ac_K[index - NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) ( val & 0x0F ); + cinfo->arith_dc_U[index] = (UINT8) ( val >> 4 ); + if ( cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index] ) { + ERREXIT1( cinfo, JERR_DAC_VALUE, val ); + } + } + } + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_dht( j_decompress_ptr cinfo ) { +/* Process a DHT marker */ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL ** htblptr; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + while ( length > 0 ) { + INPUT_BYTE( cinfo, index, return FALSE ); + + TRACEMS1( cinfo, 1, JTRC_DHT, index ); + + bits[0] = 0; + count = 0; + for ( i = 1; i <= 16; i++ ) { + INPUT_BYTE( cinfo, bits[i], return FALSE ); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8( cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8] ); + TRACEMS8( cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16] ); + + if ( ( count > 256 ) || ( ( (INT32) count ) > length ) ) { + ERREXIT( cinfo, JERR_DHT_COUNTS ); + } + + for ( i = 0; i < count; i++ ) { + INPUT_BYTE( cinfo, huffval[i], return FALSE ); + } + + length -= count; + + if ( index & 0x10 ) {/* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if ( ( index < 0 ) || ( index >= NUM_HUFF_TBLS ) ) { + ERREXIT1( cinfo, JERR_DHT_INDEX, index ); + } + + if ( *htblptr == NULL ) { + *htblptr = jpeg_alloc_huff_table( (j_common_ptr) cinfo ); + } + + MEMCOPY( ( *htblptr )->bits, bits, SIZEOF( ( *htblptr )->bits ) ); + MEMCOPY( ( *htblptr )->huffval, huffval, SIZEOF( ( *htblptr )->huffval ) ); + } + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_dqt( j_decompress_ptr cinfo ) { +/* Process a DQT marker */ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL * quant_ptr; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + length -= 2; + + while ( length > 0 ) { + INPUT_BYTE( cinfo, n, return FALSE ); + + prec = n >> 4; + n &= 0x0F; + + TRACEMS2( cinfo, 1, JTRC_DQT, n, prec ); + + if ( n >= NUM_QUANT_TBLS ) { + ERREXIT1( cinfo, JERR_DQT_INDEX, n ); + } + + if ( cinfo->quant_tbl_ptrs[n] == NULL ) { + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table( (j_common_ptr) cinfo ); + } + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for ( i = 0; i < DCTSIZE2; i++ ) { + if ( prec ) { + INPUT_2BYTES( cinfo, tmp, return FALSE ); + } else { + INPUT_BYTE( cinfo, tmp, return FALSE ); + } + quant_ptr->quantval[i] = (UINT16) tmp; + } + + for ( i = 0; i < DCTSIZE2; i += 8 ) { + TRACEMS8( cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i ], quant_ptr->quantval[i + 1], + quant_ptr->quantval[i + 2], quant_ptr->quantval[i + 3], + quant_ptr->quantval[i + 4], quant_ptr->quantval[i + 5], + quant_ptr->quantval[i + 6], quant_ptr->quantval[i + 7] ); + } + + length -= DCTSIZE2 + 1; + if ( prec ) { + length -= DCTSIZE2; + } + } + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +get_dri( j_decompress_ptr cinfo ) { +/* Process a DRI marker */ + INT32 length; + unsigned int tmp; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + + if ( length != 4 ) { + ERREXIT( cinfo, JERR_BAD_LENGTH ); + } + + INPUT_2BYTES( cinfo, tmp, return FALSE ); + + TRACEMS1( cinfo, 1, JTRC_DRI, tmp ); + + cinfo->restart_interval = tmp; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +METHODDEF boolean +skip_variable( j_decompress_ptr cinfo ) { +/* Skip over an unknown or uninteresting variable-length marker */ + INT32 length; + INPUT_VARS( cinfo ); + + INPUT_2BYTES( cinfo, length, return FALSE ); + + TRACEMS2( cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length ); + + INPUT_SYNC( cinfo ); /* do before skip_input_data */ + ( *cinfo->src->skip_input_data )( cinfo, (long) length - 2L ); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL boolean +next_marker( j_decompress_ptr cinfo ) { + int c; + INPUT_VARS( cinfo ); + + for (;; ) { + INPUT_BYTE( cinfo, c, return FALSE ); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while ( c != 0xFF ) { + cinfo->marker->discarded_bytes++; + + INPUT_SYNC( cinfo ); + INPUT_BYTE( cinfo, c, return FALSE ); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE( cinfo, c, return FALSE ); + } while ( c == 0xFF ); + if ( c != 0 ) { + break; + } /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC( cinfo ); + } + + if ( cinfo->marker->discarded_bytes != 0 ) { + WARNMS2( cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c ); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +LOCAL boolean +first_marker( j_decompress_ptr cinfo ) { +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ + int c, c2; + INPUT_VARS( cinfo ); + + INPUT_BYTE( cinfo, c, return FALSE ); + INPUT_BYTE( cinfo, c2, return FALSE ); + if ( ( c != 0xFF ) || ( c2 != (int) M_SOI ) ) { + ERREXIT2( cinfo, JERR_NO_SOI, c, c2 ); + } + + cinfo->unread_marker = c2; + + INPUT_SYNC( cinfo ); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF int +read_markers( j_decompress_ptr cinfo ) { + /* Outer loop repeats once for each marker. */ + for (;; ) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if ( cinfo->unread_marker == 0 ) { + if ( !cinfo->marker->saw_SOI ) { + if ( !first_marker( cinfo ) ) { + return JPEG_SUSPENDED; + } + } else { + if ( !next_marker( cinfo ) ) { + return JPEG_SUSPENDED; + } + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch ( cinfo->unread_marker ) { + case M_SOI: + if ( !get_soi( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF0:/* Baseline */ + case M_SOF1:/* Extended sequential, Huffman */ + if ( !get_sof( cinfo, FALSE, FALSE ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF2:/* Progressive, Huffman */ + if ( !get_sof( cinfo, TRUE, FALSE ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF9:/* Extended sequential, arithmetic */ + if ( !get_sof( cinfo, FALSE, TRUE ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_SOF10:/* Progressive, arithmetic */ + if ( !get_sof( cinfo, TRUE, TRUE ) ) { + return JPEG_SUSPENDED; + } + break; + + /* Currently unsupported SOFn types */ + case M_SOF3:/* Lossless, Huffman */ + case M_SOF5:/* Differential sequential, Huffman */ + case M_SOF6:/* Differential progressive, Huffman */ + case M_SOF7:/* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11:/* Lossless, arithmetic */ + case M_SOF13:/* Differential sequential, arithmetic */ + case M_SOF14:/* Differential progressive, arithmetic */ + case M_SOF15:/* Differential lossless, arithmetic */ + ERREXIT1( cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker ); + break; + + case M_SOS: + if ( !get_sos( cinfo ) ) { + return JPEG_SUSPENDED; + } + cinfo->unread_marker = 0;/* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS( cinfo, 1, JTRC_EOI ); + cinfo->unread_marker = 0;/* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if ( !get_dac( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_DHT: + if ( !get_dht( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_DQT: + if ( !get_dqt( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_DRI: + if ( !get_dri( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if ( !( *cinfo->marker->process_APPn[cinfo->unread_marker - (int) M_APP0] )( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_COM: + if ( !( *cinfo->marker->process_COM )( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + case M_RST0:/* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1( cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker ); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if ( !skip_variable( cinfo ) ) { + return JPEG_SUSPENDED; + } + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1( cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker ); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF boolean +read_restart_marker( j_decompress_ptr cinfo ) { + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if ( cinfo->unread_marker == 0 ) { + if ( !next_marker( cinfo ) ) { + return FALSE; + } + } + + if ( cinfo->unread_marker == + ( (int) M_RST0 + cinfo->marker->next_restart_num ) ) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1( cinfo, 2, JTRC_RST, cinfo->marker->next_restart_num ); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if ( !( *cinfo->src->resync_to_restart )( cinfo, + cinfo->marker->next_restart_num ) ) { + return FALSE; + } + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = ( cinfo->marker->next_restart_num + 1 ) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL boolean +jpeg_resync_to_restart( j_decompress_ptr cinfo, int desired ) { + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2( cinfo, JWRN_MUST_RESYNC, marker, desired ); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;; ) { + if ( marker < (int) M_SOF0 ) { + action = 2; + } /* invalid marker */ + else if ( marker < (int) M_RST0 || marker > (int) M_RST7 ) { + action = 3; + } /* valid non-restart marker */ + else { + if ( marker == ( (int) M_RST0 + ( ( desired + 1 ) & 7 ) ) || + marker == ( (int) M_RST0 + ( ( desired + 2 ) & 7 ) ) ) { + action = 3; + } /* one of the next two expected restarts */ + else if ( marker == ( (int) M_RST0 + ( ( desired - 1 ) & 7 ) ) || + marker == ( (int) M_RST0 + ( ( desired - 2 ) & 7 ) ) ) { + action = 2; + } /* a prior restart, so advance */ + else { + action = 1; + } /* desired restart or too far away */ + } + TRACEMS2( cinfo, 4, JTRC_RECOVERY_ACTION, marker, action ); + switch ( action ) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if ( !next_marker( cinfo ) ) { + return FALSE; + } + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF void +reset_marker_reader( j_decompress_ptr cinfo ) { + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + cinfo->marker->saw_SOI = FALSE; /* set internal state too */ + cinfo->marker->saw_SOF = FALSE; + cinfo->marker->discarded_bytes = 0; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_marker_reader( j_decompress_ptr cinfo ) { + int i; + + /* Create subobject in permanent pool */ + cinfo->marker = (struct jpeg_marker_reader *) + ( * cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF( struct jpeg_marker_reader ) ); + /* Initialize method pointers */ + cinfo->marker->reset_marker_reader = reset_marker_reader; + cinfo->marker->read_markers = read_markers; + cinfo->marker->read_restart_marker = read_restart_marker; + cinfo->marker->process_COM = skip_variable; + for ( i = 0; i < 16; i++ ) { + cinfo->marker->process_APPn[i] = skip_variable; + } + cinfo->marker->process_APPn[0] = get_app0; + cinfo->marker->process_APPn[14] = get_app14; + /* Reset marker processing state */ + reset_marker_reader( cinfo ); +} diff --git a/neo/renderer/jpeg-6/jdmaster.cpp b/neo/renderer/jpeg-6/jdmaster.cpp new file mode 100644 index 00000000..4bd5b00f --- /dev/null +++ b/neo/renderer/jpeg-6/jdmaster.cpp @@ -0,0 +1,568 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub;/* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample;/* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL boolean +use_merged_upsample( j_decompress_ptr cinfo ) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if ( ( cinfo->do_fancy_upsampling ) || ( cinfo->CCIR601_sampling ) ) { + return FALSE; + } + /* jdmerge.c only supports YCC=>RGB color conversion */ + if ( ( cinfo->jpeg_color_space != JCS_YCbCr ) || ( cinfo->num_components != 3 ) || + ( cinfo->out_color_space != JCS_RGB ) || + ( cinfo->out_color_components != RGB_PIXELSIZE ) ) { + return FALSE; + } + /* and it only handles 2h1v or 2h2v sampling ratios */ + if ( ( cinfo->comp_info[0].h_samp_factor != 2 ) || + ( cinfo->comp_info[1].h_samp_factor != 1 ) || + ( cinfo->comp_info[2].h_samp_factor != 1 ) || + ( cinfo->comp_info[0].v_samp_factor > 2 ) || + ( cinfo->comp_info[1].v_samp_factor != 1 ) || + ( cinfo->comp_info[2].v_samp_factor != 1 ) ) { + return FALSE; + } + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if ( ( cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size ) || + ( cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size ) || + ( cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size ) ) { + return FALSE; + } + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ + +#else + return FALSE; + +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL void +jpeg_calc_output_dimensions( j_decompress_ptr cinfo ) { +/* Do computations that are needed before master selection phase */ +#if 0 // JDC: commented out to remove warning + int ci; + jpeg_component_info * compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if ( cinfo->global_state != DSTATE_READY ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if ( cinfo->scale_num * 8 <= cinfo->scale_denom ) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, 8L ); + cinfo->output_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, 8L ); + cinfo->min_DCT_scaled_size = 1; + } else if ( cinfo->scale_num * 4 <= cinfo->scale_denom ) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, 4L ); + cinfo->output_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, 4L ); + cinfo->min_DCT_scaled_size = 2; + } else if ( cinfo->scale_num * 2 <= cinfo->scale_denom ) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width, 2L ); + cinfo->output_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height, 2L ); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + int ssize = cinfo->min_DCT_scaled_size; + while ( ssize < DCTSIZE && + ( compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size ) && + ( compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size ) ) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_width * + (long) ( compptr->h_samp_factor * compptr->DCT_scaled_size ), + (long) ( cinfo->max_h_samp_factor * DCTSIZE ) ); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up( (long) cinfo->image_height * + (long) ( compptr->v_samp_factor * compptr->DCT_scaled_size ), + (long) ( cinfo->max_v_samp_factor * DCTSIZE ) ); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch ( cinfo->out_color_space ) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = ( cinfo->quantize_colors ? 1 : + cinfo->out_color_components ); + + /* See if upsampler will want to emit more than one row at a time */ + if ( use_merged_upsample( cinfo ) ) { + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + } else { + cinfo->rec_outbuf_height = 1; + } +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL void +prepare_range_limit_table( j_decompress_ptr cinfo ) { +/* Allocate and fill in the sample_range_limit table */ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( 5 * ( MAXJSAMPLE + 1 ) + CENTERJSAMPLE ) * SIZEOF( JSAMPLE ) ); + table += ( MAXJSAMPLE + 1 );/* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO( table - ( MAXJSAMPLE + 1 ), ( MAXJSAMPLE + 1 ) * SIZEOF( JSAMPLE ) ); + /* Main part of "simple" table: limit[x] = x */ + for ( i = 0; i <= MAXJSAMPLE; i++ ) { + table[i] = (JSAMPLE) i; + } + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for ( i = CENTERJSAMPLE; i < 2 * ( MAXJSAMPLE + 1 ); i++ ) { + table[i] = MAXJSAMPLE; + } + /* Second half of post-IDCT table */ + MEMZERO( table + ( 2 * ( MAXJSAMPLE + 1 ) ), + ( 2 * ( MAXJSAMPLE + 1 ) - CENTERJSAMPLE ) * SIZEOF( JSAMPLE ) ); + MEMCOPY( table + ( 4 * ( MAXJSAMPLE + 1 ) - CENTERJSAMPLE ), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF( JSAMPLE ) ); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL void +master_selection( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions( cinfo ); + prepare_range_limit_table( cinfo ); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ( (long) jd_samplesperrow != samplesperrow ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample( cinfo ); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if ( ( !cinfo->quantize_colors ) || ( !cinfo->buffered_image ) ) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if ( cinfo->quantize_colors ) { + if ( cinfo->raw_data_out ) { + ERREXIT( cinfo, JERR_NOTIMPL ); + } + /* 2-pass quantizer only works in 3-component color space. */ + if ( cinfo->out_color_components != 3 ) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if ( cinfo->colormap != NULL ) { + cinfo->enable_external_quant = TRUE; + } else if ( cinfo->two_pass_quantize ) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if ( cinfo->enable_1pass_quant ) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer( cinfo ); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if ( ( cinfo->enable_2pass_quant ) || ( cinfo->enable_external_quant ) ) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer( cinfo ); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if ( !cinfo->raw_data_out ) { + if ( master->using_merged_upsample ) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler( cinfo );/* does color conversion too */ +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_color_deconverter( cinfo ); + jinit_upsampler( cinfo ); + } + jinit_d_post_controller( cinfo, cinfo->enable_2pass_quant ); + } + /* Inverse DCT */ + jinit_inverse_dct( cinfo ); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_decoder( cinfo ); + } + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller( cinfo, use_c_buffer ); + + if ( !cinfo->raw_data_out ) { + jinit_d_main_controller( cinfo, FALSE /* never need full buffer here */ ); + } + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Initialize input side of decompressor to consume first scan. */ + ( *cinfo->inputctl->start_input_pass )( cinfo ); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if ( ( cinfo->progress != NULL ) && ( !cinfo->buffered_image ) && + ( cinfo->inputctl->has_multiple_scans ) ) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if ( cinfo->progressive_mode ) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = ( cinfo->enable_2pass_quant ? 3 : 2 ); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapi.c will crank the pass to completion.) + */ + +METHODDEF void +prepare_for_output_pass( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + if ( master->pub.is_dummy_pass ) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + ( *cinfo->cquantize->start_pass )( cinfo, FALSE ); + ( *cinfo->post->start_pass )( cinfo, JBUF_CRANK_DEST ); + ( *cinfo->main->start_pass )( cinfo, JBUF_CRANK_DEST ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if ( ( cinfo->quantize_colors ) && ( cinfo->colormap == NULL ) ) { + /* Select new quantization method */ + if ( ( cinfo->two_pass_quantize ) && ( cinfo->enable_2pass_quant ) ) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if ( cinfo->enable_1pass_quant ) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT( cinfo, JERR_MODE_CHANGE ); + } + } + ( *cinfo->idct->start_pass )( cinfo ); + ( *cinfo->coef->start_output_pass )( cinfo ); + if ( !cinfo->raw_data_out ) { + if ( !master->using_merged_upsample ) { + ( *cinfo->cconvert->start_pass )( cinfo ); + } + ( *cinfo->upsample->start_pass )( cinfo ); + if ( cinfo->quantize_colors ) { + ( *cinfo->cquantize->start_pass )( cinfo, master->pub.is_dummy_pass ); + } + ( *cinfo->post->start_pass )( cinfo, + ( master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU ) ); + ( *cinfo->main->start_pass )( cinfo, JBUF_PASS_THRU ); + } + } + + /* Set up progress monitor's pass info if present */ + if ( cinfo->progress != NULL ) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + ( master->pub.is_dummy_pass ? 2 : 1 ); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if ( ( cinfo->buffered_image ) && ( !cinfo->inputctl->eoi_reached ) ) { + cinfo->progress->total_passes += ( cinfo->enable_2pass_quant ? 2 : 1 ); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF void +finish_output_pass( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + if ( cinfo->quantize_colors ) { + ( *cinfo->cquantize->finish_pass )( cinfo ); + } + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL void +jpeg_new_colormap( j_decompress_ptr cinfo ) { + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if ( cinfo->global_state != DSTATE_BUFIMAGE ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + + if ( ( cinfo->quantize_colors ) && ( cinfo->enable_external_quant ) && + ( cinfo->colormap != NULL ) ) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + ( *cinfo->cquantize->new_color_map )( cinfo ); + master->pub.is_dummy_pass = FALSE;/* just in case */ + } else { + ERREXIT( cinfo, JERR_MODE_CHANGE ); + } +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL void +jinit_master_decompress( j_decompress_ptr cinfo ) { + my_master_ptr master; + + master = (my_master_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_decomp_master ) ); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection( cinfo ); +} diff --git a/neo/renderer/jpeg-6/jdmerge.cpp b/neo/renderer/jpeg-6/jdmerge.cpp new file mode 100644 index 00000000..a9d31182 --- /dev/null +++ b/neo/renderer/jpeg-6/jdmerge.cpp @@ -0,0 +1,396 @@ +/* + * jdmerge.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains code for merged upsampling/color conversion. + * + * This file combines functions from jdsample.c and jdcolor.c; + * read those files first to understand what's going on. + * + * When the chroma components are to be upsampled by simple replication + * (ie, box filtering), we can save some work in color conversion by + * calculating all the output pixels corresponding to a pair of chroma + * samples at one time. In the conversion equations + * R = Y + K1 * Cr + * G = Y + K2 * Cb + K3 * Cr + * B = Y + K4 * Cb + * only the Y term varies among the group of pixels corresponding to a pair + * of chroma samples, so the rest of the terms can be calculated just once. + * At typical sampling ratios, this eliminates half or three-quarters of the + * multiplications needed for color conversion. + * + * This file currently provides implementations for the following cases: + * YCbCr => RGB color conversion only. + * Sampling ratios of 2h1v or 2h2v. + * No scaling needed at upsample time. + * Corner-aligned (non-CCIR601) sampling alignment. + * Other special cases could be added, but in most applications these are + * the only common cases. (For uncommon cases we fall back on the more + * general code in jdsample.c and jdcolor.c.) + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef UPSAMPLE_MERGING_SUPPORTED + + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Pointer to routine to do actual upsampling/conversion of one row group */ + JMETHOD( void, upmethod, ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf ) ); + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + + /* For 2:1 vertical sampling, we produce two output rows at a time. + * We need a "spare" row buffer to hold the second output row if the + * application provides just a one-row buffer; we also use the spare + * to discard the dummy last row if the image height is odd. + */ + JSAMPROW spare_row; + boolean spare_full; /* T if spare buffer is occupied */ + + JDIMENSION out_row_width;/* samples per output row */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ( (INT32) 1 << ( SCALEBITS - 1 ) ) +#define FIX( x ) ( (INT32) ( ( x ) * ( 1L << SCALEBITS ) + 0.5 ) ) + + +/* + * Initialize tables for YCC->RGB colorspace conversion. + * This is taken directly from jdcolor.c; see that file for more info. + */ + +LOCAL void +build_ycc_rgb_table( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int i; + INT32 x; + SHIFT_TEMPS + + upsample->Cr_r_tab = (int *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( int ) ); + upsample->Cb_b_tab = (int *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( int ) ); + upsample->Cr_g_tab = (INT32 *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( INT32 ) ); + upsample->Cb_g_tab = (INT32 *) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + ( MAXJSAMPLE + 1 ) * SIZEOF( INT32 ) ); + + for ( i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++ ) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + upsample->Cr_r_tab[i] = (int) + RIGHT_SHIFT( FIX( 1.40200 ) * x + ONE_HALF, SCALEBITS ); + /* Cb=>B value is nearest int to 1.77200 * x */ + upsample->Cb_b_tab[i] = (int) + RIGHT_SHIFT( FIX( 1.77200 ) * x + ONE_HALF, SCALEBITS ); + /* Cr=>G value is scaled-up -0.71414 * x */ + upsample->Cr_g_tab[i] = ( -FIX( 0.71414 ) ) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + upsample->Cb_g_tab[i] = ( -FIX( 0.34414 ) ) * x + ONE_HALF; + } +} + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_merged_upsample( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the spare buffer empty */ + upsample->spare_full = FALSE; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * The control routine just handles the row buffering considerations. + */ + +METHODDEF void +merged_2v_upsample( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { +/* 2:1 vertical sampling case: may need a spare row. */ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPROW work_ptrs[2]; + JDIMENSION num_rows; /* number of rows returned to caller */ + + if ( upsample->spare_full ) { + /* If we have a spare row saved from a previous cycle, just return it. */ + jcopy_sample_rows( &upsample->spare_row, 0, output_buf + *out_row_ctr, 0, + 1, upsample->out_row_width ); + num_rows = 1; + upsample->spare_full = FALSE; + } else { + /* Figure number of rows to return to caller. */ + num_rows = 2; + /* Not more than the distance to the end of the image. */ + if ( num_rows > upsample->rows_to_go ) { + num_rows = upsample->rows_to_go; + } + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if ( num_rows > out_rows_avail ) { + num_rows = out_rows_avail; + } + /* Create output pointer array for upsampler. */ + work_ptrs[0] = output_buf[*out_row_ctr]; + if ( num_rows > 1 ) { + work_ptrs[1] = output_buf[*out_row_ctr + 1]; + } else { + work_ptrs[1] = upsample->spare_row; + upsample->spare_full = TRUE; + } + /* Now do the upsampling. */ + ( *upsample->upmethod )( cinfo, input_buf, *in_row_group_ctr, work_ptrs ); + } + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if ( !upsample->spare_full ) { + ( *in_row_group_ctr )++; + } +} + + +METHODDEF void +merged_1v_upsample( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { +/* 1:1 vertical sampling case: much easier, never need a spare row. */ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Just do the upsampling. */ + ( *upsample->upmethod )( cinfo, input_buf, *in_row_group_ctr, + output_buf + *out_row_ctr ); + /* Adjust counts */ + ( *out_row_ctr )++; + ( *in_row_group_ctr )++; +} + + +/* + * These are the routines invoked by the control routines to do + * the actual upsampling/conversion. One row group is processed per call. + * + * Note: since we may be writing directly into application-supplied buffers, + * we have to be honest about the output width; we can't assume the buffer + * has been rounded up to an even width. + */ + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical. + */ + +METHODDEF void +h2v1_merged_upsample( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr0 = input_buf[0][in_row_group_ctr]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr = output_buf[0]; + /* Loop for each pair of output pixels */ + for ( col = cinfo->output_width >> 1; col > 0; col-- ) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE( *inptr1++ ); + cr = GETJSAMPLE( *inptr2++ ); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], SCALEBITS ); + cblue = Cbbtab[cb]; + /* Fetch 2 Y values and emit 2 pixels */ + y = GETJSAMPLE( *inptr0++ ); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + y = GETJSAMPLE( *inptr0++ ); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if ( cinfo->output_width & 1 ) { + cb = GETJSAMPLE( *inptr1 ); + cr = GETJSAMPLE( *inptr2 ); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], SCALEBITS ); + cblue = Cbbtab[cb]; + y = GETJSAMPLE( *inptr0 ); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. + */ + +METHODDEF void +h2v2_merged_upsample( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr0, outptr1; + JSAMPROW inptr00, inptr01, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr00 = input_buf[0][in_row_group_ctr * 2]; + inptr01 = input_buf[0][in_row_group_ctr * 2 + 1]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr0 = output_buf[0]; + outptr1 = output_buf[1]; + /* Loop for each group of output pixels */ + for ( col = cinfo->output_width >> 1; col > 0; col-- ) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE( *inptr1++ ); + cr = GETJSAMPLE( *inptr2++ ); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], SCALEBITS ); + cblue = Cbbtab[cb]; + /* Fetch 4 Y values and emit 4 pixels */ + y = GETJSAMPLE( *inptr00++ ); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE( *inptr00++ ); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE( *inptr01++ ); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + y = GETJSAMPLE( *inptr01++ ); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if ( cinfo->output_width & 1 ) { + cb = GETJSAMPLE( *inptr1 ); + cr = GETJSAMPLE( *inptr2 ); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT( Cbgtab[cb] + Crgtab[cr], SCALEBITS ); + cblue = Cbbtab[cb]; + y = GETJSAMPLE( *inptr00 ); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + y = GETJSAMPLE( *inptr01 ); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Module initialization routine for merged upsampling/color conversion. + * + * NB: this is called under the conditions determined by use_merged_upsample() + * in jdmaster.c. That routine MUST correspond to the actual capabilities + * of this module; no safety checks are made here. + */ + +GLOBAL void +jinit_merged_upsampler( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample; + + upsample = (my_upsample_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_upsampler ) ); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_merged_upsample; + upsample->pub.need_context_rows = FALSE; + + upsample->out_row_width = cinfo->output_width * cinfo->out_color_components; + + if ( cinfo->max_v_samp_factor == 2 ) { + upsample->pub.upsample = merged_2v_upsample; + upsample->upmethod = h2v2_merged_upsample; + /* Allocate a spare row buffer */ + upsample->spare_row = (JSAMPROW) + ( *cinfo->mem->alloc_large )( (j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) ( upsample->out_row_width * SIZEOF( JSAMPLE ) ) ); + } else { + upsample->pub.upsample = merged_1v_upsample; + upsample->upmethod = h2v1_merged_upsample; + /* No spare row needed */ + upsample->spare_row = NULL; + } + + build_ycc_rgb_table( cinfo ); +} + +#endif /* UPSAMPLE_MERGING_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jdphuff.cpp b/neo/renderer/jpeg-6/jdphuff.cpp new file mode 100644 index 00000000..80a075b1 --- /dev/null +++ b/neo/renderer/jpeg-6/jdphuff.cpp @@ -0,0 +1,670 @@ +/* + * jdphuff.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines for progressive JPEG. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdhuff.c */ + + +#ifdef D_PROGRESSIVE_SUPPORTED + +/* + * Expanded entropy decoder object for progressive Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE( dest, src ) ( ( dest ) = ( src ) ) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE( dest, src ) \ + ( ( dest ).EOBRUN = ( src ).EOBRUN, \ + ( dest ).last_dc_val[0] = ( src ).last_dc_val[0], \ + ( dest ).last_dc_val[1] = ( src ).last_dc_val[1], \ + ( dest ).last_dc_val[2] = ( src ).last_dc_val[2], \ + ( dest ).last_dc_val[3] = ( src ).last_dc_val[3] ) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub;/* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate;/* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go;/* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + d_derived_tbl * ac_derived_tbl;/* active table during an AC scan */ +} phuff_entropy_decoder; + +typedef phuff_entropy_decoder * phuff_entropy_ptr; + +/* Forward declarations */ +METHODDEF boolean decode_mcu_DC_first JPP( ( j_decompress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean decode_mcu_AC_first JPP( ( j_decompress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean decode_mcu_DC_refine JPP( ( j_decompress_ptr cinfo, + JBLOCKROW * MCU_data ) ); +METHODDEF boolean decode_mcu_AC_refine JPP( ( j_decompress_ptr cinfo, + JBLOCKROW * MCU_data ) ); + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_phuff_decoder( j_decompress_ptr cinfo ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band, bad; + int ci, coefi, tbl; + int * coef_bit_ptr; + jpeg_component_info * compptr; + + is_DC_band = ( cinfo->Ss == 0 ); + + /* Validate scan parameters */ + bad = FALSE; + if ( is_DC_band ) { + if ( cinfo->Se != 0 ) { + bad = TRUE; + } + } else { + /* need not check Ss/Se < 0 since they came from unsigned bytes */ + if ( ( cinfo->Ss > cinfo->Se ) || ( cinfo->Se >= DCTSIZE2 ) ) { + bad = TRUE; + } + /* AC scans may have only one component */ + if ( cinfo->comps_in_scan != 1 ) { + bad = TRUE; + } + } + if ( cinfo->Ah != 0 ) { + /* Successive approximation refinement scan: must have Al = Ah-1. */ + if ( cinfo->Al != cinfo->Ah - 1 ) { + bad = TRUE; + } + } + if ( cinfo->Al > 13 ) { /* need not check for < 0 */ + bad = TRUE; + } + if ( bad ) { + ERREXIT4( cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al ); + } + /* Update progression status, and verify that scan order is legal. + * Note that inter-scan inconsistencies are treated as warnings + * not fatal errors ... not clear if this is right way to behave. + */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + int cindex = cinfo->cur_comp_info[ci]->component_index; + coef_bit_ptr = &cinfo->coef_bits[cindex][0]; + if ( ( !is_DC_band ) && ( coef_bit_ptr[0] < 0 ) ) {/* AC without prior DC scan */ + WARNMS2( cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0 ); + } + for ( coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++ ) { + int expected = ( coef_bit_ptr[coefi] < 0 ) ? 0 : coef_bit_ptr[coefi]; + if ( cinfo->Ah != expected ) { + WARNMS2( cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi ); + } + coef_bit_ptr[coefi] = cinfo->Al; + } + } + + /* Select MCU decoding routine */ + if ( cinfo->Ah == 0 ) { + if ( is_DC_band ) { + entropy->pub.decode_mcu = decode_mcu_DC_first; + } else { + entropy->pub.decode_mcu = decode_mcu_AC_first; + } + } else { + if ( is_DC_band ) { + entropy->pub.decode_mcu = decode_mcu_DC_refine; + } else { + entropy->pub.decode_mcu = decode_mcu_AC_refine; + } + } + + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + compptr = cinfo->cur_comp_info[ci]; + /* Make sure requested tables are present, and compute derived tables. + * We may build same derived table more than once, but it's not expensive. + */ + if ( is_DC_band ) { + if ( cinfo->Ah == 0 ) {/* DC refinement needs no table */ + tbl = compptr->dc_tbl_no; + if ( ( tbl < 0 ) || ( tbl >= NUM_HUFF_TBLS ) || + ( cinfo->dc_huff_tbl_ptrs[tbl] == NULL ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, tbl ); + } + jpeg_make_d_derived_tbl( cinfo, cinfo->dc_huff_tbl_ptrs[tbl], + &entropy->derived_tbls[tbl] ); + } + } else { + tbl = compptr->ac_tbl_no; + if ( ( tbl < 0 ) || ( tbl >= NUM_HUFF_TBLS ) || + ( cinfo->ac_huff_tbl_ptrs[tbl] == NULL ) ) { + ERREXIT1( cinfo, JERR_NO_HUFF_TABLE, tbl ); + } + jpeg_make_d_derived_tbl( cinfo, cinfo->ac_huff_tbl_ptrs[tbl], + &entropy->derived_tbls[tbl] ); + /* remember the single active table */ + entropy->ac_derived_tbl = entropy->derived_tbls[tbl]; + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0;/* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize private state variables */ + entropy->saved.EOBRUN = 0; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND( x, s ) ( ( x ) < ( 1 << ( ( s ) - 1 ) ) ? ( x ) + ( ( ( -1 ) << ( s ) ) + 1 ) : ( x ) ) + +#else + +#define HUFF_EXTEND( x, s ) ( ( x ) < extend_test[s] ? ( x ) + extend_offset[s] : ( x ) ) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ +{ 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ +{ 0, ( ( -1 ) << 1 ) + 1, ( ( -1 ) << 2 ) + 1, ( ( -1 ) << 3 ) + 1, ( ( -1 ) << 4 ) + 1, + ( ( -1 ) << 5 ) + 1, ( ( -1 ) << 6 ) + 1, ( ( -1 ) << 7 ) + 1, ( ( -1 ) << 8 ) + 1, + ( ( -1 ) << 9 ) + 1, ( ( -1 ) << 10 ) + 1, ( ( -1 ) << 11 ) + 1, ( ( -1 ) << 12 ) + 1, + ( ( -1 ) << 13 ) + 1, ( ( -1 ) << 14 ) + 1, ( ( -1 ) << 15 ) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart( j_decompress_ptr cinfo ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if ( !( *cinfo->marker->read_restart_marker )( cinfo ) ) { + return FALSE; + } + + /* Re-initialize DC predictions to 0 */ + for ( ci = 0; ci < cinfo->comps_in_scan; ci++ ) { + entropy->saved.last_dc_val[ci] = 0; + } + /* Re-init EOB run count, too */ + entropy->saved.EOBRUN = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Huffman MCU decoding. + * Each of these routines decodes and returns one MCU's worth of + * Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. + * + * We return FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * spectral selection, since we'll just re-assign them on the next call. + * Successive approximation AC refinement has to be more careful, however.) + */ + +/* + * MCU decoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +decode_mcu_DC_first( j_decompress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Al = cinfo->Al; + register int s, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * tbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !process_restart( cinfo ) ) { + return FALSE; + } + } + } + + /* Load up working state */ + BITREAD_LOAD_STATE( cinfo, entropy->bitstate ); + ASSIGN_STATE( state, entropy->saved ); + + /* Outer loop handles each block in the MCU */ + + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + tbl = entropy->derived_tbls[compptr->dc_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE( s, br_state, tbl, return FALSE, label1 ); + if ( s ) { + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + + r = GET_BITS( s ); + s = HUFF_EXTEND( r, s ); + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Scale and output the DC coefficient (assumes jpeg_natural_order[0]=0) */ + ( *block )[0] = (JCOEF) ( s << Al ); + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE( cinfo, entropy->bitstate ); + ASSIGN_STATE( entropy->saved, state ); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +decode_mcu_AC_first( j_decompress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Se = cinfo->Se; + int Al = cinfo->Al; + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + + /* Process restart marker if needed; may have to suspend */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !process_restart( cinfo ) ) { + return FALSE; + } + } + } + + /* Load up working state. + * We can avoid loading/saving bitread state if in an EOB run. + */ + EOBRUN = entropy->saved.EOBRUN;/* only part of saved state we care about */ + + /* There is always only one block per MCU */ + + if ( EOBRUN > 0 ) { /* if it's a band of zeroes... */ + EOBRUN--; + } /* ...process it now (we do nothing) */ + else { + BITREAD_LOAD_STATE( cinfo, entropy->bitstate ); + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + for ( k = cinfo->Ss; k <= Se; k++ ) { + HUFF_DECODE( s, br_state, tbl, return FALSE, label2 ); + r = s >> 4; + s &= 15; + if ( s ) { + k += r; + + CHECK_BIT_BUFFER( br_state, s, return FALSE ); + r = GET_BITS( s ); + s = HUFF_EXTEND( r, s ); + /* Scale and output coefficient in natural (dezigzagged) order */ + ( *block )[jpeg_natural_order[k]] = (JCOEF) ( s << Al ); + } else { + if ( r == 15 ) {/* ZRL */ + k += 15;/* skip 15 zeroes in band */ + } else {/* EOBr, run length is 2^r + appended bits */ + EOBRUN = 1 << r; + if ( r ) {/* EOBr, r > 0 */ + CHECK_BIT_BUFFER( br_state, r, return FALSE ); + r = GET_BITS( r ); + EOBRUN += r; + } + EOBRUN--; /* this band is processed at this moment */ + break; /* force end-of-band */ + } + } + } + + BITREAD_SAVE_STATE( cinfo, entropy->bitstate ); + } + + /* Completed MCU, so update state */ + entropy->saved.EOBRUN = EOBRUN;/* only part of saved state we care about */ + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF boolean +decode_mcu_DC_refine( j_decompress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int p1 = 1 << cinfo->Al;/* 1 in the bit position being coded */ + int blkn; + JBLOCKROW block; + BITREAD_STATE_VARS; + + /* Process restart marker if needed; may have to suspend */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !process_restart( cinfo ) ) { + return FALSE; + } + } + } + + /* Load up working state */ + BITREAD_LOAD_STATE( cinfo, entropy->bitstate ); + + /* Outer loop handles each block in the MCU */ + + for ( blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++ ) { + block = MCU_data[blkn]; + + /* Encoded data is simply the next bit of the two's-complement DC value */ + CHECK_BIT_BUFFER( br_state, 1, return FALSE ); + if ( GET_BITS( 1 ) ) { + ( *block )[0] |= p1; + } + /* Note: since we use |=, repeating the assignment later is safe */ + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE( cinfo, entropy->bitstate ); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC successive approximation refinement scan. + */ + +METHODDEF boolean +decode_mcu_AC_refine( j_decompress_ptr cinfo, JBLOCKROW * MCU_data ) { + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Se = cinfo->Se; + int p1 = 1 << cinfo->Al;/* 1 in the bit position being coded */ + int m1 = ( -1 ) << cinfo->Al;/* -1 in the bit position being coded */ + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + JCOEFPTR thiscoef; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + int num_newnz; + int newnz_pos[DCTSIZE2]; + + /* Process restart marker if needed; may have to suspend */ + if ( cinfo->restart_interval ) { + if ( entropy->restarts_to_go == 0 ) { + if ( !process_restart( cinfo ) ) { + return FALSE; + } + } + } + + /* Load up working state */ + BITREAD_LOAD_STATE( cinfo, entropy->bitstate ); + EOBRUN = entropy->saved.EOBRUN;/* only part of saved state we care about */ + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + /* If we are forced to suspend, we must undo the assignments to any newly + * nonzero coefficients in the block, because otherwise we'd get confused + * next time about which coefficients were already nonzero. + * But we need not undo addition of bits to already-nonzero coefficients; + * instead, we can test the current bit position to see if we already did it. + */ + num_newnz = 0; + + /* initialize coefficient loop counter to start of band */ + k = cinfo->Ss; + + if ( EOBRUN == 0 ) { + for (; k <= Se; k++ ) { + HUFF_DECODE( s, br_state, tbl, goto undoit, label3 ); + r = s >> 4; + s &= 15; + if ( s ) { + if ( s != 1 ) {/* size of new coef should always be 1 */ + WARNMS( cinfo, JWRN_HUFF_BAD_CODE ); + } + CHECK_BIT_BUFFER( br_state, 1, goto undoit ); + if ( GET_BITS( 1 ) ) { + s = p1; + } /* newly nonzero coef is positive */ + else { + s = m1; + } /* newly nonzero coef is negative */ + } else { + if ( r != 15 ) { + EOBRUN = 1 << r;/* EOBr, run length is 2^r + appended bits */ + if ( r ) { + CHECK_BIT_BUFFER( br_state, r, goto undoit ); + r = GET_BITS( r ); + EOBRUN += r; + } + break; /* rest of block is handled by EOB logic */ + } + /* note s = 0 for processing ZRL */ + } + /* Advance over already-nonzero coefs and r still-zero coefs, + * appending correction bits to the nonzeroes. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + do { + thiscoef = *block + jpeg_natural_order[k]; + if ( *thiscoef != 0 ) { + CHECK_BIT_BUFFER( br_state, 1, goto undoit ); + if ( GET_BITS( 1 ) ) { + if ( ( *thiscoef & p1 ) == 0 ) {/* do nothing if already changed it */ + if ( *thiscoef >= 0 ) { + *thiscoef += p1; + } else { + *thiscoef += m1; + } + } + } + } else { + if ( --r < 0 ) { + break; + } /* reached target zero coefficient */ + } + k++; + } while ( k <= Se ); + if ( s ) { + int pos = jpeg_natural_order[k]; + /* Output newly nonzero coefficient */ + ( *block )[pos] = (JCOEF) s; + /* Remember its position in case we have to suspend */ + newnz_pos[num_newnz++] = pos; + } + } + } + + if ( EOBRUN > 0 ) { + /* Scan any remaining coefficient positions after the end-of-band + * (the last newly nonzero coefficient, if any). Append a correction + * bit to each already-nonzero coefficient. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + for (; k <= Se; k++ ) { + thiscoef = *block + jpeg_natural_order[k]; + if ( *thiscoef != 0 ) { + CHECK_BIT_BUFFER( br_state, 1, goto undoit ); + if ( GET_BITS( 1 ) ) { + if ( ( *thiscoef & p1 ) == 0 ) {/* do nothing if already changed it */ + if ( *thiscoef >= 0 ) { + *thiscoef += p1; + } else { + *thiscoef += m1; + } + } + } + } + } + /* Count one block completed in EOB run */ + EOBRUN--; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE( cinfo, entropy->bitstate ); + entropy->saved.EOBRUN = EOBRUN;/* only part of saved state we care about */ + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; + +undoit: + /* Re-zero any output coefficients that we made newly nonzero */ + while ( num_newnz > 0 ) { + ( *block )[newnz_pos[--num_newnz]] = 0; + } + + return FALSE; +} + + +/* + * Module initialization routine for progressive Huffman entropy decoding. + */ + +GLOBAL void +jinit_phuff_decoder( j_decompress_ptr cinfo ) { + phuff_entropy_ptr entropy; + int * coef_bit_ptr; + int ci, i; + + entropy = (phuff_entropy_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( phuff_entropy_decoder ) ); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_phuff_decoder; + + /* Mark derived tables unallocated */ + for ( i = 0; i < NUM_HUFF_TBLS; i++ ) { + entropy->derived_tbls[i] = NULL; + } + + /* Create progression status table */ + cinfo->coef_bits = ( int ( * )[DCTSIZE2] ) + ( *cinfo->mem->alloc_small ) ( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * DCTSIZE2 * SIZEOF( int ) ); + coef_bit_ptr = &cinfo->coef_bits[0][0]; + for ( ci = 0; ci < cinfo->num_components; ci++ ) { + for ( i = 0; i < DCTSIZE2; i++ ) { + *coef_bit_ptr++ = -1; + } + } +} + +#endif /* D_PROGRESSIVE_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jdpostct.cpp b/neo/renderer/jpeg-6/jdpostct.cpp new file mode 100644 index 00000000..e7924b3b --- /dev/null +++ b/neo/renderer/jpeg-6/jdpostct.cpp @@ -0,0 +1,290 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub;/* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image;/* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer;/* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row;/* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF void post_process_1pass +JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) ); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void post_process_prepass +JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) ); +METHODDEF void post_process_2pass +JPP( ( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) ); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_dpost( j_decompress_ptr cinfo, J_BUF_MODE pass_mode ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch ( pass_mode ) { + case JBUF_PASS_THRU: + if ( cinfo->quantize_colors ) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if ( post->buffer == NULL ) { + post->buffer = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE ); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if ( post->whole_image == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if ( post->whole_image == NULL ) { + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + } + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF void +post_process_1pass( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if ( max_rows > post->strip_height ) { + max_rows = post->strip_height; + } + num_rows = 0; + ( *cinfo->upsample->upsample )( cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows ); + /* Quantize and emit data. */ + ( *cinfo->cquantize->color_quantize )( cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows ); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF void +post_process_prepass( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if ( post->next_row == 0 ) { + post->buffer = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE ); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + ( *cinfo->upsample->upsample )( cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height ); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if ( post->next_row > old_next_row ) { + num_rows = post->next_row - old_next_row; + ( *cinfo->cquantize->color_quantize )( cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows ); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if ( post->next_row >= post->strip_height ) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF void +post_process_2pass( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if ( post->next_row == 0 ) { + post->buffer = ( *cinfo->mem->access_virt_sarray ) + ( (j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE ); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row;/* available in strip */ + max_rows = out_rows_avail - *out_row_ctr;/* available in output area */ + if ( num_rows > max_rows ) { + num_rows = max_rows; + } + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if ( num_rows > max_rows ) { + num_rows = max_rows; + } + + /* Quantize and emit data. */ + ( *cinfo->cquantize->color_quantize )( cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows ); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if ( post->next_row >= post->strip_height ) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL void +jinit_d_post_controller( j_decompress_ptr cinfo, boolean need_full_buffer ) { + my_post_ptr post; + + post = (my_post_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_post_controller ) ); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL;/* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if ( cinfo->quantize_colors ) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if ( need_full_buffer ) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = ( *cinfo->mem->request_virt_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up( (long) cinfo->output_height, + (long) post->strip_height ), + post->strip_height ); +#else + ERREXIT( cinfo, JERR_BAD_BUFFER_MODE ); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height ); + } + } +} diff --git a/neo/renderer/jpeg-6/jdsample.cpp b/neo/renderer/jpeg-6/jdsample.cpp new file mode 100644 index 00000000..6a47661a --- /dev/null +++ b/neo/renderer/jpeg-6/jdsample.cpp @@ -0,0 +1,478 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD ( void, upsample1_ptr, + ( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) ); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_upsample( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF void +sep_upsample( j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION * in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION * out_row_ctr, + JDIMENSION out_rows_avail ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if ( upsample->next_row_out >= cinfo->max_v_samp_factor ) { + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + ( *upsample->methods[ci] )( cinfo, compptr, + input_buf[ci] + ( *in_row_group_ctr * upsample->rowgroup_height[ci] ), + upsample->color_buf + ci ); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) ( cinfo->max_v_samp_factor - upsample->next_row_out ); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if ( num_rows > upsample->rows_to_go ) { + num_rows = upsample->rows_to_go; + } + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if ( num_rows > out_rows_avail ) { + num_rows = out_rows_avail; + } + + ( *cinfo->cconvert->color_convert )( cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows ); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if ( upsample->next_row_out >= cinfo->max_v_samp_factor ) { + ( *in_row_group_ctr )++; + } +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF void +fullsize_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF void +noop_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + *output_data_ptr = NULL;/* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF void +int_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while ( outrow < cinfo->max_v_samp_factor ) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while ( outptr < outend ) { + invalue = *inptr++;/* don't need GETJSAMPLE() here */ + for ( h = h_expand; h > 0; h-- ) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if ( v_expand > 1 ) { + jcopy_sample_rows( output_data, outrow, output_data, outrow + 1, + v_expand - 1, cinfo->output_width ); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v1_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for ( inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++ ) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while ( outptr < outend ) { + invalue = *inptr++;/* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v2_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while ( outrow < cinfo->max_v_samp_factor ) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while ( outptr < outend ) { + invalue = *inptr++;/* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows( output_data, outrow, output_data, outrow + 1, + 1, cinfo->output_width ); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_fancy_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for ( inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++ ) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE( *inptr++ ); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ( ( invalue * 3 + GETJSAMPLE( *inptr ) + 2 ) >> 2 ); + + for ( colctr = compptr->downsampled_width - 2; colctr > 0; colctr-- ) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE( *inptr++ ) * 3; + *outptr++ = (JSAMPLE) ( ( invalue + GETJSAMPLE( inptr[-2] ) + 1 ) >> 2 ); + *outptr++ = (JSAMPLE) ( ( invalue + GETJSAMPLE( *inptr ) + 2 ) >> 2 ); + } + + /* Special case for last column */ + invalue = GETJSAMPLE( *inptr ); + *outptr++ = (JSAMPLE) ( ( invalue * 3 + GETJSAMPLE( inptr[-1] ) + 1 ) >> 2 ); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF void +h2v2_fancy_upsample( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr ) { + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while ( outrow < cinfo->max_v_samp_factor ) { + for ( v = 0; v < 2; v++ ) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if ( v == 0 ) {/* next nearest is row above */ + inptr1 = input_data[inrow - 1]; + } else {/* next nearest is row below */ + inptr1 = input_data[inrow + 1]; + } + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE( *inptr0++ ) * 3 + GETJSAMPLE( *inptr1++ ); + nextcolsum = GETJSAMPLE( *inptr0++ ) * 3 + GETJSAMPLE( *inptr1++ ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 4 + 8 ) >> 4 ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + nextcolsum + 7 ) >> 4 ); + lastcolsum = thiscolsum; + thiscolsum = nextcolsum; + + for ( colctr = compptr->downsampled_width - 2; colctr > 0; colctr-- ) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE( *inptr0++ ) * 3 + GETJSAMPLE( *inptr1++ ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + lastcolsum + 8 ) >> 4 ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + nextcolsum + 7 ) >> 4 ); + lastcolsum = thiscolsum; + thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ( ( thiscolsum * 3 + lastcolsum + 8 ) >> 4 ); + *outptr++ = (JSAMPLE) ( ( thiscolsum * 4 + 7 ) >> 4 ); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL void +jinit_upsampler( j_decompress_ptr cinfo ) { + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_upsampler ) ); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE;/* until we find out differently */ + + if ( cinfo->CCIR601_sampling ) {/* this isn't supported */ + ERREXIT( cinfo, JERR_CCIR601_NOTIMPL ); + } + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for ( ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++ ) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = ( compptr->h_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; + v_in_group = ( compptr->v_samp_factor * compptr->DCT_scaled_size ) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group;/* save for use later */ + need_buffer = TRUE; + if ( !compptr->component_needed ) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if ( h_in_group == h_out_group && v_in_group == v_out_group ) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if ( h_in_group * 2 == h_out_group && + v_in_group == v_out_group ) { + /* Special cases for 2h1v upsampling */ + if ( ( do_fancy ) && ( compptr->downsampled_width > 2 ) ) { + upsample->methods[ci] = h2v1_fancy_upsample; + } else { + upsample->methods[ci] = h2v1_upsample; + } + } else if ( h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group ) { + /* Special cases for 2h2v upsampling */ + if ( ( do_fancy ) && ( compptr->downsampled_width > 2 ) ) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else { + upsample->methods[ci] = h2v2_upsample; + } + } else if ( ( h_out_group % h_in_group ) == 0 && + ( v_out_group % v_in_group ) == 0 ) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) ( h_out_group / h_in_group ); + upsample->v_expand[ci] = (UINT8) ( v_out_group / v_in_group ); + } else { + ERREXIT( cinfo, JERR_FRACT_SAMPLE_NOTIMPL ); + } + if ( need_buffer ) { + upsample->color_buf[ci] = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up( (long) cinfo->output_width, + (long) cinfo->max_h_samp_factor ), + (JDIMENSION) cinfo->max_v_samp_factor ); + } + } +} diff --git a/neo/renderer/jpeg-6/jdtrans.cpp b/neo/renderer/jpeg-6/jdtrans.cpp new file mode 100644 index 00000000..199e4ca2 --- /dev/null +++ b/neo/renderer/jpeg-6/jdtrans.cpp @@ -0,0 +1,125 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transdecode_master_selection JPP( (j_decompress_ptr cinfo) ); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL jvirt_barray_ptr * +jpeg_read_coefficients( j_decompress_ptr cinfo ) { + if ( cinfo->global_state == DSTATE_READY ) { + /* First call: initialize active modules */ + transdecode_master_selection( cinfo ); + cinfo->global_state = DSTATE_RDCOEFS; + } else if ( cinfo->global_state != DSTATE_RDCOEFS ) { + ERREXIT1( cinfo, JERR_BAD_STATE, cinfo->global_state ); + } + /* Absorb whole file into the coef buffer */ + for (;; ) { + int retcode; + /* Call progress monitor hook if present */ + if ( cinfo->progress != NULL ) { + ( *cinfo->progress->progress_monitor )( (j_common_ptr) cinfo ); + } + /* Absorb some more input */ + retcode = ( *cinfo->inputctl->consume_input )( cinfo ); + if ( retcode == JPEG_SUSPENDED ) { + return NULL; + } + if ( retcode == JPEG_REACHED_EOI ) { + break; + } + /* Advance progress counter if appropriate */ + if ( ( cinfo->progress != NULL ) && + ( ( retcode == JPEG_ROW_COMPLETED ) || ( retcode == JPEG_REACHED_SOS ) ) ) { + if ( ++cinfo->progress->pass_counter >= cinfo->progress->pass_limit ) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + return cinfo->coef->coef_arrays; +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL void +transdecode_master_selection( j_decompress_ptr cinfo ) { + /* Entropy decoding: either Huffman or arithmetic coding. */ + if ( cinfo->arith_code ) { + ERREXIT( cinfo, JERR_ARITH_NOTIMPL ); + } else { + if ( cinfo->progressive_mode ) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder( cinfo ); +#else + ERREXIT( cinfo, JERR_NOT_COMPILED ); +#endif + } else { + jinit_huff_decoder( cinfo ); + } + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller( cinfo, TRUE ); + + /* We can now tell the memory manager to allocate virtual arrays. */ + ( *cinfo->mem->realize_virt_arrays )( (j_common_ptr) cinfo ); + + /* Initialize input side of decompressor to consume first scan. */ + ( *cinfo->inputctl->start_input_pass )( cinfo ); + + /* Initialize progress monitoring. */ + if ( cinfo->progress != NULL ) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if ( cinfo->progressive_mode ) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if ( cinfo->inputctl->has_multiple_scans ) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/neo/renderer/jpeg-6/jerror.cpp b/neo/renderer/jpeg-6/jerror.cpp new file mode 100644 index 00000000..3f207642 --- /dev/null +++ b/neo/renderer/jpeg-6/jerror.cpp @@ -0,0 +1,233 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ + +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +extern void jpg_Error( const char * fmt, ... ); +extern void jpg_Printf( const char * fmt, ... ); + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE( code, string ) string, + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF void +error_exit( j_common_ptr cinfo ) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + ( *cinfo->err->format_message )( cinfo, buffer ); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy( cinfo ); + + jpg_Error( "%s\n", buffer ); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + */ + +METHODDEF void +output_message( j_common_ptr cinfo ) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + ( *cinfo->err->format_message )( cinfo, buffer ); + + /* Send it to stderr, adding a newline */ + jpg_Printf( "%s\n", buffer ); +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF void +emit_message( j_common_ptr cinfo, int msg_level ) { + struct jpeg_error_mgr * err = cinfo->err; + + if ( msg_level < 0 ) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if ( ( err->num_warnings == 0 ) || ( err->trace_level >= 3 ) ) { + ( *err->output_message )( cinfo ); + } + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if ( err->trace_level >= msg_level ) { + ( *err->output_message )( cinfo ); + } + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF void +format_message( j_common_ptr cinfo, char * buffer ) { + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if ( ( msg_code > 0 ) && ( msg_code <= err->last_jpeg_message ) ) { + msgtext = err->jpeg_message_table[msg_code]; + } else if ( err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message ) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if ( msgtext == NULL ) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ( ( ch = *msgptr++ ) != '\0' ) { + if ( ch == '%' ) { + if ( *msgptr == 's' ) { + isstring = TRUE; + } + break; + } + } + + /* Format the message into the passed buffer */ + if ( isstring ) { + sprintf( buffer, msgtext, err->msg_parm.s ); + } else { + sprintf( buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7] ); + } +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF void +reset_error_mgr( j_common_ptr cinfo ) { + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0;/* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL struct jpeg_error_mgr * +jpeg_std_error( struct jpeg_error_mgr * err ) { + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0;/* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/neo/renderer/jpeg-6/jerror.h b/neo/renderer/jpeg-6/jerror.h new file mode 100644 index 00000000..bf60e7ec --- /dev/null +++ b/neo/renderer/jpeg-6/jerror.h @@ -0,0 +1,273 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_COUNTS, "Bogus DHT counts") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_MINOR, "Unknown JFIF minor revision number %d.%02d") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Skipping marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/neo/renderer/jpeg-6/jfdctflt.cpp b/neo/renderer/jpeg-6/jfdctflt.cpp new file mode 100644 index 00000000..8dba08fb --- /dev/null +++ b/neo/renderer/jpeg-6/jfdctflt.cpp @@ -0,0 +1,167 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_float( FAST_FLOAT * data ) { + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT * dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3;/* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11;/* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = ( tmp12 + tmp13 ) * ( (FAST_FLOAT) 0.707106781 );/* c4 */ + dataptr[2] = tmp13 + z1;/* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5;/* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = ( tmp10 - tmp12 ) * ( (FAST_FLOAT) 0.382683433 );/* c6 */ + z2 = ( (FAST_FLOAT) 0.541196100 ) * tmp10 + z5;/* c2-c6 */ + z4 = ( (FAST_FLOAT) 1.306562965 ) * tmp12 + z5;/* c2+c6 */ + z3 = tmp11 * ( (FAST_FLOAT) 0.707106781 );/* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2;/* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[DCTSIZE * 0] + dataptr[DCTSIZE * 7]; + tmp7 = dataptr[DCTSIZE * 0] - dataptr[DCTSIZE * 7]; + tmp1 = dataptr[DCTSIZE * 1] + dataptr[DCTSIZE * 6]; + tmp6 = dataptr[DCTSIZE * 1] - dataptr[DCTSIZE * 6]; + tmp2 = dataptr[DCTSIZE * 2] + dataptr[DCTSIZE * 5]; + tmp5 = dataptr[DCTSIZE * 2] - dataptr[DCTSIZE * 5]; + tmp3 = dataptr[DCTSIZE * 3] + dataptr[DCTSIZE * 4]; + tmp4 = dataptr[DCTSIZE * 3] - dataptr[DCTSIZE * 4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3;/* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE * 0] = tmp10 + tmp11;/* phase 3 */ + dataptr[DCTSIZE * 4] = tmp10 - tmp11; + + z1 = ( tmp12 + tmp13 ) * ( (FAST_FLOAT) 0.707106781 );/* c4 */ + dataptr[DCTSIZE * 2] = tmp13 + z1;/* phase 5 */ + dataptr[DCTSIZE * 6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5;/* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = ( tmp10 - tmp12 ) * ( (FAST_FLOAT) 0.382683433 );/* c6 */ + z2 = ( (FAST_FLOAT) 0.541196100 ) * tmp10 + z5;/* c2-c6 */ + z4 = ( (FAST_FLOAT) 1.306562965 ) * tmp12 + z5;/* c2+c6 */ + z3 = tmp11 * ( (FAST_FLOAT) 0.707106781 );/* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE * 5] = z13 + z2;/* phase 6 */ + dataptr[DCTSIZE * 3] = z13 - z2; + dataptr[DCTSIZE * 1] = z11 + z4; + dataptr[DCTSIZE * 7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jfdctfst.cpp b/neo/renderer/jpeg-6/jfdctfst.cpp new file mode 100644 index 00000000..86ad2905 --- /dev/null +++ b/neo/renderer/jpeg-6/jfdctfst.cpp @@ -0,0 +1,223 @@ +/* + * jfdctfst.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jfdctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * Again to save a few shifts, the intermediate results between pass 1 and + * pass 2 are not upscaled, but are represented only to integral precision. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#define CONST_BITS 8 + + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_0_382683433 ( (INT32) 98 ) /* FIX(0.382683433) */ +#define FIX_0_541196100 ( (INT32) 139 ) /* FIX(0.541196100) */ +#define FIX_0_707106781 ( (INT32) 181 ) /* FIX(0.707106781) */ +#define FIX_1_306562965 ( (INT32) 334 ) /* FIX(1.306562965) */ +#else +#define FIX_0_382683433 FIX( 0.382683433 ) +#define FIX_0_541196100 FIX( 0.541196100 ) +#define FIX_0_707106781 FIX( 0.707106781 ) +#define FIX_1_306562965 FIX( 1.306562965 ) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE( x, n ) RIGHT_SHIFT( x, n ) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY( var, const ) ( (DCTELEM) DESCALE( ( var ) * ( const ), CONST_BITS ) ) + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_ifast( DCTELEM * data ) { + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z1, z2, z3, z4, z5, z11, z13; + DCTELEM * dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3;/* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11;/* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = MULTIPLY( tmp12 + tmp13, FIX_0_707106781 );/* c4 */ + dataptr[2] = tmp13 + z1;/* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5;/* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY( tmp10 - tmp12, FIX_0_382683433 );/* c6 */ + z2 = MULTIPLY( tmp10, FIX_0_541196100 ) + z5;/* c2-c6 */ + z4 = MULTIPLY( tmp12, FIX_1_306562965 ) + z5;/* c2+c6 */ + z3 = MULTIPLY( tmp11, FIX_0_707106781 );/* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2;/* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[DCTSIZE * 0] + dataptr[DCTSIZE * 7]; + tmp7 = dataptr[DCTSIZE * 0] - dataptr[DCTSIZE * 7]; + tmp1 = dataptr[DCTSIZE * 1] + dataptr[DCTSIZE * 6]; + tmp6 = dataptr[DCTSIZE * 1] - dataptr[DCTSIZE * 6]; + tmp2 = dataptr[DCTSIZE * 2] + dataptr[DCTSIZE * 5]; + tmp5 = dataptr[DCTSIZE * 2] - dataptr[DCTSIZE * 5]; + tmp3 = dataptr[DCTSIZE * 3] + dataptr[DCTSIZE * 4]; + tmp4 = dataptr[DCTSIZE * 3] - dataptr[DCTSIZE * 4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3;/* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE * 0] = tmp10 + tmp11;/* phase 3 */ + dataptr[DCTSIZE * 4] = tmp10 - tmp11; + + z1 = MULTIPLY( tmp12 + tmp13, FIX_0_707106781 );/* c4 */ + dataptr[DCTSIZE * 2] = tmp13 + z1;/* phase 5 */ + dataptr[DCTSIZE * 6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5;/* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY( tmp10 - tmp12, FIX_0_382683433 );/* c6 */ + z2 = MULTIPLY( tmp10, FIX_0_541196100 ) + z5;/* c2-c6 */ + z4 = MULTIPLY( tmp12, FIX_1_306562965 ) + z5;/* c2+c6 */ + z3 = MULTIPLY( tmp11, FIX_0_707106781 );/* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE * 5] = z13 + z2;/* phase 6 */ + dataptr[DCTSIZE * 3] = z13 - z2; + dataptr[DCTSIZE * 1] = z11 + z4; + dataptr[DCTSIZE * 7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jfdctint.cpp b/neo/renderer/jpeg-6/jfdctint.cpp new file mode 100644 index 00000000..eda7e42a --- /dev/null +++ b/neo/renderer/jpeg-6/jfdctint.cpp @@ -0,0 +1,282 @@ +/* + * jfdctint.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D DCT step produces outputs which are a factor of sqrt(N) + * larger than the true DCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D DCT, + * because the y0 and y4 outputs need not be divided by sqrt(N). + * In the IJG code, this factor of 8 is removed by the quantization step + * (in jcdctmgr.c), NOT in this module. + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (For 12-bit sample data, the intermediate + * array is INT32 anyway.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ( (INT32) 2446 ) /* FIX(0.298631336) */ +#define FIX_0_390180644 ( (INT32) 3196 ) /* FIX(0.390180644) */ +#define FIX_0_541196100 ( (INT32) 4433 ) /* FIX(0.541196100) */ +#define FIX_0_765366865 ( (INT32) 6270 ) /* FIX(0.765366865) */ +#define FIX_0_899976223 ( (INT32) 7373 ) /* FIX(0.899976223) */ +#define FIX_1_175875602 ( (INT32) 9633 ) /* FIX(1.175875602) */ +#define FIX_1_501321110 ( (INT32) 12299 ) /* FIX(1.501321110) */ +#define FIX_1_847759065 ( (INT32) 15137 ) /* FIX(1.847759065) */ +#define FIX_1_961570560 ( (INT32) 16069 ) /* FIX(1.961570560) */ +#define FIX_2_053119869 ( (INT32) 16819 ) /* FIX(2.053119869) */ +#define FIX_2_562915447 ( (INT32) 20995 ) /* FIX(2.562915447) */ +#define FIX_3_072711026 ( (INT32) 25172 ) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX( 0.298631336 ) +#define FIX_0_390180644 FIX( 0.390180644 ) +#define FIX_0_541196100 FIX( 0.541196100 ) +#define FIX_0_765366865 FIX( 0.765366865 ) +#define FIX_0_899976223 FIX( 0.899976223 ) +#define FIX_1_175875602 FIX( 1.175875602 ) +#define FIX_1_501321110 FIX( 1.501321110 ) +#define FIX_1_847759065 FIX( 1.847759065 ) +#define FIX_1_961570560 FIX( 1.961570560 ) +#define FIX_2_053119869 FIX( 2.053119869 ) +#define FIX_2_562915447 FIX( 2.562915447 ) +#define FIX_3_072711026 FIX( 3.072711026 ) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY( var, const ) MULTIPLY16C16( var, const ) +#else +#define MULTIPLY( var, const ) ( ( var ) * ( const ) ) +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_islow( DCTELEM * data ) { + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + DCTELEM * dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = (DCTELEM) ( ( tmp10 + tmp11 ) << PASS1_BITS ); + dataptr[4] = (DCTELEM) ( ( tmp10 - tmp11 ) << PASS1_BITS ); + + z1 = MULTIPLY( tmp12 + tmp13, FIX_0_541196100 ); + dataptr[2] = (DCTELEM) DESCALE( z1 + MULTIPLY( tmp13, FIX_0_765366865 ), + CONST_BITS - PASS1_BITS ); + dataptr[6] = (DCTELEM) DESCALE( z1 + MULTIPLY( tmp12, -FIX_1_847759065 ), + CONST_BITS - PASS1_BITS ); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY( z3 + z4, FIX_1_175875602 );/* sqrt(2) * c3 */ + + tmp4 = MULTIPLY( tmp4, FIX_0_298631336 );/* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY( tmp5, FIX_2_053119869 );/* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY( tmp6, FIX_3_072711026 );/* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY( tmp7, FIX_1_501321110 );/* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY( z1, -FIX_0_899976223 );/* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY( z2, -FIX_2_562915447 );/* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY( z3, -FIX_1_961570560 );/* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY( z4, -FIX_0_390180644 );/* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[7] = (DCTELEM) DESCALE( tmp4 + z1 + z3, CONST_BITS - PASS1_BITS ); + dataptr[5] = (DCTELEM) DESCALE( tmp5 + z2 + z4, CONST_BITS - PASS1_BITS ); + dataptr[3] = (DCTELEM) DESCALE( tmp6 + z2 + z3, CONST_BITS - PASS1_BITS ); + dataptr[1] = (DCTELEM) DESCALE( tmp7 + z1 + z4, CONST_BITS - PASS1_BITS ); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for ( ctr = DCTSIZE - 1; ctr >= 0; ctr-- ) { + tmp0 = dataptr[DCTSIZE * 0] + dataptr[DCTSIZE * 7]; + tmp7 = dataptr[DCTSIZE * 0] - dataptr[DCTSIZE * 7]; + tmp1 = dataptr[DCTSIZE * 1] + dataptr[DCTSIZE * 6]; + tmp6 = dataptr[DCTSIZE * 1] - dataptr[DCTSIZE * 6]; + tmp2 = dataptr[DCTSIZE * 2] + dataptr[DCTSIZE * 5]; + tmp5 = dataptr[DCTSIZE * 2] - dataptr[DCTSIZE * 5]; + tmp3 = dataptr[DCTSIZE * 3] + dataptr[DCTSIZE * 4]; + tmp4 = dataptr[DCTSIZE * 3] - dataptr[DCTSIZE * 4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE * 0] = (DCTELEM) DESCALE( tmp10 + tmp11, PASS1_BITS ); + dataptr[DCTSIZE * 4] = (DCTELEM) DESCALE( tmp10 - tmp11, PASS1_BITS ); + + z1 = MULTIPLY( tmp12 + tmp13, FIX_0_541196100 ); + dataptr[DCTSIZE * 2] = (DCTELEM) DESCALE( z1 + MULTIPLY( tmp13, FIX_0_765366865 ), + CONST_BITS + PASS1_BITS ); + dataptr[DCTSIZE * 6] = (DCTELEM) DESCALE( z1 + MULTIPLY( tmp12, -FIX_1_847759065 ), + CONST_BITS + PASS1_BITS ); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY( z3 + z4, FIX_1_175875602 );/* sqrt(2) * c3 */ + + tmp4 = MULTIPLY( tmp4, FIX_0_298631336 );/* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY( tmp5, FIX_2_053119869 );/* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY( tmp6, FIX_3_072711026 );/* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY( tmp7, FIX_1_501321110 );/* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY( z1, -FIX_0_899976223 );/* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY( z2, -FIX_2_562915447 );/* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY( z3, -FIX_1_961570560 );/* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY( z4, -FIX_0_390180644 );/* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[DCTSIZE * 7] = (DCTELEM) DESCALE( tmp4 + z1 + z3, + CONST_BITS + PASS1_BITS ); + dataptr[DCTSIZE * 5] = (DCTELEM) DESCALE( tmp5 + z2 + z4, + CONST_BITS + PASS1_BITS ); + dataptr[DCTSIZE * 3] = (DCTELEM) DESCALE( tmp6 + z2 + z3, + CONST_BITS + PASS1_BITS ); + dataptr[DCTSIZE * 1] = (DCTELEM) DESCALE( tmp7 + z1 + z4, + CONST_BITS + PASS1_BITS ); + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jidctflt.cpp b/neo/renderer/jpeg-6/jidctflt.cpp new file mode 100644 index 00000000..62f7a8f3 --- /dev/null +++ b/neo/renderer/jpeg-6/jidctflt.cpp @@ -0,0 +1,240 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE( coef, quantval ) ( ( (FAST_FLOAT) ( coef ) ) * ( quantval ) ) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_float( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE * range_limit = IDCT_range_limit( cinfo ); + int ctr; + FAST_FLOAT workspace[DCTSIZE2];/* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for ( ctr = DCTSIZE; ctr > 0; ctr-- ) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ( ( inptr[DCTSIZE * 1] | inptr[DCTSIZE * 2] | inptr[DCTSIZE * 3] | + inptr[DCTSIZE * 4] | inptr[DCTSIZE * 5] | inptr[DCTSIZE * 6] | + inptr[DCTSIZE * 7] ) == 0 ) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + wsptr[DCTSIZE * 2] = dcval; + wsptr[DCTSIZE * 3] = dcval; + wsptr[DCTSIZE * 4] = dcval; + wsptr[DCTSIZE * 5] = dcval; + wsptr[DCTSIZE * 6] = dcval; + wsptr[DCTSIZE * 7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + tmp1 = DEQUANTIZE( inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2] ); + tmp2 = DEQUANTIZE( inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4] ); + tmp3 = DEQUANTIZE( inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6] ); + + tmp10 = tmp0 + tmp2;/* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3;/* phases 5-3 */ + tmp12 = ( tmp1 - tmp3 ) * ( (FAST_FLOAT) 1.414213562 ) - tmp13;/* 2*c4 */ + + tmp0 = tmp10 + tmp13;/* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE( inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1] ); + tmp5 = DEQUANTIZE( inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3] ); + tmp6 = DEQUANTIZE( inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5] ); + tmp7 = DEQUANTIZE( inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7] ); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = ( z11 - z13 ) * ( (FAST_FLOAT) 1.414213562 );/* 2*c4 */ + + z5 = ( z10 + z12 ) * ( (FAST_FLOAT) 1.847759065 );/* 2*c2 */ + tmp10 = ( (FAST_FLOAT) 1.082392200 ) * z12 - z5;/* 2*(c2-c6) */ + tmp12 = ( (FAST_FLOAT) -2.613125930 ) * z10 + z5;/* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7;/* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE * 0] = tmp0 + tmp7; + wsptr[DCTSIZE * 7] = tmp0 - tmp7; + wsptr[DCTSIZE * 1] = tmp1 + tmp6; + wsptr[DCTSIZE * 6] = tmp1 - tmp6; + wsptr[DCTSIZE * 2] = tmp2 + tmp5; + wsptr[DCTSIZE * 5] = tmp2 - tmp5; + wsptr[DCTSIZE * 4] = tmp3 + tmp4; + wsptr[DCTSIZE * 3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for ( ctr = 0; ctr < DCTSIZE; ctr++ ) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = ( wsptr[2] - wsptr[6] ) * ( (FAST_FLOAT) 1.414213562 ) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = ( z11 - z13 ) * ( (FAST_FLOAT) 1.414213562 ); + + z5 = ( z10 + z12 ) * ( (FAST_FLOAT) 1.847759065 );/* 2*c2 */ + tmp10 = ( (FAST_FLOAT) 1.082392200 ) * z12 - z5;/* 2*(c2-c6) */ + tmp12 = ( (FAST_FLOAT) -2.613125930 ) * z10 + z5;/* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE( (INT32) ( tmp0 + tmp7 ), 3 ) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE( (INT32) ( tmp0 - tmp7 ), 3 ) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE( (INT32) ( tmp1 + tmp6 ), 3 ) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE( (INT32) ( tmp1 - tmp6 ), 3 ) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE( (INT32) ( tmp2 + tmp5 ), 3 ) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE( (INT32) ( tmp2 - tmp5 ), 3 ) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE( (INT32) ( tmp3 + tmp4 ), 3 ) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE( (INT32) ( tmp3 - tmp4 ), 3 ) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jidctfst.cpp b/neo/renderer/jpeg-6/jidctfst.cpp new file mode 100644 index 00000000..7fbb12dc --- /dev/null +++ b/neo/renderer/jpeg-6/jidctfst.cpp @@ -0,0 +1,366 @@ +/* + * jidctfst.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jidctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * The dequantized coefficients are not integers because the AA&N scaling + * factors have been incorporated. We represent them scaled up by PASS1_BITS, + * so that the first and second IDCT rounds have the same input scaling. + * For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = PASS1_BITS so as to + * avoid a descaling shift; this compromises accuracy rather drastically + * for small quantization table entries, but it saves a lot of shifts. + * For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway, + * so we use a much larger scaling factor to preserve accuracy. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 8 +#define PASS1_BITS 2 +#else +#define CONST_BITS 8 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_1_082392200 ( (INT32) 277 ) /* FIX(1.082392200) */ +#define FIX_1_414213562 ( (INT32) 362 ) /* FIX(1.414213562) */ +#define FIX_1_847759065 ( (INT32) 473 ) /* FIX(1.847759065) */ +#define FIX_2_613125930 ( (INT32) 669 ) /* FIX(2.613125930) */ +#else +#define FIX_1_082392200 FIX( 1.082392200 ) +#define FIX_1_414213562 FIX( 1.414213562 ) +#define FIX_1_847759065 FIX( 1.847759065 ) +#define FIX_2_613125930 FIX( 2.613125930 ) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE( x, n ) RIGHT_SHIFT( x, n ) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY( var, const ) ( (DCTELEM) DESCALE( ( var ) * ( const ), CONST_BITS ) ) + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a DCTELEM result. For 8-bit data a 16x16->16 + * multiplication will do. For 12-bit data, the multiplier table is + * declared INT32, so a 32-bit multiply will be used. + */ + +#if BITS_IN_JSAMPLE == 8 +#define DEQUANTIZE( coef, quantval ) ( ( (IFAST_MULT_TYPE) ( coef ) ) * ( quantval ) ) +#else +#define DEQUANTIZE( coef, quantval ) \ + DESCALE( ( coef ) * ( quantval ), IFAST_SCALE_BITS - PASS1_BITS ) +#endif + + +/* Like DESCALE, but applies to a DCTELEM and produces an int. + * We assume that int right shift is unsigned if INT32 right shift is. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS DCTELEM ishift_temp; +#if BITS_IN_JSAMPLE == 8 +#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ +#else +#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ +#endif +#define IRIGHT_SHIFT( x, shft ) \ + ( ( ishift_temp = ( x ) ) < 0 ? \ + ( ishift_temp >> ( shft ) ) | ( ( ~( (DCTELEM) 0 ) ) << ( DCTELEMBITS - ( shft ) ) ) : \ + ( ishift_temp >> ( shft ) ) ) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT( x, shft ) ( ( x ) >> ( shft ) ) +#endif + +#ifdef USE_ACCURATE_ROUNDING +#define IDESCALE( x, n ) ( (int) IRIGHT_SHIFT( ( x ) + ( 1 << ( ( n ) - 1 ) ), n ) ) +#else +#define IDESCALE( x, n ) ( (int) IRIGHT_SHIFT( x, n ) ) +#endif + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_ifast( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z5, z10, z11, z12, z13; + JCOEFPTR inptr; + IFAST_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE * range_limit = IDCT_range_limit( cinfo ); + int ctr; + int workspace[DCTSIZE2];/* buffers data between passes */ + SHIFT_TEMPS /* for DESCALE */ + ISHIFT_TEMPS /* for IDESCALE */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (IFAST_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for ( ctr = DCTSIZE; ctr > 0; ctr-- ) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ( ( inptr[DCTSIZE * 1] | inptr[DCTSIZE * 2] | inptr[DCTSIZE * 3] | + inptr[DCTSIZE * 4] | inptr[DCTSIZE * 5] | inptr[DCTSIZE * 6] | + inptr[DCTSIZE * 7] ) == 0 ) { + /* AC terms all zero */ + int dcval = (int) DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + wsptr[DCTSIZE * 2] = dcval; + wsptr[DCTSIZE * 3] = dcval; + wsptr[DCTSIZE * 4] = dcval; + wsptr[DCTSIZE * 5] = dcval; + wsptr[DCTSIZE * 6] = dcval; + wsptr[DCTSIZE * 7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + tmp1 = DEQUANTIZE( inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2] ); + tmp2 = DEQUANTIZE( inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4] ); + tmp3 = DEQUANTIZE( inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6] ); + + tmp10 = tmp0 + tmp2;/* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3;/* phases 5-3 */ + tmp12 = MULTIPLY( tmp1 - tmp3, FIX_1_414213562 ) - tmp13;/* 2*c4 */ + + tmp0 = tmp10 + tmp13;/* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE( inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1] ); + tmp5 = DEQUANTIZE( inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3] ); + tmp6 = DEQUANTIZE( inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5] ); + tmp7 = DEQUANTIZE( inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7] ); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY( z11 - z13, FIX_1_414213562 );/* 2*c4 */ + + z5 = MULTIPLY( z10 + z12, FIX_1_847759065 );/* 2*c2 */ + tmp10 = MULTIPLY( z12, FIX_1_082392200 ) - z5;/* 2*(c2-c6) */ + tmp12 = MULTIPLY( z10, -FIX_2_613125930 ) + z5;/* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7;/* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE * 0] = (int) ( tmp0 + tmp7 ); + wsptr[DCTSIZE * 7] = (int) ( tmp0 - tmp7 ); + wsptr[DCTSIZE * 1] = (int) ( tmp1 + tmp6 ); + wsptr[DCTSIZE * 6] = (int) ( tmp1 - tmp6 ); + wsptr[DCTSIZE * 2] = (int) ( tmp2 + tmp5 ); + wsptr[DCTSIZE * 5] = (int) ( tmp2 - tmp5 ); + wsptr[DCTSIZE * 4] = (int) ( tmp3 + tmp4 ); + wsptr[DCTSIZE * 3] = (int) ( tmp3 - tmp4 ); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for ( ctr = 0; ctr < DCTSIZE; ctr++ ) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if ( ( wsptr[1] | wsptr[2] | wsptr[3] | wsptr[4] | wsptr[5] | wsptr[6] | + wsptr[7] ) == 0 ) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[IDESCALE( wsptr[0], PASS1_BITS + 3 ) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE;/* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ( (DCTELEM) wsptr[0] + (DCTELEM) wsptr[4] ); + tmp11 = ( (DCTELEM) wsptr[0] - (DCTELEM) wsptr[4] ); + + tmp13 = ( (DCTELEM) wsptr[2] + (DCTELEM) wsptr[6] ); + tmp12 = MULTIPLY( (DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562 ) + - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = (DCTELEM) wsptr[5] + (DCTELEM) wsptr[3]; + z10 = (DCTELEM) wsptr[5] - (DCTELEM) wsptr[3]; + z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7]; + z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7]; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY( z11 - z13, FIX_1_414213562 );/* 2*c4 */ + + z5 = MULTIPLY( z10 + z12, FIX_1_847759065 );/* 2*c2 */ + tmp10 = MULTIPLY( z12, FIX_1_082392200 ) - z5;/* 2*(c2-c6) */ + tmp12 = MULTIPLY( z10, -FIX_2_613125930 ) + z5;/* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7;/* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[IDESCALE( tmp0 + tmp7, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[7] = range_limit[IDESCALE( tmp0 - tmp7, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[1] = range_limit[IDESCALE( tmp1 + tmp6, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[6] = range_limit[IDESCALE( tmp1 - tmp6, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[2] = range_limit[IDESCALE( tmp2 + tmp5, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[5] = range_limit[IDESCALE( tmp2 - tmp5, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[4] = range_limit[IDESCALE( tmp3 + tmp4, PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[3] = range_limit[IDESCALE( tmp3 - tmp4, PASS1_BITS + 3 ) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jidctint.cpp b/neo/renderer/jpeg-6/jidctint.cpp new file mode 100644 index 00000000..e1e51144 --- /dev/null +++ b/neo/renderer/jpeg-6/jidctint.cpp @@ -0,0 +1,387 @@ +/* + * jidctint.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D IDCT step produces outputs which are a factor of sqrt(N) + * larger than the true IDCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D IDCT, + * because the y0 and y4 inputs need not be divided by sqrt(N). + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (To scale up 12-bit sample data further, an + * intermediate INT32 array would be needed.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ( (INT32) 2446 ) /* FIX(0.298631336) */ +#define FIX_0_390180644 ( (INT32) 3196 ) /* FIX(0.390180644) */ +#define FIX_0_541196100 ( (INT32) 4433 ) /* FIX(0.541196100) */ +#define FIX_0_765366865 ( (INT32) 6270 ) /* FIX(0.765366865) */ +#define FIX_0_899976223 ( (INT32) 7373 ) /* FIX(0.899976223) */ +#define FIX_1_175875602 ( (INT32) 9633 ) /* FIX(1.175875602) */ +#define FIX_1_501321110 ( (INT32) 12299 ) /* FIX(1.501321110) */ +#define FIX_1_847759065 ( (INT32) 15137 ) /* FIX(1.847759065) */ +#define FIX_1_961570560 ( (INT32) 16069 ) /* FIX(1.961570560) */ +#define FIX_2_053119869 ( (INT32) 16819 ) /* FIX(2.053119869) */ +#define FIX_2_562915447 ( (INT32) 20995 ) /* FIX(2.562915447) */ +#define FIX_3_072711026 ( (INT32) 25172 ) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX( 0.298631336 ) +#define FIX_0_390180644 FIX( 0.390180644 ) +#define FIX_0_541196100 FIX( 0.541196100 ) +#define FIX_0_765366865 FIX( 0.765366865 ) +#define FIX_0_899976223 FIX( 0.899976223 ) +#define FIX_1_175875602 FIX( 1.175875602 ) +#define FIX_1_501321110 FIX( 1.501321110 ) +#define FIX_1_847759065 FIX( 1.847759065 ) +#define FIX_1_961570560 FIX( 1.961570560 ) +#define FIX_2_053119869 FIX( 2.053119869 ) +#define FIX_2_562915447 FIX( 2.562915447 ) +#define FIX_3_072711026 FIX( 3.072711026 ) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY( var, const ) MULTIPLY16C16( var, const ) +#else +#define MULTIPLY( var, const ) ( ( var ) * ( const ) ) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE( coef, quantval ) ( ( (ISLOW_MULT_TYPE) ( coef ) ) * ( quantval ) ) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_islow( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE * range_limit = IDCT_range_limit( cinfo ); + int ctr; + int workspace[DCTSIZE2];/* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for ( ctr = DCTSIZE; ctr > 0; ctr-- ) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ( ( inptr[DCTSIZE * 1] | inptr[DCTSIZE * 2] | inptr[DCTSIZE * 3] | + inptr[DCTSIZE * 4] | inptr[DCTSIZE * 5] | inptr[DCTSIZE * 6] | + inptr[DCTSIZE * 7] ) == 0 ) { + /* AC terms all zero */ + int dcval = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ) << PASS1_BITS; + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + wsptr[DCTSIZE * 2] = dcval; + wsptr[DCTSIZE * 3] = dcval; + wsptr[DCTSIZE * 4] = dcval; + wsptr[DCTSIZE * 5] = dcval; + wsptr[DCTSIZE * 6] = dcval; + wsptr[DCTSIZE * 7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE( inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2] ); + z3 = DEQUANTIZE( inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6] ); + + z1 = MULTIPLY( z2 + z3, FIX_0_541196100 ); + tmp2 = z1 + MULTIPLY( z3, -FIX_1_847759065 ); + tmp3 = z1 + MULTIPLY( z2, FIX_0_765366865 ); + + z2 = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + z3 = DEQUANTIZE( inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4] ); + + tmp0 = ( z2 + z3 ) << CONST_BITS; + tmp1 = ( z2 - z3 ) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE( inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7] ); + tmp1 = DEQUANTIZE( inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5] ); + tmp2 = DEQUANTIZE( inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3] ); + tmp3 = DEQUANTIZE( inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1] ); + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY( z3 + z4, FIX_1_175875602 );/* sqrt(2) * c3 */ + + tmp0 = MULTIPLY( tmp0, FIX_0_298631336 );/* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY( tmp1, FIX_2_053119869 );/* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY( tmp2, FIX_3_072711026 );/* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY( tmp3, FIX_1_501321110 );/* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY( z1, -FIX_0_899976223 );/* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY( z2, -FIX_2_562915447 );/* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY( z3, -FIX_1_961570560 );/* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY( z4, -FIX_0_390180644 );/* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[DCTSIZE * 0] = (int) DESCALE( tmp10 + tmp3, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 7] = (int) DESCALE( tmp10 - tmp3, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 1] = (int) DESCALE( tmp11 + tmp2, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 6] = (int) DESCALE( tmp11 - tmp2, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 2] = (int) DESCALE( tmp12 + tmp1, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 5] = (int) DESCALE( tmp12 - tmp1, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 3] = (int) DESCALE( tmp13 + tmp0, CONST_BITS - PASS1_BITS ); + wsptr[DCTSIZE * 4] = (int) DESCALE( tmp13 - tmp0, CONST_BITS - PASS1_BITS ); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for ( ctr = 0; ctr < DCTSIZE; ctr++ ) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if ( ( wsptr[1] | wsptr[2] | wsptr[3] | wsptr[4] | wsptr[5] | wsptr[6] | + wsptr[7] ) == 0 ) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE( (INT32) wsptr[0], PASS1_BITS + 3 ) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE;/* advance pointer to next row */ + continue; + } +#endif + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY( z2 + z3, FIX_0_541196100 ); + tmp2 = z1 + MULTIPLY( z3, -FIX_1_847759065 ); + tmp3 = z1 + MULTIPLY( z2, FIX_0_765366865 ); + + tmp0 = ( (INT32) wsptr[0] + (INT32) wsptr[4] ) << CONST_BITS; + tmp1 = ( (INT32) wsptr[0] - (INT32) wsptr[4] ) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY( z3 + z4, FIX_1_175875602 );/* sqrt(2) * c3 */ + + tmp0 = MULTIPLY( tmp0, FIX_0_298631336 );/* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY( tmp1, FIX_2_053119869 );/* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY( tmp2, FIX_3_072711026 );/* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY( tmp3, FIX_1_501321110 );/* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY( z1, -FIX_0_899976223 );/* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY( z2, -FIX_2_562915447 );/* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY( z3, -FIX_1_961570560 );/* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY( z4, -FIX_0_390180644 );/* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) DESCALE( tmp10 + tmp3, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE( tmp10 - tmp3, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE( tmp11 + tmp2, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE( tmp11 - tmp2, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE( tmp12 + tmp1, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE( tmp12 - tmp1, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE( tmp13 + tmp0, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE( tmp13 - tmp0, + CONST_BITS + PASS1_BITS + 3 ) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jidctred.cpp b/neo/renderer/jpeg-6/jidctred.cpp new file mode 100644 index 00000000..b08254e1 --- /dev/null +++ b/neo/renderer/jpeg-6/jidctred.cpp @@ -0,0 +1,396 @@ +/* + * jidctred.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains inverse-DCT routines that produce reduced-size output: + * either 4x4, 2x2, or 1x1 pixels from an 8x8 DCT block. + * + * The implementation is based on the Loeffler, Ligtenberg and Moschytz (LL&M) + * algorithm used in jidctint.c. We simply replace each 8-to-8 1-D IDCT step + * with an 8-to-4 step that produces the four averages of two adjacent outputs + * (or an 8-to-2 step producing two averages of four outputs, for 2x2 output). + * These steps were derived by computing the corresponding values at the end + * of the normal LL&M code, then simplifying as much as possible. + * + * 1x1 is trivial: just take the DC coefficient divided by 8. + * + * See jidctint.c for additional comments. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef IDCT_SCALING_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 +Sorry, this code only copes with 8 x8 DCTs. /* deliberate syntax err */ + #endif + + +/* Scaling is the same as in jidctint.c. */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_211164243 ( (INT32) 1730 ) /* FIX(0.211164243) */ +#define FIX_0_509795579 ( (INT32) 4176 ) /* FIX(0.509795579) */ +#define FIX_0_601344887 ( (INT32) 4926 ) /* FIX(0.601344887) */ +#define FIX_0_720959822 ( (INT32) 5906 ) /* FIX(0.720959822) */ +#define FIX_0_765366865 ( (INT32) 6270 ) /* FIX(0.765366865) */ +#define FIX_0_850430095 ( (INT32) 6967 ) /* FIX(0.850430095) */ +#define FIX_0_899976223 ( (INT32) 7373 ) /* FIX(0.899976223) */ +#define FIX_1_061594337 ( (INT32) 8697 ) /* FIX(1.061594337) */ +#define FIX_1_272758580 ( (INT32) 10426 ) /* FIX(1.272758580) */ +#define FIX_1_451774981 ( (INT32) 11893 ) /* FIX(1.451774981) */ +#define FIX_1_847759065 ( (INT32) 15137 ) /* FIX(1.847759065) */ +#define FIX_2_172734803 ( (INT32) 17799 ) /* FIX(2.172734803) */ +#define FIX_2_562915447 ( (INT32) 20995 ) /* FIX(2.562915447) */ +#define FIX_3_624509785 ( (INT32) 29692 ) /* FIX(3.624509785) */ +#else +#define FIX_0_211164243 FIX( 0.211164243 ) +#define FIX_0_509795579 FIX( 0.509795579 ) +#define FIX_0_601344887 FIX( 0.601344887 ) +#define FIX_0_720959822 FIX( 0.720959822 ) +#define FIX_0_765366865 FIX( 0.765366865 ) +#define FIX_0_850430095 FIX( 0.850430095 ) +#define FIX_0_899976223 FIX( 0.899976223 ) +#define FIX_1_061594337 FIX( 1.061594337 ) +#define FIX_1_272758580 FIX( 1.272758580 ) +#define FIX_1_451774981 FIX( 1.451774981 ) +#define FIX_1_847759065 FIX( 1.847759065 ) +#define FIX_2_172734803 FIX( 2.172734803 ) +#define FIX_2_562915447 FIX( 2.562915447 ) +#define FIX_3_624509785 FIX( 3.624509785 ) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY( var, const ) MULTIPLY16C16( var, const ) +#else +#define MULTIPLY( var, const ) ( ( var ) * ( const ) ) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE( coef, quantval ) ( ( (ISLOW_MULT_TYPE) ( coef ) ) * ( quantval ) ) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 4x4 output block. + */ + +GLOBAL void +jpeg_idct_4x4( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE * range_limit = IDCT_range_limit( cinfo ); + int ctr; + int workspace[DCTSIZE * 4];/* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for ( ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr-- ) { + /* Don't bother to process column 4, because second pass won't use it */ + if ( ctr == DCTSIZE - 4 ) { + continue; + } + if ( ( inptr[DCTSIZE * 1] | inptr[DCTSIZE * 2] | inptr[DCTSIZE * 3] | + inptr[DCTSIZE * 5] | inptr[DCTSIZE * 6] | inptr[DCTSIZE * 7] ) == 0 ) { + /* AC terms all zero; we need not examine term 4 for 4x4 output */ + int dcval = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ) << PASS1_BITS; + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + wsptr[DCTSIZE * 2] = dcval; + wsptr[DCTSIZE * 3] = dcval; + + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + tmp0 <<= ( CONST_BITS + 1 ); + + z2 = DEQUANTIZE( inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2] ); + z3 = DEQUANTIZE( inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6] ); + + tmp2 = MULTIPLY( z2, FIX_1_847759065 ) + MULTIPLY( z3, -FIX_0_765366865 ); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = DEQUANTIZE( inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7] ); + z2 = DEQUANTIZE( inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5] ); + z3 = DEQUANTIZE( inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3] ); + z4 = DEQUANTIZE( inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1] ); + + tmp0 = MULTIPLY( z1, -FIX_0_211164243 )/* sqrt(2) * (c3-c1) */ + + MULTIPLY( z2, FIX_1_451774981 )/* sqrt(2) * (c3+c7) */ + + MULTIPLY( z3, -FIX_2_172734803 )/* sqrt(2) * (-c1-c5) */ + + MULTIPLY( z4, FIX_1_061594337 );/* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY( z1, -FIX_0_509795579 )/* sqrt(2) * (c7-c5) */ + + MULTIPLY( z2, -FIX_0_601344887 )/* sqrt(2) * (c5-c1) */ + + MULTIPLY( z3, FIX_0_899976223 )/* sqrt(2) * (c3-c7) */ + + MULTIPLY( z4, FIX_2_562915447 );/* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + wsptr[DCTSIZE * 0] = (int) DESCALE( tmp10 + tmp2, CONST_BITS - PASS1_BITS + 1 ); + wsptr[DCTSIZE * 3] = (int) DESCALE( tmp10 - tmp2, CONST_BITS - PASS1_BITS + 1 ); + wsptr[DCTSIZE * 1] = (int) DESCALE( tmp12 + tmp0, CONST_BITS - PASS1_BITS + 1 ); + wsptr[DCTSIZE * 2] = (int) DESCALE( tmp12 - tmp0, CONST_BITS - PASS1_BITS + 1 ); + } + + /* Pass 2: process 4 rows from work array, store into output array. */ + + wsptr = workspace; + for ( ctr = 0; ctr < 4; ctr++ ) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if ( ( wsptr[1] | wsptr[2] | wsptr[3] | wsptr[5] | wsptr[6] | + wsptr[7] ) == 0 ) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE( (INT32) wsptr[0], PASS1_BITS + 3 ) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + + wsptr += DCTSIZE;/* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp0 = ( (INT32) wsptr[0] ) << ( CONST_BITS + 1 ); + + tmp2 = MULTIPLY( (INT32) wsptr[2], FIX_1_847759065 ) + + MULTIPLY( (INT32) wsptr[6], -FIX_0_765366865 ); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = (INT32) wsptr[7]; + z2 = (INT32) wsptr[5]; + z3 = (INT32) wsptr[3]; + z4 = (INT32) wsptr[1]; + + tmp0 = MULTIPLY( z1, -FIX_0_211164243 )/* sqrt(2) * (c3-c1) */ + + MULTIPLY( z2, FIX_1_451774981 )/* sqrt(2) * (c3+c7) */ + + MULTIPLY( z3, -FIX_2_172734803 )/* sqrt(2) * (-c1-c5) */ + + MULTIPLY( z4, FIX_1_061594337 );/* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY( z1, -FIX_0_509795579 )/* sqrt(2) * (c7-c5) */ + + MULTIPLY( z2, -FIX_0_601344887 )/* sqrt(2) * (c5-c1) */ + + MULTIPLY( z3, FIX_0_899976223 )/* sqrt(2) * (c3-c7) */ + + MULTIPLY( z4, FIX_2_562915447 );/* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE( tmp10 + tmp2, + CONST_BITS + PASS1_BITS + 3 + 1 ) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE( tmp10 - tmp2, + CONST_BITS + PASS1_BITS + 3 + 1 ) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE( tmp12 + tmp0, + CONST_BITS + PASS1_BITS + 3 + 1 ) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE( tmp12 - tmp0, + CONST_BITS + PASS1_BITS + 3 + 1 ) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 2x2 output block. + */ + +GLOBAL void +jpeg_idct_2x2( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + INT32 tmp0, tmp10, z1; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE * range_limit = IDCT_range_limit( cinfo ); + int ctr; + int workspace[DCTSIZE * 2];/* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for ( ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr-- ) { + /* Don't bother to process columns 2,4,6 */ + if ( ( ctr == DCTSIZE - 2 ) || ( ctr == DCTSIZE - 4 ) || ( ctr == DCTSIZE - 6 ) ) { + continue; + } + if ( ( inptr[DCTSIZE * 1] | inptr[DCTSIZE * 3] | + inptr[DCTSIZE * 5] | inptr[DCTSIZE * 7] ) == 0 ) { + /* AC terms all zero; we need not examine terms 2,4,6 for 2x2 output */ + int dcval = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ) << PASS1_BITS; + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + + continue; + } + + /* Even part */ + + z1 = DEQUANTIZE( inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0] ); + tmp10 = z1 << ( CONST_BITS + 2 ); + + /* Odd part */ + + z1 = DEQUANTIZE( inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7] ); + tmp0 = MULTIPLY( z1, -FIX_0_720959822 );/* sqrt(2) * (c7-c5+c3-c1) */ + z1 = DEQUANTIZE( inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5] ); + tmp0 += MULTIPLY( z1, FIX_0_850430095 );/* sqrt(2) * (-c1+c3+c5+c7) */ + z1 = DEQUANTIZE( inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3] ); + tmp0 += MULTIPLY( z1, -FIX_1_272758580 );/* sqrt(2) * (-c1+c3-c5-c7) */ + z1 = DEQUANTIZE( inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1] ); + tmp0 += MULTIPLY( z1, FIX_3_624509785 );/* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + wsptr[DCTSIZE * 0] = (int) DESCALE( tmp10 + tmp0, CONST_BITS - PASS1_BITS + 2 ); + wsptr[DCTSIZE * 1] = (int) DESCALE( tmp10 - tmp0, CONST_BITS - PASS1_BITS + 2 ); + } + + /* Pass 2: process 2 rows from work array, store into output array. */ + + wsptr = workspace; + for ( ctr = 0; ctr < 2; ctr++ ) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if ( ( wsptr[1] | wsptr[3] | wsptr[5] | wsptr[7] ) == 0 ) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE( (INT32) wsptr[0], PASS1_BITS + 3 ) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + + wsptr += DCTSIZE;/* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ( (INT32) wsptr[0] ) << ( CONST_BITS + 2 ); + + /* Odd part */ + + tmp0 = MULTIPLY( (INT32) wsptr[7], -FIX_0_720959822 )/* sqrt(2) * (c7-c5+c3-c1) */ + + MULTIPLY( (INT32) wsptr[5], FIX_0_850430095 )/* sqrt(2) * (-c1+c3+c5+c7) */ + + MULTIPLY( (INT32) wsptr[3], -FIX_1_272758580 )/* sqrt(2) * (-c1+c3-c5-c7) */ + + MULTIPLY( (INT32) wsptr[1], FIX_3_624509785 );/* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE( tmp10 + tmp0, + CONST_BITS + PASS1_BITS + 3 + 2 ) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE( tmp10 - tmp0, + CONST_BITS + PASS1_BITS + 3 + 2 ) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 1x1 output block. + */ + +GLOBAL void +jpeg_idct_1x1( j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col ) { + int dcval; + ISLOW_MULT_TYPE * quantptr; + JSAMPLE * range_limit = IDCT_range_limit( cinfo ); + SHIFT_TEMPS + + /* We hardly need an inverse DCT routine for this: just take the + * average pixel value, which is one-eighth of the DC coefficient. + */ + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + dcval = DEQUANTIZE( coef_block[0], quantptr[0] ); + dcval = (int) DESCALE( (INT32) dcval, 3 ); + + output_buf[0][output_col] = range_limit[dcval & RANGE_MASK]; +} + +#endif /* IDCT_SCALING_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jinclude.h b/neo/renderer/jpeg-6/jinclude.h new file mode 100644 index 00000000..9f93f465 --- /dev/null +++ b/neo/renderer/jpeg-6/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "../jpeg-6/jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/neo/renderer/jpeg-6/jload.cpp b/neo/renderer/jpeg-6/jload.cpp new file mode 100644 index 00000000..12aea4f4 --- /dev/null +++ b/neo/renderer/jpeg-6/jload.cpp @@ -0,0 +1,145 @@ + +#include "../Shared/Shared.h" +#include "..\Common\Common.h" + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#include "jpeglib.h" + + +int LoadJPG( const char * filename, unsigned char ** pic, int * width, int * height ) { + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + fileHandle_t infile; /* source file */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char * out; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + FS_FOpenFileRead( filename, &infile, qfalse ); + if ( infile == 0 ) { + return 0; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error( &jerr ); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress( &cinfo ); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src( &cinfo, infile ); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header( &cinfo, TRUE ); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress( &cinfo ); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + out = Z_Malloc( cinfo.output_width * cinfo.output_height * cinfo.output_components ); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while ( cinfo.output_scanline < cinfo.output_height ) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + buffer = (JSAMPARRAY)out + ( row_stride * cinfo.output_scanline ); + (void) jpeg_read_scanlines( &cinfo, buffer, 1 ); + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress( &cinfo ); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress( &cinfo ); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + FS_FCloseFile( infile ); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ + return 1; +} + diff --git a/neo/renderer/jpeg-6/jmemansi.cpp b/neo/renderer/jpeg-6/jmemansi.cpp new file mode 100644 index 00000000..c646781a --- /dev/null +++ b/neo/renderer/jpeg-6/jmemansi.cpp @@ -0,0 +1,161 @@ +/* + * jmemansi.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a simple generic implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that you have the ANSI-standard library routine tmpfile(). + * Also, the problem of determining the amount of memory available + * is shoved onto the user. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void * malloc JPP( (size_t size) ); +extern void free JPP( (void * ptr) ); +#endif + +#ifndef SEEK_SET /* pre-ANSI systems may not define this; */ +#define SEEK_SET 0 /* if not, assume 0 is correct */ +#endif + + +/* + * Memory allocation and freeing are controlled by the regular library + * routines malloc() and free(). + */ + +GLOBAL void * +jpeg_get_small( j_common_ptr cinfo, size_t sizeofobject ) { + return (void *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_small( j_common_ptr cinfo, void * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large( j_common_ptr cinfo, size_t sizeofobject ) { + return (void FAR *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_large( j_common_ptr cinfo, void FAR * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * This routine computes the total memory space available for allocation. + * It's impossible to do this in a portable way; our current solution is + * to make the user tell us (with a default value set at compile time). + * If you can actually get the available space, it's a good idea to subtract + * a slop factor of 5% or so. + */ + +#ifndef DEFAULT_MAX_MEM /* so can override from makefile */ +#define DEFAULT_MAX_MEM 1000000L /* default: one megabyte */ +#endif + +GLOBAL long +jpeg_mem_available( j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated ) { + return cinfo->mem->max_memory_to_use - already_allocated; +} + + +/* + * Backing store (temporary file) management. + * Backing store objects are only used when the value returned by + * jpeg_mem_available is less than the total space needed. You can dispense + * with these routines if you have plenty of virtual memory; see jmemnobs.c. + */ + + +METHODDEF void +read_backing_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + if ( fseek( info->temp_file, file_offset, SEEK_SET ) ) { + ERREXIT( cinfo, JERR_TFILE_SEEK ); + } + if ( JFREAD( info->temp_file, buffer_address, byte_count ) + != (size_t) byte_count ) { + ERREXIT( cinfo, JERR_TFILE_READ ); + } +} + + +METHODDEF void +write_backing_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + if ( fseek( info->temp_file, file_offset, SEEK_SET ) ) { + ERREXIT( cinfo, JERR_TFILE_SEEK ); + } + if ( JFWRITE( info->temp_file, buffer_address, byte_count ) + != (size_t) byte_count ) { + ERREXIT( cinfo, JERR_TFILE_WRITE ); + } +} + + +METHODDEF void +close_backing_store( j_common_ptr cinfo, backing_store_ptr info ) { + fclose( info->temp_file ); + /* Since this implementation uses tmpfile() to create the file, + * no explicit file deletion is needed. + */ +} + + +/* + * Initial opening of a backing-store object. + * + * This version uses tmpfile(), which constructs a suitable file name + * behind the scenes. We don't have to use info->temp_name[] at all; + * indeed, we can't even find out the actual name of the temp file. + */ + +GLOBAL void +jpeg_open_backing_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + if ( ( info->temp_file = tmpfile() ) == NULL ) { + ERREXITS( cinfo, JERR_TFILE_CREATE, "" ); + } + info->read_backing_store = read_backing_store; + info->write_backing_store = write_backing_store; + info->close_backing_store = close_backing_store; +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. + */ + +GLOBAL long +jpeg_mem_init( j_common_ptr cinfo ) { + return DEFAULT_MAX_MEM; /* default for max_memory_to_use */ +} + +GLOBAL void +jpeg_mem_term( j_common_ptr cinfo ) { + /* no work */ +} diff --git a/neo/renderer/jpeg-6/jmemdos.cpp b/neo/renderer/jpeg-6/jmemdos.cpp new file mode 100644 index 00000000..2b6e938c --- /dev/null +++ b/neo/renderer/jpeg-6/jmemdos.cpp @@ -0,0 +1,639 @@ +/* + * jmemdos.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides an MS-DOS-compatible implementation of the system- + * dependent portion of the JPEG memory manager. Temporary data can be + * stored in extended or expanded memory as well as in regular DOS files. + * + * If you use this file, you must be sure that NEED_FAR_POINTERS is defined + * if you compile in a small-data memory model; it should NOT be defined if + * you use a large-data memory model. This file is not recommended if you + * are using a flat-memory-space 386 environment such as DJGCC or Watcom C. + * Also, this code will NOT work if struct fields are aligned on greater than + * 2-byte boundaries. + * + * Based on code contributed by Ge' Weijers. + */ + +/* + * If you have both extended and expanded memory, you may want to change the + * order in which they are tried in jopen_backing_store. On a 286 machine + * expanded memory is usually faster, since extended memory access involves + * an expensive protected-mode-and-back switch. On 386 and better, extended + * memory is usually faster. As distributed, the code tries extended memory + * first (what? not everyone has a 386? :-). + * + * You can disable use of extended/expanded memory entirely by altering these + * definitions or overriding them from the Makefile (eg, -DEMS_SUPPORTED=0). + */ + +#ifndef XMS_SUPPORTED +#define XMS_SUPPORTED 1 +#endif +#ifndef EMS_SUPPORTED +#define EMS_SUPPORTED 1 +#endif + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef HAVE_STDLIB_H /* should declare these */ +extern void * malloc JPP( (size_t size) ); +extern void free JPP( (void * ptr) ); +extern char * getenv JPP( (const char * name) ); +#endif + +#ifdef NEED_FAR_POINTERS + +#ifdef __TURBOC__ +/* These definitions work for Borland C (Turbo C) */ +#include /* need farmalloc(), farfree() */ +#define far_malloc( x ) farmalloc( x ) +#define far_free( x ) farfree( x ) +#else +/* These definitions work for Microsoft C and compatible compilers */ +#include /* need _fmalloc(), _ffree() */ +#define far_malloc( x ) _fmalloc( x ) +#define far_free( x ) _ffree( x ) +#endif + +#else /* not NEED_FAR_POINTERS */ + +#define far_malloc( x ) malloc( x ) +#define far_free( x ) free( x ) + +#endif /* NEED_FAR_POINTERS */ + +#ifdef DONT_USE_B_MODE /* define mode parameters for fopen() */ +#define READ_BINARY "r" +#else +#define READ_BINARY "rb" +#endif + +#if MAX_ALLOC_CHUNK >= 65535L /* make sure jconfig.h got this right */ +MAX_ALLOC_CHUNK should be less than 64 K. /* deliberate syntax error */ + #endif + + +/* + * Declarations for assembly-language support routines (see jmemdosa.asm). + * + * The functions are declared "far" as are all pointer arguments; + * this ensures the assembly source code will work regardless of the + * compiler memory model. We assume "short" is 16 bits, "long" is 32. + */ + +typedef void far * XMSDRIVER; /* actually a pointer to code */ +typedef struct { /* registers for calling XMS driver */ + unsigned short ax, dx, bx; + void far * ds_si; +} XMScontext; +typedef struct { /* registers for calling EMS driver */ + unsigned short ax, dx, bx; + void far * ds_si; +} EMScontext; + +EXTERN short far jdos_open JPP( ( short far * handle, char far * filename ) ); +EXTERN short far jdos_close JPP( (short handle) ); +EXTERN short far jdos_seek JPP( ( short handle, long offset ) ); +EXTERN short far jdos_read JPP( ( short handle, void far * buffer, + unsigned short count ) ); +EXTERN short far jdos_write JPP( ( short handle, void far * buffer, + unsigned short count ) ); +EXTERN void far jxms_getdriver JPP( (XMSDRIVER far *) ); +EXTERN void far jxms_calldriver JPP( ( XMSDRIVER, XMScontext far * ) ); +EXTERN short far jems_available JPP( (void) ); +EXTERN void far jems_calldriver JPP( (EMScontext far *) ); + + +/* + * Selection of a file name for a temporary file. + * This is highly system-dependent, and you may want to customize it. + */ + +static int next_file_num; /* to distinguish among several temp files */ + +LOCAL void +select_file_name( char * fname ) { + const char * env; + char * ptr; + FILE * tfile; + + /* Keep generating file names till we find one that's not in use */ + for (;; ) { + /* Get temp directory name from environment TMP or TEMP variable; + * if none, use "." + */ + if ( ( env = (const char *) getenv( "TMP" ) ) == NULL ) { + if ( ( env = (const char *) getenv( "TEMP" ) ) == NULL ) { + env = "."; + } + } + if ( *env == '\0' ) {/* null string means "." */ + env = "."; + } + ptr = fname; /* copy name to fname */ + while ( *env != '\0' ) { + *ptr++ = *env++; + } + if ( ( ptr[-1] != '\\' ) && ( ptr[-1] != '/' ) ) { + *ptr++ = '\\'; + } /* append backslash if not in env variable */ + /* Append a suitable file name */ + next_file_num++; /* advance counter */ + sprintf( ptr, "JPG%03d.TMP", next_file_num ); + /* Probe to see if file name is already in use */ + if ( ( tfile = fopen( fname, READ_BINARY ) ) == NULL ) { + break; + } + fclose( tfile );/* oops, it's there; close tfile & try again */ + } +} + + +/* + * Near-memory allocation and freeing are controlled by the regular library + * routines malloc() and free(). + */ + +GLOBAL void * +jpeg_get_small( j_common_ptr cinfo, size_t sizeofobject ) { + return (void *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_small( j_common_ptr cinfo, void * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * "Large" objects are allocated in far memory, if possible + */ + +GLOBAL void FAR * +jpeg_get_large( j_common_ptr cinfo, size_t sizeofobject ) { + return (void FAR *) far_malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_large( j_common_ptr cinfo, void FAR * object, size_t sizeofobject ) { + far_free( object ); +} + + +/* + * This routine computes the total memory space available for allocation. + * It's impossible to do this in a portable way; our current solution is + * to make the user tell us (with a default value set at compile time). + * If you can actually get the available space, it's a good idea to subtract + * a slop factor of 5% or so. + */ + +#ifndef DEFAULT_MAX_MEM /* so can override from makefile */ +#define DEFAULT_MAX_MEM 300000L /* for total usage about 450K */ +#endif + +GLOBAL long +jpeg_mem_available( j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated ) { + return cinfo->mem->max_memory_to_use - already_allocated; +} + + +/* + * Backing store (temporary file) management. + * Backing store objects are only used when the value returned by + * jpeg_mem_available is less than the total space needed. You can dispense + * with these routines if you have plenty of virtual memory; see jmemnobs.c. + */ + +/* + * For MS-DOS we support three types of backing storage: + * 1. Conventional DOS files. We access these by direct DOS calls rather + * than via the stdio package. This provides a bit better performance, + * but the real reason is that the buffers to be read or written are FAR. + * The stdio library for small-data memory models can't cope with that. + * 2. Extended memory, accessed per the XMS V2.0 specification. + * 3. Expanded memory, accessed per the LIM/EMS 4.0 specification. + * You'll need copies of those specs to make sense of the related code. + * The specs are available by Internet FTP from the SIMTEL archives + * (oak.oakland.edu and its various mirror sites). See files + * pub/msdos/microsoft/xms20.arc and pub/msdos/info/limems41.zip. + */ + + +/* + * Access methods for a DOS file. + */ + + +METHODDEF void +read_file_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + if ( jdos_seek( info->handle.file_handle, file_offset ) ) { + ERREXIT( cinfo, JERR_TFILE_SEEK ); + } + /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */ + if ( byte_count > 65535L ) {/* safety check */ + ERREXIT( cinfo, JERR_BAD_ALLOC_CHUNK ); + } + if ( jdos_read( info->handle.file_handle, buffer_address, + (unsigned short) byte_count ) ) { + ERREXIT( cinfo, JERR_TFILE_READ ); + } +} + + +METHODDEF void +write_file_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + if ( jdos_seek( info->handle.file_handle, file_offset ) ) { + ERREXIT( cinfo, JERR_TFILE_SEEK ); + } + /* Since MAX_ALLOC_CHUNK is less than 64K, byte_count will be too. */ + if ( byte_count > 65535L ) {/* safety check */ + ERREXIT( cinfo, JERR_BAD_ALLOC_CHUNK ); + } + if ( jdos_write( info->handle.file_handle, buffer_address, + (unsigned short) byte_count ) ) { + ERREXIT( cinfo, JERR_TFILE_WRITE ); + } +} + + +METHODDEF void +close_file_store( j_common_ptr cinfo, backing_store_ptr info ) { + jdos_close( info->handle.file_handle );/* close the file */ + remove( info->temp_name );/* delete the file */ +/* If your system doesn't have remove(), try unlink() instead. + * remove() is the ANSI-standard name for this function, but + * unlink() was more common in pre-ANSI systems. + */ + TRACEMSS( cinfo, 1, JTRC_TFILE_CLOSE, info->temp_name ); +} + + +LOCAL boolean +open_file_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + short handle; + + select_file_name( info->temp_name ); + if ( jdos_open( (short far *) &handle, (char far *) info->temp_name ) ) { + /* might as well exit since jpeg_open_backing_store will fail anyway */ + ERREXITS( cinfo, JERR_TFILE_CREATE, info->temp_name ); + return FALSE; + } + info->handle.file_handle = handle; + info->read_backing_store = read_file_store; + info->write_backing_store = write_file_store; + info->close_backing_store = close_file_store; + TRACEMSS( cinfo, 1, JTRC_TFILE_OPEN, info->temp_name ); + return TRUE; /* succeeded */ +} + + +/* + * Access methods for extended memory. + */ + +#if XMS_SUPPORTED + +static XMSDRIVER xms_driver; /* saved address of XMS driver */ + +typedef union { /* either long offset or real-mode pointer */ + long offset; + void far * ptr; +} XMSPTR; + +typedef struct { /* XMS move specification structure */ + long length; + XMSH src_handle; + XMSPTR src; + XMSH dst_handle; + XMSPTR dst; +} XMSspec; + +#define ODD( X ) ( ( ( X ) & 1L ) != 0 ) + + +METHODDEF void +read_xms_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + XMScontext ctx; + XMSspec spec; + char endbuffer[2]; + + /* The XMS driver can't cope with an odd length, so handle the last byte + * specially if byte_count is odd. We don't expect this to be common. + */ + + spec.length = byte_count & ( ~1L ); + spec.src_handle = info->handle.xms_handle; + spec.src.offset = file_offset; + spec.dst_handle = 0; + spec.dst.ptr = buffer_address; + + ctx.ds_si = (void far *) &spec; + ctx.ax = 0x0b00; /* EMB move */ + jxms_calldriver( xms_driver, (XMScontext far *) &ctx ); + if ( ctx.ax != 1 ) { + ERREXIT( cinfo, JERR_XMS_READ ); + } + + if ( ODD( byte_count ) ) { + read_xms_store( cinfo, info, (void FAR *) endbuffer, + file_offset + byte_count - 1L, 2L ); + ( (char FAR *) buffer_address )[byte_count - 1L] = endbuffer[0]; + } +} + + +METHODDEF void +write_xms_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + XMScontext ctx; + XMSspec spec; + char endbuffer[2]; + + /* The XMS driver can't cope with an odd length, so handle the last byte + * specially if byte_count is odd. We don't expect this to be common. + */ + + spec.length = byte_count & ( ~1L ); + spec.src_handle = 0; + spec.src.ptr = buffer_address; + spec.dst_handle = info->handle.xms_handle; + spec.dst.offset = file_offset; + + ctx.ds_si = (void far *) &spec; + ctx.ax = 0x0b00; /* EMB move */ + jxms_calldriver( xms_driver, (XMScontext far *) &ctx ); + if ( ctx.ax != 1 ) { + ERREXIT( cinfo, JERR_XMS_WRITE ); + } + + if ( ODD( byte_count ) ) { + read_xms_store( cinfo, info, (void FAR *) endbuffer, + file_offset + byte_count - 1L, 2L ); + endbuffer[0] = ( (char FAR *) buffer_address )[byte_count - 1L]; + write_xms_store( cinfo, info, (void FAR *) endbuffer, + file_offset + byte_count - 1L, 2L ); + } +} + + +METHODDEF void +close_xms_store( j_common_ptr cinfo, backing_store_ptr info ) { + XMScontext ctx; + + ctx.dx = info->handle.xms_handle; + ctx.ax = 0x0a00; + jxms_calldriver( xms_driver, (XMScontext far *) &ctx ); + TRACEMS1( cinfo, 1, JTRC_XMS_CLOSE, info->handle.xms_handle ); + /* we ignore any error return from the driver */ +} + + +LOCAL boolean +open_xms_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + XMScontext ctx; + + /* Get address of XMS driver */ + jxms_getdriver( (XMSDRIVER far *) &xms_driver ); + if ( xms_driver == NULL ) { + return FALSE; + } /* no driver to be had */ + + /* Get version number, must be >= 2.00 */ + ctx.ax = 0x0000; + jxms_calldriver( xms_driver, (XMScontext far *) &ctx ); + if ( ctx.ax < (unsigned short) 0x0200 ) { + return FALSE; + } + + /* Try to get space (expressed in kilobytes) */ + ctx.dx = (unsigned short) ( ( total_bytes_needed + 1023L ) >> 10 ); + ctx.ax = 0x0900; + jxms_calldriver( xms_driver, (XMScontext far *) &ctx ); + if ( ctx.ax != 1 ) { + return FALSE; + } + + /* Succeeded, save the handle and away we go */ + info->handle.xms_handle = ctx.dx; + info->read_backing_store = read_xms_store; + info->write_backing_store = write_xms_store; + info->close_backing_store = close_xms_store; + TRACEMS1( cinfo, 1, JTRC_XMS_OPEN, ctx.dx ); + return TRUE; /* succeeded */ +} + +#endif /* XMS_SUPPORTED */ + + +/* + * Access methods for expanded memory. + */ + +#if EMS_SUPPORTED + +/* The EMS move specification structure requires word and long fields aligned + * at odd byte boundaries. Some compilers will align struct fields at even + * byte boundaries. While it's usually possible to force byte alignment, + * that causes an overall performance penalty and may pose problems in merging + * JPEG into a larger application. Instead we accept some rather dirty code + * here. Note this code would fail if the hardware did not allow odd-byte + * word & long accesses, but all 80x86 CPUs do. + */ + +typedef void far * EMSPTR; + +typedef union { /* EMS move specification structure */ + long length; /* It's easy to access first 4 bytes */ + char bytes[18]; /* Misaligned fields in here! */ +} EMSspec; + +/* Macros for accessing misaligned fields */ +#define FIELD_AT( spec, offset, type ) ( *( (type *) &( spec.bytes[offset] ) ) ) +#define SRC_TYPE( spec ) FIELD_AT( spec, 4, char ) +#define SRC_HANDLE( spec ) FIELD_AT( spec, 5, EMSH ) +#define SRC_OFFSET( spec ) FIELD_AT( spec, 7, unsigned short ) +#define SRC_PAGE( spec ) FIELD_AT( spec, 9, unsigned short ) +#define SRC_PTR( spec ) FIELD_AT( spec, 7, EMSPTR ) +#define DST_TYPE( spec ) FIELD_AT( spec, 11, char ) +#define DST_HANDLE( spec ) FIELD_AT( spec, 12, EMSH ) +#define DST_OFFSET( spec ) FIELD_AT( spec, 14, unsigned short ) +#define DST_PAGE( spec ) FIELD_AT( spec, 16, unsigned short ) +#define DST_PTR( spec ) FIELD_AT( spec, 14, EMSPTR ) + +#define EMSPAGESIZE 16384L /* gospel, see the EMS specs */ + +#define HIBYTE( W ) ( ( ( W ) >> 8 ) & 0xFF ) +#define LOBYTE( W ) ( ( W ) & 0xFF ) + + +METHODDEF void +read_ems_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + EMScontext ctx; + EMSspec spec; + + spec.length = byte_count; + SRC_TYPE( spec ) = 1; + SRC_HANDLE( spec ) = info->handle.ems_handle; + SRC_PAGE( spec ) = (unsigned short) ( file_offset / EMSPAGESIZE ); + SRC_OFFSET( spec ) = (unsigned short) ( file_offset % EMSPAGESIZE ); + DST_TYPE( spec ) = 0; + DST_HANDLE( spec ) = 0; + DST_PTR( spec ) = buffer_address; + + ctx.ds_si = (void far *) &spec; + ctx.ax = 0x5700; /* move memory region */ + jems_calldriver( (EMScontext far *) &ctx ); + if ( HIBYTE( ctx.ax ) != 0 ) { + ERREXIT( cinfo, JERR_EMS_READ ); + } +} + + +METHODDEF void +write_ems_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + EMScontext ctx; + EMSspec spec; + + spec.length = byte_count; + SRC_TYPE( spec ) = 0; + SRC_HANDLE( spec ) = 0; + SRC_PTR( spec ) = buffer_address; + DST_TYPE( spec ) = 1; + DST_HANDLE( spec ) = info->handle.ems_handle; + DST_PAGE( spec ) = (unsigned short) ( file_offset / EMSPAGESIZE ); + DST_OFFSET( spec ) = (unsigned short) ( file_offset % EMSPAGESIZE ); + + ctx.ds_si = (void far *) &spec; + ctx.ax = 0x5700; /* move memory region */ + jems_calldriver( (EMScontext far *) &ctx ); + if ( HIBYTE( ctx.ax ) != 0 ) { + ERREXIT( cinfo, JERR_EMS_WRITE ); + } +} + + +METHODDEF void +close_ems_store( j_common_ptr cinfo, backing_store_ptr info ) { + EMScontext ctx; + + ctx.ax = 0x4500; + ctx.dx = info->handle.ems_handle; + jems_calldriver( (EMScontext far *) &ctx ); + TRACEMS1( cinfo, 1, JTRC_EMS_CLOSE, info->handle.ems_handle ); + /* we ignore any error return from the driver */ +} + + +LOCAL boolean +open_ems_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + EMScontext ctx; + + /* Is EMS driver there? */ + if ( !jems_available() ) { + return FALSE; + } + + /* Get status, make sure EMS is OK */ + ctx.ax = 0x4000; + jems_calldriver( (EMScontext far *) &ctx ); + if ( HIBYTE( ctx.ax ) != 0 ) { + return FALSE; + } + + /* Get version, must be >= 4.0 */ + ctx.ax = 0x4600; + jems_calldriver( (EMScontext far *) &ctx ); + if ( ( HIBYTE( ctx.ax ) != 0 ) || ( LOBYTE( ctx.ax ) < 0x40 ) ) { + return FALSE; + } + + /* Try to allocate requested space */ + ctx.ax = 0x4300; + ctx.bx = (unsigned short) ( ( total_bytes_needed + EMSPAGESIZE - 1L ) / EMSPAGESIZE ); + jems_calldriver( (EMScontext far *) &ctx ); + if ( HIBYTE( ctx.ax ) != 0 ) { + return FALSE; + } + + /* Succeeded, save the handle and away we go */ + info->handle.ems_handle = ctx.dx; + info->read_backing_store = read_ems_store; + info->write_backing_store = write_ems_store; + info->close_backing_store = close_ems_store; + TRACEMS1( cinfo, 1, JTRC_EMS_OPEN, ctx.dx ); + return TRUE; /* succeeded */ +} + +#endif /* EMS_SUPPORTED */ + + +/* + * Initial opening of a backing-store object. + */ + +GLOBAL void +jpeg_open_backing_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + /* Try extended memory, then expanded memory, then regular file. */ +#if XMS_SUPPORTED + if ( open_xms_store( cinfo, info, total_bytes_needed ) ) { + return; + } +#endif +#if EMS_SUPPORTED + if ( open_ems_store( cinfo, info, total_bytes_needed ) ) { + return; + } +#endif + if ( open_file_store( cinfo, info, total_bytes_needed ) ) { + return; + } + ERREXITS( cinfo, JERR_TFILE_CREATE, "" ); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. + */ + +GLOBAL long +jpeg_mem_init( j_common_ptr cinfo ) { + next_file_num = 0; /* initialize temp file name generator */ + return DEFAULT_MAX_MEM; /* default for max_memory_to_use */ +} + +GLOBAL void +jpeg_mem_term( j_common_ptr cinfo ) { + /* Microsoft C, at least in v6.00A, will not successfully reclaim freed + * blocks of size > 32Kbytes unless we give it a kick in the rear, like so: + */ +#ifdef NEED_FHEAPMIN + _fheapmin(); +#endif +} diff --git a/neo/renderer/jpeg-6/jmemmgr.cpp b/neo/renderer/jpeg-6/jmemmgr.cpp new file mode 100644 index 00000000..b103c07a --- /dev/null +++ b/neo/renderer/jpeg-6/jmemmgr.cpp @@ -0,0 +1,1144 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +// bph id software added: +#define NO_GETENV + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP( (const char * name) ); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next;/* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next;/* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk;/* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next;/* link to next virtual sarray control block */ + backing_store_info b_s_info;/* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next;/* link to next virtual barray control block */ + backing_store_info b_s_info;/* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL void +print_mem_stats( j_common_ptr cinfo, int pool_id ) { + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf( stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated ); + + for ( lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next ) { + fprintf( stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used ); + } + + for ( shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next ) { + fprintf( stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left ); + } +} + +#endif /* MEM_STATS */ + + +LOCAL void +out_of_memory( j_common_ptr cinfo, int which ) { +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +#ifdef MEM_STATS + cinfo->err->trace_level = 2;/* force self_destruct to report stats */ +#endif + ERREXIT1( cinfo, JERR_OUT_OF_MEMORY, which ); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = { + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = { + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF void * +alloc_small( j_common_ptr cinfo, int pool_id, size_t sizeofobject ) { +/* Allocate a "small" object */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if ( sizeofobject > (size_t) ( MAX_ALLOC_CHUNK - SIZEOF( small_pool_hdr ) ) ) { + out_of_memory( cinfo, 1 ); + } /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF( ALIGN_TYPE ); + if ( odd_bytes > 0 ) { + sizeofobject += SIZEOF( ALIGN_TYPE ) - odd_bytes; + } + + /* See if space is available in any existing pool */ + if ( ( pool_id < 0 ) || ( pool_id >= JPOOL_NUMPOOLS ) ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); + } /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while ( hdr_ptr != NULL ) { + if ( hdr_ptr->hdr.bytes_left >= sizeofobject ) { + break; + } /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if ( hdr_ptr == NULL ) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF( small_pool_hdr ); + if ( prev_hdr_ptr == NULL ) {/* first pool in class? */ + slop = first_pool_slop[pool_id]; + } else { + slop = extra_pool_slop[pool_id]; + } + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if ( slop > (size_t) ( MAX_ALLOC_CHUNK - min_request ) ) { + slop = (size_t) ( MAX_ALLOC_CHUNK - min_request ); + } + /* Try to get space, if fail reduce slop and try again */ + for (;; ) { + hdr_ptr = (small_pool_ptr) jpeg_get_small( cinfo, min_request + slop ); + if ( hdr_ptr != NULL ) { + break; + } + slop /= 2; + if ( slop < MIN_SLOP ) {/* give up when it gets real small */ + out_of_memory( cinfo, 2 ); + } /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if ( prev_hdr_ptr == NULL ) {/* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + } else { + prev_hdr_ptr->hdr.next = hdr_ptr; + } + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) ( hdr_ptr + 1 );/* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used;/* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF void FAR * +alloc_large( j_common_ptr cinfo, int pool_id, size_t sizeofobject ) { +/* Allocate a "large" object */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if ( sizeofobject > (size_t) ( MAX_ALLOC_CHUNK - SIZEOF( large_pool_hdr ) ) ) { + out_of_memory( cinfo, 3 ); + } /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF( ALIGN_TYPE ); + if ( odd_bytes > 0 ) { + sizeofobject += SIZEOF( ALIGN_TYPE ) - odd_bytes; + } + + /* Always make a new pool */ + if ( ( pool_id < 0 ) || ( pool_id >= JPOOL_NUMPOOLS ) ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); + } /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large( cinfo, sizeofobject + + SIZEOF( large_pool_hdr ) ); + if ( hdr_ptr == NULL ) { + out_of_memory( cinfo, 4 ); + } /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF( large_pool_hdr ); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) ( hdr_ptr + 1 );/* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF JSAMPARRAY +alloc_sarray( j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows ) { +/* Allocate a 2-D sample array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = ( MAX_ALLOC_CHUNK - SIZEOF( large_pool_hdr ) ) / + ( (long) samplesperrow * SIZEOF( JSAMPLE ) ); + if ( ltemp <= 0 ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + if ( ltemp < (long) numrows ) { + rowsperchunk = (JDIMENSION) ltemp; + } else { + rowsperchunk = numrows; + } + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small( cinfo, pool_id, + (size_t) ( numrows * SIZEOF( JSAMPROW ) ) ); + + /* Get the rows themselves (large objects) */ + currow = 0; + while ( currow < numrows ) { + rowsperchunk = MIN( rowsperchunk, numrows - currow ); + workspace = (JSAMPROW) alloc_large( cinfo, pool_id, + (size_t) ( (size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF( JSAMPLE ) ) ); + for ( i = rowsperchunk; i > 0; i-- ) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF JBLOCKARRAY +alloc_barray( j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows ) { +/* Allocate a 2-D coefficient-block array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = ( MAX_ALLOC_CHUNK - SIZEOF( large_pool_hdr ) ) / + ( (long) blocksperrow * SIZEOF( JBLOCK ) ); + if ( ltemp <= 0 ) { + ERREXIT( cinfo, JERR_WIDTH_OVERFLOW ); + } + if ( ltemp < (long) numrows ) { + rowsperchunk = (JDIMENSION) ltemp; + } else { + rowsperchunk = numrows; + } + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small( cinfo, pool_id, + (size_t) ( numrows * SIZEOF( JBLOCKROW ) ) ); + + /* Get the rows themselves (large objects) */ + currow = 0; + while ( currow < numrows ) { + rowsperchunk = MIN( rowsperchunk, numrows - currow ); + workspace = (JBLOCKROW) alloc_large( cinfo, pool_id, + (size_t) ( (size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF( JBLOCK ) ) ); + for ( i = rowsperchunk; i > 0; i-- ) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF jvirt_sarray_ptr +request_virt_sarray( j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess ) { +/* Request a virtual 2-D sample array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if ( pool_id != JPOOL_IMAGE ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); + } /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small( cinfo, pool_id, + SIZEOF( struct jvirt_sarray_control ) ); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE;/* no associated backing-store object */ + result->next = mem->virt_sarray_list;/* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF jvirt_barray_ptr +request_virt_barray( j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess ) { +/* Request a virtual 2-D coefficient-block array */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if ( pool_id != JPOOL_IMAGE ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); + } /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small( cinfo, pool_id, + SIZEOF( struct jvirt_barray_control ) ); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE;/* no associated backing-store object */ + result->next = mem->virt_barray_list;/* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF void +realize_virt_arrays( j_common_ptr cinfo ) { +/* Allocate the in-memory buffers for any unrealized virtual arrays */ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for ( sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next ) { + if ( sptr->mem_buffer == NULL ) {/* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF( JSAMPLE ); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF( JSAMPLE ); + } + } + for ( bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next ) { + if ( bptr->mem_buffer == NULL ) {/* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF( JBLOCK ); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF( JBLOCK ); + } + } + + if ( space_per_minheight <= 0 ) { + return; + } /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available( cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated ); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if ( avail_mem >= maximum_space ) { + max_minheights = 1000000000L; + } else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if ( max_minheights <= 0 ) { + max_minheights = 1; + } + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for ( sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next ) { + if ( sptr->mem_buffer == NULL ) {/* if not realized yet */ + minheights = ( (long) sptr->rows_in_array - 1L ) / sptr->maxaccess + 1L; + if ( minheights <= max_minheights ) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) ( max_minheights * sptr->maxaccess ); + jpeg_open_backing_store( cinfo, &sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF( JSAMPLE ) ); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray( cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem ); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for ( bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next ) { + if ( bptr->mem_buffer == NULL ) {/* if not realized yet */ + minheights = ( (long) bptr->rows_in_array - 1L ) / bptr->maxaccess + 1L; + if ( minheights <= max_minheights ) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) ( max_minheights * bptr->maxaccess ); + jpeg_open_backing_store( cinfo, &bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF( JBLOCK ) ); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray( cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem ); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL void +do_sarray_io( j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing ) { +/* Do backing store read or write of a virtual sample array */ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF( JSAMPLE ); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for ( i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk ) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN( (long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i ); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN( rows, (long) ptr->first_undef_row - thisrow ); + /* Transfer no more than fits in file */ + rows = MIN( rows, (long) ptr->rows_in_array - thisrow ); + if ( rows <= 0 ) {/* this chunk might be past end of file! */ + break; + } + byte_count = rows * bytesperrow; + if ( writing ) { + ( *ptr->b_s_info.write_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } else { + ( *ptr->b_s_info.read_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } + file_offset += byte_count; + } +} + + +LOCAL void +do_barray_io( j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing ) { +/* Do backing store read or write of a virtual coefficient-block array */ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF( JBLOCK ); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for ( i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk ) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN( (long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i ); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN( rows, (long) ptr->first_undef_row - thisrow ); + /* Transfer no more than fits in file */ + rows = MIN( rows, (long) ptr->rows_in_array - thisrow ); + if ( rows <= 0 ) {/* this chunk might be past end of file! */ + break; + } + byte_count = rows * bytesperrow; + if ( writing ) { + ( *ptr->b_s_info.write_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } else { + ( *ptr->b_s_info.read_backing_store )( cinfo, &ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count ); + } + file_offset += byte_count; + } +} + + +METHODDEF JSAMPARRAY +access_virt_sarray( j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable ) { +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if ( ( end_row > ptr->rows_in_array ) || ( num_rows > ptr->maxaccess ) || + ( ptr->mem_buffer == NULL ) ) { + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + + /* Make the desired part of the virtual array accessible */ + if ( ( start_row < ptr->cur_start_row ) || + ( end_row > ptr->cur_start_row + ptr->rows_in_mem ) ) { + if ( !ptr->b_s_open ) { + ERREXIT( cinfo, JERR_VIRTUAL_BUG ); + } + /* Flush old buffer contents if necessary */ + if ( ptr->dirty ) { + do_sarray_io( cinfo, ptr, TRUE ); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if ( start_row > ptr->cur_start_row ) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if ( ltemp < 0 ) { + ltemp = 0; + } /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io( cinfo, ptr, FALSE ); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if ( ptr->first_undef_row < end_row ) { + if ( ptr->first_undef_row < start_row ) { + if ( writable ) {/* writer skipped over a section of array */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + undef_row = start_row;/* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if ( writable ) { + ptr->first_undef_row = end_row; + } + if ( ptr->pre_zero ) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF( JSAMPLE ); + undef_row -= ptr->cur_start_row;/* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while ( undef_row < end_row ) { + jzero_far( (void FAR *) ptr->mem_buffer[undef_row], bytesperrow ); + undef_row++; + } + } else { + if ( !writable ) {/* reader looking at undefined data */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + } + } + /* Flag the buffer dirty if caller will write in it */ + if ( writable ) { + ptr->dirty = TRUE; + } + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + ( start_row - ptr->cur_start_row ); +} + + +METHODDEF JBLOCKARRAY +access_virt_barray( j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable ) { +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if ( ( end_row > ptr->rows_in_array ) || ( num_rows > ptr->maxaccess ) || + ( ptr->mem_buffer == NULL ) ) { + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + + /* Make the desired part of the virtual array accessible */ + if ( ( start_row < ptr->cur_start_row ) || + ( end_row > ptr->cur_start_row + ptr->rows_in_mem ) ) { + if ( !ptr->b_s_open ) { + ERREXIT( cinfo, JERR_VIRTUAL_BUG ); + } + /* Flush old buffer contents if necessary */ + if ( ptr->dirty ) { + do_barray_io( cinfo, ptr, TRUE ); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if ( start_row > ptr->cur_start_row ) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if ( ltemp < 0 ) { + ltemp = 0; + } /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io( cinfo, ptr, FALSE ); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if ( ptr->first_undef_row < end_row ) { + if ( ptr->first_undef_row < start_row ) { + if ( writable ) {/* writer skipped over a section of array */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + undef_row = start_row;/* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if ( writable ) { + ptr->first_undef_row = end_row; + } + if ( ptr->pre_zero ) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF( JBLOCK ); + undef_row -= ptr->cur_start_row;/* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while ( undef_row < end_row ) { + jzero_far( (void FAR *) ptr->mem_buffer[undef_row], bytesperrow ); + undef_row++; + } + } else { + if ( !writable ) {/* reader looking at undefined data */ + ERREXIT( cinfo, JERR_BAD_VIRTUAL_ACCESS ); + } + } + } + /* Flag the buffer dirty if caller will write in it */ + if ( writable ) { + ptr->dirty = TRUE; + } + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + ( start_row - ptr->cur_start_row ); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF void +free_pool( j_common_ptr cinfo, int pool_id ) { + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if ( ( pool_id < 0 ) || ( pool_id >= JPOOL_NUMPOOLS ) ) { + ERREXIT1( cinfo, JERR_BAD_POOL_ID, pool_id ); + } /* safety check */ + +#ifdef MEM_STATS + if ( cinfo->err->trace_level > 1 ) { + print_mem_stats( cinfo, pool_id ); + } /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if ( pool_id == JPOOL_IMAGE ) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for ( sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next ) { + if ( sptr->b_s_open ) {/* there may be no backing store */ + sptr->b_s_open = FALSE;/* prevent recursive close if error */ + ( *sptr->b_s_info.close_backing_store )( cinfo, &sptr->b_s_info ); + } + } + mem->virt_sarray_list = NULL; + for ( bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next ) { + if ( bptr->b_s_open ) {/* there may be no backing store */ + bptr->b_s_open = FALSE;/* prevent recursive close if error */ + ( *bptr->b_s_info.close_backing_store )( cinfo, &bptr->b_s_info ); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while ( lhdr_ptr != NULL ) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF( large_pool_hdr ); + jpeg_free_large( cinfo, (void FAR *) lhdr_ptr, space_freed ); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while ( shdr_ptr != NULL ) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF( small_pool_hdr ); + jpeg_free_small( cinfo, (void *) shdr_ptr, space_freed ); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF void +self_destruct( j_common_ptr cinfo ) { + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for ( pool = JPOOL_NUMPOOLS - 1; pool >= JPOOL_PERMANENT; pool-- ) { + free_pool( cinfo, pool ); + } + + /* Release the memory manager control block too. */ + jpeg_free_small( cinfo, (void *) cinfo->mem, SIZEOF( my_memory_mgr ) ); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term( cinfo ); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL void +jinit_memory_mgr( j_common_ptr cinfo ) { + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ( ( SIZEOF( ALIGN_TYPE ) & ( SIZEOF( ALIGN_TYPE ) - 1 ) ) != 0 ) { + ERREXIT( cinfo, JERR_BAD_ALIGN_TYPE ); + } + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ( ( (long) test_mac != MAX_ALLOC_CHUNK ) || + ( ( MAX_ALLOC_CHUNK % SIZEOF( ALIGN_TYPE ) ) != 0 ) ) { + ERREXIT( cinfo, JERR_BAD_ALLOC_CHUNK ); + } + + max_to_use = jpeg_mem_init( cinfo );/* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small( cinfo, SIZEOF( my_memory_mgr ) ); + + if ( mem == NULL ) { + jpeg_mem_term( cinfo );/* system-dependent cleanup */ + ERREXIT1( cinfo, JERR_OUT_OF_MEMORY, 0 ); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for ( pool = JPOOL_NUMPOOLS - 1; pool >= JPOOL_PERMANENT; pool-- ) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF( my_memory_mgr ); + + /* Declare ourselves open for business */ + cinfo->mem = &mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ( ( memenv = getenv( "JPEGMEM" ) ) != NULL ) { + char ch = 'x'; + + if ( sscanf( memenv, "%ld%c", &max_to_use, &ch ) > 0 ) { + if ( ( ch == 'm' ) || ( ch == 'M' ) ) { + max_to_use *= 1000L; + } + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/neo/renderer/jpeg-6/jmemname.cpp b/neo/renderer/jpeg-6/jmemname.cpp new file mode 100644 index 00000000..ba5aafe6 --- /dev/null +++ b/neo/renderer/jpeg-6/jmemname.cpp @@ -0,0 +1,264 @@ +/* + * jmemname.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a generic implementation of the system-dependent + * portion of the JPEG memory manager. This implementation assumes that + * you must explicitly construct a name for each temp file. + * Also, the problem of determining the amount of memory available + * is shoved onto the user. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void * malloc JPP( (size_t size) ); +extern void free JPP( (void * ptr) ); +#endif + +#ifndef SEEK_SET /* pre-ANSI systems may not define this; */ +#define SEEK_SET 0 /* if not, assume 0 is correct */ +#endif + +#ifdef DONT_USE_B_MODE /* define mode parameters for fopen() */ +#define READ_BINARY "r" +#define RW_BINARY "w+" +#else +#define READ_BINARY "rb" +#define RW_BINARY "w+b" +#endif + + +/* + * Selection of a file name for a temporary file. + * This is system-dependent! + * + * The code as given is suitable for most Unix systems, and it is easily + * modified for most non-Unix systems. Some notes: + * 1. The temp file is created in the directory named by TEMP_DIRECTORY. + * The default value is /usr/tmp, which is the conventional place for + * creating large temp files on Unix. On other systems you'll probably + * want to change the file location. You can do this by editing the + * #define, or (preferred) by defining TEMP_DIRECTORY in jconfig.h. + * + * 2. If you need to change the file name as well as its location, + * you can override the TEMP_FILE_NAME macro. (Note that this is + * actually a printf format string; it must contain %s and %d.) + * Few people should need to do this. + * + * 3. mktemp() is used to ensure that multiple processes running + * simultaneously won't select the same file names. If your system + * doesn't have mktemp(), define NO_MKTEMP to do it the hard way. + * (If you don't have , also define NO_ERRNO_H.) + * + * 4. You probably want to define NEED_SIGNAL_CATCHER so that cjpeg.c/djpeg.c + * will cause the temp files to be removed if you stop the program early. + */ + +#ifndef TEMP_DIRECTORY /* can override from jconfig.h or Makefile */ +#define TEMP_DIRECTORY "/usr/tmp/" /* recommended setting for Unix */ +#endif + +static int next_file_num; /* to distinguish among several temp files */ + +#ifdef NO_MKTEMP + +#ifndef TEMP_FILE_NAME /* can override from jconfig.h or Makefile */ +#define TEMP_FILE_NAME "%sJPG%03d.TMP" +#endif + +#ifndef NO_ERRNO_H +#include /* to define ENOENT */ +#endif + +/* ANSI C specifies that errno is a macro, but on older systems it's more + * likely to be a plain int variable. And not all versions of errno.h + * bother to declare it, so we have to in order to be most portable. Thus: + */ +#ifndef errno +extern int errno; +#endif + + +LOCAL void +select_file_name( char * fname ) { + FILE * tfile; + + /* Keep generating file names till we find one that's not in use */ + for (;; ) { + next_file_num++; /* advance counter */ + sprintf( fname, TEMP_FILE_NAME, TEMP_DIRECTORY, next_file_num ); + if ( ( tfile = fopen( fname, READ_BINARY ) ) == NULL ) { + /* fopen could have failed for a reason other than the file not + * being there; for example, file there but unreadable. + * If isn't available, then we cannot test the cause. + */ +#ifdef ENOENT + if ( errno != ENOENT ) { + continue; + } +#endif + break; + } + fclose( tfile );/* oops, it's there; close tfile & try again */ + } +} + +#else /* ! NO_MKTEMP */ + +/* Note that mktemp() requires the initial filename to end in six X's */ +#ifndef TEMP_FILE_NAME /* can override from jconfig.h or Makefile */ +#define TEMP_FILE_NAME "%sJPG%dXXXXXX" +#endif + +LOCAL void +select_file_name( char * fname ) { + next_file_num++; /* advance counter */ + sprintf( fname, TEMP_FILE_NAME, TEMP_DIRECTORY, next_file_num ); + mktemp( fname ); /* make sure file name is unique */ + /* mktemp replaces the trailing XXXXXX with a unique string of characters */ +} + +#endif /* NO_MKTEMP */ + + +/* + * Memory allocation and freeing are controlled by the regular library + * routines malloc() and free(). + */ + +GLOBAL void * +jpeg_get_small( j_common_ptr cinfo, size_t sizeofobject ) { + return (void *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_small( j_common_ptr cinfo, void * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large( j_common_ptr cinfo, size_t sizeofobject ) { + return (void FAR *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_large( j_common_ptr cinfo, void FAR * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * This routine computes the total memory space available for allocation. + * It's impossible to do this in a portable way; our current solution is + * to make the user tell us (with a default value set at compile time). + * If you can actually get the available space, it's a good idea to subtract + * a slop factor of 5% or so. + */ + +#ifndef DEFAULT_MAX_MEM /* so can override from makefile */ +#define DEFAULT_MAX_MEM 1000000L /* default: one megabyte */ +#endif + +GLOBAL long +jpeg_mem_available( j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated ) { + return cinfo->mem->max_memory_to_use - already_allocated; +} + + +/* + * Backing store (temporary file) management. + * Backing store objects are only used when the value returned by + * jpeg_mem_available is less than the total space needed. You can dispense + * with these routines if you have plenty of virtual memory; see jmemnobs.c. + */ + + +METHODDEF void +read_backing_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + if ( fseek( info->temp_file, file_offset, SEEK_SET ) ) { + ERREXIT( cinfo, JERR_TFILE_SEEK ); + } + if ( JFREAD( info->temp_file, buffer_address, byte_count ) + != (size_t) byte_count ) { + ERREXIT( cinfo, JERR_TFILE_READ ); + } +} + + +METHODDEF void +write_backing_store( j_common_ptr cinfo, backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count ) { + if ( fseek( info->temp_file, file_offset, SEEK_SET ) ) { + ERREXIT( cinfo, JERR_TFILE_SEEK ); + } + if ( JFWRITE( info->temp_file, buffer_address, byte_count ) + != (size_t) byte_count ) { + ERREXIT( cinfo, JERR_TFILE_WRITE ); + } +} + + +METHODDEF void +close_backing_store( j_common_ptr cinfo, backing_store_ptr info ) { + fclose( info->temp_file );/* close the file */ + unlink( info->temp_name );/* delete the file */ +/* If your system doesn't have unlink(), use remove() instead. + * remove() is the ANSI-standard name for this function, but if + * your system was ANSI you'd be using jmemansi.c, right? + */ + TRACEMSS( cinfo, 1, JTRC_TFILE_CLOSE, info->temp_name ); +} + + +/* + * Initial opening of a backing-store object. + */ + +GLOBAL void +jpeg_open_backing_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + select_file_name( info->temp_name ); + if ( ( info->temp_file = fopen( info->temp_name, RW_BINARY ) ) == NULL ) { + ERREXITS( cinfo, JERR_TFILE_CREATE, info->temp_name ); + } + info->read_backing_store = read_backing_store; + info->write_backing_store = write_backing_store; + info->close_backing_store = close_backing_store; + TRACEMSS( cinfo, 1, JTRC_TFILE_OPEN, info->temp_name ); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. + */ + +GLOBAL long +jpeg_mem_init( j_common_ptr cinfo ) { + next_file_num = 0; /* initialize temp file name generator */ + return DEFAULT_MAX_MEM; /* default for max_memory_to_use */ +} + +GLOBAL void +jpeg_mem_term( j_common_ptr cinfo ) { + /* no work */ +} diff --git a/neo/renderer/jpeg-6/jmemnobs.cpp b/neo/renderer/jpeg-6/jmemnobs.cpp new file mode 100644 index 00000000..b7eae47a --- /dev/null +++ b/neo/renderer/jpeg-6/jmemnobs.cpp @@ -0,0 +1,95 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from ri.Malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +/* + * Memory allocation and ri.Freeing are controlled by the regular library + * routines ri.Malloc() and ri.Free(). + */ + +GLOBAL void * +jpeg_get_small( j_common_ptr cinfo, size_t sizeofobject ) { + return (void *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_small( j_common_ptr cinfo, void * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large( j_common_ptr cinfo, size_t sizeofobject ) { + return (void FAR *) malloc( sizeofobject ); +} + +GLOBAL void +jpeg_free_large( j_common_ptr cinfo, void FAR * object, size_t sizeofobject ) { + free( object ); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL long +jpeg_mem_available( j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated ) { + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL void +jpeg_open_backing_store( j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed ) { + ERREXIT( cinfo, JERR_NO_BACKING_STORE ); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL long +jpeg_mem_init( j_common_ptr cinfo ) { + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL void +jpeg_mem_term( j_common_ptr cinfo ) { + /* no work */ +} diff --git a/neo/renderer/jpeg-6/jmemsys.h b/neo/renderer/jpeg-6/jmemsys.h new file mode 100644 index 00000000..033d29a7 --- /dev/null +++ b/neo/renderer/jpeg-6/jmemsys.h @@ -0,0 +1,182 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN void * jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN void jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN void FAR * jpeg_get_large JPP((j_common_ptr cinfo,size_t sizeofobject)); +EXTERN void jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN long jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + backing_store_ptr info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +} backing_store_info; + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN void jpeg_open_backing_store JPP((j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN long jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN void jpeg_mem_term JPP((j_common_ptr cinfo)); diff --git a/neo/renderer/jpeg-6/jmorecfg.h b/neo/renderer/jpeg-6/jmorecfg.h new file mode 100644 index 00000000..958381f2 --- /dev/null +++ b/neo/renderer/jpeg-6/jmorecfg.h @@ -0,0 +1,352 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +#ifndef __MWERKS__ +#ifndef _BASETSD_H_ +typedef long INT32; +#endif +#endif + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +//#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +//typedef long INT32; +//#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These defines are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +#define METHODDEF static /* a function called through method pointers */ +#define LOCAL static /* a function used only in its module */ +#define GLOBAL /* a function referenced thru EXTERNs */ +#define EXTERN extern /* a reference to a GLOBAL function */ + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#undef FAR +#define FAR far +#else +#undef FAR +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +//#ifndef HAVE_BOOLEAN +//typedef int boolean; +//#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#undef DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#undef DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#undef D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#undef D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#undef BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#undef IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#undef UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#undef QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#undef QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 4 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/neo/renderer/jpeg-6/jpegint.h b/neo/renderer/jpeg-6/jpegint.h new file mode 100644 index 00000000..ab5bee2c --- /dev/null +++ b/neo/renderer/jpeg-6/jpegint.h @@ -0,0 +1,388 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + /* write_any_marker is exported for use by applications */ + /* Probably only COM and APPn markers should be written */ + JMETHOD(void, write_any_marker, (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN void jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN void jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN void jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN void jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN void jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN void jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN void jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN void jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN long jdiv_round_up JPP((long a, long b)); +EXTERN long jround_up JPP((long a, long b)); +EXTERN void jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN void jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN void jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/neo/renderer/jpeg-6/jpeglib.h b/neo/renderer/jpeg-6/jpeglib.h new file mode 100644 index 00000000..edfdda10 --- /dev/null +++ b/neo/renderer/jpeg-6/jpeglib.h @@ -0,0 +1,1051 @@ +/* + * jpeglib.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +typedef unsigned char boolean; +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "../jpeg-6/jconfig.h" /* widely used configuration options */ +#endif +#include "../jpeg-6/jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 60 /* Version 6 */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This field directly represents the contents of a JPEG DQT marker. + * Note: the values are always given in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is not currently used by the compressor. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + boolean is_decompressor; /* so common code can tell which is which */\ + int global_state /* for checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker: */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_create_compress jCreaCompress +#define jpeg_create_decompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN struct jpeg_error_mgr *jpeg_std_error JPP((struct jpeg_error_mgr *err)); + +/* Initialization and destruction of JPEG compression objects */ +/* NB: you must set up the error-manager BEFORE calling jpeg_create_xxx */ +EXTERN void jpeg_create_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_create_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN void jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN void jpeg_stdio_src JPP((j_decompress_ptr cinfo, unsigned char *infile)); + +/* Default parameter setup for compression */ +EXTERN void jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN void jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN void jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN void jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN void jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN int jpeg_quality_scaling JPP((int quality)); +EXTERN void jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN JQUANT_TBL * jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN JHUFF_TBL * jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN void jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN JDIMENSION jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN void jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN JDIMENSION jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN void jpeg_write_marker JPP((j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN void jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN int jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN boolean jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN JDIMENSION jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN boolean jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN JDIMENSION jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN boolean jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN boolean jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN int jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN void jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN void jpeg_set_marker_processor JPP((j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN jvirt_barray_ptr * jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN void jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN void jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN void jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN void jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN boolean jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "../jpeg-6/jpegint.h" /* fetch private declarations */ +#include "../jpeg-6/jerror.h" /* fetch error codes too */ +#endif + +#endif /* JPEGLIB_H */ diff --git a/neo/renderer/jpeg-6/jpegtran.cpp b/neo/renderer/jpeg-6/jpegtran.cpp new file mode 100644 index 00000000..1db1e2fc --- /dev/null +++ b/neo/renderer/jpeg-6/jpegtran.cpp @@ -0,0 +1,381 @@ +/* + * jpegtran.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a command-line user interface for JPEG transcoding. + * It is very similar to cjpeg.c, but provides lossless transcoding between + * different JPEG file formats. + */ + +#include "cdjpeg.h" /* Common decls for cjpeg/djpeg applications */ +#include "jversion.h" /* for version message */ + +#ifdef USE_CCOMMAND /* command-line reader for Macintosh */ +#ifdef __MWERKS__ +#include /* Metrowerks declares it here */ +#endif +#ifdef THINK_C +#include /* Think declares it here */ +#endif +#endif + + +/* + * Argument-parsing code. + * The switch parser is designed to be useful with DOS-style command line + * syntax, ie, intermixed switches and file names, where only the switches + * to the left of a given file name affect processing of that file. + * The main program in this file doesn't actually use this capability... + */ + + +static const char * progname; /* program name for error messages */ +static char * outfilename; /* for -outfile switch */ + + +LOCAL void +usage( void ) { +/* complain about bad command line */ + fprintf( stderr, "usage: %s [switches] ", progname ); +#ifdef TWO_FILE_COMMANDLINE + fprintf( stderr, "inputfile outputfile\n" ); +#else + fprintf( stderr, "[inputfile]\n" ); +#endif + + fprintf( stderr, "Switches (names may be abbreviated):\n" ); +#ifdef ENTROPY_OPT_SUPPORTED + fprintf( stderr, " -optimize Optimize Huffman table (smaller file, but slow compression)\n" ); +#endif +#ifdef C_PROGRESSIVE_SUPPORTED + fprintf( stderr, " -progressive Create progressive JPEG file\n" ); +#endif + fprintf( stderr, "Switches for advanced users:\n" ); + fprintf( stderr, " -restart N Set restart interval in rows, or in blocks with B\n" ); + fprintf( stderr, " -maxmemory N Maximum memory to use (in kbytes)\n" ); + fprintf( stderr, " -outfile name Specify name for output file\n" ); + fprintf( stderr, " -verbose or -debug Emit debug output\n" ); + fprintf( stderr, "Switches for wizards:\n" ); +#ifdef C_ARITH_CODING_SUPPORTED + fprintf( stderr, " -arithmetic Use arithmetic coding\n" ); +#endif +#ifdef C_MULTISCAN_FILES_SUPPORTED + fprintf( stderr, " -scans file Create multi-scan JPEG per script file\n" ); +#endif + exit( EXIT_FAILURE ); +} + + +LOCAL int +parse_switches( j_compress_ptr cinfo, int argc, char ** argv, + int last_file_arg_seen, boolean for_real ) { +/* Parse optional switches. + * Returns argv[] index of first file-name argument (== argc if none). + * Any file names with indexes <= last_file_arg_seen are ignored; + * they have presumably been processed in a previous iteration. + * (Pass 0 for last_file_arg_seen on the first or only iteration.) + * for_real is FALSE on the first (dummy) pass; we may skip any expensive + * processing. + */ + int argn; + char * arg; + boolean simple_progressive; + char * scansarg = NULL; /* saves -scans parm if any */ + + /* Set up default JPEG parameters. */ + simple_progressive = FALSE; + outfilename = NULL; + cinfo->err->trace_level = 0; + + /* Scan command line options, adjust parameters */ + + for ( argn = 1; argn < argc; argn++ ) { + arg = argv[argn]; + if ( *arg != '-' ) { + /* Not a switch, must be a file name argument */ + if ( argn <= last_file_arg_seen ) { + outfilename = NULL;/* -outfile applies to just one input file */ + continue; /* ignore this name if previously processed */ + } + break; /* else done parsing switches */ + } + arg++; /* advance past switch marker character */ + + if ( keymatch( arg, "arithmetic", 1 ) ) { + /* Use arithmetic coding. */ +#ifdef C_ARITH_CODING_SUPPORTED + cinfo->arith_code = TRUE; +#else + fprintf( stderr, "%s: sorry, arithmetic coding not supported\n", + progname ); + exit( EXIT_FAILURE ); +#endif + + } else if ( keymatch( arg, "debug", 1 ) || keymatch( arg, "verbose", 1 ) ) { + /* Enable debug printouts. */ + /* On first -d, print version identification */ + static boolean printed_version = FALSE; + + if ( !printed_version ) { + fprintf( stderr, "Independent JPEG Group's JPEGTRAN, version %s\n%s\n", + JVERSION, JCOPYRIGHT ); + printed_version = TRUE; + } + cinfo->err->trace_level++; + + } else if ( keymatch( arg, "maxmemory", 3 ) ) { + /* Maximum memory in Kb (or Mb with 'm'). */ + long lval; + char ch = 'x'; + + if ( ++argn >= argc ) {/* advance to next argument */ + usage(); + } + if ( sscanf( argv[argn], "%ld%c", &lval, &ch ) < 1 ) { + usage(); + } + if ( ( ch == 'm' ) || ( ch == 'M' ) ) { + lval *= 1000L; + } + cinfo->mem->max_memory_to_use = lval * 1000L; + + } else if ( keymatch( arg, "optimize", 1 ) || keymatch( arg, "optimise", 1 ) ) { + /* Enable entropy parm optimization. */ +#ifdef ENTROPY_OPT_SUPPORTED + cinfo->optimize_coding = TRUE; +#else + fprintf( stderr, "%s: sorry, entropy optimization was not compiled\n", + progname ); + exit( EXIT_FAILURE ); +#endif + + } else if ( keymatch( arg, "outfile", 4 ) ) { + /* Set output file name. */ + if ( ++argn >= argc ) {/* advance to next argument */ + usage(); + } + outfilename = argv[argn];/* save it away for later use */ + + } else if ( keymatch( arg, "progressive", 1 ) ) { + /* Select simple progressive mode. */ +#ifdef C_PROGRESSIVE_SUPPORTED + simple_progressive = TRUE; + /* We must postpone execution until num_components is known. */ +#else + fprintf( stderr, "%s: sorry, progressive output was not compiled\n", + progname ); + exit( EXIT_FAILURE ); +#endif + + } else if ( keymatch( arg, "restart", 1 ) ) { + /* Restart interval in MCU rows (or in MCUs with 'b'). */ + long lval; + char ch = 'x'; + + if ( ++argn >= argc ) {/* advance to next argument */ + usage(); + } + if ( sscanf( argv[argn], "%ld%c", &lval, &ch ) < 1 ) { + usage(); + } + if ( ( lval < 0 ) || ( lval > 65535L ) ) { + usage(); + } + if ( ( ch == 'b' ) || ( ch == 'B' ) ) { + cinfo->restart_interval = (unsigned int) lval; + cinfo->restart_in_rows = 0;/* else prior '-restart n' overrides me */ + } else { + cinfo->restart_in_rows = (int) lval; + /* restart_interval will be computed during startup */ + } + + } else if ( keymatch( arg, "scans", 2 ) ) { + /* Set scan script. */ +#ifdef C_MULTISCAN_FILES_SUPPORTED + if ( ++argn >= argc ) {/* advance to next argument */ + usage(); + } + scansarg = argv[argn]; + /* We must postpone reading the file in case -progressive appears. */ +#else + fprintf( stderr, "%s: sorry, multi-scan output was not compiled\n", + progname ); + exit( EXIT_FAILURE ); +#endif + + } else { + usage(); /* bogus switch */ + } + } + + /* Post-switch-scanning cleanup */ + + if ( for_real ) { + +#ifdef C_PROGRESSIVE_SUPPORTED + if ( simple_progressive ) {/* process -progressive; -scans can override */ + jpeg_simple_progression( cinfo ); + } +#endif + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if ( scansarg != NULL ) {/* process -scans if it was present */ + if ( !read_scan_script( cinfo, scansarg ) ) { + usage(); + } + } +#endif + } + + return argn; /* return index of next arg (file name) */ +} + + +/* + * The main program. + */ + +GLOBAL int +main( int argc, char ** argv ) { + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + struct jpeg_error_mgr jsrcerr, jdsterr; +#ifdef PROGRESS_REPORT + struct cdjpeg_progress_mgr progress; +#endif + jvirt_barray_ptr * coef_arrays; + int file_index; + FILE * input_file; + FILE * output_file; + + /* On Mac, fetch a command line. */ +#ifdef USE_CCOMMAND + argc = ccommand( &argv ); +#endif + + progname = argv[0]; + if ( ( progname == NULL ) || ( progname[0] == 0 ) ) { + progname = "jpegtran"; + } /* in case C library doesn't provide it */ + + /* Initialize the JPEG decompression object with default error handling. */ + srcinfo.err = jpeg_std_error( &jsrcerr ); + jpeg_create_decompress( &srcinfo ); + /* Initialize the JPEG compression object with default error handling. */ + dstinfo.err = jpeg_std_error( &jdsterr ); + jpeg_create_compress( &dstinfo ); + + /* Now safe to enable signal catcher. + * Note: we assume only the decompression object will have virtual arrays. + */ +#ifdef NEED_SIGNAL_CATCHER + enable_signal_catcher( ( j_common_ptr ) & srcinfo ); +#endif + + /* Scan command line to find file names. + * It is convenient to use just one switch-parsing routine, but the switch + * values read here are ignored; we will rescan the switches after opening + * the input file. + */ + + file_index = parse_switches( &dstinfo, argc, argv, 0, FALSE ); + jsrcerr.trace_level = jdsterr.trace_level; + +#ifdef TWO_FILE_COMMANDLINE + /* Must have either -outfile switch or explicit output file name */ + if ( outfilename == NULL ) { + if ( file_index != argc - 2 ) { + fprintf( stderr, "%s: must name one input and one output file\n", + progname ); + usage(); + } + outfilename = argv[file_index + 1]; + } else { + if ( file_index != argc - 1 ) { + fprintf( stderr, "%s: must name one input and one output file\n", + progname ); + usage(); + } + } +#else + /* Unix style: expect zero or one file name */ + if ( file_index < argc - 1 ) { + fprintf( stderr, "%s: only one input file\n", progname ); + usage(); + } +#endif /* TWO_FILE_COMMANDLINE */ + + /* Open the input file. */ + if ( file_index < argc ) { + if ( ( input_file = fopen( argv[file_index], READ_BINARY ) ) == NULL ) { + fprintf( stderr, "%s: can't open %s\n", progname, argv[file_index] ); + exit( EXIT_FAILURE ); + } + } else { + /* default input file is stdin */ + input_file = read_stdin(); + } + + /* Open the output file. */ + if ( outfilename != NULL ) { + if ( ( output_file = fopen( outfilename, WRITE_BINARY ) ) == NULL ) { + fprintf( stderr, "%s: can't open %s\n", progname, outfilename ); + exit( EXIT_FAILURE ); + } + } else { + /* default output file is stdout */ + output_file = write_stdout(); + } + +#ifdef PROGRESS_REPORT + start_progress_monitor( ( j_common_ptr ) & dstinfo, &progress ); +#endif + + /* Specify data source for decompression */ + jpeg_stdio_src( &srcinfo, input_file ); + + /* Read file header */ + (void) jpeg_read_header( &srcinfo, TRUE ); + + /* Read source file as DCT coefficients */ + coef_arrays = jpeg_read_coefficients( &srcinfo ); + + /* Initialize destination compression parameters from source values */ + jpeg_copy_critical_parameters( &srcinfo, &dstinfo ); + + /* Adjust default compression parameters by re-parsing the options */ + file_index = parse_switches( &dstinfo, argc, argv, 0, TRUE ); + + /* Specify data destination for compression */ + jpeg_stdio_dest( &dstinfo, output_file ); + + /* Start compressor */ + jpeg_write_coefficients( &dstinfo, coef_arrays ); + + /* ought to copy source comments here... */ + + /* Finish compression and release memory */ + jpeg_finish_compress( &dstinfo ); + jpeg_destroy_compress( &dstinfo ); + (void) jpeg_finish_decompress( &srcinfo ); + jpeg_destroy_decompress( &srcinfo ); + + /* Close files, if we opened them */ + if ( input_file != stdin ) { + fclose( input_file ); + } + if ( output_file != stdout ) { + fclose( output_file ); + } + +#ifdef PROGRESS_REPORT + end_progress_monitor( ( j_common_ptr ) & dstinfo ); +#endif + + /* All done. */ + exit( jsrcerr.num_warnings + jdsterr.num_warnings ? EXIT_WARNING : EXIT_SUCCESS ); + return 0; /* suppress no-return-value warnings */ +} diff --git a/neo/renderer/jpeg-6/jquant1.cpp b/neo/renderer/jpeg-6/jquant1.cpp new file mode 100644 index 00000000..fcb5ac0d --- /dev/null +++ b/neo/renderer/jpeg-6/jquant1.cpp @@ -0,0 +1,857 @@ +/* + * jquant1.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 1-pass color quantization (color mapping) routines. + * These routines provide mapping to a fixed color map using equally spaced + * color values. Optional Floyd-Steinberg or ordered dithering is available. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_1PASS_SUPPORTED + + +/* + * The main purpose of 1-pass quantization is to provide a fast, if not very + * high quality, colormapped output capability. A 2-pass quantizer usually + * gives better visual quality; however, for quantized grayscale output this + * quantizer is perfectly adequate. Dithering is highly recommended with this + * quantizer, though you can turn it off if you really want to. + * + * In 1-pass quantization the colormap must be chosen in advance of seeing the + * image. We use a map consisting of all combinations of Ncolors[i] color + * values for the i'th component. The Ncolors[] values are chosen so that + * their product, the total number of colors, is no more than that requested. + * (In most cases, the product will be somewhat less.) + * + * Since the colormap is orthogonal, the representative value for each color + * component can be determined without considering the other components; + * then these indexes can be combined into a colormap index by a standard + * N-dimensional-array-subscript calculation. Most of the arithmetic involved + * can be precalculated and stored in the lookup table colorindex[]. + * colorindex[i][j] maps pixel value j in component i to the nearest + * representative value (grid plane) for that component; this index is + * multiplied by the array stride for component i, so that the + * index of the colormap entry closest to a given pixel value is just + * sum( colorindex[component-number][pixel-component-value] ) + * Aside from being fast, this scheme allows for variable spacing between + * representative values with no additional lookup cost. + * + * If gamma correction has been applied in color conversion, it might be wise + * to adjust the color grid spacing so that the representative colors are + * equidistant in linear space. At this writing, gamma correction is not + * implemented by jdcolor, so nothing is done here. + */ + + +/* Declarations for ordered dithering. + * + * We use a standard 16x16 ordered dither array. The basic concept of ordered + * dithering is described in many references, for instance Dale Schumacher's + * chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991). + * In place of Schumacher's comparisons against a "threshold" value, we add a + * "dither" value to the input pixel and then round the result to the nearest + * output value. The dither value is equivalent to (0.5 - threshold) times + * the distance between output values. For ordered dithering, we assume that + * the output colors are equally spaced; if not, results will probably be + * worse, since the dither may be too much or too little at a given point. + * + * The normal calculation would be to form pixel value + dither, range-limit + * this to 0..MAXJSAMPLE, and then index into the colorindex table as usual. + * We can skip the separate range-limiting step by extending the colorindex + * table in both directions. + */ + +#define ODITHER_SIZE 16 /* dimension of dither matrix */ +/* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ +#define ODITHER_CELLS ( ODITHER_SIZE * ODITHER_SIZE ) /* # cells in matrix */ +#define ODITHER_MASK ( ODITHER_SIZE - 1 ) /* mask for wrapping around counters */ + +typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE]; +typedef int ( *ODITHER_MATRIX_PTR )[ODITHER_SIZE]; + +static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { + /* Bayer's order-4 dither array. Generated by the code given in + * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I. + * The values in this array must range from 0 to ODITHER_CELLS-1. + */ + { 0, 192, 48, 240, 12, 204, 60, 252, 3, 195, 51, 243, 15, 207, 63, 255 }, + { 128, 64, 176, 112, 140, 76, 188, 124, 131, 67, 179, 115, 143, 79, 191, 127 }, + { 32, 224, 16, 208, 44, 236, 28, 220, 35, 227, 19, 211, 47, 239, 31, 223 }, + { 160, 96, 144, 80, 172, 108, 156, 92, 163, 99, 147, 83, 175, 111, 159, 95 }, + { 8, 200, 56, 248, 4, 196, 52, 244, 11, 203, 59, 251, 7, 199, 55, 247 }, + { 136, 72, 184, 120, 132, 68, 180, 116, 139, 75, 187, 123, 135, 71, 183, 119 }, + { 40, 232, 24, 216, 36, 228, 20, 212, 43, 235, 27, 219, 39, 231, 23, 215 }, + { 168, 104, 152, 88, 164, 100, 148, 84, 171, 107, 155, 91, 167, 103, 151, 87 }, + { 2, 194, 50, 242, 14, 206, 62, 254, 1, 193, 49, 241, 13, 205, 61, 253 }, + { 130, 66, 178, 114, 142, 78, 190, 126, 129, 65, 177, 113, 141, 77, 189, 125 }, + { 34, 226, 18, 210, 46, 238, 30, 222, 33, 225, 17, 209, 45, 237, 29, 221 }, + { 162, 98, 146, 82, 174, 110, 158, 94, 161, 97, 145, 81, 173, 109, 157, 93 }, + { 10, 202, 58, 250, 6, 198, 54, 246, 9, 201, 57, 249, 5, 197, 53, 245 }, + { 138, 74, 186, 122, 134, 70, 182, 118, 137, 73, 185, 121, 133, 69, 181, 117 }, + { 42, 234, 26, 218, 38, 230, 22, 214, 41, 233, 25, 217, 37, 229, 21, 213 }, + { 170, 106, 154, 90, 166, 102, 150, 86, 169, 105, 153, 89, 165, 101, 149, 85 } +}; + + +/* Declarations for Floyd-Steinberg dithering. + * + * Errors are accumulated into the array fserrors[], at a resolution of + * 1/16th of a pixel count. The error at a given pixel is propagated + * to its not-yet-processed neighbors using the standard F-S fractions, + * ... (here) 7/16 + * 3/16 5/16 1/16 + * We work left-to-right on even rows, right-to-left on odd rows. + * + * We can get away with a single array (holding one row's worth of errors) + * by using it to store the current row's errors at pixel columns not yet + * processed, but the next row's errors at columns already processed. We + * need only a few extra variables to hold the errors immediately around the + * current column. (If we are lucky, those variables are in registers, but + * even if not, they're probably cheaper to access than array elements are.) + * + * The fserrors[] array is indexed [component#][position]. + * We provide (#columns + 2) entries per component; the extra entry at each + * end saves us from special-casing the first and last pixels. + * + * Note: on a wide image, we might not have enough room in a PC's near data + * segment to hold the error array; so it is allocated with alloc_large. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ +#else +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +#endif + +typedef FSERROR FAR * FSERRPTR; /* pointer to error array (in FAR storage!) */ + + +/* Private subobject */ + +#define MAX_Q_COMPS 4 /* max components I can handle */ + +typedef struct { + struct jpeg_color_quantizer pub;/* public fields */ + + /* Initially allocated colormap is saved here */ + JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ + int sv_actual; /* number of entries in use */ + + JSAMPARRAY colorindex; /* Precomputed mapping for speed */ + /* colorindex[i][j] = index of color closest to pixel value j in component i, + * premultiplied as described above. Since colormap indexes must fit into + * JSAMPLEs, the entries of this array will too. + */ + boolean is_padded; /* is the colorindex padded for odither? */ + + int Ncolors[MAX_Q_COMPS];/* # of values alloced to each component */ + + /* Variables for ordered dithering */ + int row_index; /* cur row's vertical index in dither matrix */ + ODITHER_MATRIX_PTR odither[MAX_Q_COMPS];/* one dither array per component */ + + /* Variables for Floyd-Steinberg dithering */ + FSERRPTR fserrors[MAX_Q_COMPS];/* accumulated errors */ + boolean on_odd_row; /* flag to remember which row we are on */ +} my_cquantizer; + +typedef my_cquantizer * my_cquantize_ptr; + + +/* + * Policy-making subroutines for create_colormap and create_colorindex. + * These routines determine the colormap to be used. The rest of the module + * only assumes that the colormap is orthogonal. + * + * * select_ncolors decides how to divvy up the available colors + * among the components. + * * output_value defines the set of representative values for a component. + * * largest_input_value defines the mapping from input values to + * representative values for a component. + * Note that the latter two routines may impose different policies for + * different components, though this is not currently done. + */ + + +LOCAL int +select_ncolors( j_decompress_ptr cinfo, int Ncolors[] ) { +/* Determine allocation of desired colors to components, */ +/* and fill in Ncolors[] array to indicate choice. */ +/* Return value is total number of colors (product of Ncolors[] values). */ + int nc = cinfo->out_color_components;/* number of color components */ + int max_colors = cinfo->desired_number_of_colors; + int total_colors, iroot, i, j; + boolean changed; + long temp; + static const int RGB_order[3] = { RGB_GREEN, RGB_RED, RGB_BLUE }; + + /* We can allocate at least the nc'th root of max_colors per component. */ + /* Compute floor(nc'th root of max_colors). */ + iroot = 1; + do { + iroot++; + temp = iroot; /* set temp = iroot ** nc */ + for ( i = 1; i < nc; i++ ) { + temp *= iroot; + } + } while ( temp <= (long) max_colors );/* repeat till iroot exceeds root */ + iroot--; /* now iroot = floor(root) */ + + /* Must have at least 2 color values per component */ + if ( iroot < 2 ) { + ERREXIT1( cinfo, JERR_QUANT_FEW_COLORS, (int) temp ); + } + + /* Initialize to iroot color values for each component */ + total_colors = 1; + for ( i = 0; i < nc; i++ ) { + Ncolors[i] = iroot; + total_colors *= iroot; + } + /* We may be able to increment the count for one or more components without + * exceeding max_colors, though we know not all can be incremented. + * Sometimes, the first component can be incremented more than once! + * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.) + * In RGB colorspace, try to increment G first, then R, then B. + */ + do { + changed = FALSE; + for ( i = 0; i < nc; i++ ) { + j = ( cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i ); + /* calculate new total_colors if Ncolors[j] is incremented */ + temp = total_colors / Ncolors[j]; + temp *= Ncolors[j] + 1;/* done in long arith to avoid oflo */ + if ( temp > (long) max_colors ) { + break; + } /* won't fit, done with this pass */ + Ncolors[j]++; /* OK, apply the increment */ + total_colors = (int) temp; + changed = TRUE; + } + } while ( changed ); + + return total_colors; +} + + +LOCAL int +output_value( j_decompress_ptr cinfo, int ci, int j, int maxj ) { +/* Return j'th output value, where j will range from 0 to maxj */ +/* The output values must fall in 0..MAXJSAMPLE in increasing order */ +/* We always provide values 0 and MAXJSAMPLE for each component; + * any additional values are equally spaced between these limits. + * (Forcing the upper and lower values to the limits ensures that + * dithering can't produce a color outside the selected gamut.) + */ + return (int) ( ( (INT32) j * MAXJSAMPLE + maxj / 2 ) / maxj ); +} + + +LOCAL int +largest_input_value( j_decompress_ptr cinfo, int ci, int j, int maxj ) { +/* Return largest input value that should map to j'th output value */ +/* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */ +/* Breakpoints are halfway between values returned by output_value */ + return (int) ( ( (INT32) ( 2 * j + 1 ) * MAXJSAMPLE + maxj ) / ( 2 * maxj ) ); +} + + +/* + * Create the colormap. + */ + +LOCAL void +create_colormap( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colormap; /* Created colormap */ + int total_colors; /* Number of distinct output colors */ + int i, j, k, nci, blksize, blkdist, ptr, val; + + /* Select number of colors for each component */ + total_colors = select_ncolors( cinfo, cquantize->Ncolors ); + + /* Report selected color counts */ + if ( cinfo->out_color_components == 3 ) { + TRACEMS4( cinfo, 1, JTRC_QUANT_3_NCOLORS, + total_colors, cquantize->Ncolors[0], + cquantize->Ncolors[1], cquantize->Ncolors[2] ); + } else { + TRACEMS1( cinfo, 1, JTRC_QUANT_NCOLORS, total_colors ); + } + + /* Allocate and fill in the colormap. */ + /* The colors are ordered in the map in standard row-major order, */ + /* i.e. rightmost (highest-indexed) color changes most rapidly. */ + + colormap = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) total_colors, (JDIMENSION) cinfo->out_color_components ); + + /* blksize is number of adjacent repeated entries for a component */ + /* blkdist is distance between groups of identical entries for a component */ + blkdist = total_colors; + + for ( i = 0; i < cinfo->out_color_components; i++ ) { + /* fill in colormap entries for i'th color component */ + nci = cquantize->Ncolors[i];/* # of distinct values for this color */ + blksize = blkdist / nci; + for ( j = 0; j < nci; j++ ) { + /* Compute j'th output value (out of nci) for component */ + val = output_value( cinfo, i, j, nci - 1 ); + /* Fill in all colormap entries that have this value of this component */ + for ( ptr = j * blksize; ptr < total_colors; ptr += blkdist ) { + /* fill in blksize entries beginning at ptr */ + for ( k = 0; k < blksize; k++ ) { + colormap[i][ptr + k] = (JSAMPLE) val; + } + } + } + blkdist = blksize; /* blksize of this color is blkdist of next */ + } + + /* Save the colormap in private storage, + * where it will survive color quantization mode changes. + */ + cquantize->sv_colormap = colormap; + cquantize->sv_actual = total_colors; +} + + +/* + * Create the color index table. + */ + +LOCAL void +create_colorindex( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPROW indexptr; + int i, j, k, nci, blksize, val, pad; + + /* For ordered dither, we pad the color index tables by MAXJSAMPLE in + * each direction (input index values can be -MAXJSAMPLE .. 2*MAXJSAMPLE). + * This is not necessary in the other dithering modes. However, we + * flag whether it was done in case user changes dithering mode. + */ + if ( cinfo->dither_mode == JDITHER_ORDERED ) { + pad = MAXJSAMPLE * 2; + cquantize->is_padded = TRUE; + } else { + pad = 0; + cquantize->is_padded = FALSE; + } + + cquantize->colorindex = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) ( MAXJSAMPLE + 1 + pad ), + (JDIMENSION) cinfo->out_color_components ); + + /* blksize is number of adjacent repeated entries for a component */ + blksize = cquantize->sv_actual; + + for ( i = 0; i < cinfo->out_color_components; i++ ) { + /* fill in colorindex entries for i'th color component */ + nci = cquantize->Ncolors[i];/* # of distinct values for this color */ + blksize = blksize / nci; + + /* adjust colorindex pointers to provide padding at negative indexes. */ + if ( pad ) { + cquantize->colorindex[i] += MAXJSAMPLE; + } + + /* in loop, val = index of current output value, */ + /* and k = largest j that maps to current val */ + indexptr = cquantize->colorindex[i]; + val = 0; + k = largest_input_value( cinfo, i, 0, nci - 1 ); + for ( j = 0; j <= MAXJSAMPLE; j++ ) { + while ( j > k ) {/* advance val if past boundary */ + k = largest_input_value( cinfo, i, ++val, nci - 1 ); + } + /* premultiply so that no multiplication needed in main processing */ + indexptr[j] = (JSAMPLE) ( val * blksize ); + } + /* Pad at both ends if necessary */ + if ( pad ) { + for ( j = 1; j <= MAXJSAMPLE; j++ ) { + indexptr[-j] = indexptr[0]; + indexptr[MAXJSAMPLE + j] = indexptr[MAXJSAMPLE]; + } + } + } +} + + +/* + * Create an ordered-dither array for a component having ncolors + * distinct output values. + */ + +LOCAL ODITHER_MATRIX_PTR +make_odither_array( j_decompress_ptr cinfo, int ncolors ) { + ODITHER_MATRIX_PTR odither; + int j, k; + INT32 num, den; + + odither = (ODITHER_MATRIX_PTR) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( ODITHER_MATRIX ) ); + /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1). + * Hence the dither value for the matrix cell with fill order f + * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1). + * On 16-bit-int machine, be careful to avoid overflow. + */ + den = 2 * ODITHER_CELLS * ( (INT32) ( ncolors - 1 ) ); + for ( j = 0; j < ODITHER_SIZE; j++ ) { + for ( k = 0; k < ODITHER_SIZE; k++ ) { + num = ( (INT32) ( ODITHER_CELLS - 1 - 2 * ( (int)base_dither_matrix[j][k] ) ) ) + * MAXJSAMPLE; + /* Ensure round towards zero despite C's lack of consistency + * about rounding negative values in integer division... + */ + odither[j][k] = (int) ( num < 0 ? -( ( -num ) / den ) : num / den ); + } + } + return odither; +} + + +/* + * Create the ordered-dither tables. + * Components having the same number of representative colors may + * share a dither table. + */ + +LOCAL void +create_odither_tables( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + ODITHER_MATRIX_PTR odither; + int i, j, nci; + + for ( i = 0; i < cinfo->out_color_components; i++ ) { + nci = cquantize->Ncolors[i];/* # of distinct values for this color */ + odither = NULL; /* search for matching prior component */ + for ( j = 0; j < i; j++ ) { + if ( nci == cquantize->Ncolors[j] ) { + odither = cquantize->odither[j]; + break; + } + } + if ( odither == NULL ) {/* need a new table? */ + odither = make_odither_array( cinfo, nci ); + } + cquantize->odither[i] = odither; + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF void +color_quantize( j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows ) { +/* General case, no dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colorindex = cquantize->colorindex; + register int pixcode, ci; + register JSAMPROW ptrin, ptrout; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + register int nc = cinfo->out_color_components; + + for ( row = 0; row < num_rows; row++ ) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for ( col = width; col > 0; col-- ) { + pixcode = 0; + for ( ci = 0; ci < nc; ci++ ) { + pixcode += GETJSAMPLE( colorindex[ci][GETJSAMPLE( *ptrin++ )] ); + } + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF void +color_quantize3( j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows ) { +/* Fast path for out_color_components==3, no dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW ptrin, ptrout; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for ( row = 0; row < num_rows; row++ ) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for ( col = width; col > 0; col-- ) { + pixcode = GETJSAMPLE( colorindex0[GETJSAMPLE( *ptrin++ )] ); + pixcode += GETJSAMPLE( colorindex1[GETJSAMPLE( *ptrin++ )] ); + pixcode += GETJSAMPLE( colorindex2[GETJSAMPLE( *ptrin++ )] ); + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF void +quantize_ord_dither( j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows ) { +/* General case, with ordered dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + int * dither; /* points to active row of dither matrix */ + int row_index, col_index;/* current indexes into dither matrix */ + int nc = cinfo->out_color_components; + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for ( row = 0; row < num_rows; row++ ) { + /* Initialize output values to 0 so can process components separately */ + jzero_far( (void FAR *) output_buf[row], + (size_t) ( width * SIZEOF( JSAMPLE ) ) ); + row_index = cquantize->row_index; + for ( ci = 0; ci < nc; ci++ ) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + colorindex_ci = cquantize->colorindex[ci]; + dither = cquantize->odither[ci][row_index]; + col_index = 0; + + for ( col = width; col > 0; col-- ) { + /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, + * select output value, accumulate into output code for this pixel. + * Range-limiting need not be done explicitly, as we have extended + * the colorindex table to produce the right answers for out-of-range + * inputs. The maximum dither is +- MAXJSAMPLE; this sets the + * required amount of padding. + */ + *output_ptr += colorindex_ci[GETJSAMPLE( *input_ptr ) + dither[col_index]]; + input_ptr += nc; + output_ptr++; + col_index = ( col_index + 1 ) & ODITHER_MASK; + } + } + /* Advance row index for next row */ + row_index = ( row_index + 1 ) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF void +quantize3_ord_dither( j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows ) { +/* Fast path for out_color_components==3, with ordered dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int * dither0; /* points to active row of dither matrix */ + int * dither1; + int * dither2; + int row_index, col_index;/* current indexes into dither matrix */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for ( row = 0; row < num_rows; row++ ) { + row_index = cquantize->row_index; + input_ptr = input_buf[row]; + output_ptr = output_buf[row]; + dither0 = cquantize->odither[0][row_index]; + dither1 = cquantize->odither[1][row_index]; + dither2 = cquantize->odither[2][row_index]; + col_index = 0; + + for ( col = width; col > 0; col-- ) { + pixcode = GETJSAMPLE( colorindex0[GETJSAMPLE( *input_ptr++ ) + + dither0[col_index]] ); + pixcode += GETJSAMPLE( colorindex1[GETJSAMPLE( *input_ptr++ ) + + dither1[col_index]] ); + pixcode += GETJSAMPLE( colorindex2[GETJSAMPLE( *input_ptr++ ) + + dither2[col_index]] ); + *output_ptr++ = (JSAMPLE) pixcode; + col_index = ( col_index + 1 ) & ODITHER_MASK; + } + row_index = ( row_index + 1 ) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF void +quantize_fs_dither( j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows ) { +/* General case, with Floyd-Steinberg dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register LOCFSERROR cur;/* current error or pixel value */ + LOCFSERROR belowerr; /* error for pixel below cur */ + LOCFSERROR bpreverr; /* error for below/prev col */ + LOCFSERROR bnexterr; /* error for below/next col */ + LOCFSERROR delta; + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + JSAMPROW colormap_ci; + int pixcode; + int nc = cinfo->out_color_components; + int dir; /* 1 for left-to-right, -1 for right-to-left */ + int dirnc; /* dir * nc */ + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE * range_limit = cinfo->sample_range_limit; + SHIFT_TEMPS + + for ( row = 0; row < num_rows; row++ ) { + /* Initialize output values to 0 so can process components separately */ + jzero_far( (void FAR *) output_buf[row], + (size_t) ( width * SIZEOF( JSAMPLE ) ) ); + for ( ci = 0; ci < nc; ci++ ) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + if ( cquantize->on_odd_row ) { + /* work right to left in this row */ + input_ptr += ( width - 1 ) * nc;/* so point to rightmost pixel */ + output_ptr += width - 1; + dir = -1; + dirnc = -nc; + errorptr = cquantize->fserrors[ci] + ( width + 1 );/* => entry after last column */ + } else { + /* work left to right in this row */ + dir = 1; + dirnc = nc; + errorptr = cquantize->fserrors[ci];/* => entry before first column */ + } + colorindex_ci = cquantize->colorindex[ci]; + colormap_ci = cquantize->sv_colormap[ci]; + /* Preset error values: no error propagated to first pixel from left */ + cur = 0; + /* and no error propagated to row below yet */ + belowerr = bpreverr = 0; + + for ( col = width; col > 0; col-- ) { + /* cur holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur = RIGHT_SHIFT( cur + errorptr[dir] + 8, 4 ); + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE; this sets the required size + * of the range_limit array. + */ + cur += GETJSAMPLE( *input_ptr ); + cur = GETJSAMPLE( range_limit[cur] ); + /* Select output value, accumulate into output code for this pixel */ + pixcode = GETJSAMPLE( colorindex_ci[cur] ); + *output_ptr += (JSAMPLE) pixcode; + /* Compute actual representation error at this pixel */ + /* Note: we can do this even though we don't have the final */ + /* pixel code, because the colormap is orthogonal. */ + cur -= GETJSAMPLE( colormap_ci[pixcode] ); + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + bnexterr = cur; + delta = cur * 2; + cur += delta;/* form error * 3 */ + errorptr[0] = (FSERROR) ( bpreverr + cur ); + cur += delta;/* form error * 5 */ + bpreverr = belowerr + cur; + belowerr = bnexterr; + cur += delta;/* form error * 7 */ + /* At this point cur contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + input_ptr += dirnc;/* advance input ptr to next column */ + output_ptr += dir;/* advance output ptr to next column */ + errorptr += dir;/* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error value into the + * final fserrors[] entry. Note we need not unload belowerr because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr;/* unload prev err into array */ + } + cquantize->on_odd_row = ( cquantize->on_odd_row ? FALSE : TRUE ); + } +} + + +/* + * Allocate workspace for Floyd-Steinberg errors. + */ + +LOCAL void +alloc_fs_workspace( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + arraysize = (size_t) ( ( cinfo->output_width + 2 ) * SIZEOF( FSERROR ) ); + for ( i = 0; i < cinfo->out_color_components; i++ ) { + cquantize->fserrors[i] = (FSERRPTR) + ( *cinfo->mem->alloc_large )( (j_common_ptr) cinfo, JPOOL_IMAGE, arraysize ); + } +} + + +/* + * Initialize for one-pass color quantization. + */ + +METHODDEF void +start_pass_1_quant( j_decompress_ptr cinfo, boolean is_pre_scan ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + /* Install my colormap. */ + cinfo->colormap = cquantize->sv_colormap; + cinfo->actual_number_of_colors = cquantize->sv_actual; + + /* Initialize for desired dithering mode. */ + switch ( cinfo->dither_mode ) { + case JDITHER_NONE: + if ( cinfo->out_color_components == 3 ) { + cquantize->pub.color_quantize = color_quantize3; + } else { + cquantize->pub.color_quantize = color_quantize; + } + break; + case JDITHER_ORDERED: + if ( cinfo->out_color_components == 3 ) { + cquantize->pub.color_quantize = quantize3_ord_dither; + } else { + cquantize->pub.color_quantize = quantize_ord_dither; + } + cquantize->row_index = 0;/* initialize state for ordered dither */ + /* If user changed to ordered dither from another mode, + * we must recreate the color index table with padding. + * This will cost extra space, but probably isn't very likely. + */ + if ( !cquantize->is_padded ) { + create_colorindex( cinfo ); + } + /* Create ordered-dither tables if we didn't already. */ + if ( cquantize->odither[0] == NULL ) { + create_odither_tables( cinfo ); + } + break; + case JDITHER_FS: + cquantize->pub.color_quantize = quantize_fs_dither; + cquantize->on_odd_row = FALSE;/* initialize state for F-S dither */ + /* Allocate Floyd-Steinberg workspace if didn't already. */ + if ( cquantize->fserrors[0] == NULL ) { + alloc_fs_workspace( cinfo ); + } + /* Initialize the propagated errors to zero. */ + arraysize = (size_t) ( ( cinfo->output_width + 2 ) * SIZEOF( FSERROR ) ); + for ( i = 0; i < cinfo->out_color_components; i++ ) { + jzero_far( (void FAR *) cquantize->fserrors[i], arraysize ); + } + break; + default: + ERREXIT( cinfo, JERR_NOT_COMPILED ); + break; + } +} + + +/* + * Finish up at the end of the pass. + */ + +METHODDEF void +finish_pass_1_quant( j_decompress_ptr cinfo ) { + /* no work in 1-pass case */ +} + + +/* + * Switch to a new external colormap between output passes. + * Shouldn't get to this module! + */ + +METHODDEF void +new_color_map_1_quant( j_decompress_ptr cinfo ) { + ERREXIT( cinfo, JERR_MODE_CHANGE ); +} + + +/* + * Module initialization routine for 1-pass color quantization. + */ + +GLOBAL void +jinit_1pass_quantizer( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize; + + cquantize = (my_cquantize_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_cquantizer ) ); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_1_quant; + cquantize->pub.finish_pass = finish_pass_1_quant; + cquantize->pub.new_color_map = new_color_map_1_quant; + cquantize->fserrors[0] = NULL;/* Flag FS workspace not allocated */ + cquantize->odither[0] = NULL;/* Also flag odither arrays not allocated */ + + /* Make sure my internal arrays won't overflow */ + if ( cinfo->out_color_components > MAX_Q_COMPS ) { + ERREXIT1( cinfo, JERR_QUANT_COMPONENTS, MAX_Q_COMPS ); + } + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if ( cinfo->desired_number_of_colors > ( MAXJSAMPLE + 1 ) ) { + ERREXIT1( cinfo, JERR_QUANT_MANY_COLORS, MAXJSAMPLE + 1 ); + } + + /* Create the colormap and color index table. */ + create_colormap( cinfo ); + create_colorindex( cinfo ); + + /* Allocate Floyd-Steinberg workspace now if requested. + * We do this now since it is FAR storage and may affect the memory + * manager's space calculations. If the user changes to FS dither + * mode in a later pass, we will allocate the space then, and will + * possibly overrun the max_memory_to_use setting. + */ + if ( cinfo->dither_mode == JDITHER_FS ) { + alloc_fs_workspace( cinfo ); + } +} + +#endif /* QUANT_1PASS_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jquant2.cpp b/neo/renderer/jpeg-6/jquant2.cpp new file mode 100644 index 00000000..2e9a6e05 --- /dev/null +++ b/neo/renderer/jpeg-6/jquant2.cpp @@ -0,0 +1,1357 @@ +/* + * jquant2.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 2-pass color quantization (color mapping) routines. + * These routines provide selection of a custom color map for an image, + * followed by mapping of the image to that color map, with optional + * Floyd-Steinberg dithering. + * It is also possible to use just the second pass to map to an arbitrary + * externally-given color map. + * + * Note: ordered dithering is not supported, since there isn't any fast + * way to compute intercolor distances; it's unclear that ordered dither's + * fundamental assumptions even hold with an irregularly spaced color map. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_2PASS_SUPPORTED + + +/* + * This module implements the well-known Heckbert paradigm for color + * quantization. Most of the ideas used here can be traced back to + * Heckbert's seminal paper + * Heckbert, Paul. "Color Image Quantization for Frame Buffer Display", + * Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304. + * + * In the first pass over the image, we accumulate a histogram showing the + * usage count of each possible color. To keep the histogram to a reasonable + * size, we reduce the precision of the input; typical practice is to retain + * 5 or 6 bits per color, so that 8 or 4 different input values are counted + * in the same histogram cell. + * + * Next, the color-selection step begins with a box representing the whole + * color space, and repeatedly splits the "largest" remaining box until we + * have as many boxes as desired colors. Then the mean color in each + * remaining box becomes one of the possible output colors. + * + * The second pass over the image maps each input pixel to the closest output + * color (optionally after applying a Floyd-Steinberg dithering correction). + * This mapping is logically trivial, but making it go fast enough requires + * considerable care. + * + * Heckbert-style quantizers vary a good deal in their policies for choosing + * the "largest" box and deciding where to cut it. The particular policies + * used here have proved out well in experimental comparisons, but better ones + * may yet be found. + * + * In earlier versions of the IJG code, this module quantized in YCbCr color + * space, processing the raw upsampled data without a color conversion step. + * This allowed the color conversion math to be done only once per colormap + * entry, not once per pixel. However, that optimization precluded other + * useful optimizations (such as merging color conversion with upsampling) + * and it also interfered with desired capabilities such as quantizing to an + * externally-supplied colormap. We have therefore abandoned that approach. + * The present code works in the post-conversion color space, typically RGB. + * + * To improve the visual quality of the results, we actually work in scaled + * RGB space, giving G distances more weight than R, and R in turn more than + * B. To do everything in integer math, we must use integer scale factors. + * The 2/3/1 scale factors used here correspond loosely to the relative + * weights of the colors in the NTSC grayscale equation. + * If you want to use this code to quantize a non-RGB color space, you'll + * probably need to change these scale factors. + */ + +#define R_SCALE 2 /* scale R distances by this much */ +#define G_SCALE 3 /* scale G distances by this much */ +#define B_SCALE 1 /* and B by this much */ + +/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined + * in jmorecfg.h. As the code stands, it will do the right thing for R,G,B + * and B,G,R orders. If you define some other weird order in jmorecfg.h, + * you'll get compile errors until you extend this logic. In that case + * you'll probably want to tweak the histogram sizes too. + */ + +#if RGB_RED == 0 +#define C0_SCALE R_SCALE +#endif +#if RGB_BLUE == 0 +#define C0_SCALE B_SCALE +#endif +#if RGB_GREEN == 1 +#define C1_SCALE G_SCALE +#endif +#if RGB_RED == 2 +#define C2_SCALE R_SCALE +#endif +#if RGB_BLUE == 2 +#define C2_SCALE B_SCALE +#endif + + +/* + * First we have the histogram data structure and routines for creating it. + * + * The number of bits of precision can be adjusted by changing these symbols. + * We recommend keeping 6 bits for G and 5 each for R and B. + * If you have plenty of memory and cycles, 6 bits all around gives marginally + * better results; if you are short of memory, 5 bits all around will save + * some space but degrade the results. + * To maintain a fully accurate histogram, we'd need to allocate a "long" + * (preferably unsigned long) for each cell. In practice this is overkill; + * we can get by with 16 bits per cell. Few of the cell counts will overflow, + * and clamping those that do overflow to the maximum value will give close- + * enough results. This reduces the recommended histogram size from 256Kb + * to 128Kb, which is a useful savings on PC-class machines. + * (In the second pass the histogram space is re-used for pixel mapping data; + * in that capacity, each cell must be able to store zero to the number of + * desired colors. 16 bits/cell is plenty for that too.) + * Since the JPEG code is intended to run in small memory model on 80x86 + * machines, we can't just allocate the histogram in one chunk. Instead + * of a true 3-D array, we use a row of pointers to 2-D arrays. Each + * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and + * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that + * on 80x86 machines, the pointer row is in near memory but the actual + * arrays are in far memory (same arrangement as we use for image arrays). + */ + +#define MAXNUMCOLORS ( MAXJSAMPLE + 1 ) /* maximum size of colormap */ + +/* These will do the right thing for either R,G,B or B,G,R color order, + * but you may not like the results for other color orders. + */ +#define HIST_C0_BITS 5 /* bits of precision in R/B histogram */ +#define HIST_C1_BITS 6 /* bits of precision in G histogram */ +#define HIST_C2_BITS 5 /* bits of precision in B/R histogram */ + +/* Number of elements along histogram axes. */ +#define HIST_C0_ELEMS ( 1 << HIST_C0_BITS ) +#define HIST_C1_ELEMS ( 1 << HIST_C1_BITS ) +#define HIST_C2_ELEMS ( 1 << HIST_C2_BITS ) + +/* These are the amounts to shift an input value to get a histogram index. */ +#define C0_SHIFT ( BITS_IN_JSAMPLE - HIST_C0_BITS ) +#define C1_SHIFT ( BITS_IN_JSAMPLE - HIST_C1_BITS ) +#define C2_SHIFT ( BITS_IN_JSAMPLE - HIST_C2_BITS ) + + +typedef UINT16 histcell; /* histogram cell; prefer an unsigned type */ + +typedef histcell FAR * histptr; /* for pointers to histogram cells */ + +typedef histcell hist1d[HIST_C2_ELEMS]; /* typedefs for the array */ +typedef hist1d FAR * hist2d; /* type for the 2nd-level pointers */ +typedef hist2d * hist3d; /* type for top-level pointer */ + + +/* Declarations for Floyd-Steinberg dithering. + * + * Errors are accumulated into the array fserrors[], at a resolution of + * 1/16th of a pixel count. The error at a given pixel is propagated + * to its not-yet-processed neighbors using the standard F-S fractions, + * ... (here) 7/16 + * 3/16 5/16 1/16 + * We work left-to-right on even rows, right-to-left on odd rows. + * + * We can get away with a single array (holding one row's worth of errors) + * by using it to store the current row's errors at pixel columns not yet + * processed, but the next row's errors at columns already processed. We + * need only a few extra variables to hold the errors immediately around the + * current column. (If we are lucky, those variables are in registers, but + * even if not, they're probably cheaper to access than array elements are.) + * + * The fserrors[] array has (#columns + 2) entries; the extra entry at + * each end saves us from special-casing the first and last pixels. + * Each entry is three values long, one value for each color component. + * + * Note: on a wide image, we might not have enough room in a PC's near data + * segment to hold the error array; so it is allocated with alloc_large. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ +#else +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +#endif + +typedef FSERROR FAR * FSERRPTR; /* pointer to error array (in FAR storage!) */ + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_quantizer pub;/* public fields */ + + /* Space for the eventually created colormap is stashed here */ + JSAMPARRAY sv_colormap; /* colormap allocated at init time */ + int desired; /* desired # of colors = size of colormap */ + + /* Variables for accumulating image statistics */ + hist3d histogram; /* pointer to the histogram */ + + boolean needs_zeroed; /* TRUE if next pass must zero histogram */ + + /* Variables for Floyd-Steinberg dithering */ + FSERRPTR fserrors; /* accumulated errors */ + boolean on_odd_row; /* flag to remember which row we are on */ + int * error_limiter; /* table for clamping the applied error */ +} my_cquantizer; + +typedef my_cquantizer * my_cquantize_ptr; + + +/* + * Prescan some rows of pixels. + * In this module the prescan simply updates the histogram, which has been + * initialized to zeroes by start_pass. + * An output_buf parameter is required by the method signature, but no data + * is actually output (in fact the buffer controller is probably passing a + * NULL pointer). + */ + +METHODDEF void +prescan_quantize( j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register JSAMPROW ptr; + register histptr histp; + register hist3d histogram = cquantize->histogram; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for ( row = 0; row < num_rows; row++ ) { + ptr = input_buf[row]; + for ( col = width; col > 0; col-- ) { + /* get pixel value and index into the histogram */ + histp = &histogram[GETJSAMPLE( ptr[0] ) >> C0_SHIFT] + [GETJSAMPLE( ptr[1] ) >> C1_SHIFT] + [GETJSAMPLE( ptr[2] ) >> C2_SHIFT]; + /* increment, check for overflow and undo increment if so. */ + if ( ++ ( *histp ) <= 0 ) { + ( *histp )--; + } + ptr += 3; + } + } +} + + +/* + * Next we have the really interesting routines: selection of a colormap + * given the completed histogram. + * These routines work with a list of "boxes", each representing a rectangular + * subset of the input color space (to histogram precision). + */ + +typedef struct { + /* The bounds of the box (inclusive); expressed as histogram indexes */ + int c0min, c0max; + int c1min, c1max; + int c2min, c2max; + /* The volume (actually 2-norm) of the box */ + INT32 volume; + /* The number of nonzero histogram cells within this box */ + long colorcount; +} box; + +typedef box * boxptr; + + +LOCAL boxptr +find_biggest_color_pop( boxptr boxlist, int numboxes ) { +/* Find the splittable box with the largest color population */ +/* Returns NULL if no splittable boxes remain */ + register boxptr boxp; + register int i; + register long maxc = 0; + boxptr which = NULL; + + for ( i = 0, boxp = boxlist; i < numboxes; i++, boxp++ ) { + if ( ( boxp->colorcount > maxc ) && ( boxp->volume > 0 ) ) { + which = boxp; + maxc = boxp->colorcount; + } + } + return which; +} + + +LOCAL boxptr +find_biggest_volume( boxptr boxlist, int numboxes ) { +/* Find the splittable box with the largest (scaled) volume */ +/* Returns NULL if no splittable boxes remain */ + register boxptr boxp; + register int i; + register INT32 maxv = 0; + boxptr which = NULL; + + for ( i = 0, boxp = boxlist; i < numboxes; i++, boxp++ ) { + if ( boxp->volume > maxv ) { + which = boxp; + maxv = boxp->volume; + } + } + return which; +} + + +LOCAL void +update_box( j_decompress_ptr cinfo, boxptr boxp ) { +/* Shrink the min/max bounds of a box to enclose only nonzero elements, */ +/* and recompute its volume and population */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0, c1, c2; + int c0min, c0max, c1min, c1max, c2min, c2max; + INT32 dist0, dist1, dist2; + long ccount; + + c0min = boxp->c0min; + c0max = boxp->c0max; + c1min = boxp->c1min; + c1max = boxp->c1max; + c2min = boxp->c2min; + c2max = boxp->c2max; + + if ( c0max > c0min ) { + for ( c0 = c0min; c0 <= c0max; c0++ ) { + for ( c1 = c1min; c1 <= c1max; c1++ ) { + histp = &histogram[c0][c1][c2min]; + for ( c2 = c2min; c2 <= c2max; c2++ ) { + if ( *histp++ != 0 ) { + boxp->c0min = c0min = c0; + goto have_c0min; + } + } + } + } + } +have_c0min: + if ( c0max > c0min ) { + for ( c0 = c0max; c0 >= c0min; c0-- ) { + for ( c1 = c1min; c1 <= c1max; c1++ ) { + histp = &histogram[c0][c1][c2min]; + for ( c2 = c2min; c2 <= c2max; c2++ ) { + if ( *histp++ != 0 ) { + boxp->c0max = c0max = c0; + goto have_c0max; + } + } + } + } + } +have_c0max: + if ( c1max > c1min ) { + for ( c1 = c1min; c1 <= c1max; c1++ ) { + for ( c0 = c0min; c0 <= c0max; c0++ ) { + histp = &histogram[c0][c1][c2min]; + for ( c2 = c2min; c2 <= c2max; c2++ ) { + if ( *histp++ != 0 ) { + boxp->c1min = c1min = c1; + goto have_c1min; + } + } + } + } + } +have_c1min: + if ( c1max > c1min ) { + for ( c1 = c1max; c1 >= c1min; c1-- ) { + for ( c0 = c0min; c0 <= c0max; c0++ ) { + histp = &histogram[c0][c1][c2min]; + for ( c2 = c2min; c2 <= c2max; c2++ ) { + if ( *histp++ != 0 ) { + boxp->c1max = c1max = c1; + goto have_c1max; + } + } + } + } + } +have_c1max: + if ( c2max > c2min ) { + for ( c2 = c2min; c2 <= c2max; c2++ ) { + for ( c0 = c0min; c0 <= c0max; c0++ ) { + histp = &histogram[c0][c1min][c2]; + for ( c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS ) { + if ( *histp != 0 ) { + boxp->c2min = c2min = c2; + goto have_c2min; + } + } + } + } + } +have_c2min: + if ( c2max > c2min ) { + for ( c2 = c2max; c2 >= c2min; c2-- ) { + for ( c0 = c0min; c0 <= c0max; c0++ ) { + histp = &histogram[c0][c1min][c2]; + for ( c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS ) { + if ( *histp != 0 ) { + boxp->c2max = c2max = c2; + goto have_c2max; + } + } + } + } + } +have_c2max: + + /* Update box volume. + * We use 2-norm rather than real volume here; this biases the method + * against making long narrow boxes, and it has the side benefit that + * a box is splittable iff norm > 0. + * Since the differences are expressed in histogram-cell units, + * we have to shift back to JSAMPLE units to get consistent distances; + * after which, we scale according to the selected distance scale factors. + */ + dist0 = ( ( c0max - c0min ) << C0_SHIFT ) * C0_SCALE; + dist1 = ( ( c1max - c1min ) << C1_SHIFT ) * C1_SCALE; + dist2 = ( ( c2max - c2min ) << C2_SHIFT ) * C2_SCALE; + boxp->volume = dist0 * dist0 + dist1 * dist1 + dist2 * dist2; + + /* Now scan remaining volume of box and compute population */ + ccount = 0; + for ( c0 = c0min; c0 <= c0max; c0++ ) { + for ( c1 = c1min; c1 <= c1max; c1++ ) { + histp = &histogram[c0][c1][c2min]; + for ( c2 = c2min; c2 <= c2max; c2++, histp++ ) { + if ( *histp != 0 ) { + ccount++; + } + } + } + } + boxp->colorcount = ccount; +} + + +LOCAL int +median_cut( j_decompress_ptr cinfo, boxptr boxlist, int numboxes, + int desired_colors ) { +/* Repeatedly select and split the largest box until we have enough boxes */ + int n, lb; + int c0, c1, c2, cmax; + register boxptr b1, b2; + + while ( numboxes < desired_colors ) { + /* Select box to split. + * Current algorithm: by population for first half, then by volume. + */ + if ( numboxes * 2 <= desired_colors ) { + b1 = find_biggest_color_pop( boxlist, numboxes ); + } else { + b1 = find_biggest_volume( boxlist, numboxes ); + } + if ( b1 == NULL ) {/* no splittable boxes left! */ + break; + } + b2 = &boxlist[numboxes];/* where new box will go */ + /* Copy the color bounds to the new box. */ + b2->c0max = b1->c0max; + b2->c1max = b1->c1max; + b2->c2max = b1->c2max; + b2->c0min = b1->c0min; + b2->c1min = b1->c1min; + b2->c2min = b1->c2min; + /* Choose which axis to split the box on. + * Current algorithm: longest scaled axis. + * See notes in update_box about scaling distances. + */ + c0 = ( ( b1->c0max - b1->c0min ) << C0_SHIFT ) * C0_SCALE; + c1 = ( ( b1->c1max - b1->c1min ) << C1_SHIFT ) * C1_SCALE; + c2 = ( ( b1->c2max - b1->c2min ) << C2_SHIFT ) * C2_SCALE; + /* We want to break any ties in favor of green, then red, blue last. + * This code does the right thing for R,G,B or B,G,R color orders only. + */ +#if RGB_RED == 0 + cmax = c1; + n = 1; + if ( c0 > cmax ) { + cmax = c0; + n = 0; + } + if ( c2 > cmax ) { + n = 2; + } +#else + cmax = c1; + n = 1; + if ( c2 > cmax ) { + cmax = c2; + n = 2; + } + if ( c0 > cmax ) { + n = 0; + } +#endif + /* Choose split point along selected axis, and update box bounds. + * Current algorithm: split at halfway point. + * (Since the box has been shrunk to minimum volume, + * any split will produce two nonempty subboxes.) + * Note that lb value is max for lower box, so must be < old max. + */ + switch ( n ) { + case 0: + lb = ( b1->c0max + b1->c0min ) / 2; + b1->c0max = lb; + b2->c0min = lb + 1; + break; + case 1: + lb = ( b1->c1max + b1->c1min ) / 2; + b1->c1max = lb; + b2->c1min = lb + 1; + break; + case 2: + lb = ( b1->c2max + b1->c2min ) / 2; + b1->c2max = lb; + b2->c2min = lb + 1; + break; + } + /* Update stats for boxes */ + update_box( cinfo, b1 ); + update_box( cinfo, b2 ); + numboxes++; + } + return numboxes; +} + + +LOCAL void +compute_color( j_decompress_ptr cinfo, boxptr boxp, int icolor ) { +/* Compute representative color for a box, put it in colormap[icolor] */ +/* Current algorithm: mean weighted by pixels (not colors) */ +/* Note it is important to get the rounding correct! */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0, c1, c2; + int c0min, c0max, c1min, c1max, c2min, c2max; + long count; + long total = 0; + long c0total = 0; + long c1total = 0; + long c2total = 0; + + c0min = boxp->c0min; + c0max = boxp->c0max; + c1min = boxp->c1min; + c1max = boxp->c1max; + c2min = boxp->c2min; + c2max = boxp->c2max; + + for ( c0 = c0min; c0 <= c0max; c0++ ) { + for ( c1 = c1min; c1 <= c1max; c1++ ) { + histp = &histogram[c0][c1][c2min]; + for ( c2 = c2min; c2 <= c2max; c2++ ) { + if ( ( count = *histp++ ) != 0 ) { + total += count; + c0total += ( ( c0 << C0_SHIFT ) + ( ( 1 << C0_SHIFT ) >> 1 ) ) * count; + c1total += ( ( c1 << C1_SHIFT ) + ( ( 1 << C1_SHIFT ) >> 1 ) ) * count; + c2total += ( ( c2 << C2_SHIFT ) + ( ( 1 << C2_SHIFT ) >> 1 ) ) * count; + } + } + } + } + + cinfo->colormap[0][icolor] = (JSAMPLE) ( ( c0total + ( total >> 1 ) ) / total ); + cinfo->colormap[1][icolor] = (JSAMPLE) ( ( c1total + ( total >> 1 ) ) / total ); + cinfo->colormap[2][icolor] = (JSAMPLE) ( ( c2total + ( total >> 1 ) ) / total ); +} + + +LOCAL void +select_colors( j_decompress_ptr cinfo, int desired_colors ) { +/* Master routine for color selection */ + boxptr boxlist; + int numboxes; + int i; + + /* Allocate workspace for box list */ + boxlist = (boxptr) ( *cinfo->mem->alloc_small ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF( box ) ); + /* Initialize one box containing whole space */ + numboxes = 1; + boxlist[0].c0min = 0; + boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT; + boxlist[0].c1min = 0; + boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT; + boxlist[0].c2min = 0; + boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT; + /* Shrink it to actually-used volume and set its statistics */ + update_box( cinfo, &boxlist[0] ); + /* Perform median-cut to produce final box list */ + numboxes = median_cut( cinfo, boxlist, numboxes, desired_colors ); + /* Compute the representative color for each box, fill colormap */ + for ( i = 0; i < numboxes; i++ ) { + compute_color( cinfo, &boxlist[i], i ); + } + cinfo->actual_number_of_colors = numboxes; + TRACEMS1( cinfo, 1, JTRC_QUANT_SELECTED, numboxes ); +} + + +/* + * These routines are concerned with the time-critical task of mapping input + * colors to the nearest color in the selected colormap. + * + * We re-use the histogram space as an "inverse color map", essentially a + * cache for the results of nearest-color searches. All colors within a + * histogram cell will be mapped to the same colormap entry, namely the one + * closest to the cell's center. This may not be quite the closest entry to + * the actual input color, but it's almost as good. A zero in the cache + * indicates we haven't found the nearest color for that cell yet; the array + * is cleared to zeroes before starting the mapping pass. When we find the + * nearest color for a cell, its colormap index plus one is recorded in the + * cache for future use. The pass2 scanning routines call fill_inverse_cmap + * when they need to use an unfilled entry in the cache. + * + * Our method of efficiently finding nearest colors is based on the "locally + * sorted search" idea described by Heckbert and on the incremental distance + * calculation described by Spencer W. Thomas in chapter III.1 of Graphics + * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that + * the distances from a given colormap entry to each cell of the histogram can + * be computed quickly using an incremental method: the differences between + * distances to adjacent cells themselves differ by a constant. This allows a + * fairly fast implementation of the "brute force" approach of computing the + * distance from every colormap entry to every histogram cell. Unfortunately, + * it needs a work array to hold the best-distance-so-far for each histogram + * cell (because the inner loop has to be over cells, not colormap entries). + * The work array elements have to be INT32s, so the work array would need + * 256Kb at our recommended precision. This is not feasible in DOS machines. + * + * To get around these problems, we apply Thomas' method to compute the + * nearest colors for only the cells within a small subbox of the histogram. + * The work array need be only as big as the subbox, so the memory usage + * problem is solved. Furthermore, we need not fill subboxes that are never + * referenced in pass2; many images use only part of the color gamut, so a + * fair amount of work is saved. An additional advantage of this + * approach is that we can apply Heckbert's locality criterion to quickly + * eliminate colormap entries that are far away from the subbox; typically + * three-fourths of the colormap entries are rejected by Heckbert's criterion, + * and we need not compute their distances to individual cells in the subbox. + * The speed of this approach is heavily influenced by the subbox size: too + * small means too much overhead, too big loses because Heckbert's criterion + * can't eliminate as many colormap entries. Empirically the best subbox + * size seems to be about 1/512th of the histogram (1/8th in each direction). + * + * Thomas' article also describes a refined method which is asymptotically + * faster than the brute-force method, but it is also far more complex and + * cannot efficiently be applied to small subboxes. It is therefore not + * useful for programs intended to be portable to DOS machines. On machines + * with plenty of memory, filling the whole histogram in one shot with Thomas' + * refined method might be faster than the present code --- but then again, + * it might not be any faster, and it's certainly more complicated. + */ + + +/* log2(histogram cells in update box) for each axis; this can be adjusted */ +#define BOX_C0_LOG ( HIST_C0_BITS - 3 ) +#define BOX_C1_LOG ( HIST_C1_BITS - 3 ) +#define BOX_C2_LOG ( HIST_C2_BITS - 3 ) + +#define BOX_C0_ELEMS ( 1 << BOX_C0_LOG ) /* # of hist cells in update box */ +#define BOX_C1_ELEMS ( 1 << BOX_C1_LOG ) +#define BOX_C2_ELEMS ( 1 << BOX_C2_LOG ) + +#define BOX_C0_SHIFT ( C0_SHIFT + BOX_C0_LOG ) +#define BOX_C1_SHIFT ( C1_SHIFT + BOX_C1_LOG ) +#define BOX_C2_SHIFT ( C2_SHIFT + BOX_C2_LOG ) + + +/* + * The next three routines implement inverse colormap filling. They could + * all be folded into one big routine, but splitting them up this way saves + * some stack space (the mindist[] and bestdist[] arrays need not coexist) + * and may allow some compilers to produce better code by registerizing more + * inner-loop variables. + */ + +LOCAL int +find_nearby_colors( j_decompress_ptr cinfo, int minc0, int minc1, int minc2, + JSAMPLE colorlist[] ) { +/* Locate the colormap entries close enough to an update box to be candidates + * for the nearest entry to some cell(s) in the update box. The update box + * is specified by the center coordinates of its first cell. The number of + * candidate colormap entries is returned, and their colormap indexes are + * placed in colorlist[]. + * This routine uses Heckbert's "locally sorted search" criterion to select + * the colors that need further consideration. + */ + int numcolors = cinfo->actual_number_of_colors; + int maxc0, maxc1, maxc2; + int centerc0, centerc1, centerc2; + int i, x, ncolors; + INT32 minmaxdist, min_dist, max_dist, tdist; + INT32 mindist[MAXNUMCOLORS];/* min distance to colormap entry i */ + + /* Compute true coordinates of update box's upper corner and center. + * Actually we compute the coordinates of the center of the upper-corner + * histogram cell, which are the upper bounds of the volume we care about. + * Note that since ">>" rounds down, the "center" values may be closer to + * min than to max; hence comparisons to them must be "<=", not "<". + */ + maxc0 = minc0 + ( ( 1 << BOX_C0_SHIFT ) - ( 1 << C0_SHIFT ) ); + centerc0 = ( minc0 + maxc0 ) >> 1; + maxc1 = minc1 + ( ( 1 << BOX_C1_SHIFT ) - ( 1 << C1_SHIFT ) ); + centerc1 = ( minc1 + maxc1 ) >> 1; + maxc2 = minc2 + ( ( 1 << BOX_C2_SHIFT ) - ( 1 << C2_SHIFT ) ); + centerc2 = ( minc2 + maxc2 ) >> 1; + + /* For each color in colormap, find: + * 1. its minimum squared-distance to any point in the update box + * (zero if color is within update box); + * 2. its maximum squared-distance to any point in the update box. + * Both of these can be found by considering only the corners of the box. + * We save the minimum distance for each color in mindist[]; + * only the smallest maximum distance is of interest. + */ + minmaxdist = 0x7FFFFFFFL; + + for ( i = 0; i < numcolors; i++ ) { + /* We compute the squared-c0-distance term, then add in the other two. */ + x = GETJSAMPLE( cinfo->colormap[0][i] ); + if ( x < minc0 ) { + tdist = ( x - minc0 ) * C0_SCALE; + min_dist = tdist * tdist; + tdist = ( x - maxc0 ) * C0_SCALE; + max_dist = tdist * tdist; + } else if ( x > maxc0 ) { + tdist = ( x - maxc0 ) * C0_SCALE; + min_dist = tdist * tdist; + tdist = ( x - minc0 ) * C0_SCALE; + max_dist = tdist * tdist; + } else { + /* within cell range so no contribution to min_dist */ + min_dist = 0; + if ( x <= centerc0 ) { + tdist = ( x - maxc0 ) * C0_SCALE; + max_dist = tdist * tdist; + } else { + tdist = ( x - minc0 ) * C0_SCALE; + max_dist = tdist * tdist; + } + } + + x = GETJSAMPLE( cinfo->colormap[1][i] ); + if ( x < minc1 ) { + tdist = ( x - minc1 ) * C1_SCALE; + min_dist += tdist * tdist; + tdist = ( x - maxc1 ) * C1_SCALE; + max_dist += tdist * tdist; + } else if ( x > maxc1 ) { + tdist = ( x - maxc1 ) * C1_SCALE; + min_dist += tdist * tdist; + tdist = ( x - minc1 ) * C1_SCALE; + max_dist += tdist * tdist; + } else { + /* within cell range so no contribution to min_dist */ + if ( x <= centerc1 ) { + tdist = ( x - maxc1 ) * C1_SCALE; + max_dist += tdist * tdist; + } else { + tdist = ( x - minc1 ) * C1_SCALE; + max_dist += tdist * tdist; + } + } + + x = GETJSAMPLE( cinfo->colormap[2][i] ); + if ( x < minc2 ) { + tdist = ( x - minc2 ) * C2_SCALE; + min_dist += tdist * tdist; + tdist = ( x - maxc2 ) * C2_SCALE; + max_dist += tdist * tdist; + } else if ( x > maxc2 ) { + tdist = ( x - maxc2 ) * C2_SCALE; + min_dist += tdist * tdist; + tdist = ( x - minc2 ) * C2_SCALE; + max_dist += tdist * tdist; + } else { + /* within cell range so no contribution to min_dist */ + if ( x <= centerc2 ) { + tdist = ( x - maxc2 ) * C2_SCALE; + max_dist += tdist * tdist; + } else { + tdist = ( x - minc2 ) * C2_SCALE; + max_dist += tdist * tdist; + } + } + + mindist[i] = min_dist;/* save away the results */ + if ( max_dist < minmaxdist ) { + minmaxdist = max_dist; + } + } + + /* Now we know that no cell in the update box is more than minmaxdist + * away from some colormap entry. Therefore, only colors that are + * within minmaxdist of some part of the box need be considered. + */ + ncolors = 0; + for ( i = 0; i < numcolors; i++ ) { + if ( mindist[i] <= minmaxdist ) { + colorlist[ncolors++] = (JSAMPLE) i; + } + } + return ncolors; +} + + +LOCAL void +find_best_colors( j_decompress_ptr cinfo, int minc0, int minc1, int minc2, + int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[] ) { +/* Find the closest colormap entry for each cell in the update box, + * given the list of candidate colors prepared by find_nearby_colors. + * Return the indexes of the closest entries in the bestcolor[] array. + * This routine uses Thomas' incremental distance calculation method to + * find the distance from a colormap entry to successive cells in the box. + */ + int ic0, ic1, ic2; + int i, icolor; + register INT32 * bptr; /* pointer into bestdist[] array */ + JSAMPLE * cptr; /* pointer into bestcolor[] array */ + INT32 dist0, dist1; /* initial distance values */ + register INT32 dist2; /* current distance in inner loop */ + INT32 xx0, xx1; /* distance increments */ + register INT32 xx2; + INT32 inc0, inc1, inc2; /* initial values for increments */ + /* This array holds the distance to the nearest-so-far color for each cell */ + INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Initialize best-distance for each cell of the update box */ + bptr = bestdist; + for ( i = BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS - 1; i >= 0; i-- ) { + *bptr++ = 0x7FFFFFFFL; + } + + /* For each color selected by find_nearby_colors, + * compute its distance to the center of each cell in the box. + * If that's less than best-so-far, update best distance and color number. + */ + + /* Nominal steps between cell centers ("x" in Thomas article) */ +#define STEP_C0 ( ( 1 << C0_SHIFT ) * C0_SCALE ) +#define STEP_C1 ( ( 1 << C1_SHIFT ) * C1_SCALE ) +#define STEP_C2 ( ( 1 << C2_SHIFT ) * C2_SCALE ) + + for ( i = 0; i < numcolors; i++ ) { + icolor = GETJSAMPLE( colorlist[i] ); + /* Compute (square of) distance from minc0/c1/c2 to this color */ + inc0 = ( minc0 - GETJSAMPLE( cinfo->colormap[0][icolor] ) ) * C0_SCALE; + dist0 = inc0 * inc0; + inc1 = ( minc1 - GETJSAMPLE( cinfo->colormap[1][icolor] ) ) * C1_SCALE; + dist0 += inc1 * inc1; + inc2 = ( minc2 - GETJSAMPLE( cinfo->colormap[2][icolor] ) ) * C2_SCALE; + dist0 += inc2 * inc2; + /* Form the initial difference increments */ + inc0 = inc0 * ( 2 * STEP_C0 ) + STEP_C0 * STEP_C0; + inc1 = inc1 * ( 2 * STEP_C1 ) + STEP_C1 * STEP_C1; + inc2 = inc2 * ( 2 * STEP_C2 ) + STEP_C2 * STEP_C2; + /* Now loop over all cells in box, updating distance per Thomas method */ + bptr = bestdist; + cptr = bestcolor; + xx0 = inc0; + for ( ic0 = BOX_C0_ELEMS - 1; ic0 >= 0; ic0-- ) { + dist1 = dist0; + xx1 = inc1; + for ( ic1 = BOX_C1_ELEMS - 1; ic1 >= 0; ic1-- ) { + dist2 = dist1; + xx2 = inc2; + for ( ic2 = BOX_C2_ELEMS - 1; ic2 >= 0; ic2-- ) { + if ( dist2 < *bptr ) { + *bptr = dist2; + *cptr = (JSAMPLE) icolor; + } + dist2 += xx2; + xx2 += 2 * STEP_C2 * STEP_C2; + bptr++; + cptr++; + } + dist1 += xx1; + xx1 += 2 * STEP_C1 * STEP_C1; + } + dist0 += xx0; + xx0 += 2 * STEP_C0 * STEP_C0; + } + } +} + + +LOCAL void +fill_inverse_cmap( j_decompress_ptr cinfo, int c0, int c1, int c2 ) { +/* Fill the inverse-colormap entries in the update box that contains */ +/* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */ +/* we can fill as many others as we wish.) */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int minc0, minc1, minc2;/* lower left corner of update box */ + int ic0, ic1, ic2; + register JSAMPLE * cptr;/* pointer into bestcolor[] array */ + register histptr cachep;/* pointer into main cache array */ + /* This array lists the candidate colormap indexes. */ + JSAMPLE colorlist[MAXNUMCOLORS]; + int numcolors; /* number of candidate colors */ + /* This array holds the actually closest colormap index for each cell. */ + JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Convert cell coordinates to update box ID */ + c0 >>= BOX_C0_LOG; + c1 >>= BOX_C1_LOG; + c2 >>= BOX_C2_LOG; + + /* Compute true coordinates of update box's origin corner. + * Actually we compute the coordinates of the center of the corner + * histogram cell, which are the lower bounds of the volume we care about. + */ + minc0 = ( c0 << BOX_C0_SHIFT ) + ( ( 1 << C0_SHIFT ) >> 1 ); + minc1 = ( c1 << BOX_C1_SHIFT ) + ( ( 1 << C1_SHIFT ) >> 1 ); + minc2 = ( c2 << BOX_C2_SHIFT ) + ( ( 1 << C2_SHIFT ) >> 1 ); + + /* Determine which colormap entries are close enough to be candidates + * for the nearest entry to some cell in the update box. + */ + numcolors = find_nearby_colors( cinfo, minc0, minc1, minc2, colorlist ); + + /* Determine the actually nearest colors. */ + find_best_colors( cinfo, minc0, minc1, minc2, numcolors, colorlist, + bestcolor ); + + /* Save the best color numbers (plus 1) in the main cache array */ + c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */ + c1 <<= BOX_C1_LOG; + c2 <<= BOX_C2_LOG; + cptr = bestcolor; + for ( ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++ ) { + for ( ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++ ) { + cachep = &histogram[c0 + ic0][c1 + ic1][c2]; + for ( ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++ ) { + *cachep++ = (histcell) ( GETJSAMPLE( *cptr++ ) + 1 ); + } + } + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF void +pass2_no_dither( j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows ) { +/* This version performs no dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register JSAMPROW inptr, outptr; + register histptr cachep; + register int c0, c1, c2; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for ( row = 0; row < num_rows; row++ ) { + inptr = input_buf[row]; + outptr = output_buf[row]; + for ( col = width; col > 0; col-- ) { + /* get pixel value and index into the cache */ + c0 = GETJSAMPLE( *inptr++ ) >> C0_SHIFT; + c1 = GETJSAMPLE( *inptr++ ) >> C1_SHIFT; + c2 = GETJSAMPLE( *inptr++ ) >> C2_SHIFT; + cachep = &histogram[c0][c1][c2]; + /* If we have not seen this color before, find nearest colormap entry */ + /* and update the cache */ + if ( *cachep == 0 ) { + fill_inverse_cmap( cinfo, c0, c1, c2 ); + } + /* Now emit the colormap index for this cell */ + *outptr++ = (JSAMPLE) ( *cachep - 1 ); + } + } +} + + +METHODDEF void +pass2_fs_dither( j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows ) { +/* This version performs Floyd-Steinberg dithering */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register LOCFSERROR cur0, cur1, cur2;/* current error or pixel value */ + LOCFSERROR belowerr0, belowerr1, belowerr2;/* error for pixel below cur */ + LOCFSERROR bpreverr0, bpreverr1, bpreverr2;/* error for below/prev col */ + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + JSAMPROW inptr; /* => current input pixel */ + JSAMPROW outptr; /* => current output pixel */ + histptr cachep; + int dir; /* +1 or -1 depending on direction */ + int dir3; /* 3*dir, for advancing inptr & errorptr */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE * range_limit = cinfo->sample_range_limit; + int * error_limit = cquantize->error_limiter; + JSAMPROW colormap0 = cinfo->colormap[0]; + JSAMPROW colormap1 = cinfo->colormap[1]; + JSAMPROW colormap2 = cinfo->colormap[2]; + SHIFT_TEMPS + + for ( row = 0; row < num_rows; row++ ) { + inptr = input_buf[row]; + outptr = output_buf[row]; + if ( cquantize->on_odd_row ) { + /* work right to left in this row */ + inptr += ( width - 1 ) * 3;/* so point to rightmost pixel */ + outptr += width - 1; + dir = -1; + dir3 = -3; + errorptr = cquantize->fserrors + ( width + 1 ) * 3;/* => entry after last column */ + cquantize->on_odd_row = FALSE;/* flip for next time */ + } else { + /* work left to right in this row */ + dir = 1; + dir3 = 3; + errorptr = cquantize->fserrors;/* => entry before first real column */ + cquantize->on_odd_row = TRUE;/* flip for next time */ + } + /* Preset error values: no error propagated to first pixel from left */ + cur0 = cur1 = cur2 = 0; + /* and no error propagated to row below yet */ + belowerr0 = belowerr1 = belowerr2 = 0; + bpreverr0 = bpreverr1 = bpreverr2 = 0; + + for ( col = width; col > 0; col-- ) { + /* curN holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur0 = RIGHT_SHIFT( cur0 + errorptr[dir3 + 0] + 8, 4 ); + cur1 = RIGHT_SHIFT( cur1 + errorptr[dir3 + 1] + 8, 4 ); + cur2 = RIGHT_SHIFT( cur2 + errorptr[dir3 + 2] + 8, 4 ); + /* Limit the error using transfer function set by init_error_limit. + * See comments with init_error_limit for rationale. + */ + cur0 = error_limit[cur0]; + cur1 = error_limit[cur1]; + cur2 = error_limit[cur2]; + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE (or less with error limiting); + * this sets the required size of the range_limit array. + */ + cur0 += GETJSAMPLE( inptr[0] ); + cur1 += GETJSAMPLE( inptr[1] ); + cur2 += GETJSAMPLE( inptr[2] ); + cur0 = GETJSAMPLE( range_limit[cur0] ); + cur1 = GETJSAMPLE( range_limit[cur1] ); + cur2 = GETJSAMPLE( range_limit[cur2] ); + /* Index into the cache with adjusted pixel value */ + cachep = &histogram[cur0 >> C0_SHIFT][cur1 >> C1_SHIFT][cur2 >> C2_SHIFT]; + /* If we have not seen this color before, find nearest colormap */ + /* entry and update the cache */ + if ( *cachep == 0 ) { + fill_inverse_cmap( cinfo, cur0 >> C0_SHIFT, cur1 >> C1_SHIFT, cur2 >> C2_SHIFT ); + } + /* Now emit the colormap index for this cell */ + { register int pixcode = *cachep - 1; + *outptr = (JSAMPLE) pixcode; + /* Compute representation error for this pixel */ + cur0 -= GETJSAMPLE( colormap0[pixcode] ); + cur1 -= GETJSAMPLE( colormap1[pixcode] ); + cur2 -= GETJSAMPLE( colormap2[pixcode] ); + } + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + { register LOCFSERROR bnexterr, delta; + + bnexterr = cur0;/* Process component 0 */ + delta = cur0 * 2; + cur0 += delta;/* form error * 3 */ + errorptr[0] = (FSERROR) ( bpreverr0 + cur0 ); + cur0 += delta;/* form error * 5 */ + bpreverr0 = belowerr0 + cur0; + belowerr0 = bnexterr; + cur0 += delta;/* form error * 7 */ + bnexterr = cur1;/* Process component 1 */ + delta = cur1 * 2; + cur1 += delta;/* form error * 3 */ + errorptr[1] = (FSERROR) ( bpreverr1 + cur1 ); + cur1 += delta;/* form error * 5 */ + bpreverr1 = belowerr1 + cur1; + belowerr1 = bnexterr; + cur1 += delta;/* form error * 7 */ + bnexterr = cur2;/* Process component 2 */ + delta = cur2 * 2; + cur2 += delta;/* form error * 3 */ + errorptr[2] = (FSERROR) ( bpreverr2 + cur2 ); + cur2 += delta;/* form error * 5 */ + bpreverr2 = belowerr2 + cur2; + belowerr2 = bnexterr; + cur2 += delta;/* form error * 7 */ + } + /* At this point curN contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + inptr += dir3; /* Advance pixel pointers to next column */ + outptr += dir; + errorptr += dir3;/* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error values into the + * final fserrors[] entry. Note we need not unload belowerrN because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr0;/* unload prev errs into array */ + errorptr[1] = (FSERROR) bpreverr1; + errorptr[2] = (FSERROR) bpreverr2; + } +} + + +/* + * Initialize the error-limiting transfer function (lookup table). + * The raw F-S error computation can potentially compute error values of up to + * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be + * much less, otherwise obviously wrong pixels will be created. (Typical + * effects include weird fringes at color-area boundaries, isolated bright + * pixels in a dark area, etc.) The standard advice for avoiding this problem + * is to ensure that the "corners" of the color cube are allocated as output + * colors; then repeated errors in the same direction cannot cause cascading + * error buildup. However, that only prevents the error from getting + * completely out of hand; Aaron Giles reports that error limiting improves + * the results even with corner colors allocated. + * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty + * well, but the smoother transfer function used below is even better. Thanks + * to Aaron Giles for this idea. + */ + +LOCAL void +init_error_limit( j_decompress_ptr cinfo ) { +/* Allocate and fill in the error_limiter table */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + int * table; + int in, out; + + table = (int *) ( *cinfo->mem->alloc_small ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, ( MAXJSAMPLE * 2 + 1 ) * SIZEOF( int ) ); + table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */ + cquantize->error_limiter = table; + +#define STEPSIZE ( ( MAXJSAMPLE + 1 ) / 16 ) + /* Map errors 1:1 up to +- MAXJSAMPLE/16 */ + out = 0; + for ( in = 0; in < STEPSIZE; in++, out++ ) { + table[in] = out; + table[-in] = -out; + } + /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */ + for (; in < STEPSIZE * 3; in++, out += ( in & 1 ) ? 0 : 1 ) { + table[in] = out; + table[-in] = -out; + } + /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */ + for (; in <= MAXJSAMPLE; in++ ) { + table[in] = out; + table[-in] = -out; + } +#undef STEPSIZE +} + + +/* + * Finish up at the end of each pass. + */ + +METHODDEF void +finish_pass1( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Select the representative colors and fill in cinfo->colormap */ + cinfo->colormap = cquantize->sv_colormap; + select_colors( cinfo, cquantize->desired ); + /* Force next pass to zero the color index table */ + cquantize->needs_zeroed = TRUE; +} + + +METHODDEF void +finish_pass2( j_decompress_ptr cinfo ) { + /* no work */ +} + + +/* + * Initialize for each processing pass. + */ + +METHODDEF void +start_pass_2_quant( j_decompress_ptr cinfo, boolean is_pre_scan ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int i; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if ( cinfo->dither_mode != JDITHER_NONE ) { + cinfo->dither_mode = JDITHER_FS; + } + + if ( is_pre_scan ) { + /* Set up method pointers */ + cquantize->pub.color_quantize = prescan_quantize; + cquantize->pub.finish_pass = finish_pass1; + cquantize->needs_zeroed = TRUE;/* Always zero histogram */ + } else { + /* Set up method pointers */ + if ( cinfo->dither_mode == JDITHER_FS ) { + cquantize->pub.color_quantize = pass2_fs_dither; + } else { + cquantize->pub.color_quantize = pass2_no_dither; + } + cquantize->pub.finish_pass = finish_pass2; + + /* Make sure color count is acceptable */ + i = cinfo->actual_number_of_colors; + if ( i < 1 ) { + ERREXIT1( cinfo, JERR_QUANT_FEW_COLORS, 1 ); + } + if ( i > MAXNUMCOLORS ) { + ERREXIT1( cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS ); + } + + if ( cinfo->dither_mode == JDITHER_FS ) { + size_t arraysize = (size_t) ( ( cinfo->output_width + 2 ) * + ( 3 * SIZEOF( FSERROR ) ) ); + /* Allocate Floyd-Steinberg workspace if we didn't already. */ + if ( cquantize->fserrors == NULL ) { + cquantize->fserrors = (FSERRPTR) ( *cinfo->mem->alloc_large ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, arraysize ); + } + /* Initialize the propagated errors to zero. */ + jzero_far( (void FAR *) cquantize->fserrors, arraysize ); + /* Make the error-limit table if we didn't already. */ + if ( cquantize->error_limiter == NULL ) { + init_error_limit( cinfo ); + } + cquantize->on_odd_row = FALSE; + } + + } + /* Zero the histogram or inverse color map, if necessary */ + if ( cquantize->needs_zeroed ) { + for ( i = 0; i < HIST_C0_ELEMS; i++ ) { + jzero_far( (void FAR *) histogram[i], + HIST_C1_ELEMS * HIST_C2_ELEMS * SIZEOF( histcell ) ); + } + cquantize->needs_zeroed = FALSE; + } +} + + +/* + * Switch to a new external colormap between output passes. + */ + +METHODDEF void +new_color_map_2_quant( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Reset the inverse color map */ + cquantize->needs_zeroed = TRUE; +} + + +/* + * Module initialization routine for 2-pass color quantization. + */ + +GLOBAL void +jinit_2pass_quantizer( j_decompress_ptr cinfo ) { + my_cquantize_ptr cquantize; + int i; + + cquantize = (my_cquantize_ptr) + ( *cinfo->mem->alloc_small )( (j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF( my_cquantizer ) ); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_2_quant; + cquantize->pub.new_color_map = new_color_map_2_quant; + cquantize->fserrors = NULL; /* flag optional arrays not allocated */ + cquantize->error_limiter = NULL; + + /* Make sure jdmaster didn't give me a case I can't handle */ + if ( cinfo->out_color_components != 3 ) { + ERREXIT( cinfo, JERR_NOTIMPL ); + } + + /* Allocate the histogram/inverse colormap storage */ + cquantize->histogram = (hist3d) ( *cinfo->mem->alloc_small ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, HIST_C0_ELEMS * SIZEOF( hist2d ) ); + for ( i = 0; i < HIST_C0_ELEMS; i++ ) { + cquantize->histogram[i] = (hist2d) ( *cinfo->mem->alloc_large ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + HIST_C1_ELEMS * HIST_C2_ELEMS * SIZEOF( histcell ) ); + } + cquantize->needs_zeroed = TRUE;/* histogram is garbage now */ + + /* Allocate storage for the completed colormap, if required. + * We do this now since it is FAR storage and may affect + * the memory manager's space calculations. + */ + if ( cinfo->enable_2pass_quant ) { + /* Make sure color count is acceptable */ + int desired = cinfo->desired_number_of_colors; + /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */ + if ( desired < 8 ) { + ERREXIT1( cinfo, JERR_QUANT_FEW_COLORS, 8 ); + } + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if ( desired > MAXNUMCOLORS ) { + ERREXIT1( cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS ); + } + cquantize->sv_colormap = ( *cinfo->mem->alloc_sarray ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, (JDIMENSION) desired, (JDIMENSION) 3 ); + cquantize->desired = desired; + } else { + cquantize->sv_colormap = NULL; + } + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if ( cinfo->dither_mode != JDITHER_NONE ) { + cinfo->dither_mode = JDITHER_FS; + } + + /* Allocate Floyd-Steinberg workspace if necessary. + * This isn't really needed until pass 2, but again it is FAR storage. + * Although we will cope with a later change in dither_mode, + * we do not promise to honor max_memory_to_use if dither_mode changes. + */ + if ( cinfo->dither_mode == JDITHER_FS ) { + cquantize->fserrors = (FSERRPTR) ( *cinfo->mem->alloc_large ) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) ( ( cinfo->output_width + 2 ) * ( 3 * SIZEOF( FSERROR ) ) ) ); + /* Might as well create the error-limiting table too. */ + init_error_limit( cinfo ); + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ diff --git a/neo/renderer/jpeg-6/jutils.cpp b/neo/renderer/jpeg-6/jutils.cpp new file mode 100644 index 00000000..e0953086 --- /dev/null +++ b/neo/renderer/jpeg-6/jutils.cpp @@ -0,0 +1,171 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2 + 16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63,/* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL long +jdiv_round_up( long a, long b ) { +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ + return ( a + b - 1L ) / b; +} + + +GLOBAL long +jround_up( long a, long b ) { +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ + a += b - 1L; + return a - ( a % b ); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY( dest, src, size ) MEMCOPY( dest, src, size ) +#define FMEMZERO( target, size ) MEMZERO( target, size ) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY( dest, src, size ) _fmemcpy( (void FAR *)( dest ), (const void FAR *)( src ), (size_t)( size ) ) +#define FMEMZERO( target, size ) _fmemset( (void FAR *)( target ), 0, (size_t)( size ) ) +#endif +#endif + + +GLOBAL void +jcopy_sample_rows( JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols ) { +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) ( num_cols * SIZEOF( JSAMPLE ) ); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for ( row = num_rows; row > 0; row-- ) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY( outptr, inptr, count ); +#else + for ( count = num_cols; count > 0; count-- ) { + *outptr++ = *inptr++; + } /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL void +jcopy_block_row( JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks ) { +/* Copy a row of coefficient blocks from one place to another. */ +#ifdef FMEMCOPY + FMEMCOPY( output_row, input_row, num_blocks * ( DCTSIZE2 * SIZEOF( JCOEF ) ) ); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for ( count = (long) num_blocks * DCTSIZE2; count > 0; count-- ) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL void +jzero_far( void FAR * target, size_t bytestozero ) { +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +#ifdef FMEMZERO + FMEMZERO( target, bytestozero ); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for ( count = bytestozero; count > 0; count-- ) { + *ptr++ = 0; + } +#endif +} diff --git a/neo/renderer/jpeg-6/jversion.h b/neo/renderer/jpeg-6/jversion.h new file mode 100644 index 00000000..f2f1b8da --- /dev/null +++ b/neo/renderer/jpeg-6/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6 2-Aug-95" + +#define JCOPYRIGHT "Copyright (C) 1995, Thomas G. Lane" diff --git a/neo/renderer/simplex.h b/neo/renderer/simplex.h new file mode 100644 index 00000000..7bacb2bc --- /dev/null +++ b/neo/renderer/simplex.h @@ -0,0 +1,600 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +const int NUM_SIMPLEX_CHARS = 95; +const static int simplex[NUM_SIMPLEX_CHARS][112] = { + { 0,16, /* Ascii 32 */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,10, /* Ascii 33 */ + 5,21, 5, 7,-1,-1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,16, /* Ascii 34 */ + 4,21, 4,14,-1,-1,12,21,12,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,21, /* Ascii 35 */ + 11,25, 4,-7,-1,-1,17,25,10,-7,-1,-1, 4,12,18,12,-1,-1, 3, 6,17, 6,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 26,20, /* Ascii 36 */ + 8,25, 8,-4,-1,-1,12,25,12,-4,-1,-1,17,18,15,20,12,21, 8,21, 5,20, 3, + 18, 3,16, 4,14, 5,13, 7,12,13,10,15, 9,16, 8,17, 6,17, 3,15, 1,12, 0, + 8, 0, 5, 1, 3, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 31,24, /* Ascii 37 */ + 21,21, 3, 0,-1,-1, 8,21,10,19,10,17, 9,15, 7,14, 5,14, 3,16, 3,18, 4, + 20, 6,21, 8,21,10,20,13,19,16,19,19,20,21,21,-1,-1,17, 7,15, 6,14, 4, + 14, 2,16, 0,18, 0,20, 1,21, 3,21, 5,19, 7,17, 7,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 34,26, /* Ascii 38 */ + 23,12,23,13,22,14,21,14,20,13,19,11,17, 6,15, 3,13, 1,11, 0, 7, 0, 5, + 1, 4, 2, 3, 4, 3, 6, 4, 8, 5, 9,12,13,13,14,14,16,14,18,13,20,11,21, + 9,20, 8,18, 8,16, 9,13,11,10,16, 3,18, 1,20, 0,22, 0,23, 1,23, 2,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 7,10, /* Ascii 39 */ + 5,19, 4,20, 5,21, 6,20, 6,18, 5,16, 4,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,14, /* Ascii 40 */ + 11,25, 9,23, 7,20, 5,16, 4,11, 4, 7, 5, 2, 7,-2, 9,-5,11,-7,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,14, /* Ascii 41 */ + 3,25, 5,23, 7,20, 9,16,10,11,10, 7, 9, 2, 7,-2, 5,-5, 3,-7,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,16, /* Ascii 42 */ + 8,21, 8, 9,-1,-1, 3,18,13,12,-1,-1,13,18, 3,12,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,26, /* Ascii 43 */ + 13,18,13, 0,-1,-1, 4, 9,22, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,10, /* Ascii 44 */ + 6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6,-1, 5,-3, 4,-4,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,26, /* Ascii 45 */ + 4, 9,22, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,10, /* Ascii 46 */ + 5, 2, 4, 1, 5, 0, 6, 1, 5, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,22, /* Ascii 47 */ + 20,25, 2,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,20, /* Ascii 48 */ + 9,21, 6,20, 4,17, 3,12, 3, 9, 4, 4, 6, 1, 9, 0,11, 0,14, 1,16, 4,17, + 9,17,12,16,17,14,20,11,21, 9,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 4,20, /* Ascii 49 */ + 6,17, 8,18,11,21,11, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 14,20, /* Ascii 50 */ + 4,16, 4,17, 5,19, 6,20, 8,21,12,21,14,20,15,19,16,17,16,15,15,13,13, + 10, 3, 0,17, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 15,20, /* Ascii 51 */ + 5,21,16,21,10,13,13,13,15,12,16,11,17, 8,17, 6,16, 3,14, 1,11, 0, 8, + 0, 5, 1, 4, 2, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 6,20, /* Ascii 52 */ + 13,21, 3, 7,18, 7,-1,-1,13,21,13, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,20, /* Ascii 53 */ + 15,21, 5,21, 4,12, 5,13, 8,14,11,14,14,13,16,11,17, 8,17, 6,16, 3,14, + 1,11, 0, 8, 0, 5, 1, 4, 2, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,20, /* Ascii 54 */ + 16,18,15,20,12,21,10,21, 7,20, 5,17, 4,12, 4, 7, 5, 3, 7, 1,10, 0,11, + 0,14, 1,16, 3,17, 6,17, 7,16,10,14,12,11,13,10,13, 7,12, 5,10, 4, 7, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,20, /* Ascii 55 */ + 17,21, 7, 0,-1,-1, 3,21,17,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 29,20, /* Ascii 56 */ + 8,21, 5,20, 4,18, 4,16, 5,14, 7,13,11,12,14,11,16, 9,17, 7,17, 4,16, + 2,15, 1,12, 0, 8, 0, 5, 1, 4, 2, 3, 4, 3, 7, 4, 9, 6,11, 9,12,13,13, + 15,14,16,16,16,18,15,20,12,21, 8,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,20, /* Ascii 57 */ + 16,14,15,11,13, 9,10, 8, 9, 8, 6, 9, 4,11, 3,14, 3,15, 4,18, 6,20, 9, + 21,10,21,13,20,15,18,16,14,16, 9,15, 4,13, 1,10, 0, 8, 0, 5, 1, 4, 3, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,10, /* Ascii 58 */ + 5,14, 4,13, 5,12, 6,13, 5,14,-1,-1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 14,10, /* Ascii 59 */ + 5,14, 4,13, 5,12, 6,13, 5,14,-1,-1, 6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6, + -1, 5,-3, 4,-4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 3,24, /* Ascii 60 */ + 20,18, 4, 9,20, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,26, /* Ascii 61 */ + 4,12,22,12,-1,-1, 4, 6,22, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 3,24, /* Ascii 62 */ + 4,18,20, 9, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 20,18, /* Ascii 63 */ + 3,16, 3,17, 4,19, 5,20, 7,21,11,21,13,20,14,19,15,17,15,15,14,13,13, + 12, 9,10, 9, 7,-1,-1, 9, 2, 8, 1, 9, 0,10, 1, 9, 2,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 55,27, /* Ascii 64 */ + 18,13,17,15,15,16,12,16,10,15, 9,14, 8,11, 8, 8, 9, 6,11, 5,14, 5,16, + 6,17, 8,-1,-1,12,16,10,14, 9,11, 9, 8,10, 6,11, 5,-1,-1,18,16,17, 8, + 17, 6,19, 5,21, 5,23, 7,24,10,24,12,23,15,22,17,20,19,18,20,15,21,12, + 21, 9,20, 7,19, 5,17, 4,15, 3,12, 3, 9, 4, 6, 5, 4, 7, 2, 9, 1,12, 0, + 15, 0,18, 1,20, 2,21, 3,-1,-1,19,16,18, 8,18, 6,19, 5 }, + { 8,18, /* Ascii 65 */ + 9,21, 1, 0,-1,-1, 9,21,17, 0,-1,-1, 4, 7,14, 7,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,21, /* Ascii 66 */ + 4,21, 4, 0,-1,-1, 4,21,13,21,16,20,17,19,18,17,18,15,17,13,16,12,13, + 11,-1,-1, 4,11,13,11,16,10,17, 9,18, 7,18, 4,17, 2,16, 1,13, 0, 4, 0, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 18,21, /* Ascii 67 */ + 18,16,17,18,15,20,13,21, 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, + 3, 7, 1, 9, 0,13, 0,15, 1,17, 3,18, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 15,21, /* Ascii 68 */ + 4,21, 4, 0,-1,-1, 4,21,11,21,14,20,16,18,17,16,18,13,18, 8,17, 5,16, + 3,14, 1,11, 0, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,19, /* Ascii 69 */ + 4,21, 4, 0,-1,-1, 4,21,17,21,-1,-1, 4,11,12,11,-1,-1, 4, 0,17, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,18, /* Ascii 70 */ + 4,21, 4, 0,-1,-1, 4,21,17,21,-1,-1, 4,11,12,11,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 22,21, /* Ascii 71 */ + 18,16,17,18,15,20,13,21, 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, + 3, 7, 1, 9, 0,13, 0,15, 1,17, 3,18, 5,18, 8,-1,-1,13, 8,18, 8,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,22, /* Ascii 72 */ + 4,21, 4, 0,-1,-1,18,21,18, 0,-1,-1, 4,11,18,11,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2, 8, /* Ascii 73 */ + 4,21, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,16, /* Ascii 74 */ + 12,21,12, 5,11, 2,10, 1, 8, 0, 6, 0, 4, 1, 3, 2, 2, 5, 2, 7,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,21, /* Ascii 75 */ + 4,21, 4, 0,-1,-1,18,21, 4, 7,-1,-1, 9,12,18, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,17, /* Ascii 76 */ + 4,21, 4, 0,-1,-1, 4, 0,16, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,24, /* Ascii 77 */ + 4,21, 4, 0,-1,-1, 4,21,12, 0,-1,-1,20,21,12, 0,-1,-1,20,21,20, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,22, /* Ascii 78 */ + 4,21, 4, 0,-1,-1, 4,21,18, 0,-1,-1,18,21,18, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 21,22, /* Ascii 79 */ + 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0,13, 0,15, + 1,17, 3,18, 5,19, 8,19,13,18,16,17,18,15,20,13,21, 9,21,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 13,21, /* Ascii 80 */ + 4,21, 4, 0,-1,-1, 4,21,13,21,16,20,17,19,18,17,18,14,17,12,16,11,13, + 10, 4,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 24,22, /* Ascii 81 */ + 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0,13, 0,15, + 1,17, 3,18, 5,19, 8,19,13,18,16,17,18,15,20,13,21, 9,21,-1,-1,12, 4, + 18,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 16,21, /* Ascii 82 */ + 4,21, 4, 0,-1,-1, 4,21,13,21,16,20,17,19,18,17,18,15,17,13,16,12,13, + 11, 4,11,-1,-1,11,11,18, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 20,20, /* Ascii 83 */ + 17,18,15,20,12,21, 8,21, 5,20, 3,18, 3,16, 4,14, 5,13, 7,12,13,10,15, + 9,16, 8,17, 6,17, 3,15, 1,12, 0, 8, 0, 5, 1, 3, 3,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,16, /* Ascii 84 */ + 8,21, 8, 0,-1,-1, 1,21,15,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,22, /* Ascii 85 */ + 4,21, 4, 6, 5, 3, 7, 1,10, 0,12, 0,15, 1,17, 3,18, 6,18,21,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,18, /* Ascii 86 */ + 1,21, 9, 0,-1,-1,17,21, 9, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,24, /* Ascii 87 */ + 2,21, 7, 0,-1,-1,12,21, 7, 0,-1,-1,12,21,17, 0,-1,-1,22,21,17, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,20, /* Ascii 88 */ + 3,21,17, 0,-1,-1,17,21, 3, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 6,18, /* Ascii 89 */ + 1,21, 9,11, 9, 0,-1,-1,17,21, 9,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,20, /* Ascii 90 */ + 17,21, 3, 0,-1,-1, 3,21,17,21,-1,-1, 3, 0,17, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,14, /* Ascii 91 */ + 4,25, 4,-7,-1,-1, 5,25, 5,-7,-1,-1, 4,25,11,25,-1,-1, 4,-7,11,-7,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,14, /* Ascii 92 */ + 0,21,14,-3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,14, /* Ascii 93 */ + 9,25, 9,-7,-1,-1,10,25,10,-7,-1,-1, 3,25,10,25,-1,-1, 3,-7,10,-7,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,16, /* Ascii 94 */ + 6,15, 8,18,10,15,-1,-1, 3,12, 8,17,13,12,-1,-1, 8,17, 8, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,16, /* Ascii 95 */ + 0,-2,16,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 7,10, /* Ascii 96 */ + 6,21, 5,20, 4,18, 4,16, 5,15, 6,16, 5,17,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 97 */ + 15,14,15, 0,-1,-1,15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 98 */ + 4,21, 4, 0,-1,-1, 4,11, 6,13, 8,14,11,14,13,13,15,11,16, 8,16, 6,15, + 3,13, 1,11, 0, 8, 0, 6, 1, 4, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 14,18, /* Ascii 99 */ + 15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0,11, + 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 100 */ + 15,21,15, 0,-1,-1,15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,18, /* Ascii 101 */ + 3, 8,15, 8,15,10,14,12,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,12, /* Ascii 102 */ + 10,21, 8,21, 6,20, 5,17, 5, 0,-1,-1, 2,14, 9,14,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 22,19, /* Ascii 103 */ + 15,14,15,-2,14,-5,13,-6,11,-7, 8,-7, 6,-6,-1,-1,15,11,13,13,11,14, 8, + 14, 6,13, 4,11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,19, /* Ascii 104 */ + 4,21, 4, 0,-1,-1, 4,10, 7,13, 9,14,12,14,14,13,15,10,15, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8, 8, /* Ascii 105 */ + 3,21, 4,20, 5,21, 4,22, 3,21,-1,-1, 4,14, 4, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,10, /* Ascii 106 */ + 5,21, 6,20, 7,21, 6,22, 5,21,-1,-1, 6,14, 6,-3, 5,-6, 3,-7, 1,-7,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,17, /* Ascii 107 */ + 4,21, 4, 0,-1,-1,14,14, 4, 4,-1,-1, 8, 8,15, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2, 8, /* Ascii 108 */ + 4,21, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 18,30, /* Ascii 109 */ + 4,14, 4, 0,-1,-1, 4,10, 7,13, 9,14,12,14,14,13,15,10,15, 0,-1,-1,15, + 10,18,13,20,14,23,14,25,13,26,10,26, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,19, /* Ascii 110 */ + 4,14, 4, 0,-1,-1, 4,10, 7,13, 9,14,12,14,14,13,15,10,15, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 111 */ + 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,16, + 6,16, 8,15,11,13,13,11,14, 8,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 112 */ + 4,14, 4,-7,-1,-1, 4,11, 6,13, 8,14,11,14,13,13,15,11,16, 8,16, 6,15, + 3,13, 1,11, 0, 8, 0, 6, 1, 4, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 113 */ + 15,14,15,-7,-1,-1,15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,13, /* Ascii 114 */ + 4,14, 4, 0,-1,-1, 4, 8, 5,11, 7,13, 9,14,12,14,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,17, /* Ascii 115 */ + 14,11,13,13,10,14, 7,14, 4,13, 3,11, 4, 9, 6, 8,11, 7,13, 6,14, 4,14, + 3,13, 1,10, 0, 7, 0, 4, 1, 3, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,12, /* Ascii 116 */ + 5,21, 5, 4, 6, 1, 8, 0,10, 0,-1,-1, 2,14, 9,14,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,19, /* Ascii 117 */ + 4,14, 4, 4, 5, 1, 7, 0,10, 0,12, 1,15, 4,-1,-1,15,14,15, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,16, /* Ascii 118 */ + 2,14, 8, 0,-1,-1,14,14, 8, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,22, /* Ascii 119 */ + 3,14, 7, 0,-1,-1,11,14, 7, 0,-1,-1,11,14,15, 0,-1,-1,19,14,15, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,17, /* Ascii 120 */ + 3,14,14, 0,-1,-1,14,14, 3, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 9,16, /* Ascii 121 */ + 2,14, 8, 0,-1,-1,14,14, 8, 0, 6,-4, 4,-6, 2,-7, 1,-7,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,17, /* Ascii 122 */ + 14,14, 3, 0,-1,-1, 3,14,14,14,-1,-1, 3, 0,14, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 39,14, /* Ascii 123 */ + 9,25, 7,24, 6,23, 5,21, 5,19, 6,17, 7,16, 8,14, 8,12, 6,10,-1,-1, 7, + 24, 6,22, 6,20, 7,18, 8,17, 9,15, 9,13, 8,11, 4, 9, 8, 7, 9, 5, 9, 3, + 8, 1, 7, 0, 6,-2, 6,-4, 7,-6,-1,-1, 6, 8, 8, 6, 8, 4, 7, 2, 6, 1, 5, + -1, 5,-3, 6,-5, 7,-6, 9,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2, 8, /* Ascii 124 */ + 4,25, 4,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 39,14, /* Ascii 125 */ + 5,25, 7,24, 8,23, 9,21, 9,19, 8,17, 7,16, 6,14, 6,12, 8,10,-1,-1, 7, + 24, 8,22, 8,20, 7,18, 6,17, 5,15, 5,13, 6,11,10, 9, 6, 7, 5, 5, 5, 3, + 6, 1, 7, 0, 8,-2, 8,-4, 7,-6,-1,-1, 8, 8, 6, 6, 6, 4, 7, 2, 8, 1, 9, + -1, 9,-3, 8,-5, 7,-6, 5,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,24, /* Ascii 126 */ + 3, 6, 3, 8, 4,11, 6,12, 8,12,10,11,14, 8,16, 7,18, 7,20, 8,21,10,-1, + -1, 3, 8, 4,10, 6,11, 8,11,10,10,14, 7,16, 6,18, 6,20, 7,21,10,21,12, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 } +}; diff --git a/neo/renderer/tr_backend_draw.cpp b/neo/renderer/tr_backend_draw.cpp new file mode 100644 index 00000000..c2aa8d88 --- /dev/null +++ b/neo/renderer/tr_backend_draw.cpp @@ -0,0 +1,2871 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +idCVar r_drawEyeColor( "r_drawEyeColor", "0", CVAR_RENDERER | CVAR_BOOL, "Draw a colored box, red = left eye, blue = right eye, grey = non-stereo" ); +idCVar r_motionBlur( "r_motionBlur", "0", CVAR_RENDERER | CVAR_INTEGER | CVAR_ARCHIVE, "1 - 5, log2 of the number of motion blur samples" ); +idCVar r_forceZPassStencilShadows( "r_forceZPassStencilShadows", "0", CVAR_RENDERER | CVAR_BOOL, "force Z-pass rendering for performance testing" ); +idCVar r_useStencilShadowPreload( "r_useStencilShadowPreload", "1", CVAR_RENDERER | CVAR_BOOL, "use stencil shadow preload algorithm instead of Z-fail" ); +idCVar r_skipShaderPasses( "r_skipShaderPasses", "0", CVAR_RENDERER | CVAR_BOOL, "" ); +idCVar r_skipInteractionFastPath( "r_skipInteractionFastPath", "1", CVAR_RENDERER | CVAR_BOOL, "" ); +idCVar r_useLightStencilSelect( "r_useLightStencilSelect", "0", CVAR_RENDERER | CVAR_BOOL, "use stencil select pass" ); + +extern idCVar stereoRender_swapEyes; + +backEndState_t backEnd; + +/* +================ +SetVertexParm +================ +*/ +static ID_INLINE void SetVertexParm( renderParm_t rp, const float * value ) { + renderProgManager.SetUniformValue( rp, value ); +} + +/* +================ +SetVertexParms +================ +*/ +static ID_INLINE void SetVertexParms( renderParm_t rp, const float * value, int num ) { + for ( int i = 0; i < num; i++ ) { + renderProgManager.SetUniformValue( (renderParm_t)( rp + i ), value + ( i * 4 ) ); + } +} + +/* +================ +SetFragmentParm +================ +*/ +static ID_INLINE void SetFragmentParm( renderParm_t rp, const float * value ) { + renderProgManager.SetUniformValue( rp, value ); +} + +/* +================ +RB_SetMVP +================ +*/ +void RB_SetMVP( const idRenderMatrix & mvp ) { + SetVertexParms( RENDERPARM_MVPMATRIX_X, mvp[0], 4 ); +} + +/* +================ +RB_SetMVPWithStereoOffset +================ +*/ +static void RB_SetMVPWithStereoOffset( const idRenderMatrix & mvp, const float stereoOffset ) { + idRenderMatrix offset = mvp; + offset[0][3] += stereoOffset; + + SetVertexParms( RENDERPARM_MVPMATRIX_X, offset[0], 4 ); +} + +static const float zero[4] = { 0, 0, 0, 0 }; +static const float one[4] = { 1, 1, 1, 1 }; +static const float negOne[4] = { -1, -1, -1, -1 }; + +/* +================ +RB_SetVertexColorParms +================ +*/ +static void RB_SetVertexColorParms( stageVertexColor_t svc ) { + switch ( svc ) { + case SVC_IGNORE: + SetVertexParm( RENDERPARM_VERTEXCOLOR_MODULATE, zero ); + SetVertexParm( RENDERPARM_VERTEXCOLOR_ADD, one ); + break; + case SVC_MODULATE: + SetVertexParm( RENDERPARM_VERTEXCOLOR_MODULATE, one ); + SetVertexParm( RENDERPARM_VERTEXCOLOR_ADD, zero ); + break; + case SVC_INVERSE_MODULATE: + SetVertexParm( RENDERPARM_VERTEXCOLOR_MODULATE, negOne ); + SetVertexParm( RENDERPARM_VERTEXCOLOR_ADD, one ); + break; + } +} + +/* +================ +RB_DrawElementsWithCounters +================ +*/ +void RB_DrawElementsWithCounters( const drawSurf_t *surf ) { + // get vertex buffer + const vertCacheHandle_t vbHandle = surf->ambientCache; + idVertexBuffer * vertexBuffer; + if ( vertexCache.CacheIsStatic( vbHandle ) ) { + vertexBuffer = &vertexCache.staticData.vertexBuffer; + } else { + const uint64 frameNum = (int)( vbHandle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( frameNum != ( ( vertexCache.currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + idLib::Warning( "RB_DrawElementsWithCounters, vertexBuffer == NULL" ); + return; + } + vertexBuffer = &vertexCache.frameData[vertexCache.drawListNum].vertexBuffer; + } + const int vertOffset = (int)( vbHandle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + + // get index buffer + const vertCacheHandle_t ibHandle = surf->indexCache; + idIndexBuffer * indexBuffer; + if ( vertexCache.CacheIsStatic( ibHandle ) ) { + indexBuffer = &vertexCache.staticData.indexBuffer; + } else { + const uint64 frameNum = (int)( ibHandle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( frameNum != ( ( vertexCache.currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + idLib::Warning( "RB_DrawElementsWithCounters, indexBuffer == NULL" ); + return; + } + indexBuffer = &vertexCache.frameData[vertexCache.drawListNum].indexBuffer; + } + const int indexOffset = (int)( ibHandle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + + RENDERLOG_PRINTF( "Binding Buffers: %p:%i %p:%i\n", vertexBuffer, vertOffset, indexBuffer, indexOffset ); + + if ( surf->jointCache ) { + if ( !verify( renderProgManager.ShaderUsesJoints() ) ) { + return; + } + } else { + if ( !verify( !renderProgManager.ShaderUsesJoints() || renderProgManager.ShaderHasOptionalSkinning() ) ) { + return; + } + } + + + if ( surf->jointCache ) { + idJointBuffer jointBuffer; + if ( !vertexCache.GetJointBuffer( surf->jointCache, &jointBuffer ) ) { + idLib::Warning( "RB_DrawElementsWithCounters, jointBuffer == NULL" ); + return; + } + assert( ( jointBuffer.GetOffset() & ( glConfig.uniformBufferOffsetAlignment - 1 ) ) == 0 ); + + const GLuint ubo = reinterpret_cast< GLuint >( jointBuffer.GetAPIObject() ); + qglBindBufferRange( GL_UNIFORM_BUFFER, 0, ubo, jointBuffer.GetOffset(), jointBuffer.GetNumJoints() * sizeof( idJointMat ) ); + } + + renderProgManager.CommitUniforms(); + + if ( backEnd.glState.currentIndexBuffer != (GLuint)indexBuffer->GetAPIObject() || !r_useStateCaching.GetBool() ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLuint)indexBuffer->GetAPIObject() ); + backEnd.glState.currentIndexBuffer = (GLuint)indexBuffer->GetAPIObject(); + } + + if ( ( backEnd.glState.vertexLayout != LAYOUT_DRAW_VERT ) || ( backEnd.glState.currentVertexBuffer != (GLuint)vertexBuffer->GetAPIObject() ) || !r_useStateCaching.GetBool() ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, (GLuint)vertexBuffer->GetAPIObject() ); + backEnd.glState.currentVertexBuffer = (GLuint)vertexBuffer->GetAPIObject(); + + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_VERTEX ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_NORMAL ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_COLOR ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_COLOR2 ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_ST ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_TANGENT ); + + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_VERTEX, 3, GL_FLOAT, GL_FALSE, sizeof( idDrawVert ), (void *)( DRAWVERT_XYZ_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_NORMAL, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( idDrawVert ), (void *)( DRAWVERT_NORMAL_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( idDrawVert ), (void *)( DRAWVERT_COLOR_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_COLOR2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( idDrawVert ), (void *)( DRAWVERT_COLOR2_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_ST, 2, GL_HALF_FLOAT, GL_TRUE, sizeof( idDrawVert ), (void *)( DRAWVERT_ST_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_TANGENT, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( idDrawVert ), (void *)( DRAWVERT_TANGENT_OFFSET ) ); + + backEnd.glState.vertexLayout = LAYOUT_DRAW_VERT; + } + + qglDrawElementsBaseVertex( GL_TRIANGLES, + r_singleTriangle.GetBool() ? 3 : surf->numIndexes, + GL_INDEX_TYPE, + (triIndex_t *)indexOffset, + vertOffset / sizeof ( idDrawVert ) ); + + +} + +/* +====================== +RB_GetShaderTextureMatrix +====================== +*/ +static void RB_GetShaderTextureMatrix( const float *shaderRegisters, const textureStage_t *texture, float matrix[16] ) { + matrix[0*4+0] = shaderRegisters[ texture->matrix[0][0] ]; + matrix[1*4+0] = shaderRegisters[ texture->matrix[0][1] ]; + matrix[2*4+0] = 0.0f; + matrix[3*4+0] = shaderRegisters[ texture->matrix[0][2] ]; + + matrix[0*4+1] = shaderRegisters[ texture->matrix[1][0] ]; + matrix[1*4+1] = shaderRegisters[ texture->matrix[1][1] ]; + matrix[2*4+1] = 0.0f; + matrix[3*4+1] = shaderRegisters[ texture->matrix[1][2] ]; + + // we attempt to keep scrolls from generating incredibly large texture values, but + // center rotations and center scales can still generate offsets that need to be > 1 + if ( matrix[3*4+0] < -40.0f || matrix[12] > 40.0f ) { + matrix[3*4+0] -= (int)matrix[3*4+0]; + } + if ( matrix[13] < -40.0f || matrix[13] > 40.0f ) { + matrix[13] -= (int)matrix[13]; + } + + matrix[0*4+2] = 0.0f; + matrix[1*4+2] = 0.0f; + matrix[2*4+2] = 1.0f; + matrix[3*4+2] = 0.0f; + + matrix[0*4+3] = 0.0f; + matrix[1*4+3] = 0.0f; + matrix[2*4+3] = 0.0f; + matrix[3*4+3] = 1.0f; +} + +/* +====================== +RB_LoadShaderTextureMatrix +====================== +*/ +static void RB_LoadShaderTextureMatrix( const float *shaderRegisters, const textureStage_t *texture ) { + float texS[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float texT[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + + if ( texture->hasMatrix ) { + float matrix[16]; + RB_GetShaderTextureMatrix( shaderRegisters, texture, matrix ); + texS[0] = matrix[0*4+0]; + texS[1] = matrix[1*4+0]; + texS[2] = matrix[2*4+0]; + texS[3] = matrix[3*4+0]; + + texT[0] = matrix[0*4+1]; + texT[1] = matrix[1*4+1]; + texT[2] = matrix[2*4+1]; + texT[3] = matrix[3*4+1]; + + RENDERLOG_PRINTF( "Setting Texture Matrix\n"); + renderLog.Indent(); + RENDERLOG_PRINTF( "Texture Matrix S : %4.3f, %4.3f, %4.3f, %4.3f\n", texS[0], texS[1], texS[2], texS[3] ); + RENDERLOG_PRINTF( "Texture Matrix T : %4.3f, %4.3f, %4.3f, %4.3f\n", texT[0], texT[1], texT[2], texT[3] ); + renderLog.Outdent(); + } + + SetVertexParm( RENDERPARM_TEXTUREMATRIX_S, texS ); + SetVertexParm( RENDERPARM_TEXTUREMATRIX_T, texT ); +} + +/* +===================== +RB_BakeTextureMatrixIntoTexgen +===================== +*/ +static void RB_BakeTextureMatrixIntoTexgen( idPlane lightProject[3], const float *textureMatrix ) { + float genMatrix[16]; + float final[16]; + + genMatrix[0*4+0] = lightProject[0][0]; + genMatrix[1*4+0] = lightProject[0][1]; + genMatrix[2*4+0] = lightProject[0][2]; + genMatrix[3*4+0] = lightProject[0][3]; + + genMatrix[0*4+1] = lightProject[1][0]; + genMatrix[1*4+1] = lightProject[1][1]; + genMatrix[2*4+1] = lightProject[1][2]; + genMatrix[3*4+1] = lightProject[1][3]; + + genMatrix[0*4+2] = 0.0f; + genMatrix[1*4+2] = 0.0f; + genMatrix[2*4+2] = 0.0f; + genMatrix[3*4+2] = 0.0f; + + genMatrix[0*4+3] = lightProject[2][0]; + genMatrix[1*4+3] = lightProject[2][1]; + genMatrix[2*4+3] = lightProject[2][2]; + genMatrix[3*4+3] = lightProject[2][3]; + + R_MatrixMultiply( genMatrix, textureMatrix, final ); + + lightProject[0][0] = final[0*4+0]; + lightProject[0][1] = final[1*4+0]; + lightProject[0][2] = final[2*4+0]; + lightProject[0][3] = final[3*4+0]; + + lightProject[1][0] = final[0*4+1]; + lightProject[1][1] = final[1*4+1]; + lightProject[1][2] = final[2*4+1]; + lightProject[1][3] = final[3*4+1]; +} + +/* +====================== +RB_BindVariableStageImage + +Handles generating a cinematic frame if needed +====================== +*/ +static void RB_BindVariableStageImage( const textureStage_t *texture, const float *shaderRegisters ) { + if ( texture->cinematic ) { + cinData_t cin; + + if ( r_skipDynamicTextures.GetBool() ) { + globalImages->defaultImage->Bind(); + return; + } + + // offset time by shaderParm[7] (FIXME: make the time offset a parameter of the shader?) + // We make no attempt to optimize for multiple identical cinematics being in view, or + // for cinematics going at a lower framerate than the renderer. + cin = texture->cinematic->ImageForTime( backEnd.viewDef->renderView.time[0] + idMath::Ftoi( 1000.0f * backEnd.viewDef->renderView.shaderParms[11] ) ); + if ( cin.imageY != NULL ) { + GL_SelectTexture( 0 ); + cin.imageY->Bind(); + GL_SelectTexture( 1 ); + cin.imageCr->Bind(); + GL_SelectTexture( 2 ); + cin.imageCb->Bind(); + } else { + globalImages->blackImage->Bind(); + // because the shaders may have already been set - we need to make sure we are not using a bink shader which would + // display incorrectly. We may want to get rid of RB_BindVariableStageImage and inline the code so that the + // SWF GUI case is handled better, too + renderProgManager.BindShader_TextureVertexColor(); + } + } else { + // FIXME: see why image is invalid + if ( texture->image != NULL ) { + texture->image->Bind(); + } + } +} + +/* +================ +RB_PrepareStageTexturing +================ +*/ +static void RB_PrepareStageTexturing( const shaderStage_t * pStage, const drawSurf_t * surf ) { + float useTexGenParm[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // set the texture matrix if needed + RB_LoadShaderTextureMatrix( surf->shaderRegisters, &pStage->texture ); + + // texgens + if ( pStage->texture.texgen == TG_REFLECT_CUBE ) { + + // see if there is also a bump map specified + const shaderStage_t *bumpStage = surf->material->GetBumpStage(); + if ( bumpStage != NULL ) { + // per-pixel reflection mapping with bump mapping + GL_SelectTexture( 1 ); + bumpStage->texture.image->Bind(); + GL_SelectTexture( 0 ); + + RENDERLOG_PRINTF( "TexGen: TG_REFLECT_CUBE: Bumpy Environment\n" ); + if ( surf->jointCache ) { + renderProgManager.BindShader_BumpyEnvironmentSkinned(); + } else { + renderProgManager.BindShader_BumpyEnvironment(); + } + } else { + RENDERLOG_PRINTF( "TexGen: TG_REFLECT_CUBE: Environment\n" ); + if ( surf->jointCache ) { + renderProgManager.BindShader_EnvironmentSkinned(); + } else { + renderProgManager.BindShader_Environment(); + } + } + + } else if ( pStage->texture.texgen == TG_SKYBOX_CUBE ) { + + renderProgManager.BindShader_SkyBox(); + + } else if ( pStage->texture.texgen == TG_WOBBLESKY_CUBE ) { + + const int * parms = surf->material->GetTexGenRegisters(); + + float wobbleDegrees = surf->shaderRegisters[ parms[0] ] * ( idMath::PI / 180.0f ); + float wobbleSpeed = surf->shaderRegisters[ parms[1] ] * ( 2.0f * idMath::PI / 60.0f ); + float rotateSpeed = surf->shaderRegisters[ parms[2] ] * ( 2.0f * idMath::PI / 60.0f ); + + idVec3 axis[3]; + { + // very ad-hoc "wobble" transform + float s, c; + idMath::SinCos( wobbleSpeed * backEnd.viewDef->renderView.time[0] * 0.001f, s, c ); + + float ws, wc; + idMath::SinCos( wobbleDegrees, ws, wc ); + + axis[2][0] = ws * c; + axis[2][1] = ws * s; + axis[2][2] = wc; + + axis[1][0] = -s * s * ws; + axis[1][2] = -s * ws * ws; + axis[1][1] = idMath::Sqrt( idMath::Fabs( 1.0f - ( axis[1][0] * axis[1][0] + axis[1][2] * axis[1][2] ) ) ); + + // make the second vector exactly perpendicular to the first + axis[1] -= ( axis[2] * axis[1] ) * axis[2]; + axis[1].Normalize(); + + // construct the third with a cross + axis[0].Cross( axis[1], axis[2] ); + } + + // add the rotate + float rs, rc; + idMath::SinCos( rotateSpeed * backEnd.viewDef->renderView.time[0] * 0.001f, rs, rc ); + + float transform[12]; + transform[0*4+0] = axis[0][0] * rc + axis[1][0] * rs; + transform[0*4+1] = axis[0][1] * rc + axis[1][1] * rs; + transform[0*4+2] = axis[0][2] * rc + axis[1][2] * rs; + transform[0*4+3] = 0.0f; + + transform[1*4+0] = axis[1][0] * rc - axis[0][0] * rs; + transform[1*4+1] = axis[1][1] * rc - axis[0][1] * rs; + transform[1*4+2] = axis[1][2] * rc - axis[0][2] * rs; + transform[1*4+3] = 0.0f; + + transform[2*4+0] = axis[2][0]; + transform[2*4+1] = axis[2][1]; + transform[2*4+2] = axis[2][2]; + transform[2*4+3] = 0.0f; + + SetVertexParms( RENDERPARM_WOBBLESKY_X, transform, 3 ); + renderProgManager.BindShader_WobbleSky(); + + } else if ( ( pStage->texture.texgen == TG_SCREEN ) || ( pStage->texture.texgen == TG_SCREEN2 ) ) { + + useTexGenParm[0] = 1.0f; + useTexGenParm[1] = 1.0f; + useTexGenParm[2] = 1.0f; + useTexGenParm[3] = 1.0f; + + float mat[16]; + R_MatrixMultiply( surf->space->modelViewMatrix, backEnd.viewDef->projectionMatrix, mat ); + + RENDERLOG_PRINTF( "TexGen : %s\n", ( pStage->texture.texgen == TG_SCREEN ) ? "TG_SCREEN" : "TG_SCREEN2" ); + renderLog.Indent(); + + float plane[4]; + plane[0] = mat[0*4+0]; + plane[1] = mat[1*4+0]; + plane[2] = mat[2*4+0]; + plane[3] = mat[3*4+0]; + SetVertexParm( RENDERPARM_TEXGEN_0_S, plane ); + RENDERLOG_PRINTF( "TEXGEN_S = %4.3f, %4.3f, %4.3f, %4.3f\n", plane[0], plane[1], plane[2], plane[3] ); + + plane[0] = mat[0*4+1]; + plane[1] = mat[1*4+1]; + plane[2] = mat[2*4+1]; + plane[3] = mat[3*4+1]; + SetVertexParm( RENDERPARM_TEXGEN_0_T, plane ); + RENDERLOG_PRINTF( "TEXGEN_T = %4.3f, %4.3f, %4.3f, %4.3f\n", plane[0], plane[1], plane[2], plane[3] ); + + plane[0] = mat[0*4+3]; + plane[1] = mat[1*4+3]; + plane[2] = mat[2*4+3]; + plane[3] = mat[3*4+3]; + SetVertexParm( RENDERPARM_TEXGEN_0_Q, plane ); + RENDERLOG_PRINTF( "TEXGEN_Q = %4.3f, %4.3f, %4.3f, %4.3f\n", plane[0], plane[1], plane[2], plane[3] ); + + renderLog.Outdent(); + + } else if ( pStage->texture.texgen == TG_DIFFUSE_CUBE ) { + + // As far as I can tell, this is never used + idLib::Warning( "Using Diffuse Cube! Please contact Brian!" ); + + } else if ( pStage->texture.texgen == TG_GLASSWARP ) { + + // As far as I can tell, this is never used + idLib::Warning( "Using GlassWarp! Please contact Brian!" ); + } + + SetVertexParm( RENDERPARM_TEXGEN_0_ENABLED, useTexGenParm ); +} + +/* +================ +RB_FinishStageTexturing +================ +*/ +static void RB_FinishStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf ) { + + if ( pStage->texture.cinematic ) { + // unbind the extra bink textures + GL_SelectTexture( 1 ); + globalImages->BindNull(); + GL_SelectTexture( 2 ); + globalImages->BindNull(); + GL_SelectTexture( 0 ); + } + + if ( pStage->texture.texgen == TG_REFLECT_CUBE ) { + // see if there is also a bump map specified + const shaderStage_t *bumpStage = surf->material->GetBumpStage(); + if ( bumpStage != NULL ) { + // per-pixel reflection mapping with bump mapping + GL_SelectTexture( 1 ); + globalImages->BindNull(); + GL_SelectTexture( 0 ); + } else { + // per-pixel reflection mapping without bump mapping + } + renderProgManager.Unbind(); + } +} + +/* +========================================================================================= + +DEPTH BUFFER RENDERING + +========================================================================================= +*/ + +/* +================== +RB_FillDepthBufferGeneric +================== +*/ +static void RB_FillDepthBufferGeneric( const drawSurf_t * const * drawSurfs, int numDrawSurfs ) { + for ( int i = 0; i < numDrawSurfs; i++ ) { + const drawSurf_t * drawSurf = drawSurfs[i]; + const idMaterial * shader = drawSurf->material; + + // translucent surfaces don't put anything in the depth buffer and don't + // test against it, which makes them fail the mirror clip plane operation + if ( shader->Coverage() == MC_TRANSLUCENT ) { + continue; + } + + // get the expressions for conditionals / color / texcoords + const float * regs = drawSurf->shaderRegisters; + + // if all stages of a material have been conditioned off, don't do anything + int stage = 0; + for ( ; stage < shader->GetNumStages(); stage++ ) { + const shaderStage_t * pStage = shader->GetStage( stage ); + // check the stage enable condition + if ( regs[ pStage->conditionRegister ] != 0 ) { + break; + } + } + if ( stage == shader->GetNumStages() ) { + continue; + } + + // change the matrix if needed + if ( drawSurf->space != backEnd.currentSpace ) { + RB_SetMVP( drawSurf->space->mvp ); + + backEnd.currentSpace = drawSurf->space; + } + + uint64 surfGLState = 0; + + // set polygon offset if necessary + if ( shader->TestMaterialFlag( MF_POLYGONOFFSET ) ) { + surfGLState |= GLS_POLYGON_OFFSET; + GL_PolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset() ); + } + + // subviews will just down-modulate the color buffer + float color[4]; + if ( shader->GetSort() == SS_SUBVIEW ) { + surfGLState |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO | GLS_DEPTHFUNC_LESS; + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; + color[3] = 1.0f; + } else { + // others just draw black + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 1.0f; + } + + renderLog.OpenBlock( shader->GetName() ); + + bool drawSolid = false; + if ( shader->Coverage() == MC_OPAQUE ) { + drawSolid = true; + } else if ( shader->Coverage() == MC_PERFORATED ) { + // we may have multiple alpha tested stages + // if the only alpha tested stages are condition register omitted, + // draw a normal opaque surface + bool didDraw = false; + + // perforated surfaces may have multiple alpha tested stages + for ( stage = 0; stage < shader->GetNumStages(); stage++ ) { + const shaderStage_t *pStage = shader->GetStage(stage); + + if ( !pStage->hasAlphaTest ) { + continue; + } + + // check the stage enable condition + if ( regs[ pStage->conditionRegister ] == 0 ) { + continue; + } + + // if we at least tried to draw an alpha tested stage, + // we won't draw the opaque surface + didDraw = true; + + // set the alpha modulate + color[3] = regs[ pStage->color.registers[3] ]; + + // skip the entire stage if alpha would be black + if ( color[3] <= 0.0f ) { + continue; + } + + uint64 stageGLState = surfGLState; + + // set privatePolygonOffset if necessary + if ( pStage->privatePolygonOffset ) { + GL_PolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * pStage->privatePolygonOffset ); + stageGLState |= GLS_POLYGON_OFFSET; + } + + GL_Color( color ); + +#ifdef USE_CORE_PROFILE + GL_State( stageGLState ); + idVec4 alphaTestValue( regs[ pStage->alphaTestRegister ] ); + SetFragmentParm( RENDERPARM_ALPHA_TEST, alphaTestValue.ToFloatPtr() ); +#else + GL_State( stageGLState | GLS_ALPHATEST_FUNC_GREATER | GLS_ALPHATEST_MAKE_REF( idMath::Ftob( 255.0f * regs[ pStage->alphaTestRegister ] ) ) ); +#endif + + if ( drawSurf->jointCache ) { + renderProgManager.BindShader_TextureVertexColorSkinned(); + } else { + renderProgManager.BindShader_TextureVertexColor(); + } + + RB_SetVertexColorParms( SVC_IGNORE ); + + // bind the texture + GL_SelectTexture( 0 ); + pStage->texture.image->Bind(); + + // set texture matrix and texGens + RB_PrepareStageTexturing( pStage, drawSurf ); + + // must render with less-equal for Z-Cull to work properly + assert( ( GL_GetCurrentState() & GLS_DEPTHFUNC_BITS ) == GLS_DEPTHFUNC_LESS ); + + // draw it + RB_DrawElementsWithCounters( drawSurf ); + + // clean up + RB_FinishStageTexturing( pStage, drawSurf ); + + // unset privatePolygonOffset if necessary + if ( pStage->privatePolygonOffset ) { + GL_PolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset() ); + } + } + + if ( !didDraw ) { + drawSolid = true; + } + } + + // draw the entire surface solid + if ( drawSolid ) { + if ( shader->GetSort() == SS_SUBVIEW ) { + renderProgManager.BindShader_Color(); + GL_Color( color ); + GL_State( surfGLState ); + } else { + if ( drawSurf->jointCache ) { + renderProgManager.BindShader_DepthSkinned(); + } else { + renderProgManager.BindShader_Depth(); + } + GL_State( surfGLState | GLS_ALPHAMASK ); + } + + // must render with less-equal for Z-Cull to work properly + assert( ( GL_GetCurrentState() & GLS_DEPTHFUNC_BITS ) == GLS_DEPTHFUNC_LESS ); + + // draw it + RB_DrawElementsWithCounters( drawSurf ); + } + + renderLog.CloseBlock(); + } + +#ifdef USE_CORE_PROFILE + SetFragmentParm( RENDERPARM_ALPHA_TEST, vec4_zero.ToFloatPtr() ); +#endif +} + +/* +===================== +RB_FillDepthBufferFast + +Optimized fast path code. + +If there are subview surfaces, they must be guarded in the depth buffer to allow +the mirror / subview to show through underneath the current view rendering. + +Surfaces with perforated shaders need the full shader setup done, but should be +drawn after the opaque surfaces. + +The bulk of the surfaces should be simple opaque geometry that can be drawn very rapidly. + +If there are no subview surfaces, we could clear to black and use fast-Z rendering +on the 360. +===================== +*/ +static void RB_FillDepthBufferFast( drawSurf_t **drawSurfs, int numDrawSurfs ) { + if ( numDrawSurfs == 0 ) { + return; + } + + // if we are just doing 2D rendering, no need to fill the depth buffer + if ( backEnd.viewDef->viewEntitys == NULL ) { + return; + } + + renderLog.OpenMainBlock( MRB_FILL_DEPTH_BUFFER ); + renderLog.OpenBlock( "RB_FillDepthBufferFast" ); + + GL_StartDepthPass( backEnd.viewDef->scissor ); + + // force MVP change on first surface + backEnd.currentSpace = NULL; + + // draw all the subview surfaces, which will already be at the start of the sorted list, + // with the general purpose path + GL_State( GLS_DEFAULT ); + + int surfNum; + for ( surfNum = 0; surfNum < numDrawSurfs; surfNum++ ) { + if ( drawSurfs[surfNum]->material->GetSort() != SS_SUBVIEW ) { + break; + } + RB_FillDepthBufferGeneric( &drawSurfs[surfNum], 1 ); + } + + const drawSurf_t ** perforatedSurfaces = (const drawSurf_t ** )_alloca( numDrawSurfs * sizeof( drawSurf_t * ) ); + int numPerforatedSurfaces = 0; + + // draw all the opaque surfaces and build up a list of perforated surfaces that + // we will defer drawing until all opaque surfaces are done + GL_State( GLS_DEFAULT ); + + // continue checking past the subview surfaces + for ( ; surfNum < numDrawSurfs; surfNum++ ) { + const drawSurf_t * surf = drawSurfs[ surfNum ]; + const idMaterial * shader = surf->material; + + // translucent surfaces don't put anything in the depth buffer + if ( shader->Coverage() == MC_TRANSLUCENT ) { + continue; + } + if ( shader->Coverage() == MC_PERFORATED ) { + // save for later drawing + perforatedSurfaces[ numPerforatedSurfaces ] = surf; + numPerforatedSurfaces++; + continue; + } + + // set polygon offset? + + // set mvp matrix + if ( surf->space != backEnd.currentSpace ) { + RB_SetMVP( surf->space->mvp ); + backEnd.currentSpace = surf->space; + } + + renderLog.OpenBlock( shader->GetName() ); + + if ( surf->jointCache ) { + renderProgManager.BindShader_DepthSkinned(); + } else { + renderProgManager.BindShader_Depth(); + } + + // must render with less-equal for Z-Cull to work properly + assert( ( GL_GetCurrentState() & GLS_DEPTHFUNC_BITS ) == GLS_DEPTHFUNC_LESS ); + + // draw it solid + RB_DrawElementsWithCounters( surf ); + + renderLog.CloseBlock(); + } + + // draw all perforated surfaces with the general code path + if ( numPerforatedSurfaces > 0 ) { + RB_FillDepthBufferGeneric( perforatedSurfaces, numPerforatedSurfaces ); + } + + // Allow platform specific data to be collected after the depth pass. + GL_FinishDepthPass(); + + renderLog.CloseBlock(); + renderLog.CloseMainBlock(); +} + +/* +========================================================================================= + +GENERAL INTERACTION RENDERING + +========================================================================================= +*/ + +const int INTERACTION_TEXUNIT_BUMP = 0; +const int INTERACTION_TEXUNIT_FALLOFF = 1; +const int INTERACTION_TEXUNIT_PROJECTION = 2; +const int INTERACTION_TEXUNIT_DIFFUSE = 3; +const int INTERACTION_TEXUNIT_SPECULAR = 4; + +/* +================== +RB_SetupInteractionStage +================== +*/ +static void RB_SetupInteractionStage( const shaderStage_t *surfaceStage, const float *surfaceRegs, const float lightColor[4], + idVec4 matrix[2], float color[4] ) { + + if ( surfaceStage->texture.hasMatrix ) { + matrix[0][0] = surfaceRegs[surfaceStage->texture.matrix[0][0]]; + matrix[0][1] = surfaceRegs[surfaceStage->texture.matrix[0][1]]; + matrix[0][2] = 0.0f; + matrix[0][3] = surfaceRegs[surfaceStage->texture.matrix[0][2]]; + + matrix[1][0] = surfaceRegs[surfaceStage->texture.matrix[1][0]]; + matrix[1][1] = surfaceRegs[surfaceStage->texture.matrix[1][1]]; + matrix[1][2] = 0.0f; + matrix[1][3] = surfaceRegs[surfaceStage->texture.matrix[1][2]]; + + // we attempt to keep scrolls from generating incredibly large texture values, but + // center rotations and center scales can still generate offsets that need to be > 1 + if ( matrix[0][3] < -40.0f || matrix[0][3] > 40.0f ) { + matrix[0][3] -= idMath::Ftoi( matrix[0][3] ); + } + if ( matrix[1][3] < -40.0f || matrix[1][3] > 40.0f ) { + matrix[1][3] -= idMath::Ftoi( matrix[1][3] ); + } + } else { + matrix[0][0] = 1.0f; + matrix[0][1] = 0.0f; + matrix[0][2] = 0.0f; + matrix[0][3] = 0.0f; + + matrix[1][0] = 0.0f; + matrix[1][1] = 1.0f; + matrix[1][2] = 0.0f; + matrix[1][3] = 0.0f; + } + + if ( color != NULL ) { + for ( int i = 0; i < 4; i++ ) { + // clamp here, so cards with a greater range don't look different. + // we could perform overbrighting like we do for lights, but + // it doesn't currently look worth it. + color[i] = idMath::ClampFloat( 0.0f, 1.0f, surfaceRegs[surfaceStage->color.registers[i]] ) * lightColor[i]; + } + } +} + +/* +================= +RB_DrawSingleInteraction +================= +*/ +static void RB_DrawSingleInteraction( drawInteraction_t * din ) { + if ( din->bumpImage == NULL ) { + // stage wasn't actually an interaction + return; + } + + if ( din->diffuseImage == NULL || r_skipDiffuse.GetBool() ) { + // this isn't a YCoCg black, but it doesn't matter, because + // the diffuseColor will also be 0 + din->diffuseImage = globalImages->blackImage; + } + if ( din->specularImage == NULL || r_skipSpecular.GetBool() || din->ambientLight ) { + din->specularImage = globalImages->blackImage; + } + if ( r_skipBump.GetBool() ) { + din->bumpImage = globalImages->flatNormalMap; + } + + // if we wouldn't draw anything, don't call the Draw function + const bool diffuseIsBlack = ( din->diffuseImage == globalImages->blackImage ) + || ( ( din->diffuseColor[0] <= 0 ) && ( din->diffuseColor[1] <= 0 ) && ( din->diffuseColor[2] <= 0 ) ); + const bool specularIsBlack = ( din->specularImage == globalImages->blackImage ) + || ( ( din->specularColor[0] <= 0 ) && ( din->specularColor[1] <= 0 ) && ( din->specularColor[2] <= 0 ) ); + if ( diffuseIsBlack && specularIsBlack ) { + return; + } + + // bump matrix + SetVertexParm( RENDERPARM_BUMPMATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); + SetVertexParm( RENDERPARM_BUMPMATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); + + // diffuse matrix + SetVertexParm( RENDERPARM_DIFFUSEMATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); + SetVertexParm( RENDERPARM_DIFFUSEMATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); + + // specular matrix + SetVertexParm( RENDERPARM_SPECULARMATRIX_S, din->specularMatrix[0].ToFloatPtr() ); + SetVertexParm( RENDERPARM_SPECULARMATRIX_T, din->specularMatrix[1].ToFloatPtr() ); + + RB_SetVertexColorParms( din->vertexColor ); + + SetFragmentParm( RENDERPARM_DIFFUSEMODIFIER, din->diffuseColor.ToFloatPtr() ); + SetFragmentParm( RENDERPARM_SPECULARMODIFIER, din->specularColor.ToFloatPtr() ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( INTERACTION_TEXUNIT_BUMP ); + din->bumpImage->Bind(); + + // texture 3 is the per-surface diffuse map + GL_SelectTexture( INTERACTION_TEXUNIT_DIFFUSE ); + din->diffuseImage->Bind(); + + // texture 4 is the per-surface specular map + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR ); + din->specularImage->Bind(); + + RB_DrawElementsWithCounters( din->surf ); +} + +/* +================= +RB_SetupForFastPathInteractions + +These are common for all fast path surfaces +================= +*/ +static void RB_SetupForFastPathInteractions( const idVec4 & diffuseColor, const idVec4 & specularColor ) { + const idVec4 sMatrix( 1, 0, 0, 0 ); + const idVec4 tMatrix( 0, 1, 0, 0 ); + + // bump matrix + SetVertexParm( RENDERPARM_BUMPMATRIX_S, sMatrix.ToFloatPtr() ); + SetVertexParm( RENDERPARM_BUMPMATRIX_T, tMatrix.ToFloatPtr() ); + + // diffuse matrix + SetVertexParm( RENDERPARM_DIFFUSEMATRIX_S, sMatrix.ToFloatPtr() ); + SetVertexParm( RENDERPARM_DIFFUSEMATRIX_T, tMatrix.ToFloatPtr() ); + + // specular matrix + SetVertexParm( RENDERPARM_SPECULARMATRIX_S, sMatrix.ToFloatPtr() ); + SetVertexParm( RENDERPARM_SPECULARMATRIX_T, tMatrix.ToFloatPtr() ); + + RB_SetVertexColorParms( SVC_IGNORE ); + + SetFragmentParm( RENDERPARM_DIFFUSEMODIFIER, diffuseColor.ToFloatPtr() ); + SetFragmentParm( RENDERPARM_SPECULARMODIFIER, specularColor.ToFloatPtr() ); +} + +/* +============= +RB_RenderInteractions + +With added sorting and trivial path work. +============= +*/ +static void RB_RenderInteractions( const drawSurf_t *surfList, const viewLight_t * vLight, int depthFunc, bool performStencilTest, bool useLightDepthBounds ) { + if ( surfList == NULL ) { + return; + } + + // change the scissor if needed, it will be constant across all the surfaces lit by the light + if ( !backEnd.currentScissor.Equals( vLight->scissorRect ) && r_useScissor.GetBool() ) { + GL_Scissor( backEnd.viewDef->viewport.x1 + vLight->scissorRect.x1, + backEnd.viewDef->viewport.y1 + vLight->scissorRect.y1, + vLight->scissorRect.x2 + 1 - vLight->scissorRect.x1, + vLight->scissorRect.y2 + 1 - vLight->scissorRect.y1 ); + backEnd.currentScissor = vLight->scissorRect; + } + + // perform setup here that will be constant for all interactions + if ( performStencilTest ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | depthFunc | GLS_STENCIL_FUNC_EQUAL | GLS_STENCIL_MAKE_REF( STENCIL_SHADOW_TEST_VALUE ) | GLS_STENCIL_MAKE_MASK( STENCIL_SHADOW_MASK_VALUE ) ); + + } else { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | depthFunc | GLS_STENCIL_FUNC_ALWAYS ); + } + + // some rare lights have multiple animating stages, loop over them outside the surface list + const idMaterial * lightShader = vLight->lightShader; + const float * lightRegs = vLight->shaderRegisters; + + drawInteraction_t inter = {}; + inter.ambientLight = lightShader->IsAmbientLight(); + + //--------------------------------- + // Split out the complex surfaces from the fast-path surfaces + // so we can do the fast path ones all in a row. + // The surfaces should already be sorted by space because they + // are added single-threaded, and there is only a negligable amount + // of benefit to trying to sort by materials. + //--------------------------------- + static const int MAX_INTERACTIONS_PER_LIGHT = 1024; + static const int MAX_COMPLEX_INTERACTIONS_PER_LIGHT = 128; + idStaticList< const drawSurf_t *, MAX_INTERACTIONS_PER_LIGHT > allSurfaces; + idStaticList< const drawSurf_t *, MAX_COMPLEX_INTERACTIONS_PER_LIGHT > complexSurfaces; + for ( const drawSurf_t * walk = surfList; walk != NULL; walk = walk->nextOnLight ) { + + // make sure the triangle culling is done + if ( walk->shadowVolumeState != SHADOWVOLUME_DONE ) { + assert( walk->shadowVolumeState == SHADOWVOLUME_UNFINISHED || walk->shadowVolumeState == SHADOWVOLUME_DONE ); + + uint64 start = Sys_Microseconds(); + while ( walk->shadowVolumeState == SHADOWVOLUME_UNFINISHED ) { + Sys_Yield(); + } + uint64 end = Sys_Microseconds(); + + backEnd.pc.shadowMicroSec += end - start; + } + + const idMaterial * surfaceShader = walk->material; + if ( surfaceShader->GetFastPathBumpImage() ) { + allSurfaces.Append( walk ); + } else { + complexSurfaces.Append( walk ); + } + } + for ( int i = 0; i < complexSurfaces.Num(); i++ ) { + allSurfaces.Append( complexSurfaces[i] ); + } + + bool lightDepthBoundsDisabled = false; + + for ( int lightStageNum = 0; lightStageNum < lightShader->GetNumStages(); lightStageNum++ ) { + const shaderStage_t *lightStage = lightShader->GetStage( lightStageNum ); + + // ignore stages that fail the condition + if ( !lightRegs[ lightStage->conditionRegister ] ) { + continue; + } + + const float lightScale = r_lightScale.GetFloat(); + const idVec4 lightColor( + lightScale * lightRegs[ lightStage->color.registers[0] ], + lightScale * lightRegs[ lightStage->color.registers[1] ], + lightScale * lightRegs[ lightStage->color.registers[2] ], + lightRegs[ lightStage->color.registers[3] ] ); + // apply the world-global overbright and the 2x factor for specular + const idVec4 diffuseColor = lightColor; + const idVec4 specularColor = lightColor * 2.0f; + + float lightTextureMatrix[16]; + if ( lightStage->texture.hasMatrix ) { + RB_GetShaderTextureMatrix( lightRegs, &lightStage->texture, lightTextureMatrix ); + } + + // texture 1 will be the light falloff texture + GL_SelectTexture( INTERACTION_TEXUNIT_FALLOFF ); + vLight->falloffImage->Bind(); + + // texture 2 will be the light projection texture + GL_SelectTexture( INTERACTION_TEXUNIT_PROJECTION ); + lightStage->texture.image->Bind(); + + // force the light textures to not use anisotropic filtering, which is wasted on them + // all of the texture sampler parms should be constant for all interactions, only + // the actual texture image bindings will change + + //---------------------------------- + // For all surfaces on this light list, generate an interaction for this light stage + //---------------------------------- + + // setup renderparms assuming we will be drawing trivial surfaces first + RB_SetupForFastPathInteractions( diffuseColor, specularColor ); + + // even if the space does not change between light stages, each light stage may need a different lightTextureMatrix baked in + backEnd.currentSpace = NULL; + + for ( int sortedSurfNum = 0; sortedSurfNum < allSurfaces.Num(); sortedSurfNum++ ) { + const drawSurf_t * const surf = allSurfaces[ sortedSurfNum ]; + + // select the render prog + if ( lightShader->IsAmbientLight() ) { + if ( surf->jointCache ) { + renderProgManager.BindShader_InteractionAmbientSkinned(); + } else { + renderProgManager.BindShader_InteractionAmbient(); + } + } else { + if ( surf->jointCache ) { + renderProgManager.BindShader_InteractionSkinned(); + } else { + renderProgManager.BindShader_Interaction(); + } + } + + const idMaterial * surfaceShader = surf->material; + const float * surfaceRegs = surf->shaderRegisters; + + inter.surf = surf; + + // change the MVP matrix, view/light origin and light projection vectors if needed + if ( surf->space != backEnd.currentSpace ) { + backEnd.currentSpace = surf->space; + + // turn off the light depth bounds test if this model is rendered with a depth hack + if ( useLightDepthBounds ) { + if ( !surf->space->weaponDepthHack && surf->space->modelDepthHack == 0.0f ) { + if ( lightDepthBoundsDisabled ) { + GL_DepthBoundsTest( vLight->scissorRect.zmin, vLight->scissorRect.zmax ); + lightDepthBoundsDisabled = false; + } + } else { + if ( !lightDepthBoundsDisabled ) { + GL_DepthBoundsTest( 0.0f, 0.0f ); + lightDepthBoundsDisabled = true; + } + } + } + + // model-view-projection + RB_SetMVP( surf->space->mvp ); + + // tranform the light/view origin into model local space + idVec4 localLightOrigin( 0.0f ); + idVec4 localViewOrigin( 1.0f ); + R_GlobalPointToLocal( surf->space->modelMatrix, vLight->globalLightOrigin, localLightOrigin.ToVec3() ); + R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.viewDef->renderView.vieworg, localViewOrigin.ToVec3() ); + + // set the local light/view origin + SetVertexParm( RENDERPARM_LOCALLIGHTORIGIN, localLightOrigin.ToFloatPtr() ); + SetVertexParm( RENDERPARM_LOCALVIEWORIGIN, localViewOrigin.ToFloatPtr() ); + + // transform the light project into model local space + idPlane lightProjection[4]; + for ( int i = 0; i < 4; i++ ) { + R_GlobalPlaneToLocal( surf->space->modelMatrix, vLight->lightProject[i], lightProjection[i] ); + } + + // optionally multiply the local light projection by the light texture matrix + if ( lightStage->texture.hasMatrix ) { + RB_BakeTextureMatrixIntoTexgen( lightProjection, lightTextureMatrix ); + } + + // set the light projection + SetVertexParm( RENDERPARM_LIGHTPROJECTION_S, lightProjection[0].ToFloatPtr() ); + SetVertexParm( RENDERPARM_LIGHTPROJECTION_T, lightProjection[1].ToFloatPtr() ); + SetVertexParm( RENDERPARM_LIGHTPROJECTION_Q, lightProjection[2].ToFloatPtr() ); + SetVertexParm( RENDERPARM_LIGHTFALLOFF_S, lightProjection[3].ToFloatPtr() ); + } + + // check for the fast path + if ( surfaceShader->GetFastPathBumpImage() && !r_skipInteractionFastPath.GetBool() ) { + renderLog.OpenBlock( surf->material->GetName() ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( INTERACTION_TEXUNIT_BUMP ); + surfaceShader->GetFastPathBumpImage()->Bind(); + + // texture 3 is the per-surface diffuse map + GL_SelectTexture( INTERACTION_TEXUNIT_DIFFUSE ); + surfaceShader->GetFastPathDiffuseImage()->Bind(); + + // texture 4 is the per-surface specular map + GL_SelectTexture( INTERACTION_TEXUNIT_SPECULAR ); + surfaceShader->GetFastPathSpecularImage()->Bind(); + + RB_DrawElementsWithCounters( surf ); + + renderLog.CloseBlock(); + continue; + } + + renderLog.OpenBlock( surf->material->GetName() ); + + inter.bumpImage = NULL; + inter.specularImage = NULL; + inter.diffuseImage = NULL; + inter.diffuseColor[0] = inter.diffuseColor[1] = inter.diffuseColor[2] = inter.diffuseColor[3] = 0; + inter.specularColor[0] = inter.specularColor[1] = inter.specularColor[2] = inter.specularColor[3] = 0; + + // go through the individual surface stages + // + // This is somewhat arcane because of the old support for video cards that had to render + // interactions in multiple passes. + // + // We also have the very rare case of some materials that have conditional interactions + // for the "hell writing" that can be shined on them. + for ( int surfaceStageNum = 0; surfaceStageNum < surfaceShader->GetNumStages(); surfaceStageNum++ ) { + const shaderStage_t *surfaceStage = surfaceShader->GetStage( surfaceStageNum ); + + switch( surfaceStage->lighting ) { + case SL_COVERAGE: { + // ignore any coverage stages since they should only be used for the depth fill pass + // for diffuse stages that use alpha test. + break; + } + case SL_AMBIENT: { + // ignore ambient stages while drawing interactions + break; + } + case SL_BUMP: { + // ignore stage that fails the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + break; + } + // draw any previous interaction + if ( inter.bumpImage != NULL ) { + RB_DrawSingleInteraction( &inter ); + } + inter.bumpImage = surfaceStage->texture.image; + inter.diffuseImage = NULL; + inter.specularImage = NULL; + RB_SetupInteractionStage( surfaceStage, surfaceRegs, NULL, + inter.bumpMatrix, NULL ); + break; + } + case SL_DIFFUSE: { + // ignore stage that fails the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + break; + } + // draw any previous interaction + if ( inter.diffuseImage != NULL ) { + RB_DrawSingleInteraction( &inter ); + } + inter.diffuseImage = surfaceStage->texture.image; + inter.vertexColor = surfaceStage->vertexColor; + RB_SetupInteractionStage( surfaceStage, surfaceRegs, diffuseColor.ToFloatPtr(), + inter.diffuseMatrix, inter.diffuseColor.ToFloatPtr() ); + break; + } + case SL_SPECULAR: { + // ignore stage that fails the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + break; + } + // draw any previous interaction + if ( inter.specularImage != NULL ) { + RB_DrawSingleInteraction( &inter ); + } + inter.specularImage = surfaceStage->texture.image; + inter.vertexColor = surfaceStage->vertexColor; + RB_SetupInteractionStage( surfaceStage, surfaceRegs, specularColor.ToFloatPtr(), + inter.specularMatrix, inter.specularColor.ToFloatPtr() ); + break; + } + } + } + + // draw the final interaction + RB_DrawSingleInteraction( &inter ); + + renderLog.CloseBlock(); + } + } + + if ( useLightDepthBounds && lightDepthBoundsDisabled ) { + GL_DepthBoundsTest( vLight->scissorRect.zmin, vLight->scissorRect.zmax ); + } + + renderProgManager.Unbind(); +} + +/* +============================================================================================== + +STENCIL SHADOW RENDERING + +============================================================================================== +*/ + +/* +===================== +RB_StencilShadowPass + +The stencil buffer should have been set to 128 on any surfaces that might receive shadows. +===================== +*/ +static void RB_StencilShadowPass( const drawSurf_t *drawSurfs, const viewLight_t * vLight ) { + if ( r_skipShadows.GetBool() ) { + return; + } + + if ( drawSurfs == NULL ) { + return; + } + + RENDERLOG_PRINTF( "---------- RB_StencilShadowPass ----------\n" ); + + renderProgManager.BindShader_Shadow(); + + GL_SelectTexture( 0 ); + globalImages->BindNull(); + + uint64 glState = 0; + + // for visualizing the shadows + if ( r_showShadows.GetInteger() ) { + // set the debug shadow color + SetFragmentParm( RENDERPARM_COLOR, colorMagenta.ToFloatPtr() ); + if ( r_showShadows.GetInteger() == 2 ) { + // draw filled in + glState = GLS_DEPTHMASK | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_LESS; + } else { + // draw as lines, filling the depth buffer + glState = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS; + } + } else { + // don't write to the color or depth buffer, just the stencil buffer + glState = GLS_DEPTHMASK | GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHFUNC_LESS; + } + + GL_PolygonOffset( r_shadowPolygonFactor.GetFloat(), -r_shadowPolygonOffset.GetFloat() ); + + // the actual stencil func will be set in the draw code, but we need to make sure it isn't + // disabled here, and that the value will get reset for the interactions without looking + // like a no-change-required + GL_State( glState | GLS_STENCIL_OP_FAIL_KEEP | GLS_STENCIL_OP_ZFAIL_KEEP | GLS_STENCIL_OP_PASS_INCR | + GLS_STENCIL_MAKE_REF( STENCIL_SHADOW_TEST_VALUE ) | GLS_STENCIL_MAKE_MASK( STENCIL_SHADOW_MASK_VALUE ) | GLS_POLYGON_OFFSET ); + + // Two Sided Stencil reduces two draw calls to one for slightly faster shadows + GL_Cull( CT_TWO_SIDED ); + + + // process the chain of shadows with the current rendering state + backEnd.currentSpace = NULL; + + for ( const drawSurf_t * drawSurf = drawSurfs; drawSurf != NULL; drawSurf = drawSurf->nextOnLight ) { + if ( drawSurf->scissorRect.IsEmpty() ) { + continue; // !@# FIXME: find out why this is sometimes being hit! + // temporarily jump over the scissor and draw so the gl error callback doesn't get hit + } + + // make sure the shadow volume is done + if ( drawSurf->shadowVolumeState != SHADOWVOLUME_DONE ) { + assert( drawSurf->shadowVolumeState == SHADOWVOLUME_UNFINISHED || drawSurf->shadowVolumeState == SHADOWVOLUME_DONE ); + + uint64 start = Sys_Microseconds(); + while ( drawSurf->shadowVolumeState == SHADOWVOLUME_UNFINISHED ) { + Sys_Yield(); + } + uint64 end = Sys_Microseconds(); + + backEnd.pc.shadowMicroSec += end - start; + } + + if ( drawSurf->numIndexes == 0 ) { + continue; // a job may have created an empty shadow volume + } + + if ( !backEnd.currentScissor.Equals( drawSurf->scissorRect ) && r_useScissor.GetBool() ) { + // change the scissor + GL_Scissor( backEnd.viewDef->viewport.x1 + drawSurf->scissorRect.x1, + backEnd.viewDef->viewport.y1 + drawSurf->scissorRect.y1, + drawSurf->scissorRect.x2 + 1 - drawSurf->scissorRect.x1, + drawSurf->scissorRect.y2 + 1 - drawSurf->scissorRect.y1 ); + backEnd.currentScissor = drawSurf->scissorRect; + } + + if ( drawSurf->space != backEnd.currentSpace ) { + // change the matrix + RB_SetMVP( drawSurf->space->mvp ); + + // set the local light position to allow the vertex program to project the shadow volume end cap to infinity + idVec4 localLight( 0.0f ); + R_GlobalPointToLocal( drawSurf->space->modelMatrix, vLight->globalLightOrigin, localLight.ToVec3() ); + SetVertexParm( RENDERPARM_LOCALLIGHTORIGIN, localLight.ToFloatPtr() ); + + backEnd.currentSpace = drawSurf->space; + } + + if ( r_showShadows.GetInteger() == 0 ) { + if ( drawSurf->jointCache ) { + renderProgManager.BindShader_ShadowSkinned(); + } else { + renderProgManager.BindShader_Shadow(); + } + } else { + if ( drawSurf->jointCache ) { + renderProgManager.BindShader_ShadowDebugSkinned(); + } else { + renderProgManager.BindShader_ShadowDebug(); + } + } + + // set depth bounds per shadow + if ( r_useShadowDepthBounds.GetBool() ) { + GL_DepthBoundsTest( drawSurf->scissorRect.zmin, drawSurf->scissorRect.zmax ); + } + + // Determine whether or not the shadow volume needs to be rendered with Z-pass or + // Z-fail. It is worthwhile to spend significant resources to reduce the number of + // cases where shadow volumes need to be rendered with Z-fail because Z-fail + // rendering can be significantly slower even on today's hardware. For instance, + // on NVIDIA hardware Z-fail rendering causes the Z-Cull to be used in reverse: + // Z-near becomes Z-far (trivial accept becomes trivial reject). Using the Z-Cull + // in reverse is far less efficient because the Z-Cull only stores Z-near per 16x16 + // pixels while the Z-far is stored per 4x2 pixels. (The Z-near coallesce buffer + // which has 4x4 granularity is only used when updating the depth which is not the + // case for shadow volumes.) Note that it is also important to NOT use a Z-Cull + // reconstruct because that would clear the Z-near of the Z-Cull which results in + // no trivial rejection for Z-fail stencil shadow rendering. + + const bool renderZPass = ( drawSurf->renderZFail == 0 ) || r_forceZPassStencilShadows.GetBool(); + + + if ( renderZPass ) { + // Z-pass + qglStencilOpSeparate( GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR ); + qglStencilOpSeparate( GL_BACK, GL_KEEP, GL_KEEP, GL_DECR ); + } else if ( r_useStencilShadowPreload.GetBool() ) { + // preload + Z-pass + qglStencilOpSeparate( GL_FRONT, GL_KEEP, GL_DECR, GL_DECR ); + qglStencilOpSeparate( GL_BACK, GL_KEEP, GL_INCR, GL_INCR ); + } else { + // Z-fail + } + + + // get vertex buffer + const vertCacheHandle_t vbHandle = drawSurf->shadowCache; + idVertexBuffer * vertexBuffer; + if ( vertexCache.CacheIsStatic( vbHandle ) ) { + vertexBuffer = &vertexCache.staticData.vertexBuffer; + } else { + const uint64 frameNum = (int)( vbHandle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( frameNum != ( ( vertexCache.currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + idLib::Warning( "RB_DrawElementsWithCounters, vertexBuffer == NULL" ); + continue; + } + vertexBuffer = &vertexCache.frameData[vertexCache.drawListNum].vertexBuffer; + } + const int vertOffset = (int)( vbHandle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + + // get index buffer + const vertCacheHandle_t ibHandle = drawSurf->indexCache; + idIndexBuffer * indexBuffer; + if ( vertexCache.CacheIsStatic( ibHandle ) ) { + indexBuffer = &vertexCache.staticData.indexBuffer; + } else { + const uint64 frameNum = (int)( ibHandle >> VERTCACHE_FRAME_SHIFT ) & VERTCACHE_FRAME_MASK; + if ( frameNum != ( ( vertexCache.currentFrame - 1 ) & VERTCACHE_FRAME_MASK ) ) { + idLib::Warning( "RB_DrawElementsWithCounters, indexBuffer == NULL" ); + continue; + } + indexBuffer = &vertexCache.frameData[vertexCache.drawListNum].indexBuffer; + } + const uint64 indexOffset = (int)( ibHandle >> VERTCACHE_OFFSET_SHIFT ) & VERTCACHE_OFFSET_MASK; + + RENDERLOG_PRINTF( "Binding Buffers: %p %p\n", vertexBuffer, indexBuffer ); + + + if ( backEnd.glState.currentIndexBuffer != (GLuint)indexBuffer->GetAPIObject() || !r_useStateCaching.GetBool() ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLuint)indexBuffer->GetAPIObject() ); + backEnd.glState.currentIndexBuffer = (GLuint)indexBuffer->GetAPIObject(); + } + + if ( drawSurf->jointCache ) { + assert( renderProgManager.ShaderUsesJoints() ); + + idJointBuffer jointBuffer; + if ( !vertexCache.GetJointBuffer( drawSurf->jointCache, &jointBuffer ) ) { + idLib::Warning( "RB_DrawElementsWithCounters, jointBuffer == NULL" ); + continue; + } + assert( ( jointBuffer.GetOffset() & ( glConfig.uniformBufferOffsetAlignment - 1 ) ) == 0 ); + + const GLuint ubo = reinterpret_cast< GLuint >( jointBuffer.GetAPIObject() ); + qglBindBufferRange( GL_UNIFORM_BUFFER, 0, ubo, jointBuffer.GetOffset(), jointBuffer.GetNumJoints() * sizeof( idJointMat ) ); + + if ( ( backEnd.glState.vertexLayout != LAYOUT_DRAW_SHADOW_VERT_SKINNED) || ( backEnd.glState.currentVertexBuffer != (GLuint)vertexBuffer->GetAPIObject() ) || !r_useStateCaching.GetBool() ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, (GLuint)vertexBuffer->GetAPIObject() ); + backEnd.glState.currentVertexBuffer = (GLuint)vertexBuffer->GetAPIObject(); + + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_VERTEX ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_NORMAL ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_COLOR ); + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_COLOR2 ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_ST ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_TANGENT ); + + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_VERTEX, 4, GL_FLOAT, GL_FALSE, sizeof( idShadowVertSkinned ), (void *)( SHADOWVERTSKINNED_XYZW_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( idShadowVertSkinned ), (void *)( SHADOWVERTSKINNED_COLOR_OFFSET ) ); + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_COLOR2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( idShadowVertSkinned ), (void *)( SHADOWVERTSKINNED_COLOR2_OFFSET ) ); + + backEnd.glState.vertexLayout = LAYOUT_DRAW_SHADOW_VERT_SKINNED; + } + + } else { + + if ( ( backEnd.glState.vertexLayout != LAYOUT_DRAW_SHADOW_VERT ) || ( backEnd.glState.currentVertexBuffer != (GLuint)vertexBuffer->GetAPIObject() ) || !r_useStateCaching.GetBool() ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, (GLuint)vertexBuffer->GetAPIObject() ); + backEnd.glState.currentVertexBuffer = (GLuint)vertexBuffer->GetAPIObject(); + + qglEnableVertexAttribArrayARB( PC_ATTRIB_INDEX_VERTEX ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_NORMAL ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_COLOR ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_COLOR2 ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_ST ); + qglDisableVertexAttribArrayARB( PC_ATTRIB_INDEX_TANGENT ); + + qglVertexAttribPointerARB( PC_ATTRIB_INDEX_VERTEX, 4, GL_FLOAT, GL_FALSE, sizeof( idShadowVert ), (void *)( SHADOWVERT_XYZW_OFFSET ) ); + + backEnd.glState.vertexLayout = LAYOUT_DRAW_SHADOW_VERT; + } + } + + renderProgManager.CommitUniforms(); + + if ( drawSurf->jointCache ) { + qglDrawElementsBaseVertex( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : drawSurf->numIndexes, GL_INDEX_TYPE, (triIndex_t *)indexOffset, vertOffset / sizeof( idShadowVertSkinned ) ); + } else { + qglDrawElementsBaseVertex( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : drawSurf->numIndexes, GL_INDEX_TYPE, (triIndex_t *)indexOffset, vertOffset / sizeof( idShadowVert ) ); + } + + if ( !renderZPass && r_useStencilShadowPreload.GetBool() ) { + // render again with Z-pass + qglStencilOpSeparate( GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR ); + qglStencilOpSeparate( GL_BACK, GL_KEEP, GL_KEEP, GL_DECR ); + + if ( drawSurf->jointCache ) { + qglDrawElementsBaseVertex( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : drawSurf->numIndexes, GL_INDEX_TYPE, (triIndex_t *)indexOffset, vertOffset / sizeof ( idShadowVertSkinned ) ); + } else { + qglDrawElementsBaseVertex( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : drawSurf->numIndexes, GL_INDEX_TYPE, (triIndex_t *)indexOffset, vertOffset / sizeof ( idShadowVert ) ); + } + } + } + + // cleanup the shadow specific rendering state + + GL_Cull( CT_FRONT_SIDED ); + + // reset depth bounds + if ( r_useShadowDepthBounds.GetBool() ) { + if ( r_useLightDepthBounds.GetBool() ) { + GL_DepthBoundsTest( vLight->scissorRect.zmin, vLight->scissorRect.zmax ); + } else { + GL_DepthBoundsTest( 0.0f, 0.0f ); + } + } +} + +/* +================== +RB_StencilSelectLight + +Deform the zeroOneCubeModel to exactly cover the light volume. Render the deformed cube model to the stencil buffer in +such a way that only fragments that are directly visible and contained within the volume will be written creating a +mask to be used by the following stencil shadow and draw interaction passes. +================== +*/ +static void RB_StencilSelectLight( const viewLight_t * vLight ) { + renderLog.OpenBlock( "Stencil Select" ); + + // enable the light scissor + if ( !backEnd.currentScissor.Equals( vLight->scissorRect ) && r_useScissor.GetBool() ) { + GL_Scissor( backEnd.viewDef->viewport.x1 + vLight->scissorRect.x1, + backEnd.viewDef->viewport.y1 + vLight->scissorRect.y1, + vLight->scissorRect.x2 + 1 - vLight->scissorRect.x1, + vLight->scissorRect.y2 + 1 - vLight->scissorRect.y1 ); + backEnd.currentScissor = vLight->scissorRect; + } + + // clear stencil buffer to 0 (not drawable) + uint64 glStateMinusStencil = GL_GetCurrentStateMinusStencil(); + GL_State( glStateMinusStencil | GLS_STENCIL_FUNC_ALWAYS | GLS_STENCIL_MAKE_REF( STENCIL_SHADOW_TEST_VALUE ) | GLS_STENCIL_MAKE_MASK( STENCIL_SHADOW_MASK_VALUE ) ); // make sure stencil mask passes for the clear + GL_Clear( false, false, true, 0, 0.0f, 0.0f, 0.0f, 0.0f ); // clear to 0 for stencil select + + // set the depthbounds + GL_DepthBoundsTest( vLight->scissorRect.zmin, vLight->scissorRect.zmax ); + + + GL_State( GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS | GLS_STENCIL_FUNC_ALWAYS | GLS_STENCIL_MAKE_REF( STENCIL_SHADOW_TEST_VALUE ) | GLS_STENCIL_MAKE_MASK( STENCIL_SHADOW_MASK_VALUE ) ); + GL_Cull( CT_TWO_SIDED ); + + renderProgManager.BindShader_Depth(); + + // set the matrix for deforming the 'zeroOneCubeModel' into the frustum to exactly cover the light volume + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( backEnd.viewDef->worldSpace.mvp, vLight->inverseBaseLightProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + + // two-sided stencil test + qglStencilOpSeparate( GL_FRONT, GL_KEEP, GL_REPLACE, GL_ZERO ); + qglStencilOpSeparate( GL_BACK, GL_KEEP, GL_ZERO, GL_REPLACE ); + + RB_DrawElementsWithCounters( &backEnd.zeroOneCubeSurface ); + + // reset stencil state + + GL_Cull( CT_FRONT_SIDED ); + + renderProgManager.Unbind(); + + + // unset the depthbounds + GL_DepthBoundsTest( 0.0f, 0.0f ); + + renderLog.CloseBlock(); +} + +/* +============================================================================================== + +DRAW INTERACTIONS + +============================================================================================== +*/ +/* +================== +RB_DrawInteractions +================== +*/ +static void RB_DrawInteractions() { + if ( r_skipInteractions.GetBool() ) { + return; + } + + renderLog.OpenMainBlock( MRB_DRAW_INTERACTIONS ); + renderLog.OpenBlock( "RB_DrawInteractions" ); + + GL_SelectTexture( 0 ); + + + const bool useLightDepthBounds = r_useLightDepthBounds.GetBool(); + + // + // for each light, perform shadowing and adding + // + for ( const viewLight_t * vLight = backEnd.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + // do fogging later + if ( vLight->lightShader->IsFogLight() ) { + continue; + } + if ( vLight->lightShader->IsBlendLight() ) { + continue; + } + + if ( vLight->localInteractions == NULL && vLight->globalInteractions == NULL && vLight->translucentInteractions == NULL ) { + continue; + } + + const idMaterial * lightShader = vLight->lightShader; + renderLog.OpenBlock( lightShader->GetName() ); + + // set the depth bounds for the whole light + if ( useLightDepthBounds ) { + GL_DepthBoundsTest( vLight->scissorRect.zmin, vLight->scissorRect.zmax ); + } + + // only need to clear the stencil buffer and perform stencil testing if there are shadows + const bool performStencilTest = ( vLight->globalShadows != NULL || vLight->localShadows != NULL ); + + // mirror flips the sense of the stencil select, and I don't want to risk accidentally breaking it + // in the normal case, so simply disable the stencil select in the mirror case + const bool useLightStencilSelect = ( r_useLightStencilSelect.GetBool() && backEnd.viewDef->isMirror == false ); + + if ( performStencilTest ) { + if ( useLightStencilSelect ) { + // write a stencil mask for the visible light bounds to hi-stencil + RB_StencilSelectLight( vLight ); + } else { + // always clear whole S-Cull tiles + idScreenRect rect; + rect.x1 = ( vLight->scissorRect.x1 + 0 ) & ~15; + rect.y1 = ( vLight->scissorRect.y1 + 0 ) & ~15; + rect.x2 = ( vLight->scissorRect.x2 + 15 ) & ~15; + rect.y2 = ( vLight->scissorRect.y2 + 15 ) & ~15; + + if ( !backEnd.currentScissor.Equals( rect ) && r_useScissor.GetBool() ) { + GL_Scissor( backEnd.viewDef->viewport.x1 + rect.x1, + backEnd.viewDef->viewport.y1 + rect.y1, + rect.x2 + 1 - rect.x1, + rect.y2 + 1 - rect.y1 ); + backEnd.currentScissor = rect; + } + GL_State( GLS_DEFAULT ); // make sure stencil mask passes for the clear + GL_Clear( false, false, true, STENCIL_SHADOW_TEST_VALUE, 0.0f, 0.0f, 0.0f, 0.0f ); + } + } + + if ( vLight->globalShadows != NULL ) { + renderLog.OpenBlock( "Global Light Shadows" ); + RB_StencilShadowPass( vLight->globalShadows, vLight ); + renderLog.CloseBlock(); + } + + if ( vLight->localInteractions != NULL ) { + renderLog.OpenBlock( "Local Light Interactions" ); + RB_RenderInteractions( vLight->localInteractions, vLight, GLS_DEPTHFUNC_EQUAL, performStencilTest, useLightDepthBounds ); + renderLog.CloseBlock(); + } + + if ( vLight->localShadows != NULL ) { + renderLog.OpenBlock( "Local Light Shadows" ); + RB_StencilShadowPass( vLight->localShadows, vLight ); + renderLog.CloseBlock(); + } + + if ( vLight->globalInteractions != NULL ) { + renderLog.OpenBlock( "Global Light Interactions" ); + RB_RenderInteractions( vLight->globalInteractions, vLight, GLS_DEPTHFUNC_EQUAL, performStencilTest, useLightDepthBounds ); + renderLog.CloseBlock(); + } + + + if ( vLight->translucentInteractions != NULL && !r_skipTranslucent.GetBool() ) { + renderLog.OpenBlock( "Translucent Interactions" ); + + // Disable the depth bounds test because translucent surfaces don't work with + // the depth bounds tests since they did not write depth during the depth pass. + if ( useLightDepthBounds ) { + GL_DepthBoundsTest( 0.0f, 0.0f ); + } + + // The depth buffer wasn't filled in for translucent surfaces, so they + // can never be constrained to perforated surfaces with the depthfunc equal. + + // Translucent surfaces do not receive shadows. This is a case where a + // shadow buffer solution would work but stencil shadows do not because + // stencil shadows only affect surfaces that contribute to the view depth + // buffer and translucent surfaces do not contribute to the view depth buffer. + + RB_RenderInteractions( vLight->translucentInteractions, vLight, GLS_DEPTHFUNC_LESS, false, false ); + + renderLog.CloseBlock(); + } + + renderLog.CloseBlock(); + } + + // disable stencil shadow test + GL_State( GLS_DEFAULT ); + + // unbind texture units + for ( int i = 0; i < 5; i++ ) { + GL_SelectTexture( i ); + globalImages->BindNull(); + } + GL_SelectTexture( 0 ); + + // reset depth bounds + if ( useLightDepthBounds ) { + GL_DepthBoundsTest( 0.0f, 0.0f ); + } + + renderLog.CloseBlock(); + renderLog.CloseMainBlock(); +} + +/* +============================================================================================= + +NON-INTERACTION SHADER PASSES + +============================================================================================= +*/ + +/* +===================== +RB_DrawShaderPasses + +Draw non-light dependent passes + +If we are rendering Guis, the drawSurf_t::sort value is a depth offset that can +be multiplied by guiEye for polarity and screenSeparation for scale. +===================== +*/ +static int RB_DrawShaderPasses( const drawSurf_t * const * const drawSurfs, const int numDrawSurfs, + const float guiStereoScreenOffset, const int stereoEye ) { + // only obey skipAmbient if we are rendering a view + if ( backEnd.viewDef->viewEntitys && r_skipAmbient.GetBool() ) { + return numDrawSurfs; + } + + renderLog.OpenBlock( "RB_DrawShaderPasses" ); + + GL_SelectTexture( 1 ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + + backEnd.currentSpace = (const viewEntity_t *)1; // using NULL makes /analyze think surf->space needs to be checked... + float currentGuiStereoOffset = 0.0f; + + int i = 0; + for ( ; i < numDrawSurfs; i++ ) { + const drawSurf_t * surf = drawSurfs[i]; + const idMaterial * shader = surf->material; + + if ( !shader->HasAmbient() ) { + continue; + } + + if ( shader->IsPortalSky() ) { + continue; + } + + // some deforms may disable themselves by setting numIndexes = 0 + if ( surf->numIndexes == 0 ) { + continue; + } + + if ( shader->SuppressInSubview() ) { + continue; + } + + if ( backEnd.viewDef->isXraySubview && surf->space->entityDef ) { + if ( surf->space->entityDef->parms.xrayIndex != 2 ) { + continue; + } + } + + // we need to draw the post process shaders after we have drawn the fog lights + if ( shader->GetSort() >= SS_POST_PROCESS && !backEnd.currentRenderCopied ) { + break; + } + + // if we are rendering a 3D view and the surface's eye index doesn't match + // the current view's eye index then we skip the surface + // if the stereoEye value of a surface is 0 then we need to draw it for both eyes. + const int shaderStereoEye = shader->GetStereoEye(); + const bool isEyeValid = stereoRender_swapEyes.GetBool() ? ( shaderStereoEye == stereoEye ) : ( shaderStereoEye != stereoEye ); + if ( ( stereoEye != 0 ) && ( shaderStereoEye != 0 ) && ( isEyeValid ) ) { + continue; + } + + renderLog.OpenBlock( shader->GetName() ); + + // determine the stereoDepth offset + // guiStereoScreenOffset will always be zero for 3D views, so the != + // check will never force an update due to the current sort value. + const float thisGuiStereoOffset = guiStereoScreenOffset * surf->sort; + + // change the matrix and other space related vars if needed + if ( surf->space != backEnd.currentSpace || thisGuiStereoOffset != currentGuiStereoOffset ) { + backEnd.currentSpace = surf->space; + currentGuiStereoOffset = thisGuiStereoOffset; + + const viewEntity_t *space = backEnd.currentSpace; + + if ( guiStereoScreenOffset != 0.0f ) { + RB_SetMVPWithStereoOffset( space->mvp, currentGuiStereoOffset ); + } else { + RB_SetMVP( space->mvp ); + } + + // set eye position in local space + idVec4 localViewOrigin( 1.0f ); + R_GlobalPointToLocal( space->modelMatrix, backEnd.viewDef->renderView.vieworg, localViewOrigin.ToVec3() ); + SetVertexParm( RENDERPARM_LOCALVIEWORIGIN, localViewOrigin.ToFloatPtr() ); + + // set model Matrix + float modelMatrixTranspose[16]; + R_MatrixTranspose( space->modelMatrix, modelMatrixTranspose ); + SetVertexParms( RENDERPARM_MODELMATRIX_X, modelMatrixTranspose, 4 ); + + // Set ModelView Matrix + float modelViewMatrixTranspose[16]; + R_MatrixTranspose( space->modelViewMatrix, modelViewMatrixTranspose ); + SetVertexParms( RENDERPARM_MODELVIEWMATRIX_X, modelViewMatrixTranspose, 4 ); + } + + // change the scissor if needed + if ( !backEnd.currentScissor.Equals( surf->scissorRect ) && r_useScissor.GetBool() ) { + GL_Scissor( backEnd.viewDef->viewport.x1 + surf->scissorRect.x1, + backEnd.viewDef->viewport.y1 + surf->scissorRect.y1, + surf->scissorRect.x2 + 1 - surf->scissorRect.x1, + surf->scissorRect.y2 + 1 - surf->scissorRect.y1 ); + backEnd.currentScissor = surf->scissorRect; + } + + // get the expressions for conditionals / color / texcoords + const float *regs = surf->shaderRegisters; + + // set face culling appropriately + if ( surf->space->isGuiSurface ) { + GL_Cull( CT_TWO_SIDED ); + } else { + GL_Cull( shader->GetCullType() ); + } + + uint64 surfGLState = surf->extraGLState; + + // set polygon offset if necessary + if ( shader->TestMaterialFlag(MF_POLYGONOFFSET) ) { + GL_PolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset() ); + surfGLState = GLS_POLYGON_OFFSET; + } + + for ( int stage = 0; stage < shader->GetNumStages(); stage++ ) { + const shaderStage_t *pStage = shader->GetStage(stage); + + // check the enable condition + if ( regs[ pStage->conditionRegister ] == 0 ) { + continue; + } + + // skip the stages involved in lighting + if ( pStage->lighting != SL_AMBIENT ) { + continue; + } + + uint64 stageGLState = surfGLState; + if ( ( surfGLState & GLS_OVERRIDE ) == 0 ) { + stageGLState |= pStage->drawStateBits; + } + + // skip if the stage is ( GL_ZERO, GL_ONE ), which is used for some alpha masks + if ( ( stageGLState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) == ( GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ) ) { + continue; + } + + // see if we are a new-style stage + newShaderStage_t *newStage = pStage->newStage; + if ( newStage != NULL ) { + //-------------------------- + // + // new style stages + // + //-------------------------- + if ( r_skipNewAmbient.GetBool() ) { + continue; + } + renderLog.OpenBlock( "New Shader Stage" ); + + GL_State( stageGLState ); + + renderProgManager.BindShader( newStage->glslProgram, newStage->glslProgram ); + + for ( int j = 0; j < newStage->numVertexParms; j++ ) { + float parm[4]; + parm[0] = regs[ newStage->vertexParms[j][0] ]; + parm[1] = regs[ newStage->vertexParms[j][1] ]; + parm[2] = regs[ newStage->vertexParms[j][2] ]; + parm[3] = regs[ newStage->vertexParms[j][3] ]; + SetVertexParm( (renderParm_t)( RENDERPARM_USER + j ), parm ); + } + + // set rpEnableSkinning if the shader has optional support for skinning + if ( surf->jointCache && renderProgManager.ShaderHasOptionalSkinning() ) { + const idVec4 skinningParm( 1.0f ); + SetVertexParm( RENDERPARM_ENABLE_SKINNING, skinningParm.ToFloatPtr() ); + } + + // bind texture units + for ( int j = 0; j < newStage->numFragmentProgramImages; j++ ) { + idImage * image = newStage->fragmentProgramImages[j]; + if ( image != NULL ) { + GL_SelectTexture( j ); + image->Bind(); + } + } + + // draw it + RB_DrawElementsWithCounters( surf ); + + // unbind texture units + for ( int j = 0; j < newStage->numFragmentProgramImages; j++ ) { + idImage * image = newStage->fragmentProgramImages[j]; + if ( image != NULL ) { + GL_SelectTexture( j ); + globalImages->BindNull(); + } + } + + // clear rpEnableSkinning if it was set + if ( surf->jointCache && renderProgManager.ShaderHasOptionalSkinning() ) { + const idVec4 skinningParm( 0.0f ); + SetVertexParm( RENDERPARM_ENABLE_SKINNING, skinningParm.ToFloatPtr() ); + } + + GL_SelectTexture( 0 ); + renderProgManager.Unbind(); + + renderLog.CloseBlock(); + continue; + } + + //-------------------------- + // + // old style stages + // + //-------------------------- + + // set the color + float color[4]; + color[0] = regs[ pStage->color.registers[0] ]; + color[1] = regs[ pStage->color.registers[1] ]; + color[2] = regs[ pStage->color.registers[2] ]; + color[3] = regs[ pStage->color.registers[3] ]; + + // skip the entire stage if an add would be black + if ( ( stageGLState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) == ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ) + && color[0] <= 0 && color[1] <= 0 && color[2] <= 0 ) { + continue; + } + + // skip the entire stage if a blend would be completely transparent + if ( ( stageGLState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) == ( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) + && color[3] <= 0 ) { + continue; + } + + stageVertexColor_t svc = pStage->vertexColor; + + renderLog.OpenBlock( "Old Shader Stage" ); + GL_Color( color ); + + if ( surf->space->isGuiSurface ) { + // Force gui surfaces to always be SVC_MODULATE + svc = SVC_MODULATE; + + // use special shaders for bink cinematics + if ( pStage->texture.cinematic ) { + if ( ( stageGLState & GLS_OVERRIDE ) != 0 ) { + // This is a hack... Only SWF Guis set GLS_OVERRIDE + // Old style guis do not, and we don't want them to use the new GUI renederProg + renderProgManager.BindShader_BinkGUI(); + } else { + renderProgManager.BindShader_Bink(); + } + } else { + if ( ( stageGLState & GLS_OVERRIDE ) != 0 ) { + // This is a hack... Only SWF Guis set GLS_OVERRIDE + // Old style guis do not, and we don't want them to use the new GUI renderProg + renderProgManager.BindShader_GUI(); + } else { + if ( surf->jointCache ) { + renderProgManager.BindShader_TextureVertexColorSkinned(); + } else { + renderProgManager.BindShader_TextureVertexColor(); + } + } + } + } else if ( ( pStage->texture.texgen == TG_SCREEN ) || ( pStage->texture.texgen == TG_SCREEN2 ) ) { + renderProgManager.BindShader_TextureTexGenVertexColor(); + } else if ( pStage->texture.cinematic ) { + renderProgManager.BindShader_Bink(); + } else { + if ( surf->jointCache ) { + renderProgManager.BindShader_TextureVertexColorSkinned(); + } else { + renderProgManager.BindShader_TextureVertexColor(); + } + } + + RB_SetVertexColorParms( svc ); + + // bind the texture + RB_BindVariableStageImage( &pStage->texture, regs ); + + // set privatePolygonOffset if necessary + if ( pStage->privatePolygonOffset ) { + GL_PolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * pStage->privatePolygonOffset ); + stageGLState |= GLS_POLYGON_OFFSET; + } + + // set the state + GL_State( stageGLState ); + + RB_PrepareStageTexturing( pStage, surf ); + + // draw it + RB_DrawElementsWithCounters( surf ); + + RB_FinishStageTexturing( pStage, surf ); + + // unset privatePolygonOffset if necessary + if ( pStage->privatePolygonOffset ) { + GL_PolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() * shader->GetPolygonOffset() ); + } + renderLog.CloseBlock(); + } + + renderLog.CloseBlock(); + } + + GL_Cull( CT_FRONT_SIDED ); + GL_Color( 1.0f, 1.0f, 1.0f ); + + renderLog.CloseBlock(); + return i; +} + +/* +============================================================================================= + +BLEND LIGHT PROJECTION + +============================================================================================= +*/ + +/* +===================== +RB_T_BlendLight +===================== +*/ +static void RB_T_BlendLight( const drawSurf_t *drawSurfs, const viewLight_t * vLight ) { + backEnd.currentSpace = NULL; + + for ( const drawSurf_t * drawSurf = drawSurfs; drawSurf != NULL; drawSurf = drawSurf->nextOnLight ) { + if ( drawSurf->scissorRect.IsEmpty() ) { + continue; // !@# FIXME: find out why this is sometimes being hit! + // temporarily jump over the scissor and draw so the gl error callback doesn't get hit + } + + if ( !backEnd.currentScissor.Equals( drawSurf->scissorRect ) && r_useScissor.GetBool() ) { + // change the scissor + GL_Scissor( backEnd.viewDef->viewport.x1 + drawSurf->scissorRect.x1, + backEnd.viewDef->viewport.y1 + drawSurf->scissorRect.y1, + drawSurf->scissorRect.x2 + 1 - drawSurf->scissorRect.x1, + drawSurf->scissorRect.y2 + 1 - drawSurf->scissorRect.y1 ); + backEnd.currentScissor = drawSurf->scissorRect; + } + + if ( drawSurf->space != backEnd.currentSpace ) { + // change the matrix + RB_SetMVP( drawSurf->space->mvp ); + + // change the light projection matrix + idPlane lightProjectInCurrentSpace[4]; + for ( int i = 0; i < 4; i++ ) { + R_GlobalPlaneToLocal( drawSurf->space->modelMatrix, vLight->lightProject[i], lightProjectInCurrentSpace[i] ); + } + + SetVertexParm( RENDERPARM_TEXGEN_0_S, lightProjectInCurrentSpace[0].ToFloatPtr() ); + SetVertexParm( RENDERPARM_TEXGEN_0_T, lightProjectInCurrentSpace[1].ToFloatPtr() ); + SetVertexParm( RENDERPARM_TEXGEN_0_Q, lightProjectInCurrentSpace[2].ToFloatPtr() ); + SetVertexParm( RENDERPARM_TEXGEN_1_S, lightProjectInCurrentSpace[3].ToFloatPtr() ); // falloff + + backEnd.currentSpace = drawSurf->space; + } + + RB_DrawElementsWithCounters( drawSurf ); + } +} + +/* +===================== +RB_BlendLight + +Dual texture together the falloff and projection texture with a blend +mode to the framebuffer, instead of interacting with the surface texture +===================== +*/ +static void RB_BlendLight( const drawSurf_t *drawSurfs, const drawSurf_t *drawSurfs2, const viewLight_t * vLight ) { + if ( drawSurfs == NULL ) { + return; + } + if ( r_skipBlendLights.GetBool() ) { + return; + } + renderLog.OpenBlock( vLight->lightShader->GetName() ); + + const idMaterial * lightShader = vLight->lightShader; + const float * regs = vLight->shaderRegisters; + + // texture 1 will get the falloff texture + GL_SelectTexture( 1 ); + vLight->falloffImage->Bind(); + + // texture 0 will get the projected texture + GL_SelectTexture( 0 ); + + renderProgManager.BindShader_BlendLight(); + + for ( int i = 0; i < lightShader->GetNumStages(); i++ ) { + const shaderStage_t *stage = lightShader->GetStage(i); + + if ( !regs[ stage->conditionRegister ] ) { + continue; + } + + GL_State( GLS_DEPTHMASK | stage->drawStateBits | GLS_DEPTHFUNC_EQUAL ); + + GL_SelectTexture( 0 ); + stage->texture.image->Bind(); + + if ( stage->texture.hasMatrix ) { + RB_LoadShaderTextureMatrix( regs, &stage->texture ); + } + + // get the modulate values from the light, including alpha, unlike normal lights + float lightColor[4]; + lightColor[0] = regs[ stage->color.registers[0] ]; + lightColor[1] = regs[ stage->color.registers[1] ]; + lightColor[2] = regs[ stage->color.registers[2] ]; + lightColor[3] = regs[ stage->color.registers[3] ]; + GL_Color( lightColor ); + + RB_T_BlendLight( drawSurfs, vLight ); + RB_T_BlendLight( drawSurfs2, vLight ); + } + + GL_SelectTexture( 1 ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + + renderProgManager.Unbind(); + renderLog.CloseBlock(); +} + +/* +========================================================================================================= + +FOG LIGHTS + +========================================================================================================= +*/ + +/* +===================== +RB_T_BasicFog +===================== +*/ +static void RB_T_BasicFog( const drawSurf_t *drawSurfs, const idPlane fogPlanes[4], const idRenderMatrix * inverseBaseLightProject ) { + backEnd.currentSpace = NULL; + + for ( const drawSurf_t * drawSurf = drawSurfs; drawSurf != NULL; drawSurf = drawSurf->nextOnLight ) { + if ( drawSurf->scissorRect.IsEmpty() ) { + continue; // !@# FIXME: find out why this is sometimes being hit! + // temporarily jump over the scissor and draw so the gl error callback doesn't get hit + } + + if ( !backEnd.currentScissor.Equals( drawSurf->scissorRect ) && r_useScissor.GetBool() ) { + // change the scissor + GL_Scissor( backEnd.viewDef->viewport.x1 + drawSurf->scissorRect.x1, + backEnd.viewDef->viewport.y1 + drawSurf->scissorRect.y1, + drawSurf->scissorRect.x2 + 1 - drawSurf->scissorRect.x1, + drawSurf->scissorRect.y2 + 1 - drawSurf->scissorRect.y1 ); + backEnd.currentScissor = drawSurf->scissorRect; + } + + if ( drawSurf->space != backEnd.currentSpace ) { + idPlane localFogPlanes[4]; + if ( inverseBaseLightProject == NULL ) { + RB_SetMVP( drawSurf->space->mvp ); + for ( int i = 0; i < 4; i++ ) { + R_GlobalPlaneToLocal( drawSurf->space->modelMatrix, fogPlanes[i], localFogPlanes[i] ); + } + } else { + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( backEnd.viewDef->worldSpace.mvp, *inverseBaseLightProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + for ( int i = 0; i < 4; i++ ) { + inverseBaseLightProject->InverseTransformPlane( fogPlanes[i], localFogPlanes[i], false ); + } + } + + SetVertexParm( RENDERPARM_TEXGEN_0_S, localFogPlanes[0].ToFloatPtr() ); + SetVertexParm( RENDERPARM_TEXGEN_0_T, localFogPlanes[1].ToFloatPtr() ); + SetVertexParm( RENDERPARM_TEXGEN_1_T, localFogPlanes[2].ToFloatPtr() ); + SetVertexParm( RENDERPARM_TEXGEN_1_S, localFogPlanes[3].ToFloatPtr() ); + + backEnd.currentSpace = ( inverseBaseLightProject == NULL ) ? drawSurf->space : NULL; + } + + if ( drawSurf->jointCache ) { + renderProgManager.BindShader_FogSkinned(); + } else { + renderProgManager.BindShader_Fog(); + } + + RB_DrawElementsWithCounters( drawSurf ); + } +} + +/* +================== +RB_FogPass +================== +*/ +static void RB_FogPass( const drawSurf_t * drawSurfs, const drawSurf_t * drawSurfs2, const viewLight_t * vLight ) { + renderLog.OpenBlock( vLight->lightShader->GetName() ); + + // find the current color and density of the fog + const idMaterial * lightShader = vLight->lightShader; + const float * regs = vLight->shaderRegisters; + // assume fog shaders have only a single stage + const shaderStage_t * stage = lightShader->GetStage( 0 ); + + float lightColor[4]; + lightColor[0] = regs[ stage->color.registers[0] ]; + lightColor[1] = regs[ stage->color.registers[1] ]; + lightColor[2] = regs[ stage->color.registers[2] ]; + lightColor[3] = regs[ stage->color.registers[3] ]; + + GL_Color( lightColor ); + + // calculate the falloff planes + float a; + + // if they left the default value on, set a fog distance of 500 + if ( lightColor[3] <= 1.0f ) { + a = -0.5f / DEFAULT_FOG_DISTANCE; + } else { + // otherwise, distance = alpha color + a = -0.5f / lightColor[3]; + } + + // texture 0 is the falloff image + GL_SelectTexture( 0 ); + globalImages->fogImage->Bind(); + + // texture 1 is the entering plane fade correction + GL_SelectTexture( 1 ); + globalImages->fogEnterImage->Bind(); + + // S is based on the view origin + const float s = vLight->fogPlane.Distance( backEnd.viewDef->renderView.vieworg ); + + const float FOG_SCALE = 0.001f; + + idPlane fogPlanes[4]; + + // S-0 + fogPlanes[0][0] = a * backEnd.viewDef->worldSpace.modelViewMatrix[0*4+2]; + fogPlanes[0][1] = a * backEnd.viewDef->worldSpace.modelViewMatrix[1*4+2]; + fogPlanes[0][2] = a * backEnd.viewDef->worldSpace.modelViewMatrix[2*4+2]; + fogPlanes[0][3] = a * backEnd.viewDef->worldSpace.modelViewMatrix[3*4+2] + 0.5f; + + // T-0 + fogPlanes[1][0] = 0.0f;//a * backEnd.viewDef->worldSpace.modelViewMatrix[0*4+0]; + fogPlanes[1][1] = 0.0f;//a * backEnd.viewDef->worldSpace.modelViewMatrix[1*4+0]; + fogPlanes[1][2] = 0.0f;//a * backEnd.viewDef->worldSpace.modelViewMatrix[2*4+0]; + fogPlanes[1][3] = 0.5f;//a * backEnd.viewDef->worldSpace.modelViewMatrix[3*4+0] + 0.5f; + + // T-1 will get a texgen for the fade plane, which is always the "top" plane on unrotated lights + fogPlanes[2][0] = FOG_SCALE * vLight->fogPlane[0]; + fogPlanes[2][1] = FOG_SCALE * vLight->fogPlane[1]; + fogPlanes[2][2] = FOG_SCALE * vLight->fogPlane[2]; + fogPlanes[2][3] = FOG_SCALE * vLight->fogPlane[3] + FOG_ENTER; + + // S-1 + fogPlanes[3][0] = 0.0f; + fogPlanes[3][1] = 0.0f; + fogPlanes[3][2] = 0.0f; + fogPlanes[3][3] = FOG_SCALE * s + FOG_ENTER; + + // draw it + GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + RB_T_BasicFog( drawSurfs, fogPlanes, NULL ); + RB_T_BasicFog( drawSurfs2, fogPlanes, NULL ); + + // the light frustum bounding planes aren't in the depth buffer, so use depthfunc_less instead + // of depthfunc_equal + GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_LESS ); + GL_Cull( CT_BACK_SIDED ); + + backEnd.zeroOneCubeSurface.space = &backEnd.viewDef->worldSpace; + backEnd.zeroOneCubeSurface.scissorRect = backEnd.viewDef->scissor; + RB_T_BasicFog( &backEnd.zeroOneCubeSurface, fogPlanes, &vLight->inverseBaseLightProject ); + + GL_Cull( CT_FRONT_SIDED ); + + GL_SelectTexture( 1 ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + + renderProgManager.Unbind(); + + renderLog.CloseBlock(); +} + +/* +================== +RB_FogAllLights +================== +*/ +static void RB_FogAllLights() { + if ( r_skipFogLights.GetBool() || r_showOverDraw.GetInteger() != 0 + || backEnd.viewDef->isXraySubview /* don't fog in xray mode*/ ) { + return; + } + renderLog.OpenMainBlock( MRB_FOG_ALL_LIGHTS ); + renderLog.OpenBlock( "RB_FogAllLights" ); + + // force fog plane to recalculate + backEnd.currentSpace = NULL; + + for ( viewLight_t * vLight = backEnd.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + if ( vLight->lightShader->IsFogLight() ) { + RB_FogPass( vLight->globalInteractions, vLight->localInteractions, vLight ); + } else if ( vLight->lightShader->IsBlendLight() ) { + RB_BlendLight( vLight->globalInteractions, vLight->localInteractions, vLight ); + } + } + + renderLog.CloseBlock(); + renderLog.CloseMainBlock(); +} + +/* +========================================================================================================= + +BACKEND COMMANDS + +========================================================================================================= +*/ + +/* +================== +RB_DrawViewInternal +================== +*/ +void RB_DrawViewInternal( const viewDef_t * viewDef, const int stereoEye ) { + renderLog.OpenBlock( "RB_DrawViewInternal" ); + + //------------------------------------------------- + // guis can wind up referencing purged images that need to be loaded. + // this used to be in the gui emit code, but now that it can be running + // in a separate thread, it must not try to load images, so do it here. + //------------------------------------------------- + drawSurf_t **drawSurfs = (drawSurf_t **)&viewDef->drawSurfs[0]; + const int numDrawSurfs = viewDef->numDrawSurfs; + + for ( int i = 0; i < numDrawSurfs; i++ ) { + const drawSurf_t * ds = viewDef->drawSurfs[ i ]; + if ( ds->material != NULL ) { + const_cast( ds->material )->EnsureNotPurged(); + } + } + + //------------------------------------------------- + // RB_BeginDrawingView + // + // Any mirrored or portaled views have already been drawn, so prepare + // to actually render the visible surfaces for this view + // + // clear the z buffer, set the projection matrix, etc + //------------------------------------------------- + + // set the window clipping + GL_Viewport( viewDef->viewport.x1, + viewDef->viewport.y1, + viewDef->viewport.x2 + 1 - viewDef->viewport.x1, + viewDef->viewport.y2 + 1 - viewDef->viewport.y1 ); + + // the scissor may be smaller than the viewport for subviews + GL_Scissor( backEnd.viewDef->viewport.x1 + viewDef->scissor.x1, + backEnd.viewDef->viewport.y1 + viewDef->scissor.y1, + viewDef->scissor.x2 + 1 - viewDef->scissor.x1, + viewDef->scissor.y2 + 1 - viewDef->scissor.y1 ); + backEnd.currentScissor = viewDef->scissor; + + backEnd.glState.faceCulling = -1; // force face culling to set next time + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + + + // Clear the depth buffer and clear the stencil to 128 for stencil shadows as well as gui masking + GL_Clear( false, true, true, STENCIL_SHADOW_TEST_VALUE, 0.0f, 0.0f, 0.0f, 0.0f ); + + // normal face culling + GL_Cull( CT_FRONT_SIDED ); + +#ifdef USE_CORE_PROFILE + // bind one global Vertex Array Object (VAO) + qglBindVertexArray( glConfig.global_vao ); +#endif + + //------------------------------------ + // sets variables that can be used by all programs + //------------------------------------ + { + // + // set eye position in global space + // + float parm[4]; + parm[0] = backEnd.viewDef->renderView.vieworg[0]; + parm[1] = backEnd.viewDef->renderView.vieworg[1]; + parm[2] = backEnd.viewDef->renderView.vieworg[2]; + parm[3] = 1.0f; + + SetVertexParm( RENDERPARM_GLOBALEYEPOS, parm ); // rpGlobalEyePos + + // sets overbright to make world brighter + // This value is baked into the specularScale and diffuseScale values so + // the interaction programs don't need to perform the extra multiply, + // but any other renderprogs that want to obey the brightness value + // can reference this. + float overbright = r_lightScale.GetFloat() * 0.5f; + parm[0] = overbright; + parm[1] = overbright; + parm[2] = overbright; + parm[3] = overbright; + SetFragmentParm( RENDERPARM_OVERBRIGHT, parm ); + + // Set Projection Matrix + float projMatrixTranspose[16]; + R_MatrixTranspose( backEnd.viewDef->projectionMatrix, projMatrixTranspose ); + SetVertexParms( RENDERPARM_PROJMATRIX_X, projMatrixTranspose, 4 ); + } + + //------------------------------------------------- + // fill the depth buffer and clear color buffer to black except on subviews + //------------------------------------------------- + RB_FillDepthBufferFast( drawSurfs, numDrawSurfs ); + + //------------------------------------------------- + // main light renderer + //------------------------------------------------- + RB_DrawInteractions(); + + //------------------------------------------------- + // now draw any non-light dependent shading passes + //------------------------------------------------- + int processed = 0; + if ( !r_skipShaderPasses.GetBool() ) { + renderLog.OpenMainBlock( MRB_DRAW_SHADER_PASSES ); + float guiScreenOffset; + if ( viewDef->viewEntitys != NULL ) { + // guiScreenOffset will be 0 in non-gui views + guiScreenOffset = 0.0f; + } else { + guiScreenOffset = stereoEye * viewDef->renderView.stereoScreenSeparation; + } + processed = RB_DrawShaderPasses( drawSurfs, numDrawSurfs, guiScreenOffset, stereoEye ); + renderLog.CloseMainBlock(); + } + + //------------------------------------------------- + // fog and blend lights, drawn after emissive surfaces + // so they are properly dimmed down + //------------------------------------------------- + RB_FogAllLights(); + + //------------------------------------------------- + // capture the depth for the motion blur before rendering any post process surfaces that may contribute to the depth + //------------------------------------------------- + if ( r_motionBlur.GetInteger() > 0 ) { + const idScreenRect & viewport = backEnd.viewDef->viewport; + globalImages->currentDepthImage->CopyDepthbuffer( viewport.x1, viewport.y1, viewport.GetWidth(), viewport.GetHeight() ); + } + + //------------------------------------------------- + // now draw any screen warping post-process effects using _currentRender + //------------------------------------------------- + if ( processed < numDrawSurfs && !r_skipPostProcess.GetBool() ) { + int x = backEnd.viewDef->viewport.x1; + int y = backEnd.viewDef->viewport.y1; + int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; + int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; + + RENDERLOG_PRINTF( "Resolve to %i x %i buffer\n", w, h ); + + GL_SelectTexture( 0 ); + + // resolve the screen + globalImages->currentRenderImage->CopyFramebuffer( x, y, w, h ); + backEnd.currentRenderCopied = true; + + // RENDERPARM_SCREENCORRECTIONFACTOR amd RENDERPARM_WINDOWCOORD overlap + // diffuseScale and specularScale + + // screen power of two correction factor (no longer relevant now) + float screenCorrectionParm[4]; + screenCorrectionParm[0] = 1.0f; + screenCorrectionParm[1] = 1.0f; + screenCorrectionParm[2] = 0.0f; + screenCorrectionParm[3] = 1.0f; + SetFragmentParm( RENDERPARM_SCREENCORRECTIONFACTOR, screenCorrectionParm ); // rpScreenCorrectionFactor + + // window coord to 0.0 to 1.0 conversion + float windowCoordParm[4]; + windowCoordParm[0] = 1.0f / w; + windowCoordParm[1] = 1.0f / h; + windowCoordParm[2] = 0.0f; + windowCoordParm[3] = 1.0f; + SetFragmentParm( RENDERPARM_WINDOWCOORD, windowCoordParm ); // rpWindowCoord + + // render the remaining surfaces + renderLog.OpenMainBlock( MRB_DRAW_SHADER_PASSES_POST ); + RB_DrawShaderPasses( drawSurfs + processed, numDrawSurfs - processed, 0.0f /* definitely not a gui */, stereoEye ); + renderLog.CloseMainBlock(); + } + + //------------------------------------------------- + // render debug tools + //------------------------------------------------- + RB_RenderDebugTools( drawSurfs, numDrawSurfs ); + + renderLog.CloseBlock(); +} + +/* +================== +RB_MotionBlur + +Experimental feature +================== +*/ +void RB_MotionBlur() { + if ( !backEnd.viewDef->viewEntitys ) { + // 3D views only + return; + } + if ( r_motionBlur.GetInteger() <= 0 ) { + return; + } + if ( backEnd.viewDef->isSubview ) { + return; + } + + GL_CheckErrors(); + + // clear the alpha buffer and draw only the hands + weapon into it so + // we can avoid blurring them + qglClearColor( 0, 0, 0, 1 ); + GL_State( GLS_COLORMASK | GLS_DEPTHMASK ); + qglClear( GL_COLOR_BUFFER_BIT ); + GL_Color( 0, 0, 0, 0 ); + GL_SelectTexture( 0 ); + globalImages->blackImage->Bind(); + backEnd.currentSpace = NULL; + + drawSurf_t **drawSurfs = (drawSurf_t **)&backEnd.viewDef->drawSurfs[0]; + for ( int surfNum = 0; surfNum < backEnd.viewDef->numDrawSurfs; surfNum++ ) { + const drawSurf_t * surf = drawSurfs[ surfNum ]; + + if ( !surf->space->weaponDepthHack && !surf->space->skipMotionBlur && !surf->material->HasSubview() ) { + // Apply motion blur to this object + continue; + } + + const idMaterial * shader = surf->material; + if ( shader->Coverage() == MC_TRANSLUCENT ) { + // muzzle flash, etc + continue; + } + + // set mvp matrix + if ( surf->space != backEnd.currentSpace ) { + RB_SetMVP( surf->space->mvp ); + backEnd.currentSpace = surf->space; + } + + // this could just be a color, but we don't have a skinned color-only prog + if ( surf->jointCache ) { + renderProgManager.BindShader_TextureVertexColorSkinned(); + } else { + renderProgManager.BindShader_TextureVertexColor(); + } + + // draw it solid + RB_DrawElementsWithCounters( surf ); + } + GL_State( GLS_DEPTHFUNC_ALWAYS ); + + // copy off the color buffer and the depth buffer for the motion blur prog + // we use the viewport dimensions for copying the buffers in case resolution scaling is enabled. + const idScreenRect & viewport = backEnd.viewDef->viewport; + globalImages->currentRenderImage->CopyFramebuffer( viewport.x1, viewport.y1, viewport.GetWidth(), viewport.GetHeight() ); + + // in stereo rendering, each eye needs to get a separate previous frame mvp + int mvpIndex = ( backEnd.viewDef->renderView.viewEyeBuffer == 1 ) ? 1 : 0; + + // derive the matrix to go from current pixels to previous frame pixels + idRenderMatrix inverseMVP; + idRenderMatrix::Inverse( backEnd.viewDef->worldSpace.mvp, inverseMVP ); + + idRenderMatrix motionMatrix; + idRenderMatrix::Multiply( backEnd.prevMVP[mvpIndex], inverseMVP, motionMatrix ); + + backEnd.prevMVP[mvpIndex] = backEnd.viewDef->worldSpace.mvp; + + RB_SetMVP( motionMatrix ); + + GL_State( GLS_DEPTHFUNC_ALWAYS ); + GL_Cull( CT_TWO_SIDED ); + + renderProgManager.BindShader_MotionBlur(); + + // let the fragment program know how many samples we are going to use + idVec4 samples( (float)( 1 << r_motionBlur.GetInteger() ) ); + SetFragmentParm( RENDERPARM_OVERBRIGHT, samples.ToFloatPtr() ); + + GL_SelectTexture( 0 ); + globalImages->currentRenderImage->Bind(); + GL_SelectTexture( 1 ); + globalImages->currentDepthImage->Bind(); + + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + GL_CheckErrors(); +} + +/* +================== +RB_DrawView + +StereoEye will always be 0 in mono modes, or -1 / 1 in stereo modes. +If the view is a GUI view that is repeated for both eyes, the viewDef.stereoEye value +is 0, so the stereoEye parameter is not always the same as that. +================== +*/ +void RB_DrawView( const void *data, const int stereoEye ) { + const drawSurfsCommand_t * cmd = (const drawSurfsCommand_t *)data; + + backEnd.viewDef = cmd->viewDef; + + // we will need to do a new copyTexSubImage of the screen + // when a SS_POST_PROCESS material is used + backEnd.currentRenderCopied = false; + + // if there aren't any drawsurfs, do nothing + if ( !backEnd.viewDef->numDrawSurfs ) { + return; + } + + // skip render bypasses everything that has models, assuming + // them to be 3D views, but leaves 2D rendering visible + if ( r_skipRender.GetBool() && backEnd.viewDef->viewEntitys ) { + return; + } + + // skip render context sets the wgl context to NULL, + // which should factor out the API cost, under the assumption + // that all gl calls just return if the context isn't valid + if ( r_skipRenderContext.GetBool() && backEnd.viewDef->viewEntitys ) { + GLimp_DeactivateContext(); + } + + backEnd.pc.c_surfaces += backEnd.viewDef->numDrawSurfs; + + RB_ShowOverdraw(); + + // render the scene + RB_DrawViewInternal( cmd->viewDef, stereoEye ); + + RB_MotionBlur(); + + // restore the context for 2D drawing if we were stubbing it out + if ( r_skipRenderContext.GetBool() && backEnd.viewDef->viewEntitys ) { + GLimp_ActivateContext(); + GL_SetDefaultState(); + } + + // optionally draw a box colored based on the eye number + if ( r_drawEyeColor.GetBool() ) { + const idScreenRect & r = backEnd.viewDef->viewport; + GL_Scissor( ( r.x1 + r.x2 ) / 2, ( r.y1 + r.y2 ) / 2, 32, 32 ); + switch ( stereoEye ) { + case -1: + GL_Clear( true, false, false, 0, 1.0f, 0.0f, 0.0f, 1.0f ); + break; + case 1: + GL_Clear( true, false, false, 0, 0.0f, 1.0f, 0.0f, 1.0f ); + break; + default: + GL_Clear( true, false, false, 0, 0.5f, 0.5f, 0.5f, 1.0f ); + break; + } + } +} + +/* +================== +RB_CopyRender + +Copy part of the current framebuffer to an image +================== +*/ +void RB_CopyRender( const void *data ) { + const copyRenderCommand_t * cmd = (const copyRenderCommand_t *)data; + + if ( r_skipCopyTexture.GetBool() ) { + return; + } + + RENDERLOG_PRINTF( "***************** RB_CopyRender *****************\n" ); + + if ( cmd->image ) { + cmd->image->CopyFramebuffer( cmd->x, cmd->y, cmd->imageWidth, cmd->imageHeight ); + } + + if ( cmd->clearColorAfterCopy ) { + GL_Clear( true, false, false, STENCIL_SHADOW_TEST_VALUE, 0, 0, 0, 0 ); + } +} + +/* +================== +RB_PostProcess + +================== +*/ +extern idCVar rs_enable; +void RB_PostProcess( const void * data ) { + + // only do the post process step if resolution scaling is enabled. Prevents the unnecessary copying of the framebuffer and + // corresponding full screen quad pass. + if ( rs_enable.GetInteger() == 0 ) { + return; + } + + // resolve the scaled rendering to a temporary texture + postProcessCommand_t * cmd = (postProcessCommand_t *)data; + const idScreenRect & viewport = cmd->viewDef->viewport; + globalImages->currentRenderImage->CopyFramebuffer( viewport.x1, viewport.y1, viewport.GetWidth(), viewport.GetHeight() ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_DEPTHMASK | GLS_DEPTHFUNC_ALWAYS ); + GL_Cull( CT_TWO_SIDED ); + + int screenWidth = renderSystem->GetWidth(); + int screenHeight = renderSystem->GetHeight(); + + // set the window clipping + GL_Viewport( 0, 0, screenWidth, screenHeight ); + GL_Scissor( 0, 0, screenWidth, screenHeight ); + + GL_SelectTexture( 0 ); + globalImages->currentRenderImage->Bind(); + renderProgManager.BindShader_PostProcess(); + + // Draw + RB_DrawElementsWithCounters( &backEnd.unitSquareSurface ); + + renderLog.CloseBlock(); +} diff --git a/neo/renderer/tr_backend_rendertools.cpp b/neo/renderer/tr_backend_rendertools.cpp new file mode 100644 index 00000000..484deafd --- /dev/null +++ b/neo/renderer/tr_backend_rendertools.cpp @@ -0,0 +1,2639 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "simplex.h" // line font definition + +idCVar r_showCenterOfProjection( "r_showCenterOfProjection", "0", CVAR_RENDERER | CVAR_BOOL, "Draw a cross to show the center of projection" ); +idCVar r_showLines( "r_showLines", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = draw alternate horizontal lines, 2 = draw alternate vertical lines" ); + +#define MAX_DEBUG_LINES 16384 + +typedef struct debugLine_s { + idVec4 rgb; + idVec3 start; + idVec3 end; + bool depthTest; + int lifeTime; +} debugLine_t; + +debugLine_t rb_debugLines[ MAX_DEBUG_LINES ]; +int rb_numDebugLines = 0; +int rb_debugLineTime = 0; + +#define MAX_DEBUG_TEXT 512 + +typedef struct debugText_s { + idStr text; + idVec3 origin; + float scale; + idVec4 color; + idMat3 viewAxis; + int align; + int lifeTime; + bool depthTest; +} debugText_t; + +debugText_t rb_debugText[ MAX_DEBUG_TEXT ]; +int rb_numDebugText = 0; +int rb_debugTextTime = 0; + +#define MAX_DEBUG_POLYGONS 8192 + +typedef struct debugPolygon_s { + idVec4 rgb; + idWinding winding; + bool depthTest; + int lifeTime; +} debugPolygon_t; + +debugPolygon_t rb_debugPolygons[ MAX_DEBUG_POLYGONS ]; +int rb_numDebugPolygons = 0; +int rb_debugPolygonTime = 0; + +static void RB_DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align ); + +void RB_SetMVP( const idRenderMatrix & mvp ); + +/* +================ +RB_DrawBounds +================ +*/ +void RB_DrawBounds( const idBounds &bounds ) { + if ( bounds.IsCleared() ) { + return; + } + qglBegin( GL_LINE_LOOP ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[0][2] ); + qglVertex3f( bounds[0][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[0][1], bounds[0][2] ); + qglEnd(); + qglBegin( GL_LINE_LOOP ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[1][2] ); + qglVertex3f( bounds[0][0], bounds[1][1], bounds[1][2] ); + qglVertex3f( bounds[1][0], bounds[1][1], bounds[1][2] ); + qglVertex3f( bounds[1][0], bounds[0][1], bounds[1][2] ); + qglEnd(); + + qglBegin( GL_LINES ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[0][2] ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[1][2] ); + + qglVertex3f( bounds[0][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[0][0], bounds[1][1], bounds[1][2] ); + + qglVertex3f( bounds[1][0], bounds[0][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[0][1], bounds[1][2] ); + + qglVertex3f( bounds[1][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[1][1], bounds[1][2] ); + qglEnd(); +} + + +/* +================ +RB_SimpleSurfaceSetup +================ +*/ +static void RB_SimpleSurfaceSetup( const drawSurf_t *drawSurf ) { + // change the matrix if needed + if ( drawSurf->space != backEnd.currentSpace ) { + qglLoadMatrixf( drawSurf->space->modelViewMatrix ); + backEnd.currentSpace = drawSurf->space; + } + + // change the scissor if needed + if ( !backEnd.currentScissor.Equals( drawSurf->scissorRect ) && r_useScissor.GetBool() ) { + GL_Scissor( backEnd.viewDef->viewport.x1 + drawSurf->scissorRect.x1, + backEnd.viewDef->viewport.y1 + drawSurf->scissorRect.y1, + drawSurf->scissorRect.x2 + 1 - drawSurf->scissorRect.x1, + drawSurf->scissorRect.y2 + 1 - drawSurf->scissorRect.y1 ); + backEnd.currentScissor = drawSurf->scissorRect; + } +} + +/* +================ +RB_SimpleWorldSetup +================ +*/ +static void RB_SimpleWorldSetup() { + backEnd.currentSpace = &backEnd.viewDef->worldSpace; + + + qglLoadMatrixf( backEnd.viewDef->worldSpace.modelViewMatrix ); + + GL_Scissor( backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, + backEnd.viewDef->scissor.x2 + 1 - backEnd.viewDef->scissor.x1, + backEnd.viewDef->scissor.y2 + 1 - backEnd.viewDef->scissor.y1 ); + backEnd.currentScissor = backEnd.viewDef->scissor; +} + +/* +================= +RB_PolygonClear + +This will cover the entire screen with normal rasterization. +Texturing is disabled, but the existing glColor, glDepthMask, +glColorMask, and the enabled state of depth buffering and +stenciling will matter. +================= +*/ +void RB_PolygonClear() { + qglPushMatrix(); + qglPushAttrib( GL_ALL_ATTRIB_BITS ); + qglLoadIdentity(); + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_SCISSOR_TEST ); + qglBegin( GL_POLYGON ); + qglVertex3f( -20, -20, -10 ); + qglVertex3f( 20, -20, -10 ); + qglVertex3f( 20, 20, -10 ); + qglVertex3f( -20, 20, -10 ); + qglEnd(); + qglPopAttrib(); + qglPopMatrix(); +} + +/* +==================== +RB_ShowDestinationAlpha +==================== +*/ +void RB_ShowDestinationAlpha() { + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ZERO | GLS_DEPTHMASK | GLS_DEPTHFUNC_ALWAYS ); + GL_Color( 1, 1, 1 ); + RB_PolygonClear(); +} + +/* +=================== +RB_ScanStencilBuffer + +Debugging tool to see what values are in the stencil buffer +=================== +*/ +void RB_ScanStencilBuffer() { + int counts[256]; + int i; + byte *stencilReadback; + + memset( counts, 0, sizeof( counts ) ); + + stencilReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight(), TAG_RENDER_TOOLS ); + qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) { + counts[ stencilReadback[i] ]++; + } + + R_StaticFree( stencilReadback ); + + // print some stats (not supposed to do from back end in SMP...) + common->Printf( "stencil values:\n" ); + for ( i = 0; i < 255; i++ ) { + if ( counts[i] ) { + common->Printf( "%i: %i\n", i, counts[i] ); + } + } +} + + +/* +=================== +RB_CountStencilBuffer + +Print an overdraw count based on stencil index values +=================== +*/ +static void RB_CountStencilBuffer() { + int count; + int i; + byte *stencilReadback; + + + stencilReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight(), TAG_RENDER_TOOLS ); + qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + count = 0; + for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) { + count += stencilReadback[i]; + } + + R_StaticFree( stencilReadback ); + + // print some stats (not supposed to do from back end in SMP...) + common->Printf( "overdraw: %5.1f\n", (float)count/(renderSystem->GetWidth() * renderSystem->GetHeight()) ); +} + +/* +=================== +R_ColorByStencilBuffer + +Sets the screen colors based on the contents of the +stencil buffer. Stencil of 0 = black, 1 = red, 2 = green, +3 = blue, ..., 7+ = white +=================== +*/ +static void R_ColorByStencilBuffer() { + int i; + static float colors[8][3] = { + {0,0,0}, + {1,0,0}, + {0,1,0}, + {0,0,1}, + {0,1,1}, + {1,0,1}, + {1,1,0}, + {1,1,1}, + }; + + // clear color buffer to white (>6 passes) + GL_Clear( true, false, false, 0, 1.0f, 1.0f, 1.0f, 1.0f ); + + // now draw color for each stencil value + qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + for ( i = 0; i < 6; i++ ) { + GL_Color( colors[i] ); + renderProgManager.BindShader_Color(); + qglStencilFunc( GL_EQUAL, i, 255 ); + RB_PolygonClear(); + } + + qglStencilFunc( GL_ALWAYS, 0, 255 ); +} + +//====================================================================== + +/* +================== +RB_ShowOverdraw +================== +*/ +void RB_ShowOverdraw() { + const idMaterial * material; + int i; + drawSurf_t * * drawSurfs; + const drawSurf_t * surf; + int numDrawSurfs; + viewLight_t * vLight; + + if ( r_showOverDraw.GetInteger() == 0 ) { + return; + } + + material = declManager->FindMaterial( "textures/common/overdrawtest", false ); + if ( material == NULL ) { + return; + } + + drawSurfs = backEnd.viewDef->drawSurfs; + numDrawSurfs = backEnd.viewDef->numDrawSurfs; + + int interactions = 0; + for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { + for ( surf = vLight->localInteractions; surf; surf = surf->nextOnLight ) { + interactions++; + } + for ( surf = vLight->globalInteractions; surf; surf = surf->nextOnLight ) { + interactions++; + } + } + + // FIXME: can't frame alloc from the renderer back-end + drawSurf_t **newDrawSurfs = (drawSurf_t **)R_FrameAlloc( numDrawSurfs + interactions * sizeof( newDrawSurfs[0] ), FRAME_ALLOC_DRAW_SURFACE_POINTER ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + surf = drawSurfs[i]; + if ( surf->material ) { + const_cast(surf)->material = material; + } + newDrawSurfs[i] = const_cast(surf); + } + + for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { + for ( surf = vLight->localInteractions; surf; surf = surf->nextOnLight ) { + const_cast(surf)->material = material; + newDrawSurfs[i++] = const_cast(surf); + } + for ( surf = vLight->globalInteractions; surf; surf = surf->nextOnLight ) { + const_cast(surf)->material = material; + newDrawSurfs[i++] = const_cast(surf); + } + vLight->localInteractions = NULL; + vLight->globalInteractions = NULL; + } + + switch( r_showOverDraw.GetInteger() ) { + case 1: // geometry overdraw + const_cast(backEnd.viewDef)->drawSurfs = newDrawSurfs; + const_cast(backEnd.viewDef)->numDrawSurfs = numDrawSurfs; + break; + case 2: // light interaction overdraw + const_cast(backEnd.viewDef)->drawSurfs = &newDrawSurfs[numDrawSurfs]; + const_cast(backEnd.viewDef)->numDrawSurfs = interactions; + break; + case 3: // geometry + light interaction overdraw + const_cast(backEnd.viewDef)->drawSurfs = newDrawSurfs; + const_cast(backEnd.viewDef)->numDrawSurfs += interactions; + break; + } +} + +/* +=================== +RB_ShowIntensity + +Debugging tool to see how much dynamic range a scene is using. +The greatest of the rgb values at each pixel will be used, with +the resulting color shading from red at 0 to green at 128 to blue at 255 +=================== +*/ +static void RB_ShowIntensity() { + byte *colorReadback; + int i, j, c; + + if ( !r_showIntensity.GetBool() ) { + return; + } + + colorReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight() * 4, TAG_RENDER_TOOLS ); + qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, colorReadback ); + + c = renderSystem->GetWidth() * renderSystem->GetHeight() * 4; + for ( i = 0; i < c; i+=4 ) { + j = colorReadback[i]; + if ( colorReadback[i+1] > j ) { + j = colorReadback[i+1]; + } + if ( colorReadback[i+2] > j ) { + j = colorReadback[i+2]; + } + if ( j < 128 ) { + colorReadback[i+0] = 2*(128-j); + colorReadback[i+1] = 2*j; + colorReadback[i+2] = 0; + } else { + colorReadback[i+0] = 0; + colorReadback[i+1] = 2*(255-j); + colorReadback[i+2] = 2*(j-128); + } + } + + // draw it back to the screen + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0, 0 ); + qglPopMatrix(); + GL_Color( 1, 1, 1 ); + globalImages->BindNull(); + qglMatrixMode( GL_MODELVIEW ); + + qglDrawPixels( renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA , GL_UNSIGNED_BYTE, colorReadback ); + + R_StaticFree( colorReadback ); +} + + +/* +=================== +RB_ShowDepthBuffer + +Draw the depth buffer as colors +=================== +*/ +static void RB_ShowDepthBuffer() { + void *depthReadback; + + if ( !r_showDepth.GetBool() ) { + return; + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0, 0 ); + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); + + GL_State( GLS_DEPTHFUNC_ALWAYS ); + GL_Color( 1, 1, 1 ); + globalImages->BindNull(); + + depthReadback = R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight()*4, TAG_RENDER_TOOLS ); + memset( depthReadback, 0, renderSystem->GetWidth() * renderSystem->GetHeight()*4 ); + + qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_DEPTH_COMPONENT , GL_FLOAT, depthReadback ); + +#if 0 + for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) { + ((byte *)depthReadback)[i*4] = + ((byte *)depthReadback)[i*4+1] = + ((byte *)depthReadback)[i*4+2] = 255 * ((float *)depthReadback)[i]; + ((byte *)depthReadback)[i*4+3] = 1; + } +#endif + + qglDrawPixels( renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA , GL_UNSIGNED_BYTE, depthReadback ); + R_StaticFree( depthReadback ); +} + +/* +================= +RB_ShowLightCount + +This is a debugging tool that will draw each surface with a color +based on how many lights are effecting it +================= +*/ +static void RB_ShowLightCount() { + int i; + const drawSurf_t *surf; + const viewLight_t *vLight; + + if ( !r_showLightCount.GetBool() ) { + return; + } + + RB_SimpleWorldSetup(); + + GL_Clear( false, false, true, 0, 0.0f, 0.0f, 0.0f, 0.0f ); + + // optionally count everything through walls + if ( r_showLightCount.GetInteger() >= 2 ) { + GL_State( GLS_DEPTHFUNC_EQUAL | GLS_STENCIL_OP_FAIL_KEEP | GLS_STENCIL_OP_ZFAIL_INCR | GLS_STENCIL_OP_PASS_INCR ); + } else { + GL_State( GLS_DEPTHFUNC_EQUAL | GLS_STENCIL_OP_FAIL_KEEP | GLS_STENCIL_OP_ZFAIL_KEEP | GLS_STENCIL_OP_PASS_INCR ); + } + + globalImages->defaultImage->Bind(); + + for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { + for ( i = 0; i < 2; i++ ) { + for ( surf = i ? vLight->localInteractions: vLight->globalInteractions; surf; surf = (drawSurf_t *)surf->nextOnLight ) { + RB_SimpleSurfaceSetup( surf ); + RB_DrawElementsWithCounters( surf ); + } + } + } + + // display the results + R_ColorByStencilBuffer(); + + if ( r_showLightCount.GetInteger() > 2 ) { + RB_CountStencilBuffer(); + } +} + +/* +=============== +RB_SetWeaponDepthHack +=============== +*/ +static void RB_SetWeaponDepthHack() { +} + +/* +=============== +RB_SetModelDepthHack +=============== +*/ +static void RB_SetModelDepthHack( float depth ) { +} + +/* +=============== +RB_EnterWeaponDepthHack +=============== +*/ +static void RB_EnterWeaponDepthHack() { + float matrix[16]; + + memcpy( matrix, backEnd.viewDef->projectionMatrix, sizeof( matrix ) ); + + const float modelDepthHack = 0.25f; + matrix[2] *= modelDepthHack; + matrix[6] *= modelDepthHack; + matrix[10] *= modelDepthHack; + matrix[14] *= modelDepthHack; + + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( matrix ); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +=============== +RB_EnterModelDepthHack +=============== +*/ +static void RB_EnterModelDepthHack( float depth ) { + float matrix[16]; + + memcpy( matrix, backEnd.viewDef->projectionMatrix, sizeof( matrix ) ); + + matrix[14] -= depth; + + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( matrix ); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +=============== +RB_LeaveDepthHack +=============== +*/ +static void RB_LeaveDepthHack() { + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( backEnd.viewDef->projectionMatrix ); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +============= +RB_LoadMatrixWithBypass + +does a glLoadMatrixf after optionally applying the low-latency bypass matrix +============= +*/ +static void RB_LoadMatrixWithBypass( const float m[16] ) { + glLoadMatrixf( m ); +} + +/* +==================== +RB_RenderDrawSurfListWithFunction + +The triangle functions can check backEnd.currentSpace != surf->space +to see if they need to perform any new matrix setup. The modelview +matrix will already have been loaded, and backEnd.currentSpace will +be updated after the triangle function completes. +==================== +*/ +static void RB_RenderDrawSurfListWithFunction( drawSurf_t **drawSurfs, int numDrawSurfs, void (*triFunc_)( const drawSurf_t *) ) { + backEnd.currentSpace = NULL; + + for ( int i = 0 ; i < numDrawSurfs ; i++ ) { + const drawSurf_t * drawSurf = drawSurfs[i]; + if ( drawSurf == NULL ) { + continue; + } + assert( drawSurf->space != NULL ); + if ( drawSurf->space != NULL ) { // is it ever NULL? Do we need to check? + // Set these values ahead of time so we don't have to reconstruct the matrices on the consoles + if ( drawSurf->space->weaponDepthHack ) { + RB_SetWeaponDepthHack(); + } + + if ( drawSurf->space->modelDepthHack != 0.0f ) { + RB_SetModelDepthHack( drawSurf->space->modelDepthHack ); + } + + // change the matrix if needed + if ( drawSurf->space != backEnd.currentSpace ) { + RB_LoadMatrixWithBypass( drawSurf->space->modelViewMatrix ); + } + + if ( drawSurf->space->weaponDepthHack ) { + RB_EnterWeaponDepthHack(); + } + + if ( drawSurf->space->modelDepthHack != 0.0f ) { + RB_EnterModelDepthHack( drawSurf->space->modelDepthHack ); + } + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( drawSurf->scissorRect ) ) { + backEnd.currentScissor = drawSurf->scissorRect; + GL_Scissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + + // render it + triFunc_( drawSurf ); + + if ( drawSurf->space != NULL && ( drawSurf->space->weaponDepthHack || drawSurf->space->modelDepthHack != 0.0f ) ) { + RB_LeaveDepthHack(); + } + + backEnd.currentSpace = drawSurf->space; + } +} + +/* +================= +RB_ShowSilhouette + +Blacks out all edges, then adds color for each edge that a shadow +plane extends from, allowing you to see doubled edges + +FIXME: not thread safe! +================= +*/ +static void RB_ShowSilhouette() { + int i; + const drawSurf_t *surf; + const viewLight_t *vLight; + + if ( !r_showSilhouette.GetBool() ) { + return; + } + + // + // clear all triangle edges to black + // + globalImages->BindNull(); + qglDisable( GL_TEXTURE_2D ); + + GL_Color( 0, 0, 0 ); + + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_POLYMODE_LINE ); + + GL_Cull( CT_TWO_SIDED ); + + RB_RenderDrawSurfListWithFunction( backEnd.viewDef->drawSurfs, backEnd.viewDef->numDrawSurfs, + RB_DrawElementsWithCounters ); + + + // + // now blend in edges that cast silhouettes + // + RB_SimpleWorldSetup(); + GL_Color( 0.5, 0, 0 ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { + for ( i = 0; i < 2; i++ ) { + for ( surf = i ? vLight->localShadows : vLight->globalShadows + ; surf; surf = (drawSurf_t *)surf->nextOnLight ) { + RB_SimpleSurfaceSetup( surf ); + + const srfTriangles_t * tri = surf->frontEndGeo; + + idVertexBuffer vertexBuffer; + if ( !vertexCache.GetVertexBuffer( tri->shadowCache, &vertexBuffer ) ) { + continue; + } + + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, (GLuint)vertexBuffer.GetAPIObject() ); + int vertOffset = vertexBuffer.GetOffset(); + + qglVertexPointer( 3, GL_FLOAT, sizeof( idShadowVert ), (void *)vertOffset ); + qglBegin( GL_LINES ); + + for ( int j = 0; j < tri->numIndexes; j+=3 ) { + int i1 = tri->indexes[j+0]; + int i2 = tri->indexes[j+1]; + int i3 = tri->indexes[j+2]; + + if ( (i1 & 1) + (i2 & 1) + (i3 & 1) == 1 ) { + if ( (i1 & 1) + (i2 & 1) == 0 ) { + qglArrayElement( i1 ); + qglArrayElement( i2 ); + } else if ( (i1 & 1 ) + (i3 & 1) == 0 ) { + qglArrayElement( i1 ); + qglArrayElement( i3 ); + } + } + } + qglEnd(); + + } + } + } + + GL_State( GLS_DEFAULT ); + GL_Color( 1,1,1 ); + GL_Cull( CT_FRONT_SIDED ); +} + +/* +===================== +RB_ShowTris + +Debugging tool +===================== +*/ +static void RB_ShowTris( drawSurf_t **drawSurfs, int numDrawSurfs ) { + + modelTrace_t mt; + idVec3 end; + + if ( r_showTris.GetInteger() == 0 ) { + return; + } + + float color[4] = { 1, 1, 1, 1 }; + + GL_PolygonOffset( -1.0f, -2.0f ); + + switch ( r_showTris.GetInteger() ) { + case 1: // only draw visible ones + GL_State( GLS_DEPTHMASK | GLS_ALPHAMASK | GLS_POLYMODE_LINE | GLS_POLYGON_OFFSET ); + break; + case 2: // draw all front facing + case 3: // draw all + GL_State( GLS_DEPTHMASK | GLS_ALPHAMASK | GLS_POLYMODE_LINE | GLS_POLYGON_OFFSET | GLS_DEPTHFUNC_ALWAYS ); + break; + case 4: // only draw visible ones with blended lines + GL_State( GLS_DEPTHMASK | GLS_ALPHAMASK | GLS_POLYMODE_LINE | GLS_POLYGON_OFFSET | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + color[3] = 0.4f; + break; + } + + if ( r_showTris.GetInteger() == 3 ) { + GL_Cull( CT_TWO_SIDED ); + } + + GL_Color( color ); + renderProgManager.BindShader_Color(); + + RB_RenderDrawSurfListWithFunction( drawSurfs, numDrawSurfs, RB_DrawElementsWithCounters ); + + if ( r_showTris.GetInteger() == 3 ) { + GL_Cull( CT_FRONT_SIDED ); + } +} + +/* +===================== +RB_ShowSurfaceInfo + +Debugging tool +===================== +*/ +static void RB_ShowSurfaceInfo( drawSurf_t **drawSurfs, int numDrawSurfs ) { + modelTrace_t mt; + idVec3 start, end; + + if ( !r_showSurfaceInfo.GetBool() ) { + return; + } + + // start far enough away that we don't hit the player model + start = tr.primaryView->renderView.vieworg + tr.primaryView->renderView.viewaxis[0] * 16; + end = start + tr.primaryView->renderView.viewaxis[0] * 1000.0f; + if ( !tr.primaryWorld->Trace( mt, start, end, 0.0f, false ) ) { + return; + } + + globalImages->BindNull(); + qglDisable( GL_TEXTURE_2D ); + + GL_Color( 1, 1, 1 ); + + static float scale = -1; + static float bias = -2; + + GL_PolygonOffset( scale, bias ); + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_POLYMODE_LINE | GLS_POLYGON_OFFSET ); + + idVec3 trans[3]; + float matrix[16]; + + // transform the object verts into global space + R_AxisToModelMatrix( mt.entity->axis, mt.entity->origin, matrix ); + + tr.primaryWorld->DrawText( mt.entity->hModel->Name(), mt.point + tr.primaryView->renderView.viewaxis[2] * 12, + 0.35f, colorRed, tr.primaryView->renderView.viewaxis ); + tr.primaryWorld->DrawText( mt.material->GetName(), mt.point, + 0.35f, colorBlue, tr.primaryView->renderView.viewaxis ); +} + +/* +===================== +RB_ShowViewEntitys + +Debugging tool +===================== +*/ +static void RB_ShowViewEntitys( viewEntity_t *vModels ) { + if ( !r_showViewEntitys.GetBool() ) { + return; + } + if ( r_showViewEntitys.GetInteger() >= 2 ) { + common->Printf( "view entities: " ); + for ( const viewEntity_t * vModel = vModels; vModel; vModel = vModel->next ) { + if ( vModel->entityDef->IsDirectlyVisible() ) { + common->Printf( "<%i> ", vModel->entityDef->index ); + } else { + common->Printf( "%i ", vModel->entityDef->index ); + } + } + common->Printf( "\n" ); + } + + globalImages->BindNull(); + + renderProgManager.BindShader_Color(); + + GL_Color( 1, 1, 1 ); + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_POLYMODE_LINE ); + GL_Cull( CT_TWO_SIDED ); + + for ( const viewEntity_t * vModel = vModels; vModel; vModel = vModel->next ) { + idBounds b; + + qglLoadMatrixf( vModel->modelViewMatrix ); + + const idRenderEntityLocal * edef = vModel->entityDef; + if ( !edef ) { + continue; + } + + + + // draw the model bounds in white if directly visible, + // or, blue if it is only-for-sahdow + idVec4 color; + if ( edef->IsDirectlyVisible() ) { + color.Set( 1, 1, 1, 1 ); + } else { + color.Set( 0, 0, 1, 1 ); + } + GL_Color( color[0], color[1], color[2] ); + RB_DrawBounds( edef->localReferenceBounds ); + + // transform the upper bounds corner into global space + if ( r_showViewEntitys.GetInteger() >= 2 ) { + idVec3 corner; + R_LocalPointToGlobal( vModel->modelMatrix, edef->localReferenceBounds[1], corner ); + + tr.primaryWorld->DrawText( + va( "%i:%s", edef->index, edef->parms.hModel->Name() ), + corner, + 0.25f, color, + tr.primaryView->renderView.viewaxis ); + } + + // draw the actual bounds in yellow if different + if ( r_showViewEntitys.GetInteger() >= 3 ) { + GL_Color( 1, 1, 0 ); + // FIXME: cannot instantiate a dynamic model from the renderer back-end + idRenderModel *model = R_EntityDefDynamicModel( vModel->entityDef ); + if ( !model ) { + continue; // particles won't instantiate without a current view + } + b = model->Bounds( &vModel->entityDef->parms ); + if ( b != vModel->entityDef->localReferenceBounds ) { + RB_DrawBounds( b ); + } + } + } +} + +/* +===================== +RB_ShowTexturePolarity + +Shade triangle red if they have a positive texture area +green if they have a negative texture area, or blue if degenerate area +===================== +*/ +static void RB_ShowTexturePolarity( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showTexturePolarity.GetBool() ) { + return; + } + globalImages->BindNull(); + + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + GL_Color( 1, 1, 1 ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + tri = drawSurf->frontEndGeo; + if ( !tri->verts ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + qglBegin( GL_TRIANGLES ); + for ( j = 0; j < tri->numIndexes; j+=3 ) { + idDrawVert *a, *b, *c; + float d0[5], d1[5]; + float area; + + a = tri->verts + tri->indexes[j]; + b = tri->verts + tri->indexes[j+1]; + c = tri->verts + tri->indexes[j+2]; + + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + d0[3] = bST[0] - aST[0]; + d0[4] = bST[1] - aST[1]; + + d1[3] = cST[0] - aST[0]; + d1[4] = cST[1] - aST[1]; + + area = d0[3] * d1[4] - d0[4] * d1[3]; + + if ( idMath::Fabs( area ) < 0.0001 ) { + GL_Color( 0, 0, 1, 0.5 ); + } else if ( area < 0 ) { + GL_Color( 1, 0, 0, 0.5 ); + } else { + GL_Color( 0, 1, 0, 0.5 ); + } + qglVertex3fv( a->xyz.ToFloatPtr() ); + qglVertex3fv( b->xyz.ToFloatPtr() ); + qglVertex3fv( c->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + +/* +===================== +RB_ShowUnsmoothedTangents + +Shade materials that are using unsmoothed tangents +===================== +*/ +static void RB_ShowUnsmoothedTangents( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showUnsmoothedTangents.GetBool() ) { + return; + } + globalImages->BindNull(); + + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + GL_Color( 0, 1, 0, 0.5 ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + + if ( !drawSurf->material->UseUnsmoothedTangents() ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->frontEndGeo; + qglBegin( GL_TRIANGLES ); + for ( j = 0; j < tri->numIndexes; j+=3 ) { + idDrawVert *a, *b, *c; + + a = tri->verts + tri->indexes[j]; + b = tri->verts + tri->indexes[j+1]; + c = tri->verts + tri->indexes[j+2]; + + qglVertex3fv( a->xyz.ToFloatPtr() ); + qglVertex3fv( b->xyz.ToFloatPtr() ); + qglVertex3fv( c->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + +/* +===================== +RB_ShowTangentSpace + +Shade a triangle by the RGB colors of its tangent space +1 = tangents[0] +2 = tangents[1] +3 = normal +===================== +*/ +static void RB_ShowTangentSpace( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showTangentSpace.GetInteger() ) { + return; + } + globalImages->BindNull(); + + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->frontEndGeo; + if ( !tri->verts ) { + continue; + } + qglBegin( GL_TRIANGLES ); + for ( j = 0; j < tri->numIndexes; j++ ) { + const idDrawVert *v; + + v = &tri->verts[tri->indexes[j]]; + + if ( r_showTangentSpace.GetInteger() == 1 ) { + const idVec3 vertexTangent = v->GetTangent(); + GL_Color( 0.5 + 0.5 * vertexTangent[0], 0.5 + 0.5 * vertexTangent[1], + 0.5 + 0.5 * vertexTangent[2], 0.5 ); + } else if ( r_showTangentSpace.GetInteger() == 2 ) { + const idVec3 vertexBiTangent = v->GetBiTangent(); + GL_Color( 0.5 + 0.5 *vertexBiTangent[0], 0.5 + 0.5 * vertexBiTangent[1], + 0.5 + 0.5 * vertexBiTangent[2], 0.5 ); + } else { + const idVec3 vertexNormal = v->GetNormal(); + GL_Color( 0.5 + 0.5 * vertexNormal[0], 0.5 + 0.5 * vertexNormal[1], + 0.5 + 0.5 * vertexNormal[2], 0.5 ); + } + qglVertex3fv( v->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + +/* +===================== +RB_ShowVertexColor + +Draw each triangle with the solid vertex colors +===================== +*/ +static void RB_ShowVertexColor( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showVertexColor.GetBool() ) { + return; + } + globalImages->BindNull(); + + GL_State( GLS_DEPTHFUNC_LESS ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->frontEndGeo; + if ( !tri->verts ) { + continue; + } + qglBegin( GL_TRIANGLES ); + for ( j = 0; j < tri->numIndexes; j++ ) { + const idDrawVert *v; + + v = &tri->verts[tri->indexes[j]]; + qglColor4ubv( v->color ); + qglVertex3fv( v->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + +/* +===================== +RB_ShowNormals + +Debugging tool +===================== +*/ +static void RB_ShowNormals( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + idVec3 end; + const srfTriangles_t *tri; + float size; + bool showNumbers; + idVec3 pos; + + if ( r_showNormals.GetFloat() == 0.0f ) { + return; + } + + globalImages->BindNull(); + + if ( !r_debugLineDepthTest.GetBool() ) { + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS ); + } else { + GL_State( GLS_POLYMODE_LINE ); + } + + size = r_showNormals.GetFloat(); + if ( size < 0.0f ) { + size = -size; + showNumbers = true; + } else { + showNumbers = false; + } + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->frontEndGeo; + if ( !tri->verts ) { + continue; + } + + qglBegin( GL_LINES ); + for ( j = 0; j < tri->numVerts; j++ ) { + const idVec3 normal = tri->verts[j].GetNormal(); + const idVec3 tangent = tri->verts[j].GetTangent(); + const idVec3 bitangent = tri->verts[j].GetBiTangent(); + GL_Color( 0, 0, 1 ); + qglVertex3fv( tri->verts[j].xyz.ToFloatPtr() ); + VectorMA( tri->verts[j].xyz, size, normal, end ); + qglVertex3fv( end.ToFloatPtr() ); + + GL_Color( 1, 0, 0 ); + qglVertex3fv( tri->verts[j].xyz.ToFloatPtr() ); + VectorMA( tri->verts[j].xyz, size, tangent, end ); + qglVertex3fv( end.ToFloatPtr() ); + + GL_Color( 0, 1, 0 ); + qglVertex3fv( tri->verts[j].xyz.ToFloatPtr() ); + VectorMA( tri->verts[j].xyz, size, bitangent, end ); + qglVertex3fv( end.ToFloatPtr() ); + } + qglEnd(); + } + + if ( showNumbers ) { + RB_SimpleWorldSetup(); + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + tri = drawSurf->frontEndGeo; + if ( !tri->verts ) { + continue; + } + + for ( j = 0; j < tri->numVerts; j++ ) { + const idVec3 normal = tri->verts[j].GetNormal(); + const idVec3 tangent = tri->verts[j].GetTangent(); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, tri->verts[j].xyz + tangent + normal * 0.2f, pos ); + RB_DrawText( va( "%d", j ), pos, 0.01f, colorWhite, backEnd.viewDef->renderView.viewaxis, 1 ); + } + + for ( j = 0; j < tri->numIndexes; j += 3 ) { + const idVec3 normal = tri->verts[ tri->indexes[ j + 0 ] ].GetNormal(); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, ( tri->verts[ tri->indexes[ j + 0 ] ].xyz + tri->verts[ tri->indexes[ j + 1 ] ].xyz + tri->verts[ tri->indexes[ j + 2 ] ].xyz ) * ( 1.0f / 3.0f ) + normal * 0.2f, pos ); + RB_DrawText( va( "%d", j / 3 ), pos, 0.01f, colorCyan, backEnd.viewDef->renderView.viewaxis, 1 ); + } + } + } +} + +#if 0 // compiler warning + +/* +===================== +RB_ShowNormals + +Debugging tool +===================== +*/ +static void RB_AltShowNormals( drawSurf_t **drawSurfs, int numDrawSurfs ) { + if ( r_showNormals.GetFloat() == 0.0f ) { + return; + } + + globalImages->BindNull(); + + GL_State( GLS_DEPTHFUNC_ALWAYS ); + + for ( int i = 0; i < numDrawSurfs; i++ ) { + drawSurf_t * drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + const srfTriangles_t * tri = drawSurf->geo; + + qglBegin( GL_LINES ); + for ( int j = 0; j < tri->numIndexes; j += 3 ) { + const idDrawVert *v[3] = { + &tri->verts[tri->indexes[j+0]], + &tri->verts[tri->indexes[j+1]], + &tri->verts[tri->indexes[j+2]] + } + + const idPlane plane( v[0]->xyz, v[1]->xyz, v[2]->xyz ); + + // make the midpoint slightly above the triangle + const idVec3 mid = ( v[0]->xyz + v[1]->xyz + v[2]->xyz ) * ( 1.0f / 3.0f ) + 0.1f * plane.Normal(); + + for ( int k = 0; k < 3; k++ ) { + const idVec3 pos = ( mid + v[k]->xyz * 3.0f ) * 0.25f; + idVec3 end; + + GL_Color( 0, 0, 1 ); + qglVertex3fv( pos.ToFloatPtr() ); + VectorMA( pos, r_showNormals.GetFloat(), v[k]->normal, end ); + qglVertex3fv( end.ToFloatPtr() ); + + GL_Color( 1, 0, 0 ); + qglVertex3fv( pos.ToFloatPtr() ); + VectorMA( pos, r_showNormals.GetFloat(), v[k]->tangents[0], end ); + qglVertex3fv( end.ToFloatPtr() ); + + GL_Color( 0, 1, 0 ); + qglVertex3fv( pos.ToFloatPtr() ); + VectorMA( pos, r_showNormals.GetFloat(), v[k]->tangents[1], end ); + qglVertex3fv( end.ToFloatPtr() ); + + GL_Color( 1, 1, 1 ); + qglVertex3fv( pos.ToFloatPtr() ); + qglVertex3fv( v[k]->xyz.ToFloatPtr() ); + } + } + qglEnd(); + } +} + +#endif + +/* +===================== +RB_ShowTextureVectors + +Draw texture vectors in the center of each triangle +===================== +*/ +static void RB_ShowTextureVectors( drawSurf_t **drawSurfs, int numDrawSurfs ) { + if ( r_showTextureVectors.GetFloat() == 0.0f ) { + return; + } + + GL_State( GLS_DEPTHFUNC_LESS ); + + globalImages->BindNull(); + + for ( int i = 0; i < numDrawSurfs; i++ ) { + drawSurf_t * drawSurf = drawSurfs[i]; + + const srfTriangles_t * tri = drawSurf->frontEndGeo; + + if ( tri->verts == NULL ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + // draw non-shared edges in yellow + qglBegin( GL_LINES ); + + for ( int j = 0; j < tri->numIndexes; j+= 3 ) { + float d0[5], d1[5]; + idVec3 temp; + idVec3 tangents[2]; + + const idDrawVert *a = &tri->verts[tri->indexes[j+0]]; + const idDrawVert *b = &tri->verts[tri->indexes[j+1]]; + const idDrawVert *c = &tri->verts[tri->indexes[j+2]]; + + const idPlane plane( a->xyz, b->xyz, c->xyz ); + + // make the midpoint slightly above the triangle + const idVec3 mid = ( a->xyz + b->xyz + c->xyz ) * ( 1.0f / 3.0f ) + 0.1f * plane.Normal(); + + // calculate the texture vectors + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = bST[0] - aST[0]; + d0[4] = bST[1] - aST[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = cST[0] - aST[0]; + d1[4] = cST[1] - aST[1]; + + const float area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( area == 0 ) { + continue; + } + const float inva = 1.0f / area; + + temp[0] = (d0[0] * d1[4] - d0[4] * d1[0]) * inva; + temp[1] = (d0[1] * d1[4] - d0[4] * d1[1]) * inva; + temp[2] = (d0[2] * d1[4] - d0[4] * d1[2]) * inva; + temp.Normalize(); + tangents[0] = temp; + + temp[0] = (d0[3] * d1[0] - d0[0] * d1[3]) * inva; + temp[1] = (d0[3] * d1[1] - d0[1] * d1[3]) * inva; + temp[2] = (d0[3] * d1[2] - d0[2] * d1[3]) * inva; + temp.Normalize(); + tangents[1] = temp; + + // draw the tangents + tangents[0] = mid + tangents[0] * r_showTextureVectors.GetFloat(); + tangents[1] = mid + tangents[1] * r_showTextureVectors.GetFloat(); + + GL_Color( 1, 0, 0 ); + qglVertex3fv( mid.ToFloatPtr() ); + qglVertex3fv( tangents[0].ToFloatPtr() ); + + GL_Color( 0, 1, 0 ); + qglVertex3fv( mid.ToFloatPtr() ); + qglVertex3fv( tangents[1].ToFloatPtr() ); + } + + qglEnd(); + } +} + +/* +===================== +RB_ShowDominantTris + +Draw lines from each vertex to the dominant triangle center +===================== +*/ +static void RB_ShowDominantTris( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showDominantTri.GetBool() ) { + return; + } + + GL_State( GLS_DEPTHFUNC_LESS ); + + GL_PolygonOffset( -1, -2 ); + qglEnable( GL_POLYGON_OFFSET_LINE ); + + globalImages->BindNull(); + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + + tri = drawSurf->frontEndGeo; + + if ( !tri->verts ) { + continue; + } + if ( !tri->dominantTris ) { + continue; + } + RB_SimpleSurfaceSetup( drawSurf ); + + GL_Color( 1, 1, 0 ); + qglBegin( GL_LINES ); + + for ( j = 0; j < tri->numVerts; j++ ) { + const idDrawVert *a, *b, *c; + idVec3 mid; + + // find the midpoint of the dominant tri + + a = &tri->verts[j]; + b = &tri->verts[tri->dominantTris[j].v2]; + c = &tri->verts[tri->dominantTris[j].v3]; + + mid = ( a->xyz + b->xyz + c->xyz ) * ( 1.0f / 3.0f ); + + qglVertex3fv( mid.ToFloatPtr() ); + qglVertex3fv( a->xyz.ToFloatPtr() ); + } + + qglEnd(); + } + qglDisable( GL_POLYGON_OFFSET_LINE ); +} + +/* +===================== +RB_ShowEdges + +Debugging tool +===================== +*/ +static void RB_ShowEdges( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j, k, m, n, o; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + const silEdge_t *edge; + int danglePlane; + + if ( !r_showEdges.GetBool() ) { + return; + } + + globalImages->BindNull(); + + GL_State( GLS_DEPTHFUNC_ALWAYS ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + drawSurf = drawSurfs[i]; + + tri = drawSurf->frontEndGeo; + + idDrawVert *ac = (idDrawVert *)tri->verts; + if ( !ac ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + // draw non-shared edges in yellow + GL_Color( 1, 1, 0 ); + qglBegin( GL_LINES ); + + for ( j = 0; j < tri->numIndexes; j+= 3 ) { + for ( k = 0; k < 3; k++ ) { + int l, i1, i2; + l = ( k == 2 ) ? 0 : k + 1; + i1 = tri->indexes[j+k]; + i2 = tri->indexes[j+l]; + + // if these are used backwards, the edge is shared + for ( m = 0; m < tri->numIndexes; m += 3 ) { + for ( n = 0; n < 3; n++ ) { + o = ( n == 2 ) ? 0 : n + 1; + if ( tri->indexes[m+n] == i2 && tri->indexes[m+o] == i1 ) { + break; + } + } + if ( n != 3 ) { + break; + } + } + + // if we didn't find a backwards listing, draw it in yellow + if ( m == tri->numIndexes ) { + qglVertex3fv( ac[ i1 ].xyz.ToFloatPtr() ); + qglVertex3fv( ac[ i2 ].xyz.ToFloatPtr() ); + } + + } + } + + qglEnd(); + + // draw dangling sil edges in red + if ( !tri->silEdges ) { + continue; + } + + // the plane number after all real planes + // is the dangling edge + danglePlane = tri->numIndexes / 3; + + GL_Color( 1, 0, 0 ); + + qglBegin( GL_LINES ); + for ( j = 0; j < tri->numSilEdges; j++ ) { + edge = tri->silEdges + j; + + if ( edge->p1 != danglePlane && edge->p2 != danglePlane ) { + continue; + } + + qglVertex3fv( ac[ edge->v1 ].xyz.ToFloatPtr() ); + qglVertex3fv( ac[ edge->v2 ].xyz.ToFloatPtr() ); + } + qglEnd(); + } +} + +/* +============== +RB_ShowLights + +Visualize all light volumes used in the current scene +r_showLights 1 : just print volumes numbers, highlighting ones covering the view +r_showLights 2 : also draw planes of each volume +r_showLights 3 : also draw edges of each volume +============== +*/ +static void RB_ShowLights() { + if ( !r_showLights.GetInteger() ) { + return; + } + + GL_State( GLS_DEFAULT ); + + // we use the 'vLight->invProjectMVPMatrix' + qglMatrixMode( GL_PROJECTION ); + qglLoadIdentity(); + + globalImages->BindNull(); + + renderProgManager.BindShader_Color(); + + GL_Cull( CT_TWO_SIDED ); + + common->Printf( "volumes: " ); // FIXME: not in back end! + + int count = 0; + for ( viewLight_t * vLight = backEnd.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + count++; + + // depth buffered planes + if ( r_showLights.GetInteger() >= 2 ) { + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK ); + GL_Color( 0.0f, 0.0f, 1.0f, 0.25f ); + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( backEnd.viewDef->worldSpace.mvp, vLight->inverseBaseLightProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + RB_DrawElementsWithCounters( &backEnd.zeroOneCubeSurface ); + } + + // non-hidden lines + if ( r_showLights.GetInteger() >= 3 ) { + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_POLYMODE_LINE | GLS_DEPTHMASK ); + GL_Color( 1.0f, 1.0f, 1.0f ); + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( backEnd.viewDef->worldSpace.mvp, vLight->inverseBaseLightProject, invProjectMVPMatrix ); + RB_SetMVP( invProjectMVPMatrix ); + RB_DrawElementsWithCounters( &backEnd.zeroOneCubeSurface ); + } + + common->Printf( "%i ", vLight->lightDef->index ); + } + + common->Printf( " = %i total\n", count ); + + // set back the default projection matrix + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( backEnd.viewDef->projectionMatrix ); + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); +} + +/* +===================== +RB_ShowPortals + +Debugging tool, won't work correctly with SMP or when mirrors are present +===================== +*/ +static void RB_ShowPortals() { + if ( !r_showPortals.GetBool() ) { + return; + } + + // all portals are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + renderProgManager.BindShader_Color(); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + + ((idRenderWorldLocal *)backEnd.viewDef->renderWorld)->ShowPortals(); +} + +/* +================ +RB_ClearDebugText +================ +*/ +void RB_ClearDebugText( int time ) { + int i; + int num; + debugText_t *text; + + rb_debugTextTime = time; + + if ( !time ) { + // free up our strings + text = rb_debugText; + for ( i = 0; i < MAX_DEBUG_TEXT; i++, text++ ) { + text->text.Clear(); + } + rb_numDebugText = 0; + return; + } + + // copy any text that still needs to be drawn + num = 0; + text = rb_debugText; + for ( i = 0; i < rb_numDebugText; i++, text++ ) { + if ( text->lifeTime > time ) { + if ( num != i ) { + rb_debugText[ num ] = *text; + } + num++; + } + } + rb_numDebugText = num; +} + +/* +================ +RB_AddDebugText +================ +*/ +void RB_AddDebugText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ) { + debugText_t *debugText; + + if ( rb_numDebugText < MAX_DEBUG_TEXT ) { + debugText = &rb_debugText[ rb_numDebugText++ ]; + debugText->text = text; + debugText->origin = origin; + debugText->scale = scale; + debugText->color = color; + debugText->viewAxis = viewAxis; + debugText->align = align; + debugText->lifeTime = rb_debugTextTime + lifetime; + debugText->depthTest = depthTest; + } +} + +/* +================ +RB_DrawTextLength + + returns the length of the given text +================ +*/ +float RB_DrawTextLength( const char *text, float scale, int len ) { + int i, num, index, charIndex; + float spacing, textLen = 0.0f; + + if ( text && *text ) { + if ( !len ) { + len = strlen(text); + } + for ( i = 0; i < len; i++ ) { + charIndex = text[i] - 32; + if ( charIndex < 0 || charIndex > NUM_SIMPLEX_CHARS ) { + continue; + } + num = simplex[charIndex][0] * 2; + spacing = simplex[charIndex][1]; + index = 2; + + while( index - 2 < num ) { + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + index += 2; + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + } + textLen += spacing * scale; + } + } + return textLen; +} + +/* +================ +RB_DrawText + + oriented on the viewaxis + align can be 0-left, 1-center (default), 2-right +================ +*/ +static void RB_DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align ) { + renderProgManager.BindShader_Color(); + + + + int i, j, len, num, index, charIndex, line; + float textLen = 1.0f, spacing = 1.0f; + idVec3 org, p1, p2; + + if ( text && *text ) { + qglBegin( GL_LINES ); + qglColor3fv( color.ToFloatPtr() ); + + if ( text[0] == '\n' ) { + line = 1; + } else { + line = 0; + } + + len = strlen( text ); + for ( i = 0; i < len; i++ ) { + + if ( i == 0 || text[i] == '\n' ) { + org = origin - viewAxis[2] * ( line * 36.0f * scale ); + if ( align != 0 ) { + for ( j = 1; i+j <= len; j++ ) { + if ( i+j == len || text[i+j] == '\n' ) { + textLen = RB_DrawTextLength( text+i, scale, j ); + break; + } + } + if ( align == 2 ) { + // right + org += viewAxis[1] * textLen; + } else { + // center + org += viewAxis[1] * ( textLen * 0.5f ); + } + } + line++; + } + + charIndex = text[i] - 32; + if ( charIndex < 0 || charIndex > NUM_SIMPLEX_CHARS ) { + continue; + } + num = simplex[charIndex][0] * 2; + spacing = simplex[charIndex][1]; + index = 2; + + while( index - 2 < num ) { + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + p1 = org + scale * simplex[charIndex][index] * -viewAxis[1] + scale * simplex[charIndex][index+1] * viewAxis[2]; + index += 2; + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + p2 = org + scale * simplex[charIndex][index] * -viewAxis[1] + scale * simplex[charIndex][index+1] * viewAxis[2]; + + qglVertex3fv( p1.ToFloatPtr() ); + qglVertex3fv( p2.ToFloatPtr() ); + } + org -= viewAxis[1] * ( spacing * scale ); + } + + qglEnd(); + } +} + +/* +================ +RB_ShowDebugText +================ +*/ +void RB_ShowDebugText() { + int i; + int width; + debugText_t *text; + + if ( !rb_numDebugText ) { + return; + } + + // all lines are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + + width = r_debugLineWidth.GetInteger(); + if ( width < 1 ) { + width = 1; + } else if ( width > 10 ) { + width = 10; + } + + // draw lines + qglLineWidth( width ); + + + if ( !r_debugLineDepthTest.GetBool() ) { + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS ); + } else { + GL_State( GLS_POLYMODE_LINE ); + } + + text = rb_debugText; + for ( i = 0; i < rb_numDebugText; i++, text++ ) { + if ( !text->depthTest ) { + RB_DrawText( text->text, text->origin, text->scale, text->color, text->viewAxis, text->align ); + } + } + + if ( !r_debugLineDepthTest.GetBool() ) { + GL_State( GLS_POLYMODE_LINE ); + } + + text = rb_debugText; + for ( i = 0; i < rb_numDebugText; i++, text++ ) { + if ( text->depthTest ) { + RB_DrawText( text->text, text->origin, text->scale, text->color, text->viewAxis, text->align ); + } + } + + qglLineWidth( 1 ); +} + +/* +================ +RB_ClearDebugLines +================ +*/ +void RB_ClearDebugLines( int time ) { + int i; + int num; + debugLine_t *line; + + rb_debugLineTime = time; + + if ( !time ) { + rb_numDebugLines = 0; + return; + } + + // copy any lines that still need to be drawn + num = 0; + line = rb_debugLines; + for ( i = 0; i < rb_numDebugLines; i++, line++ ) { + if ( line->lifeTime > time ) { + if ( num != i ) { + rb_debugLines[ num ] = *line; + } + num++; + } + } + rb_numDebugLines = num; +} + +/* +================ +RB_AddDebugLine +================ +*/ +void RB_AddDebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifeTime, const bool depthTest ) { + debugLine_t *line; + + if ( rb_numDebugLines < MAX_DEBUG_LINES ) { + line = &rb_debugLines[ rb_numDebugLines++ ]; + line->rgb = color; + line->start = start; + line->end = end; + line->depthTest = depthTest; + line->lifeTime = rb_debugLineTime + lifeTime; + } +} + +/* +================ +RB_ShowDebugLines +================ +*/ +void RB_ShowDebugLines() { + int i; + int width; + debugLine_t *line; + + if ( !rb_numDebugLines ) { + return; + } + + // all lines are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + + width = r_debugLineWidth.GetInteger(); + if ( width < 1 ) { + width = 1; + } else if ( width > 10 ) { + width = 10; + } + + // draw lines + qglLineWidth( width ); + + if ( !r_debugLineDepthTest.GetBool() ) { + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS ); + } else { + GL_State( GLS_POLYMODE_LINE ); + } + + qglBegin( GL_LINES ); + + line = rb_debugLines; + for ( i = 0; i < rb_numDebugLines; i++, line++ ) { + if ( !line->depthTest ) { + qglColor3fv( line->rgb.ToFloatPtr() ); + qglVertex3fv( line->start.ToFloatPtr() ); + qglVertex3fv( line->end.ToFloatPtr() ); + } + } + qglEnd(); + + if ( !r_debugLineDepthTest.GetBool() ) { + GL_State( GLS_POLYMODE_LINE ); + } + + qglBegin( GL_LINES ); + + line = rb_debugLines; + for ( i = 0; i < rb_numDebugLines; i++, line++ ) { + if ( line->depthTest ) { + qglColor4fv( line->rgb.ToFloatPtr() ); + qglVertex3fv( line->start.ToFloatPtr() ); + qglVertex3fv( line->end.ToFloatPtr() ); + } + } + + qglEnd(); + + qglLineWidth( 1 ); + GL_State( GLS_DEFAULT ); +} + +/* +================ +RB_ClearDebugPolygons +================ +*/ +void RB_ClearDebugPolygons( int time ) { + int i; + int num; + debugPolygon_t *poly; + + rb_debugPolygonTime = time; + + if ( !time ) { + rb_numDebugPolygons = 0; + return; + } + + // copy any polygons that still need to be drawn + num = 0; + + poly = rb_debugPolygons; + for ( i = 0; i < rb_numDebugPolygons; i++, poly++ ) { + if ( poly->lifeTime > time ) { + if ( num != i ) { + rb_debugPolygons[ num ] = *poly; + } + num++; + } + } + rb_numDebugPolygons = num; +} + +/* +================ +RB_AddDebugPolygon +================ +*/ +void RB_AddDebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ) { + debugPolygon_t *poly; + + if ( rb_numDebugPolygons < MAX_DEBUG_POLYGONS ) { + poly = &rb_debugPolygons[ rb_numDebugPolygons++ ]; + poly->rgb = color; + poly->winding = winding; + poly->depthTest = depthTest; + poly->lifeTime = rb_debugPolygonTime + lifeTime; + } +} + +/* +================ +RB_ShowDebugPolygons +================ +*/ +void RB_ShowDebugPolygons() { + int i, j; + debugPolygon_t *poly; + + if ( !rb_numDebugPolygons ) { + return; + } + + // all lines are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + + qglDisable( GL_TEXTURE_2D ); + + if ( r_debugPolygonFilled.GetBool() ) { + GL_State( GLS_POLYGON_OFFSET | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK ); + GL_PolygonOffset( -1, -2 ); + } else { + GL_State( GLS_POLYGON_OFFSET | GLS_POLYMODE_LINE ); + GL_PolygonOffset( -1, -2 ); + } + + poly = rb_debugPolygons; + for ( i = 0; i < rb_numDebugPolygons; i++, poly++ ) { +// if ( !poly->depthTest ) { + + qglColor4fv( poly->rgb.ToFloatPtr() ); + + qglBegin( GL_POLYGON ); + + for ( j = 0; j < poly->winding.GetNumPoints(); j++) { + qglVertex3fv( poly->winding[j].ToFloatPtr() ); + } + + qglEnd(); +// } + } + + GL_State( GLS_DEFAULT ); + + if ( r_debugPolygonFilled.GetBool() ) { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } else { + qglDisable( GL_POLYGON_OFFSET_LINE ); + } + + GL_State( GLS_DEFAULT ); +} + +/* +================ +RB_ShowCenterOfProjection +================ +*/ +void RB_ShowCenterOfProjection() { + if ( !r_showCenterOfProjection.GetBool() ) { + return; + } + + const int w = backEnd.viewDef->scissor.GetWidth(); + const int h = backEnd.viewDef->scissor.GetHeight(); + qglClearColor( 1, 0, 0, 1 ); + for ( float f = 0.0f ; f <= 1.0f ; f += 0.125f ) { + qglScissor( w * f - 1 , 0, 3, h ); + qglClear( GL_COLOR_BUFFER_BIT ); + qglScissor( 0, h * f - 1 , w, 3 ); + qglClear( GL_COLOR_BUFFER_BIT ); + } + qglClearColor( 0, 1, 0, 1 ); + float f = 0.5f; + qglScissor( w * f - 1 , 0, 3, h ); + qglClear( GL_COLOR_BUFFER_BIT ); + qglScissor( 0, h * f - 1 , w, 3 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + qglScissor( 0, 0, w, h ); +} + +/* +================ +RB_ShowLines + +Draw exact pixel lines to check pixel center sampling +================ +*/ +void RB_ShowLines() { + if ( !r_showLines.GetBool() ) { + return; + } + + glEnable( GL_SCISSOR_TEST ); + if ( backEnd.viewDef->renderView.viewEyeBuffer == 0 ) { + glClearColor( 1, 0, 0, 1 ); + } else if ( backEnd.viewDef->renderView.viewEyeBuffer == 1 ) { + glClearColor( 0, 1, 0, 1 ); + } else { + glClearColor( 0, 0, 1, 1 ); + } + + const int start = ( r_showLines.GetInteger() > 2 ); // 1,3 = horizontal, 2,4 = vertical + if ( r_showLines.GetInteger() == 1 || r_showLines.GetInteger() == 3 ) { + for ( int i = start ; i < tr.GetHeight() ; i+=2 ) { + glScissor( 0, i, tr.GetWidth(), 1 ); + glClear( GL_COLOR_BUFFER_BIT ); + } + } else { + for ( int i = start ; i < tr.GetWidth() ; i+=2 ) { + glScissor( i, 0, 1, tr.GetHeight() ); + glClear( GL_COLOR_BUFFER_BIT ); + } + } +} + + +/* +================ +RB_TestGamma +================ +*/ +#define G_WIDTH 512 +#define G_HEIGHT 512 +#define BAR_HEIGHT 64 + +void RB_TestGamma() { + byte image[G_HEIGHT][G_WIDTH][4]; + int i, j; + int c, comp; + int v, dither; + int mask, y; + + if ( r_testGamma.GetInteger() <= 0 ) { + return; + } + + v = r_testGamma.GetInteger(); + if ( v <= 1 || v >= 196 ) { + v = 128; + } + + memset( image, 0, sizeof( image ) ); + + for ( mask = 0; mask < 8; mask++ ) { + y = mask * BAR_HEIGHT; + for ( c = 0; c < 4; c++ ) { + v = c * 64 + 32; + // solid color + for ( i = 0; i < BAR_HEIGHT/2; i++ ) { + for ( j = 0; j < G_WIDTH/4; j++ ) { + for ( comp = 0; comp < 3; comp++ ) { + if ( mask & ( 1 << comp ) ) { + image[y+i][c*G_WIDTH/4+j][comp] = v; + } + } + } + // dithered color + for ( j = 0; j < G_WIDTH/4; j++ ) { + if ( ( i ^ j ) & 1 ) { + dither = c * 64; + } else { + dither = c * 64 + 63; + } + for ( comp = 0; comp < 3; comp++ ) { + if ( mask & ( 1 << comp ) ) { + image[y+BAR_HEIGHT/2+i][c*G_WIDTH/4+j][comp] = dither; + } + } + } + } + } + } + + // draw geometrically increasing steps in the bottom row + y = 0 * BAR_HEIGHT; + float scale = 1; + for ( c = 0; c < 4; c++ ) { + v = (int)(64 * scale); + if ( v < 0 ) { + v = 0; + } else if ( v > 255 ) { + v = 255; + } + scale = scale * 1.5; + for ( i = 0; i < BAR_HEIGHT; i++ ) { + for ( j = 0; j < G_WIDTH/4; j++ ) { + image[y+i][c*G_WIDTH/4+j][0] = v; + image[y+i][c*G_WIDTH/4+j][1] = v; + image[y+i][c*G_WIDTH/4+j][2] = v; + } + } + } + + qglLoadIdentity(); + + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + GL_Color( 1, 1, 1 ); + qglPushMatrix(); + qglLoadIdentity(); + qglDisable( GL_TEXTURE_2D ); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0.01f, 0.01f ); + qglDrawPixels( G_WIDTH, G_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, image ); + qglPopMatrix(); + qglEnable( GL_TEXTURE_2D ); + qglMatrixMode( GL_MODELVIEW ); +} + + +/* +================== +RB_TestGammaBias +================== +*/ +static void RB_TestGammaBias() { + byte image[G_HEIGHT][G_WIDTH][4]; + + if ( r_testGammaBias.GetInteger() <= 0 ) { + return; + } + + int y = 0; + for ( int bias = -40; bias < 40; bias+=10, y += BAR_HEIGHT ) { + float scale = 1; + for ( int c = 0; c < 4; c++ ) { + int v = (int)(64 * scale + bias); + scale = scale * 1.5; + if ( v < 0 ) { + v = 0; + } else if ( v > 255 ) { + v = 255; + } + for ( int i = 0; i < BAR_HEIGHT; i++ ) { + for ( int j = 0; j < G_WIDTH/4; j++ ) { + image[y+i][c*G_WIDTH/4+j][0] = v; + image[y+i][c*G_WIDTH/4+j][1] = v; + image[y+i][c*G_WIDTH/4+j][2] = v; + } + } + } + } + + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + GL_Color( 1, 1, 1 ); + qglPushMatrix(); + qglLoadIdentity(); + qglDisable( GL_TEXTURE_2D ); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0.01f, 0.01f ); + qglDrawPixels( G_WIDTH, G_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, image ); + qglPopMatrix(); + qglEnable( GL_TEXTURE_2D ); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +================ +RB_TestImage + +Display a single image over most of the screen +================ +*/ +void RB_TestImage() { + idImage *image = NULL; + idImage *imageCr = NULL; + idImage *imageCb = NULL; + int max; + float w, h; + + image = tr.testImage; + if ( !image ) { + return; + } + + if ( tr.testVideo ) { + cinData_t cin; + + cin = tr.testVideo->ImageForTime( backEnd.viewDef->renderView.time[1] - tr.testVideoStartTime ); + if ( cin.imageY != NULL ) { + image = cin.imageY; + imageCr = cin.imageCr; + imageCb = cin.imageCb; + } else { + tr.testImage = NULL; + return; + } + w = 0.25; + h = 0.25; + } else { + max = image->GetUploadWidth() > image->GetUploadHeight() ? image->GetUploadWidth() : image->GetUploadHeight(); + + w = 0.25 * image->GetUploadWidth() / max; + h = 0.25 * image->GetUploadHeight() / max; + + w *= (float)renderSystem->GetHeight() / renderSystem->GetWidth(); + } + + // Set State + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + // Set Parms + float texS[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float texT[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_S, texS ); + renderProgManager.SetRenderParm( RENDERPARM_TEXTUREMATRIX_T, texT ); + + float texGenEnabled[4] = { 0, 0, 0, 0 }; + renderProgManager.SetRenderParm( RENDERPARM_TEXGEN_0_ENABLED, texGenEnabled ); + + // not really necessary but just for clarity + const float screenWidth = 1.0f; + const float screenHeight = 1.0f; + const float halfScreenWidth = screenWidth * 0.5f; + const float halfScreenHeight = screenHeight * 0.5f; + + float scale[16] = { 0 }; + scale[0] = w; // scale + scale[5] = h; // scale + scale[12] = halfScreenWidth - ( halfScreenWidth * w ); // translate + scale[13] = halfScreenHeight - ( halfScreenHeight * h ); // translate + scale[10] = 1.0f; + scale[15] = 1.0f; + + float ortho[16] = { 0 }; + ortho[0] = 2.0f / screenWidth; + ortho[5] = -2.0f / screenHeight; + ortho[10] = -2.0f; + ortho[12] = -1.0f; + ortho[13] = 1.0f; + ortho[14] = -1.0f; + ortho[15] = 1.0f; + + float finalOrtho[16]; + R_MatrixMultiply( scale, ortho, finalOrtho ); + + float projMatrixTranspose[16]; + R_MatrixTranspose( finalOrtho, projMatrixTranspose ); + renderProgManager.SetRenderParms( RENDERPARM_MVPMATRIX_X, projMatrixTranspose, 4 ); + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( finalOrtho ); + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); + + // Set Color + GL_Color( 1, 1, 1, 1 ); + + // Bind the Texture + if ( ( imageCr != NULL ) && ( imageCb != NULL ) ) { + GL_SelectTexture( 0 ); + image->Bind(); + GL_SelectTexture( 1 ); + imageCr->Bind(); + GL_SelectTexture( 2 ); + imageCb->Bind(); + renderProgManager.BindShader_Bink(); + } else { + GL_SelectTexture( 0 ); + image->Bind(); + // Set Shader + renderProgManager.BindShader_Texture(); + } + + // Draw! + RB_DrawElementsWithCounters( &backEnd.testImageSurface ); +} + +/* +================= +RB_DrawExpandedTriangles +================= +*/ +void RB_DrawExpandedTriangles( const srfTriangles_t *tri, const float radius, const idVec3 &vieworg ) { + int i, j, k; + idVec3 dir[6], normal, point; + + for ( i = 0; i < tri->numIndexes; i += 3 ) { + + idVec3 p[3] = { tri->verts[ tri->indexes[ i + 0 ] ].xyz, tri->verts[ tri->indexes[ i + 1 ] ].xyz, tri->verts[ tri->indexes[ i + 2 ] ].xyz }; + + dir[0] = p[0] - p[1]; + dir[1] = p[1] - p[2]; + dir[2] = p[2] - p[0]; + + normal = dir[0].Cross( dir[1] ); + + if ( normal * p[0] < normal * vieworg ) { + continue; + } + + dir[0] = normal.Cross( dir[0] ); + dir[1] = normal.Cross( dir[1] ); + dir[2] = normal.Cross( dir[2] ); + + dir[0].Normalize(); + dir[1].Normalize(); + dir[2].Normalize(); + + qglBegin( GL_LINE_LOOP ); + + for ( j = 0; j < 3; j++ ) { + k = ( j + 1 ) % 3; + + dir[4] = ( dir[j] + dir[k] ) * 0.5f; + dir[4].Normalize(); + + dir[3] = ( dir[j] + dir[4] ) * 0.5f; + dir[3].Normalize(); + + dir[5] = ( dir[4] + dir[k] ) * 0.5f; + dir[5].Normalize(); + + point = p[k] + dir[j] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[3] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[4] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[5] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[k] * radius; + qglVertex3f( point[0], point[1], point[2] ); + } + + qglEnd(); + } +} + +/* +================ +RB_ShowTrace + +Debug visualization + +FIXME: not thread safe! +================ +*/ +void RB_ShowTrace( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i; + const srfTriangles_t *tri; + const drawSurf_t *surf; + idVec3 start, end; + idVec3 localStart, localEnd; + localTrace_t hit; + float radius; + + if ( r_showTrace.GetInteger() == 0 ) { + return; + } + + if ( r_showTrace.GetInteger() == 2 ) { + radius = 5.0f; + } else { + radius = 0.0f; + } + + // determine the points of the trace + start = backEnd.viewDef->renderView.vieworg; + end = start + 4000 * backEnd.viewDef->renderView.viewaxis[0]; + + // check and draw the surfaces + globalImages->whiteImage->Bind(); + + // find how many are ambient + for ( i = 0; i < numDrawSurfs; i++ ) { + surf = drawSurfs[i]; + tri = surf->frontEndGeo; + + if ( tri == NULL || tri->verts == NULL ) { + continue; + } + + // transform the points into local space + R_GlobalPointToLocal( surf->space->modelMatrix, start, localStart ); + R_GlobalPointToLocal( surf->space->modelMatrix, end, localEnd ); + + // check the bounding box + if ( !tri->bounds.Expand( radius ).LineIntersection( localStart, localEnd ) ) { + continue; + } + + qglLoadMatrixf( surf->space->modelViewMatrix ); + + // highlight the surface + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + GL_Color( 1, 0, 0, 0.25 ); + RB_DrawElementsWithCounters( surf ); + + // draw the bounding box + GL_State( GLS_DEPTHFUNC_ALWAYS ); + + GL_Color( 1, 1, 1, 1 ); + RB_DrawBounds( tri->bounds ); + + if ( radius != 0.0f ) { + // draw the expanded triangles + GL_Color( 0.5f, 0.5f, 1.0f, 1.0f ); + RB_DrawExpandedTriangles( tri, radius, localStart ); + } + + // check the exact surfaces + hit = R_LocalTrace( localStart, localEnd, radius, tri ); + if ( hit.fraction < 1.0 ) { + GL_Color( 1, 1, 1, 1 ); + RB_DrawBounds( idBounds( hit.point ).Expand( 1 ) ); + } + } +} + +/* +================= +RB_RenderDebugTools +================= +*/ +void RB_RenderDebugTools( drawSurf_t **drawSurfs, int numDrawSurfs ) { + // don't do much if this was a 2D rendering + if ( !backEnd.viewDef->viewEntitys ) { + RB_TestImage(); + RB_ShowLines(); + return; + } + + renderLog.OpenMainBlock( MRB_DRAW_DEBUG_TOOLS ); + RENDERLOG_PRINTF( "---------- RB_RenderDebugTools ----------\n" ); + + GL_State( GLS_DEFAULT ); + + GL_Scissor( backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, + backEnd.viewDef->scissor.x2 + 1 - backEnd.viewDef->scissor.x1, + backEnd.viewDef->scissor.y2 + 1 - backEnd.viewDef->scissor.y1 ); + backEnd.currentScissor = backEnd.viewDef->scissor; + + RB_ShowLightCount(); + RB_ShowTexturePolarity( drawSurfs, numDrawSurfs ); + RB_ShowTangentSpace( drawSurfs, numDrawSurfs ); + RB_ShowVertexColor( drawSurfs, numDrawSurfs ); + RB_ShowTris( drawSurfs, numDrawSurfs ); + RB_ShowUnsmoothedTangents( drawSurfs, numDrawSurfs ); + RB_ShowSurfaceInfo( drawSurfs, numDrawSurfs ); + RB_ShowEdges( drawSurfs, numDrawSurfs ); + RB_ShowNormals( drawSurfs, numDrawSurfs ); + RB_ShowViewEntitys( backEnd.viewDef->viewEntitys ); + RB_ShowLights(); + RB_ShowTextureVectors( drawSurfs, numDrawSurfs ); + RB_ShowDominantTris( drawSurfs, numDrawSurfs ); + if ( r_testGamma.GetInteger() > 0 ) { // test here so stack check isn't so damn slow on debug builds + RB_TestGamma(); + } + if ( r_testGammaBias.GetInteger() > 0 ) { + RB_TestGammaBias(); + } + RB_TestImage(); + RB_ShowPortals(); + RB_ShowSilhouette(); + RB_ShowDepthBuffer(); + RB_ShowIntensity(); + RB_ShowCenterOfProjection(); + RB_ShowLines(); + RB_ShowDebugLines(); + RB_ShowDebugText(); + RB_ShowDebugPolygons(); + RB_ShowTrace( drawSurfs, numDrawSurfs ); + + renderLog.CloseMainBlock(); +} + +/* +================= +RB_ShutdownDebugTools +================= +*/ +void RB_ShutdownDebugTools() { + for ( int i = 0; i < MAX_DEBUG_POLYGONS; i++ ) { + rb_debugPolygons[i].winding.Clear(); + } +} diff --git a/neo/renderer/tr_frontend_addlights.cpp b/neo/renderer/tr_frontend_addlights.cpp new file mode 100644 index 00000000..8ca016a2 --- /dev/null +++ b/neo/renderer/tr_frontend_addlights.cpp @@ -0,0 +1,608 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +extern idCVar r_useAreasConnectedForShadowCulling; +extern idCVar r_useParallelAddShadows; +extern idCVar r_forceShadowCaps; +extern idCVar r_useShadowPreciseInsideTest; + +idCVar r_useAreasConnectedForShadowCulling( "r_useAreasConnectedForShadowCulling", "2", CVAR_RENDERER | CVAR_INTEGER, "cull entities cut off by doors" ); +idCVar r_useParallelAddLights( "r_useParallelAddLights", "1", CVAR_RENDERER | CVAR_BOOL, "aadd all lights in parallel with jobs" ); + +/* +============================ +R_ShadowBounds + +Even though the extruded shadows are drawn projected to infinity, their effects are limited +to a fraction of the light's volume. An extruded box would require 12 faces to specify and +be a lot of trouble, but an axial bounding box is quick and easy to determine. + +If the light is completely contained in the view, there is no value in trying to cull the +shadows, as they will all pass. + +Pure function. +============================ +*/ +void R_ShadowBounds( const idBounds & modelBounds, const idBounds & lightBounds, const idVec3 & lightOrigin, idBounds & shadowBounds ) { + for ( int i = 0; i < 3; i++ ) { + shadowBounds[0][i] = __fsels( modelBounds[0][i] - lightOrigin[i], modelBounds[0][i], lightBounds[0][i] ); + shadowBounds[1][i] = __fsels( lightOrigin[i] - modelBounds[1][i], modelBounds[1][i], lightBounds[1][i] ); + } +} + +/* +============================ +idRenderEntityLocal::IsDirectlyVisible() +============================ +*/ +bool idRenderEntityLocal::IsDirectlyVisible() const { + if ( viewCount != tr.viewCount ) { + return false; + } + if ( viewEntity->scissorRect.IsEmpty() ) { + // a viewEntity was created for shadow generation, but the + // model global reference bounds isn't directly visible + return false; + } + return true; +} + +/* +=================== +R_AddSingleLight + +May be run in parallel. + +Sets vLight->removeFromList to true if the light should be removed from the list. +Builds a chain of entities that need to be added for shadows only off vLight->shadowOnlyViewEntities. +Allocates and fills in vLight->entityInteractionState. + +Calc the light shader values, removing any light from the viewLight list +if it is determined to not have any visible effect due to being flashed off or turned off. + +Add any precomputed shadow volumes. +=================== +*/ +static void R_AddSingleLight( viewLight_t * vLight ) { + // until proven otherwise + vLight->removeFromList = true; + vLight->shadowOnlyViewEntities = NULL; + vLight->preLightShadowVolumes = NULL; + + // globals we really should pass in... + const viewDef_t * viewDef = tr.viewDef; + + const idRenderLightLocal *light = vLight->lightDef; + const idMaterial * lightShader = light->lightShader; + if ( lightShader == NULL ) { + common->Error( "R_AddSingleLight: NULL lightShader" ); + return; + } + + SCOPED_PROFILE_EVENT( lightShader->GetName() ); + + // see if we are suppressing the light in this view + if ( !r_skipSuppress.GetBool() ) { + if ( light->parms.suppressLightInViewID && light->parms.suppressLightInViewID == viewDef->renderView.viewID ) { + return; + } + if ( light->parms.allowLightInViewID && light->parms.allowLightInViewID != viewDef->renderView.viewID ) { + return; + } + } + + // evaluate the light shader registers + float * lightRegs = (float *)R_FrameAlloc( lightShader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER ); + lightShader->EvaluateRegisters( lightRegs, light->parms.shaderParms, viewDef->renderView.shaderParms, + tr.viewDef->renderView.time[0] * 0.001f, light->parms.referenceSound ); + + // if this is a purely additive light and no stage in the light shader evaluates + // to a positive light value, we can completely skip the light + if ( !lightShader->IsFogLight() && !lightShader->IsBlendLight() ) { + int lightStageNum; + for ( lightStageNum = 0; lightStageNum < lightShader->GetNumStages(); lightStageNum++ ) { + const shaderStage_t *lightStage = lightShader->GetStage( lightStageNum ); + + // ignore stages that fail the condition + if ( !lightRegs[ lightStage->conditionRegister ] ) { + continue; + } + + const int * registers = lightStage->color.registers; + + // snap tiny values to zero + if ( lightRegs[ registers[0] ] < 0.001f ) { + lightRegs[ registers[0] ] = 0.0f; + } + if ( lightRegs[ registers[1] ] < 0.001f ) { + lightRegs[ registers[1] ] = 0.0f; + } + if ( lightRegs[ registers[2] ] < 0.001f ) { + lightRegs[ registers[2] ] = 0.0f; + } + + if ( lightRegs[ registers[0] ] > 0.0f || + lightRegs[ registers[1] ] > 0.0f || + lightRegs[ registers[2] ] > 0.0f ) { + break; + } + } + if ( lightStageNum == lightShader->GetNumStages() ) { + // we went through all the stages and didn't find one that adds anything + // remove the light from the viewLights list, and change its frame marker + // so interaction generation doesn't think the light is visible and + // create a shadow for it + return; + } + } + + //-------------------------------------------- + // copy data used by backend + //-------------------------------------------- + vLight->globalLightOrigin = light->globalLightOrigin; + vLight->lightProject[0] = light->lightProject[0]; + vLight->lightProject[1] = light->lightProject[1]; + vLight->lightProject[2] = light->lightProject[2]; + vLight->lightProject[3] = light->lightProject[3]; + + // the fog plane is the light far clip plane + idPlane fogPlane( light->baseLightProject[2][0] - light->baseLightProject[3][0], + light->baseLightProject[2][1] - light->baseLightProject[3][1], + light->baseLightProject[2][2] - light->baseLightProject[3][2], + light->baseLightProject[2][3] - light->baseLightProject[3][3] ); + const float planeScale = idMath::InvSqrt( fogPlane.Normal().LengthSqr() ); + vLight->fogPlane[0] = fogPlane[0] * planeScale; + vLight->fogPlane[1] = fogPlane[1] * planeScale; + vLight->fogPlane[2] = fogPlane[2] * planeScale; + vLight->fogPlane[3] = fogPlane[3] * planeScale; + + // copy the matrix for deforming the 'zeroOneCubeModel' to exactly cover the light volume in world space + vLight->inverseBaseLightProject = light->inverseBaseLightProject; + + vLight->falloffImage = light->falloffImage; + vLight->lightShader = light->lightShader; + vLight->shaderRegisters = lightRegs; + + if ( r_useLightScissors.GetInteger() != 0 ) { + // Calculate the matrix that projects the zero-to-one cube to exactly cover the + // light frustum in clip space. + idRenderMatrix invProjectMVPMatrix; + idRenderMatrix::Multiply( viewDef->worldSpace.mvp, light->inverseBaseLightProject, invProjectMVPMatrix ); + + // Calculate the projected bounds, either not clipped at all, near clipped, or fully clipped. + idBounds projected; + if ( r_useLightScissors.GetInteger() == 1 ) { + idRenderMatrix::ProjectedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube ); + } else if ( r_useLightScissors.GetInteger() == 2 ) { + idRenderMatrix::ProjectedNearClippedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube ); + } else { + idRenderMatrix::ProjectedFullyClippedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube ); + } + + if ( projected[0][2] >= projected[1][2] ) { + // the light was culled to the view frustum + return; + } + + float screenWidth = (float)viewDef->viewport.x2 - (float)viewDef->viewport.x1; + float screenHeight = (float)viewDef->viewport.y2 - (float)viewDef->viewport.y1; + + idScreenRect lightScissorRect; + lightScissorRect.x1 = idMath::Ftoi( projected[0][0] * screenWidth ); + lightScissorRect.x2 = idMath::Ftoi( projected[1][0] * screenWidth ); + lightScissorRect.y1 = idMath::Ftoi( projected[0][1] * screenHeight ); + lightScissorRect.y2 = idMath::Ftoi( projected[1][1] * screenHeight ); + lightScissorRect.Expand(); + + vLight->scissorRect.Intersect( lightScissorRect ); + vLight->scissorRect.zmin = projected[0][2]; + vLight->scissorRect.zmax = projected[1][2]; + } + + // this one stays on the list + vLight->removeFromList = false; + + //-------------------------------------------- + // create interactions with all entities the light may touch, and add viewEntities + // that may cast shadows, even if they aren't directly visible. Any real work + // will be deferred until we walk through the viewEntities + //-------------------------------------------- + const int renderViewID = viewDef->renderView.viewID; + + // this bool array will be set true whenever the entity will visibly interact with the light + vLight->entityInteractionState = (byte *)R_ClearedFrameAlloc( light->world->entityDefs.Num() * sizeof( vLight->entityInteractionState[0] ), FRAME_ALLOC_INTERACTION_STATE ); + + const bool lightCastsShadows = light->LightCastsShadows(); + idInteraction * * const interactionTableRow = light->world->interactionTable + light->index * light->world->interactionTableWidth; + + for ( areaReference_t * lref = light->references; lref != NULL; lref = lref->ownerNext ) { + portalArea_t *area = lref->area; + + // some lights have their center of projection outside the world, but otherwise + // we want to ignore areas that are not connected to the light center due to a closed door + if ( light->areaNum != -1 && r_useAreasConnectedForShadowCulling.GetInteger() == 2 ) { + if ( !light->world->AreasAreConnected( light->areaNum, area->areaNum, PS_BLOCK_VIEW ) ) { + // can't possibly be seen or shadowed + continue; + } + } + + // check all the models in this area + for ( areaReference_t * eref = area->entityRefs.areaNext; eref != &area->entityRefs; eref = eref->areaNext ) { + idRenderEntityLocal * edef = eref->entity; + + if ( vLight->entityInteractionState[ edef->index ] != viewLight_t::INTERACTION_UNCHECKED ) { + continue; + } + // until proven otherwise + vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_NO; + + // The table is updated at interaction::AllocAndLink() and interaction::UnlinkAndFree() + const idInteraction * inter = interactionTableRow[ edef->index ]; + + const renderEntity_t & eParms = edef->parms; + const idRenderModel * eModel = eParms.hModel; + + // a large fraction of static entity / light pairs will still have no interactions even though + // they are both present in the same area(s) + if ( eModel != NULL && !eModel->IsDynamicModel() && inter == INTERACTION_EMPTY ) { + // the interaction was statically checked, and it didn't generate any surfaces, + // so there is no need to force the entity onto the view list if it isn't + // already there + continue; + } + + // We don't want the lights on weapons to illuminate anything else. + // There are two assumptions here -- that allowLightInViewID is only + // used for weapon lights, and that all weapons will have weaponDepthHack. + // A more general solution would be to have an allowLightOnEntityID field. + // HACK: the armor-mounted flashlight is a private spot light, which is probably + // wrong -- you would expect to see them in multiplayer. + if ( light->parms.allowLightInViewID && light->parms.pointLight && !eParms.weaponDepthHack ) { + continue; + } + + // non-shadow casting entities don't need to be added if they aren't + // directly visible + if ( ( eParms.noShadow || ( eModel && !eModel->ModelHasShadowCastingSurfaces() ) ) && !edef->IsDirectlyVisible() ) { + continue; + } + + // if the model doesn't accept lighting or cast shadows, it doesn't need to be added + if ( eModel && !eModel->ModelHasInteractingSurfaces() && !eModel->ModelHasShadowCastingSurfaces() ) { + continue; + } + + // no interaction present, so either the light or entity has moved + // assert( lightHasMoved || edef->entityHasMoved ); + if ( inter == NULL ) { + // some big outdoor meshes are flagged to not create any dynamic interactions + // when the level designer knows that nearby moving lights shouldn't actually hit them + if ( eParms.noDynamicInteractions ) { + continue; + } + + // do a check of the entity reference bounds against the light frustum to see if they can't + // possibly interact, despite sharing one or more world areas + if ( R_CullModelBoundsToLight( light, edef->localReferenceBounds, edef->modelRenderMatrix ) ) { + continue; + } + } + + // we now know that the entity and light do overlap + + if ( edef->IsDirectlyVisible() ) { + // entity is directly visible, so the interaction is definitely needed + vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_YES; + continue; + } + + // the entity is not directly visible, but if we can tell that it may cast + // shadows onto visible surfaces, we must make a viewEntity for it + if ( !lightCastsShadows ) { + // surfaces are never shadowed in this light + continue; + } + // if we are suppressing its shadow in this view (player shadows, etc), skip + if ( !r_skipSuppress.GetBool() ) { + if ( eParms.suppressShadowInViewID && eParms.suppressShadowInViewID == renderViewID ) { + continue; + } + if ( eParms.suppressShadowInLightID && eParms.suppressShadowInLightID == light->parms.lightId ) { + continue; + } + } + + // should we use the shadow bounds from pre-calculated interactions? + idBounds shadowBounds; + R_ShadowBounds( edef->globalReferenceBounds, light->globalLightBounds, light->globalLightOrigin, shadowBounds ); + + // this test is pointless if we knew the light was completely contained + // in the view frustum, but the entity would also be directly visible in most + // of those cases. + + // this doesn't say that the shadow can't effect anything, only that it can't + // effect anything in the view, so we shouldn't set up a view entity + if ( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, shadowBounds ) ) { + continue; + } + + // debug tool to allow viewing of only one entity at a time + if ( r_singleEntity.GetInteger() >= 0 && r_singleEntity.GetInteger() != edef->index ) { + continue; + } + + // we do need it for shadows + vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_YES; + + // we will need to create a viewEntity_t for it in the serial code section + shadowOnlyEntity_t * shadEnt = (shadowOnlyEntity_t *)R_FrameAlloc( sizeof( shadowOnlyEntity_t ), FRAME_ALLOC_SHADOW_ONLY_ENTITY ); + shadEnt->next = vLight->shadowOnlyViewEntities; + shadEnt->edef = edef; + vLight->shadowOnlyViewEntities = shadEnt; + } + } + + //-------------------------------------------- + // add the prelight shadows for the static world geometry + //-------------------------------------------- + if ( light->parms.prelightModel != NULL ) { + srfTriangles_t * tri = light->parms.prelightModel->Surface( 0 )->geometry; + + // these shadows will have valid bounds, and can be culled normally, + // but they will typically cover most of the light's bounds + if ( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, tri->bounds ) ) { + return; + } + + // prelight models should always have static data that never gets purged + assert( vertexCache.CacheIsCurrent( tri->shadowCache ) ); + assert( vertexCache.CacheIsCurrent( tri->indexCache ) ); + + drawSurf_t * shadowDrawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *shadowDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + + shadowDrawSurf->frontEndGeo = tri; + shadowDrawSurf->ambientCache = 0; + shadowDrawSurf->indexCache = tri->indexCache; + shadowDrawSurf->shadowCache = tri->shadowCache; + shadowDrawSurf->jointCache = 0; + shadowDrawSurf->numIndexes = 0; + shadowDrawSurf->space = &viewDef->worldSpace; + shadowDrawSurf->material = NULL; + shadowDrawSurf->extraGLState = 0; + shadowDrawSurf->shaderRegisters = NULL; + shadowDrawSurf->scissorRect = vLight->scissorRect; // default to the light scissor and light depth bounds + shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE; // assume the shadow volume is done in case r_skipPrelightShadows is set + + if ( !r_skipPrelightShadows.GetBool() ) { + preLightShadowVolumeParms_t * shadowParms = (preLightShadowVolumeParms_t *)R_FrameAlloc( sizeof( shadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); + + shadowParms->verts = tri->preLightShadowVertexes; + shadowParms->numVerts = tri->numVerts * 2; + shadowParms->indexes = tri->indexes; + shadowParms->numIndexes = tri->numIndexes; + shadowParms->triangleBounds = tri->bounds; + shadowParms->triangleMVP = viewDef->worldSpace.mvp; + shadowParms->localLightOrigin = vLight->globalLightOrigin; + shadowParms->localViewOrigin = viewDef->renderView.vieworg; + shadowParms->zNear = r_znear.GetFloat(); + shadowParms->lightZMin = vLight->scissorRect.zmin; + shadowParms->lightZMax = vLight->scissorRect.zmax; + shadowParms->forceShadowCaps = r_forceShadowCaps.GetBool(); + shadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool(); + shadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool(); + shadowParms->numShadowIndices = & shadowDrawSurf->numIndexes; + shadowParms->renderZFail = & shadowDrawSurf->renderZFail; + shadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin; + shadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax; + shadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState; + + // the pre-light shadow volume "_prelight_light_3297" in "d3xpdm2" is malformed in that it contains the light origin so the precise inside test always fails + if ( tr.primaryWorld->mapName.IcmpPath( "maps/game/mp/d3xpdm2.map" ) == 0 && idStr::Icmp( light->parms.prelightModel->Name(), "_prelight_light_3297" ) == 0 ) { + shadowParms->useShadowPreciseInsideTest = false; + } + + shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; + + shadowParms->next = vLight->preLightShadowVolumes; + vLight->preLightShadowVolumes = shadowParms; + } + + // actually link it in + shadowDrawSurf->nextOnLight = vLight->globalShadows; + vLight->globalShadows = shadowDrawSurf; + } +} + +REGISTER_PARALLEL_JOB( R_AddSingleLight, "R_AddSingleLight" ); + +/* +================= +R_AddLights +================= +*/ +void R_AddLights() { + SCOPED_PROFILE_EVENT( "R_AddLights" ); + + //------------------------------------------------- + // check each light individually, possibly in parallel + //------------------------------------------------- + + if ( r_useParallelAddLights.GetBool() ) { + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + tr.frontEndJobList->AddJob( (jobRun_t)R_AddSingleLight, vLight ); + } + tr.frontEndJobList->Submit(); + tr.frontEndJobList->Wait(); + } else { + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + R_AddSingleLight( vLight ); + } + } + + //------------------------------------------------- + // cull lights from the list if they turned out to not be needed + //------------------------------------------------- + + tr.pc.c_viewLights = 0; + viewLight_t **ptr = &tr.viewDef->viewLights; + while ( *ptr != NULL ) { + viewLight_t *vLight = *ptr; + + if ( vLight->removeFromList ) { + vLight->lightDef->viewCount = -1; // this probably doesn't matter with current code + *ptr = vLight->next; + continue; + } + + ptr = &vLight->next; + + // serial work + tr.pc.c_viewLights++; + + for ( shadowOnlyEntity_t * shadEnt = vLight->shadowOnlyViewEntities; shadEnt != NULL; shadEnt = shadEnt->next ) { + // this will add it to the viewEntities list, but with an empty scissor rect + R_SetEntityDefViewEntity( shadEnt->edef ); + } + + if ( r_showLightScissors.GetBool() ) { + R_ShowColoredScreenRect( vLight->scissorRect, vLight->lightDef->index ); + } + } + + //------------------------------------------------- + // Add jobs to setup pre-light shadow volumes. + //------------------------------------------------- + + if ( r_useParallelAddShadows.GetInteger() == 1 ) { + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + for ( preLightShadowVolumeParms_t * shadowParms = vLight->preLightShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { + tr.frontEndJobList->AddJob( (jobRun_t)PreLightShadowVolumeJob, shadowParms ); + } + vLight->preLightShadowVolumes = NULL; + } + } else { + int start = Sys_Microseconds(); + + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + for ( preLightShadowVolumeParms_t * shadowParms = vLight->preLightShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { + PreLightShadowVolumeJob( shadowParms ); + } + vLight->preLightShadowVolumes = NULL; + } + + int end = Sys_Microseconds(); + backEnd.pc.shadowMicroSec += end - start; + } +} + +/* +===================== +R_OptimizeViewLightsList +===================== +*/ +void R_OptimizeViewLightsList() { + // go through each visible light + int numViewLights = 0; + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + numViewLights++; + // If the light didn't have any lit surfaces visible, there is no need to + // draw any of the shadows. We still keep the vLight for debugging draws. + if ( !vLight->localInteractions && !vLight->globalInteractions && !vLight->translucentInteractions ) { + vLight->localShadows = NULL; + vLight->globalShadows = NULL; + } + } + + if ( r_useShadowSurfaceScissor.GetBool() ) { + // shrink the light scissor rect to only intersect the surfaces that will actually be drawn. + // This doesn't seem to actually help, perhaps because the surface scissor + // rects aren't actually the surface, but only the portal clippings. + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight; vLight = vLight->next ) { + drawSurf_t * surf; + idScreenRect surfRect; + + if ( !vLight->lightShader->LightCastsShadows() ) { + continue; + } + + surfRect.Clear(); + + for ( surf = vLight->globalInteractions; surf != NULL; surf = surf->nextOnLight ) { + surfRect.Union( surf->scissorRect ); + } + for ( surf = vLight->localShadows; surf != NULL; surf = surf->nextOnLight ) { + surf->scissorRect.Intersect( surfRect ); + } + + for ( surf = vLight->localInteractions; surf != NULL; surf = surf->nextOnLight ) { + surfRect.Union( surf->scissorRect ); + } + for ( surf = vLight->globalShadows; surf != NULL; surf = surf->nextOnLight ) { + surf->scissorRect.Intersect( surfRect ); + } + + for ( surf = vLight->translucentInteractions; surf != NULL; surf = surf->nextOnLight ) { + surfRect.Union( surf->scissorRect ); + } + + vLight->scissorRect.Intersect( surfRect ); + } + } + + // sort the viewLights list so the largest lights come first, which will reduce + // the chance of GPU pipeline bubbles + struct sortLight_t { + viewLight_t * vLight; + int screenArea; + static int sort( const void * a, const void * b ) { + return ((sortLight_t *)a)->screenArea - ((sortLight_t *)b)->screenArea; + } + }; + sortLight_t * sortLights = (sortLight_t *)_alloca( sizeof( sortLight_t ) * numViewLights ); + int numSortLightsFilled = 0; + for ( viewLight_t * vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + sortLights[ numSortLightsFilled ].vLight = vLight; + sortLights[ numSortLightsFilled ].screenArea = vLight->scissorRect.GetArea(); + numSortLightsFilled++; + } + + qsort( sortLights, numSortLightsFilled, sizeof( sortLights[0] ), sortLight_t::sort ); + + // rebuild the linked list in order + tr.viewDef->viewLights = NULL; + for ( int i = 0; i < numSortLightsFilled; i++ ) { + sortLights[i].vLight->next = tr.viewDef->viewLights; + tr.viewDef->viewLights = sortLights[i].vLight; + } +} diff --git a/neo/renderer/tr_frontend_addmodels.cpp b/neo/renderer/tr_frontend_addmodels.cpp new file mode 100644 index 00000000..ee6dd8d5 --- /dev/null +++ b/neo/renderer/tr_frontend_addmodels.cpp @@ -0,0 +1,1081 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +idCVar r_skipStaticShadows( "r_skipStaticShadows", "0", CVAR_RENDERER | CVAR_BOOL, "skip static shadows" ); +idCVar r_skipDynamicShadows( "r_skipDynamicShadows", "0", CVAR_RENDERER | CVAR_BOOL, "skip dynamic shadows" ); +idCVar r_useParallelAddModels( "r_useParallelAddModels", "1", CVAR_RENDERER | CVAR_BOOL, "add all models in parallel with jobs" ); +idCVar r_useParallelAddShadows( "r_useParallelAddShadows", "1", CVAR_RENDERER | CVAR_INTEGER, "0 = off, 1 = threaded", 0, 1 ); +idCVar r_useShadowPreciseInsideTest( "r_useShadowPreciseInsideTest", "1", CVAR_RENDERER | CVAR_BOOL, "use a precise and more expensive test to determine whether the view is inside a shadow volume" ); +idCVar r_cullDynamicShadowTriangles( "r_cullDynamicShadowTriangles", "1", CVAR_RENDERER | CVAR_BOOL, "cull occluder triangles that are outside the light frustum so they do not contribute to the dynamic shadow volume" ); +idCVar r_cullDynamicLightTriangles( "r_cullDynamicLightTriangles", "1", CVAR_RENDERER | CVAR_BOOL, "cull surface triangles that are outside the light frustum so they do not get rendered for interactions" ); +idCVar r_forceShadowCaps( "r_forceShadowCaps", "0", CVAR_RENDERER | CVAR_BOOL, "0 = skip rendering shadow caps if view is outside shadow volume, 1 = always render shadow caps" ); + +static const float CHECK_BOUNDS_EPSILON = 1.0f; + +/* +================== +R_SortViewEntities +================== +*/ +viewEntity_t * R_SortViewEntities( viewEntity_t * vEntities ) { + SCOPED_PROFILE_EVENT( "R_SortViewEntities" ); + + // We want to avoid having a single AddModel for something complex be + // the last thing processed and hurt the parallel occupancy, so + // sort dynamic models first, _area models second, then everything else. + viewEntity_t * dynamics = NULL; + viewEntity_t * areas = NULL; + viewEntity_t * others = NULL; + for ( viewEntity_t * vEntity = vEntities; vEntity != NULL; ) { + viewEntity_t * next = vEntity->next; + const idRenderModel * model = vEntity->entityDef->parms.hModel; + if ( model->IsDynamicModel() != DM_STATIC ) { + vEntity->next = dynamics; + dynamics = vEntity; + } else if ( model->IsStaticWorldModel() ) { + vEntity->next = areas; + areas = vEntity; + } else { + vEntity->next = others; + others = vEntity; + } + vEntity = next; + } + + // concatenate the lists + viewEntity_t * all = others; + + for ( viewEntity_t * vEntity = areas; vEntity != NULL; ) { + viewEntity_t * next = vEntity->next; + vEntity->next = all; + all = vEntity; + vEntity = next; + } + + for ( viewEntity_t * vEntity = dynamics; vEntity != NULL; ) { + viewEntity_t * next = vEntity->next; + vEntity->next = all; + all = vEntity; + vEntity = next; + } + + return all; +} + +/* +================== +R_ClearEntityDefDynamicModel + +If we know the reference bounds stays the same, we +only need to do this on entity update, not the full +R_FreeEntityDefDerivedData +================== +*/ +void R_ClearEntityDefDynamicModel( idRenderEntityLocal *def ) { + // free all the interaction surfaces + for ( idInteraction *inter = def->firstInteraction; inter != NULL && !inter->IsEmpty(); inter = inter->entityNext ) { + inter->FreeSurfaces(); + } + + // clear the dynamic model if present + if ( def->dynamicModel ) { + // this is copied from cachedDynamicModel, so it doesn't need to be freed + def->dynamicModel = NULL; + } + def->dynamicModelFrameCount = 0; +} + +/* +================== +R_IssueEntityDefCallback +================== +*/ +bool R_IssueEntityDefCallback( idRenderEntityLocal *def ) { + idBounds oldBounds = def->localReferenceBounds; + + def->archived = false; // will need to be written to the demo file + + bool update; + if ( tr.viewDef != NULL ) { + update = def->parms.callback( &def->parms, &tr.viewDef->renderView ); + } else { + update = def->parms.callback( &def->parms, NULL ); + } + tr.pc.c_entityDefCallbacks++; + + if ( def->parms.hModel == NULL ) { + common->Error( "R_IssueEntityDefCallback: dynamic entity callback didn't set model" ); + } + + if ( r_checkBounds.GetBool() ) { + if ( oldBounds[0][0] > def->localReferenceBounds[0][0] + CHECK_BOUNDS_EPSILON || + oldBounds[0][1] > def->localReferenceBounds[0][1] + CHECK_BOUNDS_EPSILON || + oldBounds[0][2] > def->localReferenceBounds[0][2] + CHECK_BOUNDS_EPSILON || + oldBounds[1][0] < def->localReferenceBounds[1][0] - CHECK_BOUNDS_EPSILON || + oldBounds[1][1] < def->localReferenceBounds[1][1] - CHECK_BOUNDS_EPSILON || + oldBounds[1][2] < def->localReferenceBounds[1][2] - CHECK_BOUNDS_EPSILON ) { + common->Printf( "entity %i callback extended reference bounds\n", def->index ); + } + } + + return update; +} + +/* +=================== +R_EntityDefDynamicModel + +This is also called by the game code for idRenderWorldLocal::ModelTrace(), and idRenderWorldLocal::Trace() which is bad for performance... + +Issues a deferred entity callback if necessary. +If the model isn't dynamic, it returns the original. +Returns the cached dynamic model if present, otherwise creates it. +=================== +*/ +idRenderModel *R_EntityDefDynamicModel( idRenderEntityLocal *def ) { + if ( def->dynamicModelFrameCount == tr.frameCount ) { + return def->dynamicModel; + } + + // allow deferred entities to construct themselves + bool callbackUpdate; + if ( def->parms.callback != NULL ) { + SCOPED_PROFILE_EVENT( "R_IssueEntityDefCallback" ); + callbackUpdate = R_IssueEntityDefCallback( def ); + } else { + callbackUpdate = false; + } + + idRenderModel *model = def->parms.hModel; + + if ( model == NULL ) { + common->Error( "R_EntityDefDynamicModel: NULL model" ); + return NULL; + } + + if ( model->IsDynamicModel() == DM_STATIC ) { + def->dynamicModel = NULL; + def->dynamicModelFrameCount = 0; + return model; + } + + // continously animating models (particle systems, etc) will have their snapshot updated every single view + if ( callbackUpdate || ( model->IsDynamicModel() == DM_CONTINUOUS && def->dynamicModelFrameCount != tr.frameCount ) ) { + R_ClearEntityDefDynamicModel( def ); + } + + // if we don't have a snapshot of the dynamic model, generate it now + if ( def->dynamicModel == NULL ) { + + SCOPED_PROFILE_EVENT( "InstantiateDynamicModel" ); + + // instantiate the snapshot of the dynamic model, possibly reusing memory from the cached snapshot + def->cachedDynamicModel = model->InstantiateDynamicModel( &def->parms, tr.viewDef, def->cachedDynamicModel ); + + if ( def->cachedDynamicModel != NULL && r_checkBounds.GetBool() ) { + idBounds b = def->cachedDynamicModel->Bounds(); + if ( b[0][0] < def->localReferenceBounds[0][0] - CHECK_BOUNDS_EPSILON || + b[0][1] < def->localReferenceBounds[0][1] - CHECK_BOUNDS_EPSILON || + b[0][2] < def->localReferenceBounds[0][2] - CHECK_BOUNDS_EPSILON || + b[1][0] > def->localReferenceBounds[1][0] + CHECK_BOUNDS_EPSILON || + b[1][1] > def->localReferenceBounds[1][1] + CHECK_BOUNDS_EPSILON || + b[1][2] > def->localReferenceBounds[1][2] + CHECK_BOUNDS_EPSILON ) { + common->Printf( "entity %i dynamic model exceeded reference bounds\n", def->index ); + } + } + + def->dynamicModel = def->cachedDynamicModel; + def->dynamicModelFrameCount = tr.frameCount; + } + + // set model depth hack value + if ( def->dynamicModel != NULL && model->DepthHack() != 0.0f && tr.viewDef != NULL ) { + idPlane eye, clip; + idVec3 ndc; + R_TransformModelToClip( def->parms.origin, tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, eye, clip ); + R_TransformClipToDevice( clip, ndc ); + def->parms.modelDepthHack = model->DepthHack() * ( 1.0f - ndc.z ); + } else { + def->parms.modelDepthHack = 0.0f; + } + + return def->dynamicModel; +} + +/* +=================== +R_SetupDrawSurfShader +=================== +*/ +void R_SetupDrawSurfShader( drawSurf_t * drawSurf, const idMaterial * shader, const renderEntity_t * renderEntity ) { + drawSurf->material = shader; + drawSurf->sort = shader->GetSort(); + + // process the shader expressions for conditionals / color / texcoords + const float *constRegs = shader->ConstantRegisters(); + if ( likely( constRegs != NULL ) ) { + // shader only uses constant values + drawSurf->shaderRegisters = constRegs; + } else { + // by default evaluate with the entityDef's shader parms + const float * shaderParms = renderEntity->shaderParms; + + // a reference shader will take the calculated stage color value from another shader + // and use that for the parm0-parm3 of the current shader, which allows a stage of + // a light model and light flares to pick up different flashing tables from + // different light shaders + float generatedShaderParms[MAX_ENTITY_SHADER_PARMS]; + if ( unlikely( renderEntity->referenceShader != NULL ) ) { + // evaluate the reference shader to find our shader parms + float refRegs[MAX_EXPRESSION_REGISTERS]; + renderEntity->referenceShader->EvaluateRegisters( refRegs, renderEntity->shaderParms, + tr.viewDef->renderView.shaderParms, + tr.viewDef->renderView.time[renderEntity->timeGroup] * 0.001f, renderEntity->referenceSound ); + + const shaderStage_t * pStage = renderEntity->referenceShader->GetStage( 0 ); + + memcpy( generatedShaderParms, renderEntity->shaderParms, sizeof( generatedShaderParms ) ); + generatedShaderParms[0] = refRegs[ pStage->color.registers[0] ]; + generatedShaderParms[1] = refRegs[ pStage->color.registers[1] ]; + generatedShaderParms[2] = refRegs[ pStage->color.registers[2] ]; + + shaderParms = generatedShaderParms; + } + + // allocte frame memory for the shader register values + float * regs = (float *)R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER ); + drawSurf->shaderRegisters = regs; + + // process the shader expressions for conditionals / color / texcoords + shader->EvaluateRegisters( regs, shaderParms, tr.viewDef->renderView.shaderParms, + tr.viewDef->renderView.time[renderEntity->timeGroup] * 0.001f, renderEntity->referenceSound ); + } +} + +/* +=================== +R_SetupDrawSurfJoints +=================== +*/ +void R_SetupDrawSurfJoints( drawSurf_t * drawSurf, const srfTriangles_t * tri, const idMaterial * shader ) { + if ( tri->staticModelWithJoints == NULL || !r_useGPUSkinning.GetBool() ) { + drawSurf->jointCache = 0; + return; + } + + idRenderModelStatic * model = tri->staticModelWithJoints; + assert( model->jointsInverted != NULL ); + + if ( !vertexCache.CacheIsCurrent( model->jointsInvertedBuffer ) ) { + const int alignment = glConfig.uniformBufferOffsetAlignment; + model->jointsInvertedBuffer = vertexCache.AllocJoint( model->jointsInverted, ALIGN( model->numInvertedJoints * sizeof( idJointMat ), alignment ) ); + } + drawSurf->jointCache = model->jointsInvertedBuffer; +} + +/* +=================== +R_AddSingleModel + +May be run in parallel. + +Here is where dynamic models actually get instantiated, and necessary +interaction surfaces get created. This is all done on a sort-by-model +basis to keep source data in cache (most likely L2) as any interactions +and shadows are generated, since dynamic models will typically be lit by +two or more lights. +=================== +*/ +void R_AddSingleModel( viewEntity_t * vEntity ) { + // we will add all interaction surfs here, to be chained to the lights in later serial code + vEntity->drawSurfs = NULL; + vEntity->staticShadowVolumes = NULL; + vEntity->dynamicShadowVolumes = NULL; + + // globals we really should pass in... + const viewDef_t * viewDef = tr.viewDef; + + idRenderEntityLocal * entityDef = vEntity->entityDef; + const renderEntity_t * renderEntity = &entityDef->parms; + const idRenderWorldLocal * world = entityDef->world; + + if ( viewDef->isXraySubview && entityDef->parms.xrayIndex == 1 ) { + return; + } else if ( !viewDef->isXraySubview && entityDef->parms.xrayIndex == 2 ) { + return; + } + + SCOPED_PROFILE_EVENT( renderEntity->hModel == NULL ? "Unknown Model" : renderEntity->hModel->Name() ); + + // calculate the znear for testing whether or not the view is inside a shadow projection + const float znear = ( viewDef->renderView.cramZNear ) ? ( r_znear.GetFloat() * 0.25f ) : r_znear.GetFloat(); + + // if the entity wasn't seen through a portal chain, it was added just for light shadows + const bool modelIsVisible = !vEntity->scissorRect.IsEmpty(); + const bool addInteractions = modelIsVisible && ( !viewDef->isXraySubview || entityDef->parms.xrayIndex == 2 ); + const int entityIndex = entityDef->index; + + //--------------------------- + // Find which of the visible lights contact this entity + // + // If the entity doesn't accept light or cast shadows from any surface, + // this can be skipped. + // + // OPTIMIZE: world areas can assume all referenced lights are used + //--------------------------- + int numContactedLights = 0; + static const int MAX_CONTACTED_LIGHTS = 128; + viewLight_t * contactedLights[MAX_CONTACTED_LIGHTS]; + idInteraction * staticInteractions[MAX_CONTACTED_LIGHTS]; + + if ( renderEntity->hModel == NULL || + renderEntity->hModel->ModelHasInteractingSurfaces() || + renderEntity->hModel->ModelHasShadowCastingSurfaces() ) { + SCOPED_PROFILE_EVENT( "Find lights" ); + for ( viewLight_t * vLight = viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { + if ( vLight->scissorRect.IsEmpty() ) { + continue; + } + if ( vLight->entityInteractionState != NULL ) { + // new code path, everything was done in AddLight + if ( vLight->entityInteractionState[entityIndex] == viewLight_t::INTERACTION_YES ) { + contactedLights[numContactedLights] = vLight; + staticInteractions[numContactedLights] = world->interactionTable[vLight->lightDef->index * world->interactionTableWidth + entityIndex]; + if ( ++numContactedLights == MAX_CONTACTED_LIGHTS ) { + break; + } + } + continue; + } + + const idRenderLightLocal * lightDef = vLight->lightDef; + + if ( !lightDef->globalLightBounds.IntersectsBounds( entityDef->globalReferenceBounds ) ) { + continue; + } + + if ( R_CullModelBoundsToLight( lightDef, entityDef->localReferenceBounds, entityDef->modelRenderMatrix ) ) { + continue; + } + + if ( !modelIsVisible ) { + // some lights have their center of projection outside the world + if ( lightDef->areaNum != -1 ) { + // if no part of the model is in an area that is connected to + // the light center (it is behind a solid, closed door), we can ignore it + bool areasConnected = false; + for ( areaReference_t *ref = entityDef->entityRefs; ref != NULL; ref = ref->ownerNext ) { + if ( world->AreasAreConnected( lightDef->areaNum, ref->area->areaNum, PS_BLOCK_VIEW ) ) { + areasConnected = true; + break; + } + } + if ( areasConnected == false ) { + // can't possibly be seen or shadowed + continue; + } + } + + // check more precisely for shadow visibility + idBounds shadowBounds; + R_ShadowBounds( entityDef->globalReferenceBounds, lightDef->globalLightBounds, lightDef->globalLightOrigin, shadowBounds ); + + // this doesn't say that the shadow can't effect anything, only that it can't + // effect anything in the view + if ( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, shadowBounds ) ) { + continue; + } + } + contactedLights[numContactedLights] = vLight; + staticInteractions[numContactedLights] = world->interactionTable[vLight->lightDef->index * world->interactionTableWidth + entityIndex]; + if ( ++numContactedLights == MAX_CONTACTED_LIGHTS ) { + break; + } + } + } + + // if we aren't visible and none of the shadows stretch into the view, + // we don't need to do anything else + if ( !modelIsVisible && numContactedLights == 0 ) { + return; + } + + //--------------------------- + // create a dynamic model if the geometry isn't static + //--------------------------- + idRenderModel * model = R_EntityDefDynamicModel( entityDef ); + if ( model == NULL || model->NumSurfaces() <= 0 ) { + return; + } + + // add the lightweight blood decal surfaces if the model is directly visible + if ( modelIsVisible ) { + assert( !vEntity->scissorRect.IsEmpty() ); + + if ( entityDef->decals != NULL && !r_skipDecals.GetBool() ) { + entityDef->decals->CreateDeferredDecals( model ); + + unsigned int numDrawSurfs = entityDef->decals->GetNumDecalDrawSurfs(); + for ( unsigned int i = 0; i < numDrawSurfs; i++ ) { + drawSurf_t * decalDrawSurf = entityDef->decals->CreateDecalDrawSurf( vEntity, i ); + if ( decalDrawSurf != NULL ) { + decalDrawSurf->linkChain = NULL; + decalDrawSurf->nextOnLight = vEntity->drawSurfs; + vEntity->drawSurfs = decalDrawSurf; + } + } + } + + if ( entityDef->overlays != NULL && !r_skipOverlays.GetBool() ) { + entityDef->overlays->CreateDeferredOverlays( model ); + + unsigned int numDrawSurfs = entityDef->overlays->GetNumOverlayDrawSurfs(); + for ( unsigned int i = 0; i < numDrawSurfs; i++ ) { + drawSurf_t * overlayDrawSurf = entityDef->overlays->CreateOverlayDrawSurf( vEntity, model, i ); + if ( overlayDrawSurf != NULL ) { + overlayDrawSurf->linkChain = NULL; + overlayDrawSurf->nextOnLight = vEntity->drawSurfs; + vEntity->drawSurfs = overlayDrawSurf; + } + } + } + } + + //--------------------------- + // copy matrix related stuff for back-end use + // and setup a render matrix for faster culling + //--------------------------- + vEntity->modelDepthHack = renderEntity->modelDepthHack; + vEntity->weaponDepthHack = renderEntity->weaponDepthHack; + vEntity->skipMotionBlur = renderEntity->skipMotionBlur; + + memcpy( vEntity->modelMatrix, entityDef->modelMatrix, sizeof( vEntity->modelMatrix ) ); + R_MatrixMultiply( entityDef->modelMatrix, viewDef->worldSpace.modelViewMatrix, vEntity->modelViewMatrix ); + + idRenderMatrix viewMat; + idRenderMatrix::Transpose( *(idRenderMatrix *)vEntity->modelViewMatrix, viewMat ); + idRenderMatrix::Multiply( viewDef->projectionRenderMatrix, viewMat, vEntity->mvp ); + if ( renderEntity->weaponDepthHack ) { + idRenderMatrix::ApplyDepthHack( vEntity->mvp ); + } + if ( renderEntity->modelDepthHack != 0.0f ) { + idRenderMatrix::ApplyModelDepthHack( vEntity->mvp, renderEntity->modelDepthHack ); + } + + // local light and view origins are used to determine if the view is definitely outside + // an extruded shadow volume, which means we can skip drawing the end caps + idVec3 localViewOrigin; + R_GlobalPointToLocal( vEntity->modelMatrix, viewDef->renderView.vieworg, localViewOrigin ); + + //--------------------------- + // add all the model surfaces + //--------------------------- + for ( int surfaceNum = 0; surfaceNum < model->NumSurfaces(); surfaceNum++ ) { + const modelSurface_t *surf = model->Surface( surfaceNum ); + + // for debugging, only show a single surface at a time + if ( r_singleSurface.GetInteger() >= 0 && surfaceNum != r_singleSurface.GetInteger() ) { + continue; + } + + srfTriangles_t * tri = surf->geometry; + if ( tri == NULL ) { + continue; + } + if ( tri->numIndexes == NULL ) { + continue; // happens for particles + } + const idMaterial * shader = surf->shader; + if ( shader == NULL ) { + continue; + } + if ( !shader->IsDrawn() ) { + continue; // collision hulls, etc + } + + // RemapShaderBySkin + if ( entityDef->parms.customShader != NULL ) { + // this is sort of a hack, but causes deformed surfaces to map to empty surfaces, + // so the item highlight overlay doesn't highlight the autosprite surface + if ( shader->Deform() ) { + continue; + } + shader = entityDef->parms.customShader; + } else if ( entityDef->parms.customSkin ) { + shader = entityDef->parms.customSkin->RemapShaderBySkin( shader ); + if ( shader == NULL ) { + continue; + } + if ( !shader->IsDrawn() ) { + continue; + } + } + + // optionally override with the renderView->globalMaterial + if ( tr.primaryRenderView.globalMaterial != NULL ) { + shader = tr.primaryRenderView.globalMaterial; + } + + SCOPED_PROFILE_EVENT( shader->GetName() ); + + // debugging tool to make sure we have the correct pre-calculated bounds + if ( r_checkBounds.GetBool() ) { + for ( int j = 0; j < tri->numVerts; j++ ) { + int k; + for ( k = 0; k < 3; k++ ) { + if ( tri->verts[j].xyz[k] > tri->bounds[1][k] + CHECK_BOUNDS_EPSILON + || tri->verts[j].xyz[k] < tri->bounds[0][k] - CHECK_BOUNDS_EPSILON ) { + common->Printf( "bad tri->bounds on %s:%s\n", entityDef->parms.hModel->Name(), shader->GetName() ); + break; + } + if ( tri->verts[j].xyz[k] > entityDef->localReferenceBounds[1][k] + CHECK_BOUNDS_EPSILON + || tri->verts[j].xyz[k] < entityDef->localReferenceBounds[0][k] - CHECK_BOUNDS_EPSILON ) { + common->Printf( "bad referenceBounds on %s:%s\n", entityDef->parms.hModel->Name(), shader->GetName() ); + break; + } + } + if ( k != 3 ) { + break; + } + } + } + + // view frustum culling for the precise surface bounds, which is tighter + // than the entire entity reference bounds + // If the entire model wasn't visible, there is no need to check the + // individual surfaces. + const bool surfaceDirectlyVisible = modelIsVisible && !idRenderMatrix::CullBoundsToMVP( vEntity->mvp, tri->bounds ); + const bool gpuSkinned = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ); + + //-------------------------- + // base drawing surface + //-------------------------- + drawSurf_t * baseDrawSurf = NULL; + if ( surfaceDirectlyVisible ) { + // make sure we have an ambient cache and all necessary normals / tangents + if ( !vertexCache.CacheIsCurrent( tri->indexCache ) ) { + tri->indexCache = vertexCache.AllocIndex( tri->indexes, ALIGN( tri->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + } + if ( !vertexCache.CacheIsCurrent( tri->ambientCache ) ) { + // we are going to use it for drawing, so make sure we have the tangents and normals + if ( shader->ReceivesLighting() && !tri->tangentsCalculated ) { + assert( tri->staticModelWithJoints == NULL ); + R_DeriveTangents( tri ); + assert( false ); // this should no longer be hit + } + tri->ambientCache = vertexCache.AllocVertex( tri->verts, ALIGN( tri->numVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + } + + // add the surface for drawing + // we can re-use some of the values for light interaction surfaces + baseDrawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *baseDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + baseDrawSurf->frontEndGeo = tri; + baseDrawSurf->space = vEntity; + baseDrawSurf->scissorRect = vEntity->scissorRect; + baseDrawSurf->extraGLState = 0; + baseDrawSurf->renderZFail = 0; + + R_SetupDrawSurfShader( baseDrawSurf, shader, renderEntity ); + + // Check for deformations (eyeballs, flares, etc) + const deform_t shaderDeform = shader->Deform(); + if ( shaderDeform != DFRM_NONE ) { + drawSurf_t * deformDrawSurf = R_DeformDrawSurf( baseDrawSurf ); + if ( deformDrawSurf != NULL ) { + // any deforms may have created multiple draw surfaces + for ( drawSurf_t * surf = deformDrawSurf, * next = NULL; surf != NULL; surf = next ) { + next = surf->nextOnLight; + + surf->linkChain = NULL; + surf->nextOnLight = vEntity->drawSurfs; + vEntity->drawSurfs = surf; + } + } + } + + // Most deform source surfaces do not need to be rendered. + // However, particles are rendered in conjunction with the source surface. + if ( shaderDeform == DFRM_NONE || shaderDeform == DFRM_PARTICLE || shaderDeform == DFRM_PARTICLE2 ) { + // copy verts and indexes to this frame's hardware memory if they aren't already there + if ( !vertexCache.CacheIsCurrent( tri->ambientCache ) ) { + tri->ambientCache = vertexCache.AllocVertex( tri->verts, ALIGN( tri->numVerts * sizeof( tri->verts[0] ), VERTEX_CACHE_ALIGN ) ); + } + if ( !vertexCache.CacheIsCurrent( tri->indexCache ) ) { + tri->indexCache = vertexCache.AllocIndex( tri->indexes, ALIGN( tri->numIndexes * sizeof( tri->indexes[0] ), INDEX_CACHE_ALIGN ) ); + } + + R_SetupDrawSurfJoints( baseDrawSurf, tri, shader ); + + baseDrawSurf->numIndexes = tri->numIndexes; + baseDrawSurf->ambientCache = tri->ambientCache; + baseDrawSurf->indexCache = tri->indexCache; + baseDrawSurf->shadowCache = 0; + + baseDrawSurf->linkChain = NULL; // link to the view + baseDrawSurf->nextOnLight = vEntity->drawSurfs; + vEntity->drawSurfs = baseDrawSurf; + } + } + + //---------------------------------------- + // add all light interactions + //---------------------------------------- + for ( int contactedLight = 0; contactedLight < numContactedLights; contactedLight++ ) { + viewLight_t * vLight = contactedLights[contactedLight]; + const idRenderLightLocal * lightDef = vLight->lightDef; + const idInteraction * interaction = staticInteractions[contactedLight]; + + // check for a static interaction + surfaceInteraction_t *surfInter = NULL; + if ( interaction > INTERACTION_EMPTY && interaction->staticInteraction ) { + // we have a static interaction that was calculated accurately + assert( model->NumSurfaces() == interaction->numSurfaces ); + surfInter = &interaction->surfaces[surfaceNum]; + } else { + // try to do a more precise cull of this model surface to the light + if ( R_CullModelBoundsToLight( lightDef, tri->bounds, entityDef->modelRenderMatrix ) ) { + continue; + } + } + + // "invisible ink" lights and shaders (imp spawn drawing on walls, etc) + if ( shader->Spectrum() != lightDef->lightShader->Spectrum() ) { + continue; + } + + // Calculate the local light origin to determine if the view is inside the shadow + // projection and to calculate the triangle facing for dynamic shadow volumes. + idVec3 localLightOrigin; + R_GlobalPointToLocal( vEntity->modelMatrix, lightDef->globalLightOrigin, localLightOrigin ); + + //-------------------------- + // surface light interactions + //-------------------------- + + dynamicShadowVolumeParms_t * dynamicShadowParms = NULL; + + if ( addInteractions && surfaceDirectlyVisible && shader->ReceivesLighting() ) { + // static interactions can commonly find that no triangles from a surface + // contact the light, even when the total model does + if ( surfInter == NULL || surfInter->lightTrisIndexCache > 0 ) { + // create a drawSurf for this interaction + drawSurf_t * lightDrawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *lightDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + + if ( surfInter != NULL ) { + // optimized static interaction + lightDrawSurf->numIndexes = surfInter->numLightTrisIndexes; + lightDrawSurf->indexCache = surfInter->lightTrisIndexCache; + } else { + // throw the entire source surface at it without any per-triangle culling + lightDrawSurf->numIndexes = tri->numIndexes; + lightDrawSurf->indexCache = tri->indexCache; + + // optionally cull the triangles to the light volume + if ( r_cullDynamicLightTriangles.GetBool() ) { + + vertCacheHandle_t lightIndexCache = vertexCache.AllocIndex( NULL, ALIGN( lightDrawSurf->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + if ( vertexCache.CacheIsCurrent( lightIndexCache ) ) { + lightDrawSurf->indexCache = lightIndexCache; + + dynamicShadowParms = (dynamicShadowVolumeParms_t *)R_FrameAlloc( sizeof( dynamicShadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); + + dynamicShadowParms->verts = tri->verts; + dynamicShadowParms->numVerts = tri->numVerts; + dynamicShadowParms->indexes = tri->indexes; + dynamicShadowParms->numIndexes = tri->numIndexes; + dynamicShadowParms->silEdges = tri->silEdges; + dynamicShadowParms->numSilEdges = tri->numSilEdges; + dynamicShadowParms->joints = gpuSkinned ? tri->staticModelWithJoints->jointsInverted : NULL; + dynamicShadowParms->numJoints = gpuSkinned ? tri->staticModelWithJoints->numInvertedJoints : 0; + dynamicShadowParms->triangleBounds = tri->bounds; + dynamicShadowParms->triangleMVP = vEntity->mvp; + dynamicShadowParms->localLightOrigin = localLightOrigin; + dynamicShadowParms->localViewOrigin = localViewOrigin; + idRenderMatrix::Multiply( vLight->lightDef->baseLightProject, entityDef->modelRenderMatrix, dynamicShadowParms->localLightProject ); + dynamicShadowParms->zNear = znear; + dynamicShadowParms->lightZMin = vLight->scissorRect.zmin; + dynamicShadowParms->lightZMax = vLight->scissorRect.zmax; + dynamicShadowParms->cullShadowTrianglesToLight = false; + dynamicShadowParms->forceShadowCaps = false; + dynamicShadowParms->useShadowPreciseInsideTest = false; + dynamicShadowParms->useShadowDepthBounds = false; + dynamicShadowParms->tempFacing = NULL; + dynamicShadowParms->tempCulled = NULL; + dynamicShadowParms->tempVerts = NULL; + dynamicShadowParms->indexBuffer = NULL; + dynamicShadowParms->shadowIndices = NULL; + dynamicShadowParms->maxShadowIndices = 0; + dynamicShadowParms->numShadowIndices = NULL; + dynamicShadowParms->lightIndices = (triIndex_t *)vertexCache.MappedIndexBuffer( lightIndexCache ); + dynamicShadowParms->maxLightIndices = lightDrawSurf->numIndexes; + dynamicShadowParms->numLightIndices = &lightDrawSurf->numIndexes; + dynamicShadowParms->renderZFail = NULL; + dynamicShadowParms->shadowZMin = NULL; + dynamicShadowParms->shadowZMax = NULL; + dynamicShadowParms->shadowVolumeState = & lightDrawSurf->shadowVolumeState; + + lightDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; + + dynamicShadowParms->next = vEntity->dynamicShadowVolumes; + vEntity->dynamicShadowVolumes = dynamicShadowParms; + } + } + } + lightDrawSurf->ambientCache = tri->ambientCache; + lightDrawSurf->shadowCache = 0; + lightDrawSurf->frontEndGeo = tri; + lightDrawSurf->space = vEntity; + lightDrawSurf->material = shader; + lightDrawSurf->extraGLState = 0; + lightDrawSurf->scissorRect = vLight->scissorRect; // interactionScissor; + lightDrawSurf->sort = 0.0f; + lightDrawSurf->renderZFail = 0; + lightDrawSurf->shaderRegisters = baseDrawSurf->shaderRegisters; + + R_SetupDrawSurfJoints( lightDrawSurf, tri, shader ); + + // Determine which linked list to add the light surface to. + // There will only be localSurfaces if the light casts shadows and + // there are surfaces with NOSELFSHADOW. + if ( shader->Coverage() == MC_TRANSLUCENT ) { + lightDrawSurf->linkChain = &vLight->translucentInteractions; + } else if ( !lightDef->parms.noShadows && shader->TestMaterialFlag( MF_NOSELFSHADOW ) ) { + lightDrawSurf->linkChain = &vLight->localInteractions; + } else { + lightDrawSurf->linkChain = &vLight->globalInteractions; + } + lightDrawSurf->nextOnLight = vEntity->drawSurfs; + vEntity->drawSurfs = lightDrawSurf; + } + } + + //-------------------------- + // surface shadows + //-------------------------- + + if ( !shader->SurfaceCastsShadow() ) { + continue; + } + if ( !lightDef->LightCastsShadows() ) { + continue; + } + if ( tri->silEdges == NULL ) { + continue; // can happen for beam models (shouldn't use a shadow casting material, though...) + } + + // if the static shadow does not have any shadows + if ( surfInter != NULL && surfInter->numShadowIndexes == 0 ) { + continue; + } + + // some entities, like view weapons, don't cast any shadows + if ( entityDef->parms.noShadow ) { + continue; + } + + // No shadow if it's suppressed for this light. + if ( entityDef->parms.suppressShadowInLightID && entityDef->parms.suppressShadowInLightID == lightDef->parms.lightId ) { + continue; + } + + if ( lightDef->parms.prelightModel && lightDef->lightHasMoved == false && + entityDef->parms.hModel->IsStaticWorldModel() && !r_skipPrelightShadows.GetBool() ) { + // static light / world model shadow interacitons + // are always captured in the prelight shadow volume + continue; + } + + // If the shadow is drawn (or translucent), but the model isn't, we must include the shadow caps + // because we may be able to see into the shadow volume even though the view is outside it. + // This happens for the player world weapon and possibly some animations in multiplayer. + const bool forceShadowCaps = !addInteractions || r_forceShadowCaps.GetBool(); + + drawSurf_t * shadowDrawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *shadowDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + + if ( surfInter != NULL ) { + shadowDrawSurf->numIndexes = 0; + shadowDrawSurf->indexCache = surfInter->shadowIndexCache; + shadowDrawSurf->shadowCache = tri->shadowCache; + shadowDrawSurf->scissorRect = vLight->scissorRect; // default to the light scissor and light depth bounds + shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE; // assume the shadow volume is done in case r_skipStaticShadows is set + + if ( !r_skipStaticShadows.GetBool() ) { + staticShadowVolumeParms_t * staticShadowParms = (staticShadowVolumeParms_t *)R_FrameAlloc( sizeof( staticShadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); + + staticShadowParms->verts = tri->staticShadowVertexes; + staticShadowParms->numVerts = tri->numVerts * 2; + staticShadowParms->indexes = surfInter->shadowIndexes; + staticShadowParms->numIndexes = surfInter->numShadowIndexes; + staticShadowParms->numShadowIndicesWithCaps = surfInter->numShadowIndexes; + staticShadowParms->numShadowIndicesNoCaps = surfInter->numShadowIndexesNoCaps; + staticShadowParms->triangleBounds = tri->bounds; + staticShadowParms->triangleMVP = vEntity->mvp; + staticShadowParms->localLightOrigin = localLightOrigin; + staticShadowParms->localViewOrigin = localViewOrigin; + staticShadowParms->zNear = znear; + staticShadowParms->lightZMin = vLight->scissorRect.zmin; + staticShadowParms->lightZMax = vLight->scissorRect.zmax; + staticShadowParms->forceShadowCaps = forceShadowCaps; + staticShadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool(); + staticShadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool(); + staticShadowParms->numShadowIndices = & shadowDrawSurf->numIndexes; + staticShadowParms->renderZFail = & shadowDrawSurf->renderZFail; + staticShadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin; + staticShadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax; + staticShadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState; + + shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; + + staticShadowParms->next = vEntity->staticShadowVolumes; + vEntity->staticShadowVolumes = staticShadowParms; + } + + } else { + // When CPU skinning the dynamic shadow verts of a dynamic model may not have been copied to buffer memory yet. + if ( !vertexCache.CacheIsCurrent( tri->shadowCache ) ) { + assert( !gpuSkinned ); // the shadow cache should be static when using GPU skinning + // Extracts just the xyz values from a set of full size drawverts, and + // duplicates them with w set to 0 and 1 for the vertex program to project. + // This is constant for any number of lights, the vertex program takes care + // of projecting the verts to infinity for a particular light. + tri->shadowCache = vertexCache.AllocVertex( NULL, ALIGN( tri->numVerts * 2 * sizeof( idShadowVert ), VERTEX_CACHE_ALIGN ) ); + idShadowVert * shadowVerts = (idShadowVert *)vertexCache.MappedVertexBuffer( tri->shadowCache ); + idShadowVert::CreateShadowCache( shadowVerts, tri->verts, tri->numVerts ); + } + + const int maxShadowVolumeIndexes = tri->numSilEdges * 6 + tri->numIndexes * 2; + + shadowDrawSurf->numIndexes = 0; + shadowDrawSurf->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxShadowVolumeIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + shadowDrawSurf->shadowCache = tri->shadowCache; + shadowDrawSurf->scissorRect = vLight->scissorRect; // default to the light scissor and light depth bounds + shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE; // assume the shadow volume is done in case the index cache allocation failed + + // if the index cache was successfully allocated then setup the parms to create a shadow volume in parallel + if ( vertexCache.CacheIsCurrent( shadowDrawSurf->indexCache ) && !r_skipDynamicShadows.GetBool() ) { + + // if the parms were not already allocated for culling interaction triangles to the light frustum + if ( dynamicShadowParms == NULL ) { + dynamicShadowParms = (dynamicShadowVolumeParms_t *)R_FrameAlloc( sizeof( dynamicShadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); + } else { + // the shadow volume will be rendered first so when the interaction surface is drawn the triangles have been culled for sure + *dynamicShadowParms->shadowVolumeState = SHADOWVOLUME_DONE; + } + + dynamicShadowParms->verts = tri->verts; + dynamicShadowParms->numVerts = tri->numVerts; + dynamicShadowParms->indexes = tri->indexes; + dynamicShadowParms->numIndexes = tri->numIndexes; + dynamicShadowParms->silEdges = tri->silEdges; + dynamicShadowParms->numSilEdges = tri->numSilEdges; + dynamicShadowParms->joints = gpuSkinned ? tri->staticModelWithJoints->jointsInverted : NULL; + dynamicShadowParms->numJoints = gpuSkinned ? tri->staticModelWithJoints->numInvertedJoints : 0; + dynamicShadowParms->triangleBounds = tri->bounds; + dynamicShadowParms->triangleMVP = vEntity->mvp; + dynamicShadowParms->localLightOrigin = localLightOrigin; + dynamicShadowParms->localViewOrigin = localViewOrigin; + idRenderMatrix::Multiply( vLight->lightDef->baseLightProject, entityDef->modelRenderMatrix, dynamicShadowParms->localLightProject ); + dynamicShadowParms->zNear = znear; + dynamicShadowParms->lightZMin = vLight->scissorRect.zmin; + dynamicShadowParms->lightZMax = vLight->scissorRect.zmax; + dynamicShadowParms->cullShadowTrianglesToLight = r_cullDynamicShadowTriangles.GetBool(); + dynamicShadowParms->forceShadowCaps = forceShadowCaps; + dynamicShadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool(); + dynamicShadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool(); + dynamicShadowParms->tempFacing = NULL; + dynamicShadowParms->tempCulled = NULL; + dynamicShadowParms->tempVerts = NULL; + dynamicShadowParms->indexBuffer = NULL; + dynamicShadowParms->shadowIndices = (triIndex_t *)vertexCache.MappedIndexBuffer( shadowDrawSurf->indexCache ); + dynamicShadowParms->maxShadowIndices = maxShadowVolumeIndexes; + dynamicShadowParms->numShadowIndices = & shadowDrawSurf->numIndexes; + // dynamicShadowParms->lightIndices may have already been set for the interaction surface + // dynamicShadowParms->maxLightIndices may have already been set for the interaction surface + // dynamicShadowParms->numLightIndices may have already been set for the interaction surface + dynamicShadowParms->renderZFail = & shadowDrawSurf->renderZFail; + dynamicShadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin; + dynamicShadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax; + dynamicShadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState; + + shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; + + // if the parms we not already linked for culling interaction triangles to the light frustum + if ( dynamicShadowParms->lightIndices == NULL ) { + dynamicShadowParms->next = vEntity->dynamicShadowVolumes; + vEntity->dynamicShadowVolumes = dynamicShadowParms; + } + + tr.pc.c_createShadowVolumes++; + } + } + + assert( vertexCache.CacheIsCurrent( shadowDrawSurf->shadowCache ) ); + assert( vertexCache.CacheIsCurrent( shadowDrawSurf->indexCache ) ); + + shadowDrawSurf->ambientCache = 0; + shadowDrawSurf->frontEndGeo = NULL; + shadowDrawSurf->space = vEntity; + shadowDrawSurf->material = NULL; + shadowDrawSurf->extraGLState = 0; + shadowDrawSurf->sort = 0.0f; + shadowDrawSurf->shaderRegisters = NULL; + + R_SetupDrawSurfJoints( shadowDrawSurf, tri, NULL ); + + // determine which linked list to add the shadow surface to + shadowDrawSurf->linkChain = shader->TestMaterialFlag( MF_NOSELFSHADOW ) ? &vLight->localShadows : &vLight->globalShadows; + shadowDrawSurf->nextOnLight = vEntity->drawSurfs; + vEntity->drawSurfs = shadowDrawSurf; + } + } +} + +REGISTER_PARALLEL_JOB( R_AddSingleModel, "R_AddSingleModel" ); + +/* +================= +R_LinkDrawSurfToView + +Als called directly by GuiModel +================= +*/ +void R_LinkDrawSurfToView( drawSurf_t * drawSurf, viewDef_t * viewDef ) { + // if it doesn't fit, resize the list + if ( viewDef->numDrawSurfs == viewDef->maxDrawSurfs ) { + drawSurf_t **old = viewDef->drawSurfs; + int count; + + if ( viewDef->maxDrawSurfs == 0 ) { + viewDef->maxDrawSurfs = INITIAL_DRAWSURFS; + count = 0; + } else { + count = viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ); + viewDef->maxDrawSurfs *= 2; + } + viewDef->drawSurfs = (drawSurf_t **)R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ), FRAME_ALLOC_DRAW_SURFACE_POINTER ); + memcpy( viewDef->drawSurfs, old, count ); + } + + viewDef->drawSurfs[viewDef->numDrawSurfs] = drawSurf; + viewDef->numDrawSurfs++; +} + +/* +=================== +R_AddModels + +The end result of running this is the addition of drawSurf_t to the +tr.viewDef->drawSurfs[] array and light link chains, along with +frameData and vertexCache allocations to support the drawSurfs. +=================== +*/ +void R_AddModels() { + SCOPED_PROFILE_EVENT( "R_AddModels" ); + + tr.viewDef->viewEntitys = R_SortViewEntities( tr.viewDef->viewEntitys ); + + //------------------------------------------------- + // Go through each view entity that is either visible to the view, or to + // any light that intersects the view (for shadows). + //------------------------------------------------- + + if ( r_useParallelAddModels.GetBool() ) { + for ( viewEntity_t * vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { + tr.frontEndJobList->AddJob( (jobRun_t)R_AddSingleModel, vEntity ); + } + tr.frontEndJobList->Submit(); + tr.frontEndJobList->Wait(); + } else { + for ( viewEntity_t * vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { + R_AddSingleModel( vEntity ); + } + } + + //------------------------------------------------- + // Kick off jobs to setup static and dynamic shadow volumes. + //------------------------------------------------- + + if ( r_useParallelAddShadows.GetInteger() == 1 ) { + for ( viewEntity_t * vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { + for ( staticShadowVolumeParms_t * shadowParms = vEntity->staticShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { + tr.frontEndJobList->AddJob( (jobRun_t)StaticShadowVolumeJob, shadowParms ); + } + for ( dynamicShadowVolumeParms_t * shadowParms = vEntity->dynamicShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { + tr.frontEndJobList->AddJob( (jobRun_t)DynamicShadowVolumeJob, shadowParms ); + } + vEntity->staticShadowVolumes = NULL; + vEntity->dynamicShadowVolumes = NULL; + } + tr.frontEndJobList->Submit(); + // wait here otherwise the shadow volume index buffer may be unmapped before all shadow volumes have been constructed + tr.frontEndJobList->Wait(); + } else { + int start = Sys_Microseconds(); + + for ( viewEntity_t * vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { + for ( staticShadowVolumeParms_t * shadowParms = vEntity->staticShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { + StaticShadowVolumeJob( shadowParms ); + } + for ( dynamicShadowVolumeParms_t * shadowParms = vEntity->dynamicShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { + DynamicShadowVolumeJob( shadowParms ); + } + vEntity->staticShadowVolumes = NULL; + vEntity->dynamicShadowVolumes = NULL; + } + + int end = Sys_Microseconds(); + backEnd.pc.shadowMicroSec += end - start; + } + + //------------------------------------------------- + // Move the draw surfs to the view. + //------------------------------------------------- + + tr.viewDef->numDrawSurfs = 0; // clear the ambient surface list + tr.viewDef->maxDrawSurfs = 0; // will be set to INITIAL_DRAWSURFS on R_LinkDrawSurfToView + + for ( viewEntity_t * vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { + for ( drawSurf_t * ds = vEntity->drawSurfs; ds != NULL; ) { + drawSurf_t * next = ds->nextOnLight; + if ( ds->linkChain == NULL ) { + R_LinkDrawSurfToView( ds, tr.viewDef ); + } else { + ds->nextOnLight = *ds->linkChain; + *ds->linkChain = ds; + } + ds = next; + } + vEntity->drawSurfs = NULL; + } +} diff --git a/neo/renderer/tr_frontend_deform.cpp b/neo/renderer/tr_frontend_deform.cpp new file mode 100644 index 00000000..67d6c8a8 --- /dev/null +++ b/neo/renderer/tr_frontend_deform.cpp @@ -0,0 +1,1029 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +/* +========================================================================================== + +DEFORM SURFACES + +========================================================================================== +*/ + +/* +================= +R_FinishDeform +================= +*/ +static drawSurf_t * R_FinishDeform( drawSurf_t * surf, srfTriangles_t * newTri, const idDrawVert * newVerts, const triIndex_t * newIndexes ) { + newTri->ambientCache = vertexCache.AllocVertex( newVerts, ALIGN( newTri->numVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + newTri->indexCache = vertexCache.AllocIndex( newIndexes, ALIGN( newTri->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + + surf->frontEndGeo = newTri; + surf->numIndexes = newTri->numIndexes; + surf->ambientCache = newTri->ambientCache; + surf->indexCache = newTri->indexCache; + surf->shadowCache = 0; + surf->jointCache = 0; + surf->nextOnLight = NULL; + + return surf; +} + +/* +===================== +R_AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites. +===================== +*/ +static drawSurf_t * R_AutospriteDeform( drawSurf_t *surf ) { + const srfTriangles_t * srcTri = surf->frontEndGeo; + + if ( srcTri->numVerts & 3 ) { + common->Warning( "R_AutospriteDeform: shader had odd vertex count" ); + return NULL; + } + if ( srcTri->numIndexes != ( srcTri->numVerts >> 2 ) * 6 ) { + common->Warning( "R_AutospriteDeform: autosprite had odd index count" ); + return NULL; + } + + const idJointMat * joints = ( srcTri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? srcTri->staticModelWithJoints->jointsInverted : NULL; + + idVec3 leftDir; + idVec3 upDir; + R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[1], leftDir ); + R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[2], upDir ); + + if ( tr.viewDef->isMirror ) { + leftDir = vec3_origin - leftDir; + } + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = srcTri->numVerts; + newTri->numIndexes = srcTri->numIndexes; + + idDrawVert * newVerts = (idDrawVert *)_alloca16( ALIGN( srcTri->numVerts * sizeof( idDrawVert ), 16 ) ); + triIndex_t * newIndexes = (triIndex_t *)_alloca16( ALIGN( srcTri->numIndexes * sizeof( triIndex_t ), 16 ) ); + + for ( int i = 0; i < srcTri->numVerts; i += 4 ) { + // find the midpoint + newVerts[i+0] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[i + 0], joints ); + newVerts[i+1] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[i + 1], joints ); + newVerts[i+2] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[i + 2], joints ); + newVerts[i+3] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[i + 3], joints ); + + idVec3 mid; + mid[0] = 0.25f * ( newVerts[i+0].xyz[0] + newVerts[i+1].xyz[0] + newVerts[i+2].xyz[0] + newVerts[i+3].xyz[0] ); + mid[1] = 0.25f * ( newVerts[i+0].xyz[1] + newVerts[i+1].xyz[1] + newVerts[i+2].xyz[1] + newVerts[i+3].xyz[1] ); + mid[2] = 0.25f * ( newVerts[i+0].xyz[2] + newVerts[i+1].xyz[2] + newVerts[i+2].xyz[2] + newVerts[i+3].xyz[2] ); + + const idVec3 delta = newVerts[i+0].xyz - mid; + const float radius = delta.Length() * idMath::SQRT_1OVER2; + + const idVec3 left = leftDir * radius; + const idVec3 up = upDir * radius; + + newVerts[i+0].xyz = mid + left + up; + newVerts[i+0].SetTexCoord( 0, 0 ); + newVerts[i+1].xyz = mid - left + up; + newVerts[i+1].SetTexCoord( 1, 0 ); + newVerts[i+2].xyz = mid - left - up; + newVerts[i+2].SetTexCoord( 1, 1 ); + newVerts[i+3].xyz = mid + left - up; + newVerts[i+3].SetTexCoord( 0, 1 ); + + newIndexes[6*(i>>2)+0] = i+0; + newIndexes[6*(i>>2)+1] = i+1; + newIndexes[6*(i>>2)+2] = i+2; + + newIndexes[6*(i>>2)+3] = i+0; + newIndexes[6*(i>>2)+4] = i+2; + newIndexes[6*(i>>2)+5] = i+3; + } + + return R_FinishDeform( surf, newTri, newVerts, newIndexes ); +} + +/* +===================== +R_TubeDeform + +Will pivot a rectangular quad along the center of its long axis. + +Note that a geometric tube with even quite a few sides will almost certainly render +much faster than this, so this should only be for faked volumetric tubes. +Make sure this is used with twosided translucent shaders, because the exact side +order may not be correct. +===================== +*/ +static drawSurf_t * R_TubeDeform( drawSurf_t * surf ) { + static int edgeVerts[6][2] = { + { 0, 1 }, + { 1, 2 }, + { 2, 0 }, + { 3, 4 }, + { 4, 5 }, + { 5, 3 } + }; + + const srfTriangles_t * srcTri = surf->frontEndGeo; + + if ( srcTri->numVerts & 3 ) { + common->Error( "R_TubeDeform: shader had odd vertex count" ); + } + if ( srcTri->numIndexes != ( srcTri->numVerts >> 2 ) * 6 ) { + common->Error( "R_TubeDeform: autosprite had odd index count" ); + } + + const idJointMat * joints = ( srcTri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? srcTri->staticModelWithJoints->jointsInverted : NULL; + + // we need the view direction to project the minor axis of the tube + // as the view changes + idVec3 localView; + R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localView ); + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = srcTri->numVerts; + newTri->numIndexes = srcTri->numIndexes; + + idDrawVert * newVerts = (idDrawVert *)_alloca16( ALIGN( srcTri->numVerts * sizeof( idDrawVert ), 16 ) ); + for ( int i = 0; i < srcTri->numVerts; i++ ) { + newVerts[i].Clear(); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot if it is an issue, but it would mess up the shader abstraction + for ( int i = 0, indexes = 0; i < srcTri->numVerts; i += 4, indexes += 6 ) { + // identify the two shortest edges out of the six defined by the indexes + int nums[2] = { 0, 0 }; + float lengths[2] = { 999999.0f, 999999.0f }; + + for ( int j = 0; j < 6; j++ ) { + const idVec3 v1 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[i + edgeVerts[j][0]]], joints ); + const idVec3 v2 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[i + edgeVerts[j][1]]], joints ); + + const float l = ( v1 - v2 ).Length(); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + // find the midpoints of the two short edges, which + // will give us the major axis in object coordinates + idVec3 mid[2]; + for ( int j = 0; j < 2; j++ ) { + const idVec3 v1 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[i+edgeVerts[nums[j]][0]]], joints ); + const idVec3 v2 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[i+edgeVerts[nums[j]][1]]], joints ); + + mid[j][0] = 0.5f * ( v1[0] + v2[0] ); + mid[j][1] = 0.5f * ( v1[1] + v2[1] ); + mid[j][2] = 0.5f * ( v1[2] + v2[2] ); + } + + // find the vector of the major axis + const idVec3 major = mid[1] - mid[0]; + + // re-project the points + for ( int j = 0; j < 2; j++ ) { + const int i1 = srcTri->indexes[i+edgeVerts[nums[j]][0]]; + const int i2 = srcTri->indexes[i+edgeVerts[nums[j]][1]]; + + newVerts[i1] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[i1], joints ); + newVerts[i2] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[i2], joints ); + + const float l = 0.5f * lengths[j]; + + // cross this with the view direction to get minor axis + idVec3 dir = mid[j] - localView; + idVec3 minor; + minor.Cross( major, dir ); + minor.Normalize(); + + if ( j ) { + newVerts[i1].xyz = mid[j] - l * minor; + newVerts[i2].xyz = mid[j] + l * minor; + } else { + newVerts[i1].xyz = mid[j] + l * minor; + newVerts[i2].xyz = mid[j] - l * minor; + } + } + } + + return R_FinishDeform( surf, newTri, newVerts, srcTri->indexes ); +} + +/* +===================== +R_WindingFromTriangles +===================== +*/ +#define MAX_TRI_WINDING_INDEXES 16 +int R_WindingFromTriangles( const srfTriangles_t *tri, triIndex_t indexes[MAX_TRI_WINDING_INDEXES] ) { + int i, j, k, l; + + indexes[0] = tri->indexes[0]; + int numIndexes = 1; + int numTris = tri->numIndexes / 3; + + do { + // find an edge that goes from the current index to another + // index that isn't already used, and isn't an internal edge + for ( i = 0; i < numTris; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( tri->indexes[i*3+j] != indexes[numIndexes-1] ) { + continue; + } + int next = tri->indexes[i*3+(j+1)%3]; + + // make sure it isn't already used + if ( numIndexes == 1 ) { + if ( next == indexes[0] ) { + continue; + } + } else { + for ( k = 1; k < numIndexes; k++ ) { + if ( indexes[k] == next ) { + break; + } + } + if ( k != numIndexes ) { + continue; + } + } + + // make sure it isn't an interior edge + for ( k = 0; k < numTris; k++ ) { + if ( k == i ) { + continue; + } + for ( l = 0; l < 3; l++ ) { + int a, b; + + a = tri->indexes[k*3+l]; + if ( a != next ) { + continue; + } + b = tri->indexes[k*3+(l+1)%3]; + if ( b != indexes[numIndexes-1] ) { + continue; + } + + // this is an interior edge + break; + } + if ( l != 3 ) { + break; + } + } + if ( k != numTris ) { + continue; + } + + // add this to the list + indexes[numIndexes] = next; + numIndexes++; + break; + } + if ( j != 3 ) { + break; + } + } + if ( numIndexes == tri->numVerts ) { + break; + } + } while ( i != numTris ); + + return numIndexes; +} + +/* +===================== +R_FlareDeform +===================== +*/ +static drawSurf_t * R_FlareDeform( drawSurf_t * surf ) { + const srfTriangles_t * srcTri = surf->frontEndGeo; + + assert( srcTri->staticModelWithJoints == NULL ); + + if ( srcTri->numVerts != 4 || srcTri->numIndexes != 6 ) { + // FIXME: temp hack for flares on tripleted models + common->Warning( "R_FlareDeform: not a single quad" ); + return NULL; + } + + // find the plane + idPlane plane; + plane.FromPoints( srcTri->verts[srcTri->indexes[0]].xyz, srcTri->verts[srcTri->indexes[1]].xyz, srcTri->verts[srcTri->indexes[2]].xyz ); + + // if viewer is behind the plane, draw nothing + idVec3 localViewer; + R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer ); + float distFromPlane = localViewer * plane.Normal() + plane[3]; + if ( distFromPlane <= 0 ) { + return NULL; + } + + idVec3 center = srcTri->verts[0].xyz; + for ( int i = 1; i < srcTri->numVerts; i++ ) { + center += srcTri->verts[i].xyz; + } + center *= 1.0f / srcTri->numVerts; + + idVec3 dir = localViewer - center; + dir.Normalize(); + + const float dot = dir * plane.Normal(); + + // set vertex colors based on plane angle + int color = idMath::Ftoi( dot * 8 * 256 ); + if ( color > 255 ) { + color = 255; + } + + triIndex_t indexes[MAX_TRI_WINDING_INDEXES]; + int numIndexes = R_WindingFromTriangles( srcTri, indexes ); + + // only deal with quads + if ( numIndexes != 4 ) { + return NULL; + } + + const int maxVerts = 16; + const int maxIndexes = 18 * 3; + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = maxVerts; + newTri->numIndexes = maxIndexes; + + idDrawVert *newVerts = (idDrawVert *)_alloca16( ALIGN( maxVerts * sizeof( idDrawVert ), 16 ) ); + + idVec3 edgeDir[4][3]; + + // calculate vector directions + for ( int i = 0; i < 4; i++ ) { + newVerts[i].Clear(); + newVerts[i].xyz = srcTri->verts[ indexes[i] ].xyz; + newVerts[i].SetTexCoord( 0.5f, 0.5f ); + newVerts[i].color[0] = color; + newVerts[i].color[1] = color; + newVerts[i].color[2] = color; + newVerts[i].color[3] = 255; + + idVec3 toEye = srcTri->verts[ indexes[i] ].xyz - localViewer; + toEye.Normalize(); + + idVec3 d1 = srcTri->verts[ indexes[(i+1)%4] ].xyz - localViewer; + d1.Normalize(); + edgeDir[i][2].Cross( toEye, d1 ); + edgeDir[i][2].Normalize(); + edgeDir[i][2] = vec3_origin - edgeDir[i][2]; + + idVec3 d2 = srcTri->verts[ indexes[(i+3)%4] ].xyz - localViewer; + d2.Normalize(); + edgeDir[i][0].Cross( toEye, d2 ); + edgeDir[i][0].Normalize(); + + edgeDir[i][1] = edgeDir[i][0] + edgeDir[i][2]; + edgeDir[i][1].Normalize(); + } + + const float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat(); + + for ( int i = 4; i < 16; i++ ) { + const int index = ( i - 4 ) / 3; + idVec3 v = srcTri->verts[indexes[index]].xyz + spread * edgeDir[index][( i - 4 ) % 3]; + + idVec3 dir = v - localViewer; + const float len = dir.Normalize(); + const float ang = dir * plane.Normal(); + const float newLen = -( distFromPlane / ang ); + if ( newLen > 0.0f && newLen < len ) { + v = localViewer + dir * newLen; + } + + newVerts[i].Clear(); + newVerts[i].xyz = v; + newVerts[i].SetTexCoord( 0.0f, 0.5f ); + newVerts[i].color[0] = color; + newVerts[i].color[1] = color; + newVerts[i].color[2] = color; + newVerts[i].color[3] = 255; + } + + ALIGNTYPE16 static triIndex_t triIndexes[18*3 + 10] = { + 0, 4,5, 0,5, 6, 0,6,7, 0,7, 1, 1,7, 8, 1,8, 9, + 15, 4,0, 15,0, 3, 3,0,1, 3,1, 2, 2,1, 9, 2,9,10, + 14,15,3, 14,3,13, 13,3,2, 13,2,12, 12,2,11, 11,2,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // to make this a multiple of 16 bytes + }; + + return R_FinishDeform( surf, newTri, newVerts, triIndexes ); +} + +/* +===================== +R_ExpandDeform + +Expands the surface along it's normals by a shader amount +===================== +*/ +static drawSurf_t * R_ExpandDeform( drawSurf_t * surf ) { + const srfTriangles_t * srcTri = surf->frontEndGeo; + + assert( srcTri->staticModelWithJoints == NULL ); + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = srcTri->numVerts; + newTri->numIndexes = srcTri->numIndexes; + + idDrawVert *newVerts = (idDrawVert *)_alloca16( ALIGN( newTri->numVerts * sizeof( idDrawVert ), 16 ) ); + + const float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + for ( int i = 0; i < srcTri->numVerts; i++ ) { + newVerts[i] = srcTri->verts[i]; + newVerts[i].xyz = srcTri->verts[i].xyz + srcTri->verts[i].GetNormal() * dist; + } + + return R_FinishDeform( surf, newTri, newVerts, srcTri->indexes ); +} + +/* +===================== +R_MoveDeform + +Moves the surface along the X axis, mostly just for demoing the deforms +===================== +*/ +static drawSurf_t * R_MoveDeform( drawSurf_t * surf ) { + const srfTriangles_t * srcTri = surf->frontEndGeo; + + assert( srcTri->staticModelWithJoints == NULL ); + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = srcTri->numVerts; + newTri->numIndexes = srcTri->numIndexes; + + idDrawVert *newVerts = (idDrawVert *)_alloca16( ALIGN( newTri->numVerts * sizeof( idDrawVert ), 16 ) ); + + const float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + for ( int i = 0; i < srcTri->numVerts; i++ ) { + newVerts[i] = srcTri->verts[i]; + newVerts[i].xyz[0] += dist; + } + + return R_FinishDeform( surf, newTri, newVerts, srcTri->indexes ); +} + +/* +===================== +R_TurbulentDeform + +Turbulently deforms the texture coordinates. +===================== +*/ +static drawSurf_t * R_TurbulentDeform( drawSurf_t * surf ) { + const srfTriangles_t * srcTri = surf->frontEndGeo; + + assert( srcTri->staticModelWithJoints == NULL ); + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = srcTri->numVerts; + newTri->numIndexes = srcTri->numIndexes; + + idDrawVert *newVerts = (idDrawVert *)_alloca16( ALIGN( newTri->numVerts * sizeof( idDrawVert ), 16 ) ); + + const idDeclTable * table = (const idDeclTable *)surf->material->GetDeformDecl(); + const float range = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + const float timeOfs = surf->shaderRegisters[ surf->material->GetDeformRegister(1) ]; + const float domain = surf->shaderRegisters[ surf->material->GetDeformRegister(2) ]; + const float tOfs = 0.5f; + + for ( int i = 0; i < srcTri->numVerts; i++ ) { + float f = srcTri->verts[i].xyz[0] * 0.003f + srcTri->verts[i].xyz[1] * 0.007f + srcTri->verts[i].xyz[2] * 0.011f; + + f = timeOfs + domain * f; + f += timeOfs; + + idVec2 tempST = srcTri->verts[i].GetTexCoord(); + tempST[0] += range * table->TableLookup( f ); + tempST[1] += range * table->TableLookup( f + tOfs ); + + newVerts[i] = srcTri->verts[i]; + newVerts[i].SetTexCoord( tempST ); + } + + return R_FinishDeform( surf, newTri, newVerts, srcTri->indexes ); +} + +/* +===================== +AddTriangleToIsland_r +===================== +*/ +#define MAX_EYEBALL_TRIS 10 +#define MAX_EYEBALL_ISLANDS 6 + +typedef struct { + int tris[MAX_EYEBALL_TRIS]; + int numTris; + idBounds bounds; + idVec3 mid; +} eyeIsland_t; + +static void AddTriangleToIsland_r( const srfTriangles_t *tri, int triangleNum, bool *usedList, eyeIsland_t *island ) { + usedList[triangleNum] = true; + + // add to the current island + if ( island->numTris >= MAX_EYEBALL_TRIS ) { + common->Error( "MAX_EYEBALL_TRIS" ); + return; + } + island->tris[island->numTris] = triangleNum; + island->numTris++; + + const idJointMat * joints = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? tri->staticModelWithJoints->jointsInverted : NULL; + + // recurse into all neighbors + const int a = tri->indexes[triangleNum*3+0]; + const int b = tri->indexes[triangleNum*3+1]; + const int c = tri->indexes[triangleNum*3+2]; + + const idVec3 va = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[a], joints ); + const idVec3 vb = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[b], joints ); + const idVec3 vc = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[c], joints ); + + island->bounds.AddPoint( va ); + island->bounds.AddPoint( vb ); + island->bounds.AddPoint( vc ); + + int numTri = tri->numIndexes / 3; + for ( int i = 0; i < numTri; i++ ) { + if ( usedList[i] ) { + continue; + } + if ( tri->indexes[i*3+0] == a + || tri->indexes[i*3+1] == a + || tri->indexes[i*3+2] == a + || tri->indexes[i*3+0] == b + || tri->indexes[i*3+1] == b + || tri->indexes[i*3+2] == b + || tri->indexes[i*3+0] == c + || tri->indexes[i*3+1] == c + || tri->indexes[i*3+2] == c ) { + AddTriangleToIsland_r( tri, i, usedList, island ); + } + } +} + +/* +===================== +R_EyeballDeform + +Each eyeball surface should have an separate upright triangle behind it, long end +pointing out the eye, and another single triangle in front of the eye for the focus point. +===================== +*/ +static drawSurf_t * R_EyeballDeform( drawSurf_t * surf ) { + const srfTriangles_t * srcTri = surf->frontEndGeo; + + // separate all the triangles into islands + const int numTri = srcTri->numIndexes / 3; + if ( numTri > MAX_EYEBALL_ISLANDS * MAX_EYEBALL_TRIS ) { + common->Printf( "R_EyeballDeform: too many triangles in surface" ); + return NULL; + } + + eyeIsland_t islands[MAX_EYEBALL_ISLANDS]; + bool triUsed[MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS]; + memset( triUsed, 0, sizeof( triUsed ) ); + + int numIslands = 0; + for ( ; numIslands < MAX_EYEBALL_ISLANDS; numIslands++ ) { + islands[numIslands].numTris = 0; + islands[numIslands].bounds.Clear(); + int i; + for ( i = 0; i < numTri; i++ ) { + if ( !triUsed[i] ) { + AddTriangleToIsland_r( srcTri, i, triUsed, &islands[numIslands] ); + break; + } + } + if ( i == numTri ) { + break; + } + } + + // assume we always have two eyes, two origins, and two targets + if ( numIslands != 3 ) { + common->Printf( "R_EyeballDeform: %i triangle islands\n", numIslands ); + return NULL; + } + + const idJointMat * joints = ( srcTri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? srcTri->staticModelWithJoints->jointsInverted : NULL; + + // the srfTriangles_t are in frame memory and will be automatically disposed of + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->numVerts = srcTri->numVerts; + newTri->numIndexes = srcTri->numIndexes; + + idDrawVert *newVerts = (idDrawVert *)_alloca16( ALIGN( srcTri->numVerts * sizeof( idDrawVert ), 16 ) ); + triIndex_t *newIndexes = (triIndex_t *)_alloca16( ALIGN( srcTri->numIndexes * sizeof( triIndex_t ), 16 ) ); + + // decide which islands are the eyes and points + for ( int i = 0; i < numIslands; i++ ) { + islands[i].mid = islands[i].bounds.GetCenter(); + } + + int numIndexes = 0; + for ( int i = 0; i < numIslands; i++ ) { + eyeIsland_t * island = &islands[i]; + + if ( island->numTris == 1 ) { + continue; + } + + // the closest single triangle point will be the eye origin + // and the next-to-farthest will be the focal point + idVec3 origin; + idVec3 focus; + int originIsland = 0; + float dist[MAX_EYEBALL_ISLANDS]; + int sortOrder[MAX_EYEBALL_ISLANDS]; + + for ( int j = 0; j < numIslands; j++ ) { + idVec3 dir = islands[j].mid - island->mid; + dist[j] = dir.Length(); + sortOrder[j] = j; + for ( int k = j - 1; k >= 0; k-- ) { + if ( dist[k] > dist[k+1] ) { + int temp = sortOrder[k]; + sortOrder[k] = sortOrder[k+1]; + sortOrder[k+1] = temp; + float ftemp = dist[k]; + dist[k] = dist[k+1]; + dist[k+1] = ftemp; + } + } + } + + originIsland = sortOrder[1]; + origin = islands[originIsland].mid; + + focus = islands[sortOrder[2]].mid; + + // determine the projection directions based on the origin island triangle + idVec3 dir = focus - origin; + dir.Normalize(); + + const idVec3 p1 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[islands[originIsland].tris[0] + 0]], joints ); + const idVec3 p2 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[islands[originIsland].tris[0] + 1]], joints ); + const idVec3 p3 = idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[srcTri->indexes[islands[originIsland].tris[0] + 2]], joints ); + + idVec3 v1 = p2 - p1; + v1.Normalize(); + idVec3 v2 = p3 - p1; + v2.Normalize(); + + // texVec[0] will be the normal to the origin triangle + idVec3 texVec[2]; + texVec[0].Cross( v1, v2 ); + texVec[1].Cross( texVec[0], dir ); + + for ( int j = 0; j < 2; j++ ) { + texVec[j] -= dir * ( texVec[j] * dir ); + texVec[j].Normalize(); + } + + // emit these triangles, generating the projected texcoords + for ( int j = 0; j < islands[i].numTris; j++ ) { + for ( int k = 0; k < 3; k++ ) { + int index = islands[i].tris[j] * 3; + + index = srcTri->indexes[index + k]; + newIndexes[numIndexes++] = index; + + newVerts[index] = idDrawVert::GetSkinnedDrawVert( srcTri->verts[index], joints ); + + const idVec3 local = newVerts[index].xyz - origin; + newVerts[index].SetTexCoord( 0.5f + local * texVec[0], 0.5f + local * texVec[1] ); + } + } + } + + newTri->numIndexes = numIndexes; + + return R_FinishDeform( surf, newTri, newVerts, newIndexes ); +} + +/* +===================== +R_ParticleDeform + +Emit particles from the surface. +===================== +*/ +static drawSurf_t * R_ParticleDeform( drawSurf_t *surf, bool useArea ) { + const renderEntity_t * renderEntity = &surf->space->entityDef->parms; + const viewDef_t * viewDef = tr.viewDef; + const idDeclParticle * particleSystem = (const idDeclParticle *)surf->material->GetDeformDecl(); + const srfTriangles_t * srcTri = surf->frontEndGeo; + + if ( r_skipParticles.GetBool() ) { + return NULL; + } + + // + // calculate the area of all the triangles + // + int numSourceTris = surf->frontEndGeo->numIndexes / 3; + float totalArea = 0.0f; + float * sourceTriAreas = NULL; + + const idJointMat * joints = ( ( srcTri->staticModelWithJoints != NULL ) && r_useGPUSkinning.GetBool() ) ? srcTri->staticModelWithJoints->jointsInverted : NULL; + + if ( useArea ) { + sourceTriAreas = (float *)_alloca( sizeof( *sourceTriAreas ) * numSourceTris ); + int triNum = 0; + for ( int i = 0; i < srcTri->numIndexes; i += 3, triNum++ ) { + float area = idWinding::TriangleArea( idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[ srcTri->indexes[ i+0 ] ], joints ), + idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[ srcTri->indexes[ i+1 ] ], joints ), + idDrawVert::GetSkinnedDrawVertPosition( srcTri->verts[ srcTri->indexes[ i+2 ] ], joints ) ); + sourceTriAreas[triNum] = totalArea; + totalArea += area; + } + } + + // + // create the particles almost exactly the way idRenderModelPrt does + // + particleGen_t g; + + g.renderEnt = renderEntity; + g.renderView = &viewDef->renderView; + g.origin.Zero(); + g.axis = mat3_identity; + + int maxStageParticles[MAX_PARTICLE_STAGES] = { 0 }; + int maxStageQuads[MAX_PARTICLE_STAGES] = { 0 }; + int maxQuads = 0; + + for ( int stageNum = 0; stageNum < particleSystem->stages.Num(); stageNum++ ) { + idParticleStage *stage = particleSystem->stages[stageNum]; + + if ( stage->material == NULL ) { + continue; + } + if ( stage->cycleMsec == 0 ) { + continue; + } + if ( stage->hidden ) { // just for gui particle editor use + continue; + } + + // we interpret stage->totalParticles as "particles per map square area" + // so the systems look the same on different size surfaces + const int totalParticles = ( useArea ) ? idMath::Ftoi( stage->totalParticles * totalArea * ( 1.0f / 4096.0f ) ) : ( stage->totalParticles ); + const int numQuads = totalParticles * stage->NumQuadsPerParticle() * ( ( useArea ) ? 1 : numSourceTris ); + + maxStageParticles[stageNum] = totalParticles; + maxStageQuads[stageNum] = numQuads; + maxQuads = Max( maxQuads, numQuads ); + } + + if ( maxQuads == 0 ) { + return NULL; + } + + idTempArray tempVerts( ALIGN( maxQuads * 4 * sizeof( idDrawVert ), 16 ) ); + idDrawVert *newVerts = (idDrawVert *) tempVerts.Ptr(); + idTempArray tempIndex( ALIGN( maxQuads * 6 * sizeof( triIndex_t ), 16 ) ); + triIndex_t *newIndexes = (triIndex_t *) tempIndex.Ptr(); + + drawSurf_t * drawSurfList = NULL; + + for ( int stageNum = 0; stageNum < particleSystem->stages.Num(); stageNum++ ) { + if ( maxStageQuads[stageNum] == 0 ) { + continue; + } + + idParticleStage *stage = particleSystem->stages[stageNum]; + + int numVerts = 0; + for ( int currentTri = 0; currentTri < ( ( useArea ) ? 1 : numSourceTris ); currentTri++ ) { + + idRandom steppingRandom; + idRandom steppingRandom2; + + int stageAge = g.renderView->time[renderEntity->timeGroup] + idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_TIMEOFFSET] * 1000.0f - stage->timeOffset * 1000.0f ); + int stageCycle = stageAge / stage->cycleMsec; + + // some particles will be in this cycle, some will be in the previous cycle + steppingRandom.SetSeed( ( ( stageCycle << 10 ) & idRandom::MAX_RAND ) ^ idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); + steppingRandom2.SetSeed( ( ( ( stageCycle - 1 ) << 10 ) & idRandom::MAX_RAND ) ^ idMath::Ftoi( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); + + for ( int index = 0; index < maxStageParticles[stageNum]; index++ ) { + g.index = index; + + // bump the random + steppingRandom.RandomInt(); + steppingRandom2.RandomInt(); + + // calculate local age for this index + int bunchOffset = idMath::Ftoi( stage->particleLife * 1000 * stage->spawnBunching * index / maxStageParticles[stageNum] ); + + int particleAge = stageAge - bunchOffset; + int particleCycle = particleAge / stage->cycleMsec; + if ( particleCycle < 0 ) { + // before the particleSystem spawned + continue; + } + if ( stage->cycles != 0.0f && particleCycle >= stage->cycles ) { + // cycled systems will only run cycle times + continue; + } + + int inCycleTime = particleAge - particleCycle * stage->cycleMsec; + + if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] != 0.0f && + g.renderView->time[renderEntity->timeGroup] - inCycleTime >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] * 1000.0f ) { + // don't fire any more particles + continue; + } + + // supress particles before or after the age clamp + g.frac = (float)inCycleTime / ( stage->particleLife * 1000.0f ); + if ( g.frac < 0.0f ) { + // yet to be spawned + continue; + } + if ( g.frac > 1.0f ) { + // this particle is in the deadTime band + continue; + } + + if ( particleCycle == stageCycle ) { + g.random = steppingRandom; + } else { + g.random = steppingRandom2; + } + + //--------------- + // locate the particle origin and axis somewhere on the surface + //--------------- + + int pointTri = currentTri; + + if ( useArea ) { + // select a triangle based on an even area distribution + pointTri = idBinSearch_LessEqual( sourceTriAreas, numSourceTris, g.random.RandomFloat() * totalArea ); + } + + // now pick a random point inside pointTri + const idDrawVert v1 = idDrawVert::GetSkinnedDrawVert( srcTri->verts[ srcTri->indexes[ pointTri * 3 + 0 ] ], joints ); + const idDrawVert v2 = idDrawVert::GetSkinnedDrawVert( srcTri->verts[ srcTri->indexes[ pointTri * 3 + 1 ] ], joints ); + const idDrawVert v3 = idDrawVert::GetSkinnedDrawVert( srcTri->verts[ srcTri->indexes[ pointTri * 3 + 2 ] ], joints ); + + float f1 = g.random.RandomFloat(); + float f2 = g.random.RandomFloat(); + float f3 = g.random.RandomFloat(); + + float ft = 1.0f / ( f1 + f2 + f3 + 0.0001f ); + + f1 *= ft; + f2 *= ft; + f3 *= ft; + + g.origin = v1.xyz * f1 + v2.xyz * f2 + v3.xyz * f3; + g.axis[0] = v1.GetTangent() * f1 + v2.GetTangent() * f2 + v3.GetTangent() * f3; + g.axis[1] = v1.GetBiTangent() * f1 + v2.GetBiTangent() * f2 + v3.GetBiTangent() * f3; + g.axis[2] = v1.GetNormal() * f1 + v2.GetNormal() * f2 + v3.GetNormal() * f3; + + // this is needed so aimed particles can calculate origins at different times + g.originalRandom = g.random; + + g.age = g.frac * stage->particleLife; + + // if the particle doesn't get drawn because it is faded out or beyond a kill region, + // don't increment the verts + numVerts += stage->CreateParticle( &g, newVerts + numVerts ); + } + } + + if ( numVerts == 0 ) { + continue; + } + + // build the index list + int numIndexes = 0; + for ( int i = 0; i < numVerts; i += 4 ) { + newIndexes[numIndexes + 0] = i + 0; + newIndexes[numIndexes + 1] = i + 2; + newIndexes[numIndexes + 2] = i + 3; + newIndexes[numIndexes + 3] = i + 0; + newIndexes[numIndexes + 4] = i + 3; + newIndexes[numIndexes + 5] = i + 1; + numIndexes += 6; + } + + // allocate a srfTriangles in temp memory that can hold all the particles + srfTriangles_t * newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); + newTri->bounds = stage->bounds; // just always draw the particles + newTri->numVerts = numVerts; + newTri->numIndexes = numIndexes; + newTri->ambientCache = vertexCache.AllocVertex( newVerts, ALIGN( numVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + newTri->indexCache = vertexCache.AllocIndex( newIndexes, ALIGN( numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + + drawSurf_t * drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); + drawSurf->frontEndGeo = newTri; + drawSurf->numIndexes = newTri->numIndexes; + drawSurf->ambientCache = newTri->ambientCache; + drawSurf->indexCache = newTri->indexCache; + drawSurf->shadowCache = 0; + drawSurf->jointCache = 0; + drawSurf->space = surf->space; + drawSurf->scissorRect = surf->scissorRect; + drawSurf->extraGLState = 0; + drawSurf->renderZFail = 0; + + R_SetupDrawSurfShader( drawSurf, stage->material, renderEntity ); + + drawSurf->linkChain = NULL; + drawSurf->nextOnLight = drawSurfList; + drawSurfList = drawSurf; + } + + return drawSurfList; +} + +/* +================= +R_DeformDrawSurf +================= +*/ +drawSurf_t * R_DeformDrawSurf( drawSurf_t * drawSurf ) { + if ( drawSurf->material == NULL ) { + return NULL; + } + + if ( r_skipDeforms.GetBool() ) { + return drawSurf; + } + switch ( drawSurf->material->Deform() ) { + case DFRM_SPRITE: return R_AutospriteDeform( drawSurf ); + case DFRM_TUBE: return R_TubeDeform( drawSurf ); + case DFRM_FLARE: return R_FlareDeform( drawSurf ); + case DFRM_EXPAND: return R_ExpandDeform( drawSurf ); + case DFRM_MOVE: return R_MoveDeform( drawSurf ); + case DFRM_TURB: return R_TurbulentDeform( drawSurf ); + case DFRM_EYEBALL: return R_EyeballDeform( drawSurf ); + case DFRM_PARTICLE: return R_ParticleDeform( drawSurf, true ); + case DFRM_PARTICLE2: return R_ParticleDeform( drawSurf, false ); + default: return NULL; + } +} diff --git a/neo/renderer/tr_frontend_guisurf.cpp b/neo/renderer/tr_frontend_guisurf.cpp new file mode 100644 index 00000000..08697a7f --- /dev/null +++ b/neo/renderer/tr_frontend_guisurf.cpp @@ -0,0 +1,250 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +/* +========================================================================================== + +GUI SURFACES + +========================================================================================== +*/ + +/* +================ +R_SurfaceToTextureAxis + +Calculates two axis for the surface such that a point dotted against +the axis will give a 0.0 to 1.0 range in S and T when inside the gui surface +================ +*/ +void R_SurfaceToTextureAxis( const srfTriangles_t *tri, idVec3 &origin, idVec3 axis[3] ) { + // find the bounds of the texture + idVec2 boundsMin( 999999.0f, 999999.0f ); + idVec2 boundsMax( -999999.0f, -999999.0f ); + for ( int i = 0 ; i < tri->numVerts ; i++ ) { + const idVec2 uv = tri->verts[i].GetTexCoord(); + boundsMin.x = Min( uv.x, boundsMin.x ); + boundsMax.x = Max( uv.x, boundsMax.x ); + boundsMin.y = Min( uv.y, boundsMin.y ); + boundsMax.y = Max( uv.y, boundsMax.y ); + } + + // use the floor of the midpoint as the origin of the + // surface, which will prevent a slight misalignment + // from throwing it an entire cycle off + const idVec2 boundsOrg( floor( ( boundsMin.x + boundsMax.x ) * 0.5f ), floor( ( boundsMin.y + boundsMax.y ) * 0.5f ) ); + + // determine the world S and T vectors from the first drawSurf triangle + const idJointMat * joints = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? tri->staticModelWithJoints->jointsInverted : NULL; + + const idVec3 aXYZ = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[0] ], joints ); + const idVec3 bXYZ = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[1] ], joints ); + const idVec3 cXYZ = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[2] ], joints ); + + const idVec2 aST = tri->verts[ tri->indexes[0] ].GetTexCoord(); + const idVec2 bST = tri->verts[ tri->indexes[1] ].GetTexCoord(); + const idVec2 cST = tri->verts[ tri->indexes[2] ].GetTexCoord(); + + float d0[5]; + d0[0] = bXYZ[0] - aXYZ[0]; + d0[1] = bXYZ[1] - aXYZ[1]; + d0[2] = bXYZ[2] - aXYZ[2]; + d0[3] = bST.x - aST.x; + d0[4] = bST.y - aST.y; + + float d1[5]; + d1[0] = cXYZ[0] - aXYZ[0]; + d1[1] = cXYZ[1] - aXYZ[1]; + d1[2] = cXYZ[2] - aXYZ[2]; + d1[3] = cST.x - aST.x; + d1[4] = cST.y - aST.y; + + const float area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( area == 0.0f ) { + axis[0].Zero(); + axis[1].Zero(); + axis[2].Zero(); + return; // degenerate + } + const float inva = 1.0f / area; + + axis[0][0] = ( d0[0] * d1[4] - d0[4] * d1[0] ) * inva; + axis[0][1] = ( d0[1] * d1[4] - d0[4] * d1[1] ) * inva; + axis[0][2] = ( d0[2] * d1[4] - d0[4] * d1[2] ) * inva; + + axis[1][0] = ( d0[3] * d1[0] - d0[0] * d1[3] ) * inva; + axis[1][1] = ( d0[3] * d1[1] - d0[1] * d1[3] ) * inva; + axis[1][2] = ( d0[3] * d1[2] - d0[2] * d1[3] ) * inva; + + idPlane plane; + plane.FromPoints( aXYZ, bXYZ, cXYZ ); + axis[2][0] = plane[0]; + axis[2][1] = plane[1]; + axis[2][2] = plane[2]; + + // take point 0 and project the vectors to the texture origin + VectorMA( aXYZ, boundsOrg.x - aST.x, axis[0], origin ); + VectorMA( origin, boundsOrg.y - aST.y, axis[1], origin ); +} + +/* +================= +R_RenderGuiSurf + +Create a texture space on the given surface and +call the GUI generator to create quads for it. +================= +*/ +static void R_RenderGuiSurf( idUserInterface *gui, const drawSurf_t *drawSurf ) { + SCOPED_PROFILE_EVENT( "R_RenderGuiSurf" ); + + // for testing the performance hit + if ( r_skipGuiShaders.GetInteger() == 1 ) { + return; + } + + // don't allow an infinite recursion loop + if ( tr.guiRecursionLevel == 4 ) { + return; + } + + tr.pc.c_guiSurfs++; + + // create the new matrix to draw on this surface + idVec3 origin, axis[3]; + R_SurfaceToTextureAxis( drawSurf->frontEndGeo, origin, axis ); + + float guiModelMatrix[16]; + float modelMatrix[16]; + + guiModelMatrix[0*4+0] = axis[0][0] * ( 1.0f / 640.0f ); + guiModelMatrix[1*4+0] = axis[1][0] * ( 1.0f / 480.0f ); + guiModelMatrix[2*4+0] = axis[2][0]; + guiModelMatrix[3*4+0] = origin[0]; + + guiModelMatrix[0*4+1] = axis[0][1] * ( 1.0f / 640.0f ); + guiModelMatrix[1*4+1] = axis[1][1] * ( 1.0f / 480.0f ); + guiModelMatrix[2*4+1] = axis[2][1]; + guiModelMatrix[3*4+1] = origin[1]; + + guiModelMatrix[0*4+2] = axis[0][2] * ( 1.0f / 640.0f ); + guiModelMatrix[1*4+2] = axis[1][2] * ( 1.0f / 480.0f ); + guiModelMatrix[2*4+2] = axis[2][2]; + guiModelMatrix[3*4+2] = origin[2]; + + guiModelMatrix[0*4+3] = 0.0f; + guiModelMatrix[1*4+3] = 0.0f; + guiModelMatrix[2*4+3] = 0.0f; + guiModelMatrix[3*4+3] = 1.0f; + + R_MatrixMultiply( guiModelMatrix, drawSurf->space->modelMatrix, modelMatrix ); + + tr.guiRecursionLevel++; + + // call the gui, which will call the 2D drawing functions + tr.guiModel->Clear(); + gui->Redraw( tr.viewDef->renderView.time[0] ); + tr.guiModel->EmitToCurrentView( modelMatrix, drawSurf->space->weaponDepthHack ); + tr.guiModel->Clear(); + + tr.guiRecursionLevel--; +} + +/* +================ +R_AddInGameGuis +================ +*/ +void R_AddInGameGuis( const drawSurf_t * const drawSurfs[], const int numDrawSurfs ) { + SCOPED_PROFILE_EVENT( "R_AddInGameGuis" ); + + // check for gui surfaces + for ( int i = 0; i < numDrawSurfs; i++ ) { + const drawSurf_t * drawSurf = drawSurfs[i]; + + idUserInterface *gui = drawSurf->material->GlobalGui(); + + int guiNum = drawSurf->material->GetEntityGui() - 1; + if ( guiNum >= 0 && guiNum < MAX_RENDERENTITY_GUI ) { + if ( drawSurf->space->entityDef != NULL ) { + gui = drawSurf->space->entityDef->parms.gui[ guiNum ]; + } + } + + if ( gui == NULL ) { + continue; + } + + idBounds ndcBounds; + if ( !R_PreciseCullSurface( drawSurf, ndcBounds ) ) { + // did we ever use this to forward an entity color to a gui that didn't set color? + // memcpy( tr.guiShaderParms, shaderParms, sizeof( tr.guiShaderParms ) ); + R_RenderGuiSurf( gui, drawSurf ); + } + } +} + +/* +================, +R_ReloadGuis_f + +Reloads any guis that have had their file timestamps changed. +An optional "all" parameter will cause all models to reload, even +if they are not out of date. + +Should we also reload the map models? +================ +*/ +void R_ReloadGuis_f( const idCmdArgs &args ) { + bool all; + + if ( !idStr::Icmp( args.Argv(1), "all" ) ) { + all = true; + common->Printf( "Reloading all gui files...\n" ); + } else { + all = false; + common->Printf( "Checking for changed gui files...\n" ); + } + + uiManager->Reload( all ); +} + +/* +================, +R_ListGuis_f +================ +*/ +void R_ListGuis_f( const idCmdArgs &args ) { + uiManager->ListGuis(); +} diff --git a/neo/renderer/tr_frontend_main.cpp b/neo/renderer/tr_frontend_main.cpp new file mode 100644 index 00000000..39d82f02 --- /dev/null +++ b/neo/renderer/tr_frontend_main.cpp @@ -0,0 +1,440 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +========================================================================================== + +FRAME MEMORY ALLOCATION + +========================================================================================== +*/ + +static const unsigned int NUM_FRAME_DATA = 2; +static const unsigned int FRAME_ALLOC_ALIGNMENT = 128; +static const unsigned int MAX_FRAME_MEMORY = 64 * 1024 * 1024; // larger so that we can noclip on PC for dev purposes + +idFrameData smpFrameData[NUM_FRAME_DATA]; +idFrameData * frameData; +unsigned int smpFrame; + +//#define TRACK_FRAME_ALLOCS + +#if defined( TRACK_FRAME_ALLOCS ) +idSysInterlockedInteger frameAllocTypeCount[FRAME_ALLOC_MAX]; +int frameHighWaterTypeCount[FRAME_ALLOC_MAX]; +#endif + +/* +==================== +R_ToggleSmpFrame +==================== +*/ +void R_ToggleSmpFrame() { + // update the highwater mark + if ( frameData->frameMemoryAllocated.GetValue() > frameData->highWaterAllocated ) { + frameData->highWaterAllocated = frameData->frameMemoryAllocated.GetValue(); +#if defined( TRACK_FRAME_ALLOCS ) + frameData->highWaterUsed = frameData->frameMemoryUsed.GetValue(); + for ( int i = 0; i < FRAME_ALLOC_MAX; i++ ) { + frameHighWaterTypeCount[i] = frameAllocTypeCount[i].GetValue(); + } +#endif + } + + // switch to the next frame + smpFrame++; + frameData = &smpFrameData[smpFrame % NUM_FRAME_DATA]; + + // reset the memory allocation + const unsigned int bytesNeededForAlignment = FRAME_ALLOC_ALIGNMENT - ( (unsigned int)frameData->frameMemory & ( FRAME_ALLOC_ALIGNMENT - 1 ) ); + frameData->frameMemoryAllocated.SetValue( bytesNeededForAlignment ); + frameData->frameMemoryUsed.SetValue( 0 ); + +#if defined( TRACK_FRAME_ALLOCS ) + for ( int i = 0; i < FRAME_ALLOC_MAX; i++ ) { + frameAllocTypeCount[i].SetValue( 0 ); + } +#endif + + // clear the command chain and make a RC_NOP command the only thing on the list + frameData->cmdHead = frameData->cmdTail = (emptyCommand_t *)R_FrameAlloc( sizeof( *frameData->cmdHead ), FRAME_ALLOC_DRAW_COMMAND ); + frameData->cmdHead->commandId = RC_NOP; + frameData->cmdHead->next = NULL; +} + +/* +===================== +R_ShutdownFrameData +===================== +*/ +void R_ShutdownFrameData() { + frameData = NULL; + for ( int i = 0; i < NUM_FRAME_DATA; i++ ) { + Mem_Free16( smpFrameData[i].frameMemory ); + smpFrameData[i].frameMemory = NULL; + } +} + +/* +===================== +R_InitFrameData +===================== +*/ +void R_InitFrameData() { + R_ShutdownFrameData(); + + for ( int i = 0; i < NUM_FRAME_DATA; i++ ) { + smpFrameData[i].frameMemory = (byte *) Mem_Alloc16( MAX_FRAME_MEMORY, TAG_RENDER ); + } + + // must be set before calling R_ToggleSmpFrame() + frameData = &smpFrameData[ 0 ]; + + R_ToggleSmpFrame(); +} + +/* +================ +R_FrameAlloc + +This data will be automatically freed when the +current frame's back end completes. + +This should only be called by the front end. The +back end shouldn't need to allocate memory. + +All temporary data, like dynamic tesselations +and local spaces are allocated here. + +All memory is cache-line-cleared for the best performance. +================ +*/ +void *R_FrameAlloc( int bytes, frameAllocType_t type ) { +#if defined( TRACK_FRAME_ALLOCS ) + frameData->frameMemoryUsed.Add( bytes ); + frameAllocTypeCount[type].Add( bytes ); +#endif + + bytes = ( bytes + FRAME_ALLOC_ALIGNMENT - 1 ) & ~ ( FRAME_ALLOC_ALIGNMENT - 1 ); + + // thread safe add + int end = frameData->frameMemoryAllocated.Add( bytes ); + if ( end > MAX_FRAME_MEMORY ) { + idLib::Error( "R_FrameAlloc ran out of memory. bytes = %d, end = %d, highWaterAllocated = %d\n", bytes, end, frameData->highWaterAllocated ); + } + + byte * ptr = frameData->frameMemory + end - bytes; + + // cache line clear the memory + for ( int offset = 0; offset < bytes; offset += CACHE_LINE_SIZE ) { + ZeroCacheLine( ptr, offset ); + } + + return ptr; +} + +/* +================== +R_ClearedFrameAlloc +================== +*/ +void *R_ClearedFrameAlloc( int bytes, frameAllocType_t type ) { + // NOTE: every allocation is cache line cleared + return R_FrameAlloc( bytes, type ); +} + +/* +========================================================================================== + +FONT-END STATIC MEMORY ALLOCATION + +========================================================================================== +*/ + +/* +================= +R_StaticAlloc +================= +*/ +void *R_StaticAlloc( int bytes, const memTag_t tag ) { + tr.pc.c_alloc++; + + void * buf = Mem_Alloc( bytes, tag ); + + // don't exit on failure on zero length allocations since the old code didn't + if ( buf == NULL && bytes != 0 ) { + common->FatalError( "R_StaticAlloc failed on %i bytes", bytes ); + } + return buf; +} + +/* +================= +R_ClearedStaticAlloc +================= +*/ +void *R_ClearedStaticAlloc( int bytes ) { + void * buf = R_StaticAlloc( bytes ); + memset( buf, 0, bytes ); + return buf; +} + +/* +================= +R_StaticFree +================= +*/ +void R_StaticFree( void *data ) { + tr.pc.c_free++; + Mem_Free( data ); +} + +/* +========================================================================================== + +FONT-END RENDERING + +========================================================================================== +*/ + +/* +================= +R_SortDrawSurfs +================= +*/ +static void R_SortDrawSurfs( drawSurf_t ** drawSurfs, const int numDrawSurfs ) { +#if 1 + + uint64 * indices = (uint64 *) _alloca16( numDrawSurfs * sizeof( indices[0] ) ); + + // sort the draw surfs based on: + // 1. sort value (largest first) + // 2. depth (smallest first) + // 3. index (largest first) + assert( numDrawSurfs <= 0xFFFF ); + for ( int i = 0; i < numDrawSurfs; i++ ) { + float sort = SS_POST_PROCESS - drawSurfs[i]->sort; + assert( sort >= 0.0f ); + + uint64 dist = 0; + if ( drawSurfs[i]->frontEndGeo != NULL ) { + float min = 0.0f; + float max = 1.0f; + idRenderMatrix::DepthBoundsForBounds( min, max, drawSurfs[i]->space->mvp, drawSurfs[i]->frontEndGeo->bounds ); + dist = idMath::Ftoui16( min * 0xFFFF ); + } + + indices[i] = ( ( numDrawSurfs - i ) & 0xFFFF ) | ( dist << 16 ) | ( (uint64) ( *(uint32 *)&sort ) << 32 ); + } + + const int64 MAX_LEVELS = 128; + int64 lo[MAX_LEVELS]; + int64 hi[MAX_LEVELS]; + + // Keep the top of the stack in registers to avoid load-hit-stores. + register int64 st_lo = 0; + register int64 st_hi = numDrawSurfs - 1; + register int64 level = 0; + + for ( ; ; ) { + register int64 i = st_lo; + register int64 j = st_hi; + if ( j - i >= 4 && level < MAX_LEVELS - 1 ) { + register uint64 pivot = indices[( i + j ) / 2]; + do { + while ( indices[i] > pivot ) i++; + while ( indices[j] < pivot ) j--; + if ( i > j ) break; + uint64 h = indices[i]; indices[i] = indices[j]; indices[j] = h; + } while ( ++i <= --j ); + + // No need for these iterations because we are always sorting unique values. + //while ( indices[j] == pivot && st_lo < j ) j--; + //while ( indices[i] == pivot && i < st_hi ) i++; + + assert( level < MAX_LEVELS - 1 ); + lo[level] = i; + hi[level] = st_hi; + st_hi = j; + level++; + } else { + for( ; i < j; j-- ) { + register int64 m = i; + for ( int64 k = i + 1; k <= j; k++ ) { + if ( indices[k] < indices[m] ) { + m = k; + } + } + uint64 h = indices[m]; indices[m] = indices[j]; indices[j] = h; + } + if ( --level < 0 ) { + break; + } + st_lo = lo[level]; + st_hi = hi[level]; + } + } + + drawSurf_t ** newDrawSurfs = (drawSurf_t **) indices; + for ( int i = 0; i < numDrawSurfs; i++ ) { + newDrawSurfs[i] = drawSurfs[numDrawSurfs - ( indices[i] & 0xFFFF )]; + } + memcpy( drawSurfs, newDrawSurfs, numDrawSurfs * sizeof( drawSurfs[0] ) ); + +#else + + struct local_t { + static int R_QsortSurfaces( const void *a, const void *b ) { + const drawSurf_t * ea = *(drawSurf_t **)a; + const drawSurf_t * eb = *(drawSurf_t **)b; + if ( ea->sort < eb->sort ) { + return -1; + } + if ( ea->sort > eb->sort ) { + return 1; + } + return 0; + } + }; + + // Add a sort offset so surfaces with equal sort orders still deterministically + // draw in the order they were added, at least within a given model. + float sorfOffset = 0.0f; + for ( int i = 0; i < numDrawSurfs; i++ ) { + drawSurf[i]->sort += sorfOffset; + sorfOffset += 0.000001f; + } + + // sort the drawsurfs + qsort( drawSurfs, numDrawSurfs, sizeof( drawSurfs[0] ), local_t::R_QsortSurfaces ); + +#endif +} + +/* +================ +R_RenderView + +A view may be either the actual camera view, +a mirror / remote location, or a 3D view on a gui surface. + +Parms will typically be allocated with R_FrameAlloc +================ +*/ +void R_RenderView( viewDef_t *parms ) { + // save view in case we are a subview + viewDef_t * oldView = tr.viewDef; + + tr.viewDef = parms; + + // setup the matrix for world space to eye space + R_SetupViewMatrix( tr.viewDef ); + + // we need to set the projection matrix before doing + // portal-to-screen scissor calculations + R_SetupProjectionMatrix( tr.viewDef ); + + // setup render matrices for faster culling + idRenderMatrix::Transpose( *(idRenderMatrix *)tr.viewDef->projectionMatrix, tr.viewDef->projectionRenderMatrix ); + idRenderMatrix viewRenderMatrix; + idRenderMatrix::Transpose( *(idRenderMatrix *)tr.viewDef->worldSpace.modelViewMatrix, viewRenderMatrix ); + idRenderMatrix::Multiply( tr.viewDef->projectionRenderMatrix, viewRenderMatrix, tr.viewDef->worldSpace.mvp ); + + // the planes of the view frustum are needed for portal visibility culling + idRenderMatrix::GetFrustumPlanes( tr.viewDef->frustum, tr.viewDef->worldSpace.mvp, false, true ); + + // the DOOM 3 frustum planes point outside the frustum + for ( int i = 0; i < 6; i++ ) { + tr.viewDef->frustum[i] = - tr.viewDef->frustum[i]; + } + // remove the Z-near to avoid portals from being near clipped + tr.viewDef->frustum[4][3] -= r_znear.GetFloat(); + + // identify all the visible portal areas, and create view lights and view entities + // for all the the entityDefs and lightDefs that are in the visible portal areas + static_cast(parms->renderWorld)->FindViewLightsAndEntities(); + + // wait for any shadow volume jobs from the previous frame to finish + tr.frontEndJobList->Wait(); + + // make sure that interactions exist for all light / entity combinations that are visible + // add any pre-generated light shadows, and calculate the light shader values + R_AddLights(); + + // adds ambient surfaces and create any necessary interaction surfaces to add to the light lists + R_AddModels(); + + // build up the GUIs on world surfaces + R_AddInGameGuis( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs ); + + // any viewLight that didn't have visible surfaces can have it's shadows removed + R_OptimizeViewLightsList(); + + // sort all the ambient surfaces for translucency ordering + R_SortDrawSurfs( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs ); + + // generate any subviews (mirrors, cameras, etc) before adding this view + if ( R_GenerateSubViews( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs ) ) { + // if we are debugging subviews, allow the skipping of the main view draw + if ( r_subviewOnly.GetBool() ) { + return; + } + } + + // write everything needed to the demo file + if ( common->WriteDemo() ) { + static_cast(parms->renderWorld)->WriteVisibleDefs( tr.viewDef ); + } + + // add the rendering commands for this viewDef + R_AddDrawViewCmd( parms, false ); + + // restore view in case we are a subview + tr.viewDef = oldView; +} + +/* +================ +R_RenderPostProcess + +Because R_RenderView may be called by subviews we have to make sure the post process +pass happens after the active view and its subviews is done rendering. +================ +*/ +void R_RenderPostProcess( viewDef_t *parms ) { + viewDef_t * oldView = tr.viewDef; + + R_AddDrawPostProcess( parms ); + + tr.viewDef = oldView; +} diff --git a/neo/renderer/tr_frontend_subview.cpp b/neo/renderer/tr_frontend_subview.cpp new file mode 100644 index 00000000..ab1292dd --- /dev/null +++ b/neo/renderer/tr_frontend_subview.cpp @@ -0,0 +1,536 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +/* +========================================================================================== + +SUBVIEW SURFACES + +========================================================================================== +*/ + +struct orientation_t { + idVec3 origin; + idMat3 axis; +}; + +/* +================= +R_MirrorPoint +================= +*/ +static void R_MirrorPoint( const idVec3 in, orientation_t *surface, orientation_t *camera, idVec3 &out ) { + const idVec3 local = in - surface->origin; + + idVec3 transformed( 0.0f ); + for ( int i = 0; i < 3; i++ ) { + const float d = local * surface->axis[i]; + transformed += d * camera->axis[i]; + } + + out = transformed + camera->origin; +} + +/* +================= +R_MirrorVector +================= +*/ +static void R_MirrorVector( const idVec3 in, orientation_t *surface, orientation_t *camera, idVec3 &out ) { + out.Zero(); + for ( int i = 0; i < 3; i++ ) { + const float d = in * surface->axis[i]; + out += d * camera->axis[i]; + } +} + +/* +============= +R_PlaneForSurface + +Returns the plane for the first triangle in the surface +FIXME: check for degenerate triangle? +============= +*/ +static void R_PlaneForSurface( const srfTriangles_t *tri, idPlane &plane ) { + idDrawVert * v1 = tri->verts + tri->indexes[0]; + idDrawVert * v2 = tri->verts + tri->indexes[1]; + idDrawVert * v3 = tri->verts + tri->indexes[2]; + plane.FromPoints( v1->xyz, v2->xyz, v3->xyz ); +} + +/* +========================= +R_PreciseCullSurface + +Check the surface for visibility on a per-triangle basis +for cases when it is going to be VERY expensive to draw (subviews) + +If not culled, also returns the bounding box of the surface in +Normalized Device Coordinates, so it can be used to crop the scissor rect. + +OPTIMIZE: we could also take exact portal passing into consideration +========================= +*/ +bool R_PreciseCullSurface( const drawSurf_t *drawSurf, idBounds &ndcBounds ) { + const srfTriangles_t * tri = drawSurf->frontEndGeo; + + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + // get an exact bounds of the triangles for scissor cropping + ndcBounds.Clear(); + + const idJointMat * joints = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? tri->staticModelWithJoints->jointsInverted : NULL; + + for ( int i = 0; i < tri->numVerts; i++ ) { + const idVec3 vXYZ = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[i], joints ); + + idPlane eye, clip; + R_TransformModelToClip( vXYZ, drawSurf->space->modelViewMatrix, tr.viewDef->projectionMatrix, eye, clip ); + + unsigned int pointFlags = 0; + for ( int j = 0; j < 3; j++ ) { + if ( clip[j] >= clip[3] ) { + pointFlags |= ( 1 << (j*2+0) ); + } else if ( clip[j] <= -clip[3] ) { // FIXME: the D3D near clip plane is at zero instead of -1 + pointFlags |= ( 1 << (j*2+1) ); + } + } + + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd != 0 ) { + return true; + } + + // backface and frustum cull + idVec3 localViewOrigin; + R_GlobalPointToLocal( drawSurf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewOrigin ); + + for ( int i = 0; i < tri->numIndexes; i += 3 ) { + const idVec3 v1 = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[ i+0 ] ], joints ); + const idVec3 v2 = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[ i+1 ] ], joints ); + const idVec3 v3 = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[ i+2 ] ], joints ); + + // this is a hack, because R_GlobalPointToLocal doesn't work with the non-normalized + // axis that we get from the gui view transform. It doesn't hurt anything, because + // we know that all gui generated surfaces are front facing + if ( tr.guiRecursionLevel == 0 ) { + // we don't care that it isn't normalized, + // all we want is the sign + const idVec3 d1 = v2 - v1; + const idVec3 d2 = v3 - v1; + const idVec3 normal = d2.Cross( d1 ); + + const idVec3 dir = v1 - localViewOrigin; + + const float dot = normal * dir; + if ( dot >= 0.0f ) { + return true; + } + } + + // now find the exact screen bounds of the clipped triangle + idFixedWinding w; + w.SetNumPoints( 3 ); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, v1, w[0].ToVec3() ); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, v2, w[1].ToVec3() ); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, v3, w[2].ToVec3() ); + w[0].s = w[0].t = w[1].s = w[1].t = w[2].s = w[2].t = 0.0f; + + for ( int j = 0; j < 4; j++ ) { + if ( !w.ClipInPlace( -tr.viewDef->frustum[j], 0.1f ) ) { + break; + } + } + for ( int j = 0; j < w.GetNumPoints(); j++ ) { + idVec3 screen; + + R_GlobalToNormalizedDeviceCoordinates( w[j].ToVec3(), screen ); + ndcBounds.AddPoint( screen ); + } + } + + // if we don't enclose any area, return + if ( ndcBounds.IsCleared() ) { + return true; + } + + return false; +} + +/* +======================== +R_MirrorViewBySurface +======================== +*/ +static viewDef_t *R_MirrorViewBySurface( const drawSurf_t *drawSurf ) { + // copy the viewport size from the original + viewDef_t * parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); + *parms = *tr.viewDef; + parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons + + parms->isSubview = true; + parms->isMirror = true; + + // create plane axis for the portal we are seeing + idPlane originalPlane, plane; + R_PlaneForSurface( drawSurf->frontEndGeo, originalPlane ); + R_LocalPlaneToGlobal( drawSurf->space->modelMatrix, originalPlane, plane ); + + orientation_t surface; + surface.origin = plane.Normal() * -plane[3]; + surface.axis[0] = plane.Normal(); + surface.axis[0].NormalVectors( surface.axis[1], surface.axis[2] ); + surface.axis[2] = -surface.axis[2]; + + orientation_t camera; + camera.origin = surface.origin; + camera.axis[0] = -surface.axis[0]; + camera.axis[1] = surface.axis[1]; + camera.axis[2] = surface.axis[2]; + + // set the mirrored origin and axis + R_MirrorPoint( tr.viewDef->renderView.vieworg, &surface, &camera, parms->renderView.vieworg ); + + R_MirrorVector( tr.viewDef->renderView.viewaxis[0], &surface, &camera, parms->renderView.viewaxis[0] ); + R_MirrorVector( tr.viewDef->renderView.viewaxis[1], &surface, &camera, parms->renderView.viewaxis[1] ); + R_MirrorVector( tr.viewDef->renderView.viewaxis[2], &surface, &camera, parms->renderView.viewaxis[2] ); + + // make the view origin 16 units away from the center of the surface + const idVec3 center = ( drawSurf->frontEndGeo->bounds[0] + drawSurf->frontEndGeo->bounds[1] ) * 0.5f; + const idVec3 viewOrigin = center + ( originalPlane.Normal() * 16.0f ); + + R_LocalPointToGlobal( drawSurf->space->modelMatrix, viewOrigin, parms->initialViewAreaOrigin ); + + // set the mirror clip plane + parms->numClipPlanes = 1; + parms->clipPlanes[0] = -camera.axis[0]; + + parms->clipPlanes[0][3] = -( camera.origin * parms->clipPlanes[0].Normal() ); + + return parms; +} + +/* +======================== +R_XrayViewBySurface +======================== +*/ +static viewDef_t *R_XrayViewBySurface( const drawSurf_t *drawSurf ) { + // copy the viewport size from the original + viewDef_t * parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); + *parms = *tr.viewDef; + parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons + + parms->isSubview = true; + parms->isXraySubview = true; + + return parms; +} + +/* +=============== +R_RemoteRender +=============== +*/ +static void R_RemoteRender( const drawSurf_t *surf, textureStage_t *stage ) { + // remote views can be reused in a single frame + if ( stage->dynamicFrameCount == tr.frameCount ) { + return; + } + + // if the entity doesn't have a remoteRenderView, do nothing + if ( !surf->space->entityDef->parms.remoteRenderView ) { + return; + } + + int stageWidth = stage->width; + int stageHeight = stage->height; + + // copy the viewport size from the original + viewDef_t * parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); + *parms = *tr.viewDef; + + parms->renderView = *surf->space->entityDef->parms.remoteRenderView; + parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons + parms->initialViewAreaOrigin = parms->renderView.vieworg; + parms->isSubview = true; + parms->isMirror = false; + + + tr.CropRenderSize( stageWidth, stageHeight ); + + tr.GetCroppedViewport( &parms->viewport ); + + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->superView = tr.viewDef; + parms->subviewSurface = surf; + + // generate render commands for it + R_RenderView( parms ); + + // copy this rendering to the image + stage->dynamicFrameCount = tr.frameCount; + if ( stage->image == NULL ) { + stage->image = globalImages->scratchImage; + } + + tr.CaptureRenderToImage( stage->image->GetName(), true ); + tr.UnCrop(); +} + +/* +================= +R_MirrorRender +================= +*/ +void R_MirrorRender( const drawSurf_t *surf, textureStage_t *stage, idScreenRect scissor ) { + // remote views can be reused in a single frame + if ( stage->dynamicFrameCount == tr.frameCount ) { + return; + } + + // issue a new view command + viewDef_t * parms = R_MirrorViewBySurface( surf ); + if ( parms == NULL ) { + return; + } + + tr.CropRenderSize( stage->width, stage->height ); + + tr.GetCroppedViewport( &parms->viewport ); + + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->superView = tr.viewDef; + parms->subviewSurface = surf; + + // triangle culling order changes with mirroring + parms->isMirror = ( ( (int)parms->isMirror ^ (int)tr.viewDef->isMirror ) != 0 ); + + // generate render commands for it + R_RenderView( parms ); + + // copy this rendering to the image + stage->dynamicFrameCount = tr.frameCount; + stage->image = globalImages->scratchImage; + + tr.CaptureRenderToImage( stage->image->GetName() ); + tr.UnCrop(); +} + +/* +================= +R_XrayRender +================= +*/ +void R_XrayRender( const drawSurf_t *surf, textureStage_t *stage, idScreenRect scissor ) { + // remote views can be reused in a single frame + if ( stage->dynamicFrameCount == tr.frameCount ) { + return; + } + + // issue a new view command + viewDef_t * parms = R_XrayViewBySurface( surf ); + if ( parms == NULL ) { + return; + } + + int stageWidth = stage->width; + int stageHeight = stage->height; + + tr.CropRenderSize( stageWidth, stageHeight ); + + tr.GetCroppedViewport( &parms->viewport ); + + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->superView = tr.viewDef; + parms->subviewSurface = surf; + + // triangle culling order changes with mirroring + parms->isMirror = ( ( (int)parms->isMirror ^ (int)tr.viewDef->isMirror ) != 0 ); + + // generate render commands for it + R_RenderView( parms ); + + // copy this rendering to the image + stage->dynamicFrameCount = tr.frameCount; + stage->image = globalImages->scratchImage2; + + tr.CaptureRenderToImage( stage->image->GetName(), true ); + tr.UnCrop(); +} + +/* +================== +R_GenerateSurfaceSubview +================== +*/ +bool R_GenerateSurfaceSubview( const drawSurf_t *drawSurf ) { + // for testing the performance hit + if ( r_skipSubviews.GetBool() ) { + return false; + } + + idBounds ndcBounds; + if ( R_PreciseCullSurface( drawSurf, ndcBounds ) ) { + return false; + } + + const idMaterial * shader = drawSurf->material; + + // never recurse through a subview surface that we are + // already seeing through + viewDef_t * parms = NULL; + for ( parms = tr.viewDef; parms != NULL; parms = parms->superView ) { + if ( parms->subviewSurface != NULL + && parms->subviewSurface->frontEndGeo == drawSurf->frontEndGeo + && parms->subviewSurface->space->entityDef == drawSurf->space->entityDef ) { + break; + } + } + if ( parms ) { + return false; + } + + // crop the scissor bounds based on the precise cull + assert( tr.viewDef != NULL ); + idScreenRect * v = &tr.viewDef->viewport; + idScreenRect scissor; + scissor.x1 = v->x1 + idMath::Ftoi( ( v->x2 - v->x1 + 1 ) * 0.5f * ( ndcBounds[0][0] + 1.0f ) ); + scissor.y1 = v->y1 + idMath::Ftoi( ( v->y2 - v->y1 + 1 ) * 0.5f * ( ndcBounds[0][1] + 1.0f ) ); + scissor.x2 = v->x1 + idMath::Ftoi( ( v->x2 - v->x1 + 1 ) * 0.5f * ( ndcBounds[1][0] + 1.0f ) ); + scissor.y2 = v->y1 + idMath::Ftoi( ( v->y2 - v->y1 + 1 ) * 0.5f * ( ndcBounds[1][1] + 1.0f ) ); + + // nudge a bit for safety + scissor.Expand(); + + scissor.Intersect( tr.viewDef->scissor ); + + if ( scissor.IsEmpty() ) { + // cropped out + return false; + } + + // see what kind of subview we are making + if ( shader->GetSort() != SS_SUBVIEW ) { + for ( int i = 0; i < shader->GetNumStages(); i++ ) { + const shaderStage_t *stage = shader->GetStage( i ); + switch ( stage->texture.dynamic ) { + case DI_REMOTE_RENDER: + R_RemoteRender( drawSurf, const_cast(&stage->texture) ); + break; + case DI_MIRROR_RENDER: + R_MirrorRender( drawSurf, const_cast(&stage->texture), scissor ); + break; + case DI_XRAY_RENDER: + R_XrayRender( drawSurf, const_cast(&stage->texture), scissor ); + break; + } + } + return true; + } + + // issue a new view command + parms = R_MirrorViewBySurface( drawSurf ); + if ( parms == NULL ) { + return false; + } + + parms->scissor = scissor; + parms->superView = tr.viewDef; + parms->subviewSurface = drawSurf; + + // triangle culling order changes with mirroring + parms->isMirror = ( ( (int)parms->isMirror ^ (int)tr.viewDef->isMirror ) != 0 ); + + // generate render commands for it + R_RenderView( parms ); + + return true; +} + +/* +================ +R_GenerateSubViews + +If we need to render another view to complete the current view, +generate it first. + +It is important to do this after all drawSurfs for the current +view have been generated, because it may create a subview which +would change tr.viewCount. +================ +*/ +bool R_GenerateSubViews( const drawSurf_t * const drawSurfs[], const int numDrawSurfs ) { + SCOPED_PROFILE_EVENT( "R_GenerateSubViews" ); + + // for testing the performance hit + if ( r_skipSubviews.GetBool() ) { + return false; + } + + // scan the surfaces until we either find a subview, or determine + // there are no more subview surfaces. + bool subviews = false; + for ( int i = 0; i < numDrawSurfs; i++ ) { + const drawSurf_t * drawSurf = drawSurfs[i]; + + if ( !drawSurf->material->HasSubview() ) { + continue; + } + + if ( R_GenerateSurfaceSubview( drawSurf ) ) { + subviews = true; + } + } + + return subviews; +} diff --git a/neo/renderer/tr_local.h b/neo/renderer/tr_local.h new file mode 100644 index 00000000..78100980 --- /dev/null +++ b/neo/renderer/tr_local.h @@ -0,0 +1,1366 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __TR_LOCAL_H__ +#define __TR_LOCAL_H__ + +#include "../idlib/precompiled.h" + +#include "GLState.h" +#include "ScreenRect.h" +#include "ImageOpts.h" +#include "Image.h" +#include "RenderTexture.h" +#include "Font.h" + +// everything that is needed by the backend needs +// to be double buffered to allow it to run in +// parallel on a dual cpu machine +const int SMP_FRAMES = 1; + +// maximum texture units +const int MAX_PROG_TEXTURE_PARMS = 16; + +const int FALLOFF_TEXTURE_SIZE = 64; + +const float DEFAULT_FOG_DISTANCE = 500.0f; + +// picky to get the bilerp correct at terminator +const int FOG_ENTER_SIZE = 64; +const float FOG_ENTER = (FOG_ENTER_SIZE+1.0f)/(FOG_ENTER_SIZE*2); + +enum demoCommand_t { + DC_BAD, + DC_RENDERVIEW, + DC_UPDATE_ENTITYDEF, + DC_DELETE_ENTITYDEF, + DC_UPDATE_LIGHTDEF, + DC_DELETE_LIGHTDEF, + DC_LOADMAP, + DC_CROP_RENDER, + DC_UNCROP_RENDER, + DC_CAPTURE_RENDER, + DC_END_FRAME, + DC_DEFINE_MODEL, + DC_SET_PORTAL_STATE, + DC_UPDATE_SOUNDOCCLUSION, + DC_GUI_MODEL +}; + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +#include "ModelDecal.h" +#include "ModelOverlay.h" +#include "Interaction.h" + +class idRenderWorldLocal; +struct viewEntity_t; +struct viewLight_t; + +// drawSurf_t structures command the back end to render surfaces +// a given srfTriangles_t may be used with multiple viewEntity_t, +// as when viewed in a subview or multiple viewport render, or +// with multiple shaders when skinned, or, possibly with multiple +// lights, although currently each lighting interaction creates +// unique srfTriangles_t +// drawSurf_t are always allocated and freed every frame, they are never cached + +struct drawSurf_t { + const srfTriangles_t * frontEndGeo; // don't use on the back end, it may be updated by the front end! + int numIndexes; + vertCacheHandle_t indexCache; // triIndex_t + vertCacheHandle_t ambientCache; // idDrawVert + vertCacheHandle_t shadowCache; // idShadowVert / idShadowVertSkinned + vertCacheHandle_t jointCache; // idJointMat + const viewEntity_t * space; + const idMaterial * material; // may be NULL for shadow volumes + uint64 extraGLState; // Extra GL state |'d with material->stage[].drawStateBits + float sort; // material->sort, modified by gui / entity sort offsets + const float * shaderRegisters; // evaluated and adjusted for referenceShaders + drawSurf_t * nextOnLight; // viewLight chains + drawSurf_t ** linkChain; // defer linking to lights to a serial section to avoid a mutex + idScreenRect scissorRect; // for scissor clipping, local inside renderView viewport + int renderZFail; + volatile shadowVolumeState_t shadowVolumeState; +}; + +// areas have references to hold all the lights and entities in them +struct areaReference_t { + areaReference_t * areaNext; // chain in the area + areaReference_t * areaPrev; + areaReference_t * ownerNext; // chain on either the entityDef or lightDef + idRenderEntityLocal * entity; // only one of entity / light will be non-NULL + idRenderLightLocal * light; // only one of entity / light will be non-NULL + struct portalArea_s * area; // so owners can find all the areas they are in +}; + + +// idRenderLight should become the new public interface replacing the qhandle_t to light defs in the idRenderWorld interface +class idRenderLight { +public: + virtual ~idRenderLight() {} + + virtual void FreeRenderLight() = 0; + virtual void UpdateRenderLight( const renderLight_t *re, bool forceUpdate = false ) = 0; + virtual void GetRenderLight( renderLight_t *re ) = 0; + virtual void ForceUpdate() = 0; + virtual int GetIndex() = 0; +}; + + +// idRenderEntity should become the new public interface replacing the qhandle_t to entity defs in the idRenderWorld interface +class idRenderEntity { +public: + virtual ~idRenderEntity() {} + + virtual void FreeRenderEntity() = 0; + virtual void UpdateRenderEntity( const renderEntity_t *re, bool forceUpdate = false ) = 0; + virtual void GetRenderEntity( renderEntity_t *re ) = 0; + virtual void ForceUpdate() = 0; + virtual int GetIndex() = 0; + + // overlays are extra polygons that deform with animating models for blood and damage marks + virtual void ProjectOverlay( const idPlane localTextureAxis[2], const idMaterial *material ) = 0; + virtual void RemoveDecals() = 0; +}; + + +class idRenderLightLocal : public idRenderLight { +public: + idRenderLightLocal(); + + virtual void FreeRenderLight(); + virtual void UpdateRenderLight( const renderLight_t *re, bool forceUpdate = false ); + virtual void GetRenderLight( renderLight_t *re ); + virtual void ForceUpdate(); + virtual int GetIndex(); + + bool LightCastsShadows() const { return parms.forceShadows || ( !parms.noShadows && lightShader->LightCastsShadows() ); } + + renderLight_t parms; // specification + + bool lightHasMoved; // the light has changed its position since it was + // first added, so the prelight model is not valid + idRenderWorldLocal * world; + int index; // in world lightdefs + + int areaNum; // if not -1, we may be able to cull all the light's + // interactions if !viewDef->connectedAreas[areaNum] + + int lastModifiedFrameNum; // to determine if it is constantly changing, + // and should go in the dynamic frame memory, or kept + // in the cached memory + bool archived; // for demo writing + + + // derived information + idPlane lightProject[4]; // old style light projection where Z and W are flipped and projected lights lightProject[3] is divided by ( zNear + zFar ) + idRenderMatrix baseLightProject; // global xyz1 to projected light strq + idRenderMatrix inverseBaseLightProject;// transforms the zero-to-one cube to exactly cover the light in world space + + const idMaterial * lightShader; // guaranteed to be valid, even if parms.shader isn't + idImage * falloffImage; + + idVec3 globalLightOrigin; // accounting for lightCenter and parallel + idBounds globalLightBounds; + + int viewCount; // if == tr.viewCount, the light is on the viewDef->viewLights list + viewLight_t * viewLight; + + areaReference_t * references; // each area the light is present in will have a lightRef + idInteraction * firstInteraction; // doubly linked list + idInteraction * lastInteraction; + + struct doublePortal_s * foggedPortals; +}; + + +class idRenderEntityLocal : public idRenderEntity { +public: + idRenderEntityLocal(); + + virtual void FreeRenderEntity(); + virtual void UpdateRenderEntity( const renderEntity_t *re, bool forceUpdate = false ); + virtual void GetRenderEntity( renderEntity_t *re ); + virtual void ForceUpdate(); + virtual int GetIndex(); + + // overlays are extra polygons that deform with animating models for blood and damage marks + virtual void ProjectOverlay( const idPlane localTextureAxis[2], const idMaterial *material ); + virtual void RemoveDecals(); + + bool IsDirectlyVisible() const; + + renderEntity_t parms; + + float modelMatrix[16]; // this is just a rearrangement of parms.axis and parms.origin + idRenderMatrix modelRenderMatrix; + idRenderMatrix inverseBaseModelProject;// transforms the unit cube to exactly cover the model in world space + + idRenderWorldLocal * world; + int index; // in world entityDefs + + int lastModifiedFrameNum; // to determine if it is constantly changing, + // and should go in the dynamic frame memory, or kept + // in the cached memory + bool archived; // for demo writing + + idRenderModel * dynamicModel; // if parms.model->IsDynamicModel(), this is the generated data + int dynamicModelFrameCount; // continuously animating dynamic models will recreate + // dynamicModel if this doesn't == tr.viewCount + idRenderModel * cachedDynamicModel; + + + // the local bounds used to place entityRefs, either from parms for dynamic entities, or a model bounds + idBounds localReferenceBounds; + + // axis aligned bounding box in world space, derived from refernceBounds and + // modelMatrix in R_CreateEntityRefs() + idBounds globalReferenceBounds; + + // a viewEntity_t is created whenever a idRenderEntityLocal is considered for inclusion + // in a given view, even if it turns out to not be visible + int viewCount; // if tr.viewCount == viewCount, viewEntity is valid, + // but the entity may still be off screen + viewEntity_t * viewEntity; // in frame temporary memory + + idRenderModelDecal * decals; // decals that have been projected on this model + idRenderModelOverlay * overlays; // blood overlays on animated models + + areaReference_t * entityRefs; // chain of all references + idInteraction * firstInteraction; // doubly linked list + idInteraction * lastInteraction; + + bool needsPortalSky; +}; + +struct shadowOnlyEntity_t { + shadowOnlyEntity_t * next; + idRenderEntityLocal * edef; +}; + +// viewLights are allocated on the frame temporary stack memory +// a viewLight contains everything that the back end needs out of an idRenderLightLocal, +// which the front end may be modifying simultaniously if running in SMP mode. +// a viewLight may exist even without any surfaces, and may be relevent for fogging, +// but should never exist if its volume does not intersect the view frustum +struct viewLight_t { + viewLight_t * next; + + // back end should NOT reference the lightDef, because it can change when running SMP + idRenderLightLocal * lightDef; + + // for scissor clipping, local inside renderView viewport + // scissorRect.Empty() is true if the viewEntity_t was never actually + // seen through any portals + idScreenRect scissorRect; + + // R_AddSingleLight() determined that the light isn't actually needed + bool removeFromList; + + // R_AddSingleLight builds this list of entities that need to be added + // to the viewEntities list because they potentially cast shadows into + // the view, even though the aren't directly visible + shadowOnlyEntity_t * shadowOnlyViewEntities; + + enum interactionState_t { + INTERACTION_UNCHECKED, + INTERACTION_NO, + INTERACTION_YES + }; + byte * entityInteractionState; // [numEntities] + + idVec3 globalLightOrigin; // global light origin used by backend + idPlane lightProject[4]; // light project used by backend + idPlane fogPlane; // fog plane for backend fog volume rendering + idRenderMatrix inverseBaseLightProject; // the matrix for deforming the 'zeroOneCubeModel' to exactly cover the light volume in world space + const idMaterial * lightShader; // light shader used by backend + const float * shaderRegisters; // shader registers used by backend + idImage * falloffImage; // falloff image used by backend + + drawSurf_t * globalShadows; // shadow everything + drawSurf_t * localInteractions; // don't get local shadows + drawSurf_t * localShadows; // don't shadow local surfaces + drawSurf_t * globalInteractions; // get shadows from everything + drawSurf_t * translucentInteractions; // translucent interactions don't get shadows + + // R_AddSingleLight will build a chain of parameters here to setup shadow volumes + preLightShadowVolumeParms_t * preLightShadowVolumes; +}; + +// a viewEntity is created whenever a idRenderEntityLocal is considered for inclusion +// in the current view, but it may still turn out to be culled. +// viewEntity are allocated on the frame temporary stack memory +// a viewEntity contains everything that the back end needs out of a idRenderEntityLocal, +// which the front end may be modifying simultaneously if running in SMP mode. +// A single entityDef can generate multiple viewEntity_t in a single frame, as when seen in a mirror +struct viewEntity_t { + viewEntity_t * next; + + // back end should NOT reference the entityDef, because it can change when running SMP + idRenderEntityLocal * entityDef; + + // for scissor clipping, local inside renderView viewport + // scissorRect.Empty() is true if the viewEntity_t was never actually + // seen through any portals, but was created for shadow casting. + // a viewEntity can have a non-empty scissorRect, meaning that an area + // that it is in is visible, and still not be visible. + idScreenRect scissorRect; + + bool isGuiSurface; // force two sided and vertex colors regardless of material setting + + bool skipMotionBlur; + + bool weaponDepthHack; + float modelDepthHack; + + float modelMatrix[16]; // local coords to global coords + float modelViewMatrix[16]; // local coords to eye coords + + idRenderMatrix mvp; + + // parallelAddModels will build a chain of surfaces here that will need to + // be linked to the lights or added to the drawsurf list in a serial code section + drawSurf_t * drawSurfs; + + // R_AddSingleModel will build a chain of parameters here to setup shadow volumes + staticShadowVolumeParms_t * staticShadowVolumes; + dynamicShadowVolumeParms_t * dynamicShadowVolumes; +}; + + +const int MAX_CLIP_PLANES = 1; // we may expand this to six for some subview issues + +// viewDefs are allocated on the frame temporary stack memory +struct viewDef_t { + // specified in the call to DrawScene() + renderView_t renderView; + + float projectionMatrix[16]; + idRenderMatrix projectionRenderMatrix; // tech5 version of projectionMatrix + viewEntity_t worldSpace; + + idRenderWorldLocal *renderWorld; + + idVec3 initialViewAreaOrigin; + // Used to find the portalArea that view flooding will take place from. + // for a normal view, the initialViewOrigin will be renderView.viewOrg, + // but a mirror may put the projection origin outside + // of any valid area, or in an unconnected area of the map, so the view + // area must be based on a point just off the surface of the mirror / subview. + // It may be possible to get a failed portal pass if the plane of the + // mirror intersects a portal, and the initialViewAreaOrigin is on + // a different side than the renderView.viewOrg is. + + bool isSubview; // true if this view is not the main view + bool isMirror; // the portal is a mirror, invert the face culling + bool isXraySubview; + + bool isEditor; + bool is2Dgui; + + int numClipPlanes; // mirrors will often use a single clip plane + idPlane clipPlanes[MAX_CLIP_PLANES]; // in world space, the positive side + // of the plane is the visible side + idScreenRect viewport; // in real pixels and proper Y flip + + idScreenRect scissor; + // for scissor clipping, local inside renderView viewport + // subviews may only be rendering part of the main view + // these are real physical pixel values, possibly scaled and offset from the + // renderView x/y/width/height + + viewDef_t * superView; // never go into an infinite subview loop + const drawSurf_t * subviewSurface; + + // drawSurfs are the visible surfaces of the viewEntities, sorted + // by the material sort parameter + drawSurf_t ** drawSurfs; // we don't use an idList for this, because + int numDrawSurfs; // it is allocated in frame temporary memory + int maxDrawSurfs; // may be resized + + viewLight_t * viewLights; // chain of all viewLights effecting view + viewEntity_t * viewEntitys; // chain of all viewEntities effecting view, including off screen ones casting shadows + // we use viewEntities as a check to see if a given view consists solely + // of 2D rendering, which we can optimize in certain ways. A 2D view will + // not have any viewEntities + + idPlane frustum[6]; // positive sides face outward, [4] is the front clip plane + + int areaNum; // -1 = not in a valid area + + // An array in frame temporary memory that lists if an area can be reached without + // crossing a closed door. This is used to avoid drawing interactions + // when the light is behind a closed door. + bool * connectedAreas; +}; + + +// complex light / surface interactions are broken up into multiple passes of a +// simple interaction shader +struct drawInteraction_t { + const drawSurf_t * surf; + + idImage * bumpImage; + idImage * diffuseImage; + idImage * specularImage; + + idVec4 diffuseColor; // may have a light color baked into it + idVec4 specularColor; // may have a light color baked into it + stageVertexColor_t vertexColor; // applies to both diffuse and specular + + int ambientLight; // use tr.ambientNormalMap instead of normalization cube map + + // these are loaded into the vertex program + idVec4 bumpMatrix[2]; + idVec4 diffuseMatrix[2]; + idVec4 specularMatrix[2]; +}; + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +TR_CMDS + +============================================================= +*/ + +enum renderCommand_t { + RC_NOP, + RC_DRAW_VIEW_3D, // may be at a reduced resolution, will be upsampled before 2D GUIs + RC_DRAW_VIEW_GUI, // not resolution scaled + RC_SET_BUFFER, + RC_COPY_RENDER, + RC_POST_PROCESS, +}; + +struct emptyCommand_t { + renderCommand_t commandId; + renderCommand_t * next; +}; + +struct setBufferCommand_t { + renderCommand_t commandId; + renderCommand_t * next; + GLenum buffer; +}; + +struct drawSurfsCommand_t { + renderCommand_t commandId; + renderCommand_t * next; + viewDef_t * viewDef; +}; + +struct copyRenderCommand_t { + renderCommand_t commandId; + renderCommand_t * next; + int x; + int y; + int imageWidth; + int imageHeight; + idImage * image; + int cubeFace; // when copying to a cubeMap + bool clearColorAfterCopy; +}; + +struct postProcessCommand_t { + renderCommand_t commandId; + renderCommand_t * next; + viewDef_t * viewDef; +}; + +//======================================================================= + +// this is the inital allocation for max number of drawsurfs +// in a given view, but it will automatically grow if needed +const int INITIAL_DRAWSURFS = 2048; + +enum frameAllocType_t { + FRAME_ALLOC_VIEW_DEF, + FRAME_ALLOC_VIEW_ENTITY, + FRAME_ALLOC_VIEW_LIGHT, + FRAME_ALLOC_SURFACE_TRIANGLES, + FRAME_ALLOC_DRAW_SURFACE, + FRAME_ALLOC_INTERACTION_STATE, + FRAME_ALLOC_SHADOW_ONLY_ENTITY, + FRAME_ALLOC_SHADOW_VOLUME_PARMS, + FRAME_ALLOC_SHADER_REGISTER, + FRAME_ALLOC_DRAW_SURFACE_POINTER, + FRAME_ALLOC_DRAW_COMMAND, + FRAME_ALLOC_UNKNOWN, + FRAME_ALLOC_MAX +}; + +// all of the information needed by the back end must be +// contained in a idFrameData. This entire structure is +// duplicated so the front and back end can run in parallel +// on an SMP machine. +class idFrameData { +public: + idSysInterlockedInteger frameMemoryAllocated; + idSysInterlockedInteger frameMemoryUsed; + byte * frameMemory; + + int highWaterAllocated; // max used on any frame + int highWaterUsed; + + // the currently building command list commands can be inserted + // at the front if needed, as required for dynamically generated textures + emptyCommand_t * cmdHead; // may be of other command type based on commandId + emptyCommand_t * cmdTail; +}; + +extern idFrameData *frameData; + +//======================================================================= + +void R_AddDrawViewCmd( viewDef_t *parms, bool guiOnly ); +void R_AddDrawPostProcess( viewDef_t *parms ); + +void R_ReloadGuis_f( const idCmdArgs &args ); +void R_ListGuis_f( const idCmdArgs &args ); + +void *R_GetCommandBuffer( int bytes ); + +// this allows a global override of all materials +bool R_GlobalShaderOverride( const idMaterial **shader ); + +// this does various checks before calling the idDeclSkin +const idMaterial *R_RemapShaderBySkin( const idMaterial *shader, const idDeclSkin *customSkin, const idMaterial *customShader ); + + +//==================================================== + + +/* +** performanceCounters_t +*/ +struct performanceCounters_t { + int c_box_cull_in; + int c_box_cull_out; + int c_createInteractions; // number of calls to idInteraction::CreateInteraction + int c_createShadowVolumes; + int c_generateMd5; + int c_entityDefCallbacks; + int c_alloc; // counts for R_StaticAllc/R_StaticFree + int c_free; + int c_visibleViewEntities; + int c_shadowViewEntities; + int c_viewLights; + int c_numViews; // number of total views rendered + int c_deformedSurfaces; // idMD5Mesh::GenerateSurface + int c_deformedVerts; // idMD5Mesh::GenerateSurface + int c_deformedIndexes; // idMD5Mesh::GenerateSurface + int c_tangentIndexes; // R_DeriveTangents() + int c_entityUpdates; + int c_lightUpdates; + int c_entityReferences; + int c_lightReferences; + int c_guiSurfs; + int frontEndMicroSec; // sum of time in all RE_RenderScene's in a frame +}; + + +struct tmu_t { + unsigned int current2DMap; + unsigned int currentCubeMap; +}; + + +const int MAX_MULTITEXTURE_UNITS = 8; + +enum vertexLayoutType_t { + LAYOUT_UNKNOWN = 0, + LAYOUT_DRAW_VERT, + LAYOUT_DRAW_SHADOW_VERT, + LAYOUT_DRAW_SHADOW_VERT_SKINNED +}; + +struct glstate_t { + tmu_t tmu[MAX_MULTITEXTURE_UNITS]; + + int currenttmu; + + int faceCulling; + + vertexLayoutType_t vertexLayout; + unsigned int currentVertexBuffer; + unsigned int currentIndexBuffer; + + float polyOfsScale; + float polyOfsBias; + + uint64 glStateBits; +}; + +struct backEndCounters_t { + int c_surfaces; + int c_shaders; + + int c_drawElements; + int c_drawIndexes; + + int c_shadowElements; + int c_shadowIndexes; + + int c_copyFrameBuffer; + + float c_overDraw; + + int totalMicroSec; // total microseconds for backend run + int shadowMicroSec; +}; + +// all state modified by the back end is separated +// from the front end state +struct backEndState_t { + const viewDef_t * viewDef; + backEndCounters_t pc; + + const viewEntity_t *currentSpace; // for detecting when a matrix must change + idScreenRect currentScissor; // for scissor clipping, local inside renderView viewport + glstate_t glState; // for OpenGL state deltas + + bool currentRenderCopied; // true if any material has already referenced _currentRender + + idRenderMatrix prevMVP[2]; // world MVP from previous frame for motion blur, per-eye + + // surfaces used for code-based drawing + drawSurf_t unitSquareSurface; + drawSurf_t zeroOneCubeSurface; + drawSurf_t testImageSurface; +}; + +class idParallelJobList; + +const int MAX_GUI_SURFACES = 1024; // default size of the drawSurfs list for guis, will + // be automatically expanded as needed + +static const int MAX_RENDER_CROPS = 8; + +/* +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +class idRenderSystemLocal : public idRenderSystem { +public: + // external functions + virtual void Init(); + virtual void Shutdown(); + virtual void ResetGuiModels(); + virtual void InitOpenGL(); + virtual void ShutdownOpenGL(); + virtual bool IsOpenGLRunning() const; + virtual bool IsFullScreen() const; + virtual stereo3DMode_t GetStereo3DMode() const; + virtual bool HasQuadBufferSupport() const; + virtual bool IsStereoScopicRenderingSupported() const; + virtual stereo3DMode_t GetStereoScopicRenderingMode() const; + virtual void EnableStereoScopicRendering( const stereo3DMode_t mode ) const; + virtual int GetWidth() const; + virtual int GetHeight() const; + virtual float GetPixelAspect() const; + virtual float GetPhysicalScreenWidthInCentimeters() const; + virtual idRenderWorld * AllocRenderWorld(); + virtual void FreeRenderWorld( idRenderWorld *rw ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad(); + virtual void LoadLevelImages(); + virtual void Preload( const idPreloadManifest &manifest, const char *mapName ); + virtual void BeginAutomaticBackgroundSwaps( autoRenderIconType_t icon = AUTORENDER_DEFAULTICON ); + virtual void EndAutomaticBackgroundSwaps(); + virtual bool AreAutomaticBackgroundSwapsRunning( autoRenderIconType_t * usingAlternateIcon = NULL ) const; + + virtual idFont * RegisterFont( const char * fontName ); + virtual void ResetFonts(); + virtual void PrintMemInfo( MemInfo_t *mi ); + + virtual void SetColor( const idVec4 & color ); + virtual uint32 GetColor(); + virtual void SetGLState( const uint64 glState ) ; + virtual void DrawFilled( const idVec4 & color, float x, float y, float w, float h ); + virtual void DrawStretchPic ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ); + virtual void DrawStretchPic( const idVec4 & topLeft, const idVec4 & topRight, const idVec4 & bottomRight, const idVec4 & bottomLeft, const idMaterial * material ); + virtual void DrawStretchTri ( const idVec2 & p1, const idVec2 & p2, const idVec2 & p3, const idVec2 & t1, const idVec2 & t2, const idVec2 & t3, const idMaterial *material ); + virtual idDrawVert * AllocTris( int numVerts, const triIndex_t * indexes, int numIndexes, const idMaterial * material, const stereoDepthType_t stereoType = STEREO_DEPTH_TYPE_NONE ); + virtual void DrawSmallChar( int x, int y, int ch ); + virtual void DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor ); + virtual void DrawBigChar( int x, int y, int ch ); + virtual void DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor ); + + virtual void WriteDemoPics(); + virtual void DrawDemoPics(); + virtual const emptyCommand_t * SwapCommandBuffers( uint64 *frontEndMicroSec, uint64 *backEndMicroSec, uint64 *shadowMicroSec, uint64 *gpuMicroSec ); + + virtual void SwapCommandBuffers_FinishRendering( uint64 *frontEndMicroSec, uint64 *backEndMicroSec, uint64 *shadowMicroSec, uint64 *gpuMicroSec ); + virtual const emptyCommand_t * SwapCommandBuffers_FinishCommandBuffers(); + + virtual void RenderCommandBuffers( const emptyCommand_t * commandBuffers ); + virtual void TakeScreenshot( int width, int height, const char *fileName, int downSample, renderView_t *ref ); + virtual void CropRenderSize( int width, int height ); + virtual void CaptureRenderToImage( const char *imageName, bool clearColorAfterCopy = false ); + virtual void CaptureRenderToFile( const char *fileName, bool fixAlpha ); + virtual void UnCrop(); + virtual bool UploadImage( const char *imageName, const byte *data, int width, int height ); + + + +public: + // internal functions + idRenderSystemLocal(); + ~idRenderSystemLocal(); + + void Clear(); + void GetCroppedViewport( idScreenRect * viewport ); + void PerformResolutionScaling( int& newWidth, int& newHeight ); + int GetFrameCount() const { return frameCount; }; + +public: + // renderer globals + bool registered; // cleared at shutdown, set at InitOpenGL + + bool takingScreenshot; + + int frameCount; // incremented every frame + int viewCount; // incremented every view (twice a scene if subviewed) + // and every R_MarkFragments call + + float frameShaderTime; // shader time for all non-world 2D rendering + + idVec4 ambientLightVector; // used for "ambient bump mapping" + + idListworlds; + + idRenderWorldLocal * primaryWorld; + renderView_t primaryRenderView; + viewDef_t * primaryView; + // many console commands need to know which world they should operate on + + const idMaterial * whiteMaterial; + const idMaterial * charSetMaterial; + const idMaterial * defaultPointLight; + const idMaterial * defaultProjectedLight; + const idMaterial * defaultMaterial; + idImage * testImage; + idCinematic * testVideo; + int testVideoStartTime; + + idImage * ambientCubeImage; // hack for testing dependent ambient lighting + + viewDef_t * viewDef; + + performanceCounters_t pc; // performance counters + + viewEntity_t identitySpace; // can use if we don't know viewDef->worldSpace is valid + + idScreenRect renderCrops[MAX_RENDER_CROPS]; + int currentRenderCrop; + + // GUI drawing variables for surface creation + int guiRecursionLevel; // to prevent infinite overruns + uint32 currentColorNativeBytesOrder; + uint64 currentGLState; + class idGuiModel * guiModel; + + idList fonts; + + unsigned short gammaTable[256]; // brightness / gamma modify this + + srfTriangles_t * unitSquareTriangles; + srfTriangles_t * zeroOneCubeTriangles; + srfTriangles_t * testImageTriangles; + + // these are allocated at buffer swap time, but + // the back end should only use the ones in the backEnd stucture, + // which are copied over from the frame that was just swapped. + drawSurf_t unitSquareSurface_; + drawSurf_t zeroOneCubeSurface_; + drawSurf_t testImageSurface_; + + idParallelJobList * frontEndJobList; + + unsigned timerQueryId; // for GL_TIME_ELAPSED_EXT queries +}; + +extern backEndState_t backEnd; +extern idRenderSystemLocal tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init + +// +// cvars +// +extern idCVar r_debugContext; // enable various levels of context debug +extern idCVar r_glDriver; // "opengl32", etc +extern idCVar r_skipIntelWorkarounds; // skip work arounds for Intel driver bugs +extern idCVar r_vidMode; // video mode number +extern idCVar r_displayRefresh; // optional display refresh rate option for vid mode +extern idCVar r_fullscreen; // 0 = windowed, 1 = full screen +extern idCVar r_multiSamples; // number of antialiasing samples + +extern idCVar r_znear; // near Z clip plane + +extern idCVar r_swapInterval; // changes wglSwapIntarval +extern idCVar r_offsetFactor; // polygon offset parameter +extern idCVar r_offsetUnits; // polygon offset parameter +extern idCVar r_singleTriangle; // only draw a single triangle per primitive +extern idCVar r_logFile; // number of frames to emit GL logs +extern idCVar r_clear; // force screen clear every frame +extern idCVar r_subviewOnly; // 1 = don't render main view, allowing subviews to be debugged +extern idCVar r_lightScale; // all light intensities are multiplied by this, which is normally 2 +extern idCVar r_flareSize; // scale the flare deforms from the material def + +extern idCVar r_gamma; // changes gamma tables +extern idCVar r_brightness; // changes gamma tables + +extern idCVar r_checkBounds; // compare all surface bounds with precalculated ones +extern idCVar r_maxAnisotropicFiltering; // texture filtering parameter +extern idCVar r_useTrilinearFiltering; // Extra quality filtering +extern idCVar r_lodBias; // lod bias + +extern idCVar r_useLightPortalFlow; // 1 = do a more precise area reference determination +extern idCVar r_useShadowSurfaceScissor; // 1 = scissor shadows by the scissor rect of the interaction surfaces +extern idCVar r_useConstantMaterials; // 1 = use pre-calculated material registers if possible +extern idCVar r_useNodeCommonChildren; // stop pushing reference bounds early when possible +extern idCVar r_useSilRemap; // 1 = consider verts with the same XYZ, but different ST the same for shadows +extern idCVar r_useLightPortalCulling; // 0 = none, 1 = box, 2 = exact clip of polyhedron faces, 3 MVP to plane culling +extern idCVar r_useLightAreaCulling; // 0 = off, 1 = on +extern idCVar r_useLightScissors; // 1 = use custom scissor rectangle for each light +extern idCVar r_useEntityPortalCulling; // 0 = none, 1 = box +extern idCVar r_skipPrelightShadows; // 1 = skip the dmap generated static shadow volumes +extern idCVar r_useCachedDynamicModels; // 1 = cache snapshots of dynamic models +extern idCVar r_useScissor; // 1 = scissor clip as portals and lights are processed +extern idCVar r_usePortals; // 1 = use portals to perform area culling, otherwise draw everything +extern idCVar r_useStateCaching; // avoid redundant state changes in GL_*() calls +extern idCVar r_useEntityCallbacks; // if 0, issue the callback immediately at update time, rather than defering +extern idCVar r_lightAllBackFaces; // light all the back faces, even when they would be shadowed +extern idCVar r_useLightDepthBounds; // use depth bounds test on lights to reduce both shadow and interaction fill +extern idCVar r_useShadowDepthBounds; // use depth bounds test on individual shadows to reduce shadow fill + +extern idCVar r_skipStaticInteractions; // skip interactions created at level load +extern idCVar r_skipDynamicInteractions; // skip interactions created after level load +extern idCVar r_skipPostProcess; // skip all post-process renderings +extern idCVar r_skipSuppress; // ignore the per-view suppressions +extern idCVar r_skipInteractions; // skip all light/surface interaction drawing +extern idCVar r_skipFrontEnd; // bypasses all front end work, but 2D gui rendering still draws +extern idCVar r_skipBackEnd; // don't draw anything +extern idCVar r_skipCopyTexture; // do all rendering, but don't actually copyTexSubImage2D +extern idCVar r_skipRender; // skip 3D rendering, but pass 2D +extern idCVar r_skipRenderContext; // NULL the rendering context during backend 3D rendering +extern idCVar r_skipTranslucent; // skip the translucent interaction rendering +extern idCVar r_skipAmbient; // bypasses all non-interaction drawing +extern idCVar r_skipNewAmbient; // bypasses all vertex/fragment program ambients +extern idCVar r_skipBlendLights; // skip all blend lights +extern idCVar r_skipFogLights; // skip all fog lights +extern idCVar r_skipSubviews; // 1 = don't render any mirrors / cameras / etc +extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces +extern idCVar r_skipParticles; // 1 = don't render any particles +extern idCVar r_skipUpdates; // 1 = don't accept any entity or light updates, making everything static +extern idCVar r_skipDeforms; // leave all deform materials in their original state +extern idCVar r_skipDynamicTextures; // don't dynamically create textures +extern idCVar r_skipBump; // uses a flat surface instead of the bump map +extern idCVar r_skipSpecular; // use black for specular +extern idCVar r_skipDiffuse; // use black for diffuse +extern idCVar r_skipDecals; // skip decal surfaces +extern idCVar r_skipOverlays; // skip overlay surfaces +extern idCVar r_skipShadows; // disable shadows + +extern idCVar r_ignoreGLErrors; + +extern idCVar r_screenFraction; // for testing fill rate, the resolution of the entire screen can be changed +extern idCVar r_showUnsmoothedTangents; // highlight geometry rendered with unsmoothed tangents +extern idCVar r_showSilhouette; // highlight edges that are casting shadow planes +extern idCVar r_showVertexColor; // draws all triangles with the solid vertex color +extern idCVar r_showUpdates; // report entity and light updates and ref counts +extern idCVar r_showDemo; // report reads and writes to the demo file +extern idCVar r_showDynamic; // report stats on dynamic surface generation +extern idCVar r_showIntensity; // draw the screen colors based on intensity, red = 0, green = 128, blue = 255 +extern idCVar r_showTrace; // show the intersection of an eye trace with the world +extern idCVar r_showDepth; // display the contents of the depth buffer and the depth range +extern idCVar r_showTris; // enables wireframe rendering of the world +extern idCVar r_showSurfaceInfo; // show surface material name under crosshair +extern idCVar r_showNormals; // draws wireframe normals +extern idCVar r_showEdges; // draw the sil edges +extern idCVar r_showViewEntitys; // displays the bounding boxes of all view models and optionally the index +extern idCVar r_showTexturePolarity; // shade triangles by texture area polarity +extern idCVar r_showTangentSpace; // shade triangles by tangent space +extern idCVar r_showDominantTri; // draw lines from vertexes to center of dominant triangles +extern idCVar r_showTextureVectors; // draw each triangles texture (tangent) vectors +extern idCVar r_showLights; // 1 = print light info, 2 = also draw volumes +extern idCVar r_showLightCount; // colors surfaces based on light count +extern idCVar r_showShadows; // visualize the stencil shadow volumes +extern idCVar r_showLightScissors; // show light scissor rectangles +extern idCVar r_showMemory; // print frame memory utilization +extern idCVar r_showCull; // report sphere and box culling stats +extern idCVar r_showAddModel; // report stats from tr_addModel +extern idCVar r_showSurfaces; // report surface/light/shadow counts +extern idCVar r_showPrimitives; // report vertex/index/draw counts +extern idCVar r_showPortals; // draw portal outlines in color based on passed / not passed +extern idCVar r_showSkel; // draw the skeleton when model animates +extern idCVar r_showOverDraw; // show overdraw +extern idCVar r_jointNameScale; // size of joint names when r_showskel is set to 1 +extern idCVar r_jointNameOffset; // offset of joint names when r_showskel is set to 1 + +extern idCVar r_testGamma; // draw a grid pattern to test gamma levels +extern idCVar r_testGammaBias; // draw a grid pattern to test gamma levels + +extern idCVar r_singleLight; // suppress all but one light +extern idCVar r_singleEntity; // suppress all but one entity +extern idCVar r_singleArea; // only draw the portal area the view is actually in +extern idCVar r_singleSurface; // suppress all but one surface on each entity +extern idCVar r_shadowPolygonOffset; // bias value added to depth test for stencil shadow drawing +extern idCVar r_shadowPolygonFactor; // scale value for stencil shadow drawing + +extern idCVar r_jitter; // randomly subpixel jitter the projection matrix +extern idCVar r_orderIndexes; // perform index reorganization to optimize vertex use + +extern idCVar r_debugLineDepthTest; // perform depth test on debug lines +extern idCVar r_debugLineWidth; // width of debug lines +extern idCVar r_debugArrowStep; // step size of arrow cone line rotation in degrees +extern idCVar r_debugPolygonFilled; + +extern idCVar r_materialOverride; // override all materials + +extern idCVar r_debugRenderToTexture; + +extern idCVar stereoRender_deGhost; // subtract from opposite eye to reduce ghosting + +extern idCVar r_useGPUSkinning; + +/* +==================================================================== + +INITIALIZATION + +==================================================================== +*/ + +void R_Init(); +void R_InitOpenGL(); + +void R_SetColorMappings(); + +void R_ScreenShot_f( const idCmdArgs &args ); +void R_StencilShot(); + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +struct vidMode_t { + int width; + int height; + int displayHz; + + bool operator==( const vidMode_t & a ) { + return a.width == width && a.height == height && a.displayHz == displayHz; + } +}; + +// the number of displays can be found by itterating this until it returns false +// displayNum is the 0 based value passed to EnumDisplayDevices(), you must add +// 1 to this to get an r_fullScreen value. +bool R_GetModeListForDisplay( const int displayNum, idList & modeList ); + +struct glimpParms_t { + int x; // ignored in fullscreen + int y; // ignored in fullscreen + int width; + int height; + int fullScreen; // 0 = windowed, otherwise 1 based monitor number to go full screen on + // -1 = borderless window for spanning multiple displays + bool stereo; + int displayHz; + int multiSamples; +}; + +bool GLimp_Init( glimpParms_t parms ); +// If the desired mode can't be set satisfactorily, false will be returned. +// If succesful, sets glConfig.nativeScreenWidth, glConfig.nativeScreenHeight, and glConfig.pixelAspect + +// The renderer will then reset the glimpParms to "safe mode" of 640x480 +// fullscreen and try again. If that also fails, the error will be fatal. + +bool GLimp_SetScreenParms( glimpParms_t parms ); +// will set up gl up with the new parms + +void GLimp_Shutdown(); +// Destroys the rendering context, closes the window, resets the resolution, +// and resets the gamma ramps. + +void GLimp_SetGamma( unsigned short red[256], + unsigned short green[256], + unsigned short blue[256] ); +// Sets the hardware gamma ramps for gamma and brightness adjustment. +// These are now taken as 16 bit values, so we can take full advantage +// of dacs with >8 bits of precision + + +bool GLimp_SpawnRenderThread( void (*function)() ); +// Returns false if the system only has a single processor + +void * GLimp_BackEndSleep(); +void GLimp_FrontEndSleep(); +void GLimp_WakeBackEnd( void *data ); +// these functions implement the dual processor syncronization + +void GLimp_ActivateContext(); +void GLimp_DeactivateContext(); +// These are used for managing SMP handoffs of the OpenGL context +// between threads, and as a performance tunining aid. Setting +// 'r_skipRenderContext 1' will call GLimp_DeactivateContext() before +// the 3D rendering code, and GLimp_ActivateContext() afterwards. On +// most OpenGL implementations, this will result in all OpenGL calls +// being immediate returns, which lets us guage how much time is +// being spent inside OpenGL. + +void GLimp_EnableLogging( bool enable ); + + +/* +============================================================ + +RENDERWORLD_DEFS + +============================================================ +*/ + +void R_DeriveEntityData( idRenderEntityLocal *def ); +void R_CreateEntityRefs( idRenderEntityLocal *def ); +void R_FreeEntityDefDerivedData( idRenderEntityLocal *def, bool keepDecals, bool keepCachedDynamicModel ); +void R_FreeEntityDefCachedDynamicModel( idRenderEntityLocal *def ); +void R_FreeEntityDefDecals( idRenderEntityLocal *def ); +void R_FreeEntityDefOverlay( idRenderEntityLocal *def ); +void R_FreeEntityDefFadedDecals( idRenderEntityLocal *def, int time ); + +void R_CreateLightRefs( idRenderLightLocal *light ); +void R_FreeLightDefDerivedData( idRenderLightLocal *light ); + +void R_FreeDerivedData(); +void R_ReCreateWorldReferences(); +void R_CheckForEntityDefsUsingModel( idRenderModel *model ); +void R_ModulateLights_f( const idCmdArgs &args ); + +/* +============================================================ + +RENDERWORLD_PORTALS + +============================================================ +*/ + +viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ); +viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *def ); + +/* +==================================================================== + +TR_FRONTEND_MAIN + +==================================================================== +*/ + +void R_InitFrameData(); +void R_ShutdownFrameData(); +void R_ToggleSmpFrame(); +void *R_FrameAlloc( int bytes, frameAllocType_t type = FRAME_ALLOC_UNKNOWN ); +void *R_ClearedFrameAlloc( int bytes, frameAllocType_t type = FRAME_ALLOC_UNKNOWN ); + +void *R_StaticAlloc( int bytes, const memTag_t tag = TAG_RENDER_STATIC ); // just malloc with error checking +void *R_ClearedStaticAlloc( int bytes ); // with memset +void R_StaticFree( void *data ); + +void R_RenderView( viewDef_t *parms ); +void R_RenderPostProcess( viewDef_t *parms ); + +/* +============================================================ + +TR_FRONTEND_ADDLIGHTS + +============================================================ +*/ + +void R_ShadowBounds( const idBounds & modelBounds, const idBounds & lightBounds, const idVec3 & lightOrigin, idBounds & shadowBounds ); + +ID_INLINE bool R_CullModelBoundsToLight( const idRenderLightLocal * light, const idBounds & localBounds, const idRenderMatrix & modelRenderMatrix ) { + idRenderMatrix modelLightProject; + idRenderMatrix::Multiply( light->baseLightProject, modelRenderMatrix, modelLightProject ); + return idRenderMatrix::CullBoundsToMVP( modelLightProject, localBounds, true ); +} + +void R_AddLights(); +void R_OptimizeViewLightsList(); + +/* +============================================================ + +TR_FRONTEND_ADDMODELS + +============================================================ +*/ + +bool R_IssueEntityDefCallback( idRenderEntityLocal *def ); +idRenderModel *R_EntityDefDynamicModel( idRenderEntityLocal *def ); +void R_ClearEntityDefDynamicModel( idRenderEntityLocal *def ); + +void R_SetupDrawSurfShader( drawSurf_t * drawSurf, const idMaterial * shader, const renderEntity_t * renderEntity ); +void R_SetupDrawSurfJoints( drawSurf_t * drawSurf, const srfTriangles_t * tri, const idMaterial * shader ); +void R_LinkDrawSurfToView( drawSurf_t * drawSurf, viewDef_t * viewDef ); + +void R_AddModels(); + +/* +============================================================= + +TR_FRONTEND_DEFORM + +============================================================= +*/ + +drawSurf_t * R_DeformDrawSurf( drawSurf_t * drawSurf ); + +/* +============================================================= + +TR_FRONTEND_GUISURF + +============================================================= +*/ + +void R_SurfaceToTextureAxis( const srfTriangles_t *tri, idVec3 &origin, idVec3 axis[3] ); +void R_AddInGameGuis( const drawSurf_t * const drawSurfs[], const int numDrawSurfs ); + +/* +============================================================ + +TR_FRONTEND_SUBVIEW + +============================================================ +*/ + +bool R_PreciseCullSurface( const drawSurf_t *drawSurf, idBounds &ndcBounds ); +bool R_GenerateSubViews( const drawSurf_t * const drawSurfs[], const int numDrawSurfs ); + +/* +============================================================ + +TR_TRISURF + +============================================================ +*/ + +srfTriangles_t * R_AllocStaticTriSurf(); +void R_AllocStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ); +void R_AllocStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ); +void R_AllocStaticTriSurfPreLightShadowVerts( srfTriangles_t *tri, int numVerts ); +void R_AllocStaticTriSurfSilIndexes( srfTriangles_t *tri, int numIndexes ); +void R_AllocStaticTriSurfDominantTris( srfTriangles_t *tri, int numVerts ); +void R_AllocStaticTriSurfSilEdges( srfTriangles_t *tri, int numSilEdges ); +void R_AllocStaticTriSurfMirroredVerts( srfTriangles_t *tri, int numMirroredVerts ); +void R_AllocStaticTriSurfDupVerts( srfTriangles_t *tri, int numDupVerts ); + +srfTriangles_t * R_CopyStaticTriSurf( const srfTriangles_t *tri ); + +void R_ResizeStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ); +void R_ResizeStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ); +void R_ReferenceStaticTriSurfVerts( srfTriangles_t *tri, const srfTriangles_t *reference ); +void R_ReferenceStaticTriSurfIndexes( srfTriangles_t *tri, const srfTriangles_t *reference ); + +void R_FreeStaticTriSurfSilIndexes( srfTriangles_t *tri ); +void R_FreeStaticTriSurf( srfTriangles_t *tri ); +void R_FreeStaticTriSurfVerts( srfTriangles_t *tri ); +void R_FreeStaticTriSurfVertexCaches( srfTriangles_t *tri ); +int R_TriSurfMemory( const srfTriangles_t *tri ); + +void R_BoundTriSurf( srfTriangles_t *tri ); +void R_RemoveDuplicatedTriangles( srfTriangles_t *tri ); +void R_CreateSilIndexes( srfTriangles_t *tri ); +void R_RemoveDegenerateTriangles( srfTriangles_t *tri ); +void R_RemoveUnusedVerts( srfTriangles_t *tri ); +void R_RangeCheckIndexes( const srfTriangles_t *tri ); +void R_CreateVertexNormals( srfTriangles_t *tri ); // also called by dmap +void R_CleanupTriangles( srfTriangles_t *tri, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents ); +void R_ReverseTriangles( srfTriangles_t *tri ); + +// Only deals with vertexes and indexes, not silhouettes, planes, etc. +// Does NOT perform a cleanup triangles, so there may be duplicated verts in the result. +srfTriangles_t * R_MergeSurfaceList( const srfTriangles_t **surfaces, int numSurfaces ); +srfTriangles_t * R_MergeTriangles( const srfTriangles_t *tri1, const srfTriangles_t *tri2 ); + +// if the deformed verts have significant enough texture coordinate changes to reverse the texture +// polarity of a triangle, the tangents will be incorrect +void R_DeriveTangents( srfTriangles_t *tri ); + +// copy data from a front-end srfTriangles_t to a back-end drawSurf_t +void R_InitDrawSurfFromTri( drawSurf_t & ds, srfTriangles_t & tri ); + +// For static surfaces, the indexes, ambient, and shadow buffers can be pre-created at load +// time, rather than being re-created each frame in the frame temporary buffers. +void R_CreateStaticBuffersForTri( srfTriangles_t & tri ); + +// deformable meshes precalculate as much as possible from a base frame, then generate +// complete srfTriangles_t from just a new set of vertexes +struct deformInfo_t { + int numSourceVerts; + + // numOutputVerts may be smaller if the input had duplicated or degenerate triangles + // it will often be larger if the input had mirrored texture seams that needed + // to be busted for proper tangent spaces + int numOutputVerts; + idDrawVert * verts; + + int numIndexes; + triIndex_t * indexes; + + triIndex_t * silIndexes; // indexes changed to be the first vertex with same XYZ, ignoring normal and texcoords + + int numMirroredVerts; // this many verts at the end of the vert list are tangent mirrors + int * mirroredVerts; // tri->mirroredVerts[0] is the mirror of tri->numVerts - tri->numMirroredVerts + 0 + + int numDupVerts; // number of duplicate vertexes + int * dupVerts; // pairs of the number of the first vertex and the number of the duplicate vertex + + int numSilEdges; // number of silhouette edges + silEdge_t * silEdges; // silhouette edges + + vertCacheHandle_t staticIndexCache; // GL_INDEX_TYPE + vertCacheHandle_t staticAmbientCache; // idDrawVert + vertCacheHandle_t staticShadowCache; // idShadowCacheSkinned +}; + + +// if outputVertexes is not NULL, it will point to a newly allocated set of verts that includes the mirrored ones +deformInfo_t * R_BuildDeformInfo( int numVerts, const idDrawVert *verts, int numIndexes, const int *indexes, + bool useUnsmoothedTangents ); +void R_FreeDeformInfo( deformInfo_t *deformInfo ); +int R_DeformInfoMemoryUsed( deformInfo_t *deformInfo ); + +/* +============================================================= + +TR_TRACE + +============================================================= +*/ + +struct localTrace_t { + float fraction; + // only valid if fraction < 1.0 + idVec3 point; + idVec3 normal; + int indexes[3]; +}; + +localTrace_t R_LocalTrace( const idVec3 &start, const idVec3 &end, const float radius, const srfTriangles_t *tri ); +void RB_ShowTrace( drawSurf_t **drawSurfs, int numDrawSurfs ); + +/* +============================================================= + +BACKEND + +============================================================= +*/ + +void RB_ExecuteBackEndCommands( const emptyCommand_t *cmds ); + +/* +============================================================ + +TR_BACKEND_DRAW + +============================================================ +*/ + +void RB_DrawElementsWithCounters( const drawSurf_t *surf ); +void RB_DrawViewInternal( const viewDef_t * viewDef, const int stereoEye ); +void RB_DrawView( const void *data, const int stereoEye ); +void RB_CopyRender( const void *data ); +void RB_PostProcess( const void *data ); + +/* +============================================================= + +TR_BACKEND_RENDERTOOLS + +============================================================= +*/ + +float RB_DrawTextLength( const char *text, float scale, int len ); +void RB_AddDebugText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ); +void RB_ClearDebugText( int time ); +void RB_AddDebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifeTime, const bool depthTest ); +void RB_ClearDebugLines( int time ); +void RB_AddDebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ); +void RB_ClearDebugPolygons( int time ); +void RB_DrawBounds( const idBounds &bounds ); +void RB_ShowLights( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_ShowLightCount( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_PolygonClear(); +void RB_ScanStencilBuffer(); +void RB_ShowDestinationAlpha(); +void RB_ShowOverdraw(); +void RB_RenderDebugTools( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_ShutdownDebugTools(); + +//============================================= + +#include "ResolutionScale.h" +#include "RenderLog.h" +#include "AutoRender.h" +#include "AutoRenderBink.h" +#include "jobs/ShadowShared.h" +#include "jobs/prelightshadowvolume/PreLightShadowVolume.h" +#include "jobs/staticshadowvolume/StaticShadowVolume.h" +#include "jobs/dynamicshadowvolume/DynamicShadowVolume.h" +#include "GraphicsAPIWrapper.h" +#include "GLMatrix.h" + + + +#include "BufferObject.h" +#include "RenderProgs.h" +#include "RenderWorld_local.h" +#include "GuiModel.h" +#include "VertexCache.h" + +#endif /* !__TR_LOCAL_H__ */ diff --git a/neo/renderer/tr_trace.cpp b/neo/renderer/tr_trace.cpp new file mode 100644 index 00000000..a9d789aa --- /dev/null +++ b/neo/renderer/tr_trace.cpp @@ -0,0 +1,533 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" +#include "Model_local.h" + +#include "../idlib/geometry/DrawVert_intrinsics.h" + +/* +==================== +R_TracePointCullStatic +==================== +*/ +static void R_TracePointCullStatic( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + assert_16_byte_aligned( cullBits ); + assert_16_byte_aligned( verts ); + + + idODSStreamedArray< idDrawVert, 16, SBT_DOUBLE, 4 > vertsODS( verts, numVerts ); + + const __m128 vector_float_radius = _mm_splat_ps( _mm_load_ss( &radius ), 0 ); + const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; + const __m128i vector_int_mask0 = _mm_set1_epi32( 1 << 0 ); + const __m128i vector_int_mask1 = _mm_set1_epi32( 1 << 1 ); + const __m128i vector_int_mask2 = _mm_set1_epi32( 1 << 2 ); + const __m128i vector_int_mask3 = _mm_set1_epi32( 1 << 3 ); + const __m128i vector_int_mask4 = _mm_set1_epi32( 1 << 4 ); + const __m128i vector_int_mask5 = _mm_set1_epi32( 1 << 5 ); + const __m128i vector_int_mask6 = _mm_set1_epi32( 1 << 6 ); + const __m128i vector_int_mask7 = _mm_set1_epi32( 1 << 7 ); + + const __m128 p0 = _mm_loadu_ps( planes[0].ToFloatPtr() ); + const __m128 p1 = _mm_loadu_ps( planes[1].ToFloatPtr() ); + const __m128 p2 = _mm_loadu_ps( planes[2].ToFloatPtr() ); + const __m128 p3 = _mm_loadu_ps( planes[3].ToFloatPtr() ); + + const __m128 p0X = _mm_splat_ps( p0, 0 ); + const __m128 p0Y = _mm_splat_ps( p0, 1 ); + const __m128 p0Z = _mm_splat_ps( p0, 2 ); + const __m128 p0W = _mm_splat_ps( p0, 3 ); + + const __m128 p1X = _mm_splat_ps( p1, 0 ); + const __m128 p1Y = _mm_splat_ps( p1, 1 ); + const __m128 p1Z = _mm_splat_ps( p1, 2 ); + const __m128 p1W = _mm_splat_ps( p1, 3 ); + + const __m128 p2X = _mm_splat_ps( p2, 0 ); + const __m128 p2Y = _mm_splat_ps( p2, 1 ); + const __m128 p2Z = _mm_splat_ps( p2, 2 ); + const __m128 p2W = _mm_splat_ps( p2, 3 ); + + const __m128 p3X = _mm_splat_ps( p3, 0 ); + const __m128 p3Y = _mm_splat_ps( p3, 1 ); + const __m128 p3Z = _mm_splat_ps( p3, 2 ); + const __m128 p3W = _mm_splat_ps( p3, 3 ); + + __m128i vecTotalOrInt = { 0, 0, 0, 0 }; + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 4; + + for ( ; i <= nextNumVerts; i += 4 ) { + const __m128 v0 = _mm_load_ps( vertsODS[i + 0].xyz.ToFloatPtr() ); + const __m128 v1 = _mm_load_ps( vertsODS[i + 1].xyz.ToFloatPtr() ); + const __m128 v2 = _mm_load_ps( vertsODS[i + 2].xyz.ToFloatPtr() ); + const __m128 v3 = _mm_load_ps( vertsODS[i + 3].xyz.ToFloatPtr() ); + + const __m128 r0 = _mm_unpacklo_ps( v0, v2 ); // v0.x, v2.x, v0.z, v2.z + const __m128 r1 = _mm_unpackhi_ps( v0, v2 ); // v0.y, v2.y, v0.w, v2.w + const __m128 r2 = _mm_unpacklo_ps( v1, v3 ); // v1.x, v3.x, v1.z, v3.z + const __m128 r3 = _mm_unpackhi_ps( v1, v3 ); // v1.y, v3.y, v1.w, v3.w + + const __m128 vX = _mm_unpacklo_ps( r0, r2 ); // v0.x, v1.x, v2.x, v3.x + const __m128 vY = _mm_unpackhi_ps( r0, r2 ); // v0.y, v1.y, v2.y, v3.y + const __m128 vZ = _mm_unpacklo_ps( r1, r3 ); // v0.z, v1.z, v2.z, v3.z + + const __m128 d0 = _mm_madd_ps( vX, p0X, _mm_madd_ps( vY, p0Y, _mm_madd_ps( vZ, p0Z, p0W ) ) ); + const __m128 d1 = _mm_madd_ps( vX, p1X, _mm_madd_ps( vY, p1Y, _mm_madd_ps( vZ, p1Z, p1W ) ) ); + const __m128 d2 = _mm_madd_ps( vX, p2X, _mm_madd_ps( vY, p2Y, _mm_madd_ps( vZ, p2Z, p2W ) ) ); + const __m128 d3 = _mm_madd_ps( vX, p3X, _mm_madd_ps( vY, p3Y, _mm_madd_ps( vZ, p3Z, p3W ) ) ); + + const __m128 t0 = _mm_add_ps( d0, vector_float_radius ); + const __m128 t1 = _mm_add_ps( d1, vector_float_radius ); + const __m128 t2 = _mm_add_ps( d2, vector_float_radius ); + const __m128 t3 = _mm_add_ps( d3, vector_float_radius ); + + const __m128 t4 = _mm_sub_ps( d0, vector_float_radius ); + const __m128 t5 = _mm_sub_ps( d1, vector_float_radius ); + const __m128 t6 = _mm_sub_ps( d2, vector_float_radius ); + const __m128 t7 = _mm_sub_ps( d3, vector_float_radius ); + + __m128i c0 = __m128c( _mm_cmpgt_ps( t0, vector_float_zero ) ); + __m128i c1 = __m128c( _mm_cmpgt_ps( t1, vector_float_zero ) ); + __m128i c2 = __m128c( _mm_cmpgt_ps( t2, vector_float_zero ) ); + __m128i c3 = __m128c( _mm_cmpgt_ps( t3, vector_float_zero ) ); + + __m128i c4 = __m128c( _mm_cmplt_ps( t4, vector_float_zero ) ); + __m128i c5 = __m128c( _mm_cmplt_ps( t5, vector_float_zero ) ); + __m128i c6 = __m128c( _mm_cmplt_ps( t6, vector_float_zero ) ); + __m128i c7 = __m128c( _mm_cmplt_ps( t7, vector_float_zero ) ); + + c0 = _mm_and_si128( c0, vector_int_mask0 ); + c1 = _mm_and_si128( c1, vector_int_mask1 ); + c2 = _mm_and_si128( c2, vector_int_mask2 ); + c3 = _mm_and_si128( c3, vector_int_mask3 ); + + c4 = _mm_and_si128( c4, vector_int_mask4 ); + c5 = _mm_and_si128( c5, vector_int_mask5 ); + c6 = _mm_and_si128( c6, vector_int_mask6 ); + c7 = _mm_and_si128( c7, vector_int_mask7 ); + + c0 = _mm_or_si128( c0, c1 ); + c2 = _mm_or_si128( c2, c3 ); + c4 = _mm_or_si128( c4, c5 ); + c6 = _mm_or_si128( c6, c7 ); + + c0 = _mm_or_si128( c0, c2 ); + c4 = _mm_or_si128( c4, c6 ); + c0 = _mm_or_si128( c0, c4 ); + + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, c0 ); + + __m128i s0 = _mm_packs_epi32( c0, c0 ); + __m128i b0 = _mm_packus_epi16( s0, s0 ); + + *(unsigned int *)&cullBits[i] = _mm_cvtsi128_si32( b0 ); + } + } + + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, _mm_shuffle_epi32( vecTotalOrInt, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, _mm_shuffle_epi32( vecTotalOrInt, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + __m128i vecTotalOrShort = _mm_packs_epi32( vecTotalOrInt, vecTotalOrInt ); + __m128i vecTotalOrByte = _mm_packus_epi16( vecTotalOrShort, vecTotalOrShort ); + + totalOr = (byte) _mm_cvtsi128_si32( vecTotalOrByte ); + +} + +/* +==================== +R_TracePointCullSkinned +==================== +*/ +static void R_TracePointCullSkinned( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts, const idJointMat * joints ) { + assert_16_byte_aligned( cullBits ); + assert_16_byte_aligned( verts ); + + + idODSStreamedArray< idDrawVert, 16, SBT_DOUBLE, 4 > vertsODS( verts, numVerts ); + + const __m128 vector_float_radius = _mm_splat_ps( _mm_load_ss( &radius ), 0 ); + const __m128 vector_float_zero = { 0.0f, 0.0f, 0.0f, 0.0f }; + const __m128i vector_int_mask0 = _mm_set1_epi32( 1 << 0 ); + const __m128i vector_int_mask1 = _mm_set1_epi32( 1 << 1 ); + const __m128i vector_int_mask2 = _mm_set1_epi32( 1 << 2 ); + const __m128i vector_int_mask3 = _mm_set1_epi32( 1 << 3 ); + const __m128i vector_int_mask4 = _mm_set1_epi32( 1 << 4 ); + const __m128i vector_int_mask5 = _mm_set1_epi32( 1 << 5 ); + const __m128i vector_int_mask6 = _mm_set1_epi32( 1 << 6 ); + const __m128i vector_int_mask7 = _mm_set1_epi32( 1 << 7 ); + + const __m128 p0 = _mm_loadu_ps( planes[0].ToFloatPtr() ); + const __m128 p1 = _mm_loadu_ps( planes[1].ToFloatPtr() ); + const __m128 p2 = _mm_loadu_ps( planes[2].ToFloatPtr() ); + const __m128 p3 = _mm_loadu_ps( planes[3].ToFloatPtr() ); + + const __m128 p0X = _mm_splat_ps( p0, 0 ); + const __m128 p0Y = _mm_splat_ps( p0, 1 ); + const __m128 p0Z = _mm_splat_ps( p0, 2 ); + const __m128 p0W = _mm_splat_ps( p0, 3 ); + + const __m128 p1X = _mm_splat_ps( p1, 0 ); + const __m128 p1Y = _mm_splat_ps( p1, 1 ); + const __m128 p1Z = _mm_splat_ps( p1, 2 ); + const __m128 p1W = _mm_splat_ps( p1, 3 ); + + const __m128 p2X = _mm_splat_ps( p2, 0 ); + const __m128 p2Y = _mm_splat_ps( p2, 1 ); + const __m128 p2Z = _mm_splat_ps( p2, 2 ); + const __m128 p2W = _mm_splat_ps( p2, 3 ); + + const __m128 p3X = _mm_splat_ps( p3, 0 ); + const __m128 p3Y = _mm_splat_ps( p3, 1 ); + const __m128 p3Z = _mm_splat_ps( p3, 2 ); + const __m128 p3W = _mm_splat_ps( p3, 3 ); + + __m128i vecTotalOrInt = { 0, 0, 0, 0 }; + + for ( int i = 0; i < numVerts; ) { + + const int nextNumVerts = vertsODS.FetchNextBatch() - 4; + + for ( ; i <= nextNumVerts; i += 4 ) { + const __m128 v0 = LoadSkinnedDrawVertPosition( vertsODS[i + 0], joints ); + const __m128 v1 = LoadSkinnedDrawVertPosition( vertsODS[i + 1], joints ); + const __m128 v2 = LoadSkinnedDrawVertPosition( vertsODS[i + 2], joints ); + const __m128 v3 = LoadSkinnedDrawVertPosition( vertsODS[i + 3], joints ); + + const __m128 r0 = _mm_unpacklo_ps( v0, v2 ); // v0.x, v2.x, v0.z, v2.z + const __m128 r1 = _mm_unpackhi_ps( v0, v2 ); // v0.y, v2.y, v0.w, v2.w + const __m128 r2 = _mm_unpacklo_ps( v1, v3 ); // v1.x, v3.x, v1.z, v3.z + const __m128 r3 = _mm_unpackhi_ps( v1, v3 ); // v1.y, v3.y, v1.w, v3.w + + const __m128 vX = _mm_unpacklo_ps( r0, r2 ); // v0.x, v1.x, v2.x, v3.x + const __m128 vY = _mm_unpackhi_ps( r0, r2 ); // v0.y, v1.y, v2.y, v3.y + const __m128 vZ = _mm_unpacklo_ps( r1, r3 ); // v0.z, v1.z, v2.z, v3.z + + const __m128 d0 = _mm_madd_ps( vX, p0X, _mm_madd_ps( vY, p0Y, _mm_madd_ps( vZ, p0Z, p0W ) ) ); + const __m128 d1 = _mm_madd_ps( vX, p1X, _mm_madd_ps( vY, p1Y, _mm_madd_ps( vZ, p1Z, p1W ) ) ); + const __m128 d2 = _mm_madd_ps( vX, p2X, _mm_madd_ps( vY, p2Y, _mm_madd_ps( vZ, p2Z, p2W ) ) ); + const __m128 d3 = _mm_madd_ps( vX, p3X, _mm_madd_ps( vY, p3Y, _mm_madd_ps( vZ, p3Z, p3W ) ) ); + + const __m128 t0 = _mm_add_ps( d0, vector_float_radius ); + const __m128 t1 = _mm_add_ps( d1, vector_float_radius ); + const __m128 t2 = _mm_add_ps( d2, vector_float_radius ); + const __m128 t3 = _mm_add_ps( d3, vector_float_radius ); + + const __m128 t4 = _mm_sub_ps( d0, vector_float_radius ); + const __m128 t5 = _mm_sub_ps( d1, vector_float_radius ); + const __m128 t6 = _mm_sub_ps( d2, vector_float_radius ); + const __m128 t7 = _mm_sub_ps( d3, vector_float_radius ); + + __m128i c0 = __m128c( _mm_cmpgt_ps( t0, vector_float_zero ) ); + __m128i c1 = __m128c( _mm_cmpgt_ps( t1, vector_float_zero ) ); + __m128i c2 = __m128c( _mm_cmpgt_ps( t2, vector_float_zero ) ); + __m128i c3 = __m128c( _mm_cmpgt_ps( t3, vector_float_zero ) ); + + __m128i c4 = __m128c( _mm_cmplt_ps( t4, vector_float_zero ) ); + __m128i c5 = __m128c( _mm_cmplt_ps( t5, vector_float_zero ) ); + __m128i c6 = __m128c( _mm_cmplt_ps( t6, vector_float_zero ) ); + __m128i c7 = __m128c( _mm_cmplt_ps( t7, vector_float_zero ) ); + + c0 = _mm_and_si128( c0, vector_int_mask0 ); + c1 = _mm_and_si128( c1, vector_int_mask1 ); + c2 = _mm_and_si128( c2, vector_int_mask2 ); + c3 = _mm_and_si128( c3, vector_int_mask3 ); + + c4 = _mm_and_si128( c4, vector_int_mask4 ); + c5 = _mm_and_si128( c5, vector_int_mask5 ); + c6 = _mm_and_si128( c6, vector_int_mask6 ); + c7 = _mm_and_si128( c7, vector_int_mask7 ); + + c0 = _mm_or_si128( c0, c1 ); + c2 = _mm_or_si128( c2, c3 ); + c4 = _mm_or_si128( c4, c5 ); + c6 = _mm_or_si128( c6, c7 ); + + c0 = _mm_or_si128( c0, c2 ); + c4 = _mm_or_si128( c4, c6 ); + c0 = _mm_or_si128( c0, c4 ); + + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, c0 ); + + __m128i s0 = _mm_packs_epi32( c0, c0 ); + __m128i b0 = _mm_packus_epi16( s0, s0 ); + + *(unsigned int *)&cullBits[i] = _mm_cvtsi128_si32( b0 ); + } + } + + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, _mm_shuffle_epi32( vecTotalOrInt, _MM_SHUFFLE( 1, 0, 3, 2 ) ) ); + vecTotalOrInt = _mm_or_si128( vecTotalOrInt, _mm_shuffle_epi32( vecTotalOrInt, _MM_SHUFFLE( 2, 3, 0, 1 ) ) ); + + __m128i vecTotalOrShort = _mm_packs_epi32( vecTotalOrInt, vecTotalOrInt ); + __m128i vecTotalOrByte = _mm_packus_epi16( vecTotalOrShort, vecTotalOrShort ); + + totalOr = (byte) _mm_cvtsi128_si32( vecTotalOrByte ); + +} + +/* +==================== +R_LineIntersectsTriangleExpandedWithCircle + +The triangle is expanded in the plane with a circle of the given radius. +==================== +*/ +static bool R_LineIntersectsTriangleExpandedWithCircle( localTrace_t & hit, const idVec3 & start, const idVec3 & end, const float circleRadius, const idVec3 & triVert0, const idVec3 & triVert1, const idVec3 & triVert2 ) { + const idPlane plane( triVert0, triVert1, triVert2 ); + + const float planeDistStart = plane.Distance( start ); + const float planeDistEnd = plane.Distance( end ); + + if ( planeDistStart < 0.0f ) { + return false; // starts past the triangle + } + + if ( planeDistEnd > 0.0f ) { + return false; // finishes in front of the triangle + } + + const float planeDelta = planeDistStart - planeDistEnd; + + if ( planeDelta < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return false; // coming at the triangle from behind or parallel + } + + const float fraction = planeDistStart / planeDelta; + + if ( fraction < 0.0f ) { + return false; // shouldn't happen + } + + if ( fraction >= hit.fraction ) { + return false; // have already hit something closer + } + + // find the exact point of impact with the plane + const idVec3 point = start + fraction * ( end - start ); + + // see if the point is within the three edges + // if radius > 0 the triangle is expanded with a circle in the triangle plane + + const float radiusSqr = circleRadius * circleRadius; + + const idVec3 dir0 = triVert0 - point; + const idVec3 dir1 = triVert1 - point; + + const idVec3 cross0 = dir0.Cross( dir1 ); + float d0 = plane.Normal() * cross0; + if ( d0 > 0.0f ) { + if ( radiusSqr <= 0.0f ) { + return false; + } + idVec3 edge = triVert0 - triVert1; + const float edgeLengthSqr = edge.LengthSqr(); + if ( cross0.LengthSqr() > edgeLengthSqr * radiusSqr ) { + return false; + } + d0 = edge * dir0; + if ( d0 < 0.0f ) { + edge = triVert0 - triVert2; + d0 = edge * dir0; + if ( d0 < 0.0f ) { + if ( dir0.LengthSqr() > radiusSqr ) { + return false; + } + } + } else if ( d0 > edgeLengthSqr ) { + edge = triVert1 - triVert2; + d0 = edge * dir1; + if ( d0 < 0.0f ) { + if ( dir1.LengthSqr() > radiusSqr ) { + return false; + } + } + } + } + + const idVec3 dir2 = triVert2 - point; + + const idVec3 cross1 = dir1.Cross( dir2 ); + float d1 = plane.Normal() * cross1; + if ( d1 > 0.0f ) { + if ( radiusSqr <= 0.0f ) { + return false; + } + idVec3 edge = triVert1 - triVert2; + const float edgeLengthSqr = edge.LengthSqr(); + if ( cross1.LengthSqr() > edgeLengthSqr * radiusSqr ) { + return false; + } + d1 = edge * dir1; + if ( d1 < 0.0f ) { + edge = triVert1 - triVert0; + d1 = edge * dir1; + if ( d1 < 0.0f ) { + if ( dir1.LengthSqr() > radiusSqr ) { + return false; + } + } + } else if ( d1 > edgeLengthSqr ) { + edge = triVert2 - triVert0; + d1 = edge * dir2; + if ( d1 < 0.0f ) { + if ( dir2.LengthSqr() > radiusSqr ) { + return false; + } + } + } + } + + const idVec3 cross2 = dir2.Cross( dir0 ); + float d2 = plane.Normal() * cross2; + if ( d2 > 0.0f ) { + if ( radiusSqr <= 0.0f ) { + return false; + } + idVec3 edge = triVert2 - triVert0; + const float edgeLengthSqr = edge.LengthSqr(); + if ( cross2.LengthSqr() > edgeLengthSqr * radiusSqr ) { + return false; + } + d2 = edge * dir2; + if ( d2 < 0.0f ) { + edge = triVert2 - triVert1; + d2 = edge * dir2; + if ( d2 < 0.0f ) { + if ( dir2.LengthSqr() > radiusSqr ) { + return false; + } + } + } else if ( d2 > edgeLengthSqr ) { + edge = triVert0 - triVert1; + d2 = edge * dir0; + if ( d2 < 0.0f ) { + if ( dir0.LengthSqr() > radiusSqr ) { + return false; + } + } + } + } + + // we hit this triangle + hit.fraction = fraction; + hit.normal = plane.Normal(); + hit.point = point; + + return true; +} + +/* +==================== +R_LocalTrace +==================== +*/ +localTrace_t R_LocalTrace( const idVec3 &start, const idVec3 &end, const float radius, const srfTriangles_t *tri ) { + localTrace_t hit; + hit.fraction = 1.0f; + + ALIGNTYPE16 idPlane planes[4]; + // create two planes orthogonal to each other that intersect along the trace + idVec3 startDir = end - start; + startDir.Normalize(); + startDir.NormalVectors( planes[0].Normal(), planes[1].Normal() ); + planes[0][3] = - start * planes[0].Normal(); + planes[1][3] = - start * planes[1].Normal(); + // create front and end planes so the trace is on the positive sides of both + planes[2] = startDir; + planes[2][3] = - start * planes[2].Normal(); + planes[3] = -startDir; + planes[3][3] = - end * planes[3].Normal(); + + // catagorize each point against the four planes + byte * cullBits = (byte *) _alloca16( ALIGN( tri->numVerts, 4 ) ); // round up to a multiple of 4 for SIMD + byte totalOr = 0; + + const idJointMat * joints = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? tri->staticModelWithJoints->jointsInverted : NULL; + if ( joints != NULL ) { + R_TracePointCullSkinned( cullBits, totalOr, radius, planes, tri->verts, tri->numVerts, joints ); + } else { + R_TracePointCullStatic( cullBits, totalOr, radius, planes, tri->verts, tri->numVerts ); + } + + // if we don't have points on both sides of both the ray planes, no intersection + if ( ( totalOr ^ ( totalOr >> 4 ) ) & 3 ) { + return hit; + } + + // if we don't have any points between front and end, no intersection + if ( ( totalOr ^ ( totalOr >> 1 ) ) & 4 ) { + return hit; + } + + // start streaming the indexes + idODSStreamedArray< triIndex_t, 256, SBT_QUAD, 3 > indexesODS( tri->indexes, tri->numIndexes ); + + for ( int i = 0; i < tri->numIndexes; ) { + + const int nextNumIndexes = indexesODS.FetchNextBatch() - 3; + + for ( ; i <= nextNumIndexes; i += 3 ) { + const int i0 = indexesODS[i + 0]; + const int i1 = indexesODS[i + 1]; + const int i2 = indexesODS[i + 2]; + + // get sidedness info for the triangle + const byte triOr = cullBits[i0] | cullBits[i1] | cullBits[i2]; + + // if we don't have points on both sides of both the ray planes, no intersection + if ( likely( ( triOr ^ ( triOr >> 4 ) ) & 3 ) ) { + continue; + } + + // if we don't have any points between front and end, no intersection + if ( unlikely( ( triOr ^ ( triOr >> 1 ) ) & 4 ) ) { + continue; + } + + const idVec3 triVert0 = idDrawVert::GetSkinnedDrawVertPosition( idODSObject< idDrawVert > ( & tri->verts[i0] ), joints ); + const idVec3 triVert1 = idDrawVert::GetSkinnedDrawVertPosition( idODSObject< idDrawVert > ( & tri->verts[i1] ), joints ); + const idVec3 triVert2 = idDrawVert::GetSkinnedDrawVertPosition( idODSObject< idDrawVert > ( & tri->verts[i2] ), joints ); + + if ( R_LineIntersectsTriangleExpandedWithCircle( hit, start, end, radius, triVert0, triVert1, triVert2 ) ) { + hit.indexes[0] = i0; + hit.indexes[1] = i1; + hit.indexes[2] = i2; + } + } + } + + return hit; +} diff --git a/neo/renderer/tr_trisurf.cpp b/neo/renderer/tr_trisurf.cpp new file mode 100644 index 00000000..76add876 --- /dev/null +++ b/neo/renderer/tr_trisurf.cpp @@ -0,0 +1,1976 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "tr_local.h" + +/* +============================================================================== + +TRIANGLE MESH PROCESSING + +The functions in this file have no vertex / index count limits. + +Truly identical vertexes that match in position, normal, and texcoord can +be merged away. + +Vertexes that match in position and texcoord, but have distinct normals will +remain distinct for all purposes. This is usually a poor choice for models, +as adding a bevel face will not add any more vertexes, and will tend to +look better. + +Match in position and normal, but differ in texcoords are referenced together +for calculating tangent vectors for bump mapping. +Artists should take care to have identical texels in all maps (bump/diffuse/specular) +in this case + +Vertexes that only match in position are merged for shadow edge finding. + +Degenerate triangles. + +Overlapped triangles, even if normals or texcoords differ, must be removed. +for the silhoette based stencil shadow algorithm to function properly. +Is this true??? +Is the overlapped triangle problem just an example of the trippled edge problem? + +Interpenetrating triangles are not currently clipped to surfaces. +Do they effect the shadows? + +if vertexes are intended to deform apart, make sure that no vertexes +are on top of each other in the base frame, or the sil edges may be +calculated incorrectly. + +We might be able to identify this from topology. + +Dangling edges are acceptable, but three way edges are not. + +Are any combinations of two way edges unacceptable, like one facing +the backside of the other? + +Topology is determined by a collection of triangle indexes. + +The edge list can be built up from this, and stays valid even under +deformations. + +Somewhat non-intuitively, concave edges cannot be optimized away, or the +stencil shadow algorithm miscounts. + +Face normals are needed for generating shadow volumes and for calculating +the silhouette, but they will change with any deformation. + +Vertex normals and vertex tangents will change with each deformation, +but they may be able to be transformed instead of recalculated. + +bounding volume, both box and sphere will change with deformation. + +silhouette indexes +shade indexes +texture indexes + + shade indexes will only be > silhouette indexes if there is facet shading present + + lookups from texture to sil and texture to shade? + +The normal and tangent vector smoothing is simple averaging, no attempt is +made to better handle the cases where the distribution around the shared vertex +is highly uneven. + + we may get degenerate triangles even with the uniquing and removal + if the vertexes have different texcoords. + +============================================================================== +*/ + +// this shouldn't change anything, but previously renderbumped models seem to need it +#define USE_INVA + +// instead of using the texture T vector, cross the normal and S vector for an orthogonal axis +#define DERIVE_UNSMOOTHED_BITANGENT + +/* +================= +R_TriSurfMemory + +For memory profiling +================= +*/ +int R_TriSurfMemory( const srfTriangles_t *tri ) { + int total = 0; + + if ( tri == NULL ) { + return total; + } + + if ( tri->preLightShadowVertexes != NULL ) { + total += tri->numVerts * 2 * sizeof( tri->preLightShadowVertexes[0] ); + } + if ( tri->staticShadowVertexes != NULL ) { + total += tri->numVerts * 2 * sizeof( tri->staticShadowVertexes[0] ); + } + if ( tri->verts != NULL ) { + if ( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts ) { + total += tri->numVerts * sizeof( tri->verts[0] ); + } + } + if ( tri->indexes != NULL ) { + if ( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes ) { + total += tri->numIndexes * sizeof( tri->indexes[0] ); + } + } + if ( tri->silIndexes != NULL ) { + total += tri->numIndexes * sizeof( tri->silIndexes[0] ); + } + if ( tri->silEdges != NULL ) { + total += tri->numSilEdges * sizeof( tri->silEdges[0] ); + } + if ( tri->dominantTris != NULL ) { + total += tri->numVerts * sizeof( tri->dominantTris[0] ); + } + if ( tri->mirroredVerts != NULL ) { + total += tri->numMirroredVerts * sizeof( tri->mirroredVerts[0] ); + } + if ( tri->dupVerts != NULL ) { + total += tri->numDupVerts * sizeof( tri->dupVerts[0] ); + } + + total += sizeof( *tri ); + + return total; +} + +/* +============== +R_FreeStaticTriSurfVertexCaches +============== +*/ +void R_FreeStaticTriSurfVertexCaches( srfTriangles_t * tri ) { + // we don't support reclaiming static geometry memory + // without a level change + tri->ambientCache = 0; + tri->indexCache = 0; + tri->shadowCache = 0; +} + +/* +============== +R_FreeStaticTriSurf +============== +*/ +void R_FreeStaticTriSurf( srfTriangles_t *tri ) { + if ( !tri ) { + return; + } + + R_FreeStaticTriSurfVertexCaches( tri ); + + if ( !tri->referencedVerts ) { + if ( tri->verts != NULL ) { + // R_CreateLightTris points tri->verts at the verts of the ambient surface + if ( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts ) { + Mem_Free( tri->verts ); + } + } + } + + if ( !tri->referencedIndexes ) { + if ( tri->indexes != NULL ) { + // if a surface is completely inside a light volume R_CreateLightTris points tri->indexes at the indexes of the ambient surface + if ( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes ) { + Mem_Free( tri->indexes ); + } + } + if ( tri->silIndexes != NULL ) { + Mem_Free( tri->silIndexes ); + } + if ( tri->silEdges != NULL ) { + Mem_Free( tri->silEdges ); + } + if ( tri->dominantTris != NULL ) { + Mem_Free( tri->dominantTris ); + } + if ( tri->mirroredVerts != NULL ) { + Mem_Free( tri->mirroredVerts ); + } + if ( tri->dupVerts != NULL ) { + Mem_Free( tri->dupVerts ); + } + } + + if ( tri->preLightShadowVertexes != NULL ) { + Mem_Free( tri->preLightShadowVertexes ); + } + if ( tri->staticShadowVertexes != NULL ) { + Mem_Free( tri->staticShadowVertexes ); + } + + // clear the tri out so we don't retain stale data + memset( tri, 0, sizeof( srfTriangles_t ) ); + + Mem_Free( tri ); +} + +/* +============== +R_FreeStaticTriSurfVerts +============== +*/ +void R_FreeStaticTriSurfVerts( srfTriangles_t *tri ) { + // we don't support reclaiming static geometry memory + // without a level change + tri->ambientCache = 0; + + if ( tri->verts != NULL ) { + // R_CreateLightTris points tri->verts at the verts of the ambient surface + if ( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts ) { + Mem_Free( tri->verts ); + } + } +} + +/* +============== +R_AllocStaticTriSurf +============== +*/ +srfTriangles_t *R_AllocStaticTriSurf() { + srfTriangles_t *tris = (srfTriangles_t *)Mem_ClearedAlloc( sizeof( srfTriangles_t ), TAG_SRFTRIS ); + return tris; +} + +/* +================= +R_CopyStaticTriSurf + +This only duplicates the indexes and verts, not any of the derived data. +================= +*/ +srfTriangles_t *R_CopyStaticTriSurf( const srfTriangles_t *tri ) { + srfTriangles_t *newTri; + + newTri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( newTri, tri->numVerts ); + R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + memcpy( newTri->verts, tri->verts, tri->numVerts * sizeof( newTri->verts[0] ) ); + memcpy( newTri->indexes, tri->indexes, tri->numIndexes * sizeof( newTri->indexes[0] ) ); + + return newTri; +} + +/* +================= +R_AllocStaticTriSurfVerts +================= +*/ +void R_AllocStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ) { + assert( tri->verts == NULL ); + tri->verts = (idDrawVert *)Mem_Alloc16( numVerts * sizeof( idDrawVert ), TAG_TRI_VERTS ); +} + +/* +================= +R_AllocStaticTriSurfIndexes +================= +*/ +void R_AllocStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ) { + assert( tri->indexes == NULL ); + tri->indexes = (triIndex_t *)Mem_Alloc16( numIndexes * sizeof( triIndex_t ), TAG_TRI_INDEXES ); +} + +/* +================= +R_AllocStaticTriSurfSilIndexes +================= +*/ +void R_AllocStaticTriSurfSilIndexes( srfTriangles_t *tri, int numIndexes ) { + assert( tri->silIndexes == NULL ); + tri->silIndexes = (triIndex_t *)Mem_Alloc16( numIndexes * sizeof( triIndex_t ), TAG_TRI_SIL_INDEXES ); +} + +/* +================= +R_AllocStaticTriSurfDominantTris +================= +*/ +void R_AllocStaticTriSurfDominantTris( srfTriangles_t *tri, int numVerts ) { + assert( tri->dominantTris == NULL ); + tri->dominantTris = (dominantTri_t *)Mem_Alloc16( numVerts * sizeof( dominantTri_t ), TAG_TRI_DOMINANT_TRIS ); +} + +/* +================= +R_AllocStaticTriSurfMirroredVerts +================= +*/ +void R_AllocStaticTriSurfMirroredVerts( srfTriangles_t *tri, int numMirroredVerts ) { + assert( tri->mirroredVerts == NULL ); + tri->mirroredVerts = (int *)Mem_Alloc16( numMirroredVerts * sizeof( *tri->mirroredVerts ), TAG_TRI_MIR_VERT ); +} + +/* +================= +R_AllocStaticTriSurfDupVerts +================= +*/ +void R_AllocStaticTriSurfDupVerts( srfTriangles_t *tri, int numDupVerts ) { + assert( tri->dupVerts == NULL ); + tri->dupVerts = (int *)Mem_Alloc16( numDupVerts * 2 * sizeof( *tri->dupVerts ), TAG_TRI_DUP_VERT ); +} + +/* +================= +R_AllocStaticTriSurfSilEdges +================= +*/ +void R_AllocStaticTriSurfSilEdges( srfTriangles_t *tri, int numSilEdges ) { + assert( tri->silEdges == NULL ); + tri->silEdges = (silEdge_t *)Mem_Alloc16( numSilEdges * sizeof( silEdge_t ), TAG_TRI_SIL_EDGE ); +} + +/* +================= +R_AllocStaticTriSurfPreLightShadowVerts +================= +*/ +void R_AllocStaticTriSurfPreLightShadowVerts( srfTriangles_t *tri, int numVerts ) { + assert( tri->preLightShadowVertexes == NULL ); + tri->preLightShadowVertexes = (idShadowVert *)Mem_Alloc16( numVerts * sizeof( idShadowVert ), TAG_TRI_SHADOW ); +} + +/* +================= +R_ResizeStaticTriSurfVerts +================= +*/ +void R_ResizeStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ) { + idDrawVert * newVerts = (idDrawVert *)Mem_Alloc16( numVerts * sizeof( idDrawVert ), TAG_TRI_VERTS ); + const int copy = std::min( numVerts, tri->numVerts ); + memcpy( newVerts, tri->verts, copy * sizeof( idDrawVert ) ); + Mem_Free( tri->verts ); + tri->verts = newVerts; +} + +/* +================= +R_ResizeStaticTriSurfIndexes +================= +*/ +void R_ResizeStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ) { + triIndex_t * newIndexes = (triIndex_t *)Mem_Alloc16( numIndexes * sizeof( triIndex_t ), TAG_TRI_INDEXES ); + const int copy = std::min( numIndexes, tri->numIndexes ); + memcpy( newIndexes, tri->indexes, copy * sizeof( triIndex_t ) ); + Mem_Free( tri->indexes ); + tri->indexes = newIndexes; +} + +/* +================= +R_ReferenceStaticTriSurfVerts +================= +*/ +void R_ReferenceStaticTriSurfVerts( srfTriangles_t *tri, const srfTriangles_t *reference ) { + tri->verts = reference->verts; +} + +/* +================= +R_ReferenceStaticTriSurfIndexes +================= +*/ +void R_ReferenceStaticTriSurfIndexes( srfTriangles_t *tri, const srfTriangles_t *reference ) { + tri->indexes = reference->indexes; +} + +/* +================= +R_FreeStaticTriSurfSilIndexes +================= +*/ +void R_FreeStaticTriSurfSilIndexes( srfTriangles_t *tri ) { + Mem_Free( tri->silIndexes ); + tri->silIndexes = NULL; +} + +/* +=============== +R_RangeCheckIndexes + +Check for syntactically incorrect indexes, like out of range values. +Does not check for semantics, like degenerate triangles. + +No vertexes is acceptable if no indexes. +No indexes is acceptable. +More vertexes than are referenced by indexes are acceptable. +=============== +*/ +void R_RangeCheckIndexes( const srfTriangles_t *tri ) { + int i; + + if ( tri->numIndexes < 0 ) { + common->Error( "R_RangeCheckIndexes: numIndexes < 0" ); + } + if ( tri->numVerts < 0 ) { + common->Error( "R_RangeCheckIndexes: numVerts < 0" ); + } + + // must specify an integral number of triangles + if ( tri->numIndexes % 3 != 0 ) { + common->Error( "R_RangeCheckIndexes: numIndexes %% 3" ); + } + + for ( i = 0; i < tri->numIndexes; i++ ) { + if ( tri->indexes[i] >= tri->numVerts ) { + common->Error( "R_RangeCheckIndexes: index out of range" ); + } + } + + // this should not be possible unless there are unused verts + if ( tri->numVerts > tri->numIndexes ) { + // FIXME: find the causes of these + // common->Printf( "R_RangeCheckIndexes: tri->numVerts > tri->numIndexes\n" ); + } +} + +/* +================= +R_BoundTriSurf +================= +*/ +void R_BoundTriSurf( srfTriangles_t *tri ) { + SIMDProcessor->MinMax( tri->bounds[0], tri->bounds[1], tri->verts, tri->numVerts ); +} + +/* +================= +R_CreateSilRemap +================= +*/ +static int *R_CreateSilRemap( const srfTriangles_t *tri ) { + int c_removed, c_unique; + int *remap; + int i, j, hashKey; + const idDrawVert *v1, *v2; + + remap = (int *)R_ClearedStaticAlloc( tri->numVerts * sizeof( remap[0] ) ); + + if ( !r_useSilRemap.GetBool() ) { + for ( i = 0; i < tri->numVerts; i++ ) { + remap[i] = i; + } + return remap; + } + + idHashIndex hash( 1024, tri->numVerts ); + + c_removed = 0; + c_unique = 0; + for ( i = 0; i < tri->numVerts; i++ ) { + v1 = &tri->verts[i]; + + // see if there is an earlier vert that it can map to + hashKey = hash.GenerateKey( v1->xyz ); + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + v2 = &tri->verts[j]; + if ( v2->xyz[0] == v1->xyz[0] + && v2->xyz[1] == v1->xyz[1] + && v2->xyz[2] == v1->xyz[2] ) { + c_removed++; + remap[i] = j; + break; + } + } + if ( j < 0 ) { + c_unique++; + remap[i] = i; + hash.Add( hashKey, i ); + } + } + + return remap; +} + +/* +================= +R_CreateSilIndexes + +Uniquing vertexes only on xyz before creating sil edges reduces +the edge count by about 20% on Q3 models +================= +*/ +void R_CreateSilIndexes( srfTriangles_t *tri ) { + int i; + int *remap; + + if ( tri->silIndexes ) { + Mem_Free( tri->silIndexes ); + tri->silIndexes = NULL; + } + + remap = R_CreateSilRemap( tri ); + + // remap indexes to the first one + R_AllocStaticTriSurfSilIndexes( tri, tri->numIndexes ); + assert( tri->silIndexes != NULL ); + for ( i = 0; i < tri->numIndexes; i++ ) { + tri->silIndexes[i] = remap[tri->indexes[i]]; + } + + R_StaticFree( remap ); +} + +/* +===================== +R_CreateDupVerts +===================== +*/ +void R_CreateDupVerts( srfTriangles_t *tri ) { + int i; + + idTempArray remap( tri->numVerts ); + + // initialize vertex remap in case there are unused verts + for ( i = 0; i < tri->numVerts; i++ ) { + remap[i] = i; + } + + // set the remap based on how the silhouette indexes are remapped + for ( i = 0; i < tri->numIndexes; i++ ) { + remap[tri->indexes[i]] = tri->silIndexes[i]; + } + + // create duplicate vertex index based on the vertex remap + idTempArray tempDupVerts( tri->numVerts * 2 ); + tri->numDupVerts = 0; + for ( i = 0; i < tri->numVerts; i++ ) { + if ( remap[i] != i ) { + tempDupVerts[tri->numDupVerts*2+0] = i; + tempDupVerts[tri->numDupVerts*2+1] = remap[i]; + tri->numDupVerts++; + } + } + + R_AllocStaticTriSurfDupVerts( tri, tri->numDupVerts ); + memcpy( tri->dupVerts, tempDupVerts.Ptr(), tri->numDupVerts * 2 * sizeof( tri->dupVerts[0] ) ); +} + +/* +=============== +R_DefineEdge +=============== +*/ +static int c_duplicatedEdges, c_tripledEdges; +static const int MAX_SIL_EDGES = 0x7ffff; + +static void R_DefineEdge( const int v1, const int v2, const int planeNum, const int numPlanes, + idList & silEdges, idHashIndex & silEdgeHash ) { + int i, hashKey; + + // check for degenerate edge + if ( v1 == v2 ) { + return; + } + hashKey = silEdgeHash.GenerateKey( v1, v2 ); + // search for a matching other side + for ( i = silEdgeHash.First( hashKey ); i >= 0 && i < MAX_SIL_EDGES; i = silEdgeHash.Next( i ) ) { + if ( silEdges[i].v1 == v1 && silEdges[i].v2 == v2 ) { + c_duplicatedEdges++; + // allow it to still create a new edge + continue; + } + if ( silEdges[i].v2 == v1 && silEdges[i].v1 == v2 ) { + if ( silEdges[i].p2 != numPlanes ) { + c_tripledEdges++; + // allow it to still create a new edge + continue; + } + // this is a matching back side + silEdges[i].p2 = planeNum; + return; + } + + } + + // define the new edge + silEdgeHash.Add( hashKey, silEdges.Num() ); + + silEdge_t silEdge; + + silEdge.p1 = planeNum; + silEdge.p2 = numPlanes; + silEdge.v1 = v1; + silEdge.v2 = v2; + + silEdges.Append( silEdge ); +} + +/* +================= +SilEdgeSort +================= +*/ +static int SilEdgeSort( const void *a, const void *b ) { + if ( ((silEdge_t *)a)->p1 < ((silEdge_t *)b)->p1 ) { + return -1; + } + if ( ((silEdge_t *)a)->p1 > ((silEdge_t *)b)->p1 ) { + return 1; + } + if ( ((silEdge_t *)a)->p2 < ((silEdge_t *)b)->p2 ) { + return -1; + } + if ( ((silEdge_t *)a)->p2 > ((silEdge_t *)b)->p2 ) { + return 1; + } + return 0; +} + +/* +================= +R_IdentifySilEdges + +If the surface will not deform, coplanar edges (polygon interiors) +can never create silhouette plains, and can be omited +================= +*/ +int c_coplanarSilEdges; +int c_totalSilEdges; + +void R_IdentifySilEdges( srfTriangles_t *tri, bool omitCoplanarEdges ) { + int i; + int shared, single; + + omitCoplanarEdges = false; // optimization doesn't work for some reason + + static const int SILEDGE_HASH_SIZE = 1024; + + const int numTris = tri->numIndexes / 3; + + idList silEdges( MAX_SIL_EDGES ); + idHashIndex silEdgeHash( SILEDGE_HASH_SIZE, MAX_SIL_EDGES ); + int numPlanes = numTris; + + + silEdgeHash.Clear(); + + c_duplicatedEdges = 0; + c_tripledEdges = 0; + + for ( i = 0; i < numTris; i++ ) { + int i1, i2, i3; + + i1 = tri->silIndexes[ i*3 + 0 ]; + i2 = tri->silIndexes[ i*3 + 1 ]; + i3 = tri->silIndexes[ i*3 + 2 ]; + + // create the edges + R_DefineEdge( i1, i2, i, numPlanes, silEdges, silEdgeHash ); + R_DefineEdge( i2, i3, i, numPlanes, silEdges, silEdgeHash ); + R_DefineEdge( i3, i1, i, numPlanes, silEdges, silEdgeHash ); + } + + if ( c_duplicatedEdges || c_tripledEdges ) { + common->DWarning( "%i duplicated edge directions, %i tripled edges", c_duplicatedEdges, c_tripledEdges ); + } + + // if we know that the vertexes aren't going + // to deform, we can remove interior triangulation edges + // on otherwise planar polygons. + // I earlier believed that I could also remove concave + // edges, because they are never silhouettes in the conventional sense, + // but they are still needed to balance out all the true sil edges + // for the shadow algorithm to function + int c_coplanarCulled; + + c_coplanarCulled = 0; + if ( omitCoplanarEdges ) { + for ( i = 0; i < silEdges.Num(); i++ ) { + int i1, i2, i3; + idPlane plane; + int base; + int j; + float d; + + if ( silEdges[i].p2 == numPlanes ) { // the fake dangling edge + continue; + } + + base = silEdges[i].p1 * 3; + i1 = tri->silIndexes[ base + 0 ]; + i2 = tri->silIndexes[ base + 1 ]; + i3 = tri->silIndexes[ base + 2 ]; + + plane.FromPoints( tri->verts[i1].xyz, tri->verts[i2].xyz, tri->verts[i3].xyz ); + + // check to see if points of second triangle are not coplanar + base = silEdges[i].p2 * 3; + for ( j = 0; j < 3; j++ ) { + i1 = tri->silIndexes[ base + j ]; + d = plane.Distance( tri->verts[i1].xyz ); + if ( d != 0 ) { // even a small epsilon causes problems + break; + } + } + + if ( j == 3 ) { + // we can cull this sil edge + memmove( &silEdges[i], &silEdges[i+1], (silEdges.Num()-i-1) * sizeof( silEdges[i] ) ); + c_coplanarCulled++; + silEdges.SetNum( silEdges.Num() - 1 ); + i--; + } + } + if ( c_coplanarCulled ) { + c_coplanarSilEdges += c_coplanarCulled; +// common->Printf( "%i of %i sil edges coplanar culled\n", c_coplanarCulled, +// c_coplanarCulled + numSilEdges ); + } + } + c_totalSilEdges += silEdges.Num(); + + // sort the sil edges based on plane number + qsort( silEdges.Ptr(), silEdges.Num(), sizeof( silEdges[0] ), SilEdgeSort ); + + // count up the distribution. + // a perfectly built model should only have shared + // edges, but most models will have some interpenetration + // and dangling edges + shared = 0; + single = 0; + for ( i = 0; i < silEdges.Num(); i++ ) { + if ( silEdges[i].p2 == numPlanes ) { + single++; + } else { + shared++; + } + } + + if ( !single ) { + tri->perfectHull = true; + } else { + tri->perfectHull = false; + } + + tri->numSilEdges = silEdges.Num(); + R_AllocStaticTriSurfSilEdges( tri, silEdges.Num() ); + memcpy( tri->silEdges, silEdges.Ptr(), silEdges.Num() * sizeof( tri->silEdges[0] ) ); +} + +/* +=============== +R_FaceNegativePolarity + +Returns true if the texture polarity of the face is negative, false if it is positive or zero +=============== +*/ +static bool R_FaceNegativePolarity( const srfTriangles_t *tri, int firstIndex ) { + const idDrawVert * a = tri->verts + tri->indexes[firstIndex + 0]; + const idDrawVert * b = tri->verts + tri->indexes[firstIndex + 1]; + const idDrawVert * c = tri->verts + tri->indexes[firstIndex + 2]; + + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + float d0[5]; + d0[3] = bST[0] - aST[0]; + d0[4] = bST[1] - aST[1]; + + float d1[5]; + d1[3] = cST[0] - aST[0]; + d1[4] = cST[1] - aST[1]; + + const float area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( area >= 0 ) { + return false; + } + return true; +} + +/* +=================== +R_DuplicateMirroredVertexes + +Modifies the surface to bust apart any verts that are shared by both positive and +negative texture polarities, so tangent space smoothing at the vertex doesn't +degenerate. + +This will create some identical vertexes (which will eventually get different tangent +vectors), so never optimize the resulting mesh, or it will get the mirrored edges back. + +Reallocates tri->verts and changes tri->indexes in place +Silindexes are unchanged by this. + +sets mirroredVerts and mirroredVerts[] +=================== +*/ +struct tangentVert_t { + bool polarityUsed[2]; + int negativeRemap; +}; + +static void R_DuplicateMirroredVertexes( srfTriangles_t *tri ) { + tangentVert_t *vert; + int i, j; + int totalVerts; + int numMirror; + + idTempArray tverts( tri->numVerts ); + tverts.Zero(); + + // determine texture polarity of each surface + + // mark each vert with the polarities it uses + for ( i = 0; i < tri->numIndexes; i+=3 ) { + int polarity = R_FaceNegativePolarity( tri, i ); + for ( j = 0; j < 3; j++ ) { + tverts[tri->indexes[i+j]].polarityUsed[ polarity ] = true; + } + } + + // now create new vertex indices as needed + totalVerts = tri->numVerts; + for ( i = 0; i < tri->numVerts; i++ ) { + vert = &tverts[i]; + if ( vert->polarityUsed[0] && vert->polarityUsed[1] ) { + vert->negativeRemap = totalVerts; + totalVerts++; + } + } + + tri->numMirroredVerts = totalVerts - tri->numVerts; + + if ( tri->numMirroredVerts == 0 ) { + tri->mirroredVerts = NULL; + return; + } + + // now create the new list + R_AllocStaticTriSurfMirroredVerts( tri, tri->numMirroredVerts ); + R_ResizeStaticTriSurfVerts( tri, totalVerts ); + + // create the duplicates + numMirror = 0; + for ( i = 0; i < tri->numVerts; i++ ) { + j = tverts[i].negativeRemap; + if ( j ) { + tri->verts[j] = tri->verts[i]; + tri->mirroredVerts[numMirror] = i; + numMirror++; + } + } + tri->numVerts = totalVerts; + + // change the indexes + for ( i = 0; i < tri->numIndexes; i++ ) { + if ( tverts[tri->indexes[i]].negativeRemap && R_FaceNegativePolarity( tri, 3 * ( i / 3 ) ) ) { + tri->indexes[i] = tverts[tri->indexes[i]].negativeRemap; + } + } +} + +/* +============ +R_DeriveNormalsAndTangents + +Derives the normal and orthogonal tangent vectors for the triangle vertices. +For each vertex the normal and tangent vectors are derived from all triangles +using the vertex which results in smooth tangents across the mesh. +============ +*/ +void R_DeriveNormalsAndTangents( srfTriangles_t *tri ) { + idTempArray< idVec3 > vertexNormals( tri->numVerts ); + idTempArray< idVec3 > vertexTangents( tri->numVerts ); + idTempArray< idVec3 > vertexBitangents( tri->numVerts ); + + vertexNormals.Zero(); + vertexTangents.Zero(); + vertexBitangents.Zero(); + + for ( int i = 0; i < tri->numIndexes; i += 3 ) { + const int v0 = tri->indexes[i + 0]; + const int v1 = tri->indexes[i + 1]; + const int v2 = tri->indexes[i + 2]; + + const idDrawVert * a = tri->verts + v0; + const idDrawVert * b = tri->verts + v1; + const idDrawVert * c = tri->verts + v2; + + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + float d0[5]; + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = bST[0] - aST[0]; + d0[4] = bST[1] - aST[1]; + + float d1[5]; + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = cST[0] - aST[0]; + d1[4] = cST[1] - aST[1]; + + idVec3 normal; + normal[0] = d1[1] * d0[2] - d1[2] * d0[1]; + normal[1] = d1[2] * d0[0] - d1[0] * d0[2]; + normal[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + const float f0 = idMath::InvSqrt( normal.x * normal.x + normal.y * normal.y + normal.z * normal.z ); + + normal.x *= f0; + normal.y *= f0; + normal.z *= f0; + + // area sign bit + const float area = d0[3] * d1[4] - d0[4] * d1[3]; + unsigned int signBit = ( *(unsigned int *)&area ) & ( 1 << 31 ); + + idVec3 tangent; + tangent[0] = d0[0] * d1[4] - d0[4] * d1[0]; + tangent[1] = d0[1] * d1[4] - d0[4] * d1[1]; + tangent[2] = d0[2] * d1[4] - d0[4] * d1[2]; + + const float f1 = idMath::InvSqrt( tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z ); + *(unsigned int *)&f1 ^= signBit; + + tangent.x *= f1; + tangent.y *= f1; + tangent.z *= f1; + + idVec3 bitangent; + bitangent[0] = d0[3] * d1[0] - d0[0] * d1[3]; + bitangent[1] = d0[3] * d1[1] - d0[1] * d1[3]; + bitangent[2] = d0[3] * d1[2] - d0[2] * d1[3]; + + const float f2 = idMath::InvSqrt( bitangent.x * bitangent.x + bitangent.y * bitangent.y + bitangent.z * bitangent.z ); + *(unsigned int *)&f2 ^= signBit; + + bitangent.x *= f2; + bitangent.y *= f2; + bitangent.z *= f2; + + vertexNormals[v0] += normal; + vertexTangents[v0] += tangent; + vertexBitangents[v0] += bitangent; + + vertexNormals[v1] += normal; + vertexTangents[v1] += tangent; + vertexBitangents[v1] += bitangent; + + vertexNormals[v2] += normal; + vertexTangents[v2] += tangent; + vertexBitangents[v2] += bitangent; + } + + // add the normal of a duplicated vertex to the normal of the first vertex with the same XYZ + for ( int i = 0; i < tri->numDupVerts; i++ ) { + vertexNormals[tri->dupVerts[i*2+0]] += vertexNormals[tri->dupVerts[i*2+1]]; + } + + // copy vertex normals to duplicated vertices + for ( int i = 0; i < tri->numDupVerts; i++ ) { + vertexNormals[tri->dupVerts[i*2+1]] = vertexNormals[tri->dupVerts[i*2+0]]; + } + + // Project the summed vectors onto the normal plane and normalize. + // The tangent vectors will not necessarily be orthogonal to each + // other, but they will be orthogonal to the surface normal. + for ( int i = 0; i < tri->numVerts; i++ ) { + const float normalScale = idMath::InvSqrt( vertexNormals[i].x * vertexNormals[i].x + vertexNormals[i].y * vertexNormals[i].y + vertexNormals[i].z * vertexNormals[i].z ); + vertexNormals[i].x *= normalScale; + vertexNormals[i].y *= normalScale; + vertexNormals[i].z *= normalScale; + + vertexTangents[i] -= ( vertexTangents[i] * vertexNormals[i] ) * vertexNormals[i]; + vertexBitangents[i] -= ( vertexBitangents[i] * vertexNormals[i] ) * vertexNormals[i]; + + const float tangentScale = idMath::InvSqrt( vertexTangents[i].x * vertexTangents[i].x + vertexTangents[i].y * vertexTangents[i].y + vertexTangents[i].z * vertexTangents[i].z ); + vertexTangents[i].x *= tangentScale; + vertexTangents[i].y *= tangentScale; + vertexTangents[i].z *= tangentScale; + + const float bitangentScale = idMath::InvSqrt( vertexBitangents[i].x * vertexBitangents[i].x + vertexBitangents[i].y * vertexBitangents[i].y + vertexBitangents[i].z * vertexBitangents[i].z ); + vertexBitangents[i].x *= bitangentScale; + vertexBitangents[i].y *= bitangentScale; + vertexBitangents[i].z *= bitangentScale; + } + + // compress the normals and tangents + for ( int i = 0; i < tri->numVerts; i++ ) { + tri->verts[i].SetNormal( vertexNormals[i] ); + tri->verts[i].SetTangent( vertexTangents[i] ); + tri->verts[i].SetBiTangent( vertexBitangents[i] ); + } +} + +/* +============ +R_DeriveUnsmoothedNormalsAndTangents +============ +*/ +void R_DeriveUnsmoothedNormalsAndTangents( srfTriangles_t * tri ) { + for ( int i = 0; i < tri->numVerts; i++ ) { + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_t &dt = tri->dominantTris[i]; + + idDrawVert *a = tri->verts + i; + idDrawVert *b = tri->verts + dt.v2; + idDrawVert *c = tri->verts + dt.v3; + + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = bST[0] - aST[0]; + d4 = bST[1] - aST[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = cST[0] - aST[0]; + d9 = cST[1] - aST[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->SetNormal( n0, n1, n2 ); + a->SetTangent( t0, t1, t2 ); + a->SetBiTangent( t3, t4, t5 ); + } +} + +/* +===================== +R_CreateVertexNormals + +Averages together the contributions of all faces that are +used by a vertex, creating drawVert->normal +===================== +*/ +void R_CreateVertexNormals( srfTriangles_t *tri ) { + if ( tri->silIndexes == NULL ) { + R_CreateSilIndexes( tri ); + } + + idTempArray< idVec3 > vertexNormals( tri->numVerts ); + vertexNormals.Zero(); + + assert( tri->silIndexes != NULL ); + for ( int i = 0; i < tri->numIndexes; i += 3 ) { + const int i0 = tri->silIndexes[i + 0]; + const int i1 = tri->silIndexes[i + 1]; + const int i2 = tri->silIndexes[i + 2]; + + const idDrawVert & v0 = tri->verts[i0]; + const idDrawVert & v1 = tri->verts[i1]; + const idDrawVert & v2 = tri->verts[i2]; + + const idPlane plane( v0.xyz, v1.xyz, v2.xyz ); + + vertexNormals[i0] += plane.Normal(); + vertexNormals[i1] += plane.Normal(); + vertexNormals[i2] += plane.Normal(); + } + + // replicate from silIndexes to all indexes + for ( int i = 0; i < tri->numIndexes; i++ ) { + vertexNormals[tri->indexes[i]] = vertexNormals[tri->silIndexes[i]]; + } + + // normalize + for ( int i = 0; i < tri->numVerts; i++ ) { + vertexNormals[i].Normalize(); + } + + // compress the normals + for ( int i = 0; i < tri->numVerts; i++ ) { + tri->verts[i].SetNormal( vertexNormals[i] ); + } +} + +/* +================= +R_DeriveTangentsWithoutNormals + +Build texture space tangents for bump mapping +If a surface is deformed, this must be recalculated + +This assumes that any mirrored vertexes have already been duplicated, so +any shared vertexes will have the tangent spaces smoothed across. + +Texture wrapping slightly complicates this, but as long as the normals +are shared, and the tangent vectors are projected onto the normals, the +separate vertexes should wind up with identical tangent spaces. + +mirroring a normalmap WILL cause a slightly visible seam unless the normals +are completely flat around the edge's full bilerp support. + +Vertexes which are smooth shaded must have their tangent vectors +in the same plane, which will allow a seamless +rendering as long as the normal map is even on both sides of the +seam. + +A smooth shaded surface may have multiple tangent vectors at a vertex +due to texture seams or mirroring, but it should only have a single +normal vector. + +Each triangle has a pair of tangent vectors in it's plane + +Should we consider having vertexes point at shared tangent spaces +to save space or speed transforms? + +this version only handles bilateral symetry +================= +*/ +void R_DeriveTangentsWithoutNormals( srfTriangles_t *tri ) { + idTempArray< idVec3 > triangleTangents( tri->numIndexes / 3 ); + idTempArray< idVec3 > triangleBitangents( tri->numIndexes / 3 ); + + // + // calculate tangent vectors for each face in isolation + // + int c_positive = 0; + int c_negative = 0; + int c_textureDegenerateFaces = 0; + for ( int i = 0; i < tri->numIndexes; i += 3 ) { + idVec3 temp; + + idDrawVert * a = tri->verts + tri->indexes[i + 0]; + idDrawVert * b = tri->verts + tri->indexes[i + 1]; + idDrawVert * c = tri->verts + tri->indexes[i + 2]; + + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + float d0[5]; + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = bST[0] - aST[0]; + d0[4] = bST[1] - aST[1]; + + float d1[5]; + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = cST[0] - aST[0]; + d1[4] = cST[1] - aST[1]; + + const float area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( fabs( area ) < 1e-20f ) { + triangleTangents[i / 3].Zero(); + triangleBitangents[i / 3].Zero(); + c_textureDegenerateFaces++; + continue; + } + if ( area > 0.0f ) { + c_positive++; + } else { + c_negative++; + } + +#ifdef USE_INVA + float inva = ( area < 0.0f ) ? -1.0f : 1.0f; // was = 1.0f / area; + + temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ) * inva; + temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ) * inva; + temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ) * inva; + temp.Normalize(); + triangleTangents[i / 3] = temp; + + temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ) * inva; + temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ) * inva; + temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ) * inva; + temp.Normalize(); + triangleBitangents[i / 3] = temp; +#else + temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ); + temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ); + temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ); + temp.Normalize(); + triangleTangents[i / 3] = temp; + + temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ); + temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ); + temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ); + temp.Normalize(); + triangleBitangents[i / 3] = temp; +#endif + } + + idTempArray< idVec3 > vertexTangents( tri->numVerts ); + idTempArray< idVec3 > vertexBitangents( tri->numVerts ); + + // clear the tangents + for ( int i = 0; i < tri->numVerts; ++i ) { + vertexTangents[i].Zero(); + vertexBitangents[i].Zero(); + } + + // sum up the neighbors + for ( int i = 0; i < tri->numIndexes; i += 3 ) { + // for each vertex on this face + for ( int j = 0; j < 3; j++ ) { + vertexTangents[tri->indexes[i+j]] += triangleTangents[i / 3]; + vertexBitangents[tri->indexes[i+j]] += triangleBitangents[i / 3]; + } + } + + // Project the summed vectors onto the normal plane and normalize. + // The tangent vectors will not necessarily be orthogonal to each + // other, but they will be orthogonal to the surface normal. + for ( int i = 0; i < tri->numVerts; i++ ) { + idVec3 normal = tri->verts[i].GetNormal(); + normal.Normalize(); + + vertexTangents[i] -= ( vertexTangents[i] * normal ) * normal; + vertexTangents[i].Normalize(); + + vertexBitangents[i] -= ( vertexBitangents[i] * normal ) * normal; + vertexBitangents[i].Normalize(); + } + + for ( int i = 0; i < tri->numVerts; i++ ) { + tri->verts[i].SetTangent( vertexTangents[i] ); + tri->verts[i].SetBiTangent( vertexBitangents[i] ); + } + + tri->tangentsCalculated = true; +} + +/* +=================== +R_BuildDominantTris + +Find the largest triangle that uses each vertex +=================== +*/ +typedef struct { + int vertexNum; + int faceNum; +} indexSort_t; + +static int IndexSort( const void *a, const void *b ) { + if ( ((indexSort_t *)a)->vertexNum < ((indexSort_t *)b)->vertexNum ) { + return -1; + } + if ( ((indexSort_t *)a)->vertexNum > ((indexSort_t *)b)->vertexNum ) { + return 1; + } + return 0; +} + +void R_BuildDominantTris( srfTriangles_t *tri ) { + int i, j; + dominantTri_t *dt; + const int numIndexes = tri->numIndexes; + indexSort_t *ind = (indexSort_t *)R_StaticAlloc( numIndexes * sizeof( indexSort_t ) ); + if ( ind == NULL ) { + idLib::Error( "Couldn't allocate index sort array" ); + return; + } + + for ( i = 0; i < tri->numIndexes; i++ ) { + ind[i].vertexNum = tri->indexes[i]; + ind[i].faceNum = i / 3; + } + qsort( ind, tri->numIndexes, sizeof( *ind ), IndexSort ); + + R_AllocStaticTriSurfDominantTris( tri, tri->numVerts ); + dt = tri->dominantTris; + memset( dt, 0, tri->numVerts * sizeof( dt[0] ) ); + + for ( i = 0; i < numIndexes; i += j ) { + float maxArea = 0; +#pragma warning( disable: 6385 ) // This is simply to get pass a false defect for /analyze -- if you can figure out a better way, please let Shawn know... + int vertNum = ind[i].vertexNum; +#pragma warning( default: 6385 ) + for ( j = 0; i + j < tri->numIndexes && ind[i+j].vertexNum == vertNum; j++ ) { + float d0[5], d1[5]; + idDrawVert *a, *b, *c; + idVec3 normal, tangent, bitangent; + + int i1 = tri->indexes[ind[i+j].faceNum * 3 + 0]; + int i2 = tri->indexes[ind[i+j].faceNum * 3 + 1]; + int i3 = tri->indexes[ind[i+j].faceNum * 3 + 2]; + + a = tri->verts + i1; + b = tri->verts + i2; + c = tri->verts + i3; + + const idVec2 aST = a->GetTexCoord(); + const idVec2 bST = b->GetTexCoord(); + const idVec2 cST = c->GetTexCoord(); + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = bST[0] - aST[0]; + d0[4] = bST[1] - aST[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = cST[0] - aST[0]; + d1[4] = cST[1] - aST[1]; + + normal[0] = ( d1[1] * d0[2] - d1[2] * d0[1] ); + normal[1] = ( d1[2] * d0[0] - d1[0] * d0[2] ); + normal[2] = ( d1[0] * d0[1] - d1[1] * d0[0] ); + + float area = normal.Length(); + + // if this is smaller than what we already have, skip it + if ( area < maxArea ) { + continue; + } + maxArea = area; + + if ( i1 == vertNum ) { + dt[vertNum].v2 = i2; + dt[vertNum].v3 = i3; + } else if ( i2 == vertNum ) { + dt[vertNum].v2 = i3; + dt[vertNum].v3 = i1; + } else { + dt[vertNum].v2 = i1; + dt[vertNum].v3 = i2; + } + + float len = area; + if ( len < 0.001f ) { + len = 0.001f; + } + dt[vertNum].normalizationScale[2] = 1.0f / len; // normal + + // texture area + area = d0[3] * d1[4] - d0[4] * d1[3]; + + tangent[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ); + tangent[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ); + tangent[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ); + len = tangent.Length(); + if ( len < 0.001f ) { + len = 0.001f; + } + dt[vertNum].normalizationScale[0] = ( area > 0 ? 1 : -1 ) / len; // tangents[0] + + bitangent[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ); + bitangent[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ); + bitangent[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ); + len = bitangent.Length(); + if ( len < 0.001f ) { + len = 0.001f; + } +#ifdef DERIVE_UNSMOOTHED_BITANGENT + dt[vertNum].normalizationScale[1] = ( area > 0 ? 1 : -1 ); +#else + dt[vertNum].normalizationScale[1] = ( area > 0 ? 1 : -1 ) / len; // tangents[1] +#endif + } + } + + R_StaticFree( ind ); +} + +/* +================== +R_DeriveTangents + +This is called once for static surfaces, and every frame for deforming surfaces + +Builds tangents, normals, and face planes +================== +*/ +void R_DeriveTangents( srfTriangles_t *tri ) { + if ( tri->tangentsCalculated ) { + return; + } + + tr.pc.c_tangentIndexes += tri->numIndexes; + + if ( tri->dominantTris != NULL ) { + R_DeriveUnsmoothedNormalsAndTangents( tri ); + } else { + R_DeriveNormalsAndTangents( tri ); + } + tri->tangentsCalculated = true; +} + +/* +================= +R_RemoveDuplicatedTriangles + +silIndexes must have already been calculated + +silIndexes are used instead of indexes, because duplicated +triangles could have different texture coordinates. +================= +*/ +void R_RemoveDuplicatedTriangles( srfTriangles_t *tri ) { + int c_removed; + int i, j, r; + int a, b, c; + + c_removed = 0; + + // check for completely duplicated triangles + // any rotation of the triangle is still the same, but a mirroring + // is considered different + for ( i = 0; i < tri->numIndexes; i+=3 ) { + for ( r = 0; r < 3; r++ ) { + a = tri->silIndexes[i+r]; + b = tri->silIndexes[i+(r+1)%3]; + c = tri->silIndexes[i+(r+2)%3]; + for ( j = i + 3; j < tri->numIndexes; j+=3 ) { + if ( tri->silIndexes[j] == a && tri->silIndexes[j+1] == b && tri->silIndexes[j+2] == c ) { + c_removed++; + memmove( tri->indexes + j, tri->indexes + j + 3, ( tri->numIndexes - j - 3 ) * sizeof( tri->indexes[0] ) ); + memmove( tri->silIndexes + j, tri->silIndexes + j + 3, ( tri->numIndexes - j - 3 ) * sizeof( tri->silIndexes[0] ) ); + tri->numIndexes -= 3; + j -= 3; + } + } + } + } + + if ( c_removed ) { + common->Printf( "removed %i duplicated triangles\n", c_removed ); + } +} + +/* +================= +R_RemoveDegenerateTriangles + +silIndexes must have already been calculated +================= +*/ +void R_RemoveDegenerateTriangles( srfTriangles_t *tri ) { + int c_removed; + int i; + int a, b, c; + + assert( tri->silIndexes != NULL ); + + // check for completely degenerate triangles + c_removed = 0; + for ( i = 0; i < tri->numIndexes; i += 3 ) { + a = tri->silIndexes[i]; + b = tri->silIndexes[i+1]; + c = tri->silIndexes[i+2]; + if ( a == b || a == c || b == c ) { + c_removed++; + memmove( tri->indexes + i, tri->indexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->indexes[0] ) ); + memmove( tri->silIndexes + i, tri->silIndexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->silIndexes[0] ) ); + tri->numIndexes -= 3; + i -= 3; + } + } + + // this doesn't free the memory used by the unused verts + + if ( c_removed ) { + common->Printf( "removed %i degenerate triangles\n", c_removed ); + } +} + +/* +================= +R_TestDegenerateTextureSpace +================= +*/ +void R_TestDegenerateTextureSpace( srfTriangles_t *tri ) { + int c_degenerate; + int i; + + // check for triangles with a degenerate texture space + c_degenerate = 0; + for ( i = 0; i < tri->numIndexes; i += 3 ) { + const idDrawVert &a = tri->verts[tri->indexes[i+0]]; + const idDrawVert &b = tri->verts[tri->indexes[i+1]]; + const idDrawVert &c = tri->verts[tri->indexes[i+2]]; + + if ( a.st == b.st || b.st == c.st || c.st == a.st ) { + c_degenerate++; + } + } + + if ( c_degenerate ) { +// common->Printf( "%d triangles with a degenerate texture space\n", c_degenerate ); + } +} + +/* +================= +R_RemoveUnusedVerts +================= +*/ +void R_RemoveUnusedVerts( srfTriangles_t *tri ) { + int i; + int *mark; + int index; + int used; + + mark = (int *)R_ClearedStaticAlloc( tri->numVerts * sizeof( *mark ) ); + + for ( i = 0; i < tri->numIndexes; i++ ) { + index = tri->indexes[i]; + if ( index < 0 || index >= tri->numVerts ) { + common->Error( "R_RemoveUnusedVerts: bad index" ); + } + mark[ index ] = 1; + + if ( tri->silIndexes ) { + index = tri->silIndexes[i]; + if ( index < 0 || index >= tri->numVerts ) { + common->Error( "R_RemoveUnusedVerts: bad index" ); + } + mark[ index ] = 1; + } + } + + used = 0; + for ( i = 0; i < tri->numVerts; i++ ) { + if ( !mark[i] ) { + continue; + } + mark[i] = used + 1; + used++; + } + + if ( used != tri->numVerts ) { + for ( i = 0; i < tri->numIndexes; i++ ) { + tri->indexes[i] = mark[ tri->indexes[i] ] - 1; + if ( tri->silIndexes ) { + tri->silIndexes[i] = mark[ tri->silIndexes[i] ] - 1; + } + } + tri->numVerts = used; + + for ( i = 0; i < tri->numVerts; i++ ) { + index = mark[ i ]; + if ( !index ) { + continue; + } + tri->verts[ index - 1 ] = tri->verts[i]; + } + + // this doesn't realloc the arrays to save the memory used by the unused verts + } + + R_StaticFree( mark ); +} + +/* +================= +R_MergeSurfaceList + +Only deals with vertexes and indexes, not silhouettes, planes, etc. +Does NOT perform a cleanup triangles, so there may be duplicated verts in the result. +================= +*/ +srfTriangles_t * R_MergeSurfaceList( const srfTriangles_t **surfaces, int numSurfaces ) { + srfTriangles_t *newTri; + const srfTriangles_t *tri; + int i, j; + int totalVerts; + int totalIndexes; + + totalVerts = 0; + totalIndexes = 0; + for ( i = 0; i < numSurfaces; i++ ) { + totalVerts += surfaces[i]->numVerts; + totalIndexes += surfaces[i]->numIndexes; + } + + newTri = R_AllocStaticTriSurf(); + newTri->numVerts = totalVerts; + newTri->numIndexes = totalIndexes; + R_AllocStaticTriSurfVerts( newTri, newTri->numVerts ); + R_AllocStaticTriSurfIndexes( newTri, newTri->numIndexes ); + + totalVerts = 0; + totalIndexes = 0; + for ( i = 0; i < numSurfaces; i++ ) { + tri = surfaces[i]; + memcpy( newTri->verts + totalVerts, tri->verts, tri->numVerts * sizeof( *tri->verts ) ); + for ( j = 0; j < tri->numIndexes; j++ ) { + newTri->indexes[ totalIndexes + j ] = totalVerts + tri->indexes[j]; + } + totalVerts += tri->numVerts; + totalIndexes += tri->numIndexes; + } + + return newTri; +} + +/* +================= +R_MergeTriangles + +Only deals with vertexes and indexes, not silhouettes, planes, etc. +Does NOT perform a cleanup triangles, so there may be duplicated verts in the result. +================= +*/ +srfTriangles_t * R_MergeTriangles( const srfTriangles_t *tri1, const srfTriangles_t *tri2 ) { + const srfTriangles_t *tris[2]; + + tris[0] = tri1; + tris[1] = tri2; + + return R_MergeSurfaceList( tris, 2 ); +} + +/* +================= +R_ReverseTriangles + +Lit two sided surfaces need to have the triangles actually duplicated, +they can't just turn on two sided lighting, because the normal and tangents +are wrong on the other sides. + +This should be called before R_CleanupTriangles +================= +*/ +void R_ReverseTriangles( srfTriangles_t *tri ) { + int i; + + // flip the normal on each vertex + // If the surface is going to have generated normals, this won't matter, + // but if it has explicit normals, this will keep it on the correct side + for ( i = 0; i < tri->numVerts; i++ ) { + tri->verts[i].SetNormal( vec3_origin - tri->verts[i].GetNormal() ); + } + + // flip the index order to make them back sided + for ( i = 0; i < tri->numIndexes; i+= 3 ) { + triIndex_t temp; + + temp = tri->indexes[ i + 0 ]; + tri->indexes[ i + 0 ] = tri->indexes[ i + 1 ]; + tri->indexes[ i + 1 ] = temp; + } +} + +/* +================= +R_CleanupTriangles + +FIXME: allow createFlat and createSmooth normals, as well as explicit +================= +*/ +void R_CleanupTriangles( srfTriangles_t *tri, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents ) { + R_RangeCheckIndexes( tri ); + + R_CreateSilIndexes( tri ); + +// R_RemoveDuplicatedTriangles( tri ); // this may remove valid overlapped transparent triangles + + R_RemoveDegenerateTriangles( tri ); + + R_TestDegenerateTextureSpace( tri ); + +// R_RemoveUnusedVerts( tri ); + + if ( identifySilEdges ) { + R_IdentifySilEdges( tri, true ); // assume it is non-deformable, and omit coplanar edges + } + + // bust vertexes that share a mirrored edge into separate vertexes + R_DuplicateMirroredVertexes( tri ); + + R_CreateDupVerts( tri ); + + R_BoundTriSurf( tri ); + + if ( useUnsmoothedTangents ) { + R_BuildDominantTris( tri ); + R_DeriveTangents( tri ); + } else if ( !createNormals ) { + R_DeriveTangentsWithoutNormals( tri ); + } else { + R_DeriveTangents( tri ); + } +} + +/* +=================================================================================== + +DEFORMED SURFACES + +=================================================================================== +*/ + +/* +=================== +R_BuildDeformInfo +=================== +*/ +deformInfo_t *R_BuildDeformInfo( int numVerts, const idDrawVert *verts, int numIndexes, const int *indexes, + bool useUnsmoothedTangents ) { + srfTriangles_t tri; + memset( &tri, 0, sizeof( srfTriangles_t ) ); + + tri.numVerts = numVerts; + R_AllocStaticTriSurfVerts( &tri, tri.numVerts ); + SIMDProcessor->Memcpy( tri.verts, verts, tri.numVerts * sizeof( tri.verts[0] ) ); + + tri.numIndexes = numIndexes; + R_AllocStaticTriSurfIndexes( &tri, tri.numIndexes ); + + // don't memcpy, so we can change the index type from int to short without changing the interface + for ( int i = 0; i < tri.numIndexes; i++ ) { + tri.indexes[i] = indexes[i]; + } + + R_RangeCheckIndexes( &tri ); + R_CreateSilIndexes( &tri ); + R_IdentifySilEdges( &tri, false ); // we cannot remove coplanar edges, because they can deform to silhouettes + R_DuplicateMirroredVertexes( &tri ); // split mirror points into multiple points + R_CreateDupVerts( &tri ); + if ( useUnsmoothedTangents ) { + R_BuildDominantTris( &tri ); + } + R_DeriveTangents( &tri ); + + deformInfo_t * deform = (deformInfo_t *)R_ClearedStaticAlloc( sizeof( *deform ) ); + + deform->numSourceVerts = numVerts; + deform->numOutputVerts = tri.numVerts; + deform->verts = tri.verts; + + deform->numIndexes = numIndexes; + deform->indexes = tri.indexes; + + deform->silIndexes = tri.silIndexes; + + deform->numSilEdges = tri.numSilEdges; + deform->silEdges = tri.silEdges; + + deform->numMirroredVerts = tri.numMirroredVerts; + deform->mirroredVerts = tri.mirroredVerts; + + deform->numDupVerts = tri.numDupVerts; + deform->dupVerts = tri.dupVerts; + + if ( tri.dominantTris != NULL ) { + Mem_Free( tri.dominantTris ); + tri.dominantTris = NULL; + } + + idShadowVertSkinned * shadowVerts = (idShadowVertSkinned *) Mem_Alloc16( ALIGN( deform->numOutputVerts * 2 * sizeof( idShadowVertSkinned ), 16 ), TAG_MODEL ); + idShadowVertSkinned::CreateShadowCache( shadowVerts, deform->verts, deform->numOutputVerts ); + + deform->staticAmbientCache = vertexCache.AllocStaticVertex( deform->verts, ALIGN( deform->numOutputVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); + deform->staticIndexCache = vertexCache.AllocStaticIndex( deform->indexes, ALIGN( deform->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); + deform->staticShadowCache = vertexCache.AllocStaticVertex( shadowVerts, ALIGN( deform->numOutputVerts * 2 * sizeof( idShadowVertSkinned ), VERTEX_CACHE_ALIGN ) ); + + Mem_Free( shadowVerts ); + + return deform; +} + +/* +=================== +R_FreeDeformInfo +=================== +*/ +void R_FreeDeformInfo( deformInfo_t *deformInfo ) { + if ( deformInfo->verts != NULL ) { + Mem_Free( deformInfo->verts ); + } + if ( deformInfo->indexes != NULL ) { + Mem_Free( deformInfo->indexes ); + } + if ( deformInfo->silIndexes != NULL ) { + Mem_Free( deformInfo->silIndexes ); + } + if ( deformInfo->silEdges != NULL ) { + Mem_Free( deformInfo->silEdges ); + } + if ( deformInfo->mirroredVerts != NULL ) { + Mem_Free( deformInfo->mirroredVerts ); + } + if ( deformInfo->dupVerts != NULL ) { + Mem_Free( deformInfo->dupVerts ); + } + R_StaticFree( deformInfo ); +} + +/* +=================== +R_DeformInfoMemoryUsed +=================== +*/ +int R_DeformInfoMemoryUsed( deformInfo_t *deformInfo ) { + int total = 0; + + if ( deformInfo->verts != NULL ) { + total += deformInfo->numOutputVerts * sizeof( deformInfo->verts[0] ); + } + if ( deformInfo->indexes != NULL ) { + total += deformInfo->numIndexes * sizeof( deformInfo->indexes[0] ); + } + if ( deformInfo->mirroredVerts != NULL ) { + total += deformInfo->numMirroredVerts * sizeof( deformInfo->mirroredVerts[0] ); + } + if ( deformInfo->dupVerts != NULL ) { + total += deformInfo->numDupVerts * sizeof( deformInfo->dupVerts[0] ); + } + if ( deformInfo->silIndexes != NULL ) { + total += deformInfo->numIndexes * sizeof( deformInfo->silIndexes[0] ); + } + if ( deformInfo->silEdges != NULL ) { + total += deformInfo->numSilEdges * sizeof( deformInfo->silEdges[0] ); + } + + total += sizeof( *deformInfo ); + return total; +} + +/* +=================================================================================== + +VERTEX / INDEX CACHING + +=================================================================================== +*/ + +/* +=================== +R_InitDrawSurfFromTri +=================== +*/ +void R_InitDrawSurfFromTri( drawSurf_t & ds, srfTriangles_t & tri ) { + if ( tri.numIndexes == 0 ) { + ds.numIndexes = 0; + return; + } + + // copy verts and indexes to this frame's hardware memory if they aren't already there + // + // deformed surfaces will not have any vertices but the ambient cache will have already + // been created for them. + if ( ( tri.verts == NULL ) && !tri.referencedIndexes ) { + // pre-generated shadow models will not have any verts, just shadowVerts + tri.ambientCache = 0; + } else if ( !vertexCache.CacheIsCurrent( tri.ambientCache ) ) { + tri.ambientCache = vertexCache.AllocVertex( tri.verts, ALIGN( tri.numVerts * sizeof( tri.verts[0] ), VERTEX_CACHE_ALIGN ) ); + } + if ( !vertexCache.CacheIsCurrent( tri.indexCache ) ) { + tri.indexCache = vertexCache.AllocIndex( tri.indexes, ALIGN( tri.numIndexes * sizeof( tri.indexes[0] ), INDEX_CACHE_ALIGN ) ); + } + + ds.numIndexes = tri.numIndexes; + ds.ambientCache = tri.ambientCache; + ds.indexCache = tri.indexCache; + ds.shadowCache = tri.shadowCache; + ds.jointCache = 0; +} + +/* +=================== +R_CreateStaticBuffersForTri + +For static surfaces, the indexes, ambient, and shadow buffers can be pre-created at load +time, rather than being re-created each frame in the frame temporary buffers. +=================== +*/ +void R_CreateStaticBuffersForTri( srfTriangles_t & tri ) { + tri.indexCache = 0; + tri.ambientCache = 0; + tri.shadowCache = 0; + + // index cache + if ( tri.indexes != NULL ) { + tri.indexCache = vertexCache.AllocStaticIndex( tri.indexes, ALIGN( tri.numIndexes * sizeof( tri.indexes[0] ), INDEX_CACHE_ALIGN ) ); + } + + // vertex cache + if ( tri.verts != NULL ) { + tri.ambientCache = vertexCache.AllocStaticVertex( tri.verts, ALIGN( tri.numVerts * sizeof( tri.verts[0] ), VERTEX_CACHE_ALIGN ) ); + } + + // shadow cache + if ( tri.preLightShadowVertexes != NULL ) { + // this should only be true for the _prelight pre-calculated shadow volumes + assert( tri.verts == NULL ); // pre-light shadow volume surfaces don't have ambient vertices + const int shadowSize = ALIGN( tri.numVerts * 2 * sizeof( idShadowVert ), VERTEX_CACHE_ALIGN ); + tri.shadowCache = vertexCache.AllocStaticVertex( tri.preLightShadowVertexes, shadowSize ); + } else if ( tri.verts != NULL ) { + // the shadowVerts for normal models include all the xyz values duplicated + // for a W of 1 (near cap) and a W of 0 (end cap, projected to infinity) + const int shadowSize = ALIGN( tri.numVerts * 2 * sizeof( idShadowVert ), VERTEX_CACHE_ALIGN ); + if ( tri.staticShadowVertexes == NULL ) { + tri.staticShadowVertexes = (idShadowVert *) Mem_Alloc16( shadowSize, TAG_TEMP ); + idShadowVert::CreateShadowCache( tri.staticShadowVertexes, tri.verts, tri.numVerts ); + } + tri.shadowCache = vertexCache.AllocStaticVertex( tri.staticShadowVertexes, shadowSize ); + +#if !defined( KEEP_INTERACTION_CPU_DATA ) + Mem_Free( tri.staticShadowVertexes ); + tri.staticShadowVertexes = NULL; +#endif + } +} diff --git a/neo/sound/SoundVoice.cpp b/neo/sound/SoundVoice.cpp new file mode 100644 index 00000000..2fc2f5e0 --- /dev/null +++ b/neo/sound/SoundVoice.cpp @@ -0,0 +1,257 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "snd_local.h" + +idCVar s_subFraction( "s_subFraction", "0.5", CVAR_ARCHIVE | CVAR_FLOAT, "Amount of each sound to send to the LFE channel" ); + +idVec2 idSoundVoice_Base::speakerPositions[idWaveFile::CHANNEL_INDEX_MAX]; +int idSoundVoice_Base::speakerLeft[idWaveFile::CHANNEL_INDEX_MAX] = {0 }; +int idSoundVoice_Base::speakerRight[idWaveFile::CHANNEL_INDEX_MAX] = {0 }; +int idSoundVoice_Base::dstChannels = 0; +int idSoundVoice_Base::dstMask = 0; +int idSoundVoice_Base::dstCenter = -1; +int idSoundVoice_Base::dstLFE = -1; +int idSoundVoice_Base::dstMap[MAX_CHANNELS_PER_VOICE] = { 0 }; +int idSoundVoice_Base::invMap[idWaveFile::CHANNEL_INDEX_MAX] = { 0 }; +float idSoundVoice_Base::omniLevel = 1.0f; + +/* +======================== +idSoundVoice_Base::idSoundVoice_Base +======================== +*/ +idSoundVoice_Base::idSoundVoice_Base() : +position( 0.0f ), +gain( 1.0f ), +centerChannel( 0.0f ), +pitch( 1.0f ), +innerRadius( 32.0f ), +occlusion( 0.0f ), +channelMask( 0 ), +innerSampleRangeSqr( 0.0f ), +outerSampleRangeSqr( 0.0f ) +{ +} + +/* +======================== +idSoundVoice_Base::InitSurround +======================== +*/ +void idSoundVoice_Base::InitSurround( int outputChannels, int channelMask ) { + + speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_LEFT ].Set( 0.70710678118654752440084436210485f, 0.70710678118654752440084436210485f ); // 45 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT ].Set( 0.70710678118654752440084436210485f, -0.70710678118654752440084436210485f ); // 315 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_CENTER ].Set( 0.0f, 0.0f ); // 0 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY ].Set( 0.0f, 0.0f ); // - + speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_LEFT ].Set( -0.70710678118654752440084436210485f, 0.70710678118654752440084436210485f ); // 135 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_RIGHT ].Set( -0.70710678118654752440084436210485f, -0.70710678118654752440084436210485f ); // 225 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER ].Set( 0.92387953251128675612818318939679f, 0.3826834323650897717284599840304f ); // 22.5 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER ].Set( 0.92387953251128675612818318939679f, -0.3826834323650897717284599840304f ); // 337.5 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_CENTER ].Set( -1.0f, 0.0f ); // 180 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_SIDE_LEFT ].Set( 0.0f, 1.0f ); // 90 degrees + speakerPositions[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT ].Set( 0.0f, -1.0f ); // 270 degrees + + speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT; + speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_LEFT] = idWaveFile::CHANNEL_INDEX_SIDE_LEFT; + speakerLeft[idWaveFile::CHANNEL_INDEX_SIDE_LEFT] = idWaveFile::CHANNEL_INDEX_BACK_LEFT; + speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_LEFT] = idWaveFile::CHANNEL_INDEX_BACK_CENTER; + speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_CENTER] = idWaveFile::CHANNEL_INDEX_BACK_RIGHT; + speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_RIGHT] = idWaveFile::CHANNEL_INDEX_SIDE_RIGHT; + speakerLeft[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT; + speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER; + speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER; + + speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_CENTER; + speakerLeft[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY] = idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY; + + speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT; + speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT] = idWaveFile::CHANNEL_INDEX_SIDE_RIGHT; + speakerRight[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT] = idWaveFile::CHANNEL_INDEX_BACK_RIGHT; + speakerRight[idWaveFile::CHANNEL_INDEX_BACK_RIGHT] = idWaveFile::CHANNEL_INDEX_BACK_CENTER; + speakerRight[idWaveFile::CHANNEL_INDEX_BACK_CENTER] = idWaveFile::CHANNEL_INDEX_BACK_LEFT; + speakerRight[idWaveFile::CHANNEL_INDEX_BACK_LEFT] = idWaveFile::CHANNEL_INDEX_SIDE_LEFT; + speakerRight[idWaveFile::CHANNEL_INDEX_SIDE_LEFT] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT; + speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_LEFT] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER; + speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER; + + speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_CENTER; + speakerRight[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY] = idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY; + + dstChannels = outputChannels; + dstMask = channelMask; + + // dstMap maps a destination channel to a speaker + // invMap maps a speaker to a destination channel + dstLFE = -1; + dstCenter = -1; + memset( dstMap, 0, sizeof( dstMap ) ); + memset( invMap, 0, sizeof( invMap ) ); + for ( int i = 0, c = 0; i < idWaveFile::CHANNEL_INDEX_MAX && c < MAX_CHANNELS_PER_VOICE; i++ ) { + if ( dstMask & BIT(i) ) { + if ( i == idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY ) { + dstLFE = c; + } + if ( i == idWaveFile::CHANNEL_INDEX_FRONT_CENTER ) { + dstCenter = c; + } + dstMap[c] = i; + invMap[i] = c++; + } else { + // Remove this speaker from the chain + int right = speakerRight[i]; + int left = speakerLeft[i]; + speakerRight[left] = right; + speakerLeft[right] = left; + } + } + assert( ( dstLFE == -1 ) || ( ( dstMask & idWaveFile::CHANNEL_MASK_LOW_FREQUENCY ) != 0 ) ); + assert( ( dstCenter == -1 ) || ( ( dstMask & idWaveFile::CHANNEL_MASK_FRONT_CENTER ) != 0 ) ); + + float omniChannels = (float)dstChannels; + if ( dstMask & idWaveFile::CHANNEL_MASK_LOW_FREQUENCY ) { + omniChannels -= 1.0f; + } + if ( dstMask & idWaveFile::CHANNEL_MASK_FRONT_CENTER ) { + omniChannels -= 1.0f; + } + if ( omniChannels > 0.0f ) { + omniLevel = 1.0f / omniChannels; + } else { + // This happens in mono mode + omniLevel = 1.0f; + } +} + +/* +======================== +idSoundVoice_Base::CalculateSurround +======================== +*/ +void idSoundVoice_Base::CalculateSurround( int srcChannels, float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ], float scale ) { + // Hack for mono + if ( dstChannels == 1 ) { + if ( srcChannels == 1 ) { + pLevelMatrix[ 0 ] = scale; + } else if ( srcChannels == 2 ) { + pLevelMatrix[ 0 ] = scale * 0.7071f; + pLevelMatrix[ 1 ] = scale * 0.7071f; + } + return; + } + +#define MATINDEX( src, dst ) ( srcChannels * dst + src ) + + float subFraction = s_subFraction.GetFloat(); + + if ( srcChannels == 1 ) { + idVec2 p2 = position.ToVec2(); + + float centerFraction = centerChannel; + + float sqrLength = p2.LengthSqr(); + if ( sqrLength <= 0.01f ) { + // If we are on top of the listener, simply route all channels to each speaker equally + for ( int i = 0; i < dstChannels; i++ ) { + pLevelMatrix[MATINDEX( 0, i )] = omniLevel; + } + } else { + float invLength = idMath::InvSqrt( sqrLength ); + float distance = ( invLength * sqrLength ); + p2 *= invLength; + + float spatialize = 1.0f; + if ( distance < innerRadius ) { + spatialize = distance / innerRadius; + } + float omni = omniLevel * ( 1.0f - spatialize ); + + if ( dstCenter != -1 ) { + centerFraction *= Max( 0.0f, p2.x ); + spatialize *= ( 1.0f - centerFraction ); + omni *= ( 1.0f - centerFraction ); + } + + float channelDots[MAX_CHANNELS_PER_VOICE] = { 0 }; + for ( int i = 0; i < dstChannels; i++ ) { + // Calculate the contribution to each destination channel + channelDots[i] = speakerPositions[dstMap[i]] * p2; + } + // Find the speaker nearest to the sound + int channelA = 0; + for ( int i = 1; i < dstChannels; i++ ) { + if ( channelDots[i] > channelDots[channelA] ) { + channelA = i; + } + } + int speakerA = dstMap[channelA]; + + // Find the 2nd nearest speaker + int speakerB; + float speakerACross = ( speakerPositions[speakerA].x * p2.y ) - ( speakerPositions[speakerA].y * p2.x ); + if ( speakerACross > 0.0f ) { + speakerB = speakerLeft[speakerA]; + } else { + speakerB = speakerRight[speakerA]; + } + int channelB = invMap[speakerB]; + + // Divide the amplitude between the 2 closest speakers + float distA = ( speakerPositions[speakerA] - p2 ).Length(); + float distB = ( speakerPositions[speakerB] - p2 ).Length(); + float distCinv = 1.0f / ( distA + distB ); + float volumes[MAX_CHANNELS_PER_VOICE] = { 0 }; + volumes[channelA] = ( distB * distCinv ); + volumes[channelB] = ( distA * distCinv ); + for ( int i = 0; i < dstChannels; i++ ) { + pLevelMatrix[MATINDEX( 0, i )] = ( volumes[i] * spatialize ) + omni; + } + } + if ( dstLFE != -1 ) { + pLevelMatrix[MATINDEX( 0, dstLFE )] = subFraction; + } + if ( dstCenter != -1 ) { + pLevelMatrix[MATINDEX( 0, dstCenter )] = centerFraction; + } + } else if ( srcChannels == 2 ) { + pLevelMatrix[ MATINDEX( 0, 0 ) ] = 1.0f; + pLevelMatrix[ MATINDEX( 1, 1 ) ] = 1.0f; + if ( dstLFE != -1 ) { + pLevelMatrix[ MATINDEX( 0, dstLFE ) ] = subFraction * 0.5f; + pLevelMatrix[ MATINDEX( 1, dstLFE ) ] = subFraction * 0.5f; + } + } else { + idLib::Warning( "We don't support %d channel sound files", srcChannels ); + } + for ( int i = 0; i < srcChannels * dstChannels; i++ ) { + pLevelMatrix[ i ] *= scale; + } +} diff --git a/neo/sound/SoundVoice.h b/neo/sound/SoundVoice.h new file mode 100644 index 00000000..7566f0b7 --- /dev/null +++ b/neo/sound/SoundVoice.h @@ -0,0 +1,101 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SOUNDVOICE_H__ +#define __SOUNDVOICE_H__ + +/* +================================================ +idSoundVoice_Base +================================================ +*/ + +class idSoundVoice_Base { +public: + idSoundVoice_Base(); + + static void InitSurround( int outputChannels, int channelMask ); + + void CalculateSurround( int srcChannels, float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ], float scale ); + + void SetPosition( const idVec3 & p ) { position = p; } + void SetGain( float g ) { gain = g; } + void SetCenterChannel( float c ) { centerChannel = c; } + void SetPitch( float p ) { pitch = p; } + void SetInnerRadius( float r ) { innerRadius = r; } + void SetChannelMask( uint32 mask ) { channelMask = mask; } + + const idSoundSample * GetCurrentSample(); + + // Controls the low pass filter, where 0.0f = no filtering, 1.0f = full filter + void SetOcclusion( float f ) { occlusion = f; } + + float GetGain() { return gain; } + float GetPitch() { return pitch; } + +protected: + idVec3 position; // Position of the sound relative to listener + float gain; // Volume (0-1) + float centerChannel; // Value (0-1) which indicates how much of this voice goes to the center channel + float pitch; // Pitch multiplier + float innerRadius; // Anything closer than this is omni + float occlusion; // How much of this sound is occluded (0-1) + uint32 channelMask; // Set to override the default channel mask + + // These are some setting used to do SSF_DISTANCE_BASED_STERO blending + float innerSampleRangeSqr; + float outerSampleRangeSqr; + + idList< idSoundSample *, TAG_AUDIO> samples; + + // These are constants which are initialized with InitSurround + //------------------------------------------------------------- + + static idVec2 speakerPositions[idWaveFile::CHANNEL_INDEX_MAX]; + + // This is to figure out which speakers are "next to" this one + static int speakerLeft[idWaveFile::CHANNEL_INDEX_MAX]; + static int speakerRight[idWaveFile::CHANNEL_INDEX_MAX]; + + // Number of channels in the output hardware + static int dstChannels; + + // Mask indicating which speakers exist in the hardware configuration + static int dstMask; + + // dstMap maps a destination channel to a speaker + // invMap maps a speaker to a destination channel + static int dstCenter; + static int dstLFE; + static int dstMap[MAX_CHANNELS_PER_VOICE]; + static int invMap[idWaveFile::CHANNEL_INDEX_MAX]; + + // specifies what volume to specify for each channel when a speaker is omni + static float omniLevel; +}; + +#endif diff --git a/neo/sound/WaveFile.cpp b/neo/sound/WaveFile.cpp new file mode 100644 index 00000000..f5089457 --- /dev/null +++ b/neo/sound/WaveFile.cpp @@ -0,0 +1,516 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +================================================================================================ +Contains the WaveFile implementation. +================================================================================================ +*/ + +#include "WaveFile.h" + +/* +======================== +idWaveFile::Open + +Returns true if the Open was successful and the file matches the expected format. If this +returns false, there is no need to call Close. +======================== +*/ +bool idWaveFile::Open( const char * filename ) { + Close(); + + if ( filename == NULL || filename[0] == 0 ) { + return false; + } + + if ( file == NULL ) { + file = fileSystem->OpenFileReadMemory( filename ); + if ( file == NULL ) { + return false; + } + } + + if ( file->Length() == 0 ) { + Close(); + return false; + } + + struct header_t { + uint32 id; + uint32 size; + uint32 format; + } header; + + file->Read( &header, sizeof( header ) ); + idSwap::Big( header.id ); + idSwap::Little( header.size ); + idSwap::Big( header.format ); + + if ( header.id != 'RIFF' || header.format != 'WAVE' || header.size < 4 ) { + Close(); + idLib::Warning( "Header is not RIFF WAVE in %s", filename ); + return false; + } + + uint32 riffSize = header.size + 8; + uint32 offset = sizeof( header ); + + // Scan the file collecting chunks + while ( offset < riffSize ) { + struct chuckHeader_t { + uint32 id; + uint32 size; + } chunkHeader; + if ( file->Read( &chunkHeader, sizeof( chunkHeader ) ) != sizeof( chunkHeader ) ) { + // It seems like some tools have extra data after the last chunk for no apparent reason + // so don't treat this as an error + return true; + } + idSwap::Big( chunkHeader.id ); + idSwap::Little( chunkHeader.size ); + offset += sizeof( chunkHeader ); + + if ( chunks.Num() >= chunks.Max() ) { + Close(); + idLib::Warning( "More than %d chunks in %s", chunks.Max(), filename ); + return false; + } + + chunk_t * chunk = chunks.Alloc(); + chunk->id = chunkHeader.id; + chunk->size = chunkHeader.size; + chunk->offset = offset; + offset += chunk->size; + + file->Seek( offset, FS_SEEK_SET ); + } + + return true; +} + +/* +======================== +idWaveFile::SeekToChunk + +Seeks to the specified chunk and returns the size of the chunk or 0 if the chunk wasn't found. +======================== +*/ +uint32 idWaveFile::SeekToChunk( uint32 id ) { + for ( int i = 0; i < chunks.Num(); i++ ) { + if ( chunks[i].id == id ) { + file->Seek( chunks[i].offset, FS_SEEK_SET ); + return chunks[i].size; + } + } + return 0; +} + +/* +======================== +idWaveFile::GetChunkOffset + +Seeks to the specified chunk and returns the size of the chunk or 0 if the chunk wasn't found. +======================== +*/ +uint32 idWaveFile::GetChunkOffset( uint32 id ) { + for ( int i = 0; i < chunks.Num(); i++ ) { + if ( chunks[i].id == id ) { + return chunks[i].offset; + } + } + return 0; +} + +// Used in XMA2WAVEFORMAT for per-stream data +typedef struct XMA2STREAMFORMAT { + byte Channels; // Number of channels in the stream (1 or 2) + byte RESERVED; // Reserved for future use + uint16 ChannelMask; // Spatial positions of the channels in the stream +} XMA2STREAMFORMAT; + +// Legacy XMA2 format structure (big-endian byte ordering) +typedef struct XMA2WAVEFORMAT { + byte Version; // XMA encoder version that generated the file. + // Always 3 or higher for XMA2 files. + byte NumStreams; // Number of interleaved audio streams + byte RESERVED; // Reserved for future use + byte LoopCount; // Number of loop repetitions; 255 = infinite + uint32 LoopBegin; // Loop begin point, in samples + uint32 LoopEnd; // Loop end point, in samples + uint32 SampleRate; // The file's decoded sample rate + uint32 EncodeOptions; // Options for the XMA encoder/decoder + uint32 PsuedoBytesPerSec; // Used internally by the XMA encoder + uint32 BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except + // possibly the last one). Always a multiple of + // 2Kb, since XMA blocks are arrays of 2Kb packets. + uint32 SamplesEncoded; // Total number of PCM samples encoded in this file + uint32 SamplesInSource; // Actual number of PCM samples in the source + // material used to generate this file + uint32 BlockCount; // Number of XMA blocks in this file (and hence + // also the number of entries in its seek table) +} XMA2WAVEFORMAT; + +/* +======================== +idWaveFile::ReadWaveFormat + +Reads a wave format header, returns NULL if it found one and read it. +otherwise, returns a human-readable error message. +======================== +*/ +const char * idWaveFile::ReadWaveFormat( waveFmt_t & format ) { + memset( &format, 0, sizeof( format ) ); + + uint32 formatSize = SeekToChunk( waveFmt_t::id ); + if ( formatSize == 0 ) { + return "No format chunk"; + } + if ( formatSize < sizeof( format.basic ) ) { + return "Format chunk too small"; + } + + Read( &format.basic, sizeof( format.basic ) ); + + idSwapClass swap; + swap.Little( format.basic.formatTag ); + swap.Little( format.basic.numChannels ); + swap.Little( format.basic.samplesPerSec ); + swap.Little( format.basic.avgBytesPerSec ); + swap.Little( format.basic.blockSize ); + swap.Little( format.basic.bitsPerSample ); + + if ( format.basic.formatTag == FORMAT_PCM ) { + } else if ( format.basic.formatTag == FORMAT_ADPCM ) { + Read( &format.extraSize, sizeof( format.extraSize ) ); + idSwap::Little( format.extraSize ); + if ( format.extraSize != sizeof( waveFmt_t::extra_t::adpcm_t ) ) { + return "Incorrect number of coefficients in ADPCM file"; + } + Read( &format.extra.adpcm, sizeof( format.extra.adpcm ) ); + idSwapClass swap; + swap.Little( format.extra.adpcm.samplesPerBlock ); + swap.Little( format.extra.adpcm.numCoef ); + for ( int i = 0; i < format.extra.adpcm.numCoef; i++ ) { + swap.Little( format.extra.adpcm.aCoef[ i ].coef1 ); + swap.Little( format.extra.adpcm.aCoef[ i ].coef2 ); + } + } else if ( format.basic.formatTag == FORMAT_XMA2 ) { + Read( &format.extraSize, sizeof( format.extraSize ) ); + idSwap::Little( format.extraSize ); + if ( format.extraSize != sizeof( waveFmt_t::extra_t::xma2_t ) ) { + return "Incorrect chunk size in XMA2 file"; + } + Read( &format.extra.xma2, sizeof( format.extra.xma2 ) ); + idSwapClass swap; + swap.Little( format.extra.xma2.numStreams ); + swap.Little( format.extra.xma2.channelMask ); + swap.Little( format.extra.xma2.samplesEncoded ); + swap.Little( format.extra.xma2.bytesPerBlock ); + swap.Little( format.extra.xma2.playBegin ); + swap.Little( format.extra.xma2.playLength ); + swap.Little( format.extra.xma2.loopBegin ); + swap.Little( format.extra.xma2.loopLength ); + swap.Little( format.extra.xma2.loopCount ); + swap.Little( format.extra.xma2.encoderVersion ); + swap.Little( format.extra.xma2.blockCount ); + } else if ( format.basic.formatTag == FORMAT_EXTENSIBLE ) { + Read( &format.extraSize, sizeof( format.extraSize ) ); + idSwap::Little( format.extraSize ); + if ( format.extraSize != sizeof( waveFmt_t::extra_t::extensible_t ) ) { + return "Incorrect chunk size in extensible wave file"; + } + Read( &format.extra.extensible, sizeof( format.extra.extensible ) ); + idSwapClass swap; + swap.Little( format.extra.extensible.validBitsPerSample ); + swap.Little( format.extra.extensible.channelMask ); + swap.Little( format.extra.extensible.subFormat.data1 ); + swap.Little( format.extra.extensible.subFormat.data2 ); + swap.Little( format.extra.extensible.subFormat.data3 ); + swap.Little( format.extra.extensible.subFormat.data4 ); + swap.LittleArray( format.extra.extensible.subFormat.data5, 6 ); + waveFmt_t::extra_t::extensible_t::guid_t pcmGuid = { + FORMAT_PCM, + 0x0000, + 0x0010, + 0x8000, + { 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } + }; + if ( memcmp( &pcmGuid, &format.extra.extensible.subFormat, sizeof( pcmGuid ) ) != 0 ) { + return "Unsupported Extensible format"; + } + } else { + return "Unknown wave format tag"; + } + + return NULL; +} + +/* +======================== +idWaveFile::ReadWaveFormatDirect + +Reads a wave format header from a file ptr, +======================== +*/ +bool idWaveFile::ReadWaveFormatDirect( waveFmt_t & format, idFile *file ) { + + file->Read( &format.basic, sizeof( format.basic ) ); + idSwapClass swap; + swap.Little( format.basic.formatTag ); + swap.Little( format.basic.numChannels ); + swap.Little( format.basic.samplesPerSec ); + swap.Little( format.basic.avgBytesPerSec ); + swap.Little( format.basic.blockSize ); + swap.Little( format.basic.bitsPerSample ); + + if ( format.basic.formatTag == FORMAT_PCM ) { + } else if ( format.basic.formatTag == FORMAT_ADPCM ) { + file->Read( &format.extraSize, sizeof( format.extraSize ) ); + idSwap::Little( format.extraSize ); + if ( format.extraSize != sizeof( waveFmt_t::extra_t::adpcm_t ) ) { + return false; + } + file->Read( &format.extra.adpcm, sizeof( format.extra.adpcm ) ); + idSwapClass swap; + swap.Little( format.extra.adpcm.samplesPerBlock ); + swap.Little( format.extra.adpcm.numCoef ); + for ( int i = 0; i < format.extra.adpcm.numCoef; i++ ) { + swap.Little( format.extra.adpcm.aCoef[ i ].coef1 ); + swap.Little( format.extra.adpcm.aCoef[ i ].coef2 ); + } + } else if ( format.basic.formatTag == FORMAT_XMA2 ) { + file->Read( &format.extraSize, sizeof( format.extraSize ) ); + idSwap::Little( format.extraSize ); + if ( format.extraSize != sizeof( waveFmt_t::extra_t::xma2_t ) ) { + return false; + } + file->Read( &format.extra.xma2, sizeof( format.extra.xma2 ) ); + idSwapClass swap; + swap.Little( format.extra.xma2.numStreams ); + swap.Little( format.extra.xma2.channelMask ); + swap.Little( format.extra.xma2.samplesEncoded ); + swap.Little( format.extra.xma2.bytesPerBlock ); + swap.Little( format.extra.xma2.playBegin ); + swap.Little( format.extra.xma2.playLength ); + swap.Little( format.extra.xma2.loopBegin ); + swap.Little( format.extra.xma2.loopLength ); + swap.Little( format.extra.xma2.loopCount ); + swap.Little( format.extra.xma2.encoderVersion ); + swap.Little( format.extra.xma2.blockCount ); + } else if ( format.basic.formatTag == FORMAT_EXTENSIBLE ) { + file->Read( &format.extraSize, sizeof( format.extraSize ) ); + idSwap::Little( format.extraSize ); + if ( format.extraSize != sizeof( waveFmt_t::extra_t::extensible_t ) ) { + return false; + } + file->Read( &format.extra.extensible, sizeof( format.extra.extensible ) ); + idSwapClass swap; + swap.Little( format.extra.extensible.validBitsPerSample ); + swap.Little( format.extra.extensible.channelMask ); + swap.Little( format.extra.extensible.subFormat.data1 ); + swap.Little( format.extra.extensible.subFormat.data2 ); + swap.Little( format.extra.extensible.subFormat.data3 ); + swap.Little( format.extra.extensible.subFormat.data4 ); + swap.LittleArray( format.extra.extensible.subFormat.data5, 6 ); + waveFmt_t::extra_t::extensible_t::guid_t pcmGuid = { + FORMAT_PCM, + 0x0000, + 0x0010, + 0x8000, + { 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } + }; + if ( memcmp( &pcmGuid, &format.extra.extensible.subFormat, sizeof( pcmGuid ) ) != 0 ) { + return false; + } + } else { + return false; + } + + return true; +} + +/* +======================== +idWaveFile::WriteWaveFormatDirect + +Writes a wave format header to a file ptr, +======================== +*/ +bool idWaveFile::WriteWaveFormatDirect( waveFmt_t & format, idFile *file ) { + //idSwapClass swap; + //swap.Little( format.basic.formatTag ); + //swap.Little( format.basic.numChannels ); + //swap.Little( format.basic.samplesPerSec ); + //swap.Little( format.basic.avgBytesPerSec ); + //swap.Little( format.basic.blockSize ); + //swap.Little( format.basic.bitsPerSample ); + file->Write( &format.basic, sizeof( format.basic ) ); + if ( format.basic.formatTag == FORMAT_PCM ) { + //file->Write( &format.basic, sizeof( format.basic ) ); + } else if ( format.basic.formatTag == FORMAT_ADPCM ) { + //file->Write( &format.basic, sizeof( format.basic ) ); + file->Write( &format.extraSize, sizeof( format.extraSize ) ); + file->Write( &format.extra.adpcm, sizeof( format.extra.adpcm ) ); + } else if ( format.basic.formatTag == FORMAT_XMA2 ) { + //file->Write( &format.basic, sizeof( format.basic ) ); + file->Write( &format.extraSize, sizeof( format.extraSize ) ); + file->Write( &format.extra.xma2, sizeof( format.extra.xma2 ) ); + } else if ( format.basic.formatTag == FORMAT_EXTENSIBLE ) { + //file->Write( &format.basic, sizeof( format.basic ) ); + file->Write( &format.extraSize, sizeof( format.extraSize ) ); + file->Write( &format.extra.extensible, sizeof( format.extra.extensible ) ); + } else { + return false; + } + return true; +} + +/* +======================== +idWaveFile::WriteWaveFormatDirect + +Writes a wave format header to a file ptr, +======================== +*/ + +bool idWaveFile::WriteSampleDataDirect( idList< sampleData_t > & sampleData, idFile * file ) { + static const uint32 sample = 'smpl'; + file->WriteBig( sample ); + uint32 samplerData = sampleData.Num() * 24; + uint32 chunkSize = 36 + samplerData; + uint32 zero = 0; + uint32 numSamples = sampleData.Num(); + + file->Write( &chunkSize, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &numSamples, sizeof( uint32 ) ); + file->Write( &samplerData, sizeof( uint32 ) ); + + for ( int i = 0; i < sampleData.Num(); ++i ) { + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &sampleData[ i ].start, sizeof( uint32 ) ); + file->Write( &sampleData[ i ].end, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + file->Write( &zero, sizeof( uint32 ) ); + } + return true; +} + +/* +======================== +idWaveFile::WriteWaveFormatDirect + +Writes a data chunk to a file ptr +======================== +*/ + +bool idWaveFile::WriteDataDirect( char * _data, uint32 size, idFile * file ) { + static const uint32 data = 'data'; + file->WriteBig( data ); + file->Write( &size, sizeof( uint32 ) ); + file->WriteBigArray( _data, size ); + return true; +} + +/* +======================== +idWaveFile::WriteWaveFormatDirect + +Writes a wave header to a file ptr, +======================== +*/ + +bool idWaveFile::WriteHeaderDirect( uint32 fileSize, idFile * file ) { + static const uint32 riff = 'RIFF'; + static const uint32 wave = 'WAVE'; + file->WriteBig( riff ); + file->WriteBig( fileSize ); + file->WriteBig( wave ); + return true; +} + +/* +======================== +idWaveFile::ReadLoopPoint + +Reads a loop point from a 'smpl' chunk in a wave file, returns 0 if none are found. +======================== +*/ +bool idWaveFile::ReadLoopData( int & start, int & end ) { + uint32 chunkSize = SeekToChunk( samplerChunk_t::id ); + if ( chunkSize < sizeof( samplerChunk_t ) ) { + return false; + } + + samplerChunk_t smpl; + Read( &smpl, sizeof( smpl ) ); + idSwap::Little( smpl.numSampleLoops ); + + if ( smpl.numSampleLoops < 1 ) { + return false; // this is possible returning false lets us know there are more then 1 sample look in the file and is not appropriate for traditional looping + } + + sampleData_t smplData; + Read( &smplData, sizeof( smplData ) ); + idSwap::Little( smplData.start ); + idSwap::Little( smplData.end ); + + if ( smplData.type != 0 ) { + idLib::Warning( "Invalid loop type in %s", file->GetName() ); + return false; + } + + start = smplData.start; + end = smplData.end; + return true; +} + +/* +======================== +idWaveFile::Close + +Closes the file and frees resources. +======================== +*/ +void idWaveFile::Close() { + if ( file != NULL ) { + delete file; + file = NULL; + } + chunks.SetNum( 0 ); +} diff --git a/neo/sound/WaveFile.h b/neo/sound/WaveFile.h new file mode 100644 index 00000000..e2136c33 --- /dev/null +++ b/neo/sound/WaveFile.h @@ -0,0 +1,236 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __WAVEFILE_H +#define __WAVEFILE_H + +/* +================================================================================================ +Contains the WaveFile declaration. +================================================================================================ +*/ + +/* +================================================ +idWaveFile is used for reading generic RIFF WAVE files. +================================================ +*/ +class idWaveFile { +public: + ID_INLINE idWaveFile(); + ID_INLINE ~idWaveFile(); + + bool Open( const char * filename ); + void Close(); + uint32 SeekToChunk( uint32 id ); + size_t Read( void * buffer, size_t len ) { return file->Read( buffer, len ); } + uint32 GetChunkOffset( uint32 id ); + + ID_TIME_T Timestamp() { return file->Timestamp(); } + const char * Name() { return ( file == NULL ? "" : file->GetName() ); } + + // This maps to the channel mask in waveFmtExtensible_t + enum { + CHANNEL_INDEX_FRONT_LEFT, + CHANNEL_INDEX_FRONT_RIGHT, + CHANNEL_INDEX_FRONT_CENTER, + CHANNEL_INDEX_LOW_FREQUENCY, + CHANNEL_INDEX_BACK_LEFT, + CHANNEL_INDEX_BACK_RIGHT, + CHANNEL_INDEX_FRONT_LEFT_CENTER, + CHANNEL_INDEX_FRONT_RIGHT_CENTER, + CHANNEL_INDEX_BACK_CENTER, + CHANNEL_INDEX_SIDE_LEFT, + CHANNEL_INDEX_SIDE_RIGHT, + CHANNEL_INDEX_MAX + }; + enum { + CHANNEL_MASK_FRONT_LEFT = BIT( CHANNEL_INDEX_FRONT_LEFT ), + CHANNEL_MASK_FRONT_RIGHT = BIT( CHANNEL_INDEX_FRONT_RIGHT ), + CHANNEL_MASK_FRONT_CENTER = BIT( CHANNEL_INDEX_FRONT_CENTER ), + CHANNEL_MASK_LOW_FREQUENCY = BIT( CHANNEL_INDEX_LOW_FREQUENCY ), + CHANNEL_MASK_BACK_LEFT = BIT( CHANNEL_INDEX_BACK_LEFT ), + CHANNEL_MASK_BACK_RIGHT = BIT( CHANNEL_INDEX_BACK_RIGHT ), + CHANNEL_MASK_FRONT_LEFT_CENTER = BIT( CHANNEL_INDEX_FRONT_LEFT_CENTER ), + CHANNEL_MASK_FRONT_RIGHT_CENTER = BIT( CHANNEL_INDEX_FRONT_RIGHT_CENTER ), + CHANNEL_MASK_BACK_CENTER = BIT( CHANNEL_INDEX_BACK_CENTER ), + CHANNEL_MASK_SIDE_LEFT = BIT( CHANNEL_INDEX_SIDE_LEFT ), + CHANNEL_MASK_SIDE_RIGHT = BIT( CHANNEL_INDEX_SIDE_RIGHT ), + CHANNEL_MASK_ALL = BIT( CHANNEL_INDEX_MAX ) - 1, + }; + + // This matches waveFmt_t::formatTag + // These are the only wave formats that we understand + enum { + FORMAT_UNKNOWN = 0x0000, + FORMAT_PCM = 0x0001, + FORMAT_ADPCM = 0x0002, + FORMAT_XMA2 = 0x0166, + FORMAT_EXTENSIBLE = 0xFFFF, + }; + +#pragma pack( push, 1 ) + struct waveFmt_t { + static const uint32 id = 'fmt '; + // This is the basic data we'd expect to see in any valid wave file + struct basic_t { + uint16 formatTag; + uint16 numChannels; + uint32 samplesPerSec; + uint32 avgBytesPerSec; + uint16 blockSize; + uint16 bitsPerSample; + } basic; + // Some wav file formats have extra data after the basic header + uint16 extraSize; + // We have a few known formats that we handle: + union extra_t { + // Valid if basic.formatTag == FORMAT_EXTENSIBLE + struct extensible_t { + uint16 validBitsPerSample; // Valid bits in each sample container + uint32 channelMask; // Positions of the audio channels + struct guid_t { + uint32 data1; + uint16 data2; + uint16 data3; + uint16 data4; + byte data5[ 6 ]; + } subFormat; // Format identifier GUID + } extensible; + // Valid if basic.formatTag == FORMAT_ADPCM + // The microsoft ADPCM struct has a zero-sized array at the end + // but the array is always 7 entries, so we set it to that size + // so we can embed it in our format union. Otherwise, the struct + // is exactly the same as the one in audiodefs.h + struct adpcm_t { + uint16 samplesPerBlock; + uint16 numCoef; + struct adpcmcoef_t { + short coef1; + short coef2; + } aCoef[7]; // Always 7 coefficient pairs for MS ADPCM + } adpcm; + // Valid if basic.formatTag == FORMAT_XMA2 + struct xma2_t { + uint16 numStreams; // Number of audio streams (1 or 2 channels each) + uint32 channelMask; // matches the CHANNEL_MASK enum above + uint32 samplesEncoded; // Total number of PCM samples the file decodes to + uint32 bytesPerBlock; // XMA block size (but the last one may be shorter) + uint32 playBegin; // First valid sample in the decoded audio + uint32 playLength; // Length of the valid part of the decoded audio + uint32 loopBegin; // Beginning of the loop region in decoded sample terms + uint32 loopLength; // Length of the loop region in decoded sample terms + byte loopCount; // Number of loop repetitions; 255 = infinite + byte encoderVersion; // Version of XMA encoder that generated the file + uint16 blockCount; // XMA blocks in file (and entries in its seek table) + } xma2; + } extra; + }; + +#pragma pack( pop ) + + struct dataChunk_t { + static const uint32 id = 'data'; + uint32 size; + void * data; + }; + + struct formatChunk_t { + static const uint32 id = 'fmt '; + uint32 size; + uint16 compressionCode; + uint16 numChannels; + uint32 sampleRate; + uint32 averageBytesPerSecond; + uint16 blockAlign; + uint16 bitsPerSample; + uint16 numExtraFormatByte; + }; + + struct samplerChunk_t { + static const uint32 id = 'smpl'; + uint32 manufacturer; // ignored + uint32 product; // ignored + uint32 samplePeriod; // ignored (normally 1000000000/samplesPerSec) + uint32 MIDIUnityNote; // ignored + uint32 MIDIPitchFraction; // ignored + uint32 SMPTEFormat; // ignored + uint32 SMPTEOffset; // ignored + uint32 numSampleLoops; // number of samples in wave file + uint32 extraSamplerData; // ignored, should always be 0 + }; + + struct sampleData_t { + uint32 identifier; // ignored + uint32 type; // 0 for loop 33 multi-sample sample type + uint32 start; // start of the loop point + uint32 end; // end of the loop point + uint32 fraction; // ignored + uint32 playCount; // ignored + }; + + const char * ReadWaveFormat( waveFmt_t & waveFmt ); + static bool ReadWaveFormatDirect( waveFmt_t & format, idFile *file ); + static bool WriteWaveFormatDirect( waveFmt_t & format, idFile *file ); + static bool WriteSampleDataDirect( idList< sampleData_t > & sampleData, idFile *file ); + static bool WriteDataDirect( char * _data, uint32 size, idFile * file ); + static bool WriteHeaderDirect( uint32 fileSize, idFile * file ); + + bool ReadLoopData( int & start, int & end ); + +private: + idFile * file; + + struct chunk_t { + uint32 id; + uint32 size; + uint32 offset; + }; + + idStaticList< chunk_t, 32 > chunks; + + +}; + +/* +======================== +idWaveFile::idWaveFile +======================== +*/ +ID_INLINE idWaveFile::idWaveFile() : file( NULL ) { +} + +/* +======================== +idWaveFile::~idWaveFile +======================== +*/ +ID_INLINE idWaveFile::~idWaveFile() { + Close(); +} + +#endif // !__WAVEFILE_H__ diff --git a/neo/sound/XAudio2/XA2_SoundHardware.cpp b/neo/sound/XAudio2/XA2_SoundHardware.cpp new file mode 100644 index 00000000..c703fe5e --- /dev/null +++ b/neo/sound/XAudio2/XA2_SoundHardware.cpp @@ -0,0 +1,542 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../snd_local.h" +#include "../../../doomclassic/doom/i_sound.h" + +idCVar s_showLevelMeter( "s_showLevelMeter", "0", CVAR_BOOL|CVAR_ARCHIVE, "Show VU meter" ); +idCVar s_meterTopTime( "s_meterTopTime", "1000", CVAR_INTEGER|CVAR_ARCHIVE, "How long (in milliseconds) peaks are displayed on the VU meter" ); +idCVar s_meterPosition( "s_meterPosition", "100 100 20 200", CVAR_ARCHIVE, "VU meter location (x y w h)" ); +idCVar s_device( "s_device", "-1", CVAR_INTEGER|CVAR_ARCHIVE, "Which audio device to use (listDevices to list, -1 for default)" ); +idCVar s_showPerfData( "s_showPerfData", "0", CVAR_BOOL, "Show XAudio2 Performance data" ); +extern idCVar s_volume_dB; + +/* +======================== +idSoundHardware_XAudio2::idSoundHardware_XAudio2 +======================== +*/ +idSoundHardware_XAudio2::idSoundHardware_XAudio2() { + pXAudio2 = NULL; + pMasterVoice = NULL; + pSubmixVoice = NULL; + + vuMeterRMS = NULL; + vuMeterPeak = NULL; + + outputChannels = 0; + channelMask = 0; + + voices.SetNum( 0 ); + zombieVoices.SetNum( 0 ); + freeVoices.SetNum( 0 ); + + lastResetTime = 0; +} + +void listDevices_f( const idCmdArgs & args ) { + + IXAudio2 * pXAudio2 = soundSystemLocal.hardware.GetIXAudio2(); + + if ( pXAudio2 == NULL ) { + idLib::Warning( "No xaudio object" ); + return; + } + + UINT32 deviceCount = 0; + if ( pXAudio2->GetDeviceCount( &deviceCount ) != S_OK || deviceCount == 0 ) { + idLib::Warning( "No audio devices found" ); + return; + } + + for ( unsigned int i = 0; i < deviceCount; i++ ) { + XAUDIO2_DEVICE_DETAILS deviceDetails; + if ( pXAudio2->GetDeviceDetails( i, &deviceDetails ) != S_OK ) { + continue; + } + idStaticList< const char *, 5 > roles; + if ( deviceDetails.Role & DefaultConsoleDevice ) { + roles.Append( "Console Device" ); + } + if ( deviceDetails.Role & DefaultMultimediaDevice ) { + roles.Append( "Multimedia Device" ); + } + if ( deviceDetails.Role & DefaultCommunicationsDevice ) { + roles.Append( "Communications Device" ); + } + if ( deviceDetails.Role & DefaultGameDevice ) { + roles.Append( "Game Device" ); + } + idStaticList< const char *, 11 > channelNames; + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_LEFT ) { + channelNames.Append( "Front Left" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_RIGHT ) { + channelNames.Append( "Front Right" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_CENTER ) { + channelNames.Append( "Front Center" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_LOW_FREQUENCY ) { + channelNames.Append( "Low Frequency" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_LEFT ) { + channelNames.Append( "Back Left" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_RIGHT ) { + channelNames.Append( "Back Right" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_LEFT_OF_CENTER ) { + channelNames.Append( "Front Left of Center" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_RIGHT_OF_CENTER ) { + channelNames.Append( "Front Right of Center" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_CENTER ) { + channelNames.Append( "Back Center" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_SIDE_LEFT ) { + channelNames.Append( "Side Left" ); + } + if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_SIDE_RIGHT ) { + channelNames.Append( "Side Right" ); + } + char mbcsDisplayName[ 256 ]; + wcstombs( mbcsDisplayName, deviceDetails.DisplayName, sizeof( mbcsDisplayName ) ); + idLib::Printf( "%3d: %s\n", i, mbcsDisplayName ); + idLib::Printf( " %d channels, %d Hz\n", deviceDetails.OutputFormat.Format.nChannels, deviceDetails.OutputFormat.Format.nSamplesPerSec ); + if ( channelNames.Num() != deviceDetails.OutputFormat.Format.nChannels ) { + idLib::Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "Mismatch between # of channels and channel mask\n" ); + } + if ( channelNames.Num() == 1 ) { + idLib::Printf( " %s\n", channelNames[0] ); + } else if ( channelNames.Num() == 2 ) { + idLib::Printf( " %s and %s\n", channelNames[0], channelNames[1] ); + } else if ( channelNames.Num() > 2 ) { + idLib::Printf( " %s", channelNames[0] ); + for ( int i = 1; i < channelNames.Num() - 1; i++ ) { + idLib::Printf( ", %s", channelNames[i] ); + } + idLib::Printf( ", and %s\n", channelNames[channelNames.Num() - 1] ); + } + if ( roles.Num() == 1 ) { + idLib::Printf( " Default %s\n", roles[0] ); + } else if ( roles.Num() == 2 ) { + idLib::Printf( " Default %s and %s\n", roles[0], roles[1] ); + } else if ( roles.Num() > 2 ) { + idLib::Printf( " Default %s", roles[0] ); + for ( int i = 1; i < roles.Num() - 1; i++ ) { + idLib::Printf( ", %s", roles[i] ); + } + idLib::Printf( ", and %s\n", roles[roles.Num() - 1] ); + } + } +} + +/* +======================== +idSoundHardware_XAudio2::Init +======================== +*/ +void idSoundHardware_XAudio2::Init() { + + cmdSystem->AddCommand( "listDevices", listDevices_f, 0, "Lists the connected sound devices", NULL ); + + DWORD xAudioCreateFlags = 0; +#ifdef _DEBUG + xAudioCreateFlags |= XAUDIO2_DEBUG_ENGINE; +#endif + + XAUDIO2_PROCESSOR xAudioProcessor = XAUDIO2_DEFAULT_PROCESSOR; + + if ( FAILED( XAudio2Create( &pXAudio2, xAudioCreateFlags, xAudioProcessor ) ) ) { + if ( xAudioCreateFlags & XAUDIO2_DEBUG_ENGINE ) { + // in case the debug engine isn't installed + xAudioCreateFlags &= ~XAUDIO2_DEBUG_ENGINE; + if ( FAILED( XAudio2Create( &pXAudio2, xAudioCreateFlags, xAudioProcessor ) ) ) { + idLib::FatalError( "Failed to create XAudio2 engine. Try installing the latest DirectX." ); + return; + } + } else { + idLib::FatalError( "Failed to create XAudio2 engine. Try installing the latest DirectX." ); + return; + } + } +#ifdef _DEBUG + XAUDIO2_DEBUG_CONFIGURATION debugConfiguration = { 0 }; + debugConfiguration.TraceMask = XAUDIO2_LOG_WARNINGS; + debugConfiguration.BreakMask = XAUDIO2_LOG_ERRORS; + pXAudio2->SetDebugConfiguration( &debugConfiguration ); +#endif + + // Register the sound engine callback + pXAudio2->RegisterForCallbacks( &soundEngineCallback ); + soundEngineCallback.hardware = this; + + UINT32 deviceCount = 0; + if ( pXAudio2->GetDeviceCount( &deviceCount ) != S_OK || deviceCount == 0 ) { + idLib::Warning( "No audio devices found" ); + pXAudio2->Release(); + pXAudio2 = NULL; + return; + } + + idCmdArgs args; + listDevices_f( args ); + + int preferredDevice = s_device.GetInteger(); + if ( preferredDevice < 0 || preferredDevice >= (int)deviceCount ) { + int preferredChannels = 0; + for ( unsigned int i = 0; i < deviceCount; i++ ) { + XAUDIO2_DEVICE_DETAILS deviceDetails; + if ( pXAudio2->GetDeviceDetails( i, &deviceDetails ) != S_OK ) { + continue; + } + + if ( deviceDetails.Role & DefaultGameDevice ) { + // if we find a device the user marked as their preferred 'game' device, then always use that + preferredDevice = i; + preferredChannels = deviceDetails.OutputFormat.Format.nChannels; + break; + } + + if ( deviceDetails.OutputFormat.Format.nChannels > preferredChannels ) { + preferredDevice = i; + preferredChannels = deviceDetails.OutputFormat.Format.nChannels; + } + } + } + + idLib::Printf( "Using device %d\n", preferredDevice ); + + XAUDIO2_DEVICE_DETAILS deviceDetails; + if ( pXAudio2->GetDeviceDetails( preferredDevice, &deviceDetails ) != S_OK ) { + // One way this could happen is if a device is removed between the loop and this line of code + // Highly unlikely but possible + idLib::Warning( "Failed to get device details" ); + pXAudio2->Release(); + pXAudio2 = NULL; + return; + } + + DWORD outputSampleRate = 44100; // Max( (DWORD)XAUDIO2FX_REVERB_MIN_FRAMERATE, Min( (DWORD)XAUDIO2FX_REVERB_MAX_FRAMERATE, deviceDetails.OutputFormat.Format.nSamplesPerSec ) ); + + if ( FAILED( pXAudio2->CreateMasteringVoice( &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, outputSampleRate, 0, preferredDevice, NULL ) ) ) { + idLib::Warning( "Failed to create master voice" ); + pXAudio2->Release(); + pXAudio2 = NULL; + return; + } + pMasterVoice->SetVolume( DBtoLinear( s_volume_dB.GetFloat() ) ); + + outputChannels = deviceDetails.OutputFormat.Format.nChannels; + channelMask = deviceDetails.OutputFormat.dwChannelMask; + + idSoundVoice::InitSurround( outputChannels, channelMask ); + + // --------------------- + // Initialize the Doom classic sound system. + // --------------------- + I_InitSoundHardware( outputChannels, channelMask ); + + // --------------------- + // Create VU Meter Effect + // --------------------- + IUnknown * vuMeter = NULL; + XAudio2CreateVolumeMeter( &vuMeter, 0 ); + + XAUDIO2_EFFECT_DESCRIPTOR descriptor; + descriptor.InitialState = true; + descriptor.OutputChannels = outputChannels; + descriptor.pEffect = vuMeter; + + XAUDIO2_EFFECT_CHAIN chain; + chain.EffectCount = 1; + chain.pEffectDescriptors = &descriptor; + + pMasterVoice->SetEffectChain( &chain ); + + vuMeter->Release(); + + // --------------------- + // Create VU Meter Graph + // --------------------- + + vuMeterRMS = console->CreateGraph( outputChannels ); + vuMeterPeak = console->CreateGraph( outputChannels ); + vuMeterRMS->Enable( false ); + vuMeterPeak->Enable( false ); + + memset( vuMeterPeakTimes, 0, sizeof( vuMeterPeakTimes ) ); + + vuMeterPeak->SetFillMode( idDebugGraph::GRAPH_LINE ); + vuMeterPeak->SetBackgroundColor( idVec4( 0.0f, 0.0f, 0.0f, 0.0f ) ); + + vuMeterRMS->AddGridLine( 0.500f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) ); + vuMeterRMS->AddGridLine( 0.250f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) ); + vuMeterRMS->AddGridLine( 0.125f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) ); + + const char * channelNames[] = { "L", "R", "C", "S", "Lb", "Rb", "Lf", "Rf", "Cb", "Ls", "Rs" }; + for ( int i = 0, ci = 0; ci < sizeof( channelNames ) / sizeof( channelNames[0] ); ci++ ) { + if ( ( channelMask & BIT( ci ) ) == 0 ) { + continue; + } + vuMeterRMS->SetLabel( i, channelNames[ ci ] ); + i++; + } + + // --------------------- + // Create submix buffer + // --------------------- + if ( FAILED( pXAudio2->CreateSubmixVoice( &pSubmixVoice, 1, outputSampleRate, 0, 0, NULL, NULL ) ) ) { + idLib::FatalError( "Failed to create submix voice" ); + } + + // XAudio doesn't really impose a maximum number of voices + voices.SetNum( voices.Max() ); + freeVoices.SetNum( voices.Max() ); + zombieVoices.SetNum( 0 ); + for ( int i = 0; i < voices.Num(); i++ ) { + freeVoices[i] = &voices[i]; + } +} + +/* +======================== +idSoundHardware_XAudio2::Shutdown +======================== +*/ +void idSoundHardware_XAudio2::Shutdown() { + for ( int i = 0; i < voices.Num(); i++ ) { + voices[ i ].DestroyInternal(); + } + voices.Clear(); + freeVoices.Clear(); + zombieVoices.Clear(); + + // --------------------- + // Shutdown the Doom classic sound system. + // --------------------- + I_ShutdownSoundHardware(); + + if ( pXAudio2 != NULL ) { + // Unregister the sound engine callback + pXAudio2->UnregisterForCallbacks( &soundEngineCallback ); + } + + if ( pSubmixVoice != NULL ) { + pSubmixVoice->DestroyVoice(); + pSubmixVoice = NULL; + } + if ( pMasterVoice != NULL ) { + // release the vu meter effect + pMasterVoice->SetEffectChain( NULL ); + pMasterVoice->DestroyVoice(); + pMasterVoice = NULL; + } + if ( pXAudio2 != NULL ) { + XAUDIO2_PERFORMANCE_DATA perfData; + pXAudio2->GetPerformanceData( &perfData ); + idLib::Printf( "Final pXAudio2 performanceData: Voices: %d/%d CPU: %.2f%% Mem: %dkb\n", perfData.ActiveSourceVoiceCount, perfData.TotalSourceVoiceCount, perfData.AudioCyclesSinceLastQuery / (float)perfData.TotalCyclesSinceLastQuery, perfData.MemoryUsageInBytes / 1024 ); + pXAudio2->Release(); + pXAudio2 = NULL; + } + if ( vuMeterRMS != NULL ) { + console->DestroyGraph( vuMeterRMS ); + vuMeterRMS = NULL; + } + if ( vuMeterPeak != NULL ) { + console->DestroyGraph( vuMeterPeak ); + vuMeterPeak = NULL; + } +} + +/* +======================== +idSoundHardware_XAudio2::AllocateVoice +======================== +*/ +idSoundVoice * idSoundHardware_XAudio2::AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ) { + if ( leadinSample == NULL ) { + return NULL; + } + if ( loopingSample != NULL ) { + if ( ( leadinSample->format.basic.formatTag != loopingSample->format.basic.formatTag ) || ( leadinSample->format.basic.numChannels != loopingSample->format.basic.numChannels ) ) { + idLib::Warning( "Leadin/looping format mismatch: %s & %s", leadinSample->GetName(), loopingSample->GetName() ); + loopingSample = NULL; + } + } + + // Try to find a free voice that matches the format + // But fallback to the last free voice if none match the format + idSoundVoice * voice = NULL; + for ( int i = 0; i < freeVoices.Num(); i++ ) { + if ( freeVoices[i]->IsPlaying() ) { + continue; + } + voice = (idSoundVoice *)freeVoices[i]; + if ( voice->CompatibleFormat( (idSoundSample_XAudio2*)leadinSample ) ) { + break; + } + } + if ( voice != NULL ) { + voice->Create( leadinSample, loopingSample ); + freeVoices.Remove( voice ); + return voice; + } + + return NULL; +} + +/* +======================== +idSoundHardware_XAudio2::FreeVoice +======================== +*/ +void idSoundHardware_XAudio2::FreeVoice( idSoundVoice * voice ) { + voice->Stop(); + + // Stop() is asyncronous, so we won't flush bufferes until the + // voice on the zombie channel actually returns !IsPlaying() + zombieVoices.Append( voice ); +} + +/* +======================== +idSoundHardware_XAudio2::Update +======================== +*/ +void idSoundHardware_XAudio2::Update() { + if ( pXAudio2 == NULL ) { + int nowTime = Sys_Milliseconds(); + if ( lastResetTime + 1000 < nowTime ) { + lastResetTime = nowTime; + Init(); + } + return; + } + if ( soundSystem->IsMuted() ) { + pMasterVoice->SetVolume( 0.0f, OPERATION_SET ); + } else { + pMasterVoice->SetVolume( DBtoLinear( s_volume_dB.GetFloat() ), OPERATION_SET ); + } + + pXAudio2->CommitChanges( XAUDIO2_COMMIT_ALL ); + + // IXAudio2SourceVoice::Stop() has been called for every sound on the + // zombie list, but it is documented as asyncronous, so we have to wait + // until it actually reports that it is no longer playing. + for ( int i = 0; i < zombieVoices.Num(); i++ ) { + zombieVoices[i]->FlushSourceBuffers(); + if ( !zombieVoices[i]->IsPlaying() ) { + freeVoices.Append( zombieVoices[i] ); + zombieVoices.RemoveIndexFast( i ); + i--; + } else { + static int playingZombies; + playingZombies++; + } + } + + if ( s_showPerfData.GetBool() ) { + XAUDIO2_PERFORMANCE_DATA perfData; + pXAudio2->GetPerformanceData( &perfData ); + idLib::Printf( "Voices: %d/%d CPU: %.2f%% Mem: %dkb\n", perfData.ActiveSourceVoiceCount, perfData.TotalSourceVoiceCount, perfData.AudioCyclesSinceLastQuery / (float)perfData.TotalCyclesSinceLastQuery, perfData.MemoryUsageInBytes / 1024 ); + } + + if ( vuMeterRMS == NULL ) { + // Init probably hasn't been called yet + return; + } + + vuMeterRMS->Enable( s_showLevelMeter.GetBool() ); + vuMeterPeak->Enable( s_showLevelMeter.GetBool() ); + + if ( !s_showLevelMeter.GetBool() ) { + pMasterVoice->DisableEffect( 0 ); + return; + } else { + pMasterVoice->EnableEffect( 0 ); + } + + float peakLevels[ 8 ]; + float rmsLevels[ 8 ]; + + XAUDIO2FX_VOLUMEMETER_LEVELS levels; + levels.ChannelCount = outputChannels; + levels.pPeakLevels = peakLevels; + levels.pRMSLevels = rmsLevels; + + if ( levels.ChannelCount > 8 ) { + levels.ChannelCount = 8; + } + + pMasterVoice->GetEffectParameters( 0, &levels, sizeof( levels ) ); + + int currentTime = Sys_Milliseconds(); + for ( int i = 0; i < outputChannels; i++ ) { + if ( vuMeterPeakTimes[i] < currentTime ) { + vuMeterPeak->SetValue( i, vuMeterPeak->GetValue( i ) * 0.9f, colorRed ); + } + } + + float width = 20.0f; + float height = 200.0f; + float left = 100.0f; + float top = 100.0f; + + sscanf( s_meterPosition.GetString(), "%f %f %f %f", &left, &top, &width, &height ); + + vuMeterRMS->SetPosition( left, top, width * levels.ChannelCount, height ); + vuMeterPeak->SetPosition( left, top, width * levels.ChannelCount, height ); + + for ( uint32 i = 0; i < levels.ChannelCount; i++ ) { + vuMeterRMS->SetValue( i, rmsLevels[ i ], idVec4( 0.5f, 1.0f, 0.0f, 1.00f ) ); + if ( peakLevels[ i ] >= vuMeterPeak->GetValue( i ) ) { + vuMeterPeak->SetValue( i, peakLevels[ i ], colorRed ); + vuMeterPeakTimes[i] = currentTime + s_meterTopTime.GetInteger(); + } + } +} + + +/* +================================================ +idSoundEngineCallback +================================================ +*/ + +/* +======================== +idSoundEngineCallback::OnCriticalError +======================== +*/ +void idSoundEngineCallback::OnCriticalError( HRESULT Error ) { + soundSystemLocal.SetNeedsRestart(); +} diff --git a/neo/sound/XAudio2/XA2_SoundHardware.h b/neo/sound/XAudio2/XA2_SoundHardware.h new file mode 100644 index 00000000..46637193 --- /dev/null +++ b/neo/sound/XAudio2/XA2_SoundHardware.h @@ -0,0 +1,112 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __XA_SOUNDHARDWARE_H__ +#define __XA_SOUNDHARDWARE_H__ + +class idSoundSample_XAudio2; +class idSoundVoice_XAudio2; + +/* +================================================ +idSoundEngineCallback +================================================ +*/ +class idSoundEngineCallback : public IXAudio2EngineCallback { +public: + idSoundHardware_XAudio2 * hardware; + +private: + // Called by XAudio2 just before an audio processing pass begins. + STDMETHOD_( void, OnProcessingPassStart ) ( THIS ) {} + + // Called just after an audio processing pass ends. + STDMETHOD_( void, OnProcessingPassEnd ) ( THIS ) {} + + // Called in the event of a critical system error which requires XAudio2 + // to be closed down and restarted. The error code is given in Error. + STDMETHOD_( void, OnCriticalError ) ( THIS_ HRESULT Error ); +}; + +/* +================================================ +idSoundHardware_XAudio2 +================================================ +*/ + +class idSoundHardware_XAudio2 { +public: + idSoundHardware_XAudio2(); + + void Init(); + void Shutdown(); + + void Update(); + + idSoundVoice * AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ); + void FreeVoice( idSoundVoice * voice ); + + // video playback needs this + IXAudio2 * GetIXAudio2() const { return pXAudio2; }; + + int GetNumZombieVoices() const { return zombieVoices.Num(); } + int GetNumFreeVoices() const { return freeVoices.Num(); } + +protected: + friend class idSoundSample_XAudio2; + friend class idSoundVoice_XAudio2; + +private: + IXAudio2 * pXAudio2; + IXAudio2MasteringVoice * pMasterVoice; + IXAudio2SubmixVoice * pSubmixVoice; + + idSoundEngineCallback soundEngineCallback; + int lastResetTime; + + int outputChannels; + int channelMask; + + idDebugGraph * vuMeterRMS; + idDebugGraph * vuMeterPeak; + int vuMeterPeakTimes[ 8 ]; + + // Can't stop and start a voice on the same frame, so we have to double this to handle the worst case scenario of stopping all voices and starting a full new set + idStaticList voices; + idStaticList zombieVoices; + idStaticList freeVoices; +}; + +/* +================================================ +idSoundHardware +================================================ +*/ +class idSoundHardware : public idSoundHardware_XAudio2 { +}; + +#endif diff --git a/neo/sound/XAudio2/XA2_SoundSample.cpp b/neo/sound/XAudio2/XA2_SoundSample.cpp new file mode 100644 index 00000000..d8bb595c --- /dev/null +++ b/neo/sound/XAudio2/XA2_SoundSample.cpp @@ -0,0 +1,487 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../snd_local.h" + +extern idCVar s_useCompression; +extern idCVar s_noSound; + +#define GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( x ) x + +const uint32 SOUND_MAGIC_IDMSA = 0x6D7A7274; + +extern idCVar sys_lang; + +/* +======================== +AllocBuffer +======================== +*/ +static void * AllocBuffer( int size, const char * name ) { + return Mem_Alloc( size, TAG_AUDIO ); +} + +/* +======================== +FreeBuffer +======================== +*/ +static void FreeBuffer( void * p ) { + return Mem_Free( p ); +} + +/* +======================== +idSoundSample_XAudio2::idSoundSample_XAudio2 +======================== +*/ +idSoundSample_XAudio2::idSoundSample_XAudio2() { + timestamp = FILE_NOT_FOUND_TIMESTAMP; + loaded = false; + neverPurge = false; + levelLoadReferenced = false; + + memset( &format, 0, sizeof( format ) ); + + totalBufferSize = 0; + + playBegin = 0; + playLength = 0; + + lastPlayedTime = 0; +} + +/* +======================== +idSoundSample_XAudio2::~idSoundSample_XAudio2 +======================== +*/ +idSoundSample_XAudio2::~idSoundSample_XAudio2() { + FreeData(); +} + +/* +======================== +idSoundSample_XAudio2::WriteGeneratedSample +======================== +*/ +void idSoundSample_XAudio2::WriteGeneratedSample( idFile *fileOut ) { + fileOut->WriteBig( SOUND_MAGIC_IDMSA ); + fileOut->WriteBig( timestamp ); + fileOut->WriteBig( loaded ); + fileOut->WriteBig( playBegin ); + fileOut->WriteBig( playLength ); + idWaveFile::WriteWaveFormatDirect( format, fileOut ); + fileOut->WriteBig( ( int )amplitude.Num() ); + fileOut->Write( amplitude.Ptr(), amplitude.Num() ); + fileOut->WriteBig( totalBufferSize ); + fileOut->WriteBig( ( int )buffers.Num() ); + for ( int i = 0; i < buffers.Num(); i++ ) { + fileOut->WriteBig( buffers[ i ].numSamples ); + fileOut->WriteBig( buffers[ i ].bufferSize ); + fileOut->Write( buffers[ i ].buffer, buffers[ i ].bufferSize ); + }; +} +/* +======================== +idSoundSample_XAudio2::WriteAllSamples +======================== +*/ +void idSoundSample_XAudio2::WriteAllSamples( const idStr &sampleName ) { + idSoundSample_XAudio2 * samplePC = new idSoundSample_XAudio2(); + { + idStrStatic< MAX_OSPATH > inName = sampleName; + inName.Append( ".msadpcm" ); + idStrStatic< MAX_OSPATH > inName2 = sampleName; + inName2.Append( ".wav" ); + + idStrStatic< MAX_OSPATH > outName = "generated/"; + outName.Append( sampleName ); + outName.Append( ".idwav" ); + + if ( samplePC->LoadWav( inName ) || samplePC->LoadWav( inName2 ) ) { + idFile *fileOut = fileSystem->OpenFileWrite( outName, "fs_basepath" ); + samplePC->WriteGeneratedSample( fileOut ); + delete fileOut; + } + } + delete samplePC; +} + +/* +======================== +idSoundSample_XAudio2::LoadGeneratedSound +======================== +*/ +bool idSoundSample_XAudio2::LoadGeneratedSample( const idStr &filename ) { + idFileLocal fileIn( fileSystem->OpenFileReadMemory( filename ) ); + if ( fileIn != NULL ) { + uint32 magic; + fileIn->ReadBig( magic ); + fileIn->ReadBig( timestamp ); + fileIn->ReadBig( loaded ); + fileIn->ReadBig( playBegin ); + fileIn->ReadBig( playLength ); + idWaveFile::ReadWaveFormatDirect( format, fileIn ); + int num; + fileIn->ReadBig( num ); + amplitude.Clear(); + amplitude.SetNum( num ); + fileIn->Read( amplitude.Ptr(), amplitude.Num() ); + fileIn->ReadBig( totalBufferSize ); + fileIn->ReadBig( num ); + buffers.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + fileIn->ReadBig( buffers[ i ].numSamples ); + fileIn->ReadBig( buffers[ i ].bufferSize ); + buffers[ i ].buffer = AllocBuffer( buffers[ i ].bufferSize, GetName() ); + fileIn->Read( buffers[ i ].buffer, buffers[ i ].bufferSize ); + buffers[ i ].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[ i ].buffer ); + } + return true; + } + return false; +} +/* +======================== +idSoundSample_XAudio2::Load +======================== +*/ +void idSoundSample_XAudio2::LoadResource() { + FreeData(); + + if ( idStr::Icmpn( GetName(), "_default", 8 ) == 0 ) { + MakeDefault(); + return; + } + + if ( s_noSound.GetBool() ) { + MakeDefault(); + return; + } + + loaded = false; + + for ( int i = 0; i < 2; i++ ) { + idStrStatic< MAX_OSPATH > sampleName = GetName(); + if ( ( i == 0 ) && !sampleName.Replace( "/vo/", va( "/vo/%s/", sys_lang.GetString() ) ) ) { + i++; + } + idStrStatic< MAX_OSPATH > generatedName = "generated/"; + generatedName.Append( sampleName ); + + { + if ( s_useCompression.GetBool() ) { + sampleName.Append( ".msadpcm" ); + } else { + sampleName.Append( ".wav" ); + } + generatedName.Append( ".idwav" ); + } + loaded = LoadGeneratedSample( generatedName ) || LoadWav( sampleName ); + + if ( !loaded && s_useCompression.GetBool() ) { + sampleName.SetFileExtension( "wav" ); + loaded = LoadWav( sampleName ); + } + + if ( loaded ) { + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) { + fileSystem->AddSamplePreload( GetName() ); + WriteAllSamples( GetName() ); + + if ( sampleName.Find( "/vo/" ) >= 0 ) { + for ( int i = 0; i < Sys_NumLangs(); i++ ) { + const char * lang = Sys_Lang( i ); + if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) { + continue; + } + idStrStatic< MAX_OSPATH > locName = GetName(); + locName.Replace( "/vo/", va( "/vo/%s/", Sys_Lang( i ) ) ); + WriteAllSamples( locName ); + } + } + } + return; + } + } + + if ( !loaded ) { + // make it default if everything else fails + MakeDefault(); + } + return; +} + +/* +======================== +idSoundSample_XAudio2::LoadWav +======================== +*/ +bool idSoundSample_XAudio2::LoadWav( const idStr & filename ) { + + // load the wave + idWaveFile wave; + if ( !wave.Open( filename ) ) { + return false; + } + + idStrStatic< MAX_OSPATH > sampleName = filename; + sampleName.SetFileExtension( "amp" ); + LoadAmplitude( sampleName ); + + const char * formatError = wave.ReadWaveFormat( format ); + if ( formatError != NULL ) { + idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), formatError ); + MakeDefault(); + return false; + } + timestamp = wave.Timestamp(); + + totalBufferSize = wave.SeekToChunk( 'data' ); + + if ( format.basic.formatTag == idWaveFile::FORMAT_PCM || format.basic.formatTag == idWaveFile::FORMAT_EXTENSIBLE ) { + + if ( format.basic.bitsPerSample != 16 ) { + idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), "Not a 16 bit PCM wav file" ); + MakeDefault(); + return false; + } + + playBegin = 0; + playLength = ( totalBufferSize ) / format.basic.blockSize; + + buffers.SetNum( 1 ); + buffers[0].bufferSize = totalBufferSize; + buffers[0].numSamples = playLength; + buffers[0].buffer = AllocBuffer( totalBufferSize, GetName() ); + + + wave.Read( buffers[0].buffer, totalBufferSize ); + + if ( format.basic.bitsPerSample == 16 ) { + idSwap::LittleArray( (short *)buffers[0].buffer, totalBufferSize / sizeof( short ) ); + } + + buffers[0].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[0].buffer ); + + } else if ( format.basic.formatTag == idWaveFile::FORMAT_ADPCM ) { + + playBegin = 0; + playLength = ( ( totalBufferSize / format.basic.blockSize ) * format.extra.adpcm.samplesPerBlock ); + + buffers.SetNum( 1 ); + buffers[0].bufferSize = totalBufferSize; + buffers[0].numSamples = playLength; + buffers[0].buffer = AllocBuffer( totalBufferSize, GetName() ); + + wave.Read( buffers[0].buffer, totalBufferSize ); + + buffers[0].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[0].buffer ); + + } else if ( format.basic.formatTag == idWaveFile::FORMAT_XMA2 ) { + + if ( format.extra.xma2.blockCount == 0 ) { + idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), "No data blocks in file" ); + MakeDefault(); + return false; + } + + int bytesPerBlock = format.extra.xma2.bytesPerBlock; + assert( format.extra.xma2.blockCount == ALIGN( totalBufferSize, bytesPerBlock ) / bytesPerBlock ); + assert( format.extra.xma2.blockCount * bytesPerBlock >= totalBufferSize ); + assert( format.extra.xma2.blockCount * bytesPerBlock < totalBufferSize + bytesPerBlock ); + + buffers.SetNum( format.extra.xma2.blockCount ); + for ( int i = 0; i < buffers.Num(); i++ ) { + if ( i == buffers.Num() - 1 ) { + buffers[i].bufferSize = totalBufferSize - ( i * bytesPerBlock ); + } else { + buffers[i].bufferSize = bytesPerBlock; + } + + buffers[i].buffer = AllocBuffer( buffers[i].bufferSize, GetName() ); + wave.Read( buffers[i].buffer, buffers[i].bufferSize ); + buffers[i].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[i].buffer ); + } + + int seekTableSize = wave.SeekToChunk( 'seek' ); + if ( seekTableSize != 4 * buffers.Num() ) { + idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), "Wrong number of entries in seek table" ); + MakeDefault(); + return false; + } + + for ( int i = 0; i < buffers.Num(); i++ ) { + wave.Read( &buffers[i].numSamples, sizeof( buffers[i].numSamples ) ); + idSwap::Big( buffers[i].numSamples ); + } + + playBegin = format.extra.xma2.loopBegin; + playLength = format.extra.xma2.loopLength; + + if ( buffers[buffers.Num()-1].numSamples < playBegin + playLength ) { + // This shouldn't happen, but it's not fatal if it does + playLength = buffers[buffers.Num()-1].numSamples - playBegin; + } else { + // Discard samples beyond playLength + for ( int i = 0; i < buffers.Num(); i++ ) { + if ( buffers[i].numSamples > playBegin + playLength ) { + buffers[i].numSamples = playBegin + playLength; + // Ideally, the following loop should always have 0 iterations because playBegin + playLength ends in the last block already + // But there is no guarantee for that, so to be safe, discard all buffers beyond this one + for ( int j = i + 1; j < buffers.Num(); j++ ) { + FreeBuffer( buffers[j].buffer ); + } + buffers.SetNum( i + 1 ); + break; + } + } + } + + } else { + idLib::Warning( "LoadWav( %s ) : Unsupported wave format %d", filename.c_str(), format.basic.formatTag ); + MakeDefault(); + return false; + } + + wave.Close(); + + if ( format.basic.formatTag == idWaveFile::FORMAT_EXTENSIBLE ) { + // HACK: XAudio2 doesn't really support FORMAT_EXTENSIBLE so we convert it to a basic format after extracting the channel mask + format.basic.formatTag = format.extra.extensible.subFormat.data1; + } + + // sanity check... + assert( buffers[buffers.Num()-1].numSamples == playBegin + playLength ); + + return true; +} + + +/* +======================== +idSoundSample_XAudio2::MakeDefault +======================== +*/ +void idSoundSample_XAudio2::MakeDefault() { + FreeData(); + + static const int DEFAULT_NUM_SAMPLES = 256; + + timestamp = FILE_NOT_FOUND_TIMESTAMP; + loaded = true; + + memset( &format, 0, sizeof( format ) ); + format.basic.formatTag = idWaveFile::FORMAT_PCM; + format.basic.numChannels = 1; + format.basic.bitsPerSample = 16; + format.basic.samplesPerSec = XAUDIO2_MIN_SAMPLE_RATE; + format.basic.blockSize = format.basic.numChannels * format.basic.bitsPerSample / 8; + format.basic.avgBytesPerSec = format.basic.samplesPerSec * format.basic.blockSize; + + assert( format.basic.blockSize == 2 ); + + totalBufferSize = DEFAULT_NUM_SAMPLES * 2; + + short * defaultBuffer = (short *)AllocBuffer( totalBufferSize, GetName() ); + for ( int i = 0; i < DEFAULT_NUM_SAMPLES; i += 2 ) { + defaultBuffer[i + 0] = SHRT_MIN; + defaultBuffer[i + 1] = SHRT_MAX; + } + + buffers.SetNum( 1 ); + buffers[0].buffer = defaultBuffer; + buffers[0].bufferSize = totalBufferSize; + buffers[0].numSamples = DEFAULT_NUM_SAMPLES; + buffers[0].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[0].buffer ); + + playBegin = 0; + playLength = DEFAULT_NUM_SAMPLES; +} + +/* +======================== +idSoundSample_XAudio2::FreeData + +Called before deleting the object and at the start of LoadResource() +======================== +*/ +void idSoundSample_XAudio2::FreeData() { + if ( buffers.Num() > 0 ) { + soundSystemLocal.StopVoicesWithSample( (idSoundSample *)this ); + for ( int i = 0; i < buffers.Num(); i++ ) { + FreeBuffer( buffers[i].buffer ); + } + buffers.Clear(); + } + amplitude.Clear(); + + timestamp = FILE_NOT_FOUND_TIMESTAMP; + memset( &format, 0, sizeof( format ) ); + loaded = false; + totalBufferSize = 0; + playBegin = 0; + playLength = 0; +} + +/* +======================== +idSoundSample_XAudio2::LoadAmplitude +======================== +*/ +bool idSoundSample_XAudio2::LoadAmplitude( const idStr & name ) { + amplitude.Clear(); + idFileLocal f( fileSystem->OpenFileRead( name ) ); + if ( f == NULL ) { + return false; + } + amplitude.SetNum( f->Length() ); + f->Read( amplitude.Ptr(), amplitude.Num() ); + return true; +} + +/* +======================== +idSoundSample_XAudio2::GetAmplitude +======================== +*/ +float idSoundSample_XAudio2::GetAmplitude( int timeMS ) const { + if ( timeMS < 0 || timeMS > LengthInMsec() ) { + return 0.0f; + } + if ( IsDefault() ) { + return 1.0f; + } + int index = timeMS * 60 / 1000; + if ( index < 0 || index >= amplitude.Num() ) { + return 0.0f; + } + return (float)amplitude[index] / 255.0f; +} diff --git a/neo/sound/XAudio2/XA2_SoundSample.h b/neo/sound/XAudio2/XA2_SoundSample.h new file mode 100644 index 00000000..b3b6bfca --- /dev/null +++ b/neo/sound/XAudio2/XA2_SoundSample.h @@ -0,0 +1,129 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __XA2_SOUNDSAMPLE_H__ +#define __XA2_SOUNDSAMPLE_H__ + +/* +================================================ +idSoundSample_XAudio2 +================================================ +*/ +class idSampleInfo; +class idSoundSample_XAudio2 { +public: + idSoundSample_XAudio2(); + + // Loads and initializes the resource based on the name. + virtual void LoadResource(); + + void SetName( const char * n ) { name = n; } + const char * GetName() const { return name; } + ID_TIME_T GetTimestamp() const { return timestamp; } + + // turns it into a beep + void MakeDefault(); + + // frees all data + void FreeData(); + + int LengthInMsec() const { return SamplesToMsec( NumSamples(), SampleRate() ); } + int SampleRate() const { return format.basic.samplesPerSec; } + int NumSamples() const { return playLength; } + int NumChannels() const { return format.basic.numChannels; } + int BufferSize() const { return totalBufferSize; } + + bool IsCompressed() const { return ( format.basic.formatTag != idWaveFile::FORMAT_PCM ); } + + bool IsDefault() const { return timestamp == FILE_NOT_FOUND_TIMESTAMP; } + bool IsLoaded() const { return loaded; } + + void SetNeverPurge() { neverPurge = true; } + bool GetNeverPurge() const { return neverPurge; } + + void SetLevelLoadReferenced() { levelLoadReferenced = true; } + void ResetLevelLoadReferenced() { levelLoadReferenced = false; } + bool GetLevelLoadReferenced() const { return levelLoadReferenced; } + + int GetLastPlayedTime() const { return lastPlayedTime; } + void SetLastPlayedTime( int t ) { lastPlayedTime = t; } + + float GetAmplitude( int timeMS ) const; + +protected: + friend class idSoundHardware_XAudio2; + friend class idSoundVoice_XAudio2; + + ~idSoundSample_XAudio2(); + + bool LoadWav( const idStr & name ); + bool LoadAmplitude( const idStr & name ); + void WriteAllSamples( const idStr &sampleName ); + bool LoadGeneratedSample( const idStr &name ); + void WriteGeneratedSample( idFile *fileOut ); + + struct sampleBuffer_t { + void * buffer; + int bufferSize; + int numSamples; + }; + + idStr name; + + ID_TIME_T timestamp; + bool loaded; + + bool neverPurge; + bool levelLoadReferenced; + bool usesMapHeap; + + uint32 lastPlayedTime; + + int totalBufferSize; // total size of all the buffers + idList buffers; + + int playBegin; + int playLength; + + idWaveFile::waveFmt_t format; + + idList amplitude; +}; + +/* +================================================ +idSoundSample + +This reverse-inheritance purportedly makes working on +multiple platforms easier. +================================================ +*/ +class idSoundSample : public idSoundSample_XAudio2 { +public: +}; + +#endif diff --git a/neo/sound/XAudio2/XA2_SoundVoice.cpp b/neo/sound/XAudio2/XA2_SoundVoice.cpp new file mode 100644 index 00000000..a8aff0db --- /dev/null +++ b/neo/sound/XAudio2/XA2_SoundVoice.cpp @@ -0,0 +1,492 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../snd_local.h" + +idCVar s_skipHardwareSets( "s_skipHardwareSets", "0", CVAR_BOOL, "Do all calculation, but skip XA2 calls" ); +idCVar s_debugHardware( "s_debugHardware", "0", CVAR_BOOL, "Print a message any time a hardware voice changes" ); + +// The whole system runs at this sample rate +static int SYSTEM_SAMPLE_RATE = 44100; +static float ONE_OVER_SYSTEM_SAMPLE_RATE = 1.0f / SYSTEM_SAMPLE_RATE; + +/* +======================== +idStreamingVoiceContext +======================== +*/ +class idStreamingVoiceContext : public IXAudio2VoiceCallback { +public: + STDMETHOD_(void, OnVoiceProcessingPassStart)( UINT32 BytesRequired ) {} + STDMETHOD_(void, OnVoiceProcessingPassEnd)() {} + STDMETHOD_(void, OnStreamEnd)() {} + STDMETHOD_(void, OnBufferStart)( void * pContext ) { + idSoundSystemLocal::bufferContext_t * bufferContext = (idSoundSystemLocal::bufferContext_t *) pContext; + bufferContext->voice->OnBufferStart( bufferContext->sample, bufferContext->bufferNumber ); + } + STDMETHOD_(void, OnLoopEnd)( void * ) {} + STDMETHOD_(void, OnVoiceError)( void *, HRESULT hr ) { idLib::Warning( "OnVoiceError( %d )", hr ); } + STDMETHOD_(void, OnBufferEnd)( void* pContext ) { + idSoundSystemLocal::bufferContext_t * bufferContext = (idSoundSystemLocal::bufferContext_t *) pContext; + soundSystemLocal.ReleaseStreamBufferContext( bufferContext ); + } +} streamContext; + +/* +======================== +idSoundVoice_XAudio2::idSoundVoice_XAudio2 +======================== +*/ +idSoundVoice_XAudio2::idSoundVoice_XAudio2() +: pSourceVoice( NULL ), + leadinSample( NULL ), + loopingSample( NULL ), + formatTag( 0 ), + numChannels( 0 ), + sampleRate( 0 ), + paused( true ), + hasVUMeter( false ) { + +} + +/* +======================== +idSoundVoice_XAudio2::~idSoundVoice_XAudio2 +======================== +*/ +idSoundVoice_XAudio2::~idSoundVoice_XAudio2() { + DestroyInternal(); +} + +/* +======================== +idSoundVoice_XAudio2::CompatibleFormat +======================== +*/ +bool idSoundVoice_XAudio2::CompatibleFormat( idSoundSample_XAudio2 * s ) { + if ( pSourceVoice == NULL ) { + // If this voice has never been allocated, then it's compatible with everything + return true; + } + return false; +} + +/* +======================== +idSoundVoice_XAudio2::Create +======================== +*/ +void idSoundVoice_XAudio2::Create( const idSoundSample * leadinSample_, const idSoundSample * loopingSample_ ) { + if ( IsPlaying() ) { + // This should never hit + Stop(); + return; + } + leadinSample = (idSoundSample_XAudio2 *)leadinSample_; + loopingSample = (idSoundSample_XAudio2 *)loopingSample_; + + if ( pSourceVoice != NULL && CompatibleFormat( leadinSample ) ) { + sampleRate = leadinSample->format.basic.samplesPerSec; + } else { + DestroyInternal(); + formatTag = leadinSample->format.basic.formatTag; + numChannels = leadinSample->format.basic.numChannels; + sampleRate = leadinSample->format.basic.samplesPerSec; + + soundSystemLocal.hardware.pXAudio2->CreateSourceVoice( &pSourceVoice, (const WAVEFORMATEX *)&leadinSample->format, XAUDIO2_VOICE_USEFILTER, 4.0f, &streamContext ); + if ( pSourceVoice == NULL ) { + // If this hits, then we are most likely passing an invalid sample format, which should have been caught by the loader (and the sample defaulted) + return; + } + if ( s_debugHardware.GetBool() ) { + if ( loopingSample == NULL || loopingSample == leadinSample ) { + idLib::Printf( "%dms: %p created for %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "" ); + } else { + idLib::Printf( "%dms: %p created for %s and %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "", loopingSample ? loopingSample->GetName() : "" ); + } + } + } + sourceVoiceRate = sampleRate; + pSourceVoice->SetSourceSampleRate( sampleRate ); + pSourceVoice->SetVolume( 0.0f ); +} + +/* +======================== +idSoundVoice_XAudio2::DestroyInternal +======================== +*/ +void idSoundVoice_XAudio2::DestroyInternal() { + if ( pSourceVoice != NULL ) { + if ( s_debugHardware.GetBool() ) { + idLib::Printf( "%dms: %p destroyed\n", Sys_Milliseconds(), pSourceVoice ); + } + pSourceVoice->DestroyVoice(); + pSourceVoice = NULL; + hasVUMeter = false; + } +} + +/* +======================== +idSoundVoice_XAudio2::Start +======================== +*/ +void idSoundVoice_XAudio2::Start( int offsetMS, int ssFlags ) { + + if ( s_debugHardware.GetBool() ) { + idLib::Printf( "%dms: %p starting %s @ %dms\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "", offsetMS ); + } + + if ( !leadinSample ) { + return; + } + if ( !pSourceVoice ) { + return; + } + + if ( leadinSample->IsDefault() ) { + idLib::Warning( "Starting defaulted sound sample %s", leadinSample->GetName() ); + } + + bool flicker = ( ssFlags & SSF_NO_FLICKER ) == 0; + + if ( flicker != hasVUMeter ) { + hasVUMeter = flicker; + + if ( flicker ) { + IUnknown * vuMeter = NULL; + if ( XAudio2CreateVolumeMeter( &vuMeter, 0 ) == S_OK ) { + + XAUDIO2_EFFECT_DESCRIPTOR descriptor; + descriptor.InitialState = true; + descriptor.OutputChannels = leadinSample->NumChannels(); + descriptor.pEffect = vuMeter; + + XAUDIO2_EFFECT_CHAIN chain; + chain.EffectCount = 1; + chain.pEffectDescriptors = &descriptor; + + pSourceVoice->SetEffectChain( &chain ); + + vuMeter->Release(); + } + } else { + pSourceVoice->SetEffectChain( NULL ); + } + } + + assert( offsetMS >= 0 ); + int offsetSamples = MsecToSamples( offsetMS, leadinSample->SampleRate() ); + if ( loopingSample == NULL && offsetSamples >= leadinSample->playLength ) { + return; + } + + RestartAt( offsetSamples ); + Update(); + UnPause(); +} + +/* +======================== +idSoundVoice_XAudio2::RestartAt +======================== +*/ +int idSoundVoice_XAudio2::RestartAt( int offsetSamples ) { + offsetSamples &= ~127; + + idSoundSample_XAudio2 * sample = leadinSample; + if ( offsetSamples >= leadinSample->playLength ) { + if ( loopingSample != NULL ) { + offsetSamples %= loopingSample->playLength; + sample = loopingSample; + } else { + return 0; + } + } + + int previousNumSamples = 0; + for ( int i = 0; i < sample->buffers.Num(); i++ ) { + if ( sample->buffers[i].numSamples > sample->playBegin + offsetSamples ) { + return SubmitBuffer( sample, i, sample->playBegin + offsetSamples - previousNumSamples ); + } + previousNumSamples = sample->buffers[i].numSamples; + } + + return 0; +} + +/* +======================== +idSoundVoice_XAudio2::SubmitBuffer +======================== +*/ +int idSoundVoice_XAudio2::SubmitBuffer( idSoundSample_XAudio2 * sample, int bufferNumber, int offset ) { + + if ( sample == NULL || ( bufferNumber < 0 ) || ( bufferNumber >= sample->buffers.Num() ) ) { + return 0; + } + idSoundSystemLocal::bufferContext_t * bufferContext = soundSystemLocal.ObtainStreamBufferContext(); + if ( bufferContext == NULL ) { + idLib::Warning( "No free buffer contexts!" ); + return 0; + } + + bufferContext->voice = this; + bufferContext->sample = sample; + bufferContext->bufferNumber = bufferNumber; + + XAUDIO2_BUFFER buffer = { 0 }; + if ( offset > 0 ) { + int previousNumSamples = 0; + if ( bufferNumber > 0 ) { + previousNumSamples = sample->buffers[bufferNumber-1].numSamples; + } + buffer.PlayBegin = offset; + buffer.PlayLength = sample->buffers[bufferNumber].numSamples - previousNumSamples - offset; + } + buffer.AudioBytes = sample->buffers[bufferNumber].bufferSize; + buffer.pAudioData = (BYTE *)sample->buffers[bufferNumber].buffer; + buffer.pContext = bufferContext; + if ( ( loopingSample == NULL ) && ( bufferNumber == sample->buffers.Num() - 1 ) ) { + buffer.Flags = XAUDIO2_END_OF_STREAM; + } + pSourceVoice->SubmitSourceBuffer( &buffer ); + + return buffer.AudioBytes; +} + +/* +======================== +idSoundVoice_XAudio2::Update +======================== +*/ +bool idSoundVoice_XAudio2::Update() { + if ( pSourceVoice == NULL || leadinSample == NULL ) { + return false; + } + + XAUDIO2_VOICE_STATE state; + pSourceVoice->GetState( &state ); + + const int srcChannels = leadinSample->NumChannels(); + + float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ] = { 0 }; + CalculateSurround( srcChannels, pLevelMatrix, 1.0f ); + + if ( s_skipHardwareSets.GetBool() ) { + return true; + } + + pSourceVoice->SetOutputMatrix( soundSystemLocal.hardware.pMasterVoice, srcChannels, dstChannels, pLevelMatrix, OPERATION_SET ); + + assert( idMath::Fabs( gain ) <= XAUDIO2_MAX_VOLUME_LEVEL ); + pSourceVoice->SetVolume( gain, OPERATION_SET ); + + SetSampleRate( sampleRate, OPERATION_SET ); + + // we don't do this any longer because we pause and unpause explicitly when the soundworld is paused or unpaused + // UnPause(); + return true; +} + +/* +======================== +idSoundVoice_XAudio2::IsPlaying +======================== +*/ +bool idSoundVoice_XAudio2::IsPlaying() { + if ( pSourceVoice == NULL ) { + return false; + } + XAUDIO2_VOICE_STATE state; + pSourceVoice->GetState( &state ); + return ( state.BuffersQueued != 0 ); +} + +/* +======================== +idSoundVoice_XAudio2::FlushSourceBuffers +======================== +*/ +void idSoundVoice_XAudio2::FlushSourceBuffers() { + if ( pSourceVoice != NULL ) { + pSourceVoice->FlushSourceBuffers(); + } +} + +/* +======================== +idSoundVoice_XAudio2::Pause +======================== +*/ +void idSoundVoice_XAudio2::Pause() { + if ( !pSourceVoice || paused ) { + return; + } + if ( s_debugHardware.GetBool() ) { + idLib::Printf( "%dms: %p pausing %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "" ); + } + pSourceVoice->Stop( 0, OPERATION_SET ); + paused = true; +} + +/* +======================== +idSoundVoice_XAudio2::UnPause +======================== +*/ +void idSoundVoice_XAudio2::UnPause() { + if ( !pSourceVoice || !paused ) { + return; + } + if ( s_debugHardware.GetBool() ) { + idLib::Printf( "%dms: %p unpausing %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "" ); + } + pSourceVoice->Start( 0, OPERATION_SET ); + paused = false; +} + +/* +======================== +idSoundVoice_XAudio2::Stop +======================== +*/ +void idSoundVoice_XAudio2::Stop() { + if ( !pSourceVoice ) { + return; + } + if ( !paused ) { + if ( s_debugHardware.GetBool() ) { + idLib::Printf( "%dms: %p stopping %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "" ); + } + pSourceVoice->Stop( 0, OPERATION_SET ); + paused = true; + } +} + +/* +======================== +idSoundVoice_XAudio2::GetAmplitude +======================== +*/ +float idSoundVoice_XAudio2::GetAmplitude() { + if ( !hasVUMeter ) { + return 1.0f; + } + + float peakLevels[ MAX_CHANNELS_PER_VOICE ]; + float rmsLevels[ MAX_CHANNELS_PER_VOICE ]; + + XAUDIO2FX_VOLUMEMETER_LEVELS levels; + levels.ChannelCount = leadinSample->NumChannels(); + levels.pPeakLevels = peakLevels; + levels.pRMSLevels = rmsLevels; + + if ( levels.ChannelCount > MAX_CHANNELS_PER_VOICE ) { + levels.ChannelCount = MAX_CHANNELS_PER_VOICE; + } + + if ( pSourceVoice->GetEffectParameters( 0, &levels, sizeof( levels ) ) != S_OK ) { + return 0.0f; + } + + if ( levels.ChannelCount == 1 ) { + return rmsLevels[0]; + } + + float rms = 0.0f; + for ( uint32 i = 0; i < levels.ChannelCount; i++ ) { + rms += rmsLevels[i]; + } + + return rms / (float)levels.ChannelCount; +} + +/* +======================== +idSoundVoice_XAudio2::ResetSampleRate +======================== +*/ +void idSoundVoice_XAudio2::SetSampleRate( uint32 newSampleRate, uint32 operationSet ){ + if ( pSourceVoice == NULL || leadinSample == NULL ) { + return; + } + + sampleRate = newSampleRate; + + XAUDIO2_FILTER_PARAMETERS filter; + filter.Type = LowPassFilter; + filter.OneOverQ = 1.0f; // [0.0f, XAUDIO2_MAX_FILTER_ONEOVERQ] + float cutoffFrequency = 1000.0f / Max( 0.01f, occlusion ); + if ( cutoffFrequency * 6.0f >= (float)sampleRate ) { + filter.Frequency = XAUDIO2_MAX_FILTER_FREQUENCY; + } else { + filter.Frequency = 2.0f * idMath::Sin( idMath::PI * cutoffFrequency / (float)sampleRate ); + } + assert( filter.Frequency >= 0.0f && filter.Frequency <= XAUDIO2_MAX_FILTER_FREQUENCY ); + filter.Frequency = idMath::ClampFloat( 0.0f, XAUDIO2_MAX_FILTER_FREQUENCY, filter.Frequency ); + + pSourceVoice->SetFilterParameters( &filter, operationSet ); + + float freqRatio = pitch * (float)sampleRate / (float)sourceVoiceRate; + assert( freqRatio >= XAUDIO2_MIN_FREQ_RATIO && freqRatio <= XAUDIO2_MAX_FREQ_RATIO ); + freqRatio = idMath::ClampFloat( XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_MAX_FREQ_RATIO, freqRatio ); + + // if the value specified for maxFreqRatio is too high for the specified format, the call to CreateSourceVoice will fail + if ( numChannels == 1 ) { + assert( freqRatio * (float)SYSTEM_SAMPLE_RATE <= XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MONO ); + } else { + assert( freqRatio * (float)SYSTEM_SAMPLE_RATE <= XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MULTICHANNEL ); + } + pSourceVoice->SetFrequencyRatio( freqRatio, operationSet ); +} + +/* +======================== +idSoundVoice_XAudio2::OnBufferStart +======================== +*/ +void idSoundVoice_XAudio2::OnBufferStart( idSoundSample_XAudio2 * sample, int bufferNumber ) { + SetSampleRate( sample->SampleRate(), XAUDIO2_COMMIT_NOW ); + + idSoundSample_XAudio2 * nextSample = sample; + int nextBuffer = bufferNumber + 1; + if ( nextBuffer == sample->buffers.Num() ) { + if ( sample == leadinSample ) { + if ( loopingSample == NULL ) { + return; + } + nextSample = loopingSample; + } + nextBuffer = 0; + } + + SubmitBuffer( nextSample, nextBuffer, 0 ); +} diff --git a/neo/sound/XAudio2/XA2_SoundVoice.h b/neo/sound/XAudio2/XA2_SoundVoice.h new file mode 100644 index 00000000..b983e6ec --- /dev/null +++ b/neo/sound/XAudio2/XA2_SoundVoice.h @@ -0,0 +1,114 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __XA2_SOUNDVOICE_H__ +#define __XA2_SOUNDVOICE_H__ + +static const int MAX_QUEUED_BUFFERS = 3; + +/* +================================================ +idSoundVoice_XAudio2 +================================================ +*/ +class idSoundVoice_XAudio2 : public idSoundVoice_Base { +public: + idSoundVoice_XAudio2(); + ~idSoundVoice_XAudio2(); + + void Create( const idSoundSample * leadinSample, const idSoundSample * loopingSample ); + + // Start playing at a particular point in the buffer. Does an Update() too + void Start( int offsetMS, int ssFlags ); + + // Stop playing. + void Stop(); + + // Stop consuming buffers + void Pause(); + // Start consuming buffers again + void UnPause(); + + // Sends new position/volume/pitch information to the hardware + bool Update(); + + // returns the RMS levels of the most recently processed block of audio, SSF_FLICKER must have been passed to Start + float GetAmplitude(); + + // returns true if we can re-use this voice + bool CompatibleFormat( idSoundSample_XAudio2 * s ); + + uint32 GetSampleRate() const { return sampleRate; } + + // callback function + void OnBufferStart( idSoundSample_XAudio2 * sample, int bufferNumber ); + +private: + friend class idSoundHardware_XAudio2; + + // Returns true when all the buffers are finished processing + bool IsPlaying(); + + // Called after the voice has been stopped + void FlushSourceBuffers(); + + // Destroy the internal hardware resource + void DestroyInternal(); + + // Helper function used by the initial start as well as for looping a streamed buffer + int RestartAt( int offsetSamples ); + + // Helper function to submit a buffer + int SubmitBuffer( idSoundSample_XAudio2 * sample, int bufferNumber, int offset ); + + // Adjust the voice frequency based on the new sample rate for the buffer + void SetSampleRate( uint32 newSampleRate, uint32 operationSet ); + + IXAudio2SourceVoice * pSourceVoice; + idSoundSample_XAudio2 * leadinSample; + idSoundSample_XAudio2 * loopingSample; + + // These are the fields from the sample format that matter to us for voice reuse + uint16 formatTag; + uint16 numChannels; + + uint32 sourceVoiceRate; + uint32 sampleRate; + + bool hasVUMeter; + bool paused; +}; + +/* +================================================ +idSoundVoice +================================================ +*/ +class idSoundVoice : public idSoundVoice_XAudio2 { +}; + +#endif diff --git a/neo/sound/snd_emitter.cpp b/neo/sound/snd_emitter.cpp new file mode 100644 index 00000000..9a3a3b3e --- /dev/null +++ b/neo/sound/snd_emitter.cpp @@ -0,0 +1,977 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "snd_local.h" + +idCVar s_singleEmitter( "s_singleEmitter", "0", CVAR_INTEGER, "mute all sounds but this emitter" ); +idCVar s_showStartSound( "s_showStartSound", "0", CVAR_BOOL, "print a message every time a sound starts/stops" ); +idCVar s_useOcclusion( "s_useOcclusion", "1", CVAR_BOOL, "Attenuate sounds based on walls" ); +idCVar s_centerFractionVO( "s_centerFractionVO", "0.75", CVAR_FLOAT, "Portion of VO sounds routed to the center channel" ); + +extern idCVar s_playDefaultSound; +extern idCVar s_noSound; + +/* +================================================================================================ + + idSoundFade + +================================================================================================ +*/ + +/* +======================== +idSoundFade::Clear +======================== +*/ +void idSoundFade::Clear() { + fadeStartTime = 0; + fadeEndTime = 0; + fadeStartVolume = 0.0f; + fadeEndVolume = 0.0f; +} + +/* +======================== +idSoundFade::SetVolume +======================== +*/ +void idSoundFade::SetVolume( float to ) { + fadeStartVolume = to; + fadeEndVolume = to; + fadeStartTime = 0; + fadeEndTime = 0; +} + +/* +======================== +idSoundFade::Fade +======================== +*/ +void idSoundFade::Fade( float to, int length, int soundTime ) { + int startTime = soundTime; + // if it is already fading to this volume at this rate, don't change it + if ( fadeEndTime == startTime + length && fadeEndVolume == to ) { + return; + } + fadeStartVolume = GetVolume( soundTime ); + fadeEndVolume = to; + fadeStartTime = startTime; + fadeEndTime = startTime + length; +} + +/* +======================== +idSoundFade::GetVolume +======================== +*/ +float idSoundFade::GetVolume( const int soundTime ) const { + const float fadeDuration = ( fadeEndTime - fadeStartTime ); + const int currentTime = soundTime; + const float playTime = ( currentTime - fadeStartTime ); + if ( fadeDuration <= 0.0f ) { + return fadeEndVolume; + } else if ( currentTime >= fadeEndTime ) { + return fadeEndVolume; + } else if ( currentTime > fadeStartTime ) { + return fadeStartVolume + ( fadeEndVolume - fadeStartVolume ) * playTime / fadeDuration; + } else { + return fadeStartVolume; + } +} + +/* +======================== +idSoundChannel::idSoundChannel +======================== +*/ +idSoundChannel::idSoundChannel() { + emitter = NULL; + hardwareVoice = NULL; + + startTime = 0; + endTime = 0; + leadinSample = NULL; + loopingSample = NULL; + logicalChannel = SCHANNEL_ANY; + allowSlow = false; + soundShader = NULL; + + volumeFade.Clear(); + + volumeDB = DB_SILENCE; + currentAmplitude = 0.0f; +} + +/* +======================== +idSoundChannel::~idSoundChannel +======================== +*/ +idSoundChannel::~idSoundChannel() { +} + +/* +======================== +idSoundChannel::CanMute +Never actually mute VO because we can't restart them precisely enough for lip syncing to not fuck up +======================== +*/ +bool idSoundChannel::CanMute() const { + return true; +} + +/* +======================== +idSoundChannel::Mute + +A muted sound is considered still running, and can restart when a listener +gets close enough. +======================== +*/ +void idSoundChannel::Mute() { + if ( hardwareVoice != NULL ) { + soundSystemLocal.FreeVoice( hardwareVoice ); + hardwareVoice = NULL; + } +} + +/* +======================== +idSoundChannel::IsLooping +======================== +*/ +bool idSoundChannel::IsLooping() const { + return ( parms.soundShaderFlags & SSF_LOOPING ) != 0; +} + +/* +======================== +idSoundChannel::CheckForCompletion +======================== +*/ +bool idSoundChannel::CheckForCompletion( int currentTime ) { + if ( leadinSample == NULL ) { + return true; + } + // endTime of 0 indicates a sound should loop forever + if ( endTime > 0 && endTime < currentTime ) { + return true; + } + return false; +} + +/* +======================== +idSoundChannel::UpdateVolume +======================== +*/ +void idSoundChannel::UpdateVolume( int currentTime ) { + idSoundWorldLocal * soundWorld = emitter->soundWorld; + + volumeDB = DB_SILENCE; + currentAmplitude = 0.0f; + + if ( leadinSample == NULL ) { + return; + } + if ( startTime > currentTime ) { + return; + } + if ( endTime > 0 && endTime < currentTime ) { + return; + } + + // if you don't want to hear all the beeps from missing sounds + if ( leadinSample->IsDefault() && !s_playDefaultSound.GetBool() ) { + return; + } + + bool emitterIsListener = ( emitter->emitterId == soundWorld->listener.id ); + + // if it is a private sound, set the volume to zero unless we match the listener.id + if ( parms.soundShaderFlags & SSF_PRIVATE_SOUND ) { + if ( !emitterIsListener ) { + return; + } + } + if ( parms.soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) { + if ( emitterIsListener ) { + return; + } + } + + // volume fading + float newVolumeDB = parms.volume; + newVolumeDB += volumeFade.GetVolume( currentTime ); + newVolumeDB += soundWorld->volumeFade.GetVolume( currentTime ); + newVolumeDB += soundWorld->pauseFade.GetVolume( currentTime ); + if ( parms.soundClass >= 0 && parms.soundClass < SOUND_MAX_CLASSES ) { + newVolumeDB += soundWorld->soundClassFade[parms.soundClass].GetVolume( currentTime ); + } + + bool global = ( parms.soundShaderFlags & SSF_GLOBAL ) != 0; + + // attenuation + if ( !global && !emitterIsListener ) { + float distance = ( parms.soundShaderFlags & SSF_NO_OCCLUSION ) == 0 ? emitter->spatializedDistance : emitter->directDistance; + float mindist = parms.minDistance; + float maxdist = parms.maxDistance; + if ( distance >= maxdist ) { + newVolumeDB = DB_SILENCE; + } else if ( ( distance > mindist ) && ( maxdist > mindist ) ) { + float f = ( distance - mindist ) / ( maxdist - mindist ); + newVolumeDB += LinearToDB( Square( 1.0f - f ) ); + } + } + + if ( soundSystemLocal.musicMuted && ( parms.soundShaderFlags & SSF_MUSIC ) != 0 ) { + newVolumeDB = DB_SILENCE; + } + + // store the new volume on the channel + volumeDB = newVolumeDB; + + // keep track of the maximum volume + float currentVolumeDB = newVolumeDB; + if ( hardwareVoice != NULL ) { + float amplitude = hardwareVoice->GetAmplitude(); + if ( amplitude <= 0.0f ) { + currentVolumeDB = DB_SILENCE; + } else { + currentVolumeDB += LinearToDB( amplitude ); + } + currentAmplitude = amplitude; + } +} + +/* +======================== +idSoundChannel::UpdateHardware +======================== +*/ +void idSoundChannel::UpdateHardware( float volumeAdd, int currentTime ) { + idSoundWorldLocal * soundWorld = emitter->soundWorld; + + if ( soundWorld == NULL ) { + return; + } + if ( leadinSample == NULL ) { + return; + } + if ( startTime > currentTime ) { + return; + } + if ( endTime > 0 && endTime < currentTime ) { + return; + } + + // convert volumes from decibels to linear + float volume = Max( 0.0f, DBtoLinear( volumeDB + volumeAdd ) ); + + if ( ( parms.soundShaderFlags & SSF_UNCLAMPED ) == 0 ) { + volume = Min( 1.0f, volume ); + } + + bool global = ( parms.soundShaderFlags & SSF_GLOBAL ) != 0; + bool omni = ( parms.soundShaderFlags & SSF_OMNIDIRECTIONAL ) != 0; + bool emitterIsListener = ( emitter->emitterId == soundWorld->listener.id ); + + int startOffset = 0; + bool issueStart = false; + + if ( hardwareVoice == NULL ) { + if ( volume <= 0.00001f ) { + return; + } + + hardwareVoice = soundSystemLocal.AllocateVoice( leadinSample, loopingSample ); + + if ( hardwareVoice == NULL ) { + return; + } + + issueStart = true; + startOffset = currentTime - startTime; + } + + if ( omni || global || emitterIsListener ) { + hardwareVoice->SetPosition( vec3_zero ); + } else { + hardwareVoice->SetPosition( ( emitter->spatializedOrigin - soundWorld->listener.pos ) * soundWorld->listener.axis.Transpose() ); + } + if ( parms.soundShaderFlags & SSF_VO ) { + hardwareVoice->SetCenterChannel( s_centerFractionVO.GetFloat() ); + } else { + hardwareVoice->SetCenterChannel( 0.0f ); + } + + extern idCVar timescale; + + hardwareVoice->SetGain( volume ); + hardwareVoice->SetInnerRadius( parms.minDistance * METERS_TO_DOOM ); + hardwareVoice->SetPitch( soundWorld->slowmoSpeed * idMath::ClampFloat( 0.2f, 5.0f, timescale.GetFloat() ) ); + + if ( soundWorld->enviroSuitActive ) { + hardwareVoice->SetOcclusion( 0.5f ); + } else { + hardwareVoice->SetOcclusion( 0.0f ); + } + + if ( issueStart ) { + hardwareVoice->Start( startOffset, parms.soundShaderFlags | ( parms.shakes == 0.0f ? SSF_NO_FLICKER : 0 ) ); + } else { + hardwareVoice->Update(); + } +} + +/* +================================================================================================ + + idSoundEmitterLocal + +================================================================================================ +*/ + +/* +======================== +idSoundEmitterLocal::idSoundEmitterLocal +======================== +*/ +idSoundEmitterLocal::idSoundEmitterLocal() { + Init( 0, NULL ); +} + +/* +======================== +idSoundEmitterLocal::~idSoundEmitterLocal +======================== +*/ +idSoundEmitterLocal::~idSoundEmitterLocal() { + assert( channels.Num() == 0 ); +} + +/* +======================== +idSoundEmitterLocal::Clear +======================== +*/ +void idSoundEmitterLocal::Init( int i, idSoundWorldLocal * sw ) { + index = i; + soundWorld = sw; + + // Init should only be called on a freshly constructed sound emitter or in a Reset() + assert( channels.Num() == 0 ); + + canFree = false; + origin.Zero(); + emitterId = 0; + + directDistance = 0.0f; + lastValidPortalArea = -1; + spatializedDistance = 0.0f; + spatializedOrigin.Zero(); + + memset( &parms, 0, sizeof( parms ) ); +} + +/* +======================== +idSoundEmitterLocal::Reset +======================== +*/ +void idSoundEmitterLocal::Reset() { + for ( int i = 0; i < channels.Num(); i++ ) { + soundWorld->FreeSoundChannel( channels[i] ); + } + channels.Clear(); + Init( index, soundWorld ); +} + +/* +================== +idSoundEmitterLocal::OverrideParms +================== +*/ +void idSoundEmitterLocal::OverrideParms( const soundShaderParms_t * base, const soundShaderParms_t * over, soundShaderParms_t * out ) { + if ( !over ) { + *out = *base; + return; + } + if ( over->minDistance ) { + out->minDistance = over->minDistance; + } else { + out->minDistance = base->minDistance; + } + if ( over->maxDistance ) { + out->maxDistance = over->maxDistance; + } else { + out->maxDistance = base->maxDistance; + } + if ( over->shakes ) { + out->shakes = over->shakes; + } else { + out->shakes = base->shakes; + } + if ( over->volume ) { + out->volume = over->volume; + } else { + out->volume = base->volume; + } + if ( over->soundClass ) { + out->soundClass = over->soundClass; + } else { + out->soundClass = base->soundClass; + } + out->soundShaderFlags = base->soundShaderFlags | over->soundShaderFlags; +} + +/* +======================== +idSoundEmitterLocal::CheckForCompletion + +Checks to see if any of the channels have completed, removing them as they do + +This will also play any postSounds on the same channel as their owner. + +Returns true if the emitter should be freed. +======================== +*/ +bool idSoundEmitterLocal::CheckForCompletion( int currentTime ) { + for ( int i = channels.Num() - 1; i >= 0 ; i-- ) { + idSoundChannel * chan = channels[i]; + + if ( chan->CheckForCompletion( currentTime ) ) { + channels.RemoveIndex( i ); + soundWorld->FreeSoundChannel( chan ); + } + } + return ( canFree && channels.Num() == 0 ); +} + +/* +======================== +idSoundEmitterLocal::Update +======================== +*/ +void idSoundEmitterLocal::Update( int currentTime ) { + if ( channels.Num() == 0 ) { + return; + } + + directDistance = ( soundWorld->listener.pos - origin ).LengthFast() * DOOM_TO_METERS; + + spatializedDistance = directDistance; + spatializedOrigin = origin; + + // Initialize all channels to silence + for ( int i = 0; i < channels.Num(); i++ ) { + channels[i]->volumeDB = DB_SILENCE; + } + + if ( s_singleEmitter.GetInteger() > 0 && s_singleEmitter.GetInteger() != index ) { + return; + } + if ( soundWorld->listener.area == -1 ) { + // listener is outside the world + return; + } + if ( soundSystemLocal.muted || soundWorld != soundSystemLocal.currentSoundWorld ) { + return; + } + float maxDistance = 0.0f; + bool maxDistanceValid = false; + bool useOcclusion = false; + if ( emitterId != soundWorld->listener.id ) { + for ( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + if ( ( chan->parms.soundShaderFlags & SSF_GLOBAL ) != 0 ) { + continue; + } + useOcclusion = useOcclusion || ( ( chan->parms.soundShaderFlags & SSF_NO_OCCLUSION ) == 0 ); + maxDistanceValid = true; + if ( maxDistance < channels[i]->parms.maxDistance ) { + maxDistance = channels[i]->parms.maxDistance; + } + } + } + if ( maxDistanceValid && directDistance >= maxDistance ) { + // too far away to possibly hear it + return; + } + if ( useOcclusion && s_useOcclusion.GetBool() ) { + // work out virtual origin and distance, which may be from a portal instead of the actual origin + if ( soundWorld->renderWorld != NULL ) { + // we have a valid renderWorld + int soundInArea = soundWorld->renderWorld->PointInArea( origin ); + if ( soundInArea == -1 ) { + soundInArea = lastValidPortalArea; + } else { + lastValidPortalArea = soundInArea; + } + if ( soundInArea != -1 && soundInArea != soundWorld->listener.area ) { + spatializedDistance = maxDistance * METERS_TO_DOOM; + soundWorld->ResolveOrigin( 0, NULL, soundInArea, 0.0f, origin, this ); + spatializedDistance *= DOOM_TO_METERS; + } + } + } + + for ( int j = 0; j < channels.Num(); j++ ) { + channels[j]->UpdateVolume( currentTime ); + } + + return; +} + +/* +======================== +idSoundEmitterLocal::Index +======================== +*/ +int idSoundEmitterLocal::Index() const { + assert( soundWorld ); + assert( soundWorld->emitters[this->index] == this ); + + return index; +} + +/* +======================== +idSoundEmitterLocal::Free + +Doesn't free it until the next update. +======================== +*/ +void idSoundEmitterLocal::Free( bool immediate ) { + assert( soundWorld ); + assert( soundWorld->emitters[this->index] == this ); + + if ( canFree ) { + // Double free + return; + } + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_FREE ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( immediate ); + } + + if ( immediate ) { + Reset(); + } + + canFree = true; +} + +/* +======================== +idSoundEmitterLocal::UpdateEmitter +======================== +*/ +void idSoundEmitterLocal::UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ) { + assert( soundWorld != NULL ); + assert( soundWorld->emitters[this->index] == this ); + + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_UPDATE ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteVec3( origin ); + soundWorld->writeDemo->WriteInt( listenerId ); + soundWorld->writeDemo->WriteFloat( parms->minDistance ); + soundWorld->writeDemo->WriteFloat( parms->maxDistance ); + soundWorld->writeDemo->WriteFloat( parms->volume ); + soundWorld->writeDemo->WriteFloat( parms->shakes ); + soundWorld->writeDemo->WriteInt( parms->soundShaderFlags ); + soundWorld->writeDemo->WriteInt( parms->soundClass ); + } + + this->origin = origin; + this->emitterId = listenerId; + this->parms = *parms; +} + +/* +======================== +idSoundEmitterLocal::StartSound + +in most cases play sounds immediately, however + intercept sounds using SSF_FINITE_SPEED_OF_SOUND + and schedule them for playback later + +return: int - the length of the started sound in msec. +======================== +*/ +int idSoundEmitterLocal::StartSound( const idSoundShader * shader, const s_channelType channel, float diversity, int shaderFlags, bool allowSlow ) { + assert( soundWorld != NULL ); + assert( soundWorld->emitters[this->index] == this ); + + if ( shader == NULL ) { + return 0; + } + + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_START ); + soundWorld->writeDemo->WriteInt( index ); + + soundWorld->writeDemo->WriteHashString( shader->GetName() ); + + soundWorld->writeDemo->WriteInt( channel ); + soundWorld->writeDemo->WriteFloat( diversity ); + soundWorld->writeDemo->WriteInt( shaderFlags ); + } + + if ( s_noSound.GetBool() ) { + return 0; + } + + int currentTime = soundWorld->GetSoundTime(); + + bool showStartSound = s_showStartSound.GetBool(); + if ( showStartSound ) { + idLib::Printf( "%dms: StartSound(%d:%d): %s: ", currentTime, index, channel, shader->GetName() ); + } + + // build the channel parameters by taking the shader parms and optionally overriding + soundShaderParms_t chanParms; + chanParms = shader->parms; + OverrideParms( &chanParms, &parms, &chanParms ); + chanParms.soundShaderFlags |= shaderFlags; + + if ( shader->entries.Num() == 0 ) { + if ( showStartSound ) { + idLib::Printf( S_COLOR_RED "No Entries\n" ); + } + return 0; + } + + // PLAY_ONCE sounds will never be restarted while they are running + if ( chanParms.soundShaderFlags & SSF_PLAY_ONCE ) { + for ( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + if ( chan->soundShader == shader && !chan->CheckForCompletion( currentTime ) ) { + if ( showStartSound ) { + idLib::Printf( S_COLOR_YELLOW "Not started because of playOnce\n" ); + } + return 0; + } + } + } + + // never play the same sound twice with the same starting time, even + // if they are on different channels + for ( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + if ( chan->soundShader == shader && chan->startTime == currentTime && chan->endTime != 1 ) { + if ( showStartSound ) { + idLib::Printf( S_COLOR_RED "Already started this frame\n" ); + } + return 0; + } + } + + // kill any sound that is currently playing on this channel + if ( channel != SCHANNEL_ANY ) { + for ( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + if ( chan->soundShader && chan->logicalChannel == channel ) { + if ( showStartSound ) { + idLib::Printf( S_COLOR_YELLOW "OVERRIDE %s: ", chan->soundShader->GetName() ); + } + channels.RemoveIndex( i ); + soundWorld->FreeSoundChannel( chan ); + break; + } + } + } + + idSoundSample * leadinSample = NULL; + idSoundSample * loopingSample = NULL; + + if ( shader->leadin && ( chanParms.soundShaderFlags & SSF_LOOPING ) ) { + leadinSample = shader->entries[0]; + loopingSample = shader->entries.Num() > 1 ? shader->entries[1] : NULL; + } else { + if ( shader->entries.Num() == 1 ) { + leadinSample = shader->entries[0]; + } else { + int choice; + if ( chanParms.soundShaderFlags & SSF_NO_DUPS ) { + // Don't select the most recently played entry + int mostRecentTime = 0; + int mostRecent = 0; + for ( int i = 0; i < shader->entries.Num(); i++ ) { + int entryTime = shader->entries[i]->GetLastPlayedTime(); + if ( entryTime > mostRecentTime ) { + mostRecentTime = entryTime; + mostRecent = i; + } + } + choice = (int)( diversity * ( shader->entries.Num() - 1 ) ); + if ( choice >= mostRecent ) { + choice++; + } + } else { + // pick a sound from the list based on the passed diversity + choice = (int)( diversity * shader->entries.Num() ); + } + choice = idMath::ClampInt( 0, shader->entries.Num() - 1, choice ); + leadinSample = shader->entries[choice]; + leadinSample->SetLastPlayedTime( soundWorld->GetSoundTime() ); + } + if ( chanParms.soundShaderFlags & SSF_LOOPING ) { + loopingSample = leadinSample; + } + } + + // set all the channel parameters here, + // a hardware voice will be allocated next update if the volume is high enough to be audible + if ( channels.Num() == channels.Max() ) { + CheckForCompletion( currentTime ); // as a last chance try to release finished sounds here + if ( channels.Num() == channels.Max() ) { + if ( showStartSound ) { + idLib::Printf( S_COLOR_RED "No free emitter channels!\n" ); + } + return 0; + } + } + idSoundChannel * chan = soundWorld->AllocSoundChannel(); + if ( chan == NULL ) { + if ( showStartSound ) { + idLib::Printf( S_COLOR_RED "No free global channels!\n" ); + } + return 0; + } + channels.Append( chan ); + chan->emitter = this; + chan->parms = chanParms; + chan->soundShader = shader; + chan->logicalChannel = channel; + chan->leadinSample = leadinSample; + chan->loopingSample = loopingSample; + chan->allowSlow = allowSlow; + + // return length of sound in milliseconds + int length = chan->leadinSample->LengthInMsec(); + + // adjust the start time based on diversity for looping sounds, so they don't all start at the same point + int startOffset = 0; + + if ( chan->IsLooping() && !shader->leadin ) { + // looping sounds start at a random point... + startOffset = soundSystemLocal.random.RandomInt( length ); + } + + chan->startTime = currentTime - startOffset; + + if ( ( chanParms.soundShaderFlags & SSF_LOOPING ) != 0 ) { + // This channel will never end! + chan->endTime = 0; + } else { + // This channel will automatically end at this time + chan->endTime = chan->startTime + length + 100; + } + if ( showStartSound ) { + if ( loopingSample == NULL || leadinSample == loopingSample ) { + idLib::Printf( "Playing %s @ %d\n", leadinSample->GetName(), startOffset ); + } else { + idLib::Printf( "Playing %s then looping %s\n", leadinSample->GetName(), loopingSample->GetName() ); + } + } + + return length; +} + + +/* +======================== +idSoundEmitterLocal::OnReloadSound + +This is a shortened version of StartSound, called whenever a sound shader is reloaded. +If the emitter is currently playing the given sound shader, restart it so +a change in the sound sample used for a given sound shader will be picked up. +======================== +*/ +void idSoundEmitterLocal::OnReloadSound( const idDecl *decl ) { +} + +/* +======================== +idSoundEmitterLocal::StopSound + +Can pass SCHANNEL_ANY. +======================== +*/ +void idSoundEmitterLocal::StopSound( const s_channelType channel ) { + assert( soundWorld != NULL ); + assert( soundWorld->emitters[this->index] == this ); + + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_STOP ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( channel ); + } + + for( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + + if ( channel != SCHANNEL_ANY && chan->logicalChannel != channel ) { + continue; + } + if ( s_showStartSound.GetBool() ) { + idLib::Printf( "%dms: StopSound(%d:%d): %s\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName() ); + } + + // This forces CheckForCompletion to return true + chan->endTime = 1; + } +} + +/* +======================== +idSoundEmitterLocal::ModifySound +======================== +*/ +void idSoundEmitterLocal::ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) { + assert( soundWorld != NULL ); + assert( soundWorld->emitters[this->index] == this ); + + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_MODIFY ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( channel ); + soundWorld->writeDemo->WriteFloat( parms->minDistance ); + soundWorld->writeDemo->WriteFloat( parms->maxDistance ); + soundWorld->writeDemo->WriteFloat( parms->volume ); + soundWorld->writeDemo->WriteFloat( parms->shakes ); + soundWorld->writeDemo->WriteInt( parms->soundShaderFlags ); + soundWorld->writeDemo->WriteInt( parms->soundClass ); + } + + for ( int i = channels.Num() - 1; i >= 0; i-- ) { + idSoundChannel * chan = channels[i]; + if ( channel != SCHANNEL_ANY && chan->logicalChannel != channel ) { + continue; + } + if ( s_showStartSound.GetBool() ) { + idLib::Printf( "%dms: ModifySound(%d:%d): %s\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName() ); + } + OverrideParms( &chan->parms, parms, &chan->parms ); + } +} + +/* +======================== +idSoundEmitterLocal::FadeSound +======================== +*/ +void idSoundEmitterLocal::FadeSound( const s_channelType channel, float to, float over ) { + assert( soundWorld != NULL ); + assert( soundWorld->emitters[this->index] == this ); + + if ( soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_FADE ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( channel ); + soundWorld->writeDemo->WriteFloat( to ); + soundWorld->writeDemo->WriteFloat( over ); + } + + int overMSec = SEC2MS( over ); + + for ( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + + if ( channel != SCHANNEL_ANY && chan->logicalChannel != channel ) { + continue; + } + if ( s_showStartSound.GetBool() ) { + idLib::Printf( "%dms: FadeSound(%d:%d): %s to %.2fdb over %.2f seconds\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName(), to, over ); + } + + // fade it + chan->volumeFade.Fade( to - chan->parms.volume, overMSec, soundWorld->GetSoundTime() ); + } +} + +/* +======================== +idSoundEmitterLocal::CurrentlyPlaying +======================== +*/ +bool idSoundEmitterLocal::CurrentlyPlaying( const s_channelType channel ) const { + + if ( channel == SCHANNEL_ANY ) { + return ( channels.Num() > 0 ); + } + + for ( int i = 0; i < channels.Num(); ++i ) { + if ( channels[i] != NULL && channels[i]->logicalChannel == channel ) { + if ( channels[i]->endTime == 1 ) { + return false; + } else { + return true; + } + } + } + + return false; +} + +/* +======================== +idSoundEmitterLocal::CurrentAmplitude +======================== +*/ +float idSoundEmitterLocal::CurrentAmplitude() { + float amplitude = 0.0f; + int currentTime = soundWorld->GetSoundTime(); + for ( int i = 0; i < channels.Num(); i++ ) { + idSoundChannel * chan = channels[i]; + if ( chan == NULL || currentTime < chan->startTime || ( chan->endTime > 0 && currentTime >= chan->endTime ) ) { + continue; + } + int relativeTime = currentTime - chan->startTime; + int leadinLength = chan->leadinSample->LengthInMsec(); + if ( relativeTime < leadinLength ) { + amplitude = Max( amplitude, chan->leadinSample->GetAmplitude( relativeTime ) ); + } else if ( chan->loopingSample != NULL ) { + amplitude = Max( amplitude, chan->loopingSample->GetAmplitude( ( relativeTime - leadinLength ) % chan->loopingSample->LengthInMsec() ) ); + } + } + return amplitude; +} diff --git a/neo/sound/snd_local.h b/neo/sound/snd_local.h new file mode 100644 index 00000000..ee7c1b03 --- /dev/null +++ b/neo/sound/snd_local.h @@ -0,0 +1,483 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SND_LOCAL_H__ +#define __SND_LOCAL_H__ + +#include "WaveFile.h" + +// Maximum number of voices we can have allocated +#define MAX_HARDWARE_VOICES 48 + +// A single voice can play multiple channels (up to 5.1, but most commonly stereo) +// This is the maximum number of channels which can play simultaneously +// This is limited primarily by seeking on the optical drive, secondarily by memory consumption, and tertiarily by CPU time spent mixing +#define MAX_HARDWARE_CHANNELS 64 + +// We may need up to 3 buffers for each hardware voice if they are all long sounds +#define MAX_SOUND_BUFFERS ( MAX_HARDWARE_VOICES * 3 ) + +// Maximum number of channels in a sound sample +#define MAX_CHANNELS_PER_VOICE 8 + +/* +======================== +MsecToSamples +SamplesToMsec +======================== +*/ +ID_INLINE_EXTERN uint32 MsecToSamples( uint32 msec, uint32 sampleRate ) { return ( msec * ( sampleRate / 100 ) ) / 10; } +ID_INLINE_EXTERN uint32 SamplesToMsec( uint32 samples, uint32 sampleRate ) { return sampleRate < 100 ? 0 : ( samples * 10 ) / ( sampleRate / 100 ); } + +/* +======================== +DBtoLinear +LinearToDB +======================== +*/ +ID_INLINE_EXTERN float DBtoLinear( float db ) { return idMath::Pow( 2.0f, db * ( 1.0f / 6.0f ) ); } +ID_INLINE_EXTERN float LinearToDB( float linear ) { return ( linear > 0.0f ) ? ( idMath::Log( linear ) * ( 6.0f / 0.693147181f ) ) : -999.0f; } + +// demo sound commands +typedef enum { + SCMD_STATE, // followed by a load game state + SCMD_PLACE_LISTENER, + SCMD_ALLOC_EMITTER, + + SCMD_FREE, + SCMD_UPDATE, + SCMD_START, + SCMD_MODIFY, + SCMD_STOP, + SCMD_FADE +} soundDemoCommand_t; + +#include "SoundVoice.h" + + +#define OPERATION_SET 1 + +#include + +#include +#include +#include +#include +#include "XAudio2/XA2_SoundSample.h" +#include "XAudio2/XA2_SoundVoice.h" +#include "XAudio2/XA2_SoundHardware.h" + + + +//------------------------ +// Listener data +//------------------------ +struct listener_t { + idMat3 axis; // orientation of the listener + idVec3 pos; // position in meters + int id; // the entity number, used to detect when a sound is local + int area; // area number the listener is in +}; + +class idSoundFade { +public: + int fadeStartTime; + int fadeEndTime; + float fadeStartVolume; + float fadeEndVolume; + + +public: + idSoundFade() { Clear(); } + + void Clear(); + void SetVolume( float to ); + void Fade( float to, int length, int soundTime ); + + float GetVolume( int soundTime ) const; +}; + +/* +================================================ +idSoundChannel +================================================ +*/ +class idSoundChannel { +public: + bool CanMute() const; + + void Mute(); + bool CheckForCompletion( int currentTime ); + + void UpdateVolume( int currentTime ); + void UpdateHardware( float volumeAdd, int currentTime ); + + // returns true if this channel is marked as looping + bool IsLooping() const; + + class idSoundEmitterLocal * emitter; + + int startTime; + int endTime; + int logicalChannel; + bool allowSlow; + + soundShaderParms_t parms; // combines shader parms and per-channel overrides + const idSoundShader * soundShader; + idSoundSample * leadinSample; + idSoundSample * loopingSample; + idSoundFade volumeFade; + + float volumeDB; // last volume at which this channel will play (calculated in UpdateVolume) + float currentAmplitude; // current amplitude on the hardware voice + + // hardwareVoice will be freed and NULL'd when a sound is out of range, + // and reallocated when it comes back in range + idSoundVoice * hardwareVoice; + + // only allocated by the soundWorld block allocator + idSoundChannel(); + ~idSoundChannel(); +}; + +// Maximum number of SoundChannels for a single SoundEmitter. +// This is probably excessive... +const int MAX_CHANNELS_PER_EMITTER = 16; + +/* +=================================================================================== + +idSoundWorldLocal + +=================================================================================== +*/ + +class idSoundWorldLocal : public idSoundWorld { +public: + idSoundWorldLocal(); + virtual ~idSoundWorldLocal(); + + //------------------------ + // Functions from idSoundWorld, implemented in SoundWorld.cpp + //------------------------ + + // Called at map start + virtual void ClearAllSoundEmitters(); + + // stop all playing sounds + virtual void StopAllSounds(); + + // get a new emitter that can play sounds in this world + virtual idSoundEmitter *AllocSoundEmitter(); + + // for load games + virtual idSoundEmitter *EmitterForIndex( int index ); + + // query data from all emitters in the world + virtual float CurrentShakeAmplitude(); + + // where is the camera + virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId ); + + // fade all sounds in the world with a given shader soundClass + // to is in Db, over is in seconds + virtual void FadeSoundClasses( const int soundClass, const float to, const float over ); + + // dumps the current state and begins archiving commands + virtual void StartWritingDemo( idDemoFile *demo ); + virtual void StopWritingDemo(); + + // read a sound command from a demo file + virtual void ProcessDemoCommand( idDemoFile *readDemo ); + + // menu sounds + virtual int PlayShaderDirectly( const char *name, int channel = -1 ); + + virtual void Skip( int time ); + + virtual void Pause(); + virtual void UnPause(); + virtual bool IsPaused() { return isPaused; } + + virtual int GetSoundTime(); + + // avidump + virtual void AVIOpen( const char *path, const char *name ); + virtual void AVIClose(); + + // SaveGame Support + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual void SetSlowmoSpeed( float speed ); + virtual void SetEnviroSuit( bool active ); + + //======================================= + + //------------------------ + // Random stuff that's not exposed outside the sound system + //------------------------ + void Update(); + void OnReloadSound( const idDecl *decl ); + + idSoundChannel * AllocSoundChannel(); + void FreeSoundChannel( idSoundChannel * ); + +public: + // even though all these variables are public, nobody outside the sound system includes SoundWorld_local.h + // so this is equivalent to making it private and friending all the other classes in the sound system + + idSoundFade volumeFade; // master volume knob for the entire world + idSoundFade soundClassFade[SOUND_MAX_CLASSES]; + + idRenderWorld * renderWorld; // for debug visualization and light amplitude sampling + idDemoFile * writeDemo; // if not NULL, archive commands here + + float currentCushionDB; // channels at or below this level will be faded to 0 + float shakeAmp; // last calculated shake amplitude + + listener_t listener; + idList emitters; + + idSoundEmitter * localSound; // for PlayShaderDirectly() + + idBlockAlloc emitterAllocator; + idBlockAlloc channelAllocator; + + idSoundFade pauseFade; + int pausedTime; + int accumulatedPauseTime; + bool isPaused; + + float slowmoSpeed; + bool enviroSuitActive; + +public: + struct soundPortalTrace_t { + int portalArea; + const soundPortalTrace_t * prevStack; + }; + + void ResolveOrigin( const int stackDepth, const soundPortalTrace_t * prevStack, const int soundArea, const float dist, const idVec3 & soundOrigin, idSoundEmitterLocal * def ); +}; + + +/* +================================================ +idSoundEmitterLocal +================================================ +*/ +class idSoundEmitterLocal : public idSoundEmitter { +public: + virtual void Free( bool immediate ); + + virtual void Reset(); + + virtual void UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ); + + virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0, int shaderFlags = 0, bool allowSlow = true ); + + virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms ); + virtual void StopSound( const s_channelType channel ); + + virtual void FadeSound( const s_channelType channel, float to, float over ); + + virtual bool CurrentlyPlaying( const s_channelType channel = SCHANNEL_ANY ) const; + + virtual float CurrentAmplitude(); + + virtual int Index() const; + + //---------------------------------------------- + + void Init( int i, idSoundWorldLocal * sw ); + + // Returns true if the emitter should be freed. + bool CheckForCompletion( int currentTime ); + + void OverrideParms( const soundShaderParms_t * base, const soundShaderParms_t * over, soundShaderParms_t * out ); + + void Update( int currentTime ); + void OnReloadSound( const idDecl *decl ); + + //---------------------------------------------- + + idSoundWorldLocal * soundWorld; // the world that holds this emitter + + int index; // in world emitter list + bool canFree; // if true, this emitter can be canFree (once channels.Num() == 0) + + // a single soundEmitter can have many channels playing from the same point + idStaticList channels; + + //----- set by UpdateEmitter ----- + idVec3 origin; + soundShaderParms_t parms; + int emitterId; // sounds will be full volume when emitterId == listenerId + + //----- set by Update ----- + int lastValidPortalArea; + float directDistance; + float spatializedDistance; + idVec3 spatializedOrigin; + + // sound emitters are only allocated by the soundWorld block allocator + idSoundEmitterLocal(); + virtual ~idSoundEmitterLocal(); +}; + + +/* +=================================================================================== + +idSoundSystemLocal + +=================================================================================== +*/ +class idSoundSystemLocal : public idSoundSystem { +public: + // all non-hardware initialization + virtual void Init(); + + // shutdown routine + virtual void Shutdown(); + + virtual idSoundWorld * AllocSoundWorld( idRenderWorld *rw ); + virtual void FreeSoundWorld( idSoundWorld *sw ); + + // specifying NULL will cause silence to be played + virtual void SetPlayingSoundWorld( idSoundWorld *soundWorld ); + + // some tools, like the sound dialog, may be used in both the game and the editor + // This can return NULL, so check! + virtual idSoundWorld * GetPlayingSoundWorld(); + + // sends the current playing sound world information to the sound hardware + virtual void Render(); + + // Mutes the SSG_MUSIC group + virtual void MuteBackgroundMusic( bool mute ) { musicMuted = mute; } + + // sets the final output volume to 0 + // This should only be used when the app is deactivated + // Since otherwise there will be problems with different subsystems muting and unmuting at different times + virtual void SetMute( bool mute ) { muted = mute; } + virtual bool IsMuted() { return muted; } + + virtual void OnReloadSound( const idDecl * sound ); + + virtual void StopAllSounds(); + + virtual void InitStreamBuffers(); + virtual void FreeStreamBuffers(); + + virtual void * GetIXAudio2() const; + + // for the sound level meter window + virtual cinData_t ImageForTime( const int milliseconds, const bool waveform ); + + // Free all sounds loaded during the last map load + virtual void BeginLevelLoad(); + + // We might want to defer the loading of new sounds to this point + virtual void EndLevelLoad(); + + // prints memory info + virtual void PrintMemInfo( MemInfo_t *mi ); + + //------------------------- + + // Before a sound is reloaded, any active voices using it must + // be stopped. Returns true if any were playing, and should be + // restarted after the sound is reloaded. + void StopVoicesWithSample( const idSoundSample * const sample ); + + void Restart(); + void SetNeedsRestart() { needsRestart = true; } + + int SoundTime() const; + + // may return NULL if there are no more voices left + idSoundVoice * AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ); + void FreeVoice( idSoundVoice * ); + + idSoundSample * LoadSample( const char * name ); + + virtual void Preload( idPreloadManifest & preload ); + + struct bufferContext_t { + bufferContext_t() : + voice( NULL ), + sample( NULL ), + bufferNumber( 0 ) + { } + idSoundVoice_XAudio2 * voice; + idSoundSample_XAudio2 * sample; + int bufferNumber; + }; + + // Get a stream buffer from the free pool, returns NULL if none are available + bufferContext_t * ObtainStreamBufferContext(); + void ReleaseStreamBufferContext( bufferContext_t * p ); + + idSysMutex streamBufferMutex; + idStaticList< bufferContext_t *, MAX_SOUND_BUFFERS > freeStreamBufferContexts; + idStaticList< bufferContext_t *, MAX_SOUND_BUFFERS > activeStreamBufferContexts; + idStaticList< bufferContext_t, MAX_SOUND_BUFFERS > bufferContexts; + + idSoundWorldLocal * currentSoundWorld; + idStaticList soundWorlds; + + idList samples; + idHashIndex sampleHash; + + idSoundHardware hardware; + + idRandom2 random; + + int soundTime; + bool muted; + bool musicMuted; + bool needsRestart; + + bool insideLevelLoad; + + //------------------------- + + idSoundSystemLocal() : + soundTime( 0 ), + currentSoundWorld( NULL ), + muted( false ), + musicMuted( false ), + needsRestart( false ) + {} +}; + +extern idSoundSystemLocal soundSystemLocal; + +#endif /* !__SND_LOCAL_H__ */ diff --git a/neo/sound/snd_shader.cpp b/neo/sound/snd_shader.cpp new file mode 100644 index 00000000..51419ed6 --- /dev/null +++ b/neo/sound/snd_shader.cpp @@ -0,0 +1,421 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "snd_local.h" + +extern idCVar s_maxSamples; + +typedef enum { + SPEAKER_LEFT = 0, + SPEAKER_RIGHT, + SPEAKER_CENTER, + SPEAKER_LFE, + SPEAKER_BACKLEFT, + SPEAKER_BACKRIGHT +} speakerLabel; + +/* +=============== +idSoundShader::Init +=============== +*/ +void idSoundShader::Init() { + leadin = false; + leadinVolume = 0; + altSound = NULL; +} + +/* +=============== +idSoundShader::idSoundShader +=============== +*/ +idSoundShader::idSoundShader() { + Init(); +} + +/* +=============== +idSoundShader::~idSoundShader +=============== +*/ +idSoundShader::~idSoundShader() { +} + +/* +================= +idSoundShader::Size +================= +*/ +size_t idSoundShader::Size() const { + return sizeof( idSoundShader ); +} + +/* +=============== +idSoundShader::idSoundShader::FreeData +=============== +*/ +void idSoundShader::FreeData() { +} + +/* +=================== +idSoundShader::SetDefaultText +=================== +*/ +bool idSoundShader::SetDefaultText() { + idStr wavname; + + wavname = GetName(); + wavname.DefaultFileExtension( ".wav" ); // if the name has .ogg in it, that will stay + + // if there exists a wav file with the same name + if ( 1 ) { //fileSystem->ReadFile( wavname, NULL ) != -1 ) { + char generated[2048]; + idStr::snPrintf( generated, sizeof( generated ), + "sound %s // IMPLICITLY GENERATED\n" + "{\n" + "%s\n" + "}\n", GetName(), wavname.c_str() ); + SetText( generated ); + return true; + } else { + return false; + } +} + +/* +=================== +DefaultDefinition +=================== +*/ +const char *idSoundShader::DefaultDefinition() const { + return + "{\n" + "\t" "_default.wav\n" + "}"; +} + +/* +=============== +idSoundShader::Parse + + this is called by the declManager +=============== +*/ +bool idSoundShader::Parse( const char *text, const int textLength, bool allowBinaryVersion ) { + idLexer src; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + if ( !ParseShader( src ) ) { + MakeDefault(); + return false; + } + return true; +} + +/* +=============== +idSoundShader::ParseShader +=============== +*/ +bool idSoundShader::ParseShader( idLexer &src ) { + idToken token; + + parms.minDistance = 1; + parms.maxDistance = 10; + parms.volume = 1; + parms.shakes = 0; + parms.soundShaderFlags = 0; + parms.soundClass = 0; + + speakerMask = 0; + altSound = NULL; + + entries.Clear(); + + while ( 1 ) { + if ( !src.ExpectAnyToken( &token ) ) { + return false; + } + // end of definition + else if ( token == "}" ) { + break; + } + // minimum number of sounds + else if ( !token.Icmp( "minSamples" ) ) { + src.ParseInt(); + } + // description + else if ( !token.Icmp( "description" ) ) { + src.ReadTokenOnLine( &token ); + } + // mindistance + else if ( !token.Icmp( "mindistance" ) ) { + parms.minDistance = src.ParseFloat(); + } + // maxdistance + else if ( !token.Icmp( "maxdistance" ) ) { + parms.maxDistance = src.ParseFloat(); + } + // shakes screen + else if ( !token.Icmp( "shakes" ) ) { + src.ExpectAnyToken( &token ); + if ( token.type == TT_NUMBER ) { + parms.shakes = token.GetFloatValue(); + } else { + src.UnreadToken( &token ); + parms.shakes = 1.0f; + } + } + // reverb + else if ( !token.Icmp( "reverb" ) ) { + src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + src.FreeSource(); + return false; + } + src.ParseFloat(); + // no longer supported + } + // volume + else if ( !token.Icmp( "volume" ) ) { + parms.volume = src.ParseFloat(); + } + // leadinVolume is used to allow light breaking leadin sounds to be much louder than the broken loop + else if ( !token.Icmp( "leadinVolume" ) ) { + leadinVolume = src.ParseFloat(); + leadin = true; + } + // speaker mask + else if ( !token.Icmp( "mask_center" ) ) { + speakerMask |= 1<= SOUND_MAX_CLASSES ) { + src.Warning( "SoundClass out of range" ); + return false; + } + } + // altSound + else if ( !token.Icmp( "altSound" ) ) { + if ( !src.ExpectAnyToken( &token ) ) { + return false; + } + altSound = declManager->FindSound( token.c_str() ); + } + // ordered + else if ( !token.Icmp( "ordered" ) ) { + // no longer supported + } + // no_dups + else if ( !token.Icmp( "no_dups" ) ) { + parms.soundShaderFlags |= SSF_NO_DUPS; + } + // no_flicker + else if ( !token.Icmp( "no_flicker" ) ) { + parms.soundShaderFlags |= SSF_NO_FLICKER; + } + // plain + else if ( !token.Icmp( "plain" ) ) { + // no longer supported + } + // looping + else if ( !token.Icmp( "looping" ) ) { + parms.soundShaderFlags |= SSF_LOOPING; + } + // no occlusion + else if ( !token.Icmp( "no_occlusion" ) ) { + parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + // private + else if ( !token.Icmp( "private" ) ) { + parms.soundShaderFlags |= SSF_PRIVATE_SOUND; + } + // antiPrivate + else if ( !token.Icmp( "antiPrivate" ) ) { + parms.soundShaderFlags |= SSF_ANTI_PRIVATE_SOUND; + } + // once + else if ( !token.Icmp( "playonce" ) ) { + parms.soundShaderFlags |= SSF_PLAY_ONCE; + } + // global + else if ( !token.Icmp( "global" ) ) { + parms.soundShaderFlags |= SSF_GLOBAL; + } + // unclamped + else if ( !token.Icmp( "unclamped" ) ) { + parms.soundShaderFlags |= SSF_UNCLAMPED; + } + // omnidirectional + else if ( !token.Icmp( "omnidirectional" ) ) { + parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + else if ( !token.Icmp( "onDemand" ) ) { + // no longer loading sounds on demand + } + // the wave files + else if ( !token.Icmp( "leadin" ) ) { + leadin = true; + } else if ( token.Find( ".wav", false ) != -1 || token.Find( ".ogg", false ) != -1 ) { + if ( token.IcmpPrefixPath( "sound/vo/" ) == 0 || token.IcmpPrefixPath( "sound/guis/" ) == 0 ) { + parms.soundShaderFlags |= SSF_VO; + } + if ( token.IcmpPrefixPath( "sound/musical/" ) == 0 ) { + parms.soundShaderFlags |= SSF_MUSIC; + } + // add to the wav list + if ( s_maxSamples.GetInteger() == 0 || ( s_maxSamples.GetInteger() > 0 && entries.Num() < s_maxSamples.GetInteger() ) ) { + entries.Append( soundSystemLocal.LoadSample( token.c_str() ) ); + } + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + return false; + } + } + + return true; +} + +/* +=============== +idSoundShader::List +=============== +*/ +void idSoundShader::List() const { + idStrList shaders; + + common->Printf( "%4i: %s\n", Index(), GetName() ); + for( int k = 0; k < entries.Num(); k++ ) { + const idSoundSample *objectp = entries[k]; + if ( objectp ) { + common->Printf( " %5dms %4dKb %s\n", objectp->LengthInMsec(), (objectp->BufferSize()/1024), objectp->GetName() ); + } + } +} + +/* +=============== +idSoundShader::GetAltSound +=============== +*/ +const idSoundShader *idSoundShader::GetAltSound() const { + return altSound; +} + +/* +=============== +idSoundShader::GetMinDistance +=============== +*/ +float idSoundShader::GetMinDistance() const { + return parms.minDistance; +} + +/* +=============== +idSoundShader::GetMaxDistance +=============== +*/ +float idSoundShader::GetMaxDistance() const { + return parms.maxDistance; +} + +/* +=============== +idSoundShader::HasDefaultSound +=============== +*/ +bool idSoundShader::HasDefaultSound() const { + for ( int i = 0; i < entries.Num(); i++ ) { + if ( entries[i] && entries[i]->IsDefault() ) { + return true; + } + } + return false; +} + +/* +=============== +idSoundShader::GetParms +=============== +*/ +const soundShaderParms_t *idSoundShader::GetParms() const { + return &parms; +} + +/* +=============== +idSoundShader::GetNumSounds +=============== +*/ +int idSoundShader::GetNumSounds() const { + return entries.Num(); +} + +/* +=============== +idSoundShader::GetSound +=============== +*/ +const char *idSoundShader::GetSound( int index ) const { + if ( index >= 0 && index < entries.Num() ) { + return entries[index]->GetName(); + } + return ""; +} diff --git a/neo/sound/snd_system.cpp b/neo/sound/snd_system.cpp new file mode 100644 index 00000000..8156aa5e --- /dev/null +++ b/neo/sound/snd_system.cpp @@ -0,0 +1,596 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "snd_local.h" + +idCVar s_noSound( "s_noSound", "0", CVAR_BOOL, "returns NULL for all sounds loaded and does not update the sound rendering" ); + +#ifdef ID_RETAIL +idCVar s_useCompression( "s_useCompression", "1", CVAR_BOOL, "Use compressed sound files (mp3/xma)" ); +idCVar s_playDefaultSound( "s_playDefaultSound", "0", CVAR_BOOL, "play a beep for missing sounds" ); +idCVar s_maxSamples( "s_maxSamples", "5", CVAR_INTEGER, "max samples to load per shader" ); +#else +idCVar s_useCompression( "s_useCompression", "1", CVAR_BOOL, "Use compressed sound files (mp3/xma)" ); +idCVar s_playDefaultSound( "s_playDefaultSound", "1", CVAR_BOOL, "play a beep for missing sounds" ); +idCVar s_maxSamples( "s_maxSamples", "5", CVAR_INTEGER, "max samples to load per shader" ); +#endif + +idCVar preLoad_Samples( "preLoad_Samples", "1", CVAR_SYSTEM | CVAR_BOOL, "preload samples during beginlevelload" ); + +idSoundSystemLocal soundSystemLocal; +idSoundSystem * soundSystem = &soundSystemLocal; + +/* +================================================================================================ + +idSoundSystemLocal + +================================================================================================ +*/ + +/* +======================== +TestSound_f + +This is called from the main thread. +======================== +*/ +void TestSound_f( const idCmdArgs & args ) { + if ( args.Argc() != 2 ) { + idLib::Printf( "Usage: testSound \n" ); + return; + } + if ( soundSystemLocal.currentSoundWorld ) { + soundSystemLocal.currentSoundWorld->PlayShaderDirectly( args.Argv( 1 ) ); + } +} + +/* +======================== +RestartSound_f +======================== +*/ +void RestartSound_f( const idCmdArgs & args ) { + soundSystemLocal.Restart(); +} + +/* +======================== +ListSamples_f + +======================== +*/ +void ListSamples_f( const idCmdArgs & args ) { + idLib::Printf( "Sound samples\n-------------\n" ); + int totSize = 0; + for ( int i = 0; i < soundSystemLocal.samples.Num(); i++ ) { + idLib::Printf( "%05dkb\t%s\n", soundSystemLocal.samples[ i ]->BufferSize() / 1024, soundSystemLocal.samples[ i ]->GetName() ); + totSize += soundSystemLocal.samples[ i ]->BufferSize(); + } + idLib::Printf( "--------------------------\n" ); + idLib::Printf( "%05dkb total size\n", totSize / 1024 ); +} + +/* +======================== +idSoundSystemLocal::Restart +======================== +*/ +void idSoundSystemLocal::Restart() { + + // Mute all channels in all worlds + for ( int i = 0; i < soundWorlds.Num(); i++ ) { + idSoundWorldLocal * sw = soundWorlds[i]; + for ( int e = 0; e < sw->emitters.Num(); e++ ) { + idSoundEmitterLocal * emitter = sw->emitters[e]; + for ( int c = 0; c < emitter->channels.Num(); c++ ) { + emitter->channels[c]->Mute(); + } + } + } + // Shutdown sound hardware + hardware.Shutdown(); + // Reinitialize sound hardware + if ( !s_noSound.GetBool() ) { + hardware.Init(); + } + + InitStreamBuffers(); +} + +/* +======================== +idSoundSystemLocal::Init + +Initialize the SoundSystem. +======================== +*/ +void idSoundSystemLocal::Init() { + + idLib::Printf( "----- Initializing Sound System ------\n" ); + + soundTime = Sys_Milliseconds(); + random.SetSeed( soundTime ); + + if ( !s_noSound.GetBool() ) { + hardware.Init(); + InitStreamBuffers(); + } + + cmdSystem->AddCommand( "testSound", TestSound_f, 0, "tests a sound", idCmdSystem::ArgCompletion_SoundName ); + cmdSystem->AddCommand( "s_restart", RestartSound_f, 0, "restart sound system" ); + cmdSystem->AddCommand( "listSamples", ListSamples_f, 0, "lists all loaded sound samples" ); + + idLib::Printf( "sound system initialized.\n" ); + idLib::Printf( "--------------------------------------\n" ); +} + +/* +======================== +idSoundSystemLocal::InitStreamBuffers +======================== +*/ +void idSoundSystemLocal::InitStreamBuffers() { + streamBufferMutex.Lock(); + const bool empty = ( bufferContexts.Num() == 0 ); + if ( empty ) { + bufferContexts.SetNum( MAX_SOUND_BUFFERS ); + for ( int i = 0; i < MAX_SOUND_BUFFERS; i++ ) { + freeStreamBufferContexts.Append( &( bufferContexts[ i ] ) ); + } + } else { + for ( int i = 0; i < activeStreamBufferContexts.Num(); i++ ) { + freeStreamBufferContexts.Append( activeStreamBufferContexts[ i ] ); + } + activeStreamBufferContexts.Clear(); + } + assert( bufferContexts.Num() == MAX_SOUND_BUFFERS ); + assert( freeStreamBufferContexts.Num() == MAX_SOUND_BUFFERS ); + assert( activeStreamBufferContexts.Num() == 0 ); + streamBufferMutex.Unlock(); +} + +/* +======================== +idSoundSystemLocal::FreeStreamBuffers +======================== +*/ +void idSoundSystemLocal::FreeStreamBuffers() { + streamBufferMutex.Lock(); + bufferContexts.Clear(); + freeStreamBufferContexts.Clear(); + activeStreamBufferContexts.Clear(); + streamBufferMutex.Unlock(); +} + +/* +======================== +idSoundSystemLocal::Shutdown +======================== +*/ +void idSoundSystemLocal::Shutdown() { + hardware.Shutdown(); + FreeStreamBuffers(); + samples.DeleteContents( true ); + sampleHash.Free(); +} + +/* +======================== +idSoundSystemLocal::ObtainStreamBuffer + +Get a stream buffer from the free pool, returns NULL if none are available +======================== +*/ +idSoundSystemLocal::bufferContext_t * idSoundSystemLocal::ObtainStreamBufferContext() { + bufferContext_t * bufferContext = NULL; + streamBufferMutex.Lock(); + if ( freeStreamBufferContexts.Num() != 0 ) { + bufferContext = freeStreamBufferContexts[ freeStreamBufferContexts.Num() - 1 ]; + freeStreamBufferContexts.SetNum( freeStreamBufferContexts.Num() - 1 ); + activeStreamBufferContexts.Append( bufferContext ); + } + streamBufferMutex.Unlock(); + return bufferContext; +} + +/* +======================== +idSoundSystemLocal::ReleaseStreamBuffer + +Releases a stream buffer back to the free pool +======================== +*/ +void idSoundSystemLocal::ReleaseStreamBufferContext( bufferContext_t * bufferContext ) { + streamBufferMutex.Lock(); + if ( activeStreamBufferContexts.Remove( bufferContext ) ) { + freeStreamBufferContexts.Append( bufferContext ); + } + streamBufferMutex.Unlock(); +} + +/* +======================== +idSoundSystemLocal::AllocSoundWorld +======================== +*/ +idSoundWorld * idSoundSystemLocal::AllocSoundWorld( idRenderWorld *rw ) { + idSoundWorldLocal * local = new (TAG_AUDIO) idSoundWorldLocal; + local->renderWorld = rw; + soundWorlds.Append( local ); + return local; +} + +/* +======================== +idSoundSystemLocal::FreeSoundWorld +======================== +*/ +void idSoundSystemLocal::FreeSoundWorld( idSoundWorld *sw ) { + idSoundWorldLocal *local = static_cast( sw ); + soundWorlds.Remove( local ); + delete local; +} + +/* +======================== +idSoundSystemLocal::SetPlayingSoundWorld + +Specifying NULL will cause silence to be played. +======================== +*/ +void idSoundSystemLocal::SetPlayingSoundWorld( idSoundWorld *soundWorld ) { + if ( currentSoundWorld == soundWorld ) { + return; + } + idSoundWorldLocal * oldSoundWorld = currentSoundWorld; + + currentSoundWorld = static_cast( soundWorld ); + + if ( oldSoundWorld != NULL ) { + oldSoundWorld->Update(); + } +} + +/* +======================== +idSoundSystemLocal::GetPlayingSoundWorld +======================== +*/ +idSoundWorld * idSoundSystemLocal::GetPlayingSoundWorld() { + return currentSoundWorld; +} + +/* +======================== +idSoundSystemLocal::Render +======================== +*/ +void idSoundSystemLocal::Render() { + + if ( s_noSound.GetBool() ) { + return; + } + + if ( needsRestart ) { + needsRestart = false; + Restart(); + } + + SCOPED_PROFILE_EVENT( "SoundSystem::Render" ); + + if ( currentSoundWorld != NULL ) { + currentSoundWorld->Update(); + } + + hardware.Update(); + + // The sound system doesn't use game time or anything like that because the sounds are decoded in real time. + soundTime = Sys_Milliseconds(); +} + +/* +======================== +idSoundSystemLocal::OnReloadSound +======================== +*/ +void idSoundSystemLocal::OnReloadSound( const idDecl* sound ) { + for ( int i = 0; i < soundWorlds.Num(); i++ ) { + soundWorlds[i]->OnReloadSound( sound ); + } +} + +/* +======================== +idSoundSystemLocal::StopAllSounds +======================== +*/ +void idSoundSystemLocal::StopAllSounds() { + for ( int i = 0; i < soundWorlds.Num(); i++ ) { + idSoundWorld *sw = soundWorlds[i]; + if ( sw ) { + sw->StopAllSounds(); + } + } + hardware.Update(); +} + +/* +======================== +idSoundSystemLocal::GetIXAudio2 +======================== +*/ +void * idSoundSystemLocal::GetIXAudio2() const { + return (void *)hardware.GetIXAudio2(); +} + +/* +======================== +idSoundSystemLocal::SoundTime +======================== +*/ +int idSoundSystemLocal::SoundTime() const { + return soundTime; +} + +/* +======================== +idSoundSystemLocal::AllocateVoice +======================== +*/ +idSoundVoice * idSoundSystemLocal::AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ) { + return hardware.AllocateVoice( leadinSample, loopingSample ); +} + +/* +======================== +idSoundSystemLocal::FreeVoice +======================== +*/ +void idSoundSystemLocal::FreeVoice( idSoundVoice * voice ) { + hardware.FreeVoice( voice ); +} + +/* +======================== +idSoundSystemLocal::LoadSample +======================== +*/ +idSoundSample * idSoundSystemLocal::LoadSample( const char * name ) { + idStrStatic< MAX_OSPATH > canonical = name; + canonical.ToLower(); + canonical.BackSlashesToSlashes(); + canonical.StripFileExtension(); + int hashKey = idStr::Hash( canonical ); + for ( int i = sampleHash.First( hashKey ); i != -1; i = sampleHash.Next( i ) ) { + if ( idStr::Cmp( samples[i]->GetName(), canonical ) == 0 ) { + samples[i]->SetLevelLoadReferenced(); + return samples[i]; + } + } + idSoundSample * sample = new (TAG_AUDIO) idSoundSample; + sample->SetName( canonical ); + sampleHash.Add( hashKey, samples.Append( sample ) ); + if ( !insideLevelLoad ) { + // Sound sample referenced before any map is loaded + sample->SetNeverPurge(); + sample->LoadResource(); + } else { + sample->SetLevelLoadReferenced(); + } + + if ( cvarSystem->GetCVarBool( "fs_buildgame" ) ) { + fileSystem->AddSamplePreload( canonical ); + } + + return sample; +} + +/* +======================== +idSoundSystemLocal::StopVoicesWithSample + +A sample is about to be freed, make sure the hardware isn't mixing from it. +======================== +*/ +void idSoundSystemLocal::StopVoicesWithSample( const idSoundSample * const sample ) { + for ( int w = 0; w < soundWorlds.Num(); w++ ) { + idSoundWorldLocal * sw = soundWorlds[w]; + if ( sw == NULL ) { + continue; + } + for ( int e = 0; e < sw->emitters.Num(); e++ ) { + idSoundEmitterLocal * emitter = sw->emitters[e]; + if ( emitter == NULL ) { + continue; + } + for ( int i = 0; i < emitter->channels.Num(); i++ ) { + if ( emitter->channels[i]->leadinSample == sample || emitter->channels[i]->loopingSample == sample ) { + emitter->channels[i]->Mute(); + } + } + } + } +} + +/* +======================== +idSoundSystemLocal::FreeVoice +======================== +*/ +cinData_t idSoundSystemLocal::ImageForTime( const int milliseconds, const bool waveform ) { + cinData_t cd; + cd.imageY = NULL; + cd.imageCr = NULL; + cd.imageCb = NULL; + cd.imageWidth = 0; + cd.imageHeight = 0; + cd.status = FMV_IDLE; + return cd; +} + +/* +======================== +idSoundSystemLocal::BeginLevelLoad +======================== +*/ +void idSoundSystemLocal::BeginLevelLoad() { + insideLevelLoad = true; + for ( int i = 0; i < samples.Num(); i++ ) { + if ( samples[i]->GetNeverPurge() ) { + continue; + } + samples[i]->FreeData(); + samples[i]->ResetLevelLoadReferenced(); + } +} + + + +/* +======================== +idSoundSystemLocal::Preload +======================== +*/ +void idSoundSystemLocal::Preload( idPreloadManifest & manifest ) { + + idStrStatic< MAX_OSPATH > filename; + + int start = Sys_Milliseconds(); + int numLoaded = 0; + + idList< preloadSort_t > preloadSort; + preloadSort.Resize( manifest.NumResources() ); + for ( int i = 0; i < manifest.NumResources(); i++ ) { + const preloadEntry_s & p = manifest.GetPreloadByIndex( i ); + idResourceCacheEntry rc; + // FIXME: write these out sorted + if ( p.resType == PRELOAD_SAMPLE ) { + if ( p.resourceName.Find( "/vo/", false ) >= 0 ) { + continue; + } + filename = "generated/"; + filename += p.resourceName; + filename.SetFileExtension( "idwav" ); + if ( fileSystem->GetResourceCacheEntry( filename, rc ) ) { + preloadSort_t ps = {}; + ps.idx = i; + ps.ofs = rc.offset; + preloadSort.Append( ps ); + } + } + } + + preloadSort.SortWithTemplate( idSort_Preload() ); + + for ( int i = 0; i < preloadSort.Num(); i++ ) { + const preloadSort_t & ps = preloadSort[ i ]; + const preloadEntry_s & p = manifest.GetPreloadByIndex( ps.idx ); + filename = p.resourceName; + filename.Replace( "generated/", "" ); + numLoaded++; + idSoundSample *sample = LoadSample( filename ); + if ( sample != NULL && !sample->IsLoaded() ) { + sample->LoadResource(); + sample->SetLevelLoadReferenced(); + } + } + + int end = Sys_Milliseconds(); + common->Printf( "%05d sounds preloaded in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 ); + common->Printf( "----------------------------------------\n" ); +} + +/* +======================== +idSoundSystemLocal::EndLevelLoad +======================== +*/ +void idSoundSystemLocal::EndLevelLoad() { + + insideLevelLoad = false; + + common->Printf( "----- idSoundSystemLocal::EndLevelLoad -----\n" ); + int start = Sys_Milliseconds(); + int keepCount = 0; + int loadCount = 0; + + idList< preloadSort_t > preloadSort; + preloadSort.Resize( samples.Num() ); + + for ( int i = 0; i < samples.Num(); i++ ) { + common->UpdateLevelLoadPacifier(); + + + if ( samples[i]->GetNeverPurge() ) { + continue; + } + if ( samples[i]->IsLoaded() ) { + keepCount++; + continue; + } + if ( samples[i]->GetLevelLoadReferenced() ) { + idStrStatic< MAX_OSPATH > filename = "generated/"; + filename += samples[ i ]->GetName(); + filename.SetFileExtension( "idwav" ); + preloadSort_t ps = {}; + ps.idx = i; + idResourceCacheEntry rc; + if ( fileSystem->GetResourceCacheEntry( filename, rc ) ) { + ps.ofs = rc.offset; + } else { + ps.ofs = 0; + } + preloadSort.Append( ps ); + loadCount++; + } + } + preloadSort.SortWithTemplate( idSort_Preload() ); + for ( int i = 0; i < preloadSort.Num(); i++ ) { + common->UpdateLevelLoadPacifier(); + + + samples[ preloadSort[ i ].idx ]->LoadResource(); + } + int end = Sys_Milliseconds(); + + common->Printf( "%5i sounds loaded in %5.1f seconds\n", loadCount, (end-start) * 0.001 ); + common->Printf( "----------------------------------------\n" ); +} + + + +/* +======================== +idSoundSystemLocal::FreeVoice +======================== +*/ +void idSoundSystemLocal::PrintMemInfo( MemInfo_t *mi ) { +} diff --git a/neo/sound/snd_world.cpp b/neo/sound/snd_world.cpp new file mode 100644 index 00000000..96c90286 --- /dev/null +++ b/neo/sound/snd_world.cpp @@ -0,0 +1,1121 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "snd_local.h" + +idCVar s_lockListener( "s_lockListener", "0", CVAR_BOOL, "lock listener updates" ); +idCVar s_constantAmplitude( "s_constantAmplitude", "-1", CVAR_FLOAT, "" ); +idCVar s_maxEmitterChannels( "s_maxEmitterChannels", "48", CVAR_INTEGER, "Can be set lower than the absolute max of MAX_HARDWARE_VOICES" ); +idCVar s_cushionFadeChannels( "s_cushionFadeChannels", "2", CVAR_INTEGER, "Ramp currentCushionDB so this many emitter channels should be silent" ); +idCVar s_cushionFadeRate( "s_cushionFadeRate", "60", CVAR_FLOAT, "DB / second change to currentCushionDB" ); +idCVar s_cushionFadeLimit( "s_cushionFadeLimit", "-30", CVAR_FLOAT, "Never cushion fade beyond this level" ); +idCVar s_cushionFadeOver( "s_cushionFadeOver", "10", CVAR_FLOAT, "DB above s_cushionFadeLimit to start ramp to silence" ); +idCVar s_unpauseFadeInTime( "s_unpauseFadeInTime", "250", CVAR_INTEGER, "When unpausing a sound world, milliseconds to fade sounds in over" ); +idCVar s_doorDistanceAdd( "s_doorDistanceAdd", "150", CVAR_FLOAT, "reduce sound volume with this distance when going through a door" ); +idCVar s_drawSounds( "s_drawSounds", "0", CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar s_showVoices( "s_showVoices", "0", CVAR_BOOL, "show active voices" ); +idCVar s_volume_dB( "s_volume_dB", "0", CVAR_ARCHIVE | CVAR_FLOAT, "volume in dB" ); +extern idCVar s_noSound; + +/* +======================== +idSoundWorldLocal::idSoundWorldLocal +======================== +*/ +idSoundWorldLocal::idSoundWorldLocal() { + volumeFade.Clear(); + for ( int i = 0; i < SOUND_MAX_CLASSES; i++ ) { + soundClassFade[i].Clear(); + } + renderWorld = NULL; + writeDemo = NULL; + + listener.axis.Identity(); + listener.pos.Zero(); + listener.id = -1; + listener.area = 0; + + shakeAmp = 0.0f; + currentCushionDB = DB_SILENCE; + + localSound = AllocSoundEmitter(); + + pauseFade.Clear(); + pausedTime = 0; + accumulatedPauseTime = 0; + isPaused = false; + + slowmoSpeed = 1.0f; + enviroSuitActive = false; +} + +/* +======================== +idSoundWorldLocal::~idSoundWorldLocal +======================== +*/ +idSoundWorldLocal::~idSoundWorldLocal() { + + if ( soundSystemLocal.currentSoundWorld == this ) { + soundSystemLocal.currentSoundWorld = NULL; + } + + for ( int i = 0; i < emitters.Num(); i++ ) { + emitters[i]->Reset(); + emitterAllocator.Free( emitters[i] ); + } + + // Make sure we aren't leaking emitters or channels + assert( emitterAllocator.GetAllocCount() == 0 ); + assert( channelAllocator.GetAllocCount() == 0 ); + + emitterAllocator.Shutdown(); + channelAllocator.Shutdown(); + + renderWorld = NULL; + localSound = NULL; +} + +/* +======================== +idSoundWorldLocal::AllocSoundEmitter + +This is called from the main thread. +======================== +*/ +idSoundEmitter * idSoundWorldLocal::AllocSoundEmitter() { + idSoundEmitterLocal * emitter = emitterAllocator.Alloc(); + emitter->Init( emitters.Append( emitter ), this ); + return emitter; +} + +/* +======================== +idSoundWorldLocal::AllocSoundChannel +======================== +*/ +idSoundChannel * idSoundWorldLocal::AllocSoundChannel() { + return channelAllocator.Alloc(); +} + +/* +======================== +idSoundWorldLocal::FreeSoundChannel +======================== +*/ +void idSoundWorldLocal::FreeSoundChannel( idSoundChannel * channel ) { + channel->Mute(); + channelAllocator.Free( channel ); +} + +/* +======================== +idSoundWorldLocal::CurrentShakeAmplitude +======================== +*/ +float idSoundWorldLocal::CurrentShakeAmplitude() { + if ( s_constantAmplitude.GetFloat() >= 0.0f ) { + return s_constantAmplitude.GetFloat(); + } + return shakeAmp; +} + +/* +======================== +idSoundWorldLocal::PlaceListener +======================== +*/ +void idSoundWorldLocal::PlaceListener( const idVec3 & origin, const idMat3 & axis, const int id ) { + if ( writeDemo ) { + writeDemo->WriteInt( DS_SOUND ); + writeDemo->WriteInt( SCMD_PLACE_LISTENER ); + writeDemo->WriteVec3( origin ); + writeDemo->WriteMat3( axis ); + writeDemo->WriteInt( id ); + } + + if ( s_lockListener.GetBool() ) { + return; + } + + listener.axis = axis; + listener.pos = origin; + listener.id = id; + + if ( renderWorld ) { + listener.area = renderWorld->PointInArea( origin ); // where are we? + } else { + listener.area = 0; + } +} + +/* +======================== +idActiveChannel +======================== +*/ +class idActiveChannel { +public: + idActiveChannel() : + channel( NULL ), + sortKey( 0 ) {} + idActiveChannel( idSoundChannel * channel_, int sortKey_ ) : + channel( channel_ ), + sortKey( sortKey_ ) {} + + idSoundChannel * channel; + int sortKey; +}; + +/* +======================== +MapVolumeFromFadeDB + +Ramp down volumes that are close to fadeDB so that fadeDB is DB_SILENCE +======================== +*/ +float MapVolumeFromFadeDB( const float volumeDB, const float fadeDB ) { + if ( volumeDB <= fadeDB ) { + return DB_SILENCE; + } + + const float fadeOver = s_cushionFadeOver.GetFloat(); + const float fadeFrom = fadeDB + fadeOver; + + if ( volumeDB >= fadeFrom ) { + // unchanged + return volumeDB; + } + const float fadeFraction = ( volumeDB - fadeDB ) / fadeOver; + + const float mappedDB = DB_SILENCE + ( fadeFrom - DB_SILENCE ) * fadeFraction; + return mappedDB; +} + +/* +======================== +AdjustForCushionChannels + +In the very common case of having more sounds that would contribute to the +mix than there are available hardware voices, it can be an audible discontinuity +when a channel initially gets a voice or loses a voice. +To avoid this, make sure that the last few hardware voices are mixed with a volume +of zero, so they won't make a difference as they come and go. +It isn't obvious what the exact best volume ramping method should be, just that +it smoothly change frame to frame. +======================== +*/ +static float AdjustForCushionChannels( const idStaticList< idActiveChannel, MAX_HARDWARE_VOICES > &activeEmitterChannels, + const int uncushionedChannels, const float currentCushionDB, const float driftRate ) { + + float targetCushionDB; + if ( activeEmitterChannels.Num() <= uncushionedChannels ) { + // we should be able to hear all of them + targetCushionDB = DB_SILENCE; + } else { + // we should be able to hear all of them + targetCushionDB = activeEmitterChannels[uncushionedChannels].channel->volumeDB; + if( targetCushionDB < DB_SILENCE ) { + targetCushionDB = DB_SILENCE; + } else if ( targetCushionDB > s_cushionFadeLimit.GetFloat() ) { + targetCushionDB = s_cushionFadeLimit.GetFloat(); + } + } + + // linearly drift the currentTargetCushionDB towards targetCushionDB + float driftedDB = currentCushionDB; + if ( driftedDB < targetCushionDB ) { + driftedDB += driftRate; + if ( driftedDB > targetCushionDB ) { + driftedDB = targetCushionDB; + } + } else { + driftedDB -= driftRate; + if ( driftedDB < targetCushionDB ) { + driftedDB = targetCushionDB; + } + } + + // ramp the lower sound volumes down + for ( int i = 0; i < activeEmitterChannels.Num(); i++ ) { + idSoundChannel * chan = activeEmitterChannels[i].channel; + chan->volumeDB = MapVolumeFromFadeDB( chan->volumeDB, driftedDB ); + } + + return driftedDB; +} + +/* +======================== +idSoundWorldLocal::Update +======================== +*/ +void idSoundWorldLocal::Update() { + + if ( s_noSound.GetBool() ) { + return; + } + + // ------------------ + // Update emitters + // + // Only loop through the list once to avoid extra cache misses + // ------------------ + + // The naming convention is weird here because we reuse the name "channel" + // An idSoundChannel is a channel on an emitter, which may have an explicit channel assignment or SND_CHANNEL_ANY + // A hardware channel is a channel from the sound file itself (IE: left, right, LFE) + // We only allow MAX_HARDWARE_CHANNELS channels, which may wind up being a smaller number of idSoundChannels + idStaticList< idActiveChannel, MAX_HARDWARE_VOICES > activeEmitterChannels; + int maxEmitterChannels = s_maxEmitterChannels.GetInteger() + 1; // +1 to leave room for insert-before-sort + if ( maxEmitterChannels > MAX_HARDWARE_VOICES ) { + maxEmitterChannels = MAX_HARDWARE_VOICES; + } + + int activeHardwareChannels = 0; + int totalHardwareChannels = 0; + int totalEmitterChannels = 0; + + int currentTime = GetSoundTime(); + for ( int e = emitters.Num() - 1; e >= 0; e-- ) { + // check for freeing a one-shot emitter that is finished playing + if ( emitters[e]->CheckForCompletion( currentTime ) ) { + // do a fast list collapse by swapping the last element into + // the slot we are deleting + emitters[e]->Reset(); + emitterAllocator.Free( emitters[e] ); + int lastEmitter = emitters.Num() - 1; + if ( e != lastEmitter ) { + emitters[e] = emitters[lastEmitter]; + emitters[e]->index = e; + } + emitters.SetNum( lastEmitter ); + continue; + } + + emitters[e]->Update( currentTime ); + + totalEmitterChannels += emitters[e]->channels.Num(); + + // sort the active channels into the hardware list + for ( int i = 0; i < emitters[e]->channels.Num(); i++ ) { + idSoundChannel * channel = emitters[e]->channels[i]; + + // check if this channel contributes at all + const bool canMute = channel->CanMute(); + if ( canMute && channel->volumeDB <= DB_SILENCE ) { + channel->Mute(); + continue; + } + + // Calculate the sort key. + // VO can't be stopped and restarted accurately, so always keep VO channels by adding a large value to the sort key. + const int sortKey = idMath::Ftoi( channel->volumeDB * 100.0f + ( canMute ? 0.0f : 100000.0f ) ); + + // Keep track of the total number of hardware channels. + // This is done after calculating the sort key to avoid a load-hit-store that + // would occur when using the sort key in the loop below after the Ftoi above. + const int sampleChannels = channel->leadinSample->NumChannels(); + totalHardwareChannels += sampleChannels; + + // Find the location to insert this channel based on the sort key. + int insertIndex = 0; + for ( insertIndex = 0; insertIndex < activeEmitterChannels.Num(); insertIndex++ ) { + if ( sortKey > activeEmitterChannels[insertIndex].sortKey ) { + break; + } + } + + // Only insert at the end if there is room. + if ( insertIndex == activeEmitterChannels.Num() ) { + // Always leave one spot free in the 'activeEmitterChannels' so there is room to insert sort a potentially louder sound later. + if ( activeEmitterChannels.Num() + 1 >= activeEmitterChannels.Max() || activeHardwareChannels + sampleChannels > MAX_HARDWARE_CHANNELS ) { + // We don't have enough voices to play this, so mute it if it was playing. + channel->Mute(); + continue; + } + } + + // We want to insert the sound at this point. + activeEmitterChannels.Insert( idActiveChannel( channel, sortKey ), insertIndex ); + activeHardwareChannels += sampleChannels; + + // If we are over our voice limit or at our channel limit, mute sounds until it fits. + // If activeEmitterChannels is full, always remove the last one so there is room to insert sort a potentially louder sound later. + while ( activeEmitterChannels.Num() == maxEmitterChannels || activeHardwareChannels > MAX_HARDWARE_CHANNELS ) { + const int indexToRemove = activeEmitterChannels.Num() - 1; + idSoundChannel * const channelToMute = activeEmitterChannels[ indexToRemove ].channel; + channelToMute->Mute(); + activeHardwareChannels -= channelToMute->leadinSample->NumChannels(); + activeEmitterChannels.RemoveIndex( indexToRemove ); + } + } + } + + const float secondsPerFrame = 1.0f / com_engineHz_latched; + + // ------------------ + // In the very common case of having more sounds that would contribute to the + // mix than there are available hardware voices, it can be an audible discontinuity + // when a channel initially gets a voice or loses a voice. + // To avoid this, make sure that the last few hardware voices are mixed with a volume + // of zero, so they won't make a difference as they come and go. + // It isn't obvious what the exact best volume ramping method should be, just that + // it smoothly change frame to frame. + // ------------------ + const int uncushionedChannels = maxEmitterChannels - s_cushionFadeChannels.GetInteger(); + currentCushionDB = AdjustForCushionChannels( activeEmitterChannels, uncushionedChannels, + currentCushionDB, s_cushionFadeRate.GetFloat() * secondsPerFrame ); + + // ------------------ + // Update Hardware + // ------------------ + shakeAmp = 0.0f; + + idStr showVoiceTable; + bool showVoices = s_showVoices.GetBool(); + if ( showVoices ) { + showVoiceTable.Format( "currentCushionDB: %5.1f freeVoices: %i zombieVoices: %i buffers:%i/%i\n", currentCushionDB, + soundSystemLocal.hardware.GetNumFreeVoices(), soundSystemLocal.hardware.GetNumZombieVoices(), + soundSystemLocal.activeStreamBufferContexts.Num(), soundSystemLocal.freeStreamBufferContexts.Num() ); + } + for ( int i = 0; i < activeEmitterChannels.Num(); i++ ) { + idSoundChannel * chan = activeEmitterChannels[i].channel; + chan->UpdateHardware( 0.0f, currentTime ); + + if ( showVoices ) { + idStr voiceLine; + voiceLine.Format( "%5.1f db [%3i:%2i] %s", chan->volumeDB, chan->emitter->index, chan->logicalChannel, chan->CanMute() ? "" : " \n" ); + idSoundSample * leadinSample = chan->leadinSample; + idSoundSample * loopingSample = chan->loopingSample; + if ( loopingSample == NULL ) { + voiceLine.Append( va( "%ikhz*%i %s\n", leadinSample->SampleRate()/1000, leadinSample->NumChannels(), leadinSample->GetName() ) ); + } else if ( loopingSample == leadinSample ) { + voiceLine.Append( va( "%ikhz*%i %s\n", leadinSample->SampleRate()/1000, leadinSample->NumChannels(), leadinSample->GetName() ) ); + } else { + voiceLine.Append( va( "%ikhz*%i %s | %ikhz*%i %s\n", leadinSample->SampleRate()/1000, leadinSample->NumChannels(), leadinSample->GetName(), loopingSample->SampleRate()/1000, loopingSample->NumChannels(), loopingSample->GetName() ) ); + } + showVoiceTable += voiceLine; + } + + // Calculate shakes + if ( chan->hardwareVoice == NULL ) { + continue; + } + + shakeAmp += chan->parms.shakes * chan->hardwareVoice->GetGain() * chan->currentAmplitude; + } + if ( showVoices ) { + static idOverlayHandle handle; + console->PrintOverlay( handle, JUSTIFY_LEFT, showVoiceTable.c_str() ); + } + + if ( s_drawSounds.GetBool() && renderWorld != NULL ) { + for ( int e = 0; e < emitters.Num(); e++ ) { + idSoundEmitterLocal * emitter = emitters[e]; + bool audible = false; + float maxGain = 0.0f; + for ( int c = 0; c < emitter->channels.Num(); c++ ) { + if ( emitter->channels[c]->hardwareVoice != NULL ) { + audible = true; + maxGain = Max( maxGain, emitter->channels[c]->hardwareVoice->GetGain() ); + } + } + if ( !audible ) { + continue; + } + + static const int lifetime = 20; + + idBounds ref; + ref.Clear(); + ref.AddPoint( idVec3( -10.0f ) ); + ref.AddPoint( idVec3( 10.0f ) ); + + // draw a box + renderWorld->DebugBounds( idVec4( maxGain, maxGain, 1.0f, 1.0f ), ref, emitter->origin, lifetime ); + if ( emitter->origin != emitter->spatializedOrigin ) { + renderWorld->DebugLine( idVec4( 1.0f, 0.0f, 0.0f, 1.0f ), emitter->origin, emitter->spatializedOrigin, lifetime ); + } + + // draw the index + idVec3 textPos = emitter->origin; + textPos.z -= 8; + renderWorld->DrawText( va("%i", e), textPos, 0.1f, idVec4(1,0,0,1), listener.axis, 1, lifetime ); + textPos.z += 8; + + // run through all the channels + for ( int k = 0; k < emitter->channels.Num(); k++ ) { + idSoundChannel * chan = emitter->channels[k]; + float min = chan->parms.minDistance; + float max = chan->parms.maxDistance; + const char * defaulted = chan->leadinSample->IsDefault() ? " *DEFAULTED*" : ""; + idStr text; + text.Format( "%s (%i %i/%i)%s", chan->soundShader->GetName(), idMath::Ftoi( emitter->spatializedDistance ), idMath::Ftoi( min ), idMath::Ftoi( max ), defaulted ); + renderWorld->DrawText( text, textPos, 0.1f, idVec4(1,0,0,1), listener.axis, 1, lifetime ); + textPos.z += 8; + } + } + } +} + +/* +======================== +idSoundWorldLocal::OnReloadSound +======================== +*/ +void idSoundWorldLocal::OnReloadSound( const idDecl *shader ) { + for ( int i = 0; i < emitters.Num(); i++ ) { + emitters[i]->OnReloadSound( shader ); + } +} + +/* +======================== +idSoundWorldLocal::EmitterForIndex +======================== +*/ +idSoundEmitter *idSoundWorldLocal::EmitterForIndex( int index ) { + // This is only used by save/load code which assumes index = 0 is invalid + // Which is fine since we use index 0 for the local sound emitter anyway + if ( index <= 0 ) { + return NULL; + } + if ( index >= emitters.Num() ) { + idLib::Error( "idSoundWorldLocal::EmitterForIndex: %i >= %i", index, emitters.Num() ); + } + return emitters[index]; +} + +/* +======================== +idSoundWorldLocal::ClearAllSoundEmitters +======================== +*/ +void idSoundWorldLocal::ClearAllSoundEmitters() { + for ( int i = 0; i < emitters.Num(); i++ ) { + emitters[i]->Reset(); + emitterAllocator.Free( emitters[i] ); + } + emitters.Clear(); + localSound = AllocSoundEmitter(); +} + +/* +======================== +idSoundWorldLocal::StopAllSounds + +This is called from the main thread. +======================== +*/ +void idSoundWorldLocal::StopAllSounds() { + for ( int i = 0; i < emitters.Num(); i++ ) { + emitters[i]->Reset(); + } +} + +/* +======================== +idSoundWorldLocal::PlayShaderDirectly +======================== +*/ +int idSoundWorldLocal::PlayShaderDirectly( const char * name, int channel ) { + if ( name == NULL || name[0] == 0 ) { + localSound->StopSound( channel ); + return 0; + } + const idSoundShader * shader = declManager->FindSound( name ); + if ( shader == NULL ) { + localSound->StopSound( channel ); + return 0; + } else { + return localSound->StartSound( shader, channel, soundSystemLocal.random.RandomFloat(), SSF_GLOBAL, true ); + } +} + +/* +======================== +idSoundWorldLocal::Skip +======================== +*/ +void idSoundWorldLocal::Skip( int time ) { + accumulatedPauseTime -= time; + pauseFade.SetVolume( DB_SILENCE ); + pauseFade.Fade( 0.0f, s_unpauseFadeInTime.GetInteger(), GetSoundTime() ); +} + +/* +======================== +idSoundWorldLocal::Pause +======================== +*/ +void idSoundWorldLocal::Pause() { + if ( !isPaused ) { + pausedTime = soundSystemLocal.SoundTime(); + isPaused = true; + // just pause all unmutable voices (normally just voice overs) + for ( int e = emitters.Num() - 1; e > 0; e-- ) { + for ( int i = 0; i < emitters[e]->channels.Num(); i++ ) { + idSoundChannel * channel = emitters[e]->channels[i]; + if ( !channel->CanMute() && channel->hardwareVoice != NULL ) { + channel->hardwareVoice->Pause(); + } + } + } + } +} + +/* +======================== +idSoundWorldLocal::UnPause +======================== +*/ +void idSoundWorldLocal::UnPause() { + if ( isPaused ) { + isPaused = false; + accumulatedPauseTime += soundSystemLocal.SoundTime() - pausedTime; + pauseFade.SetVolume( DB_SILENCE ); + pauseFade.Fade( 0.0f, s_unpauseFadeInTime.GetInteger(), GetSoundTime() ); + + // just unpause all unmutable voices (normally just voice overs) + for ( int e = emitters.Num() - 1; e > 0; e-- ) { + for ( int i = 0; i < emitters[e]->channels.Num(); i++ ) { + idSoundChannel * channel = emitters[e]->channels[i]; + if ( !channel->CanMute() && channel->hardwareVoice != NULL ) { + channel->hardwareVoice->UnPause(); + } + } + } + } +} + +/* +======================== +idSoundWorldLocal::GetSoundTime +======================== +*/ +int idSoundWorldLocal::GetSoundTime() { + if ( isPaused ) { + return pausedTime - accumulatedPauseTime; + } else { + return soundSystemLocal.SoundTime() - accumulatedPauseTime; + } +} + +/* +=================== +idSoundWorldLocal::ResolveOrigin + +Find out of the sound is completely occluded by a closed door portal, or +the virtual sound origin position at the portal closest to the listener. + this is called by the main thread + +dist is the distance from the orignial sound origin to the current portal that enters soundArea +def->distance is the distance we are trying to reduce. + +If there is no path through open portals from the sound to the listener, def->spatializedDistance will remain +set at maxDistance +=================== +*/ +static const int MAX_PORTAL_TRACE_DEPTH = 10; + +void idSoundWorldLocal::ResolveOrigin( const int stackDepth, const soundPortalTrace_t *prevStack, const int soundArea, const float dist, const idVec3& soundOrigin, idSoundEmitterLocal *def ) { + + if ( dist >= def->spatializedDistance ) { + // we can't possibly hear the sound through this chain of portals + return; + } + + if ( soundArea == listener.area ) { + float fullDist = dist + (soundOrigin - listener.pos).LengthFast(); + if ( fullDist < def->spatializedDistance ) { + def->spatializedDistance = fullDist; + def->spatializedOrigin = soundOrigin; + } + return; + } + + if ( stackDepth == MAX_PORTAL_TRACE_DEPTH ) { + // don't spend too much time doing these calculations in big maps + return; + } + + soundPortalTrace_t newStack; + newStack.portalArea = soundArea; + newStack.prevStack = prevStack; + + int numPortals = renderWorld->NumPortalsInArea( soundArea ); + for( int p = 0; p < numPortals; p++ ) { + exitPortal_t re = renderWorld->GetPortal( soundArea, p ); + + float occlusionDistance = 0; + + // air blocking windows will block sound like closed doors + if ( (re.blockingBits & ( PS_BLOCK_VIEW | PS_BLOCK_AIR ) ) ) { + // we could just completely cut sound off, but reducing the volume works better + // continue; + occlusionDistance = s_doorDistanceAdd.GetFloat(); + } + + // what area are we about to go look at + int otherArea = re.areas[0]; + if ( re.areas[0] == soundArea ) { + otherArea = re.areas[1]; + } + + // if this area is already in our portal chain, don't bother looking into it + const soundPortalTrace_t *prev; + for ( prev = prevStack ; prev ; prev = prev->prevStack ) { + if ( prev->portalArea == otherArea ) { + break; + } + } + if ( prev ) { + continue; + } + + // pick a point on the portal to serve as our virtual sound origin + idVec3 source; + + idPlane pl; + re.w->GetPlane( pl ); + + float scale; + idVec3 dir = listener.pos - soundOrigin; + if ( !pl.RayIntersection( soundOrigin, dir, scale ) ) { + source = re.w->GetCenter(); + } else { + source = soundOrigin + scale * dir; + + // if this point isn't inside the portal edges, slide it in + for ( int i = 0 ; i < re.w->GetNumPoints() ; i++ ) { + int j = ( i + 1 ) % re.w->GetNumPoints(); + idVec3 edgeDir = (*(re.w))[j].ToVec3() - (*(re.w))[i].ToVec3(); + idVec3 edgeNormal; + + edgeNormal.Cross( pl.Normal(), edgeDir ); + + idVec3 fromVert = source - (*(re.w))[j].ToVec3(); + + float d = edgeNormal * fromVert; + if ( d > 0 ) { + // move it in + float div = edgeNormal.Normalize(); + d /= div; + + source -= d * edgeNormal; + } + } + } + + idVec3 tlen = source - soundOrigin; + float tlenLength = tlen.LengthFast(); + + ResolveOrigin( stackDepth+1, &newStack, otherArea, dist+tlenLength+occlusionDistance, source, def ); + } +} + +/* +======================== +idSoundWorldLocal::StartWritingDemo +======================== +*/ +void idSoundWorldLocal::StartWritingDemo( idDemoFile * demo ) { + writeDemo = demo; + + writeDemo->WriteInt( DS_SOUND ); + writeDemo->WriteInt( SCMD_STATE ); + + // use the normal save game code to archive all the emitters + WriteToSaveGame( writeDemo ); +} + +/* +======================== +idSoundWorldLocal::StopWritingDemo +======================== +*/ +void idSoundWorldLocal::StopWritingDemo() { + writeDemo = NULL; +} + +/* +======================== +idSoundWorldLocal::ProcessDemoCommand +======================== +*/ +void idSoundWorldLocal::ProcessDemoCommand( idDemoFile * readDemo ) { + + if ( !readDemo ) { + return; + } + + int index; + soundDemoCommand_t dc; + + if ( !readDemo->ReadInt( (int&)dc ) ) { + return; + } + + switch( dc ) { + case SCMD_STATE: + ReadFromSaveGame( readDemo ); + UnPause(); + break; + case SCMD_PLACE_LISTENER: + { + idVec3 origin; + idMat3 axis; + int listenerId; + + readDemo->ReadVec3( origin ); + readDemo->ReadMat3( axis ); + readDemo->ReadInt( listenerId ); + + PlaceListener( origin, axis, listenerId ); + }; + break; + case SCMD_ALLOC_EMITTER: + { + readDemo->ReadInt( index ); + if ( index < 1 || index > emitters.Num() ) { + common->Error( "idSoundWorldLocal::ProcessDemoCommand: bad emitter number" ); + } + if ( index == emitters.Num() ) { + // append a brand new one + AllocSoundEmitter(); + } + } + break; + case SCMD_FREE: + { + int immediate; + + readDemo->ReadInt( index ); + readDemo->ReadInt( immediate ); + EmitterForIndex( index )->Free( immediate != 0 ); + } + break; + case SCMD_UPDATE: + { + idVec3 origin; + int listenerId; + soundShaderParms_t parms; + + readDemo->ReadInt( index ); + readDemo->ReadVec3( origin ); + readDemo->ReadInt( listenerId ); + readDemo->ReadFloat( parms.minDistance ); + readDemo->ReadFloat( parms.maxDistance ); + readDemo->ReadFloat( parms.volume ); + readDemo->ReadFloat( parms.shakes ); + readDemo->ReadInt( parms.soundShaderFlags ); + readDemo->ReadInt( parms.soundClass ); + EmitterForIndex( index )->UpdateEmitter( origin, listenerId, &parms ); + } + break; + case SCMD_START: + { + const idSoundShader *shader; + int channel; + float diversity; + int shaderFlags; + + readDemo->ReadInt( index ); + shader = declManager->FindSound( readDemo->ReadHashString() ); + readDemo->ReadInt( channel ); + readDemo->ReadFloat( diversity ); + readDemo->ReadInt( shaderFlags ); + EmitterForIndex( index )->StartSound( shader, (s_channelType)channel, diversity, shaderFlags ); + } + break; + case SCMD_MODIFY: + { + int channel; + soundShaderParms_t parms; + + readDemo->ReadInt( index ); + readDemo->ReadInt( channel ); + readDemo->ReadFloat( parms.minDistance ); + readDemo->ReadFloat( parms.maxDistance ); + readDemo->ReadFloat( parms.volume ); + readDemo->ReadFloat( parms.shakes ); + readDemo->ReadInt( parms.soundShaderFlags ); + readDemo->ReadInt( parms.soundClass ); + EmitterForIndex( index )->ModifySound( (s_channelType)channel, &parms ); + } + break; + case SCMD_STOP: + { + int channel; + + readDemo->ReadInt( index ); + readDemo->ReadInt( channel ); + EmitterForIndex( index )->StopSound( (s_channelType)channel ); + } + break; + case SCMD_FADE: + { + int channel; + float to, over; + + readDemo->ReadInt( index ); + readDemo->ReadInt( channel ); + readDemo->ReadFloat( to ); + readDemo->ReadFloat( over ); + EmitterForIndex( index )->FadeSound((s_channelType)channel, to, over ); + } + break; + } +} + +/* +================= +idSoundWorldLocal::AVIOpen +================= +*/ +void idSoundWorldLocal::AVIOpen( const char *, const char * ) { +} + +/* +================= +idSoundWorldLocal::AVIClose +================= +*/ +void idSoundWorldLocal::AVIClose() { +} + +/* +================= +idSoundWorldLocal::WriteToSaveGame +================= +*/ +void idSoundWorldLocal::WriteToSaveGame( idFile * savefile ) { + struct helper { + static void WriteSoundFade( idFile * savefile, idSoundFade & sf ) { + savefile->WriteInt( sf.fadeStartTime ); + savefile->WriteInt( sf.fadeEndTime ); + savefile->WriteFloat( sf.fadeStartVolume ); + savefile->WriteFloat( sf.fadeEndVolume ); + } + static void WriteShaderParms( idFile * savefile, soundShaderParms_t & parms ) { + savefile->WriteFloat( parms.minDistance ); + savefile->WriteFloat( parms.maxDistance ); + savefile->WriteFloat( parms.volume ); + savefile->WriteFloat( parms.shakes ); + savefile->WriteInt( parms.soundShaderFlags ); + savefile->WriteInt( parms.soundClass ); + } + }; + savefile->WriteInt( GetSoundTime() ); + + helper::WriteSoundFade( savefile, volumeFade ); + for ( int c = 0; c < SOUND_MAX_CLASSES; c++ ) { + helper::WriteSoundFade( savefile, soundClassFade[c] ); + } + savefile->WriteFloat( slowmoSpeed ); + savefile->WriteBool( enviroSuitActive ); + + savefile->WriteMat3( listener.axis ); + savefile->WriteVec3( listener.pos ); + savefile->WriteInt( listener.id ); + savefile->WriteInt( listener.area ); + + savefile->WriteFloat( shakeAmp ); + + int num = emitters.Num(); + savefile->WriteInt( num ); + // Start at 1 because the local sound emitter is not saved + for ( int e = 1; e < emitters.Num(); e++ ) { + idSoundEmitterLocal * emitter = emitters[e]; + savefile->WriteBool( emitter->canFree ); + savefile->WriteVec3( emitter->origin ); + savefile->WriteInt( emitter->emitterId ); + helper::WriteShaderParms( savefile, emitter->parms ); + savefile->WriteInt( emitter->channels.Num() ); + for ( int c = 0; c < emitter->channels.Num(); c++ ) { + idSoundChannel * channel = emitter->channels[c]; + savefile->WriteInt( channel->startTime ); + savefile->WriteInt( channel->endTime ); + savefile->WriteInt( channel->logicalChannel ); + savefile->WriteBool( channel->allowSlow ); + helper::WriteShaderParms( savefile, channel->parms ); + helper::WriteSoundFade( savefile, channel->volumeFade ); + savefile->WriteString( channel->soundShader->GetName() ); + int leadin = -1; + int looping = -1; + for ( int i = 0; i < channel->soundShader->entries.Num(); i++ ) { + if ( channel->soundShader->entries[i] == channel->leadinSample ) { + leadin = i; + } + if ( channel->soundShader->entries[i] == channel->loopingSample ) { + looping = i; + } + } + savefile->WriteInt( leadin ); + savefile->WriteInt( looping ); + } + } +} + +/* +================= +idSoundWorldLocal::ReadFromSaveGame +================= +*/ +void idSoundWorldLocal::ReadFromSaveGame( idFile * savefile ) { + struct helper { + static void ReadSoundFade( idFile * savefile, idSoundFade & sf, int timeDelta ) { + savefile->ReadInt( sf.fadeStartTime ); + savefile->ReadInt( sf.fadeEndTime ); + savefile->ReadFloat( sf.fadeStartVolume ); + savefile->ReadFloat( sf.fadeEndVolume ); + if ( sf.fadeEndTime > 0 ) { + sf.fadeStartTime += timeDelta; + sf.fadeEndTime += timeDelta; + } + } + static void ReadShaderParms( idFile * savefile, soundShaderParms_t & parms ) { + savefile->ReadFloat( parms.minDistance ); + savefile->ReadFloat( parms.maxDistance ); + savefile->ReadFloat( parms.volume ); + savefile->ReadFloat( parms.shakes ); + savefile->ReadInt( parms.soundShaderFlags ); + savefile->ReadInt( parms.soundClass ); + } + }; + int oldSoundTime = 0; + savefile->ReadInt( oldSoundTime ); + int timeDelta = GetSoundTime() - oldSoundTime; + + helper::ReadSoundFade( savefile, volumeFade, timeDelta ); + for ( int c = 0; c < SOUND_MAX_CLASSES; c++ ) { + helper::ReadSoundFade( savefile, soundClassFade[c], timeDelta ); + } + savefile->ReadFloat( slowmoSpeed ); + savefile->ReadBool( enviroSuitActive ); + + savefile->ReadMat3( listener.axis ); + savefile->ReadVec3( listener.pos ); + savefile->ReadInt( listener.id ); + savefile->ReadInt( listener.area ); + + savefile->ReadFloat( shakeAmp ); + + int numEmitters = 0; + savefile->ReadInt( numEmitters ); + ClearAllSoundEmitters(); + idStr shaderName; + // Start at 1 because the local sound emitter is not saved + for ( int e = 1; e < numEmitters; e++ ) { + idSoundEmitterLocal * emitter = (idSoundEmitterLocal *)AllocSoundEmitter(); + assert( emitter == emitters[e] ); + assert( emitter->index == e ); + assert( emitter->soundWorld == this ); + assert( emitter->channels.Num() == 0 ); + savefile->ReadBool( emitter->canFree ); + savefile->ReadVec3( emitter->origin ); + savefile->ReadInt( emitter->emitterId ); + helper::ReadShaderParms( savefile, emitter->parms ); + int numChannels = 0; + savefile->ReadInt( numChannels ); + emitter->channels.SetNum( numChannels ); + for ( int c = 0; c < numChannels; c++ ) { + idSoundChannel * channel = AllocSoundChannel(); + emitter->channels[c] = channel; + channel->emitter = emitter; + savefile->ReadInt( channel->startTime ); + savefile->ReadInt( channel->endTime ); + savefile->ReadInt( channel->logicalChannel ); + savefile->ReadBool( channel->allowSlow ); + helper::ReadShaderParms( savefile, channel->parms ); + helper::ReadSoundFade( savefile, channel->volumeFade, timeDelta ); + savefile->ReadString( shaderName ); + channel->soundShader = declManager->FindSound( shaderName ); + int leadin = 0; + int looping = 0; + savefile->ReadInt( leadin ); + savefile->ReadInt( looping ); + // If the leadin entry is not valid (possible if the shader changed after saving) then the looping entry can't be valid either + if ( leadin >= 0 && leadin < channel->soundShader->entries.Num() ) { + channel->leadinSample = channel->soundShader->entries[ leadin ]; + if ( looping >= 0 && looping < channel->soundShader->entries.Num() ) { + channel->loopingSample = channel->soundShader->entries[ looping ]; + } + } else { + channel->leadinSample = NULL; + channel->loopingSample = NULL; + } + channel->startTime += timeDelta; + if ( channel->endTime == 0 ) { + // Do nothing, endTime == 0 means loop forever + } else if ( channel->endTime <= oldSoundTime ) { + // Channel already stopped + channel->endTime = 1; + } else { + channel->endTime += timeDelta; + } + } + } +} + +/* +================= +idSoundWorldLocal::FadeSoundClasses + +fade all sounds in the world with a given shader soundClass +to is in Db, over is in seconds +================= +*/ +void idSoundWorldLocal::FadeSoundClasses( const int soundClass, const float to, const float over ) { + if ( soundClass < 0 || soundClass >= SOUND_MAX_CLASSES ) { + common->Error( "idSoundWorldLocal::FadeSoundClasses: bad soundClass %i", soundClass ); + return; + } + soundClassFade[ soundClass ].Fade( to, SEC2MS( over ), GetSoundTime() ); +} + +/* +================= +idSoundWorldLocal::SetSlowmoSpeed +================= +*/ +void idSoundWorldLocal::SetSlowmoSpeed( float speed ) { + slowmoSpeed = speed; +} + +/* +================= +idSoundWorldLocal::SetEnviroSuit +================= +*/ +void idSoundWorldLocal::SetEnviroSuit( bool active ) { + enviroSuitActive = active; +} diff --git a/neo/sound/sound.h b/neo/sound/sound.h new file mode 100644 index 00000000..12f07451 --- /dev/null +++ b/neo/sound/sound.h @@ -0,0 +1,335 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SOUND__ +#define __SOUND__ + +/* +=============================================================================== + + SOUND SHADER DECL + +=============================================================================== +*/ + +// unfortunately, our minDistance / maxDistance is specified in meters, and +// we have far too many of them to change at this time. +const float DOOM_TO_METERS = 0.0254f; // doom to meters +const float METERS_TO_DOOM = (1.0f/DOOM_TO_METERS); // meters to doom + +const float DB_SILENCE = -60.0f; + +class idSoundSample; + +// sound shader flags +static const int SSF_PRIVATE_SOUND = BIT(0); // only plays for the current listenerId +static const int SSF_ANTI_PRIVATE_SOUND =BIT(1); // plays for everyone but the current listenerId +static const int SSF_NO_OCCLUSION = BIT(2); // don't flow through portals, only use straight line +static const int SSF_GLOBAL = BIT(3); // play full volume to all speakers and all listeners +static const int SSF_OMNIDIRECTIONAL = BIT(4); // fall off with distance, but play same volume in all speakers +static const int SSF_LOOPING = BIT(5); // repeat the sound continuously +static const int SSF_PLAY_ONCE = BIT(6); // never restart if already playing on any channel of a given emitter +static const int SSF_UNCLAMPED = BIT(7); // don't clamp calculated volumes at 1.0 +static const int SSF_NO_FLICKER = BIT(8); // always return 1.0 for volume queries +static const int SSF_NO_DUPS = BIT(9); // try not to play the same sound twice in a row +static const int SSF_VO = BIT(10);// VO - direct a portion of the sound through the center channel (set automatically on shaders that contain files that start with "sound/vo/") +static const int SSF_MUSIC = BIT(11);// Music - Muted when the player is playing his own music + +// these options can be overriden from sound shader defaults on a per-emitter and per-channel basis +typedef struct { + float minDistance; + float maxDistance; + float volume; // in dB. Negative values get quieter + float shakes; + int soundShaderFlags; // SSF_* bit flags + int soundClass; // for global fading of sounds +} soundShaderParms_t; + +// sound classes are used to fade most sounds down inside cinematics, leaving dialog +// flagged with a non-zero class full volume +const int SOUND_MAX_CLASSES = 4; + +// it is somewhat tempting to make this a virtual class to hide the private +// details here, but that doesn't fit easily with the decl manager at the moment. +class idSoundShader : public idDecl { +public: + idSoundShader(); + virtual ~idSoundShader(); + + virtual size_t Size() const; + virtual bool SetDefaultText(); + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion ); + virtual void FreeData(); + virtual void List() const; + + // so the editor can draw correct default sound spheres + // this is currently defined as meters, which sucks, IMHO. + virtual float GetMinDistance() const; // FIXME: replace this with a GetSoundShaderParms() + virtual float GetMaxDistance() const; + + // returns NULL if an AltSound isn't defined in the shader. + // we use this for pairing a specific broken light sound with a normal light sound + virtual const idSoundShader *GetAltSound() const; + + virtual bool HasDefaultSound() const; + + virtual const soundShaderParms_t *GetParms() const; + virtual int GetNumSounds() const; + virtual const char * GetSound( int index ) const; + +private: + friend class idSoundWorldLocal; + friend class idSoundEmitterLocal; + friend class idSoundChannel; + + // options from sound shader text + soundShaderParms_t parms; // can be overriden on a per-channel basis + + int speakerMask; + const idSoundShader * altSound; + + bool leadin; // true if this sound has a leadin + float leadinVolume; // allows light breaking leadin sounds to be much louder than the broken loop + + idList entries; + +private: + void Init(); + bool ParseShader( idLexer &src ); +}; + +/* +=============================================================================== + + SOUND EMITTER + +=============================================================================== +*/ + +// sound channels +static const int SCHANNEL_ANY = 0; // used in queries and commands to effect every channel at once, in + // startSound to have it not override any other channel +static const int SCHANNEL_ONE = 1; // any following integer can be used as a channel number +typedef int s_channelType; // the game uses its own series of enums, and we don't want to require casts + + +class idSoundEmitter { +public: + virtual ~idSoundEmitter() {} + + // a non-immediate free will let all currently playing sounds complete + // soundEmitters are not actually deleted, they are just marked as + // reusable by the soundWorld + virtual void Free( bool immediate ) = 0; + + // the parms specified will be the default overrides for all sounds started on this emitter. + // NULL is acceptable for parms + virtual void UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ) = 0; + + // returns the length of the started sound in msec + virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0, int shaderFlags = 0, bool allowSlow = true ) = 0; + + // pass SCHANNEL_ANY to effect all channels + virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) = 0; + virtual void StopSound( const s_channelType channel ) = 0; + // to is in Db, over is in seconds + virtual void FadeSound( const s_channelType channel, float to, float over ) = 0; + + // returns true if there are any sounds playing from this emitter. There is some conservative + // slop at the end to remove inconsistent race conditions with the sound thread updates. + // FIXME: network game: on a dedicated server, this will always be false + virtual bool CurrentlyPlaying( const s_channelType channel = SCHANNEL_ANY ) const = 0; + + // returns a 0.0 to 1.0 value based on the current sound amplitude, allowing + // graphic effects to be modified in time with the audio. + // just samples the raw wav file, it doesn't account for volume overrides in the + virtual float CurrentAmplitude() = 0; + + // for save games. Index will always be > 0 + virtual int Index() const = 0; +}; + +/* +=============================================================================== + + SOUND WORLD + +There can be multiple independent sound worlds, just as there can be multiple +independent render worlds. The prime example is the editor sound preview +option existing simultaniously with a live game. +=============================================================================== +*/ + +class idSoundWorld { +public: + virtual ~idSoundWorld() {} + + // call at each map start + virtual void ClearAllSoundEmitters() = 0; + virtual void StopAllSounds() = 0; + + // get a new emitter that can play sounds in this world + virtual idSoundEmitter *AllocSoundEmitter() = 0; + + // for load games, index 0 will return NULL + virtual idSoundEmitter *EmitterForIndex( int index ) = 0; + + // query sound samples from all emitters reaching a given listener + virtual float CurrentShakeAmplitude() = 0; + + // where is the camera/microphone + // listenerId allows listener-private and antiPrivate sounds to be filtered + virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId ) = 0; + + // fade all sounds in the world with a given shader soundClass + // to is in Db, over is in seconds + virtual void FadeSoundClasses( const int soundClass, const float to, const float over ) = 0; + + // menu sounds + virtual int PlayShaderDirectly( const char * name, int channel = -1 ) = 0; + + // dumps the current state and begins archiving commands + virtual void StartWritingDemo( idDemoFile *demo ) = 0; + virtual void StopWritingDemo() = 0; + + // read a sound command from a demo file + virtual void ProcessDemoCommand( idDemoFile *demo ) = 0; + + // when cinematics are skipped, we need to advance sound time this much + virtual void Skip( int time ) = 0; + + // pause and unpause the sound world + virtual void Pause() = 0; + virtual void UnPause() = 0; + virtual bool IsPaused() = 0; + + // Write the sound output to multiple wav files. Note that this does not use the + // work done by AsyncUpdate, it mixes explicitly in the foreground every PlaceOrigin(), + // under the assumption that we are rendering out screenshots and the gameTime is going + // much slower than real time. + // path should not include an extension, and the generated filenames will be: + // _left.raw, _right.raw, or _51left.raw, _51right.raw, + // _51center.raw, _51lfe.raw, _51backleft.raw, _51backright.raw, + // If only two channel mixing is enabled, the left and right .raw files will also be + // combined into a stereo .wav file. + virtual void AVIOpen( const char *path, const char *name ) = 0; + virtual void AVIClose() = 0; + + // SaveGame / demo Support + virtual void WriteToSaveGame( idFile *savefile ) = 0; + virtual void ReadFromSaveGame( idFile *savefile ) = 0; + + virtual void SetSlowmoSpeed( float speed ) = 0; + virtual void SetEnviroSuit( bool active ) = 0; +}; + + +/* +=============================================================================== + + SOUND SYSTEM + +=============================================================================== +*/ + +typedef struct { + idStr name; + idStr format; + int numChannels; + int numSamplesPerSecond; + int num44kHzSamples; + int numBytes; + bool looping; + float lastVolume; + int start44kHzTime; + int current44kHzTime; +} soundDecoderInfo_t; + + +class idSoundSystem { +public: + virtual ~idSoundSystem() {} + + // All non-hardware initialization. + virtual void Init() = 0; + + // Shutdown routine. + virtual void Shutdown() = 0; + + // The renderWorld is used for visualization and light amplitude sampling. + virtual idSoundWorld * AllocSoundWorld( idRenderWorld *rw ) = 0; + virtual void FreeSoundWorld( idSoundWorld *sw ) = 0; + + // Specifying NULL will cause silence to be played. + virtual void SetPlayingSoundWorld( idSoundWorld *soundWorld ) = 0; + + // Some tools, like the sound dialog, may be used in both the game and the editor + // This can return NULL, so check! + virtual idSoundWorld * GetPlayingSoundWorld() = 0; + + // Sends the current playing sound world information to the sound hardware. + virtual void Render() = 0; + + virtual void MuteBackgroundMusic( bool mute ) = 0; + + // Sets the final output volume to 0. + virtual void SetMute( bool mute ) = 0; + virtual bool IsMuted() = 0; + + // Called by the decl system when a sound decl is reloaded + virtual void OnReloadSound( const idDecl* sound ) = 0; + + // Called before freeing any sound sample resources + virtual void StopAllSounds() = 0; + + // May be called to free memory for level loads + virtual void InitStreamBuffers() = 0; + virtual void FreeStreamBuffers() = 0; + + // video playback needs to get this + virtual void * GetIXAudio2() const = 0; + + // for the sound level meter window + virtual cinData_t ImageForTime( const int milliseconds, const bool waveform ) = 0; + + // Free all sounds loaded during the last map load + virtual void BeginLevelLoad() = 0; + + // Load all sounds marked as used this level + virtual void EndLevelLoad() = 0; + + virtual void Preload( idPreloadManifest & preload ) = 0; + + // prints memory info + virtual void PrintMemInfo( MemInfo_t *mi ) = 0; +}; + +extern idSoundSystem *soundSystem; + +#endif /* !__SOUND__ */ diff --git a/neo/swf/SWF.h b/neo/swf/SWF.h new file mode 100644 index 00000000..51a0f313 --- /dev/null +++ b/neo/swf/SWF.h @@ -0,0 +1,405 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_H__ +#define __SWF_H__ + +#include "SWF_Enums.h" +#include "SWF_Types.h" +#include "SWF_Bitstream.h" +#include "SWF_ScriptVar.h" +#include "SWF_Sprites.h" +#include "SWF_ScriptObject.h" +#include "SWF_ParmList.h" +#include "SWF_ScriptFunction.h" +#include "SWF_SpriteInstance.h" +#include "SWF_ShapeParser.h" +#include "SWF_TextInstance.h" + +class idSWFDictionaryEntry { +public: + idSWFDictionaryEntry(); + ~idSWFDictionaryEntry(); + idSWFDictionaryEntry & operator=( idSWFDictionaryEntry & other ); + + swfDictType_t type; + const idMaterial * material; + idSWFShape * shape; + idSWFSprite * sprite; + idSWFFont * font; + idSWFText * text; + idSWFEditText * edittext; + + idVec2i imageSize; + idVec2i imageAtlasOffset; + // the compressed images are normalize to reduce compression artifacts, + // color must be scaled down by this + idVec4 channelScale; +}; + +struct purgableSwfImage_t { + purgableSwfImage_t() { image = NULL; swfFrameNum = 0; } + idImage * image; + unsigned swfFrameNum; +}; + +/* +================================================ +This class handles loading and rendering SWF files +================================================ +*/ +class idSWF { +public: + idSWF( const char * filename, idSoundWorld * soundWorld = NULL ); + ~idSWF(); + + bool IsLoaded() { return ( frameRate > 0 ); } + bool IsActive() { return isActive; } + void Activate( bool b ); + + const char * GetName() { return filename; } + + void Pause() { mainspriteInstance->Stop(); paused = true; } + void Resume() { mainspriteInstance->Play(); paused = false; } + bool IsPaused() { return paused; } + void SetPausedRender( bool valid ) { pausedRender = valid; } + bool GetPausedRender() { return pausedRender; } + + void Render( idRenderSystem * gui, int time = 0, bool isSplitscreen = false ); + bool HandleEvent( const sysEvent_t * event ); + bool InhibitControl(); + void ForceInhibitControl( bool val ) { inhibitControl = val; } + + void SetGlobal( const char * name, const idSWFScriptVar & value ) { globals->Set( name, value ); } + void SetGlobalNative( const char * name, idSWFScriptNativeVariable * native ) { globals->SetNative( name, native ); } + idSWFScriptVar GetGlobal( const char * name ) { return globals->Get( name ); } + idSWFScriptObject & GetRootObject() { assert( mainspriteInstance->GetScriptObject() != NULL ); return *( mainspriteInstance->GetScriptObject() ); } + + void Invoke( const char * functionName, const idSWFParmList & parms ); + void Invoke( const char * functionName, const idSWFParmList & parms, idSWFScriptVar & scriptVar ); + void Invoke( const char * functionName, const idSWFParmList & parms, bool & functionExists ); + + int PlaySound( const char * sound, int channel = SCHANNEL_ANY, bool blocking = false ); + void StopSound( int channel = SCHANNEL_ANY ); + + float GetFrameWidth() const { return frameWidth; } + float GetFrameHeight() const { return frameHeight; } + + int GetMouseX() { return mouseX; } + int GetMouseY() { return mouseY; } + + bool UseCircleForAccept(); + + void SetSWFScale( float scale ) { swfScale = scale; } + + void SetForceNonPCGetPlatform() { forceNonPCPlatform = true; } + + idRandom2 & GetRandom() { return random; } + + int GetPlatform(); + + //---------------------------------- + // SWF_Dictionary.cpp + //---------------------------------- + idSWFDictionaryEntry * AddDictionaryEntry( int characterID, swfDictType_t type ); + idSWFDictionaryEntry * FindDictionaryEntry( int characterID, swfDictType_t type ); + idSWFDictionaryEntry * FindDictionaryEntry( int characterID ); + + idSWFDictionaryEntry * GetDictionaryEntry( int index ) { return &dictionary[ index ]; } + int GetNumDictionaryEntry() { return dictionary.Num(); } + + idSWFScriptObject * HitTest( idSWFSpriteInstance * spriteInstance, const swfRenderState_t & renderState, int x, int y, idSWFScriptObject * parentObject ); + +private: + idStr filename; + ID_TIME_T timestamp; + + float frameWidth; + float frameHeight; + uint16 frameRate; + float renderBorder; + float swfScale; + + idVec2 scaleToVirtual; + + int lastRenderTime; + + bool isActive; + bool inhibitControl; + bool useInhibtControl; + + // certain screens need to be rendered when the pause menu is up so if this flag is + // set on the gui we will allow it to render at a paused state; + bool pausedRender; + + bool mouseEnabled; + bool useMouse; + + bool blackbars; + bool crop; + bool paused; + bool hasHitObject; + + bool forceNonPCPlatform; + + idRandom2 random; + + static int mouseX; // mouse x coord for all flash files + static int mouseY; // mouse y coord for all flash files + static bool isMouseInClientArea; + + idSWFScriptObject * mouseObject; + idSWFScriptObject * hoverObject; + + idSWFSprite * mainsprite; + idSWFSpriteInstance * mainspriteInstance; + + idSWFScriptObject * globals; + idSWFScriptObject * shortcutKeys; + + idSoundWorld * soundWorld; + + const idMaterial * atlasMaterial; + + idBlockAlloc< idSWFSpriteInstance, 16 > spriteInstanceAllocator; + idBlockAlloc< idSWFTextInstance, 16 > textInstanceAllocator; + +#define SWF_NATIVE_FUNCTION_SWF_DECLARE( x ) \ + class idSWFScriptFunction_##x : public idSWFScriptFunction_Nested< idSWF > { \ + public: \ + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ); \ + } scriptFunction_##x; + + SWF_NATIVE_FUNCTION_SWF_DECLARE( shortcutKeys_clear ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( deactivate ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( inhibitControl ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( useInhibit ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( precacheSound ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( playSound ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( stopSounds ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( getPlatform ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( getTruePlatform ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( getLocalString ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( swapPS3Buttons ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( getCVarInteger ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( setCVarInteger ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( strReplace ); + + SWF_NATIVE_FUNCTION_SWF_DECLARE( acos ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( cos ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( sin ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( round ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( pow ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( sqrt ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( abs ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( rand ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( floor ); + SWF_NATIVE_FUNCTION_SWF_DECLARE( ceil ); + + SWF_NATIVE_FUNCTION_SWF_DECLARE( toUpper ); + + SWF_NATIVE_VAR_DECLARE_NESTED_READONLY( platform, idSWFScriptFunction_getPlatform, Call( object, idSWFParmList() ) ); + SWF_NATIVE_VAR_DECLARE_NESTED( blackbars, idSWF ); + SWF_NATIVE_VAR_DECLARE_NESTED( crop, idSWF ); + + class idSWFScriptFunction_Object : public idSWFScriptFunction { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { return idSWFScriptVar(); } + void AddRef() { } + void Release() { } + idSWFScriptObject * GetPrototype() { return &object; } + void SetPrototype( idSWFScriptObject * _object ) { assert( false ); } + idSWFScriptObject object; + } scriptFunction_Object; + + idList< idSWFDictionaryEntry, TAG_SWF > dictionary; + + struct keyButtonImages_t { + + keyButtonImages_t() { + key = ""; + xbImage = ""; + psImage = ""; + width = 0; + height = 0; + baseline = 0; + } + + keyButtonImages_t( const char* _key, const char* _xbImage, const char* _psImage, int w, int h, int _baseline ) { + key = _key; + xbImage = _xbImage; + psImage = _psImage; + width = w; + height = h; + baseline = _baseline; + } + + const char * key; + const char * xbImage; + const char * psImage; + int width; + int height; + int baseline; + }; + idList< keyButtonImages_t, TAG_SWF > tooltipButtonImage; + + struct tooltipIcon_t { + tooltipIcon_t() { + startIndex = -1; + endIndex = -1; + material = NULL; + imageWidth = 0; + imageHeight = 0; + baseline = 0; + }; + + int startIndex; + int endIndex; + const idMaterial * material; + short imageWidth; + short imageHeight; + int baseline; + }; + idList< tooltipIcon_t, TAG_SWF > tooltipIconList; + + const idMaterial * guiSolid; + const idMaterial * guiCursor_arrow; + const idMaterial * guiCursor_hand; + const idMaterial * white; + +private: + friend class idSWFSprite; + friend class idSWFSpriteInstance; + + bool LoadSWF( const char * fullpath ); + bool LoadBinary( const char * bfilename, ID_TIME_T sourceTime ); + void WriteBinary( const char * bfilename ); + + //---------------------------------- + // SWF_Shapes.cpp + //---------------------------------- + void DefineShape( idSWFBitStream & bitstream ); + void DefineShape2( idSWFBitStream & bitstream ); + void DefineShape3( idSWFBitStream & bitstream ); + void DefineShape4( idSWFBitStream & bitstream ); + void DefineMorphShape( idSWFBitStream & bitstream ); + + //---------------------------------- + // SWF_Sprites.cpp + //---------------------------------- + void DefineSprite( idSWFBitStream & bitstream ); + + //---------------------------------- + // SWF_Sounds.cpp + //---------------------------------- + void DefineSound( idSWFBitStream & bitstream ); + + //---------------------------------- + // SWF_Render.cpp + //---------------------------------- + void DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ); + void DrawStretchPic( const idVec4 & topLeft, const idVec4 & topRight, const idVec4 & bottomRight, const idVec4 & bottomLeft, const idMaterial * material ); + void RenderSprite( idRenderSystem * gui, idSWFSpriteInstance * sprite, const swfRenderState_t & renderState, int time, bool isSplitscreen = false ); + void RenderMask( idRenderSystem * gui, const swfDisplayEntry_t * mask, const swfRenderState_t & renderState, const int stencilMode ); + void RenderShape( idRenderSystem * gui, const idSWFShape * shape, const swfRenderState_t & renderState ); + void RenderMorphShape( idRenderSystem * gui, const idSWFShape* shape, const swfRenderState_t & renderState ); + void DrawEditCursor( idRenderSystem * gui, float x, float y, float w, float h, const swfMatrix_t & matrix ); + void DrawLine( idRenderSystem * gui, const idVec2 & p1, const idVec2 & p2, float width, const swfMatrix_t & matrix ); + void RenderEditText( idRenderSystem * gui, idSWFTextInstance * textInstance, const swfRenderState_t & renderState, int time, bool isSplitscreen = false ); + uint64 GLStateForRenderState( const swfRenderState_t & renderState ); + void FindTooltipIcons( idStr * text ); + + //---------------------------------- + // SWF_Image.cpp + //---------------------------------- + + class idDecompressJPEG { + public: + idDecompressJPEG(); + ~idDecompressJPEG(); + + byte * Load( const byte * input, int inputSize, int & width, int & height ); + + private: + void * vinfo; + }; + + idDecompressJPEG jpeg; + + void LoadImage( int characterID, const byte * imageData, int width, int height ); + + void JPEGTables( idSWFBitStream & bitstream ); + void DefineBits( idSWFBitStream & bitstream ); + void DefineBitsJPEG2( idSWFBitStream & bitstream ); + void DefineBitsJPEG3( idSWFBitStream & bitstream ); + void DefineBitsLossless( idSWFBitStream & bitstream ); + void DefineBitsLossless2( idSWFBitStream & bitstream ); + + + // per-swf image atlas + struct imageToPack_t { + int characterID; + idVec2i trueSize; // in texels + byte * imageData; // trueSize.x * trueSize.y * 4 + idVec2i allocSize; // in DXT tiles, includes a border texel and rounding up to DXT blocks + }; + + class idSortBlocks : public idSort_Quick< imageToPack_t, idSortBlocks > { + public: + int Compare( const imageToPack_t & a, const imageToPack_t & b ) const { + return ( b.allocSize.x * b.allocSize.y ) - ( a.allocSize.x * a.allocSize.y ); + } + }; + + idList packImages; // only used during creation + void WriteSwfImageAtlas( const char *filename ); + + //---------------------------------- + // SWF_Text.cpp + //---------------------------------- + void DefineFont2( idSWFBitStream & bitstream ); + void DefineFont3( idSWFBitStream & bitstream ); + void DefineTextX( idSWFBitStream & bitstream, bool rgba ); + void DefineText( idSWFBitStream & bitstream ); + void DefineText2( idSWFBitStream & bitstream ); + void DefineEditText( idSWFBitStream & bitstream ); + + //---------------------------------- + // SWF_Zlib.cpp + //---------------------------------- + bool Inflate( const byte * input, int inputSize, byte * output, int outputSize ); + +public: + //---------------------------------- + // SWF_Names.cpp + //---------------------------------- + static const char * GetTagName( swfTag_t tag ); + static const char * GetActionName( swfAction_t action ); + +}; + +#endif // !__SWF_H__ diff --git a/neo/swf/SWF_Bitstream.cpp b/neo/swf/SWF_Bitstream.cpp new file mode 100644 index 00000000..cd8d1bbc --- /dev/null +++ b/neo/swf/SWF_Bitstream.cpp @@ -0,0 +1,402 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#define NBM( x ) (int32)( ( 1LL << x ) - 1 ) +int maskForNumBits[33] = { NBM( 0x00 ), NBM( 0x01 ), NBM( 0x02 ), NBM( 0x03 ), + NBM( 0x04 ), NBM( 0x05 ), NBM( 0x06 ), NBM( 0x07 ), + NBM( 0x08 ), NBM( 0x09 ), NBM( 0x0A ), NBM( 0x0B ), + NBM( 0x0C ), NBM( 0x0D ), NBM( 0x0E ), NBM( 0x0F ), + NBM( 0x10 ), NBM( 0x11 ), NBM( 0x12 ), NBM( 0x13 ), + NBM( 0x14 ), NBM( 0x15 ), NBM( 0x16 ), NBM( 0x17 ), + NBM( 0x18 ), NBM( 0x19 ), NBM( 0x1A ), NBM( 0x1B ), + NBM( 0x1C ), NBM( 0x1D ), NBM( 0x1E ), NBM( 0x1F ), -1 }; + +#define NBS( x ) (int32)( (-1) << ( x - 1 ) ) +int signForNumBits[33] = { NBS( 0x01 ), NBS( 0x01 ), NBS( 0x02 ), NBS( 0x03 ), + NBS( 0x04 ), NBS( 0x05 ), NBS( 0x06 ), NBS( 0x07 ), + NBS( 0x08 ), NBS( 0x09 ), NBS( 0x0A ), NBS( 0x0B ), + NBS( 0x0C ), NBS( 0x0D ), NBS( 0x0E ), NBS( 0x0F ), + NBS( 0x10 ), NBS( 0x11 ), NBS( 0x12 ), NBS( 0x13 ), + NBS( 0x14 ), NBS( 0x15 ), NBS( 0x16 ), NBS( 0x17 ), + NBS( 0x18 ), NBS( 0x19 ), NBS( 0x1A ), NBS( 0x1B ), + NBS( 0x1C ), NBS( 0x1D ), NBS( 0x1E ), NBS( 0x1F ), NBS( 0x20 ) }; + +#define ID_FORCEINLINE __forceinline + +/* +======================== +idSWFBitStream::idSWFBitStream +======================== +*/ +idSWFBitStream::idSWFBitStream() { + free = false; + Free(); +} + +/* +======================== +idSWFBitStream::operator= +======================== +*/ +idSWFBitStream & idSWFBitStream::operator=( idSWFBitStream & other ) { + Free(); + free = other.free; + startp = other.startp; + readp = other.readp; + endp = other.endp; + currentBit = other.currentBit; + currentByte = other.currentByte; + if ( other.free ) { + // this is actually quite dangerous, but we need to do this + // because these things are copied around inside idList + other.free = false; + } + return *this; +} + +/* +======================== +idSWFBitStream::Free +======================== +*/ +void idSWFBitStream::Free() { + if ( free ) { + Mem_Free( (void *)startp ); + } + free = false; + startp = NULL; + endp = NULL; + readp = NULL; + ResetBits(); +} + +/* +======================== +idSWFBitStream::Load +======================== +*/ +void idSWFBitStream::Load( const byte * data, uint32 len, bool copy ) { + Free(); + + if ( copy ) { + free = true; + startp = (const byte *)Mem_Alloc( len, TAG_SWF ); + memcpy( (byte *)startp, data, len ); + } else { + free = false; + startp = data; + } + endp = startp + len; + readp = startp; + + ResetBits(); +} + +/* +======================== +idSWFBitStream::ReadEncodedU32 +======================== +*/ +uint32 idSWFBitStream::ReadEncodedU32() { + uint32 result = 0; + for ( int i = 0; i < 5; i++ ) { + byte b = ReadU8(); + result |= ( b & 0x7F ) << ( 7 * i ); + if ( ( b & 0x80 ) == 0 ) { + return result; + } + } + return result; +} + +/* +======================== +idSWFBitStream::ReadData +======================== +*/ +const byte * idSWFBitStream::ReadData( int size ) { + assert( readp >= startp && readp <= endp ); + ResetBits(); + if ( readp + size > endp ) { + // buffer overrun + assert( false ); + readp = endp; + return startp; + } + const byte * buffer = readp; + readp += size; + assert( readp >= startp && readp <= endp ); + return buffer; +} + +/* +======================== +idSWFBitStream::ReadInternalU +======================== +*/ +ID_FORCEINLINE unsigned int idSWFBitStream::ReadInternalU( uint64 & regCurrentBit, uint64 & regCurrentByte, unsigned int numBits ) { + assert( numBits <= 32 ); + + // read bits with only one microcoded shift instruction (shift with variable) on the consoles + // this routine never reads more than 7 bits beyond the requested number of bits from the stream + // such that calling ResetBits() never discards more than 7 bits and aligns with the next byte + uint64 numExtraBytes = ( numBits - regCurrentBit + 7 ) >> 3; + regCurrentBit = regCurrentBit + ( numExtraBytes << 3 ) - numBits; + for ( int i = 0; i < numExtraBytes; i++ ) { + regCurrentByte = ( regCurrentByte << 8 ) | readp[i]; + } + readp += numExtraBytes; + return (unsigned int) ( ( regCurrentByte >> regCurrentBit ) & maskForNumBits[numBits] ); +} + +/* +======================== +idSWFBitStream::ReadInternalS +======================== +*/ +ID_FORCEINLINE int idSWFBitStream::ReadInternalS( uint64 & regCurrentBit, uint64 & regCurrentByte, unsigned int numBits ) { + int i = (int)ReadInternalU( regCurrentBit, regCurrentByte, numBits ); + + // sign extend without microcoded shift instrunction (shift with variable) on the consoles + int s = signForNumBits[numBits]; + return ( ( i + s ) ^ s ); +} + +/* +======================== +idSWFBitStream::ReadU +======================== +*/ +unsigned int idSWFBitStream::ReadU( unsigned int numBits ) { + return ReadInternalU( currentBit, currentByte, numBits ); +} + +/* +======================== +idSWFBitStream::ReadS +======================== +*/ +int idSWFBitStream::ReadS( unsigned int numBits ) { + return ReadInternalS( currentBit, currentByte, numBits ); +} + +/* +======================== +idSWFBitStream::ReadRect +======================== +*/ +void idSWFBitStream::ReadRect( swfRect_t & rect ) { + uint64 regCurrentBit = 0; + uint64 regCurrentByte = 0; + + int nBits = ReadInternalU( regCurrentBit, regCurrentByte, 5 ); + + int tl_x = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + int br_x = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + int tl_y = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + int br_y = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + + rect.tl.x = SWFTWIP( tl_x ); + rect.br.x = SWFTWIP( br_x ); + rect.tl.y = SWFTWIP( tl_y ); + rect.br.y = SWFTWIP( br_y ); + + currentBit = regCurrentBit; + currentByte = regCurrentByte; +} + +/* +======================== +idSWFBitStream::ReadMatrix +======================== +*/ +void idSWFBitStream::ReadMatrix( swfMatrix_t & matrix ) { + uint64 regCurrentBit = 0; + uint64 regCurrentByte = 0; + + + unsigned int hasScale = ReadInternalU( regCurrentBit, regCurrentByte, 1 ); + + int xx; + int yy; + if ( !hasScale ) { + xx = 65536; + yy = 65536; + } else { + int nBits = ReadInternalU( regCurrentBit, regCurrentByte, 5 ); + xx = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + yy = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + } + + unsigned int hasRotate = ReadInternalU( regCurrentBit, regCurrentByte, 1 ); + + int yx; + int xy; + if ( !hasRotate ) { + yx = 0; + xy = 0; + } else { + int nBits = ReadInternalU( regCurrentBit, regCurrentByte, 5 ); + yx = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + xy = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + } + + int nBits = ReadInternalU( regCurrentBit, regCurrentByte, 5 ); + int tx = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + int ty = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + + currentBit = regCurrentBit; + currentByte = regCurrentByte; + + matrix.xx = SWFFIXED16( xx ); + matrix.yy = SWFFIXED16( yy ); + matrix.yx = SWFFIXED16( yx ); + matrix.xy = SWFFIXED16( xy ); + matrix.tx = SWFTWIP( tx ); + matrix.ty = SWFTWIP( ty ); + +} + +/* +======================== +idSWFBitStream::ReadColorXFormRGBA +======================== +*/ +void idSWFBitStream::ReadColorXFormRGBA( swfColorXform_t & cxf ) { + uint64 regCurrentBit = 0; + uint64 regCurrentByte = 0; + + unsigned int hasAddTerms = ReadInternalU( regCurrentBit, regCurrentByte, 1 ); + unsigned int hasMulTerms = ReadInternalU( regCurrentBit, regCurrentByte, 1 ); + int nBits = ReadInternalU( regCurrentBit, regCurrentByte, 4 ); + + union { int i[4]; } m; + union { int i[4]; } a; + + if ( !hasMulTerms ) { + m.i[0] = 256; + m.i[1] = 256; + m.i[2] = 256; + m.i[3] = 256; + } else { + m.i[0] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + m.i[1] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + m.i[2] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + m.i[3] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + } + + if ( !hasAddTerms ) { + a.i[0] = 0; + a.i[1] = 0; + a.i[2] = 0; + a.i[3] = 0; + } else { + a.i[0] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + a.i[1] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + a.i[2] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + a.i[3] = ReadInternalS( regCurrentBit, regCurrentByte, nBits ); + } + + currentBit = regCurrentBit; + currentByte = regCurrentByte; + + for ( int i = 0; i < 4; i++ ) { + cxf.mul[i] = SWFFIXED8( m.i[i] ); + cxf.add[i] = SWFFIXED8( a.i[i] ); + } +} + +/* +======================== +idSWFBitStream::ReadString +======================== +*/ +const char * idSWFBitStream::ReadString() { + return (const char *)ReadData( idStr::Length( (const char *)readp ) + 1 ); +} + +/* +======================== +idSWFBitStream::ReadColorRGB +======================== +*/ +void idSWFBitStream::ReadColorRGB( swfColorRGB_t & color ) { + ResetBits(); + color.r = *readp++; + color.g = *readp++; + color.b = *readp++; +} + +/* +======================== +idSWFBitStream::ReadColorRGBA +======================== +*/ +void idSWFBitStream::ReadColorRGBA( swfColorRGBA_t & color ) { + ResetBits(); + color.r = *readp++; + color.g = *readp++; + color.b = *readp++; + color.a = *readp++; +} + +/* +======================== +idSWFBitStream::ReadGradient +======================== +*/ +void idSWFBitStream::ReadGradient( swfGradient_t & grad, bool rgba ) { + grad.numGradients = ReadU8() & 0xF; // the top 4 bits control spread and interpolation mode, but we ignore them + for ( int i = 0; i < grad.numGradients; i++ ) { + grad.gradientRecords[i].startRatio = ReadU8(); + if ( rgba ) { + ReadColorRGBA( grad.gradientRecords[i].startColor ); + } else { + ReadColorRGB( grad.gradientRecords[i].startColor ); + } + grad.gradientRecords[i].endRatio = grad.gradientRecords[i].startRatio; + grad.gradientRecords[i].endColor = grad.gradientRecords[i].startColor; + } +} + +/* +======================== +idSWFBitStream::ReadMorphGradient +======================== +*/ +void idSWFBitStream::ReadMorphGradient( swfGradient_t & grad ) { + grad.numGradients = ReadU8() & 0xF; // the top 4 bits control spread and interpolation mode, but we ignore them + for ( int i = 0; i < grad.numGradients; i++ ) { + grad.gradientRecords[i].startRatio = ReadU8(); + ReadColorRGBA( grad.gradientRecords[i].startColor ); + grad.gradientRecords[i].endRatio = ReadU8(); + ReadColorRGBA( grad.gradientRecords[i].endColor ); + } +} diff --git a/neo/swf/SWF_Bitstream.h b/neo/swf/SWF_Bitstream.h new file mode 100644 index 00000000..e742089d --- /dev/null +++ b/neo/swf/SWF_Bitstream.h @@ -0,0 +1,145 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_BITSTREAM_H__ +#define __SWF_BITSTREAM_H__ + +class idSWFBitStream { +public: + idSWFBitStream(); + idSWFBitStream( const byte * data, uint32 len, bool copy ) { free = false; Load( data, len, copy ); } + ~idSWFBitStream() { Free(); } + + idSWFBitStream & operator=( idSWFBitStream & other ); + + void Load( const byte * data, uint32 len, bool copy ); + void Free(); + const byte * Ptr() { return startp; } + + uint32 Length() const { return (uint32)( endp - startp ); } + uint32 Tell() const { return (uint32)( readp - startp ); } + void Seek( int32 offset ) { readp += offset; } + void Rewind() { readp = startp; } + + void ResetBits(); + + int ReadS( unsigned int numBits ); + unsigned int ReadU( unsigned int numBits ); + bool ReadBool(); + + const byte * ReadData( int size ); + + template< typename T > + void ReadLittle( T & val ); + + uint8 ReadU8(); + uint16 ReadU16(); + uint32 ReadU32(); + int16 ReadS16(); + int32 ReadS32(); + uint32 ReadEncodedU32(); + float ReadFixed8(); + float ReadFixed16(); + float ReadFloat(); + double ReadDouble(); + const char * ReadString(); + + void ReadRect( swfRect_t & rect ); + void ReadMatrix( swfMatrix_t & matrix ); + void ReadColorXFormRGBA( swfColorXform_t & cxf ); + void ReadColorRGB( swfColorRGB_t & color ); + void ReadColorRGBA( swfColorRGBA_t & color ); + void ReadGradient( swfGradient_t & grad, bool rgba ); + void ReadMorphGradient( swfGradient_t & grad ); + +private: + bool free; + + const byte * startp; + const byte * endp; + const byte * readp; + + uint64 currentBit; + uint64 currentByte; + + int ReadInternalS( uint64 & regCurrentBit, uint64 & regCurrentByte, unsigned int numBits ); + unsigned int ReadInternalU( uint64 & regCurrentBit, uint64 & regCurrentByte, unsigned int numBits ); +}; + +/* +======================== +idSWFBitStream::ResetBits +======================== +*/ +ID_INLINE void idSWFBitStream::ResetBits() { + currentBit = 0; + currentByte = 0; +} + +/* +======================== +idSWFBitStream::ReadLittle +======================== +*/ +template< typename T > +void idSWFBitStream::ReadLittle( T & val ) { + val = *(T *)ReadData( sizeof( val ) ); + idSwap::Little( val ); +} + +/* +======================== +Wrappers for the most basic types +======================== +*/ +ID_INLINE bool idSWFBitStream::ReadBool() { return ( ReadU( 1 ) != 0 ); } +ID_INLINE uint8 idSWFBitStream::ReadU8() { ResetBits(); return *readp++; } +ID_INLINE uint16 idSWFBitStream::ReadU16() { ResetBits(); readp += 2; return ( readp[-2] | ( readp[-1] << 8 ) ); } +ID_INLINE uint32 idSWFBitStream::ReadU32() { ResetBits(); readp += 4; return ( readp[-4] | ( readp[-3] << 8 ) | ( readp[-2] << 16 ) | ( readp[-1] << 24 ) ); } +ID_INLINE int16 idSWFBitStream::ReadS16() { ResetBits(); readp += 2; return ( readp[-2] | ( readp[-1] << 8 ) ); } +ID_INLINE int32 idSWFBitStream::ReadS32() { ResetBits(); readp += 4; return ( readp[-4] | ( readp[-3] << 8 ) | ( readp[-2] << 16 ) | ( readp[-1] << 24 ) ); } +ID_INLINE float idSWFBitStream::ReadFixed8() { ResetBits(); readp += 2; return SWFFIXED8( ( readp[-2] | ( readp[-1] << 8 ) ) ); } +ID_INLINE float idSWFBitStream::ReadFixed16() { ResetBits(); readp += 4; return SWFFIXED16( ( readp[-4] | ( readp[-3] << 8 ) | ( readp[-2] << 16 ) | ( readp[-1] << 24 ) ) ); } +ID_INLINE float idSWFBitStream::ReadFloat() { ResetBits(); readp += 4; uint32 i = ( readp[-4] | ( readp[-3] << 8 ) | ( readp[-2] << 16 ) | ( readp[-1] << 24 ) ); return (float &)i; } + +ID_INLINE double idSWFBitStream::ReadDouble() { + const byte * swfIsRetarded = ReadData( 8 ); + byte buffer[8]; + buffer[0] = swfIsRetarded[4]; + buffer[1] = swfIsRetarded[5]; + buffer[2] = swfIsRetarded[6]; + buffer[3] = swfIsRetarded[7]; + buffer[4] = swfIsRetarded[0]; + buffer[5] = swfIsRetarded[1]; + buffer[6] = swfIsRetarded[2]; + buffer[7] = swfIsRetarded[3]; + double d = *(double *)buffer; + idSwap::Little( d ); + return d; +} + +#endif // !__SWF_BITSTREAM_H__ diff --git a/neo/swf/SWF_Dictionary.cpp b/neo/swf/SWF_Dictionary.cpp new file mode 100644 index 00000000..4361d823 --- /dev/null +++ b/neo/swf/SWF_Dictionary.cpp @@ -0,0 +1,155 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +======================== +idSWF::idSWFDictionaryEntry::idSWFDictionaryEntry +======================== +*/ +idSWFDictionaryEntry::idSWFDictionaryEntry() : + type( SWF_DICT_NULL ), + material( NULL ), + shape( NULL ), + sprite( NULL ), + font( NULL ), + text( NULL ), + edittext( NULL ), + imageSize( 0, 0 ), + imageAtlasOffset( 0, 0 ), + channelScale( 1.0f, 1.0f, 1.0f, 1.0f ) { +} + +/* +======================== +idSWF::idSWFDictionaryEntry::idSWFDictionaryEntry +======================== +*/ +idSWFDictionaryEntry::~idSWFDictionaryEntry() { + delete shape; + delete sprite; + delete font; + delete text; + delete edittext; +} + +/* +======================== +idSWF::idSWFDictionaryEntry::operator= +This exists mostly so idList works right +======================== +*/ +idSWFDictionaryEntry & idSWFDictionaryEntry::operator=( idSWFDictionaryEntry & other ) { + type = other.type; + material = other.material; + shape = other.shape; + sprite = other.sprite; + font = other.font; + text = other.text; + edittext = other.edittext; + imageSize = other.imageSize; + imageAtlasOffset = other.imageAtlasOffset; + other.type = SWF_DICT_NULL; + other.material = NULL; + other.shape = NULL; + other.sprite = NULL; + other.font = NULL; + other.text = NULL; + other.edittext = NULL; + return *this; +} + +/* +======================== +idSWF::AddDictionaryEntry +======================== +*/ +idSWFDictionaryEntry * idSWF::AddDictionaryEntry( int characterID, swfDictType_t type ) { + + if ( dictionary.Num() < characterID + 1 ) { + dictionary.SetNum( characterID + 1 ); + } + + if ( dictionary[ characterID ].type != SWF_DICT_NULL ) { + idLib::Warning( "%s: Duplicate character %d", filename.c_str(), characterID ); + return NULL; + } + + dictionary[ characterID ].type = type; + + if ( ( type == SWF_DICT_SHAPE ) || ( type == SWF_DICT_MORPH ) ) { + dictionary[ characterID ].shape = new (TAG_SWF) idSWFShape; + } else if ( type == SWF_DICT_SPRITE ) { + dictionary[ characterID ].sprite = new (TAG_SWF) idSWFSprite( this ); + } else if ( type == SWF_DICT_FONT ) { + dictionary[ characterID ].font = new (TAG_SWF) idSWFFont; + } else if ( type == SWF_DICT_TEXT ) { + dictionary[ characterID ].text = new (TAG_SWF) idSWFText; + } else if ( type == SWF_DICT_EDITTEXT ) { + dictionary[ characterID ].edittext = new (TAG_SWF) idSWFEditText; + } + + return &dictionary[ characterID ]; +} + +/* +======================== +FindDictionaryEntry +======================== +*/ +idSWFDictionaryEntry * idSWF::FindDictionaryEntry( int characterID, swfDictType_t type ) { + + if ( dictionary.Num() < characterID + 1 ) { + idLib::Warning( "%s: Could not find character %d", filename.c_str(), characterID ); + return NULL; + } + + if ( dictionary[ characterID ].type != type ) { + idLib::Warning( "%s: Character %d is the wrong type", filename.c_str(), characterID ); + return NULL; + } + + return &dictionary[ characterID ]; +} + + +/* +======================== +FindDictionaryEntry +======================== +*/ +idSWFDictionaryEntry * idSWF::FindDictionaryEntry( int characterID ) { + + if ( dictionary.Num() < characterID + 1 ) { + idLib::Warning( "%s: Could not find character %d", filename.c_str(), characterID ); + return NULL; + } + + return &dictionary[ characterID ]; +} diff --git a/neo/swf/SWF_Enums.h b/neo/swf/SWF_Enums.h new file mode 100644 index 00000000..5657c53e --- /dev/null +++ b/neo/swf/SWF_Enums.h @@ -0,0 +1,222 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_ENUMS_H__ +#define __SWF_ENUMS_H__ + +enum swfDictType_t { + SWF_DICT_NULL, + SWF_DICT_IMAGE, + SWF_DICT_SHAPE, + SWF_DICT_MORPH, + SWF_DICT_SPRITE, + SWF_DICT_FONT, + SWF_DICT_TEXT, + SWF_DICT_EDITTEXT +}; +enum swfTag_t { + Tag_End = 0, + Tag_ShowFrame = 1, + Tag_DefineShape = 2, + Tag_PlaceObject = 4, + Tag_RemoveObject = 5, + Tag_DefineBits = 6, + Tag_DefineButton = 7, + Tag_JPEGTables = 8, + Tag_SetBackgroundColor = 9, + Tag_DefineFont = 10, + Tag_DefineText = 11, + Tag_DoAction = 12, + Tag_DefineFontInfo = 13, + Tag_DefineSound = 14, + Tag_StartSound = 15, + Tag_DefineButtonSound = 17, + Tag_SoundStreamHead = 18, + Tag_SoundStreamBlock = 19, + Tag_DefineBitsLossless = 20, + Tag_DefineBitsJPEG2 = 21, + Tag_DefineShape2 = 22, + Tag_DefineButtonCxform = 23, + Tag_Protect = 24, + Tag_PlaceObject2 = 26, + Tag_RemoveObject2 = 28, + Tag_DefineShape3 = 32, + Tag_DefineText2 = 33, + Tag_DefineButton2 = 34, + Tag_DefineBitsJPEG3 = 35, + Tag_DefineBitsLossless2 = 36, + Tag_DefineEditText = 37, + Tag_DefineSprite = 39, + Tag_FrameLabel = 43, + Tag_SoundStreamHead2 = 45, + Tag_DefineMorphShape = 46, + Tag_DefineFont2 = 48, + Tag_ExportAssets = 56, + Tag_ImportAssets = 57, + Tag_EnableDebugger = 58, + Tag_DoInitAction = 59, + Tag_DefineVideoStream = 60, + Tag_VideoFrame = 61, + Tag_DefineFontInfo2 = 62, + Tag_EnableDebugger2 = 64, + Tag_ScriptLimits = 65, + Tag_SetTabIndex = 66, + Tag_FileAttributes = 69, + Tag_PlaceObject3 = 70, + Tag_ImportAssets2 = 71, + Tag_DefineFontAlignZones = 73, + Tag_CSMTextSettings = 74, + Tag_DefineFont3 = 75, + Tag_SymbolClass = 76, + Tag_Metadata = 77, + Tag_DefineScalingGrid = 78, + Tag_DoABC = 82, + Tag_DefineShape4 = 83, + Tag_DefineMorphShape2 = 84, + Tag_DefineSceneAndFrameLabelData = 86, + Tag_DefineBinaryData = 87, + Tag_DefineFontName = 88, + Tag_StartSound2 = 89 +}; +enum swfAction_t { + Action_End = 0, + + // swf 3 + Action_NextFrame = 0x04, + Action_PrevFrame = 0x05, + Action_Play = 0x06, + Action_Stop = 0x07, + Action_ToggleQuality = 0x08, + Action_StopSounds = 0x09, + + Action_GotoFrame = 0x81, + Action_GetURL = 0x83, + Action_WaitForFrame = 0x8A, + Action_SetTarget = 0x8B, + Action_GoToLabel = 0x8C, + + // swf 4 + Action_Add = 0x0A, + Action_Subtract = 0x0B, + Action_Multiply = 0x0C, + Action_Divide = 0x0D, + Action_Equals = 0x0E, + Action_Less = 0x0F, + Action_And = 0x10, + Action_Or = 0x11, + Action_Not = 0x12, + Action_StringEquals = 0x13, + Action_StringLength = 0x14, + Action_StringExtract = 0x15, + Action_Pop = 0x17, + Action_ToInteger = 0x18, + Action_GetVariable = 0x1C, + Action_SetVariable = 0x1D, + Action_SetTarget2 = 0x20, + Action_StringAdd = 0x21, + Action_GetProperty = 0x22, + Action_SetProperty = 0x23, + Action_CloneSprite = 0x24, + Action_RemoveSprite = 0x25, + Action_Trace = 0x26, + Action_StartDrag = 0x27, + Action_EndDrag = 0x28, + Action_StringLess = 0x29, + Action_RandomNumber = 0x30, + Action_MBStringLength = 0x31, + Action_CharToAscii = 0x32, + Action_AsciiToChar = 0x33, + Action_GetTime = 0x34, + Action_MBStringExtract = 0x35, + Action_MBCharToAscii = 0x36, + Action_MBAsciiToChar = 0x37, + + Action_WaitForFrame2 = 0x8D, + Action_Push = 0x96, + Action_Jump = 0x99, + Action_GetURL2 = 0x9A, + Action_If = 0x9D, + Action_Call = 0x9E, + Action_GotoFrame2 = 0x9F, + + // swf 5 + Action_Delete = 0x3A, + Action_Delete2 = 0x3B, + Action_DefineLocal = 0x3C, + Action_CallFunction = 0x3D, + Action_Return = 0x3E, + Action_Modulo = 0x3F, + Action_NewObject = 0x40, + Action_DefineLocal2 = 0x41, + Action_InitArray = 0x42, + Action_InitObject = 0x43, + Action_TypeOf = 0x44, + Action_TargetPath = 0x45, + Action_Enumerate = 0x46, + Action_Add2 = 0x47, + Action_Less2 = 0x48, + Action_Equals2 = 0x49, + Action_ToNumber = 0x4A, + Action_ToString = 0x4B, + Action_PushDuplicate = 0x4C, + Action_StackSwap = 0x4D, + Action_GetMember = 0x4E, + Action_SetMember = 0x4F, + Action_Increment = 0x50, + Action_Decrement = 0x51, + Action_CallMethod = 0x52, + Action_NewMethod = 0x53, + Action_BitAnd = 0x60, + Action_BitOr = 0x61, + Action_BitXor = 0x62, + Action_BitLShift = 0x63, + Action_BitRShift = 0x64, + Action_BitURShift = 0x65, + + Action_StoreRegister = 0x87, + Action_ConstantPool = 0x88, + Action_With = 0x94, + Action_DefineFunction = 0x9B, + + // swf 6 + Action_InstanceOf = 0x54, + Action_Enumerate2 = 0x55, + Action_StrictEquals = 0x66, + Action_Greater = 0x67, + Action_StringGreater = 0x68, + + // swf 7 + Action_Extends = 0x69, + Action_CastOp = 0x2B, + Action_ImplementsOp = 0x2C, + Action_Throw = 0x2A, + Action_Try = 0x8F, + + Action_DefineFunction2 = 0x8E, +}; + +#endif // !__SWF_ENUMS_H__ diff --git a/neo/swf/SWF_Events.cpp b/neo/swf/SWF_Events.cpp new file mode 100644 index 00000000..9e96d219 --- /dev/null +++ b/neo/swf/SWF_Events.cpp @@ -0,0 +1,457 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +=================== +idSWF::HitTest +=================== +*/ +idSWFScriptObject * idSWF::HitTest( idSWFSpriteInstance * spriteInstance, const swfRenderState_t & renderState, int x, int y, idSWFScriptObject * parentObject ) { + + if ( spriteInstance->parent != NULL ) { + swfDisplayEntry_t * thisDisplayEntry = spriteInstance->parent->FindDisplayEntry( spriteInstance->depth ); + if ( thisDisplayEntry->cxf.mul.w + thisDisplayEntry->cxf.add.w < 0.001f ) { + return NULL; + } + } + + if ( !spriteInstance->isVisible ) { + return NULL; + } + + if ( spriteInstance->scriptObject->HasValidProperty( "onRelease" ) + || spriteInstance->scriptObject->HasValidProperty( "onPress" ) + || spriteInstance->scriptObject->HasValidProperty( "onRollOver" ) + || spriteInstance->scriptObject->HasValidProperty( "onRollOut" ) + || spriteInstance->scriptObject->HasValidProperty( "onDrag" ) + ) { + parentObject = spriteInstance->scriptObject; + } + + // rather than returning the first object we find, we actually want to return the last object we find + idSWFScriptObject * returnObject = NULL; + + float xOffset = spriteInstance->xOffset; + float yOffset = spriteInstance->yOffset; + + for ( int i = 0; i < spriteInstance->displayList.Num(); i++ ) { + const swfDisplayEntry_t & display = spriteInstance->displayList[i]; + idSWFDictionaryEntry * entry = FindDictionaryEntry( display.characterID ); + if ( entry == NULL ) { + continue; + } + swfRenderState_t renderState2; + renderState2.matrix = display.matrix.Multiply( renderState.matrix ); + renderState2.ratio = display.ratio; + + if ( entry->type == SWF_DICT_SPRITE ) { + idSWFScriptObject * object = HitTest( display.spriteInstance, renderState2, x, y, parentObject ); + if ( object != NULL && object->Get( "_visible" ).ToBool() ) { + returnObject = object; + } + } else if ( entry->type == SWF_DICT_SHAPE && ( parentObject != NULL ) ) { + idSWFShape * shape = entry->shape; + for ( int i = 0; i < shape->fillDraws.Num(); i++ ) { + const idSWFShapeDrawFill & fill = shape->fillDraws[i]; + for ( int j = 0; j < fill.indices.Num(); j+=3 ) { + idVec2 xy1 = renderState2.matrix.Transform( fill.startVerts[fill.indices[j+0]] ); + idVec2 xy2 = renderState2.matrix.Transform( fill.startVerts[fill.indices[j+1]] ); + idVec2 xy3 = renderState2.matrix.Transform( fill.startVerts[fill.indices[j+2]] ); + + idMat3 edgeEquations; + edgeEquations[0].Set( xy1.x + xOffset, xy1.y + yOffset, 1.0f ); + edgeEquations[1].Set( xy2.x + xOffset, xy2.y + yOffset, 1.0f ); + edgeEquations[2].Set( xy3.x + xOffset, xy3.y + yOffset, 1.0f ); + edgeEquations.InverseSelf(); + + idVec3 p( x, y, 1.0f ); + idVec3 signs = p * edgeEquations; + + bool bx = signs.x > 0; + bool by = signs.y > 0; + bool bz = signs.z > 0; + if ( bx == by && bx == bz ) { + // point inside + returnObject = parentObject; + } + } + } + } else if ( entry->type == SWF_DICT_MORPH ) { + // FIXME: this should be roughly the same as SWF_DICT_SHAPE + } else if ( entry->type == SWF_DICT_TEXT ) { + // FIXME: this should be roughly the same as SWF_DICT_SHAPE + } else if ( entry->type == SWF_DICT_EDITTEXT ) { + idSWFScriptObject * editObject = NULL; + + if ( display.textInstance->scriptObject.HasProperty( "onRelease" ) || display.textInstance->scriptObject.HasProperty( "onPress" ) ) { + // if the edit box itself can be clicked, then we want to return it when it's clicked on + editObject = &display.textInstance->scriptObject; + } else if ( parentObject != NULL ) { + // otherwise, we want to return the parent object + editObject = parentObject; + } + + if ( editObject == NULL ) { + continue; + } + + if ( display.textInstance->text.IsEmpty() ) { + continue; + } + + const idSWFEditText * shape = entry->edittext; + const idSWFEditText * text = display.textInstance->GetEditText(); + float textLength = display.textInstance->GetTextLength(); + + float lengthDiff = fabs( shape->bounds.br.x - shape->bounds.tl.x ) - textLength; + + idVec3 tl; + idVec3 tr; + idVec3 br; + idVec3 bl; + + float xOffset = spriteInstance->xOffset; + float yOffset = spriteInstance->yOffset; + + float topOffset = 0.0f; + + if ( text->align == SWF_ET_ALIGN_LEFT ) { + tl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); + tr.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x - lengthDiff + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); + br.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x - lengthDiff + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); + bl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); + } else if ( text->align == SWF_ET_ALIGN_RIGHT ) { + tl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + lengthDiff + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); + tr.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); + br.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); + bl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + lengthDiff + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); + } else if ( text->align == SWF_ET_ALIGN_CENTER ) { + float middle = ( ( shape->bounds.br.x + xOffset ) + ( shape->bounds.tl.x + xOffset ) ) / 2.0f; + tl.ToVec2() = renderState2.matrix.Transform( idVec2( middle - ( textLength / 2.0f ), shape->bounds.tl.y + topOffset + yOffset ) ); + tr.ToVec2() = renderState2.matrix.Transform( idVec2( middle + ( textLength / 2.0f ), shape->bounds.tl.y + topOffset + yOffset ) ); + br.ToVec2() = renderState2.matrix.Transform( idVec2( middle + ( textLength / 2.0f ), shape->bounds.br.y + topOffset + yOffset ) ); + bl.ToVec2() = renderState2.matrix.Transform( idVec2( middle - ( textLength / 2.0f ), shape->bounds.br.y + topOffset + yOffset ) ); + } else { + tl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); + tr.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.tl.y + topOffset + yOffset ) ); + br.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.br.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); + bl.ToVec2() = renderState2.matrix.Transform( idVec2( shape->bounds.tl.x + xOffset, shape->bounds.br.y + topOffset + yOffset ) ); + } + + tl.z = 1.0f; + tr.z = 1.0f; + br.z = 1.0f; + bl.z = 1.0f; + + idMat3 edgeEquations; + edgeEquations[0] = tl; + edgeEquations[1] = tr; + edgeEquations[2] = br; + edgeEquations.InverseSelf(); + + idVec3 p( x, y, 1.0f ); + idVec3 signs = p * edgeEquations; + + bool bx = signs.x > 0; + bool by = signs.y > 0; + bool bz = signs.z > 0; + if ( bx == by && bx == bz ) { + // point inside top right triangle + returnObject = editObject; + } + + edgeEquations[0] = tl; + edgeEquations[1] = br; + edgeEquations[2] = bl; + edgeEquations.InverseSelf(); + signs = p * edgeEquations; + + bx = signs.x > 0; + by = signs.y > 0; + bz = signs.z > 0; + if ( bx == by && bx == bz ) { + // point inside bottom left triangle + returnObject = editObject; + } + } + } + return returnObject; +} + +/* +=================== +idSWF::HandleEvent +=================== +*/ +bool idSWF::HandleEvent( const sysEvent_t * event ) { + if ( !IsLoaded() || !IsActive() || ( !inhibitControl && useInhibtControl ) ) { + return false; + } + if ( event->evType == SE_KEY ) { + if ( event->evValue == K_MOUSE1 ) { + mouseEnabled = true; + idSWFScriptVar var; + if ( event->evValue2 ) { + + idSWFScriptVar waitInput = globals->Get( "waitInput" ); + if ( waitInput.IsFunction() ) { + useMouse = false; + idSWFParmList waitParms; + waitParms.Append( event->evValue ); + waitInput.GetFunction()->Call( NULL, waitParms ); + waitParms.Clear(); + } else { + useMouse = true; + } + + idSWFScriptObject * hitObject = HitTest( mainspriteInstance, swfRenderState_t(), mouseX, mouseY, NULL ); + if ( hitObject != NULL ) { + mouseObject = hitObject; + mouseObject->AddRef(); + + var = hitObject->Get( "onPress" ); + if ( var.IsFunction() ) { + idSWFParmList parms; + parms.Append( event->inputDevice ); + var.GetFunction()->Call( hitObject, parms ); + parms.Clear(); + return true; + } + + idSWFScriptVar var = hitObject->Get( "onDrag" ); + if ( var.IsFunction() ) { + idSWFParmList parms; + parms.Append( mouseX ); + parms.Append( mouseY ); + parms.Append( true ); + var.GetFunction()->Call( hitObject, parms ); + parms.Clear(); + return true; + } + } + + idSWFParmList parms; + parms.Append( hitObject ); + Invoke( "setHitObject", parms ); + + } else { + if ( mouseObject ) { + var = mouseObject->Get( "onRelease" ); + if ( var.IsFunction() ) { + idSWFParmList parms; + parms.Append( mouseObject ); // FIXME: Remove this + var.GetFunction()->Call( mouseObject, parms ); + } + mouseObject->Release(); + mouseObject = NULL; + } + if ( hoverObject ) { + hoverObject->Release(); + hoverObject = NULL; + } + + if ( var.IsFunction() ) { + return true; + } + } + + return false; + } + const char * keyName = idKeyInput::KeyNumToString( (keyNum_t)event->evValue ); + idSWFScriptVar var = shortcutKeys->Get( keyName ); + // anything more than 32 levels of indirection we can be pretty sure is an infinite loop + for ( int runaway = 0; runaway < 32; runaway++ ) { + idSWFParmList eventParms; + eventParms.Clear(); + eventParms.Append( event->inputDevice ); + if ( var.IsString() ) { + // alias to another key + var = shortcutKeys->Get( var.ToString() ); + continue; + } else if ( var.IsObject() ) { + // if this object is a sprite, send fake mouse events to it + idSWFScriptObject * object = var.GetObject(); + // make sure we don't send an onRelease event unless we have already sent that object an onPress + bool wasPressed = object->Get( "_pressed" ).ToBool(); + object->Set( "_pressed", event->evValue2 ); + if ( event->evValue2 ) { + var = object->Get( "onPress" ); + } else if ( wasPressed ) { + var = object->Get( "onRelease" ); + } + if ( var.IsFunction() ) { + var.GetFunction()->Call( object, eventParms ); + return true; + } + } else if ( var.IsFunction() ) { + if ( event->evValue2 ) { + // anonymous functions only respond to key down events + var.GetFunction()->Call( NULL, eventParms ); + return true; + } + return false; + } + + idSWFScriptVar useFunction = globals->Get( "useFunction" ); + if ( useFunction.IsFunction() && event->evValue2 ) { + const char * action = idKeyInput::GetBinding( event->evValue ); + if ( idStr::Cmp( "_use", action ) == 0 ) { + useFunction.GetFunction()->Call( NULL, idSWFParmList() ); + } + } + + idSWFScriptVar waitInput = globals->Get( "waitInput" ); + if ( waitInput.IsFunction() ) { + useMouse = false; + if ( event->evValue2 ) { + idSWFParmList waitParms; + waitParms.Append( event->evValue ); + waitInput.GetFunction()->Call( NULL, waitParms ); + } + } else { + useMouse = true; + } + + idSWFScriptVar focusWindow = globals->Get( "focusWindow" ); + if ( focusWindow.IsObject() ) { + idSWFScriptVar onKey = focusWindow.GetObject()->Get( "onKey" ); + if ( onKey.IsFunction() ) { + + // make sure we don't send an onRelease event unless we have already sent that object an onPress + idSWFScriptObject * object = focusWindow.GetObject(); + bool wasPressed = object->Get( "_kpressed" ).ToBool(); + object->Set( "_kpressed", event->evValue2 ); + if ( event->evValue2 || wasPressed ) { + idSWFParmList parms; + parms.Append( event->evValue ); + parms.Append( event->evValue2 ); + onKey.GetFunction()->Call( focusWindow.GetObject(), parms ).ToBool(); + return true; + } else if ( event->evValue == K_LSHIFT || event->evValue == K_RSHIFT ) { + idSWFParmList parms; + parms.Append( event->evValue ); + parms.Append( event->evValue2 ); + onKey.GetFunction()->Call( focusWindow.GetObject(), parms ).ToBool(); + } + } + } + return false; + } + idLib::Warning( "Circular reference in %s shortcutKeys.%s", filename.c_str(), keyName ); + } else if ( event->evType == SE_CHAR ) { + idSWFScriptVar focusWindow = globals->Get( "focusWindow" ); + if ( focusWindow.IsObject() ) { + idSWFScriptVar onChar = focusWindow.GetObject()->Get( "onChar" ); + if ( onChar.IsFunction() ) { + idSWFParmList parms; + parms.Append( event->evValue ); + parms.Append( idKeyInput::KeyNumToString( (keyNum_t)event->evValue ) ); + onChar.GetFunction()->Call( focusWindow.GetObject(), parms ).ToBool(); + return true; + } + } + } else if ( event->evType == SE_MOUSE_ABSOLUTE || event->evType == SE_MOUSE ) { + mouseEnabled = true; + isMouseInClientArea = true; + + // Mouse position in screen space needs to be converted to SWF space + if ( event->evType == SE_MOUSE_ABSOLUTE ) { + const float pixelAspect = renderSystem->GetPixelAspect(); + const float sysWidth = renderSystem->GetWidth() * ( pixelAspect > 1.0f ? pixelAspect : 1.0f ); + const float sysHeight = renderSystem->GetHeight() / ( pixelAspect < 1.0f ? pixelAspect : 1.0f ); + float scale = swfScale * sysHeight / (float)frameHeight; + float invScale = 1.0f / scale; + float tx = 0.5f * ( sysWidth - ( frameWidth * scale ) ); + float ty = 0.5f * ( sysHeight - ( frameHeight * scale ) ); + + mouseX = idMath::Ftoi( ( static_cast( event->evValue ) - tx ) * invScale ); + mouseY = idMath::Ftoi( ( static_cast( event->evValue2 ) - ty ) * invScale ); + } else { + + mouseX += event->evValue; + mouseY += event->evValue2; + + mouseX = Max( Min( mouseX, idMath::Ftoi( frameWidth + renderBorder ) ), idMath::Ftoi( 0.0f - renderBorder ) ); + mouseY = Max( Min( mouseY, idMath::Ftoi(frameHeight) ), 0 ); + } + + bool retVal = false; + + idSWFScriptObject * hitObject = HitTest( mainspriteInstance, swfRenderState_t(), mouseX, mouseY, NULL ); + if ( hitObject != NULL ) { + hasHitObject = true; + } else { + hasHitObject = false; + } + + if ( hitObject != hoverObject ) { + // First check to see if we should call onRollOut on our previous hoverObject + if ( hoverObject != NULL ) { + idSWFScriptVar var = hoverObject->Get( "onRollOut" ); + if ( var.IsFunction() ) { + var.GetFunction()->Call( hoverObject, idSWFParmList() ); + retVal = true; + } + hoverObject->Release(); + hoverObject = NULL; + } + // Then call onRollOver on our hitObject + if ( hitObject != NULL ) { + hoverObject = hitObject; + hoverObject->AddRef(); + idSWFScriptVar var = hitObject->Get( "onRollOver" ); + if ( var.IsFunction() ) { + var.GetFunction()->Call( hitObject, idSWFParmList() ); + retVal = true; + } + } + } + if ( mouseObject != NULL ) { + idSWFScriptVar var = mouseObject->Get( "onDrag" ); + if ( var.IsFunction() ) { + idSWFParmList parms; + parms.Append( mouseX ); + parms.Append( mouseY ); + parms.Append( false ); + var.GetFunction()->Call( mouseObject, parms ); + return true; + } + } + return retVal; + } else if ( event->evType == SE_MOUSE_LEAVE ) { + isMouseInClientArea = false; + } else if ( event->evType == SE_JOYSTICK ) { + idSWFParmList parms; + parms.Append( event->evValue ); + parms.Append( event->evValue2 / 32.0f ); + Invoke( "onJoystick", parms ); + } + return false; +} diff --git a/neo/swf/SWF_Image.cpp b/neo/swf/SWF_Image.cpp new file mode 100644 index 00000000..0365b23b --- /dev/null +++ b/neo/swf/SWF_Image.cpp @@ -0,0 +1,575 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../renderer/Image.h" +//#include "../../renderer/ImageTools/ImageProcess.h" +#include "../renderer/jpeg-6/jpeglib.h" + +/* +======================== +idSWF::idDecompressJPEG +These are the static callback functions the jpeg library calls +======================== +*/ +void swf_jpeg_error_exit( jpeg_common_struct * cinfo ) { + char buffer[JMSG_LENGTH_MAX] = {0}; + (*cinfo->err->format_message)( cinfo, buffer ); + throw idException( buffer ); +} +void swf_jpeg_output_message( jpeg_common_struct * cinfo ) { + char buffer[JMSG_LENGTH_MAX] = {0}; + (*cinfo->err->format_message)( cinfo, buffer ); + idLib::Printf( "%s\n", buffer ); +} +void swf_jpeg_init_source( jpeg_decompress_struct * cinfo ) { +} +boolean swf_jpeg_fill_input_buffer( jpeg_decompress_struct * cinfo ) { + return TRUE; +} +void swf_jpeg_skip_input_data( jpeg_decompress_struct * cinfo, long num_bytes ) { + cinfo->src->next_input_byte += num_bytes; + cinfo->src->bytes_in_buffer -= num_bytes; +} +void swf_jpeg_term_source( jpeg_decompress_struct * cinfo ) { +} + +/* +======================== +idSWF::idDecompressJPEG::idDecompressJPEG +======================== +*/ +idSWF::idDecompressJPEG::idDecompressJPEG() { + jpeg_decompress_struct * cinfo = new (TAG_SWF) jpeg_decompress_struct; + memset( cinfo, 0, sizeof( *cinfo ) ); + + cinfo->err = new (TAG_SWF) jpeg_error_mgr; + memset( cinfo->err, 0, sizeof( jpeg_error_mgr ) ); + jpeg_std_error( cinfo->err ); + cinfo->err->error_exit = swf_jpeg_error_exit; + cinfo->err->output_message = swf_jpeg_output_message; + + jpeg_create_decompress( cinfo ); + + vinfo = cinfo; +} + +/* +======================== +idSWF::idDecompressJPEG::~idDecompressJPEG +======================== +*/ +idSWF::idDecompressJPEG::~idDecompressJPEG() { + jpeg_decompress_struct * cinfo = (jpeg_decompress_struct *)vinfo; + + jpeg_destroy_decompress( cinfo ); + delete cinfo->err; + delete cinfo; +} + +/* +======================== +idSWF::idDecompressJPEG::Load +======================== +*/ +byte * idSWF::idDecompressJPEG::Load( const byte * input, int inputSize, int & width, int & height ) { + jpeg_decompress_struct * cinfo = (jpeg_decompress_struct *)vinfo; + + try { + + width = 0; + height = 0; + + jpeg_source_mgr src; + memset( &src, 0, sizeof( src ) ); + src.next_input_byte = (JOCTET *)input; + src.bytes_in_buffer = inputSize; + src.init_source = swf_jpeg_init_source; + src.fill_input_buffer = swf_jpeg_fill_input_buffer; + src.skip_input_data = swf_jpeg_skip_input_data; + src.resync_to_restart = jpeg_resync_to_restart; + src.term_source = swf_jpeg_term_source; + cinfo->src = &src; + + int result = 0; + do { + result = jpeg_read_header( cinfo, FALSE ); + } while ( result == JPEG_HEADER_TABLES_ONLY ); + + if ( result == JPEG_SUSPENDED ) { + return NULL; + } + + jpeg_start_decompress( cinfo ); + if ( cinfo->output_components != 4 ) { + // This shouldn't really be possible, unless the source image is some kind of strange grayscale format or something + idLib::Warning( "JPEG output is not 4 components" ); + jpeg_abort_decompress( cinfo ); + cinfo->src = NULL; // value goes out of scope + return NULL; + } + int outputSize = cinfo->output_width * cinfo->output_height * cinfo->output_components; + byte * output = (byte *)Mem_Alloc( outputSize, TAG_SWF ); + memset( output, 255, outputSize ); + while ( cinfo->output_scanline < cinfo->output_height ) { + JSAMPROW scanlines = output + cinfo->output_scanline * cinfo->output_width * cinfo->output_components; + jpeg_read_scanlines( cinfo, &scanlines, 1 ); + } + jpeg_finish_decompress( cinfo ); + + width = cinfo->output_width; + height = cinfo->output_height; + + cinfo->src = NULL; // value goes out of scope + return output; + + } catch ( idException & ) { + swf_jpeg_output_message( (jpeg_common_struct *)cinfo ); + return NULL; + } +} + + +/* +======================== +idSWF::WriteSwfImageAtlas + +Now that all images have been found, allocate them in an atlas +and write it out. +======================== +*/ +void RectAllocator( const idList &inputSizes, idList &outputPositions, idVec2i &totalSize ); +float RectPackingFraction( const idList &inputSizes, const idVec2i totalSize ); + +void idSWF::WriteSwfImageAtlas( const char *filename ) { + idList inputSizes; + inputSizes.SetNum( packImages.Num() ); + for ( int i = 0 ; i < packImages.Num() ; i++ ) { + // these are in DXT blocks, not pixels + inputSizes[i] = packImages[i].allocSize; + } + + idList outputPositions; + idVec2i totalSize; + // smart allocator + RectAllocator( inputSizes, outputPositions, totalSize ); + + float frac = RectPackingFraction( inputSizes, totalSize ); + idLib::Printf( "%5.2f packing fraction in %ix%i image\n", frac, totalSize.x*4, totalSize.y*4 ); + + int atlasWidth = Max( 4, totalSize.x * 4 ) ; + int atlasHeight = Max( 4, totalSize.y * 4 ) ; + + // we require multiple-of-128 widths to use the image data directly + // without re-packing on the 360 and PS3. The growth checks in RectAllocator() + // will always align, but a single image won't necessarily be. + atlasWidth = ( atlasWidth + 127 ) & ~127; + + idTempArray swfAtlas( atlasWidth * atlasHeight * 4 ); + + // fill everything with solid red + for ( int i = 0; i < atlasWidth * atlasHeight; i++ ) { + swfAtlas[i*4+0] = 255; + swfAtlas[i*4+1] = 0; + swfAtlas[i*4+2] = 0; + swfAtlas[i*4+3] = 255; + } + + // allocate the blocks and copy the texels + for ( int i = 0 ; i < packImages.Num() ; i++ ) { + imageToPack_t & pack = packImages[i]; + assert( pack.imageData != NULL ); + + int blockWidth = pack.allocSize.x; + int blockHeight = pack.allocSize.y; + + int x = outputPositions[i].x; + int y = outputPositions[i].y; + + // get the range for each channel so we can maximize it + // for better compression + int minV[4] = { 255, 255, 255, 255 }; + int maxV[4] = { 0, 0, 0, 0 }; + for ( int j = 0 ; j < pack.trueSize.x * pack.trueSize.y * 4 ; j++ ) { + int v = pack.imageData[ j ]; + int x = j & 3; + if ( v < minV[x] ) { + minV[x] = v; + } + if ( v > maxV[x] ) { + maxV[x] = v; + } + } +// idLib::Printf( "Color normalize: %3i:%3i %3i:%3i %3i:%3i %3i:%3i\n", +// minV[0], maxV[0], minV[1], maxV[1], minV[2], maxV[2], minV[3], maxV[3] ); + + // don't divide by zero + for ( int x = 0 ; x < 4 ; x++ ) { + if ( maxV[x] == 0 ) { + maxV[x] = 1; + } + } + // rescale the image + // + // Note that this must be done in RGBA space, before YCoCg conversion, + // or the scale factors couldn't be combined with the normal swf coloring. + // + // If we create an idMaterial for each atlas element, we could add + // a bias as well as a scale to enable us to take advantage of the + // min values as well as the max, but very few gui images don't go to black, + // and just doing a scale avoids changing more code. + for ( int j = 0; j < pack.trueSize.x * pack.trueSize.y * 4; j++ ) { + int v = pack.imageData[ j ]; + int x = j & 3; + v = v * 255 / maxV[x]; + pack.imageData[ j ] = v; + } + + assert( ( x + blockWidth )* 4 <= atlasWidth ); + assert( ( y + blockHeight )* 4 <= atlasHeight ); + // Extend the pixels with clamp-to-edge to the edge of the allocation block. + // The GPU hardware addressing should completely ignore texels outside the true block + // size, but the compressor works on complete blocks, regardless of the true rect size. + x <<= 2; + y <<= 2; + for ( int dstY = 0; dstY < blockHeight<<2; dstY++ ) { + int srcY = dstY-1; + if ( srcY < 0 ) { + srcY = 0; + } + if ( srcY >= pack.trueSize.y ) { + srcY = pack.trueSize.y - 1; + } + for ( int dstX = 0 ; dstX < blockWidth<<2 ; dstX++ ) { + int srcX = dstX-1; + if ( srcX < 0 ) { + srcX = 0; + } + if ( srcX >= pack.trueSize.x ) { + srcX = pack.trueSize.x - 1; + } + ((int *)swfAtlas.Ptr())[ (y+dstY) * atlasWidth + (x+dstX) ] = + ((int *)pack.imageData)[ srcY * pack.trueSize.x + srcX ]; + } + } + + // save the information in the SWF dictionary + idSWFDictionaryEntry * entry = FindDictionaryEntry( pack.characterID ); + assert( entry->material == NULL ); + entry->imageSize.x = pack.trueSize.x; + entry->imageSize.y = pack.trueSize.y; + entry->imageAtlasOffset.x = x + 1; + entry->imageAtlasOffset.y = y + 1; + for ( int i = 0; i < 4; i++ ) { + entry->channelScale[i] = maxV[i] / 255.0f; + } + + Mem_Free( pack.imageData ); + pack.imageData = NULL; + } + + // the TGA is only for examination during development + R_WriteTGA( filename, swfAtlas.Ptr(), atlasWidth, atlasHeight, false, "fs_basepath" ); +} + +/* +======================== +idSWF::LoadImage +Loads RGBA data into an image at the specificied character id in the dictionary +======================== +*/ +void idSWF::LoadImage( int characterID, const byte * imageData, int width, int height ) { + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_IMAGE ); + if ( entry == NULL ) { + return; + } + + // save the data off so we can do the image atlas allocation after we have collected + // all the images that are used by the entire swf + imageToPack_t pack; + pack.characterID = characterID; + pack.imageData = (byte *)Mem_Alloc( width*height*4, TAG_SWF ); + memcpy( pack.imageData, imageData, width*height*4 ); + pack.trueSize.x = width; + pack.trueSize.y = height; + for ( int i = 0 ; i < 2 ; i++ ) { + int v = pack.trueSize[i]; + // Swf images are usually completely random in size, but perform all allocations in + // DXT blocks of 4. If we choose to DCT / HDP encode the image block, we should probably + // increae the block size to 8 or 16 to prevent neighbor effects. + v = ( v + 3 ) >> 2; + + // Allways allocate a single pixel border around the images so there won't be any edge + // bleeds. This can often be hidden in in the round-up to DXT size. + if ( ( v << 2 ) - pack.trueSize[i] < 2 ) { + v++; + } + pack.allocSize[i] = v; + } + packImages.Append( pack ); + + entry->material = NULL; +} + +/* +======================== +idSWF::JPEGTables +Reads jpeg table data, there can only be one of these in the file, and it has to come before any DefineBits tags +We don't have to worry about clearing the jpeg object because jpeglib will automagically overwrite any tables that are already set (I think?) +======================== +*/ +void idSWF::JPEGTables( idSWFBitStream & bitstream ) { + if ( bitstream.Length() == 0 ) { + // no clue why this happens + return; + } + int width, height; + jpeg.Load( bitstream.ReadData( bitstream.Length() ), bitstream.Length(), width, height ); +} + +/* +======================== +idSWF::DefineBits +Reads a partial jpeg image, using the tables set by the JPEGTables tag +======================== +*/ +void idSWF::DefineBits( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + + int jpegSize = bitstream.Length() - sizeof( uint16 ); + + int width, height; + byte * imageData = jpeg.Load( bitstream.ReadData( jpegSize ), jpegSize, width, height ); + if ( imageData == NULL ) { + return; + } + + LoadImage( characterID, imageData, width, height ); + + Mem_Free( imageData ); +} + +/* +======================== +idSWF::DefineBitsJPEG2 +Identical to DefineBits, except it uses a local JPEG table (not the one defined by JPEGTables) +======================== +*/ +void idSWF::DefineBitsJPEG2( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + + idDecompressJPEG jpeg; + + int jpegSize = bitstream.Length() - sizeof( uint16 ); + + int width, height; + byte * imageData = jpeg.Load( bitstream.ReadData( jpegSize ), jpegSize, width, height ); + if ( imageData == NULL ) { + return; + } + + LoadImage( characterID, imageData, width, height ); + + Mem_Free( imageData ); +} + +/* +======================== +idSWF::DefineBitsJPEG3 +Mostly identical to DefineBitsJPEG2, except it has an additional zlib compressed alpha map +======================== +*/ +void idSWF::DefineBitsJPEG3( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + uint32 jpegSize = bitstream.ReadU32(); + + idDecompressJPEG jpeg; + + int width, height; + byte * imageData = jpeg.Load( bitstream.ReadData( jpegSize ), jpegSize, width, height ); + if ( imageData == NULL ) { + return; + } + + { + idTempArray alphaMap( width * height ); + + int alphaSize = bitstream.Length() - jpegSize - sizeof( characterID ) - sizeof( jpegSize ); + if ( !Inflate( bitstream.ReadData( alphaSize ), alphaSize, alphaMap.Ptr(), (int)alphaMap.Size() ) ) { + idLib::Warning( "DefineBitsJPEG3: Failed to inflate alpha data" ); + Mem_Free( imageData ); + return; + } + for ( int i = 0; i < width * height; i++ ) { + imageData[i*4+3] = alphaMap[i]; + } + } + + LoadImage( characterID, imageData, width, height ); + + Mem_Free( imageData ); +} + +/* +======================== +idSWF::DefineBitsLossless +======================== +*/ +void idSWF::DefineBitsLossless( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + uint8 format = bitstream.ReadU8(); + uint16 width = bitstream.ReadU16(); + uint16 height = bitstream.ReadU16(); + + idTempArray< byte > buf( width * height * 4 ); + byte * imageData = buf.Ptr(); + + if ( format == 3 ) { + uint32 paddedWidth = ( width + 3 ) & ~3; + uint32 colorTableSize = ( bitstream.ReadU8() + 1 ) * 3; + idTempArray colorMapData( colorTableSize + ( paddedWidth * height ) ); + uint32 colorDataSize = bitstream.Length() - bitstream.Tell(); + if ( !Inflate( bitstream.ReadData( colorDataSize ), colorDataSize, colorMapData.Ptr(), (int)colorMapData.Size() ) ) { + idLib::Warning( "DefineBitsLossless: Failed to inflate color map data" ); + return; + } + byte * indices = colorMapData.Ptr() + colorTableSize; + for ( int h = 0; h < height; h++ ) { + for ( int w = 0; w < width; w++ ) { + byte index = indices[w + (h*paddedWidth)]; + byte * pixel = &imageData[(w + (h*width)) * 4]; + pixel[0] = colorMapData[index * 3 + 0]; + pixel[1] = colorMapData[index * 3 + 1]; + pixel[2] = colorMapData[index * 3 + 2]; + pixel[3] = 0xFF; + } + } + } else if ( format == 4 ) { + uint32 paddedWidth = ( width + 1 ) & 1; + idTempArray bitmapData( paddedWidth * height * 2 ); + uint32 colorDataSize = bitstream.Length() - bitstream.Tell(); + if ( !Inflate( bitstream.ReadData( colorDataSize ), colorDataSize, (byte *)bitmapData.Ptr(), (int)bitmapData.Size() ) ) { + idLib::Warning( "DefineBitsLossless: Failed to inflate bitmap data" ); + return; + } + for ( int h = 0; h < height; h++ ) { + for ( int w = 0; w < width; w++ ) { + uint16 pix15 = bitmapData[w + (h*paddedWidth)]; + idSwap::Big( pix15 ); + byte * pixel = &imageData[(w + (h*width)) * 4]; + pixel[0] = ( pix15 >> 10 ) & 0x1F; + pixel[1] = ( pix15 >> 5 ) & 0x1F; + pixel[2] = ( pix15 >> 0 ) & 0x1F; + pixel[3] = 0xFF; + } + } + } else if ( format == 5 ) { + idTempArray bitmapData( width * height ); + uint32 colorDataSize = bitstream.Length() - bitstream.Tell(); + if ( !Inflate( bitstream.ReadData( colorDataSize ), colorDataSize, (byte *)bitmapData.Ptr(), (int)bitmapData.Size() ) ) { + idLib::Warning( "DefineBitsLossless: Failed to inflate bitmap data" ); + return; + } + for ( int h = 0; h < height; h++ ) { + for ( int w = 0; w < width; w++ ) { + uint32 pix24 = bitmapData[w + (h*width)]; + idSwap::Big( pix24 ); + byte * pixel = &imageData[(w + (h*width)) * 4]; + pixel[0] = ( pix24 >> 16 ) & 0xFF; + pixel[1] = ( pix24 >> 8 ) & 0xFF; + pixel[2] = ( pix24 >> 0 ) & 0xFF; + pixel[3] = 0xFF; + } + } + } else { + idLib::Warning( "DefineBitsLossless: Unknown image format %d", format ); + memset( imageData, 0xFF, width * height * 4 ); + } + + LoadImage( characterID, imageData, width, height ); +} + +/* +======================== +idSWF::DefineBitsLossless2 +======================== +*/ +void idSWF::DefineBitsLossless2( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + uint8 format = bitstream.ReadU8(); + uint16 width = bitstream.ReadU16(); + uint16 height = bitstream.ReadU16(); + + idTempArray< byte > buf( width * height * 4 ); + byte * imageData = buf.Ptr(); + + if ( format == 3 ) { + uint32 paddedWidth = ( width + 3 ) & ~3; + uint32 colorTableSize = ( bitstream.ReadU8() + 1 ) * 4; + idTempArray colorMapData( colorTableSize + ( paddedWidth * height ) ); + uint32 colorDataSize = bitstream.Length() - bitstream.Tell(); + if ( !Inflate( bitstream.ReadData( colorDataSize ), colorDataSize, colorMapData.Ptr(), (int)colorMapData.Size() ) ) { + idLib::Warning( "DefineBitsLossless2: Failed to inflate color map data" ); + return; + } + byte * indices = colorMapData.Ptr() + colorTableSize; + for ( int h = 0; h < height; h++ ) { + for ( int w = 0; w < width; w++ ) { + byte index = indices[w + (h*paddedWidth)]; + byte * pixel = &imageData[(w + (h*width)) * 4]; + pixel[0] = colorMapData[index * 4 + 0]; + pixel[1] = colorMapData[index * 4 + 1]; + pixel[2] = colorMapData[index * 4 + 2]; + pixel[3] = colorMapData[index * 4 + 3]; + } + } + } else if ( format == 5 ) { + idTempArray bitmapData( width * height ); + uint32 colorDataSize = bitstream.Length() - bitstream.Tell(); + if ( !Inflate( bitstream.ReadData( colorDataSize ), colorDataSize, (byte *)bitmapData.Ptr(), (int)bitmapData.Size() ) ) { + idLib::Warning( "DefineBitsLossless2: Failed to inflate bitmap data" ); + return; + } + for ( int h = 0; h < height; h++ ) { + for ( int w = 0; w < width; w++ ) { + uint32 pix32 = bitmapData[w + (h*width)]; + idSwap::Big( pix32 ); + byte * pixel = &imageData[(w + (h*width)) * 4]; + pixel[0] = ( pix32 >> 16 ) & 0xFF; + pixel[1] = ( pix32 >> 8 ) & 0xFF; + pixel[2] = ( pix32 >> 0 ) & 0xFF; + pixel[3] = ( pix32 >> 24 ) & 0xFF; + } + } + } else { + idLib::Warning( "DefineBitsLossless2: Unknown image format %d", format ); + memset( imageData, 0xFF, width * height * 4 ); + } + + LoadImage( characterID, imageData, width, height ); +} diff --git a/neo/swf/SWF_Load.cpp b/neo/swf/SWF_Load.cpp new file mode 100644 index 00000000..6c199be7 --- /dev/null +++ b/neo/swf/SWF_Load.cpp @@ -0,0 +1,458 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../renderer/Font.h" + +#pragma warning(disable: 4355) // 'this' : used in base member initializer list + +#define BSWF_VERSION 16 // bumped to 16 for storing atlas image dimensions for unbuffered loads +#define BSWF_MAGIC ( ( 'B' << 24 ) | ( 'S' << 16 ) | ( 'W' << 8 ) | BSWF_VERSION ) + +/* +=================== +idSWF::LoadSWF +=================== +*/ +bool idSWF::LoadSWF( const char * fullpath ) { + + idFile * rawfile = fileSystem->OpenFileRead( fullpath ); + if ( rawfile == NULL ) { + idLib::Printf( "SWF File not found %s\n", fullpath ); + return false; + } + + swfHeader_t header; + rawfile->Read( &header, sizeof( header ) ); + + if ( header.W != 'W' || header.S != 'S' ) { + idLib::Warning( "Wrong signature bytes" ); + delete rawfile; + return false; + } + + if ( header.version > 9 ) { + idLib::Warning( "Unsupported version %d", header.version ); + delete rawfile; + return false; + } + + bool compressed; + if ( header.compression == 'F' ) { + compressed = false; + } else if ( header.compression == 'C' ) { + compressed = true; + } else { + idLib::Warning( "Unsupported compression type %c", header.compression ); + delete rawfile; + return false; + } + idSwap::Little( header.fileLength ); + + // header.fileLength somewhat annoyingly includes the size of the header + uint32 fileLength2 = header.fileLength - (uint32)sizeof( swfHeader_t ); + + // slurp the raw file into a giant array, which is somewhat atrocious when loading from the preload since it's already an idFile_Memory + byte * fileData = (byte *)Mem_Alloc( fileLength2, TAG_SWF ); + size_t fileSize = rawfile->Read( fileData, fileLength2 ); + delete rawfile; + + if ( compressed ) { + byte * uncompressed = (byte *)Mem_Alloc( fileLength2, TAG_SWF ); + if ( !Inflate( fileData, (int)fileSize, uncompressed, fileLength2 ) ) { + idLib::Warning( "Inflate error" ); + Mem_Free( uncompressed ); + return false; + } + Mem_Free( fileData ); + fileData = uncompressed; + } + idSWFBitStream bitstream( fileData, fileLength2, false ); + + swfRect_t frameSize; + bitstream.ReadRect( frameSize ); + + if ( !frameSize.tl.Compare( vec2_zero ) ) { + idLib::Warning( "Invalid frameSize top left" ); + Mem_Free( fileData ); + return false; + } + + frameWidth = frameSize.br.x; + frameHeight = frameSize.br.y; + frameRate = bitstream.ReadU16(); + + // parse everything + mainsprite->Load( bitstream, true ); + + // now that all images have been loaded, write out the combined image + idStr atlasFileName = "generated/"; + atlasFileName += fullpath; + atlasFileName.SetFileExtension( ".tga" ); + + WriteSwfImageAtlas( atlasFileName ); + + Mem_Free( fileData ); + + return true; +} + +/* +=================== +idSWF::LoadBinary +=================== +*/ +bool idSWF::LoadBinary( const char * bfilename, ID_TIME_T sourceTime ) { + idFile * f = fileSystem->OpenFileReadMemory( bfilename ); + if ( f == NULL || f->Length() <= 0 ) { + return false; + } + + uint32 magic = 0; + ID_TIME_T btimestamp = 0; + f->ReadBig( magic ); + f->ReadBig( btimestamp ); + + if ( magic != BSWF_MAGIC || ( !fileSystem->InProductionMode() && sourceTime != btimestamp ) ) { + delete f; + return false; + } + + f->ReadBig( frameWidth ); + f->ReadBig( frameHeight ); + f->ReadBig( frameRate ); + + if ( mouseX == -1 ) { + mouseX = ( frameWidth / 2 ); + } + + if ( mouseY == -1 ) { + mouseY = ( frameHeight / 2 ); + } + + mainsprite->Read( f ); + + int num = 0; + f->ReadBig( num ); + dictionary.SetNum( num ); + for ( int i = 0; i < dictionary.Num(); i++ ) { + f->ReadBig( dictionary[i].type ); + switch ( dictionary[i].type ) { + case SWF_DICT_IMAGE: { + idStr imageName; + f->ReadString( imageName ); + if ( imageName[0] == '.' ) { + // internal image in the atlas + dictionary[i].material = NULL; + } else { + dictionary[i].material = declManager->FindMaterial( imageName ); + } + for ( int j = 0 ; j < 2 ; j++ ) { + f->ReadBig( dictionary[i].imageSize[j] ); + f->ReadBig( dictionary[i].imageAtlasOffset[j] ); + } + for ( int j = 0 ; j < 4 ; j++ ) { + f->ReadBig( dictionary[i].channelScale[j] ); + } + break; + } + case SWF_DICT_MORPH: + case SWF_DICT_SHAPE: { + dictionary[i].shape = new (TAG_SWF) idSWFShape; + idSWFShape * shape = dictionary[i].shape; + f->ReadBig( shape->startBounds.tl ); + f->ReadBig( shape->startBounds.br ); + f->ReadBig( shape->endBounds.tl ); + f->ReadBig( shape->endBounds.br ); + f->ReadBig( num ); shape->fillDraws.SetNum( num ); + for ( int d = 0; d < shape->fillDraws.Num(); d++ ) { + idSWFShapeDrawFill & fillDraw = shape->fillDraws[d]; + f->ReadBig( fillDraw.style.type ); + f->ReadBig( fillDraw.style.subType ); + f->Read( &fillDraw.style.startColor, 4 ); + f->Read( &fillDraw.style.endColor, 4 ); + f->ReadBigArray( (float *)&fillDraw.style.startMatrix, 6 ); + f->ReadBigArray( (float *)&fillDraw.style.endMatrix, 6 ); + f->ReadBig( fillDraw.style.gradient.numGradients ); + for ( int g = 0; g < fillDraw.style.gradient.numGradients; g++ ) { + f->ReadBig( fillDraw.style.gradient.gradientRecords[g].startRatio ); + f->ReadBig( fillDraw.style.gradient.gradientRecords[g].endRatio ); + f->Read( &fillDraw.style.gradient.gradientRecords[g].startColor, 4 ); + f->Read( &fillDraw.style.gradient.gradientRecords[g].endColor, 4 ); + } + f->ReadBig( fillDraw.style.focalPoint ); + f->ReadBig( fillDraw.style.bitmapID ); + f->ReadBig( num ); fillDraw.startVerts.SetNum( num ); + f->ReadBigArray( fillDraw.startVerts.Ptr(), fillDraw.startVerts.Num() ); + f->ReadBig( num ); fillDraw.endVerts.SetNum( num ); + f->ReadBigArray( fillDraw.endVerts.Ptr(), fillDraw.endVerts.Num() ); + f->ReadBig( num ); fillDraw.indices.SetNum( num ); + f->ReadBigArray( fillDraw.indices.Ptr(), fillDraw.indices.Num() ); + } + f->ReadBig( num ); shape->lineDraws.SetNum( num ); + for ( int d = 0; d < shape->lineDraws.Num(); d++ ) { + idSWFShapeDrawLine & lineDraw = shape->lineDraws[d]; + f->ReadBig( lineDraw.style.startWidth ); + f->ReadBig( lineDraw.style.endWidth ); + f->Read( &lineDraw.style.startColor, 4 ); + f->Read( &lineDraw.style.endColor, 4 ); + f->ReadBig( num ); lineDraw.startVerts.SetNum( num ); + f->ReadBigArray( lineDraw.startVerts.Ptr(), lineDraw.startVerts.Num() ); + f->ReadBig( num ); lineDraw.endVerts.SetNum( num ); + f->ReadBigArray( lineDraw.endVerts.Ptr(), lineDraw.endVerts.Num() ); + f->ReadBig( num ); lineDraw.indices.SetNum( num ); + f->ReadBigArray( lineDraw.indices.Ptr(), lineDraw.indices.Num() ); + } + break; + } + case SWF_DICT_SPRITE: { + dictionary[i].sprite = new (TAG_SWF) idSWFSprite( this ); + dictionary[i].sprite->Read( f ); + break; + } + case SWF_DICT_FONT: { + dictionary[i].font = new (TAG_SWF) idSWFFont; + idSWFFont * font = dictionary[i].font; + idStr fontName; + f->ReadString( fontName ); + font->fontID = renderSystem->RegisterFont( fontName ); + f->ReadBig( font->ascent ); + f->ReadBig( font->descent ); + f->ReadBig( font->leading ); + f->ReadBig( num ); font->glyphs.SetNum( num ); + for ( int g = 0; g < font->glyphs.Num(); g++ ) { + f->ReadBig( font->glyphs[g].code ); + f->ReadBig( font->glyphs[g].advance ); + f->ReadBig( num ); font->glyphs[g].verts.SetNum( num ); + f->ReadBigArray( font->glyphs[g].verts.Ptr(), font->glyphs[g].verts.Num() ); + f->ReadBig( num ); font->glyphs[g].indices.SetNum( num ); + f->ReadBigArray( font->glyphs[g].indices.Ptr(), font->glyphs[g].indices.Num() ); + } + break; + } + case SWF_DICT_TEXT: { + dictionary[i].text = new (TAG_SWF) idSWFText; + idSWFText * text = dictionary[i].text; + f->ReadBig( text->bounds.tl ); + f->ReadBig( text->bounds.br ); + f->ReadBigArray( (float *)&text->matrix, 6 ); + f->ReadBig( num ); text->textRecords.SetNum( num ); + for ( int t = 0; t < text->textRecords.Num(); t++ ) { + idSWFTextRecord & textRecord = text->textRecords[t]; + f->ReadBig( textRecord.fontID ); + f->Read( &textRecord.color, 4 ); + f->ReadBig( textRecord.xOffset ); + f->ReadBig( textRecord.yOffset ); + f->ReadBig( textRecord.textHeight ); + f->ReadBig( textRecord.firstGlyph ); + f->ReadBig( textRecord.numGlyphs ); + } + f->ReadBig( num ); text->glyphs.SetNum( num ); + for ( int g = 0; g < text->glyphs.Num(); g++ ) { + f->ReadBig( text->glyphs[g].index ); + f->ReadBig( text->glyphs[g].advance ); + } + break; + } + case SWF_DICT_EDITTEXT: { + dictionary[i].edittext = new (TAG_SWF) idSWFEditText; + idSWFEditText * edittext = dictionary[i].edittext; + f->ReadBig( edittext->bounds.tl ); + f->ReadBig( edittext->bounds.br ); + f->ReadBig( edittext->flags ); + f->ReadBig( edittext->fontID ); + f->ReadBig( edittext->fontHeight ); + f->Read( &edittext->color, 4 ); + f->ReadBig( edittext->maxLength ); + f->ReadBig( edittext->align ); + f->ReadBig( edittext->leftMargin ); + f->ReadBig( edittext->rightMargin ); + f->ReadBig( edittext->indent ); + f->ReadBig( edittext->leading ); + f->ReadString( edittext->variable ); + f->ReadString( edittext->initialText ); + break; + } + } + } + delete f; + + return true; +} + +/* +=================== +idSWF::WriteBinary +=================== +*/ +void idSWF::WriteBinary( const char * bfilename ) { + idFileLocal file( fileSystem->OpenFileWrite( bfilename, "fs_basepath" ) ); + if ( file == NULL ) { + return; + } + file->WriteBig( BSWF_MAGIC ); + file->WriteBig( timestamp ); + + file->WriteBig( frameWidth ); + file->WriteBig( frameHeight ); + file->WriteBig( frameRate ); + + mainsprite->Write( file ); + + file->WriteBig( dictionary.Num() ); + for ( int i = 0; i < dictionary.Num(); i++ ) { + file->WriteBig( dictionary[i].type ); + switch ( dictionary[i].type ) { + case SWF_DICT_IMAGE: { + if ( dictionary[i].material ) { + file->WriteString( dictionary[i].material->GetName() ); + } else { + file->WriteString( "." ); + } + for ( int j = 0 ; j < 2 ; j++ ) { + file->WriteBig( dictionary[i].imageSize[j] ); + file->WriteBig( dictionary[i].imageAtlasOffset[j] ); + } + for ( int j = 0 ; j < 4 ; j++ ) { + file->WriteBig( dictionary[i].channelScale[j] ); + } + break; + } + case SWF_DICT_MORPH: + case SWF_DICT_SHAPE: { + idSWFShape * shape = dictionary[i].shape; + file->WriteBig( shape->startBounds.tl ); + file->WriteBig( shape->startBounds.br ); + file->WriteBig( shape->endBounds.tl ); + file->WriteBig( shape->endBounds.br ); + file->WriteBig( shape->fillDraws.Num() ); + for ( int d = 0; d < shape->fillDraws.Num(); d++ ) { + idSWFShapeDrawFill & fillDraw = shape->fillDraws[d]; + file->WriteBig( fillDraw.style.type ); + file->WriteBig( fillDraw.style.subType ); + file->Write( &fillDraw.style.startColor, 4 ); + file->Write( &fillDraw.style.endColor, 4 ); + file->WriteBigArray( (float *)&fillDraw.style.startMatrix, 6 ); + file->WriteBigArray( (float *)&fillDraw.style.endMatrix, 6 ); + file->WriteBig( fillDraw.style.gradient.numGradients ); + for ( int g = 0; g < fillDraw.style.gradient.numGradients; g++ ) { + file->WriteBig( fillDraw.style.gradient.gradientRecords[g].startRatio ); + file->WriteBig( fillDraw.style.gradient.gradientRecords[g].endRatio ); + file->Write( &fillDraw.style.gradient.gradientRecords[g].startColor, 4 ); + file->Write( &fillDraw.style.gradient.gradientRecords[g].endColor, 4 ); + } + file->WriteBig( fillDraw.style.focalPoint ); + file->WriteBig( fillDraw.style.bitmapID ); + file->WriteBig( fillDraw.startVerts.Num() ); + file->WriteBigArray( fillDraw.startVerts.Ptr(), fillDraw.startVerts.Num() ); + file->WriteBig( fillDraw.endVerts.Num() ); + file->WriteBigArray( fillDraw.endVerts.Ptr(), fillDraw.endVerts.Num() ); + file->WriteBig( fillDraw.indices.Num() ); + file->WriteBigArray( fillDraw.indices.Ptr(), fillDraw.indices.Num() ); + } + file->WriteBig( shape->lineDraws.Num() ); + for ( int d = 0; d < shape->lineDraws.Num(); d++ ) { + idSWFShapeDrawLine & lineDraw = shape->lineDraws[d]; + file->WriteBig( lineDraw.style.startWidth ); + file->WriteBig( lineDraw.style.endWidth ); + file->Write( &lineDraw.style.startColor, 4 ); + file->Write( &lineDraw.style.endColor, 4 ); + file->WriteBig( lineDraw.startVerts.Num() ); + file->WriteBigArray( lineDraw.startVerts.Ptr(), lineDraw.startVerts.Num() ); + file->WriteBig( lineDraw.endVerts.Num() ); + file->WriteBigArray( lineDraw.endVerts.Ptr(), lineDraw.endVerts.Num() ); + file->WriteBig( lineDraw.indices.Num() ); + file->WriteBigArray( lineDraw.indices.Ptr(), lineDraw.indices.Num() ); + } + break; + } + case SWF_DICT_SPRITE: { + dictionary[i].sprite->Write( file ); + break; + } + case SWF_DICT_FONT: { + idSWFFont * font = dictionary[i].font; + file->WriteString( font->fontID->GetName() ); + file->WriteBig( font->ascent ); + file->WriteBig( font->descent ); + file->WriteBig( font->leading ); + file->WriteBig( font->glyphs.Num() ); + for ( int g = 0; g < font->glyphs.Num(); g++ ) { + file->WriteBig( font->glyphs[g].code ); + file->WriteBig( font->glyphs[g].advance ); + file->WriteBig( font->glyphs[g].verts.Num() ); + file->WriteBigArray( font->glyphs[g].verts.Ptr(), font->glyphs[g].verts.Num() ); + file->WriteBig( font->glyphs[g].indices.Num() ); + file->WriteBigArray( font->glyphs[g].indices.Ptr(), font->glyphs[g].indices.Num() ); + } + break; + } + case SWF_DICT_TEXT: { + idSWFText * text = dictionary[i].text; + file->WriteBig( text->bounds.tl ); + file->WriteBig( text->bounds.br ); + file->WriteBigArray( (float *)&text->matrix, 6 ); + file->WriteBig( text->textRecords.Num() ); + for ( int t = 0; t < text->textRecords.Num(); t++ ) { + idSWFTextRecord & textRecord = text->textRecords[t]; + file->WriteBig( textRecord.fontID ); + file->Write( &textRecord.color, 4 ); + file->WriteBig( textRecord.xOffset ); + file->WriteBig( textRecord.yOffset ); + file->WriteBig( textRecord.textHeight ); + file->WriteBig( textRecord.firstGlyph ); + file->WriteBig( textRecord.numGlyphs ); + } + file->WriteBig( text->glyphs.Num() ); + for ( int g = 0; g < text->glyphs.Num(); g++ ) { + file->WriteBig( text->glyphs[g].index ); + file->WriteBig( text->glyphs[g].advance ); + } + break; + } + case SWF_DICT_EDITTEXT: { + idSWFEditText * edittext = dictionary[i].edittext; + file->WriteBig( edittext->bounds.tl ); + file->WriteBig( edittext->bounds.br ); + file->WriteBig( edittext->flags ); + file->WriteBig( edittext->fontID ); + file->WriteBig( edittext->fontHeight ); + file->Write( &edittext->color, 4 ); + file->WriteBig( edittext->maxLength ); + file->WriteBig( edittext->align ); + file->WriteBig( edittext->leftMargin ); + file->WriteBig( edittext->rightMargin ); + file->WriteBig( edittext->indent ); + file->WriteBig( edittext->leading ); + file->WriteString( edittext->variable ); + file->WriteString( edittext->initialText ); + break; + } + } + } +} diff --git a/neo/swf/SWF_Main.cpp b/neo/swf/SWF_Main.cpp new file mode 100644 index 00000000..db1d5029 --- /dev/null +++ b/neo/swf/SWF_Main.cpp @@ -0,0 +1,709 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../renderer/image.h" + +#pragma warning(disable: 4355) // 'this' : used in base member initializer list + +idCVar swf_loadBinary( "swf_loadBinary", "1", CVAR_INTEGER, "used to set whether to load binary swf from generated" ); + +int idSWF::mouseX = -1; +int idSWF::mouseY = -1; +bool idSWF::isMouseInClientArea = false; + +extern idCVar in_useJoystick; + +/* +=================== +idSWF::idSWF +=================== +*/ +idSWF::idSWF( const char * filename_, idSoundWorld * soundWorld_ ) { + + atlasMaterial = NULL; + + swfScale = 1.0f; + scaleToVirtual.Set( 1.0f, 1.0f ); + + random.SetSeed( Sys_Milliseconds() ); + + guiSolid = declManager->FindMaterial( "guiSolid" ); + guiCursor_arrow = declManager->FindMaterial( "ui/assets/guicursor_arrow" ); + guiCursor_hand = declManager->FindMaterial( "ui/assets/guicursor_hand" ); + white = declManager->FindMaterial( "_white" ); + + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/a", "guis/assets/hud/controller/ps3/cross", 37, 37, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/b", "guis/assets/hud/controller/ps3/circle", 37, 37, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/x", "guis/assets/hud/controller/ps3/square", 37, 37, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/y", "guis/assets/hud/controller/ps3/triangle", 37, 37, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/rt", "guis/assets/hud/controller/ps3/r2", 64, 52, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/lt", "guis/assets/hud/controller/ps3/l2", 64, 52, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/lb", "guis/assets/hud/controller/ps3/l1", 52, 32, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/xb360/rb", "guis/assets/hud/controller/ps3/r1", 52, 32, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/mouse1", "", 64, 52, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/mouse2", "", 64, 52, 0 ) ); + tooltipButtonImage.Append( keyButtonImages_t( "", "guis/assets/hud/controller/mouse3", "", 64, 52, 0 ) ); + + for ( int index = 0; index < tooltipButtonImage.Num(); index++ ) { + if ( ( tooltipButtonImage[index].xbImage != NULL ) && ( tooltipButtonImage[index].xbImage[0] != '\0' ) ) { + declManager->FindMaterial( tooltipButtonImage[index].xbImage ); + } + if ( ( tooltipButtonImage[index].psImage != NULL ) && ( tooltipButtonImage[index].psImage[0] != '\0' ) ) { + declManager->FindMaterial( tooltipButtonImage[index].psImage ); + } + } + + frameWidth = 0; + frameHeight = 0; + frameRate = 0; + lastRenderTime = 0; + + isActive = false; + inhibitControl = false; + useInhibtControl = true; + + crop = false; + blackbars = false; + paused = false; + hasHitObject = false; + + useMouse = true; + mouseEnabled = false; + renderBorder = 0; + mouseObject = NULL; + hoverObject = NULL; + soundWorld = NULL; + forceNonPCPlatform = false; + + if ( idStr::Cmpn( filename_, "swf/", 4 ) != 0 ) { + // if it doesn't already have swf/ in front of it, add it + filename = "swf/"; + filename += filename_; + } else { + filename = filename_; + } + filename.ToLower(); + filename.BackSlashesToSlashes(); + filename.SetFileExtension( ".swf" ); + + timestamp = fileSystem->GetTimestamp( filename ); + + mainsprite = new (TAG_SWF) idSWFSprite( this ); + mainspriteInstance = NULL; + + idStr binaryFileName = "generated/"; + binaryFileName += filename; + binaryFileName.SetFileExtension( ".bswf" ); + + if ( swf_loadBinary.GetBool() ) { + ID_TIME_T sourceTime = fileSystem->GetTimestamp( filename ); + if ( !LoadBinary( binaryFileName, sourceTime ) ) { + if ( LoadSWF( filename ) ) { + WriteBinary( binaryFileName ); + } + } + } else { + LoadSWF( filename ); + } + idStr atlasFileName = binaryFileName; + atlasFileName.SetFileExtension( ".tga" ); + atlasMaterial = declManager->FindMaterial( atlasFileName ); + + globals = idSWFScriptObject::Alloc(); + globals->Set( "_global", globals ); + + globals->Set( "Object", &scriptFunction_Object ); + + mainspriteInstance = spriteInstanceAllocator.Alloc(); + mainspriteInstance->Init( mainsprite, NULL, 0 ); + + shortcutKeys = idSWFScriptObject::Alloc(); + scriptFunction_shortcutKeys_clear.Bind( this ); + scriptFunction_shortcutKeys_clear.Call( shortcutKeys, idSWFParmList() ); + globals->Set( "shortcutKeys", shortcutKeys ); + + globals->Set( "deactivate", scriptFunction_deactivate.Bind( this ) ); + globals->Set( "inhibitControl", scriptFunction_inhibitControl.Bind( this ) ); + globals->Set( "useInhibit", scriptFunction_useInhibit.Bind( this ) ); + globals->Set( "precacheSound", scriptFunction_precacheSound.Bind( this ) ); + globals->Set( "playSound", scriptFunction_playSound.Bind( this ) ); + globals->Set( "stopSounds",scriptFunction_stopSounds.Bind( this ) ); + globals->Set( "getPlatform", scriptFunction_getPlatform.Bind( this ) ); + globals->Set( "getTruePlatform", scriptFunction_getTruePlatform.Bind( this ) ); + globals->Set( "getLocalString", scriptFunction_getLocalString.Bind( this ) ); + globals->Set( "swapPS3Buttons", scriptFunction_swapPS3Buttons.Bind( this ) ); + globals->Set( "_root", mainspriteInstance->scriptObject ); + globals->Set( "strReplace", scriptFunction_strReplace.Bind( this ) ); + globals->Set( "getCVarInteger", scriptFunction_getCVarInteger.Bind( this ) ); + globals->Set( "setCVarInteger", scriptFunction_setCVarInteger.Bind( this ) ); + + globals->Set( "acos", scriptFunction_acos.Bind( this ) ); + globals->Set( "cos", scriptFunction_cos.Bind( this ) ); + globals->Set( "sin", scriptFunction_sin.Bind( this ) ); + globals->Set( "round", scriptFunction_round.Bind( this ) ); + globals->Set( "pow", scriptFunction_pow.Bind( this ) ); + globals->Set( "sqrt", scriptFunction_sqrt.Bind( this ) ); + globals->Set( "abs", scriptFunction_abs.Bind( this ) ); + globals->Set( "rand", scriptFunction_rand.Bind( this ) ); + globals->Set( "floor", scriptFunction_floor.Bind( this ) ); + globals->Set( "ceil", scriptFunction_ceil.Bind( this ) ); + globals->Set( "toUpper", scriptFunction_toUpper.Bind( this ) ); + + globals->SetNative( "platform", swfScriptVar_platform.Bind( &scriptFunction_getPlatform ) ); + globals->SetNative( "blackbars", swfScriptVar_blackbars.Bind( this ) ); + globals->SetNative( "cropToHeight", swfScriptVar_crop.Bind( this ) ); + globals->SetNative( "cropToFit", swfScriptVar_crop.Bind( this ) ); + globals->SetNative( "crop", swfScriptVar_crop.Bind( this ) ); + + // Do this to touch any external references (like sounds) + // But disable script warnings because many globals won't have been created yet + extern idCVar swf_debug; + int debug = swf_debug.GetInteger(); + swf_debug.SetInteger( 0 ); + + mainspriteInstance->Run(); + mainspriteInstance->RunActions(); + mainspriteInstance->RunTo( 0 ); + + swf_debug.SetInteger( debug ); + + if ( mouseX == -1 ) { + mouseX = ( frameWidth / 2 ); + } + + if ( mouseY == -1 ) { + mouseY = ( frameHeight / 2 ); + } + + soundWorld = soundWorld_; +} + +/* +=================== +idSWF::~idSWF +=================== +*/ +idSWF::~idSWF() { + spriteInstanceAllocator.Free( mainspriteInstance ); + delete mainsprite; + + for ( int i = 0 ; i < dictionary.Num() ; i++ ) { + if ( dictionary[i].sprite ) { + delete dictionary[i].sprite; + dictionary[i].sprite = NULL; + } + if ( dictionary[i].shape ) { + delete dictionary[i].shape; + dictionary[i].shape = NULL; + } + if ( dictionary[i].font ) { + delete dictionary[i].font; + dictionary[i].font = NULL; + } + if ( dictionary[i].text ) { + delete dictionary[i].text; + dictionary[i].text = NULL; + } + if ( dictionary[i].edittext ) { + delete dictionary[i].edittext; + dictionary[i].edittext = NULL; + } + } + + globals->Clear(); + tooltipButtonImage.Clear(); + globals->Release(); + + shortcutKeys->Clear(); + shortcutKeys->Release(); +} + +/* +=================== +idSWF::Activate +when a SWF is deactivated, it rewinds the timeline back to the start +=================== +*/ +void idSWF::Activate( bool b ) { + if ( !isActive && b ) { + inhibitControl = false; + lastRenderTime = Sys_Milliseconds(); + + mainspriteInstance->FreeDisplayList(); + mainspriteInstance->Play(); + mainspriteInstance->Run(); + mainspriteInstance->RunActions(); + } + isActive = b; +} + +/* +=================== +idSWF::InhibitControl +=================== +*/ +bool idSWF::InhibitControl() { + if ( !IsLoaded() || !IsActive() ) { + return false; + } + return ( inhibitControl && useInhibtControl ); +} + +/* +=================== +idSWF::PlaySound +=================== +*/ +int idSWF::PlaySound( const char * sound, int channel, bool blocking ) { + if ( !IsActive() ) { + return -1; + } + if ( soundWorld != NULL ) { + return soundWorld->PlayShaderDirectly( sound, channel ); + } else { + idLib::Warning( "No playing sound world on soundSystem in swf play sound!" ); + return -1; + } +} + +/* +=================== +idSWF::PlaySound +=================== +*/ +void idSWF::StopSound( int channel ) { + if ( soundWorld != NULL ) { + soundWorld->PlayShaderDirectly( NULL, channel ); + } else { + idLib::Warning( "No playing sound world on soundSystem in swf play sound!" ); + } +} + +/* +=================== +idSWF::idSWFScriptFunction_inhibitControl::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_inhibitControl::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + pThis->inhibitControl = parms[0].ToBool(); + return idSWFScriptVar(); +} + +/* +=================== +idSWF::idSWFScriptFunction_inhibitControl::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_useInhibit::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + pThis->useInhibtControl = parms[0].ToBool(); + return idSWFScriptVar(); +} + +/* +=================== +idSWF::idSWFScriptFunction_deactivate::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_deactivate::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + pThis->Activate( false ); + return idSWFScriptVar(); +} + +/* +=================== +idSWF::idSWFScriptFunction_precacheSound::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_precacheSound::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + const idSoundShader * soundShader = declManager->FindSound( parms[0].ToString(), true ); + return soundShader->GetName(); +} + +/* +=================== +idSWF::idSWFScriptFunction_playSound::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_playSound::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + int channel = SCHANNEL_ANY; + // specific channel passed in + if ( parms.Num() > 1 ) { + channel = parms[1].ToInteger(); + } + + pThis->PlaySound( parms[0].ToString(), channel ); + + return idSWFScriptVar(); +} + +/* +=================== +idSWF::idSWFScriptFunction_stopSounds::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_stopSounds::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + int channel = SCHANNEL_ANY; + if ( parms.Num() == 1 ) { + channel = parms[0].ToInteger(); + } + + pThis->StopSound( channel ); + + return idSWFScriptVar(); +} + +/* +======================== +idSWFScriptFunction_GetPlatform::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_getPlatform::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + return pThis->GetPlatform(); +} + +/* +======================== +idSWFScriptFunction_GetPlatform::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_getTruePlatform::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + return 2; +} + + +/* +======================== +idSWFScriptFunction_GetPlatform::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_strReplace::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + if ( parms.Num() != 3 ) { + return ""; + } + + idStr str = parms[0].ToString(); + idStr repString = parms[1].ToString(); + idStr val = parms[2].ToString(); + str.Replace( repString, val ); + + return str; +} + +/* +======================== +idSWFScriptFunction_GetPlatform::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_getLocalString::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + + if ( parms.Num() == 0 ) { + return idSWFScriptVar(); + } + + idStr val = idLocalization::GetString( parms[0].ToString() ); + return val; +} + +/* +======================== +idSWF::UseCircleForAccept +======================== +*/ +bool idSWF::UseCircleForAccept() { + return false; +} + +/* +======================== +idSWF::GetPlatform +======================== +*/ +int idSWF::GetPlatform() { + + + if ( in_useJoystick.GetBool() || forceNonPCPlatform ) { + forceNonPCPlatform = false; + return 0; + } + + return 2; +} + +/* +======================== +idSWFScriptFunction_swapPS3Buttons::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_swapPS3Buttons::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + return pThis->UseCircleForAccept(); +} + +/* +======================== +idSWFScriptFunction_getCVarInteger::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_getCVarInteger::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + return cvarSystem->GetCVarInteger( parms[0].ToString() ); +} + +/* +======================== +idSWFScriptFunction_setCVarInteger::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_setCVarInteger::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + cvarSystem->SetCVarInteger( parms[0].ToString(), parms[1].ToInteger() ); + return idSWFScriptVar(); +} + +/* +=================== +idSWF::idSWFScriptFunction_acos::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_acos::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + return idMath::ACos( parms[0].ToFloat() ); +} + +/* +=================== +idSWF::idSWFScriptFunction_cos::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_cos::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + return idMath::Cos( parms[0].ToFloat() ); +} + +/* +=================== +idSWF::idSWFScriptFunction_sin::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_sin::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + return ( idMath::Sin( parms[0].ToFloat() ) ); +} + +/* +=================== +idSWF::idSWFScriptFunction_round::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_round::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + int value = idMath::Ftoi( parms[0].ToFloat() + 0.5f ); + return value; +} + +/* +=================== +idSWF::idSWFScriptFunction_pow::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_pow::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 2 ) { + return idSWFScriptVar(); + } + + float value = parms[0].ToFloat(); + float power = parms[1].ToFloat(); + return ( idMath::Pow( value, power ) ); +} + +/* +=================== +idSWF::idSWFScriptFunction_pow::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_sqrt::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + + float value = parms[0].ToFloat(); + return ( idMath::Sqrt( value ) ); +} + +/* +=================== +idSWF::idSWFScriptFunction_abs::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_abs::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 ) { + return idSWFScriptVar(); + } + + float value = idMath::Fabs( parms[0].ToFloat() ); + return value; +} + +/* +=================== +idSWF::idSWFScriptFunction_rand::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_rand::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + float min = 0.0f; + float max = 1.0f; + switch ( parms.Num() ) { + case 0: + break; + case 1: + max = parms[0].ToFloat(); + break; + default: + min = parms[0].ToFloat(); + max = parms[1].ToFloat(); + break; + } + return min + pThis->GetRandom().RandomFloat() * ( max - min ); +} + +/* +======================== +idSWFScriptFunction_floor::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_floor::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 || !parms[0].IsNumeric() ) { + idLib::Warning( "Invalid parameters specified for floor" ); + return idSWFScriptVar(); + } + + float num = parms[0].ToFloat(); + + return idSWFScriptVar( idMath::Floor( num ) ); +} + +/* +======================== +idSWFScriptFunction_ceil::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_ceil::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 || !parms[0].IsNumeric() ) { + idLib::Warning( "Invalid parameters specified for ceil" ); + return idSWFScriptVar(); + } + + float num = parms[0].ToFloat(); + + return idSWFScriptVar( idMath::Ceil( num ) ); +} + +/* +======================== +idSWFScriptFunction_toUpper::Call +======================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_toUpper::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + if ( parms.Num() != 1 || !parms[0].IsString() ) { + idLib::Warning( "Invalid parameters specified for toUpper" ); + return idSWFScriptVar(); + } + + idStr val = idLocalization::GetString( parms[0].ToString() ); + val.ToUpper(); + return val; +} + +/* +=================== +idSWF::idSWFScriptFunction_shortcutKeys_clear::Call +=================== +*/ +idSWFScriptVar idSWF::idSWFScriptFunction_shortcutKeys_clear::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + idSWFScriptObject * object = pThis->shortcutKeys; + object->Clear(); + object->Set( "clear", this ); + object->Set( "JOY1", "ENTER" ); + object->Set( "JOY2", "BACKSPACE" ); + object->Set( "JOY3", "START" ); + object->Set( "JOY5", "LB" ); + object->Set( "JOY6", "RB" ); + object->Set( "JOY9", "START" ); + object->Set( "JOY10", "BACKSPACE" ); + object->Set( "JOY_DPAD_UP", "UP" ); + object->Set( "JOY_DPAD_DOWN", "DOWN" ); + object->Set( "JOY_DPAD_LEFT", "LEFT" ); + object->Set( "JOY_DPAD_RIGHT", "RIGHT" ); + object->Set( "JOY_STICK1_UP", "STICK1_UP" ); + object->Set( "JOY_STICK1_DOWN", "STICK1_DOWN" ); + object->Set( "JOY_STICK1_LEFT", "STICK1_LEFT" ); + object->Set( "JOY_STICK1_RIGHT", "STICK1_RIGHT" ); + object->Set( "JOY_STICK2_UP", "STICK2_UP" ); + object->Set( "JOY_STICK2_DOWN", "STICK2_DOWN" ); + object->Set( "JOY_STICK2_LEFT", "STICK2_LEFT" ); + object->Set( "JOY_STICK2_RIGHT", "STICK2_RIGHT" ); + object->Set( "KP_ENTER", "ENTER" ); + object->Set( "MWHEELDOWN", "MWHEEL_DOWN" ); + object->Set( "MWHEELUP", "MWHEEL_UP" ); + object->Set( "K_TAB", "TAB" ); + + + // FIXME: I'm an RTARD and didn't realize the keys all have "ARROW" after them + object->Set( "LEFTARROW", "LEFT" ); + object->Set( "RIGHTARROW", "RIGHT" ); + object->Set( "UPARROW", "UP" ); + object->Set( "DOWNARROW", "DOWN" ); + + + return idSWFScriptVar(); +} + +idSWFScriptVar idSWF::idSWFScriptNativeVar_blackbars::Get( idSWFScriptObject * object ) { + return pThis->blackbars; +} + +void idSWF::idSWFScriptNativeVar_blackbars::Set( idSWFScriptObject * object, const idSWFScriptVar & value ) { + pThis->blackbars = value.ToBool(); +} + +idSWFScriptVar idSWF::idSWFScriptNativeVar_crop::Get( idSWFScriptObject * object ) { + return pThis->crop; +} + +void idSWF::idSWFScriptNativeVar_crop::Set( idSWFScriptObject * object, const idSWFScriptVar & value ) { + pThis->crop = value.ToBool(); +} diff --git a/neo/swf/SWF_Names.cpp b/neo/swf/SWF_Names.cpp new file mode 100644 index 00000000..1c269489 --- /dev/null +++ b/neo/swf/SWF_Names.cpp @@ -0,0 +1,215 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +======================== +idSWF::GetTagName +======================== +*/ +const char * idSWF::GetTagName( swfTag_t tag ) { +#define SWF_TAG_NAME( x ) case Tag_##x: return #x; + switch ( tag ) { + SWF_TAG_NAME( End ); + SWF_TAG_NAME( ShowFrame ); + SWF_TAG_NAME( DefineShape ); + SWF_TAG_NAME( PlaceObject ); + SWF_TAG_NAME( RemoveObject ); + SWF_TAG_NAME( DefineBits ); + SWF_TAG_NAME( DefineButton ); + SWF_TAG_NAME( JPEGTables ); + SWF_TAG_NAME( SetBackgroundColor ); + SWF_TAG_NAME( DefineFont ); + SWF_TAG_NAME( DefineText ); + SWF_TAG_NAME( DoAction ); + SWF_TAG_NAME( DefineFontInfo ); + SWF_TAG_NAME( DefineSound ); + SWF_TAG_NAME( StartSound ); + SWF_TAG_NAME( DefineButtonSound ); + SWF_TAG_NAME( SoundStreamHead ); + SWF_TAG_NAME( SoundStreamBlock ); + SWF_TAG_NAME( DefineBitsLossless ); + SWF_TAG_NAME( DefineBitsJPEG2 ); + SWF_TAG_NAME( DefineShape2 ); + SWF_TAG_NAME( DefineButtonCxform ); + SWF_TAG_NAME( Protect ); + SWF_TAG_NAME( PlaceObject2 ); + SWF_TAG_NAME( RemoveObject2 ); + SWF_TAG_NAME( DefineShape3 ); + SWF_TAG_NAME( DefineText2 ); + SWF_TAG_NAME( DefineButton2 ); + SWF_TAG_NAME( DefineBitsJPEG3 ); + SWF_TAG_NAME( DefineBitsLossless2 ); + SWF_TAG_NAME( DefineEditText ); + SWF_TAG_NAME( DefineSprite ); + SWF_TAG_NAME( FrameLabel ); + SWF_TAG_NAME( SoundStreamHead2 ); + SWF_TAG_NAME( DefineMorphShape ); + SWF_TAG_NAME( DefineFont2 ); + SWF_TAG_NAME( ExportAssets ); + SWF_TAG_NAME( ImportAssets ); + SWF_TAG_NAME( EnableDebugger ); + SWF_TAG_NAME( DoInitAction ); + SWF_TAG_NAME( DefineVideoStream ); + SWF_TAG_NAME( VideoFrame ); + SWF_TAG_NAME( DefineFontInfo2 ); + SWF_TAG_NAME( EnableDebugger2 ); + SWF_TAG_NAME( ScriptLimits ); + SWF_TAG_NAME( SetTabIndex ); + SWF_TAG_NAME( FileAttributes ); + SWF_TAG_NAME( PlaceObject3 ); + SWF_TAG_NAME( ImportAssets2 ); + SWF_TAG_NAME( DefineFontAlignZones ); + SWF_TAG_NAME( CSMTextSettings ); + SWF_TAG_NAME( DefineFont3 ); + SWF_TAG_NAME( SymbolClass ); + SWF_TAG_NAME( Metadata ); + SWF_TAG_NAME( DefineScalingGrid ); + SWF_TAG_NAME( DoABC ); + SWF_TAG_NAME( DefineShape4 ); + SWF_TAG_NAME( DefineMorphShape2 ); + SWF_TAG_NAME( DefineSceneAndFrameLabelData ); + SWF_TAG_NAME( DefineBinaryData ); + SWF_TAG_NAME( DefineFontName ); + SWF_TAG_NAME( StartSound2 ); + default: return "????"; + } +} + +/* +======================== +idSWF::GetActionName +======================== +*/ +const char * idSWF::GetActionName( swfAction_t action ) { +#define SWF_ACTION_NAME( x ) case Action_##x: return #x; + switch ( action ) { + SWF_ACTION_NAME( NextFrame ); + SWF_ACTION_NAME( PrevFrame ); + SWF_ACTION_NAME( Play ); + SWF_ACTION_NAME( Stop ); + SWF_ACTION_NAME( ToggleQuality ); + SWF_ACTION_NAME( StopSounds ); + SWF_ACTION_NAME( GotoFrame ); + SWF_ACTION_NAME( GetURL ); + SWF_ACTION_NAME( WaitForFrame ); + SWF_ACTION_NAME( SetTarget ); + SWF_ACTION_NAME( GoToLabel ); + SWF_ACTION_NAME( Add ); + SWF_ACTION_NAME( Subtract ); + SWF_ACTION_NAME( Multiply ); + SWF_ACTION_NAME( Divide ); + SWF_ACTION_NAME( Equals ); + SWF_ACTION_NAME( Less ); + SWF_ACTION_NAME( And ); + SWF_ACTION_NAME( Or ); + SWF_ACTION_NAME( Not ); + SWF_ACTION_NAME( StringEquals ); + SWF_ACTION_NAME( StringLength ); + SWF_ACTION_NAME( StringExtract ); + SWF_ACTION_NAME( Pop ); + SWF_ACTION_NAME( ToInteger ); + SWF_ACTION_NAME( GetVariable ); + SWF_ACTION_NAME( SetVariable ); + SWF_ACTION_NAME( SetTarget2 ); + SWF_ACTION_NAME( StringAdd ); + SWF_ACTION_NAME( GetProperty ); + SWF_ACTION_NAME( SetProperty ); + SWF_ACTION_NAME( CloneSprite ); + SWF_ACTION_NAME( RemoveSprite ); + SWF_ACTION_NAME( Trace ); + SWF_ACTION_NAME( StartDrag ); + SWF_ACTION_NAME( EndDrag ); + SWF_ACTION_NAME( StringLess ); + SWF_ACTION_NAME( RandomNumber ); + SWF_ACTION_NAME( MBStringLength ); + SWF_ACTION_NAME( CharToAscii ); + SWF_ACTION_NAME( AsciiToChar ); + SWF_ACTION_NAME( GetTime ); + SWF_ACTION_NAME( MBStringExtract ); + SWF_ACTION_NAME( MBCharToAscii ); + SWF_ACTION_NAME( MBAsciiToChar ); + SWF_ACTION_NAME( WaitForFrame2 ); + SWF_ACTION_NAME( Push ); + SWF_ACTION_NAME( Jump ); + SWF_ACTION_NAME( GetURL2 ); + SWF_ACTION_NAME( If ); + SWF_ACTION_NAME( Call ); + SWF_ACTION_NAME( GotoFrame2 ); + SWF_ACTION_NAME( Delete ); + SWF_ACTION_NAME( Delete2 ); + SWF_ACTION_NAME( DefineLocal ); + SWF_ACTION_NAME( CallFunction ); + SWF_ACTION_NAME( Return ); + SWF_ACTION_NAME( Modulo ); + SWF_ACTION_NAME( NewObject ); + SWF_ACTION_NAME( DefineLocal2 ); + SWF_ACTION_NAME( InitArray ); + SWF_ACTION_NAME( InitObject ); + SWF_ACTION_NAME( TypeOf ); + SWF_ACTION_NAME( TargetPath ); + SWF_ACTION_NAME( Enumerate ); + SWF_ACTION_NAME( Add2 ); + SWF_ACTION_NAME( Less2 ); + SWF_ACTION_NAME( Equals2 ); + SWF_ACTION_NAME( ToNumber ); + SWF_ACTION_NAME( ToString ); + SWF_ACTION_NAME( PushDuplicate ); + SWF_ACTION_NAME( StackSwap ); + SWF_ACTION_NAME( GetMember ); + SWF_ACTION_NAME( SetMember ); + SWF_ACTION_NAME( Increment ); + SWF_ACTION_NAME( Decrement ); + SWF_ACTION_NAME( CallMethod ); + SWF_ACTION_NAME( NewMethod ); + SWF_ACTION_NAME( BitAnd ); + SWF_ACTION_NAME( BitOr ); + SWF_ACTION_NAME( BitXor ); + SWF_ACTION_NAME( BitLShift ); + SWF_ACTION_NAME( BitRShift ); + SWF_ACTION_NAME( BitURShift ); + SWF_ACTION_NAME( StoreRegister ); + SWF_ACTION_NAME( ConstantPool ); + SWF_ACTION_NAME( With ); + SWF_ACTION_NAME( DefineFunction ); + SWF_ACTION_NAME( InstanceOf ); + SWF_ACTION_NAME( Enumerate2 ); + SWF_ACTION_NAME( StrictEquals ); + SWF_ACTION_NAME( Greater ); + SWF_ACTION_NAME( StringGreater ); + SWF_ACTION_NAME( Extends ); + SWF_ACTION_NAME( CastOp ); + SWF_ACTION_NAME( ImplementsOp ); + SWF_ACTION_NAME( Throw ); + SWF_ACTION_NAME( Try ); + SWF_ACTION_NAME( DefineFunction2 ); + default: return "???"; + } +} + diff --git a/neo/swf/SWF_ParmList.cpp b/neo/swf/SWF_ParmList.cpp new file mode 100644 index 00000000..47c7afac --- /dev/null +++ b/neo/swf/SWF_ParmList.cpp @@ -0,0 +1,85 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +void idSWFParmList::Append( const idSWFScriptVar & other ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + *var = other; + } +} +void idSWFParmList::Append( idSWFScriptObject * o ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetObject( o ); + } +} +void idSWFParmList::Append( idSWFScriptFunction * f ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetFunction( f ); + } +} +void idSWFParmList::Append( const char * s ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetString( s ); + } +} +void idSWFParmList::Append( const idStr & s ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetString( s ); + } +} +void idSWFParmList::Append( idSWFScriptString * s ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetString( s ); + } +} +void idSWFParmList::Append( const float f ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetFloat( f ); + } +} +void idSWFParmList::Append( const int32 i ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetInteger( i ); + } +} +void idSWFParmList::Append( const bool b ) { + idSWFScriptVar * var = Alloc(); + if ( var != NULL ) { + var->SetBool( b ); + } +} diff --git a/neo/swf/SWF_ParmList.h b/neo/swf/SWF_ParmList.h new file mode 100644 index 00000000..1ce50033 --- /dev/null +++ b/neo/swf/SWF_ParmList.h @@ -0,0 +1,60 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_PARMLIST_H__ +#define __SWF_PARMLIST_H__ + +// static list for script parameters +static const int SWF_MAX_PARMS = 16; + +/* +================================================ +idSWFParmList + +A static list for script parameters that reduces the number of SWF allocations dramatically. +================================================ +*/ +class idSWFParmList : public idStaticList< idSWFScriptVar, SWF_MAX_PARMS > { +public: + idSWFParmList() { + } + explicit idSWFParmList( const int num_ ) { + SetNum( num_ ); + } + + void Append( const idSWFScriptVar & other ); + void Append( idSWFScriptObject * o ); + void Append( idSWFScriptFunction * f ); + void Append( const char * s ); + void Append( const idStr & s ); + void Append( idSWFScriptString * s ); + void Append( const float f ); + void Append( const int32 i ); + void Append( const bool b ); +}; + +#endif // __SWF_PARMLIST_H__ diff --git a/neo/swf/SWF_PlaceObject.cpp b/neo/swf/SWF_PlaceObject.cpp new file mode 100644 index 00000000..7fe8f195 --- /dev/null +++ b/neo/swf/SWF_PlaceObject.cpp @@ -0,0 +1,231 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +int c_PlaceObject2; +int c_PlaceObject3; + +#define PlaceFlagHasClipActions BIT( 7 ) +#define PlaceFlagHasClipDepth BIT( 6 ) +#define PlaceFlagHasName BIT( 5 ) +#define PlaceFlagHasRatio BIT( 4 ) +#define PlaceFlagHasColorTransform BIT( 3 ) +#define PlaceFlagHasMatrix BIT( 2 ) +#define PlaceFlagHasCharacter BIT( 1 ) +#define PlaceFlagMove BIT( 0 ) + +#define PlaceFlagPad0 BIT( 7 ) +#define PlaceFlagPad1 BIT( 6 ) +#define PlaceFlagPad2 BIT( 5 ) +#define PlaceFlagHasImage BIT( 4 ) +#define PlaceFlagHasClassName BIT( 3 ) +#define PlaceFlagCacheAsBitmap BIT( 2 ) +#define PlaceFlagHasBlendMode BIT( 1 ) +#define PlaceFlagHasFilterList BIT( 0 ) + +/* +======================== +idSWFSpriteInstance::PlaceObject2 +======================== +*/ +void idSWFSpriteInstance::PlaceObject2( idSWFBitStream & bitstream ) { + c_PlaceObject2++; + + uint64 flags = bitstream.ReadU8(); + int depth = bitstream.ReadU16(); + + int characterID = -1; + if ( ( flags & PlaceFlagHasCharacter ) != 0 ) { + characterID = bitstream.ReadU16(); + } + + swfDisplayEntry_t * display = NULL; + + if ( ( flags & PlaceFlagMove ) != 0 ) { + // modify an existing entry + display = FindDisplayEntry( depth ); + if ( display == NULL ) { + idLib::Warning( "PlaceObject2: trying to modify entry %d, which doesn't exist", depth ); + return; + } + if ( characterID >= 0 ) { + // We are very picky about what kind of objects can change characters + // Shapes can become other shapes, but sprites can never change + if ( display->spriteInstance || display->textInstance ) { + idLib::Warning( "PlaceObject2: Trying to change the character of a sprite after it's been created" ); + return; + } + idSWFDictionaryEntry * dictEntry = sprite->swf->FindDictionaryEntry( characterID ); + if ( dictEntry != NULL ) { + if ( dictEntry->type == SWF_DICT_SPRITE || dictEntry->type == SWF_DICT_EDITTEXT ) { + idLib::Warning( "PlaceObject2: Trying to change the character of a shape to a sprite" ); + return; + } + } + display->characterID = characterID; + } + } else { + if ( characterID < 0 ) { + idLib::Warning( "PlaceObject2: Trying to create a new object without a character" ); + return; + } + // create a new entry + display = AddDisplayEntry( depth, characterID ); + if ( display == NULL ) { + idLib::Warning( "PlaceObject2: trying to create a new entry at %d, but an item already exists there", depth ); + return; + } + } + if ( ( flags & PlaceFlagHasMatrix ) != 0 ) { + bitstream.ReadMatrix( display->matrix ); + } + if ( ( flags & PlaceFlagHasColorTransform ) != 0 ) { + bitstream.ReadColorXFormRGBA( display->cxf ); + } + if ( ( flags & PlaceFlagHasRatio ) != 0 ) { + display->ratio = bitstream.ReadU16() * ( 1.0f / 65535.0f ); + } + if ( ( flags & PlaceFlagHasName ) != 0 ) { + idStr name = bitstream.ReadString(); + if ( display->spriteInstance ) { + display->spriteInstance->name = name; + scriptObject->Set( name, display->spriteInstance->GetScriptObject() ); + } else if ( display->textInstance ) { + scriptObject->Set( name, display->textInstance->GetScriptObject() ); + } + } + if ( ( flags & PlaceFlagHasClipDepth ) != 0 ) { + display->clipDepth = bitstream.ReadU16(); + } + if ( ( flags & PlaceFlagHasClipActions ) != 0 ) { + // FIXME: clip actions + } +} + +/* +======================== +idSWFSpriteInstance::PlaceObject3 +======================== +*/ +void idSWFSpriteInstance::PlaceObject3( idSWFBitStream & bitstream ) { + c_PlaceObject3++; + + uint64 flags1 = bitstream.ReadU8(); + uint64 flags2 = bitstream.ReadU8(); + uint16 depth = bitstream.ReadU16(); + + if ( ( flags2 & PlaceFlagHasClassName ) != 0 || ( ( ( flags2 & PlaceFlagHasImage ) != 0 ) && ( ( flags1 & PlaceFlagHasCharacter ) != 0 ) ) ) { + bitstream.ReadString(); // ignored + } + + int characterID = -1; + if ( ( flags1 & PlaceFlagHasCharacter ) != 0 ) { + characterID = bitstream.ReadU16(); + } + + swfDisplayEntry_t * display = NULL; + + if ( ( flags1 & PlaceFlagMove ) != 0 ) { + // modify an existing entry + display = FindDisplayEntry( depth ); + if ( display == NULL ) { + idLib::Warning( "PlaceObject3: trying to modify entry %d, which doesn't exist", depth ); + return; + } + if ( characterID >= 0 ) { + // We are very picky about what kind of objects can change characters + // Shapes can become other shapes, but sprites can never change + if ( display->spriteInstance || display->textInstance ) { + idLib::Warning( "PlaceObject3: Trying to change the character of a sprite after it's been created" ); + return; + } + idSWFDictionaryEntry * dictEntry = sprite->swf->FindDictionaryEntry( characterID ); + if ( dictEntry != NULL ) { + if ( dictEntry->type == SWF_DICT_SPRITE || dictEntry->type == SWF_DICT_EDITTEXT ) { + idLib::Warning( "PlaceObject3: Trying to change the character of a shape to a sprite" ); + return; + } + } + display->characterID = characterID; + } + } else { + if ( characterID < 0 ) { + idLib::Warning( "PlaceObject3: Trying to create a new object without a character" ); + return; + } + // create a new entry + display = AddDisplayEntry( depth, characterID ); + if ( display == NULL ) { + idLib::Warning( "PlaceObject3: trying to create a new entry at %d, but an item already exists there", depth ); + return; + } + } + if ( ( flags1 & PlaceFlagHasMatrix ) != 0 ) { + bitstream.ReadMatrix( display->matrix ); + } + if ( ( flags1 & PlaceFlagHasColorTransform ) != 0 ) { + bitstream.ReadColorXFormRGBA( display->cxf ); + } + if ( ( flags1 & PlaceFlagHasRatio ) != 0 ) { + display->ratio = bitstream.ReadU16() * ( 1.0f / 65535.0f ); + } + if ( ( flags1 & PlaceFlagHasName ) != 0 ) { + idStr name = bitstream.ReadString(); + if ( display->spriteInstance ) { + display->spriteInstance->name = name; + scriptObject->Set( name, display->spriteInstance->GetScriptObject() ); + } else if ( display->textInstance ) { + scriptObject->Set( name, display->textInstance->GetScriptObject() ); + } + } + if ( ( flags1 & PlaceFlagHasClipDepth ) != 0 ) { + display->clipDepth = bitstream.ReadU16(); + } + if ( ( flags2 & PlaceFlagHasFilterList ) != 0 ) { + // we don't support filters and because the filter list is variable length we + // can't support anything after the filter list either (blend modes and clip actions) + idLib::Warning( "PlaceObject3: has filters" ); + return; + } + if ( ( flags2 & PlaceFlagHasBlendMode ) != 0 ) { + display->blendMode = bitstream.ReadU8(); + } + if ( ( flags1 & PlaceFlagHasClipActions ) != 0 ) { + // FIXME: + } +} + +/* +======================== +idSWFSpriteInstance::RemoveObject2 +======================== +*/ +void idSWFSpriteInstance::RemoveObject2( idSWFBitStream & bitstream ) { + RemoveDisplayEntry( bitstream.ReadU16() ); +} diff --git a/neo/swf/SWF_Render.cpp b/neo/swf/SWF_Render.cpp new file mode 100644 index 00000000..1f8094c2 --- /dev/null +++ b/neo/swf/SWF_Render.cpp @@ -0,0 +1,1536 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../renderer/tr_local.h" + +idCVar swf_timescale( "swf_timescale", "1", CVAR_FLOAT, "timescale for swf files" ); +idCVar swf_stopat( "swf_stopat", "0", CVAR_FLOAT, "stop at a specific frame" ); + +idCVar swf_titleSafe( "swf_titleSafe", "0.005", CVAR_FLOAT, "space between UI elements and screen edge", 0.0f, 0.075f ); + +idCVar swf_forceAlpha( "swf_forceAlpha", "0", CVAR_FLOAT, "force an alpha value on all elements, useful to show invisible animating elements", 0.0f, 1.0f ); + +extern idCVar swf_textStrokeSize; +extern idCVar swf_textStrokeSizeGlyphSpacer; +extern idCVar in_useJoystick; + +#define ALPHA_EPSILON 0.001f + +#define STENCIL_DECR -1 +#define STENCIL_INCR -2 + +/* +======================== +idSWF::DrawStretchPic +======================== +*/ +void idSWF::DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) { + renderSystem->DrawStretchPic( x * scaleToVirtual.x, y * scaleToVirtual.y, w * scaleToVirtual.x, h * scaleToVirtual.y, s1, t1, s2, t2, material ); +} + +/* +======================== +idSWF::DrawStretchPic +======================== +*/ +void idSWF::DrawStretchPic( const idVec4 & topLeft, const idVec4 & topRight, const idVec4 & bottomRight, const idVec4 & bottomLeft, const idMaterial * material ) { + renderSystem->DrawStretchPic( + idVec4( topLeft.x * scaleToVirtual.x, topLeft.y * scaleToVirtual.y, topLeft.z, topLeft.w ), + idVec4( topRight.x * scaleToVirtual.x, topRight.y * scaleToVirtual.y, topRight.z, topRight.w ), + idVec4( bottomRight.x * scaleToVirtual.x, bottomRight.y * scaleToVirtual.y, bottomRight.z, bottomRight.w ), + idVec4( bottomLeft.x * scaleToVirtual.x, bottomLeft.y * scaleToVirtual.y, bottomLeft.z, bottomLeft.w ), + material ); +} + +/* +======================== +idSWF::Render +======================== +*/ +void idSWF::Render( idRenderSystem * gui, int time, bool isSplitscreen ) { + if ( !IsLoaded() ) { + return; + } + if ( !IsActive() ) { + return; + } + if ( swf_stopat.GetInteger() > 0 ) { + if ( mainspriteInstance->currentFrame == swf_stopat.GetInteger() ) { + swf_timescale.SetFloat( 0.0f ); + } + } + + int currentTime = Sys_Milliseconds(); + int framesToRun = 0; + + if ( paused ) { + lastRenderTime = currentTime; + } + + if ( swf_timescale.GetFloat() > 0.0f ) { + if ( lastRenderTime == 0 ) { + lastRenderTime = currentTime; + framesToRun = 1; + } else { + float deltaTime = ( currentTime - lastRenderTime ); + float fr = ( (float)frameRate / 256.0f ) * swf_timescale.GetFloat(); + framesToRun = idMath::Ftoi( ( fr * deltaTime ) / 1000.0f ); + lastRenderTime += ( framesToRun * ( 1000.0f / fr ) ); + if ( framesToRun > 10 ) { + framesToRun = 10; + } + } + for ( int i = 0; i < framesToRun; i++ ) { + mainspriteInstance->Run(); + mainspriteInstance->RunActions(); + } + } + + const float pixelAspect = renderSystem->GetPixelAspect(); + const float sysWidth = renderSystem->GetWidth() * ( pixelAspect > 1.0f ? pixelAspect : 1.0f ); + const float sysHeight = renderSystem->GetHeight() / ( pixelAspect < 1.0f ? pixelAspect : 1.0f ); + float scale = swfScale * sysHeight / (float)frameHeight; + + swfRenderState_t renderState; + renderState.stereoDepth = (stereoDepthType_t)mainspriteInstance->GetStereoDepth(); + renderState.matrix.xx = scale; + renderState.matrix.yy = scale; + renderState.matrix.tx = 0.5f * ( sysWidth - ( frameWidth * scale ) ); + renderState.matrix.ty = 0.5f * ( sysHeight - ( frameHeight * scale ) ); + + renderBorder = renderState.matrix.tx / scale; + + scaleToVirtual.Set( (float)SCREEN_WIDTH / sysWidth, (float)SCREEN_HEIGHT / sysHeight ); + + RenderSprite( gui, mainspriteInstance, renderState, time, isSplitscreen ); + + if ( blackbars ) { + float barWidth = renderState.matrix.tx + 0.5f; + float barHeight = renderState.matrix.ty + 0.5f; + if ( barWidth > 0.0f ) { + gui->SetColor( idVec4( 0.0f, 0.0f, 0.0f, 1.0f ) ); + DrawStretchPic( 0.0f, 0.0f, barWidth, sysHeight, 0, 0, 1, 1, white ); + DrawStretchPic( sysWidth - barWidth, 0.0f, barWidth, sysHeight, 0, 0, 1, 1, white ); + } + if ( barHeight > 0.0f ) { + gui->SetColor( idVec4( 0.0f, 0.0f, 0.0f, 1.0f ) ); + DrawStretchPic( 0.0f, 0.0f, sysWidth, barHeight, 0, 0, 1, 1, white ); + DrawStretchPic( 0.0f, sysHeight - barHeight, sysWidth, barHeight, 0, 0, 1, 1, white ); + } + } + + if ( isMouseInClientArea && ( mouseEnabled && useMouse ) && ( InhibitControl() || ( !InhibitControl() && !useInhibtControl ) ) ) { + gui->SetGLState( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + gui->SetColor( idVec4( 1.0f, 1.0f, 1.0f, 1.0f ) ); + idVec2 mouse = renderState.matrix.Transform( idVec2( mouseX - 1, mouseY - 2 ) ); + //idSWFScriptObject * hitObject = HitTest( mainspriteInstance, swfRenderState_t(), mouseX, mouseY, NULL ); + if ( !hasHitObject ) { //hitObject == NULL ) { + DrawStretchPic( mouse.x, mouse.y, 32.0f, 32.0f, 0, 0, 1, 1, guiCursor_arrow ); + } else { + DrawStretchPic( mouse.x, mouse.y, 32.0f, 32.0f, 0, 0, 1, 1, guiCursor_hand ); + } + } + + // restore the GL State + gui->SetGLState( 0 ); +} + +/* +======================== +idSWF::RenderMask +======================== +*/ +void idSWF::RenderMask( idRenderSystem * gui, const swfDisplayEntry_t * mask, const swfRenderState_t & renderState, const int stencilMode ) { + swfRenderState_t renderState2; + renderState2.stereoDepth = renderState.stereoDepth; + renderState2.matrix = mask->matrix.Multiply( renderState.matrix ); + renderState2.cxf = mask->cxf.Multiply( renderState.cxf ); + renderState2.ratio = mask->ratio; + renderState2.material = guiSolid; + renderState2.activeMasks = stencilMode; + + idSWFDictionaryEntry & entry = dictionary[ mask->characterID ]; + if ( entry.type == SWF_DICT_SHAPE ) { + RenderShape( gui, entry.shape, renderState2 ); + } else if ( entry.type == SWF_DICT_MORPH ) { + RenderMorphShape( gui, entry.shape, renderState2 ); + } +} + +/* +======================== +idSWF::RenderSprite +======================== +*/ +void idSWF::RenderSprite( idRenderSystem * gui, idSWFSpriteInstance * spriteInstance, const swfRenderState_t & renderState, int time, bool isSplitscreen ) { + + if ( spriteInstance == NULL ) { + idLib::Warning( "%s: RenderSprite: spriteInstance == NULL", filename.c_str() ); + return; + } + if ( !spriteInstance->isVisible ) { + return; + } + if ( ( ( renderState.cxf.mul.w + renderState.cxf.add.w ) <= ALPHA_EPSILON ) && ( swf_forceAlpha.GetFloat() <= 0.0f ) ) { + return; + } + + idStaticList activeMasks; + + for ( int i = 0; i < spriteInstance->displayList.Num(); i++ ) { + const swfDisplayEntry_t & display = spriteInstance->displayList[i]; + + for ( int j = 0; j < activeMasks.Num(); j++ ) { + const swfDisplayEntry_t * mask = activeMasks[ j ]; + if ( display.depth > mask->clipDepth ) { + RenderMask( gui, mask, renderState, STENCIL_DECR ); + activeMasks.RemoveIndexFast( j ); + } + } + if ( display.clipDepth > 0 ) { + activeMasks.Append( &display ); + RenderMask( gui, &display, renderState, STENCIL_INCR ); + continue; + } + idSWFDictionaryEntry * entry = FindDictionaryEntry( display.characterID ); + if ( entry == NULL ) { + continue; + } + + swfRenderState_t renderState2; + + if ( spriteInstance->stereoDepth != STEREO_DEPTH_TYPE_NONE ) { + renderState2.stereoDepth = ( stereoDepthType_t )spriteInstance->stereoDepth; + } else if ( renderState.stereoDepth != STEREO_DEPTH_TYPE_NONE ) { + renderState2.stereoDepth = renderState.stereoDepth; + } + + renderState2.matrix = display.matrix.Multiply( renderState.matrix ); + renderState2.cxf = display.cxf.Multiply( renderState.cxf ); + renderState2.ratio = display.ratio; + if ( display.blendMode != 0 ) { + renderState2.blendMode = display.blendMode; + } else { + renderState2.blendMode = renderState.blendMode; + } + renderState2.activeMasks = renderState.activeMasks + activeMasks.Num(); + + if ( spriteInstance->materialOverride != NULL ) { + renderState2.material = spriteInstance->materialOverride; + renderState2.materialWidth = spriteInstance->materialWidth; + renderState2.materialHeight = spriteInstance->materialHeight; + } else { + renderState2.material = renderState.material; + renderState2.materialWidth = renderState.materialWidth; + renderState2.materialHeight = renderState.materialHeight; + } + + float xOffset = 0.0f; + float yOffset = 0.0f; + + if ( entry->type == SWF_DICT_SPRITE ) { + display.spriteInstance->SetAlignment( spriteInstance->xOffset, spriteInstance->yOffset ); + + if ( display.spriteInstance->name[0] == '_' ) { + //if ( display.spriteInstance->name.Icmp( "_leftAlign" ) == 0 ) { + // float adj = (float)frameWidth * 0.10; + // renderState2.matrix.tx = ( display.matrix.tx - adj ) * renderState.matrix.xx; + //} + //if ( display.spriteInstance->name.Icmp( "_rightAlign" ) == 0 ) { + // renderState2.matrix.tx = ( (float)renderSystem->GetWidth() - ( ( (float)frameWidth - display.matrix.tx - adj ) * renderState.matrix.xx ) ); + //} + + float widthAdj = swf_titleSafe.GetFloat() * frameWidth; + float heightAdj = swf_titleSafe.GetFloat() * frameHeight; + + const float pixelAspect = renderSystem->GetPixelAspect(); + const float sysWidth = renderSystem->GetWidth() * ( pixelAspect > 1.0f ? pixelAspect : 1.0f ); + const float sysHeight = renderSystem->GetHeight() / ( pixelAspect < 1.0f ? pixelAspect : 1.0f ); + + if ( display.spriteInstance->name.Icmp( "_fullScreen" ) == 0 ) { + renderState2.matrix.tx = display.matrix.tx * renderState.matrix.xx; + renderState2.matrix.ty = display.matrix.ty * renderState.matrix.yy; + + float xScale = sysWidth / (float)frameWidth; + float yScale = sysHeight / (float)frameHeight; + + renderState2.matrix.xx = xScale; + renderState2.matrix.yy = yScale; + } + + if ( display.spriteInstance->name.Icmp( "_absTop" ) == 0 ) { + renderState2.matrix.ty = display.matrix.ty * renderState.matrix.yy; + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_top" ) == 0 ) { + renderState2.matrix.ty = ( display.matrix.ty + heightAdj ) * renderState.matrix.yy; + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_topLeft" ) == 0 ) { + renderState2.matrix.tx = ( display.matrix.tx + widthAdj ) * renderState.matrix.xx; + renderState2.matrix.ty = ( display.matrix.ty + heightAdj ) * renderState.matrix.yy; + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_left" ) == 0 ) { + float prevX = renderState2.matrix.tx; + renderState2.matrix.tx = ( display.matrix.tx + widthAdj ) * renderState.matrix.xx; + xOffset = (( renderState2.matrix.tx - prevX ) / renderState.matrix.xx ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( idStr::FindText( display.spriteInstance->name, "_absLeft", false ) >= 0 ) { + float prevX = renderState2.matrix.tx; + renderState2.matrix.tx = display.matrix.tx * renderState.matrix.xx; + xOffset = (( renderState2.matrix.tx - prevX ) / renderState.matrix.xx ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_bottomLeft" ) == 0 ) { + float prevX = renderState2.matrix.tx; + renderState2.matrix.tx = ( display.matrix.tx + widthAdj ) * renderState.matrix.xx; + xOffset = (( renderState2.matrix.tx - prevX ) / renderState.matrix.xx ); + + + float prevY = renderState2.matrix.ty; + renderState2.matrix.ty = ( (float)sysHeight - ( ( (float)frameHeight - display.matrix.ty + heightAdj ) * renderState.matrix.yy ) ); + yOffset = (( renderState2.matrix.ty - prevY ) / renderState.matrix.yy ); + + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_absBottom" ) == 0 ) { + renderState2.matrix.ty = ( (float)sysHeight - ( ( (float)frameHeight - display.matrix.ty ) * renderState.matrix.yy ) ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_bottom" ) == 0 ) { + renderState2.matrix.ty = ( (float)sysHeight - ( ( (float)frameHeight - display.matrix.ty + heightAdj ) * renderState.matrix.yy ) ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_topRight" ) == 0 ) { + renderState2.matrix.tx = ( (float)sysWidth - ( ( (float)frameWidth - display.matrix.tx + widthAdj ) * renderState.matrix.xx ) ); + renderState2.matrix.ty = ( display.matrix.ty + heightAdj ) * renderState.matrix.yy; + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_right" ) == 0 ) { + float prevX = renderState2.matrix.tx; + renderState2.matrix.tx = ( (float)sysWidth - ( ( (float)frameWidth - display.matrix.tx + widthAdj ) * renderState.matrix.xx ) ); + xOffset = (( renderState2.matrix.tx - prevX ) / renderState.matrix.xx ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( idStr::FindText( display.spriteInstance->name, "_absRight", true ) >= 0 ) { + float prevX = renderState2.matrix.tx; + renderState2.matrix.tx = ( (float)sysWidth - ( ( (float)frameWidth - display.matrix.tx ) * renderState.matrix.xx ) ); + xOffset = (( renderState2.matrix.tx - prevX ) / renderState.matrix.xx ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_bottomRight" ) == 0 ) { + renderState2.matrix.tx = ( (float)sysWidth - ( ( (float)frameWidth - display.matrix.tx + widthAdj ) * renderState.matrix.xx ) ); + renderState2.matrix.ty = ( (float)sysHeight - ( ( (float)frameHeight - display.matrix.ty + heightAdj ) * renderState.matrix.yy ) ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_absTopLeft" ) == 0 ) { // ABSOLUTE CORNERS OF SCREEN + renderState2.matrix.tx = display.matrix.tx * renderState.matrix.xx; + renderState2.matrix.ty = display.matrix.ty * renderState.matrix.yy; + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_absTopRight" ) == 0 ) { + renderState2.matrix.tx = ( (float)sysWidth - ( ( (float)frameWidth - display.matrix.tx ) * renderState.matrix.xx ) ); + renderState2.matrix.ty = display.matrix.ty * renderState.matrix.yy; + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_absBottomLeft" ) == 0 ) { + renderState2.matrix.tx = display.matrix.tx * renderState.matrix.xx; + renderState2.matrix.ty = ( (float)sysHeight - ( ( (float)frameHeight - display.matrix.ty ) * renderState.matrix.yy ) ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } else if ( display.spriteInstance->name.Icmp( "_absBottomRight" ) == 0 ) { + renderState2.matrix.tx = ( (float)sysWidth - ( ( (float)frameWidth - display.matrix.tx ) * renderState.matrix.xx ) ); + renderState2.matrix.ty = ( (float)sysHeight - ( ( (float)frameHeight - display.matrix.ty ) * renderState.matrix.yy ) ); + display.spriteInstance->SetAlignment( spriteInstance->xOffset + xOffset, spriteInstance->yOffset + yOffset ); + } + } + + RenderSprite( gui, display.spriteInstance, renderState2, time, isSplitscreen ); + } else if ( entry->type == SWF_DICT_SHAPE ) { + RenderShape( gui, entry->shape, renderState2 ); + } else if ( entry->type == SWF_DICT_MORPH ) { + RenderMorphShape( gui, entry->shape, renderState2 ); + } else if ( entry->type == SWF_DICT_EDITTEXT ) { + RenderEditText( gui, display.textInstance, renderState2, time, isSplitscreen ); + } else { + //idLib::Warning( "%s: Tried to render an unrenderable character %d", filename.c_str(), entry->type ); + } + } + for ( int j = 0; j < activeMasks.Num(); j++ ) { + const swfDisplayEntry_t * mask = activeMasks[ j ]; + RenderMask( gui, mask, renderState, STENCIL_DECR ); + } +} + +/* +======================== +idSWF::GLStateForBlendMode +======================== +*/ +uint64 idSWF::GLStateForRenderState( const swfRenderState_t & renderState ) { + uint64 extraGLState = GLS_OVERRIDE | GLS_DEPTHFUNC_LESS | GLS_DEPTHMASK; // SWF GL State always overrides what's set in the material + + if ( renderState.activeMasks > 0 ) { + extraGLState |= GLS_STENCIL_FUNC_EQUAL | GLS_STENCIL_MAKE_REF( 128 + renderState.activeMasks ) | GLS_STENCIL_MAKE_MASK( 255 ); + } else if ( renderState.activeMasks == STENCIL_INCR ) { + return GLS_COLORMASK | GLS_ALPHAMASK | GLS_STENCIL_OP_FAIL_KEEP | GLS_STENCIL_OP_ZFAIL_KEEP | GLS_STENCIL_OP_PASS_INCR; + } else if ( renderState.activeMasks == STENCIL_DECR ) { + return GLS_COLORMASK | GLS_ALPHAMASK | GLS_STENCIL_OP_FAIL_KEEP | GLS_STENCIL_OP_ZFAIL_KEEP | GLS_STENCIL_OP_PASS_DECR; + } + + switch ( renderState.blendMode ) { + case 7: // difference : dst = abs( dst - src ) + case 9: // subtract : dst = dst - src + return extraGLState | ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_BLENDOP_SUB ); + case 8: // add : dst = dst + src + return extraGLState | ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + case 6: // darken : dst = min( dst, src ) + return extraGLState | ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_BLENDOP_MIN ); + case 5: // lighten : dst = max( dst, src ) + return extraGLState | ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_BLENDOP_MAX ); + case 4: // screen : dst = dst + src - dst*src ( we only do dst - dst * src, we could do the extra + src with another pass if we need to) + return extraGLState | ( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_BLENDOP_SUB ); + case 14: // hardlight : src < 0.5 ? multiply : screen + case 13: // overlay : dst < 0.5 ? multiply : screen + case 3: // multiply : dst = ( dst * src ) + ( dst * (1-src.a) ) + return extraGLState | ( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + case 12: // erase + case 11: // alpha + case 10: // invert + case 2: // layer + case 1: // normal + case 0: // normaler + default: + return extraGLState | ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } +} + +/* +======================== +idSWF::RenderMorphShape +======================== +*/ +void idSWF::RenderMorphShape( idRenderSystem * gui, const idSWFShape * shape, const swfRenderState_t & renderState ) { + if ( shape == NULL ) { + idLib::Warning( "%s: RenderMorphShape: shape == NULL", filename.c_str() ); + return; + } + + for ( int i = 0; i < shape->fillDraws.Num(); i++ ) { + const idSWFShapeDrawFill & fill = shape->fillDraws[i]; + const idMaterial * material = NULL; + swfColorXform_t color; + + if ( renderState.material != NULL ) { + material = renderState.material; + } else if ( fill.style.type == 0 ) { + material = guiSolid; + idVec4 startColor = fill.style.startColor.ToVec4(); + idVec4 endColor = fill.style.endColor.ToVec4(); + color.mul = Lerp( startColor, endColor, renderState.ratio ); + } else if ( fill.style.type == 4 && fill.style.bitmapID != 65535 ) { + material = dictionary[ fill.style.bitmapID ].material; + } else { + material = guiSolid; + } + color = color.Multiply( renderState.cxf ); + if ( swf_forceAlpha.GetFloat() > 0.0f ) { + color.mul.w = swf_forceAlpha.GetFloat(); + color.add.w = 0.0f; + } + if ( ( color.mul.w + color.add.w ) <= ALPHA_EPSILON ) { + continue; + } + uint32 packedColorM = LittleLong( PackColor( color.mul ) ); + uint32 packedColorA = LittleLong( PackColor( ( color.add * 0.5f ) + idVec4( 0.5f ) ) ); // Compress from -1..1 to 0..1 + + swfRect_t bounds; + bounds.tl = Lerp( shape->startBounds.tl, shape->endBounds.tl, renderState.ratio ); + bounds.br = Lerp( shape->startBounds.br, shape->endBounds.br, renderState.ratio ); + idVec2 size( material->GetImageWidth(), material->GetImageHeight() ); + if ( renderState.materialWidth > 0 ) { + size.x = renderState.materialWidth; + } + if ( renderState.materialHeight > 0 ) { + size.y = renderState.materialHeight; + } + idVec2 oneOverSize( 1.0f / size.x, 1.0f / size.y ); + + swfMatrix_t styleMatrix; + styleMatrix.xx = Lerp( fill.style.startMatrix.xx, fill.style.endMatrix.xx, renderState.ratio ); + styleMatrix.yy = Lerp( fill.style.startMatrix.yy, fill.style.endMatrix.yy, renderState.ratio ); + styleMatrix.xy = Lerp( fill.style.startMatrix.xy, fill.style.endMatrix.xy, renderState.ratio ); + styleMatrix.yx = Lerp( fill.style.startMatrix.yx, fill.style.endMatrix.yx, renderState.ratio ); + styleMatrix.tx = Lerp( fill.style.startMatrix.tx, fill.style.endMatrix.tx, renderState.ratio ); + styleMatrix.ty = Lerp( fill.style.startMatrix.ty, fill.style.endMatrix.ty, renderState.ratio ); + + swfMatrix_t invMatrix = styleMatrix.Inverse(); + + gui->SetGLState( GLStateForRenderState( renderState ) ); + + idDrawVert * verts = gui->AllocTris( fill.startVerts.Num(), fill.indices.Ptr(), fill.indices.Num(), material, renderState.stereoDepth ); + if ( verts == NULL ) { + continue; + } + + for ( int j = 0; j < fill.startVerts.Num(); j++ ) { + idVec2 xy = Lerp( fill.startVerts[j], fill.endVerts[j], renderState.ratio ); + + idVec2 st; + st.x = ( ( xy.x - bounds.tl.x ) * oneOverSize.x ) * 20.0f; + st.y = ( ( xy.y - bounds.tl.y ) * oneOverSize.y ) * 20.0f; + idVec2 adjust( 0.5f * oneOverSize.x, 0.5f * oneOverSize.y ); + + ALIGNTYPE16 idDrawVert tempVert; + + tempVert.Clear(); + tempVert.xyz.ToVec2() = renderState.matrix.Transform( xy ).Scale( scaleToVirtual ); + tempVert.xyz.z = 0.0f; + tempVert.SetTexCoord( invMatrix.Transform( st ) + adjust ); + tempVert.SetNativeOrderColor( packedColorM ); + tempVert.SetNativeOrderColor2( packedColorA ); + + WriteDrawVerts16( & verts[j], & tempVert, 1 ); + } + } +} + +/* +======================== +idSWF::RenderShape +======================== +*/ +void idSWF::RenderShape( idRenderSystem * gui, const idSWFShape * shape, const swfRenderState_t & renderState ) { + if ( shape == NULL ) { + idLib::Warning( "%s: RenderShape: shape == NULL", filename.c_str() ); + return; + } + + for ( int i = 0; i < shape->fillDraws.Num(); i++ ) { + const idSWFShapeDrawFill & fill = shape->fillDraws[i]; + const idMaterial * material = NULL; + swfColorXform_t color; + + swfMatrix_t invMatrix; + idVec2 atlasScale( 0.0f, 0.0f ); + idVec2 atlasBias( 0.0f, 0.0f ); + bool useAtlas = false; + + idVec2 size( 1.0f, 1.0f ); + + if ( renderState.material != NULL ) { + material = renderState.material; + invMatrix.xx = invMatrix.yy = ( 1.0f / 20.0f ); + } else if ( fill.style.type == 0 ) { + material = guiSolid; + color.mul = fill.style.startColor.ToVec4(); + } else if ( fill.style.type == 4 && fill.style.bitmapID != 65535 ) { + // everything in a single image atlas + idSWFDictionaryEntry * entry = &dictionary[ fill.style.bitmapID ]; + material = atlasMaterial; + idVec2i atlasSize( material->GetImageWidth(), material->GetImageHeight() ); + for ( int i = 0 ; i < 2 ; i++ ) { + size[i] = entry->imageSize[i]; + atlasScale[i] = (float)size[i] / atlasSize[i]; + atlasBias[i] = (float)entry->imageAtlasOffset[i] / atlasSize[i]; + } + // de-normalize color channels after DXT decompression + color.mul = entry->channelScale; + useAtlas = true; + + const swfMatrix_t & styleMatrix = fill.style.startMatrix; + invMatrix = styleMatrix.Inverse(); + } else { + material = guiSolid; + } + color = color.Multiply( renderState.cxf ); + if ( swf_forceAlpha.GetFloat() > 0.0f ) { + color.mul.w = swf_forceAlpha.GetFloat(); + color.add.w = 0.0f; + } + if ( ( color.mul.w + color.add.w ) <= ALPHA_EPSILON ) { + continue; + } + + uint32 packedColorM = LittleLong( PackColor( color.mul ) ); + uint32 packedColorA = LittleLong( PackColor( ( color.add * 0.5f ) + idVec4( 0.5f ) ) ); // Compress from -1..1 to 0..1 + + const swfRect_t & bounds = shape->startBounds; + if ( renderState.materialWidth > 0 ) { + size.x = renderState.materialWidth; + } + if ( renderState.materialHeight > 0 ) { + size.y = renderState.materialHeight; + } + idVec2 oneOverSize( 1.0f / size.x, 1.0f / size.y ); + + gui->SetGLState( GLStateForRenderState( renderState ) ); + + idDrawVert * verts = gui->AllocTris( fill.startVerts.Num(), fill.indices.Ptr(), fill.indices.Num(), material, renderState.stereoDepth ); + if ( verts == NULL ) { + continue; + } + + ALIGNTYPE16 idDrawVert tempVerts[4]; + for ( int j = 0; j < fill.startVerts.Num(); j++ ) { + const idVec2 & xy = fill.startVerts[j]; + + idDrawVert & vert = tempVerts[j & 3]; + + vert.Clear(); + vert.xyz.ToVec2() = renderState.matrix.Transform( xy ).Scale( scaleToVirtual ); + vert.xyz.z = 0.0f; + vert.SetNativeOrderColor( packedColorM ); + vert.SetNativeOrderColor2( packedColorA ); + + // For some reason I don't understand, having texcoords + // in the range of 2000 or so causes what should be solid + // fill areas to have horizontal bands on nvidia, but not 360. + // Forcing the texcoords to zero fixes it. + if ( fill.style.type != 0 ) { + idVec2 st; + // all the swf vertexes have an implicit scale of 1/20 for some reason... + st.x = ( ( xy.x - bounds.tl.x ) * oneOverSize.x ) * 20.0f; + st.y = ( ( xy.y - bounds.tl.y ) * oneOverSize.y ) * 20.0f; + st = invMatrix.Transform( st ); + if ( useAtlas ) { + st = st.Scale( atlasScale ) + atlasBias; + } + + // inset the tc - the gui may use a vmtr and the tc might end up + // crossing page boundaries if using [0.0,1.0] + st.x = idMath::ClampFloat( 0.001f, 0.999f, st.x ); + st.y = idMath::ClampFloat( 0.001f, 0.999f, st.y ); + vert.SetTexCoord( st ); + } + + // write four verts at a time to video memory + if ( ( j & 3 ) == 3 ) { + WriteDrawVerts16( & verts[j & ~3], tempVerts, 4 ); + } + } + // write any remaining verts to video memory + WriteDrawVerts16( & verts[fill.startVerts.Num() & ~3], tempVerts, fill.startVerts.Num() & 3 ); + } + + for ( int i = 0; i < shape->lineDraws.Num(); i++ ) { + const idSWFShapeDrawLine & line = shape->lineDraws[i]; + swfColorXform_t color; + color.mul = line.style.startColor.ToVec4(); + color = color.Multiply( renderState.cxf ); + if ( swf_forceAlpha.GetFloat() > 0.0f ) { + color.mul.w = swf_forceAlpha.GetFloat(); + color.add.w = 0.0f; + } + if ( ( color.mul.w + color.add.w ) <= ALPHA_EPSILON ) { + continue; + } + uint32 packedColorM = LittleLong( PackColor( color.mul ) ); + uint32 packedColorA = LittleLong( PackColor( ( color.add * 0.5f ) + idVec4( 0.5f ) ) ); // Compress from -1..1 to 0..1 + + gui->SetGLState( GLStateForRenderState( renderState ) | GLS_POLYMODE_LINE ); + + idDrawVert * verts = gui->AllocTris( line.startVerts.Num(), line.indices.Ptr(), line.indices.Num(), white, renderState.stereoDepth ); + if ( verts == NULL ) { + continue; + } + + for ( int j = 0; j < line.startVerts.Num(); j++ ) { + const idVec2 & xy = line.startVerts[j]; + + ALIGNTYPE16 idDrawVert tempVert; + + tempVert.Clear(); + tempVert.xyz.ToVec2() = renderState.matrix.Transform( xy ).Scale( scaleToVirtual ); + tempVert.xyz.z = 0.0f; + tempVert.SetTexCoord( 0.0f, 0.0f ); + tempVert.SetNativeOrderColor( packedColorM ); + tempVert.SetNativeOrderColor2( packedColorA ); + + WriteDrawVerts16( & verts[j], & tempVert, 1 ); + } + } +} + +/* +======================== +idSWF::DrawEditCursor +======================== +*/ +void idSWF::DrawEditCursor( idRenderSystem * gui, float x, float y, float w, float h, const swfMatrix_t & matrix ) { + idVec2 topl = matrix.Transform( idVec2( x, y ) ); + idVec2 topr = matrix.Transform( idVec2( x + w, y ) ); + idVec2 br = matrix.Transform( idVec2( x + w, y + h ) ); + idVec2 bl = matrix.Transform( idVec2( x, y + h ) ); + DrawStretchPic( idVec4( topl.x, topl.y, 0.0f, 0.0f ), idVec4( topr.x, topr.y, 1.0f, 0.0f ), idVec4( br.x, br.y, 1.0f, 1.0f ), idVec4( bl.x, bl.y, 0.0f, 1.0f ), white ); +} + +/* +======================== +idSWF::RenderEditText +======================== +*/ +void idSWF::RenderEditText( idRenderSystem * gui, idSWFTextInstance * textInstance, const swfRenderState_t & renderState, int time, bool isSplitscreen ) { + if ( textInstance == NULL ) { + idLib::Warning( "%s: RenderEditText: textInstance == NULL", filename.c_str() ); + return; + } + + if ( !textInstance->visible ) { + return; + } + + const idSWFEditText * shape = textInstance->editText; + + idStr text; + + if ( textInstance->variable.IsEmpty() ) { + if ( textInstance->renderMode == SWF_TEXT_RENDER_PARAGRAPH ) { + if ( textInstance->NeedsGenerateRandomText() ) { + textInstance->StartParagraphText( Sys_Milliseconds() ); + } + text = textInstance->GetParagraphText( Sys_Milliseconds() ); + } else if ( textInstance->renderMode == SWF_TEXT_RENDER_RANDOM_APPEAR || textInstance->renderMode == SWF_TEXT_RENDER_RANDOM_APPEAR_CAPS ) { + if ( textInstance->NeedsGenerateRandomText() ) { + textInstance->StartRandomText( Sys_Milliseconds() ); + } + text = textInstance->GetRandomText( Sys_Milliseconds() ); + } else { + text = idLocalization::GetString( textInstance->text ); + } + } else { + idSWFScriptVar var = globals->Get( textInstance->variable ); + if ( var.IsUndefined() ) { + text = idLocalization::GetString( textInstance->text ); + } else { + text = idLocalization::GetString( var.ToString() ); + } + } + + if ( text.Length() == 0 ) { + textInstance->selectionEnd = -1; + textInstance->selectionStart = -1; + } + + if ( textInstance->NeedsSoundPlayed() ) { + PlaySound( textInstance->GetSoundClip() ); + textInstance->ClearPlaySound(); + } + + if ( textInstance->tooltip ) { + FindTooltipIcons( &text ); + } else { + tooltipIconList.Clear(); + } + + int selStart = textInstance->selectionStart; + int selEnd = textInstance->selectionEnd; + + int cursorPos = selEnd; + + bool inputField = false; + + idSWFScriptVar focusWindow = globals->Get( "focusWindow" ); + if ( focusWindow.IsObject() && focusWindow.GetObject() == &textInstance->scriptObject ) { + inputField = true; + } + + bool drawCursor = false; + + if ( inputField && ( ( idLib::frameNumber >> 4 ) & 1 ) == 0 ) { + cursorPos = selEnd; + drawCursor = true; + } + if ( selStart > selEnd ) { + SwapValues( selStart, selEnd ); + } + + idVec2 xScaleVec = renderState.matrix.Scale( idVec2( 1.0f, 0.0f ) ); + idVec2 yScaleVec = renderState.matrix.Scale( idVec2( 0.0f, 1.0f ) ); + + float xScale = xScaleVec.Length(); + float yScale = yScaleVec.Length(); + + if ( isSplitscreen ) { + yScale *= 0.5f; + } + + float invXScale = 1.0f / xScale; + float invYScale = 1.0f / yScale; + + swfMatrix_t matrix = renderState.matrix; + matrix.xx *= invXScale; + matrix.xy *= invXScale; + matrix.yy *= invYScale; + matrix.yx *= invYScale; + + idSWFDictionaryEntry * fontEntry = FindDictionaryEntry( shape->fontID, SWF_DICT_FONT ); + if ( fontEntry == NULL ) { + idLib::Warning( "idSWF::RenderEditText: NULL Font" ); + return; + } + idSWFFont * swfFont = fontEntry->font; + + float postTransformHeight = SWFTWIP( shape->fontHeight ) * yScale; + + const idFont * fontInfo = swfFont->fontID; + + float glyphScale = postTransformHeight / 48.0f; + float imageScale = postTransformHeight / 24.0f; + textInstance->glyphScale = glyphScale; + + idVec4 defaultColor = textInstance->color.ToVec4(); + defaultColor = defaultColor.Multiply( renderState.cxf.mul ) + renderState.cxf.add; + if ( swf_forceAlpha.GetFloat() > 0.0f ) { + defaultColor.w = swf_forceAlpha.GetFloat(); + } + if ( defaultColor.w <= ALPHA_EPSILON ) { + return; + } + + idVec4 selColor( defaultColor ); + selColor.w *= 0.5f; + + gui->SetColor( defaultColor ); + gui->SetGLState( GLStateForRenderState( renderState ) ); + + swfRect_t bounds; + bounds.tl.x = xScale * ( shape->bounds.tl.x + SWFTWIP( shape->leftMargin ) ); + bounds.br.x = xScale * ( shape->bounds.br.x - SWFTWIP( shape->rightMargin ) ); + + float linespacing = fontInfo->GetAscender( 1.15f * glyphScale ); + if ( shape->leading != 0 ) { + linespacing += SWFTWIP( shape->leading ); + } + + bounds.tl.y = yScale * ( shape->bounds.tl.y + ( 1.15f * glyphScale ) ); + bounds.br.y = yScale * ( shape->bounds.br.y ); + + textInstance->linespacing = linespacing; + textInstance->bounds = bounds; + + if ( shape->flags & SWF_ET_AUTOSIZE ) { + bounds.br.x = frameWidth; + bounds.br.y = frameHeight; + } + + if ( drawCursor && cursorPos <= 0 ) { + float yPos = 0.0f; + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, ' ', glyph ); + yPos = glyph.height / 2.0f; + DrawEditCursor( gui, bounds.tl.x, yPos, 1.0f, linespacing, matrix ); + } + + if ( textInstance->IsSubtitle() ) { + if ( text.IsEmpty() && textInstance->subtitleText.IsEmpty() ) { + return; + } + } else if ( text.IsEmpty() ) { + return; + } + + float x = bounds.tl.x; + float y = bounds.tl.y; + + int maxLines = idMath::Ftoi( ( bounds.br.y - bounds.tl.y ) / linespacing ); + if ( maxLines == 0 ) { + maxLines = 1; + } + + textInstance->maxLines = maxLines; + + idList< idStr > textLines; + idStr * currentLine = &textLines.Alloc(); + + // tracks the last breakable character we found + int lastbreak = 0; + float lastbreakX = 0; + + bool insertingImage = false; + int iconIndex = 0; + + int charIndex = 0; + + if ( textInstance->IsSubtitle() ) { + charIndex = textInstance->GetSubStartIndex(); + } + + while ( charIndex < text.Length() ) { + if ( text[ charIndex ] == '\n' ) { + if ( shape->flags & SWF_ET_MULTILINE ) { + currentLine->Append( '\n' ); + x = bounds.tl.x; + y += linespacing; + currentLine = &textLines.Alloc(); + lastbreak = 0; + charIndex++; + continue; + } else { + break; + } + } + int glyphStart = charIndex; + uint32 tc = text.UTF8Char( charIndex ); + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, tc, glyph ); + float glyphSkip = glyph.xSkip; + if ( textInstance->HasStroke() ) { + glyphSkip += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * textInstance->GetStrokeWeight() * glyphScale ); + } + + tooltipIcon_t iconCheck; + + if ( iconIndex < tooltipIconList.Num() ) { + iconCheck = tooltipIconList[iconIndex]; + } + + float imageSkip = 0.0f; + + if ( charIndex - 1 == iconCheck.startIndex ) { + insertingImage = true; + imageSkip = iconCheck.imageWidth * imageScale; + } else if ( charIndex - 1 == iconCheck.endIndex ) { + insertingImage = false; + iconIndex++; + glyphSkip = 0.0f; + } + + if ( insertingImage ) { + glyphSkip = 0.0f; + } + + if ( !inputField ) { // only break lines of text when we are not inputting data + if ( x + glyphSkip > bounds.br.x || x + imageSkip > bounds.br.x ) { + if ( shape->flags & ( SWF_ET_MULTILINE | SWF_ET_WORDWRAP ) ) { + if ( lastbreak > 0 ) { + int curLineIndex = currentLine - &textLines[0]; + idStr * newline = &textLines.Alloc(); + currentLine = &textLines[ curLineIndex ]; + if ( maxLines == 1 ) { + currentLine->CapLength( currentLine->Length() - 3 ); + currentLine->Append( "..." ); + break; + } else { + *newline = currentLine->c_str() + lastbreak; + currentLine->CapLength( lastbreak ); + currentLine = newline; + x -= lastbreakX; + } + } else { + currentLine = &textLines.Alloc(); + x = bounds.tl.x; + } + lastbreak = 0; + } else { + break; + } + } + } + while ( glyphStart < charIndex && glyphStart < text.Length() ) { + currentLine->Append( text[ glyphStart++ ] ); + } + x += glyphSkip + imageSkip; + if ( tc == ' ' || tc == '-' ) { + lastbreak = currentLine->Length(); + lastbreakX = x; + } + } + + // Subtitle functionality + if ( textInstance->IsSubtitle() && textInstance->IsUpdatingSubtitle() ) { + if ( textLines.Num() > 0 && textInstance->SubNeedsSwitch() ) { + + int lastWordIndex = textInstance->GetApporoximateSubtitleBreak( time ); + int newEndChar = textInstance->GetSubStartIndex() + textLines[0].Length(); + + int wordCount = 0; + bool earlyOut = false; + for ( int index = 0; index < textLines[0].Length(); ++index ) { + if ( textLines[0][index] == ' ' || textLines[0][index] == '-' ) { + if ( index != 0 ) { + if ( wordCount == lastWordIndex ) { + newEndChar = textInstance->GetSubStartIndex() + index; + earlyOut = true; + break; + } + + // cover the double space at the beginning of sentences + if ( index > 0 && textLines[0][index - 1 ] != ' ' ) { + wordCount++; + } + } + } else if ( index == textLines[0].Length() ) { + if ( wordCount == lastWordIndex ) { + newEndChar = textInstance->GetSubStartIndex() + index; + earlyOut = true; + break; + } + wordCount++; + } + } + + if ( wordCount <= 0 && textLines[0].Length() > 0 ) { + wordCount = 1; + } + + if ( !earlyOut ) { + textInstance->LastWordChanged( wordCount, time ); + } + + textInstance->SetSubEndIndex( newEndChar, time ); + + idStr subText = textLines[0].Left( newEndChar - textInstance->GetSubStartIndex() ); + idSWFParmList parms; + parms.Append( subText ); + parms.Append( textInstance->GetSpeaker().c_str() ); + parms.Append( textInstance->GetSubAlignment() ); + Invoke( "subtitleChanged", parms ); + parms.Clear(); + + textInstance->SetSubNextStartIndex( textInstance->GetSubEndIndex() ); + textInstance->SwitchSubtitleText( time ); + } + + if ( !textInstance->UpdateSubtitle( time ) ) { + textInstance->SubtitleComplete(); + idSWFParmList parms; + parms.Append( textInstance->GetSubAlignment() ); + Invoke( "subtitleComplete", parms ); + parms.Clear(); + textInstance->SubtitleCleanup(); + } + } + + //************************************************* + // CALCULATE THE NUMBER OF SCROLLS LINES LEFT + //************************************************* + + textInstance->CalcMaxScroll( textLines.Num() - maxLines ); + + int c = 1; + int textLine = textInstance->scroll; + + if ( textLine + maxLines > textLines.Num() && maxLines < textLines.Num() ) { + textLine = textLines.Num() - maxLines; + textInstance->scroll = textLine; + } else if ( textLine < 0 || textLines.Num() <= maxLines ) { + textLine = 0; + textInstance->scroll = textLine; + } else if ( textInstance->renderMode == SWF_TEXT_RENDER_AUTOSCROLL ) { + textLine = textLines.Num() - maxLines; + textInstance->scroll = textInstance->maxscroll; + } + + // END SCROLL CALCULATION + //************************************************* + + int index = 0; + + int startCharacter = 0; + int endCharacter = 0; + int inputEndChar = 0; + iconIndex = 0; + int overallIndex = 0; + int curIcon = 0; + float yPrevBottomOffset = 0.0f; + float yOffset = 0; + + int strokeXOffsets[] = { -1, 1, -1, 1 }; + int strokeYOffsets[] = { -1, -1, 1, 1 }; + + idStr inputText; + if ( inputField ) { + if ( textLines.Num() > 0 ) { + idStr & text = textLines[0]; + float left = bounds.tl.x; + + int startCheckIndex = textInstance->GetInputStartChar(); + + if ( startCheckIndex >= text.Length() ) { + startCheckIndex = 0; + } + + if ( cursorPos < startCheckIndex && cursorPos >= 0 ) { + startCheckIndex = cursorPos; + } + + bool endFound = false; + int c = startCheckIndex; + while ( c < text.Length() ) { + uint32 tc = text.UTF8Char( c ); + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, tc, glyph ); + float glyphSkip = glyph.xSkip; + if ( textInstance->HasStroke() ) { + glyphSkip += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * textInstance->GetStrokeWeight() * glyphScale ); + } + + if ( left + glyphSkip > bounds.br.x ) { + if ( cursorPos > c && cursorPos != endCharacter ) { + + float removeSize = 0.0f; + + while ( removeSize < glyphSkip ) { + if ( endCharacter == c ) { + break; + } + scaledGlyphInfo_t removeGlyph; + fontInfo->GetScaledGlyph( glyphScale, inputText[ endCharacter++ ], removeGlyph ); + removeSize += removeGlyph.xSkip; + } + + left -= removeSize; + } else { + inputEndChar = c; + endFound = true; + break; + } + } + inputText.AppendUTF8Char( tc ); + left += glyphSkip; + } + + if ( !endFound ) { + inputEndChar = text.Length(); + } + + startCheckIndex += endCharacter; + textInstance->SetInputStartCharacter( startCheckIndex ); + endCharacter = startCheckIndex; + } + } + + for ( int t = 0; t < textLines.Num(); t++ ) { + + if ( textInstance->IsSubtitle() && t > 0 ) { + break; + } + + if ( t < textLine ) { + idStr & text = textLines[t]; + c += text.Length(); + startCharacter = endCharacter; + endCharacter = startCharacter + text.Length(); + overallIndex += text.Length(); + + // find the right icon index if we scrolled passed the previous ones + for ( int iconChar = curIcon; iconChar < tooltipIconList.Num(); ++iconChar ) { + if ( endCharacter > tooltipIconList[iconChar].startIndex ) { + curIcon++; + } else { + break; + } + } + + continue; + } + + if ( index == maxLines ) { + break; + } + + startCharacter = endCharacter; + + idStr & text = textLines[textLine]; + int lastChar = text.Length(); + if ( textInstance->IsSubtitle() ) { + lastChar = textInstance->GetSubEndIndex(); + } + + textLine++; + + if ( inputField ) { + if ( inputEndChar == 0 ) { + inputEndChar += 1; + } + selStart -= startCharacter; + selEnd -= startCharacter; + cursorPos -= startCharacter; + endCharacter = inputEndChar; + lastChar = endCharacter; + text = text.Mid( startCharacter, endCharacter - startCharacter ); + } else { + + if ( lastChar == 0 ) { + // blank line so add space char + endCharacter = startCharacter + 1; + } else { + endCharacter = startCharacter + lastChar; + } + } + + float width = 0.0f; + insertingImage = false; + int i = 0; + while ( i < lastChar ) { + if ( curIcon < tooltipIconList.Num() && tooltipIconList[curIcon].startIndex == startCharacter + i ) { + width += tooltipIconList[curIcon].imageWidth * imageScale; + i += tooltipIconList[curIcon].endIndex - tooltipIconList[curIcon].startIndex - 1; + curIcon++; + } else { + if ( i < text.Length() ) { + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, text.UTF8Char( i ), glyph ); + width += glyph.xSkip; + if ( textInstance->HasStroke() ) { + width += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * textInstance->GetStrokeWeight() * glyphScale ); + } + } else { + i++; + } + } + } + + y = bounds.tl.y + ( index * linespacing ); + + float biggestGlyphHeight = 0.0f; + /*for ( int image = 0; image < tooltipIconList.Num(); ++image ) { + if ( tooltipIconList[image].startIndex >= startCharacter && tooltipIconList[image].endIndex < endCharacter ) { + biggestGlyphHeight = tooltipIconList[image].imageHeight > biggestGlyphHeight ? tooltipIconList[image].imageHeight : biggestGlyphHeight; + } + }*/ + + float yBottomOffset = 0.0f; + float yTopOffset = 0.0f; + + if ( biggestGlyphHeight > 0.0f ) { + + float topSpace = 0.0f; + float bottomSpace = 0.0f; + + int idx = 0; + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, text.UTF8Char( idx ), glyph ); + + topSpace = ( ( biggestGlyphHeight * imageScale ) - glyph.height ) / 2.0f; + + bottomSpace = topSpace; + + if ( topSpace > 0.0f && t != 0 ) { + yTopOffset += topSpace; + } + + if ( bottomSpace > 0.0f ) { + yBottomOffset += bottomSpace; + } + } else { + yBottomOffset = 0.0f; + } + + if ( t != 0 ) { + if ( yPrevBottomOffset > 0 || yTopOffset > 0 ) { + yOffset += yTopOffset > yPrevBottomOffset ? yTopOffset : yPrevBottomOffset; + } + } + + y += yOffset; + yPrevBottomOffset = yBottomOffset; + + float extraSpace = 0.0f; + switch ( shape->align ) { + case SWF_ET_ALIGN_LEFT: + x = bounds.tl.x; + break; + case SWF_ET_ALIGN_RIGHT: + x = bounds.br.x - width; + break; + case SWF_ET_ALIGN_CENTER: + x = ( bounds.tl.x + bounds.br.x - width ) * 0.5f; + break; + case SWF_ET_ALIGN_JUSTIFY: + x = bounds.tl.x; + if ( width > ( bounds.br.x - bounds.tl.x ) * 0.5f && index < textLines.Num() - 1 ) { + extraSpace = ( ( bounds.br.x - bounds.tl.x ) - width ) / ( (float) lastChar - 1.0f ); + } + break; + } + + tooltipIcon_t icon; + insertingImage = false; + + // find the right icon index if we scrolled passed the previous ones + for ( int iconChar = iconIndex; iconChar < tooltipIconList.Num(); ++iconChar ) { + if ( overallIndex > tooltipIconList[iconChar].startIndex ) { + iconIndex++; + } else { + break; + } + } + + float baseLine = y + ( fontInfo->GetAscender( glyphScale ) ); + + i = 0; + int overallLineIndex = 0; + idVec4 textColor = defaultColor; + while ( i < lastChar ) { + + if ( i >= text.Length() ) { + break; + } + + // Support colors + if ( !textInstance->ignoreColor ) { + if ( text[ i ] == C_COLOR_ESCAPE ) { + if ( idStr::IsColor( text.c_str() + i++ ) ) { + if ( text[ i ] == C_COLOR_DEFAULT ) { + i++; + textColor = defaultColor; + } else { + textColor = idStr::ColorForIndex( text[ i++ ] ); + textColor.w = defaultColor.w; + } + continue; + } + } + } + + uint32 character = text.UTF8Char( i ); + + if ( character == '\n' ) { + c++; + overallIndex += i - overallLineIndex; + overallLineIndex = i;; + continue; + } + + // Skip a single leading space + if ( character == ' ' && i == 1 ) { + c++; + overallIndex += i - overallLineIndex; + overallLineIndex = i; + continue; + } + + if ( iconIndex < tooltipIconList.Num() ) { + icon = tooltipIconList[iconIndex]; + } + + if ( overallIndex == icon.startIndex ) { + insertingImage = true; + + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, character, glyph ); + + float imageHeight = icon.imageHeight * imageScale; + float glyphHeight = glyph.height; + + float imageY = 0.0f; + if ( icon.baseline == 0 ) { + imageY = baseLine - glyph.top; + imageY += ( glyphHeight - imageHeight ) * 0.5f; + imageY += 2.0f; + } else { + imageY = ( y + glyphHeight ) - ( ( icon.imageHeight * imageScale ) - ( glyphHeight ) ); + } + + float imageX = x + glyph.left; + float imageW = icon.imageWidth * imageScale; + float imageH = icon.imageHeight * imageScale; + + idVec2 topl = matrix.Transform( idVec2( imageX, imageY ) ); + idVec2 topr = matrix.Transform( idVec2( imageX + imageW, imageY ) ); + idVec2 br = matrix.Transform( idVec2( imageX + imageW, imageY + imageH ) ); + idVec2 bl = matrix.Transform( idVec2( imageX, imageY + imageH ) ); + + float s1 = 0.0f; + float t1 = 0.0f; + float s2 = 1.0f; + float t2 = 1.0f; + + //uint32 color = gui->GetColor(); + idVec4 imgColor = colorWhite; + imgColor.w = defaultColor.w; + gui->SetColor( imgColor ); + DrawStretchPic( idVec4( topl.x, topl.y, s1, t1 ), idVec4( topr.x, topr.y, s2, t1 ), idVec4( br.x, br.y, s2, t2 ), idVec4( bl.x, bl.y, s1, t2 ), icon.material ); + gui->SetColor( defaultColor ); + + x += icon.imageWidth * imageScale; + x += extraSpace; + + } else if ( overallIndex == icon.endIndex ) { + insertingImage = false; + iconIndex++; + } + + if ( insertingImage ) { + overallIndex += i - overallLineIndex; + overallLineIndex = i; + continue; + } + + // the glyphs texcoords assume nearest filtering, to get proper + // bilinear support we need to go an extra half texel on each side + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, character, glyph ); + + float glyphSkip = glyph.xSkip; + if ( textInstance->HasStroke() ) { + glyphSkip += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * textInstance->GetStrokeWeight() * glyphScale ); + } + + float glyphW = glyph.width + 1.0f; // +1 for bilinear half texel on each side + float glyphH = glyph.height + 1.0f; + + float glyphY = baseLine - glyph.top; + float glyphX = x + glyph.left; + + idVec2 topl = matrix.Transform( idVec2( glyphX, glyphY ) ); + idVec2 topr = matrix.Transform( idVec2( glyphX + glyphW, glyphY ) ); + idVec2 br = matrix.Transform( idVec2( glyphX + glyphW, glyphY + glyphH ) ); + idVec2 bl = matrix.Transform( idVec2( glyphX, glyphY + glyphH ) ); + + float s1 = glyph.s1; + float t1 = glyph.t1; + float s2 = glyph.s2; + float t2 = glyph.t2; + if ( c > selStart && c <= selEnd ) { + idVec2 topl = matrix.Transform( idVec2( x, y ) ); + idVec2 topr = matrix.Transform( idVec2( x + glyphSkip, y ) ); + idVec2 br = matrix.Transform( idVec2( x + glyphSkip, y + linespacing ) ); + idVec2 bl = matrix.Transform( idVec2( x, y + linespacing ) ); + gui->SetColor( selColor ); + DrawStretchPic( idVec4( topl.x, topl.y, 0, 0 ), idVec4( topr.x, topr.y, 1, 0 ), idVec4( br.x, br.y, 1, 1 ), idVec4( bl.x, bl.y, 0, 1 ), white ); + gui->SetColor( textColor ); + } + + if ( textInstance->GetHasDropShadow() ) { + + float dsY = glyphY + glyphScale * 2.0f; + float dsX = glyphX + glyphScale * 2.0f; + + idVec2 dstopl = matrix.Transform( idVec2( dsX, dsY ) ); + idVec2 dstopr = matrix.Transform( idVec2( dsX + glyphW, dsY ) ); + idVec2 dsbr = matrix.Transform( idVec2( dsX + glyphW, dsY + glyphH ) ); + idVec2 dsbl = matrix.Transform( idVec2( dsX, dsY + glyphH ) ); + + idVec4 dsColor = colorBlack; + dsColor.w = defaultColor.w; + gui->SetColor( dsColor ); + DrawStretchPic( idVec4( dstopl.x, dstopl.y, s1, t1 ), idVec4( dstopr.x, dstopr.y, s2, t1 ), idVec4( dsbr.x, dsbr.y, s2, t2 ), idVec4( dsbl.x, dsbl.y, s1, t2 ), glyph.material ); + gui->SetColor( textColor ); + } else if ( textInstance->HasStroke() ) { + + idVec4 strokeColor = colorBlack; + strokeColor.w = textInstance->GetStrokeStrength() * defaultColor.w; + gui->SetColor( strokeColor ); + for ( int index = 0; index < 4; ++index ) { + float xPos = glyphX + ( ( strokeXOffsets[ index ] * textInstance->GetStrokeWeight() ) * glyphScale ); + float yPos = glyphY + ( ( strokeYOffsets[ index ] * textInstance->GetStrokeWeight() ) * glyphScale ); + idVec2 topLeft = matrix.Transform( idVec2( xPos, yPos ) ); + idVec2 topRight = matrix.Transform( idVec2( xPos + glyphW, yPos ) ); + idVec2 botRight = matrix.Transform( idVec2( xPos + glyphW, yPos + glyphH ) ); + idVec2 botLeft = matrix.Transform( idVec2( xPos, yPos + glyphH ) ); + DrawStretchPic( idVec4( topLeft.x, topLeft.y, s1, t1 ), idVec4( topRight.x, topRight.y, s2, t1 ), idVec4( botRight.x, botRight.y, s2, t2 ), idVec4( botLeft.x, botLeft.y, s1, t2 ), glyph.material ); + } + gui->SetColor( textColor ); + } + + DrawStretchPic( idVec4( topl.x, topl.y, s1, t1 ), idVec4( topr.x, topr.y, s2, t1 ), idVec4( br.x, br.y, s2, t2 ), idVec4( bl.x, bl.y, s1, t2 ), glyph.material ); + x += glyphSkip; + x += extraSpace; + if ( cursorPos == c ) { + DrawEditCursor( gui, x - 1.0f, y, 1.0f, linespacing, matrix ); + } + c++; + overallIndex += i - overallLineIndex; + overallLineIndex = i; + } + + index++; + } +} + +/* +======================== +idSWF::FindTooltipIcons + +This replaces text like "_use" with platform specific text like "" +======================== +*/ +void idSWF::FindTooltipIcons( idStr * text ) { + + tooltipIconList.Clear(); + + for ( int i = UB_MAX_BUTTONS - 1; i >= 0; i-- ) { + //for ( userCmdString_t * ucs = userCmdStrings ; ucs->string ; ucs++ ) { + userCmdString_t ucs = userCmdStrings[i]; + if ( ucs.string && idStr::FindText( text->c_str(), ucs.string, false ) != idStr::INVALID_POSITION ) { + idStr replacement; + + keyBindings_t bind = idKeyInput::KeyBindingsFromBinding( ucs.string, true ); + idStr gamepad = "<"; + gamepad.Append( bind.gamepad ); + gamepad.Append( ">" ); + + if ( !in_useJoystick.GetBool() ) { + + if ( !bind.mouse.IsEmpty() ) { + replacement.Format( "<%s>", bind.mouse.c_str() ); + } else if ( !bind.keyboard.IsEmpty() ) { + replacement = bind.keyboard; + } + if ( replacement.IsEmpty() ) { + text->Replace( ucs.string, idStrId( "#str_swf_unbound" ).GetLocalizedString() ); + } + } else { + replacement = gamepad; + } + if ( !replacement.IsEmpty() ) { + replacement.ToUpper(); + text->Replace( ucs.string, replacement.c_str() ); + } + } + } + + for ( int count = 0; count < tooltipButtonImage.Num(); ++count ) { + int index = -1; + while ( ( index = idStr::FindText( text->c_str(), tooltipButtonImage[count].key, false, index + 1 ) ) != idStr::INVALID_POSITION ) { + tooltipIcon_t icon; + icon.startIndex = index; + icon.endIndex = index + idStr::Length(tooltipButtonImage[count].key); + + icon.material = declManager->FindMaterial( tooltipButtonImage[count].xbImage ); + + if ( icon.material ) { + icon.imageWidth = tooltipButtonImage[count].width; + icon.imageHeight = tooltipButtonImage[count].height; + icon.baseline = tooltipButtonImage[count].baseline; + } else { + icon.imageWidth = 0; + icon.imageHeight = 0; + icon.baseline = 0; + } + + bool inserted = false; + if ( tooltipIconList.Num() > 0 ) { + for ( int i = 0; i < tooltipIconList.Num(); ++i ) { + if ( tooltipIconList[i].startIndex > icon.startIndex ) { + tooltipIconList.Insert( icon, i ); + inserted = true; + break; + } + } + } + + if ( !inserted ) { + tooltipIconList.Append( icon ); + } + } + } +} + + diff --git a/neo/swf/SWF_ScriptFunction.cpp b/neo/swf/SWF_ScriptFunction.cpp new file mode 100644 index 00000000..9b98a4a4 --- /dev/null +++ b/neo/swf/SWF_ScriptFunction.cpp @@ -0,0 +1,1255 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +idCVar swf_debug( "swf_debug", "0", CVAR_INTEGER|CVAR_ARCHIVE, "debug swf scripts. 1 shows traces/errors. 2 also shows warnings. 3 also shows disassembly. 4 shows parameters in the disassembly." ); +idCVar swf_debugInvoke( "swf_debugInvoke", "0", CVAR_INTEGER, "debug swf functions being called from game." ); + +idSWFConstantPool::idSWFConstantPool() { +} + + +/* +======================== +idSWFConstantPool::Clear +======================== +*/ +void idSWFConstantPool::Clear() { + for ( int i = 0; i < pool.Num(); i++ ) { + pool[i]->Release(); + } + pool.Clear(); +} + +/* +======================== +idSWFConstantPool::Copy +======================== +*/ +void idSWFConstantPool::Copy( const idSWFConstantPool & other ) { + Clear(); + pool.SetNum( other.pool.Num() ); + for ( int i = 0; i < pool.Num(); i++ ) { + pool[i] = other.pool[i]; + pool[i]->AddRef(); + } +} + +/* +======================== +idSWFScriptFunction_Script::~idSWFScriptFunction_Script +======================== +*/ +idSWFScriptFunction_Script::~idSWFScriptFunction_Script() { + for ( int i = 0; i < scope.Num(); i++ ) { + if ( verify( scope[i] ) ) { + scope[i]->Release(); + } + } + if ( prototype ) { + prototype->Release(); + } +} + +/* +======================== +idSWFScriptFunction_Script::Call +======================== +*/ +void idSWFScriptFunction_Script::SetScope( idList & newScope ) { + assert( scope.Num() == 0 ); + for ( int i = 0; i < scope.Num(); i++ ) { + if ( verify( scope[i] ) ) { + scope[i]->Release(); + } + } + scope.Clear(); + scope.Append( newScope ); + for ( int i = 0; i < newScope.Num(); i++ ) { + if ( verify( scope[i] ) ) { + scope[i]->AddRef(); + } + } +} + +/* +======================== +idSWFScriptFunction_Script::Call +======================== +*/ +idSWFScriptVar idSWFScriptFunction_Script::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + idSWFBitStream bitstream( data, length, false ); + + // We assume scope[0] is the global scope + assert( scope.Num() > 0 ); + + if ( thisObject == NULL ) { + thisObject = scope[0]; + } + + idSWFScriptObject * locals = idSWFScriptObject::Alloc(); + + idSWFStack stack; + stack.SetNum( parms.Num() + 1 ); + for ( int i = 0; i < parms.Num(); i++ ) { + stack[ parms.Num() - i - 1 ] = parms[i]; + + // Unfortunately at this point we don't have the function name anymore, so our warning messages aren't very detailed + if ( i < parameters.Num() ) { + if ( parameters[i].reg > 0 && parameters[i].reg < registers.Num() ) { + registers[ parameters[i].reg ] = parms[i]; + } + locals->Set( parameters[i].name, parms[i] ); + } + } + // Set any additional parameters to undefined + for ( int i = parms.Num(); i < parameters.Num(); i++ ) { + if ( parameters[i].reg > 0 && parameters[i].reg < registers.Num() ) { + registers[ parameters[i].reg ].SetUndefined(); + } + locals->Set( parameters[i].name, idSWFScriptVar() ); + } + stack.A().SetInteger( parms.Num() ); + + int preloadReg = 1; + if ( flags & BIT( 0 ) ) { + // load "this" into a register + registers[ preloadReg ].SetObject( thisObject ); + preloadReg++; + } + if ( ( flags & BIT( 1 ) ) == 0 ) { + // create "this" + locals->Set( "this", idSWFScriptVar( thisObject ) ); + } + if ( flags & BIT( 2 ) ) { + idSWFScriptObject * arguments = idSWFScriptObject::Alloc(); + // load "arguments" into a register + arguments->MakeArray(); + + int numElements = parms.Num(); + + for ( int i = 0; i < numElements; i++ ) { + arguments->Set( i, parms[i] ); + } + + registers[ preloadReg ].SetObject( arguments ); + preloadReg++; + + arguments->Release(); + } + if ( ( flags & BIT( 3 ) ) == 0 ) { + idSWFScriptObject * arguments = idSWFScriptObject::Alloc(); + + // create "arguments" + arguments->MakeArray(); + + int numElements = parms.Num(); + + for ( int i = 0; i < numElements; i++ ) { + arguments->Set( i, parms[i] ); + } + + locals->Set( "arguments", idSWFScriptVar( arguments ) ); + + arguments->Release(); + } + if ( flags & BIT( 4 ) ) { + // load "super" into a register + registers[ preloadReg ].SetObject( thisObject->GetPrototype() ); + preloadReg++; + } + if ( ( flags & BIT( 5 ) ) == 0 ) { + // create "super" + locals->Set( "super", idSWFScriptVar( thisObject->GetPrototype() ) ); + } + if ( flags & BIT( 6 ) ) { + // preload _root + registers[ preloadReg ] = scope[0]->Get( "_root" ); + preloadReg++; + } + if ( flags & BIT( 7 ) ) { + // preload _parent + if ( thisObject->GetSprite() != NULL && thisObject->GetSprite()->parent != NULL ) { + registers[ preloadReg ].SetObject( thisObject->GetSprite()->parent->scriptObject ); + } else { + registers[ preloadReg ].SetNULL(); + } + preloadReg++; + } + if ( flags & BIT( 8 ) ) { + // load "_global" into a register + registers[ preloadReg ].SetObject( scope[0] ); + preloadReg++; + } + + int scopeSize = scope.Num(); + scope.Append( locals ); + locals->AddRef(); + + idSWFScriptVar retVal = Run( thisObject, stack, bitstream ); + + assert( scope.Num() == scopeSize + 1 ); + for ( int i = scopeSize; i < scope.Num(); i++ ) { + if ( verify( scope[i] ) ) { + scope[i]->Release(); + } + } + scope.SetNum( scopeSize ); + + locals->Release(); + locals = NULL; + + return retVal; +} + +/* +======================== +::Split +======================== +*/ +namespace { + const char * GetPropertyName( int index ) { + switch ( index ) { + case 0: return "_x"; + case 1: return "_y"; + case 2: return "_xscale"; + case 3: return "_yscale"; + case 4: return "_currentframe"; + case 5: return "_totalframes"; + case 6: return "_alpha"; + case 7: return "_visible"; + case 8: return "_width"; + case 9: return "_height"; + case 10: return "_rotation"; + case 11: return "_target"; + case 12: return "_framesloaded"; + case 13: return "_name"; + case 14: return "_droptarget"; + case 15: return "_url"; + case 16: return "_highquality"; + case 17: return "_focusrect"; + case 18: return "_soundbuftime"; + case 19: return "_quality"; + case 20: return "_mousex"; + case 21: return "_mousey"; + } + return ""; + } + + const char *GetSwfActionName(swfAction_t code) + { + switch (code) + { + case Action_End: return "Action_End"; + + // swf 3 + case Action_NextFrame: return "Action_NextFrame"; + case Action_PrevFrame: return "Action_PrevFrame"; + case Action_Play: return "Action_Play"; + case Action_Stop: return "Action_Stop"; + case Action_ToggleQuality: return "Action_ToggleQuality"; + case Action_StopSounds: return "Action_StopSounds"; + + case Action_GotoFrame: return "Action_GotoFrame"; + case Action_GetURL: return "Action_GetURL"; + case Action_WaitForFrame: return "Action_WaitForFrame"; + case Action_SetTarget: return "Action_SetTarget"; + case Action_GoToLabel: return "Action_GoToLabel"; + + // swf 4 + case Action_Add: return "Action_Add"; + case Action_Subtract: return "Action_Subtract"; + case Action_Multiply: return "Action_Multiply"; + case Action_Divide: return "Action_Divide"; + case Action_Equals: return "Action_Equals"; + case Action_Less: return "Action_Less"; + case Action_And: return "Action_And"; + case Action_Or: return "Action_Or"; + case Action_Not: return "Action_Not"; + case Action_StringEquals: return "Action_StringEquals"; + case Action_StringLength: return "Action_StringLength"; + case Action_StringExtract: return "Action_StringExtract"; + case Action_Pop: return "Action_Pop"; + case Action_ToInteger: return "Action_ToInteger"; + case Action_GetVariable: return "Action_GetVariable"; + case Action_SetVariable: return "Action_SetVariable"; + case Action_SetTarget2: return "Action_SetTarget2"; + case Action_StringAdd: return "Action_StringAdd"; + case Action_GetProperty: return "Action_GetProperty"; + case Action_SetProperty: return "Action_SetProperty"; + case Action_CloneSprite: return "Action_CloneSprite"; + case Action_RemoveSprite: return "Action_RemoveSprite"; + case Action_Trace: return "Action_Trace"; + case Action_StartDrag: return "Action_StartDrag"; + case Action_EndDrag: return "Action_EndDrag"; + case Action_StringLess: return "Action_StringLess"; + case Action_RandomNumber: return "Action_RandomNumber"; + case Action_MBStringLength: return "Action_MBStringLength"; + case Action_CharToAscii: return "Action_CharToAscii"; + case Action_AsciiToChar: return "Action_AsciiToChar"; + case Action_GetTime: return "Action_GetTime"; + case Action_MBStringExtract: return "Action_MBStringExtract"; + case Action_MBCharToAscii: return "Action_MBCharToAscii"; + case Action_MBAsciiToChar: return "Action_MBAsciiToChar"; + + case Action_WaitForFrame2: return "Action_WaitForFrame2"; + case Action_Push: return "Action_Push"; + case Action_Jump: return "Action_Jump"; + case Action_GetURL2: return "Action_GetURL2"; + case Action_If: return "Action_If"; + case Action_Call: return "Action_Call"; + case Action_GotoFrame2: return "Action_GotoFrame2"; + + // swf 5 + case Action_Delete: return "Action_Delete"; + case Action_Delete2: return "Action_Delete2"; + case Action_DefineLocal: return "Action_DefineLocal"; + case Action_CallFunction: return "Action_CallFunction"; + case Action_Return: return "Action_Return"; + case Action_Modulo: return "Action_Modulo"; + case Action_NewObject: return "Action_NewObject"; + case Action_DefineLocal2: return "Action_DefineLocal2"; + case Action_InitArray: return "Action_InitArray"; + case Action_InitObject: return "Action_InitObject"; + case Action_TypeOf: return "Action_TypeOf"; + case Action_TargetPath: return "Action_TargetPath"; + case Action_Enumerate: return "Action_Enumerate"; + case Action_Add2: return "Action_Add2"; + case Action_Less2: return "Action_Less2"; + case Action_Equals2: return "Action_Equals2"; + case Action_ToNumber: return "Action_ToNumber"; + case Action_ToString: return "Action_ToString"; + case Action_PushDuplicate: return "Action_PushDuplicate"; + case Action_StackSwap: return "Action_StackSwap"; + case Action_GetMember: return "Action_GetMember"; + case Action_SetMember: return "Action_SetMember"; + case Action_Increment: return "Action_Increment"; + case Action_Decrement: return "Action_Decrement"; + case Action_CallMethod: return "Action_CallMethod"; + case Action_NewMethod: return "Action_NewMethod"; + case Action_BitAnd: return "Action_BitAnd"; + case Action_BitOr: return "Action_BitOr"; + case Action_BitXor: return "Action_BitXor"; + case Action_BitLShift: return "Action_BitLShift"; + case Action_BitRShift: return "Action_BitRShift"; + case Action_BitURShift: return "Action_BitURShift"; + + case Action_StoreRegister: return "Action_StoreRegister"; + case Action_ConstantPool: return "Action_ConstantPool"; + case Action_With: return "Action_With"; + case Action_DefineFunction: return "Action_DefineFunction"; + + // swf 6 + case Action_InstanceOf: return "Action_InstanceOf"; + case Action_Enumerate2: return "Action_Enumerate2"; + case Action_StrictEquals: return "Action_StrictEquals"; + case Action_Greater: return "Action_Greater"; + case Action_StringGreater: return "Action_StringGreater"; + + // swf 7 + case Action_Extends: return "Action_Extends"; + case Action_CastOp: return "Action_CastOp"; + case Action_ImplementsOp: return "Action_ImplementsOp"; + case Action_Throw: return "Action_Throw"; + case Action_Try: return "Action_Try"; + + case Action_DefineFunction2: return "Action_DefineFunction2"; + default: + return "UNKNOWN CODE"; + } + } +} + +/* +======================== +idSWFScriptFunction_Script::Run +======================== +*/ +idSWFScriptVar idSWFScriptFunction_Script::Run( idSWFScriptObject * thisObject, idSWFStack & stack, idSWFBitStream & bitstream ) { + static int callstackLevel = -1; + idSWFSpriteInstance * thisSprite = thisObject->GetSprite(); + idSWFSpriteInstance * currentTarget = thisSprite; + + if ( currentTarget == NULL ) { + thisSprite = currentTarget = defaultSprite; + } + + callstackLevel++; + + while ( bitstream.Tell() < bitstream.Length() ) { + swfAction_t code = (swfAction_t)bitstream.ReadU8(); + uint16 recordLength = 0; + if ( code >= 0x80 ) { + recordLength = bitstream.ReadU16(); + } + + if ( swf_debug.GetInteger() >= 3 ) { + // stack[0] is always 0 so don't read it + if ( swf_debug.GetInteger() >= 4 ) { + for ( int i = stack.Num()-1; i >= 0 ; i-- ) { + idLib::Printf(" %c: %s (%s)\n", (char)(64 + stack.Num() - i), stack[i].ToString().c_str(), stack[i].TypeOf()); + } + + for ( int i = 0; i < registers.Num(); i++ ) { + if ( !registers[i].IsUndefined() ) { + idLib::Printf(" R%d: %s (%s)\n", i, registers[i].ToString().c_str(), registers[i].TypeOf()); + } + } + } + + idLib::Printf( "SWF%d: code %s\n", callstackLevel, GetSwfActionName(code) ); + } + + switch ( code ) { + case Action_Return: + callstackLevel--; + return stack.A(); + case Action_End: + callstackLevel--; + return idSWFScriptVar(); + case Action_NextFrame: + if ( verify( currentTarget != NULL ) ) { + currentTarget->NextFrame(); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for nextFrame\n" ); + } + break; + case Action_PrevFrame: + if ( verify( currentTarget != NULL ) ) { + currentTarget->PrevFrame(); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for prevFrame\n" ); + } + break; + case Action_Play: + if ( verify( currentTarget != NULL ) ) { + currentTarget->Play(); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for play\n" ); + } + break; + case Action_Stop: + if ( verify( currentTarget != NULL ) ) { + currentTarget->Stop(); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for stop\n" ); + } + break; + case Action_ToggleQuality: break; + case Action_StopSounds: break; + case Action_GotoFrame: { + assert( recordLength == 2 ); + int frameNum = bitstream.ReadU16() + 1; + if ( verify( currentTarget != NULL ) ) { + currentTarget->RunTo( frameNum ); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for runTo %d\n", frameNum ); + } + break; + } + case Action_SetTarget: { + const char * targetName = (const char *)bitstream.ReadData( recordLength ); + if ( verify( thisSprite != NULL ) ) { + currentTarget = thisSprite->ResolveTarget( targetName ); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for setTarget %s\n", targetName ); + } + break; + } + case Action_GoToLabel: { + const char * targetName = (const char *)bitstream.ReadData( recordLength ); + if ( verify( currentTarget != NULL ) ) { + currentTarget->RunTo( currentTarget->FindFrame( targetName ) ); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for runTo %s\n", targetName ); + } + break; + } + case Action_Push: { + idSWFBitStream pushstream( bitstream.ReadData( recordLength ), recordLength, false ); + while ( pushstream.Tell() < pushstream.Length() ) { + uint8 type = pushstream.ReadU8(); + switch ( type ) { + case 0: stack.Alloc().SetString( pushstream.ReadString() ); break; + case 1: stack.Alloc().SetFloat( pushstream.ReadFloat() ); break; + case 2: stack.Alloc().SetNULL(); break; + case 3: stack.Alloc().SetUndefined(); break; + case 4: stack.Alloc() = registers[ pushstream.ReadU8() ]; break; + case 5: stack.Alloc().SetBool( pushstream.ReadU8() != 0 ); break; + case 6: stack.Alloc().SetFloat( (float)pushstream.ReadDouble() ); break; + case 7: stack.Alloc().SetInteger( pushstream.ReadS32() ); break; + case 8: stack.Alloc().SetString( constants.Get( pushstream.ReadU8() ) ); break; + case 9: stack.Alloc().SetString( constants.Get( pushstream.ReadU16() ) ); break; + } + } + break; + } + case Action_Pop: + stack.Pop( 1 ); + break; + case Action_Add: + stack.B().SetFloat( stack.B().ToFloat() + stack.A().ToFloat() ); + stack.Pop( 1 ); + break; + case Action_Subtract: + stack.B().SetFloat( stack.B().ToFloat() - stack.A().ToFloat() ); + stack.Pop( 1 ); + break; + case Action_Multiply: + stack.B().SetFloat( stack.B().ToFloat() * stack.A().ToFloat() ); + stack.Pop( 1 ); + break; + case Action_Divide: + stack.B().SetFloat( stack.B().ToFloat() / stack.A().ToFloat() ); + stack.Pop( 1 ); + break; + case Action_Equals: + stack.B().SetBool( stack.B().ToFloat() == stack.A().ToFloat() ); + stack.Pop( 1 ); + break; + case Action_Less: + stack.B().SetBool( stack.B().ToFloat() < stack.A().ToFloat() ); + stack.Pop( 1 ); + break; + case Action_And: + stack.B().SetBool( stack.B().ToBool() && stack.A().ToBool() ); + stack.Pop( 1 ); + break; + case Action_Or: + stack.B().SetBool( stack.B().ToBool() || stack.A().ToBool() ); + stack.Pop( 1 ); + break; + case Action_Not: + stack.A().SetBool( !stack.A().ToBool() ); + break; + case Action_StringEquals: + stack.B().SetBool( stack.B().ToString() == stack.A().ToString() ); + stack.Pop( 1 ); + break; + case Action_StringLength: + stack.A().SetInteger( stack.A().ToString().Length() ); + break; + case Action_StringAdd: + stack.B().SetString( stack.B().ToString() + stack.A().ToString() ); + stack.Pop( 1 ); + break; + case Action_StringExtract: + stack.C().SetString( stack.C().ToString().Mid( stack.B().ToInteger(), stack.A().ToInteger() ) ); + stack.Pop( 2 ); + break; + case Action_StringLess: + stack.B().SetBool( stack.B().ToString() < stack.A().ToString() ); + stack.Pop( 1 ); + break; + case Action_StringGreater: + stack.B().SetBool( stack.B().ToString() > stack.A().ToString() ); + stack.Pop( 1 ); + break; + case Action_ToInteger: + stack.A().SetInteger( stack.A().ToInteger() ); + break; + case Action_CharToAscii: + stack.A().SetInteger( stack.A().ToString()[0] ); + break; + case Action_AsciiToChar: + stack.A().SetString( va( "%c", stack.A().ToInteger() ) ); + break; + case Action_Jump: + bitstream.Seek( bitstream.ReadS16() ); + break; + case Action_If: { + int16 offset = bitstream.ReadS16(); + if ( stack.A().ToBool() ) { + bitstream.Seek( offset ); + } + stack.Pop( 1 ); + break; + } + case Action_GetVariable: { + idStr variableName = stack.A().ToString(); + for ( int i = scope.Num() - 1; i >= 0; i-- ) { + stack.A() = scope[i]->Get( variableName ); + if ( !stack.A().IsUndefined() ) { + break; + } + } + if ( stack.A().IsUndefined() && swf_debug.GetInteger() > 1 ) { + idLib::Printf( "SWF: unknown variable %s\n", variableName.c_str() ); + } + break; + } + case Action_SetVariable: { + idStr variableName = stack.B().ToString(); + bool found = false; + for ( int i = scope.Num() - 1; i >= 0; i-- ) { + if ( scope[i]->HasProperty( variableName ) ) { + scope[i]->Set( variableName, stack.A() ); + found = true; + break; + } + } + if ( !found ) { + thisObject->Set( variableName, stack.A() ); + } + stack.Pop( 2 ); + break; + } + case Action_GotoFrame2: { + + uint32 frameNum = 0; + uint8 flags = bitstream.ReadU8(); + if ( flags & 2 ) { + frameNum += bitstream.ReadU16(); + } + + if ( verify( thisSprite != NULL ) ) { + if ( stack.A().IsString() ) { + frameNum += thisSprite->FindFrame( stack.A().ToString() ); + } else { + frameNum += (uint32)stack.A().ToInteger(); + } + if ( ( flags & 1 ) != 0 ){ + thisSprite->Play(); + } else { + thisSprite->Stop(); + } + thisSprite->RunTo( frameNum ); + } else if ( swf_debug.GetInteger() > 0 ) { + if ( ( flags & 1 ) != 0 ){ + idLib::Printf( "SWF: no target movie clip for gotoAndPlay\n" ); + } else { + idLib::Printf( "SWF: no target movie clip for gotoAndStop\n" ); + } + } + stack.Pop( 1 ); + break; + } + case Action_GetProperty: { + if ( verify( thisSprite != NULL ) ) { + idSWFSpriteInstance * target = thisSprite->ResolveTarget( stack.B().ToString() ); + stack.B() = target->scriptObject->Get( GetPropertyName( stack.A().ToInteger() ) ); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for getProperty\n" ); + } + stack.Pop( 1 ); + break; + } + case Action_SetProperty: { + if ( verify( thisSprite != NULL ) ) { + idSWFSpriteInstance * target = thisSprite->ResolveTarget( stack.C().ToString() ); + target->scriptObject->Set( GetPropertyName( stack.B().ToInteger() ), stack.A() ); + } else if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: no target movie clip for setProperty\n" ); + } + stack.Pop( 3 ); + break; + } + case Action_Trace: + idLib::PrintfIf( swf_debug.GetInteger() > 0, "SWF Trace: %s\n", stack.A().ToString().c_str() ); + stack.Pop( 1 ); + break; + case Action_GetTime: + stack.Alloc().SetInteger( Sys_Milliseconds() ); + break; + case Action_RandomNumber: + assert( thisSprite && thisSprite->sprite && thisSprite->sprite->GetSWF() ); + stack.A().SetInteger( thisSprite->sprite->GetSWF()->GetRandom().RandomInt( stack.A().ToInteger() ) ); + break; + case Action_CallFunction: { + idStr functionName = stack.A().ToString(); + idSWFScriptVar function; + idSWFScriptObject * object = NULL; + for ( int i = scope.Num() - 1; i >= 0; i-- ) { + function = scope[i]->Get( functionName ); + if ( !function.IsUndefined() ) { + object = scope[i]; + break; + } + } + stack.Pop( 1 ); + + idSWFParmList parms; + parms.SetNum( stack.A().ToInteger() ); + stack.Pop( 1 ); + for ( int i = 0; i < parms.Num(); i++ ) { + parms[i] = stack.A(); + stack.Pop( 1 ); + } + + if ( function.IsFunction() && verify( object ) ) { + stack.Alloc() = function.GetFunction()->Call( object, parms ); + } else { + idLib::PrintfIf( swf_debug.GetInteger() > 0, "SWF: unknown function %s\n", functionName.c_str() ); + stack.Alloc().SetUndefined(); + } + + break; + } + case Action_CallMethod: { + idStr functionName = stack.A().ToString(); + // If the top stack is undefined but there is an object, it's calling the constructor + if ( functionName.IsEmpty() || stack.A().IsUndefined() || stack.A().IsNULL() ) { + functionName = "__constructor__"; + } + idSWFScriptObject * object = NULL; + idSWFScriptVar function; + if ( stack.B().IsObject() ) { + object = stack.B().GetObject(); + function = object->Get( functionName ); + if ( !function.IsFunction() ) { + idLib::PrintfIf( swf_debug.GetInteger() > 1, "SWF: unknown method %s on %s\n", functionName.c_str(), object->DefaultValue( true ).ToString().c_str() ); + } + } else { + idLib::PrintfIf( swf_debug.GetInteger() > 1, "SWF: NULL object for method %s\n", functionName.c_str() ); + } + + stack.Pop( 2 ); + + idSWFParmList parms; + parms.SetNum( stack.A().ToInteger() ); + stack.Pop( 1 ); + for ( int i = 0; i < parms.Num(); i++ ) { + parms[i] = stack.A(); + stack.Pop( 1 ); + } + + if ( function.IsFunction() ) { + stack.Alloc() = function.GetFunction()->Call( object, parms ); + } else { + stack.Alloc().SetUndefined(); + } + break; + } + case Action_ConstantPool: { + constants.Clear(); + uint16 numConstants = bitstream.ReadU16(); + for ( int i = 0; i < numConstants; i++ ) { + constants.Append( idSWFScriptString::Alloc( bitstream.ReadString() ) ); + } + break; + } + case Action_DefineFunction: { + idStr functionName = bitstream.ReadString(); + + idSWFScriptFunction_Script * newFunction = idSWFScriptFunction_Script::Alloc(); + newFunction->SetScope( scope ); + newFunction->SetConstants( constants ); + newFunction->SetDefaultSprite( defaultSprite ); + + uint16 numParms = bitstream.ReadU16(); + newFunction->AllocParameters( numParms ); + for ( int i = 0; i < numParms; i++ ) { + newFunction->SetParameter( i, 0, bitstream.ReadString() ); + } + uint16 codeSize = bitstream.ReadU16(); + newFunction->SetData( bitstream.ReadData( codeSize ), codeSize ); + + if ( functionName.IsEmpty() ) { + stack.Alloc().SetFunction( newFunction ); + } else { + thisObject->Set( functionName, idSWFScriptVar( newFunction ) ); + } + newFunction->Release(); + break; + } + case Action_DefineFunction2: { + idStr functionName = bitstream.ReadString(); + + idSWFScriptFunction_Script * newFunction = idSWFScriptFunction_Script::Alloc(); + newFunction->SetScope( scope ); + newFunction->SetConstants( constants ); + newFunction->SetDefaultSprite( defaultSprite ); + + uint16 numParms = bitstream.ReadU16(); + + // The number of registers is from 0 to 255, although valid values are 1 to 256. + // There must always be at least one register for DefineFunction2, to hold "this" or "super" when required. + uint8 numRegs = bitstream.ReadU8() + 1; + + // Note that SWF byte-ordering causes the flag bits to be reversed per-byte + // from how the swf_file_format_spec_v10.pdf document describes the ordering in ActionDefineFunction2. + // PreloadThisFlag is byte 0, not 7, PreloadGlobalFlag is 8, not 15. + uint16 flags = bitstream.ReadU16(); + + newFunction->AllocParameters( numParms ); + newFunction->AllocRegisters( numRegs ); + newFunction->SetFlags( flags ); + + for ( int i = 0; i < numParms; i++ ) { + uint8 reg = bitstream.ReadU8(); + const char * name = bitstream.ReadString(); + if ( reg >= numRegs ) { + idLib::Warning( "SWF: Parameter %s in function %s bound to out of range register %d", name, functionName.c_str(), reg ); + reg = 0; + } + newFunction->SetParameter( i, reg, name ); + } + + uint16 codeSize = bitstream.ReadU16(); + newFunction->SetData( bitstream.ReadData( codeSize ), codeSize ); + + if ( functionName.IsEmpty() ) { + stack.Alloc().SetFunction( newFunction ); + } else { + thisObject->Set( functionName, idSWFScriptVar( newFunction ) ); + } + newFunction->Release(); + break; + } + case Action_Enumerate: { + idStr variableName = stack.A().ToString(); + for ( int i = scope.Num() - 1; i >= 0; i-- ) { + stack.A() = scope[i]->Get( variableName ); + if ( !stack.A().IsUndefined() ) { + break; + } + } + if ( !stack.A().IsObject() ) { + stack.A().SetNULL(); + } else { + idSWFScriptObject * object = stack.A().GetObject(); + object->AddRef(); + stack.A().SetNULL(); + for ( int i = 0; i < object->NumVariables(); i++ ) { + stack.Alloc().SetString( object->EnumVariable( i ) ); + } + object->Release(); + } + break; + } + case Action_Enumerate2: { + if ( !stack.A().IsObject() ) { + stack.A().SetNULL(); + } else { + idSWFScriptObject * object = stack.A().GetObject(); + object->AddRef(); + stack.A().SetNULL(); + for ( int i = 0; i < object->NumVariables(); i++ ) { + stack.Alloc().SetString( object->EnumVariable( i ) ); + } + object->Release(); + } + break; + } + case Action_Equals2: { + stack.B().SetBool( stack.A().AbstractEquals( stack.B() ) ); + stack.Pop( 1 ); + break; + } + case Action_StrictEquals: { + stack.B().SetBool( stack.A().StrictEquals( stack.B() ) ); + stack.Pop( 1 ); + break; + } + case Action_GetMember: { + if ( ( stack.B().IsUndefined() || stack.B().IsNULL() ) && swf_debug.GetInteger() > 1 ) { + idLib::Printf( "SWF: tried to get member %s on an invalid object in sprite '%s'\n", stack.A().ToString().c_str(), thisSprite != NULL ? thisSprite->GetName() : "" ); + } + if ( stack.B().IsObject() ) { + idSWFScriptObject * object = stack.B().GetObject(); + if ( stack.A().IsNumeric() ) { + stack.B() = object->Get( stack.A().ToInteger() ); + } else { + stack.B() = object->Get( stack.A().ToString() ); + } + if ( stack.B().IsUndefined() && swf_debug.GetInteger() > 1 ) { + idLib::Printf( "SWF: unknown member %s\n", stack.A().ToString().c_str() ); + } + } else if ( stack.B().IsString() ) { + idStr propertyName = stack.A().ToString(); + if ( propertyName.Cmp( "length" ) == 0 ) { + stack.B().SetInteger( stack.B().ToString().Length() ); + } else if ( propertyName.Cmp( "value" ) == 0 ) { + // Do nothing + } else { + stack.B().SetUndefined(); + } + } else if ( stack.B().IsFunction() ) { + idStr propertyName = stack.A().ToString(); + if ( propertyName.Cmp( "prototype" ) == 0 ) { + // if this is a function, it's a class definition function, and it just wants the prototype object + // create it if it hasn't been already, and return it + idSWFScriptFunction * sfs = stack.B().GetFunction(); + idSWFScriptObject * object = sfs->GetPrototype(); + + if ( object == NULL ) { + object = idSWFScriptObject::Alloc(); + // Set the __proto__ to the main Object prototype + idSWFScriptVar baseObjConstructor = scope[0]->Get( "Object" ); + idSWFScriptFunction *baseObj = baseObjConstructor.GetFunction(); + object->Set( "__proto__", baseObj->GetPrototype() ); + sfs->SetPrototype( object ); + } + + stack.B() = idSWFScriptVar( object ); + } else { + stack.B().SetUndefined(); + } + } else { + stack.B().SetUndefined(); + } + stack.Pop( 1 ); + break; + } + case Action_SetMember: { + if ( stack.C().IsObject() ) { + idSWFScriptObject * object = stack.C().GetObject(); + if ( stack.B().IsNumeric() ) { + object->Set( stack.B().ToInteger(), stack.A() ); + } else { + object->Set( stack.B().ToString(), stack.A() ); + } + } + stack.Pop( 3 ); + break; + } + case Action_InitArray: { + idSWFScriptObject * object = idSWFScriptObject::Alloc(); + object->MakeArray(); + + int numElements = stack.A().ToInteger(); + stack.Pop( 1 ); + + for ( int i = 0; i < numElements; i++ ) { + object->Set( i, stack.A() ); + stack.Pop( 1 ); + } + + stack.Alloc().SetObject( object ); + + object->Release(); + break; + } + case Action_InitObject: { + idSWFScriptObject * object = idSWFScriptObject::Alloc(); + + int numElements = stack.A().ToInteger(); + stack.Pop( 1 ); + + for ( int i = 0; i < numElements; i++ ) { + object->Set( stack.B().ToString(), stack.A() ); + stack.Pop( 2 ); + } + + stack.Alloc().SetObject( object ); + + object->Release(); + break; + } + case Action_NewObject: { + idSWFScriptObject * object = idSWFScriptObject::Alloc(); + + idStr functionName = stack.A().ToString(); + stack.Pop( 1 ); + + if ( functionName.Cmp( "Array" ) == 0 ) { + object->MakeArray(); + + int numElements = stack.A().ToInteger(); + stack.Pop( 1 ); + + for ( int i = 0; i < numElements; i++ ) { + object->Set( i, stack.A() ); + stack.Pop( 1 ); + } + + idSWFScriptVar baseObjConstructor = scope[0]->Get( "Object" ); + idSWFScriptFunction *baseObj = baseObjConstructor.GetFunction(); + object->Set( "__proto__", baseObj->GetPrototype() ); + // object prototype is not set here because it will be auto created from Object later + } else { + idSWFParmList parms; + parms.SetNum( stack.A().ToInteger() ); + stack.Pop( 1 ); + for ( int i = 0; i < parms.Num(); i++ ) { + parms[i] = stack.A(); + stack.Pop( 1 ); + } + + idSWFScriptVar objdef = scope[0]->Get( functionName ); + if ( objdef.IsFunction() ) { + idSWFScriptFunction * constructorFunction = objdef.GetFunction(); + object->Set( "__proto__", constructorFunction->GetPrototype() ); + object->SetPrototype( constructorFunction->GetPrototype() ); + constructorFunction->Call( object, parms ); + } else { + idLib::Warning( "SWF: Unknown class definition %s", functionName.c_str() ); + } + } + + stack.Alloc().SetObject( object ); + + object->Release(); + break; + } + case Action_Extends: { + idSWFScriptFunction * superclassConstructorFunction = stack.A().GetFunction(); + idSWFScriptFunction *subclassConstructorFunction = stack.B().GetFunction(); + stack.Pop( 2 ); + + idSWFScriptObject * scriptObject = idSWFScriptObject::Alloc(); + scriptObject->SetPrototype( superclassConstructorFunction->GetPrototype() ); + scriptObject->Set( "__proto__", idSWFScriptVar( superclassConstructorFunction->GetPrototype() ) ); + scriptObject->Set( "__constructor__", idSWFScriptVar( superclassConstructorFunction ) ); + + subclassConstructorFunction->SetPrototype( scriptObject ); + + scriptObject->Release(); + break; + } + case Action_TargetPath: { + if ( !stack.A().IsObject() ) { + stack.A().SetUndefined(); + } else { + idSWFScriptObject * object = stack.A().GetObject(); + if ( object->GetSprite() == NULL ) { + stack.A().SetUndefined(); + } else { + idStr dotName = object->GetSprite()->name.c_str(); + for ( idSWFSpriteInstance * target = object->GetSprite()->parent; target != NULL; target = target->parent ) { + dotName = target->name + "." + dotName; + } + stack.A().SetString( dotName ); + } + } + break; + } + case Action_With: { + int withSize = bitstream.ReadU16(); + idSWFBitStream bitstream2( bitstream.ReadData( withSize ), withSize, false ); + if ( stack.A().IsObject() ) { + idSWFScriptObject * withObject = stack.A().GetObject(); + withObject->AddRef(); + stack.Pop( 1 ); + scope.Append( withObject ); + Run( thisObject, stack, bitstream2 ); + scope.SetNum( scope.Num() - 1 ); + withObject->Release(); + } else { + if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: with() invalid object specified\n" ); + } + stack.Pop( 1 ); + } + break; + } + case Action_ToNumber: + stack.A().SetFloat( stack.A().ToFloat() ); + break; + case Action_ToString: + stack.A().SetString( stack.A().ToString() ); + break; + case Action_TypeOf: + stack.A().SetString( stack.A().TypeOf() ); + break; + case Action_Add2: { + if ( stack.A().IsString() || stack.B().IsString() ) { + stack.B().SetString( stack.B().ToString() + stack.A().ToString() ); + } else { + stack.B().SetFloat( stack.B().ToFloat() + stack.A().ToFloat() ); + } + stack.Pop( 1 ); + break; + } + case Action_Less2: { + if ( stack.A().IsString() && stack.B().IsString() ) { + stack.B().SetBool( stack.B().ToString() < stack.A().ToString() ); + } else { + stack.B().SetBool( stack.B().ToFloat() < stack.A().ToFloat() ); + } + stack.Pop( 1 ); + break; + } + case Action_Greater: { + if ( stack.A().IsString() && stack.B().IsString() ) { + stack.B().SetBool( stack.B().ToString() > stack.A().ToString() ); + } else { + stack.B().SetBool( stack.B().ToFloat() > stack.A().ToFloat() ); + } + stack.Pop( 1 ); + break; + } + case Action_Modulo: { + int32 a = stack.A().ToInteger(); + int32 b = stack.B().ToInteger(); + if ( a == 0 ) { + stack.B().SetUndefined(); + } else { + stack.B().SetInteger( b % a ); + } + stack.Pop( 1 ); + break; + } + case Action_BitAnd: + stack.B().SetInteger( stack.B().ToInteger() & stack.A().ToInteger() ); + stack.Pop( 1 ); + break; + case Action_BitLShift: + stack.B().SetInteger( stack.B().ToInteger() << stack.A().ToInteger() ); + stack.Pop( 1 ); + break; + case Action_BitOr: + stack.B().SetInteger( stack.B().ToInteger() | stack.A().ToInteger() ); + stack.Pop( 1 ); + break; + case Action_BitRShift: + stack.B().SetInteger( stack.B().ToInteger() >> stack.A().ToInteger() ); + stack.Pop( 1 ); + break; + case Action_BitURShift: + stack.B().SetInteger( (uint32)stack.B().ToInteger() >> stack.A().ToInteger() ); + stack.Pop( 1 ); + break; + case Action_BitXor: + stack.B().SetInteger( stack.B().ToInteger() ^ stack.A().ToInteger() ); + stack.Pop( 1 ); + break; + case Action_Decrement: + stack.A().SetFloat( stack.A().ToFloat() - 1.0f ); + break; + case Action_Increment: + stack.A().SetFloat( stack.A().ToFloat() + 1.0f ); + break; + case Action_PushDuplicate: { + idSWFScriptVar dup = stack.A(); + stack.Alloc() = dup; + break; + } + case Action_StackSwap: { + idSWFScriptVar temp = stack.A(); + stack.A() = stack.B(); + stack.A() = temp; + break; + } + case Action_StoreRegister: { + uint8 registerNumber = bitstream.ReadU8(); + registers[ registerNumber ] = stack.A(); + break; + } + case Action_DefineLocal: { + scope[scope.Num()-1]->Set( stack.B().ToString(), stack.A() ); + stack.Pop( 2 ); + break; + } + case Action_DefineLocal2: { + scope[scope.Num()-1]->Set( stack.A().ToString(), idSWFScriptVar() ); + stack.Pop( 1 ); + break; + } + case Action_Delete: { + if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: Delete ignored\n" ); + } + // We no longer support deleting variables because the performance cost of updating the hash tables is not worth it + stack.Pop( 2 ); + break; + } + case Action_Delete2: { + if ( swf_debug.GetInteger() > 0 ) { + idLib::Printf( "SWF: Delete2 ignored\n" ); + } + // We no longer support deleting variables because the performance cost of updating the hash tables is not worth it + stack.Pop( 1 ); + break; + } + // These are functions we just don't support because we never really needed to + case Action_CloneSprite: + case Action_RemoveSprite: + case Action_Call: + case Action_SetTarget2: + case Action_NewMethod: + default: + idLib::Warning( "SWF: Unhandled Action %s", idSWF::GetActionName( code ) ); + // We have to abort here because the rest of the script is basically meaningless now + assert( false ); + callstackLevel--; + return idSWFScriptVar(); + } + } + callstackLevel--; + return idSWFScriptVar(); +} + +/* +======================== +idSWF::Invoke +======================== +*/ +void idSWF::Invoke( const char * functionName, const idSWFParmList & parms ) { + idSWFScriptObject * obj = mainspriteInstance->GetScriptObject(); + idSWFScriptVar scriptVar = obj->Get( functionName ); + + if ( swf_debugInvoke.GetBool() ) { + idLib::Printf( "SWF: Invoke %s with %d parms (%s)\n", functionName, parms.Num(), GetName() ); + } + + if ( scriptVar.IsFunction() ) { + scriptVar.GetFunction()->Call( NULL, parms ); + } +} + +/* +======================== +idSWF::Invoke +======================== +*/ +void idSWF::Invoke( const char * functionName, const idSWFParmList & parms, idSWFScriptVar & scriptVar ) { + + if ( scriptVar.IsFunction() ) { + scriptVar.GetFunction()->Call( NULL, parms ); + } else { + idSWFScriptObject * obj = mainspriteInstance->GetScriptObject(); + scriptVar = obj->Get( functionName ); + + if ( scriptVar.IsFunction() ) { + scriptVar.GetFunction()->Call( NULL, parms ); + } + } +} + +/* +======================== +idSWF::Invoke +======================== +*/ +void idSWF::Invoke( const char * functionName, const idSWFParmList & parms, bool & functionExists ) { + idSWFScriptObject * obj = mainspriteInstance->GetScriptObject(); + idSWFScriptVar scriptVar = obj->Get( functionName ); + + if ( swf_debugInvoke.GetBool() ) { + idLib::Printf( "SWF: Invoke %s with %d parms (%s)\n", functionName, parms.Num(), GetName() ); + } + + if ( scriptVar.IsFunction() ) { + scriptVar.GetFunction()->Call( NULL, parms ); + functionExists = true; + } else { + functionExists = false; + } +} \ No newline at end of file diff --git a/neo/swf/SWF_ScriptFunction.h b/neo/swf/SWF_ScriptFunction.h new file mode 100644 index 00000000..a817b887 --- /dev/null +++ b/neo/swf/SWF_ScriptFunction.h @@ -0,0 +1,184 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_SCRIPTFUNCTION_H__ +#define __SWF_SCRIPTFUNCTION_H__ + +/* +======================== +Interface for calling functions from script +======================== +*/ +class idSWFScriptFunction { +public: + virtual ~idSWFScriptFunction() {}; + + virtual idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ){ return idSWFScriptVar(); }; // this should never be hit + virtual void AddRef(){}; + virtual void Release(){}; + virtual idSWFScriptObject *GetPrototype() { return NULL; } + virtual void SetPrototype( idSWFScriptObject * _object ) { } +}; + +/* +======================== +Interface for calling functions from script, implemented statically +======================== +*/ +class idSWFScriptFunction_Static : public idSWFScriptFunction { +public: + idSWFScriptFunction_Static() { } + virtual void AddRef() { } + virtual void Release() { } +}; + +/* +======================== +Interface for calling functions from script, implemented natively on a nested class object +======================== +*/ +template< typename T > +class idSWFScriptFunction_Nested : public idSWFScriptFunction { +protected: + T * pThis; +public: + idSWFScriptFunction_Nested() : pThis( NULL ) { } + + idSWFScriptFunction * Bind( T * _pThis ) { pThis = _pThis; return this; } + virtual void AddRef() { } + virtual void Release() { } +}; + +/* +======================== +Interface for calling functions from script, with reference counting +NOTE: The ref count starts at 0 here because it assumes you do an AddRef on the allocated +object. The proper way is to start it at 1 and force the caller to Release, but that's +really kind of a pain in the ass. It was made to be used like this: +object->Set( "myFunction", new idSWFScriptFunction_MyFunction() ); +======================== +*/ +class idSWFScriptFunction_RefCounted : public idSWFScriptFunction { +public: + idSWFScriptFunction_RefCounted() : refCount( 0 ) { } + void AddRef() { refCount++; } + void Release() { if ( --refCount <= 0 ) { delete this; } } +private: + int refCount; +}; + +/* +======================== +Action Scripts can define a pool of constants then push values from that pool +The documentation isn't very clear on the scope of these things, but from what +I've gathered by testing, pool is per-function and copied into the function +whenever that function is declared. +======================== +*/ +class idSWFConstantPool { +public: + idSWFConstantPool(); + ~idSWFConstantPool() { Clear(); } + + void Clear(); + void Copy( const idSWFConstantPool & other ); + idSWFScriptString * Get( int n ) { return pool[n]; } + void Append( idSWFScriptString * s ) { pool.Append( s ); } + +private: + idList< idSWFScriptString *, TAG_SWF > pool; +}; + +/* +======================== +The idSWFStack class is just a helper routine for treating an idList like a stack +======================== +*/ +class idSWFStack : public idList< idSWFScriptVar > { +public: + idSWFScriptVar & A() { return operator[]( Num() - 1 ); } + idSWFScriptVar & B() { return operator[]( Num() - 2 ); } + idSWFScriptVar & C() { return operator[]( Num() - 3 ); } + idSWFScriptVar & D() { return operator[]( Num() - 4 ); } + void Pop( int n ) { SetNum( Num() - n ); } +}; + +/* +======================== +idSWFScriptFunction_Script is a script function that's implemented in action script +======================== +*/ +class idSWFScriptFunction_Script : public idSWFScriptFunction { +public: + idSWFScriptFunction_Script() : refCount( 1 ), flags( 0 ), prototype( NULL ), data( NULL ), length( 0 ), defaultSprite( NULL ) { registers.SetNum( 4 ); } + virtual ~idSWFScriptFunction_Script(); + + static idSWFScriptFunction_Script * Alloc() { return new (TAG_SWF) idSWFScriptFunction_Script; } + void AddRef() { refCount++; } + void Release() { if ( --refCount == 0 ) { delete this; } } + + // This could all be passed to Alloc (and was at one time) but in some places it's far more convenient to specify each separately + void SetFlags( uint16 _flags ) { flags = _flags; } + void SetData( const byte * _data, uint32 _length ) { data = _data; length = _length; } + void SetScope( idList & scope ); + void SetConstants( const idSWFConstantPool & _constants ) { constants.Copy( _constants ); } + void SetDefaultSprite( idSWFSpriteInstance * _sprite ) { defaultSprite = _sprite; } + void AllocRegisters( int numRegs ) { registers.SetNum( numRegs ); } + void AllocParameters( int numParms ) { parameters.SetNum( numParms ); } + void SetParameter( uint8 n, uint8 r, const char * name ) { parameters[n].reg = r; parameters[n].name = name; } + + idSWFScriptObject * GetPrototype() { return prototype; } + void SetPrototype( idSWFScriptObject * _prototype ) { _prototype->AddRef(); assert( prototype == NULL ); prototype = _prototype; } + + virtual idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ); + +private: + idSWFScriptVar Run( idSWFScriptObject * thisObject, idSWFStack & stack, idSWFBitStream & bitstream ); + +private: + int refCount; + + uint16 flags; + const byte * data; + uint32 length; + idSWFScriptObject * prototype; + + idSWFSpriteInstance * defaultSprite; // some actions have an implicit sprite they work off of (e.g. Action_GotoFrame outside of object scope) + + idList< idSWFScriptObject *, TAG_SWF > scope; + + idSWFConstantPool constants; + idList< idSWFScriptVar, TAG_SWF > registers; + + struct parmInfo_t { + const char * name; + uint8 reg; + }; + idList< parmInfo_t, TAG_SWF > parameters; +}; + +#endif // !__SWF_SCRIPTFUNCTION_H__ diff --git a/neo/swf/SWF_ScriptObject.cpp b/neo/swf/SWF_ScriptObject.cpp new file mode 100644 index 00000000..9c0b4f67 --- /dev/null +++ b/neo/swf/SWF_ScriptObject.cpp @@ -0,0 +1,580 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +idCVar swf_debugShowAddress( "swf_debugShowAddress", "0", CVAR_BOOL, "shows addresses along with object types when they are serialized" ); + + +/* +======================== +idSWFScriptObject::swfNamedVar_t::~swfNamedVar_t +======================== +*/ +idSWFScriptObject::swfNamedVar_t::~swfNamedVar_t() { +} + +/* +======================== +idSWFScriptObject::swfNamedVar_t::operator= +======================== +*/ +idSWFScriptObject::swfNamedVar_t & idSWFScriptObject::swfNamedVar_t::operator=( const swfNamedVar_t & other ) { + if ( &other != this ) { + index = other.index; + name = other.name; + hashNext = other.hashNext; + value = other.value; + native = other.native; + flags = other.flags; + } + return *this; +} + +/* +======================== +idSWFScriptObject::idSWFScriptObject +======================== +*/ +idSWFScriptObject::idSWFScriptObject() : prototype( NULL ), refCount( 1 ), noAutoDelete( false ), objectType( SWF_OBJECT_OBJECT ) { + data.sprite = NULL; + data.text = NULL; + Clear(); + refCount = 1; +} + +/* +======================== +idSWFScriptObject::~idSWFScriptObject +======================== +*/ +idSWFScriptObject::~idSWFScriptObject() { + if ( prototype != NULL ) { + prototype->Release(); + } +} + +/* +======================== +idSWFScriptObject::Alloc +======================== +*/ +idSWFScriptObject * idSWFScriptObject::Alloc() { + return new (TAG_SWF) idSWFScriptObject; +} + +/* +======================== +idSWFScriptObject::AddRef +======================== +*/ +void idSWFScriptObject::AddRef() { + refCount++; +} + +/* +======================== +idSWFScriptObject::Release +======================== +*/ +void idSWFScriptObject::Release() { + if ( --refCount == 0 && !noAutoDelete ) { + delete this; + } +} + +/* +======================== +idSWFScriptObject::Clear +======================== +*/ +void idSWFScriptObject::Clear() { + variables.Clear(); + for ( int i = 0; i < VARIABLE_HASH_BUCKETS; i++ ) { + variablesHash[i] = -1; + } +} + +/* +======================== +idSWFScriptObject::HasProperty +======================== +*/ +bool idSWFScriptObject::HasProperty( const char * name ) { + return ( GetVariable( name, false ) != NULL ); +} + +/* +======================== +idSWFScriptObject::HasValidProperty +======================== +*/ +bool idSWFScriptObject::HasValidProperty( const char * name ) { + idSWFScriptObject::swfNamedVar_t * const variable = GetVariable( name, false ); + if ( variable == NULL ) { + return false; + } + if ( variable->native != NULL ) { + idSWFScriptVar nv = variable->native->Get( this ); + if ( nv.IsNULL() || nv.IsUndefined() ) { + return false; + } + } else { + if ( variable->value.IsNULL() || variable->value.IsUndefined() ) { + return false; + } + } + return true; +} + +/* +======================== +idSWFScriptObject::Get +======================== +*/ +idSWFScriptVar idSWFScriptObject::Get( const char * name ) { + swfNamedVar_t * variable = GetVariable( name, false ); + if ( variable == NULL ) { + return idSWFScriptVar(); + } else { + if ( variable->native ) { + return variable->native->Get( this ); + } else { + return variable->value; + } + } +} + +/* +======================== +idSWFScriptObject::Get +======================== +*/ +idSWFScriptVar idSWFScriptObject::Get( int index ) { + swfNamedVar_t * variable = GetVariable( index, false ); + if ( variable == NULL ) { + return idSWFScriptVar(); + } else { + if ( variable->native ) { + return variable->native->Get( this ); + } else { + return variable->value; + } + } +} + +/* +======================== +idSWFScriptObject::GetSprite +======================== +*/ +idSWFSpriteInstance * idSWFScriptObject::GetSprite( int index ) { + idSWFScriptVar var = Get( index ); + return var.ToSprite(); +} + +/* +======================== +idSWFScriptObject::GetSprite +======================== +*/ +idSWFSpriteInstance * idSWFScriptObject::GetSprite( const char * name ) { + idSWFScriptVar var = Get( name ); + return var.ToSprite(); +} + +/* +======================== +idSWFScriptObject::GetObject +======================== +*/ +idSWFScriptObject * idSWFScriptObject::GetObject( int index ) { + idSWFScriptVar var = Get( index ); + if ( var.IsObject() ) { + return var.GetObject(); + } + return NULL; +} + +/* +======================== +idSWFScriptObject::GetObject +======================== +*/ +idSWFScriptObject * idSWFScriptObject::GetObject( const char * name ) { + idSWFScriptVar var = Get( name ); + if ( var.IsObject() ) { + return var.GetObject(); + } + return NULL; +} + +/* +======================== +idSWFScriptObject::GetText +======================== +*/ +idSWFTextInstance * idSWFScriptObject::GetText( int index ) { + idSWFScriptVar var = Get( index ); + if ( var.IsObject() ) { + return var.GetObject()->GetText(); + } + return NULL; +} + +/* +======================== +idSWFScriptObject::GetText +======================== +*/ +idSWFTextInstance * idSWFScriptObject::GetText( const char * name ) { + idSWFScriptVar var = Get( name ); + if ( var.IsObject() ) { + return var.GetObject()->GetText(); + } + return NULL; +} + +/* +======================== +idSWFScriptObject::Set +======================== +*/ +void idSWFScriptObject::Set( const char * name, const idSWFScriptVar & value ) { + if ( objectType == SWF_OBJECT_ARRAY ) { + if ( idStr::Cmp( name, "length" ) == 0 ) { + int newLength = value.ToInteger(); + for ( int i = 0; i < variables.Num(); i++ ) { + if ( variables[i].index >= newLength ) { + variables.RemoveIndexFast( i ); + i--; + } + } + // rebuild the hash table + for ( int i = 0; i < VARIABLE_HASH_BUCKETS; i++ ) { + variablesHash[i] = -1; + } + for ( int i = 0; i < variables.Num(); i++ ) { + int hash = idStr::Hash( variables[i].name.c_str() ) & ( VARIABLE_HASH_BUCKETS - 1 ); + variables[i].hashNext = variablesHash[hash]; + variablesHash[hash] = i; + } + } else { + int iName = atoi( name ); + if ( iName > 0 || ( iName == 0 && idStr::Cmp( name, "0" ) == 0 ) ) { + swfNamedVar_t * lengthVar = GetVariable( "length", true ); + if ( lengthVar->value.ToInteger() <= iName ) { + lengthVar->value = idSWFScriptVar( iName + 1 ); + } + } + } + } + + swfNamedVar_t * variable = GetVariable( name, true ); + if ( variable->native ) { + variable->native->Set( this, value ); + } else if ( ( variable->flags & SWF_VAR_FLAG_READONLY ) == 0 ) { + variable->value = value; + } +} + +/* +======================== +idSWFScriptObject::Set +======================== +*/ +void idSWFScriptObject::Set( int index, const idSWFScriptVar & value ) { + if ( index < 0 ) { + extern idCVar swf_debug; + if ( swf_debug.GetBool() ) { + idLib::Printf( "SWF: Trying to set a negative array index.\n" ); + } + return; + } + if ( objectType == SWF_OBJECT_ARRAY ) { + swfNamedVar_t * lengthVar = GetVariable( "length", true ); + if ( lengthVar->value.ToInteger() <= index ) { + lengthVar->value = idSWFScriptVar( index + 1 ); + } + } + + swfNamedVar_t * variable = GetVariable( index, true ); + if ( variable->native ) { + variable->native->Set( this, value ); + } else if ( ( variable->flags & SWF_VAR_FLAG_READONLY ) == 0 ) { + variable->value = value; + } +} + +/* +======================== +idSWFScriptObject::SetNative +======================== +*/ +void idSWFScriptObject::SetNative( const char * name, idSWFScriptNativeVariable * native ) { + swfNamedVar_t * variable = GetVariable( name, true ); + variable->flags = SWF_VAR_FLAG_DONTENUM; + variable->native = native; + if ( native->IsReadOnly() ) { + variable->flags |= SWF_VAR_FLAG_READONLY; + } +} + +/* +======================== +idSWFScriptObject::DefaultValue +======================== +*/ +idSWFScriptVar idSWFScriptObject::DefaultValue( bool stringHint ) { + const char * methods[2] = { "toString", "valueOf" }; + if ( !stringHint ) { + SwapValues( methods[0], methods[1] ); + } + for ( int i = 0; i < 2; i++ ) { + idSWFScriptVar method = Get( methods[i] ); + if ( method.IsFunction() ) { + idSWFScriptVar value = method.GetFunction()->Call( this, idSWFParmList() ); + if ( !value.IsObject() && !value.IsFunction() ) { + return value; + } + } + } + switch ( objectType ) { + case SWF_OBJECT_OBJECT: + if ( swf_debugShowAddress.GetBool() ) { + return idSWFScriptVar( va( "[object:%p]", this ) ); + } else { + return idSWFScriptVar( "[object]" ); + } + case SWF_OBJECT_ARRAY: + if ( swf_debugShowAddress.GetBool() ) { + return idSWFScriptVar( va( "[array:%p]", this ) ); + } else { + return idSWFScriptVar( "[array]" ); + } + case SWF_OBJECT_SPRITE: + if ( data.sprite != NULL ) { + if ( data.sprite->parent == NULL ) { + return idSWFScriptVar( "[_root]" ); + } else { + return idSWFScriptVar( va( "[%s]", data.sprite->GetName() ) ); + } + } else { + return idSWFScriptVar( "[NULL]" ); + } + case SWF_OBJECT_TEXT: + if ( swf_debugShowAddress.GetBool() ) { + return idSWFScriptVar( va( "[edittext:%p]", this ) ); + } else { + return idSWFScriptVar( "[edittext]" ); + } + } + return idSWFScriptVar( "[unknown]" ); +} + +/* +======================== +idSWFScriptObject::GetVariable +======================== +*/ +idSWFScriptObject::swfNamedVar_t * idSWFScriptObject::GetVariable( int index, bool create ) { + for ( int i = 0; i < variables.Num(); i++ ) { + if ( variables[i].index == index ) { + return &variables[i]; + } + } + if ( create ) { + swfNamedVar_t * variable = &variables.Alloc(); + variable->flags = SWF_VAR_FLAG_NONE; + variable->index = index; + variable->name = va( "%d", index ); + variable->native = NULL; + int hash = idStr::Hash( variable->name ) & ( VARIABLE_HASH_BUCKETS - 1 ); + variable->hashNext = variablesHash[hash]; + variablesHash[hash] = variables.Num() - 1; + return variable; + } + return NULL; +} + +/* +======================== +idSWFScriptObject::GetVariable +======================== +*/ +idSWFScriptObject::swfNamedVar_t * idSWFScriptObject::GetVariable( const char * name, bool create ) { + int hash = idStr::Hash( name ) & ( VARIABLE_HASH_BUCKETS - 1 ); + for ( int i = variablesHash[hash]; i >= 0; i = variables[i].hashNext ) { + if ( variables[i].name == name ) { + return &variables[i]; + } + } + + if ( prototype != NULL ) { + swfNamedVar_t * variable = prototype->GetVariable( name, false ); + if ( ( variable != NULL ) && ( variable->native || !create ) ) { + // If the variable is native, we want to pull it from the prototype even if we're going to set it + return variable; + } + } + + if ( create ) { + swfNamedVar_t * variable = &variables.Alloc(); + variable->flags = SWF_VAR_FLAG_NONE; + variable->index = atoi( name ); + if ( variable->index == 0 && idStr::Cmp( name, "0" ) != 0 ) { + variable->index = -1; + } + variable->name = name; + variable->native = NULL; + variable->hashNext = variablesHash[hash]; + variablesHash[hash] = variables.Num() - 1; + return variable; + } + return NULL; +} + +/* +======================== +idSWFScriptObject::MakeArray +======================== +*/ +void idSWFScriptObject::MakeArray() { + objectType = SWF_OBJECT_ARRAY; + swfNamedVar_t * variable = GetVariable( "length", true ); + variable->value = idSWFScriptVar( 0 ); + variable->flags = SWF_VAR_FLAG_DONTENUM; +} + +/* +======================== +idSWFScriptObject::GetNestedVar +======================== +*/ +idSWFScriptVar idSWFScriptObject::GetNestedVar( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + const char * const args[] = { arg1, arg2, arg3, arg4, arg5, arg6 }; + const int numArgs = sizeof( args ) / sizeof( const char * ); + + idStaticList< const char *, numArgs > vars; + for ( int i = 0; i < numArgs && args[ i ] != NULL; ++i ) { + vars.Append( args[ i ] ); + } + + idSWFScriptObject * baseObject = this; + idSWFScriptVar retVal; + + for ( int i = 0; i < vars.Num(); ++i ) { + idSWFScriptVar var = baseObject->Get( vars[ i ] ); + + // when at the end of object path just use the latest value as result + if ( i == vars.Num() - 1 ) { + retVal = var; + break; + } + + // encountered variable in path that wasn't an object + if ( !var.IsObject() ) { + retVal = idSWFScriptVar(); + break; + } + + baseObject = var.GetObject(); + } + + return retVal; +} + +/* +======================== +idSWFScriptObject::GetNestedObj +======================== +*/ +idSWFScriptObject * idSWFScriptObject::GetNestedObj( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + idSWFScriptVar var = GetNestedVar( arg1, arg2, arg3, arg4, arg5, arg6 ); + + if ( !var.IsObject() ) { + return NULL; + } + + return var.GetObject(); +} + +/* +======================== +idSWFScriptObject::GetNestedSprite +======================== +*/ +idSWFSpriteInstance * idSWFScriptObject::GetNestedSprite( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + idSWFScriptVar var = GetNestedVar( arg1, arg2, arg3, arg4, arg5, arg6 ); + return var.ToSprite(); + +} + +/* +======================== +idSWFScriptObject::GetNestedText +======================== +*/ +idSWFTextInstance * idSWFScriptObject::GetNestedText( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + idSWFScriptVar var = GetNestedVar( arg1, arg2, arg3, arg4, arg5, arg6 ); + return var.ToText(); + +} + +/* +======================== +idSWFScriptObject::PrintToConsole +======================== +*/ +void idSWFScriptObject::PrintToConsole() const { + if ( variables.Num() > 0 ) { + idLib::Printf( "%d subelements:\n", variables.Num() ); + int maxVarLength = 0; + + for ( int i = 0; i < variables.Num(); ++i ) { + const idSWFScriptObject::swfNamedVar_t & nv = variables[ i ]; + const int nameLength = idStr::Length( nv.name ); + if ( maxVarLength < nameLength ) { + maxVarLength = nameLength; + } + } + + maxVarLength += 2; // a little extra padding + + const char * const fmt = va( "%%-%ds %%-10s %%-s\n", maxVarLength ); + idLib::Printf( fmt, "Name", "Type", "Value" ); + idLib::Printf( "------------------------------------------------------------\n" ); + for ( int i = 0; i < variables.Num(); ++i ) { + const idSWFScriptObject::swfNamedVar_t & nv = variables[ i ]; + idLib::Printf( fmt, nv.name.c_str(), nv.value.TypeOf(), + nv.value.ToString().c_str() ); + } + } else { + idLib::Printf( "No subelements\n" ); + } +} \ No newline at end of file diff --git a/neo/swf/SWF_ScriptObject.h b/neo/swf/SWF_ScriptObject.h new file mode 100644 index 00000000..351bb75a --- /dev/null +++ b/neo/swf/SWF_ScriptObject.h @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_SCRIPTOBJECT_H__ +#define __SWF_SCRIPTOBJECT_H__ + +class idSWFSpriteInstance; + +/* +======================== +This is the base class for script variables which are implemented in code +======================== +*/ +class idSWFScriptNativeVariable { +public: + virtual bool IsReadOnly() { return false; } + virtual void Set( class idSWFScriptObject * object, const idSWFScriptVar & value ) = 0; + virtual idSWFScriptVar Get( class idSWFScriptObject * object ) = 0; +}; + +#define SWF_NATIVE_VAR_DECLARE( x ) \ + class idSWFScriptNativeVar_##x : public idSWFScriptNativeVariable { \ + public: \ + void Set( class idSWFScriptObject * object, const idSWFScriptVar & value ); \ + idSWFScriptVar Get( class idSWFScriptObject * object ); \ + } swfScriptVar_##x; + +#define SWF_NATIVE_VAR_DECLARE_READONLY( x ) \ + class idSWFScriptNativeVar_##x : public idSWFScriptNativeVariable { \ + public: \ + bool IsReadOnly() { return true; } \ + void Set( class idSWFScriptObject * object, const idSWFScriptVar & value ) { assert( false ); } \ + idSWFScriptVar Get( class idSWFScriptObject * object ); \ + } swfScriptVar_##x; + +/* +======================== +This is a helper class for quickly setting up native variables which need access to a parent class +======================== +*/ +template< typename T > +class idSWFScriptNativeVariable_Nested : public idSWFScriptNativeVariable { +public: + idSWFScriptNativeVariable_Nested() : pThis( NULL ) { } + idSWFScriptNativeVariable_Nested * Bind( T * p ) { pThis = p; return this; } + virtual void Set( class idSWFScriptObject * object, const idSWFScriptVar & value ) = 0; + virtual idSWFScriptVar Get( class idSWFScriptObject * object ) = 0; +protected: + T * pThis; +}; + +#define SWF_NATIVE_VAR_DECLARE_NESTED( x, y ) \ + class idSWFScriptNativeVar_##x : public idSWFScriptNativeVariable_Nested { \ + public: \ + void Set( class idSWFScriptObject * object, const idSWFScriptVar & value ); \ + idSWFScriptVar Get( class idSWFScriptObject * object ); \ + } swfScriptVar_##x; + +#define SWF_NATIVE_VAR_DECLARE_NESTED_READONLY( x, y, z ) \ + class idSWFScriptNativeVar_##x : public idSWFScriptNativeVariable_Nested { \ + public: \ + bool IsReadOnly() { return true; } \ + void Set( class idSWFScriptObject * object, const idSWFScriptVar & value ) { assert( false ); } \ + idSWFScriptVar Get( class idSWFScriptObject * object ) { return pThis->z; } \ + } swfScriptVar_##x; + +/* +======================== +An object in an action script is a collection of variables. functions are also variables. +======================== +*/ +class idSWFScriptObject { +public: + idSWFScriptObject(); + virtual ~idSWFScriptObject(); + + static idSWFScriptObject * Alloc(); + void AddRef(); + void Release(); + void SetNoAutoDelete( bool b ) { noAutoDelete = b; } + + void Clear(); + + void MakeArray(); + + void SetSprite( idSWFSpriteInstance * s ) { objectType = SWF_OBJECT_SPRITE; data.sprite = s; } + idSWFSpriteInstance * GetSprite() { return ( objectType == SWF_OBJECT_SPRITE ) ? data.sprite : NULL; } + + void SetText( idSWFTextInstance * t ) { objectType = SWF_OBJECT_TEXT; data.text = t; } + idSWFTextInstance * GetText() { return ( objectType == SWF_OBJECT_TEXT ) ? data.text : NULL; } + + // Also accessible via __proto__ property + idSWFScriptObject * GetPrototype() { return prototype; } + void SetPrototype( idSWFScriptObject *_prototype ) { assert( prototype == NULL ); prototype = _prototype; prototype->AddRef(); } + idSWFScriptVar Get( int index ); + idSWFScriptVar Get( const char * name ); + idSWFSpriteInstance * GetSprite( int index ); + idSWFSpriteInstance * GetSprite( const char * name ); + idSWFScriptObject * GetObject( int index ); + idSWFScriptObject * GetObject( const char * name ); + idSWFTextInstance * GetText( int index ); + idSWFTextInstance * GetText( const char * name ); + void Set( int index, const idSWFScriptVar & value ); + void Set( const char * name, const idSWFScriptVar & value ); + void SetNative( const char * name, idSWFScriptNativeVariable * native ); + bool HasProperty( const char * name ); + bool HasValidProperty( const char * name ); + idSWFScriptVar DefaultValue( bool stringHint ); + + // This is to implement for-in (fixme: respect DONTENUM flag) + int NumVariables() { return variables.Num(); } + const char * EnumVariable( int i ) { return variables[i].name; } + + idSWFScriptVar GetNestedVar( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + idSWFScriptObject * GetNestedObj( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + idSWFSpriteInstance * GetNestedSprite( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + idSWFTextInstance * GetNestedText( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + + void PrintToConsole() const; + +private: + int refCount; + bool noAutoDelete; + + enum swfNamedVarFlags_t { + SWF_VAR_FLAG_NONE = 0, + SWF_VAR_FLAG_READONLY = BIT(1), + SWF_VAR_FLAG_DONTENUM = BIT(2) + }; + struct swfNamedVar_t { + swfNamedVar_t() : native( NULL ) { } + ~swfNamedVar_t(); + swfNamedVar_t & operator=( const swfNamedVar_t & other ); + + int index; + int hashNext; + idStr name; + idSWFScriptVar value; + idSWFScriptNativeVariable * native; + int flags; + }; + idList< swfNamedVar_t, TAG_SWF > variables; + + static const int VARIABLE_HASH_BUCKETS = 16; + int variablesHash[VARIABLE_HASH_BUCKETS]; + + idSWFScriptObject * prototype; + + enum swfObjectType_t { + SWF_OBJECT_OBJECT, + SWF_OBJECT_ARRAY, + SWF_OBJECT_SPRITE, + SWF_OBJECT_TEXT + } objectType; + + union swfObjectData_t { + idSWFSpriteInstance * sprite; // only valid if objectType == SWF_OBJECT_SPRITE + idSWFTextInstance * text; // only valid if objectType == SWF_OBJECT_TEXT + } data; + + swfNamedVar_t * GetVariable( int index, bool create ); + swfNamedVar_t * GetVariable( const char * name, bool create ); +}; + +#endif // !__SWF_SCRIPTOBJECT_H__ diff --git a/neo/swf/SWF_ScriptVar.cpp b/neo/swf/SWF_ScriptVar.cpp new file mode 100644 index 00000000..d2ff1f76 --- /dev/null +++ b/neo/swf/SWF_ScriptVar.cpp @@ -0,0 +1,422 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +extern idCVar swf_debugShowAddress; + +/* +======================== +idSWFScriptVar::idSWFScriptVar +======================== +*/ +idSWFScriptVar::idSWFScriptVar( const idSWFScriptVar & other ) { + type = other.type; + value = other.value; + if ( other.type == SWF_VAR_STRING ) { + other.value.string->AddRef(); + } else if ( other.type == SWF_VAR_OBJECT ) { + other.value.object->AddRef(); + } else if ( other.type == SWF_VAR_FUNCTION ) { + other.value.function->AddRef(); + } +} + +/* +======================== +idSWFScriptVar::operator= +======================== +*/ +idSWFScriptVar & idSWFScriptVar::operator=( const idSWFScriptVar & other ) { + if ( this != &other ) { + Free(); + type = other.type; + value = other.value; + if ( other.type == SWF_VAR_STRING ) { + other.value.string->AddRef(); + } else if ( other.type == SWF_VAR_OBJECT ) { + other.value.object->AddRef(); + } else if ( other.type == SWF_VAR_FUNCTION ) { + other.value.function->AddRef(); + } + } + return *this; +} + +/* +======================== +idSWFScriptVar::~idSWFScriptVar +======================== +*/ +idSWFScriptVar::~idSWFScriptVar() { + Free(); +} + +/* +======================== +idSWFScriptVar::Free +======================== +*/ +void idSWFScriptVar::Free() { + if ( type == SWF_VAR_STRING ) { + value.string->Release(); + } else if ( type == SWF_VAR_OBJECT ) { + value.object->Release(); + } else if ( type == SWF_VAR_FUNCTION ) { + value.function->Release(); + } + value.string = NULL; + value.function = NULL; + value.object = NULL; + type = SWF_VAR_UNDEF; +} + +/* +======================== +idSWFScriptVar::SetObject +======================== +*/ +void idSWFScriptVar::SetObject( idSWFScriptObject * o ) { + Free(); + if ( o == NULL ) { + type = SWF_VAR_NULL; + } else { + type = SWF_VAR_OBJECT; + value.object = o; + o->AddRef(); + } +} + +/* +======================== +idSWFScriptVar::SetFunction +======================== +*/ +void idSWFScriptVar::SetFunction( idSWFScriptFunction * f ) { + Free(); + if ( f == NULL ) { + type = SWF_VAR_NULL; + } else { + type = SWF_VAR_FUNCTION; + value.function = f; + f->AddRef(); + } +} + +/* +======================== +idSWFScriptVar::StrictEquals +======================== +*/ +bool idSWFScriptVar::StrictEquals( const idSWFScriptVar & other ) { + if ( type != other.type ) { + return false; + } + switch ( type ) { + case SWF_VAR_STRINGID: return ( value.i == other.value.i ); + case SWF_VAR_STRING: return ( *value.string == *other.value.string ); + case SWF_VAR_FLOAT: return ( value.f == other.value.f ); + case SWF_VAR_BOOL: return ( value.b == other.value.b ); + case SWF_VAR_INTEGER: return ( value.i == other.value.i ); + case SWF_VAR_NULL: return true; + case SWF_VAR_UNDEF: return true; + case SWF_VAR_OBJECT: return ( value.object == other.value.object ); + case SWF_VAR_FUNCTION: return ( value.function == other.value.function ); + default: assert( false ); return false; + } +} + +/* +======================== +idSWFScriptVar::AbstractEquals +======================== +*/ +bool idSWFScriptVar::AbstractEquals( const idSWFScriptVar & other ) { + if ( type == other.type ) { + switch ( type ) { + case SWF_VAR_STRINGID: return ( value.i == other.value.i ); + case SWF_VAR_STRING: return ( *value.string == *other.value.string ); + case SWF_VAR_FLOAT: return ( value.f == other.value.f ); + case SWF_VAR_BOOL: return ( value.b == other.value.b ); + case SWF_VAR_INTEGER: return ( value.i == other.value.i ); + case SWF_VAR_NULL: return true; + case SWF_VAR_UNDEF: return true; + case SWF_VAR_OBJECT: return ( value.object == other.value.object ); + case SWF_VAR_FUNCTION: return ( value.function == other.value.function ); + default: assert( false ); return false; + } + } + switch ( type ) { + case SWF_VAR_STRINGID: return ToString() == other.ToString(); + case SWF_VAR_STRING: + switch ( other.type ) { + case SWF_VAR_STRINGID: return *value.string == other.ToString(); + case SWF_VAR_FLOAT: return ToFloat() == other.value.f; + case SWF_VAR_BOOL: return ToBool() == other.value.b; + case SWF_VAR_INTEGER: return ToInteger() == other.value.i; + case SWF_VAR_OBJECT: return *value.string == other.ToString(); + default: return false; + } + case SWF_VAR_FLOAT: return ( other.ToFloat() == value.f ); + case SWF_VAR_BOOL: return ( other.ToBool() == value.b ); + case SWF_VAR_INTEGER: return ( other.ToInteger() == value.i ); + case SWF_VAR_NULL: return ( other.type == SWF_VAR_UNDEF ); + case SWF_VAR_UNDEF: return ( other.type == SWF_VAR_NULL ); + case SWF_VAR_OBJECT: + switch ( other.type ) { + case SWF_VAR_STRING: return ToString() == *other.value.string; + case SWF_VAR_FLOAT: return ToFloat() == other.value.f; + case SWF_VAR_BOOL: return ToBool() == other.value.b; + case SWF_VAR_INTEGER: return ToInteger() == other.value.i; + default: return false; + } + case SWF_VAR_FUNCTION: return false; + default: assert( false ); return false; + } +} + +/* +======================== +idSWFScriptVar::ToString +======================== +*/ +idStr idSWFScriptVar::ToString() const { + switch ( type ) { + case SWF_VAR_STRINGID: return idStrId( value.i ).GetLocalizedString(); + case SWF_VAR_STRING: return *value.string; + + case SWF_VAR_FLOAT: return va( "%g", value.f ); + case SWF_VAR_BOOL: return value.b ? "true" : "false"; + case SWF_VAR_INTEGER: return va( "%i", value.i ); + + case SWF_VAR_NULL: return "[null]"; + case SWF_VAR_UNDEF: return "[undefined]"; + case SWF_VAR_OBJECT: return value.object->DefaultValue( true ).ToString(); + case SWF_VAR_FUNCTION: + if ( swf_debugShowAddress.GetBool() ) { + return va( "[function:%p]", value.function ); + } else { + return "[function]"; + } + default: assert( false ); return ""; + } +} + +/* +======================== +idSWFScriptVar::ToFloat +======================== +*/ +float idSWFScriptVar::ToFloat() const { + switch ( type ) { + case SWF_VAR_STRING: return atof( *value.string ); + + case SWF_VAR_FLOAT: return value.f; + case SWF_VAR_BOOL: return (float)value.b; + case SWF_VAR_INTEGER: return (float)value.i; + + case SWF_VAR_OBJECT: return value.object->DefaultValue( false ).ToFloat(); + + case SWF_VAR_FUNCTION: + case SWF_VAR_NULL: + case SWF_VAR_UNDEF: return 0.0f; + default: assert( false ); return 0.0f; + } +} + +/* +======================== +idSWFScriptVar::ToBool +======================== +*/ +bool idSWFScriptVar::ToBool() const { + switch ( type ) { + case SWF_VAR_STRING: return ( value.string->Icmp( "true" ) == 0 || value.string->Icmp( "1" ) == 0 ); + + case SWF_VAR_FLOAT: return ( value.f != 0.0f ); + case SWF_VAR_BOOL: return value.b; + case SWF_VAR_INTEGER: return value.i != 0; + + case SWF_VAR_OBJECT: return value.object->DefaultValue( false ).ToBool(); + + case SWF_VAR_FUNCTION: + case SWF_VAR_NULL: + case SWF_VAR_UNDEF: return false; + default: assert( false ); return false; + } +} + +/* +======================== +idSWFScriptVar::ToInteger +======================== +*/ +int32 idSWFScriptVar::ToInteger() const { + switch ( type ) { + case SWF_VAR_STRING: return atoi( *value.string ); + + case SWF_VAR_FLOAT: return idMath::Ftoi( value.f ); + + case SWF_VAR_BOOL: return value.b ? 1 : 0; + case SWF_VAR_INTEGER: return value.i; + + case SWF_VAR_OBJECT: return value.object->DefaultValue( false ).ToInteger(); + + case SWF_VAR_FUNCTION: + case SWF_VAR_NULL: + case SWF_VAR_UNDEF: return 0; + default: assert( false ); return 0; + } +} + +/* +======================== +idSWFScriptVar::ToSprite +======================== +*/ +idSWFSpriteInstance * idSWFScriptVar::ToSprite() { + if ( IsObject() && value.object != NULL ) { + return value.object->GetSprite(); + } + + return NULL; +} + +/* +======================== +idSWFScriptVar::ToText +======================== +*/ +idSWFTextInstance * idSWFScriptVar::ToText() { + if ( IsObject() && value.object != NULL ) { + return value.object->GetText(); + } + + return NULL; +} + +/* +======================== +idSWFScriptVar::GetNestedVar +======================== +*/ +idSWFScriptVar idSWFScriptVar::GetNestedVar( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + if ( !IsObject() ) { + return idSWFScriptVar(); + } + + return GetObject()->GetNestedVar( arg1, arg2, arg3, arg4, arg5, arg6 ); +} + +/* +======================== +idSWFScriptVar::GetNestedObj +======================== +*/ +idSWFScriptObject * idSWFScriptVar::GetNestedObj( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + if ( !IsObject() ) { + return NULL; + } + + return GetObject()->GetNestedObj( arg1, arg2, arg3, arg4, arg5, arg6 ); +} + +/* +======================== +idSWFScriptVar::GetNestedSprite +======================== +*/ +idSWFSpriteInstance * idSWFScriptVar::GetNestedSprite( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + if ( !IsObject() ) { + return NULL; + } + + return GetObject()->GetNestedSprite( arg1, arg2, arg3, arg4, arg5, arg6 ); +} + +/* +======================== +idSWFScriptVar::GetNestedSprite +======================== +*/ +idSWFTextInstance * idSWFScriptVar::GetNestedText( const char * arg1, const char * arg2, const char * arg3, const char * arg4, const char * arg5, const char * arg6 ) { + if ( !IsObject() ) { + return NULL; + } + + return GetObject()->GetNestedText( arg1, arg2, arg3, arg4, arg5, arg6 ); +} + +/* +======================== +idSWFScriptVar::TypeOf +======================== +*/ +const char * idSWFScriptVar::TypeOf() const { + switch ( type ) { + case SWF_VAR_STRINGID: return "stringid"; + case SWF_VAR_STRING: return "string"; + + case SWF_VAR_FLOAT: return "number"; + case SWF_VAR_BOOL: return "boolean"; + case SWF_VAR_INTEGER: return "number"; + + case SWF_VAR_OBJECT: + if ( value.object->GetSprite() != NULL ) { + return "movieclip"; + } else if ( value.object->GetText() != NULL ) { + return "text"; + } else { + return "object"; + } + + case SWF_VAR_FUNCTION: return "function"; + case SWF_VAR_NULL: return "null"; + case SWF_VAR_UNDEF: return "undefined"; + default: assert( false ); return ""; + } +} + +/* +======================== +idSWFScriptVar::PrintToConsole +======================== +*/ +void idSWFScriptVar::PrintToConsole() const { + idLib::Printf( "Object type: %s\n", TypeOf() ); + + if ( IsObject() ) { + GetObject()->PrintToConsole(); + } else if ( IsNumeric() ) { + idLib::Printf( "%d\n", ToInteger() ); + } else if ( IsString() ) { + idLib::Printf( "%s\n", ToString().c_str() ); + } else { + idLib::Printf( "unknown\n" ); + } +} diff --git a/neo/swf/SWF_ScriptVar.h b/neo/swf/SWF_ScriptVar.h new file mode 100644 index 00000000..fe062f8e --- /dev/null +++ b/neo/swf/SWF_ScriptVar.h @@ -0,0 +1,148 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_SCRIPTVAR_H__ +#define __SWF_SCRIPTVAR_H__ + +class idSWFScriptObject; +class idSWFScriptFunction; + +/* +======================== +A reference counted string +======================== +*/ +class idSWFScriptString : public idStr { +public: + idSWFScriptString( const idStr & s ) : idStr( s ), refCount( 1 ) { } + + static idSWFScriptString * Alloc( const idStr & s ) { return new (TAG_SWF) idSWFScriptString( s ); } + ID_INLINE void AddRef() { refCount++; } + ID_INLINE void Release() { if ( --refCount == 0 ) { delete this; } } + +private: + int refCount; +}; + +/* +======================== +A variable in an action script +these can be on the stack, in a script object, passed around as parameters, etc +they can contain raw data (int, float), strings, functions, or objects +======================== +*/ +class idSWFScriptVar { +public: + idSWFScriptVar() : type( SWF_VAR_UNDEF ) { } + idSWFScriptVar( const idSWFScriptVar & other ); + idSWFScriptVar( idSWFScriptObject * o ) : type( SWF_VAR_UNDEF ) { SetObject( o ); } + idSWFScriptVar( idStrId s ) : type( SWF_VAR_UNDEF ) { SetString( s ); } + idSWFScriptVar( const idStr & s ) : type( SWF_VAR_UNDEF ) { SetString( s ); } + idSWFScriptVar( const char * s ) : type( SWF_VAR_UNDEF ) { SetString( idStr( s ) ); } + idSWFScriptVar( float f ) : type( SWF_VAR_UNDEF ) { SetFloat( f ); } + idSWFScriptVar( bool b ) : type( SWF_VAR_UNDEF ) { SetBool( b ); } + idSWFScriptVar( int32 i ) : type( SWF_VAR_UNDEF ) { SetInteger( i ); } + idSWFScriptVar( idSWFScriptFunction * nf ) : type( SWF_VAR_UNDEF ) { SetFunction( nf ); } + ~idSWFScriptVar(); + + idSWFScriptVar & operator=( const idSWFScriptVar & other ); + + // implements ECMA 262 11.9.3 + bool AbstractEquals( const idSWFScriptVar & other ); + bool StrictEquals( const idSWFScriptVar & other ); + + void SetString( idStrId s ) { Free(); type = SWF_VAR_STRINGID; value.i = s.GetIndex(); } + void SetString( const idStr & s ) { Free(); type = SWF_VAR_STRING; value.string = idSWFScriptString::Alloc( s ); } + void SetString( const char * s ) { Free(); type = SWF_VAR_STRING; value.string = idSWFScriptString::Alloc( s ); } + void SetString( idSWFScriptString * s ) { Free(); type = SWF_VAR_STRING; value.string = s; s->AddRef(); } + void SetFloat( float f ) { Free(); type = SWF_VAR_FLOAT; value.f = f; } + void SetNULL() { Free(); type = SWF_VAR_NULL; } + void SetUndefined() { Free(); type = SWF_VAR_UNDEF; } + void SetBool( bool b ) { Free(); type = SWF_VAR_BOOL; value.b = b; } + void SetInteger( int32 i ) { Free(); type = SWF_VAR_INTEGER; value.i = i; } + + void SetObject( idSWFScriptObject * o ); + void SetFunction( idSWFScriptFunction * f ); + + idStr ToString() const; + float ToFloat() const; + bool ToBool() const; + int32 ToInteger() const; + + idSWFScriptObject * GetObject() { assert( type == SWF_VAR_OBJECT ); return value.object; } + idSWFScriptObject * GetObject() const { assert( type == SWF_VAR_OBJECT ); return value.object; } + idSWFScriptFunction * GetFunction() { assert( type == SWF_VAR_FUNCTION ); return value.function; } + idSWFSpriteInstance * ToSprite(); + idSWFTextInstance * ToText(); + + idSWFScriptVar GetNestedVar( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + idSWFScriptObject * GetNestedObj( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + idSWFSpriteInstance * GetNestedSprite( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + idSWFTextInstance * GetNestedText( const char * arg1, const char * arg2 = NULL, const char * arg3 = NULL, const char * arg4 = NULL, const char * arg5 = NULL, const char * arg6 = NULL ); + + const char * TypeOf() const; + + // debug print of this variable to the console + void PrintToConsole() const; + + bool IsString() const { return ( type == SWF_VAR_STRING ) || ( type == SWF_VAR_STRINGID ); } + bool IsNULL() const { return ( type == SWF_VAR_NULL ); } + bool IsUndefined() const { return ( type == SWF_VAR_UNDEF ); } + bool IsValid() const { return ( type != SWF_VAR_UNDEF ) && ( type != SWF_VAR_NULL ); } + bool IsFunction() const { return ( type == SWF_VAR_FUNCTION ); } + bool IsObject() const { return ( type == SWF_VAR_OBJECT ); } + bool IsNumeric() const { return ( type == SWF_VAR_FLOAT ) || ( type == SWF_VAR_INTEGER ) || ( type == SWF_VAR_BOOL ); } + + enum swfScriptVarType { + SWF_VAR_STRINGID, + SWF_VAR_STRING, + SWF_VAR_FLOAT, + SWF_VAR_NULL, + SWF_VAR_UNDEF, + SWF_VAR_BOOL, + SWF_VAR_INTEGER, + SWF_VAR_FUNCTION, + SWF_VAR_OBJECT + }; + + swfScriptVarType GetType() const { return type; } + +private: + void Free(); + swfScriptVarType type; + + union swfScriptVarValue_t { + float f; + int32 i; + bool b; + idSWFScriptObject * object; + idSWFScriptString * string; + idSWFScriptFunction * function; + } value; +}; + +#endif // !__SWF_SCRIPTVAR_H__ diff --git a/neo/swf/SWF_ShapeParser.cpp b/neo/swf/SWF_ShapeParser.cpp new file mode 100644 index 00000000..dbf735bb --- /dev/null +++ b/neo/swf/SWF_ShapeParser.cpp @@ -0,0 +1,903 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "float.h" + +#pragma warning( disable: 4189 ) // local variable is initialized but not referenced + +/* +======================== +idSWFShapeParser::ParseShape +======================== +*/ +void idSWFShapeParser::Parse( idSWFBitStream & bitstream, idSWFShape & shape, int recordType ) { + extendedCount = ( recordType > 1 ); + lineStyle2 = ( recordType == 4 ); + rgba = ( recordType >= 3 ); + morph = false; + + bitstream.ReadRect( shape.startBounds ); + shape.endBounds = shape.startBounds; + + if ( recordType == 4 ) { + swfRect_t edgeBounds; + bitstream.ReadRect( edgeBounds ); + bitstream.ReadU8(); // flags (that we ignore) + } + + ReadFillStyle( bitstream ); + ParseShapes( bitstream, NULL, false ); + TriangulateSoup( shape ); + + shape.lineDraws.SetNum( lineDraws.Num() ); + for ( int i = 0; i < lineDraws.Num(); i++ ) { + idSWFShapeDrawLine & ld = shape.lineDraws[i]; + swfSPDrawLine_t & spld = lineDraws[i]; + ld.style = spld.style; + ld.indices.SetNum( spld.edges.Num() * 3 ); + ld.indices.SetNum( 0 ); + for ( int e = 0; e < spld.edges.Num(); e++ ) { + int v0 = ld.startVerts.AddUnique( verts[ spld.edges[e].start.v0 ] ); + ld.indices.Append( v0 ); + ld.indices.Append( v0 ); + + // Rather then tesselating curves at run time, we do them once here by inserting a vert every 10 units + // It may not wind up being 10 actual pixels when rendered because the shape may have scaling applied to it + if ( spld.edges[e].start.cp != 0xFFFF ) { + assert( spld.edges[e].end.cp != 0xFFFF ); + float length1 = ( verts[ spld.edges[e].start.v0 ] - verts[ spld.edges[e].start.v1 ] ).Length(); + float length2 = ( verts[ spld.edges[e].end.v0 ] - verts[ spld.edges[e].end.v1 ] ).Length(); + int numPoints = 1 + idMath::Ftoi( Max( length1, length2 ) / 10.0f ); + for ( int ti = 0; ti < numPoints; ti++ ) { + float t0 = ( ti + 1 ) / ( (float) numPoints + 1.0f ); + float t1 = ( 1.0f - t0 ); + float c1 = t1 * t1; + float c2 = t0 * t1 * 2.0f; + float c3 = t0 * t0; + + idVec2 p1 = c1 * verts[ spld.edges[e].start.v0 ]; + p1 += c2 * verts[ spld.edges[e].start.cp ]; + p1 += c3 * verts[ spld.edges[e].start.v1 ]; + + int v1 = ld.startVerts.AddUnique( p1 ); + ld.indices.Append( v1 ); + ld.indices.Append( v1 ); + ld.indices.Append( v1 ); + } + } + ld.indices.Append( ld.startVerts.AddUnique( verts[ spld.edges[e].start.v1 ] ) ); + } + } +} + +/* +======================== +idSWFShapeParser::ParseMorph +======================== +*/ +void idSWFShapeParser::ParseMorph( idSWFBitStream & bitstream, idSWFShape & shape ) { + extendedCount = true; + lineStyle2 = false; + rgba = true; + morph = true; + + bitstream.ReadRect( shape.startBounds ); + bitstream.ReadRect( shape.endBounds ); + + uint32 offset = bitstream.ReadU32(); + + // offset is the byte offset from the current read position to the 'endShape' record + // we read the entire block into 'bitstream1' which moves the read pointer of 'bitstream' + // to the start of the 'endShape' record + + idSWFBitStream bitstream1; + bitstream1.Load( (byte *)bitstream.ReadData( offset ), offset, false ); + + ReadFillStyle( bitstream1 ); + ParseShapes( bitstream1, &bitstream, true ); + TriangulateSoup( shape ); +} + +/* +======================== +idSWFShapeParser::ParseFont +======================== +*/ +void idSWFShapeParser::ParseFont( idSWFBitStream & bitstream, idSWFFontGlyph & shape ) { + extendedCount = false; + lineStyle2 = false; + rgba = false; + morph = false; + + fillDraws.SetNum( 1 ); + + ParseShapes( bitstream, NULL, true ); + TriangulateSoup( shape ); +} + +/* +======================== +idSWFShapeParser::ParseShapes +======================== +*/ +void idSWFShapeParser::ParseShapes( idSWFBitStream & bitstream1, idSWFBitStream * bitstream2, bool swap ) { + int32 pen1X = 0; + int32 pen1Y = 0; + int32 pen2X = 0; + int32 pen2Y = 0; + + uint8 fillStyle0 = 0; + uint8 fillStyle1 = 0; + uint8 lineStyle = 0; + + uint16 baseFillStyle = 0; + uint16 baseLineStyle = 0; + + uint8 numBits = bitstream1.ReadU8(); + uint8 numFillBits1 = numBits >> 4; + uint8 numLineBits1 = numBits & 0xF; + + uint8 numFillBits2 = 0; + uint8 numLineBits2 = 0; + + if ( bitstream2 ) { + numBits = bitstream2->ReadU8(); + numFillBits2 = numBits >> 4; + numLineBits2 = numBits & 0xF; + } + + while ( true ) { + if ( !bitstream1.ReadBool() ) { + bool stateNewStyles = bitstream1.ReadBool(); + bool stateLineStyle = bitstream1.ReadBool(); + bool stateFillStyle1 = bitstream1.ReadBool(); + bool stateFillStyle0 = bitstream1.ReadBool(); + bool stateMoveTo = bitstream1.ReadBool(); + if ( ( stateNewStyles || stateLineStyle || stateFillStyle1 || stateFillStyle0 || stateMoveTo ) == false ) { + // end record + if ( bitstream2 ) { + uint8 flags = bitstream2->ReadU( 6 ); + if ( flags != 0 ) { + idLib::Warning( "idSWFShapeParser: morph stream 1 ends before 2" ); + break; + } + } + break; + } + if ( stateMoveTo ) { + uint8 moveBits = bitstream1.ReadU( 5 ); + pen1X = bitstream1.ReadS( moveBits ); + pen1Y = bitstream1.ReadS( moveBits ); + } + if ( stateFillStyle0 ) { + fillStyle0 = bitstream1.ReadU( numFillBits1 ); + } + if ( stateFillStyle1 ) { + fillStyle1 = bitstream1.ReadU( numFillBits1 ); + } + if ( stateLineStyle ) { + lineStyle = bitstream1.ReadU( numLineBits1 ); + } + if ( stateNewStyles ) { + baseFillStyle = fillDraws.Num(); + baseLineStyle = lineDraws.Num(); + ReadFillStyle( bitstream1 ); + numBits = bitstream1.ReadU8(); + numFillBits1 = numBits >> 4; + numLineBits1 = numBits & 0xF; + } + if ( bitstream2 ) { + bool isEdge = bitstream2->ReadBool(); + if ( isEdge ) { + idLib::Warning( "idSWFShapeParser: morph stream 1 defines style change, but stream 2 does not" ); + break; + } + bool stateNewStyles = bitstream2->ReadBool(); + bool stateLineStyle = bitstream2->ReadBool(); + bool stateFillStyle1 = bitstream2->ReadBool(); + bool stateFillStyle0 = bitstream2->ReadBool(); + bool stateMoveTo = bitstream2->ReadBool(); + if ( stateMoveTo ) { + uint8 moveBits = bitstream2->ReadU( 5 ); + pen2X = bitstream2->ReadS( moveBits ); + pen2Y = bitstream2->ReadS( moveBits ); + } + if ( stateFillStyle0 ) { + if ( bitstream2->ReadU( numFillBits2 ) != fillStyle0 ) { + idLib::Warning( "idSWFShapeParser: morph stream 2 defined a different fillStyle0 from stream 1" ); + break; + } + } + if ( stateFillStyle1 ) { + if ( bitstream2->ReadU( numFillBits2 ) != fillStyle1 ) { + idLib::Warning( "idSWFShapeParser: morph stream 2 defined a different fillStyle1 from stream 1" ); + break; + } + } + if ( stateLineStyle ) { + if ( bitstream2->ReadU( numLineBits2 ) != lineStyle ) { + idLib::Warning( "idSWFShapeParser: morph stream 2 defined a different lineStyle from stream 1" ); + break; + } + } + if ( stateNewStyles ) { + idLib::Warning( "idSWFShapeParser: morph stream 2 defines new styles" ); + break; + } + } + } else { + swfSPMorphEdge_t morphEdge; + + ParseEdge( bitstream1, pen1X, pen1Y, morphEdge.start ); + if ( bitstream2 ) { + bool isEdge = bitstream2->ReadBool(); + if ( !isEdge ) { + idLib::Warning( "idSWFShapeParser: morph stream 1 defines an edge, but stream 2 does not" ); + break; + } + ParseEdge( *bitstream2, pen2X, pen2Y, morphEdge.end ); + } else { + morphEdge.end = morphEdge.start; + } + + // one edge may be a straight edge, and the other may be a curve + // in this case, we turn the straight edge into a curve by adding + // a control point in the middle of the line + if ( morphEdge.start.cp != 0xFFFF ) { + if ( morphEdge.end.cp == 0xFFFF ) { + const idVec2 & v0 = verts[ morphEdge.end.v0 ]; + const idVec2 & v1 = verts[ morphEdge.end.v1 ]; + morphEdge.end.cp = verts.AddUnique( ( v0 + v1 ) * 0.5f ); + } + } else { + if ( morphEdge.end.cp != 0xFFFF ) { + const idVec2 & v0 = verts[ morphEdge.start.v0 ]; + const idVec2 & v1 = verts[ morphEdge.start.v1 ]; + morphEdge.start.cp = verts.AddUnique( ( v0 + v1 ) * 0.5f ); + } + } + + if ( lineStyle != 0 ) { + lineDraws[ baseLineStyle + lineStyle - 1 ].edges.Append( morphEdge ); + } + if ( swap ) { + SwapValues( morphEdge.start.v0, morphEdge.start.v1 ); + SwapValues( morphEdge.end.v0, morphEdge.end.v1 ); + } + if ( fillStyle1 != 0 ) { + fillDraws[ baseFillStyle + fillStyle1 - 1 ].edges.Append( morphEdge ); + } + if ( fillStyle0 != 0 ) { + // for fill style 0, we need to reverse the winding + swfSPMorphEdge_t swapped = morphEdge; + SwapValues( swapped.start.v0, swapped.start.v1 ); + SwapValues( swapped.end.v0, swapped.end.v1 ); + fillDraws[ baseFillStyle + fillStyle0 - 1 ].edges.Append( swapped ); + } + } + } +} + +/* +======================== +idSWFShapeParser::ParseEdge +======================== +*/ +void idSWFShapeParser::ParseEdge( idSWFBitStream & bitstream, int32 & penX, int32 & penY, swfSPEdge_t & edge ) { + bool straight = bitstream.ReadBool(); + uint8 numBits = bitstream.ReadU( 4 ) + 2; + + edge.v0 = verts.AddUnique( idVec2( SWFTWIP( penX ), SWFTWIP( penY ) ) ); + + if ( straight ) { + edge.cp = 0xFFFF; + if ( bitstream.ReadBool() ) { + penX += bitstream.ReadS( numBits ); + penY += bitstream.ReadS( numBits ); + } else { + if ( bitstream.ReadBool() ) { + penY += bitstream.ReadS( numBits ); + } else { + penX += bitstream.ReadS( numBits ); + } + } + } else { + penX += bitstream.ReadS( numBits ); + penY += bitstream.ReadS( numBits ); + edge.cp = verts.AddUnique( idVec2( SWFTWIP( penX ), SWFTWIP( penY ) ) ); + penX += bitstream.ReadS( numBits ); + penY += bitstream.ReadS( numBits ); + } + + edge.v1 = verts.AddUnique( idVec2( SWFTWIP( penX ), SWFTWIP( penY ) ) ); +} + +/* +======================== +idSWFShapeParser::MakeLoops +======================== +*/ +void idSWFShapeParser::MakeLoops() { + + // At this point, each fill style has an edge soup associated with it + // We want to turn this soup into loops of connected verts + + for ( int i = 0; i < fillDraws.Num(); i++ ) { + swfSPDrawFill_t & fill = fillDraws[i]; + + // first remove degenerate edges + for ( int e = 0; e < fill.edges.Num(); e++ ) { + if ( ( fill.edges[e].start.v0 == fill.edges[e].start.v1 ) || ( fill.edges[e].end.v0 == fill.edges[e].end.v1 ) ) { + fill.edges.RemoveIndexFast( e ); + e--; + } + } + + idList< int > unusedEdges; + unusedEdges.SetNum( fill.edges.Num() ); + for ( int e = 0; e < fill.edges.Num(); e++ ) { + unusedEdges[e] = e; + } + while ( unusedEdges.Num() > 0 ) { + swfSPLineLoop_t & loop = fill.loops.Alloc(); + loop.hole = false; + + int e1 = unusedEdges[ unusedEdges.Num() - 1 ]; + unusedEdges.SetNum( unusedEdges.Num() - 1 ); + + while ( true ) { + loop.vindex1.Append( fill.edges[e1].start.v0 ); + loop.vindex2.Append( fill.edges[e1].end.v0 ); + + // Rather then tesselating curves at run time, we do them once here by inserting a vert every 10 units + // It may not wind up being 10 actual pixels when rendered because the shape may have scaling applied to it + if ( fill.edges[e1].start.cp != 0xFFFF ) { + assert( fill.edges[e1].end.cp != 0xFFFF ); + float length1 = ( verts[ fill.edges[e1].start.v0 ] - verts[ fill.edges[e1].start.v1 ] ).Length(); + float length2 = ( verts[ fill.edges[e1].end.v0 ] - verts[ fill.edges[e1].end.v1 ] ).Length(); + int numPoints = 1 + idMath::Ftoi( Max( length1, length2 ) / 10.0f ); + for ( int ti = 0; ti < numPoints; ti++ ) { + float t0 = ( ti + 1 ) / ( (float) numPoints + 1.0f ); + float t1 = ( 1.0f - t0 ); + float c1 = t1 * t1; + float c2 = t0 * t1 * 2.0f; + float c3 = t0 * t0; + + idVec2 p1 = c1 * verts[ fill.edges[e1].start.v0 ]; + p1 += c2 * verts[ fill.edges[e1].start.cp ]; + p1 += c3 * verts[ fill.edges[e1].start.v1 ]; + + idVec2 p2 = c1 * verts[ fill.edges[e1].end.v0 ]; + p2 += c2 * verts[ fill.edges[e1].end.cp ]; + p2 += c3 * verts[ fill.edges[e1].end.v1 ]; + + loop.vindex1.Append( verts.AddUnique( p1 ) ); + loop.vindex2.Append( verts.AddUnique( p2 ) ); + } + } + + const swfSPEdge_t & edge1 = fill.edges[e1].start; + + float bestNormal = FLT_MAX; + int beste = -1; + for ( int e = 0; e < unusedEdges.Num(); e++ ) { + int e2 = unusedEdges[e]; + const swfSPEdge_t & edge2 = fill.edges[e2].start; + if ( edge1.v1 != edge2.v0 ) { + continue; + } + + assert( edge1.v0 != edge2.v0 ); + assert( edge1.v1 != edge2.v1 ); + + const idVec2 & v1 = verts[ edge1.v0 ]; + const idVec2 & v2 = verts[ edge1.v1 ]; // == edge2.v0 + const idVec2 & v3 = verts[ edge2.v1 ]; + idVec2 a = v1 - v2; + idVec2 b = v3 - v2; + + float normal = ( a.x * b.y - a.y * b.x ); + if ( normal < bestNormal ) { + bestNormal = normal; + beste = e; + } else { + assert( beste != -1 ); + } + } + if ( beste < 0 ) { + // no more edges connect to this one + break; + } + e1 = unusedEdges[beste]; + unusedEdges.RemoveIndexFast( beste ); + } + if ( loop.vindex1.Num() < 3 ) { + idLib::Warning( "idSWFShapeParser: loop with < 3 verts" ); + fill.loops.SetNum( fill.loops.Num() - 1 ); + continue; + } + // Use the left most vert to determine if it's a hole or not + float leftMostX = FLT_MAX; + int leftMostIndex = 0; + for ( int j = 0; j < loop.vindex1.Num(); j++ ) { + idVec2 & v = verts[ loop.vindex1[j] ]; + if ( v.x < leftMostX ) { + leftMostIndex = j; + leftMostX = v.x; + } + } + const idVec2 & v1 = verts[ loop.vindex1[(loop.vindex1.Num() + leftMostIndex - 1) % loop.vindex1.Num()] ]; + const idVec2 & v2 = verts[ loop.vindex1[leftMostIndex] ]; + const idVec2 & v3 = verts[ loop.vindex1[(leftMostIndex+1) % loop.vindex1.Num()] ]; + idVec2 a = v1 - v2; + idVec2 b = v3 - v2; + float normal = ( a.x * b.y - a.y * b.x ); + loop.hole = ( normal > 0.0f ); + } + + // now we have a series of loops, which define either shapes or holes + // we want to merge the holes into the shapes by inserting edges + // this assumes shapes are either completely contained or not + // we start merging holes starting on the right so nested holes work + while ( true ) { + int hole = -1; + int holeVert = -1; + float rightMostX = -1e10f; + for ( int j = 0; j < fill.loops.Num(); j++ ) { + swfSPLineLoop_t & loop = fill.loops[j]; + if ( !loop.hole ) { + continue; + } + for ( int v = 0; v < loop.vindex1.Num(); v++ ) { + if ( verts[ loop.vindex1[v] ].x > rightMostX ) { + hole = j; + holeVert = v; + rightMostX = verts[ loop.vindex1[v] ].x; + } + } + } + if ( hole == -1 ) { + break; + } + swfSPLineLoop_t & loopHole = fill.loops[ hole ]; + const idVec2 & holePoint = verts[ loopHole.vindex1[ holeVert ] ]; + + int shape = -1; + for ( int j = 0; j < fill.loops.Num(); j++ ) { + swfSPLineLoop_t & loop = fill.loops[j]; + if ( loop.hole ) { + continue; + } + bool inside = false; + for ( int k = 0; k < loop.vindex1.Num(); k++ ) { + const idVec2 & v1 = verts[ loop.vindex1[k] ]; + const idVec2 & v2 = verts[ loop.vindex1[(k + 1) % loop.vindex1.Num()] ]; + if ( v1.x < holePoint.x && v2.x < holePoint.x ) { + continue; // both on the left of the holePoint + } + if ( ( v1.y < holePoint.y ) == ( v2.y < holePoint.y ) ) { + continue; // both on the same side of the horizon + } + assert( v1 != holePoint ); + assert( v2 != holePoint ); + inside = !inside; + } + if ( inside ) { + shape = j; + break; + } + } + if ( shape == -1 ) { + idLib::Warning( "idSWFShapeParser: Hole not in a shape" ); + fill.loops.RemoveIndexFast( hole ); + continue; + } + swfSPLineLoop_t & loopShape = fill.loops[ shape ]; + + // now that we have a hole and the shape it's inside, merge the two together + + // find the nearest vert that's on the right side of holePoint + float bestDist = 1e10f; + int shapeVert = -1; + for ( int j = 0; j < loopShape.vindex1.Num(); j++ ) { + const idVec2 & v1 = verts[ loopShape.vindex1[j] ]; + if ( v1.x < holePoint.x ) { + continue; // on the left of the holePoint + } + float dist = ( v1 - holePoint ).Length(); + if ( dist < bestDist ) { + shapeVert = j; + bestDist = dist; + } + } + + // connect holeVert to shapeVert + idList< uint16 > vindex; + vindex.SetNum( loopShape.vindex1.Num() + loopHole.vindex1.Num() + 1 ); + vindex.SetNum( 0 ); + for ( int j = 0; j <= shapeVert; j++ ) { + vindex.Append( loopShape.vindex1[j] ); + } + for ( int j = holeVert; j < loopHole.vindex1.Num(); j++ ) { + vindex.Append( loopHole.vindex1[j] ); + } + for ( int j = 0; j <= holeVert; j++ ) { + vindex.Append( loopHole.vindex1[j] ); + } + for ( int j = shapeVert; j < loopShape.vindex1.Num(); j++ ) { + vindex.Append( loopShape.vindex1[j] ); + } + loopShape.vindex1 = vindex; + + vindex.Clear(); + for ( int j = 0; j <= shapeVert; j++ ) { + vindex.Append( loopShape.vindex2[j] ); + } + for ( int j = holeVert; j < loopHole.vindex2.Num(); j++ ) { + vindex.Append( loopHole.vindex2[j] ); + } + for ( int j = 0; j <= holeVert; j++ ) { + vindex.Append( loopHole.vindex2[j] ); + } + for ( int j = shapeVert; j < loopShape.vindex2.Num(); j++ ) { + vindex.Append( loopShape.vindex2[j] ); + } + loopShape.vindex2 = vindex; + + fill.loops.RemoveIndexFast( hole ); + } + } +} + +/* +======================== +idSWFShapeParser::TriangulateSoup +======================== +*/ +void idSWFShapeParser::TriangulateSoup( idSWFShape & shape ) { + + MakeLoops(); + + // Now turn the (potentially) concave line loops into triangles by using ear clipping + + shape.fillDraws.SetNum( fillDraws.Num() ); + for ( int i = 0; i < fillDraws.Num(); i++ ) { + swfSPDrawFill_t & spDrawFill = fillDraws[i]; + idSWFShapeDrawFill & drawFill = shape.fillDraws[i]; + + swfFillStyle_t & style = spDrawFill.style; + drawFill.style = spDrawFill.style; + + for ( int j = 0; j < spDrawFill.loops.Num(); j++ ) { + swfSPLineLoop_t & loop = spDrawFill.loops[j]; + int numVerts = loop.vindex1.Num(); + for ( int k = 0; k < numVerts - 2; k++ ) { + int v1 = FindEarVert( loop ); + if ( v1 == -1 ) { + idLib::Warning( "idSWFShapeParser: could not find an ear vert" ); + break; + } + int num = loop.vindex1.Num(); + int v2 = ( v1 + 1 ) % num; + int v3 = ( v1 + 2 ) % num; + + AddUniqueVert( drawFill, verts[ loop.vindex1[ v1 ] ], verts[ loop.vindex2[ v1 ] ] ); + AddUniqueVert( drawFill, verts[ loop.vindex1[ v2 ] ], verts[ loop.vindex2[ v2 ] ] ); + AddUniqueVert( drawFill, verts[ loop.vindex1[ v3 ] ], verts[ loop.vindex2[ v3 ] ] ); + + loop.vindex1.RemoveIndex( v2 ); + loop.vindex2.RemoveIndex( v2 ); + } + } + } +} + +/* +======================== +idSWFShapeParser::TriangulateSoup +======================== +*/ +void idSWFShapeParser::TriangulateSoup( idSWFFontGlyph & shape ) { + + MakeLoops(); + + // Now turn the (potentially) concave line loops into triangles by using ear clipping + + assert( fillDraws.Num() == 1 ); + swfSPDrawFill_t & spDrawFill = fillDraws[0]; + for ( int j = 0; j < spDrawFill.loops.Num(); j++ ) { + swfSPLineLoop_t & loop = spDrawFill.loops[j]; + int numVerts = loop.vindex1.Num(); + for ( int k = 0; k < numVerts - 2; k++ ) { + int v1 = FindEarVert( loop ); + if ( v1 == -1 ) { + idLib::Warning( "idSWFShapeParser: could not find an ear vert" ); + break; + } + int num = loop.vindex1.Num(); + int v2 = ( v1 + 1 ) % num; + int v3 = ( v1 + 2 ) % num; + + shape.indices.Append( shape.verts.AddUnique( verts[ loop.vindex1[ v1 ] ] ) ); + shape.indices.Append( shape.verts.AddUnique( verts[ loop.vindex1[ v2 ] ] ) ); + shape.indices.Append( shape.verts.AddUnique( verts[ loop.vindex1[ v3 ] ] ) ); + + loop.vindex1.RemoveIndex( v2 ); + loop.vindex2.RemoveIndex( v2 ); + } + } +} + +struct earVert_t { + int i1; + int i2; + int i3; + float cross; +}; +class idSort_Ears : public idSort_Quick< earVert_t, idSort_Ears > { +public: + int Compare( const earVert_t & a, const earVert_t & b ) const { + if ( a.cross < b.cross ) { + return -1; + } else if ( a.cross > b.cross ) { + return 1; + } + return 0; + } +}; + +/* +======================== +idSWFShapeParser::FindEarVert +======================== +*/ +int idSWFShapeParser::FindEarVert( const swfSPLineLoop_t & loop ) { + assert( loop.vindex1.Num() == loop.vindex2.Num() ); + int num = loop.vindex1.Num(); + + idList ears; + ears.SetNum( num ); + + for ( int i1 = 0; i1 < num; i1++ ) { + int i2 = ( i1 + 1 ) % num; + int i3 = ( i1 + 2 ) % num; + const idVec2 & v1s = verts[ loop.vindex1[ i1 ] ]; + const idVec2 & v2s = verts[ loop.vindex1[ i2 ] ]; + const idVec2 & v3s = verts[ loop.vindex1[ i3 ] ]; + + idVec2 a = v1s - v2s; + idVec2 b = v2s - v3s; + + ears[i1].cross = a.x * b.y - a.y * b.x; + ears[i1].i1 = i1; + ears[i1].i2 = i2; + ears[i1].i3 = i3; + } + ears.SortWithTemplate( idSort_Ears() ); + + for ( int i = 0; i < ears.Num(); i++ ) { + if ( ears[i].cross < 0.0f ) { + continue; + } + int i1 = ears[i].i1; + int i2 = ears[i].i2; + int i3 = ears[i].i3; + + const idVec2 & v1s = verts[ loop.vindex1[ i1 ] ]; + const idVec2 & v2s = verts[ loop.vindex1[ i2 ] ]; + const idVec2 & v3s = verts[ loop.vindex1[ i3 ] ]; + + const idVec2 & v1e = verts[ loop.vindex2[ i1 ] ]; + const idVec2 & v2e = verts[ loop.vindex2[ i2 ] ]; + const idVec2 & v3e = verts[ loop.vindex2[ i3 ] ]; + + idMat3 edgeEquations1; + edgeEquations1[0].Set( v1s.x, v1s.y, 1.0f ); + edgeEquations1[1].Set( v2s.x, v2s.y, 1.0f ); + edgeEquations1[2].Set( v3s.x, v3s.y, 1.0f ); + + idMat3 edgeEquations2; + edgeEquations2[0].Set( v1e.x, v1e.y, 1.0f ); + edgeEquations2[1].Set( v2e.x, v2e.y, 1.0f ); + edgeEquations2[2].Set( v3e.x, v3e.y, 1.0f ); + + edgeEquations1.InverseSelf(); + edgeEquations2.InverseSelf(); + + bool isEar = true; + for ( int j = 0; j < num; j++ ) { + if ( j == i1 || j == i2 || j == i3 ) { + continue; + } + + idVec3 p1; + p1.ToVec2() = verts[ loop.vindex1[j] ]; + p1.z = 1.0f; + + idVec3 signs1 = p1 * edgeEquations1; + + bool b1x = signs1.x > 0; + bool b1y = signs1.y > 0; + bool b1z = signs1.z > 0; + if ( b1x == b1y && b1x == b1z ) { + // point inside + isEar = false; + break; + } + + idVec3 p2; + p2.ToVec2() = verts[ loop.vindex2[j] ]; + p2.z = 1.0f; + + idVec3 signs2 = p2 * edgeEquations2; + + bool b2x = signs2.x > 0; + bool b2y = signs2.y > 0; + bool b2z = signs2.z > 0; + if ( b2x == b2y && b2x == b2z ) { + // point inside + isEar = false; + break; + } + } + if ( isEar ) { + return i1; + } + } + return -1; +} + +/* +======================== +idSWFShapeParser::AddUniqueVert +======================== +*/ +void idSWFShapeParser::AddUniqueVert( idSWFShapeDrawFill & drawFill, const idVec2 & start, const idVec2 & end ) { + if ( morph ) { + for ( int i = 0; i < drawFill.startVerts.Num(); i++ ) { + if ( drawFill.startVerts[i] == start && drawFill.endVerts[i] == end ) { + drawFill.indices.Append( i ); + return; + } + } + int index1 = drawFill.startVerts.Append( start ); + int index2 = drawFill.endVerts.Append( end ); + assert( index1 == index2 ); + + drawFill.indices.Append( index1 ); + } else { + drawFill.indices.Append( drawFill.startVerts.AddUnique( start ) ); + } +} + +/* +======================== +idSWFShapeParser::ReadFillStyle +======================== +*/ +void idSWFShapeParser::ReadFillStyle( idSWFBitStream & bitstream ) { + uint16 fillStyleCount = bitstream.ReadU8(); + if ( extendedCount && fillStyleCount == 0xFF ) { + fillStyleCount = bitstream.ReadU16(); + } + + for ( int i = 0; i < fillStyleCount; i++ ) { + uint8 fillStyleType = bitstream.ReadU8(); + + swfFillStyle_t & fillStyle = fillDraws.Alloc().style; + fillStyle.type = fillStyleType >> 4; + fillStyle.subType = fillStyleType & 0xF; + + if ( fillStyle.type == 0 ) { + if ( morph ) { + bitstream.ReadColorRGBA( fillStyle.startColor ); + bitstream.ReadColorRGBA( fillStyle.endColor ); + } else { + if ( rgba ) { + bitstream.ReadColorRGBA( fillStyle.startColor ); + } else { + bitstream.ReadColorRGB( fillStyle.startColor ); + } + fillStyle.endColor = fillStyle.startColor; + } + } else if ( fillStyle.type == 1 ) { + bitstream.ReadMatrix( fillStyle.startMatrix ); + if ( morph ) { + bitstream.ReadMatrix( fillStyle.endMatrix ); + bitstream.ReadMorphGradient( fillStyle.gradient ); + } else { + fillStyle.endMatrix = fillStyle.startMatrix; + bitstream.ReadGradient( fillStyle.gradient, rgba ); + } + if ( fillStyle.subType == 3 ) { + assert( morph == false ); // focal gradients aren't allowed in morph shapes + fillStyle.focalPoint = bitstream.ReadFixed8(); + } + } else if ( fillStyle.type == 4 ) { + fillStyle.bitmapID = bitstream.ReadU16(); + bitstream.ReadMatrix( fillStyle.startMatrix ); + if ( morph ) { + bitstream.ReadMatrix( fillStyle.endMatrix ); + } else { + fillStyle.endMatrix = fillStyle.startMatrix; + } + } + } + + uint16 lineStyleCount = bitstream.ReadU8(); + if ( extendedCount && lineStyleCount == 0xFF ) { + lineStyleCount = bitstream.ReadU16(); + } + + lineDraws.SetNum( lineDraws.Num() + lineStyleCount ); + lineDraws.SetNum( 0 ); + for ( int i = 0; i < lineStyleCount; i++ ) { + swfLineStyle_t & lineStyle = lineDraws.Alloc().style; + lineStyle.startWidth = bitstream.ReadU16(); + if ( lineStyle2 ) { + lineStyle.endWidth = lineStyle.startWidth; + + uint8 startCapStyle = bitstream.ReadU( 2 ); + uint8 joinStyle = bitstream.ReadU( 2 ); + bool hasFillFlag = bitstream.ReadBool(); + bool noHScaleFlag = bitstream.ReadBool(); + bool noVScaleFlag = bitstream.ReadBool(); + bool pixelHintingFlag = bitstream.ReadBool(); + uint8 reserved = bitstream.ReadU( 5 ); + bool noClose = bitstream.ReadBool(); + uint8 endCapStyle = bitstream.ReadU( 2 ); + if ( joinStyle == 2 ) { + uint16 miterLimitFactor = bitstream.ReadU16(); + } + if ( hasFillFlag ) { + // FIXME: read fill style + idLib::Warning( "idSWFShapeParser: Ignoring hasFillFlag" ); + } else { + bitstream.ReadColorRGBA( lineStyle.startColor ); + lineStyle.endColor = lineStyle.startColor; + } + } else { + if ( morph ) { + lineStyle.endWidth = bitstream.ReadU16(); + } else { + lineStyle.endWidth = lineStyle.startWidth; + } + if ( rgba ) { + bitstream.ReadColorRGBA( lineStyle.startColor ); + } else { + bitstream.ReadColorRGB( lineStyle.startColor ); + } + if ( morph ) { + bitstream.ReadColorRGBA( lineStyle.endColor ); + } else { + lineStyle.endColor = lineStyle.startColor; + } + } + } +} diff --git a/neo/swf/SWF_ShapeParser.h b/neo/swf/SWF_ShapeParser.h new file mode 100644 index 00000000..a4173420 --- /dev/null +++ b/neo/swf/SWF_ShapeParser.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_SHAPEPARSER_H__ +#define __SWF_SHAPEPARSER_H__ + +/* +================================================ +This class handles parsing and triangulating a shape +================================================ +*/ +class idSWFShapeParser { +public: + idSWFShapeParser() { } + void Parse( idSWFBitStream & bitstream, idSWFShape & shape, int recordType ); + void ParseMorph( idSWFBitStream & bitstream, idSWFShape & shape ); + void ParseFont( idSWFBitStream & bitstream, idSWFFontGlyph & shape ); + +private: + bool extendedCount; + bool rgba; + bool morph; + bool lineStyle2; + + struct swfSPEdge_t { + uint16 v0; + uint16 v1; + uint16 cp; // control point if this is a curve, 0xFFFF otherwise + }; + struct swfSPMorphEdge_t { + swfSPEdge_t start; + swfSPEdge_t end; + }; + struct swfSPLineLoop_t { + bool hole; + idList< uint16, TAG_SWF > vindex1; + idList< uint16, TAG_SWF > vindex2; + }; + struct swfSPDrawFill_t { + swfFillStyle_t style; + idList< swfSPMorphEdge_t, TAG_SWF > edges; + idList< swfSPLineLoop_t, TAG_SWF > loops; + }; + struct swfSPDrawLine_t { + swfLineStyle_t style; + idList< swfSPMorphEdge_t, TAG_SWF > edges; + }; + idList< idVec2, TAG_SWF > verts; + idList< swfSPDrawFill_t, TAG_SWF > fillDraws; + idList< swfSPDrawLine_t, TAG_SWF > lineDraws; + + +private: + void ParseShapes( idSWFBitStream & bitstream1, idSWFBitStream * bitstream2, bool swap ); + void ReadFillStyle( idSWFBitStream & bitstream ); + void ParseEdge( idSWFBitStream & bitstream, int32 & penX, int32 & penY, swfSPEdge_t & edge ); + void MakeLoops(); + void TriangulateSoup( idSWFShape & shape ); + void TriangulateSoup( idSWFFontGlyph & shape ); + int FindEarVert( const swfSPLineLoop_t & loop ); + void AddUniqueVert( idSWFShapeDrawFill & drawFill, const idVec2 & start, const idVec2 & end ); + +}; + +#endif // !__SWF_SHAPEPARSER_H__ diff --git a/neo/swf/SWF_Shapes.cpp b/neo/swf/SWF_Shapes.cpp new file mode 100644 index 00000000..af53ecb4 --- /dev/null +++ b/neo/swf/SWF_Shapes.cpp @@ -0,0 +1,110 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +======================== +idSWF::DefineShape +======================== +*/ +void idSWF::DefineShape( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_SHAPE ); + if ( entry == NULL ) { + return; + } + + idSWFShapeParser swfShapeParser; + swfShapeParser.Parse( bitstream, *entry->shape, 1 ); +} + +/* +======================== +idSWF::DefineShape2 +======================== +*/ +void idSWF::DefineShape2( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_SHAPE ); + if ( entry == NULL ) { + return; + } + + idSWFShapeParser swfShapeParser; + swfShapeParser.Parse( bitstream, *entry->shape, 2 ); +} + +/* +======================== +idSWF::DefineShape3 +======================== +*/ +void idSWF::DefineShape3( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_SHAPE ); + if ( entry == NULL ) { + return; + } + + idSWFShapeParser swfShapeParser; + swfShapeParser.Parse( bitstream, *entry->shape, 3 ); +} + +/* +======================== +idSWF::DefineShape4 +======================== +*/ +void idSWF::DefineShape4( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_SHAPE ); + if ( entry == NULL ) { + return; + } + + idSWFShapeParser swfShapeParser; + swfShapeParser.Parse( bitstream, *entry->shape, 4 ); +} + + +/* +======================== +idSWF::DefineMorphShape +======================== +*/ +void idSWF::DefineMorphShape( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_MORPH ); + if ( entry == NULL ) { + return; + } + + idSWFShapeParser swfShapeParser; + swfShapeParser.ParseMorph( bitstream, *entry->shape ); +} diff --git a/neo/swf/SWF_Sounds.cpp b/neo/swf/SWF_Sounds.cpp new file mode 100644 index 00000000..eac4e093 --- /dev/null +++ b/neo/swf/SWF_Sounds.cpp @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +======================== +idSWF::DefineSound +======================== +*/ +void idSWF::DefineSound( idSWFBitStream & bitstream ) { +} + +/* +======================== +idSWFSpriteInstance::StartSound +======================== +*/ +void idSWFSpriteInstance::StartSound( idSWFBitStream & bitstream ) { +} diff --git a/neo/swf/SWF_SpriteInstance.cpp b/neo/swf/SWF_SpriteInstance.cpp new file mode 100644 index 00000000..263ef484 --- /dev/null +++ b/neo/swf/SWF_SpriteInstance.cpp @@ -0,0 +1,1363 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +idSWFScriptObject_SpriteInstancePrototype spriteInstanceScriptObjectPrototype; + +/* +======================== +idSWFSpriteInstance::idSWFSpriteInstance +======================== +*/ +idSWFSpriteInstance::idSWFSpriteInstance() : +sprite( NULL ), +parent( NULL ), +depth( 0 ), +isPlaying( true ), +isVisible( true ), +childrenRunning( true ), +currentFrame( 0 ), +itemIndex( 0 ), +materialOverride( NULL ), +materialWidth( 0 ), +materialHeight( 0 ), +xOffset( 0.0f ), +yOffset( 0.0f ), +moveToXScale( 1.0f ), +moveToYScale( 1.0f ), +moveToSpeed( 1.0f ), +firstRun( false ), +stereoDepth( 0 ) +{ +} + +/* +======================== +idSWFSpriteInstance::Init +======================== +*/ +void idSWFSpriteInstance::Init( idSWFSprite * _sprite, idSWFSpriteInstance * _parent, int _depth ) { + sprite = _sprite; + parent = _parent; + depth = _depth; + + frameCount = sprite->frameCount; + + scriptObject = idSWFScriptObject::Alloc(); + scriptObject->SetPrototype( &spriteInstanceScriptObjectPrototype ); + scriptObject->SetSprite( this ); + + firstRun = true; + + actionScript = idSWFScriptFunction_Script::Alloc(); + + idList scope; + scope.Append( sprite->swf->globals ); + scope.Append( scriptObject ); + actionScript->SetScope( scope ); + actionScript->SetDefaultSprite( this ); + + for (int i = 0; i < sprite->doInitActions.Num(); i++) { + actionScript->SetData( sprite->doInitActions[i].Ptr(), sprite->doInitActions[i].Length() ); + actionScript->Call( scriptObject, idSWFParmList() ); + } + + Play(); +} + +/* +======================== +idSWFSpriteInstance::~idSWFSpriteInstance +======================== +*/ +idSWFSpriteInstance::~idSWFSpriteInstance() { + if ( parent != NULL ) { + parent->scriptObject->Set( name, idSWFScriptVar() ); + } + FreeDisplayList(); + displayList.Clear(); + scriptObject->SetSprite( NULL ); + scriptObject->Clear(); + scriptObject->Release(); + actionScript->Release(); +} + +/* +======================== +idSWFSpriteInstance::FreeDisplayList +======================== +*/ +void idSWFSpriteInstance::FreeDisplayList() { + for ( int i = 0; i < displayList.Num(); i++ ) { + sprite->swf->spriteInstanceAllocator.Free( displayList[i].spriteInstance ); + sprite->swf->textInstanceAllocator.Free( displayList[i].textInstance ); + } + displayList.SetNum( 0 ); // not calling Clear() so we don't continuously re-allocate memory + currentFrame = 0; +} + +/* +======================== +idSWFSpriteInstance::FindDisplayEntry +======================== +*/ +swfDisplayEntry_t * idSWFSpriteInstance::FindDisplayEntry( int depth ) { + int len = displayList.Num(); + int mid = len; + int offset = 0; + while ( mid > 0 ) { + mid = len >> 1; + if ( displayList[offset+mid].depth <= depth ) { + offset += mid; + } + len -= mid; + } + if ( displayList[offset].depth == depth ) { + return &displayList[offset]; + } + return NULL; +} + +/* +======================== +idSWFSpriteInstance::AddDisplayEntry +======================== +*/ +swfDisplayEntry_t * idSWFSpriteInstance::AddDisplayEntry( int depth, int characterID ) { + int i = 0; + for ( ; i < displayList.Num(); i++ ) { + if ( displayList[i].depth == depth ) { + return NULL; + } + if ( displayList[i].depth > depth ) { + break; + } + } + + swfDisplayEntry_t & display = displayList[ displayList.Insert( swfDisplayEntry_t(), i ) ]; + display.depth = depth; + display.characterID = characterID; + + idSWFDictionaryEntry * dictEntry = sprite->swf->FindDictionaryEntry( characterID ); + if ( dictEntry != NULL ) { + if ( dictEntry->type == SWF_DICT_SPRITE ) { + display.spriteInstance = sprite->swf->spriteInstanceAllocator.Alloc(); + display.spriteInstance->Init( dictEntry->sprite, this, depth ); + display.spriteInstance->RunTo( 1 ); + } else if ( dictEntry->type == SWF_DICT_EDITTEXT ) { + display.textInstance = sprite->swf->textInstanceAllocator.Alloc(); + display.textInstance->Init( dictEntry->edittext, sprite->GetSWF() ); + } + } + return &display; +} + +/* +======================== +idSWFSpriteInstance::RemoveDisplayEntry +======================== +*/ +void idSWFSpriteInstance::RemoveDisplayEntry( int depth ) { + swfDisplayEntry_t * entry = FindDisplayEntry( depth ); + if ( entry != NULL ) { + sprite->swf->spriteInstanceAllocator.Free( entry->spriteInstance ); + sprite->swf->textInstanceAllocator.Free( entry->textInstance ); + displayList.RemoveIndex( displayList.IndexOf( entry ) ); + } +} + +/* +================================================ +idSort_SpriteDepth +================================================ +*/ +class idSort_SpriteDepth : public idSort_Quick< swfDisplayEntry_t, idSort_SpriteDepth > { +public: + int Compare( const swfDisplayEntry_t & a, const swfDisplayEntry_t & b ) const { return a.depth - b.depth; } +}; + +/* +======================== +idSWFSpriteInstance::SwapDepths +======================== +*/ +void idSWFSpriteInstance::SwapDepths( int depth1, int depth2 ) { + for ( int i = 0; i < displayList.Num(); i++ ) { + if ( displayList[i].depth == depth1 ) { + displayList[i].depth = depth2; + + } else if ( displayList[i].depth == depth2 ) { + displayList[i].depth = depth1; + } + if ( displayList[i].spriteInstance != NULL ) { + displayList[i].spriteInstance->depth = displayList[i].depth; + } + } + + displayList.SortWithTemplate( idSort_SpriteDepth() ); +} + +/* +=================== +idSWFSpriteInstance::Run +=================== +*/ +bool idSWFSpriteInstance::Run() { + if ( !isVisible ) { + return false; + } + + if ( childrenRunning ) { + childrenRunning = false; + for ( int i = 0; i < displayList.Num(); i++ ) { + if ( displayList[i].spriteInstance != NULL ) { + Prefetch( displayList[i].spriteInstance, 0 ); + } + } + for ( int i = 0; i < displayList.Num(); i++ ) { + if ( displayList[i].spriteInstance != NULL ) { + childrenRunning |= displayList[i].spriteInstance->Run(); + } + } + } + if ( isPlaying ) { + if ( currentFrame == frameCount ) { + if ( frameCount > 1 ) { + FreeDisplayList(); + RunTo( 1 ); + } + } else { + RunTo( currentFrame + 1 ); + } + } + return childrenRunning || isPlaying; +} + +/* +=================== +idSWFSpriteInstance::RunActions +=================== +*/ +bool idSWFSpriteInstance::RunActions() { + if ( !isVisible ) { + actions.SetNum( 0 ); + return false; + } + + if ( firstRun && scriptObject->HasProperty( "onLoad" ) ) { + firstRun = false; + idSWFScriptVar onLoad = scriptObject->Get( "onLoad" ); + onLoad.GetFunction()->Call( scriptObject, idSWFParmList() ); + } + + if ( onEnterFrame.IsFunction() ) { + onEnterFrame.GetFunction()->Call( scriptObject, idSWFParmList() ); + } + + for ( int i = 0; i < actions.Num(); i++ ) { + actionScript->SetData( actions[i].data, actions[i].dataLength ); + actionScript->Call( scriptObject, idSWFParmList() ); + } + actions.SetNum( 0 ); + + for ( int i = 0; i < displayList.Num(); i++ ) { + if ( displayList[i].spriteInstance != NULL ) { + Prefetch( displayList[i].spriteInstance, 0 ); + } + } + for ( int i = 0; i < displayList.Num(); i++ ) { + if ( displayList[i].spriteInstance != NULL ) { + displayList[i].spriteInstance->RunActions(); + } + } + + return true; +} + +/* +=================== +idSWFSpriteInstance::NextFrame +=================== +*/ +void idSWFSpriteInstance::NextFrame() { + if ( currentFrame < frameCount ) { + RunTo( currentFrame + 1 ); + } +} + +/* +=================== +idSWFSpriteInstance::PrevFrame +=================== +*/ +void idSWFSpriteInstance::PrevFrame() { + if ( currentFrame > 1 ) { + RunTo( currentFrame - 1 ); + } +} + +/* +=================== +idSWFSpriteInstance::Play +=================== +*/ +void idSWFSpriteInstance::Play() { + for ( idSWFSpriteInstance * p = parent; p != NULL; p = p->parent ) { + p->childrenRunning = true; + } + isPlaying = true; +} + +/* +=================== +idSWFSpriteInstance::Stop +=================== +*/ +void idSWFSpriteInstance::Stop() { + isPlaying = false; +} + +/* +=================== +idSWFSpriteInstance::RunTo +=================== +*/ +void idSWFSpriteInstance::RunTo( int targetFrame ) { + if ( targetFrame == currentFrame ) { + return; // otherwise we'll re-run the current frame + } + if ( targetFrame < currentFrame ) { + FreeDisplayList(); + } + if ( targetFrame < 1 ) { + return; + } + + if ( targetFrame > sprite->frameOffsets.Num() - 1 ) { + targetFrame = sprite->frameOffsets.Num() - 1; + } + + //actions.Clear(); + + uint32 firstActionCommand = sprite->frameOffsets[ targetFrame - 1 ]; + + for ( uint32 c = sprite->frameOffsets[ currentFrame ]; c < sprite->frameOffsets[ targetFrame ]; c++ ) { + idSWFSprite::swfSpriteCommand_t & command = sprite->commands[ c ]; + if ( command.tag == Tag_DoAction && c < firstActionCommand ) { + // Skip DoAction up to the firstActionCommand + // This is to properly support skipping to a specific frame + // for example if we're on frame 3 and skipping to frame 10, we want + // to run all the commands PlaceObject commands for frames 4-10 but + // only the DoAction commands for frame 10 + continue; + } + command.stream.Rewind(); + switch ( command.tag ) { +#define HANDLE_SWF_TAG( x ) case Tag_##x: x( command.stream ); break; + HANDLE_SWF_TAG( PlaceObject2 ); + HANDLE_SWF_TAG( PlaceObject3 ); + HANDLE_SWF_TAG( RemoveObject2 ); + HANDLE_SWF_TAG( StartSound ); + HANDLE_SWF_TAG( DoAction ); +#undef HANDLE_SWF_TAG + default: + idLib::Printf( "Run Sprite: Unhandled tag %s\n", idSWF::GetTagName( command.tag ) ); + } + } + + currentFrame = targetFrame; +} + +/* +======================== +idSWFSpriteInstance::DoAction +======================== +*/ +void idSWFSpriteInstance::DoAction( idSWFBitStream & bitstream ) { + swfAction_t & action = actions.Alloc(); + action.data = bitstream.ReadData( bitstream.Length() ); + action.dataLength = bitstream.Length(); +} + +/* +======================== +idSWFSpriteInstance::FindChildSprite +======================== +*/ +idSWFSpriteInstance * idSWFSpriteInstance::FindChildSprite( const char * targetName ) { + for ( int i = 0; i < displayList.Num(); i++ ) { + if ( displayList[i].spriteInstance != NULL ) { + if ( displayList[i].spriteInstance->name.Icmp( targetName ) == 0 ) { + return displayList[i].spriteInstance; + } + } + } + return NULL; +} + +/* +======================== +idSWFSpriteInstance::ResolveTarget +Gets the sprite instance for names like: +../foo/bar +/foo/bar +foo/bar +======================== +*/ +idSWFSpriteInstance * idSWFSpriteInstance::ResolveTarget( const char * targetName ) { + if ( targetName[0] == 0 ) { + return this; + } + idSWFSpriteInstance * target = this; + const char * c = targetName; + if ( c[0] == '/' ) { + while ( target->parent != NULL ) { + target = target->parent; + } + c++; + } + idStrList spriteNames; + spriteNames.Append( c ); + for ( int index = 0, ofs = spriteNames[index].Find( '/' ); ofs != -1; index++, ofs = spriteNames[index].Find( '/' ) ) { + spriteNames.Append( spriteNames[index].c_str() + ofs + 1 ); + spriteNames[index].CapLength( ofs ); + } + for ( int i = 0; i < spriteNames.Num(); i++ ) { + if ( spriteNames[i] == ".." ) { + target = target->parent; + } else { + target = target->FindChildSprite( spriteNames[i] ); + } + if ( target == NULL ) { + // Everything is likely to fail after this point + idLib::Warning( "SWF: Could not resolve %s, %s not found", targetName, spriteNames[i].c_str() ); + return this; + } + } + return target; +} + +/* +======================== +idSWFSpriteInstance::FindFrame +======================== +*/ +uint32 idSWFSpriteInstance::FindFrame( const char * labelName ) const { + int frameNum = atoi( labelName ); + if ( frameNum > 0 ) { + return frameNum; + } + for ( int i = 0; i < sprite->frameLabels.Num(); i++ ) { + if ( sprite->frameLabels[i].frameLabel.Icmp( labelName ) == 0 ) { + return sprite->frameLabels[i].frameNum; + } + } + idLib::Warning( "Could not find frame '%s' in sprite '%s'", labelName, GetName() ); + return currentFrame; +} + +/* +======================== +idSWFSpriteInstance::FrameExists +======================== +*/ +bool idSWFSpriteInstance::FrameExists( const char * labelName ) const { + int frameNum = atoi( labelName ); + if ( frameNum > 0 ) { + return frameNum <= sprite->frameCount; + } + + for ( int i = 0; i < sprite->frameLabels.Num(); i++ ) { + if ( sprite->frameLabels[i].frameLabel.Icmp( labelName ) == 0 ) { + return true; + } + } + + return false; +} + +/* +======================== +idSWFSpriteInstance::IsBetweenFrames +Checks if the current frame is between the given inclusive range. +======================== +*/ +bool idSWFSpriteInstance::IsBetweenFrames( const char * frameLabel1, const char * frameLabel2 ) const { + return currentFrame >= FindFrame( frameLabel1 ) && currentFrame <= FindFrame( frameLabel2 ); +} + +/* +======================== +idSWFSpriteInstance::SetMaterial +======================== +*/ +void idSWFSpriteInstance::SetMaterial( const idMaterial * material, int width, int height ) { + materialOverride = material; + + if ( materialOverride != NULL ) { + // Converting this to a short should be safe since we don't support images larger than 8k anyway + if ( materialOverride->GetStage(0) != NULL && materialOverride->GetStage(0)->texture.cinematic != NULL ) { + materialWidth = 256; + materialHeight = 256; + } else { + assert( materialOverride->GetImageWidth() > 0 && materialOverride->GetImageHeight() > 0 ); + assert( materialOverride->GetImageWidth() <= 8192 && materialOverride->GetImageHeight() <= 8192 ); + materialWidth = (uint16)materialOverride->GetImageWidth(); + materialHeight = (uint16)materialOverride->GetImageHeight(); + } + } else { + materialWidth = 0; + materialHeight = 0; + } + + if ( width >= 0 ) { + materialWidth = (uint16)width; + } + + if ( height >= 0 ) { + materialHeight = (uint16)height; + } +} + +/* +======================== +idSWFSpriteInstance::SetVisible +======================== +*/ +void idSWFSpriteInstance::SetVisible( bool visible ) { + isVisible = visible; + if ( isVisible ) { + for ( idSWFSpriteInstance * p = parent; p != NULL; p = p->parent ) { + p->childrenRunning = true; + } + } +} + +/* +======================== +idSWFSpriteInstance::PlayFrame +======================== +*/ +void idSWFSpriteInstance::PlayFrame( const idSWFParmList & parms ) { + if ( parms.Num() > 0 ) { + actions.Clear(); + RunTo( FindFrame( parms[0].ToString() ) ); + Play(); + } else { + idLib::Warning( "gotoAndPlay: expected 1 paramater" ); + } +} + +/* +======================== +idSWFSpriteInstance::StopFrame +======================== +*/ +void idSWFSpriteInstance::StopFrame( const idSWFParmList & parms ) { + if ( parms.Num() > 0 ) { + if ( parms[0].IsNumeric() && parms[0].ToInteger() < 1 ) { + RunTo( FindFrame( "1" ) ); + } else { + RunTo( FindFrame( parms[0].ToString() ) ); + } + Stop(); + } else { + idLib::Warning( "gotoAndStop: expected 1 paramater" ); + } +} + +/* +======================== +idSWFSpriteInstance::GetXPos +======================== +*/ +float idSWFSpriteInstance::GetXPos() const { + if ( parent == NULL ) { + return 0.0f; + } + + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "GetXPos: Couldn't find our display entry in our parent's display list for depth %d", depth ); + return 0.0f; + } + + return thisDisplayEntry->matrix.tx; +} + +/* +======================== +idSWFSpriteInstance::GetYPos +======================== +*/ +float idSWFSpriteInstance::GetYPos( bool overallPos ) const { + if ( parent == NULL ) { + return 0.0f; + } + + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "GetYPos: Couldn't find our display entry in our parents display list for depth %d", depth ); + return 0.0f; + } + + return thisDisplayEntry->matrix.ty; +} + +/* +======================== +idSWFSpriteInstance::SetXPos +======================== +*/ +void idSWFSpriteInstance::SetXPos( float xPos ) { + if ( parent == NULL ) { + return; + } + + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "_y: Couldn't find our display entry in our parents display list" ); + return; + } + + thisDisplayEntry->matrix.tx = xPos; +} + +/* +======================== +idSWFSpriteInstance::SetYPos +======================== +*/ +void idSWFSpriteInstance::SetYPos( float yPos ) { + if ( parent == NULL ) { + return; + } + + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "_y: Couldn't find our display entry in our parents display list" ); + return; + } + + thisDisplayEntry->matrix.ty = yPos; +} + +/* +======================== +idSWFSpriteInstance::SetPos +======================== +*/ +void idSWFSpriteInstance::SetPos( float xPos, float yPos ) { + if ( parent == NULL ) { + return; + } + + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "_y: Couldn't find our display entry in our parents display list" ); + return; + } + + thisDisplayEntry->matrix.tx = xPos; + thisDisplayEntry->matrix.ty = yPos; +} + +/* +======================== +idSWFSpriteInstance::SetRotation +======================== +*/ +void idSWFSpriteInstance::SetRotation( float rot ) { + if ( parent == NULL ) { + return; + } + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "_rotation: Couldn't find our display entry in our parents display list" ); + return; + } + + swfMatrix_t & matrix = thisDisplayEntry->matrix; + float xscale = matrix.Scale( idVec2( 1.0f, 0.0f ) ).Length(); + float yscale = matrix.Scale( idVec2( 0.0f, 1.0f ) ).Length(); + + float s, c; + idMath::SinCos( DEG2RAD( rot ), s, c ); + matrix.xx = c * xscale; + matrix.yx = s * xscale; + matrix.xy = -s * yscale; + matrix.yy = c * yscale; +} + +/* +======================== +idSWFSpriteInstance::SetScale +======================== +*/ +void idSWFSpriteInstance::SetScale( float x, float y ) { + if ( parent == NULL ) { + return; + } + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "scale: Couldn't find our display entry in our parents display list" ); + return; + } + + float newScale = x / 100.0f; + // this is done funky to maintain the current rotation + idVec2 currentScale = thisDisplayEntry->matrix.Scale( idVec2( 1.0f, 0.0f ) ); + if ( currentScale.Normalize() == 0.0f ) { + thisDisplayEntry->matrix.xx = newScale; + thisDisplayEntry->matrix.yx = 0.0f; + } else { + thisDisplayEntry->matrix.xx = currentScale.x * newScale; + thisDisplayEntry->matrix.yx = currentScale.y * newScale; + } + + newScale = y / 100.0f; + // this is done funky to maintain the current rotation + currentScale = thisDisplayEntry->matrix.Scale( idVec2( 0.0f, 1.0f ) ); + if ( currentScale.Normalize() == 0.0f ) { + thisDisplayEntry->matrix.yy = newScale; + thisDisplayEntry->matrix.xy = 0.0f; + } else { + thisDisplayEntry->matrix.yy = currentScale.y * newScale; + thisDisplayEntry->matrix.xy = currentScale.x * newScale; + } +} + +/* +======================== +idSWFSpriteInstance::SetMoveToScale +======================== +*/ +void idSWFSpriteInstance::SetMoveToScale( float x, float y ) { + moveToXScale = x; + moveToYScale = y; +} + +/* +======================== +idSWFSpriteInstance::SetMoveToScale +======================== +*/ +bool idSWFSpriteInstance::UpdateMoveToScale( float speed ) { + + if ( parent == NULL ) { + return false; + } + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "SetMoveToScale: Couldn't find our display entry in our parents display list" ); + return false; + } + + swfMatrix_t & matrix = thisDisplayEntry->matrix; + float xscale = matrix.Scale( idVec2( 1.0f, 0.0f ) ).Length() * 100.0f; + float yscale = matrix.Scale( idVec2( 0.0f, 1.0f ) ).Length() * 100.0f; + + float toX = xscale; + if ( moveToXScale >= 0.0f ) { + toX = moveToXScale * 100.0f; + } + + float toY = yscale; + if ( moveToYScale >= 0.0f ) { + toY = moveToYScale * 100.0f; + } + + int rXTo = idMath::Ftoi( toX + 0.5f ); + int rYTo = idMath::Ftoi( toY + 0.5f ); + int rXScale = idMath::Ftoi( xscale + 0.5f ); + int rYScale = idMath::Ftoi( yscale + 0.5f ); + + if ( rXTo == rXScale && rYTo == rYScale ) { + return false; + } + + float newXScale = xscale; + float newYScale = yscale; + + if ( rXTo != rXScale && toX >= 0.0f ) { + if ( toX < xscale ) { + newXScale -= speed; + newXScale = idMath::ClampFloat( toX, 100.0f, newXScale ); + } else if ( toX > xscale ) { + newXScale += speed; + newXScale = idMath::ClampFloat( 0.0f, toX, newXScale ); + } + } + + if ( rYTo != rYScale && toY >= 0.0f ) { + if ( toY < yscale ) { + newYScale -= speed; + newYScale = idMath::ClampFloat( toY, 100.0f, newYScale ); + } else if ( toY > yscale ) { + newYScale += speed; + newYScale = idMath::ClampFloat( 0.0f, toY, newYScale ); + } + } + + SetScale( newXScale, newYScale ); + return true; +} + +/* +======================== +idSWFSpriteInstance::SetAlpha +======================== +*/ +void idSWFSpriteInstance::SetAlpha( float val ) { + if ( parent == NULL ) { + return; + } + + swfDisplayEntry_t * thisDisplayEntry = parent->FindDisplayEntry( depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != this ) { + idLib::Warning( "_alpha: Couldn't find our display entry in our parents display list" ); + return; + } + thisDisplayEntry->cxf.mul.w = val; +} + +/* +======================== +idSWFScriptObject_SpriteInstancePrototype +======================== +*/ +#define SWF_SPRITE_FUNCTION_DEFINE( x ) idSWFScriptVar idSWFScriptObject_SpriteInstancePrototype::idSWFScriptFunction_##x::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) +#define SWF_SPRITE_NATIVE_VAR_DEFINE_GET( x ) idSWFScriptVar idSWFScriptObject_SpriteInstancePrototype::idSWFScriptNativeVar_##x::Get( class idSWFScriptObject * object ) +#define SWF_SPRITE_NATIVE_VAR_DEFINE_SET( x ) void idSWFScriptObject_SpriteInstancePrototype::idSWFScriptNativeVar_##x::Set( class idSWFScriptObject * object, const idSWFScriptVar & value ) + +#define SWF_SPRITE_PTHIS_FUNC( x ) idSWFSpriteInstance * pThis = thisObject ? thisObject->GetSprite() : NULL; if ( !verify( pThis != NULL ) ) { idLib::Warning( "SWF: tried to call " x " on NULL sprite" ); return idSWFScriptVar(); } +#define SWF_SPRITE_PTHIS_GET( x ) idSWFSpriteInstance * pThis = object ? object->GetSprite() : NULL; if ( pThis == NULL ) { return idSWFScriptVar(); } +#define SWF_SPRITE_PTHIS_SET( x ) idSWFSpriteInstance * pThis = object ? object->GetSprite() : NULL; if ( pThis == NULL ) { return; } + +#define SWF_SPRITE_FUNCTION_SET( x ) scriptFunction_##x.AddRef(); Set( #x, &scriptFunction_##x ); +#define SWF_SPRITE_NATIVE_VAR_SET( x ) SetNative( #x, &swfScriptVar_##x ); + +idSWFScriptObject_SpriteInstancePrototype::idSWFScriptObject_SpriteInstancePrototype() { + SWF_SPRITE_FUNCTION_SET( duplicateMovieClip ); + SWF_SPRITE_FUNCTION_SET( gotoAndPlay ); + SWF_SPRITE_FUNCTION_SET( gotoAndStop ); + SWF_SPRITE_FUNCTION_SET( swapDepths ); + SWF_SPRITE_FUNCTION_SET( nextFrame ); + SWF_SPRITE_FUNCTION_SET( prevFrame ); + SWF_SPRITE_FUNCTION_SET( play ); + SWF_SPRITE_FUNCTION_SET( stop ); + + SWF_SPRITE_NATIVE_VAR_SET( _x ); + SWF_SPRITE_NATIVE_VAR_SET( _y ); + SWF_SPRITE_NATIVE_VAR_SET( _xscale ); + SWF_SPRITE_NATIVE_VAR_SET( _yscale ); + SWF_SPRITE_NATIVE_VAR_SET( _alpha ); + SWF_SPRITE_NATIVE_VAR_SET( _brightness ); + SWF_SPRITE_NATIVE_VAR_SET( _visible ); + SWF_SPRITE_NATIVE_VAR_SET( _width ); + SWF_SPRITE_NATIVE_VAR_SET( _height ); + SWF_SPRITE_NATIVE_VAR_SET( _rotation ); + SWF_SPRITE_NATIVE_VAR_SET( _name ); + SWF_SPRITE_NATIVE_VAR_SET( _currentframe ); + SWF_SPRITE_NATIVE_VAR_SET( _totalframes ); + SWF_SPRITE_NATIVE_VAR_SET( _target ); + SWF_SPRITE_NATIVE_VAR_SET( _framesloaded ); + SWF_SPRITE_NATIVE_VAR_SET( _droptarget ); + SWF_SPRITE_NATIVE_VAR_SET( _url ); + SWF_SPRITE_NATIVE_VAR_SET( _highquality ); + SWF_SPRITE_NATIVE_VAR_SET( _focusrect ); + SWF_SPRITE_NATIVE_VAR_SET( _soundbuftime ); + SWF_SPRITE_NATIVE_VAR_SET( _quality ); + SWF_SPRITE_NATIVE_VAR_SET( _mousex ); + SWF_SPRITE_NATIVE_VAR_SET( _mousey ); + + SWF_SPRITE_NATIVE_VAR_SET( _stereoDepth ); + SWF_SPRITE_NATIVE_VAR_SET( _itemindex ); + SWF_SPRITE_NATIVE_VAR_SET( material ); + SWF_SPRITE_NATIVE_VAR_SET( materialWidth ); + SWF_SPRITE_NATIVE_VAR_SET( materialHeight ); + SWF_SPRITE_NATIVE_VAR_SET( xOffset ); + + SWF_SPRITE_NATIVE_VAR_SET( onEnterFrame ); + //SWF_SPRITE_NATIVE_VAR_SET( onLoad ); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _target ) { return ""; } +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _droptarget ) { return ""; } +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _url ) { return ""; } +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _highquality ) { return 2; } +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _focusrect ) { return true; } +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _soundbuftime ) { return 0; } +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _quality ) { return "BEST"; } + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _width ) { return 0.0f; } +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _width ) { } + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _height ) { return 0.0f; } +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _height ) { } + +SWF_SPRITE_FUNCTION_DEFINE( duplicateMovieClip ) { + SWF_SPRITE_PTHIS_FUNC( "duplicateMovieClip" ); + + if ( pThis->parent == NULL ) { + idLib::Warning( "Tried to duplicate root movie clip" ); + return idSWFScriptVar(); + } + if ( parms.Num() < 2 ) { + idLib::Warning( "duplicateMovieClip: expected 2 parameters" ); + return idSWFScriptVar(); + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "duplicateMovieClip: Couldn't find our display entry in our parents display list" ); + return idSWFScriptVar(); + } + + swfMatrix_t matrix = thisDisplayEntry->matrix; + swfColorXform_t cxf = thisDisplayEntry->cxf; + + swfDisplayEntry_t * display = pThis->parent->AddDisplayEntry( 16384 + parms[1].ToInteger(), thisDisplayEntry->characterID ); + if ( display == NULL ) { + return idSWFScriptVar(); + } + display->matrix = matrix; + display->cxf = cxf; + + idStr name = parms[0].ToString(); + pThis->parent->scriptObject->Set( name, display->spriteInstance->scriptObject ); + display->spriteInstance->name = name; + display->spriteInstance->RunTo( 1 ); + + return display->spriteInstance->scriptObject; +} + +SWF_SPRITE_FUNCTION_DEFINE( gotoAndPlay ) { + SWF_SPRITE_PTHIS_FUNC( "gotoAndPlay" ); + + if ( parms.Num() > 0 ) { + pThis->actions.Clear(); + pThis->RunTo( pThis->FindFrame( parms[0].ToString() ) ); + pThis->Play(); + } else { + idLib::Warning( "gotoAndPlay: expected 1 paramater" ); + } + return idSWFScriptVar(); +} + +SWF_SPRITE_FUNCTION_DEFINE( gotoAndStop ) { + SWF_SPRITE_PTHIS_FUNC( "gotoAndStop" ); + + if ( parms.Num() > 0 ) { + // Flash forces frames values less than 1 to 1. + if ( parms[0].IsNumeric() && parms[0].ToInteger() < 1 ) { + pThis->RunTo( pThis->FindFrame( "1" ) ); + } else { + pThis->RunTo( pThis->FindFrame( parms[0].ToString() ) ); + } + pThis->Stop(); + } else { + idLib::Warning( "gotoAndStop: expected 1 paramater" ); + } + return idSWFScriptVar(); +} + +SWF_SPRITE_FUNCTION_DEFINE( swapDepths ) { + SWF_SPRITE_PTHIS_FUNC( "swapDepths" ); + + if ( pThis->parent == NULL ) { + idLib::Warning( "Tried to swap depths on root movie clip" ); + return idSWFScriptVar(); + } + if ( parms.Num() < 1 ) { + idLib::Warning( "swapDepths: expected 1 parameters" ); + return idSWFScriptVar(); + } + pThis->parent->SwapDepths( pThis->depth, parms[0].ToInteger() ); + return idSWFScriptVar(); +} + +SWF_SPRITE_FUNCTION_DEFINE( nextFrame ) { + SWF_SPRITE_PTHIS_FUNC( "nextFrame" ); + pThis->NextFrame(); + return idSWFScriptVar(); +} + +SWF_SPRITE_FUNCTION_DEFINE( prevFrame ) { + SWF_SPRITE_PTHIS_FUNC( "prevFrame" ); + pThis->PrevFrame(); + return idSWFScriptVar(); +} +SWF_SPRITE_FUNCTION_DEFINE( play ) { + SWF_SPRITE_PTHIS_FUNC( "play" ); + pThis->Play(); + return idSWFScriptVar(); +} +SWF_SPRITE_FUNCTION_DEFINE( stop ) { + SWF_SPRITE_PTHIS_FUNC( "stop" ); + pThis->Stop(); + return idSWFScriptVar(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _x ) { + SWF_SPRITE_PTHIS_GET( "_x" ); + return pThis->GetXPos(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _x ) { + SWF_SPRITE_PTHIS_SET( "_x" ); + pThis->SetXPos( value.ToFloat() ); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _y ) { + SWF_SPRITE_PTHIS_GET( "_y" ); + return pThis->GetYPos(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _y ) { + SWF_SPRITE_PTHIS_SET( "_y" ); + pThis->SetYPos( value.ToFloat() ); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _xscale ) { + SWF_SPRITE_PTHIS_GET( "_xscale" ); + if ( pThis->parent == NULL ) { + return 1.0f; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_xscale: Couldn't find our display entry in our parents display list" ); + return 1.0f; + } + return thisDisplayEntry->matrix.Scale( idVec2( 1.0f, 0.0f ) ).Length() * 100.0f; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _xscale ) { + SWF_SPRITE_PTHIS_SET( "_xscale" ); + if ( pThis->parent == NULL ) { + return; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_xscale: Couldn't find our display entry in our parents display list" ); + return; + } + float newScale = value.ToFloat() / 100.0f; + // this is done funky to maintain the current rotation + idVec2 currentScale = thisDisplayEntry->matrix.Scale( idVec2( 1.0f, 0.0f ) ); + if ( currentScale.Normalize() == 0.0f ) { + thisDisplayEntry->matrix.xx = newScale; + thisDisplayEntry->matrix.yx = 0.0f; + } else { + thisDisplayEntry->matrix.xx = currentScale.x * newScale; + thisDisplayEntry->matrix.yx = currentScale.y * newScale; + } +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _yscale ) { + SWF_SPRITE_PTHIS_GET( "_yscale" ); + if ( pThis->parent == NULL ) { + return 1.0f; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_yscale: Couldn't find our display entry in our parents display list" ); + return 1.0f; + } + return thisDisplayEntry->matrix.Scale( idVec2( 0.0f, 1.0f ) ).Length() * 100.0f; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _yscale ) { + SWF_SPRITE_PTHIS_SET( "_yscale" ); + if ( pThis->parent == NULL ) { + return; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_yscale: Couldn't find our display entry in our parents display list" ); + return; + } + float newScale = value.ToFloat() / 100.0f; + // this is done funky to maintain the current rotation + idVec2 currentScale = thisDisplayEntry->matrix.Scale( idVec2( 0.0f, 1.0f ) ); + if ( currentScale.Normalize() == 0.0f ) { + thisDisplayEntry->matrix.yy = newScale; + thisDisplayEntry->matrix.xy = 0.0f; + } else { + thisDisplayEntry->matrix.yy = currentScale.y * newScale; + thisDisplayEntry->matrix.xy = currentScale.x * newScale; + } +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _alpha ) { + SWF_SPRITE_PTHIS_GET( "_alpha" ); + if ( pThis->parent == NULL ) { + return 1.0f; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_alpha: Couldn't find our display entry in our parents display list" ); + return 1.0f; + } + return thisDisplayEntry->cxf.mul.w; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _alpha ) { + SWF_SPRITE_PTHIS_SET( "_alpha" ); + + pThis->SetAlpha( value.ToFloat() ); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _brightness ) { + SWF_SPRITE_PTHIS_GET( "_brightness" ); + if ( pThis->parent == NULL ) { + return 1.0f; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_brightness: Couldn't find our display entry in our parents display list" ); + return 1.0f; + } + // This works as long as the user only used the "brightess" control in the editor + // If they used anything else (tint/advanced) then this will return fairly random values + const idVec4 & mul = thisDisplayEntry->cxf.mul; + const idVec4 & add = thisDisplayEntry->cxf.add; + float avgMul = ( mul.x + mul.y + mul.z ) / 3.0f; + float avgAdd = ( add.x + add.y + add.z ) / 3.0f; + if ( avgAdd > 1.0f ) { + return avgAdd; + } else { + return avgMul - 1.0f; + } +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _brightness ) { + SWF_SPRITE_PTHIS_SET( "_brightness" ); + if ( pThis->parent == NULL ) { + return; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_brightness: Couldn't find our display entry in our parents display list" ); + return; + } + // This emulates adjusting the "brightness" slider in the editor + // Although the editor forces alpha to 100% + float b = value.ToFloat(); + float c = 1.0f - b; + if ( b < 0.0f ) { + c = 1.0f + b; + b = 0.0f; + } + thisDisplayEntry->cxf.add.Set( b, b, b, thisDisplayEntry->cxf.add.w ); + thisDisplayEntry->cxf.mul.Set( c, c, c, thisDisplayEntry->cxf.mul.w ); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _visible ) { + SWF_SPRITE_PTHIS_GET( "_visible" ); + return pThis->isVisible; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _visible ) { + SWF_SPRITE_PTHIS_SET( "_visible" ); + pThis->isVisible = value.ToBool(); + if ( pThis->isVisible ) { + for ( idSWFSpriteInstance * p = pThis->parent; p != NULL; p = p->parent ) { + p->childrenRunning = true; + } + } +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _rotation ) { + SWF_SPRITE_PTHIS_GET( "_rotation" ); + if ( pThis->parent == NULL ) { + return 0.0f; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_rotation: Couldn't find our display entry in our parents display list" ); + return 0.0f; + } + idVec2 scale = thisDisplayEntry->matrix.Scale( idVec2( 0.0f, 1.0f ) ); + scale.Normalize(); + float rotation = RAD2DEG( idMath::ACos( scale.y ) ); + if ( scale.x < 0.0f ) { + rotation = -rotation; + } + return rotation; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _rotation ) { + SWF_SPRITE_PTHIS_SET( "_rotation" ); + if ( pThis->parent == NULL ) { + return; + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_rotation: Couldn't find our display entry in our parents display list" ); + return; + } + swfMatrix_t & matrix = thisDisplayEntry->matrix; + float xscale = matrix.Scale( idVec2( 1.0f, 0.0f ) ).Length(); + float yscale = matrix.Scale( idVec2( 0.0f, 1.0f ) ).Length(); + + float s, c; + idMath::SinCos( DEG2RAD( value.ToFloat() ), s, c ); + matrix.xx = c * xscale; + matrix.yx = s * xscale; + matrix.xy = -s * yscale; + matrix.yy = c * yscale; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _name ) { + SWF_SPRITE_PTHIS_GET( "_name" ); + return pThis->name.c_str(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _currentframe ) { + SWF_SPRITE_PTHIS_GET( "_currentframe" ); + return pThis->currentFrame; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _totalframes ) { + SWF_SPRITE_PTHIS_GET( "_totalframes" ); + return pThis->frameCount; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _framesloaded ) { + SWF_SPRITE_PTHIS_GET( "_framesloaded" ); + return pThis->frameCount; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _mousex ) { + SWF_SPRITE_PTHIS_GET( "_mousex" ); + if ( pThis->parent == NULL ) { + return pThis->sprite->GetSWF()->GetMouseX(); + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_mousex: Couldn't find our display entry in our parents display list" ); + return pThis->sprite->GetSWF()->GetMouseX(); + } + return pThis->sprite->GetSWF()->GetMouseX() - thisDisplayEntry->matrix.ty; +} +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _mousey ) { + SWF_SPRITE_PTHIS_GET( "_mousey" ); + if ( pThis->parent == NULL ) { + return pThis->sprite->GetSWF()->GetMouseY(); + } + swfDisplayEntry_t * thisDisplayEntry = pThis->parent->FindDisplayEntry( pThis->depth ); + if ( thisDisplayEntry == NULL || thisDisplayEntry->spriteInstance != pThis ) { + idLib::Warning( "_mousey: Couldn't find our display entry in our parents display list" ); + return pThis->sprite->GetSWF()->GetMouseY(); + } + return pThis->sprite->GetSWF()->GetMouseY() - thisDisplayEntry->matrix.ty; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _itemindex ) { + SWF_SPRITE_PTHIS_GET( "_itemindex" ); + return pThis->itemIndex; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _itemindex ) { + SWF_SPRITE_PTHIS_SET( "_itemindex" ); + pThis->itemIndex = value.ToInteger(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( _stereoDepth ) { + SWF_SPRITE_PTHIS_SET( "_stereoDepth" ); + pThis->stereoDepth = value.ToInteger(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( _stereoDepth ) { + SWF_SPRITE_PTHIS_GET( "_stereoDepth" ); + return pThis->stereoDepth; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( material ) { + SWF_SPRITE_PTHIS_GET( "material" ); + if ( pThis->materialOverride == NULL ) { + return idSWFScriptVar(); + } else { + return pThis->materialOverride->GetName(); + } +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( material ) { + SWF_SPRITE_PTHIS_SET( "material" ); + if ( !value.IsString() ) { + pThis->materialOverride = NULL; + } else { + // God I hope this material was referenced during map load + pThis->SetMaterial( declManager->FindMaterial( value.ToString(), false ) ); + } +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( materialWidth ) { + SWF_SPRITE_PTHIS_GET( "materialWidth" ); + return pThis->materialWidth; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( materialWidth ) { + SWF_SPRITE_PTHIS_SET( "materialWidth" ); + assert( value.ToInteger() > 0 ); + assert( value.ToInteger() <= 8192 ); + pThis->materialWidth = (uint16)value.ToInteger(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( materialHeight ) { + SWF_SPRITE_PTHIS_GET( "materialHeight" ); + return pThis->materialHeight; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( materialHeight ) { + SWF_SPRITE_PTHIS_SET( "materialHeight" ); + assert( value.ToInteger() > 0 ); + assert( value.ToInteger() <= 8192 ); + pThis->materialHeight = (uint16)value.ToInteger(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( xOffset ) { + SWF_SPRITE_PTHIS_GET( "xOffset" ); + return pThis->xOffset; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( xOffset ) { + SWF_SPRITE_PTHIS_SET( "xOffset" ); + pThis->xOffset = value.ToFloat(); +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_GET( onEnterFrame ) { + SWF_SPRITE_PTHIS_GET( "onEnterFrame" ); + return pThis->onEnterFrame; +} + +SWF_SPRITE_NATIVE_VAR_DEFINE_SET( onEnterFrame ) { + SWF_SPRITE_PTHIS_SET( "onEnterFrame" ); + pThis->onEnterFrame = value; +} \ No newline at end of file diff --git a/neo/swf/SWF_SpriteInstance.h b/neo/swf/SWF_SpriteInstance.h new file mode 100644 index 00000000..045ef68d --- /dev/null +++ b/neo/swf/SWF_SpriteInstance.h @@ -0,0 +1,258 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_SPRITEINSTANCE_H__ +#define __SWF_SPRITEINSTANCE_H__ + +/* +================================================ +There can be multiple instances of a single sprite running +================================================ +*/ +class idSWFSpriteInstance { +public: + idSWFSpriteInstance(); + ~idSWFSpriteInstance(); + + void Init( idSWFSprite * sprite, idSWFSpriteInstance * parent, int depth ); + + bool Run(); + bool RunActions(); + + const char * GetName() const { return name.c_str(); } + + idSWFScriptObject * GetScriptObject() { return scriptObject; } + void SetAlignment( float x, float y ) { xOffset = x; yOffset = y; } + + void SetMaterial( const idMaterial * material, int width = -1, int height = -1 ); + void SetVisible( bool visible ); + bool IsVisible() { return isVisible; } + void PlayFrame( const idSWFParmList & parms ); + void PlayFrame( const char * frameName ) { + idSWFParmList parms; + parms.Append( frameName ); + PlayFrame( parms ); + } + void PlayFrame( const int frameNum ) { + idSWFParmList parms; + parms.Append( frameNum ); + PlayFrame( parms ); + } + void StopFrame( const idSWFParmList & parms ); + void StopFrame( const char * frameName ) { + idSWFParmList parms; + parms.Append( frameName ); + StopFrame( parms ); + } + void StopFrame( const int frameNum ) { + idSWFParmList parms; + parms.Append( frameNum ); + StopFrame( parms ); + } + // FIXME: Why do all the Set functions have defaults of -1.0f? This seems arbitrar. + // Probably better to not have a default at all, so any non-parametized calls throw a + // compilation error. + float GetXPos() const; + float GetYPos( bool overallPos = false ) const; + void SetXPos( float xPos = -1.0f ); + void SetYPos( float yPos = -1.0f ); + void SetPos( float xPos = -1.0f, float yPos = -1.0f ); + void SetAlpha( float val ); + void SetScale( float x = -1.0f, float y = -1.0f ); + void SetMoveToScale( float x = -1.0f, float y = -1.0f ); + bool UpdateMoveToScale( float speed ); // returns true if the update was successful + void SetRotation( float rot ); + uint16 GetCurrentFrame() { return currentFrame; } + bool IsPlaying() const { return isPlaying; } + int GetStereoDepth() { return stereoDepth; } + + // Removing the private access control statement due to cl 214702 + // Apparently MS's C++ compiler supports the newer C++ standard, and GCC supports C++03 + // In the new C++ standard, nested members of a friend class have access to private/protected members of the class granting friendship + // In C++03, nested members defined in a friend class do NOT have access to private/protected members of the class granting friendship friend class idSWF; + + bool isPlaying; + bool isVisible; + bool childrenRunning; + bool firstRun; + + // currentFrame is the frame number currently in the displayList + // we use 1 based frame numbers because currentFrame = 0 means nothing is in the display list + // it's also convenient because Flash also uses 1 based frame numbers + uint16 currentFrame; + uint16 frameCount; + + // the sprite this is an instance of + idSWFSprite * sprite; + + // sprite instances can be nested + idSWFSpriteInstance * parent; + + // depth of this sprite instance in the parent's display list + int depth; + + // if this is set, apply this material when rendering any child shapes + int itemIndex; + + const idMaterial * materialOverride; + uint16 materialWidth; + uint16 materialHeight; + + float xOffset; + float yOffset; + + float moveToXScale; + float moveToYScale; + float moveToSpeed; + + int stereoDepth; + + idSWFScriptObject * scriptObject; + + // children display entries + idList< swfDisplayEntry_t, TAG_SWF > displayList; + swfDisplayEntry_t * FindDisplayEntry( int depth ); + + // name of this sprite instance + idStr name; + + struct swfAction_t { + const byte * data; + uint32 dataLength; + }; + idList< swfAction_t, TAG_SWF > actions; + + idSWFScriptFunction_Script * actionScript; + + idSWFScriptVar onEnterFrame; + //idSWFScriptVar onLoad; + + // Removing the private access control statement due to cl 214702 + // Apparently MS's C++ compiler supports the newer C++ standard, and GCC supports C++03 + // In the new C++ standard, nested members of a friend class have access to private/protected members of the class granting friendship + // In C++03, nested members defined in a friend class do NOT have access to private/protected members of the class granting friendship + + //---------------------------------- + // SWF_PlaceObject.cpp + //---------------------------------- + void PlaceObject2( idSWFBitStream & bitstream ); + void PlaceObject3( idSWFBitStream & bitstream ); + void RemoveObject2( idSWFBitStream & bitstream ); + + //---------------------------------- + // SWF_Sounds.cpp + //---------------------------------- + void StartSound( idSWFBitStream & bitstream ); + + //---------------------------------- + // SWF_SpriteInstance.cpp + //---------------------------------- + void NextFrame(); + void PrevFrame(); + void RunTo( int frameNum ); + + void Play(); + void Stop(); + + void FreeDisplayList(); + swfDisplayEntry_t * AddDisplayEntry( int depth, int characterID ); + void RemoveDisplayEntry( int depth ); + void SwapDepths( int depth1, int depth2 ); + + void DoAction( idSWFBitStream & bitstream ); + + idSWFSpriteInstance * FindChildSprite( const char * childName ); + idSWFSpriteInstance * ResolveTarget( const char * targetName ); + uint32 FindFrame( const char * frameLabel ) const; + bool FrameExists( const char * frameLabel ) const; + bool IsBetweenFrames( const char * frameLabel1, const char * frameLabel2 ) const; +}; + +/* +================================================ +This is the prototype object that all the sprite instance script objects reference +================================================ +*/ +class idSWFScriptObject_SpriteInstancePrototype : public idSWFScriptObject { +public: + idSWFScriptObject_SpriteInstancePrototype(); + +#define SWF_SPRITE_FUNCTION_DECLARE( x ) \ + class idSWFScriptFunction_##x : public idSWFScriptFunction { \ + public: \ + void AddRef() {} \ + void Release() {} \ + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ); \ + } scriptFunction_##x + + SWF_SPRITE_FUNCTION_DECLARE( duplicateMovieClip ); + SWF_SPRITE_FUNCTION_DECLARE( gotoAndPlay ); + SWF_SPRITE_FUNCTION_DECLARE( gotoAndStop ); + SWF_SPRITE_FUNCTION_DECLARE( swapDepths ); + SWF_SPRITE_FUNCTION_DECLARE( nextFrame ); + SWF_SPRITE_FUNCTION_DECLARE( prevFrame ); + SWF_SPRITE_FUNCTION_DECLARE( play ); + SWF_SPRITE_FUNCTION_DECLARE( stop ); + + SWF_NATIVE_VAR_DECLARE( _x ); + SWF_NATIVE_VAR_DECLARE( _y ); + SWF_NATIVE_VAR_DECLARE( _xscale ); + SWF_NATIVE_VAR_DECLARE( _yscale ); + SWF_NATIVE_VAR_DECLARE( _alpha ); + SWF_NATIVE_VAR_DECLARE( _brightness ); + SWF_NATIVE_VAR_DECLARE( _visible ); + SWF_NATIVE_VAR_DECLARE( _width ); + SWF_NATIVE_VAR_DECLARE( _height ); + SWF_NATIVE_VAR_DECLARE( _rotation ); + + SWF_NATIVE_VAR_DECLARE_READONLY( _name ); + SWF_NATIVE_VAR_DECLARE_READONLY( _currentframe ); + SWF_NATIVE_VAR_DECLARE_READONLY( _totalframes ); + SWF_NATIVE_VAR_DECLARE_READONLY( _target ); + SWF_NATIVE_VAR_DECLARE_READONLY( _framesloaded ); + SWF_NATIVE_VAR_DECLARE_READONLY( _droptarget ); + SWF_NATIVE_VAR_DECLARE_READONLY( _url ); + SWF_NATIVE_VAR_DECLARE_READONLY( _highquality ); + SWF_NATIVE_VAR_DECLARE_READONLY( _focusrect ); + SWF_NATIVE_VAR_DECLARE_READONLY( _soundbuftime ); + SWF_NATIVE_VAR_DECLARE_READONLY( _quality ); + SWF_NATIVE_VAR_DECLARE_READONLY( _mousex ); + SWF_NATIVE_VAR_DECLARE_READONLY( _mousey ); + + SWF_NATIVE_VAR_DECLARE( _stereoDepth ); + SWF_NATIVE_VAR_DECLARE( _itemindex ); + + SWF_NATIVE_VAR_DECLARE( material ); + SWF_NATIVE_VAR_DECLARE( materialWidth ); + SWF_NATIVE_VAR_DECLARE( materialHeight ); + + SWF_NATIVE_VAR_DECLARE( xOffset ); + SWF_NATIVE_VAR_DECLARE( onEnterFrame ); + //SWF_NATIVE_VAR_DECLARE( onLoad ); +}; + +#endif diff --git a/neo/swf/SWF_Sprites.cpp b/neo/swf/SWF_Sprites.cpp new file mode 100644 index 00000000..bb6c1f7f --- /dev/null +++ b/neo/swf/SWF_Sprites.cpp @@ -0,0 +1,257 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +======================== +idSWFSprite::idSWFSprite +======================== +*/ +idSWFSprite::idSWFSprite( idSWF * _swf ) : +frameCount( 0 ), +swf( _swf ), +commandBuffer( NULL ) +{ +} + +/* +======================== +idSWFSprite::~idSWFSprite +======================== +*/ +idSWFSprite::~idSWFSprite() { + Mem_Free( commandBuffer ); +} + +/* +======================== +idSWF::DefineSprite +======================== +*/ +void idSWF::DefineSprite( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_SPRITE ); + if ( entry == NULL ) { + return; + } + entry->sprite->Load( bitstream, false ); +} + +/* +======================== +idSWFSprite::Load +======================== +*/ +void idSWFSprite::Load( idSWFBitStream & bitstream, bool parseDictionary ) { + + frameCount = bitstream.ReadU16(); + + // run through the file once, building the dictionary and accumulating control tags + frameOffsets.SetNum( frameCount + 1 ); + frameOffsets[0] = 0; + + unsigned int currentFrame = 1; + + while ( true ) { + uint16 codeAndLength = bitstream.ReadU16(); + uint32 recordLength = ( codeAndLength & 0x3F ); + if ( recordLength == 0x3F ) { + recordLength = bitstream.ReadU32(); + } + + idSWFBitStream tagStream( bitstream.ReadData( recordLength ), recordLength, false ); + + swfTag_t tag = (swfTag_t)( codeAndLength >> 6 ); + + // ---------------------- + // Definition tags + // definition tags are only allowed in the main sprite + // ---------------------- + if ( parseDictionary ) { + bool handled = true; + switch ( tag ) { +#define HANDLE_SWF_TAG( x ) case Tag_##x: swf->x( tagStream ); break; + HANDLE_SWF_TAG( JPEGTables ); + HANDLE_SWF_TAG( DefineBits ); + HANDLE_SWF_TAG( DefineBitsJPEG2 ); + HANDLE_SWF_TAG( DefineBitsJPEG3 ); + HANDLE_SWF_TAG( DefineBitsLossless ); + HANDLE_SWF_TAG( DefineBitsLossless2 ); + HANDLE_SWF_TAG( DefineShape ); + HANDLE_SWF_TAG( DefineShape2 ); + HANDLE_SWF_TAG( DefineShape3 ); + HANDLE_SWF_TAG( DefineShape4 ); + HANDLE_SWF_TAG( DefineSprite ); + HANDLE_SWF_TAG( DefineSound ); + //HANDLE_SWF_TAG( DefineMorphShape ); // these don't work right + HANDLE_SWF_TAG( DefineFont2 ); + HANDLE_SWF_TAG( DefineFont3 ); + HANDLE_SWF_TAG( DefineText ); + HANDLE_SWF_TAG( DefineText2 ); + HANDLE_SWF_TAG( DefineEditText ); +#undef HANDLE_SWF_TAG + default: handled = false; + } + if ( handled ) { + continue; + } + } + // ---------------------- + // Control tags + // control tags are stored off in the commands list and processed at run time + // except for a couple really special control tags like "End" and "FrameLabel" + // ---------------------- + switch ( tag ) { + case Tag_End: + return; + + case Tag_ShowFrame: + frameOffsets[ currentFrame ] = commands.Num(); + currentFrame++; + break; + + case Tag_FrameLabel: { + swfFrameLabel_t & label = frameLabels.Alloc(); + label.frameNum = currentFrame; + label.frameLabel = tagStream.ReadString(); + } + break; + + case Tag_DoInitAction: { + tagStream.ReadU16(); + + idSWFBitStream &initaction = doInitActions.Alloc(); + initaction.Load( tagStream.ReadData( recordLength - 2 ), recordLength - 2, true ); + } + break; + + case Tag_DoAction: + case Tag_PlaceObject2: + case Tag_PlaceObject3: + case Tag_RemoveObject2: { + swfSpriteCommand_t & command = commands.Alloc(); + command.tag = tag; + command.stream.Load( tagStream.ReadData( recordLength ), recordLength, true ); + } + break; + + default: + // We don't care, about sprite tags we don't support ... RobA + //idLib::Printf( "Load Sprite: Unhandled tag %s\n", idSWF::GetTagName( tag ) ); + break; + } + } +} + +/* +======================== +idSWFSprite::Read +======================== +*/ +void idSWFSprite::Read( idFile * f ) { + int num = 0; + f->ReadBig( frameCount ); + f->ReadBig( num ); frameOffsets.SetNum( num ); + f->ReadBigArray( frameOffsets.Ptr(), frameOffsets.Num() ); + f->ReadBig( num ); frameLabels.SetNum( num ); + for ( int i = 0; i < frameLabels.Num(); i++ ) { + f->ReadBig( frameLabels[i].frameNum ); + f->ReadString( frameLabels[i].frameLabel ); + } + + uint32 bufferSize; + f->ReadBig( bufferSize ); + + commandBuffer = (byte *)Mem_Alloc( bufferSize, TAG_SWF ); + f->Read( commandBuffer, bufferSize ); + + byte * currentBuffer = commandBuffer; + + f->ReadBig( num ); commands.SetNum( num ); + for ( int i = 0; i < commands.Num(); i++ ) { + uint32 streamLength = 0; + + f->ReadBig( commands[i].tag ); + f->ReadBig( streamLength ); + + commands[i].stream.Load( currentBuffer, streamLength, false ); + currentBuffer += streamLength; + } + + uint32 doInitActionLength = 0; + f->ReadBig( num ); + doInitActions.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + f->ReadBig( doInitActionLength ); + idSWFBitStream &initaction = doInitActions[i]; + initaction.Load( currentBuffer, doInitActionLength, true ); + currentBuffer += doInitActionLength; + } +} + +/* +======================== +idSWFSprite::Write +======================== +*/ +void idSWFSprite::Write( idFile * f ) { + f->WriteBig( frameCount ); + f->WriteBig( frameOffsets.Num() ); + f->WriteBigArray( frameOffsets.Ptr(), frameOffsets.Num() ); + f->WriteBig( frameLabels.Num() ); + for ( int i = 0; i < frameLabels.Num(); i++ ) { + f->WriteBig( frameLabels[i].frameNum ); + f->WriteString( frameLabels[i].frameLabel ); + } + uint32 totalLength = 0; + for ( int i = 0; i < commands.Num(); i++ ) { + totalLength += commands[i].stream.Length(); + } + for (int i = 0; i < doInitActions.Num(); i++ ) { + totalLength += doInitActions[i].Length(); + } + f->WriteBig( totalLength ); + for ( int i = 0; i < commands.Num(); i++ ) { + f->Write( commands[i].stream.Ptr(), commands[i].stream.Length() ); + } + for ( int i = 0; i < doInitActions.Num(); i++ ){ + f->Write( doInitActions[i].Ptr(), doInitActions[i].Length() ); + } + + f->WriteBig( commands.Num() ); + for ( int i = 0; i < commands.Num(); i++ ) { + f->WriteBig( commands[i].tag ); + f->WriteBig( commands[i].stream.Length() ); + } + + f->WriteBig( doInitActions.Num() ); + for ( int i = 0; i < doInitActions.Num(); i++ ){ + f->WriteBig( doInitActions[i].Length() ); + } +} diff --git a/neo/swf/SWF_Sprites.h b/neo/swf/SWF_Sprites.h new file mode 100644 index 00000000..7fb16cde --- /dev/null +++ b/neo/swf/SWF_Sprites.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_SPRITES_H__ +#define __SWF_SPRITES_H__ + +/* +================================================ +What the swf file format calls a "sprite" is known as a "movie clip" in Flash +There is one main sprite, and many many sub-sprites +Only the main sprite is allowed to add things to the dictionary +================================================ +*/ +class idSWFSprite { +public: + idSWFSprite( class idSWF * swf ); + ~idSWFSprite(); + + void Load( idSWFBitStream & bitstream, bool parseDictionary ); + + void Read( idFile * f ); + void Write( idFile * f ); + + class idSWF * GetSWF() { return swf; } + +private: + friend class idSWFSpriteInstance; + friend class idSWFScriptFunction_Script; + + class idSWF * swf; // this is required so things can access the dictionary, it would be kind of nice if we just had an idSWFDictionary pointer instead + + uint16 frameCount; + + // frameOffsets contains offsets into the commands list for each frame + // the first command for frame 3 is frameOffsets[2] and the last command is frameOffsets[3] + idList< uint32, TAG_SWF > frameOffsets; + + struct swfFrameLabel_t { + idStr frameLabel; + uint32 frameNum; + }; + idList< swfFrameLabel_t, TAG_SWF > frameLabels; + + struct swfSpriteCommand_t { + swfTag_t tag; + idSWFBitStream stream; + }; + idList< swfSpriteCommand_t, TAG_SWF > commands; + + //// [ES-BrianBugh 1/16/10] - There can be multiple DoInitAction tags, and all need to be executed. + idList doInitActions; + + byte * commandBuffer; + +}; + +#endif // !__SWF_SPRITES_H__ diff --git a/neo/swf/SWF_Text.cpp b/neo/swf/SWF_Text.cpp new file mode 100644 index 00000000..e1359113 --- /dev/null +++ b/neo/swf/SWF_Text.cpp @@ -0,0 +1,306 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#pragma warning( disable: 4189 ) // local variable is initialized but not referenced + +/* +======================== +idSWF::DefineFont2 +======================== +*/ +void idSWF::DefineFont2( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_FONT ); + if ( entry == NULL ) { + return; + } + uint8 flags = bitstream.ReadU8(); + uint8 language = bitstream.ReadU8(); + + char fontName[257]; + uint8 fontNameLength = bitstream.ReadU8(); + memcpy( fontName, bitstream.ReadData( fontNameLength ), fontNameLength ); + fontName[ fontNameLength ] = 0; + + entry->font->fontID = renderSystem->RegisterFont( fontName ); + + uint16 numGlyphs = bitstream.ReadU16(); + entry->font->glyphs.SetNum( numGlyphs ); + + if ( flags & BIT( 3 ) ) { + // 32 bit offsets + uint32 offsetTableSize = ( numGlyphs + 1 ) * 4; + idSWFBitStream offsetStream( bitstream.ReadData( offsetTableSize ), offsetTableSize, false ); + if ( offsetStream.ReadU32() != offsetTableSize ) { + idLib::Warning( "idSWF::DefineFont2: first glyph offset != offsetTableSize" ); + return; + } + uint32 previousOffset = offsetTableSize; + for ( int i = 0; i < numGlyphs; i++ ) { + uint32 nextOffset = offsetStream.ReadU32(); + uint32 shapeSize = nextOffset - previousOffset; + previousOffset = nextOffset; + idSWFBitStream shapeStream( bitstream.ReadData( shapeSize ), shapeSize, false ); + idSWFShapeParser swfShapeParser; + swfShapeParser.ParseFont( shapeStream, entry->font->glyphs[i] ); + } + } else { + // 16 bit offsets + uint16 offsetTableSize = ( numGlyphs + 1 ) * 2; + idSWFBitStream offsetStream( bitstream.ReadData( offsetTableSize ), offsetTableSize, false ); + if ( offsetStream.ReadU16() != offsetTableSize ) { + idLib::Warning( "idSWF::DefineFont2: first glyph offset != offsetTableSize" ); + return; + } + uint16 previousOffset = offsetTableSize; + for ( int i = 0; i < numGlyphs; i++ ) { + uint16 nextOffset = offsetStream.ReadU16(); + uint16 shapeSize = nextOffset - previousOffset; + previousOffset = nextOffset; + idSWFBitStream shapeStream( bitstream.ReadData( shapeSize ), shapeSize, false ); + idSWFShapeParser swfShapeParser; + swfShapeParser.ParseFont( shapeStream, entry->font->glyphs[i] ); + } + } + if ( flags & BIT( 2 ) ) { + // 16 bit codes + for ( int i = 0; i < numGlyphs; i++ ) { + entry->font->glyphs[i].code = bitstream.ReadU16(); + } + } else { + // 8 bit codes + for ( int i = 0; i < numGlyphs; i++ ) { + entry->font->glyphs[i].code = bitstream.ReadU8(); + } + } + if ( flags & BIT( 7 ) ) { + entry->font->ascent = bitstream.ReadS16(); + entry->font->descent = bitstream.ReadS16(); + entry->font->leading = bitstream.ReadS16(); + for ( int i = 0; i < numGlyphs; i++ ) { + entry->font->glyphs[i].advance = bitstream.ReadS16(); + } + for ( int i = 0; i < numGlyphs; i++ ) { + swfRect_t ignored; + bitstream.ReadRect( ignored ); + } + uint16 kearningCount = bitstream.ReadU16(); + if ( flags & BIT( 2 ) ) { + for ( int i = 0; i < kearningCount; i++ ) { + uint16 code1 = bitstream.ReadU16(); + uint16 code2 = bitstream.ReadU16(); + int16 adjustment = bitstream.ReadS16(); + } + } else { + for ( int i = 0; i < kearningCount; i++ ) { + uint16 code1 = bitstream.ReadU8(); + uint16 code2 = bitstream.ReadU8(); + int16 adjustment = bitstream.ReadS16(); + } + } + } +} + +/* +======================== +idSWF::DefineFont3 +======================== +*/ +void idSWF::DefineFont3( idSWFBitStream & bitstream ) { + DefineFont2( bitstream ); +} + +/* +======================== +idSWF::DefineTextX +======================== +*/ +void idSWF::DefineTextX( idSWFBitStream & bitstream, bool rgba ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_TEXT ); + if ( entry == NULL ) { + return; + } + idSWFText * text = entry->text; + + bitstream.ReadRect( text->bounds ); + bitstream.ReadMatrix( text->matrix ); + + uint8 glyphBits = bitstream.ReadU8(); + uint8 advanceBits = bitstream.ReadU8(); + + while ( true ) { + uint8 flags = bitstream.ReadU8(); + if ( flags == 0 ) { + break; + } + idSWFTextRecord & textRecord = text->textRecords.Alloc(); + + if ( flags & BIT( 3 ) ) { + textRecord.fontID = bitstream.ReadU16(); + } + if ( flags & BIT( 2 ) ) { + if ( rgba ) { + bitstream.ReadColorRGBA( textRecord.color ); + } else { + bitstream.ReadColorRGB( textRecord.color ); + } + } + if ( flags & BIT( 0 ) ) { + textRecord.xOffset = bitstream.ReadS16(); + } + if ( flags & BIT( 1 ) ) { + textRecord.yOffset = bitstream.ReadS16(); + } + if ( flags & BIT( 3 ) ) { + textRecord.textHeight = bitstream.ReadU16(); + } + textRecord.firstGlyph = text->glyphs.Num(); + textRecord.numGlyphs = bitstream.ReadU8(); + for ( int i = 0; i < textRecord.numGlyphs; i++ ) { + swfGlyphEntry_t & glyph = text->glyphs.Alloc(); + glyph.index = bitstream.ReadU( glyphBits ); + glyph.advance = bitstream.ReadS( advanceBits ); + } + }; +} + +/* +======================== +idSWF::DefineText +======================== +*/ +void idSWF::DefineText( idSWFBitStream & bitstream ) { + DefineTextX( bitstream, false ); +} + +/* +======================== +idSWF::DefineText2 +======================== +*/ +void idSWF::DefineText2( idSWFBitStream & bitstream ) { + DefineTextX( bitstream, true ); +} + +/* +======================== +idSWF::DefineEditText +======================== +*/ +void idSWF::DefineEditText( idSWFBitStream & bitstream ) { + uint16 characterID = bitstream.ReadU16(); + idSWFDictionaryEntry * entry = AddDictionaryEntry( characterID, SWF_DICT_EDITTEXT ); + if ( entry == NULL ) { + return; + } + idSWFEditText * edittext = entry->edittext; + bitstream.ReadRect( edittext->bounds ); + bitstream.ResetBits(); + bool hasText = bitstream.ReadBool(); + bool wordWrap = bitstream.ReadBool(); + bool multiline = bitstream.ReadBool(); + bool password = bitstream.ReadBool(); + bool readonly = bitstream.ReadBool(); + bool hasTextColor = bitstream.ReadBool(); + bool hasMaxLength = bitstream.ReadBool(); + bool hasFont = bitstream.ReadBool(); + bool hasFontClass = bitstream.ReadBool(); + bool autoSize = bitstream.ReadBool(); + bool hasLayout = bitstream.ReadBool(); + bool noSelect = bitstream.ReadBool(); + bool border = bitstream.ReadBool(); + bool wasStatic = bitstream.ReadBool(); + bool html = bitstream.ReadBool(); + bool useOutlines = bitstream.ReadBool(); + if ( hasFont ) { + edittext->fontID = bitstream.ReadU16(); + edittext->fontHeight = bitstream.ReadU16(); + } + if ( hasFontClass ) { + idStr fontClass = bitstream.ReadString(); + } + if ( hasTextColor ) { + bitstream.ReadColorRGBA( edittext->color ); + } + if ( hasMaxLength ) { + edittext->maxLength = bitstream.ReadU16(); + } + if ( hasLayout ) { + edittext->align = (swfEditTextAlign_t)bitstream.ReadU8(); + edittext->leftMargin = bitstream.ReadU16(); + edittext->rightMargin = bitstream.ReadU16(); + edittext->indent = bitstream.ReadU16(); + edittext->leading = bitstream.ReadS16(); + } + edittext->variable = bitstream.ReadString(); + if ( hasText ) { + const char * text = bitstream.ReadString(); + idStr initialText; + + // convert html tags if necessary + for ( int i = 0; text[i] != 0; i++ ) { + if ( text[i] == '<' ) { + if ( i != 0 && text[i+1] == 'p' ) { + initialText.Append( '\n' ); + } + for ( ; text[i] != 0 && text[i] != '>'; i++ ) { + } + continue; + } + byte tc = (byte)text[i]; + if ( tc == '&' ) { + idStr special; + for ( i++; text[i] != 0 && text[i] != ';'; i++ ) { + special.Append( text[i] ); + } + if ( special.Icmp( "amp" ) == 0 ) { + tc = '&'; + } else if ( special.Icmp( "apos" ) == 0 ) { + tc = '\''; + } else if ( special.Icmp( "lt" ) == 0 ) { + tc = '<'; + } else if ( special.Icmp( "gt" ) == 0 ) { + tc = '>'; + } else if ( special.Icmp( "quot" ) == 0 ) { + tc = '\"'; + } + } + initialText.Append( tc ); + } + edittext->initialText = initialText; + } + edittext->flags |= wordWrap ? SWF_ET_WORDWRAP : 0; + edittext->flags |= multiline ? SWF_ET_MULTILINE : 0; + edittext->flags |= password ? SWF_ET_PASSWORD : 0; + edittext->flags |= readonly ? SWF_ET_READONLY : 0; + edittext->flags |= autoSize ? SWF_ET_AUTOSIZE : 0; + edittext->flags |= border ? SWF_ET_BORDER : 0; +} diff --git a/neo/swf/SWF_TextInstance.cpp b/neo/swf/SWF_TextInstance.cpp new file mode 100644 index 00000000..8024b380 --- /dev/null +++ b/neo/swf/SWF_TextInstance.cpp @@ -0,0 +1,1242 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../renderer/Font.h" + +idSWFScriptObject_TextInstancePrototype textInstanceScriptObjectPrototype; + +idCVar swf_textScrollSpeed( "swf_textScrollSpeed", "80", CVAR_INTEGER, "scroll speed for text" ); +idCVar swf_textRndLetterSpeed( "swf_textRndLetterSpeed", "8", CVAR_INTEGER, "scroll speed for text" ); +idCVar swf_textRndLetterDelay( "swf_textRndLetterDelay", "100", CVAR_INTEGER, "scroll speed for text" ); +idCVar swf_textParagraphSpeed( "swf_textParagraphSpeed", "15", CVAR_INTEGER, "scroll speed for text" ); +idCVar swf_textParagraphInc( "swf_textParagraphInc", "1.3", CVAR_FLOAT, "scroll speed for text" ); +idCVar swf_subtitleExtraTime( "swf_subtitleExtraTime", "3500", CVAR_INTEGER, "time after subtitles vo is complete" ); +idCVar swf_subtitleEarlyTrans( "swf_subtitleEarlyTrans", "3500", CVAR_INTEGER, "early time out to switch the line" ); +idCVar swf_subtitleLengthGuess( "swf_subtitleLengthGuess", "10000", CVAR_INTEGER, "early time out to switch the line" ); +idCVar swf_textMaxInputLength( "swf_textMaxInputLength", "104", CVAR_INTEGER, "max number of characters that can go into the input line" ); +idCVar swf_textStrokeSize( "swf_textStrokeSize", "1.65f", CVAR_FLOAT, "size of font glyph stroke", 0.0f, 2.0f ); +idCVar swf_textStrokeSizeGlyphSpacer( "swf_textStrokeSizeGlyphSpacer", "1.5f", CVAR_FLOAT, "additional space for spacing glyphs using stroke" ); + + +/* +======================== +idSWFTextInstance::idSWFTextInstance +======================== +*/ +idSWFTextInstance::idSWFTextInstance() { + swf = NULL; +} + +/* +======================== +idSWFTextInstance::~idSWFTextInstance +================== ====== +*/ +idSWFTextInstance::~idSWFTextInstance() { + scriptObject.SetText( NULL ); + scriptObject.Clear(); + scriptObject.Release(); + + subtitleTimingInfo.Clear(); +} + +/* +======================== +idSWFTextInstance::Init +======================== +*/ +void idSWFTextInstance::Init( idSWFEditText * _editText, idSWF * _swf ) { + editText = _editText; + swf = _swf; + + text = idLocalization::GetString( editText->initialText ); + + lengthCalculated = false; + variable = editText->variable; + color = editText->color; + visible = true; + + selectionStart = -1; + selectionEnd = -1; + + scroll = 0; + scrollTime = 0; + maxscroll = 0; + maxLines = 0; + linespacing = 0; + glyphScale = 1.0f; + + shiftHeld = false; + tooltip = false; + renderMode = SWF_TEXT_RENDER_NORMAL; + generatingText = false; + triggerGenerate = false; + rndSpotsVisible = 0; + textSpotsVisible = 0; + startRndTime = 0; + charMultiplier = 0; + prevReplaceIndex = 0; + scrollUpdate = false; + ignoreColor = false; + + isSubtitle = false; + subLength = 0; + subAlign = 0; + subUpdating = false; + subCharStartIndex = 0; + subNextStartIndex = 0; + subCharEndIndex = 0; + subDisplayTime = 0; + subStartTime = -1; + subSourceID = -1; + subNeedsSwitch = false; + subForceKill = false; + subKillTimeDelay = 0; + subSwitchTime = 0; + subLastWordIndex = 0; + subPrevLastWordIndex = 0; + subInitialLine = true; + + textLength = 0; + + inputTextStartChar = 0; + + renderDelay = swf_textRndLetterDelay.GetInteger(); + needsSoundUpdate = false; + useDropShadow = false; + useStroke = false; + strokeStrength = 1.0f; + strokeWeight = swf_textStrokeSize.GetFloat(); + + scriptObject.SetPrototype( &textInstanceScriptObjectPrototype ); + scriptObject.SetText( this ); + scriptObject.SetNoAutoDelete( true ); +} + +/* +======================== +idSWFTextInstance::GetTextLength +======================== +*/ +float idSWFTextInstance::GetTextLength() { + // CURRENTLY ONLY WORKS FOR SINGLE LINE TEXTFIELDS + + if ( lengthCalculated && variable.IsEmpty() ) { + return textLength; + } + + idStr txtLengthCheck = ""; + + float len = 0.0f; + if ( verify( swf != NULL ) ) { + + if ( !variable.IsEmpty() ) { + idSWFScriptVar var = swf->GetGlobal( variable ); + if ( var.IsUndefined() ) { + txtLengthCheck = text; + } else { + txtLengthCheck = var.ToString(); + } + txtLengthCheck = idLocalization::GetString( txtLengthCheck ); + } else { + txtLengthCheck = idLocalization::GetString( text ); + } + + const idSWFEditText * shape = editText; + idSWFDictionaryEntry * fontEntry = swf->FindDictionaryEntry( shape->fontID, SWF_DICT_FONT ); + idSWFFont * swfFont = fontEntry->font; + float width = fabs( shape->bounds.br.x - shape->bounds.tl.x ); + float postTrans = SWFTWIP( shape->fontHeight ); + const idFont * fontInfo = swfFont->fontID; + float glyphScale = postTrans / 48.0f; + + int tlen = txtLengthCheck.Length(); + int index = 0; + while ( index < tlen ) { + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, txtLengthCheck.UTF8Char( index ), glyph ); + + len += glyph.xSkip; + if ( useStroke ) { + len += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * strokeWeight * glyphScale ); + } + + if ( !( shape->flags & SWF_ET_AUTOSIZE ) && len >= width ) { + len = width; + break; + } + } + } + + lengthCalculated = true; + textLength = len; + return textLength; +} + +/* +======================== +idSWFTextInstance::StartParagraphText +======================== +*/ +void idSWFTextInstance::StartParagraphText( int time ) { + generatingText = true; + textSpotsVisible = 0; + randomtext = ""; + triggerGenerate = false; + startRndTime = time; + rndTime = time; + rnd.SetSeed( time ); + prevReplaceIndex = 0; + rndSpotsVisible = text.Length(); + indexArray.Clear(); + charMultiplier = 0; + + text = idLocalization::GetString( text ); + lengthCalculated = false; + + for( int index = 0; index < text.Length(); ++index ) { + randomtext.Append( " " ); + indexArray.Append( index ); + } + + for( int index = 0; index < indexArray.Num(); ++index ) { + int swapIndex = rnd.RandomInt( indexArray.Num() ); + int val = indexArray[index]; + indexArray[index] = indexArray[swapIndex]; + indexArray[swapIndex] = val; + } +} + +/* +======================== +idSWFTextInstance::GetParagraphText +======================== +*/ +idStr idSWFTextInstance::GetParagraphText( int time ) { + if ( triggerGenerate ) { + return " "; + } else if ( time - startRndTime < renderDelay ) { + return " "; + } else if ( generatingText ) { + if ( time - rndTime >= renderDelay ) { + rndTime = time; + needsSoundUpdate = true; + + if ( prevReplaceIndex >= text.Length() ) { + generatingText = false; + return text; + } + + randomtext[prevReplaceIndex] = text[prevReplaceIndex]; + prevReplaceIndex++; + } + } else { + scrollUpdate = false; + return text; + } + + return randomtext; +} + +/* +======================== +idSWFTextInstance::StartRandomText +======================== +*/ +bool idSWFTextInstance::NeedsSoundPlayed() { + if ( soundClip.IsEmpty() ) { + return false; + } + + return needsSoundUpdate; +} + +/* +======================== +idSWFTextInstance::StartRandomText +======================== +*/ +void idSWFTextInstance::StartRandomText( int time ) { + generatingText = true; + textSpotsVisible = 0; + randomtext = ""; + triggerGenerate = false; + startRndTime = time; + rndTime = time; + rnd.SetSeed( time ); + rndSpotsVisible = 0; + + text = idLocalization::GetString( text ); + lengthCalculated = false; + + for( int index = 0; index < text.Length(); ++index ) { + if ( text[index] == ' ' ) { + randomtext.Append( " " ); + } else { + randomtext.Append( "." ); + rndSpotsVisible++; + } + } +} + +/* +======================== +idSWFTextInstance::GetRandomText +======================== +*/ +idStr idSWFTextInstance::GetRandomText( int time ) { + + if ( triggerGenerate ) { + return " "; + } else if ( time - startRndTime < renderDelay ) { + return " "; + } else if ( generatingText ) { + if ( rndSpotsVisible > 0 ) { + + int waitTime = swf_textRndLetterSpeed.GetInteger(); + + if ( randomtext.Length() >= 10 ) { + waitTime = waitTime / 3; + } + + if ( time - rndTime >= waitTime ) { + rndTime = time; + + int spotIndex = rnd.RandomInt( rndSpotsVisible ); + int cIndex = 0; + for( int c = 0; c < randomtext.Length(); ++ c ) { + + if ( c >= text.Length() ) { + rndSpotsVisible = 0; + break; + } + + if ( randomtext[c] == '.' ) { + cIndex++; + } + + if ( cIndex == spotIndex ) { + + bool useCaps = false; + + if ( c - 1 >= 0 && text[c-1] == ' ' ) { + useCaps = true; + } else if ( c == 0 ) { + useCaps = true; + } + + if ( useCaps || renderMode == SWF_TEXT_RENDER_RANDOM_APPEAR_CAPS ) { + randomtext[c] = rnd.RandomInt( 'Z' - 'A' ) + 'A'; + } else { + randomtext[c] = rnd.RandomInt( 'z' - 'a' ) + 'a'; + } + + rndSpotsVisible--; + if ( !soundClip.IsEmpty() ) { + needsSoundUpdate = true; + } + break; + } + } + } + } else if ( rndSpotsVisible == 0 && textSpotsVisible < text.Length() ) { + if ( textSpotsVisible >= randomtext.Length() ) { + textSpotsVisible++; + } else { + if ( time - rndTime >= swf_textRndLetterSpeed.GetInteger() ) { + rndTime = time; + randomtext[textSpotsVisible] = text[textSpotsVisible]; + textSpotsVisible++; + if ( !soundClip.IsEmpty() ) { + needsSoundUpdate = true; + } + } + } + } else { + return " "; + } + } else { + return text; + } + + if ( rndSpotsVisible == 0 && textSpotsVisible == text.Length() ) { + generatingText = false; + } + + return randomtext; +} + +/* +============================================== +SUBTITLE FUNCTIONALITY +============================================== +*/ + +/* +============================================== +idSWFTextInstance::SwitchSubtitleText +============================================== +*/ +void idSWFTextInstance::SwitchSubtitleText( int time ) { + subNeedsSwitch = false; +} + +void idSWFTextInstance::SetSubNextStartIndex( int value ) { + subNextStartIndex = value; +} + +/* +============================================== +idSWFTextInstance::UpdateSubtitle +============================================== +*/ +bool idSWFTextInstance::UpdateSubtitle( int time ) { + + if ( subForceKillQueued ) { + subForceKillQueued = false; + subForceKill = true; + subKillTimeDelay = time + swf_subtitleExtraTime.GetInteger(); + } + + if ( subUpdating && !subForceKill ) { + if ( ( time >= subSwitchTime && !subNeedsSwitch ) || ( !subNeedsSwitch && subInitialLine ) ) { + //idLib::Printf( "SWITCH TIME %d / %d \n", time, subSwitchTime ); + + if ( subInitialLine && subtitleTimingInfo.Num() > 0 ) { + if ( subStartTime == -1 ) { + subStartTime = time - 600; + } + if ( time < subStartTime + subtitleTimingInfo[0].startTime ) { + return true; + } else { + text = subtitleText;//subtitleText = ""; + subInitialLine = false; + } + } + + if ( subNextStartIndex + 1 >= text.Length( ) ) { + subForceKillQueued = true; + } else { + subCharStartIndex = subNextStartIndex; + //subtitleText.CopyRange( text, subCharStartIndex, subCharEndIndex ); + subNeedsSwitch = true; + } + } + } + + if ( subForceKill ) { + if ( time >= subKillTimeDelay ) { + subForceKill = false; + return false; + } + } + + return true; +} + +/* +============================================== +idSWFTextInstance::SubtitleComplete +============================================== +*/ +void idSWFTextInstance::SetSubEndIndex( int endChar, int time ) { + subCharEndIndex = endChar; + if ( subCharEndIndex + 1 >= text.Length() ) { + LastWordChanged( subtitleTimingInfo.Num(), time ); + } +} + +/* +============================================== +idSWFTextInstance::SubtitleComplete +============================================== +*/ +void idSWFTextInstance::SubtitleComplete() { + subInitialLine = true; + subUpdating = false; + isSubtitle = false; + subNeedsSwitch = false; + subCharDisplayTime = 0; + subForceKillQueued = false; + subForceKill = false; + subKillTimeDelay = 0; + subSwitchTime = 0; + subLastWordIndex = 0; + subPrevLastWordIndex = 0; + subStartTime = -1; + subSpeaker = ""; + subtitleText = ""; + text = ""; + subtitleTimingInfo.Clear(); +} + +/* +============================================== +idSWFTextInstance::LastWordChanged +============================================== +*/ +void idSWFTextInstance::LastWordChanged( int wordCount, int time ) { + if ( subPrevLastWordIndex + wordCount >= subtitleTimingInfo.Num() ) { + subLastWordIndex = subtitleTimingInfo.Num() - 1; + } else { + subLastWordIndex = subPrevLastWordIndex + wordCount - 1; + } + + if ( subStartTime == -1 ) { + if ( subtitleTimingInfo.Num() > 0 ) { + subStartTime = time + subtitleTimingInfo[0].startTime; + } else { + subStartTime = time; + } + } + + subSwitchTime = subStartTime + subtitleTimingInfo[subLastWordIndex].startTime;// - swf_subtitleEarlyTrans.GetInteger(); + //idLib::Printf( "switchtime set 1 %d last word %d\n", subSwitchTime, subLastWordIndex ); +} + +/* +============================================== +idSWFTextInstance::GetSubtitleBreak +============================================== +*/ +int idSWFTextInstance::GetApporoximateSubtitleBreak( int time ) { + + int wordIndex = subLastWordIndex; + bool setSwitchTime = false; + + if ( subStartTime == -1 ) { + subStartTime = time; + } + + if ( time >= subSwitchTime ) { + subPrevLastWordIndex = subLastWordIndex; + for( int i = wordIndex; i < subtitleTimingInfo.Num(); ++i ) { + if ( subtitleTimingInfo[i].forceBreak ) { + if ( i + 1 < subtitleTimingInfo.Num() ) { + subSwitchTime = subStartTime + subtitleTimingInfo[i+1].startTime;// - swf_subtitleEarlyTrans.GetInteger(); + //idLib::Printf( "switchtime set 2 %d\n", subSwitchTime ); + subLastWordIndex = i; + setSwitchTime = true; + break; + } else { + subSwitchTime = subStartTime + subtitleTimingInfo[i].startTime;// - swf_subtitleEarlyTrans.GetInteger(); + //idLib::Printf( "switchtime set 3 %d\n", subSwitchTime ); + subLastWordIndex = i; + setSwitchTime = true; + break; + } + } else { + int timeSpan = subtitleTimingInfo[i].startTime - subtitleTimingInfo[wordIndex].startTime; + if ( timeSpan > swf_subtitleLengthGuess.GetInteger() ) { + if ( i - 1 >= 0 ) { + subSwitchTime = subStartTime + subtitleTimingInfo[i].startTime;// - swf_subtitleEarlyTrans.GetInteger(); + //idLib::Printf( "switchtime set 4 %d\n", subSwitchTime ); + subLastWordIndex = i - 1; + } else { + subSwitchTime = subStartTime + subtitleTimingInfo[i].startTime;// - swf_subtitleEarlyTrans.GetInteger(); + //idLib::Printf( "switchtime set 5 %d\n", subSwitchTime ); + subLastWordIndex = i; + } + setSwitchTime = true; + break; + } + } + } + + if ( !setSwitchTime && subtitleTimingInfo.Num() > 0 ) { + subSwitchTime = subStartTime + subtitleTimingInfo[ subtitleTimingInfo.Num() - 1 ].startTime;// - swf_subtitleEarlyTrans.GetInteger(); + //idLib::Printf( "switchtime set 6 %d\n", subSwitchTime ); + subLastWordIndex = subtitleTimingInfo.Num(); + } + } + + return subLastWordIndex; +} + + +/* +============================================== +idSWFTextInstance::SubtitleComplete +============================================== +*/ +void idSWFTextInstance::SubtitleCleanup() { + subSourceID = -1; + subAlign = -1; + text = ""; +} + +/* +============================================== +idSWFTextInstance::SetStrokeInfo +============================================== +*/ +void idSWFTextInstance::SetStrokeInfo( bool use, float strength, float weight ) { + useStroke = use; + if ( use ) { + strokeWeight = weight; + strokeStrength = strength; + } +} + +/* +============================================== +idSWFTextInstance::CalcMaxScroll +============================================== +*/ +int idSWFTextInstance::CalcMaxScroll( int numLines ) { + + if ( numLines != -1 ) { + if ( numLines < 0 ) { + numLines = 0; + } + maxscroll = numLines; + return maxscroll; + } + + const idSWFEditText * shape = editText; + if ( !( shape->flags & SWF_ET_MULTILINE ) ) { + return 0; + } + + if ( swf == NULL ) { + return 0; + } + + idSWFDictionaryEntry * fontEntry = swf->FindDictionaryEntry( shape->fontID, SWF_DICT_FONT ); + if ( fontEntry == NULL ) { + return 0; + } + + idSWFFont * swfFont = fontEntry->font; + if ( swfFont == NULL ) { + return 0; + } + + const idFont * fontInfo = swfFont->fontID; + if ( fontInfo == NULL ) { + return 0; + } + + idStr textCheck; + if ( variable.IsEmpty() ) { + textCheck = idLocalization::GetString( text ); + } else { + textCheck = idLocalization::GetString( variable ); + } + + if ( textCheck.IsEmpty() ) { + return 0; + } + + float x = bounds.tl.x; + float y = bounds.tl.y; + + idList< idStr > textLines; + idStr * currentLine = &textLines.Alloc(); + + // tracks the last breakable character we found + int lastbreak = 0; + float lastbreakX = 0; + int charIndex = 0; + + if ( IsSubtitle() ) { + charIndex = GetSubStartIndex(); + } + + while ( charIndex < textCheck.Length() ) { + if ( textCheck[ charIndex ] == '\n' ) { + if ( shape->flags & SWF_ET_MULTILINE ) { + currentLine->Append( '\n' ); + x = bounds.tl.x; + y += linespacing; + currentLine = &textLines.Alloc(); + lastbreak = 0; + charIndex++; + continue; + } else { + break; + } + } + int glyphStart = charIndex; + uint32 tc = textCheck.UTF8Char( charIndex ); + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, tc, glyph ); + float glyphSkip = glyph.xSkip; + if ( HasStroke() ) { + glyphSkip += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * GetStrokeWeight() * glyphScale ); + } + + if ( x + glyphSkip > bounds.br.x ) { + if ( shape->flags & ( SWF_ET_MULTILINE | SWF_ET_WORDWRAP ) ) { + if ( lastbreak > 0 ) { + int curLineIndex = currentLine - &textLines[0]; + idStr * newline = &textLines.Alloc(); + currentLine = &textLines[ curLineIndex ]; + if ( maxLines == 1 ) { + currentLine->CapLength( currentLine->Length() - 3 ); + currentLine->Append( "..." ); + break; + } else { + *newline = currentLine->c_str() + lastbreak; + currentLine->CapLength( lastbreak ); + currentLine = newline; + x -= lastbreakX; + } + } else { + currentLine = &textLines.Alloc(); + x = bounds.tl.x; + } + lastbreak = 0; + } else { + break; + } + } + while ( glyphStart < charIndex && glyphStart < text.Length() ) { + currentLine->Append( text[ glyphStart++ ] ); + } + x += glyphSkip; + if ( tc == ' ' || tc == '-' ) { + lastbreak = currentLine->Length(); + lastbreakX = x; + } + } + + maxscroll = textLines.Num() - maxLines; + if ( maxscroll < 0 ) { + maxscroll = 0; + } + return maxscroll; +} + +int idSWFTextInstance::CalcNumLines() { + + const idSWFEditText * shape = editText; + if ( !( shape->flags & SWF_ET_MULTILINE ) ) { + return 1; + } + + idSWFDictionaryEntry * fontEntry = swf->FindDictionaryEntry( shape->fontID, SWF_DICT_FONT ); + if ( fontEntry == NULL ) { + return 1; + } + + idStr textCheck; + + if ( variable.IsEmpty() ) { + textCheck = idLocalization::GetString( text ); + } else { + textCheck = idLocalization::GetString( variable ); + } + + if ( textCheck.IsEmpty() ) { + return 1; + } + + if ( swf == NULL ) { + return 1; + } + + idSWFFont * swfFont = fontEntry->font; + float postTransformHeight = SWFTWIP( shape->fontHeight ); + const idFont * fontInfo = swfFont->fontID; + + float glyphScale = postTransformHeight / 48.0f; + + swfRect_t bounds; + bounds.tl.x = ( shape->bounds.tl.x + SWFTWIP( shape->leftMargin ) ); + bounds.br.x = ( shape->bounds.br.x - SWFTWIP( shape->rightMargin ) ); + bounds.tl.y = ( shape->bounds.tl.y + ( 1.15f * glyphScale ) ); + bounds.br.y = ( shape->bounds.br.y ); + + float linespacing = fontInfo->GetAscender( 1.15f * glyphScale ); + if ( shape->leading != 0 ) { + linespacing += ( glyphScale * SWFTWIP( shape->leading ) ); + } + + float x = bounds.tl.x; + int maxLines = idMath::Ftoi( ( bounds.br.y - bounds.tl.y ) / linespacing ); + if ( maxLines == 0 ) { + maxLines = 1; + } + + // tracks the last breakable character we found + int numLines = 1; + int lastbreak = 0; + int charIndex = 0; + + while ( charIndex < textCheck.Length() ) { + if ( textCheck[ charIndex ] == '\n' ) { + if ( numLines == maxLines ) { + return maxLines; + } + numLines++; + lastbreak = 0; + x = bounds.tl.x; + charIndex++; + } else { + uint32 tc = textCheck[ charIndex++ ]; + scaledGlyphInfo_t glyph; + fontInfo->GetScaledGlyph( glyphScale, tc, glyph ); + + float glyphSkip = glyph.xSkip; + if ( useStroke ) { + glyphSkip += ( swf_textStrokeSizeGlyphSpacer.GetFloat() * strokeWeight * glyphScale ); + } + + if ( x + glyphSkip > bounds.br.x ) { + if ( numLines == maxLines ) { + return maxLines; + } else { + numLines++; + if ( lastbreak != 0 ) { + charIndex = charIndex - ( charIndex - lastbreak ); + } + x = bounds.tl.x; + lastbreak = 0; + } + } else { + x += glyphSkip; + if ( tc == ' ' || tc == '-' ) { + lastbreak = charIndex; + } + } + } + } + + return numLines; +} + +/* +======================== +idSWFScriptObject_TextInstancePrototype +======================== +*/ + +#define SWF_TEXT_FUNCTION_DEFINE( x ) idSWFScriptVar idSWFScriptObject_TextInstancePrototype::idSWFScriptFunction_##x::Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) +#define SWF_TEXT_NATIVE_VAR_DEFINE_GET( x ) idSWFScriptVar idSWFScriptObject_TextInstancePrototype::idSWFScriptNativeVar_##x::Get( class idSWFScriptObject * object ) +#define SWF_TEXT_NATIVE_VAR_DEFINE_SET( x ) void idSWFScriptObject_TextInstancePrototype::idSWFScriptNativeVar_##x::Set( class idSWFScriptObject * object, const idSWFScriptVar & value ) + +#define SWF_TEXT_PTHIS_FUNC( x ) idSWFTextInstance * pThis = thisObject ? thisObject->GetText() : NULL; if ( !verify( pThis != NULL ) ) { idLib::Warning( "SWF: tried to call " x " on NULL edittext" ); return idSWFScriptVar(); } +#define SWF_TEXT_PTHIS_GET( x ) idSWFTextInstance * pThis = object ? object->GetText() : NULL; if ( pThis == NULL ) { return idSWFScriptVar(); } +#define SWF_TEXT_PTHIS_SET( x ) idSWFTextInstance * pThis = object ? object->GetText() : NULL; if ( pThis == NULL ) { return; } + +#define SWF_TEXT_FUNCTION_SET( x ) scriptFunction_##x.AddRef(); Set( #x, &scriptFunction_##x ); +#define SWF_TEXT_NATIVE_VAR_SET( x ) SetNative( #x, &swfScriptVar_##x ); + +idSWFScriptObject_TextInstancePrototype::idSWFScriptObject_TextInstancePrototype() { + + SWF_TEXT_FUNCTION_SET( onKey ); + SWF_TEXT_FUNCTION_SET( onChar ); + SWF_TEXT_FUNCTION_SET( generateRnd ); + SWF_TEXT_FUNCTION_SET( calcNumLines ); + SWF_TEXT_FUNCTION_SET( clearTimingInfo ); + + SWF_TEXT_NATIVE_VAR_SET( text ); + SWF_TEXT_NATIVE_VAR_SET( _textLength ); // only works on single lines of text not multiline + SWF_TEXT_NATIVE_VAR_SET( autoSize ); + SWF_TEXT_NATIVE_VAR_SET( dropShadow ); + SWF_TEXT_NATIVE_VAR_SET( _stroke ); + SWF_TEXT_NATIVE_VAR_SET( _strokeStrength ); + SWF_TEXT_NATIVE_VAR_SET( _strokeWeight ); + SWF_TEXT_NATIVE_VAR_SET( variable ); + SWF_TEXT_NATIVE_VAR_SET( _alpha ); + SWF_TEXT_NATIVE_VAR_SET( textColor ); + SWF_TEXT_NATIVE_VAR_SET( _visible ); + SWF_TEXT_NATIVE_VAR_SET( selectionStart ); + SWF_TEXT_NATIVE_VAR_SET( selectionEnd ); + SWF_TEXT_NATIVE_VAR_SET( scroll ); + SWF_TEXT_NATIVE_VAR_SET( maxscroll ); + SWF_TEXT_NATIVE_VAR_SET( isTooltip ); + SWF_TEXT_NATIVE_VAR_SET( mode ); + SWF_TEXT_NATIVE_VAR_SET( delay ); + SWF_TEXT_NATIVE_VAR_SET( renderSound ); + SWF_TEXT_NATIVE_VAR_SET( updateScroll ); + SWF_TEXT_NATIVE_VAR_SET( subtitle ); + SWF_TEXT_NATIVE_VAR_SET( subtitleAlign ); + SWF_TEXT_NATIVE_VAR_SET( subtitleSourceID ); + SWF_TEXT_NATIVE_VAR_SET( subtitleSpeaker ); + + SWF_TEXT_FUNCTION_SET( subtitleSourceCheck ); + SWF_TEXT_FUNCTION_SET( subtitleStart ); + SWF_TEXT_FUNCTION_SET( subtitleLength ); + SWF_TEXT_FUNCTION_SET( killSubtitle ); + SWF_TEXT_FUNCTION_SET( forceKillSubtitle ); + SWF_TEXT_FUNCTION_SET( subLastLine ); + SWF_TEXT_FUNCTION_SET( addSubtitleInfo ); + SWF_TEXT_FUNCTION_SET( terminateSubtitle ); +} + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( text ) { + SWF_TEXT_PTHIS_GET( "text" ); + return pThis->text; +} + +SWF_TEXT_NATIVE_VAR_DEFINE_SET( text ) { + SWF_TEXT_PTHIS_SET( "text " ); + pThis->text = idLocalization::GetString( value.ToString() ); + if ( pThis->text.IsEmpty() ) { + pThis->selectionEnd = -1; + pThis->selectionStart = -1; + pThis->inputTextStartChar = 0; + } + pThis->lengthCalculated = false; +} + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( autoSize ) { + SWF_TEXT_PTHIS_GET( "autoSize" ); + return ( pThis->editText->flags & SWF_ET_AUTOSIZE ) != 0; +} + +SWF_TEXT_NATIVE_VAR_DEFINE_SET( autoSize ) { + SWF_TEXT_PTHIS_SET( "autoSize" ); + if ( value.ToBool() ) { + pThis->editText->flags |= SWF_ET_AUTOSIZE; + } else { + pThis->editText->flags &= ~SWF_ET_AUTOSIZE; + } + pThis->lengthCalculated = false; +} + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( dropShadow ) { SWF_TEXT_PTHIS_GET( "dropShadow" ); return pThis->useDropShadow; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( dropShadow ) { SWF_TEXT_PTHIS_SET( "dropShadow" ); pThis->useDropShadow = value.ToBool(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( _stroke ) { SWF_TEXT_PTHIS_GET( "_stroke" ); return pThis->useStroke; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( _stroke ) { SWF_TEXT_PTHIS_SET( "_stroke" ); pThis->useStroke = value.ToBool(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( _strokeStrength ) { SWF_TEXT_PTHIS_GET( "_strokeStrength" ); return pThis->strokeStrength; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( _strokeStrength ) { SWF_TEXT_PTHIS_SET( "_strokeStrength" ); pThis->strokeStrength = value.ToFloat(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( _strokeWeight ) { SWF_TEXT_PTHIS_GET( "_strokeWeight" ); return pThis->strokeWeight; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( _strokeWeight ) { SWF_TEXT_PTHIS_SET( "_strokeWeight" ); pThis->strokeWeight = value.ToFloat(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( variable ) { SWF_TEXT_PTHIS_GET( "variable" ); return pThis->variable; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( variable ) { SWF_TEXT_PTHIS_SET( "variable" ); pThis->variable = value.ToString(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( _alpha ) { SWF_TEXT_PTHIS_GET( "_alpha" ); return pThis->color.a / 255.0f; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( _alpha ) { SWF_TEXT_PTHIS_SET( "_alpha" ); pThis->color.a = idMath::Ftob( value.ToFloat() * 255.0f ); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( _visible ) { SWF_TEXT_PTHIS_GET( "_visible" ); return pThis->visible; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( _visible ) { SWF_TEXT_PTHIS_SET( "_visible" ); pThis->visible = value.ToBool(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( selectionStart ) { SWF_TEXT_PTHIS_GET( "selectionStart" ); return pThis->selectionStart; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( selectionStart ) { SWF_TEXT_PTHIS_SET( "selectionStart" ); pThis->selectionStart = value.ToInteger(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( selectionEnd ) { SWF_TEXT_PTHIS_GET( "selectionEnd" ); return pThis->selectionEnd; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( selectionEnd ) { SWF_TEXT_PTHIS_SET( "selectionEnd" ); pThis->selectionEnd = value.ToInteger(); } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( isTooltip ) { SWF_TEXT_PTHIS_SET( "isTooltip" ); pThis->tooltip = value.ToBool(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( isTooltip ) { SWF_TEXT_PTHIS_GET( "isTooltip" ); return pThis->tooltip; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( delay ) { SWF_TEXT_PTHIS_SET( "delay" ); pThis->renderDelay = value.ToInteger(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( delay ) { SWF_TEXT_PTHIS_GET( "delay" ); return pThis->renderDelay; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( renderSound ) { SWF_TEXT_PTHIS_SET( "renderSound" ); pThis->soundClip = value.ToString(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( renderSound ) { SWF_TEXT_PTHIS_GET( "renderSound" ); return pThis->soundClip; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( updateScroll ) { SWF_TEXT_PTHIS_SET( "updateScroll" ); pThis->scrollUpdate = value.ToBool(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( updateScroll ) { SWF_TEXT_PTHIS_GET( "updateScroll" ); return pThis->scrollUpdate; } + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( mode ) { SWF_TEXT_PTHIS_GET( "mode" ); return pThis->renderMode; } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( scroll ) { SWF_TEXT_PTHIS_GET( "scroll" ); return pThis->scroll; } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( maxscroll ) { SWF_TEXT_PTHIS_GET( "maxscroll" ); return pThis->maxscroll; } + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( _textLength ) { + SWF_TEXT_PTHIS_GET( "_textLength" ); + return pThis->GetTextLength(); +} + +SWF_TEXT_NATIVE_VAR_DEFINE_SET( mode ) { + SWF_TEXT_PTHIS_SET( "mode" ); + + int mode = value.ToInteger(); + + if ( mode >= (int)SWF_TEXT_RENDER_MODE_COUNT || mode < 0 ) { + mode = SWF_TEXT_RENDER_NORMAL; + } + + pThis->renderMode = swfTextRenderMode_t(mode); +} + +SWF_TEXT_NATIVE_VAR_DEFINE_SET( scroll ) { + SWF_TEXT_PTHIS_SET( "scroll" ); + + int time = Sys_Milliseconds(); + if ( time >= pThis->scrollTime ) { + pThis->scrollTime = Sys_Milliseconds() + swf_textScrollSpeed.GetInteger(); + pThis->scroll = value.ToInteger(); + } +} + +SWF_TEXT_NATIVE_VAR_DEFINE_SET( maxscroll ) { + SWF_TEXT_PTHIS_SET( "maxscroll" ); + pThis->maxscroll = value.ToInteger(); +} + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( textColor ) { + SWF_TEXT_PTHIS_GET( "textColor" ); + + int r = ( pThis->color.r << 16 ); + int g = ( pThis->color.g << 8 ); + int b = pThis->color.b; + + int textColor = r | g | b; + + return textColor; +} + +SWF_TEXT_NATIVE_VAR_DEFINE_SET( textColor ) { + SWF_TEXT_PTHIS_SET( "textColor" ); + + int textColor = value.ToInteger(); + int r = ( textColor >> 16 ) & 0xFF; + int g = ( textColor >> 8 ) & 0x00FF; + int b = textColor & 0x0000FF; + + pThis->color.r = r; + pThis->color.g = g; + pThis->color.b = b; +} + +SWF_TEXT_FUNCTION_DEFINE( clearTimingInfo ) { + SWF_TEXT_PTHIS_FUNC( "clearTimingInfo" ); + pThis->subtitleTimingInfo.Clear(); + return idSWFScriptVar(); +} + + +SWF_TEXT_FUNCTION_DEFINE( generateRnd ) { + SWF_TEXT_PTHIS_FUNC( "generateRnd" ); + pThis->triggerGenerate = true; + pThis->rndSpotsVisible = -1; + pThis->generatingText = false; + return idSWFScriptVar(); +} + +SWF_TEXT_FUNCTION_DEFINE( calcNumLines ) { + SWF_TEXT_PTHIS_FUNC( "calcNumLines" ); + + return pThis->CalcNumLines(); +} + +SWF_TEXT_FUNCTION_DEFINE( onKey ) { + SWF_TEXT_PTHIS_FUNC( "onKey" ); + + int keyCode = parms[0].ToInteger(); + bool keyDown = parms[1].ToBool(); + + if ( keyDown ) { + switch ( keyCode ) { + case K_LSHIFT: + case K_RSHIFT: { + pThis->shiftHeld = true; + break; + } + case K_BACKSPACE: + case K_DEL: { + if ( pThis->selectionStart == pThis->selectionEnd ) { + if ( keyCode == K_BACKSPACE ) { + pThis->selectionStart = pThis->selectionEnd - 1; + } else { + pThis->selectionEnd = pThis->selectionStart + 1; + } + } + int start = Min( pThis->selectionStart, pThis->selectionEnd ); + int end = Max( pThis->selectionStart, pThis->selectionEnd ); + idStr left = pThis->text.Left( Max( start, 0 ) ); + idStr right = pThis->text.Right( Max( pThis->text.Length() - end, 0 ) ); + pThis->text = left + right; + pThis->selectionStart = start; + pThis->selectionEnd = start; + break; + } + case K_LEFTARROW: { + if ( pThis->selectionEnd > 0 ) { + pThis->selectionEnd--; + if ( !pThis->shiftHeld ) { + pThis->selectionStart = pThis->selectionEnd; + } + } + break; + } + case K_RIGHTARROW: { + if ( pThis->selectionEnd < pThis->text.Length() ) { + pThis->selectionEnd++; + if ( !pThis->shiftHeld ) { + pThis->selectionStart = pThis->selectionEnd; + } + } + break; + } + case K_HOME: { + pThis->selectionEnd = 0; + if ( !pThis->shiftHeld ) { + pThis->selectionStart = pThis->selectionEnd; + } + break; + } + case K_END: { + pThis->selectionEnd = pThis->text.Length(); + if ( !pThis->shiftHeld ) { + pThis->selectionStart = pThis->selectionEnd; + } + break; + } + } + } else { + if ( keyCode == K_LSHIFT || keyCode == K_RSHIFT ) { + pThis->shiftHeld = false; + } + } + return true; +} + +SWF_TEXT_FUNCTION_DEFINE( onChar ) { + SWF_TEXT_PTHIS_FUNC( "onChar" ); + + int keyCode = parms[0].ToInteger(); + + if ( keyCode < 32 || keyCode == 127 ) { + return false; + } + + char letter = ( char )keyCode; + // assume ` is meant for the console + if ( letter == '`' ) { + return false; + } + if ( pThis->selectionStart != pThis->selectionEnd ) { + int start = Min( pThis->selectionStart, pThis->selectionEnd ); + int end = Max( pThis->selectionStart, pThis->selectionEnd ); + idStr left = pThis->text.Left( Max( start, 0 ) ); + idStr right = pThis->text.Right( Max( pThis->text.Length() - end, 0 ) ); + pThis->text = left + right; + pThis->selectionStart = start; + + pThis->text.Clear(); + pThis->text.Append( left ); + pThis->text.Append( letter ); + pThis->text.Append( right ); + pThis->selectionStart++; + } else if ( pThis->selectionStart < swf_textMaxInputLength.GetInteger() ) { + if ( pThis->selectionStart < 0 ) { + pThis->selectionStart = 0; + } + pThis->text.Insert( letter, pThis->selectionStart++ ); + } + pThis->selectionEnd = pThis->selectionStart; + return true; +} + +SWF_TEXT_NATIVE_VAR_DEFINE_GET( subtitle ) { SWF_TEXT_PTHIS_GET( "subtitle" ); return pThis->isSubtitle; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( subtitle ) { SWF_TEXT_PTHIS_SET( "subtitle" ); pThis->isSubtitle = value.ToBool(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( subtitleAlign ) { SWF_TEXT_PTHIS_GET( "subtitleAlign" ); return pThis->subAlign; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( subtitleAlign ) { SWF_TEXT_PTHIS_SET( "subtitleAlign" ); pThis->subAlign = value.ToInteger(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( subtitleSourceID ) { SWF_TEXT_PTHIS_GET( "subtitleSourceID" ); return pThis->subSourceID; } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( subtitleSourceID ) { SWF_TEXT_PTHIS_SET( "subtitleSourceID" ); pThis->subSourceID = value.ToInteger(); } +SWF_TEXT_NATIVE_VAR_DEFINE_GET( subtitleSpeaker ) { SWF_TEXT_PTHIS_GET( "subtitleSpeaker" ); return pThis->subSpeaker.c_str(); } +SWF_TEXT_NATIVE_VAR_DEFINE_SET( subtitleSpeaker ) { SWF_TEXT_PTHIS_SET( "subtitleSpeaker" ); pThis->subSpeaker = value.ToString(); } + +SWF_TEXT_FUNCTION_DEFINE( subtitleLength ) { + SWF_TEXT_PTHIS_FUNC( "subtitleLength" ); + pThis->subLength = parms[0].ToInteger(); + return idSWFScriptVar(); +} + +SWF_TEXT_FUNCTION_DEFINE( subtitleSourceCheck ) { + SWF_TEXT_PTHIS_FUNC( "subtitleSourceCheck" ); + + int idCheck = parms[0].ToInteger(); + + if ( pThis->subSourceID == -1 ) { + pThis->subSourceID = idCheck; + return 1; + } + + if ( idCheck == pThis->subSourceID ) { // || pThis->subForceKill ) { + pThis->SubtitleComplete(); + pThis->subSourceID = idCheck; + return -1; + } + + return 0; +} + +SWF_TEXT_FUNCTION_DEFINE( subtitleStart ) { + SWF_TEXT_PTHIS_FUNC( "subtitleStart" ); + pThis->subUpdating = true; + pThis->subNeedsSwitch = false; + pThis->subForceKillQueued = false; + pThis->subForceKill = false; + pThis->subKillTimeDelay = 0; + // trickery to swap the text so subtitles don't show until they should + pThis->subtitleText = pThis->text; + pThis->text = ""; + pThis->subCharStartIndex = 0; + pThis->subNextStartIndex = 0; + pThis->subCharEndIndex = 0; + pThis->subSwitchTime = 0; + pThis->subLastWordIndex = 0; + pThis->subPrevLastWordIndex = 0; + pThis->subStartTime = -1; + pThis->subInitialLine = true; + return idSWFScriptVar(); +} + +SWF_TEXT_FUNCTION_DEFINE( forceKillSubtitle ) { + SWF_TEXT_PTHIS_FUNC( "forceKillSubtitle" ); + pThis->subForceKill = true; + pThis->subKillTimeDelay = 0; + return idSWFScriptVar(); +} + +SWF_TEXT_FUNCTION_DEFINE( killSubtitle ) { + SWF_TEXT_PTHIS_FUNC( "killSubtitle" ); + pThis->subForceKillQueued = true; + //pThis->SubtitleComplete(); + return idSWFScriptVar(); +} + +SWF_TEXT_FUNCTION_DEFINE( terminateSubtitle ) { + SWF_TEXT_PTHIS_FUNC( "terminateSubtitle" ); + pThis->SubtitleComplete(); + pThis->SubtitleCleanup(); + return idSWFScriptVar(); +} + +SWF_TEXT_FUNCTION_DEFINE( subLastLine ) { + SWF_TEXT_PTHIS_FUNC( "subLastLine" ); + idStr lastLine; + int len = pThis->subCharEndIndex - pThis->subCharStartIndex; + pThis->text.Mid( pThis->subCharStartIndex, len, lastLine ); + return lastLine; +} + +SWF_TEXT_FUNCTION_DEFINE( addSubtitleInfo ) { + SWF_TEXT_PTHIS_FUNC( "addSubtitleInfo" ); + + if ( parms.Num() != 3 ) { + return idSWFScriptVar(); + } + + subTimingWordData_t info; + info.phrase = parms[0].ToString(); + info.startTime = parms[1].ToInteger(); + info.forceBreak = parms[2].ToBool(); + + pThis->subtitleTimingInfo.Append( info ); + return idSWFScriptVar(); +} diff --git a/neo/swf/SWF_TextInstance.h b/neo/swf/SWF_TextInstance.h new file mode 100644 index 00000000..511b846a --- /dev/null +++ b/neo/swf/SWF_TextInstance.h @@ -0,0 +1,251 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_TEXTINSTANCE_H__ +#define __SWF_TEXTINSTANCE_H__ + +struct subTimingWordData_t { + subTimingWordData_t() { + startTime = 0; + forceBreak = false; + } + + idStr phrase; + int startTime; + bool forceBreak; +}; + +class idSWFTextInstance { +public: + idSWFTextInstance(); + ~idSWFTextInstance(); + + void Init( idSWFEditText * editText, idSWF * _swf ); + + idSWFScriptObject * GetScriptObject() { return &scriptObject; } + + bool GetHasDropShadow() { return useDropShadow; } + bool HasStroke() { return useStroke; } + float GetStrokeStrength() { return strokeStrength; } + float GetStrokeWeight() { return strokeWeight; } + + // used for when text has random render mode set + bool IsGeneratingRandomText() { return generatingText; } + void StartRandomText( int time ); + idStr GetRandomText( int time ); + void StartParagraphText( int time ); + idStr GetParagraphText( int time ); + bool NeedsGenerateRandomText() { return triggerGenerate; } + bool NeedsSoundPlayed(); + void ClearPlaySound() { needsSoundUpdate = false; } + idStr GetSoundClip() { return soundClip; } + void SetIgnoreColor( bool ignore ) { ignoreColor = ignore; } + + void SetStrokeInfo( bool use, float strength = 0.75f, float weight = 1.75f ); + int CalcMaxScroll( int numLines = -1 ); + int CalcNumLines(); + + // subtitle functions + void SwitchSubtitleText( int time ); + bool UpdateSubtitle( int time ); + bool IsSubtitle() { return isSubtitle; } + bool IsUpdatingSubtitle() { return subUpdating; } + void SetSubEndIndex( int endChar, int time ); + int GetLastWordIndex() { return subLastWordIndex; } + int GetPrevLastWordIndex() { return subPrevLastWordIndex; } + void LastWordChanged( int wordCount, int time ); + void SetSubStartIndex( int value ) { subCharStartIndex = value; } + int GetSubEndIndex() { return subCharEndIndex; } + int GetSubStartIndex() { return subCharStartIndex; } + void SetSubNextStartIndex( int value ); + int GetApporoximateSubtitleBreak( int time ); + bool SubNeedsSwitch() { return subNeedsSwitch; } + idStr GetPreviousText() { return subtitleText.c_str(); } + void SubtitleComplete(); + int GetSubAlignment() { return subAlign; } + idStr GetSpeaker() { return subSpeaker.c_str(); } + void SubtitleCleanup(); + float GetTextLength(); + int GetInputStartChar( ) { return inputTextStartChar; } + void SetInputStartCharacter( int c ) { inputTextStartChar = c; } + + const idSWFEditText * GetEditText() const { return editText; } + void SetText( idStr val ) { text = val; lengthCalculated = false; } + + // Removing the private access control statement due to cl 214702 + // Apparently MS's C++ compiler supports the newer C++ standard, and GCC supports C++03 + // In the new C++ standard, nested members of a friend class have access to private/protected members of the class granting friendship + // In C++03, nested members defined in a friend class do NOT have access to private/protected members of the class granting friendship + + idSWFEditText * editText; + idSWF * swf; + + // this text instance's script object + idSWFScriptObject scriptObject; + + idStr text; + idStr randomtext; + idStr variable; + swfColorRGBA_t color; + + bool visible; + bool tooltip; + + int selectionStart; + int selectionEnd; + bool ignoreColor; + + int scroll; + int scrollTime; + int maxscroll; + int maxLines; + float glyphScale; + swfRect_t bounds; + float linespacing; + + bool shiftHeld; + int lastInputTime; + + bool useDropShadow; + bool useStroke; + + float strokeStrength; + float strokeWeight; + + int textLength; + bool lengthCalculated; + + swfTextRenderMode_t renderMode; + bool generatingText; + int rndSpotsVisible; + int rndSpacesVisible; + int charMultiplier; + int textSpotsVisible; + int rndTime; + int startRndTime; + int prevReplaceIndex; + bool triggerGenerate; + int renderDelay; + bool scrollUpdate; + idStr soundClip; + bool needsSoundUpdate; + idList indexArray; + idRandom2 rnd; + + // used for subtitles + bool isSubtitle; + int subLength; + int subCharDisplayTime; + int subAlign; + bool subUpdating; + int subCharStartIndex; + int subNextStartIndex; + int subCharEndIndex; + int subDisplayTime; + int subStartTime; + int subSourceID; + idStr subtitleText; + bool subNeedsSwitch; + bool subForceKillQueued; + bool subForceKill; + int subKillTimeDelay; + int subSwitchTime; + int subLastWordIndex; + int subPrevLastWordIndex; + idStr subSpeaker; + bool subWaitClear; + bool subInitialLine; + + // input text + int inputTextStartChar; + + idList< subTimingWordData_t, TAG_SWF > subtitleTimingInfo; +}; + +/* +================================================ +This is the prototype object that all the text instance script objects reference +================================================ +*/ +class idSWFScriptObject_TextInstancePrototype : public idSWFScriptObject { +public: + idSWFScriptObject_TextInstancePrototype(); + + //---------------------------------- + // Native Script Functions + //---------------------------------- +#define SWF_TEXT_FUNCTION_DECLARE( x ) \ + class idSWFScriptFunction_##x : public idSWFScriptFunction_RefCounted { \ + public: \ + void AddRef() {} \ + void Release() {} \ + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ); \ + } scriptFunction_##x; + + SWF_TEXT_FUNCTION_DECLARE( onKey ); + SWF_TEXT_FUNCTION_DECLARE( onChar ); + SWF_TEXT_FUNCTION_DECLARE( generateRnd ); + SWF_TEXT_FUNCTION_DECLARE( calcNumLines ); + + SWF_NATIVE_VAR_DECLARE( text ); + SWF_NATIVE_VAR_DECLARE( autoSize ); + SWF_NATIVE_VAR_DECLARE( dropShadow ); + SWF_NATIVE_VAR_DECLARE( _stroke ); + SWF_NATIVE_VAR_DECLARE( _strokeStrength ); + SWF_NATIVE_VAR_DECLARE( _strokeWeight ); + SWF_NATIVE_VAR_DECLARE( variable ); + SWF_NATIVE_VAR_DECLARE( _alpha ); + SWF_NATIVE_VAR_DECLARE( textColor ); + SWF_NATIVE_VAR_DECLARE( _visible ); + SWF_NATIVE_VAR_DECLARE( scroll ); + SWF_NATIVE_VAR_DECLARE( maxscroll ); + SWF_NATIVE_VAR_DECLARE( selectionStart ); + SWF_NATIVE_VAR_DECLARE( selectionEnd ); + SWF_NATIVE_VAR_DECLARE( isTooltip ); + SWF_NATIVE_VAR_DECLARE( mode ); + SWF_NATIVE_VAR_DECLARE( delay ); + SWF_NATIVE_VAR_DECLARE( renderSound ); + SWF_NATIVE_VAR_DECLARE( updateScroll ); + SWF_NATIVE_VAR_DECLARE( subtitle ); + SWF_NATIVE_VAR_DECLARE( subtitleAlign ); + SWF_NATIVE_VAR_DECLARE( subtitleSourceID ); + SWF_NATIVE_VAR_DECLARE( subtitleSpeaker ); + + SWF_NATIVE_VAR_DECLARE_READONLY( _textLength ); + + SWF_TEXT_FUNCTION_DECLARE( subtitleSourceCheck ); + SWF_TEXT_FUNCTION_DECLARE( subtitleStart ); + SWF_TEXT_FUNCTION_DECLARE( subtitleLength ); + SWF_TEXT_FUNCTION_DECLARE( killSubtitle ); + SWF_TEXT_FUNCTION_DECLARE( forceKillSubtitle ); + SWF_TEXT_FUNCTION_DECLARE( subLastLine ); + SWF_TEXT_FUNCTION_DECLARE( addSubtitleInfo ); + SWF_TEXT_FUNCTION_DECLARE( terminateSubtitle ); + SWF_TEXT_FUNCTION_DECLARE( clearTimingInfo ); +}; + +#endif // !__SWF_TEXTINSTANCE_H__ diff --git a/neo/swf/SWF_Types.h b/neo/swf/SWF_Types.h new file mode 100644 index 00000000..a0ca0ed9 --- /dev/null +++ b/neo/swf/SWF_Types.h @@ -0,0 +1,410 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SWF_TYPES1_H__ +#define __SWF_TYPES1_H__ + +ID_INLINE float SWFTWIP( int twip ) { return twip * ( 1.0f / 20.0f ); } +ID_INLINE float SWFFIXED16( int fixed ) { return fixed * ( 1.0f / 65536.0f ); } +ID_INLINE float SWFFIXED8( int fixed ) { return fixed * ( 1.0f / 256.0f ); } + +struct swfHeader_t { + byte compression; + byte W; + byte S; + byte version; + uint32 fileLength; +}; +struct swfRect_t { + swfRect_t(); + idVec2 tl; + idVec2 br; +}; +struct swfMatrix_t { + swfMatrix_t(); + float xx, yy; + float xy, yx; + float tx, ty; + idVec2 Scale( const idVec2 & in ) const; + idVec2 Transform( const idVec2 & in ) const; + swfMatrix_t Multiply( const swfMatrix_t & a ) const; + swfMatrix_t Inverse() const; + swfMatrix_t & operator=( const swfMatrix_t & a ) { xx = a.xx; yy = a.yy; xy = a.xy; yx = a.yx; tx = a.tx; ty = a.ty; return *this; } +}; +struct swfColorRGB_t { + swfColorRGB_t(); + idVec4 ToVec4() const; + uint8 r, g, b; +}; +struct swfColorRGBA_t : public swfColorRGB_t { + swfColorRGBA_t(); + idVec4 ToVec4() const; + uint8 a; +}; +struct swfLineStyle_t { + swfLineStyle_t(); + uint16 startWidth; + uint16 endWidth; + swfColorRGBA_t startColor; + swfColorRGBA_t endColor; +}; +struct swfGradientRecord_t { + swfGradientRecord_t(); + uint8 startRatio; + uint8 endRatio; + swfColorRGBA_t startColor; + swfColorRGBA_t endColor; +}; +struct swfGradient_t { + swfGradient_t(); + uint8 numGradients; + swfGradientRecord_t gradientRecords[ 16 ]; +}; +struct swfFillStyle_t { + swfFillStyle_t(); + uint8 type; // 0 = solid, 1 = gradient, 4 = bitmap + uint8 subType; // 0 = linear, 2 = radial, 3 = focal; 0 = repeat, 1 = clamp, 2 = near repeat, 3 = near clamp + swfColorRGBA_t startColor; // if type = 0 + swfColorRGBA_t endColor; // if type = 0 + swfMatrix_t startMatrix; // if type > 0 + swfMatrix_t endMatrix; // if type > 0 + swfGradient_t gradient; // if type = 1 + float focalPoint; // if type = 1 and subType = 3 + uint16 bitmapID; // if type = 4 +}; +class idSWFShapeDrawFill { +public: + swfFillStyle_t style; + idList< idVec2, TAG_SWF > startVerts; + idList< idVec2, TAG_SWF > endVerts; + idList< uint16, TAG_SWF > indices; +}; +class idSWFShapeDrawLine { +public: + swfLineStyle_t style; + idList< idVec2, TAG_SWF > startVerts; + idList< idVec2, TAG_SWF > endVerts; + idList< uint16, TAG_SWF > indices; +}; +class idSWFShape { +public: + ~idSWFShape() { + fillDraws.Clear(); + lineDraws.Clear(); + } + swfRect_t startBounds; + swfRect_t endBounds; + idList< idSWFShapeDrawFill, TAG_SWF > fillDraws; + idList< idSWFShapeDrawLine, TAG_SWF > lineDraws; +}; +class idSWFFontGlyph { +public: + idSWFFontGlyph(); + uint16 code; + int16 advance; + idList< idVec2, TAG_SWF > verts; + idList< uint16, TAG_SWF > indices; +}; +class idSWFFont { +public: + idSWFFont(); + class idFont * fontID; + int16 ascent; + int16 descent; + int16 leading; + idList< idSWFFontGlyph, TAG_SWF > glyphs; +}; +class idSWFTextRecord { +public: + idSWFTextRecord(); + uint16 fontID; + swfColorRGBA_t color; + int16 xOffset; + int16 yOffset; + uint16 textHeight; + uint16 firstGlyph; + uint8 numGlyphs; +}; +struct swfGlyphEntry_t { + swfGlyphEntry_t(); + uint32 index; + int32 advance; +}; +class idSWFText { +public: + swfRect_t bounds; + swfMatrix_t matrix; + idList< idSWFTextRecord, TAG_SWF > textRecords; + idList< swfGlyphEntry_t, TAG_SWF > glyphs; +}; +enum swfEditTextFlags_t { + SWF_ET_NONE = 0, + SWF_ET_WORDWRAP = BIT(0), + SWF_ET_MULTILINE = BIT(1), + SWF_ET_PASSWORD = BIT(2), + SWF_ET_READONLY = BIT(3), + SWF_ET_AUTOSIZE = BIT(4), + SWF_ET_BORDER = BIT(5), +}; +enum swfEditTextAlign_t { + SWF_ET_ALIGN_LEFT, + SWF_ET_ALIGN_RIGHT, + SWF_ET_ALIGN_CENTER, + SWF_ET_ALIGN_JUSTIFY +}; +enum swfTextRenderMode_t { + SWF_TEXT_RENDER_NORMAL = 0, + SWF_TEXT_RENDER_RANDOM_APPEAR, + SWF_TEXT_RENDER_RANDOM_APPEAR_CAPS, + SWF_TEXT_RENDER_PARAGRAPH, + SWF_TEXT_RENDER_AUTOSCROLL, + SWF_TEXT_RENDER_MODE_COUNT, +}; + +class idSWFEditText { +public: + idSWFEditText(); + swfRect_t bounds; + uint32 flags; + uint16 fontID; + uint16 fontHeight; + swfColorRGBA_t color; + uint16 maxLength; + swfEditTextAlign_t align; + uint16 leftMargin; + uint16 rightMargin; + uint16 indent; + int16 leading; + idStr variable; + idStr initialText; +}; +struct swfColorXform_t { + swfColorXform_t(); + idVec4 mul; + idVec4 add; + swfColorXform_t Multiply( const swfColorXform_t & a ) const; + swfColorXform_t & operator=( const swfColorXform_t & a ) { mul = a.mul; add = a.add; return *this; } +}; +struct swfDisplayEntry_t { + swfDisplayEntry_t(); + uint16 characterID; + uint16 depth; + uint16 clipDepth; + uint16 blendMode; + swfMatrix_t matrix; + swfColorXform_t cxf; + float ratio; + // if this entry is a sprite, then this will point to the specific instance of that sprite + class idSWFSpriteInstance * spriteInstance; + // if this entry is text, then this will point to the specific instance of the text + class idSWFTextInstance * textInstance; +}; +struct swfRenderState_t { + swfRenderState_t(); + swfMatrix_t matrix; + swfColorXform_t cxf; + const idMaterial * material; + int materialWidth; + int materialHeight; + int activeMasks; + uint8 blendMode; + float ratio; + stereoDepthType_t stereoDepth; +}; + +ID_INLINE swfRect_t::swfRect_t() : +tl( 0.0f, 0.0f ), +br( 0.0f, 0.0f ) +{ +} + +ID_INLINE swfMatrix_t::swfMatrix_t() : +xx( 1.0f ), yy( 1.0f ), +yx( 0.0f ), xy( 0.0f ), +tx( 0.0f ), ty( 0.0f ) +{ +} + +ID_INLINE idVec2 swfMatrix_t::Scale( const idVec2 & in ) const { + return idVec2( ( in.x * xx ) + ( in.y * xy ), + ( in.y * yy ) + ( in.x * yx ) ); +} + +ID_INLINE idVec2 swfMatrix_t::Transform( const idVec2 & in ) const { + return idVec2( ( in.x * xx ) + ( in.y * xy ) + tx, + ( in.y * yy ) + ( in.x * yx ) + ty ); +} + +ID_INLINE swfMatrix_t swfMatrix_t::Inverse() const { + swfMatrix_t inverse; + float det = ( ( xx * yy ) - ( yx * xy ) ); + if ( idMath::Fabs( det ) < idMath::FLT_SMALLEST_NON_DENORMAL ) { + return *this; + } + float invDet = 1.0f / det; + inverse.xx = invDet * yy; + inverse.yx = invDet * -yx; + inverse.xy = invDet * -xy; + inverse.yy = invDet * xx; + //inverse.tx = invDet * ( xy * ty ) - ( yy * tx ); + //inverse.ty = invDet * ( yx * tx ) - ( xx * ty ); + return inverse; +} + +ID_INLINE swfMatrix_t swfMatrix_t::Multiply( const swfMatrix_t & a ) const { + swfMatrix_t result; + result.xx = xx * a.xx + yx * a.xy; + result.yx = xx * a.yx + yx * a.yy; + result.xy = xy * a.xx + yy * a.xy; + result.yy = xy * a.yx + yy * a.yy; + result.tx = tx * a.xx + ty * a.xy + a.tx; + result.ty = tx * a.yx + ty * a.yy + a.ty; + return result; +} + +ID_INLINE swfColorRGB_t::swfColorRGB_t() : +r( 255 ), g( 255 ), b( 255 ) +{ +} + +ID_INLINE idVec4 swfColorRGB_t::ToVec4() const { + return idVec4( r * ( 1.0f / 255.0f ), g * ( 1.0f / 255.0f ), b * ( 1.0f / 255.0f ), 1.0f ); +} + +ID_INLINE swfColorRGBA_t::swfColorRGBA_t() : +a( 255 ) +{ +} + +ID_INLINE idVec4 swfColorRGBA_t::ToVec4() const { + return idVec4( r * ( 1.0f / 255.0f ), g * ( 1.0f / 255.0f ), b * ( 1.0f / 255.0f ), a * ( 1.0f / 255.0f ) ); +} + +ID_INLINE swfLineStyle_t::swfLineStyle_t() : +startWidth( 20 ), +endWidth( 20 ) +{ +} + +ID_INLINE swfGradientRecord_t::swfGradientRecord_t() : +startRatio( 0 ), +endRatio( 0 ) +{ +} + +ID_INLINE swfGradient_t::swfGradient_t() : +numGradients( 0 ) +{ +} + +ID_INLINE swfFillStyle_t::swfFillStyle_t() : +type( 0 ), +subType( 0 ), +focalPoint( 0.0f ), +bitmapID( 0 ) +{ +} + +ID_INLINE swfColorXform_t::swfColorXform_t() : +mul( 1.0f, 1.0f, 1.0f, 1.0f ), +add( 0.0f, 0.0f, 0.0f, 0.0f ) +{ +} + +ID_INLINE swfColorXform_t swfColorXform_t::Multiply( const swfColorXform_t & a ) const { + swfColorXform_t result; + result.mul = mul.Multiply( a.mul ); + result.add = ( add.Multiply( a.mul ) ) + a.add; + return result; +} + +ID_INLINE swfDisplayEntry_t::swfDisplayEntry_t() : +characterID( 0 ), +ratio( 0.0f ), +depth( 0 ), +clipDepth( 0 ), +blendMode( 0 ), +spriteInstance( NULL ), +textInstance( NULL ) +{ +} + +ID_INLINE swfRenderState_t::swfRenderState_t() : +material( NULL ), +materialWidth( 0 ), +materialHeight( 0 ), +activeMasks( 0 ), +blendMode( 0 ), +ratio( 0.0f ), +stereoDepth( STEREO_DEPTH_TYPE_NONE ) +{ +} + +ID_INLINE idSWFFontGlyph::idSWFFontGlyph() : +code( 0 ), +advance( 0 ) +{ +} + +ID_INLINE idSWFFont::idSWFFont() : +fontID( 0 ), +ascent( 0 ), +descent( 0 ), +leading( 0 ) +{ +} + +ID_INLINE idSWFTextRecord::idSWFTextRecord() : +fontID( 0 ), +xOffset( 0 ), +yOffset( 0 ), +textHeight( 0 ), +firstGlyph( 0 ), +numGlyphs( 0 ) +{ +} + +ID_INLINE idSWFEditText::idSWFEditText() : +flags( SWF_ET_NONE ), +fontID( 0 ), +fontHeight( 24 ), +maxLength( 0xFFFF ), +align( SWF_ET_ALIGN_LEFT ), +leftMargin( 0 ), +rightMargin( 0 ), +indent( 0 ), +leading( 0 ) +{ +} + +ID_INLINE swfGlyphEntry_t::swfGlyphEntry_t() : +index( 0 ), +advance( 0 ) +{ +} + +#endif // !__SWF_TYPES1_H__ diff --git a/neo/swf/SWF_Zlib.cpp b/neo/swf/SWF_Zlib.cpp new file mode 100644 index 00000000..016490f7b --- /dev/null +++ b/neo/swf/SWF_Zlib.cpp @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../framework/zlib/zlib.h" + +/* +======================== +idSWF::Inflate +======================== +*/ +bool idSWF::Inflate( const byte * input, int inputSize, byte * output, int outputSize ) { + struct local_swf_alloc_t { + static void * zalloc( void * opaque, uint32 items, uint32 size ) { + return Mem_Alloc( items * size, TAG_SWF ); + } + static void zfree( void * opaque, void * ptr ) { + Mem_Free( ptr ); + } + }; + z_stream stream; + memset( &stream, 0, sizeof( stream ) ); + stream.next_in = (Bytef *)input; + stream.avail_in = inputSize; + stream.next_out = (Bytef *)output; + stream.avail_out = outputSize; + stream.zalloc = local_swf_alloc_t::zalloc; + stream.zfree = local_swf_alloc_t::zfree; + inflateInit( &stream ); + bool success = ( inflate( &stream, Z_FINISH ) == Z_STREAM_END ); + inflateEnd( &stream ); + + return success; +} diff --git a/neo/sys/LightweightCompression.cpp b/neo/sys/LightweightCompression.cpp new file mode 100644 index 00000000..99688026 --- /dev/null +++ b/neo/sys/LightweightCompression.cpp @@ -0,0 +1,488 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idLib/precompiled.h" +#include "LightweightCompression.h" + +/* +======================== +HashIndex +======================== +*/ +static int HashIndex( int w, int k ) { + return ( w ^ k ) & idLZWCompressor::HASH_MASK; +} + +/* +======================== +idLZWCompressor::Start +======================== +*/ +void idLZWCompressor::Start( uint8 * data_, int maxSize_, bool append ) { + // Clear hash + ClearHash(); + + if ( append ) { + assert( lzwData->nextCode > LZW_FIRST_CODE ); + + int originalNextCode = lzwData->nextCode; + + lzwData->nextCode = LZW_FIRST_CODE; + + // If we are appending, then fill up the hash + for ( int i = LZW_FIRST_CODE; i < originalNextCode; i++ ) { + AddToDict( lzwData->dictionaryW[i], lzwData->dictionaryK[i] ); + } + + assert( originalNextCode == lzwData->nextCode ); + } else { + for ( int i = 0; i < LZW_FIRST_CODE; i++ ) { + lzwData->dictionaryK[i] = (uint8)i; + lzwData->dictionaryW[i] = 0xFFFF; + } + + lzwData->nextCode = LZW_FIRST_CODE; + lzwData->codeBits = LZW_START_BITS; + lzwData->codeWord = -1; + lzwData->tempValue = 0; + lzwData->tempBits = 0; + lzwData->bytesWritten = 0; + } + + oldCode = -1; // Used by DecompressBlock + data = data_; + + blockSize = 0; + blockIndex = 0; + + bytesRead = 0; + + maxSize = maxSize_; + overflowed = false; + + savedBytesWritten = 0; + savedCodeWord = 0; + saveCodeBits = 0; + savedTempValue = 0; + savedTempBits = 0; +} + +/* +======================== +idLZWCompressor::ReadBits +======================== +*/ +int idLZWCompressor::ReadBits( int bits ) { + int bitsToRead = bits - lzwData->tempBits; + + while ( bitsToRead > 0 ) { + if ( bytesRead >= maxSize ) { + return -1; + } + lzwData->tempValue |= (uint64)data[bytesRead++] << lzwData->tempBits; + lzwData->tempBits += 8; + bitsToRead -= 8; + } + + int value = (int)lzwData->tempValue & ( ( 1 << bits ) - 1 ); + lzwData->tempValue >>= bits; + lzwData->tempBits -= bits; + + return value; +} + +/* +======================== +idLZWCompressor::WriteBits +======================== +*/ +void idLZWCompressor::WriteBits( uint32 value, int bits ) { + + // Queue up bits into temp value + lzwData->tempValue |= (uint64)value << lzwData->tempBits; + lzwData->tempBits += bits; + + // Flush 8 bits (1 byte) at a time ( leftovers will get caught in idLZWCompressor::End() ) + while ( lzwData->tempBits >= 8 ) { + if ( lzwData->bytesWritten >= maxSize ) { + overflowed = true; + return; + } + + data[lzwData->bytesWritten++] = (uint8)( lzwData->tempValue & 255 ); + lzwData->tempValue >>= 8; + lzwData->tempBits -= 8; + } +} + +/* +======================== +idLZWCompressor::WriteChain + +The chain is stored backwards, so we have to write it to a buffer then output the buffer in +reverse. +======================== +*/ +int idLZWCompressor::WriteChain( int code ) { + byte chain[lzwCompressionData_t::LZW_DICT_SIZE]; + int firstChar = 0; + int i = 0; + do { + assert( i < lzwCompressionData_t::LZW_DICT_SIZE && code < lzwCompressionData_t::LZW_DICT_SIZE && code >= 0 ); + chain[i++] = (byte)lzwData->dictionaryK[code]; + code = lzwData->dictionaryW[code]; + } while ( code != 0xFFFF ); + firstChar = chain[--i]; + for ( ; i >= 0; i-- ) { + block[blockSize++] = chain[i]; + } + return firstChar; +} + +/* +======================== +idLZWCompressor::DecompressBlock +======================== +*/ +void idLZWCompressor::DecompressBlock() { + assert( blockIndex == blockSize ); // Make sure we've read all we can + + blockIndex = 0; + blockSize = 0; + + int firstChar = -1; + while ( blockSize < LZW_BLOCK_SIZE - lzwCompressionData_t::LZW_DICT_SIZE ) { + assert( lzwData->codeBits <= lzwCompressionData_t::LZW_DICT_BITS ); + + int code = ReadBits( lzwData->codeBits ); + if ( code == -1 ) { + break; + } + + if ( oldCode == -1 ) { + assert( code < 256 ); + block[blockSize++] = (uint8)code; + oldCode = code; + firstChar = code; + continue; + } + + if ( code >= lzwData->nextCode ) { + assert( code == lzwData->nextCode ); + firstChar = WriteChain( oldCode ); + block[blockSize++] = (uint8)firstChar; + } else { + firstChar = WriteChain( code ); + } + AddToDict( oldCode, firstChar ); + if ( BumpBits() ) { + oldCode = -1; + } else { + oldCode = code; + } + } +} + +/* +======================== +idLZWCompressor::ReadByte +======================== +*/ +int idLZWCompressor::ReadByte( bool ignoreOverflow ) { + if ( blockIndex == blockSize ) { + DecompressBlock(); + } + + if ( blockIndex == blockSize ) { //-V581 DecompressBlock() updates these values, the if() isn't redundant + if ( !ignoreOverflow ) { + overflowed = true; + assert( !"idLZWCompressor::ReadByte overflowed!" ); + } + return -1; + } + + return block[blockIndex++]; +} + + +/* +======================== +idLZWCompressor::WriteByte +======================== +*/ +void idLZWCompressor::WriteByte( uint8 value ) { + int code = Lookup( lzwData->codeWord, value ); + if ( code >= 0 ) { + lzwData->codeWord = code; + } else { + WriteBits( lzwData->codeWord, lzwData->codeBits ); + if ( !BumpBits() ) { + AddToDict( lzwData->codeWord, value ); + } + lzwData->codeWord = value; + } + + if ( lzwData->bytesWritten >= maxSize - ( lzwData->codeBits + lzwData->tempBits + 7 ) / 8 ) { + overflowed = true; // At any point, if we can't perform an End call, then trigger an overflow + return; + } +} + +/* +======================== +idLZWCompressor::Lookup +======================== +*/ +int idLZWCompressor::Lookup( int w, int k ) { + if ( w == -1 ) { + return k; + } else { + int i = HashIndex( w, k ); + + for ( int j = hash[i]; j != 0xFFFF; j = nextHash[j] ) { + assert( j < lzwCompressionData_t::LZW_DICT_SIZE ); + if ( lzwData->dictionaryK[j] == k && lzwData->dictionaryW[j] == w ) { + return j; + } + } + } + return -1; +} + +/* +======================== +idLZWCompressor::AddToDict +======================== +*/ +int idLZWCompressor::AddToDict( int w, int k ) { + assert( w < 0xFFFF - 1 ); + assert( k < 256 ); + assert( lzwData->nextCode < lzwCompressionData_t::LZW_DICT_SIZE ); + + lzwData->dictionaryK[lzwData->nextCode] = (uint8)k; + lzwData->dictionaryW[lzwData->nextCode] = (uint16)w; + int i = HashIndex( w, k ); + nextHash[lzwData->nextCode] = hash[i]; + hash[i] = (uint16)lzwData->nextCode; + return lzwData->nextCode++; +} + +/* +======================== +idLZWCompressor::BumpBits + +Possibly increments codeBits. + return: bool - true, if the dictionary was cleared. +======================== +*/ +bool idLZWCompressor::BumpBits() { + if ( lzwData->nextCode == ( 1 << lzwData->codeBits ) ) { + lzwData->codeBits ++; + if ( lzwData->codeBits > lzwCompressionData_t::LZW_DICT_BITS ) { + lzwData->nextCode = LZW_FIRST_CODE; + lzwData->codeBits = LZW_START_BITS; + ClearHash(); + return true; + } + } + return false; +} + +/* +======================== +idLZWCompressor::End +======================== +*/ +int idLZWCompressor::End() { + assert( lzwData->tempBits < 8 ); + assert( lzwData->bytesWritten < maxSize - ( lzwData->codeBits + lzwData->tempBits + 7 ) / 8 ); + + assert( ( Length() > 0 ) == ( lzwData->codeWord != -1 ) ); + + if ( lzwData->codeWord != -1 ) { + WriteBits( lzwData->codeWord, lzwData->codeBits ); + } + + if ( lzwData->tempBits > 0 ) { + if ( lzwData->bytesWritten >= maxSize ) { + overflowed = true; + return -1; + } + data[lzwData->bytesWritten++] = (uint8)lzwData->tempValue & ( ( 1 << lzwData->tempBits ) - 1 ); + } + + return Length() > 0 ? Length() : -1; // Total bytes written (or failure) +} + +/* +======================== +idLZWCompressor::Save +======================== +*/ +void idLZWCompressor::Save() { + assert( !overflowed ); + // Check and make sure we are at a good spot (can call End) + assert( lzwData->bytesWritten < maxSize - ( lzwData->codeBits + lzwData->tempBits + 7 ) / 8 ); + + savedBytesWritten = lzwData->bytesWritten; + savedCodeWord = lzwData->codeWord; + saveCodeBits = lzwData->codeBits; + savedTempValue = lzwData->tempValue; + savedTempBits = lzwData->tempBits; +} + +/* +======================== +idLZWCompressor::Restore +======================== +*/ +void idLZWCompressor::Restore() { + lzwData->bytesWritten = savedBytesWritten; + lzwData->codeWord = savedCodeWord; + lzwData->codeBits = saveCodeBits; + lzwData->tempValue = savedTempValue; + lzwData->tempBits = savedTempBits; +} + +/* +======================== +idLZWCompressor::ClearHash +======================== +*/ +void idLZWCompressor::ClearHash() { + memset( hash, 0xFF, sizeof( hash ) ); +} + +/* +======================== +idZeroRunLengthCompressor +Simple zero based run length encoder/decoder +======================== +*/ + +void idZeroRunLengthCompressor::Start( uint8 * dest_, idLZWCompressor * comp_, int maxSize_ ) { + zeroCount = 0; + dest = dest_; + comp = comp_; + compressed = 0; + maxSize = maxSize_; +} + +bool idZeroRunLengthCompressor::WriteRun() { + if ( zeroCount > 0 ) { + assert( zeroCount <= 255 ); + if ( compressed + 2 > maxSize ) { + maxSize = -1; + return false; + } + if ( comp != NULL ) { + comp->WriteByte( 0 ); + comp->WriteByte( (uint8)zeroCount ); + } else { + *dest++ = 0; + *dest++ = (uint8)zeroCount; + } + compressed += 2; + zeroCount = 0; + } + return true; +} + +bool idZeroRunLengthCompressor::WriteByte( uint8 value ) { + if ( value != 0 || zeroCount >= 255 ) { + if ( !WriteRun() ) { + maxSize = -1; + return false; + } + } + + if ( value != 0 ) { + if ( compressed + 1 > maxSize ) { + maxSize = -1; + return false; + } + if ( comp != NULL ) { + comp->WriteByte( value ); + } else { + *dest++ = value; + } + compressed++; + } else { + zeroCount++; + } + + return true; +} + +byte idZeroRunLengthCompressor::ReadByte() { + // See if we need to possibly read more data + if ( zeroCount == 0 ) { + int value = ReadInternal(); + if ( value == -1 ) { + assert( 0 ); + } + if ( value != 0 ) { + return (byte)value; // Return non zero values immediately + } + // Read the number of zeroes + zeroCount = ReadInternal(); + } + + assert( zeroCount > 0 ); + + zeroCount--; + return 0; +} + +void idZeroRunLengthCompressor::ReadBytes( byte * dest, int count ) { + for ( int i = 0; i < count; i++ ) { + *dest++ = ReadByte(); + } +} + +void idZeroRunLengthCompressor::WriteBytes( uint8 * src, int count ) { + for ( int i = 0; i < count; i++ ) { + WriteByte( *src++ ); + } +} + +int idZeroRunLengthCompressor::End() { + WriteRun(); + if ( maxSize == -1 ) { + return -1; + } + return compressed; +} + +int idZeroRunLengthCompressor::ReadInternal() { + compressed++; + if ( comp != NULL ) { + return comp->ReadByte(); + } + return *dest++; +} \ No newline at end of file diff --git a/neo/sys/LightweightCompression.h b/neo/sys/LightweightCompression.h new file mode 100644 index 00000000..28d741ea --- /dev/null +++ b/neo/sys/LightweightCompression.h @@ -0,0 +1,209 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __LIGHTWEIGHT_COMPRESSION_H__ +#define __LIGHTWEIGHT_COMPRESSION_H__ + + +struct lzwCompressionData_t { + static const int LZW_DICT_BITS = 12; + static const int LZW_DICT_SIZE = 1 << LZW_DICT_BITS; + + uint8 dictionaryK[LZW_DICT_SIZE]; + uint16 dictionaryW[LZW_DICT_SIZE]; + + int nextCode; + int codeBits; + + int codeWord; + + uint64 tempValue; + int tempBits; + int bytesWritten; +}; + +/* +======================== +idLZWCompressor +Simple lzw based encoder/decoder +======================== +*/ +class idLZWCompressor { +public: + idLZWCompressor( lzwCompressionData_t * lzwData_ ) : lzwData( lzwData_ ) {} + + static const int LZW_BLOCK_SIZE = ( 1 << 15 ); + static const int LZW_START_BITS = 9; + static const int LZW_FIRST_CODE = ( 1 << ( LZW_START_BITS - 1 ) ); + + void Start( uint8 * data_, int maxSize, bool append = false ); + int ReadBits( int bits ); + int WriteChain( int code ); + void DecompressBlock(); + void WriteBits( uint32 value, int bits ); + int ReadByte( bool ignoreOverflow = false ); + void WriteByte( uint8 value ); + int Lookup( int w, int k ); + int AddToDict( int w, int k ); + bool BumpBits(); + int End(); + + int Length() const { return lzwData->bytesWritten; } + int GetReadCount() const { return bytesRead; } + + void Save(); + void Restore(); + + bool IsOverflowed() { return overflowed; } + + int Write( const void * data, int length ) { + uint8 * src = (uint8*)data; + + for ( int i = 0; i < length && !IsOverflowed(); i++ ) { + WriteByte( src[i] ); + } + + return length; + } + + int Read( void * data, int length, bool ignoreOverflow = false ) { + uint8 * src = (uint8*)data; + + for ( int i = 0; i < length; i++ ) { + int byte = ReadByte( ignoreOverflow ); + + if ( byte == -1 ) { + return i; + } + + src[i] = (uint8)byte; + } + + return length; + } + + int WriteR( const void * data, int length ) { + uint8 * src = (uint8*)data; + + for ( int i = 0; i < length && !IsOverflowed(); i++ ) { + WriteByte( src[length - i - 1] ); + } + + return length; + } + + int ReadR( void * data, int length, bool ignoreOverflow = false ) { + uint8 * src = (uint8*)data; + + for ( int i = 0; i < length; i++ ) { + int byte = ReadByte( ignoreOverflow ); + + if ( byte == -1 ) { + return i; + } + + src[length - i - 1] = (uint8)byte; + } + + return length; + } + + template ID_INLINE size_t WriteAgnostic( const type & c ) { + return Write( &c, sizeof( c ) ); + } + + template ID_INLINE size_t ReadAgnostic( type & c, bool ignoreOverflow = false ) { + size_t r = Read( &c, sizeof( c ), ignoreOverflow ); + return r; + } + + static const int DICTIONARY_HASH_BITS = 10; + static const int MAX_DICTIONARY_HASH = 1 << DICTIONARY_HASH_BITS; + static const int HASH_MASK = MAX_DICTIONARY_HASH - 1; + +private: + void ClearHash(); + + lzwCompressionData_t * lzwData; + uint16 hash[MAX_DICTIONARY_HASH]; + uint16 nextHash[lzwCompressionData_t::LZW_DICT_SIZE]; + + // Used by DecompressBlock + int oldCode; + + uint8 * data; // Read/write + int maxSize; + bool overflowed; + + // For reading + int bytesRead; + uint8 block[LZW_BLOCK_SIZE]; + int blockSize; + int blockIndex; + + // saving/restoring when overflow (when writing). + // Must call End directly after restoring (dictionary is bad so can't keep writing) + int savedBytesWritten; + int savedCodeWord; + int saveCodeBits; + uint64 savedTempValue; + int savedTempBits; +}; + +/* +======================== +idZeroRunLengthCompressor +Simple zero based run length encoder/decoder +======================== +*/ +class idZeroRunLengthCompressor { +public: + idZeroRunLengthCompressor() : zeroCount( 0 ), destStart( NULL ) { + } + + void Start( uint8 * dest_, idLZWCompressor * comp_, int maxSize_ ); + bool WriteRun(); + bool WriteByte( uint8 value ); + byte ReadByte(); + void ReadBytes( byte * dest, int count ); + void WriteBytes( uint8 * src, int count ); + int End(); + + int CompressedSize() const { return compressed; } + +private: + int ReadInternal(); + + int zeroCount; // Number of pending zeroes + idLZWCompressor * comp; + uint8 * destStart; + uint8 * dest; + int compressed; // Compressed size + int maxSize; +}; + +#endif // __LIGHTWEIGHT_COMPRESSION_H__ diff --git a/neo/sys/PacketProcessor.cpp b/neo/sys/PacketProcessor.cpp new file mode 100644 index 00000000..26c30aad --- /dev/null +++ b/neo/sys/PacketProcessor.cpp @@ -0,0 +1,590 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idLib/precompiled.h" + +idCVar net_maxRate( "net_maxRate", "50", CVAR_INTEGER, "max send rate in kilobytes per second" ); + +idCVar net_showReliableCompression( "net_showReliableCompression", "0", CVAR_BOOL, "Show reliable compression ratio." ); + +// we use an assert(0); return idiom in some places, which lint complains about +//lint -e527 unreachable code at token 'return' + +/* +================================================ +idPacketProcessor::QueueReliableAck +================================================ +*/ +void idPacketProcessor::QueueReliableAck( int lastReliable ) { + // NOTE - Even if it was the last known sequence, go ahead and ack it, in case our last ack for this sequence got dropped + if ( lastReliable >= reliableSequenceRecv ) { + queuedReliableAck = lastReliable; + reliableSequenceRecv = lastReliable; + } +} + +/* +================================================ +idPacketProcessor::FinalizeRead +================================================ +*/ +int idPacketProcessor::FinalizeRead( idBitMsg & inMsg, idBitMsg & outMsg, int & userValue ) { + userValue = 0; + + idInnerPacketHeader header; + header.ReadFromMsg( inMsg ); + + if ( !verify( header.Type() != PACKET_TYPE_FRAGMENTED ) ) { // We shouldn't be fragmented at this point + idLib::Printf("Received invalid fragmented packet.\n" ); + return RETURN_TYPE_NONE; + } + + if ( header.Type() == PACKET_TYPE_RELIABLE_ACK ) { + // Handle reliable ack + int reliableSequence = inMsg.ReadLong(); + reliable.RemoveOlderThan( reliableSequence + 1 ); + header.ReadFromMsg( inMsg ); // Read the new header, since the reliable ack sits on top the actual header of the message + } + + if ( header.Type() == PACKET_TYPE_OOB ) { + // out-of-band packet + userValue = header.Value(); + } else { + // At this point, this MUST be an in-band packet + if ( !verify( header.Type() == PACKET_TYPE_INBAND ) ) { + idLib::Printf("In-band packet expected, received type %i instead.\n", header.Type() ); + return RETURN_TYPE_NONE; + } + + // Reset number of reliables received (NOTE - This means you MUST unload all reliables as they are received) + numReliable = 0; + + // Handle reliable portion of in-band packets + int numReliableRecv = header.Value(); + int bufferPos = 0; + + if ( numReliableRecv > 0 ) { + // Byte align msg + inMsg.ReadByteAlign(); + + int compressedSize = inMsg.ReadShort(); + + lzwCompressionData_t lzwData; + idLZWCompressor lzwCompressor( &lzwData ); + + lzwCompressor.Start( (uint8*)inMsg.GetReadData() + inMsg.GetReadCount(), compressedSize ); // Read from msg + + int reliableSequence = 0; + + lzwCompressor.ReadAgnostic< int >( reliableSequence ); + + for ( int r = 0; r < numReliableRecv; r++ ) { + uint8 uncompMem[ MAX_MSG_SIZE ]; + + uint16 reliableDataLength = 0; + lzwCompressor.ReadAgnostic< uint16 >( reliableDataLength ); + lzwCompressor.Read( uncompMem, reliableDataLength ); + + if ( reliableSequence + r > reliableSequenceRecv ) { // Only accept newer reliable msg's than we've currently already received + if ( !verify( bufferPos + reliableDataLength <= sizeof( reliableBuffer ) ) ) { + idLib::Printf( "Reliable msg size overflow.\n" ); + return RETURN_TYPE_NONE; + } + if ( !verify( numReliable < MAX_RELIABLE_QUEUE ) ) { + idLib::Printf( "Reliable msg count overflow.\n" ); + return RETURN_TYPE_NONE; + } + memcpy( reliableBuffer + bufferPos, uncompMem, reliableDataLength ); + reliableMsgSize[ numReliable ] = reliableDataLength; + reliableMsgPtrs[ numReliable++ ] = &reliableBuffer[ bufferPos ]; + bufferPos += reliableDataLength; + } else { + extern idCVar net_verboseReliable; + if ( net_verboseReliable.GetBool() ) { + idLib::Printf( "Ignoring reliable msg %i because %i was already acked\n", ( reliableSequence + r ), reliableSequenceRecv ); + } + } + + if ( !verify( lzwCompressor.IsOverflowed() == false ) ) { + idLib::Printf( "lzwCompressor.IsOverflowed() == true.\n" ); + return RETURN_TYPE_NONE; + } + } + + inMsg.SetReadCount( inMsg.GetReadCount() + compressedSize ); + + QueueReliableAck( reliableSequence + numReliableRecv - 1 ); + } + } + + // Load actual msg + outMsg.BeginWriting(); + outMsg.WriteData( inMsg.GetReadData() + inMsg.GetReadCount(), inMsg.GetRemainingData() ); + outMsg.SetSize( inMsg.GetRemainingData() ); + + return ( header.Type() == PACKET_TYPE_OOB ) ? RETURN_TYPE_OOB : RETURN_TYPE_INBAND; +} + +/* +================================================ +idPacketProcessor::QueueReliableMessage +================================================ +*/ +bool idPacketProcessor::QueueReliableMessage( byte type, const byte * data, int dataLen ) { + return reliable.Append( reliableSequenceSend++, &type, 1, data, dataLen ); +} + +/* +======================== +idPacketProcessor::CanSendMoreData +======================== +*/ +bool idPacketProcessor::CanSendMoreData() const { + if ( net_maxRate.GetInteger() == 0 ) { + return true; + } + + return ( outgoingRateBytes <= net_maxRate.GetInteger() * 1024 ); +} + +/* +======================== +idPacketProcessor::UpdateOutgoingRate +======================== +*/ +void idPacketProcessor::UpdateOutgoingRate( const int time, const int size ) { + outgoingBytes += size; + + // update outgoing rate variables + if ( time > outgoingRateTime ) { + outgoingRateBytes -= outgoingRateBytes * (float)( time - outgoingRateTime ) / 1000.0f; + if ( outgoingRateBytes < 0.0f ) { + outgoingRateBytes = 0.0f; + } + } + + outgoingRateTime = time; + outgoingRateBytes += size; + + // compute an average bandwidth at intervals + if ( time - lastOutgoingRateTime > BANDWIDTH_AVERAGE_PERIOD ) { + currentOutgoingRate = 1000 * ( outgoingBytes - lastOutgoingBytes ) / ( time - lastOutgoingRateTime ); + lastOutgoingBytes = outgoingBytes; + lastOutgoingRateTime = time; + } +} + +/* +================= +idPacketProcessor::UpdateIncomingRate +================= +*/ +void idPacketProcessor::UpdateIncomingRate( const int time, const int size ) { + incomingBytes += size; + + // update incoming rate variables + if ( time > incomingRateTime ) { + incomingRateBytes -= incomingRateBytes * (float)( time - incomingRateTime ) / 1000.0f; + if ( incomingRateBytes < 0.0f ) { + incomingRateBytes = 0.0f; + } + } + incomingRateTime = time; + incomingRateBytes += size; + + // compute an average bandwidth at intervals + if ( time - lastIncomingRateTime > BANDWIDTH_AVERAGE_PERIOD ) { + currentIncomingRate = 1000 * ( incomingBytes - lastIncomingBytes ) / ( time - lastIncomingRateTime ); + lastIncomingBytes = incomingBytes; + lastIncomingRateTime = time; + } +} + +/* +================================================ +idPacketProcessor::ProcessOutgoing +NOTE - We only compress reliables because we assume everything else has already been compressed. +================================================ +*/ +bool idPacketProcessor::ProcessOutgoing( const int time, const idBitMsg & msg, bool isOOB, int userData ) { + // We can only do ONE ProcessOutgoing call, then we need to do GetSendFragment to + // COMPLETELY empty unsentMsg before calling ProcessOutgoing again. + if ( !verify( fragmentedSend == false ) ) { + idLib::Warning( "ProcessOutgoing: fragmentedSend == true!"); + return false; + } + + if ( !verify( unsentMsg.GetRemainingData() == 0 ) ) { + idLib::Warning( "ProcessOutgoing: unsentMsg.GetRemainingData() > 0!"); + return false; + } + + // Build the full msg to send, which could include reliable data + unsentMsg.InitWrite( unsentBuffer, sizeof( unsentBuffer ) ); + unsentMsg.BeginWriting(); + + // Ack reliables if we need to (NOTE - We will send this ack on both the in-band and out-of-band channels) + if ( queuedReliableAck >= 0 ) { + idInnerPacketHeader header( PACKET_TYPE_RELIABLE_ACK, 0 ); + header.WriteToMsg( unsentMsg ); + unsentMsg.WriteLong( queuedReliableAck ); + queuedReliableAck = -1; + } + + if ( isOOB ) { + if ( msg.GetSize() + unsentMsg.GetSize() > MAX_OOB_MSG_SIZE ) { // Fragmentation not allowed for out-of-band msg's + idLib::Printf("Out-of-band packet too large %i\n", unsentMsg.GetSize() ); + assert( 0 ); + return false; + } + // We don't need to worry about reliable for out of band packets + idInnerPacketHeader header( PACKET_TYPE_OOB, userData ); + header.WriteToMsg( unsentMsg ); + } else { + // Add reliable msg's here if this is an in-band packet + idInnerPacketHeader header( PACKET_TYPE_INBAND, reliable.Num() ); + header.WriteToMsg( unsentMsg ); + if ( reliable.Num() > 0 ) { + // Byte align unsentMsg + unsentMsg.WriteByteAlign(); + + lzwCompressionData_t lzwData; + idLZWCompressor lzwCompressor( &lzwData ); + + lzwCompressor.Start( unsentMsg.GetWriteData() + unsentMsg.GetSize() + 2, unsentMsg.GetRemainingSpace() - 2 ); // Write to compressed mem, not exceeding MAX_MSG_SIZE (+2 to reserve space for compressed size) + + int uncompressedSize = 4; + lzwCompressor.WriteAgnostic< int >( reliable.ItemSequence( 0 ) ); + for ( int i = 0; i < reliable.Num(); i++ ) { + lzwCompressor.WriteAgnostic< uint16 >( reliable.ItemLength( i ) ); + lzwCompressor.Write( reliable.ItemData( i ), reliable.ItemLength( i ) ); + uncompressedSize += 2 + reliable.ItemLength( i ); + } + + lzwCompressor.End(); + + if ( lzwCompressor.IsOverflowed() ) { + idLib::Error( "reliable msg compressor overflow." ); + } + + unsentMsg.WriteShort( lzwCompressor.Length() ); + unsentMsg.SetSize( unsentMsg.GetSize() + lzwCompressor.Length() ); + + if ( net_showReliableCompression.GetBool() ) { + static int totalUncompressed = 0; + static int totalCompressed = 0; + + totalUncompressed += uncompressedSize; + totalCompressed += lzwCompressor.Length(); + + float ratio1 = (float)lzwCompressor.Length() / (float)uncompressedSize; + float ratio2 = (float)totalCompressed / (float)totalUncompressed; + + idLib::Printf( "Uncompressed: %i, Compressed: %i, TotalUncompressed: %i, TotalCompressed: %i, (%2.2f / %2.2f )\n", uncompressedSize, lzwCompressor.Length(), totalUncompressed, totalCompressed, ratio1, ratio2 ); + } + } + } + + // Fill up with actual msg + unsentMsg.WriteData( msg.GetReadData(), msg.GetSize() ); + + if ( unsentMsg.GetSize() > MAX_PACKET_SIZE ) { + if ( isOOB ) { + idLib::Error( "oob msg's cannot fragment" ); + } + fragmentedSend = true; + } + + return true; +} + +/* +================================================ +idPacketProcessor::GetSendFragment +================================================ +*/ +bool idPacketProcessor::GetSendFragment( const int time, sessionId_t sessionID, idBitMsg & outMsg ) { + lastSendTime = time; + + if ( unsentMsg.GetRemainingData() <= 0 ) { + return false; // Nothing to send + } + + outMsg.BeginWriting(); + + + idOuterPacketHeader outerHeader( sessionID ); + + // Write outer packet header to the msg + outerHeader.WriteToMsg( outMsg ); + + if ( !fragmentedSend ) { + // Simple case, no fragments to sent + outMsg.WriteData( unsentMsg.GetReadData(), unsentMsg.GetSize() ); + unsentMsg.SetSize( 0 ); + } else { + int currentSize = idMath::ClampInt( 0, MAX_PACKET_SIZE, unsentMsg.GetRemainingData() ); + assert( currentSize > 0 ); + assert( unsentMsg.GetRemainingData() - currentSize >= 0 ); + + // See if we'll have more fragments once we subtract off how much we're about to write + bool moreFragments = ( unsentMsg.GetRemainingData() - currentSize > 0 ) ? true : false; + + if ( !unsentMsg.GetReadCount() ) { // If this is the first read, then we know it's the first fragment + assert( moreFragments ); // If we have a first, we must have more or something went wrong + idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, FRAGMENT_START ); + header.WriteToMsg( outMsg ); + } else { + idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, moreFragments ? FRAGMENT_MIDDLE : FRAGMENT_END ); + header.WriteToMsg( outMsg ); + } + + outMsg.WriteLong( fragmentSequence ); + outMsg.WriteData( unsentMsg.GetReadData() + unsentMsg.GetReadCount(), currentSize ); + unsentMsg.ReadData( NULL, currentSize ); + + assert( moreFragments == unsentMsg.GetRemainingData() > 0 ); + fragmentedSend = moreFragments; + + fragmentSequence++; // Advance sequence + + fragmentAccumulator++; // update the counter for the net debug hud + } + + + // The caller needs to send this packet, so assume he did, and update rates + UpdateOutgoingRate( time, outMsg.GetSize() ); + + return true; +} + +/* +================================================ +idPacketProcessor::ProcessIncoming +================================================ +*/ +int idPacketProcessor::ProcessIncoming( int time, sessionId_t expectedSessionID, idBitMsg & msg, idBitMsg & out, int & userData, const int peerNum ) { + assert( msg.GetSize() <= MAX_FINAL_PACKET_SIZE ); + + UpdateIncomingRate( time, msg.GetSize() ); + + + idOuterPacketHeader outerHeader; + outerHeader.ReadFromMsg( msg ); + + sessionId_t sessionID = outerHeader.GetSessionID(); + assert( sessionID == expectedSessionID ); + + if ( !verify( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) ) { + idLib::Printf( "Expected non connectionless ID, but got a connectionless one\n" ); + return RETURN_TYPE_NONE; + } + + if ( sessionID != expectedSessionID ) { + idLib::Printf( "Expected session id: %8x but got %8x instead\n", expectedSessionID, sessionID ); + return RETURN_TYPE_NONE; + } + + int c,b; + msg.SaveReadState( c, b ); + + idInnerPacketHeader header; + header.ReadFromMsg( msg ); + + if ( header.Type() != PACKET_TYPE_FRAGMENTED ) { + // Non fragmented + msg.RestoreReadState( c, b ); // Reset since we took a byte to check the type + return FinalizeRead( msg, out, userData ); + } + + // Decode fragmented packet + int readSequence = msg.ReadLong(); // Read sequence of fragment + + if ( header.Value() == FRAGMENT_START ) { + msgWritePos = 0; // Reset msg reconstruction write pos + } else if ( fragmentSequence == -1 || readSequence != fragmentSequence + 1 ) { + droppedFrags++; + idLib::Printf( "Dropped Fragments - PeerNum: %i FragmentSeq: %i, ReadSeq: %i, Total: %i\n", peerNum, fragmentSequence, readSequence, droppedFrags ); + + // If this is the middle or end, make sure we are reading in fragmentSequence + fragmentSequence = -1; + return RETURN_TYPE_NONE; // Out of sequence + } + fragmentSequence = readSequence; + assert( msg.GetRemainingData() > 0 ); + + if ( !verify( msgWritePos + msg.GetRemainingData() < sizeof( msgBuffer ) ) ) { + idLib::Error( "ProcessIncoming: Fragmented msg buffer overflow." ); + } + + memcpy( msgBuffer + msgWritePos, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); + msgWritePos += msg.GetRemainingData(); + + if ( header.Value() == FRAGMENT_END ) { + // Done reconstructing the msg + idBitMsg msg( msgBuffer, sizeof( msgBuffer ) ); + msg.SetSize( msgWritePos ); + return FinalizeRead( msg, out, userData ); + } + + if ( !verify( header.Value() == FRAGMENT_START || header.Value() == FRAGMENT_MIDDLE ) ) { + idLib::Printf( "ProcessIncoming: Invalid packet.\n" ); + } + + // If we get here, this is part (either beginning or end) of a fragmented packet. + // We return RETURN_TYPE_NONE to let the caller know they don't need to do anything yet. + return RETURN_TYPE_NONE; +} + +/* +================================================ +idPacketProcessor::ProcessConnectionlessOutgoing +================================================ +*/ +bool idPacketProcessor::ProcessConnectionlessOutgoing( idBitMsg & msg, idBitMsg & out, int lobbyType, int userData ) { + sessionId_t sessionID = lobbyType + 1; + + + // Write outer header + idOuterPacketHeader outerHeader( sessionID ); + outerHeader.WriteToMsg( out ); + + // Write inner header + idInnerPacketHeader header( PACKET_TYPE_OOB, userData ); + header.WriteToMsg( out ); + + // Write msg + out.WriteData( msg.GetReadData(), msg.GetSize() ); + + + return true; +} + +/* +================================================ +idPacketProcessor::ProcessConnectionlessIncoming +================================================ +*/ +bool idPacketProcessor::ProcessConnectionlessIncoming( idBitMsg & msg, idBitMsg & out, int & userData ) { + + idOuterPacketHeader outerHeader; + outerHeader.ReadFromMsg( msg ); + + sessionId_t sessionID = outerHeader.GetSessionID(); + + if ( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) { + // Not a connectionless msg (this can happen if a previously connected peer keeps sending data for whatever reason) + idLib::Printf( "ProcessConnectionlessIncoming: Invalid session ID - %d\n", sessionID ); + return false; + } + + idInnerPacketHeader header; + header.ReadFromMsg( msg ); + + if ( header.Type() != PACKET_TYPE_OOB ) { + idLib::Printf( "ProcessConnectionlessIncoming: header.Type() != PACKET_TYPE_OOB\n" ); + return false; // Only out-of-band packets supported for connectionless + } + + userData = header.Value(); + + out.BeginWriting(); + out.WriteData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); + out.SetSize( msg.GetRemainingData() ); + + return true; +} + +/* +================================================ +idPacketProcessor::GetSessionID +================================================ +*/ +idPacketProcessor::sessionId_t idPacketProcessor::GetSessionID( idBitMsg & msg ) { + sessionId_t sessionID; + int c,b; + msg.SaveReadState( c, b ); + // Read outer header + idOuterPacketHeader outerHeader; + outerHeader.ReadFromMsg( msg ); + + // Get session ID + sessionID = outerHeader.GetSessionID(); + + msg.RestoreReadState( c, b ); + return sessionID; +} + +/* +================================================ +idPacketProcessor::VerifyEmptyReliableQueue +================================================ +*/ +idCVar net_verifyReliableQueue( "net_verifyReliableQueue", "2", CVAR_INTEGER, "0: warn only, 1: error, 2: fixup, 3: fixup and verbose, 4: force test" ); +#define RELIABLE_VERBOSE if ( net_verifyReliableQueue.GetInteger() >= 3 ) idLib::Printf +void idPacketProcessor::VerifyEmptyReliableQueue( byte keepMsgBelowThis, byte replaceWithThisMsg ) { + if ( net_verifyReliableQueue.GetInteger() == 4 ) { + RELIABLE_VERBOSE( "pushing a fake game reliable\n" ); + const char * garbage = "garbage"; + QueueReliableMessage( keepMsgBelowThis + 4, (const byte *)garbage, 8 ); + QueueReliableMessage( replaceWithThisMsg, NULL, 0 ); + } + if ( reliable.Num() == 0 ) { + return; + } + if ( net_verifyReliableQueue.GetInteger() == 1 ) { + idLib::Error( "reliable queue is not empty: %d messages", reliable.Num() ); + return; + } + idLib::Warning( "reliable queue is not empty: %d messages", reliable.Num() ); + if ( net_verifyReliableQueue.GetInteger() == 0 ) { + return; + } + // drop some stuff that is potentially dangerous and should not transmit + idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE > clean; + RELIABLE_VERBOSE( "rollback send sequence from %d to %d\n", reliableSequenceSend, reliable.ItemSequence( 0 ) ); + for ( int i = 0; i < reliable.Num(); i++ ) { + byte peek = reliable.ItemData( i )[0]; + if ( peek < keepMsgBelowThis ) { + RELIABLE_VERBOSE( "keeping %d\n", peek ); + clean.Append( reliable.ItemSequence( i ), reliable.ItemData( i ), reliable.ItemLength( i ) ); + } else { + // Replace with fake msg, so we retain itemsequence ordering. + // If we don't do this, it's possible we remove the last msg, then append a single msg before the next send, + // and the client may think he already received the msg, since his last reliableSequenceRecv could be greater than our + // reliableSequenceSend if he already received the group of reliables we are mucking with + clean.Append( reliable.ItemSequence( i ), &replaceWithThisMsg, 1 ); + RELIABLE_VERBOSE( "dropping %d\n", peek ); + } + } + + assert( reliable.Num() == clean.Num() ); + + reliable = clean; +} diff --git a/neo/sys/PacketProcessor.h b/neo/sys/PacketProcessor.h new file mode 100644 index 00000000..b01109f0 --- /dev/null +++ b/neo/sys/PacketProcessor.h @@ -0,0 +1,262 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __PACKET_PROCESSOR_H__ +#define __PACKET_PROCESSOR_H__ + +/* +================================================ +idPacketProcessor +================================================ +*/ +class idPacketProcessor { +public: + static const int RETURN_TYPE_NONE = 0; + static const int RETURN_TYPE_OOB = 1; + static const int RETURN_TYPE_INBAND = 2; + + typedef uint16 sessionId_t; + + static const int NUM_LOBBY_TYPE_BITS = 2; + static const int LOBBY_TYPE_MASK = ( 1 << NUM_LOBBY_TYPE_BITS ) - 1; + + static const sessionId_t SESSION_ID_INVALID = 0; + static const sessionId_t SESSION_ID_CONNECTIONLESS_PARTY = 1; + static const sessionId_t SESSION_ID_CONNECTIONLESS_GAME = 2; + static const sessionId_t SESSION_ID_CONNECTIONLESS_GAME_STATE = 3; + + static const int BANDWIDTH_AVERAGE_PERIOD = 250; + + idPacketProcessor() { + Reset(); + } + + void Reset() { + msgWritePos = 0; + fragmentSequence = 0; + droppedFrags = 0; + fragmentedSend = false; + + reliable = idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE >(); + + reliableSequenceSend = 1; + reliableSequenceRecv = 0; + + numReliable = 0; + + queuedReliableAck = -1; + + unsentMsg = idBitMsg(); + + lastSendTime = 0; + + outgoingRateTime = 0; + outgoingRateBytes = 0.0f; + incomingRateTime = 0; + incomingRateBytes = 0.0f; + + outgoingBytes = 0; + incomingBytes = 0; + + currentOutgoingRate = 0; + lastOutgoingRateTime = 0; + lastOutgoingBytes = 0; + currentIncomingRate = 0; + lastIncomingRateTime = 0; + lastIncomingBytes = 0; + + fragmentAccumulator = 0; + + + } + + static const int MAX_MSG_SIZE = 8000; // This is the max size you can pass into ProcessOutgoing + static const int MAX_FINAL_PACKET_SIZE = 1200; // Lowest/safe MTU across all our platforms to avoid fragmentation at the transport layer (which is poorly supported by consumer hardware and may cause nasty latency side effects) + static const int MAX_RELIABLE_QUEUE = 64; + + // TypeInfo doesn't like sizeof( sessionId_t )?? and then fails to understand the #ifdef/#else/#endif + //static const int MAX_PACKET_SIZE = MAX_FINAL_PACKET_SIZE - 6 - sizeof( sessionId_t ); // Largest possible packet before headers and such applied (subtract some for various internal header data, and session id) + static const int MAX_PACKET_SIZE = MAX_FINAL_PACKET_SIZE - 6 - 2; // Largest possible packet before headers and such applied (subtract some for various internal header data, and session id) + static const int MAX_OOB_MSG_SIZE = MAX_PACKET_SIZE - 1; // We don't allow fragmentation for out-of-band msg's, and we need a byte for the header + +private: + void QueueReliableAck( int lastReliable ); + int FinalizeRead( idBitMsg & inMsg, idBitMsg & outMsg, int & userValue ); + +public: + bool CanSendMoreData() const; + void UpdateOutgoingRate( const int time, const int size ); + void UpdateIncomingRate( const int time, const int size ); + + void RefreshRates( int time ) { UpdateOutgoingRate( time, 0 ); UpdateIncomingRate( time, 0 ); } + + // Used to queue reliable msg's, to be sent on the next ProcessOutgoing + bool QueueReliableMessage( byte type, const byte * data, int dataLen ); + // Used to process a msg ready to be sent, could get fragmented into multiple fragments + bool ProcessOutgoing( const int time, const idBitMsg & msg, bool isOOB, int userData ); + // Used to get each fragment for sending through the actual net connection + bool GetSendFragment( const int time, sessionId_t sessionID, idBitMsg & outMsg ); + // Used to process a fragment received. Returns true when msg was reconstructed. + int ProcessIncoming( int time, sessionId_t expectedSessionID, idBitMsg & msg, idBitMsg & out, int & userData, const int peerNum ); + + // Returns true if there are more fragments to send + bool HasMoreFragments() const { return ( unsentMsg.GetRemainingData() > 0 ); } + + // Num reliables not ack'd + int NumQueuedReliables() { return reliable.Num(); } + // True if we need to send a reliable ack + int NeedToSendReliableAck() { return queuedReliableAck >= 0 ? true : false; } + + // Used for out-of-band non connected peers + // This doesn't actually support fragmentation, it is just simply here to hide the + // header structure, so the caller doesn't have to skip over the header data. + static bool ProcessConnectionlessOutgoing( idBitMsg & msg, idBitMsg & out, int lobbyType, int userData ); + static bool ProcessConnectionlessIncoming( idBitMsg & msg, idBitMsg & out, int & userData ); + + // Used to "peek" at a session id of a message fragment + static sessionId_t GetSessionID( idBitMsg & msg ); + + int GetNumReliables() const { return numReliable; } + const byte * GetReliable( int i ) const { return reliableMsgPtrs[ i ]; } + int GetReliableSize( int i ) const { return reliableMsgSize[ i ]; } + + void SetLastSendTime( int i ) { lastSendTime = i; } + int GetLastSendTime() const { return lastSendTime; } + float GetOutgoingRateBytes() const { return outgoingRateBytes; } + int GetOutgoingBytes() const { return outgoingBytes; } + float GetIncomingRateBytes() const { return incomingRateBytes; } + int GetIncomingBytes() const { return incomingBytes; } + + // more reliable computation, based on a suitably small interval + int GetOutgoingRate2() const { return currentOutgoingRate; } + int GetIncomingRate2() const { return currentIncomingRate; } + // decrease a fragmentation counter, so we reflect how much we're maxing the MTU + bool TickFragmentAccumulator() { if ( fragmentAccumulator > 0 ) { fragmentAccumulator--; return true; } return false; } + + int GetReliableDataSize() const { return reliable.GetDataLength(); } + + void VerifyEmptyReliableQueue( byte keepMsgBelowThis, byte replaceWithThisMsg ); + +private: + + // Packet header types + static const int PACKET_TYPE_INBAND = 0; // In-band. Number of reliable msg's stored in userData portion of header + static const int PACKET_TYPE_OOB = 1; // Out-of-band. userData free to use by the caller. Cannot fragment. + static const int PACKET_TYPE_RELIABLE_ACK = 2; // Header type used to piggy-back on top of msgs to ack reliable msg's + static const int PACKET_TYPE_FRAGMENTED = 3; // The msg is fragmented, fragment type stored in the userData portion of header + + // PACKET_TYPE_FRAGMENTED userData values + static const int FRAGMENT_START = 0; + static const int FRAGMENT_MIDDLE = 1; + static const int FRAGMENT_END = 2; + + class idOuterPacketHeader { + public: + idOuterPacketHeader() : sessionID( SESSION_ID_INVALID ) {} + idOuterPacketHeader( sessionId_t sessionID_ ) : sessionID( sessionID_ ) {} + + void WriteToMsg( idBitMsg & msg ) { + msg.WriteUShort( sessionID ); + } + + void ReadFromMsg( idBitMsg & msg ) { + sessionID = msg.ReadUShort(); + } + + sessionId_t GetSessionID() { return sessionID; } + private: + sessionId_t sessionID; + }; + + class idInnerPacketHeader { + public: + idInnerPacketHeader() : type( 0 ), userData( 0 ) {} + idInnerPacketHeader( int inType, int inData ) : type( inType ), userData( inData ) {} + + void WriteToMsg( idBitMsg & msg ) { + msg.WriteBits( type, 2 ); + msg.WriteBits( userData, 6 ); + } + + void ReadFromMsg( idBitMsg & msg ) { + type = msg.ReadBits( 2 ); + userData = msg.ReadBits( 6 ); + } + + int Type() { return type; } + int Value() { return userData; } + + private: + int type; + int userData; + }; + + byte msgBuffer[ MAX_MSG_SIZE ]; // Buffer used to reconstruct the msg + int msgWritePos; // Write position into the msg reconstruction buffer + int fragmentSequence; // Fragment sequence number + int droppedFrags; // Number of dropped fragments + bool fragmentedSend; // Used to determine if the current send requires fragmenting + + idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE > reliable; // list of unacknowledged reliable messages + + int reliableSequenceSend; // sequence number of the next reliable packet we're going to send to this peer + int reliableSequenceRecv; // sequence number of the last reliable packet we received from this peer + + // These are for receiving reliables, you need to get these before the next process call or they will get cleared + int numReliable; + byte reliableBuffer[ MAX_MSG_SIZE ]; // We shouldn't have to hold more than this + const byte * reliableMsgPtrs[ MAX_RELIABLE_QUEUE ]; + int reliableMsgSize[ MAX_RELIABLE_QUEUE ]; + + int queuedReliableAck; // Used to piggy back on the next send to ack reliables + + idBitMsg unsentMsg; + byte unsentBuffer[ MAX_MSG_SIZE ]; // Buffer used hold the current msg until it's all sent + + int lastSendTime; + + // variables to keep track of the rate + int outgoingRateTime; + float outgoingRateBytes; // B/S + int incomingRateTime; + float incomingRateBytes; // B/S + + int outgoingBytes; + int incomingBytes; + + int currentOutgoingRate; + int lastOutgoingRateTime; + int lastOutgoingBytes; + int currentIncomingRate; + int lastIncomingRateTime; + int lastIncomingBytes; + + + int fragmentAccumulator; // counts max size packets we are sending for the net debug hud +}; + +#endif /* !__PACKET_PROCESSOR_H__ */ diff --git a/neo/sys/Snapshot.cpp b/neo/sys/Snapshot.cpp new file mode 100644 index 00000000..cc813ec7 --- /dev/null +++ b/neo/sys/Snapshot.cpp @@ -0,0 +1,1281 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +idCVar net_verboseSnapshot( "net_verboseSnapshot", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" ); +idCVar net_verboseSnapshotCompression( "net_verboseSnapshotCompression", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" ); +idCVar net_verboseSnapshotReport( "net_verboseSnapshotReport", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" ); + +idCVar net_ssTemplateDebug( "net_ssTemplateDebug", "0", CVAR_BOOL, "Debug snapshot template states" ); +idCVar net_ssTemplateDebug_len( "net_ssTemplateDebug_len", "32", CVAR_INTEGER, "Offset to start template state debugging" ); +idCVar net_ssTemplateDebug_start( "net_ssTemplateDebug_start", "0", CVAR_INTEGER, "length of template state to print in debugging" ); + +/* +======================== +InDebugRange +Helper function for net_ssTemplateDebug debugging +======================== +*/ +bool InDebugRange( int i ) { + return ( i >= net_ssTemplateDebug_start.GetInteger() && i < net_ssTemplateDebug_start.GetInteger() + net_ssTemplateDebug_len.GetInteger() ); +} +/* +======================== +PrintAlign +Helper function for net_ssTemplateDebug debugging +======================== +*/ +void PrintAlign( const char * text ) { + idLib::Printf( "%25s: 0x", text ); +} + +/* +======================== +idSnapShot::objectState_t::Print +Helper function for net_ssTemplateDebug debugging +======================== +*/ +void idSnapShot::objectState_t::Print( const char * name ) { + + unsigned int start = (unsigned int)net_ssTemplateDebug_start.GetInteger(); + unsigned int end = Min( (unsigned int)buffer.Size(), start + net_ssTemplateDebug_len.GetInteger() ); + + PrintAlign( va( "%s: [sz %d]", name, buffer.Size() ) ); + + for ( unsigned int i = start; i < end; i++ ) { + idLib::Printf( "%02X", buffer[i] ); + } + idLib::Printf("\n"); +} + +/* +======================== +idSnapShot::objectBuffer_t::Alloc +======================== +*/ +void idSnapShot::objectBuffer_t::Alloc( int s ) { + //assert( mem.IsMapHeap() ); + if ( !verify( s < SIZE_NOT_STALE ) ) { + idLib::FatalError( "s >= SIZE_NOT_STALE" ); + } + _Release(); + data = (byte *)Mem_Alloc( s + 1, TAG_NETWORKING ); + size = s; + data[size] = 1; +} + +/* +======================== +idSnapShot::objectBuffer_t::AddRef +======================== +*/ +void idSnapShot::objectBuffer_t::_AddRef() { + if ( data != NULL ) { + assert( size > 0 ); + assert( data[size] < 255 ); + data[size]++; + } +} + +/* +======================== +idSnapShot::objectBuffer_t::Release +======================== +*/ +void idSnapShot::objectBuffer_t::_Release() { + //assert( mem.IsMapHeap() ); + if ( data != NULL ) { + assert( size > 0 ); + if ( --data[size] == 0 ) { + Mem_Free( data ); + } + data = NULL; + size = 0; + } +} + +/* +======================== +idSnapShot::objectBuffer_t::operator= +======================== +*/ +void idSnapShot::objectBuffer_t::operator=( const idSnapShot::objectBuffer_t & other ) { + //assert( mem.IsMapHeap() ); + if ( this != &other ) { + _Release(); + data = other.data; + size = other.size; + _AddRef(); + } +} + +/* +======================== +idSnapShot::idSnapShot +======================== +*/ +idSnapShot::idSnapShot() : + time( 0 ), + recvTime( 0 ) +{ +} + +/* +======================== +idSnapShot::idSnapShot +======================== +*/ +idSnapShot::idSnapShot( const idSnapShot & other ) : time( 0 ), recvTime(0) { + *this = other; +} + +/* +======================== +idSnapShot::~idSnapShot +======================== +*/ +idSnapShot::~idSnapShot() { + Clear(); +} + +/* +======================== +idSnapShot::Clear +======================== +*/ +void idSnapShot::Clear() { + time = 0; + recvTime = 0; + for ( int i = 0; i < objectStates.Num(); i++ ) { + FreeObjectState( i ); + } + objectStates.Clear(); + allocatedObjs.Shutdown(); +} + +/* +======================== +idSnapShot::operator= +======================== +*/ +void idSnapShot::operator=( const idSnapShot & other ) { + //assert( mem.IsMapHeap() ); + + if ( this != &other ) { + for ( int i = other.objectStates.Num(); i < objectStates.Num(); i++ ) { + FreeObjectState( i ); + } + objectStates.AssureSize( other.objectStates.Num(), NULL ); + for ( int i = 0; i < objectStates.Num(); i++ ) { + const objectState_t & otherState = *other.objectStates[i]; + if ( objectStates[i] == NULL ) { + objectStates[i] = allocatedObjs.Alloc(); + } + objectState_t & state = *objectStates[i]; + state.objectNum = otherState.objectNum; + state.buffer = otherState.buffer; + state.visMask = otherState.visMask; + state.stale = otherState.stale; + state.deleted = otherState.deleted; + state.changedCount = otherState.changedCount; + state.expectedSequence = otherState.expectedSequence; + state.createdFromTemplate = otherState.createdFromTemplate; + } + time = other.time; + recvTime = other.recvTime; + } +} + +/* +======================== +idSnapShot::PeekDeltaSequence +======================== +*/ +void idSnapShot::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & sequence, int & baseSequence ) { + lzwCompressionData_t lzwData; + idLZWCompressor lzwCompressor( &lzwData ); + + lzwCompressor.Start( (uint8*)deltaMem, deltaSize ); + lzwCompressor.ReadAgnostic( sequence ); + lzwCompressor.ReadAgnostic( baseSequence ); +} + +/* +======================== +idSnapShot::ReadDeltaForJob +======================== +*/ +bool idSnapShot::ReadDeltaForJob( const char * deltaMem, int deltaSize, int visIndex, idSnapShot * templateStates ) { + + bool report = net_verboseSnapshotReport.GetBool(); + net_verboseSnapshotReport.SetBool( false ); + + lzwCompressionData_t lzwData; + idZeroRunLengthCompressor rleCompressor; + idLZWCompressor lzwCompressor( &lzwData ); + int bytesRead = 0; // how many uncompressed bytes we read in. Used to figure out compression ratio + + lzwCompressor.Start( (uint8*)deltaMem, deltaSize ); + + // Skip past sequence and baseSequence + int sequence = 0; + int baseSequence = 0; + + lzwCompressor.ReadAgnostic( sequence ); + lzwCompressor.ReadAgnostic( baseSequence ); + lzwCompressor.ReadAgnostic( time ); + bytesRead += sizeof( int ) * 3; + + int objectNum = 0; + uint16 delta = 0; + + + while ( lzwCompressor.ReadAgnostic( delta, true ) == sizeof( delta ) ) { + bytesRead += sizeof( delta ); + + objectNum += delta; + if ( objectNum >= 0xFFFF ) { + // full delta + if ( net_verboseSnapshotCompression.GetBool() ) { + float compRatio = static_cast( deltaSize ) / static_cast( bytesRead ); + idLib::Printf( "Snapshot (%d/%d). ReadSize: %d DeltaSize: %d Ratio: %.3f\n", sequence, baseSequence, bytesRead, deltaSize, compRatio ); + } + return true; + } + + objectState_t & state = FindOrCreateObjectByID( objectNum ); + + objectSize_t newsize = 0; + lzwCompressor.ReadAgnostic( newsize ); + bytesRead += sizeof( newsize ); + + if ( newsize == SIZE_STALE ) { + NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum ); + // sanity + bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; + if ( !oldVisible ) { + NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" ); + } + state.visMask &= ~( 1 << visIndex ); + state.stale = true; + // We need to make sure we haven't freed stale objects. + assert( state.buffer.Size() > 0 ); + // no more data + continue; + } else if ( newsize == SIZE_NOT_STALE ) { + NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum ); + // sanity + bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; + if ( oldVisible ) { + NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" ); + } + state.visMask |= ( 1 << visIndex ); + state.stale = false; + // the latest state is packed in, get the new size and continue reading the new state + lzwCompressor.ReadAgnostic( newsize ); + bytesRead += sizeof( newsize ); + } + + objectState_t * objTemplateState = templateStates->FindObjectByID( objectNum ); + + if ( newsize == 0 ) { + // object deleted: reset state now so next one to use it doesn't have old data + state.deleted = false; + state.stale = false; + state.changedCount = 0; + state.expectedSequence = 0; + state.visMask = 0; + state.buffer._Release(); + state.createdFromTemplate = false; + + if ( objTemplateState != NULL && objTemplateState->buffer.Size() && objTemplateState->expectedSequence < baseSequence ) { + idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "Clearing old template state[%d] [%d<%d]\n", objectNum, objTemplateState->expectedSequence, baseSequence ); + objTemplateState->deleted = false; + objTemplateState->stale = false; + objTemplateState->changedCount = 0; + objTemplateState->expectedSequence = 0; + objTemplateState->visMask = 0; + objTemplateState->buffer._Release(); + } + + } else { + + // new state? + bool debug = false; + if ( state.buffer.Size() == 0 ) { + state.createdFromTemplate = true; + // Brand new state + if ( objTemplateState != NULL && objTemplateState->buffer.Size() > 0 && sequence >= objTemplateState->expectedSequence ) { + idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "\nAdding basestate for new object %d (for SS %d/%d. obj base created in ss %d) deltaSize: %d\n", objectNum, sequence, baseSequence, objTemplateState->expectedSequence, deltaSize ); + state.buffer = objTemplateState->buffer; + + if ( net_ssTemplateDebug.GetBool() ) { + state.Print( "SPAWN STATE" ); + debug = true; + PrintAlign( "DELTA STATE" ); + } + } else if ( net_ssTemplateDebug.GetBool() ) { + idLib::Printf("\nNew snapobject[%d] in snapshot %d/%d but no basestate found locally so creating new\n", objectNum, sequence, baseSequence ); + } + } else { + state.createdFromTemplate = false; + } + + // the buffer shrank or stayed the same + objectBuffer_t newbuffer( newsize ); + rleCompressor.Start( NULL, &lzwCompressor, newsize ); + objectSize_t compareSize = Min( state.buffer.Size(), newsize ); + for ( objectSize_t i = 0; i < compareSize; i++ ) { + byte b = rleCompressor.ReadByte(); + newbuffer[i] = state.buffer[i] + b; + + if ( debug && InDebugRange( i ) ) { + idLib::Printf( "%02X", b ); + } + } + // Catch leftover + if ( newsize > compareSize ) { + rleCompressor.ReadBytes( newbuffer.Ptr() + compareSize, newsize - compareSize ); + + if ( debug ) { + for ( objectSize_t i = compareSize; i < newsize; i++ ) { + if ( InDebugRange( i ) ) { + idLib::Printf( "%02X", newbuffer[i] ); + } + } + } + + } + state.buffer = newbuffer; + state.changedCount = sequence; + bytesRead += sizeof( byte ) * newsize; + if ( debug ) { + idLib::Printf( "\n" ); + state.Print( "NEW STATE" ); + } + + if ( report ) { + idLib::Printf( " Obj %d Compressed: Size %d \n", objectNum, rleCompressor.CompressedSize() ); + } + } +#ifdef SNAPSHOT_CHECKSUMS + extern uint32 SnapObjChecksum( const uint8 * data, int length ); + if ( state.buffer.Size() > 0 ) { + uint32 checksum = 0; + lzwCompressor.ReadAgnostic( checksum ); + bytesRead += sizeof( checksum ); + if ( !verify( checksum == SnapObjChecksum( state.buffer.Ptr(), state.buffer.Size() ) ) ) { + idLib::Error(" Invalid snapshot checksum" ); + } + } +#endif + } + // partial delta + return false; +} + +/* +======================== +idSnapShot::SubmitObjectJob +======================== +*/ + +void idSnapShot::SubmitObjectJob( const submitDeltaJobsInfo_t & submitDeltaJobsInfo, + objectState_t * newState, + objectState_t * oldState, + objParms_t *& baseObjParm, + objParms_t *& curObjParm, + objHeader_t *& curHeader, + uint8 *& curObjDest, + lzwParm_t *& curlzwParm +) { + assert( newState != NULL || oldState != NULL ); + assert_16_byte_aligned( curHeader ); + assert_16_byte_aligned( curObjDest ); + + int32 dataSize = newState != NULL ? newState->buffer.Size() : 0; + int totalSize = OBJ_DEST_SIZE_ALIGN16( dataSize ); + + if ( curObjParm - submitDeltaJobsInfo.objParms >= submitDeltaJobsInfo.maxObjParms ) { + idLib::Error( "Out of parms for snapshot jobs.\n"); + } + + // Check to see if we are out of dest write space, and need to flush the jobs + bool needToSubmit = ( curObjDest - submitDeltaJobsInfo.objMemory ) + totalSize >= submitDeltaJobsInfo.maxObjMemory; + needToSubmit |= ( curHeader - submitDeltaJobsInfo.headers >= submitDeltaJobsInfo.maxHeaders ); + + if ( needToSubmit ) { + // If this obj will put us over the limit, then submit the jobs now, and start over re-using the same buffers + SubmitLZWJob( submitDeltaJobsInfo, baseObjParm, curObjParm, curlzwParm, true ); + curHeader = submitDeltaJobsInfo.headers; + curObjDest = submitDeltaJobsInfo.objMemory; + } + + // Setup obj parms + assert( submitDeltaJobsInfo.visIndex < 256 ); + curObjParm->visIndex = submitDeltaJobsInfo.visIndex; + curObjParm->destHeader = curHeader; + curObjParm->dest = curObjDest; + + memset( &curObjParm->newState, 0, sizeof( curObjParm->newState ) ); + memset( &curObjParm->oldState, 0, sizeof( curObjParm->oldState ) ); + + if ( newState != NULL ) { + assert( newState->buffer.Size() <= 65535 ); + + curObjParm->newState.valid = 1; + curObjParm->newState.data = newState->buffer.Ptr(); + curObjParm->newState.size = newState->buffer.Size(); + curObjParm->newState.objectNum = newState->objectNum; + curObjParm->newState.visMask = newState->visMask; + } + + if ( oldState != NULL ) { + assert( oldState->buffer.Size() <= 65535 ); + + curObjParm->oldState.valid = 1; + curObjParm->oldState.data = oldState->buffer.Ptr(); + curObjParm->oldState.size = oldState->buffer.Size(); + curObjParm->oldState.objectNum = oldState->objectNum; + curObjParm->oldState.visMask = oldState->visMask; + } + + assert_16_byte_aligned( curObjParm ); + assert_16_byte_aligned( curObjParm->newState.data ); + assert_16_byte_aligned( curObjParm->oldState.data ); + + SnapshotObjectJob( curObjParm ); + + // Advance past header + data + curObjDest += totalSize; + + // Advance parm pointer + curObjParm++; + + // Advance header pointer + curHeader++; +} + +/* +======================== +idSnapShot::SubmitLZWJob +Take the current list of delta'd + zlre processed objects, and write them to the lzw stream. +======================== +*/ +void idSnapShot::SubmitLZWJob( + const submitDeltaJobsInfo_t & writeDeltaInfo, + objParms_t *& baseObjParm, // Pointer to the first obj parm for the current stream + objParms_t *& curObjParm, // Current obj parm + lzwParm_t *& curlzwParm, // Current delta parm + bool saveDictionary +) { + int numObjects = curObjParm - baseObjParm; + + if ( numObjects == 0 ) { + return; // Nothing to do + } + + if ( curlzwParm - writeDeltaInfo.lzwParms >= writeDeltaInfo.maxDeltaParms ) { + idLib::Error( "SubmitLZWJob: Not enough lzwParams.\n" ); + return; // Can't do anymore + } + + curlzwParm->numObjects = numObjects; + curlzwParm->headers = writeDeltaInfo.headers; // We always start grabbing from the beggining of the memory (it's reused, with fences to protect memory sharing) + curlzwParm->curTime = this->GetTime(); + curlzwParm->baseTime = writeDeltaInfo.oldSnap->GetTime(); + curlzwParm->baseSequence = writeDeltaInfo.baseSequence; + curlzwParm->fragmented = ( curlzwParm != writeDeltaInfo.lzwParms ); + curlzwParm->saveDictionary = saveDictionary; + + curlzwParm->ioData = writeDeltaInfo.lzwInOutData; + + LZWJob( curlzwParm ); + + curlzwParm++; + + // Set base so it now points to where the parms start for the new stream + baseObjParm = curObjParm; +} + +/* +======================== +idSnapShot::GetTemplateState +Helper function for getting template objectState. +newState parameter is optional and is just used for debugging/printf comparison of the template and actual state +======================== +*/ +idSnapShot::objectState_t * idSnapShot::GetTemplateState( int objNum, idSnapShot * templateStates, idSnapShot::objectState_t * newState /*=NULL*/ ) { + objectState_t * oldState = NULL; + int spawnedStateIndex = templateStates->FindObjectIndexByID( objNum ); + if ( spawnedStateIndex >= 0 ) { + oldState = templateStates->objectStates[ spawnedStateIndex ]; + + if ( net_ssTemplateDebug.GetBool() ) { + idLib::Printf( "\nGetTemplateState[%d]\n", objNum ); + oldState->Print( "SPAWN STATE" ); + if ( newState != NULL ) { + newState->Print( "CUR STATE" ); + } + } + } + return oldState; +} + +/* +======================== +idSnapShot::SubmitWriteDeltaToJobs +======================== +*/ +void idSnapShot::SubmitWriteDeltaToJobs( const submitDeltaJobsInfo_t & submitDeltaJobInfo ) { + objParms_t * curObjParms = submitDeltaJobInfo.objParms; + objParms_t * baseObjParms = submitDeltaJobInfo.objParms; + lzwParm_t * curlzwParms = submitDeltaJobInfo.lzwParms; + objHeader_t * curHeader = submitDeltaJobInfo.headers; + uint8 * curObjMemory = submitDeltaJobInfo.objMemory; + + submitDeltaJobInfo.lzwInOutData->numlzwDeltas = 0; + submitDeltaJobInfo.lzwInOutData->lzwBytes = 0; + submitDeltaJobInfo.lzwInOutData->fullSnap = false; + + int j = 0; + + int numOldStates = submitDeltaJobInfo.oldSnap->objectStates.Num(); + + for ( int i = 0; i < objectStates.Num(); i++ ) { + objectState_t & newState = *objectStates[i]; + if ( !verify( newState.buffer.Size() > 0 ) ) { + // you CANNOT have a valid ss obj state w/ size = 0: this will be interpreted as a delete in ::ReadDelta and this will completely throw + // off delta compression, eventually resulting in a checksum error that is a pain to track down. + idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum ); + continue; + } + + if ( j >= numOldStates ) { + // We no longer have old objects to compare to. + // All objects are new from this point on. + + objectState_t * oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState ); + SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); + continue; + } + + // write any deleted entities up to this one + for ( ; j < numOldStates && newState.objectNum > submitDeltaJobInfo.oldSnap->objectStates[j]->objectNum; j++ ) { + objectState_t & oldState = *submitDeltaJobInfo.oldSnap->objectStates[j]; + + if ( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 ) { + continue; // Don't delete objects that are stale and not marked as deleted + } + + SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); + } + + if ( j >= numOldStates ) { + continue; // Went past end of old list deleting objects + } + + // Beyond this point, we may have old state to compare against + objectState_t & submittedOldState = *submitDeltaJobInfo.oldSnap->objectStates[j]; + objectState_t * oldState = &submittedOldState; + + if ( newState.objectNum == oldState->objectNum ) { + if ( oldState->buffer.Size() == 0 ) { + // New state (even though snapObj existed, its size was zero) + oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState ); + } + + SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); + j++; + } else { + // Different object, this one is new, + // Spawned + oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState ); + SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); + } + } + // Finally, remove any entities at the end + for ( ; j < submitDeltaJobInfo.oldSnap->objectStates.Num(); j++ ) { + objectState_t & oldState = *submitDeltaJobInfo.oldSnap->objectStates[j]; + + if ( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 ) { + continue; // Don't delete objects that are stale and not marked as deleted + } + + SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); + } + + // Submit any objects that are left over (will be all if they all fit up to this point) + SubmitLZWJob( submitDeltaJobInfo, baseObjParms, curObjParms, curlzwParms, false ); +} + +/* +======================== +idSnapShot::ReadDelta +======================== +*/ +bool idSnapShot::ReadDelta( idFile * file, int visIndex ) { + + file->ReadBig( time ); + + int objectNum = 0; + uint16 delta = 0; + while ( file->ReadBig( delta ) == sizeof( delta ) ) { + objectNum += delta; + if ( objectNum >= 0xFFFF ) { + // full delta + return true; + } + objectState_t & state = FindOrCreateObjectByID( objectNum ); + objectSize_t newsize = 0; + file->ReadBig( newsize ); + + if ( newsize == SIZE_STALE ) { + NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum ); + // sanity + bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; + if ( !oldVisible ) { + NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" ); + } + state.visMask &= ~( 1 << visIndex ); + state.stale = true; + // We need to make sure we haven't freed stale objects. + assert( state.buffer.Size() > 0 ); + // no more data + continue; + } else if ( newsize == SIZE_NOT_STALE ) { + NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum ); + // sanity + bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; + if ( oldVisible ) { + NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" ); + } + state.visMask |= ( 1 << visIndex ); + state.stale = false; + // the latest state is packed in, get the new size and continue reading the new state + file->ReadBig( newsize ); + } + + if ( newsize == 0 ) { + // object deleted + state.buffer._Release(); + } else { + objectBuffer_t newbuffer( newsize ); + objectSize_t compareSize = Min( newsize, state.buffer.Size() ); + + for ( objectSize_t i = 0; i < compareSize; i++ ) { + uint8 delta = 0; + file->ReadBig( delta ); + newbuffer[i] = state.buffer[i] + delta; + } + + if ( newsize > compareSize ) { + file->Read( newbuffer.Ptr() + compareSize, newsize - compareSize ); + } + + state.buffer = newbuffer; + state.changedCount++; + } + +#ifdef SNAPSHOT_CHECKSUMS + if ( state.buffer.Size() > 0 ) { + unsigned int checksum = 0; + file->ReadBig( checksum ); + assert( checksum == MD5_BlockChecksum( state.buffer.Ptr(), state.buffer.Size() ) ); + } +#endif + } + + // partial delta + return false; +} + +/* +======================== +idSnapShot::WriteObject +======================== +*/ +void idSnapShot::WriteObject( idFile * file, int visIndex, objectState_t * newState, objectState_t * oldState, int & lastobjectNum ) { + assert( newState != NULL || oldState != NULL ); + + bool visChange = false; // visibility changes will be signified with a 0xffff state size + bool visSendState = false; // the state is sent when an entity is no longer stale + + // Compute visibility changes + // (we need to do this before writing out object id, because we may not need to write out the id if we early out) + // (when we don't write out the id, we assume this is an "ack" when we deserialize the objects) + if ( newState != NULL && oldState != NULL ) { + // Check visibility + assert( newState->objectNum == oldState->objectNum ); + + if ( visIndex > 0 ) { + bool oldVisible = ( oldState->visMask & ( 1 << visIndex ) ) != 0; + bool newVisible = ( newState->visMask & ( 1 << visIndex ) ) != 0; + + // Force visible if we need to either create or destroy this object + newVisible |= ( newState->buffer.Size() == 0 ) != ( oldState->buffer.Size() == 0 ); + + if ( !oldVisible && !newVisible ) { + // object is stale and ack'ed for this client, write nothing (see 'same object' below) + return; + } else if ( oldVisible && !newVisible ) { + NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex ); + visChange = true; + visSendState = false; + } else if ( !oldVisible && newVisible ) { + NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d no longer stale\n", newState->objectNum, visIndex ); + visChange = true; + visSendState = true; + } + } + + // Same object, write a delta (never early out during vis changes) + if ( !visChange && newState->buffer.Size() == oldState->buffer.Size() && + ( ( newState->buffer.Ptr() == oldState->buffer.Ptr() ) || memcmp( newState->buffer.Ptr(), oldState->buffer.Ptr(), newState->buffer.Size() ) == 0 ) ) { + // same state, write nothing + return; + } + } + + // Get the id of the object we are writing out + uint16 objectNum; + if ( newState != NULL ) { + objectNum = newState->objectNum; + } else if ( oldState != NULL ) { + objectNum = oldState->objectNum; + } else { + objectNum = 0; + } + + assert( objectNum == 0 || objectNum > lastobjectNum ); + + // Write out object id (using delta) + uint16 objectDelta = objectNum - lastobjectNum; + file->WriteBig( objectDelta ); + lastobjectNum = objectNum; + + if ( newState == NULL ) { + // Deleted, write 0 size + assert( oldState != NULL ); + file->WriteBig( 0 ); + } else if ( oldState == NULL ) { + // New object, write out full state + assert( newState != NULL ); + // delta against an empty snap + file->WriteBig( newState->buffer.Size() ); + file->Write( newState->buffer.Ptr(), newState->buffer.Size() ); + } else { + // Compare to last object + assert( newState != NULL && oldState != NULL ); + assert( newState->objectNum == oldState->objectNum ); + + if ( visChange ) { + // fake size indicates vis state change + // NOTE: we may still send a real size and a state below, for 'no longer stale' transitions + // TMP: send 0xFFFF for going stale and 0xFFFF - 1 for no longer stale + file->WriteBig( visSendState ? SIZE_NOT_STALE : SIZE_STALE ); + } + if ( !visChange || visSendState ) { + + objectSize_t compareSize = Min( newState->buffer.Size(), oldState->buffer.Size() ); // Get the number of bytes that overlap + + file->WriteBig( newState->buffer.Size() ); // Write new size + + // Compare bytes that overlap + for ( objectSize_t b = 0; b < compareSize; b++ ) { + file->WriteBig( ( 0xFF + 1 + newState->buffer[b] - oldState->buffer[b] ) & 0xFF ); + } + + // Write leftover + if ( newState->buffer.Size() > compareSize ) { + file->Write( newState->buffer.Ptr() + oldState->buffer.Size(), newState->buffer.Size() - compareSize ); + } + } + } + +#ifdef SNAPSHOT_CHECKSUMS + if ( ( !visChange || visSendState ) && newState != NULL ) { + assert( newState->buffer.Size() > 0 ); + unsigned int checksum = MD5_BlockChecksum( newState->buffer.Ptr(), newState->buffer.Size() ); + file->WriteBig( checksum ); + } +#endif +} + +/* +======================== +idSnapShot::PrintReport +======================== +*/ +void idSnapShot::PrintReport() { +} + +/* +======================== +idSnapShot::WriteDelta +======================== +*/ +bool idSnapShot::WriteDelta( idSnapShot & old, int visIndex, idFile * file, int maxLength, int optimalLength ) { + file->WriteBig( time ); + + int objectHeaderSize = sizeof( uint16 ) + sizeof( objectSize_t ); +#ifdef SNAPSHOT_CHECKSUMS + objectHeaderSize += sizeof( unsigned int ); +#endif + + int lastobjectNum = 0; + int j = 0; + + for ( int i = 0; i < objectStates.Num(); i++ ) { + objectState_t & newState = *objectStates[i]; + + if ( optimalLength > 0 && file->Length() >= optimalLength ) { + return false; + } + + if ( !verify( newState.buffer.Size() < maxLength ) ) { + // If the new state's size is > the max packet size, we'll never be able to send it! + idLib::Warning( "Snap obj [%d] state.size > max packet length. Skipping... ", newState.objectNum ); + continue; + } else if ( !verify( newState.buffer.Size() > 0 ) ) { + // you CANNOT have a valid ss obj state w/ size = 0: this will be interpreted as a delete in ::ReadDelta and this will completely throw + // off delta compression, eventually resulting in a checksum error that is a pain to track down. + idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum ); + continue; + } + + if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) { + return false; + } + + if ( j >= old.objectStates.Num() ) { + // delta against an empty snap + WriteObject( file, visIndex, &newState, NULL, lastobjectNum ); + continue; + } + + // write any deleted entities up to this one + for ( ; newState.objectNum > old.objectStates[j]->objectNum; j++ ) { + if ( file->Length() + objectHeaderSize >= maxLength ) { + return false; + } + objectState_t & oldState = *old.objectStates[j]; + WriteObject( file, visIndex, NULL, &oldState, lastobjectNum ); + } + + // Beyond this point, we have old state to compare against + objectState_t & oldState = *old.objectStates[j]; + + if ( newState.objectNum == oldState.objectNum ) { + // FIXME: We don't need to early out if WriteObject determines that we won't send the object due to being stale + if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) { + return false; + } + WriteObject( file, visIndex, &newState, &oldState, lastobjectNum ); + j++; + } else { + if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) { + return false; + } + + // Different object, this one is new, write the full state + WriteObject( file, visIndex, &newState, NULL, lastobjectNum ); + } + } + // Finally, remove any entities at the end + for ( ; j < old.objectStates.Num(); j++ ) { + if ( file->Length() + objectHeaderSize >= maxLength ) { + return false; + } + + if ( optimalLength > 0 && file->Length() >= optimalLength ) { + return false; + } + + objectState_t & oldState = *old.objectStates[j]; + WriteObject( file, visIndex, NULL, &oldState, lastobjectNum ); + } + if ( file->Length() + 2 >= maxLength ) { + return false; + } + uint16 objectDelta = 0xFFFF - lastobjectNum; + file->WriteBig( objectDelta ); + + return true; +} + +/* +======================== +idSnapShot::AddObject +======================== +*/ +idSnapShot::objectState_t * idSnapShot::S_AddObject( int objectNum, uint32 visMask, const char * data, int _size, const char * tag ) { + objectSize_t size = _size; + objectState_t & state = FindOrCreateObjectByID( objectNum ); + state.visMask = visMask; + if ( state.buffer.Size() == size && state.buffer.NumRefs() == 1 ) { + // re-use the same buffer + memcpy( state.buffer.Ptr(), data, size ); + } else { + objectBuffer_t buffer( size ); + memcpy( buffer.Ptr(), data, size ); + state.buffer = buffer; + } + return &state; +} + +/* +======================== +idSnapShot::CopyObject +======================== +*/ + +bool idSnapShot::CopyObject( const idSnapShot & oldss, int objectNum, bool forceStale ) { + + int oldIndex = oldss.FindObjectIndexByID( objectNum ); + if ( oldIndex == -1 ) { + return false; + } + + const objectState_t & oldState = *oldss.objectStates[oldIndex]; + objectState_t & newState = FindOrCreateObjectByID( objectNum ); + + newState.buffer = oldState.buffer; + newState.visMask = oldState.visMask; + newState.stale = oldState.stale; + newState.deleted = oldState.deleted; + newState.changedCount = oldState.changedCount; + newState.expectedSequence = oldState.expectedSequence; + newState.createdFromTemplate = oldState.createdFromTemplate; + + if ( forceStale ) { + newState.visMask = 0; + } + + return true; +} + +/* +======================== +idSnapShot::CompareObject +start, end, and oldStart can optionally be passed in to compare subsections of the object +default parameters will compare entire object +======================== +*/ +int idSnapShot::CompareObject( const idSnapShot * oldss, int objectNum, int start, int end, int oldStart ) { + if ( oldss == NULL ) { + return 0; + } + + assert( FindObjectIndexByID( objectNum ) >= 0 ); + + objectState_t & newState = FindOrCreateObjectByID( objectNum ); + + int oldIndex = oldss->FindObjectIndexByID( objectNum ); + if ( oldIndex == -1 ) { + return ( end == 0 ? newState.buffer.Size() : end - start ); // Didn't exist in the old state, so we take the hit on the entire size + } + + objectState_t & oldState = const_cast< objectState_t & >( *oldss->objectStates[oldIndex] ); + + int bytes = 0; + + int oldOffset = oldStart - start; + int commonSize = ( newState.buffer.Size() <= oldState.buffer.Size() - oldOffset ) ? newState.buffer.Size() : oldState.buffer.Size() - oldOffset; + if ( end == 0 ) { + // default 0 means compare the whole thing + end = commonSize; + + // Get leftover (if any) + bytes = ( newState.buffer.Size() > oldState.buffer.Size() ) ? ( newState.buffer.Size() - oldState.buffer.Size() ) : 0; + } else { + + // else only compare up to end or the max buffer and dont include leftover + end = Min( commonSize, end ); + } + + for ( int b = start; b < end; b++ ) { + if ( verify( b >= 0 && b < (int)newState.buffer.Size() && b + oldOffset >= 0 && b + oldOffset < (int)oldState.buffer.Size() ) ) { + bytes += ( newState.buffer[b] != oldState.buffer[b + oldOffset] ) ? 1 : 0; + } + } + + return bytes; +} + +/* +======================== +idSnapShot::GetObjectMsgByIndex +======================== +*/ +int idSnapShot::GetObjectMsgByIndex( int i, idBitMsg & msg, bool ignoreIfStale ) const { + if ( i < 0 || i >= objectStates.Num() ) { + return -1; + } + objectState_t & state = *objectStates[i]; + if ( state.stale && ignoreIfStale ) { + return -1; + } + msg.InitRead( state.buffer.Ptr(), state.buffer.Size() ); + return state.objectNum; +} + +/* +======================== +idSnapShot::ObjectIsStaleByIndex +======================== +*/ +bool idSnapShot::ObjectIsStaleByIndex( int i ) const { + if ( i < 0 || i >= objectStates.Num() ) { + return false; + } + return objectStates[i]->stale; +} + +/* +======================== +idSnapShot::ObjectChangedCountByIndex +======================== +*/ +int idSnapShot::ObjectChangedCountByIndex( int i ) const { + if ( i < 0 || i >= objectStates.Num() ) { + return false; + } + return objectStates[i]->changedCount; +} + +/* +======================== +idSnapShot::FindObjectIndexByID +======================== +*/ +int idSnapShot::FindObjectIndexByID( int objectNum ) const { + int i = BinarySearch( objectNum ); + if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) { + return i; + } + return -1; +} + +/* +======================== +idSnapShot::BinarySearch +======================== +*/ +int idSnapShot::BinarySearch( int objectNum ) const { + int lo = 0; + int hi = objectStates.Num(); + while ( hi != lo ) { + int mid = ( hi + lo ) >> 1; + + if ( objectStates[mid]->objectNum == objectNum ) { + return mid; // Early out if we can + } + + if ( objectStates[mid]->objectNum < objectNum ) { + lo = mid + 1; + } else { + hi = mid; + } + } + return hi; +} + +/* +======================== +idSnapShot::FindOrCreateObjectByID +======================== +*/ +idSnapShot::objectState_t & idSnapShot::FindOrCreateObjectByID( int objectNum ) { + //assert( mem.IsMapHeap() ); + + int i = BinarySearch( objectNum ); + + if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) { + return *objectStates[i]; + } + + objectState_t * newstate = allocatedObjs.Alloc(); + newstate->objectNum = objectNum; + + objectStates.Insert( newstate, i ); + + return *objectStates[i]; +} + +/* +======================== +idSnapShot::FindObjectByID +======================== +*/ +idSnapShot::objectState_t * idSnapShot::FindObjectByID( int objectNum ) const { + + //assert( mem.IsMapHeap() ); + + int i = BinarySearch( objectNum ); + + if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) { + return objectStates[i]; + } + + return NULL; +} + +/* +======================== +idSnapShot::CleanupEmptyStates +======================== +*/ +void idSnapShot::CleanupEmptyStates() { + for ( int i = objectStates.Num() - 1; i >= 0 ; i-- ) { + if ( objectStates[i]->buffer.Size() == 0 ) { + FreeObjectState( i ); + objectStates.RemoveIndex(i); + } + } +} + +/* +======================== +idSnapShot::UpdateExpectedSeq +======================== +*/ +void idSnapShot::UpdateExpectedSeq( int newSeq ) { + for ( int i = 0; i < objectStates.Num(); i++ ) { + if ( objectStates[i]->expectedSequence == -2 ) { + objectStates[i]->expectedSequence = newSeq; + } + } +} + +/* +======================== +idSnapShot::FreeObjectState +======================== +*/ +void idSnapShot::FreeObjectState( int index ) { + assert( objectStates[index] != NULL ); + //assert( mem.IsMapHeap() ); + objectStates[index]->buffer._Release(); + allocatedObjs.Free( objectStates[index] ); + objectStates[index] = NULL; +} + +/* +======================== +idSnapShot::ApplyToExistingState +Take uncompressed state in msg and add it to existing state +======================== +*/ +void idSnapShot::ApplyToExistingState( int objId, idBitMsg & msg ) { + objectState_t * objectState = FindObjectByID( objId ); + if ( !verify( objectState != NULL ) ) { + return; + } + + if ( !objectState->createdFromTemplate ) { + // We were created this from a template, so we shouldn't be applying it again + if ( net_ssTemplateDebug.GetBool() ) { + idLib::Printf( "NOT ApplyToExistingState[%d] because object was created from existing base state. %d\n", objId, objectState->expectedSequence ); + objectState->Print( "SS STATE" ); + } + return; + } + + // Debug print the template (spawn) and delta state + if ( net_ssTemplateDebug.GetBool() ) { + idLib::Printf( "\nApplyToExistingState[%d]. buffer size: %d msg size: %d\n", objId, objectState->buffer.Size(), msg.GetSize() ); + objectState->Print( "DELTA STATE" ); + + PrintAlign( "SPAWN STATE" ); + for ( int i = 0; i < msg.GetSize(); i++ ) { + if ( InDebugRange( i ) ) { + idLib::Printf( "%02X", msg.GetReadData()[i] ); + } + } + idLib::Printf( "\n" ); + } + + // Actually apply it + for ( objectSize_t i = 0; i < Min( objectState->buffer.Size(), msg.GetSize() ); i++ ) { + objectState->buffer[i] += msg.GetReadData()[i]; + } + + // Debug print the final state + if ( net_ssTemplateDebug.GetBool() ) { + objectState->Print( "NEW STATE" ); + idLib::Printf( "\n" ); + } +} + +#if 0 +CONSOLE_COMMAND( serializeQTest, "Serialization Sanity Test", 0 ) { + + byte buffer[1024]; + memset( buffer, 0, sizeof( buffer ) ); + + float values[] = { 0.0001f, 0.001f, 0.01f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.999f, + 1.0f, 1.01f, 1.1f, 10.0f, 10.1f, 10.101f, 100.0f, 101.0f, 101.1f, 101.101f }; + int num = sizeof(values) / sizeof(float); + + idLib::Printf("\n^3Testing SerializeQ and SerializeUQ \n"); + + { + idBitMsg writeBitMsg; + writeBitMsg.InitWrite( buffer, sizeof(buffer) ); + idSerializer writeSerializer( writeBitMsg, true ); + + for( int i = 0; i < num; i++ ) { + writeSerializer.SerializeUQ( values[i], 255.0f, 16 ); + writeSerializer.SerializeQ( values[i], 128.0f, 16 ); + } + } + + { + idBitMsg readBitMsg; + readBitMsg.InitRead( buffer, sizeof( buffer ) ); + idSerializer readSerializer( readBitMsg, false ); + + for( int i = 0; i < num; i++ ) { + + float resultUQ = -999.0f; + float resultQ = -999.0f; + + readSerializer.SerializeUQ( resultUQ, 255.0f, 16 ); + readSerializer.SerializeQ( resultQ, 128.0f, 16 ); + + float errorUQ = idMath::Fabs( (resultUQ - values[i]) ) / values[i]; + float errorQ = idMath::Fabs( (resultQ - values[i]) ) / values[i]; + + idLib::Printf( "%s%f SerializeUQ: %f. Error: %f \n", errorUQ > 0.1f ? "^1": "", values[i], resultUQ, errorUQ ); + idLib::Printf( "%s%f SerializeQ: %f. Error: %f \n", errorQ > 0.1f ? "^1": "", values[i], resultQ, errorQ ); + } + } +} +#endif diff --git a/neo/sys/Snapshot.h b/neo/sys/Snapshot.h new file mode 100644 index 00000000..3319fb0f --- /dev/null +++ b/neo/sys/Snapshot.h @@ -0,0 +1,201 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SNAPSHOT_H__ +#define __SNAPSHOT_H__ + +#include "snapshot_jobs.h" + +extern idCVar net_verboseSnapshot; +#define NET_VERBOSESNAPSHOT_PRINT if ( net_verboseSnapshot.GetInteger() > 0 ) idLib::Printf +#define NET_VERBOSESNAPSHOT_PRINT_LEVEL( X, Y ) if ( net_verboseSnapshot.GetInteger() >= ( X ) ) idLib::Printf( Y ) + +/* +A snapshot contains a list of objects and their states +*/ +class idSnapShot { +public: + idSnapShot(); + idSnapShot( const idSnapShot & other ); + ~idSnapShot(); + + void operator=( const idSnapShot & other ); + + // clears the snapshot + void Clear(); + + int GetTime() const { return time; } + void SetTime( int t ) { time = t; } + + int GetRecvTime() const { return recvTime; } + void SetRecvTime( int t ) { recvTime = t; } + + // Loads only sequence and baseSequence values from the compressed stream + static void PeekDeltaSequence( const char * deltaMem, int deltaSize, int & sequence, int & baseSequence ); + + // Reads a new object state packet, which is assumed to be delta compressed against this snapshot + bool ReadDeltaForJob( const char * deltaMem, int deltaSize, int visIndex, idSnapShot * templateStates ); + bool ReadDelta( idFile * file, int visIndex ); + + // Writes an object state packet which is delta compressed against the old snapshot + struct objectBuffer_t { + objectBuffer_t() : data( NULL ), size( 0 ) { } + objectBuffer_t( int s ) : data( NULL ), size( s ) { Alloc( s ); } + objectBuffer_t( const objectBuffer_t & o ) : data( NULL ), size( 0 ) { *this = o; } + ~objectBuffer_t() { _Release(); } + void Alloc( int size ); + int NumRefs() { return data == NULL ? 0 : data[size]; } + objectSize_t Size() const { return size; } + byte * Ptr() { return data == NULL ? NULL : data ; } + byte & operator[]( int i ) { return data[i]; } + void operator=( const objectBuffer_t & other ); + + // (not making private because of idSnapshot) + void _AddRef(); + void _Release(); + private: + byte * data; + objectSize_t size; + }; + + struct objectState_t { + objectState_t() : + objectNum( 0 ), + visMask( MAX_UNSIGNED_TYPE( uint32 ) ), + stale( false ), + deleted( false ), + changedCount( 0 ), + createdFromTemplate( false ), + + expectedSequence( 0 ) + { } + void Print( const char * name ); + + uint16 objectNum; + objectBuffer_t buffer; + uint32 visMask; + bool stale; // easy way for clients to check if ss obj is stale. Probably temp till client side of vismask system is more fleshed out + bool deleted; + int changedCount; // Incremented each time the state changed + int expectedSequence; + bool createdFromTemplate; + }; + + struct submitDeltaJobsInfo_t { + objParms_t * objParms; // Start of object parms + int maxObjParms; // Max parms (which will dictate how many objects can be processed) + uint8 * objMemory; // Memory that objects were written out to + objHeader_t * headers; // Memory for headers + int maxHeaders; + int maxObjMemory; // Max memory (which will dictate when syncs need to occur) + lzwParm_t * lzwParms; // Start of lzw parms + int maxDeltaParms; // Max lzw parms (which will dictate how many syncs we can have) + + idSnapShot * oldSnap; // snap we are comparing this snap to (to produce a delta) + int visIndex; + int baseSequence; + + idSnapShot * templateStates; // states for new snapObj that arent in old states + + lzwInOutData_t * lzwInOutData; + }; + + void SubmitWriteDeltaToJobs( const submitDeltaJobsInfo_t & submitDeltaJobInfo ); + + bool WriteDelta( idSnapShot & old, int visIndex, idFile * file, int maxLength, int optimalLength = 0 ); + + // Adds an object to the state, overwrites any existing object with the same number + objectState_t * S_AddObject( int objectNum, uint32 visMask, const idBitMsg & msg, const char * tag = NULL ) { return S_AddObject( objectNum, visMask, msg.GetReadData(), msg.GetSize(), tag ); } + objectState_t * S_AddObject( int objectNum, uint32 visMask, const byte * buffer, int size, const char * tag = NULL ) { return S_AddObject( objectNum, visMask, (const char *)buffer, size, tag ); } + objectState_t * S_AddObject( int objectNum, uint32 visMask, const char * buffer, int size, const char * tag = NULL ); + bool CopyObject( const idSnapShot & oldss, int objectNum, bool forceStale = false ); + int CompareObject( const idSnapShot * oldss, int objectNum, int start=0, int end=0, int oldStart=0 ); + + // returns the number of objects in this snapshot + int NumObjects() const { return objectStates.Num(); } + + // Returns the object number of the specified object, also fills the bitmsg + int GetObjectMsgByIndex( int i, idBitMsg & msg, bool ignoreIfStale = false ) const; + + // returns true if the object was found in the snapshot + bool GetObjectMsgByID( int objectNum, idBitMsg & msg, bool ignoreIfStale = false ) { return GetObjectMsgByIndex( FindObjectIndexByID( objectNum ), msg, ignoreIfStale ) == objectNum; } + + // returns the object index or -1 if it's not found + int FindObjectIndexByID( int objectNum ) const; + + // returns the object by id, or NULL if not found + objectState_t * FindObjectByID( int objectNum ) const; + + // Returns whether or not an object is stale + bool ObjectIsStaleByIndex( int i ) const; + + int ObjectChangedCountByIndex( int i ) const; + + // clears the empty states from the snapshot snapshot + void CleanupEmptyStates(); + + void PrintReport(); + + void UpdateExpectedSeq( int newSeq ); + + void ApplyToExistingState( int objId, idBitMsg & msg ); + objectState_t * GetTemplateState( int objNum, idSnapShot * templateStates, objectState_t * newState = NULL ); + + void RemoveObject( int objId ); + +private: + + idList< objectState_t *, TAG_IDLIB_LIST_SNAPSHOT> objectStates; + idBlockAlloc< objectState_t, 16, TAG_NETWORKING > allocatedObjs; + + int time; + int recvTime; + + int BinarySearch( int objectNum ) const; + objectState_t & FindOrCreateObjectByID( int objectNum ); // objIndex is optional parm for returning the index of the obj + + void SubmitObjectJob( const submitDeltaJobsInfo_t & submitDeltaJobsInfo, // Struct containing parameters originally passed in to SubmitWriteDeltaToJobs + objectState_t * newState, // New obj state (can be NULL, which means deleted) + objectState_t * oldState, // Old obj state (can be NULL, which means new) + objParms_t *& baseObjParm, // Starting obj parm of current stream + objParms_t *& curObjParm, // Current obj parm of current stream + objHeader_t *& curHeader, // Current header dest + uint8 *& curObjDest, // Current write pos of current obj + lzwParm_t *& curlzwParm ); // Current delta parm for next lzw job + void SubmitLZWJob( + const submitDeltaJobsInfo_t & writeDeltaInfo, // Struct containing parameters originally passed in to SubmitWriteDeltaToJobs + objParms_t *& baseObjParm, // Pointer to the first obj parm for the current stream + objParms_t *& curObjParm, // Current obj parm + lzwParm_t *& curlzwParm, // Current delta parm + bool saveDictionary // If true, this is the first of several calls which will be appended + ); + + void WriteObject( idFile * file, int visIndex, objectState_t * newState, objectState_t * oldState, int & lastobjectNum ); + void FreeObjectState( int index ); +}; + +#endif // __SNAPSHOT_H__ diff --git a/neo/sys/SnapshotProcessor.cpp b/neo/sys/SnapshotProcessor.cpp new file mode 100644 index 00000000..d81eaa79 --- /dev/null +++ b/neo/sys/SnapshotProcessor.cpp @@ -0,0 +1,524 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +idCVar net_optimalSnapDeltaSize( "net_optimalSnapDeltaSize", "1000", CVAR_INTEGER, "Optimal size of snapshot delta msgs." ); +idCVar net_debugBaseStates( "net_debugBaseStates", "0", CVAR_BOOL, "Log out base state information" ); +idCVar net_skipClientDeltaAppend( "net_skipClientDeltaAppend", "0", CVAR_BOOL, "Simulate delta receive buffer overflowing" ); + +/* +======================== +idSnapshotProcessor::idSnapshotProcessor +======================== +*/ +idSnapshotProcessor::idSnapshotProcessor() { + + //assert( mem.IsGlobalHeap() ); + + jobMemory = (jobMemory_t*)Mem_Alloc( sizeof( jobMemory_t) , TAG_NETWORKING ); + + assert_16_byte_aligned( jobMemory ); + assert_16_byte_aligned( jobMemory->objParms.Ptr() ); + assert_16_byte_aligned( jobMemory->headers.Ptr() ); + assert_16_byte_aligned( jobMemory->lzwParms.Ptr() ); + + Reset( true ); +} + +/* +======================== +idSnapshotProcessor::idSnapshotProcessor +======================== +*/ +idSnapshotProcessor::~idSnapshotProcessor() { + //mem.PushHeap(); + Mem_Free( jobMemory ); + //mem.PopHeap(); +} + +/* +======================== +idSnapshotProcessor::Reset +======================== +*/ +void idSnapshotProcessor::Reset( bool cstor ) { + hasPendingSnap = false; + snapSequence = INITIAL_SNAP_SEQUENCE; + baseSequence = -1; + lastFullSnapBaseSequence = -1; + + if ( !cstor && net_debugBaseStates.GetBool() ) { + idLib::Printf( "NET: Reset snapshot base"); + } + + baseState.Clear(); + submittedState.Clear(); + pendingSnap.Clear(); + deltas.Clear(); + + partialBaseSequence = -1; + + memset( &jobMemory->lzwInOutData, 0, sizeof( jobMemory->lzwInOutData ) ); +} + +/* +======================== +idSnapshotProcessor::TrySetPendingSnapshot +======================== +*/ +bool idSnapshotProcessor::TrySetPendingSnapshot( idSnapShot & ss ) { + // Don't advance to the next snap until the last one was fully sent + if ( hasPendingSnap ) { + return false; + } + pendingSnap = ss; + hasPendingSnap = true; + return true; +} + +/* +======================== +idSnapshotProcessor::PeekDeltaSequence +======================== +*/ +void idSnapshotProcessor::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & deltaSequence, int & deltaBaseSequence ) { + idSnapShot::PeekDeltaSequence( deltaMem, deltaSize, deltaSequence, deltaBaseSequence ); +} + +/* +======================== +idSnapshotProcessor::ApplyDeltaToSnapshot +======================== +*/ +bool idSnapshotProcessor::ApplyDeltaToSnapshot( idSnapShot & snap, const char * deltaMem, int deltaSize, int visIndex ) { + return snap.ReadDeltaForJob( deltaMem, deltaSize, visIndex, &templateStates ); +} + +#ifdef STRESS_LZW_MEM +// When this defined, we'll stress the lzw compressor with the smallest possible buffer, and detect when we need to grow it to make +// sure we are gacefully detecting the situation. +static int g_maxlwMem = 100; +#endif + +/* +======================== +idSnapshotProcessor::SubmitPendingSnap +======================== +*/ +void idSnapshotProcessor::SubmitPendingSnap( int visIndex, uint8 * objMemory, int objMemorySize, lzwCompressionData_t * lzwData ) { + + assert_16_byte_aligned( objMemory ); + assert_16_byte_aligned( lzwData ); + + assert( hasPendingSnap ); + assert( jobMemory->lzwInOutData.numlzwDeltas == 0 ); + + assert( net_optimalSnapDeltaSize.GetInteger() < jobMemory_t::MAX_LZW_MEM - 128 ); // Leave padding + + jobMemory->lzwInOutData.lzwDeltas = jobMemory->lzwDeltas.Ptr(); + jobMemory->lzwInOutData.maxlzwDeltas = jobMemory->lzwDeltas.Num(); + jobMemory->lzwInOutData.lzwMem = jobMemory->lzwMem.Ptr(); + +#ifdef STRESS_LZW_MEM + jobMemory->lzwInOutData.maxlzwMem = g_maxlwMem; +#else + jobMemory->lzwInOutData.maxlzwMem = jobMemory_t::MAX_LZW_MEM; +#endif + + jobMemory->lzwInOutData.lzwDmaOut = jobMemory_t::MAX_LZW_MEM; + jobMemory->lzwInOutData.numlzwDeltas = 0; + jobMemory->lzwInOutData.lzwBytes = 0; + jobMemory->lzwInOutData.optimalLength = net_optimalSnapDeltaSize.GetInteger(); + jobMemory->lzwInOutData.snapSequence = snapSequence; + jobMemory->lzwInOutData.lastObjId = 0; + jobMemory->lzwInOutData.lzwData = lzwData; + + idSnapShot::submitDeltaJobsInfo_t submitInfo; + + submitInfo.objParms = jobMemory->objParms.Ptr(); + submitInfo.maxObjParms = jobMemory->objParms.Num(); + submitInfo.headers = jobMemory->headers.Ptr(); + submitInfo.maxHeaders = jobMemory->headers.Num(); + submitInfo.objMemory = objMemory; + submitInfo.maxObjMemory = objMemorySize; + submitInfo.lzwParms = jobMemory->lzwParms.Ptr(); + submitInfo.maxDeltaParms = jobMemory->lzwParms.Num(); + + + // Use a copy of base state to avoid race conditions. + // The main thread could change it behind the jobs backs. + submittedState = baseState; + submittedTemplateStates = templateStates; + + submitInfo.templateStates = &submittedTemplateStates; + + submitInfo.oldSnap = &submittedState; + submitInfo.visIndex = visIndex; + submitInfo.baseSequence = baseSequence; + + submitInfo.lzwInOutData = &jobMemory->lzwInOutData; + + pendingSnap.SubmitWriteDeltaToJobs( submitInfo ); +} + +/* +======================== +idSnapshotProcessor::GetPendingSnapDelta +======================== +*/ +int idSnapshotProcessor::GetPendingSnapDelta( byte * outBuffer, int maxLength ) { + + assert( PendingSnapReadyToSend() ); + + if ( !verify( jobMemory->lzwInOutData.numlzwDeltas == 1 ) ) { + jobMemory->lzwInOutData.numlzwDeltas = 0; + return 0; // No more deltas left to send + } + + assert( hasPendingSnap ); + + jobMemory->lzwInOutData.numlzwDeltas = 0; + + int size = jobMemory->lzwDeltas[0].size; + + if ( !verify( size != -1 ) ) { +#ifdef STRESS_LZW_MEM + if ( g_maxlwMem < MAX_LZW_MEM ) { + g_maxlwMem += 50; + g_maxlwMem = Min( g_maxlwMem, MAX_LZW_MEM ); + return 0; + } +#endif + + // This can happen if there wasn't enough maxlzwMem to process one full obj in a single delta + idLib::Error( "GetPendingSnapDelta: Delta failed." ); + } + + uint8 * deltaData = &jobMemory->lzwMem[jobMemory->lzwDeltas[0].offset]; + + int deltaSequence = 0; + int deltaBaseSequence = 0; + PeekDeltaSequence( (const char *)deltaData, size, deltaSequence, deltaBaseSequence ); + // sanity check: does the compressed data we are about to send have the sequence number we expect + assert( deltaSequence == jobMemory->lzwDeltas[0].snapSequence ); + + if ( !verify( size <= maxLength ) ) { + idLib::Error( "GetPendingSnapDelta: Size overflow." ); + } + + // Copy to out buffer + memcpy( outBuffer, deltaData, size ); + + // Set the sequence to what this delta actually belongs to + assert( jobMemory->lzwDeltas[0].snapSequence == snapSequence + 1 ); + snapSequence = jobMemory->lzwDeltas[0].snapSequence; + + //idLib::Printf( "deltas Num: %i, Size: %i\n", deltas.Num(), deltas.GetDataLength() ); + + // Copy to delta buffer + // NOTE - We don't need to save this delta off if peer has already ack'd this basestate. + // This can happen due to the fact that we defer the processing of snap deltas on jobs. + // When we start processing a delta, we use the currently ack'd basestate. If while we were processing + // the delta, the client acks a new basestate, we can get into this situation. In this case, we simply don't + // store the delta, since it will just take up space, and just get removed anyways during ApplySnapshotDelta. + // (and cause lots of spam when it sees the delta's basestate doesn't match the current ack'd one) + if ( deltaBaseSequence >= baseSequence ) { + if ( !deltas.Append( snapSequence, deltaData, size ) ) { + int resendLength = deltas.ItemLength( deltas.Num() - 1 ); + + if ( !verify( resendLength <= maxLength ) ) { + idLib::Error( "GetPendingSnapDelta: Size overflow for resend." ); + } + + memcpy( outBuffer, deltas.ItemData( deltas.Num() - 1 ), resendLength ); + size = -resendLength; + } + } + + if ( jobMemory->lzwInOutData.fullSnap ) { + // We sent the full snap, we can stop sending this pending snap now... + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 5, va( " wrote enough deltas to a full snapshot\n" ) ); // FIXME: peer number? + + hasPendingSnap = false; + partialBaseSequence = -1; + + } else { + partialBaseSequence = deltaBaseSequence; + } + + return size; +} + +/* +======================== +idSnapshotProcessor::IsBusyConfirmingPartialSnap +======================== +*/ +bool idSnapshotProcessor::IsBusyConfirmingPartialSnap() { + if ( partialBaseSequence != -1 && baseSequence <= partialBaseSequence ) { + return true; + } + + return false; +} + +/* +======================== +idSnapshotProcessor::ReceiveSnapshotDelta +NOTE: we use ReadDeltaForJob twice, once to build the same base as the server (based on server acks, down ApplySnapshotDelta), and another time to apply the snapshot we just received +could we avoid the double apply by keeping outSnap cached in memory and avoid rebuilding it from a delta when the next one comes around? +======================== +*/ +bool idSnapshotProcessor::ReceiveSnapshotDelta( const byte * deltaData, int deltaLength, int visIndex, int & outSeq, int & outBaseSeq, idSnapShot & outSnap, bool & fullSnap ) { + + fullSnap = false; + + int deltaSequence = 0; + int deltaBaseSequence = 0; + + // Get the sequence of this delta, and the base sequence it is delta'd from + PeekDeltaSequence( (const char *)deltaData, deltaLength, deltaSequence, deltaBaseSequence ); + + //idLib::Printf("Incoming snapshot: %i, %i\n", deltaSequence, deltaBaseSequence ); + + if ( deltaSequence <= snapSequence ) { + NET_VERBOSESNAPSHOT_PRINT( "Rejecting old delta: %d (snapSequence: %d \n", deltaSequence, snapSequence ); + return false; // Completely reject older out of order deltas + } + + // Bring the base state up to date with the basestate this delta was compared to + ApplySnapshotDelta( visIndex, deltaBaseSequence ); + + // Once we get here, our base state should be caught up to that of the server + assert( baseSequence == deltaBaseSequence ); + + // Save the new delta + if ( net_skipClientDeltaAppend.GetBool() || !deltas.Append( deltaSequence, deltaData, deltaLength ) ) { + // This can happen if the delta queues get desync'd between the server and client. + // With recent fixes, this should be extremely rare, or impossible. + // Just in case this happens, we can recover by assuming we didn't even receive this delta. + idLib::Printf( "NET: ReceiveSnapshotDelta: No room to append delta %d/%d \n", deltaSequence, deltaBaseSequence ); + return false; + } + + // Update our snapshot sequence number to the newer one we just got (now that it's safe) + snapSequence = deltaSequence; + + if ( deltas.Num() > 10 ) { + NET_VERBOSESNAPSHOT_PRINT("NET: ReceiveSnapshotDelta: deltas.Num() > 10: %d\n ", deltas.Num() ); + for ( int i=0; i < deltas.Num(); i++ ) { + NET_VERBOSESNAPSHOT_PRINT( "%d ", deltas.ItemSequence( i ) ); + } + NET_VERBOSESNAPSHOT_PRINT( "\n" ); + } + + + if ( baseSequence != deltaBaseSequence ) { + // NOTE - With recent fixes, this should no longer be possible unless the delta is trashed + // We should probably disconnect from the server when this happens now. + static bool failed = false; + if ( !failed ) { + idLib::Printf( "NET: incorrect base state? not sure how this can happen... baseSequence: %d deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence ); + } + failed = true; + return false; + } + + // Copy out the current deltas sequence values to caller + outSeq = deltaSequence; + outBaseSeq = deltaBaseSequence; + + if ( baseSequence < 50 && net_debugBaseStates.GetBool() ) { + idLib::Printf( "NET: Proper basestate... baseSequence: %d deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence ); + } + + // Make a copy of the basestate the server used to create this delta, and then apply and return it + outSnap = baseState; + + fullSnap = ApplyDeltaToSnapshot( outSnap, (const char *)deltaData, deltaLength, visIndex ); + + // We received a new delta + return true; +} + +/* +======================== +idSnapshotProcessor::ApplySnapshotDelta +Apply a snapshot delta to our current basestate, and make that the new base. +We can remove all deltas that refer to the basetate we just removed. +======================== +*/ +bool idSnapshotProcessor::ApplySnapshotDelta( int visIndex, int snapshotNumber ) { + + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 6, va( "idSnapshotProcessor::ApplySnapshotDelta snapshotNumber: %d\n", snapshotNumber ) ); + + // Sanity check deltas + SanityCheckDeltas(); + + // dump any deltas older than the acknoweledged snapshot, which should only happen if there is packet loss + deltas.RemoveOlderThan( snapshotNumber ); + + if ( deltas.Num() == 0 || deltas.ItemSequence( 0 ) != snapshotNumber ) { + // this means the snapshot was either already acknowledged or came out of order + // On the server, this can happen because the client is continuously/redundantly sending acks + // Once the server has ack'd a certain base sequence, it will need to ignore all the redundant ones. + // On the client, this will only happen due to out of order, or dropped packets. + + if ( !common->IsServer() ) { + // these should be printed every time on the clients + // printing on server is not useful / results in tons of spam + if ( deltas.Num() == 0 ) { + NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.Num(): %d snapshotNumber: %d \n", deltas.Num(), snapshotNumber ); + } else { + NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.ItemSequence( 0 ): %d != snapshotNumber: %d \n ", deltas.ItemSequence( 0 ), snapshotNumber ); + + for ( int i=0; i < deltas.Num(); i++ ) { + NET_VERBOSESNAPSHOT_PRINT("%d ", deltas.ItemSequence(i) ); + } + NET_VERBOSESNAPSHOT_PRINT("\n"); + + } + } + return false; + } + + int deltaSequence = 0; + int deltaBaseSequence = 0; + + PeekDeltaSequence( (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), deltaSequence, deltaBaseSequence ); + + assert( deltaSequence == snapshotNumber ); // Make sure compressed sequence number matches that in data queue + assert( baseSequence == deltaBaseSequence ); // If this delta isn't based off of our currently ack'd basestate, something is trashed... + assert( deltaSequence > baseSequence ); + + if ( baseSequence != deltaBaseSequence ) { + // NOTE - This should no longer happen with recent fixes. + // We should probably disconnect from the server if this happens. (packets are trashed most likely) + NET_VERBOSESNAPSHOT_PRINT( "NET: Got snapshot %d but baseSequence does not match. baseSequence: %d deltaBaseSequence: %d. \n", snapshotNumber, baseSequence, deltaBaseSequence ); + return false; + } + + // Apply this delta to our base state + if ( ApplyDeltaToSnapshot( baseState, (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), visIndex ) ) { + lastFullSnapBaseSequence = deltaSequence; + } + + baseSequence = deltaSequence; // This is now our new base sequence + + // Remove deltas that we no longer need + RemoveDeltasForOldBaseSequence(); + + // Sanity check deltas + SanityCheckDeltas(); + + return true; +} + +/* +======================== +idSnapshotProcessor::RemoveDeltasForOldBaseSequence +Remove deltas for basestate we no longer have. We know we can remove them, because we will never +be able to apply them, since the basestate needed to generate a full snap from these deltas is gone. + +Ways we can get deltas based on basestate we no longer have: + 1. Server sends sequence 50 based on 49. It then sends sequence 51 based on 49. + Client acks 50, server applies it to 49, 50 is new base state. + Server now has a delta sequence 51 based on 49 that it won't ever be able to apply (50 is new basestate). + +This is annoying, because it makes a lot of our sanity checks incorrectly fire off for benign issues. +Here is a series of events that make the old ( baseSequence != deltaBaseSequence ) assert: + +Server: +49->50, 49->51, ack 50, 50->52, ack 51 (bam), 50->53 + +Client +49->50, ack 50, 49->51, ack 51 (bam), 50->52, 50->53 + +The client above will ack 51, even though he can't even apply that delta. To get around this, we simply don't +allow delta to exist in the list, unless their basestate is the current basestate we maintain. +This allows us to put sanity checks in place that don't fire off during benign conditions, and allow us +to truly check for trashed conditions. + +======================== +*/ +void idSnapshotProcessor::RemoveDeltasForOldBaseSequence() { + // Remove any deltas that would apply to the old base we no longer maintain + // (we will never be able to apply these, since we don't have that base anymore) + for ( int i = deltas.Num() - 1; i >= 0; i-- ) { + int deltaSequence = 0; + int deltaBaseSequence = 0; + baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence ); + if ( deltaBaseSequence < baseSequence ) { + // Remove this delta, and all deltas before this one + deltas.RemoveOlderThan( deltas.ItemSequence( i ) + 1 ); + break; + } + } +} + +/* +======================== +idSnapshotProcessor::SanityCheckDeltas +Make sure delta sequence and basesequence values are valid, and in order, etc +======================== +*/ +void idSnapshotProcessor::SanityCheckDeltas() { + int deltaSequence = 0; + int deltaBaseSequence = 0; + int lastDeltaSequence = -1; + int lastDeltaBaseSequence = -1; + + for ( int i = 0; i < deltas.Num(); i++ ) { + baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence ); + assert( deltaSequence == deltas.ItemSequence( i ) ); // Make sure delta stored in compressed form matches the one stored in the data queue + assert( deltaSequence > lastDeltaSequence ); // Make sure they are in order (we reject out of order sequences in ApplysnapshotDelta) + assert( deltaBaseSequence >= lastDeltaBaseSequence ); // Make sure they are in order (they can be the same, since base sequences don't change until they've been ack'd) + assert( deltaBaseSequence >= baseSequence ); // We should have removed old delta's that can no longer be applied + assert( deltaBaseSequence == baseSequence || deltaBaseSequence == lastDeltaSequence ); // Make sure we still have a base (or eventually will have) that we can apply this delta to + lastDeltaSequence = deltaSequence; + lastDeltaBaseSequence = deltaBaseSequence; + } +} + +/* +======================== +idSnapshotProcessor::AddSnapObjTemplate +======================== +*/ +void idSnapshotProcessor::AddSnapObjTemplate( int objID, idBitMsg & msg ) { + extern idCVar net_ssTemplateDebug; + idSnapShot::objectState_t * state = templateStates.S_AddObject( objID, MAX_UNSIGNED_TYPE( uint32 ), msg ); + if ( verify( state != NULL ) ) { + if ( net_ssTemplateDebug.GetBool() ) { + idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "InjectingSnapObjBaseState[%d] size: %d\n", objID, state->buffer.Size() ); + state->Print( "BASE STATE" ); + } + state->expectedSequence = snapSequence; + } +} diff --git a/neo/sys/SnapshotProcessor.h b/neo/sys/SnapshotProcessor.h new file mode 100644 index 00000000..356aeaf2 --- /dev/null +++ b/neo/sys/SnapshotProcessor.h @@ -0,0 +1,148 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SNAP_PROCESSOR_H__ +#define __SNAP_PROCESSOR_H__ + +/* +================================================ +idSnapshotProcessor +================================================ +*/ +class idSnapshotProcessor { +public: + static const int INITIAL_SNAP_SEQUENCE = 42; + + idSnapshotProcessor(); + ~idSnapshotProcessor(); + + void Reset( bool cstor = false ); + + // TrySetPendingSnapshot Sets the currently pending snap. + // No new snaps will be sent until this snap has been fully sent. + // Returns true of the newly supplied snapshot was accepted (there were no pending snaps) + bool TrySetPendingSnapshot( idSnapShot & ss ); + // Peek into delta to get deltaSequence, and deltaBaseSequence + void PeekDeltaSequence( const char * deltaMem, int deltaSize, int & deltaSequence, int & deltaBaseSequence ); + // Apply a delta to the supplied snapshot + bool ApplyDeltaToSnapshot( idSnapShot & snap, const char * deltaMem, int deltaSize, int visIndex ); + // Attempts to write the currently pending snap to the supplied buffer, which can then be sent as an unreliable msg. + // SubmitPendingSnap will submit the pending snap to a job, so that it can be retrieved later for sending. + void SubmitPendingSnap( int visIndex, uint8 * objMemory, int objMemorySize, lzwCompressionData_t * lzwData ); + // GetPendingSnapDelta + int GetPendingSnapDelta( byte * outBuffer, int maxLength ); + // If PendingSnapReadyToSend is true, then GetPendingSnapDelta will return something to send + bool PendingSnapReadyToSend() const { return jobMemory->lzwInOutData.numlzwDeltas > 0; } + // When you call WritePendingSnapshot, and then send the resulting buffer as a unreliable msg, you will eventually + // receive this on the client. Call this function to receive and apply it to the base state, and possibly return a fully received snap + // to then apply to the client game state + bool ReceiveSnapshotDelta( const byte * deltaData, int deltaLength, int visIndex, int & outSeq, int & outBaseSeq, idSnapShot & outSnap, bool & fullSnap ); + // Function to apply a received (or ack'd) delta to the base state + bool ApplySnapshotDelta( int visIndex, int snapshotNumber ); + // Remove deltas for basestate we no longer have. + // We know we can remove them, because we will never be able to apply them, since + // the basestate needed to generate a full snap from these deltas is gone. + void RemoveDeltasForOldBaseSequence(); + // Make sure delta sequence and basesequence values are valid, and in order, etc + void SanityCheckDeltas(); + // HasPendingSnap will return true if there is more of the last TrySetPendingSnapshot to be sent + bool HasPendingSnap() const { return hasPendingSnap; } + + idSnapShot * GetBaseState() { return &baseState; } + idSnapShot * GetPendingSnap(){ return &pendingSnap; } + + int GetSnapSequence() { return snapSequence; } + int GetBaseSequence() { return baseSequence; } + int GetFullSnapBaseSequence() { return lastFullSnapBaseSequence; } + + // This is used to ack the latest delta we have. If we have no deltas, we sent -1 to make sure + // Server knows we don't want to ack, since we are as up to date as we can be + int GetLastAppendedSequence() { return deltas.Num() == 0 ? -1 : deltas.ItemSequence( deltas.Num() - 1 ); } + + int GetSnapQueueSize() { return deltas.Num(); } + + bool IsBusyConfirmingPartialSnap(); + + void AddSnapObjTemplate( int objID, idBitMsg & msg ); + + static const int MAX_SNAPSHOT_QUEUE = 64; + +private: + + // Internal commands to set up, and flush the compressors + static const int MAX_SNAP_SIZE = idPacketProcessor::MAX_MSG_SIZE; + static const int MAX_SNAPSHOT_QUEUE_MEM = 64 * 1024; // 64k + + // sequence number of the last snapshot we sent/received + // on the server, the sequencing is different for each network peer (net_verboseSnapshot 1) + // on the jobbed snapshot compression path, the sequence is incremented in NewLZWStream and pulled into this in idSnapshotProcessor::GetPendingSnapDelta + int snapSequence; + int baseSequence; + int lastFullSnapBaseSequence; // Latest base sequence number that is a full snap + + idSnapShot baseState; // known snapshot base on the client + idDataQueue< MAX_SNAPSHOT_QUEUE, MAX_SNAPSHOT_QUEUE_MEM > deltas; // list of unacknowledged snapshot deltas + + idSnapShot pendingSnap; // Current snap waiting to be fully sent + bool hasPendingSnap; // true if pendingSnap is still waiting to be sent + + struct jobMemory_t { + static const int MAX_LZW_DELTAS = 1; // FIXME: cleanup the old multiple delta support completely + + // @TODO this is a hack fix to allow online to load into coop (where there are lots of entities). + // The real solution should be coming soon. + // Doom MP: we encountered the same problem, going from 1024 to 4096 as well until a better solution is in place + // (initial, useless, exchange of func_statics is killing us) + static const int MAX_OBJ_PARMS = 4096; + + static const int MAX_LZW_PARMS = 32; + static const int MAX_OBJ_HEADERS = 256; + static const int MAX_LZW_MEM = 1024 * 8; // 8k in the byte * lzwMem buffers, must be <= PS3_DMA_MAX + + // Parm memory to jobs + idArray objParms; + idArray headers; + idArray lzwParms; + + // Output memory from jobs + idArray lzwDeltas; // Info about each pending delta output from jobs + idArray lzwMem; // Memory for output from lzw jobs + + lzwInOutData_t lzwInOutData; // In/Out data used so lzw data can persist across lzw jobs + }; + + jobMemory_t * jobMemory; + + idSnapShot submittedState; + + idSnapShot templateStates; // holds default snapshot states for some newly spawned object + idSnapShot submittedTemplateStates; + + int partialBaseSequence; +}; + +#endif /* !__SNAP_PROCESSOR_H__ */ diff --git a/neo/sys/Snapshot_Jobs.cpp b/neo/sys/Snapshot_Jobs.cpp new file mode 100644 index 00000000..6f1e787c --- /dev/null +++ b/neo/sys/Snapshot_Jobs.cpp @@ -0,0 +1,424 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "Snapshot_Jobs.h" + +uint32 SnapObjChecksum( const uint8 * data, int length ) { + extern unsigned long CRC32_BlockChecksum( const void *data, int length ); + return CRC32_BlockChecksum( data, length ); +} + +/* +======================== +ObjectsSame +======================== +*/ +ID_INLINE bool ObjectsSame( objJobState_t & newState, objJobState_t & oldState ) { + assert( newState.valid && oldState.valid ); + assert( newState.objectNum == oldState.objectNum ); + + if ( newState.size != oldState.size ) { + //assert( newState.data != oldState.data) ); + return false; // Can't match if sizes different + } + + /* + if ( newState.data == oldState.data ) { + return true; // Definite match + } + */ + + if ( memcmp( newState.data, oldState.data, newState.size ) == 0 ) { + return true; // Byte match, same + } + + return false; // Not the same +} + +/* +======================== +SnapshotObjectJob +This job processes objects by delta comparing them, and then zrle encoding them to the dest stream +The dest stream is then eventually read by the lzw job, and then lzw compressed into the final delta packet +ready to be sent to peers. +======================== +*/ +void SnapshotObjectJob( objParms_t * parms ) { + int visIndex = parms->visIndex; + objJobState_t & newState = parms->newState; + objJobState_t & oldState = parms->oldState; + objHeader_t * header = parms->destHeader; + uint8 * dataStart = parms->dest; + + assert( newState.valid || oldState.valid ); + + // Setup header + header->flags = 0; + header->size = newState.valid ? newState.size : 0; + header->csize = 0; + header->objID = -1; // Default to ack + header->data = dataStart; + + assert( header->size <= MAX_UNSIGNED_TYPE( objectSize_t ) ); + + // Setup checksum and tag +#ifdef SNAPSHOT_CHECKSUMS + header->checksum = 0; +#endif + + idZeroRunLengthCompressor rleCompressor; + + bool visChange = false; // visibility changes will be signified with a 0xffff state size + bool visSendState = false; // the state is sent when an entity is no longer stale + + // Compute visibility changes + // (we need to do this before writing out object id, because we may not need to write out the id if we early out) + // (when we don't write out the id, we assume this is an "ack" when we deserialize the objects) + if ( newState.valid && oldState.valid ) { + // Check visibility + assert( newState.objectNum == oldState.objectNum ); + + if ( visIndex > 0 ) { + bool oldVisible = ( oldState.visMask & ( 1 << visIndex ) ) != 0; + bool newVisible = ( newState.visMask & ( 1 << visIndex ) ) != 0; + + // Force visible if we need to either create or destroy this object + newVisible |= ( newState.size == 0 ) != ( oldState.size == 0 ); + + if ( !oldVisible && !newVisible ) { + // object is stale and ack'ed for this client, write nothing (see 'same object' below) + header->flags |= OBJ_SAME; + return; + } else if ( oldVisible && !newVisible ) { + //SNAP_VERBOSE_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex ); + visChange = true; + visSendState = false; + } else if ( !oldVisible && newVisible ) { + //SNAP_VERBOSE_PRINT( "object %d to client %d no longer stale\n", newState->objectNum, visIndex ); + visChange = true; + visSendState = true; + } + } + + // Same object, write a delta (never early out during vis changes) + if ( !visChange && ObjectsSame( newState, oldState ) ) { + // same state, write nothing + header->flags |= OBJ_SAME; + return; + } + } + + // Get the id of the object we are writing out + int32 objectNum = ( newState.valid ) ? newState.objectNum : oldState.objectNum; + + // Write out object id + header->objID = objectNum; + + if ( !newState.valid ) { + // Deleted, write 0 size + assert( oldState.valid ); + header->flags |= OBJ_DELETED; + } else if ( !oldState.valid ) { + // New object, write out full state + assert( newState.valid ); + // delta against an empty snap + rleCompressor.Start( dataStart, NULL, OBJ_DEST_SIZE_ALIGN16( newState.size ) ); + rleCompressor.WriteBytes( newState.data, newState.size ); + header->csize = rleCompressor.End(); + header->flags |= OBJ_NEW; + if ( header->csize == -1 ) { + // Not enough space, don't compress, have lzw job do zrle compression instead + memcpy( dataStart, newState.data, newState.size ); + } + } else { + // Compare to same obj id in different snapshot + assert( newState.objectNum == oldState.objectNum ); + + header->flags |= OBJ_DIFFERENT; + + if ( visChange ) { + header->flags |= visSendState ? OBJ_VIS_NOT_STALE : OBJ_VIS_STALE; + } + + if ( !visChange || visSendState ) { + int compareSize = Min( newState.size, oldState.size ); + rleCompressor.Start( dataStart, NULL, OBJ_DEST_SIZE_ALIGN16( newState.size ) ); + for ( int b = 0; b < compareSize; b++ ) { + byte delta = newState.data[b] - oldState.data[b]; + rleCompressor.WriteByte( ( 0xFF + 1 + delta ) & 0xFF ); + } + // Get leftover + int leftOver = newState.size - compareSize; + + if ( leftOver > 0 ) { + rleCompressor.WriteBytes( newState.data + compareSize, leftOver ); + } + + header->csize = rleCompressor.End(); + + if ( header->csize == -1 ) { + // Not enough space, don't compress, have lzw job do zrle compression instead + for ( int b = 0; b < compareSize; b++ ) { + *dataStart++ = ( ( 0xFF + 1 + ( newState.data[b] - oldState.data[b] ) ) & 0xFF ); + } + // Get leftover + int leftOver = newState.size - compareSize; + + if ( leftOver > 0 ) { + memcpy( dataStart, newState.data + compareSize, leftOver ); + } + } + } + } + + assert( header->csize <= OBJ_DEST_SIZE_ALIGN16( header->size ) ); + +#ifdef SNAPSHOT_CHECKSUMS + if ( newState.valid ) { + assert( newState.size ); + header->checksum = SnapObjChecksum( newState.data, newState.size ); + } +#endif +} + +/* +======================== +FinishLZWStream +======================== +*/ +static void FinishLZWStream( lzwParm_t * parm, idLZWCompressor * lzwCompressor ) { + if ( lzwCompressor->IsOverflowed() ) { + lzwCompressor->Restore(); + } + + lzwDelta_t & pendingDelta = parm->ioData->lzwDeltas[parm->ioData->numlzwDeltas]; + + if ( lzwCompressor->End() == -1 ) { + // If we couldn't end the stream, notify the main thread + pendingDelta.offset = -1; + pendingDelta.size = -1; + pendingDelta.snapSequence = -1; + + parm->ioData->numlzwDeltas++; + return; + } + + int size = lzwCompressor->Length(); + + pendingDelta.offset = parm->ioData->lzwBytes; // Remember offset into buffer + pendingDelta.size = size; // Remember size + pendingDelta.snapSequence = parm->ioData->snapSequence; // Remember which snap sequence this delta belongs to + + parm->ioData->lzwBytes += size; + parm->ioData->numlzwDeltas++; +} + +/* +======================== +NewLZWStream +======================== +*/ +static void NewLZWStream( lzwParm_t * parm, idLZWCompressor * lzwCompressor ) { + + // Reset compressor + int maxSize = parm->ioData->maxlzwMem - parm->ioData->lzwBytes; + lzwCompressor->Start( &parm->ioData->lzwMem[parm->ioData->lzwBytes], maxSize ); + + parm->ioData->lastObjId = 0; + + parm->ioData->snapSequence++; + + lzwCompressor->WriteAgnostic( parm->ioData->snapSequence ); + lzwCompressor->WriteAgnostic( parm->baseSequence ); + lzwCompressor->WriteAgnostic( parm->curTime ); +} + +/* +======================== +ContinueLZWStream +======================== +*/ +static void ContinueLZWStream( lzwParm_t * parm, idLZWCompressor * lzwCompressor ) { + // Continue compressor where we left off + int maxSize = parm->ioData->maxlzwMem - parm->ioData->lzwBytes; + lzwCompressor->Start( &parm->ioData->lzwMem[parm->ioData->lzwBytes], maxSize, true ); +} + +/* +======================== +LZWJobInternal +This job takes a stream of objects, which should already be zrle compressed, and then lzw compresses them +and builds a final delta packet ready to be sent to peers. +======================== +*/ +void LZWJobInternal( lzwParm_t * parm, unsigned int dmaTag ) { + assert( parm->numObjects > 0 ); + +#ifndef ALLOW_MULTIPLE_DELTAS + if ( parm->ioData->numlzwDeltas > 0 ) { + // Currently, we don't use fragmented deltas. + // We only send the first one and rely on a full snap being sent to get the whole snap across + assert( parm->ioData->numlzwDeltas == 1 ); + assert( !parm->ioData->fullSnap ); + return; + } +#endif + + assert( parm->ioData->lzwBytes < parm->ioData->maxlzwMem ); + + dmaTag = dmaTag; + + ALIGN16( idLZWCompressor lzwCompressor( parm->ioData->lzwData ) ); + + if ( parm->fragmented ) { + // This packet was partially written out, we need to continue writing, using previous lzw dictionary values + ContinueLZWStream( parm, &lzwCompressor ); + } else { + // We can start a new lzw dictionary + NewLZWStream( parm, &lzwCompressor ); + } + + + int numChangedObjProcessed = 0; + + for ( int i = 0; i < parm->numObjects; i++ ) { + + // This will eventually be gracefully caught in SnapshotProcessor.cpp. + // It's nice to know right when it happens though, so you can inspect the situation. + assert( !lzwCompressor.IsOverflowed() || numChangedObjProcessed > 1 ); + + // First, see if we need to finish the current lzw stream + if ( lzwCompressor.IsOverflowed() || lzwCompressor.Length() >= parm->ioData->optimalLength ) { + FinishLZWStream( parm, &lzwCompressor ); + // indicate how much needs to be DMA'ed back out + parm->ioData->lzwDmaOut = parm->ioData->lzwBytes; +#ifdef ALLOW_MULTIPLE_DELTAS + NewLZWStream( parm, &lzwCompressor ); +#else + // Currently, we don't use fragmented deltas. + // We only send the first one and rely on a full snap being sent to get the whole snap across + assert( !parm->ioData->fullSnap ); + assert( parm->ioData->numlzwDeltas == 1 ); + return; +#endif + } + + if ( numChangedObjProcessed > 0 ) { + // We should be at a good spot in the stream if we've written at least one obj without overflowing, so save it + lzwCompressor.Save(); + } + + // Get header + objHeader_t * header = &parm->headers[i]; + + if ( header->objID == -1 ) { + assert( header->flags & OBJ_SAME ); + continue; // Don't send object (which means ack) + } + + numChangedObjProcessed++; + + // Write obj id as delta into stream + lzwCompressor.WriteAgnostic( (uint16)( header->objID - parm->ioData->lastObjId ) ); + parm->ioData->lastObjId = (uint16)header->objID; + + // Check special stale/notstale flags + if ( header->flags & ( OBJ_VIS_STALE | OBJ_VIS_NOT_STALE ) ) { + // Write stale/notstale flag + objectSize_t value = ( header->flags & OBJ_VIS_STALE ) ? SIZE_STALE : SIZE_NOT_STALE; + lzwCompressor.WriteAgnostic( value ); + } + + if ( header->flags & OBJ_VIS_STALE ) { + continue; // Don't write out data for stale objects + } + + if ( header->flags & OBJ_DELETED ) { + // Object was deleted + lzwCompressor.WriteAgnostic( 0 ); + continue; + } + + // Write size + lzwCompressor.WriteAgnostic( (objectSize_t)header->size ); + + // Get compressed data area + uint8 * compressedData = header->data; + + if ( header->csize == -1 ) { + // Wasn't zrle compressed, zrle now while lzw'ing + idZeroRunLengthCompressor rleCompressor; + rleCompressor.Start( NULL, &lzwCompressor, 0xFFFF ); + rleCompressor.WriteBytes( compressedData, header->size ); + rleCompressor.End(); + } else { + // Write out zero-rle compressed data + lzwCompressor.Write( compressedData, header->csize ); + } + +#ifdef SNAPSHOT_CHECKSUMS + // Write checksum + lzwCompressor.WriteAgnostic( header->checksum ); +#endif + // This will eventually be gracefully caught in SnapshotProcessor.cpp. + // It's nice to know right when it happens though, so you can inspect the situation. + assert( !lzwCompressor.IsOverflowed() || numChangedObjProcessed > 1 ); + } + + if ( !parm->saveDictionary ) { + // Write out terminator + uint16 objectDelta = 0xFFFF - parm->ioData->lastObjId; + lzwCompressor.WriteAgnostic( objectDelta ); + + // Last stream + FinishLZWStream( parm, &lzwCompressor ); + + // indicate how much needs to be DMA'ed back out + parm->ioData->lzwDmaOut = parm->ioData->lzwBytes; + + parm->ioData->fullSnap = true; // We sent a full snap + } else { + // the compressor did some work, wrote data to lzwMem, but since we didn't call FinishLZWStream to end the compression, + // we need to figure how much needs to be DMA'ed back out + assert( parm->ioData->lzwBytes == 0 ); // I don't think we ever hit this with lzwBytes != 0, but adding it just in case + parm->ioData->lzwDmaOut = parm->ioData->lzwBytes + lzwCompressor.Length(); + } + + assert( parm->ioData->lzwBytes < parm->ioData->maxlzwMem ); +} + +/* +======================== +LZWJob +======================== +*/ +void LZWJob( lzwParm_t * parm ) { + LZWJobInternal( parm, 0 ); +} diff --git a/neo/sys/Snapshot_Jobs.h b/neo/sys/Snapshot_Jobs.h new file mode 100644 index 00000000..85cbcbcf --- /dev/null +++ b/neo/sys/Snapshot_Jobs.h @@ -0,0 +1,127 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SNAPSHOT_JOBS_H__ +#define __SNAPSHOT_JOBS_H__ + +#include "LightweightCompression.h" + +//#define SNAPSHOT_CHECKSUMS + +typedef int32 objectSize_t; + +static const objectSize_t SIZE_STALE = MAX_TYPE( objectSize_t ); // Special size to indicate object went stale +static const objectSize_t SIZE_NOT_STALE = MAX_TYPE( objectSize_t ) - 1; // Special size to indicate object is no longer stale + +static const int RLE_COMPRESSION_PADDING = 16; // Padding to accommodate possible enlargement due to zlre compression + +// OBJ_DEST_SIZE_ALIGN16 returns the total space needed to store an object for reading/writing during jobs +#define OBJ_DEST_SIZE_ALIGN16( s ) ( ( ( s ) + 15 ) & ~15 ) + +static const uint32 OBJ_VIS_STALE = ( 1 << 0 ); // Object went stale +static const uint32 OBJ_VIS_NOT_STALE = ( 1 << 1 ); // Object no longer stale +static const uint32 OBJ_NEW = ( 1 << 2 ); // New object (not in the last snap) +static const uint32 OBJ_DELETED = ( 1 << 3 ); // Object was deleted (not going to be in the new snap) +static const uint32 OBJ_DIFFERENT = ( 1 << 4 ); // Objects are in both snaps, but different +static const uint32 OBJ_SAME = ( 1 << 5 ); // Objects are in both snaps, and are the same (we don't send these, which means ack) + +// This struct is used to communicate data from the obj jobs to the lzw job +struct ALIGNTYPE16 objHeader_t { + int32 objID; // Id of object. + int32 size; // Size data object holds (will be 0 if the obj is being deleted) + int32 csize; // Size after zrle compression + uint32 flags; // Flags used to communicate state from obj job to lzw delta job + uint8 * data; // Data ptr to obj memory +#ifdef SNAPSHOT_CHECKSUMS + uint32 checksum; // Checksum before compression, used for sanity checking +#endif +}; + +struct objJobState_t { + uint8 valid; + uint8 * data; + uint16 size; + uint16 objectNum; + uint32 visMask; +}; + +// Input to initial jobs that produce delta'd zrle compressed versions of all the snap obj's +struct ALIGNTYPE16 objParms_t { + // Input + uint8 visIndex; + + objJobState_t newState; + objJobState_t oldState; + + // Output + objHeader_t * destHeader; + uint8 * dest; +}; + +// Output from the job that takes the results of the delta'd zrle obj's. +// This struct contains the start of where the final delta packet data is within lzwMem +struct ALIGNTYPE16 lzwDelta_t { + int offset; // Offset into lzwMem + int size; + int snapSequence; +}; + +// Struct used to maintain state that needs to persist across lzw jobs +struct ALIGNTYPE16 lzwInOutData_t { + int numlzwDeltas; // Num pending deltas written + bool fullSnap; // True if entire snap was written out in one delta + lzwDelta_t * lzwDeltas; // Info about each final delta packet written out + int maxlzwDeltas; // Max lzw deltas + uint8 * lzwMem; // Resulting final lzw delta packet data + int maxlzwMem; // Max size in bytes that can fit in lzwMem + int lzwDmaOut; // How much of lzwMem needs to be DMA'ed back out + int lzwBytes; // Final delta packet bytes written + int optimalLength; // Optimal length of lzw streams + int snapSequence; + uint16 lastObjId; // Last obj id written out + lzwCompressionData_t * lzwData; +}; + +// Input to the job that takes the results of the delta'd zrle obj's, and turns them into lzw delta packets +struct ALIGNTYPE16 lzwParm_t { + // Input + int numObjects; // Number of objects this job needs to process + objHeader_t * headers; // Object headers + int curTime; // Cur snap time + int baseTime; // Base snap time + int baseSequence; + bool saveDictionary; + bool fragmented; // This lzw stream should continue where the last one left off + + // In/Out + lzwInOutData_t * ioData; // In/Out +}; + +extern void SnapshotObjectJob( objParms_t * parms ); +extern void LZWJob( lzwParm_t * parm ); + +#endif // __SNAPSHOT_JOBS_H__ diff --git a/neo/sys/sys_achievements.cpp b/neo/sys/sys_achievements.cpp new file mode 100644 index 00000000..26d0c85b --- /dev/null +++ b/neo/sys/sys_achievements.cpp @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +/* +======================== +idAchievementSystem::SyncAchievementBits +======================== +*/ +void idAchievementSystem::SyncAchievementBits( idLocalUser * user ) { + if ( user != NULL ) { + idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > achievements; + + if ( GetAchievementState( user, achievements ) ) { + for ( int i = 0; i < achievements.Num(); i++ ) { + if ( achievements[i] ) { + user->GetProfile()->SetAchievement( i ); + } + } + } + } +} diff --git a/neo/sys/sys_achievements.h b/neo/sys/sys_achievements.h new file mode 100644 index 00000000..ae2e69e3 --- /dev/null +++ b/neo/sys/sys_achievements.h @@ -0,0 +1,123 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_ACHIEVEMENTS_H__ +#define __SYS_ACHIEVEMENTS_H__ + +class idLocalUser; + +// data structure for online achievement entry descriptions +// this is used for testing purposes to make sure that the consoles +// achievement settings match the game's decls +struct achievementDescription_t { + void Clear() { + name[0] = '\0'; + description[0] = '\0'; + hidden = false; + }; + char name[500]; + char description[1000]; + bool hidden; +}; + +/* +================================================ +idAchievementSystem +================================================ +*/ +class idAchievementSystem { +public: + static const int MAX_ACHIEVEMENTS = 128; // This matches the max number of achievements bits in the profile + + virtual ~idAchievementSystem() {} + + // PC and PS3 initialize for the system, not for a particular controller + virtual void Init() {} + + // PS3 has to wait to install the .TRP file until *after* we determine there's enough space, for consistent user messaging + virtual void Start() {} + + // Do any necessary cleanup + virtual void Shutdown() {} + + // Is the achievement system ready for requests + virtual bool IsInitialized() { return false; } + + // Add a local user to the system + virtual void RegisterLocalUser( idLocalUser * user ) {} + + // This is only necessary on the 360 right now, we need this because the 360 maintains a buffer of pending actions + // per user. If a user is removed from the system, we need to inform the system so it can cancel it's in flight actions + // and allow the buffers to be reused + virtual void RemoveLocalUser( idLocalUser * user ) {} + + // Unlocks the achievement, all platforms silently fail if the achievement has already been unlocked + virtual void AchievementUnlock( idLocalUser * user, const int achievementID ) = 0; + + // Puts the achievement back to its original state, platform implementation may not allow this + virtual void AchievementLock( idLocalUser * user, const int achievementID ) {} + + // Puts alls achievements back to their original state, platform implementation may not allow this + virtual void AchievementLockAll( idLocalUser * user, const int maxId ) {} + + // Should be done every frame + virtual void Pump() = 0; + + // Cancels all in-flight achievements for all users if NULL, resets the system so a Init() must be re-issued + virtual void Reset( idLocalUser * user = NULL ) {} + + // Cancels all in-flight achievements, not very useful on PC + virtual void Cancel( idLocalUser * user ) {} + + // Retrieves textual information about a given achievement + // returns false if there was an error + virtual bool GetAchievementDescription( idLocalUser * user, const int id, achievementDescription_t & data ) const { return false; } + + // How much storage is required + // returns false if there was an error + virtual bool GetRequiredStorage( uint64 & requiredSizeTrophiesBytes ) { requiredSizeTrophiesBytes = 0; return true; } + + // Retrieves state about of all achievements cached locally (may not be online yet) + // returns false if there was an error + virtual bool GetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) const { return false; } + + // Sets state of all the achievements within list (for debug purposes only) + // returns false if there was an error + virtual bool SetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) { return false; } + + // You want to get the server's cached achievement status into the user because the profile may not have been + // saved with the achievement bits after an achievement was granted. + void SyncAchievementBits( idLocalUser * user ); + +protected: + // Retrieves the index from the local user list + int GetLocalUserIndex( idLocalUser * user ) const { return users.FindIndex( user ); } + + idStaticList< idLocalUser *, MAX_LOCAL_PLAYERS > users; +}; + +#endif // __SYS_ACHIEVEMENTS_H__ diff --git a/neo/sys/sys_dedicated_server_search.cpp b/neo/sys/sys_dedicated_server_search.cpp new file mode 100644 index 00000000..9dbaeeab --- /dev/null +++ b/neo/sys/sys_dedicated_server_search.cpp @@ -0,0 +1,183 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop + +#include "../idlib/precompiled.h" +#include "sys_lobby_backend.h" +#include "sys_dedicated_server_search.h" + +/* +======================== +idDedicatedServerSearch::idDedicatedServerSearch +======================== +*/ +idDedicatedServerSearch::idDedicatedServerSearch() : + callback( NULL ) { +} + +/* +======================== +idDedicatedServerSearch::~idDedicatedServerSearch +======================== +*/ +idDedicatedServerSearch::~idDedicatedServerSearch() { + if ( callback != NULL ) { + delete callback; + } +} + + + +/* +======================== +idDedicatedServerSearch::StartSearch +======================== +*/ +void idDedicatedServerSearch::StartSearch( const idCallback & cb ) { + Clear(); + callback = cb.Clone(); +} + +/* +======================== +idDedicatedServerSearch::Clear +======================== +*/ +void idDedicatedServerSearch::Clear() { + if ( callback != NULL ) { + delete callback; + callback = NULL; + } + list.Clear(); +} + +/* +======================== +idDedicatedServerSearch::Clear +======================== +*/ +void idDedicatedServerSearch::HandleQueryAck( lobbyAddress_t & addr, idBitMsg & msg ) { + bool found = false; + // Find the server this ack belongs to + for ( int i = 0; i < list.Num(); i++ ) { + serverInfoDedicated_t & query = list[i]; + + + if ( query.addr.Compare( addr ) ) { + // Found the server + found = true; + + bool canJoin = msg.ReadBool(); + + if ( !canJoin ) { + // If we can't join this server, then remove it + list.RemoveIndex( i-- ); + break; + } + + query.serverInfo.Read( msg ); + query.connectedPlayers.Clear(); + for ( int i = 0; i < query.serverInfo.numPlayers; i++ ) { + idStr user; + msg.ReadString( user ); + query.connectedPlayers.Append( user ); + } + break; + } + } + + if ( !found ) { + bool canJoin = msg.ReadBool(); + if ( canJoin ) { + serverInfoDedicated_t newServer; + newServer.addr = addr; + newServer.serverInfo.Read( msg ); + if ( newServer.serverInfo.serverName.IsEmpty() ) { + newServer.serverInfo.serverName = addr.ToString(); + } + newServer.connectedPlayers.Clear(); + for ( int i = 0; i < newServer.serverInfo.numPlayers; i++ ) { + idStr user; + msg.ReadString( user ); + newServer.connectedPlayers.Append( user ); + } + list.Append( newServer ); + } + } + + + if ( callback != NULL ) { + callback->Call(); + } +} + +/* +======================== +idDedicatedServerSearch::GetAddrAtIndex +======================== +*/ +bool idDedicatedServerSearch::GetAddrAtIndex( netadr_t & addr, int i ) { + if ( i >= 0 && i < list.Num() ) { + addr = list[i].addr.netAddr; + return true; + } + return false; +} + +/* +======================== +idDedicatedServerSearch::DescribeServerAtIndex +======================== +*/ +const serverInfo_t * idDedicatedServerSearch::DescribeServerAtIndex( int i ) const { + if ( i >= 0 && i < list.Num() ) { + return &list[i].serverInfo; + } + return NULL; +} + +/* +======================== +idDedicatedServerSearch::GetServerPlayersAtIndex +======================== +*/ +const idList< idStr > * idDedicatedServerSearch::GetServerPlayersAtIndex( int i ) const { + if ( i >= 0 && i < list.Num() ) { + return &list[i].connectedPlayers; + } + return NULL; +} + +/* +======================== +idDedicatedServerSearch::NumServers +======================== +*/ +int idDedicatedServerSearch::NumServers() const { + return list.Num(); +} \ No newline at end of file diff --git a/neo/sys/sys_dedicated_server_search.h b/neo/sys/sys_dedicated_server_search.h new file mode 100644 index 00000000..e7ab0956 --- /dev/null +++ b/neo/sys/sys_dedicated_server_search.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DEDICATEDSERVERSEARCH_H__ +#define __DEDICATEDSERVERSEARCH_H__ + +/* +================================================ +idDedicatedServerSearch +================================================ +*/ +class idDedicatedServerSearch { +public: + idDedicatedServerSearch(); + ~idDedicatedServerSearch(); + + void StartSearch( const idCallback & cb ); + void Clear(); + + void HandleQueryAck( lobbyAddress_t & addr, idBitMsg & msg ); + + + bool GetAddrAtIndex( netadr_t & addr, int i ); + const serverInfo_t * DescribeServerAtIndex( int i ) const; + const idList< idStr > * GetServerPlayersAtIndex( int i ) const; + + int NumServers() const; + +private: + struct serverInfoDedicated_t { + lobbyAddress_t addr; + serverInfo_t serverInfo; + idList< idStr > connectedPlayers; + }; + + idList< serverInfoDedicated_t > list; + idCallback * callback; +}; + +#endif // __DEDICATEDSERVERSEARCH_H__ \ No newline at end of file diff --git a/neo/sys/sys_leaderboards.h b/neo/sys/sys_leaderboards.h new file mode 100644 index 00000000..30fef366 --- /dev/null +++ b/neo/sys/sys_leaderboards.h @@ -0,0 +1,209 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_LEADERBOARDS_H__ +#define __SYS_LEADERBOARDS_H__ + + +/* +================================================================================================ + + Stats (for achievements, matchmaking, etc.) + +================================================================================================ +*/ + +/* +================================================ +systemStats_t +This is to give the framework the ability to deal with stat indexes that are +completely up to each game. The system needs to deal with some stat indexes for things like +the level for matchmaking, etc. +================================================ +*/ + +/* +================================================================================================ + + Leaderboards + +================================================================================================ +*/ + +const int MAX_LEADERBOARDS = 256; +const int MAX_LEADERBOARD_COLUMNS = 16; + +enum aggregationMethod_t { + AGGREGATE_MIN, // Write the new value if it is less than the existing value. + AGGREGATE_MAX, // Write the new value if it is greater than the existing value. + AGGREGATE_SUM, // Add the new value to the existing value and write the result. + AGGREGATE_LAST, // Write the new value. +}; + +enum rankOrder_t { + RANK_GREATEST_FIRST, // Rank the in descending order, greatest score is best score + RANK_LEAST_FIRST, // Rank the in ascending order, lowest score is best score +}; + +enum statsColumnDisplayType_t { + STATS_COLUMN_DISPLAY_NUMBER, + STATS_COLUMN_DISPLAY_TIME_MILLISECONDS, + STATS_COLUMN_DISPLAY_CASH, + STATS_COLUMN_NEVER_DISPLAY, +}; + +struct columnDef_t { + const char * locDisplayName; + int bits; + aggregationMethod_t aggregationMethod; + statsColumnDisplayType_t displayType; +}; + +extern struct leaderboardDefinition_t * registeredLeaderboards[MAX_LEADERBOARDS]; +extern int numRegisteredLeaderboards; + +struct leaderboardDefinition_t { + leaderboardDefinition_t() : + id ( -1 ), + numColumns( 0 ), + columnDefs( NULL ), + rankOrder( RANK_GREATEST_FIRST ), + supportsAttachments( false ), + checkAgainstCurrent( false ) { + } + leaderboardDefinition_t( int id_, int numColumns_, const columnDef_t * columnDefs_, rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) : + id ( id_ ), + numColumns( numColumns_ ), + columnDefs( columnDefs_ ), + rankOrder( rankOrder_ ), + supportsAttachments( supportsAttachments_ ), + checkAgainstCurrent( checkAgainstCurrent_ ) { + assert( numRegisteredLeaderboards < MAX_LEADERBOARDS ); + registeredLeaderboards[numRegisteredLeaderboards++] = this; + } + int32 id; + int32 numColumns; + const columnDef_t * columnDefs; + rankOrder_t rankOrder; + bool supportsAttachments; + bool checkAgainstCurrent; // Compare column 0 with the currently stored leaderboard, and only submit the new leaderboard if the new column 0 is better +}; + +struct column_t { + column_t( int64 value_ ) : value( value_ ) {} + column_t() {} + + int64 value; +}; + + +/* +================================================================================================ +Contains the Achievement and LeaderBoard free function declarations. +================================================================================================ +*/ + +typedef int32 leaderboardHandle_t; + +/* +================================================ +idLeaderBoardEntry +================================================ +*/ +class idLeaderBoardEntry { +public: + static const int MAX_LEADERBOARD_COLUMNS = 16; + idStr username; // aka gamertag + int64 score; + int64 columns[ MAX_LEADERBOARD_COLUMNS ]; +}; + +const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ); + + +//------------------------ +// leaderboardError_t +//------------------------ +enum leaderboardError_t { + LEADERBOARD_ERROR_NONE, + LEADERBOARD_ERROR_FAILED, // General error occurred + LEADERBOARD_ERROR_NOT_ONLINE, // Not online to request leaderboards + LEADERBOARD_ERROR_BUSY, // Unable to download leaderboards right now (download already in progress) + LEADERBOARD_ERROR_INVALID_USER, // Unable to request leaderboards as the given user + LEADERBOARD_ERROR_INVALID_REQUEST, // The leaderboard request was invalid + LEADERBOARD_ERROR_DOWNLOAD, // An error occurred while downloading the leaderboard + LEADERBOARD_ERROR_MAX +}; + +/* +================================================ +idLeaderboardCallback +================================================ +*/ +class idLeaderboardCallback : public idCallback { +public: + struct row_t { + bool hasAttachment; + int64 attachmentID; + idStr name; + int64 rank; + idArray columns; + + long user_id; +// CSteamID user_id; + }; + + idLeaderboardCallback() : def( NULL ), startIndex( -1 ), localIndex( -1 ), numRowsInLeaderboard( -1 ), errorCode( LEADERBOARD_ERROR_NONE ) { } + virtual idLeaderboardCallback * Clone() const = 0; + + // Used by the platform handlers to set data + void ResetRows() { rows.Clear(); } + void AddRow( const row_t & row ) { rows.Append( row ); } + void SetNumRowsInLeaderboard( int32 i ) { numRowsInLeaderboard = i; } + void SetDef( const leaderboardDefinition_t * def_ ) { def = def_; } + void SetStartIndex( int startIndex_ ) { startIndex = startIndex_; } + void SetLocalIndex( int localIndex_ ) { localIndex = localIndex_; } + void SetErrorCode( leaderboardError_t errorCode ) { this->errorCode = errorCode; } + + // Used in user callback for information retrieval + const leaderboardDefinition_t * GetDef() const { return def; } + int GetStartIndex() const { return startIndex; } + const idList< row_t > & GetRows() const { return rows; } + int GetNumRowsInLeaderboard() const { return numRowsInLeaderboard; } + int GetLocalIndex() const { return localIndex; } + leaderboardError_t GetErrorCode() const { return this->errorCode; } + +protected: + const leaderboardDefinition_t * def; // leaderboard def + int startIndex; // where the first row starts in the online leaderboard + int localIndex; // if player is in the rows, this is the offset of him within the returned rows + idList< row_t > rows; // leaderboard entries for the request + int numRowsInLeaderboard; // total number of rows in the online leaderboard + leaderboardError_t errorCode; // error, if any, that occurred during last operation +}; + +#endif // !__SYS_LEADERBOARDS_H__ diff --git a/neo/sys/sys_lobby.cpp b/neo/sys/sys_lobby.cpp new file mode 100644 index 00000000..59714665 --- /dev/null +++ b/neo/sys/sys_lobby.cpp @@ -0,0 +1,4141 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_lobby.h" + +extern idCVar net_connectTimeoutInSeconds; +extern idCVar net_headlessServer; + +idCVar net_checkVersion( "net_checkVersion", "0", CVAR_INTEGER, "Check for matching version when clients connect. 0: normal rules, 1: force check, otherwise no check (pass always)" ); +idCVar net_peerTimeoutInSeconds( "net_peerTimeoutInSeconds", "30", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." ); +idCVar net_peerTimeoutInSeconds_Lobby( "net_peerTimeoutInSeconds_Lobby", "20", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." ); + +// NOTE - The snapshot exchange does the bandwidth challenge +idCVar net_bw_challenge_enable( "net_bw_challenge_enable", "0", CVAR_BOOL, "Enable pre game bandwidth challenge for throttling snap rate" ); + +idCVar net_bw_test_interval( "net_bw_test_interval", "33", CVAR_INTEGER, "MS - how often to send packets in bandwidth test" ); +idCVar net_bw_test_numPackets( "net_bw_test_numPackets", "30", CVAR_INTEGER, "Number of bandwidth challenge packets to send" ); +idCVar net_bw_test_packetSizeBytes( "net_bw_test_packetSizeBytes", "1024", CVAR_INTEGER, "Size of each packet to send out" ); +idCVar net_bw_test_timeout( "net_bw_test_timeout", "500", CVAR_INTEGER, "MS after receiving a bw test packet that client will time out" ); +idCVar net_bw_test_host_timeout( "net_bw_test_host_timeout", "3000", CVAR_INTEGER, "How long host will wait in MS to hear bw results from peers" ); + +idCVar net_bw_test_throttle_rate_pct( "net_bw_test_throttle_rate_pct", "0.80", CVAR_FLOAT, "Min rate % a peer must match in bandwidth challenge before being throttled. 1.0=perfect, 0.0=received nothing" ); +idCVar net_bw_test_throttle_byte_pct( "net_bw_test_throttle_byte_pct", "0.80", CVAR_FLOAT, "Min byte % a peer must match in bandwidth challenge before being throttled. 1.0=perfect (received everything) 0.0=Received nothing" ); +idCVar net_bw_test_throttle_seq_pct( "net_bw_test_throttle_seq_pct", "0.80", CVAR_FLOAT, "Min sequence % a peer must match in bandwidth test before being throttled. 1.0=perfect. This score will be more adversely affected by packet loss than byte %" ); + +idCVar net_ignoreConnects( "net_ignoreConnects", "0", CVAR_INTEGER, "Test as if no one can connect to me. 0 = off, 1 = ignore with no reply, 2 = send goodbye" ); + +idCVar net_skipGoodbye( "net_skipGoodbye", "0", CVAR_BOOL, "" ); + +extern unsigned long NetGetVersionChecksum(); + +/* +======================== +idLobby::idLobby +======================== +*/ +idLobby::idLobby() { + lobbyType = TYPE_INVALID; + sessionCB = NULL; + + localReadSS = NULL; + objMemory = NULL; + haveSubmittedSnaps = false; + + state = STATE_IDLE; + failedReason = FAILED_UNKNOWN; + + host = -1; + peerIndexOnHost = -1; + isHost = false; + needToDisplayMigrateMsg = false; + migrateMsgFlags = 0; + + partyToken = 0; // will be initialized later + loaded = false; + respondToArbitrate = false; + waitForPartyOk = false; + startLoadingFromHost = false; + + nextSendPingValuesTime = 0; + lastPingValuesRecvTime = 0; + + nextSendMigrationGameTime = 0; + nextSendMigrationGamePeer = 0; + + bandwidthChallengeStartTime = 0; + bandwidthChallengeEndTime = 0; + bandwidthChallengeFinished = false; + bandwidthChallengeNumGoodSeq = 0; + + lastSnapBspHistoryUpdateSequence = -1; + + assert( userList.Max() == freeUsers.Max() ); + assert( userList.Max() == userPool.Max() ); + + userPool.SetNum( userPool.Max() ); + + assert( freeUsers.Num() == 0 ); + assert( freeUsers.Num() == 0 ); + + // Initialize free user list + for ( int i = 0; i < userPool.Num(); i++ ) { + freeUsers.Append( &userPool[i] ); + } + + showHostLeftTheSession = false; + connectIsFromInvite = false; +} + +/* +======================== +idLobby::Initialize +======================== +*/ +void idLobby::Initialize( lobbyType_t sessionType_, idSessionCallbacks * callbacks ) { + assert( callbacks != NULL ); + + lobbyType = sessionType_; + sessionCB = callbacks; + + if ( lobbyType == GetActingGameStateLobbyType() ) { + // only needed in multiplayer mode + objMemory = (uint8*)Mem_Alloc( SNAP_OBJ_JOB_MEMORY, TAG_NETWORKING ); + lzwData = (lzwCompressionData_t*)Mem_Alloc( sizeof( lzwCompressionData_t ), TAG_NETWORKING ); + } +} + +//=============================================================================== +// ** BEGIN PUBLIC INTERFACE *** +//=============================================================================== + +/* +======================== +idLobby::StartHosting +======================== +*/ +void idLobby::StartHosting( const idMatchParameters & parms_ ) { + parms = parms_; + + // Allow common to modify the parms + common->OnStartHosting( parms ); + + Shutdown(); // Make sure we're in a shutdown state before proceeding + + assert( GetNumLobbyUsers() == 0 ); + assert( lobbyBackend == NULL ); + + // Get the skill level of all the players that will eventually go into the lobby + StartCreating(); +} + +/* +======================== +idLobby::StartFinding +======================== +*/ +void idLobby::StartFinding( const idMatchParameters & parms_ ) { + parms = parms_; + + Shutdown(); // Make sure we're in a shutdown state before proceeding + + assert( GetNumLobbyUsers() == 0 ); + assert( lobbyBackend == NULL ); + + // Clear search results + searchResults.Clear(); + + lobbyBackend = sessionCB->FindLobbyBackend( parms, sessionCB->GetPartyLobby().GetNumLobbyUsers(), sessionCB->GetPartyLobby().GetAverageSessionLevel(), idLobbyBackend::TYPE_GAME ); + + SetState( STATE_SEARCHING ); +} + +/* +======================== +idLobby::Pump +======================== +*/ +void idLobby::Pump() { + + // Check the heartbeat of all our peers, make sure we shouldn't disconnect from peers that haven't sent a heartbeat in awhile + CheckHeartBeats(); + + UpdateHostMigration(); + + UpdateLocalSessionUsers(); + + switch ( state ) { + case STATE_IDLE: State_Idle(); break; + case STATE_CREATE_LOBBY_BACKEND: State_Create_Lobby_Backend(); break; + case STATE_SEARCHING: State_Searching(); break; + case STATE_OBTAINING_ADDRESS: State_Obtaining_Address(); break; + case STATE_CONNECT_HELLO_WAIT: State_Connect_Hello_Wait(); break; + case STATE_FINALIZE_CONNECT: State_Finalize_Connect(); break; + case STATE_FAILED: break; + default: + idLib::Error( "idLobby::Pump: Unknown state." ); + } +} + +/* +======================== +idLobby::ProcessSnapAckQueue +======================== +*/ +void idLobby::ProcessSnapAckQueue() { + SCOPED_PROFILE_EVENT( "ProcessSnapAckQueue" ); + + const int SNAP_ACKS_TO_PROCESS_PER_FRAME = 1; + + int numProcessed = 0; + + while ( snapDeltaAckQueue.Num() > 0 && numProcessed < SNAP_ACKS_TO_PROCESS_PER_FRAME ) { + if ( ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ) ) { + numProcessed++; + } + snapDeltaAckQueue.RemoveIndex( 0 ); + } +} + +/* +======================== +idLobby::Shutdown +======================== +*/ +void idLobby::Shutdown( bool retainMigrationInfo, bool skipGoodbye ) { + + // Cancel host migration if we were in the process of it and this is the session type that was migrating + if ( !retainMigrationInfo && migrationInfo.state != MIGRATE_NONE ) { + idLib::Printf( "Cancelling host migration on %s.\n", GetLobbyName() ); + EndMigration(); + } + + failedReason = FAILED_UNKNOWN; + + if ( lobbyBackend == NULL ) { + NET_VERBOSE_PRINT( "NET: ShutdownLobby (already shutdown) (%s)\n", GetLobbyName() ); + + // If we don't have this lobbyBackend type, we better be properly shutdown for this lobby + assert( GetNumLobbyUsers() == 0 ); + assert( host == -1 ); + assert( peerIndexOnHost == -1 ); + assert( !isHost ); + assert( lobbyType != GetActingGameStateLobbyType() || !loaded ); + assert( lobbyType != GetActingGameStateLobbyType() || !respondToArbitrate ); + assert( snapDeltaAckQueue.Num() == 0 ); + + // Make sure we don't have old peers connected to this lobby + for ( int p = 0; p < peers.Num(); p++ ) { + assert( peers[p].GetConnectionState() == CONNECTION_FREE ); + } + + state = STATE_IDLE; + + return; + } + + NET_VERBOSE_PRINT( "NET: ShutdownLobby (%s)\n", GetLobbyName() ); + + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].GetConnectionState() != CONNECTION_FREE ) { + SetPeerConnectionState( p, CONNECTION_FREE, skipGoodbye ); // This will send goodbye's + } + } + + // Remove any users that weren't handled in ResetPeers + // (this will happen as a client, because we won't get the reliable msg from the server since we are severing the connection) + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * user = GetLobbyUser( i ); + UnregisterUser( user ); + } + + FreeAllUsers(); + + host = -1; + peerIndexOnHost = -1; + isHost = false; + needToDisplayMigrateMsg = false; + migrationDlg = GDM_INVALID; + + partyToken = 0; // Reset our party token so we recompute + loaded = false; + respondToArbitrate = false; + waitForPartyOk = false; + startLoadingFromHost = false; + + snapDeltaAckQueue.Clear(); + + // Shutdown the lobbyBackend + if ( !retainMigrationInfo ) { + sessionCB->DestroyLobbyBackend( lobbyBackend ); + lobbyBackend = NULL; + } + + state = STATE_IDLE; +} + +/* +======================== +idLobby::HandlePacket +======================== +*/ +void idLobby::HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ) { + SCOPED_PROFILE_EVENT( "HandlePacket" ); + + // msg will hold a fully constructed msg using the packet processor + byte msgBuffer[ idPacketProcessor::MAX_MSG_SIZE ]; + + idBitMsg msg; + msg.InitWrite( msgBuffer, sizeof( msgBuffer ) ); + + int peerNum = FindPeer( remoteAddress, sessionID ); + int type = idPacketProcessor::RETURN_TYPE_NONE; + int userData = 0; + + if ( peerNum >= 0 ) { + if ( !peers[peerNum].IsActive() ) { + idLib::Printf( "NET: Received in-band packet from peer %s with no active connection.\n", remoteAddress.ToString() ); + return; + } + type = peers[ peerNum ].packetProc->ProcessIncoming( Sys_Milliseconds(), peers[peerNum].sessionID, fragMsg, msg, userData, peerNum ); + } else { + if ( !idPacketProcessor::ProcessConnectionlessIncoming( fragMsg, msg, userData ) ) { + idLib::Printf( "ProcessConnectionlessIncoming FAILED from %s.\n", remoteAddress.ToString() ); + // Not a valid connectionless packet + return; + } + + // Valid connectionless packets are always RETURN_TYPE_OOB + type = idPacketProcessor::RETURN_TYPE_OOB; + + // Find the peer this connectionless msg should go to + peerNum = FindPeer( remoteAddress, sessionID, true ); + } + + if ( type == idPacketProcessor::RETURN_TYPE_NONE ) { + // This packet is not necessarily invalid, it could be a start or middle of a fragmented packet that's not fully constructed. + return; + } + + if ( peerNum >= 0 ) { + // Update their heart beat (only if we've received a valid packet (we've checked type == idPacketProcessor::RETURN_TYPE_NONE)) + peers[peerNum].lastHeartBeat = Sys_Milliseconds(); + } + + // Handle server query requests. We do this before the STATE_IDLE check. This is so we respond. + // We may want to change this to just ignore the request if we are idle, and change the timeout time + // on the requesters part to just timeout faster. + if ( type == idPacketProcessor::RETURN_TYPE_OOB ) { + if ( userData == OOB_MATCH_QUERY || userData == OOB_SYSTEMLINK_QUERY ) { + sessionCB->HandleServerQueryRequest( remoteAddress, msg, userData ); + return; + } + if ( userData == OOB_MATCH_QUERY_ACK ) { + sessionCB->HandleServerQueryAck( remoteAddress, msg ); + return; + } + } + + if ( type == idPacketProcessor::RETURN_TYPE_OOB ) { + if ( userData == OOB_VOICE_AUDIO ) { + sessionCB->HandleOobVoiceAudio( remoteAddress, msg ); + } else if ( userData == OOB_HELLO ) { + // Handle new peer connect request + peerNum = HandleInitialPeerConnection( msg, remoteAddress, peerNum ); + return; + } else if ( userData == OOB_MIGRATE_INVITE ) { + NET_VERBOSE_PRINT( "NET: Migration invite for session %s from %s (state = %s)\n", GetLobbyName(), remoteAddress.ToString(), session->GetStateString() ); + + // Get connection info + lobbyConnectInfo_t connectInfo; + connectInfo.ReadFromMsg( msg ); + + if ( lobbyBackend != NULL && lobbyBackend->GetState() != idLobbyBackend::STATE_FAILED && lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) { // Ignore duplicate invites + idLib::Printf( "NET: Already migrated to %s.\n", remoteAddress.ToString() ); + return; + } + + if ( migrationInfo.state == MIGRATE_NONE ) { + if ( IsPeer() && host >= 0 && host < peers.Num() && Sys_Milliseconds() - peers[host].lastHeartBeat > 8 * 1000 ) { + // Force migration early if we get an invite, and it has been some time since we've heard from the host + PickNewHost(); + } else { + idLib::Printf( "NET: Ignoring migration invite because we are not migrating %s\n", remoteAddress.ToString() ); + SendGoodbye( remoteAddress ); // So they can remove us from their invite list + return; + } + } + + if ( !sessionCB->PreMigrateInvite( *this ) ) { + NET_VERBOSE_PRINT( "NET: sessionCB->PreMigrateInvite( *this ) failed from %s\n", remoteAddress.ToString() ); + return; + } + + // If we are also becoming a new host, see who wins + if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) { + int inviteIndex = FindMigrationInviteIndex( remoteAddress ); + + if ( inviteIndex != -1 ) { + // We found them in our list, check to make sure our ping is better + int ping1 = migrationInfo.ourPingMs; + lobbyUserID_t userId1 = migrationInfo.ourUserId; + int ping2 = migrationInfo.invites[inviteIndex].pingMs; + lobbyUserID_t userId2 = migrationInfo.invites[inviteIndex].userId; + + if ( IsBetterHost( ping1, userId1, ping2, userId2 ) ) { + idLib::Printf( "NET: Ignoring migration invite from %s, since our ping is better (%i / %i).\n", remoteAddress.ToString(), ping1, ping2 ); + return; + } + } + } + + bool fromGame = msg.ReadBool(); + + // Kill the current lobbyBackend + Shutdown(); + + // Connect to the lobby + ConnectTo( connectInfo, true ); // Pass in true for the invite flag, so we can connect to invite only lobby if we need to + + if ( verify( sessionCB != NULL ) ) { + if ( sessionCB->BecomingPeer( *this ) ) { + migrationInfo.persistUntilGameEndsData.wasMigratedJoin = true; + migrationInfo.persistUntilGameEndsData.wasMigratedGame = fromGame; + } + } + + } else if ( userData == OOB_GOODBYE || userData == OOB_GOODBYE_W_PARTY || userData == OOB_GOODBYE_FULL ) { + HandleGoodbyeFromPeer( peerNum, remoteAddress, userData ); + return; + } else if ( userData == OOB_RESOURCE_LIST ) { + + if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) { + return; + } + + if ( peerNum != host ) { + NET_VERBOSE_PRINT( "NET: Resource list from non-host %i, %s\n", peerNum, remoteAddress.ToString() ); + return; + } + + if ( peerNum >= 0 && !peers[peerNum].IsConnected() ) { + NET_VERBOSE_PRINT( "NET: Resource list from host with no game connection: %i, %s\n", peerNum, remoteAddress.ToString() ); + return; + } + } else if ( userData == OOB_BANDWIDTH_TEST ) { + int seqNum = msg.ReadLong(); + // TODO: We should read the random data and verify the MD5 checksum + + int time = Sys_Milliseconds(); + bool inOrder = ( seqNum == 0 || peers[peerNum].bandwidthSequenceNum + 1 == seqNum ); + int timeSinceLast = 0; + + if ( bandwidthChallengeStartTime <= 0 ) { + // Reset the test + NET_VERBOSE_PRINT( "\nNET: Starting bandwidth test @ %d\n", time ); + bandwidthChallengeStartTime = time; + peers[peerNum].bandwidthSequenceNum = 0; + peers[peerNum].bandwidthTestBytes = peers[peerNum].packetProc->GetIncomingBytes(); + } else { + timeSinceLast = time - (bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ); + } + + if ( inOrder ) { + bandwidthChallengeNumGoodSeq++; + } + + bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ); + NET_VERBOSE_PRINT( " NET: %sRecevied OOB bandwidth test %d delta time: %d incoming rate: %.2f incoming rate 2: %d\n", inOrder ? "^2" : "^1", seqNum, timeSinceLast, peers[peerNum].packetProc->GetIncomingRateBytes(), peers[peerNum].packetProc->GetIncomingRate2() ); + peers[peerNum].bandwidthSequenceNum = seqNum; + + } else { + NET_VERBOSE_PRINT( "NET: Unknown oob packet %d from %s (%d)\n", userData, remoteAddress.ToString(), peerNum ); + } + } else if ( type == idPacketProcessor::RETURN_TYPE_INBAND ) { + // Process in-band message + if ( peerNum < 0 ) { + idLib::Printf( "NET: In-band message from unknown peer: %s\n", remoteAddress.ToString() ); + return; + } + + if ( !verify( peers[ peerNum ].address.Compare( remoteAddress ) ) ) { + idLib::Printf( "NET: Peer with wrong address: %i, %s\n", peerNum, remoteAddress.ToString() ); + return; + } + + // Handle reliable + int numReliable = peers[ peerNum ].packetProc->GetNumReliables(); + for ( int r = 0; r < numReliable; r++ ) { + // Just in case one of the reliable msg's cause this peer to disconnect + // (this can happen when our party/game host is the same, he quits the game lobby, and sends a reliable msg for us to leave the game) + peerNum = FindPeer( remoteAddress, sessionID ); + + if ( peerNum == -1 ) { + idLib::Printf( "NET: Dropped peer while processing reliable msg's: %i, %s\n", peerNum, remoteAddress.ToString() ); + break; + } + + const byte * reliableData = peers[ peerNum ].packetProc->GetReliable( r ); + int reliableSize = peers[ peerNum ].packetProc->GetReliableSize( r ); + idBitMsg reliableMsg( reliableData, reliableSize ); + reliableMsg.SetSize( reliableSize ); + + HandleReliableMsg( peerNum, reliableMsg ); + } + + if ( peerNum == -1 || !peers[ peerNum ].IsConnected() ) { + // If the peer still has no connection after HandleReliableMsg, then something is wrong. + // (We could have been in CONNECTION_CONNECTING state for this session type, but the first message + // we should receive from the server is the ack, otherwise, something went wrong somewhere) + idLib::Printf( "NET: In-band message from host with no active connection: %i, %s\n", peerNum, remoteAddress.ToString() ); + return; + } + + // Handle unreliable part (if any) + if ( msg.GetRemainingData() > 0 && loaded ) { + if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) { + idLib::Printf( "NET: Snapshot msg for non game session lobby %s\n", remoteAddress.ToString() ); + return; + } + + if ( peerNum == host ) { + idSnapShot localSnap; + int sequence = -1; + int baseseq = -1; + bool fullSnap = false; + localReadSS = &localSnap; + + // If we are the peer, we assume we only receive snapshot data on the in-band channel + const byte * deltaData = msg.GetReadData() + msg.GetReadCount(); + int deltaLength = msg.GetRemainingData(); + + if ( peers[ peerNum ].snapProc->ReceiveSnapshotDelta( deltaData, deltaLength, 0, sequence, baseseq, localSnap, fullSnap ) ) { + + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Got %s snapshot %d delta'd against %d. SS Time: %d\n", ( fullSnap ? "partial" : "full" ), sequence, baseseq, localSnap.GetTime() ) ); + + if ( sessionCB->GetState() != idSession::INGAME && sequence != -1 ) { + int seq = peers[ peerNum ].snapProc->GetLastAppendedSequence(); + + // When we aren't in the game, we need to send this as reliable msg's, since usercmds won't be taking care of it for us + byte ackbuffer[32]; + idBitMsg ackmsg( ackbuffer, sizeof( ackbuffer ) ); + ackmsg.WriteLong( seq ); + + // Add incoming BPS for QoS + float incomingBPS = peers[ peerNum ].receivedBps; + if ( peers[ peerNum ].receivedBpsIndex != seq ) { + incomingBPS = idMath::ClampFloat( 0.0f, static_cast( idLobby::BANDWIDTH_REPORTING_MAX ), peers[host].packetProc->GetIncomingRateBytes() ); + peers[ peerNum ].receivedBpsIndex = seq; + peers[ peerNum ].receivedBps = incomingBPS; + } + + ackmsg.WriteQuantizedUFloat< idLobby::BANDWIDTH_REPORTING_MAX, idLobby::BANDWIDTH_REPORTING_BITS >( incomingBPS ); + QueueReliableMessage( host, RELIABLE_SNAPSHOT_ACK, ackbuffer, sizeof( ackbuffer ) ); + } + } + + if ( fullSnap ) { + sessionCB->ReceivedFullSnap(); + common->NetReceiveSnapshot( localSnap ); + } + + localReadSS = NULL; + + } else { + // If we are the host, we assume we only receive usercmds on the inband channel + + int snapNum = 0; + uint16 receivedBps_quantized = 0; + + byte usercmdBuffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE]; + + lzwCompressionData_t lzwData; + idLZWCompressor lzwCompressor( &lzwData ); + lzwCompressor.Start( const_cast( msg.GetReadData() ) + msg.GetReadCount(), msg.GetRemainingData() ); + lzwCompressor.ReadAgnostic( snapNum ); + lzwCompressor.ReadAgnostic( receivedBps_quantized ); + int usercmdSize = lzwCompressor.Read( usercmdBuffer, sizeof( usercmdBuffer ), true ); + lzwCompressor.End(); + + float receivedBps = ( receivedBps_quantized / (float)( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) ) * (float)idLobby::BANDWIDTH_REPORTING_MAX; + if ( peers[ peerNum ].receivedBpsIndex != snapNum ) { + peers[ peerNum ].receivedBps = receivedBps; + peers[ peerNum ].receivedBpsIndex = snapNum; + } + + if ( snapNum < 50 ) { + NET_VERBOSE_PRINT( "NET: peer %d ack'd snapNum %d\n", peerNum, snapNum ); + } + ApplySnapshotDelta( peerNum, snapNum ); + + idBitMsg usercmdMsg( (const byte *)usercmdBuffer, usercmdSize ); + common->NetReceiveUsercmds( peerNum, usercmdMsg ); + } + } + } +} + +/* +======================== +idLobby::HasActivePeers +======================== +*/ +bool idLobby::HasActivePeers() const { + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].GetConnectionState() != CONNECTION_FREE ) { + return true; + } + } + + return false; +} + +/* +======================== +idLobby::NumFreeSlots +======================== +*/ +int idLobby::NumFreeSlots() const { + if ( parms.matchFlags & MATCH_JOIN_IN_PROGRESS ) { + return parms.numSlots - GetNumConnectedUsers(); + } else { + return parms.numSlots - GetNumLobbyUsers(); + } +} + +//=============================================================================== +// ** END PUBLIC INTERFACE *** +//=============================================================================== + +//=============================================================================== +// ** BEGIN STATE CODE *** +//=============================================================================== + +const char * idLobby::stateToString[ NUM_STATES ] = { + ASSERT_ENUM_STRING( STATE_IDLE, 0 ), + ASSERT_ENUM_STRING( STATE_CREATE_LOBBY_BACKEND, 1 ), + ASSERT_ENUM_STRING( STATE_SEARCHING, 2 ), + ASSERT_ENUM_STRING( STATE_OBTAINING_ADDRESS, 3 ), + ASSERT_ENUM_STRING( STATE_CONNECT_HELLO_WAIT, 4 ), + ASSERT_ENUM_STRING( STATE_FINALIZE_CONNECT, 5 ), + ASSERT_ENUM_STRING( STATE_FAILED, 6 ), +}; + +/* +======================== +idLobby::State_Idle +======================== +*/ +void idLobby::State_Idle() { + // If lobbyBackend is in a failed state, shutdown, go to a failed state ourself, and return + if ( lobbyBackend != NULL && lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED ) { + HandleConnectionAttemptFailed(); + common->Dialog().ClearDialog( GDM_MIGRATING ); + common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); + common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); + return; + } + + if ( migrationInfo.persistUntilGameEndsData.hasGameData && sessionCB->GetState() <= idSession::IDLE ) { + // This can happen with 'leaveGame' or 'disconnect' since those paths don't go through endMatch + // This seems like an ok catch all place but there may be a better way to handle this + ResetAllMigrationState(); + common->Dialog().ClearDialog( GDM_MIGRATING ); + common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); + common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); + } +} + +/* +======================== +idLobby::State_Create_Lobby_Backend +======================== +*/ +void idLobby::State_Create_Lobby_Backend() { + if ( !verify( lobbyBackend != NULL ) ) { + SetState( STATE_FAILED ); + return; + } + + assert( lobbyBackend != NULL ); + + if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) { + const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 ); + + // If we are taking too long, cancel the connection + if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) { + if ( Sys_Milliseconds() - migrationInfo.migrationStartTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) { + SetState( STATE_FAILED ); + return; + } + } + } + + if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) { + return; // Busy but valid + } + + if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { + SetState( STATE_FAILED ); + return; + } + + // Success + InitStateLobbyHost(); + + // Set state to idle to signify to session we are done creating + SetState( STATE_IDLE ); +} + +/* +======================== +idLobby::State_Searching +======================== +*/ +void idLobby::State_Searching() { + if ( !verify( lobbyBackend != NULL ) ) { + SetState( STATE_FAILED ); + return; + } + + if ( lobbyBackend->GetState() == idLobbyBackend::STATE_SEARCHING ) { + return; // Busy but valid + } + + if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { + SetState( STATE_FAILED ); // Any other lobbyBackend state is invalid + return; + } + + // Done searching, get results from lobbyBackend + lobbyBackend->GetSearchResults( searchResults ); + + if ( searchResults.Num() == 0 ) { + // If we didn't get any results, set state to failed + SetState( STATE_FAILED ); + return; + } + + extern idCVar net_maxSearchResultsToTry; + const int maxSearchResultsToTry = session->GetTitleStorageInt( "net_maxSearchResultsToTry", net_maxSearchResultsToTry.GetInteger() ); + + if ( searchResults.Num() > maxSearchResultsToTry ) { + searchResults.SetNum( maxSearchResultsToTry ); + } + + // Set state to idle to signify we are done searching + SetState( STATE_IDLE ); +} + +/* +======================== +idLobby::State_Obtaining_Address +======================== +*/ +void idLobby::State_Obtaining_Address() { + if ( lobbyBackend->GetState() == idLobbyBackend::STATE_OBTAINING_ADDRESS ) { + return; // Valid but not ready + } + + if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { + // There was an error, signify to caller + failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED; + NET_VERBOSE_PRINT("idLobby::State_Obtaining_Address: the lobby backend failed." ); + SetState( STATE_FAILED ); + return; + } + + // + // We have the address of the lobbyBackend, we can now send a hello packet + // + + // This will be the host for this lobby type + host = AddPeer( hostAddress, GenerateSessionID() ); + + // Record start time of connection attempt to the host + helloStartTime = Sys_Milliseconds(); + lastConnectRequest = helloStartTime; + connectionAttempts = 0; + + // Change state to connecting + SetState( STATE_CONNECT_HELLO_WAIT ); + + // Send first connect attempt now (we'll send more periodically if we fail to receive an ack) + // (we do this after changing state, since the function expects we're in the right state) + SendConnectionRequest(); +} + +/* +======================== +idLobby::State_Finalize_Connect +======================== +*/ +void idLobby::State_Finalize_Connect() { + if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) { + // Valid but busy + return; + } + + if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { + // Any other state not valid, failed + SetState( STATE_FAILED ); + return; + } + + // Success + SetState( STATE_IDLE ); + + // Tell session mgr if this was a migration + if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) { + sessionCB->BecamePeer( *this ); + } +} + +/* +======================== +idLobby::State_Connect_Hello_Wait +======================== +*/ +void idLobby::State_Connect_Hello_Wait() { + if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { + // If the lobbyBackend is in an error state, shut everything down + NET_VERBOSE_PRINT( "NET: Lobby is no longer ready while waiting for lobbyType %s hello.\n", GetLobbyName() ); + HandleConnectionAttemptFailed(); + return; + } + + int time = Sys_Milliseconds(); + + const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000; + + if ( timeoutMs != 0 && time - helloStartTime > timeoutMs ) { + NET_VERBOSE_PRINT( "NET: Timeout waiting for lobbyType %s for party hello.\n", GetLobbyName() ); + HandleConnectionAttemptFailed(); + return; + } + + if ( connectionAttempts < MAX_CONNECT_ATTEMPTS ) { + assert( connectionAttempts >= 1 ); // Should have at least the initial connection attempt + + // See if we need to send another hello request + // (keep getting more frequent to increase chance due to possible packet loss, but clamp to MIN_CONNECT_FREQUENCY seconds) + // TODO: We could eventually make timing out a function of actual number of attempts rather than just plain time. + int resendTime = Max( MIN_CONNECT_FREQUENCY_IN_SECONDS, CONNECT_REQUEST_FREQUENCY_IN_SECONDS / connectionAttempts ) * 1000; + + if ( time - lastConnectRequest > resendTime ) { + SendConnectionRequest(); + lastConnectRequest = time; + } + } +} + +/* +======================== +idLobby::SetState +======================== +*/ +void idLobby::SetState( lobbyState_t newState ) { + assert( newState < NUM_STATES ); + assert( state < NUM_STATES ); + + verify_array_size( stateToString, NUM_STATES ); + + if ( state == newState ) { + NET_VERBOSE_PRINT( "NET: idLobby::SetState: State SAME %s for session %s\n", stateToString[ newState ], GetLobbyName() ); + return; + } + + // Set the current state + NET_VERBOSE_PRINT( "NET: idLobby::SetState: State changing from %s to %s for session %s\n", stateToString[ state ], stateToString[ newState ], GetLobbyName() ); + + state = newState; +} + +//=============================================================================== +// ** END STATE CODE *** +//=============================================================================== + +/* +======================== +idLobby::StartCreating +======================== +*/ +void idLobby::StartCreating() { + assert( lobbyBackend == NULL ); + assert( state == STATE_IDLE ); + + float skillLevel = GetAverageLocalUserLevel( true ); + + lobbyBackend = sessionCB->CreateLobbyBackend( parms, skillLevel, (idLobbyBackend::lobbyBackendType_t)lobbyType ); + + SetState( STATE_CREATE_LOBBY_BACKEND ); +} + +/* +======================== +idLobby::FindPeer +======================== +*/ +int idLobby::FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID ) { + + bool connectionless = ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY || + sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME || + sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ); + + if ( connectionless && !ignoreSessionID ) { + return -1; // This was meant to be connectionless. FindPeer is meant for connected (or connecting) peers + } + + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].GetConnectionState() == CONNECTION_FREE ) { + continue; + } + + if ( peers[p].address.Compare( remoteAddress ) ) { + if ( connectionless && ignoreSessionID ) { + return p; + } + + // Using a rolling check, so that we account for possible packet loss, and out of order issues + if ( IsPeer() ) { + idPacketProcessor::sessionId_t searchStart = peers[p].sessionID; + + // Since we only roll the code between matches, we should only need to look ahead a couple increments. + // Worse case, if the stars line up, the client doesn't see the new sessionId, and times out, and gets booted. + // This should be impossible though, since the timings won't be possible considering how long it takes to end the match, + // and restart, and then restart again. + int numTries = 2; + + while ( numTries-- > 0 && searchStart != sessionID ) { + searchStart = IncrementSessionID( searchStart ); + if ( searchStart == sessionID ) { + idLib::Printf( "NET: Rolling session ID check found new ID: %i\n", searchStart ); + if ( peers[p].packetProc != NULL ) { + peers[p].packetProc->VerifyEmptyReliableQueue( RELIABLE_GAME_DATA, RELIABLE_DUMMY_MSG ); + } + peers[p].sessionID = searchStart; + break; + } + } + } + + if ( peers[p].sessionID != sessionID ) { + continue; + } + return p; + } + } + return -1; +} + +/* +======================== +idLobby::FindAnyPeer +Find a peer when we don't know the session id, and we don't care since it's a connectionless msg +======================== +*/ +int idLobby::FindAnyPeer( const lobbyAddress_t & remoteAddress ) const { + + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].GetConnectionState() == CONNECTION_FREE ) { + continue; + } + + if ( peers[p].address.Compare( remoteAddress ) ) { + return p; + } + } + return -1; +} + +/* +======================== +idLobby::FindFreePeer +======================== +*/ +int idLobby::FindFreePeer() const { + + // Return the first non active peer + for ( int p = 0; p < peers.Num(); p++ ) { + if ( !peers[p].IsActive() ) { + return p; + } + } + return -1; +} + +/* +======================== +idLobby::AddPeer +======================== +*/ +int idLobby::AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ) { + // First, make sure we don't already have this peer + int p = FindPeer( remoteAddress, sessionID ); + assert( p == -1 ); // When using session ID's, we SHOULDN'T find this remoteAddress/sessionID combo + + if ( p == -1 ) { + // If we didn't find the peer, we need to add a new one + + p = FindFreePeer(); + + if ( p == -1 ) { + peer_t newPeer; + p = peers.Append( newPeer ); + } + + peer_t & peer = peers[p]; + + peer.ResetAllData(); + + assert( peer.connectionState == CONNECTION_FREE ); + + peer.address = remoteAddress; + + peer.sessionID = sessionID; + + NET_VERBOSE_PRINT( "NET: Added peer %s at index %i\n", remoteAddress.ToString(), p ); + } else { + NET_VERBOSE_PRINT( "NET: Found peer %s at index %i\n", remoteAddress.ToString(), p ); + } + + SetPeerConnectionState( p, CONNECTION_CONNECTING ); + + if ( lobbyType == GetActingGameStateLobbyType() ) { + // Reset various flags used in game mode + peers[p].ResetMatchData(); + } + + return p; +} + +/* +======================== +idLobby::DisconnectPeerFromSession +======================== +*/ +void idLobby::DisconnectPeerFromSession( int p ) { + if ( !verify( IsHost() ) ) { + return; + } + + peer_t & peer = peers[p]; + + if ( peer.GetConnectionState() != CONNECTION_FREE ) { + SetPeerConnectionState( p, CONNECTION_FREE ); + } +} + +/* +======================== +idLobby::DisconnectAllPeers +======================== +*/ +void idLobby::DisconnectAllPeers() { + for ( int p = 0; p < peers.Num(); p++ ) { + DisconnectPeerFromSession( p ); + } +} + +/* +======================== +idLobby::SendGoodbye +======================== +*/ +void idLobby::SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull ) { + + if ( net_skipGoodbye.GetBool() ) { + return; + } + + NET_VERBOSE_PRINT( "NET: Sending goodbye to %s for %s (wasFull = %i)\n", remoteAddress.ToString(), GetLobbyName(), wasFull ); + + static const int NUM_REDUNDANT_GOODBYES = 10; + + int msgType = OOB_GOODBYE; + + if ( wasFull ) { + msgType = OOB_GOODBYE_FULL; + } else if ( lobbyType == TYPE_GAME && ( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) && !( parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) ) { + msgType = OOB_GOODBYE_W_PARTY; + } + + for ( int i = 0; i < NUM_REDUNDANT_GOODBYES; i++ ) { + SendConnectionLess( remoteAddress, msgType ); + } +} + +/* +======================== +idLobby::SetPeerConnectionState +======================== +*/ +void idLobby::SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye ) { + + if ( !verify( p >= 0 && p < peers.Num() ) ) { + idLib::Printf( "NET: SetPeerConnectionState invalid peer index %i\n", p ); + return; + } + + peer_t & peer = peers[p]; + + const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType(); + + if ( peer.GetConnectionState() == newState ) { + idLib::Printf( "NET: SetPeerConnectionState: Peer already in state %i\n", newState ); + assert( 0 ); // This case means something is most likely bad, and it's the programmers fault + assert( ( peer.packetProc != NULL ) == peer.IsActive() ); + assert( ( ( peer.snapProc != NULL ) == peer.IsActive() ) == ( actingGameStateLobbyType == lobbyType ) ); + return; + } + + if ( newState == CONNECTION_CONNECTING ) { + //mem.PushHeap(); + + // We better be coming from a free connection state if we are trying to connect + assert( peer.GetConnectionState() == CONNECTION_FREE ); + + assert( peer.packetProc == NULL ); + peer.packetProc = new ( TAG_NETWORKING )idPacketProcessor(); + + if ( lobbyType == actingGameStateLobbyType ) { + assert( peer.snapProc == NULL ); + peer.snapProc = new ( TAG_NETWORKING )idSnapshotProcessor(); + } + + //mem.PopHeap(); + } else if ( newState == CONNECTION_ESTABLISHED ) { + // If we are marking this peer as connected for the first time, make sure this peer was actually trying to connect. + assert( peer.GetConnectionState() == CONNECTION_CONNECTING ); + } else if ( newState == CONNECTION_FREE ) { + // If we are freeing this connection and we had an established connection before, make sure to send a goodbye + if ( peer.GetConnectionState() == CONNECTION_ESTABLISHED && !skipGoodbye ) { + idLib::Printf("SetPeerConnectionState: Sending goodbye to peer %s from session %s\n", peer.address.ToString(), GetLobbyName() ); + SendGoodbye( peer.address ); + } + } + + peer.connectionState = newState; + + if ( !peer.IsActive() ) { + if ( peer.packetProc != NULL ) { + delete peer.packetProc; + peer.packetProc = NULL; + } + + if ( peer.snapProc != NULL ) { + assert( lobbyType == actingGameStateLobbyType ); + delete peer.snapProc; + peer.snapProc = NULL; + } + } + + // Do this in case we disconnected the peer + if ( IsHost() ) { + RemoveUsersWithDisconnectedPeers(); + } +} + +/* +======================== +idLobby::QueueReliableMessage +======================== +*/ +void idLobby::QueueReliableMessage( int p, byte type, const byte * data, int dataLen ) { + if ( !verify( p >= 0 && p < peers.Num() ) ) { + return; + } + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + // Don't send to this peer if we don't have an established connection of this session type + NET_VERBOSE_PRINT( "NET: Not sending reliable type %i to peer %i because connectionState is %i\n", type, p, peer.GetConnectionState() ); + return; + } + + if ( peer.packetProc->NumQueuedReliables() > 2 ) { + idLib::PrintfIf( false, "NET: peer.packetProc->NumQueuedReliables() > 2: %i (%i / %s)\n", peer.packetProc->NumQueuedReliables(), p, peer.address.ToString() ); + } + + if ( !peer.packetProc->QueueReliableMessage( type, data, dataLen ) ) { + // For now, when this happens, disconnect from all session types + NET_VERBOSE_PRINT( "NET: Dropping peer because we overflowed his reliable message queue\n" ); + if ( IsHost() ) { + // Disconnect peer from this session type + DisconnectPeerFromSession( p ); + } else { + Shutdown(); // Shutdown session if we can't queue the reliable + } + } +} + +/* +======================== +idLobby::GetNumConnectedPeers +======================== +*/ +int idLobby::GetNumConnectedPeers() const { + int numConnected = 0; + for ( int i = 0; i < peers.Num(); i++ ) { + if ( peers[i].IsConnected() ) { + numConnected++; + } + } + + return numConnected; +} + +/* +======================== +idLobby::GetNumConnectedPeersInGame +======================== +*/ +int idLobby::GetNumConnectedPeersInGame() const { + int numActive = 0; + for ( int i = 0; i < peers.Num(); i++ ) { + if ( peers[i].IsConnected() && peers[i].inGame ) { + numActive++; + } + } + + return numActive; +} + + +/* +======================== +idLobby::SendMatchParmsToPeers +======================== +*/ +void idLobby::SendMatchParmsToPeers() { + if ( !IsHost() ) { + return; + } + + if ( GetNumConnectedPeers() == 0 ) { + return; + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + parms.Write( msg ); + + for ( int p = 0; p < peers.Num(); p++ ) { + if ( !peers[p].IsConnected() ) { + continue; + } + QueueReliableMessage( p, RELIABLE_MATCH_PARMS, msg.GetReadData(), msg.GetSize() ); + } +} + +/* +======================== +STATIC idLobby::IsReliablePlayerToPlayerType +======================== +*/ +bool idLobby::IsReliablePlayerToPlayerType( byte type ) { + return ( type >= RELIABLE_PLAYER_TO_PLAYER_BEGIN ) && ( type < RELIABLE_PLAYER_TO_PLAYER_END ); +} + +/* +======================== +idLobby::HandleReliablePlayerToPlayerMsg +======================== +*/ +void idLobby::HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ) { + reliablePlayerToPlayerHeader_t info; + int c, b; + msg.SaveReadState( c, b ); // in case we need to forward or fail + + if ( !info.Read( this, msg ) ) { + idLib::Warning( "NET: Ignoring invalid reliable player to player message" ); + msg.RestoreReadState( c, b ); + return; + } + + const bool isForLocalPlayer = IsSessionUserIndexLocal( info.toSessionUserIndex ); + + if ( isForLocalPlayer ) { + HandleReliablePlayerToPlayerMsg( info, msg, type ); + } else if ( IsHost() ) { + const int targetPeer = PeerIndexForSessionUserIndex( info.toSessionUserIndex ); + msg.RestoreReadState( c, b ); + // forward the rest of the data + const byte * data = msg.GetReadData() + msg.GetReadCount(); + int dataLen = msg.GetSize() - msg.GetReadCount(); + + QueueReliableMessage( targetPeer, type, data, dataLen ); + } else { + idLib::Warning( "NET: Can't forward reliable message for remote player: I'm not the host" ); + } +} + +/* +======================== +idLobby::HandleReliablePlayerToPlayerMsg +======================== +*/ +void idLobby::HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ) { +#if 0 + // Remember that the reliablePlayerToPlayerHeader_t was already removed from the msg + reliablePlayerToPlayer_t type = (reliablePlayerToPlayer_t)( reliableType - RELIABLE_PLAYER_TO_PLAYER_BEGIN ); + + switch( type ) { + case RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT: { + sessionCB->HandleReliableVoiceEvent( *this, info.fromSessionUserIndex, info.toSessionUserIndex, msg ); + break; + } + + default: { + idLib::Warning( "NET: Ignored unknown player to player reliable type %i", (int) type ); + } + }; +#endif +} + +/* +======================== +idLobby::SendConnectionLess +======================== +*/ +void idLobby::SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ) { + idBitMsg msg( data, dataLen ); + msg.SetSize( dataLen ); + + byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; + idBitMsg processedMsg( buffer, sizeof( buffer ) ); + + // Process the send + idPacketProcessor::ProcessConnectionlessOutgoing( msg, processedMsg, lobbyType, type ); + + const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE ); + + // Send it + sessionCB->SendRawPacket( remoteAddress, processedMsg.GetReadData(), processedMsg.GetSize(), useDirectPort ); +} + +/* +======================== +idLobby::SendConnectionRequest +======================== +*/ +void idLobby::SendConnectionRequest() { + // Some sanity checking + assert( state == STATE_CONNECT_HELLO_WAIT ); + assert( peers[host].GetConnectionState() == CONNECTION_CONNECTING ); + assert( GetNumLobbyUsers() == 0 ); + + // Buffer to hold connect msg + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + // Add the current version info to the handshake + const unsigned long localChecksum = NetGetVersionChecksum(); + + NET_VERBOSE_PRINT( "NET: version = %i\n", localChecksum ); + + msg.WriteLong( localChecksum ); + msg.WriteUShort( peers[host].sessionID ); + msg.WriteBool( connectIsFromInvite ); + + // We use InitSessionUsersFromLocalUsers here to copy the current local users over to session users simply to have a list + // to send on the initial connection attempt. We immediately clear our session user list once sent. + InitSessionUsersFromLocalUsers( true ); + + if ( GetNumLobbyUsers() > 0 ) { + // Fill up the msg with the users on this machine + msg.WriteByte( GetNumLobbyUsers() ); + + for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { + GetLobbyUser( u )->WriteToMsg( msg ); + } + } else { + FreeAllUsers(); + SetState( STATE_FAILED ); + + return; + } + + // We just used these users to fill up the msg above, we will get the real list from the server if we connect. + FreeAllUsers(); + + NET_VERBOSE_PRINT( "NET: Sending hello to: %s (lobbyType: %s, session ID %i, attempt: %i)\n", hostAddress.ToString(), GetLobbyName(), peers[host].sessionID, connectionAttempts ); + + SendConnectionLess( hostAddress, OOB_HELLO, msg.GetReadData(), msg.GetSize() ); + + connectionAttempts++; +} + +/* +======================== +idLobby::ConnectTo + +Fires off a request to get the address of a lobbyBackend owner, and then attempts to connect (eventually handled in HandleObtainingLobbyOwnerAddress) +======================== +*/ +void idLobby::ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ) { + NET_VERBOSE_PRINT( "NET: idSessionLocal::ConnectTo: fromInvite = %i\n", fromInvite ); + + // Make sure current session is shutdown + Shutdown(); + + connectIsFromInvite = fromInvite; + + lobbyBackend = sessionCB->JoinFromConnectInfo( connectInfo, (idLobbyBackend::lobbyBackendType_t)lobbyType ); + + // First, we need the address of the lobbyBackend owner + lobbyBackend->GetOwnerAddress( hostAddress ); + + SetState( STATE_OBTAINING_ADDRESS ); + +} + +/* +======================== +idLobby::HandleGoodbyeFromPeer +======================== +*/ +void idLobby::HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ) { + if ( migrationInfo.state != MIGRATE_NONE ) { + // If this peer is on our invite list, remove them + for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) { + if ( migrationInfo.invites[i].address.Compare( remoteAddress, true ) ) { + migrationInfo.invites.RemoveIndex( i ); + break; + } + } + } + + if ( peerNum < 0 ) { + NET_VERBOSE_PRINT( "NET: Goodbye from unknown peer %s on session %s\n", remoteAddress.ToString(), GetLobbyName() ); + return; + } + + if ( peers[peerNum].GetConnectionState() == CONNECTION_FREE ) { + NET_VERBOSE_PRINT( "NET: Goodbye from peer %s on session %s that is not connected\n", remoteAddress.ToString(), GetLobbyName() ); + return; + } + + if ( IsHost() ) { + // Goodbye from peer, remove him + NET_VERBOSE_PRINT( "NET: Goodbye from peer %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() ); + DisconnectPeerFromSession( peerNum ); + } else { + // Let session handler take care of this + NET_VERBOSE_PRINT( "NET: Goodbye from host %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() ); + sessionCB->GoodbyeFromHost( *this, peerNum, remoteAddress, msgType ); + } +} + +/* +======================== +idLobby::HandleGoodbyeFromPeer +======================== +*/ +void idLobby::HandleConnectionAttemptFailed() { + Shutdown(); + failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED; + SetState( STATE_FAILED ); + + if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) { + sessionCB->FailedGameMigration( *this ); + } + + ResetAllMigrationState(); + + needToDisplayMigrateMsg = false; + migrateMsgFlags = 0; +} + +/* +======================== +idLobby::ConnectToNextSearchResult +======================== +*/ +bool idLobby::ConnectToNextSearchResult() { + if ( lobbyType != TYPE_GAME ) { + return false; // Only game sessions use matchmaking searches + } + + // End current session lobby (this WON'T free search results) + Shutdown(); + + if ( searchResults.Num() == 0 ) { + return false; // No more search results to connect to, give up + } + + // Get next search result + lobbyConnectInfo_t connectInfo = searchResults[0]; + + // Remove this search result + searchResults.RemoveIndex( 0 ); + + // If we are connecting to a game lobby, tell our party to connect to this lobby as well + if ( lobbyType == TYPE_GAME && sessionCB->GetPartyLobby().IsLobbyActive() ) { + sessionCB->GetPartyLobby().SendMembersToLobby( lobbyType, connectInfo, true ); + } + + // Attempt to connect the lobby + ConnectTo( connectInfo, true ); // Pass in true for invite, since searches are for matchmaking, and we should always be able to connect to those types of matches + + // Clear the "Lobby was Full" dialog in case it's up, since we are going to try to connect to a different lobby now + common->Dialog().ClearDialog( GDM_LOBBY_FULL ); + + return true; // Notify caller we are attempting to connect +} + +/* +======================== +idLobby::CheckVersion +======================== +*/ +bool idLobby::CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ) { + const unsigned long remoteChecksum = msg.ReadLong(); + + if ( net_checkVersion.GetInteger() == 1 ) { + const unsigned long localChecksum = NetGetVersionChecksum(); + + NET_VERBOSE_PRINT( "NET: Comparing handshake version - localChecksum = %i, remoteChecksum = %i\n", localChecksum, remoteChecksum ); + return ( remoteChecksum == localChecksum ); + } + return true; +} + +/* +======================== +idLobby::VerifyNumConnectingUsers +Make sure number of users connecting is valid, and make sure we have enough room +======================== +*/ +bool idLobby::VerifyNumConnectingUsers( idBitMsg & msg ) { + int c, b; + msg.SaveReadState( c, b ); + const int numUsers = msg.ReadByte(); + msg.RestoreReadState( c, b ); + + const int numFreeSlots = NumFreeSlots(); + + NET_VERBOSE_PRINT( "NET: VerifyNumConnectingUsers %i users, %i free slots for %s\n", numUsers, numFreeSlots, GetLobbyName() ); + + if ( numUsers <= 0 || numUsers > MAX_PLAYERS - 1 ) { + NET_VERBOSE_PRINT( "NET: Invalid numUsers %i\n", numUsers ); + return false; + } else if ( numUsers > numFreeSlots ) { + NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available\n", numUsers, numFreeSlots ); + return false; + } else if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY && sessionCB->GetGameLobby().IsLobbyActive() && !IsMigrating() ) { + const int numFreeGameSlots = sessionCB->GetGameLobby().NumFreeSlots(); + + if ( numUsers > numFreeGameSlots ) { + NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available on the active game session\n", numUsers, numFreeGameSlots ); + return false; + } + } + + return true; +} + +/* +======================== +idLobby::VerifyLobbyUserIDs +======================== +*/ +bool idLobby::VerifyLobbyUserIDs( idBitMsg & msg ) { + int c, b; + msg.SaveReadState( c, b ); + const int numUsers = msg.ReadByte(); + + // Add the new users to our own list + for ( int u = 0; u < numUsers; u++ ) { + lobbyUser_t newUser; + + // Read in the new user + newUser.ReadFromMsg( msg ); + + if ( GetLobbyUserIndexByID( newUser.lobbyUserID, true ) != -1 ) { + msg.RestoreReadState( c, b ); + return false; + } + } + + msg.RestoreReadState( c, b ); + + return true; +} + +/* +======================== +idLobby::HandleInitialPeerConnection +Received on an initial peer connect request (OOB_HELLO) +======================== +*/ +int idLobby::HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ) { + if ( net_ignoreConnects.GetInteger() > 0 ) { + if ( net_ignoreConnects.GetInteger() == 2 ) { + SendGoodbye( peerAddress ); + } + return -1; + } + + if ( !IsHost() ) { + NET_VERBOSE_PRINT( "NET: Got connectionless hello from peer %s on session, and we are not a host\n", peerAddress.ToString() ); + SendGoodbye( peerAddress ); + return -1; + } + + // See if this is a peer migrating to us, if so, remove them from our invite list + bool migrationInvite = false; + int migrationGameData = -1; + + + for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) { + if ( migrationInfo.invites[i].address.Compare( peerAddress, true ) ) { + + migrationGameData = migrationInfo.invites[i].migrationGameData; + migrationInfo.invites.RemoveIndex( i ); // Remove this peer from the list, since this peer will now be connected (or rejected, either way we don't want to keep sending invites) + migrationInvite = true; + NET_VERBOSE_PRINT( "^2NET: Response from migration invite %s. GameData: %d\n", peerAddress.ToString(), migrationGameData ); + } + } + + if ( !MatchTypeIsJoinInProgress( parms.matchFlags ) && lobbyType == TYPE_GAME && migrationInfo.persistUntilGameEndsData.wasMigratedHost && IsMigratedStatsGame() && !migrationInvite ) { + // No matter what, don't let people join migrated game sessions that are going to continue on to the same game + // Not on invite list in a migrated game session - bounce him + NET_VERBOSE_PRINT( "NET: Denying game connection from %s since not on migration invite list\n", peerAddress.ToString() ); + for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) { + NET_VERBOSE_PRINT( " Invite[%d] addr: %s\n", i, migrationInfo.invites[i].address.ToString() ); + } + SendGoodbye( peerAddress ); + return -1; + } + + + if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) { + // If this is for a game connection, make sure we have a game lobby + if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() < idSession::GAME_LOBBY ) { + NET_VERBOSE_PRINT( "NET: Denying game connection from %s because we don't have a game lobby\n", peerAddress.ToString() ); + SendGoodbye( peerAddress ); + return -1; + } + } else { + // If this is for a game connection, make sure we are in the game lobby + if ( lobbyType == TYPE_GAME && sessionCB->GetState() != idSession::GAME_LOBBY ) { + NET_VERBOSE_PRINT( "NET: Denying game connection from %s while not in game lobby\n", peerAddress.ToString() ); + SendGoodbye( peerAddress ); + return -1; + } + + // If this is for a party connection, make sure we are not in game, unless this was for host migration invite + if ( !migrationInvite && lobbyType == TYPE_PARTY && ( sessionCB->GetState() == idSession::INGAME || sessionCB->GetState() == idSession::LOADING ) ) { + NET_VERBOSE_PRINT( "NET: Denying party connection from %s because we were already in a game\n", peerAddress.ToString() ); + SendGoodbye( peerAddress ); + return -1; + } + } + + if ( !CheckVersion( msg, peerAddress ) ) { + idLib::Printf( "NET: Denying user %s with wrong version number\n", peerAddress.ToString() ); + SendGoodbye( peerAddress ); + return -1; + } + + idPacketProcessor::sessionId_t sessionID = msg.ReadUShort(); + + // Check to see if this is a peer trying to connect with a different sessionID + // If the peer got abruptly disconnected, the peer could be trying to reconnect from a non clean disconnect + if ( peerNum >= 0 ) { + peer_t & existingPeer = peers[peerNum]; + + assert( existingPeer.GetConnectionState() != CONNECTION_FREE ); + + if ( existingPeer.sessionID == sessionID ) { + return peerNum; // If this is the same sessionID, then assume redundant connection attempt + } + + // + // This peer must be trying to reconnect from a previous abrupt disconnect + // + + NET_VERBOSE_PRINT( "NET: Reconnecting peer %s for session %s\n", peerAddress.ToString(), GetLobbyName() ); + + // Assume a peer is trying to reconnect from a non clean disconnect + // We want to set the connection back to FREE manually, so we don't send a goodbye + existingPeer.connectionState = CONNECTION_FREE; + + if ( existingPeer.packetProc != NULL ) { + delete existingPeer.packetProc; + existingPeer.packetProc = NULL; + } + + if ( existingPeer.snapProc != NULL ) { + assert( lobbyType == TYPE_GAME ); // Only games sessions should be creating snap processors + delete existingPeer.snapProc; + existingPeer.snapProc = NULL; + } + + RemoveUsersWithDisconnectedPeers(); + + peerNum = -1; + } + + // See if this was from an invite we sent out. If it wasn't, make sure we aren't invite only + const bool fromInvite = msg.ReadBool(); + + if ( !fromInvite && MatchTypeInviteOnly( parms.matchFlags ) ) { + idLib::Printf( "NET: Denying user %s because they were not invited to an invite only match\n", peerAddress.ToString() ); + SendGoodbye( peerAddress ); + return -1; + } + + // Make sure we have room for the users connecting + if ( !VerifyNumConnectingUsers( msg ) ) { + NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to being out of user slots\n", peerAddress.ToString(), GetLobbyName() ); + SendGoodbye( peerAddress, true ); + return -1; + } + + // Make sure there are no lobby id conflicts + if ( !verify( VerifyLobbyUserIDs( msg ) ) ) { + NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to lobby id conflict\n", peerAddress.ToString(), GetLobbyName() ); + SendGoodbye( peerAddress, true ); + return -1; + } + + // Calling AddPeer will set our connectionState to this peer as CONNECTION_CONNECTING (which will get set to CONNECTION_ESTABLISHED below) + peerNum = AddPeer( peerAddress, sessionID ); + + peer_t & newPeer = peers[peerNum]; + + assert( newPeer.GetConnectionState() == CONNECTION_CONNECTING ); + assert( lobbyType != GetActingGameStateLobbyType() || newPeer.snapProc != NULL ); + + // First, add users from this new peer to our user list + // (which will then forward the list to all peers except peerNum) + AddUsersFromMsg( msg, peerNum ); + + // Mark the peer as connected for this session type + SetPeerConnectionState( peerNum, CONNECTION_ESTABLISHED ); + + // Update their heart beat to current + newPeer.lastHeartBeat = Sys_Milliseconds(); + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg outmsg( buffer, sizeof( buffer ) ); + + // Let them know their peer index on this host + // peerIndexOnHost (put this here so it shows up in search results when finding out where it's used/referenced) + outmsg.WriteLong( peerNum ); + + // If they are connecting to our party lobby, let them know the party token + if ( lobbyType == TYPE_PARTY ) { + outmsg.WriteLong( GetPartyTokenAsHost() ); + } + + if ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) { + // If this is a game session, reset the loading and ingame flags + newPeer.loaded = false; + newPeer.inGame = false; + } + + // Write out current match parms + parms.Write( outmsg ); + + // Send list of existing users to this new peer + // (the users from the new peer will also be in this list, since we already called AddUsersFromMsg) + outmsg.WriteByte( GetNumLobbyUsers() ); + + for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { + GetLobbyUser( u )->WriteToMsg( outmsg ); + } + + lobbyBackend->FillMsgWithPostConnectInfo( outmsg ); + + NET_VERBOSE_PRINT( "NET: Sending response to %s, lobbyType %s, sessionID %i\n", peerAddress.ToString(), GetLobbyName(), sessionID ); + + QueueReliableMessage( peerNum, RELIABLE_HELLO, outmsg.GetReadData(), outmsg.GetSize() ); + + if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) { + // If have an active game lobby, and someone joins our party, tell them to join our game + if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY ) { + SendPeerMembersToLobby( peerNum, TYPE_GAME, false ); + } + + // We are are ingame, then start the client loading immediately + if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() >= idSession::LOADING ) { + idLib::Printf( "******* JOIN IN PROGRESS ********\n" ); + if ( sessionCB->GetState() == idSession::INGAME ) { + newPeer.pauseSnapshots = true; // Since this player joined in progress, let game dictate when to start sending snaps + } + QueueReliableMessage( peerNum, idLobby::RELIABLE_START_LOADING ); + } + } else { + // If we are in a game lobby, and someone joins our party, tell them to join our game + if ( lobbyType == TYPE_PARTY && sessionCB->GetState() == idSession::GAME_LOBBY ) { + SendPeerMembersToLobby( peerNum, TYPE_GAME, false ); + } + } + + // Send mic status of the current lobby to applicable peers + SendPeersMicStatusToNewUsers( peerNum ); + + // If we made is this far, update the users migration game data index + for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { + if ( GetLobbyUser( u )->peerIndex == peerNum ) { + GetLobbyUser( u )->migrationGameData = migrationGameData; + } + } + + return peerNum; +} + +/* +======================== +idLobby::InitStateLobbyHost +======================== +*/ +void idLobby::InitStateLobbyHost() { + assert( lobbyBackend != NULL ); + + // We will be the host + isHost = true; + + if ( net_headlessServer.GetBool() ) { + return; // Don't add any players to headless server + } + + if ( migrationInfo.state != MIGRATE_NONE ) { + migrationInfo.persistUntilGameEndsData.wasMigratedHost = true; // InitSessionUsersFromLocalUsers needs to know this + migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = false; + // migrationDlg = GDM_MIGRATING_WAITING; + } + + // Initialize the initial user list for this lobby + InitSessionUsersFromLocalUsers( MatchTypeIsOnline( parms.matchFlags ) ); + + // Set the session's hostAddress to the local players' address. + const int myUserIndex = GetLobbyUserIndexByLocalUserHandle( sessionCB->GetSignInManager().GetMasterLocalUserHandle() ); + if ( myUserIndex != -1 ) { + hostAddress = GetLobbyUser( myUserIndex )->address; + } + + // Since we are the host, we have to register our initial session users with the lobby + // All additional users will join through AddUsersFromMsg, and RegisterUser is handled in there from here on out. + // Peers will add users exclusively through AddUsersFromMsg. + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * user = GetLobbyUser( i ); + RegisterUser( user ); + if ( lobbyType == TYPE_PARTY ) { + user->partyToken = GetPartyTokenAsHost(); + } + } + + // Set the lobbies skill level + lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() ); + + // Make sure and register all the addresses of the invites we'll send out as the new host + if ( migrationInfo.state != MIGRATE_NONE ) { + // Tell the session that we became the host, so the session mgr can adjust state if needed + sessionCB->BecameHost( *this ); + + // Register this address with this lobbyBackend + for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) { + lobbyBackend->RegisterAddress( migrationInfo.invites[i].address ); + } + } +} + +/* +======================== +idLobby::SendMembersToLobby +======================== +*/ +void idLobby::SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) { + + // It's not our job to send party members to a game if we aren't the party host + if ( !IsHost() ) { + return; + } + + // Send the message to all connected peers + for ( int i = 0; i < peers.Num(); i++ ) { + if ( peers[ i ].IsConnected() ) { + SendPeerMembersToLobby( i, destLobbyType, connectInfo, waitForOtherMembers ); + } + } +} + +/* +======================== +idLobby::SendMembersToLobby +======================== +*/ +void idLobby::SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ) { + if ( destLobby.lobbyBackend == NULL ) { + return; // We don't have a game lobbyBackend to get an address for + } + + lobbyConnectInfo_t connectInfo = destLobby.lobbyBackend->GetConnectInfo(); + + SendMembersToLobby( destLobby.lobbyType, connectInfo, waitForOtherMembers ); +} + +/* +======================== +idLobby::SendPeerMembersToLobby +Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server. +======================== +*/ +void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) { + // It's not our job to send party members to a game if we aren't the party host + if ( !IsHost() ) { + return; + } + + assert( peerIndex >= 0 ); + assert( peerIndex < peers.Num() ); + peer_t & peer = peers[ peerIndex ]; + + NET_VERBOSE_PRINT( "NET: Sending peer %i (%s) to game lobby\n", peerIndex, peer.address.ToString() ); + + if ( !peer.IsConnected() ) { + idLib::Warning( "NET: Can't send peer %i to game lobby: peer isn't in party", peerIndex ); + return; + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg outmsg( buffer, sizeof( buffer ) ); + + // Have lobby fill out msg with connection info + connectInfo.WriteToMsg( outmsg ); + + outmsg.WriteByte( destLobbyType ); + outmsg.WriteBool( waitForOtherMembers ); + + QueueReliableMessage( peerIndex, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, outmsg.GetReadData(), outmsg.GetSize() ); +} + +/* +======================== +idLobby::SendPeerMembersToLobby + +Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server. +======================== +*/ +void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ) { + idLobby * lobby = sessionCB->GetLobbyFromType( destLobbyType ); + + if ( !verify( lobby != NULL ) ) { + return; + } + + if ( !verify( lobby->lobbyBackend != NULL ) ) { + return; + } + + lobbyConnectInfo_t connectInfo = lobby->lobbyBackend->GetConnectInfo(); + + SendPeerMembersToLobby( peerIndex, destLobbyType, connectInfo, waitForOtherMembers ); +} + +/* +======================== +idLobby::NotifyPartyOfLeavingGameLobby +======================== +*/ +void idLobby::NotifyPartyOfLeavingGameLobby() { + if ( lobbyType != TYPE_PARTY ) { + return; // We are not a party lobby + } + + if ( !IsHost() ) { + return; // We are not the host of a party lobby, we can't do this + } + + if ( !( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) ) { + return; // Options aren't set to notify party of leaving + } + + // Tell our party to leave the game they are in + for ( int i = 0; i < peers.Num(); i++ ) { + if ( peers[ i ].IsConnected() ) { + QueueReliableMessage( i, RELIABLE_PARTY_LEAVE_GAME_LOBBY ); + } + } +} + +/* +======================== +idLobby::GetPartyTokenAsHost +======================== +*/ +uint32 idLobby::GetPartyTokenAsHost() { + assert( lobbyType == TYPE_PARTY ); + assert( IsHost() ); + + if ( partyToken == 0 ) { + // I don't know if this is mathematically sound, but it seems reasonable. + // Don't do this at app startup (i.e. in the constructor) or it will be a lot less random. + unsigned long seed = Sys_Milliseconds(); // time app has been running + idLocalUser * masterUser = session->GetSignInManager().GetMasterLocalUser(); + if ( masterUser != NULL ) { + seed += idStr::Hash( masterUser->GetGamerTag() ); + } + partyToken = idRandom( seed ).RandomInt(); + idLib::Printf( "NET: PartyToken is %u (seed = %u)\n", partyToken, seed ); + } + return partyToken; +} + +/* +======================== +idLobby::EncodeSessionID +======================== +*/ +idPacketProcessor::sessionId_t idLobby::EncodeSessionID( uint32 key ) const { + assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) ); + const int numBits = sizeof( idPacketProcessor::sessionId_t ) * 8 - idPacketProcessor::NUM_LOBBY_TYPE_BITS; + const uint32 mask = ( 1 << numBits ) - 1; + idPacketProcessor::sessionId_t sessionID = ( key & mask ) << idPacketProcessor::NUM_LOBBY_TYPE_BITS; + sessionID |= ( lobbyType + 1 ); + return sessionID; +} + +/* +======================== +idLobby::EncodeSessionID +======================== +*/ +void idLobby::DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const { + assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) ); + key = sessionID >> idPacketProcessor::NUM_LOBBY_TYPE_BITS; +} + +/* +======================== +idLobby::GenerateSessionID +======================== +*/ +idPacketProcessor::sessionId_t idLobby::GenerateSessionID() const { + idPacketProcessor::sessionId_t sessionID = EncodeSessionID( Sys_Milliseconds() ); + + // Make sure we can use it + while ( !SessionIDCanBeUsedForInBand( sessionID ) ) { + sessionID = IncrementSessionID( sessionID ); + } + + return sessionID; +} + +/* +======================== +idLobby::SessionIDCanBeUsedForInBand +======================== +*/ +bool idLobby::SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const { + if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) { + return false; + } + + if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ) { + return false; + } + + if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ) { + return false; + } + + if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ) { + return false; + } + + return true; +} + +/* +======================== +idLobby::IncrementSessionID +======================== +*/ +idPacketProcessor::sessionId_t idLobby::IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const { + // Increment, taking into account valid id's + while ( 1 ) { + uint32 key = 0; + + DecodeSessionID( sessionID, key ); + + key++; + + sessionID = EncodeSessionID( key ); + + if ( SessionIDCanBeUsedForInBand( sessionID ) ) { + break; + } + } + + return sessionID; +} + +#define VERIFY_CONNECTED_PEER( p, sessionType_, msgType ) \ + if ( !verify( lobbyType == sessionType_ ) ) { \ + idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ + return; \ + } \ + if ( peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) { \ + idLib::Printf( "NET: " #msgType ", peer:%s not connected for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ + return; \ + } + +#define VERIFY_CONNECTING_PEER( p, sessionType_, msgType ) \ + if ( !verify( lobbyType == sessionType_ ) ) { \ + idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ + return; \ + } \ + if ( peers[p].GetConnectionState() != CONNECTION_CONNECTING ) { \ + idLib::Printf( "NET: " #msgType ", peer:%s not connecting for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ + return; \ + } + +#define VERIFY_FROM_HOST( p, sessionType_, msgType ) \ + VERIFY_CONNECTED_PEER( p, sessionType_, msgType ); \ + if ( p != host ) { \ + idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \ + return; \ + } \ + +#define VERIFY_FROM_CONNECTING_HOST( p, sessionType_, msgType ) \ + VERIFY_CONNECTING_PEER( p, sessionType_, msgType ); \ + if ( p != host ) { \ + idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \ + return; \ + } \ + +/* +======================== +idLobby::HandleHelloAck +======================== +*/ +void idLobby::HandleHelloAck( int p, idBitMsg & msg ) { + peer_t & peer = peers[p]; + + if ( state != STATE_CONNECT_HELLO_WAIT ) { + idLib::Printf( "NET: Hello ack for session type %s while not waiting for hello.\n", GetLobbyName() ); + SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore + return; + } + if ( p != host ) { + // This shouldn't be possible + idLib::Printf( "NET: Hello ack for session type %s, not from correct host.\n", GetLobbyName() ); + SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore + return; + } + + assert( GetNumLobbyUsers() == 0 ); + + NET_VERBOSE_PRINT( "NET: Hello ack for session type %s from %s\n", GetLobbyName(), peer.address.ToString() ); + + // We are now connected to this session type + SetPeerConnectionState( p, CONNECTION_ESTABLISHED ); + + // Obtain what our peer index is on the host is + peerIndexOnHost = msg.ReadLong(); + + // If we connected to a party lobby, get the party token from the lobby owner + if ( lobbyType == TYPE_PARTY ) { + partyToken = msg.ReadLong(); + } + + // Read match parms + parms.Read( msg ); + + // Update lobbyBackend with parms + if ( lobbyBackend != NULL ) { + lobbyBackend->UpdateMatchParms( parms ); + } + + // Populate the user list with the one from the host (which will also include our local users) + // This ensures the user lists are kept in sync + FreeAllUsers(); + AddUsersFromMsg( msg, p ); + + // Make sure the host has a current heartbeat + peer.lastHeartBeat = Sys_Milliseconds(); + + lobbyBackend->PostConnectFromMsg( msg ); + + // Tell the lobby controller to finalize the connection + SetState( STATE_FINALIZE_CONNECT ); + + // + // Success - We've received an ack from the server, letting us know we've been registered with the lobbies + // +} + +/* +======================== +idLobby::GetLobbyUserName +======================== +*/ +const char * idLobby::GetLobbyUserName( lobbyUserID_t lobbyUserID ) const { + const int index = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( index ); + + if ( user == NULL ) { + for ( int i = 0; i < disconnectedUsers.Num(); i++ ) { + if ( disconnectedUsers[i].lobbyUserID.CompareIgnoreLobbyType( lobbyUserID ) ) { + return disconnectedUsers[i].gamertag; + } + } + return INVALID_LOBBY_USER_NAME; + } + + return user->gamertag; +} + +/* +======================== +idLobby::GetLobbyUserSkinIndex +======================== +*/ +int idLobby::GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( userIndex ); + return user ? user->selectedSkin : 0; +} + +/* +======================== +idLobby::GetLobbyUserWeaponAutoSwitch +======================== +*/ +bool idLobby::GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( userIndex ); + return user ? user->weaponAutoSwitch : true; +} + +/* +======================== +idLobby::GetLobbyUserWeaponAutoReload +======================== +*/ +bool idLobby::GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( userIndex ); + return user ? user->weaponAutoReload: true; +} + +/* +======================== +idLobby::GetLobbyUserLevel +======================== +*/ +int idLobby::GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( userIndex ); + return user ? user->level : 0; +} + +/* +======================== +idLobby::GetLobbyUserQoS +======================== +*/ +int idLobby::GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + + if ( IsHost() && IsSessionUserIndexLocal( userIndex ) ) { + return 0; // Local users on the host of the active session have 0 ping + } + + const lobbyUser_t * user = GetLobbyUser( userIndex ); + + if ( !verify( user != NULL ) ) { + return 0; + } + + return user->pingMs; +} + +/* +======================== +idLobby::GetLobbyUserTeam +======================== +*/ +int idLobby::GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( userIndex ); + return user ? user->teamNumber : 0; +} + +/* +======================== +idLobby::SetLobbyUserTeam +======================== +*/ +bool idLobby::SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + lobbyUser_t * user = GetLobbyUser( userIndex ); + + if ( user != NULL ) { + if ( teamNumber != user->teamNumber ) { + user->teamNumber = teamNumber; + if ( IsHost() ) { + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + CreateUserUpdateMessage( userIndex, msg ); + idBitMsg readMsg; + readMsg.InitRead( buffer, msg.GetSize() ); + UpdateSessionUserOnPeers( readMsg ); + } + return true; + } + } + return false; +} + +/* +======================== +idLobby::GetLobbyUserPartyToken +======================== +*/ +int idLobby::GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + const lobbyUser_t * user = GetLobbyUser( userIndex ); + return user ? user->partyToken : 0; +} + +/* +======================== +idLobby::GetProfileFromLobbyUser +======================== +*/ +idPlayerProfile * idLobby::GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + + idPlayerProfile * profile = NULL; + + idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( userIndex ); + + if ( localUser != NULL ) { + profile = localUser->GetProfile(); + } + + if ( profile == NULL ) { + // Whoops + profile = session->GetSignInManager().GetDefaultProfile(); + //idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." ); + } + + return profile; +} + +/* +======================== +idLobby::GetLocalUserFromLobbyUser +======================== +*/ +idLocalUser * idLobby::GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) { + const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + + return GetLocalUserFromLobbyUserIndex( userIndex ); +} + +/* +======================== +idLobby::GetNumLobbyUsersOnTeam +======================== +*/ +int idLobby::GetNumLobbyUsersOnTeam( int teamNumber ) const { + int numTeam = 0; + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + if ( GetLobbyUser( i )->teamNumber == teamNumber ) { + ++numTeam; + } + } + return numTeam; +} + +/* +======================== +idLobby::GetPeerName +======================== +*/ +const char * idLobby::GetPeerName( int peerNum ) const { + + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + if ( !verify( GetLobbyUser( i ) != NULL ) ) { + continue; + } + + if ( GetLobbyUser( i )->peerIndex == peerNum ) { + return GetLobbyUserName( GetLobbyUser( i )->lobbyUserID ); + } + } + + return INVALID_LOBBY_USER_NAME; +} + +/* +======================== +idLobby::HandleReliableMsg +======================== +*/ +void idLobby::HandleReliableMsg( int p, idBitMsg & msg ) { + peer_t & peer = peers[p]; + + int reliableType = msg.ReadByte(); + + //idLib::Printf(" Received reliable msg: %i \n", reliableType ); + + const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType(); + + if ( reliableType == RELIABLE_HELLO ) { + VERIFY_FROM_CONNECTING_HOST( p, lobbyType, RELIABLE_HELLO ); + // This is sent from the host acking a request to join the game lobby + HandleHelloAck( p, msg ); + return; + } else if ( reliableType == RELIABLE_USER_CONNECT_REQUEST ) { + VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_USER_CONNECT_REQUEST ); + + // This message is sent from a peer requesting for a new user to join the game lobby + // This will be sent while we are in a game lobby as a host. otherwise, denied. + NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECT_REQUEST (%s) from %s\n", GetLobbyName(), peer.address.ToString() ); + + idSession::sessionState_t expectedState = ( lobbyType == TYPE_PARTY ) ? idSession::PARTY_LOBBY : idSession::GAME_LOBBY; + + if ( sessionCB->GetState() == expectedState && IsHost() && NumFreeSlots() > 0 ) { // This assumes only one user in the msg + // Add user to session, which will also forward the operation to all other peers + AddUsersFromMsg( msg, p ); + } else { + // Let peer know user couldn't be added + HandleUserConnectFailure( p, msg, RELIABLE_USER_CONNECT_DENIED ); + } + } else if ( reliableType == RELIABLE_USER_CONNECT_DENIED ) { + // This message is sent back from the host when a RELIABLE_PARTY_USER_CONNECT_REQUEST failed + VERIFY_FROM_HOST( p, lobbyType, RELIABLE_PARTY_USER_CONNECT_DENIED ); + + // Remove this user from the sign-in manager, so we don't keep trying to add them + if ( !sessionCB->GetSignInManager().RemoveLocalUserByHandle( localUserHandle_t( msg.ReadLong() ) ) ) { + NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_USER_CONNECT_DENIED, local user not found\n" ); + return; + } + } else if ( reliableType == RELIABLE_KICK_PLAYER ) { + VERIFY_FROM_HOST( p, lobbyType, RELIABLE_KICK_PLAYER ); + common->Dialog().AddDialog( GDM_KICKED, DIALOG_ACCEPT, NULL, NULL, false ); + if ( sessionCB->GetPartyLobby().IsHost() ) { + session->SetSessionOption( idSession::OPTION_LEAVE_WITH_PARTY ); + } + session->Cancel(); + } else if ( reliableType == RELIABLE_HEADSET_STATE ) { + HandleHeadsetStateChange( p, msg ); + } else if ( reliableType == RELIABLE_USER_CONNECTED ) { + // This message is sent back from the host when users have connected, and we need to update our lists to reflect that + VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_CONNECTED ); + + NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECTED (%s) from %s\n", GetLobbyName(), peer.address.ToString() ); + AddUsersFromMsg( msg, p ); + } else if ( reliableType == RELIABLE_USER_DISCONNECTED ) { + // This message is sent back from the host when users have diconnected, and we need to update our lists to reflect that + VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_DISCONNECTED ); + + ProcessUserDisconnectMsg( msg ); + } else if ( reliableType == RELIABLE_MATCH_PARMS ) { + parms.Read( msg ); + // Update lobby with parms + if ( lobbyBackend != NULL ) { + lobbyBackend->UpdateMatchParms( parms ); + } + } else if ( reliableType == RELIABLE_START_LOADING ) { + // This message is sent from the host to start loading a map + VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_START_LOADING ); + + NET_VERBOSE_PRINT( "NET: RELIABLE_START_LOADING from %s\n", peer.address.ToString() ); + + startLoadingFromHost = true; + } else if ( reliableType == RELIABLE_LOADING_DONE ) { + // This message is sent from the peers to state they are done loading the map + VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_LOADING_DONE ); + + unsigned long networkChecksum = 0; + networkChecksum = msg.ReadLong(); + + peer.networkChecksum = networkChecksum; + peer.loaded = true; + } else if ( reliableType == RELIABLE_IN_GAME ) { + VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_IN_GAME ); + + peer.inGame = true; + } else if ( reliableType == RELIABLE_SNAPSHOT_ACK ) { + VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_SNAPSHOT_ACK ); + + // update our base state for his last received snapshot + int snapNum = msg.ReadLong(); + float receivedBps = msg.ReadQuantizedUFloat< BANDWIDTH_REPORTING_MAX, BANDWIDTH_REPORTING_BITS >(); + + // Update reported received bps + if ( peer.receivedBpsIndex != snapNum ) { + // Only do this the first time we get reported bps per snapshot. Subsequent ACKs of the same shot will usually have lower reported bps + // due to more time elapsing but not receiving a new ss + peer.receivedBps = receivedBps; + peer.receivedBpsIndex = snapNum; + } + + ApplySnapshotDelta( p, snapNum ); + + //idLib::Printf( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ); + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ) ); + + } else if ( reliableType == RELIABLE_RESOURCE_ACK ) { + } else if ( reliableType == RELIABLE_UPDATE_MATCH_PARMS ) { + VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_UPDATE_MATCH_PARMS ); + int msgType = msg.ReadLong(); + sessionCB->HandlePeerMatchParamUpdate( p, msgType ); + + } else if ( reliableType == RELIABLE_MATCHFINISHED ) { + VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_MATCHFINISHED ); + + sessionCB->ClearMigrationState(); + + } else if ( reliableType == RELIABLE_ENDMATCH ) { + VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH ); + + sessionCB->EndMatchInternal(); + + } else if ( reliableType == RELIABLE_ENDMATCH_PREMATURE ) { + VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH_PREMATURE ); + + sessionCB->EndMatchInternal( true ); + + } else if ( reliableType == RELIABLE_START_MATCH_GAME_LOBBY_HOST ) { + // This message should be from the host of the game lobby, telling us (as the host of the GameStateLobby) to start loading + VERIFY_CONNECTED_PEER( p, TYPE_GAME_STATE, RELIABLE_START_MATCH_GAME_LOBBY_HOST ); + + if ( session->GetState() >= idSession::LOADING ) { + NET_VERBOSE_PRINT( "NET: RELIABLE_START_MATCH_GAME_LOBBY_HOST already loading\n" ); + return; + } + + // Read match parms, and start loading + parms.Read( msg ); + + // Send these new match parms to currently connected peers + SendMatchParmsToPeers(); + + startLoadingFromHost = true; // Hijack this flag + } else if ( reliableType == RELIABLE_ARBITRATE ) { + VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE ); + // Host telling us to arbitrate + // Set a flag to do this later, since the lobby may not be in a state where it can fulfil the request at the moment + respondToArbitrate = true; + } else if ( reliableType == RELIABLE_ARBITRATE_OK ) { + VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE_OK ); + + NET_VERBOSE_PRINT( "NET: Got an arbitration ok from %d\n", p ); + + everyoneArbitrated = true; + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * user = GetLobbyUser( i ); + if ( !verify( user != NULL ) ) { + continue; + } + if ( user->peerIndex == p ) { + user->arbitrationAcked = true; + } else if ( !user->arbitrationAcked ) { + everyoneArbitrated = false; + } + } + + if ( everyoneArbitrated ) { + NET_VERBOSE_PRINT( "NET: Everyone says they registered for arbitration, verifying\n" ); + lobbyBackend->Arbitrate(); + //sessionCB->EveryoneArbitrated(); + return; + } + } else if ( reliableType == RELIABLE_POST_STATS ) { + VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_POST_STATS ); + sessionCB->RecvLeaderboardStats( msg ); + } else if ( reliableType == RELIABLE_SESSION_USER_MODIFIED ) { + VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_SESSION_USER_MODIFIED ); + UpdateSessionUserOnPeers( msg ); + + } else if ( reliableType == RELIABLE_UPDATE_SESSION_USER ) { + VERIFY_FROM_HOST( p, lobbyType, RELIABLE_UPDATE_SESSION_USER ); + HandleUpdateSessionUser( msg ); + } else if ( reliableType == RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ) { + VERIFY_FROM_HOST( p, lobbyType, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ); + + NET_VERBOSE_PRINT( "NET: RELIABLE_CONNECT_AND_MOVE_TO_LOBBY\n" ); + + if ( IsHost() ) { + idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: We are the host.\n" ); + return; + } + + // Get connection info + lobbyConnectInfo_t connectInfo; + connectInfo.ReadFromMsg( msg ); + + const lobbyType_t destLobbyType = (lobbyType_t)msg.ReadByte(); + const bool waitForMembers = msg.ReadBool(); + + assert( destLobbyType > lobbyType ); // Make sure this is a proper transition (i.e. TYPE_PARTY moves to TYPE_GAME, TYPE_GAME moves to TYPE_GAME_STATE) + + sessionCB->ConnectAndMoveToLobby( destLobbyType, connectInfo, waitForMembers ); + } else if ( reliableType == RELIABLE_PARTY_CONNECT_OK ) { + VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_CONNECT_OK ); + if ( !sessionCB->GetGameLobby().waitForPartyOk ) { + idLib::Printf( "RELIABLE_PARTY_CONNECT_OK: Wasn't waiting for ok.\n" ); + } + sessionCB->GetGameLobby().waitForPartyOk = false; + } else if ( reliableType == RELIABLE_PARTY_LEAVE_GAME_LOBBY ) { + VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_LEAVE_GAME_LOBBY ); + + NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_LEAVE_GAME_LOBBY\n" ); + + if ( sessionCB->GetState() != idSession::GAME_LOBBY ) { + idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Not in a game lobby, ignoring.\n" ); + return; + } + + if ( IsHost() ) { + idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Host of party, ignoring.\n" ); + return; + } + + sessionCB->LeaveGameLobby(); + } else if ( IsReliablePlayerToPlayerType( reliableType ) ) { + HandleReliablePlayerToPlayerMsg( p, msg, reliableType ); + } else if ( reliableType == RELIABLE_PING ) { + HandleReliablePing( p, msg ); + } else if ( reliableType == RELIABLE_PING_VALUES ) { + HandlePingValues( msg ); + } else if ( reliableType == RELIABLE_BANDWIDTH_VALUES ) { + HandleBandwidhTestValue( p, msg ); + } else if ( reliableType == RELIABLE_MIGRATION_GAME_DATA ) { + HandleMigrationGameData( msg ); + } else if ( reliableType >= RELIABLE_GAME_DATA ) { + + VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_GAME_DATA ); + + common->NetReceiveReliable( p, reliableType - RELIABLE_GAME_DATA, msg ); + } else if ( reliableType == RELIABLE_DUMMY_MSG ) { + // Ignore dummy msg's + NET_VERBOSE_PRINT( "NET: ignoring dummy msg from %s\n", peer.address.ToString() ); + } else { + NET_VERBOSE_PRINT( "NET: Unknown reliable packet type %d from %s\n", reliableType, peer.address.ToString() ); + } +} + +/* +======================== +idLobby::GetTotalOutgoingRate +======================== +*/ +int idLobby::GetTotalOutgoingRate() { + int totalSendRate = 0; + for ( int p = 0; p < peers.Num(); p++ ) { + const peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + continue; + } + + const idPacketProcessor & proc = *peer.packetProc; + + totalSendRate += proc.GetOutgoingRateBytes(); + } + return totalSendRate; +} + +/* +======================== +idLobby::DrawDebugNetworkHUD +======================== +*/ +extern idCVar net_forceUpstream; +void idLobby::DrawDebugNetworkHUD() const { + int totalSendRate = 0; + int totalRecvRate = 0; + float totalSentMB = 0.0f; + float totalRecvMB = 0.0f; + + const float Y_OFFSET = 20.0f; + const float X_OFFSET = 20.0f; + const float Y_SPACING = 15.0f; + + float curY = Y_OFFSET; + + int numLines = ( net_forceUpstream.GetFloat() != 0.0f ? 6: 5 ); + + renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 1550, ( peers.Num() + numLines ) * Y_SPACING + 20.0f ); + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "# Peer | Sent kB/s | Recv kB/s | Sent MB | Recv MB | Ping | L | % | R.NM | R.SZ | R.AK | T", colorGreen, false ); + curY += Y_SPACING; + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false ); + curY += Y_SPACING; + + for ( int p = 0; p < peers.Num(); p++ ) { + const peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + continue; + } + + const idPacketProcessor & proc = *peer.packetProc; + + totalSendRate += proc.GetOutgoingRateBytes(); + totalRecvRate += proc.GetIncomingRateBytes(); + float sentKps = (float)proc.GetOutgoingRateBytes() / 1024.0f; + float recvKps = (float)proc.GetIncomingRateBytes() / 1024.0f; + float sentMB = (float)proc.GetOutgoingBytes() / ( 1024.0f * 1024.0f ); + float recvMB = (float)proc.GetIncomingBytes() / ( 1024.0f * 1024.0f ); + + totalSentMB += sentMB; + totalRecvMB += recvMB; + + idVec4 color = sentKps > 20.0f ? colorRed : colorGreen; + + int resourcePercent = 0; + idStr name = peer.address.ToString(); + + name += lobbyType == TYPE_PARTY ? "(P": "(G"; + name += host == p ? ":H)" : ":C)"; + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i %22s | %2.02f kB/s | %2.02f kB/s | %2.02f MB | %2.02f MB |%4i ms | %i | %i%% | %i | %i | %i | %2.2f / %2.2f / %i", p, name.c_str(), sentKps, recvKps, sentMB, recvMB, peer.lastPingRtt, peer.loaded, resourcePercent, peer.packetProc->NumQueuedReliables(), peer.packetProc->GetReliableDataSize(), peer.packetProc->NeedToSendReliableAck(), peer.snapHz, peer.maxSnapBps, peer.failedPingRecoveries ), color, false ); + curY += Y_SPACING; + } + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false ); + curY += Y_SPACING; + + float totalSentKps = (float)totalSendRate / 1024.0f; + float totalRecvKps = (float)totalRecvRate / 1024.0f; + + idVec4 color = totalSentKps > 100.0f ? colorRed : colorGreen; + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "# %20s | %2.02f KB/s | %2.02f KB/s | %2.02f MB | %2.02f MB", "", totalSentKps, totalRecvKps, totalSentMB, totalRecvMB ), color, false ); + curY += Y_SPACING; + + if ( net_forceUpstream.GetFloat() != 0.0f ) { + float upstreamDropRate = session->GetUpstreamDropRate(); + float upstreamQueuedRate = session->GetUpstreamQueueRate(); + + int queuedBytes = session->GetQueuedBytes(); + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s -> Effective %2.02f kB/s", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f, totalSentKps - ( upstreamDropRate / 1024.0f ) + ( upstreamQueuedRate / 1024.0f ) ), color, false ); + } +} + +/* +======================== +idLobby::DrawDebugNetworkHUD2 +======================== +*/ +void idLobby::DrawDebugNetworkHUD2() const { + int totalSendRate = 0; + int totalRecvRate = 0; + + const float Y_OFFSET = 20.0f; + const float X_OFFSET = 20.0f; + const float Y_SPACING = 15.0f; + + float curY = Y_OFFSET; + + renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, (peers.Num() + 4) * Y_SPACING + 20.0f ); + + const char* stateName = session->GetStateString(); + + renderSystem->DrawFilled( idVec4( 1.0f, 1.0f, 1.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, (peers.Num() + 5) * Y_SPACING + 20.0f ); + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), va("State: %s. Local time: %d", stateName, Sys_Milliseconds() ), colorGreen, false ); + curY += Y_SPACING; + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "Peer | Sent kB/s | Recv kB/s | L | R | Resources", colorGreen, false ); + curY += Y_SPACING; + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false ); + curY += Y_SPACING; + + for ( int p = 0; p < peers.Num(); p++ ) { + + if ( !peers[ p ].IsConnected() ) { + continue; + } + + idPacketProcessor & proc = *peers[ p ].packetProc; + + totalSendRate += proc.GetOutgoingRate2(); + totalRecvRate += proc.GetIncomingRate2(); + float sentKps = ( float )proc.GetOutgoingRate2() / 1024.0f; + float recvKps = ( float )proc.GetIncomingRate2() / 1024.0f; + + // should probably complement that with a bandwidth reading + // right now I am mostly concerned about fragmentation and the latency spikes it will cause + idVec4 color = proc.TickFragmentAccumulator() ? colorRed : colorGreen; + + int rLoaded = peers[ p ].numResources; + int rTotal = 0; + + // show the names of the clients connected to the server. Also make sure it looks reasonably good. + idStr peerName; + if ( IsHost() ) { + peerName = GetPeerName( p ); + + int MAX_PEERNAME_LENGTH = 10; + int nameLength = peerName.Length(); + if ( nameLength > MAX_PEERNAME_LENGTH ) { + peerName = peerName.Left( MAX_PEERNAME_LENGTH ); + } else if ( nameLength < MAX_PEERNAME_LENGTH ) { + idStr filler; + filler.Fill( ' ', MAX_PEERNAME_LENGTH ); + peerName += filler.Left( MAX_PEERNAME_LENGTH - nameLength ); + } + } else { + peerName = "Local "; + } + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i - %s | %2.02f kB/s | %2.02f kB/s | %i | %i | %d/%d", p, peerName.c_str(), sentKps, recvKps, peers[p].loaded, peers[p].address.UsingRelay(), rLoaded, rTotal ), color, false ); + curY += Y_SPACING; + } + + renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false ); + curY += Y_SPACING; + + float totalSentKps = (float)totalSendRate / 1024.0f; + float totalRecvKps = (float)totalRecvRate / 1024.0f; + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Total | %2.02f KB/s | %2.02f KB/s", totalSentKps, totalRecvKps ), colorGreen, false ); +} + + +/* +======================== +idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics +======================== +*/ +idCVar net_debughud3_bps_max( "net_debughud3_bps_max", "5120.0f", CVAR_FLOAT, "Highest factor of server base snapRate that a client can be throttled" ); +void idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) { + const float Y_OFFSET = 20.0f; + const float X_OFFSET = 20.0f; + const float Y_SPACING = 15.0f; + idVec4 color = colorWhite; + + float curY = Y_OFFSET; + + if ( !draw ) { + for ( int p=0; p < peers.Num(); p++ ) { + for ( int i=0; i < peers[p].debugGraphs.Num(); i++ ) { + if ( peers[p].debugGraphs[i] != NULL ) { + peers[p].debugGraphs[i]->Enable( false ); + } else { + return; + } + } + } + return; + } + + static int lastTime = 0; + int time = Sys_Milliseconds(); + + for ( int p = 0; p < peers.Num(); p++ ) { + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + continue; + } + + idPacketProcessor * packetProc = peer.packetProc; + idSnapshotProcessor * snapProc = peer.snapProc; + + if ( !verify( packetProc != NULL && snapProc != NULL ) ) { + continue; + } + + int snapSeq = snapProc->GetSnapSequence(); + int snapBase = snapProc->GetBaseSequence(); + int deltaSeq = snapSeq - snapBase; + bool throttled = peer.throttledSnapRate > common->GetSnapRate(); + + int numLines = net_forceUpstream.GetBool() ? 5 : 4; + + const int width = renderSystem->GetWidth()/2.0f - (X_OFFSET * 2); + + enum netDebugGraphs_t { + GRAPH_SNAPSENT, + GRAPH_OUTGOING, + GRAPH_INCOMINGREPORTED, + GRAPH_MAX + }; + + peer.debugGraphs.SetNum( GRAPH_MAX, NULL ); + for ( int i=0; i < GRAPH_MAX; i++ ) { + // Initialize graphs + if ( peer.debugGraphs[i] == NULL ) { + peer.debugGraphs[i] = console->CreateGraph( 500 ); + if ( !verify( peer.debugGraphs[i] != NULL ) ) { + continue; + } + + peer.debugGraphs[i]->SetPosition( X_OFFSET - 10.0f + width, curY - 10.0f, width , Y_SPACING * numLines ); + } + + peer.debugGraphs[i]->Enable( true ); + } + + renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, width, ( Y_SPACING * numLines ) + 20.0f ); + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Peer %d - %s RTT %d %sPeerSnapRate: %d %s", p, GetPeerName( p ), peer.lastPingRtt, throttled ? "^1" : "^2", peer.throttledSnapRate/1000, throttled ? "^1Throttled" : "" ), color, false ); + curY += Y_SPACING; + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "SnapSeq %d BaseSeq %d Delta %d Queue %d", snapSeq, snapBase, deltaSeq, snapProc->GetSnapQueueSize() ), color, false ); + curY += Y_SPACING; + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Reliables: %d / %d bytes Reliable Ack: %d", packetProc->NumQueuedReliables(), packetProc->GetReliableDataSize(), packetProc->NeedToSendReliableAck() ), color, false ); + curY += Y_SPACING; + + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Outgoing %.2f kB/s Reported %.2f kB/s Throttle: %.2f", peer.packetProc->GetOutgoingRateBytes() / 1024.0f, peers[p].receivedBps / 1024.0f, peer.receivedThrottle ), color, false ); + curY += Y_SPACING; + + if ( net_forceUpstream.GetFloat() != 0.0f ) { + float upstreamDropRate = session->GetUpstreamDropRate(); + float upstreamQueuedRate = session->GetUpstreamQueueRate(); + int queuedBytes = session->GetQueuedBytes(); + renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s ", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f ), color, false ); + + } + + curY += Y_SPACING; + + + + if ( peer.debugGraphs[GRAPH_SNAPSENT] != NULL ) { + if ( peer.lastSnapTime > lastTime ) { + peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 1.0f, colorBlue ); + } else { + peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 0.0f, colorBlue ); + } + } + + if ( peer.debugGraphs[GRAPH_OUTGOING] != NULL ) { + idVec4 bgColor( vec4_zero ); + peer.debugGraphs[GRAPH_OUTGOING]->SetBackgroundColor( bgColor ); + + idVec4 lineColor = colorLtGrey; + lineColor.w = 0.5f; + float outgoingRate = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ]; + // peer.packetProc->GetOutgoingRateBytes() + peer.debugGraphs[GRAPH_OUTGOING]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, outgoingRate / net_debughud3_bps_max.GetFloat() ), lineColor ); + } + + + if ( peer.debugGraphs[GRAPH_INCOMINGREPORTED] != NULL ) { + idVec4 lineColor = colorYellow; + extern idCVar net_peer_throttle_bps_peer_threshold_pct; + extern idCVar net_peer_throttle_bps_host_threshold; + + if ( peer.packetProc->GetOutgoingRateBytes() > net_peer_throttle_bps_host_threshold.GetFloat() ) { + float pct = peer.packetProc->GetOutgoingRateBytes() > 0.0f ? peer.receivedBps / peer.packetProc->GetOutgoingRateBytes() : 0.0f; + if ( pct < net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) { + lineColor = colorRed; + } else { + lineColor = colorGreen; + } + } + idVec4 bgColor( vec4_zero ); + peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetBackgroundColor( bgColor ); + peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetFillMode( idDebugGraph::GRAPH_LINE ); + peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, peer.receivedBps / net_debughud3_bps_max.GetFloat() ), lineColor ); + } + + + + // Skip down + curY += ( Y_SPACING * 2.0f); + } + + lastTime = time; +} + +/* +======================== +idLobby::CheckHeartBeats +======================== +*/ +void idLobby::CheckHeartBeats() { + // Disconnect peers that haven't responded within net_peerTimeoutInSeconds + int time = Sys_Milliseconds(); + + int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000; + + if ( sessionCB->GetState() < idSession::LOADING && migrationInfo.state == MIGRATE_NONE ) { + // Use shorter timeout in lobby (TCR) + timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds_Lobby", net_peerTimeoutInSeconds_Lobby.GetInteger() ) * 1000; + } + + if ( timeoutInMs > 0 ) { + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].IsConnected() ) { + + bool peerTimeout = false; + + if( time - peers[p].lastHeartBeat > timeoutInMs ) { + peerTimeout = true; + } + + // if reliable queue is almost full, disconnect the peer. + // (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never + // have more than 3 or 4 queued) + if ( peers[ p ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE - 1 ) { + peerTimeout = true; + } + + if( peerTimeout ) { + // Disconnect the peer from any sessions we are a host of + if ( IsHost() ) { + idLib::Printf("Peer %i timed out for %s session @ %d (lastHeartBeat %d)\n", p, GetLobbyName(), time, peers[p].lastHeartBeat ); + DisconnectPeerFromSession( p ); + } + + // Handle peers not receiving a heartbeat from the host in awhile + if ( IsPeer() ) { + if ( migrationInfo.state != MIGRATE_PICKING_HOST ) { + idLib::Printf("Host timed out for %s session\n", GetLobbyName() ); + + // Pick a host for this session + PickNewHost(); + } + } + } + } + } + } + + if ( IsHost() && lobbyType == GetActingGameStateLobbyType() ) { + for ( int p = 0; p < peers.Num(); p++ ) { + if ( !peers[p].IsConnected() ) { + continue; + } + + CheckPeerThrottle( p ); + } + } +} + +/* +======================== +idLobby::CheckHeartBeats +======================== +*/ +bool idLobby::IsLosingConnectionToHost() const { + if ( !verify( IsPeer() && host >= 0 && host < peers.Num() ) ) { + return false; + } + + if ( !peers[ host ].IsConnected() ) { + return true; + } + + int time = Sys_Milliseconds(); + + int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000; + + // return true if heartbeat > half the timeout length + if ( timeoutInMs > 0 && time - peers[ host ].lastHeartBeat > timeoutInMs / 2 ) { + return true; + } + + // return true if reliable queue is more than half full + // (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never + // have more than 3 or 4 queued) + if ( peers[ host ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) { + return true; + } + + return false; +} + +/* +======================== +idLobby::IsMigratedStatsGame +======================== +*/ +bool idLobby::IsMigratedStatsGame() const { + if ( !IsLobbyActive() ) { + return false; + } + + if ( lobbyType != TYPE_GAME ) { + return false; // Only game session migrates games stats + } + + if ( !MatchTypeHasStats( parms.matchFlags ) ) { + return false; // Only stats games migrate stats + } + + if ( !MatchTypeIsRanked( parms.matchFlags ) ) { + return false; // Only ranked games should migrate stats into new game + } + + return migrationInfo.persistUntilGameEndsData.wasMigratedGame && migrationInfo.persistUntilGameEndsData.hasGameData; +} + +/* +======================== +idLobby::ShouldRelaunchMigrationGame +returns true if we are hosting a migrated game and we had valid migration data +======================== +*/ +bool idLobby::ShouldRelaunchMigrationGame() const { + if ( IsMigrating() ) { + return false; // Don't relaunch until all clients have reconnected + } + + if ( !IsMigratedStatsGame() ) { + return false; // If we are not migrating stats, we don't want to relaunch a new game + } + + if ( !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) { + return false; // Only relaunch if we are the host + } + + if ( migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame ) { + return false; // We already relaunched this game + } + + return true; +} + +/* +======================== +idLobby::ShouldShowMigratingDialog +======================== +*/ +bool idLobby::ShouldShowMigratingDialog() const { + if ( IsMigrating() ) { + return true; // If we are in the process of truly migrating, then definitely return true + } + + if ( sessionCB->GetState() == idSession::INGAME ) { + return false; + } + + // We're either waiting on the server (which could be us) to relaunch, so show the dialog + return IsMigratedStatsGame() && sessionCB->GetState() != idSession::INGAME; +} + +/* +======================== +idLobby::IsMigrating +======================== +*/ +bool idLobby::IsMigrating() const { + return migrationInfo.state != idLobby::MIGRATE_NONE; +} + +/* +======================== +idLobby::PingPeers +Host only. +======================== +*/ +void idLobby::PingPeers() { + if ( !verify( IsHost() )) { + return; + } + + const int now = Sys_Milliseconds(); + + pktPing_t packet; + memset( &packet, 0, sizeof( packet ) ); // We're gonna memset like it's 1999. + packet.timestamp = now; + + byte packetCopy[ sizeof( packet ) ]; + idBitMsg msg( packetCopy, sizeof( packetCopy ) ); + msg.WriteLong( packet.timestamp ); + + for ( int i = 0; i < peers.Num(); ++i ) { + peer_t & peer = peers[ i ]; + if ( !peer.IsConnected() ) { + continue; + } + if ( peer.nextPing <= now ) { + peer.nextPing = now + PING_INTERVAL_MS; + QueueReliableMessage( i, RELIABLE_PING, msg.GetReadData(), msg.GetSize() ); + } + } +} + +/* +======================== +idLobby::ThrottlePeerSnapRate +======================== +*/ +void idLobby::ThrottlePeerSnapRate( int p ) { + if ( !verify( IsHost() ) || !verify( p >= 0 ) ) { + return; + } + + peers[p].throttledSnapRate = common->GetSnapRate() * 2; + idLib::Printf( "^1Throttling peer %d %s!\n", p, GetPeerName(p) ); + idLib::Printf( " New snaprate: %d\n", peers[p].throttledSnapRate / 1000 ); +} + +/* +======================== +idLobby::SaturatePeers +======================== +*/ +void idLobby::BeginBandwidthTest() { + if ( !verify( IsHost() ) ) { + idLib::Warning("Bandwidth test should only be done on host"); + return; + } + + if ( bandwidthChallengeStartTime > 0 ) { + idLib::Warning("Already started bandwidth test"); + return; + } + + int time = Sys_Milliseconds(); + bandwidthChallengeStartTime = time; + bandwidthChallengeEndTime = 0; + bandwidthChallengeFinished = false; + bandwidthChallengeNumGoodSeq = 0; + + for ( int p = 0; p < peers.Num(); ++p ) { + if ( !peers[ p ].IsConnected() ) { + continue; + } + + if ( !verify( peers[ p ].packetProc != NULL ) ) { + continue; + } + + peers[ p ].bandwidthSequenceNum = 0; + peers[ p ].bandwidthChallengeStartSendTime = 0; + peers[ p ].bandwidthChallengeResults = false; + peers[ p ].bandwidthChallengeSendComplete = false; + peers[ p ].bandwidthTestBytes = peers[ p ].packetProc->GetOutgoingBytes(); // cache this off so we can see the difference when we are done + } +} + +/* +======================== +idLobby::SaturatePeers +======================== +*/ +bool idLobby::BandwidthTestStarted() { + return bandwidthChallengeStartTime != 0; +} +/* +======================== +idLobby::ServerUpdateBandwidthTest +======================== +*/ +void idLobby::ServerUpdateBandwidthTest() { + if ( bandwidthChallengeStartTime <= 0 ) { + // Not doing a test + return; + } + + if ( !verify( IsHost() )) { + return; + } + + int time = Sys_Milliseconds(); + + if ( bandwidthChallengeFinished ) { + // test is over + return; + } + + idRandom random; + random.SetSeed( time ); + + bool sentAll = true; + bool recAll = true; + + for ( int i = 0; i < peers.Num(); ++i ) { + peer_t & peer = peers[ i ]; + if ( !peer.IsConnected() ) { + continue; + } + + if ( peer.bandwidthChallengeResults ) { + continue; + } + recAll = false; + + if ( peer.bandwidthChallengeSendComplete ) { + continue; + } + sentAll = false; + + if ( time - peer.bandwidthTestLastSendTime < session->GetTitleStorageInt( "net_bw_test_interval", net_bw_test_interval.GetInteger() ) ) { + continue; + } + + if ( peer.packetProc->HasMoreFragments() ) { + continue; + } + + if ( peer.bandwidthChallengeStartSendTime == 0 ) { + peer.bandwidthChallengeStartSendTime = time; + } + + peer.bandwidthTestLastSendTime = time; + + // Ok, send him a big packet + byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; // <---- NOTE - When calling ProcessOutgoingMsg with true for oob, we can't go over this size + idBitMsg msg( buffer, sizeof(buffer) ); + + msg.WriteLong( peer.bandwidthSequenceNum++ ); + + unsigned int randomSize = Min( (unsigned int)(sizeof(buffer) - 12), (unsigned int)session->GetTitleStorageInt( "net_bw_test_packetSizeBytes", net_bw_test_packetSizeBytes.GetInteger() ) ); + msg.WriteLong( randomSize ); + + for ( unsigned int j=0; j < randomSize; j++ ) { + msg.WriteByte( random.RandomInt( 255 ) ); + } + + unsigned int checksum = MD5_BlockChecksum( &buffer[8], randomSize ); + msg.WriteLong( checksum ); + + NET_VERBOSE_PRINT("Net: Sending bw challenge to peer %d time %d packet size %d\n", i, time, msg.GetSize() ); + + ProcessOutgoingMsg( i, buffer, msg.GetSize(), true, OOB_BANDWIDTH_TEST ); + + if ( session->GetTitleStorageInt( "net_bw_test_numPackets", net_bw_test_numPackets.GetInteger() ) > 0 && peer.bandwidthSequenceNum >= net_bw_test_numPackets.GetInteger() ) { + int sentBytes = peers[i].packetProc->GetOutgoingBytes() - peers[i].bandwidthTestBytes; // FIXME: this won't include the last sent msg + peers[i].bandwidthTestBytes = sentBytes; // this now means total bytes sent (we don't care about starting/ending total bytes sent to peer) + peers[i].bandwidthChallengeSendComplete = true; + + NET_VERBOSE_PRINT("Sent enough packets to peer %d for bandwidth test in %dms. Total bytes: %d\n", i, time - bandwidthChallengeStartTime, sentBytes ); + } + } + + if ( sentAll ) { + if ( bandwidthChallengeEndTime == 0 ) { + // We finished sending all our packets, set the timeout time + bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_host_timeout", net_bw_test_host_timeout.GetInteger() ); + NET_VERBOSE_PRINT("Net: finished sending BWC to peers. Waiting until %d to hear back\n", bandwidthChallengeEndTime ); + } + } + + if ( recAll ) { + bandwidthChallengeFinished = true; + bandwidthChallengeStartTime = 0; + + } else if ( bandwidthChallengeEndTime != 0 && bandwidthChallengeEndTime < time ) { + // Timed out waiting for someone - throttle them and move on + NET_VERBOSE_PRINT("^2Net: timed out waiting for bandwidth challenge results \n"); + for ( int i=0; i < peers.Num(); i++ ) { + NET_VERBOSE_PRINT(" Peer[%d] %s. SentAll: %d RecAll: %d\n", i, GetPeerName(i), peers[i].bandwidthChallengeSendComplete, peers[i].bandwidthChallengeResults ); + if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) { + ThrottlePeerSnapRate( i ); + } + } + bandwidthChallengeFinished = true; + bandwidthChallengeStartTime = 0; + } +} + +/* +======================== +idLobby::UpdateBandwidthTest +This will be called on clients to check current state of bandwidth testing +======================== +*/ +void idLobby::ClientUpdateBandwidthTest() { + if ( !verify( !IsHost() ) || !verify( host >= 0 ) ) { + return; + } + + if ( !peers[host].IsConnected() ) { + return; + } + + if ( bandwidthChallengeStartTime <= 0 ) { + // Not doing a test + return; + } + + int time = Sys_Milliseconds(); + if ( bandwidthChallengeEndTime > time ) { + // Test is still going on + return; + } + + // Its been long enough since we last received bw test msg. So lets send the results to the server + byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + // Send total time it took to receive all the msgs + // (note, subtract net_bw_test_timeout to get 'last recevied bandwidth test packet') + // (^^ Note if the last packet is fragmented and we never get it, this is technically wrong!) + int totalTime = ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ) - bandwidthChallengeStartTime; + msg.WriteLong( totalTime ); + + // Send total number of complete, in order packets we got + msg.WriteLong( bandwidthChallengeNumGoodSeq ); + + // Send the overall average bandwidth in KBS + // Note that sending the number of good packets is not enough. If the packets going out are fragmented, and we + // drop fragments, the number of good sequences will be lower than the bandwidth we actually received. + int totalIncomingBytes = peers[host].packetProc->GetIncomingBytes() - peers[host].bandwidthTestBytes; + msg.WriteLong( totalIncomingBytes ); + + idLib::Printf("^3Finished Bandwidth test: \n"); + idLib::Printf(" Total time: %d\n", totalTime ); + idLib::Printf(" Num good packets: %d\n", bandwidthChallengeNumGoodSeq ); + idLib::Printf(" Total received byes: %d\n\n", totalIncomingBytes ); + + bandwidthChallengeStartTime = 0; + bandwidthChallengeNumGoodSeq = 0; + + QueueReliableMessage( host, RELIABLE_BANDWIDTH_VALUES, msg.GetReadData(), msg.GetSize() ); +} + +/* +======================== +idLobby::HandleBandwidhTestValue +======================== +*/ +void idLobby::HandleBandwidhTestValue( int p, idBitMsg & msg ) { + if ( !IsHost() ) { + return; + } + + idLib::Printf("Received RELIABLE_BANDWIDTH_CHECK %d\n", Sys_Milliseconds() ); + + if ( bandwidthChallengeStartTime < 0 || bandwidthChallengeFinished ) { + idLib::Warning("Received bandwidth test results too early from peer %d", p ); + return; + } + + int totalTime = msg.ReadLong(); + int totalGoodSeq = msg.ReadLong(); + int totalReceivedBytes = msg.ReadLong(); + + // This is the % of complete packets we received. If the packets used in the BWC are big enough to fragment, then pctPackets + // will be lower than bytesPct (we will have received a larger PCT of overall bandwidth than PCT of full packets received). + // Im not sure if this is a useful distinction or not, but it may be good to compare against for now. + float pctPackets = peers[p].bandwidthSequenceNum > 0 ? (float) totalGoodSeq / (float)peers[p].bandwidthSequenceNum : -1.0f; + + // This is the % of total bytes sent/bytes received. + float bytesPct = peers[p].bandwidthTestBytes > 0 ? (float) totalReceivedBytes / (float)peers[p].bandwidthTestBytes : -1.0f; + + // Calculate overall bandwidth for the test. That is, total amount received over time. + // We may want to expand this to also factor in an average instantaneous rate. + // For now we are mostly concerned with culling out poor performing clients + float peerKBS = -1.0f; + if ( verify( totalTime > 0 ) ) { + peerKBS = ( (float)totalReceivedBytes / 1024.0f ) / MS2SEC(totalTime); + } + + int totalSendTime = peers[p].bandwidthTestLastSendTime - peers[p].bandwidthChallengeStartSendTime; + float outgoingKBS = -1.0f; + if ( verify( totalSendTime > 0 ) ) { + outgoingKBS = ( (float)peers[p].bandwidthTestBytes / 1024.0f ) / MS2SEC(totalSendTime); + } + + float pctKBS = peerKBS / outgoingKBS; + + bool failedRate = ( pctKBS < session->GetTitleStorageFloat( "net_bw_test_throttle_rate_pct", net_bw_test_throttle_rate_pct.GetFloat() ) ); + bool failedByte = ( bytesPct < session->GetTitleStorageFloat( "net_bw_test_throttle_byte_pct", net_bw_test_throttle_byte_pct.GetFloat() ) ); + bool failedSeq = ( pctPackets < session->GetTitleStorageFloat( "net_bw_test_throttle_seq_pct", net_bw_test_throttle_seq_pct.GetFloat() ) ); + + idLib::Printf("^3Finished Bandwidth test %s: \n", GetPeerName(p) ); + idLib::Printf(" Total time: %dms\n", totalTime ); + idLib::Printf(" %sNum good packets: %d (%.2f%)\n", ( failedSeq ? "^1" : "^2" ), totalGoodSeq, pctPackets ); + idLib::Printf(" %sTotal received bytes: %d (%.2f%)\n", ( failedByte ? "^1" : "^2" ), totalReceivedBytes, bytesPct ); + idLib::Printf(" %sEffective downstream: %.2fkbs (host: %.2fkbs) -> %.2f%\n\n", ( failedRate ? "^1" : "^2" ), peerKBS, outgoingKBS, pctKBS ); + + // If shittConnection(totalTime, totalGoodSeq/totalSeq, totalReceivedBytes/totalSentBytes) + // throttle this user: + // peers[p].throttledSnapRate = baseSnapRate * 2 + if ( failedRate || failedByte || failedSeq ) { + ThrottlePeerSnapRate( p ); + } + + + // See if we are finished + peers[p].bandwidthChallengeResults = true; + bandwidthChallengeFinished = true; + for ( int i=0; i < peers.Num(); i++ ) { + if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) { + bandwidthChallengeFinished = false; + } + } + + if ( bandwidthChallengeFinished ) { + bandwidthChallengeStartTime = 0; + } +} + +/* +======================== +idLobby::SendPingValues +Host only +Periodically send all peers' pings to all peers (for the UI). +======================== +*/ +void idLobby::SendPingValues() { + if ( !verify( IsHost() ) ) { + // paranoia + return; + } + + const int now = Sys_Milliseconds(); + + if ( nextSendPingValuesTime > now ) { + return; + } + + nextSendPingValuesTime = now + PING_INTERVAL_MS; + + pktPingValues_t packet; + + memset( &packet, 0, sizeof(packet) ); + + for( int i = 0; i < peers.Max(); ++i ) { + if ( i >= peers.Num() ) { + packet.pings[ i ] = -1; + } else if ( peers[ i ].IsConnected() ) { + packet.pings[ i ] = peers[ i ].lastPingRtt; + } else { + packet.pings[ i ] = -1; + } + } + + byte packetCopy[ sizeof(packet) ]; + idBitMsg msg( packetCopy, sizeof(packetCopy) ); + for( int i = 0; i < peers.Max(); ++i ) { + msg.WriteShort( packet.pings[ i ] ); + } + + for ( int i = 0; i < peers.Num(); i++ ) { + if ( peers[ i ].IsConnected() ) { + QueueReliableMessage( i, RELIABLE_PING_VALUES, msg.GetReadData(), msg.GetSize() ); + } + } +} + +/* +======================== +idLobby::PumpPings +Host: Periodically determine the round-trip time for a packet to all peers, and tell everyone + what everyone else's ping to the host is so they can display it in the UI. +Client: Indicate to the player when the server hasn't updated the ping values in too long. + This is usually going to preceed a connection timeout. +======================== +*/ +void idLobby::PumpPings() { + if ( IsHost() ) { + // Calculate ping to all peers + PingPeers(); + // Send the hosts calculated ping values to each peer to everyone has updated ping times + SendPingValues(); + // Do bandwidth testing + ServerUpdateBandwidthTest(); + // Send Migration Data + SendMigrationGameData(); + } else if ( IsPeer() ) { + ClientUpdateBandwidthTest(); + + if ( lastPingValuesRecvTime + PING_INTERVAL_MS + 1000 < Sys_Milliseconds() && migrationInfo.state == MIGRATE_NONE ) { + for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { + lobbyUser_t * user = GetLobbyUser( userIndex ); + if ( !verify( user != NULL ) ) { + continue; + } + user->pingMs = 999999; + } + } + } +} + +/* +======================== +idLobby::HandleReliablePing +======================== +*/ +void idLobby::HandleReliablePing( int p, idBitMsg & msg ) { + int c, b; + msg.SaveReadState( c, b ); + + pktPing_t ping; + + memset( &ping, 0, sizeof( ping ) ); + if ( !verify( sizeof( ping ) <= msg.GetRemainingData() ) ) { + NET_VERBOSE_PRINT( "NET: Ignoring ping from peer %i because packet was the wrong size\n", p ); + return; + } + + ping.timestamp = msg.ReadLong(); + + if ( IsHost() ) { + // we should probably verify here whether or not this ping was solicited or not + HandlePingReply( p, ping ); + } else { + // this means the server is requesting a ping, so reply + msg.RestoreReadState( c, b ); + QueueReliableMessage( p, RELIABLE_PING, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); + } +} + +/* +======================== +idLobby::HandlePingReply +======================== +*/ +void idLobby::HandlePingReply( int p, const pktPing_t & ping ) { + const int now = Sys_Milliseconds(); + + const int rtt = now - ping.timestamp; + peers[p].lastPingRtt = rtt; + + for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { + lobbyUser_t * u = GetLobbyUser( userIndex ); + if ( u->peerIndex == p ) { + u->pingMs = rtt; + } + } +} + +/* +======================== +idLobby::HandlePingValues +======================== +*/ +void idLobby::HandlePingValues( idBitMsg & msg ) { + pktPingValues_t packet; + memset( &packet, 0, sizeof( packet ) ); + for( int i = 0; i < peers.Max(); ++i ) { + packet.pings[ i ] = msg.ReadShort(); + } + + assert( IsPeer() ); + + lastPingValuesRecvTime = Sys_Milliseconds(); + + for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { + lobbyUser_t * u = GetLobbyUser( userIndex ); + if ( u->peerIndex != -1 && verify( u->peerIndex >= 0 && u->peerIndex < MAX_PEERS ) ) { + u->pingMs = packet.pings[ u->peerIndex ]; + } else { + u->pingMs = 0; + } + } + + // Stuff our ping in the hosts slot + if ( peerIndexOnHost != -1 && verify( peerIndexOnHost >= 0 && peerIndexOnHost < MAX_PEERS ) ) { + peers[host].lastPingRtt = packet.pings[ peerIndexOnHost ]; + } else { + peers[host].lastPingRtt = 0; + } +} + +/* +======================== +idLobby::SendAnotherFragment +Other than connectionless sends, this should be the chokepoint for sending packets to peers. +======================== +*/ +bool idLobby::SendAnotherFragment( int p ) { + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { // Not connected to any mode (party or game), so no need to send + return false; + } + + if ( !peer.packetProc->HasMoreFragments() ) { + return false; // No fragments to send for this peer + } + + if ( !CanSendMoreData( p ) ) { + return false; // We need to throttle the sends so we don't saturate the connection + } + + int time = Sys_Milliseconds(); + + if ( time - peer.lastFragmentSendTime < 2 ) { + NET_VERBOSE_PRINT("Too soon to send another packet. Delta: %d \n", ( time-peer.lastFragmentSendTime) ); + return false; // Too soon to send another fragment + } + + peer.lastFragmentSendTime = time; + + bool sentFragment = false; + + while ( true ) { + idBitMsg msg; + // We use the final packet size here because it has been processed, and no more headers will be added + byte buffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ]; + msg.InitWrite( buffer, sizeof( buffer ) ); + + if ( !peers[p].packetProc->GetSendFragment( time, peers[p].sessionID, msg ) ) { + break; + } + + const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE ); + + msg.BeginReading(); + sessionCB->SendRawPacket( peers[p].address, msg.GetReadData(), msg.GetSize(), useDirectPort ); + sentFragment = true; + break; // Comment this out to send all fragments in one burst + } + + if ( peer.packetProc->HasMoreFragments() ) { + NET_VERBOSE_PRINT("More packets left after ::SendAnotherFragment\n"); + } + + return sentFragment; +} + +/* +======================== +idLobby::CanSendMoreData +======================== +*/ +bool idLobby::CanSendMoreData( int p ) { + if ( !verify( p >= 0 && p < peers.Num() ) ) { + NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not a peer\n", p ); + return false; + } + peer_t & peer = peers[p]; + if ( !peer.IsConnected() ) { + NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not connected\n", p ); + return false; + } + + return peer.packetProc->CanSendMoreData(); +} + +/* +======================== +idLobby::ProcessOutgoingMsg +======================== +*/ +void idLobby::ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ) { + + peer_t & peer = peers[p]; + + if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) { + idLib::Printf( "peer.GetConnectionState() != CONNECTION_ESTABLISHED\n" ); + return; // Peer not fully connected for this session type, return + } + + if ( peer.packetProc->HasMoreFragments() ) { + idLib::Error( "FATAL: Attempt to process a packet while fragments still need to be sent.\n" ); // We can't handle this case + } + + int currentTime = Sys_Milliseconds(); + + // if ( currentTime - peer.lastProcTime < 30 ) { + // idLib::Printf("ProcessOutgoingMsg called within %dms %s\n", (currentTime - peer.lastProcTime), GetLobbyName() ); + // } + + peer.lastProcTime = currentTime; + + if ( !isOOB ) { + // Keep track of the last time an in-band packet was sent + // (used for things like knowing when reliables could have been last sent) + peer.lastInBandProcTime = peer.lastProcTime; + } + + idBitMsg msg; + msg.InitRead( (byte*)data, size ); + peer.packetProc->ProcessOutgoing( currentTime, msg, isOOB, userData ); +} + +/* +======================== +idLobby::ResendReliables +======================== +*/ +void idLobby::ResendReliables( int p ) { + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + return; + } + + if ( peer.packetProc->HasMoreFragments() ) { + return; // We can't send more data while fragments are still being sent out + } + + if ( !CanSendMoreData( p ) ) { + return; + } + + int time = Sys_Milliseconds(); + + const int DEFAULT_MIN_RESEND = 20; // Quicker resend while not in game to speed up resource transmission acks + const int DEFAULT_MIN_RESEND_INGAME = 100; + + int resendWait = DEFAULT_MIN_RESEND_INGAME; + + if ( sessionCB->GetState() == idSession::INGAME ) { + // setup some minimum waits and account for ping + resendWait = Max( DEFAULT_MIN_RESEND_INGAME, peer.lastPingRtt / 2 ); + if ( lobbyType == TYPE_PARTY ) { + resendWait = Max( 500, resendWait ); // party session does not need fast frequency at all once in game + } + } else { + // don't trust the ping when still loading stuff + // need to resend fast to speed up transmission of network decls + resendWait = DEFAULT_MIN_RESEND; + } + + if ( time - peer.lastInBandProcTime < resendWait ) { + // no need to resend reliables if they went out on an in-band packet recently + return; + } + + if ( peer.packetProc->NumQueuedReliables() > 0 || peer.packetProc->NeedToSendReliableAck() ) { + //NET_VERBOSE_PRINT( "NET: ResendReliables %s\n", GetLobbyName() ); + ProcessOutgoingMsg( p, NULL, 0, false, 0 ); // Force an empty unreliable msg so any reliables will get processed as well + } +} + +/* +======================== +idLobby::PumpPackets +======================== +*/ +void idLobby::PumpPackets() { + int newTime = Sys_Milliseconds(); + + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].IsConnected() ) { + peers[p].packetProc->RefreshRates( newTime ); + } + } + + // Resend reliable msg's (do this before we send out the fragments) + for ( int p = 0; p < peers.Num(); p++ ) { + ResendReliables( p ); + } + + // If we haven't sent anything to our peers in a long time, make sure to send an empty packet (so our heartbeat gets updated) so we don't get disconnected + // NOTE - We used to only send these to the host, but the host needs to also send these to clients + for ( int p = 0; p < peers.Num(); p++ ) { + if ( !peers[p].IsConnected() || peers[p].packetProc->HasMoreFragments() ) { + continue; + } + if ( newTime - peers[p].lastProcTime > 1000 * PEER_HEARTBEAT_IN_SECONDS ) { + //NET_VERBOSE_PRINT( "NET: ProcessOutgoing Heartbeat %s\n", GetLobbyName() ); + ProcessOutgoingMsg( p, NULL, 0, false, 0 ); + } + } + + // Send any unsent fragments for each peer (do this last) + for ( int p = 0; p < peers.Num(); p++ ) { + SendAnotherFragment( p ); + } +} + +/* +======================== +idLobby::UpdateMatchParms +======================== +*/ +void idLobby::UpdateMatchParms( const idMatchParameters & p ) { + if ( !IsHost() ) { + return; + } + + parms = p; + + // Update lobbyBackend with parms + if ( lobbyBackend != NULL ) { + lobbyBackend->UpdateMatchParms( parms ); + } + + SendMatchParmsToPeers(); +} + +/* +======================== +idLobby::GetHostUserName +======================== +*/ +const char * idLobby::GetHostUserName() const { + if ( !IsLobbyActive() ) { + return INVALID_LOBBY_USER_NAME; + } + return GetPeerName( -1 ); // This will just grab the first user with this peerIndex (which should be the host) +} + +/* +======================== +idLobby::SendReliable +======================== +*/ +void idLobby::SendReliable( int type, idBitMsg & msg, bool callReceiveReliable /*= true*/, peerMask_t sessionUserMask /*= MAX_UNSIGNED_TYPE( peerMask_t ) */ ) { + //assert( lobbyType == GetActingGameStateLobbyType() ); + + assert( type < 256 ); // QueueReliable only accepts a byte for message type + + // the queuing below sends the whole message + // I don't know if whole message is a good thing or a bad thing, but if the passed message has been read from already, this is most likely not going to do what the caller expects + assert( msg.GetReadCount() + msg.GetReadBit() == 0 ); + + if ( callReceiveReliable ) { + // NOTE: this will put the msg's read status to fully read - which is why the assert check is above + common->NetReceiveReliable( -1, type, msg ); + } + + uint32 sentPeerMask = 0; + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + lobbyUser_t * user = GetLobbyUser( i ); + if ( user->peerIndex == -1 ) { + continue; + } + + // We only care about sending these to peers in our party lobby + if ( user->IsDisconnected() ) { + continue; + } + + // Don't sent to a user if they are in the exlusion session user mask + if ( sessionUserMask != 0 && ( sessionUserMask & ( BIT( i ) ) ) == 0 ) { + continue; + } + + const int peerIndex = user->peerIndex; + + if ( peerIndex >= peers.Num() ) { + continue; + } + + peer_t & peer = peers[peerIndex]; + + if ( !peer.IsConnected() ) { + continue; + } + + if ( ( sentPeerMask & ( 1 << user->peerIndex ) ) == 0 ) { + QueueReliableMessage( user->peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData(), msg.GetSize() ); + sentPeerMask |= 1 << user->peerIndex; + } + } +} + +/* +======================== +idLobby::SendReliableToLobbyUser +can only be used on the server. will take care of calling locally if addressed to player 0 +======================== +*/ +void idLobby::SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + assert( type < 256 ); // QueueReliable only accepts a byte for message type + assert( IsHost() ); // This function should only be called in the server atm + const int peerIndex = PeerIndexFromLobbyUser( lobbyUserID ); + if ( peerIndex >= 0 ) { + // will send the remainder of a message that was started reading through, but not handling a partial byte read + assert( msg.GetReadBit() == 0 ); + QueueReliableMessage( peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); + } else { + common->NetReceiveReliable( -1, type, msg ); + } +} + +/* +======================== +idLobby::SendReliableToHost +will make sure to invoke locally if used on the server +======================== +*/ +void idLobby::SendReliableToHost( int type, idBitMsg & msg ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + if ( IsHost() ) { + common->NetReceiveReliable( -1, type, msg ); + } else { + // will send the remainder of a message that was started reading through, but not handling a partial byte read + assert( msg.GetReadBit() == 0 ); + QueueReliableMessage( host, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); + } +} + +/* +================================================================================================ +idLobby::reliablePlayerToPlayerHeader_t +================================================================================================ +*/ + +/* +======================== +idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t +======================== +*/ +idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t() : fromSessionUserIndex( -1 ), toSessionUserIndex( -1 ) { +} + +/* +======================== +idSessionLocal::reliablePlayerToPlayerHeader_t::Read +======================== +*/ +bool idLobby::reliablePlayerToPlayerHeader_t::Read( idLobby * lobby, idBitMsg & msg ) { + assert( lobby != NULL ); + + lobbyUserID_t lobbyUserIDFrom; + lobbyUserID_t lobbyUserIDTo; + + lobbyUserIDFrom.ReadFromMsg( msg ); + lobbyUserIDTo.ReadFromMsg( msg ); + + fromSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDFrom ); + toSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDTo ); + + if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) { + return false; + } + + if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) { + return false; + } + + return true; +} + +/* +======================== +idLobby::reliablePlayerToPlayerHeader_t::Write +======================== +*/ +bool idLobby::reliablePlayerToPlayerHeader_t::Write( idLobby * lobby, idBitMsg & msg ) { + + + if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) { + return false; + } + + if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) { + return false; + } + + lobby->GetLobbyUser( fromSessionUserIndex )->lobbyUserID.WriteToMsg( msg ); + lobby->GetLobbyUser( toSessionUserIndex )->lobbyUserID.WriteToMsg( msg ); + + return true; +} + +/* +======================== +idLobby::GetNumActiveLobbyUsers +======================== +*/ +int idLobby::GetNumActiveLobbyUsers() const { + int numActive = 0; + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + if ( !GetLobbyUser( i )->IsDisconnected() ) { + numActive++; + } + } + return numActive; +} + +/* +======================== +idLobby::AllPeersInGame +======================== +*/ +bool idLobby::AllPeersInGame() const { + assert( lobbyType == GetActingGameStateLobbyType() ); // This function doesn't make sense on a party lobby currently + + for ( int p = 0; p < peers.Num(); p++ ) { + if ( peers[p].IsConnected() && !peers[p].inGame ) { + return false; + } + } + + return true; +} + +/* +======================== +idLobby::PeerIndexFromLobbyUser +======================== +*/ +int idLobby::PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const { + const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID ); + + const lobbyUser_t * user = GetLobbyUser( lobbyUserIndex ); + + if ( user == NULL ) { + // This needs to be OK for bot support ( or else add bots at the session level ) + return -1; + } + + return user->peerIndex; +} + +/* +======================== +idLobby::GetPeerTimeSinceLastPacket +======================== +*/ +int idLobby::GetPeerTimeSinceLastPacket( int peerIndex ) const { + if ( peerIndex < 0 ) { + return 0; + } + return Sys_Milliseconds() - peers[peerIndex].lastHeartBeat; +} + +/* +======================== +idLobby::GetActingGameStateLobbyType +======================== +*/ +idLobby::lobbyType_t idLobby::GetActingGameStateLobbyType() const { + extern idCVar net_useGameStateLobby; + return ( net_useGameStateLobby.GetBool() ) ? TYPE_GAME_STATE : TYPE_GAME; +} + +//======================================================================================================================== +// idLobby::peer_t +//======================================================================================================================== + +/* +======================== +idLobby::peer_t::GetConnectionState +======================== +*/ +idLobby::connectionState_t idLobby::peer_t::GetConnectionState() const { + return connectionState; +} diff --git a/neo/sys/sys_lobby.h b/neo/sys/sys_lobby.h new file mode 100644 index 00000000..e6ef8471 --- /dev/null +++ b/neo/sys/sys_lobby.h @@ -0,0 +1,914 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "sys_lobby_backend.h" + +#define INVALID_LOBBY_USER_NAME " " // Used to be "INVALID" but Sony might not like that. + +class idSessionCallbacks; +class idDebugGraph; +/* +======================== +idLobby +======================== +*/ +class idLobby : public idLobbyBase { +public: + idLobby(); + + enum lobbyType_t { + TYPE_PARTY = 0, + TYPE_GAME = 1, + TYPE_GAME_STATE = 2, + TYPE_INVALID = 0xff + }; + + enum lobbyState_t { + STATE_IDLE, + STATE_CREATE_LOBBY_BACKEND, + STATE_SEARCHING, + STATE_OBTAINING_ADDRESS, + STATE_CONNECT_HELLO_WAIT, + STATE_FINALIZE_CONNECT, + STATE_FAILED, + NUM_STATES + }; + + enum failedReason_t { + FAILED_UNKNOWN, + FAILED_CONNECT_FAILED, + FAILED_MIGRATION_CONNECT_FAILED, + }; + + void Initialize( lobbyType_t sessionType_, class idSessionCallbacks * callbacks ); + void StartHosting( const idMatchParameters & parms ); + void StartFinding( const idMatchParameters & parms_ ); + void Pump(); + void ProcessSnapAckQueue(); + void Shutdown( bool retainMigrationInfo = false, bool skipGoodbye = false ); // Goto idle state + void HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ); + lobbyState_t GetState() { return state; } + virtual bool HasActivePeers() const; + virtual bool IsLobbyFull() const { return NumFreeSlots() == 0; } + int NumFreeSlots() const; + +public: + + enum reliablePlayerToPlayer_t { + //RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT, + RELIABLE_PLAYER_TO_PLAYER_GAME_DATA, + // Game messages would be reserved here in the same way that RELIABLE_GAME_DATA is. + // I'm worried about using up the 0xff values we have for reliable type, so I'm not + // going to reserve anything here just yet. + NUM_RELIABLE_PLAYER_TO_PLAYER, + }; + + enum reliableType_t { + RELIABLE_HELLO, // host to peer : connection established + RELIABLE_USER_CONNECTED, // host to peer : a new session user connected + RELIABLE_USER_DISCONNECTED, // host to peer : a session user disconnected + RELIABLE_START_LOADING, // host to peer : peer should begin loading the map + RELIABLE_LOADING_DONE, // peer to host : finished loading map + RELIABLE_IN_GAME, // peer to host : first full snap received, in game now + RELIABLE_SNAPSHOT_ACK, // peer to host : got a snapshot + RELIABLE_RESOURCE_ACK, // peer to host : got some new resources + RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, // host to peer : connect to this server + RELIABLE_PARTY_CONNECT_OK, // host to peer + RELIABLE_PARTY_LEAVE_GAME_LOBBY, // host to peer : leave game lobby + RELIABLE_MATCH_PARMS, // host to peer : update in match parms + RELIABLE_UPDATE_MATCH_PARMS, // peer to host : peer updating match parms + + // User join in progress msg's (join in progress for the party/game lobby, not inside a match) + RELIABLE_USER_CONNECT_REQUEST, // peer to host: local user wants to join session in progress + RELIABLE_USER_CONNECT_DENIED, // host to peer: user join session in progress denied (not enough slots) + + // User leave in progress msg's (leave in progress for the party/game lobby, not inside a match) + RELIABLE_USER_DISCONNECT_REQUEST, // peer to host: request host to remove user from session + + RELIABLE_KICK_PLAYER, // host to peer : kick a player + + RELIABLE_MATCHFINISHED, // host to peer - Match is in post looking at score board + RELIABLE_ENDMATCH, // host to peer - End match, and go to game lobby + RELIABLE_ENDMATCH_PREMATURE, // host to peer - End match prematurely, and go to game lobby (onl possible in unrated/custom games) + + RELIABLE_SESSION_USER_MODIFIED, // peer to host : user changed something (emblem, name, etc) + RELIABLE_UPDATE_SESSION_USER, // host to peers : inform all peers of the change + + RELIABLE_HEADSET_STATE, // * to * : headset state change for user + RELIABLE_VOICE_STATE, // * to * : voice state changed for user pair (mute, unmute, etc) + RELIABLE_PING, // * to * : send host->peer, then reflected + RELIABLE_PING_VALUES, // host to peers : ping data from lobbyUser_t for everyone + + RELIABLE_BANDWIDTH_VALUES, // peer to host: data back about bandwidth test + + RELIABLE_ARBITRATE, // host to peer : start arbitration + RELIABLE_ARBITRATE_OK, // peer to host : ack arbitration request + + RELIABLE_POST_STATS, // host to peer : here, write these stats now (hacky) + + RELIABLE_MIGRATION_GAME_DATA, // host to peers: game data to use incase of a migration + + RELIABLE_START_MATCH_GAME_LOBBY_HOST, // game lobby host to game state lobby host: start the match, since all players are in + + RELIABLE_DUMMY_MSG, // used as a placeholder for old removed msg's + + RELIABLE_PLAYER_TO_PLAYER_BEGIN, + // use reliablePlayerToPlayer_t + RELIABLE_PLAYER_TO_PLAYER_END = RELIABLE_PLAYER_TO_PLAYER_BEGIN + NUM_RELIABLE_PLAYER_TO_PLAYER, + + // * to * : misc reliable game data above this + RELIABLE_GAME_DATA = RELIABLE_PLAYER_TO_PLAYER_END + }; + + // JGM: Reliable type in packet is a byte and there are a lot of reliable game messages. + // Feel free to bump this up since it's arbitrary anyway, but take a look at gameReliable_t. + // At the moment, both Doom and Rage have around 32 gameReliable_t values. + compile_time_assert( RELIABLE_GAME_DATA < 64 ); + + static const char * stateToString[ NUM_STATES ]; + + // Consts + + static const int PEER_HEARTBEAT_IN_SECONDS = 5; // Make sure something was sent every 5 seconds, so we don't time out + static const int CONNECT_REQUEST_FREQUENCY_IN_SECONDS = 5; // Frequency at which we resend a request to connect to a server (will increase in frequency over time down to MIN_CONNECT_FREQUENCY_IN_SECONDS) + static const int MIN_CONNECT_FREQUENCY_IN_SECONDS = 1; // Min frequency of connection attempts + static const int MAX_CONNECT_ATTEMPTS = 5; + static const int BANDWIDTH_REPORTING_MAX = 10240; // make bps to report receiving (clamp if higher). For quantizing + static const int BANDWIDTH_REPORTING_BITS = 16; // number of bits to use for bandwidth reporting + static const int MAX_BPS_HISTORY = 32; // size of outgoing bps history to maintain for each client + + static const int MAX_SNAP_SIZE = idPacketProcessor::MAX_MSG_SIZE; + static const int MAX_SNAPSHOT_QUEUE = 64; + + static const int OOB_HELLO = 0; + static const int OOB_GOODBYE = 1; + static const int OOB_GOODBYE_W_PARTY = 2; + static const int OOB_GOODBYE_FULL = 3; + static const int OOB_RESOURCE_LIST = 4; + static const int OOB_VOICE_AUDIO = 5; + + static const int OOB_MATCH_QUERY = 6; + static const int OOB_MATCH_QUERY_ACK = 7; + + static const int OOB_SYSTEMLINK_QUERY = 8; + + static const int OOB_MIGRATE_INVITE = 9; + + static const int OOB_BANDWIDTH_TEST = 10; + + enum connectionState_t { + CONNECTION_FREE = 0, // Free peer slot + CONNECTION_CONNECTING = 1, // Waiting for response from host for initial connection + CONNECTION_ESTABLISHED = 2, // Connection is established and active + }; + + struct peer_t { + peer_t() { + loaded = false; + inGame = false; + networkChecksum = 0; + lastSnapTime = 0; + snapHz = 0.0f; + numResources = 0; + lastHeartBeat = 0; + connectionState = CONNECTION_FREE; + packetProc = NULL; + snapProc = NULL; + nextPing = 0; // do it asap + lastPingRtt = 0; + sessionID = idPacketProcessor::SESSION_ID_INVALID; + startResourceLoadTime = 0; + nextThrottleCheck = 0; + maxSnapQueueSize = 0; + throttledSnapRate = 0; + pauseSnapshots = false; + + receivedBps = -1.0f; + maxSnapBps = -1.0f; + receivedThrottle = 0; + receivedThrottleTime = 0; + + throttleSnapsForXSeconds = 0; + recoverPing = 0; + failedPingRecoveries = 0; + rightBeforeSnapsPing = 0; + + bandwidthTestLastSendTime = 0; + bandwidthSequenceNum = 0; + bandwidthTestBytes = 0; + bandwidthChallengeStartSendTime = 0; + bandwidthChallengeResults = false; + bandwidthChallengeSendComplete = false; + + numSnapsSent = 0; + + ResetConnectState(); + }; + + void ResetConnectState() { + lastResourceTime = 0; + lastSnapTime = 0; + snapHz = + lastProcTime = 0; + lastInBandProcTime = 0; + lastFragmentSendTime = 0; + needToSubmitPendingSnap = false; + lastSnapJobTime = true; + startResourceLoadTime = 0; + + receivedBps = -1.0; + maxSnapBps = -1.0f; + receivedThrottle = 0; + receivedThrottleTime = 0; + throttleSnapsForXSeconds = 0; + recoverPing = 0; + failedPingRecoveries = 0; + rightBeforeSnapsPing = 0; + + bandwidthTestLastSendTime = 0; + bandwidthSequenceNum = 0; + bandwidthTestBytes = 0; + bandwidthChallengeStartSendTime = 0; + bandwidthChallengeResults = false; + bandwidthChallengeSendComplete = false; + memset( sentBpsHistory, 0, sizeof( sentBpsHistory ) ); + receivedBpsIndex = 0; + + debugGraphs.Clear(); + } + + void ResetAllData() { + ResetConnectState(); + ResetMatchData(); + } + + void ResetMatchData() { + loaded = false; + networkChecksum = 0; + inGame = false; + numResources = 0; + needToSubmitPendingSnap = false; + throttledSnapRate = 0; + maxSnapQueueSize = 0; + receivedBpsIndex = -1; + numSnapsSent = 0; + pauseSnapshots = false; + + // Reset the snapshot processor + if ( snapProc != NULL ) { + snapProc->Reset( false ); + } + } + + void Print() { + idLib::Printf(" lastResourceTime: %d\n", lastResourceTime ); + idLib::Printf(" lastSnapTime: %d\n", lastSnapTime ); + idLib::Printf(" lastProcTime: %d\n", lastProcTime ); + idLib::Printf(" lastInBandProcTime: %d\n", lastInBandProcTime ); + idLib::Printf(" lastFragmentSendTime: %d\n", lastFragmentSendTime ); + idLib::Printf(" needToSubmitPendingSnap: %d\n", needToSubmitPendingSnap ); + idLib::Printf(" lastSnapJobTime: %d\n", lastSnapJobTime ); + } + + bool IsActive() const { return connectionState != CONNECTION_FREE; } + bool IsConnected() const { return connectionState == CONNECTION_ESTABLISHED; } + + connectionState_t GetConnectionState() const; + + connectionState_t connectionState; + bool loaded; // true if this peer has finished loading the map + bool inGame; // true if this peer received the first snapshot, and is in-game + int lastSnapTime; // Last time a snapshot was sent on the network to this peer + float snapHz; + int lastProcTime; // Used to determine when a packet was processed for sending to this peer + int lastInBandProcTime; // Last time a in-band packet was processed for sending + int lastFragmentSendTime; // Last time a fragment was sent out (fragments are processed msg's, waiting to be fully sent) + unsigned long networkChecksum; // Checksum used to determine if a peer loaded the network resources the EXACT same as the server did + int pauseSnapshots; + + lobbyAddress_t address; + + int numResources; // number of network resources we know the peer has + + idPacketProcessor * packetProc; // Processes packets for this peer + idSnapshotProcessor * snapProc; // Processes snapshots for this peer + idStaticList< idDebugGraph *, 4 > debugGraphs;// + + int lastResourceTime; // Used to throttle the sending of resources + + int lastHeartBeat; + int nextPing; // next Sys_Milliseconds when I'll send this peer a RELIABLE_PING + int lastPingRtt; + bool needToSubmitPendingSnap; + int lastSnapJobTime; // Last time a snapshot was sent to the joblist for this peer + + + int startResourceLoadTime; // Used to determine how long a peer has been loading resources + + int maxSnapQueueSize; // how big has the snap queue gotten? + int throttledSnapRate; // effective snap rate for this peer + int nextThrottleCheck; + + int numSnapsSent; + + float sentBpsHistory[ MAX_BPS_HISTORY ]; + int receivedBpsIndex; + + float receivedBps; // peer's reported bps (they tell us their effective downstream) + float maxSnapBps; + float receivedThrottle; // amount of accumlated time this client has been lagging behind + int receivedThrottleTime; // last time we did received based throttle calculations + + int throttleSnapsForXSeconds; + int recoverPing; + int failedPingRecoveries; + int rightBeforeSnapsPing; + + int bandwidthChallengeStartSendTime; // time we sent first packet of bw challenge to this peer + int bandwidthTestLastSendTime; // last time in MS we sent them a bw challenge packet + int bandwidthTestBytes; // used to measure number of bytes we sent them + int bandwidthSequenceNum; // number of challenge sequences we sent them + bool bandwidthChallengeResults; // we got results back + bool bandwidthChallengeSendComplete; // we finished sending everything + + + idPacketProcessor::sessionId_t sessionID; + }; + + const char * GetLobbyName() { + switch ( lobbyType ) { + case TYPE_PARTY: return "TYPE_PARTY"; + case TYPE_GAME: return "TYPE_GAME"; + case TYPE_GAME_STATE: return "TYPE_GAME_STATE"; + } + + return "LOBBY_INVALID"; + } + + virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ); // find a open user slot for the bot, and return the userID. + virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ); // release the session user slot, so that it can be claimed by a player, etc. + virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const; // check to see if the lobby user is a bot or not + + virtual int GetNumLobbyUsers() const { return userList.Num(); } + virtual int GetNumActiveLobbyUsers() const; + virtual bool AllPeersInGame() const; + lobbyUser_t * GetLobbyUser( int index ) { return ( index >= 0 && index < GetNumLobbyUsers() ) ? userList[index] : NULL; } + const lobbyUser_t * GetLobbyUser( int index ) const { return ( index >= 0 && index < GetNumLobbyUsers() ) ? userList[index] : NULL; } + + virtual bool IsLobbyUserConnected( int index ) const { return !IsLobbyUserDisconnected( index ); } + + virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const; + + virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const; + virtual int PeerIndexForHost() const { return host; } + + virtual int PeerIndexOnHost() const { return peerIndexOnHost; } // Returns -1 if we are the host + + virtual const idMatchParameters & GetMatchParms() const { return parms; } + + lobbyType_t GetActingGameStateLobbyType() const; + + // If IsHost is true, we are a host accepting connections from peers + bool IsHost() const { return isHost; } + // If IsPeer is true, we are a peer, with an active connection to a host + bool IsPeer() const { + if ( host == -1 ) { + return false; // Can't possibly be a peer if we haven't setup a host + } + assert( !IsHost() ); + return peers[host].IsConnected(); + } + bool IsConnectingPeer() const { + if ( host == -1 ) { + return false; // Can't possibly be a peer if we haven't setup a host + } + assert( !IsHost() ); + return peers[host].connectionState == CONNECTION_CONNECTING; + } + + // IsRunningAsHostOrPeer means we are either an active host, and can accept connections from peers, or we are a peer with an active connection to a host + bool IsRunningAsHostOrPeer() const { return IsHost() || IsPeer(); } + bool IsLobbyActive() const { return IsRunningAsHostOrPeer(); } + + + struct reliablePlayerToPlayerHeader_t { + int fromSessionUserIndex; + int toSessionUserIndex; + + reliablePlayerToPlayerHeader_t(); + + // Both read and write return false if the data is invalid. + // The state of the msg and object are undefined if false is returned. + // The network packets contain userIds, and Read/Write will translate from userId to a + // sessionUserIndex. The sessionUserIndex should be the same on all peers, but the + // userId has to be used in case the target player quits while the message is on the + // wire from the originating peer to the server. + bool Read( idLobby * lobby, idBitMsg & msg ); + bool Write( idLobby * lobby, idBitMsg & msg ); + }; + + int GetTotalOutgoingRate(); // returns total instant outgoing bandwidth in B/s + +//private: +public: // Turning this on for now, for the sake of getting this up and running to see where things are + // State functions + void State_Idle(); + void State_Create_Lobby_Backend(); + void State_Searching(); + void State_Obtaining_Address(); + void State_Finalize_Connect(); + void State_Connect_Hello_Wait(); + + void SetState( lobbyState_t newState ); + + void StartCreating(); + + int FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID = false ); + int FindAnyPeer( const lobbyAddress_t & remoteAddress ) const; + int FindFreePeer() const; + int AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ); + void DisconnectPeerFromSession( int p ); + void SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye = false ); + void DisconnectAllPeers(); + + virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) ); + virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ); + virtual void SendReliableToHost( int type, idBitMsg & msg ); + void SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull = false ); + void QueueReliableMessage( int peerNum, byte type ) { QueueReliableMessage( peerNum, type, NULL, 0 ); } + void QueueReliableMessage( int p, byte type, const byte * data, int dataLen ); + virtual int GetNumConnectedPeers() const; + virtual int GetNumConnectedPeersInGame() const; + void SendMatchParmsToPeers(); + + static bool IsReliablePlayerToPlayerType( byte type ); + void HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ); + void HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ); + + void SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type ) { SendConnectionLess( remoteAddress, type, NULL, 0 ); } + void SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ); + void SendConnectionRequest(); + void ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ); + void HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ); + void HandleConnectionAttemptFailed(); + bool ConnectToNextSearchResult(); + bool CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ); + bool VerifyNumConnectingUsers( idBitMsg & msg ); + bool VerifyLobbyUserIDs( idBitMsg & msg ); + int HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ); + void InitStateLobbyHost(); + + void SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ); + void SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ); + void SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ); + void SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ); + void NotifyPartyOfLeavingGameLobby(); + uint32 GetPartyTokenAsHost(); + + virtual void DrawDebugNetworkHUD() const; + virtual void DrawDebugNetworkHUD2() const; + virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ); + + void CheckHeartBeats(); + bool IsLosingConnectionToHost() const; + bool IsMigratedStatsGame() const; + bool ShouldRelaunchMigrationGame() const; + bool ShouldShowMigratingDialog() const; + bool IsMigrating() const; + + // Pings + struct pktPing_t { + int timestamp; + }; + + void PingPeers(); + void SendPingValues(); + void PumpPings(); + void HandleReliablePing( int p, idBitMsg & msg ); + void HandlePingReply( int p, const pktPing_t & ping ); + void HandlePingValues( idBitMsg & msg ); + void HandleBandwidhTestValue( int p, idBitMsg & msg ); + void HandleMigrationGameData( idBitMsg & msg ); + void HandleHeadsetStateChange( int fromPeer, idBitMsg & msg ); + + bool SendAnotherFragment( int p ); + bool CanSendMoreData( int p ); + void ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ); + void ResendReliables( int p ); + void PumpPackets(); + + void UpdateMatchParms( const idMatchParameters & p ); + + // SessionID helpers + idPacketProcessor::sessionId_t EncodeSessionID( uint32 key ) const; + void DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const; + idPacketProcessor::sessionId_t GenerateSessionID() const; + bool SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const; + idPacketProcessor::sessionId_t IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const; + + void HandleHelloAck( int p, idBitMsg & msg ); + + virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const; + virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const; + virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const; + virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const; + virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const; + virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const; + virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const; + virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ); + virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const; + virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ); + virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ); + virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const; + + const char * GetPeerName( int peerNum ) const; + virtual const char * GetHostUserName() const; + + void HandleReliableMsg( int p, idBitMsg & msg ); + + // Bandwidth / Qos / Throttling + void BeginBandwidthTest(); + bool BandwidthTestStarted(); + + void ServerUpdateBandwidthTest(); + void ClientUpdateBandwidthTest(); + + void ThrottlePeerSnapRate( int peerNum ); + + // + // sys_session_instance_users.cpp + // + + lobbyUser_t * AllocUser( const lobbyUser_t & defaults ); + void FreeUser( lobbyUser_t * user ); + bool VerifyUser( const lobbyUser_t * lobbyUser ) const; + void FreeAllUsers(); + void RegisterUser( lobbyUser_t * lobbyUser ); + void UnregisterUser( lobbyUser_t * lobbyUser ); + + bool IsSessionUserLocal( const lobbyUser_t * lobbyUser ) const; + bool IsSessionUserIndexLocal( int i ) const; + int GetLobbyUserIndexByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType = false ) const; + lobbyUser_t * GetLobbyUserByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType = false ); + + // Helper function to create a lobby user from a local user + lobbyUser_t CreateLobbyUserFromLocalUser( const idLocalUser * localUser ); + + // This function is designed to initialize the session users of type lobbyType (TYPE_GAME or TYPE_PARTY) + // to the current list of local users that are being tracked by the sign-in manager + void InitSessionUsersFromLocalUsers( bool onlineMatch ); + + // Convert an local userhandle to a session user (-1 if there is no session user with this handle) + int GetLobbyUserIndexByLocalUserHandle( const localUserHandle_t localUserHandle ) const; + + // This takes a session user, and converts to a controller user + idLocalUser * GetLocalUserFromLobbyUserIndex( int lobbyUserIndex ); + + // Takes a controller user, and converts to a session user (will return NULL if there is no session user for this controller user) + lobbyUser_t * GetSessionUserFromLocalUser( const idLocalUser * controller ); + + void RemoveUsersWithDisconnectedPeers(); + void RemoveSessionUsersByIDList( idList< lobbyUserID_t > & usersToRemoveByID ); + void SendNewUsersToPeers( int skipPeer, int userStart, int numUsers ); + void SendPeersMicStatusToNewUsers( int peerNumber ); + void AddUsersFromMsg( idBitMsg & msg, int fromPeer ); + void UpdateSessionUserOnPeers( idBitMsg & msg ); + void HandleUpdateSessionUser( idBitMsg & msg ); + void CreateUserUpdateMessage( int userIndex, idBitMsg & msg ); + void UpdateLocalSessionUsers(); + int PeerIndexForSessionUserIndex( int sessionUserIndex ) const; + void HandleUserConnectFailure( int p, idBitMsg & inMsg, int reliableType ); + void ProcessUserDisconnectMsg( idBitMsg & msg ); + void CompactDisconnectedUsers(); + + // Sends a request to the host to join a local user to a session + void RequestLocalUserJoin( idLocalUser * localUser ); + // Sends a request to the host to remove a session user from the session + void RequestSessionUserDisconnect( int sessionUserIndex ); + + // This function sycs the session users with the current list of of local users on the signin manager. + // It will remove the session users that are either no longer on the signin manager, or it + // will remove them if they are no longer allowed to be in the session. + // If it finds a local users that are not in a particular session, it will add that user if allowed. + void SyncLobbyUsersWithLocalUsers( bool allowJoin, bool onlineMatch ); + + bool ValidateConnectedUser( const lobbyUser_t * user ) const; + virtual bool IsLobbyUserDisconnected( int userIndex ) const; + virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const; + virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const; + virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const; + virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const; + virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const; + virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ); + virtual bool IsPeerDisconnected( int peerIndex ) const { return !peers[peerIndex].IsConnected(); } + + float GetAverageSessionLevel(); + float GetAverageLocalUserLevel( bool onlineOnly ); + + void QueueReliablePlayerToPlayerMessage( int fromSessionUserIndex, int toSessionUserIndex, reliablePlayerToPlayer_t type, const byte * data, int dataLen ); + virtual void KickLobbyUser( lobbyUserID_t lobbyUserID ); + + int GetNumConnectedUsers() const; + + // + // sys_session_instance_migrate.cpp + // + + bool IsBetterHost( int ping1, lobbyUserID_t userId1, int ping2, lobbyUserID_t userId2 ); + int FindMigrationInviteIndex( lobbyAddress_t & address ); + void UpdateHostMigration(); + void BuildMigrationInviteList( bool inviteOldHost ); + void PickNewHost( bool forceMe = false, bool inviteOldHost = false ); + void PickNewHostInternal( bool forceMe, bool inviteOldHost ); + void BecomeHost(); + void EndMigration(); + void ResetAllMigrationState(); + + void SendMigrationGameData(); + + bool GetMigrationGameData( idBitMsg &msg, bool reading ); + bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg &msg, bool reading ); + + // + // Snapshots + // sys_session_instance_snapshot.cpp + // + void UpdateSnaps(); + bool SendCompletedSnaps(); + bool SendResources( int p ); + bool SubmitPendingSnap( int p ); + void SendCompletedPendingSnap( int p ); + void CheckPeerThrottle( int p ); + void ApplySnapshotDelta( int p, int snapshotNumber ); + bool ApplySnapshotDeltaInternal( int p, int snapshotNumber ); + void SendSnapshotToPeer( idSnapShot & ss, int p ); + bool AllPeersHaveBaseState(); + void ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ); + bool FirstSnapHasBeenSent( int p ); + virtual bool EnsureAllPeersHaveBaseState(); + virtual bool AllPeersHaveStaleSnapObj( int objId ); + virtual bool AllPeersHaveExpectedSnapObj( int objId ); + virtual void MarkSnapObjDeleted( int objId ); + virtual void RefreshSnapObj( int objId ); + void ResetBandwidthStats(); + void DetectSaturation( int p ); + virtual void AddSnapObjTemplate( int objID, idBitMsg & msg ); + + static const int MAX_PEERS = MAX_PLAYERS; + + //------------------------ + // Pings + //------------------------ + struct pktPingValues_t { + idArray pings; + }; + + static const int PING_INTERVAL_MS = 3000; + + int lastPingValuesRecvTime; // so clients can display something when server stops pinging + int nextSendPingValuesTime; // the next time to send RELIABLE_PING_VALUES + + static const int MIGRATION_GAME_DATA_INTERVAL_MS = 1000; + int nextSendMigrationGameTime; // when to send next migration game data + int nextSendMigrationGamePeer; // who to send next migration game data to + + lobbyType_t lobbyType; + lobbyState_t state; // State of this lobby + failedReason_t failedReason; + + int host; // which peer is the host of this type of session (-1 if we are the host) + int peerIndexOnHost; // -1 if we are the host + lobbyAddress_t hostAddress; // address of the host for this type of session + bool isHost; // true if we are the host + idLobbyBackend * lobbyBackend; + + int helloStartTime; // Used to determine when the first hello was sent + int lastConnectRequest; // Used to determine when the last hello was sent + int connectionAttempts; // Number of connection attempts + + + bool needToDisplayMigrateMsg; // If true, we migrated as host, so we need to display the msg as soon as the lobby is active + gameDialogMessages_t migrationDlg; // current migration dialog we should be showing + + uint8 migrateMsgFlags; // cached match flags from the old game we migrated from, so we know what type of msg to display + + bool joiningMigratedGame; // we are joining a migrated game and need to tell the session mgr if we succeed or fail + + // ------------------------ + // Bandwidth challenge + // ------------------------ + int bandwidthChallengeEndTime; // When the challenge will end/timeout + int bandwidthChallengeStartTime; // time in MS the challenge started + bool bandwidthChallengeFinished; // (HOST) test is finished and we received results back from all peers (or timed out) + int bandwidthChallengeNumGoodSeq; // (PEER) num of good, in order packets we recevieved + + int lastSnapBspHistoryUpdateSequence; + + void SaveDisconnectedUser( const lobbyUser_t & user ); // This is needed to get the a user's gamertag after disconnection. + + idSessionCallbacks * sessionCB; + + enum migrationState_t { + MIGRATE_NONE, + MIGRATE_PICKING_HOST, + MIGRATE_BECOMING_HOST, + }; + + struct migrationInvite_t { + migrationInvite_t() { + lastInviteTime = -1; + pingMs = 0; + migrationGameData = -1; + } + + lobbyAddress_t address; + int pingMs; + lobbyUserID_t userId; + int lastInviteTime; + int migrationGameData; + }; + + struct migrationInfo_t { + migrationInfo_t() { + state = MIGRATE_NONE; + ourPingMs = 0; + ourUserId = lobbyUserID_t(); + } + + migrationState_t state; + idStaticList< migrationInvite_t, MAX_PEERS > invites; + int migrationStartTime; + int ourPingMs; + lobbyUserID_t ourUserId; + + struct persistUntilGameEnds_t { + persistUntilGameEnds_t() { + Clear(); + } + void Clear() { + wasMigratedHost = false; + wasMigratedJoin = false; + wasMigratedGame = false; + ourGameData = -1; + hasGameData = false; + hasRelaunchedMigratedGame = false; + + memset( gameData, 0, sizeof( gameData ) ); + memset( gameDataUser, 0, sizeof( gameDataUser ) ); + } + + int ourGameData; + bool wasMigratedHost; // we are hosting a migrated session + bool wasMigratedJoin; // we joined a migrated session + bool wasMigratedGame; // If true, we migrated from a game + bool hasRelaunchedMigratedGame; + + // A generic blob of data that the gamechallenge (or anything else) can read and write to for host migration + static const int MIGRATION_GAME_DATA_SIZE = 32; + byte gameData[ MIGRATION_GAME_DATA_SIZE ]; + + static const int MIGRATION_GAME_DATA_USER_SIZE = 64; + byte gameDataUser[ MAX_PLAYERS ][ MIGRATION_GAME_DATA_USER_SIZE ]; + + bool hasGameData; + } persistUntilGameEndsData; + }; + + struct disconnectedUser_t { + lobbyUserID_t lobbyUserID; // Locally generated to be unique, and internally keeps the local user handle + char gamertag[lobbyUser_t::MAX_GAMERTAG]; + }; + + migrationInfo_t migrationInfo; + + bool showHostLeftTheSession; + bool connectIsFromInvite; + + idList< lobbyConnectInfo_t > searchResults; + + typedef idStaticList< lobbyUser_t *, MAX_PLAYERS > idLobbyUserList; + typedef idStaticList< lobbyUser_t, MAX_PLAYERS > idLobbyUserPool; + + idLobbyUserList userList; // list of currently connected users to this lobby + idLobbyUserList freeUsers; // list of free users + idLobbyUserPool userPool; + + idList< disconnectedUser_t> disconnectedUsers; // List of users which were connected, but aren't anymore, for printing their name on the hud + + idStaticList< peer_t, MAX_PEERS > peers; // Unique machines connected to this lobby + + uint32 partyToken; + + idMatchParameters parms; + + bool loaded; // Used for game sessions, whether this machine is loaded or not + bool respondToArbitrate; // true when the host has requested us to arbitrate our session (for TYPE_GAME only) + bool everyoneArbitrated; + bool waitForPartyOk; + bool startLoadingFromHost; + + //------------------------ + // Snapshot jobs + //------------------------ + static const int SNAP_OBJ_JOB_MEMORY = 1024 * 128; // 128k of obj memory + + lzwCompressionData_t * lzwData; // Shared across all snapshot jobs + uint8 * objMemory; // Shared across all snapshot jobs + bool haveSubmittedSnaps; // True if we previously submitted snaps to jobs + idSnapShot * localReadSS; + + struct snapDeltaAck_t { + int p; + int snapshotNumber; + }; + + idStaticList< snapDeltaAck_t, 16 > snapDeltaAckQueue; +}; + +/* +======================== +idSessionCallbacks +======================== +*/ +class idSessionCallbacks { +public: + virtual idLobby & GetPartyLobby() = 0; + virtual idLobby & GetGameLobby() = 0; + virtual idLobby & GetActingGameStateLobby() = 0; + virtual idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType ) = 0; + virtual int GetUniquePlayerId() const = 0; + virtual idSignInManagerBase & GetSignInManager() = 0; + virtual void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool useDirectPort ) = 0; + + virtual bool BecomingHost( idLobby & lobby ) = 0; // Called when a lobby is about to become host + virtual void BecameHost( idLobby & lobby ) = 0; // Called when a lobby becomes a host + virtual bool BecomingPeer( idLobby & lobby ) = 0; // Called when a lobby is about to become peer + virtual void BecamePeer( idLobby & lobby ) = 0; // Called when a lobby becomes a peer + virtual void FailedGameMigration( idLobby & lobby ) = 0; + virtual void MigrationEnded( idLobby & lobby ) = 0; + + virtual void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) = 0; + + virtual uint32 GetSessionOptions() = 0; + virtual bool AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const = 0; + + virtual idSession::sessionState_t GetState() const = 0; + + virtual void ClearMigrationState() = 0; + // Called when the lobby receives a RELIABLE_ENDMATCH msg + virtual void EndMatchInternal( bool premature=false ) = 0; + + // Called when the game lobby receives leaderboard stats + virtual void RecvLeaderboardStats( idBitMsg & msg ) = 0; + + // Called once the lobby received its first full snap (used to advance from LOADING to INGAME state) + virtual void ReceivedFullSnap() = 0; + + // Called when lobby received RELIABLE_PARTY_LEAVE_GAME_LOBBY msg + virtual void LeaveGameLobby() = 0; + + virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) = 0; + virtual bool PreMigrateInvite( idLobby & lobby ) = 0; + + virtual void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) = 0; + + // ConnectAndMoveToLobby is called when the lobby receives a RELIABLE_CONNECT_AND_MOVE_TO_LOBBY + virtual void ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk ) = 0; + + virtual class idVoiceChatMgr * GetVoiceChat() = 0; + + virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) = 0; + virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) = 0; + + virtual void HandlePeerMatchParamUpdate( int peer, int msg ) = 0; + + virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; + virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; + virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; + virtual void DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) = 0; +}; diff --git a/neo/sys/sys_lobby_backend.h b/neo/sys/sys_lobby_backend.h new file mode 100644 index 00000000..87e04067 --- /dev/null +++ b/neo/sys/sys_lobby_backend.h @@ -0,0 +1,280 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_LOBBY_BACKEND_H__ +#define __SYS_LOBBY_BACKEND_H__ + + +extern idCVar net_verboseResource; +#define NET_VERBOSERESOURCE_PRINT if ( net_verboseResource.GetBool() ) idLib::Printf + +extern idCVar net_verbose; +#define NET_VERBOSE_PRINT if ( net_verbose.GetBool() ) idLib::Printf + +class lobbyAddress_t { +public: + lobbyAddress_t(); + + void InitFromNetadr( const netadr_t & netadr ); + + void InitFromIPandPort( const char * ip, int port ); + + const char * ToString() const; + bool UsingRelay() const; + bool Compare( const lobbyAddress_t & addr, bool ignoreSessionCheck = false ) const; + void WriteToMsg( idBitMsg & msg ) const; + void ReadFromMsg( idBitMsg & msg ); + + // IP address + netadr_t netAddr; +}; + +struct lobbyConnectInfo_t { +public: + void WriteToMsg( idBitMsg & msg ) const { + msg.WriteNetadr( netAddr ); + } + void ReadFromMsg( idBitMsg & msg ) { + msg.ReadNetadr( &netAddr ); + } + lobbyConnectInfo_t() : netAddr() { } + + netadr_t netAddr; +}; + +class idNetSessionPort { +public: + idNetSessionPort(); + + bool InitPort( int portNumber, bool useBackend ); + bool ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize ); + void SendRawPacket( const lobbyAddress_t & to, const void * data, int size ); + + bool IsOpen(); + void Close(); + +private: + float forcePacketDropCurr; // Used with net_forceDrop and net_forceDropCorrelation + float forcePacketDropPrev; + + idUDP UDP; +}; + +struct lobbyUser_t { + static const int INVALID_PING = 9999; + // gamertags can be up to 16 4-byte characters + \0 + static const int MAX_GAMERTAG = 64 + 1; + + lobbyUser_t() { + isBot = false; + peerIndex = -1; + disconnecting = false; + level = 1; + pingMs = INVALID_PING; + teamNumber = 0; + arbitrationAcked = false; + partyToken = 0; + + selectedSkin = 0; + weaponAutoSwitch = true; + weaponAutoReload = true; + + migrationGameData = -1; + } + + // Common variables + bool isBot; // true if lobbyUser is a bot. + int peerIndex; // peer number on host + lobbyUserID_t lobbyUserID; // Locally generated to be unique, and internally keeps the local user handle + char gamertag[MAX_GAMERTAG]; + int pingMs; // round trip time in milliseconds + + bool disconnecting; // true if we've sent a msg to disconnect this user from the session + int level; + int teamNumber; + uint32 partyToken; // set by the server when people join as a party + + int selectedSkin; + bool weaponAutoSwitch; + bool weaponAutoReload; + + bool arbitrationAcked; // if the user is verified for arbitration + + lobbyAddress_t address; + + int migrationGameData; // index into the local migration gamedata array that is associated with this user. -1=no migration game data available + + // Platform variables + + bool IsDisconnected() const { return lobbyUserID.IsValid() ? false : true; } + + void WriteToMsg( idBitMsg & msg ) { + address.WriteToMsg( msg ); + lobbyUserID.WriteToMsg( msg ); + msg.WriteLong( peerIndex ); + msg.WriteShort( pingMs ); + msg.WriteLong( partyToken ); + msg.WriteString( gamertag, MAX_GAMERTAG, false ); + WriteClientMutableData( msg ); + } + + void ReadFromMsg( idBitMsg & msg ) { + address.ReadFromMsg( msg ); + lobbyUserID.ReadFromMsg( msg ); + peerIndex = msg.ReadLong(); + pingMs = msg.ReadShort(); + partyToken = msg.ReadLong(); + msg.ReadString( gamertag, MAX_GAMERTAG ); + ReadClientMutableData( msg ); + } + + bool UpdateClientMutableData( const idLocalUser * localUser ); + + void WriteClientMutableData( idBitMsg & msg ) { + msg.WriteBits( selectedSkin, 4 ); + msg.WriteBits( teamNumber, 2 ); // We need two bits since we use team value of 2 for spectating + msg.WriteBool( weaponAutoSwitch ); + msg.WriteBool( weaponAutoReload ); + release_assert( msg.GetWriteBit() == 0 ); + } + + void ReadClientMutableData( idBitMsg & msg ) { + selectedSkin = msg.ReadBits( 4 ); + teamNumber = msg.ReadBits( 2 ); // We need two bits since we use team value of 2 for spectating + weaponAutoSwitch = msg.ReadBool(); + weaponAutoReload = msg.ReadBool(); + } +}; + +/* +================================================ +idLobbyBackend +This class interfaces with the various back ends for the different platforms +================================================ +*/ +class idLobbyBackend { +public: + enum lobbyBackendState_t { + STATE_INVALID = 0, + STATE_READY = 1, + STATE_CREATING = 2, // In the process of creating the lobby as a host + STATE_SEARCHING = 3, // In the process of searching for a lobby to join + STATE_OBTAINING_ADDRESS = 4, // In the process of obtaining the address of the lobby owner + STATE_ARBITRATING = 5, // Arbitrating + STATE_SHUTTING_DOWN = 6, // In the process of shutting down + STATE_SHUTDOWN = 7, // Was a host or peer at one point, now ready to be deleted + STATE_FAILED = 8, // Failure occurred + NUM_STATES + }; + + static const char * GetStateString( lobbyBackendState_t state_ ) { + static const char * stateToString[NUM_STATES] = { + "STATE_INVALID", + "STATE_READY", + "STATE_CREATING", + "STATE_SEARCHING", + "STATE_OBTAINING_ADDRESS", + "STATE_ARBITRATING", + "STATE_SHUTTING_DOWN", + "STATE_SHUTDOWN", + "STATE_FAILED" + }; + + return stateToString[ state_ ]; + } + + enum lobbyBackendType_t { + TYPE_PARTY = 0, + TYPE_GAME = 1, + TYPE_GAME_STATE = 2, + TYPE_INVALID = 0xff, + }; + + idLobbyBackend() : type( TYPE_INVALID ), isHost( false ), isLocal( false ) {} + idLobbyBackend( lobbyBackendType_t lobbyType ) : type( lobbyType ), isHost( false ), isLocal( false ) {} + + virtual void StartHosting( const idMatchParameters & p, float skillLevel, lobbyBackendType_t type ) = 0; + virtual void StartFinding( const idMatchParameters & p, int numPartyUsers, float skillLevel ) = 0; + virtual void JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo ) = 0; + virtual void GetSearchResults( idList< lobbyConnectInfo_t > & searchResults ) = 0; + virtual lobbyConnectInfo_t GetConnectInfo() = 0; + virtual void FillMsgWithPostConnectInfo( idBitMsg & msg ) = 0; // Passed itno PostConnectFromMsg + virtual void PostConnectFromMsg( idBitMsg & msg ) = 0; // Uses results from FillMsgWithPostConnectInfo + virtual bool IsOwnerOfConnectInfo( const lobbyConnectInfo_t & connectInfo ) const { return false; } + virtual void Shutdown() = 0; + virtual void GetOwnerAddress( lobbyAddress_t & outAddr ) = 0; + virtual bool IsHost() { return isHost; } + virtual void SetIsJoinable( bool joinable ) {} + virtual void Pump() = 0; + virtual void UpdateMatchParms( const idMatchParameters & p ) = 0; + virtual void UpdateLobbySkill( float lobbySkill ) = 0; + virtual void SetInGame( bool value ) {} + + virtual lobbyBackendState_t GetState() = 0; + virtual bool IsLocal() const { return isLocal; } + virtual bool IsOnline() const { return !isLocal; } + + virtual bool StartArbitration() { return false; } + virtual void Arbitrate() {} + virtual void VerifyArbitration() {} + virtual bool UserArbitrated( lobbyUser_t * user ) { return false; } + + virtual void RegisterUser( lobbyUser_t * user, bool isLocal ) {} + virtual void UnregisterUser( lobbyUser_t * user, bool isLocal ) {} + + virtual void StartSession() {} + virtual void EndSession() {} + virtual bool IsSessionStarted() { return false; } + virtual void FlushStats() {} + + virtual void BecomeHost( int numInvites ) {} // Become the host of this lobby + virtual void RegisterAddress( lobbyAddress_t & address ) {} // Called after becoming a new host, to register old addresses to send invites to + virtual void FinishBecomeHost() {} + + void SetLobbyType( lobbyBackendType_t lobbyType ) { type = lobbyType; } + lobbyBackendType_t GetLobbyType() const { return type; } + const char * GetLobbyTypeString() const { return ( GetLobbyType() == TYPE_PARTY ) ? "Party" : "Game"; } + + bool IsRanked() { return MatchTypeIsRanked( parms.matchFlags ); } + bool IsPrivate() { return MatchTypeIsPrivate( parms.matchFlags ); } + +protected: + lobbyBackendType_t type; + idMatchParameters parms; + bool isLocal; // True if this lobby is restricted to local play only (won't need and can't connect to online lobbies) + bool isHost; // True if we created this lobby +}; + +class idLobbyToSessionCB { +public: + virtual class idLobbyBackend * GetLobbyBackend( idLobbyBackend::lobbyBackendType_t type ) const = 0; + virtual bool CanJoinLocalHost() const = 0; + + // Ugh, hate having to ifdef these, but we're doing some fairly platform specific callbacks +}; + +#endif // __SYS_LOBBY_BACKEND_H__ diff --git a/neo/sys/sys_lobby_backend_direct.cpp b/neo/sys/sys_lobby_backend_direct.cpp new file mode 100644 index 00000000..d2818c13 --- /dev/null +++ b/neo/sys/sys_lobby_backend_direct.cpp @@ -0,0 +1,226 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_lobby_backend.h" +#include "sys_lobby_backend_direct.h" + +extern idCVar net_port; + +extern idLobbyToSessionCB * lobbyToSessionCB; + +/* +======================== +idLobbyBackendDirect::idLobbyBackendWin +======================== +*/ +idLobbyBackendDirect::idLobbyBackendDirect() { + state = STATE_INVALID; +} + +/* +======================== +idLobbyBackendDirect::StartHosting +======================== +*/ +void idLobbyBackendDirect::StartHosting( const idMatchParameters & p, float skillLevel, lobbyBackendType_t type ) { + NET_VERBOSE_PRINT( "idLobbyBackendDirect::StartHosting\n" ); + + isLocal = MatchTypeIsLocal( p.matchFlags ); + isHost = true; + + state = STATE_READY; + isLocal = true; +} + +/* +======================== +idLobbyBackendDirect::StartFinding +======================== +*/ +void idLobbyBackendDirect::StartFinding( const idMatchParameters & p, int numPartyUsers, float skillLevel ) { + isLocal = MatchTypeIsLocal( p.matchFlags ); + isHost = false; + + if ( lobbyToSessionCB->CanJoinLocalHost() ) { + state = STATE_READY; + } else { + state = STATE_FAILED; + } +} + +/* +======================== +idLobbyBackendDirect::GetSearchResults +======================== +*/ +void idLobbyBackendDirect::GetSearchResults( idList< lobbyConnectInfo_t > & searchResults ) { + lobbyConnectInfo_t fakeResult; + searchResults.Clear(); + searchResults.Append( fakeResult ); +} + +/* +======================== +idLobbyBackendDirect::JoinFromConnectInfo +======================== +*/ +void idLobbyBackendDirect::JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo ) { + if ( lobbyToSessionCB->CanJoinLocalHost() ) { + Sys_StringToNetAdr( "localhost", &address, true ); + address.port = net_port.GetInteger(); + } else { + address = connectInfo.netAddr; + } + + state = STATE_READY; + isLocal = false; + isHost = false; +} + +/* +======================== +idLobbyBackendDirect::Shutdown +======================== +*/ +void idLobbyBackendDirect::Shutdown() { + state = STATE_SHUTDOWN; +} + +/* +======================== +idLobbyBackendDirect::BecomeHost +======================== +*/ +void idLobbyBackendDirect::BecomeHost( int numInvites ) { +} + +/* +======================== +idLobbyBackendDirect::FinishBecomeHost +======================== +*/ +void idLobbyBackendDirect::FinishBecomeHost() { + isHost = true; +} + +/* +======================== +idLobbyBackendDirect::GetOwnerAddress +======================== +*/ +void idLobbyBackendDirect::GetOwnerAddress( lobbyAddress_t & outAddr ) { + outAddr.netAddr = address; + state = STATE_READY; +} + +/* +======================== +idLobbyBackendDirect::SetIsJoinable +======================== +*/ +void idLobbyBackendDirect::SetIsJoinable( bool joinable ) { +} + +/* +======================== +idLobbyBackendDirect::GetConnectInfo +======================== +*/ +lobbyConnectInfo_t idLobbyBackendDirect::GetConnectInfo() { + lobbyConnectInfo_t connectInfo; + + // If we aren't the host, this lobby should have been joined through JoinFromConnectInfo + if ( IsHost() ) { + // If we are the host, give them our ip address + const char * ip = Sys_GetLocalIP( 0 ); + Sys_StringToNetAdr( ip, &address, false ); + address.port = net_port.GetInteger(); + } + + connectInfo.netAddr = address; + + return connectInfo; +} + +/* +======================== +idLobbyBackendDirect::IsOwnerOfConnectInfo +======================== +*/ +bool idLobbyBackendDirect::IsOwnerOfConnectInfo( const lobbyConnectInfo_t & connectInfo ) const { + return Sys_CompareNetAdrBase( address, connectInfo.netAddr ); +} + +/* +======================== +idLobbyBackendDirect::Pump +======================== +*/ +void idLobbyBackendDirect::Pump() { +} + +/* +======================== +idLobbyBackendDirect::UpdateMatchParms +======================== +*/ +void idLobbyBackendDirect::UpdateMatchParms( const idMatchParameters & p ) { +} + +/* +======================== +idLobbyBackendDirect::UpdateLobbySkill +======================== +*/ +void idLobbyBackendDirect::UpdateLobbySkill( float lobbySkill ) { +} + +/* +======================== +idLobbyBackendDirect::SetInGame +======================== +*/ +void idLobbyBackendDirect::SetInGame( bool value ) { +} + +/* +======================== +idLobbyBackendDirect::RegisterUser +======================== +*/ +void idLobbyBackendDirect::RegisterUser( lobbyUser_t * user, bool isLocal ) { +} + +/* +======================== +idLobbyBackendDirect::UnregisterUser +======================== +*/ +void idLobbyBackendDirect::UnregisterUser( lobbyUser_t * user, bool isLocal ) { +} diff --git a/neo/sys/sys_lobby_backend_direct.h b/neo/sys/sys_lobby_backend_direct.h new file mode 100644 index 00000000..f170e673 --- /dev/null +++ b/neo/sys/sys_lobby_backend_direct.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_LOBBY_BACKEND_DIRECT_H__ +#define __SYS_LOBBY_BACKEND_DIRECT_H__ + +/* +======================== +idLobbyBackendDirect +======================== +*/ +class idLobbyBackendDirect : public idLobbyBackend { +public: + idLobbyBackendDirect(); + + // idLobbyBackend interface + virtual void StartHosting( const idMatchParameters & p, float skillLevel, lobbyBackendType_t type ); + virtual void StartFinding( const idMatchParameters & p, int numPartyUsers, float skillLevel ); + virtual void JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo ); + virtual void GetSearchResults( idList< lobbyConnectInfo_t > & searchResults ); + virtual void FillMsgWithPostConnectInfo( idBitMsg & msg ) {} + virtual void PostConnectFromMsg( idBitMsg & msg ) {} + virtual void Shutdown(); + virtual void GetOwnerAddress( lobbyAddress_t & outAddr ); + virtual void SetIsJoinable( bool joinable ); + virtual lobbyConnectInfo_t GetConnectInfo(); + virtual bool IsOwnerOfConnectInfo( const lobbyConnectInfo_t & connectInfo ) const; + virtual void Pump(); + virtual void UpdateMatchParms( const idMatchParameters & p ); + virtual void UpdateLobbySkill( float lobbySkill ); + virtual void SetInGame( bool value ); + virtual lobbyBackendState_t GetState() { return state; } + + virtual void BecomeHost( int numInvites ); + virtual void FinishBecomeHost(); + + virtual void RegisterUser( lobbyUser_t * user, bool isLocal ); + virtual void UnregisterUser( lobbyUser_t * user, bool isLocal ); + +private: + + lobbyBackendState_t state; + netadr_t address; +}; + +#endif // __SYS_LOBBY_BACKEND_DIRECT_H__ diff --git a/neo/sys/sys_lobby_migrate.cpp b/neo/sys/sys_lobby_migrate.cpp new file mode 100644 index 00000000..99a0a102 --- /dev/null +++ b/neo/sys/sys_lobby_migrate.cpp @@ -0,0 +1,546 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_lobby.h" + +idCVar net_migration_debug( "net_migration_debug", "0", CVAR_BOOL, "debug" ); +idCVar net_migration_disable( "net_migration_disable", "0", CVAR_BOOL, "debug" ); +idCVar net_migration_forcePeerAsHost( "net_migration_forcePeerAsHost", "-1", CVAR_INTEGER, "When set to >-1, it forces that peer number to be the new host during migration" ); + + +/* +======================== +idLobby::IsBetterHost +======================== +*/ +bool idLobby::IsBetterHost( int ping1, lobbyUserID_t userId1, int ping2, lobbyUserID_t userId2 ) { + if ( lobbyType == TYPE_PARTY ) { + return userId1 < userId2; // Only use user id for party, since ping doesn't matter + } + + if ( ping1 < ping2 ) { + // Better ping wins + return true; + } else if ( ping1 == ping2 && userId1 < userId2 ) { + // User id is tie breaker + return true; + } + + return false; +} + +/* +======================== +idLobby::FindMigrationInviteIndex +======================== +*/ +int idLobby::FindMigrationInviteIndex( lobbyAddress_t & address ) { + if ( migrationInfo.state == MIGRATE_NONE ) { + return -1; + } + + for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) { + if ( migrationInfo.invites[i].address.Compare( address, true ) ) { + return i; + } + } + + return -1; +} + +/* +======================== +idLobby::UpdateHostMigration +======================== +*/ +void idLobby::UpdateHostMigration() { + + int time = Sys_Milliseconds(); + + // If we are picking a new host, then update that + if ( migrationInfo.state == MIGRATE_PICKING_HOST ) { + const int MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS = 20; // FIXME: set back to 5 // Give other hosts 5 seconds + + if ( time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS", MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS ) * 1000 ) { + // Just become the host if we haven't heard from a host in awhile + BecomeHost(); + } else { + return; + } + } + + // See if we are a new migrated host that needs to invite the original members back + if ( migrationInfo.state != MIGRATE_BECOMING_HOST ) { + return; + } + + if ( lobbyBackend == NULL || lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { + return; + } + + if ( state != STATE_IDLE ) { + return; + } + + if ( !IsHost() ) { + return; + } + + const int MIGRATION_TIMEOUT_IN_SECONDS = 30; // FIXME: setting to 30 for dev purposes. 10 seems more reasonable. Need to make unloading game / loading lobby async + const int MIGRATION_INVITE_TIME_IN_SECONDS = 2; + + if ( migrationInfo.invites.Num() == 0 || time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_TIMEOUT_IN_SECONDS", MIGRATION_TIMEOUT_IN_SECONDS ) * 1000 ) { + // Either everyone acked, or we timed out, just keep who we have, and stop sending invites + EndMigration(); + return; + } + + // Send invites to anyone who hasn't responded + for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) { + if ( time - migrationInfo.invites[i].lastInviteTime < session->GetTitleStorageInt( "MIGRATION_INVITE_TIME_IN_SECONDS", MIGRATION_INVITE_TIME_IN_SECONDS ) * 1000 ) { + continue; // Not enough time passed + } + + // Mark the time + migrationInfo.invites[i].lastInviteTime = time; + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg outmsg( buffer, sizeof( buffer ) ); + + // Have lobbyBackend fill out msg with connection info + lobbyConnectInfo_t connectInfo = lobbyBackend->GetConnectInfo(); + connectInfo.WriteToMsg( outmsg ); + + // Let them know whether or not this was from in game + outmsg.WriteBool( migrationInfo.persistUntilGameEndsData.wasMigratedGame ); + + NET_VERBOSE_PRINT( "NET: Sending migration invite to %s\n", migrationInfo.invites[i].address.ToString() ); + + // Send the migration invite + SendConnectionLess( migrationInfo.invites[i].address, OOB_MIGRATE_INVITE, outmsg.GetReadData(), outmsg.GetSize() ); + } +} + +/* +======================== +idLobby::BuildMigrationInviteList +======================== +*/ +void idLobby::BuildMigrationInviteList( bool inviteOldHost ) { + migrationInfo.invites.Clear(); + + // Build a list of addresses we will send invites to (gather all unique remote addresses from the session user list) + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * user = GetLobbyUser( i ); + + if ( !verify( user != NULL ) ) { + continue; + } + + if ( user->IsDisconnected() ) { + continue; + } + + if ( IsSessionUserIndexLocal( i ) ) { + migrationInfo.ourPingMs = user->pingMs; + migrationInfo.ourUserId = user->lobbyUserID; + migrationInfo.persistUntilGameEndsData.ourGameData = user->migrationGameData; + NET_VERBOSE_PRINT( "^2NET: Migration game data for local user is index %d \n", user->migrationGameData ); + + continue; // Only interested in remote users + } + + if ( !inviteOldHost && user->peerIndex == -1 ) { + continue; // Don't invite old host if told not to do so + } + + if ( FindMigrationInviteIndex( user->address ) == -1 ) { + migrationInvite_t invite; + invite.address = user->address; + invite.pingMs = user->pingMs; + invite.userId = user->lobbyUserID; + invite.migrationGameData = user->migrationGameData; + invite.lastInviteTime = 0; + + NET_VERBOSE_PRINT( "^2NET: Migration game data for user %s is index %d \n", user->gamertag, user->migrationGameData ); + + migrationInfo.invites.Append( invite ); + } + } +} + +/* +======================== +idLobby::PickNewHost +======================== +*/ +void idLobby::PickNewHost( bool forceMe, bool inviteOldHost ) { + if ( IsHost() ) { + idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() ); + return; + } + + sessionCB->PrePickNewHost( *this, forceMe, inviteOldHost ); +} + +/* +======================== +idLobby::PickNewHostInternal +======================== +*/ +void idLobby::PickNewHostInternal( bool forceMe, bool inviteOldHost ) { + + if ( migrationInfo.state == MIGRATE_PICKING_HOST ) { + return; // Already picking new host + } + + idLib::Printf( "PickNewHost: Started picking new host %s.\n", GetLobbyName() ); + + if ( IsHost() ) { + idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() ); + return; + } + + // Find the user with the lowest ping + int bestUserIndex = -1; + int bestPingMs = 0; + lobbyUserID_t bestUserId; + + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * user = GetLobbyUser( i ); + + if ( !verify( user != NULL ) ) { + continue; + } + + if ( user->IsDisconnected() ) { + continue; + } + + if ( user->peerIndex == -1 ) { + continue; // Don't try and pick old host + } + + if ( bestUserIndex == -1 || IsBetterHost( user->pingMs, user->lobbyUserID, bestPingMs, bestUserId ) ) { + bestUserIndex = i; + bestPingMs = user->pingMs; + bestUserId = user->lobbyUserID; + } + + if ( user->peerIndex == net_migration_forcePeerAsHost.GetInteger() ) { + bestUserIndex = i; + bestPingMs = user->pingMs; + bestUserId = user->lobbyUserID; + break; + } + } + + // Remember when we first started picking a new host + migrationInfo.state = MIGRATE_PICKING_HOST; + migrationInfo.migrationStartTime = Sys_Milliseconds(); + + migrationInfo.persistUntilGameEndsData.wasMigratedGame = sessionCB->GetState() == idSession::INGAME; + + if ( bestUserIndex == -1 ) { // This can happen if we call PickNewHost on an lobby that was Shutdown + NET_VERBOSE_PRINT( "MIGRATION: PickNewHost was called on an lobby that was Shutdown\n" ); + BecomeHost(); + return; + } + + NET_VERBOSE_PRINT( "MIGRATION: Chose user index %d (%s) for new host\n", bestUserIndex, GetLobbyUser( bestUserIndex )->gamertag ); + + bool bestWasLocal = IsSessionUserIndexLocal( bestUserIndex ); // Check before shutting down the lobby + migrateMsgFlags = parms.matchFlags; // Save off match parms + + // Build invite list + BuildMigrationInviteList( inviteOldHost ); + + // If the best user is on this machine, then we become the host now, otherwise, wait for a new host to contact us + if ( forceMe || bestWasLocal ) { + BecomeHost(); + } +} + +/* +======================== +idLobby::BecomeHost +======================== +*/ +void idLobby::BecomeHost() { + + if ( !verify( migrationInfo.state == MIGRATE_PICKING_HOST ) ) { + idLib::Printf( "BecomeHost: Must be called from PickNewHost.\n" ); + EndMigration(); + return; + } + + if ( IsHost() ) { + idLib::Printf( "BecomeHost: Already host of session.\n" ); + EndMigration(); + return; + } + + if ( !sessionCB->BecomingHost( *this ) ) { + EndMigration(); + return; + } + + idLib::Printf( "BecomeHost: Sending %i invites on %s.\n", migrationInfo.invites.Num(), GetLobbyName() ); + + migrationInfo.state = MIGRATE_BECOMING_HOST; + migrationInfo.migrationStartTime = Sys_Milliseconds(); + + if ( lobbyBackend == NULL ) { + // If we don't have a lobbyBackend, then just create one + Shutdown(); + StartCreating(); + return; + } + + // Shutdown the current lobby, but keep the lobbyBackend (we'll migrate it) + Shutdown( true ); + + // Migrate the lobbyBackend to host + lobbyBackend->BecomeHost( migrationInfo.invites.Num() ); + + // Wait for it to complete + SetState( STATE_CREATE_LOBBY_BACKEND ); +} + +/* +======================== +idLobby::EndMigration +This gets called when we are done migrating, and invites will no longer be sent out. +======================== +*/ +void idLobby::EndMigration() { + if ( migrationInfo.state == MIGRATE_NONE ) { + idLib::Printf( "idSessionLocal::EndMigration: Not migrating.\n" ); + return; + } + + sessionCB->MigrationEnded( *this ); + + if ( lobbyBackend != NULL ) { + lobbyBackend->FinishBecomeHost(); + } + + migrationInfo.state = MIGRATE_NONE; + migrationInfo.invites.Clear(); +} + +/* +======================== +idLobby::ResetAllMigrationState +This will reset all state related to host migration. Should be called +at match end so our next game is not treated as a migrated game +======================== +*/ +void idLobby::ResetAllMigrationState() { + migrationInfo.state = MIGRATE_NONE; + migrationInfo.invites.Clear(); + migrationInfo.persistUntilGameEndsData.Clear(); + + migrateMsgFlags = 0; + + common->Dialog().ClearDialog( GDM_MIGRATING ); + common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); + common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); +} + +/* +======================== +idLobby::GetMigrationGameData +This will setup the passed in idBitMsg to either read or write from the global migration game data buffer +======================== +*/ +bool idLobby::GetMigrationGameData( idBitMsg &msg, bool reading ) { + if ( reading ) { + if ( !IsMigratedStatsGame() || !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) { + // This was not a migrated session, we have no migration data + return false; + } + msg.InitRead( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) ); + } else { + migrationInfo.persistUntilGameEndsData.hasGameData = true; + memset( migrationInfo.persistUntilGameEndsData.gameData, 0, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) ); + msg.InitWrite( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) ); + } + + return true; +} + +/* +======================== +idLobby::GetMigrationGameDataUser +This will setup the passed in idBitMsg to either read or write from the user's migration game data buffer +======================== +*/ +bool idLobby::GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) { + const int userNum = GetLobbyUserIndexByID( lobbyUserID ); + + if ( !verify( userNum >=0 && userNum < MAX_PLAYERS ) ) { + return false; + } + + lobbyUser_t * u = GetLobbyUser( userNum ); + if ( u != NULL ) { + if ( reading ) { + + if ( !IsMigratedStatsGame() || !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) { + // This was not a migrated session, we have no migration data + return false; + } + + if ( u->migrationGameData >= 0 && u->migrationGameData < MAX_PLAYERS ) { + msg.InitRead( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ 0 ] ) ); + } else { + // We don't have migration data for this user + idLib::Warning( "No migration data for user %d in a migrated game (%d)", userNum, u->migrationGameData ); + return false; + } + } else { + // Writing + migrationInfo.persistUntilGameEndsData.hasGameData = true; + u->migrationGameData = userNum; + memset( migrationInfo.persistUntilGameEndsData.gameDataUser[ userNum ], 0, sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[0] ) ); + msg.InitWrite( migrationInfo.persistUntilGameEndsData.gameDataUser[ userNum ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[0] ) ); + + } + return true; + } + return false; +} + +/* +======================== +idLobby::HandleMigrationGameData +======================== +*/ +void idLobby::HandleMigrationGameData( idBitMsg & msg ) { + // Receives game migration data from the server. Just save off the raw data. If we ever become host we'll let the game code read + // that chunk in (we can't do anything with it now anyways: we don't have entities or any server code to read it in to) + migrationInfo.persistUntilGameEndsData.hasGameData = true; + + // Reset each user's migration game data. If we don't receive new data for them in this msg, we don't want to use the old data + for ( int i=0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * u = GetLobbyUser( i ); + if ( u != NULL ) { + u->migrationGameData = -1; + } + } + + msg.ReadData( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) ); + int numUsers = msg.ReadByte(); + int dataIndex=0; + for ( int i=0; i < numUsers; i++ ) { + lobbyUserID_t lobbyUserID; + lobbyUserID.ReadFromMsg( msg ); + lobbyUser_t * user = GetLobbyUser( GetLobbyUserIndexByID( lobbyUserID ) ); + if ( user != NULL ) { + + NET_VERBOSE_PRINT( "NET: Got migration data[%d] for user %s\n", dataIndex, user->gamertag ); + + user->migrationGameData = dataIndex; + msg.ReadData( migrationInfo.persistUntilGameEndsData.gameDataUser[ dataIndex ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ dataIndex ] ) ); + dataIndex++; + } + } +} + +/* +======================== +idLobby::SendMigrationGameData +======================== +*/ +void idLobby::SendMigrationGameData() { + if ( net_migration_disable.GetBool() ) { + return; + } + + if ( sessionCB->GetState() != idSession::INGAME ) { + return; + } + + if ( !migrationInfo.persistUntilGameEndsData.hasGameData ) { + // Haven't been given any migration game data yet + return; + } + + const int now = Sys_Milliseconds(); + if ( nextSendMigrationGameTime > now ) { + return; + } + + byte packetData[ idPacketProcessor::MAX_MSG_SIZE ]; + idBitMsg msg( packetData, sizeof(packetData) ); + + // Write global data + msg.WriteData( &migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) ); + msg.WriteByte( GetNumLobbyUsers() ); + + // Write user data + for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { + lobbyUser_t * u = GetLobbyUser( userIndex ); + if ( u->IsDisconnected() || u->migrationGameData < 0 ) { + continue; + } + + u->lobbyUserID.WriteToMsg( msg ); + msg.WriteData( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ] ) ); + } + + // Send to 1 peer + for ( int i=0; i < peers.Num(); i++ ) { + int peerToSend = ( nextSendMigrationGamePeer + i ) % peers.Num(); + + if ( peers[ peerToSend ].IsConnected() && peers[ peerToSend ].loaded ) { + if ( peers[ peerToSend ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) { + // This is kind of a hack for development so we don't DC clients by sending them too many reliable migration messages + // when they aren't responding. Doesn't seem like a horrible thing to have in a shipping product but is not necessary. + NET_VERBOSE_PRINT("NET: Skipping reliable game migration data msg because client reliable queue is > half full\n"); + + } else { + if ( net_migration_debug.GetBool() ) { + idLib::Printf( "NET: Sending migration game data to peer %d. size: %d\n", peerToSend, msg.GetSize() ); + } + QueueReliableMessage( peerToSend, RELIABLE_MIGRATION_GAME_DATA, msg.GetReadData(), msg.GetSize() ); + } + break; + } + } + + // Increment next send time / next send peer + nextSendMigrationGamePeer++; + if ( nextSendMigrationGamePeer >= peers.Num() ) { + nextSendMigrationGamePeer = 0; + } + + nextSendMigrationGameTime = now + MIGRATION_GAME_DATA_INTERVAL_MS; +} \ No newline at end of file diff --git a/neo/sys/sys_lobby_snapshot.cpp b/neo/sys/sys_lobby_snapshot.cpp new file mode 100644 index 00000000..e6335e1b --- /dev/null +++ b/neo/sys/sys_lobby_snapshot.cpp @@ -0,0 +1,833 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_lobby.h" + +idCVar net_snapshot_send_warntime( "net_snapshot_send_warntime", "500", CVAR_INTEGER, "Print warning messages if we take longer than this to send a client a snapshot." ); + +idCVar net_queueSnapAcks( "net_queueSnapAcks", "1", CVAR_BOOL, "" ); + +idCVar net_peer_throttle_mode( "net_peer_throttle_mode", "0", CVAR_INTEGER, "= 0 off, 1 = enable fixed, 2 = absolute, 3 = both" ); + +idCVar net_peer_throttle_minSnapSeq( "net_peer_throttle_minSnapSeq", "150", CVAR_INTEGER, "Minumum number of snapshot exchanges before throttling can be triggered" ); + +idCVar net_peer_throttle_bps_peer_threshold_pct( "net_peer_throttle_bps_peer_threshold_pct", "0.60", CVAR_FLOAT, "Min reported incoming bps % of sent from host that a peer must maintain before throttling kicks in" ); +idCVar net_peer_throttle_bps_host_threshold( "net_peer_throttle_bps_host_threshold", "1024", CVAR_FLOAT, "Min outgoing bps of host for bps based throttling to be considered" ); + +idCVar net_peer_throttle_bps_decay( "net_peer_throttle_bps_decay", "0.25f", CVAR_FLOAT, "If peer exceeds this number of queued snap deltas, then throttle his effective snap rate" ); +idCVar net_peer_throttle_bps_duration( "net_peer_throttle_bps_duration", "3000", CVAR_INTEGER, "If peer exceeds this number of queued snap deltas, then throttle his effective snap rate" ); + +idCVar net_peer_throttle_maxSnapRate( "net_peer_throttle_maxSnapRate", "4", CVAR_INTEGER, "Highest factor of server base snapRate that a client can be throttled" ); + +idCVar net_snap_bw_test_throttle_max_scale( "net_snap_bw_test_throttle_max_scale", "0.80", CVAR_FLOAT, "When clamping bandwidth to reported values, scale reported value by this" ); + +idCVar net_snap_redundant_resend_in_ms( "net_snap_redundant_resend_in_ms", "800", CVAR_INTEGER, "Delay between redundantly sending snaps during initial snap exchange" ); +idCVar net_min_ping_in_ms( "net_min_ping_in_ms", "1500", CVAR_INTEGER, "Ping has to be higher than this before we consider throttling to recover" ); +idCVar net_pingIncPercentBeforeRecover( "net_pingIncPercentBeforeRecover", "1.3", CVAR_FLOAT, "Percentage change increase of ping before we try to recover" ); +idCVar net_maxFailedPingRecoveries( "net_maxFailedPingRecoveries", "10", CVAR_INTEGER, "Max failed ping recoveries before we stop trying" ); +idCVar net_pingRecoveryThrottleTimeInSeconds( "net_pingRecoveryThrottleTimeInSeconds", "3", CVAR_INTEGER, "Throttle snaps for this amount of time in seconds to recover from ping spike" ); + +idCVar net_peer_timeout_loading( "net_peer_timeout_loading", "90000", CVAR_INTEGER, "time in MS to disconnect clients during loading - production only" ); + + +/* +======================== +idLobby::UpdateSnaps +======================== +*/ +void idLobby::UpdateSnaps() { + + assert( lobbyType == GetActingGameStateLobbyType() ); + + SCOPED_PROFILE_EVENT( "UpdateSnaps" ); + +#if 0 + uint64 startTimeMicroSec = Sys_Microseconds(); +#endif + + haveSubmittedSnaps = false; + + if ( !SendCompletedSnaps() ) { + // If we weren't able to send all the submitted snaps, we need to wait till we can. + // We can't start new jobs until they are all sent out. + return; + } + + for ( int p = 0; p < peers.Num(); p++ ) { + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + continue; + } + + if ( peer.needToSubmitPendingSnap ) { + // Submit the snap + if ( SubmitPendingSnap( p ) ) { + peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap + } + + } + } + +#if 0 + uint64 endTimeMicroSec = Sys_Microseconds(); + + if ( endTimeMicroSec - startTimeMicroSec > 200 ) { // .2 ms + idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", (float)( endTimeMicroSec - startTimeMicroSec ) / 1000.0f); + } +#endif +} + +/* +======================== +idLobby::SendCompletedSnaps +This function will send send off any previously submitted pending snaps if they are ready +======================== +*/ +bool idLobby::SendCompletedSnaps() { + assert( lobbyType == GetActingGameStateLobbyType() ); + + bool sentAllSubmitted = true; + + for ( int p = 0; p < peers.Num(); p++ ) { + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + continue; + } + + if ( peer.snapProc->PendingSnapReadyToSend() ) { + // Check to see if there are any snaps that were submitted that need to be sent out + SendCompletedPendingSnap( p ); + } else if ( IsHost() ) { + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 7, va(" ^8Peer %d pendingSnap not ready to send\n", p) ); + } + + if ( !peer.IsConnected() ) { // peer may have been dropped in "SendCompletedPendingSnap". ugh. + continue; + } + + if ( peer.snapProc->PendingSnapReadyToSend() ) { + // If we still have a submitted snap, we know we're not done + sentAllSubmitted = false; + if ( IsHost() ) { + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2Peer %d did not send all submitted snapshots.\n", p) ); + } + } + } + + return sentAllSubmitted; +} + +/* +======================== +idLobby::SendResources +======================== +*/ +bool idLobby::SendResources( int p ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + return false; +} + +/* +======================== +idLobby::SubmitPendingSnap +======================== +*/ +bool idLobby::SubmitPendingSnap( int p ) { + + assert( lobbyType == GetActingGameStateLobbyType() ); + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + return false; + } + + // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots + if ( SendResources( p ) ) { + return false; + } + + if ( !peer.loaded ) { + return false; + } + + if ( !peer.snapProc->HasPendingSnap() ) { + return false; + } + + int time = Sys_Milliseconds(); + + int timeFromLastSub = time - peer.lastSnapJobTime; + + int forceResendTime = session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ); + + if ( timeFromLastSub < forceResendTime && peer.snapProc->IsBusyConfirmingPartialSnap() ) { + return false; + } + + peer.lastSnapJobTime = time; + assert( !peer.snapProc->PendingSnapReadyToSend() ); + + // Submit snapshot delta to jobs + peer.snapProc->SubmitPendingSnap( p + 1, objMemory, SNAP_OBJ_JOB_MEMORY, lzwData ); + + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" Submitted snapshot to jobList for peer %d. Since last jobsub: %d\n", p, timeFromLastSub ) ); + + return true; +} + +/* +======================== +idLobby::SendCompletedPendingSnap +======================== +*/ +void idLobby::SendCompletedPendingSnap( int p ) { + + assert( lobbyType == GetActingGameStateLobbyType() ); + + int time = Sys_Milliseconds(); + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + return; + } + + if ( peer.snapProc == NULL || !peer.snapProc->PendingSnapReadyToSend() ) { + return; + } + + // If we have a pending snap ready to send, we better have a pending snap + assert( peer.snapProc->HasPendingSnap() ); + + // Get the snap data blob now, even if we don't send it. + // This is somewhat wasteful, but we have to do this to keep the snap job pipe ready to keep doing work + // If we don't do this, this peer will cause other peers to be starved of snapshots, when they may very well be ready to send a snap + byte buffer[ MAX_SNAP_SIZE ]; + int maxLength = sizeof( buffer ) - peer.packetProc->GetReliableDataSize() - 128; + + int size = peer.snapProc->GetPendingSnapDelta( buffer, maxLength ); + + if ( !CanSendMoreData( p ) ) { + return; + } + + // Can't send anymore snapshots until all fragments are sent + if ( peer.packetProc->HasMoreFragments() ) { + return; + } + + // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots + if ( SendResources( p ) ) { + return; + } + + int timeFromJobSub = time - peer.lastSnapJobTime; + int timeFromLastSend = time - peer.lastSnapTime; + + if ( timeFromLastSend > 0 ) { + peer.snapHz = 1000.0f / (float)timeFromLastSend; + } else { + peer.snapHz = 0.0f; + } + + if ( net_snapshot_send_warntime.GetInteger() > 0 && peer.lastSnapTime != 0 && net_snapshot_send_warntime.GetInteger() < timeFromLastSend ) { + idLib::Printf( "NET: Took %d ms to send peer %d snapshot\n", timeFromLastSend, p ); + } + + if ( peer.throttleSnapsForXSeconds != 0 ) { + if ( time < peer.throttleSnapsForXSeconds ) { + return; + } + + // If we were trying to recover ping, see if we succeeded + if ( peer.recoverPing != 0 ) { + if ( peer.lastPingRtt >= peer.recoverPing ) { + peer.failedPingRecoveries++; + } else { + const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() ); + if ( peer.snapProc->GetFullSnapBaseSequence() > idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) { + // If throttling recovered the ping + int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() ); + peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() ); + } + } + } + + peer.throttleSnapsForXSeconds = 0; + } + + peer.lastSnapTime = time; + + if ( size != 0 ) { + if ( size > 0 ) { + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 3, va("NET: (peer %d) Sending snapshot %d delta'd against %d. Since JobSub: %d Since LastSend: %d. Size: %d\n", p, peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence(), timeFromJobSub, timeFromLastSend, size ) ); + ProcessOutgoingMsg( p, buffer, size, false, 0 ); + } else if ( size < 0 ) { // Size < 0 indicates the delta buffer filled up + // There used to be code here that would disconnect peers if they were in game and filled up the buffer + // This was causing issues in the playtests we were running (Doom 4 MP) and after some conversation + // determined that it was not needed since a timeout mechanism has been added since + ProcessOutgoingMsg( p, buffer, -size, false, 0 ); + if ( peer.snapProc != NULL ) { + NET_VERBOSESNAPSHOT_PRINT( "NET: (peerNum: %d - name: %s) Resending last snapshot delta %d because his delta list filled up. Since JobSub: %d Since LastSend: %d Delta Size: %d\n", p, GetPeerName( p ), peer.snapProc->GetSnapSequence(), timeFromJobSub, timeFromLastSend, size ); + } + } + } + + // We calculate what our outgoing rate was for each sequence, so we can have a relative comparison + // for when the client reports what his downstream was in the same timeframe + if ( IsHost() && peer.snapProc != NULL && peer.snapProc->GetSnapSequence() > 0 ) { + //NET_VERBOSE_PRINT("^8 %i Rate: %.2f SnapSeq: %d GetBaseSequence: %d\n", lastAppendedSequence, peer.packetProc->GetOutgoingRateBytes(), peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence() ); + peer.sentBpsHistory[ peer.snapProc->GetSnapSequence() % MAX_BPS_HISTORY ] = peer.packetProc->GetOutgoingRateBytes(); + } +} + +/* +======================== +idLobby::CheckPeerThrottle +======================== +*/ +void idLobby::CheckPeerThrottle( int p ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + if ( !verify( p >= 0 && p < peers.Num() ) ) { + return; + } + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + return; + } + + if ( !IsHost() ) { + return; + } + + if ( session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ) == 0 ) { + return; + } + + if ( peer.receivedBps < 0.0f ) { + return; + } + + int time = Sys_Milliseconds(); + + if ( !AllPeersHaveBaseState() ) { + return; + } + + if ( verify( peer.snapProc != NULL ) ) { + const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() ); + if ( peer.snapProc->GetFullSnapBaseSequence() <= idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) { + return; + } + } + + // This is bps throttling which compares the sent bytes per second to the reported received bps + float peer_throttle_bps_host_threshold = session->GetTitleStorageFloat( "net_peer_throttle_bps_host_threshold", net_peer_throttle_bps_host_threshold.GetFloat() ); + + if ( peer_throttle_bps_host_threshold > 0.0f ) { + int deltaT = idMath::ClampInt( 0, 100, time - peer.receivedThrottleTime ); + if ( deltaT > 0 && peer.receivedThrottleTime > 0 && peer.receivedBpsIndex > 0 ) { + + bool throttled = false; + float sentBps = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ]; + + // Min outgoing rate from server (don't throttle if we are sending < 1k) + if ( sentBps > peer_throttle_bps_host_threshold ) { + float pct = peer.receivedBps / idMath::ClampFloat( 0.01f, static_cast( BANDWIDTH_REPORTING_MAX ), sentBps ); // note the receivedBps is implicitly clamped on client end to 10k/sec + + /* + static int lastSeq = 0; + if ( peer.receivedBpsIndex != lastSeq ) { + NET_VERBOSE_PRINT( "%ssentBpsHistory[%d] = %.2f received: %.2f PCT: %.2f \n", ( pct > 1.0f ? "^1" : "" ), peer.receivedBpsIndex, sentBps, peer.receivedBps, pct ); + } + lastSeq = peer.receivedBpsIndex; + */ + + // Increase throttle time if peer is < % of what we are sending him + if ( pct < session->GetTitleStorageFloat( "net_peer_throttle_bps_peer_threshold_pct", net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) ) { + peer.receivedThrottle += (float)deltaT; + throttled = true; + NET_VERBOSE_PRINT("NET: throttled... %.2f ....pct %.2f receivedBps %.2f outgoingBps %.2f, peer %i, seq %i\n", peer.receivedThrottle, pct, peer.receivedBps, sentBps, p, peer.snapProc->GetFullSnapBaseSequence() ); + } + } + + if ( !throttled ) { + float decayRate = session->GetTitleStorageFloat( "net_peer_throttle_bps_decay", net_peer_throttle_bps_decay.GetFloat() ); + + peer.receivedThrottle = Max( 0.0f, peer.receivedThrottle - ( ( (float)deltaT ) * decayRate ) ); + //NET_VERBOSE_PRINT("NET: !throttled... %.2f ....receivedBps %.2f outgoingBps %.2f\n", peer.receivedThrottle, peer.receivedBps, sentBps ); + } + + float duration = session->GetTitleStorageFloat( "net_peer_throttle_bps_duration", net_peer_throttle_bps_duration.GetFloat() ); + + if ( peer.receivedThrottle > duration ) { + peer.maxSnapBps = peer.receivedBps * session->GetTitleStorageFloat( "net_snap_bw_test_throttle_max_scale", net_snap_bw_test_throttle_max_scale.GetFloat() ); + + int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() ); + + if ( peer.throttledSnapRate == 0 ) { + peer.throttledSnapRate = common->GetSnapRate() * 2; + } else if ( peer.throttledSnapRate < maxRate ) { + peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() ); + } + + peer.receivedThrottle = 0.0f; // Start over, so we don't immediately throttle again + } + } + peer.receivedThrottleTime = time; + } +} + +/* +======================== +idLobby::ApplySnapshotDelta +======================== +*/ +void idLobby::ApplySnapshotDelta( int p, int snapshotNumber ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + if ( !verify( p >= 0 && p < peers.Num() ) ) { + return; + } + peer_t & peer = peers[p]; + if ( !peer.IsConnected() ) { + return; + } + + if ( net_queueSnapAcks.GetBool() && AllPeersHaveBaseState() ) { + // If we've reached our queue limit, force the oldest one out now + if ( snapDeltaAckQueue.Num() == snapDeltaAckQueue.Max() ) { + ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ); + snapDeltaAckQueue.RemoveIndex( 0 ); + } + + // Queue up acks, so we can spread them out over frames to lighten the load when they all come in at once + snapDeltaAck_t snapDeltaAck; + + snapDeltaAck.p = p; + snapDeltaAck.snapshotNumber = snapshotNumber; + + snapDeltaAckQueue.Append( snapDeltaAck ); + } else { + ApplySnapshotDeltaInternal( p, snapshotNumber ); + } +} + +/* +======================== +idLobby::ApplySnapshotDeltaInternal +======================== +*/ +bool idLobby::ApplySnapshotDeltaInternal( int p, int snapshotNumber ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + if ( !verify( p >= 0 && p < peers.Num() ) ) { + return false; + } + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + return false; + } + + // on the server, player = peer number + 1, this only works as long as we don't support clients joining and leaving during game + // on the client, always 0 + bool result = peer.snapProc->ApplySnapshotDelta( IsHost() ? p + 1 : 0, snapshotNumber ); + + if ( result && IsHost() && peer.snapProc->HasPendingSnap() ) { + // Send more of the pending snap if we have one for this peer. + // The reason we can do this, is because we know more about this peers base state now. + // And since we maxed out the optimal snap delta size, we'll now be able + // to send more data, since we assume we'll get better and better delta compression as + // our version of this peers base state approaches parity with the peers actual state. + + // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs. + peer.needToSubmitPendingSnap = true; + NET_VERBOSESNAPSHOT_PRINT( "NET: Sent more unsent snapshot data to peer %d for snapshot %d\n", p, snapshotNumber ); + } + + return result; +} + +/* +======================== +idLobby::SendSnapshotToPeer +======================== +*/ +idCVar net_forceDropSnap( "net_forceDropSnap", "0", CVAR_BOOL, "wait on snaps" ); +void idLobby::SendSnapshotToPeer( idSnapShot & ss, int p ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + peer_t & peer = peers[p]; + + if ( net_forceDropSnap.GetBool() ) { + net_forceDropSnap.SetBool( false ); + return; + } + + if ( peer.pauseSnapshots ) { + return; + } + + int time = Sys_Milliseconds(); + + const int throttleMode = session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ); + + // Real peer throttling based on performance + // -We throttle before sending to jobs rather than before sending + + if ( ( throttleMode == 1 || throttleMode == 3 ) && peer.throttledSnapRate > 0 ) { + if ( time - peer.lastSnapJobTime < peer.throttledSnapRate / 1000 ) { // fixme /1000 + // This peer is throttled, skip his snap shot + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Throttling peer %d.Skipping snapshot. Time elapsed: %d peer snap rate: %d\n", p, ( time - peer.lastSnapJobTime ), peer.throttledSnapRate ) ); + return; + } + } + + if ( throttleMode != 0 ) { + DetectSaturation( p ); + } + + if ( peer.maxSnapBps >= 0.0f && ( throttleMode == 2 || throttleMode == 3 ) ) { + if ( peer.packetProc->GetOutgoingRateBytes() > peer.maxSnapBps ) { + return; + } + } + + // TrySetPendingSnapshot will try to set the new pending snap. + // TrySetPendingSnapshot won't do anything until the last snap set was fully sent out. + + if ( peer.snapProc->TrySetPendingSnapshot( ss ) ) { + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^8Set next pending snapshot peer %d\n", 0 ) ); + + peer.numSnapsSent++; + + idSnapShot * baseState = peers[p].snapProc->GetBaseState(); + if ( verify( baseState != NULL ) ) { + baseState->UpdateExpectedSeq( peers[p].snapProc->GetSnapSequence() ); + } + + } else { + NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2FAILED Set next pending snapshot peer %d\n", 0 ) ); + } + + // We send out the pending snap, which could be the most recent, or an old one that hasn't fully been sent + // We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs. + peer.needToSubmitPendingSnap = true; +} + +/* +======================== +idLobby::AllPeersHaveBaseState +======================== +*/ +bool idLobby::AllPeersHaveBaseState() { + assert( lobbyType == GetActingGameStateLobbyType() ); + + for ( int i = 0; i < peers.Num(); ++i ) { + + if ( !peers[i].IsConnected() ) { + continue; + } + + if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) { + return false; // If a client hasn't ack'd his first full snap, then we are still sending base state to someone + } + } + + return true; +} + +/* +======================== +idLobby::ThrottleSnapsForXSeconds +======================== +*/ +void idLobby::ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + if ( peers[p].throttleSnapsForXSeconds != 0 ) { + return; // Already throttling snaps + } + + idLib::Printf( "Throttling peer %i for %i seconds...\n", p, seconds ); + + peers[p].throttleSnapsForXSeconds = Sys_Milliseconds() + seconds * 1000; + peers[p].recoverPing = recoverPing ? peers[p].lastPingRtt : 0; +} + +/* +======================== +idLobby::FirstSnapHasBeenSent +======================== +*/ +bool idLobby::FirstSnapHasBeenSent( int p ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + if ( !verify( p >= 0 && p < peers.Num() ) ) { + return false; + } + + peer_t & peer = peers[p]; + + if ( peer.numSnapsSent == 0 ) { + return false; + } + + if ( peer.snapProc == NULL ) { + return false; + } + + idSnapShot * ss = peer.snapProc->GetPendingSnap(); + + if ( ss == NULL ) { + return false; + } + + if ( ss->NumObjects() == 0 ) { + return false; + } + + return true; +} + +/* +======================== +idLobby::EnsureAllPeersHaveBaseState +This function ensures all peers that started the match together (they were in the lobby when it started) start together. +Join in progress peers will be handled as they join. +======================== +*/ +bool idLobby::EnsureAllPeersHaveBaseState() { + + assert( lobbyType == GetActingGameStateLobbyType() ); + + int time = Sys_Milliseconds(); + + + for ( int i = 0; i < peers.Num(); ++i ) { + if ( !peers[i].IsConnected() ) { + continue; + } + + if ( !FirstSnapHasBeenSent( i ) ) { + continue; // Must be join in progress peer + } + + if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) { + if ( time - peers[i].lastSnapTime > session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ) ) { + SendSnapshotToPeer( *peers[i].snapProc->GetPendingSnap(), i ); + } + return false; + } + } + + return true; +} + +/* +======================== +idLobby::AllPeersHaveStaleSnapObj +======================== +*/ +bool idLobby::AllPeersHaveStaleSnapObj( int objId ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + for ( int i = 0; i < peers.Num(); i++ ) { + if ( !peers[i].IsConnected() ) { + continue; + } + + idSnapShot * baseState = peers[i].snapProc->GetBaseState(); + + idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); + + if ( state == NULL || !state->stale ) { + return false; + } + } + return true; +} + +/* +======================== +idLobby::AllPeersHaveExpectedSnapObj +======================== +*/ +bool idLobby::AllPeersHaveExpectedSnapObj( int objId ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + for ( int i = 0; i < peers.Num(); i++ ) { + if ( !peers[i].IsConnected() ) { + continue; + } + + idSnapShot * baseState = peers[i].snapProc->GetBaseState(); + idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); + if ( state == NULL ) { + return false; + } + + if ( state->expectedSequence == -2 ) { + return false; + } + + if ( state->expectedSequence > 0 && peers[i].snapProc->GetFullSnapBaseSequence() <= state->expectedSequence ) { + //idLib::Printf("^3Not ready to go stale. obj %d Base: %d expected: %d\n", objId, peers[i].snapProc->GetBaseSequence(), state->expectedSequence ); + return false; + } + } + + return true; +} + +/* +======================== +idLobby::MarkSnapObjDeleted +======================== +*/ +void idLobby::RefreshSnapObj( int objId ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + for ( int i = 0; i < peers.Num(); i++ ) { + if ( !peers[i].IsConnected() ) { + continue; + } + + idSnapShot * baseState = peers[i].snapProc->GetBaseState(); + idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); + if ( state != NULL ) { + // Setting to -2 will defer setting the expected sequence until the current snap is ready to send + state->expectedSequence = -2; + } + } +} + +/* +======================== +idLobby::MarkSnapObjDeleted +======================== +*/ +void idLobby::MarkSnapObjDeleted( int objId ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + for ( int i = 0; i < peers.Num(); i++ ) { + if ( !peers[i].IsConnected() ) { + continue; + } + + idSnapShot * baseState = peers[i].snapProc->GetBaseState(); + + idSnapShot::objectState_t * state = baseState->FindObjectByID( objId ); + + if ( state != NULL ) { + state->deleted = true; + } + } +} + +/* +======================== +idLobby::ResetBandwidthStats +======================== +*/ +void idLobby::ResetBandwidthStats() { + assert( lobbyType == GetActingGameStateLobbyType() ); + + lastSnapBspHistoryUpdateSequence = -1; + + for ( int p = 0; p < peers.Num(); p++ ) { + peers[p].maxSnapBps = -1.0f; + peers[p].throttledSnapRate = 0; + peers[p].rightBeforeSnapsPing = peers[p].lastPingRtt; + peers[p].throttleSnapsForXSeconds = 0; + peers[p].recoverPing = 0; + peers[p].failedPingRecoveries = 0; + peers[p].rightBeforeSnapsPing = 0; + + } +} + +/* +======================== +idLobby::DetectSaturation +See if the ping shot up, which indicates a previously saturated connection +======================== +*/ +void idLobby::DetectSaturation( int p ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + peer_t & peer = peers[p]; + + if ( !peer.IsConnected() ) { + return; + } + + const float pingIncPercentBeforeThottle = session->GetTitleStorageFloat( "net_pingIncPercentBeforeRecover", net_pingIncPercentBeforeRecover.GetFloat() ); + const int pingThreshold = session->GetTitleStorageInt( "net_min_ping_in_ms", net_min_ping_in_ms.GetInteger() ); + const int maxFailedPingRecoveries = session->GetTitleStorageInt( "net_maxFailedPingRecoveries", net_maxFailedPingRecoveries.GetInteger() ); + const int pingRecoveryThrottleTimeInSeconds = session->GetTitleStorageInt( "net_pingRecoveryThrottleTimeInSeconds", net_pingRecoveryThrottleTimeInSeconds.GetInteger() ); + + if ( peer.lastPingRtt > peer.rightBeforeSnapsPing * pingIncPercentBeforeThottle && peer.lastPingRtt > pingThreshold ) { + if ( peer.failedPingRecoveries < maxFailedPingRecoveries ) { + ThrottleSnapsForXSeconds( p, pingRecoveryThrottleTimeInSeconds, true ); + } + } +} + +/* +======================== +idLobby::AddSnapObjTemplate +======================== +*/ +void idLobby::AddSnapObjTemplate( int objID, idBitMsg & msg ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + // If we are in the middle of a SS read, apply this state to what we + // just deserialized (the obj we just deserialized is a delta from the template object we are adding right now) + if ( localReadSS != NULL ) { + localReadSS->ApplyToExistingState( objID, msg ); + } + + // Add the template to the snapshot proc for future snapshot processing + for ( int p = 0; p < peers.Num(); p++ ) { + if ( !peers[p].IsConnected() || peers[p].snapProc == NULL ) { + continue; + } + peers[p].snapProc->AddSnapObjTemplate( objID, msg ); + } +} diff --git a/neo/sys/sys_lobby_users.cpp b/neo/sys/sys_lobby_users.cpp new file mode 100644 index 00000000..0cce2a7a --- /dev/null +++ b/neo/sys/sys_lobby_users.cpp @@ -0,0 +1,1411 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_lobby.h" +#include "sys_voicechat.h" + + + + +/* +======================== +idLobby::SaveDisconnectedUser +======================== +*/ +void idLobby::SaveDisconnectedUser( const lobbyUser_t & user ) { + bool found = false; + for ( int i = 0; i < disconnectedUsers.Num(); i++ ) { + if ( user.lobbyUserID.CompareIgnoreLobbyType( disconnectedUsers[i].lobbyUserID ) ) { + found = true; + memcpy( disconnectedUsers[i].gamertag, user.gamertag, sizeof( user.gamertag ) ); + } + } + if ( !found ) { + disconnectedUser_t & du = disconnectedUsers.Alloc(); + du.lobbyUserID = user.lobbyUserID; + memcpy( du.gamertag, user.gamertag, sizeof( user.gamertag ) ); + } +} + +/* +======================== +idLobby::AllocUser +======================== +*/ +lobbyUser_t * idLobby::AllocUser( const lobbyUser_t & defaults ) { + if ( !verify( freeUsers.Num() > 0 ) ) { + idLib::Error( "Out of session users" ); // This shouldn't be possible + } + + lobbyUser_t * user = freeUsers[freeUsers.Num() - 1]; + freeUsers.SetNum( freeUsers.Num() - 1 ); + + // Set defaults + *user = defaults; + + userList.Append( user ); + + assert( userList.Num() == userPool.Max() - freeUsers.Num() ); + + return user; +} + +/* +======================== +idLobby::FreeUser +======================== +*/ +void idLobby::FreeUser( lobbyUser_t * user ) { + if ( !verify( user != NULL ) ) { + return; + } + + if ( !VerifyUser( user ) ) { + return; + } + + SaveDisconnectedUser( *user ); + + verify( userList.Remove( user ) ); + + freeUsers.Append( user ); +} + +/* +======================== +idLobby::FreeAllUsers +======================== +*/ +void idLobby::FreeAllUsers() { + for ( int i = userList.Num() - 1; i >= 0; i-- ) { + FreeUser( userList[i] ); + } + + assert( userList.Num() == 0 ); + assert( freeUsers.Num() == userPool.Max() ); +} + +/* +======================== +idLobby::RegisterUser +======================== +*/ +void idLobby::RegisterUser( lobbyUser_t * lobbyUser ) { + if ( lobbyUser->isBot ) { + return; + } + + // Register the user with the various managers + bool isLocal = IsSessionUserLocal( lobbyUser ); + + if ( lobbyBackend != NULL ) { + lobbyBackend->RegisterUser( lobbyUser, isLocal ); + } + + sessionCB->GetVoiceChat()->RegisterTalker( lobbyUser, lobbyType, isLocal ); +} + +/* +======================== +idLobby::UnregisterUser +======================== +*/ +void idLobby::UnregisterUser( lobbyUser_t * lobbyUser ) { + if ( lobbyUser->isBot ) { + return; + } + + if ( lobbyUser->IsDisconnected() ) { + return; + } + + bool isLocal = IsSessionUserLocal( lobbyUser ); + + if ( lobbyBackend != NULL ) { + lobbyBackend->UnregisterUser( lobbyUser, isLocal ); + } + + sessionCB->GetVoiceChat()->UnregisterTalker( lobbyUser, lobbyType, isLocal ); +} + +/* +======================== +idLobby::VerifyUser +======================== +*/ +bool idLobby::VerifyUser( const lobbyUser_t * lobbyUser ) const { + if ( !verify( userList.FindIndex( const_cast( lobbyUser ) ) != -1 ) ) { + return false; + } + + return true; +} + +/* +======================== +idLobby::IsSessionUserLocal +======================== +*/ +bool idLobby::IsSessionUserLocal( const lobbyUser_t * lobbyUser ) const { + if ( !verify( lobbyUser != NULL ) ) { + return false; + } + + if ( !VerifyUser( lobbyUser ) ) { + return false; + } + + // This user is local if the peerIndex matches what our peerIndex is on the host + return ( lobbyUser->peerIndex == peerIndexOnHost ); +} + +/* +======================== +idLobby::IsSessionUserIndexLocal +======================== +*/ +bool idLobby::IsSessionUserIndexLocal( int i ) const { + return IsSessionUserLocal( GetLobbyUser( i ) ); +} + +/* +======================== +idLobby::GetLobbyUserIndexByID +======================== +*/ +int idLobby::GetLobbyUserIndexByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType ) const { + if ( !lobbyUserId.IsValid() ) { + return -1; + } + assert( lobbyUserId.GetLobbyType() == lobbyType || ignoreLobbyType ); + + for ( int i = 0; i < GetNumLobbyUsers(); ++ i ) { + if ( ignoreLobbyType ) { + if ( GetLobbyUser( i )->lobbyUserID.CompareIgnoreLobbyType( lobbyUserId ) ) { + return i; + } + continue; + } + if ( GetLobbyUser( i )->lobbyUserID == lobbyUserId ) { + return i; + } + } + return -1; +} + +/* +======================== +idLobby::GetLobbyUserByID +======================== +*/ +lobbyUser_t * idLobby::GetLobbyUserByID( lobbyUserID_t lobbyUserID, bool ignoreLobbyType ) { + int index = GetLobbyUserIndexByID( lobbyUserID, ignoreLobbyType ); + + if ( index == -1 ) { + return NULL; + } + + return GetLobbyUser( index ); +} + + +/* +======================== +idLobby::CreateLobbyUserFromLocalUser +This functions just defaults the session users to the signin manager local users +======================== +*/ +lobbyUser_t idLobby::CreateLobbyUserFromLocalUser( const idLocalUser * localUser ) { + + lobbyUser_t lobbyUser; + idStr::Copynz( lobbyUser.gamertag, localUser->GetGamerTag(), sizeof( lobbyUser.gamertag ) ); + lobbyUser.peerIndex = -1; + lobbyUser.lobbyUserID = lobbyUserID_t( localUser->GetLocalUserHandle(), lobbyType ); // Generate the lobby using a combination of local user id, and lobby type + lobbyUser.disconnecting = false; + + // If we are in a game lobby (or dedicated game state lobby), and we have a party lobby running, assume we can grab the party token from our equivalent user in the party. + if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetPartyLobby().IsLobbyActive() ) { + if ( ( sessionCB->GetPartyLobby().parms.matchFlags & MATCH_REQUIRE_PARTY_LOBBY ) && !( sessionCB->GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) ) { + // copy some things from my party user + const int myPartyUserIndex = sessionCB->GetPartyLobby().GetLobbyUserIndexByLocalUserHandle( lobbyUser.lobbyUserID.GetLocalUserHandle() ); + + if ( verify( myPartyUserIndex >= 0 ) ) { // Just in case + lobbyUser_t * myPartyUser = sessionCB->GetPartyLobby().GetLobbyUser( myPartyUserIndex ); + if ( myPartyUser != NULL ) { + lobbyUser.partyToken = myPartyUser->partyToken; + } + } + } + } + + lobbyUser.UpdateClientMutableData( localUser ); + + NET_VERBOSE_PRINT( "NET: CreateLobbyUserFromLocalUser: party %08x name %s (%s)\n", lobbyUser.partyToken, lobbyUser.gamertag, GetLobbyName() ); + + return lobbyUser; +} + +/* +======================== +idLobby::InitSessionUsersFromLocalUsers +This functions just defaults the session users to the signin manager local users +======================== +*/ +void idLobby::InitSessionUsersFromLocalUsers( bool onlineMatch ) { + assert( lobbyBackend != NULL ); + + // First, clear all session users of this session type + FreeAllUsers(); + + // Copy all local users from sign in mgr to the session user list + for ( int i = 0; i < sessionCB->GetSignInManager().GetNumLocalUsers(); i++ ) { + idLocalUser * localUser = sessionCB->GetSignInManager().GetLocalUserByIndex( i ); + + // Make sure this user can join lobbies + if ( onlineMatch && !localUser->CanPlayOnline() ) { + continue; + } + + lobbyUser_t lobbyUser = CreateLobbyUserFromLocalUser( localUser ); + + // Append this new session user to the session user list + lobbyUser_t * createdUser = AllocUser( lobbyUser ); + + // Get the migration game data if this is a migrated hosting + if ( verify( createdUser != NULL ) && migrationInfo.persistUntilGameEndsData.wasMigratedHost ) { + createdUser->migrationGameData = migrationInfo.persistUntilGameEndsData.ourGameData; + NET_VERBOSE_PRINT( "NET: Migration game data set for local user %s at index %d \n", createdUser->gamertag, migrationInfo.persistUntilGameEndsData.ourGameData ); + } + } +} + +/* +======================== +idLobby::GetLobbyUserIndexByLocalUserHandle +Takes a local user handle, and converts to a session user +======================== +*/ +int idLobby::GetLobbyUserIndexByLocalUserHandle( const localUserHandle_t localUserHandle ) const { + // Find the session user that uses this input device index + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + if ( !IsSessionUserIndexLocal( i ) ) { + continue; // We only want local users + } + if ( GetLobbyUser( i )->lobbyUserID.GetLocalUserHandle() == localUserHandle ) { + return i; // Found it + } + } + + return -1; +} + +/* +======================== +idLobby::GetLocalUserFromLobbyUserIndex +This takes a session user, and converts to a local user +======================== +*/ +idLocalUser * idLobby::GetLocalUserFromLobbyUserIndex( int lobbyUserIndex ) { + if ( lobbyUserIndex < 0 || lobbyUserIndex >= GetNumLobbyUsers() ) { + return NULL; + } + + if ( !IsSessionUserIndexLocal( lobbyUserIndex ) ) { + return NULL; + } + + lobbyUser_t * lobbyUser = GetLobbyUser( lobbyUserIndex ); + + if ( lobbyUser == NULL ) { + return NULL; + } + + return sessionCB->GetSignInManager().GetLocalUserByHandle( lobbyUser->lobbyUserID.GetLocalUserHandle() ); +} + +/* +======================== +idLobby::GetSessionUserFromLocalUser +Takes a local user, and converts to a session user +======================== +*/ +lobbyUser_t * idLobby::GetSessionUserFromLocalUser( const idLocalUser * localUser ) { + if ( localUser == NULL ) { + return NULL; + } + + int sessionUserIndex = GetLobbyUserIndexByLocalUserHandle( localUser->GetLocalUserHandle() ); + + if ( sessionUserIndex != -1 ) { + assert( IsSessionUserIndexLocal( sessionUserIndex ) ); + return GetLobbyUser( sessionUserIndex ); + } + + return NULL; +} + +/* +======================== +idLobby::RemoveUsersWithDisconnectedPeers +Go through each user, and remove the ones that have a peer marked as disconnected +NOTE - This should only be called from the host. The host will call RemoveSessionUsersByIDList, +which will forward the action to the connected peers. +======================== +*/ +void idLobby::RemoveUsersWithDisconnectedPeers() { + if ( !verify( IsHost() ) ) { + // We're not allowed to do this unless we are the host of this session type + // If we are the host, RemoveSessionUsersByIDList will forward the call to peers. + return; + } + + idList< lobbyUserID_t > removeList; + for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { + lobbyUser_t * user = GetLobbyUser( u ); + if ( !verify( user != NULL ) ) { + continue; + } + if ( IsSessionUserIndexLocal( u ) || user->IsDisconnected() ) { + continue; + } + + if ( user->peerIndex == -1 ) { + // Wanting to know if this actually happens. + // If this is a user on the hosts machine, IsSessionUserIndexLocal should catch it above. + assert( false ); + // The user is on the host. + // The host's peer is disconnected via other mechanisms that I don't have a firm + // grasp on yet. + continue; + } + + if ( user->peerIndex >= peers.Num() ) { + // TTimo - I am hitting this in ~12 client games for some reason? + // only throwing an assertion in debug, with no crashing, so adding a warning verbose + idLib::Warning( "idLobby::RemoveUsersWithDisconnectedPeers: user %d %s is out of range in the peers list (%d elements)", u, user->gamertag, peers.Num() ); + continue; + } + peer_t & peer = peers[ user->peerIndex ]; + if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) { + removeList.Append( user->lobbyUserID ); + } + } + + RemoveSessionUsersByIDList( removeList ); +} + +/* +======================== +idLobby::RemoveSessionUsersByIDList +This is the choke point for removing users from a session. +It will handle all the housekeeping of removing from various platform lists (xsession user tracking, etc). +Called from both host and client. +======================== +*/ +void idLobby::RemoveSessionUsersByIDList( idList< lobbyUserID_t > & usersToRemoveByID ) { + assert( lobbyBackend != NULL || usersToRemoveByID.Num() == 0 ); + + for ( int i = 0; i < usersToRemoveByID.Num(); i++ ) { + for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { + lobbyUser_t * user = GetLobbyUser( u ); + + if ( user->IsDisconnected() ) { + // User already disconnected from session but not removed from the list. + // This will happen when users are removed during the game. + continue; + } + + if ( user->lobbyUserID == usersToRemoveByID[i] ) { + if ( lobbyType == TYPE_GAME ) { + idLib::Printf( "NET: %s left the game.\n", user->gamertag ); + } else if ( lobbyType == TYPE_PARTY ) { + idLib::Printf( "NET: %s left the party.\n", user->gamertag ); + } + + UnregisterUser( user ); + + // Save the user so we can still get his gamertag, which may be needed for + // a disconnection HUD message. + SaveDisconnectedUser( *user ); + FreeUser( user ); + + break; + } + } + } + + if ( usersToRemoveByID.Num() > 0 && IsHost() ) { + if ( lobbyBackend != NULL ) { + lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() ); + } + + // If we are the host, send a message to all peers with a list of users who have disconnected + byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + msg.WriteByte( usersToRemoveByID.Num() ); + + for ( int i = 0; i < usersToRemoveByID.Num(); i++ ) { + usersToRemoveByID[i].WriteToMsg( msg ); + } + for ( int p = 0; p < peers.Num(); p++ ) { + QueueReliableMessage( p, RELIABLE_USER_DISCONNECTED, msg.GetReadData(), msg.GetSize() ); + } + } +} + +/* +======================== +idLobby::SendPeersMicStatusToNewUsers +Sends each current user mic status to the newly added peer. +======================== +*/ +void idLobby::SendPeersMicStatusToNewUsers( int peerNumber ) { + if ( !IsHost() ) { + return; + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg outmsg( buffer, sizeof( buffer ) ); + + // Count up how many users will be in the msg + int numUsersInMsg = 0; + + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + lobbyUser_t * user = GetLobbyUser( i ); + + if ( user->isBot ) { + continue; + } + + if ( user->peerIndex == peerNumber ) { + continue; + } + + numUsersInMsg++; + } + + if ( numUsersInMsg == 0 ) { + return; // Nothing to do + } + + outmsg.WriteLong( numUsersInMsg ); + + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + lobbyUser_t * user = GetLobbyUser( i ); + + if ( user->isBot ) { + continue; + } + + if ( user->peerIndex == peerNumber ) { + continue; + } + + int talkerIndex = sessionCB->GetVoiceChat()->FindTalkerByUserId( user->lobbyUserID, lobbyType ); + bool state = sessionCB->GetVoiceChat()->GetHeadsetState( talkerIndex ); + + idLib::Printf( "Packing headset state %d for user %d %s\n", state, i, user->gamertag ); + user->lobbyUserID.WriteToMsg( outmsg ); + outmsg.WriteBool( state ); + } + + + idLib::Printf( "Sending headset states to new peer %d\n", peerNumber ); + QueueReliableMessage( peerNumber, RELIABLE_HEADSET_STATE, outmsg.GetReadData(), outmsg.GetSize() ); +} + +/* +======================== +idLobby::SendNewUsersToPeers +Sends a range of users to the current list of peers. +The host calls this when he receives new users, to forward the list to the other peers. +======================== +*/ +void idLobby::SendNewUsersToPeers( int skipPeer, int userStart, int numUsers ) { + if ( !IsHost() ) { + return; + } + + assert( GetNumLobbyUsers() - userStart == numUsers ); + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg outmsg( buffer, sizeof( buffer ) ); + + // Write number of users + outmsg.WriteByte( numUsers ); + + // Fill up the msg with all the users + for ( int u = userStart; u < GetNumLobbyUsers(); u++ ) { + GetLobbyUser( u )->WriteToMsg( outmsg ); + } + + // Send the msg to all peers (except the skipPeer, or peers not connected to this session type) + for ( int p = 0; p < peers.Num(); p++ ) { + if ( p == skipPeer || peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) { + continue; // If they are not connected in this session type, don't send anything to them. + } + QueueReliableMessage( p, RELIABLE_USER_CONNECTED, outmsg.GetReadData(), outmsg.GetSize() ); + } +} + +/* +======================== +idLobby::AllocLobbyUserSlotForBot +======================== +*/ +lobbyUserID_t idLobby::AllocLobbyUserSlotForBot( const char * botName ) { + lobbyUser_t botSessionUser; + botSessionUser.peerIndex = peerIndexOnHost; + botSessionUser.isBot = true; + botSessionUser.disconnecting = false; + idStr::Copynz( botSessionUser.gamertag, botName, sizeof( botSessionUser.gamertag ) ); + + localUserHandle_t localUserHandle( session->GetSignInManager().GetUniqueLocalUserHandle( botSessionUser.gamertag ) ); + botSessionUser.lobbyUserID = lobbyUserID_t( localUserHandle, lobbyType ); + + lobbyUser_t * botUser = NULL; + + int sessionUserIndex = -1; + + // First, try to replace a disconnected user + for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { + if ( IsLobbyUserDisconnected( i ) ) { + lobbyUser_t * user = GetLobbyUser( i ); + if ( verify( user != NULL ) ) { + *user = botSessionUser; + botUser = user; + sessionUserIndex = i; + break; + } + } + } + + if ( botUser == NULL ) { + if ( freeUsers.Num() == 0 ) { + idLib::Warning( "NET: Out Of Session Users - Can't Add Bot %s!", botName ); + return lobbyUserID_t(); + } + botUser = AllocUser( botSessionUser ); + sessionUserIndex = userList.Num() - 1; + } + + if ( !verify( botUser != NULL ) ) { + idLib::Warning( "NET: Can't Find Session Slot For Bot!" ); + return lobbyUserID_t(); + } else { + NET_VERBOSE_PRINT( "NET: Created Bot %s At Index %d \n", botUser->gamertag, sessionUserIndex ); + } + + SendNewUsersToPeers( peerIndexOnHost, userList.Num() - 1, 1 ); // bot has been added to the lobby user list - update the peers so that they can see the bot too. + + return GetLobbyUser( sessionUserIndex )->lobbyUserID; +} + +/* +======================== +idLobby::RemoveBotFromLobbyUserList +======================== +*/ +void idLobby::RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ) { + const int index = GetLobbyUserIndexByID( lobbyUserID ); + + lobbyUser_t * botUser = GetLobbyUser( index ); + if ( botUser == NULL ) { + assert( false ); + idLib::Warning( "RemoveBotFromLobbyUserList: Invalid User Index!" ); + return; + } + + if ( !botUser->isBot ) { + idLib::Warning( "RemoveBotFromLobbyUserList: User Index Is Not A Bot!" ); // don't accidentally disconnect a human player. + return; + } + + botUser->isBot = false; + botUser->lobbyUserID = lobbyUserID_t(); + + FreeUser( botUser ); +} + +/* +======================== +idLobby::GetLobbyUserIsBot +======================== +*/ +bool idLobby::GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const { + const int index = GetLobbyUserIndexByID( lobbyUserID ); + + const lobbyUser_t * botLobbyUser = GetLobbyUser( index ); + if ( botLobbyUser == NULL ) { + return false; + } + + return botLobbyUser->isBot; +} + +/* +======================== +idLobby::AddUsersFromMsg +Called on peer and host. +Simply parses a msg, and adds any new users from it to our own user list. +If we are the host, we will forward this to all peers except the peer that we just received it from. +======================== +*/ +void idLobby::AddUsersFromMsg( idBitMsg & msg, int fromPeer ) { + int userStart = GetNumLobbyUsers(); + int numNewUsers = msg.ReadByte(); + + assert( lobbyBackend != NULL ); + + // Add the new users to our own list + for ( int u = 0; u < numNewUsers; u++ ) { + lobbyUser_t newUser; + + // Read in the new user + newUser.ReadFromMsg( msg ); + + // Initialize their peerIndex and userID if we are the host + // (we'll send these back to them in the initial connect) + if ( IsHost() ) { + if ( fromPeer != -1 ) { // -1 means this is the host adding his own users, and this stuff is already computed + // local users will already have this information filled out. + newUser.address = peers[ fromPeer ].address; + newUser.peerIndex = fromPeer; + if ( lobbyType == TYPE_PARTY ) { + newUser.partyToken = GetPartyTokenAsHost(); + } + } + } else { + assert( fromPeer == host ); + // The host sends us all user addresses, except his local users, so we compute that here + if ( newUser.peerIndex == -1 ) { + newUser.address = peers[ fromPeer ].address; + } + } + + idLib::Printf( "NET: %s joined (%s) [partyToken = %08x].\n", newUser.gamertag, GetLobbyName(), newUser.partyToken ); + + lobbyUser_t * appendedUser = NULL; + + // First, try to replace a disconnected user + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + lobbyUser_t * user = GetLobbyUser( i ); + + if ( user->IsDisconnected() ) { + userStart = i; + *user = newUser; + appendedUser = user; + break; + } + } + + // Add them to our list + if ( appendedUser == NULL ) { + appendedUser = AllocUser( newUser ); + } + + // Run platform-specific handler after adding + assert( appendedUser->peerIndex == newUser.peerIndex ); // paranoia + assert( appendedUser->lobbyUserID == newUser.lobbyUserID ); // paranoia + RegisterUser( appendedUser ); + } + + // Forward list of the new users to all other peers + if ( IsHost() ) { + SendNewUsersToPeers( fromPeer, userStart, numNewUsers ); + + // Set the lobbies skill level + lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() ); + } + + idLib::Printf( "---------------- %s --------------------\n", GetLobbyName() ); + for( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { + lobbyUser_t * user = GetLobbyUser( userIndex ); + idLib::Printf( "party %08x user %s\n", user->partyToken, user->gamertag ); + } + idLib::Printf( "---------------- %s --------------------\n", GetLobbyName() ); +} + +/* +======================== +idLobby::UpdateSessionUserOnPeers +======================== +*/ +void idLobby::UpdateSessionUserOnPeers( idBitMsg & msg ) { + for ( int p = 0; p < peers.Num(); p++ ) { + QueueReliableMessage( p, RELIABLE_UPDATE_SESSION_USER, msg.GetReadData() + msg.GetReadCount(), msg.GetSize() - msg.GetReadCount() ); + } + + HandleUpdateSessionUser( msg ); +} + +/* +======================== +idLobby::HandleHeadsetStateChange +======================== +*/ +void idLobby::HandleHeadsetStateChange( int fromPeer, idBitMsg & msg ) { + int userCount = msg.ReadLong(); + + for ( int i = 0; i < userCount; ++i ) { + lobbyUserID_t lobbyUserID; + lobbyUserID.ReadFromMsg( msg ); + bool state = msg.ReadBool(); + + int talkerIndex = sessionCB->GetVoiceChat()->FindTalkerByUserId( lobbyUserID, lobbyType ); + sessionCB->GetVoiceChat()->SetHeadsetState( talkerIndex, state ); + + idLib::Printf( "User %d headset status: %d\n", talkerIndex, state ); + + // If we are the host, let the other clients know about the headset state of this peer + if ( IsHost() ) { + + // We should not be receiving a message with a user count > 1 if we are the host + assert( userCount == 1 ); + + byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; + idBitMsg outMsg( buffer, sizeof( buffer ) ); + outMsg.WriteLong( 1 ); + lobbyUserID.WriteToMsg( outMsg ); + outMsg.WriteBool( state ); + + for ( int j = 0; j < peers.Num(); ++j ) { + // Don't send this to the player that we just received the message from + if ( !peers[ j ].IsConnected() || j == fromPeer ) { + continue; + } + + QueueReliableMessage( j, RELIABLE_HEADSET_STATE, outMsg.GetReadData(), outMsg.GetSize() ); + } + } + } +} + +/* +======================== +idLobby::HandleUpdateSessionUser +======================== +*/ +void idLobby::HandleUpdateSessionUser( idBitMsg & msg ) { + // FIXME: Use a user id here + int sessionUserIndex = msg.ReadByte(); + + lobbyUser_t * user = GetLobbyUser( sessionUserIndex ); + + if ( verify( user != NULL ) ) { + user->ReadClientMutableData( msg ); + } +} + +/* +======================== +idLobby::CreateUserUpdateMessage +======================== +*/ +void idLobby::CreateUserUpdateMessage( int userIndex, idBitMsg & msg ) { + lobbyUser_t * user = GetLobbyUser( userIndex ); + + if ( verify( user != NULL ) ) { + msg.WriteByte( userIndex ); + user->WriteClientMutableData( msg ); + } +} + +/* +======================== +idLobby::UpdateLocalSessionUsers +======================== +*/ +void idLobby::UpdateLocalSessionUsers() { + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( i ); + lobbyUser_t * lobbyUser = GetLobbyUser( i ); + + if ( localUser == NULL || lobbyUser == NULL ) { + continue; + } + if ( !lobbyUser->UpdateClientMutableData( localUser ) ) { + continue; + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + CreateUserUpdateMessage( i, msg ); + + if ( IsHost() ) { + UpdateSessionUserOnPeers( msg ); + } else if ( IsPeer() ) { + QueueReliableMessage( host, RELIABLE_SESSION_USER_MODIFIED, msg.GetReadData(), msg.GetSize() ); + } + } +} + +/* +======================== +idLobby::PeerIndexForSessionUserIndex +======================== +*/ +int idLobby::PeerIndexForSessionUserIndex( int sessionUserIndex ) const { + const lobbyUser_t * user = GetLobbyUser( sessionUserIndex ); + + if ( !verify( user != NULL ) ) { + return -1; + } + + return user->peerIndex; +} + +/* +======================== +idLobby::HandleUserConnectFailure +======================== +*/ +void idLobby::HandleUserConnectFailure( int p, idBitMsg & inMsg, int reliableType ) { + // Read user to get handle so we can send it back + inMsg.ReadByte(); // Num users + lobbyUser_t user; + user.ReadFromMsg( inMsg ); + + // Not enough room, send failure ack + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + user.lobbyUserID.GetLocalUserHandle().WriteToMsg( msg ); // Let peer know which user failed to connect + + // Send it + QueueReliableMessage( p, reliableType, msg.GetReadData(), msg.GetSize() ); +} + +/* +======================== +idLobby::ProcessUserDisconnectMsg +======================== +*/ +void idLobby::ProcessUserDisconnectMsg( idBitMsg & msg ) { + + idList< lobbyUserID_t > removeList; + + // Convert the msg into a list of id's + const int numUsers = msg.ReadByte(); + + for ( int u = 0; u < numUsers; u++ ) { + lobbyUserID_t lobbyUserID; + lobbyUserID.ReadFromMsg( msg ); + removeList.Append( lobbyUserID ); + } + + RemoveSessionUsersByIDList( removeList ); +} + +/* +======================== +idLobby::CompactDisconnectedUsers +Pack the user list by removing disconnected users +We need to do this, since when in a game, we aren't allowed to remove users from the game session. +======================== +*/ +void idLobby::CompactDisconnectedUsers() { + for ( int i = GetNumLobbyUsers() - 1; i >= 0; i-- ) { + lobbyUser_t * user = GetLobbyUser( i ); + if ( user->IsDisconnected() ) { + FreeUser( user ); + } + } +} + +/* +======================== +idLobby::RequestLocalUserJoin +Sends a request to the host to join a local user to a session. +If we are the host, we will do it immediately. +======================== +*/ +void idLobby::RequestLocalUserJoin( idLocalUser * localUser ) { + assert( IsRunningAsHostOrPeer() ); + + // Construct a msg that contains the user connect request + lobbyUser_t lobbyUser = CreateLobbyUserFromLocalUser( localUser ); + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + msg.WriteByte( 1 ); // 1 user + lobbyUser.WriteToMsg( msg ); // Write user + + if ( IsHost() ) { + AddUsersFromMsg( msg, -1 ); + localUser->SetJoiningLobby( lobbyType, false ); + } else { + // Send request to host to add user + QueueReliableMessage( host, RELIABLE_USER_CONNECT_REQUEST, msg.GetReadData(), msg.GetSize() ); + } +} + +/* +======================== +idLobby::RequestSessionUserDisconnect +Sends a request to the host to remove a session user from the session. +If we are the host, we will do it immediately. +======================== +*/ +void idLobby::RequestSessionUserDisconnect( int sessionUserIndex ) { + + if ( !IsRunningAsHostOrPeer() ) { + // If we are not in an actual running session, just remove it. + // This is so we accurately reflect the local user list through the session users in the menus, etc. + // FIXME: + // This is a total hack, and we should really look at better separation of local users/session users + // and not rely on session users while in the menus. + FreeUser( GetLobbyUser( sessionUserIndex ) ); + return; + } + + lobbyUser_t * lobbyUser = GetLobbyUser( sessionUserIndex ); + + if ( !verify( lobbyUser != NULL ) ) { + return; + } + + if ( lobbyUser->disconnecting == true ) { + return; // Already disconnecting + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + msg.WriteByte( 1 ); // 1 user + lobbyUser->lobbyUserID.WriteToMsg( msg ); + + if ( IsHost() ) { + idBitMsg readMsg; + readMsg.InitRead( msg.GetReadData(), msg.GetSize() ); + + // As the host, just disconnect immediately (we'll still send the notification to all peers though) + ProcessUserDisconnectMsg( readMsg ); + } else { + // Send the message + QueueReliableMessage( host, RELIABLE_USER_DISCONNECT_REQUEST, msg.GetReadData(), msg.GetSize() ); + + // Mark user as disconnecting to make sure we don't keep sending the request + lobbyUser->disconnecting = true; + } +} + +/* +======================== +idLobby::SyncLobbyUsersWithLocalUsers +This function will simply try and keep session[lobbyType].users sync'd up with local users. +As local users come and go, this function will detect that, and send msg's to the host that will +add/remove the users from the session. +======================== +*/ +void idLobby::SyncLobbyUsersWithLocalUsers( bool allowLocalJoins, bool onlineMatch ) { + + if ( lobbyBackend == NULL ) { + return; + } + + if ( !IsRunningAsHostOrPeer() ) { + return; + } + + if ( allowLocalJoins ) { + // If we are allowed to do so, allow local users to join the session user list + for ( int i = 0; i < sessionCB->GetSignInManager().GetNumLocalUsers(); i++ ) { + idLocalUser * localUser = sessionCB->GetSignInManager().GetLocalUserByIndex( i ); + + if ( GetSessionUserFromLocalUser( localUser ) != NULL ) { + continue; // Already in the lobby + } + + if ( localUser->IsJoiningLobby( lobbyType ) ) { + continue; // Already joining lobby if this type + } + + if ( onlineMatch && !localUser->CanPlayOnline() ) { + continue; // Not allowed to join this type of lobby + } + + localUser->SetJoiningLobby( lobbyType, true ); // We'll reset this when we get the ack for this request + RequestLocalUserJoin( localUser ); + } + } + + // Find session users that are no longer allowed to be in the list + for ( int i = GetNumLobbyUsers() - 1; i >= 0; i-- ) { + if ( !IsSessionUserIndexLocal( i ) ) { + continue; + } + + lobbyUser_t * lobbyUser = GetLobbyUser( i ); + if ( lobbyUser != NULL && lobbyUser->isBot ) { + continue; + } + + idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( i ); + + if ( localUser == NULL || ( onlineMatch && !localUser->CanPlayOnline() ) ) { + // Either the session user is no longer in the local user list, + // or not allowed to join online lobbies. + RequestSessionUserDisconnect( i ); + } else { + localUser->SetJoiningLobby( lobbyType, false ); // Remove joining lobby flag if we are in the lobby + } + } +} + +/* +======================== +idLobby::IsLobbyUserDisconnected +======================== +*/ +bool idLobby::IsLobbyUserDisconnected( int userIndex ) const { + const lobbyUser_t * user = GetLobbyUser( userIndex ); + if ( user == NULL ) { + return true; + } + + if ( user->isBot ) { + return false; + } + + if ( user->IsDisconnected() ) { + return true; + } + + return false; +} + +/* +======================== +idLobby::IsLobbyUserValid +======================== +*/ +bool idLobby::IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const { + if ( !lobbyUserID.IsValid() ) { + return false; + } + + if ( GetLobbyUserIndexByID( lobbyUserID ) == -1 ) { + return false; + } + + return true; +} + +/* +======================== +idLobby::ValidateConnectedUser +======================== +*/ +bool idLobby::ValidateConnectedUser( const lobbyUser_t * user ) const { + if ( user == NULL ) { + return false; + } + + if ( user->IsDisconnected() ) { + return false; + } + + if ( user->peerIndex == -1 ) { + return true; // Host + } + + if ( IsHost() ) { + if ( user->peerIndex < 0 || user->peerIndex >= peers.Num() ) { + return false; + } + + if ( !peers[user->peerIndex].IsConnected() ) { + return false; + } + } + + return true; +} + +/* +======================== +idLobby::IsLobbyUserLoaded +======================== +*/ +bool idLobby::IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const { + assert( lobbyType == GetActingGameStateLobbyType() ); + + int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + + if ( !verify( userIndex != -1 ) ) { + return false; + } + + const lobbyUser_t * user = GetLobbyUser( userIndex ); + + if ( user == NULL ) { + return false; + } + + if ( user->isBot ) { + return true; + } + + if ( !ValidateConnectedUser( user ) ) { + return false; + } + + if ( IsSessionUserLocal( user ) ) { + return loaded; // If this is a local user, check the local loaded flag + } + + if ( !verify( user->peerIndex >= 0 && user->peerIndex < peers.Num() ) ) { + return false; + } + + return peers[user->peerIndex].loaded; +} + +/* +======================== +idLobby::LobbyUserHasFirstFullSnap +======================== +*/ +bool idLobby::LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const { + assert( lobbyType == GetActingGameStateLobbyType() ); + + int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + + const lobbyUser_t * user = GetLobbyUser( userIndex ); + + if ( !ValidateConnectedUser( user ) ) { + return false; + } + + if ( user->peerIndex == -1 ) { + return false; + } + + if ( peers[user->peerIndex].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) { + return false; + } + + return true; +} + +/* +======================== +idLobby::GetLobbyUserIdByOrdinal +======================== +*/ +lobbyUserID_t idLobby::GetLobbyUserIdByOrdinal( int userIndex ) const { + const lobbyUser_t * user = GetLobbyUser( userIndex ); + if ( user == NULL ) { + return lobbyUserID_t(); + } + + if ( user->isBot ) { + return user->lobbyUserID; + } + + if ( !ValidateConnectedUser( user ) ) { + return lobbyUserID_t(); + } + + return user->lobbyUserID; +} + +/* +======================== +idLobby::GetLobbyUserIndexFromLobbyUserID +======================== +*/ +int idLobby::GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const { + return GetLobbyUserIndexByID( lobbyUserID ); +} + +/* +======================== +idLobby::EnableSnapshotsForLobbyUser +======================== +*/ +void idLobby::EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ) { + assert( lobbyType == GetActingGameStateLobbyType() ); + + int userIndex = GetLobbyUserIndexByID( lobbyUserID ); + + const lobbyUser_t * user = GetLobbyUser( userIndex ); + + if ( !ValidateConnectedUser( user ) ) { + return; + } + + if ( user->peerIndex == -1 ) { + return; + } + + peers[user->peerIndex].pauseSnapshots = false; +} + +/* +======================== +idLobby::GetAverageSessionLevel +======================== +*/ +float idLobby::GetAverageSessionLevel() { + float level = 0.0f; + int numActiveMembers = 0; + + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + const lobbyUser_t * user = GetLobbyUser( i ); + + if ( user->IsDisconnected() ) { + continue; + } + + level += user->level; + numActiveMembers++; + } + + if ( numActiveMembers > 0 ) { + level /= (float)numActiveMembers; + } + + float ret = Max( level, 1.0f ); + NET_VERBOSE_PRINT( "NET: GetAverageSessionLevel %g\n", ret ); + return ret; +} + +/* +======================== +idLobby::GetAverageLocalUserLevel +======================== +*/ +float idLobby::GetAverageLocalUserLevel( bool onlineOnly ) { + float level = 0.0f; + int numActiveMembers = 0; + + for ( int i = 0; i < sessionCB->GetSignInManager().GetNumLocalUsers(); ++i ) { + const idLocalUser * localUser = sessionCB->GetSignInManager().GetLocalUserByIndex( i ); + + if ( onlineOnly && !localUser->CanPlayOnline() ) { + continue; + } + + const idPlayerProfile * profile = localUser->GetProfile(); + + if ( profile == NULL ) { + continue; + } + + level += profile->GetLevel(); + numActiveMembers++; + } + + if ( numActiveMembers > 0 ) { + level /= (float)numActiveMembers; + } + + return Max( level, 1.0f ); +} + +/* +======================== +idLobby::QueueReliablePlayerToPlayerMessage +======================== +*/ +void idLobby::QueueReliablePlayerToPlayerMessage( int fromSessionUserIndex, int toSessionUserIndex, reliablePlayerToPlayer_t type, const byte * data, int dataLen ) { + + reliablePlayerToPlayerHeader_t info; + info.fromSessionUserIndex = fromSessionUserIndex; + info.toSessionUserIndex = toSessionUserIndex; + + // Some kind of pool allocator for packet buffers would be nice. + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg outmsg( buffer, sizeof( buffer ) ); + if ( !info.Write( this, outmsg ) ) { + idLib::Warning( "NET: Can't queue invalid reliable player to player msg" ); + return; + } + outmsg.WriteData( data, dataLen ); + + const lobbyUser_t * targetUser = GetLobbyUser( toSessionUserIndex ); + + if ( !verify( targetUser != NULL ) ) { + return; + } + + const int sendToPeer = IsHost() ? targetUser->peerIndex : host; + + QueueReliableMessage( sendToPeer, RELIABLE_PLAYER_TO_PLAYER_BEGIN + (int) type, outmsg.GetReadData(), outmsg.GetSize() ); +} + +/* +======================== +idLobby::KickLobbyUser +======================== +*/ +void idLobby::KickLobbyUser( lobbyUserID_t lobbyUserID ) { + if ( !IsHost() ) { + return; + } + + const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID ); + + lobbyUser_t * user = GetLobbyUser( lobbyUserIndex ); + + if ( user != NULL && !IsSessionUserLocal( user ) ) { + // Send an explicit kick msg, so they know why they were removed + if ( user->peerIndex >= 0 && user->peerIndex < peers.Num() ) { + byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + msg.WriteByte( lobbyUserIndex ); + QueueReliableMessage( user->peerIndex, RELIABLE_KICK_PLAYER, msg.GetReadData(), msg.GetSize() ); + } + } +} + +/* +======================== +idLobby::GetNumConnectedUsers +======================== +*/ +int idLobby::GetNumConnectedUsers() const { + int numConnectectUsers = 0; + + for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { + const lobbyUser_t * user = GetLobbyUser( i ); + + if ( user->IsDisconnected() ) { + continue; + } + + numConnectectUsers++; + } + + return numConnectectUsers; +} \ No newline at end of file diff --git a/neo/sys/sys_local.cpp b/neo/sys/sys_local.cpp new file mode 100644 index 00000000..6f5984a5 --- /dev/null +++ b/neo/sys/sys_local.cpp @@ -0,0 +1,270 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_local.h" + +const char * sysLanguageNames[] = { + ID_LANG_ENGLISH, ID_LANG_FRENCH, ID_LANG_ITALIAN, ID_LANG_GERMAN, ID_LANG_SPANISH, ID_LANG_JAPANESE, NULL +}; + +const int numLanguages = sizeof( sysLanguageNames ) / sizeof sysLanguageNames[ 0 ] - 1; + +idCVar sys_lang( "sys_lang", ID_LANG_ENGLISH, CVAR_SYSTEM | CVAR_INIT, "", sysLanguageNames, idCmdSystem::ArgCompletion_String ); + +idSysLocal sysLocal; +idSys * sys = &sysLocal; + +void idSysLocal::DebugPrintf( const char *fmt, ... ) { + va_list argptr; + + va_start( argptr, fmt ); + Sys_DebugVPrintf( fmt, argptr ); + va_end( argptr ); +} + +void idSysLocal::DebugVPrintf( const char *fmt, va_list arg ) { + Sys_DebugVPrintf( fmt, arg ); +} + +double idSysLocal::GetClockTicks() { + return Sys_GetClockTicks(); +} + +double idSysLocal::ClockTicksPerSecond() { + return Sys_ClockTicksPerSecond(); +} + +cpuid_t idSysLocal::GetProcessorId() { + return Sys_GetProcessorId(); +} + +const char *idSysLocal::GetProcessorString() { + return Sys_GetProcessorString(); +} + +const char *idSysLocal::FPU_GetState() { + return Sys_FPU_GetState(); +} + +bool idSysLocal::FPU_StackIsEmpty() { + return Sys_FPU_StackIsEmpty(); +} + +void idSysLocal::FPU_SetFTZ( bool enable ) { + Sys_FPU_SetFTZ( enable ); +} + +void idSysLocal::FPU_SetDAZ( bool enable ) { + Sys_FPU_SetDAZ( enable ); +} + +bool idSysLocal::LockMemory( void *ptr, int bytes ) { + return Sys_LockMemory( ptr, bytes ); +} + +bool idSysLocal::UnlockMemory( void *ptr, int bytes ) { + return Sys_UnlockMemory( ptr, bytes ); +} + +void idSysLocal::GetCallStack( address_t *callStack, const int callStackSize ) { + Sys_GetCallStack( callStack, callStackSize ); +} + +const char * idSysLocal::GetCallStackStr( const address_t *callStack, const int callStackSize ) { + return Sys_GetCallStackStr( callStack, callStackSize ); +} + +const char * idSysLocal::GetCallStackCurStr( int depth ) { + return Sys_GetCallStackCurStr( depth ); +} + +void idSysLocal::ShutdownSymbols() { + Sys_ShutdownSymbols(); +} + +int idSysLocal::DLL_Load( const char *dllName ) { + return Sys_DLL_Load( dllName ); +} + +void *idSysLocal::DLL_GetProcAddress( int dllHandle, const char *procName ) { + return Sys_DLL_GetProcAddress( dllHandle, procName ); +} + +void idSysLocal::DLL_Unload( int dllHandle ) { + Sys_DLL_Unload( dllHandle ); +} + +void idSysLocal::DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) { + idStr::snPrintf( dllName, maxLength, "%s" CPUSTRING ".dll", baseName ); +} + +sysEvent_t idSysLocal::GenerateMouseButtonEvent( int button, bool down ) { + sysEvent_t ev; + ev.evType = SE_KEY; + ev.evValue = K_MOUSE1 + button - 1; + ev.evValue2 = down; + ev.evPtrLength = 0; + ev.evPtr = NULL; + return ev; +} + +sysEvent_t idSysLocal::GenerateMouseMoveEvent( int deltax, int deltay ) { + sysEvent_t ev; + ev.evType = SE_MOUSE; + ev.evValue = deltax; + ev.evValue2 = deltay; + ev.evPtrLength = 0; + ev.evPtr = NULL; + return ev; +} + +void idSysLocal::FPU_EnableExceptions( int exceptions ) { + Sys_FPU_EnableExceptions( exceptions ); +} + +/* +================= +Sys_TimeStampToStr +================= +*/ +const char *Sys_TimeStampToStr( ID_TIME_T timeStamp ) { + static char timeString[MAX_STRING_CHARS]; + timeString[0] = '\0'; + + time_t ts = (time_t)timeStamp; + tm* time = localtime( &ts ); + if ( time == NULL ) { + return "??/??/???? ??:??"; + } + + idStr out; + + idStr lang = cvarSystem->GetCVarString( "sys_lang" ); + if ( lang.Icmp( ID_LANG_ENGLISH ) == 0 ) { + // english gets "month/day/year hour:min" + "am" or "pm" + out = va( "%02d", time->tm_mon + 1 ); + out += "/"; + out += va( "%02d", time->tm_mday ); + out += "/"; + out += va( "%d", time->tm_year + 1900 ); + out += " "; // changed to spaces since flash doesn't recognize \t + if ( time->tm_hour > 12 ) { + out += va( "%02d", time->tm_hour - 12 ); + } else if ( time->tm_hour == 0 ) { + out += "12"; + } else { + out += va( "%02d", time->tm_hour ); + } + out += ":"; + out +=va( "%02d", time->tm_min ); + if ( time->tm_hour >= 12 ) { + out += "pm"; + } else { + out += "am"; + } + } else { + // europeans get "day/month/year 24hour:min" + out = va( "%02d", time->tm_mday ); + out += "/"; + out += va( "%02d", time->tm_mon + 1 ); + out += "/"; + out += va( "%d", time->tm_year + 1900 ); + out += " "; // changed to spaces since flash doesn't recognize \t + out += va( "%02d", time->tm_hour ); + out += ":"; + out += va( "%02d", time->tm_min ); + } + idStr::Copynz( timeString, out, sizeof( timeString ) ); + + return timeString; +} + +/* +======================== +Sys_SecToStr +======================== +*/ +const char * Sys_SecToStr( int sec ) { + static char timeString[MAX_STRING_CHARS]; + + int weeks = sec / ( 3600 * 24 * 7 ); + sec -= weeks * ( 3600 * 24 * 7 ); + + int days = sec / ( 3600 * 24 ); + sec -= days * ( 3600 * 24 ); + + int hours = sec / 3600; + sec -= hours * 3600; + + int min = sec / 60; + sec -= min * 60; + + if ( weeks > 0 ) { + sprintf( timeString, "%dw, %dd, %d:%02d:%02d", weeks, days, hours, min, sec ); + } else if ( days > 0 ) { + sprintf( timeString, "%dd, %d:%02d:%02d", days, hours, min, sec ); + } else { + sprintf( timeString, "%d:%02d:%02d", hours, min, sec ); + } + + return timeString; +} + +// return number of supported languages +int Sys_NumLangs() { + return numLanguages; +} + +// get language name by index +const char * Sys_Lang( int idx ) { + if ( idx >= 0 && idx < numLanguages ) { + return sysLanguageNames[ idx ]; + } + return ""; +} + +const char * Sys_DefaultLanguage() { + // sku breakdowns are as follows + // EFIGS Digital + // EF S North America + // FIGS EU + // E UK + // JE Japan + + // If japanese exists, default to japanese + // else if english exists, defaults to english + // otherwise, french + + if ( !fileSystem->UsingResourceFiles() ) { + return ID_LANG_ENGLISH; + } + + return ID_LANG_ENGLISH; +} diff --git a/neo/sys/sys_local.h b/neo/sys/sys_local.h new file mode 100644 index 00000000..1efd5f7c --- /dev/null +++ b/neo/sys/sys_local.h @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SYS_LOCAL__ +#define __SYS_LOCAL__ + +/* +============================================================== + + idSysLocal + +============================================================== +*/ + +class idSysLocal : public idSys { +public: + virtual void DebugPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ); + virtual void DebugVPrintf( const char *fmt, va_list arg ); + + virtual double GetClockTicks(); + virtual double ClockTicksPerSecond(); + virtual cpuid_t GetProcessorId(); + virtual const char * GetProcessorString(); + virtual const char * FPU_GetState(); + virtual bool FPU_StackIsEmpty(); + virtual void FPU_SetFTZ( bool enable ); + virtual void FPU_SetDAZ( bool enable ); + + virtual void FPU_EnableExceptions( int exceptions ); + + virtual void GetCallStack( address_t *callStack, const int callStackSize ); + virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize ); + virtual const char * GetCallStackCurStr( int depth ); + virtual void ShutdownSymbols(); + + virtual bool LockMemory( void *ptr, int bytes ); + virtual bool UnlockMemory( void *ptr, int bytes ); + + virtual int DLL_Load( const char *dllName ); + virtual void * DLL_GetProcAddress( int dllHandle, const char *procName ); + virtual void DLL_Unload( int dllHandle ); + virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength ); + + virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down ); + virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay ); + + virtual void OpenURL( const char *url, bool quit ); + virtual void StartProcess( const char *exeName, bool quit ); +}; + +#endif /* !__SYS_LOCAL__ */ diff --git a/neo/sys/sys_localuser.cpp b/neo/sys/sys_localuser.cpp new file mode 100644 index 00000000..e8f803cd --- /dev/null +++ b/neo/sys/sys_localuser.cpp @@ -0,0 +1,192 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +extern idCVar fs_savepath; + +/* +======================== +idLocalUser::idLocalUser +======================== +*/ +idLocalUser::idLocalUser() { + memset( joiningLobby, 0, sizeof( joiningLobby ) ); + profileMgr.Init( this ); + syncAchievementsRequested = 0; +} + +void idLocalUser::Pump() { + // Pump the profile + GetProfileMgr().Pump(); + + if ( GetProfileMgr().GetProfile() != NULL && GetProfileMgr().GetProfile()->GetState() == idPlayerProfile::IDLE ) { + // Pump achievements + if ( syncAchievementsRequested ) { + if ( session->GetAchievementSystem().IsInitialized() ) { + session->GetAchievementSystem().SyncAchievementBits( this ); + syncAchievementsRequested = false; + } + } + session->GetAchievementSystem().Pump(); + } + + // Extra platform pump if necessary + PumpPlatform(); +} + +/* +======================== +idLocalUser::IsStorageDeviceAvailable +======================== +*/ +bool idLocalUser::IsStorageDeviceAvailable() const { + return saveGame_enable.GetBool(); +} + +/* +======================== +idLocalUser::ResetStorageDevice +======================== +*/ +void idLocalUser::ResetStorageDevice() { +} + +/* +======================== +idLocalUser::StorageSizeAvailable +======================== +*/ +bool idLocalUser::StorageSizeAvailable( uint64 minSizeInBytes, int64 & neededBytes ) { + int64 size = Sys_GetDriveFreeSpaceInBytes( fs_savepath.GetString() ); + + neededBytes = minSizeInBytes - size; + if ( neededBytes < 0 ) { + neededBytes = 0; + } + + return neededBytes == 0; +} + +/* +======================== +idLocalUser::SetStatInt +======================== +*/ +void idLocalUser::SetStatInt( int s, int v ) { + idPlayerProfile * profile = GetProfile(); + if ( profile != NULL ) { + return profile->StatSetInt( s, v ); + } +} + +/* +======================== +idLocalUser::SetStatFloat +======================== +*/ +void idLocalUser::SetStatFloat( int s, float v ) { + idPlayerProfile * profile = GetProfile(); + if ( profile != NULL ) { + return profile->StatSetFloat( s, v ); + } +} + +/* +======================== +idLocalUser::GetStatInt +======================== +*/ +int idLocalUser::GetStatInt( int s ) { + const idPlayerProfile * profile = GetProfile(); + + if ( profile != NULL && s >= 0 ) { + return profile->StatGetInt( s ); + } + + return 0; +} + +/* +======================== +idLocalUser::GetStatFloat +======================== +*/ +float idLocalUser::GetStatFloat( int s ) { + const idPlayerProfile * profile = GetProfile(); + + if ( profile != NULL ) { + return profile->StatGetFloat( s ); + } + + return 0.0f; +} + +/* +======================== +idLocalUser::LoadProfileSettings +======================== +*/ +void idLocalUser::LoadProfileSettings() { + idPlayerProfile * profile = GetProfileMgr().GetProfile(); + + // Lazy instantiation + if ( profile == NULL ) { + // Create a new profile + profile = idPlayerProfile::CreatePlayerProfile( GetInputDevice() ); + } + + if ( profile != NULL ) { + profile->LoadSettings(); + } + + return; +} + +/* +======================== +idLocalUser::SaveProfileSettings +======================== +*/ +void idLocalUser::SaveProfileSettings() { + idPlayerProfile * profile = GetProfileMgr().GetProfile(); + if ( profile != NULL ) { + profile->SaveSettings( true ); + } + + return; +} + +/* +======================== +localUserHandle_t::Serialize +======================== +*/ +void localUserHandle_t::Serialize( idSerializer & ser ) { + ser.Serialize( handle ); +} diff --git a/neo/sys/sys_localuser.h b/neo/sys/sys_localuser.h new file mode 100644 index 00000000..3a8107d5 --- /dev/null +++ b/neo/sys/sys_localuser.h @@ -0,0 +1,149 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_LOCALUSER_H__ +#define __SYS_LOCALUSER_H__ + +#include "sys_profile.h" + +struct achievementDescription_t; +class idPlayerProfile; +class idProfileMgr; + +enum onlineCaps_t { + CAP_IS_ONLINE = BIT( 0 ), + CAP_BLOCKED_PERMISSION = BIT( 1 ), + CAP_CAN_PLAY_ONLINE = BIT( 2 ), +}; + +class idSerializer; + +/* +================================================ +localUserHandle_t +================================================ +*/ +struct localUserHandle_t { +public: + typedef uint32 userHandleType_t; + + localUserHandle_t() : handle( 0 ) {} + + explicit localUserHandle_t( userHandleType_t handle_ ) : handle( handle_ ) {} + + bool operator == ( const localUserHandle_t & other ) const { + return handle == other.handle; + } + + bool operator < ( const localUserHandle_t & other ) const { + return handle < other.handle; + } + + bool IsValid() const { return handle > 0; } + + void WriteToMsg( idBitMsg & msg ) { + msg.WriteLong( handle ); + } + + void ReadFromMsg( const idBitMsg & msg ) { + handle = msg.ReadLong(); + } + + void Serialize( idSerializer & ser ); + +private: + userHandleType_t handle; +}; + +/* +================================================ +idLocalUser +An idLocalUser is a user holding a controller. +It represents someone controlling the menu or game. +They may not necessarily be in a game (which would be a session user of TYPE_GAME). +A controller user references an input device (which is a gamepad, keyboard, etc). +================================================ +*/ +class idLocalUser { +public: + idLocalUser(); + virtual ~idLocalUser() {} + + void Pump(); + virtual void PumpPlatform() = 0; + + virtual bool IsPersistent() const { return IsProfileReady(); } // True if this user is a persistent user, and can save stats, etc (signed in) + virtual bool IsProfileReady() const = 0; // True if IsPersistent is true AND profile is signed into LIVE service + virtual bool IsOnline() const = 0; // True if this user has online capabilities + virtual uint32 GetOnlineCaps() const = 0; // Returns combination of onlineCaps_t flags + virtual bool HasOwnerChanged() const { return false; } // Whether or not the original persistent owner has changed since it was first registered + virtual int GetInputDevice() const = 0; // Input device of controller + virtual const char * GetGamerTag() const = 0; // Gamertag of user + virtual bool IsInParty() const = 0; // True if the user is in a party (do we support this on pc and ps3? ) + virtual int GetPartyCount() const = 0; // Gets the amount of users in the party + + // Storage related + virtual bool IsStorageDeviceAvailable() const; // Only false if the player has chosen to play without a storage device, only possible on 360, if available, everything needs to check for available space + virtual void ResetStorageDevice(); + virtual bool StorageSizeAvailable( uint64 minSizeInBytes, int64 & neededBytes ); + + // These set stats within the profile as a enum/value pair + virtual void SetStatInt( int stat, int value ); + virtual void SetStatFloat( int stat, float value ); + virtual int GetStatInt( int stat ); + virtual float GetStatFloat( int stat); + + virtual idPlayerProfile * GetProfile() { return GetProfileMgr().GetProfile(); } + const idPlayerProfile * GetProfile() const { return const_cast< idLocalUser * >( this )->GetProfile(); } + + idProfileMgr & GetProfileMgr() { return profileMgr; } + + // Helper state to determine if the user is joining a party lobby or not + void SetJoiningLobby( int lobbyType, bool value ) { joiningLobby[lobbyType] = value; } + bool IsJoiningLobby( int lobbyType ) const { return joiningLobby[lobbyType]; } + + bool CanPlayOnline() const { return ( GetOnlineCaps() & CAP_CAN_PLAY_ONLINE ) > 0; } + + localUserHandle_t GetLocalUserHandle() const { return localUserHandle; } + void SetLocalUserHandle( localUserHandle_t newHandle ) { localUserHandle = newHandle; } + + // Creates a new profile if one not already there + void LoadProfileSettings(); + void SaveProfileSettings(); + + // Will attempt to sync the achievement bits between the server and the localUser when the achievement system is ready + void RequestSyncAchievements() { syncAchievementsRequested = true; } + +private: + bool joiningLobby[2]; + localUserHandle_t localUserHandle; + idProfileMgr profileMgr; + + bool syncAchievementsRequested; +}; + +#endif // __SYS_LOCALUSER_H__ diff --git a/neo/sys/sys_profile.cpp b/neo/sys/sys_profile.cpp new file mode 100644 index 00000000..92ffa22e --- /dev/null +++ b/neo/sys/sys_profile.cpp @@ -0,0 +1,430 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#define SAVEGAME_PROFILE_FILENAME "profile.bin" + +idCVar profile_verbose( "profile_verbose", "0", CVAR_BOOL, "Turns on debug spam for profiles" ); + +/* +================================================ +idProfileMgr +================================================ +*/ + +/* +======================== +idProfileMgr +======================== +*/ +idProfileMgr::idProfileMgr() : + profileSaveProcessor( new (TAG_SAVEGAMES) idSaveGameProcessorSaveProfile ), + profileLoadProcessor( new (TAG_SAVEGAMES) idSaveGameProcessorLoadProfile ), + profile( NULL ), + handle( 0 ) { +} + + +/* +================================================ +~idProfileMgr +================================================ +*/ +idProfileMgr::~idProfileMgr() { +} + +/* +======================== +idProfileMgr::Init +======================== +*/ +void idProfileMgr::Init( idLocalUser * user_ ) { + user = user_; + handle = 0; +} + +/* +======================== +idProfileMgr::Pump +======================== +*/ +void idProfileMgr::Pump() { + // profile can be NULL if we forced the user to register as in the case of map-ing into a level from the press start screen + if ( profile == NULL ) { + return; + } + + // See if we are done with saving/loading the profile + bool saving = profile->GetState() == idPlayerProfile::SAVING; + bool loading = profile->GetState() == idPlayerProfile::LOADING; + if ( ( saving || loading ) && session->IsSaveGameCompletedFromHandle( handle ) ) { + profile->SetState( idPlayerProfile::IDLE ); + + if ( saving ) { + // Done saving + } else if ( loading ) { + // Done loading + const idSaveLoadParms & parms = profileLoadProcessor->GetParms(); + if ( parms.GetError() == SAVEGAME_E_FOLDER_NOT_FOUND || parms.GetError() == SAVEGAME_E_FILE_NOT_FOUND ) { + profile->SaveSettings( true ); + } else if ( parms.GetError() == SAVEGAME_E_CORRUPTED ) { + idLib::Warning( "Profile corrupt, creating a new one..." ); + common->Dialog().AddDialog( GDM_CORRUPT_PROFILE, DIALOG_CONTINUE, NULL, NULL, false ); + profile->SetDefaults(); + profile->SaveSettings( true ); + } else if ( parms.GetError() != SAVEGAME_E_NONE ) { + profile->SetState( idPlayerProfile::ERR ); + } + + session->OnLocalUserProfileLoaded( user ); + } + } else if ( saving || loading ) { + return; + } + + // See if we need to save/load the profile + if ( profile->GetRequestedState() == idPlayerProfile::SAVE_REQUESTED && profile->IsDirty() ) { + profile->MarkDirty( false ); + SaveSettingsAsync(); + // Syncs the steam data + //session->StoreStats(); + profile->SetRequestedState( idPlayerProfile::IDLE ); + } else if ( profile->GetRequestedState() == idPlayerProfile::LOAD_REQUESTED ) { + LoadSettingsAsync(); + profile->SetRequestedState( idPlayerProfile::IDLE ); + } +} + +/* +======================== +idProfileMgr::GetProfile +======================== +*/ +idPlayerProfile * idProfileMgr::GetProfile() { + assert( user != NULL ); + if ( profile == NULL ) { + // Lazy instantiation + // Create a new profile + profile = idPlayerProfile::CreatePlayerProfile( user->GetInputDevice() ); + if ( profile == NULL ) { + return NULL; + } + } + + bool loading = ( profile->GetState() == idPlayerProfile::LOADING ) || ( profile->GetRequestedState() == idPlayerProfile::LOAD_REQUESTED ); + if ( loading ) { + return NULL; + } + + return profile; +} + +/* +======================== +idProfileMgr::SaveSettingsAsync +======================== +*/ +void idProfileMgr::SaveSettingsAsync() { + if ( !saveGame_enable.GetBool() ) { + idLib::Warning( "Skipping profile save because saveGame_enable = 0" ); + } + + if ( GetProfile() != NULL ) { + // Issue the async save... + if ( profileSaveProcessor->InitSaveProfile( profile, "" ) ) { + + + profileSaveProcessor->AddCompletedCallback( MakeCallback( this, &idProfileMgr::OnSaveSettingsCompleted, &profileSaveProcessor->GetParmsNonConst() ) ); + handle = session->GetSaveGameManager().ExecuteProcessor( profileSaveProcessor.get() ); + profile->SetState( idPlayerProfile::SAVING ); + + } + } else { + idLib::Warning( "Not saving profile, profile is NULL." ); + } +} + +/* +======================== +idProfileMgr::LoadSettingsAsync +======================== +*/ +void idProfileMgr::LoadSettingsAsync() { + if ( profile != NULL && saveGame_enable.GetBool() ) { + if ( profileLoadProcessor->InitLoadProfile( profile, "" ) ) { + // Skip the not found error because this might be the first time to play the game! + profileLoadProcessor->SetSkipSystemErrorDialogMask( SAVEGAME_E_FOLDER_NOT_FOUND | SAVEGAME_E_FILE_NOT_FOUND ); + + profileLoadProcessor->AddCompletedCallback( MakeCallback( this, &idProfileMgr::OnLoadSettingsCompleted, &profileLoadProcessor->GetParmsNonConst() ) ); + handle = session->GetSaveGameManager().ExecuteProcessor( profileLoadProcessor.get() ); + profile->SetState( idPlayerProfile::LOADING ); + + + } + } else { + // If not able to save the profile, just change the state and leave + if ( profile == NULL ) { + idLib::Warning( "Not loading profile, profile is NULL." ); + } + if ( !saveGame_enable.GetBool() ) { + idLib::Warning( "Skipping profile load because saveGame_enable = 0" ); + } + } +} + +/* +======================== +idProfileMgr::OnLoadSettingsCompleted +======================== +*/ +void idProfileMgr::OnLoadSettingsCompleted( idSaveLoadParms * parms ) { + + + + + + // Don't process if error already detected + if ( parms->errorCode != SAVEGAME_E_NONE ) { + return; + } + + // Serialize the loaded profile + idFile_SaveGame ** profileFileContainer = FindFromGenericPtr( parms->files, SAVEGAME_PROFILE_FILENAME ); + idFile_SaveGame * profileFile = profileFileContainer == NULL ? NULL : *profileFileContainer; + + bool foundProfile = profileFile != NULL && profileFile->Length() > 0; + + if ( foundProfile ) { + idTempArray< byte > buffer( MAX_PROFILE_SIZE ); + + // Serialize settings from this buffer + profileFile->MakeReadOnly(); + unsigned int originalChecksum; + profileFile->ReadBig( originalChecksum ); + + int dataLength = profileFile->Length() - (int)sizeof( originalChecksum ); + profileFile->ReadBigArray( buffer.Ptr(), dataLength ); + + // Validate the checksum before we let the game serialize the settings + unsigned int checksum = MD5_BlockChecksum( buffer.Ptr(), dataLength ); + if ( originalChecksum != checksum ) { + idLib::Warning( "Checksum: 0x%08x, originalChecksum: 0x%08x, size = %d", checksum, originalChecksum, dataLength ); + parms->errorCode = SAVEGAME_E_CORRUPTED; + } else { + idBitMsg msg; + msg.InitRead( buffer.Ptr(), (int)buffer.Size() ); + idSerializer ser( msg, false ); + if ( !profile->Serialize( ser ) ) { + parms->errorCode = SAVEGAME_E_CORRUPTED; + } + } + + } else { + parms->errorCode = SAVEGAME_E_FILE_NOT_FOUND; + } +} + +/* +======================== +idProfileMgr::OnSaveSettingsCompleted +======================== +*/ +void idProfileMgr::OnSaveSettingsCompleted( idSaveLoadParms * parms ) { + common->Dialog().ShowSaveIndicator( false ); + + if ( parms->GetError() != SAVEGAME_E_NONE ) { + common->Dialog().AddDialog( GDM_PROFILE_SAVE_ERROR, DIALOG_CONTINUE, NULL, NULL, false ); + } + if ( game ) { + game->Shell_UpdateSavedGames(); + } +} + +/* +================================================ +idSaveGameProcessorSaveProfile +================================================ +*/ + +/* +======================== +idSaveGameProcessorSaveProfile::idSaveGameProcessorSaveProfile +======================== +*/ +idSaveGameProcessorSaveProfile::idSaveGameProcessorSaveProfile() { + profileFile = NULL; + profile = NULL; + +} + +/* +======================== +idSaveGameProcessorSaveProfile::InitSaveProfile +======================== +*/ +bool idSaveGameProcessorSaveProfile::InitSaveProfile( idPlayerProfile * profile_, const char * folder ) { + // Serialize the profile and pass a file to the processor + profileFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_PROFILE_FILENAME, SAVEGAMEFILE_BINARY | SAVEGAMEFILE_AUTO_DELETE ); + profileFile->MakeWritable(); + profileFile->SetMaxLength( MAX_PROFILE_SIZE ); + + // Create a serialization object and let the game serialize the settings into the buffer + const int serializeSize = MAX_PROFILE_SIZE - 8; // -8 for checksum (all platforms) and length (on 360) + idTempArray< byte > buffer( serializeSize ); + idBitMsg msg; + msg.InitWrite( buffer.Ptr(), serializeSize ); + idSerializer ser( msg, true ); + profile_->Serialize( ser ); + + // Get and write the checksum & length first + unsigned int checksum = MD5_BlockChecksum( msg.GetReadData(), msg.GetSize() ); + profileFile->WriteBig( checksum ); + + idLib::PrintfIf( profile_verbose.GetBool(), "checksum: 0x%08x, length: %d\n", checksum, msg.GetSize() ); + + // Add data to the file and prepare for save + profileFile->Write( msg.GetReadData(), msg.GetSize() ); + profileFile->MakeReadOnly(); + + saveFileEntryList_t files; + files.Append( profileFile ); + + idSaveGameDetails description; + if ( !idSaveGameProcessorSaveFiles::InitSave( folder, files, description, idSaveGameManager::PACKAGE_PROFILE ) ) { + return false; + } + + + profile = profile_; + + + return true; +} + +/* +======================== +idSaveGameProcessorSaveProfile::Process +======================== +*/ +bool idSaveGameProcessorSaveProfile::Process() { + + + // Files already setup for save, just execute as normal files + return idSaveGameProcessorSaveFiles::Process(); + +} + + +/* +================================================ +idSaveGameProcessorLoadProfile +================================================ +*/ + +/* +======================== +idSaveGameProcessorLoadProfile::idSaveGameProcessorLoadProfile +======================== +*/ +idSaveGameProcessorLoadProfile::idSaveGameProcessorLoadProfile() { + profileFile = NULL; + profile = NULL; + +} + +/* +======================== +idSaveGameProcessorLoadProfile::~idSaveGameProcessorLoadProfile +======================== +*/ +idSaveGameProcessorLoadProfile::~idSaveGameProcessorLoadProfile() { +} + +/* +======================== +idSaveGameProcessorLoadProfile::InitLoadFiles +======================== +*/ +bool idSaveGameProcessorLoadProfile::InitLoadProfile( idPlayerProfile * profile_, const char * folder_ ) { + if ( !idSaveGameProcessor::Init() ) { + return false; + } + + parms.directory = AddSaveFolderPrefix( folder_, idSaveGameManager::PACKAGE_PROFILE ); + parms.description.slotName = folder_; + parms.mode = SAVEGAME_MBF_LOAD; + + profileFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_PROFILE_FILENAME, SAVEGAMEFILE_BINARY | SAVEGAMEFILE_AUTO_DELETE ); + parms.files.Append( profileFile ); + + profile = profile_; + + + return true; +} + +/* +======================== +idSaveGameProcessorLoadProfile::Process +======================== +*/ +bool idSaveGameProcessorLoadProfile::Process() { + + + return idSaveGameProcessorLoadFiles::Process(); + +} + + +/* +======================== +Sys_SaveGameProfileCheck +======================== +*/ +bool Sys_SaveGameProfileCheck() { + bool exists = false; + const char * saveFolder = "savegame"; + + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFiles( saveFolder, SAVEGAME_PROFILE_FILENAME ); + const idStrList & fileList = files->GetList(); + + for ( int i = 0; i < fileList.Num(); i++ ) { + idStr filename = fileList[i]; + if ( filename == SAVEGAME_PROFILE_FILENAME ) { + exists = true; + break; + } + } + + fileSystem->FreeFileList( files ); + } + + return exists; +} diff --git a/neo/sys/sys_profile.h b/neo/sys/sys_profile.h new file mode 100644 index 00000000..f704b98e --- /dev/null +++ b/neo/sys/sys_profile.h @@ -0,0 +1,117 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_PROFILE_H__ +#define __SYS_PROFILE_H__ + +#include "sys_savegame.h" +#include "sys_session_savegames.h" + + +class idSaveGameProcessorSaveProfile; +class idSaveGameProcessorLoadProfile; +class idLocalUser; +class idPlayerProfile; + +/* +================================================ +idProfileMgr +================================================ +*/ +class idProfileMgr { +public: + idProfileMgr(); + ~idProfileMgr(); + + // Called the first time it's asked to load + void Init( idLocalUser * user ); + + void Pump(); + idPlayerProfile * GetProfile(); + +private: + void LoadSettingsAsync(); + void SaveSettingsAsync(); + + void OnLoadSettingsCompleted( idSaveLoadParms * parms ); + void OnSaveSettingsCompleted( idSaveLoadParms * parms ); + +private: + std::auto_ptr< idSaveGameProcessorSaveProfile > profileSaveProcessor; + std::auto_ptr< idSaveGameProcessorLoadProfile > profileLoadProcessor; + + idLocalUser * user; // reference passed in + idPlayerProfile * profile; + saveGameHandle_t handle; +}; + +/* +================================================ +idSaveGameProcessorSaveProfile +================================================ +*/ +class idSaveGameProcessorSaveProfile : public idSaveGameProcessorSaveFiles { +public: + DEFINE_CLASS( idSaveGameProcessorSaveProfile ); + + idSaveGameProcessorSaveProfile(); + + bool InitSaveProfile( idPlayerProfile * profile, const char * folder ); + virtual bool Process(); + +private: + idFile_SaveGame * profileFile; + idPlayerProfile * profile; + +}; + +/* +================================================ +idSaveGameProcessorLoadProfile +================================================ +*/ +class idSaveGameProcessorLoadProfile : public idSaveGameProcessorLoadFiles { +public: + DEFINE_CLASS( idSaveGameProcessorLoadProfile ); + + idSaveGameProcessorLoadProfile(); + ~idSaveGameProcessorLoadProfile(); + + bool InitLoadProfile( idPlayerProfile * profile, const char * folder ); + virtual bool Process(); + + +private: + idFile_SaveGame * profileFile; + idPlayerProfile * profile; + +}; + +// Synchronous check, just checks if a profile exists within the savegame location +bool Sys_SaveGameProfileCheck(); + +#endif \ No newline at end of file diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h new file mode 100644 index 00000000..61831ebc --- /dev/null +++ b/neo/sys/sys_public.h @@ -0,0 +1,726 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SYS_PUBLIC__ +#define __SYS_PUBLIC__ + +#include "../idlib/CmdArgs.h" + +/* +=============================================================================== + + Non-portable system services. + +=============================================================================== +*/ + +enum cpuid_t { + CPUID_NONE = 0x00000, + CPUID_UNSUPPORTED = 0x00001, // unsupported (386/486) + CPUID_GENERIC = 0x00002, // unrecognized processor + CPUID_INTEL = 0x00004, // Intel + CPUID_AMD = 0x00008, // AMD + CPUID_MMX = 0x00010, // Multi Media Extensions + CPUID_3DNOW = 0x00020, // 3DNow! + CPUID_SSE = 0x00040, // Streaming SIMD Extensions + CPUID_SSE2 = 0x00080, // Streaming SIMD Extensions 2 + CPUID_SSE3 = 0x00100, // Streaming SIMD Extentions 3 aka Prescott's New Instructions + CPUID_ALTIVEC = 0x00200, // AltiVec + CPUID_HTT = 0x01000, // Hyper-Threading Technology + CPUID_CMOV = 0x02000, // Conditional Move (CMOV) and fast floating point comparison (FCOMI) instructions + CPUID_FTZ = 0x04000, // Flush-To-Zero mode (denormal results are flushed to zero) + CPUID_DAZ = 0x08000, // Denormals-Are-Zero mode (denormal source operands are set to zero) + CPUID_XENON = 0x10000, // Xbox 360 + CPUID_CELL = 0x20000 // PS3 +}; + +enum fpuExceptions_t { + FPU_EXCEPTION_INVALID_OPERATION = 1, + FPU_EXCEPTION_DENORMALIZED_OPERAND = 2, + FPU_EXCEPTION_DIVIDE_BY_ZERO = 4, + FPU_EXCEPTION_NUMERIC_OVERFLOW = 8, + FPU_EXCEPTION_NUMERIC_UNDERFLOW = 16, + FPU_EXCEPTION_INEXACT_RESULT = 32 +}; + +enum fpuPrecision_t { + FPU_PRECISION_SINGLE = 0, + FPU_PRECISION_DOUBLE = 1, + FPU_PRECISION_DOUBLE_EXTENDED = 2 +}; + +enum fpuRounding_t { + FPU_ROUNDING_TO_NEAREST = 0, + FPU_ROUNDING_DOWN = 1, + FPU_ROUNDING_UP = 2, + FPU_ROUNDING_TO_ZERO = 3 +}; + +enum joystickAxis_t { + AXIS_LEFT_X, + AXIS_LEFT_Y, + AXIS_RIGHT_X, + AXIS_RIGHT_Y, + AXIS_LEFT_TRIG, + AXIS_RIGHT_TRIG, + MAX_JOYSTICK_AXIS +}; + +enum sysEventType_t { + SE_NONE, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_MOUSE_ABSOLUTE, // evValue and evValue2 are absolute coordinates in the window's client area. + SE_MOUSE_LEAVE, // evValue and evValue2 are meaninless, this indicates the mouse has left the client area. + SE_JOYSTICK, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE // evPtr is a char*, from typing something at a non-game console +}; + +enum sys_mEvents { + M_ACTION1, + M_ACTION2, + M_ACTION3, + M_ACTION4, + M_ACTION5, + M_ACTION6, + M_ACTION7, + M_ACTION8, + M_DELTAX, + M_DELTAY, + M_DELTAZ, + M_INVALID +}; + +enum sys_jEvents { + J_ACTION1, + J_ACTION2, + J_ACTION3, + J_ACTION4, + J_ACTION5, + J_ACTION6, + J_ACTION7, + J_ACTION8, + J_ACTION9, + J_ACTION10, + J_ACTION11, + J_ACTION12, + J_ACTION13, + J_ACTION14, + J_ACTION15, + J_ACTION16, + J_ACTION17, + J_ACTION18, + J_ACTION19, + J_ACTION20, + J_ACTION21, + J_ACTION22, + J_ACTION23, + J_ACTION24, + J_ACTION25, + J_ACTION26, + J_ACTION27, + J_ACTION28, + J_ACTION29, + J_ACTION30, + J_ACTION31, + J_ACTION32, + J_ACTION_MAX = J_ACTION32, + + J_AXIS_MIN, + J_AXIS_LEFT_X = J_AXIS_MIN + AXIS_LEFT_X, + J_AXIS_LEFT_Y = J_AXIS_MIN + AXIS_LEFT_Y, + J_AXIS_RIGHT_X = J_AXIS_MIN + AXIS_RIGHT_X, + J_AXIS_RIGHT_Y = J_AXIS_MIN + AXIS_RIGHT_Y, + J_AXIS_LEFT_TRIG = J_AXIS_MIN + AXIS_LEFT_TRIG, + J_AXIS_RIGHT_TRIG = J_AXIS_MIN + AXIS_RIGHT_TRIG, + + J_AXIS_MAX = J_AXIS_MIN + MAX_JOYSTICK_AXIS - 1, + + J_DPAD_UP, + J_DPAD_DOWN, + J_DPAD_LEFT, + J_DPAD_RIGHT, + + MAX_JOY_EVENT +}; + +/* +================================================ +The first part of this table maps directly to Direct Input scan codes (DIK_* from dinput.h) +But they are duplicated here for console portability +================================================ +*/ +enum keyNum_t { + K_NONE, + + K_ESCAPE, + K_1, + K_2, + K_3, + K_4, + K_5, + K_6, + K_7, + K_8, + K_9, + K_0, + K_MINUS, + K_EQUALS, + K_BACKSPACE, + K_TAB, + K_Q, + K_W, + K_E, + K_R, + K_T, + K_Y, + K_U, + K_I, + K_O, + K_P, + K_LBRACKET, + K_RBRACKET, + K_ENTER, + K_LCTRL, + K_A, + K_S, + K_D, + K_F, + K_G, + K_H, + K_J, + K_K, + K_L, + K_SEMICOLON, + K_APOSTROPHE, + K_GRAVE, + K_LSHIFT, + K_BACKSLASH, + K_Z, + K_X, + K_C, + K_V, + K_B, + K_N, + K_M, + K_COMMA, + K_PERIOD, + K_SLASH, + K_RSHIFT, + K_KP_STAR, + K_LALT, + K_SPACE, + K_CAPSLOCK, + K_F1, + K_F2, + K_F3, + K_F4, + K_F5, + K_F6, + K_F7, + K_F8, + K_F9, + K_F10, + K_NUMLOCK, + K_SCROLL, + K_KP_7, + K_KP_8, + K_KP_9, + K_KP_MINUS, + K_KP_4, + K_KP_5, + K_KP_6, + K_KP_PLUS, + K_KP_1, + K_KP_2, + K_KP_3, + K_KP_0, + K_KP_DOT, + K_F11 = 0x57, + K_F12 = 0x58, + K_F13 = 0x64, + K_F14 = 0x65, + K_F15 = 0x66, + K_KANA = 0x70, + K_CONVERT = 0x79, + K_NOCONVERT = 0x7B, + K_YEN = 0x7D, + K_KP_EQUALS = 0x8D, + K_CIRCUMFLEX = 0x90, + K_AT = 0x91, + K_COLON = 0x92, + K_UNDERLINE = 0x93, + K_KANJI = 0x94, + K_STOP = 0x95, + K_AX = 0x96, + K_UNLABELED = 0x97, + K_KP_ENTER = 0x9C, + K_RCTRL = 0x9D, + K_KP_COMMA = 0xB3, + K_KP_SLASH = 0xB5, + K_PRINTSCREEN = 0xB7, + K_RALT = 0xB8, + K_PAUSE = 0xC5, + K_HOME = 0xC7, + K_UPARROW = 0xC8, + K_PGUP = 0xC9, + K_LEFTARROW = 0xCB, + K_RIGHTARROW = 0xCD, + K_END = 0xCF, + K_DOWNARROW = 0xD0, + K_PGDN = 0xD1, + K_INS = 0xD2, + K_DEL = 0xD3, + K_LWIN = 0xDB, + K_RWIN = 0xDC, + K_APPS = 0xDD, + K_POWER = 0xDE, + K_SLEEP = 0xDF, + + //------------------------ + // K_JOY codes must be contiguous, too + //------------------------ + + K_JOY1 = 256, + K_JOY2, + K_JOY3, + K_JOY4, + K_JOY5, + K_JOY6, + K_JOY7, + K_JOY8, + K_JOY9, + K_JOY10, + K_JOY11, + K_JOY12, + K_JOY13, + K_JOY14, + K_JOY15, + K_JOY16, + + K_JOY_STICK1_UP, + K_JOY_STICK1_DOWN, + K_JOY_STICK1_LEFT, + K_JOY_STICK1_RIGHT, + + K_JOY_STICK2_UP, + K_JOY_STICK2_DOWN, + K_JOY_STICK2_LEFT, + K_JOY_STICK2_RIGHT, + + K_JOY_TRIGGER1, + K_JOY_TRIGGER2, + + K_JOY_DPAD_UP, + K_JOY_DPAD_DOWN, + K_JOY_DPAD_LEFT, + K_JOY_DPAD_RIGHT, + + //------------------------ + // K_MOUSE enums must be contiguous (no char codes in the middle) + //------------------------ + + K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + + K_MWHEELDOWN, + K_MWHEELUP, + + K_LAST_KEY +}; + +struct sysEvent_t { + sysEventType_t evType; + int evValue; + int evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void * evPtr; // this must be manually freed if not NULL + + int inputDevice; + bool IsKeyEvent() const { return evType == SE_KEY; } + bool IsMouseEvent() const { return evType == SE_MOUSE; } + bool IsCharEvent() const { return evType == SE_CHAR; } + bool IsJoystickEvent() const { return evType == SE_JOYSTICK; } + bool IsKeyDown() const { return evValue2 != 0; } + keyNum_t GetKey() const { return static_cast< keyNum_t >( evValue ); } + int GetXCoord() const { return evValue; } + int GetYCoord() const { return evValue2; } +}; + +struct sysMemoryStats_t { + int memoryLoad; + int totalPhysical; + int availPhysical; + int totalPageFile; + int availPageFile; + int totalVirtual; + int availVirtual; + int availExtendedVirtual; +}; + +typedef unsigned long address_t; + +void Sys_Init(); +void Sys_Shutdown(); +void Sys_Error( const char *error, ...); +const char * Sys_GetCmdLine(); +void Sys_ReLaunch( void * launchData, unsigned int launchDataSize ); +void Sys_Launch( const char * path, idCmdArgs & args, void * launchData, unsigned int launchDataSize ); +void Sys_SetLanguageFromSystem(); +const char * Sys_DefaultLanguage(); +void Sys_Quit(); + +bool Sys_AlreadyRunning(); + +// note that this isn't journaled... +char * Sys_GetClipboardData(); +void Sys_SetClipboardData( const char *string ); + +// will go to the various text consoles +// NOT thread safe - never use in the async paths +void Sys_Printf( VERIFY_FORMAT_STRING const char *msg, ... ); + +// guaranteed to be thread-safe +void Sys_DebugPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ); +void Sys_DebugVPrintf( const char *fmt, va_list arg ); + +// a decent minimum sleep time to avoid going below the process scheduler speeds +#define SYS_MINSLEEP 20 + +// allow game to yield CPU time +// NOTE: due to SYS_MINSLEEP this is very bad portability karma, and should be completely removed +void Sys_Sleep( int msec ); + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds(); +uint64 Sys_Microseconds(); + +// for accurate performance testing +double Sys_GetClockTicks(); +double Sys_ClockTicksPerSecond(); + +// returns a selection of the CPUID_* flags +cpuid_t Sys_GetProcessorId(); +const char * Sys_GetProcessorString(); + +// returns true if the FPU stack is empty +bool Sys_FPU_StackIsEmpty(); + +// empties the FPU stack +void Sys_FPU_ClearStack(); + +// returns the FPU state as a string +const char * Sys_FPU_GetState(); + +// enables the given FPU exceptions +void Sys_FPU_EnableExceptions( int exceptions ); + +// sets the FPU precision +void Sys_FPU_SetPrecision( int precision ); + +// sets the FPU rounding mode +void Sys_FPU_SetRounding( int rounding ); + +// sets Flush-To-Zero mode (only available when CPUID_FTZ is set) +void Sys_FPU_SetFTZ( bool enable ); + +// sets Denormals-Are-Zero mode (only available when CPUID_DAZ is set) +void Sys_FPU_SetDAZ( bool enable ); + +// returns amount of system ram +int Sys_GetSystemRam(); + +// returns amount of video ram +int Sys_GetVideoRam(); + +// returns amount of drive space in path +int Sys_GetDriveFreeSpace( const char *path ); + +// returns amount of drive space in path in bytes +int64 Sys_GetDriveFreeSpaceInBytes( const char * path ); + +// returns memory stats +void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ); +void Sys_GetExeLaunchMemoryStatus( sysMemoryStats_t &stats ); + +// lock and unlock memory +bool Sys_LockMemory( void *ptr, int bytes ); +bool Sys_UnlockMemory( void *ptr, int bytes ); + +// set amount of physical work memory +void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ); + +// allows retrieving the call stack at execution points +void Sys_GetCallStack( address_t *callStack, const int callStackSize ); +const char * Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ); +const char * Sys_GetCallStackCurStr( int depth ); +const char * Sys_GetCallStackCurAddressStr( int depth ); +void Sys_ShutdownSymbols(); + +// DLL loading, the path should be a fully qualified OS path to the DLL file to be loaded +int Sys_DLL_Load( const char *dllName ); +void * Sys_DLL_GetProcAddress( int dllHandle, const char *procName ); +void Sys_DLL_Unload( int dllHandle ); + +// event generation +void Sys_GenerateEvents(); +sysEvent_t Sys_GetEvent(); +void Sys_ClearEvents(); + +// input is tied to windows, so it needs to be started up and shut down whenever +// the main window is recreated +void Sys_InitInput(); +void Sys_ShutdownInput(); + +// keyboard input polling +int Sys_PollKeyboardInputEvents(); +int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ); +void Sys_EndKeyboardInputEvents(); + +// mouse input polling +static const int MAX_MOUSE_EVENTS = 256; +int Sys_PollMouseInputEvents( int mouseEvents[MAX_MOUSE_EVENTS][2] ); + +// joystick input polling +void Sys_SetRumble( int device, int low, int hi ); +int Sys_PollJoystickInputEvents( int deviceNum ); +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ); +void Sys_EndJoystickInputEvents(); + +// when the console is down, or the game is about to perform a lengthy +// operation like map loading, the system can release the mouse cursor +// when in windowed mode +void Sys_GrabMouseCursor( bool grabIt ); + +void Sys_ShowWindow( bool show ); +bool Sys_IsWindowVisible(); +void Sys_ShowConsole( int visLevel, bool quitOnClose ); + +// This really isn't the right place to have this, but since this is the 'top level' include +// and has a function signature with 'FILE' in it, it kinda needs to be here =/ +typedef HANDLE idFileHandle; + + +ID_TIME_T Sys_FileTimeStamp( idFileHandle fp ); +// NOTE: do we need to guarantee the same output on all platforms? +const char * Sys_TimeStampToStr( ID_TIME_T timeStamp ); +const char * Sys_SecToStr( int sec ); + +const char * Sys_DefaultBasePath(); +const char * Sys_DefaultSavePath(); + +// know early if we are performing a fatal error shutdown so the error message doesn't get lost +void Sys_SetFatalError( const char *error ); + +// Execute the specified process and wait until it's done, calling workFn every waitMS milliseconds. +// If showOutput == true, std IO from the executed process will be output to the console. +// Note that the return value is not an indication of the exit code of the process, but is false +// only if the process could not be created at all. If you wish to check the exit code of the +// spawned process, check the value returned in exitCode. +typedef bool ( *execProcessWorkFunction_t )(); +typedef void ( *execOutputFunction_t)( const char * text ); +bool Sys_Exec( const char * appPath, const char * workingPath, const char * args, + execProcessWorkFunction_t workFn, execOutputFunction_t outputFn, const int waitMS, + unsigned int & exitCode ); + +// localization + +#define ID_LANG_ENGLISH "english" +#define ID_LANG_FRENCH "french" +#define ID_LANG_ITALIAN "italian" +#define ID_LANG_GERMAN "german" +#define ID_LANG_SPANISH "spanish" +#define ID_LANG_JAPANESE "japanese" +int Sys_NumLangs(); +const char * Sys_Lang( int idx ); + +/* +============================================================== + + Networking + +============================================================== +*/ + +typedef enum { + NA_BAD, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP +} netadrtype_t; + +typedef struct { + netadrtype_t type; + unsigned char ip[4]; + unsigned short port; +} netadr_t; + +#define PORT_ANY -1 + +/* +================================================ +idUDP +================================================ +*/ +class idUDP { +public: + // this just zeros netSocket and port + idUDP(); + virtual ~idUDP(); + + // if the InitForPort fails, the idUDP.port field will remain 0 + bool InitForPort( int portNumber ); + + int GetPort() const { return bound_to.port; } + netadr_t GetAdr() const { return bound_to; } + uint32 GetUIntAdr() const { return ( bound_to.ip[0] | bound_to.ip[1] << 8 | bound_to.ip[2] << 16 | bound_to.ip[3] << 24 ); } + void Close(); + + bool GetPacket( netadr_t &from, void *data, int &size, int maxSize ); + + bool GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize, + int timeout ); + + void SendPacket( const netadr_t to, const void *data, int size ); + + void SetSilent( bool silent ) { this->silent = silent; } + bool GetSilent() const { return silent; } + + int packetsRead; + int bytesRead; + + int packetsWritten; + int bytesWritten; + + bool IsOpen() const { return netSocket > 0; } + +private: + netadr_t bound_to; // interface and port + int netSocket; // OS specific socket + bool silent; // don't emit anything ( black hole ) +}; + + + + // parses the port number + // can also do DNS resolve if you ask for it. + // NOTE: DNS resolve is a slow/blocking call, think before you use + // ( could be exploited for server DoS ) +bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve ); +const char * Sys_NetAdrToString( const netadr_t a ); +bool Sys_IsLANAddress( const netadr_t a ); +bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ); + +int Sys_GetLocalIPCount(); +const char * Sys_GetLocalIP( int i ); + +void Sys_InitNetworking(); +void Sys_ShutdownNetworking(); + + + +/* +================================================ +idJoystick is managed by each platform's local Sys implementation, and +provides full *Joy Pad* support (the most common device, these days). +================================================ +*/ +class idJoystick { +public: + virtual ~idJoystick() { } + + virtual bool Init() { return false; } + virtual void Shutdown() { } + virtual void Deactivate() { } + virtual void SetRumble( int deviceNum, int rumbleLow, int rumbleHigh ) { } + virtual int PollInputEvents( int inputDeviceNum ) { return 0; } + virtual int ReturnInputEvent( const int n, int &action, int &value ) { return 0; } + virtual void EndInputEvents() { } +}; + + + +/* +============================================================== + + idSys + +============================================================== +*/ + +class idSys { +public: + virtual void DebugPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0; + virtual void DebugVPrintf( const char *fmt, va_list arg ) = 0; + + virtual double GetClockTicks() = 0; + virtual double ClockTicksPerSecond() = 0; + virtual cpuid_t GetProcessorId() = 0; + virtual const char * GetProcessorString() = 0; + virtual const char * FPU_GetState() = 0; + virtual bool FPU_StackIsEmpty() = 0; + virtual void FPU_SetFTZ( bool enable ) = 0; + virtual void FPU_SetDAZ( bool enable ) = 0; + + virtual void FPU_EnableExceptions( int exceptions ) = 0; + + virtual bool LockMemory( void *ptr, int bytes ) = 0; + virtual bool UnlockMemory( void *ptr, int bytes ) = 0; + + virtual void GetCallStack( address_t *callStack, const int callStackSize ) = 0; + virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize ) = 0; + virtual const char * GetCallStackCurStr( int depth ) = 0; + virtual void ShutdownSymbols() = 0; + + virtual int DLL_Load( const char *dllName ) = 0; + virtual void * DLL_GetProcAddress( int dllHandle, const char *procName ) = 0; + virtual void DLL_Unload( int dllHandle ) = 0; + virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) = 0; + + virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down ) = 0; + virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay ) = 0; + + virtual void OpenURL( const char *url, bool quit ) = 0; + virtual void StartProcess( const char *exePath, bool quit ) = 0; +}; + +extern idSys * sys; + +bool Sys_LoadOpenAL(); +void Sys_FreeOpenAL(); + + +#endif /* !__SYS_PUBLIC__ */ diff --git a/neo/sys/sys_savegame.cpp b/neo/sys/sys_savegame.cpp new file mode 100644 index 00000000..da7fe067 --- /dev/null +++ b/neo/sys/sys_savegame.cpp @@ -0,0 +1,903 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_session_local.h" + + +idCVar saveGame_verbose( "saveGame_verbose", "0", CVAR_BOOL | CVAR_ARCHIVE, "debug spam" ); +idCVar saveGame_checksum( "saveGame_checksum", "1", CVAR_BOOL, "data integrity check" ); +idCVar saveGame_enable( "saveGame_enable", "1", CVAR_BOOL, "are savegames enabled" ); + +void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms ); + + +/* +======================== +Sys_ExecuteSavegameCommandAsync +======================== +*/ +void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms ) { + if ( savegameParms == NULL ) { + idLib::Error( "Programming Error with [%s]", __FUNCTION__ ); + return; + } + + if ( !saveGame_enable.GetBool() ) { + idLib::Warning( "Savegames are disabled (saveGame_enable = 0). Skipping physical save to media." ); + savegameParms->errorCode = SAVEGAME_E_CANCELLED; + savegameParms->callbackSignal.Raise(); + return; + } + + Sys_ExecuteSavegameCommandAsyncImpl( savegameParms ); +} + +#define ASSERT_ENUM_STRING_BITFIELD( string, index ) ( 1 / (int)!( string - ( 1 << index ) ) ) ? #string : "" + +const char * saveGameErrorStrings[ SAVEGAME_E_NUM ] = { + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CANCELLED, 0 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INSUFFICIENT_ROOM, 1 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CORRUPTED, 2 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE, 3 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNKNOWN, 4 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_FILENAME, 5 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_STEAM_ERROR, 6 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FOLDER_NOT_FOUND, 7 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FILE_NOT_FOUND, 8 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DLC_NOT_FOUND, 9 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_USER, 10 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_PROFILE_TOO_BIG, 11 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DISC_SWAP, 12 ), + ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION, 13 ), +}; + +CONSOLE_COMMAND( savegamePrintErrors, "Prints error code corresponding to each bit", 0 ) { + idLib::Printf( "Bit Description\n" + "--- -----------\n" ); + for ( int i = 0; i < SAVEGAME_E_BITS_USED; i++ ) { + idLib::Printf( "%03d %s\n", i, saveGameErrorStrings[i] ); + } +} + +/* +======================== +GetSaveGameErrorString + +This returns a comma delimited string of all the errors within the bitfield. We don't ever expect more than one error, +the errors are bitfields so that they can be used to mask which errors we want to handle in the engine or leave up to the +game. + +Example: + SAVEGAME_E_LOAD, SAVEGAME_E_INVALID_FILENAME +======================== +*/ +idStr GetSaveGameErrorString( int errorMask ) { + idStr errorString; + bool continueProcessing = errorMask > 0; + int localError = errorMask; + + for ( int i = 0; i < SAVEGAME_E_NUM && continueProcessing; ++i ) { + int mask = ( 1 << i ); + + if ( localError & mask ) { + localError ^= mask; // turn off this error so we can quickly see if we are done + + continueProcessing = localError > 0; + + errorString.Append( saveGameErrorStrings[i] ); + + if ( continueProcessing ) { + errorString.Append( ", " ); + } + } + } + + return errorString; +} + +/* +======================== +GetSaveFolder + +Prefixes help the PS3 filter what to show in lists +Files using the hidden prefix are not shown in the load game screen +Directory name for savegames, slot number or user-defined name appended to it +TRC R116 - PS3 folder must start with the product code +======================== +*/ +const idStr & GetSaveFolder( idSaveGameManager::packageType_t type ) { + static bool initialized = false; + static idStrStatic saveFolder[idSaveGameManager::PACKAGE_NUM]; + + if ( !initialized ) { + initialized = true; + + idStr ps3Header = ""; + + saveFolder[idSaveGameManager::PACKAGE_GAME].Format( "%s%s", ps3Header.c_str(), SAVEGAME_GAME_DIRECTORY_PREFIX ); + saveFolder[idSaveGameManager::PACKAGE_PROFILE].Format( "%s%s", ps3Header.c_str(), SAVEGAME_PROFILE_DIRECTORY_PREFIX ); + saveFolder[idSaveGameManager::PACKAGE_RAW].Format( "%s%s", ps3Header.c_str(), SAVEGAME_RAW_DIRECTORY_PREFIX ); + } + + return saveFolder[type]; +} + +/* +======================== +idStr AddSaveFolderPrefix + + input = RAGE_0 + output = GAMES-RAGE_0 +======================== +*/ +idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) { + idStr dir = GetSaveFolder( type ); + dir.Append( folder ); + + + return dir; +} + +/* +======================== +RemoveSaveFolderPrefix + + input = GAMES-RAGE_0 + output = RAGE_0 +======================== +*/ +idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) { + idStr dir = folder; + idStr prefix = GetSaveFolder( type ); + dir.StripLeading( prefix ); + return dir; +} + +/* +======================== +bool SavegameReadDetailsFromFile + +returns false when catastrophic error occurs, not when damaged +======================== +*/ +bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details ) { + details.damaged = false; + + // Read the DETAIL file for the enumerated data + if ( !details.descriptors.ReadFromIniFile( file ) ) { + details.damaged = true; + } + + bool ignoreChecksum = details.descriptors.GetBool( "ignore_checksum", false ); + if ( !ignoreChecksum ) { + // Get the checksum from the dict + int readChecksum = details.descriptors.GetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, 0 ); + + // Calculate checksum + details.descriptors.Delete( SAVEGAME_DETAIL_FIELD_CHECKSUM ); + int checksum = (int)details.descriptors.Checksum(); + if ( readChecksum == 0 || checksum != readChecksum ) { + details.damaged = true; + } + } + + return true; +} + +/* +======================== +idSaveGameDetails::idSaveGameDetails +======================== +*/ +idSaveGameDetails::idSaveGameDetails() { + Clear(); +} + +/* +======================== +idSaveGameDetails::Clear +======================== +*/ +void idSaveGameDetails::Clear() { + descriptors.Clear(); + damaged = false; + date = 0; + slotName[0] = NULL; +} + +/* +======================== +idSaveLoadParms::idSaveLoadParms +======================== +*/ +idSaveLoadParms::idSaveLoadParms() { + // These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and + // these are set once and shouldn't be touched until the processor is re-initialized + cancelled = false; + + Init(); +} + +/* +======================== +idSaveLoadParms::~idSaveLoadParms +======================== +*/ +idSaveLoadParms::~idSaveLoadParms() { + for ( int i = 0; i < files.Num(); ++i ) { + if ( files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) { + delete files[i]; + } + } +} + +/* +======================== +idSaveLoadParms::ResetCancelled +======================== +*/ +void idSaveLoadParms::ResetCancelled() { + cancelled = false; +} + +/* +======================== +idSaveLoadParms::Init + +This should not touch anything statically created outside this class! +======================== +*/ +void idSaveLoadParms::Init() { + files.Clear(); + mode = SAVEGAME_MBF_NONE; + directory = ""; + pattern = ""; + postPattern = ""; + requiredSpaceInBytes = 0; + description.Clear(); + detailList.Clear(); + callbackSignal.Clear(); + errorCode = SAVEGAME_E_NONE; + inputDeviceId = -1; + skipErrorDialogMask = 0; + + // These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and + // these are set once and shouldn't be touched until the processor is re-initialized + // cancelled = false; +} + +/* +======================== +idSaveLoadParms::SetDefaults +======================== +*/ +void idSaveLoadParms::SetDefaults( int newInputDevice ) { + // These are pulled out so SetDefaults() isn't called during global instantiation of objects that have savegame processors + // in them that then require a session reference. + Init(); + + // fill in the user information (inputDeviceId & userId) from the master user + idLocalUser * user = NULL; + + if ( newInputDevice != -1 ) { + user = session->GetSignInManager().GetLocalUserByInputDevice( newInputDevice ); + } else if ( session != NULL ) { + user = session->GetSignInManager().GetMasterLocalUser(); + } + + if ( user != NULL ) { + idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user ); + userId = idStr::Hash( userWin->GetGamerTag() ); + idLib::PrintfIf( saveGame_verbose.GetBool(), "profile userId/gamertag: %s (%d)\n", userWin->GetGamerTag(), userId ); + inputDeviceId = user->GetInputDevice(); + } +} + +/* +======================== +idSaveLoadParms::CancelSaveGameFilePipelines +======================== +*/ +void idSaveLoadParms::CancelSaveGameFilePipelines() { + for ( int i = 0; i < files.Num(); i++ ) { + if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { + idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] ); + assert( file != NULL ); + + if ( file->GetMode() == idFile_SaveGamePipelined::WRITE ) { + // Notify the save game file that all writes failed which will cause all + // writes on the other end of the pipeline to drop on the floor. + file->NextWriteBlock( NULL ); + } else if ( file->GetMode() == idFile_SaveGamePipelined::READ ) { + // Notify end-of-file to the save game file which will cause all + // reads on the other end of the pipeline to return zero bytes. + file->NextReadBlock( NULL, 0 ); + } + } + } +} + +/* +======================== +idSaveLoadParms::AbortSaveGameFilePipeline +======================== +*/ +void idSaveLoadParms::AbortSaveGameFilePipeline() { + for ( int i = 0; i < files.Num(); i++ ) { + if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { + idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] ); + assert( file != NULL ); + file->Abort(); + } + } +} + +/* +================================================================================================ +idSaveGameProcessor +================================================================================================ +*/ + +/* +======================== +idSaveGameProcessor::idSaveGameProcessor +======================== +*/ +idSaveGameProcessor::idSaveGameProcessor() : working( false ), init( false ) { +} + +/* +======================== +idSaveGameProcessor::Init +======================== +*/ +bool idSaveGameProcessor::Init() { + if ( !verify( !IsWorking() ) ) { + idLib::Warning( "[%s] Someone is trying to execute this processor twice, this is really bad!", this->Name() ); + return false; + } + + parms.ResetCancelled(); + parms.SetDefaults(); + savegameLogicTestIterator = 0; + working = false; + init = true; + completedCallbacks.Clear(); + + return true; +} + +/* +======================== +idSaveGameProcessor::IsThreadFinished +======================== +*/ +bool idSaveGameProcessor::IsThreadFinished() { + return parms.callbackSignal.Wait( 0 ); +} + +/* +======================== +idSaveGameProcessor::AddCompletedCallback +======================== +*/ +void idSaveGameProcessor::AddCompletedCallback( const idCallback & callback ) { + completedCallbacks.Append( callback.Clone() ); +} + +/* +================================================================================================ +idSaveGameManager +================================================================================================ +*/ + +/* +======================== +idSaveGameManager::idSaveGameManager +======================== +*/ +idSaveGameManager::idSaveGameManager() : + processor( NULL ), + cancel( false ), + startTime( 0 ), + continueProcessing( false ), + submittedProcessorHandle( 0 ), + executingProcessorHandle( 0 ), + lastExecutedProcessorHandle( 0 ), + storageAvailable( true ), + retryFolder( NULL ) { +} + +/* +======================== +idSaveGameManager::~idSaveGameManager +======================== +*/ +idSaveGameManager::~idSaveGameManager() { + processor = NULL; + enumeratedSaveGames.Clear(); +} + +/* +======================== +idSaveGameManager::ExecuteProcessor +======================== +*/ +saveGameHandle_t idSaveGameManager::ExecuteProcessor( idSaveGameProcessor * processor ) { + idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s\n", __FUNCTION__, processor->Name() ); + + // may not be running yet, but if we've init'd successfuly, the IsWorking() call should return true if this + // method has been called. You have problems when callees are asking if the processor is done working by using IsWorking() + // the next frame after they've executed the processor. + processor->working = true; + + if ( this->processor != NULL ) { + if ( !verify( this->processor != processor ) ) { + idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:1 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" ); + return processor->GetHandle(); + } else { + idSaveGameProcessor ** localProcessor = processorQueue.Find( processor ); + if ( !verify( localProcessor == NULL ) ) { + idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:2 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" ); + return (*localProcessor)->GetHandle(); + } + } + } + + processorQueue.Append( processor ); + + // Don't allow processors to start sub-processors. + // They need to manage their own internal state. + assert( idLib::IsMainThread() ); + + Sys_InterlockedIncrement( submittedProcessorHandle ); + processor->parms.handle = submittedProcessorHandle; + + return submittedProcessorHandle; +} + +/* +======================== +idSaveGameManager::ExecuteProcessorAndWait +======================== +*/ +saveGameHandle_t idSaveGameManager::ExecuteProcessorAndWait( idSaveGameProcessor * processor ) { + saveGameHandle_t handle = ExecuteProcessor( processor ); + if ( handle == 0 ) { + return 0; + } + + while ( !IsSaveGameCompletedFromHandle( handle ) ) { + Pump(); + Sys_Sleep( 10 ); + } + + // One more pump to get the completed callback + //Pump(); + + return handle; +} + +/* +======================== +idSaveGameManager::WaitForAllProcessors + +Since the load & nextMap processors calls execute map change, we can't wait if they are the only ones in the queue +If there are only resettable processors in the queue or no items in the queue, don't wait. + +We would need to overrideSimpleProcessorCheck if we were sure we had done something that would cause the processors +to bail out nicely. Something like canceling a disc swap during a loading disc swap dialog... +======================== +*/ +void idSaveGameManager::WaitForAllProcessors( bool overrideSimpleProcessorCheck ) { + assert( idLib::IsMainThread() ); + + while ( IsWorking() || ( processorQueue.Num() > 0 ) ) { + + if ( !overrideSimpleProcessorCheck ) { + // BEFORE WE WAIT, and potentially hang everything, make sure processors about to be executed won't sit and + // wait for themselves to complete. + // Since we pull off simple processors first, we can stop waiting when the processor being executed is not simple + if ( processor != NULL ) { + if ( !processor->IsSimpleProcessor() ) { + break; + } + } else if ( !processorQueue[0]->IsSimpleProcessor() ) { + break; + } + } + + saveThread.WaitForThread(); + Pump(); + } +} + +/* +======================== +idSaveGameManager::CancelAllProcessors +======================== +*/ +void idSaveGameManager::CancelAllProcessors( const bool forceCancelInFlightProcessor ) { + assert( idLib::IsMainThread() ); + + cancel = true; + + if ( forceCancelInFlightProcessor ) { + if ( processor != NULL ) { + processor->GetSignal().Raise(); + } + } + + Pump(); // must be called from the main thread + Clear(); + cancel = false; +} + +/* +======================== +idSaveGameManager::CancelToTerminate +======================== +*/ +void idSaveGameManager::CancelToTerminate() { + if ( processor != NULL ) { + processor->parms.cancelled = true; + processor->GetSignal().Raise(); + saveThread.WaitForThread(); + } +} + +/* +======================== +idSaveGameManager::DeviceSelectorWaitingOnSaveRetry +======================== +*/ +bool idSaveGameManager::DeviceSelectorWaitingOnSaveRetry() { + + if ( retryFolder == NULL ) { + return false; + } + + return ( idStr::Icmp( retryFolder, "GAME-autosave" ) == 0 ); +} + +/* +======================== +idSaveGameManager::Set360RetrySaveAfterDeviceSelected +======================== +*/ +void idSaveGameManager::Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes ) { + retryFolder = folder; + retryBytes = bytes; +} + +/* +======================== +idSaveGameManager::ClearRetryInfo +======================== +*/ +void idSaveGameManager::ClearRetryInfo() { + retryFolder = NULL; + retryBytes = 0; +} + +/* +======================== +idSaveGameManager::RetrySave +======================== +*/ +void idSaveGameManager::RetrySave() { + if ( DeviceSelectorWaitingOnSaveRetry() && !common->Dialog().HasDialogMsg( GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, false ) ) { + cmdSystem->AppendCommandText( "savegame autosave\n" ); + } +} + +/* +======================== +idSaveGameManager::ShowRetySaveDialog +======================== +*/ +void idSaveGameManager::ShowRetySaveDialog() { + ShowRetySaveDialog( retryFolder, retryBytes ); +} + +/* +======================== +idSaveGameManager::ShowRetySaveDialog +======================== +*/ +void idSaveGameManager::ShowRetySaveDialog( const char * folder, const int64 bytes ) { + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + + class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE ); + session->GetSaveGameManager().ClearRetryInfo(); + return idSWFScriptVar(); + } + }; + + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_Continue() ); + optionText.Append( idStrId( "#str_dlg_continue_without_saving" ) ); + + + + // build custom space required string + // #str_dlg_space_required ~= "There is insufficient storage available. Please free %s and try again." + idStr format = idStrId( "#str_dlg_space_required" ).GetLocalizedString(); + idStr size; + if ( bytes > ( 1024 * 1024 ) ) { + const float roundUp = ( ( 1024.0f * 1024.0f / 10.0f )- 1.0f ); + size = va( "%.1f MB", ( roundUp + (float) bytes ) / ( 1024.0f * 1024.0f ) ); + } else { + const float roundUp = 1024.0f - 1.0f; + size = va( "%.0f KB", ( roundUp + (float) bytes ) / 1024.0f ); + } + idStr msg = va( format.c_str(), size.c_str() ); + + common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg, true ); +} + +/* +======================== +idSaveGameManager::CancelWithHandle +======================== +*/ +void idSaveGameManager::CancelWithHandle( const saveGameHandle_t & handle ) { + if ( handle == 0 || IsSaveGameCompletedFromHandle( handle ) ) { + return; + } + + // check processor in flight first + if ( processor != NULL ) { + if ( processor->GetHandle() == handle ) { + processor->Cancel(); + return; + } + } + + // remove from queue + for ( int i = 0; i < processorQueue.Num(); ++i ) { + if ( processorQueue[i]->GetHandle() == handle ) { + processorQueue[i]->Cancel(); + return; + } + } +} + +/* +======================== +idSaveGameManager::StartNextProcessor + +Get the next not-reset-capable processor. If there aren't any left, just get what's next. +======================== +*/ +void idSaveGameManager::StartNextProcessor() { + if ( cancel ) { + return; + } + + idSaveGameProcessor * nextProcessor = NULL; + int index = 0; + + // pick off the first simple processor + for ( int i = 0; i < processorQueue.Num(); ++i ) { + if ( processorQueue[i]->IsSimpleProcessor() ) { + index = i; + break; + } + } + + if ( processorQueue.Num() > 0 ) { + nextProcessor = processorQueue[index]; + + + Sys_InterlockedIncrement( executingProcessorHandle ); + + processorQueue.RemoveIndex( index ); + processor = nextProcessor; + processor->parms.callbackSignal.Raise(); // signal that the thread is ready for work + startTime = Sys_Milliseconds(); + } +} + +/* +======================== +idSaveGameManager::FinishProcessor +======================== +*/ +void idSaveGameManager::FinishProcessor( idSaveGameProcessor * localProcessor ) { + + assert( localProcessor != NULL ); + idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s, %d ms\n", __FUNCTION__, localProcessor->Name(), Sys_Milliseconds() - startTime ); + + // This will delete from the files set for auto-deletion + // Don't remove files not set for auto-deletion, they may be used outside of the savegame manager by game-side callbacks for example + for ( int i = ( localProcessor->parms.files.Num() - 1 ); i >= 0; --i ) { + if ( localProcessor->parms.files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) { + delete localProcessor->parms.files[i]; + localProcessor->parms.files.RemoveIndexFast( i ); + } + } + + localProcessor->init = false; + localProcessor = NULL; +} + +/* +======================== +idSaveGameManager::Clear +======================== +*/ +void idSaveGameManager::Clear() { + processorQueue.Clear(); +} + +/* +======================== +idSaveGameManager::IsWorking +======================== +*/ +bool idSaveGameManager::IsWorking() const { + return processor != NULL; +} + +/* +======================== +idSaveGameManager::Pump + +Important sections called out with -- EXTRA LARGE -- comments! +======================== +*/ +void idSaveGameManager::Pump() { + + + + // After a processor is done, the next is pulled off the queue so the only way the manager isn't working is if + // there isn't something executing or in the queue. + if ( !IsWorking() ) { + // Unified start to initialize system on PS3 and do appropriate checks for system combination issues + // ------------------------------------ + // START + // ------------------------------------ + StartNextProcessor(); + + if ( !IsWorking() ) { + return; + } + + continueProcessing = true; + } + + if ( cancel ) { + processor->parms.AbortSaveGameFilePipeline(); + } + + // Quickly checks to see if the savegame thread is done, otherwise, exit and continue frame commands + if ( processor->IsThreadFinished() ) { + idLib::PrintfIf( saveGame_verbose.GetBool(), "%s waited on processor [%s], error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() ); + + if ( !cancel && continueProcessing ) { + // Check for available storage unit + if ( session->GetSignInManager().GetMasterLocalUser() != NULL ) { + if ( !session->GetSignInManager().GetMasterLocalUser()->IsStorageDeviceAvailable() ) { + // this will not allow further processing + processor->parms.errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE; + } + } + + // Execute Process() on the processor, if there was an error in a previous Process() call, give the + // processor the chance to validate that error and either clean itself up or convert it to another error or none. + if ( processor->GetError() == SAVEGAME_E_NONE || processor->ValidateLastError() ) { + idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::Process(), error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() ); + + // ------------------------------------ + // PROCESS + // ------------------------------------ + continueProcessing = processor->Process(); + + // If we don't return here, the completedCallback will be executed before it's done with it's async operation + // during it's last process stage. + return; + } else { + continueProcessing = false; + } + } + + // This section does specific post-processing for each of the save commands + if ( !continueProcessing ) { + + // Clear out details if we detect corruption but keep directory/slot information + for ( int i = 0; i < processor->parms.detailList.Num(); ++i ) { + idSaveGameDetails & details = processor->parms.detailList[i]; + if ( details.damaged ) { + details.descriptors.Clear(); + } + } + + idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::CompletedCallback()\n", __FUNCTION__, processor->Name() ); + processor->working = false; + + // This ensures that the savegame manager will believe the processor is done when there is a potentially + // catastrophic thing that will happen within CompletedCallback which might try to sync all threads + // The most common case of this is executing a map change (which we no longer do). + // We flush the heap and wait for all background processes to finish. After all this is called, we will + // cleanup the old processor within FinishProcessor() + idSaveGameProcessor * localProcessor = processor; + processor = NULL; + + // ------------------------------------ + // COMPLETEDCALLBACK + // At this point, the handle will be completed + // ------------------------------------ + Sys_InterlockedIncrement( lastExecutedProcessorHandle ); + + for ( int i = 0; i < localProcessor->completedCallbacks.Num(); i++ ) { + localProcessor->completedCallbacks[i]->Call(); + } + localProcessor->completedCallbacks.DeleteContents( true ); + + // ------------------------------------ + // FINISHPROCESSOR + // ------------------------------------ + FinishProcessor( localProcessor ); + } + } else if ( processor->ShouldTimeout() ) { + // Hack for the PS3 threading hang + idLib::PrintfIf( saveGame_verbose.GetBool(), "----- PROCESSOR TIMEOUT ----- (%s)\n", processor->Name() ); + + idSaveGameProcessor * tempProcessor = processor; + + CancelAllProcessors( true ); + + class idSWFScriptFunction_TryAgain : public idSWFScriptFunction_RefCounted { + public: + idSWFScriptFunction_TryAgain( idSaveGameManager * manager, idSaveGameProcessor * processor ) { + this->manager = manager; + this->processor = processor; + } + idSWFScriptVar Call ( idSWFScriptObject * thisObject, const idSWFParmList & parms ) { + common->Dialog().ClearDialog( GDM_ERROR_SAVING_SAVEGAME ); + manager->ExecuteProcessor( processor ); + return idSWFScriptVar(); + } + private: + idSaveGameManager * manager; + idSaveGameProcessor * processor; + }; + + idStaticList< idSWFScriptFunction *, 4 > callbacks; + idStaticList< idStrId, 4 > optionText; + callbacks.Append( new (TAG_SWF) idSWFScriptFunction_TryAgain( this, tempProcessor ) ); + optionText.Append( idStrId( "#STR_SWF_RETRY" ) ); + common->Dialog().AddDynamicDialog( GDM_ERROR_SAVING_SAVEGAME, callbacks, optionText, true, "" ); + } +} diff --git a/neo/sys/sys_savegame.h b/neo/sys/sys_savegame.h new file mode 100644 index 00000000..f192bda3 --- /dev/null +++ b/neo/sys/sys_savegame.h @@ -0,0 +1,483 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_SAVEGAME_H__ +#define __SYS_SAVEGAME_H__ + +#ifdef OUTPUT_FUNC +#undef OUTPUT_FUNC +#endif +#ifdef OUTPUT_FUNC_EXIT +#undef OUTPUT_FUNC_EXIT +#endif +#define OUTPUT_FUNC() idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] Enter\n", __FUNCTION__ ) +#define OUTPUT_FUNC_EXIT() idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] Exit\n", __FUNCTION__ ) + + +#define DEFINE_CLASS( x ) virtual const char * Name() const { return #x; } +#define MAX_SAVEGAMES 16 +#define MAX_FILES_WITHIN_SAVEGAME 10 +#define MIN_SAVEGAME_SIZE_BYTES ( 4 * 1024 * 1024 ) +#define MAX_SAVEGAME_STRING_TABLE_SIZE 400 * 1024 // 400 kB max string table size + + +#define MAX_FILENAME_LENGTH 255 +#define MAX_FILENAME_LENGTH_PATTERN 8 +#define MAX_FOLDER_NAME_LENGTH 64 +#define SAVEGAME_DETAILS_FILENAME "game.details" + +// PS3 restrictions: The only characters that can be used are 0-9 (numbers), A-Z (uppercase alphabet), "_" (underscore), and "-" (hyphen) +#define SAVEGAME_AUTOSAVE_FOLDER "AUTOSAVE" // auto save slot + +// common descriptors for savegame description fields +#define SAVEGAME_DETAIL_FIELD_EXPANSION "expansion" +#define SAVEGAME_DETAIL_FIELD_MAP "mapName" +#define SAVEGAME_DETAIL_FIELD_MAP_LOCATE "mapLocation" +#define SAVEGAME_DETAIL_FIELD_DIFFICULTY "difficulty" +#define SAVEGAME_DETAIL_FIELD_PLAYTIME "playTime" +#define SAVEGAME_DETAIL_FIELD_LANGUAGE "language" +#define SAVEGAME_DETAIL_FIELD_SAVE_VERSION "saveVersion" +#define SAVEGAME_DETAIL_FIELD_CHECKSUM "checksum" + +#define SAVEGAME_GAME_DIRECTORY_PREFIX "GAME-" +#define SAVEGAME_PROFILE_DIRECTORY_PREFIX "" +#define SAVEGAME_RAW_DIRECTORY_PREFIX "" + + +extern idCVar saveGame_verbose; +extern idCVar saveGame_enable; + +class idGameSpawnInfo; +class idSession; +class idSessionLocal; +class idSaveGameManager; + +// Specific savegame sub-system errors +enum saveGameError_t { + SAVEGAME_E_NONE = 0, + SAVEGAME_E_CANCELLED = BIT( 0 ), + SAVEGAME_E_INSUFFICIENT_ROOM = BIT( 1 ), + SAVEGAME_E_CORRUPTED = BIT( 2 ), + SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE = BIT( 3 ), + SAVEGAME_E_UNKNOWN = BIT( 4 ), + SAVEGAME_E_INVALID_FILENAME = BIT( 5 ), + SAVEGAME_E_STEAM_ERROR = BIT( 6 ), + SAVEGAME_E_FOLDER_NOT_FOUND = BIT( 7 ), + SAVEGAME_E_FILE_NOT_FOUND = BIT( 8 ), + SAVEGAME_E_DLC_NOT_FOUND = BIT( 9 ), + SAVEGAME_E_INVALID_USER = BIT( 10 ), + SAVEGAME_E_PROFILE_TOO_BIG = BIT( 11 ), + SAVEGAME_E_DISC_SWAP = BIT( 12 ), + SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION = BIT( 13 ), + + SAVEGAME_E_BITS_USED = 14, + SAVEGAME_E_NUM = SAVEGAME_E_BITS_USED + 1 // because we're counting "none" +}; + +// Modes to control behavior of savegame manager +enum saveGameModeBitfield_t { + SAVEGAME_MBF_NONE = 0, + SAVEGAME_MBF_LOAD = BIT( 0 ), // standard file load (can be individual/multiple files described in parms) + SAVEGAME_MBF_SAVE = BIT( 1 ), // standard file save (can be individual/multiple files described in parms) + SAVEGAME_MBF_DELETE_FOLDER = BIT( 2 ), // standard package delete + SAVEGAME_MBF_DELETE_ALL_FOLDERS = BIT( 3 ), // deletes all of the savegame folders (should only be used in testing) + SAVEGAME_MBF_ENUMERATE = BIT( 4 ), // gets listing of all savegame folders, typically used with READ_DETAILS to read the description file + SAVEGAME_MBF_NO_COMPRESS = BIT( 5 ), // tells the system the files aren't compressed, usually only needed when reading the descriptors file internally + SAVEGAME_MBF_ENUMERATE_FILES = BIT( 6 ), // enumerates all the files within a particular savegame folder (can be individual/multiple files or pattern described in parms) + SAVEGAME_MBF_DELETE_FILES = BIT( 7 ), // deletes individual files within a particular savegame folder (can be individual/multiple files or pattern described in parms) + SAVEGAME_MBF_READ_DETAILS = BIT( 8 ), // reads the description file (if specified, parms.enumeratedEntry.name & parms.enumeratedEntry.type must be specified) + SAVEGAME_MBF_KEEP_FOLDER = BIT( 9 ) // don't delete the folder before saving +}; + +typedef interlockedInt_t saveGameHandle_t; + +typedef int savegameUserId_t; // [internal] hash of gamer tag for steam + +/* +================================================ +saveGameCheck_t +================================================ +*/ +struct saveGameCheck_t { + saveGameCheck_t() { + exists = false; + autosaveExists = false; + autosaveFolder = NULL; + } + bool exists; + bool autosaveExists; + const char * autosaveFolder; +}; + +/* +================================================ +idSaveGameDetails +================================================ +*/ +class idSaveGameDetails { +public: + idSaveGameDetails(); + ~idSaveGameDetails() { Clear(); } + + void Clear(); + bool operator==( const idSaveGameDetails & other ) const { return ( idStr::Icmp( slotName, other.slotName ) == 0 ); } + idSaveGameDetails & operator=( const idSaveGameDetails &other ) { + descriptors.Clear(); + descriptors = other.descriptors; + damaged = other.damaged; + date = other.date; + slotName = other.slotName; + return *this; + } + // for std::sort, sort newer (larger date) towards start of list + bool operator<( const idSaveGameDetails & other ) { return date > other.date; } + + idStr GetMapName() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP, "" ); } + idStr GetLocation() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP_LOCATE, "" ); } + idStr GetLanguage() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_LANGUAGE, "" ); } + int GetPlaytime() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_PLAYTIME, 0 ); } + int GetExpansion() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_EXPANSION, 0 ); } + int GetDifficulty() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_DIFFICULTY, -1 ); } + int GetSaveVersion() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_SAVE_VERSION, 0 ); } + +public: + idDict descriptors; // [in] Descriptors available to be shown on the save/load screen. Each game can define their own, e.g. Difficulty, level, map, score, time. + bool damaged; // [out] + time_t date; // [out] read from the filesystem, not set by client + idStrStatic< MAX_FOLDER_NAME_LENGTH > slotName; // [out] folder/slot name, e.g. AUTOSAVE +}; + +typedef idStaticList< idSaveGameDetails, MAX_SAVEGAMES > saveGameDetailsList_t; + +// Making a auto_ptr to handle lifetime issues better +typedef idList< idFile_SaveGame *, TAG_SAVEGAMES > saveFileEntryList_t; + +/* +================================================ +idSaveLoadParms +================================================ +*/ +class idSaveLoadParms { +public: + idSaveLoadParms(); + ~idSaveLoadParms(); + + void ResetCancelled(); + void Init(); + void SetDefaults( int inputDevice = -1 ); // doesn't clear out things that should be persistent across entire processor + void CancelSaveGameFilePipelines(); + void AbortSaveGameFilePipeline(); + const int & GetError() const { return errorCode; } + const int & GetHandledErrors() const { return handledErrorCodes; } + const saveGameHandle_t & GetHandle() const { return handle; } + +public: + idStrStatic< MAX_FOLDER_NAME_LENGTH > directory; // [in] real directory of the savegame package + idStrStatic< MAX_FILENAME_LENGTH_PATTERN > pattern; // [in] pattern to use while enumerating/deleting files within a savegame folder + idStrStatic< MAX_FILENAME_LENGTH_PATTERN > postPattern; // [in] pattern at the end of the file to use while enumerating/deleting files within a savegame folder + + int mode; // [in] SAVE, LOAD, ENUM, DELETE, etc. + idSaveGameDetails description; // [in/out] in: description used to serialize into game.details file, out: if SAVEGAME_MBF_READ_DETAILS used with certain modes, item 0 contains the read details + saveFileEntryList_t files; // [in/out] in: files to be saved, out: objects loaded, for SAVEGAME_MBF_ENUMERATE_FILES, it contains a listing of the filenames only + saveGameDetailsList_t detailList; // [out] listing of the enumerated savegames used only with SAVEGAME_MBF_ENUMERATE + + int errorCode; // [out] combination of saveGameError_t bits + int handledErrorCodes; // [out] combination of saveGameError_t bits + int64 requiredSpaceInBytes; // [out] when fails for insufficient space, this is populated with additional space required + int skipErrorDialogMask; + + // ---------------------- + // Internal vars + // ---------------------- + idSysSignal callbackSignal; // [internal] used to signal savegame manager that the Process() call is completed (we still might have more Process() calls to make though...) + volatile bool cancelled; // [internal] while processor is running, this can be set outside of the normal operation of the processor. Each implementation should check this during operation to allow it to shutdown cleanly. + savegameUserId_t userId; // [internal] to get the proper user during every step + int inputDeviceId; // [internal] consoles will use this to segregate each player's files + saveGameHandle_t handle; + +private: + // Don't allow copies + idSaveLoadParms( const idSaveLoadParms & s ) {} + void operator=( const idSaveLoadParms & s ) {} +}; + +// Using function pointers because: +// 1. CompletedCallback methods in processors weren't generic enough, we could use SaveFiles processors +// for profiles/games, but there would be a single completed callback and we'd have to update +// the callback to detect what type of call it was, store the type in the processor, etc. +// 2. Using a functor class would require us to define classes for each callback. The definition of those +// classes could be scattered and a little difficult to follow +// 3. With callback methods, we assign them when needed and know exactly where they are defined/declared. +//typedef void (*saveGameProcessorCallback_t)( idSaveLoadParms & parms ); + +/* +================================================ +saveGameThreadArgs_t +================================================ +*/ +struct saveGameThreadArgs_t { + saveGameThreadArgs_t() : + saveLoadParms( NULL ) { + } + + + idSaveLoadParms * saveLoadParms; +}; + +/* +================================================ +idSaveGameThread +================================================ +*/ +class idSaveGameThread : public idSysThread { +public: + idSaveGameThread() : cancel( false ) {} + + int Run(); + void CancelOperations() { cancel = true; } + +private: + int Save(); + int Load(); + int Enumerate(); + int Delete(); + int DeleteAll(); + int DeleteFiles(); + int EnumerateFiles(); + +public: + saveGameThreadArgs_t data; + volatile bool cancel; +}; + +/* +================================================ +idSaveGameProcessor +================================================ +*/ +class idSaveGameProcessor { + friend class idSaveGameManager; + +public: + DEFINE_CLASS( idSaveGameProcessor ); + static const int MAX_COMPLETED_CALLBACKS = 5; + + idSaveGameProcessor(); + virtual ~idSaveGameProcessor() { } + + //------------------------ + // Virtuals + //------------------------ + // Basic init + virtual bool Init(); + + // This method should returns true if the processor has additional sub-states to + // manage. The saveGameManager will retain the current state and Process() will be called again. When this method + // returns false Process() will not be called again. For example, during save, you might want to load other files + // and save them somewhere else, return true until you are done with the entire state. + virtual bool Process() { return false; } + + // Gives each processor to validate an error returned from the previous process call. + // This is useful when processors have a multi-stage Process() and expect some benign errors like + // deleting a savegame folder before copying into it. + virtual bool ValidateLastError() { return false; } + + // Processors need to override this if they will eventually reset the map. + // If it could possibly reset the map through any of its stages, including kicking off another processor in completed callback, return false. + // We will force non-simple processors to execute last and won't block the map heap reset due if non-simple processors are still executing. + virtual bool IsSimpleProcessor() const { return true; } + + // This is a fail-safe to catch a timing issue on the PS3 where the nextmap processor could sometimes hang during a level transition + virtual bool ShouldTimeout() const { return false; } + + //------------------------ + // Commands + //------------------------ + // Cancels this processor in whatever state it's currently in and sets an error code for SAVEGAME_E_CANCELLED + void Cancel() { parms.cancelled = true; parms.errorCode = SAVEGAME_E_CANCELLED; } + + //------------------------ + // Accessors + //------------------------ + // Returns error status + idSysSignal & GetSignal() { return parms.callbackSignal; } + + // Returns error status + const int & GetError() const { return parms.errorCode; } + + // Returns the processor's save/load parms + const idSaveLoadParms & GetParms() const { return parms; } + + // Returns the processor's save/load parms + idSaveLoadParms & GetParmsNonConst() { return parms; } + + // Returns if this processor is currently working + bool IsWorking() const { return working; } + + // This is a way to tell the processor which errors shouldn't be handled by the processor or system. + void SetSkipSystemErrorDialogMask( const int errorMask ) { parms.skipErrorDialogMask = errorMask; } + int GetSkipSystemErrorDialogMask() const { return parms.skipErrorDialogMask; } + + // Returns the handle given by execution + saveGameHandle_t GetHandle() const { return parms.GetHandle(); } + + // These can be overridden by game code, like the GUI, when the processor is done executing. + // Game classes like the GUI can create a processor derived from a game's Save processor impl and simply use + // this method to know when everything is done. It eases the burden of constantly checking the working flag. + // This will be called back within the game thread during SaveGameManager::Pump(). + void AddCompletedCallback( const idCallback & callback ); + +private: + // Returns whether or not the thread is finished operating, should only be called by the savegame manager + bool IsThreadFinished(); + +protected: + idSaveLoadParms parms; + int savegameLogicTestIterator; + +private: + bool init; + bool working; + + idStaticList< idCallback *, MAX_COMPLETED_CALLBACKS > completedCallbacks; +}; + +/* +================================================ +idSaveGameManager + +Why all the object-oriented nonsense? +- Savegames need to be processed asynchronously, saving/loading/deleting files should happen during the game frame + so there is a common way to update the render device. +- When executing commands, if no "strategy"s are used, the pump() method would need to have a switch statement, + extending the manager for other commands would mean modifying the manager itself for various commands. + By making it a strategy, we are able to create custom commands and define the behavior within game code and keep + the manager code in the engine static. +================================================ +*/ +class idSaveGameManager { +public: + enum packageType_t { + PACKAGE_PROFILE, + PACKAGE_GAME, + PACKAGE_RAW, + PACKAGE_NUM + }; + + const static int MAX_SAVEGAME_DIRECTORY_DEPTH = 5; + + explicit idSaveGameManager(); + ~idSaveGameManager(); + + // Called within main game thread + void Pump(); + + // Has the storage device been selected yet? This is only an issue on the 360, and primarily for development purposes + bool IsStorageAvailable() const { return storageAvailable; } + void SetStorageAvailable( const bool available ) { storageAvailable = available; } + + // Check to see if a processor is set within the manager + bool IsWorking() const; + + // Assign a processor to the manager. The processor should belong in game-side code + // This queues up processors and executes them serially + // Returns whether or not the processor is immediately executed + saveGameHandle_t ExecuteProcessor( idSaveGameProcessor * processor ); + + // Synchronous version, CompletedCallback is NOT called. + saveGameHandle_t ExecuteProcessorAndWait( idSaveGameProcessor * processor ); + + // Lets the currently processing queue finish, but clears the processor queue + void Clear(); + + void WaitForAllProcessors( bool overrideSimpleProcessorCheck = false ); + + const bool IsCancelled() const { return cancel; } + void CancelAllProcessors( const bool forceCancelInFlightProcessor ); + + void CancelToTerminate(); + + idSaveGameThread & GetSaveGameThread() { return saveThread; } + + bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const { return handle <= lastExecutedProcessorHandle || handle == 0; } // last case should never be reached since it would be also be true in first case, this is just to show intent + void Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes ); + bool DeviceSelectorWaitingOnSaveRetry(); + void ShowRetySaveDialog( const char * folder, const int64 bytes ); + void ShowRetySaveDialog(); + void ClearRetryInfo(); + void RetrySave(); + // This will cause the processor to cancel execution, the completion callback will be called + void CancelWithHandle( const saveGameHandle_t & handle ); + + const saveGameDetailsList_t & GetEnumeratedSavegames() const { return enumeratedSaveGames; } + saveGameDetailsList_t & GetEnumeratedSavegamesNonConst() { return enumeratedSaveGames; } + +private: + // These are to make sure that all processors start and finish in the same way without a lot of code duplication. + // We need to make sure that we adhere to PS3 system combination initialization issues. + void StartNextProcessor(); + void FinishProcessor( idSaveGameProcessor * processor ); + + // Calls start on the processor after it's been assigned + void Start(); + +private: + idSaveGameProcessor * processor; + idStaticList< idSaveGameProcessor *, 4 > processorQueue; + bool cancel; + idSaveGameThread saveThread; + int startTime; + bool continueProcessing; + saveGameHandle_t submittedProcessorHandle; + saveGameHandle_t executingProcessorHandle; + saveGameHandle_t lastExecutedProcessorHandle; + saveGameDetailsList_t enumeratedSaveGames; + bool storageAvailable; // On 360, this is false by default, after the storage device is selected + // it becomes true. This allows us to start the game without a storage device + // selected and pop the selector when necessary. + const char * retryFolder; + int64 retryBytes; + bool retrySave; + idSysSignal deviceRequestedSignal; +}; + +// Bridge between the session's APIs and the savegame thread +void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms ); + +// Folder prefix should be NULL for everything except PS3 +// Synchronous check, just checks if any savegame exists for master local user and if one is an autosave +void Sys_SaveGameCheck( bool & exists, bool & autosaveExists ); + +const idStr & GetSaveFolder( idSaveGameManager::packageType_t type ); +idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ); +idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ); + +bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details ); + +idStr GetSaveGameErrorString( int errorMask ); + +#endif // __SYS_SAVEGAME_H__ diff --git a/neo/sys/sys_session.h b/neo/sys/sys_session.h new file mode 100644 index 00000000..e6b0ed26 --- /dev/null +++ b/neo/sys/sys_session.h @@ -0,0 +1,643 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_SESSION_H__ +#define __SYS_SESSION_H__ + +#include "../framework/Serializer.h" +#include "sys_localuser.h" + +typedef uint8 peerMask_t; +static const int MAX_PLAYERS = 8; + +static const int MAX_REDUNDANT_CMDS = 3; + +static const int MAX_LOCAL_PLAYERS = 2; +static const int MAX_INPUT_DEVICES = 4; +enum matchFlags_t { + MATCH_STATS = BIT( 0 ), // Match will upload leaderboard/achievement scores + MATCH_ONLINE = BIT( 1 ), // Match will require users to be online + MATCH_RANKED = BIT( 2 ), // Match will affect rank + MATCH_PRIVATE = BIT( 3 ), // Match will NOT be searchable through FindOrCreateMatch + MATCH_INVITE_ONLY = BIT( 4 ), // Match visible through invite only + + MATCH_REQUIRE_PARTY_LOBBY = BIT( 5 ), // This session uses a party lobby + MATCH_PARTY_INVITE_PLACEHOLDER = BIT( 6 ), // Party is never shown in the UI, it's simply used as a placeholder for invites + MATCH_JOIN_IN_PROGRESS = BIT( 7 ), // Join in progress supported for this match +}; + +ID_INLINE bool MatchTypeIsOnline( uint8 matchFlags ) { return ( matchFlags & MATCH_ONLINE ) ? true : false; } +ID_INLINE bool MatchTypeIsLocal( uint8 matchFlags ) { return !MatchTypeIsOnline( matchFlags ); } +ID_INLINE bool MatchTypeIsPrivate( uint8 matchFlags ) { return ( matchFlags & MATCH_PRIVATE ) ? true : false; } +ID_INLINE bool MatchTypeIsRanked( uint8 matchFlags ) { return ( matchFlags & MATCH_RANKED ) ? true : false; } +ID_INLINE bool MatchTypeHasStats( uint8 matchFlags ) { return ( matchFlags & MATCH_STATS ) ? true : false; } +ID_INLINE bool MatchTypeInviteOnly( uint8 matchFlags ) { return ( matchFlags & MATCH_INVITE_ONLY ) ? true : false; } +ID_INLINE bool MatchTypeIsSearchable( uint8 matchFlags ) { return !MatchTypeIsPrivate( matchFlags ); } +ID_INLINE bool MatchTypeIsJoinInProgress( uint8 matchFlags ){ return ( matchFlags & MATCH_JOIN_IN_PROGRESS ) ? true : false; } + +class idCompressor; +class idLeaderboardSubmission; +class idLeaderboardQuery; +class idSignInManagerBase; +class idPlayerProfile; +class idGameSpawnInfo; +class idSaveLoadParms; +class idSaveGameManager; +class idLocalUser; +class idDedicatedServerSearch; +class idAchievementSystem; +class idLeaderboardCallback; + +struct leaderboardDefinition_t; +struct column_t; + +const int8 GAME_MODE_RANDOM = -1; +const int8 GAME_MODE_SINGLEPLAYER = -2; + +const int8 GAME_MAP_RANDOM = -1; +const int8 GAME_MAP_SINGLEPLAYER = -2; + +const int8 GAME_EPISODE_UNKNOWN = -1; +const int8 GAME_SKILL_DEFAULT = -1; + +const int DefaultPartyFlags = MATCH_JOIN_IN_PROGRESS | MATCH_ONLINE; +const int DefaultPublicGameFlags = MATCH_JOIN_IN_PROGRESS | MATCH_REQUIRE_PARTY_LOBBY | MATCH_RANKED | MATCH_STATS; +const int DefaultPrivateGameFlags = MATCH_JOIN_IN_PROGRESS | MATCH_REQUIRE_PARTY_LOBBY | MATCH_PRIVATE; + +/* +================================================ +idMatchParameters +================================================ +*/ +class idMatchParameters { +public: + idMatchParameters() : + numSlots( MAX_PLAYERS ), + gameMode( GAME_MODE_RANDOM ), + gameMap( GAME_MAP_RANDOM ), + gameEpisode( GAME_EPISODE_UNKNOWN ), + gameSkill( GAME_SKILL_DEFAULT ), + matchFlags( 0 ) + {} + + void Write( idBitMsg & msg ) { idSerializer s( msg, true ); Serialize( s ); } + void Read( idBitMsg & msg ) { idSerializer s( msg, false ); Serialize( s ); } + + void Serialize( idSerializer & serializer ) { + serializer.Serialize( gameMode ); + serializer.Serialize( gameMap ); + serializer.Serialize( gameEpisode ); + serializer.Serialize( gameSkill ); + serializer.Serialize( numSlots ); + serializer.Serialize( matchFlags ); + serializer.SerializeString( mapName ); + serverInfo.Serialize( serializer ); + } + + uint8 numSlots; + int8 gameMode; + int8 gameMap; + int8 gameEpisode; // Episode for doom classic support. + int8 gameSkill; // Skill for doom classic support. + uint8 matchFlags; + + idStr mapName; // This is only used for SP (gameMap == GAME_MAP_SINGLEPLAYER) + idDict serverInfo; +}; + +/* +================================================ +serverInfo_t +Results from calling ListServers/ServerInfo for game browser / system link. +================================================ +*/ +struct serverInfo_t { + serverInfo_t() : + gameMode( GAME_MODE_RANDOM ), + gameMap( GAME_MAP_RANDOM ), + joinable(), + numPlayers(), + maxPlayers() + {} + + + void Write( idBitMsg & msg ) { idSerializer s( msg, true ); Serialize( s ); } + void Read( idBitMsg & msg ) { idSerializer s( msg, false ); Serialize( s ); } + + void Serialize( idSerializer & serializer ) { + serializer.SerializeString( serverName ); + serializer.Serialize( gameMode ); + serializer.Serialize( gameMap ); + SERIALIZE_BOOL( serializer, joinable ); + serializer.Serialize( numPlayers ); + serializer.Serialize( maxPlayers ); + } + + idStr serverName; + int8 gameMode; + int8 gameMap; + bool joinable; + int numPlayers; + int maxPlayers; +}; + +//------------------------ +// voiceState_t +//------------------------ +enum voiceState_t { + VOICECHAT_STATE_NO_MIC, + VOICECHAT_STATE_MUTED_LOCAL, + VOICECHAT_STATE_MUTED_REMOTE, + VOICECHAT_STATE_MUTED_ALL, + VOICECHAT_STATE_NOT_TALKING, + VOICECHAT_STATE_TALKING, + VOICECHAT_STATE_TALKING_GLOBAL, + NUM_VOICECHAT_STATE +}; + +//------------------------ +// voiceStateDisplay_t +//------------------------ +enum voiceStateDisplay_t { + VOICECHAT_DISPLAY_NONE, + VOICECHAT_DISPLAY_NOTTALKING, + VOICECHAT_DISPLAY_TALKING, + VOICECHAT_DISPLAY_TALKING_GLOBAL, + VOICECHAT_DISPLAY_MUTED, + VOICECHAT_DISPLAY_MAX +}; + +static const int QOS_RESULT_CRAPPY = 200; +static const int QOS_RESULT_WEAK = 100; +static const int QOS_RESULT_GOOD = 50; +static const int QOS_RESULT_GREAT = 0; + +//------------------------ +// qosState_t +//------------------------ +enum qosState_t { + QOS_STATE_CRAPPY = 1, + QOS_STATE_WEAK, + QOS_STATE_GOOD, + QOS_STATE_GREAT, + QOS_STATE_MAX, +}; + +//------------------------ +// leaderboardDisplayError_t +//------------------------ +enum leaderboardDisplayError_t { + LEADERBOARD_DISPLAY_ERROR_NONE, + LEADERBOARD_DISPLAY_ERROR_FAILED, // General error occurred + LEADERBOARD_DISPLAY_ERROR_NOT_ONLINE, // No longer online + LEADERBOARD_DISPLAY_ERROR_NOT_RANKED, // Attempting to view a "My Score" leaderboard you aren't ranked on + LEADERBOARD_DISPLAY_ERROR_MAX +}; + +/* +================================================ +lobbyUserID_t +================================================ +*/ +struct lobbyUserID_t { +public: + + lobbyUserID_t() : lobbyType( 0xFF ) {} + + explicit lobbyUserID_t( localUserHandle_t localUser_, byte lobbyType_ ) : localUserHandle( localUser_ ), lobbyType( lobbyType_ ) {} + + bool operator == ( const lobbyUserID_t & other ) const { + return localUserHandle == other.localUserHandle && lobbyType == other.lobbyType; // Lobby type must match + } + + bool operator != ( const lobbyUserID_t & other ) const { + return !( *this == other ); + } + + bool operator < ( const lobbyUserID_t & other ) const { + if ( localUserHandle == other.localUserHandle ) { + return lobbyType < other.lobbyType; // Lobby type tie breaker + } + + return localUserHandle < other.localUserHandle; + } + + bool CompareIgnoreLobbyType( const lobbyUserID_t & other ) const { + return localUserHandle == other.localUserHandle; + } + + localUserHandle_t GetLocalUserHandle() const { return localUserHandle; } + byte GetLobbyType() const { return lobbyType; } + + bool IsValid() const { return localUserHandle.IsValid() && lobbyType != 0xFF; } + + void WriteToMsg( idBitMsg & msg ) { + localUserHandle.WriteToMsg( msg ); + msg.WriteByte( lobbyType ); + } + + void ReadFromMsg( const idBitMsg & msg ) { + localUserHandle.ReadFromMsg( msg ); + lobbyType = msg.ReadByte(); + } + + void Serialize( idSerializer & ser ); + +private: + localUserHandle_t localUserHandle; + byte lobbyType; +}; + +/* +================================================ +idLobbyBase +================================================ +*/ +class idLobbyBase { +public: + // General lobby functionality + virtual bool IsHost() const = 0; + virtual bool IsPeer() const = 0; + virtual bool HasActivePeers() const = 0; + virtual int GetNumLobbyUsers() const = 0; + virtual int GetNumActiveLobbyUsers() const = 0; + virtual bool IsLobbyUserConnected( int index ) const = 0; + + virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const = 0; + virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const = 0; + + virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) ) = 0; + virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) = 0; + virtual void SendReliableToHost( int type, idBitMsg & msg ) = 0; + + // Lobby user access + virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const = 0; + virtual void KickLobbyUser( lobbyUserID_t lobbyUserID ) = 0; + virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const = 0; + virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const = 0; + virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const = 0; + virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ) = 0; + + virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const = 0; + virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const = 0; + virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const = 0; + virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const = 0; + virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const = 0; + virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const = 0; + virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) = 0; + virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const = 0; + virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) = 0; + virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) = 0; + virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const = 0; + + virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const = 0; + virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const = 0; + virtual int PeerIndexForHost() const = 0; + + virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ) = 0; + virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ) = 0; + virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const = 0; + + virtual const char * GetHostUserName() const = 0; + virtual const idMatchParameters & GetMatchParms() const = 0; + virtual bool IsLobbyFull() const = 0; + + // Peer access + virtual bool EnsureAllPeersHaveBaseState() = 0; + virtual bool AllPeersInGame() const = 0; + virtual int GetNumConnectedPeers() const = 0; + virtual int GetNumConnectedPeersInGame() const = 0; + virtual int PeerIndexOnHost() const = 0; + virtual bool IsPeerDisconnected( int peerIndex ) const = 0; + + // Snapshots + virtual bool AllPeersHaveStaleSnapObj( int objId ) = 0; + virtual bool AllPeersHaveExpectedSnapObj( int objId ) = 0; + virtual void RefreshSnapObj( int objId ) = 0; + virtual void MarkSnapObjDeleted( int objId ) = 0; + virtual void AddSnapObjTemplate( int objID, idBitMsg & msg ) = 0; + + // Debugging + virtual void DrawDebugNetworkHUD() const = 0; + virtual void DrawDebugNetworkHUD2() const = 0; + virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) = 0; +}; + +/* +================================================ +idSession +================================================ +*/ +class idSession { +public: + + enum sessionState_t { + PRESS_START, + IDLE, + SEARCHING, + CONNECTING, + PARTY_LOBBY, + GAME_LOBBY, + LOADING, + INGAME, + BUSY, + MAX_STATES + }; + + enum sessionOption_t { + OPTION_LEAVE_WITH_PARTY = BIT( 0 ), // As a party leader, whether or not to drag your party members with you when you leave a game lobby + OPTION_ALL = 0xFFFFFFFF + }; + + idSession() : + signInManager( NULL ), + saveGameManager( NULL ), + achievementSystem( NULL ), + dedicatedServerSearch( NULL ) { } + virtual ~idSession(); + + virtual void Initialize() = 0; + virtual void Shutdown() = 0; + + virtual void InitializeSoundRelatedSystems() = 0; + virtual void ShutdownSoundRelatedSystems() = 0; + + //===================================================================================================== + // Lobby management + //===================================================================================================== + virtual void CreatePartyLobby( const idMatchParameters & parms_ ) = 0; + virtual void FindOrCreateMatch( const idMatchParameters & parms_ ) = 0; + virtual void CreateMatch( const idMatchParameters & parms_ ) = 0; + virtual void CreateGameStateLobby( const idMatchParameters & parms_ ) = 0; + virtual void UpdateMatchParms( const idMatchParameters & parms_ ) = 0; + virtual void UpdatePartyParms( const idMatchParameters & parms_ ) = 0; + virtual void StartMatch() = 0; + virtual void EndMatch( bool premature=false ) = 0; // Meant for host to end match gracefully, go back to lobby, tally scores, etc + virtual void MatchFinished() = 0; // this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time + virtual void QuitMatch() = 0; // Meant for host or peer to quit the match before it ends, will instigate host migration, etc + virtual void QuitMatchToTitle() = 0; // Will forcefully quit the match and return to the title screen. + virtual void SetSessionOption( sessionOption_t option ) = 0; + virtual void ClearSessionOption( sessionOption_t option ) = 0; + virtual sessionState_t GetBackState() = 0; + virtual void Cancel() = 0; + virtual void MoveToPressStart() = 0; + virtual void FinishDisconnect() = 0; + virtual void LoadingFinished() = 0; + virtual bool IsCurrentLobbyMigrating() const = 0; + virtual bool IsLosingConnectionToHost() const = 0; + virtual bool WasMigrationGame() const = 0; + virtual bool ShouldRelaunchMigrationGame() const = 0; + virtual bool WasGameLobbyCoalesced() const = 0; + + virtual bool GetMigrationGameData( idBitMsg & msg, bool reading ) = 0; + virtual bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) = 0; + + virtual bool GetMatchParamUpdate( int & peer, int & msg ) = 0; + + virtual void Pump() = 0; + virtual void ProcessSnapAckQueue() = 0; + + virtual void InviteFriends() = 0; + virtual void InviteParty() = 0; + virtual void ShowPartySessions() = 0; + + virtual bool IsPlatformPartyInLobby() = 0; + + // Lobby user/peer access + // The party and game lobby are the two platform lobbies that notify the backends (Steam/PSN/LIVE of changes) + virtual idLobbyBase & GetPartyLobbyBase() = 0; + virtual idLobbyBase & GetGameLobbyBase() = 0; + + // Game state lobby is the lobby used while in-game. It is so the dedicated server can host this lobby + // and have all platform clients join. It does NOT notify the backends of changes, it's purely for the dedicated + // server to be able to host the in-game lobby. + virtual idLobbyBase & GetActingGameStateLobbyBase() = 0; + + // GetActivePlatformLobbyBase will return either the game or party lobby, it won't return the game state lobby + // This function is generally used for menus, in-game code should refer to GetActingGameStateLobby + virtual idLobbyBase & GetActivePlatformLobbyBase() = 0; + + virtual idLobbyBase & GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID ) = 0; + + virtual idPlayerProfile * GetProfileFromMasterLocalUser() = 0; + + virtual bool ProcessInputEvent( const sysEvent_t * ev ) = 0; + virtual float GetUpstreamDropRate() = 0; + virtual float GetUpstreamQueueRate() = 0; + virtual int GetQueuedBytes() = 0; + + virtual int GetLoadingID() = 0; + virtual bool IsAboutToLoad() const = 0; + + virtual const char * GetLocalUserName( int i ) const = 0; + + virtual sessionState_t GetState() const = 0; + virtual const char * GetStateString() const = 0; + + virtual int NumServers() const = 0; + virtual void ListServers( const idCallback & callback ) = 0; + virtual void CancelListServers() = 0; + virtual void ConnectToServer( int i ) = 0; + virtual const serverInfo_t * ServerInfo( int i ) const = 0; + virtual const idList< idStr > * ServerPlayerList( int i ) = 0; + virtual void ShowServerGamerCardUI( int i ) = 0; + + virtual void ShowOnlineSignin() = 0; + + virtual void DropClient( int peerNum, int lobbyType ) = 0; + + virtual void JoinAfterSwap( void * joinID ) = 0; + + //===================================================================================================== + // Downloadable Content + //===================================================================================================== + virtual void EnumerateDownloadableContent() = 0; + virtual int GetNumContentPackages() const = 0; + virtual int GetContentPackageID( int contentIndex ) const = 0; + virtual const char * GetContentPackagePath( int contentIndex ) const = 0; + virtual int GetContentPackageIndexForID( int contentID ) const = 0; + + virtual void ShowSystemMarketplaceUI() const = 0; + virtual bool GetSystemMarketplaceHasNewContent() const = 0; + virtual void SetSystemMarketplaceHasNewContent( bool hasNewContent ) = 0; + + //===================================================================================================== + // Title Storage Vars + //===================================================================================================== + virtual float GetTitleStorageFloat( const char * name, float defaultFloat ) const = 0; + virtual int GetTitleStorageInt( const char * name, int defaultInt ) const = 0; + virtual bool GetTitleStorageBool( const char * name, bool defaultBool ) const = 0; + virtual const char * GetTitleStorageString( const char * name, const char * defaultString ) const = 0; + + virtual bool GetTitleStorageFloat( const char * name, float defaultFloat, float & out ) const { out = defaultFloat; return false; } + virtual bool GetTitleStorageInt( const char * name, int defaultInt, int & out ) const { out = defaultInt; return false; } + virtual bool GetTitleStorageBool( const char * name, bool defaultBool, bool & out ) const { out = defaultBool; return false; } + virtual bool GetTitleStorageString( const char * name, const char * defaultString, const char ** out ) const { if ( out != NULL ) { *out = defaultString; } return false; } + + virtual bool IsTitleStorageLoaded() = 0; + + //===================================================================================================== + // Leaderboard + //===================================================================================================== + virtual void LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment = NULL ) = 0; + virtual void LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ) = 0; + virtual void LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ) = 0; + virtual void LeaderboardFlush() = 0; + + //===================================================================================================== + // Scoring (currently just for TrueSkill) + //===================================================================================================== + virtual void SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) = 0; + + + //===================================================================================================== + // Savegames + // + // Default async implementations, saves to a folder, uses game.details file to describe each save. + // Files saved are up to the game and provide through a callback mechanism. If you want to be notified when + // one of these operations have completed, either modify the framework's completedCallback of each of the + // savegame processors or create your own processors and execute with the savegameManager. + //===================================================================================================== + virtual saveGameHandle_t SaveGameSync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) = 0; + virtual saveGameHandle_t SaveGameAsync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) = 0; + virtual saveGameHandle_t LoadGameSync( const char * name, saveFileEntryList_t & files ) = 0; + virtual saveGameHandle_t EnumerateSaveGamesSync() = 0; + virtual saveGameHandle_t EnumerateSaveGamesAsync() = 0; + virtual saveGameHandle_t DeleteSaveGameSync( const char * name ) = 0; + virtual saveGameHandle_t DeleteSaveGameAsync( const char * name ) = 0; + + virtual bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const = 0; + virtual void CancelSaveGameWithHandle( const saveGameHandle_t & handle ) = 0; + + // Needed for main menu integration + virtual bool IsEnumerating() const = 0; + virtual saveGameHandle_t GetEnumerationHandle() const = 0; + + // Returns the known list of savegames, must first enumerate for the savegames ( via session->Enumerate() ) + virtual const saveGameDetailsList_t & GetEnumeratedSavegames() const = 0; + + // These are on session and not idGame so it can persist across game deallocations + virtual void SetCurrentSaveSlot( const char * slotName ) = 0; + virtual const char * GetCurrentSaveSlot() const = 0; + + // Error checking + virtual bool IsDLCAvailable( const char * mapName ) = 0; + virtual bool LoadGameCheckDiscNumber( idSaveLoadParms & parms ) = 0; + + //===================================================================================================== + // GamerCard UI + //===================================================================================================== + + virtual void ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ) = 0; + + //===================================================================================================== + virtual void UpdateRichPresence() = 0; + + virtual void SendUsercmds( idBitMsg & msg ) = 0; + virtual void SendSnapshot( class idSnapShot & ss ) = 0; + + virtual int GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ); + + virtual void UpdateSignInManager() = 0; + + idSignInManagerBase & GetSignInManager() { return *signInManager; } + idSaveGameManager & GetSaveGameManager() { return *saveGameManager; } + idAchievementSystem & GetAchievementSystem() { return *achievementSystem; } + + bool HasSignInManager() const { return ( signInManager != NULL ); } + bool HasAchievementSystem() const { return ( achievementSystem != NULL ); } + + virtual bool IsSystemUIShowing() const = 0; + virtual void SetSystemUIShowing( bool show ) = 0; + + //===================================================================================================== + // Voice chat + //===================================================================================================== + virtual voiceState_t GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID ) = 0; + virtual voiceStateDisplay_t GetDisplayStateFromVoiceState( voiceState_t voiceState ) const = 0; + virtual void ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID ) = 0; + virtual void SetActiveChatGroup( int groupIndex ) = 0; + virtual void CheckVoicePrivileges() = 0; + virtual void SetVoiceGroupsToTeams() = 0; + virtual void ClearVoiceGroups() = 0; + + //===================================================================================================== + // Bandwidth / QoS checking + //===================================================================================================== + virtual bool StartOrContinueBandwidthChallenge( bool forceStart ) = 0; + virtual void DebugSetPeerSnaprate( int peerIndex, int snapRateMS ) = 0; + virtual float GetIncomingByteRate() = 0; + + //===================================================================================================== + // Invites + //===================================================================================================== + virtual void HandleBootableInvite( int64 lobbyId = 0 ) = 0; + virtual void HandleExitspawnInvite( const lobbyConnectInfo_t & connectInfo ) {} + virtual void ClearBootableInvite() = 0; + virtual void ClearPendingInvite() = 0; + virtual bool HasPendingBootableInvite() = 0; + virtual void SetDiscSwapMPInvite( void * parm ) = 0; + virtual void * GetDiscSwapMPInviteParms() = 0; + virtual bool IsDiscSwapMPInviteRequested() const = 0; + + //===================================================================================================== + // Notifications + //===================================================================================================== + // This is called when a LocalUser is signed in/out + virtual void OnLocalUserSignin( idLocalUser * user ) = 0; + virtual void OnLocalUserSignout( idLocalUser * user ) = 0; + + // This is called when the master LocalUser is signed in/out, these are called after OnLocalUserSignin/out() + virtual void OnMasterLocalUserSignout() = 0; + virtual void OnMasterLocalUserSignin() = 0; + + // After a local user has signed in and their profile has loaded + virtual void OnLocalUserProfileLoaded( idLocalUser * user ) = 0; + +protected: + idSignInManagerBase * signInManager; // pointer so we can treat dynamically bind platform-specific impl + idSaveGameManager * saveGameManager; + idAchievementSystem * achievementSystem; // pointer so we can treat dynamically bind platform-specific impl + idDedicatedServerSearch * dedicatedServerSearch; +}; + +/* +======================== +idSession::idGetInputRouting +======================== +*/ +ID_INLINE int idSession::GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ) { + for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { + inputRouting[ i ] = -1; + } + inputRouting[0] = 0; + return 1; +} + +extern idSession * session; + +#endif // __SYS_SESSION_H__ diff --git a/neo/sys/sys_session_callbacks.cpp b/neo/sys/sys_session_callbacks.cpp new file mode 100644 index 00000000..4b5de405 --- /dev/null +++ b/neo/sys/sys_session_callbacks.cpp @@ -0,0 +1,409 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "../framework/Common_local.h" +#include "sys_session_local.h" + +// The more the idLobby class needs to call back into this class, the more likely we're doing something wrong, and there is a better way. + +/* +======================== +idSessionLocalCallbacks::BecomingHost +This is called when +======================== +*/ +bool idSessionLocalCallbacks::BecomingHost( idLobby & lobby ) { + if ( lobby.lobbyType == idLobby::TYPE_GAME ) { + if ( sessionLocal->GetActivePlatformLobby() != &lobby ) { + idLib::Printf( "BecomingHost: Must be past the party lobby to become host of a game lobby.\n" ); + return false; + } + if ( sessionLocal->localState == idSessionLocal::STATE_INGAME || sessionLocal->localState == idSessionLocal::STATE_LOADING ) { + // If we are in a game, go back to the lobby before becoming the new host of a game lobby + sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_PEER ); + + // session mgr housekeeping that would usually be done through the standard EndMatch path + sessionLocal->EndMatchForMigration(); + } + } + + return true; +} + +/* +======================== +idSessionLocalCallbacks::BecameHost +======================== +*/ +void idSessionLocalCallbacks::BecameHost( idLobby & lobby ) { + // If we were in the lobby when we switched to host, then set the right state + if ( lobby.lobbyType == idLobby::TYPE_PARTY && sessionLocal->localState == idSessionLocal::STATE_PARTY_LOBBY_PEER ) { + sessionLocal->SetState( idSessionLocal::STATE_PARTY_LOBBY_HOST ); + } else if ( lobby.lobbyType == idLobby::TYPE_GAME && sessionLocal->localState == idSessionLocal::STATE_GAME_LOBBY_PEER ) { + sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_HOST ); + } +} + +/* +======================== +idSessionLocalCallbacks::BecomingPeer +======================== +*/ +bool idSessionLocalCallbacks::BecomingPeer( idLobby & lobby ) { + if ( lobby.lobbyType == idLobby::TYPE_GAME ) { + if ( sessionLocal->localState == idSessionLocal::STATE_INGAME || sessionLocal->localState == idSessionLocal::STATE_LOADING ) { + // Go to the party lobby while we try to connect to the new host + // This isn't totally necessary but we want to end the current game now and go to some screen. + // When the connection goes through or fails will send the session mgr to the appropriate state (game lobby or main menu) + + // What happens if we got the game migration before the party migration? + sessionLocal->SetState( sessionLocal->GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER ); + + // session mgr housekeeping that would usually be done through the standard EndMatch path + sessionLocal->EndMatchForMigration(); + + return true; // return true tells the session that we want him to tell us when the connects/fails + } + } + return false; +} + +/* +======================== +idSessionLocalCallbacks::BecamePeer +======================== +*/ +void idSessionLocalCallbacks::BecamePeer( idLobby & lobby ) { + if ( lobby.lobbyType == idLobby::TYPE_GAME ) { + sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_PEER ); + } +} + +/* +======================== +idSessionLocalCallbacks::FailedGameMigration +======================== +*/ +void idSessionLocalCallbacks::FailedGameMigration( idLobby & lobby ) { + // We failed to complete a game migration this could happen for a couple reasons: + // -The network invites failed / failed to join migrated session + // -There was nobody to invite + lobby.ResetAllMigrationState(); + if ( lobby.lobbyType == idLobby::TYPE_GAME ) { // this check is a redundant since we should only get this CB from the game session + + sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_HOST ); + + // Make sure the sessions are joinable again + sessionLocal->EndSessions(); + } +} + +/* +======================== +idSessionLocalCallbacks::MigrationEnded +======================== +*/ +void idSessionLocalCallbacks::MigrationEnded( idLobby & lobby ) { + if ( lobby.migrationInfo.persistUntilGameEndsData.wasMigratedGame ) { +#if 1 + if ( lobby.lobbyType == idLobby::TYPE_GAME || ( lobby.lobbyType == idLobby::TYPE_PARTY && session->GetState() <= idSession::PARTY_LOBBY ) ) { + common->Dialog().ClearDialog( GDM_MIGRATING ); + common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); + common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); + + if ( lobby.GetNumLobbyUsers() <= 1 ) { + if ( MatchTypeHasStats( lobby.parms.matchFlags ) ) { + common->Dialog().AddDialog( GDM_MIGRATING_FAILED_DISBANDED_STATS, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Game has disbanded + } else { + common->Dialog().AddDialog( GDM_MIGRATING_FAILED_DISBANDED, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Game has disbanded + } + } else { + //common->Dialog().AddDialog( GDM_MIGRATING_FAILED_CONNECTION, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Lost connection to game + if ( lobby.lobbyType == idLobby::TYPE_GAME && MatchTypeHasStats( lobby.parms.matchFlags ) ) { + // This means we came from a public match, so tell them they didn't lose stats + common->Dialog().AddDialog( GDM_HOST_CONNECTION_LOST_STATS, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // The connection to the host has been lost. This game will not count towards your ranking. + } else { + // This means we came from a private match, just say host quit + common->Dialog().AddDialog( GDM_HOST_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // The connection to the host has been lost. + } + } + + lobby.ResetAllMigrationState(); + + // Make sure the sessions are joinable again + sessionLocal->EndSessions(); + } +#else + // If we get here, we migrated from a game + if ( lobby.GetNumLobbyUsers() <= 1 && lobby.lobbyType == idLobby::TYPE_GAME ) { + if ( !MatchTypeIsJoinInProgress( lobby.parms.matchFlags ) ) { + // Handles 'soft' failed game migration where we migrated from a game and are now alone + gameDialogMessages_t errorDlg = GDM_INVALID; + lobby.migrationInfo.persistUntilGameEndsData.hasGameData = false; // never restart the game if we are by ourselves + if ( lobby.migrationInfo.invites.Num() > 0 ) { + // outstanding invites: migration failed + errorDlg = ( MatchTypeHasStats( lobby.migrateMsgFlags ) && ( sessionLocal->GetFlushedStats() == false ) ) ? GDM_MIGRATING_FAILED_CONNECTION_STATS : GDM_MIGRATING_FAILED_CONNECTION; + } else { + // there was no one to invite + errorDlg = ( MatchTypeHasStats( lobby.migrateMsgFlags ) && ( sessionLocal->GetFlushedStats() == false ) ) ? GDM_MIGRATING_FAILED_DISBANDED_STATS : GDM_MIGRATING_FAILED_DISBANDED; + } + if ( errorDlg != GDM_INVALID ) { + common->Dialog().AddDialog( errorDlg, DIALOG_ACCEPT, NULL, NULL, false ); + } + common->Dialog().ClearDialog( GDM_MIGRATING ); + common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); + common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); + + FailedGameMigration( lobby ); + } + } else if ( lobby.lobbyType == idLobby::TYPE_PARTY ) { + if ( session->GetState() <= idSession::PARTY_LOBBY ) { + // We got dropped the party lobby, let them know what happened + common->Dialog().ClearDialog( GDM_MIGRATING ); + common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); + common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); + + if ( lobby.GetNumLobbyUsers() <= 1 ) { + common->Dialog().AddDialog( GDM_MIGRATING_FAILED_DISBANDED, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Game has disbanded + } else { + //common->Dialog().AddDialog( GDM_MIGRATING_FAILED_CONNECTION, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Lost connection to game + common->Dialog().AddDialog( GDM_HOST_CONNECTION_LOST_STATS, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); + } + + lobby.ResetAllMigrationState(); + + // Make sure the sessions are joinable again + sessionLocal->EndSessions(); + } + } +#endif + } else if ( lobby.GetNumLobbyUsers() <= 1 && session->GetState() == idSession::PARTY_LOBBY ) { + // If they didn't come from a game, and are by themselves, just show the lobby disband msg + common->Dialog().AddDialog( GDM_LOBBY_DISBANDED, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // The lobby you were previously in has disbanded + + // Make sure the sessions are joinable again + sessionLocal->EndSessions(); + } +} + +/* +======================== +idSessionLocalCallbacks::GoodbyeFromHost +======================== +*/ +void idSessionLocalCallbacks::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) { + sessionLocal->GoodbyeFromHost( lobby, peerNum, remoteAddress, msgType ); +} + +/* +======================== +idSessionLocalCallbacks::AnyPeerHasAddress +======================== +*/ +bool idSessionLocalCallbacks::AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const { + return sessionLocal->GetPartyLobby().FindAnyPeer( remoteAddress ) || sessionLocal->GetGameLobby().FindAnyPeer( remoteAddress ); +} + +/* +======================== +idSessionLocalCallbacks::RecvLeaderboardStats +======================== +*/ +void idSessionLocalCallbacks::RecvLeaderboardStats( idBitMsg & msg ) { + // Steam and PS3 just write them as they come per player, they don't need to flush + sessionLocal->RecvLeaderboardStatsForPlayer( msg ); +} + +/* +======================== +idSessionLocalCallbacks::ReceivedFullSnap +======================== +*/ +void idSessionLocalCallbacks::ReceivedFullSnap() { + // If we received a full snap, then we can transition into the INGAME state + sessionLocal->numFullSnapsReceived++; + + if ( sessionLocal->numFullSnapsReceived < 2 ) { + return; + } + + if ( sessionLocal->localState != idSessionLocal::STATE_INGAME ) { + sessionLocal->GetActingGameStateLobby().QueueReliableMessage( sessionLocal->GetActingGameStateLobby().host, idLobby::RELIABLE_IN_GAME ); // Let host know we are in game now + sessionLocal->SetState( idSessionLocal::STATE_INGAME ); + } +} + +/* +======================== +idSessionLocalCallbacks::LeaveGameLobby +======================== +*/ +void idSessionLocalCallbacks::LeaveGameLobby() { + + // Make sure we're in the game lobby + if ( session->GetState() != idSession::GAME_LOBBY ) { + return; + } + + // If we're the host of the party, only we are allowed to make this call + if ( sessionLocal->GetPartyLobby().IsHost() ) { + return; + } + + sessionLocal->GetGameLobby().Shutdown(); + sessionLocal->SetState( idSessionLocal::STATE_PARTY_LOBBY_PEER ); +} + +/* +======================== +idSessionLocalCallbacks::PrePickNewHost +This is called when we have determined that we need to pick a new host. +Call PickNewHostInternal to continue on with the host picking process. +======================== +*/ +void idSessionLocalCallbacks::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) { + sessionLocal->PrePickNewHost( lobby, forceMe, inviteOldHost ); +} + +/* +======================== +idSessionLocalCallbacks::PreMigrateInvite +This is called just before we get invited to a migrated session +If we return false, the invite will be ignored +======================== +*/ +bool idSessionLocalCallbacks::PreMigrateInvite( idLobby & lobby ) { + return sessionLocal->PreMigrateInvite( lobby ); +} + +/* +======================== +idSessionLocalCallbacks::ConnectAndMoveToLobby +======================== +*/ +void idSessionLocalCallbacks::ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk ) { + + // See if we are already in the game lobby + idLobby * lobby = sessionLocal->GetLobbyFromType( destLobbyType ); + + if ( lobby == NULL ) { + idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: Invalid lobby type.\n" ); + return; + } + + if ( lobby->lobbyBackend != NULL && lobby->lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) { + idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: Already in lobby.\n" ); + return; + } + + // See if we are in a game, or loading into a game. If so, ignore invites from our party host + if ( destLobbyType == idLobby::TYPE_GAME || destLobbyType == idLobby::TYPE_GAME_STATE ) { + if ( GetState() == idSession::INGAME || GetState() == idSession::LOADING ) { + idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: In a different game, ignoring.\n" ); + return; + } + } + + // End current game lobby + lobby->Shutdown(); + + // waitForPartyOk will be true if the party host wants us to wait for his ok to stay in the lobby + lobby->waitForPartyOk = waitForPartyOk; + + // Connect to new game lobby + sessionLocal->ConnectAndMoveToLobby( *lobby, connectInfo, true ); // Consider this an invite if party host told us to connect +} + +/* +======================== +idSessionLocalCallbacks::HandleServerQueryRequest +======================== +*/ +void idSessionLocalCallbacks::HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) { + sessionLocal->HandleServerQueryRequest( remoteAddr, msg, msgType ); +} + +/* +======================== +idSessionLocalCallbacks::HandleServerQueryAck +======================== +*/ +void idSessionLocalCallbacks::HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) { + sessionLocal->HandleServerQueryAck( remoteAddr, msg ); +} + +extern idCVar net_headlessServer; + +/* +======================== +idSessionLocalCallbacks::HandlePeerMatchParamUpdate +======================== +*/ +void idSessionLocalCallbacks::HandlePeerMatchParamUpdate( int peer, int msg ) { + if ( net_headlessServer.GetBool() ) { + sessionLocal->storedPeer = peer; + sessionLocal->storedMsgType = msg; + } +} + +/* +======================== +idSessionLocalCallbacks::CreateLobbyBackend +======================== +*/ +idLobbyBackend * idSessionLocalCallbacks::CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) { + return sessionLocal->CreateLobbyBackend( p, skillLevel, lobbyType ); +} + +/* +======================== +idSessionLocalCallbacks::FindLobbyBackend +======================== +*/ +idLobbyBackend * idSessionLocalCallbacks::FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) { + return sessionLocal->FindLobbyBackend( p, numPartyUsers, skillLevel, lobbyType ); +} + +/* +======================== +idSessionLocalCallbacks::JoinFromConnectInfo +======================== +*/ +idLobbyBackend * idSessionLocalCallbacks::JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) { + return sessionLocal->JoinFromConnectInfo( connectInfo, lobbyType ); +} + +/* +======================== +idSessionLocalCallbacks::DestroyLobbyBackend +======================== +*/ +void idSessionLocalCallbacks::DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) { + sessionLocal->DestroyLobbyBackend( lobbyBackend ); +} diff --git a/neo/sys/sys_session_local.cpp b/neo/sys/sys_session_local.cpp new file mode 100644 index 00000000..142d8938 --- /dev/null +++ b/neo/sys/sys_session_local.cpp @@ -0,0 +1,4093 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_session_local.h" +#include "sys_voicechat.h" +#include "sys_dedicated_server_search.h" + + +idCVar ui_skinIndex( "ui_skinIndex", "0", CVAR_ARCHIVE, "Selected skin index" ); +idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" ); +idCVar ui_autoReload( "ui_autoReload", "1", CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" ); + +idCVar net_maxSearchResults( "net_maxSearchResults", "25", CVAR_INTEGER, "Max results that are allowed to be returned in a search request" ); +idCVar net_maxSearchResultsToTry( "net_maxSearchResultsToTry", "5", CVAR_INTEGER, "Max results to try before giving up." ); // At 15 second timeouts per, 1 min 15 worth of connecting attempt time + +idCVar net_LobbyCoalesceTimeInSeconds( "net_LobbyCoalesceTimeInSeconds", "30", CVAR_INTEGER, "Time in seconds when a lobby will try to coalesce with another lobby when there is only one user." ); +idCVar net_LobbyRandomCoalesceTimeInSeconds( "net_LobbyRandomCoalesceTimeInSeconds", "3", CVAR_INTEGER, "Random time to add to net_LobbyCoalesceTimeInSeconds" ); + +idCVar net_useGameStateLobby( "net_useGameStateLobby", "0", CVAR_BOOL, "" ); +//idCVar net_useGameStateLobby( "net_useGameStateLobby", "1", CVAR_BOOL, "" ); + +#if !defined( ID_RETAIL ) || defined( ID_RETAIL_INTERNAL ) +idCVar net_ignoreTitleStorage( "net_ignoreTitleStorage", "0", CVAR_BOOL, "Ignore title storage" ); +#endif + +idCVar net_maxLoadResourcesTimeInSeconds( "net_maxLoadResourcesTimeInSeconds", "0", CVAR_INTEGER, "How long, in seconds, clients have to load resources. Used for loose asset builds." ); +idCVar net_migrateHost( "net_migrateHost", "-1", CVAR_INTEGER, "Become host of session (0 = party, 1 = game) for testing purposes" ); +extern idCVar net_debugBaseStates; + +idCVar net_testPartyMemberConnectFail( "net_testPartyMemberConnectFail", "-1", CVAR_INTEGER, "Force this party member index to fail to connect to games." ); + +//FIXME: this could use a better name. +idCVar net_offlineTransitionThreshold( "net_offlineTransitionThreshold", "1000", CVAR_INTEGER, "Time, in milliseconds, to wait before kicking back to the main menu when a profile losses backend connection during an online game"); + +idCVar net_port( "net_port", "27015", CVAR_INTEGER, "host port number" ); // Port to host when using dedicated servers, port to broadcast on when looking for a dedicated server to connect to +idCVar net_headlessServer( "net_headlessServer", "0", CVAR_BOOL, "toggle to automatically host a game and allow peer[0] to control menus" ); + +const char * idSessionLocal::stateToString[ NUM_STATES ] = { + ASSERT_ENUM_STRING( STATE_PRESS_START, 0 ), + ASSERT_ENUM_STRING( STATE_IDLE, 1 ), + ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_HOST, 2 ), + ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_PEER, 3 ), + ASSERT_ENUM_STRING( STATE_GAME_LOBBY_HOST, 4 ), + ASSERT_ENUM_STRING( STATE_GAME_LOBBY_PEER, 5 ), + ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_HOST, 6 ), + ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_PEER, 7 ), + ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, 8 ), + ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, 9 ), + ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, 10 ), + ASSERT_ENUM_STRING( STATE_FIND_OR_CREATE_MATCH, 11 ), + ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_PARTY, 12 ), + ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME, 13 ), + ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME_STATE, 14 ), + ASSERT_ENUM_STRING( STATE_BUSY, 15 ), + ASSERT_ENUM_STRING( STATE_LOADING, 16 ), + ASSERT_ENUM_STRING( STATE_INGAME, 17 ), +}; + +struct netVersion_s { + netVersion_s() { sprintf( string, "%s.%d", ENGINE_VERSION, BUILD_NUMBER ); } + char string[256]; +} netVersion; + +/* +======================== +NetGetVersionChecksum +======================== +*/ +unsigned long NetGetVersionChecksum() { +#if 0 + return idStr( com_version.GetString() ).Hash(); +#else + unsigned long ret = 0; + + CRC32_InitChecksum( ret ); + CRC32_UpdateChecksum( ret, netVersion.string, idStr::Length( netVersion.string ) ); + CRC32_FinishChecksum( ret ); + + NET_VERBOSE_PRINT( "NetGetVersionChecksum - string : %s\n", netVersion.string ); + NET_VERBOSE_PRINT( "NetGetVersionChecksum - checksum : %i\n", ret ); + return ret; +#endif +} + +/* +======================== +idSessionLocal::idSessionLocal +======================== +*/ +idSessionLocal::idSessionLocal() : + processorSaveFiles( new (TAG_SAVEGAMES) idSaveGameProcessorSaveFiles ), + processorLoadFiles( new (TAG_SAVEGAMES) idSaveGameProcessorLoadFiles ), + processorDelete( new (TAG_SAVEGAMES) idSaveGameProcessorDelete ), + processorEnumerate( new (TAG_SAVEGAMES) idSaveGameProcessorEnumerateGames ) { + InitBaseState(); +} + +/* +======================== +idSessionLocal::idSessionLocal +======================== +*/ +idSessionLocal::~idSessionLocal() { + delete processorSaveFiles; + delete processorLoadFiles; + delete processorDelete; + delete processorEnumerate; + delete sessionCallbacks; +} + + +/* +======================== +idSessionLocal::InitBaseState +======================== +*/ +void idSessionLocal::InitBaseState() { + + //assert( mem.IsGlobalHeap() ); + + localState = STATE_PRESS_START; + sessionOptions = 0; + currentID = 0; + + sessionCallbacks = new (TAG_NETWORKING) idSessionLocalCallbacks( this ); + + connectType = CONNECT_NONE; + connectTime = 0; + + upstreamDropRate = 0.0f; + upstreamDropRateTime = 0; + upstreamQueueRate = 0.0f; + upstreamQueueRateTime = 0; + queuedBytes = 0; + + lastVoiceSendtime = 0; + hasShownVoiceRestrictionDialog = false; + + isSysUIShowing = false; + + pendingInviteDevice = 0; + pendingInviteMode = PENDING_INVITE_NONE; + + downloadedContent.Clear(); + marketplaceHasNewContent = false; + + offlineTransitionTimerStart = 0; + showMigratingInfoStartTime = 0; + nextGameCoalesceTime = 0; + gameLobbyWasCoalesced = false; + numFullSnapsReceived = 0; + + flushedStats = false; + + titleStorageLoaded = false; + + droppedByHost = false; + loadingID = 0; + + storedPeer = -1; + storedMsgType = -1; + + inviteInfoRequested = false; + + enumerationHandle = 0; + + waitingOnGameStateMembersToLeaveTime = 0; + waitingOnGameStateMembersToJoinTime = 0; +} + +/* +======================== +idSessionLocal::FinishDisconnect +======================== +*/ +void idSessionLocal::FinishDisconnect() { + GetPort().Close(); + while ( sendQueue.Peek() != NULL ) { + sendQueue.RemoveFirst(); + } + while ( recvQueue.Peek() != NULL ) { + recvQueue.RemoveFirst(); + } +} + +//==================================================================================== + +idCVar net_connectTimeoutInSeconds( "net_connectTimeoutInSeconds", "15", CVAR_INTEGER, "timeout (in seconds) while connecting" ); + +/* +======================== +idSessionLocal::CreatePartyLobby +======================== +*/ +void idSessionLocal::CreatePartyLobby( const idMatchParameters & parms_ ) { + NET_VERBOSE_PRINT( "NET: CreatePartyLobby\n" ); + + // Shutdown any possible party lobby + GetPartyLobby().Shutdown(); + GetPartyLobby().ResetAllMigrationState(); + + // Shutdown any possible game lobby + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + + // Start hosting a new party lobby + GetPartyLobby().StartHosting( parms_ ); + + connectType = CONNECT_NONE; + connectTime = Sys_Milliseconds(); + + // Wait for it to complete + SetState( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY ); +} + +/* +======================== +idSessionLocal::CreateMatch +======================== +*/ +void idSessionLocal::CreateMatch( const idMatchParameters & p ) { + NET_VERBOSE_PRINT( "NET: CreateMatch\n" ); + + if ( ( p.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) && !GetPartyLobby().IsLobbyActive() ) { + NET_VERBOSE_PRINT( "NET: CreateMatch MATCH_PARTY_INVITE_PLACEHOLDER\n" ); + CreatePartyLobby( p ); + connectType = CONNECT_NONE; + return; + } + + // Shutdown any possible game lobby + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + GetGameLobby().ResetAllMigrationState(); + + // Start hosting a new game lobby + GetGameLobby().StartHosting( p ); + + connectType = CONNECT_NONE; + connectTime = Sys_Milliseconds(); + + // Wait for it to complete + SetState( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY ); +} + +/* +======================== +idSessionLocal::CreateGameStateLobby +======================== +*/ +void idSessionLocal::CreateGameStateLobby( const idMatchParameters & p ) { + NET_VERBOSE_PRINT( "NET: CreateGameStateLobby\n" ); + + // Shutdown any possible game state lobby + GetGameStateLobby().Shutdown(); + GetGameStateLobby().ResetAllMigrationState(); + + // Start hosting a new game lobby + GetGameStateLobby().StartHosting( p ); + + connectType = CONNECT_NONE; + connectTime = Sys_Milliseconds(); + + waitingOnGameStateMembersToLeaveTime = 0; // Make sure to reset + waitingOnGameStateMembersToJoinTime = 0; + + // Wait for it to complete + SetState( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY ); +} + +/* +======================== +idSessionLocal::FindOrCreateMatch +======================== +*/ +void idSessionLocal::FindOrCreateMatch( const idMatchParameters & p ) { + NET_VERBOSE_PRINT( "NET: FindOrCreateMatch\n" ); + + if ( ( p.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) && !GetPartyLobby().IsLobbyActive() ) { + NET_VERBOSE_PRINT( "NET: FindOrCreateMatch MATCH_PARTY_INVITE_PLACEHOLDER\n" ); + CreatePartyLobby( p ); + connectType = CONNECT_FIND_OR_CREATE; + return; + } + + // Shutdown any possible game lobby + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + GetGameLobby().ResetAllMigrationState(); + + // Start searching for a game + GetGameLobby().StartFinding( p ); + + connectType = CONNECT_FIND_OR_CREATE; + connectTime = Sys_Milliseconds(); + gameLobbyWasCoalesced = false; + numFullSnapsReceived = 0; + + // Wait for searching to complete + SetState( STATE_FIND_OR_CREATE_MATCH ); +} + +/* +======================== +idSessionLocal::StartLoading +======================== +*/ +void idSessionLocal::StartLoading() { + NET_VERBOSE_PRINT( "NET: StartLoading\n" ); + + if ( MatchTypeIsOnline( GetActingGameStateLobby().parms.matchFlags ) ) { + if ( !GetActingGameStateLobby().IsHost() ) { + idLib::Warning( "Ignoring call to StartLoading because we are not the host. state is %s", stateToString[ localState ] ); + return; + } + + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + if ( GetActingGameStateLobby().peers[p].IsConnected() ) { + GetActingGameStateLobby().QueueReliableMessage( p, idLobby::RELIABLE_START_LOADING ); + GetActingGameStateLobby().peers[p].startResourceLoadTime = Sys_Milliseconds(); + } + } + } + + VerifySnapshotInitialState(); + + SetState( STATE_LOADING ); +} + +/* +======================== +idSessionLocal::StartMatch +======================== +*/ +void idSessionLocal::StartMatch() { + NET_VERBOSE_PRINT( "NET: StartMatch\n" ); + + if ( net_headlessServer.GetBool() ) { + StartLoading(); // This is so we can force start matches on headless servers to test performance using bots + return; + } + + if ( localState != STATE_GAME_LOBBY_HOST ) { + idLib::Warning( "idSessionLocal::StartMatch called when not hosting game lobby" ); + return; + } + + assert( !GetGameStateLobby().IsLobbyActive() ); + + // make absolutely sure we only call StartMatch once per migrate + GetGameLobby().migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = true; + + // Clear snap ack queue between games + GetGameLobby().snapDeltaAckQueue.Clear(); + + extern idCVar net_bw_challenge_enable; + if ( session->GetTitleStorageBool( "net_bw_challenge_enable", net_bw_challenge_enable.GetBool() ) && GetGameLobby().HasActivePeers() ) { + GetGameLobby().bandwidthChallengeFinished = false; + StartOrContinueBandwidthChallenge( false ); + } + + if ( GetGameLobby().BandwidthTestStarted() ) { + // Put session in busy state + NET_VERBOSE_PRINT( "NET: StartMatch -> Start Bandwidth Challenge\n" ); + SetState( STATE_BUSY ); + } else { + // Start loading + StartLoading(); + } +} + +/* +======================== +idSessionLocal::GetBackState +======================== +*/ +idSessionLocal::sessionState_t idSessionLocal::GetBackState() { + sessionState_t currentState = GetState(); + + const bool isInGameLobby = currentState == GAME_LOBBY; + const bool isInPartyLobby = currentState == PARTY_LOBBY; + const bool isInGame = currentState == INGAME || currentState == LOADING; // Counting loading as ingame as far as what back state to go to + + if ( isInGame ) { + return GAME_LOBBY; // If in the game, go back to game lobby + } + + if ( !isInPartyLobby && isInGameLobby && ShouldHavePartyLobby() ) { + return PARTY_LOBBY; // If in the game lobby, and we should have a party lobby, and we are the host, go back to party lobby + } + + if ( currentState != IDLE ) { + return IDLE; // From here, go to idle if we aren't there yet + } + + return PRESS_START; // Otherwise, go back to press start +} + +/* +======================== +idSessionLocal::Cancel +======================== +*/ +void idSessionLocal::Cancel() { + NET_VERBOSE_PRINT( "NET: Cancel\n" ); + + if ( localState == STATE_PRESS_START ) { + return; // We're as far back as we can go + } + + ClearVoiceGroups(); // this is here as a catch-all + + // See what state we need to go to + switch ( GetBackState() ) { + case GAME_LOBBY: + EndMatch(); // End current match to go to game lobby + break; + + case PARTY_LOBBY: + if ( GetPartyLobby().IsHost() ) { + if ( sessionOptions & OPTION_LEAVE_WITH_PARTY ) { + // NOTE - This will send a message on the team lobby channel, + // so it won't be affected by the fact that we're shutting down the game lobby + GetPartyLobby().NotifyPartyOfLeavingGameLobby(); + } else { + // Host wants to be alone, disconnect all peers from the party + GetPartyLobby().DisconnectAllPeers(); + } + + // End the game lobby, and go back to party lobby as host + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + SetState( STATE_PARTY_LOBBY_HOST ); + + // Always remove this flag. SendGoodbye uses this to determine if we should send a "leave with party" + // and we don't want this flag hanging around, and causing false positives when it's called in the future. + // Make them set this each time. + sessionOptions &= ~OPTION_LEAVE_WITH_PARTY; + } else { + // If we aren't the host of a party and we want to go back to one, we need to create a party now + CreatePartyLobby( GetPartyLobby().parms ); + } + break; + + case IDLE: + // Go back to main menu + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + GetPartyLobby().Shutdown(); + SetState( STATE_IDLE ); + break; + + case PRESS_START: + // Go back to press start/main + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + GetPartyLobby().Shutdown(); + SetState( STATE_PRESS_START ); + break; + } + + // Validate the current lobby immediately + ValidateLobbies(); +} + +/* +======================== +idSessionLocal::MoveToPressStart +======================== +*/ +void idSessionLocal::MoveToPressStart() { + if ( localState != STATE_PRESS_START ) { + assert( signInManager != NULL ); + signInManager->RemoveAllLocalUsers(); + hasShownVoiceRestrictionDialog = false; + MoveToMainMenu(); + session->FinishDisconnect(); + SetState( STATE_PRESS_START ); + } +} + +/* +======================== +idSessionLocal::ShouldShowMigratingDialog +======================== +*/ +bool idSessionLocal::ShouldShowMigratingDialog() const { + const idLobby * activeLobby = GetActivePlatformLobby(); + + if ( activeLobby == NULL ) { + return false; + } + + return activeLobby->ShouldShowMigratingDialog(); +} + +/* +======================== +idSessionLocal::IsCurrentLobbyMigrating +======================== +*/ +bool idSessionLocal::IsCurrentLobbyMigrating() const { + const idLobby * activeLobby = GetActivePlatformLobby(); + + if ( activeLobby == NULL ) { + return false; + } + + return activeLobby->IsMigrating(); +} + +/* +======================== +idSessionLocal::IsLosingConnectionToHost +======================== +*/ +bool idSessionLocal::IsLosingConnectionToHost() const { + return GetActingGameStateLobby().IsLosingConnectionToHost(); +} + +/* +======================== +idSessionLocal::WasMigrationGame +returns true if we are hosting a migrated game and we had valid migration data +======================== +*/ +bool idSessionLocal::WasMigrationGame() const { + return GetGameLobby().IsMigratedStatsGame(); +} + +/* +======================== +idSessionLocal::ShouldRelaunchMigrationGame +returns true if we are hosting a migrated game and we had valid migration data +======================== +*/ +bool idSessionLocal::ShouldRelaunchMigrationGame() const { + return GetGameLobby().ShouldRelaunchMigrationGame() && !IsCurrentLobbyMigrating(); +} + +/* +======================== +idSessionLocal::GetMigrationGameData +======================== +*/ +bool idSessionLocal::GetMigrationGameData( idBitMsg & msg, bool reading ) { + return GetGameLobby().GetMigrationGameData( msg, reading ); +} + +/* +======================== +idSessionLocal::GetMigrationGameDataUser +======================== +*/ +bool idSessionLocal::GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) { + if ( GetGameStateLobby().IsHost() ) { + return false; + } + + return GetGameLobby().GetMigrationGameDataUser( lobbyUserID, msg, reading ); +} + + +/* +======================== +idSessionLocal::GetMatchParamUpdate +======================== +*/ +bool idSessionLocal::GetMatchParamUpdate( int &peer, int &msg ){ + if ( storedPeer != -1 && storedMsgType != -1 ) { + peer = storedPeer; + msg = storedMsgType; + storedPeer = -1; + storedMsgType = -1; + return true; + } + return false; +} + + + +/* +======================== +idSessionLocal::UpdatePartyParms + +Updates the party parameters when in a party lobby OR a game lobby in order to keep them always in sync. +======================== +*/ +void idSessionLocal::UpdatePartyParms( const idMatchParameters & p ) { + if ( ( GetState() != PARTY_LOBBY && GetState() != GAME_LOBBY ) || !GetPartyLobby().IsHost() ) { + return; + } + + // NET_VERBOSE_PRINT( "NET: UpdatePartyParms\n" ); + + GetPartyLobby().UpdateMatchParms( p ); +} + +/* +======================== +idSessionLocal::UpdateMatchParms +======================== +*/ +void idSessionLocal::UpdateMatchParms( const idMatchParameters & p ) { + if ( GetState() != GAME_LOBBY || !GetGameLobby().IsHost() ) { + return; + } + + NET_VERBOSE_PRINT( "NET: UpdateMatchParms\n" ); + + GetGameLobby().UpdateMatchParms( p ); +} + +/* +======================== +idSessionLocal::StartSessions +======================== +*/ +void idSessionLocal::StartSessions() { + if ( GetPartyLobby().lobbyBackend != NULL ) { + GetPartyLobby().lobbyBackend->StartSession(); + } + + if ( GetGameLobby().lobbyBackend != NULL ) { + GetGameLobby().lobbyBackend->StartSession(); + } + + SetLobbiesAreJoinable( false ); +} + +/* +======================== +idSessionLocal::EndSessions +======================== +*/ +void idSessionLocal::EndSessions() { + if ( GetPartyLobby().lobbyBackend != NULL ) { + GetPartyLobby().lobbyBackend->EndSession(); + } + + if ( GetGameLobby().lobbyBackend != NULL ) { + GetGameLobby().lobbyBackend->EndSession(); + } + + SetLobbiesAreJoinable( true ); +} + +/* +======================== +idSessionLocal::SetLobbiesAreJoinable +======================== +*/ +void idSessionLocal::SetLobbiesAreJoinable( bool joinable ) { + // NOTE - We don't manipulate the joinable state when we are supporting join in progress + // Lobbies will naturally be non searchable when there are no free slots + if ( GetPartyLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetPartyLobby().parms.matchFlags ) ) { + NET_VERBOSE_PRINT( "Party lobbyBackend SetIsJoinable: %d\n", joinable ); + GetPartyLobby().lobbyBackend->SetIsJoinable( joinable ); + } + + if ( GetGameLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetGameLobby().parms.matchFlags ) ) { + GetGameLobby().lobbyBackend->SetIsJoinable( joinable ); + NET_VERBOSE_PRINT( "Game lobbyBackend SetIsJoinable: %d\n", joinable ); + + } +} + +/* +======================== +idSessionLocal::MoveToMainMenu +======================== +*/ +void idSessionLocal::MoveToMainMenu() { + GetPartyLobby().Shutdown(); + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + SetState( STATE_IDLE ); +} + +/* +======================== +idSessionLocal::HandleVoiceRestrictionDialog +======================== +*/ +void idSessionLocal::HandleVoiceRestrictionDialog() { + // don't bother complaining about voice restrictions when in a splitscreen lobby + if ( MatchTypeIsLocal( GetActivePlatformLobby()->parms.matchFlags ) ) { + return; + } + + // Pop a dialog up the first time we are in a lobby and have voice chat restrictions due to account privileges + if ( voiceChat != NULL && voiceChat->IsRestrictedByPrivleges() && !hasShownVoiceRestrictionDialog ) { + common->Dialog().AddDialog( GDM_VOICE_RESTRICTED, DIALOG_ACCEPT, NULL, NULL, false ); + hasShownVoiceRestrictionDialog = true; + } +} + +/* +======================== +idSessionLocal::WaitOnLobbyCreate + +Called from State_Create_And_Move_To_Party_Lobby and State_Create_And_Move_To_Game_Lobby and State_Create_And_Move_To_Game_State_Lobby. +This function will create the lobby, then wait for it to either succeed or fail. +======================== +*/ +bool idSessionLocal::WaitOnLobbyCreate( idLobby & lobby ) { + + assert( localState == STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY || localState == STATE_CREATE_AND_MOVE_TO_GAME_LOBBY || localState == STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY ); + assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_NONE ); + + if ( lobby.GetState() == idLobby::STATE_FAILED ) { + NET_VERBOSE_PRINT( "NET: idSessionLocal::WaitOnLobbyCreate lobby.GetState() == idLobby::STATE_FAILED (%s)\n", lobby.GetLobbyName() ); + // If we failed to create a lobby, assume connection to backend service was lost + MoveToMainMenu(); + common->Dialog().ClearDialogs( true ); + common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, true, "", 0, true ); + return false; + } + + if ( DetectDisconnectFromService( true ) ) { + return false; + } + + if ( lobby.GetState() != idLobby::STATE_IDLE ) { + return false; // Valid but busy + } + + NET_VERBOSE_PRINT( "NET: idSessionLocal::WaitOnLobbyCreate SUCCESS (%s)\n", lobby.GetLobbyName() ); + + return true; +} + +/* +======================== +idSessionLocal::DetectDisconnectFromService +Called from CreateMatch/CreatePartyLobby/FindOrCreateMatch state machines +======================== +*/ +bool idSessionLocal::DetectDisconnectFromService( bool cancelAndShowMsg ) { + const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 ); + + // If we are taking too long, cancel the connection + if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) { + if ( Sys_Milliseconds() - connectTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) { + NET_VERBOSE_PRINT( "NET: idSessionLocal::DetectDisconnectFromService timed out\n" ); + if ( cancelAndShowMsg ) { + MoveToMainMenu(); + common->Dialog().ClearDialogs( true ); + common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); + } + + return true; + } + } + + return false; +} + +/* +======================== +idSessionLocal::HandleConnectionFailed +Called anytime a connection fails, and does the right thing. +======================== +*/ +void idSessionLocal::HandleConnectionFailed( idLobby & lobby, bool wasFull ) { + assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); + assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT ); + bool canPlayOnline = true; + + // Check for online status (this is only a problem on the PS3 at the moment. The 360 LIVE system handles this for us + if ( GetSignInManager().GetMasterLocalUser() != NULL ) { + canPlayOnline = GetSignInManager().GetMasterLocalUser()->CanPlayOnline(); + } + + if ( connectType == CONNECT_FIND_OR_CREATE ) { + // Clear the "Lobby was Full" dialog in case it's up + // We only want to see this msg when doing a direct connect (CONNECT_DIRECT) + common->Dialog().ClearDialog( GDM_LOBBY_FULL ); + + assert( localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); + assert( lobby.lobbyType == idLobby::TYPE_GAME ); + if ( !lobby.ConnectToNextSearchResult() ) { + CreateMatch( GetGameLobby().parms ); // Assume any time we are connecting to a game lobby, it is from a FindOrCreateMatch call, so create a match + } + } else if ( connectType == CONNECT_DIRECT ) { + if ( localState == STATE_CONNECT_AND_MOVE_TO_GAME && GetPartyLobby().IsPeer() ) { + int flags = GetPartyLobby().parms.matchFlags; + + if ( MatchTypeIsOnline( flags ) && ( flags & MATCH_REQUIRE_PARTY_LOBBY ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) { + // We get here when our party host told us to connect to a game, but the game didn't exist. + // Just drop back to the party lobby and wait for further orders. + SetState( STATE_PARTY_LOBBY_PEER ); + return; + } + } + + if ( wasFull ) { + common->Dialog().AddDialog( GDM_LOBBY_FULL, DIALOG_ACCEPT, NULL, NULL, false ); + } else if ( !canPlayOnline ) { + common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT, NULL, NULL, false ); + } else { + // TEMP HACK: We detect the steam lobby is full in idLobbyBackendWin, and then STATE_FAILED, which brings us here. Need to find a way to notify + // session local that the game was full so we don't do this check here + // eeubanks: Pollard, how do you think we should handle this? + if ( !common->Dialog().HasDialogMsg( GDM_LOBBY_FULL, NULL ) ) { + common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false ); + } + } + MoveToMainMenu(); + } else { + // Shouldn't be possible, but just in case + MoveToMainMenu(); + } +} + +/* +======================== +idSessionLocal::HandleConnectAndMoveToLobby +Called from State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game +======================== +*/ +bool idSessionLocal::HandleConnectAndMoveToLobby( idLobby & lobby ) { + assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); + assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT ); + + if ( lobby.GetState() == idLobby::STATE_FAILED ) { + // If we get here, we were trying to connect to a lobby (from state State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game) + HandleConnectionFailed( lobby, false ); + return true; + } + + if ( lobby.GetState() != idLobby::STATE_IDLE ) { + return HandlePackets(); // Valid but busy + } + + assert( !GetPartyLobby().waitForPartyOk ); + + // + // Past this point, we've connected to the lobby + // + + // If we are connecting to a game lobby, see if we need to keep waiting as either a host or peer while we're confirming all party members made it + if ( lobby.lobbyType == idLobby::TYPE_GAME ) { + if ( GetPartyLobby().IsHost() ) { + // As a host, wait until all party members make it + assert( !GetGameLobby().waitForPartyOk ); + + const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000; + + if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) { + // Took too long, move to next result, or create a game instead + HandleConnectionFailed( lobby, false ); + return true; + } + + int numUsersIn = 0; + + for ( int i = 0; i < GetPartyLobby().GetNumLobbyUsers(); i++ ) { + + if ( net_testPartyMemberConnectFail.GetInteger() == i ) { + continue; + } + + bool foundUser = false; + + lobbyUser_t * partyUser = GetPartyLobby().GetLobbyUser( i ); + + for ( int j = 0; j < GetGameLobby().GetNumLobbyUsers(); j++ ) { + lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( j ); + + if ( GetGameLobby().IsSessionUserLocal( gameUser ) || gameUser->address.Compare( partyUser->address, true ) ) { + numUsersIn++; + foundUser = true; + break; + } + } + + assert( !GetPartyLobby().IsSessionUserIndexLocal( i ) || foundUser ); + } + + if ( numUsersIn != GetPartyLobby().GetNumLobbyUsers() ) { + return HandlePackets(); // All users not in, keep waiting until all user make it, or we time out + } + + NET_VERBOSE_PRINT( "NET: All party members made it into the game lobby.\n" ); + + // Let all the party members know everyone made it, and it's ok to stay at this server + for ( int i = 0; i < GetPartyLobby().peers.Num(); i++ ) { + if ( GetPartyLobby().peers[ i ].IsConnected() ) { + GetPartyLobby().QueueReliableMessage( i, idLobby::RELIABLE_PARTY_CONNECT_OK ); + } + } + } else { + if ( !verify ( lobby.host != -1 ) ) { + MoveToMainMenu(); + connectType = CONNECT_NONE; + return false; + } + + // As a peer, wait for server to tell us everyone made it + if ( GetGameLobby().waitForPartyOk ) { + const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000; + + if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) { + GetGameLobby().waitForPartyOk = false; // Just connect to this game lobby if we haven't heard from the party host for the entire timeout duration + } + } + + if ( GetGameLobby().waitForPartyOk ) { + return HandlePackets(); // Waiting on party host to tell us everyone made it + } + } + } + + // Success + switch ( lobby.lobbyType ) { + case idLobby::TYPE_PARTY: + SetState( STATE_PARTY_LOBBY_PEER ); + break; + case idLobby::TYPE_GAME: + SetState( STATE_GAME_LOBBY_PEER ); + break; + case idLobby::TYPE_GAME_STATE: + waitingOnGameStateMembersToJoinTime = Sys_Milliseconds(); + // As a host of the game lobby, it's our duty to notify our members to also join this game state lobby + GetGameLobby().SendMembersToLobby( GetGameStateLobby(), false ); + SetState( STATE_GAME_STATE_LOBBY_PEER ); + break; + } + + connectType = CONNECT_NONE; + + return false; +} + +/* +======================== +idSessionLocal::State_Create_And_Move_To_Party_Lobby +======================== +*/ +bool idSessionLocal::State_Create_And_Move_To_Party_Lobby() { + if ( WaitOnLobbyCreate( GetPartyLobby() ) ) { + + if ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) { + // If this party lobby was for a placeholder, continue on with either finding or creating a game lobby + if ( connectType == CONNECT_FIND_OR_CREATE ) { + FindOrCreateMatch( GetPartyLobby().parms ); + return true; + } else if ( connectType == CONNECT_NONE ) { + CreateMatch( GetPartyLobby().parms ); + return true; + } + } + + // Success + SetState( STATE_PARTY_LOBBY_HOST ); + + return true; + } + + return HandlePackets(); // Valid but busy +} + +/* +======================== +idSessionLocal::State_Create_And_Move_To_Game_Lobby +======================== +*/ +bool idSessionLocal::State_Create_And_Move_To_Game_Lobby() { + + if ( WaitOnLobbyCreate( GetGameLobby() ) ) { + // Success + SetState( STATE_GAME_LOBBY_HOST ); + + // Now that we've created our game lobby, send our own party users to it + // NOTE - We pass in false to wait on party members since we are the host, and we know they can connect to us + GetPartyLobby().SendMembersToLobby( GetGameLobby(), false ); + return true; + } + + return false; +} + +/* +======================== +idSessionLocal::State_Create_And_Move_To_Game_State_Lobby +======================== +*/ +bool idSessionLocal::State_Create_And_Move_To_Game_State_Lobby() { + + if ( WaitOnLobbyCreate( GetGameStateLobby() ) ) { + // Success + SetState( STATE_GAME_STATE_LOBBY_HOST ); + + // Now that we've created our game state lobby, send our own game users to it + // NOTE - We pass in false to wait on party members since we are the host, and we know they can connect to us + GetGameLobby().SendMembersToLobby( GetGameStateLobby(), false ); + + // If we are the host of a game lobby, we know we are not using dedicated servers, so we want to start the match immediately + // as soon as we detect all users have connected. + if ( GetGameLobby().IsHost() ) { + waitingOnGameStateMembersToJoinTime = Sys_Milliseconds(); + } + + return true; + } + + return false; +} + +/* +======================== +idSessionLocal::State_Find_Or_Create_Match +======================== +*/ +bool idSessionLocal::State_Find_Or_Create_Match() { + assert( connectType == CONNECT_FIND_OR_CREATE ); + + if ( GetGameLobby().GetState() == idLobby::STATE_FAILED ) { + // Failed to find any games. Create one instead (we're assuming this always gets called from FindOrCreateMatch + CreateMatch( GetGameLobby().parms ); + return true; + } + + if ( DetectDisconnectFromService( true ) ) { + return false; + } + + if ( GetGameLobby().GetState() != idLobby::STATE_IDLE ) { + return HandlePackets(); // Valid but busy + } + + // Done searching, connect to the first search result + if ( !GetGameLobby().ConnectToNextSearchResult() ) { + // Failed to find any games. Create one instead (we're assuming this always gets called from FindOrCreateMatch + CreateMatch( GetGameLobby().parms ); + return true; + } + + SetState( STATE_CONNECT_AND_MOVE_TO_GAME ); + + return true; +} + +/* +======================== +idSessionLocal::State_Connect_And_Move_To_Party +======================== +*/ +bool idSessionLocal::State_Connect_And_Move_To_Party() { + return HandleConnectAndMoveToLobby( GetPartyLobby() ); +} + +/* +======================== +idSessionLocal::State_Connect_And_Move_To_Game +======================== +*/ +bool idSessionLocal::State_Connect_And_Move_To_Game() { + return HandleConnectAndMoveToLobby( GetGameLobby() ); +} + +/* +======================== +idSessionLocal::State_Connect_And_Move_To_Game_State +======================== +*/ +bool idSessionLocal::State_Connect_And_Move_To_Game_State() { + return HandleConnectAndMoveToLobby( GetGameStateLobby() ); +} + +/* +======================== +idSessionLocal::State_InGame +======================== +*/ +bool idSessionLocal::State_InGame() { + return HandlePackets(); +} + +/* +======================== +idSessionLocal::State_Loading +======================== +*/ +bool idSessionLocal::State_Loading() { + + HandlePackets(); + + if ( !GetActingGameStateLobby().loaded ) { + return false; + } + + SetVoiceGroupsToTeams(); + + if ( GetActingGameStateLobby().IsHost() ) { + bool everyoneLoaded = true; + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + idLobby::peer_t & peer = GetActingGameStateLobby().peers[p]; + + if ( !peer.IsConnected() ) { + continue; // We don't care about peers that aren't connected as a game session + } + + if ( !peer.loaded ) { + everyoneLoaded = false; + continue; // Don't waste time sending resources to a peer who hasn't loaded the map yet + } + + if ( GetActingGameStateLobby().SendResources( p ) ) { + everyoneLoaded = false; + + // if client is taking a LONG time to load up - give them the boot: they're just holding up the lunch line. Useful for loose assets playtesting. + int time = Sys_Milliseconds(); + int maxLoadTime = net_maxLoadResourcesTimeInSeconds.GetInteger(); + if ( maxLoadTime > 0 && peer.startResourceLoadTime + SEC2MS( maxLoadTime ) < time ) { + NET_VERBOSERESOURCE_PRINT( "NET: dropping client %i - %s because they took too long to load resources.\n Check 'net_maxLoadResourcesTimeInSeconds' to adjust the time allowed.\n", p, GetPeerName( p ) ); + GetActingGameStateLobby().DisconnectPeerFromSession( p ); + continue; + } + } + } + if ( !everyoneLoaded ) { + return false; + } + } else { + // not sure how we got there, but we won't be receiving anything that could get us out of this state anymore + // possible step towards fixing the join stalling/disconnect problems + if ( GetActingGameStateLobby().peers.Num() == 0 ) { + NET_VERBOSE_PRINT( "NET: no peers in idSessionLocal::State_Loading - giving up\n" ); + MoveToMainMenu(); + } + // need at least a peer with a real connection + bool haveOneGoodPeer = false; + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + if ( GetActingGameStateLobby().peers[p].IsConnected() ) { + haveOneGoodPeer = true; + break; + } + } + if ( !haveOneGoodPeer ) { + NET_VERBOSE_PRINT( "NET: no good peers in idSessionLocal::State_Loading - giving up\n" ); + MoveToMainMenu(); + } + + return false; + } + + GetActingGameStateLobby().ResetBandwidthStats(); + + // if we got here then we're the host and everyone indicated map load finished + NET_VERBOSE_PRINT( "NET: (loading) Starting Game\n" ); + SetState( STATE_INGAME ); // NOTE - Only the host is in-game at this point, all peers will start becoming in-game when they receive their first full snap + return true; +} + +/* +======================== +idSessionLocal::State_Busy +======================== +*/ +bool idSessionLocal::State_Busy() { + idLobby * activeLobby = GetActivePlatformLobby(); + if ( activeLobby == NULL ) { + idLib::Warning("No active session lobby when idSessionLocal::State_Busy called"); + return false; + } + + if ( activeLobby->bandwidthChallengeFinished ) { + // Start loading + NET_VERBOSE_PRINT( "NET: Bandwidth test finished - Start loading\n" ); + StartLoading(); + } + + return HandlePackets(); +} + +/* +======================== +idSessionLocal::VerifySnapshotInitialState +======================== +*/ +void idSessionLocal::VerifySnapshotInitialState() { + // Verify that snapshot state is reset + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + if ( !GetActingGameStateLobby().peers[p].IsConnected() ) { + assert( GetActingGameStateLobby().peers[p].snapProc == NULL ); + continue; + } + + assert( GetActingGameStateLobby().peers[p].snapProc != NULL ); + + if ( !verify( GetActingGameStateLobby().peers[p].needToSubmitPendingSnap == false ) ) { + idLib::Error( "Invalid needToSubmitPendingSnap state\n" ); + } + if ( !verify( GetActingGameStateLobby().peers[p].snapProc->HasPendingSnap() == false ) ) { + idLib::Error( "Invalid HasPendingSnap state\n" ); + } + if ( !verify( GetActingGameStateLobby().peers[p].snapProc->GetSnapSequence() == idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) ) { + idLib::Error( "Invalid INITIAL_SNAP_SEQUENCE state %d for peer %d \n", GetActingGameStateLobby().peers[p].snapProc->GetSnapSequence(), p ); + } + if ( !verify( GetActingGameStateLobby().peers[p].snapProc->GetBaseSequence() == -1 ) ) { + idLib::Error( "Invalid GetBaseSequence state\n" ); + } + } +} + +/* +======================== +idSessionLocal::State_Party_Lobby_Host +======================== +*/ +bool idSessionLocal::State_Party_Lobby_Host() { + HandleVoiceRestrictionDialog(); + return HandlePackets(); +} + +/* +======================== +idSessionLocal::State_Game_Lobby_Host +======================== +*/ +bool idSessionLocal::State_Game_Lobby_Host() { + HandleVoiceRestrictionDialog(); + return HandlePackets(); +} + +/* +======================== +idSessionLocal::State_Game_State_Lobby_Host +======================== +*/ +bool idSessionLocal::State_Game_State_Lobby_Host() { + HandleVoiceRestrictionDialog(); + + if ( waitingOnGameStateMembersToLeaveTime != 0 ) { + + const int MAX_LEAVE_WAIT_TIME_IN_SECONDS = 5; + + const bool forceDisconnectMembers = ( Sys_Milliseconds() - waitingOnGameStateMembersToLeaveTime ) > MAX_LEAVE_WAIT_TIME_IN_SECONDS * 1000; + + // Check to see if all peers have finally left + if ( GetGameStateLobby().GetNumConnectedPeers() == 0 || forceDisconnectMembers ) { + + // + // All peers left, we can stop waiting + // + + waitingOnGameStateMembersToLeaveTime = 0; + + assert( !GetGameLobby().IsPeer() ); + + if ( GetGameLobby().IsHost() ) { + // If we aren't a dedicated game state host, then drop back to the game lobby as host + GetGameStateLobby().Shutdown(); + SetState( STATE_GAME_LOBBY_HOST ); + } else { + // A dedicated game state host will remain in State_Game_State_Lobby_Host mode while waiting for another set of users to join + // DEDICATED_SERVER_FIXME: Notify master server we can server another game now + GetGameStateLobby().DisconnectAllPeers(); + } + } + } else { + // When all the players from the game lobby are in the game state lobby, StartLoading + if ( GetGameLobby().IsHost() ) { + if ( GetGameStateLobby().GetNumLobbyUsers() == GetGameLobby().GetNumLobbyUsers() ) { + waitingOnGameStateMembersToJoinTime = 0; + StartLoading(); + } + } else { + // The dedicated server host relies on the game host to say when all users are in + if ( GetGameStateLobby().startLoadingFromHost ) { + GetGameStateLobby().startLoadingFromHost = false; + StartLoading(); + } + } + } + + return HandlePackets(); +} + +/* +======================== +idSessionLocal::State_Party_Lobby_Peer +======================== +*/ +bool idSessionLocal::State_Party_Lobby_Peer() { + HandleVoiceRestrictionDialog(); + return HandlePackets(); +} + +/* +======================== +idSessionLocal::State_Game_Lobby_Peer +======================== +*/ +bool idSessionLocal::State_Game_Lobby_Peer() { + HandleVoiceRestrictionDialog(); + bool saving = false; + idPlayerProfile * profile = GetProfileFromMasterLocalUser(); + if ( profile != NULL && ( profile->GetState() == idPlayerProfile::SAVING || profile->GetRequestedState() == idPlayerProfile::SAVE_REQUESTED ) ) { + saving = true; + } + + if ( GetActingGameStateLobby().startLoadingFromHost && !saving ) { + common->Dialog().ClearDialog( GDM_HOST_RETURNED_TO_LOBBY ); + common->Dialog().ClearDialog( GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED ); + + VerifySnapshotInitialState(); + + // Set loading flag back to false + GetActingGameStateLobby().startLoadingFromHost = false; + + // Set state to loading + SetState( STATE_LOADING ); + + loadingID++; + + return true; + } + + return HandlePackets(); +} + +/* +======================== +idSessionLocal::State_Game_State_Lobby_Peer +======================== +*/ +bool idSessionLocal::State_Game_State_Lobby_Peer() { + // We are in charge of telling the dedicated host that all our members are in + if ( GetGameLobby().IsHost() && waitingOnGameStateMembersToJoinTime != 0 ) { + int foundMembers = 0; + + for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); i++ ) { + if ( GetGameStateLobby().GetLobbyUserByID( GetGameLobby().GetLobbyUser( i )->lobbyUserID, true ) != NULL ) { + foundMembers++; + } + } + + // Give all of our game members 10 seconds to join, otherwise start without them + const int MAX_JOIN_WAIT_TIME_IN_SECONDS = 10; + + const bool forceStart = ( Sys_Milliseconds() - waitingOnGameStateMembersToJoinTime ) > MAX_JOIN_WAIT_TIME_IN_SECONDS * 1000; + + if ( foundMembers == GetGameLobby().GetNumLobbyUsers() || forceStart ) { + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + + idBitMsg msg( buffer, sizeof( buffer ) ); + + // Write match paramaters to the game state host, and tell him to start + GetGameLobby().parms.Write( msg ); + + // Tell the game state lobby host we are ready + GetGameStateLobby().QueueReliableMessage( GetGameStateLobby().host, idLobby::RELIABLE_START_MATCH_GAME_LOBBY_HOST, msg.GetReadData(), msg.GetSize() ); + + waitingOnGameStateMembersToJoinTime = 0; + } + } + + return State_Game_Lobby_Peer(); +} + +/* +======================== +idSessionLocal::~idSession +======================== +*/ +idSession::~idSession() { + delete signInManager; + signInManager = NULL; + delete saveGameManager; + saveGameManager = NULL; + delete dedicatedServerSearch; + dedicatedServerSearch = NULL; +} + +idCVar net_verbose( "net_verbose", "0", CVAR_BOOL, "Print a bunch of message about the network session" ); +idCVar net_verboseResource( "net_verboseResource", "0", CVAR_BOOL, "Prints a bunch of message about network resources" ); +idCVar net_verboseReliable( "net_verboseReliable", "0", CVAR_BOOL, "Prints the more spammy messages about reliable network msgs" ); +idCVar si_splitscreen( "si_splitscreen", "0", CVAR_INTEGER, "force splitscreen" ); + +idCVar net_forceLatency( "net_forceLatency", "0", CVAR_INTEGER, "Simulate network latency (milliseconds round trip time - applied equally on the receive and on the send)" ); +idCVar net_forceDrop( "net_forceDrop", "0", CVAR_INTEGER, "Percentage chance of simulated network packet loss" ); +idCVar net_forceUpstream( "net_forceUpstream", "0", CVAR_FLOAT, "Force a maximum upstream in kB/s (256kbps <-> 32kB/s)" ); // I would much rather deal in kbps but most of the code is written in bytes .. +idCVar net_forceUpstreamQueue( "net_forceUpstreamQueue", "64", CVAR_INTEGER, "How much data is queued when enforcing upstream (in kB)" ); +idCVar net_verboseSimulatedTraffic( "net_verboseSimulatedTraffic", "0", CVAR_BOOL, "Print some stats about simulated traffic (net_force* cvars)" ); + +/* +======================== +idSessionLocal::Initialize +======================== +*/ +void idSessionLocal::Initialize() { +} + +/* +======================== +idSessionLocal::Shutdown +======================== +*/ +void idSessionLocal::Shutdown() { +} + +/* +======================== +idSession interface semi-common between platforms (#ifdef's in sys_session_local.cpp) +======================== +*/ + +idCVar com_deviceZeroOverride( "com_deviceZeroOverride", "-1", CVAR_INTEGER, "change input routing for device 0 to poll a different device" ); +idCVar mp_bot_input_override( "mp_bot_input_override", "-1", CVAR_INTEGER, "Override local input routing for bot control" ); + +/* +======================== +idSessionLocal::GetInputRouting +This function sets up inputRouting to be a mapping from inputDevice index to session user index. +======================== +*/ +int idSessionLocal::GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ) { + + int numLocalUsers = 0; + for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { + inputRouting[i] = -1; + } + + for ( int i = 0; i < GetActingGameStateLobby().GetNumLobbyUsers(); i++ ) { + if ( GetActingGameStateLobby().IsSessionUserIndexLocal( i ) ) { + + // Find the local user that this session user maps to + const idLocalUser * localUser = GetActingGameStateLobby().GetLocalUserFromLobbyUserIndex( i ); + + if ( localUser != NULL ) { + int localDevice = localUser->GetInputDevice(); + if ( localDevice == 0 && com_deviceZeroOverride.GetInteger() > 0 ) { + localDevice = com_deviceZeroOverride.GetInteger(); + } + assert( localDevice < MAX_INPUT_DEVICES ); + // Route the input device that this local user is mapped to + assert( inputRouting[localDevice] == -1 ); // Make sure to only initialize each entry once + inputRouting[localDevice] = i; + + if ( mp_bot_input_override.GetInteger() >= 0 ) { + inputRouting[localDevice] = mp_bot_input_override.GetInteger(); + } + + numLocalUsers++; + } + } + } + + // For testing swapping controllers + if ( si_splitscreen.GetInteger() == 2 && numLocalUsers == 2 ) { + SwapValues( inputRouting[0], inputRouting[1] ); + } + + return numLocalUsers; +} + +/* +======================== +idSessionLocal::EndMatch +EndMatch is meant for the host to cleanly end a match and return to the lobby page +======================== +*/ +void idSessionLocal::EndMatch( bool premature /*=false*/ ) { + if ( verify( GetActingGameStateLobby().IsHost() ) ) { + // Host quits back to game lobby, and will notify peers internally to do the same + EndMatchInternal( premature ); + } +} + +/* +======================== +idSessionLocal::EndMatch +this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time +======================== +*/ +void idSessionLocal::MatchFinished( ) { + if ( verify( GetActingGameStateLobby().IsHost() ) ) { + // host is putting up end game stats make sure other peers know and clear migration data + MatchFinishedInternal(); + } +} + +/* +======================== +idSessionLocal::QuitMatch +QuitMatch is considered a premature ending of a match, and does the right thing depending on whether the host or peer is quitting +======================== +*/ +void idSessionLocal::QuitMatch() { + if ( GetActingGameStateLobby().IsHost() && !MatchTypeIsRanked( GetActingGameStateLobby().parms.matchFlags ) ) { + EndMatch( true ); // When host quits private match, takes members back to game lobby + } else { + // Quitting a public match (or not being a host) before it ends takes you to an empty party lobby + CreatePartyLobby( GetActingGameStateLobby().parms ); + } +} + +/* +======================== +idSessionLocal::QuitMatchToTitle +QuitMatchToTitle will forcefully quit the match and return to the title screen. +======================== +*/ +void idSessionLocal::QuitMatchToTitle() { + MoveToMainMenu(); +} + +/* +======================== +idSessionLocal::ClearMigrationState +======================== +*/ +void idSessionLocal::ClearMigrationState() { + // We are ending the match without migration, so clear that state + GetPartyLobby().ResetAllMigrationState(); + GetGameLobby().ResetAllMigrationState(); +} + +/* +======================== +idSessionLocal::EndMatchInternal +======================== +*/ +void idSessionLocal::EndMatchInternal( bool premature/*=false*/ ) { + assert( GetGameStateLobby().IsLobbyActive() == net_useGameStateLobby.GetBool() ); + + ClearVoiceGroups(); + + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + // If we are the host, increment the session ID. The client will use a rolling check to catch it + if ( GetActingGameStateLobby().IsHost() ) { + if ( GetActingGameStateLobby().peers[p].IsConnected() ) { + if ( GetActingGameStateLobby().peers[p].packetProc != NULL ) { + GetActingGameStateLobby().peers[p].packetProc->VerifyEmptyReliableQueue( idLobby::RELIABLE_GAME_DATA, idLobby::RELIABLE_DUMMY_MSG ); + } + GetActingGameStateLobby().peers[p].sessionID = GetActingGameStateLobby().IncrementSessionID( GetActingGameStateLobby().peers[p].sessionID ); + } + } + GetActingGameStateLobby().peers[p].ResetMatchData(); + } + + GetActingGameStateLobby().snapDeltaAckQueue.Clear(); + + GetActingGameStateLobby().loaded = false; + + gameLobbyWasCoalesced = false; // Reset this back to false. We use this so the lobby code doesn't randomly choose a map when we coalesce + numFullSnapsReceived = 0; + + ClearMigrationState(); + + if ( GetActingGameStateLobby().IsLobbyActive() && ( GetActingGameStateLobby().GetMatchParms().matchFlags & MATCH_REQUIRE_PARTY_LOBBY ) ) { + // All peers need to remove disconnected users to stay in sync + GetActingGameStateLobby().CompactDisconnectedUsers(); + + // Go back to the game lobby + if ( GetActingGameStateLobby().IsHost() ) { + // We want the game state host to go back to STATE_GAME_STATE_LOBBY_HOST, so he can wait on all his game state peers to leave + SetState( GetGameStateLobby().IsHost() ? STATE_GAME_STATE_LOBBY_HOST : STATE_GAME_LOBBY_HOST ); // We want the dedicated host to go back to STATE_GAME_STATE_LOBBY_HOST + } else { + SetState( STATE_GAME_LOBBY_PEER ); + } + } else { + SetState( STATE_IDLE ); + } + + if ( GetActingGameStateLobby().IsHost() ) { + // Send a reliable msg to all peers to also "EndMatch" + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + GetActingGameStateLobby().QueueReliableMessage( p, premature ? idLobby::RELIABLE_ENDMATCH_PREMATURE : idLobby::RELIABLE_ENDMATCH ); + } + } else if ( premature ) { + // Notify client that host left early and thats why we are back in the lobby + const bool stats = MatchTypeHasStats( GetActingGameStateLobby().GetMatchParms().matchFlags ) && ( GetFlushedStats() == false ); + common->Dialog().AddDialog( stats ? GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED : GDM_HOST_RETURNED_TO_LOBBY, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); + } + + if ( GetGameStateLobby().IsLobbyActive() ) { + if ( GetGameStateLobby().IsHost() ) { + // As a game state host, keep the lobby around, so we can make sure we know when everyone leaves (which means they got the reliable msg to EndMatch) + waitingOnGameStateMembersToLeaveTime = Sys_Milliseconds(); + } else if ( GetGameStateLobby().IsPeer() ) { + // Game state lobby peers should disconnect now + GetGameStateLobby().Shutdown(); + } + } +} + +/* +======================== +idSessionLocal::MatchFinishedInternal +======================== +*/ +void idSessionLocal::MatchFinishedInternal() { + ClearMigrationState(); + + if ( GetActingGameStateLobby().IsHost() ) { + // Send a reliable msg to all peers to also "EndMatch" + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + GetActingGameStateLobby().QueueReliableMessage( p, idLobby::RELIABLE_MATCHFINISHED ); + } + } +} + +/* +======================== +idSessionLocal::EndMatchForMigration +======================== +*/ +void idSessionLocal::EndMatchForMigration() { + ClearVoiceGroups(); +} + +/* +======================== +idSessionLocal::ShouldHavePartyLobby +======================== +*/ +bool idSessionLocal::ShouldHavePartyLobby() { + if ( GetActivePlatformLobby() == NULL ) { + return false; + } + + idMatchParameters & parms = GetActivePlatformLobby()->parms; + + int flags = parms.matchFlags; + + // Don't we always have a party lobby if we're online? At least in Doom 3? + return MatchTypeIsOnline( flags ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ); +} + +/* +======================== +idSessionLocal::ValidateLobbies +Determines if any of the session instances need to become the host +======================== +*/ +void idSessionLocal::ValidateLobbies() { + if ( localState == STATE_PRESS_START || localState == STATE_IDLE ) { + // At press start or main menu, don't do anything + return; + } + + if ( GetActivePlatformLobby() == NULL ) { + // If we're in between lobbies, don't do anything yet (the state transitioning code will handle error cases) + return; + } + + // Validate lobbies that should be alive and active + if ( ShouldHavePartyLobby() && GetState() >= idSession::PARTY_LOBBY ) { + ValidateLobby( GetPartyLobby() ); + } + if ( GetState() >= idSession::GAME_LOBBY && !net_headlessServer.GetBool() ) { + ValidateLobby( GetGameLobby() ); + } +} + +/* +======================== +idSessionLocal::ValidateLobby +======================== +*/ +void idSessionLocal::ValidateLobby( idLobby & lobby ) { + if ( lobby.lobbyBackend == NULL || lobby.lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED || lobby.GetState() == idLobby::STATE_FAILED ) { + NET_VERBOSE_PRINT( "NET: ValidateLobby: FAILED (lobbyType = %i, state = %s)\n", lobby.lobbyType, stateToString[ localState ] ); + if ( lobby.failedReason == idLobby::FAILED_MIGRATION_CONNECT_FAILED || lobby.failedReason == idLobby::FAILED_CONNECT_FAILED ) { + MoveToMainMenu(); + common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false ); // The game session no longer exists + } else { + // If the lobbyBackend goes bad under our feet for no known reason, assume we lost connection to the back end service + MoveToMainMenu(); + common->Dialog().ClearDialogs( true ); + common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false ); // Lost connection to XBox LIVE + } + } +} + +/* +======================== +idSessionLocal::Pump +======================== +*/ +void idSessionLocal::Pump() { + SCOPED_PROFILE_EVENT( "Session::Pump" ); + + static int lastPumpTime = -1; + + const int time = Sys_Milliseconds(); + const int elapsedPumpSeconds = ( time - lastPumpTime ) / 1000; + + if ( lastPumpTime != -1 && elapsedPumpSeconds > 2 ) { + idLib::Warning( "idSessionLocal::Pump was not called for %i seconds", elapsedPumpSeconds ); + } + + lastPumpTime = time; + + if ( net_migrateHost.GetInteger() >= 0 ) { + if ( net_migrateHost.GetInteger() <= 2 ) { + if ( net_migrateHost.GetInteger() == 0 ) { + GetPartyLobby().PickNewHost( true, true ); + } else { + GetGameLobby().PickNewHost( true, true ); + } + } else { + GetPartyLobby().PickNewHost( true, true ); + GetGameLobby().PickNewHost( true, true ); + } + net_migrateHost.SetInteger( -1 ); + } + + PlatformPump(); + + if ( HasAchievementSystem() ) { + GetAchievementSystem().Pump(); + } + + // Send any voice packets if it's time + SendVoiceAudio(); + + bool shouldContinue = true; + + while ( shouldContinue ) { + // Each iteration, validate the session instances + ValidateLobbies(); + + // Pump state + shouldContinue = HandleState(); + + // Pump lobbies + PumpLobbies(); + } + + if ( GetPartyLobby().lobbyBackend != NULL ) { + // Make sure game properties aren't set on the lobbyBackend if we aren't in a game lobby. + // This is so we show up properly in search results in Play with Friends option + GetPartyLobby().lobbyBackend->SetInGame( GetGameLobby().IsLobbyActive() ); + + // Temp location + UpdateMasterUserHeadsetState(); + } + + // Do some last minute checks, make sure everything about the current state and lobbyBackend state is valid, otherwise, take action + ValidateLobbies(); + + GetActingGameStateLobby().UpdateSnaps(); + + idLobby * activeLobby = GetActivePlatformLobby(); + + // Pump pings for the active lobby + if ( activeLobby != NULL ) { + activeLobby->PumpPings(); + } + + // Pump packet processing for all lobbies + GetPartyLobby().PumpPackets(); + GetGameLobby().PumpPackets(); + GetGameStateLobby().PumpPackets(); + + int currentTime = Sys_Milliseconds(); + + const int SHOW_MIGRATING_INFO_IN_SECONDS = 3; // Show for at least this long once we start showing it + + if ( ShouldShowMigratingDialog() ) { + showMigratingInfoStartTime = currentTime; + } else if ( showMigratingInfoStartTime > 0 && ( ( currentTime - showMigratingInfoStartTime ) > SHOW_MIGRATING_INFO_IN_SECONDS * 1000 ) ) { + showMigratingInfoStartTime = 0; + } + + bool isShowingMigrate = common->Dialog().HasDialogMsg( GDM_MIGRATING, NULL ); + + if ( showMigratingInfoStartTime != 0 ) { + if ( !isShowingMigrate ) { + common->Dialog().AddDialog( GDM_MIGRATING, DIALOG_WAIT, NULL, NULL, false, "", 0, false, false, true ); + } + } else if ( isShowingMigrate ) { + common->Dialog().ClearDialog( GDM_MIGRATING ); + } + + // Update possible pending invite + UpdatePendingInvite(); + + // Check to see if we should coalesce the lobby + if ( nextGameCoalesceTime != 0 ) { + + if ( GetGameLobby().IsLobbyActive() && + GetGameLobby().IsHost() && + GetState() == idSession::GAME_LOBBY && + GetPartyLobby().GetNumLobbyUsers() <= 1 && + GetGameLobby().GetNumLobbyUsers() == 1 && + MatchTypeIsRanked( GetGameLobby().parms.matchFlags ) && + Sys_Milliseconds() > nextGameCoalesceTime ) { + + // If the player doesn't care about the mode or map, + // make sure the search is broadened. + idMatchParameters newGameParms = GetGameLobby().parms; + newGameParms.gameMap = GAME_MAP_RANDOM; + + // Assume that if the party lobby's mode is random, + // the player chose "Quick Match" and doesn't care about the mode. + // If the player chose "Find Match" and a specific mode, + // the party lobby mode will be set to non-random. + if ( GetPartyLobby().parms.gameMode == GAME_MODE_RANDOM ) { + newGameParms.gameMode = GAME_MODE_RANDOM; + } + + FindOrCreateMatch( newGameParms ); + + gameLobbyWasCoalesced = true; // Remember that this round was coalesced. We so this so main menu doesn't randomize the map, which looks odd + nextGameCoalesceTime = 0; + } + } +} + +/* +======================== +idSessionLocal::ProcessSnapAckQueue +======================== +*/ +void idSessionLocal::ProcessSnapAckQueue() { + if ( GetActingGameStateLobby().IsLobbyActive() ) { + GetActingGameStateLobby().ProcessSnapAckQueue(); + } +} + +/* +======================== +idSessionLocal::UpdatePendingInvite +======================== +*/ +void idSessionLocal::UpdatePendingInvite() { + if ( pendingInviteMode == PENDING_INVITE_NONE ) { + return; // No pending invite + } + + idLocalUser * masterLocalUser = signInManager->GetMasterLocalUser(); + + if ( masterLocalUser == NULL && signInManager->IsDeviceBeingRegistered( pendingInviteDevice ) ) { + idLib::Printf( "masterLocalUser == NULL\n" ); + return; // Waiting on master to sign in to continue with invite + } + + const bool wasFromInvite = pendingInviteMode == PENDING_INVITE_WAITING; // Remember if this was a real invite, or a self invitation (matters when lobby is invite only) + + // At this point, the invitee should be ready + pendingInviteMode = PENDING_INVITE_NONE; + + if ( masterLocalUser == NULL || masterLocalUser->GetInputDevice() != pendingInviteDevice || !masterLocalUser->IsOnline() ) { + idLib::Printf( "ignoring invite - master local user is not setup properly\n" ); + return; // If there is no master, if the invitee is not online, or different than the current master, then ignore invite + } + + // Clear any current dialogs, as we're going into a state which will be unstable for any current dialogs. + // Do we want to throw an assert if a dialog is currently up? + common->Dialog().ClearDialogs( true ); + + // Everything looks good, let's join the party + ConnectAndMoveToLobby( GetPartyLobby(), pendingInviteConnectInfo, wasFromInvite ); +} + +/* +======================== +idSessionLocal::HandleState +======================== +*/ +bool idSessionLocal::HandleState() { + // Handle individual lobby states + GetPartyLobby().Pump(); + GetGameLobby().Pump(); + GetGameStateLobby().Pump(); + + // Let IsHost be authoritative on the qualification of peer/host state types + if ( GetPartyLobby().IsHost() && localState == STATE_PARTY_LOBBY_PEER ) { + SetState( STATE_PARTY_LOBBY_HOST ); + } else if ( GetPartyLobby().IsPeer() && localState == STATE_PARTY_LOBBY_HOST ) { + SetState( STATE_PARTY_LOBBY_PEER ); + } + + // Let IsHost be authoritative on the qualification of peer/host state types + if ( GetGameLobby().IsHost() && localState == STATE_GAME_LOBBY_PEER ) { + SetState( STATE_GAME_LOBBY_HOST ); + } else if ( GetGameLobby().IsPeer() && localState == STATE_GAME_LOBBY_HOST ) { + SetState( STATE_GAME_LOBBY_PEER ); + } + + switch ( localState ) { + case STATE_PRESS_START: return false; + case STATE_IDLE: HandlePackets(); return false; // Call handle packets, since packets from old sessions could still be in flight, which need to be emptied + case STATE_PARTY_LOBBY_HOST: return State_Party_Lobby_Host(); + case STATE_PARTY_LOBBY_PEER: return State_Party_Lobby_Peer(); + case STATE_GAME_LOBBY_HOST: return State_Game_Lobby_Host(); + case STATE_GAME_LOBBY_PEER: return State_Game_Lobby_Peer(); + case STATE_GAME_STATE_LOBBY_HOST: return State_Game_State_Lobby_Host(); + case STATE_GAME_STATE_LOBBY_PEER: return State_Game_State_Lobby_Peer(); + case STATE_LOADING: return State_Loading(); + case STATE_INGAME: return State_InGame(); + case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return State_Create_And_Move_To_Party_Lobby(); + case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return State_Create_And_Move_To_Game_Lobby(); + case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return State_Create_And_Move_To_Game_State_Lobby(); + case STATE_FIND_OR_CREATE_MATCH: return State_Find_Or_Create_Match(); + case STATE_CONNECT_AND_MOVE_TO_PARTY: return State_Connect_And_Move_To_Party(); + case STATE_CONNECT_AND_MOVE_TO_GAME: return State_Connect_And_Move_To_Game(); + case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return State_Connect_And_Move_To_Game_State(); + case STATE_BUSY: return State_Busy(); + default: + idLib::Error( "HandleState: Unknown state in idSessionLocal" ); + } +} + +/* +======================== +idSessionLocal::GetState +======================== +*/ +idSessionLocal::sessionState_t idSessionLocal::GetState() const { + // Convert our internal state to one of the external states + switch ( localState ) { + case STATE_PRESS_START: return PRESS_START; + case STATE_IDLE: return IDLE; + case STATE_PARTY_LOBBY_HOST: return PARTY_LOBBY; + case STATE_PARTY_LOBBY_PEER: return PARTY_LOBBY; + case STATE_GAME_LOBBY_HOST: return GAME_LOBBY; + case STATE_GAME_LOBBY_PEER: return GAME_LOBBY; + case STATE_GAME_STATE_LOBBY_HOST: return GAME_LOBBY; + case STATE_GAME_STATE_LOBBY_PEER: return GAME_LOBBY; + case STATE_LOADING: return LOADING; + case STATE_INGAME: return INGAME; + case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return CONNECTING; + case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return CONNECTING; + case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return CONNECTING; + case STATE_FIND_OR_CREATE_MATCH: return SEARCHING; + case STATE_CONNECT_AND_MOVE_TO_PARTY: return CONNECTING; + case STATE_CONNECT_AND_MOVE_TO_GAME: return CONNECTING; + case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return CONNECTING; + case STATE_BUSY: return BUSY; + default: { + idLib::Error( "GetState: Unknown state in idSessionLocal" ); + } + }; +} + +const char * idSessionLocal::GetStateString() const { + static const char * stateToString[] = { + ASSERT_ENUM_STRING( STATE_PRESS_START, 0 ), + ASSERT_ENUM_STRING( STATE_IDLE, 1 ), + ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_HOST, 2 ), + ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_PEER, 3 ), + ASSERT_ENUM_STRING( STATE_GAME_LOBBY_HOST, 4 ), + ASSERT_ENUM_STRING( STATE_GAME_LOBBY_PEER, 5 ), + ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_HOST, 6 ), + ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_PEER, 7 ), + ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, 8 ), + ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, 9 ), + ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, 10 ), + ASSERT_ENUM_STRING( STATE_FIND_OR_CREATE_MATCH, 11 ), + ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_PARTY, 12 ), + ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME, 13 ), + ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME_STATE, 14 ), + ASSERT_ENUM_STRING( STATE_BUSY, 15 ), + ASSERT_ENUM_STRING( STATE_LOADING, 16 ), + ASSERT_ENUM_STRING( STATE_INGAME, 17 ) + }; + return stateToString[ localState ]; +} + +// idSession interface + +/* +======================== +idSessionLocal::LoadingFinished + +Only called by idCommonLocal::FinalizeMapChange +======================== +*/ +void idSessionLocal::LoadingFinished() { + NET_VERBOSE_PRINT( "NET: Loading Finished\n" ); + + assert( GetState() == idSession::LOADING ); + + common->Dialog().ClearDialog( GDM_VOICE_RESTRICTED ); + GetActingGameStateLobby().loaded = true; + + if ( MatchTypeIsLocal( GetActingGameStateLobby().parms.matchFlags ) ) { + SetState( STATE_INGAME ); + } else if ( !GetActingGameStateLobby().IsHost() ) { // Tell game host we're done loading + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + GetActingGameStateLobby().QueueReliableMessage( GetActingGameStateLobby().host, idLobby::RELIABLE_LOADING_DONE, msg.GetReadData(), msg.GetSize() ); + } else { + SetState( STATE_INGAME ); + } + + SetFlushedStats( false ); +} + +/* +======================== +idSessionLocal::SendUsercmds +======================== +*/ +void idSessionLocal::SendUsercmds( idBitMsg & msg ) { + if ( localState != STATE_INGAME ) { + return; + } + + if ( GetActingGameStateLobby().IsPeer() ) { + idLobby::peer_t & hostPeer = GetActingGameStateLobby().peers[GetActingGameStateLobby().host]; + + // Don't send user cmds if we have unsent packet fragments + // (This can happen if we have packets to send, but SendAnotherFragment got throttled) + if ( hostPeer.packetProc->HasMoreFragments() ) { + idLib::Warning( "NET: Client called SendUsercmds while HasMoreFragments(). Skipping userCmds for this frame." ); + return; + } + + int sequence = hostPeer.snapProc->GetLastAppendedSequence(); + + // Add incoming BPS for QoS + float incomingBPS = hostPeer.receivedBps; + if ( hostPeer.receivedBpsIndex != sequence ) { + incomingBPS = idMath::ClampFloat( 0.0f, static_cast( idLobby::BANDWIDTH_REPORTING_MAX ), hostPeer.packetProc->GetIncomingRateBytes() ); + hostPeer.receivedBpsIndex = sequence; + hostPeer.receivedBps = incomingBPS; + } + uint16 incomingBPS_quantized = idMath::Ftoi( incomingBPS * ( ( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) / idLobby::BANDWIDTH_REPORTING_MAX ) ); + + byte buffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE]; + lzwCompressionData_t lzwData; + idLZWCompressor lzwCompressor( &lzwData ); + lzwCompressor.Start( buffer, sizeof( buffer ) ); + lzwCompressor.WriteAgnostic( sequence ); + lzwCompressor.WriteAgnostic( incomingBPS_quantized ); + lzwCompressor.Write( msg.GetReadData(), msg.GetSize() ); + lzwCompressor.End(); + + GetActingGameStateLobby().ProcessOutgoingMsg( GetActingGameStateLobby().host, buffer, lzwCompressor.Length(), false, 0 ); + + if ( net_debugBaseStates.GetBool() && sequence < 50 ) { + idLib::Printf( "NET: Acking snap %d \n", sequence ); + } + } +} + +/* +======================== +idSessionLocal::SendSnapshot +======================== +*/ +void idSessionLocal::SendSnapshot( idSnapShot & ss ) { + for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { + idLobby::peer_t & peer = GetActingGameStateLobby().peers[p]; + + if ( !peer.IsConnected() ) { + continue; + } + + if ( !peer.loaded ) { + continue; + } + + if ( peer.pauseSnapshots ) { + continue; + } + + GetActingGameStateLobby().SendSnapshotToPeer( ss, p ); + } +} + +/* +======================== +idSessionLocal::UpdateSignInManager +======================== +*/ +void idSessionLocal::UpdateSignInManager() { + if ( !HasSignInManager() ) { + return; + } + + if ( net_headlessServer.GetBool() ) { + return; + } + + // FIXME: We need to ask the menu system for this info. Just making a best guess for now + // (assume we are allowed to join the party as a splitscreen user if we are in the party lobby) + bool allowJoinParty = ( localState == STATE_PARTY_LOBBY_HOST || localState == STATE_PARTY_LOBBY_PEER ) && GetPartyLobby().state == idLobby::STATE_IDLE; + bool allowJoinGame = ( localState == STATE_GAME_LOBBY_HOST || localState == STATE_GAME_LOBBY_PEER ) && GetGameLobby().state == idLobby::STATE_IDLE; + + bool eitherLobbyRunning = GetActivePlatformLobby() != NULL && ( GetPartyLobby().IsLobbyActive() || GetGameLobby().IsLobbyActive() ); + bool onlineMatch = eitherLobbyRunning && MatchTypeIsOnline( GetActivePlatformLobby()->parms.matchFlags ); + + //================================================================================= + // Get the number of desired signed in local users depending on what mode we're in. + //================================================================================= + int minDesiredUsers = 0; + int maxDesiredUsers = Max( 1, signInManager->GetNumLocalUsers() ); + + if ( si_splitscreen.GetInteger() != 0 ) { + // For debugging, force 2 splitscreen players + minDesiredUsers = 2; + maxDesiredUsers = 2; + allowJoinGame = true; + } else if ( onlineMatch || ( eitherLobbyRunning == false ) ) { + // If this an online game, then only 1 user can join locally. + // Also, if no sessions are active, remove any extra players. + maxDesiredUsers = 1; + } else if ( allowJoinParty || allowJoinGame ) { + // If we are in the party lobby, allow 2 splitscreen users to join + maxDesiredUsers = 2; + } + + // Set the number of desired users + signInManager->SetDesiredLocalUsers( minDesiredUsers, maxDesiredUsers ); + + //================================================================================= + // Update signin manager + //================================================================================= + + // Update signin mgr. This manager tracks signed in local users, which the session then uses + // to determine who should be in the lobby. + signInManager->Pump(); + + // Get the master local user + idLocalUser * masterUser = signInManager->GetMasterLocalUser(); + + if ( onlineMatch && masterUser != NULL && !masterUser->CanPlayOnline() && !masterUser->HasOwnerChanged() ) { + if ( localState > STATE_IDLE ) { + // User is still valid, just no longer online + if ( offlineTransitionTimerStart == 0 ) { + offlineTransitionTimerStart = Sys_Milliseconds(); + } + + if ( ( Sys_Milliseconds() - offlineTransitionTimerStart ) > net_offlineTransitionThreshold.GetInteger() ) { + MoveToMainMenu(); + common->Dialog().ClearDialogs(); + common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); + } + } + return; // Bail out so signInManager->ValidateLocalUsers below doesn't prematurely remove the master user before we can detect loss of connection + } else { + offlineTransitionTimerStart = 0; + } + + // Remove local users (from the signin manager) who aren't allowed to be online if this is an online match. + // Remove local user (from the signin manager) who are not properly signed into a profile. + signInManager->ValidateLocalUsers( onlineMatch ); + + //================================================================================= + // Check to see if we need to go to "Press Start" + //================================================================================= + + // Get the master local user (again, after ValidateOnlineLocalUsers, to make sure he is still valid) + masterUser = signInManager->GetMasterLocalUser(); + + if ( masterUser == NULL ) { + // If we don't have a master user at all, then we need to be at "Press Start" + MoveToPressStart( GDM_SP_SIGNIN_CHANGE_POST ); + return; + } else if ( localState == STATE_PRESS_START ) { + + + // If we have a master user, and we are at press start, move to the menu area + SetState( STATE_IDLE ); + + } + + // See if the master user either isn't persistent (but needs to be), OR, if the owner changed + // RequirePersistentMaster is poorly named, this really means RequireSignedInMaster + if ( masterUser->HasOwnerChanged() || ( RequirePersistentMaster() && !masterUser->IsProfileReady() ) ) { + MoveToPressStart( GDM_SP_SIGNIN_CHANGE_POST ); + return; + } + + //================================================================================= + // Sync lobby users with the signed in users + // The initial list of session users are normally determined at connect or create time. + // These functions allow splitscreen users to join in, or check to see if existing + // users (including the master) need to be removed. + //================================================================================= + GetPartyLobby().SyncLobbyUsersWithLocalUsers( allowJoinParty, onlineMatch ); + GetGameLobby().SyncLobbyUsersWithLocalUsers( allowJoinGame, onlineMatch ); + GetGameStateLobby().SyncLobbyUsersWithLocalUsers( allowJoinGame, onlineMatch ); +} + +/* +======================== +idSessionLocal::GetProfileFromMasterLocalUser +======================== +*/ +idPlayerProfile * idSessionLocal::GetProfileFromMasterLocalUser() { + idPlayerProfile * profile = NULL; + idLocalUser * masterUser = signInManager->GetMasterLocalUser(); + + if ( masterUser != NULL ) { + profile = masterUser->GetProfile(); + } + + if ( profile == NULL ) { + // Whoops + profile = signInManager->GetDefaultProfile(); + //idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." ); + } + + return profile; +} + +/* +======================== +/* +======================== +idSessionLocal::MoveToPressStart +======================== +*/ +void idSessionLocal::MoveToPressStart( gameDialogMessages_t msg ) { + if ( localState != STATE_PRESS_START ) { + MoveToPressStart(); + common->Dialog().ClearDialogs(); + common->Dialog().AddDialog( msg, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); + } +} + +/* +======================== +idSessionLocal::GetPeerName +======================== +*/ +const char * idSessionLocal::GetPeerName( int peerNum ) { + return GetActingGameStateLobby().GetPeerName( peerNum ); +} + + +/* +======================== +idSessionLocal::SetState +======================== +*/ +void idSessionLocal::SetState( state_t newState ) { + + assert( newState < NUM_STATES ); + assert( localState < NUM_STATES ); + verify_array_size( stateToString, NUM_STATES ); + + if ( newState == localState ) { + NET_VERBOSE_PRINT( "NET: SetState: State SAME %s\n", stateToString[ newState ] ); + return; + } + + // Set the current state + NET_VERBOSE_PRINT( "NET: SetState: State changing from %s to %s\n", stateToString[ localState ], stateToString[ newState ] ); + + if ( localState < STATE_LOADING && newState >= STATE_LOADING ) { + // Tell lobby instances that the match has started + StartSessions(); + // Clear certain dialog boxes we don't want to see in-game + common->Dialog().ClearDialog( GDM_LOBBY_DISBANDED ); // The lobby you were previously in has disbanded + } else if ( localState >= STATE_LOADING && newState < STATE_LOADING ) { + // Tell lobby instances that the match has ended + if ( !WasMigrationGame() ) { // Don't end the session if we are going right back into the game + EndSessions(); + } + } + + if ( newState == STATE_GAME_LOBBY_HOST || newState == STATE_GAME_LOBBY_PEER ) { + ComputeNextGameCoalesceTime(); + } + + localState = newState; +} + +/* +======================== +idSessionLocal::HandlePackets +======================== +*/ +bool idSessionLocal::HandlePackets() { + SCOPED_PROFILE_EVENT( "Session::HandlePackets" ); + + byte packetBuffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ]; + lobbyAddress_t remoteAddress; + int recvSize = 0; + bool fromDedicated = false; + + while ( ReadRawPacket( remoteAddress, packetBuffer, recvSize, fromDedicated, sizeof( packetBuffer ) ) && recvSize > 0 ) { + + // fragMsg will hold the raw packet + idBitMsg fragMsg; + fragMsg.InitRead( packetBuffer, recvSize ); + + // Peek at the session ID + idPacketProcessor::sessionId_t sessionID = idPacketProcessor::GetSessionID( fragMsg ); + + // idLib::Printf( "NET: HandlePackets - session %d, size %d \n", sessionID, recvSize ); + + // Make sure it's valid + if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) { + idLib::Printf( "NET: Invalid sessionID %s.\n", remoteAddress.ToString() ); + continue; + } + + // + // Distribute the packet to the proper lobby + // + + const int maskedType = sessionID & idPacketProcessor::LOBBY_TYPE_MASK; + + if ( !verify( maskedType > 0 ) ) { + continue; + } + + idLobby::lobbyType_t lobbyType = (idLobby::lobbyType_t)( maskedType - 1 ); + + switch ( lobbyType ) { + case idLobby::TYPE_PARTY: GetPartyLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); break; + case idLobby::TYPE_GAME: GetGameLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); break; + case idLobby::TYPE_GAME_STATE: GetGameStateLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); break; + default: assert( 0 ); + } + } + + return false; +} + +/* +======================== +idSessionLocal::GetActivePlatformLobby +======================== +*/ +idLobby * idSessionLocal::GetActivePlatformLobby() { + sessionState_t state = GetState(); + + if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) { + return &GetGameLobby(); + } else if ( state == PARTY_LOBBY ) { + return &GetPartyLobby(); + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetActivePlatformLobby +======================== +*/ +const idLobby * idSessionLocal::GetActivePlatformLobby() const { + sessionState_t state = GetState(); + + if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) { + return &GetGameLobby(); + } else if ( state == PARTY_LOBBY ) { + return &GetPartyLobby(); + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetActingGameStateLobby +======================== +*/ +idLobby & idSessionLocal::GetActingGameStateLobby() { + if ( net_useGameStateLobby.GetBool() ) { + return GetGameStateLobby(); + } + + return GetGameLobby(); +} + +/* +======================== +idSessionLocal::GetActingGameStateLobby +======================== +*/ +const idLobby & idSessionLocal::GetActingGameStateLobby() const { + if ( net_useGameStateLobby.GetBool() ) { + return GetGameStateLobby(); + } + + return GetGameLobby(); +} + +/* +======================== +idSessionLocal::GetLobbyFromType +======================== +*/ +idLobby * idSessionLocal::GetLobbyFromType( idLobby::lobbyType_t lobbyType ) { + switch ( lobbyType ) { + case idLobby::TYPE_PARTY: return &GetPartyLobby(); + case idLobby::TYPE_GAME: return &GetGameLobby(); + case idLobby::TYPE_GAME_STATE: return &GetGameStateLobby(); + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetActivePlatformLobbyBase +This returns the base version for the idSession version +======================== +*/ +idLobbyBase & idSessionLocal::GetActivePlatformLobbyBase() { + idLobby * activeLobby = GetActivePlatformLobby(); + + if ( activeLobby != NULL ) { + return *activeLobby; + } + + return stubLobby; // So we can return at least something +} + +/* +======================== +idSessionLocal::GetLobbyFromLobbyUserID +======================== +*/ +idLobbyBase & idSessionLocal::GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID ) { + if ( !lobbyUserID.IsValid() ) { + return stubLobby; // So we can return at least something + } + + idLobby * lobby = GetLobbyFromType( (idLobby::lobbyType_t)lobbyUserID.GetLobbyType() ); + + if ( lobby != NULL ) { + return *lobby; + } + + return stubLobby; // So we can return at least something +} + +/* +======================== +idSessionLocal::TickSendQueue +======================== +*/ +void idSessionLocal::TickSendQueue() { + assert( !sendQueue.IsEmpty() ); + int now = Sys_Milliseconds(); + idQueuePacket * packet = sendQueue.Peek(); + while ( packet != NULL ) { + if ( now < packet->time ) { + break; + } + + GetPort( packet->dedicated ).SendRawPacket( packet->address, packet->data, packet->size ); + + if ( net_forceUpstream.GetFloat() != 0.0f && net_forceUpstreamQueue.GetFloat() != 0.0f ) { + // FIXME: no can do both + assert( net_forceLatency.GetInteger() == 0 ); + // compute / update an added traffic due to the queuing + // we can't piggyback on upstreamDropRate because of the way it's computed and clamped to zero + int time = Sys_Milliseconds(); + if ( time > upstreamQueueRateTime ) { + upstreamQueueRate -= upstreamQueueRate * ( float )( time - upstreamQueueRateTime ) / 1000.0f; + if ( upstreamQueueRate < 0.0f ) { + upstreamQueueRate = 0.0f; + } + upstreamQueueRateTime = time; + } + // update queued bytes + queuedBytes -= packet->size; + if ( net_verboseSimulatedTraffic.GetBool() ) { + idLib::Printf( "send queued packet size %d to %s\n", packet->size, packet->address.ToString() ); + } + } + + sendQueue.RemoveFirst(); // we have it already, just push it off the queue before freeing + packetAllocator.Free( packet ); + packet = sendQueue.Peek(); + } +} + +/* +======================== +idSessionLocal::QueuePacket +======================== +*/ +void idSessionLocal::QueuePacket( idQueue< idQueuePacket,&idQueuePacket::queueNode > & queue, int time, const lobbyAddress_t & to, const void * data, int size, bool dedicated ) { + //mem.PushHeap(); + + idQueuePacket * packet = packetAllocator.Alloc(); + + packet->address = to; + packet->size = size; + packet->dedicated = dedicated; + packet->time = time; + + memcpy( packet->data, data, size ); + + queue.Add( packet ); + + //mem.PopHeap(); +} + +/* +======================== +idSessionLocal::ReadRawPacketFromQueue +======================== +*/ +bool idSessionLocal::ReadRawPacketFromQueue( int time, lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ) { + idQueuePacket * packet = recvQueue.Peek(); + + if ( packet == NULL || time < packet->time ) { + return false; // Either there are no packets, or no packet is ready + } + + //idLib::Printf( "NET: Packet recvd: %d ms\n", now ); + + from = packet->address; + size = packet->size; + assert( size <= maxSize ); + outDedicated = packet->dedicated; + memcpy( data, packet->data, packet->size ); + recvQueue.RemoveFirst(); // we have it already, just push it off the queue before freeing + packetAllocator.Free( packet ); + + return true; +} + +/* +======================== +idSessionLocal::SendRawPacket +======================== +*/ +void idSessionLocal::SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool dedicated ) { + const int now = Sys_Milliseconds(); + + if ( net_forceUpstream.GetFloat() != 0 ) { + + // the total bandwidth rate at which the networking systems are trying to push data through + float totalOutgoingRate = (float)GetActingGameStateLobby().GetTotalOutgoingRate(); // B/s + + // update the rate at which we have been taking data out by dropping it + int time = Sys_Milliseconds(); + if ( time > upstreamDropRateTime ) { + upstreamDropRate -= upstreamDropRate * ( float )( time - upstreamDropRateTime ) / 1000.0f; + if ( upstreamDropRate < 0.0f ) { + upstreamDropRate = 0.0f; + } + upstreamDropRateTime = time; + } + + if ( (float)( totalOutgoingRate - upstreamDropRate + upstreamQueueRate ) > net_forceUpstream.GetFloat() * 1024.0f ) { // net_forceUpstream is in kB/s, everything else in B/s + if ( net_forceUpstreamQueue.GetFloat() == 0.0f ) { + // just drop the packet - not representative, but simple + if ( net_verboseSimulatedTraffic.GetBool() ) { + idLib::Printf( "drop %d bytes to %s\n", size, to.ToString() ); + } + // increase the instant drop rate with the data we just dropped + upstreamDropRate += size; + return; + } + + // simulate a network device with a send queue + // do we have room in the queue? + assert( net_forceUpstreamQueue.GetFloat() > 0.0f ); + if ( (float)( queuedBytes + size ) > net_forceUpstreamQueue.GetFloat() * 1024.0f ) { // net_forceUpstreamQueue is in kB/s + // too much queued, this is still a drop + // FIXME: factorize + // just drop the packet - not representative, but simple + if ( net_verboseSimulatedTraffic.GetBool() ) { + idLib::Printf( "full queue: drop %d bytes to %s\n", size, to.ToString() ); + } + // increase the instant drop rate with the data we just dropped + upstreamDropRate += size; + return; + } + // there is room to buffer up in the queue + queuedBytes += size; + // with queuedBytes and the current upstream, when should this packet be sent? + int queuedPacketSendDelay = 1000.0f * ( (float)queuedBytes / ( net_forceUpstream.GetFloat() * 1024.0f ) ); // in ms + // queue for sending + if ( net_verboseSimulatedTraffic.GetBool() ) { + idLib::Printf( "queuing packet: %d bytes delayed %d ms\n", size, queuedPacketSendDelay ); + } + + QueuePacket( sendQueue, now + queuedPacketSendDelay, to, data, size, dedicated ); + + // will abuse the forced latency code below to take care of the sending + // FIXME: right now, can't have both on + assert( net_forceLatency.GetInteger() == 0 ); + } + } + + // short path + // NOTE: network queuing: will go to tick the queue whenever sendQueue isn't empty, regardless of latency + if ( net_forceLatency.GetInteger() == 0 && sendQueue.IsEmpty() ) { + GetPort( dedicated ).SendRawPacket( to, data, size ); + return; + } + + if ( net_forceUpstream.GetFloat() != 0.0f && net_forceUpstreamQueue.GetFloat() != 0.0f ) { + // FIXME: not doing both just yet + assert( net_forceLatency.GetInteger() == 0 ); + TickSendQueue(); + return; // we done (at least for queue only path) + } + + // queue up + assert( size != 0 && size <= idPacketProcessor::MAX_FINAL_PACKET_SIZE ); + + QueuePacket( sendQueue, now + net_forceLatency.GetInteger() / 2, to, data, size, dedicated ); + + TickSendQueue(); +} + +/* +======================== +idSessionLocal::ReadRawPacket +======================== +*/ +bool idSessionLocal::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ) { + SCOPED_PROFILE_EVENT( "Session::ReadRawPacket" ); + + assert( maxSize <= idPacketProcessor::MAX_FINAL_PACKET_SIZE ); + + if ( !sendQueue.IsEmpty() ) { + TickSendQueue(); + } + + const int now = Sys_Milliseconds(); + + // Make sure we give both ports equal time + static bool currentDedicated = false; + currentDedicated = !currentDedicated; + + for ( int i = 0; i < 2; i++ ) { + // BRIAN_FIXME: Dedicated servers fuck up running 2 instances on the same machine + // outDedicated = ( i == 0 ) ? currentDedicated : !currentDedicated; + outDedicated = false; + + if ( GetPort( outDedicated ).ReadRawPacket( from, data, size, maxSize ) ) { + if ( net_forceLatency.GetInteger() == 0 && recvQueue.IsEmpty() ) { + // If we aren't forcing latency, and queue is empty, return result immediately + return true; + } + + // the cvar is meant to be a round trip latency so we're applying half on the send and half on the recv + const int time = ( net_forceLatency.GetInteger() == 0 ) ? 0 : now + net_forceLatency.GetInteger() / 2; + + // Otherwise, queue result + QueuePacket( recvQueue, time, from, data, size, outDedicated ); + } + } + + // Return any queued results + return ReadRawPacketFromQueue( now, from, data, size, outDedicated, maxSize ); +} + +/* +======================== +idSessionLocal::ConnectAndMoveToLobby +======================== +*/ +void idSessionLocal::ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ) { + + // Since we are connecting directly to a lobby, make sure no search results are left over from previous FindOrCreateMatch results + // If we don't do this, we might think we should attempt to connect to an old search result, and we don't want to in this case + lobby.searchResults.Clear(); + + // Attempt to connect to the lobby + lobby.ConnectTo( connectInfo, fromInvite ); + + connectType = CONNECT_DIRECT; + + // Wait for connection + switch ( lobby.lobbyType ) { + case idLobby::TYPE_PARTY: SetState( STATE_CONNECT_AND_MOVE_TO_PARTY ); break; + case idLobby::TYPE_GAME: SetState( STATE_CONNECT_AND_MOVE_TO_GAME ); break; + case idLobby::TYPE_GAME_STATE: SetState( STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); break; + } +} + +/* +======================== +idSessionLocal::GoodbyeFromHost +======================== +*/ +void idSessionLocal::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) { + if ( !verify( localState > STATE_IDLE ) ) { + idLib::Printf( "NET: Got disconnected from host %s on session %s when we were not in a lobby or game.\n", remoteAddress.ToString(), lobby.GetLobbyName() ); + MoveToMainMenu(); + return; // Ignore if we are not past the main menu + } + + // Goodbye from host. See if we were connecting vs connected + if ( ( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ) && lobby.peers[peerNum].GetConnectionState() == idLobby::CONNECTION_CONNECTING ) { + // We were denied a connection attempt + idLib::Printf( "NET: Denied connection attempt from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType ); + // This will try to move to the next connection if one exists, otherwise will create a match + HandleConnectionFailed( lobby, msgType == idLobby::OOB_GOODBYE_FULL ); + } else { + // We were disconnected from a server we were previously connected to + idLib::Printf( "NET: Disconnected from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType ); + + const bool leaveGameWithParty = ( msgType == idLobby::OOB_GOODBYE_W_PARTY ); + + if ( leaveGameWithParty && lobby.lobbyType == idLobby::TYPE_GAME && lobby.IsPeer() && GetState() == idSession::GAME_LOBBY && GetPartyLobby().host >= 0 && + lobby.peers[peerNum].address.Compare( GetPartyLobby().peers[GetPartyLobby().host].address, true ) ) { + // If a host is telling us goodbye from a game lobby, and the game host is the same as our party host, + // and we aren't in a game, and the host wants us to leave with him, then do so now + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + SetState( STATE_PARTY_LOBBY_PEER ); + } else { + // Host left, so pick a new host (possibly even us) for this lobby + lobby.PickNewHost(); + } + } +} + +/* +======================== +idSessionLocal::WriteLeaderboardToMsg +======================== +*/ +void idSessionLocal::WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ) { + assert( Sys_FindLeaderboardDef( leaderboard->id ) == leaderboard ); + + msg.WriteLong( leaderboard->id ); + + for ( int i = 0; i < leaderboard->numColumns; ++i ) { + uint64 value = stats[i].value; + + //idLib::Printf( "value = %i\n", (int32)value ); + + for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) { + msg.WriteBits( value & 1, 1 ); + value >>= 1; + } + //msg.WriteData( &stats[i].value, sizeof( stats[i].value ) ); + } +} + +/* +======================== +idSessionLocal::ReadLeaderboardFromMsg +======================== +*/ +const leaderboardDefinition_t * idSessionLocal::ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ) { + int id = msg.ReadLong(); + + const leaderboardDefinition_t * leaderboard = Sys_FindLeaderboardDef( id ); + + if ( leaderboard == NULL ) { + idLib::Printf( "NET: Invalid leaderboard id: %i\n", id ); + return NULL; + } + + for ( int i = 0; i < leaderboard->numColumns; ++i ) { + uint64 value = 0; + + for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) { + value |= (uint64)( msg.ReadBits( 1 ) & 1 ) << j; + } + + stats[i].value = value; + + //idLib::Printf( "value = %i\n", (int32)value ); + //msg.ReadData( &stats[i].value, sizeof( stats[i].value ) ); + } + + return leaderboard; +} + +/* +======================== +idSessionLocal::SendLeaderboardStatsToPlayer +======================== +*/ +void idSessionLocal::SendLeaderboardStatsToPlayer( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats ) { + + const int sessionUserIndex = GetActingGameStateLobby().GetLobbyUserIndexByID( lobbyUserID ); + + if ( GetActingGameStateLobby().IsLobbyUserDisconnected( sessionUserIndex ) ) { + idLib::Warning( "Tried to tell disconnected user to report stats" ); + return; + } + + const int peerIndex = GetActingGameStateLobby().PeerIndexFromLobbyUser( lobbyUserID ); + + if ( peerIndex == -1 ) { + idLib::Warning( "Tried to tell invalid peer index to report stats" ); + return; + } + + if ( !verify( GetActingGameStateLobby().IsHost() ) || + !verify( peerIndex < GetActingGameStateLobby().peers.Num() ) || + !verify( GetActingGameStateLobby().peers[ peerIndex ].IsConnected() ) ) { + idLib::Warning( "Tried to tell invalid peer to report stats" ); + return; + } + + NET_VERBOSE_PRINT( "Telling sessionUserIndex %i (peer %i) to report stats\n", sessionUserIndex, peerIndex ); + + lobbyUser_t * gameUser = GetActingGameStateLobby().GetLobbyUser( sessionUserIndex ); + + if ( !verify( gameUser != NULL ) ) { + return; + } + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + // Use the user ID + gameUser->lobbyUserID.WriteToMsg( msg ); + + WriteLeaderboardToMsg( msg, leaderboard, stats ); + + GetActingGameStateLobby().QueueReliableMessage( peerIndex, idLobby::RELIABLE_POST_STATS, msg.GetReadData(), msg.GetSize() ); +} + +/* +======================== +idSessionLocal::RecvLeaderboardStatsForPlayer +======================== +*/ +void idSessionLocal::RecvLeaderboardStatsForPlayer( idBitMsg & msg ) { + column_t stats[ MAX_LEADERBOARD_COLUMNS ]; + + lobbyUserID_t lobbyUserID; + lobbyUserID.ReadFromMsg( msg ); + + const leaderboardDefinition_t * leaderboard = ReadLeaderboardFromMsg( msg, stats ); + + if ( leaderboard == NULL ) { + idLib::Printf( "RecvLeaderboardStatsForPlayer: Invalid lb.\n" ); + return; + } + + LeaderboardUpload( lobbyUserID, leaderboard, stats ); +} + +/* +======================== +idSessionLocal::RequirePersistentMaster +======================== +*/ +bool idSessionLocal::RequirePersistentMaster() { + return signInManager->RequirePersistentMaster(); +} + +/* +======================== +CheckAndUpdateValue +======================== +*/ +template +bool CheckAndUpdateValue( T & value, const T & newValue ) { + if ( value == newValue ) { + return false; + } + value = newValue; + return true; +} + +/* +======================== +lobbyUser_t::UpdateClientMutableData +======================== +*/ +bool lobbyUser_t::UpdateClientMutableData( const idLocalUser * localUser ) { + bool updated = false; + const idPlayerProfile * profile = localUser->GetProfile(); + if ( profile != NULL ) { + updated |= CheckAndUpdateValue( level, profile->GetLevel() ); + } + updated |= CheckAndUpdateValue( selectedSkin, ui_skinIndex.GetInteger() ); + updated |= CheckAndUpdateValue( weaponAutoSwitch, ui_autoSwitch.GetBool() ); + updated |= CheckAndUpdateValue( weaponAutoReload, ui_autoReload.GetBool() ); + return updated; +} + +/* +======================== +idSessionLocal::ComputeNextGameCoalesceTime +======================== +*/ +void idSessionLocal::ComputeNextGameCoalesceTime() { + const int coalesceTimeInSeconds = session->GetTitleStorageInt( "net_LobbyCoalesceTimeInSeconds", net_LobbyCoalesceTimeInSeconds.GetInteger() ); + const int randomCoalesceTimeInSeconds = session->GetTitleStorageInt( "net_LobbyRandomCoalesceTimeInSeconds", net_LobbyRandomCoalesceTimeInSeconds.GetInteger() ); + + if ( coalesceTimeInSeconds != 0 ) { + static idRandom2 random( Sys_Milliseconds() ); + + nextGameCoalesceTime = Sys_Milliseconds() + ( coalesceTimeInSeconds + random.RandomInt( randomCoalesceTimeInSeconds ) ) * 1000; + } else { + nextGameCoalesceTime = 0; + } +} + +/* +======================== +lobbyUser_t::Net_BandwidthChallenge +======================== +*/ +CONSOLE_COMMAND( Net_BandwidthChallenge, "Test network bandwidth", 0 ) { + session->StartOrContinueBandwidthChallenge( true ); +} + +/* +======================== +lobbyUser_t::Net_ThrottlePeer +======================== +*/ +CONSOLE_COMMAND( Net_ThrottlePeer, "Test network bandwidth", 0 ) { + + int peerNum = -1; + int snapRate = 0; + + if ( args.Argc() >= 3 ) { + peerNum = atoi( args.Argv(1) ); + snapRate = atoi( args.Argv(2) ); + } + + // Note DebugSetPeerSnaprate will handle peerNum=-1 by printing out list of peers + session->DebugSetPeerSnaprate( peerNum, snapRate ); +} + + +// FIXME: Move to sys_stats.cpp +idStaticList< leaderboardDefinition_t *, MAX_LEADERBOARDS > registeredLeaderboards; + +/* +======================== +Sys_FindLeaderboardDef +======================== +*/ +const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ) { + for ( int i = 0; i < registeredLeaderboards.Num() ; i++ ) { + if ( registeredLeaderboards[i] && registeredLeaderboards[i]->id == id ) { + return registeredLeaderboards[i]; + } + } + + return NULL; +} + +/* +======================== +Sys_CreateLeaderboardDef +======================== +*/ +leaderboardDefinition_t * Sys_CreateLeaderboardDef( int id_, int numColumns_, const columnDef_t * columnDefs_, + rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) { + + leaderboardDefinition_t * newDef = new (TAG_NETWORKING) leaderboardDefinition_t( id_, numColumns_, columnDefs_, rankOrder_, supportsAttachments_, checkAgainstCurrent_ ); + + // try and reuse a free spot + int leaderboardHandle = registeredLeaderboards.FindNull(); + + if ( leaderboardHandle == -1 ) { + leaderboardHandle = registeredLeaderboards.Append( NULL ); + } + + registeredLeaderboards[ leaderboardHandle ] = newDef; + + return newDef; +} + +/* +======================== +Sys_CreateLeaderboardDef +======================== +*/ +void Sys_DestroyLeaderboardDefs() { + + // delete and clear all the contents of the registeredLeaderboards static list. + registeredLeaderboards.DeleteContents( true ); +} + +/* +======================== +idSessionLocal::StartOrContinueBandwidthChallenge +This will start a bandwidth test if one is not active +returns true if a test has completed +======================== +*/ +bool idSessionLocal::StartOrContinueBandwidthChallenge( bool forceStart ) { + idLobby * activeLobby = GetActivePlatformLobby(); + if ( activeLobby == NULL ) { + idLib::Warning("No active session lobby when idSessionLocal::StartBandwidthChallenge called"); + return true; + } + + if ( !forceStart && activeLobby->bandwidthChallengeFinished ) { + activeLobby->bandwidthChallengeFinished = false; + return true; + } + + if ( !activeLobby->BandwidthTestStarted() ) { + activeLobby->BeginBandwidthTest(); + } + + return false; +} + +/* +======================== +idSessionLocal::DebugSetPeerSnaprate +This is debug function for manually setting peer's snaprate in game +======================== +*/ +void idSessionLocal::DebugSetPeerSnaprate( int peerIndex, int snapRateMS ) { + idLobby * activeLobby = GetActivePlatformLobby(); + if ( activeLobby == NULL ) { + idLib::Warning("No active session lobby when idSessionLocal::StartBandwidthChallenge called"); + return; + } + + if ( peerIndex < 0 || peerIndex > activeLobby->peers.Num() ) { + idLib::Printf("Invalid peer %d\n", peerIndex ); + for ( int i=0; i < activeLobby->peers.Num(); i++ ) { + idLib::Printf( "Peer[%d] %s\n", i, activeLobby->GetPeerName(i) ); + } + return; + } + + activeLobby->peers[peerIndex].throttledSnapRate = snapRateMS * 1000; + activeLobby->peers[peerIndex].receivedThrottle = 0; + idLib::Printf( "Set peer %s new snapRate: %d\n", activeLobby->GetPeerName(peerIndex), activeLobby->peers[peerIndex].throttledSnapRate ); +} + +/* +======================== +idSessionLocal::DebugSetPeerSnaprate +This is debug function for manually setting peer's snaprate in game +======================== +*/ +float idSessionLocal::GetIncomingByteRate() { + idLobby * activeLobby = GetActivePlatformLobby(); + if ( activeLobby == NULL ) { + idLib::Warning("No active session lobby when idSessionLocal::GetIncomingByteRate called"); + return 0; + } + + float total = 0; + for ( int p=0; p < activeLobby->peers.Num(); p++ ) { + if ( activeLobby->peers[p].IsConnected() ) { + total += activeLobby->peers[p].packetProc->GetIncomingRateBytes(); + } + } + + return total; +} + +/* +======================== +idSessionLocal::OnLocalUserSignin +======================== +*/ +void idSessionLocal::OnLocalUserSignin( idLocalUser * user ) { + // Do stuff before calling OnMasterLocalUserSignin() + session->GetAchievementSystem().RegisterLocalUser( user ); + + // We may not have a profile yet, need to call user's version... + user->LoadProfileSettings(); + + // for all consoles except the PS3 we enumerate right away because they don't + // take such a long time as the PS3. PS3 enumeration is done in the + // background and kicked off when the profile callback is triggered + if ( user == GetSignInManager().GetMasterLocalUser() ) { + OnMasterLocalUserSignin(); + } +} + +/* +======================== +idSessionLocal::OnLocalUserSignout +======================== +*/ +void idSessionLocal::OnLocalUserSignout( idLocalUser * user ) { + // Do stuff before calling OnMasterLocalUserSignout() + session->GetAchievementSystem().RemoveLocalUser( user ); + + if ( GetSignInManager().GetMasterLocalUser() == NULL ) { + OnMasterLocalUserSignout(); + } +} + +/* +======================== +idSessionLocal::OnMasterLocalUserSignout +======================== +*/ +void idSessionLocal::OnMasterLocalUserSignout() { + CancelSaveGameWithHandle( enumerationHandle ); + enumerationHandle = 0; + GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear(); +} + +/* +======================== +idSessionLocal::OnMasterLocalUserSignin +======================== +*/ +void idSessionLocal::OnMasterLocalUserSignin() { + enumerationHandle = EnumerateSaveGamesAsync(); +} + +/* +======================== +idSessionLocal::OnLocalUserProfileLoaded +======================== +*/ +void idSessionLocal::OnLocalUserProfileLoaded( idLocalUser * user ) { + user->RequestSyncAchievements(); +} + +/* +======================== +idSessionLocal::SetVoiceGroupsToTeams +======================== +*/ +void idSessionLocal::SetVoiceGroupsToTeams() { + // move voice chat to team + int myTeam = 0; + for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) { + const lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( i ); + + if ( !verify( gameUser != NULL ) ) { + continue; + } + + if ( gameUser->IsDisconnected() ) { + continue; + } + + int userTeam = gameUser->teamNumber; + + voiceChat->SetTalkerGroup( gameUser, GetGameLobby().lobbyType, userTeam ); + + if ( GetGameLobby().IsSessionUserIndexLocal( i ) ) { + myTeam = userTeam; + } + } + + SetActiveChatGroup( myTeam ); +} + +/* +======================== +idSessionLocal::ClearVoiceGroups +======================== +*/ +void idSessionLocal::ClearVoiceGroups() { + for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) { + const lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( i ); + + if ( !verify( gameUser != NULL ) ) { + continue; + } + + if ( gameUser->IsDisconnected() ) { + continue; + } + + voiceChat->SetTalkerGroup( gameUser, GetGameLobby().lobbyType, 0 ); + } + + SetActiveChatGroup( 0 ); +} + +/* +======================== +idSessionLocal::SendVoiceAudio +======================== +*/ +void idSessionLocal::SendVoiceAudio() { + if ( voiceChat == NULL ) { + return; + } + + idLobby * activeLobby = GetActivePlatformLobby(); + + int activeSessionIndex = ( activeLobby != NULL ) ? activeLobby->lobbyType : -1; + + voiceChat->SetActiveLobby( activeSessionIndex ); + voiceChat->Pump(); + + if ( activeLobby == NULL ) { + return; + } + + int time = Sys_Milliseconds(); + + const int VOICE_THROTTLE_TIME_IN_MS = session->GetTitleStorageInt( "VOICE_THROTTLE_TIME_IN_MS", 33) ; // Don't allow faster than 30hz send rate + + if ( time - lastVoiceSendtime < VOICE_THROTTLE_TIME_IN_MS ) { + return; + } + + lastVoiceSendtime = time; + + idStaticList< int, MAX_PLAYERS > localTalkers; + + voiceChat->GetActiveLocalTalkers( localTalkers ); + + for ( int i = 0; i < localTalkers.Num(); i++ ) { + + // NOTE - For 360, we don't need more than XHV_MAX_VOICECHAT_PACKETS * XHV_VOICECHAT_MODE_PACKET_SIZE bytes + const int MAX_VDP_DATA_SIZE = 1000; + + byte buffer[MAX_VDP_DATA_SIZE]; + + const int titleStorageDataSize = session->GetTitleStorageInt( "MAX_VDP_DATA_SIZE", 1000 ); + const int dataSizeAvailable = Min< int >( titleStorageDataSize, sizeof( buffer ) ); + + // in-out parameter + int dataSize = dataSizeAvailable; + if ( !voiceChat->GetLocalChatData( localTalkers[i], buffer, dataSize ) ) { + continue; + } + assert( dataSize <= sizeof( buffer ) ); + + idStaticList< const lobbyAddress_t *, MAX_PLAYERS > recipients; + + voiceChat->GetRecipientsForTalker( localTalkers[i], recipients ); + + for ( int j = 0; j < recipients.Num(); j++ ) { + activeLobby->SendConnectionLess( *recipients[j], idLobby::OOB_VOICE_AUDIO, buffer, dataSize ); + } + } +} + +/* +======================== +idSessionLocal::HandleOobVoiceAudio +======================== +*/ +void idSessionLocal::HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) { + + idLobby * activeLobby = GetActivePlatformLobby(); + + if ( activeLobby == NULL ) { + return; + } + + voiceChat->SetActiveLobby( activeLobby->lobbyType ); + + voiceChat->SubmitIncomingChatData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); +} + +/* +======================== +idSessionLocal::SetActiveChatGroup +======================== +*/ +void idSessionLocal::SetActiveChatGroup( int groupIndex ) { + voiceChat->SetActiveChatGroup( groupIndex ); +} + +/* +======================== +idSessionLocal::GetLobbyUserVoiceState +======================== +*/ +voiceState_t idSessionLocal::GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID ) { + idLobby * activeLobby = GetActivePlatformLobby(); + + if ( activeLobby == NULL ) { + return VOICECHAT_STATE_NOT_TALKING; + } + + const lobbyUser_t * user = activeLobby->GetLobbyUserByID( lobbyUserID ); + + if ( !verify( user != NULL ) ) { + return VOICECHAT_STATE_NOT_TALKING; + } + + return voiceChat->GetVoiceState( user ); +} + +/* +======================== +idSessionLocal::GetDisplayStateFromVoiceState +======================== +*/ +voiceStateDisplay_t idSessionLocal::GetDisplayStateFromVoiceState( voiceState_t voiceState ) const { + if ( ( GetState() == GAME_LOBBY && MatchTypeIsLocal( GetGameLobby().GetMatchParms().matchFlags ) ) + || ( GetState() == PARTY_LOBBY && MatchTypeIsLocal( GetPartyLobby().GetMatchParms().matchFlags ) ) ) { + return VOICECHAT_DISPLAY_NONE; // never show voice stuff in splitscreen + } + + switch ( voiceState ) { + case VOICECHAT_STATE_MUTED_REMOTE: + case VOICECHAT_STATE_MUTED_LOCAL: + case VOICECHAT_STATE_MUTED_ALL: + return VOICECHAT_DISPLAY_MUTED; + case VOICECHAT_STATE_NOT_TALKING: + return VOICECHAT_DISPLAY_NOTTALKING; + case VOICECHAT_STATE_TALKING: + return VOICECHAT_DISPLAY_TALKING; + case VOICECHAT_STATE_TALKING_GLOBAL: + return VOICECHAT_DISPLAY_TALKING_GLOBAL; + case VOICECHAT_STATE_NO_MIC: + default: + return VOICECHAT_DISPLAY_NOTTALKING; + } +} + +/* +======================== +idSessionLocal::ToggleLobbyUserVoiceMute +======================== +*/ +void idSessionLocal::ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID ) { + idLobby * activeLobby = GetActivePlatformLobby(); + + if ( activeLobby == NULL ) { + return; + } + + // Get the master local user + idLocalUser * masterUser = signInManager->GetMasterLocalUser(); + + if ( masterUser == NULL ) { + return; + } + + const lobbyUser_t * srcUser = activeLobby->GetLobbyUser( activeLobby->GetLobbyUserIndexByLocalUserHandle( masterUser->GetLocalUserHandle() ) ); + + if ( srcUser == NULL ) { + return; + } + + const lobbyUser_t * targetUser = activeLobby->GetLobbyUserByID( lobbyUserID ); + + if ( !verify( targetUser != NULL ) ) { + return; + } + + if ( srcUser == targetUser ) { + return; // Can't toggle yourself + } + + voiceChat->ToggleMuteLocal( srcUser, targetUser ); +} + +/* +======================== +idSessionLocal::UpdateMasterUserHeadsetState +======================== +*/ +void idSessionLocal::UpdateMasterUserHeadsetState() +{ + if ( GetState() != PARTY_LOBBY && GetState() != GAME_LOBBY && GetState() != INGAME ) { + return; + } + + lobbyUser_t * user = GetActivePlatformLobby()->GetSessionUserFromLocalUser( signInManager->GetMasterLocalUser() ); + + // TODO: Is this possible? + if ( user == NULL ) { + return; + } + + int talkerIndex = voiceChat->FindTalkerByUserId( user->lobbyUserID, GetActivePlatformLobby()->lobbyType ); + bool voiceChanged = voiceChat->HasHeadsetStateChanged( talkerIndex ); + + if ( voiceChanged ) { + byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + msg.WriteLong( 1 ); + user->lobbyUserID.WriteToMsg( msg ); + msg.WriteBool( voiceChat->GetHeadsetState( talkerIndex ) ); + + idLib::Printf( "Sending voicestate %d for user %d %s\n", voiceChat->GetHeadsetState( talkerIndex ), talkerIndex, user->gamertag ); + + if ( GetActivePlatformLobby()->IsHost() ) { + for ( int p = 0; p < GetActivePlatformLobby()->peers.Num(); p++ ) { + if ( GetActivePlatformLobby()->peers[p].IsConnected() ) { + GetActivePlatformLobby()->QueueReliableMessage( p, idLobby::RELIABLE_HEADSET_STATE, msg.GetReadData(), msg.GetSize() ); + } + } + + } else { + GetActivePlatformLobby()->QueueReliableMessage( GetActivePlatformLobby()->host, idLobby::RELIABLE_HEADSET_STATE, msg.GetReadData(), msg.GetSize() ); + } + } + +} + +/* +======================== +idSessionLocal::GetNumContentPackages +======================== +*/ +int idSessionLocal::GetNumContentPackages() const { + return downloadedContent.Num(); +} + +/* +======================== +idSessionLocal::GetContentPackageID +======================== +*/ +int idSessionLocal::GetContentPackageID( int contentIndex ) const { + assert( contentIndex < MAX_CONTENT_PACKAGES ); + + if ( downloadedContent[ contentIndex ].isMounted ) { + return downloadedContent[ contentIndex ].dlcID; + } + + return 0; +} + +/* +======================== +idSessionLocal::GetContentPackagePath +======================== +*/ +const char * idSessionLocal::GetContentPackagePath( int contentIndex ) const { + assert( contentIndex < MAX_CONTENT_PACKAGES ); + + if ( downloadedContent[ contentIndex ].isMounted ) { + return downloadedContent[ contentIndex ].rootPath.c_str(); + } + + return NULL; +} + +/* +======================== +idSessionLocal::GetContentPackageIndexForID +======================== +*/ +int idSessionLocal::GetContentPackageIndexForID( int contentID ) const { + int contentIndex = -1; + + for ( int i = 0; i < downloadedContent.Num(); i++ ) { + if ( downloadedContent[i].dlcID == contentID ) { + contentIndex = i; + break; + } + } + + return contentIndex; +} + +/* +======================== +idSessionLocal::SetLobbyUserRelativeScore +======================== +*/ +void idSessionLocal::SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) { + // All platforms but 360 stub this out +} + +/* +======================== +idSessionLocal::ReadTitleStorage +======================== +*/ +void idSessionLocal::ReadTitleStorage( void * buffer, int bufferLen ) { + // https://ps3.scedev.net/projects/ps3_sdk_docs/docs/ps3-en,NP_Lookup-Reference,sceNpLookupTitleSmallStorageAsync/1 + // If the file is not on the server, this will be handled as though a file of 0 bytes were on the server. + // This means that 0 will be set to contentLength and 0 (for normal termination) will return for the return value. + // This situation can occur with problems in actual operation, so the application must be designed not to hang up even in such situations + //bufferLen = 0; + + idLib::Printf( "ReadTitleStorage: %i bytes\n", bufferLen ); + +#if !defined( ID_RETAIL ) || defined( ID_RETAIL_INTERNAL ) + if ( net_ignoreTitleStorage.GetBool() ) {//&& idLib::GetProduction() < PROD_PRODUCTION ) { + idLib::Printf( "ReadTitleStorage: *********************** IGNORING ********************\n" ); + return; + } +#endif + + //idScopedGlobalHeap everythingHereGoesInTheGlobalHeap; + + idParser parser( LEXFL_NOERRORS | LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT ); + parser.LoadMemory( ( const char* )buffer, bufferLen, "default.tss" ); + + bool valid = true; + + while ( true ) { + idToken token; + + if ( !parser.ReadToken( &token ) ) { + break; + } + + if ( token.Icmp( "netvars" ) == 0 ) { + if ( !titleStorageVars.Parse( parser ) ) { + valid = false; + break; + } + } else { + valid = false; + break; + } + } + + if ( valid ) { + titleStorageLoaded = true; + idLib::Printf( "ReadTitleStorage: SUCCESS\n" ); + titleStorageVars.Print(); + } else { + titleStorageLoaded = false; + idLib::Printf( "ReadTitleStorage: FAILED\n" ); + titleStorageVars.Clear(); + } +} + +/* +======================== +idSessionLocal::ReadDLCInfo +======================== +*/ +bool idSessionLocal::ReadDLCInfo( idDict & dlcInfo, void * buffer, int bufferLen ) { + idParser parser( LEXFL_NOERRORS | LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT ); + parser.LoadMemory( ( const char* )buffer, bufferLen, "info.txt" ); + + bool valid = true; + + while ( true ) { + idToken token; + + if ( !parser.ReadToken( &token ) ) { + break; + } + + if ( token.Icmp( "dlcInfo" ) == 0 ) { + if ( !dlcInfo.Parse( parser ) ) { + valid = false; + break; + } + } else { + valid = false; + break; + } + } + + return valid; +} + +/* +======================== +idSessionLocal::IsPlatformPartyInLobby +======================== +*/ +bool idSessionLocal::IsPlatformPartyInLobby() { + idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); + idLobby * lobby = GetActivePlatformLobby(); + + if ( user == NULL || lobby == NULL ) { + return false; + } + + if ( user->GetPartyCount() > MAX_PLAYERS || user->GetPartyCount() < 2 ) { + return false; + } + + // TODO: Implement PC + return false; +} + +/* +======================== +idSessionLocal::PrePickNewHost +This is called when we have determined that we need to pick a new host. +Call PickNewHostInternal to continue on with the host picking process. +======================== +*/ +void idSessionLocal::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: (%s)\n", lobby.GetLobbyName() ); + + if ( GetActivePlatformLobby() == NULL ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetActivePlatformLobby() == NULL (%s)\n", lobby.GetLobbyName() ); + return; + } + + // Check to see if we can migrate AT ALL + // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION) + if ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MATCH_PARTY_INVITE_PLACEHOLDER (%s)\n", lobby.GetLobbyName() ); + + // Can't migrate, shut both lobbies down, and create a new match using the original parms + GetGameStateLobby().Shutdown(); + GetGameLobby().Shutdown(); + GetPartyLobby().Shutdown(); + + // Throw up the appropriate dialog message so the player knows what happeend + if ( localState >= idSessionLocal::STATE_LOADING ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState >= idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() ); + common->Dialog().AddDialog( GDM_BECAME_HOST_GAME_STATS_DROPPED, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); + } else { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState < idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() ); + common->Dialog().AddDialog( GDM_LOBBY_BECAME_HOST_GAME, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); + } + + CreateMatch( GetActivePlatformLobby()->parms ); + + return; + } + + // Check to see if the match is searchable + if ( GetState() >= idSession::GAME_LOBBY && MatchTypeIsSearchable( GetGameLobby().parms.matchFlags ) ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MatchTypeIsSearchable (%s)\n", lobby.GetLobbyName() ); + // Searchable games migrate lobbies independently, and don't need to stay in sync + lobby.PickNewHostInternal( forceMe, inviteOldHost ); + return; + } + + // + // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status + // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs + // + + // Check to see if we should go back to a party lobby + if ( GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY ) { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() ); + // Force the party lobby to start picking a new host if we lost the game lobby host + GetPartyLobby().PickNewHostInternal( forceMe, inviteOldHost ); + + // End the game lobby, and go back to party lobby + GetGameStateLobby().Shutdown(); + GetGameLobby().Shutdown(); + SetState( GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER ); + } else { + NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() < idSessionLocal::PARTY_LOBBY && GetState() != idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() ); + if ( localState >= idSessionLocal::STATE_LOADING ) { + common->Dialog().AddDialog( GDM_HOST_QUIT, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); // The host has quit the session. Returning to the main menu. + } + + // Go back to main menu + GetGameLobby().Shutdown(); + GetGameStateLobby().Shutdown(); + GetPartyLobby().Shutdown(); + SetState( idSessionLocal::STATE_IDLE ); + } +} +/* +======================== +idSessionLocal::PreMigrateInvite +This is called just before we get invited to a migrated session +If we return false, the invite will be ignored +======================== +*/ +bool idSessionLocal::PreMigrateInvite( idLobby & lobby ) +{ + if ( GetActivePlatformLobby() == NULL ) { + return false; + } + + // Check to see if we can migrate AT ALL + // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION) + if ( !verify( ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) { + return false; // Shouldn't get invites for coop (we should make this a specific option (MATCH_ALLOW_MIGRATION)) + } + + // Check to see if the match is searchable + if ( MatchTypeIsSearchable( GetGameLobby().parms.matchFlags ) ) { + // Searchable games migrate lobbies independently, and don't need to stay in sync + return true; + } + + // + // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status + // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs + // + + if ( lobby.lobbyType != idLobby::TYPE_PARTY ) { + return false; // We shouldn't be getting invites from non party lobbies when in a non searchable game + } + + // Non placeholder Party lobbies can always migrate + if ( GetBackState() >= idSessionLocal::PARTY_LOBBY ) { + // Non searchable games go back to the party lobby + GetGameLobby().Shutdown(); + SetState( GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER ); + } + + return true; // Non placeholder Party lobby invites joinable +} + +/* +================================================================================================ +lobbyAddress_t +================================================================================================ +*/ + +/* +======================== +lobbyAddress_t::lobbyAddress_t +======================== +*/ +lobbyAddress_t::lobbyAddress_t() { + memset( &netAddr, 0, sizeof( netAddr ) ); + netAddr.type = NA_BAD; +} + +/* +======================== +lobbyAddress_t::InitFromIPandPort +======================== +*/ +void lobbyAddress_t::InitFromIPandPort( const char * ip, int port ) { + Sys_StringToNetAdr( ip, &netAddr, true ); + if ( !netAddr.port ) { + netAddr.port = port; + } +} + + +/* +======================== +lobbyAddress_t::InitFromNetadr +======================== +*/ +void lobbyAddress_t::InitFromNetadr( const netadr_t & netadr ) { + assert( netadr.type != NA_BAD ); + netAddr = netadr; +} + +/* +======================== +lobbyAddress_t::ToString +======================== +*/ +const char * lobbyAddress_t::ToString() const { + return Sys_NetAdrToString( netAddr ); +} + +/* +======================== +lobbyAddress_t::UsingRelay +======================== +*/ +bool lobbyAddress_t::UsingRelay() const { + return false; +} + +/* +======================== +lobbyAddress_t::Compare +======================== +*/ +bool lobbyAddress_t::Compare( const lobbyAddress_t & addr, bool ignoreSessionCheck ) const { + return Sys_CompareNetAdrBase( netAddr, addr.netAddr ); +} + +/* +======================== +lobbyAddress_t::WriteToMsg +======================== +*/ +void lobbyAddress_t::WriteToMsg( idBitMsg & msg ) const { + msg.WriteData( &netAddr, sizeof( netAddr ) ); +} + +/* +======================== +lobbyAddress_t::ReadFromMsg +======================== +*/ +void lobbyAddress_t::ReadFromMsg( idBitMsg & msg ) { + msg.ReadData( &netAddr, sizeof( netAddr ) ); +} + +/* +================================================================================================ +idNetSessionPort +================================================================================================ +*/ + +/* +======================== +idNetSessionPort::idNetSessionPort +======================== +*/ +idNetSessionPort::idNetSessionPort() : + forcePacketDropPrev( 0.0f ), + forcePacketDropCurr( 0.0f ) +{ +} + +/* +======================== +idNetSessionPort::InitPort +======================== +*/ +bool idNetSessionPort::InitPort( int portNumber, bool useBackend ) { + return UDP.InitForPort( portNumber ); +} + +/* +======================== +idNetSessionPort::ReadRawPacket +======================== +*/ +bool idNetSessionPort::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize ) { + bool result = UDP.GetPacket( from.netAddr, data, size, maxSize ); + + static idRandom2 random( Sys_Milliseconds() ); + if ( net_forceDrop.GetInteger() != 0 ) { + forcePacketDropCurr = random.RandomInt( 100 ); + if ( net_forceDrop.GetInteger() >= forcePacketDropCurr ) { + return false; + } + } + + return result; +} + +/* +======================== +idNetSessionPort::SendRawPacket +======================== +*/ +void idNetSessionPort::SendRawPacket( const lobbyAddress_t & to, const void * data, int size ) { + static idRandom2 random( Sys_Milliseconds() ); + if ( net_forceDrop.GetInteger() != 0 && net_forceDrop.GetInteger() >= random.RandomInt( 100 ) ) { + return; + } + assert( size <= idPacketProcessor::MAX_FINAL_PACKET_SIZE ); + + UDP.SendPacket( to.netAddr, data, size ); +} + +/* +======================== +idNetSessionPort::IsOpen +======================== +*/ +bool idNetSessionPort::IsOpen() { + return UDP.IsOpen(); +} + +/* +======================== +idNetSessionPort::Close +======================== +*/ +void idNetSessionPort::Close() { + UDP.Close(); +} + +/* +================================================================================================ +Commands +================================================================================================ +*/ + +//==================================================================================== + +CONSOLE_COMMAND( voicechat_mute, "TEMP", 0 ) { + if ( args.Argc() != 2 ) { + idLib::Printf( "Usage: voicechat_mute \n" ); + return; + } + + int i = atoi( args.Argv( 1 ) ); + session->ToggleLobbyUserVoiceMute( session->GetActivePlatformLobbyBase().GetLobbyUserIdByOrdinal( i ) ); +} + +/* +======================== +force_disconnect_all +======================== +*/ +CONSOLE_COMMAND( force_disconnect_all, "force disconnect on all users", 0 ) { + session->GetSignInManager().RemoveAllLocalUsers(); +} + +/* +======================== +void Net_DebugOutputSignedInUsers_f +======================== +*/ +void Net_DebugOutputSignedInUsers_f( const idCmdArgs &args ) { + session->GetSignInManager().DebugOutputLocalUserInfo(); +} +idCommandLink Net_DebugOutputSignedInUsers( "net_debugOutputSignedInUsers", Net_DebugOutputSignedInUsers_f, "Outputs all the local users and other debugging information from the sign in manager" ); + +/* +======================== +void Net_RemoveUserFromLobby_f +======================== +*/ +void Net_RemoveUserFromLobby_f( const idCmdArgs &args ) { + if ( args.Argc() > 1 ) { + int localUserNum = atoi( args.Argv( 1 ) ); + if ( localUserNum < session->GetSignInManager().GetNumLocalUsers() ) { + session->GetSignInManager().RemoveLocalUserByIndex( localUserNum ); + } else { + idLib::Printf( "This user is not in the lobby\n" ); + } + } else { + idLib::Printf( "Usage: net_RemoveUserFromLobby \n" ); + } +} + +idCommandLink Net_RemoveUserFromLobby( "net_removeUserFromLobby", Net_RemoveUserFromLobby_f, "Removes the given user from the lobby" ); + +/* +======================== +Net_dropClient +======================== +*/ +CONSOLE_COMMAND( Net_DropClient, "Drop a client", 0 ) { + if ( args.Argc() < 3 ) { + idLib::Printf( "usage: Net_DropClient [] 0/default: drop from game, 1: drop from party, otherwise drop from both\n" ); + return; + } + int lobbyType = 0; + if ( args.Argc() > 2 ) { + lobbyType = atoi( args.Argv( 2 ) ); + } + session->DropClient( atoi( args.Argv(1) ), lobbyType ); +} + +/* +======================== +idSessionLocal::DropClient +======================== +*/ +void idSessionLocal::DropClient( int peerNum, int session ) { + if ( session == 1 || session >= 2 ) { + GetPartyLobby().DisconnectPeerFromSession( peerNum ); + } + if ( session == 0 || session >= 2 ) { + GetGameLobby().DisconnectPeerFromSession( peerNum ); + } +} + +/* +======================== +idSessionLocal::ListServersCommon +======================== +*/ +void idSessionLocal::ListServersCommon() { + netadr_t broadcast; + memset( &broadcast, 0, sizeof( broadcast ) ); + broadcast.type = NA_BROADCAST; + broadcast.port = net_port.GetInteger(); + + lobbyAddress_t address; + address.InitFromNetadr( broadcast ); + + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg msg( buffer, sizeof( buffer ) ); + + // Add the current version info to the query + const unsigned long localChecksum = NetGetVersionChecksum(); + + NET_VERBOSE_PRINT( "ListServers: Hash checksum: %i, broadcasting to: %s\n", localChecksum, address.ToString() ); + + msg.WriteLong( localChecksum ); + + GetPort(); + // Send the query as a broadcast + GetPartyLobby().SendConnectionLess( address, idLobby::OOB_MATCH_QUERY, msg.GetReadData(), msg.GetSize() ); +} + +/* +======================== +idSessionLocal::HandleDedicatedServerQueryRequest +======================== +*/ +void idSessionLocal::HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) { + NET_VERBOSE_PRINT( "HandleDedicatedServerQueryRequest from %s\n", remoteAddr.ToString() ); + + bool canJoin = true; + + const unsigned long localChecksum = NetGetVersionChecksum(); + const unsigned long remoteChecksum = msg.ReadLong(); + + if ( remoteChecksum != localChecksum ) { + NET_VERBOSE_PRINT( "HandleServerQueryRequest: Invalid version from %s\n", remoteAddr.ToString() ); + canJoin = false; + } + + // Make sure we are the host of this party session + if ( !GetPartyLobby().IsHost() ) { + NET_VERBOSE_PRINT( "HandleServerQueryRequest: Not host of party\n" ); + canJoin = false; + } + + // Make sure there is a session active + if ( GetActivePlatformLobby() == NULL ) { + canJoin = false; + } + + // Make sure we have enough free slots + if ( GetPartyLobby().NumFreeSlots() == 0 || GetGameLobby().NumFreeSlots() == 0 ) { + NET_VERBOSE_PRINT( "No free slots\n" ); + canJoin = false; + } + + if ( MatchTypeInviteOnly( GetPartyLobby().parms.matchFlags ) ) { + canJoin = false; + } + + // Buffer to hold reply msg + byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; + idBitMsg retmsg( buffer, sizeof( buffer ) ); + + idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser(); + + if ( masterUser == NULL && !net_headlessServer.GetBool() ) { + canJoin = false; + } + + // Send the info about this game session to the caller + retmsg.WriteBool( canJoin ); + + if ( canJoin ) { + serverInfo_t serverInfo; + serverInfo.joinable = ( session->GetState() >= idSession::LOADING ); + + if ( !net_headlessServer.GetBool() ) { + serverInfo.serverName = masterUser->GetGamerTag(); + } + + if ( GetGameLobby().IsLobbyActive() ) { + serverInfo.gameMap = GetGameLobby().parms.gameMap; + serverInfo.gameMode = GetGameLobby().parms.gameMode; + } else { + serverInfo.gameMode = -1; + } + + serverInfo.numPlayers = GetActivePlatformLobby()->GetNumLobbyUsers(); + serverInfo.maxPlayers = GetActivePlatformLobby()->parms.numSlots; + serverInfo.Write( retmsg ); + + for ( int i = 0; i < GetActivePlatformLobby()->GetNumLobbyUsers(); i++ ) { + retmsg.WriteString( GetActivePlatformLobby()->GetLobbyUserName( GetActivePlatformLobby()->GetLobbyUserIdByOrdinal( i ) ) ); + } + } + + // Send it + GetPartyLobby().SendConnectionLess( remoteAddr, idLobby::OOB_MATCH_QUERY_ACK, retmsg.GetReadData(), retmsg.GetSize() ); +} + +/* +======================== +idSessionLocal::HandleDedicatedServerQueryAck +======================== +*/ +void idSessionLocal::HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) { + NET_VERBOSE_PRINT( "HandleDedicatedServerQueryAck from %s\n", remoteAddr.ToString() ); + dedicatedServerSearch->HandleQueryAck( remoteAddr, msg ); +} + +/* +======================== +idSessionLocal::ServerPlayerList +======================== +*/ +const idList< idStr > * idSessionLocal::ServerPlayerList( int i ) { + return NULL; +} + +/* +======================== +lobbyUserID_t::Serialize +======================== +*/ +void lobbyUserID_t::Serialize( idSerializer & ser ) { + localUserHandle.Serialize( ser ); + ser.Serialize( lobbyType ); +} diff --git a/neo/sys/sys_session_local.h b/neo/sys/sys_session_local.h new file mode 100644 index 00000000..468db95a --- /dev/null +++ b/neo/sys/sys_session_local.h @@ -0,0 +1,700 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + + +#undef private +#undef protected + +#include "win32/win_achievements.h" +#include "win32/win_signin.h" + +#include "sys_lobby_backend.h" +#include "sys_lobby.h" + +class idSaveGameProcessorNextMap; +class idSaveGameProcessorSaveGame; +class idSaveGameProcessorLoadGame; +class idSaveGameProcessorDelete; +class idSaveGameProcessorEnumerateGames; + +/* +================================================ +idLobbyStub +================================================ +*/ +class idLobbyStub : public idLobbyBase { +public: + virtual bool IsHost() const { return false; } + virtual bool IsPeer() const { return false; } + virtual bool HasActivePeers() const { return false; } + virtual int GetNumLobbyUsers() const { return 0; } + virtual int GetNumActiveLobbyUsers() const { return 0; } + virtual bool IsLobbyUserConnected( int index ) const { return false; } + + virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const { return lobbyUserID_t(); } + virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const { return -1; } + + virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) ) {} + virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) {} + virtual void SendReliableToHost( int type, idBitMsg & msg ) {} + + virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const { return "INVALID"; } + virtual void KickLobbyUser( lobbyUserID_t lobbyUserID ) {} + virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const { return false; } + virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const { return false; } + virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const { return false; } + virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ) {} + + virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const { return 0; } + virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const { return false; } + virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const { return false; } + virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const { return 0; } + virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const { return 0; } + virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const { return 0; } + virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) { return false; } + virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const { return 0; } + virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) { return NULL; } + virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) { return NULL; } + virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const { return 0; } + + virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const { return -1; } + + virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const { return 0; } + virtual int PeerIndexForHost() const { return -1; } + + virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ) { return lobbyUserID_t(); } + virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ) {} + virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const { return false; } + + virtual const char * GetHostUserName() const { return "INVALID"; } + virtual const idMatchParameters & GetMatchParms() const { return fakeParms; } + virtual bool IsLobbyFull() const { return false; } + + virtual bool EnsureAllPeersHaveBaseState() { return false; } + virtual bool AllPeersInGame() const { return false; } + virtual int GetNumConnectedPeers() const { return 0; } + virtual int GetNumConnectedPeersInGame() const { return 0; } + virtual int PeerIndexOnHost() const { return -1; } + virtual bool IsPeerDisconnected( int peerIndex ) const { return false; } + + virtual bool AllPeersHaveStaleSnapObj( int objId ) { return false; } + virtual bool AllPeersHaveExpectedSnapObj( int objId ) { return false; } + virtual void RefreshSnapObj( int objId ) {} + virtual void MarkSnapObjDeleted( int objId ) {} + virtual void AddSnapObjTemplate( int objID, idBitMsg & msg ) {} + + virtual void DrawDebugNetworkHUD() const {} + virtual void DrawDebugNetworkHUD2() const {} + virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) {} +private: + idMatchParameters fakeParms; +}; + +/* +================================================ +idSessionLocal +================================================ +*/ +class idSessionLocal : public idSession { + friend class idLeaderboards; + friend class idStatsSession; + friend class idLobbyBackend360; + friend class idLobbyBackendPS3; + friend class idSessionLocalCallbacks; + friend class idPsnAsyncSubmissionLookupPS3_TitleStorage; + friend class idNetSessionPort; + friend class lobbyAddress_t; + +protected: + //===================================================================================================== + // Mixed Common/Platform enums/structs + //===================================================================================================== + + // Overall state of the session + enum state_t { + STATE_PRESS_START, // We are at press start + STATE_IDLE, // We are at the main menu + STATE_PARTY_LOBBY_HOST, // We are in the party lobby menu as host + STATE_PARTY_LOBBY_PEER, // We are in the party lobby menu as a peer + STATE_GAME_LOBBY_HOST, // We are in the game lobby as a host + STATE_GAME_LOBBY_PEER, // We are in the game lobby as a peer + STATE_GAME_STATE_LOBBY_HOST, // We are in the game state lobby as a host + STATE_GAME_STATE_LOBBY_PEER, // We are in the game state lobby as a peer + STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, // We are creating a party lobby, and will move to that state when done + STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, // We are creating a game lobby, and will move to that state when done + STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, // We are creating a game state lobby, and will move to that state when done + STATE_FIND_OR_CREATE_MATCH, + STATE_CONNECT_AND_MOVE_TO_PARTY, + STATE_CONNECT_AND_MOVE_TO_GAME, + STATE_CONNECT_AND_MOVE_TO_GAME_STATE, + STATE_BUSY, // Doing something internally like a QoS/bandwidth challenge + + // These are last, so >= STATE_LOADING tests work + STATE_LOADING, // We are loading the map, preparing to go into a match + STATE_INGAME, // We are currently in a match + NUM_STATES + }; + + enum connectType_t { + CONNECT_NONE = 0, + CONNECT_DIRECT = 1, + CONNECT_FIND_OR_CREATE = 2, + }; + + enum pendingInviteMode_t { + PENDING_INVITE_NONE = 0, // No invite waiting + PENDING_INVITE_WAITING = 1, // Invite is waiting + PENDING_SELF_INVITE_WAITING = 2, // We invited ourselves to a match + }; + + struct contentData_t { + bool isMounted; + idStrStatic<128> displayName; + idStrStatic< MAX_OSPATH > packageFileName; + idStrStatic< MAX_OSPATH > rootPath; + int dlcID; + }; + +public: + idSessionLocal(); + virtual ~idSessionLocal(); + + void InitBaseState(); + + virtual bool IsPlatformPartyInLobby(); + + // Downloadable Content + virtual int GetNumContentPackages() const; + virtual int GetContentPackageID( int contentIndex ) const; + virtual const char * GetContentPackagePath( int contentIndex ) const; + virtual int GetContentPackageIndexForID( int contentID ) const; + + virtual bool GetSystemMarketplaceHasNewContent() const { return marketplaceHasNewContent; } + virtual void SetSystemMarketplaceHasNewContent( bool hasNewContent ) { marketplaceHasNewContent = hasNewContent; } + + // Lobby management + virtual void CreatePartyLobby( const idMatchParameters & parms_ ); + virtual void FindOrCreateMatch( const idMatchParameters & parms ); + virtual void CreateMatch( const idMatchParameters & parms_ ); + virtual void CreateGameStateLobby( const idMatchParameters & parms_ ); + + virtual void UpdatePartyParms( const idMatchParameters & parms_ ); + virtual void UpdateMatchParms( const idMatchParameters & parms_ ); + virtual void StartMatch(); + virtual void SetSessionOption( sessionOption_t option ) { sessionOptions |= option; } + virtual void ClearSessionOption( sessionOption_t option ) { sessionOptions &= ~option; } + virtual sessionState_t GetBackState(); + virtual void Cancel(); + virtual void MoveToPressStart(); + virtual void FinishDisconnect(); + virtual bool ShouldShowMigratingDialog() const; // Note this is not in sys_session.h + virtual bool IsCurrentLobbyMigrating() const; + virtual bool IsLosingConnectionToHost() const; + + // Migration + virtual bool WasMigrationGame() const; + virtual bool ShouldRelaunchMigrationGame() const; + virtual bool GetMigrationGameData( idBitMsg & msg, bool reading ); + virtual bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ); + + virtual bool WasGameLobbyCoalesced() const { return gameLobbyWasCoalesced; } + + // Misc + virtual int GetLoadingID() { return loadingID; } + virtual bool IsAboutToLoad() const { return GetGameLobby().IsLobbyActive() && GetGameLobby().startLoadingFromHost; } + virtual bool GetMatchParamUpdate( int &peer, int &msg ); + virtual int GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ); + virtual void EndMatch( bool premature=false ); // Meant for host to end match gracefully, go back to lobby, tally scores, etc + virtual void MatchFinished(); // this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time + virtual void QuitMatch(); // Meant for host or peer to quit the match before it ends, will instigate host migration, etc + virtual void QuitMatchToTitle(); // Will forcefully quit the match and return to the title screen. + virtual void LoadingFinished(); + virtual void Pump(); + virtual void ProcessSnapAckQueue(); + + virtual sessionState_t GetState() const; + virtual const char * GetStateString() const ; + + virtual void SendUsercmds( idBitMsg & msg ); + virtual void SendSnapshot( idSnapShot & ss ); + virtual const char * GetPeerName( int peerNum ); + + virtual const char * GetLocalUserName( int i ) const { return signInManager->GetLocalUserByIndex( i )->GetGamerTag(); } + virtual void UpdateSignInManager(); + virtual idPlayerProfile * GetProfileFromMasterLocalUser(); + + virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ); + virtual bool PreMigrateInvite( idLobby & lobby ); + + //===================================================================================================== + // Title Storage Vars + //===================================================================================================== + virtual float GetTitleStorageFloat( const char * name, float defaultFloat ) const { return titleStorageVars.GetFloat( name, defaultFloat ); } + virtual int GetTitleStorageInt( const char * name, int defaultInt ) const { return titleStorageVars.GetInt( name, defaultInt ); } + virtual bool GetTitleStorageBool( const char * name, bool defaultBool ) const { return titleStorageVars.GetBool( name, defaultBool ); } + virtual const char * GetTitleStorageString( const char * name, const char * defaultString ) const { return titleStorageVars.GetString( name, defaultString ); } + + virtual bool GetTitleStorageFloat( const char * name, float defaultFloat, float & out ) const { return titleStorageVars.GetFloat( name, defaultFloat, out ); } + virtual bool GetTitleStorageInt( const char * name, int defaultInt, int & out ) const { return titleStorageVars.GetInt( name, defaultInt, out ); } + virtual bool GetTitleStorageBool( const char * name, bool defaultBool, bool & out ) const { return titleStorageVars.GetBool( name, defaultBool, out ); } + virtual bool GetTitleStorageString( const char * name, const char * defaultString, const char ** out ) const { return titleStorageVars.GetString( name, defaultString, out ); } + + virtual bool IsTitleStorageLoaded() { return titleStorageLoaded; } + + //===================================================================================================== + // Voice chat + //===================================================================================================== + virtual voiceState_t GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID ); + virtual voiceStateDisplay_t GetDisplayStateFromVoiceState( voiceState_t voiceState ) const; + virtual void ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID ); + virtual void SetActiveChatGroup( int groupIndex ); + virtual void UpdateMasterUserHeadsetState(); + + //===================================================================================================== + // Bandwidth / QoS checking + //===================================================================================================== + virtual bool StartOrContinueBandwidthChallenge( bool forceStart ); + virtual void DebugSetPeerSnaprate( int peerIndex, int snapRateMS ); + virtual float GetIncomingByteRate(); + + //===================================================================================================== + // Invites + //===================================================================================================== + virtual void HandleBootableInvite( int64 lobbyId = 0 ) = 0; + virtual void ClearBootableInvite() = 0; + virtual void ClearPendingInvite() = 0; + virtual bool HasPendingBootableInvite() = 0; + virtual void SetDiscSwapMPInvite( void * parm ) = 0; // call to request a discSwap multiplayer invite + virtual void * GetDiscSwapMPInviteParms() = 0; + virtual bool IsDiscSwapMPInviteRequested() const { return inviteInfoRequested; } + + bool GetFlushedStats() { return flushedStats; } + void SetFlushedStats( bool _flushedStats ) { flushedStats = _flushedStats; } + + //===================================================================================================== + // Notifications + //===================================================================================================== + // This is called when a LocalUser is signed in/out + virtual void OnLocalUserSignin( idLocalUser * user ); + virtual void OnLocalUserSignout( idLocalUser * user ); + + // This is called when the master LocalUser is signed in/out, these are called after OnLocalUserSignin/out() + virtual void OnMasterLocalUserSignout(); + virtual void OnMasterLocalUserSignin(); + + // After a local user has signed in and their profile has loaded + virtual void OnLocalUserProfileLoaded( idLocalUser * user ); + + //===================================================================================================== + // Platform specific (different platforms implement these differently) + //===================================================================================================== + + virtual void Initialize() = 0; + virtual void Shutdown() = 0; + + virtual void InitializeSoundRelatedSystems() = 0; + virtual void ShutdownSoundRelatedSystems() = 0; + + virtual void PlatformPump() = 0; + + virtual void InviteFriends() = 0; + virtual void InviteParty() = 0; + virtual void ShowPartySessions() = 0; + + virtual bool ProcessInputEvent( const sysEvent_t * ev ) = 0; + + // Play with Friends server listing + virtual int NumServers() const = 0; + virtual void ListServers( const idCallback & callback ) = 0; + virtual void ListServersCommon(); + virtual void CancelListServers() = 0; + virtual void ConnectToServer( int i ) = 0; + virtual const serverInfo_t * ServerInfo( int i ) const = 0; + virtual const idList< idStr > * ServerPlayerList( int i ); + virtual void ShowServerGamerCardUI( int i ) = 0; + + virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) = 0; + virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) = 0; + + // System UI + virtual bool IsSystemUIShowing() const = 0; + virtual void SetSystemUIShowing( bool show ) = 0; + + virtual void ShowSystemMarketplaceUI() const = 0; + virtual void ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ) = 0; + + // Leaderboards + virtual void LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment = NULL ) = 0; + virtual void LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ) = 0; + virtual void LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ) = 0; + + // Scoring (currently just for TrueSkill) + virtual void SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) = 0; + + virtual void LeaderboardFlush() = 0; + + //=====================================================================================================i' + // Savegames + //===================================================================================================== + virtual saveGameHandle_t SaveGameSync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ); + virtual saveGameHandle_t SaveGameAsync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ); + virtual saveGameHandle_t LoadGameSync( const char * name, saveFileEntryList_t & files ); + virtual saveGameHandle_t EnumerateSaveGamesSync(); + virtual saveGameHandle_t EnumerateSaveGamesAsync(); + virtual saveGameHandle_t DeleteSaveGameSync( const char * name ); + virtual saveGameHandle_t DeleteSaveGameAsync( const char * name ); + + virtual bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const { return saveGameManager->IsSaveGameCompletedFromHandle( handle ); } + virtual void CancelSaveGameWithHandle( const saveGameHandle_t & handle ) { GetSaveGameManager().CancelWithHandle( handle ); } + virtual const saveGameDetailsList_t & GetEnumeratedSavegames() const { return saveGameManager->GetEnumeratedSavegames(); } + virtual bool IsEnumerating() const; + virtual saveGameHandle_t GetEnumerationHandle() const; + virtual void SetCurrentSaveSlot( const char * slotName ) { currentSaveSlot = slotName; } + virtual const char * GetCurrentSaveSlot() const { return currentSaveSlot.c_str(); } + + // Notifications + void OnSaveCompleted( idSaveLoadParms * parms ); + void OnLoadCompleted( idSaveLoadParms * parms ); + void OnDeleteCompleted( idSaveLoadParms * parms ); + void OnEnumerationCompleted( idSaveLoadParms * parms ); + + // Error checking + virtual bool IsDLCAvailable( const char * mapName ); + virtual bool LoadGameCheckDiscNumber( idSaveLoadParms & parms ); + bool LoadGameCheckDescriptionFile( idSaveLoadParms & parms ); + + // Downloadable Content + virtual void EnumerateDownloadableContent() = 0; + + void DropClient( int peerNum, int session ); + +protected: + + float GetUpstreamDropRate() { return upstreamDropRate; } + float GetUpstreamQueueRate() { return upstreamQueueRate; } + int GetQueuedBytes() { return queuedBytes; } + + //===================================================================================================== + // Common functions (sys_session_local.cpp) + //===================================================================================================== + void HandleLobbyControllerState( int lobbyType ); + virtual void UpdatePendingInvite(); + bool HandleState(); + + // The party and game lobby are the two platform lobbies that notify the backends (Steam/PSN/LIVE of changes) + idLobby & GetPartyLobby() { return partyLobby; } + const idLobby & GetPartyLobby() const { return partyLobby; } + idLobby & GetGameLobby() { return gameLobby; } + const idLobby & GetGameLobby() const { return gameLobby; } + + // Game state lobby is the lobby used while in-game. It is so the dedicated server can host this lobby + // and have all platform clients join. It does NOT notify the backends of changes, it's purely for the dedicated + // server to be able to host the in-game lobby. + // Generally, you would call GetActingGameStateLobby. If we are not using game state lobby, GetActingGameStateLobby will return GetGameLobby insread. + idLobby & GetGameStateLobby() { return gameStateLobby; } + const idLobby & GetGameStateLobby() const { return gameStateLobby; } + + idLobby & GetActingGameStateLobby(); + const idLobby & GetActingGameStateLobby() const; + + // GetActivePlatformLobby will return either the game or party lobby, it won't return the game state lobby + // This function is generally used for menus, in-game code should refer to GetActingGameStateLobby + idLobby * GetActivePlatformLobby(); + const idLobby * GetActivePlatformLobby() const; + + idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType ); + + virtual idLobbyBase & GetPartyLobbyBase() { return partyLobby; } + virtual idLobbyBase & GetGameLobbyBase() { return gameLobby; } + virtual idLobbyBase & GetActingGameStateLobbyBase() { return GetActingGameStateLobby(); } + + virtual idLobbyBase & GetActivePlatformLobbyBase(); + virtual idLobbyBase & GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID ); + + void SetState( state_t newState ); + bool HandlePackets(); + + void HandleVoiceRestrictionDialog(); + void SetDroppedByHost( bool dropped ) { droppedByHost = dropped; } + bool GetDroppedByHost() { return droppedByHost; } + +public: + int storedPeer; + int storedMsgType; + +protected: + static const char * stateToString[ NUM_STATES ]; + + state_t localState; + uint32 sessionOptions; + + connectType_t connectType; + int connectTime; + + idLobby partyLobby; + idLobby gameLobby; + idLobby gameStateLobby; + idLobbyStub stubLobby; // We use this when we request the active lobby when we are not in a lobby (i.e at press start) + + int currentID; // The host used this to send out a unique id to all users so we can identify them + + class idVoiceChatMgr * voiceChat; + int lastVoiceSendtime; + bool hasShownVoiceRestrictionDialog; + + pendingInviteMode_t pendingInviteMode; + int pendingInviteDevice; + lobbyConnectInfo_t pendingInviteConnectInfo; + + bool isSysUIShowing; + + idDict titleStorageVars; + bool titleStorageLoaded; + + int showMigratingInfoStartTime; + + int nextGameCoalesceTime; + bool gameLobbyWasCoalesced; + int numFullSnapsReceived; + + bool flushedStats; + + int loadingID; + + bool inviteInfoRequested; + + idSaveGameProcessorSaveFiles * processorSaveFiles; + idSaveGameProcessorLoadFiles * processorLoadFiles; + idSaveGameProcessorDelete * processorDelete; + idSaveGameProcessorEnumerateGames * processorEnumerate; + + idStr currentSaveSlot; + saveGameHandle_t enumerationHandle; + + //------------------------ + // State functions + //------------------------ + bool State_Party_Lobby_Host(); + bool State_Party_Lobby_Peer(); + bool State_Game_Lobby_Host(); + bool State_Game_Lobby_Peer(); + bool State_Game_State_Lobby_Host(); + bool State_Game_State_Lobby_Peer(); + bool State_Loading(); + bool State_InGame(); + bool State_Find_Or_Create_Match(); + bool State_Create_And_Move_To_Party_Lobby(); + bool State_Create_And_Move_To_Game_Lobby(); + bool State_Create_And_Move_To_Game_State_Lobby(); + + bool State_Connect_And_Move_To_Party(); + bool State_Connect_And_Move_To_Game(); + bool State_Connect_And_Move_To_Game_State(); + bool State_Finalize_Connect(); + bool State_Busy(); + + // ----------------------- + // Downloadable Content + // ----------------------- + static const int MAX_CONTENT_PACKAGES = 16; + + + idStaticList downloadedContent; + bool marketplaceHasNewContent; + + class idQueuePacket { + public: + byte data[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ]; + lobbyAddress_t address; + int size; + int time; + bool dedicated; + idQueueNode queueNode; + }; + + idBlockAlloc< idQueuePacket, 64, TAG_NETWORKING > packetAllocator; + idQueue< idQueuePacket,&idQueuePacket::queueNode > sendQueue; + idQueue< idQueuePacket,&idQueuePacket::queueNode > recvQueue; + + float upstreamDropRate; // instant rate in B/s at which we are dropping packets due to simulated upstream saturation + int upstreamDropRateTime; + + float upstreamQueueRate; // instant rate in B/s at which queued packets are coming out after local buffering due to upstream saturation + int upstreamQueueRateTime; + + int queuedBytes; + + int waitingOnGameStateMembersToLeaveTime; + int waitingOnGameStateMembersToJoinTime; + + void TickSendQueue(); + + void QueuePacket( idQueue< idQueuePacket,&idQueuePacket::queueNode > & queue, int time, const lobbyAddress_t & to, const void * data, int size, bool dedicated ); + bool ReadRawPacketFromQueue( int time, lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ); + + void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool dedicated ); + bool ReadRawPacket( lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ); + + void ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ); + void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ); + + void WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ); + void SendLeaderboardStatsToPlayer( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats ); + void RecvLeaderboardStatsForPlayer( idBitMsg & msg ); + + const leaderboardDefinition_t * ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ); + bool RequirePersistentMaster(); + + virtual idNetSessionPort & GetPort( bool dedicated = false ) = 0; + virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; + virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; + virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; + virtual void DestroyLobbyBackend( idLobbyBackend * lobby ) = 0; + virtual void PumpLobbies() = 0; + virtual bool GetLobbyAddressFromNetAddress( const netadr_t & netAddr, lobbyAddress_t & outAddr ) const = 0; + virtual bool GetNetAddressFromLobbyAddress( const lobbyAddress_t & lobbyAddress, netadr_t & outNetAddr ) const = 0; + + void HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ); + void HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ); + + + void ClearMigrationState(); + // this is called when the mathc is over and returning to lobby + void EndMatchInternal( bool premature=false ); + // this is called when the game finished and we are in the end match recap + void MatchFinishedInternal(); + void EndMatchForMigration(); + + void MoveToPressStart( gameDialogMessages_t msg ); + + // Voice chat + void SendVoiceAudio(); + void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ); + void SetVoiceGroupsToTeams(); + void ClearVoiceGroups(); + + // All the new functions going here for now until it can all be cleaned up + void StartSessions(); + void EndSessions(); + void SetLobbiesAreJoinable( bool joinable ); + void MoveToMainMenu(); // End all session (async), and return to IDLE state + bool WaitOnLobbyCreate( idLobby & lobby ); + bool DetectDisconnectFromService( bool cancelAndShowMsg ); + void HandleConnectionFailed( idLobby & lobby, bool wasFull ); + void ConnectToNextSearchResultFailed( idLobby & lobby ); + bool HandleConnectAndMoveToLobby( idLobby & lobby ); + + void VerifySnapshotInitialState(); + + void ComputeNextGameCoalesceTime(); + + void StartLoading(); + + bool ShouldHavePartyLobby(); + void ValidateLobbies(); + void ValidateLobby( idLobby & lobby ); + + void ReadTitleStorage( void * buffer, int bufferLen ); + + bool ReadDLCInfo( idDict & dlcInfo, void * buffer, int bufferLen ); + + idSessionCallbacks * sessionCallbacks; + + int offlineTransitionTimerStart; + + bool droppedByHost; + + +}; + +/* +======================== +idSessionLocalCallbacks + The more the idLobby class needs to call back into this class, the more likely we're doing something wrong, and there is a better way. +======================== +*/ +class idSessionLocalCallbacks : public idSessionCallbacks { +public: + idSessionLocalCallbacks( idSessionLocal * sessionLocal_ ) { sessionLocal = sessionLocal_; } + + virtual idLobby & GetPartyLobby() { return sessionLocal->GetPartyLobby(); } + virtual idLobby & GetGameLobby() { return sessionLocal->GetGameLobby(); } + virtual idLobby & GetActingGameStateLobby() { return sessionLocal->GetActingGameStateLobby(); } + virtual idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType ) { return sessionLocal->GetLobbyFromType( lobbyType ); } + + virtual int GetUniquePlayerId() const { return sessionLocal->currentID++; } + virtual idSignInManagerBase & GetSignInManager() { return *sessionLocal->signInManager; } + virtual void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool useDirectPort ) { sessionLocal->SendRawPacket( to, data, size, useDirectPort ); } + + virtual bool BecomingHost( idLobby & lobby ); + virtual void BecameHost( idLobby & lobby ); + virtual bool BecomingPeer( idLobby & lobby ); + virtual void BecamePeer( idLobby & lobby ); + + virtual void FailedGameMigration( idLobby & lobby ); + virtual void MigrationEnded( idLobby & lobby ); + + virtual void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ); + virtual uint32 GetSessionOptions() { return sessionLocal->sessionOptions; } + + virtual bool AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const; + + virtual idSession::sessionState_t GetState() const { return sessionLocal->GetState(); } + + virtual void ClearMigrationState() { GetPartyLobby().ResetAllMigrationState(); GetGameLobby().ResetAllMigrationState(); } + + virtual void EndMatchInternal( bool premature=false ) { sessionLocal->EndMatchInternal( premature ); } + + virtual void RecvLeaderboardStats( idBitMsg & msg ); + + virtual void ReceivedFullSnap(); + + virtual void LeaveGameLobby(); + + virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ); + virtual bool PreMigrateInvite( idLobby & lobby ); + + virtual void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) { sessionLocal->HandleOobVoiceAudio( from, msg ); } + + virtual void ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk ); + + virtual idVoiceChatMgr * GetVoiceChat() { return sessionLocal->voiceChat; } + + virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ); + virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ); + + virtual void HandlePeerMatchParamUpdate( int peer, int msg ); + + virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ); + virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ); + virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ); + virtual void DestroyLobbyBackend( idLobbyBackend * lobby ); + + idSessionLocal * sessionLocal; +}; + diff --git a/neo/sys/sys_session_savegames.cpp b/neo/sys/sys_session_savegames.cpp new file mode 100644 index 00000000..79a422f7 --- /dev/null +++ b/neo/sys/sys_session_savegames.cpp @@ -0,0 +1,864 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_savegame.h" +#include "sys_session_local.h" +#include "sys_session_savegames.h" + + +extern idCVar saveGame_verbose; + +idCVar savegame_error( "savegame_error", "0", CVAR_INTEGER, "Combination of bits that will simulate and error, see 'savegamePrintErrors'. 0 = no error" ); + +void OutputDetailList( const saveGameDetailsList_t & savegameList ); + +#pragma region PROCESSORS + +/* +================================================================================================ + +idSaveGameProcessorLoadFiles + +================================================================================================ +*/ + +/* +======================== +idSaveGameProcessorLoadFiles::InitLoadFiles +======================== +*/ +bool idSaveGameProcessorLoadFiles::InitLoadFiles( const char * folder_, const saveFileEntryList_t & files, idSaveGameManager::packageType_t type ) { + if ( !idSaveGameProcessor::Init() ) { + return false; + } + + parms.directory = AddSaveFolderPrefix( folder_, type ); + parms.description.slotName = folder_; + parms.mode = SAVEGAME_MBF_LOAD; + + for ( int i = 0; i < files.Num(); ++i ) { + parms.files.Append( files[i] ); + } + + return true; +} + +/* +======================== +idSaveGameProcessorLoadFiles::Process +======================== +*/ +bool idSaveGameProcessorLoadFiles::Process() { + // Platform-specific impl + // This will populate an idFile_Memory with the contents of the save game + // This will not initialize the game, only load the file from the file-system + Sys_ExecuteSavegameCommandAsync( &parms ); + return false; +} + +/* +================================================================================================ + +idSaveGameProcessorDelete + +================================================================================================ +*/ + +/* +======================== +idSaveGameProcessorDelete::Init +======================== +*/ +bool idSaveGameProcessorDelete::InitDelete( const char * folder_, idSaveGameManager::packageType_t type ) { + if ( !idSaveGameProcessor::Init() ) { + return false; + } + + parms.description.slotName = folder_; + parms.directory = AddSaveFolderPrefix( folder_, type ); + parms.mode = SAVEGAME_MBF_DELETE_FOLDER; + + return true; +} + +/* +======================== +idSaveGameProcessorDelete::Process +======================== +*/ +bool idSaveGameProcessorDelete::Process() { + // Platform-specific impl + // This will populate an idFile_Memory with the contents of the save game + // This will not initialize the game, only load the file from the file-system + Sys_ExecuteSavegameCommandAsync( &parms ); + + return false; +} + +/* +================================================================================================ + +idSaveGameProcessorSaveFiles + +================================================================================================ +*/ + +/* +======================== +idSaveGameProcessorSaveFiles::InitSave +======================== +*/ +bool idSaveGameProcessorSaveFiles::InitSave( const char * folder, const saveFileEntryList_t & files, const idSaveGameDetails & descriptionForPS3, idSaveGameManager::packageType_t type ) { + if ( !idSaveGameProcessor::Init() ) { + return false; + } + + if ( files.Num() == 0 ) { + idLib::Warning( "No files to save." ); + return false; + } + + // Setup save system + parms.directory = AddSaveFolderPrefix( folder, type ); + parms.mode = SAVEGAME_MBF_SAVE; // do NOT delete the existing files + for ( int i = 0; i < files.Num(); ++i ) { + parms.files.Append( files[i] ); + } + + + this->parms.description = descriptionForPS3; + parms.description.slotName = folder; + + return true; +} + +/* +======================== +idSaveGameProcessorSaveFiles::Process +======================== +*/ +bool idSaveGameProcessorSaveFiles::Process() { + // Platform-specific implementation + // This will start a worker thread for async operation. + // It will always signal when it's completed. + Sys_ExecuteSavegameCommandAsync( &parms ); + + return false; +} + +/* +================================================================================================ +idSaveGameProcessorEnumerateGames +================================================================================================ +*/ + +/* +======================== +idSaveGameProcessorEnumerateGames::Process +======================== +*/ +bool idSaveGameProcessorEnumerateGames::Process() { + parms.mode = SAVEGAME_MBF_ENUMERATE | SAVEGAME_MBF_READ_DETAILS; + + // Platform-specific implementation + // This will start a worker thread for async operation. + // It will always signal when it's completed. + Sys_ExecuteSavegameCommandAsync( &parms ); + + return false; +} + +#pragma endregion + +/* +======================== +idSessionLocal::SaveGameSync +======================== +*/ +saveGameHandle_t idSessionLocal::SaveGameSync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) { + saveGameHandle_t handle = 0; + + // serialize the description file behind their back... + saveFileEntryList_t filesWithDetails( files ); + idFile_SaveGame * gameDetailsFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_DETAILS_FILENAME, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE ); + gameDetailsFile->MakeWritable(); + description.descriptors.WriteToIniFile( gameDetailsFile ); + filesWithDetails.Append( gameDetailsFile ); + + if ( processorSaveFiles->InitSave( name, filesWithDetails, description ) ) { + processorSaveFiles->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnSaveCompleted, &processorSaveFiles->GetParmsNonConst() ) ); + handle = GetSaveGameManager().ExecuteProcessorAndWait( processorSaveFiles ); + } + + // Errors within the process of saving are handled in OnSaveCompleted() + // so that asynchronous save errors are handled the same was as synchronous. + if ( handle == 0 ) { + idSaveLoadParms & parms = processorSaveFiles->GetParmsNonConst(); + parms.errorCode = SAVEGAME_E_UNKNOWN; + + // Uniform error handling + OnSaveCompleted( &parms ); + } + + return handle; +} + +/* +======================== +idSessionLocal::SaveGameAsync +======================== +*/ +saveGameHandle_t idSessionLocal::SaveGameAsync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) { + saveGameHandle_t handle = 0; + + // Done this way so we know it will be shutdown properly on early exit or exception + struct local_t { + local_t( idSaveLoadParms * localparms ) : parms( localparms ) { + // Prepare background renderer + } + ~local_t() { + // Shutdown background renderer + } + idSaveLoadParms * parms; + } local( &processorSaveFiles->GetParmsNonConst() ); + + // serialize the description file behind their back... + saveFileEntryList_t filesWithDetails( files ); + idFile_SaveGame * gameDetailsFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_DETAILS_FILENAME, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE ); + gameDetailsFile->MakeWritable(); + description.descriptors.WriteToIniFile( gameDetailsFile ); + filesWithDetails.Append( gameDetailsFile ); + + if ( processorSaveFiles->InitSave( name, filesWithDetails, description ) ) { + processorSaveFiles->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnSaveCompleted, &processorSaveFiles->GetParmsNonConst() ) ); + handle = GetSaveGameManager().ExecuteProcessor( processorSaveFiles ); + } + + // Errors within the process of saving are handled in OnSaveCompleted() + // so that asynchronous save errors are handled the same was as synchronous. + if ( handle == 0 ) { + idSaveLoadParms & parms = processorSaveFiles->GetParmsNonConst(); + parms.errorCode = SAVEGAME_E_UNKNOWN; + + common->Dialog().ShowSaveIndicator( false ); + // Uniform error handling + OnSaveCompleted( &parms ); + } + + + return handle; +} + +/* +======================== +idSessionLocal::OnSaveCompleted +======================== +*/ +void idSessionLocal::OnSaveCompleted( idSaveLoadParms * parms ) { + idLocalUser * master = session->GetSignInManager().GetMasterLocalUser(); + + if ( parms->GetError() != SAVEGAME_E_INSUFFICIENT_ROOM ) { + // if savegame completeed we can clear retry info + GetSaveGameManager().ClearRetryInfo(); + } + + // Only turn off the indicator if we're not also going to save the profile settings + if ( master != NULL && master->GetProfile() != NULL && !master->GetProfile()->IsDirty() ) { + common->Dialog().ShowSaveIndicator( false ); + } + + if ( parms->GetError() == SAVEGAME_E_NONE ) { + // Save the profile any time we save the game + if ( master != NULL && master->GetProfile() != NULL ) { + master->GetProfile()->SaveSettings( false ); + } + + // Update the enumerated savegames + saveGameDetailsList_t & detailList = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst(); + idSaveGameDetails * details = detailList.Find( parms->description ); + if ( details == NULL ) { + // add it + detailList.Append( parms->description ); + } else { + // replace it + *details = parms->description; + } + } + + // Error handling and additional processing + common->OnSaveCompleted( *parms ); +} + +/* +======================== +idSessionLocal::LoadGameSync + +We still want to use the savegame manager because we could have file system operations in flight and need to +======================== +*/ +saveGameHandle_t idSessionLocal::LoadGameSync( const char * name, saveFileEntryList_t & files ) { + idSaveLoadParms & parms = processorLoadFiles->GetParmsNonConst(); + saveGameHandle_t handle = 0; + + { + // Put in a local block so everything will go in the global heap before the map change, but the heap is + // automatically popped out on early return or exception + // You cannot be in the global heap during a map change... + //idScopedGlobalHeap everythingGoesInTheGlobalHeap; + + // Done this way so we know it will be shutdown properly on early exit or exception + struct local_t { + local_t( idSaveLoadParms * parms_ ) : parms( parms_ ) { + // Prepare background renderer or loadscreen with what you want to show + { + // with mode: SAVE_GAME_MODE_LOAD + } + } + ~local_t() { + // Shutdown background renderer or loadscreen + { + } + + common->OnLoadCompleted( *parms ); + } + idSaveLoadParms * parms; + } local( &parms ); + + // Read the details file when loading games + saveFileEntryList_t filesWithDetails( files ); + std::auto_ptr< idFile_SaveGame > gameDetailsFile( new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_DETAILS_FILENAME, SAVEGAMEFILE_TEXT ) ); + filesWithDetails.Append( gameDetailsFile.get() ); + + // Check the cached save details from the enumeration and make sure we don't load a save from a newer version of the game! + const saveGameDetailsList_t details = GetSaveGameManager().GetEnumeratedSavegames(); + for ( int i = 0; i < details.Num(); ++i ) { + if ( idStr::Cmp( name, details[i].slotName ) == 0 ) { + if ( details[i].GetSaveVersion() > BUILD_NUMBER ) { + parms.errorCode = SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION; + return 0; + } + } + } + + // Synchronous load + if ( processorLoadFiles->InitLoadFiles( name, filesWithDetails ) ) { + handle = GetSaveGameManager().ExecuteProcessorAndWait( processorLoadFiles ); + } + + if ( handle == 0 ) { + parms.errorCode = SAVEGAME_E_UNKNOWN; + } + + if ( parms.GetError() != SAVEGAME_E_NONE ) { + return 0; + } + + // Checks the description file to see if corrupted or if it's from a newer savegame + if ( !LoadGameCheckDescriptionFile( parms ) ) { + return 0; + } + + // Checks to see if loaded map is from a DLC map and if that DLC is active + if ( !IsDLCAvailable( parms.description.GetMapName() ) ) { + parms.errorCode = SAVEGAME_E_DLC_NOT_FOUND; + return 0; + } + } + + common->OnLoadFilesCompleted( parms ); + + return handle; +} + +/* +======================== +idSessionLocal::OnLoadCompleted +======================== +*/ +void idSessionLocal::OnLoadCompleted( idSaveLoadParms * parms ) { +} + +/* +======================== +idSessionLocal::EnumerateSaveGamesSync +======================== +*/ +saveGameHandle_t idSessionLocal::EnumerateSaveGamesSync() { + saveGameHandle_t handle = 0; + + // Done this way so we know it will be shutdown properly on early exit or exception + struct local_t { + local_t() { + // Prepare background renderer or loadscreen with what you want to show + { + // with mode: SAVE_GAME_MODE_ENUMERATE + } + } + ~local_t() { + // Shutdown background renderer or loadscreen + { + } + } + } local; + + // flush the old enumerated list + GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear(); + + if ( processorEnumerate->Init() ) { + processorEnumerate->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnEnumerationCompleted, &processorEnumerate->GetParmsNonConst() ) ); + handle = GetSaveGameManager().ExecuteProcessorAndWait( processorEnumerate ); + } + + // Errors within the process of saving are handled in OnEnumerationCompleted() + // so that asynchronous save errors are handled the same was as synchronous. + if ( handle == 0 ) { + idSaveLoadParms & parms = processorEnumerate->GetParmsNonConst(); + parms.errorCode = SAVEGAME_E_UNKNOWN; + + // Uniform error handling + OnEnumerationCompleted( &parms ); + } + + return handle; +} + +/* +======================== +idSessionLocal::EnumerateSaveGamesAsync +======================== +*/ +saveGameHandle_t idSessionLocal::EnumerateSaveGamesAsync() { + saveGameHandle_t handle = 0; + + // flush the old enumerated list + GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear(); + + if ( processorEnumerate->Init() ) { + processorEnumerate->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnEnumerationCompleted, &processorEnumerate->GetParmsNonConst() ) ); + handle = GetSaveGameManager().ExecuteProcessor( processorEnumerate ); + } + + // Errors within the process of saving are handled in OnEnumerationCompleted() + // so that asynchronous save errors are handled the same was as synchronous. + if ( handle == 0 ) { + idSaveLoadParms & parms = processorEnumerate->GetParmsNonConst(); + parms.errorCode = SAVEGAME_E_UNKNOWN; + + // Uniform error handling + OnEnumerationCompleted( &parms ); + } + + return handle; +} + +int idSort_EnumeratedSavegames( const idSaveGameDetails * a, const idSaveGameDetails * b ) { + return b->date - a->date; +} + +/* +======================== +idSessionLocal::OnEnumerationCompleted +======================== +*/ +void idSessionLocal::OnEnumerationCompleted( idSaveLoadParms * parms ) { + // idTech4 idList::sort is just a qsort wrapper, which doesn't deal with + // idStrStatic properly! + // parms->detailList.Sort( idSort_EnumeratedSavegames ); + std::sort( parms->detailList.Ptr(), parms->detailList.Ptr() + parms->detailList.Num() ); + + if ( parms->GetError() == SAVEGAME_E_NONE ) { + // Copy into the maintained list + saveGameDetailsList_t & detailsList = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst(); + //mem.PushHeap(); + detailsList = parms->detailList; // copies new list into the savegame manager's reference + //mem.PopHeap(); + + // The platform-specific implementations don't know about the prefixes + // If we don't do this here, we will end up with slots like: GAME-GAME-GAME-GAME-AUTOSAVE... + for ( int i = 0; i < detailsList.Num(); i++ ) { + idSaveGameDetails & details = detailsList[i]; + + const idStr original = details.slotName; + const idStr stripped = RemoveSaveFolderPrefix( original, idSaveGameManager::PACKAGE_GAME ); + details.slotName = stripped; + } + + if ( saveGame_verbose.GetBool() ) { + OutputDetailList( detailsList ); + } + } + + common->OnEnumerationCompleted( *parms ); +} + +/* +======================== +idSessionLocal::DeleteSaveGameSync +======================== +*/ +saveGameHandle_t idSessionLocal::DeleteSaveGameSync( const char * name ) { + saveGameHandle_t handle = 0; + + // Done this way so we know it will be shutdown properly on early exit or exception + struct local_t { + local_t() { + // Prepare background renderer or loadscreen with what you want to show + { + // with mode: SAVE_GAME_MODE_DELETE + } + } + ~local_t() { + // Shutdown background renderer or loadscreen + { + } + } + } local; + + if ( processorDelete->InitDelete( name ) ) { + processorDelete->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnDeleteCompleted, &processorDelete->GetParmsNonConst() ) ); + handle = GetSaveGameManager().ExecuteProcessorAndWait( processorDelete ); + } + + // Errors within the process of saving are handled in OnDeleteCompleted() + // so that asynchronous save errors are handled the same was as synchronous. + if ( handle == 0 ) { + idSaveLoadParms & parms = processorDelete->GetParmsNonConst(); + parms.errorCode = SAVEGAME_E_UNKNOWN; + + // Uniform error handling + OnDeleteCompleted( &parms ); + } + + return handle; +} + +/* +======================== +idSessionLocal::DeleteSaveGameAsync +======================== +*/ +saveGameHandle_t idSessionLocal::DeleteSaveGameAsync( const char * name ) { + saveGameHandle_t handle = 0; + if ( processorDelete->InitDelete( name ) ) { + processorDelete->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnDeleteCompleted, &processorDelete->GetParmsNonConst() ) ); + common->Dialog().ShowSaveIndicator( true ); + handle = GetSaveGameManager().ExecuteProcessor( processorDelete ); + } + + // Errors within the process of saving are handled in OnDeleteCompleted() + // so that asynchronous save errors are handled the same was as synchronous. + if ( handle == 0 ) { + idSaveLoadParms & parms = processorDelete->GetParmsNonConst(); + parms.errorCode = SAVEGAME_E_UNKNOWN; + + // Uniform error handling + OnDeleteCompleted( &parms ); + } + + return handle; +} + +/* +======================== +idSessionLocal::OnDeleteCompleted +======================== +*/ +void idSessionLocal::OnDeleteCompleted( idSaveLoadParms * parms ) { + common->Dialog().ShowSaveIndicator( false ); + + if ( parms->GetError() == SAVEGAME_E_NONE ) { + // Update the enumerated list + saveGameDetailsList_t & details = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst(); + details.Remove( parms->description ); + } + + common->OnDeleteCompleted( *parms ); +} + +/* +======================== +idSessionLocal::IsEnumerating +======================== +*/ +bool idSessionLocal::IsEnumerating() const { + return !session->IsSaveGameCompletedFromHandle( processorEnumerate->GetHandle() ); +} + +/* +======================== +idSessionLocal::GetEnumerationHandle +======================== +*/ +saveGameHandle_t idSessionLocal::GetEnumerationHandle() const { + return processorEnumerate->GetHandle(); +} + +/* +======================== +idSessionLocal::IsDLCAvailable +======================== +*/ +bool idSessionLocal::IsDLCAvailable( const char * mapName ) { + bool hasContentPackage = true; + return hasContentPackage; +} + +/* +======================== +idSessionLocal::LoadGameCheckDiscNumber +======================== +*/ +bool idSessionLocal::LoadGameCheckDiscNumber( idSaveLoadParms & parms ) { +#if 0 + idStr mapName = parms.description.GetMapName(); + + assert( !discSwapStateMgr->IsWorking() ); + discSwapStateMgr->Init( &parms.callbackSignal, idDiscSwapStateManager::DISC_SWAP_COMMAND_LOAD ); + //// TODO_KC this is probably broken now... + //discSwapStateMgr->folder = folder; + //discSwapStateMgr->spawnInfo = newSpawnInfo; + //discSwapStateMgr->spawnSpot = newSpawnPoint; + //discSwapStateMgr->instanceFileName = instanceFileName; + discSwapStateMgr->user = session->GetSignInManager().GetMasterLocalUser(); + discSwapStateMgr->map = mapName; + + discSwapStateMgr->Pump(); + while ( discSwapStateMgr->IsWorking() ) { + Sys_Sleep( 15 ); + // process input and render + + discSwapStateMgr->Pump(); + } + + idDiscSwapStateManager::discSwapStateError_t discSwapError = discSwapStateMgr->GetError(); + + if ( discSwapError == idDiscSwapStateManager::DSSE_CANCEL ) { + parms.errorCode = SAVEGAME_E_CANCELLED; + } else if ( discSwapError == idDiscSwapStateManager::DSSE_INSUFFICIENT_ROOM ) { + parms.errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; + parms.requiredSpaceInBytes = discSwapStateMgr->GetRequiredStorageBytes(); + } else if ( discSwapError == idDiscSwapStateManager::DSSE_CORRECT_DISC_ALREADY ) { + parms.errorCode = SAVEGAME_E_NONE; + } else if ( discSwapError != idDiscSwapStateManager::DSSE_OK ) { + parms.errorCode = SAVEGAME_E_DISC_SWAP; + } + + if ( parms.errorCode == SAVEGAME_E_UNKNOWN ) { + parms.errorCode = SAVEGAME_E_DISC_SWAP; + } +#endif + + return ( parms.GetError() == SAVEGAME_E_NONE ); +} + +/* +======================== +idSessionLocal::LoadGameCheckDescriptionFile +======================== +*/ +bool idSessionLocal::LoadGameCheckDescriptionFile( idSaveLoadParms & parms ) { + idFile_SaveGame ** detailsFile = FindFromGenericPtr( parms.files, SAVEGAME_DETAILS_FILENAME ); + if ( detailsFile == NULL ) { + parms.errorCode = SAVEGAME_E_FILE_NOT_FOUND; + return false; + } + + assert( *detailsFile != NULL ); + (*detailsFile)->MakeReadOnly(); + + if ( !SavegameReadDetailsFromFile( *detailsFile, parms.description ) ) { + parms.errorCode = SAVEGAME_E_CORRUPTED; + } else { + if ( parms.description.GetSaveVersion() > BUILD_NUMBER ) { + parms.errorCode = SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION; + } + } + + return ( parms.GetError() == SAVEGAME_E_NONE ); +} + +#pragma region COMMANDS +/* +================================================================================================ + +COMMANDS + +================================================================================================ +*/ +CONSOLE_COMMAND( testSavegameDeleteAll, "delete all savegames without confirmation", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + idSaveLoadParms parms; + + parms.SetDefaults(); + parms.mode = SAVEGAME_MBF_DELETE_ALL_FOLDERS | SAVEGAME_MBF_NO_COMPRESS; + + Sys_ExecuteSavegameCommandAsync( &parms ); + + parms.callbackSignal.Wait(); + idLib::Printf( "Completed process.\n" ); + idLib::Printf( "Error = 0x%08X, %s\n", parms.GetError(), GetSaveGameErrorString( parms.GetError() ).c_str() ); +} + +CONSOLE_COMMAND( testSavegameDelete, "deletes a savegames without confirmation", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + if ( args.Argc() != 2 ) { + idLib::Printf( "Usage: testSavegameDelete \n" ); + return; + } + + idStr folder = args.Argv( 1 ); + idSaveGameProcessorDelete testDeleteSaveGamesProc; + if ( testDeleteSaveGamesProc.InitDelete( folder ) ) { + session->GetSaveGameManager().ExecuteProcessorAndWait( &testDeleteSaveGamesProc ); + } + + idLib::Printf( "Completed process.\n" ); + idLib::Printf( "Error = 0x%08X, %s\n", testDeleteSaveGamesProc.GetParms().GetError(), GetSaveGameErrorString( testDeleteSaveGamesProc.GetParms().GetError() ).c_str() ); +} + +CONSOLE_COMMAND( testSavegameEnumerateFiles, "enumerates all the files in a folder (blank for 'current slot' folder, use 'autosave' for the autosave slot)", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + idStr folder = session->GetCurrentSaveSlot(); + if ( args.Argc() > 1 ) { + folder = args.Argv( 1 ); + } + + idLib::Printf( "Testing folder: %s\n\n", folder.c_str() ); + + idSaveLoadParms parms; + parms.SetDefaults(); + parms.mode = SAVEGAME_MBF_ENUMERATE_FILES; + + // Platform-specific implementation + // This will start a worker thread for async operation. + // It will always signal when it's completed. + Sys_ExecuteSavegameCommandAsync( &parms ); + parms.callbackSignal.Wait(); + + for ( int i = 0; i < parms.files.Num(); i++ ) { + idLib::Printf( S_COLOR_YELLOW "\t%d: %s\n" S_COLOR_DEFAULT, i, parms.files[i]->GetName() ); + } +} + +/* +======================== +OutputDetailList +======================== +*/ +void OutputDetailList( const saveGameDetailsList_t & savegameList ) { + for ( int i = 0; i < savegameList.Num(); ++i ) { + idLib::Printf( S_COLOR_YELLOW "\t%s - %s\n" S_COLOR_DEFAULT + "\t\tMap: %s\n" + "\t\tTime: %s\n", + savegameList[i].slotName.c_str(), + savegameList[i].damaged ? S_COLOR_RED "CORRUPT" : S_COLOR_GREEN "OK", + savegameList[i].damaged ? "?" : savegameList[i].descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP, "" ), + Sys_TimeStampToStr( savegameList[i].date ) + ); + } +} + +CONSOLE_COMMAND( testSavegameEnumerate, "enumerates the savegames available", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + saveGameHandle_t handle = session->EnumerateSaveGamesSync(); + if ( handle == 0 ) { + idLib::Printf( "Error enumerating.\n" ); + return; + } + + const saveGameDetailsList_t gameList = session->GetSaveGameManager().GetEnumeratedSavegames(); + idLib::Printf( "Savegames found: %d\n\n", gameList.Num() ); + OutputDetailList( gameList ); +} + +CONSOLE_COMMAND( testSaveGameCheck, "tests existence of savegame", 0 ) { + bool exists; + bool autosaveExists; + Sys_SaveGameCheck( exists, autosaveExists ); + idLib::Printf( "Savegame check: exists = %d, autosaveExists = %d\n", exists, autosaveExists ); +} + +CONSOLE_COMMAND( testSaveGameOutputEnumeratedSavegames, "outputs the list of savegames already enumerated, this does not re-enumerate", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + const saveGameDetailsList_t & savegames = session->GetSaveGameManager().GetEnumeratedSavegames(); + OutputDetailList( savegames ); +} + +CONSOLE_COMMAND( testSavegameGetCurrentSlot, "returns the current slot in use", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + idLib::Printf( "Current slot: %s\n", session->GetCurrentSaveSlot() ); +} + +CONSOLE_COMMAND( testSavegameSetCurrentSlot, "returns the current slot in use", 0 ) { + if ( session == NULL ) { + idLib::Printf( "Invalid session.\n" ); + return; + } + + if ( args.Argc() != 2 ) { + idLib::Printf( "Usage: testSavegameSetCurrentSlot name\n" ); + return; + } + + const char * slot = args.Argv( 1 ); + + session->SetCurrentSaveSlot( slot ); + idLib::Printf( "Current slot: %s\n", session->GetCurrentSaveSlot() ); +} + +CONSOLE_COMMAND( savegameSetErrorBit, "Allows you to set savegame_error by bit instead of integer value", 0 ) { + int bit = atoi( args.Argv( 1 ) ); + savegame_error.SetInteger( savegame_error.GetInteger() | ( 1 << bit ) ); +} + +#pragma endregion \ No newline at end of file diff --git a/neo/sys/sys_session_savegames.h b/neo/sys/sys_session_savegames.h new file mode 100644 index 00000000..3e00da5b --- /dev/null +++ b/neo/sys/sys_session_savegames.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_SESSION_SAVEGAMES_H__ +#define __SYS_SESSION_SAVEGAMES_H__ + +/* +================================================ +idSaveGameProcessorLoadFiles +================================================ +*/ +class idSaveGameProcessorLoadFiles : public idSaveGameProcessor { +public: + DEFINE_CLASS( idSaveGameProcessorLoadFiles ); + + virtual bool InitLoadFiles( const char * folder, + const saveFileEntryList_t & files, + idSaveGameManager::packageType_t type = idSaveGameManager::PACKAGE_GAME ); + virtual bool Process(); +}; + +/* +================================================win_sav +idSaveGameProcessorDelete +================================================ +*/ +class idSaveGameProcessorDelete : public idSaveGameProcessor { +public: + DEFINE_CLASS( idSaveGameProcessorDelete ); + + bool InitDelete( const char * folder, idSaveGameManager::packageType_t type = idSaveGameManager::PACKAGE_GAME ); + virtual bool Process(); +}; + +/* +================================================ +idSaveGameProcessorSaveFiles +================================================ +*/ +class idSaveGameProcessorSaveFiles : public idSaveGameProcessor { +public: + DEFINE_CLASS( idSaveGameProcessorSaveFiles ); + + // Passing in idSaveGameDetails so that we have a copy on output + bool InitSave( const char * folder, + const saveFileEntryList_t & files, + const idSaveGameDetails & description, + idSaveGameManager::packageType_t type = idSaveGameManager::PACKAGE_GAME ); + virtual bool Process(); +}; + +/* +================================================ +idSaveGameProcessorEnumerateGames +================================================ +*/ +class idSaveGameProcessorEnumerateGames : public idSaveGameProcessor { +public: + DEFINE_CLASS( idSaveGameProcessorEnumerateGames ); + + virtual bool Process(); +}; + +#endif diff --git a/neo/sys/sys_signin.cpp b/neo/sys/sys_signin.cpp new file mode 100644 index 00000000..654ba1ed --- /dev/null +++ b/neo/sys/sys_signin.cpp @@ -0,0 +1,249 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +idCVar com_requireNonProductionSignIn( "com_requireNonProductionSignIn", "1", CVAR_BOOL|CVAR_ARCHIVE, "If true, will require sign in, even on non production builds." ); +extern idCVar fs_savepath; + +extern idCVar g_demoMode; + +/* +======================== +idSignInManagerBase::ProcessInputEvent +======================== +*/ +bool idSignInManagerBase::ProcessInputEvent( const sysEvent_t * ev ) { + // If we could use more local users, poll for them + if ( GetNumLocalUsers() < maxDesiredLocalUsers && !IsAnyDeviceBeingRegistered() ) { + if ( ev->IsKeyDown() ) { + if ( ev->GetKey() == K_JOY1 || ev->GetKey() == K_JOY9 || ev->GetKey() == K_ENTER || ev->GetKey() == K_KP_ENTER ) { + + + // Register a local user so they can use this input device only done for demos press start screen + // otherwise the user is registered when selecting which game to play. + } + } + } + + return false; +} + +/* +======================== +idSignInManagerBase::GetDefaultProfile +======================== +*/ +idPlayerProfile * idSignInManagerBase::GetDefaultProfile() { + if ( defaultProfile == NULL ) { + // Create a new profile + defaultProfile = idPlayerProfile::CreatePlayerProfile( 0 ); + } + return defaultProfile; +} + +/* +======================== +idSignInManagerBase::GetLocalUserByInputDevice +======================== +*/ +idLocalUser * idSignInManagerBase::GetLocalUserByInputDevice( int index ) { + for ( int i = 0; i < GetNumLocalUsers(); i++ ) { + if ( GetLocalUserByIndex( i )->GetInputDevice() == index ) { + return GetLocalUserByIndex( i ); // Found it + } + } + + return NULL; // Not found +} + +/* +======================== +idSignInManagerBase::GetLocalUserByHandle +======================== +*/ +idLocalUser * idSignInManagerBase::GetLocalUserByHandle( localUserHandle_t handle ) { + for ( int i = 0; i < GetNumLocalUsers(); i++ ) { + if ( GetLocalUserByIndex( i )->GetLocalUserHandle() == handle ) { + return GetLocalUserByIndex( i ); // Found it + } + } + + return NULL; // Not found +} + +/* +======================== +idSignInManagerBase::GetPlayerProfileByInputDevice +======================== +*/ +idPlayerProfile * idSignInManagerBase::GetPlayerProfileByInputDevice( int index ) { + idLocalUser * user = session->GetSignInManager().GetLocalUserByInputDevice( index ); + idPlayerProfile * profile = NULL; + if ( user != NULL ) { + profile = user->GetProfile(); + } + return profile; +} + +/* +======================== +idSignInManagerBase::RemoveLocalUserByInputDevice +======================== +*/ +bool idSignInManagerBase::RemoveLocalUserByInputDevice( int index ) { + for ( int i = 0; i < GetNumLocalUsers(); i++ ) { + if ( GetLocalUserByIndex( i )->GetInputDevice() == index ) { + RemoveLocalUserByIndex( i ); + return true; + } + } + + return false; // Not found +} + +/* +======================== +idSignInManagerBase::RemoveLocalUserByHandle +======================== +*/ +bool idSignInManagerBase::RemoveLocalUserByHandle( localUserHandle_t handle ) { + for ( int i = 0; i < GetNumLocalUsers(); i++ ) { + if ( GetLocalUserByIndex( i )->GetLocalUserHandle() == handle ) { + RemoveLocalUserByIndex( i ); + return true; + } + } + + return false; // Not found +} + +/* +======================== +idSignInManagerBase::SaveUserProfiles +======================== +*/ +void idSignInManagerBase::SaveUserProfiles() { + for ( int i = 0; i < GetNumLocalUsers(); i++ ) { + idLocalUser * localUser = GetLocalUserByIndex( i ); + if ( localUser != NULL ) { + idPlayerProfile * profile = localUser->GetProfile(); + if ( profile != NULL ) { + profile->SaveSettings( false ); + } + } + } +} + +/* +======================== +idSignInManagerBase::RemoveAllLocalUsers +======================== +*/ +void idSignInManagerBase::RemoveAllLocalUsers() { + while ( GetNumLocalUsers() > 0 ) { + RemoveLocalUserByIndex( 0 ); + } +} + +/* +======================== +idSignInManagerBase::ValidateLocalUsers +======================== +*/ +void idSignInManagerBase::ValidateLocalUsers( bool requireOnline ) { + if ( !RequirePersistentMaster() ) { + return; + } + for ( int i = GetNumLocalUsers() - 1; i >= 0; i-- ) { + idLocalUser * localUser = GetLocalUserByIndex( i ); + + // If this user does not have a profile, remove them. + // If this user is not online-capable and we require online, remove them. + if ( !localUser->IsProfileReady() || + ( requireOnline && ( !localUser->IsOnline() || !localUser->CanPlayOnline() ) ) ) { + RemoveLocalUserByIndex( i ); + } + } +} + +/* +======================== +idSignInManagerBase::RequirePersistentMaster +======================== +*/ +bool idSignInManagerBase::RequirePersistentMaster() { +#ifdef ID_RETAIL + return true; // ALWAYS require persistent master on retail builds +#else + // Non retail production builds require a persistent profile unless si_splitscreen is set + extern idCVar si_splitscreen; + + // If we are forcing splitscreen, then we won't require a profile (this is for development) + if ( si_splitscreen.GetInteger() != 0 ) { + return false; + } + + return com_requireNonProductionSignIn.GetBool(); +#endif +} + +/* +======================== +idSignInManagerBase::GetUniqueLocalUserHandle +Uniquely generate a handle based on name and time +======================== +*/ +localUserHandle_t idSignInManagerBase::GetUniqueLocalUserHandle( const char * name ) { + MD5_CTX ctx; + unsigned char digest[16]; + int64 clockTicks = Sys_GetClockTicks(); + + MD5_Init( &ctx ); + MD5_Update( &ctx, (const unsigned char *)name, idStr::Length( name ) ); + MD5_Update( &ctx, (const unsigned char *)&clockTicks, sizeof( clockTicks ) ); + MD5_Final( &ctx, (unsigned char *)digest ); + + // Quantize the 128 bit hash down to the number of bits needed for a localUserHandle_t + const int STRIDE_BYTES = sizeof( localUserHandle_t::userHandleType_t ); + const int NUM_LOOPS = 16 / STRIDE_BYTES; + + localUserHandle_t::userHandleType_t handle = 0; + + for ( int i = 0; i < NUM_LOOPS; i++ ) { + localUserHandle_t::userHandleType_t tempHandle = 0; + + for ( int j = 0; j < STRIDE_BYTES; j++ ) { + tempHandle |= ( ( localUserHandle_t::userHandleType_t )digest[ ( i * STRIDE_BYTES ) + j ] ) << ( j * 8 ); + } + + handle ^= tempHandle; + } + + return localUserHandle_t( handle ); +} diff --git a/neo/sys/sys_signin.h b/neo/sys/sys_signin.h new file mode 100644 index 00000000..2761bdf9 --- /dev/null +++ b/neo/sys/sys_signin.h @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_SIGNIN_H__ +#define __SYS_SIGNIN_H__ + +/* +================================================ +idSignInManagerBase +================================================ +*/ +class idSignInManagerBase { +public: + + idSignInManagerBase() : + minDesiredLocalUsers( 0 ), + maxDesiredLocalUsers( 0 ), + defaultProfile( NULL ) {} + virtual ~idSignInManagerBase() {} + + virtual void Pump() = 0; + virtual int GetNumLocalUsers() const = 0; + virtual idLocalUser * GetLocalUserByIndex( int index ) = 0; + virtual const idLocalUser * GetLocalUserByIndex( int index ) const = 0; + virtual void RemoveLocalUserByIndex( int index ) = 0; + virtual void RegisterLocalUser( int inputDevice ) = 0; // Register a local controller user to the passed in input device + virtual idLocalUser * GetRegisteringUser() { return NULL; } // This is a user that has started the registration process but is not yet a local user. + virtual idLocalUser * GetRegisteringUserByInputDevice( int inputDevice ) { return NULL; } + virtual void SignIn() {} + virtual bool IsDeviceBeingRegistered( int intputDevice ) { return false; } + virtual bool IsAnyDeviceBeingRegistered() { return false; } + virtual void Shutdown() {} + + // Outputs all the local users and other debugging information from the sign in manager + virtual void DebugOutputLocalUserInfo() {} + + //================================================================================ + // Common helper functions + //================================================================================ + + void SetDesiredLocalUsers( int minDesiredLocalUsers, int maxDesiredLocalUsers ) { this->minDesiredLocalUsers = minDesiredLocalUsers; this->maxDesiredLocalUsers = maxDesiredLocalUsers; } + bool ProcessInputEvent( const sysEvent_t * ev ); + idPlayerProfile * GetDefaultProfile(); + + // Master user always index 0 + idLocalUser * GetMasterLocalUser() { return ( GetNumLocalUsers() > 0 ) ? GetLocalUserByIndex( 0 ) : NULL; } + const idLocalUser * GetMasterLocalUser() const { return ( GetNumLocalUsers() > 0 ) ? GetLocalUserByIndex( 0 ) : NULL; } + + bool IsMasterLocalUserPersistent() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->IsPersistent() : false; } + bool IsMasterLocalUserOnline() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->IsOnline() : false; } + int GetMasterInputDevice() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->GetInputDevice() : -1; } + localUserHandle_t GetMasterLocalUserHandle() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->GetLocalUserHandle() : localUserHandle_t(); } + idLocalUser * GetLocalUserByInputDevice( int index ); + idLocalUser * GetLocalUserByHandle( localUserHandle_t handle ); + idPlayerProfile * GetPlayerProfileByInputDevice( int index ); + bool RemoveLocalUserByInputDevice( int index ); + bool RemoveLocalUserByHandle( localUserHandle_t handle ); + void RemoveAllLocalUsers(); + void SaveUserProfiles(); + + // This will remove local players that are not signed into a profile. + // If requiredOnline: This removes the users who cannot play online + void ValidateLocalUsers( bool requireOnline ); + + bool RequirePersistentMaster(); + + localUserHandle_t GetUniqueLocalUserHandle( const char * name ); + +protected: + int minDesiredLocalUsers; + int maxDesiredLocalUsers; + idPlayerProfile * defaultProfile; +}; + +#endif // __SYS_SIGNIN_H__ diff --git a/neo/sys/sys_stats.h b/neo/sys/sys_stats.h new file mode 100644 index 00000000..ba9aedbd --- /dev/null +++ b/neo/sys/sys_stats.h @@ -0,0 +1,91 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_STATS_H__ +#define __SYS_STATS_H__ + +//------------------------ +// leaderboardError_t +//------------------------ +enum leaderboardError_t { + LEADERBOARD_ERROR_NONE, + LEADERBOARD_ERROR_FAILED, // General error occurred + LEADERBOARD_ERROR_NOT_ONLINE, // Not online to request leaderboards + LEADERBOARD_ERROR_BUSY, // Unable to download leaderboards right now (download already in progress) + LEADERBOARD_ERROR_INVALID_USER, // Unable to request leaderboards as the given user + LEADERBOARD_ERROR_INVALID_REQUEST, // The leaderboard request was invalid + LEADERBOARD_ERROR_DOWNLOAD, // An error occurred while downloading the leaderboard + LEADERBOARD_ERROR_MAX +}; + +/* +================================================ +idLeaderboardCallback +================================================ +*/ +class idLeaderboardCallback : public idCallback { +public: + struct row_t { + bool hasAttachment; + int64 attachmentID; + idStr name; + int64 rank; + idArray< int64, MAX_LEADERBOARD_COLUMNS > columns; + }; + + idLeaderboardCallback() : def( NULL ), startIndex( -1 ), localIndex( -1 ), numRowsInLeaderboard( -1 ), errorCode( LEADERBOARD_ERROR_NONE ) { } + virtual idLeaderboardCallback * Clone() const = 0; + + // Used by the platform handlers to set data + void ResetRows() { rows.Clear(); } + void AddRow( const row_t & row ) { rows.Append( row ); } + void SetNumRowsInLeaderboard( int32 i ) { numRowsInLeaderboard = i; } + void SetDef( const leaderboardDefinition_t * def_ ) { def = def_; } + void SetStartIndex( int startIndex_ ) { startIndex = startIndex_; } + void SetLocalIndex( int localIndex_ ) { localIndex = localIndex_; } + void SetErrorCode( leaderboardError_t errorCode ) { this->errorCode = errorCode; } + + // Used in user callback for information retrieval + const leaderboardDefinition_t * GetDef() const { return def; } + int GetStartIndex() const { return startIndex; } + const idList< row_t > & GetRows() const { return rows; } + int GetNumRowsInLeaderboard() const { return numRowsInLeaderboard; } + int GetLocalIndex() const { return localIndex; } + leaderboardError_t GetErrorCode() const { return this->errorCode; } + +protected: + const leaderboardDefinition_t * def; // leaderboard def + int startIndex; // where the first row starts in the online leaderboard + int localIndex; // if player is in the rows, this is the offset of him within the returned rows + idList< row_t > rows; // leaderboard entries for the request + int numRowsInLeaderboard; // total number of rows in the online leaderboard + leaderboardError_t errorCode; // error, if any, that occurred during last operation +}; + + + +#endif // !__SYS_STATS_H__ diff --git a/neo/sys/sys_stats_misc.h b/neo/sys/sys_stats_misc.h new file mode 100644 index 00000000..69f5bf0c --- /dev/null +++ b/neo/sys/sys_stats_misc.h @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_STATS_MISC_H__ +#define __SYS_STATS_MISC_H__ + +/* +================================================================================================ + + Stats (for achievements, matchmaking, etc.) + +================================================================================================ +*/ + +/* +================================================ +systemStats_t +This is to give the framework the ability to deal with stat indexes that are +completely up to each game. The system needs to deal with some stat indexes for things like +the level for matchmaking, etc. +================================================ +*/ + +/* +================================================================================================ + + Leader Boards + +================================================================================================ +*/ + +const int MAX_LEADERBOARDS = 256; +const int MAX_LEADERBOARD_COLUMNS = 16; + +enum aggregationMethod_t { + AGGREGATE_MIN, // Write the new value if it is less than the existing value. + AGGREGATE_MAX, // Write the new value if it is greater than the existing value. + AGGREGATE_SUM, // Add the new value to the existing value and write the result. + AGGREGATE_LAST, // Write the new value. +}; + +enum rankOrder_t { + RANK_GREATEST_FIRST, // Rank the in descending order, greatest score is best score + RANK_LEAST_FIRST, // Rank the in ascending order, lowest score is best score +}; + +enum statsColumnDisplayType_t { + STATS_COLUMN_DISPLAY_NUMBER, + STATS_COLUMN_DISPLAY_TIME_MILLISECONDS, + STATS_COLUMN_DISPLAY_CASH, + STATS_COLUMN_NEVER_DISPLAY, +}; + +struct columnDef_t { + const char * locDisplayName; + int bits; + aggregationMethod_t aggregationMethod; + statsColumnDisplayType_t displayType; +}; + +struct leaderboardDefinition_t { + + leaderboardDefinition_t() : + id ( -1 ), + numColumns( 0 ), + columnDefs( NULL ), + rankOrder( RANK_GREATEST_FIRST ), + supportsAttachments( false ), + checkAgainstCurrent( false ) { + } + + leaderboardDefinition_t( int id_, int numColumns_, const columnDef_t * columnDefs_, rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) : + id ( id_ ), + numColumns( numColumns_ ), + columnDefs( columnDefs_ ), + rankOrder( rankOrder_ ), + supportsAttachments( supportsAttachments_ ), + checkAgainstCurrent( checkAgainstCurrent_ ) { + } + + int32 id; + int32 numColumns; + const columnDef_t * columnDefs; + rankOrder_t rankOrder; + bool supportsAttachments; + bool checkAgainstCurrent; // Compare column 0 with the currently stored leaderboard, and only submit the new leaderboard if the new column 0 is better + idStr boardName; // Only used for display name within steam. If Empty, will generate. must be specifically set. +}; + +struct column_t { + column_t( int64 value_ ) : value( value_ ) {} + column_t() {} + + int64 value; +}; + + +/* +================================================================================================ +Contains the Achievement and LeaderBoard free function declarations. +================================================================================================ +*/ + +typedef int32 leaderboardHandle_t; + +/* +================================================ +idLeaderBoardEntry +================================================ +*/ +class idLeaderBoardEntry { +public: + static const int MAX_LEADERBOARD_COLUMNS = 16; + idStr username; // aka gamertag + int64 score; + int64 columns[ MAX_LEADERBOARD_COLUMNS ]; +}; + +const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ); +leaderboardDefinition_t * Sys_CreateLeaderboardDef( int id_, int numColumns_, const columnDef_t * columnDefs_, rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ); +void Sys_DestroyLeaderboardDefs(); + +#endif // !__SYS_STATS_MISC_H__ diff --git a/neo/sys/sys_voicechat.cpp b/neo/sys/sys_voicechat.cpp new file mode 100644 index 00000000..a29aee0b --- /dev/null +++ b/neo/sys/sys_voicechat.cpp @@ -0,0 +1,593 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" +#include "sys_voicechat.h" + +/* +================================================ +idVoiceChatMgr::Init +================================================ +*/ +void idVoiceChatMgr::Init( void * pXAudio2 ) { +} + +/* +================================================ +idVoiceChatMgr::Shutdown +================================================ +*/ +void idVoiceChatMgr::Shutdown() { + + // We shouldn't have voice users if everything shutdown correctly + assert( talkers.Num() == 0 ); + assert( remoteMachines.Num() == 0 ); +} + +/* +================================================ +idVoiceChatMgr::RegisterTalker +================================================ +*/ +void idVoiceChatMgr::RegisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal ) { + + int i = FindTalkerIndex( user, lobbyType ); + + if ( !verify( i == -1 ) ) { + assert( talkers[i].lobbyType == lobbyType ); + idLib::Printf( "RegisterTalker: Talker already registered.\n" ); + return; + } + + // Talker not found, need to create a new one + + talker_t newTalker; + + newTalker.user = user; + newTalker.isLocal = isLocal; + newTalker.lobbyType = lobbyType; + newTalker.registered = false; + newTalker.registeredSuccess = false; + newTalker.machineIndex = -1; + newTalker.groupIndex = 0; // 0 is default group + + if ( !newTalker.IsLocal() ) { // If this is a remote talker, register his machine address + newTalker.machineIndex = AddMachine( user->address, lobbyType ); + } + + talkers.Append( newTalker ); + + // Since we added a new talker, make sure he is registered. UpdateRegisteredTalkers will catch all users, including this one. + UpdateRegisteredTalkers(); +} + +/* +================================================ +idVoiceChatMgr::UnregisterTalker +================================================ +*/ +void idVoiceChatMgr::UnregisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal ) { + int i = FindTalkerIndex( user, lobbyType ); + + if ( !verify( i != -1 ) ) { + idLib::Printf( "UnregisterTalker: Talker not found.\n" ); + return; + } + + talker_t & talker = talkers[i]; + + assert( talker.IsLocal() == ( talker.machineIndex == -1 ) ); + assert( talker.IsLocal() == isLocal ); + + talker.lobbyType = -1; // Mark for removal + UpdateRegisteredTalkers(); // Make sure the user gets unregistered before we remove him/her + + if ( talker.machineIndex != -1 ) { + // Unregister the talkers machine (unique address) handle + RemoveMachine( talker.machineIndex, lobbyType ); + } + + talkers.RemoveIndex( i ); // Finally, remove the talker +} + +/* +================================================ +idVoiceChatMgr::GetActiveLocalTalkers +================================================ +*/ +void idVoiceChatMgr::GetActiveLocalTalkers( idStaticList< int, MAX_PLAYERS > & localTalkers ) { + + localTalkers.Clear(); + + for ( int i = 0; i < talkers.Num(); i++ ) { + + if ( !talkers[i].IsLocal() ) { + continue; + } + + if ( !talkers[i].registeredSuccess ) { + continue; + } + + if ( !TalkerHasData( i ) ) { + continue; + } + + localTalkers.Append( i ); + } +} + +/* +================================================ +idVoiceChatMgr::GetRecipientsForTalker +================================================ +*/ +void idVoiceChatMgr::GetRecipientsForTalker( int talkerIndex, idStaticList< const lobbyAddress_t *, MAX_PLAYERS > & recipients ) { + + recipients.Clear(); + + talker_t & talker = talkers[talkerIndex]; + + if ( !talker.IsLocal() ) { + return; + } + + sendFrame++; + + for ( int i = 0; i < talkers.Num(); i++ ) { + if ( !talkers[i].registeredSuccess ) { + continue; + } + + if ( talkers[i].IsLocal() ) { + continue; // Only want to send to remote talkers + } + + if ( !CanSendVoiceTo( talkerIndex, i ) ) { + continue; + } + + if ( !sendGlobal && talkers[i].groupIndex != activeGroupIndex ) { + continue; + } + + assert( talkers[i].machineIndex >= 0 ); + + remoteMachine_t & remoteMachine = remoteMachines[ talkers[i].machineIndex ]; + + assert( remoteMachine.refCount > 0 ); + assert( remoteMachine.lobbyType == activeLobbyType ); + + if ( remoteMachine.sendFrame == sendFrame ) { + continue; // Already on the recipient list + } + + remoteMachine.sendFrame = sendFrame; + + recipients.Append( &remoteMachine.address ); + } +} + +/* +================================================ +idVoiceChatMgr::SetTalkerGroup +================================================ +*/ +void idVoiceChatMgr::SetTalkerGroup( const lobbyUser_t * user, int lobbyType, int groupIndex ) { + int i = FindTalkerIndex( user, lobbyType ); + + if ( !verify( i != -1 ) ) { + idLib::Printf( "SetTalkerGroup: Talker not found.\n" ); + return; + } + + // Assign the new group index to this talker + talkers[i].groupIndex = groupIndex; + + // Since the group index of this player changed, call UpdateRegisteredTalkers, which will register the + // appropriate users based on the current active group and session + UpdateRegisteredTalkers(); +} + +/* +================================================ +idVoiceChatMgr::SetActiveLobby +================================================ +*/ +void idVoiceChatMgr::SetActiveLobby( int lobbyType ) { + if ( activeLobbyType != lobbyType ) { + activeLobbyType = lobbyType; + // When the active session changes, we need to immediately call UpdateRegisteredTalkers, + // which will make sure the appropriate talkers are registered depending on the activeSession. + UpdateRegisteredTalkers(); + } +} + +/* +================================================ +idVoiceChatMgr::SetActiveChatGroup +================================================ +*/ +void idVoiceChatMgr::SetActiveChatGroup( int groupIndex ) { + if ( activeGroupIndex != groupIndex ) { + activeGroupIndex = groupIndex; + // When the active group changes, we need to immediately call UpdateRegisteredTalkers, + // which will make sure the appropriate talkers are registered depending on the activeGroup. + UpdateRegisteredTalkers(); + } +} + +/* +================================================ +idVoiceChatMgr::FindTalkerByUserId +================================================ +*/ +int idVoiceChatMgr::FindTalkerByUserId( lobbyUserID_t userID, int lobbyType ) { + for ( int i = 0; i < talkers.Num(); i++ ) { + if ( talkers[i].user->lobbyUserID == userID && talkers[i].lobbyType == lobbyType ) { + return i; + } + } + + return -1; // Not found +} + +/* +================================================ +idVoiceChatMgr::GetLocalChatData +================================================ +*/ +bool idVoiceChatMgr::GetLocalChatData( int talkerIndex, byte * data, int & dataSize ) { + talker_t & talker = talkers[talkerIndex]; + + if ( !talker.IsLocal() ) { + idLib::Printf( "GetLocalChatData: Talker not local.\n" ); + return false; // Talker is remote + } + + if ( !talker.registeredSuccess ) { + return false; + } + + idBitMsg voiceMsg; + voiceMsg.InitWrite( data, dataSize ); + + talker.user->lobbyUserID.WriteToMsg( voiceMsg ); + voiceMsg.WriteByteAlign(); + + // Remove the size of the userid field from the available buffer size + int voiceDataSize = dataSize - voiceMsg.GetSize(); + + if ( !GetLocalChatDataInternal( talkerIndex, voiceMsg.GetWriteData() + voiceMsg.GetSize(), voiceDataSize ) ) { + dataSize = 0; + return false; + } + + dataSize = voiceDataSize + voiceMsg.GetSize(); + + // Mark the user as talking + talker.talking = true; + talker.talkingTime = Sys_Milliseconds(); + + return dataSize > 0 ? true : false; +} + +/* +================================================ +idVoiceChatMgr::SubmitIncomingChatData +================================================ +*/ +void idVoiceChatMgr::SubmitIncomingChatData( const byte * data, int dataSize ) { + lobbyUserID_t lobbyUserID; + + idBitMsg voiceMsg; + voiceMsg.InitRead( data, dataSize ); + + lobbyUserID.ReadFromMsg( voiceMsg ); + voiceMsg.ReadByteAlign(); + + int i = FindTalkerByUserId( lobbyUserID, activeLobbyType ); + + if ( i == -1 ) { + idLib::Printf( "SubmitIncomingChatData: Talker not found in session.\n" ); + return; // Talker is not in this session + } + + talker_t & talker = talkers[i]; + + if ( talker.registeredSuccess && !talker.isMuted ) { + // Mark the user as talking + talker.talking = true; + talker.talkingTime = Sys_Milliseconds(); + + SubmitIncomingChatDataInternal( i, voiceMsg.GetReadData() + voiceMsg.GetReadCount(), voiceMsg.GetRemainingData() ); + } +} + +/* +======================== +idVoiceChatMgr::GetVoiceState +======================== +*/ +voiceState_t idVoiceChatMgr::GetVoiceState( const lobbyUser_t * user ) { + int i = FindTalkerByUserId( user->lobbyUserID, activeLobbyType ); + + if ( i == -1 ) { + return VOICECHAT_STATE_NO_MIC; + } + + talker_t & talker = talkers[i]; + + if ( !talker.hasHeadset ) { + return VOICECHAT_STATE_NO_MIC; + } + + if ( talker.isMuted ) { + return VOICECHAT_STATE_MUTED_LOCAL; + } + + if ( talker.talking && Sys_Milliseconds() - talker.talkingTime > 200 ) { + talker.talking = false; + } + + return talker.talking ? (talker.talkingGlobal ? VOICECHAT_STATE_TALKING_GLOBAL : VOICECHAT_STATE_TALKING ) : VOICECHAT_STATE_NOT_TALKING; +} + +/* +======================== +idVoiceChatMgr::CanSendVoiceTo +======================== +*/ +bool idVoiceChatMgr::CanSendVoiceTo( int talkerFromIndex, int talkerToIndex ) { + talker_t & talkerFrom = talkers[talkerFromIndex]; + + if ( !talkerFrom.IsLocal() ) { + return false; + } + + talker_t & talkerTo = talkers[talkerToIndex]; + + if ( talkerTo.isMuted ) { + return false; + } + + return true; +} + +/* +======================== +idVoiceChatMgr::IsRestrictedByPrivleges +======================== +*/ +bool idVoiceChatMgr::IsRestrictedByPrivleges() { + return ( disableVoiceReasons & REASON_PRIVILEGES ) != 0; +} + +/* +======================== +idVoiceChatMgr::ToggleMuteLocal +======================== +*/ +void idVoiceChatMgr::ToggleMuteLocal( const lobbyUser_t * src, const lobbyUser_t * target ) { + int fromTalkerIndex = FindTalkerByUserId( src->lobbyUserID, activeLobbyType ); + + if ( fromTalkerIndex == -1 ) { + return; + } + + int toTalkerIndex = FindTalkerByUserId( target->lobbyUserID, activeLobbyType ); + + if ( toTalkerIndex == -1 ) { + return; + } + + talker_t & targetTalker = talkers[toTalkerIndex]; + + targetTalker.isMuted = targetTalker.isMuted ? false : true; +} + +//================================================ +// **** INTERNAL ********** +//================================================ + +/* +================================================ +idVoiceChatMgr::FindTalkerIndex +================================================ +*/ +int idVoiceChatMgr::FindTalkerIndex( const lobbyUser_t * user, int lobbyType ) { + for ( int i = 0; i < talkers.Num(); i++ ) { + if ( talkers[i].user == user && talkers[i].lobbyType == lobbyType ) { + return i; + } + } + + return -1; // Not found +} + +/* +================================================ +idVoiceChatMgr::FindMachine +================================================ +*/ +int idVoiceChatMgr::FindMachine( const lobbyAddress_t & address, int lobbyType ) { + for ( int i = 0; i < remoteMachines.Num(); i++ ) { + if ( remoteMachines[i].refCount == 0 ) { + continue; + } + if ( remoteMachines[i].lobbyType == lobbyType && remoteMachines[i].address.Compare( address ) ) { + return i; + } + } + return -1; // Not found +} + +/* +================================================ +idVoiceChatMgr::AddMachine +================================================ +*/ +int idVoiceChatMgr::AddMachine( const lobbyAddress_t & address, int lobbyType ) { + + int machineIndex = FindMachine( address, lobbyType ); + + if ( machineIndex != -1 ) { + // If we find an existing machine, just increase the ref + remoteMachines[machineIndex].refCount++; + return machineIndex; + } + + // + // We didn't find a machine, we'll need to add one + // + + // First, see if there is a free machine slot to take + int index = -1; + + for ( int i = 0; i < remoteMachines.Num(); i++ ) { + if ( remoteMachines[i].refCount == 0 ) { + index = i; + break; + } + } + + remoteMachine_t newMachine; + + newMachine.lobbyType = lobbyType; + newMachine.address = address; + newMachine.refCount = 1; + newMachine.sendFrame = -1; + + if ( index == -1 ) { + // If we didn't find a machine slot, then add one + index = remoteMachines.Append( newMachine ); + } else { + // Re-use the machine slot we found + remoteMachines[index] = newMachine; + } + + return index; +} + +/* +================================================ +idVoiceChatMgr::RemoveMachine +================================================ +*/ +void idVoiceChatMgr::RemoveMachine( int machineIndex, int lobbyType ) { + + assert( remoteMachines[machineIndex].refCount > 0 ); + assert( remoteMachines[machineIndex].lobbyType == lobbyType ); + + // Don't remove the machine. refCount will eventually reach 0, which will free up the slot to reclaim. + // We don't want to remove it, because that would invalidate users machineIndex handles into the array + remoteMachines[machineIndex].refCount--; +} + +/* +================================================ +idVoiceChatMgr::UpdateRegisteredTalkers +================================================ +*/ +void idVoiceChatMgr::UpdateRegisteredTalkers() { + for ( int pass = 0; pass < 2; pass++ ) { + for ( int i = 0; i < talkers.Num(); i++ ) { + talker_t & talker = talkers[i]; + + bool shouldBeRegistered = ( talker.lobbyType != -1 && disableVoiceReasons == 0 && talker.lobbyType == activeLobbyType ); + + if ( shouldBeRegistered && pass == 0 ) { + continue; // Only unregister on first pass to make room for when the second pass will possibly register new talkers + } + + if ( talker.registered != shouldBeRegistered ) { + if ( !talker.registered ) { + talker.registeredSuccess = RegisterTalkerInternal( i ); + } else if ( talker.registeredSuccess ) { + UnregisterTalkerInternal( i ); + talker.registeredSuccess = false; + } + + talker.registered = shouldBeRegistered; + } + } + } +} + +/* +================================================ +idVoiceChatMgr::SetDisableVoiceReason +================================================ +*/ +void idVoiceChatMgr::SetDisableVoiceReason( disableVoiceReason_t reason ) { + if ( ( disableVoiceReasons & reason ) == 0 ) { + disableVoiceReasons |= reason; + UpdateRegisteredTalkers(); + } +} + +/* +================================================ +idVoiceChatMgr::ClearDisableVoiceReason +================================================ +*/ +void idVoiceChatMgr::ClearDisableVoiceReason( disableVoiceReason_t reason ) { + if ( ( disableVoiceReasons & reason ) != 0 ) { + disableVoiceReasons &= ~reason; + UpdateRegisteredTalkers(); + } +} + +/* +================================================ +idVoiceChatMgr::SetHeadsetState +================================================ +*/ +void idVoiceChatMgr::SetHeadsetState( int talkerIndex, bool state ) { + talker_t & talker = talkers[ talkerIndex ]; + + talker.hasHeadset = state; +} + +/* +================================================ +idVoiceChatMgr::HasHeadsetStateChanged +================================================ +*/ +bool idVoiceChatMgr::HasHeadsetStateChanged( int talkerIndex ) +{ + talker_t & talker = talkers[ talkerIndex ]; + + // We should only be checking this on the local user + assert( talker.IsLocal() ); + + bool ret = talker.hasHeadsetChanged; + talker.hasHeadsetChanged = false; + + return ret; +} \ No newline at end of file diff --git a/neo/sys/sys_voicechat.h b/neo/sys/sys_voicechat.h new file mode 100644 index 00000000..c49178d0 --- /dev/null +++ b/neo/sys/sys_voicechat.h @@ -0,0 +1,143 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __SYS_VOICECHATMGR_H__ +#define __SYS_VOICECHATMGR_H__ + +#include "sys_lobby_backend.h" + +/* +================================================ +idVoiceChatMgr +================================================ +*/ +class idVoiceChatMgr { +public: + idVoiceChatMgr() : activeLobbyType( -1 ), activeGroupIndex( 0 ), sendFrame( 0 ), disableVoiceReasons( 0 ), sendGlobal( false ) {} + + virtual void Init( void * pXAudio2 ); + virtual void Shutdown(); + + void RegisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal ); + void UnregisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal ); + void GetActiveLocalTalkers( idStaticList< int, MAX_PLAYERS > & localTalkers ); + void GetRecipientsForTalker( int talkerIndex, idStaticList< const lobbyAddress_t *, MAX_PLAYERS > & recipients ); + + void SetTalkerGroup( const lobbyUser_t * user, int lobbyType, int groupIndex ); + + void SetActiveLobby( int lobbyType ); + void SetActiveChatGroup( int groupIndex ); + int FindTalkerByUserId( lobbyUserID_t lobbyUserID, int lobbyType ); + bool GetLocalChatData( int talkerIndex, byte * data, int & dataSize ); + void SubmitIncomingChatData( const byte * data, int dataSize ); + voiceState_t GetVoiceState( const lobbyUser_t * user ); + bool CanSendVoiceTo( int talkerFromIndex, int talkerToIndex ); + bool IsRestrictedByPrivleges(); + + void SetHeadsetState( int talkerIndex, bool state ); + bool GetHeadsetState( int talkerIndex ) const { return talkers[ talkerIndex ].hasHeadset; } + bool HasHeadsetStateChanged( int talkerIndex ); + + enum disableVoiceReason_t { + REASON_GENERIC = BIT( 0 ), + REASON_PRIVILEGES = BIT( 1 ), + }; + + void SetDisableVoiceReason( disableVoiceReason_t reason ); + void ClearDisableVoiceReason( disableVoiceReason_t reason ); + + virtual bool GetLocalChatDataInternal( int talkerIndex, byte * data, int & dataSize ) = 0; + virtual void SubmitIncomingChatDataInternal( int talkerIndex, const byte * data, int dataSize ) = 0; + virtual bool TalkerHasData( int talkerIndex ) = 0; + virtual void Pump() {} + virtual void FlushBuffers() {} + virtual void ToggleMuteLocal( const lobbyUser_t * src, const lobbyUser_t * target ); + +protected: + + struct remoteMachine_t { + int lobbyType; + lobbyAddress_t address; + int refCount; + int sendFrame; + }; + + struct talker_t { + talker_t() : + user( NULL ), + isLocal( false ), + lobbyType( -1 ), + groupIndex( -1 ), + registered( false ), + registeredSuccess( false ), + machineIndex( -1 ), + isMuted( false ), + hasHeadset( true ), + hasHeadsetChanged( false ), + talking( false ), + talkingGlobal( false ), + talkingTime( 0 ) + {} + + lobbyUser_t * user; + bool isLocal; + int lobbyType; + int groupIndex; + bool registered; // True if this user is currently registered with the XHV engine + bool registeredSuccess; // True if this user is currently successfully registered with the XHV engine + int machineIndex; // Index into remote machines array (-1 if this is a local talker) + bool isMuted; // This machine is not allowed to hear or talk to this player + bool hasHeadset; // This user has a headset connected + bool hasHeadsetChanged; // This user's headset state has changed + bool talking; + bool talkingGlobal; + int talkingTime; + + bool IsLocal() const { return isLocal; } + }; + + virtual bool RegisterTalkerInternal( int index ) = 0; + virtual void UnregisterTalkerInternal( int index ) = 0; + + int FindTalkerIndex( const lobbyUser_t * user, int lobbyType ); + int FindMachine( const lobbyAddress_t & address, int lobbyType ); + int AddMachine( const lobbyAddress_t & address, int lobbyType ); + void RemoveMachine( int machineIndex, int lobbyType ); + void UpdateRegisteredTalkers(); + + idStaticList< talker_t, MAX_PLAYERS * 2 > talkers; // * 2 to account for handling both session types + idStaticList< remoteMachine_t, MAX_PLAYERS * 2 > remoteMachines; // * 2 to account for handling both session types + + int activeLobbyType; + int activeGroupIndex; + int sendFrame; + uint32 disableVoiceReasons; + bool sendGlobal; +}; + + +#endif // __SYS_VOICECHATMGR_H__ \ No newline at end of file diff --git a/neo/sys/win32/rc/doom.rc b/neo/sys/win32/rc/doom.rc new file mode 100644 index 00000000..57aba193 --- /dev/null +++ b/neo/sys/win32/rc/doom.rc @@ -0,0 +1,109 @@ +// Microsoft Visual C++ generated resource script. +// +#include "doom_resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "doom_resource.h\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "res\\doom.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "id Software LLC, a ZeniMax Media company" + VALUE "FileDescription", "DOOM 3: BFG Edition" + VALUE "FileVersion", "1.0.0.1" + VALUE "InternalName", "Doom3BFG" + VALUE "LegalCopyright", "© 1993-2012 id Software LLC, a ZeniMax Media company" + VALUE "OriginalFilename", "Doom3BFG" + VALUE "ProductName", "DOOM® 3: BFG Edition™" + VALUE "ProductVersion", "1.0.0.1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/neo/sys/win32/rc/doom_resource.h b/neo/sys/win32/rc/doom_resource.h new file mode 100644 index 00000000..c5029a9c --- /dev/null +++ b/neo/sys/win32/rc/doom_resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by doom.rc +// +#define IDI_ICON1 4001 +#define IDR_IDR_USYSGZPS11 4002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 4003 +#define _APS_NEXT_COMMAND_VALUE 24000 +#define _APS_NEXT_CONTROL_VALUE 4200 +#define _APS_NEXT_SYMED_VALUE 4002 +#endif +#endif diff --git a/neo/sys/win32/rc/res/doom.ico b/neo/sys/win32/rc/res/doom.ico new file mode 100644 index 0000000000000000000000000000000000000000..2a6b79ac0049b07edb55cd5b66b4aa7454d66794 GIT binary patch literal 22486 zcmeHvb$k`cvVR}99ErQTdx$#(qJ#tp5iDrXK!5}SgaAQ;+u|DBSy&tbBv=B$7k3t4 zWS0dNmim3?1a`T5<-Pmfy}$R*oe#~NIcKK3y1L|BRXt&h$HdIXhv6(`YgCMNV~p9@ ze7=@4rcyJe(KKH75Ha?hG5dVK4O`>Ln3d(1`*Dr?j>dku--h)NGS)YPF&FfSAy`K= zZk&H~5Rvw0m*MrSbl)$GClE2??k8Ai#ch^8?*+?V@`80;^^z5B{)OqewqU7~9}{m&z#yFWp14>G55Sv%ssc3d38F?>=PF;m-O|_v)vBnox7KX^aTxF zPqMf{4_HXo!%Q5skx4uzGMWE;rW;?&ESha%HmUoWeMTK~%DTWDGisP|z)7$*kj+Fo(n)%rbly3+sBG3GGHQvD-{0G|y*Z$FWRkmczvMLzvuu1Cu-9 zH={(Raw}*0A*-35-%Ms4v5Dz=3}<|iis@LjVlu-pW*N1fSwvvmutm(K={Dxp@(_#Z zd5iI_MlinRP$sY$!}vxWn82hP6BxB&B8N##>baGPtO}XfdKl9SJ;HQ@cQK9EVx}Lk zff)p@U~0#{%rfo-Xs%)o$rqVv;B*$4)d0Z}F~0d&Ca^3=pPfPfEG96+7-mI`uZqDu zMlgXP=HOVxgr>cjKtG+y+*UD(%~&RLTEZk&WlW&s#U#ea-Gd|YzARo z^O&ytbf)J%ff@QPW<0)_*+hQJ9O4f!#@AtdQ_RN@eVUJEeBC^>EG956W_)!D#!h5H zi%OiEF}^Yqw}&#Z%|a#ut$c+)6B%`7BEvi;Htxif4ilK3*G8uAv7A{1ZbIKG#?wh< zJXsU)XgvDu#CTFKCNL;O-x-W2ab-NQ9TQp1WDIv z?K{t(|M=6}|Hjb2ytz8Ig@sBXS2_1zcK63O|M-y~e`phE>fjiYu27VZj57-k99w_; z??3T&gk@k!`+dh*IA6Qw_q5VZ0z4UU+Zw@^km$!kjxmo^;{(6YFOIvii zkd|_2{@@v%=jvE?Txi;3%B9s2;g1;mtxMq2AOAGM+ok>^jvopuDQZ7x@a7EvW;>r%FzD-PIUUI5eyS3HD1AiXJE`D>Ju`UTe{62*j&Eig;8BjcN|GKc8 z$s5|!_7UyCDO>` z_WehXZhm;Z>F3&p$H%i0KT|YXx7+-UsZ)>Ky0-Pm{tbukgzPR&EsgZ>C=Gf2=-Q>D zTb`Zi*>l1C`J=2BYX^9js2kO$yJu!*=BPyl1qFM`3$HF5-95ea;;V&wNA-w_P45vO z-==LsLNit33+{otN})n!rP6hFu=j1=EUQmNNR^#^LR+UMt*r{HR>U@K8trS_#K|c{ zU#Psx-M?7lqf)h=^-blb4V#W0KJ%!5`KE21if7L{bGtO@-th+^(4Ks}cy3DvaAooO*g*$R5o;47k$#WgH-fBYez_a5Q1~wmdazb(S?nKD-kCxr8 zT|2-3-mQgBS&t5v><+kjcx}gL6H;o1Zphsq&^2&oh)pI`or_fy&Usu;$ zTUT5>@mzUD;kJ^Lv+aw|ZEv4*VDPb76%}<2tE;!KyEZfI(4#q4xc_a7T{R!3tef0^ z-Q(-ep6n@1+*7$}^3X$vhRtYbn3Gu8`{C)yTOLihemrdAwT0Y#e{I)o-^cC6GgeQ! zc7Mae{@Y4@`+ie-bMyYwt4}X=ZoR3+iOrAptv-MKTF%y6(fz>BPfHRO?Ay@(`sVo; zlV@a@H=LQ_mry!q-P!50+B&5jxY)ecu(0_J8_w4p*|6Df8K+~HTg>|~V>@j2l#TZ@ zn>{OfaB!(b%jea*gQH6hrX1=Ue|*T0nw94Uf<(W|ocz2ESTrU4@rBxzA0Ax`NS`&S zxbnscrxi;IM;sL+h=QR8FjZJ^i<`QW-?9jE2sfJtQvcP@qvCDShNA`1GnvF$qNo7h}3)^q^yR`pM+uEu7zZo_BxT&wtq}VOe`Gl~S2d ze5PZEnuy|k?9uBF2X3vjiIz!~feX6ZF8hNT?zxwJ{bF<#%FMa7GY8F_-)ZYdSipV1 z?K?GTc9cvgYic+8?pM8YAFl>F$yGY%e!l?kfw$l9@w}Yf5rb^@VT|?(X^t4Cq zhwBf9bv2SYE`Ir?)4x2w8*6Ibyjwy3zU3d_wyvKZ(xtntQ}ADr{?$Xjc5WLGkTrVY z!aYkyl%@OGJM>%l*OY&CKh*8*)unv`LzOD$px(>sZ-347zqs$Ki%;)=dGqw?k1yVR z`s&NyKLP`BFo?rKpU>55?e!RaOrZV$Uve(vy80XARCC{dMbB;S z`YU?>JfAP~{4(z^d>}l=@#S|up(8EdzVPu2U#XGLK)lQbEgJ1Vaq55K)c?e(|A|xo z&2j4cU+yleulwQs?>^w`51-y%9$ldl3iT?g>hAvf^)K4bFS7bqjqL8PP{sGyQ`LX; zUwU!=r*Hd>>2)EwSG-)+EgUYQ*=}@!MYKmM-4lU&0pI4cRKfGZ~iS+;Bsa}yei#8t}`R0fz|Gu-}f`j z;@4GOEh&CBz3AEP5br0iLR$a29wwDtEcgt3;#Z8mQIcHr@cjLQ`eRPs9iw0Jk$t>A zt@gsQ(b^H}volkle7s)s=;@e{!Vp!6{@vyCN_zFmu*s~@ju0d)45{efziLH!MMdf5 zS)~;rrG-j8U6s;G7zozH2PyPR%ZHbj?|wO^JT*7GvZQeOm{=oQBP+Qu32b^Xjrs z|EVf}aPjz$n|Jm2Le&*_qPpF)ON0oLcAS+jBlEn%5u0gMlmd_Ux5u8C_IY(R9tK{UiOZc2-)2*A9%V z+S>?R>P_QqqE%7e(h)huE`vnn(&!j@T54oL(iZ_3ZIKzIFIRbc3?4Y(x5|uom7T(K zsI`3**E@H#>7yqXs=ICqzm&Y4ZN4yHN2!qMSoHe?&9w=X%L{rw%FD}z;9RdA+QUky z%KGDHKEGYRq~5()=d|m$_K7a#XDrFqZLzem?=Sz?t*9vMlAj?GWfUx`$nM`5GJpAz zJIBAQ8{I!Cq%gZ8YfoML*T4M!`O~KtcMjft`IkL6;xqhz+ZwP;Kpc$VAO&zq)NMcv zu2loUlmXp*X{-@3ap>u9wlDW_=W0BA`*S~BpIqOKP(`2}lfx+*G#j-4;FSO1l>gwA z|KODWHcnZZB(&@EyM0>`Ff3l!YiL5-#%}_;x!#JG`X9fU8Nhw9qSadK2O+;$t{L}X zZaZ9uE-g5IS=Cqd{)$e9KQ4v({r6UlNgiHdXJRF^;3wo;wryzrgW=?Ux4Y~4ui#f? z?CV}pb>qR5Ukd$mP9^c%K4=}acGd>}|Jx1}LX2dLDNvM=nlYwh&zRhmF_|YC zNYFNX7J(!bgtYfCT~H%MQ9qiB zHcQ)>dJfVJ9zdCUOW zgVEYDCJY4Wrf68lf7d)*QGvI`80O%cUI|ZNuwS4Kdmyvy^s8aO1afY*o`QSVP~5OH z)M4R|RJ!LK^{IM8d9zoS;mT@P^z;7)%q#dEq0hcMDOC9-l*hBWsmyk)swv{{P?0No@v`!g&s1&kE^!jU<6Oo_JUXoMexCzGom8&n zF>?>rs1u0{Taj~aJ=wOHPeOg@ zhwWJKwi##^6W_du1Qva9?SpGR@x|`YqjXYO4j`Az-DDAkXN+=R!=z#zI9bx_1E8Z##gtzY@2+ToLkqD%6}bbOCo{waFW;#BdOaI(oNk%V$(F@>jYyR{BRB+ zp+Ppr_oA@=M@Z#4kAxOOpc}rV7g|Mu1$88IUx{_<3_hp{8#AB-9k6LZLqgq5=uTG> zsk2Ea4b!fNQ0YQ`-OrJ4@2luL<~e*I^nuf9t-t#_wsoFRAj!1nNrDc#cDPF6rFY3F zW;ZceQ-qnu#1lbYJl2vr{TN|?6e$z;Lmm=HXjckaT0wWSNoCWPLi?Q{iQ{ZiH{C{j zt6?OxucY7s7ci$81ba_Hx0%G#34<-_3;8i7p)!?t0)4DQB4K}smVPjpFFKYhN$bDCjD)o(ss&yZW^i;#_e#Mf<)=i><*fJk6J zlH@Vpky&@ppLB?L3ao({WZz{vWM~kXhfjv=oFYxr8WLONlhA!3$%FTkWA-t!jG6%( z1XY-|;5MJcq2Ga~so-^Q;_HDfGtg~P0NR>BhGEZT%}C|5l#-@A zCEeH)(9OY+IXmc0Q{pS(w@hbIXz_irPB{d5L*F7N*it2E{6aq;pSd|eFZfE#EeLav zYn#}h1vTk?iY#JRKt}>^@OUy{4J}^cFdvS3p;WhdtlE3xqu8=rFIWyWz1x~LTVU4)1&h_!u~_j_4|f|GJD8HGUNoH600)BrM)&Mw|NhPZIWWC_3cUf~@P|5B$)PK_(YCZKSrBpni z^chbmZsbi0>3fd+x}Knb?&ry?{Sk7{J_Mh<2bg6$S%t4Azl<}~qUbR>CLEx^o>$1f z=Q&{0)npUDfze@F6IDP$896m>Y zoP01w@PHc>KIkSz7Tuy~$X$oIFKP7tx0F5W8L7Qje&C6W;19SMi0Ml763QJjk5X*O zRr1I^Ntv@AQ=3^Y$tv+U>A0_j9V{b}-5e5H!se+`U~|Tk&l)LaH1@(DAaV7VF$M?c+c$@+25ywf*#JVZ# z-P-IYziy{UYMet7O#u1zsHcvrUXoqHPK?`+1j^>HYoR3d-vHY^8K0rIz%G1Q1Sy=B zQ)vI|kl~huJ&YuDnnmiC)r7saBzBue%H})q-89U56fjUa*7AGIV=HXfI1<_bf0z}M zJZ=}sqW2I_)e(KL$8oW&R)bz9kt_&>_M3sAD#s} z+nJamk94!ENx%D1@U#cytqUo_)<7$=8K# z(}j(X0u3ox3-CznNGkW)6fx*BIVA7I@5Y#4A!PMi=+XvqO4&dHDSWCi^g%Bd@(X%I z-te&)*LM}>>xP(X0CXS?^8W}w{* zc*_;v!B3*`6-ngV^(=X2?^}ki)DPGq1+*-J4g+IKV9$Xs`38OP zIT$fQ5@C-z{2X)y{!ioFg&dmiCXoeXMkkh>QrA*w(N&Vz3_@QHByxlt2CX2bo3G^q zU)2gYtP||XMoJh4UDF+aZ4!<2i;RbSY=wAM*L^1FgdfyN)bf$>Z83i*%twXwhy(8F zMq>NLJw+STnoC&&t zUxiNSI}@>TCp>$e^xaocMBx>3%WEKk0&~W?3oU_}j1Zrhbs@gZXy7mKN0ET%I*|hO zx1RYUo7&JdTTMk%$WUA0u0j$!z{csJKc+#GLdQL@#?8sVeJRBi+@*k= zT2gy1g{_}WB1`xNvq2=b9)P|)Av<2s(-M3Ko=W|JK>|tcyo7wSYACkXEwYK+g#K)? zez0d2gP30V`h>^Hh81Wd-xB&8k zd2sZ@o(OG5KrRtCLoaNT50Gb8El0x>l-YW5{tI|~xmC<6vTa%gzYvUPRInAPBz7K) zKBVA%Gx#aUlGuzy&WK4(dXZ3WNnw3XfuBLpCFq4|U-IsBiX4;HkzQCWNnDnok9HV0 z3;nDhnae7&iCY96y#X7t3Na_*PROcTn`)AqhGUI}kvMP-WC!{tfh|xcV-As8+r?95 zfL>rlbprWxY9PDBec(;_RmejYbU=>@e2WYN7m{nMZ6q~qqm>aZw&Uyg;d5htgFEx} zAkVsq&jtu;v zXCj*eT+X;L?;RN~G0ynev+Z`$v4uZG3@Y~;*SMAvEAYw|pE#0}q#lF=}kVTtjYe`~1k)*C=u)DBbkUj2MylBzL6QLzAxdpKPS@aFQi<^MQneg!= z;UlMDOpI?l9#0qkExuC0$k4ssC7Bk!%v-Q!MHFJ*%8;d)*Z5G(H{y`c5z z8_%jEv7Y&Omh)*`Y%0LoL;{~rCHLH8@Uh5gsvwupQzk-8V=^3l7m}g>8fpTJiT8Iv z`(z4ee~^rVmVzGma{Jl1*9kU$0Eyj!XO-b(mvNk|Vu2$Ri1{?>7>kP~#l%NmOzbru zHpmKCq90<&i)0uAdnIz(4~&D{Jy(C=j(X%%lD$O+S=R%5!ZxH-18>HHUSrU%13d}{ zUmdZg-Xzo{l1r;~@cnC$ZzutOfKN<^qKzb}a}{!3+bDG4b&NL-w3HB&w*>y4NS+aroE{@Av)ysK2$0?@3oW9+@CpGX6$8X$&{!Ad_ok#|JAv5wpJ{I=H z7TDfvBV=qENs$v$`BVYp?I7>2mtb?pV$3wqwihy0(a1}wJ=vxm#oTU^N%S_X6?}vX zFn&rcV&el8fxLxX+d7O>P6D4*Bmib*(m-HTYtr#sj(F%SnMT82NPTM>>knKF@772ZJYyqFz1U!rhVXdmr%&S@9}vsWO)Ip!}cOibOAZqi^!u- zMSczWIsYmWxy~SgBe1A>FUT*jAo5c}lRl*JSximK??ILiL-vsuz+6S}$A|L`gLjj4MjfS0en1LO#A;^4NYipB z@nFxKx}Bx?Nk5Qv>zki=61;H796cC|=U$%r8%I5I?DZ<1G^)xzqZTpq1~LeQofDap z$f^|Bypj?|U83M2KagAgRcbo^Aq5s*B2CCfQbp~AKF%VM%QWx}n89TZ&N2yaEL-(Q6V&skB7mb^3C-3|Q#IYx^ zhFSGo4W+RT^fyS?ex=m|)GVU?MMedOo!jgp`;?WWAFv2DiLt0z)FL*1N~u$R#8`LG z?;4UiR3f+1og~iiGZv^%06)on<`5s>bADV)KYRv5Zb$1sq(QJJ4zQI<vN~8+D6{cK$-imG?>Q*n>nmp75J?$lJn(8g(F^KKwjt88Tnws})=x z8T$VvKM4EXXorzwpA)N;__%q z5q|*>0<-W0=I~$cz{kL84yZ>M0e8!t@f-5(Tr7%w9TyX6^RXfe=r@-y<#0IWEet^1 zJ`8qw3t46CqquR8DC3*w z*sfD#=^b+IaDmi*OYTX{I?tBrw|8W06#Nr6_P^rL>Vv?{?2=q&kP{>~@|&%8PQPsy zGZVR?o#c~;d`ISHVE?UT0)DDozacg93i|-{OCg1;T2?s_44d+mV%I5Y=Szs z2Xd>H2?r4qPWnNjpS((}6Wu~8Ezo0Z9`y8IMf=zD*Um$zV{%p@)wJ~!>v)ul)J+b{ z%rk#B@S98)kqgKSYsNufpnYTp93XR={TBNn zP70*KlZCR_Xs-Ui*b2z`zk}|t=fHh#oCnTQp{ha66UIisw-oV&PRn^Bx1-39+!kok zUx|!+eH5D(eiZ0;eJ#**zQa?boZv~r*C1XR!B}7dW93GmnUjzw99l!5_I_|UHdk=o`n|0RR-&8?$?}TR9A+LzO!xLNA0Z*;Qa~wVG(SjTMVE>G-<9H3s zEpf!(HYgICEZ{kL!Jh0y`s~_gzVH zx5d8*4U%sl&$9#laCE?DF^9%J|BUbP?Oz_;*gD9c`0%8jYbE9bUx)O%Pl4^~$uS-= zF!IV={mM3NJ{4_#OBpkNLfl(`yen|D>uPdsafE_9p1^*Q8swn{yahi{TjtM0pWM1| zvVy+q1byhK`fi?Y> zbnFL{B=iFE#W}!~gArE^Cb^*l1@=YFY`{(GwB$MJ3;nUD1bKd=ZirE`5Wh`<_A%7; z63MI8an$Qj7g9EV1i9d7;P}9;#h>K?GHPiqv>3X_we3}M0=^a_*DFMQ#=P?>#Bazm ziab&4h$TIjL~1+x9@)08Bd_i!C}G?^(m^c1IDEiidbvO97hO@0z@93N1L-4AtS5QYx4_}u5X;TS9-0zL zM_$}GaW|Q#pQRpaULkK5Nm8p`B=Xt-I#I*I{P_|)5~Efi*4UAC)LQucb;#j4LLRg- z$>{~Bo7}dZjgrVsQvHqNjz3E% zy^UH)N8~T|f``aWecm^~=ki0?kHR#4$g%k~${F~AS`<8{#KAvO=D1hXYVb4aG~zXK zAOp4ION`r+61hpaNiazbV^DY0NAn{C?_7%RgFI`{ws!)l(K?LH*-4Gkv8b%goS$gi z{$DA)+kHwL_KbX5*ONPHY98r`(NO!f3fo1t;rl4C-z5ffc4n9ax1^21tgclSy;uUfhB7MsX1{Tk%s5(V_XPu@K)W3SOQN*Mbibz1g@rXT)DnWJA} z|3=%p7@PAKb|Rx8XCr%F!~U|4$bapEJ$*td$BoEOG$)bU7Q{TrT|u6t&Ic%^2=zS; zavlM@NR@toz3)v(NgT6kYtl2-3dDLo( z>T(x&z4!(C^nIG#P-_<= z?}l6lsZzfqRoV{ddm3bKE=es$Qh1LmB=tgW&3qhkVbdw1e?XS;l_a3h)pMxeI)YShM6SA zo;QIe4tX2ov?Yj7^aHRLX$kbi5_xj$dCY#S&2OfZ|3Ln|8%Qq}HBV>kS3x~jq-;i6 zWlvyB*Z%fpe1+St_euRwdlPtIoX(Bx_*zm%w~@N zTKN;Ivg`d?pCpB8JmUWzsIwvGYf*t`kU!9E2L5*-&*YQ%Z5An=R!~&W%VZi+i{Fta zQDk7>$2a7Zu!llA+$7tuQ)KJ|+sVyCzXj}p5%`#|9bbUlg2HM5g?79I+WI}i_*(rF zD6^K?$L}GHV=?laW!if=dig5s{j;h-Ew&229W_?eUId1HFoz5Z?Q)A8;v2{$2sz%I zi)4=6qE0{!WNZrgz~>1hQt%sjN=*y+Bs1s-w+17D6ULKg=4o8Fx{NWj_J=14>7jOC zM*i&}@7TA%kdNl0roXjNAm1`_GGD621QCWSvH$VO*)@PPQwTI!V5gKYM-R{d z{^+-%!2Ei0$^pGPh}lu2=Xfc07zbRE4QzCpBo51eLjA1}-#7XnhOYH{PV>&a2^m@k zIn=<=qmp}n`x>?ABFz0y_=8K;j3{wO&O35QJz2zbB)3j` zC<}R`?(5%B+Vp4S+Upu=k}-E<)HRVq=39=ZW)Wj4Yu)V<@WaqM3%7CE~MugKw{(q^SrtE;vXL<5n8tWQ7LyU8?N%*e$OlCIyHqa z5ASjv_G7HpPHJs6{1xZ3xX%i+B+_*%A-}fu)NJq%(3@vulydc%&~(%+iGhzL2K=k_ z{>Maczam{L2nfJl#Ay=q;(KcM6^Os~Qj^?sS{t4QpP5p4gPImxgF4T*o(&s_bu;Leuv)+&Axde zG@EdSZ&avj^kv2f;Kz$nu!nJBxg);QPHTFM0 zw9lgtLG|Z;xiQe9Fy2TAHMyW~4Vn$we=|?P(ZfCBhh^=;6PZsJST|cGka^<0HSt`0 zKNgMiZB5Yh(U4{N{otO}XFSs+i9u$y+HuU=p!TOf4c+-G*6;Qwizcf+$V~F@fW(cs zkLxEH%@mF6gL_tuKjhfnB7Dm`&(t;8moWl)+Cmz7=ojGaFp`=TAs4tBF~Ne5Jb~j$ z+&>kK>w}|1%ojILc5Aufqn__{lErU^UvUJc??VZ5-jYk}g}63B3ni6RJ~?G<{=^gc zp1^Y)9Zi@r%)&Bo{TmbPXJhIPB*(sCo-~G{N}iAedzYgIoj`ob*+h}HiIhIrZ#oA! zN3#jfchMw$Q_&=MFBy8Eu3&@O3Uc^ro1v6b_71pV9_5UEM+VkecuyppG`1Q62Ux$YjHu5Po z9rv83T>eB+`3JElj7_!*S^h?D(wvftPLO>J;#KT*bZl`F@2&JBIk)G+WB}^>sOPJO zkbd|^iYq%w_RZ#!ZOA?@)iuSh&6Qt;VoQjO3h&lzppe`fe2F1h#T)__jVBY2Nfg!X6&VKZ z!t3UwHmK<5?x)c}`!LzVA}QkZ${bnR$z_KcySS@W8%P z?C0U?Xs9jl#d_3q*f|<;^c8vaJp7R-j_$}khQ`Uh_Rwv0$ke?}-4iEZBTKwrr(@fm z#3CulHKwHRGK75F*HirXS3gV5Tc+ayZrs1Je>`55NFeW%tFj+($|h*mPu^{>zjsVH z`Nk|_)fIu>h%&|tLS0V#$1r~%w;G?ywDvS*I%D@i?+vuZJOyLB@VOrCTQtmz%||Pt PGWLY3dAsSh;4J+=5?>2Y literal 0 HcmV?d00001 diff --git a/neo/sys/win32/win_achievements.cpp b/neo/sys/win32/win_achievements.cpp new file mode 100644 index 00000000..45f7afa9 --- /dev/null +++ b/neo/sys/win32/win_achievements.cpp @@ -0,0 +1,103 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "win_achievements.h" +#include "../sys_session_local.h" + +extern idCVar achievements_Verbose; + +#define STEAM_ACHIEVEMENT_PREFIX "ach_" + +/* +======================== +idAchievementSystemWin::idAchievementSystemWin +======================== +*/ +idAchievementSystemWin::idAchievementSystemWin() { +} + +/* +======================== +idAchievementSystemWin::IsInitialized +======================== +*/ +bool idAchievementSystemWin::IsInitialized() { + return false; +} + +/* +================================ +idAchievementSystemWin::AchievementUnlock +================================ +*/ +void idAchievementSystemWin::AchievementUnlock( idLocalUser * user, int achievementID ) { +} + +/* +======================== +idAchievementSystemWin::AchievementLock +======================== +*/ +void idAchievementSystemWin::AchievementLock( idLocalUser * user, const int achievementID ) { +} + +/* +======================== +idAchievementSystemWin::AchievementLockAll +======================== +*/ +void idAchievementSystemWin::AchievementLockAll( idLocalUser * user, const int maxId ) { +} + +/* +======================== +idAchievementSystemWin::GetAchievementDescription +======================== +*/ +bool idAchievementSystemWin::GetAchievementDescription( idLocalUser * user, const int achievementID, achievementDescription_t & data ) const { + return false; +} + +/* +======================== +idAchievementSystemWin::GetAchievementState +======================== +*/ +bool idAchievementSystemWin::GetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) const { + return false; +} + +/* +================================ +idAchievementSystemWin::Pump +================================ +*/ +void idAchievementSystemWin::Pump() { +} diff --git a/neo/sys/win32/win_achievements.h b/neo/sys/win32/win_achievements.h new file mode 100644 index 00000000..97882c93 --- /dev/null +++ b/neo/sys/win32/win_achievements.h @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __WIN_ACHIEVEMENTS_H__ +#define __WIN_ACHIEVEMENTS_H__ + +/* +================================================ +idAchievementSystemWin +================================================ +*/ +class idAchievementSystemWin : public idAchievementSystem { +public: + idAchievementSystemWin(); + + bool IsInitialized(); + void AchievementUnlock( idLocalUser * user, const int achievementID ); + void AchievementLock( idLocalUser * user, const int achievementID ); + void AchievementLockAll( idLocalUser * user, const int maxId ); + void Pump(); + bool GetAchievementDescription( idLocalUser * user, const int id, achievementDescription_t & data ) const; + bool GetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) const; +}; + +#endif // __WIN_ACHIEVEMENTS_H__ diff --git a/neo/sys/win32/win_cpu.cpp b/neo/sys/win32/win_cpu.cpp new file mode 100644 index 00000000..65d819fb --- /dev/null +++ b/neo/sys/win32/win_cpu.cpp @@ -0,0 +1,1105 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "win_local.h" + +#pragma warning(disable:4740) // warning C4740: flow in or out of inline asm code suppresses global optimization +#pragma warning(disable:4731) // warning C4731: 'XXX' : frame pointer register 'ebx' modified by inline assembly code + +/* +============================================================== + + Clock ticks + +============================================================== +*/ + +/* +================ +Sys_GetClockTicks +================ +*/ +double Sys_GetClockTicks() { +#if 0 + + LARGE_INTEGER li; + + QueryPerformanceCounter( &li ); + return = (double ) li.LowPart + (double) 0xFFFFFFFF * li.HighPart; + +#else + + unsigned long lo, hi; + + __asm { + push ebx + xor eax, eax + cpuid + rdtsc + mov lo, eax + mov hi, edx + pop ebx + } + return (double ) lo + (double) 0xFFFFFFFF * hi; + +#endif +} + +/* +================ +Sys_ClockTicksPerSecond +================ +*/ +double Sys_ClockTicksPerSecond() { + static double ticks = 0; +#if 0 + + if ( !ticks ) { + LARGE_INTEGER li; + QueryPerformanceFrequency( &li ); + ticks = li.QuadPart; + } + +#else + + if ( !ticks ) { + HKEY hKey; + LPBYTE ProcSpeed; + DWORD buflen, ret; + + if ( !RegOpenKeyEx( HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey ) ) { + ProcSpeed = 0; + buflen = sizeof( ProcSpeed ); + ret = RegQueryValueEx( hKey, "~MHz", NULL, NULL, (LPBYTE) &ProcSpeed, &buflen ); + // If we don't succeed, try some other spellings. + if ( ret != ERROR_SUCCESS ) { + ret = RegQueryValueEx( hKey, "~Mhz", NULL, NULL, (LPBYTE) &ProcSpeed, &buflen ); + } + if ( ret != ERROR_SUCCESS ) { + ret = RegQueryValueEx( hKey, "~mhz", NULL, NULL, (LPBYTE) &ProcSpeed, &buflen ); + } + RegCloseKey( hKey ); + if ( ret == ERROR_SUCCESS ) { + ticks = (double) ((unsigned long)ProcSpeed) * 1000000; + } + } + } + +#endif + return ticks; +} + + +/* +============================================================== + + CPU + +============================================================== +*/ + +/* +================ +HasCPUID +================ +*/ +static bool HasCPUID() { + __asm + { + pushfd // save eflags + pop eax + test eax, 0x00200000 // check ID bit + jz set21 // bit 21 is not set, so jump to set_21 + and eax, 0xffdfffff // clear bit 21 + push eax // save new value in register + popfd // store new value in flags + pushfd + pop eax + test eax, 0x00200000 // check ID bit + jz good + jmp err // cpuid not supported +set21: + or eax, 0x00200000 // set ID bit + push eax // store new value + popfd // store new value in EFLAGS + pushfd + pop eax + test eax, 0x00200000 // if bit 21 is on + jnz good + jmp err + } + +err: + return false; +good: + return true; +} + +#define _REG_EAX 0 +#define _REG_EBX 1 +#define _REG_ECX 2 +#define _REG_EDX 3 + +/* +================ +CPUID +================ +*/ +static void CPUID( int func, unsigned regs[4] ) { + unsigned regEAX, regEBX, regECX, regEDX; + + __asm pusha + __asm mov eax, func + __asm __emit 00fh + __asm __emit 0a2h + __asm mov regEAX, eax + __asm mov regEBX, ebx + __asm mov regECX, ecx + __asm mov regEDX, edx + __asm popa + + regs[_REG_EAX] = regEAX; + regs[_REG_EBX] = regEBX; + regs[_REG_ECX] = regECX; + regs[_REG_EDX] = regEDX; +} + + +/* +================ +IsAMD +================ +*/ +static bool IsAMD() { + char pstring[16]; + char processorString[13]; + + // get name of processor + CPUID( 0, ( unsigned int * ) pstring ); + processorString[0] = pstring[4]; + processorString[1] = pstring[5]; + processorString[2] = pstring[6]; + processorString[3] = pstring[7]; + processorString[4] = pstring[12]; + processorString[5] = pstring[13]; + processorString[6] = pstring[14]; + processorString[7] = pstring[15]; + processorString[8] = pstring[8]; + processorString[9] = pstring[9]; + processorString[10] = pstring[10]; + processorString[11] = pstring[11]; + processorString[12] = 0; + + if ( strcmp( processorString, "AuthenticAMD" ) == 0 ) { + return true; + } + return false; +} + +/* +================ +HasCMOV +================ +*/ +static bool HasCMOV() { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 15 of EDX denotes CMOV existence + if ( regs[_REG_EDX] & ( 1 << 15 ) ) { + return true; + } + return false; +} + +/* +================ +Has3DNow +================ +*/ +static bool Has3DNow() { + unsigned regs[4]; + + // check AMD-specific functions + CPUID( 0x80000000, regs ); + if ( regs[_REG_EAX] < 0x80000000 ) { + return false; + } + + // bit 31 of EDX denotes 3DNow! support + CPUID( 0x80000001, regs ); + if ( regs[_REG_EDX] & ( 1 << 31 ) ) { + return true; + } + + return false; +} + +/* +================ +HasMMX +================ +*/ +static bool HasMMX() { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 23 of EDX denotes MMX existence + if ( regs[_REG_EDX] & ( 1 << 23 ) ) { + return true; + } + return false; +} + +/* +================ +HasSSE +================ +*/ +static bool HasSSE() { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 25 of EDX denotes SSE existence + if ( regs[_REG_EDX] & ( 1 << 25 ) ) { + return true; + } + return false; +} + +/* +================ +HasSSE2 +================ +*/ +static bool HasSSE2() { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 26 of EDX denotes SSE2 existence + if ( regs[_REG_EDX] & ( 1 << 26 ) ) { + return true; + } + return false; +} + +/* +================ +HasSSE3 +================ +*/ +static bool HasSSE3() { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 0 of ECX denotes SSE3 existence + if ( regs[_REG_ECX] & ( 1 << 0 ) ) { + return true; + } + return false; +} + +/* +================ +LogicalProcPerPhysicalProc +================ +*/ +#define NUM_LOGICAL_BITS 0x00FF0000 // EBX[23:16] Bit 16-23 in ebx contains the number of logical + // processors per physical processor when execute cpuid with + // eax set to 1 +static unsigned char LogicalProcPerPhysicalProc() { + unsigned int regebx = 0; + __asm { + mov eax, 1 + cpuid + mov regebx, ebx + } + return (unsigned char) ((regebx & NUM_LOGICAL_BITS) >> 16); +} + +/* +================ +GetAPIC_ID +================ +*/ +#define INITIAL_APIC_ID_BITS 0xFF000000 // EBX[31:24] Bits 24-31 (8 bits) return the 8-bit unique + // initial APIC ID for the processor this code is running on. + // Default value = 0xff if HT is not supported +static unsigned char GetAPIC_ID() { + unsigned int regebx = 0; + __asm { + mov eax, 1 + cpuid + mov regebx, ebx + } + return (unsigned char) ((regebx & INITIAL_APIC_ID_BITS) >> 24); +} + +/* +================ +CPUCount + + logicalNum is the number of logical CPU per physical CPU + physicalNum is the total number of physical processor + returns one of the HT_* flags +================ +*/ +#define HT_NOT_CAPABLE 0 +#define HT_ENABLED 1 +#define HT_DISABLED 2 +#define HT_SUPPORTED_NOT_ENABLED 3 +#define HT_CANNOT_DETECT 4 + +int CPUCount( int &logicalNum, int &physicalNum ) { + int statusFlag; + SYSTEM_INFO info; + + physicalNum = 1; + logicalNum = 1; + statusFlag = HT_NOT_CAPABLE; + + info.dwNumberOfProcessors = 0; + GetSystemInfo (&info); + + // Number of physical processors in a non-Intel system + // or in a 32-bit Intel system with Hyper-Threading technology disabled + physicalNum = info.dwNumberOfProcessors; + + unsigned char HT_Enabled = 0; + + logicalNum = LogicalProcPerPhysicalProc(); + + if ( logicalNum >= 1 ) { // > 1 doesn't mean HT is enabled in the BIOS + HANDLE hCurrentProcessHandle; + DWORD dwProcessAffinity; + DWORD dwSystemAffinity; + DWORD dwAffinityMask; + + // Calculate the appropriate shifts and mask based on the + // number of logical processors. + + unsigned char i = 1, PHY_ID_MASK = 0xFF, PHY_ID_SHIFT = 0; + + while( i < logicalNum ) { + i *= 2; + PHY_ID_MASK <<= 1; + PHY_ID_SHIFT++; + } + + hCurrentProcessHandle = GetCurrentProcess(); + GetProcessAffinityMask( hCurrentProcessHandle, &dwProcessAffinity, &dwSystemAffinity ); + + // Check if available process affinity mask is equal to the + // available system affinity mask + if ( dwProcessAffinity != dwSystemAffinity ) { + statusFlag = HT_CANNOT_DETECT; + physicalNum = -1; + return statusFlag; + } + + dwAffinityMask = 1; + while ( dwAffinityMask != 0 && dwAffinityMask <= dwProcessAffinity ) { + // Check if this CPU is available + if ( dwAffinityMask & dwProcessAffinity ) { + if ( SetProcessAffinityMask( hCurrentProcessHandle, dwAffinityMask ) ) { + unsigned char APIC_ID, LOG_ID, PHY_ID; + + Sleep( 0 ); // Give OS time to switch CPU + + APIC_ID = GetAPIC_ID(); + LOG_ID = APIC_ID & ~PHY_ID_MASK; + PHY_ID = APIC_ID >> PHY_ID_SHIFT; + + if ( LOG_ID != 0 ) { + HT_Enabled = 1; + } + } + } + dwAffinityMask = dwAffinityMask << 1; + } + + // Reset the processor affinity + SetProcessAffinityMask( hCurrentProcessHandle, dwProcessAffinity ); + + if ( logicalNum == 1 ) { // Normal P4 : HT is disabled in hardware + statusFlag = HT_DISABLED; + } else { + if ( HT_Enabled ) { + // Total physical processors in a Hyper-Threading enabled system. + physicalNum /= logicalNum; + statusFlag = HT_ENABLED; + } else { + statusFlag = HT_SUPPORTED_NOT_ENABLED; + } + } + } + return statusFlag; +} + +/* +================ +HasHTT +================ +*/ +static bool HasHTT() { + unsigned regs[4]; + int logicalNum, physicalNum, HTStatusFlag; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 28 of EDX denotes HTT existence + if ( !( regs[_REG_EDX] & ( 1 << 28 ) ) ) { + return false; + } + + HTStatusFlag = CPUCount( logicalNum, physicalNum ); + if ( HTStatusFlag != HT_ENABLED ) { + return false; + } + return true; +} + +/* +================ +HasHTT +================ +*/ +static bool HasDAZ() { + __declspec(align(16)) unsigned char FXSaveArea[512]; + unsigned char *FXArea = FXSaveArea; + DWORD dwMask = 0; + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 24 of EDX denotes support for FXSAVE + if ( !( regs[_REG_EDX] & ( 1 << 24 ) ) ) { + return false; + } + + memset( FXArea, 0, sizeof( FXSaveArea ) ); + + __asm { + mov eax, FXArea + FXSAVE [eax] + } + + dwMask = *(DWORD *)&FXArea[28]; // Read the MXCSR Mask + return ( ( dwMask & ( 1 << 6 ) ) == ( 1 << 6 ) ); // Return if the DAZ bit is set +} + +/* +================================================================================================ + + CPU + +================================================================================================ +*/ + +/* +======================== +CountSetBits +Helper function to count set bits in the processor mask. +======================== +*/ +DWORD CountSetBits( ULONG_PTR bitMask ) { + DWORD LSHIFT = sizeof( ULONG_PTR ) * 8 - 1; + DWORD bitSetCount = 0; + ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT; + + for ( DWORD i = 0; i <= LSHIFT; i++ ) { + bitSetCount += ( ( bitMask & bitTest ) ? 1 : 0 ); + bitTest /= 2; + } + + return bitSetCount; +} + +typedef BOOL (WINAPI *LPFN_GLPI)( PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD ); + +enum LOGICAL_PROCESSOR_RELATIONSHIP_LOCAL { + localRelationProcessorCore, + localRelationNumaNode, + localRelationCache, + localRelationProcessorPackage +}; + +struct cpuInfo_t { + int processorPackageCount; + int processorCoreCount; + int logicalProcessorCount; + int numaNodeCount; + struct cacheInfo_t { + int count; + int associativity; + int lineSize; + int size; + } cacheLevel[3]; +}; + +/* +======================== +GetCPUInfo +======================== +*/ +bool GetCPUInfo( cpuInfo_t & cpuInfo ) { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; + PCACHE_DESCRIPTOR Cache; + LPFN_GLPI glpi; + BOOL done = FALSE; + DWORD returnLength = 0; + DWORD byteOffset = 0; + + memset( & cpuInfo, 0, sizeof( cpuInfo ) ); + + glpi = (LPFN_GLPI)GetProcAddress( GetModuleHandle(TEXT("kernel32")), "GetLogicalProcessorInformation" ); + if ( NULL == glpi ) { + idLib::Printf( "\nGetLogicalProcessorInformation is not supported.\n" ); + return 0; + } + + while ( !done ) { + DWORD rc = glpi( buffer, &returnLength ); + + if ( FALSE == rc ) { + if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { + if ( buffer ) { + free( buffer ); + } + + buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc( returnLength ); + } else { + idLib::Printf( "Sys_CPUCount error: %d\n", GetLastError() ); + return false; + } + } else { + done = TRUE; + } + } + + ptr = buffer; + + while ( byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength ) { + switch ( (LOGICAL_PROCESSOR_RELATIONSHIP_LOCAL) ptr->Relationship ) { + case localRelationProcessorCore: + cpuInfo.processorCoreCount++; + + // A hyperthreaded core supplies more than one logical processor. + cpuInfo.logicalProcessorCount += CountSetBits( ptr->ProcessorMask ); + break; + + case localRelationNumaNode: + // Non-NUMA systems report a single record of this type. + cpuInfo.numaNodeCount++; + break; + + case localRelationCache: + // Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache. + Cache = &ptr->Cache; + if ( Cache->Level >= 1 && Cache->Level <= 3 ) { + int level = Cache->Level - 1; + if ( cpuInfo.cacheLevel[level].count > 0 ) { + cpuInfo.cacheLevel[level].count++; + } else { + cpuInfo.cacheLevel[level].associativity = Cache->Associativity; + cpuInfo.cacheLevel[level].lineSize = Cache->LineSize; + cpuInfo.cacheLevel[level].size = Cache->Size; + } + } + break; + + case localRelationProcessorPackage: + // Logical processors share a physical package. + cpuInfo.processorPackageCount++; + break; + + default: + idLib::Printf( "Error: Unsupported LOGICAL_PROCESSOR_RELATIONSHIP value.\n" ); + break; + } + byteOffset += sizeof( SYSTEM_LOGICAL_PROCESSOR_INFORMATION ); + ptr++; + } + + free( buffer ); + + return true; +} + +/* +======================== +Sys_GetCPUCacheSize +======================== +*/ +void Sys_GetCPUCacheSize( int level, int & count, int & size, int & lineSize ) { + assert( level >= 1 && level <= 3 ); + cpuInfo_t cpuInfo; + + GetCPUInfo( cpuInfo ); + + count = cpuInfo.cacheLevel[level - 1].count; + size = cpuInfo.cacheLevel[level - 1].size; + lineSize = cpuInfo.cacheLevel[level - 1].lineSize; +} + +/* +======================== +Sys_CPUCount + +numLogicalCPUCores - the number of logical CPU per core +numPhysicalCPUCores - the total number of cores per package +numCPUPackages - the total number of packages (physical processors) +======================== +*/ +void Sys_CPUCount( int & numLogicalCPUCores, int & numPhysicalCPUCores, int & numCPUPackages ) { + cpuInfo_t cpuInfo; + GetCPUInfo( cpuInfo ); + + numPhysicalCPUCores = cpuInfo.processorCoreCount; + numLogicalCPUCores = cpuInfo.logicalProcessorCount; + numCPUPackages = cpuInfo.processorPackageCount; +} + +/* +================ +Sys_GetCPUId +================ +*/ +cpuid_t Sys_GetCPUId() { + int flags; + + // verify we're at least a Pentium or 486 with CPUID support + if ( !HasCPUID() ) { + return CPUID_UNSUPPORTED; + } + + // check for an AMD + if ( IsAMD() ) { + flags = CPUID_AMD; + } else { + flags = CPUID_INTEL; + } + + // check for Multi Media Extensions + if ( HasMMX() ) { + flags |= CPUID_MMX; + } + + // check for 3DNow! + if ( Has3DNow() ) { + flags |= CPUID_3DNOW; + } + + // check for Streaming SIMD Extensions + if ( HasSSE() ) { + flags |= CPUID_SSE | CPUID_FTZ; + } + + // check for Streaming SIMD Extensions 2 + if ( HasSSE2() ) { + flags |= CPUID_SSE2; + } + + // check for Streaming SIMD Extensions 3 aka Prescott's New Instructions + if ( HasSSE3() ) { + flags |= CPUID_SSE3; + } + + // check for Hyper-Threading Technology + if ( HasHTT() ) { + flags |= CPUID_HTT; + } + + // check for Conditional Move (CMOV) and fast floating point comparison (FCOMI) instructions + if ( HasCMOV() ) { + flags |= CPUID_CMOV; + } + + // check for Denormals-Are-Zero mode + if ( HasDAZ() ) { + flags |= CPUID_DAZ; + } + + return (cpuid_t)flags; +} + + +/* +=============================================================================== + + FPU + +=============================================================================== +*/ + +typedef struct bitFlag_s { + char * name; + int bit; +} bitFlag_t; + +static byte fpuState[128], *statePtr = fpuState; +static char fpuString[2048]; +static bitFlag_t controlWordFlags[] = { + { "Invalid operation", 0 }, + { "Denormalized operand", 1 }, + { "Divide-by-zero", 2 }, + { "Numeric overflow", 3 }, + { "Numeric underflow", 4 }, + { "Inexact result (precision)", 5 }, + { "Infinity control", 12 }, + { "", 0 } +}; +static char *precisionControlField[] = { + "Single Precision (24-bits)", + "Reserved", + "Double Precision (53-bits)", + "Double Extended Precision (64-bits)" +}; +static char *roundingControlField[] = { + "Round to nearest", + "Round down", + "Round up", + "Round toward zero" +}; +static bitFlag_t statusWordFlags[] = { + { "Invalid operation", 0 }, + { "Denormalized operand", 1 }, + { "Divide-by-zero", 2 }, + { "Numeric overflow", 3 }, + { "Numeric underflow", 4 }, + { "Inexact result (precision)", 5 }, + { "Stack fault", 6 }, + { "Error summary status", 7 }, + { "FPU busy", 15 }, + { "", 0 } +}; + +/* +=============== +Sys_FPU_PrintStateFlags +=============== +*/ +int Sys_FPU_PrintStateFlags( char *ptr, int ctrl, int stat, int tags, int inof, int inse, int opof, int opse ) { + int i, length = 0; + + length += sprintf( ptr+length, "CTRL = %08x\n" + "STAT = %08x\n" + "TAGS = %08x\n" + "INOF = %08x\n" + "INSE = %08x\n" + "OPOF = %08x\n" + "OPSE = %08x\n" + "\n", + ctrl, stat, tags, inof, inse, opof, opse ); + + length += sprintf( ptr+length, "Control Word:\n" ); + for ( i = 0; controlWordFlags[i].name[0]; i++ ) { + length += sprintf( ptr+length, " %-30s = %s\n", controlWordFlags[i].name, ( ctrl & ( 1 << controlWordFlags[i].bit ) ) ? "true" : "false" ); + } + length += sprintf( ptr+length, " %-30s = %s\n", "Precision control", precisionControlField[(ctrl>>8)&3] ); + length += sprintf( ptr+length, " %-30s = %s\n", "Rounding control", roundingControlField[(ctrl>>10)&3] ); + + length += sprintf( ptr+length, "Status Word:\n" ); + for ( i = 0; statusWordFlags[i].name[0]; i++ ) { + ptr += sprintf( ptr+length, " %-30s = %s\n", statusWordFlags[i].name, ( stat & ( 1 << statusWordFlags[i].bit ) ) ? "true" : "false" ); + } + length += sprintf( ptr+length, " %-30s = %d%d%d%d\n", "Condition code", (stat>>8)&1, (stat>>9)&1, (stat>>10)&1, (stat>>14)&1 ); + length += sprintf( ptr+length, " %-30s = %d\n", "Top of stack pointer", (stat>>11)&7 ); + + return length; +} + +/* +=============== +Sys_FPU_StackIsEmpty +=============== +*/ +bool Sys_FPU_StackIsEmpty() { + __asm { + mov eax, statePtr + fnstenv [eax] + mov eax, [eax+8] + xor eax, 0xFFFFFFFF + and eax, 0x0000FFFF + jz empty + } + return false; +empty: + return true; +} + +/* +=============== +Sys_FPU_ClearStack +=============== +*/ +void Sys_FPU_ClearStack() { + __asm { + mov eax, statePtr + fnstenv [eax] + mov eax, [eax+8] + xor eax, 0xFFFFFFFF + mov edx, (3<<14) + emptyStack: + mov ecx, eax + and ecx, edx + jz done + fstp st + shr edx, 2 + jmp emptyStack + done: + } +} + +/* +=============== +Sys_FPU_GetState + + gets the FPU state without changing the state +=============== +*/ +const char *Sys_FPU_GetState() { + double fpuStack[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + double *fpuStackPtr = fpuStack; + int i, numValues; + char *ptr; + + __asm { + mov esi, statePtr + mov edi, fpuStackPtr + fnstenv [esi] + mov esi, [esi+8] + xor esi, 0xFFFFFFFF + mov edx, (3<<14) + xor eax, eax + mov ecx, esi + and ecx, edx + jz done + fst qword ptr [edi+0] + inc eax + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(1) + fst qword ptr [edi+8] + inc eax + fxch st(1) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(2) + fst qword ptr [edi+16] + inc eax + fxch st(2) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(3) + fst qword ptr [edi+24] + inc eax + fxch st(3) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(4) + fst qword ptr [edi+32] + inc eax + fxch st(4) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(5) + fst qword ptr [edi+40] + inc eax + fxch st(5) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(6) + fst qword ptr [edi+48] + inc eax + fxch st(6) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(7) + fst qword ptr [edi+56] + inc eax + fxch st(7) + done: + mov numValues, eax + } + + int ctrl = *(int *)&fpuState[0]; + int stat = *(int *)&fpuState[4]; + int tags = *(int *)&fpuState[8]; + int inof = *(int *)&fpuState[12]; + int inse = *(int *)&fpuState[16]; + int opof = *(int *)&fpuState[20]; + int opse = *(int *)&fpuState[24]; + + ptr = fpuString; + ptr += sprintf( ptr,"FPU State:\n" + "num values on stack = %d\n", numValues ); + for ( i = 0; i < 8; i++ ) { + ptr += sprintf( ptr, "ST%d = %1.10e\n", i, fpuStack[i] ); + } + + Sys_FPU_PrintStateFlags( ptr, ctrl, stat, tags, inof, inse, opof, opse ); + + return fpuString; +} + +/* +=============== +Sys_FPU_EnableExceptions +=============== +*/ +void Sys_FPU_EnableExceptions( int exceptions ) { + __asm { + mov eax, statePtr + mov ecx, exceptions + and cx, 63 + not cx + fnstcw word ptr [eax] + mov bx, word ptr [eax] + or bx, 63 + and bx, cx + mov word ptr [eax], bx + fldcw word ptr [eax] + } +} + +/* +=============== +Sys_FPU_SetPrecision +=============== +*/ +void Sys_FPU_SetPrecision( int precision ) { + short precisionBitTable[4] = { 0, 1, 3, 0 }; + short precisionBits = precisionBitTable[precision & 3] << 8; + short precisionMask = ~( ( 1 << 9 ) | ( 1 << 8 ) ); + + __asm { + mov eax, statePtr + mov cx, precisionBits + fnstcw word ptr [eax] + mov bx, word ptr [eax] + and bx, precisionMask + or bx, cx + mov word ptr [eax], bx + fldcw word ptr [eax] + } +} + +/* +================ +Sys_FPU_SetRounding +================ +*/ +void Sys_FPU_SetRounding( int rounding ) { + short roundingBitTable[4] = { 0, 1, 2, 3 }; + short roundingBits = roundingBitTable[rounding & 3] << 10; + short roundingMask = ~( ( 1 << 11 ) | ( 1 << 10 ) ); + + __asm { + mov eax, statePtr + mov cx, roundingBits + fnstcw word ptr [eax] + mov bx, word ptr [eax] + and bx, roundingMask + or bx, cx + mov word ptr [eax], bx + fldcw word ptr [eax] + } +} + +/* +================ +Sys_FPU_SetDAZ +================ +*/ +void Sys_FPU_SetDAZ( bool enable ) { + DWORD dwData; + + _asm { + movzx ecx, byte ptr enable + and ecx, 1 + shl ecx, 6 + STMXCSR dword ptr dwData + mov eax, dwData + and eax, ~(1<<6) // clear DAX bit + or eax, ecx // set the DAZ bit + mov dwData, eax + LDMXCSR dword ptr dwData + } +} + +/* +================ +Sys_FPU_SetFTZ +================ +*/ +void Sys_FPU_SetFTZ( bool enable ) { + DWORD dwData; + + _asm { + movzx ecx, byte ptr enable + and ecx, 1 + shl ecx, 15 + STMXCSR dword ptr dwData + mov eax, dwData + and eax, ~(1<<15) // clear FTZ bit + or eax, ecx // set the FTZ bit + mov dwData, eax + LDMXCSR dword ptr dwData + } +} diff --git a/neo/sys/win32/win_gamma.cpp b/neo/sys/win32/win_gamma.cpp new file mode 100644 index 00000000..2d68f757 --- /dev/null +++ b/neo/sys/win32/win_gamma.cpp @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +** WIN_GAMMA.C +*/ +#include +#include "win_local.h" +#include "../../renderer/tr_local.h" + +static unsigned short s_oldHardwareGamma[3][256]; + +/* +** WG_GetOldGammaRamp +** +*/ +void WG_GetOldGammaRamp( void ) +{ + HDC hDC; + + hDC = GetDC( GetDesktopWindow() ); + GetDeviceGammaRamp( hDC, s_oldHardwareGamma ); + ReleaseDC( GetDesktopWindow(), hDC ); + + +/* +** GLimp_SetGamma +** +*/ +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ) +{ + unsigned short table[3][256]; + int i; + + if ( !glw_state.hDC ) + { + return; + } + + for ( i = 0; i < 256; i++ ) + { + table[0][i] = ( ( ( unsigned short ) red[i] ) << 8 ) | red[i]; + table[1][i] = ( ( ( unsigned short ) green[i] ) << 8 ) | green[i]; + table[2][i] = ( ( ( unsigned short ) blue[i] ) << 8 ) | blue[i]; + } + + if ( !SetDeviceGammaRamp( glw_state.hDC, table ) ) { + common->Printf( "WARNING: SetDeviceGammaRamp failed.\n" ); + } +} + +/* +** WG_RestoreGamma +*/ +void WG_RestoreGamma( void ) +{ + HDC hDC; + + // if we never read in a reasonable looking + // table, don't write it out + if ( s_oldHardwareGamma[0][255] == 0 ) { + return; + } + + hDC = GetDC( GetDesktopWindow() ); + SetDeviceGammaRamp( hDC, s_oldHardwareGamma ); + ReleaseDC( GetDesktopWindow(), hDC ); +} + diff --git a/neo/sys/win32/win_glimp.cpp b/neo/sys/win32/win_glimp.cpp new file mode 100644 index 00000000..30c31bba --- /dev/null +++ b/neo/sys/win32/win_glimp.cpp @@ -0,0 +1,1562 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +** WIN_GLIMP.C +** +** This file contains ALL Win32 specific stuff having to do with the +** OpenGL refresh. When a port is being made the following functions +** must be implemented by the port: +** +** GLimp_SwapBuffers +** GLimp_Init +** GLimp_Shutdown +** GLimp_SetGamma +** +** Note that the GLW_xxx functions are Windows specific GL-subsystem +** related functions that are relevant ONLY to win_glimp.c +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "win_local.h" +#include "rc/doom_resource.h" +#include "../../renderer/tr_local.h" + +// WGL_ARB_extensions_string +PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; + +// WGL_EXT_swap_interval +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + +// WGL_ARB_pixel_format +PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB; +PFNWGLGETPIXELFORMATATTRIBFVARBPROC wglGetPixelFormatAttribfvARB; +PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; + +// WGL_ARB_create_context +PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; + + +idCVar r_useOpenGL32( "r_useOpenGL32", "1", CVAR_INTEGER, "0 = OpenGL 2.0, 1 = OpenGL 3.2 compatibility profile, 2 = OpenGL 3.2 core profile", 0, 2 ); + +// +// function declaration +// +bool QGL_Init( const char *dllname ); +void QGL_Shutdown(); + + +/* +======================== +GLimp_TestSwapBuffers +======================== +*/ +void GLimp_TestSwapBuffers( const idCmdArgs &args ) { + idLib::Printf( "GLimp_TimeSwapBuffers\n" ); + static const int MAX_FRAMES = 5; + uint64 timestamps[MAX_FRAMES]; + qglDisable( GL_SCISSOR_TEST ); + + int frameMilliseconds = 16; + for ( int swapInterval = 2 ; swapInterval >= -1 ; swapInterval-- ) { + wglSwapIntervalEXT( swapInterval ); + for ( int i = 0 ; i < MAX_FRAMES ; i++ ) { + if ( swapInterval == -1 ) { + Sys_Sleep( frameMilliseconds ); + } + if ( i & 1 ) { + qglClearColor( 0, 1, 0, 1 ); + } else { + qglClearColor( 1, 0, 0, 1 ); + } + qglClear( GL_COLOR_BUFFER_BIT ); + qwglSwapBuffers( win32.hDC ); + qglFinish(); + timestamps[i] = Sys_Microseconds(); + } + + idLib::Printf( "\nswapinterval %i\n", swapInterval ); + for ( int i = 1 ; i < MAX_FRAMES ; i++ ) { + idLib::Printf( "%i microseconds\n", (int)(timestamps[i] - timestamps[i-1]) ); + } + } +} + +/* +======================== +GLimp_GetOldGammaRamp +======================== +*/ +static void GLimp_SaveGamma() { + HDC hDC; + BOOL success; + + hDC = GetDC( GetDesktopWindow() ); + success = GetDeviceGammaRamp( hDC, win32.oldHardwareGamma ); + common->DPrintf( "...getting default gamma ramp: %s\n", success ? "success" : "failed" ); + ReleaseDC( GetDesktopWindow(), hDC ); +} + +/* +======================== +GLimp_RestoreGamma +======================== +*/ +static void GLimp_RestoreGamma() { + HDC hDC; + BOOL success; + + // if we never read in a reasonable looking + // table, don't write it out + if ( win32.oldHardwareGamma[0][255] == 0 ) { + return; + } + + hDC = GetDC( GetDesktopWindow() ); + success = SetDeviceGammaRamp( hDC, win32.oldHardwareGamma ); + common->DPrintf ( "...restoring hardware gamma: %s\n", success ? "success" : "failed" ); + ReleaseDC( GetDesktopWindow(), hDC ); +} + + +/* +======================== +GLimp_SetGamma + +The renderer calls this when the user adjusts r_gamma or r_brightness +======================== +*/ +void GLimp_SetGamma( unsigned short red[256], unsigned short green[256], unsigned short blue[256] ) { + unsigned short table[3][256]; + int i; + + if ( !win32.hDC ) { + return; + } + + for ( i = 0; i < 256; i++ ) { + table[0][i] = red[i]; + table[1][i] = green[i]; + table[2][i] = blue[i]; + } + + if ( !SetDeviceGammaRamp( win32.hDC, table ) ) { + common->Printf( "WARNING: SetDeviceGammaRamp failed.\n" ); + } +} + +/* +============================================================================= + +WglExtension Grabbing + +This is gross -- creating a window just to get a context to get the wgl extensions + +============================================================================= +*/ + +/* +======================== +R_CheckWinExtension +======================== +*/ +bool R_CheckWinExtension( const char * name ) { + + if ( !strstr( glConfig.wgl_extensions_string, name ) ) { + idLib::Printf( "X..%s not found\n", name ); + return false; + } + + idLib::Printf( "...using %s\n", name ); + return true; +} + + +/* +==================== +FakeWndProc + +Only used to get wglExtensions +==================== +*/ +LONG WINAPI FakeWndProc ( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + + if ( uMsg == WM_DESTROY ) { + PostQuitMessage(0); + } + + if ( uMsg != WM_CREATE ) { + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + const static PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, + 24, + 0, 0, 0, 0, 0, 0, + 8, 0, + 0, 0, 0, 0, + 24, 8, + 0, + PFD_MAIN_PLANE, + 0, + 0, + 0, + 0, + }; + int pixelFormat; + HDC hDC; + HGLRC hGLRC; + + hDC = GetDC(hWnd); + + // Set up OpenGL + pixelFormat = ChoosePixelFormat(hDC, &pfd); + SetPixelFormat(hDC, pixelFormat, &pfd); + hGLRC = qwglCreateContext(hDC); + qwglMakeCurrent(hDC, hGLRC); + + // free things + wglMakeCurrent(NULL, NULL); + wglDeleteContext(hGLRC); + ReleaseDC(hWnd, hDC); + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + + +/* +================== +GLW_GetWGLExtensionsWithFakeWindow +================== +*/ +void GLW_CheckWGLExtensions( HDC hDC ) { + wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC) + GLimp_ExtensionPointer("wglGetExtensionsStringARB"); + if ( wglGetExtensionsStringARB ) { + glConfig.wgl_extensions_string = (const char *) wglGetExtensionsStringARB(hDC); + } else { + glConfig.wgl_extensions_string = ""; + } + + // WGL_EXT_swap_control + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) GLimp_ExtensionPointer( "wglSwapIntervalEXT" ); + r_swapInterval.SetModified(); // force a set next frame + + // WGL_EXT_swap_control_tear + glConfig.swapControlTearAvailable = R_CheckWinExtension( "WGL_EXT_swap_control_tear" ); + + // WGL_ARB_pixel_format + wglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)GLimp_ExtensionPointer("wglGetPixelFormatAttribivARB"); + wglGetPixelFormatAttribfvARB = (PFNWGLGETPIXELFORMATATTRIBFVARBPROC)GLimp_ExtensionPointer("wglGetPixelFormatAttribfvARB"); + wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)GLimp_ExtensionPointer("wglChoosePixelFormatARB"); + + // wglCreateContextAttribsARB + wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress( "wglCreateContextAttribsARB" ); +} + +/* +================== +GLW_GetWGLExtensionsWithFakeWindow +================== +*/ +static void GLW_GetWGLExtensionsWithFakeWindow() { + HWND hWnd; + MSG msg; + + // Create a window for the sole purpose of getting + // a valid context to get the wglextensions + hWnd = CreateWindow(WIN32_FAKE_WINDOW_CLASS_NAME, GAME_NAME, + WS_OVERLAPPEDWINDOW, + 40, 40, + 640, + 480, + NULL, NULL, win32.hInstance, NULL ); + if ( !hWnd ) { + common->FatalError( "GLW_GetWGLExtensionsWithFakeWindow: Couldn't create fake window" ); + } + + HDC hDC = GetDC( hWnd ); + HGLRC gRC = wglCreateContext( hDC ); + wglMakeCurrent( hDC, gRC ); + GLW_CheckWGLExtensions( hDC ); + wglDeleteContext( gRC ); + ReleaseDC( hWnd, hDC ); + + DestroyWindow( hWnd ); + while ( GetMessage( &msg, NULL, 0, 0 ) ) { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +} + +//============================================================================= + +/* +==================== +GLW_WM_CREATE +==================== +*/ +void GLW_WM_CREATE( HWND hWnd ) { +} + +/* +======================== +CreateOpenGLContextOnDC +======================== +*/ +static HGLRC CreateOpenGLContextOnDC( const HDC hdc, const bool debugContext ) { + int useOpenGL32 = r_useOpenGL32.GetInteger(); + HGLRC m_hrc = NULL; + + for ( int i = 0; i < 2; i++ ) { + const int glMajorVersion = ( useOpenGL32 != 0 ) ? 3 : 2; + const int glMinorVersion = ( useOpenGL32 != 0 ) ? 2 : 0; + const int glDebugFlag = debugContext ? WGL_CONTEXT_DEBUG_BIT_ARB : 0; + const int glProfileMask = ( useOpenGL32 != 0 ) ? WGL_CONTEXT_PROFILE_MASK_ARB : 0; + const int glProfile = ( useOpenGL32 == 1 ) ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : ( ( useOpenGL32 == 2 ) ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : 0 ); + const int attribs[] = + { + WGL_CONTEXT_MAJOR_VERSION_ARB, glMajorVersion, + WGL_CONTEXT_MINOR_VERSION_ARB, glMinorVersion, + WGL_CONTEXT_FLAGS_ARB, glDebugFlag, + glProfileMask, glProfile, + 0 + }; + + m_hrc = wglCreateContextAttribsARB( hdc, 0, attribs ); + if ( m_hrc != NULL ) { + idLib::Printf( "created OpenGL %d.%d context\n", glMajorVersion, glMinorVersion ); + break; + } + + idLib::Printf( "failed to create OpenGL %d.%d context\n", glMajorVersion, glMinorVersion ); + useOpenGL32 = 0; // fall back to OpenGL 2.0 + } + + if ( m_hrc == NULL ) { + int err = GetLastError(); + switch( err ) { + case ERROR_INVALID_VERSION_ARB: idLib::Printf( "ERROR_INVALID_VERSION_ARB\n" ); break; + case ERROR_INVALID_PROFILE_ARB: idLib::Printf( "ERROR_INVALID_PROFILE_ARB\n" ); break; + default: idLib::Printf( "unknown error: 0x%x\n", err ); break; + } + } + + return m_hrc; +} + +/* +==================== +GLW_ChoosePixelFormat + +Returns -1 on failure, or a pixel format +==================== +*/ +static int GLW_ChoosePixelFormat( const HDC hdc, const int multisamples, const bool stereo3D ) { + FLOAT fAttributes[] = { 0, 0 }; + int iAttributes[] = { + WGL_SAMPLE_BUFFERS_ARB, ( ( multisamples > 1 ) ? 1 : 0 ), + WGL_SAMPLES_ARB, multisamples, + WGL_DOUBLE_BUFFER_ARB, TRUE, + WGL_STENCIL_BITS_ARB, 8, + WGL_DEPTH_BITS_ARB, 24, + WGL_RED_BITS_ARB, 8, + WGL_BLUE_BITS_ARB, 8, + WGL_GREEN_BITS_ARB, 8, + WGL_ALPHA_BITS_ARB, 8, + WGL_STEREO_ARB, ( stereo3D ? TRUE : FALSE ), + 0, 0 + }; + + int pixelFormat; + UINT numFormats; + if ( !wglChoosePixelFormatARB( hdc, iAttributes, fAttributes, 1, &pixelFormat, &numFormats ) ) { + return -1; + } + return pixelFormat; +} + + +/* +==================== +GLW_InitDriver + +Set the pixelformat for the window before it is +shown, and create the rendering context +==================== +*/ +static bool GLW_InitDriver( glimpParms_t parms ) { + 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 + 32, // 32-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 8, // 8 bit destination alpha + 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 + }; + + common->Printf( "Initializing OpenGL driver\n" ); + + // + // get a DC for our window if we don't already have one allocated + // + if ( win32.hDC == NULL ) { + common->Printf( "...getting DC: " ); + + if ( ( win32.hDC = GetDC( win32.hWnd ) ) == NULL ) { + common->Printf( "^3failed^0\n" ); + return false; + } + common->Printf( "succeeded\n" ); + } + + // the multisample path uses the wgl + if ( wglChoosePixelFormatARB ) { + win32.pixelformat = GLW_ChoosePixelFormat( win32.hDC, parms.multiSamples, parms.stereo ); + } else { + // this is the "classic" choose pixel format path + common->Printf( "Using classic ChoosePixelFormat\n" ); + + // eventually we may need to have more fallbacks, but for + // now, ask for everything + if ( parms.stereo ) { + common->Printf( "...attempting to use stereo\n" ); + src.dwFlags |= PFD_STEREO; + } + + // + // choose, set, and describe our desired pixel format. If we're + // using a minidriver then we need to bypass the GDI functions, + // otherwise use the GDI functions. + // + if ( ( win32.pixelformat = ChoosePixelFormat( win32.hDC, &src ) ) == 0 ) { + common->Printf( "...^3GLW_ChoosePFD failed^0\n"); + return false; + } + common->Printf( "...PIXELFORMAT %d selected\n", win32.pixelformat ); + } + + // get the full info + DescribePixelFormat( win32.hDC, win32.pixelformat, sizeof( win32.pfd ), &win32.pfd ); + glConfig.colorBits = win32.pfd.cColorBits; + glConfig.depthBits = win32.pfd.cDepthBits; + glConfig.stencilBits = win32.pfd.cStencilBits; + + // XP seems to set this incorrectly + if ( !glConfig.stencilBits ) { + glConfig.stencilBits = 8; + } + + // the same SetPixelFormat is used either way + if ( SetPixelFormat( win32.hDC, win32.pixelformat, &win32.pfd ) == FALSE ) { + common->Printf( "...^3SetPixelFormat failed^0\n", win32.hDC ); + return false; + } + + // + // startup the OpenGL subsystem by creating a context and making it current + // + common->Printf( "...creating GL context: " ); + win32.hGLRC = CreateOpenGLContextOnDC( win32.hDC, r_debugContext.GetBool() ); + if ( win32.hGLRC == 0 ) { + common->Printf( "^3failed^0\n" ); + return false; + } + common->Printf( "succeeded\n" ); + + common->Printf( "...making context current: " ); + if ( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) { + qwglDeleteContext( win32.hGLRC ); + win32.hGLRC = NULL; + common->Printf( "^3failed^0\n" ); + return false; + } + common->Printf( "succeeded\n" ); + + return true; +} + +/* +==================== +GLW_CreateWindowClasses +==================== +*/ +static void GLW_CreateWindowClasses() { + WNDCLASS wc; + + // + // register the window class if necessary + // + if ( win32.windowClassRegistered ) { + return; + } + + memset( &wc, 0, sizeof( wc ) ); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) MainWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = win32.hInstance; + wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = NULL; + wc.hbrBackground = (struct HBRUSH__ *)COLOR_GRAYTEXT; + wc.lpszMenuName = 0; + wc.lpszClassName = WIN32_WINDOW_CLASS_NAME; + + if ( !RegisterClass( &wc ) ) { + common->FatalError( "GLW_CreateWindow: could not register window class" ); + } + common->Printf( "...registered window class\n" ); + + // now register the fake window class that is only used + // to get wgl extensions + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) FakeWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = win32.hInstance; + wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (struct HBRUSH__ *)COLOR_GRAYTEXT; + wc.lpszMenuName = 0; + wc.lpszClassName = WIN32_FAKE_WINDOW_CLASS_NAME; + + if ( !RegisterClass( &wc ) ) { + common->FatalError( "GLW_CreateWindow: could not register window class" ); + } + common->Printf( "...registered fake window class\n" ); + + win32.windowClassRegistered = true; +} + +/* +======================== +GetDisplayName +======================== +*/ +static const char * GetDisplayName( const int deviceNum ) { + static DISPLAY_DEVICE device; + device.cb = sizeof( device ); + if ( !EnumDisplayDevices( + 0, // lpDevice + deviceNum, + &device, + 0 /* dwFlags */ ) ) { + return NULL; + } + return device.DeviceName; +} + +/* +======================== +GetDeviceName +======================== +*/ +static idStr GetDeviceName( const int deviceNum ) { + DISPLAY_DEVICE device = {}; + device.cb = sizeof( device ); + if ( !EnumDisplayDevices( + 0, // lpDevice + deviceNum, + &device, + 0 /* dwFlags */ ) ) { + return false; + } + + // get the monitor for this display + if ( ! (device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP ) ) { + return false; + } + + return idStr( device.DeviceName ); +} + +/* +======================== +GetDisplayCoordinates +======================== +*/ +static bool GetDisplayCoordinates( const int deviceNum, int & x, int & y, int & width, int & height, int & displayHz ) { + idStr deviceName = GetDeviceName( deviceNum ); + if ( deviceName.Length() == 0 ) { + return false; + } + + DISPLAY_DEVICE device = {}; + device.cb = sizeof( device ); + if ( !EnumDisplayDevices( + 0, // lpDevice + deviceNum, + &device, + 0 /* dwFlags */ ) ) { + return false; + } + + DISPLAY_DEVICE monitor; + monitor.cb = sizeof( monitor ); + if ( !EnumDisplayDevices( + deviceName.c_str(), + 0, + &monitor, + 0 /* dwFlags */ ) ) { + return false; + } + + DEVMODE devmode; + devmode.dmSize = sizeof( devmode ); + if ( !EnumDisplaySettings( deviceName.c_str(),ENUM_CURRENT_SETTINGS, &devmode ) ) { + return false; + } + + common->Printf( "display device: %i\n", deviceNum ); + common->Printf( " DeviceName : %s\n", device.DeviceName ); + common->Printf( " DeviceString: %s\n", device.DeviceString ); + common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); + common->Printf( " DeviceID : %s\n", device.DeviceID ); + common->Printf( " DeviceKey : %s\n", device.DeviceKey ); + common->Printf( " DeviceName : %s\n", monitor.DeviceName ); + common->Printf( " DeviceString: %s\n", monitor.DeviceString ); + common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); + common->Printf( " DeviceID : %s\n", monitor.DeviceID ); + common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); + common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); + common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); + common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); + common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); + common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); + common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); + common->Printf( " dmDisplayFrequency: %i\n", devmode.dmDisplayFrequency ); + + x = devmode.dmPosition.x; + y = devmode.dmPosition.y; + width = devmode.dmPelsWidth; + height = devmode.dmPelsHeight; + displayHz = devmode.dmDisplayFrequency; + + return true; +} + +/* +==================== +DMDFO +==================== +*/ +static const char * DMDFO( int dmDisplayFixedOutput ) { + switch( dmDisplayFixedOutput ) { + case DMDFO_DEFAULT: return "DMDFO_DEFAULT"; + case DMDFO_CENTER: return "DMDFO_CENTER"; + case DMDFO_STRETCH: return "DMDFO_STRETCH"; + } + return "UNKNOWN"; +} + +/* +==================== +PrintDevMode +==================== +*/ +static void PrintDevMode( DEVMODE & devmode ) { + common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); + common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); + common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); + common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); + common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); + common->Printf( " dmDisplayFixedOutput: %s\n", DMDFO( devmode.dmDisplayFixedOutput ) ); + common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); + common->Printf( " dmDisplayFrequency : %i\n", devmode.dmDisplayFrequency ); +} + +/* +==================== +DumpAllDisplayDevices +==================== +*/ +void DumpAllDisplayDevices() { + common->Printf( "\n" ); + for ( int deviceNum = 0 ; ; deviceNum++ ) { + DISPLAY_DEVICE device = {}; + device.cb = sizeof( device ); + if ( !EnumDisplayDevices( + 0, // lpDevice + deviceNum, + &device, + 0 /* dwFlags */ ) ) { + break; + } + + common->Printf( "display device: %i\n", deviceNum ); + common->Printf( " DeviceName : %s\n", device.DeviceName ); + common->Printf( " DeviceString: %s\n", device.DeviceString ); + common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); + common->Printf( " DeviceID : %s\n", device.DeviceID ); + common->Printf( " DeviceKey : %s\n", device.DeviceKey ); + + for ( int monitorNum = 0 ; ; monitorNum++ ) { + DISPLAY_DEVICE monitor = {}; + monitor.cb = sizeof( monitor ); + if ( !EnumDisplayDevices( + device.DeviceName, + monitorNum, + &monitor, + 0 /* dwFlags */ ) ) { + break; + } + + common->Printf( " DeviceName : %s\n", monitor.DeviceName ); + common->Printf( " DeviceString: %s\n", monitor.DeviceString ); + common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); + common->Printf( " DeviceID : %s\n", monitor.DeviceID ); + common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); + + DEVMODE currentDevmode = {}; + if ( !EnumDisplaySettings( device.DeviceName,ENUM_CURRENT_SETTINGS, ¤tDevmode ) ) { + common->Printf( "ERROR: EnumDisplaySettings(ENUM_CURRENT_SETTINGS) failed!\n" ); + } + common->Printf( " -------------------\n" ); + common->Printf( " ENUM_CURRENT_SETTINGS\n" ); + PrintDevMode( currentDevmode ); + + DEVMODE registryDevmode = {}; + if ( !EnumDisplaySettings( device.DeviceName,ENUM_REGISTRY_SETTINGS, ®istryDevmode ) ) { + common->Printf( "ERROR: EnumDisplaySettings(ENUM_CURRENT_SETTINGS) failed!\n" ); + } + common->Printf( " -------------------\n" ); + common->Printf( " ENUM_CURRENT_SETTINGS\n" ); + PrintDevMode( registryDevmode ); + + for ( int modeNum = 0 ; ; modeNum++ ) { + DEVMODE devmode = {}; + + if ( !EnumDisplaySettings( device.DeviceName,modeNum, &devmode ) ) { + break; + } + + if ( devmode.dmBitsPerPel != 32 ) { + continue; + } + if ( devmode.dmDisplayFrequency < 60 ) { + continue; + } + if ( devmode.dmPelsHeight < 720 ) { + continue; + } + common->Printf( " -------------------\n" ); + common->Printf( " modeNum : %i\n", modeNum ); + PrintDevMode( devmode ); + } + } + } + common->Printf( "\n" ); +} + +/* +==================== +R_GetModeListForDisplay +==================== +*/ +bool R_GetModeListForDisplay( const int requestedDisplayNum, idList & modeList ) { + modeList.Clear(); + + bool verbose = false; + + for ( int displayNum = requestedDisplayNum; ; displayNum++ ) { + DISPLAY_DEVICE device; + device.cb = sizeof( device ); + if ( !EnumDisplayDevices( + 0, // lpDevice + displayNum, + &device, + 0 /* dwFlags */ ) ) { + return false; + } + + // get the monitor for this display + if ( ! (device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP ) ) { + continue; + } + + DISPLAY_DEVICE monitor; + monitor.cb = sizeof( monitor ); + if ( !EnumDisplayDevices( + device.DeviceName, + 0, + &monitor, + 0 /* dwFlags */ ) ) { + continue; + } + + DEVMODE devmode; + devmode.dmSize = sizeof( devmode ); + + if ( verbose ) { + common->Printf( "display device: %i\n", displayNum ); + common->Printf( " DeviceName : %s\n", device.DeviceName ); + common->Printf( " DeviceString: %s\n", device.DeviceString ); + common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); + common->Printf( " DeviceID : %s\n", device.DeviceID ); + common->Printf( " DeviceKey : %s\n", device.DeviceKey ); + common->Printf( " DeviceName : %s\n", monitor.DeviceName ); + common->Printf( " DeviceString: %s\n", monitor.DeviceString ); + common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); + common->Printf( " DeviceID : %s\n", monitor.DeviceID ); + common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); + } + + for ( int modeNum = 0 ; ; modeNum++ ) { + if ( !EnumDisplaySettings( device.DeviceName,modeNum, &devmode ) ) { + break; + } + + if ( devmode.dmBitsPerPel != 32 ) { + continue; + } + if ( ( devmode.dmDisplayFrequency != 60 ) && ( devmode.dmDisplayFrequency != 120 ) ) { + continue; + } + if ( devmode.dmPelsHeight < 720 ) { + continue; + } + if ( verbose ) { + common->Printf( " -------------------\n" ); + common->Printf( " modeNum : %i\n", modeNum ); + common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); + common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); + common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); + common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); + common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); + common->Printf( " dmDisplayFixedOutput: %s\n", DMDFO( devmode.dmDisplayFixedOutput ) ); + common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); + common->Printf( " dmDisplayFrequency : %i\n", devmode.dmDisplayFrequency ); + } + vidMode_t mode; + mode.width = devmode.dmPelsWidth; + mode.height = devmode.dmPelsHeight; + mode.displayHz = devmode.dmDisplayFrequency; + modeList.AddUnique( mode ); + } + if ( modeList.Num() > 0 ) { + + class idSort_VidMode : public idSort_Quick< vidMode_t, idSort_VidMode > { + public: + int Compare( const vidMode_t & a, const vidMode_t & b ) const { + int wd = a.width - b.width; + int hd = a.height - b.height; + int fd = a.displayHz - b.displayHz; + return ( hd != 0 ) ? hd : ( wd != 0 ) ? wd : fd; + } + }; + + // sort with lowest resolution first + modeList.SortWithTemplate( idSort_VidMode() ); + + return true; + } + } + // Never gets here +} + +/* +==================== +GLW_GetWindowDimensions +==================== +*/ +static bool GLW_GetWindowDimensions( const glimpParms_t parms, int &x, int &y, int &w, int &h ) { + // + // compute width and height + // + if ( parms.fullScreen != 0 ) { + if ( parms.fullScreen == -1 ) { + // borderless window at specific location, as for spanning + // multiple monitor outputs + x = parms.x; + y = parms.y; + w = parms.width; + h = parms.height; + } else { + // get the current monitor position and size on the desktop, assuming + // any required ChangeDisplaySettings has already been done + int displayHz = 0; + if ( !GetDisplayCoordinates( parms.fullScreen - 1, x, y, w, h, displayHz ) ) { + return false; + } + } + } else { + RECT r; + + // adjust width and height for window border + r.bottom = parms.height; + r.left = 0; + r.top = 0; + r.right = parms.width; + + AdjustWindowRect (&r, WINDOW_STYLE|WS_SYSMENU, FALSE); + + w = r.right - r.left; + h = r.bottom - r.top; + + x = parms.x; + y = parms.y; + } + + return true; +} + + +/* +======================= +GLW_CreateWindow + +Responsible for creating the Win32 window. +If fullscreen, it won't have a border +======================= +*/ +static bool GLW_CreateWindow( glimpParms_t parms ) { + int x, y, w, h; + if ( !GLW_GetWindowDimensions( parms, x, y, w, h ) ) { + return false; + } + + int stylebits; + int exstyle; + if ( parms.fullScreen != 0 ) { + exstyle = WS_EX_TOPMOST; + stylebits = WS_POPUP|WS_VISIBLE|WS_SYSMENU; + } else { + exstyle = 0; + stylebits = WINDOW_STYLE|WS_SYSMENU; + } + + win32.hWnd = CreateWindowEx ( + exstyle, + WIN32_WINDOW_CLASS_NAME, + GAME_NAME, + stylebits, + x, y, w, h, + NULL, + NULL, + win32.hInstance, + NULL); + + if ( !win32.hWnd ) { + common->Printf( "^3GLW_CreateWindow() - Couldn't create window^0\n" ); + return false; + } + + ::SetTimer( win32.hWnd, 0, 100, NULL ); + + ShowWindow( win32.hWnd, SW_SHOW ); + UpdateWindow( win32.hWnd ); + common->Printf( "...created window @ %d,%d (%dx%d)\n", x, y, w, h ); + + // makeCurrent NULL frees the DC, so get another + win32.hDC = GetDC( win32.hWnd ); + if ( !win32.hDC ) { + common->Printf( "^3GLW_CreateWindow() - GetDC()failed^0\n" ); + return false; + } + + // Check to see if we can get a stereo pixel format, even if we aren't going to use it, + // so the menu option can be + if ( GLW_ChoosePixelFormat( win32.hDC, parms.multiSamples, true ) != -1 ) { + glConfig.stereoPixelFormatAvailable = true; + } else { + glConfig.stereoPixelFormatAvailable = false; + } + + if ( !GLW_InitDriver( parms ) ) { + ShowWindow( win32.hWnd, SW_HIDE ); + DestroyWindow( win32.hWnd ); + win32.hWnd = NULL; + return false; + } + + SetForegroundWindow( win32.hWnd ); + SetFocus( win32.hWnd ); + + glConfig.isFullscreen = parms.fullScreen; + + return true; +} + +/* +=================== +PrintCDSError +=================== +*/ +static void PrintCDSError( int value ) { + switch ( value ) { + case DISP_CHANGE_RESTART: + common->Printf( "restart required\n" ); + break; + case DISP_CHANGE_BADPARAM: + common->Printf( "bad param\n" ); + break; + case DISP_CHANGE_BADFLAGS: + common->Printf( "bad flags\n" ); + break; + case DISP_CHANGE_FAILED: + common->Printf( "DISP_CHANGE_FAILED\n" ); + break; + case DISP_CHANGE_BADMODE: + common->Printf( "bad mode\n" ); + break; + case DISP_CHANGE_NOTUPDATED: + common->Printf( "not updated\n" ); + break; + default: + common->Printf( "unknown error %d\n", value ); + break; + } +} + +/* +=================== +GLW_ChangeDislaySettingsIfNeeded + +Optionally ChangeDisplaySettings to get a different fullscreen resolution. +Default uses the full desktop resolution. +=================== +*/ +static bool GLW_ChangeDislaySettingsIfNeeded( glimpParms_t parms ) { + // If we had previously changed the display settings on a different monitor, + // go back to standard. + if ( win32.cdsFullscreen != 0 && win32.cdsFullscreen != parms.fullScreen ) { + win32.cdsFullscreen = 0; + ChangeDisplaySettings( 0, 0 ); + Sys_Sleep( 1000 ); // Give the driver some time to think about this change + } + + // 0 is dragable mode on desktop, -1 is borderless window on desktop + if ( parms.fullScreen <= 0 ) { + return true; + } + + // if we are already in the right resolution, don't do a ChangeDisplaySettings + int x, y, width, height, displayHz; + + if ( !GetDisplayCoordinates( parms.fullScreen - 1, x, y, width, height, displayHz ) ) { + return false; + } + if ( width == parms.width && height == parms.height && ( displayHz == parms.displayHz || parms.displayHz == 0 ) ) { + return true; + } + + DEVMODE dm = {}; + + dm.dmSize = sizeof( dm ); + + dm.dmPelsWidth = parms.width; + dm.dmPelsHeight = parms.height; + dm.dmBitsPerPel = 32; + dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + if ( parms.displayHz != 0 ) { + dm.dmDisplayFrequency = parms.displayHz; + dm.dmFields |= DM_DISPLAYFREQUENCY; + } + + common->Printf( "...calling CDS: " ); + + const char * const deviceName = GetDisplayName( parms.fullScreen - 1 ); + + int cdsRet; + if ( ( cdsRet = ChangeDisplaySettingsEx( + deviceName, + &dm, + NULL, + CDS_FULLSCREEN, + NULL) ) == DISP_CHANGE_SUCCESSFUL ) { + common->Printf( "ok\n" ); + win32.cdsFullscreen = parms.fullScreen; + return true; + } + + common->Printf( "^3failed^0, " ); + PrintCDSError( cdsRet ); + return false; +} + +/* +=================== +GLimp_Init + +This is the platform specific OpenGL initialization function. It +is responsible for loading OpenGL, initializing it, +creating a window of the appropriate size, doing +fullscreen manipulations, etc. Its overall responsibility is +to make sure that a functional OpenGL subsystem is operating +when it returns to the ref. + +If there is any failure, the renderer will revert back to safe +parameters and try again. +=================== +*/ +bool GLimp_Init( glimpParms_t parms ) { + const char *driverName; + HDC hDC; + + cmdSystem->AddCommand( "testSwapBuffers", GLimp_TestSwapBuffers, CMD_FL_SYSTEM, "Times swapbuffer options" ); + + common->Printf( "Initializing OpenGL subsystem with multisamples:%i stereo:%i fullscreen:%i\n", + parms.multiSamples, parms.stereo, parms.fullScreen ); + + // check our desktop attributes + hDC = GetDC( GetDesktopWindow() ); + win32.desktopBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + win32.desktopWidth = GetDeviceCaps( hDC, HORZRES ); + win32.desktopHeight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + // we can't run in a window unless it is 32 bpp + if ( win32.desktopBitsPixel < 32 && parms.fullScreen <= 0 ) { + common->Printf("^3Windowed mode requires 32 bit desktop depth^0\n"); + return false; + } + + // save the hardware gamma so it can be + // restored on exit + GLimp_SaveGamma(); + + // create our window classes if we haven't already + GLW_CreateWindowClasses(); + + // this will load the dll and set all our qgl* function pointers, + // but doesn't create a window + + // r_glDriver is only intended for using instrumented OpenGL + // dlls. Normal users should never have to use it, and it is + // not archived. + driverName = r_glDriver.GetString()[0] ? r_glDriver.GetString() : "opengl32"; + if ( !QGL_Init( driverName ) ) { + common->Printf( "^3GLimp_Init() could not load r_glDriver \"%s\"^0\n", driverName ); + return false; + } + + // getting the wgl extensions involves creating a fake window to get a context, + // which is pretty disgusting, and seems to mess with the AGP VAR allocation + GLW_GetWGLExtensionsWithFakeWindow(); + + + + // Optionally ChangeDisplaySettings to get a different fullscreen resolution. + if ( !GLW_ChangeDislaySettingsIfNeeded( parms ) ) { + GLimp_Shutdown(); + return false; + } + + // try to create a window with the correct pixel format + // and init the renderer context + if ( !GLW_CreateWindow( parms ) ) { + GLimp_Shutdown(); + return false; + } + + glConfig.isFullscreen = parms.fullScreen; + glConfig.isStereoPixelFormat = parms.stereo; + glConfig.nativeScreenWidth = parms.width; + glConfig.nativeScreenHeight = parms.height; + glConfig.multisamples = parms.multiSamples; + + glConfig.pixelAspect = 1.0f; // FIXME: some monitor modes may be distorted + // should side-by-side stereo modes be consider aspect 0.5? + + // get the screen size, which may not be reliable... + // If we use the windowDC, I get my 30" monitor, even though the window is + // on a 27" monitor, so get a dedicated DC for the full screen device name. + const idStr deviceName = GetDeviceName( Max( 0, parms.fullScreen - 1 ) ); + + HDC deviceDC = CreateDC( deviceName.c_str(), deviceName.c_str(), NULL, NULL ); + const int mmWide = GetDeviceCaps( win32.hDC, HORZSIZE ); + DeleteDC( deviceDC ); + + if ( mmWide == 0 ) { + glConfig.physicalScreenWidthInCentimeters = 100.0f; + } else { + glConfig.physicalScreenWidthInCentimeters = 0.1f * mmWide; + } + + + // wglSwapinterval, etc + GLW_CheckWGLExtensions( win32.hDC ); + + // check logging + GLimp_EnableLogging( ( r_logFile.GetInteger() != 0 ) ); + + return true; +} + +/* +=================== +GLimp_SetScreenParms + +Sets up the screen based on passed parms.. +=================== +*/ +bool GLimp_SetScreenParms( glimpParms_t parms ) { + // Optionally ChangeDisplaySettings to get a different fullscreen resolution. + if ( !GLW_ChangeDislaySettingsIfNeeded( parms ) ) { + return false; + } + + int x, y, w, h; + if ( !GLW_GetWindowDimensions( parms, x, y, w, h ) ) { + return false; + } + + int exstyle; + int stylebits; + + if ( parms.fullScreen ) { + exstyle = WS_EX_TOPMOST; + stylebits = WS_POPUP|WS_VISIBLE|WS_SYSMENU; + } else { + exstyle = 0; + stylebits = WINDOW_STYLE|WS_SYSMENU; + } + + SetWindowLong( win32.hWnd, GWL_STYLE, stylebits ); + SetWindowLong( win32.hWnd, GWL_EXSTYLE, exstyle ); + SetWindowPos( win32.hWnd, parms.fullScreen ? HWND_TOPMOST : HWND_NOTOPMOST, x, y, w, h, SWP_SHOWWINDOW ); + + glConfig.isFullscreen = parms.fullScreen; + glConfig.pixelAspect = 1.0f; // FIXME: some monitor modes may be distorted + + glConfig.isFullscreen = parms.fullScreen; + glConfig.nativeScreenWidth = parms.width; + glConfig.nativeScreenHeight = parms.height; + + return true; +} + +/* +=================== +GLimp_Shutdown + +This routine does all OS specific shutdown procedures for the OpenGL +subsystem. +=================== +*/ +void GLimp_Shutdown() { + const char *success[] = { "failed", "success" }; + int retVal; + + common->Printf( "Shutting down OpenGL subsystem\n" ); + + // set current context to NULL + if ( qwglMakeCurrent ) { + retVal = qwglMakeCurrent( NULL, NULL ) != 0; + common->Printf( "...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] ); + } + + // delete HGLRC + if ( win32.hGLRC ) { + retVal = qwglDeleteContext( win32.hGLRC ) != 0; + common->Printf( "...deleting GL context: %s\n", success[retVal] ); + win32.hGLRC = NULL; + } + + // release DC + if ( win32.hDC ) { + retVal = ReleaseDC( win32.hWnd, win32.hDC ) != 0; + common->Printf( "...releasing DC: %s\n", success[retVal] ); + win32.hDC = NULL; + } + + // destroy window + if ( win32.hWnd ) { + common->Printf( "...destroying window\n" ); + ShowWindow( win32.hWnd, SW_HIDE ); + DestroyWindow( win32.hWnd ); + win32.hWnd = NULL; + } + + // reset display settings + if ( win32.cdsFullscreen ) { + common->Printf( "...resetting display\n" ); + ChangeDisplaySettings( 0, 0 ); + win32.cdsFullscreen = 0; + } + + // close the thread so the handle doesn't dangle + if ( win32.renderThreadHandle ) { + common->Printf( "...closing smp thread\n" ); + CloseHandle( win32.renderThreadHandle ); + win32.renderThreadHandle = NULL; + } + + // restore gamma + GLimp_RestoreGamma(); + + // shutdown QGL subsystem + QGL_Shutdown(); +} + +/* +===================== +GLimp_SwapBuffers +===================== +*/ +void GLimp_SwapBuffers() { + if ( r_swapInterval.IsModified() ) { + r_swapInterval.ClearModified(); + + int interval = 0; + if ( r_swapInterval.GetInteger() == 1 ) { + interval = ( glConfig.swapControlTearAvailable ) ? -1 : 1; + } else if ( r_swapInterval.GetInteger() == 2 ) { + interval = 1; + } + + if ( wglSwapIntervalEXT ) { + wglSwapIntervalEXT( interval ); + } + } + + qwglSwapBuffers( win32.hDC ); +} + +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +/* +=================== +GLimp_ActivateContext +=================== +*/ +void GLimp_ActivateContext() { + if ( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) { + win32.wglErrors++; + } +} + +/* +=================== +GLimp_DeactivateContext +=================== +*/ +void GLimp_DeactivateContext() { + qglFinish(); + if ( !qwglMakeCurrent( win32.hDC, NULL ) ) { + win32.wglErrors++; + } +} + +/* +=================== +GLimp_RenderThreadWrapper +=================== +*/ +static void GLimp_RenderThreadWrapper() { + win32.glimpRenderThread(); + + // unbind the context before we die + qwglMakeCurrent( win32.hDC, NULL ); +} + +/* +======================= +GLimp_SpawnRenderThread + +Returns false if the system only has a single processor +======================= +*/ +bool GLimp_SpawnRenderThread( void (*function)() ) { + SYSTEM_INFO info; + + // check number of processors + GetSystemInfo( &info ); + if ( info.dwNumberOfProcessors < 2 ) { + return false; + } + + // create the IPC elements + win32.renderCommandsEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + win32.renderCompletedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + win32.renderActiveEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + + win32.glimpRenderThread = function; + + win32.renderThreadHandle = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)GLimp_RenderThreadWrapper, // LPTHREAD_START_ROUTINE lpStartAddr, + 0, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &win32.renderThreadId ); + + if ( !win32.renderThreadHandle ) { + common->Error( "GLimp_SpawnRenderThread: failed" ); + } + + SetThreadPriority( win32.renderThreadHandle, THREAD_PRIORITY_ABOVE_NORMAL ); +#if 0 + // make sure they always run on different processors + SetThreadAffinityMask( GetCurrentThread, 1 ); + SetThreadAffinityMask( win32.renderThreadHandle, 2 ); +#endif + + return true; +} + + +//#define DEBUG_PRINTS + +/* +=================== +GLimp_BackEndSleep +=================== +*/ +void *GLimp_BackEndSleep() { + void *data; + +#ifdef DEBUG_PRINTS +OutputDebugString( "-->GLimp_BackEndSleep\n" ); +#endif + ResetEvent( win32.renderActiveEvent ); + + // after this, the front end can exit GLimp_FrontEndSleep + SetEvent( win32.renderCompletedEvent ); + + WaitForSingleObject( win32.renderCommandsEvent, INFINITE ); + + ResetEvent( win32.renderCompletedEvent ); + ResetEvent( win32.renderCommandsEvent ); + + data = win32.smpData; + + // after this, the main thread can exit GLimp_WakeRenderer + SetEvent( win32.renderActiveEvent ); + +#ifdef DEBUG_PRINTS +OutputDebugString( "<--GLimp_BackEndSleep\n" ); +#endif + return data; +} + +/* +=================== +GLimp_FrontEndSleep +=================== +*/ +void GLimp_FrontEndSleep() { +#ifdef DEBUG_PRINTS +OutputDebugString( "-->GLimp_FrontEndSleep\n" ); +#endif + WaitForSingleObject( win32.renderCompletedEvent, INFINITE ); + +#ifdef DEBUG_PRINTS +OutputDebugString( "<--GLimp_FrontEndSleep\n" ); +#endif +} + +volatile bool renderThreadActive; + +/* +=================== +GLimp_WakeBackEnd +=================== +*/ +void GLimp_WakeBackEnd( void *data ) { + int r; + +#ifdef DEBUG_PRINTS +OutputDebugString( "-->GLimp_WakeBackEnd\n" ); +#endif + win32.smpData = data; + + if ( renderThreadActive ) { + common->FatalError( "GLimp_WakeBackEnd: already active" ); + } + + r = WaitForSingleObject( win32.renderActiveEvent, 0 ); + if ( r == WAIT_OBJECT_0 ) { + common->FatalError( "GLimp_WakeBackEnd: already signaled" ); + } + + r = WaitForSingleObject( win32.renderCommandsEvent, 0 ); + if ( r == WAIT_OBJECT_0 ) { + common->FatalError( "GLimp_WakeBackEnd: commands already signaled" ); + } + + // after this, the renderer can continue through GLimp_RendererSleep + SetEvent( win32.renderCommandsEvent ); + + r = WaitForSingleObject( win32.renderActiveEvent, 5000 ); + + if ( r == WAIT_TIMEOUT ) { + common->FatalError( "GLimp_WakeBackEnd: WAIT_TIMEOUT" ); + } + +#ifdef DEBUG_PRINTS +OutputDebugString( "<--GLimp_WakeBackEnd\n" ); +#endif +} + +/* +=================== +GLimp_ExtensionPointer + +Returns a function pointer for an OpenGL extension entry point +=================== +*/ +GLExtension_t GLimp_ExtensionPointer( const char *name ) { + void (*proc)(); + + proc = (GLExtension_t)qwglGetProcAddress( name ); + + if ( !proc ) { + common->Printf( "Couldn't find proc address for: %s\n", name ); + } + + return proc; +} diff --git a/neo/sys/win32/win_input.cpp b/neo/sys/win32/win_input.cpp new file mode 100644 index 00000000..a2ff2d6b --- /dev/null +++ b/neo/sys/win32/win_input.cpp @@ -0,0 +1,967 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../sys_session_local.h" + +#include "win_local.h" + +#define DINPUT_BUFFERSIZE 256 + +/* +============================================================ + +DIRECT INPUT KEYBOARD CONTROL + +============================================================ +*/ + +bool IN_StartupKeyboard() { + HRESULT hr; + bool bExclusive; + bool bForeground; + bool bImmediate; + bool bDisableWindowsKey; + DWORD dwCoopFlags; + + if (!win32.g_pdi) { + common->Printf("keyboard: DirectInput has not been started\n"); + return false; + } + + if (win32.g_pKeyboard) { + win32.g_pKeyboard->Release(); + win32.g_pKeyboard = NULL; + } + + // Detrimine where the buffer would like to be allocated + bExclusive = false; + bForeground = true; + bImmediate = false; + bDisableWindowsKey = true; + + if( bExclusive ) + dwCoopFlags = DISCL_EXCLUSIVE; + else + dwCoopFlags = DISCL_NONEXCLUSIVE; + + if( bForeground ) + dwCoopFlags |= DISCL_FOREGROUND; + else + dwCoopFlags |= DISCL_BACKGROUND; + + // Disabling the windows key is only allowed only if we are in foreground nonexclusive + if( bDisableWindowsKey && !bExclusive && bForeground ) + dwCoopFlags |= DISCL_NOWINKEY; + + // Obtain an interface to the system keyboard device. + if( FAILED( hr = win32.g_pdi->CreateDevice( GUID_SysKeyboard, &win32.g_pKeyboard, NULL ) ) ) { + common->Printf("keyboard: couldn't find a keyboard device\n"); + return false; + } + + // Set the data format to "keyboard format" - a predefined data format + // + // A data format specifies which controls on a device we + // are interested in, and how they should be reported. + // + // This tells DirectInput that we will be passing an array + // of 256 bytes to IDirectInputDevice::GetDeviceState. + if( FAILED( hr = win32.g_pKeyboard->SetDataFormat( &c_dfDIKeyboard ) ) ) + return false; + + // Set the cooperativity level to let DirectInput know how + // this device should interact with the system and with other + // DirectInput applications. + hr = win32.g_pKeyboard->SetCooperativeLevel( win32.hWnd, dwCoopFlags ); + if( hr == DIERR_UNSUPPORTED && !bForeground && bExclusive ) { + common->Printf("keyboard: SetCooperativeLevel() returned DIERR_UNSUPPORTED.\nFor security reasons, background exclusive keyboard access is not allowed.\n"); + return false; + } + + if( FAILED(hr) ) { + return false; + } + + if( !bImmediate ) { + // IMPORTANT STEP TO USE BUFFERED DEVICE DATA! + // + // DirectInput uses unbuffered I/O (buffer size = 0) by default. + // If you want to read buffered data, you need to set a nonzero + // buffer size. + // + // Set the buffer size to DINPUT_BUFFERSIZE (defined above) elements. + // + // The buffer size is a DWORD property associated with the device. + DIPROPDWORD dipdw; + + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DINPUT_BUFFERSIZE; // Arbitary buffer size + + if( FAILED( hr = win32.g_pKeyboard->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) ) + return false; + } + + // Acquire the newly created device + win32.g_pKeyboard->Acquire(); + + common->Printf( "keyboard: DirectInput initialized.\n"); + return true; +} + +/* +========================== +IN_DeactivateKeyboard +========================== +*/ +void IN_DeactivateKeyboard() { + if (!win32.g_pKeyboard) { + return; + } + win32.g_pKeyboard->Unacquire( ); +} + +/* +============================================================ + +DIRECT INPUT MOUSE CONTROL + +============================================================ +*/ + +/* +======================== +IN_InitDirectInput +======================== +*/ + +void IN_InitDirectInput() { + HRESULT hr; + + common->Printf( "Initializing DirectInput...\n" ); + + if ( win32.g_pdi != NULL ) { + win32.g_pdi->Release(); // if the previous window was destroyed we need to do this + win32.g_pdi = NULL; + } + + // Register with the DirectInput subsystem and get a pointer + // to a IDirectInput interface we can use. + // Create the base DirectInput object + if ( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&win32.g_pdi, NULL ) ) ) { + common->Printf ("DirectInputCreate failed\n"); + } +} + +/* +======================== +IN_InitDIMouse +======================== +*/ +bool IN_InitDIMouse() { + HRESULT hr; + + if ( win32.g_pdi == NULL) { + return false; + } + + // obtain an interface to the system mouse device. + hr = win32.g_pdi->CreateDevice( GUID_SysMouse, &win32.g_pMouse, NULL); + + if (FAILED(hr)) { + common->Printf ("mouse: Couldn't open DI mouse device\n"); + return false; + } + + // Set the data format to "mouse format" - a predefined data format + // + // A data format specifies which controls on a device we + // are interested in, and how they should be reported. + // + // This tells DirectInput that we will be passing a + // DIMOUSESTATE2 structure to IDirectInputDevice::GetDeviceState. + if( FAILED( hr = win32.g_pMouse->SetDataFormat( &c_dfDIMouse2 ) ) ) { + common->Printf ("mouse: Couldn't set DI mouse format\n"); + return false; + } + + // set the cooperativity level. + hr = win32.g_pMouse->SetCooperativeLevel( win32.hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); + + if (FAILED(hr)) { + common->Printf ("mouse: Couldn't set DI coop level\n"); + return false; + } + + + // IMPORTANT STEP TO USE BUFFERED DEVICE DATA! + // + // DirectInput uses unbuffered I/O (buffer size = 0) by default. + // If you want to read buffered data, you need to set a nonzero + // buffer size. + // + // Set the buffer size to SAMPLE_BUFFER_SIZE (defined above) elements. + // + // The buffer size is a DWORD property associated with the device. + DIPROPDWORD dipdw; + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DINPUT_BUFFERSIZE; // Arbitary buffer size + + if( FAILED( hr = win32.g_pMouse->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) ) { + common->Printf ("mouse: Couldn't set DI buffersize\n"); + return false; + } + + IN_ActivateMouse(); + + // clear any pending samples + int mouseEvents[MAX_MOUSE_EVENTS][2]; + Sys_PollMouseInputEvents( mouseEvents ); + + common->Printf( "mouse: DirectInput initialized.\n"); + return true; +} + + +/* +========================== +IN_ActivateMouse +========================== +*/ +void IN_ActivateMouse() { + int i; + HRESULT hr; + + if ( !win32.in_mouse.GetBool() || win32.mouseGrabbed || !win32.g_pMouse ) { + return; + } + + win32.mouseGrabbed = true; + for ( i = 0; i < 10; i++ ) { + if ( ::ShowCursor( false ) < 0 ) { + break; + } + } + + // we may fail to reacquire if the window has been recreated + hr = win32.g_pMouse->Acquire(); + if (FAILED(hr)) { + return; + } + + // set the cooperativity level. + hr = win32.g_pMouse->SetCooperativeLevel( win32.hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); +} + +/* +========================== +IN_DeactivateMouse +========================== +*/ +void IN_DeactivateMouse() { + int i; + + if (!win32.g_pMouse || !win32.mouseGrabbed ) { + return; + } + + win32.g_pMouse->Unacquire(); + + for ( i = 0; i < 10; i++ ) { + if ( ::ShowCursor( true ) >= 0 ) { + break; + } + } + win32.mouseGrabbed = false; +} + +/* +========================== +IN_DeactivateMouseIfWindowed +========================== +*/ +void IN_DeactivateMouseIfWindowed() { + if ( !win32.cdsFullscreen ) { + IN_DeactivateMouse(); + } +} + +/* +============================================================ + + MOUSE CONTROL + +============================================================ +*/ + + +/* +=========== +Sys_ShutdownInput +=========== +*/ +void Sys_ShutdownInput() { + IN_DeactivateMouse(); + IN_DeactivateKeyboard(); + if ( win32.g_pKeyboard ) { + win32.g_pKeyboard->Release(); + win32.g_pKeyboard = NULL; + } + + if ( win32.g_pMouse ) { + win32.g_pMouse->Release(); + win32.g_pMouse = NULL; + } + + if ( win32.g_pdi ) { + win32.g_pdi->Release(); + win32.g_pdi = NULL; + } +} + +/* +=========== +Sys_InitInput +=========== +*/ +void Sys_InitInput() { + common->Printf ("\n------- Input Initialization -------\n"); + IN_InitDirectInput(); + if ( win32.in_mouse.GetBool() ) { + IN_InitDIMouse(); + // don't grab the mouse on initialization + Sys_GrabMouseCursor( false ); + } else { + common->Printf ("Mouse control not active.\n"); + } + IN_StartupKeyboard(); + + common->Printf ("------------------------------------\n"); + win32.in_mouse.ClearModified(); +} + +/* +================== +IN_Frame + +Called every frame, even if not generating commands +================== +*/ +void IN_Frame() { + bool shouldGrab = true; + + if ( !win32.in_mouse.GetBool() ) { + shouldGrab = false; + } + // if fullscreen, we always want the mouse + if ( !win32.cdsFullscreen ) { + if ( win32.mouseReleased ) { + shouldGrab = false; + } + if ( win32.movingWindow ) { + shouldGrab = false; + } + if ( !win32.activeApp ) { + shouldGrab = false; + } + } + + if ( shouldGrab != win32.mouseGrabbed ) { + if ( usercmdGen != NULL ) { + usercmdGen->Clear(); + } + + if ( win32.mouseGrabbed ) { + IN_DeactivateMouse(); + } else { + IN_ActivateMouse(); + +#if 0 // if we can't reacquire, try reinitializing + if ( !IN_InitDIMouse() ) { + win32.in_mouse.SetBool( false ); + return; + } +#endif + } + } +} + + +void Sys_GrabMouseCursor( bool grabIt ) { + win32.mouseReleased = !grabIt; + if ( !grabIt ) { + // release it right now + IN_Frame(); + } +} + +//===================================================================================== + +static DIDEVICEOBJECTDATA polled_didod[ DINPUT_BUFFERSIZE ]; // Receives buffered data + +static int diFetch; +static byte toggleFetch[2][ 256 ]; + + +#if 1 +// I tried doing the full-state get to address a keyboard problem on one system, +// but it didn't make any difference + +/* +==================== +Sys_PollKeyboardInputEvents +==================== +*/ +int Sys_PollKeyboardInputEvents() { + DWORD dwElements; + HRESULT hr; + + if( win32.g_pKeyboard == NULL ) { + return 0; + } + + dwElements = DINPUT_BUFFERSIZE; + hr = win32.g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), + polled_didod, &dwElements, 0 ); + if( hr != DI_OK ) + { + // We got an error or we got DI_BUFFEROVERFLOW. + // + // Either way, it means that continuous contact with the + // device has been lost, either due to an external + // interruption, or because the buffer overflowed + // and some events were lost. + hr = win32.g_pKeyboard->Acquire(); + + + + // nuke the garbage + if (!FAILED(hr)) { + //Bug 951: The following command really clears the garbage input. + //The original will still process keys in the buffer and was causing + //some problems. + win32.g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), NULL, &dwElements, 0 ); + dwElements = 0; + } + // hr may be DIERR_OTHERAPPHASPRIO or other errors. This + // may occur when the app is minimized or in the process of + // switching, so just try again later + } + + if( FAILED(hr) ) { + return 0; + } + + return dwElements; +} + +#else + +/* +==================== +Sys_PollKeyboardInputEvents + +Fake events by getting the entire device state +and checking transitions +==================== +*/ +int Sys_PollKeyboardInputEvents() { + HRESULT hr; + + if( win32.g_pKeyboard == NULL ) { + return 0; + } + + hr = win32.g_pKeyboard->GetDeviceState( sizeof( toggleFetch[ diFetch ] ), toggleFetch[ diFetch ] ); + if( hr != DI_OK ) + { + // We got an error or we got DI_BUFFEROVERFLOW. + // + // Either way, it means that continuous contact with the + // device has been lost, either due to an external + // interruption, or because the buffer overflowed + // and some events were lost. + hr = win32.g_pKeyboard->Acquire(); + + // nuke the garbage + if (!FAILED(hr)) { + hr = win32.g_pKeyboard->GetDeviceState( sizeof( toggleFetch[ diFetch ] ), toggleFetch[ diFetch ] ); + } + // hr may be DIERR_OTHERAPPHASPRIO or other errors. This + // may occur when the app is minimized or in the process of + // switching, so just try again later + } + + if( FAILED(hr) ) { + return 0; + } + + // build faked events + int numChanges = 0; + + for ( int i = 0 ; i < 256 ; i++ ) { + if ( toggleFetch[0][i] != toggleFetch[1][i] ) { + polled_didod[ numChanges ].dwOfs = i; + polled_didod[ numChanges ].dwData = toggleFetch[ diFetch ][i] ? 0x80 : 0; + numChanges++; + } + } + + diFetch ^= 1; + + return numChanges; +} + +#endif + +/* +==================== +Sys_PollKeyboardInputEvents +==================== +*/ +int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ) { + ch = polled_didod[ n ].dwOfs; + state = ( polled_didod[ n ].dwData & 0x80 ) == 0x80; + if ( ch == K_PRINTSCREEN || ch == K_LCTRL || ch == K_LALT || ch == K_RCTRL || ch == K_RALT ) { + // for windows, add a keydown event for print screen here, since + // windows doesn't send keydown events to the WndProc for this key. + // ctrl and alt are handled here to get around windows sending ctrl and + // alt messages when the right-alt is pressed on non-US 102 keyboards. + Sys_QueEvent( SE_KEY, ch, state, 0, NULL, 0 ); + } + return ch; +} + + +void Sys_EndKeyboardInputEvents() { +} + +//===================================================================================== + + +int Sys_PollMouseInputEvents( int mouseEvents[MAX_MOUSE_EVENTS][2] ) { + DWORD dwElements; + HRESULT hr; + + if ( !win32.g_pMouse || !win32.mouseGrabbed ) { + return 0; + } + + dwElements = DINPUT_BUFFERSIZE; + hr = win32.g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), polled_didod, &dwElements, 0 ); + + if( hr != DI_OK ) { + hr = win32.g_pMouse->Acquire(); + // clear the garbage + if (!FAILED(hr)) { + win32.g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), polled_didod, &dwElements, 0 ); + } + } + + if( FAILED(hr) ) { + return 0; + } + + if ( dwElements > MAX_MOUSE_EVENTS ) { + dwElements = MAX_MOUSE_EVENTS; + } + + for( DWORD i = 0; i < dwElements; i++ ) { + mouseEvents[i][0] = M_INVALID; + mouseEvents[i][1] = 0; + + if ( polled_didod[i].dwOfs >= DIMOFS_BUTTON0 && polled_didod[i].dwOfs <= DIMOFS_BUTTON7 ) { + const int mouseButton = ( polled_didod[i].dwOfs - DIMOFS_BUTTON0 ); + const bool mouseDown = (polled_didod[i].dwData & 0x80) == 0x80; + mouseEvents[i][0] = M_ACTION1 + mouseButton; + mouseEvents[i][1] = mouseDown; + Sys_QueEvent( SE_KEY, K_MOUSE1 + mouseButton, mouseDown, 0, NULL, 0 ); + } else { + switch (polled_didod[i].dwOfs) { + case DIMOFS_X: + mouseEvents[i][0] = M_DELTAX; + mouseEvents[i][1] = polled_didod[i].dwData; + Sys_QueEvent( SE_MOUSE, polled_didod[i].dwData, 0, 0, NULL, 0 ); + break; + case DIMOFS_Y: + mouseEvents[i][0] = M_DELTAY; + mouseEvents[i][1] = polled_didod[i].dwData; + Sys_QueEvent( SE_MOUSE, 0, polled_didod[i].dwData, 0, NULL, 0 ); + break; + case DIMOFS_Z: + mouseEvents[i][0] = M_DELTAZ; + mouseEvents[i][1] = (int)polled_didod[i].dwData / WHEEL_DELTA; + { + const int value = (int)polled_didod[i].dwData / WHEEL_DELTA; + const int key = value < 0 ? K_MWHEELDOWN : K_MWHEELUP; + const int iterations = abs( value ); + for ( int i = 0; i < iterations; i++ ) { + Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 ); + Sys_QueEvent( SE_KEY, key, false, 0, NULL, 0 ); + } + } + break; + } + } + } + + return dwElements; +} + +//===================================================================================== +// Joystick Input Handling +//===================================================================================== + +void Sys_SetRumble( int device, int low, int hi ) { + return win32.g_Joystick.SetRumble( device, low, hi ); +} + +int Sys_PollJoystickInputEvents( int deviceNum ) { + return win32.g_Joystick.PollInputEvents( deviceNum ); +} + + +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ) { + return win32.g_Joystick.ReturnInputEvent( n, action, value ); +} + + +void Sys_EndJoystickInputEvents() { +} + + +/* +======================== +JoystickSamplingThread +======================== +*/ +static int threadTimeDeltas[256]; +static int threadPacket[256]; +static int threadCount; +void JoystickSamplingThread( void *data ) { + static int prevTime = 0; + static uint64 nextCheck[MAX_JOYSTICKS] = { 0 }; + const uint64 waitTime = 5000000; // poll every 5 seconds to see if a controller was connected + while( 1 ) { + // hopefully we see close to 4000 usec each loop + int now = Sys_Microseconds(); + int delta; + if ( prevTime == 0 ) { + delta = 4000; + } else { + delta = now - prevTime; + } + prevTime = now; + threadTimeDeltas[threadCount&255] = delta; + threadCount++; + + { + XINPUT_STATE joyData[MAX_JOYSTICKS]; + bool validData[MAX_JOYSTICKS]; + for ( int i = 0 ; i < MAX_JOYSTICKS ; i++ ) { + if ( now >= nextCheck[i] ) { + // XInputGetState might block... for a _really_ long time.. + validData[i] = XInputGetState( i, &joyData[i] ) == ERROR_SUCCESS; + + // allow an immediate data poll if the input device is connected else + // wait for some time to see if another device was reconnected. + // Checking input state infrequently for newly connected devices prevents + // severe slowdowns on PC, especially on WinXP64. + if ( validData[i] ) { + nextCheck[i] = 0; + } else { + nextCheck[i] = now + waitTime; + } + } + } + + // do this short amount of processing inside a critical section + idScopedCriticalSection cs( win32.g_Joystick.mutexXis ); + + for ( int i = 0 ; i < MAX_JOYSTICKS ; i++ ) { + controllerState_t * cs = &win32.g_Joystick.controllers[i]; + + if ( !validData[i] ) { + cs->valid = false; + continue; + } + cs->valid = true; + + XINPUT_STATE& current = joyData[i]; + + cs->current = current; + + // Switch from using cs->current to current to reduce chance of Load-Hit-Store on consoles + + threadPacket[threadCount&255] = current.dwPacketNumber; +#if 0 + if ( xis.dwPacketNumber == oldXis[ inputDeviceNum ].dwPacketNumber ) { + return numEvents; + } +#endif + cs->buttonBits |= current.Gamepad.wButtons; + } + } + + // we want this to be processed at least 250 times a second + WaitForSingleObject( win32.g_Joystick.timer, INFINITE ); + } +} + + +/* +======================== +idJoystickWin32::idJoystickWin32 +======================== +*/ +idJoystickWin32::idJoystickWin32() { + numEvents = 0; + memset( &events, 0, sizeof( events ) ); + memset( &controllers, 0, sizeof( controllers ) ); + memset( buttonStates, 0, sizeof( buttonStates ) ); + memset( joyAxis, 0, sizeof( joyAxis ) ); +} + +/* +======================== +idJoystickWin32::Init +======================== +*/ +bool idJoystickWin32::Init() { + idJoystick::Init(); + + // setup the timer that the high frequency thread will wait on + // to fire every 4 msec + timer = CreateWaitableTimer( NULL, FALSE, "JoypadTimer" ); + LARGE_INTEGER dueTime; + dueTime.QuadPart = -1; + if ( !SetWaitableTimer( timer, &dueTime, 4, NULL, NULL, FALSE ) ) { + idLib::FatalError( "SetWaitableTimer for joystick failed" ); + } + + // spawn the high frequency joystick reading thread + Sys_CreateThread( (xthread_t)JoystickSamplingThread, NULL, THREAD_HIGHEST, "Joystick", CORE_1A ); + + return false; +} + +/* +======================== +idJoystickWin32::SetRumble +======================== +*/ +void idJoystickWin32::SetRumble( int inputDeviceNum, int rumbleLow, int rumbleHigh ) { + if ( inputDeviceNum < 0 || inputDeviceNum >= MAX_JOYSTICKS ) { + return; + } + if ( !controllers[inputDeviceNum].valid ) { + return; + } + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = idMath::ClampInt( 0, 65535, rumbleLow ); + vibration.wRightMotorSpeed = idMath::ClampInt( 0, 65535, rumbleHigh ); + DWORD err = XInputSetState( inputDeviceNum, &vibration ); + if ( err != ERROR_SUCCESS ) { + idLib::Warning( "XInputSetState error: 0x%x", err ); + } +} + +/* +======================== +idJoystickWin32::PostInputEvent +======================== +*/ +void idJoystickWin32::PostInputEvent( int inputDeviceNum, int event, int value, int range ) { + // These events are used for GUI button presses + if ( ( event >= J_ACTION1 ) && ( event <= J_ACTION_MAX ) ) { + PushButton( inputDeviceNum, K_JOY1 + ( event - J_ACTION1 ), value != 0 ); + } else if ( event == J_AXIS_LEFT_X ) { + PushButton( inputDeviceNum, K_JOY_STICK1_LEFT, ( value < -range ) ); + PushButton( inputDeviceNum, K_JOY_STICK1_RIGHT, ( value > range ) ); + } else if ( event == J_AXIS_LEFT_Y ) { + PushButton( inputDeviceNum, K_JOY_STICK1_UP, ( value < -range ) ); + PushButton( inputDeviceNum, K_JOY_STICK1_DOWN, ( value > range ) ); + } else if ( event == J_AXIS_RIGHT_X ) { + PushButton( inputDeviceNum, K_JOY_STICK2_LEFT, ( value < -range ) ); + PushButton( inputDeviceNum, K_JOY_STICK2_RIGHT, ( value > range ) ); + } else if ( event == J_AXIS_RIGHT_Y ) { + PushButton( inputDeviceNum, K_JOY_STICK2_UP, ( value < -range ) ); + PushButton( inputDeviceNum, K_JOY_STICK2_DOWN, ( value > range ) ); + } else if ( ( event >= J_DPAD_UP ) && ( event <= J_DPAD_RIGHT ) ) { + PushButton( inputDeviceNum, K_JOY_DPAD_UP + ( event - J_DPAD_UP ), value != 0 ); + } else if ( event == J_AXIS_LEFT_TRIG ) { + PushButton( inputDeviceNum, K_JOY_TRIGGER1, ( value > range ) ); + } else if ( event == J_AXIS_RIGHT_TRIG ) { + PushButton( inputDeviceNum, K_JOY_TRIGGER2, ( value > range ) ); + } + if ( event >= J_AXIS_MIN && event <= J_AXIS_MAX ) { + int axis = event - J_AXIS_MIN; + int percent = ( value * 16 ) / range; + if ( joyAxis[inputDeviceNum][axis] != percent ) { + joyAxis[inputDeviceNum][axis] = percent; + Sys_QueEvent( SE_JOYSTICK, axis, percent, 0, NULL, inputDeviceNum ); + } + } + + // These events are used for actual game input + events[numEvents].event = event; + events[numEvents].value = value; + numEvents++; +} + +/* +======================== +idJoystickWin32::PollInputEvents +======================== +*/ +int idJoystickWin32::PollInputEvents( int inputDeviceNum ) { + numEvents = 0; + + if ( !win32.activeApp ) { + return numEvents; + } + + assert( inputDeviceNum < 4 ); + +// if ( inputDeviceNum > in_joystick.GetInteger() ) { +// return numEvents; +// } + + controllerState_t *cs = &controllers[ inputDeviceNum ]; + + // grab the current packet under a critical section + XINPUT_STATE xis; + XINPUT_STATE old; + int orBits; + { + idScopedCriticalSection crit( mutexXis ); + xis = cs->current; + old = cs->previous; + cs->previous = xis; + // fetch or'd button bits + orBits = cs->buttonBits; + cs->buttonBits = 0; + } +#if 0 + if ( XInputGetState( inputDeviceNum, &xis ) != ERROR_SUCCESS ) { + return numEvents; + } +#endif + for ( int i = 0 ; i < 32 ; i++ ) { + int bit = 1<IsSystemUIShowing() ) { + // memset xis so the current input does not get latched if the UI is showing + memset( &xis, 0, sizeof( XINPUT_STATE ) ); + } + + int joyRemap[16] = { + J_DPAD_UP, J_DPAD_DOWN, // Up, Down + J_DPAD_LEFT, J_DPAD_RIGHT, // Left, Right + J_ACTION9, J_ACTION10, // Start, Back + J_ACTION7, J_ACTION8, // Left Stick Down, Right Stick Down + J_ACTION5, J_ACTION6, // Black, White (Left Shoulder, Right Shoulder) + 0, 0, // Unused + J_ACTION1, J_ACTION2, // A, B + J_ACTION3, J_ACTION4, // X, Y + }; + + // Check the digital buttons + for ( int i = 0; i < 16; i++ ) { + int mask = ( 1 << i ); + if ( ( xis.Gamepad.wButtons & mask ) != ( old.Gamepad.wButtons & mask ) ) { + PostInputEvent( inputDeviceNum, joyRemap[i], ( xis.Gamepad.wButtons & mask ) > 0 ); + } + } + + // Check the triggers + if ( xis.Gamepad.bLeftTrigger != old.Gamepad.bLeftTrigger ) { + PostInputEvent( inputDeviceNum, J_AXIS_LEFT_TRIG, xis.Gamepad.bLeftTrigger * 128 ); + } + if ( xis.Gamepad.bRightTrigger != old.Gamepad.bRightTrigger ) { + PostInputEvent( inputDeviceNum, J_AXIS_RIGHT_TRIG, xis.Gamepad.bRightTrigger * 128 ); + } + + if ( xis.Gamepad.sThumbLX != old.Gamepad.sThumbLX ) { + PostInputEvent( inputDeviceNum, J_AXIS_LEFT_X, xis.Gamepad.sThumbLX ); + } + if ( xis.Gamepad.sThumbLY != old.Gamepad.sThumbLY ) { + PostInputEvent( inputDeviceNum, J_AXIS_LEFT_Y, -xis.Gamepad.sThumbLY ); + } + if ( xis.Gamepad.sThumbRX != old.Gamepad.sThumbRX ) { + PostInputEvent( inputDeviceNum, J_AXIS_RIGHT_X, xis.Gamepad.sThumbRX ); + } + if ( xis.Gamepad.sThumbRY != old.Gamepad.sThumbRY ) { + PostInputEvent( inputDeviceNum, J_AXIS_RIGHT_Y, -xis.Gamepad.sThumbRY ); + } + + return numEvents; +} + + +/* +======================== +idJoystickWin32::ReturnInputEvent +======================== +*/ +int idJoystickWin32::ReturnInputEvent( const int n, int & action, int &value ) { + if ( ( n < 0 ) || ( n >= MAX_JOY_EVENT ) ) { + return 0; + } + + action = events[ n ].event; + value = events[ n ].value; + + return 1; +} + +/* +======================== +idJoystickWin32::PushButton +======================== +*/ +void idJoystickWin32::PushButton( int inputDeviceNum, int key, bool value ) { + // So we don't keep sending the same SE_KEY message over and over again + if ( buttonStates[inputDeviceNum][key] != value ) { + buttonStates[inputDeviceNum][key] = value; + Sys_QueEvent( SE_KEY, key, value, 0, NULL, inputDeviceNum ); + } +} \ No newline at end of file diff --git a/neo/sys/win32/win_input.h b/neo/sys/win32/win_input.h new file mode 100644 index 00000000..dfc45497 --- /dev/null +++ b/neo/sys/win32/win_input.h @@ -0,0 +1,96 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +//#if defined( ID_VS2010 ) +//#include "../../../libs/dxsdk_June2010/include/xinput.h" +//#else +#include +//#endif + +static const int MAX_JOYSTICKS = 4; + +/* +================================================================================================ + + Joystick Win32 + +================================================================================================ +*/ + +struct controllerState_t { + // the current states are updated by the input thread at 250 hz + XINPUT_STATE current; + + // the previous state is latched at polling time + XINPUT_STATE previous; + + // The current button bits are or'd into this at the high sampling rate, then + // zero'd by the main thread when a usercmd_t is created. This prevents the + // complete missing of a button press that went down and up between two usercmd_t + // creations, although it can add sn extra frame of latency to sensing a release. + int buttonBits; + + // Only valid controllers will have their rumble set + bool valid; +}; + + +class idJoystickWin32 : idJoystick { +public: + idJoystickWin32(); + + virtual bool Init(); + virtual void SetRumble( int deviceNum, int rumbleLow, int rumbleHigh ); + virtual int PollInputEvents( int inputDeviceNum ); + virtual int ReturnInputEvent( const int n, int &action, int &value ); + virtual void EndInputEvents() {} + +protected: + friend void JoystickSamplingThread( void *data ); + + void PushButton( int inputDeviceNum, int key, bool value ); + void PostInputEvent( int inputDeviceNum, int event, int value, int range = 16384 ); + + idSysMutex mutexXis; // lock this before using currentXis or stickIntegrations + HANDLE timer; // fire every 4 msec + + int numEvents; + + struct { + int event; + int value; + } events[ MAX_JOY_EVENT ]; + + controllerState_t controllers[ MAX_JOYSTICKS ]; + + // should these be per-controller? + bool buttonStates[MAX_INPUT_DEVICES][K_LAST_KEY]; // For keeping track of button up/down events + int joyAxis[MAX_INPUT_DEVICES][MAX_JOYSTICK_AXIS]; // For keeping track of joystick axises +}; \ No newline at end of file diff --git a/neo/sys/win32/win_local.h b/neo/sys/win32/win_local.h new file mode 100644 index 00000000..e16dd9c1 --- /dev/null +++ b/neo/sys/win32/win_local.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __WIN_LOCAL_H__ +#define __WIN_LOCAL_H__ + +#include +#include "../../renderer/OpenGL/wglext.h" // windows OpenGL extensions +#include "win_input.h" + +// WGL_ARB_extensions_string +extern PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; + +// WGL_EXT_swap_interval +extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + +// WGL_ARB_pixel_format +extern PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB; +extern PFNWGLGETPIXELFORMATATTRIBFVARBPROC wglGetPixelFormatAttribfvARB; +extern PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; + +// WGL_ARB_pbuffer +extern PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB; +extern PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB; +extern PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB; +extern PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB; +extern PFNWGLQUERYPBUFFERARBPROC wglQueryPbufferARB; + +// WGL_ARB_render_texture +extern PFNWGLBINDTEXIMAGEARBPROC wglBindTexImageARB; +extern PFNWGLRELEASETEXIMAGEARBPROC wglReleaseTexImageARB; +extern PFNWGLSETPBUFFERATTRIBARBPROC wglSetPbufferAttribARB; + +#define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_CAPTION|WS_VISIBLE | WS_THICKFRAME) + +void Sys_QueEvent( sysEventType_t type, int value, int value2, int ptrLength, void *ptr, int inputDeviceNum ); + +void Sys_CreateConsole(); +void Sys_DestroyConsole(); + +char *Sys_ConsoleInput (); +char *Sys_GetCurrentUser(); + +void Win_SetErrorText( const char *text ); + +cpuid_t Sys_GetCPUId(); + +// Input subsystem + +void IN_Init (); +void IN_Shutdown (); +// add additional non keyboard / non mouse movement on top of the keyboard move cmd + +void IN_DeactivateMouseIfWindowed(); +void IN_DeactivateMouse(); +void IN_ActivateMouse(); + +void IN_Frame(); + +void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr ); + +uint64 Sys_Microseconds(); + +// window procedure +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +void Conbuf_AppendText( const char *msg ); + +typedef struct { + HWND hWnd; + HINSTANCE hInstance; + + bool activeApp; // changed with WM_ACTIVATE messages + bool mouseReleased; // when the game has the console down or is doing a long operation + bool movingWindow; // inhibit mouse grab when dragging the window + bool mouseGrabbed; // current state of grab and hide + + OSVERSIONINFOEX osversion; + + cpuid_t cpuid; + + // when we get a windows message, we store the time off so keyboard processing + // can know the exact time of an event (not really needed now that we use async direct input) + int sysMsgTime; + + bool windowClassRegistered; + + WNDPROC wndproc; + + HDC hDC; // handle to device context + HGLRC hGLRC; // handle to GL rendering context + PIXELFORMATDESCRIPTOR pfd; + int pixelformat; + + HINSTANCE hinstOpenGL; // HINSTANCE for the OpenGL library + + int desktopBitsPixel; + int desktopWidth, desktopHeight; + + int cdsFullscreen; // 0 = not fullscreen, otherwise monitor number + + idFileHandle log_fp; + + unsigned short oldHardwareGamma[3][256]; + // desktop gamma is saved here for restoration at exit + + static idCVar sys_arch; + static idCVar sys_cpustring; + static idCVar in_mouse; + static idCVar win_allowAltTab; + static idCVar win_notaskkeys; + static idCVar win_username; + static idCVar win_outputEditString; + static idCVar win_viewlog; + static idCVar win_timerUpdate; + static idCVar win_allowMultipleInstances; + + CRITICAL_SECTION criticalSections[MAX_CRITICAL_SECTIONS]; + + HINSTANCE hInstDI; // direct input + + LPDIRECTINPUT8 g_pdi; + LPDIRECTINPUTDEVICE8 g_pMouse; + LPDIRECTINPUTDEVICE8 g_pKeyboard; + idJoystickWin32 g_Joystick; + + HANDLE renderCommandsEvent; + HANDLE renderCompletedEvent; + HANDLE renderActiveEvent; + HANDLE renderThreadHandle; + unsigned long renderThreadId; + void (*glimpRenderThread)(); + void *smpData; + int wglErrors; + // SMP acceleration vars + +} Win32Vars_t; + +extern Win32Vars_t win32; + +#endif /* !__WIN_LOCAL_H__ */ diff --git a/neo/sys/win32/win_localuser.cpp b/neo/sys/win32/win_localuser.cpp new file mode 100644 index 00000000..a60bc389 --- /dev/null +++ b/neo/sys/win32/win_localuser.cpp @@ -0,0 +1,121 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "win_localuser.h" + +extern idCVar win_userPersistent; +extern idCVar win_userOnline; +extern idCVar win_isInParty; +extern idCVar win_partyCount; + +/* +======================== +idLocalUserWin::Init +======================== +*/ +void idLocalUserWin::Init( int inputDevice_, const char * gamertag_, int numLocalUsers ) { + if ( numLocalUsers == 1 ) { // Check for 1, since this is now incremented before we get in here + // This is the master user + gamertag = gamertag_; + } else { + // On steam, we need to generate a name based off the master user for split-screen users. + // We use the number of users on the system to generate the name rather than the device + // number so that it is always consistently "username (2)" for the second player. + gamertag.Format( "%s (%i)", gamertag_, numLocalUsers ); + } + + inputDevice = inputDevice_; +} + +/* +======================== +idLocalUserWin::IsProfileReady +======================== +*/ +bool idLocalUserWin::IsProfileReady() const { +#ifdef _DEBUG + return win_userPersistent.GetBool(); +#else + return true; +#endif +} + +/* +======================== +idLocalUserWin::IsOnline +======================== +*/ +bool idLocalUserWin::IsOnline() const { +#ifdef _DEBUG + return win_userOnline.GetBool(); +#else + return true; +#endif +} + +/* +======================== +idLocalUserWin::IsInParty +======================== +*/ +bool idLocalUserWin::IsInParty() const { +#ifdef _DEBUG + return win_isInParty.GetBool(); +#else + return false; +#endif +} + +/* +======================== +idLocalUserWin::GetPartyCount +======================== +*/ +int idLocalUserWin::GetPartyCount() const { + // TODO: Implement +#ifdef _DEBUG + return win_partyCount.GetInteger(); +#else + return 0; +#endif +} + +/* +======================== +idLocalUserWin::VerifyUserState +======================== +*/ +bool idLocalUserWin::VerifyUserState( winUserState_t & state ) { + + if ( state.inputDevice != inputDevice ) { + return false; + } + return true; +} diff --git a/neo/sys/win32/win_localuser.h b/neo/sys/win32/win_localuser.h new file mode 100644 index 00000000..f7181f06 --- /dev/null +++ b/neo/sys/win32/win_localuser.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __WIN_LOCALUSER_H__ +#define __WIN_LOCALUSER_H__ + +// This is to quickly get/set the data needed for disc-swapping +typedef struct { + int inputDevice; +} winUserState_t; + +/* +================================================ +idLocalUserWin +================================================ +*/ +class idLocalUserWin : public idLocalUser { +public: + static const int MAX_GAMERTAG = 64; // max number of bytes for a gamertag + static const int MAX_GAMERTAG_CHARS = 16; // max number of UTF-8 characters to show + + idLocalUserWin() : inputDevice( 0 ) {} + + //========================================================================================== + // idLocalUser interface + //========================================================================================== + virtual bool IsProfileReady() const; + virtual bool IsOnline() const; + virtual bool IsInParty() const; + virtual int GetPartyCount() const; + virtual uint32 GetOnlineCaps() const { return ( IsPersistent() && IsOnline() ) ? ( CAP_IS_ONLINE | CAP_CAN_PLAY_ONLINE ) : 0; } + virtual int GetInputDevice() const { return inputDevice; } + virtual const char * GetGamerTag() const { return gamertag.c_str(); } + virtual void PumpPlatform() {} + + //========================================================================================== + // idLocalUserWin interface + //========================================================================================== + void SetInputDevice( int inputDevice_ ) { inputDevice = inputDevice_; } + void SetGamerTag( const char * gamerTag_ ) { gamertag = gamerTag_; } + winUserState_t GetUserState() { winUserState_t a = { inputDevice }; return a; } + bool VerifyUserState( winUserState_t & state ); + + void Init( int inputDevice_, const char * gamertag_, int numLocalUsers ); + +private: + idStrStatic< MAX_GAMERTAG > gamertag; + int inputDevice; +}; + +#endif // __WIN_LOCALUSER_H__ diff --git a/neo/sys/win32/win_main.cpp b/neo/sys/win32/win_main.cpp new file mode 100644 index 00000000..b184ef57 --- /dev/null +++ b/neo/sys/win32/win_main.cpp @@ -0,0 +1,1661 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __MRC__ +#include +#include +#endif + +#include "../sys_local.h" +#include "win_local.h" +#include "../../renderer/tr_local.h" + +idCVar Win32Vars_t::sys_arch( "sys_arch", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar Win32Vars_t::sys_cpustring( "sys_cpustring", "detect", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar Win32Vars_t::in_mouse( "in_mouse", "1", CVAR_SYSTEM | CVAR_BOOL, "enable mouse input" ); +idCVar Win32Vars_t::win_allowAltTab( "win_allowAltTab", "0", CVAR_SYSTEM | CVAR_BOOL, "allow Alt-Tab when fullscreen" ); +idCVar Win32Vars_t::win_notaskkeys( "win_notaskkeys", "0", CVAR_SYSTEM | CVAR_INTEGER, "disable windows task keys" ); +idCVar Win32Vars_t::win_username( "win_username", "", CVAR_SYSTEM | CVAR_INIT, "windows user name" ); +idCVar Win32Vars_t::win_outputEditString( "win_outputEditString", "1", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar Win32Vars_t::win_viewlog( "win_viewlog", "0", CVAR_SYSTEM | CVAR_INTEGER, "" ); +idCVar Win32Vars_t::win_timerUpdate( "win_timerUpdate", "0", CVAR_SYSTEM | CVAR_BOOL, "allows the game to be updated while dragging the window" ); +idCVar Win32Vars_t::win_allowMultipleInstances( "win_allowMultipleInstances", "0", CVAR_SYSTEM | CVAR_BOOL, "allow multiple instances running concurrently" ); + +Win32Vars_t win32; + +static char sys_cmdline[MAX_STRING_CHARS]; + +static sysMemoryStats_t exeLaunchMemoryStats; + +static HANDLE hProcessMutex; + +/* +================ +Sys_GetExeLaunchMemoryStatus +================ +*/ +void Sys_GetExeLaunchMemoryStatus( sysMemoryStats_t &stats ) { + stats = exeLaunchMemoryStats; +} + +/* +================== +Sys_Sentry +================== +*/ +void Sys_Sentry() { +} + + +#pragma optimize( "", on ) + +#ifdef DEBUG + + +static unsigned int debug_total_alloc = 0; +static unsigned int debug_total_alloc_count = 0; +static unsigned int debug_current_alloc = 0; +static unsigned int debug_current_alloc_count = 0; +static unsigned int debug_frame_alloc = 0; +static unsigned int debug_frame_alloc_count = 0; + +idCVar sys_showMallocs( "sys_showMallocs", "0", CVAR_SYSTEM, "" ); + +// _HOOK_ALLOC, _HOOK_REALLOC, _HOOK_FREE + +typedef struct CrtMemBlockHeader +{ + struct _CrtMemBlockHeader *pBlockHeaderNext; // Pointer to the block allocated just before this one: + struct _CrtMemBlockHeader *pBlockHeaderPrev; // Pointer to the block allocated just after this one + char *szFileName; // File name + int nLine; // Line number + size_t nDataSize; // Size of user block + int nBlockUse; // Type of block + long lRequest; // Allocation number + byte gap[4]; // Buffer just before (lower than) the user's memory: +} CrtMemBlockHeader; + +#include + +/* +================== +Sys_AllocHook + + called for every malloc/new/free/delete +================== +*/ +int Sys_AllocHook( int nAllocType, void *pvData, size_t nSize, int nBlockUse, long lRequest, const unsigned char * szFileName, int nLine ) +{ + CrtMemBlockHeader *pHead; + byte *temp; + + if ( nBlockUse == _CRT_BLOCK ) + { + return( TRUE ); + } + + // get a pointer to memory block header + temp = ( byte * )pvData; + temp -= 32; + pHead = ( CrtMemBlockHeader * )temp; + + switch( nAllocType ) { + case _HOOK_ALLOC: + debug_total_alloc += nSize; + debug_current_alloc += nSize; + debug_frame_alloc += nSize; + debug_total_alloc_count++; + debug_current_alloc_count++; + debug_frame_alloc_count++; + break; + + case _HOOK_FREE: + assert( pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd ); + + debug_current_alloc -= pHead->nDataSize; + debug_current_alloc_count--; + debug_total_alloc_count++; + debug_frame_alloc_count++; + break; + + case _HOOK_REALLOC: + assert( pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd ); + + debug_current_alloc -= pHead->nDataSize; + debug_total_alloc += nSize; + debug_current_alloc += nSize; + debug_frame_alloc += nSize; + debug_total_alloc_count++; + debug_current_alloc_count--; + debug_frame_alloc_count++; + break; + } + return( TRUE ); +} + +/* +================== +Sys_DebugMemory_f +================== +*/ +void Sys_DebugMemory_f() { + common->Printf( "Total allocation %8dk in %d blocks\n", debug_total_alloc / 1024, debug_total_alloc_count ); + common->Printf( "Current allocation %8dk in %d blocks\n", debug_current_alloc / 1024, debug_current_alloc_count ); +} + +/* +================== +Sys_MemFrame +================== +*/ +void Sys_MemFrame() { + if( sys_showMallocs.GetInteger() ) { + common->Printf("Frame: %8dk in %5d blocks\n", debug_frame_alloc / 1024, debug_frame_alloc_count ); + } + + debug_frame_alloc = 0; + debug_frame_alloc_count = 0; +} + +#endif + +/* +================== +Sys_FlushCacheMemory + +On windows, the vertex buffers are write combined, so they +don't need to be flushed from the cache +================== +*/ +void Sys_FlushCacheMemory( void *base, int bytes ) { +} + +/* +============= +Sys_Error + +Show the early console as an error dialog +============= +*/ +void Sys_Error( const char *error, ... ) { + va_list argptr; + char text[4096]; + MSG msg; + + va_start( argptr, error ); + vsprintf( text, error, argptr ); + va_end( argptr); + + Conbuf_AppendText( text ); + Conbuf_AppendText( "\n" ); + + Win_SetErrorText( text ); + Sys_ShowConsole( 1, true ); + + timeEndPeriod( 1 ); + + Sys_ShutdownInput(); + + GLimp_Shutdown(); + + extern idCVar com_productionMode; + if ( com_productionMode.GetInteger() == 0 ) { + // wait for the user to quit + while ( 1 ) { + if ( !GetMessage( &msg, NULL, 0, 0 ) ) { + common->Quit(); + } + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + } + Sys_DestroyConsole(); + + exit (1); +} + +/* +======================== +Sys_Launch +======================== +*/ +void Sys_Launch( const char * path, idCmdArgs & args, void * data, unsigned int dataSize ) { + + TCHAR szPathOrig[_MAX_PATH]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + + strcpy( szPathOrig, va( "\"%s\" %s", Sys_EXEPath(), (const char *)data ) ); + + if ( !CreateProcess( NULL, szPathOrig, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ) { + idLib::Error( "Could not start process: '%s' ", szPathOrig ); + return; + } + cmdSystem->AppendCommandText( "quit\n" ); +} + +/* +======================== +Sys_GetCmdLine +======================== +*/ +const char * Sys_GetCmdLine() { + return sys_cmdline; +} + +/* +======================== +Sys_ReLaunch +======================== +*/ +void Sys_ReLaunch( void * data, const unsigned int dataSize ) { + TCHAR szPathOrig[MAX_PRINT_MSG]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + + strcpy( szPathOrig, va( "\"%s\" %s", Sys_EXEPath(), (const char *)data ) ); + + CloseHandle( hProcessMutex ); + + if ( !CreateProcess( NULL, szPathOrig, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ) { + idLib::Error( "Could not start process: '%s' ", szPathOrig ); + return; + } + cmdSystem->AppendCommandText( "quit\n" ); +} + +/* +============== +Sys_Quit +============== +*/ +void Sys_Quit() { + timeEndPeriod( 1 ); + Sys_ShutdownInput(); + Sys_DestroyConsole(); + ExitProcess( 0 ); +} + + +/* +============== +Sys_Printf +============== +*/ +#define MAXPRINTMSG 4096 +void Sys_Printf( const char *fmt, ... ) { + char msg[MAXPRINTMSG]; + + va_list argptr; + va_start(argptr, fmt); + idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, argptr ); + va_end(argptr); + msg[sizeof(msg)-1] = '\0'; + + OutputDebugString( msg ); + + if ( win32.win_outputEditString.GetBool() && idLib::IsMainThread() ) { + Conbuf_AppendText( msg ); + } +} + +/* +============== +Sys_DebugPrintf +============== +*/ +#define MAXPRINTMSG 4096 +void Sys_DebugPrintf( const char *fmt, ... ) { + char msg[MAXPRINTMSG]; + + va_list argptr; + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, argptr ); + msg[ sizeof(msg)-1 ] = '\0'; + va_end( argptr ); + + OutputDebugString( msg ); +} + +/* +============== +Sys_DebugVPrintf +============== +*/ +void Sys_DebugVPrintf( const char *fmt, va_list arg ) { + char msg[MAXPRINTMSG]; + + idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, arg ); + msg[ sizeof(msg)-1 ] = '\0'; + + OutputDebugString( msg ); +} + +/* +============== +Sys_Sleep +============== +*/ +void Sys_Sleep( int msec ) { + Sleep( msec ); +} + +/* +============== +Sys_ShowWindow +============== +*/ +void Sys_ShowWindow( bool show ) { + ::ShowWindow( win32.hWnd, show ? SW_SHOW : SW_HIDE ); +} + +/* +============== +Sys_IsWindowVisible +============== +*/ +bool Sys_IsWindowVisible() { + return ( ::IsWindowVisible( win32.hWnd ) != 0 ); +} + +/* +============== +Sys_Mkdir +============== +*/ +void Sys_Mkdir( const char *path ) { + _mkdir (path); +} + +/* +================= +Sys_FileTimeStamp +================= +*/ +ID_TIME_T Sys_FileTimeStamp( idFileHandle fp ) { + FILETIME writeTime; + GetFileTime( fp, NULL, NULL, &writeTime ); + + /* + FILETIME = number of 100-nanosecond ticks since midnight + 1 Jan 1601 UTC. time_t = number of 1-second ticks since + midnight 1 Jan 1970 UTC. To translate, we subtract a + FILETIME representation of midnight, 1 Jan 1970 from the + time in question and divide by the number of 100-ns ticks + in one second. + */ + + SYSTEMTIME base_st = { + 1970, // wYear + 1, // wMonth + 0, // wDayOfWeek + 1, // wDay + 0, // wHour + 0, // wMinute + 0, // wSecond + 0 // wMilliseconds + }; + + FILETIME base_ft; + SystemTimeToFileTime( &base_st, &base_ft ); + + LARGE_INTEGER itime; + itime.QuadPart = reinterpret_cast( writeTime ).QuadPart; + itime.QuadPart -= reinterpret_cast( base_ft ).QuadPart; + itime.QuadPart /= 10000000LL; + return itime.QuadPart; +} + +/* +======================== +Sys_Rmdir +======================== +*/ +bool Sys_Rmdir( const char *path ) { + return _rmdir( path ) == 0; +} + +/* +======================== +Sys_IsFileWritable +======================== +*/ +bool Sys_IsFileWritable( const char *path ) { + struct _stat st; + if ( _stat( path, &st ) == -1 ) { + return true; + } + return ( st.st_mode & S_IWRITE ) != 0; +} + +/* +======================== +Sys_IsFolder +======================== +*/ +sysFolder_t Sys_IsFolder( const char *path ) { + struct _stat buffer; + if ( _stat( path, &buffer ) < 0 ) { + return FOLDER_ERROR; + } + return ( buffer.st_mode & _S_IFDIR ) != 0 ? FOLDER_YES : FOLDER_NO; +} + +/* +============== +Sys_Cwd +============== +*/ +const char *Sys_Cwd() { + static char cwd[MAX_OSPATH]; + + _getcwd( cwd, sizeof( cwd ) - 1 ); + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============== +Sys_DefaultBasePath +============== +*/ +const char *Sys_DefaultBasePath() { + return Sys_Cwd(); +} + +// Vista shit +typedef HRESULT (WINAPI * SHGetKnownFolderPath_t)( const GUID & rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath ); +// NOTE: FOLIDERID_SavedGames is already exported from in shell32.dll in Windows 7. We can only detect +// the compiler version, but that doesn't doesn't tell us which version of the OS we're linking against. +// This GUID value should never change, so we name it something other than FOLDERID_SavedGames to get +// around this problem. +const GUID FOLDERID_SavedGames_IdTech5 = { 0x4c5c32ff, 0xbb9d, 0x43b0, { 0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4 } }; + +/* +============== +Sys_DefaultSavePath +============== +*/ +const char *Sys_DefaultSavePath() { + static char savePath[ MAX_PATH ]; + memset( savePath, 0, MAX_PATH ); + + HMODULE hShell = LoadLibrary( "shell32.dll" ); + if ( hShell ) { + SHGetKnownFolderPath_t SHGetKnownFolderPath = (SHGetKnownFolderPath_t)GetProcAddress( hShell, "SHGetKnownFolderPath" ); + if ( SHGetKnownFolderPath ) { + wchar_t * path; + if ( SUCCEEDED( SHGetKnownFolderPath( FOLDERID_SavedGames_IdTech5, CSIDL_FLAG_CREATE | CSIDL_FLAG_PER_USER_INIT, 0, &path ) ) ) { + if ( wcstombs( savePath, path, MAX_PATH ) > MAX_PATH ) { + savePath[0] = 0; + } + CoTaskMemFree( path ); + } + } + FreeLibrary( hShell ); + } + + if ( savePath[0] == 0 ) { + SHGetFolderPath( NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, savePath ); + strcat( savePath, "\\My Games" ); + } + + strcat( savePath, SAVE_PATH ); + + return savePath; +} + +/* +============== +Sys_EXEPath +============== +*/ +const char *Sys_EXEPath() { + static char exe[ MAX_OSPATH ]; + GetModuleFileName( NULL, exe, sizeof( exe ) - 1 ); + return exe; +} + +/* +============== +Sys_ListFiles +============== +*/ +int Sys_ListFiles( const char *directory, const char *extension, idStrList &list ) { + idStr search; + struct _finddata_t findinfo; + int findhandle; + int flag; + + if ( !extension) { + extension = ""; + } + + // passing a slash as extension will find directories + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + flag = 0; + } else { + flag = _A_SUBDIR; + } + + sprintf( search, "%s\\*%s", directory, extension ); + + // search + list.Clear(); + + findhandle = _findfirst( search, &findinfo ); + if ( findhandle == -1 ) { + return -1; + } + + do { + if ( flag ^ ( findinfo.attrib & _A_SUBDIR ) ) { + list.Append( findinfo.name ); + } + } while ( _findnext( findhandle, &findinfo ) != -1 ); + + _findclose( findhandle ); + + return list.Num(); +} + + +/* +================ +Sys_GetClipboardData +================ +*/ +char *Sys_GetClipboardData() { + char *data = NULL; + char *cliptext; + + if ( OpenClipboard( NULL ) != 0 ) { + HANDLE hClipboardData; + + if ( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != 0 ) { + if ( ( cliptext = (char *)GlobalLock( hClipboardData ) ) != 0 ) { + data = (char *)Mem_Alloc( GlobalSize( hClipboardData ) + 1, TAG_CRAP ); + strcpy( data, cliptext ); + GlobalUnlock( hClipboardData ); + + strtok( data, "\n\r\b" ); + } + } + CloseClipboard(); + } + return data; +} + +/* +================ +Sys_SetClipboardData +================ +*/ +void Sys_SetClipboardData( const char *string ) { + HGLOBAL HMem; + char *PMem; + + // allocate memory block + HMem = (char *)::GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, strlen( string ) + 1 ); + if ( HMem == NULL ) { + return; + } + // lock allocated memory and obtain a pointer + PMem = (char *)::GlobalLock( HMem ); + if ( PMem == NULL ) { + return; + } + // copy text into allocated memory block + lstrcpy( PMem, string ); + // unlock allocated memory + ::GlobalUnlock( HMem ); + // open Clipboard + if ( !OpenClipboard( 0 ) ) { + ::GlobalFree( HMem ); + return; + } + // remove current Clipboard contents + EmptyClipboard(); + // supply the memory handle to the Clipboard + SetClipboardData( CF_TEXT, HMem ); + HMem = 0; + // close Clipboard + CloseClipboard(); +} + +/* +======================== +ExecOutputFn +======================== +*/ +static void ExecOutputFn( const char * text ) { + idLib::Printf( text ); +} + + +/* +======================== +Sys_Exec + +if waitMsec is INFINITE, completely block until the process exits +If waitMsec is -1, don't wait for the process to exit +Other waitMsec values will allow the workFn to be called at those intervals. +======================== +*/ +bool Sys_Exec( const char * appPath, const char * workingPath, const char * args, + execProcessWorkFunction_t workFn, execOutputFunction_t outputFn, const int waitMS, + unsigned int & exitCode ) { + exitCode = 0; + SECURITY_ATTRIBUTES secAttr; + secAttr.nLength = sizeof( SECURITY_ATTRIBUTES ); + secAttr.bInheritHandle = TRUE; + secAttr.lpSecurityDescriptor = NULL; + + HANDLE hStdOutRead; + HANDLE hStdOutWrite; + CreatePipe( &hStdOutRead, &hStdOutWrite, &secAttr, 0 ); + SetHandleInformation( hStdOutRead, HANDLE_FLAG_INHERIT, 0 ); + + HANDLE hStdInRead; + HANDLE hStdInWrite; + CreatePipe( &hStdInRead, &hStdInWrite, &secAttr, 0 ); + SetHandleInformation( hStdInWrite, HANDLE_FLAG_INHERIT, 0 ); + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + si.hStdError = hStdOutWrite; + si.hStdOutput = hStdOutWrite; + si.hStdInput = hStdInRead; + si.wShowWindow = FALSE; + si.dwFlags |= STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + + PROCESS_INFORMATION pi; + memset ( &pi, 0, sizeof( pi ) ); + + if ( outputFn != NULL ) { + outputFn( va( "^2Executing Process: ^7%s\n^2working path: ^7%s\n^2args: ^7%s\n", appPath, workingPath, args ) ); + } else { + outputFn = ExecOutputFn; + } + + // we duplicate args here so we can concatenate the exe name and args into a single command line + const char * imageName = appPath; + char * cmdLine = NULL; + { + // if we have any args, we need to copy them to a new buffer because CreateProcess modifies + // the command line buffer. + if ( args != NULL ) { + if ( appPath != NULL ) { + int len = idStr::Length( args ) + idStr::Length( appPath ) + 1 /* for space */ + 1 /* for NULL terminator */ + 2 /* app quotes */; + cmdLine = (char*)Mem_Alloc( len, TAG_TEMP ); + // note that we're putting quotes around the appPath here because when AAS2.exe gets an app path with spaces + // in the path "w:/zion/build/win32/Debug with Inlines/AAS2.exe" it gets more than one arg for the app name, + // which it most certainly should not, so I am assuming this is a side effect of using CreateProcess. + idStr::snPrintf( cmdLine, len, "\"%s\" %s", appPath, args ); + } else { + int len = idStr::Length( args ) + 1; + cmdLine = (char*)Mem_Alloc( len, TAG_TEMP ); + idStr::Copynz( cmdLine, args, len ); + } + // the image name should always be NULL if we have command line arguments because it is already + // prefixed to the command line. + imageName = NULL; + } + } + + BOOL result = CreateProcess( imageName, (LPSTR)cmdLine, NULL, NULL, TRUE, 0, NULL, workingPath, &si, &pi ); + + if ( result == FALSE ) { + TCHAR szBuf[1024]; + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + + wsprintf( szBuf, "%d: %s", dw, lpMsgBuf ); + if ( outputFn != NULL ) { + outputFn( szBuf ); + } + LocalFree( lpMsgBuf ); + if ( cmdLine != NULL ) { + Mem_Free( cmdLine ); + } + return false; + } else if ( waitMS >= 0 ) { // if waitMS == -1, don't wait for process to exit + DWORD ec = 0; + DWORD wait = 0; + char buffer[ 4096 ]; + for ( ; ; ) { + wait = WaitForSingleObject( pi.hProcess, waitMS ); + GetExitCodeProcess( pi.hProcess, &ec ); + + DWORD bytesRead = 0; + DWORD bytesAvail = 0; + DWORD bytesLeft = 0; + BOOL ok = PeekNamedPipe( hStdOutRead, NULL, 0, NULL, &bytesAvail, &bytesLeft ); + if ( ok && bytesAvail != 0 ) { + ok = ReadFile( hStdOutRead, buffer, sizeof( buffer ) - 3, &bytesRead, NULL ); + if ( ok && bytesRead > 0 ) { + buffer[ bytesRead ] = '\0'; + if ( outputFn != NULL ) { + int length = 0; + for ( int i = 0; buffer[i] != '\0'; i++ ) { + if ( buffer[i] != '\r' ) { + buffer[length++] = buffer[i]; + } + } + buffer[length++] = '\0'; + outputFn( buffer ); + } + } + } + + if ( ec != STILL_ACTIVE ) { + exitCode = ec; + break; + } + + if ( workFn != NULL ) { + if ( !workFn() ) { + TerminateProcess( pi.hProcess, 0 ); + break; + } + } + } + } + + // this assumes that windows duplicates the command line string into the created process's + // environment space. + if ( cmdLine != NULL ) { + Mem_Free( cmdLine ); + } + + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + CloseHandle( hStdOutRead ); + CloseHandle( hStdOutWrite ); + CloseHandle( hStdInRead ); + CloseHandle( hStdInWrite ); + return true; +} + +/* +======================================================================== + +DLL Loading + +======================================================================== +*/ + +/* +===================== +Sys_DLL_Load +===================== +*/ +int Sys_DLL_Load( const char *dllName ) { + HINSTANCE libHandle = LoadLibrary( dllName ); + return (int)libHandle; +} + +/* +===================== +Sys_DLL_GetProcAddress +===================== +*/ +void *Sys_DLL_GetProcAddress( int dllHandle, const char *procName ) { + return GetProcAddress( (HINSTANCE)dllHandle, procName ); +} + +/* +===================== +Sys_DLL_Unload +===================== +*/ +void Sys_DLL_Unload( int dllHandle ) { + if ( !dllHandle ) { + return; + } + if ( FreeLibrary( (HINSTANCE)dllHandle ) == 0 ) { + int lastError = GetLastError(); + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + lastError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + Sys_Error( "Sys_DLL_Unload: FreeLibrary failed - %s (%d)", lpMsgBuf, lastError ); + } +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +int eventHead = 0; +int eventTail = 0; + +/* +================ +Sys_QueEvent + +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( sysEventType_t type, int value, int value2, int ptrLength, void *ptr, int inputDeviceNum ) { + sysEvent_t * ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + + if ( eventHead - eventTail >= MAX_QUED_EVENTS ) { + common->Printf("Sys_QueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) { + Mem_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; + ev->inputDevice = inputDeviceNum; +} + +/* +============= +Sys_PumpEvents + +This allows windows to be moved during renderbump +============= +*/ +void Sys_PumpEvents() { + MSG msg; + + // pump the message loop + while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) { + if ( !GetMessage( &msg, NULL, 0, 0 ) ) { + common->Quit(); + } + + // save the msg time, because wndprocs don't have access to the timestamp + if ( win32.sysMsgTime && win32.sysMsgTime > (int)msg.time ) { + // don't ever let the event times run backwards +// common->Printf( "Sys_PumpEvents: win32.sysMsgTime (%i) > msg.time (%i)\n", win32.sysMsgTime, msg.time ); + } else { + win32.sysMsgTime = msg.time; + } + + TranslateMessage (&msg); + DispatchMessage (&msg); + } +} + +/* +================ +Sys_GenerateEvents +================ +*/ +void Sys_GenerateEvents() { + static int entered = false; + char *s; + + if ( entered ) { + return; + } + entered = true; + + // pump the message loop + Sys_PumpEvents(); + + // grab or release the mouse cursor if necessary + IN_Frame(); + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) { + char *b; + int len; + + len = strlen( s ) + 1; + b = (char *)Mem_Alloc( len, TAG_EVENTS ); + strcpy( b, s ); + Sys_QueEvent( SE_CONSOLE, 0, 0, len, b, 0 ); + } + + entered = false; +} + +/* +================ +Sys_ClearEvents +================ +*/ +void Sys_ClearEvents() { + eventHead = eventTail = 0; +} + +/* +================ +Sys_GetEvent +================ +*/ +sysEvent_t Sys_GetEvent() { + sysEvent_t ev; + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // return the empty event + memset( &ev, 0, sizeof( ev ) ); + + return ev; +} + +//================================================================ + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( const idCmdArgs &args ) { + Sys_ShutdownInput(); + Sys_InitInput(); +} + +/* +================ +Sys_AlreadyRunning + +returns true if there is a copy of D3 running already +================ +*/ +bool Sys_AlreadyRunning() { +#ifndef DEBUG + if ( !win32.win_allowMultipleInstances.GetBool() ) { + hProcessMutex = ::CreateMutex( NULL, FALSE, "DOOM3" ); + if ( ::GetLastError() == ERROR_ALREADY_EXISTS || ::GetLastError() == ERROR_ACCESS_DENIED ) { + return true; + } + } +#endif + return false; +} + +/* +================ +Sys_Init + +The cvar system must already be setup +================ +*/ +#define OSR2_BUILD_NUMBER 1111 +#define WIN98_BUILD_NUMBER 1998 + +void Sys_Init() { + + CoInitialize( NULL ); + + // get WM_TIMER messages pumped every millisecond +// SetTimer( NULL, 0, 100, NULL ); + + cmdSystem->AddCommand( "in_restart", Sys_In_Restart_f, CMD_FL_SYSTEM, "restarts the input system" ); + + // + // Windows user name + // + win32.win_username.SetString( Sys_GetCurrentUser() ); + + // + // Windows version + // + win32.osversion.dwOSVersionInfoSize = sizeof( win32.osversion ); + + if ( !GetVersionEx( (LPOSVERSIONINFO)&win32.osversion ) ) + Sys_Error( "Couldn't get OS info" ); + + if ( win32.osversion.dwMajorVersion < 4 ) { + Sys_Error( GAME_NAME " requires Windows version 4 (NT) or greater" ); + } + if ( win32.osversion.dwPlatformId == VER_PLATFORM_WIN32s ) { + Sys_Error( GAME_NAME " doesn't run on Win32s" ); + } + + if( win32.osversion.dwPlatformId == VER_PLATFORM_WIN32_NT ) { + if( win32.osversion.dwMajorVersion <= 4 ) { + win32.sys_arch.SetString( "WinNT (NT)" ); + } else if( win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 0 ) { + win32.sys_arch.SetString( "Win2K (NT)" ); + } else if( win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 1 ) { + win32.sys_arch.SetString( "WinXP (NT)" ); + } else if ( win32.osversion.dwMajorVersion == 6 ) { + win32.sys_arch.SetString( "Vista" ); + } else { + win32.sys_arch.SetString( "Unknown NT variant" ); + } + } else if( win32.osversion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) { + if( win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 0 ) { + // Win95 + if( win32.osversion.szCSDVersion[1] == 'C' ) { + win32.sys_arch.SetString( "Win95 OSR2 (95)" ); + } else { + win32.sys_arch.SetString( "Win95 (95)" ); + } + } else if( win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 10 ) { + // Win98 + if( win32.osversion.szCSDVersion[1] == 'A' ) { + win32.sys_arch.SetString( "Win98SE (95)" ); + } else { + win32.sys_arch.SetString( "Win98 (95)" ); + } + } else if( win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 90 ) { + // WinMe + win32.sys_arch.SetString( "WinMe (95)" ); + } else { + win32.sys_arch.SetString( "Unknown 95 variant" ); + } + } else { + win32.sys_arch.SetString( "unknown Windows variant" ); + } + + // + // CPU type + // + if ( !idStr::Icmp( win32.sys_cpustring.GetString(), "detect" ) ) { + idStr string; + + common->Printf( "%1.0f MHz ", Sys_ClockTicksPerSecond() / 1000000.0f ); + + win32.cpuid = Sys_GetCPUId(); + + string.Clear(); + + if ( win32.cpuid & CPUID_AMD ) { + string += "AMD CPU"; + } else if ( win32.cpuid & CPUID_INTEL ) { + string += "Intel CPU"; + } else if ( win32.cpuid & CPUID_UNSUPPORTED ) { + string += "unsupported CPU"; + } else { + string += "generic CPU"; + } + + string += " with "; + if ( win32.cpuid & CPUID_MMX ) { + string += "MMX & "; + } + if ( win32.cpuid & CPUID_3DNOW ) { + string += "3DNow! & "; + } + if ( win32.cpuid & CPUID_SSE ) { + string += "SSE & "; + } + if ( win32.cpuid & CPUID_SSE2 ) { + string += "SSE2 & "; + } + if ( win32.cpuid & CPUID_SSE3 ) { + string += "SSE3 & "; + } + if ( win32.cpuid & CPUID_HTT ) { + string += "HTT & "; + } + string.StripTrailing( " & " ); + string.StripTrailing( " with " ); + win32.sys_cpustring.SetString( string ); + } else { + common->Printf( "forcing CPU type to " ); + idLexer src( win32.sys_cpustring.GetString(), idStr::Length( win32.sys_cpustring.GetString() ), "sys_cpustring" ); + idToken token; + + int id = CPUID_NONE; + while( src.ReadToken( &token ) ) { + if ( token.Icmp( "generic" ) == 0 ) { + id |= CPUID_GENERIC; + } else if ( token.Icmp( "intel" ) == 0 ) { + id |= CPUID_INTEL; + } else if ( token.Icmp( "amd" ) == 0 ) { + id |= CPUID_AMD; + } else if ( token.Icmp( "mmx" ) == 0 ) { + id |= CPUID_MMX; + } else if ( token.Icmp( "3dnow" ) == 0 ) { + id |= CPUID_3DNOW; + } else if ( token.Icmp( "sse" ) == 0 ) { + id |= CPUID_SSE; + } else if ( token.Icmp( "sse2" ) == 0 ) { + id |= CPUID_SSE2; + } else if ( token.Icmp( "sse3" ) == 0 ) { + id |= CPUID_SSE3; + } else if ( token.Icmp( "htt" ) == 0 ) { + id |= CPUID_HTT; + } + } + if ( id == CPUID_NONE ) { + common->Printf( "WARNING: unknown sys_cpustring '%s'\n", win32.sys_cpustring.GetString() ); + id = CPUID_GENERIC; + } + win32.cpuid = (cpuid_t) id; + } + + common->Printf( "%s\n", win32.sys_cpustring.GetString() ); + common->Printf( "%d MB System Memory\n", Sys_GetSystemRam() ); + common->Printf( "%d MB Video Memory\n", Sys_GetVideoRam() ); + if ( ( win32.cpuid & CPUID_SSE2 ) == 0 ) { + common->Error( "SSE2 not supported!" ); + } + + win32.g_Joystick.Init(); +} + +/* +================ +Sys_Shutdown +================ +*/ +void Sys_Shutdown() { + CoUninitialize(); +} + +/* +================ +Sys_GetProcessorId +================ +*/ +cpuid_t Sys_GetProcessorId() { + return win32.cpuid; +} + +/* +================ +Sys_GetProcessorString +================ +*/ +const char *Sys_GetProcessorString() { + return win32.sys_cpustring.GetString(); +} + +//======================================================================= + +//#define SET_THREAD_AFFINITY + + +/* +==================== +Win_Frame +==================== +*/ +void Win_Frame() { + // if "viewlog" has been modified, show or hide the log console + if ( win32.win_viewlog.IsModified() ) { + win32.win_viewlog.ClearModified(); + } +} + +extern "C" { void _chkstk( int size ); }; +void clrstk(); + +/* +==================== +TestChkStk +==================== +*/ +void TestChkStk() { + int buffer[0x1000]; + + buffer[0] = 1; +} + +/* +==================== +HackChkStk +==================== +*/ +void HackChkStk() { + DWORD old; + VirtualProtect( _chkstk, 6, PAGE_EXECUTE_READWRITE, &old ); + *(byte *)_chkstk = 0xe9; + *(int *)((int)_chkstk+1) = (int)clrstk - (int)_chkstk - 5; + + TestChkStk(); +} + +/* +==================== +GetExceptionCodeInfo +==================== +*/ +const char *GetExceptionCodeInfo( UINT code ) { + switch( code ) { + case EXCEPTION_ACCESS_VIOLATION: return "The thread tried to read from or write to a virtual address for which it does not have the appropriate access."; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking."; + case EXCEPTION_BREAKPOINT: return "A breakpoint was encountered."; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on."; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value."; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "The thread tried to divide a floating-point value by a floating-point divisor of zero."; + case EXCEPTION_FLT_INEXACT_RESULT: return "The result of a floating-point operation cannot be represented exactly as a decimal fraction."; + case EXCEPTION_FLT_INVALID_OPERATION: return "This exception represents any floating-point exception not included in this list."; + case EXCEPTION_FLT_OVERFLOW: return "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type."; + case EXCEPTION_FLT_STACK_CHECK: return "The stack overflowed or underflowed as the result of a floating-point operation."; + case EXCEPTION_FLT_UNDERFLOW: return "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type."; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "The thread tried to execute an invalid instruction."; + case EXCEPTION_IN_PAGE_ERROR: return "The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network."; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "The thread tried to divide an integer value by an integer divisor of zero."; + case EXCEPTION_INT_OVERFLOW: return "The result of an integer operation caused a carry out of the most significant bit of the result."; + case EXCEPTION_INVALID_DISPOSITION: return "An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception."; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "The thread tried to continue execution after a noncontinuable exception occurred."; + case EXCEPTION_PRIV_INSTRUCTION: return "The thread tried to execute an instruction whose operation is not allowed in the current machine mode."; + case EXCEPTION_SINGLE_STEP: return "A trace trap or other single-instruction mechanism signaled that one instruction has been executed."; + case EXCEPTION_STACK_OVERFLOW: return "The thread used up its stack."; + default: return "Unknown exception"; + } +} + +/* +==================== +EmailCrashReport + + emailer originally from Raven/Quake 4 +==================== +*/ +void EmailCrashReport( LPSTR messageText ) { + static int lastEmailTime = 0; + + if ( Sys_Milliseconds() < lastEmailTime + 10000 ) { + return; + } + + lastEmailTime = Sys_Milliseconds(); + + HINSTANCE mapi = LoadLibrary( "MAPI32.DLL" ); + if( mapi ) { + LPMAPISENDMAIL MAPISendMail = ( LPMAPISENDMAIL )GetProcAddress( mapi, "MAPISendMail" ); + if( MAPISendMail ) { + MapiRecipDesc toProgrammers = + { + 0, // ulReserved + MAPI_TO, // ulRecipClass + "DOOM 3 Crash", // lpszName + "SMTP:programmers@idsoftware.com", // lpszAddress + 0, // ulEIDSize + 0 // lpEntry + }; + + MapiMessage message = {}; + message.lpszSubject = "DOOM 3 Fatal Error"; + message.lpszNoteText = messageText; + message.nRecipCount = 1; + message.lpRecips = &toProgrammers; + + MAPISendMail( + 0, // LHANDLE lhSession + 0, // ULONG ulUIParam + &message, // lpMapiMessage lpMessage + MAPI_DIALOG, // FLAGS flFlags + 0 // ULONG ulReserved + ); + } + FreeLibrary( mapi ); + } +} + +int Sys_FPU_PrintStateFlags( char *ptr, int ctrl, int stat, int tags, int inof, int inse, int opof, int opse ); + +/* +==================== +_except_handler +==================== +*/ +EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, + struct _CONTEXT *ContextRecord, void * DispatcherContext ) { + + static char msg[ 8192 ]; + char FPUFlags[2048]; + + Sys_FPU_PrintStateFlags( FPUFlags, ContextRecord->FloatSave.ControlWord, + ContextRecord->FloatSave.StatusWord, + ContextRecord->FloatSave.TagWord, + ContextRecord->FloatSave.ErrorOffset, + ContextRecord->FloatSave.ErrorSelector, + ContextRecord->FloatSave.DataOffset, + ContextRecord->FloatSave.DataSelector ); + + + sprintf( msg, + "Please describe what you were doing when DOOM 3 crashed!\n" + "If this text did not pop into your email client please copy and email it to programmers@idsoftware.com\n" + "\n" + "-= FATAL EXCEPTION =-\n" + "\n" + "%s\n" + "\n" + "0x%x at address 0x%08p\n" + "\n" + "%s\n" + "\n" + "EAX = 0x%08x EBX = 0x%08x\n" + "ECX = 0x%08x EDX = 0x%08x\n" + "ESI = 0x%08x EDI = 0x%08x\n" + "EIP = 0x%08x ESP = 0x%08x\n" + "EBP = 0x%08x EFL = 0x%08x\n" + "\n" + "CS = 0x%04x\n" + "SS = 0x%04x\n" + "DS = 0x%04x\n" + "ES = 0x%04x\n" + "FS = 0x%04x\n" + "GS = 0x%04x\n" + "\n" + "%s\n", + com_version.GetString(), + ExceptionRecord->ExceptionCode, + ExceptionRecord->ExceptionAddress, + GetExceptionCodeInfo( ExceptionRecord->ExceptionCode ), + ContextRecord->Eax, ContextRecord->Ebx, + ContextRecord->Ecx, ContextRecord->Edx, + ContextRecord->Esi, ContextRecord->Edi, + ContextRecord->Eip, ContextRecord->Esp, + ContextRecord->Ebp, ContextRecord->EFlags, + ContextRecord->SegCs, + ContextRecord->SegSs, + ContextRecord->SegDs, + ContextRecord->SegEs, + ContextRecord->SegFs, + ContextRecord->SegGs, + FPUFlags + ); + + EmailCrashReport( msg ); + common->FatalError( msg ); + + // Tell the OS to restart the faulting instruction + return ExceptionContinueExecution; +} + +#define TEST_FPU_EXCEPTIONS /* FPU_EXCEPTION_INVALID_OPERATION | */ \ + /* FPU_EXCEPTION_DENORMALIZED_OPERAND | */ \ + /* FPU_EXCEPTION_DIVIDE_BY_ZERO | */ \ + /* FPU_EXCEPTION_NUMERIC_OVERFLOW | */ \ + /* FPU_EXCEPTION_NUMERIC_UNDERFLOW | */ \ + /* FPU_EXCEPTION_INEXACT_RESULT | */ \ + 0 + +/* +================== +WinMain +================== +*/ +int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { + + const HCURSOR hcurSave = ::SetCursor( LoadCursor( 0, IDC_WAIT ) ); + + Sys_SetPhysicalWorkMemory( 192 << 20, 1024 << 20 ); + + Sys_GetCurrentMemoryStatus( exeLaunchMemoryStats ); + +#if 0 + DWORD handler = (DWORD)_except_handler; + __asm + { // Build EXCEPTION_REGISTRATION record: + push handler // Address of handler function + push FS:[0] // Address of previous handler + mov FS:[0],ESP // Install new EXECEPTION_REGISTRATION + } +#endif + + win32.hInstance = hInstance; + idStr::Copynz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) ); + + // done before Com/Sys_Init since we need this for error output + Sys_CreateConsole(); + + // no abort/retry/fail errors + SetErrorMode( SEM_FAILCRITICALERRORS ); + + for ( int i = 0; i < MAX_CRITICAL_SECTIONS; i++ ) { + InitializeCriticalSection( &win32.criticalSections[i] ); + } + + // make sure the timer is high precision, otherwise + // NT gets 18ms resolution + timeBeginPeriod( 1 ); + + // get the initial time base + Sys_Milliseconds(); + +#ifdef DEBUG + // disable the painfully slow MS heap check every 1024 allocs + _CrtSetDbgFlag( 0 ); +#endif + +// Sys_FPU_EnableExceptions( TEST_FPU_EXCEPTIONS ); + Sys_FPU_SetPrecision( FPU_PRECISION_DOUBLE_EXTENDED ); + + common->Init( 0, NULL, lpCmdLine ); + +#if TEST_FPU_EXCEPTIONS != 0 + common->Printf( Sys_FPU_GetState() ); +#endif + + if ( win32.win_notaskkeys.GetInteger() ) { + DisableTaskKeys( TRUE, FALSE, /*( win32.win_notaskkeys.GetInteger() == 2 )*/ FALSE ); + } + + // hide or show the early console as necessary + if ( win32.win_viewlog.GetInteger() ) { + Sys_ShowConsole( 1, true ); + } else { + Sys_ShowConsole( 0, false ); + } + +#ifdef SET_THREAD_AFFINITY + // give the main thread an affinity for the first cpu + SetThreadAffinityMask( GetCurrentThread(), 1 ); +#endif + + ::SetCursor( hcurSave ); + + ::SetFocus( win32.hWnd ); + + // main game loop + while( 1 ) { + + Win_Frame(); + +#ifdef DEBUG + Sys_MemFrame(); +#endif + + // set exceptions, even if some crappy syscall changes them! + Sys_FPU_EnableExceptions( TEST_FPU_EXCEPTIONS ); + + // run the game + common->Frame(); + } + + // never gets here + return 0; +} + +/* +==================== +clrstk + +I tried to get the run time to call this at every function entry, but +==================== +*/ +static int parmBytes; +__declspec( naked ) void clrstk() { + // eax = bytes to add to stack + __asm { + mov [parmBytes],eax + neg eax ; compute new stack pointer in eax + add eax,esp + add eax,4 + xchg eax,esp + mov eax,dword ptr [eax] ; copy the return address + push eax + + ; clear to zero + push edi + push ecx + mov edi,esp + add edi,12 + mov ecx,[parmBytes] + shr ecx,2 + xor eax,eax + cld + rep stosd + pop ecx + pop edi + + ret + } +} + +/* +================== +idSysLocal::OpenURL +================== +*/ +void idSysLocal::OpenURL( const char *url, bool doexit ) { + static bool doexit_spamguard = false; + HWND wnd; + + if (doexit_spamguard) { + common->DPrintf( "OpenURL: already in an exit sequence, ignoring %s\n", url ); + return; + } + + common->Printf("Open URL: %s\n", url); + + if ( !ShellExecute( NULL, "open", url, NULL, NULL, SW_RESTORE ) ) { + common->Error( "Could not open url: '%s' ", url ); + return; + } + + wnd = GetForegroundWindow(); + if ( wnd ) { + ShowWindow( wnd, SW_MAXIMIZE ); + } + + if ( doexit ) { + doexit_spamguard = true; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + } +} + +/* +================== +idSysLocal::StartProcess +================== +*/ +void idSysLocal::StartProcess( const char *exePath, bool doexit ) { + TCHAR szPathOrig[_MAX_PATH]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + + strncpy( szPathOrig, exePath, _MAX_PATH ); + + if( !CreateProcess( NULL, szPathOrig, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ) { + common->Error( "Could not start process: '%s' ", szPathOrig ); + return; + } + + if ( doexit ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + } +} + +/* +================== +Sys_SetFatalError +================== +*/ +void Sys_SetFatalError( const char *error ) { +} + +/* +================ +Sys_SetLanguageFromSystem +================ +*/ +extern idCVar sys_lang; +void Sys_SetLanguageFromSystem() { + sys_lang.SetString( Sys_DefaultLanguage() ); +} diff --git a/neo/sys/win32/win_net.cpp b/neo/sys/win32/win_net.cpp new file mode 100644 index 00000000..e15833ed --- /dev/null +++ b/neo/sys/win32/win_net.cpp @@ -0,0 +1,987 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +/* +================================================================================================ +Contains the NetworkSystem implementation specific to Win32. +================================================================================================ +*/ + +#include +#include + +static WSADATA winsockdata; +static bool winsockInitialized = false; +static bool usingSocks = false; + +//lint -e569 ioctl macros trigger this + +// force these libs to be included, so users of idLib don't need to add them to every project +#pragma comment(lib, "iphlpapi.lib" ) +#pragma comment(lib, "wsock32.lib" ) + + +/* +================================================================================================ + + Network CVars + +================================================================================================ +*/ + +idCVar net_socksServer( "net_socksServer", "", CVAR_ARCHIVE, "" ); +idCVar net_socksPort( "net_socksPort", "1080", CVAR_ARCHIVE | CVAR_INTEGER, "" ); +idCVar net_socksUsername( "net_socksUsername", "", CVAR_ARCHIVE, "" ); +idCVar net_socksPassword( "net_socksPassword", "", CVAR_ARCHIVE, "" ); + +idCVar net_ip( "net_ip", "localhost", 0, "local IP address" ); + +static struct sockaddr_in socksRelayAddr; + +static SOCKET ip_socket; +static SOCKET socks_socket; +static char socksBuf[4096]; + +typedef struct { + unsigned long ip; + unsigned long mask; + char addr[16]; +} net_interface; + +#define MAX_INTERFACES 32 +int num_interfaces = 0; +net_interface netint[MAX_INTERFACES]; + +/* +================================================================================================ + + Free Functions + +================================================================================================ +*/ + +/* +======================== +NET_ErrorString +======================== +*/ +char *NET_ErrorString() { + int code; + + code = WSAGetLastError(); + switch( code ) { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEDISCON: return "WSAEDISCON"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSAECONNABORTED"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSASYSNOTREADY: return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: return "WSATRY_AGAIN"; + case WSANO_RECOVERY: return "WSANO_RECOVERY"; + case WSANO_DATA: return "WSANO_DATA"; + default: return "NO ERROR"; + } +} + +/* +======================== +Net_NetadrToSockadr +======================== +*/ +void Net_NetadrToSockadr( const netadr_t *a, sockaddr_in *s ) { + memset( s, 0, sizeof(*s) ); + + if ( a->type == NA_BROADCAST ) { + s->sin_family = AF_INET; + s->sin_addr.s_addr = INADDR_BROADCAST; + } else if ( a->type == NA_IP || a->type == NA_LOOPBACK ) { + s->sin_family = AF_INET; + s->sin_addr.s_addr = *(int *)a->ip; + } + + s->sin_port = htons( (short)a->port ); +} + +/* +======================== +Net_SockadrToNetadr +======================== +*/ +void Net_SockadrToNetadr( sockaddr_in *s, netadr_t *a ) { + unsigned int ip; + if ( s->sin_family == AF_INET ) { + ip = s->sin_addr.s_addr; + *(unsigned int *)a->ip = ip; + a->port = htons( s->sin_port ); + // we store in network order, that loopback test is host order.. + ip = ntohl( ip ); + if ( ip == INADDR_LOOPBACK ) { + a->type = NA_LOOPBACK; + } else { + a->type = NA_IP; + } + } +} + +/* +======================== +Net_ExtractPort +======================== +*/ +static bool Net_ExtractPort( const char *src, char *buf, int bufsize, int *port ) { + char *p; + strncpy( buf, src, bufsize ); + p = buf; p += Min( bufsize - 1, idStr::Length( src ) ); *p = '\0'; + p = strchr( buf, ':' ); + if ( !p ) { + return false; + } + *p = '\0'; + *port = strtol( p+1, NULL, 10 ); + if ( errno == ERANGE ) { + return false; + } + return true; +} + +/* +======================== +Net_StringToSockaddr +======================== +*/ +static bool Net_StringToSockaddr( const char *s, sockaddr_in *sadr, bool doDNSResolve ) { + struct hostent *h; + char buf[256]; + int port; + + memset( sadr, 0, sizeof( *sadr ) ); + + sadr->sin_family = AF_INET; + sadr->sin_port = 0; + + if( s[0] >= '0' && s[0] <= '9' ) { + unsigned long ret = inet_addr(s); + if ( ret != INADDR_NONE ) { + *(int *)&sadr->sin_addr = ret; + } else { + // check for port + if ( !Net_ExtractPort( s, buf, sizeof( buf ), &port ) ) { + return false; + } + ret = inet_addr( buf ); + if ( ret == INADDR_NONE ) { + return false; + } + *(int *)&sadr->sin_addr = ret; + sadr->sin_port = htons( port ); + } + } else if ( doDNSResolve ) { + // try to remove the port first, otherwise the DNS gets confused into multiple timeouts + // failed or not failed, buf is expected to contain the appropriate host to resolve + if ( Net_ExtractPort( s, buf, sizeof( buf ), &port ) ) { + sadr->sin_port = htons( port ); + } + h = gethostbyname( buf ); + if ( h == 0 ) { + return false; + } + *(int *)&sadr->sin_addr = *(int *)h->h_addr_list[0]; + } + + return true; +} + +/* +======================== +NET_IPSocket +======================== +*/ +int NET_IPSocket( const char *net_interface, int port, netadr_t *bound_to ) { + SOCKET newsocket; + sockaddr_in address; + unsigned long _true = 1; + int i = 1; + int err; + + if ( port != PORT_ANY ) { + if( net_interface ) { + idLib::Printf( "Opening IP socket: %s:%i\n", net_interface, port ); + } else { + idLib::Printf( "Opening IP socket: localhost:%i\n", port ); + } + } + + if( ( newsocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { + err = WSAGetLastError(); + if( err != WSAEAFNOSUPPORT ) { + idLib::Printf( "WARNING: UDP_OpenSocket: socket: %s\n", NET_ErrorString() ); + } + return 0; + } + + // make it non-blocking + if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) { + idLib::Printf( "WARNING: UDP_OpenSocket: ioctl FIONBIO: %s\n", NET_ErrorString() ); + closesocket( newsocket ); + return 0; + } + + // make it broadcast capable + if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i) ) == SOCKET_ERROR ) { + idLib::Printf( "WARNING: UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() ); + closesocket( newsocket ); + return 0; + } + + if( !net_interface || !net_interface[0] || !idStr::Icmp( net_interface, "localhost" ) ) { + address.sin_addr.s_addr = INADDR_ANY; + } + else { + Net_StringToSockaddr( net_interface, &address, true ); + } + + if( port == PORT_ANY ) { + address.sin_port = 0; + } + else { + address.sin_port = htons( (short)port ); + } + + address.sin_family = AF_INET; + + if( bind( newsocket, (const sockaddr *)&address, sizeof(address) ) == SOCKET_ERROR ) { + idLib::Printf( "WARNING: UDP_OpenSocket: bind: %s\n", NET_ErrorString() ); + closesocket( newsocket ); + return 0; + } + + // if the port was PORT_ANY, we need to query again to know the real port we got bound to + // ( this used to be in idUDP::InitForPort ) + if ( bound_to ) { + int len = sizeof( address ); + getsockname( newsocket, (sockaddr *)&address, &len ); + Net_SockadrToNetadr( &address, bound_to ); + } + + return newsocket; +} + +/* +======================== +NET_OpenSocks +======================== +*/ +void NET_OpenSocks( int port ) { + sockaddr_in address; + struct hostent *h; + int len; + bool rfc1929; + unsigned char buf[64]; + + usingSocks = false; + + idLib::Printf( "Opening connection to SOCKS server.\n" ); + + if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) { + idLib::Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() ); + return; + } + + h = gethostbyname( net_socksServer.GetString() ); + if ( h == NULL ) { + idLib::Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() ); + return; + } + if ( h->h_addrtype != AF_INET ) { + idLib::Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" ); + return; + } + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(int *)h->h_addr_list[0]; + address.sin_port = htons( (short)net_socksPort.GetInteger() ); + + if ( connect( socks_socket, (sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() ); + return; + } + + // send socks authentication handshake + if ( *net_socksUsername.GetString() || *net_socksPassword.GetString() ) { + rfc1929 = true; + } + else { + rfc1929 = false; + } + + buf[0] = 5; // SOCKS version + // method count + if ( rfc1929 ) { + buf[1] = 2; + len = 4; + } + else { + buf[1] = 1; + len = 3; + } + buf[2] = 0; // method #1 - method id #00: no authentication + if ( rfc1929 ) { + buf[2] = 2; // method #2 - method id #02: username/password + } + if ( send( socks_socket, (const char *)buf, len, 0 ) == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 5 ) { + idLib::Printf( "NET_OpenSocks: bad response\n" ); + return; + } + switch( buf[1] ) { + case 0: // no authentication + break; + case 2: // username/password authentication + break; + default: + idLib::Printf( "NET_OpenSocks: request denied\n" ); + return; + } + + // do username/password authentication if needed + if ( buf[1] == 2 ) { + int ulen; + int plen; + + // build the request + ulen = idStr::Length( net_socksUsername.GetString() ); + plen = idStr::Length( net_socksPassword.GetString() ); + + buf[0] = 1; // username/password authentication version + buf[1] = ulen; + if ( ulen ) { + memcpy( &buf[2], net_socksUsername.GetString(), ulen ); + } + buf[2 + ulen] = plen; + if ( plen ) { + memcpy( &buf[3 + ulen], net_socksPassword.GetString(), plen ); + } + + // send it + if ( send( socks_socket, (const char *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 1 ) { + idLib::Printf( "NET_OpenSocks: bad response\n" ); + return; + } + if ( buf[1] != 0 ) { + idLib::Printf( "NET_OpenSocks: authentication failed\n" ); + return; + } + } + + // send the UDP associate request + buf[0] = 5; // SOCKS version + buf[1] = 3; // command: UDP associate + buf[2] = 0; // reserved + buf[3] = 1; // address type: IPV4 + *(int *)&buf[4] = INADDR_ANY; + *(short *)&buf[8] = htons( (short)port ); // port + if ( send( socks_socket, (const char *)buf, 10, 0 ) == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if( len == SOCKET_ERROR ) { + idLib::Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if( len < 2 || buf[0] != 5 ) { + idLib::Printf( "NET_OpenSocks: bad response\n" ); + return; + } + // check completion code + if( buf[1] != 0 ) { + idLib::Printf( "NET_OpenSocks: request denied: %i\n", buf[1] ); + return; + } + if( buf[3] != 1 ) { + idLib::Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] ); + return; + } + socksRelayAddr.sin_family = AF_INET; + socksRelayAddr.sin_addr.s_addr = *(int *)&buf[4]; + socksRelayAddr.sin_port = *(short *)&buf[8]; + memset( socksRelayAddr.sin_zero, 0, sizeof( socksRelayAddr.sin_zero ) ); + + usingSocks = true; +} + +/* +======================== +Net_WaitForData +======================== +*/ +bool Net_WaitForData( int netSocket, int timeout ) { + int ret; + fd_set set; + struct timeval tv; + + if ( !netSocket ) { + return false; + } + + if ( timeout < 0 ) { + return true; + } + + FD_ZERO( &set ); + FD_SET( static_cast( netSocket ), &set ); + + tv.tv_sec = 0; + tv.tv_usec = timeout * 1000; + + ret = select( netSocket + 1, &set, NULL, NULL, &tv ); + + if ( ret == -1 ) { + idLib::Printf( "Net_WaitForData select(): %s\n", strerror( errno ) ); + return false; + } + + // timeout with no data + if ( ret == 0 ) { + return false; + } + + return true; +} + +/* +======================== +Net_GetUDPPacket +======================== +*/ +bool Net_GetUDPPacket( int netSocket, netadr_t &net_from, char *data, int &size, int maxSize ) { + int ret; + sockaddr_in from; + int fromlen; + int err; + + if ( !netSocket ) { + return false; + } + + fromlen = sizeof(from); + ret = recvfrom( netSocket, data, maxSize, 0, (sockaddr *)&from, &fromlen ); + if ( ret == SOCKET_ERROR ) { + err = WSAGetLastError(); + + if ( err == WSAEWOULDBLOCK || err == WSAECONNRESET ) { + return false; + } + char buf[1024]; + sprintf( buf, "Net_GetUDPPacket: %s\n", NET_ErrorString() ); + idLib::Printf( buf ); + return false; + } + + if ( static_cast( netSocket ) == ip_socket ) { + memset( from.sin_zero, 0, sizeof( from.sin_zero ) ); + } + + if ( usingSocks && static_cast( netSocket ) == ip_socket && memcmp( &from, &socksRelayAddr, fromlen ) == 0 ) { + if ( ret < 10 || data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 1 ) { + return false; + } + net_from.type = NA_IP; + net_from.ip[0] = data[4]; + net_from.ip[1] = data[5]; + net_from.ip[2] = data[6]; + net_from.ip[3] = data[7]; + net_from.port = *(short *)&data[8]; + memmove( data, &data[10], ret - 10 ); + } else { + Net_SockadrToNetadr( &from, &net_from ); + } + + if ( ret > maxSize ) { + char buf[1024]; + sprintf( buf, "Net_GetUDPPacket: oversize packet from %s\n", Sys_NetAdrToString( net_from ) ); + idLib::Printf( buf ); + return false; + } + + size = ret; + + return true; +} + +/* +======================== +Net_SendUDPPacket +======================== +*/ +void Net_SendUDPPacket( int netSocket, int length, const void *data, const netadr_t to ) { + int ret; + sockaddr_in addr; + + if ( !netSocket ) { + return; + } + + Net_NetadrToSockadr( &to, &addr ); + + if ( usingSocks && to.type == NA_IP ) { + socksBuf[0] = 0; // reserved + socksBuf[1] = 0; + socksBuf[2] = 0; // fragment (not fragmented) + socksBuf[3] = 1; // address type: IPV4 + *(int *)&socksBuf[4] = addr.sin_addr.s_addr; + *(short *)&socksBuf[8] = addr.sin_port; + memcpy( &socksBuf[10], data, length ); + ret = sendto( netSocket, socksBuf, length+10, 0, (sockaddr *)&socksRelayAddr, sizeof(socksRelayAddr) ); + } else { + ret = sendto( netSocket, (const char *)data, length, 0, (sockaddr *)&addr, sizeof(addr) ); + } + if ( ret == SOCKET_ERROR ) { + int err = WSAGetLastError(); + + // some PPP links do not allow broadcasts and return an error + if ( ( err == WSAEADDRNOTAVAIL ) && ( to.type == NA_BROADCAST ) ) { + return; + } + + // NOTE: WSAEWOULDBLOCK used to be silently ignored, + // but that means the packet will be dropped so I don't feel it's a good thing to ignore + idLib::Printf( "UDP sendto error - packet dropped: %s\n", NET_ErrorString() ); + } +} + +/* +======================== +Sys_InitNetworking +======================== +*/ +void Sys_InitNetworking() { + int r; + + if ( winsockInitialized ) { + return; + } + r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata ); + if( r ) { + idLib::Printf( "WARNING: Winsock initialization failed, returned %d\n", r ); + return; + } + + winsockInitialized = true; + idLib::Printf( "Winsock Initialized\n" ); + + PIP_ADAPTER_INFO pAdapterInfo; + PIP_ADAPTER_INFO pAdapter = NULL; + DWORD dwRetVal = 0; + PIP_ADDR_STRING pIPAddrString; + ULONG ulOutBufLen; + bool foundloopback; + + num_interfaces = 0; + foundloopback = false; + + pAdapterInfo = (IP_ADAPTER_INFO *)malloc( sizeof( IP_ADAPTER_INFO ) ); + if( !pAdapterInfo ) { + idLib::FatalError( "Sys_InitNetworking: Couldn't malloc( %d )", sizeof( IP_ADAPTER_INFO ) ); + } + ulOutBufLen = sizeof( IP_ADAPTER_INFO ); + + // Make an initial call to GetAdaptersInfo to get + // the necessary size into the ulOutBufLen variable + if( GetAdaptersInfo( pAdapterInfo, &ulOutBufLen ) == ERROR_BUFFER_OVERFLOW ) { + free( pAdapterInfo ); + pAdapterInfo = (IP_ADAPTER_INFO *)malloc( ulOutBufLen ); + if( !pAdapterInfo ) { + idLib::FatalError( "Sys_InitNetworking: Couldn't malloc( %ld )", ulOutBufLen ); + } + } + + if( ( dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen) ) != NO_ERROR ) { + // happens if you have no network connection + idLib::Printf( "Sys_InitNetworking: GetAdaptersInfo failed (%ld).\n", dwRetVal ); + } else { + pAdapter = pAdapterInfo; + while( pAdapter ) { + idLib::Printf( "Found interface: %s %s - ", pAdapter->AdapterName, pAdapter->Description ); + pIPAddrString = &pAdapter->IpAddressList; + while( pIPAddrString ) { + unsigned long ip_a, ip_m; + if( !idStr::Icmp( "127.0.0.1", pIPAddrString->IpAddress.String ) ) { + foundloopback = true; + } + ip_a = ntohl( inet_addr( pIPAddrString->IpAddress.String ) ); + ip_m = ntohl( inet_addr( pIPAddrString->IpMask.String ) ); + //skip null netmasks + if( !ip_m ) { + idLib::Printf( "%s NULL netmask - skipped\n", pIPAddrString->IpAddress.String ); + pIPAddrString = pIPAddrString->Next; + continue; + } + idLib::Printf( "%s/%s\n", pIPAddrString->IpAddress.String, pIPAddrString->IpMask.String ); + netint[num_interfaces].ip = ip_a; + netint[num_interfaces].mask = ip_m; + idStr::Copynz( netint[num_interfaces].addr, pIPAddrString->IpAddress.String, sizeof( netint[num_interfaces].addr ) ); + num_interfaces++; + if( num_interfaces >= MAX_INTERFACES ) { + idLib::Printf( "Sys_InitNetworking: MAX_INTERFACES(%d) hit.\n", MAX_INTERFACES ); + free( pAdapterInfo ); + return; + } + pIPAddrString = pIPAddrString->Next; + } + pAdapter = pAdapter->Next; + } + } + // for some retarded reason, win32 doesn't count loopback as an adapter... + if( !foundloopback && num_interfaces < MAX_INTERFACES ) { + idLib::Printf( "Sys_InitNetworking: adding loopback interface\n" ); + netint[num_interfaces].ip = ntohl( inet_addr( "127.0.0.1" ) ); + netint[num_interfaces].mask = ntohl( inet_addr( "255.0.0.0" ) ); + num_interfaces++; + } + free( pAdapterInfo ); +} + +/* +======================== +Sys_ShutdownNetworking +======================== +*/ +void Sys_ShutdownNetworking() { + if ( !winsockInitialized ) { + return; + } + WSACleanup(); + winsockInitialized = false; +} + +/* +======================== +Sys_StringToNetAdr +======================== +*/ +bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve ) { + sockaddr_in sadr; + + if ( !Net_StringToSockaddr( s, &sadr, doDNSResolve ) ) { + return false; + } + + Net_SockadrToNetadr( &sadr, a ); + return true; +} + +/* +======================== +Sys_NetAdrToString +======================== +*/ +const char *Sys_NetAdrToString( const netadr_t a ) { + static int index = 0; + static char buf[ 4 ][ 64 ]; // flip/flop + char *s; + + s = buf[index]; + index = (index + 1) & 3; + + if ( a.type == NA_LOOPBACK ) { + if ( a.port ) { + idStr::snPrintf( s, 64, "localhost:%i", a.port ); + } else { + idStr::snPrintf( s, 64, "localhost" ); + } + } else if ( a.type == NA_IP ) { + idStr::snPrintf( s, 64, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], a.port ); + } + return s; +} + +/* +======================== +Sys_IsLANAddress +======================== +*/ +bool Sys_IsLANAddress( const netadr_t adr ) { + if ( adr.type == NA_LOOPBACK ) { + return true; + } + + if ( adr.type != NA_IP ) { + return false; + } + + if ( num_interfaces ) { + int i; + unsigned long *p_ip; + unsigned long ip; + p_ip = (unsigned long *)&adr.ip[0]; + ip = ntohl( *p_ip ); + + for( i = 0; i < num_interfaces; i++ ) { + if( ( netint[i].ip & netint[i].mask ) == ( ip & netint[i].mask ) ) { + return true; + } + } + } + return false; +} + +/* +======================== +Sys_CompareNetAdrBase + +Compares without the port. +======================== +*/ +bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ) { + if ( a.type != b.type ) { + return false; + } + + if ( a.type == NA_LOOPBACK ) { + if ( a.port == b.port ) { + return true; + } + + return false; + } + + if ( a.type == NA_IP ) { + if ( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] ) { + return true; + } + return false; + } + + idLib::Printf( "Sys_CompareNetAdrBase: bad address type\n" ); + return false; +} + +/* +======================== +Sys_GetLocalIPCount +======================== +*/ +int Sys_GetLocalIPCount() { + return num_interfaces; +} + +/* +======================== +Sys_GetLocalIP +======================== +*/ +const char * Sys_GetLocalIP( int i ) { + if ( ( i < 0 ) || ( i >= num_interfaces ) ) { + return NULL; + } + return netint[i].addr; +} + +/* +================================================================================================ + + idUDP + +================================================================================================ +*/ + +/* +======================== +idUDP::idUDP +======================== +*/ +idUDP::idUDP() { + netSocket = 0; + memset( &bound_to, 0, sizeof( bound_to ) ); + silent = false; + packetsRead = 0; + bytesRead = 0; + packetsWritten = 0; + bytesWritten = 0; +} + +/* +======================== +idUDP::~idUDP +======================== +*/ +idUDP::~idUDP() { + Close(); +} + +/* +======================== +idUDP::InitForPort +======================== +*/ +bool idUDP::InitForPort( int portNumber ) { + netSocket = NET_IPSocket( net_ip.GetString(), portNumber, &bound_to ); + if ( netSocket <= 0 ) { + netSocket = 0; + memset( &bound_to, 0, sizeof( bound_to ) ); + return false; + } + + return true; +} + +/* +======================== +idUDP::Close +======================== +*/ +void idUDP::Close() { + if ( netSocket ) { + closesocket( netSocket ); + netSocket = 0; + memset( &bound_to, 0, sizeof( bound_to ) ); + } +} + +/* +======================== +idUDP::GetPacket +======================== +*/ +bool idUDP::GetPacket( netadr_t &from, void *data, int &size, int maxSize ) { + bool ret; + + while ( 1 ) { + + ret = Net_GetUDPPacket( netSocket, from, (char *)data, size, maxSize ); + if ( !ret ) { + break; + } + + packetsRead++; + bytesRead += size; + + break; + } + + return ret; +} + +/* +======================== +idUDP::GetPacketBlocking +======================== +*/ +bool idUDP::GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize, int timeout ) { + + if ( !Net_WaitForData( netSocket, timeout ) ) { + return false; + } + + if ( GetPacket( from, data, size, maxSize ) ) { + return true; + } + + return false; +} + +/* +======================== +idUDP::SendPacket +======================== +*/ +void idUDP::SendPacket( const netadr_t to, const void *data, int size ) { + if ( to.type == NA_BAD ) { + idLib::Warning( "idUDP::SendPacket: bad address type NA_BAD - ignored" ); + return; + } + + packetsWritten++; + bytesWritten += size; + + if ( silent ) { + return; + } + + Net_SendUDPPacket( netSocket, size, data, to ); +} \ No newline at end of file diff --git a/neo/sys/win32/win_qgl.cpp b/neo/sys/win32/win_qgl.cpp new file mode 100644 index 00000000..6d474604 --- /dev/null +++ b/neo/sys/win32/win_qgl.cpp @@ -0,0 +1,2011 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +/* +** QGL_WIN.C +** +** This file implements the operating system binding of GL to QGL function +** pointers. When doing a port of Doom you must implement the following +** two functions: +** +** QGL_Init() - loads libraries, assigns function pointers, etc. +** QGL_Shutdown() - unloads libraries, NULLs function pointers +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include +#include "win_local.h" +#include "../../renderer/tr_local.h" + + +int ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR *); +int ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +int ( WINAPI * qwglGetPixelFormat)(HDC); +BOOL ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +BOOL ( WINAPI * qwglSwapBuffers)(HDC); + +BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +HGLRC ( WINAPI * qwglCreateContext)(HDC); +HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +HDC ( WINAPI * qwglGetCurrentDC)(VOID); +PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF *); +int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF *); +BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +void ( APIENTRY * qglArrayElement )(GLint i); +void ( APIENTRY * qglBegin )(GLenum mode); +void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +void ( APIENTRY * qglCallList )(GLuint list); +void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +void ( APIENTRY * qglClear )(GLbitfield mask); +void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void ( APIENTRY * qglClearDepth )(GLclampd depth); +void ( APIENTRY * qglClearIndex )(GLfloat c); +void ( APIENTRY * qglClearStencil )(GLint s); +void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +void ( APIENTRY * qglColor3bv )(const GLbyte *v); +void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +void ( APIENTRY * qglColor3dv )(const GLdouble *v); +void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +void ( APIENTRY * qglColor3fv )(const GLfloat *v); +void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +void ( APIENTRY * qglColor3iv )(const GLint *v); +void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +void ( APIENTRY * qglColor3sv )(const GLshort *v); +void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +void ( APIENTRY * qglColor3uiv )(const GLuint *v); +void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +void ( APIENTRY * qglColor3usv )(const GLushort *v); +void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +void ( APIENTRY * qglColor4bv )(const GLbyte *v); +void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +void ( APIENTRY * qglColor4dv )(const GLdouble *v); +void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglColor4fv )(const GLfloat *v); +void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +void ( APIENTRY * qglColor4iv )(const GLint *v); +void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +void ( APIENTRY * qglColor4sv )(const GLshort *v); +void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +void ( APIENTRY * qglColor4uiv )(const GLuint *v); +void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +void ( APIENTRY * qglColor4usv )(const GLushort *v); +void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglCullFace )(GLenum mode); +void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +void ( APIENTRY * qglDepthFunc )(GLenum func); +void ( APIENTRY * qglDepthMask )(GLboolean flag); +void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +void ( APIENTRY * qglDisable )(GLenum cap); +void ( APIENTRY * qglDisableClientState )(GLenum array); +void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +void ( APIENTRY * qglDrawBuffer )(GLenum mode); +void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +void ( APIENTRY * qglEnable )(GLenum cap); +void ( APIENTRY * qglEnableClientState )(GLenum array); +void ( APIENTRY * qglEnd )(void); +void ( APIENTRY * qglEndList )(void); +void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +void ( APIENTRY * qglEvalPoint1 )(GLint i); +void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +void ( APIENTRY * qglFinish )(void); +void ( APIENTRY * qglFlush )(void); +void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglFrontFace )(GLenum mode); +void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * qglGenLists )(GLsizei range); +void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * qglGetError )(void); +void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +void ( APIENTRY * qglIndexMask )(GLuint mask); +void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglIndexd )(GLdouble c); +void ( APIENTRY * qglIndexdv )(const GLdouble *c); +void ( APIENTRY * qglIndexf )(GLfloat c); +void ( APIENTRY * qglIndexfv )(const GLfloat *c); +void ( APIENTRY * qglIndexi )(GLint c); +void ( APIENTRY * qglIndexiv )(const GLint *c); +void ( APIENTRY * qglIndexs )(GLshort c); +void ( APIENTRY * qglIndexsv )(const GLshort *c); +void ( APIENTRY * qglIndexub )(GLubyte c); +void ( APIENTRY * qglIndexubv )(const GLubyte *c); +void ( APIENTRY * qglInitNames )(void); +void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * qglIsList )(GLuint list); +GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +void ( APIENTRY * qglLineWidth )(GLfloat width); +void ( APIENTRY * qglListBase )(GLuint base); +void ( APIENTRY * qglLoadIdentity )(void); +void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +void ( APIENTRY * qglLoadName )(GLuint name); +void ( APIENTRY * qglLogicOp )(GLenum opcode); +void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +void ( APIENTRY * qglMatrixMode )(GLenum mode); +void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +void ( APIENTRY * qglNormal3iv )(const GLint *v); +void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +void ( APIENTRY * qglNormal3sv )(const GLshort *v); +void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( APIENTRY * qglPassThrough )(GLfloat token); +void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +void ( APIENTRY * qglPointSize )(GLfloat size); +void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +void ( APIENTRY * qglPopAttrib )(void); +void ( APIENTRY * qglPopClientAttrib )(void); +void ( APIENTRY * qglPopMatrix )(void); +void ( APIENTRY * qglPopName )(void); +void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushMatrix )(void); +void ( APIENTRY * qglPushName )(GLuint name); +void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +void ( APIENTRY * qglReadBuffer )(GLenum mode); +void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * qglRenderMode )(GLenum mode); +void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +void ( APIENTRY * qglShadeModel )(GLenum mode); +void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +void ( APIENTRY * qglStencilMask )(GLuint mask); +void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +void ( APIENTRY * qglTexCoord1d )(GLdouble s); +void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord1f )(GLfloat s); +void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord1i )(GLint s); +void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +void ( APIENTRY * qglTexCoord1s )(GLshort s); +void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +void ( APIENTRY * qglVertex2iv )(const GLint *v); +void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +void ( APIENTRY * qglVertex2sv )(const GLshort *v); +void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglVertex3iv )(const GLint *v); +void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglVertex3sv )(const GLshort *v); +void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglVertex4iv )(const GLint *v); +void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglVertex4sv )(const GLshort *v); +void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + + + +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); + +typedef struct { + GLenum e; + const char *name; +} glEnumName_t; + +#define DEF(x) { x, #x }, + +glEnumName_t glEnumNames[] = { +DEF(GL_FALSE) +DEF(GL_TRUE) + + { GL_BYTE, "GL_BYTE" }, + { GL_UNSIGNED_BYTE, "GL_UNSIGNED_BYTE" }, + { GL_SHORT, "GL_SHORT" }, + { GL_UNSIGNED_SHORT, "GL_UNSIGNED_SHORT" }, + { GL_INT, "GL_INT" }, + { GL_UNSIGNED_INT, "GL_UNSIGNED_INT" }, + { GL_FLOAT, "GL_FLOAT" }, + { GL_DOUBLE, "GL_DOUBLE" }, + + { GL_TEXTURE_CUBE_MAP_EXT, "GL_TEXTURE_CUBE_MAP_EXT" }, + { GL_TEXTURE_3D, "GL_TEXTURE_3D" }, + { GL_TEXTURE_2D, "GL_TEXTURE_2D" }, + { GL_BLEND, "GL_BLEND" }, + { GL_DEPTH_TEST, "GL_DEPTH_TEST" }, + { GL_CULL_FACE, "GL_CULL_FACE" }, + { GL_CLIP_PLANE0, "GL_CLIP_PLANE0" }, + { GL_COLOR_ARRAY, "GL_COLOR_ARRAY" }, + { GL_TEXTURE_COORD_ARRAY, "GL_TEXTURE_COORD_ARRAY" }, + { GL_VERTEX_ARRAY, "GL_VERTEX_ARRAY" }, + { GL_ALPHA_TEST, "GL_ALPHA_TEST" }, + { GL_TEXTURE_GEN_S, "GL_TEXTURE_GEN_S" }, + { GL_TEXTURE_GEN_T, "GL_TEXTURE_GEN_T" }, + { GL_TEXTURE_GEN_R, "GL_TEXTURE_GEN_R" }, + { GL_TEXTURE_GEN_Q, "GL_TEXTURE_GEN_Q" }, + { GL_STENCIL_TEST, "GL_STENCIL_TEST" }, + { GL_POLYGON_OFFSET_FILL, "GL_POLYGON_OFFSET_FILL" }, + + { GL_TRIANGLES, "GL_TRIANGLES" }, + { GL_TRIANGLE_STRIP, "GL_TRIANGLE_STRIP" }, + { GL_TRIANGLE_FAN, "GL_TRIANGLE_FAN" }, + { GL_QUADS, "GL_QUADS" }, + { GL_QUAD_STRIP, "GL_QUAD_STRIP" }, + { GL_POLYGON, "GL_POLYGON" }, + { GL_POINTS, "GL_POINTS" }, + { GL_LINES, "GL_LINES" }, + { GL_LINE_STRIP, "GL_LINE_STRIP" }, + { GL_LINE_LOOP, "GL_LINE_LOOP" }, + + { GL_ALWAYS, "GL_ALWAYS" }, + { GL_NEVER, "GL_NEVER" }, + { GL_LEQUAL, "GL_LEQUAL" }, + { GL_LESS, "GL_LESS" }, + { GL_EQUAL, "GL_EQUAL" }, + { GL_GREATER, "GL_GREATER" }, + { GL_GEQUAL, "GL_GEQUAL" }, + { GL_NOTEQUAL, "GL_NOTEQUAL" }, + + { GL_ONE, "GL_ONE" }, + { GL_ZERO, "GL_ZERO" }, + { GL_SRC_ALPHA, "GL_SRC_ALPHA" }, + { GL_ONE_MINUS_SRC_ALPHA, "GL_ONE_MINUS_SRC_ALPHA" }, + { GL_DST_COLOR, "GL_DST_COLOR" }, + { GL_ONE_MINUS_DST_COLOR, "GL_ONE_MINUS_DST_COLOR" }, + { GL_DST_ALPHA, "GL_DST_ALPHA" }, + + { GL_MODELVIEW, "GL_MODELVIEW" }, + { GL_PROJECTION, "GL_PROJECTION" }, + { GL_TEXTURE, "GL_TEXTURE" }, + +/* DrawBufferMode */ +DEF(GL_NONE) +DEF(GL_FRONT_LEFT) +DEF(GL_FRONT_RIGHT) +DEF(GL_BACK_LEFT) +DEF(GL_BACK_RIGHT) +DEF(GL_FRONT) +DEF(GL_BACK) +DEF(GL_LEFT) +DEF(GL_RIGHT) +DEF(GL_FRONT_AND_BACK) +DEF(GL_AUX0) +DEF(GL_AUX1) +DEF(GL_AUX2) +DEF(GL_AUX3) + +/* GetTarget */ +DEF(GL_CURRENT_COLOR) +DEF(GL_CURRENT_INDEX) +DEF(GL_CURRENT_NORMAL) +DEF(GL_CURRENT_TEXTURE_COORDS) +DEF(GL_CURRENT_RASTER_COLOR) +DEF(GL_CURRENT_RASTER_INDEX) +DEF(GL_CURRENT_RASTER_TEXTURE_COORDS) +DEF(GL_CURRENT_RASTER_POSITION) +DEF(GL_CURRENT_RASTER_POSITION_VALID) +DEF(GL_CURRENT_RASTER_DISTANCE) +DEF(GL_POINT_SMOOTH) +DEF(GL_POINT_SIZE) +DEF(GL_POINT_SIZE_RANGE) +DEF(GL_POINT_SIZE_GRANULARITY) +DEF(GL_LINE_SMOOTH) +DEF(GL_LINE_WIDTH) +DEF(GL_LINE_WIDTH_RANGE) +DEF(GL_LINE_WIDTH_GRANULARITY) +DEF(GL_LINE_STIPPLE) +DEF(GL_LINE_STIPPLE_PATTERN) +DEF(GL_LINE_STIPPLE_REPEAT) +DEF(GL_LIST_MODE) +DEF(GL_MAX_LIST_NESTING) +DEF(GL_LIST_BASE) +DEF(GL_LIST_INDEX) +DEF(GL_POLYGON_MODE) +DEF(GL_POLYGON_SMOOTH) +DEF(GL_POLYGON_STIPPLE) +DEF(GL_EDGE_FLAG) +DEF(GL_CULL_FACE) +DEF(GL_CULL_FACE_MODE) +DEF(GL_FRONT_FACE) +DEF(GL_LIGHTING) +DEF(GL_LIGHT_MODEL_LOCAL_VIEWER) +DEF(GL_LIGHT_MODEL_TWO_SIDE) +DEF(GL_LIGHT_MODEL_AMBIENT) +DEF(GL_SHADE_MODEL) +DEF(GL_COLOR_MATERIAL_FACE) +DEF(GL_COLOR_MATERIAL_PARAMETER) +DEF(GL_COLOR_MATERIAL) +DEF(GL_FOG) +DEF(GL_FOG_INDEX) +DEF(GL_FOG_DENSITY) +DEF(GL_FOG_START) +DEF(GL_FOG_END) +DEF(GL_FOG_MODE) +DEF(GL_FOG_COLOR) +DEF(GL_DEPTH_RANGE) +DEF(GL_DEPTH_TEST) +DEF(GL_DEPTH_WRITEMASK) +DEF(GL_DEPTH_CLEAR_VALUE) +DEF(GL_DEPTH_FUNC) +DEF(GL_ACCUM_CLEAR_VALUE) +DEF(GL_STENCIL_TEST) +DEF(GL_STENCIL_CLEAR_VALUE) +DEF(GL_STENCIL_FUNC) +DEF(GL_STENCIL_VALUE_MASK) +DEF(GL_STENCIL_FAIL) +DEF(GL_STENCIL_PASS_DEPTH_FAIL) +DEF(GL_STENCIL_PASS_DEPTH_PASS) +DEF(GL_STENCIL_REF) +DEF(GL_STENCIL_WRITEMASK) +DEF(GL_MATRIX_MODE) +DEF(GL_NORMALIZE) +DEF(GL_VIEWPORT) +DEF(GL_MODELVIEW_STACK_DEPTH) +DEF(GL_PROJECTION_STACK_DEPTH) +DEF(GL_TEXTURE_STACK_DEPTH) +DEF(GL_MODELVIEW_MATRIX) +DEF(GL_PROJECTION_MATRIX) +DEF(GL_TEXTURE_MATRIX) +DEF(GL_ATTRIB_STACK_DEPTH) +DEF(GL_CLIENT_ATTRIB_STACK_DEPTH) +DEF(GL_ALPHA_TEST) +DEF(GL_ALPHA_TEST_FUNC) +DEF(GL_ALPHA_TEST_REF) +DEF(GL_DITHER) +DEF(GL_BLEND_DST) +DEF(GL_BLEND_SRC) +DEF(GL_BLEND) +DEF(GL_LOGIC_OP_MODE) +DEF(GL_INDEX_LOGIC_OP) +DEF(GL_COLOR_LOGIC_OP) +DEF(GL_AUX_BUFFERS) +DEF(GL_DRAW_BUFFER) +DEF(GL_READ_BUFFER) +DEF(GL_SCISSOR_BOX) +DEF(GL_SCISSOR_TEST) +DEF(GL_INDEX_CLEAR_VALUE) +DEF(GL_INDEX_WRITEMASK) +DEF(GL_COLOR_CLEAR_VALUE) +DEF(GL_COLOR_WRITEMASK) +DEF(GL_INDEX_MODE) +DEF(GL_RGBA_MODE) +DEF(GL_DOUBLEBUFFER) +DEF(GL_STEREO) +DEF(GL_RENDER_MODE) +DEF(GL_PERSPECTIVE_CORRECTION_HINT) +DEF(GL_POINT_SMOOTH_HINT) +DEF(GL_LINE_SMOOTH_HINT) +DEF(GL_POLYGON_SMOOTH_HINT) +DEF(GL_FOG_HINT) +DEF(GL_TEXTURE_GEN_S) +DEF(GL_TEXTURE_GEN_T) +DEF(GL_TEXTURE_GEN_R) +DEF(GL_TEXTURE_GEN_Q) +DEF(GL_PIXEL_MAP_I_TO_I) +DEF(GL_PIXEL_MAP_S_TO_S) +DEF(GL_PIXEL_MAP_I_TO_R) +DEF(GL_PIXEL_MAP_I_TO_G) +DEF(GL_PIXEL_MAP_I_TO_B) +DEF(GL_PIXEL_MAP_I_TO_A) +DEF(GL_PIXEL_MAP_R_TO_R) +DEF(GL_PIXEL_MAP_G_TO_G) +DEF(GL_PIXEL_MAP_B_TO_B) +DEF(GL_PIXEL_MAP_A_TO_A) +DEF(GL_PIXEL_MAP_I_TO_I_SIZE) +DEF(GL_PIXEL_MAP_S_TO_S_SIZE) +DEF(GL_PIXEL_MAP_I_TO_R_SIZE) +DEF(GL_PIXEL_MAP_I_TO_G_SIZE) +DEF(GL_PIXEL_MAP_I_TO_B_SIZE) +DEF(GL_PIXEL_MAP_I_TO_A_SIZE) +DEF(GL_PIXEL_MAP_R_TO_R_SIZE) +DEF(GL_PIXEL_MAP_G_TO_G_SIZE) +DEF(GL_PIXEL_MAP_B_TO_B_SIZE) +DEF(GL_PIXEL_MAP_A_TO_A_SIZE) +DEF(GL_UNPACK_SWAP_BYTES) +DEF(GL_UNPACK_LSB_FIRST) +DEF(GL_UNPACK_ROW_LENGTH) +DEF(GL_UNPACK_SKIP_ROWS) +DEF(GL_UNPACK_SKIP_PIXELS) +DEF(GL_UNPACK_ALIGNMENT) +DEF(GL_PACK_SWAP_BYTES) +DEF(GL_PACK_LSB_FIRST) +DEF(GL_PACK_ROW_LENGTH) +DEF(GL_PACK_SKIP_ROWS) +DEF(GL_PACK_SKIP_PIXELS) +DEF(GL_PACK_ALIGNMENT) +DEF(GL_MAP_COLOR) +DEF(GL_MAP_STENCIL) +DEF(GL_INDEX_SHIFT) +DEF(GL_INDEX_OFFSET) +DEF(GL_RED_SCALE) +DEF(GL_RED_BIAS) +DEF(GL_ZOOM_X) +DEF(GL_ZOOM_Y) +DEF(GL_GREEN_SCALE) +DEF(GL_GREEN_BIAS) +DEF(GL_BLUE_SCALE) +DEF(GL_BLUE_BIAS) +DEF(GL_ALPHA_SCALE) +DEF(GL_ALPHA_BIAS) +DEF(GL_DEPTH_SCALE) +DEF(GL_DEPTH_BIAS) +DEF(GL_MAX_EVAL_ORDER) +DEF(GL_MAX_LIGHTS) +DEF(GL_MAX_CLIP_PLANES) +DEF(GL_MAX_TEXTURE_SIZE) +DEF(GL_MAX_PIXEL_MAP_TABLE) +DEF(GL_MAX_ATTRIB_STACK_DEPTH) +DEF(GL_MAX_MODELVIEW_STACK_DEPTH) +DEF(GL_MAX_NAME_STACK_DEPTH) +DEF(GL_MAX_PROJECTION_STACK_DEPTH) +DEF(GL_MAX_TEXTURE_STACK_DEPTH) +DEF(GL_MAX_VIEWPORT_DIMS) +DEF(GL_MAX_CLIENT_ATTRIB_STACK_DEPTH) +DEF(GL_SUBPIXEL_BITS) +DEF(GL_INDEX_BITS) +DEF(GL_RED_BITS) +DEF(GL_GREEN_BITS) +DEF(GL_BLUE_BITS) +DEF(GL_ALPHA_BITS) +DEF(GL_DEPTH_BITS) +DEF(GL_STENCIL_BITS) +DEF(GL_ACCUM_RED_BITS) +DEF(GL_ACCUM_GREEN_BITS) +DEF(GL_ACCUM_BLUE_BITS) +DEF(GL_ACCUM_ALPHA_BITS) +DEF(GL_NAME_STACK_DEPTH) +DEF(GL_AUTO_NORMAL) +DEF(GL_MAP1_COLOR_4) +DEF(GL_MAP1_INDEX) +DEF(GL_MAP1_NORMAL) +DEF(GL_MAP1_TEXTURE_COORD_1) +DEF(GL_MAP1_TEXTURE_COORD_2) +DEF(GL_MAP1_TEXTURE_COORD_3) +DEF(GL_MAP1_TEXTURE_COORD_4) +DEF(GL_MAP1_VERTEX_3) +DEF(GL_MAP1_VERTEX_4) +DEF(GL_MAP2_COLOR_4) +DEF(GL_MAP2_INDEX) +DEF(GL_MAP2_NORMAL) +DEF(GL_MAP2_TEXTURE_COORD_1) +DEF(GL_MAP2_TEXTURE_COORD_2) +DEF(GL_MAP2_TEXTURE_COORD_3) +DEF(GL_MAP2_TEXTURE_COORD_4) +DEF(GL_MAP2_VERTEX_3) +DEF(GL_MAP2_VERTEX_4) +DEF(GL_MAP1_GRID_DOMAIN) +DEF(GL_MAP1_GRID_SEGMENTS) +DEF(GL_MAP2_GRID_DOMAIN) +DEF(GL_MAP2_GRID_SEGMENTS) +DEF(GL_TEXTURE_1D) +DEF(GL_TEXTURE_2D) +DEF(GL_FEEDBACK_BUFFER_POINTER) +DEF(GL_FEEDBACK_BUFFER_SIZE) +DEF(GL_FEEDBACK_BUFFER_TYPE) +DEF(GL_SELECTION_BUFFER_POINTER) +DEF(GL_SELECTION_BUFFER_SIZE) + +/* PixelCopyType */ +DEF(GL_COLOR) +DEF(GL_DEPTH) +DEF(GL_STENCIL) + +/* PixelFormat */ +DEF(GL_COLOR_INDEX) +DEF(GL_STENCIL_INDEX) +DEF(GL_DEPTH_COMPONENT) +DEF(GL_RED) +DEF(GL_GREEN) +DEF(GL_BLUE) +DEF(GL_ALPHA) +DEF(GL_RGB) +DEF(GL_RGBA) +DEF(GL_LUMINANCE) +DEF(GL_LUMINANCE_ALPHA) + +/* 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 */ +DEF(GL_BITMAP) +/* GL_BYTE */ +/* GL_UNSIGNED_BYTE */ +/* GL_SHORT */ +/* GL_UNSIGNED_SHORT */ +/* GL_INT */ +/* GL_UNSIGNED_INT */ +/* GL_FLOAT */ + +/* PolygonMode */ +DEF(GL_POINT) +DEF(GL_LINE) +DEF(GL_FILL) + +/* RenderingMode */ +DEF(GL_RENDER) +DEF(GL_FEEDBACK) +DEF(GL_SELECT) + +/* ShadingModel */ +DEF(GL_FLAT) +DEF(GL_SMOOTH) + +/* StencilOp */ +/* GL_ZERO */ +DEF(GL_KEEP) +DEF(GL_REPLACE) +DEF(GL_INCR) +DEF(GL_DECR) +/* GL_INVERT */ + +/* StringName */ +DEF(GL_VENDOR) +DEF(GL_RENDERER) +DEF(GL_VERSION) +DEF(GL_EXTENSIONS) + +/* TextureCoordName */ +DEF(GL_S) +DEF(GL_T) +DEF(GL_R) +DEF(GL_Q) + +/* TexCoordPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* TextureEnvMode */ +DEF(GL_MODULATE) +DEF(GL_DECAL) +/* GL_BLEND */ +/* GL_REPLACE */ + +/* TextureEnvParameter */ +DEF(GL_TEXTURE_ENV_MODE) +DEF(GL_TEXTURE_ENV_COLOR) + +/* TextureEnvTarget */ +DEF(GL_TEXTURE_ENV) + +/* TextureGenMode */ +DEF(GL_EYE_LINEAR) +DEF(GL_OBJECT_LINEAR) +DEF(GL_SPHERE_MAP) + +/* TextureGenParameter */ +DEF(GL_TEXTURE_GEN_MODE) +DEF(GL_OBJECT_PLANE) +DEF(GL_EYE_PLANE) + +/* TextureMagFilter */ +DEF(GL_NEAREST) +DEF(GL_LINEAR) + +/* TextureMinFilter */ +/* GL_NEAREST */ +/* GL_LINEAR */ +DEF(GL_NEAREST_MIPMAP_NEAREST) +DEF(GL_LINEAR_MIPMAP_NEAREST) +DEF(GL_NEAREST_MIPMAP_LINEAR) +DEF(GL_LINEAR_MIPMAP_LINEAR) + +/* TextureParameterName */ +DEF(GL_TEXTURE_MAG_FILTER) +DEF(GL_TEXTURE_MIN_FILTER) +DEF(GL_TEXTURE_WRAP_S) +DEF(GL_TEXTURE_WRAP_T) +/* GL_TEXTURE_BORDER_COLOR */ +/* GL_TEXTURE_PRIORITY */ + +/* TextureTarget */ +/* GL_TEXTURE_1D */ +/* GL_TEXTURE_2D */ +/* GL_PROXY_TEXTURE_1D */ +/* GL_PROXY_TEXTURE_2D */ + +/* TextureWrapMode */ +DEF(GL_CLAMP) +DEF(GL_REPEAT) + +/* VertexPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* ClientAttribMask */ +DEF(GL_CLIENT_PIXEL_STORE_BIT) +DEF(GL_CLIENT_VERTEX_ARRAY_BIT) +DEF(GL_CLIENT_ALL_ATTRIB_BITS) + +/* polygon_offset */ +DEF(GL_POLYGON_OFFSET_FACTOR) +DEF(GL_POLYGON_OFFSET_UNITS) +DEF(GL_POLYGON_OFFSET_POINT) +DEF(GL_POLYGON_OFFSET_LINE) +DEF(GL_POLYGON_OFFSET_FILL) + + { 0, NULL } +}; + +/* +** 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 ) +{ + common->Printf( "...shutting down QGL\n" ); + + if ( win32.hinstOpenGL ) + { + common->Printf( "...unloading OpenGL DLL\n" ); + FreeLibrary( win32.hinstOpenGL ); + } + + win32.hinstOpenGL = NULL; + + qglAccum = NULL; + qglAlphaFunc = NULL; + qglAreTexturesResident = NULL; + qglArrayElement = NULL; + qglBegin = NULL; + qglBindTexture = NULL; + qglBitmap = NULL; + qglBlendFunc = NULL; + qglCallList = NULL; + qglCallLists = NULL; + qglClear = NULL; + qglClearAccum = NULL; + qglClearColor = NULL; + qglClearDepth = NULL; + qglClearIndex = NULL; + qglClearStencil = NULL; + qglClipPlane = NULL; + qglColor3b = NULL; + qglColor3bv = NULL; + qglColor3d = NULL; + qglColor3dv = NULL; + qglColor3f = NULL; + qglColor3fv = NULL; + qglColor3i = NULL; + qglColor3iv = NULL; + qglColor3s = NULL; + qglColor3sv = NULL; + qglColor3ub = NULL; + qglColor3ubv = NULL; + qglColor3ui = NULL; + qglColor3uiv = NULL; + qglColor3us = NULL; + qglColor3usv = NULL; + qglColor4b = NULL; + qglColor4bv = NULL; + qglColor4d = NULL; + qglColor4dv = NULL; + qglColor4f = NULL; + qglColor4fv = NULL; + qglColor4i = NULL; + qglColor4iv = NULL; + qglColor4s = NULL; + qglColor4sv = NULL; + qglColor4ub = NULL; + qglColor4ubv = NULL; + qglColor4ui = NULL; + qglColor4uiv = NULL; + qglColor4us = NULL; + qglColor4usv = NULL; + qglColorMask = NULL; + qglColorMaterial = NULL; + qglColorPointer = NULL; + qglCopyPixels = NULL; + qglCopyTexImage1D = NULL; + qglCopyTexImage2D = NULL; + qglCopyTexSubImage1D = NULL; + qglCopyTexSubImage2D = NULL; + qglCullFace = NULL; + qglDeleteLists = NULL; + qglDeleteTextures = NULL; + qglDepthFunc = NULL; + qglDepthMask = NULL; + qglDepthRange = NULL; + qglDisable = NULL; + qglDisableClientState = NULL; + qglDrawArrays = NULL; + qglDrawBuffer = NULL; + qglDrawElements = NULL; + qglDrawPixels = NULL; + qglEdgeFlag = NULL; + qglEdgeFlagPointer = NULL; + qglEdgeFlagv = NULL; + qglEnable = NULL; + qglEnableClientState = NULL; + qglEnd = NULL; + qglEndList = NULL; + qglEvalCoord1d = NULL; + qglEvalCoord1dv = NULL; + qglEvalCoord1f = NULL; + qglEvalCoord1fv = NULL; + qglEvalCoord2d = NULL; + qglEvalCoord2dv = NULL; + qglEvalCoord2f = NULL; + qglEvalCoord2fv = NULL; + qglEvalMesh1 = NULL; + qglEvalMesh2 = NULL; + qglEvalPoint1 = NULL; + qglEvalPoint2 = NULL; + qglFeedbackBuffer = NULL; + qglFinish = NULL; + qglFlush = NULL; + qglFogf = NULL; + qglFogfv = NULL; + qglFogi = NULL; + qglFogiv = NULL; + qglFrontFace = NULL; + qglFrustum = NULL; + qglGenLists = NULL; + qglGenTextures = NULL; + qglGetBooleanv = NULL; + qglGetClipPlane = NULL; + qglGetDoublev = NULL; + qglGetError = NULL; + qglGetFloatv = NULL; + qglGetIntegerv = NULL; + qglGetLightfv = NULL; + qglGetLightiv = NULL; + qglGetMapdv = NULL; + qglGetMapfv = NULL; + qglGetMapiv = NULL; + qglGetMaterialfv = NULL; + qglGetMaterialiv = NULL; + qglGetPixelMapfv = NULL; + qglGetPixelMapuiv = NULL; + qglGetPixelMapusv = NULL; + qglGetPointerv = NULL; + qglGetPolygonStipple = NULL; + qglGetString = NULL; + qglGetTexEnvfv = NULL; + qglGetTexEnviv = NULL; + qglGetTexGendv = NULL; + qglGetTexGenfv = NULL; + qglGetTexGeniv = NULL; + qglGetTexImage = NULL; + qglGetTexLevelParameterfv = NULL; + qglGetTexLevelParameteriv = NULL; + qglGetTexParameterfv = NULL; + qglGetTexParameteriv = NULL; + qglHint = NULL; + qglIndexMask = NULL; + qglIndexPointer = NULL; + qglIndexd = NULL; + qglIndexdv = NULL; + qglIndexf = NULL; + qglIndexfv = NULL; + qglIndexi = NULL; + qglIndexiv = NULL; + qglIndexs = NULL; + qglIndexsv = NULL; + qglIndexub = NULL; + qglIndexubv = NULL; + qglInitNames = NULL; + qglInterleavedArrays = NULL; + qglIsEnabled = NULL; + qglIsList = NULL; + qglIsTexture = NULL; + qglLightModelf = NULL; + qglLightModelfv = NULL; + qglLightModeli = NULL; + qglLightModeliv = NULL; + qglLightf = NULL; + qglLightfv = NULL; + qglLighti = NULL; + qglLightiv = NULL; + qglLineStipple = NULL; + qglLineWidth = NULL; + qglListBase = NULL; + qglLoadIdentity = NULL; + qglLoadMatrixd = NULL; + qglLoadMatrixf = NULL; + qglLoadName = NULL; + qglLogicOp = NULL; + qglMap1d = NULL; + qglMap1f = NULL; + qglMap2d = NULL; + qglMap2f = NULL; + qglMapGrid1d = NULL; + qglMapGrid1f = NULL; + qglMapGrid2d = NULL; + qglMapGrid2f = NULL; + qglMaterialf = NULL; + qglMaterialfv = NULL; + qglMateriali = NULL; + qglMaterialiv = NULL; + qglMatrixMode = NULL; + qglMultMatrixd = NULL; + qglMultMatrixf = NULL; + qglNewList = NULL; + qglNormal3b = NULL; + qglNormal3bv = NULL; + qglNormal3d = NULL; + qglNormal3dv = NULL; + qglNormal3f = NULL; + qglNormal3fv = NULL; + qglNormal3i = NULL; + qglNormal3iv = NULL; + qglNormal3s = NULL; + qglNormal3sv = NULL; + qglNormalPointer = NULL; + qglOrtho = NULL; + qglPassThrough = NULL; + qglPixelMapfv = NULL; + qglPixelMapuiv = NULL; + qglPixelMapusv = NULL; + qglPixelStoref = NULL; + qglPixelStorei = NULL; + qglPixelTransferf = NULL; + qglPixelTransferi = NULL; + qglPixelZoom = NULL; + qglPointSize = NULL; + qglPolygonMode = NULL; + qglPolygonOffset = NULL; + qglPolygonStipple = NULL; + qglPopAttrib = NULL; + qglPopClientAttrib = NULL; + qglPopMatrix = NULL; + qglPopName = NULL; + qglPrioritizeTextures = NULL; + qglPushAttrib = NULL; + qglPushClientAttrib = NULL; + qglPushMatrix = NULL; + qglPushName = NULL; + qglRasterPos2d = NULL; + qglRasterPos2dv = NULL; + qglRasterPos2f = NULL; + qglRasterPos2fv = NULL; + qglRasterPos2i = NULL; + qglRasterPos2iv = NULL; + qglRasterPos2s = NULL; + qglRasterPos2sv = NULL; + qglRasterPos3d = NULL; + qglRasterPos3dv = NULL; + qglRasterPos3f = NULL; + qglRasterPos3fv = NULL; + qglRasterPos3i = NULL; + qglRasterPos3iv = NULL; + qglRasterPos3s = NULL; + qglRasterPos3sv = NULL; + qglRasterPos4d = NULL; + qglRasterPos4dv = NULL; + qglRasterPos4f = NULL; + qglRasterPos4fv = NULL; + qglRasterPos4i = NULL; + qglRasterPos4iv = NULL; + qglRasterPos4s = NULL; + qglRasterPos4sv = NULL; + qglReadBuffer = NULL; + qglReadPixels = NULL; + qglRectd = NULL; + qglRectdv = NULL; + qglRectf = NULL; + qglRectfv = NULL; + qglRecti = NULL; + qglRectiv = NULL; + qglRects = NULL; + qglRectsv = NULL; + qglRenderMode = NULL; + qglRotated = NULL; + qglRotatef = NULL; + qglScaled = NULL; + qglScalef = NULL; + qglScissor = NULL; + qglSelectBuffer = NULL; + qglShadeModel = NULL; + qglStencilFunc = NULL; + qglStencilMask = NULL; + qglStencilOp = NULL; + qglTexCoord1d = NULL; + qglTexCoord1dv = NULL; + qglTexCoord1f = NULL; + qglTexCoord1fv = NULL; + qglTexCoord1i = NULL; + qglTexCoord1iv = NULL; + qglTexCoord1s = NULL; + qglTexCoord1sv = NULL; + qglTexCoord2d = NULL; + qglTexCoord2dv = NULL; + qglTexCoord2f = NULL; + qglTexCoord2fv = NULL; + qglTexCoord2i = NULL; + qglTexCoord2iv = NULL; + qglTexCoord2s = NULL; + qglTexCoord2sv = NULL; + qglTexCoord3d = NULL; + qglTexCoord3dv = NULL; + qglTexCoord3f = NULL; + qglTexCoord3fv = NULL; + qglTexCoord3i = NULL; + qglTexCoord3iv = NULL; + qglTexCoord3s = NULL; + qglTexCoord3sv = NULL; + qglTexCoord4d = NULL; + qglTexCoord4dv = NULL; + qglTexCoord4f = NULL; + qglTexCoord4fv = NULL; + qglTexCoord4i = NULL; + qglTexCoord4iv = NULL; + qglTexCoord4s = NULL; + qglTexCoord4sv = NULL; + qglTexCoordPointer = NULL; + qglTexEnvf = NULL; + qglTexEnvfv = NULL; + qglTexEnvi = NULL; + qglTexEnviv = NULL; + qglTexGend = NULL; + qglTexGendv = NULL; + qglTexGenf = NULL; + qglTexGenfv = NULL; + qglTexGeni = NULL; + qglTexGeniv = NULL; + qglTexImage1D = NULL; + qglTexImage2D = NULL; + qglTexParameterf = NULL; + qglTexParameterfv = NULL; + qglTexParameteri = NULL; + qglTexParameteriv = NULL; + qglTexSubImage1D = NULL; + qglTexSubImage2D = NULL; + qglTranslated = NULL; + qglTranslatef = NULL; + qglVertex2d = NULL; + qglVertex2dv = NULL; + qglVertex2f = NULL; + qglVertex2fv = NULL; + qglVertex2i = NULL; + qglVertex2iv = NULL; + qglVertex2s = NULL; + qglVertex2sv = NULL; + qglVertex3d = NULL; + qglVertex3dv = NULL; + qglVertex3f = NULL; + qglVertex3fv = NULL; + qglVertex3i = NULL; + qglVertex3iv = NULL; + qglVertex3s = NULL; + qglVertex3sv = NULL; + qglVertex4d = NULL; + qglVertex4dv = NULL; + qglVertex4f = NULL; + qglVertex4fv = NULL; + qglVertex4i = NULL; + qglVertex4iv = NULL; + qglVertex4s = NULL; + qglVertex4sv = NULL; + qglVertexPointer = NULL; + qglViewport = 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 + + +#pragma warning (disable : 4113 4133 4047 ) +#define GPA( a ) GetProcAddress( win32.hinstOpenGL, a ) + +/* +** QGL_Init +** +** This is responsible for binding our qgl 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 ) +{ + assert( win32.hinstOpenGL == 0 ); + + common->Printf( "...initializing QGL\n" ); + + common->Printf( "...calling LoadLibrary( '%s' ): ", dllname ); + + if ( ( win32.hinstOpenGL = LoadLibrary( dllname ) ) == 0 ) + { + common->Printf( "failed\n" ); + return false; + } + common->Printf( "succeeded\n" ); + + qglAccum = dllAccum = glAccum; + qglAlphaFunc = dllAlphaFunc = glAlphaFunc; + qglAreTexturesResident = dllAreTexturesResident = glAreTexturesResident; + qglArrayElement = dllArrayElement = glArrayElement; + qglBegin = dllBegin = glBegin; + qglBindTexture = dllBindTexture = glBindTexture; + qglBitmap = dllBitmap = glBitmap; + qglBlendFunc = dllBlendFunc = glBlendFunc; + qglCallList = dllCallList = glCallList; + qglCallLists = dllCallLists = glCallLists; + qglClear = dllClear = glClear; + qglClearAccum = dllClearAccum = glClearAccum; + qglClearColor = dllClearColor = glClearColor; + qglClearDepth = dllClearDepth = glClearDepth; + qglClearIndex = dllClearIndex = glClearIndex; + qglClearStencil = dllClearStencil = glClearStencil; + qglClipPlane = dllClipPlane = glClipPlane; + qglColor3b = dllColor3b = glColor3b; + qglColor3bv = dllColor3bv = glColor3bv; + qglColor3d = dllColor3d = glColor3d; + qglColor3dv = dllColor3dv = glColor3dv; + qglColor3f = dllColor3f = glColor3f; + qglColor3fv = dllColor3fv = glColor3fv; + qglColor3i = dllColor3i = glColor3i; + qglColor3iv = dllColor3iv = glColor3iv; + qglColor3s = dllColor3s = glColor3s; + qglColor3sv = dllColor3sv = glColor3sv; + qglColor3ub = dllColor3ub = glColor3ub; + qglColor3ubv = dllColor3ubv = glColor3ubv; + qglColor3ui = dllColor3ui = glColor3ui; + qglColor3uiv = dllColor3uiv = glColor3uiv; + qglColor3us = dllColor3us = glColor3us; + qglColor3usv = dllColor3usv = glColor3usv; + qglColor4b = dllColor4b = glColor4b; + qglColor4bv = dllColor4bv = glColor4bv; + qglColor4d = dllColor4d = glColor4d; + qglColor4dv = dllColor4dv = glColor4dv; + qglColor4f = dllColor4f = glColor4f; + qglColor4fv = dllColor4fv = glColor4fv; + qglColor4i = dllColor4i = glColor4i; + qglColor4iv = dllColor4iv = glColor4iv; + qglColor4s = dllColor4s = glColor4s; + qglColor4sv = dllColor4sv = glColor4sv; + qglColor4ub = dllColor4ub = glColor4ub; + qglColor4ubv = dllColor4ubv = glColor4ubv; + qglColor4ui = dllColor4ui = glColor4ui; + qglColor4uiv = dllColor4uiv = glColor4uiv; + qglColor4us = dllColor4us = glColor4us; + qglColor4usv = dllColor4usv = glColor4usv; + qglColorMask = dllColorMask = glColorMask; + qglColorMaterial = dllColorMaterial = glColorMaterial; + qglColorPointer = dllColorPointer = glColorPointer; + qglCopyPixels = dllCopyPixels = glCopyPixels; + qglCopyTexImage1D = dllCopyTexImage1D = glCopyTexImage1D; + qglCopyTexImage2D = dllCopyTexImage2D = glCopyTexImage2D; + qglCopyTexSubImage1D = dllCopyTexSubImage1D = glCopyTexSubImage1D; + qglCopyTexSubImage2D = dllCopyTexSubImage2D = glCopyTexSubImage2D; + qglCullFace = dllCullFace = glCullFace; + qglDeleteLists = dllDeleteLists = glDeleteLists; + qglDeleteTextures = dllDeleteTextures = glDeleteTextures; + qglDepthFunc = dllDepthFunc = glDepthFunc; + qglDepthMask = dllDepthMask = glDepthMask; + qglDepthRange = dllDepthRange = glDepthRange; + qglDisable = dllDisable = glDisable; + qglDisableClientState = dllDisableClientState = glDisableClientState; + qglDrawArrays = dllDrawArrays = glDrawArrays; + qglDrawBuffer = dllDrawBuffer = glDrawBuffer; + qglDrawElements = dllDrawElements = glDrawElements; + qglDrawPixels = dllDrawPixels = glDrawPixels; + qglEdgeFlag = dllEdgeFlag = glEdgeFlag; + qglEdgeFlagPointer = dllEdgeFlagPointer = glEdgeFlagPointer; + qglEdgeFlagv = dllEdgeFlagv = glEdgeFlagv; + qglEnable = dllEnable = glEnable; + qglEnableClientState = dllEnableClientState = glEnableClientState; + qglEnd = dllEnd = glEnd; + qglEndList = dllEndList = glEndList; + qglEvalCoord1d = dllEvalCoord1d = glEvalCoord1d; + qglEvalCoord1dv = dllEvalCoord1dv = glEvalCoord1dv; + qglEvalCoord1f = dllEvalCoord1f = glEvalCoord1f; + qglEvalCoord1fv = dllEvalCoord1fv = glEvalCoord1fv; + qglEvalCoord2d = dllEvalCoord2d = glEvalCoord2d; + qglEvalCoord2dv = dllEvalCoord2dv = glEvalCoord2dv; + qglEvalCoord2f = dllEvalCoord2f = glEvalCoord2f; + qglEvalCoord2fv = dllEvalCoord2fv = glEvalCoord2fv; + qglEvalMesh1 = dllEvalMesh1 = glEvalMesh1; + qglEvalMesh2 = dllEvalMesh2 = glEvalMesh2; + qglEvalPoint1 = dllEvalPoint1 = glEvalPoint1; + qglEvalPoint2 = dllEvalPoint2 = glEvalPoint2; + qglFeedbackBuffer = dllFeedbackBuffer = glFeedbackBuffer; + qglFinish = dllFinish = glFinish; + qglFlush = dllFlush = glFlush; + qglFogf = dllFogf = glFogf; + qglFogfv = dllFogfv = glFogfv; + qglFogi = dllFogi = glFogi; + qglFogiv = dllFogiv = glFogiv; + qglFrontFace = dllFrontFace = glFrontFace; + qglFrustum = dllFrustum = glFrustum; + qglGenLists = dllGenLists = ( GLuint (__stdcall * )(int) ) glGenLists; + qglGenTextures = dllGenTextures = glGenTextures; + qglGetBooleanv = dllGetBooleanv = glGetBooleanv; + qglGetClipPlane = dllGetClipPlane = glGetClipPlane; + qglGetDoublev = dllGetDoublev = glGetDoublev; + qglGetError = dllGetError = ( GLenum (__stdcall * )(void) ) glGetError; + qglGetFloatv = dllGetFloatv = glGetFloatv; + qglGetIntegerv = dllGetIntegerv = glGetIntegerv; + qglGetLightfv = dllGetLightfv = glGetLightfv; + qglGetLightiv = dllGetLightiv = glGetLightiv; + qglGetMapdv = dllGetMapdv = glGetMapdv; + qglGetMapfv = dllGetMapfv = glGetMapfv; + qglGetMapiv = dllGetMapiv = glGetMapiv; + qglGetMaterialfv = dllGetMaterialfv = glGetMaterialfv; + qglGetMaterialiv = dllGetMaterialiv = glGetMaterialiv; + qglGetPixelMapfv = dllGetPixelMapfv = glGetPixelMapfv; + qglGetPixelMapuiv = dllGetPixelMapuiv = glGetPixelMapuiv; + qglGetPixelMapusv = dllGetPixelMapusv = glGetPixelMapusv; + qglGetPointerv = dllGetPointerv = glGetPointerv; + qglGetPolygonStipple = dllGetPolygonStipple = glGetPolygonStipple; + qglGetString = dllGetString = glGetString; + qglGetTexEnvfv = dllGetTexEnvfv = glGetTexEnvfv; + qglGetTexEnviv = dllGetTexEnviv = glGetTexEnviv; + qglGetTexGendv = dllGetTexGendv = glGetTexGendv; + qglGetTexGenfv = dllGetTexGenfv = glGetTexGenfv; + qglGetTexGeniv = dllGetTexGeniv = glGetTexGeniv; + qglGetTexImage = dllGetTexImage = glGetTexImage; + qglGetTexLevelParameterfv = dllGetTexLevelParameterfv = glGetTexLevelParameterfv; + qglGetTexLevelParameteriv = dllGetTexLevelParameteriv = glGetTexLevelParameteriv; + qglGetTexParameterfv = dllGetTexParameterfv = glGetTexParameterfv; + qglGetTexParameteriv = dllGetTexParameteriv = glGetTexParameteriv; + qglHint = dllHint = glHint; + qglIndexMask = dllIndexMask = glIndexMask; + qglIndexPointer = dllIndexPointer = glIndexPointer; + qglIndexd = dllIndexd = glIndexd; + qglIndexdv = dllIndexdv = glIndexdv; + qglIndexf = dllIndexf = glIndexf; + qglIndexfv = dllIndexfv = glIndexfv; + qglIndexi = dllIndexi = glIndexi; + qglIndexiv = dllIndexiv = glIndexiv; + qglIndexs = dllIndexs = glIndexs; + qglIndexsv = dllIndexsv = glIndexsv; + qglIndexub = dllIndexub = glIndexub; + qglIndexubv = dllIndexubv = glIndexubv; + qglInitNames = dllInitNames = glInitNames; + qglInterleavedArrays = dllInterleavedArrays = glInterleavedArrays; + qglIsEnabled = dllIsEnabled = glIsEnabled; + qglIsList = dllIsList = glIsList; + qglIsTexture = dllIsTexture = glIsTexture; + qglLightModelf = dllLightModelf = glLightModelf; + qglLightModelfv = dllLightModelfv = glLightModelfv; + qglLightModeli = dllLightModeli = glLightModeli; + qglLightModeliv = dllLightModeliv = glLightModeliv; + qglLightf = dllLightf = glLightf; + qglLightfv = dllLightfv = glLightfv; + qglLighti = dllLighti = glLighti; + qglLightiv = dllLightiv = glLightiv; + qglLineStipple = dllLineStipple = glLineStipple; + qglLineWidth = dllLineWidth = glLineWidth; + qglListBase = dllListBase = glListBase; + qglLoadIdentity = dllLoadIdentity = glLoadIdentity; + qglLoadMatrixd = dllLoadMatrixd = glLoadMatrixd; + qglLoadMatrixf = dllLoadMatrixf = glLoadMatrixf; + qglLoadName = dllLoadName = glLoadName; + qglLogicOp = dllLogicOp = glLogicOp; + qglMap1d = dllMap1d = glMap1d; + qglMap1f = dllMap1f = glMap1f; + qglMap2d = dllMap2d = glMap2d; + qglMap2f = dllMap2f = glMap2f; + qglMapGrid1d = dllMapGrid1d = glMapGrid1d; + qglMapGrid1f = dllMapGrid1f = glMapGrid1f; + qglMapGrid2d = dllMapGrid2d = glMapGrid2d; + qglMapGrid2f = dllMapGrid2f = glMapGrid2f; + qglMaterialf = dllMaterialf = glMaterialf; + qglMaterialfv = dllMaterialfv = glMaterialfv; + qglMateriali = dllMateriali = glMateriali; + qglMaterialiv = dllMaterialiv = glMaterialiv; + qglMatrixMode = dllMatrixMode = glMatrixMode; + qglMultMatrixd = dllMultMatrixd = glMultMatrixd; + qglMultMatrixf = dllMultMatrixf = glMultMatrixf; + qglNewList = dllNewList = glNewList; + qglNormal3b = dllNormal3b = glNormal3b; + qglNormal3bv = dllNormal3bv = glNormal3bv; + qglNormal3d = dllNormal3d = glNormal3d; + qglNormal3dv = dllNormal3dv = glNormal3dv; + qglNormal3f = dllNormal3f = glNormal3f; + qglNormal3fv = dllNormal3fv = glNormal3fv; + qglNormal3i = dllNormal3i = glNormal3i; + qglNormal3iv = dllNormal3iv = glNormal3iv; + qglNormal3s = dllNormal3s = glNormal3s; + qglNormal3sv = dllNormal3sv = glNormal3sv; + qglNormalPointer = dllNormalPointer = glNormalPointer; + qglOrtho = dllOrtho = glOrtho; + qglPassThrough = dllPassThrough = glPassThrough; + qglPixelMapfv = dllPixelMapfv = glPixelMapfv; + qglPixelMapuiv = dllPixelMapuiv = glPixelMapuiv; + qglPixelMapusv = dllPixelMapusv = glPixelMapusv; + qglPixelStoref = dllPixelStoref = glPixelStoref; + qglPixelStorei = dllPixelStorei = glPixelStorei; + qglPixelTransferf = dllPixelTransferf = glPixelTransferf; + qglPixelTransferi = dllPixelTransferi = glPixelTransferi; + qglPixelZoom = dllPixelZoom = glPixelZoom; + qglPointSize = dllPointSize = glPointSize; + qglPolygonMode = dllPolygonMode = glPolygonMode; + qglPolygonOffset = dllPolygonOffset = glPolygonOffset; + qglPolygonStipple = dllPolygonStipple = glPolygonStipple; + qglPopAttrib = dllPopAttrib = glPopAttrib; + qglPopClientAttrib = dllPopClientAttrib = glPopClientAttrib; + qglPopMatrix = dllPopMatrix = glPopMatrix; + qglPopName = dllPopName = glPopName; + qglPrioritizeTextures = dllPrioritizeTextures = glPrioritizeTextures; + qglPushAttrib = dllPushAttrib = glPushAttrib; + qglPushClientAttrib = dllPushClientAttrib = glPushClientAttrib; + qglPushMatrix = dllPushMatrix = glPushMatrix; + qglPushName = dllPushName = glPushName; + qglRasterPos2d = dllRasterPos2d = glRasterPos2d; + qglRasterPos2dv = dllRasterPos2dv = glRasterPos2dv; + qglRasterPos2f = dllRasterPos2f = glRasterPos2f; + qglRasterPos2fv = dllRasterPos2fv = glRasterPos2fv; + qglRasterPos2i = dllRasterPos2i = glRasterPos2i; + qglRasterPos2iv = dllRasterPos2iv = glRasterPos2iv; + qglRasterPos2s = dllRasterPos2s = glRasterPos2s; + qglRasterPos2sv = dllRasterPos2sv = glRasterPos2sv; + qglRasterPos3d = dllRasterPos3d = glRasterPos3d; + qglRasterPos3dv = dllRasterPos3dv = glRasterPos3dv; + qglRasterPos3f = dllRasterPos3f = glRasterPos3f; + qglRasterPos3fv = dllRasterPos3fv = glRasterPos3fv; + qglRasterPos3i = dllRasterPos3i = glRasterPos3i; + qglRasterPos3iv = dllRasterPos3iv = glRasterPos3iv; + qglRasterPos3s = dllRasterPos3s = glRasterPos3s; + qglRasterPos3sv = dllRasterPos3sv = glRasterPos3sv; + qglRasterPos4d = dllRasterPos4d = glRasterPos4d; + qglRasterPos4dv = dllRasterPos4dv = glRasterPos4dv; + qglRasterPos4f = dllRasterPos4f = glRasterPos4f; + qglRasterPos4fv = dllRasterPos4fv = glRasterPos4fv; + qglRasterPos4i = dllRasterPos4i = glRasterPos4i; + qglRasterPos4iv = dllRasterPos4iv = glRasterPos4iv; + qglRasterPos4s = dllRasterPos4s = glRasterPos4s; + qglRasterPos4sv = dllRasterPos4sv = glRasterPos4sv; + qglReadBuffer = dllReadBuffer = glReadBuffer; + qglReadPixels = dllReadPixels = glReadPixels; + qglRectd = dllRectd = glRectd; + qglRectdv = dllRectdv = glRectdv; + qglRectf = dllRectf = glRectf; + qglRectfv = dllRectfv = glRectfv; + qglRecti = dllRecti = glRecti; + qglRectiv = dllRectiv = glRectiv; + qglRects = dllRects = glRects; + qglRectsv = dllRectsv = glRectsv; + qglRenderMode = dllRenderMode = glRenderMode; + qglRotated = dllRotated = glRotated; + qglRotatef = dllRotatef = glRotatef; + qglScaled = dllScaled = glScaled; + qglScalef = dllScalef = glScalef; + qglScissor = dllScissor = glScissor; + qglSelectBuffer = dllSelectBuffer = glSelectBuffer; + qglShadeModel = dllShadeModel = glShadeModel; + qglStencilFunc = dllStencilFunc = glStencilFunc; + qglStencilMask = dllStencilMask = glStencilMask; + qglStencilOp = dllStencilOp = glStencilOp; + qglTexCoord1d = dllTexCoord1d = glTexCoord1d; + qglTexCoord1dv = dllTexCoord1dv = glTexCoord1dv; + qglTexCoord1f = dllTexCoord1f = glTexCoord1f; + qglTexCoord1fv = dllTexCoord1fv = glTexCoord1fv; + qglTexCoord1i = dllTexCoord1i = glTexCoord1i; + qglTexCoord1iv = dllTexCoord1iv = glTexCoord1iv; + qglTexCoord1s = dllTexCoord1s = glTexCoord1s; + qglTexCoord1sv = dllTexCoord1sv = glTexCoord1sv; + qglTexCoord2d = dllTexCoord2d = glTexCoord2d; + qglTexCoord2dv = dllTexCoord2dv = glTexCoord2dv; + qglTexCoord2f = dllTexCoord2f = glTexCoord2f; + qglTexCoord2fv = dllTexCoord2fv = glTexCoord2fv; + qglTexCoord2i = dllTexCoord2i = glTexCoord2i; + qglTexCoord2iv = dllTexCoord2iv = glTexCoord2iv; + qglTexCoord2s = dllTexCoord2s = glTexCoord2s; + qglTexCoord2sv = dllTexCoord2sv = glTexCoord2sv; + qglTexCoord3d = dllTexCoord3d = glTexCoord3d; + qglTexCoord3dv = dllTexCoord3dv = glTexCoord3dv; + qglTexCoord3f = dllTexCoord3f = glTexCoord3f; + qglTexCoord3fv = dllTexCoord3fv = glTexCoord3fv; + qglTexCoord3i = dllTexCoord3i = glTexCoord3i; + qglTexCoord3iv = dllTexCoord3iv = glTexCoord3iv; + qglTexCoord3s = dllTexCoord3s = glTexCoord3s; + qglTexCoord3sv = dllTexCoord3sv = glTexCoord3sv; + qglTexCoord4d = dllTexCoord4d = glTexCoord4d; + qglTexCoord4dv = dllTexCoord4dv = glTexCoord4dv; + qglTexCoord4f = dllTexCoord4f = glTexCoord4f; + qglTexCoord4fv = dllTexCoord4fv = glTexCoord4fv; + qglTexCoord4i = dllTexCoord4i = glTexCoord4i; + qglTexCoord4iv = dllTexCoord4iv = glTexCoord4iv; + qglTexCoord4s = dllTexCoord4s = glTexCoord4s; + qglTexCoord4sv = dllTexCoord4sv = glTexCoord4sv; + qglTexCoordPointer = dllTexCoordPointer = glTexCoordPointer; + qglTexEnvf = dllTexEnvf = glTexEnvf; + qglTexEnvfv = dllTexEnvfv = glTexEnvfv; + qglTexEnvi = dllTexEnvi = glTexEnvi; + qglTexEnviv = dllTexEnviv = glTexEnviv; + qglTexGend = dllTexGend = glTexGend; + qglTexGendv = dllTexGendv = glTexGendv; + qglTexGenf = dllTexGenf = glTexGenf; + qglTexGenfv = dllTexGenfv = glTexGenfv; + qglTexGeni = dllTexGeni = glTexGeni; + qglTexGeniv = dllTexGeniv = glTexGeniv; + qglTexImage1D = dllTexImage1D = glTexImage1D; + qglTexImage2D = dllTexImage2D = glTexImage2D; + qglTexParameterf = dllTexParameterf = glTexParameterf; + qglTexParameterfv = dllTexParameterfv = glTexParameterfv; + qglTexParameteri = dllTexParameteri = glTexParameteri; + qglTexParameteriv = dllTexParameteriv = glTexParameteriv; + qglTexSubImage1D = dllTexSubImage1D = glTexSubImage1D; + qglTexSubImage2D = dllTexSubImage2D = glTexSubImage2D; + qglTranslated = dllTranslated = glTranslated; + qglTranslatef = dllTranslatef = glTranslatef; + qglVertex2d = dllVertex2d = glVertex2d; + qglVertex2dv = dllVertex2dv = glVertex2dv; + qglVertex2f = dllVertex2f = glVertex2f; + qglVertex2fv = dllVertex2fv = glVertex2fv; + qglVertex2i = dllVertex2i = glVertex2i; + qglVertex2iv = dllVertex2iv = glVertex2iv; + qglVertex2s = dllVertex2s = glVertex2s; + qglVertex2sv = dllVertex2sv = glVertex2sv; + qglVertex3d = dllVertex3d = glVertex3d; + qglVertex3dv = dllVertex3dv = glVertex3dv; + qglVertex3f = dllVertex3f = glVertex3f; + qglVertex3fv = dllVertex3fv = glVertex3fv; + qglVertex3i = dllVertex3i = glVertex3i; + qglVertex3iv = dllVertex3iv = glVertex3iv; + qglVertex3s = dllVertex3s = glVertex3s; + qglVertex3sv = dllVertex3sv = glVertex3sv; + qglVertex4d = dllVertex4d = glVertex4d; + qglVertex4dv = dllVertex4dv = glVertex4dv; + qglVertex4f = dllVertex4f = glVertex4f; + qglVertex4fv = dllVertex4fv = glVertex4fv; + qglVertex4i = dllVertex4i = glVertex4i; + qglVertex4iv = dllVertex4iv = glVertex4iv; + qglVertex4s = dllVertex4s = glVertex4s; + qglVertex4sv = dllVertex4sv = glVertex4sv; + qglVertexPointer = dllVertexPointer = glVertexPointer; + qglViewport = dllViewport = glViewport; + + qwglCopyContext = wglCopyContext; + qwglCreateContext = wglCreateContext; + qwglCreateLayerContext = wglCreateLayerContext; + qwglDeleteContext = wglDeleteContext; + qwglDescribeLayerPlane = wglDescribeLayerPlane; + qwglGetCurrentContext = wglGetCurrentContext; + qwglGetCurrentDC = wglGetCurrentDC; + qwglGetLayerPaletteEntries = wglGetLayerPaletteEntries; + qwglGetProcAddress = wglGetProcAddress; + qwglMakeCurrent = wglMakeCurrent; + qwglRealizeLayerPalette = wglRealizeLayerPalette; + qwglSetLayerPaletteEntries = wglSetLayerPaletteEntries; + qwglShareLists = wglShareLists; + qwglSwapLayerBuffers = wglSwapLayerBuffers; + qwglUseFontBitmaps = wglUseFontBitmapsA; + qwglUseFontOutlines = wglUseFontOutlinesA; + + qwglChoosePixelFormat = ChoosePixelFormat; + qwglDescribePixelFormat = DescribePixelFormat; + qwglGetPixelFormat = GetPixelFormat; + qwglSetPixelFormat = SetPixelFormat; + qwglSwapBuffers = SwapBuffers; + + return true; +} + +/* +================== +GLimp_EnableLogging +================== +*/ +void GLimp_EnableLogging( bool enable ) { + +} diff --git a/neo/sys/win32/win_savegame.cpp b/neo/sys/win32/win_savegame.cpp new file mode 100644 index 00000000..3b5d3829 --- /dev/null +++ b/neo/sys/win32/win_savegame.cpp @@ -0,0 +1,698 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../sys_session_local.h" +#include "../sys_savegame.h" + +idCVar savegame_winInduceDelay( "savegame_winInduceDelay", "0", CVAR_INTEGER, "on windows, this is a delay induced before any file operation occurs" ); +extern idCVar fs_savepath; +extern idCVar saveGame_checksum; +extern idCVar savegame_error; + +#define SAVEGAME_SENTINAL 0x12358932 + +/* +======================== +void Sys_ExecuteSavegameCommandAsync +======================== +*/ +void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms ) { + assert( savegameParms != NULL ); + + session->GetSaveGameManager().GetSaveGameThread().data.saveLoadParms = savegameParms; + + if ( session->GetSaveGameManager().GetSaveGameThread().GetThreadHandle() == 0 ) { + session->GetSaveGameManager().GetSaveGameThread().StartWorkerThread( "Savegame", CORE_ANY ); + } + + session->GetSaveGameManager().GetSaveGameThread().SignalWork(); +} + +/* +======================== +idLocalUser * GetLocalUserFromUserId +======================== +*/ +idLocalUserWin * GetLocalUserFromSaveParms( const saveGameThreadArgs_t & data ) { + if ( ( data.saveLoadParms != NULL) && ( data.saveLoadParms->inputDeviceId >= 0 ) ) { + idLocalUser * user = session->GetSignInManager().GetLocalUserByInputDevice( data.saveLoadParms->inputDeviceId ); + if ( user != NULL ) { + idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user ); + if ( userWin != NULL && data.saveLoadParms->userId == idStr::Hash( userWin->GetGamerTag() ) ) { + return userWin; + } + } + } + + return NULL; +} + +/* +======================== +idSaveGameThread::SaveGame +======================== +*/ +int idSaveGameThread::Save() { + idLocalUserWin * user = GetLocalUserFromSaveParms( data ); + if ( user == NULL ) { + data.saveLoadParms->errorCode = SAVEGAME_E_INVALID_USER; + return -1; + } + + idSaveLoadParms * callback = data.saveLoadParms; + idStr saveFolder = "savegame"; + + saveFolder.AppendPath( callback->directory ); + + // Check for the required storage space. + int64 requiredSizeBytes = 0; + { + for ( int i = 0; i < callback->files.Num(); i++ ) { + idFile_SaveGame * file = callback->files[i]; + requiredSizeBytes += ( file->Length() + sizeof( unsigned int ) ); // uint for checksum + if ( file->type == SAVEGAMEFILE_PIPELINED ) { + requiredSizeBytes += MIN_SAVEGAME_SIZE_BYTES; + } + } + } + + int ret = ERROR_SUCCESS; + + // Check size of previous files if needed + // ALL THE FILES RIGHT NOW---- could use pattern later... + idStrList filesToDelete; + if ( ( callback->mode & SAVEGAME_MBF_DELETE_FILES ) && !callback->cancelled ) { + if ( fileSystem->IsFolder( saveFolder.c_str(), "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFilesTree( saveFolder.c_str(), "*.*" ); + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + requiredSizeBytes -= fileSystem->GetFileLength( files->GetFile( i ) ); + filesToDelete.Append( files->GetFile( i ) ); + } + fileSystem->FreeFileList( files ); + } + } + + // Inform user about size required if necessary + if ( requiredSizeBytes > 0 && !callback->cancelled ) { + user->StorageSizeAvailable( requiredSizeBytes, callback->requiredSpaceInBytes ); + if ( callback->requiredSpaceInBytes > 0 ) { + // check to make sure savepath actually exists before erroring + idStr directory = fs_savepath.GetString(); + directory += "\\"; // so it doesn't think the last part is a file and ignores in the directory creation + fileSystem->CreateOSPath( directory ); // we can't actually check FileExists in production builds, so just try to create it + user->StorageSizeAvailable( requiredSizeBytes, callback->requiredSpaceInBytes ); + + if ( callback->requiredSpaceInBytes > 0 ) { + callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; + // safe to return, haven't written any files yet + return -1; + } + } + } + + // Delete all previous files if needed + // ALL THE FILES RIGHT NOW---- could use pattern later... + for ( int i = 0; i < filesToDelete.Num() && !callback->cancelled; i++ ) { + fileSystem->RemoveFile( filesToDelete[i].c_str() ); + } + + // Save the raw files. + for ( int i = 0; i < callback->files.Num() && ret == ERROR_SUCCESS && !callback->cancelled; i++ ) { + idFile_SaveGame * file = callback->files[i]; + + idStr fileName = saveFolder; + fileName.AppendPath( file->GetName() ); + idStr tempFileName = va( "%s.temp", fileName.c_str() ); + + idFile * outputFile = fileSystem->OpenFileWrite( tempFileName, "fs_savePath" ); + if ( outputFile == NULL ) { + idLib::Warning( "[%s]: Couldn't open file for writing, %s. Error = %08x", __FUNCTION__, tempFileName.c_str(), GetLastError() ); + file->error = true; + callback->errorCode = SAVEGAME_E_UNKNOWN; + ret = -1; + continue; + } + + if ( ( file->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { + + idFile_SaveGamePipelined * inputFile = dynamic_cast< idFile_SaveGamePipelined * >( file ); + assert( inputFile != NULL ); + + blockForIO_t block; + while ( inputFile->NextWriteBlock( & block ) ) { + if ( (size_t)outputFile->Write( block.data, block.bytes ) != block.bytes ) { + idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); + file->error = true; + callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; + ret = -1; + break; + } + } + + } else { + + if ( ( file->type & SAVEGAMEFILE_BINARY ) || ( file->type & SAVEGAMEFILE_COMPRESSED ) ) { + if ( saveGame_checksum.GetBool() ) { + unsigned int checksum = MD5_BlockChecksum( file->GetDataPtr(), file->Length() ); + size_t size = outputFile->WriteBig( checksum ); + if ( size != sizeof( checksum ) ) { + idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); + file->error = true; + callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; + ret = -1; + } + } + } + + size_t size = outputFile->Write( file->GetDataPtr(), file->Length() ); + if ( size != (size_t)file->Length() ) { + idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); + file->error = true; + callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; + ret = -1; + } else { + idLib::PrintfIf( saveGame_verbose.GetBool(), "Saved %s (%s)\n", fileName.c_str(), outputFile->GetFullPath() ); + } + } + + delete outputFile; + + if ( ret == ERROR_SUCCESS ) { + // Remove the old file + if ( !fileSystem->RenameFile( tempFileName, fileName, "fs_savePath" ) ) { + idLib::Warning( "Could not start to rename temporary file %s to %s.", tempFileName.c_str(), fileName.c_str() ); + } + } else { + fileSystem->RemoveFile( tempFileName ); + idLib::Warning( "Invalid write to temporary file %s.", tempFileName.c_str() ); + } + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + // Removed because it seemed a bit drastic +#if 0 + // If there is an error, delete the partially saved folder + if ( callback->errorCode != SAVEGAME_E_NONE ) { + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + fileSystem->RemoveFile( files->GetFile( i ) ); + } + fileSystem->FreeFileList( files ); + fileSystem->RemoveDir( saveFolder ); + } + } +#endif + + return ret; +} + +/* +======================== +idSessionLocal::LoadGame +======================== +*/ +int idSaveGameThread::Load() { + idSaveLoadParms * callback = data.saveLoadParms; + idStr saveFolder = "savegame"; + + saveFolder.AppendPath( callback->directory ); + + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) != FOLDER_YES ) { + callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; + return -1; + } + + int ret = ERROR_SUCCESS; + for ( int i = 0; i < callback->files.Num() && ret == ERROR_SUCCESS && !callback->cancelled; i++ ) { + idFile_SaveGame * file = callback->files[i]; + + idStr filename = saveFolder; + filename.AppendPath( file->GetName() ); + + idFile * inputFile = fileSystem->OpenFileRead( filename.c_str() ); + if ( inputFile == NULL ) { + file->error = true; + if ( !( file->type & SAVEGAMEFILE_OPTIONAL ) ) { + callback->errorCode = SAVEGAME_E_CORRUPTED; + ret = -1; + } + continue; + } + + if ( ( file->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { + + idFile_SaveGamePipelined * outputFile = dynamic_cast< idFile_SaveGamePipelined * >( file ); + assert( outputFile != NULL ); + + size_t lastReadBytes = 0; + blockForIO_t block; + while ( outputFile->NextReadBlock( &block, lastReadBytes ) && !callback->cancelled ) { + lastReadBytes = inputFile->Read( block.data, block.bytes ); + if ( lastReadBytes != block.bytes ) { + // Notify end-of-file to the save game file which will cause all reads on the + // other end of the pipeline to return zero bytes after the pipeline is drained. + outputFile->NextReadBlock( NULL, lastReadBytes ); + break; + } + } + + } else { + + size_t size = inputFile->Length(); + + unsigned int originalChecksum = 0; + if ( ( file->type & SAVEGAMEFILE_BINARY ) != 0 || ( file->type & SAVEGAMEFILE_COMPRESSED ) != 0 ) { + if ( saveGame_checksum.GetBool() ) { + if ( size >= sizeof( originalChecksum ) ) { + inputFile->ReadBig( originalChecksum ); + size -= sizeof( originalChecksum ); + } + } + } + + file->SetLength( size ); + + size_t sizeRead = inputFile->Read( (void *)file->GetDataPtr(), size ); + if ( sizeRead != size ) { + file->error = true; + callback->errorCode = SAVEGAME_E_CORRUPTED; + ret = -1; + } + + if ( ( file->type & SAVEGAMEFILE_BINARY ) != 0 || ( file->type & SAVEGAMEFILE_COMPRESSED ) != 0 ) { + if ( saveGame_checksum.GetBool() ) { + unsigned int checksum = MD5_BlockChecksum( file->GetDataPtr(), file->Length() ); + if ( checksum != originalChecksum ) { + file->error = true; + callback->errorCode = SAVEGAME_E_CORRUPTED; + ret = -1; + } + } + } + } + + delete inputFile; + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + return ret; +} + +/* +======================== +idSaveGameThread::Delete + +This deletes a complete savegame directory +======================== +*/ +int idSaveGameThread::Delete() { + idSaveLoadParms * callback = data.saveLoadParms; + idStr saveFolder = "savegame"; + + saveFolder.AppendPath( callback->directory ); + + int ret = ERROR_SUCCESS; + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); + for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) { + fileSystem->RemoveFile( files->GetFile( i ) ); + } + fileSystem->FreeFileList( files ); + + fileSystem->RemoveDir( saveFolder ); + } else { + callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; + ret = -1; + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + return ret; +} + +/* +======================== +idSaveGameThread::Enumerate +======================== +*/ +int idSaveGameThread::Enumerate() { + idSaveLoadParms * callback = data.saveLoadParms; + idStr saveFolder = "savegame"; + + callback->detailList.Clear(); + + int ret = ERROR_SUCCESS; + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFilesTree( saveFolder, SAVEGAME_DETAILS_FILENAME ); + const idStrList & fileList = files->GetList(); + + for ( int i = 0; i < fileList.Num() && !callback->cancelled; i++ ) { + idSaveGameDetails * details = callback->detailList.Alloc(); + // We have more folders on disk than we have room in our save detail list, stop trying to read them in and continue with what we have + if ( details == NULL ) { + break; + } + idStr directory = fileList[i]; + + idFile * file = fileSystem->OpenFileRead( directory.c_str() ); + + if ( file != NULL ) { + // Read the DETAIL file for the enumerated data + if ( callback->mode & SAVEGAME_MBF_READ_DETAILS ) { + if ( !SavegameReadDetailsFromFile( file, *details ) ) { + details->damaged = true; + ret = -1; + } + } + + // Use the date from the directory + WIN32_FILE_ATTRIBUTE_DATA attrData; + BOOL attrRet = GetFileAttributesEx( file->GetFullPath(), GetFileExInfoStandard, &attrData ); + delete file; + if ( attrRet == TRUE ) { + FILETIME lastWriteTime = attrData.ftLastWriteTime; + const ULONGLONG second = 10000000L; // One second = 10,000,000 * 100 nsec + SYSTEMTIME base_st = { 1970, 1, 0, 1, 0, 0, 0, 0 }; + ULARGE_INTEGER itime; + FILETIME base_ft; + BOOL success = SystemTimeToFileTime( &base_st, &base_ft ); + + itime.QuadPart = ((ULARGE_INTEGER *)&lastWriteTime)->QuadPart; + if ( success ) { + itime.QuadPart -= ((ULARGE_INTEGER *)&base_ft)->QuadPart; + } else { + // Hard coded number of 100-nanosecond units from 1/1/1601 to 1/1/1970 + itime.QuadPart -= 116444736000000000LL; + } + itime.QuadPart /= second; + details->date = itime.QuadPart; + } + } else { + details->damaged = true; + } + + // populate the game details struct + directory = directory.StripFilename(); + details->slotName = directory.c_str() + saveFolder.Length() + 1; // Strip off the prefix too +// JDC: I hit this all the time assert( fileSystem->IsFolder( directory.c_str(), "fs_savePath" ) == FOLDER_YES ); + } + fileSystem->FreeFileList( files ); + } else { + callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; + ret = -3; + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + return ret; +} + +/* +======================== +idSaveGameThread::EnumerateFiles +======================== +*/ +int idSaveGameThread::EnumerateFiles() { + idSaveLoadParms * callback = data.saveLoadParms; + idStr folder = "savegame"; + + folder.AppendPath( callback->directory ); + + callback->files.Clear(); + + int ret = ERROR_SUCCESS; + if ( fileSystem->IsFolder( folder, "fs_savePath" ) == FOLDER_YES ) { + // get listing of all the files, but filter out below + idFileList * files = fileSystem->ListFilesTree( folder, "*.*" ); + + // look for the instance pattern + for ( int i = 0; i < files->GetNumFiles() && ret == 0 && !callback->cancelled; i++ ) { + idStr fullFilename = files->GetFile( i ); + idStr filename = fullFilename; + filename.StripPath(); + + if ( filename.IcmpPrefix( callback->pattern ) != 0 ) { + continue; + } + if ( !callback->postPattern.IsEmpty() && filename.Right( callback->postPattern.Length() ).IcmpPrefix( callback->postPattern ) != 0 ) { + continue; + } + + // Read the DETAIL file for the enumerated data + if ( callback->mode & SAVEGAME_MBF_READ_DETAILS ) { + idSaveGameDetails & details = callback->description; + idFile * uncompressed = fileSystem->OpenFileRead( fullFilename.c_str() ); + + if ( uncompressed == NULL ) { + details.damaged = true; + } else { + if ( !SavegameReadDetailsFromFile( uncompressed, details ) ) { + ret = -1; + } + + delete uncompressed; + } + + // populate the game details struct + details.slotName = callback->directory; + assert( fileSystem->IsFolder( details.slotName, "fs_savePath" ) == FOLDER_YES ); + } + + idFile_SaveGame * file = new (TAG_SAVEGAMES) idFile_SaveGame( filename, SAVEGAMEFILE_AUTO_DELETE ); + callback->files.Append( file ); + } + fileSystem->FreeFileList( files ); + } else { + callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; + ret = -3; + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + return ret; +} + +/* +======================== +idSaveGameThread::DeleteFiles +======================== +*/ +int idSaveGameThread::DeleteFiles() { + idSaveLoadParms * callback = data.saveLoadParms; + idStr folder = "savegame"; + + folder.AppendPath( callback->directory ); + + // delete the explicitly requested files first + for ( int j = 0; j < callback->files.Num() && !callback->cancelled; ++j ) { + idFile_SaveGame * file = callback->files[j]; + idStr fullpath = folder; + fullpath.AppendPath( file->GetName() ); + fileSystem->RemoveFile( fullpath ); + } + + int ret = ERROR_SUCCESS; + if ( fileSystem->IsFolder( folder, "fs_savePath" ) == FOLDER_YES ) { + // get listing of all the files, but filter out below + idFileList * files = fileSystem->ListFilesTree( folder, "*.*" ); + + // look for the instance pattern + for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) { + idStr filename = files->GetFile( i ); + filename.StripPath(); + + // If there are post/pre patterns to match, make sure we adhere to the patterns + if ( callback->pattern.IsEmpty() || ( filename.IcmpPrefix( callback->pattern ) != 0 ) ) { + continue; + } + if ( callback->postPattern.IsEmpty() || ( filename.Right( callback->postPattern.Length() ).IcmpPrefix( callback->postPattern ) != 0 ) ) { + continue; + } + + fileSystem->RemoveFile( files->GetFile( i ) ); + } + fileSystem->FreeFileList( files ); + } else { + callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; + ret = -3; + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + return ret; +} + +/* +======================== +idSaveGameThread::DeleteAll + +This deletes all savegame directories +======================== +*/ +int idSaveGameThread::DeleteAll() { + idSaveLoadParms * callback = data.saveLoadParms; + idStr saveFolder = "savegame"; + int ret = ERROR_SUCCESS; + + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); + // remove directories after files + for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) { + // contained files should always be first + if ( fileSystem->IsFolder( files->GetFile( i ), "fs_savePath" ) == FOLDER_YES ) { + fileSystem->RemoveDir( files->GetFile( i ) ); + } else { + fileSystem->RemoveFile( files->GetFile( i ) ); + } + } + fileSystem->FreeFileList( files ); + } else { + callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; + ret = -3; + } + + if ( data.saveLoadParms->cancelled ) { + data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; + } + + return ret; +} + +/* +======================== +idSaveGameThread::Run +======================== +*/ +int idSaveGameThread::Run() { + int ret = ERROR_SUCCESS; + + try { + idLocalUserWin * user = GetLocalUserFromSaveParms( data ); + if ( user != NULL && !user->IsStorageDeviceAvailable() ) { + data.saveLoadParms->errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE; + } + + if ( savegame_winInduceDelay.GetInteger() > 0 ) { + Sys_Sleep( savegame_winInduceDelay.GetInteger() ); + } + + if ( data.saveLoadParms->mode & SAVEGAME_MBF_SAVE ) { + ret = Save(); + } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_LOAD ) { + ret = Load(); + } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_ENUMERATE ) { + ret = Enumerate(); + } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_FOLDER ) { + ret = Delete(); + } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_ALL_FOLDERS ) { + ret = DeleteAll(); + } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_FILES ) { + ret = DeleteFiles(); + } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_ENUMERATE_FILES ) { + ret = EnumerateFiles(); + } + + // if something failed and no one set an error code, do it now. + if ( ret != 0 && data.saveLoadParms->errorCode == SAVEGAME_E_NONE ) { + data.saveLoadParms->errorCode = SAVEGAME_E_UNKNOWN; + } + } catch ( ... ) { + // if anything horrible happens, leave it up to the savegame processors to handle in PostProcess(). + data.saveLoadParms->errorCode = SAVEGAME_E_UNKNOWN; + } + + // Make sure to cancel any save game file pipelines. + if ( data.saveLoadParms->errorCode != SAVEGAME_E_NONE ) { + data.saveLoadParms->CancelSaveGameFilePipelines(); + } + + // Override error if cvar set + if ( savegame_error.GetInteger() != 0 ) { + data.saveLoadParms->errorCode = (saveGameError_t)savegame_error.GetInteger(); + } + + // Tell the waiting caller that we are done + data.saveLoadParms->callbackSignal.Raise(); + + return ret; +} + +/* +======================== +Sys_SaveGameCheck +======================== +*/ +void Sys_SaveGameCheck( bool & exists, bool & autosaveExists ) { + exists = false; + autosaveExists = false; + + const idStr autosaveFolderStr = AddSaveFolderPrefix( SAVEGAME_AUTOSAVE_FOLDER, idSaveGameManager::PACKAGE_GAME ); + const char * autosaveFolder = autosaveFolderStr.c_str(); + const char * saveFolder = "savegame"; + + if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { + idFileList * files = fileSystem->ListFiles( saveFolder, "/" ); + const idStrList & fileList = files->GetList(); + + idLib::PrintfIf( saveGame_verbose.GetBool(), "found %d savegames\n", fileList.Num() ); + + for ( int i = 0; i < fileList.Num(); i++ ) { + const char * directory = va( "%s/%s", saveFolder, fileList[i].c_str() ); + + if ( fileSystem->IsFolder( directory, "fs_savePath" ) == FOLDER_YES ) { + exists = true; + + idLib::PrintfIf( saveGame_verbose.GetBool(), "found savegame: %s\n", fileList[i].c_str() ); + + if ( idStr::Icmp( fileList[i].c_str(), autosaveFolder ) == 0 ) { + autosaveExists = true; + break; + } + } + } + + fileSystem->FreeFileList( files ); + } +} diff --git a/neo/sys/win32/win_session_local.cpp b/neo/sys/win32/win_session_local.cpp new file mode 100644 index 00000000..7b17e19d --- /dev/null +++ b/neo/sys/win32/win_session_local.cpp @@ -0,0 +1,668 @@ +/* +================================================================================================ +CONFIDENTIAL AND PROPRIETARY INFORMATION/NOT FOR DISCLOSURE WITHOUT WRITTEN PERMISSION +Copyright 2010 id Software LLC, a ZeniMax Media company. All Rights Reserved. +================================================================================================ +*/ + +/* +================================================================================================ + +Contains the windows implementation of the network session + +================================================================================================ +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../../framework/Common_local.h" +#include "../sys_session_local.h" +#include "../sys_stats.h" +#include "../sys_savegame.h" +#include "../sys_lobby_backend_direct.h" +#include "../sys_voicechat.h" +#include "win_achievements.h" +#include "win_local.h" + +/* +======================== +Global variables +======================== +*/ + +extern idCVar net_port; + + +/* +======================== +idSessionLocalWin::idSessionLocalWin +======================== +*/ +class idSessionLocalWin : public idSessionLocal { +friend class idLobbyToSessionCBLocal; + +public: + idSessionLocalWin(); + virtual ~idSessionLocalWin(); + + // idSessionLocal interface + virtual void Initialize(); + virtual void Shutdown(); + + virtual void InitializeSoundRelatedSystems(); + virtual void ShutdownSoundRelatedSystems(); + + virtual void PlatformPump(); + + virtual void InviteFriends(); + virtual void InviteParty(); + virtual void ShowPartySessions(); + + virtual void ShowSystemMarketplaceUI() const; + + virtual void ListServers( const idCallback & callback ); + virtual void CancelListServers(); + virtual int NumServers() const; + virtual const serverInfo_t * ServerInfo( int i ) const; + virtual void ConnectToServer( int i ); + virtual void ShowServerGamerCardUI( int i ); + + virtual void ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ); + + virtual void ShowOnlineSignin() {} + virtual void UpdateRichPresence() {} + virtual void CheckVoicePrivileges() {} + + virtual bool ProcessInputEvent( const sysEvent_t * ev ); + + // System UI + virtual bool IsSystemUIShowing() const; + virtual void SetSystemUIShowing( bool show ); + + // Invites + virtual void HandleBootableInvite( int64 lobbyId = 0 ); + virtual void ClearBootableInvite(); + virtual void ClearPendingInvite(); + + virtual bool HasPendingBootableInvite(); + virtual void SetDiscSwapMPInvite( void * parm ); + virtual void * GetDiscSwapMPInviteParms(); + + virtual void EnumerateDownloadableContent(); + + virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ); + virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ); + + // Leaderboards + virtual void LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment = NULL ); + virtual void LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ); + virtual void LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ); + + // Scoring (currently just for TrueSkill) + virtual void SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) {} + + virtual void LeaderboardFlush(); + + virtual idNetSessionPort & GetPort( bool dedicated = false ); + virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ); + virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ); + virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ); + virtual void DestroyLobbyBackend( idLobbyBackend * lobbyBackend ); + virtual void PumpLobbies(); + virtual void JoinAfterSwap( void * joinID ); + + virtual bool GetLobbyAddressFromNetAddress( const netadr_t & netAddr, lobbyAddress_t & outAddr ) const; + virtual bool GetNetAddressFromLobbyAddress( const lobbyAddress_t & lobbyAddress, netadr_t & outNetAddr ) const; + +public: + void Connect_f( const idCmdArgs &args ); + +private: + void EnsurePort(); + + idLobbyBackend * CreateLobbyInternal( idLobbyBackend::lobbyBackendType_t lobbyType ); + + idArray< idLobbyBackend *, 3 > lobbyBackends; + + idNetSessionPort port; + bool canJoinLocalHost; + + idLobbyToSessionCBLocal * lobbyToSessionCB; +}; + +idSessionLocalWin sessionLocalWin; +idSession * session = &sessionLocalWin; + +/* +======================== +idLobbyToSessionCBLocal +======================== +*/ +class idLobbyToSessionCBLocal : public idLobbyToSessionCB { +public: + idLobbyToSessionCBLocal( idSessionLocalWin * sessionLocalWin_ ) : sessionLocalWin( sessionLocalWin_ ) { } + + virtual bool CanJoinLocalHost() const { sessionLocalWin->EnsurePort(); return sessionLocalWin->canJoinLocalHost; } + virtual class idLobbyBackend * GetLobbyBackend( idLobbyBackend::lobbyBackendType_t type ) const { return sessionLocalWin->lobbyBackends[ type ]; } + +private: + idSessionLocalWin * sessionLocalWin; +}; + +idLobbyToSessionCBLocal lobbyToSessionCBLocal( &sessionLocalWin ); +idLobbyToSessionCB * lobbyToSessionCB = &lobbyToSessionCBLocal; + +class idVoiceChatMgrWin : public idVoiceChatMgr { +public: + virtual bool GetLocalChatDataInternal( int talkerIndex, byte * data, int & dataSize ) { return false; } + virtual void SubmitIncomingChatDataInternal( int talkerIndex, const byte * data, int dataSize ) { } + virtual bool TalkerHasData( int talkerIndex ) { return false; } + virtual bool RegisterTalkerInternal( int index ) { return true; } + virtual void UnregisterTalkerInternal( int index ) { } +}; + +/* +======================== +idSessionLocalWin::idSessionLocalWin +======================== +*/ +idSessionLocalWin::idSessionLocalWin() { + signInManager = new (TAG_SYSTEM) idSignInManagerWin; + saveGameManager = new (TAG_SAVEGAMES) idSaveGameManager(); + voiceChat = new (TAG_SYSTEM) idVoiceChatMgrWin(); + lobbyToSessionCB = new (TAG_SYSTEM) idLobbyToSessionCBLocal( this ); + + canJoinLocalHost = false; + + lobbyBackends.Zero(); +} + +/* +======================== +idSessionLocalWin::idSessionLocalWin +======================== +*/ +idSessionLocalWin::~idSessionLocalWin() { + delete voiceChat; + delete lobbyToSessionCB; +} + +/* +======================== +idSessionLocalWin::Initialize +======================== +*/ +void idSessionLocalWin::Initialize() { + idSessionLocal::Initialize(); + + // The shipping path doesn't load title storage + // Instead, we inject values through code which is protected through steam DRM + titleStorageVars.Set( "MAX_PLAYERS_ALLOWED", "8" ); + titleStorageLoaded = true; + + // First-time check for downloadable content once game is launched + EnumerateDownloadableContent(); + + GetPartyLobby().Initialize( idLobby::TYPE_PARTY, sessionCallbacks ); + GetGameLobby().Initialize( idLobby::TYPE_GAME, sessionCallbacks ); + GetGameStateLobby().Initialize( idLobby::TYPE_GAME_STATE, sessionCallbacks ); + + achievementSystem = new (TAG_SYSTEM) idAchievementSystemWin(); + achievementSystem->Init(); +} + +/* +======================== +idSessionLocalWin::Shutdown +======================== +*/ +void idSessionLocalWin::Shutdown() { + NET_VERBOSE_PRINT( "NET: Shutdown\n" ); + idSessionLocal::Shutdown(); + + MoveToMainMenu(); + + // Wait until we fully shutdown + while ( localState != STATE_IDLE && localState != STATE_PRESS_START ) { + Pump(); + } + + if ( achievementSystem != NULL ) { + achievementSystem->Shutdown(); + delete achievementSystem; + achievementSystem = NULL; + } +} + +/* +======================== +idSessionLocalWin::InitializeSoundRelatedSystems +======================== +*/ +void idSessionLocalWin::InitializeSoundRelatedSystems() { + if ( voiceChat != NULL ) { + voiceChat->Init( NULL ); + } +} + +/* +======================== +idSessionLocalWin::ShutdownSoundRelatedSystems +======================== +*/ +void idSessionLocalWin::ShutdownSoundRelatedSystems() { + if ( voiceChat != NULL ) { + voiceChat->Shutdown(); + } +} + +/* +======================== +idSessionLocalWin::PlatformPump +======================== +*/ +void idSessionLocalWin::PlatformPump() { +} + +/* +======================== +idSessionLocalWin::InviteFriends +======================== +*/ +void idSessionLocalWin::InviteFriends() { +} + +/* +======================== +idSessionLocalWin::InviteParty +======================== +*/ +void idSessionLocalWin::InviteParty() { +} + +/* +======================== +idSessionLocalWin::ShowPartySessions +======================== +*/ +void idSessionLocalWin::ShowPartySessions() { +} + +/* +======================== +idSessionLocalWin::ShowSystemMarketplaceUI +======================== +*/ +void idSessionLocalWin::ShowSystemMarketplaceUI() const { +} + +/* +======================== +idSessionLocalWin::ListServers +======================== +*/ +void idSessionLocalWin::ListServers( const idCallback & callback ) { + ListServersCommon(); +} + +/* +======================== +idSessionLocalWin::CancelListServers +======================== +*/ +void idSessionLocalWin::CancelListServers() { +} + +/* +======================== +idSessionLocalWin::NumServers +======================== +*/ +int idSessionLocalWin::NumServers() const { + return 0; +} + +/* +======================== +idSessionLocalWin::ServerInfo +======================== +*/ +const serverInfo_t * idSessionLocalWin::ServerInfo( int i ) const { + return NULL; +} + +/* +======================== +idSessionLocalWin::ConnectToServer +======================== +*/ +void idSessionLocalWin::ConnectToServer( int i ) { +} + +/* +======================== +idSessionLocalWin::Connect_f +======================== +*/ +void idSessionLocalWin::Connect_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + idLib::Printf( "Usage: Connect to IP. Use with net_port. \n"); + return; + } + + Cancel(); + + if ( signInManager->GetMasterLocalUser() == NULL ) { + signInManager->RegisterLocalUser( 0 ); + } + + lobbyConnectInfo_t connectInfo; + + Sys_StringToNetAdr( args.Argv(1), &connectInfo.netAddr, true ); + connectInfo.netAddr.port = net_port.GetInteger(); + + ConnectAndMoveToLobby( GetPartyLobby(), connectInfo, false ); +} + +/* +======================== +void Connect_f +======================== +*/ +CONSOLE_COMMAND( connect, "Connect to the specified IP", NULL ) { + sessionLocalWin.Connect_f( args ); +} + +/* +======================== +idSessionLocalWin::ShowServerGamerCardUI +======================== +*/ +void idSessionLocalWin::ShowServerGamerCardUI( int i ) { +} + +/* +======================== +idSessionLocalWin::ShowLobbyUserGamerCardUI( +======================== +*/ +void idSessionLocalWin::ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ) { +} + +/* +======================== +idSessionLocalWin::ProcessInputEvent +======================== +*/ +bool idSessionLocalWin::ProcessInputEvent( const sysEvent_t * ev ) { + if ( GetSignInManager().ProcessInputEvent( ev ) ) { + return true; + } + return false; +} + +/* +======================== +idSessionLocalWin::IsSystemUIShowing +======================== +*/ +bool idSessionLocalWin::IsSystemUIShowing() const { + return !win32.activeApp || isSysUIShowing; // If the user alt+tabs away, treat it the same as bringing up the steam overlay +} + +/* +======================== +idSessionLocalWin::SetSystemUIShowing +======================== +*/ +void idSessionLocalWin::SetSystemUIShowing( bool show ) { + isSysUIShowing = show; +} + +/* +======================== +idSessionLocalWin::HandleServerQueryRequest +======================== +*/ +void idSessionLocalWin::HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) { + NET_VERBOSE_PRINT( "HandleServerQueryRequest from %s\n", remoteAddr.ToString() ); +} + +/* +======================== +idSessionLocalWin::HandleServerQueryAck +======================== +*/ +void idSessionLocalWin::HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) { + NET_VERBOSE_PRINT( "HandleServerQueryAck from %s\n", remoteAddr.ToString() ); + +} + +/* +======================== +idSessionLocalWin::ClearBootableInvite +======================== +*/ +void idSessionLocalWin::ClearBootableInvite() { +} + +/* +======================== +idSessionLocalWin::ClearPendingInvite +======================== +*/ +void idSessionLocalWin::ClearPendingInvite() { +} + +/* +======================== +idSessionLocalWin::HandleBootableInvite +======================== +*/ +void idSessionLocalWin::HandleBootableInvite( int64 lobbyId ) { +} + +/* +======================== +idSessionLocalWin::HasPendingBootableInvite +======================== +*/ +bool idSessionLocalWin::HasPendingBootableInvite() { + return false; +} + +/* +======================== +idSessionLocal::SetDiscSwapMPInvite +======================== +*/ +void idSessionLocalWin::SetDiscSwapMPInvite( void * parm ) { +} + +/* +======================== +idSessionLocal::GetDiscSwapMPInviteParms +======================== +*/ +void * idSessionLocalWin::GetDiscSwapMPInviteParms() { + return NULL; +} + +/* +======================== +idSessionLocalWin::EnumerateDownloadableContent +======================== +*/ +void idSessionLocalWin::EnumerateDownloadableContent() { +} + +/* +======================== +idSessionLocalWin::LeaderboardUpload +======================== +*/ +void idSessionLocalWin::LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment ) { +} + +/* +======================== +idSessionLocalWin::LeaderboardFlush +======================== +*/ +void idSessionLocalWin::LeaderboardFlush() { +} + +/* +======================== +idSessionLocalWin::LeaderboardDownload +======================== +*/ +void idSessionLocalWin::LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ) { +} + +/* +======================== +idSessionLocalWin::LeaderboardDownloadAttachment +======================== +*/ +void idSessionLocalWin::LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ) { +} + +/* +======================== +idSessionLocalWin::EnsurePort +======================== +*/ +void idSessionLocalWin::EnsurePort() { + // Init the port using reqular windows sockets + if ( port.IsOpen() ) { + return; // Already initialized + } + + if ( port.InitPort( net_port.GetInteger(), false ) ) { + canJoinLocalHost = false; + } else { + // Assume this is another instantiation on the same machine, and just init using any available port + port.InitPort( PORT_ANY, false ); + canJoinLocalHost = true; + } +} + +/* +======================== +idSessionLocalWin::GetPort +======================== +*/ +idNetSessionPort & idSessionLocalWin::GetPort( bool dedicated ) { + EnsurePort(); + return port; +} + +/* +======================== +idSessionLocalWin::CreateLobbyBackend +======================== +*/ +idLobbyBackend * idSessionLocalWin::CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) { + idLobbyBackend * lobbyBackend = CreateLobbyInternal( lobbyType ); + lobbyBackend->StartHosting( p, skillLevel, lobbyType ); + return lobbyBackend; +} + +/* +======================== +idSessionLocalWin::FindLobbyBackend +======================== +*/ +idLobbyBackend * idSessionLocalWin::FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) { + idLobbyBackend * lobbyBackend = CreateLobbyInternal( lobbyType ); + lobbyBackend->StartFinding( p, numPartyUsers, skillLevel ); + return lobbyBackend; +} + +/* +======================== +idSessionLocalWin::JoinFromConnectInfo +======================== +*/ +idLobbyBackend * idSessionLocalWin::JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo, idLobbyBackend::lobbyBackendType_t lobbyType ) { + idLobbyBackend * lobbyBackend = CreateLobbyInternal( lobbyType ); + lobbyBackend->JoinFromConnectInfo( connectInfo ); + return lobbyBackend; +} + +/* +======================== +idSessionLocalWin::DestroyLobbyBackend +======================== +*/ +void idSessionLocalWin::DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) { + assert( lobbyBackend != NULL ); + assert( lobbyBackends[lobbyBackend->GetLobbyType()] == lobbyBackend ); + + lobbyBackends[lobbyBackend->GetLobbyType()] = NULL; + + lobbyBackend->Shutdown(); + delete lobbyBackend; +} + +/* +======================== +idSessionLocalWin::PumpLobbies +======================== +*/ +void idSessionLocalWin::PumpLobbies() { + assert( lobbyBackends[idLobbyBackend::TYPE_PARTY] == NULL || lobbyBackends[idLobbyBackend::TYPE_PARTY]->GetLobbyType() == idLobbyBackend::TYPE_PARTY ); + assert( lobbyBackends[idLobbyBackend::TYPE_GAME] == NULL || lobbyBackends[idLobbyBackend::TYPE_GAME]->GetLobbyType() == idLobbyBackend::TYPE_GAME ); + assert( lobbyBackends[idLobbyBackend::TYPE_GAME_STATE] == NULL || lobbyBackends[idLobbyBackend::TYPE_GAME_STATE]->GetLobbyType() == idLobbyBackend::TYPE_GAME_STATE ); + + // Pump lobbyBackends + for ( int i = 0; i < lobbyBackends.Num(); i++ ) { + if ( lobbyBackends[i] != NULL ) { + lobbyBackends[i]->Pump(); + } + } +} + +/* +======================== +idSessionLocalWin::CreateLobbyInternal +======================== +*/ +idLobbyBackend * idSessionLocalWin::CreateLobbyInternal( idLobbyBackend::lobbyBackendType_t lobbyType ) { + EnsurePort(); + idLobbyBackend * lobbyBackend = new (TAG_NETWORKING) idLobbyBackendDirect(); + + lobbyBackend->SetLobbyType( lobbyType ); + + assert( lobbyBackends[lobbyType] == NULL ); + lobbyBackends[lobbyType] = lobbyBackend; + + return lobbyBackend; +} + +/* +======================== +idSessionLocalWin::JoinAfterSwap +======================== +*/ +void idSessionLocalWin::JoinAfterSwap( void * joinID ) { +} + +/* +======================== +idSessionLocalWin::GetLobbyAddressFromNetAddress +======================== +*/ +bool idSessionLocalWin::GetLobbyAddressFromNetAddress( const netadr_t & netAddr, lobbyAddress_t & outAddr ) const { + return false; +} + +/* +======================== +idSessionLocalWin::GetNetAddressFromLobbyAddress +======================== +*/ +bool idSessionLocalWin::GetNetAddressFromLobbyAddress( const lobbyAddress_t & lobbyAddress, netadr_t & outNetAddr ) const { + return false; +} diff --git a/neo/sys/win32/win_shared.cpp b/neo/sys/win32/win_shared.cpp new file mode 100644 index 00000000..5c371fbd --- /dev/null +++ b/neo/sys/win32/win_shared.cpp @@ -0,0 +1,800 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "win_local.h" +#include +#include +#include +#include +#include +#include +#include +#include +#undef StrCmpN +#undef StrCmpNI +#undef StrCmpI +#include + +#include +#include +#include + +#pragma comment (lib, "wbemuuid.lib") + +#pragma warning(disable:4740) // warning C4740: flow in or out of inline asm code suppresses global optimization + +/* +================ +Sys_Milliseconds +================ +*/ +int Sys_Milliseconds() { + static DWORD sys_timeBase = timeGetTime(); + return timeGetTime() - sys_timeBase; +} + +/* +======================== +Sys_Microseconds +======================== +*/ +uint64 Sys_Microseconds() { + static uint64 ticksPerMicrosecondTimes1024 = 0; + + if ( ticksPerMicrosecondTimes1024 == 0 ) { + ticksPerMicrosecondTimes1024 = ( (uint64)Sys_ClockTicksPerSecond() << 10 ) / 1000000; + assert( ticksPerMicrosecondTimes1024 > 0 ); + } + + return ((uint64)( (int64)Sys_GetClockTicks() << 10 )) / ticksPerMicrosecondTimes1024; +} + +/* +================ +Sys_GetSystemRam + + returns amount of physical memory in MB +================ +*/ +int Sys_GetSystemRam() { + MEMORYSTATUSEX statex; + statex.dwLength = sizeof ( statex ); + GlobalMemoryStatusEx (&statex); + int physRam = statex.ullTotalPhys / ( 1024 * 1024 ); + // HACK: For some reason, ullTotalPhys is sometimes off by a meg or two, so we round up to the nearest 16 megs + physRam = ( physRam + 8 ) & ~15; + return physRam; +} + + +/* +================ +Sys_GetDriveFreeSpace +returns in megabytes +================ +*/ +int Sys_GetDriveFreeSpace( const char *path ) { + DWORDLONG lpFreeBytesAvailable; + DWORDLONG lpTotalNumberOfBytes; + DWORDLONG lpTotalNumberOfFreeBytes; + int ret = 26; + //FIXME: see why this is failing on some machines + if ( ::GetDiskFreeSpaceEx( path, (PULARGE_INTEGER)&lpFreeBytesAvailable, (PULARGE_INTEGER)&lpTotalNumberOfBytes, (PULARGE_INTEGER)&lpTotalNumberOfFreeBytes ) ) { + ret = ( double )( lpFreeBytesAvailable ) / ( 1024.0 * 1024.0 ); + } + return ret; +} + +/* +======================== +Sys_GetDriveFreeSpaceInBytes +======================== +*/ +int64 Sys_GetDriveFreeSpaceInBytes( const char * path ) { + DWORDLONG lpFreeBytesAvailable; + DWORDLONG lpTotalNumberOfBytes; + DWORDLONG lpTotalNumberOfFreeBytes; + int64 ret = 1; + //FIXME: see why this is failing on some machines + if ( ::GetDiskFreeSpaceEx( path, (PULARGE_INTEGER)&lpFreeBytesAvailable, (PULARGE_INTEGER)&lpTotalNumberOfBytes, (PULARGE_INTEGER)&lpTotalNumberOfFreeBytes ) ) { + ret = lpFreeBytesAvailable; + } + return ret; +} + +/* +================ +Sys_GetVideoRam +returns in megabytes +================ +*/ +int Sys_GetVideoRam() { + unsigned int retSize = 64; + + CComPtr spLoc = NULL; + HRESULT hr = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, ( LPVOID * ) &spLoc ); + if ( hr != S_OK || spLoc == NULL ) { + return retSize; + } + + CComBSTR bstrNamespace( _T( "\\\\.\\root\\CIMV2" ) ); + CComPtr spServices; + + // Connect to CIM + hr = spLoc->ConnectServer( bstrNamespace, NULL, NULL, 0, NULL, 0, 0, &spServices ); + if ( hr != WBEM_S_NO_ERROR ) { + return retSize; + } + + // Switch the security level to IMPERSONATE so that provider will grant access to system-level objects. + hr = CoSetProxyBlanket( spServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE ); + if ( hr != S_OK ) { + return retSize; + } + + // Get the vid controller + CComPtr spEnumInst = NULL; + hr = spServices->CreateInstanceEnum( CComBSTR( "Win32_VideoController" ), WBEM_FLAG_SHALLOW, NULL, &spEnumInst ); + if ( hr != WBEM_S_NO_ERROR || spEnumInst == NULL ) { + return retSize; + } + + ULONG uNumOfInstances = 0; + CComPtr spInstance = NULL; + hr = spEnumInst->Next( 10000, 1, &spInstance, &uNumOfInstances ); + + if ( hr == S_OK && spInstance ) { + // Get properties from the object + CComVariant varSize; + hr = spInstance->Get( CComBSTR( _T( "AdapterRAM" ) ), 0, &varSize, 0, 0 ); + if ( hr == S_OK ) { + retSize = varSize.intVal / ( 1024 * 1024 ); + if ( retSize == 0 ) { + retSize = 64; + } + } + } + return retSize; +} + +/* +================ +Sys_GetCurrentMemoryStatus + + returns OS mem info + all values are in kB except the memoryload +================ +*/ +void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { + MEMORYSTATUSEX statex = {}; + unsigned __int64 work; + + statex.dwLength = sizeof( statex ); + GlobalMemoryStatusEx( &statex ); + + memset( &stats, 0, sizeof( stats ) ); + + stats.memoryLoad = statex.dwMemoryLoad; + + work = statex.ullTotalPhys >> 20; + stats.totalPhysical = *(int*)&work; + + work = statex.ullAvailPhys >> 20; + stats.availPhysical = *(int*)&work; + + work = statex.ullAvailPageFile >> 20; + stats.availPageFile = *(int*)&work; + + work = statex.ullTotalPageFile >> 20; + stats.totalPageFile = *(int*)&work; + + work = statex.ullTotalVirtual >> 20; + stats.totalVirtual = *(int*)&work; + + work = statex.ullAvailVirtual >> 20; + stats.availVirtual = *(int*)&work; + + work = statex.ullAvailExtendedVirtual >> 20; + stats.availExtendedVirtual = *(int*)&work; +} + +/* +================ +Sys_LockMemory +================ +*/ +bool Sys_LockMemory( void *ptr, int bytes ) { + return ( VirtualLock( ptr, (SIZE_T)bytes ) != FALSE ); +} + +/* +================ +Sys_UnlockMemory +================ +*/ +bool Sys_UnlockMemory( void *ptr, int bytes ) { + return ( VirtualUnlock( ptr, (SIZE_T)bytes ) != FALSE ); +} + +/* +================ +Sys_SetPhysicalWorkMemory +================ +*/ +void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ) { + ::SetProcessWorkingSetSize( GetCurrentProcess(), minBytes, maxBytes ); +} + +/* +================ +Sys_GetCurrentUser +================ +*/ +char *Sys_GetCurrentUser() { + static char s_userName[1024]; + unsigned long size = sizeof( s_userName ); + + + if ( !GetUserName( s_userName, &size ) ) { + strcpy( s_userName, "player" ); + } + + if ( !s_userName[0] ) { + strcpy( s_userName, "player" ); + } + + return s_userName; +} + + +/* +=============================================================================== + + Call stack + +=============================================================================== +*/ + + +#define PROLOGUE_SIGNATURE 0x00EC8B55 + +#include + +const int UNDECORATE_FLAGS = UNDNAME_NO_MS_KEYWORDS | + UNDNAME_NO_ACCESS_SPECIFIERS | + UNDNAME_NO_FUNCTION_RETURNS | + UNDNAME_NO_ALLOCATION_MODEL | + UNDNAME_NO_ALLOCATION_LANGUAGE | + UNDNAME_NO_MEMBER_TYPE; + +#if defined(_DEBUG) && 1 + +typedef struct symbol_s { + int address; + char * name; + struct symbol_s * next; +} symbol_t; + +typedef struct module_s { + int address; + char * name; + symbol_t * symbols; + struct module_s * next; +} module_t; + +module_t *modules; + +/* +================== +SkipRestOfLine +================== +*/ +void SkipRestOfLine( const char **ptr ) { + while( (**ptr) != '\0' && (**ptr) != '\n' && (**ptr) != '\r' ) { + (*ptr)++; + } + while( (**ptr) == '\n' || (**ptr) == '\r' ) { + (*ptr)++; + } +} + +/* +================== +SkipWhiteSpace +================== +*/ +void SkipWhiteSpace( const char **ptr ) { + while( (**ptr) == ' ' ) { + (*ptr)++; + } +} + +/* +================== +ParseHexNumber +================== +*/ +int ParseHexNumber( const char **ptr ) { + int n = 0; + while( (**ptr) >= '0' && (**ptr) <= '9' || (**ptr) >= 'a' && (**ptr) <= 'f' ) { + n <<= 4; + if ( **ptr >= '0' && **ptr <= '9' ) { + n |= ( (**ptr) - '0' ); + } else { + n |= 10 + ( (**ptr) - 'a' ); + } + (*ptr)++; + } + return n; +} + +/* +================== +Sym_Init +================== +*/ +void Sym_Init( long addr ) { + TCHAR moduleName[MAX_STRING_CHARS]; + MEMORY_BASIC_INFORMATION mbi; + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) ); + + char *ext = moduleName + strlen( moduleName ); + while( ext > moduleName && *ext != '.' ) { + ext--; + } + if ( ext == moduleName ) { + strcat( moduleName, ".map" ); + } else { + strcpy( ext, ".map" ); + } + + module_t *module = (module_t *) malloc( sizeof( module_t ) ); + module->name = (char *) malloc( strlen( moduleName ) + 1 ); + strcpy( module->name, moduleName ); + module->address = (int)mbi.AllocationBase; + module->symbols = NULL; + module->next = modules; + modules = module; + + FILE * fp = fopen( moduleName, "rb" ); + if ( fp == NULL ) { + return; + } + + int pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + int length = ftell( fp ); + fseek( fp, pos, SEEK_SET ); + + char *text = (char *) malloc( length+1 ); + fread( text, 1, length, fp ); + text[length] = '\0'; + fclose( fp ); + + const char *ptr = text; + + // skip up to " Address" on a new line + while( *ptr != '\0' ) { + SkipWhiteSpace( &ptr ); + if ( idStr::Cmpn( ptr, "Address", 7 ) == 0 ) { + SkipRestOfLine( &ptr ); + break; + } + SkipRestOfLine( &ptr ); + } + + int symbolAddress; + int symbolLength; + char symbolName[MAX_STRING_CHARS]; + symbol_t *symbol; + + // parse symbols + while( *ptr != '\0' ) { + + SkipWhiteSpace( &ptr ); + + ParseHexNumber( &ptr ); + if ( *ptr == ':' ) { + ptr++; + } else { + break; + } + ParseHexNumber( &ptr ); + + SkipWhiteSpace( &ptr ); + + // parse symbol name + symbolLength = 0; + while( *ptr != '\0' && *ptr != ' ' ) { + symbolName[symbolLength++] = *ptr++; + if ( symbolLength >= sizeof( symbolName ) - 1 ) { + break; + } + } + symbolName[symbolLength++] = '\0'; + + SkipWhiteSpace( &ptr ); + + // parse symbol address + symbolAddress = ParseHexNumber( &ptr ); + + SkipRestOfLine( &ptr ); + + symbol = (symbol_t *) malloc( sizeof( symbol_t ) ); + symbol->name = (char *) malloc( symbolLength ); + strcpy( symbol->name, symbolName ); + symbol->address = symbolAddress; + symbol->next = module->symbols; + module->symbols = symbol; + } + + free( text ); +} + +/* +================== +Sym_Shutdown +================== +*/ +void Sym_Shutdown() { + module_t *m; + symbol_t *s; + + for ( m = modules; m != NULL; m = modules ) { + modules = m->next; + for ( s = m->symbols; s != NULL; s = m->symbols ) { + m->symbols = s->next; + free( s->name ); + free( s ); + } + free( m->name ); + free( m ); + } + modules = NULL; +} + +/* +================== +Sym_GetFuncInfo +================== +*/ +void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) { + MEMORY_BASIC_INFORMATION mbi; + module_t *m; + symbol_t *s; + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + for ( m = modules; m != NULL; m = m->next ) { + if ( m->address == (int) mbi.AllocationBase ) { + break; + } + } + if ( !m ) { + Sym_Init( addr ); + m = modules; + } + + for ( s = m->symbols; s != NULL; s = s->next ) { + if ( s->address == addr ) { + + char undName[MAX_STRING_CHARS]; + if ( UnDecorateSymbolName( s->name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) { + funcName = undName; + } else { + funcName = s->name; + } + for ( int i = 0; i < funcName.Length(); i++ ) { + if ( funcName[i] == '(' ) { + funcName.CapLength( i ); + break; + } + } + module = m->name; + return; + } + } + + sprintf( funcName, "0x%08x", addr ); + module = ""; +} + +#elif defined(_DEBUG) + +DWORD lastAllocationBase = -1; +HANDLE processHandle; +idStr lastModule; + +/* +================== +Sym_Init +================== +*/ +void Sym_Init( long addr ) { + TCHAR moduleName[MAX_STRING_CHARS]; + TCHAR modShortNameBuf[MAX_STRING_CHARS]; + MEMORY_BASIC_INFORMATION mbi; + + if ( lastAllocationBase != -1 ) { + Sym_Shutdown(); + } + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) ); + _splitpath( moduleName, NULL, NULL, modShortNameBuf, NULL ); + lastModule = modShortNameBuf; + + processHandle = GetCurrentProcess(); + if ( !SymInitialize( processHandle, NULL, FALSE ) ) { + return; + } + if ( !SymLoadModule( processHandle, NULL, moduleName, NULL, (DWORD)mbi.AllocationBase, 0 ) ) { + SymCleanup( processHandle ); + return; + } + + SymSetOptions( SymGetOptions() & ~SYMOPT_UNDNAME ); + + lastAllocationBase = (DWORD) mbi.AllocationBase; +} + +/* +================== +Sym_Shutdown +================== +*/ +void Sym_Shutdown() { + SymUnloadModule( GetCurrentProcess(), lastAllocationBase ); + SymCleanup( GetCurrentProcess() ); + lastAllocationBase = -1; +} + +/* +================== +Sym_GetFuncInfo +================== +*/ +void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) { + MEMORY_BASIC_INFORMATION mbi; + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + if ( (DWORD) mbi.AllocationBase != lastAllocationBase ) { + Sym_Init( addr ); + } + + BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + MAX_STRING_CHARS ]; + PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)&symbolBuffer[0]; + pSymbol->SizeOfStruct = sizeof(symbolBuffer); + pSymbol->MaxNameLength = 1023; + pSymbol->Address = 0; + pSymbol->Flags = 0; + pSymbol->Size =0; + + DWORD symDisplacement = 0; + if ( SymGetSymFromAddr( processHandle, addr, &symDisplacement, pSymbol ) ) { + // clean up name, throwing away decorations that don't affect uniqueness + char undName[MAX_STRING_CHARS]; + if ( UnDecorateSymbolName( pSymbol->Name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) { + funcName = undName; + } else { + funcName = pSymbol->Name; + } + module = lastModule; + } + else { + LPVOID lpMsgBuf; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + LocalFree( lpMsgBuf ); + + // Couldn't retrieve symbol (no debug info?, can't load dbghelp.dll?) + sprintf( funcName, "0x%08x", addr ); + module = ""; + } +} + +#else + +/* +================== +Sym_Init +================== +*/ +void Sym_Init( long addr ) { +} + +/* +================== +Sym_Shutdown +================== +*/ +void Sym_Shutdown() { +} + +/* +================== +Sym_GetFuncInfo +================== +*/ +void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) { + module = ""; + sprintf( funcName, "0x%08x", addr ); +} + +#endif + +/* +================== +GetFuncAddr +================== +*/ +address_t GetFuncAddr( address_t midPtPtr ) { + long temp; + do { + temp = (long)(*(long*)midPtPtr); + if ( (temp&0x00FFFFFF) == PROLOGUE_SIGNATURE ) { + break; + } + midPtPtr--; + } while(true); + + return midPtPtr; +} + +/* +================== +GetCallerAddr +================== +*/ +address_t GetCallerAddr( long _ebp ) { + long midPtPtr; + long res = 0; + + __asm { + mov eax, _ebp + mov ecx, [eax] // check for end of stack frames list + test ecx, ecx // check for zero stack frame + jz label + mov eax, [eax+4] // get the ret address + test eax, eax // check for zero return address + jz label + mov midPtPtr, eax + } + res = GetFuncAddr( midPtPtr ); +label: + return res; +} + +/* +================== +Sys_GetCallStack + + use /Oy option +================== +*/ +void Sys_GetCallStack( address_t *callStack, const int callStackSize ) { +#if 1 //def _DEBUG + int i; + long m_ebp; + + __asm { + mov eax, ebp + mov m_ebp, eax + } + // skip last two functions + m_ebp = *((long*)m_ebp); + m_ebp = *((long*)m_ebp); + // list functions + for ( i = 0; i < callStackSize; i++ ) { + callStack[i] = GetCallerAddr( m_ebp ); + if ( callStack[i] == 0 ) { + break; + } + m_ebp = *((long*)m_ebp); + } +#else + int i = 0; +#endif + while( i < callStackSize ) { + callStack[i++] = 0; + } +} + +/* +================== +Sys_GetCallStackStr +================== +*/ +const char *Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ) { + static char string[MAX_STRING_CHARS*2]; + int index, i; + idStr module, funcName; + + index = 0; + for ( i = callStackSize-1; i >= 0; i-- ) { + Sym_GetFuncInfo( callStack[i], module, funcName ); + index += sprintf( string+index, " -> %s", funcName.c_str() ); + } + return string; +} + +/* +================== +Sys_GetCallStackCurStr +================== +*/ +const char *Sys_GetCallStackCurStr( int depth ) { + address_t *callStack; + + callStack = (address_t *) _alloca( depth * sizeof( address_t ) ); + Sys_GetCallStack( callStack, depth ); + return Sys_GetCallStackStr( callStack, depth ); +} + +/* +================== +Sys_GetCallStackCurAddressStr +================== +*/ +const char *Sys_GetCallStackCurAddressStr( int depth ) { + static char string[MAX_STRING_CHARS*2]; + address_t *callStack; + int index, i; + + callStack = (address_t *) _alloca( depth * sizeof( address_t ) ); + Sys_GetCallStack( callStack, depth ); + + index = 0; + for ( i = depth-1; i >= 0; i-- ) { + index += sprintf( string+index, " -> 0x%08x", callStack[i] ); + } + return string; +} + +/* +================== +Sys_ShutdownSymbols +================== +*/ +void Sys_ShutdownSymbols() { + Sym_Shutdown(); +} diff --git a/neo/sys/win32/win_signin.cpp b/neo/sys/win32/win_signin.cpp new file mode 100644 index 00000000..171c2956 --- /dev/null +++ b/neo/sys/win32/win_signin.cpp @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" +#include "../../framework/PlayerProfile.h" +#include "../sys_session_local.h" +#include "win_signin.h" + +#ifdef _DEBUG +idCVar win_userPersistent( "win_userPersistent", "1", CVAR_BOOL, "debugging cvar for profile persistence status" ); +idCVar win_userOnline( "win_userOnline", "1", CVAR_BOOL, "debugging cvar for profile online status" ); +idCVar win_isInParty( "win_isInParty", "0", CVAR_BOOL, "debugging cvar for platform party status" ); +idCVar win_partyCount( "win_partyCount", "0", CVAR_INTEGER, "debugginc var for platform party count" ); +#endif + +/* +======================== +idSignInManagerWin::Shutdown +======================== +*/ +void idSignInManagerWin::Shutdown() { +} + +/* +======================== +idSignInManagerWin::Pump +======================== +*/ +void idSignInManagerWin::Pump() { + + // If we have more users than we need, then set to the lower amount + // (don't remove the master user though) + if ( localUsers.Num() > 1 && localUsers.Num() > maxDesiredLocalUsers ) { + localUsers.SetNum( maxDesiredLocalUsers ); + } + +#ifndef ID_RETAIL + // If we don't have enough, then make sure we do + // NOTE - We always want at least one user on windows for now, + // and this master user will always use controller 0 + while ( localUsers.Num() < minDesiredLocalUsers ) { + RegisterLocalUser( localUsers.Num() ); + } +#endif + + // See if we need to save settings on any of the profiles + for ( int i = 0; i < localUsers.Num(); i++ ) { + localUsers[i].Pump(); + } +} + +/* +======================== +idSignInManagerWin::RemoveLocalUserByIndex +======================== +*/ +void idSignInManagerWin::RemoveLocalUserByIndex( int index ) { + session->OnLocalUserSignout( &localUsers[index] ); + localUsers.RemoveIndex( index ); +} + +/* +======================== +idSignInManagerWin::RegisterLocalUser +======================== +*/ +void idSignInManagerWin::RegisterLocalUser( int inputDevice ) { + if ( GetLocalUserByInputDevice( inputDevice ) != NULL ) { + return; + } + + static char machineName[128]; + DWORD len = 128; + ::GetComputerName( machineName, &len ); + + const char * nameSource = machineName; + + idStr name( nameSource ); + int nameLength = name.Length(); + if ( idStr::IsValidUTF8( nameSource, nameLength ) ) { + int nameIndex = 0; + int numChars = 0; + name.Empty(); + while ( nameIndex < nameLength && numChars++ < idLocalUserWin::MAX_GAMERTAG_CHARS ) { + uint32 c = idStr::UTF8Char( nameSource, nameIndex ); + name.AppendUTF8Char( c ); + } + } + + idLocalUserWin & localUser = *localUsers.Alloc(); + + localUser.Init( inputDevice, name.c_str(), localUsers.Num() ); + localUser.SetLocalUserHandle( GetUniqueLocalUserHandle( localUser.GetGamerTag() ) ); + + session->OnLocalUserSignin( &localUser ); +} + +/* +======================== +idSignInManagerWin::CreateNewUser +======================== +*/ +bool idSignInManagerWin::CreateNewUser( winUserState_t & state ) { + //idScopedGlobalHeap everythingHereGoesInTheGlobalHeap; // users obviously persist across maps + + RemoveAllLocalUsers(); + RegisterLocalUser( state.inputDevice ); + + if ( localUsers.Num() > 0 ) { + if ( !localUsers[0].VerifyUserState( state ) ) { + RemoveAllLocalUsers(); + } + } + + return true; +} + +CONSOLE_COMMAND( testRemoveAllLocalUsers, "Forces removal of local users - mainly for PC testing", NULL ) { + session->GetSignInManager().RemoveAllLocalUsers(); +} diff --git a/neo/sys/win32/win_signin.h b/neo/sys/win32/win_signin.h new file mode 100644 index 00000000..33005825 --- /dev/null +++ b/neo/sys/win32/win_signin.h @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __WIN_SIGNIN_H__ +#define __WIN_SIGNIN_H__ + +#include "win_localuser.h" + +/* +================================================ +idSignInManagerWin +================================================ +*/ +class idSignInManagerWin : public idSignInManagerBase { +public: + + idSignInManagerWin() : dlcVersionChecked( false ) {} + virtual ~idSignInManagerWin() {} + + //========================================================================================== + // idSignInManagerBase interface + //========================================================================================== + virtual void Pump(); + virtual void Shutdown(); + virtual int GetNumLocalUsers() const { return localUsers.Num(); } + virtual idLocalUser * GetLocalUserByIndex( int index ) { return &localUsers[index]; } + virtual const idLocalUser * GetLocalUserByIndex( int index ) const { return &localUsers[index]; } + virtual void RemoveLocalUserByIndex( int index ); + virtual void RegisterLocalUser( int inputDevice ); // Register a local user to the passed in controller + + bool CreateNewUser( winUserState_t & state ); + +private: + idStaticList< idLocalUserWin, MAX_INPUT_DEVICES > localUsers; + bool dlcVersionChecked; +}; + +#endif diff --git a/neo/sys/win32/win_snd.cpp b/neo/sys/win32/win_snd.cpp new file mode 100644 index 00000000..4730b029 --- /dev/null +++ b/neo/sys/win32/win_snd.cpp @@ -0,0 +1,37 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../idlib/precompiled.h" + +// DirectX SDK +#include + +#include +#include +#include "../../sound/snd_local.h" +#include "win_local.h" diff --git a/neo/sys/win32/win_stats.cpp b/neo/sys/win32/win_stats.cpp new file mode 100644 index 00000000..8bc6202a --- /dev/null +++ b/neo/sys/win32/win_stats.cpp @@ -0,0 +1,29 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../../framework/precompiled.h" diff --git a/neo/sys/win32/win_stats.h b/neo/sys/win32/win_stats.h new file mode 100644 index 00000000..95cec4b8 --- /dev/null +++ b/neo/sys/win32/win_stats.h @@ -0,0 +1,32 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __WIN_STATS_H__ +#define __WIN_STATS_H__ + + +#endif // !__WIN_STATS_H__ diff --git a/neo/sys/win32/win_syscon.cpp b/neo/sys/win32/win_syscon.cpp new file mode 100644 index 00000000..38fc2902 --- /dev/null +++ b/neo/sys/win32/win_syscon.cpp @@ -0,0 +1,553 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "win_local.h" +#include "rc/doom_resource.h" + +#define COPY_ID 1 +#define QUIT_ID 2 +#define CLEAR_ID 3 + +#define ERRORBOX_ID 10 +#define ERRORTEXT_ID 11 + +#define EDIT_ID 100 +#define INPUT_ID 101 + +#define COMMAND_HISTORY 64 + +typedef struct { + HWND hWnd; + HWND hwndBuffer; + + HWND hwndButtonClear; + HWND hwndButtonCopy; + HWND hwndButtonQuit; + + HWND hwndErrorBox; + HWND hwndErrorText; + + HBITMAP hbmLogo; + HBITMAP hbmClearBitmap; + + HBRUSH hbrEditBackground; + HBRUSH hbrErrorBackground; + + HFONT hfBufferFont; + HFONT hfButtonFont; + + HWND hwndInputLine; + + char errorString[80]; + + char consoleText[512], returnedText[512]; + bool quitOnClose; + int windowWidth, windowHeight; + + WNDPROC SysInputLineWndProc; + + idEditField historyEditLines[COMMAND_HISTORY]; + + int nextHistoryLine;// the last line in the history buffer, not masked + int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + + idEditField consoleField; + +} WinConData; + +static WinConData s_wcd; + +static LONG WINAPI ConWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + char *cmdString; + static bool s_timePolarity; + + switch (uMsg) { + case WM_ACTIVATE: + if ( LOWORD( wParam ) != WA_INACTIVE ) { + SetFocus( s_wcd.hwndInputLine ); + } + break; + case WM_CLOSE: + if ( s_wcd.quitOnClose ) { + PostQuitMessage( 0 ); + } else { + Sys_ShowConsole( 0, false ); + win32.win_viewlog.SetBool( false ); + } + return 0; + case WM_CTLCOLORSTATIC: + if ( ( HWND ) lParam == s_wcd.hwndBuffer ) { + SetBkColor( ( HDC ) wParam, RGB( 0x00, 0x00, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0xff, 0xff, 0x00 ) ); + return ( long ) s_wcd.hbrEditBackground; + } else if ( ( HWND ) lParam == s_wcd.hwndErrorBox ) { + if ( s_timePolarity & 1 ) { + SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0xff, 0x0, 0x00 ) ); + } else { + SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0x00, 0x0, 0x00 ) ); + } + return ( long ) s_wcd.hbrErrorBackground; + } + break; + case WM_SYSCOMMAND: + if ( wParam == SC_CLOSE ) { + PostQuitMessage( 0 ); + } + break; + case WM_COMMAND: + if ( wParam == COPY_ID ) { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, WM_COPY, 0, 0 ); + } else if ( wParam == QUIT_ID ) { + if ( s_wcd.quitOnClose ) { + PostQuitMessage( 0 ); + } else { + cmdString = Mem_CopyString( "quit" ); + Sys_QueEvent( SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, cmdString, 0 ); + } + } else if ( wParam == CLEAR_ID ) { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, ( LPARAM ) "" ); + UpdateWindow( s_wcd.hwndBuffer ); + } + break; + case WM_CREATE: + s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x00, 0x00, 0x80 ) ); + s_wcd.hbrErrorBackground = CreateSolidBrush( RGB( 0x80, 0x80, 0x80 ) ); + SetTimer( hWnd, 1, 1000, NULL ); + break; +/* + case WM_ERASEBKGND: + HGDIOBJ oldObject; + HDC hdcScaled; + hdcScaled = CreateCompatibleDC( ( HDC ) wParam ); + assert( hdcScaled != 0 ); + if ( hdcScaled ) { + oldObject = SelectObject( ( HDC ) hdcScaled, s_wcd.hbmLogo ); + assert( oldObject != 0 ); + if ( oldObject ) + { + StretchBlt( ( HDC ) wParam, 0, 0, s_wcd.windowWidth, s_wcd.windowHeight, + hdcScaled, 0, 0, 512, 384, + SRCCOPY ); + } + DeleteDC( hdcScaled ); + hdcScaled = 0; + } + return 1; +*/ + case WM_TIMER: + if ( wParam == 1 ) { + s_timePolarity = (bool)!s_timePolarity; + if ( s_wcd.hwndErrorBox ) { + InvalidateRect( s_wcd.hwndErrorBox, NULL, FALSE ); + } + } + break; + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + +LONG WINAPI InputLineWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + int key, cursor; + switch ( uMsg ) { + case WM_KILLFOCUS: + if ( ( HWND ) wParam == s_wcd.hWnd || ( HWND ) wParam == s_wcd.hwndErrorBox ) { + SetFocus( hWnd ); + return 0; + } + break; + + case WM_KEYDOWN: + key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 ); + + // command history + if ( ( key == K_UPARROW ) || ( key == K_KP_8 ) ) { + if ( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 ) { + s_wcd.historyLine--; + } + s_wcd.consoleField = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ]; + + SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() ); + return 0; + } + + if ( ( key == K_DOWNARROW ) || ( key == K_KP_2 ) ) { + if ( s_wcd.historyLine == s_wcd.nextHistoryLine ) { + return 0; + } + s_wcd.historyLine++; + s_wcd.consoleField = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ]; + + SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() ); + return 0; + } + break; + + case WM_CHAR: + key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 ); + + GetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer(), MAX_EDIT_LINE ); + SendMessage( s_wcd.hwndInputLine, EM_GETSEL, (WPARAM) NULL, (LPARAM) &cursor ); + s_wcd.consoleField.SetCursor( cursor ); + + // enter the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + strncat( s_wcd.consoleText, s_wcd.consoleField.GetBuffer(), sizeof( s_wcd.consoleText ) - strlen( s_wcd.consoleText ) - 5 ); + strcat( s_wcd.consoleText, "\n" ); + SetWindowText( s_wcd.hwndInputLine, "" ); + + Sys_Printf( "]%s\n", s_wcd.consoleField.GetBuffer() ); + + // copy line to history buffer + s_wcd.historyEditLines[s_wcd.nextHistoryLine % COMMAND_HISTORY] = s_wcd.consoleField; + s_wcd.nextHistoryLine++; + s_wcd.historyLine = s_wcd.nextHistoryLine; + + s_wcd.consoleField.Clear(); + + return 0; + } + + // command completion + if ( key == K_TAB ) { + s_wcd.consoleField.AutoComplete(); + + SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() ); + //s_wcd.consoleField.SetWidthInChars( strlen( s_wcd.consoleField.GetBuffer() ) ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() ); + + return 0; + } + + // clear autocompletion buffer on normal key input + if ( ( key >= K_SPACE && key <= K_BACKSPACE ) || + ( key >= K_KP_SLASH && key <= K_KP_PLUS ) || ( key >= K_KP_STAR && key <= K_KP_EQUALS ) ) { + s_wcd.consoleField.ClearAutoComplete(); + } + break; + } + + return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); +} + +/* +** Sys_CreateConsole +*/ +void Sys_CreateConsole() { + HDC hDC; + WNDCLASS wc; + RECT rect; + const char *DEDCLASS = WIN32_CONSOLE_CLASS; + int nHeight; + int swidth, sheight; + int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX; + int i; + + memset( &wc, 0, sizeof( wc ) ); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) ConWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = win32.hInstance; + wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (struct HBRUSH__ *)COLOR_WINDOW; + wc.lpszMenuName = 0; + wc.lpszClassName = DEDCLASS; + + if ( !RegisterClass (&wc) ) { + return; + } + + rect.left = 0; + rect.right = 540; + rect.top = 0; + rect.bottom = 450; + AdjustWindowRect( &rect, DEDSTYLE, FALSE ); + + hDC = GetDC( GetDesktopWindow() ); + swidth = GetDeviceCaps( hDC, HORZRES ); + sheight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + s_wcd.windowWidth = rect.right - rect.left + 1; + s_wcd.windowHeight = rect.bottom - rect.top + 1; + + //s_wcd.hbmLogo = LoadBitmap( win32.hInstance, MAKEINTRESOURCE( IDB_BITMAP_LOGO) ); + + s_wcd.hWnd = CreateWindowEx( 0, + DEDCLASS, + GAME_NAME, + DEDSTYLE, + ( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1, + NULL, + NULL, + win32.hInstance, + NULL ); + + if ( s_wcd.hWnd == NULL ) { + return; + } + + // + // create fonts + // + hDC = GetDC( s_wcd.hWnd ); + nHeight = -MulDiv( 8, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ); + + s_wcd.hfBufferFont = CreateFont( nHeight, 0, 0, 0, FW_LIGHT, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_MODERN | FIXED_PITCH, "Courier New" ); + + ReleaseDC( s_wcd.hWnd, hDC ); + + // + // create the input line + // + s_wcd.hwndInputLine = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | + ES_LEFT | ES_AUTOHSCROLL, + 6, 400, 528, 20, + s_wcd.hWnd, + ( HMENU ) INPUT_ID, // child window ID + win32.hInstance, NULL ); + + // + // create the buttons + // + s_wcd.hwndButtonCopy = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 5, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) COPY_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonCopy, WM_SETTEXT, 0, ( LPARAM ) "copy" ); + + s_wcd.hwndButtonClear = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 82, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) CLEAR_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonClear, WM_SETTEXT, 0, ( LPARAM ) "clear" ); + + s_wcd.hwndButtonQuit = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 462, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) QUIT_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonQuit, WM_SETTEXT, 0, ( LPARAM ) "quit" ); + + + // + // create the scrollbuffer + // + s_wcd.hwndBuffer = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | + ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, + 6, 40, 526, 354, + s_wcd.hWnd, + ( HMENU ) EDIT_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndBuffer, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + + s_wcd.SysInputLineWndProc = ( WNDPROC ) SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, ( long ) InputLineWndProc ); + SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + +// don't show it now that we have a splash screen up + if ( win32.win_viewlog.GetBool() ) { + ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT); + UpdateWindow( s_wcd.hWnd ); + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hwndInputLine ); + } + + + + s_wcd.consoleField.Clear(); + + for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { + s_wcd.historyEditLines[i].Clear(); + } +} + +/* +** Sys_DestroyConsole +*/ +void Sys_DestroyConsole() { + if ( s_wcd.hWnd ) { + ShowWindow( s_wcd.hWnd, SW_HIDE ); + CloseWindow( s_wcd.hWnd ); + DestroyWindow( s_wcd.hWnd ); + s_wcd.hWnd = 0; + } +} + +/* +** Sys_ShowConsole +*/ +void Sys_ShowConsole( int visLevel, bool quitOnClose ) { + + s_wcd.quitOnClose = quitOnClose; + + if ( !s_wcd.hWnd ) { + return; + } + + switch ( visLevel ) { + case 0: + ShowWindow( s_wcd.hWnd, SW_HIDE ); + break; + case 1: + ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL ); + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + break; + case 2: + ShowWindow( s_wcd.hWnd, SW_MINIMIZE ); + break; + default: + Sys_Error( "Invalid visLevel %d sent to Sys_ShowConsole\n", visLevel ); + break; + } +} + +/* +** Sys_ConsoleInput +*/ +char *Sys_ConsoleInput() { + + if ( s_wcd.consoleText[0] == 0 ) { + return NULL; + } + + strcpy( s_wcd.returnedText, s_wcd.consoleText ); + s_wcd.consoleText[0] = 0; + + return s_wcd.returnedText; +} + +/* +** Conbuf_AppendText +*/ +void Conbuf_AppendText( const char *pMsg ) +{ +#define CONSOLE_BUFFER_SIZE 16384 + + char buffer[CONSOLE_BUFFER_SIZE*2]; + char *b = buffer; + const char *msg; + int bufLen; + int i = 0; + static unsigned long s_totalChars; + + // + // if the message is REALLY long, use just the last portion of it + // + if ( strlen( pMsg ) > CONSOLE_BUFFER_SIZE - 1 ) { + msg = pMsg + strlen( pMsg ) - CONSOLE_BUFFER_SIZE + 1; + } else { + msg = pMsg; + } + + // + // copy into an intermediate buffer + // + while ( msg[i] && ( ( b - buffer ) < sizeof( buffer ) - 1 ) ) { + if ( msg[i] == '\n' && msg[i+1] == '\r' ) { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + i++; + } else if ( msg[i] == '\r' ) { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } else if ( msg[i] == '\n' ) { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } else if ( idStr::IsColor( &msg[i] ) ) { + i++; + } else { + *b= msg[i]; + b++; + } + i++; + } + *b = 0; + bufLen = b - buffer; + + s_totalChars += bufLen; + + // + // replace selection instead of appending if we're overflowing + // + if ( s_totalChars > 0x7000 ) { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + s_totalChars = bufLen; + } + + // + // put this text into the windows console + // + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM) buffer ); +} + +/* +** Win_SetErrorText +*/ +void Win_SetErrorText( const char *buf ) { + idStr::Copynz( s_wcd.errorString, buf, sizeof( s_wcd.errorString ) ); + if ( !s_wcd.hwndErrorBox ) { + s_wcd.hwndErrorBox = CreateWindow( "static", NULL, WS_CHILD | WS_VISIBLE | SS_SUNKEN, + 6, 5, 526, 30, + s_wcd.hWnd, + ( HMENU ) ERRORBOX_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndErrorBox, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + SetWindowText( s_wcd.hwndErrorBox, s_wcd.errorString ); + + DestroyWindow( s_wcd.hwndInputLine ); + s_wcd.hwndInputLine = NULL; + } +} diff --git a/neo/sys/win32/win_taskkeyhook.cpp b/neo/sys/win32/win_taskkeyhook.cpp new file mode 100644 index 00000000..fc507dc2 --- /dev/null +++ b/neo/sys/win32/win_taskkeyhook.cpp @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +// +// This file implements the low-level keyboard hook that traps the task keys. +// +#include "win_local.h" + +#define DLLEXPORT __declspec(dllexport) + +// Magic registry key/value for "Remove Task Manager" policy. +LPCTSTR KEY_DisableTaskMgr = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; +LPCTSTR VAL_DisableTaskMgr = "DisableTaskMgr"; + +// The section is SHARED among all instances of this DLL. +// A low-level keyboard hook is always a system-wide hook. +#pragma data_seg (".mydata") +HHOOK g_hHookKbdLL = NULL; // hook handle +BOOL g_bBeep = FALSE; // beep on illegal key +#pragma data_seg () +#pragma comment(linker, "/SECTION:.mydata,RWS") // tell linker: make it shared + +/* +================ +MyTaskKeyHookLL + + Low-level keyboard hook: + Trap task-switching keys by returning without passing along. +================ +*/ +LRESULT CALLBACK MyTaskKeyHookLL( int nCode, WPARAM wp, LPARAM lp ) { + KBDLLHOOKSTRUCT *pkh = (KBDLLHOOKSTRUCT *) lp; + + if ( nCode == HC_ACTION ) { + BOOL bCtrlKeyDown = GetAsyncKeyState( VK_CONTROL)>>((sizeof(SHORT) * 8) - 1 ); + + if ( ( pkh->vkCode == VK_ESCAPE && bCtrlKeyDown ) // Ctrl+Esc + || ( pkh->vkCode == VK_TAB && pkh->flags & LLKHF_ALTDOWN ) // Alt+TAB + || ( pkh->vkCode == VK_ESCAPE && pkh->flags & LLKHF_ALTDOWN ) // Alt+Esc + || ( pkh->vkCode == VK_LWIN || pkh->vkCode == VK_RWIN ) // Start Menu + ) { + + if ( g_bBeep && ( wp == WM_SYSKEYDOWN || wp == WM_KEYDOWN ) ) { + MessageBeep( 0 ); // beep on downstroke if requested + } + return 1; // return without processing the key strokes + } + } + return CallNextHookEx( g_hHookKbdLL, nCode, wp, lp ); +} + +/* +================ +AreTaskKeysDisabled + + Are task keys disabled--ie, is hook installed? + Note: This assumes there's no other hook that does the same thing! +================ +*/ +BOOL AreTaskKeysDisabled() { + return g_hHookKbdLL != NULL; +} + +/* +================ +IsTaskMgrDisabled +================ +*/ +BOOL IsTaskMgrDisabled() { + HKEY hk; + + if ( RegOpenKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ) != ERROR_SUCCESS ) { + return FALSE; // no key ==> not disabled + } + + DWORD val = 0; + DWORD len = 4; + return RegQueryValueEx( hk, VAL_DisableTaskMgr, NULL, NULL, (BYTE*)&val, &len ) == ERROR_SUCCESS && val == 1; +} + +/* +================ +DisableTaskKeys +================ +*/ +void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr ) { + + // task keys (Ctrl+Esc, Alt-Tab, etc.) + if ( bDisable ) { + if ( !g_hHookKbdLL ) { + g_hHookKbdLL = SetWindowsHookEx( WH_KEYBOARD_LL, MyTaskKeyHookLL, win32.hInstance, 0 ); + } + } else if ( g_hHookKbdLL != NULL ) { + UnhookWindowsHookEx( g_hHookKbdLL ); + g_hHookKbdLL = NULL; + } + g_bBeep = bBeep; + + // task manager (Ctrl+Alt+Del) + if ( bTaskMgr ) { + HKEY hk; + if ( RegOpenKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ) != ERROR_SUCCESS ) { + RegCreateKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ); + } + if ( bDisable ) { + // disable TM: set policy = 1 + DWORD val = 1; + RegSetValueEx( hk, VAL_DisableTaskMgr, NULL, REG_DWORD, (BYTE*)&val, sizeof(val) ); + } else { + // enable TM: remove policy + RegDeleteValue( hk,VAL_DisableTaskMgr ); + } + } +} diff --git a/neo/sys/win32/win_wndproc.cpp b/neo/sys/win32/win_wndproc.cpp new file mode 100644 index 00000000..5ec146c2 --- /dev/null +++ b/neo/sys/win32/win_wndproc.cpp @@ -0,0 +1,429 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../../idlib/precompiled.h" + +#include "win_local.h" +#include "../../renderer/tr_local.h" + +#include + +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + +static bool s_alttab_disabled; + +extern idCVar r_windowX; +extern idCVar r_windowY; +extern idCVar r_windowWidth; +extern idCVar r_windowHeight; + +static void WIN_DisableAltTab() { + if ( s_alttab_disabled || win32.win_allowAltTab.GetBool() ) { + return; + } + if ( !idStr::Icmp( cvarSystem->GetCVarString( "sys_arch" ), "winnt" ) ) { + RegisterHotKey( 0, 0, MOD_ALT, VK_TAB ); + } else { + BOOL old; + + SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, &old, 0 ); + } + s_alttab_disabled = true; +} + +static void WIN_EnableAltTab() { + if ( !s_alttab_disabled || win32.win_allowAltTab.GetBool() ) { + return; + } + if ( !idStr::Icmp( cvarSystem->GetCVarString( "sys_arch" ), "winnt" ) ) { + UnregisterHotKey( 0, 0 ); + } else { + BOOL old; + + SystemParametersInfo( SPI_SCREENSAVERRUNNING, 0, &old, 0 ); + } + + s_alttab_disabled = false; +} + +void WIN_Sizing(WORD side, RECT *rect) +{ + if ( !R_IsInitialized() || renderSystem->GetWidth() <= 0 || renderSystem->GetHeight() <= 0 ) { + return; + } + + // restrict to a standard aspect ratio + int width = rect->right - rect->left; + int height = rect->bottom - rect->top; + + // Adjust width/height for window decoration + RECT decoRect = { 0, 0, 0, 0 }; + AdjustWindowRect( &decoRect, WINDOW_STYLE|WS_SYSMENU, FALSE ); + int decoWidth = decoRect.right - decoRect.left; + int decoHeight = decoRect.bottom - decoRect.top; + + width -= decoWidth; + height -= decoHeight; + + // Clamp to a minimum size + if ( width < SCREEN_WIDTH / 4 ) { + width = SCREEN_WIDTH / 4; + } + if ( height < SCREEN_HEIGHT / 4 ) { + height = SCREEN_HEIGHT / 4; + } + + const int minWidth = height * 4 / 3; + const int maxHeight = width * 3 / 4; + + const int maxWidth = height * 16 / 9; + const int minHeight = width * 9 / 16; + + // Set the new size + switch ( side ) { + case WMSZ_LEFT: + rect->left = rect->right - width - decoWidth; + rect->bottom = rect->top + idMath::ClampInt( minHeight, maxHeight, height ) + decoHeight; + break; + case WMSZ_RIGHT: + rect->right = rect->left + width + decoWidth; + rect->bottom = rect->top + idMath::ClampInt( minHeight, maxHeight, height ) + decoHeight; + break; + case WMSZ_BOTTOM: + case WMSZ_BOTTOMRIGHT: + rect->bottom = rect->top + height + decoHeight; + rect->right = rect->left + idMath::ClampInt( minWidth, maxWidth, width ) + decoWidth; + break; + case WMSZ_TOP: + case WMSZ_TOPRIGHT: + rect->top = rect->bottom - height - decoHeight; + rect->right = rect->left + idMath::ClampInt( minWidth, maxWidth, width ) + decoWidth; + break; + case WMSZ_BOTTOMLEFT: + rect->bottom = rect->top + height + decoHeight; + rect->left = rect->right - idMath::ClampInt( minWidth, maxWidth, width ) - decoWidth; + break; + case WMSZ_TOPLEFT: + rect->top = rect->bottom - height - decoHeight; + rect->left = rect->right - idMath::ClampInt( minWidth, maxWidth, width ) - decoWidth; + break; + } +} + +/* +==================== +MainWndProc + +main window procedure +==================== +*/ +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { + int key; + switch( uMsg ) { + case WM_WINDOWPOSCHANGED: + if (R_IsInitialized()) { + RECT rect; + if (::GetClientRect(win32.hWnd, &rect)) { + + if ( rect.right > rect.left && rect.bottom > rect.top ) { + glConfig.nativeScreenWidth = rect.right - rect.left; + glConfig.nativeScreenHeight = rect.bottom - rect.top; + + // save the window size in cvars if we aren't fullscreen + int style = GetWindowLong( hWnd, GWL_STYLE ); + if ( ( style & WS_POPUP ) == 0 ) { + r_windowWidth.SetInteger( glConfig.nativeScreenWidth ); + r_windowHeight.SetInteger( glConfig.nativeScreenHeight ); + } + } + } + } + break; + case WM_MOVE: { + int xPos, yPos; + RECT r; + + // save the window origin in cvars if we aren't fullscreen + int style = GetWindowLong( hWnd, GWL_STYLE ); + if ( ( style & WS_POPUP ) == 0 ) { + xPos = (short) LOWORD(lParam); // horizontal position + yPos = (short) HIWORD(lParam); // vertical position + + r.left = 0; + r.top = 0; + r.right = 1; + r.bottom = 1; + + AdjustWindowRect( &r, style, FALSE ); + + r_windowX.SetInteger( xPos + r.left ); + r_windowY.SetInteger( yPos + r.top ); + } + break; + } + case WM_CREATE: + + win32.hWnd = hWnd; + + if ( win32.cdsFullscreen ) { + WIN_DisableAltTab(); + } else { + WIN_EnableAltTab(); + } + + // do the OpenGL setup + void GLW_WM_CREATE( HWND hWnd ); + GLW_WM_CREATE( hWnd ); + + break; + + case WM_DESTROY: + // let sound and input know about this? + win32.hWnd = NULL; + if ( win32.cdsFullscreen ) { + WIN_EnableAltTab(); + } + break; + + case WM_CLOSE: + soundSystem->SetMute( true ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + break; + + case WM_ACTIVATE: + // if we got here because of an alt-tab or maximize, + // we should activate immediately. If we are here because + // the mouse was clicked on a title bar or drag control, + // don't activate until the mouse button is released + { + int fActive, fMinimized; + + fActive = LOWORD(wParam); + fMinimized = (BOOL) HIWORD(wParam); + + win32.activeApp = (fActive != WA_INACTIVE); + if ( win32.activeApp ) { + idKeyInput::ClearStates(); + Sys_GrabMouseCursor( true ); + if ( common->IsInitialized() ) { + SetCursor( NULL ); + } + } + + if ( fActive == WA_INACTIVE ) { + win32.movingWindow = false; + if ( common->IsInitialized() ) { + SetCursor( LoadCursor( 0, IDC_ARROW ) ); + } + } + + // start playing the game sound world + soundSystem->SetMute( !win32.activeApp ); + + // we do not actually grab or release the mouse here, + // that will be done next time through the main loop + } + break; + case WM_TIMER: { + if ( win32.win_timerUpdate.GetBool() ) { + common->Frame(); + } + break; + } + case WM_SYSCOMMAND: + if ( wParam == SC_SCREENSAVE || wParam == SC_KEYMENU ) { + return 0; + } + break; + + case WM_SYSKEYDOWN: + if ( wParam == 13 ) { // alt-enter toggles full-screen + cvarSystem->SetCVarBool( "r_fullscreen", !renderSystem->IsFullScreen() ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + return 0; + } + // fall through for other keys + case WM_KEYDOWN: + key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 ); + if ( key == K_LCTRL || key == K_LALT || key == K_RCTRL || key == K_RALT ) { + // let direct-input handle this because windows sends Alt-Gr + // as two events (ctrl then alt) + break; + } + // D + if ( key == K_NUMLOCK ) { + key = K_PAUSE; + } else if ( key == K_PAUSE ) { + key = K_NUMLOCK; + } + Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 ); + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 ); + if ( key == K_PRINTSCREEN ) { + // don't queue printscreen keys. Since windows doesn't send us key + // down events for this, we handle queueing them with DirectInput + break; + } else if ( key == K_LCTRL || key == K_LALT || key == K_RCTRL || key == K_RALT ) { + // let direct-input handle this because windows sends Alt-Gr + // as two events (ctrl then alt) + break; + } + Sys_QueEvent( SE_KEY, key, false, 0, NULL, 0 ); + break; + + case WM_CHAR: + Sys_QueEvent( SE_CHAR, wParam, 0, 0, NULL, 0 ); + break; + + case WM_NCLBUTTONDOWN: +// win32.movingWindow = true; + break; + + case WM_ENTERSIZEMOVE: + win32.movingWindow = true; + break; + + case WM_EXITSIZEMOVE: + win32.movingWindow = false; + break; + + case WM_SIZING: + WIN_Sizing(wParam, (RECT *)lParam); + break; + case WM_MOUSEMOVE: { + if ( !common->IsInitialized() ) { + break; + } + + const bool isShellActive = ( game && ( game->Shell_IsActive() || game->IsPDAOpen() ) ); + const bool isConsoleActive = console->Active(); + + if ( win32.activeApp ) { + if ( isShellActive ) { + // If the shell is active, it will display its own cursor. + SetCursor( NULL ); + } else if ( isConsoleActive ) { + // The console is active but the shell is not. + // Show the Windows cursor. + SetCursor( LoadCursor( 0, IDC_ARROW ) ); + } else { + // The shell not active and neither is the console. + // This is normal gameplay, hide the cursor. + SetCursor( NULL ); + } + } else { + if ( !isShellActive ) { + // Always show the cursor when the window is in the background + SetCursor( LoadCursor( 0, IDC_ARROW ) ); + } else { + SetCursor( NULL ); + } + } + + const int x = GET_X_LPARAM( lParam ); + const int y = GET_Y_LPARAM( lParam ); + + // Generate an event + Sys_QueEvent( SE_MOUSE_ABSOLUTE, x, y, 0, NULL, 0 ); + + // Get a mouse leave message + TRACKMOUSEEVENT tme = { + sizeof( TRACKMOUSEEVENT ), + TME_LEAVE, + hWnd, + 0 + }; + + TrackMouseEvent( &tme ); + + return 0; + } + case WM_MOUSELEAVE: { + Sys_QueEvent( SE_MOUSE_LEAVE, 0, 0, 0, NULL, 0 ); + return 0; + } + case WM_LBUTTONDOWN: { + Sys_QueEvent( SE_KEY, K_MOUSE1, 1, 0, NULL, 0 ); + return 0; + } + case WM_LBUTTONUP: { + Sys_QueEvent( SE_KEY, K_MOUSE1, 0, 0, NULL, 0 ); + return 0; + } + case WM_RBUTTONDOWN: { + Sys_QueEvent( SE_KEY, K_MOUSE2, 1, 0, NULL, 0 ); + return 0; + } + case WM_RBUTTONUP: { + Sys_QueEvent( SE_KEY, K_MOUSE2, 0, 0, NULL, 0 ); + return 0; + } + case WM_MBUTTONDOWN: { + Sys_QueEvent( SE_KEY, K_MOUSE3, 1, 0, NULL, 0 ); + return 0; + } + case WM_MBUTTONUP: { + Sys_QueEvent( SE_KEY, K_MOUSE3, 0, 0, NULL, 0 ); + return 0; + } + case WM_XBUTTONDOWN: { + int button = GET_XBUTTON_WPARAM( wParam ); + if ( button == 1 ) { + Sys_QueEvent( SE_KEY, K_MOUSE4, 1, 0, NULL, 0 ); + } else if ( button == 2 ) { + Sys_QueEvent( SE_KEY, K_MOUSE5, 1, 0, NULL, 0 ); + } + return 0; + } + case WM_XBUTTONUP: { + int button = GET_XBUTTON_WPARAM( wParam ); + if ( button == 1 ) { + Sys_QueEvent( SE_KEY, K_MOUSE4, 0, 0, NULL, 0 ); + } else if ( button == 2 ) { + Sys_QueEvent( SE_KEY, K_MOUSE5, 0, 0, NULL, 0 ); + } + return 0; + } + case WM_MOUSEWHEEL: { + int delta = GET_WHEEL_DELTA_WPARAM( wParam ) / WHEEL_DELTA; + int key = delta < 0 ? K_MWHEELDOWN : K_MWHEELUP; + delta = abs( delta ); + while( delta-- > 0 ) { + Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 ); + Sys_QueEvent( SE_KEY, key, false, 0, NULL, 0 ); + } + break; + } + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} diff --git a/neo/ui/BindWindow.cpp b/neo/ui/BindWindow.cpp new file mode 100644 index 00000000..b6a7639c --- /dev/null +++ b/neo/ui/BindWindow.cpp @@ -0,0 +1,122 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "BindWindow.h" + + +void idBindWindow::CommonInit() { + bindName = ""; + waitingOnKey = false; +} + +idBindWindow::idBindWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idBindWindow::~idBindWindow() { + +} + + +const char *idBindWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + static char ret[ 256 ]; + + if (!(event->evType == SE_KEY && event->evValue2)) { + return ""; + } + + int key = event->evValue; + + if (waitingOnKey) { + waitingOnKey = false; + if (key == K_ESCAPE) { + idStr::snPrintf( ret, sizeof( ret ), "clearbind \"%s\"", bindName.GetName()); + } else { + idStr::snPrintf( ret, sizeof( ret ), "bind %i \"%s\"", key, bindName.GetName()); + } + return ret; + } else { + if (key == K_MOUSE1) { + waitingOnKey = true; + gui->SetBindHandler(this); + return ""; + } + } + + return ""; +} + +idWinVar *idBindWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + + if (idStr::Icmp(_name, "bind") == 0) { + return &bindName; + } + + return idWindow::GetWinVarByName(_name, fixup,owner); +} + +void idBindWindow::PostParse() { + idWindow::PostParse(); + bindName.SetGuiInfo( gui->GetStateDict(), bindName ); + bindName.Update(); + //bindName = state.GetString("bind"); + flags |= (WIN_HOLDCAPTURE | WIN_CANFOCUS); +} + +void idBindWindow::Draw(int time, float x, float y) { + idVec4 color = foreColor; + + idStr str; + if ( waitingOnKey ) { + str = idLocalization::GetString( "#str_07000" ); + } else if ( bindName.Length() ) { + str = bindName.c_str(); + } else { + str = idLocalization::GetString( "#str_07001" ); + } + + if ( waitingOnKey || ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) ) { + color = hoverColor; + } else { + hover = false; + } + + dc->DrawText(str, textScale, textAlign, color, textRect, false, -1); +} + +void idBindWindow::Activate( bool activate, idStr &act ) { + idWindow::Activate( activate, act ); + bindName.Update(); +} diff --git a/neo/ui/BindWindow.h b/neo/ui/BindWindow.h new file mode 100644 index 00000000..51f1c6ab --- /dev/null +++ b/neo/ui/BindWindow.h @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __BINDWINDOW_H +#define __BINDWINDOW_H + +class idUserInterfaceLocal; +class idBindWindow : public idWindow { +public: + idBindWindow(idUserInterfaceLocal *gui); + virtual ~idBindWindow(); + + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual size_t Allocated(){return idWindow::Allocated();}; +// +// + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL ); +// + virtual void Activate( bool activate, idStr &act ); + +private: + void CommonInit(); + idWinStr bindName; + bool waitingOnKey; +}; + +#endif // __BINDWINDOW_H diff --git a/neo/ui/ChoiceWindow.cpp b/neo/ui/ChoiceWindow.cpp new file mode 100644 index 00000000..ca472699 --- /dev/null +++ b/neo/ui/ChoiceWindow.cpp @@ -0,0 +1,396 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "ChoiceWindow.h" + +/* +============ +idChoiceWindow::InitVars +============ +*/ +void idChoiceWindow::InitVars( ) { + if ( cvarStr.Length() ) { + cvar = cvarSystem->Find( cvarStr ); + if ( !cvar ) { + common->Warning( "idChoiceWindow::InitVars: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); + return; + } + updateStr.Append( &cvarStr ); + } + if ( guiStr.Length() ) { + updateStr.Append( &guiStr ); + } + updateStr.SetGuiInfo( gui->GetStateDict() ); + updateStr.Update(); +} + +/* +============ +idChoiceWindow::CommonInit +============ +*/ +void idChoiceWindow::CommonInit() { + currentChoice = 0; + choiceType = 0; + cvar = NULL; + liveUpdate = true; + choices.Clear(); +} + +idChoiceWindow::idChoiceWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idChoiceWindow::~idChoiceWindow() { + +} + +void idChoiceWindow::RunNamedEvent( const char* eventName ) { + idStr event, group; + + if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { + event = eventName; + group = event.Mid( 10, event.Length() - 10 ); + if ( !group.Cmp( updateGroup ) ) { + UpdateVars( true, true ); + } + } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { + event = eventName; + group = event.Mid( 11, event.Length() - 11 ); + if ( !group.Cmp( updateGroup ) ) { + UpdateVars( false, true ); + } + } +} + +void idChoiceWindow::UpdateVars( bool read, bool force ) { + if ( force || liveUpdate ) { + if ( cvar && cvarStr.NeedsUpdate() ) { + if ( read ) { + cvarStr.Set( cvar->GetString() ); + } else { + cvar->SetString( cvarStr.c_str() ); + } + } + if ( !read && guiStr.NeedsUpdate() ) { + guiStr.Set( va( "%i", currentChoice ) ); + } + } +} + +const char *idChoiceWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + int key; + bool runAction = false; + bool runAction2 = false; + + if ( event->evType == SE_KEY ) { + key = event->evValue; + + if ( key == K_RIGHTARROW || key == K_KP_6 || key == K_MOUSE1) { + // never affects the state, but we want to execute script handlers anyway + if ( !event->evValue2 ) { + RunScript( ON_ACTIONRELEASE ); + return cmd; + } + currentChoice++; + if (currentChoice >= choices.Num()) { + currentChoice = 0; + } + runAction = true; + } + + if ( key == K_LEFTARROW || key == K_KP_4 || key == K_MOUSE2) { + // never affects the state, but we want to execute script handlers anyway + if ( !event->evValue2 ) { + RunScript( ON_ACTIONRELEASE ); + return cmd; + } + currentChoice--; + if (currentChoice < 0) { + currentChoice = choices.Num() - 1; + } + runAction = true; + } + + if ( !event->evValue2 ) { + // is a key release with no action catch + return ""; + } + + } else if ( event->evType == SE_CHAR ) { + + key = event->evValue; + + int potentialChoice = -1; + for ( int i = 0; i < choices.Num(); i++ ) { + if ( toupper(key) == toupper(choices[i][0]) ) { + if ( i < currentChoice && potentialChoice < 0 ) { + potentialChoice = i; + } else if ( i > currentChoice ) { + potentialChoice = -1; + currentChoice = i; + break; + } + } + } + if ( potentialChoice >= 0 ) { + currentChoice = potentialChoice; + } + + runAction = true; + runAction2 = true; + + } else { + return ""; + } + + if ( runAction ) { + RunScript( ON_ACTION ); + } + + if ( choiceType == 0 ) { + cvarStr.Set( va( "%i", currentChoice ) ); + } else if ( values.Num() ) { + cvarStr.Set( values[ currentChoice ] ); + } else { + cvarStr.Set( choices[ currentChoice ] ); + } + + UpdateVars( false ); + + if ( runAction2 ) { + RunScript( ON_ACTIONRELEASE ); + } + + return cmd; +} + +void idChoiceWindow::ValidateChoice() { + if ( currentChoice < 0 || currentChoice >= choices.Num() ) { + currentChoice = 0; + } + if ( choices.Num() == 0 ) { + choices.Append( "No Choices Defined" ); + } +} + +void idChoiceWindow::UpdateChoice() { + if ( !updateStr.Num() ) { + return; + } + UpdateVars( true ); + updateStr.Update(); + if ( choiceType == 0 ) { + // ChoiceType 0 stores current as an integer in either cvar or gui + // If both cvar and gui are defined then cvar wins, but they are both updated + if ( updateStr[ 0 ]->NeedsUpdate() ) { + currentChoice = atoi( updateStr[ 0 ]->c_str() ); + } + ValidateChoice(); + } else { + // ChoiceType 1 stores current as a cvar string + int c = ( values.Num() ) ? values.Num() : choices.Num(); + int i; + for ( i = 0; i < c; i++ ) { + if ( idStr::Icmp( cvarStr.c_str(), ( values.Num() ) ? values[i] : choices[i] ) == 0 ) { + break; + } + } + if (i == c) { + i = 0; + } + currentChoice = i; + ValidateChoice(); + } +} + +bool idChoiceWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if (idStr::Icmp(_name, "choicetype") == 0) { + choiceType = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "currentchoice") == 0) { + currentChoice = src->ParseInt(); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + + +idWinVar *idChoiceWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + if ( idStr::Icmp( _name, "choices" ) == 0 ) { + return &choicesStr; + } + if ( idStr::Icmp( _name, "values" ) == 0 ) { + return &choiceVals; + } + if ( idStr::Icmp( _name, "cvar" ) == 0 ) { + return &cvarStr; + } + if ( idStr::Icmp( _name, "gui" ) == 0 ) { + return &guiStr; + } + if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { + return &liveUpdate; + } + if ( idStr::Icmp( _name, "updateGroup" ) == 0 ) { + return &updateGroup; + } + + return idWindow::GetWinVarByName(_name, fixup, owner); +} + +// update the lists whenever the WinVar have changed +void idChoiceWindow::UpdateChoicesAndVals() { + idToken token; + idStr str2, str3; + idLexer src; + + if ( latchedChoices.Icmp( choicesStr ) ) { + choices.Clear(); + src.FreeSource(); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( choicesStr, choicesStr.Length(), "" ); + if ( src.IsLoaded() ) { + while( src.ReadToken( &token ) ) { + if ( token == ";" ) { + if ( str2.Length() ) { + str2.StripTrailingWhitespace(); + str2 = idLocalization::GetString( str2 ); + choices.Append(str2); + str2 = ""; + } + continue; + } + str2 += token; + str2 += " "; + } + if ( str2.Length() ) { + str2.StripTrailingWhitespace(); + choices.Append( str2 ); + } + } + latchedChoices = choicesStr.c_str(); + } + if ( choiceVals.Length() && latchedVals.Icmp( choiceVals ) ) { + values.Clear(); + src.FreeSource(); + src.SetFlags( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( choiceVals, choiceVals.Length(), "" ); + str2 = ""; + bool negNum = false; + if ( src.IsLoaded() ) { + while( src.ReadToken( &token ) ) { + if (token == "-") { + negNum = true; + continue; + } + if (token == ";") { + if (str2.Length()) { + str2.StripTrailingWhitespace(); + values.Append( str2 ); + str2 = ""; + } + continue; + } + if ( negNum ) { + str2 += "-"; + negNum = false; + } + str2 += token; + str2 += " "; + } + if ( str2.Length() ) { + str2.StripTrailingWhitespace(); + values.Append( str2 ); + } + } + if ( choices.Num() != values.Num() ) { + common->Warning( "idChoiceWindow:: gui '%s' window '%s' has value count unequal to choices count", gui->GetSourceFile(), name.c_str()); + } + latchedVals = choiceVals.c_str(); + } +} + +void idChoiceWindow::PostParse() { + idWindow::PostParse(); + UpdateChoicesAndVals(); + + InitVars(); + UpdateChoice(); + UpdateVars(false); + + flags |= WIN_CANFOCUS; +} + +void idChoiceWindow::Draw(int time, float x, float y) { + idVec4 color = foreColor; + + UpdateChoicesAndVals(); + UpdateChoice(); + + // FIXME: It'd be really cool if textAlign worked, but a lot of the guis have it set wrong because it used to not work + textAlign = 0; + + if ( textShadow ) { + idStr shadowText = choices[currentChoice]; + idRectangle shadowRect = textRect; + + shadowText.RemoveColors(); + shadowRect.x += textShadow; + shadowRect.y += textShadow; + + dc->DrawText( shadowText, textScale, textAlign, colorBlack, shadowRect, false, -1 ); + } + + if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + hover = false; + } + if ( flags & WIN_FOCUS ) { + color = hoverColor; + } + + dc->DrawText( choices[currentChoice], textScale, textAlign, color, textRect, false, -1 ); +} + +void idChoiceWindow::Activate( bool activate, idStr &act ) { + idWindow::Activate( activate, act ); + if ( activate ) { + // sets the gui state based on the current choice the window contains + UpdateChoice(); + } +} diff --git a/neo/ui/ChoiceWindow.h b/neo/ui/ChoiceWindow.h new file mode 100644 index 00000000..76434a68 --- /dev/null +++ b/neo/ui/ChoiceWindow.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __CHOICEWINDOW_H +#define __CHOICEWINDOW_H + +#include "Window.h" + +class idUserInterfaceLocal; +class idChoiceWindow : public idWindow { +public: + idChoiceWindow(idUserInterfaceLocal *gui); + virtual ~idChoiceWindow(); + + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual void Activate( bool activate, idStr &act ); + virtual size_t Allocated(){return idWindow::Allocated();}; + + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + void RunNamedEvent( const char* eventName ); + +private: + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void CommonInit(); + void UpdateChoice(); + void ValidateChoice(); + + void InitVars(); + // true: read the updated cvar from cvar system, gui from dict + // false: write to the cvar system, to the gui dict + // force == true overrides liveUpdate 0 + void UpdateVars( bool read, bool force = false ); + + void UpdateChoicesAndVals(); + + int currentChoice; + int choiceType; + idStr latchedChoices; + idWinStr choicesStr; + idStr latchedVals; + idWinStr choiceVals; + idStrList choices; + idStrList values; + + idWinStr guiStr; + idWinStr cvarStr; + idCVar * cvar; + idMultiWinVar updateStr; + + idWinBool liveUpdate; + idWinStr updateGroup; +}; + +#endif // __CHOICEWINDOW_H diff --git a/neo/ui/DeviceContext.cpp b/neo/ui/DeviceContext.cpp new file mode 100644 index 00000000..b2d13a3c --- /dev/null +++ b/neo/ui/DeviceContext.cpp @@ -0,0 +1,1094 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "../renderer/GuiModel.h" + +extern idCVar in_useJoystick; + +// bypass rendersystem to directly work on guiModel +extern idGuiModel * tr_guiModel; + +idVec4 idDeviceContext::colorPurple; +idVec4 idDeviceContext::colorOrange; +idVec4 idDeviceContext::colorYellow; +idVec4 idDeviceContext::colorGreen; +idVec4 idDeviceContext::colorBlue; +idVec4 idDeviceContext::colorRed; +idVec4 idDeviceContext::colorBlack; +idVec4 idDeviceContext::colorWhite; +idVec4 idDeviceContext::colorNone; + +void idDeviceContext::Init() { + xScale = 1.0f; + yScale = 1.0f; + xOffset = 0.0f; + yOffset = 0.0f; + whiteImage = declManager->FindMaterial("guis/assets/white.tga"); + whiteImage->SetSort( SS_GUI ); + activeFont = renderSystem->RegisterFont( "" ); + colorPurple = idVec4(1, 0, 1, 1); + colorOrange = idVec4(1, 1, 0, 1); + colorYellow = idVec4(0, 1, 1, 1); + colorGreen = idVec4(0, 1, 0, 1); + colorBlue = idVec4(0, 0, 1, 1); + colorRed = idVec4(1, 0, 0, 1); + colorWhite = idVec4(1, 1, 1, 1); + colorBlack = idVec4(0, 0, 0, 1); + colorNone = idVec4(0, 0, 0, 0); + cursorImages[CURSOR_ARROW] = declManager->FindMaterial("ui/assets/guicursor_arrow.tga"); + cursorImages[CURSOR_HAND] = declManager->FindMaterial("ui/assets/guicursor_hand.tga"); + cursorImages[CURSOR_HAND_JOY1] = declManager->FindMaterial("ui/assets/guicursor_hand_cross.tga"); + cursorImages[CURSOR_HAND_JOY2] = declManager->FindMaterial("ui/assets/guicursor_hand_circle.tga"); + cursorImages[CURSOR_HAND_JOY3] = declManager->FindMaterial("ui/assets/guicursor_hand_square.tga"); + cursorImages[CURSOR_HAND_JOY4] = declManager->FindMaterial("ui/assets/guicursor_hand_triangle.tga"); + cursorImages[CURSOR_HAND_JOY1] = declManager->FindMaterial("ui/assets/guicursor_hand_a.tga"); + cursorImages[CURSOR_HAND_JOY2] = declManager->FindMaterial("ui/assets/guicursor_hand_b.tga"); + cursorImages[CURSOR_HAND_JOY3] = declManager->FindMaterial("ui/assets/guicursor_hand_x.tga"); + cursorImages[CURSOR_HAND_JOY4] = declManager->FindMaterial("ui/assets/guicursor_hand_y.tga"); + scrollBarImages[SCROLLBAR_HBACK] = declManager->FindMaterial("ui/assets/scrollbarh.tga"); + scrollBarImages[SCROLLBAR_VBACK] = declManager->FindMaterial("ui/assets/scrollbarv.tga"); + scrollBarImages[SCROLLBAR_THUMB] = declManager->FindMaterial("ui/assets/scrollbar_thumb.tga"); + scrollBarImages[SCROLLBAR_RIGHT] = declManager->FindMaterial("ui/assets/scrollbar_right.tga"); + scrollBarImages[SCROLLBAR_LEFT] = declManager->FindMaterial("ui/assets/scrollbar_left.tga"); + scrollBarImages[SCROLLBAR_UP] = declManager->FindMaterial("ui/assets/scrollbar_up.tga"); + scrollBarImages[SCROLLBAR_DOWN] = declManager->FindMaterial("ui/assets/scrollbar_down.tga"); + cursorImages[CURSOR_ARROW]->SetSort( SS_GUI ); + cursorImages[CURSOR_HAND]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_HBACK]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_VBACK]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_THUMB]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_RIGHT]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_LEFT]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_UP]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_DOWN]->SetSort( SS_GUI ); + cursor = CURSOR_ARROW; + enableClipping = true; + overStrikeMode = true; + mat.Identity(); + matIsIdentity = true; + origin.Zero(); + initialized = true; +} + +void idDeviceContext::Shutdown() { + clipRects.Clear(); + Clear(); +} + +void idDeviceContext::Clear() { + initialized = false; +} + +idDeviceContext::idDeviceContext() { + Clear(); +} + +void idDeviceContext::SetTransformInfo(const idVec3 &org, const idMat3 &m) { + origin = org; + mat = m; + matIsIdentity = mat.IsIdentity(); +} + +// +// added method +void idDeviceContext::GetTransformInfo(idVec3& org, idMat3& m ) +{ + m = mat; + org = origin; +} +// + +void idDeviceContext::EnableClipping(bool b) { + enableClipping = b; +}; + +void idDeviceContext::PopClipRect() { + if (clipRects.Num()) { + clipRects.RemoveIndex(clipRects.Num()-1); + } +} + +void idDeviceContext::PushClipRect(idRectangle r) { + clipRects.Append(r); +} + +bool idDeviceContext::ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2) { + + if ( enableClipping == false || clipRects.Num() == 0 ) { + return false; + } + + int c = clipRects.Num(); + while( --c > 0 ) { + idRectangle *clipRect = &clipRects[c]; + + float ox = *x; + float oy = *y; + float ow = *w; + float oh = *h; + + if ( ow <= 0.0f || oh <= 0.0f ) { + break; + } + + if (*x < clipRect->x) { + *w -= clipRect->x - *x; + *x = clipRect->x; + } else if (*x > clipRect->x + clipRect->w) { + *x = *w = *y = *h = 0; + } + if (*y < clipRect->y) { + *h -= clipRect->y - *y; + *y = clipRect->y; + } else if (*y > clipRect->y + clipRect->h) { + *x = *w = *y = *h = 0; + } + if (*w > clipRect->w) { + *w = clipRect->w - *x + clipRect->x; + } else if (*x + *w > clipRect->x + clipRect->w) { + *w = clipRect->Right() - *x; + } + if (*h > clipRect->h) { + *h = clipRect->h - *y + clipRect->y; + } else if (*y + *h > clipRect->y + clipRect->h) { + *h = clipRect->Bottom() - *y; + } + + if ( s1 && s2 && t1 && t2 && ow > 0.0f ) { + float ns1, ns2, nt1, nt2; + // upper left + float u = ( *x - ox ) / ow; + ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u ); + + // upper right + u = ( *x + *w - ox ) / ow; + ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u ); + + // lower left + u = ( *y - oy ) / oh; + nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u ); + + // lower right + u = ( *y + *h - oy ) / oh; + nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u ); + + // set values + *s1 = ns1; + *s2 = ns2; + *t1 = nt1; + *t2 = nt2; + } + } + + return (*w == 0 || *h == 0) ? true : false; +} + +/* +============= +DrawStretchPic +============= +*/ +void idDeviceContext::DrawWinding( idWinding & w, const idMaterial * mat ) { + + idPlane p; + + p.Normal().Set( 1.0f, 0.0f, 0.0f ); + p.SetDist( 0.0f ); + w.ClipInPlace( p ); + + p.Normal().Set( -1.0f, 0.0f, 0.0f ); + p.SetDist( -SCREEN_WIDTH ); + w.ClipInPlace( p ); + + p.Normal().Set( 0.0f, 1.0f, 0.0f ); + p.SetDist( 0.0f ); + w.ClipInPlace( p ); + + p.Normal().Set( 0.0f, -1.0f, 0.0f ); + p.SetDist( -SCREEN_HEIGHT ); + w.ClipInPlace( p ); + + if ( w.GetNumPoints() < 3 ) { + return; + } + + int numIndexes = 0; + triIndex_t tempIndexes[(MAX_POINTS_ON_WINDING-2)*3]; + for ( int j = 2; j < w.GetNumPoints(); j++ ) { + tempIndexes[numIndexes++] = 0; + tempIndexes[numIndexes++] = j - 1; + tempIndexes[numIndexes++] = j; + } + assert( numIndexes == ( w.GetNumPoints() - 2 ) * 3 ); + + idDrawVert * verts = renderSystem->AllocTris( w.GetNumPoints(), tempIndexes, numIndexes, mat ); + if ( verts == NULL ) { + return; + } + uint32 currentColor = renderSystem->GetColor(); + + for ( int j = 0 ; j < w.GetNumPoints() ; j++ ) { + verts[j].xyz.x = xOffset + w[j].x * xScale; + verts[j].xyz.y = yOffset + w[j].y * yScale; + verts[j].xyz.z = w[j].z; + verts[j].SetTexCoord( w[j].s, w[j].t ); + verts[j].SetColor( currentColor ); + verts[j].ClearColor2(); + verts[j].SetNormal( 0.0f, 0.0f, 1.0f ); + verts[j].SetTangent( 1.0f, 0.0f, 0.0f ); + verts[j].SetBiTangent( 0.0f, 1.0f, 0.0f ); + } +} + +void idDeviceContext::DrawStretchPic(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader) { + if ( matIsIdentity ) { + renderSystem->DrawStretchPic( xOffset + x * xScale, yOffset + y * yScale, w * xScale, h * yScale, s1, t1, s2, t2, shader ); + return; + } + + idFixedWinding winding; + winding.AddPoint( idVec5( x, y, 0.0f, s1, t1 ) ); + winding.AddPoint( idVec5( x+w, y, 0.0f, s2, t1 ) ); + winding.AddPoint( idVec5( x+w, y+h, 0.0f, s2, t2 ) ); + winding.AddPoint( idVec5( x, y+h, 0.0f, s1, t2 ) ); + + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() -= origin; + winding[i].ToVec3() *= mat; + winding[i].ToVec3() += origin; + } + + DrawWinding( winding, shader ); +} + + +void idDeviceContext::DrawMaterial(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex, float scaley) { + + renderSystem->SetColor(color); + + float s0, s1, t0, t1; +// +// handle negative scales as well + if ( scalex < 0 ) + { + w *= -1; + scalex *= -1; + } + if ( scaley < 0 ) + { + h *= -1; + scaley *= -1; + } +// + if( w < 0 ) { // flip about vertical + w = -w; + s0 = 1 * scalex; + s1 = 0; + } + else { + s0 = 0; + s1 = 1 * scalex; + } + + if( h < 0 ) { // flip about horizontal + h = -h; + t0 = 1 * scaley; + t1 = 0; + } + else { + t0 = 0; + t1 = 1 * scaley; + } + + if ( ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) ) { + return; + } + + DrawStretchPic( x, y, w, h, s0, t0, s1, t1, mat); +} + +void idDeviceContext::DrawMaterialRotated(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex, float scaley, float angle) { + + renderSystem->SetColor(color); + + float s0, s1, t0, t1; + // + // handle negative scales as well + if ( scalex < 0 ) + { + w *= -1; + scalex *= -1; + } + if ( scaley < 0 ) + { + h *= -1; + scaley *= -1; + } + // + if( w < 0 ) { // flip about vertical + w = -w; + s0 = 1 * scalex; + s1 = 0; + } + else { + s0 = 0; + s1 = 1 * scalex; + } + + if( h < 0 ) { // flip about horizontal + h = -h; + t0 = 1 * scaley; + t1 = 0; + } + else { + t0 = 0; + t1 = 1 * scaley; + } + + if ( angle == 0.0f && ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) ) { + return; + } + + DrawStretchPicRotated( x, y, w, h, s0, t0, s1, t1, mat, angle); +} + +void idDeviceContext::DrawStretchPicRotated(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader, float angle) { + + idFixedWinding winding; + winding.AddPoint( idVec5( x, y, 0.0f, s1, t1 ) ); + winding.AddPoint( idVec5( x+w, y, 0.0f, s2, t1 ) ); + winding.AddPoint( idVec5( x+w, y+h, 0.0f, s2, t2 ) ); + winding.AddPoint( idVec5( x, y+h, 0.0f, s1, t2 ) ); + + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() -= origin; + winding[i].ToVec3() *= mat; + winding[i].ToVec3() += origin; + } + + //Generate a translation so we can translate to the center of the image rotate and draw + idVec3 origTrans; + origTrans.x = x+(w/2); + origTrans.y = y+(h/2); + origTrans.z = 0; + + + //Rotate the verts about the z axis before drawing them + idMat3 rotz; + rotz.Identity(); + float sinAng, cosAng; + idMath::SinCos( angle, sinAng, cosAng ); + rotz[0][0] = cosAng; + rotz[0][1] = sinAng; + rotz[1][0] = -sinAng; + rotz[1][1] = cosAng; + for (int i = 0; i < winding.GetNumPoints(); i++) { + winding[i].ToVec3() -= origTrans; + winding[i].ToVec3() *= rotz; + winding[i].ToVec3() += origTrans; + } + + DrawWinding( winding, shader ); +} + +void idDeviceContext::DrawFilledRect( float x, float y, float w, float h, const idVec4 &color) { + + if ( color.w == 0.0f ) { + return; + } + + renderSystem->SetColor(color); + + if (ClippedCoords(&x, &y, &w, &h, NULL, NULL, NULL, NULL)) { + return; + } + + DrawStretchPic( x, y, w, h, 0, 0, 0, 0, whiteImage); +} + + +void idDeviceContext::DrawRect( float x, float y, float w, float h, float size, const idVec4 &color) { + + if ( color.w == 0.0f ) { + return; + } + + renderSystem->SetColor(color); + + if (ClippedCoords(&x, &y, &w, &h, NULL, NULL, NULL, NULL)) { + return; + } + + DrawStretchPic( x, y, size, h, 0, 0, 0, 0, whiteImage ); + DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, whiteImage ); + DrawStretchPic( x, y, w, size, 0, 0, 0, 0, whiteImage ); + DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, whiteImage ); +} + +void idDeviceContext::DrawMaterialRect( float x, float y, float w, float h, float size, const idMaterial *mat, const idVec4 &color) { + + if ( color.w == 0.0f ) { + return; + } + + renderSystem->SetColor(color); + DrawMaterial( x, y, size, h, mat, color ); + DrawMaterial( x + w - size, y, size, h, mat, color ); + DrawMaterial( x, y, w, size, mat, color ); + DrawMaterial( x, y + h - size, w, size, mat, color ); +} + + +void idDeviceContext::SetCursor(int n) { + + if ( n > CURSOR_ARROW && n < CURSOR_COUNT ) { + + keyBindings_t binds = idKeyInput::KeyBindingsFromBinding( "_use", true ); + + keyNum_t keyNum = K_NONE; + if ( in_useJoystick.GetBool() ) { + keyNum = idKeyInput::StringToKeyNum( binds.gamepad.c_str() ); + } + + if ( keyNum != K_NONE ) { + + if ( keyNum == K_JOY1 ) { + cursor = CURSOR_HAND_JOY1; + } else if ( keyNum == K_JOY2 ) { + cursor = CURSOR_HAND_JOY2; + } else if ( keyNum == K_JOY3 ) { + cursor = CURSOR_HAND_JOY3; + } else if ( keyNum == K_JOY4 ) { + cursor = CURSOR_HAND_JOY4; + } + + } else { + cursor = CURSOR_HAND; + } + + } else { + cursor = CURSOR_ARROW; + } +} + +void idDeviceContext::DrawCursor(float *x, float *y, float size) { + if (*x < 0) { + *x = 0; + } + + if (*x >= VIRTUAL_WIDTH) { + *x = VIRTUAL_WIDTH; + } + + if (*y < 0) { + *y = 0; + } + + if (*y >= VIRTUAL_HEIGHT) { + *y = VIRTUAL_HEIGHT; + } + + renderSystem->SetColor(colorWhite); + DrawStretchPic( *x, *y, size, size, 0, 0, 1, 1, cursorImages[cursor]); +} +/* + ======================================================================================================================= + ======================================================================================================================= + */ + +void idDeviceContext::PaintChar( float x, float y, const scaledGlyphInfo_t & glyphInfo ) { + y -= glyphInfo.top; + x += glyphInfo.left; + + float w = glyphInfo.width; + float h = glyphInfo.height; + float s = glyphInfo.s1; + float t = glyphInfo.t1; + float s2 = glyphInfo.s2; + float t2 = glyphInfo.t2; + const idMaterial * hShader = glyphInfo.material; + + if ( ClippedCoords( &x, &y, &w, &h, &s, &t, &s2, &t2) ) { + return; + } + + DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader); +} + +int idDeviceContext::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) { + int len; + idVec4 newColor; + + idStr drawText = text; + int charIndex = 0; + + if ( text && color.w != 0.0f ) { + renderSystem->SetColor(color); + memcpy(&newColor[0], &color[0], sizeof(idVec4)); + len = drawText.Length(); + if (limit > 0 && len > limit) { + len = limit; + } + + float prevGlyphSkip = 0.0f; + + while ( charIndex < len ) { + uint32 textChar = drawText.UTF8Char( charIndex ); + + if ( idStr::IsColor( drawText.c_str() + charIndex ) ) { + if ( drawText[ charIndex++ ] == C_COLOR_DEFAULT ) { + newColor = color; + } else { + newColor = idStr::ColorForIndex( charIndex ); + newColor[3] = color[3]; + } + if (cursor == charIndex-1 || cursor == charIndex) { + float backup = 0.0f; + if ( prevGlyphSkip > 0.0f ) { + backup = ( prevGlyphSkip + adjust) / 5.0f; + } + if ( cursor == charIndex-1 ) { + backup *= 2.0f; + } else { + renderSystem->SetColor(newColor); + } + DrawEditCursor(x - backup, y, scale); + } + renderSystem->SetColor(newColor); + continue; + } else { + scaledGlyphInfo_t glyphInfo; + activeFont->GetScaledGlyph( scale, textChar, glyphInfo ); + prevGlyphSkip = glyphInfo.xSkip; + + PaintChar( x, y, glyphInfo ); + + if (cursor == charIndex-1) { + DrawEditCursor(x, y, scale); + } + x += glyphInfo.xSkip + adjust; + } + } + if (cursor == len) { + DrawEditCursor(x, y, scale); + } + } + return drawText.Length(); +} + +void idDeviceContext::SetSize( float width, float height ) { + xScale = VIRTUAL_WIDTH / width; + yScale = VIRTUAL_HEIGHT / height; +} + +void idDeviceContext::SetOffset( float x, float y ) { + xOffset = x; + yOffset = y; +} + +int idDeviceContext::CharWidth( const char c, float scale ) { + return idMath::Ftoi( activeFont->GetGlyphWidth( scale, c ) ); +} + +int idDeviceContext::TextWidth( const char *text, float scale, int limit ) { + if ( text == NULL ) { + return 0; + } + + int i; + float width = 0; + if ( limit > 0 ) { + for ( i = 0; text[i] != '\0' && i < limit; i++ ) { + if ( idStr::IsColor( text + i ) ) { + i++; + } else { + width += activeFont->GetGlyphWidth( scale, ((const unsigned char *)text)[i] ); + } + } + } else { + for ( i = 0; text[i] != '\0'; i++ ) { + if ( idStr::IsColor( text + i ) ) { + i++; + } else { + width += activeFont->GetGlyphWidth( scale, ((const unsigned char *)text)[i] ); + } + } + } + return idMath::Ftoi( width ); +} + +int idDeviceContext::TextHeight(const char *text, float scale, int limit) { + return idMath::Ftoi( activeFont->GetLineHeight( scale ) ); +} + +int idDeviceContext::MaxCharWidth(float scale) { + return idMath::Ftoi( activeFont->GetMaxCharWidth( scale ) ); +} + +int idDeviceContext::MaxCharHeight(float scale) { + return idMath::Ftoi( activeFont->GetLineHeight( scale ) ); +} + +const idMaterial *idDeviceContext::GetScrollBarImage(int index) { + if (index >= SCROLLBAR_HBACK && index < SCROLLBAR_COUNT) { + return scrollBarImages[index]; + } + return scrollBarImages[SCROLLBAR_HBACK]; +} + +// this only supports left aligned text +idRegion *idDeviceContext::GetTextRegion(const char *text, float textScale, idRectangle rectDraw, float xStart, float yStart) { + return NULL; +} + +void idDeviceContext::DrawEditCursor( float x, float y, float scale ) { + if ( (int)( idLib::frameNumber >> 4 ) & 1 ) { + return; + } + char cursorChar = (overStrikeMode) ? '_' : '|'; + scaledGlyphInfo_t glyphInfo; + activeFont->GetScaledGlyph( scale, cursorChar, glyphInfo ); + PaintChar( x, y, glyphInfo ); +} + +int idDeviceContext::DrawText( const char *text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor, bool calcOnly, idList *breaks, int limit ) { + int count = 0; + int charIndex = 0; + int lastBreak = 0; + float y = 0.0f; + float textWidth = 0.0f; + float textWidthAtLastBreak = 0.0f; + + float charSkip = MaxCharWidth( textScale ) + 1; + float lineSkip = MaxCharHeight( textScale ); + + bool lineBreak = false; + bool wordBreak = false; + + idStr drawText = text; + idStr textBuffer; + + if (!calcOnly && !(text && *text)) { + if (cursor == 0) { + renderSystem->SetColor(color); + DrawEditCursor(rectDraw.x, lineSkip + rectDraw.y, textScale); + } + return idMath::Ftoi( rectDraw.w / charSkip ); + } + + y = lineSkip + rectDraw.y; + + if ( breaks ) { + breaks->Append(0); + } + + while ( charIndex < drawText.Length() ) { + uint32 textChar = drawText.UTF8Char( charIndex ); + + // See if we need to start a new line. + if ( textChar == '\n' || textChar == '\r' || charIndex == drawText.Length() ) { + lineBreak = true; + if ( charIndex < drawText.Length() ) { + // New line character and we still have more text to read. + char nextChar = drawText[ charIndex + 1 ]; + if ( ( textChar == '\n' && nextChar == '\r' ) || ( textChar == '\r' && nextChar == '\n' ) ) { + // Just absorb extra newlines. + textChar = drawText.UTF8Char( charIndex ); + } + } + } + + // Check for escape colors if not then simply get the glyph width. + if ( textChar == C_COLOR_ESCAPE && charIndex < drawText.Length() ) { + textBuffer.AppendUTF8Char( textChar ); + textChar = drawText.UTF8Char( charIndex ); + } + + // If the character isn't a new line then add it to the text buffer. + if ( textChar != '\n' && textChar != '\r' ) { + textWidth += activeFont->GetGlyphWidth( textScale, textChar ); + textBuffer.AppendUTF8Char( textChar ); + } + + if ( !lineBreak && ( textWidth > rectDraw.w ) ) { + // The next character will cause us to overflow, if we haven't yet found a suitable + // break spot, set it to be this character + if ( textBuffer.Length() > 0 && lastBreak == 0 ) { + lastBreak = textBuffer.Length(); + textWidthAtLastBreak = textWidth; + } + wordBreak = true; + } else if ( lineBreak || ( wrap && ( textChar == ' ' || textChar == '\t' ) ) ) { + // The next character is in view, so if we are a break character, store our position + lastBreak = textBuffer.Length(); + textWidthAtLastBreak = textWidth; + } + + // We need to go to a new line + if ( lineBreak || wordBreak ) { + float x = rectDraw.x; + + if ( textWidthAtLastBreak > 0 ) { + textWidth = textWidthAtLastBreak; + } + + // Align text if needed + if (textAlign == ALIGN_RIGHT) { + x = rectDraw.x + rectDraw.w - textWidth; + } else if (textAlign == ALIGN_CENTER) { + x = rectDraw.x + (rectDraw.w - textWidth) / 2; + } + + if ( wrap || lastBreak > 0 ) { + // This is a special case to handle breaking in the middle of a word. + // if we didn't do this, the cursor would appear on the end of this line + // and the beginning of the next. + if ( wordBreak && cursor >= lastBreak && lastBreak == textBuffer.Length() ) { + cursor++; + } + } + + // Draw what's in the current text buffer. + if (!calcOnly) { + if ( lastBreak > 0 ) { + count += DrawText(x, y, textScale, color, textBuffer.Left( lastBreak ).c_str(), 0, 0, 0, cursor); + textBuffer = textBuffer.Right( textBuffer.Length() - lastBreak ); + } else { + count += DrawText(x, y, textScale, color, textBuffer.c_str(), 0, 0, 0, cursor); + textBuffer.Clear(); + } + } + + if ( cursor < lastBreak ) { + cursor = -1; + } else if ( cursor >= 0 ) { + cursor -= ( lastBreak + 1 ); + } + + // If wrap is disabled return at this point. + if ( !wrap ) { + return lastBreak; + } + + // If we've hit the allowed character limit then break. + if ( limit && count > limit ) { + break; + } + + y += lineSkip + 5; + + if ( !calcOnly && y > rectDraw.Bottom() ) { + break; + } + + // If breaks were requested then make a note of this one. + if (breaks) { + breaks->Append( drawText.Length() - charIndex ); + } + + // Reset necessary parms for next line. + lastBreak = 0; + textWidth = 0; + textWidthAtLastBreak = 0; + lineBreak = false; + wordBreak = false; + + // Reassess the remaining width + for ( int i = 0; i < textBuffer.Length(); ) { + if ( textChar != C_COLOR_ESCAPE ) { + textWidth += activeFont->GetGlyphWidth( textScale, textBuffer.UTF8Char( i ) ); + } + } + + continue; + } + } + + return idMath::Ftoi( rectDraw.w / charSkip ); +} + +/* +============= +idRectangle::String +============= +*/ +char *idRectangle::String() const { + static int index = 0; + static char str[ 8 ][ 48 ]; + char *s; + + // use an array so that multiple toString's won't collide + s = str[ index ]; + index = (index + 1)&7; + + sprintf( s, "%.2f %.2f %.2f %.2f", x, y, w, h ); + + return s; +} + + +/* +================================================================================================ + +OPTIMIZED VERSIONS + +================================================================================================ +*/ + +// this is only called for the cursor and debug strings, and it should +// scope properly with push/pop clipRect +void idDeviceContextOptimized::EnableClipping(bool b) { + if ( b == enableClipping ) { + return; + } + enableClipping = b; + if ( !enableClipping ) { + PopClipRect(); + } else { + // the actual value of the rect is irrelvent + PushClipRect( idRectangle( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ) ); + + // allow drawing beyond the normal bounds for debug text + // this also allows the cursor to draw outside, so we might want + // to make this exactly the screen bounds, since we aren't likely + // to ever turn on the gui debug text again... + clipX1 = -SCREEN_WIDTH; + clipX2 = SCREEN_WIDTH * 2; + clipY1 = -SCREEN_HEIGHT; + clipY2 = SCREEN_HEIGHT * 2; + } +}; + + +void idDeviceContextOptimized::PopClipRect() { + if (clipRects.Num()) { + clipRects.SetNum( clipRects.Num()-1 ); // don't resize the list, just change num + } + if ( clipRects.Num() > 0 ) { + const idRectangle & clipRect = clipRects[ clipRects.Num() - 1 ]; + clipX1 = clipRect.x; + clipY1 = clipRect.y; + clipX2 = clipRect.x + clipRect.w; + clipY2 = clipRect.y + clipRect.h; + } else { + clipX1 = 0; + clipY1 = 0; + clipX2 = SCREEN_WIDTH; + clipY2 = SCREEN_HEIGHT; + } +} + +static const idRectangle baseScreenRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); + +void idDeviceContextOptimized::PushClipRect(idRectangle r) { + const idRectangle & prev = ( clipRects.Num() == 0 ) ? baseScreenRect : clipRects[clipRects.Num()-1]; + + // instead of storing the rect, store the intersection of the rect + // with the previous rect, so ClippedCoords() only has to test against one rect + idRectangle intersection = prev; + intersection.ClipAgainst( r, false ); + clipRects.Append( intersection ); + + const idRectangle & clipRect = clipRects[ clipRects.Num() - 1 ]; + clipX1 = clipRect.x; + clipY1 = clipRect.y; + clipX2 = clipRect.x + clipRect.w; + clipY2 = clipRect.y + clipRect.h; +} + +bool idDeviceContextOptimized::ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2) { + const float ox = *x; + const float oy = *y; + const float ow = *w; + const float oh = *h; + + // presume visible first + if ( ox >= clipX1 && oy >= clipY1 && ox + ow <= clipX2 && oy + oh <= clipY2 ) { + return false; + } + + // do clipping + if ( ox < clipX1 ) { + *w -= clipX1 - ox; + *x = clipX1; + } else if ( ox > clipX2 ) { + return true; + } + if ( oy < clipY1) { + *h -= clipY1 - oy; + *y = clipY1; + } else if ( oy > clipY2) { + return true; + } + if ( *x + *w > clipX2 ) { + *w = clipX2 - *x; + } + if ( *y + *h > clipY2 ) { + *h = clipY2 - *y; + } + + if ( *w <= 0 || *h <= 0 ) { + return true; + } + + // filled rects won't pass in texcoords + if ( s1 ) { + float ns1, ns2, nt1, nt2; + // upper left + float u = ( *x - ox ) / ow; + ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u ); + + // upper right + u = ( *x + *w - ox ) / ow; + ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u ); + + // lower left + u = ( *y - oy ) / oh; + nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u ); + + // lower right + u = ( *y + *h - oy ) / oh; + nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u ); + + // set values + *s1 = ns1; + *s2 = ns2; + *t1 = nt1; + *t2 = nt2; + } + + // still needs to be drawn + return false; +} + +/* +============= +idDeviceContextOptimized::DrawText +============= +*/ +static triIndex_t quadPicIndexes[6] = { 3, 0, 2, 2, 0, 1 }; +int idDeviceContextOptimized::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor) { + if ( !matIsIdentity || cursor != -1 ) { + // fallback to old code + return idDeviceContext::DrawText( x, y, scale, color, text, adjust, limit, style, cursor ); + } + + idStr drawText = text; + + if ( drawText.Length() == 0 ) { + return 0; + } + if ( color.w == 0.0f ) { + return 0; + } + + const uint32 currentColor = PackColor( color ); + uint32 currentColorNativeByteOrder = LittleLong( currentColor ); + + int len = drawText.Length(); + if (limit > 0 && len > limit) { + len = limit; + } + + int charIndex = 0; + while ( charIndex < drawText.Length() ) { + uint32 textChar = drawText.UTF8Char( charIndex ); + if ( textChar == C_COLOR_ESCAPE ) { + // I'm not sure if inline text color codes are used anywhere in the game, + // they may only be needed for multi-color user names + idVec4 newColor; + uint32 colorIndex = drawText.UTF8Char( charIndex ); + if ( colorIndex == C_COLOR_DEFAULT ) { + newColor = color; + } else { + newColor = idStr::ColorForIndex( colorIndex ); + newColor[3] = color[3]; + } + renderSystem->SetColor(newColor); + currentColorNativeByteOrder = LittleLong( PackColor( newColor ) ); + continue; + } + + scaledGlyphInfo_t glyphInfo; + activeFont->GetScaledGlyph( scale, textChar, glyphInfo ); + + // PaintChar( x, y, glyphInfo ); + float drawY = y - glyphInfo.top; + float drawX = x + glyphInfo.left; + float w = glyphInfo.width; + float h = glyphInfo.height; + float s = glyphInfo.s1; + float t = glyphInfo.t1; + float s2 = glyphInfo.s2; + float t2 = glyphInfo.t2; + + if ( !ClippedCoords( &drawX, &drawY, &w, &h, &s, &t, &s2, &t2) ) { + float x1 = xOffset + drawX * xScale; + float x2 = xOffset + ( drawX + w ) * xScale; + float y1 = yOffset + drawY * yScale; + float y2 = yOffset + ( drawY + h ) * yScale; + idDrawVert * verts = tr_guiModel->AllocTris( 4, quadPicIndexes, 6, glyphInfo.material, 0, STEREO_DEPTH_TYPE_NONE ); + if ( verts != NULL ) { + verts[0].xyz[0] = x1; + verts[0].xyz[1] = y1; + verts[0].xyz[2] = 0.0f; + verts[0].SetTexCoord( s, t ); + verts[0].SetNativeOrderColor( currentColorNativeByteOrder ); + verts[0].ClearColor2(); + + verts[1].xyz[0] = x2; + verts[1].xyz[1] = y1; + verts[1].xyz[2] = 0.0f; + verts[1].SetTexCoord( s2, t ); + verts[1].SetNativeOrderColor( currentColorNativeByteOrder ); + verts[1].ClearColor2(); + + verts[2].xyz[0] = x2; + verts[2].xyz[1] = y2; + verts[2].xyz[2] = 0.0f; + verts[2].SetTexCoord( s2, t2 ); + verts[2].SetNativeOrderColor( currentColorNativeByteOrder ); + verts[2].ClearColor2(); + + verts[3].xyz[0] = x1; + verts[3].xyz[1] = y2; + verts[3].xyz[2] = 0.0f; + verts[3].SetTexCoord( s, t2 ); + verts[3].SetNativeOrderColor( currentColorNativeByteOrder ); + verts[3].ClearColor2(); + } + } + + x += glyphInfo.xSkip + adjust; + } + return drawText.Length(); +} diff --git a/neo/ui/DeviceContext.h b/neo/ui/DeviceContext.h new file mode 100644 index 00000000..2ba2b174 --- /dev/null +++ b/neo/ui/DeviceContext.h @@ -0,0 +1,176 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __DEVICECONTEXT_H__ +#define __DEVICECONTEXT_H__ + +// device context support for gui stuff +// + +#include "Rectangle.h" +#include "../renderer/Font.h" + +const int VIRTUAL_WIDTH = 640; +const int VIRTUAL_HEIGHT = 480; +const int BLINK_DIVISOR = 200; + +class idDeviceContext { +public: + idDeviceContext(); + ~idDeviceContext() { } + + void Init(); + void Shutdown(); + bool Initialized() { return initialized; } + void EnableLocalization(); + + void GetTransformInfo(idVec3& origin, idMat3& mat ); + + void SetTransformInfo(const idVec3 &origin, const idMat3 &mat); + void DrawMaterial(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex = 1.0, float scaley = 1.0); + void DrawRect(float x, float y, float width, float height, float size, const idVec4 &color); + void DrawFilledRect(float x, float y, float width, float height, const idVec4 &color); + int DrawText(const char *text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor = -1, bool calcOnly = false, idList *breaks = NULL, int limit = 0 ); + void DrawMaterialRect( float x, float y, float w, float h, float size, const idMaterial *mat, const idVec4 &color); + void DrawStretchPic(float x, float y, float w, float h, float s0, float t0, float s1, float t1, const idMaterial *mat); + void DrawMaterialRotated(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex = 1.0, float scaley = 1.0, float angle = 0.0f); + void DrawStretchPicRotated(float x, float y, float w, float h, float s0, float t0, float s1, float t1, const idMaterial *mat, float angle = 0.0f); + void DrawWinding( idWinding & w, const idMaterial * mat ); + + int CharWidth( const char c, float scale ); + int TextWidth(const char *text, float scale, int limit); + int TextHeight(const char *text, float scale, int limit); + int MaxCharHeight(float scale); + int MaxCharWidth(float scale); + + idRegion * GetTextRegion(const char *text, float textScale, idRectangle rectDraw, float xStart, float yStart); + + void SetSize( float width, float height ); + void SetOffset( float x, float y ); + + const idMaterial * GetScrollBarImage(int index); + + void DrawCursor(float *x, float *y, float size); + void SetCursor(int n); + + // clipping rects + virtual bool ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2); + virtual void PushClipRect(idRectangle r); + virtual void PopClipRect(); + virtual void EnableClipping(bool b); + + void SetFont( idFont * font ) { activeFont = font; } + + void SetOverStrike(bool b) { overStrikeMode = b; } + + bool GetOverStrike() { return overStrikeMode; } + + void DrawEditCursor(float x, float y, float scale); + + enum { + CURSOR_ARROW, + CURSOR_HAND, + CURSOR_HAND_JOY1, + CURSOR_HAND_JOY2, + CURSOR_HAND_JOY3, + CURSOR_HAND_JOY4, + CURSOR_COUNT + }; + + enum { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + + enum { + SCROLLBAR_HBACK, + SCROLLBAR_VBACK, + SCROLLBAR_THUMB, + SCROLLBAR_RIGHT, + SCROLLBAR_LEFT, + SCROLLBAR_UP, + SCROLLBAR_DOWN, + SCROLLBAR_COUNT + }; + + static idVec4 colorPurple; + static idVec4 colorOrange; + static idVec4 colorYellow; + static idVec4 colorGreen; + static idVec4 colorBlue; + static idVec4 colorRed; + static idVec4 colorWhite; + static idVec4 colorBlack; + static idVec4 colorNone; + +protected: + virtual int DrawText( float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor = -1); + void PaintChar( float x, float y, const scaledGlyphInfo_t & glyphInfo ); + void Clear(); + + const idMaterial * cursorImages[CURSOR_COUNT]; + const idMaterial * scrollBarImages[SCROLLBAR_COUNT]; + const idMaterial * whiteImage; + idFont * activeFont; + + float xScale; + float yScale; + float xOffset; + float yOffset; + + int cursor; + + idList clipRects; + + bool enableClipping; + + bool overStrikeMode; + + idMat3 mat; + bool matIsIdentity; + idVec3 origin; + bool initialized; +}; + +class idDeviceContextOptimized : public idDeviceContext { + + virtual bool ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2); + virtual void PushClipRect(idRectangle r); + virtual void PopClipRect(); + virtual void EnableClipping(bool b); + + virtual int DrawText( float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor = -1); + + float clipX1; + float clipX2; + float clipY1; + float clipY2; +}; + +#endif /* !__DEVICECONTEXT_H__ */ diff --git a/neo/ui/EditWindow.cpp b/neo/ui/EditWindow.cpp new file mode 100644 index 00000000..15fa7228 --- /dev/null +++ b/neo/ui/EditWindow.cpp @@ -0,0 +1,632 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SliderWindow.h" +#include "EditWindow.h" + + +bool idEditWindow::ParseInternalVar( const char *_name, idTokenParser *src ) { + if ( idStr::Icmp( _name, "maxchars" ) == 0) { + maxChars = src->ParseInt(); + return true; + } + if ( idStr::Icmp( _name, "numeric" ) == 0) { + numeric = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "wrap" ) == 0) { + wrap = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "readonly" ) == 0) { + readonly = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "forceScroll" ) == 0) { + forceScroll = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "source" ) == 0) { + ParseString( src, sourceFile ); + return true; + } + if ( idStr::Icmp( _name, "password" ) == 0 ) { + password = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "cvarMax" ) == 0) { + cvarMax = src->ParseInt(); + return true; + } + + return idWindow::ParseInternalVar( _name, src ); +} + +idWinVar *idEditWindow::GetWinVarByName( const char *_name, bool fixup, drawWin_t** owner ) { + if ( idStr::Icmp( _name, "cvar" ) == 0 ) { + return &cvarStr; + } + if ( idStr::Icmp( _name, "password" ) == 0 ) { + return &password; + } + if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { + return &liveUpdate; + } + if ( idStr::Icmp( _name, "cvarGroup" ) == 0 ) { + return &cvarGroup; + } + return idWindow::GetWinVarByName( _name, fixup, owner ); +} + +void idEditWindow::CommonInit() { + maxChars = 128; + numeric = false; + paintOffset = 0; + cursorPos = 0; + cursorLine = 0; + cvarMax = 0; + wrap = false; + sourceFile = ""; + scroller = NULL; + sizeBias = 0; + lastTextLength = 0; + forceScroll = false; + password = NULL; + cvar = NULL; + liveUpdate = true; + readonly = false; + + scroller = new (TAG_OLD_UI) idSliderWindow(gui); +} + + +idEditWindow::idEditWindow( idUserInterfaceLocal *g ) : idWindow(g) { + gui = g; + CommonInit(); +} + +idEditWindow::~idEditWindow() { + +} + +void idEditWindow::GainFocus() { + cursorPos = text.Length(); + EnsureCursorVisible(); +} + +void idEditWindow::Draw( int time, float x, float y ) { + idVec4 color = foreColor; + + UpdateCvar( true ); + + int len = text.Length(); + if ( len != lastTextLength ) { + scroller->SetValue( 0.0f ); + EnsureCursorVisible(); + lastTextLength = len; + } + float scale = textScale; + + idStr pass; + const char* buffer; + if ( password ) { + const char* temp = text; + for ( ; *temp; temp++ ) { + pass += "*"; + } + buffer = pass; + } else { + buffer = text; + } + + if ( cursorPos > len ) { + cursorPos = len; + } + + idRectangle rect = textRect; + + rect.x -= paintOffset; + rect.w += paintOffset; + + if ( wrap && scroller->GetHigh() > 0.0f ) { + float lineHeight = GetMaxCharHeight( ) + 5; + rect.y -= scroller->GetValue() * lineHeight; + rect.w -= sizeBias; + rect.h = ( breaks.Num() + 1 ) * lineHeight; + } + + if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + hover = false; + } + if ( flags & WIN_FOCUS ) { + color = hoverColor; + } + + dc->DrawText( buffer, scale, 0, color, rect, wrap, (flags & WIN_FOCUS) ? cursorPos : -1); +} + +/* +============= +idEditWindow::HandleEvent +============= +*/ +const char *idEditWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + static char buffer[ MAX_EDITFIELD ]; + + if ( wrap ) { + // need to call this to allow proper focus and capturing on embedded children + const char * ret = idWindow::HandleEvent( event, updateVisuals ); + if ( ret != NULL && *ret != NULL ) { + return ret; + } + } + + if ( ( event->evType != SE_CHAR && event->evType != SE_KEY ) ) { + return ""; + } + + idStr::Copynz( buffer, text.c_str(), sizeof( buffer ) ); + int key = event->evValue; + int len = text.Length(); + + if ( event->evType == SE_CHAR ) { + if ( key == '`' ) { + return ""; + } + + if ( updateVisuals ) { + *updateVisuals = true; + } + + if ( maxChars && len > maxChars ) { + len = maxChars; + } + + if ( readonly ) { + return ""; + } + + // + // ignore any non printable chars (except enter when wrap is enabled) + // + if ( wrap && (key == K_ENTER || key == K_KP_ENTER) ) { + } else if ( !idStr::CharIsPrintable( key ) ) { + return ""; + } + + if ( numeric ) { + if ( ( key < '0' || key > '9' ) && key != '.' ) { + return ""; + } + } + + if ( dc->GetOverStrike() ) { + if ( maxChars && cursorPos >= maxChars ) { + return ""; + } + } else { + if ( ( len == MAX_EDITFIELD - 1 ) || ( maxChars && len >= maxChars ) ) { + return ""; + } + memmove( &buffer[ cursorPos + 1 ], &buffer[ cursorPos ], len + 1 - cursorPos ); + } + + buffer[ cursorPos ] = key; + + text = buffer; + UpdateCvar( false ); + RunScript( ON_ACTION ); + + if ( cursorPos < len + 1 ) { + cursorPos++; + } + EnsureCursorVisible(); + + } else if ( event->evType == SE_KEY && event->evValue2 ) { + + if ( updateVisuals ) { + *updateVisuals = true; + } + + if ( key == K_DEL ) { + if ( readonly ) { + return ""; + } + if ( cursorPos < len ) { + memmove( &buffer[cursorPos], &buffer[cursorPos + 1], len - cursorPos); + text = buffer; + UpdateCvar( false ); + RunScript( ON_ACTION ); + } + return ""; + } + + if ( key == K_BACKSPACE ) { // ctrl-h is backspace + if ( readonly ) { + return ""; + } + if ( cursorPos > 0 ) { + if ( cursorPos >= len ) { + buffer[len - 1] = 0; + cursorPos = len - 1; + } else { + memmove( &buffer[ cursorPos - 1 ], &buffer[ cursorPos ], len + 1 - cursorPos); + cursorPos--; + } + + text = buffer; + UpdateCvar( false ); + RunScript( ON_ACTION ); + } + + return ""; + } + if ( key == K_RIGHTARROW ) { + if ( cursorPos < len ) { + if ( ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + // skip to next word + while( ( cursorPos < len ) && ( buffer[ cursorPos ] != ' ' ) ) { + cursorPos++; + } + + while( ( cursorPos < len ) && ( buffer[ cursorPos ] == ' ' ) ) { + cursorPos++; + } + } else { + if ( cursorPos < len ) { + cursorPos++; + } + } + } + + EnsureCursorVisible(); + + return ""; + } + + if ( key == K_LEFTARROW ) { + if ( ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + // skip to previous word + while( ( cursorPos > 0 ) && ( buffer[ cursorPos - 1 ] == ' ' ) ) { + cursorPos--; + } + + while( ( cursorPos > 0 ) && ( buffer[ cursorPos - 1 ] != ' ' ) ) { + cursorPos--; + } + } else { + if ( cursorPos > 0 ) { + cursorPos--; + } + } + + EnsureCursorVisible(); + + return ""; + } + + if ( key == K_HOME ) { + if ( ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) || cursorLine <= 0 || ( cursorLine >= breaks.Num() ) ) { + cursorPos = 0; + } else { + cursorPos = breaks[cursorLine]; + } + EnsureCursorVisible(); + return ""; + } + + if ( key == K_END ) { + if ( ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) || (cursorLine < -1) || ( cursorLine >= breaks.Num() - 1 ) ) { + cursorPos = len; + } else { + cursorPos = breaks[cursorLine + 1] - 1; + } + EnsureCursorVisible(); + return ""; + } + + if ( key == K_INS ) { + if ( !readonly ) { + dc->SetOverStrike( !dc->GetOverStrike() ); + } + return ""; + } + + if ( key == K_DOWNARROW ) { + if ( ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + scroller->SetValue( scroller->GetValue() + 1.0f ); + } else { + if ( cursorLine < breaks.Num() - 1 ) { + int offset = cursorPos - breaks[cursorLine]; + cursorPos = breaks[cursorLine + 1] + offset; + EnsureCursorVisible(); + } + } + } + + if (key == K_UPARROW ) { + if ( ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + scroller->SetValue( scroller->GetValue() - 1.0f ); + } else { + if ( cursorLine > 0 ) { + int offset = cursorPos - breaks[cursorLine]; + cursorPos = breaks[cursorLine - 1] + offset; + EnsureCursorVisible(); + } + } + } + + if ( key == K_ENTER || key == K_KP_ENTER ) { + RunScript( ON_ACTION ); + RunScript( ON_ENTER ); + return cmd; + } + + if ( key == K_ESCAPE ) { + RunScript( ON_ESC ); + return cmd; + } + + } else if ( event->evType == SE_KEY && !event->evValue2 ) { + if ( key == K_ENTER || key == K_KP_ENTER ) { + RunScript( ON_ENTERRELEASE ); + return cmd; + } else { + RunScript( ON_ACTIONRELEASE ); + } + } + + return ""; +} + +void idEditWindow::PostParse() { + idWindow::PostParse(); + + if ( maxChars == 0 ) { + maxChars = 10; + } + if ( sourceFile.Length() ) { + void *buffer; + fileSystem->ReadFile( sourceFile, &buffer ); + text = (char *) buffer; + fileSystem->FreeFile( buffer ); + } + + InitCvar(); + InitScroller(false); + + EnsureCursorVisible(); + + flags |= WIN_CANFOCUS; +} + +/* +================ +idEditWindow::InitScroller + +This is the same as in idListWindow +================ +*/ +void idEditWindow::InitScroller( bool horizontal ) +{ + const char *thumbImage = "guis/assets/scrollbar_thumb.tga"; + const char *barImage = "guis/assets/scrollbarv.tga"; + const char *scrollerName = "_scrollerWinV"; + + if (horizontal) { + barImage = "guis/assets/scrollbarh.tga"; + scrollerName = "_scrollerWinH"; + } + + const idMaterial *mat = declManager->FindMaterial( barImage ); + mat->SetSort( SS_GUI ); + sizeBias = mat->GetImageWidth(); + + idRectangle scrollRect; + if (horizontal) { + sizeBias = mat->GetImageHeight(); + scrollRect.x = 0; + scrollRect.y = (clientRect.h - sizeBias); + scrollRect.w = clientRect.w; + scrollRect.h = sizeBias; + } else { + scrollRect.x = (clientRect.w - sizeBias); + scrollRect.y = 0; + scrollRect.w = sizeBias; + scrollRect.h = clientRect.h; + } + + scroller->InitWithDefaults(scrollerName, scrollRect, foreColor, matColor, mat->GetName(), thumbImage, !horizontal, true); + InsertChild(scroller, NULL); + scroller->SetBuddy(this); +} + +void idEditWindow::HandleBuddyUpdate( idWindow *buddy ) { +} + +void idEditWindow::EnsureCursorVisible() +{ + if ( readonly ) { + cursorPos = -1; + } else if ( maxChars == 1 ) { + cursorPos = 0; + } + + if ( !dc ) { + return; + } + + SetFont(); + if ( !wrap ) { + int cursorX = 0; + if ( password ) { + cursorX = cursorPos * dc->CharWidth( '*', textScale ); + } else { + int i = 0; + while ( i < text.Length() && i < cursorPos ) { + if ( idStr::IsColor( &text[i] ) ) { + i += 2; + } else { + cursorX += dc->CharWidth( text[i], textScale ); + i++; + } + } + } + int maxWidth = GetMaxCharWidth( ); + int left = cursorX - maxWidth; + int right = ( cursorX - textRect.w ) + maxWidth; + + if ( paintOffset > left ) { + // When we go past the left side, we want the text to jump 6 characters + paintOffset = left - maxWidth * 6; + } + if ( paintOffset < right) { + paintOffset = right; + } + if ( paintOffset < 0 ) { + paintOffset = 0; + } + scroller->SetRange(0.0f, 0.0f, 1.0f); + + } else { + // Word wrap + + breaks.Clear(); + idRectangle rect = textRect; + rect.w -= sizeBias; + dc->DrawText(text, textScale, textAlign, colorWhite, rect, true, (flags & WIN_FOCUS) ? cursorPos : -1, true, &breaks ); + + int fit = textRect.h / (GetMaxCharHeight() + 5); + if ( fit < breaks.Num() + 1 ) { + scroller->SetRange(0, breaks.Num() + 1 - fit, 1); + } else { + // The text fits completely in the box + scroller->SetRange(0.0f, 0.0f, 1.0f); + } + + if ( forceScroll ) { + scroller->SetValue( breaks.Num() - fit ); + } else if ( readonly ) { + } else { + cursorLine = 0; + for ( int i = 1; i < breaks.Num(); i++ ) { + if ( cursorPos >= breaks[i] ) { + cursorLine = i; + } else { + break; + } + } + int topLine = idMath::Ftoi( scroller->GetValue() ); + if ( cursorLine < topLine ) { + scroller->SetValue( cursorLine ); + } else if ( cursorLine >= topLine + fit) { + scroller->SetValue( ( cursorLine - fit ) + 1 ); + } + } + } +} + +void idEditWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + if ( activate ) { + UpdateCvar( true, true ); + EnsureCursorVisible(); + } +} + +/* +============ +idEditWindow::InitCvar +============ +*/ +void idEditWindow::InitCvar( ) { + if ( cvarStr[0] == '\0' ) { + if ( text.GetName() == NULL ) { + common->Warning( "idEditWindow::InitCvar: gui '%s' window '%s' has an empty cvar string", gui->GetSourceFile(), name.c_str() ); + } + cvar = NULL; + return; + } + + cvar = cvarSystem->Find( cvarStr ); + if ( !cvar ) { + common->Warning( "idEditWindow::InitCvar: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); + return; + } +} + +/* +============ +idEditWindow::UpdateCvar +============ +*/ +void idEditWindow::UpdateCvar( bool read, bool force ) { + if ( force || liveUpdate ) { + if ( cvar ) { + if ( read ) { + text = cvar->GetString(); + } else { + cvar->SetString( text ); + if ( cvarMax && ( cvar->GetInteger() > cvarMax ) ) { + cvar->SetInteger( cvarMax ); + } + } + } + } +} + +/* +============ +idEditWindow::RunNamedEvent +============ +*/ +void idEditWindow::RunNamedEvent( const char* eventName ) { + idStr event, group; + + if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { + event = eventName; + group = event.Mid( 10, event.Length() - 10 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( true, true ); + } + } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { + event = eventName; + group = event.Mid( 11, event.Length() - 11 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( false, true ); + } + } +} diff --git a/neo/ui/EditWindow.h b/neo/ui/EditWindow.h new file mode 100644 index 00000000..2b2f4b4d --- /dev/null +++ b/neo/ui/EditWindow.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __EDITWINDOW_H__ +#define __EDITWINDOW_H__ + +#include "Window.h" + +const int MAX_EDITFIELD = 4096; + +class idUserInterfaceLocal; +class idSliderWindow; + +class idEditWindow : public idWindow { +public: + idEditWindow(idUserInterfaceLocal *gui); + virtual ~idEditWindow(); + + virtual void Draw( int time, float x, float y ); + virtual const char *HandleEvent( const sysEvent_t *event, bool *updateVisuals ); + virtual void PostParse(); + virtual void GainFocus(); + virtual size_t Allocated(){return idWindow::Allocated();}; + + virtual idWinVar * GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL ); + + virtual void HandleBuddyUpdate(idWindow *buddy); + virtual void Activate(bool activate, idStr &act); + + void RunNamedEvent( const char* eventName ); + +private: + + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + + void InitCvar(); + // true: read the updated cvar from cvar system + // false: write to the cvar system + // force == true overrides liveUpdate 0 + void UpdateCvar( bool read, bool force = false ); + + void CommonInit(); + void EnsureCursorVisible(); + void InitScroller( bool horizontal ); + + int maxChars; + int paintOffset; + int cursorPos; + int cursorLine; + int cvarMax; + bool wrap; + bool readonly; + bool numeric; + idStr sourceFile; + idSliderWindow * scroller; + idList breaks; + float sizeBias; + int textIndex; + int lastTextLength; + bool forceScroll; + idWinBool password; + + idWinStr cvarStr; + idCVar * cvar; + + idWinBool liveUpdate; + idWinStr cvarGroup; +}; + +#endif /* !__EDITWINDOW_H__ */ diff --git a/neo/ui/FieldWindow.cpp b/neo/ui/FieldWindow.cpp new file mode 100644 index 00000000..0cb4c747 --- /dev/null +++ b/neo/ui/FieldWindow.cpp @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "FieldWindow.h" + + +void idFieldWindow::CommonInit() { + cursorPos = 0; + lastTextLength = 0; + lastCursorPos = 0; + paintOffset = 0; + showCursor = false; +} + +idFieldWindow::idFieldWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idFieldWindow::~idFieldWindow() { + +} + +bool idFieldWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if (idStr::Icmp(_name, "cursorvar") == 0) { + ParseString(src, cursorVar); + return true; + } + if (idStr::Icmp(_name, "showcursor") == 0) { + showCursor = src->ParseBool(); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + + +void idFieldWindow::CalcPaintOffset(int len) { + lastCursorPos = cursorPos; + lastTextLength = len; + paintOffset = 0; + int tw = dc->TextWidth(text, textScale, -1); + if (tw < textRect.w) { + return; + } + while (tw > textRect.w && len > 0) { + tw = dc->TextWidth(text, textScale, --len); + paintOffset++; + } +} + + +void idFieldWindow::Draw(int time, float x, float y) { + float scale = textScale; + int len = text.Length(); + cursorPos = gui->State().GetInt( cursorVar ); + if (len != lastTextLength || cursorPos != lastCursorPos) { + CalcPaintOffset(len); + } + idRectangle rect = textRect; + if (paintOffset >= len) { + paintOffset = 0; + } + if (cursorPos > len) { + cursorPos = len; + } + dc->DrawText(&text[paintOffset], scale, 0, foreColor, rect, false, ((flags & WIN_FOCUS) || showCursor) ? cursorPos - paintOffset : -1); +} + diff --git a/neo/ui/FieldWindow.h b/neo/ui/FieldWindow.h new file mode 100644 index 00000000..9c587891 --- /dev/null +++ b/neo/ui/FieldWindow.h @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __FIELDWINDOW_H +#define __FIELDWINDOW_H + +#include "Window.h" + + +class idFieldWindow : public idWindow { +public: + idFieldWindow(idUserInterfaceLocal *gui); + virtual ~idFieldWindow(); + + virtual void Draw(int time, float x, float y); + +private: + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void CommonInit(); + void CalcPaintOffset(int len); + int cursorPos; + int lastTextLength; + int lastCursorPos; + int paintOffset; + bool showCursor; + idStr cursorVar; +}; + +#endif // __FIELDWINDOW_H diff --git a/neo/ui/GameBearShootWindow.cpp b/neo/ui/GameBearShootWindow.cpp new file mode 100644 index 00000000..1b5d01d9 --- /dev/null +++ b/neo/ui/GameBearShootWindow.cpp @@ -0,0 +1,893 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "GameBearShootWindow.h" + + +#define BEAR_GRAVITY 240 +#define BEAR_SIZE 24.f +#define BEAR_SHRINK_TIME 2000.f + +#define MAX_WINDFORCE 100.f + +idCVar bearTurretAngle( "bearTurretAngle", "0", CVAR_FLOAT, "" ); +idCVar bearTurretForce( "bearTurretForce", "200", CVAR_FLOAT, "" ); + +/* +***************************************************************************** +* BSEntity +**************************************************************************** +*/ +BSEntity::BSEntity(idGameBearShootWindow* _game) { + game = _game; + visible = true; + + entColor = colorWhite; + materialName = ""; + material = NULL; + width = height = 8; + rotation = 0.f; + rotationSpeed = 0.f; + fadeIn = false; + fadeOut = false; + + position.Zero(); + velocity.Zero(); +} + +BSEntity::~BSEntity() { +} + +/* +====================== +BSEntity::WriteToSaveGame +====================== +*/ +void BSEntity::WriteToSaveGame( idFile *savefile ) { + + game->WriteSaveGameString( materialName, savefile ); + + savefile->Write( &width, sizeof(width) ); + savefile->Write( &height, sizeof(height) ); + savefile->Write( &visible, sizeof(visible) ); + + savefile->Write( &entColor, sizeof(entColor) ); + savefile->Write( &position, sizeof(position) ); + savefile->Write( &rotation, sizeof(rotation) ); + savefile->Write( &rotationSpeed, sizeof(rotationSpeed) ); + savefile->Write( &velocity, sizeof(velocity) ); + + savefile->Write( &fadeIn, sizeof(fadeIn) ); + savefile->Write( &fadeOut, sizeof(fadeOut) ); +} + +/* +====================== +BSEntity::ReadFromSaveGame +====================== +*/ +void BSEntity::ReadFromSaveGame( idFile *savefile, idGameBearShootWindow* _game ) { + game = _game; + + game->ReadSaveGameString( materialName, savefile ); + SetMaterial( materialName ); + + savefile->Read( &width, sizeof(width) ); + savefile->Read( &height, sizeof(height) ); + savefile->Read( &visible, sizeof(visible) ); + + savefile->Read( &entColor, sizeof(entColor) ); + savefile->Read( &position, sizeof(position) ); + savefile->Read( &rotation, sizeof(rotation) ); + savefile->Read( &rotationSpeed, sizeof(rotationSpeed) ); + savefile->Read( &velocity, sizeof(velocity) ); + + savefile->Read( &fadeIn, sizeof(fadeIn) ); + savefile->Read( &fadeOut, sizeof(fadeOut) ); +} + +/* +====================== +BSEntity::SetMaterial +====================== +*/ +void BSEntity::SetMaterial(const char* name) { + materialName = name; + material = declManager->FindMaterial( name ); + material->SetSort( SS_GUI ); +} + +/* +====================== +BSEntity::SetSize +====================== +*/ +void BSEntity::SetSize( float _width, float _height ) { + width = _width; + height = _height; +} + +/* +====================== +BSEntity::SetVisible +====================== +*/ +void BSEntity::SetVisible( bool isVisible ) { + visible = isVisible; +} + +/* +====================== +BSEntity::Update +====================== +*/ +void BSEntity::Update( float timeslice ) { + + if ( !visible ) { + return; + } + + // Fades + if ( fadeIn && entColor.w < 1.f ) { + entColor.w += 1 * timeslice; + if ( entColor.w >= 1.f ) { + entColor.w = 1.f; + fadeIn = false; + } + } + if ( fadeOut && entColor.w > 0.f ) { + entColor.w -= 1 * timeslice; + if ( entColor.w <= 0.f ) { + entColor.w = 0.f; + fadeOut = false; + } + } + + // Move the entity + position += velocity * timeslice; + + // Rotate Entity + rotation += rotationSpeed * timeslice; +} + +/* +====================== +BSEntity::Draw +====================== +*/ +void BSEntity::Draw() { + if ( visible ) { + dc->DrawMaterialRotated( position.x, position.y, width, height, material, entColor, 1.0f, 1.0f, DEG2RAD(rotation) ); + } +} + +/* +***************************************************************************** +* idGameBearShootWindow +**************************************************************************** +*/ + +idGameBearShootWindow::idGameBearShootWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idGameBearShootWindow::~idGameBearShootWindow() { + entities.DeleteContents(true); +} + +/* +============================= +idGameBearShootWindow::WriteToSaveGame +============================= +*/ +void idGameBearShootWindow::WriteToSaveGame( idFile *savefile ) { + idWindow::WriteToSaveGame( savefile ); + + gamerunning.WriteToSaveGame( savefile ); + onFire.WriteToSaveGame( savefile ); + onContinue.WriteToSaveGame( savefile ); + onNewGame.WriteToSaveGame( savefile ); + + savefile->Write( &timeSlice, sizeof(timeSlice) ); + savefile->Write( &timeRemaining, sizeof(timeRemaining) ); + savefile->Write( &gameOver, sizeof(gameOver) ); + + savefile->Write( ¤tLevel, sizeof(currentLevel) ); + savefile->Write( &goalsHit, sizeof(goalsHit) ); + savefile->Write( &updateScore, sizeof(updateScore) ); + savefile->Write( &bearHitTarget, sizeof(bearHitTarget) ); + + savefile->Write( &bearScale, sizeof(bearScale) ); + savefile->Write( &bearIsShrinking, sizeof(bearIsShrinking) ); + savefile->Write( &bearShrinkStartTime, sizeof(bearShrinkStartTime) ); + + savefile->Write( &turretAngle, sizeof(turretAngle) ); + savefile->Write( &turretForce, sizeof(turretForce) ); + + savefile->Write( &windForce, sizeof(windForce) ); + savefile->Write( &windUpdateTime, sizeof(windUpdateTime) ); + + int numberOfEnts = entities.Num(); + savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); + + for ( int i=0; iWriteToSaveGame( savefile ); + } + + int index; + index = entities.FindIndex( turret ); + savefile->Write( &index, sizeof(index) ); + index = entities.FindIndex( bear ); + savefile->Write( &index, sizeof(index) ); + index = entities.FindIndex( helicopter ); + savefile->Write( &index, sizeof(index) ); + index = entities.FindIndex( goal ); + savefile->Write( &index, sizeof(index) ); + index = entities.FindIndex( wind ); + savefile->Write( &index, sizeof(index) ); + index = entities.FindIndex( gunblast ); + savefile->Write( &index, sizeof(index) ); +} + +/* +============================= +idGameBearShootWindow::ReadFromSaveGame +============================= +*/ +void idGameBearShootWindow::ReadFromSaveGame( idFile *savefile ) { + idWindow::ReadFromSaveGame( savefile ); + + // Remove all existing entities + entities.DeleteContents(true); + + gamerunning.ReadFromSaveGame( savefile ); + onFire.ReadFromSaveGame( savefile ); + onContinue.ReadFromSaveGame( savefile ); + onNewGame.ReadFromSaveGame( savefile ); + + savefile->Read( &timeSlice, sizeof(timeSlice) ); + savefile->Read( &timeRemaining, sizeof(timeRemaining) ); + savefile->Read( &gameOver, sizeof(gameOver) ); + + savefile->Read( ¤tLevel, sizeof(currentLevel) ); + savefile->Read( &goalsHit, sizeof(goalsHit) ); + savefile->Read( &updateScore, sizeof(updateScore) ); + savefile->Read( &bearHitTarget, sizeof(bearHitTarget) ); + + savefile->Read( &bearScale, sizeof(bearScale) ); + savefile->Read( &bearIsShrinking, sizeof(bearIsShrinking) ); + savefile->Read( &bearShrinkStartTime, sizeof(bearShrinkStartTime) ); + + savefile->Read( &turretAngle, sizeof(turretAngle) ); + savefile->Read( &turretForce, sizeof(turretForce) ); + + savefile->Read( &windForce, sizeof(windForce) ); + savefile->Read( &windUpdateTime, sizeof(windUpdateTime) ); + + int numberOfEnts; + savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); + + for ( int i=0; iReadFromSaveGame( savefile, this ); + entities.Append( ent ); + } + + int index; + savefile->Read( &index, sizeof(index) ); + turret = entities[index]; + savefile->Read( &index, sizeof(index) ); + bear = entities[index]; + savefile->Read( &index, sizeof(index) ); + helicopter = entities[index]; + savefile->Read( &index, sizeof(index) ); + goal = entities[index]; + savefile->Read( &index, sizeof(index) ); + wind = entities[index]; + savefile->Read( &index, sizeof(index) ); + gunblast = entities[index]; +} + +/* +============================= +idGameBearShootWindow::ResetGameState +============================= +*/ +void idGameBearShootWindow::ResetGameState() { + gamerunning = false; + gameOver = false; + onFire = false; + onContinue = false; + onNewGame = false; + + // Game moves forward 16 milliseconds every frame + timeSlice = 0.016f; + timeRemaining = 60.f; + goalsHit = 0; + updateScore = false; + bearHitTarget = false; + currentLevel = 1; + turretAngle = 0.f; + turretForce = 200.f; + windForce = 0.f; + windUpdateTime = 0; + + bearIsShrinking = false; + bearShrinkStartTime = 0; + bearScale = 1.f; +} + +/* +============================= +idGameBearShootWindow::CommonInit +============================= +*/ +void idGameBearShootWindow::CommonInit() { + BSEntity * ent; + + // Precache sounds + declManager->FindSound( "arcade_beargroan" ); + declManager->FindSound( "arcade_sargeshoot" ); + declManager->FindSound( "arcade_balloonpop" ); + declManager->FindSound( "arcade_levelcomplete1" ); + + // Precache dynamically used materials + declManager->FindMaterial( "game/bearshoot/helicopter_broken" ); + declManager->FindMaterial( "game/bearshoot/goal_dead" ); + declManager->FindMaterial( "game/bearshoot/gun_blast" ); + + ResetGameState(); + + ent = new (TAG_OLD_UI) BSEntity( this ); + turret = ent; + ent->SetMaterial( "game/bearshoot/turret" ); + ent->SetSize( 272, 144 ); + ent->position.x = -44; + ent->position.y = 260; + entities.Append( ent ); + + ent = new (TAG_OLD_UI) BSEntity( this ); + ent->SetMaterial( "game/bearshoot/turret_base" ); + ent->SetSize( 144, 160 ); + ent->position.x = 16; + ent->position.y = 280; + entities.Append( ent ); + + ent = new (TAG_OLD_UI) BSEntity( this ); + bear = ent; + ent->SetMaterial( "game/bearshoot/bear" ); + ent->SetSize( BEAR_SIZE, BEAR_SIZE ); + ent->SetVisible( false ); + ent->position.x = 0; + ent->position.y = 0; + entities.Append( ent ); + + ent = new (TAG_OLD_UI) BSEntity( this ); + helicopter = ent; + ent->SetMaterial( "game/bearshoot/helicopter" ); + ent->SetSize( 64, 64 ); + ent->position.x = 550; + ent->position.y = 100; + entities.Append( ent ); + + ent = new (TAG_OLD_UI) BSEntity( this ); + goal = ent; + ent->SetMaterial( "game/bearshoot/goal" ); + ent->SetSize( 64, 64 ); + ent->position.x = 550; + ent->position.y = 164; + entities.Append( ent ); + + ent = new (TAG_OLD_UI) BSEntity( this ); + wind = ent; + ent->SetMaterial( "game/bearshoot/wind" ); + ent->SetSize( 100, 40 ); + ent->position.x = 500; + ent->position.y = 430; + entities.Append( ent ); + + ent = new (TAG_OLD_UI) BSEntity( this ); + gunblast = ent; + ent->SetMaterial( "game/bearshoot/gun_blast" ); + ent->SetSize( 64, 64 ); + ent->SetVisible( false ); + entities.Append( ent ); +} + +/* +============================= +idGameBearShootWindow::HandleEvent +============================= +*/ +const char *idGameBearShootWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + int key = event->evValue; + + // need to call this to allow proper focus and capturing on embedded children + const char *ret = idWindow::HandleEvent(event, updateVisuals); + + if ( event->evType == SE_KEY ) { + + if ( !event->evValue2 ) { + return ret; + } + if ( key == K_MOUSE1) { + // Mouse was clicked + } else { + return ret; + } + } + + return ret; +} + +/* +============================= +idGameBearShootWindow::ParseInternalVar +============================= +*/ +bool idGameBearShootWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if ( idStr::Icmp(_name, "gamerunning") == 0 ) { + gamerunning = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onFire") == 0 ) { + onFire = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onContinue") == 0 ) { + onContinue = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onNewGame") == 0 ) { + onNewGame = src->ParseBool(); + return true; + } + + return idWindow::ParseInternalVar(_name, src); +} + +/* +============================= +idGameBearShootWindow::GetWinVarByName +============================= +*/ +idWinVar *idGameBearShootWindow::GetWinVarByName(const char *_name, bool winLookup, drawWin_t** owner) { + idWinVar *retVar = NULL; + + if ( idStr::Icmp(_name, "gamerunning") == 0 ) { + retVar = &gamerunning; + } else if ( idStr::Icmp(_name, "onFire") == 0 ) { + retVar = &onFire; + } else if ( idStr::Icmp(_name, "onContinue") == 0 ) { + retVar = &onContinue; + } else if ( idStr::Icmp(_name, "onNewGame") == 0 ) { + retVar = &onNewGame; + } + + if(retVar) { + return retVar; + } + + return idWindow::GetWinVarByName(_name, winLookup, owner); +} + +/* +============================= +idGameBearShootWindow::PostParse +============================= +*/ +void idGameBearShootWindow::PostParse() { + idWindow::PostParse(); +} + +/* +============================= +idGameBearShootWindow::Draw +============================= +*/ +void idGameBearShootWindow::Draw(int time, float x, float y) { + int i; + + //Update the game every frame before drawing + UpdateGame(); + + for( i = entities.Num()-1; i >= 0; i-- ) { + entities[i]->Draw(); + } +} + +/* +============================= +idGameBearShootWindow::Activate +============================= +*/ +const char *idGameBearShootWindow::Activate(bool activate) { + return ""; +} + +/* +============================= +idGameBearShootWindow::UpdateTurret +============================= +*/ +void idGameBearShootWindow::UpdateTurret() { + idVec2 pt; + idVec2 turretOrig; + idVec2 right; + float dot, angle; + + pt.x = gui->CursorX(); + pt.y = gui->CursorY(); + turretOrig.Set( 80.f, 348.f ); + + pt = pt - turretOrig; + pt.NormalizeFast(); + + right.x = 1.f; + right.y = 0.f; + + dot = pt * right; + + angle = RAD2DEG( acosf( dot ) ); + + turretAngle = idMath::ClampFloat( 0.f, 90.f, angle ); +} + +/* +============================= +idGameBearShootWindow::UpdateBear +============================= +*/ +void idGameBearShootWindow::UpdateBear() { + int time = gui->GetTime(); + bool startShrink = false; + + // Apply gravity + bear->velocity.y += BEAR_GRAVITY * timeSlice; + + // Apply wind + bear->velocity.x += windForce * timeSlice; + + // Check for collisions + if ( !bearHitTarget && !gameOver ) { + idVec2 bearCenter; + bool collision = false; + + bearCenter.x = bear->position.x + bear->width/2; + bearCenter.y = bear->position.y + bear->height/2; + + if ( bearCenter.x > (helicopter->position.x + 16) && bearCenter.x < (helicopter->position.x + helicopter->width - 29) ) { + if ( bearCenter.y > (helicopter->position.y + 12) && bearCenter.y < (helicopter->position.y + helicopter->height - 7) ) { + collision = true; + } + } + + if ( collision ) { + // balloons pop and bear tumbles to ground + helicopter->SetMaterial( "game/bearshoot/helicopter_broken" ); + helicopter->velocity.y = 230.f; + goal->velocity.y = 230.f; + common->SW()->PlayShaderDirectly( "arcade_balloonpop" ); + + bear->SetVisible( false ); + if ( bear->velocity.x > 0 ) { + bear->velocity.x *= -1.f; + } + bear->velocity *= 0.666f; + bearHitTarget = true; + updateScore = true; + startShrink = true; + } + } + + // Check for ground collision + if ( bear->position.y > 380 ) { + bear->position.y = 380; + + if ( bear->velocity.Length() < 25 ) { + bear->velocity.Zero(); + } else { + startShrink = true; + + bear->velocity.y *= -1.f; + bear->velocity *= 0.5f; + + if ( bearScale ) { + common->SW()->PlayShaderDirectly( "arcade_balloonpop" ); + } + } + } + + // Bear rotation is based on velocity + float angle; + idVec2 dir; + + dir = bear->velocity; + dir.NormalizeFast(); + + angle = RAD2DEG( atan2( dir.x, dir.y ) ); + bear->rotation = angle - 90; + + // Update Bear scale + if ( bear->position.x > 650 ) { + startShrink = true; + } + + if ( !bearIsShrinking && bearScale && startShrink ) { + bearShrinkStartTime = time; + bearIsShrinking = true; + } + + if ( bearIsShrinking ) { + if ( bearHitTarget ) { + bearScale = 1 - ( (float)(time - bearShrinkStartTime) / BEAR_SHRINK_TIME ); + } else { + bearScale = 1 - ( (float)(time - bearShrinkStartTime) / 750 ); + } + bearScale *= BEAR_SIZE; + bear->SetSize( bearScale, bearScale ); + + if ( bearScale < 0 ) { + gui->HandleNamedEvent( "EnableFireButton" ); + bearIsShrinking = false; + bearScale = 0.f; + + if ( bearHitTarget ) { + goal->SetMaterial( "game/bearshoot/goal" ); + goal->position.x = 550; + goal->position.y = 164; + goal->velocity.Zero(); + goal->velocity.y = (currentLevel-1) * 30; + goal->entColor.w = 0.f; + goal->fadeIn = true; + goal->fadeOut = false; + + helicopter->SetVisible( true ); + helicopter->SetMaterial( "game/bearshoot/helicopter" ); + helicopter->position.x = 550; + helicopter->position.y = 100; + helicopter->velocity.Zero(); + helicopter->velocity.y = goal->velocity.y; + helicopter->entColor.w = 0.f; + helicopter->fadeIn = true; + helicopter->fadeOut = false; + } + } + } +} + +/* +============================= +idGameBearShootWindow::UpdateHelicopter +============================= +*/ +void idGameBearShootWindow::UpdateHelicopter() { + + if ( bearHitTarget && bearIsShrinking ) { + if ( helicopter->velocity.y != 0 && helicopter->position.y > 264 ) { + helicopter->velocity.y = 0; + goal->velocity.y = 0; + + helicopter->SetVisible( false ); + goal->SetMaterial( "game/bearshoot/goal_dead" ); + common->SW()->PlayShaderDirectly( "arcade_beargroan", 1 ); + + helicopter->fadeOut = true; + goal->fadeOut = true; + } + } else if ( currentLevel > 1 ) { + int height = helicopter->position.y; + float speed = (currentLevel-1) * 30; + + if ( height > 240 ) { + helicopter->velocity.y = -speed; + goal->velocity.y = -speed; + } else if ( height < 30 ) { + helicopter->velocity.y = speed; + goal->velocity.y = speed; + } + } +} + +/* +============================= +idGameBearShootWindow::UpdateButtons +============================= +*/ +void idGameBearShootWindow::UpdateButtons() { + + if ( onFire ) { + idVec2 vec; + + gui->HandleNamedEvent( "DisableFireButton" ); + common->SW()->PlayShaderDirectly( "arcade_sargeshoot" ); + + bear->SetVisible( true ); + bearScale = 1.f; + bear->SetSize( BEAR_SIZE, BEAR_SIZE ); + + vec.x = idMath::Cos( DEG2RAD(turretAngle) ); + vec.x += ( 1 - vec.x ) * 0.18f; + vec.y = -idMath::Sin( DEG2RAD(turretAngle) ); + + turretForce = bearTurretForce.GetFloat(); + + bear->position.x = 80 + ( 96 * vec.x ); + bear->position.y = 334 + ( 96 * vec.y ); + bear->velocity.x = vec.x * turretForce; + bear->velocity.y = vec.y * turretForce; + + gunblast->position.x = 55 + ( 96 * vec.x ); + gunblast->position.y = 310 + ( 100 * vec.y ); + gunblast->SetVisible( true ); + gunblast->entColor.w = 1.f; + gunblast->rotation = turretAngle; + gunblast->fadeOut = true; + + bearHitTarget = false; + + onFire = false; + } +} + +/* +============================= +idGameBearShootWindow::UpdateScore +============================= +*/ +void idGameBearShootWindow::UpdateScore() { + + if ( gameOver ) { + gui->HandleNamedEvent( "GameOver" ); + return; + } + + goalsHit++; + gui->SetStateString( "player_score", va("%i", goalsHit ) ); + + // Check for level progression + if ( !(goalsHit % 5) ) { + currentLevel++; + gui->SetStateString( "current_level", va("%i", currentLevel ) ); + common->SW()->PlayShaderDirectly( "arcade_levelcomplete1", 3 ); + + timeRemaining += 30; + } +} + +/* +============================= +idGameBearShootWindow::UpdateGame +============================= +*/ +void idGameBearShootWindow::UpdateGame() { + int i; + + if ( onNewGame ) { + ResetGameState(); + if ( goal ) { + goal->position.x = 550; + goal->position.y = 164; + goal->velocity.Zero(); + } + if ( helicopter ) { + helicopter->position.x = 550; + helicopter->position.y = 100; + helicopter->velocity.Zero(); + } + if ( bear ) { + bear->SetVisible( false ); + } + + bearTurretAngle.SetFloat( 0.f ); + bearTurretForce.SetFloat( 200.f ); + + gamerunning = true; + } + if ( onContinue ) { + gameOver = false; + timeRemaining = 60.f; + + onContinue = false; + } + + if(gamerunning == true) { + int current_time = gui->GetTime(); + idRandom rnd( current_time ); + + // Check for button presses + UpdateButtons(); + + if ( bear ) { + UpdateBear(); + } + if ( helicopter && goal ) { + UpdateHelicopter(); + } + + // Update Wind + if ( windUpdateTime < current_time ) { + float scale; + int width; + + windForce = rnd.CRandomFloat() * ( MAX_WINDFORCE * 0.75f ); + if (windForce > 0) { + windForce += ( MAX_WINDFORCE * 0.25f ); + wind->rotation = 0; + } else { + windForce -= ( MAX_WINDFORCE * 0.25f ); + wind->rotation = 180; + } + + scale = 1.f - (( MAX_WINDFORCE - idMath::Fabs(windForce) ) / MAX_WINDFORCE); + width = 100*scale; + + if ( windForce < 0 ) { + wind->position.x = 500 - width + 1; + } else { + wind->position.x = 500; + } + wind->SetSize( width, 40 ); + + windUpdateTime = current_time + 7000 + rnd.RandomInt(5000); + } + + // Update turret rotation angle + if ( turret ) { + turretAngle = bearTurretAngle.GetFloat(); + turret->rotation = turretAngle; + } + + for( i = 0; i < entities.Num(); i++ ) { + entities[i]->Update( timeSlice ); + } + + // Update countdown timer + timeRemaining -= timeSlice; + timeRemaining = idMath::ClampFloat( 0.f, 99999.f, timeRemaining ); + gui->SetStateString( "time_remaining", va("%2.1f", timeRemaining ) ); + + if ( timeRemaining <= 0.f && !gameOver ) { + gameOver = true; + updateScore = true; + } + + if ( updateScore ) { + UpdateScore(); + updateScore = false; + } + } +} diff --git a/neo/ui/GameBearShootWindow.h b/neo/ui/GameBearShootWindow.h new file mode 100644 index 00000000..b5a2c674 --- /dev/null +++ b/neo/ui/GameBearShootWindow.h @@ -0,0 +1,132 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __GAME_BEARSHOOT_WINDOW_H__ +#define __GAME_BEARSHOOT_WINDOW_H__ + +class idGameBearShootWindow; + +class BSEntity { +public: + const idMaterial * material; + idStr materialName; + float width, height; + bool visible; + + idVec4 entColor; + idVec2 position; + float rotation; + float rotationSpeed; + idVec2 velocity; + + bool fadeIn; + bool fadeOut; + + idGameBearShootWindow * game; + +public: + BSEntity(idGameBearShootWindow* _game); + virtual ~BSEntity(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameBearShootWindow* _game ); + + void SetMaterial(const char* name); + void SetSize( float _width, float _height ); + void SetVisible( bool isVisible ); + + virtual void Update( float timeslice ); + virtual void Draw(); + +private: +}; + + +class idGameBearShootWindow : public idWindow { +public: + idGameBearShootWindow(idUserInterfaceLocal *gui); + ~idGameBearShootWindow(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual const char* Activate(bool activate); + virtual idWinVar * GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + +private: + void CommonInit(); + void ResetGameState(); + + void UpdateBear(); + void UpdateHelicopter(); + void UpdateTurret(); + void UpdateButtons(); + void UpdateGame(); + void UpdateScore(); + + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + +private: + + idWinBool gamerunning; + idWinBool onFire; + idWinBool onContinue; + idWinBool onNewGame; + + float timeSlice; + float timeRemaining; + bool gameOver; + + int currentLevel; + int goalsHit; + bool updateScore; + bool bearHitTarget; + + float bearScale; + bool bearIsShrinking; + int bearShrinkStartTime; + + float turretAngle; + float turretForce; + + float windForce; + int windUpdateTime; + + idList entities; + + BSEntity *turret; + BSEntity *bear; + BSEntity *helicopter; + BSEntity *goal; + BSEntity *wind; + BSEntity *gunblast; +}; + +#endif //__GAME_BEARSHOOT_WINDOW_H__ diff --git a/neo/ui/GameBustOutWindow.cpp b/neo/ui/GameBustOutWindow.cpp new file mode 100644 index 00000000..09fab0f7 --- /dev/null +++ b/neo/ui/GameBustOutWindow.cpp @@ -0,0 +1,1335 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "../renderer/Image.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "GameBustOutWindow.h" + +#define BALL_RADIUS 12.f +#define BALL_SPEED 250.f +#define BALL_MAXSPEED 450.f + +#define S_UNIQUE_CHANNEL 6 + +/* +***************************************************************************** +* BOEntity +**************************************************************************** +*/ +BOEntity::BOEntity(idGameBustOutWindow* _game) { + game = _game; + visible = true; + + materialName = ""; + material = NULL; + width = height = 8; + color = colorWhite; + powerup = POWERUP_NONE; + + position.Zero(); + velocity.Zero(); + + removed = false; + fadeOut = 0; +} + +BOEntity::~BOEntity() { +} + +/* +====================== +BOEntity::WriteToSaveGame +====================== +*/ +void BOEntity::WriteToSaveGame( idFile *savefile ) { + + savefile->Write( &visible, sizeof(visible) ); + + game->WriteSaveGameString( materialName, savefile ); + + savefile->Write( &width, sizeof(width) ); + savefile->Write( &height, sizeof(height) ); + + savefile->Write( &color, sizeof(color) ); + savefile->Write( &position, sizeof(position) ); + savefile->Write( &velocity, sizeof(velocity) ); + + savefile->Write( &powerup, sizeof(powerup) ); + savefile->Write( &removed, sizeof(removed) ); + savefile->Write( &fadeOut, sizeof(fadeOut) ); +} + +/* +====================== +BOEntity::ReadFromSaveGame +====================== +*/ +void BOEntity::ReadFromSaveGame( idFile *savefile, idGameBustOutWindow* _game ) { + game = _game; + + savefile->Read( &visible, sizeof(visible) ); + + game->ReadSaveGameString( materialName, savefile ); + SetMaterial( materialName ); + + savefile->Read( &width, sizeof(width) ); + savefile->Read( &height, sizeof(height) ); + + savefile->Read( &color, sizeof(color) ); + savefile->Read( &position, sizeof(position) ); + savefile->Read( &velocity, sizeof(velocity) ); + + savefile->Read( &powerup, sizeof(powerup) ); + savefile->Read( &removed, sizeof(removed) ); + savefile->Read( &fadeOut, sizeof(fadeOut) ); +} + +/* +====================== +BOEntity::SetMaterial +====================== +*/ +void BOEntity::SetMaterial(const char* name) { + materialName = name; + material = declManager->FindMaterial( name ); + material->SetSort( SS_GUI ); +} + +/* +====================== +BOEntity::SetSize +====================== +*/ +void BOEntity::SetSize( float _width, float _height ) { + width = _width; + height = _height; +} + +/* +====================== +BOEntity::SetVisible +====================== +*/ +void BOEntity::SetColor( float r, float g, float b, float a ) { + color.x = r; + color.y = g; + color.z = b; + color.w = a; +} + +/* +====================== +BOEntity::SetVisible +====================== +*/ +void BOEntity::SetVisible( bool isVisible ) { + visible = isVisible; +} + +/* +====================== +BOEntity::Update +====================== +*/ +void BOEntity::Update( float timeslice, int guiTime ) { + + if ( !visible ) { + return; + } + + // Move the entity + position += velocity * timeslice; + + // Fade out the ent + if ( fadeOut ) { + color.w -= timeslice * 2.5; + + if ( color.w <= 0.f ) { + color.w = 0.f; + removed = true; + } + } +} + +/* +====================== +BOEntity::Draw +====================== +*/ +void BOEntity::Draw() { + if ( visible ) { + dc->DrawMaterialRotated( position.x, position.y, width, height, material, color, 1.0f, 1.0f, DEG2RAD(0.f) ); + } +} + +/* +***************************************************************************** +* BOBrick +**************************************************************************** +*/ +BOBrick::BOBrick() { + ent = NULL; + x = y = width = height = 0; + powerup = POWERUP_NONE; + isBroken = false; +} + +BOBrick::BOBrick( BOEntity *_ent, float _x, float _y, float _width, float _height ) { + ent = _ent; + x = _x; + y = _y; + width = _width; + height = _height; + powerup = POWERUP_NONE; + + isBroken = false; + + ent->position.x = x; + ent->position.y = y; + ent->SetSize( width, height ); + ent->SetMaterial( "game/bustout/brick" ); + + ent->game->entities.Append( ent ); +} + +BOBrick::~BOBrick() { +} + +/* +====================== +BOBrick::WriteToSaveGame +====================== +*/ +void BOBrick::WriteToSaveGame( idFile *savefile ) { + savefile->Write( &x, sizeof(x) ); + savefile->Write( &y, sizeof(y) ); + savefile->Write( &width, sizeof(width) ); + savefile->Write( &height, sizeof(height) ); + + savefile->Write( &powerup, sizeof(powerup) ); + savefile->Write( &isBroken, sizeof(isBroken) ); + + int index = ent->game->entities.FindIndex( ent ); + savefile->Write( &index, sizeof(index) ); +} + +/* +====================== +BOBrick::ReadFromSaveGame +====================== +*/ +void BOBrick::ReadFromSaveGame( idFile *savefile, idGameBustOutWindow *game ) { + savefile->Read( &x, sizeof(x) ); + savefile->Read( &y, sizeof(y) ); + savefile->Read( &width, sizeof(width) ); + savefile->Read( &height, sizeof(height) ); + + savefile->Read( &powerup, sizeof(powerup) ); + savefile->Read( &isBroken, sizeof(isBroken) ); + + int index; + savefile->Read( &index, sizeof(index) ); + ent = game->entities[index]; +} + +/* +====================== +BOBrick::SetColor +====================== +*/ +void BOBrick::SetColor( idVec4 bcolor ) { + ent->SetColor( bcolor.x, bcolor.y, bcolor.z, bcolor.w ); +} + +/* +====================== +BOBrick::checkCollision +====================== +*/ +collideDir_t BOBrick::checkCollision( idVec2 pos, idVec2 vel ) { + idVec2 ptA, ptB; + float dist; + + collideDir_t result = COLLIDE_NONE; + + if ( isBroken ) { + return result; + } + + // Check for collision with each edge + idVec2 vec; + + // Bottom + ptA.x = x; + ptA.y = y + height; + + ptB.x = x + width; + ptB.y = y + height; + + if ( vel.y < 0 && pos.y > ptA.y ) { + if( pos.x > ptA.x && pos.x < ptB.x ) { + dist = pos.y - ptA.y; + + if ( dist < BALL_RADIUS ) { + result = COLLIDE_DOWN; + } + } else { + if ( pos.x <= ptA.x ) { + vec = pos - ptA; + } else { + vec = pos - ptB; + } + + if ( (idMath::Fabs(vec.y) > idMath::Fabs(vec.x)) && (vec.LengthFast() < BALL_RADIUS) ) { + result = COLLIDE_DOWN; + } + } + } + + if ( result == COLLIDE_NONE ) { + // Top + ptA.y = y; + ptB.y = y; + + if ( vel.y > 0 && pos.y < ptA.y ) { + if( pos.x > ptA.x && pos.x < ptB.x ) { + dist = ptA.y - pos.y; + + if ( dist < BALL_RADIUS ) { + result = COLLIDE_UP; + } + } else { + if ( pos.x <= ptA.x ) { + vec = pos - ptA; + } else { + vec = pos - ptB; + } + + if ( (idMath::Fabs(vec.y) > idMath::Fabs(vec.x)) && (vec.LengthFast() < BALL_RADIUS) ) { + result = COLLIDE_UP; + } + } + } + + if ( result == COLLIDE_NONE ) { + // Left side + ptA.x = x; + ptA.y = y; + + ptB.x = x; + ptB.y = y + height; + + if ( vel.x > 0 && pos.x < ptA.x ) { + if( pos.y > ptA.y && pos.y < ptB.y ) { + dist = ptA.x - pos.x; + + if ( dist < BALL_RADIUS ) { + result = COLLIDE_LEFT; + } + } else { + if ( pos.y <= ptA.y ) { + vec = pos - ptA; + } else { + vec = pos - ptB; + } + + if ( (idMath::Fabs(vec.x) >= idMath::Fabs(vec.y)) && (vec.LengthFast() < BALL_RADIUS) ) { + result = COLLIDE_LEFT; + } + } + } + + if ( result == COLLIDE_NONE ) { + // Right side + ptA.x = x + width; + ptB.x = x + width; + + if ( vel.x < 0 && pos.x > ptA.x ) { + if( pos.y > ptA.y && pos.y < ptB.y ) { + dist = pos.x - ptA.x; + + if ( dist < BALL_RADIUS ) { + result = COLLIDE_LEFT; + } + } else { + if ( pos.y <= ptA.y ) { + vec = pos - ptA; + } else { + vec = pos - ptB; + } + + if ( (idMath::Fabs(vec.x) >= idMath::Fabs(vec.y)) && (vec.LengthFast() < BALL_RADIUS) ) { + result = COLLIDE_LEFT; + } + } + } + + } + } + } + + return result; +} + +/* +***************************************************************************** +* idGameBustOutWindow +**************************************************************************** +*/ +idGameBustOutWindow::idGameBustOutWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idGameBustOutWindow::~idGameBustOutWindow() { + entities.DeleteContents(true); + + Mem_Free( levelBoardData ); +} + +/* +============================= +idGameBustOutWindow::WriteToSaveGame +============================= +*/ +void idGameBustOutWindow::WriteToSaveGame( idFile *savefile ) { + idWindow::WriteToSaveGame( savefile ); + + gamerunning.WriteToSaveGame( savefile ); + onFire.WriteToSaveGame( savefile ); + onContinue.WriteToSaveGame( savefile ); + onNewGame.WriteToSaveGame( savefile ); + onNewLevel.WriteToSaveGame( savefile ); + + savefile->Write( &timeSlice, sizeof(timeSlice) ); + savefile->Write( &gameOver, sizeof(gameOver) ); + savefile->Write( &numLevels, sizeof(numLevels) ); + + // Board Data is loaded when GUI is loaded, don't need to save + + savefile->Write( &numBricks, sizeof(numBricks) ); + savefile->Write( ¤tLevel, sizeof(currentLevel) ); + + savefile->Write( &updateScore, sizeof(updateScore) ); + savefile->Write( &gameScore, sizeof(gameScore) ); + savefile->Write( &nextBallScore, sizeof(nextBallScore) ); + + savefile->Write( &bigPaddleTime, sizeof(bigPaddleTime) ); + savefile->Write( &paddleVelocity, sizeof(paddleVelocity) ); + + savefile->Write( &ballSpeed, sizeof(ballSpeed) ); + savefile->Write( &ballsRemaining, sizeof(ballsRemaining) ); + savefile->Write( &ballsInPlay, sizeof(ballsInPlay) ); + savefile->Write( &ballHitCeiling, sizeof(ballHitCeiling) ); + + // Write Entities + int i; + int numberOfEnts = entities.Num(); + savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iWriteToSaveGame( savefile ); + } + + // Write Balls + numberOfEnts = balls.Num(); + savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iWrite( &ballIndex, sizeof(ballIndex) ); + } + + // Write Powerups + numberOfEnts = powerUps.Num(); + savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iWrite( &powerIndex, sizeof(powerIndex) ); + } + + // Write paddle + paddle->WriteToSaveGame( savefile ); + + // Write Bricks + int row; + for ( row=0; rowWrite( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iWriteToSaveGame( savefile ); + } + } +} + +/* +============================= +idGameBustOutWindow::ReadFromSaveGame +============================= +*/ +void idGameBustOutWindow::ReadFromSaveGame( idFile *savefile ) { + idWindow::ReadFromSaveGame( savefile ); + + // Clear out existing paddle and entities from GUI load + delete paddle; + entities.DeleteContents( true ); + + gamerunning.ReadFromSaveGame( savefile ); + onFire.ReadFromSaveGame( savefile ); + onContinue.ReadFromSaveGame( savefile ); + onNewGame.ReadFromSaveGame( savefile ); + onNewLevel.ReadFromSaveGame( savefile ); + + savefile->Read( &timeSlice, sizeof(timeSlice) ); + savefile->Read( &gameOver, sizeof(gameOver) ); + savefile->Read( &numLevels, sizeof(numLevels) ); + + // Board Data is loaded when GUI is loaded, don't need to save + + savefile->Read( &numBricks, sizeof(numBricks) ); + savefile->Read( ¤tLevel, sizeof(currentLevel) ); + + savefile->Read( &updateScore, sizeof(updateScore) ); + savefile->Read( &gameScore, sizeof(gameScore) ); + savefile->Read( &nextBallScore, sizeof(nextBallScore) ); + + savefile->Read( &bigPaddleTime, sizeof(bigPaddleTime) ); + savefile->Read( &paddleVelocity, sizeof(paddleVelocity) ); + + savefile->Read( &ballSpeed, sizeof(ballSpeed) ); + savefile->Read( &ballsRemaining, sizeof(ballsRemaining) ); + savefile->Read( &ballsInPlay, sizeof(ballsInPlay) ); + savefile->Read( &ballHitCeiling, sizeof(ballHitCeiling) ); + + int i; + int numberOfEnts; + + // Read entities + savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iReadFromSaveGame( savefile, this ); + entities.Append( ent ); + } + + // Read balls + savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iRead( &ballIndex, sizeof(ballIndex) ); + balls.Append( entities[ballIndex] ); + } + + // Read powerups + savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iRead( &powerIndex, sizeof(powerIndex) ); + balls.Append( entities[powerIndex] ); + } + + // Read paddle + paddle = new (TAG_OLD_UI) BOBrick(); + paddle->ReadFromSaveGame( savefile, this ); + + // Read board + int row; + for ( row=0; rowRead( &numberOfEnts, sizeof(numberOfEnts) ); + for ( i=0; iReadFromSaveGame( savefile, this ); + board[row].Append( brick ); + } + } +} + +/* +============================= +idGameBustOutWindow::ResetGameState +============================= +*/ +void idGameBustOutWindow::ResetGameState() { + gamerunning = false; + gameOver = false; + onFire = false; + onContinue = false; + onNewGame = false; + onNewLevel = false; + + // Game moves forward 16 milliseconds every frame + timeSlice = 0.016f; + ballsRemaining = 3; + ballSpeed = BALL_SPEED; + ballsInPlay = 0; + updateScore = false; + numBricks = 0; + currentLevel = 1; + gameScore = 0; + bigPaddleTime = 0; + nextBallScore = gameScore + 10000; + + ClearBoard(); +} + +/* +============================= +idGameBustOutWindow::CommonInit +============================= +*/ +void idGameBustOutWindow::CommonInit() { + BOEntity *ent; + + // Precache images + declManager->FindMaterial( "game/bustout/ball" ); + declManager->FindMaterial( "game/bustout/doublepaddle" ); + declManager->FindMaterial( "game/bustout/powerup_bigpaddle" ); + declManager->FindMaterial( "game/bustout/powerup_multiball" ); + declManager->FindMaterial( "game/bustout/brick" ); + + // Precache sounds + declManager->FindSound( "arcade_ballbounce" ); + declManager->FindSound( "arcade_brickhit" ); + declManager->FindSound( "arcade_missedball" ); + declManager->FindSound( "arcade_sadsound" ); + declManager->FindSound( "arcade_extraball" ); + declManager->FindSound( "arcade_powerup" ); + + ResetGameState(); + + numLevels = 0; + boardDataLoaded = false; + levelBoardData = NULL; + + // Create Paddle + ent = new (TAG_OLD_UI) BOEntity( this ); + paddle = new (TAG_OLD_UI) BOBrick( ent, 260.f, 440.f, 96.f, 24.f ); + paddle->ent->SetMaterial( "game/bustout/paddle" ); +} + +/* +============================= +idGameBustOutWindow::HandleEvent +============================= +*/ +const char *idGameBustOutWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + int key = event->evValue; + + // need to call this to allow proper focus and capturing on embedded children + const char *ret = idWindow::HandleEvent(event, updateVisuals); + + if ( event->evType == SE_KEY ) { + + if ( !event->evValue2 ) { + return ret; + } + if ( key == K_MOUSE1) { + // Mouse was clicked + if ( ballsInPlay == 0 ) { + BOEntity *ball = CreateNewBall(); + + ball->SetVisible( true ); + ball->position.x = paddle->ent->position.x + 48.f; + ball->position.y = 430.f; + + ball->velocity.x = ballSpeed; + ball->velocity.y = -ballSpeed*2.f; + ball->velocity.NormalizeFast(); + ball->velocity *= ballSpeed; + } + } else { + return ret; + } + } + + return ret; +} + +/* +============================= +idGameBustOutWindow::ParseInternalVar +============================= +*/ +bool idGameBustOutWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if ( idStr::Icmp(_name, "gamerunning") == 0 ) { + gamerunning = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onFire") == 0 ) { + onFire = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onContinue") == 0 ) { + onContinue = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onNewGame") == 0 ) { + onNewGame = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "onNewLevel") == 0 ) { + onNewLevel = src->ParseBool(); + return true; + } + if ( idStr::Icmp(_name, "numLevels") == 0 ) { + numLevels = src->ParseInt(); + + // Load all the level images + LoadBoardFiles(); + return true; + } + + return idWindow::ParseInternalVar(_name, src); +} + +/* +============================= +idGameBustOutWindow::GetWinVarByName +============================= +*/ +idWinVar *idGameBustOutWindow::GetWinVarByName(const char *_name, bool winLookup, drawWin_t** owner) { + idWinVar *retVar = NULL; + + if ( idStr::Icmp(_name, "gamerunning") == 0 ) { + retVar = &gamerunning; + } else if ( idStr::Icmp(_name, "onFire") == 0 ) { + retVar = &onFire; + } else if ( idStr::Icmp(_name, "onContinue") == 0 ) { + retVar = &onContinue; + } else if ( idStr::Icmp(_name, "onNewGame") == 0 ) { + retVar = &onNewGame; + } else if ( idStr::Icmp(_name, "onNewLevel") == 0 ) { + retVar = &onNewLevel; + } + + if(retVar) { + return retVar; + } + + return idWindow::GetWinVarByName(_name, winLookup, owner); +} + +/* +============================= +idGameBustOutWindow::PostParse +============================= +*/ +void idGameBustOutWindow::PostParse() { + idWindow::PostParse(); +} + +/* +============================= +idGameBustOutWindow::Draw +============================= +*/ +void idGameBustOutWindow::Draw(int time, float x, float y) { + int i; + + //Update the game every frame before drawing + UpdateGame(); + + for( i = entities.Num()-1; i >= 0; i-- ) { + entities[i]->Draw(); + } +} + +/* +============================= +idGameBustOutWindow::Activate +============================= +*/ +const char *idGameBustOutWindow::Activate(bool activate) { + return ""; +} + + +/* +============================= +idGameBustOutWindow::UpdateScore +============================= +*/ +void idGameBustOutWindow::UpdateScore() { + + if ( gameOver ) { + gui->HandleNamedEvent( "GameOver" ); + return; + } + + // Check for level progression + if ( numBricks == 0 ) { + ClearBalls(); + + gui->HandleNamedEvent( "levelComplete" ); + } + + // Check for new ball score + if ( gameScore >= nextBallScore ) { + ballsRemaining++; + gui->HandleNamedEvent( "extraBall" ); + + // Play sound + common->SW()->PlayShaderDirectly( "arcade_extraball", S_UNIQUE_CHANNEL ); + + nextBallScore = gameScore + 10000; + } + + gui->SetStateString( "player_score", va("%i", gameScore ) ); + gui->SetStateString( "balls_remaining", va("%i", ballsRemaining ) ); + gui->SetStateString( "current_level", va("%i", currentLevel ) ); + gui->SetStateString( "next_ball_score", va("%i", nextBallScore ) ); +} + +/* +============================= +idGameBustOutWindow::ClearBoard +============================= +*/ +void idGameBustOutWindow::ClearBoard() { + int i,j; + + ClearPowerups(); + + ballHitCeiling = false; + + for ( i=0; ient->removed = true; + } + + board[i].DeleteContents( true ); + } +} + +/* +============================= +idGameBustOutWindow::ClearPowerups +============================= +*/ +void idGameBustOutWindow::ClearPowerups() { + while ( powerUps.Num() ) { + powerUps[0]->removed = true; + powerUps.RemoveIndex( 0 ); + } +} + +/* +============================= +idGameBustOutWindow::ClearBalls +============================= +*/ +void idGameBustOutWindow::ClearBalls() { + while ( balls.Num() ) { + balls[0]->removed = true; + balls.RemoveIndex( 0 ); + } + + ballsInPlay = 0; +} + +/* +============================= +idGameBustOutWindow::LoadBoardFiles +============================= +*/ +void idGameBustOutWindow::LoadBoardFiles() { + int i; + int w,h; + ID_TIME_T time; + int boardSize; + byte *currentBoard; + + if ( boardDataLoaded ) { + return; + } + + boardSize = 9 * 12 * 4; + levelBoardData = (byte*)Mem_Alloc( boardSize * numLevels, TAG_CRAP ); + + currentBoard = levelBoardData; + + for ( i=0; iDWarning( "Hell Bust-Out level image not correct dimensions! (%d x %d)", w, h ); + } + + memcpy( currentBoard, pic, boardSize ); + Mem_Free(pic); + } + + currentBoard += boardSize; + } + + boardDataLoaded = true; +} + +/* +============================= +idGameBustOutWindow::SetCurrentBoard +============================= +*/ +void idGameBustOutWindow::SetCurrentBoard() { + int i,j; + int realLevel = ((currentLevel-1) % numLevels); + int boardSize; + byte *currentBoard; + float bx = 11.f; + float by = 24.f; + float stepx = 619.f / 9.f; + float stepy = ( 256 / 12.f ); + + boardSize = 9 * 12 * 4; + currentBoard = levelBoardData + ( realLevel * boardSize ); + + for ( j=0; jSetColor( bcolor ); + + pType = currentBoard[pixelindex + 3] / 255.f; + if ( pType > 0.f && pType < 1.f ) { + if ( pType < 0.5f ) { + brick->powerup = POWERUP_BIGPADDLE; + } else { + brick->powerup = POWERUP_MULTIBALL; + } + } + + board[j].Append( brick ); + numBricks++; + } + + bx += stepx; + } + + by += stepy; + } +} + +/* +============================= +idGameBustOutWindow::CreateNewBall +============================= +*/ +BOEntity * idGameBustOutWindow::CreateNewBall() { + BOEntity *ball; + + ball = new (TAG_OLD_UI) BOEntity( this ); + ball->position.x = 300.f; + ball->position.y = 416.f; + ball->SetMaterial( "game/bustout/ball" ); + ball->SetSize( BALL_RADIUS*2.f, BALL_RADIUS*2.f ); + ball->SetVisible( false ); + + ballsInPlay++; + + balls.Append( ball ); + entities.Append( ball ); + + return ball; +} + +/* +============================= +idGameBustOutWindow::CreatePowerup +============================= +*/ +BOEntity * idGameBustOutWindow::CreatePowerup( BOBrick *brick ) { + BOEntity *powerEnt = new (TAG_OLD_UI) BOEntity( this ); + + powerEnt->position.x = brick->x; + powerEnt->position.y = brick->y; + powerEnt->velocity.x = 0.f; + powerEnt->velocity.y = 64.f; + + powerEnt->powerup = brick->powerup; + + switch( powerEnt->powerup ) { + case POWERUP_BIGPADDLE: + powerEnt->SetMaterial( "game/bustout/powerup_bigpaddle" ); + break; + case POWERUP_MULTIBALL: + powerEnt->SetMaterial( "game/bustout/powerup_multiball" ); + break; + default: + powerEnt->SetMaterial( "textures/common/nodraw" ); + break; + } + + powerEnt->SetSize( 619/9, 256/12 ); + powerEnt->SetVisible( true ); + + powerUps.Append( powerEnt ); + entities.Append( powerEnt ); + + return powerEnt; +} + +/* +============================= +idGameBustOutWindow::UpdatePowerups +============================= +*/ +void idGameBustOutWindow::UpdatePowerups() { + idVec2 pos; + + for ( int i=0; i < powerUps.Num(); i++ ) { + BOEntity *pUp = powerUps[i]; + + // Check for powerup falling below screen + if ( pUp->position.y > 480 ) { + + powerUps.RemoveIndex( i ); + pUp->removed = true; + continue; + } + + // Check for the paddle catching a powerup + pos.x = pUp->position.x + ( pUp->width / 2 ); + pos.y = pUp->position.y + ( pUp->height / 2 ); + + collideDir_t collision = paddle->checkCollision( pos, pUp->velocity ); + if ( collision != COLLIDE_NONE ) { + BOEntity *ball; + + // Give the powerup to the player + switch( pUp->powerup ) { + case POWERUP_BIGPADDLE: + bigPaddleTime = gui->GetTime() + 15000; + break; + case POWERUP_MULTIBALL: + // Create 2 new balls in the spot of the existing ball + for ( int b=0; b<2; b++ ) { + ball = CreateNewBall(); + ball->position = balls[0]->position; + ball->velocity = balls[0]->velocity; + + if ( b == 0 ) { + ball->velocity.x -= 35.f; + } else { + ball->velocity.x += 35.f; + } + ball->velocity.NormalizeFast(); + ball->velocity *= ballSpeed; + + ball->SetVisible( true ); + } + break; + default: + break; + } + + // Play the sound + common->SW()->PlayShaderDirectly( "arcade_powerup", S_UNIQUE_CHANNEL ); + + // Remove it + powerUps.RemoveIndex( i ); + pUp->removed = true; + } + } +} + +/* +============================= +idGameBustOutWindow::UpdatePaddle +============================= +*/ +void idGameBustOutWindow::UpdatePaddle() { + idVec2 cursorPos; + float oldPos = paddle->x; + + cursorPos.x = gui->CursorX(); + cursorPos.y = gui->CursorY(); + + if ( bigPaddleTime > gui->GetTime() ) { + paddle->x = cursorPos.x - 80.f; + paddle->width = 160; + paddle->ent->width = 160; + paddle->ent->SetMaterial( "game/bustout/doublepaddle" ); + } else { + paddle->x = cursorPos.x - 48.f; + paddle->width = 96; + paddle->ent->width = 96; + paddle->ent->SetMaterial( "game/bustout/paddle" ); + } + paddle->ent->position.x = paddle->x; + + paddleVelocity = (paddle->x - oldPos); +} + +/* +============================= +idGameBustOutWindow::UpdateBall +============================= +*/ +void idGameBustOutWindow::UpdateBall() { + int ballnum,i,j; + bool playSoundBounce = false; + bool playSoundBrick = false; + static int bounceChannel = 1; + + if ( ballsInPlay == 0 ) { + return; + } + + for ( ballnum = 0; ballnum < balls.Num(); ballnum++ ) { + BOEntity *ball = balls[ballnum]; + + // Check for ball going below screen, lost ball + if ( ball->position.y > 480.f ) { + ball->removed = true; + continue; + } + + // Check world collision + if ( ball->position.y < 20 && ball->velocity.y < 0 ) { + ball->velocity.y = -ball->velocity.y; + + // Increase ball speed when it hits ceiling + if ( !ballHitCeiling ) { + ballSpeed *= 1.25f; + ballHitCeiling = true; + } + playSoundBounce = true; + } + + if ( ball->position.x > 608 && ball->velocity.x > 0 ) { + ball->velocity.x = -ball->velocity.x; + playSoundBounce = true; + } else if ( ball->position.x < 8 && ball->velocity.x < 0 ) { + ball->velocity.x = -ball->velocity.x; + playSoundBounce = true; + } + + // Check for Paddle collision + idVec2 ballCenter = ball->position + idVec2( BALL_RADIUS, BALL_RADIUS ); + collideDir_t collision = paddle->checkCollision( ballCenter, ball->velocity ); + + if ( collision == COLLIDE_UP ) { + if ( ball->velocity.y > 0 ) { + idVec2 paddleVec( paddleVelocity*2, 0 ); + float centerX; + + if ( bigPaddleTime > gui->GetTime() ) { + centerX = paddle->x + 80.f; + } else { + centerX = paddle->x + 48.f; + } + + ball->velocity.y = -ball->velocity.y; + + paddleVec.x += (ball->position.x - centerX) * 2; + + ball->velocity += paddleVec; + ball->velocity.NormalizeFast(); + ball->velocity *= ballSpeed; + + playSoundBounce = true; + } + } else if ( collision == COLLIDE_LEFT || collision == COLLIDE_RIGHT ) { + if ( ball->velocity.y > 0 ) { + ball->velocity.x = -ball->velocity.x; + playSoundBounce = true; + } + } + + collision = COLLIDE_NONE; + + // Check for collision with bricks + for ( i=0; icheckCollision( ballCenter, ball->velocity ); + if ( collision ) { + // Now break the brick if there was a collision + brick->isBroken = true; + brick->ent->fadeOut = true; + + if ( brick->powerup > POWERUP_NONE ) { + verify( CreatePowerup( brick ) != NULL ); + } + + numBricks--; + gameScore += 100; + updateScore = true; + + // Go ahead an forcibly remove the last brick, no fade + if ( numBricks == 0 ) { + brick->ent->removed = true; + } + board[i].Remove( brick ); + break; + } + } + + if ( collision ) { + playSoundBrick = true; + break; + } + } + + if ( collision == COLLIDE_DOWN || collision == COLLIDE_UP ) { + ball->velocity.y *= -1; + } else if ( collision == COLLIDE_LEFT || collision == COLLIDE_RIGHT ) { + ball->velocity.x *= -1; + } + + if ( playSoundBounce ) { + common->SW()->PlayShaderDirectly( "arcade_ballbounce", bounceChannel ); + } else if ( playSoundBrick ) { + common->SW()->PlayShaderDirectly( "arcade_brickhit", bounceChannel ); + } + + if ( playSoundBounce || playSoundBrick ) { + bounceChannel++; + if ( bounceChannel == 4 ) { + bounceChannel = 1; + } + } + } + + // Check to see if any balls were removed from play + for ( ballnum=0; ballnumremoved ) { + ballsInPlay--; + balls.RemoveIndex( ballnum ); + } + } + + // If all the balls were removed, update the game accordingly + if ( ballsInPlay == 0 ) { + if ( ballsRemaining == 0 ) { + gameOver = true; + + // Game Over sound + common->SW()->PlayShaderDirectly( "arcade_sadsound", S_UNIQUE_CHANNEL ); + } else { + ballsRemaining--; + + // Ball was lost, but game is not over + common->SW()->PlayShaderDirectly( "arcade_missedball", S_UNIQUE_CHANNEL ); + } + + ClearPowerups(); + updateScore = true; + } +} + +/* +============================= +idGameBustOutWindow::UpdateGame +============================= +*/ +void idGameBustOutWindow::UpdateGame() { + int i; + + if ( onNewGame ) { + ResetGameState(); + + // Create Board + SetCurrentBoard(); + + gamerunning = true; + } + if ( onContinue ) { + gameOver = false; + ballsRemaining = 3; + + onContinue = false; + } + if ( onNewLevel ) { + currentLevel++; + + ClearBoard(); + SetCurrentBoard(); + + ballSpeed = BALL_SPEED * ( 1.f + ((float)currentLevel/5.f) ); + if ( ballSpeed > BALL_MAXSPEED ) { + ballSpeed = BALL_MAXSPEED; + } + updateScore = true; + onNewLevel = false; + } + + if(gamerunning == true) { + + UpdatePaddle(); + UpdateBall(); + UpdatePowerups(); + + for( i = 0; i < entities.Num(); i++ ) { + entities[i]->Update( timeSlice, gui->GetTime() ); + } + + // Delete entities that need to be deleted + for( i = entities.Num()-1; i >= 0; i-- ) { + if( entities[i]->removed ) { + BOEntity* ent = entities[i]; + delete ent; + entities.RemoveIndex(i); + } + } + + if ( updateScore ) { + UpdateScore(); + updateScore = false; + } + } +} diff --git a/neo/ui/GameBustOutWindow.h b/neo/ui/GameBustOutWindow.h new file mode 100644 index 00000000..a09ae389 --- /dev/null +++ b/neo/ui/GameBustOutWindow.h @@ -0,0 +1,185 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __GAME_BUSTOUT_WINDOW_H__ +#define __GAME_BUSTOUT_WINDOW_H__ + +class idGameBustOutWindow; + +typedef enum { + POWERUP_NONE = 0, + POWERUP_BIGPADDLE, + POWERUP_MULTIBALL +} powerupType_t; + +class BOEntity { +public: + bool visible; + + idStr materialName; + const idMaterial * material; + float width, height; + idVec4 color; + idVec2 position; + idVec2 velocity; + + powerupType_t powerup; + + bool removed; + bool fadeOut; + + idGameBustOutWindow * game; + +public: + BOEntity(idGameBustOutWindow* _game); + virtual ~BOEntity(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameBustOutWindow* _game ); + + void SetMaterial(const char* name); + void SetSize( float _width, float _height ); + void SetColor( float r, float g, float b, float a ); + void SetVisible( bool isVisible ); + + virtual void Update( float timeslice, int guiTime ); + virtual void Draw(); + +private: +}; + +typedef enum { + COLLIDE_NONE = 0, + COLLIDE_DOWN, + COLLIDE_UP, + COLLIDE_LEFT, + COLLIDE_RIGHT +} collideDir_t; + +class BOBrick { +public: + float x; + float y; + float width; + float height; + powerupType_t powerup; + + bool isBroken; + + BOEntity *ent; + +public: + BOBrick(); + BOBrick( BOEntity *_ent, float _x, float _y, float _width, float _height ); + ~BOBrick(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameBustOutWindow *game ); + + void SetColor( idVec4 bcolor ); + collideDir_t checkCollision( idVec2 pos, idVec2 vel ); + +private: +}; + +#define BOARD_ROWS 12 + +class idGameBustOutWindow : public idWindow { +public: + idGameBustOutWindow(idUserInterfaceLocal *gui); + ~idGameBustOutWindow(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual const char* Activate(bool activate); + virtual idWinVar * GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + idList entities; + +private: + void CommonInit(); + void ResetGameState(); + + void ClearBoard(); + void ClearPowerups(); + void ClearBalls(); + + void LoadBoardFiles(); + void SetCurrentBoard(); + void UpdateGame(); + void UpdatePowerups(); + void UpdatePaddle(); + void UpdateBall(); + void UpdateScore(); + + BOEntity * CreateNewBall(); + BOEntity * CreatePowerup( BOBrick *brick ); + + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + +private: + + idWinBool gamerunning; + idWinBool onFire; + idWinBool onContinue; + idWinBool onNewGame; + idWinBool onNewLevel; + + float timeSlice; + bool gameOver; + + int numLevels; + byte * levelBoardData; + bool boardDataLoaded; + + int numBricks; + int currentLevel; + + bool updateScore; + int gameScore; + int nextBallScore; + + int bigPaddleTime; + float paddleVelocity; + + float ballSpeed; + int ballsRemaining; + int ballsInPlay; + bool ballHitCeiling; + + idList balls; + idList powerUps; + + BOBrick *paddle; + idList board[BOARD_ROWS]; +}; + +#endif //__GAME_BUSTOUT_WINDOW_H__ diff --git a/neo/ui/GameSSDWindow.cpp b/neo/ui/GameSSDWindow.cpp new file mode 100644 index 00000000..aa5a59e5 --- /dev/null +++ b/neo/ui/GameSSDWindow.cpp @@ -0,0 +1,2289 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "GameSSDWindow.h" + + +#define Z_NEAR 100.0f +#define Z_FAR 4000.0f +#define ENTITY_START_DIST 3000 + +#define V_WIDTH 640.0f +#define V_HEIGHT 480.0f + +/* +***************************************************************************** +* SSDCrossHair +**************************************************************************** +*/ + +#define CROSSHAIR_STANDARD_MATERIAL "game/SSD/crosshair_standard" +#define CROSSHAIR_SUPER_MATERIAL "game/SSD/crosshair_super" + +SSDCrossHair::SSDCrossHair() { +} + +SSDCrossHair::~SSDCrossHair() { +} + +void SSDCrossHair::WriteToSaveGame( idFile *savefile ) { + + savefile->Write(¤tCrosshair, sizeof(currentCrosshair)); + savefile->Write(&crosshairWidth, sizeof(crosshairWidth)); + savefile->Write(&crosshairHeight, sizeof(crosshairHeight)); + +} + +void SSDCrossHair::ReadFromSaveGame( idFile *savefile ) { + + InitCrosshairs(); + + savefile->Read(¤tCrosshair, sizeof(currentCrosshair)); + savefile->Read(&crosshairWidth, sizeof(crosshairWidth)); + savefile->Read(&crosshairHeight, sizeof(crosshairHeight)); + +} + +void SSDCrossHair::InitCrosshairs() { + + crosshairMaterial[CROSSHAIR_STANDARD] = declManager->FindMaterial( CROSSHAIR_STANDARD_MATERIAL ); + crosshairMaterial[CROSSHAIR_SUPER] = declManager->FindMaterial( CROSSHAIR_SUPER_MATERIAL ); + + crosshairWidth = 64; + crosshairHeight = 64; + + currentCrosshair = CROSSHAIR_STANDARD; + +} + +void SSDCrossHair::Draw(const idVec2& cursor) { + + float x,y; + x = cursor.x-(crosshairWidth/2); + y = cursor.y-(crosshairHeight/2); + dc->DrawMaterial(x, y, crosshairWidth, crosshairHeight, crosshairMaterial[currentCrosshair], colorWhite, 1.0f, 1.0f); + +} + +/* +***************************************************************************** +* SSDEntity +**************************************************************************** +*/ + +SSDEntity::SSDEntity() { + EntityInit(); +} + +SSDEntity::~SSDEntity() { +} + +void SSDEntity::WriteToSaveGame( idFile *savefile ) { + + savefile->Write(&type, sizeof(type)); + game->WriteSaveGameString(materialName, savefile); + savefile->Write(&position, sizeof(position)); + savefile->Write(&size, sizeof(size)); + savefile->Write(&radius, sizeof(radius)); + savefile->Write(&hitRadius, sizeof(hitRadius)); + savefile->Write(&rotation, sizeof(rotation)); + + savefile->Write(&matColor, sizeof(matColor)); + + game->WriteSaveGameString(text, savefile); + savefile->Write(&textScale, sizeof(textScale)); + savefile->Write(&foreColor, sizeof(foreColor)); + + savefile->Write(¤tTime, sizeof(currentTime)); + savefile->Write(&lastUpdate, sizeof(lastUpdate)); + savefile->Write(&elapsed, sizeof(elapsed)); + + savefile->Write(&destroyed, sizeof(destroyed)); + savefile->Write(&noHit, sizeof(noHit)); + savefile->Write(&noPlayerDamage, sizeof(noPlayerDamage)); + + savefile->Write(&inUse, sizeof(inUse)); + +} + +void SSDEntity::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + + savefile->Read(&type, sizeof(type)); + game->ReadSaveGameString(materialName, savefile); + SetMaterial(materialName); + savefile->Read(&position, sizeof(position)); + savefile->Read(&size, sizeof(size)); + savefile->Read(&radius, sizeof(radius)); + savefile->Read(&hitRadius, sizeof(hitRadius)); + savefile->Read(&rotation, sizeof(rotation)); + + savefile->Read(&matColor, sizeof(matColor)); + + game->ReadSaveGameString(text, savefile); + savefile->Read(&textScale, sizeof(textScale)); + savefile->Read(&foreColor, sizeof(foreColor)); + + game = _game; + savefile->Read(¤tTime, sizeof(currentTime)); + savefile->Read(&lastUpdate, sizeof(lastUpdate)); + savefile->Read(&elapsed, sizeof(elapsed)); + + savefile->Read(&destroyed, sizeof(destroyed)); + savefile->Read(&noHit, sizeof(noHit)); + savefile->Read(&noPlayerDamage, sizeof(noPlayerDamage)); + + savefile->Read(&inUse, sizeof(inUse)); +} + +void SSDEntity::EntityInit() { + + inUse = false; + + + type = SSD_ENTITY_BASE; + + materialName = ""; + material = NULL; + position.Zero(); + size.Zero(); + radius = 0.0f; + hitRadius = 0.0f; + rotation = 0.0f; + + + currentTime = 0; + lastUpdate = 0; + + destroyed = false; + noHit = false; + noPlayerDamage = false; + + matColor.Set(1, 1, 1, 1); + + text = ""; + textScale = 1.0f; + foreColor.Set(1, 1, 1, 1); +} + +void SSDEntity::SetGame(idGameSSDWindow* _game) { + game = _game; +} + +void SSDEntity::SetMaterial(const char* name) { + materialName = name; + material = declManager->FindMaterial( name ); + material->SetSort( SS_GUI ); +} + +void SSDEntity::SetPosition(const idVec3& _position) { + position = _position; +} + +void SSDEntity::SetSize(const idVec2& _size) { + size = _size; +} + +void SSDEntity::SetRadius(float _radius, float _hitFactor) { + radius = _radius; + hitRadius = _radius*_hitFactor; +} + +void SSDEntity::SetRotation(float _rotation) { + rotation = _rotation; +} + +void SSDEntity::Update() { + + currentTime = game->ssdTime; + + //Is this the first update + if(lastUpdate == 0) { + lastUpdate = currentTime; + return; + } + + elapsed = currentTime - lastUpdate; + + EntityUpdate(); + + lastUpdate = currentTime; +} + +bool SSDEntity::HitTest(const idVec2& pt) { + + if(noHit) { + return false; + } + + idVec3 screenPos = WorldToScreen(position); + + + //Scale the radius based on the distance from the player + float scale = 1.0f -((screenPos.z-Z_NEAR)/(Z_FAR-Z_NEAR)); + float scaledRad = scale*hitRadius; + + //So we can compare against the square of the length between two points + float scaleRadSqr = scaledRad*scaledRad; + + idVec2 diff = screenPos.ToVec2()-pt; + float dist = idMath::Fabs(diff.LengthSqr()); + + if(dist < scaleRadSqr) { + return true; + } + return false; +} + +void SSDEntity::Draw() { + + + idVec2 persize; + float x,y; + + idBounds bounds; + bounds[0] = idVec3(position.x - (size.x/2.0f), position.y - (size.y/2.0f), position.z); + bounds[1] = idVec3(position.x + (size.x/2.0f), position.y + (size.y/2.0f), position.z); + + idBounds screenBounds = WorldToScreen(bounds); + persize.x = idMath::Fabs(screenBounds[1].x - screenBounds[0].x); + persize.y = idMath::Fabs(screenBounds[1].y - screenBounds[0].y); + + idVec3 center = screenBounds.GetCenter(); + + x = screenBounds[0].x; + y = screenBounds[1].y; + dc->DrawMaterialRotated(x, y, persize.x, persize.y, material, matColor, 1.0f, 1.0f, DEG2RAD(rotation)); + + if(text.Length() > 0) { + idRectangle rect( x, y, VIRTUAL_WIDTH, VIRTUAL_HEIGHT ); + dc->DrawText( text, textScale, 0, foreColor, rect, false ); + } + +} + +void SSDEntity::DestroyEntity() { + inUse = false; +} + +idBounds SSDEntity::WorldToScreen(const idBounds worldBounds) { + + idVec3 screenMin = WorldToScreen(worldBounds[0]); + idVec3 screenMax = WorldToScreen(worldBounds[1]); + + idBounds screenBounds(screenMin, screenMax); + return screenBounds; +} + +idVec3 SSDEntity::WorldToScreen(const idVec3& worldPos) { + + float d = 0.5f*V_WIDTH*idMath::Tan(DEG2RAD(90.0f)/2.0f); + + //World To Camera Coordinates + idVec3 cameraTrans(0,0,d); + idVec3 cameraPos; + cameraPos = worldPos + cameraTrans; + + //Camera To Screen Coordinates + idVec3 screenPos; + screenPos.x = d*cameraPos.x/cameraPos.z + (0.5f*V_WIDTH-0.5f); + screenPos.y = -d*cameraPos.y/cameraPos.z + (0.5f*V_HEIGHT-0.5f); + screenPos.z = cameraPos.z; + + return screenPos; +} + +idVec3 SSDEntity::ScreenToWorld(const idVec3& screenPos) { + + idVec3 worldPos; + + worldPos.x = screenPos.x - 0.5f * V_WIDTH; + worldPos.y = -(screenPos.y - 0.5f * V_HEIGHT); + worldPos.z = screenPos.z; + + return worldPos; +} + +/* +***************************************************************************** +* SSDMover +**************************************************************************** +*/ + +SSDMover::SSDMover() { +} + +SSDMover::~SSDMover() { +} + +void SSDMover::WriteToSaveGame( idFile *savefile ) { + SSDEntity::WriteToSaveGame(savefile); + + savefile->Write(&speed, sizeof(speed)); + savefile->Write(&rotationSpeed, sizeof(rotationSpeed)); +} + +void SSDMover::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDEntity::ReadFromSaveGame(savefile, _game); + + savefile->Read(&speed, sizeof(speed)); + savefile->Read(&rotationSpeed, sizeof(rotationSpeed)); +} + +void SSDMover::MoverInit(const idVec3& _speed, float _rotationSpeed) { + + speed = _speed; + rotationSpeed = _rotationSpeed; +} + +void SSDMover::EntityUpdate() { + + SSDEntity::EntityUpdate(); + + //Move forward based on speed (units per second) + idVec3 moved = ((float)elapsed/1000.0f)*speed; + position += moved; + + float rotated = ((float)elapsed/1000.0f)*rotationSpeed*360.0f; + rotation += rotated; + if(rotation >= 360) { + rotation -= 360.0f; + } + if(rotation < 0) { + rotation += 360.0f; + } +} + + +/* +***************************************************************************** +* SSDAsteroid +**************************************************************************** +*/ + +SSDAsteroid SSDAsteroid::asteroidPool[MAX_ASTEROIDS]; + +#define ASTEROID_MATERIAL "game/SSD/asteroid" + +SSDAsteroid::SSDAsteroid() { +} + +SSDAsteroid::~SSDAsteroid() { +} + +void SSDAsteroid::WriteToSaveGame( idFile *savefile ) { + SSDMover::WriteToSaveGame(savefile); + + savefile->Write(&health, sizeof(health)); +} + +void SSDAsteroid::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDMover::ReadFromSaveGame(savefile, _game); + + savefile->Read(&health, sizeof(health)); +} + +void SSDAsteroid::Init(idGameSSDWindow* _game, const idVec3& startPosition, const idVec2& _size, float _speed, float rotate, int _health) { + + EntityInit(); + MoverInit(idVec3(0,0, -_speed), rotate); + + SetGame(_game); + + type = SSD_ENTITY_ASTEROID; + + SetMaterial(ASTEROID_MATERIAL); + SetSize(_size); + SetRadius(Max(size.x, size.y), 0.3f); + SetRotation(game->random.RandomInt(360)); + + + position = startPosition; + + health = _health; +} + +void SSDAsteroid::EntityUpdate() { + + SSDMover::EntityUpdate(); +} + +SSDAsteroid* SSDAsteroid::GetNewAsteroid(idGameSSDWindow* _game, const idVec3& startPosition, const idVec2& _size, float _speed, float rotate, int _health) { + for(int i = 0; i < MAX_ASTEROIDS; i++) { + if(!asteroidPool[i].inUse) { + asteroidPool[i].Init(_game, startPosition, _size, _speed, rotate, _health); + asteroidPool[i].inUse = true; + asteroidPool[i].id = i; + + return &asteroidPool[i]; + } + } + return NULL; +} + +SSDAsteroid* SSDAsteroid::GetSpecificAsteroid(int id) { + return &asteroidPool[id]; +} + +void SSDAsteroid::WriteAsteroids(idFile* savefile) { + int count = 0; + for(int i = 0; i < MAX_ASTEROIDS; i++) { + if(asteroidPool[i].inUse) { + count++; + } + } + savefile->Write(&count, sizeof(count)); + for(int i = 0; i < MAX_ASTEROIDS; i++) { + if(asteroidPool[i].inUse) { + savefile->Write(&(asteroidPool[i].id), sizeof(asteroidPool[i].id)); + asteroidPool[i].WriteToSaveGame(savefile); + } + } +} + +void SSDAsteroid::ReadAsteroids( idFile* savefile, idGameSSDWindow* _game) { + + int count; + savefile->Read(&count, sizeof(count)); + for(int i = 0; i < count; i++) { + int id; + savefile->Read(&id, sizeof(id)); + SSDAsteroid* ent = GetSpecificAsteroid(id); + ent->ReadFromSaveGame(savefile, _game); + } +} + +/* +***************************************************************************** +* SSDAstronaut +**************************************************************************** +*/ + +SSDAstronaut SSDAstronaut::astronautPool[MAX_ASTRONAUT]; + +#define ASTRONAUT_MATERIAL "game/SSD/astronaut" + +SSDAstronaut::SSDAstronaut() { +} + +SSDAstronaut::~SSDAstronaut() { +} + +void SSDAstronaut::WriteToSaveGame( idFile *savefile ) { + SSDMover::WriteToSaveGame(savefile); + + savefile->Write(&health, sizeof(health)); +} + +void SSDAstronaut::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDMover::ReadFromSaveGame(savefile, _game); + + savefile->Read(&health, sizeof(health)); +} + +void SSDAstronaut::Init(idGameSSDWindow* _game, const idVec3& startPosition, float _speed, float rotate, int _health) { + + EntityInit(); + MoverInit(idVec3(0,0, -_speed), rotate); + + SetGame(_game); + + type = SSD_ENTITY_ASTRONAUT; + + SetMaterial(ASTRONAUT_MATERIAL); + SetSize(idVec2(256,256)); + SetRadius(Max(size.x, size.y), 0.3f); + SetRotation(game->random.RandomInt(360)); + + position = startPosition; + health = _health; +} + +SSDAstronaut* SSDAstronaut::GetNewAstronaut(idGameSSDWindow* _game, const idVec3& startPosition, float _speed, float rotate, int _health) { + for(int i = 0; i < MAX_ASTRONAUT; i++) { + if(!astronautPool[i].inUse) { + astronautPool[i].Init(_game, startPosition, _speed, rotate, _health); + astronautPool[i].inUse = true; + astronautPool[i].id = i; + return &astronautPool[i]; + } + } + return NULL; +} + +SSDAstronaut* SSDAstronaut::GetSpecificAstronaut(int id) { + return &astronautPool[id]; + +} + +void SSDAstronaut::WriteAstronauts(idFile* savefile) { + int count = 0; + for(int i = 0; i < MAX_ASTRONAUT; i++) { + if(astronautPool[i].inUse) { + count++; + } + } + savefile->Write(&count, sizeof(count)); + for(int i = 0; i < MAX_ASTRONAUT; i++) { + if(astronautPool[i].inUse) { + savefile->Write(&(astronautPool[i].id), sizeof(astronautPool[i].id)); + astronautPool[i].WriteToSaveGame(savefile); + } + } +} + +void SSDAstronaut::ReadAstronauts(idFile* savefile, idGameSSDWindow* _game) { + + int count; + savefile->Read(&count, sizeof(count)); + for(int i = 0; i < count; i++) { + int id; + savefile->Read(&id, sizeof(id)); + SSDAstronaut* ent = GetSpecificAstronaut(id); + ent->ReadFromSaveGame(savefile, _game); + } +} + +/* +***************************************************************************** +* SSDExplosion +**************************************************************************** +*/ + +SSDExplosion SSDExplosion::explosionPool[MAX_EXPLOSIONS]; + + +//#define EXPLOSION_MATERIAL "game/SSD/fball" +//#define EXPLOSION_TELEPORT "game/SSD/teleport" + +const char* explosionMaterials[] = { + "game/SSD/fball", + "game/SSD/teleport" +}; + +#define EXPLOSION_MATERIAL_COUNT 2 + +SSDExplosion::SSDExplosion() { + type = SSD_ENTITY_EXPLOSION; +} + +SSDExplosion::~SSDExplosion() { +} + +void SSDExplosion::WriteToSaveGame( idFile *savefile ) { + SSDEntity::WriteToSaveGame(savefile); + + savefile->Write(&finalSize, sizeof(finalSize)); + savefile->Write(&length, sizeof(length)); + savefile->Write(&beginTime, sizeof(beginTime)); + savefile->Write(&endTime, sizeof(endTime)); + savefile->Write(&explosionType, sizeof(explosionType)); + + + savefile->Write(&(buddy->type), sizeof(buddy->type)); + savefile->Write(&(buddy->id), sizeof(buddy->id)); + + savefile->Write(&killBuddy, sizeof(killBuddy)); + savefile->Write(&followBuddy, sizeof(followBuddy)); +} + +void SSDExplosion::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDEntity::ReadFromSaveGame(savefile, _game); + + savefile->Read(&finalSize, sizeof(finalSize)); + savefile->Read(&length, sizeof(length)); + savefile->Read(&beginTime, sizeof(beginTime)); + savefile->Read(&endTime, sizeof(endTime)); + savefile->Read(&explosionType, sizeof(explosionType)); + + int type, id; + savefile->Read(&type, sizeof(type)); + savefile->Read(&id, sizeof(id)); + + //Get a pointer to my buddy + buddy = _game->GetSpecificEntity(type, id); + + savefile->Read(&killBuddy, sizeof(killBuddy)); + savefile->Read(&followBuddy, sizeof(followBuddy)); +} + +void SSDExplosion::Init(idGameSSDWindow* _game, const idVec3& _position, const idVec2& _size, int _length, int _type, SSDEntity* _buddy, bool _killBuddy, bool _followBuddy) { + + EntityInit(); + + SetGame(_game); + + type = SSD_ENTITY_EXPLOSION; + explosionType = _type; + + SetMaterial(explosionMaterials[explosionType]); + SetPosition(_position); + position.z -= 50; + + finalSize = _size; + length = _length; + beginTime = game->ssdTime; + endTime = beginTime + length; + + buddy = _buddy; + killBuddy = _killBuddy; + followBuddy = _followBuddy; + + //Explosion Starts from nothing and will increase in size until it gets to final size + size.Zero(); + + noPlayerDamage = true; + noHit = true; +} + +void SSDExplosion::EntityUpdate() { + + SSDEntity::EntityUpdate(); + + //Always set my position to my buddies position except change z to be on top + if(followBuddy) { + position = buddy->position; + position.z -= 50; + } else { + //Only mess with the z if we are not following + position.z = buddy->position.z - 50; + } + + //Scale the image based on the time + size = finalSize*((float)(currentTime-beginTime)/(float)length); + + //Destroy myself after the explosion is done + if(currentTime > endTime) { + destroyed = true; + + if(killBuddy) { + //Destroy the exploding object + buddy->destroyed = true; + } + } +} + +SSDExplosion* SSDExplosion::GetNewExplosion(idGameSSDWindow* _game, const idVec3& _position, const idVec2& _size, int _length, int _type, SSDEntity* _buddy, bool _killBuddy, bool _followBuddy) { + for(int i = 0; i < MAX_EXPLOSIONS; i++) { + if(!explosionPool[i].inUse) { + explosionPool[i].Init(_game, _position, _size, _length, _type, _buddy, _killBuddy, _followBuddy); + explosionPool[i].inUse = true; + return &explosionPool[i]; + } + } + return NULL; +} + +SSDExplosion* SSDExplosion::GetSpecificExplosion(int id) { + return &explosionPool[id]; +} + +void SSDExplosion::WriteExplosions(idFile* savefile) { + int count = 0; + for(int i = 0; i < MAX_EXPLOSIONS; i++) { + if(explosionPool[i].inUse) { + count++; + } + } + savefile->Write(&count, sizeof(count)); + for(int i = 0; i < MAX_EXPLOSIONS; i++) { + if(explosionPool[i].inUse) { + savefile->Write(&(explosionPool[i].id), sizeof(explosionPool[i].id)); + explosionPool[i].WriteToSaveGame(savefile); + } + } +} + +void SSDExplosion::ReadExplosions(idFile* savefile, idGameSSDWindow* _game) { + + int count; + savefile->Read(&count, sizeof(count)); + for(int i = 0; i < count; i++) { + int id; + savefile->Read(&id, sizeof(id)); + SSDExplosion* ent = GetSpecificExplosion(id); + ent->ReadFromSaveGame(savefile, _game); + } +} + +/* +***************************************************************************** +* SSDPoints +**************************************************************************** +*/ + +SSDPoints SSDPoints::pointsPool[MAX_POINTS]; + +SSDPoints::SSDPoints() { + type = SSD_ENTITY_POINTS; +} + +SSDPoints::~SSDPoints() { +} + +void SSDPoints::WriteToSaveGame( idFile *savefile ) { + SSDEntity::WriteToSaveGame(savefile); + + savefile->Write(&length, sizeof(length)); + savefile->Write(&distance, sizeof(distance)); + savefile->Write(&beginTime, sizeof(beginTime)); + savefile->Write(&endTime, sizeof(endTime)); + + savefile->Write(&beginPosition, sizeof(beginPosition)); + savefile->Write(&endPosition, sizeof(endPosition)); + + savefile->Write(&beginColor, sizeof(beginColor)); + savefile->Write(&endColor, sizeof(endColor)); + +} + +void SSDPoints::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDEntity::ReadFromSaveGame(savefile, _game); + + savefile->Read(&length, sizeof(length)); + savefile->Read(&distance, sizeof(distance)); + savefile->Read(&beginTime, sizeof(beginTime)); + savefile->Read(&endTime, sizeof(endTime)); + + savefile->Read(&beginPosition, sizeof(beginPosition)); + savefile->Read(&endPosition, sizeof(endPosition)); + + savefile->Read(&beginColor, sizeof(beginColor)); + savefile->Read(&endColor, sizeof(endColor)); +} + +void SSDPoints::Init(idGameSSDWindow* _game, SSDEntity* _ent, int _points, int _length, int _distance, const idVec4& color) { + + EntityInit(); + + SetGame(_game); + + length = _length; + distance = _distance; + beginTime = game->ssdTime; + endTime = beginTime + length; + + textScale = 0.4f; + text = va("%d", _points); + + float width = 0; + for(int i = 0; i < text.Length(); i++) { + width += dc->CharWidth(text[i], textScale); + } + + size.Set(0,0); + + //Set the start position at the top of the passed in entity + position = WorldToScreen(_ent->position); + position = ScreenToWorld(position); + + position.z = 0; + position.x -= (width/2.0f); + + beginPosition = position; + + endPosition = beginPosition; + endPosition.y += _distance; + + //beginColor.Set(0,1,0,1); + endColor.Set(1,1,1,0); + + beginColor = color; + beginColor.w = 1; + + noPlayerDamage = true; + noHit = true; +} + +void SSDPoints::EntityUpdate() { + + float t = (float)(currentTime - beginTime)/(float)length; + + //Move up from the start position + position.Lerp(beginPosition, endPosition, t); + + //Interpolate the color + foreColor.Lerp(beginColor, endColor, t); + + if(currentTime > endTime) { + destroyed = true; + } +} + +SSDPoints* SSDPoints::GetNewPoints(idGameSSDWindow* _game, SSDEntity* _ent, int _points, int _length, int _distance, const idVec4& color) { + for(int i = 0; i < MAX_POINTS; i++) { + if(!pointsPool[i].inUse) { + pointsPool[i].Init(_game, _ent, _points, _length, _distance, color); + pointsPool[i].inUse = true; + return &pointsPool[i]; + } + } + return NULL; +} + +SSDPoints* SSDPoints::GetSpecificPoints(int id) { + return &pointsPool[id]; +} + +void SSDPoints::WritePoints(idFile* savefile) { + int count = 0; + for(int i = 0; i < MAX_POINTS; i++) { + if(pointsPool[i].inUse) { + count++; + } + } + savefile->Write(&count, sizeof(count)); + for(int i = 0; i < MAX_POINTS; i++) { + if(pointsPool[i].inUse) { + savefile->Write(&(pointsPool[i].id), sizeof(pointsPool[i].id)); + pointsPool[i].WriteToSaveGame(savefile); + } + } +} + +void SSDPoints::ReadPoints(idFile* savefile, idGameSSDWindow* _game) { + + int count; + savefile->Read(&count, sizeof(count)); + for(int i = 0; i < count; i++) { + int id; + savefile->Read(&id, sizeof(id)); + SSDPoints* ent = GetSpecificPoints(id); + ent->ReadFromSaveGame(savefile, _game); + } +} + +/* +***************************************************************************** +* SSDProjectile +**************************************************************************** +*/ + +SSDProjectile SSDProjectile::projectilePool[MAX_PROJECTILES]; + +#define PROJECTILE_MATERIAL "game/SSD/fball" + +SSDProjectile::SSDProjectile() { + type = SSD_ENTITY_PROJECTILE; +} + +SSDProjectile::~SSDProjectile() { +} + +void SSDProjectile::WriteToSaveGame( idFile *savefile ) { + SSDEntity::WriteToSaveGame(savefile); + + savefile->Write(&dir, sizeof(dir)); + savefile->Write(&speed, sizeof(speed)); + savefile->Write(&beginTime, sizeof(beginTime)); + savefile->Write(&endTime, sizeof(endTime)); + + savefile->Write(&endPosition, sizeof(endPosition)); +} + +void SSDProjectile::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDEntity::ReadFromSaveGame(savefile, _game); + + savefile->Read(&dir, sizeof(dir)); + savefile->Read(&speed, sizeof(speed)); + savefile->Read(&beginTime, sizeof(beginTime)); + savefile->Read(&endTime, sizeof(endTime)); + + savefile->Read(&endPosition, sizeof(endPosition)); +} + +void SSDProjectile::Init(idGameSSDWindow* _game, const idVec3& _beginPosition, const idVec3& _endPosition, float _speed, float _size) { + + EntityInit(); + + SetGame(_game); + + SetMaterial(PROJECTILE_MATERIAL); + size.Set(_size,_size); + + position = _beginPosition; + endPosition = _endPosition; + + dir = _endPosition - position; + dir.Normalize(); + + //speed.Zero(); + speed.x = speed.y = speed.z = _speed; + + noHit = true; +} + +void SSDProjectile::EntityUpdate() { + + SSDEntity::EntityUpdate(); + + //Move forward based on speed (units per second) + idVec3 moved = dir*((float)elapsed/1000.0f)*speed.z; + position += moved; + + if(position.z > endPosition.z) { + //We have reached our position + destroyed = true; + } +} + +SSDProjectile* SSDProjectile::GetNewProjectile(idGameSSDWindow* _game, const idVec3& _beginPosition, const idVec3& _endPosition, float _speed, float _size) { + for(int i = 0; i < MAX_PROJECTILES; i++) { + if(!projectilePool[i].inUse) { + projectilePool[i].Init(_game, _beginPosition, _endPosition, _speed, _size); + projectilePool[i].inUse = true; + return &projectilePool[i]; + } + } + return NULL; +} + +SSDProjectile* SSDProjectile::GetSpecificProjectile(int id) { + return &projectilePool[id]; +} + +void SSDProjectile::WriteProjectiles(idFile* savefile) { + int count = 0; + for(int i = 0; i < MAX_PROJECTILES; i++) { + if(projectilePool[i].inUse) { + count++; + } + } + savefile->Write(&count, sizeof(count)); + for(int i = 0; i < MAX_PROJECTILES; i++) { + if(projectilePool[i].inUse) { + savefile->Write(&(projectilePool[i].id), sizeof(projectilePool[i].id)); + projectilePool[i].WriteToSaveGame(savefile); + } + } +} + +void SSDProjectile::ReadProjectiles(idFile* savefile, idGameSSDWindow* _game) { + + int count; + savefile->Read(&count, sizeof(count)); + for(int i = 0; i < count; i++) { + int id; + savefile->Read(&id, sizeof(id)); + SSDProjectile* ent = GetSpecificProjectile(id); + ent->ReadFromSaveGame(savefile, _game); + } +} + +/* +***************************************************************************** +* SSDPowerup +**************************************************************************** +*/ + +const char* powerupMaterials[][2] = { + "game/SSD/powerupHealthClosed", "game/SSD/powerupHealthOpen", + "game/SSD/powerupSuperBlasterClosed", "game/SSD/powerupSuperBlasterOpen", + "game/SSD/powerupNukeClosed", "game/SSD/powerupNukeOpen", + "game/SSD/powerupRescueClosed", "game/SSD/powerupRescueOpen", + "game/SSD/powerupBonusPointsClosed", "game/SSD/powerupBonusPointsOpen", + "game/SSD/powerupDamageClosed", "game/SSD/powerupDamageOpen", +}; + +#define POWERUP_MATERIAL_COUNT 6 + +SSDPowerup SSDPowerup::powerupPool[MAX_POWERUPS]; + +SSDPowerup::SSDPowerup() { + +} + +SSDPowerup::~SSDPowerup() { +} + +void SSDPowerup::WriteToSaveGame( idFile *savefile ) { + SSDMover::WriteToSaveGame(savefile); + + savefile->Write(&powerupState, sizeof(powerupState)); + savefile->Write(&powerupType, sizeof(powerupType)); +} + +void SSDPowerup::ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ) { + SSDMover::ReadFromSaveGame(savefile, _game); + + savefile->Read(&powerupState, sizeof(powerupState)); + savefile->Read(&powerupType, sizeof(powerupType)); +} + +void SSDPowerup::OnHit(int key) { + + if(powerupState == POWERUP_STATE_CLOSED) { + + //Small explosion to indicate it is opened + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(game, position, size*2.0f, 300, SSDExplosion::EXPLOSION_NORMAL, this, false, true); + game->entities.Append(explosion); + + + powerupState = POWERUP_STATE_OPEN; + SetMaterial(powerupMaterials[powerupType][powerupState]); + } else { + //Destory the powerup with a big explosion + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(game, position, size*2, 300, SSDExplosion::EXPLOSION_NORMAL, this); + game->entities.Append(explosion); + game->PlaySound("arcade_explode"); + + noHit = true; + noPlayerDamage = true; + } +} + +void SSDPowerup::OnStrikePlayer() { + + if(powerupState == POWERUP_STATE_OPEN) { + //The powerup was open so activate it + OnActivatePowerup(); + } + + //Just destroy the powerup + destroyed = true; +} + +void SSDPowerup::OnOpenPowerup() { +} + +void SSDPowerup::OnActivatePowerup() { + switch(powerupType) { + case POWERUP_TYPE_HEALTH: + { + game->AddHealth(10); + break; + } + case POWERUP_TYPE_SUPER_BLASTER: + { + game->OnSuperBlaster(); + break; + } + case POWERUP_TYPE_ASTEROID_NUKE: + { + game->OnNuke(); + break; + } + case POWERUP_TYPE_RESCUE_ALL: + { + game->OnRescueAll(); + break; + } + case POWERUP_TYPE_BONUS_POINTS: + { + int points = (game->random.RandomInt(5)+1) * 100; + game->AddScore(this, points); + break; + } + case POWERUP_TYPE_DAMAGE: + { + game->AddDamage(10); + game->PlaySound("arcade_explode"); + break; + } + + } +} + + +void SSDPowerup::Init(idGameSSDWindow* _game, float _speed, float _rotation) { + + EntityInit(); + MoverInit(idVec3(0,0, -_speed), _rotation); + + SetGame(_game); + SetSize(idVec2(200,200)); + SetRadius(Max(size.x, size.y), 0.3f); + + type = SSD_ENTITY_POWERUP; + + idVec3 startPosition; + startPosition.x = game->random.RandomInt(V_WIDTH)-(V_WIDTH/2.0f); + startPosition.y = game->random.RandomInt(V_HEIGHT)-(V_HEIGHT/2.0f); + startPosition.z = ENTITY_START_DIST; + + position = startPosition; + //SetPosition(startPosition); + + powerupState = POWERUP_STATE_CLOSED; + powerupType = game->random.RandomInt(POWERUP_TYPE_MAX+1); + if(powerupType >= POWERUP_TYPE_MAX) { + powerupType = 0; + } + + /*OutputDebugString(va("Powerup: %d\n", powerupType)); + if(powerupType == 0) { + int x = 0; + }*/ + + SetMaterial(powerupMaterials[powerupType][powerupState]); +} + +SSDPowerup* SSDPowerup::GetNewPowerup(idGameSSDWindow* _game, float _speed, float _rotation) { + + for(int i = 0; i < MAX_POWERUPS; i++) { + if(!powerupPool[i].inUse) { + powerupPool[i].Init(_game, _speed, _rotation); + powerupPool[i].inUse = true; + return &powerupPool[i]; + } + } + return NULL; +} + +SSDPowerup* SSDPowerup::GetSpecificPowerup(int id) { + return &powerupPool[id]; +} + +void SSDPowerup::WritePowerups(idFile* savefile) { + int count = 0; + for(int i = 0; i < MAX_POWERUPS; i++) { + if(powerupPool[i].inUse) { + count++; + } + } + savefile->Write(&count, sizeof(count)); + for(int i = 0; i < MAX_POWERUPS; i++) { + if(powerupPool[i].inUse) { + savefile->Write(&(powerupPool[i].id), sizeof(powerupPool[i].id)); + powerupPool[i].WriteToSaveGame(savefile); + } + } +} + +void SSDPowerup::ReadPowerups(idFile* savefile, idGameSSDWindow* _game) { + + int count; + savefile->Read(&count, sizeof(count)); + for(int i = 0; i < count; i++) { + int id; + savefile->Read(&id, sizeof(id)); + SSDPowerup* ent = GetSpecificPowerup(id); + ent->ReadFromSaveGame(savefile, _game); + } +} + +/* +***************************************************************************** +* idGameSSDWindow +**************************************************************************** +*/ + +idRandom idGameSSDWindow::random; + +idGameSSDWindow::idGameSSDWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idGameSSDWindow::~idGameSSDWindow() { + ResetGameStats(); +} + +void idGameSSDWindow::WriteToSaveGame( idFile *savefile ) { + idWindow::WriteToSaveGame(savefile); + + savefile->Write(&ssdTime, sizeof(ssdTime)); + + beginLevel.WriteToSaveGame(savefile); + resetGame.WriteToSaveGame(savefile); + continueGame.WriteToSaveGame(savefile); + refreshGuiData.WriteToSaveGame(savefile); + + crosshair.WriteToSaveGame(savefile); + savefile->Write(&screenBounds, sizeof(screenBounds)); + + savefile->Write(&levelCount, sizeof(levelCount)); + for(int i = 0; i < levelCount; i++) { + savefile->Write(&(levelData[i]), sizeof(SSDLevelData_t)); + savefile->Write(&(asteroidData[i]), sizeof(SSDAsteroidData_t)); + savefile->Write(&(astronautData[i]), sizeof(SSDAstronautData_t)); + savefile->Write(&(powerupData[i]), sizeof(SSDPowerupData_t)); + } + + savefile->Write(&weaponCount, sizeof(weaponCount)); + for(int i = 0; i < weaponCount; i++) { + savefile->Write(&(weaponData[i]), sizeof(SSDWeaponData_t)); + } + + savefile->Write(&superBlasterTimeout, sizeof(superBlasterTimeout)); + savefile->Write(&gameStats, sizeof(SSDGameStats_t)); + + //Write All Static Entities + SSDAsteroid::WriteAsteroids(savefile); + SSDAstronaut::WriteAstronauts(savefile); + SSDExplosion::WriteExplosions(savefile); + SSDPoints::WritePoints(savefile); + SSDProjectile::WriteProjectiles(savefile); + SSDPowerup::WritePowerups(savefile); + + int entCount = entities.Num(); + savefile->Write(&entCount, sizeof(entCount)); + for(int i = 0; i < entCount; i++) { + savefile->Write(&(entities[i]->type), sizeof(entities[i]->type)); + savefile->Write(&(entities[i]->id), sizeof(entities[i]->id)); + } +} + +void idGameSSDWindow::ReadFromSaveGame( idFile *savefile ) { + idWindow::ReadFromSaveGame(savefile); + + + savefile->Read(&ssdTime, sizeof(ssdTime)); + + beginLevel.ReadFromSaveGame(savefile); + resetGame.ReadFromSaveGame(savefile); + continueGame.ReadFromSaveGame(savefile); + refreshGuiData.ReadFromSaveGame(savefile); + + crosshair.ReadFromSaveGame(savefile); + savefile->Read(&screenBounds, sizeof(screenBounds)); + + savefile->Read(&levelCount, sizeof(levelCount)); + for(int i = 0; i < levelCount; i++) { + SSDLevelData_t newLevel; + savefile->Read(&newLevel, sizeof(SSDLevelData_t)); + levelData.Append(newLevel); + + SSDAsteroidData_t newAsteroid; + savefile->Read(&newAsteroid, sizeof(SSDAsteroidData_t)); + asteroidData.Append(newAsteroid); + + SSDAstronautData_t newAstronaut; + savefile->Read(&newAstronaut, sizeof(SSDAstronautData_t)); + astronautData.Append(newAstronaut); + + SSDPowerupData_t newPowerup; + savefile->Read(&newPowerup, sizeof(SSDPowerupData_t)); + powerupData.Append(newPowerup); + } + + savefile->Read(&weaponCount, sizeof(weaponCount)); + for(int i = 0; i < weaponCount; i++) { + SSDWeaponData_t newWeapon; + savefile->Read(&newWeapon, sizeof(SSDWeaponData_t)); + weaponData.Append(newWeapon); + } + + savefile->Read(&superBlasterTimeout, sizeof(superBlasterTimeout)); + + savefile->Read(&gameStats, sizeof(SSDGameStats_t)); + //Reset this because it is no longer valid + gameStats.levelStats.targetEnt = NULL; + + SSDAsteroid::ReadAsteroids(savefile, this); + SSDAstronaut::ReadAstronauts(savefile, this); + SSDExplosion::ReadExplosions(savefile, this); + SSDPoints::ReadPoints(savefile, this); + SSDProjectile::ReadProjectiles(savefile, this); + SSDPowerup::ReadPowerups(savefile, this); + + int entCount; + savefile->Read(&entCount, sizeof(entCount)); + + for(int i = 0; i < entCount; i++) { + int type, id; + savefile->Read(&type, sizeof(type)); + savefile->Read(&id, sizeof(id)); + + SSDEntity* ent = GetSpecificEntity(type, id); + if(ent) { + entities.Append(ent); + } + } +} + +const char *idGameSSDWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + + // need to call this to allow proper focus and capturing on embedded children + const char *ret = idWindow::HandleEvent(event, updateVisuals); + + if(!gameStats.gameRunning) { + return ret; + } + + int key = event->evValue; + + if ( event->evType == SE_KEY ) { + + if ( !event->evValue2 ) { + return ret; + } + + if ( key == K_MOUSE1 || key == K_MOUSE2) { + FireWeapon(key); + } else { + return ret; + } + } + return ret; +} + +idWinVar *idGameSSDWindow::GetWinVarByName (const char *_name, bool winLookup, drawWin_t** owner) { + + idWinVar *retVar = NULL; + + if (idStr::Icmp(_name, "beginLevel") == 0) { + retVar = &beginLevel; + } + + if (idStr::Icmp(_name, "resetGame") == 0) { + retVar = &resetGame; + } + + if (idStr::Icmp(_name, "continueGame") == 0) { + retVar = &continueGame; + } + if (idStr::Icmp(_name, "refreshGuiData") == 0) { + retVar = &refreshGuiData; + } + + + if(retVar) { + return retVar; + } + + return idWindow::GetWinVarByName(_name, winLookup, owner); +} + + +void idGameSSDWindow::Draw(int time, float x, float y) { + + //Update the game every frame before drawing + UpdateGame(); + + RefreshGuiData(); + + if(gameStats.gameRunning) { + + ZOrderEntities(); + + //Draw from back to front + for(int i = entities.Num()-1; i >= 0; i--) { + entities[i]->Draw(); + } + + //The last thing to draw is the crosshair + idVec2 cursor; + //GetCursor(cursor); + cursor.x = gui->CursorX(); + cursor.y = gui->CursorY(); + + crosshair.Draw(cursor); + } +} + + +bool idGameSSDWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + + if (idStr::Icmp(_name, "beginLevel") == 0) { + beginLevel = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "resetGame") == 0) { + resetGame = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "continueGame") == 0) { + continueGame = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "refreshGuiData") == 0) { + refreshGuiData = src->ParseBool(); + return true; + } + + if(idStr::Icmp(_name, "levelcount") == 0) { + levelCount = src->ParseInt(); + for(int i = 0; i < levelCount; i++) { + SSDLevelData_t newLevel; + memset(&newLevel, 0, sizeof(SSDLevelData_t)); + levelData.Append(newLevel); + + SSDAsteroidData_t newAsteroid; + memset(&newAsteroid, 0, sizeof(SSDAsteroidData_t)); + asteroidData.Append(newAsteroid); + + SSDAstronautData_t newAstronaut; + memset(&newAstronaut, 0, sizeof(SSDAstronautData_t)); + astronautData.Append(newAstronaut); + + SSDPowerupData_t newPowerup; + memset(&newPowerup, 0, sizeof(SSDPowerupData_t)); + powerupData.Append(newPowerup); + + + } + return true; + } + if(idStr::Icmp(_name, "weaponCount") == 0) { + weaponCount = src->ParseInt(); + for(int i = 0; i < weaponCount; i++) { + SSDWeaponData_t newWeapon; + memset(&newWeapon, 0, sizeof(SSDWeaponData_t)); + weaponData.Append(newWeapon); + } + return true; + } + + if(idStr::FindText(_name, "leveldata", false) >= 0) { + idStr tempName = _name; + int level = atoi(tempName.Right(2))-1; + + idStr levelData; + ParseString(src, levelData); + ParseLevelData(level, levelData); + return true; + } + + if(idStr::FindText(_name, "asteroiddata", false) >= 0) { + idStr tempName = _name; + int level = atoi(tempName.Right(2))-1; + + idStr asteroidData; + ParseString(src, asteroidData); + ParseAsteroidData(level, asteroidData); + return true; + } + + if(idStr::FindText(_name, "weapondata", false) >= 0) { + idStr tempName = _name; + int weapon = atoi(tempName.Right(2))-1; + + idStr weaponData; + ParseString(src, weaponData); + ParseWeaponData(weapon, weaponData); + return true; + } + + if(idStr::FindText(_name, "astronautdata", false) >= 0) { + idStr tempName = _name; + int level = atoi(tempName.Right(2))-1; + + idStr astronautData; + ParseString(src, astronautData); + ParseAstronautData(level, astronautData); + return true; + } + + if(idStr::FindText(_name, "powerupdata", false) >= 0) { + idStr tempName = _name; + int level = atoi(tempName.Right(2))-1; + + idStr powerupData; + ParseString(src, powerupData); + ParsePowerupData(level, powerupData); + return true; + } + + return idWindow::ParseInternalVar(_name, src); +} + +void idGameSSDWindow::ParseLevelData(int level, const idStr& levelDataString) { + + idParser parser; + idToken token; + parser.LoadMemory(levelDataString.c_str(), levelDataString.Length(), "LevelData"); + + levelData[level].spawnBuffer = parser.ParseFloat(); + levelData[level].needToWin = parser.ParseInt(); //Required Destroyed + +} + +void idGameSSDWindow::ParseAsteroidData(int level, const idStr& asteroidDataString) { + + idParser parser; + idToken token; + parser.LoadMemory(asteroidDataString.c_str(), asteroidDataString.Length(), "AsteroidData"); + + asteroidData[level].speedMin = parser.ParseFloat(); //Speed Min + asteroidData[level].speedMax = parser.ParseFloat(); //Speed Max + + asteroidData[level].sizeMin = parser.ParseFloat(); //Size Min + asteroidData[level].sizeMax = parser.ParseFloat(); //Size Max + + asteroidData[level].rotateMin = parser.ParseFloat(); //Rotate Min (rotations per second) + asteroidData[level].rotateMax = parser.ParseFloat(); //Rotate Max (rotations per second) + + asteroidData[level].spawnMin = parser.ParseInt(); //Spawn Min + asteroidData[level].spawnMax = parser.ParseInt(); //Spawn Max + + asteroidData[level].asteroidHealth = parser.ParseInt(); //Health of the asteroid + asteroidData[level].asteroidDamage = parser.ParseInt(); //Asteroid Damage + asteroidData[level].asteroidPoints = parser.ParseInt(); //Points awarded for destruction +} + +void idGameSSDWindow::ParsePowerupData(int level, const idStr& powerupDataString) { + + idParser parser; + idToken token; + parser.LoadMemory(powerupDataString.c_str(), powerupDataString.Length(), "PowerupData"); + + powerupData[level].speedMin = parser.ParseFloat(); //Speed Min + powerupData[level].speedMax = parser.ParseFloat(); //Speed Max + + powerupData[level].rotateMin = parser.ParseFloat(); //Rotate Min (rotations per second) + powerupData[level].rotateMax = parser.ParseFloat(); //Rotate Max (rotations per second) + + powerupData[level].spawnMin = parser.ParseInt(); //Spawn Min + powerupData[level].spawnMax = parser.ParseInt(); //Spawn Max + +} + +void idGameSSDWindow::ParseWeaponData(int weapon, const idStr& weaponDataString) { + + idParser parser; + idToken token; + parser.LoadMemory(weaponDataString.c_str(), weaponDataString.Length(), "WeaponData"); + + weaponData[weapon].speed = parser.ParseFloat(); + weaponData[weapon].damage = parser.ParseFloat(); + weaponData[weapon].size = parser.ParseFloat(); +} + +void idGameSSDWindow::ParseAstronautData(int level, const idStr& astronautDataString) { + + idParser parser; + idToken token; + parser.LoadMemory(astronautDataString.c_str(), astronautDataString.Length(), "AstronautData"); + + astronautData[level].speedMin = parser.ParseFloat(); //Speed Min + astronautData[level].speedMax = parser.ParseFloat(); //Speed Max + + astronautData[level].rotateMin = parser.ParseFloat(); //Rotate Min (rotations per second) + astronautData[level].rotateMax = parser.ParseFloat(); //Rotate Max (rotations per second) + + astronautData[level].spawnMin = parser.ParseInt(); //Spawn Min + astronautData[level].spawnMax = parser.ParseInt(); //Spawn Max + + astronautData[level].health = parser.ParseInt(); //Health of the asteroid + astronautData[level].points = parser.ParseInt(); //Asteroid Damage + astronautData[level].penalty = parser.ParseInt(); //Points awarded for destruction +} + +void idGameSSDWindow::CommonInit() { + crosshair.InitCrosshairs(); + + + beginLevel = false; + resetGame = false; + continueGame = false; + refreshGuiData = false; + + ssdTime = 0; + levelCount = 0; + weaponCount = 0; + screenBounds = idBounds(idVec3(-320,-240,0), idVec3(320,240,0)); + + superBlasterTimeout = 0; + + currentSound = 0; + + //Precahce all assets that are loaded dynamically + declManager->FindMaterial(ASTEROID_MATERIAL); + declManager->FindMaterial(ASTRONAUT_MATERIAL); + + for(int i = 0; i < EXPLOSION_MATERIAL_COUNT; i++) { + declManager->FindMaterial(explosionMaterials[i]); + } + declManager->FindMaterial(PROJECTILE_MATERIAL); + for(int i = 0; i < POWERUP_MATERIAL_COUNT; i++) { + declManager->FindMaterial(powerupMaterials[i][0]); + declManager->FindMaterial(powerupMaterials[i][1]); + } + + // Precache sounds + declManager->FindSound( "arcade_blaster" ); + declManager->FindSound( "arcade_capture" ); + declManager->FindSound( "arcade_explode" ); + + ResetGameStats(); +} + +void idGameSSDWindow::ResetGameStats() { + + ResetEntities(); + + //Reset the gamestats structure + memset(&gameStats, 0, sizeof(gameStats)); + + gameStats.health = 100; + +} + +void idGameSSDWindow::ResetLevelStats() { + + ResetEntities(); + + //Reset the level statistics structure + memset(&gameStats.levelStats, 0, sizeof(gameStats.levelStats)); + + +} + +void idGameSSDWindow::ResetEntities() { + //Destroy all of the entities + for(int i = 0; i < entities.Num(); i++) { + entities[i]->DestroyEntity(); + } + entities.Clear(); +} + +void idGameSSDWindow::StartGame() { + + gameStats.gameRunning = true; +} + +void idGameSSDWindow::StopGame() { + + gameStats.gameRunning = false; +} + +void idGameSSDWindow::GameOver() { + + + StopGame(); + + gui->HandleNamedEvent("gameOver"); +} + +void idGameSSDWindow::BeginLevel(int level) { + + ResetLevelStats(); + + gameStats.currentLevel = level; + + StartGame(); +} + +/** +* Continue game resets the players health +*/ +void idGameSSDWindow::ContinueGame() { + gameStats.health = 100; + + StartGame(); +} + +void idGameSSDWindow::LevelComplete() { + + gameStats.prebonusscore = gameStats.score; + + // Add the bonuses + int accuracy; + if( !gameStats.levelStats.shotCount ) { + accuracy = 0; + } else { + accuracy = (int)( ( (float)gameStats.levelStats.hitCount / (float)gameStats.levelStats.shotCount ) * 100.0f ); + } + int accuracyPoints = Max( 0, accuracy - 50 ) * 20; + + gui->SetStateString("player_accuracy_score", va("%i", accuracyPoints)); + + gameStats.score += accuracyPoints; + + int saveAccuracy; + int totalAst = gameStats.levelStats.savedAstronauts + gameStats.levelStats.killedAstronauts; + if( !totalAst ) { + saveAccuracy = 0; + } else { + saveAccuracy = (int)( ( (float)gameStats.levelStats.savedAstronauts / (float)totalAst ) * 100.0f ); + } + accuracyPoints = Max( 0, saveAccuracy - 50 ) * 20; + + gui->SetStateString("save_accuracy_score", va("%i", accuracyPoints)); + + gameStats.score += accuracyPoints; + + + + StopSuperBlaster(); + + gameStats.nextLevel++; + + if(gameStats.nextLevel >= levelCount) { + //Have they beaten the game + GameComplete(); + } else { + + //Make sure we don't go above the levelcount + //min(gameStats.nextLevel, levelCount-1); + + StopGame(); + gui->HandleNamedEvent("levelComplete"); + } +} + +void idGameSSDWindow::GameComplete() { + StopGame(); + gui->HandleNamedEvent("gameComplete"); +} + + +void idGameSSDWindow::UpdateGame() { + + //Check to see if and functions where called by the gui + if(beginLevel == true) { + beginLevel = false; + BeginLevel(gameStats.nextLevel); + } + if(resetGame == true) { + resetGame = false; + ResetGameStats(); + } + if(continueGame == true) { + continueGame = false; + ContinueGame(); + } + if(refreshGuiData == true) { + refreshGuiData = false; + RefreshGuiData(); + } + + if(gameStats.gameRunning) { + + //We assume an upate every 16 milliseconds + ssdTime += 16; + + if(superBlasterTimeout && ssdTime > superBlasterTimeout) { + StopSuperBlaster(); + } + + //Find if we are targeting and enemy + idVec2 cursor; + //GetCursor(cursor); + cursor.x = gui->CursorX(); + cursor.y = gui->CursorY(); + gameStats.levelStats.targetEnt = EntityHitTest(cursor); + + //Update from back to front + for(int i = entities.Num()-1; i >= 0; i--) { + entities[i]->Update(); + } + + CheckForHits(); + + //Delete entities that need to be deleted + for(int i = entities.Num()-1; i >= 0; i--) { + if(entities[i]->destroyed) { + SSDEntity* ent = entities[i]; + ent->DestroyEntity(); + entities.RemoveIndex(i); + } + } + + //Check if we can spawn an asteroid + SpawnAsteroid(); + + //Check if we should spawn an astronaut + SpawnAstronaut(); + + //Check if we should spawn an asteroid + SpawnPowerup(); + } +} + +void idGameSSDWindow::CheckForHits() { + + //See if the entity has gotten close enough + for(int i = 0; i < entities.Num(); i++) { + SSDEntity* ent = entities[i]; + if(ent->position.z <= Z_NEAR) { + + if(!ent->noPlayerDamage) { + + //Is the object still in the screen + idVec3 entPos = ent->position; + entPos.z = 0; + + idBounds entBounds(entPos); + entBounds.ExpandSelf(ent->hitRadius); + + if(screenBounds.IntersectsBounds(entBounds)) { + + ent->OnStrikePlayer(); + + //The entity hit the player figure out what is was and act appropriately + if(ent->type == SSD_ENTITY_ASTEROID) { + AsteroidStruckPlayer(static_cast(ent)); + } else if(ent->type == SSD_ENTITY_ASTRONAUT) { + AstronautStruckPlayer(static_cast(ent)); + } + } else { + //Tag for removal later in the frame + ent->destroyed = true; + } + } + } + } +} + +void idGameSSDWindow::ZOrderEntities() { + //Z-Order the entities + //Using a simple sorting method + for (int i = entities.Num()-1; i >= 0; i--) { + bool flipped = false; + for (int j = 0; jposition.z > entities[j+1]->position.z) { + SSDEntity* ent = entities[j]; + entities[j] = entities[j+1]; + entities[j+1] = ent; + flipped = true; + } + } + if (!flipped) { + //Jump out because it is sorted + break; + } + } +} + +void idGameSSDWindow::SpawnAsteroid() { + + int currentTime = ssdTime; + + if(currentTime < gameStats.levelStats.nextAsteroidSpawnTime) { + //Not time yet + return; + } + + //Lets spawn it + idVec3 startPosition; + + float spawnBuffer = levelData[gameStats.currentLevel].spawnBuffer*2.0f; + startPosition.x = random.RandomInt(V_WIDTH+spawnBuffer)-((V_WIDTH/2.0f)+spawnBuffer); + startPosition.y = random.RandomInt(V_HEIGHT+spawnBuffer)-((V_HEIGHT/2.0f)+spawnBuffer); + startPosition.z = ENTITY_START_DIST; + + float speed = random.RandomInt(asteroidData[gameStats.currentLevel].speedMax - asteroidData[gameStats.currentLevel].speedMin) + asteroidData[gameStats.currentLevel].speedMin; + float size = random.RandomInt(asteroidData[gameStats.currentLevel].sizeMax - asteroidData[gameStats.currentLevel].sizeMin) + asteroidData[gameStats.currentLevel].sizeMin; + float rotate = (random.RandomFloat() * (asteroidData[gameStats.currentLevel].rotateMax - asteroidData[gameStats.currentLevel].rotateMin)) + asteroidData[gameStats.currentLevel].rotateMin; + + SSDAsteroid* asteroid = SSDAsteroid::GetNewAsteroid(this, startPosition, idVec2(size, size), speed, rotate, asteroidData[gameStats.currentLevel].asteroidHealth); + entities.Append(asteroid); + + gameStats.levelStats.nextAsteroidSpawnTime = currentTime + random.RandomInt(asteroidData[gameStats.currentLevel].spawnMax - asteroidData[gameStats.currentLevel].spawnMin) + asteroidData[gameStats.currentLevel].spawnMin; +} + +void idGameSSDWindow::FireWeapon(int key) { + + idVec2 cursorWorld = GetCursorWorld(); + idVec2 cursor; + //GetCursor(cursor); + cursor.x = gui->CursorX(); + cursor.y = gui->CursorY(); + + if(key == K_MOUSE1) { + + gameStats.levelStats.shotCount++; + + if(gameStats.levelStats.targetEnt) { + //Aim the projectile from the bottom of the screen directly at the ent + //SSDProjectile* newProj = new (TAG_OLD_UI) SSDProjectile(this, idVec3(320,0,0), gameStats.levelStats.targetEnt->position, weaponData[gameStats.currentWeapon].speed, weaponData[gameStats.currentWeapon].size); + SSDProjectile* newProj = SSDProjectile::GetNewProjectile(this, idVec3(0,-180,0), gameStats.levelStats.targetEnt->position, weaponData[gameStats.currentWeapon].speed, weaponData[gameStats.currentWeapon].size); + entities.Append(newProj); + //newProj = SSDProjectile::GetNewProjectile(this, idVec3(-320,-0,0), gameStats.levelStats.targetEnt->position, weaponData[gameStats.currentWeapon].speed, weaponData[gameStats.currentWeapon].size); + //entities.Append(newProj); + + //We hit something + gameStats.levelStats.hitCount++; + + gameStats.levelStats.targetEnt->OnHit(key); + + if(gameStats.levelStats.targetEnt->type == SSD_ENTITY_ASTEROID) { + HitAsteroid(static_cast(gameStats.levelStats.targetEnt), key); + } else if(gameStats.levelStats.targetEnt->type == SSD_ENTITY_ASTRONAUT) { + HitAstronaut(static_cast(gameStats.levelStats.targetEnt), key); + } + } else { + ////Aim the projectile at the cursor position all the way to the far clipping + //SSDProjectile* newProj = SSDProjectile::GetNewProjectile(this, idVec3(0,-180,0), idVec3(cursorWorld.x, cursorWorld.y, (Z_FAR-Z_NEAR)/2.0f), weaponData[gameStats.currentWeapon].speed, weaponData[gameStats.currentWeapon].size); + + //Aim the projectile so it crosses the cursor 1/4 of screen + idVec3 vec = idVec3(cursorWorld.x, cursorWorld.y, (Z_FAR-Z_NEAR)/8.0f); + vec *= 8; + SSDProjectile* newProj = SSDProjectile::GetNewProjectile(this, idVec3(0,-180,0), vec, weaponData[gameStats.currentWeapon].speed, weaponData[gameStats.currentWeapon].size); + entities.Append(newProj); + + } + + + //Play the blaster sound + PlaySound("arcade_blaster"); + + } /*else if (key == K_MOUSE2) { + if(gameStats.levelStats.targetEnt) { + if(gameStats.levelStats.targetEnt->type == SSD_ENTITY_ASTRONAUT) { + HitAstronaut(static_cast(gameStats.levelStats.targetEnt), key); + } + } + }*/ +} + +SSDEntity* idGameSSDWindow::EntityHitTest(const idVec2& pt) { + + for(int i = 0; i < entities.Num(); i++) { + //Since we ZOrder the entities every frame we can stop at the first entity we hit. + //ToDo: Make sure this assumption is true + if(entities[i]->HitTest(pt)) { + return entities[i]; + } + } + return NULL; +} + +void idGameSSDWindow::HitAsteroid(SSDAsteroid* asteroid, int key) { + + + + asteroid->health -= weaponData[gameStats.currentWeapon].damage; + + if(asteroid->health <= 0) { + + //The asteroid has been destroyed + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, asteroid->position, asteroid->size*2, 300, SSDExplosion::EXPLOSION_NORMAL, asteroid); + entities.Append(explosion); + PlaySound("arcade_explode"); + + AddScore(asteroid, asteroidData[gameStats.currentLevel].asteroidPoints); + + //Don't let the player hit it anymore because + asteroid->noHit = true; + + gameStats.levelStats.destroyedAsteroids++; + //if(gameStats.levelStats.destroyedAsteroids >= levelData[gameStats.currentLevel].needToWin) { + // LevelComplete(); + //} + + } else { + //This was a damage hit so create a real small quick explosion + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, asteroid->position, asteroid->size/2.0f, 200, SSDExplosion::EXPLOSION_NORMAL, asteroid, false, false); + entities.Append(explosion); + } +} + +void idGameSSDWindow::AsteroidStruckPlayer(SSDAsteroid* asteroid) { + + asteroid->noPlayerDamage = true; + asteroid->noHit = true; + + AddDamage(asteroidData[gameStats.currentLevel].asteroidDamage); + + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, asteroid->position, asteroid->size*2, 300, SSDExplosion::EXPLOSION_NORMAL, asteroid); + entities.Append(explosion); + PlaySound("arcade_explode"); +} + +void idGameSSDWindow::AddScore(SSDEntity* ent, int points) { + + SSDPoints* pointsEnt; + + if(points > 0) { + pointsEnt = SSDPoints::GetNewPoints(this, ent, points, 1000, 50, idVec4(0,1,0,1)); + } else { + pointsEnt = SSDPoints::GetNewPoints(this, ent, points, 1000, 50, idVec4(1,0,0,1)); + } + entities.Append(pointsEnt); + + gameStats.score += points; + gui->SetStateString( "player_score", va("%i", gameStats.score ) ); +} + +void idGameSSDWindow::AddDamage(int damage) { + gameStats.health -= damage; + gui->SetStateString( "player_health", va("%i", gameStats.health ) ); + + gui->HandleNamedEvent( "playerDamage" ); + + if(gameStats.health <= 0) { + //The player is dead + GameOver(); + } +} + +void idGameSSDWindow::AddHealth(int health) { + gameStats.health += health; + gameStats.health = Min( 100, gameStats.health ); +} + + +void idGameSSDWindow::OnNuke() { + + gui->HandleNamedEvent("nuke"); + + //Destory All Asteroids + for(int i = 0 ; i < entities.Num(); i++) { + + if(entities[i]->type == SSD_ENTITY_ASTEROID) { + + //The asteroid has been destroyed + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, entities[i]->position, entities[i]->size*2, 300, SSDExplosion::EXPLOSION_NORMAL, entities[i]); + entities.Append(explosion); + + AddScore(entities[i], asteroidData[gameStats.currentLevel].asteroidPoints); + + //Don't let the player hit it anymore because + entities[i]->noHit = true; + + gameStats.levelStats.destroyedAsteroids++; + } + } + PlaySound("arcade_explode"); + + //Check to see if a nuke ends the level + /*if(gameStats.levelStats.destroyedAsteroids >= levelData[gameStats.currentLevel].needToWin) { + LevelComplete(); + + }*/ +} + +void idGameSSDWindow::OnRescueAll() { + + gui->HandleNamedEvent("rescueAll"); + + //Rescue All Astronauts + for(int i = 0 ; i < entities.Num(); i++) { + + if(entities[i]->type == SSD_ENTITY_ASTRONAUT) { + + AstronautStruckPlayer((SSDAstronaut*)entities[i]); + } + } +} + +void idGameSSDWindow::OnSuperBlaster() { + + StartSuperBlaster(); +} + + + +void idGameSSDWindow::RefreshGuiData() { + + + gui->SetStateString("nextLevel", va("%i", gameStats.nextLevel+1)); + gui->SetStateString("currentLevel", va("%i", gameStats.currentLevel+1)); + + float accuracy; + if(!gameStats.levelStats.shotCount) { + accuracy = 0; + } else { + accuracy = ((float)gameStats.levelStats.hitCount/(float)gameStats.levelStats.shotCount)*100.0f; + } + gui->SetStateString( "player_accuracy", va("%d%%", (int)accuracy)); + + float saveAccuracy; + int totalAst = gameStats.levelStats.savedAstronauts + gameStats.levelStats.killedAstronauts; + + if(!totalAst) { + saveAccuracy = 0; + } else { + saveAccuracy = ((float)gameStats.levelStats.savedAstronauts/(float)totalAst)*100.0f; + } + gui->SetStateString( "save_accuracy", va("%d%%", (int)saveAccuracy)); + + + + + if(gameStats.levelStats.targetEnt) { + int dist = (gameStats.levelStats.targetEnt->position.z/100.0f); + dist *= 100; + gui->SetStateString("target_info", va("%i meters", dist)); + } else { + gui->SetStateString("target_info", "No Target"); + } + + gui->SetStateString( "player_health", va("%i", gameStats.health ) ); + gui->SetStateString( "player_score", va("%i", gameStats.score ) ); + gui->SetStateString( "player_prebonusscore", va("%i", gameStats.prebonusscore ) ); + gui->SetStateString( "level_complete", va("%i/%i", gameStats.levelStats.savedAstronauts, levelData[gameStats.currentLevel].needToWin )); + + + if(superBlasterTimeout) { + float timeRemaining = (superBlasterTimeout - ssdTime)/1000.0f; + gui->SetStateString("super_blaster_time", va("%.2f", timeRemaining)); + } +} + +idVec2 idGameSSDWindow::GetCursorWorld() { + + idVec2 cursor; + //GetCursor(cursor); + cursor.x = gui->CursorX(); + cursor.y = gui->CursorY(); + cursor.x = cursor.x - 0.5f * V_WIDTH; + cursor.y = -(cursor.y - 0.5f * V_HEIGHT); + return cursor; +} + +void idGameSSDWindow::SpawnAstronaut() { + + int currentTime = ssdTime; + + if(currentTime < gameStats.levelStats.nextAstronautSpawnTime) { + //Not time yet + return; + } + + //Lets spawn it + idVec3 startPosition; + + startPosition.x = random.RandomInt(V_WIDTH)-(V_WIDTH/2.0f); + startPosition.y = random.RandomInt(V_HEIGHT)-(V_HEIGHT/2.0f); + startPosition.z = ENTITY_START_DIST; + + float speed = random.RandomInt(astronautData[gameStats.currentLevel].speedMax - astronautData[gameStats.currentLevel].speedMin) + astronautData[gameStats.currentLevel].speedMin; + float rotate = (random.RandomFloat() * (astronautData[gameStats.currentLevel].rotateMax - astronautData[gameStats.currentLevel].rotateMin)) + astronautData[gameStats.currentLevel].rotateMin; + + SSDAstronaut* astronaut = SSDAstronaut::GetNewAstronaut(this, startPosition, speed, rotate, astronautData[gameStats.currentLevel].health); + entities.Append(astronaut); + + gameStats.levelStats.nextAstronautSpawnTime = currentTime + random.RandomInt(astronautData[gameStats.currentLevel].spawnMax - astronautData[gameStats.currentLevel].spawnMin) + astronautData[gameStats.currentLevel].spawnMin; +} + +void idGameSSDWindow::HitAstronaut(SSDAstronaut* astronaut, int key) { + + + if(key == K_MOUSE1) { + astronaut->health -= weaponData[gameStats.currentWeapon].damage; + + if(astronaut->health <= 0) { + + gameStats.levelStats.killedAstronauts++; + + //The astronaut has been destroyed + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, astronaut->position, astronaut->size*2, 300, SSDExplosion::EXPLOSION_NORMAL, astronaut); + entities.Append(explosion); + PlaySound("arcade_explode"); + + //Add the penalty for killing the astronaut + AddScore(astronaut, astronautData[gameStats.currentLevel].penalty); + + //Don't let the player hit it anymore + astronaut->noHit = true; + } else { + //This was a damage hit so create a real small quick explosion + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, astronaut->position, astronaut->size/2.0f, 200, SSDExplosion::EXPLOSION_NORMAL, astronaut, false, false); + entities.Append(explosion); + } + } +} + +void idGameSSDWindow::AstronautStruckPlayer(SSDAstronaut* astronaut) { + + gameStats.levelStats.savedAstronauts++; + + astronaut->noPlayerDamage = true; + astronaut->noHit = true; + + //We are saving an astronaut + SSDExplosion* explosion = SSDExplosion::GetNewExplosion(this, astronaut->position, astronaut->size*2, 300, SSDExplosion::EXPLOSION_TELEPORT, astronaut); + entities.Append(explosion); + PlaySound("arcade_capture"); + + //Give the player points for saving the astronaut + AddScore(astronaut, astronautData[gameStats.currentLevel].points); + + if(gameStats.levelStats.savedAstronauts >= levelData[gameStats.currentLevel].needToWin) { + LevelComplete(); + } + +} + +void idGameSSDWindow::SpawnPowerup() { + + int currentTime = ssdTime; + + if(currentTime < gameStats.levelStats.nextPowerupSpawnTime) { + //Not time yet + return; + } + + float speed = random.RandomInt(powerupData[gameStats.currentLevel].speedMax - powerupData[gameStats.currentLevel].speedMin) + powerupData[gameStats.currentLevel].speedMin; + float rotate = (random.RandomFloat() * (powerupData[gameStats.currentLevel].rotateMax - powerupData[gameStats.currentLevel].rotateMin)) + powerupData[gameStats.currentLevel].rotateMin; + + SSDPowerup* powerup = SSDPowerup::GetNewPowerup(this, speed, rotate); + entities.Append(powerup); + + gameStats.levelStats.nextPowerupSpawnTime = currentTime + random.RandomInt(powerupData[gameStats.currentLevel].spawnMax - powerupData[gameStats.currentLevel].spawnMin) + powerupData[gameStats.currentLevel].spawnMin; + +} + +void idGameSSDWindow::StartSuperBlaster() { + + gui->HandleNamedEvent("startSuperBlaster"); + gameStats.currentWeapon = 1; + superBlasterTimeout = ssdTime + 10000; + +} +void idGameSSDWindow::StopSuperBlaster() { + gui->HandleNamedEvent("stopSuperBlaster"); + gameStats.currentWeapon = 0; + superBlasterTimeout = 0; + +} + +SSDEntity* idGameSSDWindow::GetSpecificEntity(int type, int id) { + SSDEntity* ent = NULL; + switch(type) { + case SSD_ENTITY_ASTEROID: + ent = SSDAsteroid::GetSpecificAsteroid(id); + break; + case SSD_ENTITY_ASTRONAUT: + ent = SSDAstronaut::GetSpecificAstronaut(id); + break; + case SSD_ENTITY_EXPLOSION: + ent = SSDExplosion::GetSpecificExplosion(id); + break; + case SSD_ENTITY_POINTS: + ent = SSDPoints::GetSpecificPoints(id); + break; + case SSD_ENTITY_PROJECTILE: + ent = SSDProjectile::GetSpecificProjectile(id); + break; + case SSD_ENTITY_POWERUP: + ent = SSDPowerup::GetSpecificPowerup(id); + break; + } + return ent; +} + +#define MAX_SOUND_CHANNEL 8 + +void idGameSSDWindow::PlaySound(const char* sound) { + + common->SW()->PlayShaderDirectly(sound, currentSound); + + currentSound++; + if(currentSound >= MAX_SOUND_CHANNEL) { + currentSound = 0; + } +} diff --git a/neo/ui/GameSSDWindow.h b/neo/ui/GameSSDWindow.h new file mode 100644 index 00000000..671bac92 --- /dev/null +++ b/neo/ui/GameSSDWindow.h @@ -0,0 +1,615 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __GAME_SSD_WINDOW_H__ +#define __GAME_SSD_WINDOW_H__ + +class idGameSSDWindow; + +class SSDCrossHair { + +public: + enum { + CROSSHAIR_STANDARD = 0, + CROSSHAIR_SUPER, + CROSSHAIR_COUNT + }; + const idMaterial* crosshairMaterial[CROSSHAIR_COUNT]; + int currentCrosshair; + float crosshairWidth, crosshairHeight; + +public: + SSDCrossHair(); + ~SSDCrossHair(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + void InitCrosshairs(); + void Draw(const idVec2& cursor); +}; + +enum { + SSD_ENTITY_BASE = 0, + SSD_ENTITY_ASTEROID, + SSD_ENTITY_ASTRONAUT, + SSD_ENTITY_EXPLOSION, + SSD_ENTITY_POINTS, + SSD_ENTITY_PROJECTILE, + SSD_ENTITY_POWERUP +}; + +class SSDEntity { + +public: + //SSDEntity Information + int type; + int id; + idStr materialName; + const idMaterial* material; + idVec3 position; + idVec2 size; + float radius; + float hitRadius; + float rotation; + + idVec4 matColor; + + idStr text; + float textScale; + idVec4 foreColor; + + idGameSSDWindow* game; + int currentTime; + int lastUpdate; + int elapsed; + + bool destroyed; + bool noHit; + bool noPlayerDamage; + + bool inUse; + + +public: + SSDEntity(); + virtual ~SSDEntity(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void EntityInit(); + + void SetGame(idGameSSDWindow* _game); + void SetMaterial(const char* _name); + void SetPosition(const idVec3& _position); + void SetSize(const idVec2& _size); + void SetRadius(float _radius, float _hitFactor = 1.0f); + void SetRotation(float _rotation); + + void Update(); + bool HitTest(const idVec2& pt); + + + virtual void EntityUpdate() {}; + virtual void Draw(); + virtual void DestroyEntity(); + + virtual void OnHit(int key) {}; + virtual void OnStrikePlayer() {}; + + idBounds WorldToScreen(const idBounds worldBounds); + idVec3 WorldToScreen(const idVec3& worldPos); + + idVec3 ScreenToWorld(const idVec3& screenPos); + +}; + +/* +***************************************************************************** +* SSDMover +**************************************************************************** +*/ + +class SSDMover : public SSDEntity { + +public: + idVec3 speed; + float rotationSpeed; + +public: + SSDMover(); + virtual ~SSDMover(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void MoverInit(const idVec3& _speed, float _rotationSpeed); + + virtual void EntityUpdate(); + + +}; + +/* +***************************************************************************** +* SSDAsteroid +**************************************************************************** +*/ + +#define MAX_ASTEROIDS 64 + +class SSDAsteroid : public SSDMover { + +public: + + int health; + +public: + SSDAsteroid(); + ~SSDAsteroid(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& startPosition, const idVec2& _size, float _speed, float rotate, int _health); + + virtual void EntityUpdate(); + static SSDAsteroid* GetNewAsteroid(idGameSSDWindow* _game, const idVec3& startPosition, const idVec2& _size, float _speed, float rotate, int _health); + static SSDAsteroid* GetSpecificAsteroid(int id); + static void WriteAsteroids(idFile* savefile); + static void ReadAsteroids(idFile* savefile, idGameSSDWindow* _game); + + + +protected: + static SSDAsteroid asteroidPool[MAX_ASTEROIDS]; + +}; + +/* +***************************************************************************** +* SSDAstronaut +**************************************************************************** +*/ +#define MAX_ASTRONAUT 8 + +class SSDAstronaut : public SSDMover { + +public: + + int health; + +public: + SSDAstronaut(); + ~SSDAstronaut(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& startPosition, float _speed, float rotate, int _health); + + static SSDAstronaut* GetNewAstronaut(idGameSSDWindow* _game, const idVec3& startPosition, float _speed, float rotate, int _health); + static SSDAstronaut* GetSpecificAstronaut(int id); + static void WriteAstronauts(idFile* savefile); + static void ReadAstronauts(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDAstronaut astronautPool[MAX_ASTRONAUT]; + +}; + +/* +***************************************************************************** +* SSDExplosion +**************************************************************************** +*/ +#define MAX_EXPLOSIONS 64 + +class SSDExplosion : public SSDEntity { + +public: + idVec2 finalSize; + int length; + int beginTime; + int endTime; + int explosionType; + + //The entity that is exploding + SSDEntity* buddy; + bool killBuddy; + bool followBuddy; + + enum { + EXPLOSION_NORMAL = 0, + EXPLOSION_TELEPORT = 1 + }; + +public: + SSDExplosion(); + ~SSDExplosion(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& _position, const idVec2& _size, int _length, int _type, SSDEntity* _buddy, bool _killBuddy = true, bool _followBuddy = true); + + virtual void EntityUpdate(); + static SSDExplosion* GetNewExplosion(idGameSSDWindow* _game, const idVec3& _position, const idVec2& _size, int _length, int _type, SSDEntity* _buddy, bool _killBuddy = true, bool _followBuddy = true); + static SSDExplosion* GetSpecificExplosion(int id); + static void WriteExplosions(idFile* savefile); + static void ReadExplosions(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDExplosion explosionPool[MAX_EXPLOSIONS]; +}; + +#define MAX_POINTS 16 + +class SSDPoints : public SSDEntity { + + int length; + int distance; + int beginTime; + int endTime; + + idVec3 beginPosition; + idVec3 endPosition; + + idVec4 beginColor; + idVec4 endColor; + + +public: + SSDPoints(); + ~SSDPoints(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, SSDEntity* _ent, int _points, int _length, int _distance, const idVec4& color); + virtual void EntityUpdate(); + + static SSDPoints* GetNewPoints(idGameSSDWindow* _game, SSDEntity* _ent, int _points, int _length, int _distance, const idVec4& color); + static SSDPoints* GetSpecificPoints(int id); + static void WritePoints(idFile* savefile); + static void ReadPoints(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDPoints pointsPool[MAX_POINTS]; +}; + +#define MAX_PROJECTILES 64 + +class SSDProjectile : public SSDEntity { + + idVec3 dir; + idVec3 speed; + int beginTime; + int endTime; + + idVec3 endPosition; + +public: + SSDProjectile(); + ~SSDProjectile(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& _beginPosition, const idVec3& _endPosition, float _speed, float _size); + virtual void EntityUpdate(); + + static SSDProjectile* GetNewProjectile(idGameSSDWindow* _game, const idVec3& _beginPosition, const idVec3& _endPosition, float _speed, float _size); + static SSDProjectile* GetSpecificProjectile(int id); + static void WriteProjectiles(idFile* savefile); + static void ReadProjectiles(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDProjectile projectilePool[MAX_PROJECTILES]; +}; + + +#define MAX_POWERUPS 64 + +/** +* Powerups work in two phases: +* 1.) Closed container hurls at you +* If you shoot the container it open +* 3.) If an opened powerup hits the player he aquires the powerup +* Powerup Types: +* Health - Give a specific amount of health +* Super Blaster - Increases the power of the blaster (lasts a specific amount of time) +* Asteroid Nuke - Destroys all asteroids on screen as soon as it is aquired +* Rescue Powerup - Rescues all astronauts as soon as it is acquited +* Bonus Points - Gives some bonus points when acquired +*/ +class SSDPowerup : public SSDMover { + + enum { + POWERUP_STATE_CLOSED = 0, + POWERUP_STATE_OPEN + }; + + enum { + POWERUP_TYPE_HEALTH = 0, + POWERUP_TYPE_SUPER_BLASTER, + POWERUP_TYPE_ASTEROID_NUKE, + POWERUP_TYPE_RESCUE_ALL, + POWERUP_TYPE_BONUS_POINTS, + POWERUP_TYPE_DAMAGE, + POWERUP_TYPE_MAX + }; + + int powerupState; + int powerupType; + + +public: + + +public: + SSDPowerup(); + virtual ~SSDPowerup(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + virtual void OnHit(int key); + virtual void OnStrikePlayer(); + + void OnOpenPowerup(); + void OnActivatePowerup(); + + + + void Init(idGameSSDWindow* _game, float _speed, float _rotation); + + static SSDPowerup* GetNewPowerup(idGameSSDWindow* _game, float _speed, float _rotation); + static SSDPowerup* GetSpecificPowerup(int id); + static void WritePowerups(idFile* savefile); + static void ReadPowerups(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDPowerup powerupPool[MAX_POWERUPS]; + +}; + + +typedef struct { + float spawnBuffer; + int needToWin; +} SSDLevelData_t; + +typedef struct { + float speedMin, speedMax; + float sizeMin, sizeMax; + float rotateMin, rotateMax; + int spawnMin, spawnMax; + int asteroidHealth; + int asteroidPoints; + int asteroidDamage; +} SSDAsteroidData_t; + +typedef struct { + float speedMin, speedMax; + float rotateMin, rotateMax; + int spawnMin, spawnMax; + int health; + int points; + int penalty; +} SSDAstronautData_t; + +typedef struct { + float speedMin, speedMax; + float rotateMin, rotateMax; + int spawnMin, spawnMax; +} SSDPowerupData_t; + +typedef struct { + float speed; + int damage; + int size; +} SSDWeaponData_t; + +/** +* SSDLevelStats_t +* Data that is used for each level. This data is reset +* each new level. +*/ +typedef struct { + int shotCount; + int hitCount; + int destroyedAsteroids; + int nextAsteroidSpawnTime; + + int killedAstronauts; + int savedAstronauts; + + //Astronaut Level Data + int nextAstronautSpawnTime; + + //Powerup Level Data + int nextPowerupSpawnTime; + + SSDEntity* targetEnt; +} SSDLevelStats_t; + +/** +* SSDGameStats_t +* Data that is used for the game that is currently running. Memset this +* to completely reset the game +*/ +typedef struct { + bool gameRunning; + + int score; + int prebonusscore; + + int health; + + int currentWeapon; + int currentLevel; + int nextLevel; + + SSDLevelStats_t levelStats; +} SSDGameStats_t; + + +class idGameSSDWindow : public idWindow { +public: + idGameSSDWindow(idUserInterfaceLocal *gui); + ~idGameSSDWindow(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual idWinVar* GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + + virtual void Draw(int time, float x, float y); + + void AddHealth(int health); + void AddScore(SSDEntity* ent, int points); + void AddDamage(int damage); + + void OnNuke(); + void OnRescueAll(); + void OnSuperBlaster(); + + SSDEntity* GetSpecificEntity(int type, int id); + + void PlaySound(const char* sound); + + + + + static idRandom random; + int ssdTime; + +private: + + //Initialization + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void ParseLevelData(int level, const idStr& levelDataString); + void ParseAsteroidData(int level, const idStr& asteroidDataString); + void ParseWeaponData(int weapon, const idStr& weaponDataString); + void ParseAstronautData(int level, const idStr& astronautDataString); + void ParsePowerupData(int level, const idStr& powerupDataString); + + void CommonInit(); + void ResetGameStats(); + void ResetLevelStats(); + void ResetEntities(); + + //Game Running Methods + void StartGame(); + void StopGame(); + void GameOver(); + + //Starting the Game + void BeginLevel(int level); + void ContinueGame(); + + //Stopping the Game + void LevelComplete(); + void GameComplete(); + + + + void UpdateGame(); + void CheckForHits(); + void ZOrderEntities(); + + void SpawnAsteroid(); + + void FireWeapon(int key); + SSDEntity* EntityHitTest(const idVec2& pt); + + void HitAsteroid(SSDAsteroid* asteroid, int key); + void AsteroidStruckPlayer(SSDAsteroid* asteroid); + + + + + + void RefreshGuiData(); + + idVec2 GetCursorWorld(); + + //Astronaut Methods + void SpawnAstronaut(); + void HitAstronaut(SSDAstronaut* astronaut, int key); + void AstronautStruckPlayer(SSDAstronaut* astronaut); + + //Powerup Methods + void SpawnPowerup(); + + + void StartSuperBlaster(); + void StopSuperBlaster(); + + //void FreeSoundEmitter( bool immediate ); + + + + +public: + + //WinVars used to call functions from the guis + idWinBool beginLevel; + idWinBool resetGame; + idWinBool continueGame; + idWinBool refreshGuiData; + + SSDCrossHair crosshair; + idBounds screenBounds; + + //Level Data + int levelCount; + idList levelData; + idList asteroidData; + idList astronautData; + idList powerupData; + + + //Weapon Data + int weaponCount; + idList weaponData; + + int superBlasterTimeout; + + //All current game data is stored in this structure (except the entity list) + SSDGameStats_t gameStats; + idList entities; + + int currentSound; + +}; + +#endif //__GAME_SSD_WINDOW_H__ diff --git a/neo/ui/GameWindow.cpp b/neo/ui/GameWindow.cpp new file mode 100644 index 00000000..820fbd5c --- /dev/null +++ b/neo/ui/GameWindow.cpp @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "GameWindow.h" + +/* +================ +idGameWindowProxy::idGameWindowProxy +================ +*/ +idGameWindowProxy::idGameWindowProxy( idDeviceContext *d, idUserInterfaceLocal *g ) : idWindow( d, g ) { } + +/* +================ +idGameWindowProxy::Draw +================ +*/ +void idGameWindowProxy::Draw( int time, float x, float y ) { + common->Printf( "TODO: idGameWindowProxy::Draw\n" ); +} + diff --git a/neo/ui/GameWindow.h b/neo/ui/GameWindow.h new file mode 100644 index 00000000..782ec987 --- /dev/null +++ b/neo/ui/GameWindow.h @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __GAMEWINDOW_H__ +#define __GAMEWINDOW_H__ + +class idGameWindowProxy : public idWindow { +public: + idGameWindowProxy( idDeviceContext *d, idUserInterfaceLocal *gui ); + void Draw( int time, float x, float y ); +}; + +#endif diff --git a/neo/ui/GuiScript.cpp b/neo/ui/GuiScript.cpp new file mode 100644 index 00000000..2809c3cd --- /dev/null +++ b/neo/ui/GuiScript.cpp @@ -0,0 +1,631 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "Window.h" +#include "Winvar.h" +#include "GuiScript.h" +#include "UserInterfaceLocal.h" + +/* +========================= +Script_Set +========================= +*/ +void Script_Set(idWindow *window, idList *src) { + idStr key, val; + idWinStr *dest = dynamic_cast((*src)[0].var); + if (dest) { + if (idStr::Icmp(*dest, "cmd") == 0) { + dest = dynamic_cast((*src)[1].var); + int parmCount = src->Num(); + if (parmCount > 2) { + val = dest->c_str(); + int i = 2; + while (i < parmCount) { + val += " \""; + val += (*src)[i].var->c_str(); + val += "\""; + i++; + } + window->AddCommand(val); + } else { + window->AddCommand(*dest); + } + return; + } + } + (*src)[0].var->Set((*src)[1].var->c_str()); + (*src)[0].var->SetEval(false); +} + +/* +========================= +Script_SetFocus +========================= +*/ +void Script_SetFocus(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if (parm) { + drawWin_t *win = window->GetGui()->GetDesktop()->FindChildByName(*parm); + if ( win != NULL && win->win != NULL ) { + window->SetFocus(win->win); + } + } +} + +/* +========================= +Script_ShowCursor +========================= +*/ +void Script_ShowCursor(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if ( parm ) { + if ( atoi( *parm ) ) { + window->GetGui()->GetDesktop()->ClearFlag( WIN_NOCURSOR ); + } else { + window->GetGui()->GetDesktop()->SetFlag( WIN_NOCURSOR ); + } + } +} + +/* +========================= +Script_RunScript + + run scripts must come after any set cmd set's in the script +========================= +*/ +void Script_RunScript(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if (parm) { + idStr str = window->cmd; + str += " ; runScript "; + str += parm->c_str(); + window->cmd = str; + } +} + +/* +========================= +Script_LocalSound +========================= +*/ +void Script_LocalSound(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if (parm) { + common->SW()->PlayShaderDirectly(*parm); + } +} + +/* +========================= +Script_EvalRegs +========================= +*/ +void Script_EvalRegs(idWindow *window, idList *src) { + window->EvalRegs(-1, true); +} + +/* +========================= +Script_EndGame +========================= +*/ +void Script_EndGame( idWindow *window, idList *src ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); +} + +/* +========================= +Script_ResetTime +========================= +*/ +void Script_ResetTime(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + drawWin_t *win = NULL; + if (parm != NULL && src->Num() > 1) { + win = window->GetGui()->GetDesktop()->FindChildByName(*parm); + parm = dynamic_cast((*src)[1].var); + } + if ( parm == NULL ) { + return; + } + + if (win != NULL && win->win != NULL) { + win->win->ResetTime(atoi(*parm)); + win->win->EvalRegs(-1, true); + } else { + window->ResetTime(atoi(*parm)); + window->EvalRegs(-1, true); + } +} + +/* +========================= +Script_ResetCinematics +========================= +*/ +void Script_ResetCinematics(idWindow *window, idList *src) { + window->ResetCinematics(); +} + +/* +========================= +Script_Transition +========================= +*/ +void Script_Transition(idWindow *window, idList *src) { + // transitions always affect rect or vec4 vars + if (src->Num() >= 4) { + idWinRectangle *rect = NULL; + idWinVec4 *vec4 = dynamic_cast((*src)[0].var); + // + // added float variable + idWinFloat* val = NULL; + // + if (vec4 == NULL) { + rect = dynamic_cast((*src)[0].var); + // + // added float variable + if ( NULL == rect ) { + val = dynamic_cast((*src)[0].var); + } + // + } + idWinVec4 *from = dynamic_cast((*src)[1].var); + idWinVec4 *to = dynamic_cast((*src)[2].var); + idWinStr *timeStr = dynamic_cast((*src)[3].var); + // + // added float variable + if (!((vec4 || rect || val) && from && to && timeStr)) { + // + common->Warning("Bad transition in gui %s in window %s\n", window->GetGui()->GetSourceFile(), window->GetName()); + return; + } + int time = atoi(*timeStr); + float ac = 0.0f; + float dc = 0.0f; + if (src->Num() > 4) { + idWinStr *acv = dynamic_cast((*src)[4].var); + idWinStr *dcv = dynamic_cast((*src)[5].var); + assert(acv && dcv); + ac = atof(*acv); + dc = atof(*dcv); + } + + if (vec4) { + vec4->SetEval(false); + window->AddTransition(vec4, *from, *to, time, ac, dc); + // + // added float variable + } else if ( val ) { + val->SetEval ( false ); + window->AddTransition(val, *from, *to, time, ac, dc); + // + } else { + rect->SetEval(false); + window->AddTransition(rect, *from, *to, time, ac, dc); + } + window->StartTransition(); + } +} + +typedef struct { + const char *name; + void (*handler) (idWindow *window, idList *src); + int mMinParms; + int mMaxParms; +} guiCommandDef_t; + +guiCommandDef_t commandList[] = { + { "set", Script_Set, 2, 999 }, + { "setFocus", Script_SetFocus, 1, 1 }, + { "endGame", Script_EndGame, 0, 0 }, + { "resetTime", Script_ResetTime, 0, 2 }, + { "showCursor", Script_ShowCursor, 1, 1 }, + { "resetCinematics", Script_ResetCinematics, 0, 2 }, + { "transition", Script_Transition, 4, 6 }, + { "localSound", Script_LocalSound, 1, 1 }, + { "runScript", Script_RunScript, 1, 1 }, + { "evalRegs", Script_EvalRegs, 0, 0 } +}; + +int scriptCommandCount = sizeof(commandList) / sizeof(guiCommandDef_t); + + +/* +========================= +idGuiScript::idGuiScript +========================= +*/ +idGuiScript::idGuiScript() { + ifList = NULL; + elseList = NULL; + conditionReg = -1; + handler = NULL; + parms.SetGranularity( 2 ); +} + +/* +========================= +idGuiScript::~idGuiScript +========================= +*/ +idGuiScript::~idGuiScript() { + delete ifList; + delete elseList; + int c = parms.Num(); + for ( int i = 0; i < c; i++ ) { + if ( parms[i].own ) { + delete parms[i].var; + } + } +} + +/* +========================= +idGuiScript::WriteToSaveGame +========================= +*/ +void idGuiScript::WriteToSaveGame( idFile *savefile ) { + int i; + + if ( ifList ) { + ifList->WriteToSaveGame( savefile ); + } + if ( elseList ) { + elseList->WriteToSaveGame( savefile ); + } + + savefile->Write( &conditionReg, sizeof( conditionReg ) ); + + for ( i = 0; i < parms.Num(); i++ ) { + if ( parms[i].own ) { + parms[i].var->WriteToSaveGame( savefile ); + } + } +} + +/* +========================= +idGuiScript::ReadFromSaveGame +========================= +*/ +void idGuiScript::ReadFromSaveGame( idFile *savefile ) { + int i; + + if ( ifList ) { + ifList->ReadFromSaveGame( savefile ); + } + if ( elseList ) { + elseList->ReadFromSaveGame( savefile ); + } + + savefile->Read( &conditionReg, sizeof( conditionReg ) ); + + for ( i = 0; i < parms.Num(); i++ ) { + if ( parms[i].own ) { + parms[i].var->ReadFromSaveGame( savefile ); + } + } +} + +/* +========================= +idGuiScript::Parse +========================= +*/ +bool idGuiScript::Parse(idTokenParser *src) { + int i; + + // first token should be function call + // then a potentially variable set of parms + // ended with a ; + idToken token; + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + + handler = NULL; + + for ( i = 0; i < scriptCommandCount ; i++ ) { + if ( idStr::Icmp(token, commandList[i].name) == 0 ) { + handler = commandList[i].handler; + break; + } + } + + if (handler == NULL) { + src->Error("Uknown script call %s", token.c_str()); + } + // now read parms til ; + // all parms are read as idWinStr's but will be fixed up later + // to be proper types + while (1) { + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + + if (idStr::Icmp(token, ";") == 0) { + break; + } + + if (idStr::Icmp(token, "}") == 0) { + src->UnreadToken(&token); + break; + } + + idWinStr *str = new (TAG_OLD_UI) idWinStr(); + *str = token; + idGSWinVar wv; + wv.own = true; + wv.var = str; + parms.Append( wv ); + } + + // + // verify min/max params + if ( handler && (parms.Num() < commandList[i].mMinParms || parms.Num() > commandList[i].mMaxParms ) ) { + src->Error("incorrect number of parameters for script %s", commandList[i].name ); + } + // + + return true; +} + +/* +========================= +idGuiScriptList::Execute +========================= +*/ +void idGuiScriptList::Execute(idWindow *win) { + int c = list.Num(); + for (int i = 0; i < c; i++) { + idGuiScript *gs = list[i]; + assert(gs); + if (gs->conditionReg >= 0) { + if (win->HasOps()) { + float f = win->EvalRegs(gs->conditionReg); + if (f) { + if (gs->ifList) { + win->RunScriptList(gs->ifList); + } + } else if (gs->elseList) { + win->RunScriptList(gs->elseList); + } + } + } + gs->Execute(win); + } +} + +/* +========================= +idGuiScriptList::FixupParms +========================= +*/ +void idGuiScript::FixupParms(idWindow *win) { + if (handler == &Script_Set) { + bool precacheBackground = false; + bool precacheSounds = false; + idWinStr *str = dynamic_cast(parms[0].var); + assert(str); + idWinVar *dest = win->GetWinVarByName(*str, true); + if (dest) { + delete parms[0].var; + parms[0].var = dest; + parms[0].own = false; + + if ( dynamic_cast(dest) != NULL ) { + precacheBackground = true; + } + } else if ( idStr::Icmp( str->c_str(), "cmd" ) == 0 ) { + precacheSounds = true; + } + int parmCount = parms.Num(); + for (int i = 1; i < parmCount; i++) { + idWinStr *str = dynamic_cast(parms[i].var); + if (idStr::Icmpn(*str, "gui::", 5) == 0) { + + // always use a string here, no point using a float if it is one + // FIXME: This creates duplicate variables, while not technically a problem since they + // are all bound to the same guiDict, it does consume extra memory and is generally a bad thing + idWinStr* defvar = new (TAG_OLD_UI) idWinStr(); + defvar->Init ( *str, win ); + win->AddDefinedVar ( defvar ); + delete parms[i].var; + parms[i].var = defvar; + parms[i].own = false; + + //dest = win->GetWinVarByName(*str, true); + //if (dest) { + // delete parms[i].var; + // parms[i].var = dest; + // parms[i].own = false; + //} + // + } else if ((*str[0]) == '$') { + // + // dont include the $ when asking for variable + dest = win->GetGui()->GetDesktop()->GetWinVarByName((const char*)(*str) + 1, true); + // + if (dest) { + delete parms[i].var; + parms[i].var = dest; + parms[i].own = false; + } + } else if ( idStr::Cmpn( str->c_str(), STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + str->Set( idLocalization::GetString( str->c_str() ) ); + } else if ( precacheBackground ) { + const idMaterial *mat = declManager->FindMaterial( str->c_str() ); + mat->SetSort( SS_GUI ); + } else if ( precacheSounds ) { + // Search for "play <...>" + idToken token; + idParser parser( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + parser.LoadMemory(str->c_str(), str->Length(), "command"); + + while ( parser.ReadToken(&token) ) { + if ( token.Icmp("play") == 0 ) { + if ( parser.ReadToken(&token) && ( token != "" ) ) { + declManager->FindSound( token.c_str() ); + } + } + } + } + } + } else if (handler == &Script_Transition) { + if (parms.Num() < 4) { + common->Warning("Window %s in gui %s has a bad transition definition", win->GetName(), win->GetGui()->GetSourceFile()); + } + idWinStr *str = dynamic_cast(parms[0].var); + assert(str); + + // + drawWin_t *destowner; + idWinVar *dest = win->GetWinVarByName(*str, true, &destowner ); + // + + if (dest) { + delete parms[0].var; + parms[0].var = dest; + parms[0].own = false; + } else { + common->Warning("Window %s in gui %s: a transition does not have a valid destination var %s", win->GetName(), win->GetGui()->GetSourceFile(),str->c_str()); + } + + // + // support variables as parameters + int c; + for ( c = 1; c < 3; c ++ ) { + str = dynamic_cast(parms[c].var); + + idWinVec4 *v4 = new (TAG_OLD_UI) idWinVec4; + parms[c].var = v4; + parms[c].own = true; + + drawWin_t* owner = NULL; + + if ( (*str[0]) == '$' ) { + dest = win->GetWinVarByName ( (const char*)(*str) + 1, true, &owner ); + } else { + dest = NULL; + } + + if ( dest ) { + idWindow* ownerparent; + idWindow* destparent; + if ( owner != NULL ) { + ownerparent = owner->simp?owner->simp->GetParent():owner->win->GetParent(); + destparent = destowner->simp?destowner->simp->GetParent():destowner->win->GetParent(); + + // If its the rectangle they are referencing then adjust it + if ( ownerparent && destparent && + (dest == (owner->simp?owner->simp->GetWinVarByName ( "rect" ):owner->win->GetWinVarByName ( "rect" ) ) ) ) + { + idRectangle rect; + rect = *(dynamic_cast(dest)); + ownerparent->ClientToScreen ( &rect ); + destparent->ScreenToClient ( &rect ); + *v4 = rect.ToVec4 ( ); + } else { + v4->Set ( dest->c_str ( ) ); + } + } else { + v4->Set ( dest->c_str ( ) ); + } + } else { + v4->Set(*str); + } + + delete str; + } + // + } else if (handler == &Script_LocalSound) { + idWinStr * str = dynamic_cast(parms[0].var); + if ( str ) { + declManager->FindSound( str->c_str() ); + } + } else { + int c = parms.Num(); + for (int i = 0; i < c; i++) { + parms[i].var->Init(parms[i].var->c_str(), win); + } + } +} + +/* +========================= +idGuiScriptList::FixupParms +========================= +*/ +void idGuiScriptList::FixupParms(idWindow *win) { + int c = list.Num(); + for (int i = 0; i < c; i++) { + idGuiScript *gs = list[i]; + gs->FixupParms(win); + if (gs->ifList) { + gs->ifList->FixupParms(win); + } + if (gs->elseList) { + gs->elseList->FixupParms(win); + } + } +} + +/* +========================= +idGuiScriptList::WriteToSaveGame +========================= +*/ +void idGuiScriptList::WriteToSaveGame( idFile *savefile ) { + int i; + + for ( i = 0; i < list.Num(); i++ ) { + list[i]->WriteToSaveGame( savefile ); + } +} + +/* +========================= +idGuiScriptList::ReadFromSaveGame +========================= +*/ +void idGuiScriptList::ReadFromSaveGame( idFile *savefile ) { + int i; + + for ( i = 0; i < list.Num(); i++ ) { + list[i]->ReadFromSaveGame( savefile ); + } +} diff --git a/neo/ui/GuiScript.h b/neo/ui/GuiScript.h new file mode 100644 index 00000000..648ce1f8 --- /dev/null +++ b/neo/ui/GuiScript.h @@ -0,0 +1,105 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __GUISCRIPT_H +#define __GUISCRIPT_H + +#include "Window.h" +#include "Winvar.h" + +struct idGSWinVar { + idGSWinVar() { + var = NULL; + own = false; + } + idWinVar* var; + bool own; +}; + +class idGuiScriptList; + +class idGuiScript { + friend class idGuiScriptList; + friend class idWindow; + +public: + idGuiScript(); + ~idGuiScript(); + + bool Parse(idTokenParser *src); + void Execute(idWindow *win) { + if (handler) { + handler(win, &parms); + } + } + void FixupParms(idWindow *win); + size_t Size() { + int sz = sizeof(*this); + for (int i = 0; i < parms.Num(); i++) { + sz += parms[i].var->Size(); + } + return sz; + } + + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); + +protected: + int conditionReg; + idGuiScriptList *ifList; + idGuiScriptList *elseList; + idList parms; + void (*handler) (idWindow *window, idList *src); + +}; + + +class idGuiScriptList { + idList list; +public: + idGuiScriptList() { list.SetGranularity( 4 ); }; + ~idGuiScriptList() { list.DeleteContents(true); }; + void Execute(idWindow *win); + void Append(idGuiScript* gs) { + list.Append(gs); + } + size_t Size() { + int sz = sizeof(*this); + for (int i = 0; i < list.Num(); i++) { + sz += list[i]->Size(); + } + return sz; + } + void FixupParms(idWindow *win); + void ReadFromDemoFile( class idDemoFile *f ) {}; + void WriteToDemoFile( class idDemoFile *f ) {}; + + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); +}; + +#endif // __GUISCRIPT_H diff --git a/neo/ui/ListGUI.cpp b/neo/ui/ListGUI.cpp new file mode 100644 index 00000000..e4441bf6 --- /dev/null +++ b/neo/ui/ListGUI.cpp @@ -0,0 +1,184 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "ListGUILocal.h" + +/* +==================== +idListGUILocal::StateChanged +==================== +*/ +void idListGUILocal::StateChanged() { + int i; + + if ( !m_stateUpdates ) { + return; + } + + for( i = 0; i < Num(); i++ ) { + m_pGUI->SetStateString( va( "%s_item_%i", m_name.c_str(), i ), (*this)[i].c_str() ); + } + for( i = Num() ; i < m_water ; i++ ) { + m_pGUI->SetStateString( va( "%s_item_%i", m_name.c_str(), i ), "" ); + } + m_water = Num(); + m_pGUI->StateChanged( Sys_Milliseconds() ); +} + +/* +==================== +idListGUILocal::GetNumSelections +==================== +*/ +int idListGUILocal::GetNumSelections() { + return m_pGUI->State().GetInt( va( "%s_numsel", m_name.c_str() ) ); +} + +/* +==================== +idListGUILocal::GetSelection +==================== +*/ +int idListGUILocal::GetSelection( char *s, int size, int _sel ) const { + if ( s ) { + s[ 0 ] = '\0'; + } + int sel = m_pGUI->State().GetInt( va( "%s_sel_%i", m_name.c_str(), _sel ), "-1" ); + if ( sel == -1 || sel >= m_ids.Num() ) { + return -1; + } + if ( s ) { + idStr::snPrintf( s, size, m_pGUI->State().GetString( va( "%s_item_%i", m_name.c_str(), sel ), "" ) ); + } + // don't let overflow + if ( sel >= m_ids.Num() ) { + sel = 0; + } + m_pGUI->SetStateInt( va( "%s_selid_0", m_name.c_str() ), m_ids[ sel ] ); + return m_ids[ sel ]; +} + +/* +==================== +idListGUILocal::SetSelection +==================== +*/ +void idListGUILocal::SetSelection( int sel ) { + m_pGUI->SetStateInt( va( "%s_sel_0", m_name.c_str() ), sel ); + StateChanged(); +} + +/* +==================== +idListGUILocal::Add +==================== +*/ +void idListGUILocal::Add( int id, const idStr &s ) { + int i = m_ids.FindIndex( id ); + if ( i == -1 ) { + Append( s ); + m_ids.Append( id ); + } else { + (*this)[ i ] = s; + } + StateChanged(); +} + +/* +==================== +idListGUILocal::Push +==================== +*/ +void idListGUILocal::Push( const idStr& s ) { + Append( s ); + m_ids.Append( m_ids.Num() ); + StateChanged(); +} + +/* +==================== +idListGUILocal::Del +==================== +*/ +bool idListGUILocal::Del(int id) { + int i = m_ids.FindIndex(id); + if ( i == -1 ) { + return false; + } + m_ids.RemoveIndex( i ); + this->RemoveIndex( i ); + StateChanged(); + return true; +} + +/* +==================== +idListGUILocal::Clear +==================== +*/ +void idListGUILocal::Clear() { + m_ids.Clear(); + idList::Clear(); + if ( m_pGUI ) { + // will clear all the GUI variables and will set m_water back to 0 + StateChanged(); + } +} + +/* +==================== +idListGUILocal::IsConfigured +==================== +*/ +bool idListGUILocal::IsConfigured() const { + return m_pGUI != NULL; +} + +/* +==================== +idListGUILocal::SetStateChanges +==================== +*/ +void idListGUILocal::SetStateChanges( bool enable ) { + m_stateUpdates = enable; + StateChanged(); +} + +/* +==================== +idListGUILocal::Shutdown +==================== +*/ +void idListGUILocal::Shutdown() { + m_pGUI = NULL; + m_name.Clear(); + Clear(); +} diff --git a/neo/ui/ListGUI.h b/neo/ui/ListGUI.h new file mode 100644 index 00000000..fd754511 --- /dev/null +++ b/neo/ui/ListGUI.h @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LISTGUI_H__ +#define __LISTGUI_H__ + +/* +=============================================================================== + + feed data to a listDef + each item has an id and a display string + +=============================================================================== +*/ + +class idListGUI { +public: + virtual ~idListGUI() { } + + virtual void Config( idUserInterface *pGUI, const char *name ) = 0; + virtual void Add( int id, const idStr& s ) = 0; + // use the element count as index for the ids + virtual void Push( const idStr& s ) = 0; + virtual bool Del( int id ) = 0; + virtual void Clear() = 0; + virtual int Num() = 0; + virtual int GetSelection( char *s, int size, int sel = 0 ) const = 0; // returns the id, not the list index (or -1) + virtual void SetSelection( int sel ) = 0; + virtual int GetNumSelections() = 0; + virtual bool IsConfigured() const = 0; + // by default, any modification to the list will trigger a full GUI refresh immediately + virtual void SetStateChanges( bool enable ) = 0; + virtual void Shutdown() = 0; +}; + +#endif /* !__LISTGUI_H__ */ diff --git a/neo/ui/ListGUILocal.h b/neo/ui/ListGUILocal.h new file mode 100644 index 00000000..ba635767 --- /dev/null +++ b/neo/ui/ListGUILocal.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __LISTGUILOCAL_H__ +#define __LISTGUILOCAL_H__ + +/* +=============================================================================== + + feed data to a listDef + each item has an id and a display string + +=============================================================================== +*/ + +class idListGUILocal : protected idList, public idListGUI { +public: + idListGUILocal() { m_pGUI = NULL; m_water = 0; m_stateUpdates = true; } + + // idListGUI interface + void Config( idUserInterface *pGUI, const char *name ) { m_pGUI = pGUI; m_name = name; } + void Add( int id, const idStr& s ); + // use the element count as index for the ids + void Push( const idStr& s ); + bool Del( int id ); + void Clear(); + int Num() { return idList::Num(); } + int GetSelection( char *s, int size, int sel = 0 ) const; // returns the id, not the list index (or -1) + void SetSelection( int sel ); + int GetNumSelections(); + bool IsConfigured() const; + void SetStateChanges( bool enable ); + void Shutdown(); + +private: + idUserInterface * m_pGUI; + idStr m_name; + int m_water; + idList m_ids; + bool m_stateUpdates; + + void StateChanged(); +}; + +#endif /* !__LISTGUILOCAL_H__ */ diff --git a/neo/ui/ListWindow.cpp b/neo/ui/ListWindow.cpp new file mode 100644 index 00000000..238452c1 --- /dev/null +++ b/neo/ui/ListWindow.cpp @@ -0,0 +1,621 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SliderWindow.h" +#include "ListWindow.h" + +// Number of pixels above the text that the rect starts +static const int pixelOffset = 3; + +// number of pixels between columns +static const int tabBorder = 4; + +// Time in milliseconds between clicks to register as a double-click +static const int doubleClickSpeed = 300; + +void idListWindow::CommonInit() { + typed = ""; + typedTime = 0; + clickTime = 0; + currentSel.Clear(); + top = 0; + sizeBias = 0; + horizontal = false; + scroller = new (TAG_OLD_UI) idSliderWindow(gui); + multipleSel = false; +} + +idListWindow::idListWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +void idListWindow::SetCurrentSel( int sel ) { + currentSel.Clear(); + currentSel.Append( sel ); +} + +void idListWindow::ClearSelection( int sel ) { + int cur = currentSel.FindIndex( sel ); + if ( cur >= 0 ) { + currentSel.RemoveIndex( cur ); + } +} + +void idListWindow::AddCurrentSel( int sel ) { + currentSel.Append( sel ); +} + +int idListWindow::GetCurrentSel() { + return ( currentSel.Num() ) ? currentSel[0] : 0; +} + +bool idListWindow::IsSelected( int index ) { + return ( currentSel.FindIndex( index ) >= 0 ); +} + +const char *idListWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + // need to call this to allow proper focus and capturing on embedded children + const char *ret = idWindow::HandleEvent(event, updateVisuals); + + float vert = GetMaxCharHeight(); + int numVisibleLines = textRect.h / vert; + + int key = event->evValue; + + if ( event->evType == SE_KEY ) { + if ( !event->evValue2 ) { + // We only care about key down, not up + return ret; + } + + if ( key == K_MOUSE1 || key == K_MOUSE2 ) { + // If the user clicked in the scroller, then ignore it + if ( scroller->Contains(gui->CursorX(), gui->CursorY()) ) { + return ret; + } + } + + if ( ( key == K_ENTER || key == K_KP_ENTER ) ) { + RunScript( ON_ENTER ); + return cmd; + } + + if ( key == K_MWHEELUP ) { + key = K_UPARROW; + } else if ( key == K_MWHEELDOWN ) { + key = K_DOWNARROW; + } + + if ( key == K_MOUSE1) { + if (Contains(gui->CursorX(), gui->CursorY())) { + int cur = ( int )( ( gui->CursorY() - actualY - pixelOffset ) / vert ) + top; + if ( cur >= 0 && cur < listItems.Num() ) { + if ( multipleSel && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { + if ( IsSelected( cur ) ) { + ClearSelection( cur ); + } else { + AddCurrentSel( cur ); + } + } else { + if ( IsSelected( cur ) && ( gui->GetTime() < clickTime + doubleClickSpeed ) ) { + // Double-click causes ON_ENTER to get run + RunScript(ON_ENTER); + return cmd; + } + SetCurrentSel( cur ); + + clickTime = gui->GetTime(); + } + } else { + SetCurrentSel( listItems.Num() - 1 ); + } + } + } else if ( key == K_UPARROW || key == K_PGUP || key == K_DOWNARROW || key == K_PGDN ) { + int numLines = 1; + + if ( key == K_PGUP || key == K_PGDN ) { + numLines = numVisibleLines / 2; + } + + if ( key == K_UPARROW || key == K_PGUP ) { + numLines = -numLines; + } + + if ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) { + top += numLines; + } else { + SetCurrentSel( GetCurrentSel() + numLines ); + } + } else { + return ret; + } + } else if ( event->evType == SE_CHAR ) { + if ( !idStr::CharIsPrintable(key) ) { + return ret; + } + + if ( gui->GetTime() > typedTime + 1000 ) { + typed = ""; + } + typedTime = gui->GetTime(); + typed.Append( key ); + + for ( int i=0; i= listItems.Num() ) { + SetCurrentSel( listItems.Num() - 1 ); + } + + if ( scroller->GetHigh() > 0.0f ) { + if ( !idKeyInput::IsDown( K_LCTRL ) && !idKeyInput::IsDown( K_RCTRL ) ) { + if ( top > GetCurrentSel() - 1 ) { + top = GetCurrentSel() - 1; + } + if ( top < GetCurrentSel() - numVisibleLines + 2 ) { + top = GetCurrentSel() - numVisibleLines + 2; + } + } + + if ( top > listItems.Num() - 2 ) { + top = listItems.Num() - 2; + } + if ( top < 0 ) { + top = 0; + } + scroller->SetValue(top); + } else { + top = 0; + scroller->SetValue(0.0f); + } + + if ( key != K_MOUSE1 ) { + // Send a fake mouse click event so onAction gets run in our parents + const sysEvent_t ev = sys->GenerateMouseButtonEvent( 1, true ); + idWindow::HandleEvent(&ev, updateVisuals); + } + + if ( currentSel.Num() > 0 ) { + for ( int i = 0; i < currentSel.Num(); i++ ) { + gui->SetStateInt( va( "%s_sel_%i", listName.c_str(), i ), currentSel[i] ); + } + } else { + gui->SetStateInt( va( "%s_sel_0", listName.c_str() ), 0 ); + } + gui->SetStateInt( va( "%s_numsel", listName.c_str() ), currentSel.Num() ); + + return ret; +} + + +bool idListWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if (idStr::Icmp(_name, "horizontal") == 0) { + horizontal = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "listname") == 0) { + ParseString(src, listName); + return true; + } + if (idStr::Icmp(_name, "tabstops") == 0) { + ParseString(src, tabStopStr); + return true; + } + if (idStr::Icmp(_name, "tabaligns") == 0) { + ParseString(src, tabAlignStr); + return true; + } + if (idStr::Icmp(_name, "multipleSel") == 0) { + multipleSel = src->ParseBool(); + return true; + } + if(idStr::Icmp(_name, "tabvaligns") == 0) { + ParseString(src, tabVAlignStr); + return true; + } + if(idStr::Icmp(_name, "tabTypes") == 0) { + ParseString(src, tabTypeStr); + return true; + } + if(idStr::Icmp(_name, "tabIconSizes") == 0) { + ParseString(src, tabIconSizeStr); + return true; + } + if(idStr::Icmp(_name, "tabIconVOffset") == 0) { + ParseString(src, tabIconVOffsetStr); + return true; + } + + idStr strName = _name; + if(idStr::Icmp(strName.Left(4), "mtr_") == 0) { + idStr matName; + const idMaterial* mat; + + ParseString(src, matName); + mat = declManager->FindMaterial(matName); + if ( mat != NULL && !mat->TestMaterialFlag( MF_DEFAULTED ) ) { + mat->SetSort(SS_GUI ); + } + iconMaterials.Set(_name, mat); + return true; + } + + return idWindow::ParseInternalVar(_name, src); +} + +idWinVar *idListWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + return idWindow::GetWinVarByName(_name, fixup, owner); +} + +void idListWindow::PostParse() { + idWindow::PostParse(); + + InitScroller(horizontal); + + idList tabStops; + idList tabAligns; + if (tabStopStr.Length()) { + idParser src(tabStopStr, tabStopStr.Length(), "tabstops", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabStops.Append(atoi(tok)); + } + } + if (tabAlignStr.Length()) { + idParser src(tabAlignStr, tabAlignStr.Length(), "tabaligns", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabAligns.Append(atoi(tok)); + } + } + idList tabVAligns; + if (tabVAlignStr.Length()) { + idParser src(tabVAlignStr, tabVAlignStr.Length(), "tabvaligns", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabVAligns.Append(atoi(tok)); + } + } + + idList tabTypes; + if (tabTypeStr.Length()) { + idParser src(tabTypeStr, tabTypeStr.Length(), "tabtypes", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabTypes.Append(atoi(tok)); + } + } + idList tabSizes; + if (tabIconSizeStr.Length()) { + idParser src(tabIconSizeStr, tabIconSizeStr.Length(), "tabiconsizes", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + idVec2 size; + size.x = atoi(tok); + + src.ReadToken(&tok); //"," + src.ReadToken(&tok); + + size.y = atoi(tok); + tabSizes.Append(size); + } + } + + idList tabIconVOffsets; + if (tabIconVOffsetStr.Length()) { + idParser src(tabIconVOffsetStr, tabIconVOffsetStr.Length(), "tabiconvoffsets", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabIconVOffsets.Append(atof(tok)); + } + } + + int c = tabStops.Num(); + bool doAligns = (tabAligns.Num() == tabStops.Num()); + for (int i = 0; i < c; i++) { + idTabRect r; + r.x = tabStops[i]; + r.w = (i < c - 1) ? tabStops[i+1] - r.x - tabBorder : -1; + r.align = (doAligns) ? tabAligns[i] : 0; + if(tabVAligns.Num() > 0) { + r.valign = tabVAligns[i]; + } else { + r.valign = 0; + } + if(tabTypes.Num() > 0) { + r.type = tabTypes[i]; + } else { + r.type = TAB_TYPE_TEXT; + } + if(tabSizes.Num() > 0) { + r.iconSize = tabSizes[i]; + } else { + r.iconSize.Zero(); + } + if(tabIconVOffsets.Num() > 0 ) { + r.iconVOffset = tabIconVOffsets[i]; + } else { + r.iconVOffset = 0; + } + tabInfo.Append(r); + } + flags |= WIN_CANFOCUS; +} + +/* +================ +idListWindow::InitScroller + +This is the same as in idEditWindow +================ +*/ +void idListWindow::InitScroller( bool horizontal ) +{ + const char *thumbImage = "guis/assets/scrollbar_thumb.tga"; + const char *barImage = "guis/assets/scrollbarv.tga"; + const char *scrollerName = "_scrollerWinV"; + + if (horizontal) { + barImage = "guis/assets/scrollbarh.tga"; + scrollerName = "_scrollerWinH"; + } + + const idMaterial *mat = declManager->FindMaterial( barImage ); + mat->SetSort( SS_GUI ); + sizeBias = mat->GetImageWidth(); + + idRectangle scrollRect; + if (horizontal) { + sizeBias = mat->GetImageHeight(); + scrollRect.x = 0; + scrollRect.y = (clientRect.h - sizeBias); + scrollRect.w = clientRect.w; + scrollRect.h = sizeBias; + } else { + scrollRect.x = (clientRect.w - sizeBias); + scrollRect.y = 0; + scrollRect.w = sizeBias; + scrollRect.h = clientRect.h; + } + + scroller->InitWithDefaults(scrollerName, scrollRect, foreColor, matColor, mat->GetName(), thumbImage, !horizontal, true); + InsertChild(scroller, NULL); + scroller->SetBuddy(this); +} + +void idListWindow::Draw(int time, float x, float y) { + idVec4 color; + idStr work; + int count = listItems.Num(); + idRectangle rect = textRect; + float scale = textScale; + float lineHeight = GetMaxCharHeight(); + + float bottom = textRect.Bottom(); + float width = textRect.w; + + if ( scroller->GetHigh() > 0.0f ) { + if ( horizontal ) { + bottom -= sizeBias; + } else { + width -= sizeBias; + rect.w = width; + } + } + + if ( noEvents || !Contains(gui->CursorX(), gui->CursorY()) ) { + hover = false; + } + + for (int i = top; i < count; i++) { + if ( IsSelected( i ) ) { + rect.h = lineHeight; + dc->DrawFilledRect(rect.x, rect.y + pixelOffset, rect.w, rect.h, borderColor); + if ( flags & WIN_FOCUS ) { + idVec4 color = borderColor; + color.w = 1.0f; + dc->DrawRect(rect.x, rect.y + pixelOffset, rect.w, rect.h, 1.0f, color ); + } + } + rect.y ++; + rect.h = lineHeight - 1; + if ( hover && !noEvents && Contains(rect, gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + color = foreColor; + } + rect.h = lineHeight + pixelOffset; + rect.y --; + + if ( tabInfo.Num() > 0 ) { + int start = 0; + int tab = 0; + int stop = listItems[i].Find('\t', 0); + while ( start < listItems[i].Length() ) { + if ( tab >= tabInfo.Num() ) { + common->Warning( "idListWindow::Draw: gui '%s' window '%s' tabInfo.Num() exceeded", gui->GetSourceFile(), name.c_str() ); + break; + } + listItems[i].Mid(start, stop - start, work); + + rect.x = textRect.x + tabInfo[tab].x; + rect.w = (tabInfo[tab].w == -1) ? width - tabInfo[tab].x : tabInfo[tab].w; + dc->PushClipRect( rect ); + + if ( tabInfo[tab].type == TAB_TYPE_TEXT ) { + dc->DrawText(work, scale, tabInfo[tab].align, color, rect, false, -1); + } else if (tabInfo[tab].type == TAB_TYPE_ICON) { + + const idMaterial **hashMat; + const idMaterial *iconMat; + + // leaving the icon name empty doesn't draw anything + if ( work[0] != '\0' ) { + + if ( iconMaterials.Get(work, &hashMat) == false ) { + iconMat = declManager->FindMaterial("_default"); + } else { + iconMat = *hashMat; + } + + idRectangle iconRect; + iconRect.w = tabInfo[tab].iconSize.x; + iconRect.h = tabInfo[tab].iconSize.y; + + if(tabInfo[tab].align == idDeviceContext::ALIGN_LEFT) { + iconRect.x = rect.x; + } else if (tabInfo[tab].align == idDeviceContext::ALIGN_CENTER) { + iconRect.x = rect.x + rect.w/2.0f - iconRect.w/2.0f; + } else if (tabInfo[tab].align == idDeviceContext::ALIGN_RIGHT) { + iconRect.x = rect.x + rect.w - iconRect.w; + } + + if(tabInfo[tab].valign == 0) { //Top + iconRect.y = rect.y + tabInfo[tab].iconVOffset; + } else if(tabInfo[tab].valign == 1) { //Center + iconRect.y = rect.y + rect.h/2.0f - iconRect.h/2.0f + tabInfo[tab].iconVOffset; + } else if(tabInfo[tab].valign == 2) { //Bottom + iconRect.y = rect.y + rect.h - iconRect.h + tabInfo[tab].iconVOffset; + } + + dc->DrawMaterial(iconRect.x, iconRect.y, iconRect.w, iconRect.h, iconMat, idVec4(1.0f,1.0f,1.0f,1.0f), 1.0f, 1.0f); + + } + } + + dc->PopClipRect(); + + start = stop + 1; + stop = listItems[i].Find('\t', start); + if ( stop < 0 ) { + stop = listItems[i].Length(); + } + tab++; + } + rect.x = textRect.x; + rect.w = width; + } else { + dc->DrawText(listItems[i], scale, 0, color, rect, false, -1); + } + rect.y += lineHeight; + if ( rect.y > bottom ) { + break; + } + } +} + +void idListWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + + if ( activate ) { + UpdateList(); + } +} + +void idListWindow::HandleBuddyUpdate(idWindow *buddy) { + top = scroller->GetValue(); +} + +void idListWindow::UpdateList() { + idStr str, strName; + listItems.Clear(); + for (int i = 0; i < MAX_LIST_ITEMS; i++) { + if (gui->State().GetString( va("%s_item_%i", listName.c_str(), i), "", str) ) { + if ( str.Length() ) { + listItems.Append(str); + } + } else { + break; + } + } + float vert = GetMaxCharHeight(); + int fit = textRect.h / vert; + if ( listItems.Num() < fit ) { + scroller->SetRange(0.0f, 0.0f, 1.0f); + } else { + scroller->SetRange(0.0f, (listItems.Num() - fit) + 1.0f, 1.0f); + } + + SetCurrentSel( gui->State().GetInt( va( "%s_sel_0", listName.c_str() ) ) ); + + float value = scroller->GetValue(); + if ( value > listItems.Num() - 1 ) { + value = listItems.Num() - 1; + } + if ( value < 0.0f ) { + value = 0.0f; + } + scroller->SetValue(value); + top = value; + + typedTime = 0; + clickTime = 0; + typed = ""; +} + +void idListWindow::StateChanged( bool redraw ) { + UpdateList(); +} + diff --git a/neo/ui/ListWindow.h b/neo/ui/ListWindow.h new file mode 100644 index 00000000..57386e66 --- /dev/null +++ b/neo/ui/ListWindow.h @@ -0,0 +1,97 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __LISTWINDOW_H +#define __LISTWINDOW_H + +class idSliderWindow; + +enum { + TAB_TYPE_TEXT = 0, + TAB_TYPE_ICON = 1 +}; + +struct idTabRect { + int x; + int w; + int align; + int valign; + int type; + idVec2 iconSize; + float iconVOffset; +}; + +class idListWindow : public idWindow { +public: + idListWindow(idUserInterfaceLocal *gui); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual void Activate(bool activate, idStr &act); + virtual void HandleBuddyUpdate(idWindow *buddy); + virtual void StateChanged( bool redraw = false ); + virtual size_t Allocated(){return idWindow::Allocated();}; + virtual idWinVar* GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + void UpdateList(); + +private: + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void CommonInit(); + void InitScroller( bool horizontal ); + void SetCurrentSel( int sel ); + void AddCurrentSel( int sel ); + int GetCurrentSel(); + bool IsSelected( int index ); + void ClearSelection( int sel ); + + idList tabInfo; + int top; + float sizeBias; + bool horizontal; + idStr tabStopStr; + idStr tabAlignStr; + idStr tabVAlignStr; + idStr tabTypeStr; + idStr tabIconSizeStr; + idStr tabIconVOffsetStr; + idHashTable iconMaterials; + bool multipleSel; + + idStrList listItems; + idSliderWindow* scroller; + idList currentSel; + idStr listName; + + int clickTime; + + int typedTime; + idStr typed; +}; + +#endif // __LISTWINDOW_H diff --git a/neo/ui/Rectangle.h b/neo/ui/Rectangle.h new file mode 100644 index 00000000..90e0432c --- /dev/null +++ b/neo/ui/Rectangle.h @@ -0,0 +1,224 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef IDRECTANGLE_H_ +#define IDRECTANGLE_H_ +// +// simple rectangle +// +extern void RotateVector(idVec3 &v, idVec3 origin, float a, float c, float s); +class idRectangle { +public: + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; + idRectangle() { x = y = w= h = 0.0; } + idRectangle(float ix, float iy, float iw, float ih) { x = ix; y = iy; w = iw; h = ih; } + float Bottom() const { return y + h; } + float Right() const { return x + w; } + void Offset (float x, float y) { + this->x += x; + this->y += y; + } + bool Contains(float xt, float yt) { + if (w == 0.0 && h == 0.0) { + return false; + } + if (xt >= x && xt <= Right() && yt >= y && yt <= Bottom()) { + return true; + } + return false; + } + void Empty() { x = y = w = h = 0.0; }; + + void ClipAgainst(idRectangle r, bool sizeOnly) { + if (!sizeOnly) { + if (x < r.x) { + w -= r.x - x; + x = r.x; + } + if (y < r.y) { + h -= r.y - y; + y = r.y; + } + } + if (x + w > r.x + r.w) { + w = (r.x + r.w) - x; + } + if (y + h > r.y + r.h) { + h = (r.y + r.h) - y; + } + } + + + + void Rotate(float a, idRectangle &out) { + idVec3 p1, p2, p3, p4, p5; + float c, s; + idVec3 center; + center.Set((x + w) / 2.0, (y + h) / 2.0, 0); + p1.Set(x, y, 0); + p2.Set(Right(), y, 0); + p4.Set(x, Bottom(), 0); + if (a) { + s = sin( DEG2RAD( a ) ); + c = cos( DEG2RAD( a ) ); + } + else { + s = c = 0; + } + RotateVector(p1, center, a, c, s); + RotateVector(p2, center, a, c, s); + RotateVector(p4, center, a, c, s); + out.x = p1.x; + out.y = p1.y; + out.w = (p2 - p1).Length(); + out.h = (p4 - p1).Length(); + } + + idRectangle & operator+=( const idRectangle &a ); + idRectangle & operator-=( const idRectangle &a ); + idRectangle & operator/=( const idRectangle &a ); + idRectangle & operator/=( const float a ); + idRectangle & operator*=( const float a ); + idRectangle & operator=( const idVec4 v ); + int operator==(const idRectangle &a) const; + float & operator[]( const int index ); + char * String() const; + const idVec4& ToVec4() const; + +}; + +ID_INLINE const idVec4 &idRectangle::ToVec4() const { + return *reinterpret_cast(&x); +} + + +ID_INLINE idRectangle &idRectangle::operator+=( const idRectangle &a ) { + x += a.x; + y += a.y; + w += a.w; + h += a.h; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator/=( const idRectangle &a ) { + x /= a.x; + y /= a.y; + w /= a.w; + h /= a.h; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + w *= inva; + h *= inva; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator-=( const idRectangle &a ) { + x -= a.x; + y -= a.y; + w -= a.w; + h -= a.h; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator*=( const float a ) { + x *= a; + y *= a; + w *= a; + h *= a; + + return *this; +} + + +ID_INLINE idRectangle &idRectangle::operator=( const idVec4 v ) { + x = v.x; + y = v.y; + w = v.z; + h = v.w; + return *this; +} + +ID_INLINE int idRectangle::operator==( const idRectangle &a ) const { + return (x == a.x && y == a.y && w == a.w && a.h); +} + +ID_INLINE float& idRectangle::operator[]( int index ) { + return ( &x )[ index ]; +} + +class idRegion { +public: + idRegion() { }; + + void Empty() { + rects.Clear(); + } + + bool Contains(float xt, float yt) { + int c = rects.Num(); + for (int i = 0; i < c; i++) { + if (rects[i].Contains(xt, yt)) { + return true; + } + } + return false; + } + + void AddRect(float x, float y, float w, float h) { + rects.Append(idRectangle(x, y, w, h)); + } + + int GetRectCount() { + return rects.Num(); + } + + idRectangle *GetRect(int index) { + if (index >= 0 && index < rects.Num()) { + return &rects[index]; + } + return NULL; + } + +protected: + + idList rects; +}; + + +#endif diff --git a/neo/ui/RegExp.cpp b/neo/ui/RegExp.cpp new file mode 100644 index 00000000..18361595 --- /dev/null +++ b/neo/ui/RegExp.cpp @@ -0,0 +1,402 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "RegExp.h" +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" + +int idRegister::REGCOUNT[NUMTYPES] = {4, 1, 1, 1, 0, 2, 3, 4}; + +/* +==================== +idRegister::SetToRegs +==================== +*/ +void idRegister::SetToRegs( float *registers ) { + int i; + idVec4 v; + idVec2 v2; + idVec3 v3; + idRectangle rect; + + if ( !enabled || var == NULL || ( var && ( var->GetDict() || !var->GetEval() ) ) ) { + return; + } + + switch( type ) { + case VEC4: { + v = *static_cast(var); + break; + } + case RECTANGLE: { + rect = *static_cast(var); + v = rect.ToVec4(); + break; + } + case VEC2: { + v2 = *static_cast(var); + v[0] = v2[0]; + v[1] = v2[1]; + break; + } + case VEC3: { + v3 = *static_cast(var); + v[0] = v3[0]; + v[1] = v3[1]; + v[2] = v3[2]; + break; + } + case FLOAT: { + v[0] = *static_cast(var); + break; + } + case INT: { + v[0] = *static_cast(var); + break; + } + case BOOL: { + v[0] = *static_cast(var); + break; + } + default: { + common->FatalError( "idRegister::SetToRegs: bad reg type" ); + break; + } + } + for ( i = 0; i < regCount; i++ ) { + registers[ regs[ i ] ] = v[i]; + } +} + +/* +================= +idRegister::GetFromRegs +================= +*/ +void idRegister::GetFromRegs( float *registers ) { + idVec4 v; + idRectangle rect; + + if (!enabled || var == NULL || (var && (var->GetDict() || !var->GetEval()))) { + return; + } + + for ( int i = 0; i < regCount; i++ ) { + v[i] = registers[regs[i]]; + } + + switch( type ) { + case VEC4: { + *dynamic_cast(var) = v; + break; + } + case RECTANGLE: { + rect.x = v.x; + rect.y = v.y; + rect.w = v.z; + rect.h = v.w; + *static_cast(var) = rect; + break; + } + case VEC2: { + *static_cast(var) = v.ToVec2(); + break; + } + case VEC3: { + *static_cast(var) = v.ToVec3(); + break; + } + case FLOAT: { + *static_cast(var) = v[0]; + break; + } + case INT: { + *static_cast(var) = v[0]; + break; + } + case BOOL: { + *static_cast(var) = ( v[0] != 0.0f ); + break; + } + default: { + common->FatalError( "idRegister::GetFromRegs: bad reg type" ); + break; + } + } +} + +/* +================= +idRegister::ReadFromDemoFile +================= +*/ +void idRegister::ReadFromDemoFile(idDemoFile *f) { + f->ReadBool( enabled ); + f->ReadShort( type ); + f->ReadInt( regCount ); + for ( int i = 0; i < 4; i++ ) + f->ReadUnsignedShort( regs[i] ); + name = f->ReadHashString(); +} + +/* +================= +idRegister::WriteToDemoFile +================= +*/ +void idRegister::WriteToDemoFile( idDemoFile *f ) { + f->WriteBool( enabled ); + f->WriteShort( type ); + f->WriteInt( regCount ); + for (int i = 0; i < 4; i++) + f->WriteUnsignedShort( regs[i] ); + f->WriteHashString( name ); +} + +/* +================= +idRegister::WriteToSaveGame +================= +*/ +void idRegister::WriteToSaveGame( idFile *savefile ) { + int len; + + savefile->Write( &enabled, sizeof( enabled ) ); + savefile->Write( &type, sizeof( type ) ); + savefile->Write( ®Count, sizeof( regCount ) ); + savefile->Write( ®s[0], sizeof( regs ) ); + + len = name.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( name.c_str(), len ); + + var->WriteToSaveGame( savefile ); +} + +/* +================ +idRegister::ReadFromSaveGame +================ +*/ +void idRegister::ReadFromSaveGame( idFile *savefile ) { + int len; + + savefile->Read( &enabled, sizeof( enabled ) ); + savefile->Read( &type, sizeof( type ) ); + savefile->Read( ®Count, sizeof( regCount ) ); + savefile->Read( ®s[0], sizeof( regs ) ); + + savefile->Read( &len, sizeof( len ) ); + name.Fill( ' ', len ); + savefile->Read( &name[0], len ); + + var->ReadFromSaveGame( savefile ); +} + +/* +==================== +idRegisterList::AddReg +==================== +*/ +void idRegisterList::AddReg( const char *name, int type, idVec4 data, idWindow *win, idWinVar *var ) { + if ( FindReg( name ) == NULL ) { + assert( type >= 0 && type < idRegister::NUMTYPES ); + int numRegs = idRegister::REGCOUNT[type]; + idRegister *reg = new (TAG_OLD_UI) idRegister( name, type ); + reg->var = var; + for ( int i = 0; i < numRegs; i++ ) { + reg->regs[i] = win->ExpressionConstant(data[i]); + } + int hash = regHash.GenerateKey( name, false ); + regHash.Add( hash, regs.Append( reg ) ); + } +} + +/* +==================== +idRegisterList::AddReg +==================== +*/ +void idRegisterList::AddReg( const char *name, int type, idTokenParser *src, idWindow *win, idWinVar *var ) { + idRegister* reg; + + reg = FindReg( name ); + + if ( reg == NULL ) { + assert(type >= 0 && type < idRegister::NUMTYPES); + int numRegs = idRegister::REGCOUNT[type]; + reg = new (TAG_OLD_UI) idRegister( name, type ); + reg->var = var; + if ( type == idRegister::STRING ) { + idToken tok; + if ( src->ReadToken( &tok ) ) { + tok = idLocalization::GetString( tok ); + var->Init( tok, win ); + } + } else { + for ( int i = 0; i < numRegs; i++ ) { + reg->regs[i] = win->ParseExpression(src, NULL); + if ( i < numRegs-1 ) { + src->ExpectTokenString(","); + } + } + } + int hash = regHash.GenerateKey( name, false ); + regHash.Add( hash, regs.Append( reg ) ); + } else { + int numRegs = idRegister::REGCOUNT[type]; + reg->var = var; + if ( type == idRegister::STRING ) { + idToken tok; + if ( src->ReadToken( &tok ) ) { + var->Init( tok, win ); + } + } else { + for ( int i = 0; i < numRegs; i++ ) { + reg->regs[i] = win->ParseExpression( src, NULL ); + if ( i < numRegs-1 ) { + src->ExpectTokenString(","); + } + } + } + } +} + +/* +==================== +idRegisterList::GetFromRegs +==================== +*/ +void idRegisterList::GetFromRegs(float *registers) { + for ( int i = 0; i < regs.Num(); i++ ) { + regs[i]->GetFromRegs( registers ); + } +} + +/* +==================== +idRegisterList::SetToRegs +==================== +*/ + +void idRegisterList::SetToRegs( float *registers ) { + int i; + for ( i = 0; i < regs.Num(); i++ ) { + regs[i]->SetToRegs( registers ); + } +} + +/* +==================== +idRegisterList::FindReg +==================== +*/ +idRegister *idRegisterList::FindReg( const char *name ) { + int hash = regHash.GenerateKey( name, false ); + for ( int i = regHash.First( hash ); i != -1; i = regHash.Next( i ) ) { + if ( regs[i]->name.Icmp( name ) == 0 ) { + return regs[i]; + } + } + return NULL; +} + +/* +==================== +idRegisterList::Reset +==================== +*/ +void idRegisterList::Reset() { + regs.DeleteContents( true ); + regHash.Clear(); +} + +/* +==================== +idRegisterList::ReadFromSaveGame +==================== +*/ +void idRegisterList::ReadFromDemoFile(idDemoFile *f) { + int c; + + f->ReadInt( c ); + regs.DeleteContents( true ); + for ( int i = 0; i < c; i++ ) { + idRegister *reg = new (TAG_OLD_UI) idRegister; + reg->ReadFromDemoFile( f ); + regs.Append( reg ); + } +} + +/* +==================== +idRegisterList::ReadFromSaveGame +==================== +*/ +void idRegisterList::WriteToDemoFile(idDemoFile *f) { + int c = regs.Num(); + + f->WriteInt( c ); + for ( int i = 0 ; i < c; i++ ) { + regs[i]->WriteToDemoFile(f); + } +} + +/* +===================== +idRegisterList::WriteToSaveGame +===================== +*/ +void idRegisterList::WriteToSaveGame( idFile *savefile ) { + int i, num; + + num = regs.Num(); + savefile->Write( &num, sizeof( num ) ); + + for ( i = 0; i < num; i++ ) { + regs[i]->WriteToSaveGame( savefile ); + } +} + +/* +==================== +idRegisterList::ReadFromSaveGame +==================== +*/ +void idRegisterList::ReadFromSaveGame( idFile *savefile ) { + int i, num; + + savefile->Read( &num, sizeof( num ) ); + for ( i = 0; i < num; i++ ) { + regs[i]->ReadFromSaveGame( savefile ); + } +} diff --git a/neo/ui/RegExp.h b/neo/ui/RegExp.h new file mode 100644 index 00000000..454535f8 --- /dev/null +++ b/neo/ui/RegExp.h @@ -0,0 +1,112 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __REGEXP_H__ +#define __REGEXP_H__ + +class idTokenParser; +class idWindow; +class idWinVar; + +class idRegister { +public: + idRegister(); + idRegister( const char *p, int t ); + + enum REGTYPE { VEC4 = 0, FLOAT, BOOL, INT, STRING, VEC2, VEC3, RECTANGLE, NUMTYPES } ; + static int REGCOUNT[NUMTYPES]; + + bool enabled; + short type; + idStr name; + int regCount; + unsigned short regs[4]; + idWinVar * var; + + void SetToRegs( float *registers ); + void GetFromRegs( float *registers ); + void CopyRegs( idRegister *src ); + void Enable( bool b ) { enabled = b; } + void ReadFromDemoFile( idDemoFile *f ); + void WriteToDemoFile( idDemoFile *f ); + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); +}; + +ID_INLINE idRegister::idRegister() { +} + +ID_INLINE idRegister::idRegister( const char *p, int t ) { + name = p; + type = t; + assert( t >= 0 && t < NUMTYPES ); + regCount = REGCOUNT[t]; + enabled = ( type == STRING ) ? false : true; + var = NULL; +}; + +ID_INLINE void idRegister::CopyRegs( idRegister *src ) { + regs[0] = src->regs[0]; + regs[1] = src->regs[1]; + regs[2] = src->regs[2]; + regs[3] = src->regs[3]; +} + +class idRegisterList { +public: + + idRegisterList(); + ~idRegisterList(); + + void AddReg( const char *name, int type, idTokenParser *src, idWindow *win, idWinVar *var ); + void AddReg( const char *name, int type, idVec4 data, idWindow *win, idWinVar *var ); + + idRegister * FindReg( const char *name ); + void SetToRegs( float *registers ); + void GetFromRegs( float *registers ); + void Reset(); + void ReadFromDemoFile( idDemoFile *f ); + void WriteToDemoFile( idDemoFile *f ); + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); + +private: + idList regs; + idHashIndex regHash; +}; + +ID_INLINE idRegisterList::idRegisterList() { + regs.SetGranularity( 4 ); + regHash.SetGranularity( 4 ); + regHash.Clear( 32, 4 ); +} + +ID_INLINE idRegisterList::~idRegisterList() { +} + +#endif /* !__REGEXP_H__ */ diff --git a/neo/ui/RegExp_old.h b/neo/ui/RegExp_old.h new file mode 100644 index 00000000..7cbfd4cf --- /dev/null +++ b/neo/ui/RegExp_old.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef REGEXP_H_ +#define REGEXP_H_ + +class idWindow; + +class idRegister { +public: + idRegister() {}; + idRegister(const char *p, int t) { + name = p; + type = t; + assert(t >= 0 && t < NUMTYPES); + regCount = REGCOUNT[t]; + enabled = (type == STRING) ? false : true; + }; + bool enabled; + int type; + int regCount; + enum REGTYPE { VEC4 = 0, FLOAT, BOOL, INT, STRING, VEC2, VEC3, NUMTYPES } ; + static int REGCOUNT[NUMTYPES]; + idStr name; + int regs[4]; + void SetToRegs(float *registers, idTypedDict *state); + void SetToRegList(idList *registers, idTypedDict *state); + void GetFromRegs(float *registers, idTypedDict *state); + void CopyRegs(idRegister *src) { + regs[0] = src->regs[0]; + regs[1] = src->regs[1]; + regs[2] = src->regs[2]; + regs[3] = src->regs[3]; + } + void Enable(bool b) { + enabled = b; + } + void ReadFromDemoFile(idDemoFile *f); + void WriteToDemoFile(idDemoFile *f); + +}; + +class idRegisterList { + idList regs; +public: + + // + void RemoveReg ( const char* name ); + // + + void AddReg(const char *name, int type, idTokenParser *src, idWindow *win); + void AddReg(const char *name, int type, idVec4 data, idWindow *win); + idRegister *FindReg(const char *name); + int FindRegIndex ( const char* name ); + void SetToRegs(float *registers, idTypedDict *state); + void GetFromRegs(float *registers, idTypedDict *state); + void Reset(); + void ReadFromDemoFile(idDemoFile *f); + void WriteToDemoFile(idDemoFile *f); + +}; + +#endif \ No newline at end of file diff --git a/neo/ui/RenderWindow.cpp b/neo/ui/RenderWindow.cpp new file mode 100644 index 00000000..d0544dd8 --- /dev/null +++ b/neo/ui/RenderWindow.cpp @@ -0,0 +1,209 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "RenderWindow.h" + +// NO LONGER SUPPORTED! +// +// D3 could render a 3D model in a subrect of a full screen +// GUI for the main menus, but we have cut that ability so +// we don't need to deal with offset viewports on all platforms. + +idRenderWindow::idRenderWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idRenderWindow::~idRenderWindow() { + renderSystem->FreeRenderWorld( world ); +} + +void idRenderWindow::CommonInit() { + world = renderSystem->AllocRenderWorld(); + needsRender = true; + lightOrigin = idVec4(-128.0f, 0.0f, 0.0f, 1.0f); + lightColor = idVec4(1.0f, 1.0f, 1.0f, 1.0f); + modelOrigin.Zero(); + viewOffset = idVec4(-128.0f, 0.0f, 0.0f, 1.0f); + modelAnim = NULL; + animLength = 0; + animEndTime = -1; + modelDef = -1; + updateAnimation = true; +} + + +void idRenderWindow::BuildAnimation(int time) { + + if (!updateAnimation) { + return; + } + + if (animName.Length() && animClass.Length()) { + worldEntity.numJoints = worldEntity.hModel->NumJoints(); + worldEntity.joints = ( idJointMat * )Mem_Alloc16( SIMD_ROUND_JOINTS( worldEntity.numJoints ) * sizeof( *worldEntity.joints ), TAG_JOINTMAT ); + modelAnim = gameEdit->ANIM_GetAnimFromEntityDef( animClass, animName ); + if ( modelAnim ) { + animLength = gameEdit->ANIM_GetLength( modelAnim ); + animEndTime = time + animLength; + } + } + updateAnimation = false; + +} + +void idRenderWindow::PreRender() { + if (needsRender) { + world->InitFromMap( NULL ); + idDict spawnArgs; + spawnArgs.Set("classname", "light"); + spawnArgs.Set("name", "light_1"); + spawnArgs.Set("origin", lightOrigin.ToVec3().ToString()); + spawnArgs.Set("_color", lightColor.ToVec3().ToString()); + gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &rLight ); + lightDef = world->AddLightDef( &rLight ); + if ( !modelName[0] ) { + common->Warning( "Window '%s' in gui '%s': no model set", GetName(), GetGui()->GetSourceFile() ); + } + memset( &worldEntity, 0, sizeof( worldEntity ) ); + spawnArgs.Clear(); + spawnArgs.Set("classname", "func_static"); + spawnArgs.Set("model", modelName); + spawnArgs.Set("origin", modelOrigin.c_str()); + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &worldEntity ); + if ( worldEntity.hModel ) { + idVec3 v = modelRotate.ToVec3(); + worldEntity.axis = v.ToMat3(); + worldEntity.shaderParms[0] = 1; + worldEntity.shaderParms[1] = 1; + worldEntity.shaderParms[2] = 1; + worldEntity.shaderParms[3] = 1; + modelDef = world->AddEntityDef( &worldEntity ); + } + needsRender = false; + } +} + +void idRenderWindow::Render( int time ) { + rLight.origin = lightOrigin.ToVec3(); + rLight.shaderParms[SHADERPARM_RED] = lightColor.x(); + rLight.shaderParms[SHADERPARM_GREEN] = lightColor.y(); + rLight.shaderParms[SHADERPARM_BLUE] = lightColor.z(); + world->UpdateLightDef(lightDef, &rLight); + if ( worldEntity.hModel ) { + if (updateAnimation) { + BuildAnimation(time); + } + if (modelAnim) { + if (time > animEndTime) { + animEndTime = time + animLength; + } + gameEdit->ANIM_CreateAnimFrame(worldEntity.hModel, modelAnim, worldEntity.numJoints, worldEntity.joints, animLength - (animEndTime - time), vec3_origin, false ); + } + worldEntity.axis = idAngles(modelRotate.x(), modelRotate.y(), modelRotate.z()).ToMat3(); + world->UpdateEntityDef(modelDef, &worldEntity); + } +} + + + + +void idRenderWindow::Draw(int time, float x, float y) { + PreRender(); + Render(time); + + memset( &refdef, 0, sizeof( refdef ) ); + refdef.vieworg = viewOffset.ToVec3();; + //refdef.vieworg.Set(-128, 0, 0); + + refdef.viewaxis.Identity(); + refdef.shaderParms[0] = 1; + refdef.shaderParms[1] = 1; + refdef.shaderParms[2] = 1; + refdef.shaderParms[3] = 1; + + refdef.fov_x = 90; + refdef.fov_y = 2 * atan((float)drawRect.h / drawRect.w) * idMath::M_RAD2DEG; + + refdef.time[0] = time; + refdef.time[1] = time; + world->RenderScene(&refdef); +} + +void idRenderWindow::PostParse() { + idWindow::PostParse(); +} + +// +// +idWinVar *idRenderWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner ) { +// + if (idStr::Icmp(_name, "model") == 0) { + return &modelName; + } + if (idStr::Icmp(_name, "anim") == 0) { + return &animName; + } + if (idStr::Icmp(_name, "lightOrigin") == 0) { + return &lightOrigin; + } + if (idStr::Icmp(_name, "lightColor") == 0) { + return &lightColor; + } + if (idStr::Icmp(_name, "modelOrigin") == 0) { + return &modelOrigin; + } + if (idStr::Icmp(_name, "modelRotate") == 0) { + return &modelRotate; + } + if (idStr::Icmp(_name, "viewOffset") == 0) { + return &viewOffset; + } + if (idStr::Icmp(_name, "needsRender") == 0) { + return &needsRender; + } + +// +// + return idWindow::GetWinVarByName(_name, fixup, owner); +// +} + +bool idRenderWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if (idStr::Icmp(_name, "animClass") == 0) { + ParseString(src, animClass); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} diff --git a/neo/ui/RenderWindow.h b/neo/ui/RenderWindow.h new file mode 100644 index 00000000..3e2df49b --- /dev/null +++ b/neo/ui/RenderWindow.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ +#ifndef __RENDERWINDOW_H +#define __RENDERWINDOW_H + +class idUserInterfaceLocal; +class idRenderWindow : public idWindow { +public: + idRenderWindow(idUserInterfaceLocal *gui); + virtual ~idRenderWindow(); + + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual size_t Allocated(){return idWindow::Allocated();}; +// +// + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); +// + +private: + void CommonInit(); + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void Render(int time); + void PreRender(); + void BuildAnimation(int time); + renderView_t refdef; + idRenderWorld *world; + renderEntity_t worldEntity; + renderLight_t rLight; + const idMD5Anim *modelAnim; + + qhandle_t worldModelDef; + qhandle_t lightDef; + qhandle_t modelDef; + idWinStr modelName; + idWinStr animName; + idStr animClass; + idWinVec4 lightOrigin; + idWinVec4 lightColor; + idWinVec4 modelOrigin; + idWinVec4 modelRotate; + idWinVec4 viewOffset; + idWinBool needsRender; + int animLength; + int animEndTime; + bool updateAnimation; +}; + +#endif // __RENDERWINDOW_H diff --git a/neo/ui/SimpleWindow.cpp b/neo/ui/SimpleWindow.cpp new file mode 100644 index 00000000..fc18cf93 --- /dev/null +++ b/neo/ui/SimpleWindow.cpp @@ -0,0 +1,443 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SimpleWindow.h" + + +idSimpleWindow::idSimpleWindow(idWindow *win) { + gui = win->GetGui(); + drawRect = win->drawRect; + clientRect = win->clientRect; + textRect = win->textRect; + origin = win->origin; + font = win->font; + name = win->name; + matScalex = win->matScalex; + matScaley = win->matScaley; + borderSize = win->borderSize; + textAlign = win->textAlign; + textAlignx = win->textAlignx; + textAligny = win->textAligny; + background = win->background; + flags = win->flags; + textShadow = win->textShadow; + + visible = win->visible; + text = win->text; + rect = win->rect; + backColor = win->backColor; + matColor = win->matColor; + foreColor = win->foreColor; + borderColor = win->borderColor; + textScale = win->textScale; + rotate = win->rotate; + shear = win->shear; + backGroundName = win->backGroundName; + if (backGroundName.Length()) { + background = declManager->FindMaterial(backGroundName); + background->SetSort( SS_GUI ); + } + backGroundName.SetMaterialPtr(&background); + +// +// added parent + mParent = win->GetParent(); +// + + hideCursor = win->hideCursor; + + idWindow *parent = win->GetParent(); + if (parent) { + if (text.NeedsUpdate()) { + parent->AddUpdateVar(&text); + } + if (visible.NeedsUpdate()) { + parent->AddUpdateVar(&visible); + } + if (rect.NeedsUpdate()) { + parent->AddUpdateVar(&rect); + } + if (backColor.NeedsUpdate()) { + parent->AddUpdateVar(&backColor); + } + if (matColor.NeedsUpdate()) { + parent->AddUpdateVar(&matColor); + } + if (foreColor.NeedsUpdate()) { + parent->AddUpdateVar(&foreColor); + } + if (borderColor.NeedsUpdate()) { + parent->AddUpdateVar(&borderColor); + } + if (textScale.NeedsUpdate()) { + parent->AddUpdateVar(&textScale); + } + if (rotate.NeedsUpdate()) { + parent->AddUpdateVar(&rotate); + } + if (shear.NeedsUpdate()) { + parent->AddUpdateVar(&shear); + } + if (backGroundName.NeedsUpdate()) { + parent->AddUpdateVar(&backGroundName); + } + } +} + +idSimpleWindow::~idSimpleWindow() { + +} + +void idSimpleWindow::StateChanged( bool redraw ) { +} + +void idSimpleWindow::SetupTransforms(float x, float y) { + static idMat3 trans; + static idVec3 org; + + trans.Identity(); + org.Set( origin.x + x, origin.y + y, 0 ); + if ( rotate ) { + static idRotation rot; + static idVec3 vec( 0, 0, 1 ); + rot.Set( org, vec, rotate ); + trans = rot.ToMat3(); + } + + static idMat3 smat; + smat.Identity(); + if (shear.x() || shear.y()) { + smat[0][1] = shear.x(); + smat[1][0] = shear.y(); + trans *= smat; + } + + if ( !trans.IsIdentity() ) { + dc->SetTransformInfo( org, trans ); + } +} + +void idSimpleWindow::DrawBackground(const idRectangle &drawRect) { + if (backColor.w() > 0) { + dc->DrawFilledRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, backColor); + } + + if (background) { + if (matColor.w() > 0) { + float scalex, scaley; + if ( flags & WIN_NATURALMAT ) { + scalex = drawRect.w / background->GetImageWidth(); + scaley = drawRect.h / background->GetImageHeight(); + } else { + scalex = matScalex; + scaley = matScaley; + } + dc->DrawMaterial(drawRect.x, drawRect.y, drawRect.w, drawRect.h, background, matColor, scalex, scaley); + } + } +} + +void idSimpleWindow::DrawBorderAndCaption(const idRectangle &drawRect) { + if (flags & WIN_BORDER) { + if (borderSize) { + dc->DrawRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, borderSize, borderColor); + } + } +} + +void idSimpleWindow::CalcClientRect(float xofs, float yofs) { + + drawRect = rect; + + if ( flags & WIN_INVERTRECT ) { + drawRect.x = rect.x() - rect.w(); + drawRect.y = rect.y() - rect.h(); + } + + drawRect.x += xofs; + drawRect.y += yofs; + + clientRect = drawRect; + if (rect.h() > 0.0 && rect.w() > 0.0) { + + if (flags & WIN_BORDER && borderSize != 0.0) { + clientRect.x += borderSize; + clientRect.y += borderSize; + clientRect.w -= borderSize; + clientRect.h -= borderSize; + } + + textRect = clientRect; + textRect.x += 2.0; + textRect.w -= 2.0; + textRect.y += 2.0; + textRect.h -= 2.0; + textRect.x += textAlignx; + textRect.y += textAligny; + + } + origin.Set( rect.x() + ( rect.w() / 2 ), rect.y() + ( rect.h() / 2 ) ); + +} + + +void idSimpleWindow::Redraw(float x, float y) { + + if (!visible) { + return; + } + + CalcClientRect(0, 0); + dc->SetFont( font ); + drawRect.Offset(x, y); + clientRect.Offset(x, y); + textRect.Offset(x, y); + SetupTransforms(x, y); + if ( flags & WIN_NOCLIP ) { + dc->EnableClipping( false ); + } + DrawBackground(drawRect); + DrawBorderAndCaption(drawRect); + if ( textShadow ) { + idStr shadowText = text; + idRectangle shadowRect = textRect; + + shadowText.RemoveColors(); + shadowRect.x += textShadow; + shadowRect.y += textShadow; + + dc->DrawText( shadowText, textScale, textAlign, colorBlack, shadowRect, !( flags & WIN_NOWRAP ), -1 ); + } + dc->DrawText(text, textScale, textAlign, foreColor, textRect, !( flags & WIN_NOWRAP ), -1); + dc->SetTransformInfo(vec3_origin, mat3_identity); + if ( flags & WIN_NOCLIP ) { + dc->EnableClipping( true ); + } + drawRect.Offset(-x, -y); + clientRect.Offset(-x, -y); + textRect.Offset(-x, -y); +} + +int idSimpleWindow::GetWinVarOffset( idWinVar *wv, drawWin_t* owner) { + int ret = -1; + + if ( wv == &rect ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->rect; + } + + if ( wv == &backColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->backColor; + } + + if ( wv == &matColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->matColor; + } + + if ( wv == &foreColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->foreColor; + } + + if ( wv == &borderColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->borderColor; + } + + if ( wv == &textScale ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->textScale; + } + + if ( wv == &rotate ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->rotate; + } + + if ( ret != -1 ) { + owner->simp = this; + } + return ret; +} + +idWinVar *idSimpleWindow::GetWinVarByName(const char *_name) { + idWinVar *retVar = NULL; + if (idStr::Icmp(_name, "background") == 0) { + retVar = &backGroundName; + } + if (idStr::Icmp(_name, "visible") == 0) { + retVar = &visible; + } + if (idStr::Icmp(_name, "rect") == 0) { + retVar = ▭ + } + if (idStr::Icmp(_name, "backColor") == 0) { + retVar = &backColor; + } + if (idStr::Icmp(_name, "matColor") == 0) { + retVar = &matColor; + } + if (idStr::Icmp(_name, "foreColor") == 0) { + retVar = &foreColor; + } + if (idStr::Icmp(_name, "borderColor") == 0) { + retVar = &borderColor; + } + if (idStr::Icmp(_name, "textScale") == 0) { + retVar = &textScale; + } + if (idStr::Icmp(_name, "rotate") == 0) { + retVar = &rotate; + } + if (idStr::Icmp(_name, "shear") == 0) { + retVar = &shear; + } + if (idStr::Icmp(_name, "text") == 0) { + retVar = &text; + } + return retVar; +} + +/* +======================== +idSimpleWindow::WriteToSaveGame +======================== +*/ +void idSimpleWindow::WriteToSaveGame( idFile *savefile ) { + + savefile->Write( &flags, sizeof( flags ) ); + savefile->Write( &drawRect, sizeof( drawRect ) ); + savefile->Write( &clientRect, sizeof( clientRect ) ); + savefile->Write( &textRect, sizeof( textRect ) ); + savefile->Write( &origin, sizeof( origin ) ); + savefile->Write( &matScalex, sizeof( matScalex ) ); + savefile->Write( &matScaley, sizeof( matScaley ) ); + savefile->Write( &borderSize, sizeof( borderSize ) ); + savefile->Write( &textAlign, sizeof( textAlign ) ); + savefile->Write( &textAlignx, sizeof( textAlignx ) ); + savefile->Write( &textAligny, sizeof( textAligny ) ); + savefile->Write( &textShadow, sizeof( textShadow ) ); + savefile->WriteString( font->GetName() ); + + text.WriteToSaveGame( savefile ); + visible.WriteToSaveGame( savefile ); + rect.WriteToSaveGame( savefile ); + backColor.WriteToSaveGame( savefile ); + matColor.WriteToSaveGame( savefile ); + foreColor.WriteToSaveGame( savefile ); + borderColor.WriteToSaveGame( savefile ); + textScale.WriteToSaveGame( savefile ); + rotate.WriteToSaveGame( savefile ); + shear.WriteToSaveGame( savefile ); + backGroundName.WriteToSaveGame( savefile ); + + int stringLen; + + if ( background ) { + stringLen = strlen( background->GetName() ); + savefile->Write( &stringLen, sizeof( stringLen ) ); + savefile->Write( background->GetName(), stringLen ); + } else { + stringLen = 0; + savefile->Write( &stringLen, sizeof( stringLen ) ); + } + +} + +/* +======================== +idSimpleWindow::ReadFromSaveGame +======================== +*/ +void idSimpleWindow::ReadFromSaveGame( idFile *savefile ) { + + savefile->Read( &flags, sizeof( flags ) ); + savefile->Read( &drawRect, sizeof( drawRect ) ); + savefile->Read( &clientRect, sizeof( clientRect ) ); + savefile->Read( &textRect, sizeof( textRect ) ); + savefile->Read( &origin, sizeof( origin ) ); +/* if ( savefile->GetFileVersion() < BUILD_NUMBER_8TH_ANNIVERSARY_1 ) { + int fontNum; + savefile->Read( &fontNum, sizeof( fontNum ) ); + font = renderSystem->RegisterFont( "" ); + } */ + savefile->Read( &matScalex, sizeof( matScalex ) ); + savefile->Read( &matScaley, sizeof( matScaley ) ); + savefile->Read( &borderSize, sizeof( borderSize ) ); + savefile->Read( &textAlign, sizeof( textAlign ) ); + savefile->Read( &textAlignx, sizeof( textAlignx ) ); + savefile->Read( &textAligny, sizeof( textAligny ) ); + savefile->Read( &textShadow, sizeof( textShadow ) ); +// if ( savefile->GetFileVersion() >= BUILD_NUMBER_8TH_ANNIVERSARY_1 ) { + idStr fontName; + savefile->ReadString( fontName ); + font = renderSystem->RegisterFont( fontName ); +// } + + text.ReadFromSaveGame( savefile ); + visible.ReadFromSaveGame( savefile ); + rect.ReadFromSaveGame( savefile ); + backColor.ReadFromSaveGame( savefile ); + matColor.ReadFromSaveGame( savefile ); + foreColor.ReadFromSaveGame( savefile ); + borderColor.ReadFromSaveGame( savefile ); + textScale.ReadFromSaveGame( savefile ); + rotate.ReadFromSaveGame( savefile ); + shear.ReadFromSaveGame( savefile ); + backGroundName.ReadFromSaveGame( savefile ); + + int stringLen; + + savefile->Read( &stringLen, sizeof( stringLen ) ); + if ( stringLen > 0 ) { + idStr backName; + + backName.Fill( ' ', stringLen ); + savefile->Read( &(backName)[0], stringLen ); + + background = declManager->FindMaterial( backName ); + background->SetSort( SS_GUI ); + } else { + background = NULL; + } + +} + + +/* +=============================== +*/ + +size_t idSimpleWindow::Size() { + size_t sz = sizeof(*this); + sz += name.Size(); + sz += text.Size(); + sz += backGroundName.Size(); + return sz; +} diff --git a/neo/ui/SimpleWindow.h b/neo/ui/SimpleWindow.h new file mode 100644 index 00000000..db3077d0 --- /dev/null +++ b/neo/ui/SimpleWindow.h @@ -0,0 +1,100 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SIMPLEWIN_H__ +#define __SIMPLEWIN_H__ + +class idUserInterfaceLocal; +class idDeviceContext; +class idSimpleWindow; + +typedef struct { + idWindow *win; + idSimpleWindow *simp; +} drawWin_t; + +class idSimpleWindow { + friend class idWindow; +public: + idSimpleWindow(idWindow* win); + virtual ~idSimpleWindow(); + void Redraw(float x, float y); + void StateChanged( bool redraw ); + + idStr name; + + idWinVar * GetWinVarByName(const char *_name); + int GetWinVarOffset( idWinVar *wv, drawWin_t* owner); + size_t Size(); + + idWindow* GetParent () { return mParent; } + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + +protected: + void CalcClientRect(float xofs, float yofs); + void SetupTransforms(float x, float y); + void DrawBackground(const idRectangle &drawRect); + void DrawBorderAndCaption(const idRectangle &drawRect); + + idUserInterfaceLocal *gui; + int flags; + idRectangle drawRect; // overall rect + idRectangle clientRect; // client area + idRectangle textRect; + idVec2 origin; + class idFont * font; + float matScalex; + float matScaley; + float borderSize; + int textAlign; + float textAlignx; + float textAligny; + int textShadow; + + idWinStr text; + idWinBool visible; + idWinRectangle rect; // overall rect + idWinVec4 backColor; + idWinVec4 matColor; + idWinVec4 foreColor; + idWinVec4 borderColor; + idWinFloat textScale; + idWinFloat rotate; + idWinVec2 shear; + idWinBackground backGroundName; + + const idMaterial* background; + + idWindow * mParent; + + idWinBool hideCursor; +}; + +#endif /* !__SIMPLEWIN_H__ */ diff --git a/neo/ui/SliderWindow.cpp b/neo/ui/SliderWindow.cpp new file mode 100644 index 00000000..b77cd99b --- /dev/null +++ b/neo/ui/SliderWindow.cpp @@ -0,0 +1,410 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SliderWindow.h" + +/* +============ +idSliderWindow::CommonInit +============ +*/ +void idSliderWindow::CommonInit() { + value = 0.0; + low = 0.0; + high = 100.0; + stepSize = 1.0; + thumbMat = declManager->FindMaterial("_default"); + buddyWin = NULL; + + cvar = NULL; + cvar_init = false; + liveUpdate = true; + + vertical = false; + scrollbar = false; + + verticalFlip = false; +} + +idSliderWindow::idSliderWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idSliderWindow::~idSliderWindow() { + +} + +bool idSliderWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + if (idStr::Icmp(_name, "stepsize") == 0 || idStr::Icmp(_name, "step") == 0) { + stepSize = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "low") == 0) { + low = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "high") == 0) { + high = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "vertical") == 0) { + vertical = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "verticalflip") == 0) { + verticalFlip = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "scrollbar") == 0) { + scrollbar = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "thumbshader") == 0) { + ParseString(src, thumbShader); + declManager->FindMaterial(thumbShader); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + +idWinVar *idSliderWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + + if (idStr::Icmp(_name, "value") == 0) { + return &value; + } + if (idStr::Icmp(_name, "cvar") == 0) { + return &cvarStr; + } + if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { + return &liveUpdate; + } + if ( idStr::Icmp( _name, "cvarGroup" ) == 0 ) { + return &cvarGroup; + } + + return idWindow::GetWinVarByName(_name, fixup, owner); +} + +const char *idSliderWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + + if (!(event->evType == SE_KEY && event->evValue2)) { + return ""; + } + + int key = event->evValue; + + if ( event->evValue2 && key == K_MOUSE1 ) { + SetCapture(this); + RouteMouseCoords(0.0f, 0.0f); + return ""; + } + + if ( key == K_RIGHTARROW || key == K_KP_6 || ( key == K_MOUSE2 && gui->CursorY() > thumbRect.y ) ) { + value = value + stepSize; + } + + if ( key == K_LEFTARROW || key == K_KP_4 || ( key == K_MOUSE2 && gui->CursorY() < thumbRect.y ) ) { + value = value - stepSize; + } + + if (buddyWin) { + buddyWin->HandleBuddyUpdate(this); + } else { + gui->SetStateFloat( cvarStr, value ); + UpdateCvar( false ); + } + + return ""; +} + + +void idSliderWindow::SetBuddy(idWindow *buddy) { + buddyWin = buddy; +} + +void idSliderWindow::PostParse() { + idWindow::PostParse(); + value = 0.0; + thumbMat = declManager->FindMaterial(thumbShader); + thumbMat->SetSort( SS_GUI ); + thumbWidth = thumbMat->GetImageWidth(); + thumbHeight = thumbMat->GetImageHeight(); + //vertical = state.GetBool("vertical"); + //scrollbar = state.GetBool("scrollbar"); + flags |= (WIN_HOLDCAPTURE | WIN_CANFOCUS); + InitCvar(); +} + +void idSliderWindow::InitWithDefaults(const char *_name, const idRectangle &_rect, const idVec4 &_foreColor, const idVec4 &_matColor, const char *_background, const char *thumbShader, bool _vertical, bool _scrollbar) { + SetInitialState(_name); + rect = _rect; + foreColor = _foreColor; + matColor = _matColor; + thumbMat = declManager->FindMaterial(thumbShader); + thumbMat->SetSort( SS_GUI ); + thumbWidth = thumbMat->GetImageWidth(); + thumbHeight = thumbMat->GetImageHeight(); + background = declManager->FindMaterial(_background); + background->SetSort( SS_GUI ); + vertical = _vertical; + scrollbar = _scrollbar; + flags |= WIN_HOLDCAPTURE; +} + +void idSliderWindow::SetRange(float _low, float _high, float _step) { + low = _low; + high = _high; + stepSize = _step; +} + +void idSliderWindow::SetValue(float _value) { + value = _value; +} + +void idSliderWindow::Draw(int time, float x, float y) { + idVec4 color = foreColor; + + if ( !cvar && !buddyWin ) { + return; + } + + if ( !thumbWidth || !thumbHeight ) { + thumbWidth = thumbMat->GetImageWidth(); + thumbHeight = thumbMat->GetImageHeight(); + } + + UpdateCvar( true ); + if ( value > high ) { + value = high; + } else if ( value < low ) { + value = low; + } + + float range = high - low; + + if ( range <= 0.0f ) { + return; + } + + float thumbPos = (range) ? (value - low) / range : 0.0; + if (vertical) { + if ( verticalFlip ) { + thumbPos = 1.f - thumbPos; + } + thumbPos *= drawRect.h - thumbHeight; + thumbPos += drawRect.y; + thumbRect.y = thumbPos; + thumbRect.x = drawRect.x; + } else { + thumbPos *= drawRect.w - thumbWidth; + thumbPos += drawRect.x; + thumbRect.x = thumbPos; + thumbRect.y = drawRect.y; + } + thumbRect.w = thumbWidth; + thumbRect.h = thumbHeight; + + if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + hover = false; + } + if ( flags & WIN_CAPTURE ) { + color = hoverColor; + hover = true; + } + + dc->DrawMaterial(thumbRect.x, thumbRect.y, thumbRect.w, thumbRect.h, thumbMat, color); + if ( flags & WIN_FOCUS ) { + dc->DrawRect(thumbRect.x+1.0f, thumbRect.y+1.0f, thumbRect.w-2.0f, thumbRect.h-2.0f, 1.0f, color); + } +} + + +void idSliderWindow::DrawBackground(const idRectangle &_drawRect) { + if ( !cvar && !buddyWin ) { + return; + } + + if ( high - low <= 0.0f ) { + return; + } + + idRectangle r = _drawRect; + if (!scrollbar) { + if ( vertical ) { + r.y += thumbHeight / 2.f; + r.h -= thumbHeight; + } else { + r.x += thumbWidth / 2.0; + r.w -= thumbWidth; + } + } + idWindow::DrawBackground(r); +} + +const char *idSliderWindow::RouteMouseCoords(float xd, float yd) { + float pct; + + if (!(flags & WIN_CAPTURE)) { + return ""; + } + + idRectangle r = drawRect; + r.x = actualX; + r.y = actualY; + r.x += thumbWidth / 2.0; + r.w -= thumbWidth; + if (vertical) { + r.y += thumbHeight / 2; + r.h -= thumbHeight; + if (gui->CursorY() >= r.y && gui->CursorY() <= r.Bottom()) { + pct = (gui->CursorY() - r.y) / r.h; + if ( verticalFlip ) { + pct = 1.f - pct; + } + value = low + (high - low) * pct; + } else if (gui->CursorY() < r.y) { + if ( verticalFlip ) { + value = high; + } else { + value = low; + } + } else { + if ( verticalFlip ) { + value = low; + } else { + value = high; + } + } + } else { + r.x += thumbWidth / 2; + r.w -= thumbWidth; + if (gui->CursorX() >= r.x && gui->CursorX() <= r.Right()) { + pct = (gui->CursorX() - r.x) / r.w; + value = low + (high - low) * pct; + } else if (gui->CursorX() < r.x) { + value = low; + } else { + value = high; + } + } + + if (buddyWin) { + buddyWin->HandleBuddyUpdate(this); + } else { + gui->SetStateFloat( cvarStr, value ); + } + UpdateCvar( false ); + + return ""; +} + + +void idSliderWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + if ( activate ) { + UpdateCvar( true, true ); + } +} + +/* +============ +idSliderWindow::InitCvar +============ +*/ +void idSliderWindow::InitCvar( ) { + if ( cvarStr[0] == '\0' ) { + if ( !buddyWin ) { + common->Warning( "idSliderWindow::InitCvar: gui '%s' window '%s' has an empty cvar string", gui->GetSourceFile(), name.c_str() ); + } + cvar_init = true; + cvar = NULL; + return; + } + + cvar = cvarSystem->Find( cvarStr ); + if ( !cvar ) { + common->Warning( "idSliderWindow::InitCvar: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); + cvar_init = true; + return; + } +} + +/* +============ +idSliderWindow::UpdateCvar +============ +*/ +void idSliderWindow::UpdateCvar( bool read, bool force ) { + if ( buddyWin || !cvar ) { + return; + } + if ( force || liveUpdate ) { + value = cvar->GetFloat(); + if ( value != gui->State().GetFloat( cvarStr ) ) { + if ( read ) { + gui->SetStateFloat( cvarStr, value ); + } else { + value = gui->State().GetFloat( cvarStr ); + cvar->SetFloat( value ); + } + } + } +} + +/* +============ +idSliderWindow::RunNamedEvent +============ +*/ +void idSliderWindow::RunNamedEvent( const char* eventName ) { + idStr event, group; + + if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { + event = eventName; + group = event.Mid( 10, event.Length() - 10 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( true, true ); + } + } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { + event = eventName; + group = event.Mid( 11, event.Length() - 11 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( false, true ); + } + } +} + diff --git a/neo/ui/SliderWindow.h b/neo/ui/SliderWindow.h new file mode 100644 index 00000000..f9700fed --- /dev/null +++ b/neo/ui/SliderWindow.h @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __SLIDERWINDOW_H__ +#define __SLIDERWINDOW_H__ + +class idUserInterfaceLocal; + +class idSliderWindow : public idWindow { +public: + idSliderWindow(idUserInterfaceLocal *gui); + virtual ~idSliderWindow(); + + void InitWithDefaults(const char *_name, const idRectangle &rect, const idVec4 &foreColor, const idVec4 &matColor, const char *_background, const char *thumbShader, bool _vertical, bool _scrollbar); + + void SetRange(float _low, float _high, float _step); + float GetLow() { return low; } + float GetHigh() { return high; } + + void SetValue(float _value); + float GetValue() { return value; }; + + virtual size_t Allocated(){return idWindow::Allocated();}; + virtual idWinVar * GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual void DrawBackground(const idRectangle &drawRect); + virtual const char *RouteMouseCoords(float xd, float yd); + virtual void Activate(bool activate, idStr &act); + virtual void SetBuddy(idWindow *buddy); + + void RunNamedEvent( const char* eventName ); + +private: + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void CommonInit(); + void InitCvar(); + // true: read the updated cvar from cvar system + // false: write to the cvar system + // force == true overrides liveUpdate 0 + void UpdateCvar( bool read, bool force = false ); + + idWinFloat value; + float low; + float high; + float thumbWidth; + float thumbHeight; + float stepSize; + float lastValue; + idRectangle thumbRect; + const idMaterial * thumbMat; + bool vertical; + bool verticalFlip; + bool scrollbar; + idWindow * buddyWin; + idStr thumbShader; + + idWinStr cvarStr; + idCVar * cvar; + bool cvar_init; + idWinBool liveUpdate; + idWinStr cvarGroup; +}; + +#endif /* !__SLIDERWINDOW_H__ */ + diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp new file mode 100644 index 00000000..ef641d63 --- /dev/null +++ b/neo/ui/UserInterface.cpp @@ -0,0 +1,698 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "ListGUILocal.h" +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" + +extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces + +idUserInterfaceManagerLocal uiManagerLocal; +idUserInterfaceManager * uiManager = &uiManagerLocal; + +// These used to be in every window, but they all pointed at the same one in idUserInterfaceManagerLocal. +// Made a global so it can be switched out dynamically to test optimized versions. +idDeviceContext *dc; + +idCVar g_useNewGuiCode( "g_useNewGuiCode", "1", CVAR_GAME | CVAR_INTEGER, "use optimized device context code, 2 = toggle on/off every frame" ); + +extern idCVar sys_lang; + + + + +/* +=============================================================================== + + idUserInterfaceManagerLocal + +=============================================================================== +*/ + +void idUserInterfaceManagerLocal::Init() { + screenRect = idRectangle(0, 0, 640, 480); + dcOld.Init(); + dcOptimized.Init(); + + SetDrawingDC(); + +} + +void idUserInterfaceManagerLocal::Shutdown() { + guis.DeleteContents( true ); + demoGuis.DeleteContents( true ); + dcOld.Shutdown(); + dcOptimized.Shutdown(); + mapParser.Clear(); +} + +void idUserInterfaceManagerLocal::SetDrawingDC() { + static int toggle; + + // to make it more obvious that there is a difference between the old and + // new paths, toggle between them every frame if g_useNewGuiCode is set to 2 + toggle++; + + if ( g_useNewGuiCode.GetInteger() == 1 || + ( g_useNewGuiCode.GetInteger() == 2 && ( toggle & 1 ) ) ) { + dc = &dcOptimized; + } else { + dc = &dcOld; + } +} + +void idUserInterfaceManagerLocal::Touch( const char *name ) { + idUserInterface *gui = Alloc(); + gui->InitFromFile( name ); +// delete gui; +} + +void idUserInterfaceManagerLocal::WritePrecacheCommands( idFile *f ) { + + int c = guis.Num(); + for( int i = 0; i < c; i++ ) { + char str[1024]; + sprintf( str, "touchGui %s\n", guis[i]->Name() ); + common->Printf( "%s", str ); + f->Printf( "%s", str ); + } +} + +void idUserInterfaceManagerLocal::SetSize( float width, float height ) { + dc->SetSize( width, height ); +} + +void idUserInterfaceManagerLocal::Preload( const char *mapName ) { + if ( mapName != NULL && mapName[ 0 ] != '\0' ) { + mapParser.LoadFromFile( va( "generated/guis/%s.bgui", mapName ) ); + } +} + +void idUserInterfaceManagerLocal::BeginLevelLoad() { + for ( int i = 0; i < guis.Num(); i++ ) { + guis[ i ]->ClearRefs(); + } + } + +void idUserInterfaceManagerLocal::EndLevelLoad( const char *mapName ) { + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( guis[i]->GetRefs() == 0 ) { + //common->Printf( "purging %s.\n", guis[i]->GetSourceFile() ); + + // use this to make sure no materials still reference this gui + bool remove = true; + for ( int j = 0; j < declManager->GetNumDecls( DECL_MATERIAL ); j++ ) { + const idMaterial *material = static_cast(declManager->DeclByIndex( DECL_MATERIAL, j, false )); + if ( material->GlobalGui() == guis[i] ) { + remove = false; + break; + } + } + if ( remove ) { + delete guis[ i ]; + guis.RemoveIndex( i ); + i--; c--; + } + } + common->UpdateLevelLoadPacifier(); + } + if ( cvarSystem->GetCVarBool( "fs_buildresources" ) && mapName != NULL && mapName[ 0 ] != '\0' ) { + mapParser.WriteToFile( va( "generated/guis/%s.bgui", mapName ) ); + idFile *f = fileSystem->OpenFileRead( va( "generated/guis/%s.bgui", mapName ) ); + delete f; + } + + dcOld.Init(); + dcOptimized.Init(); +} + +void idUserInterfaceManagerLocal::Reload( bool all ) { + ID_TIME_T ts; + + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( !all ) { + fileSystem->ReadFile( guis[i]->GetSourceFile(), NULL, &ts ); + if ( ts <= guis[i]->GetTimeStamp() ) { + continue; + } + } + + guis[i]->InitFromFile( guis[i]->GetSourceFile() ); + common->Printf( "reloading %s.\n", guis[i]->GetSourceFile() ); + } +} + +void idUserInterfaceManagerLocal::ListGuis() const { + int c = guis.Num(); + common->Printf( "\n size refs name\n" ); + size_t total = 0; + int copies = 0; + int unique = 0; + for ( int i = 0; i < c; i++ ) { + idUserInterfaceLocal *gui = guis[i]; + size_t sz = gui->Size(); + bool isUnique = guis[i]->interactive; + if ( isUnique ) { + unique++; + } else { + copies++; + } + common->Printf( "%6.1fk %4i (%s) %s ( %i transitions )\n", sz / 1024.0f, guis[i]->GetRefs(), isUnique ? "unique" : "copy", guis[i]->GetSourceFile(), guis[i]->desktop->NumTransitions() ); + total += sz; + } + common->Printf( "===========\n %i total Guis ( %i copies, %i unique ), %.2f total Mbytes", c, copies, unique, total / ( 1024.0f * 1024.0f ) ); +} + +bool idUserInterfaceManagerLocal::CheckGui( const char *qpath ) const { + idFile *file = fileSystem->OpenFileRead( qpath ); + if ( file ) { + fileSystem->CloseFile( file ); + return true; + } + return false; +} + +idUserInterface *idUserInterfaceManagerLocal::Alloc() const { + return new (TAG_OLD_UI) idUserInterfaceLocal(); +} + +void idUserInterfaceManagerLocal::DeAlloc( idUserInterface *gui ) { + if ( gui ) { + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( guis[i] == gui ) { + delete guis[i]; + guis.RemoveIndex( i ); + return; + } + } + } +} + +idUserInterface *idUserInterfaceManagerLocal::FindGui( const char *qpath, bool autoLoad, bool needUnique, bool forceNOTUnique ) { + int c = guis.Num(); + + for ( int i = 0; i < c; i++ ) { + idUserInterfaceLocal *gui = guis[i]; + if ( gui == NULL ) { + continue; + } + + if ( !idStr::Icmp( gui->GetSourceFile(), qpath ) ) { + if ( !forceNOTUnique && ( needUnique || guis[i]->IsInteractive() ) ) { + break; + } + // Reload the gui if it's been cleared + if ( guis[i]->GetRefs() == 0 ) { + guis[i]->InitFromFile( guis[i]->GetSourceFile() ); + } + guis[i]->AddRef(); + return guis[i]; + } + } + + if ( autoLoad ) { + idUserInterface *gui = Alloc(); + if ( gui->InitFromFile( qpath ) ) { + gui->SetUniqued( forceNOTUnique ? false : needUnique ); + return gui; + } else { + delete gui; + } + } + return NULL; +} + +idUserInterface *idUserInterfaceManagerLocal::FindDemoGui( const char *qpath ) { + int c = demoGuis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( !idStr::Icmp( demoGuis[i]->GetSourceFile(), qpath ) ) { + return demoGuis[i]; + } + } + return NULL; +} + +idListGUI * idUserInterfaceManagerLocal::AllocListGUI() const { + return new (TAG_OLD_UI) idListGUILocal(); +} + +void idUserInterfaceManagerLocal::FreeListGUI( idListGUI *listgui ) { + delete listgui; +} + +/* +=============================================================================== + + idUserInterfaceLocal + +=============================================================================== +*/ + +idUserInterfaceLocal::idUserInterfaceLocal() { + cursorX = cursorY = 0.0; + desktop = NULL; + loading = false; + active = false; + interactive = false; + uniqued = false; + bindHandler = NULL; + //so the reg eval in gui parsing doesn't get bogus values + time = 0; + refs = 1; +} + +idUserInterfaceLocal::~idUserInterfaceLocal() { + delete desktop; + desktop = NULL; +} + +const char *idUserInterfaceLocal::Name() const { + return source; +} + +const char *idUserInterfaceLocal::Comment() const { + if ( desktop ) { + return desktop->GetComment(); + } + return ""; +} + +bool idUserInterfaceLocal::IsInteractive() const { + return interactive; +} + +bool idUserInterfaceLocal::InitFromFile( const char *qpath, bool rebuild, bool cache ) { + + if ( !( qpath && *qpath ) ) { + // FIXME: Memory leak!! + return false; + } + + int sz = sizeof( idWindow ); + sz = sizeof( idSimpleWindow ); + loading = true; + + if ( rebuild ) { + delete desktop; + desktop = new (TAG_OLD_UI) idWindow( this ); + } else if ( desktop == NULL ) { + desktop = new (TAG_OLD_UI) idWindow( this ); + } + + // First try loading the localized version + // Then fall back to the english version + for ( int i = 0; i < 2; i++ ) { + source = qpath; + idStr trySource = qpath; + trySource.ToLower(); + trySource.BackSlashesToSlashes(); + if ( i == 0 ) { + trySource.Replace( "guis/", va( "guis/%s/", sys_lang.GetString() ) ); + } + fileSystem->ReadFile( trySource, NULL, &timeStamp); + if ( timeStamp != FILE_NOT_FOUND_TIMESTAMP ) { + source = trySource; + break; + } + } + state.Set( "text", "Test Text!" ); + + idTokenParser &bsrc = uiManagerLocal.GetBinaryParser(); + if ( !bsrc.IsLoaded() || !bsrc.StartParsing( source ) ) { + idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadFile( source ); + if ( src.IsLoaded() ) { + bsrc.LoadFromParser( src, source ); + ID_TIME_T ts = fileSystem->GetTimestamp( source ); + bsrc.UpdateTimeStamp( ts ); + } + } + if ( bsrc.IsLoaded() && bsrc.StartParsing( source) ) { + idToken token; + while( bsrc.ReadToken( &token ) ) { + if ( idStr::Icmp( token, "windowDef" ) == 0 ) { + if ( desktop->Parse( &bsrc, rebuild ) ) { + desktop->SetFlag( WIN_DESKTOP ); + desktop->FixupParms(); + } + continue; + } + } + state.Set( "name", qpath ); // don't use localized name + bsrc.DoneParsing(); + } else { + desktop->SetFlag( WIN_DESKTOP ); + desktop->name = "Desktop"; + desktop->text = va( "Invalid GUI: %s", qpath ); + desktop->rect = idRectangle( 0.0f, 0.0f, 640.0f, 480.0f ); + desktop->drawRect = desktop->rect; + desktop->foreColor = idVec4( 1.0f, 1.0f, 1.0f, 1.0f ); + desktop->backColor = idVec4( 0.0f, 0.0f, 0.0f, 1.0f ); + desktop->SetupFromState(); + common->Warning( "Couldn't load gui: '%s'", source.c_str() ); + } + interactive = desktop->Interactive(); + if ( uiManagerLocal.guis.Find( this ) == NULL ) { + uiManagerLocal.guis.Append( this ); + } + loading = false; + return true; +} + +const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _time, bool *updateVisuals ) { + + time = _time; + + if ( bindHandler && event->evType == SE_KEY && event->evValue2 == 1 ) { + const char *ret = bindHandler->HandleEvent( event, updateVisuals ); + bindHandler = NULL; + return ret; + } + + if ( event->evType == SE_MOUSE ) { + cursorX += event->evValue; + cursorY += event->evValue2; + + if (cursorX < 0) { + cursorX = 0; + } + if (cursorY < 0) { + cursorY = 0; + } + } + + if ( desktop ) { + return desktop->HandleEvent( event, updateVisuals ); + } + + return ""; +} + +void idUserInterfaceLocal::HandleNamedEvent ( const char* eventName ) { + desktop->RunNamedEvent( eventName ); +} + +void idUserInterfaceLocal::Redraw( int _time, bool hud ) { + if ( r_skipGuiShaders.GetInteger() > 5 ) { + return; + } + if ( !loading && desktop ) { + time = _time; + dc->PushClipRect( uiManagerLocal.screenRect ); + desktop->Redraw( 0, 0, hud ); + dc->PopClipRect(); + } +} + +void idUserInterfaceLocal::DrawCursor() { + if ( !desktop || desktop->GetFlags() & WIN_MENUGUI ) { + dc->DrawCursor(&cursorX, &cursorY, 32.0f ); + } else { + dc->DrawCursor(&cursorX, &cursorY, 56.0f ); + } +} + +const idDict &idUserInterfaceLocal::State() const { + return state; +} + +void idUserInterfaceLocal::DeleteStateVar( const char *varName ) { + state.Delete( varName ); +} + +void idUserInterfaceLocal::SetStateString( const char *varName, const char *value ) { + state.Set( varName, value ); +} + +void idUserInterfaceLocal::SetStateBool( const char *varName, const bool value ) { + state.SetBool( varName, value ); +} + +void idUserInterfaceLocal::SetStateInt( const char *varName, const int value ) { + state.SetInt( varName, value ); +} + +void idUserInterfaceLocal::SetStateFloat( const char *varName, const float value ) { + state.SetFloat( varName, value ); +} + +const char* idUserInterfaceLocal::GetStateString( const char *varName, const char* defaultString ) const { + return state.GetString(varName, defaultString); +} + +bool idUserInterfaceLocal::GetStateBool( const char *varName, const char* defaultString ) const { + return state.GetBool(varName, defaultString); +} + +int idUserInterfaceLocal::GetStateInt( const char *varName, const char* defaultString ) const { + return state.GetInt(varName, defaultString); +} + +float idUserInterfaceLocal::GetStateFloat( const char *varName, const char* defaultString ) const { + return state.GetFloat(varName, defaultString); +} + +void idUserInterfaceLocal::StateChanged( int _time, bool redraw ) { + time = _time; + if (desktop) { + desktop->StateChanged( redraw ); + } + if ( state.GetBool( "noninteractive" ) ) { + interactive = false; + } + else { + if (desktop) { + interactive = desktop->Interactive(); + } else { + interactive = false; + } + } +} + +const char *idUserInterfaceLocal::Activate(bool activate, int _time) { + time = _time; + active = activate; + if ( desktop ) { + activateStr = ""; + desktop->Activate( activate, activateStr ); + return activateStr; + } + return ""; +} + +void idUserInterfaceLocal::Trigger(int _time) { + time = _time; + if ( desktop ) { + desktop->Trigger(); + } +} + +void idUserInterfaceLocal::ReadFromDemoFile( class idDemoFile *f ) { + idStr work; + f->ReadDict( state ); + source = state.GetString("name"); + + if (desktop == NULL) { + f->Log("creating new gui\n"); + desktop = new (TAG_OLD_UI) idWindow(this); + desktop->SetFlag( WIN_DESKTOP ); + desktop->ReadFromDemoFile(f); + } else { + f->Log("re-using gui\n"); + desktop->ReadFromDemoFile(f, false); + } + + f->ReadFloat( cursorX ); + f->ReadFloat( cursorY ); + + bool add = true; + int c = uiManagerLocal.demoGuis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( uiManagerLocal.demoGuis[i] == this ) { + add = false; + break; + } + } + + if (add) { + uiManagerLocal.demoGuis.Append(this); + } +} + +void idUserInterfaceLocal::WriteToDemoFile( class idDemoFile *f ) { + idStr work; + f->WriteDict( state ); + if (desktop) { + desktop->WriteToDemoFile(f); + } + + f->WriteFloat( cursorX ); + f->WriteFloat( cursorY ); +} + +bool idUserInterfaceLocal::WriteToSaveGame( idFile *savefile ) const { + int len; + const idKeyValue *kv; + const char *string; + + int num = state.GetNumKeyVals(); + savefile->Write( &num, sizeof( num ) ); + + for( int i = 0; i < num; i++ ) { + kv = state.GetKeyVal( i ); + len = kv->GetKey().Length(); + string = kv->GetKey().c_str(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( string, len ); + + len = kv->GetValue().Length(); + string = kv->GetValue().c_str(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( string, len ); + } + + savefile->Write( &active, sizeof( active ) ); + savefile->Write( &interactive, sizeof( interactive ) ); + savefile->Write( &uniqued, sizeof( uniqued ) ); + savefile->Write( &time, sizeof( time ) ); + len = activateStr.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( activateStr.c_str(), len ); + len = pendingCmd.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( pendingCmd.c_str(), len ); + len = returnCmd.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( returnCmd.c_str(), len ); + + savefile->Write( &cursorX, sizeof( cursorX ) ); + savefile->Write( &cursorY, sizeof( cursorY ) ); + + desktop->WriteToSaveGame( savefile ); + + return true; +} + +bool idUserInterfaceLocal::ReadFromSaveGame( idFile * savefile ) { + int num; + int i, len; + idStr key; + idStr value; + + savefile->Read( &num, sizeof( num ) ); + + state.Clear(); + for( i = 0; i < num; i++ ) { + savefile->Read( &len, sizeof( len ) ); + key.Fill( ' ', len ); + savefile->Read( &key[0], len ); + + savefile->Read( &len, sizeof( len ) ); + value.Fill( ' ', len ); + savefile->Read( &value[0], len ); + + state.Set( key, value ); + } + + savefile->Read( &active, sizeof( active ) ); + savefile->Read( &interactive, sizeof( interactive ) ); + savefile->Read( &uniqued, sizeof( uniqued ) ); + savefile->Read( &time, sizeof( time ) ); + + savefile->Read( &len, sizeof( len ) ); + activateStr.Fill( ' ', len ); + savefile->Read( &activateStr[0], len ); + savefile->Read( &len, sizeof( len ) ); + pendingCmd.Fill( ' ', len ); + savefile->Read( &pendingCmd[0], len ); + savefile->Read( &len, sizeof( len ) ); + returnCmd.Fill( ' ', len ); + savefile->Read( &returnCmd[0], len ); + + savefile->Read( &cursorX, sizeof( cursorX ) ); + savefile->Read( &cursorY, sizeof( cursorY ) ); + + desktop->ReadFromSaveGame( savefile ); + + return true; +} + +size_t idUserInterfaceLocal::Size() { + size_t sz = sizeof(*this) + state.Size() + source.Allocated(); + if ( desktop ) { + sz += desktop->Size(); + } + return sz; +} + +void idUserInterfaceLocal::RecurseSetKeyBindingNames( idWindow *window ) { + int i; + idWinVar *v = window->GetWinVarByName( "bind" ); + if ( v ) { + SetStateString( v->GetName(), idKeyInput::KeysFromBinding( v->GetName() ) ); + } + i = 0; + while ( i < window->GetChildCount() ) { + idWindow *next = window->GetChild( i ); + if ( next ) { + RecurseSetKeyBindingNames( next ); + } + i++; + } +} + +/* +============== +idUserInterfaceLocal::SetKeyBindingNames +============== +*/ +void idUserInterfaceLocal::SetKeyBindingNames() { + if ( !desktop ) { + return; + } + // walk the windows + RecurseSetKeyBindingNames( desktop ); +} + +/* +============== +idUserInterfaceLocal::SetCursor +============== +*/ +void idUserInterfaceLocal::SetCursor( float x, float y ) { + cursorX = x; + cursorY = y; +} + diff --git a/neo/ui/UserInterface.h b/neo/ui/UserInterface.h new file mode 100644 index 00000000..e6cc1d7b --- /dev/null +++ b/neo/ui/UserInterface.h @@ -0,0 +1,167 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __USERINTERFACE_H__ +#define __USERINTERFACE_H__ + +/* +=============================================================================== + + Draws an interactive 2D surface. + Used for all user interaction with the game. + +=============================================================================== +*/ + +class idFile; +class idDemoFile; + + +class idUserInterface { +public: + virtual ~idUserInterface() {}; + + // Returns the name of the gui. + virtual const char * Name() const = 0; + + // Returns a comment on the gui. + virtual const char * Comment() const = 0; + + // Returns true if the gui is interactive. + virtual bool IsInteractive() const = 0; + + virtual bool IsUniqued() const = 0; + + virtual void SetUniqued( bool b ) = 0; + // returns false if it failed to load + virtual bool InitFromFile( const char *qpath, bool rebuild = true, bool cache = true ) = 0; + + // handles an event, can return an action string, the caller interprets + // any return and acts accordingly + virtual const char * HandleEvent( const sysEvent_t *event, int time, bool *updateVisuals = NULL ) = 0; + + // handles a named event + virtual void HandleNamedEvent( const char *eventName ) = 0; + + // repaints the ui + virtual void Redraw( int time, bool hud = false ) = 0; + + // repaints the cursor + virtual void DrawCursor() = 0; + + // Provides read access to the idDict that holds this gui's state. + virtual const idDict & State() const = 0; + + // Removes a gui state variable + virtual void DeleteStateVar( const char *varName ) = 0; + + // Sets a gui state variable. + virtual void SetStateString( const char *varName, const char *value ) = 0; + virtual void SetStateBool( const char *varName, const bool value ) = 0; + virtual void SetStateInt( const char *varName, const int value ) = 0; + virtual void SetStateFloat( const char *varName, const float value ) = 0; + + // Gets a gui state variable + virtual const char* GetStateString( const char *varName, const char* defaultString = "" ) const = 0; + virtual bool GetStateBool( const char *varName, const char* defaultString = "0" ) const = 0; + virtual int GetStateInt( const char *varName, const char* defaultString = "0" ) const = 0; + virtual float GetStateFloat( const char *varName, const char* defaultString = "0" ) const = 0; + + // The state has changed and the gui needs to update from the state idDict. + virtual void StateChanged( int time, bool redraw = false ) = 0; + + // Activated the gui. + virtual const char * Activate( bool activate, int time ) = 0; + + // Triggers the gui and runs the onTrigger scripts. + virtual void Trigger( int time ) = 0; + + virtual void ReadFromDemoFile( class idDemoFile *f ) = 0; + virtual void WriteToDemoFile( class idDemoFile *f ) = 0; + + virtual bool WriteToSaveGame( idFile * savefile ) const = 0; + virtual bool ReadFromSaveGame( idFile * savefile ) = 0; + virtual void SetKeyBindingNames() = 0; + + virtual void SetCursor( float x, float y ) = 0; + virtual float CursorX() = 0; + virtual float CursorY() = 0; +}; + + +class idUserInterfaceManager { +public: + virtual ~idUserInterfaceManager() {}; + + virtual void Init() = 0; + virtual void Shutdown() = 0; + virtual void Touch( const char *name ) = 0; + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // use either the optimized or legacy implementation for + // testing, based on the g_useNewGuiCode cvar + virtual void SetDrawingDC() = 0; + + // Sets the size for 640x480 adjustment. + virtual void SetSize( float width, float height ) = 0; + + virtual void BeginLevelLoad() = 0; + virtual void EndLevelLoad( const char *mapName ) = 0; + virtual void Preload( const char *mapName ) = 0; + + // Reloads changed guis, or all guis. + virtual void Reload( bool all ) = 0; + + // lists all guis + virtual void ListGuis() const = 0; + + // Returns true if gui exists. + virtual bool CheckGui( const char *qpath ) const = 0; + + // Allocates a new gui. + virtual idUserInterface * Alloc() const = 0; + + // De-allocates a gui.. ONLY USE FOR PRECACHING + virtual void DeAlloc( idUserInterface *gui ) = 0; + + // Returns NULL if gui by that name does not exist. + virtual idUserInterface * FindGui( const char *qpath, bool autoLoad = false, bool needUnique = false, bool forceUnique = false ) = 0; + + // Returns NULL if gui by that name does not exist. + virtual idUserInterface * FindDemoGui( const char *qpath ) = 0; + + // Allocates a new GUI list handler + virtual idListGUI * AllocListGUI() const = 0; + + // De-allocates a list gui + virtual void FreeListGUI( idListGUI *listgui ) = 0; +}; + +extern idUserInterfaceManager * uiManager; + +#endif /* !__USERINTERFACE_H__ */ diff --git a/neo/ui/UserInterfaceLocal.h b/neo/ui/UserInterfaceLocal.h new file mode 100644 index 00000000..7fee861b --- /dev/null +++ b/neo/ui/UserInterfaceLocal.h @@ -0,0 +1,156 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +class idWindow; + +class idUserInterfaceLocal : public idUserInterface { + friend class idUserInterfaceManagerLocal; +public: + idUserInterfaceLocal(); + virtual ~idUserInterfaceLocal(); + + virtual const char * Name() const; + virtual const char * Comment() const; + virtual bool IsInteractive() const; + virtual bool InitFromFile( const char *qpath, bool rebuild = true, bool cache = true ); + virtual const char * HandleEvent( const sysEvent_t *event, int time, bool *updateVisuals ); + virtual void HandleNamedEvent( const char* namedEvent ); + virtual void Redraw( int time, bool hud ); + virtual void DrawCursor(); + virtual const idDict & State() const; + virtual void DeleteStateVar( const char *varName ); + virtual void SetStateString( const char *varName, const char *value ); + virtual void SetStateBool( const char *varName, const bool value ); + virtual void SetStateInt( const char *varName, const int value ); + virtual void SetStateFloat( const char *varName, const float value ); + + // Gets a gui state variable + virtual const char* GetStateString( const char *varName, const char* defaultString = "" ) const; + virtual bool GetStateBool( const char *varName, const char* defaultString = "0" ) const; + virtual int GetStateInt( const char *varName, const char* defaultString = "0" ) const; + virtual float GetStateFloat( const char *varName, const char* defaultString = "0" ) const; + + virtual void StateChanged( int time, bool redraw ); + virtual const char * Activate( bool activate, int time ); + virtual void Trigger( int time ); + virtual void ReadFromDemoFile( class idDemoFile *f ); + virtual void WriteToDemoFile( class idDemoFile *f ); + virtual bool WriteToSaveGame( idFile *savefile ) const; + virtual bool ReadFromSaveGame( idFile * savefile ); + virtual void SetKeyBindingNames(); + virtual bool IsUniqued() const { return uniqued; }; + virtual void SetUniqued( bool b ) { uniqued = b; }; + virtual void SetCursor( float x, float y ); + + virtual float CursorX() { return cursorX; } + virtual float CursorY() { return cursorY; } + + size_t Size(); + + idDict * GetStateDict() { return &state; } + + const char * GetSourceFile() const { return source; } + ID_TIME_T GetTimeStamp() const { return timeStamp; } + + idWindow * GetDesktop() const { return desktop; } + void SetBindHandler( idWindow *win ) { bindHandler = win; } + bool Active() const { return active; } + int GetTime() const { return time; } + void SetTime( int _time ) { time = _time; } + + void ClearRefs() { refs = 0; } + void AddRef() { refs++; } + int GetRefs() { return refs; } + + void RecurseSetKeyBindingNames( idWindow *window ); + idStr &GetPendingCmd() { return pendingCmd; }; + idStr &GetReturnCmd() { return returnCmd; }; + +private: + bool active; + bool loading; + bool interactive; + bool uniqued; + + idDict state; + idWindow * desktop; + idWindow * bindHandler; + + idStr source; + idStr activateStr; + idStr pendingCmd; + idStr returnCmd; + ID_TIME_T timeStamp; + + float cursorX; + float cursorY; + + int time; + + int refs; +}; + + + +class idUserInterfaceManagerLocal : public idUserInterfaceManager { + friend class idUserInterfaceLocal; + +public: + virtual void Init(); + virtual void Shutdown(); + virtual void SetDrawingDC(); + virtual void Touch( const char *name ); + virtual void WritePrecacheCommands( idFile *f ); + virtual void SetSize( float width, float height ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad( const char *mapName ); + virtual void Preload( const char *mapName ); + virtual void Reload( bool all ); + virtual void ListGuis() const; + virtual bool CheckGui( const char *qpath ) const; + virtual idUserInterface * Alloc() const; + virtual void DeAlloc( idUserInterface *gui ); + virtual idUserInterface * FindGui( const char *qpath, bool autoLoad = false, bool needInteractive = false, bool forceUnique = false ); + virtual idUserInterface * FindDemoGui( const char *qpath ); + virtual idListGUI * AllocListGUI() const; + virtual void FreeListGUI( idListGUI *listgui ); + idTokenParser & GetBinaryParser() { return mapParser; } +private: + idRectangle screenRect; + idDeviceContext dcOld; + idDeviceContextOptimized dcOptimized; + + idList guis; + idList demoGuis; + + idTokenParser mapParser; +}; + +// These used to be in every window, but they all pointed at the same one in idUserInterfaceManagerLocal. +// Made a global so it can be switched out dynamically to test optimized versions. +extern idDeviceContext *dc; diff --git a/neo/ui/Window.cpp b/neo/ui/Window.cpp new file mode 100644 index 00000000..51fe14ec --- /dev/null +++ b/neo/ui/Window.cpp @@ -0,0 +1,4109 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "EditWindow.h" +#include "ChoiceWindow.h" +#include "SliderWindow.h" +#include "BindWindow.h" +#include "ListWindow.h" +#include "RenderWindow.h" +#include "FieldWindow.h" + +#include "GameSSDWindow.h" +#include "GameBearShootWindow.h" +#include "GameBustOutWindow.h" + +bool idWindow::registerIsTemporary[MAX_EXPRESSION_REGISTERS]; // statics to assist during parsing +//float idWindow::shaderRegisters[MAX_EXPRESSION_REGISTERS]; +//wexpOp_t idWindow::shaderOps[MAX_EXPRESSION_OPS]; + +idCVar idWindow::gui_debug( "gui_debug", "0", CVAR_GUI | CVAR_BOOL, "" ); +idCVar idWindow::gui_edit( "gui_edit", "0", CVAR_GUI | CVAR_BOOL, "" ); + +idCVar hud_titlesafe( "hud_titlesafe", "0.0", CVAR_GUI | CVAR_FLOAT, "fraction of the screen to leave around hud for titlesafe area" ); + +extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces + +// made RegisterVars a member of idWindow +const idRegEntry idWindow::RegisterVars[] = { + { "forecolor", idRegister::VEC4 }, + { "hovercolor", idRegister::VEC4 }, + { "backcolor", idRegister::VEC4 }, + { "bordercolor", idRegister::VEC4 }, + { "rect", idRegister::RECTANGLE }, + { "matcolor", idRegister::VEC4 }, + { "scale", idRegister::VEC2 }, + { "translate", idRegister::VEC2 }, + { "rotate", idRegister::FLOAT }, + { "textscale", idRegister::FLOAT }, + { "visible", idRegister::BOOL }, + { "noevents", idRegister::BOOL }, + { "text", idRegister::STRING }, + { "background", idRegister::STRING }, + { "runscript", idRegister::STRING }, + { "varbackground", idRegister::STRING }, + { "cvar", idRegister::STRING }, + { "choices", idRegister::STRING }, + { "choiceVar", idRegister::STRING }, + { "bind", idRegister::STRING }, + { "modelRotate", idRegister::VEC4 }, + { "modelOrigin", idRegister::VEC4 }, + { "lightOrigin", idRegister::VEC4 }, + { "lightColor", idRegister::VEC4 }, + { "viewOffset", idRegister::VEC4 }, + { "hideCursor", idRegister::BOOL} +}; + +const int idWindow::NumRegisterVars = sizeof(RegisterVars) / sizeof(idRegEntry); + +const char *idWindow::ScriptNames[] = { + "onMouseEnter", + "onMouseExit", + "onAction", + "onActivate", + "onDeactivate", + "onESC", + "onEvent", + "onTrigger", + "onActionRelease", + "onEnter", + "onEnterRelease" +}; + +/* +================ +idWindow::CommonInit +================ +*/ +void idWindow::CommonInit() { + childID = 0; + flags = 0; + lastTimeRun = 0; + origin.Zero(); + font = renderSystem->RegisterFont( "" ); + timeLine = -1; + xOffset = yOffset = 0.0; + cursor = 0; + forceAspectWidth = 640; + forceAspectHeight = 480; + matScalex = 1; + matScaley = 1; + borderSize = 0; + noTime = false; + visible = true; + textAlign = 0; + textAlignx = 0; + textAligny = 0; + noEvents = false; + rotate = 0; + shear.Zero(); + textScale = 0.35f; + backColor.Zero(); + foreColor = idVec4(1, 1, 1, 1); + hoverColor = idVec4(1, 1, 1, 1); + matColor = idVec4(1, 1, 1, 1); + borderColor.Zero(); + background = NULL; + backGroundName = ""; + focusedChild = NULL; + captureChild = NULL; + overChild = NULL; + parent = NULL; + saveOps = NULL; + saveRegs = NULL; + timeLine = -1; + textShadow = 0; + hover = false; + + for (int i = 0; i < SCRIPT_COUNT; i++) { + scripts[i] = NULL; + } + + hideCursor = false; +} + +/* +================ +idWindow::Size +================ +*/ +size_t idWindow::Size() { + int c = children.Num(); + int sz = 0; + for (int i = 0; i < c; i++) { + sz += children[i]->Size(); + } + sz += sizeof(*this) + Allocated(); + return sz; +} + +/* +================ +idWindow::Allocated +================ +*/ +size_t idWindow::Allocated() { + int i, c; + int sz = name.Allocated(); + sz += text.Size(); + sz += backGroundName.Size(); + + c = definedVars.Num(); + for (i = 0; i < c; i++) { + sz += definedVars[i]->Size(); + } + + for (i = 0; i < SCRIPT_COUNT; i++) { + if (scripts[i]) { + sz += scripts[i]->Size(); + } + } + c = timeLineEvents.Num(); + for (i = 0; i < c; i++) { + sz += timeLineEvents[i]->Size(); + } + + c = namedEvents.Num(); + for (i = 0; i < c; i++) { + sz += namedEvents[i]->Size(); + } + + c = drawWindows.Num(); + for (i = 0; i < c; i++) { + if (drawWindows[i].simp) { + sz += drawWindows[i].simp->Size(); + } + } + + return sz; +} + +/* +================ +idWindow::idWindow +================ +*/ +idWindow::idWindow(idUserInterfaceLocal *ui) { + gui = ui; + CommonInit(); +} + +/* +================ +idWindow::CleanUp +================ +*/ +void idWindow::CleanUp() { + int i, c = drawWindows.Num(); + for (i = 0; i < c; i++) { + delete drawWindows[i].simp; + } + + // ensure the register list gets cleaned up + regList.Reset ( ); + + // Cleanup the named events + namedEvents.DeleteContents(true); + + drawWindows.Clear(); + children.DeleteContents(true); + definedVars.DeleteContents(true); + timeLineEvents.DeleteContents(true); + for (i = 0; i < SCRIPT_COUNT; i++) { + delete scripts[i]; + } + CommonInit(); +} + +/* +================ +idWindow::~idWindow +================ +*/ +idWindow::~idWindow() { + CleanUp(); +} + +/* +================ +idWindow::Move +================ +*/ +void idWindow::Move(float x, float y) { + idRectangle rct = rect; + rct.x = x; + rct.y = y; + idRegister *reg = RegList()->FindReg("rect"); + if (reg) { + reg->Enable(false); + } + rect = rct; +} + +/* +================ +idWindow::SetFont +================ +*/ +void idWindow::SetFont() { + dc->SetFont( font ); +} + +/* +================ +idWindow::GetMaxCharHeight +================ +*/ +float idWindow::GetMaxCharHeight() { + SetFont(); + return dc->MaxCharHeight(textScale); +} + +/* +================ +idWindow::GetMaxCharWidth +================ +*/ +float idWindow::GetMaxCharWidth() { + SetFont(); + return dc->MaxCharWidth(textScale); +} + +/* +================ +idWindow::Draw +================ +*/ +void idWindow::Draw( int time, float x, float y ) { + if ( text.Length() == 0 ) { + return; + } + if ( textShadow ) { + idStr shadowText = text; + idRectangle shadowRect = textRect; + + shadowText.RemoveColors(); + shadowRect.x += textShadow; + shadowRect.y += textShadow; + + dc->DrawText( shadowText, textScale, textAlign, colorBlack, shadowRect, !( flags & WIN_NOWRAP ), -1 ); + } + dc->DrawText( text, textScale, textAlign, foreColor, textRect, !( flags & WIN_NOWRAP ), -1 ); + + if ( gui_edit.GetBool() ) { + dc->EnableClipping( false ); + dc->DrawText( va( "x: %i y: %i", ( int )rect.x(), ( int )rect.y() ), 0.25, 0, dc->colorWhite, idRectangle( rect.x(), rect.y() - 15, 100, 20 ), false ); + dc->DrawText( va( "w: %i h: %i", ( int )rect.w(), ( int )rect.h() ), 0.25, 0, dc->colorWhite, idRectangle( rect.x() + rect.w(), rect.w() + rect.h() + 5, 100, 20 ), false ); + dc->EnableClipping( true ); + } + +} + +/* +================ +idWindow::BringToTop +================ +*/ +void idWindow::BringToTop(idWindow *w) { + + if (w && !(w->flags & WIN_MODAL)) { + return; + } + + int c = children.Num(); + for (int i = 0; i < c; i++) { + if (children[i] == w) { + // this is it move from i - 1 to 0 to i to 1 then shove this one into 0 + for (int j = i+1; j < c; j++) { + children[j-1] = children[j]; + } + children[c-1] = w; + break; + } + } +} + +/* +================ +idWindow::Size +================ +*/ +void idWindow::Size(float x, float y, float w, float h) { + idRectangle rct = rect; + rct.x = x; + rct.y = y; + rct.w = w; + rct.h = h; + rect = rct; + CalcClientRect(0,0); +} + +/* +================ +idWindow::MouseEnter +================ +*/ +void idWindow::MouseEnter() { + + if (noEvents) { + return; + } + + RunScript(ON_MOUSEENTER); +} + +/* +================ +idWindow::MouseExit +================ +*/ +void idWindow::MouseExit() { + + if (noEvents) { + return; + } + + RunScript(ON_MOUSEEXIT); +} + +/* +================ +idWindow::GetChildWithOnAction +================ +*/ +idWindow * idWindow::GetChildWithOnAction( float xd, float yd ) { + + int c = children.Num(); + while ( c > 0 ) { + idWindow *child = children[--c]; + if ( child->visible && child->Contains(child->drawRect, gui->CursorX(), gui->CursorY() ) && !child->noEvents ) { + child->hover = true; + if ( child->cursor > 0 ) { + return child; + } + } + + idWindow * check = child->GetChildWithOnAction( xd, yd ); + if ( check != NULL && check != child ) { + return check; + } + } + + return this; + +} + +/* +================ +idWindow::RouteMouseCoords +================ +*/ +const char *idWindow::RouteMouseCoords(float xd, float yd) { + idStr str; + if (GetCaptureChild()) { + //FIXME: unkludge this whole mechanism + return GetCaptureChild()->RouteMouseCoords(xd, yd); + } + + if (xd == -2000 || yd == -2000) { + return ""; + } + + idWindow * child = GetChildWithOnAction( xd, yd ); + if ( overChild != child ) { + if ( overChild ) { + overChild->MouseExit(); + str = overChild->cmd; + if (str.Length()) { + gui->GetDesktop()->AddCommand(str); + overChild->cmd = ""; + } + } + overChild = child; + if ( overChild ) { + overChild->MouseEnter(); + str = overChild->cmd; + if (str.Length()) { + gui->GetDesktop()->AddCommand(str); + overChild->cmd = ""; + } + + dc->SetCursor( overChild->cursor ); + } + } + + return ""; +} + +/* +================ +idWindow::Activate +================ +*/ +void idWindow::Activate( bool activate, idStr &act ) { + + int n = (activate) ? ON_ACTIVATE : ON_DEACTIVATE; + + // make sure win vars are updated before activation + UpdateWinVars ( ); + + RunScript(n); + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->Activate( activate, act ); + } + + if ( act.Length() ) { + act += " ; "; + } +} + +/* +================ +idWindow::Trigger +================ +*/ +void idWindow::Trigger() { + RunScript( ON_TRIGGER ); + int c = children.Num(); + for ( int i = 0; i < c; i++ ) { + children[i]->Trigger(); + } + StateChanged( true ); +} + +/* +================ +idWindow::StateChanged +================ +*/ +void idWindow::StateChanged( bool redraw ) { + + UpdateWinVars(); + + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(); + } + + int c = drawWindows.Num(); + for ( int i = 0; i < c; i++ ) { + if ( drawWindows[i].win ) { + drawWindows[i].win->StateChanged( redraw ); + } else { + drawWindows[i].simp->StateChanged( redraw ); + } + } +} + +/* +================ +idWindow::SetCapture +================ +*/ +idWindow *idWindow::SetCapture(idWindow *w) { + // only one child can have the focus + + idWindow *last = NULL; + int c = children.Num(); + for (int i = 0; i < c; i++) { + if ( children[i]->flags & WIN_CAPTURE ) { + last = children[i]; + //last->flags &= ~WIN_CAPTURE; + last->LoseCapture(); + break; + } + } + + w->flags |= WIN_CAPTURE; + w->GainCapture(); + gui->GetDesktop()->captureChild = w; + return last; +} + +/* +================ +idWindow::AddUpdateVar +================ +*/ +void idWindow::AddUpdateVar(idWinVar *var) { + updateVars.AddUnique(var); +} + +/* +================ +idWindow::UpdateWinVars +================ +*/ +void idWindow::UpdateWinVars() { + int c = updateVars.Num(); + for (int i = 0; i < c; i++) { + updateVars[i]->Update(); + } +} + +/* +================ +idWindow::RunTimeEvents +================ +*/ +bool idWindow::RunTimeEvents(int time) { + + if ( time == lastTimeRun ) { + return false; + } + + lastTimeRun = time; + + UpdateWinVars(); + + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(); + } + + if ( flags & WIN_INTRANSITION ) { + Transition(); + } + + Time(); + + // renamed ON_EVENT to ON_FRAME + RunScript(ON_FRAME); + + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->RunTimeEvents(time); + } + + return true; +} + +/* +================ +idWindow::RunNamedEvent +================ +*/ +void idWindow::RunNamedEvent ( const char* eventName ) +{ + int i; + int c; + + // Find and run the event + c = namedEvents.Num( ); + for ( i = 0; i < c; i ++ ) { + if ( namedEvents[i]->mName.Icmp( eventName ) ) { + continue; + } + + UpdateWinVars(); + + // Make sure we got all the current values for stuff + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(-1, true); + } + + RunScriptList( namedEvents[i]->mEvent ); + + break; + } + + // Run the event in all the children as well + c = children.Num(); + for ( i = 0; i < c; i++ ) { + children[i]->RunNamedEvent ( eventName ); + } +} + +/* +================ +idWindow::Contains +================ +*/ +bool idWindow::Contains(const idRectangle &sr, float x, float y) { + idRectangle r = sr; + r.x += actualX - drawRect.x; + r.y += actualY - drawRect.y; + return r.Contains(x, y); +} + +/* +================ +idWindow::Contains +================ +*/ +bool idWindow::Contains(float x, float y) { + idRectangle r = drawRect; + r.x = actualX; + r.y = actualY; + return r.Contains(x, y); +} + +/* +================ +idWindow::AddCommand +================ +*/ +void idWindow::AddCommand(const char *_cmd) { + idStr str = cmd; + if (str.Length()) { + str += " ; "; + str += _cmd; + } else { + str = _cmd; + } + cmd = str; +} + +/* +================ +idWindow::HandleEvent +================ +*/ +const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + static bool actionDownRun; + static bool actionUpRun; + + cmd = ""; + + if ( flags & WIN_DESKTOP ) { + actionDownRun = false; + actionUpRun = false; + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(); + } + RunTimeEvents(gui->GetTime()); + CalcRects(0,0); + + if ( overChild != NULL ) { + dc->SetCursor( overChild->cursor ); + } else { + dc->SetCursor( idDeviceContext::CURSOR_ARROW ); + } + } + + if (visible && !noEvents) { + + if (event->evType == SE_KEY) { + EvalRegs(-1, true); + if (updateVisuals) { + *updateVisuals = true; + } + + if (event->evValue == K_MOUSE1) { + + if (!event->evValue2 && GetCaptureChild()) { + GetCaptureChild()->LoseCapture(); + gui->GetDesktop()->captureChild = NULL; + return ""; + } + + int c = children.Num(); + while (--c >= 0) { + if (children[c]->visible && children[c]->Contains(children[c]->drawRect, gui->CursorX(), gui->CursorY()) && !(children[c]->noEvents)) { + idWindow *child = children[c]; + if (event->evValue2) { + BringToTop(child); + SetFocus(child); + if (child->flags & WIN_HOLDCAPTURE) { + SetCapture(child); + } + } + if (child->Contains(child->clientRect, gui->CursorX(), gui->CursorY())) { + //if ((gui_edit.GetBool() && (child->flags & WIN_SELECTED)) || (!gui_edit.GetBool() && (child->flags & WIN_MOVABLE))) { + // SetCapture(child); + //} + SetFocus(child); + const char *childRet = child->HandleEvent(event, updateVisuals); + if (childRet != NULL && *childRet != NULL) { + return childRet; + } + if (child->flags & WIN_MODAL) { + return ""; + } + } else { + if (event->evValue2) { + SetFocus(child); + bool capture = true; + if (capture && ((child->flags & WIN_MOVABLE) || gui_edit.GetBool())) { + SetCapture(child); + } + return ""; + } else { + } + } + } + } + if (event->evValue2 && !actionDownRun) { + actionDownRun = RunScript( ON_ACTION ); + } else if (!actionUpRun) { + actionUpRun = RunScript( ON_ACTIONRELEASE ); + } + } else if (event->evValue == K_MOUSE2) { + + if (!event->evValue2 && GetCaptureChild()) { + GetCaptureChild()->LoseCapture(); + gui->GetDesktop()->captureChild = NULL; + return ""; + } + + int c = children.Num(); + while (--c >= 0) { + if (children[c]->visible && children[c]->Contains(children[c]->drawRect, gui->CursorX(), gui->CursorY()) && !(children[c]->noEvents)) { + idWindow *child = children[c]; + if (event->evValue2) { + BringToTop(child); + SetFocus(child); + } + if (child->Contains(child->clientRect,gui->CursorX(), gui->CursorY()) || GetCaptureChild() == child) { + if ((gui_edit.GetBool() && (child->flags & WIN_SELECTED)) || (!gui_edit.GetBool() && (child->flags & WIN_MOVABLE))) { + SetCapture(child); + } + const char *childRet = child->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + if (child->flags & WIN_MODAL) { + return ""; + } + } + } + } + } else if (event->evValue == K_MOUSE3) { + if (gui_edit.GetBool()) { + int c = children.Num(); + for (int i = 0; i < c; i++) { + if (children[i]->drawRect.Contains(gui->CursorX(), gui->CursorY())) { + if (event->evValue2) { + children[i]->flags ^= WIN_SELECTED; + if (children[i]->flags & WIN_SELECTED) { + flags &= ~WIN_SELECTED; + return "childsel"; + } + } + } + } + } + } else if (event->evValue == K_TAB && event->evValue2) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + + // If the window didn't handle the tab, then move the focus to the next window + // or the previous window if shift is held down + + int direction = 1; + if ( idKeyInput::IsDown( K_LSHIFT ) || idKeyInput::IsDown( K_RSHIFT ) ) { + direction = -1; + } + + idWindow *currentFocus = GetFocusedChild(); + idWindow *child = GetFocusedChild(); + idWindow *parent = child->GetParent(); + while ( parent ) { + bool foundFocus = false; + bool recurse = false; + int index = 0; + if ( child ) { + index = parent->GetChildIndex( child ) + direction; + } else if ( direction < 0 ) { + index = parent->GetChildCount() - 1; + } + while ( index < parent->GetChildCount() && index >= 0) { + idWindow *testWindow = parent->GetChild( index ); + if ( testWindow == currentFocus ) { + // we managed to wrap around and get back to our starting window + foundFocus = true; + break; + } + if ( testWindow && !testWindow->noEvents && testWindow->visible ) { + if ( testWindow->flags & WIN_CANFOCUS ) { + SetFocus( testWindow ); + foundFocus = true; + break; + } else if ( testWindow->GetChildCount() > 0 ) { + parent = testWindow; + child = NULL; + recurse = true; + break; + } + } + index += direction; + } + if ( foundFocus ) { + // We found a child to focus on + break; + } else if ( recurse ) { + // We found a child with children + continue; + } else { + // We didn't find anything, so go back up to our parent + child = parent; + parent = child->GetParent(); + if ( parent == gui->GetDesktop() ) { + // We got back to the desktop, so wrap around but don't actually go to the desktop + parent = NULL; + child = NULL; + } + } + } + } + } else if ( ( event->evValue == K_ESCAPE || event->evValue == K_JOY9 ) && event->evValue2) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + RunScript( ON_ESC ); + } else if (event->evValue == K_ENTER ) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + if ( flags & WIN_WANTENTER ) { + if ( event->evValue2 ) { + RunScript( ON_ACTION ); + } else { + RunScript( ON_ACTIONRELEASE ); + } + } + } else { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + } + + } else if (event->evType == SE_MOUSE) { + if (updateVisuals) { + *updateVisuals = true; + } + const char *mouseRet = RouteMouseCoords(event->evValue, event->evValue2); + if (mouseRet != NULL && *mouseRet != NULL) { + return mouseRet; + } + } else if (event->evType == SE_NONE) { + } else if (event->evType == SE_CHAR) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + } + } + + gui->GetReturnCmd() = cmd; + if ( gui->GetPendingCmd().Length() ) { + gui->GetReturnCmd() += " ; "; + gui->GetReturnCmd() += gui->GetPendingCmd(); + gui->GetPendingCmd().Clear(); + } + cmd = ""; + return gui->GetReturnCmd(); +} + +/* +================ +idWindow::DebugDraw +================ +*/ +void idWindow::DebugDraw(int time, float x, float y) { + static char buff[16384] = { 0 }; + if (dc) { + dc->EnableClipping(false); + if (gui_debug.GetInteger() == 1) { + dc->DrawRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, 1, idDeviceContext::colorRed); + } else if (gui_debug.GetInteger() == 2) { + char out[1024]; + idStr str; + str = text.c_str(); + + if (str.Length()) { + sprintf(buff, "%s\n", str.c_str()); + } + + sprintf(out, "Rect: %0.1f, %0.1f, %0.1f, %0.1f\n", rect.x(), rect.y(), rect.w(), rect.h()); + strcat(buff, out); + sprintf(out, "Draw Rect: %0.1f, %0.1f, %0.1f, %0.1f\n", drawRect.x, drawRect.y, drawRect.w, drawRect.h); + strcat(buff, out); + sprintf(out, "Client Rect: %0.1f, %0.1f, %0.1f, %0.1f\n", clientRect.x, clientRect.y, clientRect.w, clientRect.h); + strcat(buff, out); + sprintf(out, "Cursor: %0.1f : %0.1f\n", gui->CursorX(), gui->CursorY()); + strcat(buff, out); + + + //idRectangle tempRect = textRect; + //tempRect.x += offsetX; + //drawRect.y += offsetY; + dc->DrawText(buff, textScale, textAlign, foreColor, textRect, true); + } + dc->EnableClipping(true); + } +} + +/* +================ +idWindow::Transition +================ +*/ +void idWindow::Transition() { + int i, c = transitions.Num(); + bool clear = true; + + for ( i = 0; i < c; i++ ) { + idTransitionData *data = &transitions[i]; + idWinRectangle *r = NULL; + idWinVec4 *v4 = dynamic_cast(data->data); + idWinFloat* val = NULL; + if (v4 == NULL) { + r = dynamic_cast(data->data); + if ( r == NULL ) { + val = dynamic_cast(data->data); + } + } + if ( data->interp.IsDone( gui->GetTime() ) && data->data) { + if (v4) { + *v4 = data->interp.GetEndValue(); + } else if ( val ) { + *val = data->interp.GetEndValue()[0]; + } else if ( r != NULL ) { + *r = data->interp.GetEndValue(); + } + } else { + clear = false; + if (data->data) { + if (v4) { + *v4 = data->interp.GetCurrentValue( gui->GetTime() ); + } else if ( val ) { + *val = data->interp.GetCurrentValue( gui->GetTime() )[0]; + } else if ( r != NULL ) { + *r = data->interp.GetCurrentValue( gui->GetTime() ); + } + } else { + common->Warning("Invalid transitional data for window %s in gui %s", GetName(), gui->GetSourceFile()); + } + } + } + + if ( clear ) { + transitions.SetNum( 0 ); + flags &= ~WIN_INTRANSITION; + } +} + +/* +================ +idWindow::Time +================ +*/ +void idWindow::Time() { + + if ( noTime ) { + return; + } + + if ( timeLine == -1 ) { + timeLine = gui->GetTime(); + } + + cmd = ""; + + int c = timeLineEvents.Num(); + if ( c > 0 ) { + for (int i = 0; i < c; i++) { + if ( timeLineEvents[i]->pending && gui->GetTime() - timeLine >= timeLineEvents[i]->time ) { + timeLineEvents[i]->pending = false; + RunScriptList( timeLineEvents[i]->event ); + } + } + } + + if ( gui->Active() ) { + if ( gui->GetPendingCmd().Length() > 0 ) { + gui->GetPendingCmd() += ";"; + } + gui->GetPendingCmd() += cmd; + } +} + +/* +================ +idWindow::EvalRegs +================ +*/ +float idWindow::EvalRegs(int test, bool force) { + static float regs[MAX_EXPRESSION_REGISTERS]; + static idWindow *lastEval = NULL; + + if (!force && test >= 0 && test < MAX_EXPRESSION_REGISTERS && lastEval == this) { + return regs[test]; + } + + lastEval = this; + + if (expressionRegisters.Num()) { + regList.SetToRegs(regs); + EvaluateRegisters(regs); + regList.GetFromRegs(regs); + } + + if (test >= 0 && test < MAX_EXPRESSION_REGISTERS) { + return regs[test]; + } + + return 0.0; +} + +/* +================ +idWindow::DrawBackground +================ +*/ +void idWindow::DrawBackground(const idRectangle &drawRect) { + if ( backColor.w() ) { + dc->DrawFilledRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, backColor); + } + + if ( background && matColor.w() ) { + float scalex, scaley; + if ( flags & WIN_NATURALMAT ) { + scalex = drawRect.w / background->GetImageWidth(); + scaley = drawRect.h / background->GetImageHeight(); + } else { + scalex = matScalex; + scaley = matScaley; + } + dc->DrawMaterial(drawRect.x, drawRect.y, drawRect.w, drawRect.h, background, matColor, scalex, scaley); + } +} + +/* +================ +idWindow::DrawBorderAndCaption +================ +*/ +void idWindow::DrawBorderAndCaption(const idRectangle &drawRect) { + if ( flags & WIN_BORDER && borderSize && borderColor.w() ) { + dc->DrawRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, borderSize, borderColor); + } +} + +/* +================ +idWindow::SetupTransforms +================ +*/ +void idWindow::SetupTransforms(float x, float y) { + static idMat3 trans; + static idVec3 org; + + trans.Identity(); + org.Set( origin.x + x, origin.y + y, 0 ); + + if ( rotate ) { + static idRotation rot; + static idVec3 vec(0, 0, 1); + rot.Set( org, vec, rotate ); + trans = rot.ToMat3(); + } + + if ( shear.x || shear.y ) { + static idMat3 smat; + smat.Identity(); + smat[0][1] = shear.x; + smat[1][0] = shear.y; + trans *= smat; + } + + if ( !trans.IsIdentity() ) { + dc->SetTransformInfo( org, trans ); + } +} + +/* +================ +idWindow::CalcRects +================ +*/ +void idWindow::CalcRects(float x, float y) { + CalcClientRect(0, 0); + drawRect.Offset(x, y); + clientRect.Offset(x, y); + actualX = drawRect.x; + actualY = drawRect.y; + int c = drawWindows.Num(); + for (int i = 0; i < c; i++) { + if (drawWindows[i].win) { + drawWindows[i].win->CalcRects(clientRect.x + xOffset, clientRect.y + yOffset); + } + } + drawRect.Offset(-x, -y); + clientRect.Offset(-x, -y); +} + +/* +================ +idWindow::Redraw +================ +*/ +void idWindow::Redraw(float x, float y, bool hud) { + idStr str; + + if (r_skipGuiShaders.GetInteger() == 1 || dc == NULL ) { + return; + } + + int time = gui->GetTime(); + + if ( flags & WIN_DESKTOP && r_skipGuiShaders.GetInteger() != 3 ) { + RunTimeEvents( time ); + } + + if ( r_skipGuiShaders.GetInteger() == 2 ) { + return; + } + + if ( flags & WIN_SHOWTIME ) { + dc->DrawText(va(" %0.1f seconds\n%s", (float)(time - timeLine) / 1000, gui->State().GetString("name")), 0.35f, 0, dc->colorWhite, idRectangle(100, 0, 80, 80), false); + } + + if ( flags & WIN_SHOWCOORDS ) { + dc->EnableClipping(false); + sprintf(str, "x: %i y: %i cursorx: %i cursory: %i", (int)rect.x(), (int)rect.y(), (int)gui->CursorX(), (int)gui->CursorY()); + dc->DrawText(str, 0.25f, 0, dc->colorWhite, idRectangle(0, 0, 100, 20), false); + dc->EnableClipping(true); + } + + if (!visible) { + return; + } + + CalcClientRect(0, 0); + + SetFont(); + + if ( hud ) { + float tileSafeOffset = hud_titlesafe.GetFloat(); + float tileSafeScale = 1.0f / ( 1.0f - hud_titlesafe.GetFloat() * 2.0f ); + dc->SetSize( forceAspectWidth * tileSafeScale, forceAspectHeight * tileSafeScale ); + dc->SetOffset( forceAspectWidth * tileSafeOffset, forceAspectHeight * tileSafeOffset ); + } else { + dc->SetSize( forceAspectWidth, forceAspectHeight ); + dc->SetOffset( 0.0f, 0.0f ); + } + + //FIXME: go to screen coord tracking + drawRect.Offset(x, y); + clientRect.Offset(x, y); + textRect.Offset(x, y); + actualX = drawRect.x; + actualY = drawRect.y; + + idVec3 oldOrg; + idMat3 oldTrans; + + dc->GetTransformInfo( oldOrg, oldTrans ); + + SetupTransforms(x, y); + DrawBackground(drawRect); + DrawBorderAndCaption(drawRect); + + if ( !( flags & WIN_NOCLIP) ) { + dc->PushClipRect(clientRect); + } + + if ( r_skipGuiShaders.GetInteger() < 5 ) { + Draw(time, x, y); + } + + if ( gui_debug.GetInteger() ) { + DebugDraw(time, x, y); + } + + int c = drawWindows.Num(); + for ( int i = 0; i < c; i++ ) { + if ( drawWindows[i].win ) { + drawWindows[i].win->Redraw( clientRect.x + xOffset, clientRect.y + yOffset, hud ); + } else { + drawWindows[i].simp->Redraw( clientRect.x + xOffset, clientRect.y + yOffset ); + } + } + + // Put transforms back to what they were before the children were processed + dc->SetTransformInfo(oldOrg, oldTrans); + + if ( ! ( flags & WIN_NOCLIP ) ) { + dc->PopClipRect(); + } + + if (gui_edit.GetBool() || (flags & WIN_DESKTOP && !( flags & WIN_NOCURSOR ) && !hideCursor && (gui->Active() || ( flags & WIN_MENUGUI ) ))) { + dc->SetTransformInfo(vec3_origin, mat3_identity); + gui->DrawCursor(); + } + + if (gui_debug.GetInteger() && flags & WIN_DESKTOP) { + dc->EnableClipping(false); + sprintf(str, "x: %1.f y: %1.f", gui->CursorX(), gui->CursorY()); + dc->DrawText(str, 0.25, 0, dc->colorWhite, idRectangle(0, 0, 100, 20), false); + dc->DrawText(gui->GetSourceFile(), 0.25, 0, dc->colorWhite, idRectangle(0, 20, 300, 20), false); + dc->EnableClipping(true); + } + + drawRect.Offset(-x, -y); + clientRect.Offset(-x, -y); + textRect.Offset(-x, -y); +} + +/* +================ +idWindow::ArchiveToDictionary +================ +*/ +void idWindow::ArchiveToDictionary(idDict *dict, bool useNames) { + //FIXME: rewrite without state + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->ArchiveToDictionary(dict); + } +} + +/* +================ +idWindow::InitFromDictionary +================ +*/ +void idWindow::InitFromDictionary(idDict *dict, bool byName) { + //FIXME: rewrite without state + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->InitFromDictionary(dict); + } +} + +/* +================ +idWindow::CalcClientRect +================ +*/ +void idWindow::CalcClientRect(float xofs, float yofs) { + drawRect = rect; + + if ( flags & WIN_INVERTRECT ) { + drawRect.x = rect.x() - rect.w(); + drawRect.y = rect.y() - rect.h(); + } + + if (flags & (WIN_HCENTER | WIN_VCENTER) && parent) { + // in this case treat xofs and yofs as absolute top left coords + // and ignore the original positioning + if (flags & WIN_HCENTER) { + drawRect.x = (parent->rect.w() - rect.w()) / 2; + } else { + drawRect.y = (parent->rect.h() - rect.h()) / 2; + } + } + + drawRect.x += xofs; + drawRect.y += yofs; + + clientRect = drawRect; + if (rect.h() > 0.0 && rect.w() > 0.0) { + + if (flags & WIN_BORDER && borderSize != 0.0) { + clientRect.x += borderSize; + clientRect.y += borderSize; + clientRect.w -= borderSize; + clientRect.h -= borderSize; + } + + textRect = clientRect; + textRect.x += 2.0; + textRect.w -= 2.0; + textRect.y += 2.0; + textRect.h -= 2.0; + + textRect.x += textAlignx; + textRect.y += textAligny; + + } + origin.Set( rect.x() + (rect.w() / 2 ), rect.y() + ( rect.h() / 2 ) ); + +} + +/* +================ +idWindow::SetupBackground +================ +*/ +void idWindow::SetupBackground() { + if (backGroundName.Length()) { + background = declManager->FindMaterial(backGroundName); + if ( background != NULL && !background->TestMaterialFlag( MF_DEFAULTED ) ) { + background->SetSort(SS_GUI ); + } + } + backGroundName.SetMaterialPtr(&background); +} + +/* +================ +idWindow::SetupFromState +================ +*/ +void idWindow::SetupFromState() { + idStr str; + background = NULL; + + SetupBackground(); + + if (borderSize) { + flags |= WIN_BORDER; + } + + if (regList.FindReg("rotate") || regList.FindReg("shear")) { + flags |= WIN_TRANSFORM; + } + + CalcClientRect(0,0); + if ( scripts[ ON_ACTION ] ) { + cursor = idDeviceContext::CURSOR_HAND; + flags |= WIN_CANFOCUS; + } +} + +/* +================ +idWindow::Moved +================ +*/ +void idWindow::Moved() { +} + +/* +================ +idWindow::Sized +================ +*/ +void idWindow::Sized() { +} + +/* +================ +idWindow::GainFocus +================ +*/ +void idWindow::GainFocus() { +} + +/* +================ +idWindow::LoseFocus +================ +*/ +void idWindow::LoseFocus() { +} + +/* +================ +idWindow::GainCapture +================ +*/ +void idWindow::GainCapture() { +} + +/* +================ +idWindow::LoseCapture +================ +*/ +void idWindow::LoseCapture() { + flags &= ~WIN_CAPTURE; +} + +/* +================ +idWindow::SetFlag +================ +*/ +void idWindow::SetFlag(unsigned int f) { + flags |= f; +} + +/* +================ +idWindow::ClearFlag +================ +*/ +void idWindow::ClearFlag(unsigned int f) { + flags &= ~f; +} + + +/* +================ +idWindow::SetParent +================ +*/ +void idWindow::SetParent(idWindow *w) { + parent = w; +} + +/* +================ +idWindow::GetCaptureChild +================ +*/ +idWindow *idWindow::GetCaptureChild() { + if (flags & WIN_DESKTOP) { + return gui->GetDesktop()->captureChild; + } + return NULL; +} + +/* +================ +idWindow::GetFocusedChild +================ +*/ +idWindow *idWindow::GetFocusedChild() { + if (flags & WIN_DESKTOP) { + return gui->GetDesktop()->focusedChild; + } + return NULL; +} + + +/* +================ +idWindow::SetFocus +================ +*/ +idWindow *idWindow::SetFocus(idWindow *w, bool scripts) { + // only one child can have the focus + idWindow *lastFocus = NULL; + if (w->flags & WIN_CANFOCUS) { + lastFocus = gui->GetDesktop()->focusedChild; + if ( lastFocus ) { + lastFocus->flags &= ~WIN_FOCUS; + lastFocus->LoseFocus(); + } + + // call on lose focus + if ( scripts && lastFocus ) { + // calling this broke all sorts of guis + // lastFocus->RunScript(ON_MOUSEEXIT); + } + // call on gain focus + if ( scripts && w ) { + // calling this broke all sorts of guis + // w->RunScript(ON_MOUSEENTER); + } + + w->flags |= WIN_FOCUS; + w->GainFocus(); + gui->GetDesktop()->focusedChild = w; + } + + return lastFocus; +} + +/* +================ +idWindow::ParseScript +================ +*/ +bool idWindow::ParseScript(idTokenParser *src, idGuiScriptList &list, int *timeParm, bool elseBlock ) { + + bool ifElseBlock = false; + + idToken token; + + // scripts start with { ( unless parm is true ) and have ; separated command lists.. commands are command, + // arg.. basically we want everything between the { } as it will be interpreted at + // run time + + if ( elseBlock ) { + src->ReadToken ( &token ); + + if ( !token.Icmp ( "if" ) ) { + ifElseBlock = true; + } + + src->UnreadToken ( &token ); + + if ( !ifElseBlock && !src->ExpectTokenString( "{" ) ) { + return false; + } + } + else if ( !src->ExpectTokenString( "{" ) ) { + return false; + } + + int nest = 0; + + while (1) { + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + + if ( token == "{" ) { + nest++; + } + + if ( token == "}" ) { + if (nest-- <= 0) { + return true; + } + } + + idGuiScript *gs = new (TAG_OLD_UI) idGuiScript(); + if (token.Icmp("if") == 0) { + gs->conditionReg = ParseExpression(src); + gs->ifList = new (TAG_OLD_UI) idGuiScriptList(); + ParseScript(src, *gs->ifList, NULL); + if (src->ReadToken(&token)) { + if (token == "else") { + gs->elseList = new (TAG_OLD_UI) idGuiScriptList(); + // pass true to indicate we are parsing an else condition + ParseScript(src, *gs->elseList, NULL, true ); + } else { + src->UnreadToken(&token); + } + } + + list.Append(gs); + + // if we are parsing an else if then return out so + // the initial "if" parser can handle the rest of the tokens + if ( ifElseBlock ) { + return true; + } + continue; + } else { + src->UnreadToken(&token); + } + + // empty { } is not allowed + if ( token == "{" ) { + src->Error ( "Unexpected {" ); + delete gs; + return false; + } + + gs->Parse(src); + list.Append(gs); + } + +} + +/* +================ +idWindow::SaveExpressionParseState +================ +*/ +void idWindow::SaveExpressionParseState() { + saveTemps = (bool*)Mem_Alloc(MAX_EXPRESSION_REGISTERS * sizeof(bool), TAG_CRAP); + memcpy(saveTemps, registerIsTemporary, MAX_EXPRESSION_REGISTERS * sizeof(bool)); +} + +/* +================ +idWindow::RestoreExpressionParseState +================ +*/ +void idWindow::RestoreExpressionParseState() { + memcpy(registerIsTemporary, saveTemps, MAX_EXPRESSION_REGISTERS * sizeof(bool)); + Mem_Free(saveTemps); +} + +/* +================ +idWindow::ParseScriptEntry +================ +*/ +bool idWindow::ParseScriptEntry(const char *name, idTokenParser *src) { + for (int i = 0; i < SCRIPT_COUNT; i++) { + if (idStr::Icmp(name, ScriptNames[i]) == 0) { + delete scripts[i]; + scripts[i] = new (TAG_OLD_UI) idGuiScriptList; + return ParseScript(src, *scripts[i]); + } + } + return false; +} + +/* +================ +idWindow::DisableRegister +================ +*/ +void idWindow::DisableRegister(const char *_name) { + idRegister *reg = RegList()->FindReg(_name); + if (reg) { + reg->Enable(false); + } +} + +/* +================================ +idSort_TimeLine +================================ +*/ +class idSort_TimeLine : public idSort_Quick< idTimeLineEvent *, idSort_TimeLine > { +public: + int Compare( idTimeLineEvent * const & a, idTimeLineEvent * const & b ) const { + return a->time - b->time; + } +}; + +/* +================ +idWindow::PostParse +================ +*/ +void idWindow::PostParse() { + // Sort timeline events + idSort_TimeLine sorter; + timeLineEvents.SortWithTemplate( sorter ); +} + +/* +================ +idWindow::GetWinVarOffset +================ +*/ +int idWindow::GetWinVarOffset( idWinVar *wv, drawWin_t* owner) { + int ret = -1; + + if ( wv == &rect ) { + ret = (int)&( ( idWindow * ) 0 )->rect; + } + + if ( wv == &backColor ) { + ret = (int)&( ( idWindow * ) 0 )->backColor; + } + + if ( wv == &matColor ) { + ret = (int)&( ( idWindow * ) 0 )->matColor; + } + + if ( wv == &foreColor ) { + ret = (int)&( ( idWindow * ) 0 )->foreColor; + } + + if ( wv == &hoverColor ) { + ret = (int)&( ( idWindow * ) 0 )->hoverColor; + } + + if ( wv == &borderColor ) { + ret = (int)&( ( idWindow * ) 0 )->borderColor; + } + + if ( wv == &textScale ) { + ret = (int)&( ( idWindow * ) 0 )->textScale; + } + + if ( wv == &rotate ) { + ret = (int)&( ( idWindow * ) 0 )->rotate; + } + + if ( ret != -1 ) { + owner->win = this; + return ret; + } + + for ( int i = 0; i < drawWindows.Num(); i++ ) { + if ( drawWindows[i].win ) { + ret = drawWindows[i].win->GetWinVarOffset( wv, owner ); + } else { + ret = drawWindows[i].simp->GetWinVarOffset( wv, owner ); + } + if ( ret != -1 ) { + break; + } + } + + return ret; +} + +/* +================ +idWindow::GetWinVarByName +================ +*/ +idWinVar *idWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + idWinVar *retVar = NULL; + + if ( owner ) { + *owner = NULL; + } + + if (idStr::Icmp(_name, "notime") == 0) { + retVar = &noTime; + } + if (idStr::Icmp(_name, "background") == 0) { + retVar = &backGroundName; + } + if (idStr::Icmp(_name, "visible") == 0) { + retVar = &visible; + } + if (idStr::Icmp(_name, "rect") == 0) { + retVar = ▭ + } + if (idStr::Icmp(_name, "backColor") == 0) { + retVar = &backColor; + } + if (idStr::Icmp(_name, "matColor") == 0) { + retVar = &matColor; + } + if (idStr::Icmp(_name, "foreColor") == 0) { + retVar = &foreColor; + } + if (idStr::Icmp(_name, "hoverColor") == 0) { + retVar = &hoverColor; + } + if (idStr::Icmp(_name, "borderColor") == 0) { + retVar = &borderColor; + } + if (idStr::Icmp(_name, "textScale") == 0) { + retVar = &textScale; + } + if (idStr::Icmp(_name, "rotate") == 0) { + retVar = &rotate; + } + if (idStr::Icmp(_name, "noEvents") == 0) { + retVar = &noEvents; + } + if (idStr::Icmp(_name, "text") == 0) { + retVar = &text; + } + if (idStr::Icmp(_name, "backGroundName") == 0) { + retVar = &backGroundName; + } + if (idStr::Icmp(_name, "hidecursor") == 0) { + retVar = &hideCursor; + } + + idStr key = _name; + bool guiVar = (key.Find(VAR_GUIPREFIX) >= 0); + int c = definedVars.Num(); + for (int i = 0; i < c; i++) { + if (idStr::Icmp(_name, (guiVar) ? va("%s",definedVars[i]->GetName()) : definedVars[i]->GetName()) == 0) { + retVar = definedVars[i]; + break; + } + } + + if (retVar) { + if (fixup && *_name != '$') { + DisableRegister(_name); + } + + if ( owner && parent ) { + *owner = parent->FindChildByName ( name ); + } + + return retVar; + } + + int len = key.Length(); + if ( len > 5 && guiVar ) { + idWinVar *var = new (TAG_OLD_UI) idWinStr; + var->Init(_name, this); + definedVars.Append(var); + return var; + } else if (fixup) { + int n = key.Find("::"); + if (n > 0) { + idStr winName = key.Left(n); + idStr var = key.Right(key.Length() - n - 2); + drawWin_t *win = GetGui()->GetDesktop()->FindChildByName(winName); + if (win) { + if (win->win) { + return win->win->GetWinVarByName(var, false, owner); + } else { + if ( owner ) { + *owner = win; + } + return win->simp->GetWinVarByName(var); + } + } + } + } + return NULL; +} + +/* +================ +idWindow::ParseString +================ +*/ +void idWindow::ParseString(idTokenParser *src, idStr &out) { + idToken tok; + if (src->ReadToken(&tok)) { + out = tok; + } +} + +/* +================ +idWindow::ParseVec4 +================ +*/ +void idWindow::ParseVec4(idTokenParser *src, idVec4 &out) { + idToken tok; + src->ReadToken(&tok); + out.x = atof(tok); + src->ExpectTokenString(","); + src->ReadToken(&tok); + out.y = atof(tok); + src->ExpectTokenString(","); + src->ReadToken(&tok); + out.z = atof(tok); + src->ExpectTokenString(","); + src->ReadToken(&tok); + out.w = atof(tok); +} + +/* +================ +idWindow::ParseInternalVar +================ +*/ +bool idWindow::ParseInternalVar(const char *_name, idTokenParser *src) { + + if (idStr::Icmp(_name, "showtime") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_SHOWTIME; + } + return true; + } + if (idStr::Icmp(_name, "showcoords") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_SHOWCOORDS; + } + return true; + } + if (idStr::Icmp(_name, "forceaspectwidth") == 0) { + forceAspectWidth = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "forceaspectheight") == 0) { + forceAspectHeight = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "matscalex") == 0) { + matScalex = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "matscaley") == 0) { + matScaley = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "bordersize") == 0) { + borderSize = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "nowrap") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NOWRAP; + } + return true; + } + if (idStr::Icmp(_name, "shadow") == 0) { + textShadow = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "textalign") == 0) { + textAlign = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "textalignx") == 0) { + textAlignx = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "textaligny") == 0) { + textAligny = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "shear") == 0) { + shear.x = src->ParseFloat(); + idToken tok; + src->ReadToken( &tok ); + if ( tok.Icmp( "," ) ) { + src->Error( "Expected comma in shear definiation" ); + return false; + } + shear.y = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "wantenter") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_WANTENTER; + } + return true; + } + if (idStr::Icmp(_name, "naturalmatscale") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NATURALMAT; + } + return true; + } + if (idStr::Icmp(_name, "noclip") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NOCLIP; + } + return true; + } + if (idStr::Icmp(_name, "nocursor") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NOCURSOR; + } + return true; + } + if (idStr::Icmp(_name, "menugui") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_MENUGUI; + } + return true; + } + if (idStr::Icmp(_name, "modal") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_MODAL; + } + return true; + } + if (idStr::Icmp(_name, "invertrect") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_INVERTRECT; + } + return true; + } + if (idStr::Icmp(_name, "name") == 0) { + ParseString(src, name); + return true; + } + if (idStr::Icmp(_name, "play") == 0) { + common->Warning( "play encountered during gui parse.. see Robert\n" ); + idStr playStr; + ParseString(src, playStr); + return true; + } + if (idStr::Icmp(_name, "comment") == 0) { + ParseString(src, comment); + return true; + } + if ( idStr::Icmp( _name, "font" ) == 0 ) { + idStr fontName; + ParseString( src, fontName ); + font = renderSystem->RegisterFont( fontName ); + return true; + } + return false; +} + +/* +================ +idWindow::ParseRegEntry +================ +*/ +bool idWindow::ParseRegEntry(const char *name, idTokenParser *src) { + idStr work; + work = name; + work.ToLower(); + + idWinVar *var = GetWinVarByName(work, NULL); + if ( var ) { + for (int i = 0; i < NumRegisterVars; i++) { + if (idStr::Icmp(work, RegisterVars[i].name) == 0) { + regList.AddReg(work, RegisterVars[i].type, src, this, var); + return true; + } + } + } + + // not predefined so just read the next token and add it to the state + idToken tok; + idVec4 v; + idWinInt *vari; + idWinFloat *varf; + idWinStr *vars; + if (src->ReadToken(&tok)) { + if (var) { + var->Set(tok); + return true; + } + switch (tok.type) { + case TT_NUMBER : + if (tok.subtype & TT_INTEGER) { + vari = new (TAG_OLD_UI) idWinInt(); + *vari = atoi(tok); + vari->SetName(work); + definedVars.Append(vari); + } else if (tok.subtype & TT_FLOAT) { + varf = new (TAG_OLD_UI) idWinFloat(); + *varf = atof(tok); + varf->SetName(work); + definedVars.Append(varf); + } else { + vars = new (TAG_OLD_UI) idWinStr(); + *vars = tok; + vars->SetName(work); + definedVars.Append(vars); + } + break; + default : + vars = new (TAG_OLD_UI) idWinStr(); + *vars = tok; + vars->SetName(work); + definedVars.Append(vars); + break; + } + } + + return true; +} + +/* +================ +idWindow::SetInitialState +================ +*/ +void idWindow::SetInitialState(const char *_name) { + name = _name; + matScalex = 1.0; + matScaley = 1.0; + forceAspectWidth = 640.0; + forceAspectHeight = 480.0; + noTime = false; + visible = true; + flags = 0; +} + +/* +================ +idWindow::Parse +================ +*/ +bool idWindow::Parse( idTokenParser *src, bool rebuild) { + idToken token, token2, token3, token4, token5, token6, token7; + idStr work; + + if (rebuild) { + CleanUp(); + } + + drawWin_t dwt; + + timeLineEvents.Clear(); + transitions.Clear(); + + namedEvents.DeleteContents( true ); + + src->ExpectTokenType( TT_NAME, 0, &token ); + + SetInitialState(token); + + src->ExpectTokenString( "{" ); + src->ExpectAnyToken( &token ); + + bool ret = true; + + while( token != "}" ) { + // track what was parsed so we can maintain it for the guieditor + src->SetMarker ( ); + + if ( token == "windowDef" || token == "animationDef" ) { + if (token == "animationDef") { + visible = false; + rect = idRectangle(0,0,0,0); + } + src->ExpectTokenType( TT_NAME, 0, &token ); + token2 = token; + src->UnreadToken(&token); + drawWin_t *dw = FindChildByName(token2.c_str()); + if (dw != NULL && dw->win != NULL) { + SaveExpressionParseState(); + dw->win->Parse(src, rebuild); + RestoreExpressionParseState(); + } else { + idWindow *win = new (TAG_OLD_UI) idWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = NULL; + if (win->IsSimple()) { + idSimpleWindow *simple = new (TAG_OLD_UI) idSimpleWindow(win); + dwt.simp = simple; + drawWindows.Append(dwt); + delete win; + } else { + AddChild(win); + SetFocus(win,false); + dwt.win = win; + drawWindows.Append(dwt); + } + } + } + else if ( token == "editDef" ) { + idEditWindow *win = new (TAG_OLD_UI) idEditWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "choiceDef" ) { + idChoiceWindow *win = new (TAG_OLD_UI) idChoiceWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "sliderDef" ) { + idSliderWindow *win = new (TAG_OLD_UI) idSliderWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "bindDef" ) { + idBindWindow *win = new (TAG_OLD_UI) idBindWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "listDef" ) { + idListWindow *win = new (TAG_OLD_UI) idListWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "fieldDef" ) { + idFieldWindow *win = new (TAG_OLD_UI) idFieldWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "renderDef" ) { + // D3 could render a 3D model in a subrect of a full screen + // GUI for the main menus, but we have cut that ability so + // we don't need to deal with offset viewports on all platforms. + idRenderWindow *win = new (TAG_OLD_UI) idRenderWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "gameSSDDef" ) { + idGameSSDWindow *win = new (TAG_OLD_UI) idGameSSDWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "gameBearShootDef" ) { + idGameBearShootWindow *win = new (TAG_OLD_UI) idGameBearShootWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "gameBustOutDef" ) { + idGameBustOutWindow *win = new (TAG_OLD_UI) idGameBustOutWindow(gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } +// +// added new onEvent + else if ( token == "onNamedEvent" ) { + // Read the event name + if ( !src->ReadToken(&token) ) { + src->Error( "Expected event name" ); + return false; + } + + rvNamedEvent* ev = new (TAG_OLD_UI) rvNamedEvent ( token ); + + src->SetMarker ( ); + + if ( !ParseScript ( src, *ev->mEvent ) ) { + ret = false; + break; + } + + namedEvents.Append(ev); + } + else if ( token == "onTime" ) { + idTimeLineEvent *ev = new (TAG_OLD_UI) idTimeLineEvent; + + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + ev->time = atoi(token.c_str()); + + // reset the mark since we dont want it to include the time + src->SetMarker ( ); + + if (!ParseScript(src, *ev->event, &ev->time)) { + ret = false; + break; + } + + // this is a timeline event + ev->pending = true; + timeLineEvents.Append(ev); + } + else if ( token == "definefloat" ) { + src->ReadToken(&token); + work = token; + work.ToLower(); + idWinFloat *varf = new (TAG_OLD_UI) idWinFloat(); + varf->SetName(work); + definedVars.Append(varf); + + // add the float to the editors wrapper dict + // Set the marker after the float name + src->SetMarker ( ); + + // Read in the float + regList.AddReg(work, idRegister::FLOAT, src, this, varf); + } + else if ( token == "definevec4" ) { + src->ReadToken(&token); + work = token; + work.ToLower(); + idWinVec4 *var = new (TAG_OLD_UI) idWinVec4(); + var->SetName(work); + + // set the marker so we can determine what was parsed + // set the marker after the vec4 name + src->SetMarker ( ); + + // FIXME: how about we add the var to the desktop instead of this window so it won't get deleted + // when this window is destoyed which even happens during parsing with simple windows ? + //definedVars.Append(var); + gui->GetDesktop()->definedVars.Append( var ); + gui->GetDesktop()->regList.AddReg( work, idRegister::VEC4, src, gui->GetDesktop(), var ); + } + else if ( token == "float" ) { + src->ReadToken(&token); + work = token; + work.ToLower(); + idWinFloat *varf = new (TAG_OLD_UI) idWinFloat(); + varf->SetName(work); + definedVars.Append(varf); + + // add the float to the editors wrapper dict + // set the marker to after the float name + src->SetMarker ( ); + + // Parse the float + regList.AddReg(work, idRegister::FLOAT, src, this, varf); + } + else if (ParseScriptEntry(token, src)) { + + } else if (ParseInternalVar(token, src)) { + + } + else { + ParseRegEntry(token, src); + } + if ( !src->ReadToken( &token ) ) { + src->Error( "Unexpected end of file" ); + ret = false; + break; + } + } + + if (ret) { + EvalRegs(-1, true); + } + + SetupFromState(); + PostParse(); + + return ret; +} + +/* +================ +idWindow::FindSimpleWinByName +================ +*/ +idSimpleWindow *idWindow::FindSimpleWinByName(const char *_name) { + int c = drawWindows.Num(); + for (int i = 0; i < c; i++) { + if (drawWindows[i].simp == NULL) { + continue; + } + if ( idStr::Icmp(drawWindows[i].simp->name, _name) == 0 ) { + return drawWindows[i].simp; + } + } + return NULL; +} + +/* +================ +idWindow::FindChildByName +================ +*/ +drawWin_t *idWindow::FindChildByName(const char *_name) { + static drawWin_t dw; + if (idStr::Icmp(name,_name) == 0) { + dw.simp = NULL; + dw.win = this; + return &dw; + } + int c = drawWindows.Num(); + for (int i = 0; i < c; i++) { + if (drawWindows[i].win) { + if (idStr::Icmp(drawWindows[i].win->name, _name) == 0) { + return &drawWindows[i]; + } + drawWin_t *win = drawWindows[i].win->FindChildByName(_name); + if (win) { + return win; + } + } else { + if (idStr::Icmp(drawWindows[i].simp->name, _name) == 0) { + return &drawWindows[i]; + } + } + } + return NULL; +} + +/* +================ +idWindow::GetStrPtrByName +================ +*/ +idStr* idWindow::GetStrPtrByName(const char *_name) { + return NULL; +} + +/* +================ +idWindow::AddTransition +================ +*/ +void idWindow::AddTransition(idWinVar *dest, idVec4 from, idVec4 to, int time, float accelTime, float decelTime) { + idTransitionData data; + data.data = dest; + data.interp.Init(gui->GetTime(), accelTime * time, decelTime * time, time, from, to); + transitions.Append(data); +} + + +/* +================ +idWindow::StartTransition +================ +*/ +void idWindow::StartTransition() { + flags |= WIN_INTRANSITION; +} + +/* +================ +idWindow::ResetCinematics +================ +*/ +void idWindow::ResetCinematics() { + if ( background ) { + background->ResetCinematicTime( gui->GetTime() ); + } +} + +/* +================ +idWindow::ResetTime +================ +*/ +void idWindow::ResetTime(int t) { + + timeLine = gui->GetTime() - t; + + int i, c = timeLineEvents.Num(); + for ( i = 0; i < c; i++ ) { + if ( timeLineEvents[i]->time >= t ) { + timeLineEvents[i]->pending = true; + } + } + + noTime = false; + + c = transitions.Num(); + for ( i = 0; i < c; i++ ) { + idTransitionData *data = &transitions[i]; + if ( data->interp.IsDone( gui->GetTime() ) && data->data ) { + transitions.RemoveIndex( i ); + i--; + c--; + } + } + +} + + +/* +================ +idWindow::RunScriptList +================ +*/ +bool idWindow::RunScriptList(idGuiScriptList *src) { + if (src == NULL) { + return false; + } + src->Execute(this); + return true; +} + +/* +================ +idWindow::RunScript +================ +*/ +bool idWindow::RunScript(int n) { + if (n >= ON_MOUSEENTER && n < SCRIPT_COUNT) { + return RunScriptList(scripts[n]); + } + return false; +} + +/* +================ +idWindow::ExpressionConstant +================ +*/ +int idWindow::ExpressionConstant(float f) { + int i; + + for ( i = WEXP_REG_NUM_PREDEFINED ; i < expressionRegisters.Num() ; i++ ) { + if ( !registerIsTemporary[i] && expressionRegisters[i] == f ) { + return i; + } + } + if ( expressionRegisters.Num() == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "expressionConstant: gui %s hit MAX_EXPRESSION_REGISTERS", gui->GetSourceFile() ); + return 0; + } + + int c = expressionRegisters.Num(); + if (i > c) { + while (i > c) { + expressionRegisters.Append(-9999999); + i--; + } + } + + i = expressionRegisters.Append(f); + registerIsTemporary[i] = false; + return i; +} + +/* +================ +idWindow::ExpressionTemporary +================ +*/ +int idWindow::ExpressionTemporary() { + if ( expressionRegisters.Num() == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "expressionTemporary: gui %s hit MAX_EXPRESSION_REGISTERS", gui->GetSourceFile()); + return 0; + } + int i = expressionRegisters.Num(); + registerIsTemporary[i] = true; + i = expressionRegisters.Append(0); + return i; +} + +/* +================ +idWindow::ExpressionOp +================ +*/ +wexpOp_t *idWindow::ExpressionOp() { + if ( ops.Num() == MAX_EXPRESSION_OPS ) { + common->Warning( "expressionOp: gui %s hit MAX_EXPRESSION_OPS", gui->GetSourceFile()); + return &ops[0]; + } + wexpOp_t wop; + memset(&wop, 0, sizeof(wexpOp_t)); + int i = ops.Append(wop); + return &ops[i]; +} + +/* +================ +idWindow::EmitOp +================ +*/ + +int idWindow::EmitOp( int a, int b, wexpOpType_t opType, wexpOp_t **opp ) { + wexpOp_t *op; +/* + // optimize away identity operations + if ( opType == WOP_TYPE_ADD ) { + if ( !registerIsTemporary[a] && shaderRegisters[a] == 0 ) { + return b; + } + if ( !registerIsTemporary[b] && shaderRegisters[b] == 0 ) { + return a; + } + if ( !registerIsTemporary[a] && !registerIsTemporary[b] ) { + return ExpressionConstant( shaderRegisters[a] + shaderRegisters[b] ); + } + } + if ( opType == WOP_TYPE_MULTIPLY ) { + if ( !registerIsTemporary[a] && shaderRegisters[a] == 1 ) { + return b; + } + if ( !registerIsTemporary[a] && shaderRegisters[a] == 0 ) { + return a; + } + if ( !registerIsTemporary[b] && shaderRegisters[b] == 1 ) { + return a; + } + if ( !registerIsTemporary[b] && shaderRegisters[b] == 0 ) { + return b; + } + if ( !registerIsTemporary[a] && !registerIsTemporary[b] ) { + return ExpressionConstant( shaderRegisters[a] * shaderRegisters[b] ); + } + } +*/ + op = ExpressionOp(); + + op->opType = opType; + op->a = a; + op->b = b; + op->c = ExpressionTemporary(); + + if (opp) { + *opp = op; + } + return op->c; +} + +/* +================ +idWindow::ParseEmitOp +================ +*/ +int idWindow::ParseEmitOp( idTokenParser *src, int a, wexpOpType_t opType, int priority, wexpOp_t **opp ) { + int b = ParseExpressionPriority( src, priority ); + return EmitOp( a, b, opType, opp ); +} + + +/* +================ +idWindow::ParseTerm + +Returns a register index +================= +*/ +int idWindow::ParseTerm( idTokenParser *src, idWinVar *var, int component ) { + idToken token; + int a, b; + + src->ReadToken( &token ); + + if ( token == "(" ) { + a = ParseExpression( src ); + src->ExpectTokenString(")"); + return a; + } + + if ( !token.Icmp( "time" ) ) { + return WEXP_REG_TIME; + } + + // parse negative numbers + if ( token == "-" ) { + src->ReadToken( &token ); + if ( token.type == TT_NUMBER || token == "." ) { + return ExpressionConstant( -(float) token.GetFloatValue() ); + } + src->Warning( "Bad negative number '%s'", token.c_str() ); + return 0; + } + + if ( token.type == TT_NUMBER || token == "." || token == "-" ) { + return ExpressionConstant( (float) token.GetFloatValue() ); + } + + // see if it is a table name + const idDeclTable *table = static_cast( declManager->FindType( DECL_TABLE, token.c_str(), false ) ); + if ( table ) { + a = table->Index(); + // parse a table expression + src->ExpectTokenString("["); + b = ParseExpression(src); + src->ExpectTokenString("]"); + return EmitOp( a, b, WOP_TYPE_TABLE ); + } + + if (var == NULL) { + var = GetWinVarByName(token, true); + } + if (var) { + a = (int)var; + //assert(dynamic_cast(var)); + var->Init(token, this); + b = component; + if (dynamic_cast(var)) { + if (src->ReadToken(&token)) { + if (token == "[") { + b = ParseExpression(src); + src->ExpectTokenString("]"); + } else { + src->UnreadToken(&token); + } + } + return EmitOp(a, b, WOP_TYPE_VAR); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARF); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARI); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARB); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARS); + } else { + src->Warning("Var expression not vec4, float or int '%s'", token.c_str()); + } + return 0; + } else { + // ugly but used for post parsing to fixup named vars + char *p = new (TAG_OLD_UI) char[token.Length()+1]; + strcpy(p, token); + a = (int)p; + b = -2; + return EmitOp(a, b, WOP_TYPE_VAR); + } + +} + +/* +================= +idWindow::ParseExpressionPriority + +Returns a register index +================= +*/ +#define TOP_PRIORITY 4 +int idWindow::ParseExpressionPriority( idTokenParser *src, int priority, idWinVar *var, int component ) { + idToken token; + int a; + + if ( priority == 0 ) { + return ParseTerm( src, var, component ); + } + + a = ParseExpressionPriority( src, priority - 1, var, component ); + + if ( !src->ReadToken( &token ) ) { + // we won't get EOF in a real file, but we can + // when parsing from generated strings + return a; + } + + if ( priority == 1 && token == "*" ) { + return ParseEmitOp( src, a, WOP_TYPE_MULTIPLY, priority ); + } + if ( priority == 1 && token == "/" ) { + return ParseEmitOp( src, a, WOP_TYPE_DIVIDE, priority ); + } + if ( priority == 1 && token == "%" ) { // implied truncate both to integer + return ParseEmitOp( src, a, WOP_TYPE_MOD, priority ); + } + if ( priority == 2 && token == "+" ) { + return ParseEmitOp( src, a, WOP_TYPE_ADD, priority ); + } + if ( priority == 2 && token == "-" ) { + return ParseEmitOp( src, a, WOP_TYPE_SUBTRACT, priority ); + } + if ( priority == 3 && token == ">" ) { + return ParseEmitOp( src, a, WOP_TYPE_GT, priority ); + } + if ( priority == 3 && token == ">=" ) { + return ParseEmitOp( src, a, WOP_TYPE_GE, priority ); + } + if ( priority == 3 && token == "<" ) { + return ParseEmitOp( src, a, WOP_TYPE_LT, priority ); + } + if ( priority == 3 && token == "<=" ) { + return ParseEmitOp( src, a, WOP_TYPE_LE, priority ); + } + if ( priority == 3 && token == "==" ) { + return ParseEmitOp( src, a, WOP_TYPE_EQ, priority ); + } + if ( priority == 3 && token == "!=" ) { + return ParseEmitOp( src, a, WOP_TYPE_NE, priority ); + } + if ( priority == 4 && token == "&&" ) { + return ParseEmitOp( src, a, WOP_TYPE_AND, priority ); + } + if ( priority == 4 && token == "||" ) { + return ParseEmitOp( src, a, WOP_TYPE_OR, priority ); + } + if ( priority == 4 && token == "?" ) { + wexpOp_t *oop = NULL; + int o = ParseEmitOp( src, a, WOP_TYPE_COND, priority, &oop ); + if ( !src->ReadToken( &token ) ) { + return o; + } + if (token == ":") { + a = ParseExpressionPriority( src, priority - 1, var ); + oop->d = a; + } + return o; + } + + // assume that anything else terminates the expression + // not too robust error checking... + + src->UnreadToken( &token ); + + return a; +} + +/* +================ +idWindow::ParseExpression + +Returns a register index +================ +*/ +int idWindow::ParseExpression(idTokenParser *src, idWinVar *var, int component) { + return ParseExpressionPriority( src, TOP_PRIORITY, var ); +} + +/* +================ +idWindow::ParseBracedExpression +================ +*/ +void idWindow::ParseBracedExpression(idTokenParser *src) { + src->ExpectTokenString("{"); + ParseExpression(src); + src->ExpectTokenString("}"); +} + +/* +=============== +idWindow::EvaluateRegisters + +Parameters are taken from the localSpace and the renderView, +then all expressions are evaluated, leaving the shader registers +set to their apropriate values. +=============== +*/ +void idWindow::EvaluateRegisters(float *registers) { + int i, b; + wexpOp_t *op; + idVec4 v; + + int erc = expressionRegisters.Num(); + int oc = ops.Num(); + // copy the constants + for ( i = WEXP_REG_NUM_PREDEFINED ; i < erc ; i++ ) { + registers[i] = expressionRegisters[i]; + } + + // copy the local and global parameters + registers[WEXP_REG_TIME] = gui->GetTime(); + + for ( i = 0 ; i < oc ; i++ ) { + op = &ops[i]; + if (op->b == -2) { + continue; + } + switch( op->opType ) { + case WOP_TYPE_ADD: + registers[op->c] = registers[op->a] + registers[op->b]; + break; + case WOP_TYPE_SUBTRACT: + registers[op->c] = registers[op->a] - registers[op->b]; + break; + case WOP_TYPE_MULTIPLY: + registers[op->c] = registers[op->a] * registers[op->b]; + break; + case WOP_TYPE_DIVIDE: + if ( registers[op->b] == 0.0f ) { + common->Warning( "Divide by zero in window '%s' in %s", GetName(), gui->GetSourceFile() ); + registers[op->c] = registers[op->a]; + } else { + registers[op->c] = registers[op->a] / registers[op->b]; + } + break; + case WOP_TYPE_MOD: + b = (int)registers[op->b]; + b = b != 0 ? b : 1; + registers[op->c] = (int)registers[op->a] % b; + break; + case WOP_TYPE_TABLE: + { + const idDeclTable *table = static_cast( declManager->DeclByIndex( DECL_TABLE, op->a ) ); + registers[op->c] = table->TableLookup( registers[op->b] ); + } + break; + case WOP_TYPE_GT: + registers[op->c] = registers[ op->a ] > registers[op->b]; + break; + case WOP_TYPE_GE: + registers[op->c] = registers[ op->a ] >= registers[op->b]; + break; + case WOP_TYPE_LT: + registers[op->c] = registers[ op->a ] < registers[op->b]; + break; + case WOP_TYPE_LE: + registers[op->c] = registers[ op->a ] <= registers[op->b]; + break; + case WOP_TYPE_EQ: + registers[op->c] = registers[ op->a ] == registers[op->b]; + break; + case WOP_TYPE_NE: + registers[op->c] = registers[ op->a ] != registers[op->b]; + break; + case WOP_TYPE_COND: + registers[op->c] = (registers[ op->a ]) ? registers[op->b] : registers[op->d]; + break; + case WOP_TYPE_AND: + registers[op->c] = registers[ op->a ] && registers[op->b]; + break; + case WOP_TYPE_OR: + registers[op->c] = registers[ op->a ] || registers[op->b]; + break; + case WOP_TYPE_VAR: + if ( !op->a ) { + registers[op->c] = 0.0f; + break; + } + if ( op->b >= 0 && registers[op->b] >= 0 && registers[op->b] < 4 ) { + // grabs vector components + idWinVec4 *var = (idWinVec4 *)( op->a ); + registers[op->c] = ((idVec4&)var)[registers[op->b]]; + } else { + registers[op->c] = ((idWinVar*)(op->a))->x(); + } + break; + case WOP_TYPE_VARS: + if (op->a) { + idWinStr *var = (idWinStr*)(op->a); + registers[op->c] = atof(var->c_str()); + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARF: + if (op->a) { + idWinFloat *var = (idWinFloat*)(op->a); + registers[op->c] = *var; + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARI: + if (op->a) { + idWinInt *var = (idWinInt*)(op->a); + registers[op->c] = *var; + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARB: + if (op->a) { + idWinBool *var = (idWinBool*)(op->a); + registers[op->c] = *var; + } else { + registers[op->c] = 0; + } + break; + default: + common->FatalError( "R_EvaluateExpression: bad opcode" ); + } + } + +} + +/* +================ +idWindow::ReadFromDemoFile +================ +*/ +void idWindow::ReadFromDemoFile( class idDemoFile *f, bool rebuild ) { + + // should never hit unless we re-enable WRITE_GUIS +#ifndef WRITE_GUIS + assert( false ); +#else + + if (rebuild) { + CommonInit(); + } + + f->SetLog(true, "window1"); + backGroundName = f->ReadHashString(); + f->SetLog(true, backGroundName); + if ( backGroundName[0] ) { + background = declManager->FindMaterial(backGroundName); + } else { + background = NULL; + } + f->ReadUnsignedChar( cursor ); + f->ReadUnsignedInt( flags ); + f->ReadInt( timeLine ); + f->ReadInt( lastTimeRun ); + idRectangle rct = rect; + f->ReadFloat( rct.x ); + f->ReadFloat( rct.y ); + f->ReadFloat( rct.w ); + f->ReadFloat( rct.h ); + f->ReadFloat( drawRect.x ); + f->ReadFloat( drawRect.y ); + f->ReadFloat( drawRect.w ); + f->ReadFloat( drawRect.h ); + f->ReadFloat( clientRect.x ); + f->ReadFloat( clientRect.y ); + f->ReadFloat( clientRect.w ); + f->ReadFloat( clientRect.h ); + f->ReadFloat( textRect.x ); + f->ReadFloat( textRect.y ); + f->ReadFloat( textRect.w ); + f->ReadFloat( textRect.h ); + f->ReadFloat( xOffset); + f->ReadFloat( yOffset); + int i, c; + + idStr work; + if (rebuild) { + f->SetLog(true, (work + "-scripts")); + for (i = 0; i < SCRIPT_COUNT; i++) { + bool b; + f->ReadBool( b ); + if (b) { + delete scripts[i]; + scripts[i] = new (TAG_OLD_UI) idGuiScriptList; + scripts[i]->ReadFromDemoFile(f); + } + } + + f->SetLog(true, (work + "-timelines")); + f->ReadInt( c ); + for (i = 0; i < c; i++) { + idTimeLineEvent *tl = new (TAG_OLD_UI) idTimeLineEvent; + f->ReadInt( tl->time ); + f->ReadBool( tl->pending ); + tl->event->ReadFromDemoFile(f); + if (rebuild) { + timeLineEvents.Append(tl); + } else { + assert(i < timeLineEvents.Num()); + timeLineEvents[i]->time = tl->time; + timeLineEvents[i]->pending = tl->pending; + } + } + } + + f->SetLog(true, (work + "-transitions")); + f->ReadInt( c ); + for (i = 0; i < c; i++) { + idTransitionData td; + td.data = NULL; + f->ReadInt ( td.offset ); + + float startTime, accelTime, linearTime, decelTime; + idVec4 startValue, endValue; + f->ReadFloat( startTime ); + f->ReadFloat( accelTime ); + f->ReadFloat( linearTime ); + f->ReadFloat( decelTime ); + f->ReadVec4( startValue ); + f->ReadVec4( endValue ); + td.interp.Init( startTime, accelTime, decelTime, accelTime + linearTime + decelTime, startValue, endValue ); + + // read this for correct data padding with the win32 savegames + // the extrapolate is correctly initialized through the above Init call + int extrapolationType; + float duration; + idVec4 baseSpeed, speed; + float currentTime; + idVec4 currentValue; + f->ReadInt( extrapolationType ); + f->ReadFloat( startTime ); + f->ReadFloat( duration ); + f->ReadVec4( startValue ); + f->ReadVec4( baseSpeed ); + f->ReadVec4( speed ); + f->ReadFloat( currentTime ); + f->ReadVec4( currentValue ); + + transitions.Append(td); + } + + f->SetLog(true, (work + "-regstuff")); + if (rebuild) { + f->ReadInt( c ); + for (i = 0; i < c; i++) { + wexpOp_t w; + f->ReadInt( (int&)w.opType ); + f->ReadInt( w.a ); + f->ReadInt( w.b ); + f->ReadInt( w.c ); + f->ReadInt( w.d ); + ops.Append(w); + } + + f->ReadInt( c ); + for (i = 0; i < c; i++) { + float ff; + f->ReadFloat( ff ); + expressionRegisters.Append(ff); + } + + regList.ReadFromDemoFile(f); + + } + f->SetLog(true, (work + "-children")); + f->ReadInt( c ); + for (i = 0; i < c; i++) { + if (rebuild) { + idWindow *win = new (TAG_OLD_UI) idWindow(dc, gui); + win->ReadFromDemoFile(f); + AddChild(win); + } else { + for (int j = 0; j < c; j++) { + if (children[j]->childID == i) { + children[j]->ReadFromDemoFile(f,rebuild); + break; + } else { + continue; + } + } + } + } +#endif /* WRITE_GUIS */ +} + +/* +================ +idWindow::WriteToDemoFile +================ +*/ +void idWindow::WriteToDemoFile( class idDemoFile *f ) { + // should never hit unless we re-enable WRITE_GUIS +#ifndef WRITE_GUIS + assert( false ); +#else + + f->SetLog(true, "window"); + f->WriteHashString(backGroundName); + f->SetLog(true, backGroundName); + f->WriteUnsignedChar( cursor ); + f->WriteUnsignedInt( flags ); + f->WriteInt( timeLine ); + f->WriteInt( lastTimeRun ); + idRectangle rct = rect; + f->WriteFloat( rct.x ); + f->WriteFloat( rct.y ); + f->WriteFloat( rct.w ); + f->WriteFloat( rct.h ); + f->WriteFloat( drawRect.x ); + f->WriteFloat( drawRect.y ); + f->WriteFloat( drawRect.w ); + f->WriteFloat( drawRect.h ); + f->WriteFloat( clientRect.x ); + f->WriteFloat( clientRect.y ); + f->WriteFloat( clientRect.w ); + f->WriteFloat( clientRect.h ); + f->WriteFloat( textRect.x ); + f->WriteFloat( textRect.y ); + f->WriteFloat( textRect.w ); + f->WriteFloat( textRect.h ); + f->WriteFloat( xOffset ); + f->WriteFloat( yOffset ); + idStr work; + f->SetLog(true, work); + + int i, c; + + f->SetLog(true, (work + "-transitions")); + c = transitions.Num(); + f->WriteInt( c ); + for (i = 0; i < c; i++) { + f->WriteInt( 0 ); + f->WriteInt( transitions[i].offset ); + + f->WriteFloat( transitions[i].interp.GetStartTime() ); + f->WriteFloat( transitions[i].interp.GetAccelTime() ); + f->WriteFloat( transitions[i].interp.GetLinearTime() ); + f->WriteFloat( transitions[i].interp.GetDecelTime() ); + f->WriteVec4( transitions[i].interp.GetStartValue() ); + f->WriteVec4( transitions[i].interp.GetEndValue() ); + + // write to keep win32 render demo format compatiblity - we don't actually read them back anymore + f->WriteInt( transitions[i].interp.GetExtrapolate()->GetExtrapolationType() ); + f->WriteFloat( transitions[i].interp.GetExtrapolate()->GetStartTime() ); + f->WriteFloat( transitions[i].interp.GetExtrapolate()->GetDuration() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetStartValue() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetBaseSpeed() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetSpeed() ); + f->WriteFloat( transitions[i].interp.GetExtrapolate()->GetCurrentTime() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetCurrentValue() ); + } + + f->SetLog(true, (work + "-regstuff")); + + f->SetLog(true, (work + "-children")); + c = children.Num(); + f->WriteInt( c ); + for (i = 0; i < c; i++) { + for (int j = 0; j < c; j++) { + if (children[j]->childID == i) { + children[j]->WriteToDemoFile(f); + break; + } else { + continue; + } + } + } +#endif /* WRITE_GUIS */ +} + +/* +=============== +idWindow::WriteString +=============== +*/ +void idWindow::WriteSaveGameString( const char *string, idFile *savefile ) { + int len = strlen( string ); + + savefile->Write( &len, sizeof( len ) ); + savefile->Write( string, len ); +} + +/* +=============== +idWindow::WriteSaveGameTransition +=============== +*/ +void idWindow::WriteSaveGameTransition( idTransitionData &trans, idFile *savefile ) { + drawWin_t dw, *fdw; + idStr winName(""); + dw.simp = NULL; + dw.win = NULL; + int offset = gui->GetDesktop()->GetWinVarOffset( trans.data, &dw ); + if ( dw.win || dw.simp ) { + winName = ( dw.win ) ? dw.win->GetName() : dw.simp->name.c_str(); + } + fdw = gui->GetDesktop()->FindChildByName( winName ); + if ( offset != -1 && fdw != NULL && ( fdw->win != NULL || fdw->simp != NULL ) ) { + savefile->Write( &offset, sizeof( offset ) ); + WriteSaveGameString( winName, savefile ); + savefile->Write( &trans.interp, sizeof( trans.interp ) ); + } else { + offset = -1; + savefile->Write( &offset, sizeof( offset ) ); + } +} + +/* +=============== +idWindow::ReadSaveGameTransition +=============== +*/ +void idWindow::ReadSaveGameTransition( idTransitionData &trans, idFile *savefile ) { + int offset; + + savefile->Read( &offset, sizeof( offset ) ); + if ( offset != -1 ) { + idStr winName; + ReadSaveGameString( winName, savefile ); + savefile->Read( &trans.interp, sizeof( trans.interp ) ); + trans.data = NULL; + trans.offset = offset; + if ( winName.Length() ) { + idWinStr *strVar = new (TAG_OLD_UI) idWinStr(); + strVar->Set( winName ); + trans.data = dynamic_cast< idWinVar* >( strVar ); + } + } +} + +/* +=============== +idWindow::WriteToSaveGame +=============== +*/ +void idWindow::WriteToSaveGame( idFile *savefile ) { + int i; + + WriteSaveGameString( cmd, savefile ); + + savefile->Write( &actualX, sizeof( actualX ) ); + savefile->Write( &actualY, sizeof( actualY ) ); + savefile->Write( &childID, sizeof( childID ) ); + savefile->Write( &flags, sizeof( flags ) ); + savefile->Write( &lastTimeRun, sizeof( lastTimeRun ) ); + savefile->Write( &drawRect, sizeof( drawRect ) ); + savefile->Write( &clientRect, sizeof( clientRect ) ); + savefile->Write( &origin, sizeof( origin ) ); + savefile->Write( &timeLine, sizeof( timeLine ) ); + savefile->Write( &xOffset, sizeof( xOffset ) ); + savefile->Write( &yOffset, sizeof( yOffset ) ); + savefile->Write( &cursor, sizeof( cursor ) ); + savefile->Write( &forceAspectWidth, sizeof( forceAspectWidth ) ); + savefile->Write( &forceAspectHeight, sizeof( forceAspectHeight ) ); + savefile->Write( &matScalex, sizeof( matScalex ) ); + savefile->Write( &matScaley, sizeof( matScaley ) ); + savefile->Write( &borderSize, sizeof( borderSize ) ); + savefile->Write( &textAlign, sizeof( textAlign ) ); + savefile->Write( &textAlignx, sizeof( textAlignx ) ); + savefile->Write( &textAligny, sizeof( textAligny ) ); + savefile->Write( &textShadow, sizeof( textShadow ) ); + savefile->Write( &shear, sizeof( shear ) ); + + savefile->WriteString( font->GetName() ); + + WriteSaveGameString( name, savefile ); + WriteSaveGameString( comment, savefile ); + + // WinVars + noTime.WriteToSaveGame( savefile ); + visible.WriteToSaveGame( savefile ); + rect.WriteToSaveGame( savefile ); + backColor.WriteToSaveGame( savefile ); + matColor.WriteToSaveGame( savefile ); + foreColor.WriteToSaveGame( savefile ); + hoverColor.WriteToSaveGame( savefile ); + borderColor.WriteToSaveGame( savefile ); + textScale.WriteToSaveGame( savefile ); + noEvents.WriteToSaveGame( savefile ); + rotate.WriteToSaveGame( savefile ); + text.WriteToSaveGame( savefile ); + backGroundName.WriteToSaveGame( savefile ); + hideCursor.WriteToSaveGame(savefile); + + // Defined Vars + for ( i = 0; i < definedVars.Num(); i++ ) { + definedVars[i]->WriteToSaveGame( savefile ); + } + + savefile->Write( &textRect, sizeof( textRect ) ); + + // Window pointers saved as the child ID of the window + int winID; + + winID = focusedChild ? focusedChild->childID : -1 ; + savefile->Write( &winID, sizeof( winID ) ); + + winID = captureChild ? captureChild->childID : -1 ; + savefile->Write( &winID, sizeof( winID ) ); + + winID = overChild ? overChild->childID : -1 ; + savefile->Write( &winID, sizeof( winID ) ); + + + // Scripts + for ( i = 0; i < SCRIPT_COUNT; i++ ) { + if ( scripts[i] ) { + scripts[i]->WriteToSaveGame( savefile ); + } + } + + // TimeLine Events + for ( i = 0; i < timeLineEvents.Num(); i++ ) { + if ( timeLineEvents[i] ) { + savefile->Write( &timeLineEvents[i]->pending, sizeof( timeLineEvents[i]->pending ) ); + savefile->Write( &timeLineEvents[i]->time, sizeof( timeLineEvents[i]->time ) ); + if ( timeLineEvents[i]->event ) { + timeLineEvents[i]->event->WriteToSaveGame( savefile ); + } + } + } + + // Transitions + int num = transitions.Num(); + + savefile->Write( &num, sizeof( num ) ); + for ( i = 0; i < transitions.Num(); i++ ) { + WriteSaveGameTransition( transitions[ i ], savefile ); + } + + + // Named Events + for ( i = 0; i < namedEvents.Num(); i++ ) { + if ( namedEvents[i] ) { + WriteSaveGameString( namedEvents[i]->mName, savefile ); + if ( namedEvents[i]->mEvent ) { + namedEvents[i]->mEvent->WriteToSaveGame( savefile ); + } + } + } + + // regList + regList.WriteToSaveGame( savefile ); + + if ( background ) { + savefile->WriteInt( background->GetCinematicStartTime() ); + } else { + savefile->WriteInt( -1 ); + } + + // Save children + for ( i = 0; i < drawWindows.Num(); i++ ) { + drawWin_t window = drawWindows[i]; + + if ( window.simp ) { + window.simp->WriteToSaveGame( savefile ); + } else if ( window.win ) { + window.win->WriteToSaveGame( savefile ); + } + } +} + +/* +=============== +idWindow::ReadSaveGameString +=============== +*/ +void idWindow::ReadSaveGameString( idStr &string, idFile *savefile ) { + int len; + + savefile->Read( &len, sizeof( len ) ); + if ( len < 0 ) { + common->Warning( "idWindow::ReadSaveGameString: invalid length" ); + } + + string.Fill( ' ', len ); + savefile->Read( &string[0], len ); +} + +/* +=============== +idWindow::ReadFromSaveGame +=============== +*/ +void idWindow::ReadFromSaveGame( idFile *savefile ) { + int i; + + transitions.Clear(); + + ReadSaveGameString( cmd, savefile ); + + savefile->Read( &actualX, sizeof( actualX ) ); + savefile->Read( &actualY, sizeof( actualY ) ); + savefile->Read( &childID, sizeof( childID ) ); + savefile->Read( &flags, sizeof( flags ) ); + savefile->Read( &lastTimeRun, sizeof( lastTimeRun ) ); + savefile->Read( &drawRect, sizeof( drawRect ) ); + savefile->Read( &clientRect, sizeof( clientRect ) ); + savefile->Read( &origin, sizeof( origin ) ); +/* if ( savefile->GetFileVersion() < BUILD_NUMBER_8TH_ANNIVERSARY_1 ) { + unsigned char fontNum; + savefile->Read( &fontNum, sizeof( fontNum ) ); + font = renderSystem->RegisterFont( "" ); + }*/ + savefile->Read( &timeLine, sizeof( timeLine ) ); + savefile->Read( &xOffset, sizeof( xOffset ) ); + savefile->Read( &yOffset, sizeof( yOffset ) ); + savefile->Read( &cursor, sizeof( cursor ) ); + savefile->Read( &forceAspectWidth, sizeof( forceAspectWidth ) ); + savefile->Read( &forceAspectHeight, sizeof( forceAspectHeight ) ); + savefile->Read( &matScalex, sizeof( matScalex ) ); + savefile->Read( &matScaley, sizeof( matScaley ) ); + savefile->Read( &borderSize, sizeof( borderSize ) ); + savefile->Read( &textAlign, sizeof( textAlign ) ); + savefile->Read( &textAlignx, sizeof( textAlignx ) ); + savefile->Read( &textAligny, sizeof( textAligny ) ); + savefile->Read( &textShadow, sizeof( textShadow ) ); + savefile->Read( &shear, sizeof( shear ) ); + +// if ( savefile->GetFileVersion() >= BUILD_NUMBER_8TH_ANNIVERSARY_1 ) { + idStr fontName; + savefile->ReadString( fontName ); + font = renderSystem->RegisterFont( fontName ); +// } + + ReadSaveGameString( name, savefile ); + ReadSaveGameString( comment, savefile ); + + // WinVars + noTime.ReadFromSaveGame( savefile ); + visible.ReadFromSaveGame( savefile ); + rect.ReadFromSaveGame( savefile ); + backColor.ReadFromSaveGame( savefile ); + matColor.ReadFromSaveGame( savefile ); + foreColor.ReadFromSaveGame( savefile ); + hoverColor.ReadFromSaveGame( savefile ); + borderColor.ReadFromSaveGame( savefile ); + textScale.ReadFromSaveGame( savefile ); + noEvents.ReadFromSaveGame( savefile ); + rotate.ReadFromSaveGame( savefile ); + text.ReadFromSaveGame( savefile ); + backGroundName.ReadFromSaveGame( savefile ); + hideCursor.ReadFromSaveGame(savefile); + + // Defined Vars + for ( i = 0; i < definedVars.Num(); i++ ) { + definedVars[i]->ReadFromSaveGame( savefile ); + } + + savefile->Read( &textRect, sizeof( textRect ) ); + + // Window pointers saved as the child ID of the window + int winID = -1; + + savefile->Read( &winID, sizeof( winID ) ); + for ( i = 0; i < children.Num(); i++ ) { + if ( children[i]->childID == winID ) { + focusedChild = children[i]; + } + } + savefile->Read( &winID, sizeof( winID ) ); + for ( i = 0; i < children.Num(); i++ ) { + if ( children[i]->childID == winID ) { + captureChild = children[i]; + } + } + savefile->Read( &winID, sizeof( winID ) ); + for ( i = 0; i < children.Num(); i++ ) { + if ( children[i]->childID == winID ) { + overChild = children[i]; + } + } + + // Scripts + for ( i = 0; i < SCRIPT_COUNT; i++ ) { + if ( scripts[i] ) { + scripts[i]->ReadFromSaveGame( savefile ); + } + } + + // TimeLine Events + for ( i = 0; i < timeLineEvents.Num(); i++ ) { + if ( timeLineEvents[i] ) { + savefile->Read( &timeLineEvents[i]->pending, sizeof( timeLineEvents[i]->pending ) ); + savefile->Read( &timeLineEvents[i]->time, sizeof( timeLineEvents[i]->time ) ); + if ( timeLineEvents[i]->event ) { + timeLineEvents[i]->event->ReadFromSaveGame( savefile ); + } + } + } + + + // Transitions + int num; + savefile->Read( &num, sizeof( num ) ); + for ( i = 0; i < num; i++ ) { + idTransitionData trans; + trans.data = NULL; + ReadSaveGameTransition( trans, savefile ); + if ( trans.data ) { + transitions.Append( trans ); + } + } + + + // Named Events + for ( i = 0; i < namedEvents.Num(); i++ ) { + if ( namedEvents[i] ) { + ReadSaveGameString( namedEvents[i]->mName, savefile ); + if ( namedEvents[i]->mEvent ) { + namedEvents[i]->mEvent->ReadFromSaveGame( savefile ); + } + } + } + + // regList + regList.ReadFromSaveGame( savefile ); + + int cinematicStartTime = 0; + savefile->ReadInt( cinematicStartTime ); + if ( background ) { + background->ResetCinematicTime( cinematicStartTime ); + } + + // Read children + for ( i = 0; i < drawWindows.Num(); i++ ) { + drawWin_t window = drawWindows[i]; + + if ( window.simp ) { + window.simp->ReadFromSaveGame( savefile ); + } else if ( window.win ) { + window.win->ReadFromSaveGame( savefile ); + } + } + + if ( flags & WIN_DESKTOP ) { + FixupTransitions(); + } +} + +/* +=============== +idWindow::NumTransitions +=============== +*/ +int idWindow::NumTransitions() { + int c = transitions.Num(); + for ( int i = 0; i < children.Num(); i++ ) { + c += children[i]->NumTransitions(); + } + return c; +} + + +/* +=============== +idWindow::FixupTransitions +=============== +*/ +void idWindow::FixupTransitions() { + int i, c = transitions.Num(); + for ( i = 0; i < c; i++ ) { + drawWin_t *dw = gui->GetDesktop()->FindChildByName( ( ( idWinStr* )transitions[i].data )->c_str() ); + delete transitions[i].data; + transitions[i].data = NULL; + if ( dw != NULL && ( dw->win != NULL || dw->simp != NULL ) ){ + if ( dw->win != NULL ) { + if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->rect ) { + transitions[i].data = &dw->win->rect; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->backColor ) { + transitions[i].data = &dw->win->backColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->matColor ) { + transitions[i].data = &dw->win->matColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->foreColor ) { + transitions[i].data = &dw->win->foreColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->borderColor ) { + transitions[i].data = &dw->win->borderColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->textScale ) { + transitions[i].data = &dw->win->textScale; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->rotate ) { + transitions[i].data = &dw->win->rotate; + } + } else { + if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->rect ) { + transitions[i].data = &dw->simp->rect; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->backColor ) { + transitions[i].data = &dw->simp->backColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->matColor ) { + transitions[i].data = &dw->simp->matColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->foreColor ) { + transitions[i].data = &dw->simp->foreColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->borderColor ) { + transitions[i].data = &dw->simp->borderColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->textScale ) { + transitions[i].data = &dw->simp->textScale; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->rotate ) { + transitions[i].data = &dw->simp->rotate; + } + } + } + if ( transitions[i].data == NULL ) { + transitions.RemoveIndex( i ); + i--; + c--; + } + } + for ( c = 0; c < children.Num(); c++ ) { + children[c]->FixupTransitions(); + } +} + + +/* +=============== +idWindow::AddChild +=============== +*/ +void idWindow::AddChild(idWindow *win) { + win->childID = children.Append(win); +} + +/* +================ +idWindow::FixupParms +================ +*/ +void idWindow::FixupParms() { + int i; + int c = children.Num(); + for (i = 0; i < c; i++) { + children[i]->FixupParms(); + } + for (i = 0; i < SCRIPT_COUNT; i++) { + if (scripts[i]) { + scripts[i]->FixupParms(this); + } + } + + c = timeLineEvents.Num(); + for (i = 0; i < c; i++) { + timeLineEvents[i]->event->FixupParms(this); + } + + c = namedEvents.Num(); + for (i = 0; i < c; i++) { + namedEvents[i]->mEvent->FixupParms(this); + } + + c = ops.Num(); + for (i = 0; i < c; i++) { + if (ops[i].b == -2) { + // need to fix this up + const char *p = (const char*)(ops[i].a); + idWinVar *var = GetWinVarByName(p, true); + delete []p; + ops[i].a = (int)var; + ops[i].b = -1; + } + } + + + if (flags & WIN_DESKTOP) { + CalcRects(0,0); + } + +} + +/* +================ +idWindow::IsSimple +================ +*/ +bool idWindow::IsSimple() { + + if (ops.Num()) { + return false; + } + if (flags & (WIN_HCENTER | WIN_VCENTER)) { + return false; + } + if (children.Num() || drawWindows.Num()) { + return false; + } + for (int i = 0; i < SCRIPT_COUNT; i++) { + if (scripts[i]) { + return false; + } + } + if (timeLineEvents.Num()) { + return false; + } + + if ( namedEvents.Num() ) { + return false; + } + + return true; +} + +/* +================ +idWindow::ContainsStateVars +================ +*/ +bool idWindow::ContainsStateVars() { + if ( updateVars.Num() ) { + return true; + } + int c = children.Num(); + for (int i = 0; i < c; i++) { + if ( children[i]->ContainsStateVars() ) { + return true; + } + } + return false; +} + +/* +================ +idWindow::Interactive +================ +*/ +bool idWindow::Interactive() { + if ( scripts[ ON_ACTION ] ) { + return true; + } + int c = children.Num(); + for (int i = 0; i < c; i++) { + if (children[i]->Interactive()) { + return true; + } + } + return false; +} + +/* +================ +idWindow::SetChildWinVarVal +================ +*/ +void idWindow::SetChildWinVarVal(const char *name, const char *var, const char *val) { + drawWin_t *dw = FindChildByName(name); + idWinVar *wv = NULL; + if (dw != NULL && dw->simp != NULL) { + wv = dw->simp->GetWinVarByName(var); + } else if (dw != NULL && dw->win != NULL) { + wv = dw->win->GetWinVarByName(var); + } + if (wv) { + wv->Set(val); + wv->SetEval(false); + } +} + + +/* +================ +idWindow::FindChildByPoint + +Finds the window under the given point +================ +*/ +idWindow* idWindow::FindChildByPoint ( float x, float y, idWindow** below ) { + int c = children.Num(); + + // If we are looking for a window below this one then + // the next window should be good, but this one wasnt it + if ( *below == this ) { + *below = NULL; + return NULL; + } + + if ( !Contains ( drawRect, x, y ) ) { + return NULL; + } + + for (int i = c - 1; i >= 0 ; i-- ) { + idWindow* found = children[i]->FindChildByPoint ( x, y, below ); + if ( found ) { + if ( *below ) { + continue; + } + + return found; + } + } + + return this; +} + +/* +================ +idWindow::FindChildByPoint +================ +*/ +idWindow* idWindow::FindChildByPoint ( float x, float y, idWindow* below ) +{ + return FindChildByPoint ( x, y, &below ); +} + +/* +================ +idWindow::GetChildCount + +Returns the number of children +================ +*/ +int idWindow::GetChildCount () +{ + return drawWindows.Num ( ); +} + +/* +================ +idWindow::GetChild + +Returns the child window at the given index +================ +*/ +idWindow* idWindow::GetChild ( int index ) +{ + return drawWindows[index].win; +} + +/* +================ +idWindow::GetChildIndex + +Returns the index of the given child window +================ +*/ +int idWindow::GetChildIndex ( idWindow* window ) { + int find; + for ( find = 0; find < drawWindows.Num(); find ++ ) { + if ( drawWindows[find].win == window ) { + return find; + } + } + return -1; +} + +/* +================ +idWindow::RemoveChild + +Removes the child from the list of children. Note that the child window being +removed must still be deallocated by the caller +================ +*/ +void idWindow::RemoveChild ( idWindow *win ) { + int find; + + // Remove the child window + children.Remove ( win ); + + for ( find = 0; find < drawWindows.Num(); find ++ ) + { + if ( drawWindows[find].win == win ) + { + drawWindows.RemoveIndex ( find ); + break; + } + } +} + +/* +================ +idWindow::InsertChild + +Inserts the given window as a child into the given location in the zorder. +================ +*/ +bool idWindow::InsertChild ( idWindow *win, idWindow* before ) +{ + AddChild ( win ); + + win->parent = this; + + drawWin_t dwt; + dwt.simp = NULL; + dwt.win = win; + + // If not inserting before anything then just add it at the end + if ( before ) { + int index; + index = GetChildIndex ( before ); + if ( index != -1 ) { + drawWindows.Insert ( dwt, index ); + return true; + } + } + + drawWindows.Append ( dwt ); + return true; +} + +/* +================ +idWindow::ScreenToClient +================ +*/ +void idWindow::ScreenToClient ( idRectangle* r ) { + int x; + int y; + idWindow* p; + + for ( p = this, x = 0, y = 0; p; p = p->parent ) { + x += p->rect.x(); + y += p->rect.y(); + } + + r->x -= x; + r->y -= y; +} + +/* +================ +idWindow::ClientToScreen +================ +*/ +void idWindow::ClientToScreen ( idRectangle* r ) { + int x; + int y; + idWindow* p; + + for ( p = this, x = 0, y = 0; p; p = p->parent ) { + x += p->rect.x(); + y += p->rect.y(); + } + + r->x += x; + r->y += y; +} + +/* +================ +idWindow::SetDefaults + +Set the window do a default window with no text, no background and +default colors, etc.. +================ +*/ +void idWindow::SetDefaults () { + forceAspectWidth = 640.0f; + forceAspectHeight = 480.0f; + matScalex = 1; + matScaley = 1; + borderSize = 0; + noTime = false; + visible = true; + textAlign = 0; + textAlignx = 0; + textAligny = 0; + noEvents = false; + rotate = 0; + shear.Zero(); + textScale = 0.35f; + backColor.Zero(); + foreColor = idVec4(1, 1, 1, 1); + hoverColor = idVec4(1, 1, 1, 1); + matColor = idVec4(1, 1, 1, 1); + borderColor.Zero(); + text = ""; + + background = NULL; + backGroundName = ""; +} + +/* +================ +idWindow::UpdateFromDictionary + +The editor only has a dictionary to work with so the easiest way to push the +values of the dictionary onto the window is for the window to interpret the +dictionary as if were a file being parsed. +================ +*/ +bool idWindow::UpdateFromDictionary ( idDict& dict ) { + const idKeyValue* kv; + int i; + + SetDefaults ( ); + + // Clear all registers since they will get recreated + regList.Reset ( ); + expressionRegisters.Clear ( ); + ops.Clear ( ); + + for ( i = 0; i < dict.GetNumKeyVals(); i ++ ) { + kv = dict.GetKeyVal ( i ); + + // Special case name + if ( !kv->GetKey().Icmp ( "name" ) ) { + name = kv->GetValue(); + continue; + } + + idParser src( kv->GetValue().c_str(), kv->GetValue().Length(), "", + LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + idTokenParser src2; + src2.LoadFromParser( src, "temp" ); + src2.StartParsing( "temp" ); + if ( !ParseInternalVar ( kv->GetKey(), &src2 ) ) { + // Kill the old register since the parse reg entry will add a new one + if ( !ParseRegEntry ( kv->GetKey(), &src2 ) ) { + continue; + } + } + } + + EvalRegs(-1, true); + + SetupFromState(); + PostParse(); + + return true; +} diff --git a/neo/ui/Window.h b/neo/ui/Window.h new file mode 100644 index 00000000..6f6c38f4 --- /dev/null +++ b/neo/ui/Window.h @@ -0,0 +1,450 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __WINDOW_H__ +#define __WINDOW_H__ + +#include "Rectangle.h" +#include "DeviceContext.h" +#include "RegExp.h" +#include "Winvar.h" +#include "GuiScript.h" +#include "SimpleWindow.h" + +const int WIN_CHILD = 0x00000001; +const int WIN_CAPTION = 0x00000002; +const int WIN_BORDER = 0x00000004; +const int WIN_SIZABLE = 0x00000008; +const int WIN_MOVABLE = 0x00000010; +const int WIN_FOCUS = 0x00000020; +const int WIN_CAPTURE = 0x00000040; +const int WIN_HCENTER = 0x00000080; +const int WIN_VCENTER = 0x00000100; +const int WIN_MODAL = 0x00000200; +const int WIN_INTRANSITION = 0x00000400; +const int WIN_CANFOCUS = 0x00000800; +const int WIN_SELECTED = 0x00001000; +const int WIN_TRANSFORM = 0x00002000; +const int WIN_HOLDCAPTURE = 0x00004000; +const int WIN_NOWRAP = 0x00008000; +const int WIN_NOCLIP = 0x00010000; +const int WIN_INVERTRECT = 0x00020000; +const int WIN_NATURALMAT = 0x00040000; +const int WIN_NOCURSOR = 0x00080000; +const int WIN_MENUGUI = 0x00100000; +const int WIN_ACTIVE = 0x00200000; +const int WIN_SHOWCOORDS = 0x00400000; +const int WIN_SHOWTIME = 0x00800000; +const int WIN_WANTENTER = 0x01000000; + +const int WIN_DESKTOP = 0x10000000; + +const char CAPTION_HEIGHT[] = "16.0"; +const char SCROLLER_SIZE[] = "16.0"; +const int SCROLLBAR_SIZE = 16; + +const int MAX_WINDOW_NAME = 32; +const int MAX_LIST_ITEMS = 1024; + +const char DEFAULT_BACKCOLOR[] = "1 1 1 1"; +const char DEFAULT_FORECOLOR[] = "0 0 0 1"; +const char DEFAULT_BORDERCOLOR[] = "0 0 0 1"; +const char DEFAULT_TEXTSCALE[] = "0.4"; + +typedef enum { + WOP_TYPE_ADD, + WOP_TYPE_SUBTRACT, + WOP_TYPE_MULTIPLY, + WOP_TYPE_DIVIDE, + WOP_TYPE_MOD, + WOP_TYPE_TABLE, + WOP_TYPE_GT, + WOP_TYPE_GE, + WOP_TYPE_LT, + WOP_TYPE_LE, + WOP_TYPE_EQ, + WOP_TYPE_NE, + WOP_TYPE_AND, + WOP_TYPE_OR, + WOP_TYPE_VAR, + WOP_TYPE_VARS, + WOP_TYPE_VARF, + WOP_TYPE_VARI, + WOP_TYPE_VARB, + WOP_TYPE_COND +} wexpOpType_t; + +typedef enum { + WEXP_REG_TIME, + WEXP_REG_NUM_PREDEFINED +} wexpRegister_t; + +typedef struct { + wexpOpType_t opType; + int a, b, c, d; +} wexpOp_t; + +struct idRegEntry { + const char *name; + idRegister::REGTYPE type; + int index; +}; + + +class rvGEWindowWrapper; +class idWindow; + +struct idTimeLineEvent { + idTimeLineEvent() { + event = new (TAG_OLD_UI) idGuiScriptList; + } + ~idTimeLineEvent() { + delete event; + } + int time; + idGuiScriptList *event; + bool pending; + size_t Size() { + return sizeof(*this) + event->Size(); + } +}; + +class rvNamedEvent +{ +public: + + rvNamedEvent(const char* name) + { + mEvent = new (TAG_OLD_UI) idGuiScriptList; + mName = name; + } + ~rvNamedEvent() + { + delete mEvent; + } + size_t Size() + { + return sizeof(*this) + mEvent->Size(); + } + + idStr mName; + idGuiScriptList* mEvent; +}; + +struct idTransitionData { + idWinVar *data; + int offset; + idInterpolateAccelDecelLinear interp; +}; + + +class idUserInterfaceLocal; +class idWindow { +public: + idWindow(idUserInterfaceLocal *gui); + virtual ~idWindow(); + + enum { + ON_MOUSEENTER = 0, + ON_MOUSEEXIT, + ON_ACTION, + ON_ACTIVATE, + ON_DEACTIVATE, + ON_ESC, + ON_FRAME, + ON_TRIGGER, + ON_ACTIONRELEASE, + ON_ENTER, + ON_ENTERRELEASE, + SCRIPT_COUNT + }; + + enum { + ADJUST_MOVE = 0, + ADJUST_TOP, + ADJUST_RIGHT, + ADJUST_BOTTOM, + ADJUST_LEFT, + ADJUST_TOPLEFT, + ADJUST_BOTTOMRIGHT, + ADJUST_TOPRIGHT, + ADJUST_BOTTOMLEFT + }; + + static const char *ScriptNames[SCRIPT_COUNT]; + + static const idRegEntry RegisterVars[]; + static const int NumRegisterVars; + + idWindow *SetFocus(idWindow *w, bool scripts = true); + + idWindow *SetCapture(idWindow *w); + void SetParent(idWindow *w); + void SetFlag(unsigned int f); + void ClearFlag(unsigned int f); + unsigned GetFlags() {return flags;}; + void Move(float x, float y); + void BringToTop(idWindow *w); + void Adjust(float xd, float yd); + void SetAdjustMode(idWindow *child); + void Size(float x, float y, float w, float h); + void SetupFromState(); + void SetupBackground(); + drawWin_t *FindChildByName(const char *name); + idSimpleWindow *FindSimpleWinByName(const char *_name); + idWindow *GetParent() { return parent; } + idUserInterfaceLocal *GetGui() {return gui;}; + bool Contains(float x, float y); + size_t Size(); + virtual size_t Allocated(); + idStr* GetStrPtrByName(const char *_name); + + virtual idWinVar *GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + int GetWinVarOffset( idWinVar *wv, drawWin_t *dw ); + float GetMaxCharHeight(); + float GetMaxCharWidth(); + void SetFont(); + void SetInitialState(const char *_name); + void AddChild(idWindow *win); + void DebugDraw(int time, float x, float y); + void CalcClientRect(float xofs, float yofs); + void CommonInit(); + void CleanUp(); + void DrawBorderAndCaption(const idRectangle &drawRect); + void DrawCaption(int time, float x, float y); + void SetupTransforms(float x, float y); + bool Contains(const idRectangle &sr, float x, float y); + const char *GetName() { return name; }; + + virtual bool Parse( idTokenParser *src, bool rebuild = true); + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + void CalcRects(float x, float y); + virtual void Redraw(float x, float y, bool hud); + + virtual void ArchiveToDictionary(idDict *dict, bool useNames = true); + virtual void InitFromDictionary(idDict *dict, bool byName = true); + virtual void PostParse(); + virtual void Activate( bool activate, idStr &act ); + virtual void Trigger(); + virtual void GainFocus(); + virtual void LoseFocus(); + virtual void GainCapture(); + virtual void LoseCapture(); + virtual void Sized(); + virtual void Moved(); + virtual void Draw(int time, float x, float y); + virtual void MouseExit(); + virtual void MouseEnter(); + virtual void DrawBackground(const idRectangle &drawRect); + virtual idWindow * GetChildWithOnAction( float xd, float yd ); + virtual const char *RouteMouseCoords(float xd, float yd); + virtual void SetBuddy(idWindow *buddy) {}; + virtual void HandleBuddyUpdate(idWindow *buddy) {}; + virtual void StateChanged( bool redraw ); + virtual void ReadFromDemoFile( class idDemoFile *f, bool rebuild = true ); + virtual void WriteToDemoFile( class idDemoFile *f ); + + // SaveGame support + void WriteSaveGameString( const char *string, idFile *savefile ); + void WriteSaveGameTransition( idTransitionData &trans, idFile *savefile ); + virtual void WriteToSaveGame( idFile *savefile ); + void ReadSaveGameString( idStr &string, idFile *savefile ); + void ReadSaveGameTransition( idTransitionData & trans, idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + void FixupTransitions(); + virtual void HasAction(){}; + virtual void HasScripts(){}; + + void FixupParms(); + void GetScriptString(const char *name, idStr &out); + void SetScriptParams(); + bool HasOps() { return (ops.Num() > 0); }; + float EvalRegs(int test = -1, bool force = false); + void StartTransition(); + void AddTransition(idWinVar *dest, idVec4 from, idVec4 to, int time, float accelTime, float decelTime); + void ResetTime(int time); + void ResetCinematics(); + + int NumTransitions(); + + bool ParseScript(idTokenParser *src, idGuiScriptList &list, int *timeParm = NULL, bool allowIf = false); + bool RunScript(int n); + bool RunScriptList(idGuiScriptList *src); + void SetRegs(const char *key, const char *val); + int ParseExpression( idTokenParser *src, idWinVar *var = NULL, int component = 0 ); + int ExpressionConstant(float f); + idRegisterList *RegList() { return ®List; } + void AddCommand(const char *cmd); + void AddUpdateVar(idWinVar *var); + bool Interactive(); + bool ContainsStateVars(); + void SetChildWinVarVal(const char *name, const char *var, const char *val); + idWindow *GetFocusedChild(); + idWindow *GetCaptureChild(); + const char *GetComment() { return comment; } + void SetComment( const char * p) { comment = p; } + + idStr cmd; + + virtual void RunNamedEvent ( const char* eventName ); + + void AddDefinedVar ( idWinVar* var ); + + idWindow* FindChildByPoint ( float x, float y, idWindow* below = NULL ); + int GetChildIndex ( idWindow* window ); + int GetChildCount (); + idWindow* GetChild ( int index ); + void RemoveChild ( idWindow *win ); + bool InsertChild ( idWindow *win, idWindow* before ); + + void ScreenToClient ( idRectangle* rect ); + void ClientToScreen ( idRectangle* rect ); + + bool UpdateFromDictionary ( idDict& dict ); + +protected: + + friend class rvGEWindowWrapper; + + idWindow* FindChildByPoint ( float x, float y, idWindow** below ); + void SetDefaults (); + + friend class idSimpleWindow; + friend class idUserInterfaceLocal; + bool IsSimple(); + void UpdateWinVars(); + void DisableRegister(const char *_name); + void Transition(); + void Time(); + bool RunTimeEvents(int time); + void Dump(); + + int ExpressionTemporary(); + wexpOp_t *ExpressionOp(); + int EmitOp( int a, int b, wexpOpType_t opType, wexpOp_t **opp = NULL ); + int ParseEmitOp( idTokenParser *src, int a, wexpOpType_t opType, int priority, wexpOp_t **opp = NULL ); + int ParseTerm( idTokenParser *src, idWinVar *var = NULL, int component = 0 ); + int ParseExpressionPriority( idTokenParser *src, int priority, idWinVar *var = NULL, int component = 0 ); + void EvaluateRegisters(float *registers); + void SaveExpressionParseState(); + void RestoreExpressionParseState(); + void ParseBracedExpression(idTokenParser *src); + bool ParseScriptEntry(const char *name, idTokenParser *src); + bool ParseRegEntry(const char *name, idTokenParser *src); + virtual bool ParseInternalVar(const char *name, idTokenParser *src); + void ParseString(idTokenParser *src, idStr &out); + void ParseVec4(idTokenParser *src, idVec4 &out); + void ConvertRegEntry(const char *name, idTokenParser *src, idStr &out, int tabs); + + float actualX; // physical coords + float actualY; // '' + int childID; // this childs id + unsigned int flags; // visible, focus, mouseover, cursor, border, etc.. + int lastTimeRun; // + idRectangle drawRect; // overall rect + idRectangle clientRect; // client area + idVec2 origin; + + int timeLine; // time stamp used for various fx + float xOffset; + float yOffset; + float forceAspectWidth; + float forceAspectHeight; + float matScalex; + float matScaley; + float borderSize; + float textAlignx; + float textAligny; + idStr name; + idStr comment; + idVec2 shear; + + class idFont * font; + signed char textShadow; + unsigned char cursor; // + signed char textAlign; + + idWinBool noTime; // + idWinBool visible; // + idWinBool noEvents; + idWinRectangle rect; // overall rect + idWinVec4 backColor; + idWinVec4 matColor; + idWinVec4 foreColor; + idWinVec4 hoverColor; + idWinVec4 borderColor; + idWinFloat textScale; + idWinFloat rotate; + idWinStr text; + idWinBackground backGroundName; // + + idList definedVars; + idList updateVars; + + idRectangle textRect; // text extented rect + const idMaterial *background; // background asset + + idWindow *parent; // parent window + idList children; // child windows + idList drawWindows; + + idWindow *focusedChild; // if a child window has the focus + idWindow *captureChild; // if a child window has mouse capture + idWindow *overChild; // if a child window has mouse capture + bool hover; + + idUserInterfaceLocal *gui; + + static idCVar gui_debug; + static idCVar gui_edit; + + idGuiScriptList *scripts[SCRIPT_COUNT]; + bool *saveTemps; + + idList timeLineEvents; + idList transitions; + + static bool registerIsTemporary[MAX_EXPRESSION_REGISTERS]; // statics to assist during parsing + + idList ops; // evaluate to make expressionRegisters + idList expressionRegisters; + idList *saveOps; // evaluate to make expressionRegisters + idList namedEvents; // added named events + idList *saveRegs; + + idRegisterList regList; + + idWinBool hideCursor; +}; + +ID_INLINE void idWindow::AddDefinedVar( idWinVar* var ) { + definedVars.AddUnique( var ); +} + +#endif /* !__WINDOW_H__ */ diff --git a/neo/ui/Winvar.cpp b/neo/ui/Winvar.cpp new file mode 100644 index 00000000..8641df55 --- /dev/null +++ b/neo/ui/Winvar.cpp @@ -0,0 +1,83 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma hdrstop +#include "../idlib/precompiled.h" + +#include "Window.h" +#include "Winvar.h" +#include "UserInterfaceLocal.h" + +idWinVar::idWinVar() { + guiDict = NULL; + name = NULL; + eval = true; +} + +idWinVar::~idWinVar() { + delete name; + name = NULL; +} + +void idWinVar::SetGuiInfo(idDict *gd, const char *_name) { + guiDict = gd; + SetName(_name); +} + + +void idWinVar::Init(const char *_name, idWindow *win) { + idStr key = _name; + guiDict = NULL; + int len = key.Length(); + if (len > 5 && key[0] == 'g' && key[1] == 'u' && key[2] == 'i' && key[3] == ':') { + key = key.Right(len - VAR_GUIPREFIX_LEN); + SetGuiInfo( win->GetGui()->GetStateDict(), key ); + win->AddUpdateVar(this); + } else { + Set(_name); + } +} + +void idMultiWinVar::Set( const char *val ) { + for ( int i = 0; i < Num(); i++ ) { + (*this)[i]->Set( val ); + } +} + +void idMultiWinVar::Update() { + for ( int i = 0; i < Num(); i++ ) { + (*this)[i]->Update(); + } +} + +void idMultiWinVar::SetGuiInfo( idDict *dict ) { + for ( int i = 0; i < Num(); i++ ) { + (*this)[i]->SetGuiInfo( dict, (*this)[i]->c_str() ); + } +} + diff --git a/neo/ui/Winvar.h b/neo/ui/Winvar.h new file mode 100644 index 00000000..d2319416 --- /dev/null +++ b/neo/ui/Winvar.h @@ -0,0 +1,861 @@ +/* +=========================================================================== + +Doom 3 BFG Edition GPL Source Code +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). + +Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 BFG Edition Source Code. If not, see . + +In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#ifndef __WINVAR_H__ +#define __WINVAR_H__ + +#include "Rectangle.h" + +static const char *VAR_GUIPREFIX = "gui::"; +static const int VAR_GUIPREFIX_LEN = strlen(VAR_GUIPREFIX); + +class idWindow; +class idWinVar { +public: + idWinVar(); + virtual ~idWinVar(); + + void SetGuiInfo(idDict *gd, const char *_name); + const char *GetName() const { + if (name) { + if (guiDict && *name == '*') { + return guiDict->GetString(&name[1]); + } + return name; + } + return ""; + } + void SetName(const char *_name) { + delete []name; + name = NULL; + if (_name) { + name = new (TAG_OLD_UI) char[strlen(_name)+1]; + strcpy(name, _name); + } + } + + idWinVar &operator=( const idWinVar &other ) { + guiDict = other.guiDict; + SetName(other.name); + return *this; + } + + idDict *GetDict() const { return guiDict; } + bool NeedsUpdate() { return (guiDict != NULL); } + + virtual void Init(const char *_name, idWindow* win) = 0; + virtual void Set(const char *val) = 0; + virtual void Update() = 0; + virtual const char *c_str() const = 0; + virtual size_t Size() { size_t sz = (name) ? strlen(name) : 0; return sz + sizeof(*this); } + + virtual void WriteToSaveGame( idFile *savefile ) = 0; + virtual void ReadFromSaveGame( idFile *savefile ) = 0; + + virtual float x() const = 0; + + void SetEval(bool b) { + eval = b; + } + bool GetEval() { + return eval; + } + +protected: + idDict *guiDict; + char *name; + bool eval; +}; + +class idWinBool : public idWinVar { +public: + idWinBool() : idWinVar() {}; + ~idWinBool() {}; + virtual void Init(const char *_name, idWindow *win) { idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetBool(GetName()); + } + } + int operator==( const bool &other ) { return (other == data); } + bool &operator=( const bool &other ) { + data = other; + if (guiDict) { + guiDict->SetBool(GetName(), data); + } + return data; + } + idWinBool &operator=( const idWinBool &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + + operator bool() const { return data; } + + virtual void Set(const char *val) { + data = ( atoi( val ) != 0 ); + if (guiDict) { + guiDict->SetBool(GetName(), data); + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetBool( s ); + } + } + + virtual const char *c_str() const {return va("%i", data); } + + // SaveGames + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + + virtual float x() const { return data ? 1.0f : 0.0f; }; + +protected: + bool data; +}; + +class idWinStr : public idWinVar { +public: + idWinStr() : idWinVar() {}; + ~idWinStr() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + const char * name = GetName(); + if ( name[0] == 0 ) { + data = ""; + } else { + data = guiDict->GetString( name ); + } + } + } + int operator==( const idStr &other ) const { + return (other == data); + } + int operator==( const char *other ) const { + return (data == other); + } + idStr &operator=( const idStr &other ) { + data = other; + if (guiDict) { + guiDict->Set(GetName(), data); + } + return data; + } + idWinStr &operator=( const idWinStr &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + operator const char *() const { + return data.c_str(); + } + operator const idStr &() const { + return data; + } + int LengthWithoutColors() { + if (guiDict && name && *name) { + data = guiDict->GetString(GetName()); + } + return data.LengthWithoutColors(); + } + int Length() { + if (guiDict && name && *name) { + data = guiDict->GetString(GetName()); + } + return data.Length(); + } + void RemoveColors() { + if (guiDict && name && *name) { + data = guiDict->GetString(GetName()); + } + data.RemoveColors(); + } + virtual const char *c_str() const { + return data.c_str(); + } + + virtual void Set(const char *val) { + data = val; + if ( guiDict ) { + guiDict->Set(GetName(), data); + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetString( s ); + } + } + + virtual size_t Size() { + size_t sz = idWinVar::Size(); + return sz +data.Allocated(); + } + + // SaveGames + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + + int len = data.Length(); + savefile->Write( &len, sizeof( len ) ); + if ( len > 0 ) { + savefile->Write( data.c_str(), len ); + } + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + + int len; + savefile->Read( &len, sizeof( len ) ); + if ( len > 0 ) { + data.Fill( ' ', len ); + savefile->Read( &data[0], len ); + } + } + + // return wether string is emtpy + virtual float x() const { return data[0] ? 1.0f : 0.0f; }; + +protected: + idStr data; +}; + +class idWinInt : public idWinVar { +public: + idWinInt() : idWinVar() {}; + ~idWinInt() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetInt(GetName()); + } + } + int &operator=( const int &other ) { + data = other; + if (guiDict) { + guiDict->SetInt(GetName(), data); + } + return data; + } + idWinInt &operator=( const idWinInt &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + operator int () const { + return data; + } + virtual void Set(const char *val) { + data = atoi(val);; + if (guiDict) { + guiDict->SetInt(GetName(), data); + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetInt( s ); + } + } + virtual const char *c_str() const { + return va("%i", data); + } + + // SaveGames + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + + // no suitable conversion + virtual float x() const { assert( false ); return 0.0f; }; + +protected: + int data; +}; + +class idWinFloat : public idWinVar { +public: + idWinFloat() : idWinVar() {}; + ~idWinFloat() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetFloat(GetName()); + } + } + idWinFloat &operator=( const idWinFloat &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + float &operator=( const float &other ) { + data = other; + if (guiDict) { + guiDict->SetFloat(GetName(), data); + } + return data; + } + operator float() const { + return data; + } + virtual void Set(const char *val) { + data = atof(val); + if (guiDict) { + guiDict->SetFloat(GetName(), data); + } + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetFloat( s ); + } + } + virtual const char *c_str() const { + return va("%f", data); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + + virtual float x() const { return data; }; +protected: + float data; +}; + +class idWinRectangle : public idWinVar { +public: + idWinRectangle() : idWinVar() {}; + ~idWinRectangle() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + idVec4 v = guiDict->GetVec4(GetName()); + data.x = v.x; + data.y = v.y; + data.w = v.z; + data.h = v.w; + } + } + + int operator==( const idRectangle &other ) const { + return (other == data); + } + + idWinRectangle &operator=( const idWinRectangle &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + idRectangle &operator=( const idVec4 &other ) { + data = other; + if (guiDict) { + guiDict->SetVec4(GetName(), other); + } + return data; + } + + idRectangle &operator=( const idRectangle &other ) { + data = other; + if (guiDict) { + idVec4 v = data.ToVec4(); + guiDict->SetVec4(GetName(), v); + } + return data; + } + + operator const idRectangle&() const { + return data; + } + + float x() const { + return data.x; + } + float y() const { + return data.y; + } + float w() const { + return data.w; + } + float h() const { + return data.h; + } + float Right() const { + return data.Right(); + } + float Bottom() const { + return data.Bottom(); + } + idVec4 &ToVec4() { + static idVec4 ret; + ret = data.ToVec4(); + return ret; + } + virtual void Set(const char *val) { + if ( strchr ( val, ',' ) ) { + sscanf( val, "%f,%f,%f,%f", &data.x, &data.y, &data.w, &data.h ); + } else { + sscanf( val, "%f %f %f %f", &data.x, &data.y, &data.w, &data.h ); + } + if (guiDict) { + idVec4 v = data.ToVec4(); + guiDict->SetVec4(GetName(), v); + } + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + idVec4 v = guiDict->GetVec4( s ); + data.x = v.x; + data.y = v.y; + data.w = v.z; + data.h = v.w; + } + } + + virtual const char *c_str() const { + return data.ToVec4().ToString(); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idRectangle data; +}; + +class idWinVec2 : public idWinVar { +public: + idWinVec2() : idWinVar() {}; + ~idWinVec2() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetVec2(GetName()); + } + } + int operator==( const idVec2 &other ) const { + return (other == data); + } + idWinVec2 &operator=( const idWinVec2 &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + + idVec2 &operator=( const idVec2 &other ) { + data = other; + if (guiDict) { + guiDict->SetVec2(GetName(), data); + } + return data; + } + float x() const { + return data.x; + } + float y() const { + return data.y; + } + virtual void Set(const char *val) { + if ( strchr ( val, ',' ) ) { + sscanf( val, "%f,%f", &data.x, &data.y ); + } else { + sscanf( val, "%f %f", &data.x, &data.y); + } + if (guiDict) { + guiDict->SetVec2(GetName(), data); + } + } + operator const idVec2&() const { + return data; + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetVec2( s ); + } + } + virtual const char *c_str() const { + return data.ToString(); + } + void Zero() { + data.Zero(); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idVec2 data; +}; + +class idWinVec4 : public idWinVar { +public: + idWinVec4() : idWinVar() {}; + ~idWinVec4() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetVec4(GetName()); + } + } + int operator==( const idVec4 &other ) const { + return (other == data); + } + idWinVec4 &operator=( const idWinVec4 &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + idVec4 &operator=( const idVec4 &other ) { + data = other; + if (guiDict) { + guiDict->SetVec4(GetName(), data); + } + return data; + } + operator const idVec4&() const { + return data; + } + + float x() const { + return data.x; + } + + float y() const { + return data.y; + } + + float z() const { + return data.z; + } + + float w() const { + return data.w; + } + virtual void Set(const char *val) { + if ( strchr ( val, ',' ) ) { + sscanf( val, "%f,%f,%f,%f", &data.x, &data.y, &data.z, &data.w ); + } else { + sscanf( val, "%f %f %f %f", &data.x, &data.y, &data.z, &data.w); + } + if ( guiDict ) { + guiDict->SetVec4( GetName(), data ); + } + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetVec4( s ); + } + } + virtual const char *c_str() const { + return data.ToString(); + } + + void Zero() { + data.Zero(); + if ( guiDict ) { + guiDict->SetVec4(GetName(), data); + } + } + + const idVec3 &ToVec3() const { + return data.ToVec3(); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idVec4 data; +}; + +class idWinVec3 : public idWinVar { +public: + idWinVec3() : idWinVar() {}; + ~idWinVec3() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetVector(GetName()); + } + } + int operator==( const idVec3 &other ) const { + return (other == data); + } + idWinVec3 &operator=( const idWinVec3 &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + idVec3 &operator=( const idVec3 &other ) { + data = other; + if (guiDict) { + guiDict->SetVector(GetName(), data); + } + return data; + } + operator const idVec3&() const { + return data; + } + + float x() const { + return data.x; + } + + float y() const { + return data.y; + } + + float z() const { + return data.z; + } + + virtual void Set(const char *val) { + sscanf( val, "%f %f %f", &data.x, &data.y, &data.z); + if (guiDict) { + guiDict->SetVector(GetName(), data); + } + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetVector( s ); + } + } + virtual const char *c_str() const { + return data.ToString(); + } + + void Zero() { + data.Zero(); + if (guiDict) { + guiDict->SetVector(GetName(), data); + } + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idVec3 data; +}; + +class idWinBackground : public idWinStr { +public: + idWinBackground() : idWinStr() { + mat = NULL; + }; + ~idWinBackground() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinStr::Init(_name, win); + if (guiDict) { + data = guiDict->GetString(GetName()); + } + } + int operator==( const idStr &other ) const { + return (other == data); + } + int operator==( const char *other ) const { + return (data == other); + } + idStr &operator=( const idStr &other ) { + data = other; + if (guiDict) { + guiDict->Set(GetName(), data); + } + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + return data; + } + idWinBackground &operator=( const idWinBackground &other ) { + idWinVar::operator=(other); + data = other.data; + mat = other.mat; + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + return *this; + } + operator const char *() const { + return data.c_str(); + } + operator const idStr &() const { + return data; + } + int Length() { + if (guiDict) { + data = guiDict->GetString(GetName()); + } + return data.Length(); + } + virtual const char *c_str() const { + return data.c_str(); + } + + virtual void Set(const char *val) { + data = val; + if (guiDict) { + guiDict->Set(GetName(), data); + } + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetString( s ); + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + } + } + + virtual size_t Size() { + size_t sz = idWinVar::Size(); + return sz +data.Allocated(); + } + + void SetMaterialPtr( const idMaterial **m ) { + mat = m; + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + + int len = data.Length(); + savefile->Write( &len, sizeof( len ) ); + if ( len > 0 ) { + savefile->Write( data.c_str(), len ); + } + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + + int len; + savefile->Read( &len, sizeof( len ) ); + if ( len > 0 ) { + data.Fill( ' ', len ); + savefile->Read( &data[0], len ); + } + if ( mat ) { + if ( len > 0 ) { + (*mat) = declManager->FindMaterial( data ); + } else { + (*mat) = NULL; + } + } + } + +protected: + idStr data; + const idMaterial **mat; +}; + +/* +================ +idMultiWinVar +multiplexes access to a list if idWinVar* +================ +*/ +class idMultiWinVar : public idList< idWinVar * > { +public: + void Set( const char *val ); + void Update(); + void SetGuiInfo( idDict *dict ); +}; + +#endif /* !__WINVAR_H__ */ +